Eguite - reverse Link to heading
eguite.elf is a rust graphical application (coded with the library “egui”), checking for a specific license.
Static analysis Link to heading
Opening the binary in ghidra, we quickly see that it’s gonna be a mess finding our code amount all the boilerplate/graphical stuff.
I first tried looking for string in the binary like “license”, “check”, “SECCON”, but nothing looked interesting. I the tried to find specific functions name in the symbol tree in ghidra (thankfully the binary was not stripped) and found something really interesting when searching “click”: a function named _ZN6eguite7Crackme7onclick17ha26112793d42c9d8E
With no surprise, the code checking the license seems to be here. (some variables in the screenshot have been renamed)
The first if checks that the value at param+0x90
is 0x2b (43). I guessed that it probably is the length of the license, if it’s not the correct length it would return false without any more execution. This guess has been confirmed later during the dynamic analysis.
Now the next few lines of code are interesting and confirms that we are indeed at the right place:
input = *(uint **)(entry + 0x80);
if ((*(uint *)((long)input + 3) ^ 0x7b4e4f43 | *input ^ 0x43434553) != 0) {
return false;
}
if (*(byte *)((long)input + 0x2a) != 0x7d) {
return false;
}
Here we see some ASCII chars: 0x7b4e4f43 = “{NOC” and 0x43434553 = “CCES”. Those are “SECCON{” stored in little endian.
So we now know that entry+0x80
is the adress pointing to our raw string input and the first 7 letters should be “SECCON{”.
Note: Dereferencing input+3 means that instead of “HELLO_WORLD”, we have “LO_WORLD” and since it’s stored as a DWORD (the uint in the decompiled code) , we have only 4 bytes, so “LO_W”. If we used “SECCON{TEST}” as input, we would have input+3 = "CON{"
(wich is “{NOC” in little endian).
On the next if, we are testing that the byte at index 0x2a (42) is equal to “}” wich is the end of the flag.
Seing the rest of the function that seems to be pretty hard to understand, I switched to dynamic analysis.
Dynamic analysis Link to heading
Placing a breakpoint on _ZN6eguite7Crackme7onclick17ha26112793d42c9d8E
and entering some junk flag in the application, we can access the code of the function.
We can skip the test for the length, we know what it’s doing
0x00005555555b417e <+14>: cmp QWORD PTR [rdi+0x90],0x2b
0x00005555555b4186 <+22>: jne 0x5555555b44d9 <_ZN6eguite7Crackme7onclick17ha26112793d42c9d8E+873>
And just after that, we see our 2 ifs that check the flag format
0x00005555555b418c <+28>: mov r15,QWORD PTR [rdi+0x80]
0x00005555555b4193 <+35>: mov eax,0x43434553
0x00005555555b4198 <+40>: xor eax,DWORD PTR [r15]
0x00005555555b419b <+43>: mov ecx,0x7b4e4f43
0x00005555555b41a0 <+48>: xor ecx,DWORD PTR [r15+0x3]
0x00005555555b41a4 <+52>: or ecx,eax
0x00005555555b41a6 <+54>: jne 0x5555555b44d9 <_ZN6eguite7Crackme7onclick17ha26112793d42c9d8E+873>
0x00005555555b41ac <+60>: cmp BYTE PTR [r15+0x2a],0x7d
0x00005555555b41b1 <+65>: jne 0x5555555b44d9 <_ZN6eguite7Crackme7onclick17ha26112793d42c9d8E+873>
We easily confirm the guess we made during the static analysis simply by looking at our registers here.
For the next part, we recognize a loop that uses ecx as index, starting from 0x13 to 0.
0x00005555555b41d0 <+96>: add rax,0x1
=> 0x00005555555b41d4 <+100>: add rcx,0xffffffffffffffff
0x00005555555b41d8 <+104>: je 0x5555555b4235 <_ZN6eguite7Crackme7onclick[...]+197> # Exit Loop
0x00005555555b41da <+106>: cmp rax,rbx
0x00005555555b41dd <+109>: je 0x5555555b427f <_ZN6eguite7Crackme7onclick[...]+271> # Return False
0x00005555555b41e3 <+115>: movzx edx,BYTE PTR [rax]
0x00005555555b41e6 <+118>: test dl,dl
0x00005555555b41e8 <+120>: jns 0x5555555b41d0 <_ZN6eguite7Crackme7onclick[...]+96> # Continue Loop
The only thing that this loop does, is removing $ecx bytes from the start of the input.
So before the loop, rax = "SECCON{123456789abcdef...}"
And after the loop, rax = rax+0x13 (19) = "def...}"
Once out of the loop, the code will check that BYTE PTR [rax] == 0x2d
, 0x2d being “-”, we know that at index 19, there is a “-”.
This loop+checking for “-” is done 3 times and gives us a pattern for our flag:
flag[19] = "-"
flag[26] = "-"
flag[33] = "-"
SECCON{AAAAAAAAAAAA-BBBBBB-CCCCCC-DDDDDDDD}
Now for the hard part! Link to heading
We now enter the part where our kinda sanitized input will be checked with the actual flag. The issue here, is that the code is using some rust functions and I had to understand what they were doing first.
Continuing in the function, we see a call to _ZN95_$LT$alloc..string..String$u20$as$u20$core..iter..traits..collect..FromIterator$LT$char$GT$$GT$9from_iter17h6979761c404513e0E
… Yeah that’s a long name, but we can find some interesting words, “alloc” “String” “iter” “FromIterator” “char”.
And looking at the registers after the call, i see in the stack “AAAAAAAAAAAA”, at this point my input being “SECCON{AAAAAAAAAAAA-BBBBBB-CCCCCC-DDDDDDDD}”, it seems that this function call splitted our flag and retrieved the first part, “AAAAAAAAAAAA”.
A few instruction later, we have again a call to a function, this time it’s _ZN4core3num14from_str_radix17h893e9e49eefa3035E
. Asking google what “rust from_str_radix” is, I understood that this was to transform our input (string) into an int in a certain base. (We dont really care wich base since it’s stored as hex anyway)
Alright so, we split our flag, get the first part and stores it as an integer. We can see that we are right by looking at the stack:
0x007fffffffad28│+0x0008: 0x0000aaaaaaaaaaaa
This is done for every other part of the flag, and here’s the state of our register:
r12, r13, rbp and r14 stores the 4 parts of our input.
A few instructions later, we found what we are looking for, actual comparaisons !
(asm is simplified)
→ 0x5555555b4633 <eguite::Crackme::onclick+1219> lea rcx, [r12+r13*1]
0x5555555b4639 <eguite::Crackme::onclick+1225> cmp rcx, 0x8b228bf35f6a
[...]
0x5555555b464c <eguite::Crackme::onclick+1244> mov rcx, rbp
0x5555555b464f <eguite::Crackme::onclick+1247> add rcx, r13
0x5555555b4652 <eguite::Crackme::onclick+1250> cmp rcx, 0xe78241
[...]
0x5555555b4664 <eguite::Crackme::onclick+1268> lea rcx, [r14+rbp*1]
0x5555555b465f <eguite::Crackme::onclick+1263> mov rsi, 0xfa4c1a9f
0x5555555b466f <eguite::Crackme::onclick+1279> cmp rcx, rsi
[...]
0x5555555b4678 <eguite::Crackme::onclick+1288> add r12, r14
0x5555555b467b <eguite::Crackme::onclick+1291> cmp r12, 0x8b238557f7c8
[...]
0x5555555b468e <eguite::Crackme::onclick+1310> xor rbp, r13
0x5555555b4691 <eguite::Crackme::onclick+1313> xor rbp, r14
0x5555555b4694 <eguite::Crackme::onclick+1316> cmp rbp, 0xf9686f4d
So! The correct flag need to respect those equations:
r12 + r13 == 0x8b228bf35f6a
rbp + r13 == 0xe78241
r14 + rbo == 0xfa4c1a9f
r12 + r14 == 0x8b238557f7c8
rbp ^ r13 ^ r14 == 0xf9686f4d
Using this script i was able to solve this and retrieve the correct flag: SECCON{8B228B98E458-5A7B12-8D072F-F9BF1370}