0%

多架构多平台CPU仿真器-Unicorn实用手册

本文就是写写Unicorn咋用的
不涉及原理层面

地址

官方网站
https://www.unicorn-engine.org/
Github
https://github.com/unicorn-engine/unicorn

介绍

Unicorn是一个轻量级的多平台多架构CPU仿真器

高亮特点:

  • 多架构支持: Arm, Arm64 (Armv8), M68K, Mips, Sparc, & X86 (包括X86_64).
  • 干净/简单/轻量级/直观的API,同时不依赖任何架构。
  • 以C/C++实现,可在众多语言里使用:Pharo, Crystal, Clojure, Visual Basic, Perl, Rust, Haskell, Ruby, Python, Java, Go, .NET, Delphi/Pascal & MSVC。
  • 原生支持Windows和*nix系统(以下系统已确认支持:Mac OSX, Linux, *BSD & Solaris)。
  • 通过使用即时编译器技术以实现高性能。
  • 支持各种级别的细粒度检测。
  • 线程安全的设计。
  • 允许在开源软件许可 GPLv2 下分发。

使用

Input

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
from unicorn import *

# 要执行的字节码
# 可以使用Keystone引擎生成
binary = b'1\xc0Ph//shh/bin\x89\xe3PS\x89\xe1\xb0\x0b'

# 规定内存地址
ADDRESS = 0x400000

print("开始模拟-X86")

# 初始化仿真器,模式为X86-32
mu = Uc(UC_ARCH_X86, UC_MODE_32)

# 给仿真器映射2MB内存
mu.mem_map(ADDRESS, 2 * 1024 * 1024)

# 将指令写到内存
mu.mem_write(ADDRESS, binary)

# 初始化仿真器的寄存器,并赋值
# ESP给了内存的最后一个位置
mu.reg_write(x86_const.UC_X86_REG_EAX, 0x1000)
mu.reg_write(x86_const.UC_X86_REG_ESP, ADDRESS + 2 * 1024 * 1024 - 1)


# 这里创建一个钩子,每执行一条指令会先执行一次这里,如果有不想执行的语句可以跳过
# 跳过方法:EIP+size
def hook(emu: Uc, addr, size, userdata):
# 反汇编当前语句的语义,详情请看Capstone篇
from capstone import Cs, CS_ARCH_X86, CS_MODE_32
CS = Cs(CS_ARCH_X86, CS_MODE_32)
instruction = list(CS.disasm(emu.mem_read(addr, size), 0))[0]
print(f"当前位置:{hex(addr)},指令长度:{size},指令:{instruction.mnemonic} {instruction.op_str}")
userdata.Count += 1


# 这里可以传给钩子一个变量(注意不能是初始类型,否则就只传值而不是引用了),执行结束的时候可以读取到钩子里处理的数据
class InstCount:
Count = 0


count = InstCount()
# 添加钩子,钩子类型为CODE,别的暂时还不知道
mu.hook_add(UC_HOOK_CODE, hook, count)

# 开始仿真
mu.emu_start(ADDRESS, ADDRESS + len(binary))

# 仿真结束
print("仿真结束")
print(f"共执行{count.Count}条指令")

# 读寄存器
r_eax = mu.reg_read(x86_const.UC_X86_REG_EAX)
r_ebx = mu.reg_read(x86_const.UC_X86_REG_EBX)
r_ecx = mu.reg_read(x86_const.UC_X86_REG_ECX)
r_esp = mu.reg_read(x86_const.UC_X86_REG_ESP)
print(">>> EAX = 0x%x" % r_eax)
print(">>> EBX = 0x%x" % r_ebx)
print(">>> ECX = 0x%x" % r_ecx)
print(">>> ESP = 0x%x" % r_esp)

# 读栈内容
now = r_esp
while now != ADDRESS + 2 * 1024 * 1024 - 1:
buf = mu.mem_read(now, 4)
print(f"Stack > ESP - {hex(now)} - {buf}")
now += 4

Output

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
开始模拟-X86
当前位置:0x400000,指令长度:2,指令:xor eax, eax
当前位置:0x400002,指令长度:1,指令:push eax
当前位置:0x400003,指令长度:5,指令:push 0x68732f2f
当前位置:0x400008,指令长度:5,指令:push 0x6e69622f
当前位置:0x40000d,指令长度:2,指令:mov ebx, esp
当前位置:0x40000f,指令长度:1,指令:push eax
当前位置:0x400010,指令长度:1,指令:push ebx
当前位置:0x400011,指令长度:2,指令:mov ecx, esp
当前位置:0x400013,指令长度:2,指令:mov al, 0xb
仿真结束
共执行9条指令
>>> EAX = 0xb
>>> EBX = 0x5ffff3
>>> ECX = 0x5fffeb
>>> ESP = 0x5fffeb
Stack > ESP - 0x5fffeb - bytearray(b'\xf3\xff_\x00')
Stack > ESP - 0x5fffef - bytearray(b'\x00\x00\x00\x00')
Stack > ESP - 0x5ffff3 - bytearray(b'/bin')
Stack > ESP - 0x5ffff7 - bytearray(b'//sh')
Stack > ESP - 0x5ffffb - bytearray(b'\x00\x00\x00\x00')