逆向利器Unicorn——一款很酷的指令模拟器

什么是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, &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文件上传附件,需要的朋友可以自行下载。

  • 8
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

麦子zzy

谢谢支持

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

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

打赏作者

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

抵扣说明:

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

余额充值