def angr_load_mapped_pages( self, uc: Uc, state: angr.SimState ) -> List[Tuple[int, int, int]]: """ Loads all currently mapped unicorn mem regions into angr :param uc: The uc instance to load from :param state: the angr instance to load to :returns Lst of pages mapped """ mapped = [] for begin, end, perms in uc.mem_regions(): mapped += (begin, end - begin + 1, perms) angr_store_mem(state, begin, bytes(uc.mem_read(begin, end - begin + 1))) return mapped
class Emulator: """ :type mu Uc :type modules Modules :type memory Memory """ 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() # Android self.system_properties = {"libc.debug.malloc.options": ""} # 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) # Executable data. 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.HOOK_MEMORY_BASE, config.HOOK_MEMORY_SIZE) self.hooker = Hooker(self, config.HOOK_MEMORY_BASE, config.HOOK_MEMORY_SIZE) # JavaVM self.java_classloader = JavaClassLoader() self.java_vm = JavaVM(self, self.java_classloader, self.hooker) # Native self.native_memory = NativeMemory(self.mu, config.HEAP_BASE, config.HEAP_SIZE, self.syscall_handler) self.native_hooks = NativeHooks(self, self.native_memory, self.modules, self.hooker) # Tracer self.tracer = Tracer(self.mu, self.modules) # 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 _call_init_array(self): pass def load_library(self, filename, do_init=True): libmod = self.modules.load_module(filename) if do_init: logger.debug("Calling Init for: %s " % filename) for fun_ptr in libmod.init_array: logger.debug("Calling Init function: %x " % fun_ptr) self.call_native(fun_ptr) return libmod def call_symbol(self, module, symbol_name, *argv): symbol = module.find_symbol(symbol_name) if symbol is None: logger.error('Unable to find symbol \'%s\' in module \'%s\'.' % (symbol_name, module.filename)) return self.call_native(symbol.address, *argv) def call_native(self, addr, *argv): # Detect JNI call is_jni = False if len(argv) >= 1: is_jni = argv[0] == self.java_vm.address_ptr or argv[ 0] == self.java_vm.jni_env.address_ptr # TODO: Write JNI args to local ref table if jni. try: # Execute native call. native_write_args(self, *argv) stop_pos = randint(HOOK_MEMORY_BASE, HOOK_MEMORY_BASE + HOOK_MEMORY_SIZE) | 1 self.mu.reg_write(UC_ARM_REG_LR, stop_pos) self.mu.emu_start(addr, stop_pos - 1) # Read result from locals if jni. if is_jni: result_idx = self.mu.reg_read(UC_ARM_REG_R0) result = self.java_vm.jni_env.get_local_reference(result_idx) if result is None: return result return result.value finally: # Clear locals if jni. if is_jni: self.java_vm.jni_env.clear_locals() def dump(self, out_dir): os.makedirs(out_dir) for begin, end, prot in [reg for reg in self.mu.mem_regions()]: filename = "{:#010x}-{:#010x}.bin".format(begin, end) pathname = os.path.join(out_dir, filename) with open(pathname, "w") as f: f.write( hexdump.hexdump(self.mu.mem_read(begin, end - begin), result='return'))
class Emulator: """ :type mu Uc :type modules Modules """ def __init__(self, vfs_root: str = None, vfp_inst_set: bool = False): # Unicorn. self.mu = Uc(UC_ARCH_ARM, UC_MODE_ARM) if vfp_inst_set: self._enable_vfp() # Android self.system_properties = {"libc.debug.malloc.options": ""} # Stack. self.mu.mem_map(STACK_ADDR, STACK_SIZE) self.mu.reg_write(UC_ARM_REG_SP, STACK_ADDR + STACK_SIZE) # Executable data. self.modules = Modules(self) self.memory_manager = MemoryManager(self.mu) # CPU self.interrupt_handler = InterruptHandler(self.mu) self.syscall_handler = SyscallHandlers(self.interrupt_handler) self.syscall_hooks = SyscallHooks(self.mu, self.syscall_handler, self.modules) self.syscall_hooks_memory = SyscallHooksMemory(self.mu, self.memory_manager, 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(HOOK_MEMORY_BASE, HOOK_MEMORY_SIZE) self.hooker = Hooker(self, HOOK_MEMORY_BASE, HOOK_MEMORY_SIZE) # JavaVM self.java_classloader = JavaClassLoader() self.java_vm = JavaVM(self, self.java_classloader, self.hooker) # Native self.native_hooks = NativeHooks(self, self.memory_manager, self.modules, self.hooker) # Tracer self.tracer = Tracer(self.mu, self.modules) # Thread. self._setup_thread_register() # 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 _setup_thread_register(self): """ Set up thread register. This is currently not accurate and just filled with garbage to ensure the emulator does not crash. https://developer.arm.com/documentation/ddi0211/k/system-control-coprocessor/system-control-coprocessor-register-descriptions/c13--thread-and-process-id-registers """ thread_info_size = 64 thread_info = self.memory_manager.allocate(thread_info_size * 5) thread_info_1 = thread_info + (thread_info_size * 0) thread_info_2 = thread_info + (thread_info_size * 1) thread_info_3 = thread_info + (thread_info_size * 2) thread_info_4 = thread_info + (thread_info_size * 3) thread_info_5 = thread_info + (thread_info_size * 4) # Thread name write_utf8(self.mu, thread_info_5, "AndroidNativeEmu") # R4 self.mu.mem_write(thread_info_2 + 0x4, int(thread_info_5).to_bytes(4, byteorder='little')) self.mu.mem_write(thread_info_2 + 0xC, int(thread_info_3).to_bytes(4, byteorder='little')) # R1 self.mu.mem_write(thread_info_1 + 0x4, int(thread_info_4).to_bytes(4, byteorder='little')) self.mu.mem_write(thread_info_1 + 0xC, int(thread_info_2).to_bytes(4, byteorder='little')) self.mu.reg_write(UC_ARM_REG_C13_C0_3, thread_info_1) def load_library(self, filename, do_init=True): libmod = self.modules.load_module(filename) if do_init: logger.debug("Calling init for: %s " % filename) for fun_ptr in libmod.init_array: logger.debug("Calling Init function: %x " % fun_ptr) self.call_native(fun_ptr, 0, 0, 0) return libmod def call_symbol(self, module, symbol_name, *argv, is_return_jobject=True): symbol = module.find_symbol(symbol_name) if symbol is None: logger.error('Unable to find symbol \'%s\' in module \'%s\'.' % (symbol_name, module.filename)) return return self.call_native(symbol.address, *argv, is_return_jobject=is_return_jobject) def call_native(self, addr, *argv, is_return_jobject=True): # Detect JNI call is_jni = False if len(argv) >= 1: is_jni = argv[0] == self.java_vm.address_ptr or argv[0] == self.java_vm.jni_env.address_ptr # TODO: Write JNI args to local ref table if jni. try: # Execute native call. self.mu.reg_write(UC_ARM_REG_SP, STACK_ADDR + STACK_SIZE) native_write_args(self, *argv) stop_pos = randint(HOOK_MEMORY_BASE, HOOK_MEMORY_BASE + HOOK_MEMORY_SIZE) | 1 self.mu.reg_write(UC_ARM_REG_LR, stop_pos) self.mu.emu_start(addr, stop_pos - 1) # Read result from locals if jni. if is_jni and is_return_jobject: result_idx = self.mu.reg_read(UC_ARM_REG_R0) result = self.java_vm.jni_env.get_local_reference(result_idx) if result is None: return result return result.value else: return self.mu.reg_read(UC_ARM_REG_R0) finally: # Clear locals if jni. if is_jni: self.java_vm.jni_env.clear_locals() def dump(self, out_dir): os.makedirs(out_dir) for begin, end, prot in [reg for reg in self.mu.mem_regions()]: filename = "{:#010x}-{:#010x}.bin".format(begin, end) pathname = os.path.join(out_dir, filename) with open(pathname, "w") as f: f.write(hexdump.hexdump(self.mu.mem_read(begin, end - begin), result='return'))
class CPU: def __init__(self, firmware: Firmware = None, state: CpuState = None, verbose=0, init=True): self.firmware = firmware self.uc = Uc(UC_ARCH_ARM, UC_MODE_THUMB) self.cs = Cs(CS_ARCH_ARM, CS_MODE_THUMB) self.cs.detail = True self.state = state self.has_error = None self.last_addr = None self.ready = False self.context = None self.verbose = verbose if init: self.init() def init(self): if self.firmware: self.firmware.refresh() self.state.verify() self.init_memory() self.init_hook() self.init_firmware() self.context = self.uc.context_save() self.reset() self.ready = True def init_firmware(self): if not self.firmware: raise Exception("firmware missing error") addr = MemoryMap.FLASH.address self.uc.mem_write(addr, self.firmware.buffer) def reset(self): addr = MemoryMap.FLASH.address self.uc.context_restore(self.context) self.uc.reg_write(UC_ARM_REG_PC, from_bytes(self.uc.mem_read(addr + 4, 4))) def run(self): if not self.ready: raise Exception("init() does not called") INST_SIZE = 2 if self.firmware: self.last_func = self.firmware.text_map[self.uc.reg_read( UC_ARM_REG_PC)] if self.verbose >= 2: print(self.last_func) try: while self.step(): pass except UcError as e: print("ERROR:", e) addr = self.uc.reg_read(UC_ARM_REG_PC) self.debug_addr(addr - INST_SIZE * 8 - 2, count=7) print(">", end=" ") self.debug_addr(addr) self.debug_addr(addr + INST_SIZE, count=7) for reg in REGS: uc_value = self.uc.reg_read(reg) print(REGS_NAME[reg].ljust(5), hex32(uc_value), sep='\t') raise def step(self, count=None): addr = self.uc.reg_read(UC_ARM_REG_PC) cycle = self.state.cycle if count is not None: self.state.cycle = count try: self.uc.emu_start(addr | 1, MemoryMap.FLASH.address_until, 0, self.state.cycle) finally: if count is not None: self.state.cycle = cycle if self.has_error: raise UcError(0) return True def init_memory(self): for region in MemoryMap: # type: MemoryRegion self.uc.mem_map(region.address, region.size, region.uc_mode) def init_hook(self): peripheral = MemoryMap.PERIPHERAL self.uc.hook_add( UC_HOOK_MEM_READ, self.hook_peripheral_read, None, peripheral.address, peripheral.address_until, ) self.uc.hook_add(UC_HOOK_MEM_WRITE, self.hook_peripheral_write, None, peripheral.address, peripheral.address_until) self.uc.hook_add( UC_HOOK_MEM_READ_UNMAPPED | UC_HOOK_MEM_WRITE_UNMAPPED, self.hook_unmapped) self.uc.hook_add( UC_HOOK_INTR, self.hook_intr, ) if self.verbose >= 2: self.uc.hook_add(UC_HOOK_CODE, self.hook_inst) def hook_intr(self, uc: Uc, intno, user_data): # self.debug_addr(uc.reg_read(UC_ARM_REG_PC) - 40, 40) if intno == 2: swi = from_bytes(uc.mem_read(uc.reg_read(UC_ARM_REG_PC) - 2, 1)) r0 = uc.reg_read(UC_ARM_REG_R0) r1 = uc.reg_read(UC_ARM_REG_R1) r2 = uc.reg_read(UC_ARM_REG_R2) r3 = uc.reg_read(UC_ARM_REG_R3) if swi == 0: print("done?") print(intno, swi, ":", uc.reg_read(UC_ARM_REG_R0), uc.reg_read(UC_ARM_REG_R1), uc.reg_read(UC_ARM_REG_R2), uc.reg_read(UC_ARM_REG_R3)) uc.reg_write(UC_ARM_REG_R0, 16) uc.reg_write(UC_ARM_REG_R1, 32) uc.reg_write(UC_ARM_REG_R2, 48) uc.reg_write(UC_ARM_REG_R3, 64) elif swi == 1: # TODO: address and size vaild required? buffer = uc.mem_read(r0, r1).decode('utf-8', 'replace') if self.state.write_to_stdout: print("API_REQ", buffer) self.api_response("hello") self.uc.emu_stop() else: self.has_error = True self.uc.emu_stop() def api_response(self, *args): bufs = json.dumps(args) buf = bufs.encode("utf-8") if self.state.write_to_stdout: print("API_RES", buf) self.uc.mem_write(MemoryMap.SYSCALL_BUFFER.address, buf) self.uc.mem_write(MemoryMap.SYSCALL_BUFFER.address + len(buf), b'\0') self.uc.reg_write(UC_ARM_REG_R0, MemoryMap.SYSCALL_BUFFER.address) self.uc.reg_write(UC_ARM_REG_R1, len(buf)) def hook_peripheral_read(self, uc: Uc, access, address, size, value, data): if address == PeripheralAddress.OP_CON_RAM_SIZE: uc.mem_write(address, to_bytes(self.state.ram_size)) elif address == PeripheralAddress.OP_IO_RXR: if self.state.input_buffer: uc.mem_write(address, to_bytes(self.state.input_buffer.pop(0))) else: uc.mem_write(address, to_bytes(0)) elif address == PeripheralAddress.OP_RTC_TICKS_MS: pass # uc.mem_write(address, to_bytes(int((time.time() - self.state.epoch) * 1000))) else: if self.verbose >= 1: print("read", access, hex(address), size, value, data) def hook_peripheral_write(self, uc: Uc, access, address, size, value, data): if address == PeripheralAddress.OP_CON_PENDING: if self.verbose >= 1: print("OPENPYTHON_CONTROLLER_PENDING", value) elif address == PeripheralAddress.OP_CON_EXCEPTION: if self.verbose >= 1: print("OPENPYTHON_CONTROLLER_EXCEPTION", value) elif address == PeripheralAddress.OP_CON_INTR_CHAR: if self.verbose >= 1: print("OPENPYTHON_CONTROLLER_INTR_CHAR", value) elif address == PeripheralAddress.OP_IO_TXR: self.state.output_storage.append(value) if self.state.write_to_stdout: print(chr(value), end="") sys.stdout.flush() else: if self.verbose >= 1: print("write", access, hex(address), size, value, data) def hook_unmapped(self, uc: Uc, access, address, size, value, data): print("unmapped:", access, hex(address), size, value, data) uc.emu_stop() self.has_error = True def hook_inst(self, uc: Uc, address, size, data): func = None if self.firmware: func = self.firmware.text_map[address] if func in HELPER_FUNCTIONS: return if self.last_func != func: self.last_func = func print("#inst", hex(address), func) self.last_addr = address def report_memory(self): total_size = 0 for mem_start, mem_end, perm in self.uc.mem_regions(): total_size += mem_end - mem_start print("memory:", hex(mem_start), hex(mem_end - mem_start), perm) print("memory total:", total_size / 1024, "kb") INST_SIZE = 2 def debug_addr(self, addr, count=1, *, end="\n"): INST_SIZE = 4 try: for inst in self.cs.disasm( self.uc.mem_read(addr, INST_SIZE * count), addr, count): # type: CsInsn if self.firmware: print(self.firmware.text_map[inst.address], end=" ") print(hex(inst.address), hex(from_bytes(inst.bytes)), inst.mnemonic, inst.op_str, end=end) except UcError as exc: if exc.errno == UC_ERR_READ_UNMAPPED: print("fail to read memory", hex(addr)) def debug_addr_bin(self, addr, count=1): INST_SIZE = 4 try: for inst in self.cs.disasm( self.uc.mem_read(addr, INST_SIZE * count), addr, count): # type: CsInsn if self.firmware: print(self.firmware.text_map[inst.address], end=" ") if len(inst.bytes) != 2: raise Exception( f"len(inst) != 2; {inst.bytes} => {inst.mnemonic} {inst.op_str}" ) bcode = bin(from_bytes(inst.bytes))[2:].zfill(16) print(hex(inst.address), bcode[0:4], bcode[4:8], bcode[8:12], bcode[12:16], inst.mnemonic, inst.op_str) except UcError as exc: if exc.errno == UC_ERR_READ_UNMAPPED: print("fail to read memory", hex(addr))