首页
论坛
课程
招聘
[分享]ollvm反混淆学习
2021-9-22 00:10 20354

[分享]ollvm反混淆学习

2021-9-22 00:10
20354

看了@无名侠大佬发的一篇关于使用unicorn模拟执行还原ollvm的贴子受到了很大的启发, 自己也基于这个思路做了些样本学习,下面来探讨以下。

ollvm原理

Ollvm大致可分为 bcf(虚假块), fla(控制流展开), sub(指令膨胀), Split(基本块分割)
bcf:
克隆一个真实块,并随机替换其中的一些指令,然后用一个永远为真的条件建立一个分支。克隆后的块是不会被执行的。
Fla:
将所有的真实块使用一个switch case结构包裹起来,每个真实块执行完毕后都会重新赋值switch var,对于有分支的块会使用select指令,并跳转到switch起始代码块(分发器)上,根据switch var来执行下一个真实块。
Sub:
指令膨胀,将一条运算指令,替换为多条等价的运算指令。
Split:
利用随机数产生分割点,将一个基本块分割为两个,并使用绝对跳转连接起来。
关于ollvm具体的实现,可参考源码。

还原思路:

网上有很多还原ollvm的脚本,但是只能还原特征很明显的ollvm,或者说只是debug版的ollvm。在debug版中ollvm的特征非常明显,一个分发器,和引用了这个分发器的真实块。但经过编译器优化后,分发器可能会变成多个,基本块会合并造成虚假块也可能会和真实块合并,等等。
现实情况是,你基本上碰不到简单的ollvm,所以那些东西个人感觉意义不是很大,还是需要靠自己。
谈下还原思路
Bcf:
Bcf块是执行不到的块,所以说当使用unicorn 跑过一遍函数后,其中没有执行到的块肯定有包括bcf块,我们只需要将它挑出来标记下就好。
但函数中可能存在分支,只跑一遍函数是无法覆盖到所有分支的,所以要想办法找到函数的所有分支。一开始采用的是无名侠大佬的方法,当碰到csel指令时人工干预让其覆盖所有分支,但整个函数经常陷入死循环,分析过后发现虚假块的跳转也有可能使用csel指令。后来想到了在二进制漏洞挖掘中的思路fuzz(模糊测试),即变异函数的参数传递给函数,来覆盖更多的分支。这样做也不能说能够找到函数的所有分支。影响一个函数的分支执行大概有三种情况,
参数,全局变量,内部函数调用的返回值。后两种情况的话留意下模糊执行的trace应该能找到些蛛丝马迹,可能会比较麻烦。

 

Fla
这个环节会产生控制流块,我们只需要将这些块挑出来标记,找出所有的真实块,并通过模拟执行还原真实块之间的关系就好。
控制流块的剔除采用了无名侠大佬对基本块签名的方法。

 

Sub:
指令膨胀的还原,使用llvm的pass优化效果还可以,但目前一些ir翻译工具对arm64的支持不怎么样。
Split:
基本块分割更多是用来增加bcf和fla效果的。
总结整体思路:
1 利用模拟执行和fuzz技术,找出bcf块并剔除。
2 使用基本块签名剔除控制流块。
3 将剩余的块标记为真实块,并使用模拟执行找出对应关系。
4 根据对应关系,重构cfg。

实战:

自己编译的一个样本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
void HexDump(char *buf,int len,int addr)
__attribute((__annotate__(("split"))))
__attribute((__annotate__(("fla"))))
__attribute((__annotate__(("bcf"))))
{
    int i,j,k;
    char binstr[80];
 
    for (i=0;i<len;i++) {
        if (0==(i%16)) {
            sprintf(binstr,"%08x -",i+addr);
            sprintf(binstr,"%s %02x",binstr,(unsigned char)buf[i]);
        } else if (15==(i%16)) {
            sprintf(binstr,"%s %02x",binstr,(unsigned char)buf[i]);
            sprintf(binstr,"%s  ",binstr);
            for (j=i-15;j<=i;j++) {
                sprintf(binstr,"%s%c",binstr,('!'<buf[j]&&buf[j]<='~')?buf[j]:'.');
            }
            printf("%s\n",binstr);
        } else {
            sprintf(binstr,"%s %02x",binstr,(unsigned char)buf[i]);
        }
    }
    if (0!=(i%16)) {
        k=16-(i%16);
        for (j=0;j<k;j++) {
            sprintf(binstr,"%s   ",binstr);
        }
        sprintf(binstr,"%s  ",binstr);
        k=16-k;
        for (j=i-k;j<i;j++) {
            sprintf(binstr,"%s%c",binstr,('!'<buf[j]&&buf[j]<='~')?buf[j]:'.');
        }
        printf("%s\n",binstr);
    }
}

图片描述

 

先找出所有的基本块(以跳转指令结尾的块)
这里需要注意下由于编译器优化的关系,基本块会合并,有些基本块并不是以跳转指令结尾,就如这样
图片描述
这些情况,是因为两个基本块同时引用了这个块,所以需要将这个块拷贝一份,并将另一个块的引用修改为新拷贝的块,不然还原关系的时候会乱掉。
图片描述
我这里占用了main函数的空间。
找出所有的基本块后开始fuzz执行,并统计所有被执行到的块。这里fuzz采用了,先使用peach编写规则生成参数的语料库保存到文件中,然后读取文件中的内容当作参数传递给函数, 当然如果不关心函数的其他分支,fuzz的步骤感觉可以跳过,例如一些纯算法函数。

 

经过几十轮fuzz后,共统计到如下被执行了的块。
图片描述
这些块中肯定是包含了控制流块的,所以现在用签名法来过滤掉控制流块。
图片描述
过滤后还剩下169个块,这些块就是真实块了,为了保险起见我还人工过滤了一下,基本没什么问题。
接下来开始模拟执行找出他们之间的对应关系了,当碰到一个真实块时记录下它上一个执行的真实块,并保存起来。
传递给函数的参数也需要使用上面fuzz使用的参数,这样才能执行到每一个块。
模拟执行后,基本块之间的关系如下:
图片描述
如果数组中只有一个基本块的话,那么他们是一个顺序关系,
如果有两个的话则是分支关系, 如果2个以上则有三种情况
1 漏了真实块, 2 该块不是一个真实块, 3 该块是一个分支共用块。
经排查这里是第三种情况,如下:
图片描述
图片描述
9e8这个块被两个基本块引用,并两个基本块都是一个分支块,所以会出现这种情况。 具分析其中一个块的分支对应的是bcf,不会被执行到,所以数组中是3个基本块而不是4个。对于这个情况也需要将9e8这个块copy一份,将两个基本块中的其中一个引用修改为copy后的块。
图片描述
图片描述
修改完毕后,记得将copy块添加到真实块中,并重试。
图片描述
可以看到问题解决了。
找出对应关系后需要接着还原分支关系,当条件为真时跳到那个块,为假时跳转到那个块。 因为每个分支块都会有一条cmp 和csel指令, 如果找到的分支块中没有这两条指令,那么就是漏了真实块。
图片描述
还原他们的关系,只需要在模拟执行时,记录cmp的返回值,和返回值对应的真实块即可,这里会比较麻烦,需要手动找到cmp的地址, 左右值, 和比较关系。
图片描述
模拟分支块的关系如下:
图片描述
我这里根据记录的条件,翻译成了汇编。
最后根据这些真实块之间的关系patch即可, 注意在patch分支块时需要注意csel和cmp的关系,像这种
图片描述
如果我们如果在基本块的最后patch b.ne xxx b xx, 那么标志位就会被上面的一个cmp干扰,所以需要将上面 一个cmp也patch掉。

 

好了现在大功告成,直接来看伪代码。

 

图片描述
把伪代码拿出来编译测试:
图片描述

写在最后:

目前还在测试大概还原了5 6个样本,可能还有一些细节方面未考虑到,所以发出来希望听下大佬的意见。
之所以没贴代码出来是因为代码太杂了和 篇幅太大了,实在是不太方便,有需要的话可以参考无名侠大佬的帖子和源码,我都是基于他之上的。
如果大家感觉以上有不妥或者不理解的地方,欢迎和我一起探讨一起学习。


第五届安全开发者峰会(SDC 2021)10月23日上海召开!限时2.5折门票(含自助午餐1份)

收藏
点赞9
打赏
分享
最新回复 (6)
雪    币: 3
活跃值: 活跃值 (645)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
lookzo 活跃值 2021-9-23 09:00
2
0
支持
雪    币: 8977
活跃值: 活跃值 (5042)
能力值: ( LV13,RANK:370 )
在线值:
发帖
回帖
粉丝
TkBinary 活跃值 5 2021-9-23 09:03
3
1
好文章,咋没人顶....
雪    币: 132
活跃值: 活跃值 (573)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
hackbs 活跃值 2021-9-23 10:02
4
1
哈哈
雪    币: 10
活跃值: 活跃值 (254)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
bullyxy 活跃值 2021-9-23 19:06
5
0
大佬康康代码
雪    币: 263
活跃值: 活跃值 (619)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wx_0xC05StackOver 活跃值 2021-9-24 14:25
6
0
好文章!
雪    币: 155
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_hgrbqfun 活跃值 2021-9-24 15:52
7
0
大佬啊
游客
登录 | 注册 方可回帖
返回