一:初步认识
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))
代码解释
- 导入库: 导入 Unicorn 库和 ARM 常量。
- 定义机器码: 定义要执行的 ARM Thumb 机器码。
- 创建模拟器实例: 创建一个 Unicorn 模拟器实例,指定架构和模式。
- 映射内存: 映射一段内存区域以存放机器码。
- 写入机器码: 将机器码写入映射的内存区域。
- 初始化寄存器: 设置寄存器的初始值。
- 执行指令: 启动模拟器,从指定地址开始执行指令。
- 打印寄存器值: 执行完成后,打印寄存器的值。
二:寄存器常量命名规则
在使用 Unicorn 模拟器时,寄存器常量的命名规则通常遵循特定的约定,以便于开发者理解和使用。以下是一些常见的寄存器常量命名规则和示例,主要以 ARM 和 x86 架构为例。
1. ARM 架构寄存器常量
在 Unicorn 中,ARM 寄存器常量通常以 UC_ARM_REG_
前缀开头,后面跟随寄存器的名称。
-
通用寄存器:
UC_ARM_REG_R0
到UC_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_map
、uc_mem_read
和 uc_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_map
、mu.mem_read
和 mu.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_read
和 uc_reg_write
是用于读取和写入寄存器的函数,而在 Python 接口中,mu.reg_read
和 mu.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 示例 :
pythondef 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 示例 :
pythondef 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 示例 :
pythondef 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 示例 :
pythondef 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_CODE
和UC_HOOK_BLOCK
)。 -
end
: 可选参数,指定钩子监控的结束地址(适用于UC_HOOK_CODE
和UC_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 示例 :
pythondef 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 示例 :
pythondef 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 示例 :
pythondef 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 示例 :
pythondef 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 示例 :
pythondef 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 示例 :
pythondef 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 示例 :
pythondef 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 示例 :
pythondef 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)
代码解释,看看都能不能看懂了
代码解释
-
导入库:
pythonfrom unicorn import * from unicorn.arm_const import *
这两行代码导入了 Unicorn 模拟器的核心库和 ARM 架构的常量。
-
定义 ARM 代码:
pythonARM_CODE = b"\x37\x00\xa0\xe3\x03\x10\x42\xe0"
这里定义了一段 ARM 机器码,包含两个指令:
mov r0, #0x37
:将立即数0x37
移动到寄存器r0
。sub r1, r2, r3
:将r2
和r3
的值相减,并将结果存储到r1
。
-
钩子函数:
pythondef hook_code(uc, address, size, user_data): print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" % (address, size))
这个回调函数在每次执行指令时被调用,打印出当前指令的地址和大小。
-
主测试函数:
pythondef test_arm(): print("Emulate ARM code") try: # Initialize emulator in ARM mode mu = Uc(UC_ARCH_ARM, UC_MODE_THUMB)
这里初始化了一个 Unicorn 模拟器实例,设置为 ARM 架构和 Thumb 模式。
-
内存映射和写入:
pythonADDRESS = 0x10000 mu.mem_map(ADDRESS, 2 * 0x10000) mu.mem_write(ADDRESS, ARM_CODE)
映射了 2MB 的内存,并将 ARM 代码写入指定的内存地址。
-
寄存器初始化:
pythonmu.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
的值。 -
添加钩子:
pythonmu.hook_add(UC_HOOK_CODE, hook_code, begin=ADDRESS, end=ADDRESS)
添加代码钩子,以便在执行代码时调用
hook_code
函数。 -
开始模拟:
pythonmu.emu_start(ADDRESS, ADDRESS + len(ARM_CODE))
开始模拟执行 ARM 代码。
-
读取寄存器值:
pythonr0 = 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)
模拟结束后,读取并打印寄存器
r0
和r1
的值。 -
异常处理:
pythonexcept UcError as e: print("ERROR: %s" % e)
代码修改
为了确保代码能够正确运行,以下是一些必要的修改:
- 钩子范围 : 在添加钩子时,
begin
和end
参数应设置为代码的起始和结束地址。 - 寄存器值 : 在执行
sub
指令之前,确保r2
和r3
的值已正确设置。
更新后的代码
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()
运行结果
运行此代码后,将看到每条指令的跟踪输出,以及最终寄存器 r0
和 r1
的值。r0
应该是 0x37
,而 r1
将是 0x6789 - 0x3333
的结果。