title: Writeup ESAIP CTF 2023 - Super Oiram Party
date: May 29, 2023
tags: ESAIP_CTF_2023 writeups reverse
Description:
You are still in the Nantendo secret room, and you came across a strange file in one of the computers.
You need to dig into this file a little bit, you might find something interesting.
**Author: Ooggle**"
Files:
Let's download the file and run some commands:
$ file super_oiram_party.pbp
super_oiram_party.pbp: data
$ binwalk super_oiram_party.pbp
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
448 0x1C0 PNG image, 80 x 80, 8-bit/color RGBA, non-interlaced
889 0x379 Zlib compressed data, compressed
6433 0x1921 PNG image, 480 x 272, 8-bit/color RGBA, non-interlaced
6874 0x1ADA Zlib compressed data, compressed
55202 0xD7A2 ELF, 32-bit LSB MIPS-II executable, MIPS, version 1 (SYSV)
108816 0x1A910 bix header, header size: 64 bytes, header CRC: 0x82801F00, created: 2004-01-22 20:09:36, image size: 170139136 bytes, Data Address: 0x60502140, Entry Point: 0x200, data CRC: 0x86801900, CPU: Blackfin, compression type: none, image name: ""'()
Ok, I now know that it contains a MIPS executable file, and by searching for "pbp file" on Internet it says that it is either a psx (PS1) or a PSP game file. I will try to run this file on both consoles emulators (ePSXe and PPSSPP).
And it run on PPSSPP! Little gameplay footage ahead:
And when I click on start
, the following screen appears:
By now, it's still not clear what we have to do, and I want to reverse the code statically and not bother doing it with the PPSSPP debuger I know nothing about for now.
By googling how to extract code from pbp files, I came across PBP Unpacker, a windows only tool to extract all the content of a pbp file into separate ones.
After installing it using wine (because I'm on Linux), I loaded my pbp file into PBP Unpacker:
It match with the output binwalk gave me earlier, so DATA.PSP must be the MIPS executable.
By opening DATA.PSP
in Ghidra, it detect it as a MIPS 32bit binary, so we have our code!
First of all, we can check for strings we know: "You shall not know my secret."
and "(pspsdk is really amazing!!)"
and... it's stored in clear in the binary and used in a big function:
The majority of the function code is in a big while(true)
I can see a lot of if
statments before the code can reach the function with the "Well done!"
parameter. This is probably the main game loop:
from https://gameprogrammingpatterns.com/game-loop.html
I assume the other ifs
are here to handle the controls but some magic numbers are used to defined the keys. Also, pressing a button seems to be filling a buffer called local_154
that is then used to make checks when pressing start button.
Ok, let's rename variables now that we have a better understanding of the code:
It's better but we still have no idea what those magic numbers are refering to!
The game give the information that it's using pspsdk as a development toolkit, and luckily it's open source: https://github.com/pspdev/pspsdk.
By digging in some tutorials and the source code, we found all the button declarations and replace them in Ghidra.
enum PspCtrlButtons
{
/** Select button. */
PSP_CTRL_SELECT = 0x000001,
/** Start button. */
PSP_CTRL_START = 0x000008,
/** Up D-Pad button. */
PSP_CTRL_UP = 0x000010,
/** Right D-Pad button. */
PSP_CTRL_RIGHT = 0x000020,
/** Down D-Pad button. */
PSP_CTRL_DOWN = 0x000040,
/** Left D-Pad button. */
PSP_CTRL_LEFT = 0x000080,
/** Left trigger. */
PSP_CTRL_LTRIGGER = 0x000100,
/** Right trigger. */
PSP_CTRL_RTRIGGER = 0x000200,
/** Triangle button. */
PSP_CTRL_TRIANGLE = 0x001000,
/** Circle button. */
PSP_CTRL_CIRCLE = 0x002000,
/** Cross button. */
PSP_CTRL_CROSS = 0x004000,
/** Square button. */
PSP_CTRL_SQUARE = 0x008000,
/** Home button. In user mode this bit is set if the exit dialog is visible. */
PSP_CTRL_HOME = 0x010000,
/** Hold button. */
PSP_CTRL_HOLD = 0x020000,
/** Music Note button. */
PSP_CTRL_NOTE = 0x800000,
/** Screen button. */
PSP_CTRL_SCREEN = 0x400000,
/** Volume up button. */
PSP_CTRL_VOLUP = 0x100000,
/** Volume down button. */
PSP_CTRL_VOLDOWN = 0x200000,
/** Wlan switch up. */
PSP_CTRL_WLAN_UP = 0x040000,
/** Remote hold position. */
PSP_CTRL_REMOTE = 0x080000,
/** Disc present. */
PSP_CTRL_DISC = 0x1000000,
/** Memory stick present. */
PSP_CTRL_MS = 0x2000000,
};
Now let's sum up all the ifs we have when start
is pressed:
if(buffer[6] == buffer[5]) // 5 = PSP_CTRL_LEFT
if(buffer[6] == 0x80) // 6 = PSP_CTRL_LEFT
if(buffer[6] << 1 == buffer[2]) // 2 = 0x100 = PSP_CTRL_LTRIGGER
if(buffer[7] == 0x20) // 7 = PSP_CTRL_RIGHT
if(buffer[0] == buffer[4]) // 4 = PSP_CTRL_UP
if(buffer[0] == 0x10) // 0 = PSP_CTRL_UP
if((buffer[1] >> 7 == 0) && ((buffer[1] & 0x40U) != 0)) // 1 = PSP_CTRL_DOWN
if(buffer[3] >> 0xf != 0) goto LAB_08901340;
if(((buffer[3] & 0x4000U) == 0) || (!bVar2)) goto LAB_08901340; // 3 = PSP_CTRL_CROSS
You can see in the disassembly that the first 8 inputs are recorded, and are stored into buffer
. Now that we have all our inputs, we must follow them then press start in the right order: up, down, L, cross, up, left, left, right, start.
Here is a gif of the flag:
flag: ECTF{up_down_L_cross_up_left_left_right}