首页
社区
课程
招聘
[原创]深入浅出 Capstone 框架学习
发表于: 4小时前 100

[原创]深入浅出 Capstone 框架学习

4小时前
100

通过前面的文章:[原创]深入浅出 Unicorn 框架学习

我们已经掌握了 Unicorn 框架的基础,你会发现它虽然强大,但有一个明显的缺陷——它是一个只有大脑,没有眼睛的模拟器。

Unicorn 是一个纯粹的 CPU 模拟执行引擎。它极其擅长执行二进制流,改变寄存器和内存的状态。
在 Unicorn 的眼中,一切皆是 0 和 1。它不知道当前是在做加法(ADD​)还是在跳转(JMP),这使得我们在调试和分析时如同盲人摸象。

Capstone 就是我们为模拟器安装的 “眼睛” (反汇编引擎)。

将 Capstone 引入 Unicorn 的 Hook 回调中,我们就能实现从“黑盒运行”到“白盒监控”的质变:

在开始编写代码之前,我们需要先搭建环境,并理解 Capstone 的几个核心概念。

Capstone 提供了完美的 Python 绑定,安装非常简单:

:建议在与 Unicorn 相同的虚拟环境(conda/venv)中安装,确保两者可以无缝协作。

image

在使用 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

运行上述脚本,你将看到如下输出:

image

关键点解读

这部分我们将打通 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 捕获并翻译了出来:

image

通过这种方式,我们成功构建了一个可视化的执行流监视器!

在基础用法中,我们只能拿到指令的字符串(如 "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)

深度解析示例

运行输出

image

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

运行结果

image

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(轻量模式)

输出

image

在 Unicorn 的 Hook 回调中,如果你的目的仅仅是打印 0x4000: mov rax, 1​ 这样的日志,首选 disasm_lite​。只有当你需要判断 insn.group(CS_GRP_CALL) 或分析操作数时,才使用标准模式。

通过学习了解,我们得知:

两者的结合,让我们不仅能“跑”代码,更能“看懂”代码,进而实现从自动化追踪到恶意代码分析的各种高级功能。接下来的文章,将引入 Keystone(汇编框架/引擎),实现动态代码修改。

pip install capstone
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 *
 
# 1. 准备机器码 (Shellcode)
# 这是一段 x64 机器码
CODE = b"\x55\x48\x8b\x05\xb8\x13\x00\x00"
 
# 2. 初始化反汇编引擎
# 架构: x86, 模式: 64位
cs = Cs(CS_ARCH_X86, CS_MODE_64)
 
print(f"Disassembly result:")
print("-" * 30)
 
# 3. 执行反汇编
# md.disasm(code, offset)
# code:   要反汇编的二进制数据 (bytes)
# offset: 这段代码在内存中的起始地址 (int),用于计算相对跳转等地址
# 返回值: 一个生成器 (generator),每次迭代返回一个 CsInsn 对象
for insn in cs.disasm(CODE, 0x1000):
    # 4. 打印指令详情
    # address:  指令地址
    # mnemonic: 助记符 (如 mov, call)
    # op_str:   操作数字符串 (如 rax, 1)
    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 *
 
# 1. 准备机器码 (Shellcode)
# 这是一段 x64 机器码
CODE = b"\x55\x48\x8b\x05\xb8\x13\x00\x00"
 
# 2. 初始化反汇编引擎
# 架构: x86, 模式: 64位
cs = Cs(CS_ARCH_X86, CS_MODE_64)
 
print(f"Disassembly result:")
print("-" * 30)
 
# 3. 执行反汇编
# md.disasm(code, offset)
# code:   要反汇编的二进制数据 (bytes)
# offset: 这段代码在内存中的起始地址 (int),用于计算相对跳转等地址
# 返回值: 一个生成器 (generator),每次迭代返回一个 CsInsn 对象
for insn in cs.disasm(CODE, 0x1000):
    # 4. 打印指令详情
    # address:  指令地址
    # mnemonic: 助记符 (如 mov, call)
    # op_str:   操作数字符串 (如 rax, 1)
    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 *
 
# ==========================================
# 1. 准备代码 (x64 Linux Syscall: write)
# ==========================================
# 汇编逻辑:
#   mov rdi, 1          ; fd = stdout
#   lea rsi, [msg]      ; buf = "Hello Unicorn"
#   mov rdx, 13         ; count = 13
#   mov rax, 1          ; syscall_write
#   syscall
#   ... (msg string) ...
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
 
# ==========================================
# 2. 初始化引擎 (Unicorn + Capstone)
# ==========================================
# 初始化 Unicorn (大脑)
uc = Uc(UC_ARCH_X86, UC_MODE_64)
 
# 初始化 Capstone (眼睛)
# 注意:架构必须与 Unicorn 保持一致!
cs = Cs(CS_ARCH_X86, CS_MODE_64)
 
# ==========================================
# 3. 编写 Hook 回调 (Trace Logger)
# ==========================================
def hook_code(uc, address, size, user_data):
    """
    指令级 Hook,每条指令执行前触发
    """
    # 3.1 从内存读取当前指令的机器码
    # size 参数由 Unicorn 提供,表示当前指令长度
    try:
        code = uc.mem_read(address, size)
    except:
        return # 读取失败忽略
 
    # 3.2 使用 Capstone 反汇编
    # disasm 返回一个生成器,这里每次只反汇编一条指令
    for insn in cs.disasm(code, address):
        # 3.3 打印 Trace 日志
        # 格式:地址  助记符  操作数
        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
 
# ==========================================
# 4. 环境设置与启动
# ==========================================
# 映射内存
uc.mem_map(ADDRESS, MEM_SIZE)
 
# 写入代码和数据
uc.mem_write(ADDRESS, CODE)
uc.mem_write(ADDRESS + len(CODE), DATA)
 
# 注册 Hook
uc.hook_add(UC_HOOK_CODE, hook_code)
 
print(">>> Start Tracing...")
print("-" * 20)
 
# 开始模拟
# 注意:syscall 可能会抛出异常,这里仅演示反汇编,忽略 syscall 错误
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 *
 
# ==========================================
# 1. 准备代码 (x64 Linux Syscall: write)
# ==========================================
# 汇编逻辑:
#   mov rdi, 1          ; fd = stdout
#   lea rsi, [msg]      ; buf = "Hello Unicorn"
#   mov rdx, 13         ; count = 13
#   mov rax, 1          ; syscall_write
#   syscall
#   ... (msg string) ...
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
 
# ==========================================
# 2. 初始化引擎 (Unicorn + Capstone)
# ==========================================
# 初始化 Unicorn (大脑)
uc = Uc(UC_ARCH_X86, UC_MODE_64)
 
# 初始化 Capstone (眼睛)
# 注意:架构必须与 Unicorn 保持一致!
cs = Cs(CS_ARCH_X86, CS_MODE_64)
 
# ==========================================
# 3. 编写 Hook 回调 (Trace Logger)
# ==========================================
def hook_code(uc, address, size, user_data):
    """
    指令级 Hook,每条指令执行前触发
    """
    # 3.1 从内存读取当前指令的机器码
    # size 参数由 Unicorn 提供,表示当前指令长度
    try:
        code = uc.mem_read(address, size)
    except:
        return # 读取失败忽略
 
    # 3.2 使用 Capstone 反汇编
    # disasm 返回一个生成器,这里每次只反汇编一条指令
    for insn in cs.disasm(code, address):
        # 3.3 打印 Trace 日志
        # 格式:地址  助记符  操作数
        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
 
# ==========================================
# 4. 环境设置与启动
# ==========================================
# 映射内存
uc.mem_map(ADDRESS, MEM_SIZE)
 
# 写入代码和数据
uc.mem_write(ADDRESS, CODE)
uc.mem_write(ADDRESS + len(CODE), DATA)
 
# 注册 Hook
uc.hook_add(UC_HOOK_CODE, hook_code)
 
print(">>> Start Tracing...")
print("-" * 20)
 
# 开始模拟
# 注意:syscall 可能会抛出异常,这里仅演示反汇编,忽略 syscall 错误
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
# 假设 insn 是当前反汇编得到的指令对象
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")
# 假设 insn 是当前反汇编得到的指令对象
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 *
 
# 机器码: mov eax, dword ptr [rbp + rbx*4 - 0x10]
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  # 获取内存结构体
             
            # 1. 解析基址 (Base)
            if mem.base != 0:
                print(f"    Base : {insn.reg_name(mem.base)}")
            else:
                print(f"    Base : None")
                 
            # 2. 解析变址 (Index)
            if mem.index != 0:
                print(f"    Index: {insn.reg_name(mem.index)}")
                print(f"    Scale: {mem.scale}")
             
            # 3. 解析偏移 (Displacement)
            # 注意:disp 是有符号整数,可能为负
            if mem.disp != 0:
                print(f"    Disp : {mem.disp:#x}")
from capstone import *
from capstone.x86 import *
 
# 机器码: mov eax, dword ptr [rbp + rbx*4 - 0x10]
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编辑 ,原因:
收藏
免费 2
支持
分享
最新回复 (2)
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
666,good ideas 
3小时前
1
雪    币: 7
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
感谢分享
3小时前
0
游客
登录 | 注册 方可回帖
返回