I started modding games when I was around 14 years old. At that time, I mostly asked “How do I mod this?” Over time, that question slowly changed into “How does this program think?” I cannot say this method will work for everyone, but it changed the way I learn reverse engineering. I want to share the approach that worked for me.
When you begin asking that, the target changes. You stop seeing only:
The moment my progress changed was when I stopped collecting answers and started collecting questions. So, when you face something difficult, try asking “If an expert encountered this target, what would they examine first?” Then inspect the evidence:
It’s okay if your question is not clear today. Sometimes, looking at the problem from another perspective is enough to reveal the next step. I would like to share one of my classic approaches for dealing with problems in Android reverse engineering. Instead of asking only “How can I hook or patch this function?” Try asking “Why does this function exist, how is it used, and what assumption does the program make about its result?” Let’s begin with a simple example function hooking.
Frida Custom Script Example:
Hooking does not always mean running code before the original function. Depending on the technique, a hook can:
In simple terms, hooking gives our code control before, after, or instead of the original function.
Example variables inside
Why Is Padding Added?
The compiler may insert unused bytes between fields so that each value begins at an address suitable for its alignment requirements.
For example, a 4-byte
A pointer in a 64-bit process is commonly 8 bytes and is usually aligned to an 8-byte boundary:
In this example,
However, the exact layout can depend on the compiler, platform, ABI, packing settings, inheritance, and runtime. In reverse engineering, we should confirm the real offsets from the binary or runtime instead of assuming them.
Do not assume the layout. Verify how the program actually stores it. These offsets are a simplified example. The real layout may differ depending on the compiler, architecture, ABI, packing, inheritance, and runtime. In reverse engineering, always verify the actual offsets.
They can change from:
Normalize:
If packed:
Maybe they gonna change to:
These are the game's original functions running every frame. When we want to modify behaviour, we have two options:
We want the player speed to always be
Why does the hook have
Case 2: Enable/ Disable Toggle
We want the user to be able to turn the speed hack on or off:
The
Case 3: Speed Multiplier
Instead of a fixed value, make all movement 3x faster while preserving the walking/running/crouching difference:
Now walking, running, and crouching speeds are all increased by 3 times. Each speed value keeps its original behavior and is multiplied, instead of replacing everything with one fixed speed value.
Problem: The
To fix it, you'd also need to change stamina:
And for a toggle, values written to memory remain even after you disable the hack, until the game overwrites them naturally. The hack cannot be cleanly disabled unless the original values are restored.
Field editing is also useful for:
A hook, an offset, or a patch is only one answer to one problem. What matters more is understanding why it works.
If field editing does not give the result you expected, follow the logic.
If a hook behaves differently than expected, look at the caller, the arguments, the object, and what happens afterward.
You do not need to understand everything in one day. Just understand one more decision than you understood yesterday.
This is simply the approach that worked for me, and I hope it gives someone here another way to look at the problem.
Don’t just collect offsets. Collect questions.
See you around.
When you begin asking that, the target changes. You stop seeing only:
- a license check
- an integrity check
- an anti-debug function
- a branch that needs patching
- trust boundaries
- state transitions
- design decisions
- failure conditions
- the relationship between input, logic, and result
The moment my progress changed was when I stopped collecting answers and started collecting questions. So, when you face something difficult, try asking “If an expert encountered this target, what would they examine first?” Then inspect the evidence:
- inputs
- comparisons
- branches
- return values
- callers and callees
- what happens before and after the check
It’s okay if your question is not clear today. Sometimes, looking at the problem from another perspective is enough to reveal the next step. I would like to share one of my classic approaches for dealing with problems in Android reverse engineering. Instead of asking only “How can I hook or patch this function?” Try asking “Why does this function exist, how is it used, and what assumption does the program make about its result?” Let’s begin with a simple example function hooking.
How Hooking Works
Hooking is a method where you intercept a function call and change what happens. Your code gets control, so you can modify the arguments, replace the result, call the original function, or block it entirely. And Hooking intercepts a function call and gives our code control before, after, or instead of the original function.Frida Custom Script Example:
JavaScript:
Please,
Log in
to view codes content!
Hooking does not always mean running code before the original function. Depending on the technique, a hook can:
- inspect or modify arguments before the function runs,
- inspect or modify the return value after it finishes,
- call the original function with additional logic,
- or replace the original function entirely.
In simple terms, hooking gives our code control before, after, or instead of the original function.
Game Class Structure
Suppose there is an open-world game with players, enemies, vehicles, guns, and bullets. The developer starts by creating aPlayerController class with variables and functions.Example variables inside
PlayerController:
C++:
Please,
Log in
to view codes content!
Memory Layout & Padding
In memory, class fields are usually stored in order, with each field located at an offset from the beginning of the object.
C++:
Please,
Log in
to view codes content!
Why Is Padding Added?
The compiler may insert unused bytes between fields so that each value begins at an address suitable for its alignment requirements.
For example, a 4-byte
float is commonly aligned to a 4-byte boundary:
C++:
Please,
Log in
to view codes content!
C++:
Please,
Log in
to view codes content!
isAlive and id use offsets 0x4 and 0x5. The compiler adds two padding bytes at 0x6–0x7, allowing health to begin at the next 4-byte boundary, 0x8.However, the exact layout can depend on the compiler, platform, ABI, packing settings, inheritance, and runtime. In reverse engineering, we should confirm the real offsets from the binary or runtime instead of assuming them.
Do not assume the layout. Verify how the program actually stores it. These offsets are a simplified example. The real layout may differ depending on the compiler, architecture, ABI, packing, inheritance, and runtime. In reverse engineering, always verify the actual offsets.
They can change from:
- compiler and ABI
- 32-bit vs 64-bit
- #pragma pack or packed struct
- inheritance / virtual table
- field reordering on some runtime
- IL2CPP, Unity, Java/Kotlin object layout
- optimization and metadata by engine
C++:
Please,
Log in
to view codes content!
Normalize:
C++:
Please,
Log in
to view codes content!
If packed:
C++:
Please,
Log in
to view codes content!
Maybe they gonna change to:
C++:
Please,
Log in
to view codes content!
Original Game Functions
The game uses functions to control logic. For example:
C++:
Please,
Log in
to view codes content!
These are the game's original functions running every frame. When we want to modify behaviour, we have two options:
- Hook the function - intercept it and return our own value
- Edit the field directly - change the variable in memory
Method 1: Function Hooking
Case 1: Fixed Speed (no toggle)We want the player speed to always be
10.0f, no enable/disable needed:
C++:
Please,
Log in
to view codes content!
Why does the hook have
void *instance but the original function does not? When the game calls a function inside a class, it already knows which object it is working with. However, when we hook that function from outside, we need to receive the address of that object, which is called the [I]instance[/I]. Every class function internally receives a hidden this pointer as the first parameter, which points to the current object.Case 2: Enable/ Disable Toggle
We want the user to be able to turn the speed hack on or off:
C++:
Please,
Log in
to view codes content!
orig_getSpeed pointer stores the original function address, allowing us to call the game's original code whenever the hack is disabled. This keeps the game working normally when we do not want to modify the function behavior.Case 3: Speed Multiplier
Instead of a fixed value, make all movement 3x faster while preserving the walking/running/crouching difference:
C++:
Please,
Log in
to view codes content!
Method 2: Field Editing
Instead of hooking, we write directly to the memory offset:
C++:
Please,
Log in
to view codes content!
getSpeed() function still applies the stamina formula: speed * 0.1f * stamina. If stamina is 5, final speed = 10 * 0.1 * 5 = 5, not 10. Field editing here alone doesn't work as expected.To fix it, you'd also need to change stamina:
C++:
Please,
Log in
to view codes content!
When Is Field Editing Better?
Field editing is the right choice when there is no function available to hook. For example, stamina may drain gradually over time, and there is nogetStamina() function. In this case, we have no choice except to directly modify the stamina field in memory:
C++:
Please,
Log in
to view codes content!
- Reading values (e.g. checking health or position)
- Getting class pointer chains
- Changing variables that have no associated getter function
Final Thought
When I was younger, I mostly asked “How do I mod this?” Today, I usually ask something different “How does this program think?”A hook, an offset, or a patch is only one answer to one problem. What matters more is understanding why it works.
If field editing does not give the result you expected, follow the logic.
If a hook behaves differently than expected, look at the caller, the arguments, the object, and what happens afterward.
You do not need to understand everything in one day. Just understand one more decision than you understood yesterday.
This is simply the approach that worked for me, and I hope it gives someone here another way to look at the problem.
Don’t just collect offsets. Collect questions.
See you around.