Unicorn 使用总结

Unicorn 是一个轻量级、跨平台的开源模拟器(Emulator),可以用于模拟不同架构的程序执行。它基于 QEMU 项目,但比 QEMU 更加轻量和易于使用,主要面向动态分析、漏洞挖掘和逆向工程等领域。Unicorn 允许开发者模拟目标平台的指令集架构(ISA),并且能够执行二进制代码,模拟程序运行时的行为,捕捉寄存器、内存等状态。
Unicorn 安装
可以通过 Python 的包管理工具 pip
安装:
1 | pip install unicorn |
基本使用
创建 Unicorn 对象
和前面提到的 Keystone 和 Capstone 的流程类似,Unicorn 模拟执行首先需要创建一个 Unicorn 对象,并设置目标架构和模式。
1 | from unicorn import * |
注意
在模拟 x86 架构时,程序可能会访问特定段(如 gs
或 fs
)中的数据,这些数据通常与操作系统、线程信息、TLS(线程局部存储)等有关(例如 mov rax, gs:[0x28]
指令获取 canary)。
由于 Unicorn 模拟器仅提供模拟功能,导致像 fs
、gs
这样的段寄存器都被默认初始化为 0,因此访问这些段中数据的指令会触发程序内存读写异常。
为了避免这种情况影响程序模拟执行,最好在 0 地址处映射一段内存。
1 | mu.mem_map(0, 0x1000) # 避免异常 "mov rax, gs:[0x28]" |
寄存器操作
Unicorn 允许你模拟寄存器的读写操作,你可以通过 mu.reg_write()
来写入寄存器的值,使用 mu.reg_read()
来读取寄存器的值。
写入寄存器
mu.reg_write(reg_id, value)
用来写入寄存器的值,reg_id
是寄存器的标识符,value
是要写入的值。
注意
一些特殊的寄存器如 gs
寄存器不能直接通过 reg_write
修改。
1 | mu.reg_write(UC_X86_REG_RIP, 0x1000) # 设置 RIP 寄存器的值 |
读取寄存器
mu.reg_read(reg_id)
返回指定寄存器的当前值。
1 | rip_value = mu.reg_read(UC_X86_REG_RIP) # 读取 RIP 寄存器的值 |
内存操作
Unicorn 允许你模拟内存的读写操作,模拟程序运行时的内存访问。你可以通过 mu.mem_map()
来映射内存区域,通过 mu.mem_write()
来写入数据,通过 mu.mem_read()
来读取内存数据。
映射内存
mu.mem_map(start, size)
用来映射一段内存区域,start
是内存起始地址,size
是内存的大小。
注意
start
和end
要关于 0x1000 对齐。- 要确保映射的内存地址空间不要与之前映射的内存出现重叠。
1 | mu.mem_map(0x1000, 0x1000) # 映射 0x1000 大小的内存 |
写入内存
mu.mem_write(addr, data)
将数据写入指定地址的内存区域。data
是二进制数据(字节串),addr
是写入的地址。
1 | code = b"\x48\x31\xc0" # x86_64: xor rax, rax |
读取内存
mu.mem_read(addr, size)
从指定地址读取 size
字节的数据。
1 | data = mu.mem_read(0x1000, 4) # 从地址 0x1000 读取 4 字节 |
加载二进制程序
实际情况下我们可能需要模拟执行一个二进制程序中的某个解密算法函数。与 shellcode 不同,在二进制程序中即使一个纯算法函数也不是只加载代码段就可以正常仿真的,因为这个函数在执行过程中还可能访问全局变量。因此我们需要想办法将这个二进制程序加载到内存中,因此通常 Unicorn 还要配合 angr 的静态分析功能使用。
下面这段代码是加载二进制到 Unicorn 中的模板,可以应对大多数情况。
提示
- 这个模板的段映射部分写的很奇怪,这是因为有的二进制程序(比如一些 Linux 内核镜像)的段并不是关于 0x1000 对齐,但是 Unicorn 的
mem_map
要求地址范围按 0x1000 对齐,并且不能与之前映射的内存重叠。因此这里要做内存对齐处理并且多次捕获异常。 - x86 架构设置
bp
寄存器是因为有的函数会通过bp
寄存器访问函数的参数和局部变量。 - 为了实现代码复用,通常的做法是把每个架构相关的操作抽象为接口类,然后每个架构实现这个接口类,而真正的核心逻辑不需要关注架构的细节。下面的模板就是遵循这一设计原则。
1 | # 定义 amd64 架构的上下文 |
模拟执行
虚拟机启动
当前期虚拟机初始化完成之后(主要是程序加载、栈初始化、寄存器设置等),就可以启动虚拟机进行模拟了。虚拟机通过 emu_start
函数启动,该函数定义如下:
1 | def emu_start(self, begin: int, until: int, timeout: int=0, count: int=0) -> None: |
begin
:仿真开始的地址。表示从该地址开始执行机器码,通常是程序的入口点。until
:仿真结束的地址。表示程序执行到该地址时仿真停止。until
应大于begin
,否则可能导致仿真无限执行。timeout
:超时设置(单位:毫秒)。在仿真执行过程中,如果超时值非零,仿真会在超时后自动停止。如果设置为 0,表示不限制时间。count
:执行次数。如果count
非零,仿真会执行指定的次数后停止。如果为 0,表示按地址范围执行,直到end
地址或其他条件触发。
在虚拟机执行过程中可能会抛出异常,如访问非法内存地址、执行未定义指令等,通常这些异常会触发钩子函数或返回错误信息。为了应对这些可能的异常,可以通过在模拟中添加错误处理代码,保证仿真顺利进行或在发生错误时及时停止仿真。
常见的异常包括:
UC_ERR_READ_UNMAPPED
:尝试读取未映射的内存。UC_ERR_WRITE_UNMAPPED
:尝试写入未映射的内存。UC_ERR_FETCH_UNMAPPED
:尝试获取未映射的指令。UC_ERR_READ_PROT
:尝试读取保护内存。UC_ERR_WRITE_PROT
:尝试写入保护内存。UC_ERR_FETCH_PROT
:尝试获取受保护的指令。
异常处理可以通过 try-except
结构来完成。下面是一个例子,展示了如何捕捉异常并处理:
1 | try: |
事件监控
Unicorn 提供了事件钩子(hooks)功能,用于在程序执行过程中捕获特定的事件(如指令执行、内存读写、寄存器访问等)。通过钩子机制,用户可以在仿真时监控程序的特定行为并调用用户自己实现的回调函数处理。
Unicorn 提供了多种类型的监控事件,这里只介绍我们常用的事件类型。
注意
在添加事件钩子之后 Unicorn 的执行效率会变得非常低。因此在实际编写脚本的时候应当尽量避免使用 Unicorn 的事件监控功能,而是将其作为调试脚本的工具。
例如在一段代码中间的某个位置处需要做一些额外操作,时候可以可以以该位置为边界将代码分为两部分模拟,而不是设置一个指令执行事件来监控程序只不是执行到需要做额外操作的位置了。
指令执行事件
指令执行事件 UC_HOOK_CODE
在每执行一条指令时触发,该事件对应的回调函数如下:
1 | def hook_code(uc, address, size, user_data): |
uc
:当前的 Unicorn 仿真对象,类型为Uc
。它是对当前仿真状态的一个引用,用户可以通过它访问模拟环境中的寄存器、内存等。address
:当前执行指令的地址(内存中的位置)。这个地址是指指令被执行时在仿真内存中加载的地址。size
:当前指令的大小(以字节为单位)。这表示指令在内存中占用的字节数。user_data
:用户自定义的数据。可以在调用hook_add
时传入,通常用于传递上下文信息或者保持状态。
我们通常使用下面这个模板打印模拟执行的汇编指令来调试脚本:
1 | from capstone import * |
内存事件
我们常用的内存事件有内存读入事件(UC_HOOK_MEM_READ
)和内存写入事件(UC_HOOK_MEM_WRITE
),另外针对指令的有获取指令事件(UC_HOOK_MEM_FETCH
)。对应的回调函数定义如下:
1 | def hook_mem(uc, access, address, size, value, user_data): |
uc
:当前的 Unicorn 仿真对象,类型为Uc
,同样提供对仿真环境的访问。access
:内存访问的类型。可以是以下几种类型:UC_MEM_READ
:表示是数据读取。UC_MEM_WRITE
:表示是数据写入。UC_MEM_FETCH
:表示读取的是指令(通常用于指令获取)。
address
:发生内存访问的地址(内存中的位置)。这个地址指的是访问的内存单元的地址。size
:内存读取操作的大小(以字节为单位)。表示读取或写入了多少字节的数据。value
:读取或写入的值。指在指定地址处读取或写入的实际内容(以整数形式表示)。user_data
:用户自定义的数据,通常用于在钩子中传递一些额外的上下文信息。
1 | # 定义一个回调函数,用于监控内存读取 |
常用脚本
trace shellcode
1 | import time |
- Title: Unicorn 使用总结
- Author: sky123
- Created at : 2025-08-19 02:12:45
- Updated at : 2025-08-19 02:16:58
- Link: https://skyi23.github.io/2025/08/19/Unicorn 使用总结/
- License: This work is licensed under CC BY-NC-SA 4.0.