Skip to content

Commit

Permalink
Added symbol hooking for malloc, memcpy & friends.
Browse files Browse the repository at this point in the history
Also moved some stuff inside the Emulator class because that is what this entire project is about.
  • Loading branch information
AeonLucid committed Jan 28, 2019
1 parent 6e28648 commit d59cf7c
Show file tree
Hide file tree
Showing 14 changed files with 154 additions and 44 deletions.
3 changes: 3 additions & 0 deletions androidemu/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@
MEMORY_BASE = 0x1000000
MEMORY_SIZE = 0x0200000 # 2 * 1024 * 1024 - 2MB

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

BASE_ADDR = 0xCBBCB000
21 changes: 18 additions & 3 deletions androidemu/emulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@
from unicorn.arm_const import UC_ARM_REG_SP

from androidemu import config
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


class Emulator:
Expand All @@ -14,16 +18,27 @@ class Emulator:
:type memory Memory
"""
def __init__(self):
# Initialize unicorn.
# Unicorn.
self.mu = Uc(UC_ARCH_ARM, UC_MODE_ARM)

# Initialize stack.
# Stack.
self.mu.mem_map(config.STACK_ADDR, config.STACK_SIZE)
self.mu.mem_map(config.MEMORY_BASE, config.MEMORY_SIZE)
self.mu.reg_write(UC_ARM_REG_SP, config.STACK_ADDR + config.STACK_SIZE)

# Executable data.
self.modules = Modules(self)
self.memory = Memory(self)

# Hooker
self.mu.mem_map(config.MEMORY_BASE, config.MEMORY_SIZE)
self.hooker = Hooker(self.mu, config.MEMORY_BASE, config.MEMORY_SIZE)

# JavaVM
self.java_vm = JavaVM(self.hooker)

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

def load_library(self, filename):
return self.modules.load_module(filename)
13 changes: 9 additions & 4 deletions androidemu/hooker.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ class Hooker:
"""
:type mu Uc
"""
def __init__(self, mu, base_addr):
def __init__(self, mu, base_addr, size):
self._mu = mu
self._keystone = Ks(KS_ARCH_ARM, KS_MODE_THUMB)
self._base_addr = base_addr
self._size = size
self._current_id = 0xFF00
self._current_addr = self._base_addr
self._hooks = dict()
self._mu.hook_add(UC_HOOK_CODE, self._hook, None, self._base_addr, self._base_addr + size)

def _get_next_id(self):
idx = self._current_id
Expand Down Expand Up @@ -87,7 +89,10 @@ def _hook(self, mu, address, size, user_data):
hook_func = self._hooks[hook_id]

# Call hook.
hook_func(mu)

def enable(self):
self._mu.hook_add(UC_HOOK_CODE, self._hook, None, self._base_addr, self._current_addr)
try:
hook_func(mu)
except:
# Make sure we catch exceptions inside hooks and stop emulation.
mu.emu_stop()
raise
5 changes: 1 addition & 4 deletions androidemu/internal/memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,19 @@


class Memory:

"""
:type emu androidemu.emulator.Emulator
"""

def __init__(self, emu):
self.emu = emu
self.counter_memory = config.BASE_ADDR
self.counter_stack = config.STACK_ADDR + config.STACK_SIZE

def mem_reserve(self, size):
(_, size_aligned) = align(0, size, True)

ret = self.counter_memory

self.counter_memory += size_aligned

return ret

def mem_map(self, address, size, prot):
Expand Down
22 changes: 15 additions & 7 deletions androidemu/internal/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ class Modules:
def __init__(self, emu):
self.emu = emu
self.modules = list()
self.symbol_hooks = dict()

def add_symbol_hook(self, symbol_name, addr):
self.symbol_hooks[symbol_name] = addr

def load_module(self, filename):
logger.debug("Loading module '%s'." % filename)
Expand Down Expand Up @@ -86,15 +90,19 @@ def load_module(self, filename):
# Write the new value
self.emu.mu.mem_write(rel_addr, value.to_bytes(4, byteorder='little'))
elif rel_info_type == arm.R_ARM_GLOB_DAT or rel_info_type == arm.R_ARM_JUMP_SLOT:
# Resolve the symbol.
(sym_base, resolved_symbol) = self._resolv_symbol(load_base, dynsym, sym)
# Check if we have a hook for this symbol.
if sym.name in self.symbol_hooks:
value = self.symbol_hooks[sym.name]
else:
# Resolve the symbol.
(sym_base, resolved_symbol) = self._resolv_symbol(load_base, dynsym, sym)

if resolved_symbol is None:
logger.debug("=> Unable to resolve symbol: %s" % sym.name)
continue
if resolved_symbol is None:
logger.debug("=> Unable to resolve symbol: %s" % sym.name)
continue

# Create the new value.
value = sym_base + resolved_symbol['st_value']
# Create the new value.
value = sym_base + resolved_symbol['st_value']

# Write the new value
self.emu.mu.mem_write(rel_addr, value.to_bytes(4, byteorder='little'))
Expand Down
18 changes: 11 additions & 7 deletions androidemu/java/helpers/native_method.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@
from unicorn import Uc
from unicorn.arm_const import *

from androidemu.java.jni_const import JNI_ERR


def native_method(func):
def native_method_wrapper(self, mu):
def native_method_wrapper(*argv):
"""
:type self
:type mu Uc
"""

mu = argv[1] if len(argv) == 2 else argv[0]

args = inspect.getfullargspec(func).args
args_count = len(args) - (2 if 'self' in args else 1)

Expand All @@ -35,17 +39,17 @@ def native_method_wrapper(self, mu):
if args_count >= 5:
raise NotImplementedError("We don't support more than 4 args yet, read from the stack.")

try:
result = func(self, mu, *native_args)
except:
# Make sure we catch exceptions inside hooks and stop emulation.
mu.emu_stop()
raise
if len(argv) == 1:
result = func(mu, *native_args)
else:
result = func(argv[0], mu, *native_args)

if result is not None:
if isinstance(result, int):
mu.reg_write(UC_ARM_REG_R0, result)
else:
raise NotImplementedError("Unable to write response '%s' to emulator." % str(result))
else:
mu.reg_write(UC_ARM_REG_R0, JNI_ERR)

return native_method_wrapper
28 changes: 24 additions & 4 deletions androidemu/java/jni_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from androidemu.hooker import Hooker
from androidemu.java.helpers.native_method import native_method
from androidemu.java.jni_const import *
from androidemu.utils import memory_helpers

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -255,8 +256,15 @@ def define_class(self, mu, env):
raise NotImplementedError()

@native_method
def find_class(self, mu, env):
raise NotImplementedError()
def find_class(self, mu, env, name_ptr):
"""
Returns a class object from a fully-qualified name, or NULL if the class cannot be found.
"""

# TODO: Actually retrieve a class id from a class map.
name = memory_helpers.read_utf8(mu, name_ptr)
logger.debug(name)
return 0xFF

@native_method
def from_reflected_method(self, mu, env):
Expand Down Expand Up @@ -1091,8 +1099,20 @@ def set_double_array_region(self, mu, env):
raise NotImplementedError()

@native_method
def register_natives(self, mu, env):
raise NotImplementedError()
def register_natives(self, mu, env, clazz, methods, methods_count):
for i in range(0, methods_count):
ptr_name = memory_helpers.read_ptr(mu, (i * 12) + methods)
ptr_sign = memory_helpers.read_ptr(mu, (i * 12) + methods + 4)
ptr_func = memory_helpers.read_ptr(mu, (i * 12) + methods + 8)

name = memory_helpers.read_utf8(mu, ptr_name)
signature = memory_helpers.read_utf8(mu, ptr_sign)

print(name, signature, "%x" % ptr_func)

# TODO: Store these so we can call them.

return JNI_OK

@native_method
def unregister_natives(self, mu, env):
Expand Down
Empty file added androidemu/native/__init__.py
Empty file.
34 changes: 34 additions & 0 deletions androidemu/native/hooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import logging

from androidemu.hooker import Hooker
from androidemu.native.memory import NativeMemory

from androidemu.java.helpers.native_method import native_method

logger = logging.getLogger(__name__)


class NativeHooks:

"""
:type memory NativeMemory
:type modules Modules
:type hooker Hooker
"""
def __init__(self, memory, modules, hooker):
self._memory = memory

modules.add_symbol_hook('malloc', hooker.write_function(self.malloc) + 1)
modules.add_symbol_hook('memcpy', hooker.write_function(self.memcpy) + 1)

@native_method
def malloc(self, mu, size):
# TODO: Actually reserve memory with checks.
logger.warning("Application requested %d bytes." % size)
return 0x10

@native_method
def memcpy(self, mu, dst, src, count):
# TODO: Actually copy memory with checks.
logger.warning("Application copies %d bytes from 0x%x to 0x%x." % (count, src, dst))
return 0x10
5 changes: 5 additions & 0 deletions androidemu/native/memory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class NativeMemory:

def __init__(self, memory_base, memory_size):
self._memory_base = memory_base
self._memory_size = memory_size
Empty file added androidemu/utils/__init__.py
Empty file.
27 changes: 27 additions & 0 deletions androidemu/utils/memory_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import hexdump


def hex_dump(mu, address, size):
data = mu.mem_read(address, size)
return hexdump.hexdump(data)


def read_ptr(mu, address):
return int.from_bytes(mu.mem_read(address, 4), byteorder='little')


def read_utf8(mu, address):
buffer_address = address
buffer_read_size = 32
buffer = b""
null_pos = None

# Keep reading until we read something that contains a null terminator.
while null_pos is None:
buf_read = mu.mem_read(buffer_address, buffer_read_size)
if b'\x00' in buf_read:
null_pos = len(buffer) + buf_read.index(b'\x00')
buffer += buf_read
buffer_address += buffer_read_size

return buffer[:null_pos].decode("utf-8")
19 changes: 5 additions & 14 deletions example_jni.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
import logging
import sys

from unicorn import *
from unicorn.arm_const import *

import debug_utils
from androidemu import config
from androidemu.emulator import Emulator
from androidemu.hooker import Hooker
from androidemu.java.java_vm import JavaVM

# Configure logging
logging.basicConfig(
Expand All @@ -21,6 +17,8 @@

# Initialize emulator
emulator = Emulator()

# Load all libraries.
emulator.load_library("example_binaries/libdl.so")
emulator.load_library("example_binaries/libc.so")
emulator.load_library("example_binaries/libstdc++.so")
Expand All @@ -33,24 +31,17 @@
for module in emulator.modules:
logger.info("=> 0x%08x - %s" % (module.base_addr, module.filename))

# Initialize hooker.
hooker = Hooker(emulator.mu, config.MEMORY_BASE)

# Initialize fake JavaVM.
java_vm = JavaVM(hooker)

# Enable hooker to catch calls (call after writing all your hooks).
hooker.enable()

# Debug
# emulator.mu.hook_add(UC_HOOK_CODE, debug_utils.hook_code)
# emulator.mu.hook_add(UC_HOOK_MEM_UNMAPPED, debug_utils.hook_unmapped)
# emulator.mu.hook_add(UC_HOOK_MEM_WRITE, debug_utils.hook_mem_write)
# emulator.mu.hook_add(UC_HOOK_MEM_READ, debug_utils.hook_mem_read)

# Prepare registers for JNI_OnLoad.
emulator.mu.reg_write(UC_ARM_REG_R0, java_vm.address_ptr) # JavaVM* vm
emulator.mu.reg_write(UC_ARM_REG_R0, emulator.java_vm.address_ptr) # JavaVM* vm
emulator.mu.reg_write(UC_ARM_REG_R1, 0x00) # void* reserved

# Run JNI_OnLoad.
emulator.mu.emu_start(base_address + 0x7DEC + 1, base_address + 0x7EEA)

logger.info("Exited EMU.")
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
unicorn==1.0.1
pyelftools==0.24
pyelftools==0.24
hexdump==3.3

0 comments on commit d59cf7c

Please sign in to comment.