文章目录
什么是Unicorn
Unicorn是一种基于QEMU的轻量级多架构CPU模拟器,可以模拟在不同的CPU架构上执行二进制代码。它提供了统一的接口,使得在不同的CPU架构上运行代码变得简单和高效。Unicorn的设计目标是提供一个轻量级、高效且可扩展的模拟器,以便在安全研究、调试和分析等领域中使用。
Unicorn的特点包括:
- 轻量级:Unicorn模拟器在运行时占用的内存和CPU资源都非常少,可以轻松地嵌入到其他应用程序中。
- 多架构:Unicorn支持多种不同的CPU架构,包括x86、ARM、MIPS等,可以轻松地模拟在不同的架构上执行二进制代码。
- 高效:Unicorn使用动态翻译技术,将二进制代码翻译成模拟器可以执行的指令,从而提高了模拟器的执行效率。
- 可扩展:Unicorn提供了丰富的API接口,可以方便地进行扩展和定制,以便满足不同的需求。
使用Unicorn可以带来很多好处,比如可以在不同的CPU架构上测试和验证二进制代码的行为,可以模拟程序的执行过程并收集性能数据,可以进行安全漏洞的分析和验证等。同时,Unicorn的使用也非常灵活,可以将其集成到其他应用程序中,也可以使用其提供的API接口进行扩展和定制。 - 跨平台性:Unicorn API可以在不同的操作系统上运行,包括Windows、Linux、Mac OS等。
Unicorn可以做什么
Unicorn是一个强大的指令模拟器,可以用于执行和分析各种不同的指令集。在下面的领域,Unicorn都可以一展身手:
- 漏洞分析:Unicorn可以用于模拟目标程序的运行环境,帮助安全研究人员和分析师测试和验证漏洞。通过在Unicorn中加载目标程序,并模拟其执行过程,研究人员可以观察程序的行为和状态,以便发现潜在的安全问题。
-调试和分析:Unicorn可以用于调试和分析应用程序的执行过程。通过在Unicorn中加载目标程序,并模拟其执行过程,开发人员可以逐步执行代码,观察变量的值和内存状态,以便调试程序中的错误和问题。
-逆向工程:Unicorn可以用于逆向工程二进制文件。通过加载目标文件并模拟其执行过程,分析人员可以观察程序的指令集和行为,以便理解程序的功能和结构。这有助于分析人员识别潜在的恶意软件或破解程序。
-性能分析:Unicorn可以用于分析和优化程序的性能。通过模拟程序的执行过程并收集性能数据,开发人员可以了解程序的瓶颈和优化机会,以便提高程序的性能和响应速度。
软件开发和测试:Unicorn可以用于开发和测试嵌入式系统和硬件驱动程序。通过模拟硬件环境并加载测试程序,开发人员可以测试程序的功能和稳定性,以便验证硬件和软件之间的交互和行为。
安装Unicorn
Unicorn支持多种编程语言。如果你使用C/C++开发,那么你可以在官网下载动态链接库(dll)或者静态链接(lib)以及头文件,直接使用即可。当然,你也可以下载源码自行编译使用。Java或其他语言同理。如果你使用Python语言开发,那么你可以在安装了Python环境的情况下,使用pip按装Unicorn模块,命令如下:
pip install unicorn
常用API简介
创建和关闭实例
uc_open
uc_err uc_open(uc_arch arch, uc_mode mode, uc_engine **uc);
这个函数用于新建一个Unicorn engine实例。
@arch: 架构类型
@mode: 硬件模式
@uc: 创建的 uc_engine 实例
@return 成功则返回UC_ERR_OK , 否则返回 uc_err 枚举的其他错误类型
Unicorn支持的架构类型和硬件模式如下:
// Architecture type
typedef enum uc_arch {
UC_ARCH_ARM = 1, // ARM architecture (including Thumb, Thumb-2)
UC_ARCH_ARM64, // ARM-64, also called AArch64
UC_ARCH_MIPS, // Mips architecture
UC_ARCH_X86, // X86 architecture (including x86 & x86-64)
UC_ARCH_PPC, // PowerPC architecture
UC_ARCH_SPARC, // Sparc architecture
UC_ARCH_M68K, // M68K architecture
UC_ARCH_RISCV, // RISCV architecture
UC_ARCH_S390X, // S390X architecture
UC_ARCH_TRICORE, // TriCore architecture
UC_ARCH_MAX,
} uc_arch;
// Mode type
typedef enum uc_mode {
UC_MODE_LITTLE_ENDIAN = 0, // little-endian mode (default mode)
UC_MODE_BIG_ENDIAN = 1 << 30, // big-endian mode
// arm / arm64
UC_MODE_ARM = 0, // ARM mode
UC_MODE_THUMB = 1 << 4, // THUMB mode (including Thumb-2)
// Depreciated, use UC_ARM_CPU_* with uc_ctl instead.
UC_MODE_MCLASS = 1 << 5, // ARM's Cortex-M series.
UC_MODE_V8 = 1 << 6, // ARMv8 A32 encodings for ARM
UC_MODE_ARMBE8 = 1 << 10, // Big-endian data and Little-endian code.
// Legacy support for UC1 only.
// arm (32bit) cpu types
// Depreciated, use UC_ARM_CPU_* with uc_ctl instead.
UC_MODE_ARM926 = 1 << 7, // ARM926 CPU type
UC_MODE_ARM946 = 1 << 8, // ARM946 CPU type
UC_MODE_ARM1176 = 1 << 9, // ARM1176 CPU type
// mips
UC_MODE_MICRO = 1 << 4, // MicroMips mode (currently unsupported)
UC_MODE_MIPS3 = 1 << 5, // Mips III ISA (currently unsupported)
UC_MODE_MIPS32R6 = 1 << 6, // Mips32r6 ISA (currently unsupported)
UC_MODE_MIPS32 = 1 << 2, // Mips32 ISA
UC_MODE_MIPS64 = 1 << 3, // Mips64 ISA
// x86 / x64
UC_MODE_16 = 1 << 1, // 16-bit mode
UC_MODE_32 = 1 << 2, // 32-bit mode
UC_MODE_64 = 1 << 3, // 64-bit mode
// ppc
UC_MODE_PPC32 = 1 << 2, // 32-bit mode
UC_MODE_PPC64 = 1 << 3, // 64-bit mode (currently unsupported)
UC_MODE_QPX =
1 << 4, // Quad Processing eXtensions mode (currently unsupported)
// sparc
UC_MODE_SPARC32 = 1 << 2, // 32-bit mode
UC_MODE_SPARC64 = 1 << 3, // 64-bit mode
UC_MODE_V9 = 1 << 4, // SparcV9 mode (currently unsupported)
// riscv
UC_MODE_RISCV32 = 1 << 2, // 32-bit mode
UC_MODE_RISCV64 = 1 << 3, // 64-bit mode
// m68k
} uc_mode;
uc_close
uc_err uc_close(uc_engine *uc);
这个函数用于关闭Unicorn引擎实例。
注意:只有uc实例不再使用时才能调用此函数。这个API释放了uc的一些缓存内存,因此在关闭uc之后,再使用任何其他的Unicorn API时可能使程序崩溃。在此之后,uc无效,并且不可用。
映射内存
uc_mem_map
uc_err uc_mem_map(uc_engine *uc, uint64_t address, size_t size, uint32_t perms);
使用Unicorn模拟执行代码,需要首先用uc_mem_map将要执行的代码映射到Unicorn虚拟内存中。
@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_unmap
uc_err uc_mem_unmap(uc_engine *uc, uint64_t address, size_t size);
此函数用于删除对虚拟内存区域的映射。
@uc: uc_open() 返回的句柄
@address: 要删除映射的内存区域的起始地址。此地址必须对齐为4KB,否则将返回UC_ERR_ARG错误。
@size: 要修改的内存区域大小。这个大小必须是4KB的倍数,否则将返回UC_ERR_ARG错误。
@return 成功则返回UC_ERR_OK , 否则返回 uc_err 枚举的其他错误类型。
读写内存
uc_mem_read
uc_err uc_mem_read(uc_engine *uc, uint64_t address, void *bytes, size_t size);
读取内存中的一系列字节。
@uc:uc_open() 返回的句柄
@address:要获取的字节的起始内存地址。
@bytes:指向包含从内存复制的数据的变量的指针。
@size:要读取的内存大小。
注意:@bytes 必须足够大,以包含 @size 字节。
@return 成功时返回UC_ERR_OK,失败时返回其他值
uc_mem_write
uc_err uc_mem_write(uc_engine *uc, uint64_t address, void *bytes, size_t size);
写入内存中的一系列字节。
@uc:uc_open() 返回的句柄
@address:要设置的字节的起始内存地址。
@bytes:指向包含要写入内存的数据的变量的指针。
@size:要写入的内存大小。
注意:@bytes 必须足够大,以包含 @size 字节。
@return 成功时返回UC_ERR_OK,失败时返回其他值
读写寄存器
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_reg_write
uc_err uc_reg_write(uc_engine *uc, int regid, const void *value);
写入寄存器。
@uc:uc_open() 返回的句柄
@regid:要修改的寄存器ID。
@value:指向将设置到寄存器 @regid 的值的指针。
@return 成功时返回UC_ERR_OK,失败时返回其他值
内存权限
uc_mem_protect
uc_err uc_mem_protect(uc_engine *uc, uint64_t address, size_t size, uint32_t perms);
为虚拟内存设置内存权限。此API更改现有内存区域的权限。
@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,失败时返回其他值
添加hook
uc_hook_add
uc_err uc_hook_add(uc_engine *uc, uc_hook *hh, int type, void *callback,
void *user_data, uint64_t begin, uint64_t end, ...);
为hook事件注册回调。当hook事件发生时,将运行callback回调函数。
@uc:uc_open() 返回的句柄
@hh:从此次注册中获得的钩子句柄。用于uc_hook_del() API
@type:hook类型。
@callback:当指令命中时运行的回调
@user_data:用户定义的数据。将作为回调函数的最后一个参数传递给该数据
@begin:回调生效的区域起始地址(包括)
@end:回调生效的区域结束地址(包括)
注意1:仅当相关地址在此范围[@begin, @end]内时,才会调用回调函数
注意2:如果@begin > @end,每当此钩子类型被触发时都会调用回调函数
@…:可变参数(取决于@type)
注意:如果@type = UC_HOOK_INSN,这是指令ID。
目前,只支持x86的in、out、syscall、sysenter、cpuid。
注意:如果@type = UC_HOOK_TCG_OPCODE,参数是@opcode和@flags。
@return UC_ERR_OK表示成功,其他值表示失败。
Unicorn支持颗粒化的hook机制,为我们了解代码行为提供了便利。Unicorn的hook类型可以分为以下三种:
- 指令执行类
UC_HOOK_INTR
UC_HOOK_INSN
UC_HOOK_CODE
UC_HOOK_BLOCK
- 内存访问类
UC_HOOK_MEM_READ
UC_HOOK_MEM_WRITE
UC_HOOK_MEM_FETCH
UC_HOOK_MEM_READ_AFTER
UC_HOOK_MEM_PROT
UC_HOOK_MEM_FETCH_INVALID
UC_HOOK_MEM_INVALID
UC_HOOK_MEM_VALID
- 异常处理类
UC_HOOK_MEM_READ_UNMAPPED
UC_HOOK_MEM_WRITE_UNMAPPED
UC_HOOK_MEM_FETCH_UNMAPPED
开机与关机
uc_emu_start
uc_err uc_emu_start(uc_engine *uc, uint64_t begin, uint64_t until,
uint64_t timeout, size_t count);
启动模拟器,开始执行代码。
@uc:由uc_open()返回的句柄
@begin:模拟开始所在的地址
@until:模拟停止的地址(即当此地址被命中时)
@timeout:模拟代码的时间(以微秒为单位)。当此值为0时,我们将无限期模拟代码,直到代码完成。
@count:要模拟的指令数量。当此值为0时,我们将模拟所有可用的代码,直到代码完成。
注意:只有当uc_emu_start返回无错误或错误已在回调中处理时,才能保证引擎的内部状态正确。
@return UC_ERR_OK表示成功,其他值表示失败
uc_emu_stop
uc_err uc_emu_stop(uc_engine *uc);
停止模拟(由uc_emu_start() API启动)。
@uc:由uc_open()返回的句柄
@return UC_ERR_OK表示成功,其他值表示失败
示例代码
这里给出使用Unicorn模拟执行arm指令的完整代码。想要了解更多架构的代码,可以前往官网查看。
/* Unicorn Emulator Engine */
/* By Nguyen Anh Quynh, 2015 */
/* Sample code to demonstrate how to emulate ARM code */
#include <unicorn/unicorn.h>
#include <string.h>
// code to be emulated
// #define ARM_CODE "\x37\x00\xa0\xe3" // mov r0, #0x37
#define ARM_CODE "\x00\xf0\x20\xe3" // nop
// #define ARM_CODE "\x37\x00\xa0\xe3\x03\x10\x42\xe0" // mov r0, #0x37; sub r1,
// r2, r3
#define THUMB_CODE "\x83\xb0" // sub sp, #0xc
#define ARM_THUM_COND_CODE \
"\x9a\x42\x14\xbf\x68\x22\x4d\x22" // 'cmp r2, r3\nit ne\nmov r2, #0x68\nmov
// r2, #0x4d'
// code to be emulated
#define ARM_CODE_EB \
"\xe3\xa0\x00\x37\xe0\x42\x10\x03" // mov r0, #0x37; sub r1, r2, r3
#define THUMB_CODE_EB "\xb0\x83" // sub sp, #0xc
// memory address where emulation starts
#define ADDRESS 0x10000
static void hook_block(uc_engine *uc, uint64_t address, uint32_t size,
void *user_data)
{
printf(">>> Tracing basic block at 0x%" PRIx64 ", block size = 0x%x\n",
address, size);
}
static void hook_code(uc_engine *uc, uint64_t address, uint32_t size,
void *user_data)
{
printf(">>> Tracing instruction at 0x%" PRIx64
", instruction size = 0x%x\n",
address, size);
}
static void test_arm(void)
{
uc_engine *uc;
uc_err err;
uc_hook trace1, trace2;
int r0 = 0x1234; // R0 register
int r2 = 0x6789; // R1 register
int r3 = 0x3333; // R2 register
int r1; // R1 register
printf("Emulate ARM code\n");
// Initialize emulator in ARM mode
err = uc_open(UC_ARCH_ARM, UC_MODE_ARM, &uc);
if (err) {
printf("Failed on uc_open() with error returned: %u (%s)\n", err,
uc_strerror(err));
return;
}
// map 2MB memory for this emulation
uc_mem_map(uc, ADDRESS, 2 * 1024 * 1024, UC_PROT_ALL);
// write machine code to be emulated to memory
uc_mem_write(uc, ADDRESS, ARM_CODE, sizeof(ARM_CODE) - 1);
// initialize machine registers
uc_reg_write(uc, UC_ARM_REG_R0, &r0);
uc_reg_write(uc, UC_ARM_REG_R2, &r2);
uc_reg_write(uc, UC_ARM_REG_R3, &r3);
// tracing all basic blocks with customized callback
uc_hook_add(uc, &trace1, UC_HOOK_BLOCK, hook_block, NULL, 1, 0);
// tracing one instruction at ADDRESS with customized callback
uc_hook_add(uc, &trace2, UC_HOOK_CODE, hook_code, NULL, ADDRESS, ADDRESS);
// emulate machine code in infinite time (last param = 0), or when
// finishing all the code.
err = uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(ARM_CODE) - 1, 0, 0);
if (err) {
printf("Failed on uc_emu_start() with error returned: %u\n", err);
}
// now print out some registers
printf(">>> Emulation done. Below is the CPU context\n");
uc_reg_read(uc, UC_ARM_REG_R0, &r0);
uc_reg_read(uc, UC_ARM_REG_R1, &r1);
printf(">>> R0 = 0x%x\n", r0);
printf(">>> R1 = 0x%x\n", r1);
uc_close(uc);
}
static void test_thumb(void)
{
uc_engine *uc;
uc_err err;
uc_hook trace1, trace2;
int sp = 0x1234; // R0 register
printf("Emulate THUMB code\n");
// Initialize emulator in ARM mode
err = uc_open(UC_ARCH_ARM, UC_MODE_THUMB, &uc);
if (err) {
printf("Failed on uc_open() with error returned: %u (%s)\n", err,
uc_strerror(err));
return;
}
// map 2MB memory for this emulation
uc_mem_map(uc, ADDRESS, 2 * 1024 * 1024, UC_PROT_ALL);
// write machine code to be emulated to memory
uc_mem_write(uc, ADDRESS, THUMB_CODE, sizeof(THUMB_CODE) - 1);
// initialize machine registers
uc_reg_write(uc, UC_ARM_REG_SP, &sp);
// tracing all basic blocks with customized callback
uc_hook_add(uc, &trace1, UC_HOOK_BLOCK, hook_block, NULL, 1, 0);
// tracing one instruction at ADDRESS with customized callback
uc_hook_add(uc, &trace2, UC_HOOK_CODE, hook_code, NULL, ADDRESS, ADDRESS);
// emulate machine code in infinite time (last param = 0), or when
// finishing all the code.
// Note we start at ADDRESS | 1 to indicate THUMB mode.
err = uc_emu_start(uc, ADDRESS | 1, ADDRESS + sizeof(THUMB_CODE) - 1, 0, 0);
if (err) {
printf("Failed on uc_emu_start() with error returned: %u\n", err);
}
// now print out some registers
printf(">>> Emulation done. Below is the CPU context\n");
uc_reg_read(uc, UC_ARM_REG_SP, &sp);
printf(">>> SP = 0x%x\n", sp);
uc_close(uc);
}
static void test_armeb(void)
{
uc_engine *uc;
uc_err err;
uc_hook trace1, trace2;
int r0 = 0x1234; // R0 register
int r2 = 0x6789; // R1 register
int r3 = 0x3333; // R2 register
int r1; // R1 register
printf("Emulate ARM Big-Endian code\n");
// Initialize emulator in ARM mode
err = uc_open(UC_ARCH_ARM, UC_MODE_ARM + UC_MODE_BIG_ENDIAN, &uc);
if (err) {
printf("Failed on uc_open() with error returned: %u (%s)\n", err,
uc_strerror(err));
return;
}
// map 2MB memory for this emulation
uc_mem_map(uc, ADDRESS, 2 * 1024 * 1024, UC_PROT_ALL);
// write machine code to be emulated to memory
uc_mem_write(uc, ADDRESS, ARM_CODE_EB, sizeof(ARM_CODE_EB) - 1);
// initialize machine registers
uc_reg_write(uc, UC_ARM_REG_R0, &r0);
uc_reg_write(uc, UC_ARM_REG_R2, &r2);
uc_reg_write(uc, UC_ARM_REG_R3, &r3);
// tracing all basic blocks with customized callback
uc_hook_add(uc, &trace1, UC_HOOK_BLOCK, hook_block, NULL, 1, 0);
// tracing one instruction at ADDRESS with customized callback
uc_hook_add(uc, &trace2, UC_HOOK_CODE, hook_code, NULL, ADDRESS, ADDRESS);
// emulate machine code in infinite time (last param = 0), or when
// finishing all the code.
err = uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(ARM_CODE_EB) - 1, 0, 0);
if (err) {
printf("Failed on uc_emu_start() with error returned: %u\n", err);
}
// now print out some registers
printf(">>> Emulation done. Below is the CPU context\n");
uc_reg_read(uc, UC_ARM_REG_R0, &r0);
uc_reg_read(uc, UC_ARM_REG_R1, &r1);
printf(">>> R0 = 0x%x\n", r0);
printf(">>> R1 = 0x%x\n", r1);
uc_close(uc);
}
static void test_thumbeb(void)
{
uc_engine *uc;
uc_err err;
uc_hook trace1, trace2;
int sp = 0x1234; // R0 register
printf("Emulate THUMB Big-Endian code\n");
// Initialize emulator in ARM mode
err = uc_open(UC_ARCH_ARM, UC_MODE_THUMB + UC_MODE_BIG_ENDIAN, &uc);
if (err) {
printf("Failed on uc_open() with error returned: %u (%s)\n", err,
uc_strerror(err));
return;
}
// map 2MB memory for this emulation
uc_mem_map(uc, ADDRESS, 2 * 1024 * 1024, UC_PROT_ALL);
// write machine code to be emulated to memory
uc_mem_write(uc, ADDRESS, THUMB_CODE_EB, sizeof(THUMB_CODE_EB) - 1);
// initialize machine registers
uc_reg_write(uc, UC_ARM_REG_SP, &sp);
// tracing all basic blocks with customized callback
uc_hook_add(uc, &trace1, UC_HOOK_BLOCK, hook_block, NULL, 1, 0);
// tracing one instruction at ADDRESS with customized callback
uc_hook_add(uc, &trace2, UC_HOOK_CODE, hook_code, NULL, ADDRESS, ADDRESS);
// emulate machine code in infinite time (last param = 0), or when
// finishing all the code.
// Note we start at ADDRESS | 1 to indicate THUMB mode.
err = uc_emu_start(uc, ADDRESS | 1, ADDRESS + sizeof(THUMB_CODE_EB) - 1, 0,
0);
if (err) {
printf("Failed on uc_emu_start() with error returned: %u\n", err);
}
// now print out some registers
printf(">>> Emulation done. Below is the CPU context\n");
uc_reg_read(uc, UC_ARM_REG_SP, &sp);
printf(">>> SP = 0x%x\n", sp);
uc_close(uc);
}
static void test_thumb_mrs(void)
{
uc_engine *uc;
uc_err err;
uc_hook trace1, trace2;
int pc;
printf("Emulate THUMB MRS instruction\n");
// 0xf3ef8014 - mrs r0, control
// Initialize emulator in ARM mode
err = uc_open(UC_ARCH_ARM, UC_MODE_THUMB, &uc);
if (err) {
printf("Failed on uc_open() with error returned: %u (%s)\n", err,
uc_strerror(err));
return;
}
// Setup the cpu model.
err = uc_ctl_set_cpu_model(uc, UC_CPU_ARM_CORTEX_M33);
if (err) {
printf("Failed on uc_ctl() with error returned: %u (%s)\n", err,
uc_strerror(err));
return;
}
// map 2MB memory for this emulation
uc_mem_map(uc, ADDRESS, 2 * 1024 * 1024, UC_PROT_ALL);
// write machine code to be emulated to memory
uc_mem_write(uc, ADDRESS, "\xef\xf3\x14\x80", 4);
// tracing all basic blocks with customized callback
uc_hook_add(uc, &trace1, UC_HOOK_BLOCK, hook_block, NULL, 1, 0);
// tracing one instruction at ADDRESS with customized callback
uc_hook_add(uc, &trace2, UC_HOOK_CODE, hook_code, NULL, ADDRESS, ADDRESS);
// emulate machine code in infinite time (last param = 0), or when
// finishing all the code.
// Note we start at ADDRESS | 1 to indicate THUMB mode.
err = uc_emu_start(uc, ADDRESS | 1, ADDRESS + 4, 0, 1);
if (err) {
printf("Failed on uc_emu_start() with error returned: %u\n", err);
}
// now print out some registers
printf(">>> Emulation done. Below is the CPU context\n");
uc_reg_read(uc, UC_ARM_REG_PC, &pc);
printf(">>> PC = 0x%x\n", pc);
if (pc != ADDRESS + 4) {
printf("Error, PC was 0x%x, expected was 0x%x.\n", pc, ADDRESS + 4);
}
uc_close(uc);
}
static void test_thumb_ite_internal(bool step, uint32_t *r2_out,
uint32_t *r3_out)
{
uc_engine *uc;
uc_err err;
uint32_t sp = 0x1234;
uint32_t r2 = 0, r3 = 1;
err = uc_open(UC_ARCH_ARM, UC_MODE_THUMB, &uc);
if (err) {
printf("Failed on uc_open() with error returned: %u (%s)\n", err,
uc_strerror(err));
return;
}
uc_mem_map(uc, ADDRESS, 2 * 1024 * 1024, UC_PROT_ALL);
uc_mem_write(uc, ADDRESS, ARM_THUM_COND_CODE,
sizeof(ARM_THUM_COND_CODE) - 1);
uc_reg_write(uc, UC_ARM_REG_SP, &sp);
uc_reg_write(uc, UC_ARM_REG_R2, &r2);
uc_reg_write(uc, UC_ARM_REG_R3, &r3);
if (!step) {
err = uc_emu_start(uc, ADDRESS | 1,
ADDRESS + sizeof(ARM_THUM_COND_CODE) - 1, 0, 0);
if (err) {
printf("Failed on uc_emu_start() with error returned: %u\n", err);
}
} else {
int i, addr = ADDRESS;
for (i = 0; i < sizeof(ARM_THUM_COND_CODE) / 2; i++) {
err = uc_emu_start(uc, addr | 1,
ADDRESS + sizeof(ARM_THUM_COND_CODE) - 1, 0, 1);
if (err) {
printf("Failed on uc_emu_start() with error returned: %u\n",
err);
}
uc_reg_read(uc, UC_ARM_REG_PC, &addr);
}
}
uc_reg_read(uc, UC_ARM_REG_R2, &r2);
uc_reg_read(uc, UC_ARM_REG_R3, &r3);
uc_close(uc);
*r2_out = r2;
*r3_out = r3;
}
static void test_thumb_ite()
{
uint32_t r2, r3;
uint32_t step_r2, step_r3;
printf("Emulate a THUMB ITE block as a whole or per instruction.\n");
// Run once.
printf("Running the entire binary.\n");
test_thumb_ite_internal(false, &r2, &r3);
printf(">>> R2: %d\n", r2);
printf(">>> R3: %d\n\n", r3);
// Step each instruction.
printf("Running the binary one instruction at a time.\n");
test_thumb_ite_internal(true, &step_r2, &step_r3);
printf(">>> R2: %d\n", step_r2);
printf(">>> R3: %d\n\n", step_r3);
if (step_r2 != r2 || step_r3 != r3) {
printf("Failed with ARM ITE blocks stepping!\n");
}
}
static void test_read_sctlr()
{
uc_engine *uc;
uc_err err;
uc_arm_cp_reg reg;
printf("Read the SCTLR register.\n");
err = uc_open(UC_ARCH_ARM, UC_MODE_ARM, &uc);
if (err != UC_ERR_OK) {
printf("Failed on uc_emu_start() with error returned: %u\n", err);
}
// SCTLR. See arm reference.
reg.cp = 15;
reg.is64 = 0;
reg.sec = 0;
reg.crn = 1;
reg.crm = 0;
reg.opc1 = 0;
reg.opc2 = 0;
err = uc_reg_read(uc, UC_ARM_REG_CP_REG, ®);
if (err != UC_ERR_OK) {
printf("Failed on uc_reg_read() with error returned: %u\n", err);
}
printf(">>> SCTLR = 0x%" PRIx32 "\n", (uint32_t)reg.val);
printf(">>> SCTLR.IE = %" PRId32 "\n", (uint32_t)((reg.val >> 31) & 1));
printf(">>> SCTLR.B = %" PRId32 "\n", (uint32_t)((reg.val >> 7) & 1));
uc_close(uc);
}
int main(int argc, char **argv, char **envp)
{
test_arm();
printf("==========================\n");
test_thumb();
printf("==========================\n");
test_armeb();
printf("==========================\n");
test_thumbeb();
printf("==========================\n");
test_thumb_mrs();
printf("==========================\n");
test_thumb_ite();
printf("==========================\n");
test_read_sctlr();
return 0;
}
总结
Unicorn不仅仅是一个模拟器,更是一种“硬件级”调试器,使用Unicorn的API可以轻松控制CPU寄存器、内存等资源,调试或调用目标二进制代码。而且,Unicorn相比于其他一些模拟器,它对性能的要求更高。
此外,Unicorn可以轻松的和其他工具进行集成,例如Frida。在某些情况下,我们可能需要在基本块级别或指令级别(即在插桩上有hooks)上具有更细的粒度。为了解决这个限制,常用的一个技巧是将hook与仿真结合起来。我们可以使用Frida来hook我们感兴趣的函数,然后我们可以转储CPU上下文和进程的内存状态,最终通过Unicorn这样的模拟器继续执行。
为了方便大家学习使用,我已经将unicorn的dll和lib文件上传附件,需要的朋友可以自行下载。