HTB — reversing a custom VM in 3 hours

Full breakdown of the 'VM-ware' HackTheBox challenge: opcode table, emulator, exploit chain.

2 min read

The challenge binary implemented a 16-instruction stack VM in ~800 lines of C. The flag was encrypted and stored as VM bytecode. My job: reverse the ISA, write an emulator in Python, then work backwards from the encrypted output to the key.

recon

❯ file vm-ware
vm-ware: ELF 64-bit LSB executable, x86-64, statically linked

❯ checksec --file vm-ware
Arch:     amd64-64-little
RELRO:    Full RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      No PIE

Static, no PIE — reversing is straightforward, no ASLR to fight. Opened in Ghidra, found dispatch_loop() immediately from the switch statement shape.

the ISA

After an hour with Ghidra I had the full opcode table:

Opcode Mnemonic Description
0x01 PUSH push immediate
0x02 POP pop to register
0x03 ADD r0 = r0 + r1
0x04 XOR r0 = r0 ^ r1
0x05 JNZ jump if r0 != 0
0x06 LOAD r0 = mem[r1]
0x07 STORE mem[r1] = r0
0x08 CALL push ip, jump
0x09 RET pop ip
0x0A HALT stop

the emulator

class VM:
    def __init__(self, bytecode):
        self.code = bytecode
        self.stack = []
        self.mem = bytearray(4096)
        self.regs = [0] * 8
        self.ip = 0

    def step(self):
        op = self.code[self.ip]; self.ip += 1
        if   op == 0x01: self.stack.append(self.code[self.ip]); self.ip += 1
        elif op == 0x04: self.regs[0] ^= self.regs[1]
        elif op == 0x0A: return False
        # ... rest of opcodes
        return True

Running the emulator against the bytecode and tracing every XOR operation gave me the key schedule. The encryption was a simple rolling-XOR with a 4-byte key. Brute-force over 2^32 possible keys took 8 seconds.

flag

HTB{vm_b4by_st3ps_t0_r3v3rs1ng}

Total time: 2h 47m. The hardest part was convincing myself the key was only 4 bytes — the VM had room for a longer one and I spent 30 minutes looking for a second key derivation step that wasn’t there.