For some reason, I’ve found VM exploitation challenges to be quite interesting recently.

I managed to solve a challenge called NoRegVM from M0lecon CTF 2023. I also tried to solve a reversing challenge involving the same binary, but I couldn’t find the final solution. But that’s for another blog post.

Step 1: Reversing

This program lets you provide the code and memory that a VM is initialized with.

The VM can perform 13 different functions including reading and writing to the standard input/output.

This VM does not use registers, so all the operands used for an operation should be immediate values or memory addresses. Data can be copied to and from the memory using the pop_in and pop_out instructions in the VM.

Step 2: The Bug(s)

I found three bugs in this challenge and I eventually used all of them for the final exploit.

The first bug is a buffer overflow vulnerability in the pop_out function.

The loop in pop_out can be executed as many times as we want. This would eventually lead to an out-of-bounds write of the output buffer.

And that leads us to the second bug which is a format string vulnerability in the write_buf function.

Looking at the pseudo-code generated by IDA, this vulnerability is not apparent.

However, if you look at the actual assembly code, we see that the format specifier used by printf is a buffer in the data section of the binary.

And it so happens that this FMT_STR buffer lies just after the output buffer.

Therefore, by overflowing the output buffer using the pop_out function, we can trigger a format string vulnerability in the write_buf function. This can be used to leak whatever data we need.

I ended up leaking the heap, canary, stack, binary and libc base addresses.

The final bug is in the len function.

In this function again, we can execute the loop as many times as we want. This eventually leads to an out-of-bounds write of the stack buffer of size 200;

Step 3: The Exploit

So the plan was clear,

  • Overflow `output` buffer to overwrite `FMT_STR`
  • Leak all the pointers
  • Overflow stack buffer in `len` and get PC control
  • ????
  • Profit

A slightly annoying part about this challenge is that the loop in the len function will stop at the first instance of an int value 0. However, only the lower 8 bits from each memory address is copied into the stack buffer. Therefore, if the value at a memory address was 0x0100, the check for 0 would pass while only the last byte (i.e NULL) would be appended to the stack buffer.

In order to do this, I modified the program code to generate this 0x100 value (by performing 0x10 * 0x10) and writing it to the indices where I needed NULL bytes.

The final exploit uses the leaks in a simple ROP chain to call system("/bin/sh")

I had initially written this exploit by hard-coding the opcodes and operand values. And it was very hard to read for someone who didn’t understand my plan.

So, after the CTF, I tried re-writing the exploit to use my assembler. And it worked with minimal adjustments! Even I was surprised.

The final exploit is available here: Exploit

And since the assembler will keep getting updated, its better to store it somewhere like GitHub.