首页
社区
课程
招聘
[原创]ARM64 OLLVM反混淆
发表于: 2019-6-29 16:19 49163

[原创]ARM64 OLLVM反混淆

2019-6-29 16:19
49163

基于Unicorn 的ARM64 OLLVM反混淆

0x0

最近在研究娜迦加固,娜迦加固的SO文件被ollvm混淆,转而研究ollvm反混淆。网上的很多关于ollvm反混淆的资料都基于符号执行。符号执行主要用框架是Miasm和Angr。Miasm和Angr对arm64指令集支持都不是很好,于是采用模拟执行的方式来寻找两个真实块的关系。本文仅提供大体思路,如果有更好的方法欢迎一起讨论,细节优化还要根据具体情况分析

 

我把ollvm混淆后的代码块分为两类:虚假块和真实块。虚假块仅包含ollvm流程控制指令,不含任何真实指令。只要含有真实指令的代码块就是真实块。

 

反ollvm混淆的关键是找出两个真实块之间的关系。

 

两个真实块之间的关系有两种:1、顺序;2、分支

graph TD
A[真实块1] -->|A1| B(真实块2)
B --> C{csel 指令条件判断}
C -->|True| E[真实块3]
C -->|False| F[真实块4]

使用符号执行、模拟执行找出真实块之间的关系之后,patch相应的跳转即可实现反混淆。

0x1 反汇编、代码块、CFG分析

要获得目标函数的代码块和CFG,首先要反汇编。反汇编使用Capstone 反汇编引擎。

offset = 0x70438 #function start
end = 0x7170C    #function end

bin1 = open('libvdog.so','rb').read()
md = Cs(CS_ARCH_ARM64,CS_MODE_ARM)
md.detail = True #enable detail analyise

list_blocks = {}
block_item = {}
processors = {}
dead_loop = []
real_blocks = []
csel_list_cond = {}


isNew = True
insStr = ''
for i in md.disasm(bin1[offset:end],offset):
    insStr += "0x%x:\t%s\t%s\n" %(i.address, i.mnemonic, i.op_str) 
    if isNew:
        isNew = False
        block_item = {}
        block_item["saddress"] = i.address
        block_item['capstone'] = []

    block_item['capstone'].append(i)
    block_item['flag'] = False


    if len(i.groups) > 0 or i.mnemonic == 'ret':
        isNew = True
        block_item["eaddress"] = i.address
        block_item['ins'] = insStr
        insStr = ''
        for op in i.operands:
            if op.type == ARM64_OP_IMM:
                block_item["naddress"] = op.value.imm

                if op.value.imm == i.address:
                    print "dead loop:%x" % i.address
                    dead_loop.append(i.address)

                if not processors.has_key(op.value.imm):
                    processors[op.value.imm] = 1
                else:
                    processors[op.value.imm] += 1
        if not block_item.has_key("naddress"):
            block_item['naddress'] = None
        list_blocks[block_item["saddress"]] = block_item

#delete dead loop
for dead in dead_loop:
    if processors.has_key(dead):
        del(processors[dead])

娜迦的样本中存在死循环路径,必须删掉。

0x2 识别真实块

真实块结束后会重新跳转入ollvm分发流程的代码块,该代码块一定有很多引用,所以反汇编代码块的时候记录后继节点的引用数目(见前一段代码)

                if not processors.has_key(op.value.imm):
                    processors[op.value.imm] = 1
                else:
                    processors[op.value.imm] += 1

反汇编和代码块识别完成后,如果某个代码块的引用大于1,就可能是分发器,引用该分发器的代码块就可能为真实块,筛选代码如下:

for b in list_blocks:
    if processors.has_key(list_blocks[b]['naddress']):
        if processors[list_blocks[b]['naddress']] > 1:
            real_blocks.append(list_blocks[b]['saddress'])

真实情况下,虚假代码块也会引用分发器,所以还要进一步筛选。

ssign = {u'b2',
 u'cmp11b.ne2',
 u'cmp11mov11b.ne2',
 u'movz12movk12b2',
 u'movz12movk12cmp11b.ne2',
 u'movz12movk12cmp11mov11b.ne2',
 'movz12movk12cmp11mov11movz12movk12movz12movk12b.ne2',
 u'movz12movk12cmp11movz12movk12b.eq2',
 'movz12movk12cmp11movz12movk12b.ne2',
 'movz12movk12movz12movk12b2',
 'movz12movk12cmp11movz12movk12movz12movk12movz12movk12b.eq2',
 'movz12movk12movz12movk12cmp11b.eq2',
 'movz12movk12movz12movk12cmp11movz12movk12b.eq2',
 'movz12movk12cmp11b.eq2',
         'ldr13b2',
         'mov11movz12movk12cmp11movz12movk12b.eq2'
 }
ssign2 = set()
def  is_real_blocks(ins):
    sign = get_code_sign(ins)
    if sign in ssign:
        return False
    if sign.endswith('movk12movz12movk12b.ne2'):
        return False

    for insn in item['capstone']:
        #print insn.mnemonic
        if insn.mnemonic not in ['movz','movk','cmp','b.eq','b.ne']:
            return True
    ssign2.add(sign)
    return False


fake_blocks = []
for i in real_blocks:
    item = list_blocks[i]
    if not is_real_blocks(item):
        print '## fake block ###'
        print item['ins']
        fake_blocks.append(i)

采用特征码的方法识别ollvm的虚假块。采用的特征码与寄存器、常量无关,只与指令类型、操作数类型有关。计算一个代码块的特征后进行匹配即可筛掉大量虚假代码块。

 

如果虚假块没有识别出来,那么可能导致后文的死循环,也是一种筛选思路。

 

按照定义,含有ret的代码块也属于真实块,但是前文真实块的识别基于引用 大量代码块的共同引用代码块,ret指令显然不会引用任何代码块,故单独处理。

for i in list_blocks:
    if list_blocks[i]['ins'].find('ret') != -1:
        print 'ret block:%x' % i
        real_blocks.append(i)

使用 Unicorn 模拟执行

初始化虚拟机

关于Unicorn的教程请自行Google,这里仅讨论Unicorn在寻找路径方面的应用。

mu = Uc(UC_ARCH_ARM64, UC_MODE_ARM)
#init stack
mu.mem_map(0x80000000,0x10000 * 8)

mu.mem_map(0, 4 * 1024 * 1024)
mu.mem_write(0,bin1)
mu.reg_write(UC_ARM64_REG_SP, 0x80000000 + 0x10000 * 6)
mu.hook_add(UC_HOOK_CODE, hook_code)
mu.hook_add(UC_HOOK_MEM_UNMAPPED,hook_mem_access)

Unicorn 基于qemu的模拟,所以必须给代码提供可运行的环境。最大的问题就是要关心内存处理。
我们只想得到ollvm路径,而不是真实代码块的运行结果,因此要尽可能屏蔽非ollvm的内存操作。具体屏蔽方法稍后介绍。上面这段代码初始化Unicorn的虚拟CPU,并映射程序代码内存以及栈空间,最后调用hook_add设置UC_HOOK_CODE和UC_HOOK_MEM_UNMAPPED的事件回调。UC_HOOK_CODE回调会在每条指令执行前被调用,UC_HOOK_MEM_UNMAPPED会在内存异常的时候调用。

启动虚拟机

启动虚拟机的函数叫find_path,顾名思义,用于寻找真实块的下一个代码块。branch为分支控制。
如果branch = 1,则虚拟机在遇到csel指令的时候会走csel条件分支,否则走csel条件相反的分支。至于如何控制虚拟机内部指令的执行,稍后介绍。


[注意]看雪招聘,专注安全领域的专业人才平台!

最后于 2019-6-29 16:20 被无名侠编辑 ,原因:
上传的附件:
收藏
免费 46
支持
分享
最新回复 (53)
雪    币: 1014
活跃值: (623)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
膜。
2019-6-29 16:37
0
雪    币: 6737
活跃值: (796)
能力值: ( LV13,RANK:393 )
在线值:
发帖
回帖
粉丝
3
666
2019-6-29 16:51
0
雪    币: 977
活跃值: (435)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
4
66666666666666666666666666
2019-6-29 17:38
0
雪    币: 1769
活跃值: (2387)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
顶,加油
2019-6-29 17:41
0
雪    币: 16678
活跃值: (6976)
能力值: ( LV13,RANK:923 )
在线值:
发帖
回帖
粉丝
6
侠哥还是这么牛,自从看了侠哥的视频(尤其是下楼给我买冰糕的那集)从此妈妈再也不用担心我的学习了。
2019-6-29 18:00
1
雪    币: 7000
活跃值: (9519)
能力值: ( LV17,RANK:797 )
在线值:
发帖
回帖
粉丝
7
大帅锅 侠哥还是这么牛,自从看了侠哥的视频(尤其是下楼给我买冰糕的那集)从此妈妈再也不用担心我的学习了。
什么鬼东西????
2019-6-29 20:41
0
雪    币: 2708
活跃值: (1718)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
8
收藏了
2019-6-30 01:13
0
雪    币: 1557
活跃值: (4079)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
谢谢楼主
2019-6-30 12:11
0
雪    币: 3549
活跃值: (941)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
10
正想研究下,谢谢楼主了
2019-6-30 20:17
0
雪    币: 916
活跃值: (3444)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
11
无名侠 什么鬼东西????
雪糕给你买好了,哥哥
2019-6-30 20:25
1
雪    币: 159
活跃值: (205)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
12
这混淆的代码貌似没加垃圾指令与无用分支,ollvm一共三种混淆方式,互相配合,再加大强度,其实还是很难还原的
2019-6-30 21:35
0
雪    币: 292
活跃值: (153)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
13
为什么要复读。。。。。专升本都可以。。。。复读浪费时间。。
2019-7-1 01:31
0
雪    币: 36
活跃值: (1151)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
14
66666666666666666666
2019-7-1 09:42
0
雪    币: 3907
活跃值: (5812)
能力值: ( LV12,RANK:200 )
在线值:
发帖
回帖
粉丝
15
无名侠 什么鬼东西????
雪糕给你买好了,哥哥
2019-7-1 14:41
0
雪    币: 222
活跃值: (3663)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
16
cmp11b.ne2  这个指令怎么来的。ida没看到带11的
2019-7-2 19:06
0
雪    币: 92
活跃值: (1075)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
谢谢 楼主啦
2019-7-2 21:53
0
雪    币: 14
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
18
招聘一个逆向工程师13996084443同微信
2019-7-3 14:26
0
雪    币: 34
活跃值: (118)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
看到雪糕,好好笑
2019-7-3 21:59
0
雪    币: 1387
活跃值: (5614)
能力值: ( LV3,RANK:25 )
在线值:
发帖
回帖
粉丝
20
膜拜
2019-7-15 08:42
0
雪    币: 12
活跃值: (55)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
21
是个神仙。
2019-7-25 10:03
0
雪    币: 2214
活跃值: (388)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
22
大神好啊,正好有个娜迦的APP,以前打开过这个软件的老版本,无壳。今天更新,发现无法用反编译软件Android killer打开,apk ide也报错。用Jadx-gui打开后,发现根目录下的dex体积很小,比较异常。后来在/asset/main000下发现三个dex文件,若干so文件,其中有libhdog.so、libvdog.so,如何脱壳修复啊,期待大神的教程,非常感谢,地址:d66K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6E0L8$3u0A6L8r3g2Q4x3X3g2W2i4K6u0V1j5$3S2A6L8X3q4D9K9h3k6W2i4K6u0W2j5$3!0E0i4K6u0r3b7$3S2A6L8X3q4D9K9h3k6W2f1X3g2F1k6i4N6Q4x3V1k6m8f1p5E0Q4x3V1k6W2K9r3!0E0k6g2)9#2k6X3#2G2j5X3W2D9k6g2)9#2k6X3k6G2M7W2)9#2k6Y4m8D9N6i4y4Q4y4h3k6D9j5i4y4@1i4K6g2X3y4U0q4Q4x3X3g2S2M7r3D9`.
2019-7-30 17:42
0
雪    币: 63
活跃值: (364)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
23
感谢分享 拜读
2019-8-4 18:26
0
雪    币: 2708
活跃值: (1718)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
24
好文章 
2019-9-20 18:55
0
雪    币: 447
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
25
年轻有为啊,继续努力
2019-10-20 09:56
0
游客
登录 | 注册 方可回帖
返回