最近准备用开源的反汇编引擎做个项目,研究了OllyDebug的ODDisasm,disasm与assembl部分代码的思想都很值得学习,但毕竟是2000年的产物,指令集只支持x86,也没有对语义的深度分析,于是转向了对Capstone的研究。
Capstone反汇编引擎可以说是如今世界上最优秀的反汇编引擎,IDA,Radare2,Qemu等著名项目都使用了Capstone Engine,所以选择它来开发是一个不错的选择。
但在开发时发现官方并未给出详细API文档,网上也没有类似的分析,因此想到自己阅读源码和试验,由此写出了一个简单的非官方版本的API手册,希望能与大家分享。
个人博客: kabeor.cn
Capstone官网: http://www.capstone-engine.org
源码: 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支持问题。
运行结果
用于生成调用capstone API的句柄
size_t csh
用法:
csh handle;
架构选择
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 架构
模式选择
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模式
内存操作
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()
自定义助记符
struct cs_opt_mnem { /// 需要自定义的指令ID unsigned int id; /// 自定义的助记符 const char *mnemonic; } cs_opt_mnem;
反编译的运行时选项
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_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);中第三个参数
通用指令操作数类型,在所有架构中保持一致
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_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_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中未调用
用户自定义设置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;
指令的详细信息
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;
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,敬请期待