After you extracted the tar, you will see two binaries called vm
and puzzle1
. Let's just execute them as shown on the web endpoint and see where we are getting.
Additionally, let's take a look at their file types.
Now we know what we are dealing with, the vm
binary is an ELF 64-bit executable and the puzzle1
is an ELF 32-bit RISC-V executable. So, we can already assume that vm
executes the puzzle1
binary and takes the role of a RISC-V32 emulator.
We can try to examine both the vm
and the puzzle1
binary with Ghidra through a local installation or remotely through for example dogbolt.org. When just pasting the executables into dogbolt and use Ghidra, we get a possible C-Code representation for both of the binaries, which you will be able to find here (/binaries_in_c). After analyzing the puzzle1
C-File which Ghidra yielded, we can see that in the main method, after the prompt to enter the password, user input is taken and saved along with its length. Afterwards, the length of the user input is being compared to a constant value of 0x2f
(47 in decimal) and the input itself with memcmp(3) to a global variable called password
. If the comparisons succeed (length == 0x2f && memcmp), the win
method will be executed.
printf("[\x1b[32m*\x1b[0m] please enter the passphrase: ");
gets(acStack_91 + 1);
sVar1 = strlen(acStack_91 + 1);
acStack_91[sVar1] = '\0';
sVar1 = strlen(acStack_91 + 1);
if ((sVar1 == 0x2f) && (iVar2 = memcmp(acStack_91 + 1,password,0x2f), iVar2 == 0)) {
win(acStack_91 + 1);
return 0;
}
Seems quite easy, right? Now we just need to find the password
variable. The password
variable though seems to be just a bunch of binary rubbish when printed with GDB. When checking the memcmp
implementation in the puzzle1
binary, we notice that it is a modified version of the classic memcmp
function. At this point, you could try a bunch of stuff, for example bypassing the comparisons, timing the memcmp
call for different characters, patching the binary, etc. What I ended up doing was investigating the vm
binary. How is the puzzle1
binary being executed? This can take some time, but what I soon noticed were plenty of similar statements like the following:
if (DAT_0040b341 != 0) {
fprintf((FILE *)__stderr_FILE,"[\x1b[32m+\x1b[0m] address=0x%08x, instruction=0x%08x\n",
(ulong)*param_1,(ulong)local_1c);
}
That means that there is a constant at address 0x0040b341
(see DAT_0040b341
) that when set to something else than zero prints out some interesting stuff to stderr. Let's try this out! For that purpose you might use GNU objdump to examine the assembly and patch the binary or GDB (GNU Debugger) where we can just change the value of this constant:
When we continue running the puzzle1 binary now, something gets printed.
Continuing.
[+] address=0x80000000, instruction=0xff010113
[+] address=0x80000004, instruction=0x00112623
[+] address=0x80000008, instruction=0x00000097
[+] address=0x8000000c, instruction=0x3a0080e7
[+] address=0x800003a8, instruction=0xf7010113
[+] address=0x800003ac, instruction=0x08112623
[+] address=0x800003b0, instruction=0x08812423
[+] address=0x800003b4, instruction=0x80001437
[+] address=0x800003b8, instruction=0xaf440513
[+] address=0x800003bc, instruction=0x00000097
[+] address=0x800003c0, instruction=0x678080e7
[+] address=0x80000a34, instruction=0xff010113
[+] address=0x80000a38, instruction=0x00112623
[+] address=0x80000a3c, instruction=0x00812423
[+] address=0x80000a40, instruction=0x00050413
[+] address=0x80000a44, instruction=0x00000097
[+] address=0x80000a48, instruction=0xc24080e7
...{TRUNCATED}
Apparently all instructions that are being executed by the vm
are printed! That means we can grab the base address of some method executed inside the memcmp
method and crack the password by iterating over all valid characters for each character position in the password and choose the one with which more instruction addresses are printed! That in combination with the length 0x2f
which we gathered earlier will get us to the password.
We can execute GDB with puzzle1
as the argument and enter i functions
which will yield some addresses. Note that for example the hash
function is executed inside the modified memcmp
, which has an address of 0x80000044
. We can use this and a simple python script with gdblib to crack the password and exploit the side channel leakage vulnerability. You can find the python script here (sol.py). You can run this in GDB by typing into your terminal first gdb vm
and afterwards source sol.py
while your current working directory is set to the directory where your vm
binary and your puzzle1
binary are contained in. We get following result:
We cracked the password! We can now enter this into our hackvm binary and will get
All in all a funny challenge.
Note Raw timing of the
memcmp
function would have been a possibility, too. Because of the whole OS noise though, this would require many more steps.
Note I recommend checking the pwntools framework out. It facilitates many processes in the domain of Pen-Testing and especially in RE.
We learned about how to approach reverse-engineering programs, by using tools such as Ghidra and dogbolt, reading Assembly with objdump or gdb or modifying values in memory with gdb and how to automate basic side channel leakage address call monitoring with gdblib in python. Tackle the next puzzle. Visit vmhack, a similar but harder reverse engineering puzzle.