PROJEKT: OVERFLOW
RISC-V assembly board game
hack the planet
- [PLAY WEB VERSION: ALONE]
- [PLAY WEB VERSION: WITH A FRIEND]
- [PRINT]
- [RULES]
- [SIMILAR PROJECTS]
- [SYMBOLS]
- [CREDITS]
- [CONTACT]
- [GAME HELPER (ESP32)]
- [ASSEMBLY GUIDE]
THE GAME
I made this game to teach my daughter how buffer overflows work. Looking at programs as something you can play with, and poke and twist and make it do something else, is my favourite part of modern computing. I think its the right way to look at programs. When your microwave oven gets an update and starts crashing, you can hack it. Or when your keyboard controller’s firmware is bad, you can hack it (looking at you vortex pok3r). She is 12 yo now but her assembler skills are getting better and better, hopefully one day she will be able to hack her own keyboard :)
The game is about creating a small shellcode in memory by copying existing instructions and then exploiting a buffer overflow to jump into it, so that you can overwrite your opponent’s return address to force them to go to the game_over() function.There are other mechanics as well and more layers of strategy (like setting the exception handler or monkeypatching).
All players share the same memory, and execute the same program, while time sharing the same processor running preemptive scheduling os, so each turn the player executes 10 instructions, after that the process is interrupted by the operating system, and its the other player’s turn. Each player’s stack pointer starts at a different location. There is no virtual memory.
How it looks when we play the game:
The code is compiled with riscv64-unknown-elf-gcc -fomit-frame-pointer -fno-inline-small-functions -mno-shorten-memrefs -march=rv32g -mabi=ilp32 -ffreestanding -nostdlib -nostartfiles -T code/link.ld -O0 -fverbose-asm -g -o game game.c, the linker script link.ld has just offsets for the specific functions so the fit nicely on the board. The no optimization -O0 makes the machine code pretty verbose, but also straight forward. Then I use riscv64-unknown-elf-objdump -S -l -fd game, parse it and fix the ▲ and ✎ instructions, convert the jump offset from hex to dec and clean the assembly a bit, then match it with the source code and generate a svg which is then covnerted to a pdf with inkscape.
- Board: left part [A3 or A4 PDF]
- Board: right part [A3 or A4 PDF]
- Memory template player 1,3 (address 1988 to 2244) [A4 PDF]
- Memory template player 2,4 (address 3588 to 3840) [A4 PDF]
Board Preview:
Tabletop Requirements
- Printer: A3 preferably, A4 works as well but its a bit small
- Pawns: 1 pawn for the loose nop instruction, one pawn for the trap address, and 2 pawns per player are needed, one for the program counter and one for the stack pointer, I use capacitors with their legs cut off or bicycle tire valve caps
- Pencil and eraser
Game Rules
✱
Board Setup:
- Both players start with all registers set to zero, except for the return address register (ra), which starts at 1000.
- Player 1’s stack pointer (sp) is initialized at address 2244.
- Player 2’s stack pointer (sp) is initialized at address 3844.
- The nop instruction pawn is not initially placed on the board.
- Both players’ program counters (pc) start at address 1000, which is the beginning of the main function.
- Place the trap pawn at address 1000.
- All memory addresses are set to zero, except for the pre-loaded program.
✱
Gameplay:
- Moves per Turn: Players are required to execute 10 instructions during their turn.
- Execute Instructions: Players must follow the program counter and execute either 10 instructions or an accumulated number up to 20. All jumps (jal, beq, etc.) must be followed.
- Suspend Execution: Players can opt to suspend their turn after executing at least one instruction, saving the remaining instructions for their next turn. A maximum of 20 instructions can be accumulated.
- Monkeypatch: At the start of each turn, players can move a nop instruction pawn to any valid address within the program memory, ranging from 128 to 1188. When the program counter reaches this address, the instruction will act as a no-operation (nop). Moving the nop pawn costs the player their current and next turn, allowing the opponent to execute up to 20 instructions on their next turn. Players cannot place the nop pawn immediately after it was placed by the opponent.
◉
Winning Conditions [HARD MODE]:
- Game Over: The game ends when one player successfully hacks their opponent to call the game_over() function.
- Draw: It is possible that you get the game in a state where nobody can force the other player into game_over(), in this case the game is a draw.
⦿
Winning Conditions [MEDIUM MODE]:
- Stack Overflow: Whoever overflows their stack first wins. Player 1’s stack ends at 2120 and Player 2’s end at 3720.
⊙
Winning Conditions [EASY MODE]:
- Break Free: The winner is the first player that leaves the main loop (executing ret in main).
✱
Special Symbols:
- ✎: This symbol allows players to choose any 12-bit number (0 to 4095) as the immediate value in the li instruction.
- ▲: This symbol allows players to choose a value for the load instruction that is within ±128 bytes of their stack pointer. For example, if your sp is 2180, you can choose a value between 2052 and 2308.
✱
Rules and Constraints:
- Illegal Actions: Overwriting a memory address below 1192, or doing unaligned read or write (read or write to address not multiple of 4), or executing an illegal instruction will crash your program.
- Exception Handling: A crash invokes the exception handler, which jumps to the trap address, initially set to 1000 but you can overwrite it from the set_trap() function. Once an exception happen the program counter and the return address to the specific value and you continue execution. Your stack pointer is reset to initial position. (In reality only the program counter will jump to the trap handler, and then it will probably store the registers state deeper on the stack and call some function to deal with the exception, but in our case we also reset the return address and the stack pointer so the game state is not completely corrupted)
- Cheating/Mistakes: If a player cheats or makes a mistake and is caught, their program state, including memory and registers, is reset.
Extended Rules for More Players:
- Player 3’s stack pointer (sp) register is set to 2116.
- Player 4’s stack pointer (sp) register is set to 3716.
- ▲ symbol now a