Playing with gdb. Reverse engineer your way.

Playing a very basic level of a known wargame the other day I was trying to solve the following problem:

Given an executable (ELF, Linux) which accepts a password as an argument and verifies if this is correct, find this string. As most of the problems, it’s quite easy if you have the right tools (and knowledge of course).
Anyway, I thought the solution(s) would make a nice blog entry, so here it is!

In order to play around with it in my own Ubuntu, I wrote a very simple C program, named pass_cli.c

carlos@dell:~/Dropbox/hacking/wargames$ cat pass_cli.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

main(int argc, char **argv)
char password[] = “yomama”;

if(argc < 2)
printf(“Usage: %s <password>n”, argv[0]);

if(strncmp(argv[1], password, strlen(password)))
} else {


return(0); // never reached :)


I compiled the program with no special options.

carlos@dell:~/Dropbox/hacking/wargames$ file pass_cli
pass_cli: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.15, not stripped

A first tool of the trade when you are looking for strings in a binary is strings, which will look for strings of printable characters inside the binary.

carlos@dell:~/Dropbox/hacking/wargames$ strings pass_cli
Usage: %s <password>

Well, we cannot find the password here (and even if it were displayed here, how could you identify it?) but that gave us some preliminar information about the program. We can see some familiar libc functions (printf, strlen, strncmp, etc.), strncmp will be of interest a bit later.

Another method you could have tried is for example to dump the contents of the .text section inside the binary. This is actually the same idea behind the use of strings, just a lot messier ;)
Remember that the .text section of a binary contains the machine instructions (the compiled code) but that doesn’t mean the strings are neccesarily stored in a readable way. Anyway we could check the contents of this section with the help of objdump.

carlos@dell:~/Dropbox/hacking/wargames$ objdump
Usage: objdump <option(s)> <file(s)>
Display information from object <file(s)>.
At least one of the following switches must be given:
-a, –archive-headers    Display archive header information
-f, –file-headers       Display the contents of the overall file header
-p, –private-headers    Display object format specific file header contents
-h, –[section-]headers  Display the contents of the section headers
-x, –all-headers        Display the contents of all headers
-d, –disassemble        Display assembler contents of executable sections
-D, –disassemble-all    Display assembler contents of all sections
-S, –source             Intermix source code with disassembly
-s, –full-contents      Display the full contents of all sections requested
-g, –debugging          Display debug information in object file
-e, –debugging-tags     Display debug information using ctags style
-G, –stabs              Display (in raw form) any STABS info in the file
-W[lLiaprmfFsoR] or
Display DWARF info in the file
-t, –syms               Display the contents of the symbol table(s)
-T, –dynamic-syms       Display the contents of the dynamic symbol table
-r, –reloc              Display the relocation entries in the file
-R, –dynamic-reloc      Display the dynamic relocation entries in the file
@<file>                  Read options from <file>
-v, –version            Display this program’s version number
-i, –info               List object formats and architectures supported
-H, –help               Display this information

carlos@dell:~/Dropbox/hacking/wargames$ objdump -h pass_cli

pass_cli:     file format elf32-i386

Idx Name          Size      VMA       LMA       File off  Algn
0 .interp       00000013  08048134  08048134  00000134  2**0
1 .note.ABI-tag 00000020  08048148  08048148  00000148  2**2


11 .init         00000030  0804834c  0804834c  0000034c  2**2
12 .plt          00000080  0804837c  0804837c  0000037c  2**2
13 .text         000001fc  08048400  08048400  00000400  2**4   <—- here
14 .fini         0000001c  080485fc  080485fc  000005fc  2**2


This section starts at the address 0x08048400, that is, the offset is 0x400 (the start virtual address is 0x08048000)

Armed with this knowledge we could hexdump this section and manually inspect it

carlos@dell:~/Dropbox/hacking/wargames$ hexdump -C -s 0x400 pass_cli | less

00000400  31 ed 5e 89 e1 83 e4 f0  50 54 52 68 60 85 04 08  |1.^…..PTRh`…|
00000410  68 70 85 04 08 51 56 68  b4 84 04 08 e8 7b ff ff  |hp…QVh…..{..|
000004a0  00 00 00 00 85 c0 74 09  c7 04 24 1c 9f 04 08 ff  |……t…$…..|
000004b0  d0 c9 c3 90 55 89 e5 83  e4 f0 83 ec 20 c7 44 24  |….U……. .D$|
000004c0  19 79 6f 6d 61 66 c7 44  24 1d 6d 61 c6 44 24 1f  |.yomaf.D$.ma.D$.| <—   :(
000004d0  00 83 7d 08 01 7f 22 8b  45 0c 8b 10 b8 20 86 04  |..}…”.E…. ..|
000004e0  08 89 54 24 04 89 04 24  e8 cf fe ff ff c7 04 24  |..T$…$…….$|
00000600  83 ec 04 e8 00 00 00 00  5b 81 c3 ec 19 00 00 e8  |……..[…….|
00000610  1c fe ff ff 59 5b c9 c3  03 00 00 00 01 00 02 00  |….Y[……….|
00000620  55 73 61 67 65 3a 20 25  73 20 3c 70 61 73 73 77  |Usage: %s <passw|
00000630  6f 72 64 3e 0a 00 46 41  49 4c 00 57 49 4e 00 00  |ord>..FAIL.WIN..|
00000640  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |…………….|

As we can see, the string isn’t stored in a readable format, as expected :(
A very useful tool in this case is “ltrace” for we can monitor every system call (with the corresponding arguments!)

carlos@dell:~/Dropbox/hacking/wargames$ ltrace ./pass_cli AAAAA
__libc_start_main(0x80484b4, 2, 0xbf952854, 0x8048570, 0x8048560 <unfinished …>
strlen(“yomama”)                                                   = 6
strncmp(“AAAAA”, “yomama”, 6)                                      = -1      <— TA-DA!!!! :)
)                                                       = 5
exit(1 <unfinished …>
+++ exited (status 1) +++

But what if our system doesn’t have this tool? Then we have to take the heavy weapons and perform some elegant debugging :)

carlos@dell:~/Dropbox/hacking/wargames$ objdump -T pass_cli

pass_cli:     file format elf32-i386

00000000  w   D  *UND*  00000000              __gmon_start__
00000000      DF *UND*  00000000  GLIBC_2.0   __libc_start_main
00000000      DF *UND*  00000000  GLIBC_2.0   strlen
00000000      DF *UND*  00000000  GLIBC_2.0   printf
00000000      DF *UND*  00000000  GLIBC_2.0   puts
00000000      DF *UND*  00000000  GLIBC_2.0   strncmp     <—- idea!
00000000      DF *UND*  00000000  GLIBC_2.0   exit
0804861c g    DO .rodata        00000004  Base        _IO_stdin_used

We have very good reasons to suspect that strncmp is being used to check our input against the password so let’s start the program in gdb and quickly disassemble it in order to localize the call to strncmp.

carlos@dell:~/Dropbox/hacking/wargames$ gdb -q ./pass_cli
Reading symbols from /home/carlos/Dropbox/hacking/wargames/pass_cli…(no debugging symbols found)…done.

(gdb) set disassembly-flavor intel <—- PLEASE ;)
(gdb) disass main
Dump of assembler code for function main:
0x080484b4 <+0>: push ebp
0x080484b5 <+1>: mov ebp,esp
0x080484b7 <+3>: and esp,0xfffffff0
0x080484ba <+6>: sub esp,0x20
0x080484bd <+9>: mov DWORD PTR [esp+0x19],0x616d6f79
0x080484c5 <+17>: mov WORD PTR [esp+0x1d],0x616d
0x080484cc <+24>: mov BYTE PTR [esp+0x1f],0x0
0x080484d1 <+29>: cmp DWORD PTR [ebp+0x8],0x1
0x080484d5 <+33>: jg 0x80484f9
0x080484d7 <+35>: mov eax,DWORD PTR [ebp+0xc]
0x080484da <+38>: mov edx,DWORD PTR [eax]
0x080484dc <+40>: mov eax,0x8048620
0x080484e1 <+45>: mov DWORD PTR [esp+0x4],edx
0x080484e5 <+49>: mov DWORD PTR [esp],eax
0x080484e8 <+52>: call 0x80483bc
0x080484ed <+57>: mov DWORD PTR [esp],0x1
0x080484f4 <+64>: call 0x80483ec
0x080484f9 <+69>: lea eax,[esp+0x19]
0x080484fd <+73>: mov DWORD PTR [esp],eax
0x08048500 <+76>: call 0x80483ac
0x08048505 <+81>: mov edx,eax
0x08048507 <+83>: mov eax,DWORD PTR [ebp+0xc]
0x0804850a <+86>: add eax,0x4
0x0804850d <+89>: mov eax,DWORD PTR [eax]
0x0804850f <+91>: mov DWORD PTR [esp+0x8],edx
0x08048513 <+95>: lea edx,[esp+0x19]
0x08048517 <+99>: mov DWORD PTR [esp+0x4],edx
0x0804851b <+103>: mov DWORD PTR [esp],eax
0x0804851e <+106>: call 0x80483dc
0x08048523 <+111>: test eax,eax
0x08048525 <+113>: je 0x804853f
0x08048527 <+115>: mov DWORD PTR [esp],0x8048636

Now I will place a breakpoint exactly on the address of this call instruction.

(gdb) b *0x0804851e
Breakpoint 1 at 0x804851e

Remember to use the asterisk (*) right in front of the address, so gdb understands what follows isn’t a literal.

Why did I do it this way? Think about it the other way. What happens when the call instruction is executed? The whole stack frame stup parafernalia, namely

  • EIP is pushed to the stack (by the call itself)
  • The function prologue is executed, that is
  • the current value of EBP is pushed to the stack (from now on, known as SFP)
  • the current value of ESP passes to be the new EBP (“a new stack frame starts here”)
  • a value is subtracted from ESP (to allocate space for local variables)

That is too messy to keep track of, so if we stop the execution flow right before the call instruction, what we have is the stack right before all this stuff… the function arguments at the top of the stack ;)

(gdb) run AAAAAA
Starting program: /home/carlos/Dropbox/hacking/wargames/pass_cli AAAAAA

Breakpoint 1, 0x0804851e in main () <— breakpoint is hit
(gdb) info reg
eax 0xbffff620 -1073744352
ecx 0x6 6
edx 0xbffff3c9 -1073744951
ebx 0x283ff4 2637812
esp 0xbffff3b0 0xbffff3b0 <— “top” of the stack
ebp 0xbffff3d8 0xbffff3d8
esi 0x0 0
edi 0x0 0
eip 0x804851e 0x804851e
eflags 0x286 [ PF SF IF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51

Let’s examine the top of the stack. We are actually interested in the first three words.

(gdb) x/12x $esp
0xbffff3b0: 0xbffff620 0xbffff3c9 0x00000006 0xbffff3d8
0xbffff3c0: 0x0015d4a5 0x0011e0c0 0x6d6f797b 0x00616d61
0xbffff3d0: 0x08048570 0x00000000 0xbffff458 0x00144bd6

Without further inspection we can see a 6, the number of char the function has to compare. It looks good at least ;) Let’s remember the exact syntaxis of strncmp()

carlos@dell:~/Dropbox/hacking/wargames$ man 3 strncmp



int strcmp(const char *s1, const char *s2);

int strncmp(const char *s1, const char *s2, size_t n); <— allright, three arguments…


Also one of the first two words must be a pointer to the hardcoded password, the other a pointer to the user input (argv[1] in this case)

(gdb) x/4x 0xbffff620
0xbffff620: 0x41414141 0x4f004141 0x54494252 0x434f535f

Well, this is pretty clearly our string of 6 “A’s” null-terminated. The other address must be the location of our password!

(gdb) x/4x 0xbffff3c9
0xbffff3c9: 0x616d6f79 0x7000616d 0x00080485 0x58000000

OK, another string of 6 characters, null-terminated. The ASCII char associated to these values can be displayed in gdb as follows:

(gdb) p/c *0xbffff3c9
$16 = 121 ‘y
(gdb) p/c *0xbffff3ca
$17 = 111 ‘o
(gdb) p/c *0xbffff3cb
$18 = 109 ‘m
(gdb) p/c *0xbffff3cc
$19 = 97 ‘a
(gdb) p/c *0xbffff3cd
$20 = 109 ‘m
(gdb) p/c *0xbffff3ce
$21 = 97 ‘a
(gdb) p/c *0xbffff3cf
$22 = 0 ‘00

So here’s the magic password! YOMAMA! ;)


6 thoughts on “Playing with gdb. Reverse engineer your way.

  1. This was a great article. It helped me with a CTF I have been doing from vulnhub. I was stuck before this. It’s the IMF box from vulnhub if anyone wants to try it. Great fun!

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s