Skip to content

Instantly share code, notes, and snippets.

@zwade
Created August 16, 2020 18:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zwade/5b48179b63cc57cc6282f97f9edcfc8b to your computer and use it in GitHub Desktop.
Save zwade/5b48179b63cc57cc6282f97f9edcfc8b to your computer and use it in GitHub Desktop.
Reimplementation of DEF CON 2020's Pinboool Binary
from typing import Tuple, Dict, List, TypedDict
import copy
import struct
from functools import cached_property
import math
from pwnlib.util.packing import flat, u16
def twos_byte(b):
b = b & 0xFF
if b <= 0x7F:
return b
else:
return b - 0x100
def twos_int(b):
b = b & 0xFF
if b <= 0x7F:
return b
else:
return b - 0x100
def cmd_from_bytes(cmd):
return struct.unpack("<HII", cmd)
class MemAssignment(TypedDict):
addr: int
value: bytes
class GameState:
commands: Tuple[int, int, int]
memory_bank: List[MemAssignment]
command_idx: int = 0
rng_state: int = 0x78a
ball_loc: int = 0xAA
score: int = 0
sr_bonus: int = 0
tilt_points = 0
mission_available = 0
active_mission = 0
cold_war_bonus = 0
watching_bonus = 0
sold_the_moon_bonus = 0
def __init__(self, commands: Tuple[int, int, int], memory_bank: List[MemAssignment]):
serialized_commands = flat([
struct.pack("<HII", command[0], command[1], command[2])
for command in commands
])
self.commands = commands
self.memory_bank = [
{ "addr": 0, "value": serialized_commands },
*memory_bank,
]
self.memory = None
self.create_memory()
def clone(self):
g = GameState(copy(self.commands), copy(self.memory_bank))
g.command_idx = self.command_idx
g.rng_state = self.rng_state
g.ball_loc = self.ball_loc
g.score = self.score
g.sr_bonus = self.sr_bonus
g.tilt_points = self.tilt_points
g.mission_available = self.mission_available
g.active_mission = self.active_mission
g.watching_bonus = self.watching_bonus
g.cold_war_bonus = self.cold_war_bonus
g.sold_the_moon_bonus = self.sold_the_moon_bonus
def next(self):
self.command_idx += 1
def read(self, addr, len):
return self.memory[addr:addr + len]
def advance_rng(self):
self.rng_state = 214013 * self.rng_state + 2531011
self.rng_state = self.rng_state & 0xFFFFFFFF
ret = (self.rng_state >> 16) & 0x7FFF
return ret
def die(self):
self.ball_loc = 1
def assign(self, assignment: MemAssignment):
self.memory_bank.append(assignment)
self.create_memory()
def create_memory(self):
mem = [0 for i in range(1024)]
for assignment in self.memory_bank:
for offset in range(len(assignment["value"])):
mem[assignment["addr"] + offset] = assignment["value"][offset]
self.memory = bytes(mem)
return self.memory
def __repr__(self):
return f"<GameState command={self.command} rng={self.rng_state:x} score={self.score}+{self.sr_bonus}>"
@property
def command(self):
byte_command = self.read(self.command_idx * 10, 10)
return struct.unpack("<HII", byte_command)
@property
def cmd(self):
return self.command[0]
@property
def addr(self):
return self.command[1]
@property
def len(self):
return self.command[2]
@property
def command_str(self):
cmd = self.command
return struct.pack("<HII", cmd[0], cmd[1], cmd[2])
@property
def ball(self):
return (self.ball_loc + 0x10000) % 0x100
@staticmethod
def from_file(filename):
with open(filename, "rb") as f:
byte_data = f.read()
assert len(byte_data) == 1024
g = GameState([], [ { "addr": 0, "value": byte_data }])
return g
def entry(gs: GameState):
correct_header = struct.pack("<4sHI", b"BALL", 10, 100)
if (correct_header[:4] != gs.command_str[:4]):
print("Invalid ball")
return (gs, -1)
gs.next()
while gs.command[0] != 1:
gs.next()
return main_switch(gs)
def rocket_launch(gs: GameState):
file_name = gs.read(gs.command[1], gs.command[2])
print(f"Opening [{file_name}]")
if file_name == b"secret_base":
unique = 14
elif file_name == b"..":
unique = 0
else:
unique = 1
if unique != 0:
if unique < 0x39:
if unique <= 5:
print("Rocket failed")
gs.score += 1
return (gs, 0)
else:
print("Rocket launched")
gs.score += unique
else:
print("JACKPORT (sputnik)")
gs.score += 250
gs.sr_bonus += 20
gs.ball_loc = gs.advance_rng() % 100
print("Doing seccomp crap")
def roswell(gs: GameState):
curr_char = gs.memory[gs.addr + 5]
read_str = [0x20 for i in range(32)]
for i in range(32):
read_byte = gs.memory[gs.addr + curr_char]
if read_byte == 0:
print("Trying to compare secret base to " + bytes(read_str).decode("ascii"))
break
read_str[i] = read_byte
if read_byte % 2 == 1:
curr_char -= read_byte
else:
curr_char += read_byte
if curr_char > gs.len:
print("Bad ball :(")
return (gs, 1)
gs.ball_loc = gs.advance_rng() % 100
def main_switch(gs: GameState):
if (gs.command[0] == 1):
res = rocket_launch(gs)
if res is not None:
return res
return main_loop(gs)
raise Exception("Thought this was unreachable")
# todo (the rest???)
def rng_then_main(gs: GameState):
ball_loc = gs.advance_rng() % 100
main_loop(gs)
def main_loop(gs: GameState):
gs.sr_bonus += 10
gs.next()
return main_loop_continue_entry(gs)
def main_loop_continue(gs: GameState):
gs.next()
return main_loop_continue_entry(gs)
def main_loop_continue_entry(gs: GameState):
if gs.cmd != 0:
print(gs)
if gs.ball == 0xFF or gs.command_idx - 1 >= 0x64:
# go to end
print("Rocket landed!")
gs.score += gs.sr_bonus
gs.sr_bonus = 0
return (gs, 0)
cmd = gs.command[0]
if cmd == 0:
return main_loop_continue(gs)
if cmd == 1 and gs.ball != 0xAA:
gs.die()
return main_loop_continue(gs)
if cmd == 3:
if gs.ball_loc > 0x64:
return main_loop_continue(gs)
if gs.ball_loc > 0x23 and gs.ball_loc <= 0x40:
print("MISS")
gs.die()
return main_loop_continue(gs)
if gs.ball_loc > 0x40:
print("WRONG FLIPPER")
gs.die()
if cmd == 4:
if gs.ball_loc > 0x64:
return main_loop_continue(gs)
if gs.ball_loc > 0x23 and gs.ball_loc <= 0x40:
print("MISS")
gs.die()
return main_loop_continue(gs)
if gs.ball_loc <= 0x22:
print("WRONG FLIPPER")
gs.die()
return main_loop_continue(gs)
if gs.command[2] > 0x3e7:
print("Bad ball")
return (gs, -1)
if cmd == 1:
res = rocket_launch(gs)
if res is not None:
return res
elif cmd == 2:
tilt_spend = twos_byte(gs.memory[gs.addr]) or 1
tilt_amt = twos_byte(gs.memory[gs.addr + 1])
if gs.tilt_points + tilt_amt > 99:
print("TILT")
return (gs, 0)
rand_val = gs.advance_rng() % 100
if rand_val < tilt_spend:
if gs.ball > 0x31 and gs.ball <= 0x64:
gs.ball_loc = min(gs.ball + tilt_amt, 90)
else:
gs.ball_loc = max(gs.ball - tilt_amt, 10)
gs.tilt_points += tilt_spend
elif gs.ball > 0x23 and gs.ball <= 0x4A:
print("Tilt failed")
gs.ball_loc = 0xFF
gs.sr_bonus -= 10
elif cmd == 4:
print("testing command")
read_data = gs.read(gs.addr, 4)
print(read_data)
if read_data == b"QQQQ":
res = roswell(gs)
if res is not None:
return res
elif read_data == b"\xFF\x00\x00\x00":
ball_step = 3 * (gs.ball - 65)
buffer_offset = 4
accumulator = 1
if gs.mission_available:
for i in range(10):
data_bytes = gs.read(gs.addr + buffer_offset, 2)
accumulator *= struct.unpack("<H", data_bytes)
accumulator &= 0xFFFFFFFF
buffer_offset += ball_step
if accumulator == 0x97d7cfc0:
if gs.active_mission == 1:
print("Mission 1!")
gs.score += 200
if gs.active_mission == 2:
print("Mission 2")
gs.score += 400
if gs.active_mission == 3:
print("Mission 3")
gs.score += 1000
gs.ball_loc = 0
gs.mission_available = 0
else:
print("Mission wrong")
ball_loc = gs.advance_rng() % 100
elif read_data == b"\x00\x01`P":
print(gs.len, gs.addr, gs.read(gs.addr + 4, 4))
if (
gs.len == 520 and
gs.addr != 0 and
gs.addr != 0xa2a and
gs.read(gs.addr + 4, 4) == b"\0\0\0\0"
):
print("bumpa")
bumper_counter = 0
for i in range(512):
if gs.memory[gs.addr + 8 + i] == 0:
return rng_then_main(gs)
if gs.memory[gs.addr + 8 + i] == gs.ball:
bumper_counter += 1
print(bumper_counter, gs.ball)
if bumper_counter == 100:
print("We're doing crap with some stack pointer and maybe crashing")
gs.score += gs.advance_rng() % 100
print("Bumper!")
bumpers = 1
while ((gs.advance_rng() % 100) > 0x31):
print("Combo bumper!")
gs.score += gs.advance_rng() % 100
bumpers += 1
if bumpers > 4:
print("Jackpot bumper!")
gs.score += 800
return rng_then_main(gs)
elif cmd == 3:
read_data = gs.read(gs.addr, 4)
if read_data == b"QQQQ":
res = roswell(gs)
if res is not None:
return res
elif read_data == b"\x04\x03\x02\x01":
copy_buffer = "All Systems GO"
print(gs.mission_available, gs.len)
if gs.mission_available == 0 and gs.len <= 0x100:
gs.assign({ "addr": gs.addr + gs.len, "value": b"\0" * 16 })
rng_state = gs.rng_state
rng_buffer = bytes([gs.advance_rng() % 255 for i in range(100)])
gs.assign({ "addr": gs.addr + gs.len + 16, "value": rng_buffer })
gs.rng_state = rng_state
i = 0
while 3 * i < gs.len:
mem_1, = struct.unpack("<H", gs.read(gs.addr + 3 * i + 4, 2))
print(hex(mem_1), gs.read(gs.addr + mem_1, 10))
gs.assign({
"addr": gs.addr + gs.len + gs.memory[gs.addr + 3 * i + 6],
"value": bytes([gs.memory[gs.addr + mem_1], 0])
})
i += 1
print(gs.read(gs.addr + gs.len, 12))
if gs.read(gs.addr + gs.len, 12) == copy_buffer:
gs.active_mission += 1
if gs.active_mission <= 3:
print(f"Active mission: {gs.active_mission}")
gs.sr_bonus += 15
gs.mission_available = 1
else:
print("Mission jackpot")
gs.score += 500
gs.mission_available = 1
gs.ball_loc = gs.advance_rng()
elif read_data == b"P`p\x80":
if gs.ball == 0:
gs.sr_bonus += 5
return main_loop(gs)
if gs.memory[gs.addr + 4] == 0x65:
if gs.cold_war_bonus == 1:
return rng_then_main(gs)
for i in range(4, gs.ball + 14):
if gs.len < i or gs.memory[gs.addr + i] != 0x65:
return rng_then_main(gs)
gs.cold_war_bonus = 1
print("Cold war x 2")
# ???
elif gs.memory[gs.addr + 4] == 0x66:
if gs.cold_war_bonus == 0 or gs.watching_bonus == 1:
return rng_then_main(gs)
for i in range(10 * gs.ball):
if i + 4 > gs.len or gs.memory[gs.addr + i + 4] != 0x66:
return rng_then_main(gs)
gs.watching_bonus = 1
print("Wathcing bonsus!!!!")
elif gs.memory[gs.addr + 4] == 0x77:
if gs.cold_war_bonus == 0 or gs.watching_bonus == 0 or gs.sold_the_moon_bonus == 1:
return rng_then_main(gs)
for i in range(10 ** gs.ball):
if i + 4 > gs.len or gs.memory[gs.addr + i + 4] != 0x67:
return rng_then_main(gs)
gs.sold_the_moon_bonus = 1
print("Sold the moon!!!!!!!!!!!1")
gs.sr_bonus += 5
return rng_then_main(gs)
gs.sr_bonus += 10
return main_loop_continue(gs)
# intial_state = GameState([
# cmd_from_bytes(b"BALL\x0a\x00\x64\x00\x00\x00"),
# [1, 1010, 11],
# *([[5, 0, 0]] * 99),
# ],
# [
# { "addr": 1010, "value": b"secret_base" }
# ]
# )
initial_state = GameState.from_file("boll")
with open("./test", "wb+") as f:
f.write(initial_state.memory)
print(entry(initial_state))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment