Hacking is hard.
That's what I was thinking when I first started getting into security. I knew a few things; I had some crude knowledge of exploiting buffer overflows in the '90s. I could step through a program with vanilla GDB and write some shell scripts using commands like echo -e
.
I was at a point where I would solve the first binary exploitation challenge of entry-level CTFs. I'd then wonder how everyone else had the patience to find the other vulns with a debugger written in 1986 and meticulously craft inputs with echo
byte-by-byte.
When I finally got a job, I found myself surrounded by some fantastic researchers that could actually play CTFs 😄
I learned some important lessons. The first one was I had a lot to learn. The second was there exists modern tooling that makes hacking waaaaay easier.
I wanted to write a few things about these tools since I had only seen them in passing, and I wish I had found someone writing about these things in one place. In this metahacking series, I want to explore different tools, their history, and some helpful tips on how to use them.
Who Should Read This?
Any hacker determined to disavow the old, rusty tools of days long past. Or anyone interested in learning about the tools modern security enthusiasts use.
If you're a seasoned CTF player or security researcher, you'll likely know about the tools I'm about to explore.
If you’re interested in learning more about hacking, and you aren’t subscribed yet, you should! You’ll get weekly posts like this one sent directly to your inbox! You know you want to 😈
GDB Like it's the 1980's
The Gnu DeBugger (GDB) is, well, a debugger. Debuggers are helpful for monitoring and manipulating programs. Richard Stallman first released GDB in 1986, which was inspired by the older DBX debugger.
At first, GDB allowed users to alter program execution for C programs. Eventually, it became a mature tool for all aspects of program introspection. It was still ugly as hell though.
Despite being an ancient relic, GDB competes with other modern dynamic disassemblers and debuggers like radare2, IDA Pro, Ghidra, etc.
Vanilla GDB has no GUI and works like an interactive shell. It'll take commands and arguments to do things to the program you're debugging. Nowadays, there exists GDB frontends, but most prefer using GDB in the terminal.
Here are some important builtin commands vanilla GDB ships with:
break
: Inserts a breakpointrun
: Runs the programnext
ornexti
: Executes the next instruction, stepping overcall
instructionsstep
orstepi
: Executes the next instruction, stepping intocall
instructionscontinue
: Resumes executiondisassemble
: disassembles memoryfinish
: Resumes execution until the current subroutine returnsinfo
: Displays information about GDB's current statusx
: Examines memorybt
: Print a backtrace of the current programwatch
: Places a watchpoint on some data, which triggers a break when its value changesprint
: Prints thingsup
: Moves up the current callstackdown
: Moves down the current callstackdelete
: Deletes thingsquit
: Quits GDB 😞
The last major update to GDB was in version 7.0, where they added support for Python scripting. This is where GDB really started being cool.
Python Exploit Development Assistance for GDB (PEDA)
Enter, PEDA. Released ~10 years ago when GDB added support for Python. This was the first step in making GDB actually bearable to look at.
PEDA gave GDB a makeover with colorized registers, disassembly, and memory printouts.
Here are those juicy commands it added:
checksec
: Displays security mitigations present.vmmap
: Displays all the virtual mappings of the binary's segments.dumpargs
: Displays the arguments passed to a function.pattern
: Does useful things with cyclic patterns.
PEDA has been somewhat maintained but has generally been replaced by other frameworks that build off these ideas.
GDB Enhanced Features (GEF)
Stepping into the present day, we have GEF. This single god-tier GDB script adds modern features to GDB, making GDB "cool again for exploit dev". GEF looks great, feels great, and really makes you feel like a hacker
GEF is essentially just PEDA but less tied to Intel architectures.
Besides its good looks and easy installation, GEF added these commands to the GDB universe:
telescope
: Telescopes the stack.canary
: Prints the current frame's canary value.entry-break
: Searches for and breaks at a binary's entry.pie
: Translates addresses when PIE is enabled given an offset.got
: Displays GOT entries.xinfo
: Displays handy information about a provided address.heap
: A family of commands for heap introspection.emu
: Emulates instructions using Unicorn.
GEF is probably the most popular GDB framework, given how easy it is to install and the breadth of modern commands it provides. 10/10 would smash (stacks).
pwndbg
pwndbg is very similar to GEF, intending to simplify implementation.
The edge pwndbg has over GEF is its tools for heap inspection and compatibility with QEMU. pwndbg also has some "native" decompilation ability when combined with radare2 and Ghidra.
These sexy heap introspection commands include:
heap
: Print chunks on a heap.visualize
: Display the heap of an arena with lots of pretty colors 🌈arena
: Print stuff in an arena.bins
: Prints the bins and tcache in an arena.fastbins
,unsorted
,smallbins
, etc.: Prints the bins in an arena.top_chunk
: Prints info on the top_chunk of an arena.find_fake_fast
: Finds a possible value that could be used to craft a fake fastbin chunk.try_free
: Attempt to free a chunk.
The drawbacks I've seen coming from GEF are:
Documentation kinda sucks.
Can't use the
disas
shorthand to disassemble functions since pwndbg introduces adisasm
command.
Demo or GTFO
I want to give you an idea of what a debugging session would look like. Specifically, what it looks like to debug with vanilla GDB compared to debugging with GEF.
In the two sessions below, I do a few operations:
Load the program into the debugger.
Examine the binary's functions.
Set a breakpoint at
main
.Run the program.
Step into a function, then step out of it.
Step over a function.
Disassemble the
vuln
function.Set a breakpoint before the call to
strcpy
invuln
and continue execution.Give the program a long input.
Hit our breakpoint and examine the stack.
Step over the
strcpy
and examine the stack again to see the overflow.Continue execution to retrieve the flag.
Quit GDB.
Have an existential crisis.
Note that GDB supports "shortened" commands, where it can infer what command you want to execute without having to type the whole command out. Granted, the number of letters you type must make it unambiguous what command you want to run.
I would like to embed my asciinema recordings on this post, but unfortunatly Substack doesn’t support this. Here are the links for the videos of vanilla GDB and GEF.
And that's all for now! I hope you learned something new about the history of GDB and some new commands along the way. Go ahead and comment on this post to let me know what you think. Now go forth and debug!
Love this. Makes it easier to understand the space. Thank you for sharing.