Reversing a Simple CrackMe with Ghidra Decompiler

Craig Young
5 min readApr 10, 2023

--

In this article, I will walk through a simple crackme challenge from the collection of sample files for A Guide to Reversing with Ghidra which will be taught this summer at Black Hat USA. The challenge file is available for download along with a slightly more complicated variation you can try on your own.

UPDATE 3/2/24: Registration is currently open for “A Basic Guide to Bug Hunting with Ghidra” at Black Hat USA 2024. The two day class will be offered August 3–4 and again August 5–6.

Today’s crackme challenges involve recovering an algorithm used to validate an unlock key. The objective is to, using only static analysis, understand the logic employed to check the key and then use this knowledge to make a key generator.

Download the unlock_me1 challenge file here if you want to follow along with the walkthrough. You can also download the ‘Take Home Challenge’ introduced at the end of this post.

The basic steps for handling a challenge like this are:

Step 1: Create a Ghidra project and import the challenge program.

Step 2: Open unlock_me1 in CodeBrowser and initiate auto-analysis.

Step 3: Find the main function (first argument to __libc_start_main()

Step 4: Rename variables and functions until you understand the logic.

Step 5: Reconstruct the logic used to check input codes.

Step 6: Generate and test a valid input code.

Finding Main

After importing and analyzing the unlock_me1 you will need to locate the entry point for the function. For a libc application such as this, __libc_start_main() is used to load main() within an appropriate environment. You can find this in the binary by using the ‘G’ keyboard shortcut from Ghidra’s Listing view for unlock_me1 and jumping to __libc_start_main() which will be found as a Thunked-Function as shown here:

Go To __libc_start_main by pressing ‘G’ in Listing view
Thunked-Function has an XREF from FUN_00101080

When opening the calling XREF from FUN_00101080:001010a4 it shows as follows:

Decompiled entry into main()

Per the documentation, the first argument into __libc_start_main() is the main() function. You can double-click FUN_00101165 to jump into the decompiled view of this function and update the signature to match the standard convention:

Adding the Signature for main

Now we can look at the decompiled output and start looking for variables to rename within the function. To begin with, Ghidra shows us the following code:

Starting from the top, we see that user input is collected via scanf on line 13 and stored into local_38 and the string length is placed in sVar2. These variables can be renamed input and input_len respectively making the decompiled output as shown here:

Decompiled view after renaming input and input_len

It’s now clear that line 15 is checking the input length and will reject any input unless it has a length of exactly 0x14 (20 in decimal). We can see local_1c and local_20 both get initialized to 0 before entering a while loop. Within the loop we can see that local_20 is being used as an index into input on line 22 and then local_20 is incremented on line 23. Also on line 22 we see that the characters of the input string are being summed into local_1c giving us the logical names input_sum and index for local_1c and local_20 respectively as shown in the updated decompilation:

Cursor highlight showing use of input_sum in main

Reading through the code now it should be clear that the input must satisfy two properties to reach the ‘Unlocked!’ message on line 26. Per line 15, the input must be exactly 20 bytes long. Per line 25, the sum of the bytes in the input must equal a multiple of 7.

Solution

From this point, the crackme challenge has transformed into a basic math problem. It should be possible to sum any 20 printable characters and then use modulus to determine how many bytes to add or subtract to produce a multiple of 7.

For example, we can compute the sum of 20 ‘0’ characters and find that it is one off from a multiple of 7.

>>> ord("0")*20 % 7
1

This means we just need to add 6 bytes to the total sum. This is easily done by changing one ‘0’ (0x30) to ‘6’ (0x36) giving us the following solution:

$ ./unlock_me1
Enter the unlock key: 00000000000000000006
Unlocked!

Homework

The homework sample for this post follows the same basic idea but has some additional checks you will need to work out in order to produce a valid input. The decompiled main function in this sample is shown below:

Preview from the Take Home Challenge

Download Challenge #2 Here

Join Me At Black Hat To Learn More

As always, I hope you have enjoyed reading this post and hopefully learning something. If you found this interesting and want to learn more, please consider joining me in Vegas this summer for ‘A Guide to Reversing with Ghidra’. The class is offered twice, Saturday/Sunday and Monday/Tuesday. Early-bird pricing is still available but spaces are limited so please reserve your space today!

Weekend Class Registration

Monday/Tuesday Registration

--

--

Craig Young

I’m a 15-year veteran of the infosec industry with 200+ CVEs, two USENIX papers, a Pwnie award, and a bunch of bounties to my name. Currently teaching Ghidra.