Files
site/static/posts/crackmes-unlockme/post.typ
Jeremy 6de53568a8
All checks were successful
Build / build (push) Successful in 3s
crackmes unlockme
2026-02-12 20:58:55 -05:00

117 lines
5.9 KiB
Typst

#let post_slug = "crackmes-unlockme"
#let post_preview_image = "cover.png"
#let post_summary = "Dynamically reverse engineering a disassembled binary file using Binary Ninja"
#let post_date = "2026-02-12"
= Crackmes.one unlockme solution
First get a feel for the program.
```sh
$ ./a.out
Input method: <appname> <password>
$ ./a.out someapp somepass
Input method: <appname> <password>
$ ./a.out oneword
You have failed...
```
So this program takes one input.
Next, open it the program Binary Ninja. Checking the graph view we see the success string. Let's look at how we can get there. Since this is a C/C++ challenge we can label argc and argv right away.
#image("/static/posts/crackmes-unlockme/assets/image-1.png")
Lets switch from High Level Instruction Language to disassembly (top left). Then enter the debugger by clicking the bug on the left. Since this takes one input, press the blue gear and enter some input to the command line arguments. I went with `somestring`. Then exit out of that.
#image("/static/posts/crackmes-unlockme/assets/image-2.png")
A breakpoint at the first compare looks useful, to understand what's happening. Prior to this point, mainly initialization and setting variables for the program occur.
#image("/static/posts/crackmes-unlockme/assets/image-3.png")
Launch by clicking the red arrow in the top left, and again (resume) to reach the breakpoint.
#image("/static/posts/crackmes-unlockme/assets/image-4.png")
We know the program takes two inputs, and this comparison leads to complaints if not two are received. Also, we see `rbx` holding our argument string. By having two inputs ("./a.out" and "somestring") we already reach another block closer to our target. Click "Step into" until reaching the function call.
#image("/static/posts/crackmes-unlockme/assets/image-5.png")
Notice above `rbx` (our input string) is copied to `rdi` right before the call. This is for `c_fun003` to use, so we know this function takes our input string. Step in to it.
#image("/static/posts/crackmes-unlockme/assets/image-6.png")
Lets step through this repeating structure until reaching the bottom block, and watch which registers change on the left. Do this now.
My `eax` increments from `0x0` to `0xa`. Remember this function takes in a word, and my word is 10 (`0xa`) characters long.. So `eax` is the length of the string.
The second last line is `eax = 2*eax - 1` before returning. My function returns `0x13` (19).
Stepping out, the next thing we do is compare `eax` (19) to `0x1d` (29). My word fails. To pass this check, 2*len-1 = 29 => len=15.
#image("/static/posts/crackmes-unlockme/assets/image-7.png")
Set a 15 letter word in the command line argument the same as before. Remove the first breakpoint and put one on the first instruction of the next block to enter, and restart the debugger. Notice we are another block closer to our destination.
#image("/static/posts/crackmes-unlockme/assets/image-8.png")
Step into the first function then step over to this point. Stepping over some of those sub functions saves some mental space to understand what's happening at this level. If needed, the lower level functions can be recursively analyzed similarly later. While stepping over, pay attention to the registers and the instructions.
Try to understand what happened to reach the position below. Do this now.
#image("/static/posts/crackmes-unlockme/assets/image-10.png")
Recall the function took in the argument and the value `0x50`. Then, iterating through, each character was replaced with its xor on the inputted value `0x50`.
Move to before the next function. Notice we no longer have our original input in any register or the stack. All we have now is the xor'd value and some odd string thats been around since the first block.
#image("/static/posts/crackmes-unlockme/assets/image-11.png")
Going through this next function, try to understand what it does.
#image("/static/posts/crackmes-unlockme/assets/image-12.png")
For my xor'd word the grabs the first character, and since it doesn't match the first character of the odd word from the beginning it exits.
Here we can guess what this function does. We have an odd string, call it the password. If the beginning of our string matches the password, we will get to move on.
Let's construct such a string. We need the first characters xor'd with `0x50` to be `\t5#\t?%`.
Note we are looking for a key that makes this password. The program checks `(input[i]^0x50)^password[i] == 0`. Here's some math to isolate `input[i]`
```
(input[i]^0x50)^password[i] == 0
=> (input[i]^0x50)^password[i]^password[i] == 0^password[i]
=> input[i]^0x50 == password[i] # using xor properties a^a=0 and 0^a=a
=> input[i]^0x50^0x50 == password[i]^0x50
=> input[i] == password[i]^0x50 # using xor property a^a=0
```
Let's write a python script that does this and makes a 15 character long word.
```py
password = "\t5#\t?%"
key = ""
for char in password:
key += chr(ord(char)^0x50)
while len(key) < 15:
key += "A"
print(key)
```
_YesYouAAAAAAAAA_
Well that looks like a key! Let's try it!
... Ok that didn't work. Notice the first 6 characters were working but once we passed the `%` it exited, thats where we are going wrong. It read past the end of the password! Checking the address of the password, lets grab 15 bytes/characters from there and retry.
#image("/static/posts/crackmes-unlockme/assets/image-13.png")
```py
password = "\t5#\t?%\x13\"13;54\x1d5"
key = ""
for char in password:
key += chr(ord(char)^0x50)
print(key)
```
_YesYouCrackedMe_
Let's test this:
```sh
$ ./a.out YesYouCrackedMe
You have done it
```
There we go! That's it! And all that random junk being initialized in the beginning turns out to have been the xor encrypted password, it should look familiar now:
#image("/static/posts/crackmes-unlockme/assets/image-14.png")
= Notes
Author:
pranav\
Challenge Link: https://crackmes.one/crackme/5ff53c5c33c5d42c3d0165b7\
Description:\
Might not be straightforward.. but it should be fairly easy. Takes code as argument 1