A write up for the challenges posted here: https://github.com/wapiflapi/exrs
I’ve only done r1-r7, and I’ve yet to do r8 and r9
As straight forward as running “strings” on the binary
Some dynamic analysis is involved. We run the program through IDA, and breakpoint at the string comparison to see what it’s comparing to
Static analysis with Ghidra. We open the binary and let Ghidra decompile the function for us
A small step up in the difficulty, we have to analyze the code to see what value the loop stops at.
The flow pass if “check_password” returns 0
We can simulate inputs to the binary in IDA under “Debugger->Process Options->Parameters”
The loop checks the input against multiple strings, and if it matches, it checks if the counter value is equals to 539h, or 1337
We let the program run until EAX == 1337, and look at the string it’s comparing against. We do this by setting a breakpoint, and editing the Condition
When we let the program run and break on 1337, we find our required string:
The binary checks for an input string length to be 28h, or 40, if not it exits the program
The program proceed to do some complicated operations on our input string, and compares the final output the string “this_aint_that_simple_but_good_luck_haha”
When we try different inputs, the output is always deterministic. We can thus try to deduce the ASCII shift of our input string by taking the difference between the input ASCII and the output ASCII. Using this difference, we apply it to “this_aint_that_simple_but_good_luck_haha” to reverse what string is required to be inputted.
Static Analysis with Ghidra shows that the length of the input needs to be 21 characters long
We observe that local_10 is being returned, and this needs to be 0 at the end of the operation so that “password OK” may be printed.
This also means that local_19 needs to be 0 on each loop, so that local_10 will never be incremented. (Since it’s unsigned, we can’t minus any values)
We convert the code above to python for simpler analysis
looper is called 3 times, but if we look at what it’s doing, its XORing local_19 43 times with the input string of length 21. This is equivalent to XOR-ing the string with itself with the exception of the one character of our input string. This means that on each iteration, local_19 is XORed by 1 character of our string, and since looper is being run 3 times, 2 of it will cancel itself out, which means it’s only XORed once.
The final operation at index 0 would therefore be:
input_19 = input_19 ^ key1_list ^ key2_list ^ input_string
For input_19 to be 0, key1_list ^ key2_list ^ input_string need to be 0. Therefore, to figure out input_string, we just XOR key1 and key2 together (As shown in the bottom)
R7 is pretty straight forward. We open it with Ghidra to see this function happening
MD5_Init, MD5_Update and MD5_Final are all part of the MD5 hashing operation. Simply put, it takes out string, generates an MD5, and checks it with -0x71a6158f5f87a14, or 0x8e590ea70a0785ec, and -0x430566d217348d26 or 0x0bcfa992de8cb72da.
local_c0 is the top half of the MD5 string, and local_c8 is the bottom of the MD5 string. Because the stack grows downwards, we need to reverse the hex values. The final MD5 string we need to obtain is: ec85070aa70e598eda72cbe82d99fabc
Throwing this to any cracker online, we get the flag to be: md5password
This set of challenges showed that “Reverse Engineering” isn’t just about looking at assembly code, but to understand the logic behind what a program is doing, and how to figure out the right input. No patching is meant to be done, and the flag can be found purely by analysis.
Perhaps I’ll try r8 and r9 someday, but as there are no solutions posted, and given my stubborn nature, I fear I may be stuck on those problems for a long time, which will be detrimental to my learning. In my next set of challenges, I’ll be focusing more on cracking here: https://crackmes.one/
Thanks for reading!
Leave a Reply