|
| 1 | ++++ |
| 2 | +title = "MultiArch2" |
| 3 | +date = "2025-09-03" |
| 4 | +authors = ["Vrishab"] |
| 5 | ++++ |
| 6 | +## Summary of the Exploit |
| 7 | + |
| 8 | +This challenge is built around exploiting a custom VM built with two separate instruction sets (stack‑based and register‑based) |
| 9 | + |
| 10 | +Inside the register‑based instruction set, a key bug in the XOR instruction lets us write outside the space reserved for registers. This allows us to trick the VM so that a normal VM memory address (`0xA000`) actually points to the VM’s own internal state struct `masm_struct`. We can leak useful memory addresses from inside the VM, overwrite the VM function pointer `get_flag` to make it point to our shellcode and thereafter trigger that function pointer and get code execution |
| 11 | + |
| 12 | +--- |
| 13 | + |
| 14 | +## VM Setup |
| 15 | + |
| 16 | +When the VM runs, it loads a custom file format called `.masm`, each of which consists of several regions that get mapped when the VM starts: |
| 17 | +- **Code segment**: the instructions the VM runs |
| 18 | +- **Data segment**: attacker‑controlled, marked as rwx i.e. read, write, execute |
| 19 | +- **Stack segment**: where stack‑arch instructions operate |
| 20 | +- **Arch table**: tells the VM about whether each instruction runs in stack mode or register mode |
| 21 | + |
| 22 | +This design means we can place custom shellcode directly into the data segment, then aim to transfer execution there |
| 23 | + |
| 24 | +--- |
| 25 | + |
| 26 | +### VM State: `masm_struct` |
| 27 | +Internally, the VM maintains everything in a large structure `masm_struct`. It contains: |
| 28 | +- pointers to the code, data, and arch table in memory |
| 29 | +- four general registers, a stack pointer, and program counter |
| 30 | +- function pointer called **`get_flag`** at offset `0x28`, which we want to hijack |
| 31 | +- metadata about heap allocations (`heap_array`) |
| 32 | + |
| 33 | +--- |
| 34 | + |
| 35 | +## Vulnerability: Out‑of‑Bounds XOR |
| 36 | + |
| 37 | +Inside the register instruction set, there is an instruction |
| 38 | +```0x41 <idx> <imm32>` is supposed to do `registers[idx] ^= imm32``` |
| 39 | + |
| 40 | +The issue is that the VM never checks whether `idx` is a valid register index. By giving a large enough index, we can XOR into memory well beyond the register array, which overlaps with the `heap_array`. By crafting values, we can change one of the heap metadata pointers stored there |
| 41 | + |
| 42 | +--- |
| 43 | + |
| 44 | +## Exploit |
| 45 | + |
| 46 | +### Step 1 – Making Heap Allocation |
| 47 | +Using stack‑arch syscall `6`, we allocate a heap block. The VM maps it at virtual address `0xA000`, and internally records the mapping in `heap_array[0]` - `{ real_heap_ptr, 0xA000 }` |
| 48 | + |
| 49 | +--- |
| 50 | + |
| 51 | +### Step 2 – Corrupting Heap Metadata |
| 52 | +We switch to register‑arch and use the buggy XOR on a big index so we land inside and overwrite `heap_array.real_ptr`. Instead of pointing to the real heap, we flip it so it points to the `masm_struct` itself. So now whenever we use `0xA000`, we are actually accessing the VM’s internal state struct |
| 53 | + |
| 54 | +--- |
| 55 | + |
| 56 | +### Step 3 – Leaking Information |
| 57 | +We use stack‑arch syscall 2 (fwrite) to dump memory starting from `0xA000`. But `0xA000` maps `masm_struct`, so this gives us a leak of critical VM internals i.e. the real memory address of the **data segment** (`seg1`), which we control. This is the RWX memory where shellcode is already stored |
| 58 | + |
| 59 | +--- |
| 60 | + |
| 61 | +### Step 4 – Overwriting the get_flag Pointer |
| 62 | +We now use register‑arch syscall 1 (fread) to write into VM memory (at `0xA000 + 0x28`, which resolves to `&masm_struct->get_flag` due to our corruption). We overwrite that with the leaked `seg1` address. Now whenever the VM tries to call `get_flag`, it will instead jump to our data segment where we've placed our shellcode |
| 63 | + |
| 64 | +--- |
| 65 | + |
| 66 | +### Step 5 – Running Shellcode |
| 67 | +Finally, we use stack‑arch syscall 5, which usually calls `get_flag`. But it now points to our RWX data segment, so the syscall runs our injected shellcode |
| 68 | + |
| 69 | +--- |
| 70 | + |
| 71 | +## Overall Exploit Flow |
| 72 | +1. Allocate heap chunk at `0xA000` |
| 73 | +2. Use buggy XOR to poison heap_array so `0xA000` maps to `masm_struct` |
| 74 | +3. Dump (`fwrite`) `masm_struct` and find seg1 address |
| 75 | +4. Overwrite `get_flag` with that address |
| 76 | +5. Call `syscall 5` → shellcode runs |
| 77 | + |
| 78 | + |
| 79 | +**Credits**: 堇姬 Naup (https://naup.mygo.tw/2025/07/05/2025-Google-CTF-writeup/) |
0 commit comments