[GameHacking] Cheat Engine Basics - Tutorials, Tips, and Tricks

jamarirjamarir
33 min read

Just another Memory Scan / IEEE754 / Pointer Map_Scan / Code Cave / Structure Cheat Table Framework Code Differentiation Write-up (and rebirth from childhood).

Some far-from-exhaustive Game Hacking resources:

Introduction

Installing a Windows VM

When dealing with hacking in general, it’s best practice to separate your personal and hacking machines. In particular, a crashing game might freeze a computer’s display. Therefore, we’ll first need to install a Windows Virtual Machine. VirtualBox, VMWare, Hyper-V, among many other solutions, can be used. I’ll be using VirtualBox and a Windows 11 Pro OS.

You may check here to see how to install a Windows VM in Virtual Box. Debloat tools could be used, such as Windows10Debloater, or Win11Debloat, to save Windows from eating the VM’s CPU and RAM.

CE ? What’z th4’ ?

Cheat Engine (or CE) is a program that can attach to a process in order to scan and patch its memory. It is mostly used to modify games (e.g. getting infinite money, increase the player’s stats, etc.). This tool is flagged by Windows Defender, so we could disable Defender permanently (shown here, or here) to use CE.

When opened, we’re presented 4 main panes:

  • Search Results: Contains the process’s addresses, with their respective first, previous and current values.

  • Search Options: Allows to filter the addresses we look for. Indeed, games are tremendously complex, and filtering the addresses is paramount to find our desired value. For example, if we know our in-game’s health value is 100, then we could filter the addresses with value 100, displayed in the Search Results pane.

  • Memory View: Gives the disassembled code (i.e. the opcodes, or assembly instructions) of a specific address we may patch. For example, if an instruction decreases our health by one (e.g. using the dec instruction), we might patch that behaviour with a NOP instruction to preserve our health.

  • Address List (aka. Cheat Table): Contains a list of addresses we kept aside / selected for further inspection or modification.

Let’s make these sections clearer through the CE’s tutorial.

CE's Tutorial

Many solutions might be found online, like this Cheat Engine Youtube walkthrough, this Cheat Engine page, or this forum topic. I’ll try to make my own detailed walkthrough below.

Step 1: Process Attachment

The tutorial can be launched from the Help’s tab:

Once launched, we may attach CE to the tutorial’s process using the top-left magnifying glass icon:

Now we can start solving the tutorial's steps using CE.

As a side note, CE shows a hint on how to perform each step upon exit. For instance, closing the tutorial on step 1 shows the following popup:

On the other hand, skipping a step gets ourselves discarded :(

Step 2: Exact Value Scanning (PW=090453)

In step 2, we have an in-game health value of 100:

Our objective is to update that number to 1000.

Finding a 4 Byte Health Address

The first step is to look where that value is stored in memory using the Search Options filters. We’re given 2 hints:

  • It’s preferable not to use an 8 Bytes value type.

  • The value is stored as an integer.

Knowing the exact value is 100, we might put that in the value field and do a First Scan:

51 values were found in my case. In order to know which address actually represents our health, we can narrow down our results changing our health in the game, and do a next scan. So we click the Hit me button, and Next Scan our new health:

Without a doubt, this address contains our in-game health’s value. We may confirm this by clicking the Hit me button multiple times, and check this value updates accordingly.

Finally, we double-click that entry to add this address to the address list, and update its value to 1000. To freeze that cheat (i.e. update the value every frame), we can check the Active box:

Our health is now 1000. Indeed, clicking the Hit me button again shows that our health is decreased from 1000:

Even if our health is graphically shown as 997, its in-memory value is in fact 1000, as the Active box is checked. We may click Hit me multiple times to realize that our health still decreases from 1000:

All Scan & Value Types

In this particular case, we knew the exact value and type of our health. However, we won’t be that lucky with other games. Thus, we would have to resort to broader type filters:

Assuming we didn’t know our health value and type, a broader, but costly, solution would have been to select the Scan Type Unknown initial value, and the Value Type All. Then, use Increased value, Decreased value, Changed value or Unchanged value, for example, between each scan:

Finding an 8 Byte Health Address ?

As we saw above, multiple value types are available. Here, we can’t find our health using the Float, Double, String, or Array of byte types, because our health is an integer (bytes), which is stored differently in memory. Therefore, we’re limited to Byte value types.

However, the tutorial states:

The 8-byte may perhaps works if the bytes after the address are 0, but I wouldn't take the bet.

Indeed, we can’t find our health’s address with an 8 Bytes search:

To understand why, let’s find out our health value again (with a 4 Byte search), and access its memory region:

As we can see, our health is stored in the reddened 4 bytes region in memory, where the current in-game health is 89 (i.e. 0×59 in hexadecimal):

As a side note, in this view, the LSB (Least Significant Bit) is on the left, while the MSB (Most Significant Bit) is on the right. Therefore, casting these 4 bytes to integer is done via the following calculation: 0×59*16^0 + 0×0*16^1 +0×0*16^2 +0×0*16^3 = 89*1). We’ll dig a little deeper afterwards about this memory endianness representation.

Note that this memory view could also be opened clicking the Memory View button (obviously).

Therefore, looking for an 8 Bytes health won’t be possible here. Indeed, that would mean searching in the following red region:

However, the last 4 Bytes aren’t zeros, but C0 04 00 00. So we may have looked for an 8 Byte health only if that region was, if we were lucky enough, 59 00 00 00 00 00 00 00.

Also, even if this isn't an absolute truth, developers tend to store integers in C/C++ using the int type, whose size is 4 Bytes. Therefore, looking for a 4 Bytes value type is generally a good guess.

On top of that, our maximum in-game health is 100. Therefore, we might have assumed that the developer stored this data in a single byte. Indeed, one byte ranges from 0 to 255, which is enough to store 100:

But if the health was to be stored as a long long for example, then looking for an 8 Bytes value would have been okay.

Wanna exit step 2 ?

Step 3: Unknown initial value (PW=419482)

This step is the same as step 2. We’re asked to find our health’s memory address and change its value to 5000. The only hint is that the value is between 0 and 500.

Because we’re starting a new scan, we mustn’t forget to click on New Scan to reset our previous step 2’s search filters.

Therefore, we’ll search our health by, firstly, looking for any value between 0 and 500:

Because we only know that our health is located between 0 and 500, the number of addresses found is enormous (450 684 in my case). We may play with other scan types to narrow down the addresses. For example, we could:

  • Perform any action that doesn’t change the health, and scan for Unchanged value. Such action could be: waiting some seconds, scrolling the tutorial’s text, pressing random keyboard keys, moving the tutorial’s window…. Obviously, we won’t click on the Hit me button.

  • Press Hit me and scan for Decreased value if we don’t know by how much our value decreased. Here, however, we may do a more specific search, choosing the Decreased by … scan, as the amount of health lost is shown for about one second.

Repeating these actions gives our health’s address:

Finally, we can set that value to 5000 to continue the tutorial. This might be done, for example, right-clicking the entry and changing its value:

As opposed to step 2 (where the Active button was checked), such patch isn’t persistent. Here, the health's value is set to 5000 once only (and not every frame).

Wanna exit step 3 ?

Step 4: Floating points (PW=890124)

Finding health and ammo

This time, we’re asked to set our health and ammo to 5000 or above. The only change is that these are stored as a float and a double respectively:

Once we find these values’ addresses:

Simply change them to 5000 or above:

Wanna exit step 4 ?

IEEE754 - Floats & Doubles in memory

This section isn’t necessary. You may skip that IEEE754 discussion if you wish.

The health and ammo values are respectively stored in-memory as follows:

Now, why a float (resp. double) of value 100.0 (resp. 100.0) is stored in memory as 00 00 C8 42 (resp. 00 00 00 00 00 00 59 40) ? The way floats are stored in memory, also called the floating point representation, is defined by the IEEE754 norm. Many resources online illustrate this floating point representation, such as this wiki page, or this forum answer. Let’s try to translate the float and double hexadecimals into real numbers using this ARM’s developer documentation and this Microsoft’s documentation.

When writing down a binary on a paper, the conventional notation is to put the LSB on the right, and the MSB on the left. Also, in a little-endian system, the lowest memory slot has the LSB, while the highest one has the MSB. Such system is prominent worldwide, as this is the one used by x86 architectures (such as Intel and AMD).

Said differently, in a little-endian system (which we’re on), the lowest memory is on the right. Therefore, the above float and double are respectively conventionally written down as:

  • 42 c8 00 00.

  • 40 59 00 00 00 00 00 00.

Definition

Floats and doubles are made up of 3 parts in memory: a sign bit, an exponent and a mantissa (also known as a significand, or a fractional part). For floats (resp. doubles), these respective parts’ sizes in bytes are 1 + 8 + 23 (resp. 1 + 11 + 52).

Mathematically speaking, floats and doubles are defined as \(x=2^e(1+m)\), where:

  • The sign of \(x\) is \(+\) if the sign bit is 0, \(-\) otherwise.

  • \(m \in [0, 1[\).

  • \(e \in \{-2^{p-1}+1; 2^{p-1}\}\), \(p\) being the number of exponent’s bits. Therefore:

    • In a 32 bit context, \(p=8\) bits and \(e \in \{-127;128\}\).

    • In a 64 bit context, \(p=11\) bits and \(e \in \{-1023; 1024\}\).

In the scope of the current article, we won’t tackle edge cases, such as \(\infty\), or \(NaN\).

When converted into binary, our health and ammo divide into the following sign, exponent, and mantissa parts:

  • Float (32 bit): health is 42 c8 00 00, i.e.

    0 10000101 10010000000000000000000

  • Double (64 bit): ammo is 40 59 00 00 00 00 00 00, i.e.

    0 10000000101 1001000000000000000000000000000000000000000000000000

Sign bit

Both sign bits are set to 0, meaning our health and ammo values are positive (fortunately).

Exponent

Regarding the exponent, we must first notice that in the mathematical expression above, \(e\) can be negative. But in the computer’s binary representation, the exponent is a binary, thus always positive. So how can we represent a negative exponent ? The trick is to keep in mind that the offset between the real exponent (annotated \(e_{real}\), negative or positive), and the machine exponent (annotated \(e_{machine}\), always positive) is \(2^{p-1}-1\).

As an example, let’s consider a float’s exponent \(\left( p=8 \right)\). We know \(e \in \{-127;128\}\), and its binary representation is, instead, shifted into \(\{0;255\}\). Then, for floats, the offset between the real and machine exponents would be 127. For doubles, it would have been 1023.

Then, we get the real exponent using the formula \(e_{real} = e_{machine} - \text{offset}\):

  • The health’s machine exponent is 133 (10000101), giving the real exponent \(133-127=6\).

  • The ammo’s machine exponent is 1029 (10000000101), giving the real exponent \(1029-1023=6\).

Mantissa

Finally, we consider the mantissa. First, we must note that there’s an understood binary of 1 on the left of every mantissa. Therefore, this implicit digit is not stored in memory.

Notice I’ve written \(x=2^e(1+m)\), and not \(x=2^em\), because the 1 on the far left is implicit.

Then, we must take the mantissa’s binary representation, add an implicit 1. on the left, and finally \(e_{real}\) shift” the dot. If the real exponent is positive, then we shift the . to the right \(e_{real}\) times, and to the left otherwise. Thus:

  • Our health’s exponent is +6. So we shift the . to the right 6 times, giving: 1100100.00000000000000000, i.e. 100.0 health.

  • Our ammo’s exponent is +6. So we shift the . to the right 6 times, giving: 1100100.0000000000000000000000000000000000000000000000, i.e. 100.0 ammo.

As we can see, the way integers (bytes) and floats are stored in memory is completely different. Storing 5 as an integer is as easy as 101, while storing 5.0 as a float requires a sign bit, an exponent and a mantissa. That’s basically why scanning for a float, when looking for an integer (and vice-versa) gives no result in Cheat Engine.

In other words, searching with the right value type is paramount. Again, in the case we don’t know the value type, we may anyway resort to the global, but costly, All value type.

Step 5: Code finder (PW=888899)

In this step, we may change a value clicking a Change value button:

The objective is to make that button to do nothing. To get that button’s logic in memory, we must find what is writing to our value’s address:

This attaches a debugger to our process. That way, when we change this value in-game, we see which instructions alter our value. Once we know these instructions, we may stop the debugger:

The instruction (stored at address 0x00426E02) altering our health is mov [eax], edx, meaning: “Write what’s in the edx register into the memory slot referenced by the eax pointer”. To take a C/C++ analogy, this intruction is like doing *eax = edx, where edx is a data (value), and eax a pointer (address). Our health is stored in eax (i.e. 0x017D58E0), and the health to be written is edx (i.e. 0×86=134).

Without entering into to much details, registers are named differently according to their sizes. For instance, the first register, aka. the Accumator, can be one of the followings depending on the memory slot’s size: rax (8 bytes), eax (4 bytes), ax (2 bytes), or ah/al (1 byte).

Also, GPRs (General Purpose Registers, namely EAX,EBX,ECX,EDX,ESI,EDI,ESP,EBP) can store either a data (e.g. a health’s value), or an address (i.e. a pointer). Therefore:

  • mov eax, ebx writes what’s in ebx into eax. There’s no square bracket ([, ]), so both registers contain either a data, or an address. They are the same “type”.

  • mov [eax], ebx writes the data in ebx into the memory slot referenced by the eax pointer. The C/C++ analogy would be *eax = ebx.

  • mov eax, [ebx] writes the memory slot referenced by the ebx pointer into eax. The C/C++ analogy would be eax = *ebx.

  • Affectations from a value referenced by a pointer to another value referenced by a pointer (also known as a mov from memory to memory) is not supported. Then, mov [eax], [ebx] is impossible.

You might refer to this Intel x86 Assembly Language cheatsheet, this Game Hacking Academy’s Assembly Fundamentals, or this University of Virginia Computer Science course for more details.

In mov [eax], edx, edx contains the value to write, and eax is a pointer to our value. If we wan’t the button to do nothing, we could update that instruction into a NOP instruction (No OPeration, with opcode 0×90):

Semantically speaking, 0x8910 and 0×90 are opcodes, while mov [eax], edx and nop are their respective mnemonic (human-readable) instruction. Understanding how to translate a mnemonic instruction to its opcode is not necessary in the context of the current article.

Wanna exit step 5 ?

Step 6: Pointers: (PW=098712)

In this step 6, we are given 2 buttons: Change value and Change pointer.

The objective is to find which pointer references our value, in order to change the pointer’s referenced value to 5000 or more. Dealing with pointers can be a bit tricky at first, so let’s introduce it slowly.

A pointer is an object (often graphically represented as an arrow) that references / points to a slot / location in memory. What’s important to note is that the location which the pointer references corresponds to its own value in memory. Also, the pointer is itself stored in a specific address in the memory. In other words, when dealing with pointers:

  • There are 2 memory slots: the pointer itself and the value it references; and each memory slot is stored at a specific address.

  • The content of the pointer’s slot is the address to be referenced.

  • The content of the memory being referenced can be anything (a value, another pointer, etc.).

Let’s make this clearer through the tutorial. The pointer to our value is disclosed by the instruction writing our value:

The instruction mov [edx], eax writes what’s in eax into the slot referenced / pointed by the edx pointer. edx being the pointer to our value, we know that edx=01824B80 implies our value is stored at 0x01824B80. Graphically speaking, this would look like:

Now, the question is: where is EDX itself stored (0x????????) ? Indeed, in order to add a pointer in the address list, we need the address of where the pointer is stored; not its destination's address.

Therefore, because the tutorial’s process is using a 32 bit architecture:

Each memory slot is 4 bytes (32 bits), so an address is stored as 4 bytes. Therefore, to find the register’s address, we must scan its 4 Bytes value (i.e. 0×01824B80), with Hex checked:

The Address field we found is greened out, meaning it’s a static address (aka. a base address). Such static address aren’t randomly generated; rather, they are process-relative, i.e. located at a static offset from the program’s location (the offset being +0x2566B0):

Finding a static address that persists across restarts is also called “Defeating DMA (Dynamic Memory Allocation)”. However, not every static address is persistent accross restarts. But that's out of the scope of the current article.

Then, we can add this address to our address list:

We’ve let the offset to 0 here, as there was no offset within the square brackets: [edx].

If, for example, the instruction was mov [edx + esi*2 + 10], eax, with edx being our pointer, and esi=2, then we would have added two offsets: 4 and 10.

Notice how our pointer is indeed pointing to the in-game value, 664. Once this pointer is added, we may confirm its referenced value updates accordingly when changing the value:

Then, we can change the pointer’s referenced value to 5000 and check the Active box to freeze it. Even if we click the Change pointer button (which changes the destination address, i.e. 0×01824B80), our patch would stay persistent, as our pointer’s location is static, and dynamic towards our in-game value.

Wanna exit step 6 ?

Step 7: Code Injection: (PW=013370)

This step involves injecting some code within the program’s logic. So far, the only instruction we’ve written is a single NOP to prevent our health from being decreased. The advantage of code injection is to inject more than just a NOP, but a whole NASM script. This allows us to refine more precisely our cheats.

Again, the game gives us 100 health, and clicking Hit me decreases it by 1:

The objective is to patch the Hit me button to increase our health by 2 instead. First, let’s find the instruction decreasing our health:

That instruction is sub dword ptr [ebx+000004A4],01. The pointer referencing our health is ebx+000004A4; therefore our health is stored at ebx+000004A4:

dword ptr is a directive allowing to target a specific memory region of size DWORD (4 bytes). As this University of Virginia course states:

Therefore, dword ptr [ebx+000004A4] is just a way of retrieving the DWORD value at ebx+000004A4, where our health is indeed stored as 4 Bytes (DWORD) in memory. If we wanted a single byte instead, we would have used byte ptr [ebx+000004A4].

To patch sub dword ptr [ebx+000004A4],01 with an NASM script, we click Show disassembler and navigate into Tools > Auto assemble > Template > Code Injection:

Note that we could also have directly jumped from the instruction to the Auto assemble window pressing Ctrl+Alt+A:

Every instruction of this NASM script is detailed in the Cheat Engine wiki, as well as the Auto Assembler’s basics.

This script allocates a certain amount in memory via alloc(newmem,2048) we have RWX (Read, Write, eXecute) access on. This is where we may put our cheatcode.

Then, three labels are defined. Labels allow to jump to certain parts of the script without having to know the address. Basically, labels are human-readable strings that translate into addresses at runtime.

The starting point of our script is the original instruction’s address, i.e. "Tutorial-i386.exe"+27B38 (equal to 0x00427B38). Thus, the first instruction executed by our script here is jmp newmem on l.16 (i.e. go to label newmem).

In the Game Hacking scene:

  • A detour is a jump to our arbitrary code (l.16). (N.B. : The detour’s nop 2 instruction (l.17) allows to keep the same size as the overwritten instruction’s size, whose size was 7: 83 AB A4040000 01. That’s out of the scope of the current article, but this means the jmp newmem's size is 5, because 5+2=7.)

  • A code cave is an unused memory section (newmem section), allocated to us in which we can put our arbitrary code. As this Game Hacking Academy page states: “The name comes from the fact that we are creating a hidden “cave” of instructions. Most games will have large sections of unused memory between functions or at the end of the executable. These locations are perfect for creating a code cave in.”

  • A trampoline (aka. a gate) executes a copy of the original instruction (originalcode section), and jumps back to its end address (l.13) to resume the original logic’s flow (returnhere section). The following image illustrates this concept (image is taken from this Jurriaan Bremer’s blog):

Ultimately, these steps allow us to hook the original program’s instruction. More information could be found in the Microsoft Detours’s wiki.

For example, in the above image, 0×401000 is the detour, function_B is the code cave, and function_A_gate is the trampoline / gate (restoring the initial function_A’s stack (i.e. ebp, esp here), and jumping to the original’s instruction 0×402006). This illustrates how function_B hooked function_A.

In order to increase our health by 2 when Hit me is pressed, we could use any of the following scripts:

  • Starting at line 16: jump to newmem (detour) to increase our health by 3, then decrease it by 1, and finally jump to returnhere (trampoline) to exit the code cave:

  • Starting at line 16: jump to newmem (detour) to increase our health by 2, then jump to exit, and finally jump to returnhere (trampoline) to exit the code cave:

Considering the first patch, we click Execute to inject the code in the process. This overwrites our original instruction at "Tutorial-i386.exe"+27B38 by a jump to our code cave (jmp 01750000):

Clicking Hit me increases our health by 2.

Numerous Auto assemble templates exist. Understanding each template is out-of-scope of the current article, but they’re all well documentated in the Cheat Engine’s wiki:

As the HackTheBox Academy Game Hacking Fundamentals Module states: “All of these templates implement what is known as a trampoline“. In other words, all of these templates allows us to inject a code cave in the memory to alter the game’s logic.

Wanna exit step 7 ?

Step 8: Multilevel pointers: (PW=525927)

Step 8 is similar to step 6: we must find the root (static) pointer that updates our in-game value:

But there’s a little difference though: the tutorial informs us that we’re looking for a level-4 pointer (and not a level-1 pointer, as in step 6). In other words, we’re looking for a pointer (1) to a pointer (2) to a pointer (3) to a pointer (4) to a value:

First, let’s find our in-game value’s address, as usual:

Manual method

Then, we find the pointer accessing our address, being esi+18 here:

We must take note of each offset (e.g. +18 here), as we’ll need them at the end. So far, what we know is that esi+18 points to our in-game value, i.e.:

Technically speaking, each offset is an implicit hexadecimal value. Thus, +18 refers to +0×18.

Next, we repeat searching for the pointers’ addresses (without mentionning the offsets, as they’ll be taken into account at the end), until we find the root static pointer. For instance, the next step to get the orange pointer is:

Ultimately, the root pointer to our in-game value is stored at "Tutorial-i386.exe"+2566E0:

Then, we may, as we did in step 6, add our static pointer, with the corresponding offsets:

Notice that each offset is taken into account here. Again, we solve step 8 by patching the pointer’s value to 5000 or above, changing the destination pointer (i.e. 0×0189E0E8+18), and see that the change is persistent when Active is checked.

Pointer Scan / Pointer Map method

A more easy method to get that root pointer is to use the Pointer Scan feature. Once we found our in-game value address, we right-click that entry in the Address List and choose Pointer scan for this address:

Which opens the following window:

Understanding each option is not that important. We can simply understand the following options:

  • Scan for address: Allows to scan pointers referencing a given address in memory. In our case, we want all pointers referencing to our value’s address, i.e. 0x01A8E080.

  • Maximum offset value: Sets the maximum offset threashold between each pointer. In our case, the maximum offset was 0×0C=12. In general, the default 4095 should be fine, but game developers might “hide” a pointer putting at least one enormous offset (e.g. 99999). This prevents cheaters from finding the root pointer with this default 4095 threashold. Then, we might need to increase that option if we can’t find our root pointer.

  • Max level: Sets the maximum depth of our recursive pointer search. In our case, we are looking for a level-4 pointer, so we must set the level to 4 or above. If we were looking for a level-12 pointer, we would have needed to set that to 12 or above. Empirically, it sounds like 8 or 9 should be a good guess.

We could also narrow down the result by setting the last offset manually in the Pointers must end with specific offsets option, looking the associated opcode.

Once we click OK, the following window opens:

A big list of static addresses is given, that all share one point in common: they ultimately all reference a memory slot containing our in-game value: 2266. Most of these values are false-positives. Thus, we may filter them by:

  • Changing our in-game value, and perform another pointer scan:

  • Change our pointer’s address, and perform another pointer scan:

Ultimately, we find the same level-4 root pointer with the appropriate offsets:

Again, we add this static address in the Address List to update our in-game value persistently.

Broadly speaking, the steps to consistently find static pointers (persistent accross restarts) can be:

  1. Locate a constant in-game value accross restarts (e.g. player’s profile name) related to the value to patch (e.g. player’s gold, in the same Player class’s instance as the profile name).

  2. Generate a pointermap of our address and restart the game.

  3. Repeat the step 2 to narrow down the persistent pointers, until the step 4’s addresses is minimum.

  4. Restart the game, and pointer scan our address, comparatively with the previous pointermaps’ addresses. The pointer scan window can be let open to scan for the value as well.

If necessary, the pointer scan results can later be opened in Memory View > Tools > Pointer scan > File > Open.

Wanna exit step 8 ?

Step 9: Shared code: (PW=31337157)

This step is the final boss. Two teams are fighting: 2 Players vs. 2 Computers. The purpose is to patch the game so that the players’ healths remain intact when they’re attacked.

The issue, though, is that the Attack buttons are executing a shared function, inherited by both the player and computer object instances.

Guessed UML Class & Instances

To illustrates that point, a conceptual, and guessed, UML diagram of the game could be:

As we can see, 4 instances of the Actor class are created: actor1, actor2, actor3 and actor4. Every instance has its own attributes: name, health, team and type. For example, actor1.name="Dave", and actor4.name="KITT". Howbeit, they all share one identical method: attack(target). Therefore, if we patch attack() to do nothing as we did in step 5 (NOP) or step 7 (Code Injection), the computers in team 2 would be harmless. But unfortunately, so does our attacks as well.

The purpose is to patch the attack() method to do nothing, but only if the targeted actor is a player. In our conceptual UML diagram above, a valid pseudo-code patch would look like:

if targeted_instance is player:
    do nothing
else:
    attack

Another logically valid patch would look like:

if attacking_instance is computer:
    do nothing
else:
    attack

However, this pseudo-code wouldn’t be applicable here. Indeed, when we click the Attack button, we have no clue of who called that method. The only thing we know is the instance losing health, the instance being attacked. For example, if we click the first Attack button, we know that actor1 loses health, but we don’t know if it was actor3 or actor4 who attacked.

So we’ll consider the first pseudo-code above.

To differentiate players from computers in our guessed UML diagram, we could say that, for example:

  • The upper-case name differentiates players and computers.

  • The initial health differentiates players and computers.

  • The team number differentiates players and computers.

  • The type attribute differentiates players and computers.

So we can have multiple answers to achieve the same goal: identify players and make the attacks towards them harmless.

Finding a differentiating attribute between structures

The challenge is mainly to find at least one differentiating data in memory between the players and the computers objects.

Getting the opcode of each structure

The first step is to find the opcode decreasing a health. Afterwards, we may find the other actors’ healths’ addresses by finding which addresses that opcode accesses:

The first two addresses are the players’ ones, while the other two are the computers’ ones. Now, we group each pair in group 1 and group 2:

Getting a differentiating attribute (using the Structures’ Commonalities Scanner)

Now that we grouped the structures to differentiate, we may scan for commonalities with a structure comparison:

The instruction decreasing the health is mov [ebx+04], eax. Therefore, we launch the EBX’s structure comparison.

This opens a Stucture Compare window:

We check Only find matching groups before scanning for commonalities to get a list of matching (resp. dismatching) attributes between the objects of the same (resp. different) group:

For example, we see that the first pointer references a value of 1 in the first group, while referencing a value of 2 in the second group. Assuming this value is the team number of the actor, graphically speaking, it would look like the following for actor1:

In other words, we know 1 is stored at address 0×06FA2758+10 = 0×06FA2768 in the actor1’s structure.

Getting a differentiating attribute (using the Structure Dissect)

We could also have compared the stuctures of these 4 objects using the dissect data feature:

Many lines can differentiate each groups. In the above image though, the only line differentiating the first two structures against the last two is the line framed in blue, at offset 0×10.

Conditional Code Cave (Cheat Table Framework Code Template)

Back to our opcode in the memory viewer, we may patch the mov [ebx+04], eax instruction as in step 7 (Code Injection). However, we’ll first use a Cheat Table Framework Code template before Code Injection:

The Cheat Table Framework Code template defines 2 sections in the NASM code: [ENABLE] and [DISABLE]:

Once the Code Injection template is inserted, the generated script becomes:

The [DISABLE] section deallocates our code cave’s memory (l.25). Then, looking back at the original game’s opcodes:

They are hardcoded back into 0×89 43 04 D9 EE (l.27), using the db (Define Byte) instruction. The destination where the bytes are put by db is directly where db is executed, i.e. into "Tutorial-i386.exe"+28E89. To put it concisely, the [DISABLE] section restores the original memory and opcodes, thus disabling our cheat.

We may use the [ENABLE] and [DISABLE] sections to execute a conditional cheat. Here, if the attacked object is a player, we won't execute the original code, otherwise we will.

In our code cave (i.e. in newmem), we need to refer back to our differentiating attribute. We know a differentiating attribute for actor1 is at address 0×06FA2758+10, i.e. ebx+10 when attack() is called:

So, in our code cave, [ebx+10] will be a differentiating attribute. As a reminder, our pseudo-code injection was:

if targeted_instance is player:
    do nothing
else:
    attack

Therefore, we could say:

if targeted_instance.team is 1:
    do nothing
else:
    attack

Where targeted_instance.team is [ebx+10]. In assembly, we can use the cmp and je instructions to patch the game’s logic:

Notice that the fldz instruction is moved from l.15 to l.17. Letting it at l.15 would crash the tutorial (because the FPU stack, aka. the x87 stack, would be corrupted; but that’s out of the scope of this article).

In fact, the only code we wanna patch is mov [ebx+04], eax alone. Everything else should be kept executed to avoid unexpected behaviors !

As a result:

  • If the team number is 1, then je exit is executed, skipping the original code (l.14). The player isn’t hurt.

  • If the team number is not 1, the original code is executed (l.14). The computer is hurt.

We may save our script in the Address List, clicking File > Assign to current cheat table:

That way, we may edit our code cave double-clicking <script> later on.

Finally, we enable our script checking the Active box, making attacks against players harmless, while harmful against computers ! 😎

Wanna exit step 9 ?

CE Tips & Tricks

Here are some handful, while non-exhaustive, interesting Cheat Engine features.

Pause Hotkey

Once attached to a game’s process, we may pause the process pressing a Hotkey we defined in Edit > Settings > Hotkeys:

This might be useful when patching a real-time game, where we want to pause it and take our time to inspect its memory.

Memory View

Nearby Memory Analysis & Data Types

Once we found an interesting value in memory (e.g. a step 9’s health), we may browse its memory region to reverse-engineer / guess in-game structures. For instance, we see that next to our health’s value, the player’s name Dave is stored:

This alludes to the fact that the game developers are likely to be using a structure to store a player’s instance in memory. On top of that, we can see the dynamically changing nearby regions (reddened bytes) while playing.

Also, the memory’s display type could be changed to anything we want, e.g. float:

Guessing Memory Structures regions …

As we saw in step 9, we may dissect a specific section in memory to look for a potential in-game structure. Another way to perform the same structure analysis would have been to guess where a structure is stored in memory, based on an attribute we found (e.g. a player’s health):

For instance, here, we see bunch of NULL bytes (0×00) before our health, which are likely to be unused. Then, a potential player structure might start at address 0×02DB25E4:

Finally, we might let Cheat Engine guess that potential structure’s attributes’ types with the Tools > Dissect data/structures feature:

… And Correcting Structure Datatype Discrepencies

Here, let’s assume that our health’s type was incorrectly guessed by CE as a pointer (instead of a float), at offset 0×10:

Then, we could right-click that entry, and check the different types’ values:

Here, float is definitely the type we want. That way, we corrected any CE-type-guessed discrepency.

Patching instructions on-the-fly …

In step 5, we found that the instruction changing our in-game value was:

In the memory view, or disassembler view, we can see that this instruction is actually stored as 2 bytes: 0x8910

When we replace this instruction with NOP, a new line is added in the memory view:

Here, we haven’t actually “added” a new instruction byte in the memory. This is just CE showing us 2 opcodes, 0×90 and 0×90, which replaced 0×89 and 0×10. CE just padded our patch with one NOP instruction to fill the original instructions’ length.

… And opcode size discrepencies

Also, notice that because NOP is a 1 byte instruction, it can replace any instruction, as its size is always smaller or equal to the instruction to patch (“it always fits”). On the contrary, if the original instruction was NOP (1 byte), and we wanted to update it to mov [eax],edx (2 bytes), it would most likely crash the game, as our new instruction is larger (“it wouldn’t fit”).

For example, let’s change edx in the instruction by 1a (a 4 bytes integer):

When such instruction-size discrepency occurs, Cheat Engine proposes to fill out the instructions with NOP instructions:

Selecting No produces the following instructions:

While selecting Yes produces the followings:

It should be noted that in both cases, looking at the Bytes column, the original instructions have been overwritten. Sometimes, these changes are harmless, and the game can resume normally. But most of the time, these unwanted opcodes shifts will crash the game. For example, the original instruction cmp eax, [ebp-0C] (whose opcode is 3B 45 F4) generated a hlt instruction (with opcode F4, corresponding the last byte of 3B 45 F4). This new instruction will simply close the program.

In other words, injecting instructions larger than the size of the original instructions completely disrupts the program and generates unexpected behavior, often leading to crashes.

Therefore, we must make sure our patches are smaller than the original instructions we wanna overwrite. If we want to inject larger patches, we must refer back to a code cave, using the Code Injection template for example.

Restoring original instructions

The original instructions we’ve overwritten are backed up, and can be retrieved clicking the Advanced Options button:

This allows us to restore any original instruction we want:

Setting a breakpoint

We may manually set a breakpoint on any instruction, or a byte in memory (e.g. on a value we found):

When the breakpoint is triggered, a debugging window pops up, stopping the execution, with a lot of information:

In particular:

  • In the top-right, we may see the registers’ and flags’ respective content. For instance, edx contains 0×3A5; therefore, [eax] will be changed to 933. Also, any register could be updated on-the-fly clicking it, while debugging.

  • In the bottom-right, we may see the stack trace.

  • In the top-left, we may use any debugging feature, namely:

    • Run to resume the execution.

    • Step Into to execute one instruction, while entering function calls.

    • Step Over to execute one instruction, without entering function calls.

    • Step Out to execute all the function’s instructions, and exit it. This is especially useful if we want debug and patch a higher function, also known as bubbling up (e.g. used for wallhacks).

    • Run till… to resume the execution flow until we reach a selected instruction.

Getting the program’s base address

We may get the base address of the loaded program in memory by using the Goto address feature, and enter the program’s EXE name:

Here, the base address is 0×00400000.

0
Subscribe to my newsletter

Read articles from jamarir directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

jamarir
jamarir

Jamaledine AMARIR. Pentester, CTF Player, Game Modding enthusiast | CRTO