Level 4 - Really Unfair Battleships Game
Domain(s): Pwn, Misc
Last updated
Domain(s): Pwn, Misc
Last updated
After last year's hit online RPG game "Slay The Dragon", the cybercriminal organization PALINDROME has once again released another seemingly impossible game called "Really Unfair Battleships Game" (RUBG). This version of Battleships is played on a 16x16 grid, and you only have one life. Once again, we suspect that the game is being used as a recruitment campaign. So once again, you're up!
Things are a little different this time. According to the intelligence we've gathered, just getting a VICTORY in the game is not enough.
PALINDROME would only be handing out flags to hackers who can get a FLAWLESS VICTORY.
You are tasked to beat the game and provide us with the flag (a string in the format TISC{xxx}) that would be displayed after getting a FLAWLESS VICTORY. Our success is critical to ensure the safety of Singapore's cyberspace, as it would allow us to send more undercover operatives to infiltrate PALINDROME.
Godspeed!
We are given an executable file that opens up a custom version of the well-known battleships game.
The game ends upon a single wrongly clicked tile, which makes it virtually impossible to get a victory.
First, let's analyze the application and try to view the source code. We can extract the files from a windows executable by unzipping it. Here, we can see the 7-zip archived application along with some DLLs.
Decompress the archive to view the source code and dependency files, in which we will find a .asar file in the resource/ directory.
Researching more about .asar files, we find that it is an Electron Archive file which is used to store the source code of a compiled Electron app. We can use the following command to decompile it.
In the extracted/ directory, we can now look through the source code.
Opening index.html, we see that the page pulls the file ./assets/index-c08c228b.js, which is likely used to load the contents of the page.
After viewing and beautify-ing the Javascript code, we see that it is heavily obfuscated.
Firstly, let's try to find the section of code that shows us the "Defeat" message.
We see that the _c variable contains the URL for the "Defeat" image. We can trace this variable and find a function that uses this variable.
Similarly, we can look for references to the nf variable, where we will find the conditional logic for the game.
This code seems to handle onClick events, specifically for the "START GAME" button, cell/tile buttons, and the "RETRY" buttons.
Digging into the handler for the tile buttons, we see a function call m(y-1)
. This function likely handles the logic of checking if the tile clicked corresponds to a ship.
Search for this function in the file, and we can see a slightly complex looking chunk of code.
Just by analyzing the code, d(x) likely checks if the tile clicked was correct.
We can also see that a flag attribute is accessed from the output of the function $u.
Searching for the $u variable in the code, we see that it is simply a HTTP POST request to the /solve endpoint, to check if the puzzle was solved correctly.
But first let's look at the source, how are these puzzles generated? Let's try to analyze the application dynamically. Before we start, there are some issues we need to fix in the source code. In lines 2567, 3018, 3023 and 3925, there are syntax errors in "? ?", change these to "??"
In the dist/ directory, start a HTTP server on port 80, if you see a blank page upon entering the application, you may need to run this custom HTTP server to force the Javascript MIME types.
Now, you should be able to run the game in your browser with debugging capabilities.
Upon clicking START GAME, we can see a GET request to the challenge server at /generate. This returns a 32 value long array and some other information.
The 32 byte array seems to be the encoded form of coordinates for the correct tiles.
We can trace this in the source code by searching for the /generate endpoint, where we see a function similar to the /solve one.
Similarly, look for the function call and we can see a function use the response data to calculate the certain values.
Let's start debugging a bit. First, let's add some code to bruteforce "clicking" all tiles on the map. We know that the map is 16x16 tiles, therefore there are 256 clickable tiles. We can simply write a for loop to simulate a click on all tiles.
Next, add some console log statements to m(x) to log how the application reacts to correct and wrong tiles clicked.
We add 1 each for:
When 1 valid tile has been found
The array c.value that eventually gets sent to /solve
The array c.value that does get sent to /solve
Refresh the page in the browser, click START GAME, and click 1 tile.
This is likely because our bruteforce breaks some of the arrays that some conditions depend on to reach the Flawless Victory state. Now we can take a look at our console log.
We see that 16 values were logged as found, which ends up populating the c.value array as expected. However, we also see that this array was never sent to /solve.
Let's try to manually submit our solution to the endpoint. But first we need to know what the value of b
is. Taking a look at the network requests to /solve, we see that the value of b is constant and is likely the identifier of the puzzle for the backend to verify.
Now we can craft the request on Postman and get the flag :D
Flag: TISC{t4rg3t5_4cqu1r3d_fl4wl355ly_64b35477ac}
First, you should see that you do reach the Flawless Victory page, but no flag is given.