Write-up for FlareOn7 challenge #3 - wednesday

10 min

Description

We are introduced to the challenge with the following message:

Be the wednesday. Unlike challenge 1, you probably won’t be able to beat this game the old fashioned way. Read the README.txt file, it is very important.

If we open the README.txt file, we find the following:

                   --- BE THE WEDNESDAY ---

                               S
                               M
                               T
                              DUDE
                               T
                               F
                               S

            --- Enable accelerated graphics in VM ---
              --- Attach sound card device to VM ---
                --- Only reverse mydude.exe ---
                   --- Enjoy it my dudes ---

We set up our VM accordingly and start analyzing the file mydude.exe as indicated.

Analysis

Basic exploration

If we load our binary into Detect It Easy, we find that mydude.exe is a 32-bit PE Windows executable.

Output of Detect It Easy (DIE)

We launch mydude.exe and are prompted with what appears to be the welcome screen of a weird game called Wednesday starring an amorphous frog.

Wednesday's welcome screen

Clicking into the DUDE button will get us into the actual game screen. It appears to be a simple game where we have to avoid the obstacles in order to advance, either by jumping over them or ducking down. If we hit one of the obstacles, the game is automatically reset.

Moreover, it looks like we do not only need to avoid the obstacles, but also we must do so in a certain fashion for each obstacle (according to its appearing order), either by jumping or ducking.

Wednesday's game screen

After a couple minutes playing, we start to discover the pattern that describes the way in which each appearing obstacle has to be dodged. In particular, for the first obstacles, we have:

_ _ * * _ _ _ * _ * * * _ * _ _ ... jump (*) | duck (_)

Locate interesting parts of code

Let’s load mydude.exe in IDA, and make sure to allow it to load the embedded symbols detected. A ton of functions will be found, most of them having seemingly weird/random suffixes. Such rare function names appear to be related to the fact that the game has been coded in Nim (you can find a bunch of symbols containing “Nim” in it).

Although we have symbols, we still have a huge amount of functions and code to explore. Thus, in order to locate interesting sections of the code logic, we will use a common trick from the old days of game hacking: finding interesting values in memory for things whose values change in a predictable and known manner. By locating them, we will be able to track back to meaningul code that references them. In this case, we will use Cheat Engine to do so.

One obvious candidate is the Score value that gets incremented any time we pass an obstacle. We just need to start the game (Score = 0) and inmediately look for memory locations storing the value 0 with New Scan and a Value: 0. After we pass a couple obstacles, we can quickly change the value field to Value: 2 and clicking Next Scan. This way, we will find memory locations that held a value of 0 during first scan, and a value of 2 during second scan.

Cheat Engine showing the two memory references to Score value

We find two interesting memory locations, 0x00443D64 and 0x0044DDB0, lying within our executable memory region and referencing values having the exact behavior of the score being updated as we pass through obstacles. These memory addresses have been named SCORE_REF_1 and SCORE_REF_2 in Cheat Engine for clarity purposes.

Let’s take a look at the memory address 0x00443D64 in IDA. We find that it is a named symbol starting with _prev_score. Also, it gets cross referenced by functions @resetEverything__Q1G0... and @update__Arw3....

IDA's view of first reference to Score value found

If we take a quick look into the @resetEverything__Q1G0... function, we instantly find in its first basic block a reference to an interesting memory location named _obstacles__Xqz....

First basic block of @resetEverything__Q1G0... function

At its turn, obstacles__Xqz... points to another memory location named _TM__V45tF8....

Memory view of _TM__V45tF8... value pointed by obstacles__Xqz...

But wait… we observe a «table» of 1’s and 0’s describing a familiar pattern:

_ _ * * _ _ _ * _ * * * _ * _ _
0 0 1 1 0 0 0 1 0 1 1 1 0 1 0 0

Exactly! We find the same pattern we encountered while playing the game at the very beginning. If we take a closer look, there are 296 (0x00000128) values, either 1 or 0, conforming this array of bytes.

Let’s take a look at cross references to _obstacles_Xqz.... We find three functions reading it. From these, one appears to be an initialization function and another one is the @resetEverything__Q1G0... function where we found it being referenced before.

Cross references to _obstacles_Xqz...

Notice that the third function is @update__Arw3..., the same function that appeared also as a cross reference to the _prev_score memory location, so it is a good idea to dig deeper into this function.

But, before exploring this @update__Arw3... function, let’s take a look at 0x0044DDB0, which is the other interesting memory location we found on Cheat Engine. We find that it is a named symbol starting with _score and cross referenced by the function @onCollide__9byA....

IDA's view of second reference to Score value found

If we pull the thread on cross references, we find that @onCollide__9byA... is referenced by @onCollide__BN6X..., which is referenced by @checkCollisions__P9bT..., which is referenced by @updateScene__rbzI....

Cross references to @onCollide__9byA...
Cross references to @onCollide__BN6X...
Cross references to @checkCollisions__P9bT...

Now let’s finally explore @update__Arw3... function. We find that it calls @updateScene__rbzI..., followed by a conditional jump that will lead into a call to @resetEverything__Q1G0... in case that the byte value pointed by the memory location stored at eax+F9 is equal to 1.

First basic blocks of @update__Arw3... function

So, with the information we have obtained so far, we can make an educated guess about the inner working of the game with respect to collision management:

  • The game gets constantly updated (for example, frame based updates)
  • Every time the game updates, it will check for «general collisions» with an obstacle.
  • Every time the game goes through an obstacle, it will check for «table collisions», i.e. whether the way in which the obstacle is dodged coincides with the correspoinding move, as described on the obstacle’s table.
  • If either of the two collisions occur, the memory location pointed previously by eax+F9 will be set to 1. Thus, at next update, it will enter into the reset function to restart the game.
  • After 296 obstacles successfully passed, «something» would happen; presumably, the flag should be revealed in some way.

Bypass collisions

Our objective will be to patch the binary so that we can bypass all kinds of collisions while preserving the game behavior as if we completed it in a legit way.

The hasty idea would be to just patch the conditional jump after the comparison cmp byte ptr [eax+0F9h], 1 so it never gets into the basic block calling the reset function. However, this does not look like a good idea, as the code logic for recreating and showing the flag could (and indeed, will) be dependant of the actual code flow around the checks that set the memory location pointed by eax+F9 to 1 at that point.

Thus, our approach will be to find where this memory location gets written to 1, so we can explore the code around and patch effectively without altering the global code flow. To do so, we will use the x64dbg debugger (well, actually the 32-bit version, x32dbg).

We load mydude.exe on x32dbg and let it run until it shows the welcome screen. Now we place a regular breakpoint at 0x433D55 (the location of the previously described comparison) and click on DUDE to start the game.

Breakpoint set at 0x433D55 in x32dbg

The breakpoint will be hit inmediately and pause the execution. Now, let’s get the memory address pointed by eax+F9 into the memory dump view by doing: Right click -> Follow in Dump -> Address: EAX+F9

Then, we will add a hardware breakpoint on write access (at byte level, as the comparison is done at byte level as well) to this memory location. This can be seen in the following screenshot.

Set hardware breakpoint on write access to the byte pointed by eax+F9

We can now remove (or disable) the previous breakpoint on the comparison. Let’s continue running the program (pressing F9) and observe that the hardware breakpoint is getting hit repeatedly at two different places.

  • At 0x43266C the memory location gets written to 0.
  • At 0x432247 the memory location gets written to 1.

It looks like these writes are used at each game update to control if a «general» collision happened.

We are only interested in the location where it gets set to 1 (indicating a collision). Thus, in order not to hit the breakpoint for the location that sets the value to 0, we will modify the hardware breakpoint to be conditional. In particular, we set it not to be triggered whenever the EIP would land into the address 0x432673, which is the instruction following the write instruction at 0x43266C.

Edit hardware breakpoint to add condition

Patch general collision check

If we take a look at the code flow around this write at 0x432247, we observe that we arrive to the basic block containing it depending on a previous conditional jump js.

Code flow around general collision write at 0x432247

One quick and naive approach would be to patch this 1 (indicating collision) into a 0. However, this would not guarantee that the correct code path is followed. Thus, it is safer to patch the conditional jump to always take the correct path, where the memory location of our interest would not be written to 1.

We can easily patch this conditional jump into an inconditional jump by modifying the assembly instruction in the debugger, from js into jmp.

Patch conditional jump to bypass general collision check

Patch table collision check

Now, we allow the game to run, noticing that it does not break instantly as before (the 0 write is not triggered because of the condition in the hardware breakpoint; the 1 write is bypassed with the previous patch to the conditional jump).

Indeed, we observe that if we start the game, it will go over the first two obstacles without hitting them and will only trigger the hardware breakpoint when hitting the third obstacle. This makes sense, as this third obstacle is the first one in which we would have to jump over, i.e. we have the first 1 at third position in the pattern/table we found before.

This time, the breakpoint will lead us to a write of value 1 to the memory location we are tracking, ocurring at 0x43235E.

If we take a look at the code flow around this write at 0x43235E, we observe a similar situation as before: we arrive to the basic block containing the write depending on a previous conditional jump jz.

Code flow around table collision write at 0x43235E

As we did before, instead of patching this 1 into a 0, we will patch the conditional jump by changing this jz into jmp in the debugger, so it always follows the correct path.

Patch conditional jump to bypass table collision check

Retrieve flag

We have patched both conditionals that led to basic blocks containing writes of value 1 (indicating collision) into the memory location that was checked for restarting the game in case of a collision. Essentially, we have bypassed both «general» and «table» collisions, while preserving as much as possible the control flow followed by the original code.

Thus, now it is just a matter of letting it run for a while. After 296 obstacles passed, it will show us a victory screen with the string “Winner!” as well as our flag: [email protected]

Wednesday's victory screen showing the flag

A quicker alternative

Actually, if we had taken the table of 296 1’s and 0’s and treated them as binary digits, we could have decoded them and obtained the flag directly without the need to allow the game to be run.

As an example, a simple python decoding routine is shown below.

table = "00110001011101000101111101101001001101010101111101110111010001010110010001101110001100110111001101100100001101000111100101011111011011010101100101011111010001000111010101100100001100110111001101000000011001100110110001100001011100100110010100101101011011110110111000101110011000110110111101101101"
n = int(table, 2)
flag = n.to_bytes((n.bit_length() + 7) // 8, 'big').decode('ascii')
print (flag)
$ python dec.py
[email protected]

This approach might have saved some time, but also would have spoiled the fun we had during our journey with this fancy frog ;)

Anyway, we obtained the flag and completed the third challenge!

Twitter Follow

Comments