def hook_TerminateProcess(ql: Qiling, address: int, params): # Samples will try to kill other process! We don't want to always stop! process = params["hProcess"] if process == ql.os.profile.getint( "KERNEL", "pid"): # or process == ql.os.image_address: ql.emu_stop() ql.os.PE_RUN = False return 1
def test_x8664_uefi(self): def force_notify_RegisterProtocolNotify(ql: Qiling, address: int, params): ql.log.info( f'[force_notify] address = {address:#x}, params = {params}') self.ck.visited_oncall = True event_id = params['Event'] if event_id in ql.loader.events: event = ql.loader.events[event_id] # let's force notify event["Set"] = False utils.signal_event(ql, event_id) utils.execute_protocol_notifications(ql, True) return EFI_SUCCESS return EFI_INVALID_PARAMETER def my_onenter(ql: Qiling, address: int, params): ql.log.info( f'[my_onenter] address = {address:#x}, params = {params}') self.ck.visited_onenter = True def my_onexit(ql: Qiling, address: int, params, retval: int): ql.log.info( f'[my_onexit] address = {address:#x}, params = {params}') self.ck.visited_onexit = True if __name__ == "__main__": with open(f'{ROOTFS_UEFI}/rom2_nvar.pickel', 'rb') as f: env = pickle.load(f) ql = Qiling([f'{ROOTFS_UEFI}/bin/TcgPlatformSetupPolicy'], ROOTFS_UEFI, env=env, verbose=QL_VERBOSE.DEBUG) self.ck = Checklist() ql.set_api("RegisterProtocolNotify", force_notify_RegisterProtocolNotify) ql.set_api("CopyMem", my_onenter, QL_INTERCEPT.ENTER) ql.set_api("LocateProtocol", my_onexit, QL_INTERCEPT.EXIT) ql.run() self.assertTrue(self.ck.visited_oncall) self.assertTrue(self.ck.visited_onenter) self.assertTrue(self.ck.visited_onexit)
def sandbox(path: str, args: List[str], rootfs: str) -> None: options = { "filename": [path, *args], "rootfs": rootfs, "env": None, "shellcoder": None, "ostype": "Linux", "archtype": "arm_thumb", # "archtype": "arm", "bigendian": False, "output": "debug", "verbose": 1, "profile": None, "console": True, "log_dir": None, "log_split": None, "append": None, "libcache": False, "stdin": 0, "stdout": 0, "stderr": 0 } ql = Qiling(**options) for syscall, hook in hooks.items(): ql.set_syscall(syscall, syscall_hook(syscall)) ql.hook_mem_invalid(hook_invalid_memory) ql.debugger = ":9999" ql.run()
def test_elf_linux_x86_getdents64(self): mock_stdout = pipe.SimpleOutStream(sys.stdout.fileno()) ql = Qiling(["../examples/rootfs/x86_linux/bin/x86_getdents64"], "../examples/rootfs/x86_linux", verbose=QL_VERBOSE.DEBUG, stdout=mock_stdout) ql.run() self.assertTrue("bin\n" in ql.os.stdout.read().decode("utf-8")) del ql
def test_elf_linux_mips32el_static(self): def random_generator(size=6, chars=string.ascii_uppercase + string.digits): return ''.join(random.choice(chars) for x in range(size)) ql = Qiling([ "../examples/rootfs/mips32el_linux/bin/mips32el_hello_static", random_generator(random.randint(1, 99)) ], "../examples/rootfs/mips32el_linux") ql.run() del ql
def ql_syscall_prlimit64(ql: Qiling, pid: int, res: int, new_limit: int, old_limit: int): # setrlimit() and getrlimit() if pid == 0 and new_limit == 0: rlim = resource.getrlimit(res) ql.mem.write(old_limit, ql.packs(rlim[0]) + ql.packs(rlim[1])) return 0 # set other process which pid != 0 return -1
def hook___p___argv(ql: Qiling, address: int, params): ret = ql.os.heap.alloc(ql.pointersize) argv_addr = ql.os.heap.alloc(ql.pointersize * len(ql.os.argv)) count = 0 for each in ql.os.argv: argv = ql.os.heap.alloc(len(each) + 1) ql.mem.write(argv, bytes(each, 'ascii') + b'\x00') ql.mem.write(argv_addr + count * ql.pointersize, ql.pack(argv)) count += 1 ql.mem.write(ret, ql.pack(argv_addr)) return ret
def sanitized_emulate(path, rootfs, fault_type, output="debug", enable_trace=False): ql = Qiling([path], rootfs, output=output) ql.env['FaultType'] = fault_type enable_sanitized_heap(ql) ql.run() if not ql.os.heap.validate(): my_abort("Canary corruption detected")
def test_x8664_getcwd(self): mock_stdout = pipe.SimpleOutStream(sys.stdout.fileno()) ql = Qiling(["../examples/rootfs/x8664_linux/bin/testcwd"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG, stdout=mock_stdout) ql.run() self.assertEqual(ql.os.stdout.read(), b'/\n/lib\n/bin\n/\n') del ql
def test_elf_linux_execve_x8664(self): ql = Qiling(["../examples/rootfs/x8664_linux/bin/posix_syscall_execve"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG) ql.run() for key, value in ql.loader.env.items(): QL_TEST=value self.assertEqual("TEST_QUERY", QL_TEST) self.assertEqual("child", ql.loader.argv[0]) del QL_TEST del ql
def hook___p__environ(ql: Qiling, address: int, params): ret = ql.os.heap.alloc(ql.pointersize * len(ql.os.env)) count = 0 for key in ql.os.env: pointer = ql.os.heap.alloc(ql.pointersize) env = key + "=" + ql.os.env[key] env_addr = ql.os.heap.alloc(len(env) + 1) ql.mem.write(env_addr, bytes(env, 'ascii') + b'\x00') ql.mem.write(pointer, ql.pack(env_addr)) ql.mem.write(ret + count * ql.pointersize, ql.pack(pointer)) count += 1 return ret
def test_pe_win_x8664_cmdln(self): ql = Qiling( ["../examples/rootfs/x8664_windows/bin/cmdln64.exe", 'arg1', 'arg2 with spaces'], "../examples/rootfs/x8664_windows") ql.os.stdout = TestOut() ql.run() expected_string = b'<C:\\Users\\Qiling\\Desktop\\cmdln64.exe arg1 "arg2 with spaces">\n' expected_keys = [b'_acmdln', b'_wcmdln', b'GetCommandLineA', b'GetCommandLineW'] for key in expected_keys: self.assertTrue(key in ql.os.stdout.output) self.assertEqual(expected_string, ql.os.stdout.output[key]) del ql
def test_elf_linux_x8664_path_traversion(self): mock_stdout = pipe.SimpleOutStream(sys.stdout.fileno()) ql = Qiling( ["../examples/rootfs/x8664_linux/bin/path_traverse_static"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG, stdout=mock_stdout) ql.run() self.assertTrue("root\n" not in ql.os.stdout.read().decode("utf-8")) del ql
def sanitized_emulate(path, rootfs, fault_type, verbose=QL_VERBOSE.DEBUG, enable_trace=False): ql = Qiling([path], rootfs, verbose=verbose) ql.env['FaultType'] = fault_type enable_sanitized_heap(ql) ql.run() if not ql.os.heap.validate(): my_abort("Canary corruption detected")
def test_x8664_absolute_path(self): mock_stdout = pipe.SimpleOutStream(sys.stdout.fileno()) ql = Qiling(["../examples/rootfs/x8664_linux/bin/absolutepath"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG, stdout=mock_stdout) ql.run() self.assertEqual(ql.os.stdout.read(), b'test_complete\n\ntest_complete\n\n') del ql
def __getrlimit_common(ql: Qiling, res: int, rlim: int) -> int: RLIMIT_STACK = 3 if res == RLIMIT_STACK: if ql.arch.bits == 64: stack_size = int(ql.os.profile.get("OS64", "stack_size"), 16) elif ql.arch.bits == 32: stack_size = int(ql.os.profile.get("OS32", "stack_size"), 16) rlimit = (stack_size, -1) else: rlimit = resource.getrlimit(res) ql.mem.write(rlim, ql.pack64s(rlimit[0]) + ql.pack64s(rlimit[1])) return 0
def test_elf_linux_arm(self): def my_puts(ql): params = ql.os.resolve_fcall_params(ELFTest.PARAMS_PUTS) print(f'puts("{params["s"]}")') all_mem = ql.mem.save() ql.mem.restore(all_mem) ql = Qiling(["../examples/rootfs/arm_linux/bin/arm_hello"], "../examples/rootfs/arm_linux", verbose=QL_VERBOSE.DEBUG, profile='profiles/append_test.ql') ql.os.set_api('puts', my_puts) ql.run() del ql
def ql_qnx_msg_sys_conf(ql:Qiling, coid, smsg, sparts, rmsg, rparts, *args, **kw): # struct _sys_conf in services/system/public/sys/sysmsg.h (type, subtype, cmd, name, spare, value) = unpack("<HHiiiq", get_message_body(ql, smsg, sparts)) # check parameters assert (c_int32(sparts).value) == (-24), "input size is wrong" assert (type) == (0x000) if not subtype in sysconf_subtypes: raise NotImplementedError(f'subtype {subtype} not implemented') if not cmd in sysconf_conditions: raise NotImplementedError(f'cmd type {cmd} not implemented') # sys_conf(_SYS_SUB_GET, _CONF_STR, *) in lib/c/1a/confstr.c if subtype == 0 and cmd == (1 << 20): # check parameters assert (c_int32(rparts).value) == (2), "output size is wrong" if not name in sysconf_names: raise NotImplementedError(f'name type {name} not implemented') ql.log.debug(f'msg_sys_conf(subtype = {sysconf_subtypes[subtype]}, cmd = {sysconf_conditions[cmd]}, name = {sysconf_names[name]}, spare = {spare}, value = {value})') # get string if name == 200: # == _CS_LIBPATH retstr = "/usr/lib\0" elif name == 203: # == _CS_TIMEZONE retstr = "UTC\0" else: raise NotImplementedError("sys_conf name not implemented") # first iov_t iov_base = ql.unpack32(ql.mem.read(rmsg, 4)) iov_len = ql.unpack32(ql.mem.read(rmsg + 4, 4)) ql.mem.write(iov_base, pack("<IIIiq", 0, 0, 0, 0, len(retstr))) if value != 0: # second iov_t iov_base = ql.unpack32(ql.mem.read(rmsg + 8, 4)) iov_len = ql.unpack32(ql.mem.read(rmsg + 12, 4)) ql.mem.write(iov_base, retstr.encode("utf-8")) # sys_conf(_SYS_SUB_GET, _CONF_NUM, *) in lib/c/1/sysconf.c elif subtype == 0 and cmd == (2 << 20): # check parameters assert (c_int32(rparts).value) == (-24), "output size is wrong" if not name in sysconf_consts: raise NotImplementedError(f'name type {name} not implemented') ql.log.debug(f'msg_sys_conf(subtype = {sysconf_subtypes[subtype]}, cmd = {sysconf_conditions[cmd]}, name = {sysconf_consts[name]}, spare = {spare}, value = {value})') # get value if name == 11: # == _SC_PAGESIZE retval = PAGESIZE else: raise NotImplementedError("sys_conf name not implemented") # struct _sys_conf_reply in services/system/public/sys/sysmsg.h ql.mem.write(rmsg, pack("<IIIiq", 0, 0, 0, 0, retval)) else: raise NotImplementedError("sys_conf message not implemented") return 0
def enable_history_trace(ql: Qiling, nrecords: int): """Enable instruction-level tracing in history mode. To allow faster execution, the trace info collected throughout program execution is not emitted and undergo as minimal post-processing as possible. When program crahses, the last `nrecords` trace lines are shown. Args: ql: qiling instance nrecords: number of last records to show """ # enable detailed disassembly info md = ql.create_disassembler() md.detail = True # if available, use symbols map to resolve memory accesses symsmap = getattr(ql.loader, 'symsmap', {}) # wrap the trace records list to allow it to be passed and modified by-ref history: UserList[TraceRecord] = UserList() def __trace_hook(ql: Qiling, address: int, size: int): """[internal] Trace hook callback. """ recent = list(__get_trace_records(ql, address, size, md)) history.data = (history + recent)[-nrecords:] ql.hook_code(__trace_hook) # replace the emulation error handler with our own so we can emit the trace # records when program crashes. before we do that, we save the original one # so we can call it. orig_emu_error = ql.os.emu_error def __emu_error(*args): # first run the original emulation error handler orig_emu_error(*args) # then parse and emit the trace info we collected ql.log.error(f'History:') for record in history: line = __to_trace_line(record, symsmap) ql.log.error(line) ql.log.error(f'') ql.os.emu_error = __emu_error
def our_sandbox(path, rootfs): ql = Qiling(path, rootfs) ql.patch(0x004010B5, b'\x90\x90') ql.patch(0x004010CD, b'\x90\x90') ql.patch(0x0040110B, b'\x90\x90') ql.patch(0x00401112, b'\x90\x90') ql.os.stdin = pipe.SimpleStringBuffer() ql.os.stdin.write(b"Ea5yR3versing\n") ql.hook_address(force_call_dialog_func, 0x00401016) ql.run() del ql
def ql_syscall_rt_sigaction(ql: Qiling, signum: int, act: int, oldact: int): if oldact: if ql.os.sigaction_act[signum] == 0: data = b'\x00' * 20 else: data = b''.join(ql.pack32(key) for key in ql.os.sigaction_act[signum]) ql.mem.write(oldact, data) if act: ql.os.sigaction_act[signum] = [ql.unpack32(ql.mem.read(act + 4 * key, 4)) for key in range(5)] return 0
def __init__(self, ql: Qiling, data: bytes) -> None: assert data[0:2] == b'MZ' nbytes = ql.unpack16( data[2:4] ) or 0x200 # number of bytes in last block; 0 means it is fully populated nblocks = ql.unpack16(data[4:6]) # number of blocks used self.size = (nblocks - 1) * 0x200 + nbytes self.init_ss = ql.unpack16(data[14:16]) self.init_sp = ql.unpack16(data[16:18]) self.init_ip = ql.unpack16(data[20:22]) self.init_cs = ql.unpack16(data[22:24])
def enable_history_trace(ql: Qiling, nrecords: int): """Enable instruction-level tracing in history mode. To allow faster execution, the trace info collected throughout program execution is not emitted and undergo as minimal post-processing as possible. When program crahses, the last `nrecords` trace lines are shown. Args: ql: qiling instance nrecords: number of last records to show """ # enable detailed disassembly info md = ql.create_disassembler() md.detail = True assert md.arch == CS_ARCH_X86, 'currently available only for intel architecture' # if available, use symbols map to resolve memory accesses symsmap = getattr(ql.loader, 'symsmap', {}) history: Deque[TraceRecord] = deque(maxlen=nrecords) def __trace_hook(ql: Qiling, address: int, size: int): """[internal] Trace hook callback. """ history.extend(__get_trace_records(ql, address, size, md)) ql.hook_code(__trace_hook) # replace the emulation error handler with our own so we can emit the trace # records when program crashes. before we do that, we save the original one # so we can call it. orig_emu_error = ql.os.emu_error def __emu_error(*args): # first run the original emulation error handler orig_emu_error(*args) # then parse and emit the trace info we collected ql.log.error(f'History:') for record in history: line = __to_trace_line(record, symsmap) ql.log.error(line) ql.log.error(f'') ql.os.emu_error = __emu_error
def _t(): if 'QL_FAST_TEST' in os.environ: return ql = Qiling(["../examples/rootfs/x86_windows/bin/al-khaser.bin"], "../examples/rootfs/x86_windows") # The hooks are to remove the prints to file. It crashes. will debug why in the future def results(ql): if ql.reg.ebx == 1: print("BAD") else: print("GOOD ") ql.reg.eip = 0x402ee4 #ql.hook_address(results, 0x00402e66) # the program alloc 4 bytes and then tries to write 0x2cc bytes. # I have no idea of why this code should work without this patch ql.patch(0x00401984, b'\xb8\x04\x00\x00\x00') def end(ql): print("We are finally done") ql.emu_stop() ql.hook_address(end, 0x004016ae) ql.run() del ql return True
def ql_syscall_lseek(ql: Qiling, fd: int, offset: int, lseek_origin: int): if 0 <= fd < NR_OPEN and ql.os.fd[fd] != 0: offset = ql.unpacks(ql.pack(offset)) try: regreturn = ql.os.fd[fd].seek(offset, lseek_origin) except OSError: regreturn = -1 else: regreturn = -EBADF # ql.log.debug("lseek(fd = %d, ofset = 0x%x, origin = 0x%x) = %d" % (lseek_fd, lseek_ofset, lseek_origin, regreturn)) return regreturn
def test_elf_hijackapi_linux_x8664(self): def my_puts_enter(ql: Qiling): params = ql.os.resolve_fcall_params(ELFTest.PARAMS_PUTS) self.test_enter_str = params["s"] def my_puts_exit(ql): self.test_exit_rdi = ql.reg.rdi ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_puts"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG) ql.set_api('puts', my_puts_enter, QL_INTERCEPT.ENTER) ql.set_api('puts', my_puts_exit, QL_INTERCEPT.EXIT) ql.run() if self.test_exit_rdi == 140736282240864: self.test_exit_rdi = 0x1 self.assertEqual(0x1, self.test_exit_rdi) self.assertEqual("CCCC", self.test_enter_str) del self.test_exit_rdi del self.test_enter_str del ql
def test_elf_linux_x8664(self): def my_puts(ql: Qiling): params = ql.os.resolve_fcall_params(ELFTest.PARAMS_PUTS) print(f'puts("{params["s"]}")') reg = ql.reg.read("rax") print("reg : %#x" % reg) self.set_api = reg def write_onEnter(ql: Qiling, fd: int, str_ptr: int, str_len: int, *args): self.set_api_onenter = True print("enter write syscall!") # override syscall pc (ignored) and set of params with our own return None, (fd, str_ptr + 1, str_len - 1) def write_onexit(ql: Qiling, fd: int, str_ptr: int, str_len: int, retval: int, *args): self.set_api_onexit = True print("exit write syscall!") # override syscall return value with our own return str_len + 1 ql = Qiling([ "../examples/rootfs/x8664_linux/bin/x8664_args", "1234test", "12345678", "bin/x8664_hello" ], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG) ql.set_syscall(1, write_onEnter, QL_INTERCEPT.ENTER) ql.set_api('puts', my_puts) ql.set_syscall(1, write_onexit, QL_INTERCEPT.EXIT) ql.mem.map(0x1000, 0x1000) ql.mem.write(0x1000, b"\xFF\xFE\xFD\xFC\xFB\xFA\xFB\xFC\xFC\xFE\xFD") ql.mem.map(0x2000, 0x1000) ql.mem.write(0x2000, b"\xFF\xFE\xFD\xFC\xFB\xFA\xFB\xFC\xFC\xFE\xFD") ql.run() self.assertEqual( [0x1000, 0x2000], ql.mem.search(b"\xFF\xFE\xFD\xFC\xFB\xFA\xFB\xFC\xFC\xFE\xFD")) self.assertEqual(0x5555555546ca, self.set_api) self.assertEqual(True, self.set_api_onexit) self.assertEqual(True, self.set_api_onenter) del self.set_api del self.set_api_onexit del self.set_api_onenter del ql
def __leaf_53(ql: Qiling): al = ql.arch.regs.al if al == 0x01: ql.os.clear_cf() elif al == 0x0e: ql.arch.regs.ax = 0x0102 ql.os.clear_cf() elif al == 0x07: if (ql.arch.regs.bx == 1) and (ql.arch.regs.cx == 3): ql.log.info("Emulation Stop") ql.emu_stop() else: raise NotImplementedError()
def test_x86_fake_urandom(self): class Fake_urandom(QlFsMappedObject): def read(self, size): return b"\x01" def fstat(self): return -1 def close(self): return 0 ql = Qiling(["../examples/rootfs/x86_linux/bin/x86_fetch_urandom"], "../examples/rootfs/x86_linux", verbose=QL_VERBOSE.DEBUG) ql.add_fs_mapper("/dev/urandom", Fake_urandom()) ql.exit_code = 0 ql.exit_group_code = 0 def check_exit_group_code(ql, exit_code, *args, **kw): ql.exit_group_code = exit_code def check_exit_code(ql, exit_code, *args, **kw): ql.exit_code = exit_code ql.os.set_syscall("exit_group", check_exit_group_code, QL_INTERCEPT.ENTER) ql.os.set_syscall("exit", check_exit_code, QL_INTERCEPT.ENTER) ql.run() self.assertEqual(0, ql.exit_code) self.assertEqual(0, ql.exit_group_code) del ql
def test_gdbdebug_mips32(self): ql = Qiling(["../examples/rootfs/mips32_linux/bin/mips32_hello"], "../examples/rootfs/mips32_linux", verbose=QL_VERBOSE.DEBUG) ql.debugger = True # some random command test just to make sure we covered most of the command def gdb_test_client(): # yield to allow ql to launch its gdbserver time.sleep(1.337 * 2) with SimpleGdbClient('127.0.0.1', 9999) as client: client.send( 'qSupported:multiprocess+;swbreak+;hwbreak+;qRelocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+;xmlRegisters=i386' ) client.send('vMustReplyEmpty') client.send('QStartNoAckMode') client.send('Hgp0.0') client.send('qXfer:auxv:read::0, 1000') client.send('?') client.send('qXfer:threads:read::0,fff') client.send(f'qAttached:{ql.os.pid}') client.send('qC') client.send('g') client.send('m47ccd10,4') client.send('qXfer:threads:read::0,1000') client.send('m56555620,4') client.send('m5655561c,4') client.send('m56555620,4') client.send('m5655561c,4') client.send('m56555620,4') client.send('qTStatus') client.send('qTfP') client.send('m56555600,40') client.send('m56555620,4') client.send('Z0,47ccd10,4') client.send( 'QPassSignals:e;10;14;17;1a;1b;1c;21;24;25;2c;4c;97;') client.send('vCont?') client.send('vCont;c:pa410.-1') client.send('c') client.send('k') # yield to make sure ql gdbserver has enough time to receive our last command time.sleep(1.337) threading.Thread(target=gdb_test_client, daemon=True).start() ql.run() del ql