- Giving custom array size of NaN, passes checks while allowing OOB r/w
- Use OOB r/w to get libc, stack (environ) addresses
- Craft fake chunk on array and overwrite fastbin fd
- Reset machine to allocate register context on fake chunk
- Overwrite VM sp with real stack
- Push ropchain onto stack and halt VM to execute ropchain
Challenge Points: 996
No. of solves: 4
Challenge Author: k1R4
The VM is only kawaii from the outside T_T
Handout has the kawaii_vm binary & ld, libc and libseccomp inside the lib folder
The binary is stripped when checked with
file command. Typical for a VM challenge.
Here is the checksec output:
[k1r4@zg15 handout]$ checksec kawaii_vm
All mitigations are enabled. Since libseccomp is included checking the binary with seccomp-tools reveals:
line CODE JT JF K
Only syscalls allowed that are relevant are open,read,write. So the goal would be to perform orw on flag file.
Running the binary it is seen that it intially checks if a custom array size is needed, if so it takes input in unit pages. Then it reads bytecode to be executed.
Full src can be found here
I will be including only the important parts here. It is recommended to have the full src open while going through this writeup.
This VM has:
- Some general purpose registers
- A program counter
- A stack pointer
- An array pointer
Bytecode, stack are allocated a fixed size of 0x10000 bytes, whereas array can have maximum size of 0x10000 bytes.
The register context contains 4 general purpose registers, PC, SP and an AR (array pointer), and is allocated on the heap.
The instruction set can be found in full src. It doesn’t have any useful bugs, nor does the bytecode sanity checker.
The general instruction format for this VM goes something like:
<INSTRUCTION> <DEST_REG> <SRC_REG1> <SRC_REG2> (for arithmetic instructions)
The bug here is hidden in plain sight since its a niche one. My goal with this challenge was to demonstrate this cool trick.
The scanf is taking in a float and the bounds are checked, however passing in
NaN as input bypasses these checks. I learnt this cool trick from Histogram - ACSC 2021 authored by ptr-yudai. The value of
NaN in memory is
0x8000000000000. So when multiplied with 0x1000,
array_size would be
0x8000000000000000 which is clearly out of bounds.
set instructions can be used to achieve OOB read/write on the mmapped region which is meant to hold the bytecode, stack and array of the VM. Since mmapped regions are adjacent to libc, the primary attack vector is libc rw region which holds
main_arena, file structures and critical function pointers.
Only open,read,write is viable in this challenge, so overwriting function pointers or hijacking file structures wouldn’t be useful. This is where the
reset instruction comes into play. It updates the register context allocation without freeing the current one. So messing with the
main_arena could potentially let us control the register context after which we would have arbitrary r/w.
- Read libc base from ld rw using
- Read stack address from
environin libc rw using
- Store both values in array for later use, with
- Craft fake fastbin chunk on array by setting
- Since this challenge uses libc 2.36,
fdhas to be set to mangled ptr to the fake chunk
resetto allocate register context on array
spto point to the real stack
- Craft orw ropchain with libc gadgets using previously saved libc base in array
pushinstructions to put ropchain on the stack
haltto execute ropchain
Since this was my first time writing a VM challenge, I learnt a lot. Hope it was fun to solve this :D
You can find the full exploit here