Pewpewboat was one of the most fun challenges so far! The executable provided is a 64-bit Linux executable (the reason for 64-bit will be apparant later), which is the game Battleship. You enter coordinates in a prompt which consist of a letter for the Y-coordinate and a number for the X-coordinate. If you sink all the ships in one level, you win! However, use too many shots and you will run out of ammo and the game ends.

   1 2 3 4 5 6 7 8
A |_|_|_|_|_|_|_|_|
B |_|_|_|_|_|_|_|_|
C |_|_|_|_|_|_|_|_|
D |_|_|_|_|_|_|_|_|
E |_|_|_|_|_|_|_|_|
F |_|_|_|_|_|_|_|_|
G |_|_|_|_|_|_|_|_|
H |_|_|_|_|_|_|_|_|

Rank: Seaman Recruit

Welcome to pewpewboat! We just loaded a pew pew map, start shootin'!

Enter a coordinate:

Playing along, after solving the first level, there is another obstacle:

   1 2 3 4 5 6 7 8
A |_|_|_|_|_|_|_|_|
B |_|_|_|X|X|X|X|_|
C |_|_|_|X|_|_|_|_|
D |_|_|_|X|_|_|_|_|
E |_|_|_|X|X|X|X|_|
F |_|_|_|X|_|_|_|_|
G |_|_|_|X|_|_|_|_|
H |_|_|_|_|_|_|_|_|

Rank: Seaman Recruit

Nice shot! Hit!
You sunk all the ships!!


NotMd5Hash("OHHG") >

The game wants us to hash a value and input it in a prompt. Also, the hits on the map of the first level is in the shape of the character F, which is obviously significant!

Time to start reversing

There are a few interesting points in the binary:

  • 0x40398f - out-of-ammo check
  • 0x403eda - max moves check
  • 0x403b4e - check if the player finished the map
  • 0x403be0 - the call to NotMD5Hash()
  • 0x403757 - memcmp() call after the ‘NotMD5Hash’ prompt

I tried patching the binary at first to show the position of the boats on the map and to skip the NotMD5Hash() prompt; but for some reason in the later levels the program started outputting crap and crashing. Not sure if my patching was not done well or there is some sort of protection in place; I quickly abandoned this approach.

Debugger approach

What I did instead is use gdb with two breakpoints. The first one at 0x403757, after the prompt for NotMD5Hash(). The prompt is easy to pass; simply set $rdi to $rsi or vice-versa so the parameters are equal, and you skip the hash check.

The second one I placed at 0x403b4e, which is the check to see if the level is finished. By printing $rax with the debugger, you can get the bitmask with valid hits in the level. I did this for every level and wrote a little python script to get the moves required to beat each level (b is the bitmask obtained from $rax):

 for x in range(0, 63):
    if b & (1 << x):
        print("%c%d" % ((int)(ord('a') + (x / 8)), (x % 8) + 1))

I simply pasted the output of the python script into the game to easely beat each level.

End of the game

When all the levels are finished, the following message appears:

Aye!PEWYouPEWfoundPEWsomePEWlettersPEWdidPEWya?PEWToPEWfindPEWwhatPEWyou'rePEWlookingPEWfor,PEWyou'llPEWwantPEWtoPEWre-orderPEWthem:PEW9,PEW1,PEW2,PEW7,PEW3,PEW5,PEW6,PEW5,PEW8,PEW0,PEW2,PEW3,PEW5,PEW6,PEW1,PEW4.PEWNextPEWyouPEWletPEW13PEWROTPEWinPEWthePEWsea!PEWTHEPEWFINALPEWSECRETPEWCANPEWBEPEWFOUNDPEWWITHPEWONLYPEWTHEPEWUPPERPEWCASE.
Thanks for playing!

The letters we obtained are FHGUZREJVO. After re-ordering, that turns to OHGJURERVFGUREHZ. ROT13 de-coding that turns it into BUTWHEREISTHERUM.

Re-running the game and entering BUTWHEREISTHERUM into the coordinate prompt (‘in the sea’) gives us the flag!