首页
社区
课程
招聘
[原创]利用Triton 污点分析识别垃圾指令
2023-6-19 23:46 21393

[原创]利用Triton 污点分析识别垃圾指令

2023-6-19 23:46
21393

概要

此样本主要使用间接跳转
图片描述
可以利用unicorn模拟执行来确定x13的值,然后修改成直接跳转或者条件跳转,
本文的重点不在这里,不在多叙。本文的重点是如何识别这些垃圾指令,
因为光恢复它的跳转,ida还是不能反编译,它指令膨胀的非常严重,将max_func_size设置成4096也不行,所以对于BR x13这种指令,需要识别x13是由哪些指令生成的,将这些指令全部nop掉,才能减少方法的size。

如上图,代码中的所有跳转全部为BR指令,包括条件跳转和直接跳转,其中还包括不透明谓词

两种处理方式

一种是自下而上进行活跃变量分析,将BR x8 patch成b 0x1000后,将x8认为是不活跃变量,然后就可以进行活跃变量分析,然后根据ud链,找到定义x8的地方,将这个定义也删除掉。这个定义语句也会使用到变量,此时它使用到的变量也属于不活跃变量,所以可以继续向上分析,这种方式只是我理论想出来的,并没有实践

第二种是我用到的方法,污点分析,将mov x8,0x234234,这种指令的x8定义为污染源,然后向下分析,将污染过得指令全部nop掉。我觉得这种方式处理起来比较简单,对于br,bl这种跳转语句不执行,还有return块也不执行,这样就可以把整个方法当做一个巨大的代码块,由上而下的执行,将特征指令会赋值的寄存器设置成污染源,这样也可以处理这种特殊情况,在方法序言处将一个常数写入栈,在后面的代码块中从栈里面读取这个值,再使用计算,这个可以避免自己再做上下文的分析,污点分析能直接定位到。

Tirton简单介绍

github地址https://github.com/JonathanSalwan/Triton.git
这个工具很强大,有污点分析和符号执行两个功能,我这次只使用到了它的污点分析功能
安装 pip install tritondse

使用到的主要api

1
2
3
4
5
6
7
8
9
10
Triton.setArchitecture(ARCH.AARCH64)设置架构
Triton.setConcreteMemoryAreaValue(0, bin1) 写入具体内存
Triton.taintRegister()  设置寄存器为污染源
Triton.taintMemory()设置内存地址为污染源
Triton.getConcreteRegisterValue()获取寄存器的值
Triton.untaintRegister()对寄存器进行去污染处理
Triton.untaintMemory()对内存进行去污染处理
Instruction.setOpcode()设置字节码
Instruction.setAddress()设置指令地址
Triton.processing() 执行一条语句

实战

还是利用上面的那段代码,执行完1E7D00后,将w8寄存器设置为污染源

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
37
38
39
40
41
42
43
44
45
46
47
from triton import *
import idc
import ida_bytes
from capstone import *
from capstone.arm64 import *
 
cs = Cs(CS_ARCH_ARM64, CS_MODE_LITTLE_ENDIAN)
cs.detail = True
 
def get_insn2(opcode0, addr):
    insns = cs.disasm(opcode0, addr)
    for i in insns:
        return i
 
def taint_analysis2(start, end):
    Triton = TritonContext()
    with open('C:\\Users\\lj\\Desktop\\junks\\test1\\CoreBook2', 'rb') as f:
        bin1 = f.read()
    Triton.setArchitecture(ARCH.AARCH64)
    Triton.setConcreteMemoryAreaValue(0, bin1)
    sp = 0x100000000
    Triton.setConcreteRegisterValue(Triton.registers.x29, sp)
    Triton.setConcreteRegisterValue(Triton.registers.sp, sp)
    pc = start
    nop_addrs = []
    while pc:
        inst = Instruction()
        opcode0 = ida_bytes.get_bytes(pc, 4)
        cs_insn: CsInsn = get_insn2(opcode0, pc)
 
 
        inst.setOpcode(opcode0)
        inst.setAddress(pc)
        Triton.processing(inst)
        print(str(inst))
        if pc == 0x1E7D00:
            Triton.taintRegister(Triton.registers.w8)
 
        if inst.isTainted():
            idc.set_color(pc, idc.CIC_ITEM, 0xffe699)
            nop_addrs.append(pc)
 
        if pc >= end:
            break
        pc = pc + 4
if __name__ == '__main__':
    taint_analysis2(0x1E7D00, 0x1E7D98)

其中的大部分代码都是模板代码,主要有用的是这两句代码,将w8设置为污染源

1
2
if pc == 0x1E7D00:
    Triton.taintRegister(Triton.registers.w8)

看下效果
图片描述
在1e7d68处使用到了w8,并且将cpsr污染了,所以下一句cset指令也是污染的,
继续向下分析,可以看到间接引用w8寄存器的指令都识别出来了。
注意1e7d94处,虽然x13寄存器是被污染的,但是这条指令并没有被污染。
Triton将这种问题留给我们自己来处理,就是如果寄存器是被污染的,那么需要我们根据实际情况,来决定是否将寄存器指向的内存污染掉,上代码

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
37
38
39
40
41
42
43
44
45
46
47
48
from triton import *
import idc
import ida_bytes
from capstone import *
from capstone.arm64 import *
 
cs = Cs(CS_ARCH_ARM64, CS_MODE_LITTLE_ENDIAN)
cs.detail = True
 
def get_insn2(opcode0, addr):
    insns = cs.disasm(opcode0, addr)
    for i in insns:
        return i
 
def taint_analysis2(start, end):
    Triton = TritonContext()
    with open('C:\\Users\\lj\\Desktop\\junks\\test1\\CoreBook2', 'rb') as f:
        bin1 = f.read()
    Triton.setArchitecture(ARCH.AARCH64)
    Triton.setConcreteMemoryAreaValue(0, bin1)
    sp = 0x100000000
    Triton.setConcreteRegisterValue(Triton.registers.x29, sp)
    Triton.setConcreteRegisterValue(Triton.registers.sp, sp)
    pc = start
    nop_addrs = []
    while pc:
        inst = Instruction()
        opcode0 = ida_bytes.get_bytes(pc, 4)
 
 
        inst.setOpcode(opcode0)
        inst.setAddress(pc)
        Triton.processing(inst)
        print(str(inst))
        if pc == 0x1E7D00:
            Triton.taintRegister(Triton.registers.w8)
        if pc == 0x1e7d90:
            Triton.taintMemory(Triton.getConcreteRegisterValue(Triton.registers.x13))
 
        if inst.isTainted():
            idc.set_color(pc, idc.CIC_ITEM, 0xffe699)
            nop_addrs.append(pc)
 
        if pc >= end:
            break
        pc = pc + 4
if __name__ == '__main__':
    taint_analysis2(0x1E7D00, 0x1E7D9c)

这个脚本多了这几句代码

1
2
if pc == 0x1e7d90:
    Triton.taintMemory(Triton.getConcreteRegisterValue(Triton.registers.x13))

作用也很简单,执行完这句代码后,将x13指向的内存也污染掉
看下效果
图片描述
可以看到又多识别出来一些垃圾指令
那我们继续分别将代码块开始的几个寄存器,w8,w9,w10,w11,w12都给污染掉,上代码

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
from triton import *
import idc
import ida_bytes
from capstone import *
from capstone.arm64 import *
 
cs = Cs(CS_ARCH_ARM64, CS_MODE_LITTLE_ENDIAN)
cs.detail = True
 
def get_insn2(opcode0, addr):
    insns = cs.disasm(opcode0, addr)
    for i in insns:
        return i
 
def taint_analysis2(start, end):
    Triton = TritonContext()
    with open('C:\\Users\\lj\\Desktop\\junks\\test1\\CoreBook2', 'rb') as f:
        bin1 = f.read()
    Triton.setArchitecture(ARCH.AARCH64)
    Triton.setConcreteMemoryAreaValue(0, bin1)
    sp = 0x100000000
    Triton.setConcreteRegisterValue(Triton.registers.x29, sp)
    Triton.setConcreteRegisterValue(Triton.registers.sp, sp)
    pc = start
    nop_addrs = []
    while pc:
        inst = Instruction()
        opcode0 = ida_bytes.get_bytes(pc, 4)
        cs_insn: CsInsn = get_insn2(opcode0, pc)
         
 
        inst.setOpcode(opcode0)
        inst.setAddress(pc)
        Triton.processing(inst)
        print(str(inst))
        if pc == 0x1E7D00:
            Triton.taintRegister(Triton.registers.w8)
        if pc == 0x1E7D08:
            Triton.taintRegister(Triton.registers.w9)
        if pc == 0x1E7D10:
            Triton.taintRegister(Triton.registers.w10)
        if pc == 0x1E7D18:
            Triton.taintRegister(Triton.registers.w11)
        if pc == 0x1E7D20:
            Triton.taintRegister(Triton.registers.w12)
 
 
        if pc == 0x1e7d90:
            Triton.taintMemory(Triton.getConcreteRegisterValue(Triton.registers.x13))
 
        if inst.isTainted():
            idc.set_color(pc, idc.CIC_ITEM, 0xffe699)
            nop_addrs.append(pc)
 
        if pc >= end:
            break
        pc = pc + 4
if __name__ == '__main__':
    taint_analysis2(0x1E7D00, 0x1E7D98)

看下效果
图片描述
可以看到又多识别了一些垃圾指令。
还有一种特殊的栈变量,比如1E7D34处的指令,这种如何识别呢,其实在最开始已经谈到过这种情况的处理方式,这种栈变量的赋值方式一般是在方法序言处
图片描述
在这里给x4赋值
图片描述
在这里写到栈上
所以我们可以在0x1E4DB8处,将x4寄存器也给污染掉,上代码

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
from triton import *
import idc
import ida_bytes
from capstone import *
from capstone.arm64 import *
 
cs = Cs(CS_ARCH_ARM64, CS_MODE_LITTLE_ENDIAN)
cs.detail = True
 
def get_insn2(opcode0, addr):
    insns = cs.disasm(opcode0, addr)
    for i in insns:
        return i
 
def taint_analysis2(start, end):
    Triton = TritonContext()
    with open('C:\\Users\\lj\\Desktop\\junks\\test1\\CoreBook2', 'rb') as f:
        bin1 = f.read()
    Triton.setArchitecture(ARCH.AARCH64)
    Triton.setConcreteMemoryAreaValue(0, bin1)
    sp = 0x100000000
    Triton.setConcreteRegisterValue(Triton.registers.x29, sp)
    Triton.setConcreteRegisterValue(Triton.registers.sp, sp)
    pc = start
    nop_addrs = []
    while pc:
        inst = Instruction()
        opcode0 = ida_bytes.get_bytes(pc, 4)
        cs_insn: CsInsn = get_insn2(opcode0, pc)
        if cs_insn is None or cs_insn.mnemonic in ['br', 'bl', 'b']:
            pc = pc + 4
            continue
 
        inst.setOpcode(opcode0)
        inst.setAddress(pc)
        Triton.processing(inst)
        print(str(inst))
        if pc == 0x1E7D00:
            Triton.taintRegister(Triton.registers.w8)
        if pc == 0x1E7D08:
            Triton.taintRegister(Triton.registers.w9)
        if pc == 0x1E7D10:
            Triton.taintRegister(Triton.registers.w10)
        if pc == 0x1E7D18:
            Triton.taintRegister(Triton.registers.w11)
        if pc == 0x1E7D20:
            Triton.taintRegister(Triton.registers.w12)
 
        if pc == 0x1E4DB8:
            Triton.taintRegister(Triton.registers.x4)
            Triton.taintMemory(Triton.getConcreteRegisterValue(Triton.registers.x4))
 
        if pc == 0x1e7d90:
            Triton.taintMemory(Triton.getConcreteRegisterValue(Triton.registers.x8))
 
        if inst.isTainted():
            idc.set_color(pc, idc.CIC_ITEM, 0xffe699)
            nop_addrs.append(pc)
 
        if pc >= end:
            break
        pc = pc + 4
if __name__ == '__main__':
    taint_analysis2(0x1E4D28, 0x1E7D98)

这个脚本相比之前多了这几句代码,目的是跳过所有的跳转指令,将整个方法当成一个方法块来处理,这样会简单很多,此处我没有处理ret代码块,实际使用中,还需要跳过ret代码块,不然运行过程中pc会跑飞

1
if cs_insn is None or cs_insn.mnemonic in ['br', 'bl', 'b']:

在这里将x4寄存器和它指向的内存污染掉

1
2
3
if pc == 0x1E4DB8:
    Triton.taintRegister(Triton.registers.x4)
    Triton.taintMemory(Triton.getConcreteRegisterValue(Triton.registers.x4))

看下效果
图片描述
可以看到将对栈变量的处理也识别到了。
其实讲到这里已经差不多了,剩下的就是全局扫描识别mov adrp 这种指令,当然需要过滤掉其中的正常指令,然后适配上面的代码,就可以将大部分的垃圾指令识别到,然后nop掉。对于强迫症患者来说,也是一个福音。
代码就不上传了,文章中的代码运行应该都没问题


[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

最后于 2023-6-20 22:43 被普通人张三编辑 ,原因:
上传的附件:
收藏
点赞11
打赏
分享
最新回复 (12)
雪    币: 22
活跃值: (3619)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
New对象处 2023-6-20 00:04
2
0
666,躺下刷篇好文再睡
雪    币: 3498
活跃值: (17954)
能力值: ( LV12,RANK:277 )
在线值:
发帖
回帖
粉丝
0x指纹 5 2023-6-20 08:42
3
0
感谢分享~
雪    币: 964
活跃值: (1250)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
mb_fssslkzs 2023-6-20 11:03
4
0
爽文,让我彻夜不眠,瞬间蹦起来要完美修正我的问题
雪    币: 19323
活跃值: (28938)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
秋狝 2023-6-20 13:54
5
1
感谢分享
雪    币: 3663
活跃值: (3843)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
caolinkai 2023-9-17 11:58
6
0
感谢分享
雪    币: 7
活跃值: (374)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
花少年 2024-2-22 11:01
7
0
很棒,感谢分享
雪    币: 1506
活跃值: (3270)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
小希希 2024-2-22 12:14
8
0
感谢分享
雪    币: 249
活跃值: (464)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
chenxia0 2024-2-26 09:27
9
0
另一种方式:frida stalker采集区间指令(0x1E4D28, 0x1E7D98),然后通过IDC来修复
雪    币: 14
活跃值: (255)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
hiltercn 2024-3-11 10:36
10
0
你这样反混淆 人都累死了
雪    币: 20
活跃值: (494)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
普通人张三 2024-3-13 11:04
11
0
chenxia0 另一种方式:frida stalker采集区间指令(0x1E4D28, 0x1E7D98),然后通过IDC来修复
这个方式主要是识别垃圾指令,因为垃圾指令太多,导致方法size非常大,所以即便是恢复了间接跳转,ida f5也会失败
雪    币: 20
活跃值: (494)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
普通人张三 2024-3-13 11:05
12
0
有啥更好的方式识别垃圾指令,还请赐教,开始写规则去识别垃圾指令,但是发现根本写不完,后来才想到污点分析这种方式,因为污点分析是别人写好的,框架很好用,所以也不累
雪    币: 14
活跃值: (255)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
hiltercn 2024-4-3 13:24
13
0
普通人张三 有啥更好的方式识别垃圾指令,还请赐教,开始写规则去识别垃圾指令,但是发现根本写不完,后来才想到污点分析这种方式,因为污点分析是别人写好的,框架很好用,所以也不累[em_13]
你感觉不累可能还是因为你要分析的文件混淆函数还不够多不够大 要达成完成反混淆去除block跳转相关垃圾代码 必须要做到stack analyze, instrcution pathing,stack reg protected, branch nop 还有一些细节的处理 你去找几个文件超过100M 并且 单个函数超过1M的文件多分析一下就明白了
游客
登录 | 注册 方可回帖
返回