序言
最近在学习如何使用自动化脚本解除OLLVM控制流平坦化的混淆时,遇到了一个难题。对于真实块的执行顺序与上下文存在关联时,如何找到真实块间的执行顺序,然后恢复控制流?
所幸已经有前辈给出了答案!
通过unicorn 模拟执行解决。但是没学习过unicorn 的使用,于是有了这篇文章。
unicorn 简介与安装
简介
Unicorn是一个基于Qemu的轻量级的多平台、多架构的 CPU 模拟器框架。
具备如下优点:
- 多架构:ARM, ARM64 (ARMv8), m68k, MIPS, PowerPC, RISC-V, S390x (SystemZ), SPARC, TriCore & x86 (include x86_64)
- 干净/简单/轻量级/直观的架构中立 API
- 对Windows和**nix系统(已确认包含Mac OSX, Linux,* BSD & Solaris)的原生支持
- 以纯 C 语言实现,绑定了 Crystal、Clojure、Visual Basic、Perl、Rust、Ruby、Python、Java、.NET、Go、Delphi/Free Pascal、Haskell、Pharo 和 Lua。
- 使用JIT编译技术,实现高性能
- 考虑了多线程安全
conda 安装
# conda 创建虚拟环境
conda create -n unicorn_ollvm python=3.9
# 安装 unicorn 模块
pip install unicorn -i http://pypi.douban.com/simple --trusted-host pypi.douban.com
unicorn 基础知识简记
虚拟内存
Unicorn 采用虚拟内存机制,使得虚拟CPU的内存与真实CPU的内存隔离。
# unicorn 操作内存的api
uc_mem_map(address, size)
uc_mem_read(address, size)
uc_mem_write(addreee, code)
使用uc_mem_map映射内存的时候,address 与 size 都需要与0x1000对齐,也就是0x1000的整数倍,否则会报UC_ERR_ARG 异常。
Hook 机制
Unicorn的Hook机制为编程控制虚拟CPU提供了便利。
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
调用hook_add函数可添加一个Hook。Unicorn的Hook是链式的,而不是传统Hook的覆盖式,也就是说,可以同时添加多个同类型的Hook,Unicorn会依次调用每一个handler。
实战学习
[翻译]Unicorn引擎教程-外文翻译-看雪论坛-安全社区|安全招聘|bbs.pediy.com (kanxue.com)
task1
该任务是hxp CTF 2017上的一个叫做Fibonacci
的例子。
由于程序使用递归进行,导致flag的print越来越慢,我们需要使用unicorn 模拟执行这一过程,将flag快速print。
main() | Fibonacci() |
---|---|
![]() |
![]() |
part1-unicorn 模拟该程序
脚本如下:【都有注释】
运行效果:【成功模拟执行,但是print速度比原程序还慢】
part2-模拟加速
上面步骤,已经可以成功模拟程序的执行了。
那么如何提高模拟运行效率呢?【】
涉及到算法中的记忆化搜索 - 知乎 (zhihu.com)
fibonacci() 这个函数有两个参数,分别存于EDI、RSI中,返回值写到RAX中,同时RSI的值只会为0或1。
由于是递归调用fibonacci()函数,那么当其参数固定时返回值也是确定的,所以可以考虑用dp数组保存这两个参数是否被访问过,来形成记忆化搜索。
具体可以使用dict保存这些成对的值:
- 在函数开始的时候,检查参数对应的值是否已经被dict记录
- 如果是,直接返回这个key-value就行,只需将返回值写入到
RAX
中,同时设置RIP
为RET
指令的值,退出这个函数。不能在fabonacci
函数内直接跳转到RET
,因为这条指令已经被HOOK了,所以我们跳转到main
中的ret
。 - 如果dict中没有出现参数和对应的值,将参数添加到dict中。
- 如果是,直接返回这个key-value就行,只需将返回值写入到
- 当退出函数的时候,保存返回值。可以从我们的栈结构中读取参数和返回值。
在hook_code
函数中添加
stack = []
dp = {}
FIBONACCI_ENTRY = 0x400670
FIBONACCI_END = [0x4006F1, 0x400709]
再次unicorn 模拟执行,很快得到了flag。
【注意:由于此时fibonacci这个函数还在被hook中,所以不能跳转到该函数本身的ret指令。因为这样会触发 hook函数中elif address in FIBONACCI_END:
的判断条件,导致stack失衡】
task2
分析下面的shellcode
hint
注意:代码基于x86-32
架构。syscall 的调用号所对应的功能可以在这里找到
你可以HOOKint 80h
指令,二进制代码是cd 80
,然后读取寄存器和内存。请记住,shellcode是可以在任何地址被加载执行的代码,绝大多数的shellcode使用栈来执行。
part1-disasm
Ubuntu 下使用pwntools 的disasm ,对shellcode 进行反汇编得到。
part2-unicorn 模拟执行
执行得到
调用号15对应 int chmod(const char *filename,int mode)
调用号1对应void exit(int status)
part3-获取完整信息
那么知道是对 chmod
和 exit
的系统调用。同时结合汇编一共用到了 eax, ebx, ecx, edx
四个寄存器存储对应调用的参数,可以再次修改 hook 函数对其进行判断输出详细信息。
模拟执行
可以知道,shellcode对/etc/shadow
设置666权限,也就是【所有者、用户组、其他】都具有可读写权限。
part4-capstone
使用capstone 反汇编引擎 print 完整汇编代码。
# 添加
from capstone import *
md = Cs(CS_ARCH_X86, CS_MODE_32)
# def hookcode() 中增加
for code in md.disasm(machine_code,address):
print(" 0x%x:\t%s\t%s" % (code.address, code.mnemonic, code.op_str))
完整汇编代码很长,就不贴出了。
task3
代码使用的编译选项:gcc function.c -m32 -o function
任务:如何使用unicorn 模拟执行使得函数super_function()的返回值为1。
一个显然的思路,使用unicorn在调用super_function()时将参数修改。
我们知道stdcall 函数调用约定,参数从右往左依次入栈。栈空间结构如下:
part1-unicorn-hook参数
在.text:000005CA E8 AC FF FF FF call super_function
处,进行参数hook。
task4
hint:目标架构不是x86
而是小端ARM32
- 函数的第一个参数通过
R0
传递(UC_ARM_REG_R0
) - 返回值同样在
R0
中 - 函数的第二个参数通过
R1
传递(UC_ARM_REG_R1
) - 可以通过
mu = Uc (UC_ARCH_ARM,UC_MODE_LITTLE_ENDIAN)
来初始化Unicorn。
a@x:~/Desktop/unicorn_engine_lessons$ file task4
task4: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=3dbf508680ba3d023d3422025954311e1d8fb4a1, not stripped
ida查看程序,发现和task1类似都是递归。
part1 - unicorn 模拟执行
程序的分析和task1差不多,直接贴脚本。
得到正确答案:2635833876
unicorn 小抄
from unicorn import *
- 加载Unicorn库。包含一些函数和基本的常量。
from unicorn.x86_const import*
- 加载 X86 和X64架构相关的常量
unicorn 模块中的常量
UC_API_MAJOR UC_ERR_VERSION UC_MEM_READ UC_PROT_ALL
UC_API_MINOR UC_ERR_WRITE_PROT UC_MEM_READ_AFTER UC_PROT_EXEC
UC_ARCH_ARM UC_ERR_WRITE_UNALIGNED UC_MEM_READ_PROT UC_PROT_NONE
UC_ARCH_ARM64 UC_ERR_WRITE_UNMAPPED UC_MEM_READ_UNMAPPED UC_PROT_READ
UC_ARCH_M68K UC_HOOK_BLOCK UC_MEM_WRITE UC_PROT_WRITE
UC_ARCH_MAX UC_HOOK_CODE UC_MEM_WRITE_PROT UC_QUERY_MODE
UC_ARCH_MIPS UC_HOOK_INSN UC_MEM_WRITE_UNMAPPED UC_QUERY_PAGE_SIZE
UC_ARCH_PPC UC_HOOK_INTR UC_MILISECOND_SCALE UC_SECOND_SCALE
UC_ARCH_SPARC UC_HOOK_MEM_FETCH UC_MODE_16 UC_VERSION_EXTRA
UC_ARCH_X86 UC_HOOK_MEM_FETCH_INVALID UC_MODE_32 UC_VERSION_MAJOR
UC_ERR_ARCH UC_HOOK_MEM_FETCH_PROT UC_MODE_64 UC_VERSION_MINOR
UC_ERR_ARG UC_HOOK_MEM_FETCH_UNMAPPED UC_MODE_ARM Uc
UC_ERR_EXCEPTION UC_HOOK_MEM_INVALID UC_MODE_BIG_ENDIAN UcError
UC_ERR_FETCH_PROT UC_HOOK_MEM_PROT UC_MODE_LITTLE_ENDIAN arm64_const
UC_ERR_FETCH_UNALIGNED UC_HOOK_MEM_READ UC_MODE_MCLASS arm_const
UC_ERR_FETCH_UNMAPPED UC_HOOK_MEM_READ_AFTER UC_MODE_MICRO debug
UC_ERR_HANDLE UC_HOOK_MEM_READ_INVALID UC_MODE_MIPS3 m68k_const
UC_ERR_HOOK UC_HOOK_MEM_READ_PROT UC_MODE_MIPS32 mips_const
UC_ERR_HOOK_EXIST UC_HOOK_MEM_READ_UNMAPPED UC_MODE_MIPS32R6 sparc_const
UC_ERR_INSN_INVALID UC_HOOK_MEM_UNMAPPED UC_MODE_MIPS64 uc_arch_supported
UC_ERR_MAP UC_HOOK_MEM_VALID UC_MODE_PPC32 uc_version
UC_ERR_MODE UC_HOOK_MEM_WRITE UC_MODE_PPC64 unicorn
UC_ERR_NOMEM UC_HOOK_MEM_WRITE_INVALID UC_MODE_QPX unicorn_const
UC_ERR_OK UC_HOOK_MEM_WRITE_PROT UC_MODE_SPARC32 version_bind
UC_ERR_READ_PROT UC_HOOK_MEM_WRITE_UNMAPPED UC_MODE_SPARC64 x86_const
UC_ERR_READ_UNALIGNED UC_MEM_FETCH UC_MODE_THUMB
UC_ERR_READ_UNMAPPED UC_MEM_FETCH_PROT UC_MODE_V8
UC_ERR_RESOURCE UC_MEM_FETCH_UNMAPPED UC_MODE_V9
unicorn.x86_const
中的常量
UC_X86_REG_EAX
UC_X86_REG_RIP
UC_X86_REG_RAX
常用函数示例
mu = Uc(arch,mode)
- 获取Uc
实例。在这里指定目标架构,例如:
mu = Uc(UC_ARCH_X86,UC_MODE_64)
- 获取X86-64
架构的实例。mu = Uc(UC_ARCH_X86,UC_MODE_32)
- 获取X86-32
架构的实例。
mu.mem_map(ADDRESS,4096)
- 映射一片内存区域
mu.mem_write(ADDRESS,DATA)
- 向内存中写入数据
tmp = mu.mem_read(ADDRESS,SIZE)
- 从内存中读取数据
mu.reg_write(UC_X86_REG_ECX,0X0)
- 设置ECX值。
r_esp = mu.reg_read(UC_X86_REG_ESP)
- 读取ESP的值。
mu.emu_start(ADDRESS_START,ADDRESS_END)
- 开始执行模拟。
Hook 命令追踪
def hook_code(mu, address, size, user_data):
print('>>> Tracing instruction at 0x%x, instruction size = 0x%x' %(address, size))
mu.hook_add(UC_HOOK_CODE, hook_code)
这段代码添加了一个HOOK(向Unicorn引擎中),我们定义的函数会在执行每一条命令之前被执行。参数含义如下:
Uc
实例- 指令的地址
- 指令的长度
- 用户定义数据(通过
hook_add()
函数传递)
hook的类型
C++
// All type of hooks for uc_hook_add() API.
typedef enum uc_hook_type {
// Hook all interrupt/syscall events
UC_HOOK_INTR = 1 << 0,
// Hook a particular instruction - only a very small subset of instructions supported here
UC_HOOK_INSN = 1 << 1,
// Hook a range of code
UC_HOOK_CODE = 1 << 2,
// Hook basic blocks
UC_HOOK_BLOCK = 1 << 3,
// Hook for memory read on unmapped memory
UC_HOOK_MEM_READ_UNMAPPED = 1 << 4,
// Hook for invalid memory write events
UC_HOOK_MEM_WRITE_UNMAPPED = 1 << 5,
// Hook for invalid memory fetch for execution events
UC_HOOK_MEM_FETCH_UNMAPPED = 1 << 6,
// Hook for memory read on read-protected memory
UC_HOOK_MEM_READ_PROT = 1 << 7,
// Hook for memory write on write-protected memory
UC_HOOK_MEM_WRITE_PROT = 1 << 8,
// Hook for memory fetch on non-executable memory
UC_HOOK_MEM_FETCH_PROT = 1 << 9,
// Hook memory read events.
UC_HOOK_MEM_READ = 1 << 10,
// Hook memory write events.
UC_HOOK_MEM_WRITE = 1 << 11,
// Hook memory fetch for execution events
UC_HOOK_MEM_FETCH = 1 << 12,
// Hook memory read events, but only successful access.
// The callback will be triggered after successful read.
UC_HOOK_MEM_READ_AFTER = 1 << 13,
} uc_hook_type;
关键函数的声明等
uc_open
C
/*
Create new instance of unicorn engine.
@arch: architecture type (UC_ARCH_*)
@mode: hardware mode. This is combined of UC_MODE_*
@uc: pointer to uc_engine, which will be updated at return time
@return UC_ERR_OK on success, or other value on failure (refer to uc_err enum
for detailed error).
*/
UNICORN_EXPORT
uc_err uc_open(uc_arch arch, uc_mode mode, uc_engine **uc);
@arch:架构类型(UC_ARCH_)
@mode:硬件模式。 这是结合 UC_MODE_
@uc:指向uc_engine的指针,返回时会更新
@return UC_ERR_OK 成功,或其他值失败(参考 uc_err 枚举详细错误)。
uc_mem_map
C
/*
Map memory in for emulation.
This API adds a memory region that can be used by emulation.
@uc: handle returned by uc_open()
@address: starting address of the new memory region to be mapped in.
This address must be aligned to 4KB, or this will return with UC_ERR_ARG error.
@size: size of the new memory region to be mapped in.
This size must be a multiple of 4KB, or this will return with UC_ERR_ARG error.
@perms: Permissions for the newly mapped region.
This must be some combination of UC_PROT_READ | UC_PROT_WRITE | UC_PROT_EXEC,
or this will return with UC_ERR_ARG error.
@return UC_ERR_OK on success, or other value on failure (refer to uc_err enum
for detailed error).
*/
UNICORN_EXPORT
uc_err uc_mem_map(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 成功,或其他值失败(参考 uc_err 枚举详细错误)。
最后一个参数是uc_prot的枚举值:
C
typedef enum uc_prot {
UC_PROT_NONE = 0,
UC_PROT_READ = 1,
UC_PROT_WRITE = 2,
UC_PROT_EXEC = 4,
UC_PROT_ALL = 7,
} uc_prot;
uc_mem_write
C
/*
Write to a range of bytes in memory.
@uc: handle returned by uc_open()
@address: starting memory address of bytes to set.
@bytes: pointer to a variable containing data to be written to memory.
@size: size of memory to write to.
NOTE: @bytes must be big enough to contain @size bytes.
@return UC_ERR_OK on success, or other value on failure (refer to uc_err enum
for detailed error).
*/
UNICORN_EXPORT
uc_err uc_mem_write(uc_engine *uc, uint64_t address, const void *bytes, size_t size);
写入内存中的字节范围。
@uc: uc_open() 返回的句柄
@address:要设置的字节的起始内存地址。
@bytes:指向包含要写入内存的数据的变量的指针。
@size:要写入的内存大小。
注意:@bytes 必须足够大以包含 @size 字节。
@return UC_ERR_OK 成功,或其他值失败(参考 uc_err 枚举详细错误)
uc_reg_write
C
/*
Write to register.
@uc: handle returned by uc_open()
@regid: register ID that is to be modified.
@value: pointer to the value that will set to register @regid
@return UC_ERR_OK on success, or other value on failure (refer to uc_err enum
for detailed error).
*/
UNICORN_EXPORT
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_err 枚举详细错误)。
uc_hook_add
C
/*
Register callback for a hook event.
The callback will be run when the hook event is hit.
@uc: handle returned by uc_open()
@hh: hook handle returned from this registration. To be used in uc_hook_del() API
@type: hook type, refer to uc_hook_type enum
@callback: callback to be run when instruction is hit
@user_data: user-defined data. This will be passed to callback function in its
last argument @user_data
@begin: start address of the area where the callback is in effect (inclusive)
@end: end address of the area where the callback is in effect (inclusive)
NOTE 1: the callback is called only if related address is in range [@begin, @end]
NOTE 2: if @begin > @end, callback is called whenever this hook type is triggered
@...: variable arguments (depending on @type)
NOTE: if @type = UC_HOOK_INSN, this is the instruction ID (ex: UC_X86_INS_OUT)
@return UC_ERR_OK on success, or other value on failure (refer to uc_err enum
for detailed error).
*/
UNICORN_EXPORT
uc_err uc_hook_add(uc_engine *uc, uc_hook *hh, int type, void *callback,
void *user_data, uint64_t begin, uint64_t end, ...);
为钩子事件注册回调
当钩子事件被命中时,回调将运行@uc: uc_open() 返回的句柄
@hh:从这个注册函数返回的钩子句柄。用于 uc_hook_del() API
@type:钩子类型,参考uc_hook_type枚举
@callback:当指令被命中时运行的回调
@user_data:用户定义的数据。这将传递给回调函数的最后一个参数@user_data
@begin:回调生效区域的起始地址(含)
@end:回调生效区域的结束地址(含)
注意 1:仅当相关地址在 [@begin, @end] 范围内时才会调用回调
注意 2:如果 @begin > @end,每当触发此钩子类型时都会调用回调
@…:可变参数(取决于@type)
注意:如果@type = UC_HOOK_INSN,这是指令ID(例如:UC_X86_INS_OUT)
@return UC_ERR_OK 成功,或其他值失败(参考 uc_err 枚举详细错误)。
C
// All type of hooks for uc_hook_add() API.
typedef enum uc_hook_type {
// Hook all interrupt/syscall events
UC_HOOK_INTR = 1 << 0,
**// Hook a particular instruction - only a very small subset of instructions supported here
UC_HOOK_INSN = 1 << 1,**
// Hook a range of code
UC_HOOK_CODE = 1 << 2,
// Hook basic blocks
UC_HOOK_BLOCK = 1 << 3,
// Hook for memory read on unmapped memory
UC_HOOK_MEM_READ_UNMAPPED = 1 << 4,
// Hook for invalid memory write events
UC_HOOK_MEM_WRITE_UNMAPPED = 1 << 5,
// Hook for invalid memory fetch for execution events
UC_HOOK_MEM_FETCH_UNMAPPED = 1 << 6,
// Hook for memory read on read-protected memory
UC_HOOK_MEM_READ_PROT = 1 << 7,
// Hook for memory write on write-protected memory
UC_HOOK_MEM_WRITE_PROT = 1 << 8,
// Hook for memory fetch on non-executable memory
UC_HOOK_MEM_FETCH_PROT = 1 << 9,
**// Hook memory read events.
UC_HOOK_MEM_READ = 1 << 10,**
// Hook memory write events.
UC_HOOK_MEM_WRITE = 1 << 11,
// Hook memory fetch for execution events
UC_HOOK_MEM_FETCH = 1 << 12,
// Hook memory read events, but only successful access.
// The callback will be triggered after successful read.
UC_HOOK_MEM_READ_AFTER = 1 << 13,
// Hook invalid instructions exceptions.
UC_HOOK_INSN_INVALID = 1 << 14,
} uc_hook_type;
uc_hook_add的第三个参数的值是2,即(uc_hook_type)UC_HOOK_INSN, 钩住一个特定的指令——这里只支持非常小的指令子集
如果第三个参数的值是0x400,说明这是钩取的内存读 // Hook memory read events.
反正这个函数的关键就在第三个参数
最后一个参数,就像函数声明中说的一样,如果@type = UC_HOOK_INSN,这是指令ID(例如:UC_X86_INS_OUT,就是一些X86的指令)。
标签:入门,ERR,hook,MEM,学习,HOOK,UC,unicorn,uc From: https://www.cnblogs.com/Only-xiaoxiao/p/17316343.html