From edd446e8a1015c2d4c0271b60c1f82b4a8268fc9 Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 10 Jan 2022 11:29:17 +0200 Subject: [PATCH 1/6] Prevent tracing from being used on non-intel archs --- qiling/extensions/trace.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/qiling/extensions/trace.py b/qiling/extensions/trace.py index e2ddbd188..121a8678a 100644 --- a/qiling/extensions/trace.py +++ b/qiling/extensions/trace.py @@ -154,6 +154,8 @@ def enable_full_trace(ql: Qiling): 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', {}) @@ -189,6 +191,8 @@ def enable_history_trace(ql: Qiling, nrecords: int): 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', {}) From 2079f9f0dbdfab4508c6488f121b8f83904ea303 Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 10 Jan 2022 11:30:22 +0200 Subject: [PATCH 2/6] Add "fword ptr" notation --- qiling/extensions/trace.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qiling/extensions/trace.py b/qiling/extensions/trace.py index 121a8678a..5e1266ecc 100644 --- a/qiling/extensions/trace.py +++ b/qiling/extensions/trace.py @@ -125,6 +125,7 @@ def __parse_op(op: X86Op) -> str: 2: 'word', 4: 'dword', 8: 'qword', + 10: 'fword', 16: 'xmmword' }[op.size] From b59818f4d1f7614d736dbf1d1bb0af283aa3e0fb Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 10 Jan 2022 11:30:58 +0200 Subject: [PATCH 3/6] Comments and other minor changes --- qiling/extensions/trace.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/qiling/extensions/trace.py b/qiling/extensions/trace.py index 5e1266ecc..d2d7692d0 100644 --- a/qiling/extensions/trace.py +++ b/qiling/extensions/trace.py @@ -2,11 +2,10 @@ # More info, please refer to https://github.com/qilingframework/qiling/pull/765 - from collections import deque from typing import Deque, Iterable, Iterator, Mapping, Tuple -from capstone import Cs, CsInsn, CS_OP_IMM, CS_OP_MEM, CS_OP_REG +from capstone import Cs, CsInsn, CS_ARCH_X86, CS_OP_IMM, CS_OP_MEM, CS_OP_REG from capstone.x86 import X86Op from capstone.x86_const import X86_INS_LEA, X86_REG_INVALID, X86_REG_RIP @@ -16,7 +15,7 @@ # def __uc2_workaround() -> Mapping[int, int]: - """Starting from Unicron2, Unicron and Capstone Intel registers definitions are + """Starting from Unicorn2, Unicorn and Capstone Intel registers definitions are no longer aligned and cannot be used interchangebly. This temporary workaround maps capstone x86 registers definitions to unicorn x86 registers definitions. @@ -47,6 +46,7 @@ def __get_trace_records(ql: Qiling, address: int, size: int, md: Cs) -> Iterator # unicorn denotes unsupported instructions by a magic size value. though these instructions # are not emulated, capstone can still parse them. if size == 0xf1f1f1f1: + # note that invalid instructions will generate a StopIteration exception here yield next(__get_trace_records(ql, address, 16, md)) return @@ -162,8 +162,8 @@ def enable_full_trace(ql: Qiling): # show trace lines in a darker color so they would be easily distinguished from # ordinary log records - DarkGray = "\x1b[90m" - Default = "\x1b[39m" + faded_color = "\033[2m" + reset_color = "\033[0m" def __trace_hook(ql: Qiling, address: int, size: int): """[internal] Trace hook callback. @@ -172,7 +172,7 @@ def __trace_hook(ql: Qiling, address: int, size: int): for record in __get_trace_records(ql, address, size, md): line = __to_trace_line(record, symsmap) - ql.log.debug(f'{DarkGray}{line}{Default}') + ql.log.debug(f'{faded_color}{line}{reset_color}') ql.hook_code(__trace_hook) From b25cbe176201bb0e814cbeb319e794c998e8a049 Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 10 Jan 2022 11:32:20 +0200 Subject: [PATCH 4/6] Unpack zip generator into a tuple for robustness --- qiling/os/fcall.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiling/os/fcall.py b/qiling/os/fcall.py index 1a4cfd54b..f8aa1960e 100644 --- a/qiling/os/fcall.py +++ b/qiling/os/fcall.py @@ -114,7 +114,7 @@ def __get_typed_args(proto: Mapping[str, Any], args: Mapping[str, Any]) -> Itera if len(names) > len(types): types.extend([None] * (len(names) - len(types))) - return zip(types, names, values) + return tuple(zip(types, names, values)) def call(self, func: CallHook, proto: Mapping[str, Any], params: Mapping[str, Any], hook_onenter: Optional[OnEnterHook], hook_onexit: Optional[OnExitHook], passthru: bool) -> Tuple[Iterable[TypedArg], int, int]: """Execute a hooked function. From adda4c73bed1ef01eb28348a37ac9c1b463904a4 Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 10 Jan 2022 11:33:28 +0200 Subject: [PATCH 5/6] Slightly improve string extraction performance --- qiling/os/utils.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/qiling/os/utils.py b/qiling/os/utils.py index f525276f7..06dd02ba8 100644 --- a/qiling/os/utils.py +++ b/qiling/os/utils.py @@ -68,21 +68,21 @@ def string_appearance(self, s: str) -> None: self.appeared_strings.setdefault(token, set()).add(self.syscalls_counter) @staticmethod - def read_string(ql: Qiling, address: int, terminator: str) -> str: - result = "" + def read_string(ql: Qiling, address: int, terminator: bytes) -> str: + result = bytearray() charlen = len(terminator) char = ql.mem.read(address, charlen) - while char.decode(errors="ignore") != terminator: + while char != terminator: address += charlen - result += char.decode(errors="ignore") + result += char char = ql.mem.read(address, charlen) - return result + return result.decode(errors="ignore") def read_wstring(self, address: int) -> str: - s = QlOsUtils.read_string(self.ql, address, '\x00\x00') + s = QlOsUtils.read_string(self.ql, address, b'\x00\x00') # We need to remove \x00 inside the string. Compares do not work otherwise s = s.replace("\x00", "") @@ -91,7 +91,7 @@ def read_wstring(self, address: int) -> str: return s def read_cstring(self, address: int) -> str: - s = QlOsUtils.read_string(self.ql, address, '\x00') + s = QlOsUtils.read_string(self.ql, address, b'\x00') self.string_appearance(s) From f6c1ab5f56817c36c737c43a21e7d50353ada1b1 Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 10 Jan 2022 11:37:31 +0200 Subject: [PATCH 6/6] Fix unreliable RaiseException code (untested) --- qiling/os/utils.py | 21 ------------------- .../windows/dlls/kernel32/errhandlingapi.py | 5 +---- qiling/os/windows/utils.py | 15 ------------- 3 files changed, 1 insertion(+), 40 deletions(-) diff --git a/qiling/os/utils.py b/qiling/os/utils.py index 06dd02ba8..a5c3294c3 100644 --- a/qiling/os/utils.py +++ b/qiling/os/utils.py @@ -184,24 +184,3 @@ def printf(self, format: str, args: MutableSequence, wstring: bool = False) -> i def update_ellipsis(self, params: MutableMapping, args: Sequence) -> None: params.update((f'{QlOsUtils.ELLIPSIS_PREF}{i}', a) for i, a in enumerate(args)) - - def exec_arbitrary(self, start: int, end: int): - old_sp = self.ql.reg.arch_sp - - # we read where this hook is supposed to return - ret = self.ql.stack_read(0) - - def restore(ql: Qiling): - self.ql.log.debug(f"Executed code from {start:#x} to {end:#x}") - # now we can restore the register to be where we were supposed to - ql.reg.arch_sp = old_sp + ql.pointersize - ql.reg.arch_pc = ret - - # we want to execute the code once, not more - hret.remove() - - # we have to set an address to restore the registers - hret = self.ql.hook_address(restore, end) - # we want to rewrite the return address to the function - self.ql.stack_write(0, start) - diff --git a/qiling/os/windows/dlls/kernel32/errhandlingapi.py b/qiling/os/windows/dlls/kernel32/errhandlingapi.py index 44f05ef44..3926a2730 100644 --- a/qiling/os/windows/dlls/kernel32/errhandlingapi.py +++ b/qiling/os/windows/dlls/kernel32/errhandlingapi.py @@ -75,9 +75,7 @@ def hook_SetErrorMode(ql: Qiling, address: int, params): def hook_RaiseException(ql: Qiling, address: int, params): func_addr = ql.os.handle_manager.search("TopLevelExceptionHandler").obj - # TODO: this implementation won't work most of the time - size = find_size_function(ql, func_addr) - ql.os.exec_arbitrary(func_addr, func_addr + size) + ql.os.fcall.call_native(func_addr, [], None) return 0 @@ -122,7 +120,6 @@ def exec_standard_into(ql: Qiling, intno: int, user_data): ql.reg.esi = user_data addr = params["Handler"] - #size = find_size_function(ql, addr) # the interrupts 0x2d, 0x3 must be hooked hook = ql.hook_intno(exec_standard_into, 0x3, user_data=addr) diff --git a/qiling/os/windows/utils.py b/qiling/os/windows/utils.py index 7f874161a..6b6cf6a77 100644 --- a/qiling/os/windows/utils.py +++ b/qiling/os/windows/utils.py @@ -37,21 +37,6 @@ def path_leaf(path): head, tail = ntpath.split(path) return tail or ntpath.basename(head) -# FIXME: determining a function size by locating 'ret' opcodes in its code is a very unreliable way, to say -# the least. not only that 'ret' instructions may appear more than once in a single function, they not are -# necessarily located at the last function basic block: think of a typical nested loop spaghetty. -# -# also, there is no telling whether a 0xC3 value found in function code is actually a 'ret' instruction, or -# just part of a magic value (e.g. "mov eax, 0xffffffc3"). -# -# finally, if this method happens to find the correct function size, by any chance, that would be a pure luck. -def find_size_function(ql: Qiling, func_addr: int): - # We have to retrieve the return address position - code = ql.mem.read(func_addr, 0x100) - return_procedures = [b"\xc3", b"\xc2", b"\xcb", b"\xca"] - min_index = min([code.index(return_value) for return_value in return_procedures if return_value in code]) - return min_index - def io_Write(ql: Qiling, in_buffer: bytes): heap = ql.os.heap