大土豆安全笔记 | LLVM编译框架与混淆Pass

以前大家说LLVM的时候是指编译后端,现在的LLVM是一个集编译前端,优化器和编译后端于一体的一整套编译框架

我这段时间重点重新梳理了LLVM相关的知识,用的是最新的LLVM-11.1.0版本,同时在这个版本上移植了OLLVM

编译过程按照官方初学者文档操作即可

  • https://llvm.org/docs/GettingStarted.html#overview

整体一遍下来除了耗时间之外,就是耗硬盘空间了

以C语言为例,Clang会把源码编译成一种叫作LLVM IR的中间语言,然后LLVM项目里的llvm会调用多个LLVM Pass对LLVM IR进行处理,最后编译成目标平台的二进制固件

OLLVM全称Obfuscator-LLVM,它会向Pass管理器注册多个自定义Pass,每个Pass都是通过处理LLVM IR来进行混淆

LLVM-9.0移植OLLVM需要处理的错误很少,公开的文章都有描述,我这里就不过多啰嗦了

主要讲一下LLVM-11.1.0移植的时候额外需要做的操作

报错点一

../lib/Transforms/Obfuscation/Flattening.cpp:129:14: error: no matching constructor for initialization of 'llvm::LoadInst'
  load = new LoadInst(switchVar, "switchVar", loopEntry);
             ^        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

找到LoadInst的构造函数,可以看到新版本的LLVM多了一个参数Type *Ty

// llvm 4.0
class LoadInst : public UnaryInstruction {
    ...

public:
  LoadInst(Value *Ptr, const Twine &NameStr, Instruction *InsertBefore);
  
// llvm 11.1.0
class LoadInst : public UnaryInstruction {
    ...

public:
  LoadInst(Type *Ty, Value *Ptr, const Twine &NameStr, Instruction *InsertBefore);

通过搜索这个构造函数的使用例子,我们修改代码

load = new LoadInst(switchVar->getAllocatedType(), switchVar, "switchVar", loopEntry);

报错点二和报错点一是类似的

../lib/Transforms/Obfuscation/BogusControlFlow.cpp:557:19: error: no matching constructor for initialization of 'llvm::LoadInst'
        opX = new LoadInst ((Value *)x, "", (*i));
                  ^         ~~~~~~~~~~~~~~~~~~~~
../lib/Transforms/Obfuscation/BogusControlFlow.cpp:558:19: error: no matching constructor for initialization of 'llvm::LoadInst'
        opY = new LoadInst ((Value *)y, "", (*i));
                  ^         ~~~~~~~~~~~~~~~~~~~~

修改方式和第一处报错一致,参考构造函数添加第一个参数

// llvm-project-11.1.0.src/llvm/lib/IR/Instructions.cpp
LoadInst::LoadInst(Type *Ty, Value *Ptr, const Twine &Name, bool isVolatile,
                   Align Align, AtomicOrdering Order, SyncScope::ID SSID,
                   Instruction *InsertBef)
    : UnaryInstruction(Ty, Load, Ptr, InsertBef) {
  assert(Ty == cast<PointerType>(Ptr->getType())->getElementType());

opX = new LoadInst (cast<PointerType>(((Value *)x)->getType())->getElementType(), (Value *)x, "", (*i));
opY = new LoadInst (cast<PointerType>(((Value *)y)->getType())->getElementType(), (Value *)y, "", (*i));

报错点三,找了半天发现这函数被删了

../lib/Transforms/Obfuscation/Substitution.cpp:232:26: error: no member named 'CreateFNeg' in 'llvm::BinaryOperator'; did you mean 'CreateNeg'?
    op = BinaryOperator::CreateFNeg(bo->getOperand(0), "", bo);
         ~~~~~~~~~~~~~~~~^~~~~~~~~~
                         CreateNeg
../include/llvm/IR/InstrTypes.h:378:26: note: 'CreateNeg' declared here

把删了的代码加回去,这里的处理仅保证代码能运行,具体业务逻辑我还没分析不知道会不会有影响

// llvm-project-11.1.0.src/llvm/include/llvm/IR/InstrTypes.h
static BinaryOperator *CreateFNeg(Value *Op, const Twine &Name = "",
                                    Instruction *InsertBefore = nullptr);
static BinaryOperator *CreateFNeg(Value *Op, const Twine &Name,
                                    BasicBlock *InsertAtEnd);
                                    
// llvm-project-11.1.0.src/llvm/lib/IR/Instructions.cpp
BinaryOperator *BinaryOperator::CreateFNeg(Value *Op, const Twine &Name,
                                           Instruction *InsertBefore) {
  Value *zero = ConstantFP::getZeroValueForNegation(Op->getType());
  return new BinaryOperator(Instruction::FSub, zero, Op,
                            Op->getType(), Name, InsertBefore);
}

BinaryOperator *BinaryOperator::CreateFNeg(Value *Op, const Twine &Name,
                                           BasicBlock *InsertAtEnd) {
  Value *zero = ConstantFP::getZeroValueForNegation(Op->getType());
  return new BinaryOperator(Instruction::FSub, zero, Op,
                            Op->getType(), Name, InsertAtEnd);
}

我们需要再做几处修改修复LowerSwitch的问题,参考修改

  • https://github.com/amimo/goron/commit/e943a8a78325632df64988e05d66ad5fa0e0c6f6

给不熟悉OLLVM的同学感受一下这个混淆的强度

原始GRAPH

IMAGE

开启虚假控制流选项

IMAGE

开启控制流平坦化选项

IMAGE

我有一年参加阿里的移动安全挑战赛,一打开那个So,其中一个函数用了代码膨胀,汇编九万多行,都没法F5,我单步调试的时候一步一卡的

以至于后来我都没敢找逆向岗,怕一打开又是九万行汇编的函数

今儿个为了确认这个具体行数我又打开看了一眼,哎呀妈呀

代码膨胀以我目前的知识水平确实是没法处理,我不知道它是怎么膨胀的,自然没有优化的方案

这两天看到一个比较好玩的文章是TikTok的One Click RCE,我有空仔细写篇文章聊一聊

  • https://medium.com/@dPhoeniixx/tiktok-for-android-1-click-rce-240266e78105

大家在测试APP的时候看到什么TestActivity之类的,那都要仔细看看,因为有的开发人员为了测试方便,会在这种组件里面主动调用很多东西,而且代码根本就不review,方便了他们,也方便了咱们

现在安卓平台流行插件化开发,会有很多动态下发的插件,那些插件往往也是review的少,业务有时候随便打个包就放上去了,漏洞相当多,用Frida写个Hook脚本监控这些动态加载的行为或者源码层面修改一劳永逸

还有一个是利用CodeQL来挖漏洞,不过我只是把Demo跑起来了,具体的还没有开始看,这个月忙的焦虑了都

下个月估计就好了