首页
论坛
课程
招聘
[原创]利用unicorn分析固件中的算法
2021-4-22 13:19 9331

[原创]利用unicorn分析固件中的算法

2021-4-22 13:19
9331

前言

近年来,越来越多安全研究员开始使用QEMU以及unicorn这类虚拟化技术对固件进行模拟执行,甚至是FUZZ测试。模拟执行在嵌入式固件分析中应用越来越广泛,为了让大家能了解这一技术的使用方法,本文从实战出发,利用unicorn框架分析某个设备的加密算法。

分析固件

本文的目的想要分析并调用该固件的一个魔改的MD5。

我们简单地对比了该算法和标准md5的区别,发现大量算法常数被修改了。

MD5Init对比

标准算法:

修改后的:

MD5Step对比

标准算法:

修改后的:

分析思路

  1. 对照标准算法实现,分析魔改的地方,然后对标准算法进行修改。
    缺点:分析时间长,修改的地方多的话还容易出错。特别是遇上二进制代码混淆,分析起来更加麻烦。
  2. 无需花大量时间分析算法,直接复制IDA反编译的代码,重新编译即可。
    缺点:反编译的伪代码不一定准确,如果代码函数较多,需要复制和整理的函数也比较多,特别是变量类型这块,也要进行修复。
    3.只需要分析函数的参数,使用模拟执行技术,对关键算法进行模拟执行。

模拟执行

分析函数参数

标准的MD5一般有三个函数,分别如下所示:

为了模拟执行,我们需要在IDA找到这三个函数的地址,如下所示:

用法如下,这里就不多介绍了

模拟环境初始化

该固件是MIPS大端架构系统,初始化一些加载地址,栈地址之类的全局变量

解析ELF文件,把固件的代码段读取到模拟器中:

分配栈空间,以及变量空间,用于存放md5_ctx以及输入的变量字符串

调用MD5函数

首先,我们为了让模拟器调用完每个函数之后能够停止运行,必须将返回地址设置为一个指定的地址,当callback检测到运行到该地址,立刻停止下来了:

分别调用3个函数:

通过代码可以知道,最终的md5值在MD5Context偏移为88的地方:

所以在调用完成之后直接把MD5_CTX偏移为88的数据读取出来即为MD5运算结果:

运行结果

当输入为12345678得到下面的MD5值

所有代码如下:

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
from unicorn import *
from capstone import *
from unicorn.mips_const import *
from elftools.elf.elffile import ELFFile
from elftools.elf.segments import Segment
import ctypes
import binascii
import hexdump
filepath='fw'
 
load_base=0
stack_base=0
stack_size=0x20000
var_base=load_base+stack_size
var_size=0x10000
stop_stub_addr=0x30000
stop_stub_size=0x10000
 
 
emu = Uc(UC_ARCH_MIPS,UC_MODE_MIPS32 + UC_MODE_BIG_ENDIAN)
 
def disasm(bytecode,addr):
    md=Cs(CS_ARCH_MIPS,CS_MODE_MIPS32+ CS_MODE_BIG_ENDIAN)
    for asm in md.disasm(bytecode,addr):
        return '%s\t%s'%(asm.mnemonic,asm.op_str)
def align(addr, size, growl):
    UC_MEM_ALIGN = 0x1000
    to = ctypes.c_uint64(UC_MEM_ALIGN).value
    mask = ctypes.c_uint64(0xFFFFFFFFFFFFFFFF).value ^ ctypes.c_uint64(to - 1).value
    right = addr + size
    right = (right + to - 1) & mask
    addr &= mask
    size = right - addr
    if growl:
        size = (size + to - 1) & mask
    return addr, size
def hook_code(uc, address, size, user_data):
    bytecode=emu.mem_read(address,size)
    print(" 0x%x :%s"%(address,disasm(bytecode,address)))
    if address==stop_stub_addr:
        emu.emu_stop()
 
 
#init var
 
def my_md5(key):
 
    with open(filepath, 'rb') as elffile:
        elf=ELFFile(elffile)
        load_segments = [x for x in elf.iter_segments() if x.header.p_type == 'PT_LOAD']
 
        for segment in load_segments:
            prot = UC_PROT_ALL
            print('mem_map: addr=0x%x  size=0x%x'%(segment.header.p_vaddr,segment.header.p_memsz))
 
            addr,size=align(load_base + segment.header.p_vaddr,segment.header.p_memsz,True)
            emu.mem_map(addr, size, prot)
            emu.mem_write(addr, segment.data())
 
    emu.mem_map(stack_base, stack_size, UC_PROT_ALL)
    emu.mem_map(var_base, var_size, UC_PROT_ALL)
 
    md5_ctx=var_base
    psw=var_base+0x5000
 
    emu.mem_write(psw,key)
 
 
    emu.mem_map(stop_stub_addr, stop_stub_size, UC_PROT_ALL)
    emu.reg_write(UC_MIPS_REG_A0, md5_ctx)
    emu.reg_write(UC_MIPS_REG_RA,stop_stub_addr)
    emu.reg_write(UC_MIPS_REG_SP,stack_base+stack_size)
 
    my_MD5Init_addr=0x0041FAA8
    my_MD5Update_addr=0x0041FAE4
    my_MD5Final_addr=0x0041FC18
 
    #MD5Init
    code=emu.mem_read(my_MD5Init_addr, 8)
    emu.hook_add(UC_HOOK_CODE, hook_code)
    emu.emu_start(my_MD5Init_addr, my_MD5Init_addr + 0x1000)
 
    #MD5Update
    emu.reg_write(UC_MIPS_REG_A0, md5_ctx)
    emu.reg_write(UC_MIPS_REG_A1, psw)
    emu.reg_write(UC_MIPS_REG_A2, len(key))
    emu.reg_write(UC_MIPS_REG_SP,stack_base+stack_size)
    emu.reg_write(UC_MIPS_REG_RA,stop_stub_addr)
    emu.emu_start(my_MD5Update_addr, my_MD5Update_addr + 0x1000)
    #MD5Final
 
    emu.reg_write(UC_MIPS_REG_A0, md5_ctx)
    emu.reg_write(UC_MIPS_REG_SP,stack_base+stack_size)
    emu.reg_write(UC_MIPS_REG_RA,stop_stub_addr)
    emu.emu_start(my_MD5Final_addr, my_MD5Final_addr + 0x1000)
 
    return emu.mem_read(md5_ctx+88,16)
 
if __name__=="__main__":
    key=b'12345678'
    hexdump.hexdump(my_md5(key))

总结

本文通过unicorn将固件中魔改的md5算法成功进行模拟执行,并输出了正确的值,说明使用unicorn对固件中的算法进行分析是非常有效的。这将会对使用了混淆的算法特别有用,逆向研究人员只需要分析关键函数以及参数,让unicorn执行模拟即可,当然还有些不足,比如有些CPU指令支持不完全,运行效率等问题。

参考文章

https://bbs.pediy.com/thread-253868.htm


[注意] 欢迎加入看雪团队!base上海,招聘安全工程师、逆向工程师多个坑位等你投递!

收藏
点赞5
打赏
分享
最新回复 (4)
雪    币: 2032
活跃值: 活跃值 (1075)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
caolinkai 活跃值 2021-4-23 11:24
2
0
支持下
雪    币: 15
活跃值: 活跃值 (50)
能力值: ( LV12,RANK:285 )
在线值:
发帖
回帖
粉丝
daiweizhi 活跃值 1 2021-6-9 15:36
3
0

你这个硬件的是ARM指令集么?直接用python调用ARM固件的接口函数么?

最后于 2021-6-9 15:44 被daiweizhi编辑 ,原因:
雪    币: 9140
活跃值: 活跃值 (8361)
能力值: ( LV12,RANK:310 )
在线值:
发帖
回帖
粉丝
wmsuper 活跃值 5 2021-6-9 18:59
4
0
daiweizhi 你这个硬件的是ARM指令集么?直接用python调用ARM固件的接口函数么?
是mips的,通过模拟器模拟执行关键函数
雪    币: 1002
活跃值: 活跃值 (57)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
lynxtang 活跃值 2021-6-12 08:28
5
0
感谢分享,学习中,小白看的有点头大,哈哈
游客
登录 | 注册 方可回帖
返回