在阅读this nice article from Quarkslab on obfuscating zeroes后,我想我会去提前调整它有点混淆任意整数常量。
然而,似乎我的通行证被忽略或不具有对所得到的LLVM位码(甚至是二进制可执行文件)的任何影响。
简单的模糊处理的工作原理如下:随机INT产生,那么常量隐藏异或与此键。 2的补加的结果。
这产生的整数,它然后通过发射所需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
斌/ out.ll的内容是一样的斌/ 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是(略)“隐藏”,只是为了它的缘故。不甚感兴趣的评论说,混淆是徒劳的,或指出的东西,你不要在不相关的问题的代码一样。
这里的问题是IRBuilder,它在创建新的IR指令默认执行Constant Folding。
为了解决这个问题,我不得不创建一个新的挥发性(挥发性不是强制性的,但我可以,所以我做)的IR变量,对其执行了“混淆”的算术运算,并更换指令的使用“1337操作数“与所得到的值。
该代码是相同的问题,除了功能obfuscateInt(...),现在看起来如下:
// replValue = ~(originalInt ^ key) -1
Value *obfuscateInt(BasicBlock &BB, Instruction &Inst, Constant *C) {
srand(time(NULL));
int key = std::rand();
int32_t replacedValue = ~(C->getUniqueInteger().getLimitedValue() ^ key);
Constant *replValue = ConstantInt::get(C->getType(), replacedValue),
*keyValue = ConstantInt::get(C->getType(), key);
IRBuilder<> Builder(&Inst);
// allocate enough space on the stack to store a 32-bit value. Var name = "AA"
AllocaInst *varAlloc = Builder.CreateAlloca(Builder.getInt32Ty(), nullptr, "AA");
// Store the key in AA, set "volatile" to true
Builder.CreateStore(keyValue, varAlloc, true);
// read the variable "AA"
LoadInst *loadVar = Builder.CreateLoad(varAlloc, true, "AA");
// use it
Value *repl = Builder.CreateXor(replValue, loadVar);
Value *finValue = Builder.CreateNeg(repl);
return Builder.CreateSub(finValue, Builder.getInt32(1));
}
生成的IR现在看起来像:
%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 = alloca i32
store volatile i32 525933950, i32* %4
%5 = load volatile i32, i32* %4
%6 = xor i32 -525932616, %5
%7 = sub i32 0, %6
%8 = sub i32 %7, 1
%9 = add nsw i32 %3, %8
store volatile i32 %9, i32* %2, align 4
%10 = load volatile i32, i32* %2, align 4
%11 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([41 x i8], [41 x i8]* @.str, i32 0, i32 0), i32 %10)
%12 = load volatile i32, i32* %2, align 4
ret i32 %12
和拆卸显示,1337没有出现在任何地方,但该程序的行为被保留:
0000000000001140 <main>:
1140: 55 push rbp
1141: 48 89 e5 mov rbp,rsp
1144: 48 83 ec 10 sub rsp,0x10
1148: 31 c0 xor eax,eax
114a: c7 45 fc 00 00 00 00 mov DWORD PTR [rbp-0x4],0x0
1151: c7 45 f8 03 00 00 00 mov DWORD PTR [rbp-0x8],0x3
1158: 8b 4d f8 mov ecx,DWORD PTR [rbp-0x8]
115b: c7 45 f4 02 77 c4 31 mov DWORD PTR [rbp-0xc],0x31c47702
1162: 8b 55 f4 mov edx,DWORD PTR [rbp-0xc]
1165: 81 f2 c4 8d 3b ce xor edx,0xce3b8dc4
116b: 29 d0 sub eax,edx
116d: 83 e8 01 sub eax,0x1
1170: 01 c1 add ecx,eax
1172: 89 4d f8 mov DWORD PTR [rbp-0x8],ecx
....
该IRBuilder
performs basic constant folding by default(所报告的在他的answer问题作者)。
在IRBuilder
禁用常量合并如下创建它。
IRBuilder<NoFolder> Builder;