Unicorn 是一个基于 QEMU 的轻量级、多平台、多架构 CPU 模拟器框架。
相比于重量级的符号执行框架(如 Angr),Unicorn 虽然没有内置 Z3 约束求解器,但凭借其极致的性能、友好的 API 设计以及强大的 Hook 机制(指令级回调),成为了二进制分析、反混淆和 CTF 竞赛中的利器。
简单来说,Unicorn 是一款 CPU 模拟器。
但与 VMWare 或 Android 模拟器不同,Unicorn 不负责模拟整个操作系统或完整的硬件环境,它不支持系统调用 (Syscall) 。
你需要像操作“裸机”一样:
为了避免污染系统的 Python 环境,推荐使用 Conda 进行环境隔离。
使用 pip 安装官方的 Python 绑定:
验证安装
在终端输入 python 进入交互模式,尝试导入:

如果无报错且输出了版本号,说明环境搭建成功。
打开你的 IDE(如 PyCharm 或 VSCode),创建一个新的 Python 项目。在解释器设置(Interpreter Settings)中,选择 Existing Environment(现有环境),并指向刚刚创建的 Conda 环境路径。

学习 Unicorn 的过程,其实就是学习如何手动扮演操作系统的角色,为 CPU 准备好它运行所需的一切资源。
我们将按照以下顺序攻克 Unicorn 的核心要素:
在开始编写 Unicorn 代码之前,我们必须建立一个清晰的思维模型。使用 Unicorn 就像是在扮演一个“手动挡”的操作系统,你必须亲手为 CPU 准备好它运行所需的一切资源。所有的 Unicorn 脚本(无论多么复杂)都逃不出以下 5 个标准步骤:
首先,我们需要引入 Unicorn 库,并实例化一个 Uc 对象。这一步决定了我们要模拟的硬件环境。
Unicorn 中的 CPU 只能访问已被映射的内存区域。我们需要像操作系统一样,规划并申请虚拟内存空间。
有了内存空间后,我们需要将机器码(Shellcode)或二进制文件内容写入其中。这相当于加载器(Loader)将程序加载到内存的过程。
在运行前,必须初始化 CPU 的关键寄存器。最重要的是设置 指令指针 (RIP/EIP) 和 栈指针 (RSP/ESP) 。
一切准备就绪,指定入口点和结束点,按下“启动键”。
API:mu.emu_start(begin, end)
在 Unicorn 中,一切的起点都是 Uc 类的实例化。这个步骤决定了你要模拟的“硬件规格”——即 CPU 的架构(Architecture)和运行模式(Mode)。
要创建一个模拟器实例,我们需要传入两个核心参数:
Unicorn 支持多种主流架构,以下是逆向分析中最常用的几种:
模式常量用于进一步细分 CPU 的工作状态。需要注意的是,不同的架构支持的模式不同,且模式可以通过 + 号进行组合。
在实际使用中,我们经常需要组合使用这些常量。
场景 A:模拟 PC 上的 32位 Windows 程序
场景 B:模拟 Android 上的 ARM64 代码
场景 C:模拟 ARM Thumb 指令
ARM 处理器可以在 ARM 状态(4字节指令)和 Thumb 状态(2字节指令)之间切换。
场景 D:模拟 IoT 设备的 MIPS 大端序程序
某些路由器固件使用大端序 MIPS。
Unicorn 模拟器内部没有操作系统的 malloc 或 Heap 管理器。作为“上帝视角”的控制者,你必须手动管理每一页内存的分配、读写和释放。
Unicorn 的内存操作 API 非常精简,核心只有三个:映射(申请) 、写入 和 读取。
在 CPU 访问任何内存地址之前,该地址必须先被“映射”。访问未映射的内存会导致 UC_ERR_READ_UNMAPPED 或 UC_ERR_WRITE_UNMAPPED 异常。
API: uc.mem_map(address, size, perms=UC_PROT_ALL)
4KB 对齐:
现代操作系统和 CPU 通常以“页(Page)”为单位管理内存,一页通常是 4096 字节 (0x1000)。
如果你尝试申请 0x100 字节,Unicorn 会直接报错。必须向上取整到 0x1000。
有了内存空间后,我们需要将机器码(Shellcode)或数据填充进去。
API: uc.mem_write(address, data)
在模拟执行结束后,或者在 Hook 回调中,我们通常需要读取内存中的数据来验证计算结果。
API: uc.mem_read(address, size)
在 mem_map 或 mem_protect 中使用,用于控制内存页的读写执行权限(类似于 Linux 的 mprotect)。
示例:修改内存权限
寄存器是 CPU 的“内部工作台”,几乎所有的运算指令都依赖于它。在 Unicorn 中,我们通过统一的接口来读写不同架构下的数百个寄存器。
核心 API 非常直观:读 (Read) 和 写 (Write) 。
在开始模拟执行之前,通常需要初始化一些关键寄存器:
模拟执行结束后,或者在 Hook 回调函数中,我们需要读取寄存器的值来检查程序的运行状态或计算结果。
API: uc.reg_read(reg_id)
Unicorn 为每种架构都定义了海量的寄存器常量。为了方便使用,我们需要导入对应架构的 const 模块。
x86 / x64 架构
ARM 架构
ARM64 架构
当内存和寄存器都准备就绪后,最后一步就是按下“启动键”,让虚拟 CPU 开始运转。Unicorn 提供了灵活的执行控制接口,允许我们指定运行范围、时间限制甚至指令数量。
这是 Unicorn 中唯一用于启动 CPU 的 API。调用它是阻塞的,意味着直到模拟结束(或报错),Python 脚本才会继续往下执行。
参数详解:
begin (int) : 起始地址。CPU 将从这里提取第一条指令。
end (int) : 结束地址。
timeout (int, 可选) : 超时时间(单位:微秒 / microseconds)。
count (int, 可选) : 指令计数。
在某些情况下(例如在 Hook 回调函数中检测到了某个特定条件,或者想要实现断点功能),我们需要中途强行停止模拟。
注意:emu_stop 通常配合 Hook 使用。
如果说 emu_start 是让程序跑起来,那么 Hook 就是让程序“透明化”。
Hook 机制是 Unicorn 最强大的特性之一。它允许我们在模拟执行的过程中插入自定义的回调函数(Callback),相当于在 CPU 内部安装了无数个“监控探头”。
通过 Hook,我们可以实现:
API: uc.hook_add(hook_type, callback, user_data=None, begin=1, end=0)
API: uc.hook_del(hook_handle)
Unicorn 的 Hook 系统非常灵活,以下是三种最基础且最常用的 Hook 类型:
回调函数签名:
注册示例:
作用:当 CPU 尝试读取、写入或获取内存指令时触发。
常量组合:
回调函数签名:
注册示例:
回调函数签名:
注册示例:
将上述三种 Hook 结合起来,我们就能得到一个功能完备的“调试监视器”。它可以帮我们实时追踪指令流,并在发生非法内存访问时自动报警。
纸上得来终觉浅。现在,我们将前面的所有知识点串联起来,编写一个完整的 Unicorn 脚本。
我们将从零开始构建一个 x86-32 虚拟环境,并让它执行一条最简单的汇编指令:INC EAX (将 EAX 寄存器的值加 1)。
当你运行这段代码时,终端输出如下内容:

在代码中我们使用了 try...except UcError 结构。这是编写 Unicorn 脚本的最佳实践。
如果我们在 mem_map 时传入了非 4KB 对齐的大小,或者试图执行未写入指令的内存区域,Unicorn 会抛出 UcError。
常见错误代码:
在掌握了 Unicorn 的基础操作后,我们将目光转向更复杂的真实场景。真实的程序不仅仅是简单的加减乘除,它们涉及文件 IO、内存管理、函数调用以及复杂的控制流。
本章节将围绕 “如何模拟一个真实的 ELF/PE 可执行文件” 展开,逐步解决以下核心挑战:
在二进制分析的战场上,单打独斗往往力不从心。为了构建一个功能完备的分析环境,我们通常会将 Unicorn 与另外两款神器——Capstone 和 Keystone 结合使用。
这三者同宗同源(均由同一团队开发),被誉为逆向工程界的“三剑客”:
在 Unicorn 的 UC_HOOK_CODE 回调中,我们只能拿到当前指令的地址和长度。如果不进行反汇编,我们无法知道具体执行了什么逻辑。
基本用法示例:
有时候我们需要在模拟过程中动态修改程序行为,比如将一条复杂的 CALL 指令替换为 NOP,或者直接注入一段 Shellcode。Keystone 让我们可以直接写汇编,而不用手动拼凑十六进制机器码。
基本用法示例:
这里只做简单了解,具体的学习文章后续更新。
挑战:Unicorn 只是一个裸机 CPU,它没有操作系统内核,也没有加载标准库(libc.so / kernel32.dll)。
当被模拟的程序执行到 call printf 或 call malloc 时,CPU 会跳转到这些函数的地址。但在 Unicorn 的内存中,这些地址通常是未映射的空洞,或者只有符号没有代码。直接执行会导致 Crash。
为了让程序继续跑下去,我们需要手动接管这些“外部调用”。
对于那些不影响核心逻辑的函数(如打印日志、Sleep、复杂的系统初始化),我们可以直接跳过。
对于影响程序逻辑的关键函数(如 malloc、strcpy、AES_encrypt),我们不能简单跳过,而是要用 Python 代码来“重写”它们的功能。这被称为 High Level Emulation (HLE) 。
概念:
在现代操作系统中,用户态程序无法直接访问硬件(如读写磁盘、网络通信)。它们必须通过特殊的指令——SYSCALL (x64) 或 INT 0x80 (x86)——陷入内核态,请求操作系统服务。
由于 Unicorn 只是用户态模拟器,遇到这些指令时会停止或报错。我们需要捕获这些信号,并用 Python 代码扮演“操作系统内核”的角色。
Unicorn 提供了专门的 Hook 类型来拦截这些事件:
注意: 通常我们统一使用 UC_HOOK_INTR 来捕获所有类型的中断和系统调用请求,然后根据中断号(intno)进行判断。
我们将实现一个迷你 Linux 内核,支持程序打印字符串 (sys_write) 和正常退出 (sys_exit)。
Unicorn 不仅仅是一个执行器,它更是一个“全知全能”的观察者。通过模拟执行,我们可以拿到静态分析(IDA)无法获取的运行时数据。利用这些数据,我们可以对抗 OLLVM 等现代混淆技术。
痛点:OLLVM 将简单的 if-else 或 while 循环打碎,塞进一个巨大的 switch-case 分发器中(平坦化),导致流程图像一团乱麻。
还原思路:
纸上得来终觉浅,我们通过一个真实的 CTF 题目来实战演练 Unicorn 的使用技巧。本题源自 **hxpCTF 2017**,名为 Fibonacci。
题目附件: f2fK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6W2N6r3g2J5L8X3q4D9i4K6u0W2M7X3g2V1i4K6u0r3j5i4y4K6k6i4c8K6i4K6u0r3k6X3W2D9k6i4y4Q4x3V1j5J5x3o6p5%4i4K6u0r3g2f1g2Q4x3V1k6X3K9h3u0G2L8X3q4U0j5$3V1`.
目标: 程序会计算并输出 Flag。我们的任务是让 Unicorn 模拟运行它,并优化执行速度以获取完整 Flag。
当我们运行这个程序的时候,可以注意到这个程序计算和输出Flag非常的慢。Flag的下一个字节计算的越来越慢。

在拿到二进制文件后,第一步是让它跑起来。我们需要解决内存映射、寄存器初始化以及外部函数调用等问题。
根据前面学习的知识,我们先搭好架子:
当我们首次运行脚本时,Unicorn 会抛出 UC_ERR_READ_UNMAPPED 错误,提示读取 0x601038 失败。
原因分析:
在 IDA 中查看该地址,发现它是 stdout(标准输出流指针)。

程序在 main 函数中调用 setbuf(stdout, NULL) 或 putc(..., stdout) 时,会尝试读取这个指针。由于 Unicorn 没有操作系统环境,.bss 段的这个变量未被 libc 初始化,因此指向了无效区域。
解决方案:
对于这种只影响输出缓冲、不影响核心算法逻辑的代码,直接 Patch (跳过) 即可。我们需要跳过以下地址的指令:


程序使用 printf 输出提示信息,用 putc 逐个输出 Flag 字符。我们需要 Hook 这些地址,用 Python 的 print 替代。
运行结果:
程序成功运行并输出了前三个字符 hxp,但随后似乎陷入了卡顿。这是因为程序内部的算法效率极低。
在能够成功运行程序后,我们发现输出 Flag 的速度极慢。通过分析代码可知,该程序使用的是递归方式计算斐波那契数列,时间复杂度为指数级 O(2^n) 。如果不进行优化,模拟可能需要数小时甚至数天。

为了解决这个问题,我们采用 Unicorn Hook 实现 记忆化搜索 (Memoization) 算法,以空间换时间。
我们需要拦截目标函数 fibonacci 的入口和出口:
建立缓存 (Cache) :
使用字典 fibonacci_dp 存储计算结果,映射关系为:{(参数1, 参数2): (返回值RAX, 返回值RSI)}。
处理函数入口 (On Enter) :
处理函数出口 (On Leave) :
加入记忆化搜索算法后,原本需要数小时的计算过程被瞬间完成:

通过 Unicorn,我们在没有修改任何二进制文件的情况下,仅通过 Python 脚本就实现了对目标程序算法的动态热补丁 (Hot Patching) 优化。
460K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2U0L8X3u0D9L8$3N6K6i4K6u0W2j5$3!0E0i4K6u0r3e0$3&6D9P5g2)9J5k6s2S2A6j5h3!0^5K9h3q4G2i4K6u0r3M7q4)9J5c8U0p5%4x3K6p5$3x3K6b7K6i4K6u0W2K9s2c8E0L8l9`.`.
https://bbs.kanxue.com/thread-224330-1.htm
e1fK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6W2N6r3g2J5L8X3q4D9i4K6u0W2M7X3g2V1i4K6u0r3x3U0l9I4z5q4)9J5c8Y4g2F1K9h3y4G2M7X3&6Q4x3X3c8W2L8X3N6A6L8X3g2Q4x3X3c8@1N6i4c8G2M7X3W2S2L8q4)9J5c8R3`.`.
a36K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1j5I4K9K6m8U0N6q4)9J5k6h3N6A6N6r3S2#2j5W2)9J5k6h3W2G2i4K6u0r3x3U0l9J5y4g2)9J5c8U0l9@1i4K6u0r3x3U0y4Q4x3V1k6g2L8X3W2U0L8%4u0F1i4K6t1#2c8e0g2Q4x3U0g2n7b7#2)9J5y4e0V1#2i4K6t1#2c8e0k6Q4x3U0f1&6x3#2)9J5y4e0S2q4i4K6t1#2c8e0g2Q4x3U0g2m8c8q4)9J5y4f1p5$3i4K6t1#2c8e0c8Q4x3U0g2n7z5g2)9J5y4f1p5H3i4K6u0r3
388K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2A6L8%4c8K6k6h3y4Q4x3X3c8*7L8$3&6W2i4K6u0W2j5$3!0E0i4K6u0r3j5i4u0@1K9h3y4D9k6g2)9J5c8U0x3^5x3l9`.`.
conda create -n Unicorn python=3.9
conda create -n Unicorn python=3.9
conda activate Unicorn
pip install unicorn
import unicorn
print(unicorn.__version__)
import unicorn
print(unicorn.__version__)
from unicorn import *
from unicorn.x86_const import *
mu = Uc(UC_ARCH_X86, UC_MODE_64)
from unicorn import *
from unicorn.x86_const import *
mu = Uc(UC_ARCH_X86, UC_MODE_64)
ADDRESS = 0x400000
MEM_SIZE = 2 * 1024 * 1024
STACK_ADDR = 0x0
STACK_SIZE = 1024 * 1024
mu.mem_map(ADDRESS, MEM_SIZE)
mu.mem_map(STACK_ADDR, STACK_SIZE)
ADDRESS = 0x400000
MEM_SIZE = 2 * 1024 * 1024
STACK_ADDR = 0x0
STACK_SIZE = 1024 * 1024
mu.mem_map(ADDRESS, MEM_SIZE)
mu.mem_map(STACK_ADDR, STACK_SIZE)
with open("./test", "rb") as f:
CODE = f.read()
mu.mem_write(ADDRESS, CODE)
with open("./test", "rb") as f:
CODE = f.read()
mu.mem_write(ADDRESS, CODE)
mu.reg_write(UC_X86_REG_RSP, STACK_ADDR + STACK_SIZE - 8)
mu.reg_write(UC_X86_REG_RSP, STACK_ADDR + STACK_SIZE - 8)
try:
print(">>> Start emulation...")
mu.emu_start(ADDRESS, ADDRESS + len(CODE))
print(">>> Emulation done.")
except UcError as e:
print(f"ERROR: {e}")
r_rax = mu.reg_read(UC_X86_REG_RAX)
print(f">>> RAX = 0x{r_rax:x}")
try:
print(">>> Start emulation...")
mu.emu_start(ADDRESS, ADDRESS + len(CODE))
print(">>> Emulation done.")
except UcError as e:
print(f"ERROR: {e}")
r_rax = mu.reg_read(UC_X86_REG_RAX)
print(f">>> RAX = 0x{r_rax:x}")
mu = Uc(UC_ARCH_X86, UC_MODE_32)
mu = Uc(UC_ARCH_X86, UC_MODE_32)
| 常量名 |
对应架构 |
说明 |
UC_ARCH_X86 |
x86 / x64 |
包含 Intel/AMD 的 16位、32位和 64位架构。 |
UC_ARCH_ARM |
ARM |
经典的 32位 ARM 架构(常见于旧版 Android)。 |
UC_ARCH_ARM64 |
AArch64 |
现代移动设备(Android/iOS)的主流 64位架构。 |
UC_ARCH_MIPS |
MIPS |
常见于路由器、IoT 设备。 |
| 常量名 |
说明 |
适用架构 |
UC_MODE_32 |
32位模式 |
x86, ARM, MIPS 等 |
UC_MODE_64 |
64位模式 |
x86, ARM64, MIPS64 等 |
UC_MODE_THUMB |
Thumb 模式 |
仅限 ARM。用于模拟 16位 Thumb 指令集。 |
UC_MODE_LITTLE_ENDIAN |
小端序 (默认) |
所有架构。数据低位存储在低地址。 |
UC_MODE_BIG_ENDIAN |
大端序 |
MIPS, PowerPC 等。数据高位存储在低地址。 |
mu = Uc(UC_ARCH_X86, UC_MODE_32)
mu = Uc(UC_ARCH_X86, UC_MODE_32)
mu = Uc(UC_ARCH_ARM64, UC_MODE_64)
mu = Uc(UC_ARCH_ARM64, UC_MODE_64)
mu = Uc(UC_ARCH_ARM, UC_MODE_THUMB)
mu = Uc(UC_ARCH_ARM, UC_MODE_THUMB)
mu = Uc(UC_ARCH_MIPS, UC_MODE_32 + UC_MODE_BIG_ENDIAN)
mu = Uc(UC_ARCH_MIPS, UC_MODE_32 + UC_MODE_BIG_ENDIAN)
ADDRESS = 0x400000
SIZE = 2 * 1024 * 1024
mu.mem_map(ADDRESS, SIZE)
DATA_ADDR = 0x800000
mu.mem_map(DATA_ADDR, 0x1000, UC_PROT_READ)
ADDRESS = 0x400000
SIZE = 2 * 1024 * 1024
mu.mem_map(ADDRESS, SIZE)
DATA_ADDR = 0x800000
mu.mem_map(DATA_ADDR, 0x1000, UC_PROT_READ)
machine_code = b"\x40"
mu.mem_write(ADDRESS, machine_code)
mu.mem_write(DATA_ADDR, b"Hello Unicorn")
machine_code = b"\x40"
mu.mem_write(ADDRESS, machine_code)
mu.mem_write(DATA_ADDR, b"Hello Unicorn")
data = mu.mem_read(DATA_ADDR, 5)
print(f"Read from memory: {bytes(data)}")
data = mu.mem_read(DATA_ADDR, 5)
print(f"Read from memory: {bytes(data)}")
| 常量名 |
权限 |
说明 |
UC_PROT_READ |
可读 (r) |
允许读取数据。 |
UC_PROT_WRITE |
可写 (w) |
允许写入数据。 |
UC_PROT_EXEC |
可执行 (x) |
允许 CPU 在此区域执行指令。 |
UC_PROT_ALL |
rwx |
全权限(默认值)。 |
UC_PROT_NONE |
无权限 |
禁止任何访问。 |
mu.mem_protect(ADDRESS, SIZE, UC_PROT_READ | UC_PROT_EXEC)
mu.mem_protect(ADDRESS, SIZE, UC_PROT_READ | UC_PROT_EXEC)
mu.reg_write(UC_X86_REG_EAX, 100)
STACK_ADDR = 0x0
STACK_SIZE = 1024 * 1024
mu.reg_write(UC_X86_REG_ESP, STACK_ADDR + STACK_SIZE - 4)
mu.reg_write(UC_X86_REG_EAX, 100)
STACK_ADDR = 0x0
STACK_SIZE = 1024 * 1024
mu.reg_write(UC_X86_REG_ESP, STACK_ADDR + STACK_SIZE - 4)
eax_val = mu.reg_read(UC_X86_REG_EAX)
print(f">>> EAX = 0x{eax_val:x}")
rip_val = mu.reg_read(UC_X86_REG_RIP)
eax_val = mu.reg_read(UC_X86_REG_EAX)
print(f">>> EAX = 0x{eax_val:x}")
rip_val = mu.reg_read(UC_X86_REG_RIP)
from unicorn.x86_const import *
UC_X86_REG_EAX, UC_X86_REG_EBX, UC_X86_REG_ECX ...
UC_X86_REG_RAX, UC_X86_REG_RBX ... (64位)
UC_X86_REG_EIP / UC_X86_REG_RIP
UC_X86_REG_ESP / UC_X86_REG_RSP
UC_X86_REG_EFLAGS
from unicorn.x86_const import *
UC_X86_REG_EAX, UC_X86_REG_EBX, UC_X86_REG_ECX ...
UC_X86_REG_RAX, UC_X86_REG_RBX ... (64位)
UC_X86_REG_EIP / UC_X86_REG_RIP
UC_X86_REG_ESP / UC_X86_REG_RSP
UC_X86_REG_EFLAGS
from unicorn.arm_const import *
UC_ARM_REG_R0, UC_ARM_REG_R1 ...
UC_ARM_REG_PC
UC_ARM_REG_SP
UC_ARM_REG_LR
UC_ARM_REG_CPSR
from unicorn.arm_const import *
UC_ARM_REG_R0, UC_ARM_REG_R1 ...
UC_ARM_REG_PC
UC_ARM_REG_SP
UC_ARM_REG_LR
UC_ARM_REG_CPSR
from unicorn.arm64_const import *
UC_ARM64_REG_X0 ...
UC_ARM64_REG_PC
UC_ARM64_REG_SP
UC_ARM64_REG_LR
from unicorn.arm64_const import *
UC_ARM64_REG_X0 ...
UC_ARM64_REG_PC
UC_ARM64_REG_SP
UC_ARM64_REG_LR
ADDRESS = 0x400000
CODE_LEN = 2 * 1024 * 1024
mu.emu_start(ADDRESS, ADDRESS + CODE_LEN)
try:
mu.emu_start(ADDRESS, ADDRESS + CODE_LEN, timeout=2 * 1000 * 1000)
except UcError as e:
print(f"Execution stopped: {e}")
mu.emu_start(ADDRESS, ADDRESS + CODE_LEN, count=1)
ADDRESS = 0x400000
CODE_LEN = 2 * 1024 * 1024
mu.emu_start(ADDRESS, ADDRESS + CODE_LEN)
try:
mu.emu_start(ADDRESS, ADDRESS + CODE_LEN, timeout=2 * 1000 * 1000)
except UcError as e:
print(f"Execution stopped: {e}")
mu.emu_start(ADDRESS, ADDRESS + CODE_LEN, count=1)
def hook_code(uc, address, size, user_data):
print(f">>> Tracing instruction at 0x{address:x}")
if address == 0x1000020:
print(">>> Breakpoint hit! Stopping emulation.")
uc.emu_stop()
mu.emu_start(ADDRESS, ADDRESS + CODE_LEN)
print(">>> Emulation finished or stopped by hook.")
def hook_code(uc, address, size, user_data):
print(f">>> Tracing instruction at 0x{address:x}")
if address == 0x1000020:
print(">>> Breakpoint hit! Stopping emulation.")
uc.emu_stop()
mu.emu_start(ADDRESS, ADDRESS + CODE_LEN)
print(">>> Emulation finished or stopped by hook.")
def hook_code(uc, address, size, user_data):
print(f">>> Tracing instruction at 0x{address:x}, instruction size = {size}")
def hook_code(uc, address, size, user_data):
print(f">>> Tracing instruction at 0x{address:x}, instruction size = {size}")
mu.hook_add(UC_HOOK_CODE, hook_code)
mu.hook_add(UC_HOOK_CODE, hook_code, begin=0x400000, end=0x400008)
mu.hook_add(UC_HOOK_CODE, hook_code)
mu.hook_add(UC_HOOK_CODE, hook_code, begin=0x400000, end=0x400008)
def hook_mem_access(uc, access, address, size, value, user_data):
if access == UC_MEM_WRITE:
print(f">>> Memory WRITE at 0x{address:x}, size={size}, value=0x{value:x}")
else:
print(f">>> Memory READ at 0x{address:x}, size={size}")
return False
def hook_mem_access(uc, access, address, size, value, user_data):
if access == UC_MEM_WRITE:
print(f">>> Memory WRITE at 0x{address:x}, size={size}, value=0x{value:x}")
else:
print(f">>> Memory READ at 0x{address:x}, size={size}")
return False
mu.hook_add(UC_HOOK_MEM_READ | UC_HOOK_MEM_WRITE, hook_mem_access)
mu.hook_add(UC_HOOK_MEM_READ | UC_HOOK_MEM_WRITE, hook_mem_access)
def hook_intr(uc, intno, user_data):
print(f"[INT] Triggered interrupt number: {intno}")
def hook_intr(uc, intno, user_data):
print(f"[INT] Triggered interrupt number: {intno}")
mu.hook_add(UC_HOOK_INTR, hook_intr)
mu.hook_add(UC_HOOK_INTR, hook_intr)
def trace_inst(uc, address, size, user_data):
rip = uc.reg_read(UC_X86_REG_RIP)
print(f"--- IP: 0x{rip:x} | Inst Size: {size} ---")
def hook_mem_invalid(uc, access, address, size, value, user_data):
access_type = {
UC_MEM_READ_UNMAPPED: "READ_UNMAPPED",
UC_MEM_WRITE_UNMAPPED: "WRITE_UNMAPPED",
UC_MEM_FETCH_UNMAPPED: "FETCH_UNMAPPED",
}.get(access, "UNKNOWN")
print(f"[CRASH] Invalid Memory {access_type} at 0x{address:x}, size={size}")
return False
def hook_intr(uc, intno, user_data):
print(f"[INT] Interrupt {intno} hit!")
print("[*] Installing hooks...")
mu.hook_add(UC_HOOK_CODE, trace_inst)
mu.hook_add(UC_HOOK_MEM_UNMAPPED, hook_mem_invalid)
mu.hook_add(UC_HOOK_INTR, hook_intr)
def trace_inst(uc, address, size, user_data):
rip = uc.reg_read(UC_X86_REG_RIP)
print(f"--- IP: 0x{rip:x} | Inst Size: {size} ---")
def hook_mem_invalid(uc, access, address, size, value, user_data):
access_type = {
UC_MEM_READ_UNMAPPED: "READ_UNMAPPED",
UC_MEM_WRITE_UNMAPPED: "WRITE_UNMAPPED",
UC_MEM_FETCH_UNMAPPED: "FETCH_UNMAPPED",
}.get(access, "UNKNOWN")
print(f"[CRASH] Invalid Memory {access_type} at 0x{address:x}, size={size}")
return False
def hook_intr(uc, intno, user_data):
print(f"[INT] Interrupt {intno} hit!")
print("[*] Installing hooks...")
mu.hook_add(UC_HOOK_CODE, trace_inst)
mu.hook_add(UC_HOOK_MEM_UNMAPPED, hook_mem_invalid)
mu.hook_add(UC_HOOK_INTR, hook_intr)
from unicorn import *
from unicorn.x86_const import *
X86_CODE32 = b"\x40"
ADDRESS = 0x1000000
MEM_SIZE = 2 * 1024 * 1024
def test_x86():
print("=== Unicorn x86-64 Demo Start ===")
try:
mu = Uc(UC_ARCH_X86, UC_MODE_32)
print(f"[*] Mapping memory at 0x{ADDRESS:x}, size={MEM_SIZE} bytes")
mu.mem_map(ADDRESS, MEM_SIZE)
print(f"[*] Writing machine code to 0x{ADDRESS:x}")
mu.mem_write(ADDRESS, X86_CODE32)
print("[*] Setting EAX = 100")
mu.reg_write(UC_X86_REG_EAX, 100)
print("[*] Starting emulation...")
mu.emu_start(ADDRESS, ADDRESS + len(X86_CODE32))
print("[*] Emulation done.")
r_eax = mu.reg_read(UC_X86_REG_EAX)
print(f">>> Result: EAX = {r_eax}")
if r_eax == 101:
print(">>> SUCCESS! (100 + 1 = 101)")
else:
print(">>> FAILED!")
except UcError as e:
print(f"ERROR: {e}")
if __name__ == '__main__':
test_x86()
from unicorn import *
from unicorn.x86_const import *
X86_CODE32 = b"\x40"
ADDRESS = 0x1000000
MEM_SIZE = 2 * 1024 * 1024
def test_x86():
print("=== Unicorn x86-64 Demo Start ===")
try:
mu = Uc(UC_ARCH_X86, UC_MODE_32)
print(f"[*] Mapping memory at 0x{ADDRESS:x}, size={MEM_SIZE} bytes")
mu.mem_map(ADDRESS, MEM_SIZE)
print(f"[*] Writing machine code to 0x{ADDRESS:x}")
mu.mem_write(ADDRESS, X86_CODE32)
print("[*] Setting EAX = 100")
mu.reg_write(UC_X86_REG_EAX, 100)
print("[*] Starting emulation...")
mu.emu_start(ADDRESS, ADDRESS + len(X86_CODE32))
print("[*] Emulation done.")
r_eax = mu.reg_read(UC_X86_REG_EAX)
print(f">>> Result: EAX = {r_eax}")
if r_eax == 101:
print(">>> SUCCESS! (100 + 1 = 101)")
else:
print(">>> FAILED!")
except UcError as e:
print(f"ERROR: {e}")
if __name__ == '__main__':
test_x86()
| 工具 |
角色 |
功能描述 |
核心能力 |
| Unicorn |
大脑 (CPU) |
模拟执行引擎 |
负责跑代码,改变寄存器与内存状态。 |
| Capstone |
眼睛 (Eye) |
反汇编框架 |
将二进制机器码翻译成汇编指令,让我们“看懂”正在执行什么。 |
| Keystone |
双手 (Hand) |
汇编框架 |
将汇编指令编译成机器码,用于动态 Patch 代码或生成 Shellcode。 |
from capstone import *
cs = Cs(CS_ARCH_X86, CS_MODE_32)
def hook_code(uc, address, size, user_data):
code = uc.mem_read(address, size)
for insn in cs.disasm(code, address):
print(f"0x{insn.address:x}:\t{insn.mnemonic}\t{insn.op_str}")
from capstone import *
cs = Cs(CS_ARCH_X86, CS_MODE_32)
def hook_code(uc, address, size, user_data):
code = uc.mem_read(address, size)
for insn in cs.disasm(code, address):
print(f"0x{insn.address:x}:\t{insn.mnemonic}\t{insn.op_str}")
from keystone import *
ks = Ks(KS_ARCH_X86, KS_MODE_32)
encoding, count = ks.asm(b"mov rax, 0x1234; inc rax")
machine_code = bytes(encoding)
uc.mem_write(0x400000, machine_code)
from keystone import *
ks = Ks(KS_ARCH_X86, KS_MODE_32)
encoding, count = ks.asm(b"mov rax, 0x1234; inc rax")
[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!