Fruit VM
Fruit VM
Platform: CUSTOM | Category: Reversing | Type: Challenge | Difficulty: N/A | OS: N/A | **Author: XESXOR Techniques: bytecode_constraint_solving, checksum_reconstruction, vm_opcode_recovery
Summary
Walkthrough of the CUSTOM Reversing Fruit VM. Task: Reverse a fruit-themed stack VM whose accepted bytecode must print ASCII 0 and derive the correct decryption key. Solution: Reconstruct the opcodes and rolling checksum, then execute a six-token
Description
Разберись как работает фруктовая виртуальная машина...очень интересно
The supplied amd64 ELF parses fruit names into bytecode and executes them in a stack-based virtual machine. The goal is to provide a program that passes the visible output check while also producing the checksum needed to decrypt the embedded flag.
Analysis
The important functions in the ELF are:
vm_executeat0x401cc8decrypt_flagat0x401e94- the fruit-token parser at
0x401f82 mainat0x40226a
Reversing the parser and interpreter gives the following instruction set:
| ID | Token | Operation |
|---|---|---|
| 1 | APPLE | Push the following one-byte immediate |
| 2 | BANANA | Pop |
| 3 | CHERRY | Add two stack values modulo 256 |
| 4 | DRAGON | XOR |
| 5 | ELDER | Compare |
| 6 | FIG | Jump |
| 7 | GRAPE | Jump if zero |
| 8 | HONEY | Jump if nonzero |
| 9 | KIWI | Pop, print, and save the value as the last output |
| 10 | LEMON | Push the next value from a stateful table |
| 11 | MANGO | Halt and return the last output |
| 12 | OLIVE | Duplicate the top value |
| 13 | PEACH | Swap the top two values |
| 14 | QUINCE | Subtract modulo 256 |
| 15 | RAISIN | Multiply modulo 256 |
| |
LEMON cycles through this table, beginning with decimal 19:
text read-only • copy allowed13 37 42 5a 6f 7b 2c 11 55 6e
Before dispatching each instruction, the interpreter updates an eight-bit checksum:
text read-only • copy allowedchecksum = ROL8(checksum, 1) XOR opcode
An APPLE operand is data rather than an opcode. The APPLE handler consumes it by advancing the instruction pointer, so the operand does not enter the checksum.
main first requires the VM's last output to equal 0x30, the ASCII character 0. That condition alone is insufficient: the checksum of the executed opcode sequence is passed to decrypt_flag as its key. A program that merely prints 0 but follows a different instruction path decrypts the ciphertext with the wrong key and produces garbage.
The decryptor processes the 28 embedded ciphertext bytes as follows:
text read-only • copy allowedplain[i] = cipher[i] XOR ((checksum + 7*i) & 0xff)
Therefore, the program must satisfy two constraints simultaneously:
- its last printed byte must be
0x30; - its executed opcode checksum must be
0x55.
Solution
The accepted program is:
text read-only • copy allowedAPPLE 29 LEMON CHERRY KIWI MANGO
Its data path is straightforward:
APPLE 29pushes decimal 29.- The first
LEMONpushes0x13, or decimal 19. CHERRYadds them:(29 + 19) & 0xff = 48 = 0x30.KIWIpops and prints0, saving it as the last output.MANGOhalts and returns that output.
The parsed bytecode is [1, 29, 10, 3, 9, 11], but the executed opcode IDs are only [1, 10, 3, 9, 11]. Starting from zero, the checksum develops as:
text read-only • copy allowed00 --APPLE(01)--> 01 01 --LEMON(0a)--> 08 08 --CHERRY(03)--> 13 13 --KIWI(09)--> 2f 2f --MANGO(0b)--> 55
The following exact solver emulates the relevant VM path, checks both acceptance conditions, and decrypts the flag:
python read-only • copy allowed#!/usr/bin/env python3 LEMON_VALUES = [0x13, 0x37, 0x42, 0x5A, 0x6F, 0x7B, 0x2C, 0x11, 0x55, 0x6E] CIPHERTEXT = bytes.fromhex( "1605212f23392dc3ccefddd0fcd9e3e1b789a39680ab8abb984a7f6f" ) # Parsed bytes for: APPLE 29 LEMON CHERRY KIWI MANGO # APPLE's immediate 29 is consumed by PUSH and is not checksumed as an opcode. bytecode = [1, 29, 10, 3, 9, 11] stack = [] ip = 0 lemon_index = 0 checksum = 0 last_output = -1 while ip < len(bytecode): opcode = bytecode[ip] checksum = (((checksum << 1) | (checksum >> 7)) & 0xff) ^ opcode if opcode == 1: # APPLE / PUSH immediate ip += 1 stack.append(bytecode[ip]) elif opcode == 3: # CHERRY / ADD b, a = stack.pop(), stack.pop() stack.append((a + b) & 0xff) elif opcode == 9: # KIWI / PRINT last_output = stack.pop() elif opcode == 10: # LEMON stack.append(LEMON_VALUES[lemon_index % len(LEMON_VALUES)]) lemon_index += 1 elif opcode == 11: # MANGO / HALT break else: raise ValueError(f"unexpected opcode {opcode}") ip += 1 flag = bytes(c ^ ((checksum + 7 * i) & 0xff) for i, c in enumerate(CIPHERTEXT)).decode() assert last_output == ord("0") assert checksum == 0x55 assert flag == "CYBERAREA{REDACTED}" print("program: APPLE 29 LEMON CHERRY KIWI MANGO") print(f"output: {chr(last_output)!r}; checksum/key: 0x{checksum:02x}") print(f"flag: {flag}")
Run the solver with:
bash read-only • copy allowedpython3 solve.py
The program was also supplied to the original ELF in a Linux amd64 container. It printed 0 followed by the same flag, confirming that the emulation matches the interpreter.
Key Takeaways / Lessons
- Mastering bytecode_constraint_solving, checksum_reconstruction, vm_opcode_recovery was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-07-15 • CUSTOM • Fruit VM • Difficulty: N/A