Contents

Reversing ELFs on TryHackMe: Crackme8

Collecting Information

As always, we will start by collecting as much information as we can about our file and looking for low-hanging fruit (hardcoded strings, function names, etc).

/images/thm/reversing/reversingelf/c8_strings.png

From our strings output, we do see the strings returned during the initial run (e.g. granted/denied) and also some interesting functions like giveFlag but there’s no obvious flag.

GDB

To load this file into gdb, we can simply use the command gdb ./crackme8. I’m using pwndbg, which is a more user-friendly and feature-rich way of interfacing with GDB. My outputs may differ from yours, but the commands should all have parity.

Dissassembly

Once the file is loaded into the debugger, we can use two commands to gather some additional information:

  • info variables
    • This will show us any hardcoded variable names. If we were to find any, we could then use print <varname> - which doesn’t appear to be the case for this file

/images/thm/reversing/reversingelf/c8_vars.png

  • info functions
    • This would be equivalent to above, just applicable to function names:

/images/thm/reversing/reversingelf/c8_funcs.png

Unfortunately, we didn’t get anything particularly useful out of either of these commands, so we’ll move on to looking at the main function which is (luckily) defined clearly. So let’s drilldown into the function and see what’s happening.

main()

To disassemble any function, we just need to run disass <func_name>. However, before we do that, I like to change the disassembly representation to the Intel format. To do that, we can set disassembly-flavor intel. This is a completely optional step, but I prefer seeing the information represented this way.

Dump of assembler code for function main:
   0x0804849b <+0>:     lea    ecx,[esp+0x4]
   0x0804849f <+4>:     and    esp,0xfffffff0
   0x080484a2 <+7>:     push   DWORD PTR [ecx-0x4]
   0x080484a5 <+10>:    push   ebp
   0x080484a6 <+11>:    mov    ebp,esp
   0x080484a8 <+13>:    push   ecx
   0x080484a9 <+14>:    sub    esp,0x4
   0x080484ac <+17>:    mov    eax,ecx
   0x080484ae <+19>:    cmp    DWORD PTR [eax],0x2
   0x080484b1 <+22>:    je     0x80484d0 <main+53>
   0x080484b3 <+24>:    mov    eax,DWORD PTR [eax+0x4]
   0x080484b6 <+27>:    mov    eax,DWORD PTR [eax]
   0x080484b8 <+29>:    sub    esp,0x8
   0x080484bb <+32>:    push   eax
   0x080484bc <+33>:    push   0x8048660
   0x080484c1 <+38>:    call   0x8048340 <printf@plt>
   0x080484c6 <+43>:    add    esp,0x10
   0x080484c9 <+46>:    mov    eax,0x1
   0x080484ce <+51>:    jmp    0x804851c <main+129>
   0x080484d0 <+53>:    mov    eax,DWORD PTR [eax+0x4]
   0x080484d3 <+56>:    add    eax,0x4
   0x080484d6 <+59>:    mov    eax,DWORD PTR [eax]
   0x080484d8 <+61>:    sub    esp,0xc
   0x080484db <+64>:    push   eax
   0x080484dc <+65>:    call   0x8048380 <atoi@plt>
   0x080484e1 <+70>:    add    esp,0x10
   0x080484e4 <+73>:    cmp    eax,0xcafef00d
   0x080484e9 <+78>:    je     0x8048502 <main+103>

In this block, there are two really important pieces:

  • 0x080484ae <+19>: cmp DWORD PTR [eax],0x2
  • 0x080484e4 <+73>: cmp eax,0xcafef00d

So in the first case, we’re comparing the value of the EAX register by reference to the value 0x2 (essentially, if (*eax == 2)), which is equivalent to the decimal value 2. The second case, we’re comparing EAX to 0xcafef00d, which is equivalent to the decimal value 3405705229. We need both of these checks to succeed in order for giveFlag to … give us the flag. Here is a visualization of the flow of main():

                                      ┌────────┐
                                      │crackme8│
                                      └─┬────┬─┘
                                        │    │
                                        │    │
                       ┌──────────┐     │    │          ┌───────────┐
                       │ Argument ├─────┘    └──────────┤No Argument│
                       └─────┬────┘                     └─────┬─────┘
                             │                                │
                             │                                │
                      ┌──────┴─────┐                          │
                      │ 0x080484ae │ ;cmp DWORD PTR [eax],0x2 │
                      └──┬────┬────┘                          │
                         │    │                               │
    ┌─────┐              │    │ ┌───────┐                     │
    │  2  ├──────────────┘    └─┤ else  ├───┐                 │
    └──┬──┘                     └───────┘   │                 │
       │                                    │                 │
       │    ;cmp eax,0xcafef00d             │                 │
       │     ┌────────────┐                 │                 │
       └─────┤ 0x080484e4 │                 │                 │
             └───┬────┬───┘                 │                 │
                 │    │                     │                 │
 ┌────────────┐  │    │ ┌───────┐           │                 │
 │ 0xcafef00d ├──┘    └─┤  else ├─────────┐ │                 │
 └──┬─────────┘         └───────┘         │ │                 │
    │                                     │ │                 │
    │                                     │ │                 │
┌───┴─────┐                               │ │                 │
│giveFlag ├─────────────────────────────┐ │ │                 │
└─────────┘                             │ │ │                 │
                                        │ │ │                 │
                                      ┌─┴─┴─┴┐                │
                                      │ Exit ├────────────────┘
                                      └──────┘

Counter-Intuitive inputs

So here’s where the challenge becomes slightly counter-intuitive… we’re told that the usage is Usage: ./crackme8 password but that is a misleading statement. What we actually need to do, is pass in 2 in order to pass through the first flow gate. In order to do this from within GDB we can just use run 2. However, before doing that, we’ll want to set a breakpoint at 0x080484e4 (the second gate) via b *0x080484e4 because we will need to modify the contents of the register (if you’re wondering why we didn’t also just do that for the first gate at 0x080484ae, it’s frankly because I want to bake in a small GDB tutorial into this write-up and needed a place to explain how to actually run the program from inside the debugger… tangent off).

Second Gate (0x080484e4)

With our breakpoint set, and the program executed via run 2, it’s time to now change the value of EAX in order to pass the second gate (0x080484e4 <+73>: cmp eax,0xcafef00d). To accomplish this, we need to remember that the decimal value of 0xcafef00d is 3405705229 and then just use the command: set $eax=3405705229. Now, when we execute the next instruction, we should jump properly - having passed the comparison instruction:

(gdb) set $eax=3405705229 // setting EAX = 0xcafef00d
(gdb) ni // repeat this by just hitting enter a few times
0x080484e9 in main () // je 0x8048502 <main+103>
0x08048502 in main ()
0x08048505 in main ()
0x0804850a in main ()
Access granted. // BINGO!
0x0804850f in main ()
0x08048512 in main ()
flag{XXX} // Collect your loot!
0x08048517 in main ()