在阅读this nice article from Quarkslab on obfuscating zeroes之后,我想我将对其进行一些微调以混淆任意整数常量。
但是,看来我的密码被忽略了,或者对生成的LLVM位代码(甚至二进制可执行文件)没有任何影响。
简单的混淆处理如下:生成一个随机的int,然后使用该键对要隐藏的常量进行XOR运算。将二进制补码应用于结果。
这将产生一个整数,然后通过发出所需的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被(略)“隐藏”,仅出于此目的。对评论说混淆是徒劳的,或者指出代码中您不喜欢的与问题无关的东西,不太感兴趣。
答案 0 :(得分:0)
这里的问题是IRBuilder,它在创建新的IR指令时默认执行Constant Folding。
要解决此问题,我必须在IR中创建一个新的volatile(volatile不是必须的,但我可以这样做)变量,对其执行“混淆”算术运算,并替换该指令的操作数使用“ 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
....
答案 1 :(得分:0)
IRBuilder
performs basic constant folding by default(由问题作者在其answer中报告)。
要在IRBuilder
中禁用常量折叠,请按以下步骤创建。
IRBuilder<NoFolder> Builder;