GDB Tutorial

GDB is a great debugger that comes ready with many linux OS's. For those of you who dont know, a debugger is a computer program used to test and debug other computer programs. This is just a simple walkthrough to show you how this program can be used. I stress the fact that the example i'm giving here is extreemely simple, and you're likely to encounter much more complex problems in real life situations. This is only meant as a beginners tutorial for the debugger, not for assembly.
Also, you should be aware that this article assumes that you have a basic understanding of assembly, and a bit of experience with C programming may also be helpful.
So, let's say we have a password program written in C that we need to crack and we don't have the source code. Well, we can always use gdb to find the assembly and trace through it. So, let's get started.
It's usually safe to assume that there will be a 'main()' function in a program, so let's start by finding out what it looks like:
(gdb)> disasseble main
Dump of assembler code for function main:
0x0804840c <main+0>: lea 0x4(%esp),%ecx 0x08048410 <main+4>: and $0xfffffff0,%esp 0x08048413 <main+7>: pushl 0xfffffffc(%ecx) 0x08048416 <main+10>: push %ebp 0x08048417 <main+11>: mov %esp,%ebp 0x08048419 <main+13>: push %ecx 0x0804841a <main+14>: sub $0x34,%esp 0x0804841d <main+17>: lea 0xffffffde(%ebp),%eax 0x08048420 <main+20>: mov %eax,0x4(%esp) 0x08048424 <main+24>: movl $0x804854e,(%esp) 0x0804842b <main+31>: call 0x80482d8 <[email protected]> 0x08048430 <main+36>: lea 0xffffffde(%ebp),%eax 0x08048433 <main+39>: mov %eax,(%esp) 0x08048436 <main+42>: call 0x80483c8 <checkPass> 0x0804843b <main+47>: mov $0x0,%eax 0x08048440 <main+52>: add $0x34,%esp 0x08048443 <main+55>: pop %ecx 0x08048444 <main+56>: pop %ebp 0x08048445 <main+57>: lea 0xfffffffc(%ecx),%esp 0x08048448 <main+60>: ret 0x08048449 <main+61>: nop
Briefly skim over, and you should notice that it makes a few function calls. The first is a call to the built-in function scanf which makes sense since the program asks you to input a password. The second method call is to a method called checkPass which sounds like what we're looking for, so our next step should be to see what that looks like:
(gdb)> disassemble checkPass
Dump of assembler code for function checkPass:
0x080483c8 <checkPass+0>: push %ebp 0x080483c9 <checkPass+1>: mov %esp,%ebp 0x080483cb <checkPass+3>: sub $0x18,%esp 0x080483ce <checkPass+6>: mov 0x8048548,%eax 0x080483d3 <checkPass+11>: mov %eax,0xfffffffa(%ebp) 0x080483d6 <checkPass+14>: movzwl 0x804854c,%eax 0x080483dd <checkPass+21>: mov %ax,0xfffffffe(%ebp) 0x080483e1 <checkPass+25>: lea 0xfffffffa(%ebp),%eax 0x080483e4 <checkPass+28>: mov %eax,0x4(%esp) 0x080483e8 <checkPass+32>: mov 0x8(%ebp),%eax 0x080483eb <checkPass+35>: mov %eax,(%esp) 0x080483ee <checkPass+38>: call 0x80482f8 <[email protected]> 0x080483f3 <checkPass+43>: test %eax,%eax 0x080483f5 <checkPass+45>: jne 0x8048405 <checkPass+61> 0x080483f7 <checkPass+47>: movl $0x804853e,(%esp) 0x080483fe <checkPass+54>: call 0x80482e8 <[email protected]> 0x08048403 <checkPass+59>: jmp 0x804840a <checkPass+66> 0x08048405 <checkPass+61>: call 0x80483b4 <notifyAuthorities> 0x0804840a <checkPass+66>: leave 0x0804840b <checkPass+67>: ret
Again, briefly skim over the code and again, you should notice two method calls. The first method call is to the built-in function strcmp (string compare). You should be excited now because that probably means that we're just comparing our string to the correct password, meaning this will be very easy. The second function call is to a method called notifyAuthorities which doesnt sound like very much fun to me, so we should make sure we tell the debugger to stop the program before it gets to that:
(gdb)> break notifyAuthorites
Now, if we accidentally input the wrong password, the program will stop at that method call and we can type 'kill' into the prompt to end the program before the authorities are notified.
So, now that we're all set, lets find out what the password is. I suppose you could try to sit there and translate all that assembly, but i really prefer to just run through it a few times first and see what's happening, so that's what we're going to do. The important function is checkPass, so let's set a breakpoint there (we dont need to trace through the main function in this example).
(gdb)> break checkPass
Now, before we begin it'd probably be helpful if i gave you a list of some useful gdb commands, so here it is:
help - pull up the help pages (takes a command as an arguement)
break - sets a breakpoint (takes a function name or address as an arguement)
continue(also cont or c) - continue a program run from breakpoint
run(also r) - runs the program (takes command line parameters as arguements)
nexti(also ni) - next instruction, skipping functions
stepi(also si) - next instruction, stepping into functions
finish(also fi) - complete current function then break
del - deletes breakpoint. takes a breakpoint name as an arguement
disassemble(also disas) - breaks down a program into assembly (takes a function name as an arguement)
kill(also k) - stops program execution
print - prints out a value
x - examine (dereference arguement and print) can have the following type specifiers added:
/d = decimal
/x = hex
/a = address
/s = string
/c = character
info registers - prints out values in all registers as well as the value of the flags
Also, you probably want to note that in assembly code, registers have a percent sign(%) in front of them, but in gdb you put a dollar sign($) in front of them instead when referencing a register.
So, let's start (i'm going to enter the first word that pops into my head as the password this time around):
(gdb)> run
> pizza
Breakpoint 2, 0x080483ce in checkPass ()
Okay, it should now be stopped at our first breakpoint at checkPass. Info registers is usually a good place to start, so let's enter that:
(gdb)> info registers eax 0xbff4ff46 -1074462906 ecx 0x48b8e420 1220076576 edx 0x0 0 ebx 0x48b8dff4 1220075508 esp 0xbff4ff10 0xbff4ff10 ebp 0xbff4ff28 0xbff4ff28 esi 0x4809fca0 1208614048 edi 0x0 0 eip 0x80483ce 0x80483ce <checkPass+6> eflags 0x200282 [ SF IF ID ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x33 51
Well, nothing really stands out to me there, but let's take a look into the eax register since that is generally the all-purpose register. Since we're probably looking for a string, let's try that:
(gdb)> x/s $eax
0xbff4ff46: "pizza"
Hey, look at that. It's our input. Okay, we're currently on the line labled <checkPass+6>, and you can see that it's putting something else into the eax register. Why dont we step once and see what it puts in there.
(gdb)> si
0x080483d3 in checkPass ()
(gdb)> x/s $eax
0x6863616e: <address 0x6863616e out of bounds>
Well, that was a bust. Oh well, lets take a look at the code again:
0x080483d3 <checkPass+11>: mov %eax,0xfffffffa(%ebp) 0x080483d6 <checkPass+14>: movzwl 0x804854c,%eax 0x080483dd <checkPass+21>: mov %ax,0xfffffffe(%ebp) 0x080483e1 <checkPass+25>: lea 0xfffffffa(%ebp),%eax 0x080483e4 <checkPass+28>: mov %eax,0x4(%esp) 0x080483e8 <checkPass+32>: mov 0x8(%ebp),%eax 0x080483eb <checkPass+35>: mov %eax,(%esp) 0x080483ee <checkPass+38>: call 0x80482f8 <[email protected]>
Well, the three lines before the call to strcmp are setting up the arguements for the function call, and we can probably assume that one of the arguements is our input and the other is probably the correct password, so lets step ahead a few more time to checkPass+28. At that point, the program has just moved a new value into eax, so lets see what it is:
(gdb)> si
0x080483d6 in checkPass ()
(gdb)> si
0x080483dd in checkPass ()
(gdb)> si
0x080483e1 in checkPass ()
(gdb)> si
0x080483e4 in checkPass ()
(gdb)> x/s $eax
0xbff4ff22: "nacho"
Hey, it's a nice string. It looks like a likely candidate for a password. We set our breakpoint at notifyAuthorities, so we've got nothing to lose if we try it, so let's kill this thing and see if it's right (remember, we still have our breakpoint set at checkPass, so we'll have to tell it to keep going once it stops there):
(gdb)> kill
Kill the program being debugged? (y or n) y
(gdb)> run
> nacho
Breakpoint 2, 0x080483ce in checkPass ()
(gdb)> continue
Continueing.
good job!
Program exited normally.
Well, looks like we did it. Just in case you're curious, here's what the C code looks like for the program we were running:
#include <stdio.h> #include <string.h> void notifyAuthorities() { //doesnt really do anything, just an example printf("authorites have been notified\n"); } void checkPass(char input[30]) { char pass[]="nacho"; if(!strcmp(input, pass)) { printf("good job!\n"); } else{ notifyAuthorities(); } } int main() { char input[30]; scanf("%s", input); checkPass(input); return(0); }
If you compile your code with gcc using the -g option (e.g. gcc -g filename.c -o exeName) then you can use the debugger to trace through your source code. So, say you did create the executable file that way, but there was a problem and you can't figure out why the program isnt working. You could open up gdb in the same way:
> gdb ./Password
And you could use most of the same commands such as break, print, continue, etc. You could then trace through the program line-by-line and keep track of the values in each variable to find where things go wrong.
A useful command to use for this purpose is the display command. You can type: display variableName and it will show the value of that variable as you step through the program. It's very useful when tracing through loops.