Assembly
Assembly requires a basic understanding of bitwise math |
Contents
Introduction
- Assembler
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 "mmemonic" 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 mmemonics 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.
- Linker
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 flat binary interacts with ram and hardware gates according to the rules that the Operating System follows, performing the desired function of the program.
Binary
- Main article: Bitwise Math
- counting
- endianness
- nybble - An uncommon unit of memory equivalent to 4 bits.
- byte - A byte is a unit of memory equivalent to 8 bits.
- word - 2 bytes
- dword - 4 bytes, also called a long
- qword - 8 bytes
Number handling
- signed - 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.
- unsigned - 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).
Data storage
Register
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.
Pointer
A pointer is simply a hexadecimal four-byte address that points to another location in memory. Pointers have many uses within programming, namely that it is easier to work with a 4-byte 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.
Sub-Register
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.
For example:
- 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
x86
32 bit general purpose registers
- eax
eax | |||
ax | |||
'ah' | 'al' | ||
## | ## | ## | ## |
eax is part of a family of 4 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, 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.
This register structure is adhered to by the other general purpose registers - for example, ebx can be split into bx, bh and bl.
64 bit general purpose
- rax
- r8-15
mmx
sse
Memory Addressing
Stack Pointer
Commonly known as the ESP 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 downards) so that it points to the new top of the stack. Likewise, esp is added to when data is popped from the stack.
Instruction Pointer
Commonly known as the EIP 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.
Base Pointer
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 our place.
movl %esp, %ebp |
Addressing Modes
When writing assembly code there are different ways of referencing the value being worked with. These are referred to as addressing modes.
- Direct Addressing
Direct addressing references the memory address of a given location. An example of this would be referring to memory stored in a variable outside of memory.
movl string_1, %eax |
- Immediate Addressing
Immediate addressing references an actual numerical value, be it "1", "2", or "0x80".
movl $1, %eax |
- Indirect Addressing
References the value that a memory address points to. For example, in order to read the return address from the top of the stack:
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. In its most complex form, it can be used to reference the nth n-bute segment of n.
For example:
movl string_1(,4,2), %eax |
This references the 4th segment of string_1, where 1 segment is 2 bytes long.
Scalar Multipliers
A scalar multiplier is used to reference memory based on an offset to a pointer. For example, using indexed addressing, we could reference the instruction that eip points to with (%eip). However, if we wanted to reference the data stored 4 bytes after eip, we would use 4(%eip). Likewise, we could reference data 4 bytes before the instruction that eip point to with -4(%eip).
Instructions
Syntaxes
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)
Data manipulation basic primitives
- mov
- push
- pop
Basic arithmetic
- add
- sub
- div
- mul
Bitwise mathematics operators
- and
- not
- or
- xor
Shifts and rotations
- shl
- shr
- rol
- ror
Control flow operators
- cmp
- jmp
- call
- ret
Taking it further
- kernel interrupt
- architecture - i386, i686, x86_64
- operating system