认识一下Unicorn

一:初步认识

Unicorn 是一个轻量级的、跨平台的 CPU 模拟器框架,旨在为开发者提供一个简单而强大的工具,用于模拟和执行机器代码。它是基于 QEMU 的一部分,专注于提供高性能的 CPU 模拟,支持多种架构和模式。下面我们对 Unicorn 进行简单介绍,包括其特点、架构、使用场景和基本用法。

1. 特点

  • 多架构支持: Unicorn 支持多种 CPU 架构,包括但不限于 x86、ARM、AArch64、MIPS、PowerPC 和 SPARC。这使得它适用于广泛的应用场景。

  • 高性能: Unicorn 设计为高效的模拟器,能够快速执行机器代码,适合需要高性能的应用,如动态分析和逆向工程。

  • 易于使用: Unicorn 提供了简单的 API,开发者可以轻松地集成到自己的项目中。它支持多种编程语言的绑定,包括 C、Python、Ruby 和 Go。

  • 灵活的内存管理: Unicorn 允许开发者自定义内存映射,可以模拟不同的内存布局和访问权限。

  • 钩子机制: Unicorn 提供了钩子(hook)机制,允许开发者在特定事件(如指令执行、内存读写、系统调用等)发生时插入自定义代码,以便进行监控和调试。

2. 架构

Unicorn 的架构主要由以下几个部分组成:

  • 核心模拟器: 负责执行机器代码,管理 CPU 状态和内存。

  • 内存管理: 提供灵活的内存映射和访问控制,允许开发者定义可读、可写和可执行的内存区域。

  • 钩子系统: 允许开发者在特定事件发生时插入自定义处理逻辑。

  • 多架构支持: 每种架构都有其特定的实现,确保模拟的准确性和性能。

3. 使用场景

Unicorn 适用于多种场景,包括但不限于:

  • 动态分析: 在安全研究中,Unicorn 可以用于动态分析恶意软件,帮助研究人员理解其行为。

  • 逆向工程: 开发者可以使用 Unicorn 模拟执行二进制文件,以便分析其逻辑和功能。

  • 漏洞研究: Unicorn 可以帮助研究人员模拟漏洞利用过程,测试和验证漏洞的影响。

  • 教育和培训: Unicorn 是学习计算机体系结构和操作系统的一个很好的工具,学生可以通过模拟器理解底层原理。

4. 基本用法

以下是使用 Unicorn 的基本步骤,展示如何设置和运行一个简单的模拟:

安装 Unicorn

在使用 Unicorn 之前,需要先安装它。可以通过源代码编译或使用包管理器(如 pip)安装 Python 绑定。

bash 复制代码
# 使用 pip 安装 Python 绑定
pip install unicorn

示例代码

以下是一个简单的示例,展示如何使用 Unicorn 模拟执行一段 ARM Thumb 机器码:

python 复制代码
import unicorn
import unicorn.arm_const

# 定义要执行的机器码
CODE = b'\x0a\x46\x03\x46\x04\x92\x4F\xF0\x0B\x07\x00\xdf'  # 示例 ARM Thumb 代码

# 创建 Unicorn 模拟器实例
mu = unicorn.Uc(unicorn.UC_ARCH_ARM, unicorn.UC_MODE_THUMB)

# 映射内存
ADDRESS = 0x1000
SIZE = 1024
mu.mem_map(ADDRESS, SIZE)

# 写入机器码到内存
mu.mem_write(ADDRESS, CODE)

# 初始化寄存器
mu.reg_write(unicorn.arm_const.UC_ARM_REG_R0, 0x100)
mu.reg_write(unicorn.arm_const.UC_ARM_REG_R1, 0x200)

# 执行指令
mu.emu_start(ADDRESS, ADDRESS + len(CODE))

# 打印寄存器值
print("R0: 0x%x" % mu.reg_read(unicorn.arm_const.UC_ARM_REG_R0))
print("R1: 0x%x" % mu.reg_read(unicorn.arm_const.UC_ARM_REG_R1))

代码解释

  1. 导入库: 导入 Unicorn 库和 ARM 常量。
  2. 定义机器码: 定义要执行的 ARM Thumb 机器码。
  3. 创建模拟器实例: 创建一个 Unicorn 模拟器实例,指定架构和模式。
  4. 映射内存: 映射一段内存区域以存放机器码。
  5. 写入机器码: 将机器码写入映射的内存区域。
  6. 初始化寄存器: 设置寄存器的初始值。
  7. 执行指令: 启动模拟器,从指定地址开始执行指令。
  8. 打印寄存器值: 执行完成后,打印寄存器的值。

二:寄存器常量命名规则

在使用 Unicorn 模拟器时,寄存器常量的命名规则通常遵循特定的约定,以便于开发者理解和使用。以下是一些常见的寄存器常量命名规则和示例,主要以 ARM 和 x86 架构为例。

1. ARM 架构寄存器常量

在 Unicorn 中,ARM 寄存器常量通常以 UC_ARM_REG_ 前缀开头,后面跟随寄存器的名称。

  • 通用寄存器:

    • UC_ARM_REG_R0UC_ARM_REG_R15: 表示 ARM 的通用寄存器 R0 到 R15。
  • 程序计数器:

    • UC_ARM_REG_PC: 表示程序计数器(Program Counter),通常是 R15。
  • 堆栈指针:

    • UC_ARM_REG_SP: 表示堆栈指针(Stack Pointer),通常是 R13。
  • 链接寄存器:

    • UC_ARM_REG_LR: 表示链接寄存器(Link Register),通常是 R14。
  • 状态寄存器:

    • UC_ARM_REG_CPSR: 表示当前程序状态寄存器(Current Program Status Register)。

2. x86 架构寄存器常量

在 Unicorn 中,x86 寄存器常量通常以 UC_X86_REG_ 前缀开头,后面跟随寄存器的名称。

  • 通用寄存器:

    • UC_X86_REG_EAX: 表示扩展累加寄存器 EAX。
    • UC_X86_REG_EBX: 表示扩展基寄存器 EBX。
    • UC_X86_REG_ECX: 表示扩展计数寄存器 ECX。
    • UC_X86_REG_EDX: 表示扩展数据寄存器 EDX。
  • 指针寄存器:

    • UC_X86_REG_ESP: 表示扩展堆栈指针寄存器 ESP。
    • UC_X86_REG_EBP: 表示扩展基指针寄存器 EBP。
  • 程序计数器:

    • UC_X86_REG_EIP: 表示扩展指令指针寄存器 EIP。
  • 状态寄存器:

    • UC_X86_REG_EFLAGS: 表示扩展标志寄存器 EFLAGS。

3. MIPS 架构寄存器常量

在 Unicorn 中,MIPS 寄存器常量通常以 UC_MIPS_REG_ 前缀开头,后面跟随寄存器的名称。

  • 通用寄存器:

    • UC_MIPS_REG_R0: 表示 MIPS 的 R0 寄存器。
    • UC_MIPS_REG_R1: 表示 MIPS 的 R1 寄存器。
    • UC_MIPS_REG_R31: 表示 MIPS 的 R31 寄存器(通常是返回地址寄存器)。
  • 程序计数器:

    • UC_MIPS_REG_PC: 表示程序计数器(Program Counter)。

4. 总结

寄存器常量的命名规则在不同的架构中略有不同,但通常遵循以下原则:

  • 前缀 : 使用特定的前缀(如 UC_ARM_REG_UC_X86_REG_UC_MIPS_REG_)来标识寄存器的架构。
  • 寄存器名称: 使用寄存器的名称或编号,通常以大写字母表示。
  • 一致性 : 保持命名的一致性,以便于开发者理解和使用。
    简化概况:
    UC_ + 指令集 + REG + 大写寄存器名
    UC_ARMREG + 大写寄存器名 (UC_ARM_REG_R0)
    UC_X86REG + 大写寄存器名 (UC_X86_REG_EAX)

三:unicorn中的异常处理类

在 Unicorn 模拟器中,异常处理类主要用于处理模拟过程中可能出现的错误和异常情况。Unicorn 是一个轻量级的多架构 CPU 模拟器,广泛用于安全研究、逆向工程和动态分析。虽然 Unicorn 本身并没有专门的异常处理类,但它提供了一些机制和方法来处理在模拟过程中可能发生的错误。

1. Unicorn 的异常处理机制

Unicorn 的异常处理主要通过返回值和错误码来实现。

  • 返回值: Unicorn 的许多 API 函数在发生错误时会返回特定的错误码。开发者需要检查这些返回值,以确定是否发生了错误。
  • 错误码: Unicorn 定义了一系列错误码,表示不同类型的错误,例如内存访问错误、无效的指令等。

2. 常见的错误码

以下是一些 Unicorn 中常见的错误码及其含义:

  • UC_ERR_OK: 表示没有错误。
  • UC_ERR_MEM_UNMAPPED: 表示访问了未映射的内存。
  • UC_ERR_INSN_INVALID: 表示无效的指令。
  • UC_ERR_MODE: 表示模式不匹配(例如,尝试在不支持的模式下执行指令)。

3. 使用示例

以下是一个使用 Unicorn 的示例,展示如何处理可能的异常情况:

python 复制代码
import unicorn
from unicorn import *
from unicorn.x86_const import *

# 模拟器初始化
mu = Uc(UC_ARCH_X86, UC_MODE_64)

# 分配内存
ADDRESS = 0x1000
mu.mem_map(ADDRESS, 2 * 1024)  # 映射 2KB 内存

# 写入代码
CODE = b"\x90\x90"  # NOP 指令
mu.mem_write(ADDRESS, CODE)

# 设置程序计数器
mu.reg_write(UC_X86_REG_RIP, ADDRESS)

try:
    # 开始模拟
    mu.emu_start(ADDRESS, ADDRESS + len(CODE))
except UcError as e:
    print("Unicorn error: {}".format(e))

# 检查返回值
if mu.errno != UC_ERR_OK:
    print("Error occurred: {}".format(mu.errno))

4. 自定义异常处理

虽然 Unicorn 本身没有专门的异常处理类,但开发者可以创建自定义异常类来处理 Unicorn 模拟中的错误。可以帮助更好地管理错误并提供更清晰的错误信息。

示例:

python 复制代码
class UnicornException(Exception):
    """自定义异常类,用于处理 Unicorn 模拟中的错误"""
    def __init__(self, message, error_code):
        super().__init__(message)
        self.error_code = error_code

# 使用自定义异常处理
try:
    mu.emu_start(ADDRESS, ADDRESS + len(CODE))
except UcError as e:
    raise UnicornException("Unicorn error occurred", e.errno)

# 捕获自定义异常
try:
    # 可能引发异常的代码
    mu.emu_start(ADDRESS, ADDRESS + len(CODE))
except UnicornException as ue:
    print("Caught custom exception: {} with error code: {}".format(ue, ue.error_code))

四:Unicorn中的接口(包括python接口)

1.内存相关

在 Unicorn 模拟器中,内存管理是一个重要的功能,涉及到内存的映射、读取和写入。以下是对 uc_mem_mapuc_mem_readuc_mem_write 函数的介绍。

1. uc_mem_map

函数原型:

c 复制代码
uc_err uc_mem_map(uc_engine *uc, uint64_t address, size_t size, uint32_t perm);

用途 :
uc_mem_map 用于在模拟器中映射一段内存。映射的内存区域可以用于存储代码、数据等。

参数:

  • uc_engine *uc: 指向 Unicorn 引擎的指针。
  • uint64_t address: 要映射的内存起始地址。
  • size_t size: 要映射的内存大小(以字节为单位)。
  • uint32_t perm: 内存权限标志,通常是以下组合:
    • UC_MEM_WRITE: 可写
    • UC_MEM_READ: 可读
    • UC_MEM_EXEC: 可执行

示例:

c 复制代码
uc_err err;
err = uc_mem_map(uc, 0x1000, 0x2000, UC_MEM_READ | UC_MEM_WRITE | UC_MEM_EXEC);
if (err != UC_ERR_OK) {
    printf("Failed to map memory: %s\n", uc_strerror(err));
}

2. uc_mem_read

函数原型:

c 复制代码
uc_err uc_mem_read(uc_engine *uc, uint64_t address, void *buf, size_t size);

用途 :
uc_mem_read 用于从模拟器的内存中读取数据。

参数:

  • uc_engine *uc: 指向 Unicorn 引擎的指针。
  • uint64_t address: 要读取的内存地址。
  • void *buf: 指向存储读取数据的缓冲区。
  • size_t size: 要读取的字节数。

示例:

c 复制代码
uint8_t buffer[4];
uc_err err;
err = uc_mem_read(uc, 0x1000, buffer, sizeof(buffer));
if (err != UC_ERR_OK) {
    printf("Failed to read memory: %s\n", uc_strerror(err));
} else {
    printf("Read data: %02x %02x %02x %02x\n", buffer[0], buffer[1], buffer[2], buffer[3]);
}

3. uc_mem_write

函数原型:

c 复制代码
uc_err uc_mem_write(uc_engine *uc, uint64_t address, const void *buf, size_t size);

用途 :
uc_mem_write 用于向模拟器的内存中写入数据。

参数:

  • uc_engine *uc: 指向 Unicorn 引擎的指针。
  • uint64_t address: 要写入的内存地址。
  • const void *buf: 指向要写入数据的缓冲区。
  • size_t size: 要写入的字节数。

示例:

c 复制代码
uint8_t data[4] = {0x90, 0x90, 0x90, 0x90}; // NOP 指令
uc_err err;
err = uc_mem_write(uc, 0x1000, data, sizeof(data));
if (err != UC_ERR_OK) {
    printf("Failed to write memory: %s\n", uc_strerror(err));
}

4. 总结

  • uc_mem_map: 用于在 Unicorn 模拟器中映射一段内存,设置内存的权限。
  • uc_mem_read: 用于从映射的内存中读取数据。
  • uc_mem_write: 用于向映射的内存中写入数据。

python中的接口

在 Unicorn 模拟器的 Python 接口中,mu.mem_mapmu.mem_readmu.mem_write 是用于内存管理的关键函数。

1. mu.mem_map

函数原型:

python 复制代码
mu.mem_map(address, size, perm)

用途 :
mu.mem_map 用于在模拟器中映射一段内存。映射的内存区域可以用于存储代码、数据等。

参数:

  • address: 要映射的内存起始地址(整数)。
  • size: 要映射的内存大小(以字节为单位,整数)。
  • perm: 内存权限标志,可以是以下组合(可省略):
    • UC_MEM_READ: 可读
    • UC_MEM_WRITE: 可写
    • UC_MEM_EXEC: 可执行

示例:

python 复制代码
from unicorn import Uc, UC_ARCH_X86, UC_MODE_64, UC_MEM_READ, UC_MEM_WRITE, UC_MEM_EXEC

# 初始化 Unicorn 模拟器
mu = Uc(UC_ARCH_X86, UC_MODE_64)

# 映射一段内存
ADDRESS = 0x1000
SIZE = 0x2000  # 8KB
mu.mem_map(ADDRESS, SIZE, UC_MEM_READ | UC_MEM_WRITE | UC_MEM_EXEC)

2. mu.mem_read

函数原型:

python 复制代码
mu.mem_read(address, size)

用途 :
mu.mem_read 用于从模拟器的内存中读取数据。

参数:

  • address: 要读取的内存地址(整数)。
  • size: 要读取的字节数(整数)。

返回值 :

返回读取的数据,类型为字节串(bytes)。

示例:

python 复制代码
# 从映射的内存中读取数据
data = mu.mem_read(ADDRESS, 4)  # 读取4字节
print(f"Read data: {data.hex()}")

3. mu.mem_write

函数原型:

python 复制代码
mu.mem_write(address, data)

用途 :
mu.mem_write 用于向模拟器的内存中写入数据。

参数:

  • address: 要写入的内存地址(整数)。
  • data: 要写入的数据,类型为字节串(bytes)。

示例:

python 复制代码
# 向映射的内存中写入数据
data_to_write = b'\x90\x90\x90\x90'  # NOP 指令
mu.mem_write(ADDRESS, data_to_write)
print("Data written to memory.")

4. 总结

  • mu.mem_map: 用于在 Unicorn 模拟器中映射一段内存,设置内存的权限。
  • mu.mem_read: 用于从映射的内存中读取数据,返回读取的字节串。
  • mu.mem_write: 用于向映射的内存中写入数据,接受字节串作为输入。

2.寄存器相关

在 Unicorn 模拟器中,uc_reg_readuc_reg_write 是用于读取和写入寄存器的函数,而在 Python 接口中,mu.reg_readmu.reg_write 提供了相同的功能。

1. uc_reg_read (C API)

函数原型:

c 复制代码
uc_err uc_reg_read(uc_engine *uc, int reg, uint64_t *value);

用途 :
uc_reg_read 用于从模拟器中读取指定寄存器的值。

参数:

  • uc_engine *uc: 指向 Unicorn 引擎的指针。
  • int reg: 要读取的寄存器的标识符(例如,UC_X86_REG_EAX)。
  • uint64_t *value: 指向存储寄存器值的变量的指针。

返回值 :

返回错误代码,成功时为 UC_ERR_OK

示例:

c 复制代码
uint64_t eax_value;
uc_err err = uc_reg_read(uc, UC_X86_REG_EAX, &eax_value);
if (err != UC_ERR_OK) {
    printf("Failed to read EAX register: %s\n", uc_strerror(err));
} else {
    printf("EAX: %llu\n", eax_value);
}

2. uc_reg_write (C API)

函数原型:

c 复制代码
uc_err uc_reg_write(uc_engine *uc, int reg, uint64_t value);

用途 :
uc_reg_write 用于向模拟器中写入指定寄存器的值。

参数:

  • uc_engine *uc: 指向 Unicorn 引擎的指针。
  • int reg: 要写入的寄存器的标识符。
  • uint64_t value: 要写入的值。

返回值 :

返回错误代码,成功时为 UC_ERR_OK

示例:

c 复制代码
uc_err err = uc_reg_write(uc, UC_X86_REG_EAX, 0x12345678);
if (err != UC_ERR_OK) {
    printf("Failed to write EAX register: %s\n", uc_strerror(err));
}

3. mu.reg_read (Python API)

函数原型:

python 复制代码
value = mu.reg_read(reg)

用途 :
mu.reg_read 用于从模拟器中读取指定寄存器的值。

参数:

  • reg: 要读取的寄存器的标识符(例如,UC_X86_REG_EAX)。

返回值 :

返回寄存器的值(整数)。

示例:

python 复制代码
from unicorn import Uc, UC_ARCH_X86, UC_MODE_64, UC_X86_REG_EAX

# 初始化 Unicorn 模拟器
mu = Uc(UC_ARCH_X86, UC_MODE_64)

# 读取 EAX 寄存器的值
eax_value = mu.reg_read(UC_X86_REG_EAX)
print(f"EAX: {eax_value}")

4. mu.reg_write (Python API)

函数原型:

python 复制代码
mu.reg_write(reg, value)

用途 :
mu.reg_write 用于向模拟器中写入指定寄存器的值。

参数:

  • reg: 要写入的寄存器的标识符。
  • value: 要写入的值(整数)。

示例:

python 复制代码
# 向 EAX 寄存器写入值
mu.reg_write(UC_X86_REG_EAX, 0x12345678)
print("EAX register updated.")

5. 总结

  • uc_reg_read: C API,用于读取指定寄存器的值。
  • uc_reg_write: C API,用于向指定寄存器写入值。
  • mu.reg_read: Python API,用于读取指定寄存器的值,返回整数。
  • mu.reg_write: Python API,用于向指定寄存器写入值,接受整数作为输入。

3.指令执行类

在 Unicorn 模拟器中,指令执行类的钩子(hooks)允许开发者监控和控制指令的执行过程。这些钩子可以帮助开发者实现调试、分析和控制程序的执行流。以下是对指令执行钩子的详细介绍,包括它们的用途、参数、Python 中的对应类和示例。

1. UC_HOOK_INTR

  • 用途: 在处理器接收到中断时触发。可以用于监控中断的发生和处理。

  • 参数 :

    • uc: 当前的 Unicorn 模拟器实例。
    • intno: 中断号,表示触发的中断类型。
    • user_data: 用户自定义数据,可以在添加钩子时传递。
  • Python 示例 :

    python 复制代码
    def hook_intr(uc, intno, user_data):
        print(f"Interrupt received: {intno}")
    
    mu.hook_add(UC_HOOK_INTR, hook_intr)

2. UC_HOOK_INSN

  • 用途: 在每条指令执行之前触发。可以用于监控指令的执行,进行调试或分析。

  • 参数 :

    • uc: 当前的 Unicorn 模拟器实例。
    • address: 当前执行的指令地址。
    • size: 指令的字节数。
    • user_data: 用户自定义数据。
  • Python 示例 :

    python 复制代码
    def hook_insn(uc, address, size, user_data):
        print(f"Instruction executed at address: 0x{address:x}, size: {size}")
    
    mu.hook_add(UC_HOOK_INSN, hook_insn)

3. UC_HOOK_CODE

  • 用途: 在执行代码块时触发。可以用于监控代码块的执行情况。

  • 参数 :

    • uc: 当前的 Unicorn 模拟器实例。
    • address: 代码块的起始地址。
    • size: 代码块的字节数。
    • user_data: 用户自定义数据。
  • Python 示例 :

    python 复制代码
    def hook_code(uc, address, size, user_data):
        print(f"Code block executed at address: 0x{address:x}, size: {size}")
    
    mu.hook_add(UC_HOOK_CODE, hook_code)

4. UC_HOOK_BLOCK

  • 用途: 在执行代码块(基本块)时触发。可以用于监控基本块的执行情况,通常用于分析控制流。

  • 参数 :

    • uc: 当前的 Unicorn 模拟器实例。
    • address: 基本块的起始地址。
    • size: 基本块的字节数。
    • user_data: 用户自定义数据。
  • Python 示例 :

    python 复制代码
    def hook_block(uc, address, size, user_data):
        print(f"Basic block executed at address: 0x{address:x}, size: {size}")
    
    mu.hook_add(UC_HOOK_BLOCK, hook_block)

5.mu.hook_add 方法

方法原型:

python 复制代码
mu.hook_add(hook_type, callback, begin=0, end=0)

参数:

  • hook_type: 指定钩子的类型,可以是以下之一:

    • UC_HOOK_INTR: 中断钩子。
    • UC_HOOK_INSN: 指令钩子。
    • UC_HOOK_CODE: 代码块钩子。
    • UC_HOOK_BLOCK: 基本块钩子。
  • callback: 当钩子触发时调用的回调函数。该函数应接受相应的参数,具体取决于钩子的类型。

  • begin : 可选参数,指定钩子监控的起始地址(适用于 UC_HOOK_CODEUC_HOOK_BLOCK)。

  • end : 可选参数,指定钩子监控的结束地址(适用于 UC_HOOK_CODEUC_HOOK_BLOCK)。

cpp 复制代码
例如:mu.hook_add(UC_HOOK_CODE, hook_code, begin=ADDRESS, end=ADDRESS)
1. 添加指令钩子
python 复制代码
from unicorn import Uc, UC_ARCH_X86, UC_MODE_64, UC_HOOK_INSN, UC_X86_REG_EAX

# 初始化 Unicorn 模拟器
mu = Uc(UC_ARCH_X86, UC_MODE_64)

# 定义指令钩子的回调函数
def hook_insn(uc, address, size, user_data):
    print(f"Instruction executed at address: 0x{address:x}, size: {size}")

# 添加指令钩子
mu.hook_add(UC_HOOK_INSN, hook_insn)

# 这里可以添加代码以执行模拟
2. 添加代码块钩子
python 复制代码
# 定义代码块钩子的回调函数
def hook_code(uc, address, size, user_data):
    print(f"Code executed at address: 0x{address:x}, size: {size}")

# 添加代码块钩子,监控特定地址范围
mu.hook_add(UC_HOOK_CODE, hook_code, begin=0x1000, end=0x2000)
3. 添加中断钩子
python 复制代码
# 定义中断钩子的回调函数
def hook_intr(uc, intr_idx, user_data):
    print(f"Interrupt occurred: {intr_idx}")

# 添加中断钩子
mu.hook_add(UC_HOOK_INTR, hook_intr)
4. 添加基本块钩子
python 复制代码
# 定义基本块钩子的回调函数
def hook_block(uc, address, user_data):
    print(f"Basic block executed at address: 0x{address:x}")

# 添加基本块钩子
mu.hook_add(UC_HOOK_BLOCK, hook_block)

4.内存访问类

在 Unicorn 模拟器中,内存访问钩子(hooks)允许开发者监控和控制内存的读写操作。每个钩子都有特定的用途和参数。

1. UC_HOOK_MEM_READ

  • 用途: 在内存读取操作时触发。可以用于监控何时读取了特定内存地址的内容。

  • 参数 :

    • uc: 当前的 Unicorn 模拟器实例。
    • access: 访问类型(通常为 UC_MEM_READ)。
    • address: 被读取的内存地址。
    • size: 读取的字节数。
    • user_data: 用户自定义数据,可以在添加钩子时传递。
  • Python 示例 :

    python 复制代码
    def hook_mem_read(uc, access, address, size, user_data):
        print(f"Memory read at address: 0x{address:x}, size: {size}")
    
    mu.hook_add(UC_HOOK_MEM_READ, hook_mem_read)

2. UC_HOOK_MEM_WRITE

  • 用途: 在内存写入操作时触发。可以用于监控何时向特定内存地址写入数据。

  • 参数 :

    • uc: 当前的 Unicorn 模拟器实例。
    • access: 访问类型(通常为 UC_MEM_WRITE)。
    • address: 被写入的内存地址。
    • size: 写入的字节数。
    • user_data: 用户自定义数据。
  • Python 示例 :

    python 复制代码
    def hook_mem_write(uc, access, address, size, user_data):
        print(f"Memory write at address: 0x{address:x}, size: {size}")
    
    mu.hook_add(UC_HOOK_MEM_WRITE, hook_mem_write)

3. UC_HOOK_MEM_FETCH

  • 用途: 在内存取指令时触发。用于监控指令从内存中获取的情况。

  • 参数 :

    • uc: 当前的 Unicorn 模拟器实例。
    • access: 访问类型(通常为 UC_MEM_FETCH)。
    • address: 被取指令的内存地址。
    • size: 取指令的字节数。
    • user_data: 用户自定义数据。
  • Python 示例 :

    python 复制代码
    def hook_mem_fetch(uc, access, address, size, user_data):
        print(f"Memory fetch at address: 0x{address:x}, size: {size}")
    
    mu.hook_add(UC_HOOK_MEM_FETCH, hook_mem_fetch)

4. UC_HOOK_MEM_READ_AFTER

  • 用途: 在内存读取操作之后触发。可以用于在读取操作完成后执行某些逻辑。

  • 参数 :

    • uc: 当前的 Unicorn 模拟器实例。
    • access: 访问类型(通常为 UC_MEM_READ)。
    • address: 被读取的内存地址。
    • size: 读取的字节数。
    • user_data: 用户自定义数据。
  • Python 示例 :

    python 复制代码
    def hook_mem_read_after(uc, access, address, size, user_data):
        print(f"Memory read completed at address: 0x{address:x}, size: {size}")
    
    mu.hook_add(UC_HOOK_MEM_READ_AFTER, hook_mem_read_after)

5. UC_HOOK_MEM_PROT

  • 用途: 在内存保护操作时触发。用于监控内存保护的设置和更改。

  • 参数 :

    • uc: 当前的 Unicorn 模拟器实例。
    • access: 访问类型(通常为 UC_MEM_PROT)。
    • address: 被保护的内存地址。
    • size: 保护的字节数。
    • user_data: 用户自定义数据。
  • Python 示例 :

    python 复制代码
    def hook_mem_prot(uc, access, address, size, user_data):
        print(f"Memory protection change at address: 0x{address:x}, size: {size}")
    
    mu.hook_add(UC_HOOK_MEM_PROT, hook_mem_prot)

6. UC_HOOK_MEM_FETCH_INVALID

  • 用途: 在尝试从无效内存地址获取指令时触发。用于处理无效内存访问的情况。

  • 参数 :

    • uc: 当前的 Unicorn 模拟器实例。
    • access: 访问类型(通常为 UC_MEM_FETCH_INVALID)。
    • address: 尝试获取指令的无效内存地址。
    • size: 取指令的字节数。
    • user_data: 用户自定义数据。
  • Python 示例 :

    python 复制代码
    def hook_mem_fetch_invalid(uc, access, address, size, user_data):
        print(f"Invalid memory fetch attempt at address: 0x{address:x}")
    
    mu.hook_add(UC_HOOK_MEM_FETCH_INVALID, hook_mem_fetch_invalid)

7. UC_HOOK_MEM_INVALID

  • 用途: 在访问无效内存地址时触发。用于处理无效内存访问的情况。

  • 参数 :

    • uc: 当前的 Unicorn 模拟器实例。
    • access: 访问类型(通常为 UC_MEM_INVALID)。
    • address: 被访问的无效内存地址。
    • size: 访问的字节数。
    • user_data: 用户自定义数据。
  • Python 示例 :

    python 复制代码
    def hook_mem_invalid(uc, access, address, size, user_data):
        print(f"Invalid memory access at address: 0x{address:x}")
    
    mu.hook_add(UC_HOOK_MEM_INVALID, hook_mem_invalid)

8. UC_HOOK_MEM_VALID

  • 用途: 在访问有效内存地址时触发。用于确认内存访问的有效性。

  • 参数 :

    • uc: 当前的 Unicorn 模拟器实例。
    • access: 访问类型(通常为 UC_MEM_VALID)。
    • address: 被访问的有效内存地址。
    • size: 访问的字节数。
    • user_data: 用户自定义数据。
  • Python 示例 :

    python 复制代码
    def hook_mem_valid(uc, access, address, size, user_data):
        print(f"Valid memory access at address: 0x{address:x}")
    
    mu.hook_add(UC_HOOK_MEM_VALID, hook_mem_valid)

五:完结,看一下完整的代码叭

cpp 复制代码
from unicorn import *
from unicorn.arm_const import *
ARM_CODE   = b"\x37\x00\xa0\xe3\x03\x10\x42\xe0"
# mov r0, #0x37;
# sub r1, r2, r3
# Test ARM
 
# callback for tracing instructions
def hook_code(uc, address, size, user_data):
    print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" %(address, size))
 
def test_arm():
    print("Emulate ARM code")
    try:
        # Initialize emulator in ARM mode
        mu = Uc(UC_ARCH_ARM, UC_MODE_THUMB)
 
        # map 2MB memory for this emulation
        ADDRESS = 0x10000
        mu.mem_map(ADDRESS, 2 * 0x10000)
        mu.mem_write(ADDRESS, ARM_CODE)
 
        mu.reg_write(UC_ARM_REG_R0, 0x1234)
        mu.reg_write(UC_ARM_REG_R2, 0x6789)
        mu.reg_write(UC_ARM_REG_R3, 0x3333)
 
        mu.hook_add(UC_HOOK_CODE, hook_code, begin=ADDRESS, end=ADDRESS)
        # emulate machine code in infinite time
        mu.emu_start(ADDRESS, ADDRESS + len(ARM_CODE))
        r0 = mu.reg_read(UC_ARM_REG_R0)
        r1 = mu.reg_read(UC_ARM_REG_R1)
        print(">>> R0 = 0x%x" % r0)
        print(">>> R1 = 0x%x" % r1)
    except UcError as e:
        print("ERROR: %s" % e)

代码解释,看看都能不能看懂了

代码解释

  1. 导入库:

    python 复制代码
    from unicorn import *
    from unicorn.arm_const import *

    这两行代码导入了 Unicorn 模拟器的核心库和 ARM 架构的常量。

  2. 定义 ARM 代码:

    python 复制代码
    ARM_CODE = b"\x37\x00\xa0\xe3\x03\x10\x42\xe0"

    这里定义了一段 ARM 机器码,包含两个指令:

    • mov r0, #0x37:将立即数 0x37 移动到寄存器 r0
    • sub r1, r2, r3:将 r2r3 的值相减,并将结果存储到 r1
  3. 钩子函数:

    python 复制代码
    def hook_code(uc, address, size, user_data):
        print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" % (address, size))

    这个回调函数在每次执行指令时被调用,打印出当前指令的地址和大小。

  4. 主测试函数:

    python 复制代码
    def test_arm():
        print("Emulate ARM code")
        try:
            # Initialize emulator in ARM mode
            mu = Uc(UC_ARCH_ARM, UC_MODE_THUMB)

    这里初始化了一个 Unicorn 模拟器实例,设置为 ARM 架构和 Thumb 模式。

  5. 内存映射和写入:

    python 复制代码
            ADDRESS = 0x10000
            mu.mem_map(ADDRESS, 2 * 0x10000)
            mu.mem_write(ADDRESS, ARM_CODE)

    映射了 2MB 的内存,并将 ARM 代码写入指定的内存地址。

  6. 寄存器初始化:

    python 复制代码
            mu.reg_write(UC_ARM_REG_R0, 0x1234)
            mu.reg_write(UC_ARM_REG_R2, 0x6789)
            mu.reg_write(UC_ARM_REG_R3, 0x3333)

    初始化寄存器 r0, r2, 和 r3 的值。

  7. 添加钩子:

    python 复制代码
            mu.hook_add(UC_HOOK_CODE, hook_code, begin=ADDRESS, end=ADDRESS)

    添加代码钩子,以便在执行代码时调用 hook_code 函数。

  8. 开始模拟:

    python 复制代码
            mu.emu_start(ADDRESS, ADDRESS + len(ARM_CODE))

    开始模拟执行 ARM 代码。

  9. 读取寄存器值:

    python 复制代码
            r0 = mu.reg_read(UC_ARM_REG_R0)
            r1 = mu.reg_read(UC_ARM_REG_R1)
            print(">>> R0 = 0x%x" % r0)
            print(">>> R1 = 0x%x" % r1)

    模拟结束后,读取并打印寄存器 r0r1 的值。

  10. 异常处理:

    python 复制代码
       except UcError as e:
           print("ERROR: %s" % e)

代码修改

为了确保代码能够正确运行,以下是一些必要的修改:

  1. 钩子范围 : 在添加钩子时,beginend 参数应设置为代码的起始和结束地址。
  2. 寄存器值 : 在执行 sub 指令之前,确保 r2r3 的值已正确设置。

更新后的代码

python 复制代码
from unicorn import *
from unicorn.arm_const import *

ARM_CODE = b"\x37\x00\xa0\xe3\x03\x10\x42\xe0"  # mov r0, #0x37; sub r1, r2, r3

# callback for tracing instructions
def hook_code(uc, address, size, user_data):
    print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" % (address, size))

def test_arm():
    print("Emulate ARM code")
    try:
        # Initialize emulator in ARM mode
        mu = Uc(UC_ARCH_ARM, UC_MODE_THUMB)

        # map 2MB memory for this emulation
        ADDRESS = 0x10000
        mu.mem_map(ADDRESS, 2 * 0x10000)
        mu.mem_write(ADDRESS, ARM_CODE)

        # Initialize registers
        mu.reg_write(UC_ARM_REG_R0, 0x1234)  # r0 will be set to 0x37
        mu.reg_write(UC_ARM_REG_R2, 0x6789)  # r2 value for subtraction
        mu.reg_write(UC_ARM_REG_R3, 0x3333)  # r3 value for subtraction

        # Add hook for the code range
        mu.hook_add(UC_HOOK_CODE, hook_code, begin=ADDRESS, end=ADDRESS + len(ARM_CODE))

        # emulate machine code
        mu.emu_start(ADDRESS, ADDRESS + len(ARM_CODE))

        # Read back the values of registers
        r0 = mu.reg_read(UC_ARM_REG_R0)
        r1 = mu.reg_read(UC_ARM_REG_R1)
        print(">>> R0 = 0x%x" % r0)
        print(">>> R1 = 0x%x" % r1)
    except UcError as e:
        print("ERROR: %s" % e)

# Run the test
test_arm()

运行结果

运行此代码后,将看到每条指令的跟踪输出,以及最终寄存器 r0r1 的值。r0 应该是 0x37,而 r1 将是 0x6789 - 0x3333 的结果。

相关推荐
sunly_1 小时前
Flutter:AnimatedPadding动态修改padding
android·flutter
Tony_long74832 小时前
Python学习——猜拳小游戏
开发语言·python·学习
诸神黄昏EX2 小时前
Android 常用命令和工具解析之GPU相关
android
顾北川_野2 小时前
Android 布局菜单或按钮图标或Menu/Item设置可见和不可见
android
练习本2 小时前
android 动画原理分析
android
别拿曾经看以后~2 小时前
原生Android调用uniapp项目中的方法
android·vue.js·uni-app
Winston Wood2 小时前
Android Binder技术概览
android·binder·进程通信
踏雪羽翼2 小时前
android 使用实现音效--Equalizer
android·音效·eqequalizer·bassboost·presetreverb
老码沉思录2 小时前
Android开发实战班 - Android开发基础之 Kotlin语言基础与特性
android·微信·kotlin
陈苏同学2 小时前
机器翻译 & 数据集 (NLP基础 - 预处理 → tokenize → 词表 → 截断/填充 → 迭代器) + 代码实现 —— 笔记3.9《动手学深度学习》
人工智能·pytorch·笔记·python·深度学习·自然语言处理·机器翻译