Jason Turley's Website

TryHackMe: Reversing ELF Writeup

The TryHackMe Reversing ELF room contains eight x86 binaries that need to be analyzed and/or reverse engineering in order to find the hidden password.

Note: all solution passwords have been redacted.


Crackme1

Let’s start with a basic warmup, can you run the binary?

This is the warmup challenge. Simply download and execute the binary file.

$ ./crackme1    
flag{REDACTED}

That was easy!


Crackme2

Find the super-secret password! and use it to obtain the flag

Run the binary.

$ ./crackme2                      
Usage: ./crackme2 password

Looks like we need to provide the password as a command line argument.

$ ./crackme2 abc123     
Access denied.

Hmm. Let’s use ltrace to see what library calls the crackme is using.

$ ltrace ./crackme2 abc123
__libc_start_main(0x804849b, 2, 0xff8a03c4, 0x80485c0 <unfinished ...>
strcmp("abc123", "REDACTED_PASSWORD_HERE")                     = -1
puts("Access denied."Access denied.
)                                        = 15
+++ exited (status 1) +++

We see that our guess of “abc123” is being compared to the actual (redacted) password. Since they differ, strcmp() returns -1 and “Access denied.” is printed with puts().

Run the binary again with the hidden password.

$ ./crackme2 REDACTED_PASSWORD_HERE
Access granted.
flag{REDACTED}

Crackme3

Use basic reverse engineering skills to obtain the flag

We run the program and it mocks us for giving the wrong password.

$ ./crackme3 beans
Come on, even my aunt Mildred got this one!

Let’s check if there are any useful ASCII strings in the binary with rabin2.

$ rabin2 -z ./crackme3
[Strings]                                                                                            
nth paddr      vaddr      len size section type  string                                              
―――――――――――――――――――――――――――――――――――――――――――――――――――――――                                              
0   0x00000e68 0x08048e68 19  20   .rodata ascii Usage: %s PASSWORD\n                                
1   0x00000e7c 0x08048e7c 14  15   .rodata ascii malloc failed\n
2   0x00000e8b 0x08048e8b 64  65   .rodata ascii ZjByX3kwdXJfNWVjMG5kX2xlNTVvbl91bmJhc2U2NF80bGxfN2gzXzdoMW5nNQ==
3   0x00000ed0 0x08048ed0 17  18   .rodata ascii Correct password!                                   
4   0x00000ef0 0x08048ef0 43  44   .rodata ascii Come on, even my aunt Mildred got this one!         
5   0x00000f1c 0x08048f1c 64  65   .rodata ascii ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/

There seems to be a base64 encoded string on line 2. Let’s decode it.

$ echo "ZjByX3kwdXJfNWVjMG5kX2xlNTVvbl91bmJhc2U2NF80bGxfN2gzXzdoMW5nNQ==" | base64 -d
REDACTED_ANSWER

Sweet! Take that Mildred!


Crackme4

Analyze and find the password for the binary?

Run the crackme.

$ ./crackme4
Usage : ./crackme4 password
This time the string is hidden and we used strcmp

This time the binary tells us to look for the strcmp() function. Since strcmp() a part of libc, we can just use ltrace again.

$ ltrace ./crackme4 AAAAAA           
__libc_start_main(0x400716, 2, 0x7ffcede30f08, 0x400760 <unfinished ...>
strcmp("THE_REDACTED_PASSWORD", "AAAAAA")					= 44
printf("password "%s" not OK\n", "AAAAAA"password "AAAAAA" not OK
)									= 25
+++ exited (status 0) +++

Crackme5

What will be the input of the file to get output “Good game”?

Execute the binary.

$ ./crackme5    
Enter your input:
HELLO
Always dig deeper

Ok, “HELLO” was not the correct password. Let’s inspect the library function calls with ltrace once more.

$ ltrace ./crackme5                                                                                
__libc_start_main(0x400773, 1, 0x7fff41b6ac88, 0x4008d0 <unfinished ...>                             
puts("Enter your input:"Enter your input:                                                            
)                                     = 18                                                           
__isoc99_scanf(0x400966, 0x7fff41b6ab40, 0, 0x7fa283e16f33HELLO                                      
)   = 1                                                                                              
strlen("HELLO")                                               = 5                                    
strlen("HELLO")                                               = 5                                    
strlen("HELLO")                                               = 5                                    
strlen("HELLO")                                               = 5                                    
strlen("HELLO")                                               = 5                                    
strlen("HELLO")                                               = 5                                    
strncmp("HELLO", "REDACTED", 28)          = -7                                   
puts("Always dig deeper"Always dig deeper

Given a string of length N characters, the crackme calls strlen() N+1 times. It then calls strncmp() to check our value with the correct password.

Once again, the password is clearly visible.


Crackme6

Analyze the binary for the easy password

Note: I and uploaded it to my GitHub. Check it out if interested.

Run the program.

$ ./crackme6    
Usage : ./crackme6 password
Good luck, read the source

We get a message to read the source code. Since none was provided, I used radare2 to help me reverse engineer the binary into working C code.

Start by disassembling the crackme in radare2.

$ r2 -d ./crackme6

Next, analyze (aaa) and list (afl) all the functions.

The function of interest is called “my_secure_test”. Examine it by entering pdf @sym.my_secure_test.

This is a long function but we only need to concern ourselves with the cmp instructions. Each one compares $al (the lower 8-bits of register $rax) with a single byte. A truncated snippet is below:

... redacted assembly ...
cmp	al, 0x31
...
cmp	al, 0x33
... more redacted assembly ...

I wrote a small python script that translates each hex value into an ASCII character:

#!/usr/bin/env python3
data = [0x31, 0x33, 0x33, 0x37, 0x5f, 0x70, 0x77, 0x64]

for item in data:
    print(chr(item), end="")

print()

Using this I was able to print the password on one line!


Crackme7

Run the binary.

crackme7

Hmmm, there’s an interactive menu to play around with. We can continue to play around with it in radare2.

$ r2 -d ./crackme7

After analyzing all functions with aaa, disassemble main. There is quite a bit of logic here, but the following line stood out to me:

0x08048665      3d697a0000     cmp eax, 0x7a69

0x7a69 == 31337. Run the binary again and enter 31337 as a menu option to print the password!

$ ./crackme7
Menu:

[1] Say hello
[2] Add numbers
[3] Quit

[>] 31337
Wow such h4x0r!
flag{REDACTED_FLAG}

Crackme8

As usual, begin by running the crackme.

$ ./crackme8
Usage: ./crackme8 password

It wants us to provide a password. Instead, let’s analyze it with radare2.

$ r2 -d ./crackme8

Disassemble the main function:

main

Here’s a breakdown of what’s going on here:

Ok, so all we need to do is convert 0xcafef00d to an integer and then we get the flag right?

$ ./crackme8 $((0xcafef00d))
Access denied.

What happend? Well, 0xcafef00d as an int is 3405705229 which is greater than what an integer in C can hold. So the value wraps around to a negative number. As a result, atoi returns 0. To test this out, I reversed the crackme into C.

One correct solution is to use radare2 to set a breakpoint at the compare instruction (0x080484e4) and then change the value of eax to 0xcafef00d.

[0xf7f8d0b0]> db0x080484e4                                                                                                                                                                         
[0xf7f8d0b0]> dc
hit breakpoint at: 80484e4

Now change eax:

[0x080484e4]> dr eax=0xcafef00d
0x00000064 ->0xcafef00d

Finally, let the program continue running and watch it print the flag.

[0x080484e4]> dc
Access granted.
flag{REDACTED_FLAG}