In the last post, I talked about the development of pynject and the research leading up to it. Our target is a pygame game that's bundled with pyinstaller. When we left off we had just achieved code injection. Now the question remains, what exactly comes next? The demonstration showed me modifying a variable in a program I wrote. How do we hack this game knowing nothing about how it's written? Well, there are a slue of techniques we can use to learn a bit more information about what's going on behind the scenes.
One of the most powerful tools in our arsenal is python's 'globals()' function. This will return a dictionary containing the symbols in the global scope as keys, and the objects they are mapped to as values. Consider the following code.
def someFunc(): localVar = 22 something = "nothing"
If we were to get the globals from this script, our variable something would be returned. But our function's local variable would not. The only way we can get variables from the local scope would be to use python's 'locals()' function from within the target function. Nevertheless, we can get some quite powerful information from the globals alone. Global variables are often used as flags, so pay special attention to integers and booleans.
Garbage Collection & Objects
Given that python's garbage collector is enabled (it is by default), we can get a list of all objects currently being tracked. We can do this by importing the 'gc' module, and using the 'gc.get_objects()' function.
The disassembler is an essential tool for reversing functions, though it can be used on many more instance types. To disassemble a function we need to import the 'dis' module and use the 'dis.dis()' function. An example of the output is as follows.
def printMe(thing): print(thing) =========== Turns into =========== 2 0 LOAD_GLOBAL 0 (print) 2 LOAD_FAST 0 (thing) 4 CALL_FUNCTION 1 6 POP_TOP 8 LOAD_CONST 0 (None) 10 RETURN_VALUE
You can find a full list of disassembly instructions and their meanings here.
Enumerating Instance Children
We have a few options when it comes to enumerating children of an instance. If we want to get the attributes of a class we can use 'dir()', 'vars()', or 'gc.get_referents()'. The 'dir' and 'vars' functions do not require any imports. 'dir' will return a dictionary containing attributes of our class and it's base classes. 'vars' will only return a dictionary containing attributes of the specified class.
'gc.get_referents' will return a list containing every object referred to by the object, which includes it's attributes and methods.
And so on...
There are many, many more methods you could use to get more information about your target. We haven't even touched on pdb. Nonetheless, if you use each of the methods above in combination you begin to get a very clear idea of the data available to manipulate, and a basic understanding of the flow of some functions and methods.
Here is where the bundled payloads come in. What if I told you that I've written payloads which make it effortless to employ all of the above tactics? Well... I have. They come bundled with pynject.
This is a script I wrote to make it easy to manage your scripts, and execute them without the need to call functions of the C API. Your scripts will be executed in a local scope. This means if you need to modify a global variable, you will need to declare it in the scope first as follows.
myGlobal = "This didn't work..." global myGlobal myGlobal = "Now it works!"
The executor redirects console output to it's own console window to make it easier to debug scripts. The executor also has a number of API functions of it's own. They allow for things like module injection into applications with limited paths (i.e. pyinstaller), filtering it's own objects from the globals, and finding object instances by type. As an added bonus, the executor cleans up after itself very well. It also spawns in its own thread.
This is a script which automates the inspection of objects in a target process. Every method I mentioned above is done in the background, and the results inserted into trees. There's not much to say about it beyond what you can see for yourself. It's still very early in development like most of this project, and I am a single developer. There are a few glitches I'm aware of but it will get the job done.
So, we're armed with a working knowledge of reverse engineering python processes. Now what? Well, now we hack Rift Wizard! Next time anyway.