LLVM is collection of tools and libraries that helps in developing compilers, debuggers, software and more.
llvm have a popular c/++ compiler, clang, which also supports plugins.
In this chapter we will build a tiny, simple llvm plugin.
First, we need to setup the plugin. We will be writing it in c++. The following code will register our plugin in llvm passes:
class MBASub
{
public:
static void runOnSub(BasicBlock *BB);
static void runOnAdd(BasicBlock *BB);
static void runOnBasicBlock(BasicBlock *);
static void runOnFunction(Function *);
};
class TinyObfuscator : public PassInfoMixin<TinyObfuscator>
{
public:
PreservedAnalyses run(Module &M, ModuleAnalysisManager &MAM);
static bool isRequired() { return true; }
};
std::atomic<bool> STATE{false};
PassPluginLibraryInfo getPluginInfo()
{
return {LLVM_PLUGIN_API_VERSION, "TinyObfuscator", LLVM_VERSION_STRING,
[](PassBuilder &PB)
{
PB.registerPipelineEarlySimplificationEPCallback(
[&](ModulePassManager &MPM, OptimizationLevel opt)
{
if (STATE.load())
return true;
STATE.store(true);
MPM.addPass(TinyObfuscator());
return true;
});
}};
}
extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo
llvmGetPassPluginInfo()
{
return getPluginInfo();
}
PreservedAnalyses TinyObfuscator::run(Module &M, ModuleAnalysisManager &MAM)
{
for (Function &F : M.getFunctionList()) {
MBASub::runOnFunction(Func);
}
return PreservedAnalyses::none();
}
return PreservedAnalyses::none(); means that the module did change.
Now, we need to implement MBASub::runOnFunction, the last peice of this journey.
it is the process of changing a simple mathematical expression, into a more complex one, that give the same result, and can be repeated over and over again to
achieve a code harder to be understood by the naked eye.
Some examples of mixed boolean arithmetic expanded expressions:
X - Y == (X ^ -Y) + 2*(X & -Y)
x + y = (~ (x +
((- x) +
((- x) + (~ y))
)
))
r = rand(); c = b + r; a = a + c; a = a - r // a and b won't change
we will obfuscate only sub expression (a - b) in this example:
void MBASub::runOnBasicBlock(BasicBlock *BB)
{
runOnSub(BB);
runOnAdd(BB);
}
void MBASub::runOnFunction(Function *Func)
{
for (auto &BB : Func->getBasicBlockList())
{
runOnBasicBlock(&BB);
}
}
Value ObfuscateSub(IRBuilder<> &Builder, BinaryOperator *Operation) {
// X - Y == (X ^ -Y) + 2*(X & -Y)
Type *type = Operation->getOperand(0)->getType();
return Builder->CreateAdd(
Builder->CreateXor(Operation->getOperand(0),
Builder->CreateNeg(Operation->getOperand(1))),
Builder->CreateMul(
ConstantInt::getSigned(type, 2),
Builder->CreateAnd(
Operation->getOperand(0),
Builder->CreateNeg(Operation->getOperand(1)
)
)
)
);
}
void MBASub::runOnSub(BasicBlock *BB)
{
std::vector<Instruction *> instructions;
for (auto &Instr : BB->getInstList())
if (Instr.getOpcode() == Instruction::Sub)
instructions.push_back(&Instr);
for (auto &Instr : instructions)
{
BinaryOperator *BinOp = (BinaryOperator *)Instr;
IRBuilder<> Builder(Instr);
Instr->replaceAllUsesWith(ObfuscateSub(Builder, BinOp));
}
}
To compile the code, you can use my CMake configuration below, after installing llvm-devel:
project(tobf DESCRIPTION "obfuscator llvm pass")
set(CMAKE_C_COMPILER clang)
set(CMAKE_CXX_COMPILER clang++)
find_package(LLVM REQUIRED CONFIG)
add_definitions(${LLVM_DEFINITIONS})
include_directories(
${LLVM_INCLUDE_DIRS}
include
)
add_library(tobf SHARED
src/TinyObfuscator.cpp
src/passes/MBASub.cpp
)
To use it, simply run the following command:
clang++ -fpass-plugin=./build/libtobf.so main.cpp -o ./out/main.so