首页
论坛
专栏
课程

[原创]ARM64 OLLVM反混淆(续)

2019-8-1 19:23 2870

[原创]ARM64 OLLVM反混淆(续)

2019-8-1 19:23
2870
感谢无名侠大佬提供的思路
感谢《利用符号执行去除控制流平坦化》提供的思路

在无名侠大佬 发表了《ARM64 OLLVM反混淆》之后,尝试跑了一下附件的脚本,发现有很大缺陷。的确这篇文章只是提供了大体思路,后续优化的还需要很多。

仔细研究了现有资料,分享分享自己的心得,抛砖引玉。


先简单的总结下反ollvm混淆的思路

1. 获取真实块、序言、retn块和无用块(也就是区别出真实块和垃圾块),通过静态分析提取真实块内在的某些规则

2. 确定真实块、序言和retn块的前后关系,利用符号执行或者Unicorn 模拟执行,记录一个真实块到下一个真实块的关联
3. 修复真实块之间的关联

实际在反ollvm遇到最大的难题是 如何得到所有的真实块

遇到的一些问题:
1. 现有的文章ollvm混淆的cfg图和实际遇到的差别很大?





    可以看到这两个生成的cfg图是完全不一样的,深究原因发现是因为在编译Debug时编译器并没有进行优化,而在编译Release的时候编译器优化指令后 "预分发器" 被优化掉了,生成的cfg就不同了,那么采用 《利用符号执行去除控制流平坦化》文章中寻找真实块是不可行的。

2. 采用《ARM64 OLLVM反混淆》文章中的方式寻找到的真实块存在无用块?
    文章中采用寻找真实块的方式是: 如果某个代码块的引用大于1,就可能是分发器,引用该分发器的代码块就可能为真实块。
那么思路其实是先找分发器再找真实块。那么这样就存在一个问题:如果子分发器后面再跟随子分发器,那么子分发器也被识别为了真实块?代码中不得已加入了特征码将这些子分发器移除。仔细研究最后发现这种方式还会漏掉某些真实块。那么这种方式其实也不是特别靠谱

那么还有没有其他方式去寻找真实块?
其实在研究真实块的逻辑关系时,debug版本给了很多可以参考的位置

第一次的思路: 《利用符号执行去除控制流平坦化》文章中寻找真实块是通过预分发器,那么既然预分发器已经被优化掉了,引用主分发器的是否就是真实块?
那么这个思路就是:先找到主分发器,在查看谁引用了主分发器。被引用次数最多的块一定就是主分发器

结果显而是失败的,原因在于在release下并不是所有的真实块都是引用主分发器,一些真实块居然引用了次分发器?

第二次的思路: 其实第一次思路并没有出现错误的识别,而是出现了漏识别,那么我们可以在第一次的思路上进行优化: 从主分发器开始遍历接下来引用的块,记录每一次遍历到的块,如果发现这个块在记录列表里,那么他上次遍历到的块是真实块。

类似二叉树,从上往下找,发现最后的节点的下一个节点是之前遍历到的,那么这个节点就是真实块。
这个思路能够将引用次分发器的真实块寻找到
get_relevant_nodes(supergraph, main_dispatcher_node, [])

def get_relevant_nodes(supergraph, node, founded_node):
    global relevant_nodes
    branch_nodes = list(supergraph.successors(node))

    founded_node.append(node)      
    for i in branch_nodes:
        if i not in founded_node:
            get_relevant_nodes(supergraph, i, founded_node)

大部分的真实块已经被识别到了,但是还是存在没有找到真实块,原因在于被release优化的部分存在一个逻辑关系,真实块的前继块有可能还是真实块,举一个例子



这三个都是真实块,原因在于release将两个puts优化为了一个puts。

第三次的思路:这一次只需要找到真实块前面的真实块,规则是真实块的前继块的后继如果只有一个,那么则也是真实块。有点绕,看代码
def get_relevant_nodes(supergraph, node, founded_node):
    global relevant_nodes
    branch_nodes = list(supergraph.successors(node))

    if len(branch_nodes) == 1 and branch_nodes[0] in founded_node:
        if node in relevant_nodes:
            for i in supergraph.predecessors(node):
                relevant_nodes.append(i)
        else:
            relevant_nodes.append(node)
    else:
        founded_node.append(node)      
        for i in branch_nodes:
            if i not in founded_node:
                get_relevant_nodes(supergraph, i, founded_node)

通过这三次优化 已经能够将使用 -fla 参数 被ollvm混淆的的真实块识别出来了,并且已经修复(这个是拿真实的样本测试的)

仍未解决的点:
1. 在使用 -bcf (虚假控制流) 参数之后 有一些块是死循环,而本来一个真实块被死循环打乱成为了两个真实块,例如


这个时候真实块1没有被识别出来,需要更进一步的优化。

这个没太深入研究了,可以直接将真实块1 手动的加入到真实块列表中

2. 在修复真实块的条件跳转逻辑时,有时候条件完全是相反的,这个后续还需要优化

参考


[公告]安全服务和外包项目请将项目需求发到看雪企服平台:https://qifu.kanxue.com

打赏 + 2.00
打赏次数 1 金额 + 2.00
收起 
赞赏  junkboy   +2.00 2019/08/01
最新回复 (14)
longpoxin 2019-8-2 11:40
2
0
Superoot 2019-8-2 15:23
3
0
先顶一个, 
alphc 2019-8-2 19:39
4
0
大佬牛逼!无名侠牛逼!
xia0 1 2019-8-4 13:11
5
0
那种死循环块正常不会进入的,模拟执行的时候应该也不影响。
无名侠 10 2019-8-4 16:20
6
0
xia0 那种死循环块正常不会进入的,模拟执行的时候应该也不影响。
会受到影响,但是死循环很好检测,剔除就行
xia0 1 2019-8-4 16:41
7
0
无名侠 会受到影响,但是死循环很好检测,剔除就行
没注意看,以为是ret块中的。这种特征明显的,剔除就行或者执行的时候判断下不进行真值处理应该也行。
mb_zozgbwqw 2019-8-5 01:39
8
0
牛逼啊
FraMeQ 2019-8-5 09:33
9
0
无名侠 会受到影响,但是死循环很好检测,剔除就行
主要是死循环把一个真实块分割成了两个真实块
无名侠 10 2019-8-5 09:46
10
0
FraMeQ 主要是死循环把一个真实块分割成了两个真实块
哦哦,可以改进一下算法,希望得到样本
FraMeQ 2019-8-5 10:31
11
0
这个问题你那个样本就存在,我测试arm64的时候使用的是你的样本,最后截图就是vdog样本

最后于 2019-8-5 10:31 被FraMeQ编辑 ,原因:
尐进 2019-8-5 15:21
12
0
支持 学习下
SFQin 2019-8-5 16:24
13
0
mark
AqCxBoM 2019-8-6 10:26
14
0
mark
Breathleas 2019-8-27 20:52
15
0
支持一下
游客
登录 | 注册 方可回帖
返回