Real quick step away from the RSA series. I just put in an application for Ubuntu and I want to make sure I have at least one something here about reversing in case they check out my site. ::prayer hands::
I like to use crackmes.one to get publicly available crack-mes. This particular file can be found there and is called timotei crackme#1. The solution you can find on the site is actually this solution. It appears that I am, to date, the only one who has submitted an acceptable solution.
I expect this is because it uses a custom rolled hash function that clobbers information we need to actually reverse the algorithm used. Which leaves us with brute force or OSINT to obtain the answer.
On the other hand, there is a pin that needs to be obtained as well. And, despite it’s use of modulo division, there are several ways to crack it to obtain a valid pin
The preamble
I was focusing on learning radare2 and will therefore be giving instructions on solving this challenge using the r2 tool set.
However, I started by running the `file` command against the file. This showed that it was a unstripped 64-bit ELF. Running ‘readelf -S’ against the file showed that there were 5 named sections in the file
Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .text PROGBITS 0000000000401000 00001000 0000000000000162 0000000000000000 AX 0 0 16 [ 2] .data PROGBITS 0000000000402000 00002000 00000000000001b1 0000000000000000 WA 0 0 4 [ 3] .symtab SYMTAB 0000000000000000 000021b8 0000000000000258 0000000000000018 4 21 8 [ 4] .strtab STRTAB 0000000000000000 00002410 00000000000000b1 0000000000000000 0 0 1 [ 5] .shstrtab STRTAB 0000000000000000 000024c1 0000000000000027 0000000000000000 0 0 1
It’s always a good idea to see what type of data is stored in our .data section. To check that out, we can run ‘readelf -x .data’ against the file.
Hex dump of section '.data': 0x00402000 2e5f3a74 696d6f74 65692063 7261636b ._:timotei crack 0x00402010 6d652331 3a5f2e2e 2e746861 6e6b7320 me#1:_...thanks 0x00402020 47726261 76614369 676c6120 666f7220 GrbavaCigla for 0x00402030 62657461 20746573 74696e67 21002e3a beta testing!..: 0x00402040 6b6e6f63 6b2c6b6e 6f636b2e 2e2e796f knock,knock...yo 0x00402050 75722070 696e2070 6c656173 652e2e2e ur pin please... 0x00402060 3a200a2e 3a576865 72652064 6964202b : ..:Where did + 0x00402070 46726176 69612074 61756768 74207573 Fravia taught us 0x00402080 3f203a20 0a4e6f20 6e656564 20746f20 ? : .No need to 0x00402090 70617463 68206f72 20627275 7465666f patch or brutefo 0x004020a0 7263652c 20657870 6c6f7265 20746865 rce, explore the 0x004020b0 20776562 2e2e2e00 0a2e3a47 6f6f642c web......:Good, 0x004020c0 20746861 74277320 616c6c20 666f7220 that's all for 0x004020d0 68697374 6f727920 6c657373 6f6e2074 history lesson t 0x004020e0 6f646179 3a2d290a 00000000 00000000 oday:-)......... 0x004020f0 00000000 00000000 00000000 00000000 ................ 0x00402100 00000000 00000000 00000000 00000000 ................ 0x00402110 00000000 00000000 00000000 00000000 ................ 0x00402120 00000000 00000000 00000000 00000000 ................ 0x00402130 00000000 00000000 00000000 00000000 ................ 0x00402140 00000000 00000000 00000000 00000000 ................ 0x00402150 00000000 00000000 00000000 00000000 ................ 0x00402160 00000000 00000000 00000000 00000000 ................ 0x00402170 00000000 00000000 00000000 00000000 ................ 0x00402180 00000000 00000000 00000000 00000000 ................ 0x00402190 00000000 00000000 00000000 00000000 ................ 0x004021a0 00000000 00000000 00000000 00000000 ................ 0x004021b0 00 .
Using radare2 to analyze the binary
Let’s open our file in radare2 `r2 <filename>`, analyze it `> aaa`, and print our functions `> afl`. We see only one function:
0x00401000 19 354 entry0
┌ 354: entry0 (int64_t arg4); │ ; arg int64_t arg4 @ rcx │ 0x00401000 b801000000 mov eax, 1 ; [01] -r-x section size 354 named .text │ 0x00401005 bf01000000 mov edi, 1 │ 0x0040100a be3e204000 mov esi, loc.message ; 0x40203e ; ".:knock,knock...your pin please...: \n.:Where did +Fravia taught us? : \nNo need to patch or bruteforce, explore the web..." │ 0x0040100f ba24000000 mov edx, 0x24 ; '$' ; 36 │ 0x00401014 0f05 syscall │ 0x00401016 b800000000 mov eax, 0 │ 0x0040101b bf00000000 mov edi, 0 │ 0x00401020 bee8204000 mov esi, loc.buffer ; 0x4020e8 │ 0x00401025 ba0a000000 mov edx, 0xa │ 0x0040102a 0f05 syscall │ 0x0040102c 6780b8e72040. cmp byte [eax + 0x4020e7], 0xa │ ┌─< 0x00401034 7427 je loc._garbage_end │ │ 0x00401036 bf00000000 mov edi, 0 │ │ 0x0040103b 48beb0214000. movabs rsi, loc.dummy ; 0x4021b0 │ │ 0x00401045 ba01000000 mov edx, 1 │ │ ;-- _garbage: │ │ ; CODE XREF from entry0 @ 0x40105b(x) │ ┌──> 0x0040104a b800000000 mov eax, 0 │ ╎│ 0x0040104f 0f05 syscall │ ╎│ 0x00401051 803c25b02140. cmp byte [loc.dummy], 0xa ; [0x4021b0:1]=0 │ ┌───< 0x00401059 7402 je loc._garbage_end │ │└──< 0x0040105b ebed jmp loc._garbage │ │ │ ;-- _garbage_end: │ │ │ ; CODE XREFS from entry0 @ 0x401034(x), 0x401059(x) │ └─└─> 0x0040105d bfe8204000 mov edi, loc.buffer ; 0x4020e8 │ 0x00401062 29c9 sub ecx, ecx ; arg4 │ 0x00401064 28c0 sub al, al │ 0x00401066 f7d1 not ecx ; arg4 │ 0x00401068 fc cld │ 0x00401069 f2ae repne scasb al, byte [rdi] │ 0x0040106b f7d1 not ecx ; arg4 │ 0x0040106d ffc9 dec ecx ; arg4 │ 0x0040106f 89c8 mov eax, ecx ; arg4 │ 0x00401071 83f803 cmp eax, 3 ; 3 │ ┌─< 0x00401074 0f8ede000000 jle loc.out │ │ 0x0040107a ffc8 dec eax │ │ 0x0040107c 31db xor ebx, ebx │ │ 0x0040107e ba39050000 mov edx, 0x539 ; 1337 │ │ 0x00401083 b9e8204000 mov ecx, loc.buffer ; 0x4020e8 │ │ 0x00401088 89ce mov esi, ecx │ │ ;-- __: │ │ ; CODE XREF from entry0 @ 0x401094(x) │ ┌──> 0x0040108a 678a5901 mov bl, byte [ecx + 1] │ ╎│ 0x0040108e 01da add edx, ebx │ ╎│ 0x00401090 ffc1 inc ecx │ ╎│ 0x00401092 ffc8 dec eax │ └──< 0x00401094 75f4 jne loc.__ │ │ 0x00401096 01d2 add edx, edx │ │ 0x00401098 89d0 mov eax, edx │ │ 0x0040109a 31d2 xor edx, edx │ │ 0x0040109c b911000000 mov ecx, 0x11 ; 17 │ │ 0x004010a1 f7f1 div ecx │ │ 0x004010a3 80c230 add dl, 0x30 ; 48 │ │ 0x004010a6 678a06 mov al, byte [esi] │ │ 0x004010a9 28c2 sub dl, al │ │ 0x004010ab 84d2 test dl, dl │ ┌──< 0x004010ad 7405 je loc.go_on │ ┌───< 0x004010af e9a4000000 jmp loc.out │ │││ ;-- go_on: │ │││ ; CODE XREF from entry0 @ 0x4010ad(x) │ │└──> 0x004010b4 b801000000 mov eax, 1 │ │ │ 0x004010b9 bf01000000 mov edi, 1 │ │ │ 0x004010be be63204000 mov esi, loc.message2 ; 0x402063 ; ".:Where did +Fravia taught us? : \nNo need to patch or bruteforce, explore the web..." │ │ │ 0x004010c3 ba21000000 mov edx, 0x21 ; '!' ; 33 │ │ │ 0x004010c8 0f05 syscall │ │ │ 0x004010ca b800000000 mov eax, 0 │ │ │ 0x004010cf bf00000000 mov edi, 0 │ │ │ 0x004010d4 be4c214000 mov esi, loc.buffer2 ; 0x40214c │ │ │ 0x004010d9 ba0a000000 mov edx, 0xa │ │ │ 0x004010de 0f05 syscall │ │ │ 0x004010e0 6780b84b2140. cmp byte [eax + 0x40214b], 0xa │ │┌──< 0x004010e8 7427 je loc._garbage_end2 │ │││ 0x004010ea bf00000000 mov edi, 0 │ │││ 0x004010ef 48beb0214000. movabs rsi, loc.dummy ; 0x4021b0 │ │││ 0x004010f9 ba01000000 mov edx, 1 │ │││ ;-- _garbage2: │ │││ ; CODE XREF from entry0 @ 0x40110f(x) │ ┌────> 0x004010fe b800000000 mov eax, 0 │ ╎│││ 0x00401103 0f05 syscall │ ╎│││ 0x00401105 803c25b02140. cmp byte [loc.dummy], 0xa ; [0x4021b0:1]=0 │ ┌─────< 0x0040110d 7402 je loc._garbage_end2 │ │└────< 0x0040110f ebed jmp loc._garbage2 │ │ │││ ;-- _garbage_end2: │ │ │││ ; CODE XREFS from entry0 @ 0x4010e8(x), 0x40110d(x) │ └──└──> 0x00401111 be85204000 mov esi, loc.message2_help ; 0x402085 ; "No need to patch or bruteforce, explore the web..." │ │ │ 0x00401116 be4c214000 mov esi, loc.buffer2 ; 0x40214c │ │ │ 0x0040111b b904000000 mov ecx, 4 │ │ │ 0x00401120 b8c59d1c81 mov eax, 0x811c9dc5 │ │ │ 0x00401125 bf93010001 mov edi, 0x1000193 │ │ │ 0x0040112a 31db xor ebx, ebx │ │ │ ;-- nextbyte: │ │ │ ; CODE XREF from entry0 @ 0x401137(x) │ │┌──> 0x0040112c f7e7 mul edi │ │╎│ 0x0040112e 678a1e mov bl, byte [esi] │ │╎│ 0x00401131 31d8 xor eax, ebx │ │╎│ 0x00401133 ffc6 inc esi │ │╎│ 0x00401135 ffc9 dec ecx │ │└──< 0x00401137 75f3 jne loc.nextbyte │ │ │ 0x00401139 3df8dccf86 cmp eax, 0x86cfdcf8 │ │┌──< 0x0040113e 7518 jne loc.out │ │││ 0x00401140 beb9204000 mov esi, loc.good ; 0x4020b9 ; ".:Good, that's all for history lesson today:-)\n" │ ┌────< 0x00401145 eb00 jmp loc.goodway │ ││││ ;-- goodway: │ ││││ ; CODE XREF from entry0 @ 0x401145(x) │ └────> 0x00401147 b801000000 mov eax, 1 │ │││ 0x0040114c bf01000000 mov edi, 1 │ │││ 0x00401151 ba2f000000 mov edx, 0x2f ; '/' ; 47 │ │││ 0x00401156 0f05 syscall │ │││ ;-- out: │ │││ ; CODE XREFS from entry0 @ 0x401074(x), 0x4010af(x), 0x40113e(x) │ └└└─> 0x00401158 b83c000000 mov eax, 0x3c ; '<' ; 60 │ 0x0040115d 4831ff xor rdi, rdi └ 0x00401160 0f05 syscall
If you’ve ever disassembled 32-bit ELF files, you may be used to the `int 80` instruction used for hand off to the kernel. One of the features of 64-bit was the inclusion of a new instruction: syscall. It’s still a hand off to the system kernel but uses the conventions found here.
As such, our first five lines equate to a sys_write call that prints the message ‘.:knock,knock…your pin please…:’ The input received by the user is then stored in a memory buffer just beyond the preexisting data in the .data section.
It is important to note that functions can have side effects that change data without anything being explicitly spelled out in the disassembly code. The sys_read call is one such function. When the sys_read function is evoked and executed, the length of the string that was input is stored in the eax register as a return value.
This is important here because our very next instruction ‘cmp byte [eax + 0x4020e7], 0xa’ uses that return value to create a pointer. Remember, the square brackets around this pointer mean that we are opening up and inspecting the memory address that the pointer inside those brackets is pointing to.
Essentially, the instruction is saying, take the length of the input string and add to it the address that represents the end of our stack .data section to create a pointer that points at the last byte of user input. De-reference that pointer and see if the byte located there matches the `0xa` (which is the same as `0x0a` which is the newline character).
Because of how sys_read works, the newline character should be the last character and this check should pass fine, skipping the section of code labeled _garbage and moving you right into the section labeled _garbage_end.
Once we make that little hop, we find
mov edi, loc.buffer ; 0x4020e8 sub ecx, ecx sub al, al not ecx cld repne scasb al, byte [rdi] not ecx dec ecx mov eax, ecx cmp eax, 3 jle loc.out
This basically just checks that the pin that you entered is at least three characters long.
Because it might not be super obvious how this is being done, we’ll break it down and see if we can’t flush out the details.
So, user input goes into edi register. ecx and al are both set to 0 through subtraction. ecx is then bitwise flipped, making it equal to all f’s. Unsigned, this is the largest number the register can hold. cld sets the direction flag to zero and reads from left to right.
Next we have a repeated (while not equal) scasb. According to the International Technological University, the scasb instruction looks for a particular byte within a string. The byte being looked for is held in the al register (which we know is set to zero). Thus, it looks at each character in the string and decreases the ecx register by one after each comparision until the sought byte is found. In this case, the memory space for our string buffer is zeroed out so the sought after byte 0x00 is found just behind our input string (which includes a 0x0a at it’s end).
The ecx is flipped again, getting the total string length, including the newline character. ecx is moved into eax and then compared against the constant 3. The requirement to continue, that is to say, not get jumped to loc.out, is for the string (including 0x0a) length to be greater than 3. In other words, without the newline character at the end, the input must be at least three characters long.
Breaking the pin
We know that we are expected to enter a “pin” and that the pin is expected to be at least three characters long. Now, let’s look at the logic for the pin itself and see how we can get through to the next step.
dec eax xor ebx, ebx mov edx, 0x539 ; 1337 mov ecx, loc.buffer ; 0x4020e8 mov esi, ecx
This section sets our registers to
| eax | ebx | ecx | edx | esi | | -------------------------------------- | --- | -------- | ----- | -------- | | length of user input (min length is 3) | 0 | 0x4020e8 | 0x539 | 0x4020e8 |
Then we have the actual algorithm for checking that our pin is acceptable.
;-- __: mov bl, byte [ecx + 1] add edx, ebx inc ecx dec eax jne loc.__ add edx, edx mov eax, edx xor edx, edx mov ecx, 0x11 ; 17 div ecx add dl, 0x30 ; 48 mov al, byte [esi] sub dl, al test dl, dl je loc.go_on jmp loc.out
It starts with a loop that adds the hex value of each character in our input string (see note below) to the edx register.
_Note_ : The loop begins with the character at index 1 and continues through to the newline character
It then doubles the edx register, moves the computed value into eax, and zeros out edx altogether. Next, it sets ecx to 0x11. Next comes a div instruction, which divides edx:eax (and hence why edx had to be zeroed out) by some dividend. It stores the quotient in eax and the remainder in edx. We then add 0x30 to the remainder of our division problem. Looking forward in the set of instructions, we see that the only part that actually matters is the remainder. Thus, this algorithm uses modulo division.
Next, we finally make reference to the first character in our input string by moving it’s hex value into the al register. We subtract this value from the dl register and then test to see if dl is zero. If it is zero, we contiue on, if it is anything other than zero, we are kicked out.
To the best of my knowledge, there is no way to actually reverse modulo division. If an anolog clock moves from being twelve o’clock to being one o’clock, it is impossible to determine by that fact alone whether it has only been one hour or thirteen hours that have passed. However, that same logic is somewhat beneficial to us because it means that there are multiple different inputs that will qualify as a valid pin.
In our clock example, the formula would be something akin to y=(12x + z) mod 12 where z=1. But since our x variable is a factor of some larger product that is evenly divisible by 12, it gets completely knocked out by the modular division. leaving y=z or, in this case, y=1.
The same type of formula can be utilized to obtain a valid solution for our pin algorithm as well. In this case, the formula is something like:
y – 48 = ((1337 + x + 10) * 2) mod 17
where y=ordinal value of first string character
and x=the sum of the ordinal values of the remaining string charaters.
Completing the maths, we get
y – 48 = (2x + 2694) mod 17
y – 48 = 2x mod 17 + 8
y – 56 = 2x mod 17
Remembering that we want each side to equate to zero, we can see that y equals 56 and x must be a number that is evenly divisable by 17. Options include 17, 34, 51, 68, 85, etc. From this, we can derive a number of solutions for a valid pin.
For those who can’t follow the maths, another way to solve this is to script a solution. Basically, we are going to rewrite the same algorithm in a scripting language (I prefer python) and then let it iterate through different solutions until it finds a valid answer.
We know that the pin needs to be at least 3 characters long. The smallest three digit number is 100. Thus, I will use this as our starting place in a python script built to perfrorm the same algorithm as outlined in the assembly code.
test = 100
while True:
pin = str(test)
var = 1337
var += sum([ord(c) for c in pin[1:]]) + 10
var *= 2
var %= 17
var += 48
if var == ord(pin[0]):
print(test)
break
test += 1
Running the above script will yeild one of many many acceptable pins. Which means that we have passed our first hurdle and may now move on.
So what’s next?
Next, we see the same familiar pattern for outputting text to the terminal followed by recieving input from the user. This time, we are trying to answer the question ‘.:Where did +Fravia taught us? : ‘ and our answer is being stored in loc.buffer2 at address 0x40214c.
If we look at our next instruction, we get a little hint… ‘mov esi, loc.message2_help ; 0x402085 ; “No need to patch or bruteforce, explore the web…”‘.
But before we try to google-fu ourselves a victory, let’s look over the actual algorithm that the program will be using to check for a valid answer.
mov esi, loc.buffer2 ; 0x40214c mov ecx, 4 mov eax, 0x811c9dc5 mov edi, 0x1000193 xor ebx, ebx
set’s our registers to
| eax | ebx | ecx | edi | esi | | ------------- | --- | --- | ---------- | --------------------- | | 2,166,136,261 | 0 | 4 | 16,777,619 | pointer to user input |
Next, we get into the actual algorithm
;-- nextbyte: mul edi mov bl, byte [esi] xor eax, ebx inc esi dec ecx jne loc.nextbyte cmp eax, 0x86cfdcf8 jne loc.out mov esi, loc.good ; 0x4020b9 ; ".:Good, that's all for history lesson today:-)\n" jmp loc.goodway
So… we multiply eax by edi and store the value in edx:eax but then make no further reference to edx in the algorithm. This means that we are essentially axing our most significant bits of our product and focusing solely on the least significant 32 bits of the product.
Next, we grab the value of the first character of the user input string and xor it against the least significant bits of our multiplication product, stored in eax. Then we step our pointer forward to point to the next character and decrease a counter in ecx. Since ecx was originally set to 4, we know that we are looking for a four-character answer. Or at least, that the algorithm is only going to check the first four characters of whatver answer we give it.
Once the program has repeated this process with the first four characters of the user input string, it will compare what is left in eax with 2,261,769,464. If that number is found in eax, we load our victory string and move to the loc.goodway. If not, we lose and are kicked loc.out.
Armed with what the program is expecting, it isn’t hard to search the internet for the expected string. But is there a way to figure this out strictly from the code itself? Unfortunately, there is not. The algorithm used acts as a basic hashing function whereby data that would be needed to reverse it is destroyed. I have no idea the collision rate for this algorithm but I did run a brute force check against the printable ascii characters 32 – 126 and the only one that worked was the intended solution.
But, like the hint said, there really is no need to brute force as the answer is readily available via internet search. And, if we get it all right, we should get dropped into our loc.goodway section where it prints our victory message: ‘.:Good, that’s all for history lesson today:-)’
;-- goodway: mov eax, 1 mov edi, 1 mov edx, 0x2f ; '/' ; 47 syscall
What about dynamic analysis?
To be honest, I really don’t see a need for dynamic analysis on this one. It could help you figure out the pin if you have yet to determine that piece, but otherwise, is unneccessary. However, for the sake of those still stuck on the pin, we’ll breifly look at how we can use dynamic analysis to figure that out.
Despite the fact that we have a pretty solid understanding about what this program does and how it works, it is unwise to take anything for granted. You should always have a sandboxed environment before opening up and running any untrusted program. I use a VM that I snapshot before starting any new project. The VM has no network card and copy/paste from guest to host is disabled. You can set up your sandbox however you like, but do set up something.
‘ood’ to reload our binary into debug mode. We want to break at the instruction ‘mov al, byte [esi]’. At this point in the algorithm, the program has already done all the maths it intends to do with the user string except to subtract the value of the first character from the dl register and then check dl for 0. Therefore, dl should hold the value of the expected first character.
Using this to our advantage, we dcu <address> to halt at our intended stopping point. On our way to it, we will be asked to enter a pin. We can enter any three characters. We will then be greeted with a message saying that we have hit our breakpoint. From here, we can ‘dr dl’ to obtain the hex value of the expected first character.
‘?a’ to look at our ascci table and figure out what our first character should be. Your new working pin should be the character you looked up, plus the last two characters of your original input. ‘dc’ to continue running the program to termination. ‘do’ to reload it and ‘dc’ to run it normal, this time entering your newly constructed pin.
For the win
Did you figure out the answer to the `+Fravia` question? If not, I’ll give you a hint… It is the reason he has the little `+` moniker in front of his name. Once you’ve figured it out and you have a valid pin, run the program and enter in the information as asked.
You should be greeted with: ‘.:Good, that’s all for history lesson today:-)’.
Congratulations!
And that is it. Hopefully, there was something in there for you. Stay well!