Tutorial for Unicorn:Unicorn Engine 的开发和使用

0x10 Unicorn

Unicorn 是一个轻量级的多平台多架构的 CPU 仿真框架。作为一款著名的开源CPU 模拟框架,很多二进制逆向分析软件都用到了 Unicorn ,或者使用到了它的思想。比如 Radare2、Pwndbg、gdb-gef。Unicorn 在 QEMU 的基础上,增加了许多新特性,以及对 CPU 仿真更好的支持 1

  • 多架构支持:Arm, Arm64 (Armv8), M68K, Mips, Sparc, & X86 (include X86_64)
  • 轻量级的 API
  • 纯 C 语言实现,并支持 Pharo,Crystal,Clojure,Visual Basic,Perl,Rust,Haskell,Ruby,Python,Java,Go,.NET,Delphi / Pascal 和 MSVC 的编译
  • 原生支持 Windows 和类 Unix 系统,如 Mac OSX, Linux, *BSD & Solaris
  • 使用 JIT(Just-In-Time,即时编译技术)提高性能
  • 支持各种级别的细粒度分析
  • 线程安全
  • 根据免费软件许可 GPLv2 分发

原作者在 2015 年的 BlackHat 上,发表了相关议题, BlackHat USA 2015 slides 提供更多的信息,有兴趣的读者可以观看一波。


0x20 基于 C/C++ 使用 Unicorn 进行开发

0x21 编译生成库文件

项目地址:https://github.com/unicorn-engine/unicorn,以下是该项目的详细结构 2

   .                   <- 主要引擎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 编译)

由于项目原生支持 Windows,本次使用 Win10+Microsoft Visual Studio 2015 进行编译。使用 VS 2015 直接打开 msvc 目录下的 unicorn.sln,即可自动载入项目。右击 解决方案 -> 属性,可以弹出如下属性页,从而选择自己想编译的项目。
在这里插入图片描述
当然,你也可以直接在项目右下角的 属性 视图里面进行快捷设置
在这里插入图片描述
在项目的编译属性中,设置如下,其余设置默认即可

  • 不使用预编译头
  • 附加选项 /wd4018 /wd4244 /wd4267

编译完成之后,会在项目文件夹的编译器目录下(如果你使用的是 Win32 编译器,那么就是 Win32)的 Debug 目录中生成相应的链接库(unicorn.lib 静态链接库 + unicorn.dll 动态链接库)
在这里插入图片描述
这两个库以及项目的头文件,就是我们开发需要的东西了。


0x22 新建项目调用引擎

新建一个VS工程(Win32 项目 或者 控制台项目)。将…\unicorn-master\include\unicorn中的头文件以及编译好的 lib 和 dll 文件全部拷贝到新建项目的主目录下

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

#define X86_CODE "\x41\x4a"		// 要模拟的指令
#define ADDRESS 0x2000000		// 起始地址

using namespace std;

int main()
{
	uc_engine * uc;
	uc_err err;
	int r_ecx = 0x1234;
	int r_edx = 0x5678;

	cout << "Emulate i386 code" << endl;

	/*x86模式初始化*/
	err = uc_open(UC_ARCH_X86, UC_MODE_32, &uc);
	if (err != UC_ERR_OK)
	{
		cout << "Something error in uc_open() %u" << err << endl;
		return -1;
	}

	/*申请模拟器内存大小*/
	uc_mem_map(uc, ADDRESS, 4 * 1024 * 1024, UC_PROT_ALL);		// 4MB

	/*要模拟的指令写入寄存器*/
	if (uc_mem_write(uc, ADDRESS, X86_CODE, sizeof(X86_CODE) - 1))
	{
		cout << "Failed to write emulation code to memory, abort" << endl;
		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_CODE) - 1, 0, 0);
	if (err)
	{
		printf("Something wrong in uc_emu_start(), error code: %u %s\n", err, uc_strerror(err));
		return -1;
	}

	/*打印寄存器内容*/
	printf("Emulation done. Blew 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;

}

解决方案资源管理器 头文件添加现有项 unicorn.h,资源文件中添加 unicorn.lib,重新生成解决方案。编译并运行。如下图所示,成功的执行指令 ecx + 1 edx - 1。如果报错,请参考 0x3 Q&A
在这里插入图片描述

0x23 关键代码解析

  • 第4行:我们要模拟的原始二进制代码。此示例中的代码处于十六进制模式,代表两个X86指令“ INC ecx ”和“ DEC edx ” 3
  • 第11行:声明一个指向uc_engine类型的句柄的指针。该句柄将在Unicorn的每个API中使用
  • 第12行:声明数据类型为uc_err的变量,以防 Unicor API返回错误。
  • 第53行:调用uc_close函数完成仿真

其余代码请看下面的函数解析

0x24 API 解析: unicorn.h 关键函数

uc_engineuc_struct 的别名,这里第一行代码,是定义 uc 为指向 unicorn engine 的指针。
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_open() 用于创建 unicorn 实例

uc_err uc_open(uc_arch arch, uc_mode mode, uc_engine **uc);

@arch: 架构类型 (UC_ARCH_*)
@mode: 硬件模式. 由 UC_MODE_* 组合
@uc: 指向 uc_engine 的指针, 返回时更新
@return 成功则返回UC_ERR_OK , 否则返回 uc_err 枚举的其他错误类型

uc_mem_map() 为模拟器映射一块内存

uc_err uc_mem_map(uc_engine *uc, uint64_t address, size_t size, uint32_t perms);

@uc: uc_open() 返回的句柄
@address: 要映射到的新内存区域的起始地址。这个地址必须与4KB对齐,否则将返回UC_ERR_ARG错误。
@size: 要映射到的新内存区域的大小。这个大小必须是4KB的倍数,否则将返回UC_ERR_ARG错误。
@perms: 新映射区域的权限。参数必须是UC_PROT_READ | UC_PROT_WRITE | UC_PROT_EXEC或这些的组合,否则返回UC_ERR_ARG错误。
@return 成功则返回UC_ERR_OK , 否则返回 uc_err 枚举的其他错误类型

uc_mem_write() 向内存写入一段字节码

uc_err uc_mem_write(uc_engine *uc, uint64_t address, const void *bytes, size_t size);

@uc: uc_open() 返回的句柄
@address: 写入字节的起始地址
@bytes:   指向一个包含要写入内存的数据的指针
@size:   要写入的内存大小。
注意: @bytes 必须足够大以包含 @size 字节。
@return 成功则返回UC_ERR_OK , 否则返回 uc_err 枚举的其他错误类型

uc_reg_write() 向寄存器写入值

uc_err uc_reg_write(uc_engine *uc, int regid, const void *value);

@uc: uc_open()返回的句柄
@regid:  将被修改的寄存器ID
@value:  指向寄存器将被修改成的值的指针
@return 成功则返回UC_ERR_OK , 否则返回 uc_err 枚举的其他错误类型

uc_reg_read 读取寄存器的值

uc_err uc_reg_read(uc_engine *uc, int regid, void *value);

@uc: uc_open()返回的句柄
@regid:  将被读取的寄存器ID
@value:  指向保存寄存器值的指针
@return 成功则返回UC_ERR_OK , 否则返回 uc_err 枚举的其他错误类型

0x30 Q&A

0x31 使用了不安全函数

在这里插入图片描述
在预处理器中,添加相应的定义即可
在这里插入图片描述
或者在程序中添加以下任意一行代码

#define _CRT_SECURE_NO_DEPRECATE;
#define _CRT_SECURE_NO_WARNINGS;
#pragma warning(disable:4996);

重新编译,并运行。

0x32 error LNK2019: 无法解析的外部符号 _main

在这里插入图片描述
打开项目属性页,修改 调试器 >系统 > 子系统 ,切换成控制台(如果原来是控制台,则切换为窗口)
在这里插入图片描述

0x32 无法查找或打开 PDB 文件

在这里插入图片描述
【工具】->【选项】->【调试】->【常规]】勾选“启用源服务器支持” 4
在这里插入图片描述
【工具】->【选项】->【调试】->【符号】,勾选“Microsoft符号服务器”
在这里插入图片描述

0x40 基于 Python 调用 Unicorn Engine

使用 Python 调用 Unicorn 相对来说,要简单许多。当然首先得安装相应的包

pip install unicorn -i https://pypi.mirrors.ustc.edu.cn/simple/
from __future__ import print_function
from unicorn import *
from unicorn.x86_const import *

# code to be emulated
X86_CODE32 = b"\x41\x4a" # INC ecx; DEC edx

# memory address where emulation starts
ADDRESS = 0x1000000
 
print("Emulate i386 code")
try:
    # Initialize emulator in X86-32bit mode
    mu = Uc(UC_ARCH_X86, UC_MODE_32)

    # map 2MB memory for this emulation
    mu.mem_map(ADDRESS, 2 * 1024 * 1024)

    # write machine code to be emulated to memory
    mu.mem_write(ADDRESS, X86_CODE32)

    # initialize machine registers
    mu.reg_write(UC_X86_REG_ECX, 0x1234)
    mu.reg_write(UC_X86_REG_EDX, 0x5678)

    # emulate code in infinite time & unlimited instructions
    mu.emu_start(ADDRESS, ADDRESS + len(X86_CODE32))

    # now print out some registers
    print("Emulation done. Below is the CPU context")

    r_ecx = mu.reg_read(UC_X86_REG_ECX)
    r_edx = mu.reg_read(UC_X86_REG_EDX)
    print(">>> ECX = 0x%x" %r_ecx)
    print(">>> EDX = 0x%x" %r_edx)

except UcError as e:
    print("ERROR: %s" % e)

输出结果
在这里插入图片描述

  • 第2〜3行:在使用Unicorn之前,导入unicorn模块。此示例还使用了一些X86寄存器常量,因此也需要unicorn.x86_const
  • 第6行:要模拟的原始二进制代码。此示例中的代码处于十六进制模式,代表两个X86指令“ INC ecx ”和“ DEC edx ”
  • 第9行:将在其中模拟上面的代码的虚拟地址
  • 第14行:使用类Uc初始化Unicorn 。此类接受2个参数:硬件体系结构和硬件模式。在此示例中,我们要模拟X86体系结构的32位代码。mu接受返回值
  • 第17行:mem_map在第9行声明的地址处映射2MB的内存用于此仿真。在此过程中,所有CPU操作都只能访问该内存。该内存使用默认权限READ,WRITE和EXECUTE映射
  • 第20行:将要模拟的代码写入到我们上面刚刚映射的内存中。mem_write方法采用2个参数:要写入的地址和要写入内存的代码
  • 23〜24行:使用reg_write方法设置ECX和EDX寄存器的值
  • 第27行:使用方法emu_start启动仿真。该API包含4个参数:仿真代码的地址,仿真停止的地址(紧随X86_CODE32的最后一个字节之后),要仿真的时间以及要仿真的指令数。如果像本例一样忽略最后两个参数,Unicorn将在无限的时间和无限数量的指令中模拟代码
  • 第32〜35行:打印出寄存器ECX和EDX的值。我们使用reg_read函数读取寄存器的值

0x50 总结

Unicorn Engine 用于仿真各种架构的 CPU 指令集,在 QEMU 的基础上,扩展了很多新特性和新功能,本文初步介绍了其简单的用法,利用 C 和 Python 调用了其 API,模拟了 x86 指令集,如果用其实际开发一个项目,你将会体会到 Unicorn 的独特魅力。在这里,是希望通过简短的介绍,让大家能够从源码以及用法的角度上,对 Unicorn 有一个更加全面的了解,为后续 Afl-Unicorn 进行 Fuzz 测试,或者编写自己的模拟器,提供一种思想上的理念。附录中的参考网址,都是值得学习的平台,尤其感谢 kabeor制作的非官方 API 参考文档。


  1. http://www.unicorn-engine.org/ ↩︎

  2. https://github.com/kabeor/Micro-Unicorn-Engine-API-Documentation/blob/master/Micro%20Unicorn-Engine%20API%20Documentation.md ↩︎

  3. http://www.unicorn-engine.org/docs/tutorial.html ↩︎

  4. https://blog.csdn.net/qq_38410428/article/details/102720550 ↩︎

AAAI 2020的教程“可解释人工智能”将重点介绍可解释人工智能的概念、方法和应用。可解释人工智能是指人工智能系统能够以一种可理解的方式解释其决策和行为的能力。该教程将涵盖可解释人工智能的基本原则和方法,包括规则推理、可视化技术、模型解释和对抗性机器学习等。 在教程中,我们将首先介绍可解释人工智能的背景和意义,解释为什么可解释性对于人工智能的发展至关重要。然后,我们将深入探讨可解释人工智能的基本概念和技术,例如局部解释和全局解释。我们还将介绍一些关键的可解释性方法,如LIME(局部诠释模型)和SHAP(SHapley Additive exPlanations),并解释它们的原理和应用场景。 此外,我们还将探讨可解释人工智能在各个领域的具体应用,包括医疗诊断、金融风险管理和智能驾驶等。我们将分享一些成功的案例和实践经验,探讨可解释人工智能在实际应用中的挑战和解决方案。最后,我们还将讨论未来可解释人工智能的发展趋势和挑战,展望可解释性在人工智能领域的重要性和前景。 通过参加该教程,学习者将能够全面了解可解释人工智能的概念、方法和应用,理解其在实际应用中的重要性,掌握一些关键的可解释性技术和工具,并对可解释人工智能的未来发展有一个清晰的认识。希望通过这次教程,能够为学习者提供一个全面而深入的可解释人工智能学习和交流平台。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

江下枫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值