Skip to content

Commit

Permalink
Added SVC 0 support, implemented few syscalls and VFP support.
Browse files Browse the repository at this point in the history
  • Loading branch information
AeonLucid committed Jan 28, 2019
1 parent 174b03c commit 141d881
Show file tree
Hide file tree
Showing 21 changed files with 429 additions and 39 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ This is an educational project to learn more about the ELF file format and [Unic

- Emulation of the [JNI Invocation API](https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/invocation.html) so `JNI_OnLoad` can be called properly.
- Emulation of native memory for malloc / memcpy.
- Emulation of syscalls (SVC #0) instruction.
- Hooking through the symbol table.
- All JavaVM, JNIEnv and hooked functions are handled by python.
- Enable VFP support.

> The first two are still being worked on, please contribute if you can! :)
Expand All @@ -30,3 +32,4 @@ All resources used while developing AndroidNativeEmu.

### Code sources
- https://github.com/lunixbochs/usercorn
- https://github.com/slick1015/pad_unpacker (SVC 0 instruction)
2 changes: 1 addition & 1 deletion androidemu/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
MEMORY_SIZE = 0x0200000 # 2 * 1024 * 1024 - 2MB

MEMORY_DYN_BASE = 0x2000000
MEMORY_DYN_SIZE = 0x0200000 # 2 * 1024 * 1024 - 2MB
MEMORY_DYN_SIZE = 0x0200000 # 2 * 1024 * 1024 - 2MB

BASE_ADDR = 0xCBBCB000
Empty file added androidemu/const/__init__.py
Empty file.
1 change: 1 addition & 0 deletions androidemu/const/android.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
PR_SET_VMA = 0x53564d41
10 changes: 10 additions & 0 deletions androidemu/const/linux.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
CLOCK_REALTIME = 0
CLOCK_MONOTONIC = 1
CLOCK_PROCESS_CPUTIME_ID = 2
CLOCK_THREAD_CPUTIME_ID = 3
CLOCK_MONOTONIC_RAW = 4
CLOCK_REALTIME_COARSE = 5
CLOCK_MONOTONIC_COARSE = 6
CLOCK_BOOTTIME = 7
CLOCK_REALTIME_ALARM = 8
CLOCK_BOOTTIME_ALARM = 9
Empty file added androidemu/cpu/__init__.py
Empty file.
27 changes: 27 additions & 0 deletions androidemu/cpu/interrupt_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import logging

from unicorn import *
from unicorn.arm_const import *

logger = logging.getLogger(__name__)


class InterruptHandler:

"""
:type mu Uc
"""
def __init__(self, mu):
self._mu = mu
self._mu.hook_add(UC_HOOK_INTR, self._hook_interrupt)
self._handlers = dict()

def _hook_interrupt(self, uc, intno, data):
if intno in self._handlers:
self._handlers[intno](uc)
else:
logger.error("Unhandled interrupt %d at %x, stopping emulation" % (intno, self._mu.reg_read(UC_ARM_REG_PC)))
self._mu.emu_stop()

def set_handler(self, intno, handler):
self._handlers[intno] = handler
7 changes: 7 additions & 0 deletions androidemu/cpu/syscall_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class SyscallHandler:

def __init__(self, idx, name, arg_count, callback):
self.idx = idx
self.name = name
self.arg_count = arg_count
self.callback = callback
46 changes: 46 additions & 0 deletions androidemu/cpu/syscall_handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import logging

from unicorn import *
from unicorn.arm_const import *

from androidemu.cpu.interrupt_handler import InterruptHandler
from androidemu.cpu.syscall_handler import SyscallHandler
from androidemu.utils import memory_helpers

logger = logging.getLogger(__name__)


class SyscallHandlers:

"""
:type interrupt_handler InterruptHandler
"""
def __init__(self, interrupt_handler):
self._handlers = dict()
interrupt_handler.set_handler(2, self._handle_syscall)

def set_handler(self, idx, name, arg_count, callback):
self._handlers[idx] = SyscallHandler(idx, name, arg_count, callback)

def _handle_syscall(self, mu):
idx = mu.reg_read(UC_ARM_REG_R7)
args = [mu.reg_read(reg_idx) for reg_idx in range(UC_ARM_REG_R0, UC_ARM_REG_R6 + 1)]

if idx in self._handlers:
handler = self._handlers[idx]
args = args[:handler.arg_count]
args_formatted = ", ".join(["%08x" % arg for arg in args])
logger.info("Executing syscall %s(%s)" % (handler.name, args_formatted))

try:
result = handler.callback(mu, *args)
except:
logger.error("An error occured during in %x syscall hander, stopping emulation" % idx)
mu.emu_stop()
raise

if result is not None:
mu.reg_write(UC_ARM_REG_R0, result)
else:
logger.error("Unhandled syscall 0x%x at 0x%x, stopping emulation" % (idx, mu.reg_read(UC_ARM_REG_PC)))
mu.emu_stop()
54 changes: 54 additions & 0 deletions androidemu/cpu/syscall_hooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from unicorn import Uc

from androidemu.const.android import PR_SET_VMA
from androidemu.const.linux import CLOCK_MONOTONIC_COARSE
from androidemu.cpu.syscall_handlers import SyscallHandlers
from androidemu.utils import memory_helpers


class SyscallHooks:

"""
:type mu Uc
:type syscall_handler SyscallHandlers
"""
def __init__(self, mu, syscall_handler):
self._mu = mu
self._syscall_handler = syscall_handler
self._syscall_handler.set_handler(0xAC, "prctl", 5, self._handle_prctl)
self._syscall_handler.set_handler(0x107, "clock_gettime", 2, self._handle_clock_gettime)

def _handle_prctl(self, mu, option, arg2, arg3, arg4, arg5):
"""
int prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5);
See:
- https://linux.die.net/man/2/prctl
- https://github.com/torvalds/linux/blob/master/include/uapi/linux/prctl.h
For PR_SET_VMA:
- https://android.googlesource.com/platform/bionic/+/263325d/libc/include/sys/prctl.h
- https://sourceforge.net/p/strace/mailman/message/34329772/
"""

if option == PR_SET_VMA:
# arg5 contains ptr to a name.
return 0
else:
raise NotImplementedError("Unsupported prctl option %d (0x%x)" % (option, option))

def _handle_clock_gettime(self, mu, clk_id, tp_ptr):
"""
The functions clock_gettime() retrieve the time of the specified clock clk_id.
The clk_id argument is the identifier of the particular clock on which to act. A clock may be system-wide and
hence visible for all processes, or per-process if it measures time only within a single process.
clock_gettime(), clock_settime() and clock_getres() return 0 for success, or -1 for failure (in which case
errno is set appropriately).
"""

if clk_id == CLOCK_MONOTONIC_COARSE:
# TODO: Actually write time.
return 0
else:
raise NotImplementedError("Unsupported clk_id: %d (%x)" % (clk_id, clk_id))
54 changes: 52 additions & 2 deletions androidemu/emulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@
from unicorn.arm_const import UC_ARM_REG_SP

from androidemu import config
from androidemu.cpu.interrupt_handler import InterruptHandler
from androidemu.cpu.syscall_handlers import SyscallHandlers
from androidemu.cpu.syscall_hooks import SyscallHooks
from androidemu.hooker import Hooker
from androidemu.internal.memory import Memory
from androidemu.internal.modules import Modules
from androidemu.java.java_vm import JavaVM
from androidemu.native.hooks import NativeHooks
from androidemu.native.memory import NativeMemory
from androidemu.vfs.file_system import VirtualFileSystem


class Emulator:
Expand All @@ -17,10 +21,13 @@ class Emulator:
:type modules Modules
:type memory Memory
"""
def __init__(self):
def __init__(self, vfs_root=None, vfp_inst_set=False):
# Unicorn.
self.mu = Uc(UC_ARCH_ARM, UC_MODE_ARM)

if vfp_inst_set:
self._enable_vfp()

# Stack.
self.mu.mem_map(config.STACK_ADDR, config.STACK_SIZE)
self.mu.reg_write(UC_ARM_REG_SP, config.STACK_ADDR + config.STACK_SIZE)
Expand All @@ -29,6 +36,17 @@ def __init__(self):
self.modules = Modules(self)
self.memory = Memory(self)

# CPU
self.interrupt_handler = InterruptHandler(self.mu)
self.syscall_handler = SyscallHandlers(self.interrupt_handler)
self.syscall_hooks = SyscallHooks(self.mu, self.syscall_handler)

# File System
if vfs_root is not None:
self.vfs = VirtualFileSystem(vfs_root, self.syscall_handler)
else:
self.vfs = None

# Hooker
self.mu.mem_map(config.MEMORY_BASE, config.MEMORY_SIZE)
self.hooker = Hooker(self.mu, config.MEMORY_BASE, config.MEMORY_SIZE)
Expand All @@ -37,8 +55,40 @@ def __init__(self):
self.java_vm = JavaVM(self.hooker)

# Native
self.native_memory = NativeMemory(config.MEMORY_BASE, config.MEMORY_SIZE)
self.native_memory = NativeMemory(self.mu, config.MEMORY_DYN_BASE, config.MEMORY_DYN_SIZE, self.syscall_handler)
self.native_hooks = NativeHooks(self.native_memory, self.modules, self.hooker)

# https://github.com/unicorn-engine/unicorn/blob/8c6cbe3f3cabed57b23b721c29f937dd5baafc90/tests/regress/arm_fp_vfp_disabled.py#L15
def _enable_vfp(self):
# MRC p15, #0, r1, c1, c0, #2
# ORR r1, r1, #(0xf << 20)
# MCR p15, #0, r1, c1, c0, #2
# MOV r1, #0
# MCR p15, #0, r1, c7, c5, #4
# MOV r0,#0x40000000
# FMXR FPEXC, r0
code = '11EE501F'
code += '41F47001'
code += '01EE501F'
code += '4FF00001'
code += '07EE951F'
code += '4FF08040'
code += 'E8EE100A'
# vpush {d8}
code += '2ded028b'

address = 0x1000
mem_size = 0x1000
code_bytes = bytes.fromhex(code)

try:
self.mu.mem_map(address, mem_size)
self.mu.mem_write(address, code_bytes)
self.mu.reg_write(UC_ARM_REG_SP, address + mem_size)

self.mu.emu_start(address | 1, address + len(code_bytes))
finally:
self.mu.mem_unmap(address, mem_size)

def load_library(self, filename):
return self.modules.load_module(filename)
2 changes: 2 additions & 0 deletions androidemu/internal/memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,5 @@ def mem_map(self, address, size, prot):

def mem_write(self, address, data):
self.emu.mu.mem_write(address, data)


Loading

0 comments on commit 141d881

Please sign in to comment.