Difference between revisions of "Assembly"
m (Improved readability on register's data types list.)
m (Corrected a little typo on formatting.)
|Line 590:||Line 590:|
Revision as of 01:47, 2 July 2016
|This article needs immediate attention, and is in desperate need of more content. There should be zero full assembly programs on this page, however examples for most common instructions.|
|Assembly requires a basic understanding of bitwise math|
- 1 Introduction
- 2 Binary
- 3 Number handling
- 4 Data storage
- 5 Architecture-specific Registers and Sub-Registers
- 5.1 x86
- 5.1.1 32 bit general purpose registers
- 5.1.2 64 bit general purpose
- 5.1.3 mmx
- 5.1.4 sse
- 5.1 x86
- 6 Memory Addressing
- 7 Instructions
- 7.1 Syntaxes
- 7.2 Intel Syntax (dest, src)
- 7.3 ATT Syntax (src, dest)
- 7.4 Data manipulation basic primitives
- 7.5 Basic arithmetic
- 7.6 Bitwise mathematics operators
- 7.7 Shifts and rotations
- 7.8 Control flow operators
- 7.9 Taking it further
An assembler is a program that compiles relatively human-readable operations into instructions that can be directly interpreted by the processor. Assembly Language can be seen as a "mnemonic" for machine instructions - it consists of words that are easier for humans to remember than machine language instructions - for example, "int" for the interrupt operation rather than "0xcd". The assembler produces compiled "objects", translating these mnemonics into machine code - also known as opcodes or as "binary code" in popular culture. By convention, many assembly programmers use the .s extension for assembly code and the .o extension for compiled objects.
On Linux platforms, 'as' is the standard assembler.
A linker is a program that combines the compiled assembly objects into a binary. 'ld' is the standard linker on Linux platforms.
The way in which assembly code is assembled and linked is similar to the way in which higher-level compilers operate. Compilers such as GCC are often simply wrappers for an assembler and a linker that perform both functions dynamically. GCC assembles code into a number of object files based on the rules of the C programming language, then links them into a flat binary of the opcode sequences created at the assembly stage - this flat binary is encoded according to the executable format of the Operating System, which is why the same code will need to be recompiled for different operating systems in order for it to run cross-platform. At runtime, this binary interacts with ram and hardware gates on the CPU according to the rules that the Operating System and CPU architecture follow, performing the desired function of the program.
- Main article: Bitwise Math
- A nybble is an uncommon unit of memory equivalent to 4 bits of data. This unit is not used very often, especially in 32bit and 64bit programming. A nybble can also be thought of as a half of a byte of data.
- A byte is the base unit of every type of data. It is equivalent to 8 bits of data or 2 nybbles and is one of the most used data types in programming. Within assembly there are three main kinds of instructions, one that deals with bytes, one that deals with dwords, and one that deals with qwords. Depending on which instructions you use with which data types there would be padding of null bytes added to it. For instance if you used the instruction pushl 0x0a the data that is actually being pushed to the stack would be 0x0000000a where as if you used the proper instruction(pushb) for pushing bytes to the stack it would simply push 0x0a by itself. The same would happen if you tried to push a byte to the stack using a qword instruction, except that instead of pushing 3 padded nullbytes with your data it would push 7.
- A word is a data type which is 2 bytes in size or 16 bits. There are fewer instructions that work with this size directly. Most instructions either accept 1 byte, 1 dword, or 1 qword of data to operate upon.
- A dword is the second most common data type compared to the byte. A dword consists of 4 bytes and is quivalent to 32 bits of data. Dwords are also known as longs and have a large array of instructions that use and manipulate data using this size. The only instructions that are capable of padding dwords with nullbytes are the ones that use qwords as data input. Dwords are also the maximum size of memory addresses on the 32bit operating system.
- This is the last data type that is used in assembly programming. It is 8 bytes in size and is equivalent to 64 bits of data. No current instructions can cause nullbyte padding for qwords on the 64bit operating systems. Qwords are also the maximum size of memory addresses on the 64bit operating systems.
- signed integers
- Signed values are required to represent negative numbers. Most languages by default assume values are signed. The range of numbers it can assign extends from -1 downwards, depending on the data type. The method by which signed values are handled is known as two's complement. For a value signed with two's complement, the maximum number of possible permutations of the space allocated for the value are divided in two, and the higher value half is used to represent negative numbers. For example, with a signed byte, 00000000 is equal to 0, 00000001 is equal to 1 as would be expected. However, in addition 1111111 is equal to -1, 11111110 is equal to -2, and so on until it reaches the halfway point, which is the highest positive value.
- unsigned integers
- Despite not being able to assign negative numbers, unsigned values are particularly advantageous for positive ranges. The memory that would have been assigned to the negative range is instead added to the positive range (twice as many positive numbers). Unsigned numbers work exactly as would be expected when counting - 0001 is one, 0010 is 2, 0011 is 3 and so on until the all the bits are filled, which is the maximum number that can be represented with that many bits. For example, since a 2 bits have 4 possible permutations - 00, 01, 10 and 11 - it would be possible to count from 0 to 3 with 2 bits. The key feature is that they can only represent 0 and positive numbers.
- The C datatype short int denotes an integer stored in 2 bytes The unsigned short int is able to represent numbers between 0 and 65,535.
- The signed short int, on the other hand, is able to represent numbers between -32,768 and 32,767.
A location where memory can be stored temporarily. A register has the bit-width of a cpu's bit description. So for 32 bit systems, a register is 32 bits (4 bytes or a doubleword, also called long) whereas on a 64 bit system a register is 64 bits in length (8 bytes or a qword).
A register is a location where memory can be stored temporarily. It can be considered to be a sort of basic variable, which can hold any value that the processor stores in it. The difference between registers and the variables of higher-level programming languages is twofold: firstly, registers are limited in size. The reigsters of a processer will have the bit width of the cpu's bit description - 32-bit processors have 32-bit (4-byte) registers, whereas 64-bit systems have a 64-bit (8 byte) register.
The other difference is that registers have both a limited number and in many cases a specific purpose. Registers cannot be defined at the whim of the programmer because unlike variables, a register is not simply a region of allocated memory. They occupy physical space and have a limited number. Some registers are open to use by programs and are "general purpose registers" - they can be loaded with any information and are often used by the operating system to hold arguments to function calls. Other registers such as the instruction pointer, which holds the address of the next instruction that is being executed, are reserved for cpu function.
- A pointer is simply a hexadecimal memory address or offset that points to another location in memory. Pointers have many uses within programming, namely that it is easier to work with a pointer to some data than to have to copy and recopy the data each time it is referenced. One area in which pointers are used is the instruction pointer register, which contains a pointer to the next instruction to be executed.
A subregister is a portion of a register that is always divisible by a whole byte in size. They are often used where an entire 4 bytes (or 8 bytes) is not necessary to complete an operation. One area in which sub-registers are used is in writing Shellcode which is intended to be injected into a string - subregisters are used to avoid the inclusion of null-bytes, which are interpreted by most systems as the string terminator.
CPU Flag Registers
A flag register is a collection of bits used by a processor to indicate various on-off states of different conditions. Each bit corresponds to its own flag, which can be set to etiher on or off by instructions, and can be referenced by a program (or manually set/unset by a program) in order to make decisions based on the result of an operation.
- PF - the parity flag.
- This indicates whether the number of bits of the previous result is even or odd.
- ZF - the zero flag.
- This indicates, when set, that the result of the previous operation was zero.
- CF - the carry flag.
- This indicates, when set, that bit rotation across datasets larger than the cpu register's bit-width will be successful. Without this, data will be rotated in increments of the bit-width of a register for the cpu of the given architecture.
Architecture-specific Registers and Sub-Registers
The following registers and subregisters are specific to 32 bit x86 instruction sets. The 32 bit registers are also found in conjunction with 64 bit specific registers in 64 bit x86 instruction sets.
32 bit general purpose registers
eax is part of a family of several general purpose registers. The other members of this family are ebx, ecx and edx. The general purpose registers are used for general operations, serving as temporary storage for data used by the program. Many functions also use the general purpose registers to hold arguments - for example, on Linux, the kernel interrupt stores the interrupt code in eax, and any additional arguments in ebx, ecx and so on.
As can be seen from the diagram above, eax is split into several sub-registers. The least significant 2 bytes of eax forms another subregister called ax. This in turn, is split into 2 one-byte sub-registers. The most significant byte of ax is ah, while the least significant byte is al.
- The eip register is the 32-bit instruction pointer used to represent the location of the current process counter in memory.
64 bit general purpose
The following registers and subregisters are specific to 64bit x86 instruction sets, they include the 32 bit registers found in 32 bit x86 instruction sets.
rax is the 64-bit equivalent of eax. Being a 64-bit register, it is 8 bytes rather than 4 but apart from this it can be treated as a normal register. The relationships that apply to eax still apply to rax - there are other 64-bit registers in the form of rbx, rcx and rdx. Keep in mind that in AT&T syntax, q is the operation suffix for working with 64-bit operands.
The lowest-order 4 bytes of rax are taken up by eax, which itself is divided as mentioned above under 32-bit general purpose registers.
The rip instruction pointer register does not break down at all. Memory addresses on a 64-bit system can go as high as 8 bytes, although most systems do not have anywhere near enough RAM to break above 6.
64-bit processors include several additional registers not found on a 32-bit processor. The registers, labelled from 8 to 15, function as general purpose registers. The way they break down is slightly different. The letter appended to a subregister matches the initial of the size of the register. For example, for %r8: %r8d is the 4-byte (DWORD) subregister, while %r8b is the 1-byte (BYTE) subregister.
Commonly known as the ESP register in x86 Assembly, the stack pointer is a register that contains the location of the top of the stack. Every time a push instruction is used to add data to the top of the stack, the number of bytes added is subtracted from esp (remember, the stack grows downwards) so that it points to the new top of the stack. Similarly, esp increments when data is popped from the stack.
When first writing an assembly application, a few declarations appear in the header:
.section .data .section .bss .section .text
- These areas are mapped to the data segment, the stack segment, and the code segment frame registers during execution of an executable file by the operating system, respectively.
|Code||Segment register||Segment name|
- These are used as prefixes for other addresses:
Commonly known as the EIP register in x86 Assembly, the instruction pointer is a register that holds the address to the next instuction. When a return instruction is executed, the instruction pointer derives its address from the return address, which exists on the stack. Likewise, when the call instruction is used to transfer execution to a function, the current value of eip (which holds the address of the instruction after the call function) is pushed to the stack so that there is a return address to pop when the return instruction executes and execution resumes from the main thread.
The instruction pointer is very useful in executing Shellcode within overflows - if eip is hijacked, the execution of the program can be arbitrarily controlled.
Commonly known as the EBP register in 32 bit x86 Assembly, the base pointer is generally used to find local variables and parameters on the stack. It is often used during function calls to "save" the position that esp pointed to when the function was called, so that additional space can be used on the stack without losing it's place.
movl %esp, %ebp subl $4, %esp
When writing assembly code there are different ways of referencing the value being worked with. These are referred to as addressing modes.
Direct addressing references the memory address of a given location and uses it for the instruction at hand. One could think of it as loading a register with the data at a specific address. An example of this would be referring to data stored in a variable at the address string_1 points at.
movl string_1, %eax
Immediate addressing references an actual numerical value, be it "1", "2", or "0x80".
movl $1, %eax
References the value that a memory address points to. For example if we used indirect addressing mode and specified the %eax register, and the register contained the value 4, whatever was stored at the memory location 4 would be used. For another example, would be to read the return address from the top of the stack. This would move what ever the %esp pointed to into %eax.
movl (%esp), %eax
As discussed below, indirect addressing can be offset using scalar multipliers.
Indexed addressing uses an index to reference data according to a specific rule. A simple example of indexed addressing is if we specify the address of 1002 and an index register of 4 the actual address the data is being loaded from is 1006. Along with the index register you can also specify a multiplier which multiplies the index. We can use this form of addressing to cycle through a set of numbers a byte, word, or any other size at a time. For example if we specify the start of an array with the address of 1002, we can skip to the third element if we set the index to 2 and the multiplier to 4 if each element is 4 bytes in size. The reason why we set the index to 2 is because we always start counting from 0. This would position us at the memory address of 1010, the third element in the array. Please note that depending on the type not all arrays will be the same size.
The index format is:
- [offset]([base],[index counter],[scalar multiplier])
The formula to calculate the absolute address referenced by an index pointer is:
- (offset + base) + (index counter * scalar multiplier)
movl 0x30(%ecx,%edi,2), %ebx
Refers to (0x30 + %ecx) + (%edi * 2).
A base offset is used to reference memory based on an offset to a pointer. For example, using indexed addressing, the instruction that eip points to with can be referenced (%eip). However, to reference the data stored 4 bytes after eip, 4(%eip) would be used. Likewise, data could be referenced 4 bytes before the instruction that eip points to with -4(%eip).
The base is where we specify the address to start from. As mentioned in an earlier example 1002 would be our base address we would start at and then the index and multiplier would modify that base address.
The index counter is a counter that moves the address ahead a certain number of bytes. If coupled with the multiplier it can be used to access data at certain intervals. For example if we have an array with elements 4 bytes in size starting at the memory address of 1002, we can access the third element via having the index counter set to 2 and a multiplier set to 4. This would cause the address to skip ahead 4 bytes two times which would be the start of the third element.
The scalar multiplier allows us to specify how many bytes each index will skip. If we specify a base address of 1002, an index of 2 and a multiplier of 1 our address we would be accessing would be 1004, where as if we had a multiplier of 4 our address would result in being 1010.
Primarily two syntaxes of assembly have been the most prominent to date. Intel assembly syntax is traditionally used for Microsoft Windows environments, whereas AT&T System V syntax is generally used on Linux and Unix machines.
Intel Syntax (dest, src)
Generally, in intel syntax, all instructions are applied to destination, source operands. For example, to move 8 into the eax register:
mov eax, 8h
ATT Syntax (src, dest)
Generally, in ATR syntax, all instructions are applied to source, destination operands. For example, to move 8 into the eax register:
movl $8, %eax
Within ATT syntax, many operations can be appended with a single letter to indicate what kind of data the operation is being performed upon. For example:
- movq operates on 8 bytes (qword)
- movl operates on 4 bytes (dword)
- movw operates on 2 bytes (word)
- movb operates on single bytes
As a result of this, movl is usually used for 32-bit registers, whereas movq is usually used for 64-bit registers.
Data manipulation basic primitives
The mov instruction, which is a mnemonic for "move", copies some data from one location in memory to another.
- For example, to put the value 8 into the eax register in ATT syntax:
movl $8, %eax
- Using the 16-bit ax sub-register of eax:
movw $0x1212, %ax
- Using the 8-bit al sub-register of eax:
movb $0x1, %al
- And with eax's qword counterpart, rax:
movq $0xx1212121212121212, %rax
The push instruction takes a single operand and pushes some data onto the top of the stack. It creates as much room as the data will take up at the top of the stack, copies the data into the newly created space, and adjust the esp register to point to the new top of the stack.
Some examples of the use of the push instruction:
- Push the 16-bit %dx sub-register of %edx. Subtracts 4 or 8 from esp or rsp, respectively.
- Push a single byte, 0x6a. Subtracts 4 from esp or 1 from rsp, respectively.
The pop instruction takes a single operand and performs the reverse of the push - it takes data from the top of the stack and 'pops' it into the register provided, while adding to %esp or %rsp so that it points to a lower point in the stack, effectively erasing the data from the top.
- An example of the use of the pop instruction:
pop %rax ; subtract 8 from rsp
pop %eax ; subtract 4 from esp
The add operation adds two values together, depositing the result in a register (which is one of the operands). Which register the result is deposited in depends on the assembly syntax being used.
For example, ATT syntax uses the (src, dest) format. As such, take the following code:
addq %rax, %rbx
addl %eax, %ebx
addw %ax, %bx
addb %al, %bl
This code would add eax and ebx together and store the result in ebx.
Following the same rules that the add operation does, when the sub operation is called the source operand is subtracted from the destination operand, the result being stored in the destination operand.
subq %rax, %rbx
subl %eax, %ebx
subw %ax, %bx
subb %al, %bl
As this is in ATT syntax, %eax is the source operand and %ebx is the destination operand. So %eax is subtracted from %ebx and the result is stored in %ebx.
The div operation divides the source by the destination and stores the result in the destination.
divq %rax, %rbx
divl %eax, %ebx
divw %ax, %bx
divb %al, %bl
The preceding code would divide eax by ebx and store the result in ebx.
The mul operation multiplies two operands, storing the product in the destination.
mul %rax, %rcx, %rbx
mul %eax, %ecx, %ebx
mul %ax, %bx, %edx
mul %al, %bl, %cx
Bitwise mathematics operators
- Main article: Bitwise Math
These instructions perform bitwise operations on the data they are given on a bit-by-bit basis.
This instruction performs the AND bitwise operation on two operands, returning a set bit for each instances where the source and destination are either both set or both unset, and an unset bit otherwise.
and %eax, %eax
This instruction performs the NOT bitwise operation on a single operand, inverting each bit - each unset bit becomes a set bit, and each set bit becomes an unset bit. In ATT syntax, this instruction stores the result in the destination register.
not %eax, %eax
This instruction performs the OR bitwise operation on two operands - it returns a set bit in each instance in which either one or the other or both bits at a given position are set.
orl %eax, %eax
This instruction performs the XOR bitwise operation on two operands - it returns a set bit if the compared bits are different, and an unset bit if the compared bits are the same.
Because anything xor'd with itself is 0, the xor instruction is used in Shellcode to zero out a register without introducing any null bytes to the code:
xorl %eax, %eax
Shifts and rotations
- Main article: bit shift
In a shift, the bits of some data are literally shifted either towards the left or the right. To use the example of a string in C:
shifted to the left becomes:
For the binary string:
A right shift will produce the following result:
Note that the least significant (rightmost) bit has been "shifted out" of the binary string.
Takes two operands and shifts each bit to the left by the number of bits specified.
For example, if eax contains a pointer to the string "things\0",
shl $0x8, (%eax)
This would shift the string 8 bits - 1 byte - to the left. Because 1 character is equal to 1 byte, this shift would mean that eax now points to "hings\0".
Takes two operands and shifts each bit to the right by the number of bits specified..
Binary is a base-2 system. Because of this, a left shift has the effect of multiplying a binary value by 2, while a right shift has the effect of dividing a binary value by 2.
If eax contains a pointer to the string "things\0",
shr $0x8, (%eax)
This time, the operation would shift the string 8 bits - 1 byte - to the right. This shift would mean that eax now points to "things" with no null byte.
- Main article: bit rotation
A bit rotation is similar to a shift, the difference being that instead of the shifted digit "disappearing", the bit shifted out is replaced on the other end.
If we apply a left rotate:
Note that the 1 that was previously the most significant (leftmost) bit has been shifted out and is now the least significant (rightmost) bit.
Takes two operands and performs a left rotate upon it by the number of bits specified.
If eax contains a pointer to the string "things\0",
rol $0x8, (%eax)
This would perform a rotate by 8 bits on the string to the left, meaning that eax would now point to "hings\0t" - the first character (which occupies 8 bits) has been rotated to the end of the string.
Takes two operands and performs a right rotate upon it by the number of bits specified.
If eax contains a pointer to the string "things\0",
ror $0x8, (%eax)
This would perform a rotate by 8 bits on the string, meaning that eax would now point to "\0things".
Control flow operators
A vital instruction for decision-making, the cmp instruction simply compares two operands that are passed to it and stores the result in the eflags register. After this, a jmp instruction can be used to move execution to various parts of the program depending on the result of this comparison.
The jmp instruction is tied closely to the cmp instruction. It has several incarnations - jmp, je, jne, jg, jge, jl and jle. In each case, the jump instruction accepts a single operand - the symbol or memory address to be jumped to. If jmp is used, it will jump to this symbol regardless of the result of the comparison stored in eflags. If je is used, it will jump to the symbol provided only if the two values compared were equal. Inversely, jne will only jump if the two operands were inequal. The jg instruction jumps only if the destination operand was greater than the source operand (in ATT syntax, this means that the second operand was greater than the first), while the jl instruction jumps only if the destination operand was less than the source operand and finally the jge and jle instructions do the same as jg and jl except that it adds an or equal clause to the statement.
An example of using cmp and jmp in ATT syntax:
cmpl $5, %ebx jne _start
Here is an example for every flow control instruction and their use. All of these are extremely simple by nature.
The jmp instructions jumps to a label or memory address without any checks or any need to compare. You can think of this as a "forced" jmp.
The je instruction checks the last compare instruction and if the two data types are equal it will jump to the label or memory address. If they are not equal the program flow will skip the jump instruction and continue on.
cmpl $5, %ebx je if_equals
The jne instruction does the exact opposite of the je instruction. It checks to see if the two data types are not equal and if they are it will jump to the label or memory address.
cmpl $5, %ebx jne not_equal
The jg instruction checks the compare instruction and if the first argument is larger then the second it will jump the the label. The jge variant simply adds a "or equals" clause to the statement which would cause it to jump if the data is equal or greater then.
cmpl $5, %ebx jg if_greater
The jl instruction is the inverse of the lg instruction and simply jumps if the first argument to the compare instruction is less than the second. Its counterpart jle is the same as lge as to it will jump if the compare statement is less then or equal to.
cmpl $5, %ebx jl if_less
As discussed previously, the call instruction transfers execution to a symbol that has been defined as a function. In order to do this, it pushes the current value of eip - which is a pointer to the instruction after the function call - onto the stack, "saving" the return address. It then alters the value of eip so that it points to the first instruction of the function being called. From that point, it is the function's responsibility to ensure that it transfers execution back to the return address once the function has completed.
The counterpart to call, the ret instruction is used at the end of a function to return to the main program. Before ret can be called, it is important that the return address - which should have been pushed onto the stack by call - is at the top of the stack. If this is the case, then ret simply pops the return address back into eip so that the program can continue on its way after the function has completed execution.
Taking it further
A kernel interrupt is an instruction that interrupts normal program flow to pass it to another entity such as the systems kernel. Once the proper data is moved into the required registers one may issue the interrupt to pass control to the kernel. The operating system then processes the data and executes the correct function that corresponds to the data it received.
Certain architectures may come with extended functionality, such as mmx, sse2, sse3, etc. Much of the time, this simply means that the architecture is allocated additional general-purpose registers in order for optimization of applications. The more registers one can simultaneously manipulate, the more things an application can do.
On Linux, the kernel knows what system function we want to invoke by checking the %eax register for our system call number. Each system call also has their own requirements for specific data that it requires in order to execute the system call. After the kernel is done its work it passes program flow back to the program to continue executing commands from where it left off.
Windows has an entirely different interrupt system.