To be clear, the cheats shown below are the results of client-side modifications. No malicious actions were performed against Disney's servers.
To start things off, here are the results of a simple jump hack which allows the avatar to leap over 15 times higher than what he otherwise should:
This next video demonstrates a similar cheat, modifying our character's speed. Towards the end of the video you'll see us stop and wait for another user to run by to give you a sense of what is "normal":
Here is what the ridiculous jump height looks like from the perspective of another user:
Some side effects of the jump hack include the ability to... fly? Apparently if a character is able to leave the "sphere" of influence of their ship, the physics engine sets the avatar's Z coordinate to the height at which they moved away from the ship:
Some more flying... we can actually fly along the water faster than any ship in the game can move:
Except of course, our ship, which can travel faster than enemy's cannonballs. This is the default ship given to you in the game. We have modified the ship's speed, acceleration, ramming power, maximum crew, maximum cannons, and other fun traits:
If you're interested in the technical aspects behind these modifications, read on...
There are three files we need to worry about modifying, all located within the install directory:
- Phase1.pyd
- phase_1.mf
- Launcher1.exe
phase_1.mf is an archive containing DLLs needed during runtime, as well as a copy of Phase1.pyd. When the game is run, this file's contents are extracted and the internal Phase1.pyd overwrites the external one.
Launcher1.exe is a UPX-packed executable that contains MD5 checks to ensure integrity of the .mf and .pyd files. UPX is an executable packer commonly used by malware. However, it is quite efficient and is also used for non-malicious executables like this one. Here's an example unpacking session:
$ upx -d Launcher1.exe
File size Ratio Format Name
-------------------- ------ ----------- -----------
1925120 <- 1512960 78.59% win32/pe Launcher1.exe
Unpacked 1 file.
Once we've unpacked the file, we can make the 2-byte NOP patch to bypass the MD5 check, allowing us to arbitrarily modify the Python code within the other files. Here's the before:
Launcher1.exe (base address 0x400000)
00422188 E8 88 8F FE+ call std::basic_string....
0042218D 85 C0 test eax, eax
0042218F 75 49 jnz short loc_4221DA
00422191 8D 75 94 lea esi, [ebp+var_6C]
And here's the after:
Launcher1.exe (base address 0x400000)
00422188 E8 88 8F FE+ call std::basic_string....
0042218D 85 C0 test eax, eax
0042218F 90 nop
00422190 90 nop
00422191 8D 75 94 lea esi, [ebp+var_6C]The above code is where the Launcher does a string comparison between the calculated MD5 on disk and the MD5 value sent from the server. By overwriting the conditional jump that was there with NOP instructions, we force the check to always pass.
Now that the Launcher process ignores the integrity of the frozen Python code, we can go ahead and begin to modify the Python objects in Phase1.pyd.
The code objects within the PYD file are serialized, and while we could modify values in a hex editor for a quick and dirty cheat, there is a much more surgically precise and flexible method that we demonstrated in our presentation at REcon. The process involves deserializing and disassembling the Python code objects. You can then modify constant values, change byte code instruction logic, and re-inject the code back into the PYD. If you're interested in this method, you can peruse our slides. For the rest of you, we will continue and walk through some simple byte swap cheats that require nothing more than a hex editor.
If we load Phase1.pyd into a hex editor, or IDA, we can see string references to Python namespaces. Most of the interesting ones are suffixed with Globals. For example, pirates.reputation.ReputationGlobals, which governs experience needed to level up, pirates.economy.EconomyGlobals defines the cost of any item in the game, pirates.piratebase.PirateGlobals controls the physics of your character, including speed, acceleration, and jump height. The format of the object table within the PYD is: name, pointer, length. So, if you find a reference to a possibly interesting namespace, you can follow the pointer value to locate the serialized object. That would be where the values to change would reside.
I will cover one simple jump hack and leave the rest as an exercise for the reader. The default value for a character's speed and jump force in Pirates is 24.0. I discovered this using our AntiFreeze tool (see slides mentioned previously). For the purposes of continuing this walkthrough, here is what the disassembly of the Python code looks like that initializes the in-game character's physics attributes:
load_const 161 # '24.0'
store_name 196 # "'ToonForwardFastSpeed'"
load_const 161 # '24.0'
store_name 197 # "'ToonJumpFastForce'"
load_const 162 # '8.0'
store_name 198 # "'ToonReverseFastSpeed'"
load_const 163 # '120.0'
store_name 199 # "'ToonRotateFastSpeed'"
load_const 161 # '24.0'
store_name 200 # "'ToonForwardSpeed'"
load_const 161 # '24.0'
store_name 201 # "'ToonJumpForce'"The code above is from a disassembled python code object stored within the PYD. The float values themselves can be found with a simple byte search. However, because of how Python code objects are structured, these data values are not necessarily next to each other within the file. Also, if you are not rebuilding the PYD's object table, any changes must not affect the size of the file. Otherwise, the game will bail while trying to deserialize the code objects.
When a float value is serialized in Python, it is stored in a string representation. So, 24.0 is stored in file as "24.0f", the "f" indicating it's a float. For the purposes of a demonstration everyone can easily follow along with, simply search Phase1.pyd for all occurrences of "24.0f". Now, if we simply replace the correct 24.0 value to, say, a 99.0 value, our character will be able to leap buildings.
After we do that, however, we also have to consider the phase_1.mf archive file. This file contains a copy of Phase1.pyd, and it overwrites our changes upon game execution. Here is a quick Python script that will copy the contents of your Phase1.pyd file into the phase_1.mf so that the changes are persistent:
#!c:\python\python.exe
#
# injects Phase1.pyd into phase_1.mf in parts because
# the file size is too large for python to manipulate
# in memory as a string.
pyd = file('Phase1.pyd', 'rb').read()
old_mf = file('phase_1.mf', 'rb')foo = old_mf.read(1770) # index of PYD within MF
foo += pyd
old_mf.seek(1770+len(pyd))
bar = old_mf.read()
result = foo + bar
old_mf.close()
new_mf = file('phase_1.mf', 'wb')new_mf.write(result)Once this has been completed, we are able to run the game and test out the injected cheat. If all went well, our character should be able to jump as depicted in the videos above. The cheats involving modification of the game economy and ship characteristics become more complicated--too complicated to attempt using simply a hex editor--but our slides give the information needed to start down that path.
Hopefully you've got a basic understanding of how to mess around with this game. The demonstrated method above is a quick solution, so check out the slides if you'd like to see the "correct" way to do it. Also, keep in mind these techniques can be applied to any game written in a dynamic language due to the inherent type information requirements. For those who would like to extend the research, take a look at EVE Online, which reportedly also utilizes Python.
If you've got any questions, feel free to e-mail me @tippingpoint.com, my e-mail id is aportnoy.
