#!/usr/bin/env python3 # # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # """ This module is intended for general purpose functions that are only used in qiling.os """ from typing import Callable, Iterable, Iterator, List, MutableMapping, Sequence, Tuple, TypeVar, Union from uuid import UUID from qiling import Qiling from qiling.const import QL_VERBOSE # TODO: separate windows-specific implementation from qiling.os.windows.structs import make_unicode_string class QlOsUtils: ELLIPSIS_PREF = r'__qlva_' def __init__(self, ql: Qiling): self.ql = ql def read_string(self, address: int, encoding: str, maxlen: int = 0) -> str: """Read a null-terminated string from memory. Args: address : starting address encoding: string encoding to use maxlen : limit number of characters to read before reaching null terminator, 0 for unlimited length Returns: decoded string """ terminator = '\x00'.encode(encoding) data = bytearray() charlen = len(terminator) strlen = 0 while True: char = self.ql.mem.read(address, charlen) if char == terminator: break data += char strlen += 1 if strlen == maxlen: break address += charlen s = data.decode(encoding, errors='backslashreplace') self.ql.os.stats.log_string(s) return s def read_wstring(self, address: int, maxlen: int = 0) -> str: """Read a null-terminated wide string from memory. """ return self.read_string(address, 'utf-16le', maxlen) def read_cstring(self, address: int, maxlen: int = 0) -> str: """Read a null-terminated ASCII string from memory. """ return self.read_string(address, 'latin1', maxlen) def read_guid(self, address: int) -> UUID: raw_guid = self.ql.mem.read(address, 16) return UUID(bytes_le=bytes(raw_guid)) @staticmethod def stringify(s: str) -> str: """Decorate a string with quotation marks. """ return f'"{repr(s)[1:-1]}"' def print_function(self, address: int, fname: str, pargs: Sequence[Tuple[str, str]], ret: Union[int, str, None], passthru: bool): '''Print out function invocation detais. Args: address: fucntion address fnamr: function name pargs: processed args list: a sequence of 2-tuples consisting of arg names paired to string representation of arg values ret: function return value, or None if no such value passthru: whether this is a passthrough invocation (no frame unwinding) ''' if fname.startswith('hook_'): fname = fname[5:] def __assign_arg(name: str, value: str) -> str: # ignore arg names generated by variadric functions if name.startswith(QlOsUtils.ELLIPSIS_PREF): name = '' return f'{name} = {value}' if name else f'{value}' # arguments list fargs = ', '.join(__assign_arg(name, value) for name, value in pargs) if type(ret) is int: ret = f'{ret:#x}' # optional prefixes and suffixes fret = f' = {ret}' if ret is not None else '' fpass = f' (PASSTHRU)' if passthru else '' faddr = f'{address:#0{self.ql.arch.bits // 4 + 2}x}: ' if self.ql.verbose >= QL_VERBOSE.DEBUG else '' log = f'{faddr}{fname}({fargs}){fret}{fpass}' if self.ql.verbose >= QL_VERBOSE.DEBUG: self.ql.log.debug(log) else: self.ql.log.info(log) def __common_printf(self, format: str, va_args: Iterator[int], wstring: bool) -> Tuple[str, Sequence[int]]: import re # https://docs.microsoft.com/en-us/cpp/c-runtime-library/format-specification-syntax-printf-and-wprintf-functions # %[flags][width][.precision][size]type fmtstr = re.compile(r'''% (?P%| (?P[-+0 #]+)? (?P[*]|[0-9]+)? (?:.(?P[*]|[0-9]+))? (?Phh|ll|I32|I64|[hjltwzIL])? (?P[diopuaAcCeEfFgGsSxXZ]) ) ''', re.VERBOSE) T = TypeVar('T') def __dup(iterator: Iterator[T], out: List[T]) -> Iterator[T]: """A wrapper iterator to record iterator elements as they are being yielded. """ for elem in iterator: out.append(elem) yield elem repl_args = [] # processed arguments orig_args = [] # original arguments va_list = __dup(va_args, orig_args) read_string = self.read_wstring if wstring else self.read_cstring def __repl(m: re.Match) -> str: """Convert printf format string tokens into Python's. """ if m['follows'] == '%': return '%%' else: flags = m['flags'] or '' fill = ' ' if ' ' in flags else '' align = '<' if '-' in flags else '' sign = '+' if '+' in flags else '' pound = '#' if '#' in flags else '' zeros = '0' if '0' in flags else '' width = m['width'] or '' if width == '*': width = f'{next(va_list)}' prec = m['precision'] or '' if prec == '*': prec = f'{next(va_list)}' if prec: prec = f'.{prec}' typ = m['type'] arg = next(va_list) if typ in 'sS': typ = 's' arg = read_string(arg) elif typ == 'Z': # note: ANSI_STRING and UNICODE_STRING have identical layout ucstr_struct = make_unicode_string(self.ql.arch.bits) with ucstr_struct.ref(self.ql.mem, arg) as ucstr_obj: typ = 's' arg = read_string(ucstr_obj.Buffer) elif typ == 'p': pound = '#' typ = 'x' repl_args.append(arg) return f'%{fill}{align}{sign}{pound}{zeros}{width}{prec}{typ}' out = fmtstr.sub(__repl, format) return out % tuple(repl_args), orig_args def va_list(self, ptr: int) -> Iterator[int]: while True: yield self.ql.mem.read_ptr(ptr) ptr += self.ql.arch.pointersize def sprintf(self, buff: int, format: str, va_args: Iterator[int], wstring: bool = False) -> Tuple[int, Callable]: out, args = self.__common_printf(format, va_args, wstring) enc = 'utf-16le' if wstring else 'utf-8' self.ql.mem.write(buff, (out + '\x00').encode(enc)) return len(out), self.__update_ellipsis(args) def printf(self, format: str, va_args: Iterator[int], wstring: bool = False) -> Tuple[int, Callable]: out, args = self.__common_printf(format, va_args, wstring) enc = 'utf-8' self.ql.os.stdout.write(out.encode(enc)) return len(out), self.__update_ellipsis(args) def __update_ellipsis(self, args: Iterable[int]) -> Callable[[MutableMapping], None]: def __do_update(params: MutableMapping) -> None: params.update((f'{QlOsUtils.ELLIPSIS_PREF}{i}', a) for i, a in enumerate(args)) return __do_update