Python is an extremely flexible and powerful language that allows for development of post-exploitation capabilities in Empire. The glaring disadvantage is that for some capabilities, a host may not have the necessary Python modules installed, meaning you may have to drop a package to disk. I don’t believe there are huge implications from having to drop module files to disk; however, it is not the most OPSEC friendly method.
I started to investigate the Python import process, when I came across the ability to define a custom import hook. Import hooks allow you to modify the logic in which Python modules are located and how they are loaded. This involves defining a custom “Finder” class and either adding this class to sys.meta_path or sys.path_hooks. This concept is not new and has been used for quite some time. I found one particular example of importing packages from zip files on a remote server. To work with Empire, it would just need to be slightly modified. Let’s move on to how this can be weaponized.
Zip files are a great file format to house Python packages. With Python, we can extract files from a ZipFile object in memory without writing it to disk. In Empire, we can install our custom Finder class and extract files as they are requested during the import process. In order to properly import a module from a zip file in Empire, the folder structure needs to strictly follow the Python package format. You can view an example here. Once a zip file is created from a package and imported, it is added to a repository (dictionary) of all imported packages. A zip file can contain as many packages as necessary. The logic built into the finder class will still fetch the appropriate files during an import.
Let’s walk through a quick example with this simple Python package for interacting with Gmail. After we confirm that the package format is correct, we can import the zip.
After the package is imported, we can use the “viewrepo” command to view all repos and their contents. Provide the name of a repo (the name of the zip file) as an argument to just view it’s contents.
Now we can run a simple script to login into Gmail and dump the body of the first unread email.
When executed, Python will try to locate the module in the built-in modules first, then it will look in directories listed in the variable sys.path. Then our custom finder class will be called upon to locate and load the Gmail module. This is a simple example and doesn’t really demonstrate an offensive use case, but surely there are some. Here is a short video to demonstrate using the pywinrm python module to spawn a new agent on a remote windows system:
So there are definitely some drawbacks to in-memory imports. You can only import Python packages written in Python. There are some modules written in native C, but there is no logic in the custom finder class to handle these. Also, dependencies can be a nightmare. If the package being imported into Empire requires other modules, those must be imported as well (does not have to be in the same repository). Without them, the imports will fail when python processes import statements. This feature is now available in the Empire 2.0_beta branch as well as the EmPyre master branch.