def runX86_64(code): from unicorn.x86_const import UC_X86_INS_SYSCALL output = [] def hook_syscall(uc, user_data): from unicorn.x86_const import UC_X86_REG_RAX, UC_X86_REG_RDI from unicorn.x86_const import UC_X86_REG_RSI, UC_X86_REG_RDX rax = uc.reg_read(UC_X86_REG_RAX) if rax == 1: rdi = uc.reg_read(UC_X86_REG_RDI) rsi = uc.reg_read(UC_X86_REG_RSI) rdx = uc.reg_read(UC_X86_REG_RDX) try: buf = uc.mem_read(rsi, rdx) if rdi == 1: user_data.extend(map(chr, buf)) uc.reg_write(UC_X86_REG_RAX, rdx) except UcError: uc.emu_stop() raise Error("Segmentation fault") elif rax == 60: uc.emu_stop() else: raise Error("Unknown system call") mu = Uc(UC_ARCH_X86, UC_MODE_64) mu.mem_map(ADDRESS, 0x1000) mu.mem_write(ADDRESS, code) mu.hook_add(UC_HOOK_INSN, hook_syscall, output, 1, 0, UC_X86_INS_SYSCALL) mu.emu_start(ADDRESS, ADDRESS + len(code)) return ''.join(output)
def runarm64(code): output = [] def hook_interrupt(uc, intno, user_data): from unicorn.arm64_const import UC_ARM64_REG_X0, UC_ARM64_REG_X1 from unicorn.arm64_const import UC_ARM64_REG_X2, UC_ARM64_REG_X8 if intno != 2: uc.emu_stop() raise Exception("Unknwon Interrupt") x8 = uc.reg_read(UC_ARM64_REG_X8) if x8 == 64: # SYS_WRITE x0 = uc.reg_read(UC_ARM64_REG_X0) x1 = uc.reg_read(UC_ARM64_REG_X1) x2 = uc.reg_read(UC_ARM64_REG_X2) try: buf = uc.mem_read(x1, x2) if x0 == 1: user_data.extend(map(chr, buf)) uc.reg_write(UC_ARM64_REG_X0, x2) except UcError: uc.emu_stop() raise Error("Segmentation fault") elif x8 == 93: uc.emu_stop() else: uc.emu_stop() raise Error("Unknown system call") mu = Uc(UC_ARCH_ARM64, UC_MODE_ARM) mu.mem_map(ADDRESS, 0x1000) mu.mem_write(ADDRESS, code) mu.hook_add(UC_HOOK_INTR, hook_interrupt, output) mu.emu_start(ADDRESS, ADDRESS + len(code)) return ''.join(output)
def runX86(code): output = [] def hook_interrupt(uc, intno, user_data): from unicorn.x86_const import UC_X86_REG_EAX, UC_X86_REG_EBX from unicorn.x86_const import UC_X86_REG_ECX, UC_X86_REG_EDX if intno != 0x80: uc.emu_stop() raise Error("Unknwon Interrupt") eax = uc.reg_read(UC_X86_REG_EAX) if eax == 4: # SYS_WRITE ebx = uc.reg_read(UC_X86_REG_EBX) ecx = uc.reg_read(UC_X86_REG_ECX) edx = uc.reg_read(UC_X86_REG_EDX) try: buf = uc.mem_read(ecx, edx) if ebx == 1: user_data.extend(map(chr, buf)) uc.reg_write(UC_X86_REG_EAX, edx) except UcError: uc.emu_stop() raise Error("Segmentation fault") elif eax == 1: uc.emu_stop() else: raise Error("Unknown system call") mu = Uc(UC_ARCH_X86, UC_MODE_32) mu.mem_map(ADDRESS, 0x1000) mu.mem_write(ADDRESS, code) mu.hook_add(UC_HOOK_INTR, hook_interrupt, output) mu.emu_start(ADDRESS, ADDRESS + len(code)) return ''.join(output)
def get_msr(uc: Uc, scratch: int, msr: int) -> int: """ fetch the contents of the given model-specific register (MSR). this will clobber some memory at the given scratch address, as it emits some code. """ # save clobbered registers orax = uc.reg_read(UC_X86_REG_RAX) ordx = uc.reg_read(UC_X86_REG_RDX) orcx = uc.reg_read(UC_X86_REG_RCX) orip = uc.reg_read(UC_X86_REG_RIP) # x86: rdmsr buf = b"\x0f\x32" uc.mem_write(scratch, buf) uc.reg_write(UC_X86_REG_RCX, msr & 0xFFFFFFFF) uc.emu_start(scratch, scratch + len(buf), count=1) eax = uc.reg_read(UC_X86_REG_EAX) edx = uc.reg_read(UC_X86_REG_EDX) # restore clobbered registers uc.reg_write(UC_X86_REG_RAX, orax) uc.reg_write(UC_X86_REG_RDX, ordx) uc.reg_write(UC_X86_REG_RCX, orcx) uc.reg_write(UC_X86_REG_RIP, orip) return (edx << 32) | (eax & 0xFFFFFFFF)
def run(): print("Start emulate ARM...") try: # 创建虚拟机 mu = Uc(UC_ARCH_ARM, UC_MODE_THUMB) # 分配内存 ADDRESS = 0x10000 mu.mem_map(ADDRESS, 0x1000) mu.mem_write(ADDRESS, ARM_CODE) # 写寄存器 mu.reg_write(UC_ARM_REG_R0, 0x1234) mu.reg_write(UC_ARM_REG_R2, 0x6789) mu.reg_write(UC_ARM_REG_R3, 0x3333) # Hook 代码 mu.hook_add(UC_HOOK_CODE, hook_code, None, ADDRESS, ADDRESS + 0x1000) # 启动虚拟机 mu.emu_start(ADDRESS, ADDRESS + len(ARM_CODE)) # 获取结果 r0 = mu.reg_read(UC_ARM_REG_R0) r1 = mu.reg_read(UC_ARM_REG_R1) print(f">>> R0 = 0x{r0:x}") print(f">>> R1 = 0x{r1:x}") except UcError as e: print(f"Emulate error: {e}")
def load_test_case(self, test_case: TestCase) -> None: self.test_case = test_case # create and read a binary with open(test_case.bin_path, 'rb') as f: code = f.read() self.code_end = self.code_start + len(code) # initialize emulator in x86-64 mode emulator = Uc(uni.UC_ARCH_X86, uni.UC_MODE_64) try: # allocate memory emulator.mem_map(self.code_start, self.CODE_SIZE) emulator.mem_map(self.sandbox_base - self.WORKING_MEMORY_SIZE // 2, self.WORKING_MEMORY_SIZE) # write machine code to be emulated to memory emulator.mem_write(self.code_start, code) # set up callbacks emulator.hook_add(uni.UC_HOOK_MEM_READ | uni.UC_HOOK_MEM_WRITE, self.trace_mem_access, self) emulator.hook_add(uni.UC_HOOK_CODE, self.instruction_hook, self) self.emulator = emulator except UcError as e: LOGGER.error("[X86UnicornModel:load_test_case] %s" % e)
def set_exits(self, uc: Uc, base_address: int, exits: List[int]): """ We replace all hooks and exits with syscalls since they should be rare in kernel code. Then, when we encounter a syscall, we figure out if a syscall or exit occurred. This can also be used to add additional hooks in the future. :param uc: Unicorn instance :param exits: The exit counts :param base_address: the address we're mapping """ arch = self.arch # TODO: This only works for X64! if exits is None: self._deferred_exits.append(base_address) else: if len(exits) <= 1: # No need to patch anything, uc supports a single exit. return if len(exits) > 1: if arch == X64: for end_addr in exits: if self.get_base(end_addr) == base_address: print( "[*] Setting exit 0x{:016x}".format(end_addr)) # Abusing the syscall opcode for exits. # Just make sure the whole opcode will fit here by trying to map the end addr. self.map_page( uc, end_addr + len(x64utils.SYSCALL_OPCODE)) uc.mem_write(end_addr, x64utils.SYSCALL_OPCODE) else: raise (ValueError( "Multiple exits are not yet supported for arch {} - {}" .format(arch, exits)))
def runmips(code): output = [] def hook_interrupt(uc, intno, user_data): from unicorn.mips_const import UC_MIPS_REG_2, UC_MIPS_REG_4 from unicorn.mips_const import UC_MIPS_REG_5, UC_MIPS_REG_6 if intno != 17: uc.emu_stop() raise Exception("Unknwon Interrupt") v0 = uc.reg_read(UC_MIPS_REG_2) if v0 == 4004: # SYS_WRITE a0 = uc.reg_read(UC_MIPS_REG_4) a1 = uc.reg_read(UC_MIPS_REG_5) a2 = uc.reg_read(UC_MIPS_REG_6) try: buf = uc.mem_read(a1, a2) if a0 == 1: user_data.extend(map(chr, buf)) uc.reg_write(UC_MIPS_REG_2, a2) except UcError: uc.emu_stop() raise Error("Segmentation fault") elif v0 == 4001: uc.emu_stop() else: uc.emu_stop() raise Error("Unknown system call") mu = Uc(UC_ARCH_MIPS, UC_MODE_MIPS32 + UC_MODE_LITTLE_ENDIAN) mu.mem_map(ADDRESS, 0x1000) mu.mem_write(ADDRESS, code) mu.hook_add(UC_HOOK_INTR, hook_interrupt, output) mu.emu_start(ADDRESS, ADDRESS + len(code)) return ''.join(output)
def place_input(ucf: Unicorefuzz, uc: Uc, input: bytes) -> None: """ Places the input in memory and alters the input. This is an example for sk_buff in openvsswitch """ if len(input) < 1500: import os os._exit(0) rdx = uc.reg_read(UC_X86_REG_RDX) # struct sk_buff* skb ucf.map_page(uc, rdx) # ensure sk_buf is mapped data_ptr = struct.unpack("<Q", uc.mem_read(rdx + 0xD0, 8))[0] ucf.map_page(uc, data_ptr) # ensure the buffer is mapped uc.mem_write(data_ptr, input) # insert afl input
def emulate(data, mode): try: stack = 0x90000 uc = Uc(UC_ARCH_X86, mode) uc.mem_map(stack, 0x1000 * 10) uc.mem_map(shellcode_code_base, 0x100000) uc.mem_write(shellcode_code_base, data) uc.reg_write(UC_X86_REG_ESP, stack + 0x1000) uc.hook_add(UC_HOOK_CODE, hook_instr, user_data=mode) uc.emu_start(shellcode_code_base, shellcode_code_base + len(data), 0, shellcode_limit) except unicorn.UcError: pass
def place_input_skb(ucf: Unicorefuzz, uc: Uc, input: bytes) -> None: """ Places the input in memory and alters the input. This is an example for sk_buff in openvsswitch """ if len(input) > 1500: import os os._exit(0) # too big! # read input to the correct position at param rdx here: rdx = uc.reg_read(UC_X86_REG_RDX) rdi = uc.reg_read(UC_X86_REG_RDI) ucf.map_page(uc, rdx) # ensure sk_buf is mapped bufferPtr = struct.unpack("<Q", uc.mem_read(rdx + 0xD8, 8))[0] ucf.map_page(uc, bufferPtr) # ensure the buffer is mapped uc.mem_write(rdi, input) # insert afl input uc.mem_write(rdx + 0xC4, b"\xdc\x05") # fix tail
def test_exception_in_hook(self): uc = Uc(UC_ARCH_X86, UC_MODE_64) uc.mem_map(CODE_ADDR, 0x1000) uc.mem_write(CODE_ADDR, CODE) counter = HookCounter() uc.hook_add(UC_HOOK_CODE, counter.good_code_hook, begin=CODE_ADDR, end=CODE_ADDR + len(CODE)) uc.hook_add(UC_HOOK_CODE, counter.bad_code_hook, begin=CODE_ADDR, end=CODE_ADDR + len(CODE)) self.assertRaises(ValueError, uc.emu_start, CODE_ADDR, CODE_ADDR + len(CODE)) # Make sure hooks calls finish before raising (hook_calls == 2) self.assertEqual(counter.hook_calls, 2)
def speculate_instruction(emulator: Uc, address, size, model) -> None: # reached max spec. window? skip if len(model.checkpoints) >= model.nesting: return if model.previous_store[0]: store_addr = model.previous_store[0] old_value = bytes(model.previous_store[2]) new_is_signed = model.previous_store[3] < 0 new_value = (model.previous_store[3]). \ to_bytes(model.previous_store[1], byteorder='little', signed=new_is_signed) # store a checkpoint model.checkpoint(emulator, address) # cancel the previous store but preserve its value emulator.mem_write(store_addr, old_value) model.store_logs[-1].append((store_addr, new_value)) model.previous_store = (0, 0, 0, 0)
def load_mem(mu: Uc, addr, size): filename = "7.4AT-02+V2.001.EFM.bin" data = None with open(filename, "rb") as fh: fh.seek(addr) data = fh.read(size) _addr = addr & PAGE_MASK _size = ((size) & PAGE_MASK) + PAGE_SIZE print(f"[+] Mapping Memory:\n\tAddress: {_addr:08X}\n\tSize: {_size:X}") mu.mem_map(_addr, _size) load_addr = addr load_size = size print( f"[>] Loading Memory:\n\tAddress: {load_addr:08X}\n\tSize: {load_size:X}" ) mu.mem_write(load_addr, data)
def map_page(self, uc: Uc, addr: int) -> None: """ Maps a page at addr in the harness, asking probe_wrapper. :param uc: The unicore :param addr: The address """ page_size = self.config.PAGE_SIZE base_address = self.get_base(addr) if base_address not in self._mapped_page_cache.keys(): input_file_name = os.path.join(self.requestdir, "{:016x}".format(addr)) dump_file_name = os.path.join(self.statedir, "{:016x}".format(base_address)) if os.path.isfile(dump_file_name + REJECTED_ENDING): print("CAN I HAZ EXPLOIT?") os.kill(os.getpid(), signal.SIGSEGV) if not os.path.isfile(dump_file_name): open(input_file_name, "a").close() print("mapping {}".format(hex(base_address))) while 1: try: if os.path.isfile(dump_file_name + REJECTED_ENDING): print("CAN I HAZ EXPLOIT?") os.kill(os.getpid(), signal.SIGSEGV) with open(dump_file_name, "rb") as f: content = f.read() if len(content) < page_size: time.sleep(0.001) continue self._mapped_page_cache[base_address] = content uc.mem_map(base_address, len(content)) uc.mem_write(base_address, content) self.set_exits(uc, base_address, self.exits) return except IOError: pass except UcError as ex: return except Exception as ex: # todo this should never happen if we don't map like idiots print( "map_page_blocking failed: base address=0x{:016x} ({})" .format(base_address, ex))
def set_msr(uc: Uc, scratch: int, msr: int, val: int) -> None: """ set the given model-specific register (MSR) to the given value. this will clobber some memory at the given scratch address, as it emits some code. """ # save clobbered registers orax = uc.reg_read(UC_X86_REG_RAX) ordx = uc.reg_read(UC_X86_REG_RDX) orcx = uc.reg_read(UC_X86_REG_RCX) orip = uc.reg_read(UC_X86_REG_RIP) # x86: wrmsr uc.mem_write(scratch, INSN_WRMSR) uc.reg_write(UC_X86_REG_RAX, val & 0xFFFFFFFF) uc.reg_write(UC_X86_REG_RDX, (val >> 32) & 0xFFFFFFFF) uc.reg_write(UC_X86_REG_RCX, msr & 0xFFFFFFFF) uc.emu_start(scratch, scratch + len(INSN_WRMSR), count=1) # restore clobbered registers uc.reg_write(UC_X86_REG_RAX, orax) uc.reg_write(UC_X86_REG_RDX, ordx) uc.reg_write(UC_X86_REG_RCX, orcx) uc.reg_write(UC_X86_REG_RIP, orip)
def test_x86_64_syscall(self): print("Emulate x86_64 code with 'syscall' instruction") ADDRESS = 0x1000000 X86_CODE64_SYSCALL = b"\x0f\x05" # SYSCALL # Initialize emulator in X86-64bit mode mu = Uc(UC_ARCH_X86, UC_MODE_64) # map 2MB memory for this emulation mu.mem_map(ADDRESS, 2 * 1024 * 1024) # write machine code to be emulated to memory mu.mem_write(ADDRESS, X86_CODE64_SYSCALL) def hook_syscall(mu, user_data): rax = mu.reg_read(UC_X86_REG_RAX) if rax == 0x100: mu.reg_write(UC_X86_REG_RAX, 0x200) else: print("ERROR: was not expecting rax=%d in syscall" % rax) # hook interrupts for syscall mu.hook_add(UC_HOOK_INSN, hook_syscall, None, 1, 0, UC_X86_INS_SYSCALL) # syscall handler is expecting rax=0x100 mu.reg_write(UC_X86_REG_RAX, 0x100) try: # emulate machine code in infinite time mu.emu_start(ADDRESS, ADDRESS + len(X86_CODE64_SYSCALL)) except UcError as e: print("ERROR: %s" % e) # now print out some registers print(">>> Emulation done. Below is the CPU context") rax = mu.reg_read(UC_X86_REG_RAX) print(">>> RAX = 0x%x" % rax)
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)
ret |= UC_PROT_WRITE if p_flags & P_FLAGS.PF_X != 0: ret |= UC_PROT_EXEC return ret load_base = 0 emu = Uc(UC_ARCH_ARM, UC_MODE_LITTLE_ENDIAN) load_segments = [ x for x in elf.iter_segments() if x.header.p_type == 'PT_LOAD' ] for segment in load_segments: fr_addr, size = align(load_base + segment.header.p_vaddr, segment.header.p_memsz, segment.header.p_align) emu.mem_map(fr_addr, size, pflags2prot(segment.header.p_flags)) emu.mem_write(load_base + segment.header.p_vaddr, segment.data()) STACK_ADDR = 0x7F000000 STACK_SIZE = 1024 * 1024 start_addr = None emu.mem_map(STACK_ADDR, STACK_SIZE, UC_PROT_READ | UC_PROT_WRITE) bss_section_header = elf.get_section_by_name('.bss').header bss_section_start, bss_section_end = bss_section_header.sh_addr, bss_section_header.sh_addr + bss_section_header.sh_size data_section_header = elf.get_section_by_name('.data').header data_section_start, data_section_end = data_section_header.sh_addr, data_section_header.sh_addr + data_section_header.sh_size def dbg_hook_memory_access(uc: Uc, access, address, size, value, data): if access == UC_MEM_WRITE: print("Write: addr=0x{0:016x} size={1} data=0x{2:016x}".format(
ARM_BYTECODE, _ = ks.asm(ARM_CODE) # convert the array of integers into bytes ARM_BYTECODE = bytes(ARM_BYTECODE) print(f"Code successfully assembled (length = {len(ARM_BYTECODE)})") print("ARM bytecode:", ARM_BYTECODE) except KsError as e: print("Keystone Error: %s" % e) exit(1) # memory address where emulation starts ADDRESS = 0x1000000 print("Emulating the ARM code") try: # Initialize emulator in ARM mode mu = Uc(UC_ARCH_ARM, UC_MODE_ARM) # map 2MB memory for this emulation mu.mem_map(ADDRESS, 2 * 1024 * 1024) # write machine code to be emulated to memory mu.mem_write(ADDRESS, ARM_BYTECODE) # Set the r0 register in the code, let's calculate factorial(5) mu.reg_write(UC_ARM_REG_R0, 5) # emulate code in infinite time and unlimited instructions mu.emu_start(ADDRESS, ADDRESS + len(ARM_BYTECODE)) # now print out the R0 register print("Emulation done. Below is the result") # retrieve the result from the R1 register r1 = mu.reg_read(UC_ARM_REG_R1) print(">> R1 = %u" % r1) except UcError as e: print("Unicorn Error: %s" % e)
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 :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() # 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.MEMORY_BASE, config.MEMORY_SIZE) self.hooker = Hooker(self, config.MEMORY_BASE, config.MEMORY_SIZE) # JavaVM self.java_classloader = JavaClassLoader() self.java_vm = JavaVM(self.java_classloader, self.hooker) # Native 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) 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.mu, *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) # 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 set_exit(uc: Uc, addr: int) -> None: """ We use syscalls for this as unicorn offers hooks. Could also throw an UB2 instead and catch. """ uc.mem_write(addr, SYSCALL_OPCODE)
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))
class emu: """ Loads ELF file to unicorn, sets watchpoints and stdin """ def __init__(self, fname, stdin, watchpoints=[], drcov=True, emulator_base=None, fw_entry_symbol="cont"): self.stdin = stdin self.exception = "" self.uc = Uc(UC_ARCH_ARM, UC_MODE_ARM) self.fname = fname self.fd = open(fname, "rb") self.elf = elffile.ELFFile(self.fd) self.symbols = {} self.symbols_reverse = {} for i in range(self.elf.num_sections()): sec = self.elf.get_section(i) if sec.name == ".symtab": for sym in sec.iter_symbols(): self.symbols[sym.name] = sym.entry["st_value"] self.symbols_reverse[sym.entry["st_value"]] = sym.name self.results = [] self.result_id = 0 self.coverage_pc = set() self.coverage_bb = set() self.read = set() self.write = set() self.trace_initialized = False self.coverage_activity = {} self.read_activity = {} self.write_activity = {} self.stdout = "" self.stderr = "" self.emulator_base_start = None self.emulator_base_stop = None if fw_entry_symbol in self.symbols: self.fw_entry = self.symbols[fw_entry_symbol] # ignore everything until that symbol else: self.fw_entry = None #loading prog headrs self.state = [] self.segments = [] for i in range(self.elf.num_sections()): section = self.elf.get_section(i) if section.header["sh_flags"] & SH_FLAGS.SHF_ALLOC != 0: addr = section.header["sh_addr"] size = section.header["sh_size"] name = section.name #NOBITS sections contains no data in file #Will be initialized with zero if section.header["sh_type"] == "SHT_NOBITS": data = b"\x00" * size else: data = section.data() print("Found %s @ 0x%x - 0x%x (%d bytes)" % (name, addr, addr+len(data), len(data))) if emulator_base == addr: self.emulator_base_start = emulator_base self.emulator_base_stop = emulator_base + size self.segments += [(name, addr, size)] self.state += [(addr, size, data)] #compute memory map from sections self.maps = [] if self.emulator_base_start is not None: self.maps += [(self.emulator_base_start, self.emulator_base_stop)] self.segments = sorted(self.segments, key=lambda x:x[0]) for name, addr, size in self.segments: size += addr & 0x3ff addr = addr & (~0x3ff) altered = False for i in range(len(self.maps)): map_addr, map_size = self.maps[i] offset = addr - map_addr if addr >= map_addr and addr <= map_addr + map_size: self.maps[i] = (map_addr, self.pageresize(max(map_size, offset+size))) altered = True if not altered: self.maps += [(addr, self.pageresize(size))] for addr, size in self.maps: print("Mapping 0x%x - 0x%x (%d bytes)" % (addr, addr+size, size)) self.uc.mem_map(addr, size, UC_PROT_ALL) for addr,size,data in self.state: print("Loading 0x%x - 0x%x (%d bytes)" % (addr, addr+len(data), len(data))) self.uc.mem_write(addr, data) #stack stack = 0xdead0000 stack_size = 16384 print("Mapping Stack 0x%x - 0x%x (%d bytes)" % (stack, stack+stack_size, stack_size)) self.uc.mem_map(stack, stack_size, UC_PROT_ALL) self.uc.reg_write(arm_const.UC_ARM_REG_SP, stack + stack_size) #syscalls self.uc.hook_add(UC_HOOK_INTR, self.hook_intr, self) #tracing self.watchpoints = watchpoints self.uc.hook_add(UC_HOOK_CODE, self.hook_code, self) self.uc.hook_add(UC_HOOK_MEM_READ | UC_HOOK_MEM_WRITE, self.hook_mem_access, self) #prepare drcov file self.drcov = drcov if drcov: self.uc.hook_add(UC_HOOK_BLOCK, self.hook_bb, self) def pageresize(self, s, pagesize=1024): if s % pagesize == 0: return s return (int(s / pagesize) + 1) * pagesize """ We need to emulate read and write for emulation """ @staticmethod def hook_intr(uc, size, self): #print hex(uc.reg_read(arm_const.UC_ARM_REG_PC)) pc = uc.reg_read(arm_const.UC_ARM_REG_PC) for name in ["read","write"]: if self.symbols[name] <= pc and self.symbols[name] + 8 >= pc: #print name if name == "read": fd = uc.reg_read(arm_const.UC_ARM_REG_R0) target = uc.reg_read(arm_const.UC_ARM_REG_R1) size = uc.reg_read(arm_const.UC_ARM_REG_R2) data = self.stdin[:size] self.stdin = self.stdin[size:] uc.mem_write(target, data) self.uc.reg_write(arm_const.UC_ARM_REG_R0, len(data)) elif name == "write": fd = uc.reg_read(arm_const.UC_ARM_REG_R0) target = uc.reg_read(arm_const.UC_ARM_REG_R1) size = uc.reg_read(arm_const.UC_ARM_REG_R2) data = uc.mem_read(target, size) if fd == 1: self.stdout += data.decode("utf-8") sys.stdout.write(data.decode("utf-8")) else: self.stderr += data.decode("utf-8") sys.stderr.write(data.decode("utf-8")) else: print("unknown intr") """ Implement memory and code watchpoints """ @staticmethod def hook_bb(uc, address, size, self): if self.emulator_base_start is not None: if address >= self.emulator_base_start and address < self.emulator_base_stop: return #print(hex(address)) self.coverage_bb.add((address, size)) @staticmethod def hook_code(uc, address, size, self): # Unicorn will for some reason giv old register values after a crash # The last update seems to be on the entry of the bb self.regs = {} self.regs["r0"] = self.uc.reg_read(arm_const.UC_ARM_REG_R0) self.regs["r1"] = self.uc.reg_read(arm_const.UC_ARM_REG_R1) self.regs["r2"] = self.uc.reg_read(arm_const.UC_ARM_REG_R2) self.regs["r3"] = self.uc.reg_read(arm_const.UC_ARM_REG_R3) self.regs["r4"] = self.uc.reg_read(arm_const.UC_ARM_REG_R4) self.regs["r5"] = self.uc.reg_read(arm_const.UC_ARM_REG_R5) self.regs["r6"] = self.uc.reg_read(arm_const.UC_ARM_REG_R6) self.regs["r7"] = self.uc.reg_read(arm_const.UC_ARM_REG_R7) self.regs["r8"] = self.uc.reg_read(arm_const.UC_ARM_REG_R8) self.regs["r9"] = self.uc.reg_read(arm_const.UC_ARM_REG_R9) self.regs["r10"] = self.uc.reg_read(arm_const.UC_ARM_REG_R10) self.regs["r11"] = self.uc.reg_read(arm_const.UC_ARM_REG_R11) self.regs["r12"] = self.uc.reg_read(arm_const.UC_ARM_REG_R12) self.regs["sp"] = self.uc.reg_read(arm_const.UC_ARM_REG_R13) self.regs["lr"] = self.uc.reg_read(arm_const.UC_ARM_REG_R14) self.regs["pc"] = self.uc.reg_read(arm_const.UC_ARM_REG_R15) if self.fw_entry is not None and address & 0xfffffffe == self.fw_entry & 0xfffffffe: self.trace_init_state() if self.fw_entry is None and not self.trace_initialized: self.trace_init_state() if self.emulator_base_start is not None: if address >= self.emulator_base_start and address < self.emulator_base_stop: return self.coverage_pc.add(address) if address in self.coverage_activity: self.coverage_activity[address] += 1 else: self.coverage_activity[address] = 1 if address in self.watchpoints or address^1 in self.watchpoints: self.trace_state_change("Execute") @staticmethod def hook_mem_access(uc, access, address, size, value, self): pc = self.uc.reg_read(arm_const.UC_ARM_REG_R15) if self.emulator_base_start is not None: if pc >= self.emulator_base_start and pc < self.emulator_base_stop: return if access == UC_MEM_WRITE: self.write.add((pc, address, value)) if address in self.write_activity: self.write_activity[address] += 1 else: self.write_activity[address] = 1 else: self.read.add((pc, address)) if address in self.read_activity: self.read_activity[address] += 1 else: self.read_activity[address] = 1 if address in self.watchpoints: if access == UC_MEM_WRITE: self.trace_state_change("Write 0x%x" % address) else: self.trace_state_change("Read 0x%x" % address) """ For each tracepoint that was hit Dump Registers Do Memory Dump """ def trace_init_state(self): self.state = [] self.trace_initialized = True for name, addr, size in self.segments: data = self.uc.mem_read(addr, size) #data = list(map(chr, data)) self.state += [(addr, size, data)] """ Called if a tracepoint is hit Will save registers and analyzes changes made im memory """ def trace_state_change(self, reason): print(reason) new_state = [] memdiff = [] for addr, size, data in self.state: new_data = self.uc.mem_read(addr, size) #new_data = list(map(chr, new_data)) if data != new_data: new = old = "" for i in range(len(data)): if data[i] != new_data[i]: old += "%02x" % data[i] new += "%02x" % new_data[i] elif new != "": memdiff += [(i+addr-len(new), old, new)] new = old = "" new_state += [(addr, size, new_data)] #XXX memdif_rendered = self.render_mem_diff() sys.stderr.write(self.stderr) sys.stderr.write("\n"+memdif_rendered+"\n") self.state = new_state # disassemble current instruction try: pc = self.regs["pc"] md = capstone.Cs(capstone.CS_ARCH_ARM, capstone.CS_MODE_THUMB) instr = list(md.disasm(self.uc.mem_read(pc, 4), pc))[0] instr = instr.mnemonic + " " + instr.op_str except: import traceback; traceback.print_exc() instr = hexlify(self.uc.mem_read(pc, 4)) # Save tracepoint object tp = {} tp["reason"] = reason tp["regs"] = self.regs tp["instr"] = instr tp["memdiff"] = memdiff tp["memdif_rendered"] = memdif_rendered tp["stdout"] = self.stdout tp["stderr"] = self.stderr tp["resid"] = self.result_id self.results += [tp] self.stdout = "" self.stderr = "" self.result_id += 1 return [{"regs": self.regs, "memdiff": sorted(memdiff)}] def render_mem_diff(self, block_size=32): ret = "----------" + ("-"*(3*block_size+1)) + "\n" ret += " |\n" print_dots = False for addr, size, data in self.state: new_data = self.uc.mem_read(addr, size) #new_data = list(map(chr, new_data)) current_offset = 0 #print(len(data), len(new_data), size) #for each hexdump row while current_offset < size: old_row = data[current_offset: current_offset+block_size] new_row = new_data[current_offset: current_offset+block_size] #ugly equal comparison equal = True for x,y in zip(new_row, old_row): equal = equal and (x==y) if not equal: hex_new = "%8x | " % (addr + current_offset) hex_old = " | " symbols = "" #render diff for i in range(min(block_size, len(new_row))): if new_row[i] == old_row[i]: hex_new += "%02x " % new_row[i] hex_old += " " else: hex_new += "\033[;32m%02x\033[;00m " % new_row[i] hex_old += "\033[;31m%02x\033[;00m " % old_row[i] if (addr + current_offset + i) in self.watchpoints: symbols += " | " if len("Watchpoint") < 3*i - 1: symbols += " " * (3*i - len("Watchpoint") - 1) symbols += "\033[;33mWatchpoint ^^\033[;00m\n" else: symbols += " " * i symbols += "\033[;33m^^ Watchpoint\033[;00m\n" elif (addr + current_offset + i) in self.symbols_reverse: name = self.symbols_reverse[addr + current_offset + i] symbols += " | " if len(name) < 3*i - 1: symbols += " " * (3*i - len(name) - 1) symbols += "%s ^^\n" % name else: symbols += " " * i symbols += "^^ %s\n" % name ret += hex_new + "\n" + hex_old + "\n" if len(symbols) > 1: ret += symbols print_dots = True else: if print_dots: print_dots = False ret += " |\n" ret += " |" + ("-"*(3*block_size+1)) + "\n" ret += " |\n" current_offset += block_size #cleanup end split = ret.split("\n") if len(split) <= 3: return "" ret = "\n".join(split[:-3]) + "\n" ret += "----------" + ("-"*(3*block_size+1)) + "\n" return ret """ Run the Emulation """ def run(self, timeout=300): try: print("running until exit @ 0x%x" % self.symbols["exit"]) self.uc.emu_start(self.elf.header.e_entry, self.symbols["exit"], timeout=timeout*UC_SECOND_SCALE) self.trace_state_change("Exit") except KeyboardInterrupt: sys.exit(1) except Exception as e: self.exception = str(e) print(e) import traceback; traceback.print_exc() print(hex(self.uc.reg_read(arm_const.UC_ARM_REG_PC))) self.trace_state_change(str(e)) # Seems to be broken n lighthouse def get_drcov(self): drcov = b"DRCOV VERSION: 2\nDRCOV FLAVOR: drcov\n" drcov += b"Module Table: version 2, count %d\n" % len(self.state) drcov += b"Columns: id, base, end, entry, path\n" for i in range(len(self.state)): addr, size, _ = self.state[i] drcov += b"%d, 0x%x, 0x%x, 0x%x, %s\n" % (i, addr, addr+size+1, addr, os.path.basename(self.fname).encode()) drcov += b"BB Table: %d bbs\n" % len(self.coverage_bb) bb_table = b"" for address, size in self.coverage_bb: for module_id in range(len(self.state)): base_addr, module_size, _ = self.state[module_id] if address >= base_addr and address <= base_addr + module_size: bb_table += struct.pack("<Ihh", address - base_addr, size, module_id) break return drcov + bb_table def get_tracefile(self): trace = "" for address in self.coverage_pc: trace += "0x%x\n" % address return trace.encode()
# memory address where emulation starts ADDRESS = 0x1000000 try: # Initialize the disassembler in x86 mode md = Cs(CS_ARCH_X86, CS_MODE_64) # iterate over each instruction and print it for instruction in md.disasm(X86_MACHINE_CODE, 0x1000): print("0x%x:\t%s\t%s" % (instruction.address, instruction.mnemonic, instruction.op_str)) except CsError as e: print("Capstone Error: %s" % e) try: # Initialize emulator in x86_64 mode mu = Uc(UC_ARCH_X86, UC_MODE_64) # map 2MB memory for this emulation mu.mem_map(ADDRESS, 2 * 1024 * 1024) # write machine code to be emulated to memory mu.mem_write(ADDRESS, X86_MACHINE_CODE) # Set the r0 register in the code to the number of 7 mu.reg_write(UC_X86_REG_RDI, 7) # emulate code in infinite time & unlimited instructions mu.emu_start(ADDRESS, ADDRESS + len(X86_MACHINE_CODE)) # now print out the R0 register print("Emulation done. Below is the result") rax = mu.reg_read(UC_X86_REG_RAX) print(">>> RAX = %u" % rax) except UcError as e: print("Unicorn Error: %s" % e)
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'))
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'))
def hook_memory(uc, access, address, size, value, user_data): pc = uc.reg_read(UC_ARM_REG_PC) print(f">>> Memory err pc:0x{pc:x} address:0x{address:x}, size:0x{size:x}") a1 = b'123' mu = Uc(UC_ARCH_ARM, UC_MODE_THUMB) # 分配 so 内存 image_base = 0x0 image_size = 0x10000 * 8 mu.mem_map(image_base, image_size) with open("libnative-lib.so", "rb") as f: sofile = f.read() mu.mem_write(image_base, sofile) # 分配 Stack 内存 stack_base = 0xA0000 stack_size = 0x10000 * 3 stack_top = stack_base + stack_size - 0x4 mu.mem_map(stack_base, stack_size) mu.reg_write(UC_ARM_REG_SP, stack_top) # 分配数据内存 data_base = 0xF0000 data_size = 0x10000 * 3 mu.mem_map(data_base, data_size) mu.mem_write(data_base, a1) mu.reg_write(UC_ARM_REG_R0, data_base)
def place_input(ucf: Unicorefuzz, uc: Uc, input: bytes) -> None: rax = uc.reg_read(UC_X86_REG_RAX) # make sure the parameter memory is mapped ucf.map_page(uc, rax) uc.mem_write(rax, input) # insert afl input