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_mapmem_readmem_write 管理虚拟内存
  • 寄存器操作reg_readreg_write 读写 CPU 寄存器
  • 执行控制emu_startemu_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:将人的意图(汇编语言)转化为机器码,是代码修改和生成的基础

在实际项目中,这三个工具几乎总是配合使用。理解它们各自的定位和联动方式,是构建高效逆向分析工作流的关键。