There are a lot of LLVM-based obfuscators out in the wild. This repository makes it (far) easier to get them up and running, using out-of-tree LLVM compilation.
Out-of-tree LLVM compilation differs in that, instead of compiling the entire LLVM project, we only compile the code as objects that are loaded by respective LLVM tools.
Supported LLVM versions:
Projects | Description | Author |
---|---|---|
Goron | from goron project | amimo |
Hikari | from hikari project | *maintained by |
Loyalty | stack-string obfuscation, outlining | loyaltypollution |
Callfuscator | Per-instruction outlining to functions | d0minik |
Pass | Description |
---|---|
-enable-sstring |
Performs stack-string obfuscation on all global strings. |
-enable-outline |
Performs outlining on all BinaryOperator instructions. |
Pass | Description |
---|---|
outlinefuncs |
Performs outlinining on all instructions |
The project comes with a .devcontainer Dockerfile that can be used to spin up debian container with LLVM 16.0 pre-installed.
The default standard for pass-writing seems to build the entire project. After all, the introductory LLVM documentation also does so.
Here's how we can modify existing passes for out-of-tree compilation:
- Place the folder containing the LLVM passes into
lib\[projectname]
. Likely, we will be looking at these directories:
headers:
llvm/include/llvm/Analysis/[projectname]
llvm/include/llvm/Transforms/[projectname]
source:
llvm/lib/Analysis/[projectname]
llvm/lib/Transforms/[projectname]
llvm/lib/[projectname]
- Rename all include paths. For instance, in-tree directories will place these header files in their respective directories.
#include "llvm/Transforms/Obfuscation/AntiHook.h"
#include "llvm/Transforms/Obfuscation/CryptoUtils.h"
Instead, since we got lazy and placed everything in the same place, we have:
#include "AntiHook.h"
#include "CryptoUtils.h"
- In order to “register” the pass, we typically add it to
llvm/lib/Passes/PassRegistry.def
and so on.- When compiling out-of-tree, we add a new cpp file that loads in the plugin by interfacing with the PassBuilder object via
registerPipelineParsingCallback
directly. - PassBuilder also exposes other public methods for us to register our passes.
- Example:
- When compiling out-of-tree, we add a new cpp file that loads in the plugin by interfacing with the PassBuilder object via
llvm::PassPluginLibraryInfo getObfuscationPluginInfo() {
return {
LLVM_PLUGIN_API_VERSION, "[passname]", LLVM_VERSION_STRING,
[](PassBuilder &PB) {
PB.registerPipelineParsingCallback([](StringRef Name, ModulePassManager &MPM,
ArrayRef<PassBuilder::PipelineElement>) {
if (Name == "...") {
MPM.addPass(...);
return true;
}
return false;
});
}};
}
extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo
llvmGetPassPluginInfo() {
return getObfuscationPluginInfo();
}
- ensure the project has a CMakeLists.txt file that builds the project as an LLVM shared library. example:
set(BUILD_SHARED_LIBS OFF)
llvm_add_library([projectname] SHARED
[...].cpp
)
- upon building, we load all shared objects via
opt-16 -load-pass-plugin [built_object_path]
and continue usage as per normal