NyaVM [RE]

6 minute read

Read. Understand. Patch. Get flag. A cool challenge that requires patching binaries. Second RE challenge from DSO-NUS 2021.

Challenge Description

Help! It seems that my VM snapshot has problems with Meowing...
Can you help me fix my VM's Meow and recover my real flag?

Files (Any of the links are fine):
https://nusdsoctf2.s3-ap-southeast-1.amazonaws.com/S3/NyaVM/nyavm
https://nusdsoctf.s3-ap-southeast-1.amazonaws.com/S3/NyaVM/nyavm

 >> Note if a submitted flag doesn't work, you may have to TrY HaRDer
 >> Flag format conversion may have to be done for this challenge (Refer to notifications)

Solution

First thing to do: play around with the binary.

-------------------------------------
Welcome to Meow VM Terminal!
-------------------------------------
Select an action:
1. Load VM snapshot
2. Run VM
3. Pause VM
4. Meow in VM
5. Quit
>

In short, we need to load the vm, run it, then make it meow. But upon doing so, we get this

++++++++++++++++++++++++++++++++++
VM successfully started
++++++++++++++++++++++++++++++++++
Select an action:
1. Load VM snapshot
2. Run VM
3. Pause VM
4. Meow in VM
5. Quit
> 4
++++++++++++++++++++++++++++++++++
boom?!

and then the program exits. So we need to somehow patch the binary to fix this, and the flag will come out…?

Throwing things into ghidra, we see gibberish every where, as expected from a stripped binary. So I wasted most of my time reading through the code and understanding what each function does. I’ll only run through the few important ones that are related to the solution.

The main function is labelled here
The main function is labelled here

No decompilation? Then we’ll just have to select the function block (from the label to a return instruction), and create our own function.

Hey look! Switch cases, which should be related to our input to the program
Hey look! Switch cases, which should be related to our input to the program

We’re interested in the function in case 4, since that’s when we make the VM meow.

Let’s do some analysing of what each function does and rename things.

So here we see the two keys that are responsible of checking whether the VM is loaded and running (it can be found in the functions of case 1 and 2). The problem lies in the second if statement, there doesn’t seem to be anything in the program’s execution flow that allows the change of that value to not satisfy that condition, so this leads to one possible workaround: patching the binary (not surprising huh? :P)

Side note: I am not sure what the function randomize does, but it seems to not affect the program execution in any way meaningful, so I ended up ignoring it.

We can change JZ to JNZ for if statement
We can change JZ to JNZ for if statement

With this, the VM won’t go boom. One thing to keep in mind when patching binaries is that we should only change a k-byte instruction to another k-byte instruction to avoid memory alignment issues (#saynotosegfaults)

Changing the instruction here in ghidra doesn’t affect the original binary, we need to write this patch back to the executable with the help of a ghidra script.

Also notice near the end of the function, there’s an if statement checking for the result of big_compare (we’ll check this out later), if it is non-zero, the program will exit (you can verify this by looking at the use of that variable continue_if_0 in the main function). So, here’s big_compare:

The function returns 0 at the inner most if block, so that means we need to run through all the functions (and have them return 0 as well).

This is one of the functions
This is one of the functions

They all look basically the same: a big condition and some return statements with a random function call before the return. At first glance I thought I might need to use z3 to solve for it, but then I remembered patching, so I essentially only need to remove the if statements (jump instructions in the context of assembly) to change the program execution in my favor.

To make sure I don’t miss anything, I ensure that all those random function calls are also executed and that the function returns 0. With that in mind, I just replace all the jump instructions to a few nop instructions (with the correct number of bytes).

For example:

Remove this JNZ
Remove this JNZ
by changing it to 6 bytes of NOP
by changing it to 6 bytes of NOP

Final result after patching everything:

++++++++++++++++++++++++++++++++++
VM successfully started
++++++++++++++++++++++++++++++++++
Select an action:
1. Load VM snapshot
2. Run VM
3. Pause VM
4. Meow in VM
5. Quit
> 4
++++++++++++++++++++++++++++++++++
Meow!
++++++++++++++++++++++++++++++++++
Congrats! You found the real flag!
-------------------------------------
Nya! Thank you!
Here's your flag:
e22bdd97faabbd70da25dd18135b6cead0199fbbaee585e22473dfc2d8630975
a'!3:xxx'$x:$ap^!'^ae%W^^%eeZ00x!!$p0'x0%vav:x:ZWx$%$$%p0xa+$ve$
-------------------------------------
Nya! Thank you!
Here's your flag:
680fe2228d2ed6a30836b15331bbc77200da78271464e2ec52d1dd1a7269d4bd
-------------------------------------
Nya! Thank you!
Here's your flag:
flag: c4263f5e6660537f56c5d3a67d4142369d53785c44d4cf4aba931f5af5c3c96d

I actually missed out one if statement initially and got the bottom two fake flags only T.T

Flag: DSO-NUS{e22bdd97faabbd70da25dd18135b6cead0199fbbaee585e22473dfc2d8630975}