TOC

参考链接

https://bbs.pediy.com/thread-224330.htm
http://eternal.red/2018/unicorn-engine-tutorial/#cheatsheet
https://ctf-wiki.github.io/ctf-wiki/reverse/unicorn/introduction-zh/
这里有很多代码示例: https://github.com/unicorn-engine/unicorn/tree/master/bindings/python

介绍

  • Unocorn引擎是什么?

简单的来讲,一款模拟器。尽管不太常见,你不能用来模拟整个程序或者系统,同时它也不支持syscall。你只能通过手动的方式来映射内存以及数据写入,然后就可以从某个指定的地址开始执行模拟了。

  • 模拟器在什么时候是有用的?

  • 你可以执行一些恶意软件中你感兴趣的函数而不必创建整个进程

  • CTF比赛中也很常用

  • Fuzzing

  • GDB插件扩充,例如支持长跳转

  • 模拟混淆后的代码

备忘

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

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) - 开始执行模拟。
命令追踪:

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()函数传递)

第一个例子: fibonacci

#coding=utf-8
from unicorn import *
from unicorn.x86_const import *

import struct

def read(name):
with open(name, 'rb') as f:
    return f.read()
  
def u32(data):
return struct.unpack("I", data)[0]

def p32(num):
return struct.pack("I", num)

# 为x86-64架构初始化一下Unicorn引擎。
mu = Uc (UC_ARCH_X86, UC_MODE_64)
# Uc函数需要一下参数:
# 第一个参数:架构类型。这些常量以UC_ATCH_为前缀
# 第二个参数:架构细节说明。这些常量以UC_MODE_为前缀

BASE = 0x400000
STACK_ADDR = 0x0
STACK_SIZE = 1024*1024
# mem_map: 映射内存
mu.mem_map(BASE, 1024*1024) # 初始化存储空间
mu.mem_map(STACK_ADDR, STACK_SIZE) # 初始化栈空间


mu.mem_write(BASE, read("./fibonacci")) # 加载程序
# rsp指向栈顶
mu.reg_write(UC_X86_REG_RSP, STACK_ADDR + STACK_SIZE - 1)
# 因为库函数没有加载,所以调用库函数的地方需要跳过
instructions_skip_list = [0x00000000004004EF, 0x00000000004004F6, 0x0000000000400502, 0x000000000040054F]

def hook_code(mu, address, size, user_data):  
#print('>>> Tracing instruction at 0x%x, instruction size = 0x%x' %(address, size))

if address in instructions_skip_list:
    mu.reg_write(UC_X86_REG_RIP, address+size)

elif address == 0x400560: #that instruction writes a byte of the flag
    c = mu.reg_read(UC_X86_REG_RDI)
    print(chr(c))
    mu.reg_write(UC_X86_REG_RIP, address+size)

mu.hook_add(UC_HOOK_CODE, hook_code)

mu.emu_start(0x00000000004004E0, 0x0000000000400575)

第二个例子: 分析shellcode

from unicorn import *
from unicorn.x86_const import *
 
shellcode = "\xe8\xff\xff\xff\xff\xc0\x5d\x6a\x05\x5b\x29\xdd\x83\xc5\x4e\x89\xe9\x6a\x02\x03\x0c\x24\x5b\x31\xd2\x66\xba\x12\x00\x8b\x39\xc1\xe7\x10\xc1\xef\x10\x81\xe9\xfe\xff\xff\xff\x8b\x45\x00\xc1\xe0\x10\xc1\xe8\x10\x89\xc3\x09\xfb\x21\xf8\xf7\xd0\x21\xd8\x66\x89\x45\x00\x83\xc5\x02\x4a\x85\xd2\x0f\x85\xcf\xff\xff\xff\xec\x37\x75\x5d\x7a\x05\x28\xed\x24\xed\x24\xed\x0b\x88\x7f\xeb\x50\x98\x38\xf9\x5c\x96\x2b\x96\x70\xfe\xc6\xff\xc6\xff\x9f\x32\x1f\x58\x1e\x00\xd3\x80"
 
 
BASE = 0x400000
STACK_ADDR = 0x0
STACK_SIZE = 1024*1024
 
mu = Uc (UC_ARCH_X86, UC_MODE_32)
 
mu.mem_map(BASE, 1024*1024)
mu.mem_map(STACK_ADDR, STACK_SIZE)
 
 
mu.mem_write(BASE, shellcode)
mu.reg_write(UC_X86_REG_ESP, STACK_ADDR + STACK_SIZE/2)
 
def syscall_num_to_name(num):
syscalls = {1: "sys_exit", 15: "sys_chmod"}
return syscalls[num]
 
def hook_code(mu, address, size, user_data):
#print('>>> Tracing instruction at 0x%x, instruction size = 0x%x' %(address, size)) 
 
machine_code = mu.mem_read(address, size)
if machine_code == "\xcd\x80": # int 80
 
    r_eax = mu.reg_read(UC_X86_REG_EAX)
    r_ebx = mu.reg_read(UC_X86_REG_EBX)
    r_ecx = mu.reg_read(UC_X86_REG_ECX)
    r_edx = mu.reg_read(UC_X86_REG_EDX)
    syscall_name = syscall_num_to_name(r_eax)
 
    print "--------------"
    print "We intercepted system call: "+syscall_name
 
    if syscall_name == "sys_chmod":
        s = mu.mem_read(r_ebx, 20).split("\x00")[0]
        print "arg0 = 0x%x -> %s" % (r_ebx, s)
        print "arg1 = " + oct(r_ecx)
    elif syscall_name == "sys_exit":
        print "arg0 = " + hex(r_ebx)
        exit()
 
    mu.reg_write(UC_X86_REG_EIP, address + size)
 
mu.hook_add(UC_HOOK_CODE, hook_code)
 
mu.emu_start(BASE, BASE-1) # 根据exit命令退出,直接加载整个shellcode

所以不仅可以直接可执行文件中的一段代码,还可以直接执行shellcode

第三个例子

from unicorn import *
from unicorn.x86_const import *
import struct
 
 
def read(name):
with open(name) as f:
    return f.read()
 
def u32(data):
return struct.unpack("I", data)[0]
 
def p32(num):
return struct.pack("I", num)
 
mu = Uc (UC_ARCH_X86, UC_MODE_32)
 
BASE = 0x08048000
STACK_ADDR = 0x0
STACK_SIZE = 1024*1024
 
mu.mem_map(BASE, 1024*1024)
mu.mem_map(STACK_ADDR, STACK_SIZE)
 
 
mu.mem_write(BASE, read("./function"))
r_esp = STACK_ADDR + (STACK_SIZE/2)     #ESP points to this address at function call
 
STRING_ADDR = 0x0
mu.mem_write(STRING_ADDR, "batman\x00") #write "batman" somewhere. We have choosen an address 0x0 which belongs to the stack.
 
mu.reg_write(UC_X86_REG_ESP, r_esp)     #set ESP
mu.mem_write(r_esp+4, p32(5))           #set the first argument. It is integer 5
mu.mem_write(r_esp+8, p32(STRING_ADDR)) #set the second argument. This is a pointer to the string "batman"
 
 
mu.emu_start(0x8048464, 0x804849A)      #start emulation from the beginning of super_function, end at RET instruction
return_value = mu.reg_read(UC_X86_REG_EAX)
print "The returned value is: %d" % return_value

可以通过修改内存来控制函数的调用参数

第四个例子

from unicorn import *
from unicorn.arm_const import *
 
 
import struct
 
def read(name):
with open(name) as f:
    return f.read()
 
def u32(data):
return struct.unpack("I", data)[0]
 
def p32(num):
return struct.pack("I", num)
 
 
mu = Uc (UC_ARCH_ARM, UC_MODE_LITTLE_ENDIAN)
 
 
BASE = 0x10000
STACK_ADDR = 0x300000
STACK_SIZE = 1024*1024
 
mu.mem_map(BASE, 1024*1024)
mu.mem_map(STACK_ADDR, STACK_SIZE)
 
 
mu.mem_write(BASE, read("./task4"))
mu.reg_write(UC_ARM_REG_SP, STACK_ADDR + STACK_SIZE/2)
 
instructions_skip_list = []
 
CCC_ENTRY = 0x000104D0
CCC_END = 0x00010580
 
stack = []                                          # Stack for storing the arguments
d = {}                                              # Dictionary that holds return values for given function arguments
 
def hook_code(mu, address, size, user_data): 
#print('>>> Tracing instruction at 0x%x, instruction size = 0x%x' %(address, size))
 
if address == CCC_ENTRY:                        # Are we at the beginning of ccc function?
    arg0 = mu.reg_read(UC_ARM_REG_R0)           # Read the first argument. it is passed by R0
 
    if arg0 in d:                               # Check whether return value for this function is already saved.
        ret = d[arg0]
        mu.reg_write(UC_ARM_REG_R0, ret)        # Set return value in R0
        mu.reg_write(UC_ARM_REG_PC, 0x105BC)    # Set PC to point at "BX LR" instruction. We want to return from fibonacci function
 
    else:
        stack.append(arg0)                      # If return value is not saved for this argument, add it to stack.
 
elif address == CCC_END:
    arg0 = stack.pop()                          # We know arguments when exiting the function
 
    ret = mu.reg_read(UC_ARM_REG_R0)            # Read the return value (R0)
    d[arg0] = ret                               # Remember the return value for this argument
 
mu.hook_add(UC_HOOK_CODE, hook_code)
 
mu.emu_start(0x00010584, 0x000105A8)
 
return_value = mu.reg_read(UC_ARM_REG_R1)           # We end the emulation at printf("%d\n", ccc(x)).
print "The return value is %d" % return_value

arm版本