最近准备用开源的反汇编引擎做个项目,研究了OllyDebug的ODDisasm,disasm与assembl部分代码的思想都很值得学习,但毕竟是2000年的产物,指令集只支持x86,也没有对语义的深度分析,于是转向了对Capstone的研究。

Capstone反汇编引擎可以说是如今世界上最优秀的反汇编引擎,IDA,Radare2,Qemu等著名项目都使用了Capstone Engine,所以选择它来开发是一个不错的选择。
但在开发时发现官方并未给出详细API文档,网上也没有类似的分析,因此想到自己阅读源码和试验,由此写出了一个简单的非官方版本的API手册,希望能与大家分享。

个人博客: kabeor.cn

0x0 开发准备

Capstone官网: http://www.capstone-engine.org

自行编译lib和dll方法

源码: https://github.com/aquynh/capstone/archive/4.0.1.zip

下载后解压
文件结构如下:

. <- 主要引擎core engine + README + 编译文档COMPILE.TXT 等
├── arch <- 各语言反编译支持的代码实现
│ ├── AArch64 <- ARM64 (aka ARMv8) 引擎
│ ├── ARM <- ARM 引擎
│ ├── EVM <- Ethereum 引擎
│ ├── M680X <- M680X 引擎
│ ├── M68K <- M68K 引擎
│ ├── Mips <- Mips 引擎
│ ├── PowerPC <- PowerPC 引擎
│ ├── Sparc <- Sparc 引擎
│ ├── SystemZ <- SystemZ 引擎
│ ├── TMS320C64x <- TMS320C64x 引擎
│ ├── X86 <- X86 引擎
│ └── XCore <- XCore 引擎
├── bindings <- 中间件
│ ├── java <- Java 中间件 + 测试代码
│ ├── ocaml <- Ocaml 中间件 + 测试代码
│ └── python <- Python 中间件 + 测试代码
├── contrib <- 社区代码
├── cstool <- Cstool 检测工具源码
├── docs <- 文档,主要是capstone的实现思路
├── include <- C头文件
├── msvc <- Microsoft Visual Studio 支持(Windows)
├── packages <- Linux/OSX/BSD包
├── windows <- Windows 支持(Windows内核驱动编译)
├── suite <- Capstone开发测试工具
├── tests <- C语言测试用例
└── xcode <- Xcode 支持 (MacOSX 编译)

下面演示Windows10使用Visual Studio2019编译

复制msvc文件夹到一个比较清爽的位置(强迫症专用),内部结构如下:

VS打开capstone.sln项目文件,解决方案自动载入这些

可以看到支持的所有语言都在这里了,如果都需要的话,直接编译就好了,只需要其中几种,则右键解决方案->属性->配置属性 如下

生成选项中勾选你需要的支持项即可
编译后会在当前文件夹Debug目录下生成capstone.lib静态编译库和capstone.dll动态库这样就可以开始使用Capstone进行开发了

如果不想自己编译,官方也提供了官方编译版本
Win32: https://github.com/aquynh/capstone/releases/download/4.0.1/capstone-4.0.1-win32.zip
Win64: https://github.com/aquynh/capstone/releases/download/4.0.1/capstone-4.0.1-win64.zip

选x32或x64将影响后面开发的位数

引擎调用测试

新建一个VS项目,将..\capstone-4.0.1\include\capstone中的头文件以及编译好的lib和dll文件全部拷贝到新建项目的主目录下

在VS解决方案中,头文件添加现有项capstone.h,资源文件中添加capstone.lib,重新生成解决方案

那么现在来测试一下我们自己的capstone引擎吧

主文件写入如下代码

#include <iostream>
#include <stdio.h>
#include <cinttypes>  
#include "capstone.h"
using namespace std;

#define CODE "\x55\x48\x8b\x05\xb8\x13\x00\x00"

int main(void)
{
    csh handle;
    cs_insn* insn;
    size_t count;

    if (cs_open(CS_ARCH_X86, CS_MODE_64, &handle)) {
        printf("ERROR: Failed to initialize engine!\n");
        return -1;
    }

    count = cs_disasm(handle, (unsigned char*)CODE, sizeof(CODE) - 1, 0x1000, 0, &insn);
    if (count) {
        size_t j;

        for (j = 0; j < count; j++) {
            printf("0x%""Ix"":\t%s\t\t%s\n", insn[j].address, insn[j].mnemonic, insn[j].op_str);
        }

        cs_free(insn, count);
    }
    else
        printf("ERROR: Failed to disassemble given code!\n");

    cs_close(&handle);

    return 0;
}

事实上这是官方给出的C语言开发唯一几个例子之一,但注意到代码cs_open(CS_ARCH_X86, CS_MODE_64, &handle),测试的是archx64的反编译,因此编译选项也需要设置为x64,除此以外,如果你的项目像我一样是c++开发,那么printf("0x%""Ix"":\t%s\t\t%s\n", insn[j].address, insn[j].mnemonic, insn[j].op_str);处官方给出的"0x%"PRIx64":\t%s\t\t%s\n"应修改为我这里的"0x%""Ix"":\t%s\t\t%s\n",这是inttypes支持问题。

运行结果

0x1 数据类型及API分析

数据类型

csh

用于生成调用capstone API的句柄
size_t csh

用法: csh handle;

cs_arch

架构选择

enum cs_arch {
    CS_ARCH_ARM = 0,    ///< ARM 架构 (包括 Thumb, Thumb-2)
    CS_ARCH_ARM64,      ///< ARM-64, 也叫 AArch64
    CS_ARCH_MIPS,       ///< Mips 架构
   CS_ARCH_X86,     ///< X86 架构 (包括 x86 & x86-64)
    CS_ARCH_PPC,        ///< PowerPC 架构
    CS_ARCH_SPARC,      ///< Sparc 架构
    CS_ARCH_SYSZ,       ///< SystemZ 架构
    CS_ARCH_XCORE,      ///< XCore 架构
    CS_ARCH_M68K,       ///< 68K 架构
    CS_ARCH_TMS320C64X, ///< TMS320C64x 架构
    CS_ARCH_M680X,      ///< 680X 架构
    CS_ARCH_EVM,        ///< Ethereum 架构
    CS_ARCH_MAX,
    CS_ARCH_ALL = 0xFFFF, // All 架构 - for cs_support()
} cs_arch;

用法:API中cs_arch参数填入枚举内容,如API中cs_open(cs_arch arch, cs_mode mode, csh *handle);第一个参数填CS_ARCH_X86则支持X86 架构

cs_mode

模式选择

enum cs_mode {
    CS_MODE_LITTLE_ENDIAN = 0,  ///< little-endian 模式 (default 模式)
    CS_MODE_ARM = 0,    ///< 32-bit ARM
    CS_MODE_16 = 1 << 1,    ///< 16-bit 模式 (X86)
    CS_MODE_32 = 1 << 2,    ///< 32-bit 模式 (X86)
    CS_MODE_64 = 1 << 3,    ///< 64-bit 模式 (X86, PPC)
    CS_MODE_THUMB = 1 << 4, ///< ARM's Thumb 模式, including Thumb-2
    CS_MODE_MCLASS = 1 << 5,    ///< ARM's Cortex-M series
    CS_MODE_V8 = 1 << 6,    ///< ARMv8 A32 encodings for ARM
    CS_MODE_MICRO = 1 << 4, ///< MicroMips 模式 (MIPS)
    CS_MODE_MIPS3 = 1 << 5, ///< Mips III ISA
    CS_MODE_MIPS32R6 = 1 << 6, ///< Mips32r6 ISA
    CS_MODE_MIPS2 = 1 << 7, ///< Mips II ISA
    CS_MODE_V9 = 1 << 4, ///< SparcV9 模式 (Sparc)
    CS_MODE_QPX = 1 << 4, ///< Quad Processing eXtensions 模式 (PPC)
    CS_MODE_M68K_000 = 1 << 1, ///< M68K 68000 模式
    CS_MODE_M68K_010 = 1 << 2, ///< M68K 68010 模式
    CS_MODE_M68K_020 = 1 << 3, ///< M68K 68020 模式
    CS_MODE_M68K_030 = 1 << 4, ///< M68K 68030 模式
    CS_MODE_M68K_040 = 1 << 5, ///< M68K 68040 模式
    CS_MODE_M68K_060 = 1 << 6, ///< M68K 68060 模式
    CS_MODE_BIG_ENDIAN = 1 << 31,   ///< big-endian 模式
    CS_MODE_MIPS32 = CS_MODE_32,    ///< Mips32 ISA (Mips)
    CS_MODE_MIPS64 = CS_MODE_64,    ///< Mips64 ISA (Mips)
    CS_MODE_M680X_6301 = 1 << 1, ///< M680X Hitachi 6301,6303 模式
    CS_MODE_M680X_6309 = 1 << 2, ///< M680X Hitachi 6309 模式
    CS_MODE_M680X_6800 = 1 << 3, ///< M680X Motorola 6800,6802 模式
    CS_MODE_M680X_6801 = 1 << 4, ///< M680X Motorola 6801,6803 模式
    CS_MODE_M680X_6805 = 1 << 5, ///< M680X Motorola/Freescale 6805 模式
    CS_MODE_M680X_6808 = 1 << 6, ///< M680X Motorola/Freescale/NXP 68HC08 模式
    CS_MODE_M680X_6809 = 1 << 7, ///< M680X Motorola 6809 模式
    CS_MODE_M680X_6811 = 1 << 8, ///< M680X Motorola/Freescale/NXP 68HC11 模式
    CS_MODE_M680X_CPU12 = 1 << 9, ///< M680X Motorola/Freescale/NXP CPU12
                    ///< 用于 M68HC12/HCS12
    CS_MODE_M680X_HCS08 = 1 << 10, ///< M680X Freescale/NXP HCS08 模式
} cs_mode;

用法:API中cs_mode参数填入枚举内容,如API中cs_open(cs_arch arch, cs_mode mode, csh *handle);第二个参数填CS_MODE_64则支持X64模式

cs_opt_mem

内存操作

struct cs_opt_mem {
    cs_malloc_t malloc;
    cs_calloc_t calloc;
    cs_realloc_t realloc;
    cs_free_t free;
    cs_vsnprintf_t vsnprintf;
} cs_opt_mem;

用法:可使用用户自定义的malloc/calloc/realloc/free/vsnprintf()函数,默认使用系统自带malloc(), calloc(), realloc(), free() & vsnprintf()

cs_opt_mnem

自定义助记符

struct cs_opt_mnem {
    /// 需要自定义的指令ID
    unsigned int id;
    /// 自定义的助记符
    const char *mnemonic;
} cs_opt_mnem;

cs_opt_type

反编译的运行时选项

enum cs_opt_type {
    CS_OPT_INVALID = 0, ///< 无特殊要求
    CS_OPT_SYNTAX,  ///< 汇编输出语法
    CS_OPT_DETAIL,  ///< 将指令结构分解为多个细节
    CS_OPT_MODE,    ///< 运行时改变引擎模式
    CS_OPT_MEM, ///< 用户定义的动态内存相关函数
    CS_OPT_SKIPDATA, ///< 在反汇编时跳过数据。然后引擎将处于SKIPDATA模式
    CS_OPT_SKIPDATA_SETUP, ///< 为SKIPDATA选项设置用户定义函数
    CS_OPT_MNEMONIC, ///<自定义指令助记符
    CS_OPT_UNSIGNED, ///< 以无符号形式打印立即操作数
} cs_opt_type;

用法:API cs_option(csh handle, cs_opt_type type, size_t value);中第二个参数

cs_opt_value

运行时选项值(与cs_opt_type关联)

enum cs_opt_value {
    CS_OPT_OFF = 0,  ///< 关闭一个选项 - 默认为CS_OPT_DETAIL, CS_OPT_SKIPDATA, CS_OPT_UNSIGNED.
    CS_OPT_ON = 3, ///< 打开一个选项 (CS_OPT_DETAIL, CS_OPT_SKIPDATA).
    CS_OPT_SYNTAX_DEFAULT = 0, ///< 默认asm语法 (CS_OPT_SYNTAX).
    CS_OPT_SYNTAX_INTEL, ///< X86 Intel asm语法 - 默认开启 X86 (CS_OPT_SYNTAX).
    CS_OPT_SYNTAX_ATT,   ///< X86 ATT 汇编语法 (CS_OPT_SYNTAX).
    CS_OPT_SYNTAX_NOREGNAME, ///< 只打印寄存器名和编号 (CS_OPT_SYNTAX)
    CS_OPT_SYNTAX_MASM, ///< X86 Intel Masm 语法 (CS_OPT_SYNTAX).
} cs_opt_value;

用法:API cs_option(csh handle, cs_opt_type type, size_t value);中第三个参数

cs_op_type

通用指令操作数类型,在所有架构中保持一致

enum cs_op_type {
    CS_OP_INVALID = 0,  ///< 未初始化/无效的操作数
    CS_OP_REG,          ///< 寄存器操作数
    CS_OP_IMM,          ///< 立即操作数
    CS_OP_MEM,          ///< 内存操作数
    CS_OP_FP,           ///< 浮点数
} cs_op_type;

目前开放的API中未调用

cs_ac_type

通用指令操作数访问类型,在所有架构中保持一致
可以组合访问类型,例如:CS_AC_READ | CS_AC_WRITE

enum cs_ac_type {
    CS_AC_INVALID = 0,        ///< 未初始化/无效的访问类型
    CS_AC_READ    = 1 << 0,   ///< 操作数从内存或寄存器中读取
    CS_AC_WRITE   = 1 << 1,   ///< 操作数从内存或寄存器中写入
} cs_ac_type;

目前开放的API中未调用

cs_group_type

公共指令组,在所有架构中保持一致

cs_group_type {
    CS_GRP_INVALID = 0,  ///< 未初始化/无效指令组
    CS_GRP_JUMP,    ///< 所有跳转指令(条件跳转+直接跳转+间接跳转)
    CS_GRP_CALL,    ///< 所有调用指令
    CS_GRP_RET,     ///< 所有返回指令
    CS_GRP_INT,     ///< 所有中断指令(int+syscall)
    CS_GRP_IRET,    ///< 所有中断返回指令
    CS_GRP_PRIVILEGE,    ///< 所有特权指令
    CS_GRP_BRANCH_RELATIVE, ///< 所有相关分支指令
} cs_group_type;

目前开放的API中未调用

cs_opt_skipdata

用户自定义设置SKIPDATA选项

struct cs_opt_skipdata {
    /// Capstone认为要跳过的数据是特殊的“指令”
    /// 用户可以在这里指定该指令的“助记符”字符串
    /// 默认情况下(@mnemonic为NULL), Capstone使用“.byte”
    const char *mnemonic;

    /// 用户定义的回调函数,当Capstone命中数据时调用
    /// 如果这个回调返回的值是正数(>0),Capstone将跳过这个字节数并继续。如果回调返回0,Capstone将停止反汇编并立即从cs_disasm()返回
    /// 注意:如果这个回调指针为空,Capstone会根据架构跳过一些字节,如下所示:
    /// Arm:     2 bytes (Thumb mode) or 4 bytes.
    /// Arm64:   4 bytes.
    /// Mips:    4 bytes.
    /// M680x:   1 byte.
    /// PowerPC: 4 bytes.
    /// Sparc:   4 bytes.
    /// SystemZ: 2 bytes.
    /// X86:     1 bytes.
    /// XCore:   2 bytes.
    /// EVM:     1 bytes.
    cs_skipdata_cb_t callback;  // 默认值为 NULL

    /// 用户自定义数据将被传递给@callback函数指针
    void *user_data;
} cs_opt_skipdata;

目前开放的API中未调用

cs_detail

注意:只有当CS_OPT_DETAIL = CS_OPT_ON时,cs_detail中的所有信息才可用

在arch/ARCH/ARCHDisassembler.c的ARCH_getInstruction中初始化为memset(., 0, offsetof(cs_detail, ARCH)+sizeof(cs_ARCH))

如果cs_detail发生了变化,特别是在union之后添加了字段,那么相应地更新arch/ arch/ archdisassembly .c

struct cs_detail {
    uint16_t regs_read[12]; ///< 这个参数读取隐式寄存器列表
    uint8_t regs_read_count; ///< 这个参数读取隐式寄存器计数

    uint16_t regs_write[20]; ///< 这个参数修改隐式寄存器列表
    uint8_t regs_write_count; ///< 这个参数修改隐式寄存器计数

    uint8_t groups[8]; ///< 此指令所属的指令组的列表
    uint8_t groups_count; ///< 此指令所属的组的数

    /// 特定于体系结构的信息
    union {
        cs_x86 x86;     ///< X86 架构, 包括 16-bit, 32-bit & 64-bit 模式
        cs_arm64 arm64; ///< ARM64 架构 (aka AArch64)
        cs_arm arm;     ///< ARM 架构 (包括 Thumb/Thumb2)
        cs_m68k m68k;   ///< M68K 架构
        cs_mips mips;   ///< MIPS 架构
        cs_ppc ppc;     ///< PowerPC 架构
        cs_sparc sparc; ///< Sparc 架构
        cs_sysz sysz;   ///< SystemZ 架构
        cs_xcore xcore; ///< XCore 架构
        cs_tms320c64x tms320c64x;  ///< TMS320C64x 架构
        cs_m680x m680x; ///< M680X 架构
        cs_evm evm;     ///< Ethereum 架构
    };
} cs_detail;

cs_insn

指令的详细信息

struct cs_insn {
    /// 指令ID(基本上是一个用于指令助记符的数字ID)
    /// 应在相应架构的头文件中查找'[ARCH]_insn' enum中的指令id,如ARM.h中的'arm_insn'代表ARM, X86.h中的'x86_insn'代表X86等…
    /// 即使在CS_OPT_DETAIL = CS_OPT_OFF时也可以使用此信息
    /// 注意:在Skipdata模式下,这个id字段的“data”指令为0
    unsigned int id;

    /// 指令地址 (EIP)
    /// 即使在CS_OPT_DETAIL = CS_OPT_OFF时也可以使用此信息
    uint64_t address;

    /// 指令长度
    /// 即使在CS_OPT_DETAIL = CS_OPT_OFF时也可以使用此信息
    uint16_t size;

    /// 此指令的机器码,其字节数由上面的@size表示
    /// 即使在CS_OPT_DETAIL = CS_OPT_OFF时也可以使用此信息
    uint8_t bytes[16];

    /// 指令的Ascii文本助记符
    /// 即使在CS_OPT_DETAIL = CS_OPT_OFF时也可以使用此信息
    char mnemonic[CS_MNEMONIC_SIZE];

    /// 指令操作数的Ascii文本
    /// 即使在CS_OPT_DETAIL = CS_OPT_OFF时也可以使用此信息
    char op_str[160];

    /// cs_detail指针
    /// 注意:只有同时满足以下两个要求时,detail指针才有效:
    /// (1) CS_OP_DETAIL = CS_OPT_ON
    /// (2) 引擎未处于Skipdata模式(CS_OP_SKIPDATA选项设置为CS_OPT_ON)
    ///
    /// 注意2:当处于Skipdata模式或detail模式关闭时,即使这个指针不是NULL,它的内容仍然是不相关的。
    cs_detail *detail;
} cs_insn;

cs_err

Capstone API遇到的各类型的错误时cs_errno()的返回值

typedef enum cs_err {
    CS_ERR_OK = 0,   ///< 无错误
    CS_ERR_MEM,      ///< 内存不足: cs_open(), cs_disasm(), cs_disasm_iter()
    CS_ERR_ARCH,     ///< 不支持的架构: cs_open()
    CS_ERR_HANDLE,   ///<句柄不可用: cs_op_count(), cs_op_index()
    CS_ERR_CSH,      ///< csh参数不可用: cs_close(), cs_errno(), cs_option()
    CS_ERR_MODE,     ///< 无效的或不支持的模式: cs_open()
    CS_ERR_OPTION,   ///< 无效的或不支持的选项: cs_option()
    CS_ERR_DETAIL,   ///< 信息不可用,因为detail选项是关闭的
    CS_ERR_MEMSETUP, ///< 动态内存管理未初始化(见 CS_OPT_MEM)
    CS_ERR_VERSION,  ///< 不支持版本 (bindings)
    CS_ERR_DIET,     ///< 在“diet”引擎中访问不相关的数据
    CS_ERR_SKIPDATA, ///< 在SKIPDATA模式下访问与“数据”指令无关的数据
    CS_ERR_X86_ATT,  ///< X86 AT&T 语法不支持(在编译时退出)
    CS_ERR_X86_INTEL, ///< X86 Intel 语法不支持(在编译时退出)
    CS_ERR_X86_MASM, ///< X86 Intel 语法不支持(在编译时退出)
} cs_err;

本文下一部分将分析Capstone API,敬请期待

点击收藏 | 0 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖