arrow-left

All pages
gitbookPowered by GitBook
1 of 7

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Packing

Packing with the in-built python struct module is often a pain with loads of unnecessary options to remember. pwntools makes this a breeze, using the context global variable to automatically calculate how the packing should work.

hashtag
p64(addr)

Packs addr depending on context, which by default is little-endian.

circle-info

p64() returns a bytes-like object, so you'll have to form your padding as b'A' instead of just 'A'.

hashtag
u64(data)

Unpacks data depending on context; exact opposite of p64().

hashtag
flat(*args)

Can take a bunch of arguments and packs them all according to context. The full functionality is quite , but essentially:

is equivalent to

triangle-exclamation

flat() uses context, so unless you specify that it is 64 bits it will attempt to pack it as 32 bits.

pwntools

This Section is a run-through of the most useful features in python's pwntools library.

complexarrow-up-right

Processes and Communication

hashtag
Processes

A process is the main way you interact with something in pwntools, and starting one is easy.

You can also start remote processes and connect to sockets using remote:

hashtag
Sending Data to Processes

The power of pwntools is incredibly simple communication with your processes.

hashtag
p.send(data)

Sends data to the process. Data can either be a string or a bytes-like object - pwntools handles it all for you.

hashtag
p.sendline(data)

Sends data to the process, followed by a newline character \n. Some programs require the \n to take in the input (think about how you need to hit the enter key to send the data with nc) while others don't.

p.sendline(data) is equivalent to p.send(data + '\n')

triangle-exclamation

An incorrect number of these may cause your exploit to stall when there's nothing wrong with it. This should be the first thing you check. If you're uncertain, use p.clean() instead.

hashtag
Receiving Data From Processes

hashtag
p.recv(numb)

Receives numb bytes from the process.

hashtag
p.recvuntil(delimiter, drop=False)

Receives all the data until it encounters the delimiter, after which it returns the data. If drop is True then the returned data does not include the delimiter.

hashtag
p.recvline(keepends=True)

Essentially equivalent to p.recvuntil('\n', drop=keepends). Receives up until a \n is reached, then returns the data including the \n if keepends is True.

hashtag
p.clean(timeout=0.02)

Receives all data for timeout seconds and returns it. Another similar function is p.recvall(), but this regularly takes far too long to execute so p.clean() is much better.

hashtag
Timeout

All receiving functions all contain a timeout parameter as well as the other listed ones. For example, p.recv(numb=16, timeout=1) will execute but if numb bytes are not received within timeout seconds the data is buffered for the next receiving function and an empty string '' is returned.

p64(0x04030201) == b'\x01\x02\x03\x04'

context.endian = 'big'
p64(0x04030201) == b'\x04\x03\x02\x01'
payload = flat(
    0x01020304,
    0x59549342,
    0x12186354
)
payload = p64(0x01020304) + p64(0x59549342) + p64(0x12186354)
p = process('./vulnerable_binary')
p = remote('my.special.ip', port)

Logging and Context

hashtag
Logging

Logging is a very useful feature of pwntools that lets you know where in your code you've gotten up to, and you can log in different ways for different types of data.

hashtag
log.info(text)

hashtag
log.success(text)

hashtag
log.error(text)

hashtag
Context

context is a 'global' variable in pwntools that allows you to set certain values once and all future functions automatically use that data.

Now every time you generate shellcode or use the p64() and u64() functions it will be specifically designed to use the context variables, meaning it will just work. The power of pwntools.

If you think that's a lot of setup, make it even simpler.

This enables you to do a lot more things as well - for example, if you run

it will automatically use the context binary and you will not have to specify it again.

ELF

The pwntools ELF class is the most useful class you will probably ever need, so understanding the full power of it will make your life easier. Essentially, the ELF class allows you to look up variables at runtime and stop hardcoding.

hashtag
Creating an ELF object

Creating an ELF object is very simple.

hashtag
Getting a process

Rather than specifying another process, we can just get it from the ELF:

hashtag
The PLT and GOT

Want to do a ret2plt? Easy peasy.

hashtag
Functions

Need to return to a function called vuln? Don't bother using a disassembler or debugger to find where it is.

Note that elf.functions returns a Function object, so if you only want the address you can use elf.symbols:

hashtag
elf.libc

When local, we can grab the libc the binary is running with. Easy peasy.

hashtag
elf.search(needle, writable=False)

Search the entire binary for a specific sequence needle of characters. Very useful when trying to do a ret2libc. If writable is set it only checks for sections in memory that you can write to. Note this returns a generator so if you want the first match you have to enclose it in next().

hashtag
elf.address

elf.address is the base address of the binary. If the binary does not have PIE enabled, then it's absolute; if it does, all addresses are relative (they pretend the binary base is 0x0).

Setting the address value automatically updates the address of symbols, got, plt and functions, which makes it invaluable when adjusting for PIE or ASLR.

Let's say you leak the base address of libc while ASLR is enabled; with pwntools, it's ridiculously easy to get the location of system for a ret2libc.

Introduction

hashtag
Pwntools

Pwntools is an immensely powerful framework used primarily for binary exploitation, but I have also used it for an challenges that require sockets due to how simplified such interactions are with it.

Here we will be using the python version of pwntools, though there is also a Ruby version.

hashtag
Installation

The installation is as simple as it can be with python.

hashtag
Windows

Unfortunately many features of pwntools are not available on Windows as it uses the _curses module, which is not available for Windows.

elf = ELF('./vulnerable_program')
pip3 install pwntools
>>> log.info('Binary Base is at 0x400000')
[*] Binary Base is at 0x400000
>>> log.success('ASLR bypassed! Libc base is at 0xf7653000')
[+] ASLR bypassed! Libc base is at 0xf7653000
>>> log.success('The payload is too long')
[-] The payload is too long
context.arch = 'i386'
context.os = 'linux'
context.endian = 'little'
context.bits = 64
context.binary = './vulnerable_binary'
p = process()
p = elf.process()
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main_address = elf.functions['vuln']
main_address = elf.symbols['symbol']
libc = elf.libc
binsh = next(libc.search(b'/bin/sh\x00'))
libc = elf.libc
libc.address = 0xf7f23000           # You 'leaked' this

system = libc.symbols['system']
binsh = next(libc.search(b'/bin/sh\x00'))
exit_addr = libc.symbols['exit']

# Now you can do the ret2libc

ROP

The ROP class is insanely powerful, enabling you to create readable ropchains in many less lines.

hashtag
Creating a ROP object

hashtag
Adding Padding

hashtag
Adding a Packed Value

hashtag
Calling the Function win()

And if you need parameters:

hashtag
Dumping the Logic

dump() output:

hashtag
Sending the Chain

hashtag
Showcase

Without pwntools:

With pwntools:

rop = ROP(elf)
rop.raw('A' * 64)
rop.raw(0x12345678)
rop.win()
rop.win(0xdeadc0de, 0xdeadbeef)
from pwn import *

elf = context.binary = ELF('./showcase')
rop = ROP(elf)

rop.win1(0x12345678)
rop.win2(0xdeadbeef, 0xdeadc0de)
rop.flag(0xc0ded00d)

print(rop.dump())
0x0000:         0x40118b pop rdi; ret
0x0008:       0x12345678 [arg0] rdi = 305419896
0x0010:         0x401102 win1
0x0018:         0x40118b pop rdi; ret
0x0020:       0xdeadbeef [arg0] rdi = 3735928559
0x0028:         0x401189 pop rsi; pop r15; ret
0x0030:       0xdeadc0de [arg1] rsi = 3735929054
0x0038:       'oaaapaaa' <pad r15>
0x0040:         0x40110c win2
0x0048:         0x40118b pop rdi; ret
0x0050:       0xc0ded00d [arg0] rdi = 3235827725
0x0058:         0x401119 flag
p.sendline(rop.chain())
payload = flat(
    POP_RDI,
    0xdeadc0de,
    elf.sym['win1'],
    POP_RDI,
    0xdeadbeef,
    POP_RSI,
    0x98765432,
    elf.sym['win2'],
    POP_RDI,
    0x54545454,
    elf.sym['flag']
)

p.sendline(payload)
rop.win1(0xdeadc0de)
rop.win2(0xdeadbeef, 0x98765432)
rop.flag(0x54545454)

p.sendline(rop.chain())