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

  • A+
所属分类:逆向工程

作者 | 绿盟科技格物实验室 陈杰

一、前言

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

二、Unicorn框架

Unicorn是基于QEMU的轻量级,多平台,多体系结构的CPU仿真器框架,支持的架构包括:ARM,ARM64(ARMv8),M68K,MIPS,SPARC和X86(16、32、64位)。

下面是使用Unicorn框架的优点:

框架:QEMU是一组仿真器,但不是框架。因此不能在QEMU之上构建自己的工具,但是Unicorn可以。

灵活:QEMU不能在没有任何上下文的情况下模拟大量原始二进制代码:它需要适当的可执行二进制文件(例如,ELF格式的文件),或内部带有完整OS的整个系统镜像。而Unicorn只专注于CPU操作,并且可以在没有上下文的情况下模拟原始代码。

检测:QEMU不支持动态检测,但是使用Unicorn,可以为各种事件(从CPU执行到内存访问)注册自定义处理程序。此工具为程序员提供了监视和分析仿真代码所需的全部功能。

三、析固件

本文以某固件中一个魔改的MD5算法为例进行分享。

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

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

MD5Init对比

标准算法:

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

修改后的:

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

MD5Step对比

标准算法:

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

修改后的:

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

分析思

1. 对照标准算法实现,分析魔改的地方,然后对标准算法进行修改。

缺点:分析时间长,修改的地方多的话还容易出错。特别是遇上二进制代码混淆,分析起来更加麻烦。

2. 无需花大量时间分析算法,直接复制IDA反编译的代码,重新编译即可。

缺点:反编译的伪代码不一定准确,如果代码函数较多,需要复制和整理的函数也比较多,特别是变量类型这块,也要进行修复。

3. 只需要分析函数的参数,使用模拟执行技术,对关键算法进行模拟执行。

四、模拟执行

分析函数参数

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

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

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

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

计算MD5的函数用法如下:

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

模拟环境初始化

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

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

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

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

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

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

调用MD5函

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

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

分别调用3个函数:

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

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

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

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

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

运行结果

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

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

所有代码如下:

from unicorn import *from capstone import *from unicorn.mips_const import *from elftools.elf.elffile import ELFFilefrom elftools.elf.segments import Segmentimport ctypesimport binasciiimport hexdumpfilepath='fw'
load_base=0stack_base=0stack_size=0x20000var_base=load_base+stack_sizevar_size=0x10000stop_stub_addr=0x30000stop_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 '%st%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, sizedef 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 vardef 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执行模拟即可,极大提高了分析人员的研究效率。



转载请注明来源:网络安全应急技术国家工程实验室

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

本文始发于微信公众号(网络安全应急技术国家工程实验室):原创 | 利用unicorn分析固件中的算法

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: