How to Pwn? - A workflow for solving pwn challenges

Pwn is one of the more difficult categories to get started with. This is partially due to the setup required in order to efficiently solve pwn challenges. This post aims to introduce a workflow you can use for solving any pwn challenge.

 Installation

This worklow requires the following tools:

 Template

For every pwn challenge you try to solve, you can create a ‘solve script’ that will pwn the challenge for you. This is very useful to make your exploit understandable and reproducible. Moreover, it makes debugging much easier. And if you need to do (complex) calculations for an exploit, you have the power of programming on your side.

The following Python code is a modified version of the official template of CryptoCat and serves as a basis for every pwn challenge solve script. The explanation is just below the template.

You can download the template using wget https://radboudinstituteof.pwning.nl/posts/how2pwn/exploit.py or you can copy the file below:

from pwn import *
import subprocess


def start(argv=[], *a, **kw):
    if args.GDB:  # Set GDBscript below
        return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
    elif args.REMOTE:  # ('server', 'port')
        return remote(sys.argv[1], sys.argv[2], *a, **kw)
    else:  # Run locally
        return process([exe] + argv, *a, **kw)


"""
notes:

"""

gdbscript = ""

breakpoints = [
    #    'breakrva 0xoffset',
    "continue"
]

for s in breakpoints:
    gdbscript += s + "\n"


exe = "./vuln"
elf = context.binary = ELF(exe, checksec=False)
# context.log_level = 'info' # use DEBUG in args for debugging. LOG_LEVEL=warn/info/error for anything else

"""
if args.REMOTE:
    libc = ELF('./libc.so.6', checksec=False)
else:
    libc = ELF('/usr/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
"""

# ===========================================================
#                    EXPLOIT GOES HERE
# ===========================================================
offset = 0

io = start()

payload = flat({offset: []})


io.sendlineafter(b">", payload)

io.interactive()

 Explanation

We will go into detail about how to use the template. The start function allows you to call the exploit with the following arguments:

python3 exploit.py
python3 exploit.py GDB
python3 exploit.py GDB DEBUG
python3 exploit.py GDB DEBUG NOASLR
python3 exploit.py REMOTE localhost 1337
python3 exploit.py REMOTE localhost 1337 DEBUG

The arguments do the following:

breakpoints = [
#    'breakrva 0xoffset',
    'continue'
]

In the above array, you can add breakpoints to the program (other GDB commands can be used as well). With the breakrva command of pwndbg, you can set a relative breakpoint in the binary. A relative breakpoint can easily be retrieved using Ghidra by subtracting the top address in Ghidra of the address of the instruction.

An example: Top address in Ghidra

The top address would be 00400000 (scroll all the way up in Ghidra). And we want to break on the following CALL instruction for puts: The breakpoint

Then the relative breakpoint would be:

➜ python3 -c 'print(hex(0x00401ec1 - 0x00400000))'
0x1ec1

You can add this breakpoint with breakrva 0x1ec1 in the array and every time you run the exploit with GDB as argument, GDB will break at the CALL instruction in the program.

exe = './vuln'

exe should be the path to the binary.

#context.log_level = 'info' # use DEBUG in args for debugging. LOG_LEVEL=warn/info/error for anything else

With context.log_level you can controll the amount of output of pwndbg. context.log_level = 'debug' is equal to adding the DEBUG argument when running your exploit.

'''
if args.REMOTE:
    libc = ELF('./libc.so.6', checksec=False)
else:
    libc = ELF('/usr/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
'''

This code is useful when you have to use libc in your exploit. It automatically loads the local libc when running locally and the remote libc (located in ./libc.so.6) when running remote. If you have to use libc depends on the challenge, but it is quite common. For example, if you want to do a ret2libc, you will need this to interact with the libc binary.

Everything below EXPLOIT GOES HERE is custom code that depends on the challenge. So here you write how to interact with the challenge binary/server and what to send to pwn it. There is still some template here, because some things you will always need.

io = start()

The io variable is the process you interact with. So this is either the remote or the local binary where you can send bytes to or receive bytes from.

payload = flat({
    offset: [

    ]
})

Using flat is properly explained in the pwntools documentation. But a short explanation is that the offset is a number where you want to insert the strings that are in the given array. So

payload = flat({
    4: [
        b'AAAA'
    ]
})

will create a string of 8 characters where the first 4 characters are automatically generated garbage and at index 4 there will be the string AAAA. This makes it easy to create a payload with specific bytes at the right offsets.

io.sendlineafter(b'>', payload)

When you have your payload ready, you can send it to the process. io.sendlineafter will first wait until it receives the bytes in the first argument, in this case >. Then it will send the payload (should be bytes) to the process. There are more methods like sendlineafter, for example send and sendline. Check the pwntools documentation for more functions!

io.interactive()

The last line is very important to have and you should never remove it. The io.interactive() will start an interactive session in your shell where you can type input and send it to the process. This is useful if you have spawned a shell, but this line also serves a different purpose: it keeps the process alive for debugging. If you remove this line, then the process will stop immediately and you won’t be able to debug it with GDB.

 Resources

CryptoCat has a great beginners playlist where he explain the basics of pwn challenges, like the stack, buffer overflows, shellcode, ret2libc and format string exploits. Furthermore, his retired Hack The Box challenges playlist is a good starting point to get practical experience with real (pwn) challenges.