How I hacked a 38-year-old videogame in a bug bounty contest
Introduction
On September 2016 I went to the BruCON security conference in Ghent. As usual, multiple sponsors were promoting their latest products and platforms. After enjoying some talks by the speakers, I went by every booth to check out all of the vendors. One in particular caught my attention: intigriti, the first Belgian bug bounty platform. The vendor had setup a Capture The Flag event to promote their new platform. Hack PAC-MAN.
I had a look at the CTF, but I didn’t find anything at first sight. Since many other conference attendees were trying to solve it and given the small time span (wanted to go see the other talks), I decided to skip the CTF and went on with the conference.
Fast forward to the end of 2017, where I was reintroduced into intigriti by some colleagues at work. The first thing I noticed was that the PAC-MAN challenge still wasn’t solved after almost 1.5 years. I decided to give a try. At this very moment, the challenge is still online, for anyone who wants to solve it.
SPOILER ALERT: continuing to read will explain how to resolve this challenge!
Over the next parts I'll give a detailed explanation of every step, how you complete all the necessary levels to get to the flag.
Stage 1: The game
Opening the website prompts you with a PAC-MAN game written in JavaScript. Browsing through the source code revealed the game was an opensource project by platzh1rsch. Comparing the original source code with the challenge showed a difference in the way the high score was uploaded to the server. In the original game the username, the score and the level was send to the server. In the modified version a md5 hash of all these values was calculated and added to the request. Whenever I tried changing any parameter I got a HTTP response containing “Hash mismatch”. My initial idea was to create a proxy that generated a corresponding md5 hash for all the parameters. After some coding it became clear that the username, score, level and time parameters weren’t injectable. The only other available parameter was the md5 hash. Attaching ‘; whoami’ responded with “no match restricted\n”. This could only mean that the whoami command could not be executed.
Stage 2 : exploiting the vulnerability
Since whoami returned “restricted”, I tried commands that are installed on Debian by default. All of these can be listed by using compgen -c.
Using burp intruder we can test all programs on PAC-MAN. Comparing all of the commands, resulted in base64 returning “invalid argument”. So this means that we need to attach a file to the base64 command. The only file that I knew to exist is index.php. This resulted in the complete source code of the index page. In this page I found the next clue for the challenge: 5b7e3f5267e3bec8230a98669243ecf7.sqlite3
//Functions
function writeScore($uname, $uscore, $utime){
$db = new SQLite3(‘5b7e3f5267e3bec8230a98669243ecf7.sqlite3’);
$query = $db->prepare(“INSERT INTO ‘scoreboard’(‘nickname’,’score’,’dateTime’) VALUES (:nickname,:score,:dateTime);”);
$query->bindValue(‘:nickname’, $uname);
$query->bindValue(‘:score’, $uscore);
$query->bindValue(‘:dateTime’, $utime);
$result = $query->execute();
}
Stage 3: Decryption
Downloading the database resulted in 2 values: the username ‘ksexbjwfoestnmqvlarqmfsevqkpymvxmpitbvykmtlafyw’ and a long binary as score. This was the trickiest part of the challenge. The challenge revealed a hint on 22/05:
a zero in front of a number would not resolve in a number
This hinted to converting a binary into a decimal, since a zero in front of a decimal doesn’t resolve in a number. Converting the 764-bit binary into a decimal number and putting a zero in front of it, lead to the following string:
068111110039116032116114121032114117110110105110103032098101104105110100032116104101032107101121046032071101116032116104101032102108097103032105110115116101097100046032089111117032097114101032097108109111115116032116104101114101046
The most common sequence in this number was 032. After some digging I found out that 032 is the octal ASCII representation of a space. Decrypting the complete sequence resulted in a string:
Don’t try running behind the key. Get the flag instead. You are almost there.
I knew I was onto something. The other value that was found in the database was an encrypted string ‘ksexbjwfoestnmqvlarqmfsevqkpymvxmpitbvykmtlafyw’. The hint in this last step was the word ‘running’. Researching the running key cipher resulted into the vigenère cipher. Decrypting the string with the key ‘DonttryrunningbehindthekeyGettheflaginsteadYouarealmostthere’ reveals the actual flag:
hereisyourflagpresentyourselftotheintigriticrew
I would like to thank the entire intigriti crew for creating this challenge. It was a very challenging CTF.