Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
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.
The installation is as simple as it can be with python.
Unfortunately many features of pwntools are not available on Windows as it uses the _curses
module, which is not available for Windows.
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.
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.
This Section is a run-through of the most useful features in python's pwntools
library.
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
:
The power of pwntools
is incredibly simple communication with your processes.
Sends data
to the process. Data can either be a string
or a bytes-like object
- pwntools handles it all for you.
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')
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.
Receives numb
bytes from the process.
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
.
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
.
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.
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.
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.
Packs addr
depending on context
, which by default is little-endian.
p64()
returns a bytes-like object, so you'll have to form your padding as b'A'
instead of just 'A'
.
Unpacks data
depending on context
; exact opposite of p64()
.
Can take a bunch of arguments and packs them all according to context
. The full functionality is quite , but essentially:
is equivalent to
flat()
uses context
, so unless you specify that it is 64 bits it will attempt to pack it as 32 bits.
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.
Creating an ELF object is very simple.
Rather than specifying another process, we can just get it from the ELF
:
Want to do a ret2plt
? Easy peasy.
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
:
When local, we can grab the libc
the binary is running with. Easy peasy.
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()
.
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
.