RustPack 1.8.0
This blog post covers some of the new features that were implemented in RustPack version 1.8.0, which is now available to all of our customers.
New output formats
With this release, we provide three more output types for payloads:
- Node payloads for Electron applications
- Python output
- .NET executable or DLL output
.node payloads can be used to sideload into Electron applications such as Slack, Teams, Discord or similar. Customers can either backdoor JavaScript files so that they load the custom .node file at runtime, or alternatively replace existing files with a malicious one. In the second case, exports can be forwarded to the original .node file in order to not break the functionality of the application, similar to DLL sideloading.
Python scripts have the advantage that the code always runs in a trusted, signed python.exe process. Customers therefore either need to bring a portable Python to the target system, or alternatively use these payloads in environments where Python is already installed. The only way to properly detect malicious Python scripts is via signatures on disk. To avoid such detections, our scripts are heavily polymorphic and payloads are encoded differently each time for more variance.
.NET output has the advantage that it can easily be used for ClickOnce weaponization, for example, or alternatively be loaded into PowerShell via assembly::load or used from a C2 framework via execute-assembly. Each .NET output has different AppIDs, namespaces, class names, function names and custom string encoding. Building signatures for these payloads on disk, or AMSI/ETW based detections, is therefore not applicable.
Both Python and .NET payloads first build custom position independent code with all the operator provided input options, and then load this custom polymorphic shellcode from memory at runtime. RustPack has supported shellcode output since version 1.7.0, and as mentioned in the previous blog post RustPack TTPs, weaponizing this output in scripting languages is relatively easy and more OPSEC safe than re-implementing the whole loader logic in that language.
Payload obfuscation
The initial input payloads for RustPack were the following three:
- Shellcode
- Portable Executable (PE)
- .NET assemblies
Since RustPack 1.7.0, .NET Core payloads can also be loaded from memory at runtime and therefore be used as input. For the first three input types, the --obfuscate flag now obfuscates these input payloads BEFORE encrypting/encoding and embedding them in the loader, which is quite powerful for some use cases.
Depending on the input type, one of two obfuscation engines is used. For .NET assembly input, that means that whenever --obfuscate is used, operators don’t need to use any AMSI or ETW bypass on top anymore. A custom .NET obfuscator built into RustPack decompiles the input assembly, changes namespace names, class names, variable names, function names and AppIDs, and applies custom string encoding, so that the original input cannot be detected via signatures anymore. A C2 implant in .NET Framework, for example, can also no longer be detected via memory scanner rules, as it will look completely different even at runtime. For some .NET post exploitation tooling such as Rubeus, execution could still be detected via command line parameters. To avoid such detections, operators can either use --obfuscatecommandline and use the output payload with new command line parameters, or use --arguments kerberoasting to pass the parameters automatically in the loader.
For shellcode and PE input, the obfuscation engine is more complex, as we needed to mutate instructions on ASM level and/or build a custom bin2bin obfuscator. --obfuscate=light will only replace ASM instructions with instructions of the same size, while --obfuscate=full will also apply size changing transformations and add junk code to the input. It has to be mentioned here that obfuscation of shellcode input works better for some payloads and less well for others. BRC4 shellcode payloads are a good example: only a very small part of them can be obfuscated, as the real PIC payload is decoded/decrypted at runtime from the shellcode that operators usually have, so only a small wrapper/loader can be obfuscated here. If you obfuscate shellcode that is not a reflective loader, however, many more instructions will be adjusted. For Portable Executables, the same transformations are applied to the .text section to avoid byte pattern signatures. However, many real world detections on executables also rely heavily on string patterns, such as sekurlsa::logonpasswords in Mimikatz. To also modify the embedded strings, --obfuscatestrings can be used. As this can and will change the command line usage, operators are also provided with a mapping of which strings were modified, so that they can look up the old vs. new values. And no - you should not use this feature to pack Mimikatz thinking that you won’t get detected! You are likely still detected, last but not least by its behaviour - this is just a good example for signatures ;-)
Various new payload encodings
Some customers were asking for encodings that also apply to a decoupled payload file when --shellcodeFile is being used. So instead of having an encrypted byte blob on disk, something legitimate looking such as a CSV or JSON file could be loaded. We therefore first implemented a custom CSV and JSON encoding, where the encoded blob is embedded by default, but with --shellcodeFile a benign looking CSV or JSON file will be loaded from disk at runtime.
RustPack has always used custom, non public encoding techniques by default for all payloads, but the optional changes in the encoding mainly provided well known public encodings such as --payloadEncoding MAC or --payloadEncoding IPv4. Using MAC or IPv4 encoding against some vendors will trigger machine learning based detections, as hundreds of thousands of MAC addresses in a payload are a clear IoC, which is only seen in malware but not in benign software. So just to provide more variance here, we added NINE new non public encoding techniques on top. None of these will trigger such detections, as they were not and are not heavily used by threat actors or red teams in the wild.
Embedded stego images
Since RustPack 1.7.0, steganography can be used on .PNG or .WAV files. The encrypted payload is then retrieved from a legitimate image on disk or from a remote web server, for example. Since 1.8.0, and when neither --shellcodeFile nor --shellcodeURL is used, the PNG or WAV file will be embedded into the output payload, so operators don’t need to provide or host the original file on top. Just a small convenience change.
.NET Core runtime bundling
As mentioned above, operators have been able to pack .NET Core payloads since the last release already. The Athena Mythic implant, for example, is written in .NET Core and can be loaded from memory with RustPack at runtime. There are very few public offensive tools with this capability, because loading .NET Core from memory usually still requires the .NET Core runtime to be present on the target system. Without .NET Core installed, execution from memory is - well, nothing is impossible - but at least not yet documented or done by anyone to our knowledge. With the 1.8.0 release, we ship one more convenience feature, namely --bundleRuntime. This feature will bundle a minimal .NET Core runtime inside the payload and drop the runtime files on the target system’s disk first. After that, those files can be used to reflectively load the .NET Core payload from memory, so that it will even run on systems without the runtime installed.
This also increases the size of the final payload by several MB. If size hurts too much, --compressedBundle can be used on top to compress the runtime before embedding it.
Debug based injection
We have already implemented several more OPSEC safe remote injection techniques in RustPack. This release ships one more technique with --debuginject, just to increase the variation and the options available to operators. We won’t dig into the technical implementation here though, for obvious detection reasons.
Further ML evasion
We have already implemented various features in this area and blogged about them. This release fine tunes payloads based on that.
As already outlined in multiple blog posts this year - for example by SpecterOps - reversing EDR components has become much simpler and more efficient this year with LLM support. We were able to extract the ML engines of various vendors for offline scanning, and adjusted our post compilation process in RustPack to modify various payload metadata, so that payloads look more benign to the ML engines of these vendors. These changes lead to low ML scores for all tested vendors, for both executable and DLL output formats.
GUI
There is a GUI now! Run RustPack with --gui and payload generation becomes point & click. This might help some customers handle the huge number of parameters and features we have already implemented.

The GUI also supports storing payload profiles and loading previous configurations, so that you can re-use known working options across projects.
Future work
The plan for the next release is primarily to add more output formats that can be used for initial access purposes. Making RustPack a one stop, all in one tool for various formats will likely help our customers save time and costs for development or other tooling in their projects.