Automating GDB Debugging with Pwntools
Table of Contents
Automating pwnable debugging workflow by integrating pwntools, gdb, and tmux into a single exploit script When solving pwnable challenges, I primarily use Use The The second argument takes a gdb script, so you can automate setting breakpoints and continuing code flow. Also, you can attach debugger to a process inside of docker with With this script, it is possible to minimize the difference between local and remote environment. However, errors can occasionally occur when inside and outside of container has permissions that don’t match (see Permission Error). You need to configure where the attached debugger shows up. Simple enough with the I know it’s pretty old-fashioned, but I’ve been using Here’s the final script I use as my The When you’re done debugging, pass 0 to Finally, to hit the actual server, pass the domain or IP to Side note: I’m already doing When debugging a process inside of docker, the error above might occurs sometimes. If we take a closer look at the error, it’s something to do with permission. Printing out the current process list is as follows: The debugger opened via python script is executed with You should note that the This is what greeted me when I first ran the script: The error hit at The script tries to create a terminal in a 0x00. Introduction
pwntools for scripting and gdb for debugging. Usually, you’d spawn a process and attach gdb from another terminal - but with pwntools and tmux, we can automate all of this within the script itself. 0x01. Debugging with pwntools
Spawning Process
BINARY = ""
LIBRARY = ""
# for remote
s = remote(server, port)
# for local
s = process(BINARY, env={"LD_PRELOAD" : LIBRARY})remote for remote, and process for local to run and connect to processes. Especially, remote is used not only when connecting to actual servers but also when connecting to locally running docker. Open Debugger
attach function from pwntools’ gdb module lets you attach a debugger to a running process:bp = {
'main' : 0x555555555249,
'end_of_main' : 0x5555555552d3,
'foo' : 0x55555555566f,
}
gs = f'''
b *{bp['main']}
continue
'''
gdb.attach(s, gs)pid, using the following script:CONTAINER = ""
pid = os.popen(f"sudo docker top {CONTAINER} -eo pid,comm | grep {BINARY} | awk '{{print $1}}'").read()
gdb.attach(int(pid), gs, exe=BINARY) Set Terminal
context module’s terminal variable - just pick your preferred layout:context.terminal = ['tmux', 'splitw', '-h'] # split current window horizontally
context.terminal = ['tmux', 'splitw', '-v'] # split current window vertically
context.terminal = ['tmux', 'splitw', '-hf'] # split entire window horizontally
context.terminal = ['tmux', 'splitw', '-vf'] # split entire window verticallytmux since it’s pretty clean. You should also note that you’re actually running inside a tmux session before executing the script (see Tmux Session Error). 0x02. Conclusion
exploit.py template:from pwn import *
from pwnlib.util.packing import p32, p64, u32, u64
from time import sleep
from argparse import ArgumentParser
BINARY = ""
LIBRARY = ""
CONTAINER = ""
code_base = 0x555555554000
bp = {
'main' : code_base + 0x16ae,
}
gs = f'''
b *{bp["main"]}
continue
'''
context.terminal = ['tmux', 'splitw', '-hf']
def main(server, port, debug):
if(port):
s = remote(server, port)
if debug:
pid = os.popen(f"sudo docker top {CONTAINER} -eo pid,comm | grep {BINARY} | awk '{{print $1}}'").read()
gdb.attach(int(pid), gs, exe=BINARY)
else:
s = process(BINARY, env={"LD_PRELOAD" : LIBRARY})
if debug:
gdb.attach(s, gs)
elf = ELF(BINARY)
lib = ELF(LIBRARY)
s.interactive()
if __name__=='__main__':
parser = ArgumentParser()
parser.add_argument('-s', '--server', type=str, default="0.0.0.0")
parser.add_argument('-p', '--port', type=int)
parser.add_argument('-d', '--debug', type=int, default=1)
args = parser.parse_args()
main(args.server, args.port, args.debug)main function takes three arguments. If -p or --port is provided, it connects remotely. Otherwise, it runs locally for debugging:➜ python3 exploit.py -p 7777
➜ python3 exploit.py --port 7777-d or --debug to run without a debugger:➜ python3 exploit.py -d 0
➜ python3 exploit.py --debug 0-s or --server - and definitely disable debugging:➜ python3 exploit.py -s server.com -d 0
➜ python3 exploit.py --server x.x.x.x --debug 0from pwn import *, but I explicitly import the packing functions again on line 2 because vscode doesn’t seem to find them. Otherwise it shows annoying underlines. 0x03. Troubleshooting
Permission Error
GEF for linux ready, type `gef' to start, `gef config' to configure
93 commands loaded and 5 functions added for GDB 12.1 in 0.00ms using Python engine 3.10
Reading symbols from challenge...
(No debugging symbols found in challenge)
Attaching to program: /home/user/challenge, process 165353
Could not attach to process. If your uid matches the uid of the target
process, check the setting of /proc/sys/kernel/yama/ptrace_scope, or try
again as the root user. For more details, see /etc/sysctl.d/10-ptrace.conf
ptrace: Operation not permitted.
/home/user/challenge/165353: No such file or directory.
/tmp/pwnlib-gdbscript-kd8sfrgq.gdb:4: Error in sourced command file:
The program is not being run.
gef➤➜ ps -ef | grep challenge
root 165261 165241 0 16:01 ? 00:00:00 /bin/sh -c socat TCP-LISTEN:8794,reuseaddr,fork EXEC:/home/ctf/challenge
root 165284 165261 0 16:01 ? 00:00:00 socat TCP-LISTEN:8794,reuseaddr,fork EXEC:/home/ctf/challenge
root 165521 165284 0 16:05 ? 00:00:00 socat TCP-LISTEN:8794,reuseaddr,fork EXEC:/home/ctf/challenge
root 165524 165521 0 16:05 ? 00:00:00 /home/ctf/challenge
user 165545 66182 0 16:05 pts/9 00:00:00 /usr/bin/gdb -q challenge 165524 -x /tmp/pwnlib-gdbscript-webj6wb7.gdbuser permission, whereas the target process challenge is executed with root permission. In this case attaching to the process fails, so the following lines should be added to Dockerfile.RUN /usr/sbin/useradd -u 1000 ctf
USER ctfuid above should be identical with the user’s, who is running the python script. In the case of Ubuntu 24.04, for example, the user ubuntu is preempting uid 1000, possibly causing error because a new user will have uid 1001. Tmux Session Error
➜ python3 exploit.py
[+] Starting local process './challenge': pid 3886107
[*] running in new terminal: ['/usr/bin/gdb', '-q', './challenge', '3886107', '-x', '/tmp/pwnjmmmxq9k.gdb']
Traceback (most recent call last):
File "/home/user/exploit.py", line 133, in <module>
main()
File "/home/user/exploit.py", line 60, in main
gdb.attach(s, gs)
File "/home/user/.local/lib/python3.10/site-packages/pwnlib/context/__init__.py", line 1581, in setter
return function(*a, **kw)
File "/home/user/.local/lib/python3.10/site-packages/pwnlib/gdb.py", line 1100, in attach
gdb_pid = misc.run_in_new_terminal(cmd, preexec_fn = preexec_fn)
File "/home/user/.local/lib/python3.10/site-packages/pwnlib/util/misc.py", line 413, in run_in_new_terminal
pid = int(out)
ValueError: invalid literal for int() with base 10: b''
[*] Stopped process './challenge' (pid 3886107)gdb.attach(s, gs) with a type issue, so I spent way too long thinking it was a pid datatype mismatch. Turns out it was something completely different.tmux session - but there was no active tmux session to attach to. I naively assumed it would just create one automatically…➜ tmux
➜ python3 exploit.py