✅Until the Cows Come Home
Last updated
Last updated
At "Pwnie Express" in the game, we discover that the game implements fast travel functionalities:
Search for "FastTravel" in symbols, we find a function named Player::GetFastTravelDestinations()
. In this function, we see 4 interesting strings:
Town
UnbearableWoods
TailMountains
GoldFarm
Obviously, these are the locations that the player is able to travel to. The code for each location looks similar. For example:
Each block of code contains a destination string and an "ID" that represents that destination. There must be more strings like this in the binary. Double click on one of the strings, for example, "GoldFarm". Scroll up a bit, we can see some "hidden destinations" such as "Cowabungalow" and "CowLevel":
But how do we get the "ID" for these two locations? Let's examine what we know so far:
0x4
-> 4 -> "Town"
0xf
-> 15 -> "UnbearableWoods"
0xd
-> 13 -> "TailMountains"
0x8
-> 8 -> "GoldFarm"
Hmm...These numbers are not IDs, they are just the lengths of location names! Now we conclude:
Index | Destination |
---|---|
| Town |
| UnbearableWoods |
| TailMountains |
| GoldFarm |
| Cowabungalow |
| CowLevel |
To expose Cowabungalow / CowLevel, we have to overwrite an existing destination with new index and name string. I chose to overwrite "UnbearableWoods" with "Cowabungalow" or "CowLevel". Note that we are modifying pointers, not ASCII strings.
After some trial and error, I found that "CowLevel" is the correct pointer and "Cowabungalow" is just a display name of that pointer. Overwrite "UnbearableWoods" with "CowLevel":
To change the pointer, IDA does not allow us to use "Assemble" here, so we have to patch raw byets. The highlighted bytes represent the offset address of "aUnbearablewood":
Change it to the offset address of "aCowabungalow":
However, fast travel is not available if we haven't discovered the location yet. How to deal with this problem?
Look deeper in Player::GetFastTravelDestinations()
, we see a function named FastTravelDestination::AddToListIfValid()
gets called for each destination:
Now the problem is where to patch. There are quite a few jz
and jnz
instructions in this function. In the pseudocode, there is an if-block caught my eye:
Based on my understanding, this function's logic seems to be "if the destination is available, then add it to the fast travel list". We can reverse this logic somehow and get "if the destination is NOT available, then add it to the fast travel list". By doing so we can unlock the "hidden destinations" that we haven't been to.
In assembly, the this->IsAvailable(this, player)
check corresponds to the following block:
In some other disassembler you may see this is jne
. Note that jnz
and jne
are the same thing: a conditional jump when ZF
(the "zero" flag) is equal to 1.
What we need to do here is patching it to jnz
so that the logic is flipped:
Go back to the game:
Teleport to Cowabungalow and talk to Michael Angelo:
Get a rubik cube from him and use it to kill the Cow king 😎