Create and Load Transforms
Last updated
Last updated
Create transforms and load transforms are python scripts that are executed to help create payloads and turn commands into appropriate formats for module loading. All of this is payload type specific and can be viewed at any time.
For the most part, these transforms should be transparent to the user. However, it's good to know what's going on under the hood. Take the demo poseidon
agent as an example:
These transforms, descriptions, and parameters types all come from the transforms in the Transforms management page. In this page, you assign an instance of a Create/Load transform to your payload type and give it an order of execution. You then give it a description for the operator to see when creating a payload and any necessary parameter instance values.
For a parameter type of ChooseOne
, you supply options (one per line) that then get changed to a select dropdown for the operator when creating the payload.
The first and last steps must always be the same (which is why you can't edit them):
Step 0: After all of the payload code pieces are stamped together, a zip of all the code is sent to the corresponding payload type container and unzipped. This is where the user supplied steps come into play.
Step n: Now that the contents of the final payload file is sent back from the payload type container to the main Apfell container, the last step is to write the file to disk and register the file in the system as completed.
The above transform setup results in the following for the user when creating a payload:
Not every payload type needs creation or load transforms; it just depends on the language and how the agent works. For example, the apfell-jxa
payload is just a script that don't need any additional transforms applied.
Despite no additional steps absolutely needed, this is where additional steps can be taken to safeguard payloads before deploying them in operations. You could write transforms that take the final payload, run it through some obfuscation, remove potential static indicators, stamp in fake values, etc and then return that as the final payload.
Most of the process is the same for load transforms as it is for create transforms.
You can get as complicated or simplistic as needed for load commands. But a few thing are always the same:
The user types load shell
on the command line. The command is looked up, the code is read, and sent off to the payload type container along with the rest of the files normally used for creating your payload. The reason the other files are sent off are in case they're needed for compilation purposes.
In this case, the command code is combined together. For the apfell-jxa agent, you can provide a list of command names like load shell,screencapture,ls,download
. Because of this, the code will combine them all together into a single array of bytes
Finally, the return payload is registered in the system as a special load-timestamp
file, given a file ID number, and that number is updated in the load command. The agent can then pull down this specific set of commands by getting that file. To help prevent these files from bloating the Apfell server, load files only exist to be downloaded once, then are deleted.
Creating your own transforms can be very handy as you start working on new payloads or extending the existing ones. There are a few gotchas though that need to be taken into consideration when developing these:
Creation/Load transforms are executed by the "service" running within the associated payload type's Docker container.
When all of your code is moved to the docker container, there is a temporary directory created where all of your code lives. You can access this directory by using the self.working_dir
variable in your path.
For example. If you have a file called payload.c
that's not in any other sub folders, then you can access it within your create/load script via: "{}/payload.c".format(self.working_dir)
.
If you're running a subprocess to do some of your compilation, be sure to keep track of where
you are - if you set the cwd=self.working_dir
when doing subprocesses, then you could access that file simply via payload.c
. It's important to remember your current working directory when accessing files normally and from within any subprocesses you spawn.
At the end of the day, your transforms need to return the bytes of the file represents your finished payload. If you need to return multiple files, zip them up and return the bytes of the zip instead (the Poseidon compile has an example of this).
You can save state between transforms by accessing the shared class variables
self.saved_array
self.saved_dict
This allows you to save information from one transform to the next or even take in user input in a clean way that can be easily accessed later on in a script. For example, the Poseidon create transforms presents the user with a variety of options for compiling the golang payload and saves them into the self.saved_array
. Then, in the final transform, Poseidon uses these options to create the payload.
Be sure to look at the above bullet points and keep track of your current working directory when accessing your payload files