Reverse Engineering - Playing with gdb


























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>
int
main(int argc, char **argv)
{
char password[] = “yomama”;
if(argc < 2)
{
printf(“Usage: %s <password>\n”, argv[0]);
exit(1);
}
if(strncmp(argv[1], password, strlen(password)))
{
printf(“FAIL\n”);
exit(1);
} else {
printf(“WIN\n”);
return(0);
}
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
/lib/ld-linux.so.2
`:6K*
__gmon_start__
libc.so.6
_IO_stdin_used
exit
strncmp
puts
printf
strlen
__libc_start_main
GLIBC_2.0
PTRh`
yomaf
[^_]
Usage: %s <password>
FAIL
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

-h, –[section-]headers  Display the contents of the section headers

-T, –dynamic-syms       Display the contents of the dynamic symbol table

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

CONTENTS, ALLOC, LOAD, READONLY, CODE
13 .text         000001fc  08048400  08048400  00000400  2**4   <—- here

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

000004c0  19 79 6f 6d 61 66 c7 44  24 1d 6d 61 c6 44 24 1f  |.yomaf.D$.ma.D$.| <—   :(
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!!!! :)
puts(“FAIL”FAIL
)                                                       = 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
DYNAMIC SYMBOL TABLE:
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
[…]
SYNOPSIS
#include
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! ;)

Categories: , Share

Leave a Reply