diff --git a/androidemu/config.py b/androidemu/config.py index 0425b29..4efa070 100644 --- a/androidemu/config.py +++ b/androidemu/config.py @@ -9,4 +9,4 @@ BASE_ADDR = 0xCBBCB000 -WRITE_FSTAT_TIMES = False +WRITE_FSTAT_TIMES = True diff --git a/androidemu/cpu/syscall_handlers.py b/androidemu/cpu/syscall_handlers.py index f9258a2..5c646ab 100644 --- a/androidemu/cpu/syscall_handlers.py +++ b/androidemu/cpu/syscall_handlers.py @@ -42,5 +42,6 @@ def _handle_syscall(self, mu): 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))) + logger.error("Unhandled syscall 0x%x (%u) at 0x%x, stopping emulation" % (idx, idx, + mu.reg_read(UC_ARM_REG_PC))) mu.emu_stop() diff --git a/androidemu/cpu/syscall_hooks.py b/androidemu/cpu/syscall_hooks.py index b6dbcf0..eb23241 100644 --- a/androidemu/cpu/syscall_hooks.py +++ b/androidemu/cpu/syscall_hooks.py @@ -1,3 +1,7 @@ +import math +import time +from random import randint + from unicorn import Uc from androidemu.const.android import * @@ -15,9 +19,35 @@ class SyscallHooks: def __init__(self, mu, syscall_handler): self._mu = mu self._syscall_handler = syscall_handler + self._syscall_handler.set_handler(0x4E, "gettimeofday", 2, self._handle_gettimeofday) + self._syscall_handler.set_handler(0x5B, "fchmod", 2, self._handle_fchmod) self._syscall_handler.set_handler(0xAC, "prctl", 5, self._handle_prctl) self._syscall_handler.set_handler(0xF0, "futex", 6, self._handle_futex) self._syscall_handler.set_handler(0x107, "clock_gettime", 2, self._handle_clock_gettime) + self._clock_start = time.time() + self._clock_offset = randint(1000, 2000) + + def _handle_gettimeofday(self, uc, tv, tz): + """ + If either tv or tz is NULL, the corresponding structure is not set or returned. + """ + + if tv != 0: + timestamp = time.time() + (usec, sec) = math.modf(timestamp) + usec = abs(int(usec * 100000)) + + uc.mem_write(tv + 0, int(sec).to_bytes(4, byteorder='little')) + uc.mem_write(tv + 4, int(usec).to_bytes(4, byteorder='little')) + + if tz != 0: + uc.mem_write(tz + 0, int(-120).to_bytes(4, byteorder='little')) # minuteswest -(+GMT_HOURS) * 60 + uc.mem_write(tz + 4, int().to_bytes(4, byteorder='little')) # dsttime + + return 0 + + def _handle_fchmod(self, uc, fd, mode): + raise NotImplementedError() def _handle_prctl(self, mu, option, arg2, arg3, arg4, arg5): """ @@ -68,7 +98,11 @@ def _handle_clock_gettime(self, mu, clk_id, tp_ptr): """ if clk_id == CLOCK_MONOTONIC_COARSE: - # TODO: Actually write time. + clock_add = time.time() - self._clock_start # Seconds passed since clock_start was set. + + mu.mem_write(tp_ptr + 0, int(self._clock_start + clock_add).to_bytes(4, byteorder='little')) + mu.mem_write(tp_ptr + 4, int(0).to_bytes(4, byteorder='little')) + return 0 else: raise NotImplementedError("Unsupported clk_id: %d (%x)" % (clk_id, clk_id)) diff --git a/androidemu/emulator.py b/androidemu/emulator.py index 7849cbb..c4bced7 100644 --- a/androidemu/emulator.py +++ b/androidemu/emulator.py @@ -61,7 +61,7 @@ def __init__(self, vfs_root=None, vfp_inst_set=False): # JavaVM self.java_classloader = JavaClassLoader() - self.java_vm = JavaVM(self.java_classloader, self.hooker) + self.java_vm = JavaVM(self, self.java_classloader, self.hooker) # Native self.native_memory = NativeMemory(self.mu, config.MEMORY_DYN_BASE, config.MEMORY_DYN_SIZE, self.syscall_handler) @@ -122,7 +122,7 @@ def call_native(self, addr, *argv): try: # Execute native call. - native_write_args(self.mu, *argv) + native_write_args(self, *argv) stop_pos = randint(MEMORY_BASE, MEMORY_BASE + MEMORY_SIZE) | 1 self.mu.reg_write(UC_ARM_REG_LR, stop_pos) self.mu.emu_start(addr, stop_pos - 1) diff --git a/androidemu/java/helpers/native_method.py b/androidemu/java/helpers/native_method.py index 9987ae1..68092a0 100644 --- a/androidemu/java/helpers/native_method.py +++ b/androidemu/java/helpers/native_method.py @@ -5,36 +5,56 @@ from androidemu.java.java_class_def import JavaClassDef from androidemu.java.jni_const import JNI_ERR -from androidemu.java.jni_ref import jobject +from androidemu.java.jni_ref import jobject, jstring, jobjectArray, jbyteArray -def native_write_args(mu, *argv): +def native_write_args(emu, *argv): amount = len(argv) if amount == 0: return if amount >= 1: - native_write_arg_register(mu, UC_ARM_REG_R0, argv[0]) + native_write_arg_register(emu, UC_ARM_REG_R0, argv[0]) if amount >= 2: - native_write_arg_register(mu, UC_ARM_REG_R1, argv[1]) + native_write_arg_register(emu, UC_ARM_REG_R1, argv[1]) if amount >= 3: - native_write_arg_register(mu, UC_ARM_REG_R2, argv[2]) + native_write_arg_register(emu, UC_ARM_REG_R2, argv[2]) if amount >= 4: - native_write_arg_register(mu, UC_ARM_REG_R3, argv[3]) + native_write_arg_register(emu, UC_ARM_REG_R3, argv[3]) if amount >= 5: - raise NotImplementedError("We don't support more than 4 args yet, write to the stack.") + # TODO: I have no idea why this dark magic is required but it works (for me).. + sp_start = emu.mu.reg_read(UC_ARM_REG_SP) + sp_current = sp_start - 8 + for arg in argv[4:]: + emu.mu.mem_write(sp_current - 8, native_translate_arg(emu, arg).to_bytes(4, byteorder='little')) + sp_current = sp_current - 4 -def native_write_arg_register(mu, reg, val): + emu.mu.reg_write(UC_ARM_REG_SP, sp_current) + + +def native_translate_arg(emu, val): if isinstance(val, int): - mu.reg_write(reg, val) + return val + elif isinstance(val, str): + return emu.java_vm.jni_env.add_local_reference(jstring(val)) + elif isinstance(val, list): + return emu.java_vm.jni_env.add_local_reference(jobjectArray(val)) + elif isinstance(val, bytearray): + return emu.java_vm.jni_env.add_local_reference(jbyteArray(val)) + elif isinstance(type(val), JavaClassDef): + return emu.java_vm.jni_env.add_local_reference(jobject(val)) else: - raise ValueError('Unsupported val type.') + raise NotImplementedError("Unable to write response '%s' type '%s' to emulator." % (str(val), type(val))) + + +def native_write_arg_register(emu, reg, val): + emu.mu.reg_write(reg, native_translate_arg(emu, val)) def native_method(func): @@ -69,7 +89,19 @@ def native_method_wrapper(*argv): native_args.append(mu.reg_read(UC_ARM_REG_R3)) if args_count >= 5: - raise NotImplementedError("We don't support more than 4 args yet, read from the stack.") + native_args.append(mu.reg_read(UC_ARM_REG_R4)) + + if args_count >= 6: + native_args.append(mu.reg_read(UC_ARM_REG_R5)) + + if args_count >= 7: + native_args.append(mu.reg_read(UC_ARM_REG_R6)) + + if args_count >= 8: + native_args.append(mu.reg_read(UC_ARM_REG_R7)) + + if args_count >= 9: + raise NotImplementedError("We don't support more than 8 args yet, read from the stack.") if len(argv) == 1: result = func(mu, *native_args) @@ -77,14 +109,7 @@ def native_method_wrapper(*argv): result = func(argv[0], mu, *native_args) if result is not None: - if isinstance(result, int): - mu.reg_write(UC_ARM_REG_R0, result) - elif isinstance(type(result), JavaClassDef): - # This is a Java class object, so we should probably respond with a local jobject reference. - ref = emu.java_vm.jni_env.add_local_reference(jobject(result)) - mu.reg_write(UC_ARM_REG_R0, ref) - else: - raise NotImplementedError("Unable to write response '%s' to emulator." % str(result)) + native_write_arg_register(emu, UC_ARM_REG_R0, result) else: mu.reg_write(UC_ARM_REG_R0, JNI_ERR) diff --git a/androidemu/java/java_field_def.py b/androidemu/java/java_field_def.py index 4cced09..75acce2 100644 --- a/androidemu/java/java_field_def.py +++ b/androidemu/java/java_field_def.py @@ -1,7 +1,11 @@ class JavaFieldDef: - def __init__(self, name, signature, is_static): + def __init__(self, name, signature, is_static, static_value=None): self.jvm_id = None # Assigned by JavaClassDef. self.name = name self.signature = signature self.is_static = is_static + self.static_value = static_value + + if self.is_static and self.static_value is None: + raise ValueError('Static value may not be None for a static field.') diff --git a/androidemu/java/java_method_def.py b/androidemu/java/java_method_def.py index 2b623b1..14471d0 100644 --- a/androidemu/java/java_method_def.py +++ b/androidemu/java/java_method_def.py @@ -1,6 +1,6 @@ class JavaMethodDef: - def __init__(self, func_name, func, name, signature, native): + def __init__(self, func_name, func, name, signature, native, args_list=None): self.jvm_id = None # Assigned by JavaClassDef. self.func_name = func_name self.func = func @@ -8,9 +8,10 @@ def __init__(self, func_name, func, name, signature, native): self.signature = signature self.native = native self.native_addr = None + self.args_list = args_list -def java_method_def(name, signature, native=False): +def java_method_def(name, signature, native=False, args_list=None): def java_method_def_real(func): def native_wrapper(self, emulator, *argv): return emulator.call_native( @@ -26,7 +27,7 @@ def normal_wrapper(*args, **kwargs): return result wrapper = native_wrapper if native else normal_wrapper - wrapper.jvm_method = JavaMethodDef(func.__name__, wrapper, name, signature, native) + wrapper.jvm_method = JavaMethodDef(func.__name__, wrapper, name, signature, native, args_list=args_list) return wrapper return java_method_def_real diff --git a/androidemu/java/java_vm.py b/androidemu/java/java_vm.py index 936ad90..1a790d4 100644 --- a/androidemu/java/java_vm.py +++ b/androidemu/java/java_vm.py @@ -17,7 +17,7 @@ class JavaVM: :type class_loader JavaClassLoader :type hooker Hooker """ - def __init__(self, class_loader, hooker): + def __init__(self, emu, class_loader, hooker): (self.address_ptr, self.address) = hooker.write_function_table({ 3: self.destroy_java_vm, 4: self.attach_current_thread, @@ -26,7 +26,7 @@ def __init__(self, class_loader, hooker): 7: self.attach_current_thread }) - self.jni_env = JNIEnv(class_loader, hooker) + self.jni_env = JNIEnv(emu, class_loader, hooker) @native_method def destroy_java_vm(self, mu): diff --git a/androidemu/java/jni_env.py b/androidemu/java/jni_env.py index 0f80798..62df870 100644 --- a/androidemu/java/jni_env.py +++ b/androidemu/java/jni_env.py @@ -18,7 +18,8 @@ class JNIEnv: :type hooker Hooker """ - def __init__(self, class_loader, hooker): + def __init__(self, emu, class_loader, hooker): + self._emu = emu self._class_loader = class_loader self._locals = ReferenceTable(start=1, max_entries=2048) self._globals = ReferenceTable(start=4096, max_entries=512000) @@ -297,6 +298,26 @@ def delete_global_reference(self, obj): return self._globals.remove(obj) + def read_args_v(self, mu, args_ptr, args_list): + if args_list is None: + return [] + + result = [] + + for arg_name in args_list: + if arg_name == 'jstring': + ref = int.from_bytes(mu.mem_read(args_ptr, 4), byteorder='little') + result.append(self.get_reference(ref)) + args_ptr = args_ptr + 4 + elif arg_name == 'jobject': + ref = int.from_bytes(mu.mem_read(args_ptr, 4), byteorder='little') + result.append(self.get_reference(ref)) + args_ptr = args_ptr + 4 + else: + raise NotImplementedError('Unknown arg name %s' % arg_name) + + return result + @native_method def get_version(self, mu, env): raise NotImplementedError() @@ -366,7 +387,13 @@ def exception_describe(self, mu, env): @native_method def exception_clear(self, mu, env): - raise NotImplementedError() + """ + Clears any exception that is currently being thrown. + If no exception is currently being thrown, this routine has no effect. + """ + logger.debug("JNIEnv->ExceptionCheck() was called") + # TODO: Implement + return None @native_method def fatal_error(self, mu, env): @@ -441,8 +468,29 @@ def new_object(self, mu, env): raise NotImplementedError() @native_method - def new_object_v(self, mu, env): - raise NotImplementedError() + def new_object_v(self, mu, env, clazz_idx, method_id, args): + logger.debug("JNIEnv->NewObjectV(%u, %u, 0x%x) was called" % (clazz_idx, method_id, args)) + + # Get class reference. + clazz = self.get_reference(clazz_idx) + if not isinstance(clazz, jclass): + raise ValueError('Expected a jclass.') + + # Create class instance. + obj = clazz.value() + + # Get constructor method. + method = obj.__class__.find_method_by_id(method_id) + if method.name != '' or not method.signature.endswith('V'): + raise ValueError('Class constructor has the wrong name or does not return void.') + + # Parse arguments. + constructor_args = self.read_args_v(mu, args, method.args_list) + + # Execute function. + method.func(obj, self._emu, *constructor_args) + + return self.add_local_reference(jobject(obj)) @native_method def new_object_a(self, mu, env): @@ -502,7 +550,7 @@ def call_object_method_v(self, mu, env, obj_idx, method_id, args): # TODO: Args. - return method.func(obj.value) + return method.func(obj.value, self._emu) @native_method def call_object_method_a(self, mu, env): @@ -609,8 +657,26 @@ def call_void_method(self, mu, env): raise NotImplementedError() @native_method - def call_void_method_v(self, mu, env): - raise NotImplementedError() + def call_void_method_v(self, mu, env, obj_idx, method_id, args): + obj = self.get_reference(obj_idx) + + if not isinstance(obj, jobject): + raise ValueError('Expected a jobject.') + + method = obj.value.__class__.find_method_by_id(method_id) + + if method is None: + # TODO: Proper Java error? + raise RuntimeError("Could not find method %d in object %s by id." % (method_id, obj.value.jvm_name)) + + logger.debug("JNIEnv->CallVoidMethodV(%s, %s <%s>, 0x%x) was called" % ( + obj.value.jvm_name, + method.name, + method.signature, args)) + + method.func(obj.value, self._emu) + + return None @native_method def call_void_method_a(self, mu, env): @@ -758,9 +824,23 @@ def get_field_id(self, mu, env, clazz_idx, name_ptr, sig_ptr): return field.jvm_id @native_method - def get_object_field(self, mu, env, obj, field_id): - logger.debug("JNIEnv->GetObjectField(%d, %d) was called" % (obj, field_id)) - raise NotImplementedError() + def get_object_field(self, mu, env, obj_idx, field_id): + obj = self.get_reference(obj_idx) + + if not isinstance(obj, jobject): + raise ValueError('Expected a jobject.') + + field = obj.value.__class__.find_field_by_id(field_id) + + if field is None: + # TODO: Proper Java error? + raise RuntimeError("Could not find field %d in object %s by id." % (field_id, obj.value.jvm_name)) + + logger.debug("JNIEnv->GetObjectField(%s, %s <%s>) was called" % (obj.value.jvm_name, + field.name, + field.signature)) + + return getattr(obj.value, field.name) @native_method def get_boolean_field(self, mu, env): @@ -795,7 +875,7 @@ def get_int_field(self, mu, env, obj_idx, field_id): field.name, field.signature)) - raise NotImplementedError() + return getattr(obj.value, field.name) @native_method def get_long_field(self, mu, env): @@ -890,9 +970,10 @@ def call_static_object_method_v(self, mu, env, clazz_idx, method_id, args): method.name, method.signature, args)) - # TODO: Args. + # Parse arguments. + constructor_args = self.read_args_v(mu, args, method.args_list) - return method.func() + return method.func(self._emu, *constructor_args) @native_method def call_static_object_method_a(self, mu, env): @@ -1029,9 +1110,13 @@ def get_static_field_id(self, mu, env, clazz_idx, name_ptr, sig_ptr): return field.jvm_id @native_method - def get_static_object_field(self, mu, env, clazz, field_id): - logger.debug("JNIEnv->GetStaticObjectField(%d, %d) was called" % (clazz, field_id)) - raise NotImplementedError() + def get_static_object_field(self, mu, env, clazz_idx, field_id): + logger.debug("JNIEnv->GetStaticObjectField(%d, %d) was called" % (clazz_idx, field_id)) + + clazz = self.get_reference(clazz_idx) + field = clazz.value.find_field_by_id(field_id) + + return field.static_value @native_method def get_static_boolean_field(self, mu, env): @@ -1050,9 +1135,13 @@ def get_static_short_field(self, mu, env): raise NotImplementedError() @native_method - def get_static_int_field(self, mu, env, clazz, field_id): - logger.debug("JNIEnv->GetStaticIntField(%d, %d) was called" % (clazz, field_id)) - raise NotImplementedError() + def get_static_int_field(self, mu, env, clazz_idx, field_id): + logger.debug("JNIEnv->GetStaticIntField(%d, %d) was called" % (clazz_idx, field_id)) + + clazz = self.get_reference(clazz_idx) + field = clazz.value.find_field_by_id(field_id) + + return field.static_value @native_method def get_static_long_field(self, mu, env): @@ -1131,7 +1220,20 @@ def get_string_utf_length(self, mu, env): @native_method def get_string_utf_chars(self, mu, env, string, is_copy_ptr): - raise NotImplementedError() + logger.debug("JNIEnv->GetStringUtfChars(%u, %x) was called" % (string, is_copy_ptr)) + + if is_copy_ptr != 0: + raise NotImplementedError() + + str_ref = self.get_reference(string) + str_val = str_ref.value + str_ptr = self._emu.native_memory.allocate(len(str_val) + 1) + + logger.debug("=> %s" % str_val) + + memory_helpers.write_utf8(mu, str_ptr, str_val) + + return str_ptr @native_method def release_string_utf_chars(self, mu, env, string, utf_ptr): @@ -1139,15 +1241,29 @@ def release_string_utf_chars(self, mu, env, string, utf_ptr): @native_method def get_array_length(self, mu, env, array): - raise NotImplementedError() + logger.debug("JNIEnv->GetArrayLength(%u) was called" % array) + + obj = self.get_reference(array) + + if not isinstance(obj, jarray): + raise ValueError('Expected a jarray.') + + return len(obj.value) @native_method def new_object_array(self, mu, env): raise NotImplementedError() @native_method - def get_object_array_element(self, mu, env): - raise NotImplementedError() + def get_object_array_element(self, mu, env, array_idx, item_idx): + logger.debug("JNIEnv->GetObjectArrayElement(%u, %u) was called" % (array_idx, item_idx)) + + obj = self.get_reference(array_idx) + + if not isinstance(obj, jarray): + raise ValueError('Expected a jarray.') + + return obj.value[item_idx] @native_method def set_object_array_element(self, mu, env): @@ -1254,8 +1370,17 @@ def get_boolean_array_region(self, mu, env): raise NotImplementedError() @native_method - def get_byte_array_region(self, mu, env): - raise NotImplementedError() + def get_byte_array_region(self, mu, env, array_idx, start, len_in, buf_ptr): + logger.debug("JNIEnv->GetArrayLength(%u, %u, %u, 0x%x) was called" % (array_idx, start, len_in, buf_ptr)) + + obj = self.get_reference(array_idx) + + if not isinstance(obj, jbyteArray): + raise ValueError('Expected a jbyteArray.') + + mu.mem_write(buf_ptr, bytes(obj.value[start:start + len_in])) + + return None @native_method def get_char_array_region(self, mu, env): diff --git a/androidemu/native/memory.py b/androidemu/native/memory.py index b50785a..7c4c5f9 100644 --- a/androidemu/native/memory.py +++ b/androidemu/native/memory.py @@ -1,5 +1,6 @@ -from unicorn import Uc +from unicorn import Uc, UC_PROT_READ, UC_PROT_WRITE from androidemu.cpu.syscall_handlers import SyscallHandlers +from androidemu.native.memory_heap import UnicornSimpleHeap class NativeMemory: @@ -10,30 +11,30 @@ class NativeMemory: """ def __init__(self, mu, memory_base, memory_size, syscall_handler): self._mu = mu + self._heap = UnicornSimpleHeap(mu, memory_base, memory_base + memory_size) self._memory_base = memory_base self._memory_current = memory_base self._memory_size = memory_size self._syscall_handler = syscall_handler - self._syscall_handler.set_handler(0x7D, "mprotect", 6, self._handle_mmap2) + self._syscall_handler.set_handler(0x7D, "mprotect", 3, self._handle_mprotect) self._syscall_handler.set_handler(0xC0, "mmap2", 6, self._handle_mmap2) self._syscall_handler.set_handler(0xDC, "madvise", 3, self._handle_madvise) - def allocate(self, length, prot=7): - alloc_base = self._memory_current - - if alloc_base + length > self._memory_base + self._memory_size: - raise OverflowError("Our native memory is overflowing..") - - self._mu.mem_map(alloc_base, length, perms=prot) - self._memory_current += length - - return alloc_base + def allocate(self, length, prot=UC_PROT_READ | UC_PROT_WRITE): + return self._heap.malloc(length, prot) def _handle_mmap2(self, mu, addr, length, prot, flags, fd, offset): """ void *mmap2(void *addr, size_t length, int prot, int flags, int fd, off_t pgoffset); """ - return self.allocate(length, prot=prot) + + # MAP_FILE 0 + # MAP_SHARED 0x01 + # MAP_PRIVATE 0x02 + # MAP_FIXED 0x10 + # MAP_ANONYMOUS 0x20 + + return self._heap.malloc(length, prot) def _handle_madvise(self, mu, start, len_in, behavior): """ @@ -51,8 +52,5 @@ def _handle_mprotect(self, mu, addr, len_in, prot): mprotect() changes protection for the calling process's memory page(s) containing any part of the address range in the interval [addr, addr+len-1]. addr must be aligned to a page boundary. """ - if addr < self._memory_base and addr + len_in > self._memory_base + self._memory_size: - raise RuntimeError("Tried to protect memory not in the native range..") - - self._mu.mem_protect(addr, len_in, perms=prot) + self._heap.protect(addr, len_in, prot) return 0 diff --git a/androidemu/native/memory_heap.py b/androidemu/native/memory_heap.py index e69de29..bcb3c9b 100644 --- a/androidemu/native/memory_heap.py +++ b/androidemu/native/memory_heap.py @@ -0,0 +1,102 @@ +from unicorn import * +from unicorn.arm64_const import * + +# Page size required by Unicorn +UNICORN_PAGE_SIZE = 0x1000 + +# Max allowable segment size (1G) +MAX_ALLOWABLE_SEG_SIZE = 1024 * 1024 * 1024 + +# Alignment functions to align all memory segments to Unicorn page boundaries (4KB pages only) +ALIGN_PAGE_DOWN = lambda x: x & ~(UNICORN_PAGE_SIZE - 1) +ALIGN_PAGE_UP = lambda x: (x + UNICORN_PAGE_SIZE - 1) & ~(UNICORN_PAGE_SIZE-1) + + +# Implementation from +# https://github.com/Battelle/afl-unicorn/blob/44a50c8a9426ffe4ad8714ef8a35dc011e62f739/unicorn_mode/helper_scripts/unicorn_loader.py#L45 +class UnicornSimpleHeap: + """ Use this class to provide a simple heap implementation. This should + be used if malloc/free calls break things during emulation. + """ + + # Helper data-container used to track chunks + class HeapChunk(object): + def __init__(self, data_addr, data_size): + self.data_addr = data_addr + self.data_size = data_size + + # Returns true if the specified buffer is completely within the chunk, else false + def is_buffer_in_chunk(self, addr, size): + if addr >= self.data_addr and ((addr + size) <= (self.data_addr + self.data_size)): + return True + else: + return False + + _uc = None # Unicorn engine instance to interact with + _chunks = [] # List of all known chunks + _debug_print = False # True to print debug information + + def __init__(self, uc, heap_min_addr, heap_max_addr, debug_print=False): + self._uc = uc + self._heap_min_addr = heap_min_addr + self._heap_max_addr = heap_max_addr + self._debug_print = debug_print + + # Add the watchpoint hook that will be used to implement psuedo-guard page support + # self._uc.hook_add(UC_HOOK_MEM_WRITE | UC_HOOK_MEM_READ, self.__check_mem_access) + + def malloc(self, size, prot=UC_PROT_READ | UC_PROT_WRITE): + # Figure out the overall size to be allocated/mapped + # - Allocate at least 1 4k page of memory to make Unicorn happy + data_size = ALIGN_PAGE_UP(size) + # Gross but efficient way to find space for the chunk: + chunk = None + for addr in range(self._heap_min_addr, self._heap_max_addr, UNICORN_PAGE_SIZE): + try: + self._uc.mem_map(addr, data_size, prot) + chunk = self.HeapChunk(addr, data_size) + if self._debug_print: + print("Allocating 0x{0:x}-byte chunk @ 0x{1:016x}".format(chunk.data_size, chunk.data_addr)) + break + except UcError as e: + continue + # Something went very wrong + if chunk is None: + raise Exception("Oh no.") + self._chunks.append(chunk) + return chunk.data_addr + + def calloc(self, size, count): + # Simple wrapper around malloc with calloc() args + return self.malloc(size * count) + + def realloc(self, ptr, new_size): + # Wrapper around malloc(new_size) / memcpy(new, old, old_size) / free(old) + if self._debug_print: + print("Reallocating chunk @ 0x{0:016x} to be 0x{1:x} bytes".format(ptr, new_size)) + old_chunk = None + for chunk in self._chunks: + if chunk.data_addr == ptr: + old_chunk = chunk + new_chunk_addr = self.malloc(new_size) + if old_chunk is not None: + self._uc.mem_write(new_chunk_addr, str(self._uc.mem_read(old_chunk.data_addr, old_chunk.data_size))) + self.free(old_chunk.data_addr) + return new_chunk_addr + + def protect(self, addr, len_in, prot): + for chunk in self._chunks: + if chunk.is_buffer_in_chunk(addr, len_in): + self._uc.mem_protect(chunk.data_addr, chunk.data_size, perms=prot) + return True + return False + + def free(self, addr): + for chunk in self._chunks: + if chunk.is_buffer_in_chunk(addr, 1): + if self._debug_print: + print("Freeing 0x{0:x}-byte chunk @ 0x{0:016x}".format(chunk.data_addr, chunk.data_size)) + self._uc.mem_unmap(chunk.data_addr, chunk.data_size) + self._chunks.remove(chunk) + return True + return False diff --git a/androidemu/utils/memory_helpers.py b/androidemu/utils/memory_helpers.py index 04a5a1c..d417acd 100644 --- a/androidemu/utils/memory_helpers.py +++ b/androidemu/utils/memory_helpers.py @@ -25,3 +25,7 @@ def read_utf8(mu, address): buffer_address += buffer_read_size return buffer[:null_pos].decode("utf-8") + + +def write_utf8(mu, address, value): + mu.mem_write(address, value.encode(encoding="utf-8") + b"\x00") diff --git a/androidemu/vfs/file_helpers.py b/androidemu/vfs/file_helpers.py index e69de29..1f4e25c 100644 --- a/androidemu/vfs/file_helpers.py +++ b/androidemu/vfs/file_helpers.py @@ -0,0 +1,70 @@ +import json +import os +from os import stat_result + +from unicorn import Uc + +from androidemu.config import WRITE_FSTAT_TIMES + + +def stat64(path): + meta_path = path + '.meta_emu' + + if not os.path.exists(meta_path): + with open(meta_path, 'w') as f: + json.dump({ + 'st_dev': 0, + '__st_ino': 0, + 'st_mode': 0, + 'st_nlink': 0, + 'st_uid': 0, + 'st_gid': 0, + 'st_rdev': 0, + 'st_size': 0, + 'st_blksize': 0, + 'st_blocks': 0, + 'st_atime': 0, + 'st_atime_ns': 0, + 'st_mtime': 0, + 'st_mtime_ns': 0, + 'st_ctime': 0, + 'st_ctime_ns': 0, + 'st_ino': 0 + }, fp=f, indent=4) + + with open(meta_path, 'r') as f: + return json.load(fp=f) + + +def stat_to_memory(uc: Uc, buf_ptr, stat, write_times): + uc.mem_write(buf_ptr, stat['st_dev'].to_bytes(8, byteorder='little')) + uc.mem_write(buf_ptr + 8, int(0).to_bytes(4, byteorder='little')) # PAD 4 + uc.mem_write(buf_ptr + 12, stat['__st_ino'].to_bytes(4, byteorder='little')) + uc.mem_write(buf_ptr + 16, stat['st_mode'].to_bytes(4, byteorder='little')) + uc.mem_write(buf_ptr + 20, stat['st_nlink'].to_bytes(4, byteorder='little')) + uc.mem_write(buf_ptr + 24, stat['st_uid'].to_bytes(4, byteorder='little')) + uc.mem_write(buf_ptr + 28, stat['st_gid'].to_bytes(4, byteorder='little')) + uc.mem_write(buf_ptr + 32, stat['st_rdev'].to_bytes(8, byteorder='little')) + uc.mem_write(buf_ptr + 40, int(0).to_bytes(4, byteorder='little')) # PAD 4 + uc.mem_write(buf_ptr + 44, int(0).to_bytes(4, byteorder='little')) # PAD 4 + uc.mem_write(buf_ptr + 48, stat['st_size'].to_bytes(8, byteorder='little')) + uc.mem_write(buf_ptr + 56, stat['st_blksize'].to_bytes(4, byteorder='little')) + uc.mem_write(buf_ptr + 60, int(0).to_bytes(4, byteorder='little')) # PAD 4 + uc.mem_write(buf_ptr + 64, stat['st_blocks'].to_bytes(8, byteorder='little')) + + if write_times: + uc.mem_write(buf_ptr + 72, stat['st_atime'].to_bytes(4, byteorder='little')) + uc.mem_write(buf_ptr + 76, stat['st_atime_ns'].to_bytes(4, byteorder='little')) + uc.mem_write(buf_ptr + 80, stat['st_mtime'].to_bytes(4, byteorder='little')) + uc.mem_write(buf_ptr + 84, stat['st_mtime_ns'].to_bytes(4, byteorder='little')) + uc.mem_write(buf_ptr + 88, stat['st_ctime'].to_bytes(4, byteorder='little')) + uc.mem_write(buf_ptr + 92, stat['st_ctime_ns'].to_bytes(4, byteorder='little')) + else: + uc.mem_write(buf_ptr + 72, int(0).to_bytes(4, byteorder='little')) + uc.mem_write(buf_ptr + 76, int(0).to_bytes(4, byteorder='little')) + uc.mem_write(buf_ptr + 80, int(0).to_bytes(4, byteorder='little')) + uc.mem_write(buf_ptr + 84, int(0).to_bytes(4, byteorder='little')) + uc.mem_write(buf_ptr + 88, int(0).to_bytes(4, byteorder='little')) + uc.mem_write(buf_ptr + 92, int(0).to_bytes(4, byteorder='little')) + + uc.mem_write(buf_ptr + 96, stat['st_ino'].to_bytes(8, byteorder='little')) diff --git a/androidemu/vfs/file_system.py b/androidemu/vfs/file_system.py index 3504a0e..dd09eea 100644 --- a/androidemu/vfs/file_system.py +++ b/androidemu/vfs/file_system.py @@ -6,14 +6,16 @@ from androidemu.config import WRITE_FSTAT_TIMES from androidemu.cpu.syscall_handlers import SyscallHandlers from androidemu.utils import memory_helpers +from androidemu.vfs import file_helpers logger = logging.getLogger(__name__) class VirtualFile: - def __init__(self, name, file_descriptor): + def __init__(self, name, file_descriptor, name_virt=None): self.name = name + self.name_virt = name_virt self.descriptor = file_descriptor @@ -38,11 +40,24 @@ def __init__(self, root_path, syscall_handler): syscall_handler.set_handler(0x92, "writev", 3, self._handle_writev) syscall_handler.set_handler(0xC5, "fstat64", 2, self._handle_fstat64) syscall_handler.set_handler(0x142, "openat", 4, self._handle_openat) + syscall_handler.set_handler(0x147, "fstatat64", 4, self._handle_fstatat64) - def _store_fd(self, name, file_descriptor): + def translate_path(self, filename): + if filename.startswith("/"): + filename = filename[1:] + + file_path = posixpath.join(self._root_path, filename) + file_path = posixpath.normpath(file_path) + + if posixpath.commonpath([file_path, self._root_path]) != self._root_path: + raise RuntimeError("Emulated binary tried to escape vfs jail.") + + return file_path + + def _store_fd(self, name, name_virt, file_descriptor): next_fd = self._file_descriptor_counter self._file_descriptor_counter += 1 - self._file_descriptors[next_fd] = VirtualFile(name, file_descriptor) + self._file_descriptors[next_fd] = VirtualFile(name, file_descriptor, name_virt=name_virt) return next_fd def _open_file(self, filename): @@ -51,23 +66,16 @@ def _open_file(self, filename): if filename == '/dev/urandom': logger.info("File opened '%s'" % filename) - return self._store_fd('/dev/urandom', 'urandom') + return self._store_fd('/dev/urandom', None, 'urandom') - if filename.startswith("/"): - filename = filename[1:] - - file_path = posixpath.join(self._root_path, filename) - file_path = posixpath.normpath(file_path) - - if posixpath.commonpath([file_path, self._root_path]) != self._root_path: - raise RuntimeError("Emulated binary tried to escape vfs jail.") + file_path = self.translate_path(filename) if os.path.isfile(file_path): logger.info("File opened '%s'" % orig_filename) flags = os.O_RDWR if hasattr(os, "O_BINARY"): flags |= os.O_BINARY - return self._store_fd(orig_filename, os.open(file_path, flags=flags)) + return self._store_fd(orig_filename, file_path, os.open(file_path, flags=flags)) else: logger.info("File does not exist %s" % file_path) return -1 @@ -163,38 +171,9 @@ def _handle_fstat64(self, mu, fd, buf_ptr): file = self._file_descriptors[fd] logger.info("File stat64 '%s'" % file.name) - stat = os.fstat(file.descriptor) - - mu.mem_write(buf_ptr, stat.st_dev.to_bytes(8, byteorder='little')) - # PAD 4 - mu.mem_write(buf_ptr + 12, stat.st_ino.to_bytes(8, byteorder='little')) - mu.mem_write(buf_ptr + 20, stat.st_mode.to_bytes(4, byteorder='little')) - mu.mem_write(buf_ptr + 24, stat.st_nlink.to_bytes(4, byteorder='little')) - mu.mem_write(buf_ptr + 28, stat.st_uid.to_bytes(8, byteorder='little')) - mu.mem_write(buf_ptr + 36, stat.st_gid.to_bytes(8, byteorder='little')) - mu.mem_write(buf_ptr + 44, int(0).to_bytes(8, byteorder='little')) # st_rdev - # PAD 4 - mu.mem_write(buf_ptr + 56, stat.st_size.to_bytes(8, byteorder='little')) - mu.mem_write(buf_ptr + 64, int(0).to_bytes(8, byteorder='little')) # st_blksize - mu.mem_write(buf_ptr + 72, int(0).to_bytes(8, byteorder='little')) # st_blocks - - if WRITE_FSTAT_TIMES: - mu.mem_write(buf_ptr + 80, int(stat.st_atime).to_bytes(4, byteorder='little')) - mu.mem_write(buf_ptr + 84, int(stat.st_atime_ns).to_bytes(4, byteorder='little')) - mu.mem_write(buf_ptr + 88, int(stat.st_mtime).to_bytes(4, byteorder='little')) - mu.mem_write(buf_ptr + 92, int(stat.st_mtime_ns).to_bytes(4, byteorder='little')) - mu.mem_write(buf_ptr + 96, int(stat.st_ctime).to_bytes(4, byteorder='little')) - mu.mem_write(buf_ptr + 100, int(stat.st_ctime_ns).to_bytes(4, byteorder='little')) - else: - mu.mem_write(buf_ptr + 80, int(0).to_bytes(4, byteorder='little')) - mu.mem_write(buf_ptr + 84, int(0).to_bytes(4, byteorder='little')) - mu.mem_write(buf_ptr + 88, int(0).to_bytes(4, byteorder='little')) - mu.mem_write(buf_ptr + 92, int(0).to_bytes(4, byteorder='little')) - mu.mem_write(buf_ptr + 96, int(0).to_bytes(4, byteorder='little')) - mu.mem_write(buf_ptr + 100, int(0).to_bytes(4, byteorder='little')) - - # New.. - # mu.mem_write(buf_ptr + 104, stat.st_ino.to_bytes(8, byteorder='little')) + stat = file_helpers.stat64(file.name_virt) + # stat = os.fstat(file.descriptor) + file_helpers.stat_to_memory(mu, buf_ptr, stat, WRITE_FSTAT_TIMES) return 0 @@ -216,3 +195,36 @@ def _handle_openat(self, mu, dfd, filename_ptr, flags, mode): raise NotImplementedError("Directory file descriptor has not been implemented yet.") return self._open_file(filename) + + def _handle_fstatat64(self, mu, dirfd, pathname_ptr, buf, flags): + """ + int fstatat(int dirfd, const char *pathname, struct stat *buf, int flags); + + If the pathname given in pathname is relative, then it is interpreted relative to the directory referred + to by the file descriptor dirfd (rather than relative to the current working directory of the calling process, + as is done by stat(2) for a relative pathname). + + If pathname is relative and dirfd is the special value AT_FDCWD, + then pathname is interpreted relative to the current working directory of the calling process (like stat(2)). + + If pathname is absolute, then dirfd is ignored. + + flags can either be 0, or include one or more of the following flags .. + + On success, fstatat() returns 0. On error, -1 is returned and errno is set to indicate the error. + """ + pathname = memory_helpers.read_utf8(mu, pathname_ptr) + + if not pathname.startswith('/'): + raise NotImplementedError("Directory file descriptor has not been implemented yet.") + + if not flags == 0: + raise NotImplementedError("Flags has not been implemented yet.") + + pathname = self.translate_path(pathname) + + stat = file_helpers.stat64(path=pathname) + # stat = os.stat(path=file_path, dir_fd=None, follow_symlinks=False) + file_helpers.stat_to_memory(mu, buf, stat, WRITE_FSTAT_TIMES) + + return 0 diff --git a/samples/debug_utils.py b/samples/debug_utils.py index c20acfb..23bfd74 100644 --- a/samples/debug_utils.py +++ b/samples/debug_utils.py @@ -32,7 +32,8 @@ def hook_mem_write(uc, access, address, size, value, user_data): def hook_mem_read(uc, access, address, size, value, user_data): pc = uc.reg_read(UC_ARM_REG_PC) - logger.debug(">>> Memory READ at 0x%x, data size = %u, pc: %x" % (address, size, pc)) + data = uc.mem_read(address, size) + logger.debug(">>> Memory READ at 0x%x, data size = %u, pc: %x, data value = 0x%s" % (address, size, pc, data.hex())) def hook_interrupt(uc, intno, data): diff --git a/samples/vfs/sys/devices/system/cpu/online.meta_emu b/samples/vfs/sys/devices/system/cpu/online.meta_emu new file mode 100644 index 0000000..e2c4312 --- /dev/null +++ b/samples/vfs/sys/devices/system/cpu/online.meta_emu @@ -0,0 +1,19 @@ +{ + "st_dev": 0, + "__st_ino": 0, + "st_mode": 0, + "st_nlink": 0, + "st_uid": 0, + "st_gid": 0, + "st_rdev": 0, + "st_size": 0, + "st_blksize": 0, + "st_blocks": 0, + "st_atime": 0, + "st_atime_ns": 0, + "st_mtime": 0, + "st_mtime_ns": 0, + "st_ctime": 0, + "st_ctime_ns": 0, + "st_ino": 0 +} \ No newline at end of file