Bypassing Passwords Through Reverse Engineering
I am planning to launch a Medium series about Carnegie Mellon University’s famous ‘Binary Bomb’, but before getting our hands dirty with that, I wanted to write a small RE50 blog post, hence you are reading this.
Since you are reading this blog post, I will assume that you do not need those boring definitions with regards to what is reverse engineering, what is assembly, or disassembler, etc. And just like how I like to read it, I will jump start on the fun part!
For this blog, I will be using nasm_crack from crackmes.one where you can download here. Do not forget that the password for the downloaded zip file is ‘crackmes.one’
< Part 1: Recon />
Sometimes we think that everybody is a secure coder, a clean coder. It is not always possible to find the answer in the open. That is actually very intuitive, but not a good practice. Be it a reverse engineering challenge or penetration testing, a simple recon on the target will often yield very important intel. Let’s make it a rule of thumb and always sniff the target before jumping on it.
One of the first things I do is to run the ‘file’ command on the target which I do with the ‘nasm_crack’ file too.
Here I get some idea about the target. Firstly, it is a 64-bit file. It is an ELF that stands for ‘Executable and Linkable Format’ which hints to me that this is a Linux executable. Moreover, LSB stands for least significant byte so our binary is little-endian.
Going forward, the binary is statically linked, which means it is not dynamically linked, hence we will be able to find the contents of the file directly in the file since that is where they were inserted during the linking process. Another very good hint is that the file is not stripped which is a great advantage since it signals that during the compilation process, the function names and related memory fields are untouched and not stripped off.
< Part 2: We Are Pulling the Strings(pun intended)/>
Strings are ASCII characters where each one of them is stored as one byte. Depending on the application we want to reverse engineer, finding the hardcoded strings can be very helpful, sometimes it can even accelerate our job by hours if not by days. So without further ado, I will search for the hardcoded ASCII strings in the file, using the strings command:
So without running the application, I saw that this is a basic password-checker thanks to the strings command. Also, did you notice something? There is an interesting string over there that reads
supersecret. That could be our password. Let's just write it down somewhere and continue our surgery.
I see some pairs of words: ‘Correct! — Wrong!’ I safely assume that these are the stdout messages upon entering my password. Also, a careful eye will notice another pair: ‘passwd — input’. This tells me that there is an input, that is my input and there is a passwd somewhere, so there is a comparison ongoing.
I can use further tools to have a better picture like we can use objdump, nm, ltrace, strace, and so on. But given the size and the complexity of the application, I believe this much intel is enough so far.
< Part 3: Surgery on the Binary/>
At this point, I can take benefit from supercool gadgets at my disposal, all of which are free and open-source (except IDA PRO). If I want to go for the old-good terminal, I can use objdump to have static analysis and read the binary dump. I can use my favorite gdb, or redare2 for further analysis. As for GUI options, I can use IDA, NSA’s now open-sourced Ghidra, or other sources like Binary Ninja, Hopper Disassembler, Cutter...
Here is the screenshot of the objdump output. Note that I used -M flag with intel argument to escape from arguably uglier AT&T syntax and switched to Intel syntax. With the -d flag, I pointed to the file.
For the sake of ease, I will use Cutter, which is based on redare2, to illustrate the beginner’s magic. However, your sharp eyes should already catch important steps on the image above.
< Part 4: Going Visual/>
When I ran Cutter in write mode and checked for the graph view, the application gave me the following graph:
Checking the graph, I started to write pseudocode immediately:
Here I have a couple of options to proceed.
One of them is to set a breakpoint where the program takes the input to compare. Since I can give an empty string as input, I do not have to provide any command-line arguments and just let the program run and pause in the line just before mov ecx, 0xb line.
At that moment, the expected password is already loaded into the memory to be compared with the user-supplied input so freezing the application there will let me go to the exact memory address and read the password from there.
Let's do it:
I set the breakpoint to the place where input is written into rsi register, which is after the expected password is taken into the memory address: 0x402026.
To make sure, I can check the current register values:
All is set up, rdi register is exactly where I want it to be. So I will just go and read it in the hex editor:
In the picture, the 0x402026 address is (0x042020 from left blocks and +6 from the top level) showing me that 0x402026 is starting to read the password which is supersecret.
This is confirming the strings output and verifying that the initial reconing is super important. Once I go and run the application, supply it with the passwords, voila! it works!
The above-mentioned approach is fine, but a bit longer than it should be for this small and basic application. A careful eye can see that our only blockage to the correct_func is actually a string comparison. So instead of trying to find the string — don’t forget that you may not always be as lucky as this example to find the answer- we can directly manipulate the code logic.
To that end, I will read the code again and see what the code is doing. The code basically reads both strings byte-by-byte and compares them. If they are equal, I am directed to exit0,(assured by je mnemonic) which is the success code.
So what if I do not know the password but still want to bypass the application? If I change the je mnemonic to something else, I can redirect the course of the application right?
So here I have 2 options:
The first option is, changing je with jne which means Jump Not Equal. This will work in all possible conditions except two:
- an overflow-causing input in the memory that results in a crash
- If I supply the app with the real expected password.
This is less than ideal because I want my solution to work always except for the overflows. See below:
Although the password is correct, since I changed je mnemonic to jne, the application started to accept everything except the correct password. This is not optimum.
The second option is, bypassing the wrong_function path and directing the application to the correct_function no matter what the input is. I can do it by changing the same mnemonic. But this time, not to jne, but the more neutral mnemonic, jmp. Unlike the other jumping mnemonic, the jmp is not conditional and bypasses the string comparison made just before its own call. Let's do it.
I will change the je mnemonic and will redirect it to the point where correct_function starts, which is the memory address 0x0401000. Hence the final edition will be jmp 0x041000.
As you see, this time the input supersecret worked alongside other arbitrary passwords although in the previous option it was the only input that could cause a failure.
< Part 5: Final Words/>
Hence, in a matter of a couple of minutes, I could bypass the password protection and got my hands on the application and did it in a couple of different ways. If you enjoyed this far, I can assure you that all the fun starts after this since this example was a super basic one just to start getting our hands dirty.
In the following posts, I will go through the Binary Bomb step by step and show how to use GNU Debugger (gdb), and finish that challenge in the span of a couple of posts.
Binary Bomb will be followed by real-world examples, and I am planning to go for WannaCry ransomware.
Please ping me if you have any suggestions and/or for errata purposes.
Thanks for reading. Until next time..