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.
If we load our binary into Detect It Easy, we find that
mydude.exe is a 32-bit PE Windows executable.
mydude.exe and are prompted with what appears to be the welcome screen of a weird game called
Wednesday starring an amorphous frog.
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.
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
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
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.
We find two interesting memory locations,
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_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
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
At its turn,
obstacles__Xqz... points to another memory location named
But wait… we observe a «table» of
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
0x00000128) values, either
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.
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
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
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.
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+F9will be set to
1. Thus, at next update, it will enter into the reset function to restart the game.
296obstacles successfully passed, «something» would happen; presumably, the flag should be revealed in some way.
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
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).
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.
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.
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.
0x43266Cthe memory location gets written to
0x432247the memory location gets written to
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
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
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
We can easily patch this conditional jump into an inconditional jump by modifying the assembly instruction in the debugger, from
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
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
As we did before, instead of patching this
1 into a
0, we will patch the conditional jump by changing this
jmp in the debugger, so it always follows the correct path.
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:
A quicker alternative
Actually, if we had taken the table of
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!