Flare-On 2019 challenge 8 - Snake for Dummies

Ever since I was 8 years old I thought snake was a game for dummies. To me it seemed more a test of patience than skill, a test I would always fail. But I knew if you were happy to take a scenic route, you could make a robot play the game and you wouldn’t even have to be that smart.

A month ago I was participating in Flare-On, a CTF made for reverse engineers, by cyber security company FireEye. They base their challenges on real-world malware they’ve had to analyse. They like to include a “for fun” challenge and this year’s was to beat a snake game built for the NES.

I guess the intended solution for the challenge was to cheat. Learn how the game is coded so you can find a vulnerability and determine the flag. However, I saw an opportunity to make my dream come true. FCEUX is a NES emulator which offers Lua (similar to python) scripting. With Lua I was able to write to arbitrary addresses in memory. And that’s all I needed. The game was simple enough that I only ever read from one or two bytes in the entirety of memory and wrote ever wrote to one byte.

At the bottom of this post I’ve attached the code I used below, it’s simple enough for anybody with a little programming experience to understand. I even activated “turbo mode” to make it run super fast.

Slow mode

Turbo mode

I learned later that the snake has to eat 25 apples, 4 times in a row to win the game. Fortunately, I left the program running for 10 minutes and came back to the victory flag on the screen.

RIGHTWALL = 23;
BOTTOMWALL = 21;
STARTING_Y = 11;
DIRECTION = {
  UP = 2,
  DOWN = 3,
  LEFT = 1,
  RIGHT = 0,
};

-- 5 is intended direction
-- 4 is current direction

-- emu.speedmode("turbo");

while (true) do
  currentDirection = memory.readbyte(4)
  currentLength = memory.readbyte(0xA);
  currentX = memory.readbyte(0x7)
  currentY = memory.readbyte(0x8)
  
  -- handle very first move in game
  if currentX == RIGHTWALL - 1 and currentY == STARTING_Y and currentDirection == DIRECTION.RIGHT then
    memory.writebyte(0x5, DIRECTION.DOWN)
  end
  -- handle very first move in loop
  if currentX == RIGHTWALL - 1 and currentY == 0 and currentDirection == DIRECTION.RIGHT then
    memory.writebyte(0x5, DIRECTION.DOWN)
  end

  -- handle going left and back up
  if currentY == BOTTOMWALL - 1 and currentDirection == DIRECTION.DOWN then
    memory.writebyte(0x5, DIRECTION.LEFT)
  end
  if currentY == BOTTOMWALL and currentDirection == DIRECTION.LEFT then
    if currentX ~= 2 then    -- hopefully saviour if there's a bug, input lag
      memory.writebyte(0x5, DIRECTION.UP)
    end
  end

  -- handle going left and back down
  if currentY == 2 and currentDirection == DIRECTION.UP and currentX ~= 0 then
    memory.writebyte(0x5, DIRECTION.LEFT)
  end
  if currentY == 1 and currentDirection == DIRECTION.LEFT then
    memory.writebyte(0x5, DIRECTION.DOWN)
  end


  -- handle going across the roof
  if currentX == 0 and currentY == 1 and currentDirection == DIRECTION.UP then
    memory.writebyte(0x5, DIRECTION.RIGHT)
  end  

  emu.frameadvance();
end;