Simple Challenge to Simply Practice

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

https://crackmes.one/crackme/5aef37c733c5d41ac64b492e

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!