Unicorn-CPU模拟框架数据类型及API分析与示例(一)

Author Avatar
kabeor 2月 13, 2020

Unicorn-CPU模拟框架数据类型及API分析与示例(一)

之前对Capstone反汇编引擎的API分析文档(https://xz.aliyun.com/t/5753)已经被[官方](http://www.capstone-engine.org/documentation.html)收录 https://github.com/kabeor/Micro-Capstone-Engine-API-Documentation ,在实现自己想要做出的调试器的路上,又遇到了与Capstone同作者的国外大佬aquynh的另一个著名项目Unicorn,不巧的是,详尽的API文档仍然较少,更多的是大篇幅的代码,因此决定继续分析Unicorn框架,包括数据类型,已开放API及其实现。

Unicorn是一个轻量级, 多平台, 多架构的CPU模拟器框架,基于qemu开发,它可以代替CPU模拟代码的执行,常用于恶意代码分析,Fuzz等,该项目被用于Radare2逆向分析框架,GEF(gdb的pwn分析插件),Pwndbg,Angr符号执行框架等多个著名项目。接下来我也将通过阅读源码和代码实际调用来写一个简单的非官方版本的API手册。

个人博客: kabeor.cn

0x0 开发准备

Unicorn官网: http://www.unicorn-engine.org

自行编译lib和dll方法

源码: https://github.com/unicorn-engine/unicorn/archive/master.zip

下载后解压

文件结构如下:

. <- 主要引擎core engine + README + 编译文档COMPILE.TXT 等
├── arch <- 各语言反编译支持的代码实现
├── bindings <- 中间件
│ ├── dotnet <- .Net 中间件 + 测试代码
│ ├── go <- go 中间件 + 测试代码
│ ├── haskell <- Haskell 中间件 + 测试代码
│ ├── java <- Java 中间件 + 测试代码
│ ├── pascal <- Pascal 中间件 + 测试代码
│ ├── python <- Python 中间件 + 测试代码
│ ├── ruby <- Ruby 中间件 + 测试代码
│ └── vb6 <- VB6 中间件 + 测试代码
├── docs <- 文档,主要是Unicorn的实现思路
├── include <- C头文件
├── msvc <- Microsoft Visual Studio 支持(Windows)
├── qemu <- qemu框架源码
├── samples <- Unicorn使用示例
└── tests <- C语言测试用例

下面演示Windows10使用Visual Studio2019编译

打开msvc文件夹,内部结构如下

image.png

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

image.png

如果都需要的话,直接编译就好了,只需要其中几种,则右键解决方案->属性->配置属性 如下

image.png

生成选项中勾选你需要的支持项即可

项目编译属性为:

  1. 使用多字节字符集
  2. 不使用预编译头
  3. 附加选项 /wd4018 /wd4244 /wd4267
  4. 预处理器定义中添加 _CRT_SECURE_NO_WARNINGS

编译后会在当前文件夹Debug目录下生成unicorn.lib静态编译库和unicorn.dll动态库这样就可以开始使用Unicorn进行开发了

编译到最后一项可能会报错系统找不到指定的路径,查看makefile发现问题出现在此处
image.png

事实上只不过是不能将生成的lib和dll复制到新建的文件夹而已,只需要到生成目录去找即可。

官方目前提供的最新已编译版本为1.0.1版本,比较老,建议自己编辑最新版本源码,以获得更多可用API。
Win32:https://github.com/unicorn-engine/unicorn/releases/download/1.0.1/unicorn-1.0.1-win32.zip
Win64:https://github.com/unicorn-engine/unicorn/releases/download/1.0.1/unicorn-1.0.1-win64.zip

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

引擎调用测试

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

image.png

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

image.png

接下来测试我们生成的unicorn框架

主文件代码如下

#include <iostream>
#include "unicorn/unicorn.h"

// 要模拟的指令
#define X86_CODE32 "\x41\x4a" // INC ecx; DEC edx

// 起始地址
#define ADDRESS 0x1000000

int main()
{
uc_engine* uc;
uc_err err;
int r_ecx = 0x1234; // ECX 寄存器
int r_edx = 0x7890; // EDX 寄存器

printf("Emulate i386 code\n");

// X86-32bit 模式初始化模拟
err = uc_open(UC_ARCH_X86, UC_MODE_32, &uc);
if (err != UC_ERR_OK) {
printf("Failed on uc_open() with error returned: %u\n", err);
return -1;
}

// 给模拟器申请 2MB 内存
uc_mem_map(uc, ADDRESS, 2 * 1024 * 1024, UC_PROT_ALL);

// 将要模拟的指令写入内存
if (uc_mem_write(uc, ADDRESS, X86_CODE32, sizeof(X86_CODE32) - 1)) {
printf("Failed to write emulation code to memory, quit!\n");
return -1;
}

// 初始化寄存器
uc_reg_write(uc, UC_X86_REG_ECX, &r_ecx);
uc_reg_write(uc, UC_X86_REG_EDX, &r_edx);

printf(">>> ECX = 0x%x\n", r_ecx);
printf(">>> EDX = 0x%x\n", r_edx);

// 模拟代码
err = uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(X86_CODE32) - 1, 0, 0);
if (err) {
printf("Failed on uc_emu_start() with error returned %u: %s\n",
err, uc_strerror(err));
}

// 打印寄存器值
printf("Emulation done. Below is the CPU context\n");

uc_reg_read(uc, UC_X86_REG_ECX, &r_ecx);
uc_reg_read(uc, UC_X86_REG_EDX, &r_edx);
printf(">>> ECX = 0x%x\n", r_ecx);
printf(">>> EDX = 0x%x\n", r_edx);

uc_close(uc);

return 0;
}

运行结果如下

image.png

ecx+1和edx-1成功模拟。

0x1 数据类型分析

uc_arch

架构选择

typedef enum uc_arch {
UC_ARCH_ARM = 1, // ARM 架构 (包括 Thumb, Thumb-2)
UC_ARCH_ARM64, // ARM-64, 也称 AArch64
UC_ARCH_MIPS, // Mips 架构
UC_ARCH_X86, // X86 架构 (包括 x86 & x86-64)
UC_ARCH_PPC, // PowerPC 架构 (暂不支持)
UC_ARCH_SPARC, // Sparc 架构
UC_ARCH_M68K, // M68K 架构
UC_ARCH_MAX,
} uc_arch;

uc_mode

模式选择

typedef enum uc_mode {
UC_MODE_LITTLE_ENDIAN = 0, // 小端序模式 (默认)
UC_MODE_BIG_ENDIAN = 1 << 30, // 大端序模式

// arm / arm64
UC_MODE_ARM = 0, // ARM 模式
UC_MODE_THUMB = 1 << 4, // THUMB 模式 (包括 Thumb-2)
UC_MODE_MCLASS = 1 << 5, // ARM's Cortex-M 系列 (暂不支持)
UC_MODE_V8 = 1 << 6, // ARMv8 A32 encodings for ARM (暂不支持)

// arm (32bit) cpu 类型
UC_MODE_ARM926 = 1 << 7, // ARM926 CPU 类型
UC_MODE_ARM946 = 1 << 8, // ARM946 CPU 类型
UC_MODE_ARM1176 = 1 << 9, // ARM1176 CPU 类型

// mips
UC_MODE_MICRO = 1 << 4, // MicroMips 模式 (暂不支持)
UC_MODE_MIPS3 = 1 << 5, // Mips III ISA (暂不支持)
UC_MODE_MIPS32R6 = 1 << 6, // Mips32r6 ISA (暂不支持)
UC_MODE_MIPS32 = 1 << 2, // Mips32 ISA
UC_MODE_MIPS64 = 1 << 3, // Mips64 ISA

// x86 / x64
UC_MODE_16 = 1 << 1, // 16-bit 模式
UC_MODE_32 = 1 << 2, // 32-bit 模式
UC_MODE_64 = 1 << 3, // 64-bit 模式

// ppc
UC_MODE_PPC32 = 1 << 2, // 32-bit 模式 (暂不支持)
UC_MODE_PPC64 = 1 << 3, // 64-bit 模式 (暂不支持)
UC_MODE_QPX = 1 << 4, // Quad Processing eXtensions 模式 (暂不支持)

// sparc
UC_MODE_SPARC32 = 1 << 2, // 32-bit 模式
UC_MODE_SPARC64 = 1 << 3, // 64-bit 模式
UC_MODE_V9 = 1 << 4, // SparcV9 模式 (暂不支持)

// m68k
} uc_mode;

uc_err

错误类型,是uc_errno()的返回值

typedef enum uc_err {
UC_ERR_OK = 0, // 无错误
UC_ERR_NOMEM, // 内存不足: uc_open(), uc_emulate()
UC_ERR_ARCH, // 不支持的架构: uc_open()
UC_ERR_HANDLE, // 不可用句柄
UC_ERR_MODE, // 不可用/不支持架构: uc_open()
UC_ERR_VERSION, // 不支持版本 (中间件)
UC_ERR_READ_UNMAPPED, // 由于在未映射的内存上读取而退出模拟: uc_emu_start()
UC_ERR_WRITE_UNMAPPED, // 由于在未映射的内存上写入而退出模拟: uc_emu_start()
UC_ERR_FETCH_UNMAPPED, // 由于在未映射的内存中获取数据而退出模拟: uc_emu_start()
UC_ERR_HOOK, // 无效的hook类型: uc_hook_add()
UC_ERR_INSN_INVALID, // 由于指令无效而退出模拟: uc_emu_start()
UC_ERR_MAP, // 无效的内存映射: uc_mem_map()
UC_ERR_WRITE_PROT, // 由于UC_MEM_WRITE_PROT冲突而停止模拟: uc_emu_start()
UC_ERR_READ_PROT, // 由于UC_MEM_READ_PROT冲突而停止模拟: uc_emu_start()
UC_ERR_FETCH_PROT, // 由于UC_MEM_FETCH_PROT冲突而停止模拟: uc_emu_start()
UC_ERR_ARG, // 提供给uc_xxx函数的无效参数
UC_ERR_READ_UNALIGNED, // 未对齐读取
UC_ERR_WRITE_UNALIGNED, // 未对齐写入
UC_ERR_FETCH_UNALIGNED, // 未对齐的提取
UC_ERR_HOOK_EXIST, // 此事件的钩子已经存在
UC_ERR_RESOURCE, // 资源不足: uc_emu_start()
UC_ERR_EXCEPTION, // 未处理的CPU异常
UC_ERR_TIMEOUT // 模拟超时
} uc_err;

uc_mem_type

UC_HOOKMEM*的所有内存访问类型

typedef enum uc_mem_type {
UC_MEM_READ = 16, // 内存从..读取
UC_MEM_WRITE, // 内存写入到..
UC_MEM_FETCH, // 内存被获取
UC_MEM_READ_UNMAPPED, // 未映射内存从..读取
UC_MEM_WRITE_UNMAPPED, // 未映射内存写入到..
UC_MEM_FETCH_UNMAPPED, // 未映射内存被获取
UC_MEM_WRITE_PROT, // 内存写保护,但是已映射
UC_MEM_READ_PROT, // 内存读保护,但是已映射
UC_MEM_FETCH_PROT, // 内存不可执行,但是已映射
UC_MEM_READ_AFTER, // 内存从 (成功访问的地址) 读入
} uc_mem_type;

uc_hook_type

uc_hook_add()的所有hook类型参数

typedef enum uc_hook_type {
// Hook 所有中断/syscall 事件
UC_HOOK_INTR = 1 << 0,
// Hook 一条特定的指令 - 只支持非常小的指令子集
UC_HOOK_INSN = 1 << 1,
// Hook 一段代码
UC_HOOK_CODE = 1 << 2,
// Hook 基本块
UC_HOOK_BLOCK = 1 << 3,
// 用于在未映射的内存上读取内存的Hook
UC_HOOK_MEM_READ_UNMAPPED = 1 << 4,
// Hook 无效的内存写事件
UC_HOOK_MEM_WRITE_UNMAPPED = 1 << 5,
// Hook 执行事件的无效内存
UC_HOOK_MEM_FETCH_UNMAPPED = 1 << 6,
// Hook 读保护的内存
UC_HOOK_MEM_READ_PROT = 1 << 7,
// Hook 写保护的内存
UC_HOOK_MEM_WRITE_PROT = 1 << 8,
// Hook 不可执行内存上的内存
UC_HOOK_MEM_FETCH_PROT = 1 << 9,
// Hook 内存读取事件
UC_HOOK_MEM_READ = 1 << 10,
// Hook 内存写入事件
UC_HOOK_MEM_WRITE = 1 << 11,
// Hook 内存获取执行事件
UC_HOOK_MEM_FETCH = 1 << 12,
// Hook 内存读取事件,只允许能成功访问的地址
// 成功读取后将触发回调
UC_HOOK_MEM_READ_AFTER = 1 << 13,
// Hook 无效指令异常
UC_HOOK_INSN_INVALID = 1 << 14,
} uc_hook_type;

宏定义Hook类型

// Hook 所有未映射内存访问的事件
#define UC_HOOK_MEM_UNMAPPED (UC_HOOK_MEM_READ_UNMAPPED + UC_HOOK_MEM_WRITE_UNMAPPED + UC_HOOK_MEM_FETCH_UNMAPPED)
// Hook 所有对受保护内存的非法访问事件
#define UC_HOOK_MEM_PROT (UC_HOOK_MEM_READ_PROT + UC_HOOK_MEM_WRITE_PROT + UC_HOOK_MEM_FETCH_PROT)
// Hook 所有非法读取存储器的事件
#define UC_HOOK_MEM_READ_INVALID (UC_HOOK_MEM_READ_PROT + UC_HOOK_MEM_READ_UNMAPPED)
// Hook 所有非法写入存储器的事件
#define UC_HOOK_MEM_WRITE_INVALID (UC_HOOK_MEM_WRITE_PROT + UC_HOOK_MEM_WRITE_UNMAPPED)
// Hook 所有非法获取内存的事件
#define UC_HOOK_MEM_FETCH_INVALID (UC_HOOK_MEM_FETCH_PROT + UC_HOOK_MEM_FETCH_UNMAPPED)
// Hook 所有非法的内存访问事件
#define UC_HOOK_MEM_INVALID (UC_HOOK_MEM_UNMAPPED + UC_HOOK_MEM_PROT)
// Hook 所有有效内存访问的事件
// 注意: UC_HOOK_MEM_READ 在 UC_HOOK_MEM_READ_PROT 和 UC_HOOK_MEM_READ_UNMAPPED 之前触发 ,
// 因此这个Hook可能会触发一些无效的读取。
#define UC_HOOK_MEM_VALID (UC_HOOK_MEM_READ + UC_HOOK_MEM_WRITE + UC_HOOK_MEM_FETCH)

uc_mem_region

由uc_mem_map()和uc_mem_map_ptr()映射内存区域
使用uc_mem_regions()检索该内存区域的列表

typedef struct uc_mem_region {
uint64_t begin; // 区域起始地址 (包括)
uint64_t end; // 区域结束地址 (包括)
uint32_t perms; // 区域的内存权限
} uc_mem_region;

uc_query_type

uc_query()的所有查询类型参数

typedef enum uc_query_type {
// 动态查询当前硬件模式
UC_QUERY_MODE = 1,
UC_QUERY_PAGE_SIZE,
UC_QUERY_ARCH,
} uc_query_type;

uc_context

与uccontext*()一起使用,管理CPU上下文的不透明存储

struct uc_context;
typedef struct uc_context uc_context;

uc_prot

新映射区域的权限

typedef enum uc_prot {
UC_PROT_NONE = 0, //无
UC_PROT_READ = 1, //读取
UC_PROT_WRITE = 2, //写入
UC_PROT_EXEC = 4, //可执行
UC_PROT_ALL = 7, //所有权限
} uc_prot;

From https://kabeor.github.io/Unicorn-CPU模拟框架数据类型及API分析与示例(一)//) bye

This blog is under a CC BY-NC-SA 4.0 Unported License
本文链接:https://kabeor.github.io/Unicorn-CPU模拟框架数据类型及API分析与示例(一)/