Modding games
You're going to learn a couple of techniques for creating mods with the
command line interface of Proctal. This is not meant to be an extensive
tutorial on modding but it covers enough concepts so that you get a
general idea of how this works.
Games are just programs that run on a computer, and all programs load
code and data into memory. With the ability to modify the contents in
memory, you're able to change the behavior of a game however you want.
The examples access a program whose Process ID (PID) is
12345 on x86-64 Linux.
Finding known values
Suppose that the game shows you that you have 100 health
points. How would you go about searching for that value in
memory? First you must guess in what form that the programmers
decided to store it. It could be stored as an integer or
IEEE754 floating-point number. If the game does not use commas
to represent health points, it's most likely using an integer.
Now you must guess how many bits it's using to represent that
integer. The most common number of bits for integers is
32, so that's a first good guess.
You can search for values in memory by using the search command. Here's
how you search for every 32-bit integer with the value
100:
$ proctal search --pid=12345 --type=integer --integer-bits=32 --eq=100
7FCDB1D56638 100
7FCDB1D567A8 100
7FCDB1D571B0 100
7FCDB1D577F8 100
7FCDB1D57F38 100
7FCDB1D58738 100
7FCDB1D58BA8 100
7FCDB1D58DA8 100
7FCDB1D58FA8 100
7FCDB1D591A8 100
7FCDB1D593A8 100
7FCDB1D595A8 100
[...]
On each line you get an address and the current value on it.
It's not uncommon to get thousands of lines because there can
be many other things with the same value in memory.
There is a way to filter out the noise. Start by saving the
output to a file. Here's the same command from before but now
redirecting the output to a file called
results1.
$ proctal search --pid=12345 --type=integer --integer-bits=32 --eq=100 > results1
Using the wc program you can count how many matches you got.
$ wc -l < results1
3463
In this example there were 3463.
A common strategy for reducing the number of matches is to
cause actions in the game that can change the value. The search
command has the --review option that allows you
to match against the results from a previous search, so you're
able to filter out the addresses that do not contain the new
value.
Assuming that your actions in game changed the value to
58, here's how you would match against the
results stored in results1 and store the new
results in results2:
$ proctal search --pid=12345 --type=integer --integer-bits=32 --eq=58 --review < results1 > results2
And counting yields:
$ wc -l < results2
34
Down to 34. That's much easier to handle. You can repeat the
previous step until you either end up with 1 value or you're
unable to shorten the results any further.
You can also use the watch program to see how
the values change as you're playing the game:
$ watch proctal search --pid=12345 --type=integer --integer-bits=32 --eq=58 --review '<' results2
Every 2.0s: proctal search --pid=12345 --type=integer --integer-bits=32 --eq=58 --review < results2
7FCDB1D577F8 58
7FCDB1D57F38 58
7FCDB1D58738 58
7FCDB1D58BA8 58
[...]
If your guesses were right you should be able to pick the right
address from the search results. If you get no results then you
need to rethink about how the value might be stored in memory.
Thinking like a programmer will help you guessing better.
Sometimes programmers store copies of a value in different
places in memory. You need to pick the source of the copies.
It's usually the one that when modified updates the others.
Read more about searching values here.
Finding unknown values
Suppose that the game displays a health bar instead of health
points. How can you search for the address of a value that you
don't know?
You can look for all possible values of a given type and then
cause actions in the game that change the value. In this case
when you gain health points you can assume that the value
increases and when you lose health points the value decreases.
Using the --increased and
--decreased options, you can gradually reduce
the number of matches.
$ proctal search --pid=12345 --type=integer --integer-bits=32 > results1
$ wc -l < results1
172341
$ proctal search --pid=12345 --type=integer --integer-bits=32 --increased --review < results1 > results2
$ wc -l < results2
41346
$ proctal search --pid=12345 --type=integer --integer-bits=32 --decreased --review < results2 > results3
$ wc -l < results3
6121
[...]
While you may not always be able to shrink the search results to a single match, this greatly reduces the number of addresses you would have to manually check.
Reading and modifying values
By knowing the address of a value in memory you are able to
read and write to it by using the read and write commands,
respectively.
Assuming that 55B4AA941048 is the address where
the game stores your health, this is how you would check its
current value:
$ proctal read --pid=12345 --type=integer --integer-bits=32 --address=55B4AA941048
58
And this is how you would replenish your health back to 100:
$ proctal write --pid=12345 --type=integer --integer-bits=32 --address=55B4AA941048 100
Check its value again and you should see that it has changed to 100:
$ proctal read --pid=12345 --type=integer --integer-bits=32 --address=55B4AA941048
100
Freezing values
What if instead of having to manually set your health points
back to 100 you could just make the game unable to change it?
You can sort of achieve this by passing the
--repeat option to the write command. Assuming
that the address for your health points is
55B4AA941048, here's what the command would look
like:
$ proctal write --pid=12345 --type=integer --integer-bits=32 --address=55B4AA941048 --repeat 1000
What the command will actually do is repeatedly write the same
value over and over again, making it seem like the value is
never changing when actually any changes that the game attempts
to make are just overwritten quickly. The command will keep
executing indefinitely until you explicitly terminate it.
The game may sometimes modify and access the value faster than
the command can write to it again, though.
Rewriting code
Programs can place code and data segments at random offsets.
This means that the addresses for the values you find using the
search command
become useless after you close the program. You would have to
search for them again every time you start the program.
While data is very volatile in memory, code tends to be static
which makes it very easy to search for. If instead of needing
to modify values, you modify the instructions that access those
values then you only have to search for code.
The watch command can
track reads and writes to a memory address. After it detects
that an instruction accessed the memory address, it prints out
the address of the instruction that would be executed next.
Even though it's not the actual instruction that accessed the
memory address, it's still useful information.
Suppose that the address where the game stores your health
points is 55B4AA941048. Here's how you track for
reads and writes on that address:
$ proctal watch --pid=12345 --read --write --unique 55B4AA941048
55B4AA7406AB
55B4AA7406B6
The --unique option is used so that the watch
command does not print the same address twice.
Now when you lose health in the game, more addresses should
show up:
$ proctal watch --pid=12345 --read --write --unique 726BA1DA
[...]
55B4AA740676
55B4AA740682
55B4AA740688
The instruction that decreases the value must be located nearby
those addresses that showed up. You need to examine the code
around those areas. One way to do this is by using
gdb which supports breakpoints, disassembling
instructions and reading registers.
Here's a gdb session where a breakpoint is set
at address 55B4AA740676, then when the
breakpoint is hit the surrounding instructions are inspected
and the value of a register is printed:
$ gdb --pid=12345
Attaching to process 12345
(gdb) break *0x55B4AA740676
(gdb) continue
Continuing.
Breakpoint 1, 0x55b4aa740676
(gdb) layout asm
0x55b4aa740670 mov eax,DWORD PTR [rip+0x2009d2]
> 0x55b4aa740676 mov rdi,rbx
0x55b4aa740679 sub eax,0x1
0x55b4aa74067c mov DWORD PTR [rip+0x2009c6],eax
0x55b4aa740682 mov esi,DWORD PTR [rip+0x2009c0]
(gdb) print $eax
58
There seems to be a mov instruction that loads a
value from memory to the register eax, then a
sub instruction substracts 1 from
that value and finally another mov instruction
stores the new value to that address. If the eax
register contains the current amount of health points then this
is the code that is decrementing the value. Everything else
seems irrelevant.
But before you make any changes to the code, store a copy of
the bytecode. You will need this later to find the code again.
The read command can also disassemble
instructions and supports the --show-bytes
option that makes it print the bytecode underneath each
instruction.
$ proctal read --pid=12345 --type=x86 --address=55B4AA740676 --array=4 --show-address --show-bytes
55B4AA740676 mov rdi, rbx
48 89 DF
55B4AA740679 sub eax, 1
83 E8 01
55B4AA74067C mov dword ptr [rip + 0x2009c6], eax
89 05 C6 09 20 00
55B4AA740682 mov esi, dword ptr [rip + 0x2009c0]
8B 35 C0 09 20 00
To prevent the game from decreasing health points you must
prevent that sub instruction from executing.
If you were to actually remove the instruction, you would have
to relocate every instruction coming after it and recalculate
the addresses that might be used by the instructions. This
would be a costly operation.
But you don't have to.
You can instead overwrite the sub instruction
with nop instructions. There is a variety of
nop instructions but the most commonly used one
takes 1 byte.
That particular sub instruction takes 3 bytes,
so you have to write 3 nop instructions over it.
$ proctal write --pid=12345 --type=x86 --address=55b4aa740679 --array=3 'nop'
The game's code would look like this:
$ proctal read --pid=12345 --type=x86 --address=55B4AA740676 --array=6 --show-address
55B4AA740676 mov rdi, rbx
55b4aa740679 nop
55b4aa74067A nop
55b4aa74067B nop
55b4aa74067c mov DWORD PTR [rip + 0x2009c6], eax
55b4aa740682 mov esi, dword ptr [rip + 0x2009c0]
Now the game will not decrease your health points anymore.
You also could have made the game increase your health points
by replacing the sub instruction with an
add instruction, which also takes the same
number of bytes.
$ proctal write --pid=12345 --type=x86 --address=55b4aa740679 'add eax, 1'
The game's code would look like this:
$ proctal read --pid=12345 --type=x86 --address=55B4AA740676 --array=4 --show-address
55B4AA740676 mov rdi, rbx
55B4AA740679 add eax, 1
55B4AA74067C mov DWORD PTR [rip + 0x2009c6], eax
55B4AA740682 mov esi, dword ptr [rip + 0x2009c0]
Now close and start the game. You're going to use the
pattern command to find the new address of the
code. You must build a pattern out of the original bytecode.
You can just copy and paste, however, you can also use
?? (question marks) to match any byte. This
allows you to ignore volatile instruction operands, such as
addresses which increases the chance of the pattern working on
different versions of the game.
Here's an example that matches the code exactly as it is in
memory:
$ proctal pattern --pid=12345 '
48 89 DF
83 E8 01
89 05 C6 09 20 00
8B 35 C0 09 20 00
55B24157B676
And here's how you would be able to find the code again even if the addresses of the last two instructions change in a new version of the game:
$ proctal pattern --pid=12345 '
48 89 DF
83 E8 01
89 05 ?? ?? ?? ??
8B 35 ?? ?? ?? ??'
55B24157B676
There you have it, now the code is at address 55B24157B676.
$ proctal read --pid=12345 --type=x86 --address=55B24157B676 --array=4 --show-address
55B24157B676 mov rdi, rbx
55B24157B679 sub eax, 1
55B24157B67C mov dword ptr [rip + 0x2009c6], eax
55B24157B682 mov esi, dword ptr [rip + 0x2009c0]
You can apply your changes again using the new address.
Injecting code
In the previous example it just so happened that the
modification could fit into the existing space but what if it
didn't? You can instead redirect code execution to a different
block of memory that you have more control of.
You can use the allocate command to allocate new
blocks of memory. This is how you allocate 1000
bytes with read, write and execute permissions:
$ proctal allocate --pid=12345 --read --write --execute 1000
7F6E0F750008
Here 7F6E0F750008 is the address where the new
block starts.
Suppose that you want to count how many health points you would
have lost. You can store the counter as a 32-bit integer in
memory. You can use the first 4 bytes of the memory block for
that.
Here's the code from the previous example again.
$ proctal read --pid=12345 --type=x86 --address=55B4AA740676 --array=4 --show-address
55B4AA740676 mov rdi, rbx
55B4AA740679 sub eax, 1
55B4AA74067C mov DWORD PTR [rip + 0x2009c6], eax
55B4AA740682 mov esi, dword ptr [rip + 0x2009c0]
One way to redirect execution is to place a jmp instruction. You have to place the address of the code you want to execute in a register. You can use the rax register seeing as its holding the number of healths points that will be decremented by 1 which you no longer want to happen. Here's how the code for the jump instruction looks like:
mov rax, 0x7F6E0F75000C
jmp [rax]
You can use the measure command to figure out how much space this takes up.
$ proctal measure --type=x86 --address=55B4AA740676 "mov rax, 0x7F6E0F75000C" "jmp [rax]"
12
12 bytes. That means that if you were to place this code at
55B4AA740676, you would overwrite all the
instructions up to 55B4AA740682. While it does
overwrite some instructions that you don't want to execute, it
also overwrites one that is needed to keep the program running
correctly. When this happens you can move the instructions you
want to preserve into the memory block.
The code in the memory block will be made up of three sections.
The first section increments the health point counter. The
second section contains the instructions that were overwritten.
The third and last section returns control back to the program.
The code will have to be placed at 7F6E0F75000C
because, remember, the counter is stored at
7F6E0F750008.
$ proctal write --pid=12345 --type=integer --integer-bits=32 --address=7F6E0F750008 0
$ proctal write --pid=12345 --type=x86 --address=7F6E0F75000C \
'mov rax, 0x7F6E0F750008' \
'add DWORD PTR [rax], 1' \
\
'mov rdi, rbx' \
\
'mov rax, 0x55B4AA941048' \
'jmp rax'
With the code written to the memory block, you can now redirect execution to it.
$ proctal write --pid=12345 --type=x86 --address=55B4AA740676 \
'mov rdi, 0x7F6E0F75000C' \
'jmp rdi'
Now you can check how many health points you would have lost by reading the 32-bit integer at address 7F6E0F750008.
$ proctal read --pid=12345 --type=integer --integer-bits=32 --address=7F6E0F750008
548
When overwriting code, you need to make sure that the code is
not being referenced by other instructions, such as jump
instructions, otherwise you risk having the program jump to the
middle of the bytecode of a single instruction which will
result in the CPU misinterpreting every instruction it then
come across.
You also need to be careful with what you leave in the
registers and in memory. If you were to increase the stack
space and then forget to decrease before returning control back
to the program, you risk having the program read the wrong
return address from memory that could make the program jump to
some random block that could potentially have instructions that
hopefully lead to a crash instead of corrupting data.
Scripting
Being able to create mods out of text command sounds very
exciting but it's be very time consuming to have to type all
those commands every time you want to activate a mod. And
unless you take notes you will eventually forget the thought
process that you went through when those commands commands were
written.
The command line interface of Proctal can be scripted very
easily. Here is a bash script that does everything that was
described in the previous section. When it finishes it prints
the address of the health point counter that you can then read
from.
#!/bin/bash
pid="12345"
# Look for the address where we want to redirect program execution to our code.
# We're only expecting a single address but in case more show up, use the
# first one.
inject_address="$(proctal pattern --pid="$pid" '
48 89 DF
83 E8 01
89 05 ?? ?? ?? ??
8B 35 ?? ?? ?? ??' | head -1)"
# Allocate new block with enough space to store our code.
new_block_address="$(proctal allocate --pid="$pid" --read --write --execute 1000)"
# The return address. Calculated from the injection point.
return_address="$(printf "%X" $(("0x$inject_address" + 12)))"
# The address for the health point counter. It's located at the first address
# of the new block.
counter_address="$new_block_address"
# The address where the code is located. It's located after the counter.
code_address="$(printf "%X" $(("0x$counter_address" + 4)))"
# Initialize the counter to 0.
proctal write --pid="$pid" --type=integer --integer-bits=32 --address="$counter_address" 0
# Write out the code. Notice that it's separated in 3 sections.
# The first section increments the counter.
# The second section contains the code that was overwritten.
# The third section returns control back to the program.
proctal write --pid="$pid" --type=x86 --address="$code_address" \
"mov rax, 0x$counter_address" \
'add DWORD PTR [rax], 1' \
\
'mov rdi, rbx' \
\
"mov rax, 0x$return_address" \
'jmp rax'
# Redirect execution to our code.
proctal write --pid="$pid" --type=x86 --address="$inject_address" \
"mov rax, 0x$code_address" \
'jmp rax'
echo "$counter_address"
Shell scripts come with a lot of advantages.
They do not require manual intervention. As you can see, it's
possible to store the address of the new block in a variable
and use it in the code. It even makes the code more readable.
Being able to write comments lets you document what the
commands are doing right on the spot. Good comments can help
you make changes quicker in the future.