通过前面的文章:[原创]深入浅出 Unicorn 框架学习
我们已经掌握了 Unicorn 框架的基础,你会发现它虽然强大,但有一个明显的缺陷——它是一个只有大脑,没有眼睛的模拟器。
Unicorn 是一个纯粹的 CPU 模拟执行引擎。它极其擅长执行二进制流,改变寄存器和内存的状态。
在 Unicorn 的眼中,一切皆是 0 和 1。它不知道当前是在做加法(ADD)还是在跳转(JMP),这使得我们在调试和分析时如同盲人摸象。
Capstone 就是我们为模拟器安装的 “眼睛” (反汇编引擎)。
将 Capstone 引入 Unicorn 的 Hook 回调中,我们就能实现从“黑盒运行”到“白盒监控”的质变:
在开始编写代码之前,我们需要先搭建环境,并理解 Capstone 的几个核心概念。
Capstone 提供了完美的 Python 绑定,安装非常简单:
注:建议在与 Unicorn 相同的虚拟环境(conda/venv)中安装,确保两者可以无缝协作。

在使用 Capstone 时,我们主要会和以下两个类打交道:
Cs (Capstone Engine)
这是反汇编引擎的核心类。你需要实例化它来创建一个反汇编器对象。
CsInsn (Capstone Instruction)
这是“指令对象”。每当你反汇编一条机器码,Capstone 就会返回一个 CsInsn 实例。
关键属性:
Capstone 的架构常量命名规范与 Unicorn 高度相似,使用时必须保持一致。
初始化原型:md = Cs(arch, mode)
模式对应表:
在将 Capstone 集成到 Unicorn 之前,我们先单独运行它,体验一下如何将一段纯粹的二进制数据(Bytes)还原为汇编代码。
反汇编一段 x64 机器码:\x55\x48\x8b\x05\xb8\x13\x00\x00。
运行上述脚本,你将看到如下输出:

关键点解读:
这部分我们将打通 Unicorn(大脑)和 Capstone(眼睛)的连接。通过在 Unicorn 的 UC_HOOK_CODE 回调中调用 Capstone,我们可以实时打印出 CPU 正在执行的每一条汇编指令,实现类似 GDB 或 x64dbg 的 Trace 功能。
要在 Hook 中高效使用 Capstone,必须遵循以下步骤:
全局初始化:在调用 emu_start 之前,先初始化好 Cs 实例。
Hook 注入:编写 hook_code 回调函数。
读取指令:在 Hook 中,利用 address 和 size 参数,通过 uc.mem_read 从内存中取出当前指令的机器码。
反汇编:将取出的机器码喂给 cs.disasm。
输出日志:格式化打印指令的地址、助记符和操作数。
下面的脚本模拟了一段 x64 汇编代码,该代码通过 Linux 系统调用 (syscall) 打印 "Hello Unicorn"。我们将通过 Capstone 实时监控这一过程。
运行上述脚本,你将看到如下输出。每一条指令在被 CPU 执行前,都被 Capstone 捕获并翻译了出来:

通过这种方式,我们成功构建了一个可视化的执行流监视器!
在基础用法中,我们只能拿到指令的字符串(如 "mov rax, 1")。虽然我们可以用 if "mov" in mnemonic 来做判断,但这种字符串匹配是非常脆弱且不严谨的。
Capstone 提供了强大的 Detail Mode(详细模式) ,开启后,它会帮我们把指令“解剖”成结构化的数据,让我们能理解指令的语义。
默认情况下,为了性能,Capstone 不会分析指令细节。我们需要手动开启:
开启详细模式后,CsInsn 对象会多出几个关键属性:
很多指令在表面上看不出它修改了谁。例如 push rax,它不仅读取了 rax,还隐式修改了 rsp(栈指针)。
在逆向分析中,我们往往不需要关心具体的指令是 JE 还是 JNE,是 CALL 还是 SYSCALL,而只需要知道它们的行为类别。Capstone 的 Groups 机制提供了一种与架构无关的通用分类方法。
常用组常量:
这些常量定义在 capstone 模块中。
实战用法:
这是详细模式中最强大的功能。通过遍历 insn.operands 列表,我们可以精准地提取每一个操作数的类型和值。
对于 x86 架构,操作数主要分为三类。我们需要引入 capstone.x86 模块来使用相关常量。
结构:操作数包含一个 reg 属性,存储寄存器的 ID。
示例:inc rax 中的 rax。
代码:
结构:操作数包含一个 imm 属性,存储具体的数值。
示例:mov rax, 0x100 中的 0x100。
代码:
这是 x86 汇编中最灵活、也最复杂的操作数类型。一条典型的内存访问指令(如 LEA 或 MOV)可能包含极其复杂的寻址模式。
典型格式:[Base + Index * Scale + Displacement]
例如:mov eax, [rbp + rbx*4 - 0x10]
Capstone 将这种结构解析为一个 op.mem 对象,包含以下四个核心字段:
关键 API 解析
insn.reg_name(reg_id)
深度解析示例
运行输出:

下面的脚本演示了如何利用详细模式,精准捕捉所有显式或隐式写入了 RAX 寄存器的指令。
运行结果:

push rax 读取了 rax 但没进行写入,所以结果是 PASS
将 Capstone 与 Unicorn 结合,我们可以解决逆向工程中许多棘手的难题。以下是三个最经典的应用场景。
痛点:在模拟执行大体积二进制文件时,程序往往会调用大量的外部库函数(如 printf, memcpy)。如果我们不想去模拟这些函数的具体逻辑,也不想程序因为跳到非法地址而 Crash,最好的办法就是统统跳过。
解决方案:
在 Hook 中实时检测当前指令。如果是 CALL 类型指令,直接修改 RIP 指针到下一条指令,从而实现“视而不见”。
痛点:在分析混淆代码(如 OLLVM)或去除非法路径时,我们需要知道程序到底走了哪些分支。
解决方案:
基本块(Basic Block)通常以跳转指令结束。通过监控 CS_GRP_JUMP(跳转)和 CS_GRP_RET(返回)指令,我们可以精准捕捉程序的每一次“变道”,从而绘制出真实的执行流图(CFG)。
痛点:恶意软件往往会通过 RDTSC 检测调试器,通过 CPUID 检测虚拟机,或者通过 SYSCALL 进行文件操作。
解决方案:
利用 Capstone 的助记符匹配或指令分组,建立一个“敏感指令黑名单”。一旦模拟执行到这些指令,立即报警或暂停。
Capstone 虽然强大,但如果使用不当,很容易成为整个模拟器的性能瓶颈。以下是几个关键的优化技巧和避坑建议。
这是最容易犯的致命错误。
错误写法:
后果:Unicorn 每秒可能执行数万条指令。如果在 Hook 中实例化 Cs,模拟速度将下降 100 倍以上。
正确做法:在 emu_start 之前全局初始化 Cs 实例,并在 Hook 中重复使用。
在大多数场景下(例如仅打印指令 Trace),我们并不需要分析操作数类型(寄存器还是内存)或指令分组。我们只需要知道“这是一条 MOV 指令”就够了。
此时可以使用 disasm_lite。
disasm (标准模式) :
disasm_lite (轻量模式) :
输出:

在 Unicorn 的 Hook 回调中,如果你的目的仅仅是打印 0x4000: mov rax, 1 这样的日志,首选 disasm_lite。只有当你需要判断 insn.group(CS_GRP_CALL) 或分析操作数时,才使用标准模式。
通过学习了解,我们得知:
两者的结合,让我们不仅能“跑”代码,更能“看懂”代码,进而实现从自动化追踪到恶意代码分析的各种高级功能。接下来的文章,将引入 Keystone(汇编框架/引擎),实现动态代码修改。
pip install capstone
| 架构 |
Unicorn 常量 |
Capstone 常量 |
说明 |
| x86 / x64 |
UC_ARCH_X86 |
CS_ARCH_X86 |
通用 Intel/AMD 架构 |
| ARM |
UC_ARCH_ARM |
CS_ARCH_ARM |
32位 ARM |
| ARM64 |
UC_ARCH_ARM64 |
CS_ARCH_ARM64 |
64位 ARM (AArch64) |
| 模式 |
Unicorn 常量 |
Capstone 常量 |
| 32位 |
UC_MODE_32 |
CS_MODE_32 |
| 64位 |
UC_MODE_64 |
CS_MODE_64 |
| Thumb |
UC_MODE_THUMB |
CS_MODE_THUMB |
| Little Endian |
UC_MODE_LITTLE_ENDIAN |
CS_MODE_LITTLE_ENDIAN |
from capstone import *
CODE = b"\x55\x48\x8b\x05\xb8\x13\x00\x00"
cs = Cs(CS_ARCH_X86, CS_MODE_64)
print(f"Disassembly result:")
print("-" * 30)
for insn in cs.disasm(CODE, 0x1000):
print(f"0x{insn.address:x}:\t{insn.mnemonic}\t{insn.op_str}")
print("Machine code: ", end="")
for i in insn.bytes:
print(f"{i:02x}", end=" ")
print()
print("-" * 30)
from capstone import *
CODE = b"\x55\x48\x8b\x05\xb8\x13\x00\x00"
cs = Cs(CS_ARCH_X86, CS_MODE_64)
print(f"Disassembly result:")
print("-" * 30)
for insn in cs.disasm(CODE, 0x1000):
print(f"0x{insn.address:x}:\t{insn.mnemonic}\t{insn.op_str}")
print("Machine code: ", end="")
for i in insn.bytes:
print(f"{i:02x}", end=" ")
print()
print("-" * 30)
from unicorn import *
from unicorn.x86_const import *
from capstone import *
CODE = b"\x48\xc7\xc7\x01\x00\x00\x00\x48\x8d\x35\x10\x00\x00\x00\x48\xc7\xc2\x0d\x00\x00\x00\x48\xc7\xc0\x01\x00\x00\x00\x0f\x05"
DATA = b"Hello Unicorn"
ADDRESS = 0x400000
MEM_SIZE = 2 * 1024 * 1024
uc = Uc(UC_ARCH_X86, UC_MODE_64)
cs = Cs(CS_ARCH_X86, CS_MODE_64)
def hook_code(uc, address, size, user_data):
try:
code = uc.mem_read(address, size)
except:
return
for insn in cs.disasm(code, address):
print(f"0x{insn.address:x}:\t{insn.mnemonic}\t{insn.op_str}")
print("Machine code: ", end="")
for i in range(len(insn.bytes)):
print(f"{insn.bytes[i]:02x} ", end="")
print()
if insn.mnemonic == "syscall":
print(">>> Syscall: write")
print("-" * 20)
return
uc.mem_map(ADDRESS, MEM_SIZE)
uc.mem_write(ADDRESS, CODE)
uc.mem_write(ADDRESS + len(CODE), DATA)
uc.hook_add(UC_HOOK_CODE, hook_code)
print(">>> Start Tracing...")
print("-" * 20)
try:
uc.emu_start(ADDRESS, ADDRESS + len(CODE))
except UcError as e:
print("-" * 20)
print(f">>> Emulation stopped: {e}")
from unicorn import *
from unicorn.x86_const import *
from capstone import *
CODE = b"\x48\xc7\xc7\x01\x00\x00\x00\x48\x8d\x35\x10\x00\x00\x00\x48\xc7\xc2\x0d\x00\x00\x00\x48\xc7\xc0\x01\x00\x00\x00\x0f\x05"
DATA = b"Hello Unicorn"
ADDRESS = 0x400000
MEM_SIZE = 2 * 1024 * 1024
uc = Uc(UC_ARCH_X86, UC_MODE_64)
cs = Cs(CS_ARCH_X86, CS_MODE_64)
def hook_code(uc, address, size, user_data):
try:
code = uc.mem_read(address, size)
except:
return
for insn in cs.disasm(code, address):
print(f"0x{insn.address:x}:\t{insn.mnemonic}\t{insn.op_str}")
print("Machine code: ", end="")
for i in range(len(insn.bytes)):
print(f"{insn.bytes[i]:02x} ", end="")
print()
if insn.mnemonic == "syscall":
print(">>> Syscall: write")
print("-" * 20)
return
uc.mem_map(ADDRESS, MEM_SIZE)
uc.mem_write(ADDRESS, CODE)
uc.mem_write(ADDRESS + len(CODE), DATA)
uc.hook_add(UC_HOOK_CODE, hook_code)
print(">>> Start Tracing...")
print("-" * 20)
try:
uc.emu_start(ADDRESS, ADDRESS + len(CODE))
except UcError as e:
print("-" * 20)
print(f">>> Emulation stopped: {e}")
md = Cs(CS_ARCH_X86, CS_MODE_64)
md.detail = True
md = Cs(CS_ARCH_X86, CS_MODE_64)
md.detail = True
if insn.group(CS_GRP_JUMP):
print(f"Detected a jump at 0x{insn.address:x}")
elif insn.group(CS_GRP_CALL):
print(f"Detected a function call at 0x{insn.address:x}")
elif insn.group(CS_GRP_RET):
print("End of function detected")
if insn.group(CS_GRP_JUMP):
print(f"Detected a jump at 0x{insn.address:x}")
elif insn.group(CS_GRP_CALL):
print(f"Detected a function call at 0x{insn.address:x}")
elif insn.group(CS_GRP_RET):
print("End of function detected")
if op.type == X86_OP_REG:
print(f"Register: {insn.reg_name(op.reg)}")
if op.type == X86_OP_REG:
print(f"Register: {insn.reg_name(op.reg)}")
if op.type == X86_OP_IMM:
print(f"Immediate: 0x{op.imm:x}")
if op.type == X86_OP_IMM:
print(f"Immediate: 0x{op.imm:x}")
| 字段 |
类型 |
说明 |
示例 ([rbp+rbx*4-0x10]) |
base |
int (Reg ID) |
基址寄存器。若无基址,值为 0 (X86_REG_INVALID)。 |
rbp |
index |
int (Reg ID) |
变址寄存器。若无变址,值为 0。 |
rbx |
scale |
int |
比例因子。通常为 1, 2, 4, 8。默认 1。 |
4 |
disp |
int |
偏移量(Displacement)。正负整数。 |
-0x10 |
from capstone import *
from capstone.x86 import *
CODE = b"\x8b\x44\x9d\xf0"
md = Cs(CS_ARCH_X86, CS_MODE_64)
md.detail = True
for insn in md.disasm(CODE, 0x1000):
print(f"Instruction: {insn.mnemonic} {insn.op_str}")
for i, op in enumerate(insn.operands):
if op.type == X86_OP_MEM:
print(f"--- Operand {i} is MEMORY ---")
mem = op.mem
if mem.base != 0:
print(f" Base : {insn.reg_name(mem.base)}")
else:
print(f" Base : None")
if mem.index != 0:
print(f" Index: {insn.reg_name(mem.index)}")
print(f" Scale: {mem.scale}")
if mem.disp != 0:
print(f" Disp : {mem.disp:#x}")
from capstone import *
from capstone.x86 import *
CODE = b"\x8b\x44\x9d\xf0"
md = Cs(CS_ARCH_X86, CS_MODE_64)
md.detail = True
for insn in md.disasm(CODE, 0x1000):
print(f"Instruction: {insn.mnemonic} {insn.op_str}")
for i, op in enumerate(insn.operands):
if op.type == X86_OP_MEM:
传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 3小时前
被xiusi编辑
,原因: