Unicorn、usercornを使ってみる

Unicornとは

この記事での Unicorn は、エミュレータの方であり、決してRackサーバの方ではない。
Unicornは軽量でマルチプラットフォーム、マルチアーキテクチャな CPU emulator frameworkである。

UnicornはNguyen Anh Quynh氏が中心となって開発されているが、彼は disassembly/disassembler frameworkであるCapstone、 assembler frameworkであるKeystoneの開発も行っており、すごい!

また、似たものとしてQEMUが思い浮かぶが、UnicornQEMUをベースに開発されていて、QEMUより優れている点として、frameworkであること、flexibleであること、dynamic instrumententionをサポートしていること、thread-safeであること、いろんな言語によるbindingsがあること、軽量であること、セキュアであることが挙げられている。

Unicorn & QEMU – Unicorn – The ultimate CPU emulator

Unicornのインストール

基本的に/docs/COMPILE.mdの通りにやればうまくいく。

ただ、macにインストールする場合、Homebrewで入るものは古く、GitHub上のmasterは違うものになっているので、手でインストールするほうが無難。特にHomebrewでは入らないbindingをインストールするとき、手抜きで本体だけHomebrewでいれてbindingだけを手でインストールしても動かないので注意。これでハマった...

各言語によるbindingのインストール方法は/bindings/各言語/にあるREADMEを参照。
例えばPythonだと/bindings/python/README.TXTをみればいい。

動かしてみる

Python bindingを使って動かしてみる。

コード

#!/usr/bin/env python2
# coding: UTF-8

from unicorn import *
from unicorn.x86_const import *
import sys

# memory address where emulation starts
ADDRESS = 0x1000000

# initialize unicorn emulator
mu = Uc(UC_ARCH_X86, UC_MODE_32)
# map 4MB for this emulation
mu.mem_map(ADDRESS, 4 * 1024 * 1024)

binary = ''
if len(sys.argv) == 2:
    with open(sys.argv[1], 'rb') as f:
        binary = f.read()
else:
    binary = b"\x41\x4a" # INC ecx; DEC edx


mu.mem_write(ADDRESS, binary)

# emulate code in infinite time & unlimited instructions
mu.emu_start(ADDRESS, ADDRESS + len(binary))

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)
r_ebp = mu.reg_read(UC_X86_REG_EBP)
r_esp = mu.reg_read(UC_X86_REG_ESP)
r_edi = mu.reg_read(UC_X86_REG_EDI)
r_esi = mu.reg_read(UC_X86_REG_ESI)
r_eip = mu.reg_read(UC_X86_REG_EIP)
r_eflags = mu.reg_read(UC_X86_REG_EFLAGS)

print(">>> EAX = 0x%x" % r_eax)
print(">>> EBX = 0x%x" % r_ebx)
print(">>> ECX = 0x%x" % r_ecx)
print(">>> EDX = 0x%x" % r_edx)
print(">>> EBP = 0x%x" % r_ebp)
print(">>> ESP = 0x%x" % r_esp)
print(">>> EDI = 0x%x" % r_edi)
print(">>> ESI = 0x%x" % r_esi)
print(">>> EIP = 0x%x" % r_eip)
print(">>> EFLAGS = 0x%x" % r_eflags)

実行結果

$ python nishizumi.py 
>>> EAX = 0x0
>>> EBX = 0x0
>>> ECX = 0x1
>>> EDX = 0xffffffff
>>> EBP = 0x0
>>> ESP = 0x0
>>> EDI = 0x0
>>> ESI = 0x0
>>> EIP = 0x1000002
>>> EFLAGS = 0x94

INC ecx; DEC edxを実行した。正しくレジスタが加減されているのが分かる。

Unicornとusercorn

Unicornを試したところで、やはりPEやELF等のバイナリを実行したくなる。
しかし、Unicornはframeworkであり、ツールではないので、ヘッダーをパースして、メモリ上に配置したり、スタックを初期化したりといったことまではできない。

unicorn emulate binary · Issue #227 · unicorn-engine/unicorn · GitHub


そこでusercornを使う。usercornは、Unicornを使った User-space system emulatorでバイナリを実行できる。

GitHub - lunixbochs/usercorn: dynamic binary analysis via platform emulation

usercornのインストール

CapstoneとUnicornを入れて、go getする。

go get -u github.com/lunixbochs/usercorn/go/cmd/usercorn

動かしてみる

サンプルファイルを動かしてみる。

$ usercorn bins/x86_64.linux.elf
hello with printf: world
args (1):
  0 @0x607ffa37 = "bins/x86_64.linux.elf"
test: hi
ceil(1.0) = 1, ceil(1.1) = 2
strcmp: -1, 1, 0
file test 1: success

file test 2: success

$ usercorn bins/mipsel.linux.elf
hello with printf: world
args (1):
  0 @0x607ffa3d = "bins/mipsel.linux.elf"
test: hi
memcmp("aa", "aa") == 0
memcmp("aa", "bb") == -1
memcmp("bb", "aa") == 1
strcmp: -1, 1, 0
file test 1: success

file test 2: success

感想

unicorn、usercornを用いてなにか便利ツールを作っていきたい。