@Hack24 Facade - Wed, Mar 6, 2024 - Linkster78
A mario clone with some well-guarded secrets | STEG | Athack24
Facade
Steganography - Facade
Did Ancient Egypt have any flagpoles Ctrl + W
A .zip file is provided on the challenge site, when extracted, it reveals an executable a.exe
and a user
folder which contains all of the game’s assets and resources. It’s not made clear where the flag is, which lead to quite a bit of struggle on my part down the line.
When opened, the game looks like this, cute!
Gaming.
When I first saw this challenge, I assumed that it was similar to the Hackfest 2023’s game challenge. In that case, the flag was at the end of the game, it was just impossible to beat it without modifying the code.
So I went on to try and beat the game, without much success. This jump was simply too high and the bugs were annoying to avoid.
Clearly some game/resource patching was in order. Let’s see what we’re dealing with.
tutorial_1.txt
The first thing that I noticed was the tutorial_1.txt
in user\Mukki\levelinfo
. The file looks something like this:
# TekiInfo
11 # 敵最大数
{
1
... more entries ...
4512 128
}
I quickly realized that 11 matched up with the amount of entries in the file. Setting it to 0 and removing all of the entries between the curly brackets yielded this:
The bug at the start is gone! (spoiler alert: so were all of the other ones)
After playing with it for a bit, I came to this understanding of the format:
By setting the entity type id
to 0, we even get a second player that doesn’t move.
Who knew that making a multiplayer game was this easy?
Extra controls
Unfortunately even with all of the enemies removed, I still couldn’t cross the high jump, so I thought I’d look into the game’s internals.
I first checked for some hidden controls since there’s no help menu or manual to help us.
Searching for calls to GetKeyState
and GetAsyncKeyState
, we find 0x25
, 0x27
, 0x20
and 0x58
.
Comparing these calls to the Virtual-Key codes table, we find our expected Left/Right
and Space
keys, but also find X
.
As it turns out, holding X
lets us run, which lets us barely cross the gap and go through the rest of the level normally. (Albeit with a lot of failures, skill is still part of the equation)
My teammate @klammydia also figured out through keyboard-sliding that
Ctrl+C
displays a debugging menu, which came in handy later on.
Anyways, completing the level leads us to a whole big load of nothing:
Now what?
So completing the level didn’t lead to anything…
Looking back, the category of the challenge is Steganography, so my next thought was that maybe the flag was stored in one of the asset/resource files? It could be in a hidden frame of animation, hidden in the background or it could be in unviewable level data (above the viewport perhaps).
Unfortunately, running strings
on the files didn’t give us a flag, we’re now all out of options.
Just kidding, obviously. This is when I started going through the decompilation provided by Ghidra, it wasn’t much and it was terribly mangled, but it was better than nothing.
The whole game code is contained within one 2700 line function.
The challenge designer later told me that he force inlined the entirety of his codebase.
Since the code wasn’t great, I couldn’t figure out the exact format for the files, but I came out with a few takeaways.
Here are the filetypes and their purposes:
.lvl
: Entry level file (raw level data).tmp
: Texture map file.bci
: Graphic/texture file.mld
: Mold file (pretty much entity/texture metadata).txt
: The entity list, as seen earlier
From the reversing effort, I only really picked up on a few details since after reading the headers, the rest of the data was read in some hard to follow buffer and handled way further down the line.
- For
.mld
files, there are only 4 bytes. The first two bytes are the width and height of the associated.bci
texture. The fourth byte is the amount of frames (of animation) contained within the texture file. I have no idea what the third byte is. - For
.bci
files, the first byte represented the amount of colors in the color palette. Then
following bytes were then indices (? unsure) into a color palette. I never really figured out the rest. - For
.lvl
files, the first two bytes represent a big-endian short which seems to be the level width/length.
After figuring this (very little) information out, I spent a lot of time messing with the beetle.bci
and beetle.mld
files, realizing that they weren’t used in the game, but I couldn’t figure anything out at this point. Maybe it’ll come into play later? (it will)
The first flag
Back to one of the earlier theories, I thought that maybe the flag was in the level, but off-screen somehow. After learning a little bit about the .lvl
format, I tried extending the level length by changing the value in the header.
Changing 0x12CC (4812) to 0x20CC (8396):
Playing through the level until the end, I could now see something just out of reach…
If that doesn’t look like an
A
on the right, I don’t know what does.
The only issue was that I couldn’t get over this wall. It might look like there’s a hole near the top but do not be fooled, the top of the screen is impenetrable.
Fortunately, my years of procrastinating school work to play video games paid off and with the right set of inputs, I managed to trick the game into letting me through the wall.
The flag reads as follows: ATHACKCTF{th3Catac0mbs0fVide0game5}
Beetles and co.
Again, back to an earlier theory. I noticed that the beetle.bci
and beetle.mld
files were unreferenced in the game’s executable. Normally I’d chuck it up to unused content, but since this is a steganography challenge, there’s no such thing as unused content.
If we simply replace the player
files with the beetle
files to get them to load, we see this:
If you’re anything like me, two thoughts will come to mind:
- This is the shittiest-looking beetle that I’ve ever seen.
- That seems like an appropriate length for a flag.
Looking at the beetle.mld
file, one thing seems peculiar.
The fourth byte, which we determined earlier to be the amount of frames, is set to 0. This explains the garbage looking beetle, since this is probably just RAM data being rendered as an image.
Let’s change this byte to 1 and reopen the game.
That seems… better? Still not a flag though.
Another thing that seemed odd was the color palette used by beetle.bci
.
The beetle.bci
file calls for 3 colors, as indicated by the first byte, but all three bytes are set to 0, effectively merging all three colors of the sprite into one. I messed with these for a bit, changing one of them at a time, then two, but it kept giving me garbage.
After being up for nearly 26 hours, I even managed to convince myself that I had uncovered a Y
in this image (to the great amusement of my teammates).
Can you see it? Tell me I’m not alone on this.
The answer came as a hint from one of the organizers:
Six colors? God dammit, let’s change the file and see.
Well this isn’t quite it, but we see some organic shapes. After more trial and error, I came up with this arrangement.
Flipping it we get our flag: ATHACKCTF{spr1teEdit0rMastrPhar0ah}