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