Solving a Simple Crack-Me with Cutter

Using the reverse engineering tool Cutter to solve a crack-me challenge.

In my quest to learn reverse engineering, I stumbled upon this GitHub repository containing several x64 crack-me challenges, with the difficulty increasing gradually.

Since level 1 was too simple, I focused on level 2 for this write-up.

After cloning the repository, we have the crack-me challenges in C.

1_cHXqDOzQQuuW6Oky3aOsoQ.png

To compile it and disassemble it (because analyzing the C code wouldn’t be fun), we run the command make <crackme>:

1_9yqDPJo9oeR0QlKidtbBhQ.png

Now we have a .64 file, and the file command provides us with some additional information:

We confirm that it’s an “ELF x64, not stripped” file, which is normal for an easy crack-me like this one.

Let’s execute this binary and see what it gives us!

1_rttdl6nLnBfpgNiwDtzI-A.png

We can see that the binary requires only one argument. So, let’s start with a simple string: “aaaa.”

1_K-RH_gFkgRVppJ5VbdKHdg.png

To my surprise, the string “aaaa” doesn’t work, but we know that we can find the failure condition by disassembling this binary.

Now, let’s start Cutter and inspect this binary in more depth.

1_BmlieUeeD-BHXd-1mg9dqQ.png

Let’s start with the basic analysis.

The first thing I usually do when analyzing a binary is to take a look at the functions in the program. Here, we see two “entry” functions, but in these crack-me challenges, they are not used. Nevertheless, it’s always a good practice to analyze them.

1_87xdJNNljH1MINPIOj-gZA.png

We find a “main” function, which we will analyze here.

The next thing I typically do is to analyze the strings in the program to get an idea of what’s inside the binary, especially the failure and success strings.

1_goQspBrXG3cO04_PiIzpCg.png

First, we see the functions used (which can be very interesting in the context of a binary with strcmp, for example, to find the compared password). Then, we find the strings we saw during the program execution:

1_QhMEMQU3ap4nnM-KK0r5Og.png

“Password1”! This is probably the password for the binary.

1_BO3YB9Lz-e2tKSF7EqOb0g.png

Wrong, it would be too simple.

Enough talk; let’s finally see what this “main” function does.

1_amSH-1nPnUpGYQvXAy78Kw.png

The first thing that catches my eye is the presence of a loop (the green arrow following the jump at 0x1163), right after the comparison (cmp eax, edx). What I understand here is that there are multiple conditions that need to be met before the program succeeds.

We can also confirm the presence of a loop with the increment of the rcx register by 1 at each iteration, which corresponds to a counter.

1_ygfhqPa01TMPYzZQnHYhWA.png

By looking at the decompiler (which I rarely do because I don’t understand much in C), we confirm once and for all the presence of a loop with “while.”

Static analysis doesn’t give us a way to guess the password. Let’s start debugging and focus on this block, with a comparison that, if successful (the values are equal), will loop.

1_vD8MownqoLoN7qkTIcHvZg.png

Here’s the block we’re interested in.

I start debugging with “password1” as an argument, and we’ll try to find out why this password doesn’t work.

1_SfpLstlzlxTydeLc5m03cw.png

We’re ready! I’ve positioned myself at the beginning of the “main” function, and we’ll quickly go through the lines that don’t interest us.

1_jsRTddp3waj9pNqgj_HOow.png

Before we start, at the second instruction, we see that the program checks if we’ve provided an argument by comparing edi (the pointer) with 2. Since rdi is at 2, the program continues.

1_Tdddz_10xTepm40zhyxxKQ.png

The rax register is loaded with the hexadecimal value of ‘p’.

The first interesting instruction is where the program puts ‘p’ into the eax register and resets the ecx register to zero, which will be our counter. We also see that the offset of the string ‘password1’ that we tested earlier is loaded into rdi.

1_t38CK3z675oC7pth17M6zA.png

Another interesting block: we see that the edx register is loaded with the value of rsi + rcx (where rsi is the register containing our password and rcx is the counter). So, we have a test of the first index of our string, which is argc[0].

We also see a test dl, dl that jumps to the end of the program (the successful end!) if the value of the least significant bits of rdx is 0. So, we know that the string to guess ends with 0, which terminates the loop.

1_LSFmfGMsBFxpNEGC7AAKKg.png

Stop! eax has been decremented, and it now contains the value 6f, which corresponds to ‘o’. The program then compares this value with the first letter of the password we provided, which is stored in rdx.

1_YpNNFChJNMi7imFF8ujvtw.png

As expected, ‘o’ is not equal to ‘p’, so the program immediately breaks and shows us that the password is incorrect.

So, let’s run the program again with ‘o’ as the first letter to see what happens in this case.

1_nDTmMXbHvnFkfLHtTH3yA.png

Here we go again.

1_A0CmSgeIG0BOto6w4pzn0Q.png

I skipped all the previous steps because we already know what happens, so let’s go back a bit before our comparison. By examining our registers, we see that rdx (our password) is at 6f (‘o’, the first letter). Let’s see the comparison after the decrement of eax.

1_9KiamJ1IAJcM4w9Epq0tYw.png

Finally, we have a new block! Since rax and rdx are equal, we enter the loop, rcx increments by 1 (we can translate this as “1 comparison has been made”). Then, eax will change and move to rdi + rcx (where rdi is the offset of the password “password1” loaded at the beginning of the program, and rcx is the counter, so eax = character_to_find[1]).

Once again, the test al, al instruction that ends the program if the result is 0 confirms one last time that the string to find ends with 0.

1_G0awdE3fdQjJE15DACzbUg.png

We loop back to this block (which has been used before), and here, edx is incremented by the counter. This means that the next value to test will be argc[1], which is the letter ‘a’ from our argument ‘oassword1’.

1_TVFdIPZypA6tBjgcRaCbuQ.png

eax decrements further and now contains the value 60, which is “`”.

Here, I’m a bit confused. Initially, I thought that with each loop, the index of the “password1” string hardcoded in the binary would change, but in a random way. In other words, we could have ended up with a password like “srwaps1od,” but here, a backtick is contained in eax, which doesn’t correspond to any letter in the “password1” string.

I then remember that we are certain that the string ends with 0, so there is a change in the values of the characters in the string with each iteration.

1_j07-f4pjrwbLnIv8nE6Z_A.png

No surprises here, as ‘a’ is not equal to “`”, the program breaks.

It would have been possible to solve this crack-me by running the binary several times, each time with a different letter from the ASCII table, discovering the next character at each iteration, and modifying our argument so that the condition is satisfied.

But with some reflection, I notice that the hexadecimal code (60) in rax corresponds to the ASCII code 96 of the character “", and, surprisingly, just below, we have the character "a" previously loaded in eax` before being decremented:

1_fMQr7v5FjKGEZIQdrWNXKA.png

So, I conclude that in each loop, the binary compares the index i (represented by the rcx register) of the argument with that of the password -1, but not -1 in the index, -1 in the hexadecimal value. This will change the ASCII code of the character in the string.

Therefore, I start decomposing the “password1” string by rewriting it with the previous ASCII table value. For example, ‘p’, which has an ASCII value of 70, becomes 6f, which is the value of ‘o’.

Note that “o” was indeed the first character of the password that we found.

By decomposing this string, I arrive at “o`rrvnqc0,” which must be the password for the binary.

1_0_YjW0RoaT1e3NH_aYBaQQ.png

Here, the “" allows us to pass a quote in an argument.

Great! The password is “o`rrvnqc0.”

Thanks for reading this first write-up, which, believe it or not, took me a few hours to write. We’ll meet again very soon for the crack-me number 3 in this series.

Crack-me: https://github.com/NoraCodes/crackmes

Cutter: cutter.re

Licensed under CC BY-NC-SA 4.0
comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy