首页
社区
课程
招聘
[求助][原创]利用编译器优化干掉虚假控制流
发表于: 2021-1-15 22:23 14947

[求助][原创]利用编译器优化干掉虚假控制流

2021-1-15 22:23
14947

首先,感谢极目楚天舒大佬提供的学习资料,本人通过断断续续的学习,懂了点llvm的皮毛,简单运用下,介绍一下这两个工具

1.强大的recdec反编译器,项目地址https://github.com/avast/retdec  类似于f5的反编译功能:

    capstone2llvmir与bin2llvmir,这个模块通过最流行的capstone反汇编引擎把汇编还原为llvmir,这是此次操作的开始

    llvmir2hll,这个模块把llvm ir还原为c代码,这里的效果与f5不相上下

2.llvm10的opt优化与dot画图,用于优化ir和画图看cfg流程


1.执行retdec-decompiler.py这个脚本

python retdec-decompiler.py libshell-super.2019.so   --select-decode-only --select-functions mmap --backend-no-opts  --backend-emit-cfg --stop-after  bin2llvmir

2.简单画个图看一下,观察这几个需要处理的点:x*(x-1)%2相关的混淆,gloabal全局变量有关的cmp和call i32 @__asm_it()这种引用了保存当前地址的metadata的函数


opt -dot-cfg libshell-super.2019.so.ll  
dot -Tpng -o $1.png .mmap.dot


%r1.1 = select i1 %2, i32 or (i32 ptrtoint (i32* @global_var_2f01 to i32), i32 -1665531904), i32 %r1.14.reg2mem.0   @global_var_2f01 = constant i32 6815836

把@global_var_2f01替换为i32 6815836,方便下一步常量传播与死代码消除等优化,关键pass代码如下

    bool runOnModule(Module &M) {
      for (Module::global_iterator gi = M.global_begin(), ge = M.global_end();gi != ge; ++gi) {
          // errs() << "global:" << gi->getName()<< '\n';
          GlobalVariable* gv = &(*gi);
          std::vector<Value*>obj;
          std::vector<Constant*>obj2;
          for (User *U : gv->users()) {
            if(U->getType()->isIntegerTy()){//这里只替换作为整数使用的global
              for (Use &U1 : U->operands()) {
                Value *v = U1.get();
                obj.push_back(v);
                errs() << "valueV:" <<*v<< '\n';
                if(cast<GlobalVariable>(v)){
                  Constant *initializer = gv->getInitializer();
                  errs() << "valueC:" <<*initializer<< '\n';
                  obj2.push_back(initializer);
                }
              }
            }
          }
          for(int i=0;i<obj2.size();i++)
          {
            errs() << "obj:" <<*obj[i]<< '\n';
            errs() << "obj2:" <<*obj2[i]<< '\n'; 
            if(dyn_cast<ConstantInt>(obj2[i])){
              obj[i]->replaceAllUsesWith(obj2[i]);
            }
          }          
      }
      return false;
    }


 %4 = call i32 @__asm_it(), !insn.addr !5      !5 = !{i64 127874},把 %4 替换为127874

这里替换之后,不知道为何call语句还没有被清除掉,还需要再循环一次清除所有call i32 @__asm_it()的语句,

bool runOnFunction(Function &F){
     Function *tmp=&F;
     for (Function::iterator bb = tmp->begin(); bb != tmp->end(); ++bb) {
       for (BasicBlock::iterator inst = bb->begin(); inst != bb->end(); ++inst) {
         if(isa<CallInst>(inst)){            
           if(inst->getOperand(0)->getName()=="__asm_it"){
             CallInst *instt=cast<CallInst>(inst);
             errs() << "getcall:" << *instt << '\n';
             MDNode *N = inst->getMetadata("insn.addr");
             Metadata *n1;
             n1=&*(N->getOperand(0));
             Value *v= cast< ValueAsMetadata >(n1)->getValue();
             ConstantInt *a=cast<ConstantInt>(v);
             TruncInst *t=new TruncInst(a,inst->getType(),"",instt);
             errs() << "t:" << *t<< '\n';
             instt->replaceAllUsesWith(t);
             // instt->eraseFromParent();//这里不知道为啥CallInst没有被清除掉
          }
         }
       }
     }
     Function *tmp2=&*tmp;
   for (auto &B : *tmp2) {
       auto It = B.begin();
       // we modify B, so we must reevaluate end()
       while(It != B.end()) {
           auto &I = *It;
           if(isa<CallInst>(&I)) {            
             if(I.getOperand(0)->getName()=="__asm_it")
              errs() << "getcall:" << I << '\n';
               // we continue with the next element
               It = I.eraseFromParent();
           } else {
               ++It;
           }
       }
    }
     return false;
   }


看ollvm的源码可知,bcf主要有两种不可到达分支,一种是工具确定不了值(其实值是确定的)的全局变量,好像叫不透明谓词,这里我用的笨方法,把它们一个一个手工添加上去;

另一种是类似(x*(x-1))%2这种恒为0控制的不可到达分支,原文这么写的

For this, we declare two global values: x and y, and replace the FCMP_TRUE
predicate with (y < 10 || x * (x + 1) % 2 == 0) (this could be improved, as the global
values give a hint on where are the opaque predicates)//    values give a hint on where are the opaque predicates


这里我们通过pass把类似(x*(x-1))%2或者(x*(x-1))&1这种全部优化为0,具体步骤如下

1.手动修复未识别的global数据,通过opt进行sccp+ipsccp常量传播优化,simplifycfg+adcecfg简化和死代码消除优化,-mem2reg促进内存引用为寄存器引用


opt -sccp -ipsccp -simplifycfg -adce libshell-super.2019.so.ll -S

2.-instcombine指令优化,这之后rem取余会被等价优化成and 1,优化后如下

%1 = add i32 %0, -1, !insn.addr !1
%2 = mul i32 %1, %0, !insn.addr !2
%3 = and i32 %2, 1//这里本来是urem指令

3.最关键的,写一个pass识别这种三句模式(x*(x-1))&1的ir,直接替换为0,然后adce+simplifycfg优化掉不可达分支


opt -load libdbcfPass.so -dbcf d2dbcf.ll -S

关键代码如下

bool runOnFunction(Function &F) override {
       Function *tmp=&F;
       //DEBUG_WITH_TYPE(DEBUG_TYPE, errs() << "charge function:" << tmp->getName() << "\n");
       errs() << "Function:" << F.getName() << '\n';
    for (Function::iterator bb = tmp->begin(); bb != tmp->end(); ++bb) {
           //errs() << "block:" << *bb << '\n';
       for (BasicBlock::iterator inst = bb->begin(); inst != bb->end(); ++inst) {
           // errs() << "inst:" << inst->getOpcode() << '\n';
           // errs() << "inst:" << *inst << '\n';
       //if (inst->isBinaryOp()){
           // errs() << "inst:" << inst->getOpcode() << '\n';
           // errs() << "inst:" << *inst << '\n';
           ConstantInt *a=(ConstantInt *)ConstantInt::get(inst->getType(),-1);
           ConstantInt *b=(ConstantInt *)ConstantInt::get(inst->getType(),0);
           if(inst->getOpcode() == 17){
               errs() << "inst:" << *inst << '\n';

               BasicBlock::iterator instb=(--inst);
               ++inst;
               // BasicBlock::iterator insta=(++inst);
               // --inst;
               if(instb->getOpcode() == 13){
                   errs() << "inst:" << *instb << '\n';
                   if(instb->getOperand(1) == a){
                       if(inst->getOperand(1) == instb->getOperand(0)){
                           // errs() << "inst:" << *insta << '\n';
                           BinaryOperator *instt=cast<BinaryOperator>(inst);
                           BinaryOperator *op=BinaryOperator::Create(Instruction::And,b,b,"",instt);
                           
                           instt->replaceAllUsesWith(op);
                           errs() << "inst:" << *instt << '\n';
                       }
                   }
               }
           }
           
       //}
       }
 
       }
     return false;
   }

优化过后,所有bcf已经消除了,下面可以继续编译成arm,用ida打开看或者直接反编译为c,可以用retdec-llvmir2hll这个工具,此时就能发现虚假控制流已经没了,下一步可以着手处理其他混淆


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2021-3-4 18:39 被挤蹭菌衣编辑 ,原因:
收藏
免费 6
支持
分享
最新回复 (9)
雪    币: 2466
活跃值: (4550)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
好文,支持一下
2021-1-16 00:22
0
雪    币: 6573
活跃值: (3873)
能力值: (RANK:200 )
在线值:
发帖
回帖
粉丝
3
好文
2021-1-18 10:53
0
雪    币: 3355
活跃值: (14008)
能力值: ( LV9,RANK:230 )
在线值:
发帖
回帖
粉丝
4
收藏收藏
2021-1-18 11:16
0
雪    币: 2335
活跃值: (1319)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
5
试了下,跑简单函数还行,跑复杂点的问题还是有点多。
2021-1-22 17:08
0
雪    币: 5235
活跃值: (3260)
能力值: ( LV10,RANK:175 )
在线值:
发帖
回帖
粉丝
6
只要能转ir,后面就可以观察混淆特征自己写pass
2021-1-22 19:24
0
雪    币: 18
活跃值: (941)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
大佬的学习资料是什么呀
2021-6-28 10:50
0
雪    币: 5235
活跃值: (3260)
能力值: ( LV10,RANK:175 )
在线值:
发帖
回帖
粉丝
8
bullyxy 大佬的学习资料是什么呀

先看llvm官网  然后啃源码这个资料不多  网上的只能入个门

最后于 2021-6-29 17:30 被挤蹭菌衣编辑 ,原因:
2021-6-29 17:29
0
雪    币: 14
活跃值: (271)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
求个样本
2022-8-11 01:42
0
雪    币: 0
活跃值: (532)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
retdec bug太多了,遇到jni的直接都识别成return了
2022-8-16 10:51
0
游客
登录 | 注册 方可回帖
返回
//