pwntools cheatsheet
running a program
from pwn import *
p = process('./vuln') # run the program
r = remote('1.2.3.4', 5678) # basically nc 1.2.3.4 5678
p.interactive() # drops you into an interactive shell
p.close() # closes the program/connection (kinda unnecessary)
pause() # pauses (help for connecting with gdb)
debugging with gdb
# https://docs.pwntools.com/en/stable/gdb.html
context.arch = 'i386'
context.terminal = ['urxvt', '-e', 'sh', '-c']
gdb.attach(p, gdb_cmd) # attach to an existing process, gdb_cmd is a string containing gdb command to execute (optional), you could use this to set breakpoints, etc.
gdb.debug('./vuln', gdb_cmd) # spin up a debugger process, stopped at the first instruction
sending data
p.recvuntil(until) # read input from p until 'line'
p.sendline(line) # sends the line to the program
p.sendlineafter(until, line) # combines recvuntil() and sendline()
packing data
p64(0x12345678910ABCDE) # packs a 64-bit hex number (b'\xDE\xBC\x0A\91\x78\x56\x34\x12')
u64(b'\xDE\xBC\xOA\x91\x78\x56\x34\x12') # unpacks a 64-bit (little-endian) number, basically the inverse of p64.
# also note, p8/p16/p32/u8/u16/u32
hex(123) # > 0x7b, converts a number to its hex representation
bytes() # encode string as bytes
f''.encode() # encode string as bytes
b''.decode() # decode bytes to string
shellcode
asm("""
mov rax, 0x0068732f6e69622f # '/bin/sh\x00' as hex
push rax
mov rdi, rsp
mov rax, 0x3b
xor rsi, rsi
xor rdx, rdx
syscall
""") # a simple system('/bin/sh') payload
reading memory address
magic numbers are bad, use these instead
elf = ELF('./vuln') # reads information from the file
elf = p.elf # only works locally (as you provide the file)
elf.symbols['win'] # gives you the win() address (from the binary)
elf.got['puts'] # gives you the puts() address (from the got)
# if ASLR is enabled, you'll need to specify the binary base), otherwise it'll just give you the offset
elf.address = 0xDEADBEEF
locating what part of your input overwrites certain registers
# You could spam a bunch of As into the program, but it's hard to work out which part of that input overwrites important values (e.g. RIP)
# Cyclic will generate a https://en.wikipedia.org/wiki/De_Bruijn_sequence, TLDR every chunk of 4 characters will be unique, which makes it very easy to identify which part of your input overwrote which registers
c = cyclic_gen()
c.get(n) # Get a chunk of length n
c.find(b'caaa') # -> (8, 0, 8): position 8, which is chunk 0 at position 8
building a payload
payload = b'A' * SOME_PADDING + win_address
# what if you need to store multiple values at offsets in the payload (e.g. stack canary, nopsled)
payload = b'A' * PADDING1 + canary + b'A' * PADDING2 + win_address
# instead a better way to do this is fit()
payload = fit({
PADDING1: canary,
PADDING2: win_address,
})
finding ROPTools
You can only use these in the final exam
# you can ignore this until week7
rop = ROP('./vuln') #
## TODO https://docs.pwntools.com/en/stable/rop/rop.html