Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save berkedel/5c01c2b1e8f6111334f94d49a29eb1a1 to your computer and use it in GitHub Desktop.
Save berkedel/5c01c2b1e8f6111334f94d49a29eb1a1 to your computer and use it in GitHub Desktop.
Instance Variables and Function Pointers

The Unity tool. I hate it. All it does is make people worse at hacking because no one is developing actual analysis skills anymore. Now all you have to do to make an awesome hack is to CTRL-F everything until you have 100 features. If you want to get good at something, take the hard route. I can't stress that enough. Anyway, when I first heard about it, I thought it just revealed method names and locations. I was surprised upon finding that not only does it reveal method names and their locations, it reveals class names, parameters, instance variables, and the location in memory where said instance variables can be found. I couldn't believe what was right in front of me because everyone was just taking advantage of visible methods and their locations.

This applies to non-Unity games as well. You just need to have knowledge of object oriented programming to really know how to take advantage of instance variables. I guess I could cover that in a later tutorial. Anyway, let's get started.

This tutorial pertains to iOS only. Not the concepts, just the tutorial.

Part A. Instance Variables

1. Memory Layout

I went to make this absolutely clear. For example, this...

STR X3, [X0, #0x248]

...is telling the machine to store whatever X3 is holding (let's say ammo) in X0+0x248 (let's say X0 points to a Gun object). X0 contains the address of wherever the Gun object is held in memory. Let's say the address of the Gun object is 0x16fd27640. That means the machine is assigning whatever is at 0x16fd27640+0x248 to X3. That's why when you NOP a STR instruction, the value freezes. The machine can no longer update the value at the location of whatever you NOP'ed.

Let's look at an actual example involving arrays:

#include <stdio.h>
#include <malloc.h>
#include <conio.h>

int main(){
	int *a = (int *)malloc(sizeof(int)*4);
	
	free(a);

	_getch();
}

This program allocates some memory for an array of four integers, then frees that memory. _getch() forces the machine to wait for a letter to be pressed before it terminates the program. Now I'll give the elements in this array some values:

#include <stdio.h>
#include <malloc.h>
#include <conio.h>

int main(){
	int *a = (int *)malloc(sizeof(int)*4);
	
	a[0] = 3;
	a[1] = 2;
	a[2] = 4;
	a[3] = 1;
  
	free(a);

	_getch();
  
  	return 0;
}

The memory map of this array would be as follows:

  a[0]		a[1]		a[2]		a[3]
	 3	     2       4       1

But that's not all. Here's another equivalent way of writing the memory map:

   *(a+0)		*(a+1)		*(a+2)		*(a+3)
	   3	      2         4         1

This is the way we'll be able to get and set instance variables on various objects, but that is later down the line. Why does this work? Because when the compiler sees the [] operator, it translates it into pointer addition (as well as a dereference), which is exactly what we are doing by writing *(a+X). If you're still confused, hopefully this next part will clear this up. When we created the array of four ints, the machine allocated sixteen bytes space on the heap for it (as well as a pointer for it on the stack, but that isn't important for this tutorial). Why sixteen bytes? Because the size of an int on most machines is four, and we allocated memory for four ints. 4*4=16 :) We can take a look at what the memory looks like where the array is located in Visual Studio's debugger:

array_on_the_heap.png

The highlighted area is where the array is located. You can see the elements in the exact order as they were declared (3, 2, 4, 1) on the heap. Now we can use our newfound knowledge of memory layout to access and modify instance variables in iOS games.

2. The 'this' pointer

In C++, the 'this' pointer is best thought of as a hidden argument in every non-static function call. (Static methods do not need to be called with a class object) It references the current instance of its class. To better illustrate this concept, I have created a tiny class called Test. Also, take note that both of Test's instance variables are private, which means I cannot access them directly. Here is Test.h:

class Test {
private:
	int a;
	int b;

public:
	Test();
	
	int getA() const;
	int getB() const;

	void setA(int newA);
	void setB(int newB);

	~Test();
};

Here is Test.cpp:

#include "Test.h"

//create a new Test object and set its instance variables to 5 and 8 respectively
Test::Test(){
	this->a = 5;
	this->b = 8;
}

int Test::getA() const {
	return this->a;
}

int Test::getB() const {
	return this->b;
}

void Test::setA(int newA){
	this->a = newA;
}

void Test::setB(int newB){
	this->b = newB;
}

Test::~Test(){}

See how I use the this pointer to get and set Test's instance variables? If I wanted to call setA, I would do this:

Test *t = new Test();

t.setA(100);

Obviously, in assembly, we don't have the luxury of syntax. In assembly, the call to setA would look like this:

setA(t, 100);

t is the this pointer. In assembly, the this pointer is always the first argument to any (non-static) function. For additional clarity, if I included this method in the Test class:

void Test::setAB(int newA, int newB){
	this->a = newA;
	this->b = newB;
}

and called setAB like this:

Test *t = new Test();

t.setAB(1000, 2000);

The function call in assembly would be setAB(t, 1000, 2000). No matter what type the function is, however many arguments it has, or whatever class it belongs to, the this pointer is always the first argument. If the method is static, there is simply no this pointer.

3. A "Hacky" Way of Getting and Setting Instance Variables

Recall our class called Test and the array example. In the array example, our array was located at 0xba5d38, with sixteen bytes of extra space for the four elements. This is no different with our Test class. Consider this code:

#include <stdio.h>
#include <malloc.h>
#include <conio.h>
#include "Test.h"

int main(){
	Test *t = new Test();
  
	_getch();

	return 0;
}

The machine created a pointer to our Test object on the stack and allocated the appropriate amount of memory on the heap for its instance variables. In the Test constructor, I set a and b to 5 and 8 for visibility. Let's take a look at our memory in Visual Studio's debugger:

test-instance-variables.png

You can see t's instance variables on the heap! Again, since an int is four bytes on most machines, there are eight byes of memory reserved for the two instance variables. And remember, they are private. When I try and directly access the instance variable "a", I get this error:

trying-to-access-private-instance-variables.png

(side note: I changed my project directory and I forgot to change it back)

Fortunately for us, since C++ gives us complete control over our memory, we can access and modify a without a function through pointer arithmetic! Since a is our first instance variable, it is located where our Test object is located. b is located at our test object + 0x4, and so on if we had more instance variables. And remember, t is our this pointer. Consider this code:

int instanceVariableA = *(int *)(t + 0x0);
                                /*---1---*/
                        /*--2--*/

Don't be worried if this looks confusing. I'll explain this step by step. Just like with the array example, we can access data through pointer arithmetic. In the comments I've numbered each thing I am going to explain.

  1. Since t is literally just the address to its location on the heap, this is also the address to its first instance variable. Also, throughout this entire tutorial I have been including "+ 0x0" for clarity. In your code you don't have to do this.

  2. Cast whatever is at t + 0x0 to an int pointer and dereference it to access its value.

After all that, we have successfully grabbed t's instance variable a without a function. Remember that when a Test object is created, a is set to 5 and b is set to 8.

accessing-A-without-function.png

if I wanted to grab b, I would replace t + 0x0 with t + 0x4.

We can modify a in a similar manner in which we used to grab it. All we have to do is treat all of our pointer arithmetic and casting like a variable, and set it to whatever we want, like so:

*(int *)(t + 0x0) = 1000;

Let's see if this is successful:

accessing-A-and-setting-it-without-function.png

Success! I call getA() to make sure that I actually did change a. Let's take a look at our memory on the heap:

memory-on-heap-after-changing-A.png

Sure enough, the data at where a is located changed to 0xe803. But since the hex here is in little endian, 0xe803 is actually 0x03e8, which is 1000. We successfully modified a without calling a function. This will be extremely useful when making game hacks because we won't need to call a function that may or may not be present in the game itself every time we want to modify an instance variable. Everytime we call a function from the game, a little instability is added because we don't actually know how it works, and we want as much stability as possible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment