Unicorn Engine
2026-03-26 10:41:28 2026-03-29 20:08:58
Unicorn Engine 是一个基于 qemu 的轻量级多平台、多架构的反汇编引擎,支持 x86、x86_64、ARM、AArch64、MIPS、PowerPC、SPARC 等多种架构。它提供了一个简单的 API,可以用于模拟和调试二进制代码。
Unicorn 只负责模拟 CPU 的指令执行,因此在学习使用时候需要手动模拟操作系统为 cpu 使用。
安装
最简单的方法就是通过 pip 安装 Unicorn Engine
Unicorn 还有两个同源项目,分别是 Keystone Engine(一个汇编引擎)和 Capstone Engine(一个反汇编引擎),它们也可以通过 pip 安装:
Bash pip install keystone-engine
pip install capstone
当然还以上都可以自己编译,官方文档里有详细的说明。
基础使用
示例代码
很多博客还有官方文档都使用的下面这段代码介绍基础使用
Python #!/usr/bin/python
from __future__ import print_function
from unicorn import *
from unicorn.x86_const import *
# code to be emulated
X86_CODE32 = b " \x41\x4a " # INC ecx; DEC edx
# memory address where emulation starts
ADDRESS = 0x1000000
print ( "Emulate i386 code" )
try :
# Initialize emulator in X86-32bit mode
mu = Uc ( UC_ARCH_X86 , UC_MODE_32 )
# map 2MB memory for this emulation
mu . mem_map ( ADDRESS , 2 * 1024 * 1024 )
# write machine code to be emulated to memory
mu . mem_write ( ADDRESS , X86_CODE32 )
# initialize machine registers
mu . reg_write ( UC_X86_REG_ECX , 0x1234 )
mu . reg_write ( UC_X86_REG_EDX , 0x7890 )
# emulate code in infinite time & unlimited instructions
mu . emu_start ( ADDRESS , ADDRESS + len ( X86_CODE32 ))
# now print out some registers
print ( "Emulation done. Below is the CPU context" )
r_ecx = mu . reg_read ( UC_X86_REG_ECX )
r_edx = mu . reg_read ( UC_X86_REG_EDX )
print ( ">>> ECX = 0x %x " % r_ecx )
print ( ">>> EDX = 0x %x " % r_edx )
except UcError as e :
print ( "ERROR: %s " % e )
这段代码可以很简单且直观的大致看出 unicorn 的使用框架和能处理的任务:
初始化模拟器,指定架构和模式
分配内存大小和地址并写入要模拟的机器码
初始化寄存器
开始模拟
读取最终结果寄存器的值
我们很直观的感受到 Unicorn 就是一个可以模拟执行机器码的 cpu 模拟器。
模拟构建流程
当我们希望使用 Unicorn 来构建一个模拟器来模拟一段机器码时,正常来说,我们应该完成以下这些步骤:
1. 模拟器初始化
使用 Uc() 可以初始化一个模拟器,此时我们需要选择我们需要的 cpu 架构,比如我们需要使用 32bit 的 x86 架构,那么我们构建模拟器时就应该选择 mu = Uc(UC_ARCH_X86, UC_MODE_32) 来初始化模拟器。
2. 内存分配与写入
我们需要手动为模拟器分配内存空间 mu.mem_map(ADDRESS, 2 * 1024 * 1024) 这个接口会为第一个参数的地址分配第二个参数大小的内存空间,当我们需要额外开辟堆栈在其他起始地址的内存空间时,我们可以多次调用 mu.mem_map() 来往其他多个起始地址出分配内存。
使用 mu.mem_write(ADDRESS, X86_CODE32) 可以往第一个参数所代表的地址写入第二个参数的值。
3. 寄存器初始化
使用 mu.reg_write() 可以初始化寄存器的值,比如 mu.reg_write(UC_X86_REG_ECX, 0x1234)。
4. 开始模拟
使用 mu.emu_start() 可以开始模拟代码的执行,第一个参数传入起始地址,第二个参数传入最终地址。
5. 读取最终结果寄存器的值
使用 mu.reg_read() 可以读取寄存器的值,比如 r_ecx = mu.reg_read(UC_X86_REG_ECX)。
进阶使用
汇编转机器码
在上述范例中我们需要手写机器码,其实 Unicorn 有一个同源兄弟专门可以将汇编代码转换成机器码的引擎 Keystone Engine,我们可以使用它来生成机器码:
Python from keystone import Ks , KS_ARCH_ARM64 , KS_MODE_LITTLE_ENDIAN
CODE_ADDR = 0x100000
ASM_CODE = r """
start:
cbz x1, done
loop:
ldrb w2, [x0]
add w2, w2, #1
strb w2, [x0], #1
subs x1, x1, #1
b.ne loop
done:
ret
"""
def assemble_arm64 ( asm_code : str , base_addr : int ) -> bytes :
"""用 Keystone 组装 ARM64 汇编"""
ks = Ks ( KS_ARCH_ARM64 , KS_MODE_LITTLE_ENDIAN )
encoding , _ = ks . asm ( asm_code , addr = base_addr )
return bytes ( encoding )
code = assemble_arm64 ( ASM_CODE , CODE_ADDR )
这里生成的 code 就可以直接像上面 mu.mem_write(CODE_ADDR, code) 一样作为机器码写入内存中进行模拟了。
反汇编机器码
在运行时,我们从内存中读取到机器码时,我们可以使用同源兄弟 Capstone Engine 来将机器码反汇编成汇编指令:
Python from capstone import Cs , CS_ARCH_ARM64 , CS_MODE_LITTLE_ENDIAN
def disasm_one ( md : Cs , code : bytes , address : int ) -> str :
"""反汇编单条指令"""
insns = list ( md . disasm ( code , address ))
if not insns :
return f "0x { address : x } : <invalid>"
insn = insns [ 0 ]
return f "0x { insn . address : 016x } : { insn . mnemonic : <8 } { insn . op_str } "
md = Cs ( CS_ARCH_ARM64 , CS_MODE_LITTLE_ENDIAN )
raw = bytes ( mu . mem_read ( address , size ))
line = disasm_one ( md , raw , address )
print ( line )
HOOK
Unicorn 还提供了一些 hook 接口,可以在模拟过程中 hook 指令执行、内存读写等事件。
Python # ========= hook:代码执行 =========
def hook_code ( uc : Uc , address : int , size : int , user_data ) -> None :
raw = bytes ( uc . mem_read ( address , size ))
line = disasm_one ( md , raw , address )
x0 = uc . reg_read ( UC_ARM64_REG_X0 )
x1 = uc . reg_read ( UC_ARM64_REG_X1 )
x2 = uc . reg_read ( UC_ARM64_REG_X2 )
print ( f " { line } | X0=0x { x0 : x } X1=0x { x1 : x } X2=0x { x2 : x } " )
# ========= hook:内存读 =========
def hook_mem_read ( uc : Uc , access : int , address : int , size : int , value : int , user_data ) -> None :
print ( f " [mem] READ addr=0x { address : x } , size= { size } " )
# ========= hook:内存写 =========
def hook_mem_write ( uc : Uc , access : int , address : int , size : int , value : int , user_data ) -> None :
print ( f " [mem] WRITE addr=0x { address : x } , size= { size } , value=0x { value : x } " )
mu . hook_add ( UC_HOOK_CODE , hook_code )
mu . hook_add ( UC_HOOK_MEM_READ , hook_mem_read )
mu . hook_add ( UC_HOOK_MEM_WRITE , hook_mem_write )
上面这段代码展示了如何使用 Unicorn 的 hook 接口来 hook 代码执行、内存读写事件,在 hook 函数中我们可以获取当前指令的地址、大小、寄存器值等信息,并且在代码执行到特定地址时停止模拟。