unicorn模拟执行初探

参考博客:

[[翻译]Unicorn引擎教程]: https://bbs.kanxue.com/thread-224330.htm#msg_header_h3_2
[unicorn模拟执行初探]: https://eps1l0h.github.io/2023/01/18/unicorn%E5%88%9D%E6%8E%A2/
[[Android 原创] 汇编与反汇编神器Unicorn]: https://www.52pojie.cn/thread-1026209-1-1.html

什么是unicorn?

Unicorn是一个轻量级,多平台,多架构的CPU模拟器框架,基于qemu开发,它可以代替CPU模拟代码的执行,就比如说我们需要调试某个程序,常见的调试器需要配置可执行文件需要的环境并且还需要考虑一些恶意代码导致的安全问题,但是通过unicorn我们就可以单纯的模拟代码的执行(甚至可以指定从某个地址开始执行)而不需要考虑这些问题。

Unicorn的优点(也是基于qemu而开发的):

  • 支持多种架构: Arm, Arm64 (Armv8), M68K, Mips, Sparc, & X86 (include X86_64).
  • Unicorn 为多种语言提供编程接口比如C/C++、Python、Java 等语言。Unicorn的DLL 可以被更多的语言调用,比如易语言、Delphi,前途无量。
  • 对Windows和nix系统(已确认包含Mac OSX, Linux, BSD & Solaris)的原生支持
  • 具有平台独立且简洁易于使用的API
  • 使用JIT编译技术, 性能表现优异
快速入门:

虚拟内存

Unicorn 采用虚拟内存机制,使得虚拟CPU的内存与真实CPU的内存隔离。Unicorn 使用如下API来操作内存:

1
2
3
uc_mem_map
uc_mem_read
uc_mem_write

这三个 API 都与内存操作有关:

1
2
3
uc_mem_map:这个 API 用于映射内存。当你需要在 Unicorn 中模拟一个程序时,可以使用 uc_mem_map 将一块内存映射到指定的地址。注意,这里的地址和大小都需要与 0x1000 对齐,也就是 0x1000 的整数倍。如果不满足对齐要求,会报 UC_ERR_ARG 异常123
uc_mem_read:这个 API 用于从模拟器内存中读取数据。可以指定地址和大小,然后 Unicorn 将返回相应的内存内容。
uc_mem_write:这个 API 用于向模拟器内存中写入数据。同样需要指定地址、大小和要写入的值。

Hook 机制

Unicorn的Hook机制为编程控制虚拟CPU提供了便利。
Unicorn 支持多种不同类型的Hook。
大致可以分为(hook_add第一参数,Unicorn常量):

指令执行类

1
2
3
4
UC_HOOK_INTR
UC_HOOK_INSN
UC_HOOK_CODE
UC_HOOK_BLOCK

这几个 API 都与 Unicorn 模拟器中的钩子(hook)有关:

1
2
3
4
UC_HOOK_INTR:这个 API 用于钩住特定的指令,但只支持非常少的指令。具体来说,它用于在模拟执行过程中拦截某个特定的指令。这个功能在调试和分析代码时非常有用。
UC_HOOK_INSN:这个 API 用于钩住一段代码范围内的指令。可以指定一个代码块,然后 Unicorn 将在执行这段代码时触发钩子。这对于跟踪代码执行流程、分析控制流等非常有帮助。
UC_HOOK_CODE:这个 API 用于钩住基本块(basic block)。基本块是一组连续的指令,通常是一个基本的控制流单元。当 Unicorn 模拟执行到一个基本块时,可以使用这个钩子来执行自定义的操作。
UC_HOOK_BLOCK:这个 API 用于在未映射内存上进行内存读取的钩子。当模拟执行到一个未映射的内存地址时,可以使用这个钩子来处理内存读取操作。

内存访问类

1
2
3
4
5
6
7
8
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
1
2
3
4
5
6
7
8
UC_HOOK_MEM_READ:这个 API 用于在模拟器执行期间钩住内存读取操作。当模拟执行到读取内存的指令时,可以使用这个钩子来执行自定义的操作。这对于跟踪内存访问、分析代码执行流程等非常有帮助。
UC_HOOK_MEM_WRITE:这个 API 用于在模拟器执行期间钩住内存写入操作。类似于 UC_HOOK_MEM_READ,可以在写入内存时触发这个钩子,以执行自定义的操作。
UC_HOOK_MEM_FETCH:这个 API 用于在模拟器执行期间钩住内存取指令(fetch)。当模拟执行到取指令的操作时,可以使用这个钩子来处理。
UC_HOOK_MEM_READ_AFTER:这个 API 用于在成功读取内存后触发钩子。与 UC_HOOK_MEM_READ 不同,这个钩子只在成功读取内存后执行。
UC_HOOK_MEM_PROT:这个 API 用于在模拟器执行期间钩住内存保护(protection)操作。当内存保护状态发生变化时,可以使用这个钩子来处理。
UC_HOOK_MEM_FETCH_INVALID:这个 API 用于在模拟器执行期间钩住无效的内存取指令。当模拟执行到无效的内存地址时,可以使用这个钩子来处理。
UC_HOOK_MEM_INVALID:这个 API 用于在模拟器执行期间钩住无效的内存访问操作。类似于 UC_HOOK_MEM_FETCH_INVALID,可以在无效的内存访问时触发这个钩子。
UC_HOOK_MEM_VALID:这个 API 用于在模拟器执行期间钩住有效的内存访问操作。当模拟执行到有效的内存地址时,可以使用这个钩子来处理。

异常处理类

1
2
3
UC_HOOK_MEM_READ_UNMAPPED
UC_HOOK_MEM_WRITE_UNMAPPED
UC_HOOK_MEM_FETCH_UNMAPPED
1
2
3
UC_HOOK_MEM_READ_UNMAPPED:这个 API 用于在模拟器执行期间钩住对未映射内存的读取操作。当模拟执行到一个未映射的内存地址时,可以使用这个钩子来处理内存读取操作。例如,如果程序试图读取一个未映射的内存地址,可以在这里添加自定义的逻辑。
UC_HOOK_MEM_WRITE_UNMAPPED:类似于 UC_HOOK_MEM_READ_UNMAPPED,这个 API 用于在模拟器执行期间钩住对未映射内存的写入操作。当程序试图向未映射的内存地址写入数据时,可以使用这个钩子来执行自定义的操作。
UC_HOOK_MEM_FETCH_UNMAPPED:这个 API 用于在模拟器执行期间钩住对未映射内存的取指令(fetch)。当模拟执行到一个未映射的内存地址时,可以使用这个钩子来处理取指令操作。

注意:调用hook_add函数可添加一个Hook。Unicorn的Hook是链式的,而不是传统Hook的覆盖式,也就是说,可以同时添加多个同类型的Hook,Unicorn会依次调用每一个handler。hook callback 是有作用范围的(见hook_add begin参数)。