Skip to content

Instantly share code, notes, and snippets.

@Frontear
Last active January 17, 2020 14:32
Show Gist options
  • Select an option

  • Save Frontear/4e52cadfa57011e4bcdecb8c4e32096a to your computer and use it in GitHub Desktop.

Select an option

Save Frontear/4e52cadfa57011e4bcdecb8c4e32096a to your computer and use it in GitHub Desktop.
An example of how you can use reinterpret_cast to access and modify private properties of classes in C++
#ifndef FRONTEAR_JAILBREAK
#define FRONTEAR_JAILBREAK
/* This class is meant to mimic the layout of the class you want to access
* It won't necessarily always work, as the compiler can potentially move
* properties around in one, but not the other. This shouldn't be used
* in daily code, and serves moreso as a demonstration.
*/
struct Jailbreak {
int x; // a publically accessible property
};
#endif//FRONTEAR_JAILBREAK
#include "jailbreak.hpp"
#include "object.hpp"
#include <iostream>
int main() {
Object obj(100);
std::cout << obj.getX() << std::endl; // should print the value we gave in
Jailbreak* jb = reinterpret_cast<Jailbreak*>(&obj); // reinterpret the memory of our Object to a Jailbreak type instead
jb->x = 500; // rewrites the value of 'x' in memory, which obviously carries through back to Object, as they share that memory
std::cout << obj.getX() << std::endl; // should print the value that our Jailbreak object set it to
return 0;
}
#ifndef FRONTEAR_OBJECT
#define FRONTEAR_OBJECT
class Object {
int x; // a private property. This will be accessible by Jailbreak
public:
explicit Object(int value) : x(value) {} // basic, boring constructor
int getX() { // so that we can verify that 'x' has been modified
return x;
}
};
#endif//FRONTEAR_OBJECT
@Frontear

Copy link
Copy Markdown
Author

For an annoying reason, GitHub is moving the files so that main.cpp for some reason is in the middle. This breaks the linear reading and structure of these files..

@Frontear

Copy link
Copy Markdown
Author

For the record, yes I know you could do something really underhanded and use #define to completely modify the classes by defining access modifiers differently:

#define private public

Neither of these are recommended ways to grab object references. Quite frankly, I don't think any way should be recommended. Private members are private for reasons that the original programmers knows. Furthermore, since it depends so heavily on internal layout, basically any change to the private members will completely break your solution, leading to maintainability issues.

@Frontear

Frontear commented Jan 17, 2020

Copy link
Copy Markdown
Author

To understand why this works, you need to have an understanding as to how the underlying memory handling occurs.

When creating an allocation, you have 2 different options. These options are known as the stack, and the heap. The stack is what is used within this example. It's a small block of memory given to applications. This memory block is managed entirely automatically, allocations and deallocations happen whenever they are deemed necessary. The heap is another style of allocations, that makes use of the C++ new operator (or the C malloc). This memory block is not managed automatically, and needs intervention by the user. For C++, it uses delete to deallocate the memory and release it, while C uses free.

Object obj(100) is a stack allocation. It automatically allocates an instance of Object onto the stack, and sets the value for the parameter to 100. Let's assume this allocated at 0x1234. The head, or beginning part of the memory block would point to this value in the memory. This allows us to access any properties allocated within it.

How big would the size be? It would be sizeof(Object) big. In this case, that will be sizeof(int), since there is only an int property within the Object. If we were to try and access the int (assume that Object defines the int as a public property), we'd point to an address in memory. What address? It would be the initial, or head address, plus the size of the property. For most systems, sizeof(int) will be 4 (this isn't always true, but assume it is). In that situation, the memory address would be 0x1234 + 4, which translates into 0x1238. Therefore, the compiler will try to read the integer at this location in memory, and bring it back to the program for usage.

What about reinterpret_cast. What's that? This is a special function in C++ that will attempt to interpret certain memory differently. You can think of this kind of like a cast ((int) 'a'). C++ will attempt to interpret a certain memory block as a completely different type, and thereby change the way it handles the memory. This does not work in the case that the two types are so different, the memory can't be interpreted for the other. For example, an int can't be interpreted as a std::string (maybe it can, but it shouldn't be).

What does this all have to do with using Jailbreak? Jailbreak also contains only 1 property, an int. This makes both Jailbreak and Object have the exact same memory contract (only 1 integer). Being public/private is only a programmer thing, the computer doesn't even understand that, so that has absolutely no effect. When we do a reinterpret_cast of the memory address to a type of Jailbreak, we are tricking the compiler into believing the data we are accessing is actually bound to a type that allows public access of the int. Truth is, it originally didn't, but since both the computer and memory won't understand accessibility, this makes no impact.

To conclude, we basically read the same memory, but trick the compiler by using a different accessibility type. This in turn allows us to access the int with no problems.

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