Last active
January 17, 2020 14:32
-
-
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++
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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 |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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++
newoperator (or the Cmalloc). This memory block is not managed automatically, and needs intervention by the user. For C++, it usesdeleteto deallocate the memory and release it, while C usesfree.Object obj(100)is a stack allocation. It automatically allocates an instance ofObjectonto the stack, and sets the value for the parameter to 100. Let's assume this allocated at0x1234. 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 besizeof(int), since there is only anintproperty within theObject. If we were to try and access theint(assume thatObjectdefines theintas 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 be0x1234 + 4, which translates into0x1238. 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, anintcan't be interpreted as astd::string(maybe it can, but it shouldn't be).What does this all have to do with using
Jailbreak?Jailbreakalso contains only 1 property, anint. This makes bothJailbreakandObjecthave 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 areinterpret_castof the memory address to a type ofJailbreak, we are tricking the compiler into believing the data we are accessing is actually bound to a type that allows public access of theint. 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
intwith no problems.