Questions about this topic? Sign up to ask in the talk tab.

Assembly Basics

From NetSec
Revision as of 01:43, 23 November 2011 by LashawnSeccombe (Talk | contribs) (Overflows)

Jump to: navigation, search

Basic Assembly

Preface

Understanding of assembly and stack growth are important for the machine coder. In assembly, there are a limited amount of temporary variables, also called registers, with only the stack or heap to contain additional application data. There is a limited number of instructions that can be issued to a processor. Understand that at this "low level", this close to the machine, we are designing code to execute processor-specific sequences. This would imply that it is possible to write cross-operating-system shellcoded applications, also known as flat binaries, or flat binary applications.

Because of the limitations of most shellcoding and overflow tutorials, I have chosen to document both intel standard syntax and some AT&T System V syntax for the techniques described by Security 101.

Intel Syntax and AT&T System V syntax are syntax standards for coding assembly. The two syntaxes are both translated into the same machine code, however they are translated from machine code to human-readable code differently. On Windows XP SP2 systems, we will be demonstrating our code in Intel 32 Bit Standard Syntax. On Linux and UNIX systems, we will be demonstrating our code in AT&T System V syntax, of course for the intel 32 bit processor.

On the low level, a machine is actually very simple. A processor is able to understand absolute hexadecimal values, then add, subtract, multiply, and divide them by whatever it is asked to. Other than that, the functionality of a processor is mostly limited to memory operations and execution procedures, such as the management of functions and stack growth.

The C programming language is more or less assembly with a bit more english strapped to it. For now, that's all I'll describe in reference to the C programming language. More details are included later on in Security 101.

Instructions & Concepts

So, now we have a good framework of the concepts of assembly language, but how can we see them be utilized to actually do something? Well, we'll start with explaining a bit about registers. General purpose registers on an intel 32 bit processor are 4 bytes in length, also referred to as a "double word" or "DWORD". A Byte consists of 8 bits. It takes four bits to compose a hexadecimal digit, or a nybble.

Your general-purpose registers are eax, ecx, ebx, edi, and edx. Other, non general purpose registers are esi, esp, ebp, and eip. So lets test out a few small instructions to get the hang of this. In intel syntax, to put the value of 1 into eax, we will say

<syntaxhighlight lang="asm">mov eax, 1</syntaxhighlight>

In AT&T System V syntax, we would say

<syntaxhighlight lang="asm">movl $1, %eax</syntaxhighlight>

I'll explain this quite quickly for you. Intel syntax uses the format:

 [instruction] [destination],[source]

Whereas AT&T System V syntax uses the syntax format:

 [instruction] [source],[destination]

Now in our small amount of code, eax = 1. Say we want to add 3 to eax, we would say:

Intel:

<syntaxhighlight lang="asm">add eax, 3</syntaxhighlight>

AT&T:

<syntaxhighlight lang="asm">addl $3, %eax</syntaxhighlight>

This is the simple explanation of some instructions. A full list of instructions which we will be using and a table of their purpose is as follows :

General Purpose Instructions
Instruction Purpose
ADD Adds value to register
SUB Subtracts value from register
IMUL Multiplies value into register
IDIV Divides value into register
MOV Places value into register
CMP Compare register with value
INC Increment specified register
DEC Decrement specified register
Special Instructions
Instruction Description
CALL Calls a function
JMP Jumps to a different segment of code
RET Returns from function
XOR Bitwise exclusive or
PUSH Push something onto the stack
POP Pop the value off the stack and place it in a register
Special Jumps
Instruction Description
JNE Jump if not equal
JE Jump if equal
JGE Jump if greater than or equal to >=
JLE Jump if less than or equal to >=
JL Jump if less than
JG Jump if greater than
JZ Jump if ZFLAG set
JNZ Jump if ZFLAG not set
JP Jump with parity
JNP Jump if no parity

The Stack

So obviously you'll want me to go over these so-called 'special instructions' and the 'special jumps' as soon as possible. Well lets look through this. First we need to get a solid understanding of functions, and then we also need to get a solid understanding of stack-based operations. The execution stack and the data stack are two different things and it must be understood as such.

When an application loads into memory, it begins at what is referred to as the "entry point" of the binary. Program execution then occurs in increasing memory addresses. Memory addresses are stored in DWORD values. Let us suppose that the entry point to app.exe is 0x101c879d. Let's suppose for a moment that the code there is mov eax, 1, and the next segment of instruction is add eax, 3. The program would look something like the following in memory:


Memory Address Machine Code Assembly Instructions
0x101c879d \xb8\x01\x00\x00\x00 <syntaxhighlight lang="asm">mov eax, 1</syntaxhighlight>
0x101c87a2 \x83\xc0\x03 <syntaxhighlight lang="asm">add eax, 3</syntaxhighlight>


Binary & Hexadecimal

Counting

In case we missed something here, lets go over a bit of binary and a little bit of hexadecimal. A nybble consists of 4 bits, a half-byte, or one hexadecimal digit. Hex works in conjunction with binary based on this fact. Hexadecimal is base 16, using the characters 0-9 and a-f, each nybble is able to represent values 0-15. Binary works by doubles. Lets take a quick detour for binary.

Binary works by doubling with each digit to the left. In other words, because binary assumes that 1 is on and 0 is off, we can only represent values with static place holders. A nybble takes the following layout :

 ? ? ? ?
 8 4 2 1

In the above diagram, a "?" represents a 1 or 0. So, lets say we had 0010. This is the value 2. Now, when there is more than one "1" in a binary nibble, we add the two place-held values together, for example 0011 = 3 because the 2 and the 1 bits are active so we add 2 and 1 together to get three, where 1011 is 11 because the 8, 2, and 1 bits are activated, we add the three together to get the decimal value 11. In hexadecimal, however, these values are represented differently.

Hex Nybble Binary Decimal
0 0000 0
1 0001 1
2 0010 2
3 0011 3
4 0100 4
5 0101 5
6 0110 6
7 0111 7
8 1000 8
9 1001 9
a 1010 10
b 1011 11
c 1100 12
d 1101 13
e 1110 14
f 1111 15

Analyzing Code

This kind of gives us an understanding of how hex values work, so lets revisit the previous shellcode of mov eax, 1 and add eax, 3.

Memory Address Machine Code Assembly Instructions
0x101c879d \xb8\x01\x00\x00\x00 <syntaxhighlight lang="asm">mov eax, 1</syntaxhighlight>
0x101c87a2 \x83\xc0\x03 <syntaxhighlight lang="asm">add eax, 3</syntaxhighlight>

Time to go to expanded view and show you WHY the addresses work the way they do, notice that the machine code in the middle contains a certain amount of bytes. Now we'll take a look at the expanded view of the execution stack or what is refered to as the .text segment. In stead of viewing it by one line of assembly instructions at a time, we'll view it one Byte at a time.

Memory Address Machine Code Byte
0x101c879d \xb8
0x101c879e \x01
0x101c879f \x00
0x101c87a0 \x00
0x101c87a1 \x00
0x101c87a2 \x83
0x101c87a3 \xc0
0x101c87a4 \x03

Now if we look at the expanded view, we can tell how 0x101c87a2 is the beginning of the next line of code, and even get a feel for certain raw instructions. How can we do this? Well, we can see the code for each instruction:

<syntaxhighlight lang="asm"> mov eax, 1 ; \xb8\x01\x00\x00\x00 add eax, 3 ; \x83\xc0\x03 </syntaxhighlight>

So lets rip this apart a little bit. We know that the eax register can contain a DWORD value, so lets extrapolate on the mov eax, 1 instruction. Because eax can hold a DWORD value, we can safely assume that the machine code instruction for moving or setting a value to the eax register must be done in proper DWORD format, that is, it must take 4 bytes. So we notice that the value \x01 is followed by \x00\x00\x00. These are three "null bytes". Seeing these null bytes and knowing the way that assembly operates, we can safely say that the value of eax is assigned backwards in DWORD format. Following all of these logical trains of thought, we can draw the following conclusions:

The machine code instruction "\xb8" is equivilent to mov eax, [dword].

<syntaxhighlight lang="asm"> mov eax, 1  ; \xb8 \x01\x00\x00\x00 mov eax, 0x00000001 ; \xb8 \x01\x00\x00\x00 </syntaxhighlight>

All of these instructions are synonymous. Now lets analyze the add eax, 3 instruction:

<syntaxhighlight lang="asm"> add eax, 3 \x83\xc0 \x03

add eax, \x03 \x83\xc0, 3 </syntaxhighlight>

Overflows

Can also be seen to be all synonymous. Therefore we can draw the conclusion that the machine code instructions \x83\xc0 is equivilent to the assembly instructions add eax, [byte]. Now lets get to the stack. The stack is a linear region of memory with a particularly allocated size, which cannot exceed 16 megabytes, for various reasons. Think of the stack like a stack of paper. While the location of each piece of paper is stored in a hexadecimal DWORD address, the stack grows backwards. This is to say that a stack is assigned a certain memory range and that range of memory is written to from the highest value to the lowest, not the lowest value to the highest. The stack is considered a part of the .data segment of an application, or the .bss segment. This is important because of something called DEP, or Data Execution Prevention. The stack region of an application is the region that can be "overflowed" to allow us to execute arbitrary machine code, however because it is a data region of the program certain safeguards have been put in place to prevent code located within the stack from executing.

Lets make a fake stack, for the sake of example and explanation so that the reader may better grasp this concept. lets say that our stack exists between addresses 0x100010ff and 0x10001000. The stack would look like this, in memory :

Memory Address/Range Location Contents
0x10001000 [Top of the Stack]
0x10001001-0x100010fe [Data]
0x100010ff [Bottom of the Stack]

Now we can see that the top of the stack has a lower memory address than the bottom of the stack. Now we're going to need a bit more information about our non-general purpose registers and a bit more information about our stack operation special instructions.

Push & Pop

Instruction Purpose
PUSH "Pushes" a value on top of the stack
POP "Pops" a value off of the top of the stack

Obviously push applies to both registers as well as static data. For example, we can push direct data in hexadecimal format like push 0x1badc0de or we can push a byte like push 0x1b or we can push a register's dword value like push eax. Pushing a nybble is not supported. Lets go over the push and pop concept a bit more with our "fake stack" that we're creating for the sake of education. My fake machine code is as follows :

Assembly Machine Code
<syntaxhighlight lang="asm">push 0x1badc0de</syntaxhighlight> \x68\xde\xc0\xad\x1b
<syntaxhighlight lang="asm">push 0xabad1dea</syntaxhighlight> \x68\xea\x1d\xad\xab
<syntaxhighlight lang="asm">push 0xcafebabe</syntaxhighlight> \x68\xbe\xba\xfe\xca
<syntaxhighlight lang="asm">push 0xdeadbeef</syntaxhighlight> \x68\xef\xbe\xad\xde

Alright, so say we execute that code starting with the top line first. The data stack will now look something like the following :

Memory Address Location Value
0x100010ff [Top of Stack]  ???
0x100010f0-0x100010f3 "deadbeef"
0x100010f4-0x100010f7 "cafebabe"
0x100010f8-0x100010fb "abad1dea"
0x100010fc-0x100010ff [Bottom of Stack] "1badcode"

A detailed view of the bottom of the stack:

Memory Address Value
0x100010f8 "ab"
0x100010f9 "ad"
0x100010fa "1d"
0x100010fb "ea"
0x100010fc "1b"
0x100010fd "ad"
0x100010fe "c0"
0x100010ff "de"

So we can sort of understand how the stack grows backwards now, as 1badcode was the first thing to get pushed onto the stack and is at the bottom of the stack. Think of the stack like a stack of papers. When you push something onto it, its like putting a piece of paper on it. When you push something else on it, you're putting another piece of paper on the first piece of paper. This is where these "special registers" come into play:

Special Registers

ESP

Stack Pointer - This register remembers the current index of the stack. At the beginning of the small code with the pushes, esp is 0x100010ff, and then it becomes 0x100010f8, f0, so on and so forth as DWORD values are pushed onto the stack. The program uses this to keep track of where it is pushing and popping data from inside of the stack.

EIP

Instruction Pointer - This register represents the location of the actual code executing, for example when add eax, 1 is executed, eip is the memory address of the \x83 byte.

EBP

Base Pointer - This points to the very bottom of the stack, however it can be used to hold addresses of other bases.

ESI

Function call register - when a function is called using the call instruction, the location of the beginning of the function is placed into esi.

Needless to say, our goal when attempting a buffer overflow is going to be manipulating the value of the eip register to make the computer execute our code as opposed to the original code. There are several ways to do this, but first we need to look at a few of our "special instructions" in assembly:

Special Instructions

JMP

Jumps to a location - this location can be defined by a byte offset (jump this many bytes forward or backward), but can also be defined with a DWORD memory address. The jmp instruction effectively modifies the value of the eip register to match that of the location to jump into.

CALL

Calls a function - this is a somewhat difficult concept in assembly, however a function will be demonstrated before the end of this section. When a call instruction is executed, the location of the function that is being called is placed in the esi register, while the location of the instruction immediately after the call is pushed to a stack for return purposes


RET

Exits a function - keep in mind that the call instruction pushes the location of the instruction after it to the stack, so at the end of the function, assuming that the esp register has the same value it contained when the function was first called, ret will jump back to the value that call pushed onto the stack. In other words, call pushes a pointer to the stack for the ret instruction to use when the function is finished executing so that the program can return to "normal" execution flow. This is what is being exploited during a buffer overflow attack.

NOP

NO OPERATION - this instruction literally does nothing. It is the equivilent of saying do nothing.

Call and Return

Lets make another fake flat binary. Reason being, we have to demonstrate the call and return sequence of assembly so that the reader can understand exactly what's going on here. Lets make our function that we are calling near pointless. There's not really much reason to have some uber-awesome code in this part of the article, we are still learning basic concepts. Our function will simply add 3 to the current value of eax. So here goes with another fake program.

Memory Address Machine Code Assembly Instructions
0x0010109d \x83\xc0\x03 <syntaxhighlight lang="asm">add eax, 3</syntaxhighlight>
0x001010a0 \xc3 <syntaxhighlight lang="asm">ret</syntaxhighlight>
0x001010a1 \xb8\x02\x00\x00\x00 <syntaxhighlight lang="asm">mov eax, 2</syntaxhighlight>
0x001010a6 \xe8\xf8\xff\xff\xff <syntaxhighlight lang="asm">call 0x0010109d</syntaxhighlight>
0x001010ab \x90 <syntaxhighlight lang="asm">nop</syntaxhighlight>

Lets say our entry point is at 0x001010a1. This would put the first instruction at mov eax, 2. When the call instruction is executed, it pushes 0x001010ab onto the stack. When the ret instruction at 0x001010a0 is executed, it pops the value 0x001010ab off of the stack and jumps to that location, jumping to the line after the call function, where no operation is preformed.

Lets take a closer look at all of these registers for a moment so we can try to go ahead and get a better idea of where all our application's data is stored in the computer's memory. The eax register is a standard 32 bit register (4 bytes), however it has several sub-registers, like the other standard 32 bit registers. The registers break down as follows :

regs.JPG

That's all for now, have fun with bytecode-asm comparison. I reccomend OllyDBG or GDB to test and expirement. Both are free and available from our friend google :)