Capstone、Unicorn、Keystone 工具的介绍
Capstone、Unicorn、Keystone 工具的介绍
在逆向工程和二进制分析领域,有三个基于同一技术体系的工具经常被配合使用:Capstone(反汇编引擎)、Unicorn(CPU 模拟器)和 Keystone(汇编引擎)。它们三者都基于 QEMU 的相关技术,分别覆盖了二进制分析中"反汇编→模拟执行→汇编"的完整链路。本文将详细介绍这三个工具的定位、功能和使用方法。
三大工具的定位和关系
在理解这三个工具之前,先用一个类比来说明它们各自的角色:
| 工具 | 功能 | 类比 | 方向 |
|---|---|---|---|
| Capstone | 反汇编引擎 | “翻译官”:将机器码翻译成人能读懂的汇编语言 | 机器码 → 汇编 |
| Unicorn | CPU 模拟器 | “虚拟 CPU”:在电脑上模拟执行各种架构的指令 | 执行机器码 |
| Keystone | 汇编引擎 | “编译器”:将汇编语言翻译成机器码 | 汇编 → 机器码 |
三者之间的关系可以用下图描述:
汇编文本 ──Keystone──→ 机器码 ──Unicorn──→ 执行结果
↑ ↓
└──────── Capstone ←──────────────┘
(反汇编)
它们共享的技术特点:
- 都支持 ARM、ARM64、x86、x64、MIPS、PowerPC 等多种架构
- 都提供 Python、C/C++ 等多语言绑定
- 都由 Nguyen Anh Quynh 主导开发,API 风格高度一致
- 都是开源项目,社区活跃
Capstone 反汇编引擎
Capstone 是一个轻量级的多平台多架构反汇编框架。它的核心能力是将二进制机器码反汇编成可读的汇编指令。
安装
pip install capstone
支持的架构
Capstone 支持的架构包括:
- ARM:ARM32(ARM、Thumb、Thumb-2 指令集)
- ARM64:AArch64
- x86:16/32/64 位
- MIPS:MIPS32/64(大端/小端/微码)
- PowerPC、SPARC、SystemZ、XCore、M68K、TMS320C64X、M680X、EVM
基本 API 用法
from capstone import *
# 创建反汇编器
# 参数: 架构, 模式
md = Cs(CS_ARCH_ARM, CS_MODE_ARM)
# 反汇编机器码
machine_code = bytes.fromhex("01 00 a0 e3 02 00 a0 e3 00 20 80 e0 1e ff 2f e1")
for insn in md.disasm(machine_code, 0x10000):
print(f"地址: {insn.address:#x}\t指令: {insn.mnemonic}\t操作数: {insn.op_str}")
输出:
地址: 0x10000 指令: mov 操作数: r0, #1
地址: 0x10004 指令: mov 操作数: r1, #2
地址: 0x10008 指令: add 操作数: r2, r0, r1
地址: 0x1000c 指令: bx 操作数: lr
详细模式
Capstone 的 detail 模式可以提供更丰富的信息,包括操作数类型、寄存器使用情况、内存访问信息等:
from capstone import *
md = Cs(CS_ARCH_ARM, CS_MODE_ARM)
md.detail = True # 启用详细模式
code = bytes.fromhex("01 00 a0 e3")
for insn in md.disasm(code, 0x10000):
print(f"指令: {insn.mnemonic} {insn.op_str}")
print(f"大小: {insn.size} 字节")
print(f"前缀: {insn.prefix}")
print(f"操作数数量: {len(insn.operands)}")
# 访问每个操作数的详细信息
for op in insn.operands:
if op.type == CS_OP_REG:
# 寄存器操作数
print(f" 寄存器: {op.reg}")
elif op.type == CS_OP_IMM:
# 立即数操作数
print(f" 立即数: {op.imm:#x}")
elif op.type == CS_OP_MEM:
# 内存操作数
print(f" 内存基址: {op.mem.base}, 索引: {op.mem.index}, 位移: {op.mem.disp}")
ARM/Thumb 反汇编
ARM 架构有 ARM 和 Thumb 两种指令集模式。Capstone 支持分别处理:
from capstone import *
# ARM 模式(32 位定长指令)
md_arm = Cs(CS_ARCH_ARM, CS_MODE_ARM)
# Thumb 模式(16/32 位混合指令)
md_thumb = Cs(CS_ARCH_ARM, CS_MODE_THUMB)
arm_code = bytes.fromhex("05 00 a0 e3") # MOV R0, #5
thumb_code = bytes.fromhex("05 20") # MOVS R0, #5
print("ARM 模式:")
for insn in md_arm.disasm(arm_code, 0):
print(f" {insn.mnemonic} {insn.op_str}")
print("Thumb 模式:")
for insn in md_thumb.disasm(thumb_code, 0):
print(f" {insn.mnemonic} {insn.op_str}")
实际应用:反汇编 SO 文件
from capstone import *
import lief
def disasm_so(so_path):
"""反汇编 SO 文件中的代码段"""
binary = lief.parse(so_path)
# 选择反汇编模式
header = binary.header
if header.machine_type == lief.ELF.ARCH.ARM:
md = Cs(CS_ARCH_ARM, CS_MODE_ARM)
elif header.machine_type == lief.ELF.ARCH.AARCH64:
md = Cs(CS_ARCH_ARM64, CS_MODE_ARM)
else:
print("不支持的架构")
return
md.detail = True
# 反汇编 .text 段
text_section = binary.get_section(".text")
if text_section:
code = bytes(text_section.content)
base_addr = text_section.virtual_address
for insn in md.disasm(code, base_addr):
print(f"{insn.address:#010x}: {insn.mnemonic:8s} {insn.op_str}")
disasm_so("lib/armeabi-v7a/libnative.so")
Unicorn 模拟执行框架
Unicorn 的核心概念和基本用法在上一篇已经详细介绍过。这里补充它与 Capstone、Keystone 配合使用的关键点。
核心概念回顾
- 引擎实例:
Uc(arch, mode)创建指定架构的模拟器 - 内存管理:
mem_map、mem_read、mem_write管理虚拟内存 - 寄存器操作:
reg_read、reg_write读写 CPU 寄存器 - 执行控制:
emu_start、emu_stop控制执行流程 - Hook 机制:
hook_add注册回调,监控代码执行、内存访问、中断等
与 Capstone 联合使用
最经典的组合方式是在 Unicorn 的代码执行 hook 中使用 Capstone 进行实时反汇编:
from unicorn import *
from unicorn.arm_const import *
from capstone import *
# 创建模拟器和反汇编器
uc = Uc(UC_ARCH_ARM, UC_MODE_ARM)
cs = Cs(CS_ARCH_ARM, CS_MODE_ARM)
# 在代码执行时自动反汇编
def hook_code(uc, address, size, user_data):
code = bytes(uc.mem_read(address, size))
for insn in cs.disasm(code, address):
print(f"{insn.address:#x}: {insn.mnemonic} {insn.op_str}")
break
uc.hook_add(UC_HOOK_CODE, hook_code)
基本使用示例
from unicorn import *
from unicorn.arm_const import *
# ARM 代码:ADD R0, R1, R2; BX LR
code = b'\x01\x10\x81\xe0\x1e\xff\x2f\xe1'
uc = Uc(UC_ARCH_ARM, UC_MODE_ARM)
uc.mem_map(0x1000, 0x1000)
uc.mem_write(0x1000, code)
# 设置输入
uc.reg_write(UC_ARM_REG_R1, 10)
uc.reg_write(UC_ARM_REG_R2, 20)
uc.reg_write(UC_ARM_REG_LR, 0x2000)
uc.emu_start(0x1000, 0x2000)
result = uc.reg_read(UC_ARM_REG_R0)
print(f"R1 + R2 = {result}") # 输出: 30
Keystone 汇编引擎
Keystone 是 Capstone 的"逆操作"——它将汇编文本转换为机器码。这在需要动态生成代码的场景中非常有用。
安装
pip install keystone-engine
基本 API 用法
from keystone import *
# 创建汇编器
ks = Ks(KS_ARCH_ARM, KS_MODE_ARM)
# 将汇编文本转换为机器码
assembly = "MOV R0, #0x42"
encoding, count = ks.asm(assembly)
print(f"机器码: {bytes(encoding).hex()}") # 输出十六进制机器码
print(f"指令数: {count}")
ARM/Thumb 汇编
from keystone import *
# ARM 模式汇编
ks_arm = Ks(KS_ARCH_ARM, KS_MODE_ARM)
code, _ = ks_arm.asm("ADD R0, R1, R2")
print(f"ARM: {bytes(code).hex()}")
# Thumb 模式汇编
ks_thumb = Ks(KS_ARCH_ARM, KS_MODE_THUMB)
code, _ = ks_thumb.asm("MOV R0, #5")
print(f"Thumb: {bytes(code).hex()}")
使用标签和复杂指令
from keystone import *
ks = Ks(KS_ARCH_ARM, KS_MODE_ARM)
# 多行汇编
assembly = """
PUSH {R4, LR}
MOV R4, R0
MOV R0, #1
BL func
MOV R0, R4
POP {R4, PC}
"""
try:
code, count = ks.asm(assembly)
print(f"成功汇编 {count} 条指令")
print(f"机器码: {bytes(code).hex()}")
except KsError as e:
print(f"汇编错误: {e}")
动态生成 Shellcode
Keystone 的一个典型应用是动态生成 shellcode:
from keystone import *
def generate_arm_shellcode(value):
"""动态生成 ARM shellcode"""
ks = Ks(KS_ARCH_ARM, KS_MODE_ARM)
# 使用占位符动态生成代码
assembly = f"""
MOV R0, #{value}
BX LR
"""
code, _ = ks.asm(assembly)
return bytes(code)
# 生成不同参数的 shellcode
code1 = generate_arm_shellcode(0x1234)
code2 = generate_arm_shellcode(0xDEAD)
print(f"Shellcode 1: {code1.hex()}")
print(f"Shellcode 2: {code2.hex()}")
三者联动的典型应用场景
场景一:反编译 → 修改 → 汇编 → 执行验证
这是最经典的三者联动场景,完整的逆向分析工作流:
from capstone import *
from keystone import *
from unicorn import *
from unicorn.arm_const import *
# 原始机器码
original_code = bytes.fromhex("05 00 a0 e3 03 10 a0 e3 02 20 80 e0 1e ff 2f e1")
# 第一步:Capstone 反编译 —— 理解代码逻辑
cs = Cs(CS_ARCH_ARM, CS_MODE_ARM)
print("=== 反汇编原始代码 ===")
for insn in cs.disasm(original_code, 0x10000):
print(f"{insn.address:#x}: {insn.mnemonic} {insn.op_str}")
# 第二步:修改汇编 —— 将 MOV R0, #5 改为 MOV R0, #100
modified_asm = """
MOV R0, #100
MOV R1, #3
ADD R2, R0, R1
BX LR
"""
# 第三步:Keystone 汇编 —— 生成新的机器码
ks = Ks(KS_ARCH_ARM, KS_MODE_ARM)
new_code, _ = ks.asm(modified_asm)
new_code = bytes(new_code)
print("\n=== 汇编修改后的代码 ===")
for insn in cs.disasm(new_code, 0x10000):
print(f"{insn.address:#x}: {insn.mnemonic} {insn.op_str}")
# 第四步:Unicorn 执行验证 —— 确认修改后的逻辑正确
uc = Uc(UC_ARCH_ARM, UC_MODE_ARM)
uc.mem_map(0x10000, 0x1000)
uc.mem_map(0x20000, 0x1000)
uc.mem_write(0x10000, new_code)
uc.reg_write(UC_ARM_REG_LR, 0x20000)
uc.emu_start(0x10000, 0x20000)
result = uc.reg_read(UC_ARM_REG_R2)
print(f"\n=== 执行结果 ===")
print(f"R2 (R0 + R1 = 100 + 3) = {result}") # 输出: 103
场景二:动态 Patch SO 文件
在逆向分析中,经常需要修改 SO 文件中的某些函数逻辑:
from capstone import *
from keystone import *
def patch_function(so_data, func_offset, original_size):
"""替换 SO 文件中的函数实现"""
# 反汇编原始函数,理解其行为
cs = Cs(CS_ARCH_ARM, CS_MODE_ARM)
original_code = so_data[func_offset:func_offset+original_size]
print("原始函数:")
for insn in cs.disasm(bytes(original_code), func_offset):
print(f" {insn.address:#x}: {insn.mnemonic} {insn.op_str}")
# 使用 Keystone 生成替换代码
# 新函数直接返回固定值
ks = Ks(KS_ARCH_ARM, KS_MODE_ARM)
patch_code, _ = ks.asm("MOV R0, #1; BX LR")
patch_code = bytes(patch_code)
# 确保补丁不超过原始函数大小
if len(patch_code) <= original_size:
# 替换代码
new_data = bytearray(so_data)
new_data[func_offset:func_offset+len(patch_code)] = patch_code
# 用 NOP 填充剩余空间
nop = bytes.fromhex("00 00 a0 e1") # ARM NOP
for i in range(len(patch_code), original_size, 4):
new_data[func_offset+i:func_offset+i+4] = nop
print(f"\n补丁已应用: {func_offset:#x}, 大小 {len(patch_code)} 字节")
return bytes(new_data)
else:
print("补丁过大,无法应用")
return None
场景三:模拟执行 + 实时反汇编调试器
将三者结合,构建一个功能完善的调试器:
from unicorn import *
from unicorn.arm_const import *
from capstone import *
from keystone import *
class InteractiveDebugger:
def __init__(self, arch=UC_ARCH_ARM, mode=UC_MODE_ARM):
self.uc = Uc(arch, mode)
# 配置对应的 Capstone 和 Keystone
if arch == UC_ARCH_ARM and mode == UC_MODE_ARM:
self.cs = Cs(CS_ARCH_ARM, CS_MODE_ARM)
self.ks = Ks(KS_ARCH_ARM, KS_MODE_ARM)
elif arch == UC_ARCH_ARM64:
self.cs = Cs(CS_ARCH_ARM64, CS_MODE_ARM)
self.ks = Ks(KS_ARCH_ARM64, KS_MODE_ARM)
self.breakpoints = set()
self.trace = False
def load_code(self, addr, code):
"""加载代码到指定地址"""
self.uc.mem_map(addr, 0x1000)
self.uc.mem_write(addr, code)
return addr
def assemble(self, asm_str, addr=0):
"""使用 Keystone 汇编"""
code, count = self.ks.asm(asm_str, addr)
return bytes(code)
def disasm_at(self, addr, count=1):
"""使用 Capstone 反汇编指定地址"""
code = bytes(self.uc.mem_read(addr, count * 4))
result = []
for insn in self.cs.disasm(code, addr):
result.append((insn.address, insn.mnemonic, insn.op_str))
if len(result) >= count:
break
return result
def add_breakpoint(self, addr):
self.breakpoints.add(addr)
def step(self):
"""单步执行"""
pc = self.uc.reg_read(UC_ARM_REG_PC)
disasm = self.disasm_at(pc)
if disasm:
addr, mnem, ops = disasm[0]
print(f"[{addr:#x}] {mnem} {ops}")
# 读取关键寄存器
r0 = self.uc.reg_read(UC_ARM_REG_R0)
r1 = self.uc.reg_read(UC_ARM_REG_R1)
print(f" R0={r0:#x} R1={r1:#x}")
# 计算下一条指令地址
self.uc.emu_start(pc, pc + 4, count=1)
总结
Capstone、Unicorn、Keystone 三大工具覆盖了逆向分析中最核心的三个操作:
- Capstone:将不可读的机器码转化为可读的汇编指令,是静态分析的基础
- Unicorn:在可控环境中执行代码,观察运行时行为,是动态分析的核心
- Keystone:将人的意图(汇编语言)转化为机器码,是代码修改和生成的基础
在实际项目中,这三个工具几乎总是配合使用。理解它们各自的定位和联动方式,是构建高效逆向分析工作流的关键。