In order to keep these skills of mine sharp, I must continue to practice. This post will walk us through a simple crackmes.de challenge to put my newly gained knowledge to the test.
Crackmes.de - Challenge by destructeur
user@debian:~/crackmes$ file crackMe1.bin crackMe1.bin: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=9a480eb4b1711f12768f5cee5915d3024a8815cc, not stripped
I ran the
strings command against the file to look for signs of passwords to submit to the program but none were found. The binary was compiled on an older version of Debian which was also identified by strings command: (Current version is 9)
user@debian:~/crackmes$ strings crackMe1.bin |grep -i debian GCC: (Debian 6.3.0-18+deb9u1) 6.3.0 20170516
Deciding to debug the binary using gdb, I disassembled the main() function and created a breakpoint on main() before running the program to get a better idea of how it works. Nearly every program you will encounter when debugging will have a main() / start() function. This is the entry point to most applications.
user@debian:~/crackmes$ gdb -q ./crackMe1.bin Reading symbols from ./crackMe1.bin...(no debugging symbols found)...done. (gdb) disass main Dump of assembler code for function main: 0x0000000000000a8d <+0>: push rbp 0x0000000000000a8e <+1>: mov rbp,rsp 0x0000000000000a91 <+4>: call 0x9d0 <_Z7systemvv> 0x0000000000000a96 <+9>: call 0x9ec <_Z7systemov> 0x0000000000000a9b <+14>: mov eax,0x0 0x0000000000000aa0 <+19>: pop rbp 0x0000000000000aa1 <+20>: ret End of assembler dump. (gdb) break *main Breakpoint 1 at 0xa8d (gdb)
Looking at the main() of the program I notice there are two functions being called.
0x0000000000000a91 <+4>: call 0x9d0 <_Z7systemvv> 0x0000000000000a96 <+9>: call 0x9ec <_Z7systemov>
Once these functions are called then 0 is copied into EAX which is probably an indicator of status after the functions have been called, but more on that later. Let's disassemble both called functions identified to paint a better picture.
Using the short hand gdb command version for disassemble (disass), we start to identify some basic operations about the first function _Z7systemvv
(gdb) disass _Z7systemvv Dump of assembler code for function _Z7systemvv: 0x00005555555549d0 <+0>: push rbp 0x00005555555549d1 <+1>: mov rbp,rsp 0x00005555555549d4 <+4>: mov DWORD PTR [rbp-0x4],0x5 0x00005555555549db <+11>: mov DWORD PTR [rbp-0x8],0x7 0x00005555555549e2 <+18>: mov DWORD PTR [rbp-0xc],0x1f5 0x00005555555549e9 <+25>: nop 0x00005555555549ea <+26>: pop rbp 0x00005555555549eb <+27>: ret End of assembler dump.
It's important to understand that when a function is being called, it must save the state of the previous function to resume that function later down the line. This is called a Function Prologue and when a function has finished its operations it will do the inverse which is called an Epilogue.
Now that we know more about how functions are saved on the stack, we can also see some operations with numbers being copied at register RBP [-offset]. These are hexadecimal representations of numbers (integers) which are essentially pointer (size) directives. In short, it allows for the code to point to values referenced in memory.
(gdb) p 0x5 $1 = 5 (gdb) p 0x7 $2 = 7 (gdb) p 0x1f5 $3 = 501
Knowing the values and their locations, the function _Z7systemvv does a no operation (nop) and pops the RBP register off the stack and returns (ret), which puts us back in the main() function, to call the next function _Z7systemov
The next function being called _Z7systemov, might seem scary but, there is a lot of "noise on the stack" which you might want to ignore. Time to go ahead and disassemble the function!
After the prologue in this function, we notice that 0x10 (16 bytes) are being subtracted from the stack pointer (RSP) to make room for some operations later on.
- Saved RBP (4 bytes)
- RBP-0x8 (4 bytes)
- RBP-0x4 (4 bytes)
- RBP-0x4 (4 bytes)
- 4*4 = 16 == 0x10
(gdb) disass _Z7systemov Dump of assembler code for function _Z7systemov: 0x00005555555549ec <+0>: push rbp 0x00005555555549ed <+1>: mov rbp,rsp 0x00005555555549f0 <+4>: sub rsp,0x10 0x00005555555549f4 <+8>: mov eax,DWORD PTR [rbp-0x8] 0x00005555555549f7 <+11>: add DWORD PTR [rbp-0x4],eax 0x00005555555549fa <+14>: mov eax,DWORD PTR [rbp-0x4] 0x00005555555549fd <+17>: imul eax,eax,0x2d <--snippet-->
The first argument [rbp-0x8] is being copied to the EAX register. If you recall from the previous function _Z7systemvv, [rbp-0x8] contained the value 0x7 (7). Let's see if that is the case. To examine, start by setting a breakpoint on _Z7systemov function.
(gdb) break _Z7systemov Breakpoint 2 at 0x5555555549f0
Now stepping into the function, we can examine the contents of the instructions in the function.
(gdb) r The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/user/crackmes/crackMe1.bin Breakpoint 1, 0x0000555555554a8d in main () (gdb) disass Dump of assembler code for function main: => 0x0000555555554a8d <+0>: push rbp 0x0000555555554a8e <+1>: mov rbp,rsp 0x0000555555554a91 <+4>: call 0x5555555549d0 <_Z7systemvv> 0x0000555555554a96 <+9>: call 0x5555555549ec <_Z7systemov> 0x0000555555554a9b <+14>: mov eax,0x0 0x0000555555554aa0 <+19>: pop rbp 0x0000555555554aa1 <+20>: ret End of assembler dump. (gdb) c Continuing. Breakpoint 2, 0x00005555555549f0 in systemo() () (gdb) si 0x00005555555549f4 in systemo() ()
To note a couple new commands here that were used:
- 'r' which is the short command for 'run' program. Can be ran with arguments
- 'c' short command for 'continue' to next breakpoint or end of program
- 'si' short command for 'step into' a function
GDB lets us know that we are in the function and current instruction by using "=>", thus now we can examine the state of the program:
(gdb) disass Dump of assembler code for function _Z7systemov: 0x00005555555549ec <+0>: push rbp 0x00005555555549ed <+1>: mov rbp,rsp 0x00005555555549f0 <+4>: sub rsp,0x10 => 0x00005555555549f4 <+8>: mov eax,DWORD PTR [rbp-0x8] <--snippet--> (gdb) x/2wx $rbp-0x8 0x7fffffffe578: 0x00000007 0x00000005
Notice rbp-0x8 contains exactly what we saw in the previous function <_Z7systemvv>, 0x7. If we examine (x) 3 dwords (w) from the RBP register and display in hex (x) we should see all 3 values referenced previously
(gdb) x/3wx $rbp-0xc 0x7fffffffe574: 0x000001f5 0x00000007 0x00000005
Let us check one more time to see if it moves 0x7 into EAX
(gdb) x $eax 0x55554a8d: Cannot access memory at address 0x55554a8d (gdb) ni 0x00005555555549f7 in systemo() () (gdb) x $eax 0x7: Cannot access memory at address 0x7
We were correct that the program puts 0x7 in the EAX register. Let's examine the next two instructions
(gdb) x/2i $rip # show next to instructions starting at RIP register => 0x5555555549f7 <_Z7systemov+11>: add DWORD PTR [rbp-0x4],eax 0x5555555549fa <_Z7systemov+14>: mov eax,DWORD PTR [rbp-0x4] (gdb) x/1wx $rbp-0x4 0x7fffffffe57c: 0x00000005
EAX holds the value 0x7 currently, while the next instruction adds that value to [rbp-0x4]. If you have been following along, you know that [rbp-0x4] is currently set to 0x5. So once the addition of EAX is added, it should calculate to 0xC (12)
(gdb) ni # run next instruction 0x00005555555549fa in systemo() () (gdb) x/1wx $rbp-0x4 0x7fffffffe57c: 0x0000000c (gdb)
The contents of [rbp-0x4] is now 0xC (12), and the next instruction takes the value of [rbp-0x4] and copies it to EAX. So we should see the value in EAX is now 0xC.
(gdb) ni 0x00005555555549fd in systemo() () (gdb) x/2i $rip => 0x5555555549fd <_Z7systemov+17>: imul eax,eax,0x2d 0x555555554a00 <_Z7systemov+20>: mov DWORD PTR [rbp-0xc],eax (gdb) i r eax # 'i r' - short for info register eax 0xc 12
So far, everything checks out. Now it looks like the value in EAX, 0xC, is getting multiplied by the value of 0x2d which is 45. If you are curious to what the instruction imul does, the description and usage can be found here.
(gdb) p 0x2d $6 = 45 (gdb) x/1xw $rbp-0xc 0x7fffffffe574: 0x000001f5 (gdb) p $rbp-0xc $7 = (void *) 0x7fffffffe574 (gdb) p 0x000001f5 $8 = 501 (gdb) ni 0x0000555555554a00 in systemo() () (gdb) x/2i $rip => 0x555555554a00 <_Z7systemov+20>: mov DWORD PTR [rbp-0xc],eax 0x555555554a03 <_Z7systemov+23>: mov DWORD PTR [rbp-0x10],0x0 (gdb) i r eax eax 0x21c 540 (gdb)
The next two instructions, we see EAX will be copied into [rbp-0xC] after the multiplication and now will hold the value 0x21c (540)
(gdb) x/3xw $rbp-0xc 0x7fffffffe574: 0x000001f5 0x00000007 0x0000000c
(gdb) ni 0x0000555555554a03 in systemo() ()
(gdb) x/3xw $rbp-0xc 0x7fffffffe574: 0x0000021c 0x00000007 0x0000000c
It starting to look as if the math that has been completed might be the answer to our solution but let's continue on further to find out. The next address that will be "load effective address" (lea) or called will be 0x555555554b85.
0x0000555555554a0a <+30>: lea rsi,[rip+0x174] # 0x555555554b85 => 0x0000555555554a11 <+37>: lea rdi,[rip+0x201768] # 0x555555756180 <_ZSt4cout@@GLIBCXX_3.4>
Examining that address shows the below content
(gdb) x/3s 0x555555554b85 0x555555554b85: "Password: " 0x555555554b90: "Good password" 0x555555554b9e: "Bad password"
This is a sight to see. We are nearing the section of code where we input a password. The input password will be compared to other known stored passwords. Let's look further down the function to see if we hit any compare (cmp) statements.
0x0000555555554a30 <+68>: mov eax,DWORD PTR [rbp-0x10] 0x0000555555554a33 <+71>: cmp eax,DWORD PTR [rbp-0xc] 0x0000555555554a36 <+74>: jne 0x555555554a62 <_Z7systemov+118>
As you can see there is a cmp statement! I have a feeling this is exactly what we are looking for and we do know the value stored at both locations of [rbp-offset]. If the input matches the expected value then we should get a "Good Password". If not, the jump not equal (jne) instruction will land us in the "Bad Password" prompt. Let's single step to the end of the function to see what happens.
(gdb) n Single stepping until exit from function _Z7systemov, which has no line number information. Password: 540 Good password 0x0000555555554a9b in main () (gdb) disass main Dump of assembler code for function main: 0x0000555555554a8d <+0>: push rbp 0x0000555555554a8e <+1>: mov rbp,rsp 0x0000555555554a91 <+4>: call 0x5555555549d0 <_Z7systemvv> 0x0000555555554a96 <+9>: call 0x5555555549ec <_Z7systemov> => 0x0000555555554a9b <+14>: mov eax,0x0 0x0000555555554aa0 <+19>: pop rbp 0x0000555555554aa1 <+20>: ret End of assembler dump. (gdb) c Continuing. [Inferior 1 (process 9276) exited normally]
Yaay! Look what we got, a "Good Password" response back from the program! After the function has completed its operation or ret (returned) back to main, it breaks at the next instruction back in main(), setting the EAX register to a 0x0 (exit) status.
This crackme was fun, simple and can be solved fairly quickly. Rather than speeding through the challenge, take some time to get to know your target program you are attempting to reverse. For me, this was an exercise that allowed me to get more comfortable with my tooling and see if I truly understood the task at hand. Until next time!