Прочитав эту прекрасную статью из Quarkslab о запутывающих нолях , я подумал, что мне нужно немного изменить ее, чтобы запутать произвольные целочисленные константы.
Однако, похоже, что мойпроход игнорируется или не оказывает никакого влияния на результирующий битовый код LLVM (или даже двоичный исполняемый файл).
Простое запутывание работает следующим образом: генерируется случайное int, затем константа для скрытия XORed с этимключ.На результат применяется дополнение к двум.
Это дает целое число, которое затем вычисляется до его исходного значения путем выдачи требуемого битового кода LLVM.
Вот мой PoC (адаптировано из 1 ):
#include "llvm/IR/Constants.h"
#include "llvm/IR/DataLayout.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/GlobalVariable.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/IntrinsicInst.h"
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/IR/Module.h"
#include "llvm/Pass.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Transforms/IPO/PassManagerBuilder.h"
#include "llvm/Transforms/Utils/BasicBlockUtils.h"
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <sstream>
using namespace llvm;
namespace {
class MyPass : public BasicBlockPass {
public:
static char ID;
MyPass() : BasicBlockPass(ID) {}
bool runOnBasicBlock(BasicBlock &BB) override {
bool modified = false;
for (typename BasicBlock::iterator I = BB.getFirstInsertionPt(),
end = BB.end();
I != end; ++I) {
Instruction &Inst = *I;
if (!isValidCandidateInstruction(Inst))
continue;
for (size_t i = 0; i < Inst.getNumOperands(); ++i) {
if (Constant *C = isValidCandidateOperand(Inst.getOperand(i))) {
std::stringstream stream;
stream << std::hex << C->getUniqueInteger().getLimitedValue();
std::string result(stream.str());
errs() << "Found an integer: 0x" << result << "\n";
if (C->getUniqueInteger().getLimitedValue() == 1337) {
errs() << "Obfuscating constant 1337\n";
if (Value *New_val = obfuscateInt(Inst, C)) {
Inst.setOperand(i, New_val);
modified = true;
errs() << "Replaced with " << New_val << "\n";
} else {
errs() << "ObfuscateZero: could not rand pick a variable for "
"replacement\n";
}
}
}
}
}
return modified;
}
// replValue = ~(originalInt ^ key) -1
Value *obfuscateInt(Instruction &Inst, Constant *C) {
srand(time(NULL));
int key = std::rand();
int64_t replacedValue = ~(C->getUniqueInteger().getLimitedValue() ^ key);
Constant *replValue = ConstantInt::get(C->getType(), replacedValue),
*keyValue = ConstantInt::get(C->getType(), key);
IRBuilder<> Builder(&Inst);
Value *repl = Builder.CreateXor(replValue, keyValue);
Value *finValue = Builder.CreateNeg(repl);
return Builder.CreateSub(finValue, ConstantInt::get(C->getType(), 1));
}
// only interested in integer values
Constant *isValidCandidateOperand(Value *V) {
Constant *C;
if (!(C = dyn_cast<Constant>(V)))
return nullptr;
if (!C->getType()->isIntegerTy()) {
return nullptr;
}
return C;
}
bool isValidCandidateInstruction(Instruction &Inst) {
if (isa<GetElementPtrInst>(&Inst)) {
errs() << "Ignoring GEP\n";
return false;
} else if (isa<SwitchInst>(&Inst)) {
errs() << "Ignoring Switch\n";
return false;
} else if (isa<CallInst>(&Inst)) {
errs() << "Ignoring Calls\n";
return false;
} else {
return true;
}
}
};
} // namespace
char MyPass::ID = 0;
static RegisterPass<MyPass> X("MyPass", "Obfuscates 1337", true, false);
// register pass for clang use
static void registerMyPassPass(const PassManagerBuilder &,
llvm::legacy::PassManagerBase &PM) {
PM.add(new MyPass());
}
static RegisterStandardPasses
RegisterMBAPass(PassManagerBuilder::EP_OptimizerLast, registerMyPassPass);
И простая тестовая программа:
int main(void)
{
volatile int a = 3;
a += 1337;
return a;
}
Я компилирую проход LLVM следующим образом:
clang -g3 -shared -fPIC MyPass.cpp -o pass/MyPass.so
Затем я запускаюпередача битового кода LLVM вышеупомянутого простого теста:
opt -S -load pass/MyPass.so -MyPass bin/simple_test.ll -o bin/out.ll
Содержимое bin / out.ll такое же, как bin / simple_test.ll, что, очевидно, противоположно тому, что я хочу:
; ModuleID = 'bin/simple_test.ll'
source_filename = "tests/simple_test.c"
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-linux-gnu"
; Function Attrs: noinline nounwind optnone sspstrong uwtable
define dso_local i32 @main() #0 {
%1 = alloca i32, align 4
%2 = alloca i32, align 4
store i32 0, i32* %1, align 4
store volatile i32 3, i32* %2, align 4
%3 = load volatile i32, i32* %2, align 4
%4 = add nsw i32 %3, 1337
store volatile i32 %4, i32* %2, align 4
%5 = load volatile i32, i32* %2, align 4
ret i32 %5
}
attributes #0 = { noinline nounwind optnone sspstrong uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
!llvm.module.flags = !{!0, !1, !2}
!llvm.ident = !{!3}
!0 = !{i32 1, !"wchar_size", i32 4}
!1 = !{i32 7, !"PIC Level", i32 2}
!2 = !{i32 7, !"PIE Level", i32 2}
!3 = !{!"clang version 7.0.1 (tags/RELEASE_701/final)"}
Конечно, я думал, что компилятор оптимизировал мою небольшую попытку запутывания, но после ручного применения небольшого преобразования к тестовой программе я мог видеть дополнительные операции XOR, NEG и SUB в результирующемдизассемблирование, которое заставляет меня думать, что оптимизатор здесь не виноват.
Меня интересует подтверждение концепции, в которой константа 1337 (немного) «скрыта», просто ради этого.Меньше интересуются комментариями, в которых говорится, что запутывание бесполезно, или указанием на то, что вам не нравится в коде, который не имеет отношения к вопросу.