首页
社区
课程
招聘
控制流平坦化的实现
发表于: 2016-4-8 07:08 18865

控制流平坦化的实现

2016-4-8 07:08
18865

要想了解什么是控制流平坦化(control flow flatten),可以找论文"obfuscating c++ programs via control flow flattening"了解。基本思想是让所有的基本块都有共同的前驱块,而该前驱块进行基本块的分发,分发用switch语句,依赖于switch变量进行分发。先为每个基本块进行值编号,每个基本块就是一个case块,如果基本块有后继块,就替换每个基本块的后继块,新的后继块更新switch变量为后继块的值,然后跳转到switch开始分发处,初始switch变量为入口entry块的值。
  一、控制流平坦化
  假设有以下代码:
  for(int i=0;i<5;i++){
    if(i<2)
       printf("i<2\n");
    else{
       switch(i){
         case 2:  printf("i==2\n");break;
         case 3:  printf("i==3\n");break;
         default: printf("i==4\n");break;
       }
    }
  }
  其控制流程图可能为:
  block0:
    int i=0;
    goto block1;
  block1:
    if(i<5) goto block2 else goto block9;
  block2:
    if(i<2) goto block3 else goto block4;
  block3:
    printf("i<2\n");
    goto block8;
  block4:
    switch(i){
     case 2:
       goto block5;
     case 3:
       goto block6;
     default:
       goto block7;
    }
  block5:
    printf("i==2\n");
    goto block8;
  block6:
    printf("i==3\n");
    goto block8;
  block7:
    printf("i==4\n");
    goto block8;
  block8:
    i++;
    goto block1;
  block9:
  
    block0为entry块,后继块为block1,block1的后继块为block2、block9,block2的后继块为block3、block4,block3的后继块为block8,block4的后继块为block5、block6、block7,block5的后继块为block8,block6的后继块为block8,block7的后继块为block8,block8的后继块为block1,block9为exit块。现在为每个块编号,假设block0至block9编号分别为1、2、4、7、11、16、22、29、37、46,进行控制流平坦化,block0为entry块,block9为exit块,可能生成的代码代码如下:
  int v=1;
  start:
    switch(v){
       case 1:
           int i=0;
           v=2;
           goto start;

       case 2:
           if(i<5){
             v=4;
             goto start;
           }else{
             v=46;
             goto start;
           };

       case 4:
           if(i<2){
              v=7;
              goto start;
           }else{
              v=11;
              goto start;
           };

       case 7:
           printf("i<2\n");
           v=37;
           goto start;

       case 11:
           switch(i){
             case 2:
               v=16;
               goto start;
             case 3:
               v=22;
               goto start;
             default:
               v=29;
               goto start;
           };
      
       case 16:
           printf("i==2\n");
           v=37;
           goto start;

       case 22:
           printf("i==3\n");
           v=37;
           goto start;

       case 29:
           printf("i==4\n");
           v=37;
           goto start;

       case 37:
           i++;
           v=2;
           goto start;

       case 46:
    }
  c++的异常处理没有考虑,还有流程图如果有多个exit块有可能有问题。

  二、常量展开
    虽然已经进行了平坦化,但是在更新switch变量时暴露了后继块的case值,因此有必要对这条更新语句进行常量展开。常量展开,可以看成用一个数去生成另一个数的过程。假设要进行展开的常量为a,另一常量为b,定义b→a表示用b生成a,可以这样进行常量展开:
   1、基本运算
     主要是异或、加和减运算:
     //用b生成a
     v=b;
     //v=v^(b^a);
     //v=v-(b-a);
     v=v+(a-b);
   2、预运算
     在进行生成时,先对数值进行一系列的运算:
     v=b;
      //x=b*random1
      v=v*random1;
      //y=x&random2
      v=v&random2;
     //z=y|random3
     v=v|random3;
     //z=((b*random1)&random2)|random3
     //v=v^(a^z);
    //v=v-(z-a);
    v=v+(a-((b*random1)&random2)|random3);
   3、多次迭代
    要想从b生成a,可以先生成中间值c,然后再生成a,即从b→a变成b→c→…→a。可能生成的代码:
    v=b;
    //这里可以先进行预运算
    //v=v^(c^b);
    //v=v-(b-c);
    v=v+(c-b);
    //v=v^(a^c);
    //v=v-(c-a);
    v=v+(a-c);

   对更新分发switch变量进行常量展开,即用当前块的case值去生成后继块的case值,注意switch语句,后继case块还可以用当前switch的case值来生成后继块的case值,可能生成的代码(没有预运算,2次迭代):
  int v=1;
  start:
    switch(v){
       case 1:
         int i=0;
         //v=1+5(6)
         v=v+5;
         //v=6^4(2)
         v=v^4;
         goto start;

       case 2:
         if(i<5){
            //v=2^10(8)
            v=v^10;
            //v=8-4(4)
            v=v-4;
             goto start;
           }else{
             //v=2+7(9)
             v=v+7;
            //v=9+37(46)
             v=v+37;
             goto start;
           };

       case 4:
           if(i<2){
              //v=4+1(5)
              v=v+1;
              //v=5^2(7)
              v=v^2;
              goto start;
           }else{
              //v=4^71(67)
              v=v^71;
              //v=67^72(11)
              v=v^72;
              goto start;
           };

       case 7:
           printf("i<2\n");
           //v=7-3(4)
           v=v-3;
           //v=4+33(37)
           v=v+33;
           goto start;

       case 11:
           switch(i){
             //这里使用i
             case 2:
               //v=2+3(5)
               v=i+3;
               //v=5^21(16)
               v=v^21;
               goto start;
             case 3:
               //v=3-1(2)
               v=i-1;
               //v=2+20(22)
               v=v+20;
               goto start;
             default:
               //v=11^3(8)
               v=v^3;
               //v=8^21(29)
               v=v^21;
               goto start;
           };
      
       case 16:
           printf("i==2\n");
           //v=16+4(20)
           v=v+4;
           //v=20+17(37)
           v=v+17;
           goto start;

       case 22:
           printf("i==3\n");
           //v=22^3(21)
           v=v^3;
           //v=21+16(37)
           v=v^16;
           goto start;

       case 29:
           printf("i==4\n");
           //v=29-5(24)
           v=v-5;
           //v=24^61(37)
           v=v^61;
           goto start;

       case 37:
           i++;
           //v=37+8(45)
           //v=v+8;
           //v=45^47(2)
           //v=v^47;
           goto start;

       case 46:
    }

  三、其它
  这里的方法是实验性的。
  1、隐藏case值
   不仅要隐藏后继块的case值,还必须对当前块的case值进行隐藏,即从switch(x)变成switch(f(x)),而f(x)可以是hash、在区间[case最小值,case最大值]有唯一值的数学函数、rsa、离散对数、椭圆曲线离散对数等,当然switch语句需要转化为if语句。
  2、多重分支
    在常量展开b→a时,首先用switch语句生成多个中间值展开成a,然后随机选择一个中间值,用b来展开。
  3、生成函数
    在常量展开时生成的都是一个变量和一个常量进行计算的二元操作符,可以转化为函数。首先记录当前计算前变量的值和计算后的值,生成的函数参数个数不定,每个参数都是当前变量,在函数内部进行常量展开,即用多个当前变量生成计算后的值。

  四、实现
    基于clang/llvm或者gcc实现。这两个编译器都支持插件。clang/llvm主要是用于ios平台,已经存在的控制流平坦化的实现:
      Obfuscator-LLVM: https://github.com/obfuscator-llvm/obfuscator
      LO!LLVM Obfuscator:http://klondike.es/programas/llvm_obf/llvm_v1.tar.xz
      LLVM-Obfuscator:https://github.com/lawliet89/LLVM-Obfuscator
  gcc主要用于android平台,gcc的实现相对比较困难,相关的资料很少。


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

收藏
免费 3
支持
分享
最新回复 (10)
雪    币: 576
活跃值: (1163)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
2
好东西,果断收藏
2016-4-8 15:33
0
雪    币: 11
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
上一个android的。
https://github.com/Fuzion24/AndroidObfuscation-NDK
2016-4-11 23:26
0
雪    币: 3425
活跃值: (1479)
能力值: ( LV9,RANK:320 )
在线值:
发帖
回帖
粉丝
4
上面那个实现是obfuscator-llvm,我还以为是新的实现呢
2016-4-13 23:17
0
雪    币: 3425
活跃值: (1479)
能力值: ( LV9,RANK:320 )
在线值:
发帖
回帖
粉丝
5
以上实现都是基于LLVM IR的,有一个基于Clang的AST的实现:

https://sourceforge.net/projects/acob/
2016-4-17 23:39
0
雪    币: 11
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
菜鸟一个,两者有什么区别,能详细说明一下吗?
2016-4-23 06:46
0
雪    币: 78
活跃值: (1875)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
顶,学习
2018-3-20 18:22
0
雪    币: 1705
活跃值: (676)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
基本塊標號的時候,用代碼怎麼標啊
最后于 2019-9-22 23:38 被弱冠甕卿还仓编辑 ,原因:
2019-9-22 22:59
0
雪    币: 1705
活跃值: (676)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
1
最后于 2019-9-22 23:12 被弱冠甕卿还仓编辑 ,原因:
2019-9-22 23:05
0
雪    币: 1705
活跃值: (676)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
这算是vmp么
2019-11-4 13:35
0
雪    币: 802
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
727
11
牛逼
2021-7-7 10:04
0
游客
登录 | 注册 方可回帖
返回
//