diff --git a/examples/sality.py b/examples/sality.py index b07c4273d..22d6f6515 100644 --- a/examples/sality.py +++ b/examples/sality.py @@ -119,16 +119,16 @@ def hook_WriteFile(ql: Qiling, address: int, params): if hFile == 0x13371337: buffer = ql.mem.read(lpBuffer, nNumberOfBytesToWrite) try: - r, nNumberOfBytesToWrite = utils.io_Write(ql.amsint32_driver, buffer) + nNumberOfBytesToWrite = utils.io_Write(ql.amsint32_driver, buffer) ql.mem.write_ptr(lpNumberOfBytesWritten, nNumberOfBytesToWrite, 4) - except Exception as e: + except Exception: ql.log.exception("") - print("Exception = %s" % str(e)) - r = 1 - if r: - return 1 + r = False else: - return 0 + r = True + + return int(r) + else: return _WriteFile(ql, address, params) diff --git a/qiling/loader/pe.py b/qiling/loader/pe.py index ea740da1c..724a33a9a 100644 --- a/qiling/loader/pe.py +++ b/qiling/loader/pe.py @@ -335,68 +335,66 @@ def set_cmdline(self, name: bytes, address: int, memory: bytearray): return {"name": name, "address": address} def init_teb(self): - teb_obj = make_teb(self.ql.arch.bits) + teb_struct = make_teb(self.ql.arch.bits) teb_addr = self.structure_last_addr - peb_addr = self.ql.mem.align_up(teb_addr + ctypes.sizeof(teb_obj), 0x10) + peb_addr = self.ql.mem.align_up(teb_addr + teb_struct.sizeof(), 0x10) + teb_obj = teb_struct.volatile_ref(self.ql.mem, teb_addr) teb_obj.StackBase = self.stack_address + self.stack_size teb_obj.StackLimit = self.stack_address teb_obj.TebAddress = teb_addr teb_obj.PebAddress = peb_addr self.ql.log.info(f'TEB is at {teb_addr:#x}') - self.ql.mem.write(teb_addr, bytes(teb_obj)) self.structure_last_addr = peb_addr self.TEB = teb_obj def init_peb(self): - peb_obj = make_peb(self.ql.arch.bits) + peb_struct = make_peb(self.ql.arch.bits) peb_addr = self.structure_last_addr - ldr_addr = self.ql.mem.align_up(peb_addr + ctypes.sizeof(peb_obj), 0x10) + ldr_addr = self.ql.mem.align_up(peb_addr + peb_struct.sizeof(), 0x10) - # we must set an heap, will try to retrieve this value. Is ok to be all \x00 + # we must set a heap, will try to retrieve this value. Is ok to be all \x00 + peb_obj = peb_struct.volatile_ref(self.ql.mem, peb_addr) + peb_obj.ImageBaseAddress = self.pe_image_address peb_obj.LdrAddress = ldr_addr peb_obj.ProcessParameters = self.ql.os.heap.alloc(0x100) peb_obj.ProcessHeap = self.ql.os.heap.alloc(0x100) peb_obj.NumberOfProcessors = self.ql.os.profile.getint('HARDWARE', 'number_processors') self.ql.log.info(f'PEB is at {peb_addr:#x}') - self.ql.mem.write(peb_addr, bytes(peb_obj)) self.structure_last_addr = ldr_addr self.PEB = peb_obj def init_ldr_data(self): - ldr_obj = make_ldr_data(self.ql.arch.bits) - ldr_cls = ldr_obj.__class__ + ldr_struct = make_ldr_data(self.ql.arch.bits) ldr_addr = self.structure_last_addr - nobj_addr = self.ql.mem.align_up(ldr_addr + ctypes.sizeof(ldr_obj), 0x10) + nobj_addr = self.ql.mem.align_up(ldr_addr + ldr_struct.sizeof(), 0x10) - ldr_obj.InLoadOrderModuleList.Flink = ldr_addr + ldr_cls.InLoadOrderModuleList.offset - ldr_obj.InLoadOrderModuleList.Blink = ldr_addr + ldr_cls.InLoadOrderModuleList.offset + ldr_obj = ldr_struct.volatile_ref(self.ql.mem, ldr_addr) + ldr_obj.InLoadOrderModuleList.Flink = ldr_addr + ldr_struct.InLoadOrderModuleList.offset + ldr_obj.InLoadOrderModuleList.Blink = ldr_addr + ldr_struct.InLoadOrderModuleList.offset - ldr_obj.InMemoryOrderModuleList.Flink = ldr_addr + ldr_cls.InMemoryOrderModuleList.offset - ldr_obj.InMemoryOrderModuleList.Blink = ldr_addr + ldr_cls.InMemoryOrderModuleList.offset + ldr_obj.InMemoryOrderModuleList.Flink = ldr_addr + ldr_struct.InMemoryOrderModuleList.offset + ldr_obj.InMemoryOrderModuleList.Blink = ldr_addr + ldr_struct.InMemoryOrderModuleList.offset - ldr_obj.InInitializationOrderModuleList.Flink = ldr_addr + ldr_cls.InInitializationOrderModuleList.offset - ldr_obj.InInitializationOrderModuleList.Blink = ldr_addr + ldr_cls.InInitializationOrderModuleList.offset + ldr_obj.InInitializationOrderModuleList.Flink = ldr_addr + ldr_struct.InInitializationOrderModuleList.offset + ldr_obj.InInitializationOrderModuleList.Blink = ldr_addr + ldr_struct.InInitializationOrderModuleList.offset self.ql.log.info(f'LDR is at {ldr_addr:#x}') - self.ql.mem.write(ldr_addr, bytes(ldr_obj)) self.structure_last_addr = nobj_addr self.LDR = ldr_obj def add_ldr_data_table_entry(self, dll_name: str): - entry_obj = make_ldr_data_table_entry(self.ql.arch.bits) - entry_cls = entry_obj.__class__ + entry_struct = make_ldr_data_table_entry(self.ql.arch.bits) - entry_size = ctypes.sizeof(entry_obj) - entry_addr = self.ql.os.heap.alloc(entry_size) + entry_addr = self.ql.os.heap.alloc(entry_struct.sizeof()) def populate_unistr(obj, s: str) -> None: encoded = s.encode('utf-16le') @@ -412,50 +410,47 @@ def populate_unistr(obj, s: str) -> None: image = self.get_image_by_name(dll_name, casefold=True) assert image, 'image should have been added to loader.images first' - entry_obj.DllBase = image.base - populate_unistr(entry_obj.FullDllName, ntpath.join(self.ql.os.winsys, dll_name)) - populate_unistr(entry_obj.BaseDllName, dll_name) + with entry_struct.ref(self.ql.mem, entry_addr) as entry_obj: + entry_obj.DllBase = image.base + populate_unistr(entry_obj.FullDllName, ntpath.join(self.ql.os.winsys, dll_name)) + populate_unistr(entry_obj.BaseDllName, dll_name) - # Flink - if self.ldr_list: - flink_base, flink = self.ldr_list[-1] + # Flink + if self.ldr_list: + with entry_struct.ref(self.ql.mem, self.ldr_list[-1]) as flink: + entry_obj.InLoadOrderLinks.Flink = flink.InLoadOrderLinks.Flink + entry_obj.InMemoryOrderLinks.Flink = flink.InMemoryOrderLinks.Flink + entry_obj.InInitializationOrderLinks.Flink = flink.InInitializationOrderLinks.Flink - entry_obj.InLoadOrderLinks.Flink = flink.InLoadOrderLinks.Flink - entry_obj.InMemoryOrderLinks.Flink = flink.InMemoryOrderLinks.Flink - entry_obj.InInitializationOrderLinks.Flink = flink.InInitializationOrderLinks.Flink + flink.InLoadOrderLinks.Flink = entry_addr + entry_struct.InLoadOrderLinks.offset + flink.InMemoryOrderLinks.Flink = entry_addr + entry_struct.InMemoryOrderLinks.offset + flink.InInitializationOrderLinks.Flink = entry_addr + entry_struct.InInitializationOrderLinks.offset - flink.InLoadOrderLinks.Flink = entry_addr + entry_cls.InLoadOrderLinks.offset - flink.InMemoryOrderLinks.Flink = entry_addr + entry_cls.InMemoryOrderLinks.offset - flink.InInitializationOrderLinks.Flink = entry_addr + entry_cls.InInitializationOrderLinks.offset - - else: - flink_base, flink = (self.PEB.LdrAddress, self.LDR) + else: + # a volatile ref to self.PEB.LdrAddress + flink = self.LDR - entry_obj.InLoadOrderLinks.Flink = flink.InLoadOrderModuleList.Flink - entry_obj.InMemoryOrderLinks.Flink = flink.InMemoryOrderModuleList.Flink - entry_obj.InInitializationOrderLinks.Flink = flink.InInitializationOrderModuleList.Flink + entry_obj.InLoadOrderLinks.Flink = flink.InLoadOrderModuleList.Flink + entry_obj.InMemoryOrderLinks.Flink = flink.InMemoryOrderModuleList.Flink + entry_obj.InInitializationOrderLinks.Flink = flink.InInitializationOrderModuleList.Flink - flink.InLoadOrderModuleList.Flink = entry_addr + entry_cls.InLoadOrderLinks.offset - flink.InMemoryOrderModuleList.Flink = entry_addr + entry_cls.InMemoryOrderLinks.offset - flink.InInitializationOrderModuleList.Flink = entry_addr + entry_cls.InInitializationOrderLinks.offset + flink.InLoadOrderModuleList.Flink = entry_addr + entry_struct.InLoadOrderLinks.offset + flink.InMemoryOrderModuleList.Flink = entry_addr + entry_struct.InMemoryOrderLinks.offset + flink.InInitializationOrderModuleList.Flink = entry_addr + entry_struct.InInitializationOrderLinks.offset - # Blink - blink_base = self.PEB.LdrAddress - blink = self.LDR + # Blink + blink = self.LDR - entry_obj.InLoadOrderLinks.Blink = blink.InLoadOrderModuleList.Blink - entry_obj.InMemoryOrderLinks.Blink = blink.InMemoryOrderModuleList.Blink - entry_obj.InInitializationOrderLinks.Blink = blink.InInitializationOrderModuleList.Blink + entry_obj.InLoadOrderLinks.Blink = blink.InLoadOrderModuleList.Blink + entry_obj.InMemoryOrderLinks.Blink = blink.InMemoryOrderModuleList.Blink + entry_obj.InInitializationOrderLinks.Blink = blink.InInitializationOrderModuleList.Blink - blink.InLoadOrderModuleList.Blink = entry_addr + entry_cls.InLoadOrderLinks.offset - blink.InMemoryOrderModuleList.Blink = entry_addr + entry_cls.InMemoryOrderLinks.offset - blink.InInitializationOrderModuleList.Blink = entry_addr + entry_cls.InInitializationOrderLinks.offset + blink.InLoadOrderModuleList.Blink = entry_addr + entry_struct.InLoadOrderLinks.offset + blink.InMemoryOrderModuleList.Blink = entry_addr + entry_struct.InMemoryOrderLinks.offset + blink.InInitializationOrderModuleList.Blink = entry_addr + entry_struct.InInitializationOrderLinks.offset - self.ql.mem.write(flink_base, bytes(flink)) - self.ql.mem.write(blink_base, bytes(blink)) - self.ql.mem.write(entry_addr, bytes(entry_obj)) - self.ldr_list.append((entry_addr, entry_obj)) + self.ldr_list.append(entry_addr) @staticmethod def directory_exists(pe: pefile.PE, entry: str) -> bool: @@ -582,55 +577,68 @@ def init_driver_object(self): drv_addr = self.structure_last_addr # PDRIVER_OBJECT DriverObject - drv_obj = make_driver_object(self.ql, drv_addr, self.ql.arch.bits) - nobj_addr = self.ql.mem.align_up(drv_addr + ctypes.sizeof(drv_obj), 0x10) + drvobj_cls = make_driver_object(self.ql.arch.bits) + nobj_addr = self.ql.mem.align_up(drv_addr + drvobj_cls.sizeof(), 0x10) self.ql.log.info(f'DriverObject is at {drv_addr:#x}') - self.ql.mem.write(drv_addr, bytes(drv_obj)) + # note: driver object is volatile; no need to flush its contents to mem self.structure_last_addr = nobj_addr self.driver_object_address = drv_addr - self.driver_object = drv_obj + self.driver_object = drvobj_cls.volatile_ref(self.ql.mem, drv_addr) def init_registry_path(self): regpath_addr = self.structure_last_addr # PUNICODE_STRING RegistryPath - regpath_obj = make_unicode_string(self.ql.arch.bits, + ucstrtype = make_unicode_string(self.ql.arch.bits) + + regpath_obj = ucstrtype( Length=0, MaximumLength=0, - Buffer=regpath_addr + Buffer=regpath_addr # FIXME: pointing to self? this does not seem right ) - nobj_addr = self.ql.mem.align_up(regpath_addr + ctypes.sizeof(regpath_obj), 0x10) + nobj_addr = self.ql.mem.align_up(regpath_addr + ucstrtype.sizeof(), 0x10) self.ql.log.info(f'RegistryPath is at {regpath_addr:#x}') - self.ql.mem.write(regpath_addr, bytes(regpath_obj)) + regpath_obj.save_to(self.ql.mem, regpath_addr) self.structure_last_addr = nobj_addr self.regitry_path_address = regpath_addr def init_eprocess(self): - eproc_obj = make_eprocess(self.ql.arch.bits) - eproc_addr = self.structure_last_addr - nobj_addr = self.ql.mem.align_up(eproc_addr + ctypes.sizeof(eproc_obj), 0x10) - self.ql.mem.write(eproc_addr, bytes(eproc_obj)) + eproc_struct = make_eprocess(self.ql.arch.bits) + nobj_addr = self.ql.mem.align_up(eproc_addr + eproc_struct.sizeof(), 0x10) + + with eproc_struct.ref(self.ql.mem, eproc_addr) as eproc_obj: + eproc_obj.dummy = b'' self.structure_last_addr = nobj_addr self.eprocess_address = eproc_addr def init_ki_user_shared_data(self): - addr = self.ql.os.profile.getint(f'OS{self.ql.arch.bits}', 'KI_USER_SHARED_DATA') - - user_shared_data_obj = KUSER_SHARED_DATA() - user_shared_data_size = ctypes.sizeof(KUSER_SHARED_DATA) - - # TODO: initialize key fields in this structure - - self.ql.mem.map(addr, self.ql.mem.align_up(user_shared_data_size), info='[kuser shared]') - self.ql.mem.write(addr, bytes(user_shared_data_obj)) + sysconf = self.ql.os.profile['SYSTEM'] + osconf = self.ql.os.profile[f'OS{self.ql.arch.bits}'] + + kusd_addr = osconf.getint('KI_USER_SHARED_DATA') + kust_struct = KUSER_SHARED_DATA + self.ql.mem.map(kusd_addr, self.ql.mem.align_up(kust_struct.sizeof()), info='[kuser shared data]') + + # initialize an instance with a few key fields + kusd_obj = kust_struct.volatile_ref(self.ql.mem, kusd_addr) + kusd_obj.ImageNumberLow = 0x014c # IMAGE_FILE_MACHINE_I386 + kusd_obj.ImageNumberHigh = 0x8664 # IMAGE_FILE_MACHINE_AMD64 + kusd_obj.NtSystemRoot = self.ql.os.windir + kusd_obj.NtProductType = sysconf.getint('productType') + kusd_obj.NtMajorVersion = sysconf.getint('majorVersion') + kusd_obj.NtMinorVersion = sysconf.getint('minorVersion') + kusd_obj.KdDebuggerEnabled = 0 + kusd_obj.NXSupportPolicy = 0 # NX_SUPPORT_POLICY_ALWAYSOFF + + self.ql.os.KUSER_SHARED_DATA = kusd_obj def init_security_cookie(self, pe: pefile.PE, image_base: int): if not Process.directory_exists(pe, 'IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG'): diff --git a/qiling/os/struct.py b/qiling/os/struct.py new file mode 100644 index 000000000..31ed7dc7f --- /dev/null +++ b/qiling/os/struct.py @@ -0,0 +1,244 @@ +from __future__ import annotations + +import ctypes + +from contextlib import contextmanager +from functools import lru_cache +from typing import TYPE_CHECKING, Any, Iterator, Type, TypeVar, Optional + +if TYPE_CHECKING: + from qiling.os.memory import QlMemoryManager + + +class BaseStruct(ctypes.LittleEndianStructure): + """An abstract class for C structures. + """ + + T = TypeVar('T', bound='BaseStruct') + + def save_to(self, mem: QlMemoryManager, address: int) -> None: + """Store structure contents to a specified memory address. + + Args: + mem: memory manager instance + address: destination address + """ + + data = bytes(self) + + mem.write(address, data) + + @classmethod + def load_from(cls: Type[T], mem: QlMemoryManager, address: int) -> T: + """Construct and populate a structure from saved contents. + + Args: + mem: memory manager instance + address: source address + + Returns: populated structure instance + """ + + data = mem.read(address, cls.sizeof()) + + return cls.from_buffer(data) + + @classmethod + def volatile_ref(cls: Type[T], mem: QlMemoryManager, address: int) -> T: + """Refer to a memory location as a volatile structure variable. + + Args: + mem : memory manager instance + address : bind address + + Example: + >>> class Point(BaseStruct): + ... _fields_ = [ + ... ('x', ctypes.c_uint32), + ... ('y', ctypes.c_uint32) + ... ] + + >>> # bind a volatile Point structure to address `ptr` + >>> p = Point.volatile_ref(ql.mem, ptr) + ... if p.x > 10: # x value is read directly from memory + ... p.x = 10 # x value is written directly to memory + ... # y value in memory remains unchanged + >>> + """ + + # map all structure field names to their types + _fields = dict((fname, ftype) for fname, ftype, *_ in cls._fields_) + + class VolatileStructRef(cls): + """Turn a BaseStruct subclass into a volatile structure. + + Field values are never cached: when retrieving a field's value, its value + is read from memory and when setting a field's value, its value is flushed + to memory. + + This is useful to make sure a structure's fields are alway synced with memory. + """ + + def __getattribute__(self, name: str) -> Any: + # accessing a structure field? + if name in _fields: + field = cls.__dict__[name] + ftype = _fields[name] + + if issubclass(ftype, BaseStruct): + fvalue = ftype.volatile_ref(mem, address + field.offset) + + else: + # load field's bytes from memory and tranform them into a value + data = mem.read(address + field.offset, field.size) + fvalue = ftype.from_buffer(data) + + if hasattr(fvalue, 'value'): + fvalue = fvalue.value + + # set the value to the structure in order to maintain consistency with ctypes.Structure + super().__setattr__(name, fvalue) + return fvalue + + # return attribute value + return super().__getattribute__(name) + + def __setattr__(self, name: str, value: Any) -> None: + # accessing a structure field? + if name in _fields: + field = cls.__dict__[name] + ftype = _fields[name] + + # transform value into field bytes and write them to memory + fvalue = ftype(*value) if hasattr(ftype, '_length_') else ftype(value) + data = bytes(fvalue) + + mem.write(address + field.offset, data) + + # proceed to set the value to the structure in order to maintain consistency with ctypes.Structure + + # set attribute value + super().__setattr__(name, value) + + + return VolatileStructRef() + + @classmethod + @contextmanager + def ref(cls: Type[T], mem: QlMemoryManager, address: int) -> Iterator[T]: + """A structure context manager to facilitate updating structure contents. + + On context enter, a structure is created and populated from the specified memory + address. All changes to structure content are written back to memory on context + exit. If the structure content has not changed, no memory writes occur. + + Args: + mem : memory manager instance + address : bind address + + Example: + >>> class Point(BaseStruct): + ... _fields_ = [ + ... ('x', ctypes.c_uint32), + ... ('y', ctypes.c_uint32) + ... ] + + >>> # bind a Point structure to address `ptr` + >>> with Point.ref(ql.mem, ptr) as p: + ... p.x = 10 + ... p.y = 20 + >>> # p data has changed and will be written back to `ptr` + + >>> # bind a Point structure to address `ptr` + >>> with Point.ref(ql.mem, ptr) as p: + ... print(f'saved coordinates: {p.x}, {p.y}') + >>> # p data has not changed and nothing will be written back + """ + + instance = cls.load_from(mem, address) + orig_data = hash(bytes(instance)) + + try: + yield instance + finally: + curr_data = hash(bytes(instance)) + + if curr_data != orig_data: + instance.save_to(mem, address) + + @classmethod + def sizeof(cls) -> int: + """Get structure size in bytes. + """ + + return ctypes.sizeof(cls) + + @classmethod + def offsetof(cls, fname: str) -> int: + """Get field offset within the structure. + + Args: + fname: field name + + Returns: field offset in bytes + Raises: `AttributeError` if the specified field does not exist + """ + + return getattr(cls, fname).offset + + @classmethod + def memberat(cls, offset: int) -> Optional[str]: + """Get the member name at a given offset. + + Args: + offset: field offset within the structure + + Returns: field name, or None if no field starts at the specified offset + """ + + return next((fname for fname, *_ in cls._fields_ if cls.offsetof(fname) == offset), None) + + +# TODO: replace the lru_cache decorator with functools.cache when moving to Python 3.9 +@lru_cache(maxsize=2) +def get_aligned_struct(archbits: int) -> Type[BaseStruct]: + """Provide an aligned version of BaseStruct based on the underlying + architecture properties. + + Args: + archbits: required alignment in bits + """ + + class AlignedStruct(BaseStruct): + _pack_ = archbits // 8 + + return AlignedStruct + +def get_aligned_union(archbits: int): + """Provide an aligned union class based on the underlying architecture + properties. This class does not inherit the special BaseStruct methods. + + FIXME: ctypes.Union endianess cannot be set arbitrarily, rather it depends + on the hosting system. ctypes.LittleEndianUnion and ctypes.BigEndianUnion + are available only starting from Python 3.11 + + Args: + archbits: required alignment in bits + """ + + class AlignedUnion(ctypes.Union): + _pack_ = archbits // 8 + + return AlignedUnion + +def get_native_type(archbits: int) -> Type[ctypes._SimpleCData]: + """Select a ctypes integer type whose size matches the emulated + architecture native size. + """ + + __type = { + 32 : ctypes.c_uint32, + 64 : ctypes.c_uint64 + } + + return __type[archbits] diff --git a/qiling/os/utils.py b/qiling/os/utils.py index fa4bacca0..5e7f58ec2 100644 --- a/qiling/os/utils.py +++ b/qiling/os/utils.py @@ -7,7 +7,6 @@ This module is intended for general purpose functions that are only used in qiling.os """ -import ctypes from typing import Callable, Iterable, Iterator, List, MutableMapping, Sequence, Tuple, TypeVar, Union from uuid import UUID @@ -175,12 +174,11 @@ def __repl(m: re.Match) -> str: elif typ == 'Z': # note: ANSI_STRING and UNICODE_STRING have identical layout - strcls = make_unicode_string(self.ql.arch.bits).__class__ - data = self.ql.mem.read(arg, ctypes.sizeof(strcls)) - strobj = strcls.from_buffer_copy(data) + ucstr_struct = make_unicode_string(self.ql.arch.bits) - typ = 's' - arg = read_string(strobj.Buffer) + with ucstr_struct.ref(self.ql.mem, arg) as ucstr_obj: + typ = 's' + arg = read_string(ucstr_obj.Buffer) elif typ == 'p': pound = '#' diff --git a/qiling/os/windows/api.py b/qiling/os/windows/api.py index 12a8cca3d..1960adb8b 100644 --- a/qiling/os/windows/api.py +++ b/qiling/os/windows/api.py @@ -100,6 +100,7 @@ LPMESSAGEFILTER = POINTER LPMODULEINFO = POINTER LPNLSVERSIONINFO = POINTER +LPOSVERSIONINFOEXA = POINTER LPOSVERSIONINFOEXW = POINTER LPOVERLAPPED = POINTER LPPOINT = POINTER diff --git a/qiling/os/windows/dlls/advapi32.py b/qiling/os/windows/dlls/advapi32.py index 153e08a4b..77eaa9b5b 100644 --- a/qiling/os/windows/dlls/advapi32.py +++ b/qiling/os/windows/dlls/advapi32.py @@ -3,6 +3,8 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # +from typing import Callable, Tuple + from qiling import Qiling from qiling.os.windows.api import * from qiling.os.windows.const import * @@ -496,10 +498,12 @@ def hook_GetTokenInformation(ql: Qiling, address: int, params): 'pSid' : PSID }) def hook_GetSidSubAuthorityCount(ql: Qiling, address: int, params): - sid = ql.os.handle_manager.get(params["pSid"]).obj - addr_authority_count = sid.addr + 1 # +1 because the first byte is revision + pSid = params['pSid'] + + # SID address is used as its handle id + sid = ql.os.handle_manager.get(pSid).obj - return addr_authority_count + return pSid + sid.offsetof('SubAuthorityCount') # PDWORD GetSidSubAuthority( # PSID pSid, @@ -510,11 +514,13 @@ def hook_GetSidSubAuthorityCount(ql: Qiling, address: int, params): 'nSubAuthority' : DWORD }) def hook_GetSidSubAuthority(ql: Qiling, address: int, params): - num = params["nSubAuthority"] - sid = ql.os.handle_manager.get(params["pSid"]).obj - addr_authority = sid.addr + 8 + (ql.arch.pointersize * num) + pSid = params['pSid'] + nSubAuthority = params['nSubAuthority'] + + # SID address is used as its handle id + sid = ql.os.handle_manager.get(pSid).obj - return addr_authority + return pSid + sid.offsetof('SubAuthority') + (4 * nSubAuthority) # LSTATUS RegEnumValueA( # HKEY hKey, @@ -677,69 +683,91 @@ def hook_StartServiceA(ql: Qiling, address: int, params): }) def hook_AllocateAndInitializeSid(ql: Qiling, address: int, params): count = params["nSubAuthorityCount"] - subs = b''.join(ql.pack32(params[f'nSubAuthority{i}']) for i in range(count)) + subauths = tuple(params[f'nSubAuthority{i}'] for i in range(count)) + + sid_struct = make_sid(auth_count=len(subauths)) + sid_addr = ql.os.heap.alloc(sid_struct.sizeof()) + + sid_obj = sid_struct( + Revision = 1, + SubAuthorityCount = len(subauths), + IdentifierAuthority = (5,), + SubAuthority = subauths + ) - sid = Sid(ql, revision=1, subs_count=count, identifier=5, subs=subs) - sid_addr = ql.os.heap.alloc(sid.size) - sid.write(sid_addr) + sid_obj.save_to(ql.mem, sid_addr) - handle = Handle(obj=sid, id=sid_addr) + handle = Handle(obj=sid_obj, id=sid_addr) ql.os.handle_manager.append(handle) + dest = params["pSid"] ql.mem.write_ptr(dest, sid_addr) return 1 -# Some default Sids: -__adminsid = None # Administrators (S-1-5-32-544) -__userssid = None # All Users (S-1-5-32-545) -__guestssid = None # All Users (S-1-5-32-546) -__poweruserssid = None # Power Users (S-1-5-32-547) +def __create_default_sid(ql: Qiling, subauths: Tuple[int, ...]): + sid_struct = make_sid(auth_count=len(subauths)) + + sid_obj = sid_struct( + Revision = 1, + SubAuthorityCount = len(subauths), + IdentifierAuthority = (5,), + SubAuthority = tuple(subauths) + ) + + return sid_obj + +def singleton(func: Callable): + """A decorator for functions that produce singleton objects. + When a decorated function is called for the first time, its + singleton object will be created. The same object will be returned + on all consequent calls regardless of the passed arguments (if any). + """ -def get_adminsid(ql): - global __adminsid + __singleton = None - if __adminsid is None: - # nSubAuthority0 = SECURITY_BUILTIN_DOMAIN_RID[0x20] - # nSubAuthority1 = DOMAIN_ALIAS_RID_ADMINS[0x220] - subs = b"\x20\x00\x00\x00\x20\x02\x00\x00" - __adminsid = Sid(ql, revision=1, identifier=5, subs=subs, subs_count=2) + def wrapper(*args, **kwargs): + nonlocal __singleton - return __adminsid + if __singleton is None: + __singleton = func(*args, **kwargs) -def get_userssid(ql): - global __userssid + return __singleton - if __userssid is None: - # nSubAuthority0 = SECURITY_BUILTIN_DOMAIN_RID[0x20] - # nSubAuthority1 = DOMAIN_ALIAS_RID_USERS[0x221] - subs = b"\x20\x00\x00\x00\x21\x02\x00\x00" - __userssid = Sid(ql, revision=1, identifier=5, subs=subs, subs_count=2) + return wrapper - return __userssid +# Administrators (S-1-5-32-544) +@singleton +def __admin_sid(ql: Qiling): + # nSubAuthority0 = SECURITY_BUILTIN_DOMAIN_RID[0x20] + # nSubAuthority1 = DOMAIN_ALIAS_RID_ADMINS[0x220] -def get_guestssid(ql): - global __guestssid + return __create_default_sid(ql, (0x20, 0x220)) - if __guestssid is None: - # nSubAuthority0 = SECURITY_BUILTIN_DOMAIN_RID[0x20] - # nSubAuthority1 = DOMAIN_ALIAS_RID_GUESTS[0x222] - subs = b"\x20\x00\x00\x00\x22\x02\x00\x00" - __guestssid = Sid(ql, revision=1, identifier=5, subs=subs, subs_count=2) +# All Users (S-1-5-32-545) +@singleton +def __users_sid(ql: Qiling): + # nSubAuthority0 = SECURITY_BUILTIN_DOMAIN_RID[0x20] + # nSubAuthority1 = DOMAIN_ALIAS_RID_USERS[0x221] - return __guestssid + return __create_default_sid(ql, (0x20, 0x221)) -def get_poweruserssid(ql): - global __poweruserssid +# All Users (S-1-5-32-546) +@singleton +def __guests_sid(ql: Qiling): + # nSubAuthority0 = SECURITY_BUILTIN_DOMAIN_RID[0x20] + # nSubAuthority1 = DOMAIN_ALIAS_RID_GUESTS[0x222] - if __poweruserssid is None: - # nSubAuthority0 = SECURITY_BUILTIN_DOMAIN_RID[0x20] - # nSubAuthority1 = DOMAIN_ALIAS_RID_POWER_USERS[0x223] - subs = b"\x20\x00\x00\x00\x23\x02\x00\x00" - __poweruserssid = Sid(ql, revision=1, identifier=5, subs=subs, subs_count=2) + return __create_default_sid(ql, (0x20, 0x222)) - return __poweruserssid +# Power Users (S-1-5-32-547) +@singleton +def __powerusers_sid(ql: Qiling): + # nSubAuthority0 = SECURITY_BUILTIN_DOMAIN_RID[0x20] + # nSubAuthority1 = DOMAIN_ALIAS_RID_POWER_USERS[0x223] + + return __create_default_sid(ql, (0x20, 0x223)) # BOOL WINAPI CheckTokenMembership( @@ -753,24 +781,32 @@ def get_poweruserssid(ql): 'IsMember' : PBOOL }) def hook_CheckTokenMembership(ql: Qiling, address: int, params): - token_handle = params["TokenHandle"] - sid = ql.os.handle_manager.get(params["SidToCheck"]).obj + TokenHandle = params['TokenHandle'] + SidToCheck = params['SidToCheck'] + + sid = ql.os.handle_manager.get(SidToCheck).obj + IsMember = False + # If TokenHandle is NULL, CheckTokenMembership uses the impersonation token of the calling thread. - IsMember = 0 - if token_handle == 0: + if not TokenHandle: # For now, treat power users as admins - if get_adminsid(ql) == sid or get_poweruserssid(ql) == sid: - IsMember = 1 if ql.os.profile["SYSTEM"]["permission"] == "root" else 0 - elif get_userssid(ql) == sid: + if __admin_sid(ql) == sid or __powerusers_sid(ql) == sid: + IsMember = ql.os.profile["SYSTEM"]["permission"] == "root" + + elif __users_sid(ql) == sid: # FIXME: is this true for all tokens? probably not... - IsMember = 1 - elif get_guestssid(ql) == sid: - IsMember = 0 + IsMember = True + + elif __guests_sid(ql) == sid: + IsMember = False + else: - assert False, 'unimplemented' + raise NotImplementedError else: - assert False, 'unimplemented' - ql.mem.write_ptr(params['IsMember'], IsMember) + raise NotImplementedError + + ql.mem.write_ptr(params['IsMember'], int(IsMember)) + return 1 @@ -781,6 +817,7 @@ def hook_CheckTokenMembership(ql: Qiling, address: int, params): 'pSid' : PSID }) def hook_FreeSid(ql: Qiling, address: int, params): + # TODO: should also remove from ql.os.handle_manager ? ql.os.heap.free(params["pSid"]) return 0 diff --git a/qiling/os/windows/dlls/kernel32/debugapi.py b/qiling/os/windows/dlls/kernel32/debugapi.py index 38d25d5e8..7eba966fa 100644 --- a/qiling/os/windows/dlls/kernel32/debugapi.py +++ b/qiling/os/windows/dlls/kernel32/debugapi.py @@ -8,10 +8,17 @@ from qiling.os.windows.const import * from qiling.os.windows.fncc import * +def __is_debugger_present(ql: Qiling) -> int: + """Read PEB.BeingDebugger fied to determine whether debugger + is present or not. + """ + + return ql.loader.PEB.BeingDebugged + # BOOL IsDebuggerPresent(); @winsdkapi(cc=STDCALL, params={}) def hook_IsDebuggerPresent(ql: Qiling, address: int, params): - return 0 + return __is_debugger_present(ql) # BOOL CheckRemoteDebuggerPresent( # HANDLE hProcess, @@ -22,9 +29,10 @@ def hook_IsDebuggerPresent(ql: Qiling, address: int, params): 'pbDebuggerPresent' : PBOOL }) def hook_CheckRemoteDebuggerPresent(ql: Qiling, address: int, params): - pointer = params["pbDebuggerPresent"] + pbDebuggerPresent = params['pbDebuggerPresent'] - ql.mem.write(pointer, b'\x00') + res = __is_debugger_present(ql) + ql.mem.write(pbDebuggerPresent, ql.pack8(res)) return 1 diff --git a/qiling/os/windows/dlls/kernel32/fileapi.py b/qiling/os/windows/dlls/kernel32/fileapi.py index 8f90153e0..482471086 100644 --- a/qiling/os/windows/dlls/kernel32/fileapi.py +++ b/qiling/os/windows/dlls/kernel32/fileapi.py @@ -15,7 +15,7 @@ from qiling.os.windows.const import * from qiling.os.windows.fncc import * from qiling.os.windows.handle import Handle -from qiling.os.windows.structs import Win32FindData +from qiling.os.windows.structs import FILETIME, make_win32_find_data # DWORD GetFileType( # HANDLE hFile @@ -46,50 +46,67 @@ def hook_FindFirstFileA(ql: Qiling, address: int, params): filename = params['lpFileName'] pointer = params['lpFindFileData'] - if filename == 0: + if not filename: return INVALID_HANDLE_VALUE - elif len(filename) >= MAX_PATH: + + if len(filename) >= MAX_PATH: return ERROR_INVALID_PARAMETER - target_dir = os.path.join(ql.rootfs, filename.replace("\\", os.sep)) - ql.log.info('TARGET_DIR = %s' % target_dir) - real_path = ql.os.path.transform_to_real_path(filename) + host_path = ql.os.path.virtual_to_host_path(filename) # Verify the directory is in ql.rootfs to ensure no path traversal has taken place - if not os.path.exists(real_path): + if not ql.os.path.is_safe_host_path(host_path): ql.os.last_error = ERROR_FILE_NOT_FOUND + return INVALID_HANDLE_VALUE # Check if path exists filesize = 0 + try: - f = ql.os.fs_mapper.open(real_path, "r") - filesize = os.path.getsize(real_path) + f = ql.os.fs_mapper.open(host_path, "r") + + filesize = os.path.getsize(host_path) except FileNotFoundError: ql.os.last_error = ERROR_FILE_NOT_FOUND - return INVALID_HANDLE_VALUE - # Get size of the file - file_size_low = filesize & 0xffffff - file_size_high = filesize >> 32 + return INVALID_HANDLE_VALUE # Create a handle for the path new_handle = Handle(obj=f) ql.os.handle_manager.append(new_handle) - # Spoof filetime values - filetime = ql.pack64(datetime.now().microsecond) - - find_data = Win32FindData( - ql, - FILE_ATTRIBUTE_NORMAL, - filetime, filetime, filetime, - file_size_high, file_size_low, - 0, 0, - filename, - 0, 0, 0, 0,) - - find_data.write(pointer) + # calculate file time + epoch = datetime(1601, 1, 1) + elapsed = datetime.now() - epoch + + # number of 100-nanosecond intervals since Jan 1, 1601 utc + # where: (10 ** 9) / 100 -> (10 ** 7) + hnano = int(elapsed.total_seconds() * (10 ** 7)) + + mask = (1 << 32) - 1 + + ftime = FILETIME( + (hnano >> 0) & mask, + (hnano >> 32) & mask + ) + + fdata_struct = make_win32_find_data(ql.arch.bits, wide=False) + + with fdata_struct.ref(ql.mem, pointer) as fdata_obj: + fdata_obj.dwFileAttributes = FILE_ATTRIBUTE_NORMAL + fdata_obj.ftCreationTime = ftime + fdata_obj.ftLastAccessTime = ftime + fdata_obj.ftLastWriteTime = ftime + fdata_obj.nFileSizeHigh = (filesize >> 32) & mask + fdata_obj.nFileSizeLow = (filesize >> 0) & mask + fdata_obj.dwReserved0 = 0 + fdata_obj.dwReserved1 = 0 + fdata_obj.cFileName = filename + fdata_obj.cAlternateFileName = 0 + fdata_obj.dwFileType = 0 + fdata_obj.dwCreatorType = 0 + fdata_obj.wFinderFlags = 0 return new_handle.id diff --git a/qiling/os/windows/dlls/kernel32/processthreadsapi.py b/qiling/os/windows/dlls/kernel32/processthreadsapi.py index 27937f0fc..800f3520f 100644 --- a/qiling/os/windows/dlls/kernel32/processthreadsapi.py +++ b/qiling/os/windows/dlls/kernel32/processthreadsapi.py @@ -10,7 +10,7 @@ from qiling.os.windows.thread import QlWindowsThread, THREAD_STATUS from qiling.os.windows.handle import Handle -from qiling.os.windows.structs import Token, StartupInfo +from qiling.os.windows.structs import Token, make_startup_info # void ExitProcess( # UINT uExitCode @@ -22,14 +22,34 @@ def hook_ExitProcess(ql: Qiling, address: int, params): ql.emu_stop() ql.os.PE_RUN = False -def _GetStartupInfo(ql: Qiling, address: int, params): - startup_info = StartupInfo(ql, 0xc3c930, 0, 0, 0, 0x64, 0x64, 0x84, 0x80, 0xff, 0x40, 0x1, STD_INPUT_HANDLE, - STD_OUTPUT_HANDLE, STD_ERROR_HANDLE) - - pointer = params["lpStartupInfo"] - startup_info.write(pointer) - - return 0 +def _GetStartupInfo(ql: Qiling, address: int, params, *, wide: bool): + lpStartupInfo = params['lpStartupInfo'] + sui_struct = make_startup_info(ql.arch.bits) + + enc = 'utf-16le' if wide else 'latin1' + desktop_title = f'QilingDesktop\x00'.encode(enc) + + # TODO: fill out with real / configurable values rather than bogus / fixed ones + with sui_struct.ref(ql.mem, lpStartupInfo) as sui_obj: + sui_obj.cb = sui_struct.sizeof() + sui_obj.lpDesktop = ql.os.heap.alloc(len(desktop_title)) + sui_obj.lpTitle = 0 + sui_obj.dwX = 0 + sui_obj.dwY = 0 + sui_obj.dwXSize = 100 + sui_obj.dwYSize = 100 + sui_obj.dwXCountChars = 132 + sui_obj.dwYCountChars = 128 + sui_obj.dwFillAttribute = 0xff + sui_obj.dwFlags = 0x40 + sui_obj.wShowWindow = 1 + sui_obj.cbReserved2 = 0 + sui_obj.lpReserved2 = 0 + sui_obj.hStdInput = STD_INPUT_HANDLE + sui_obj.hStdOutput = STD_OUTPUT_HANDLE + sui_obj.hStdError = STD_ERROR_HANDLE + + return STATUS_SUCCESS # VOID WINAPI GetStartupInfoA( # _Out_ LPSTARTUPINFO lpStartupInfo @@ -38,7 +58,7 @@ def _GetStartupInfo(ql: Qiling, address: int, params): 'lpStartupInfo' : LPSTARTUPINFOA }) def hook_GetStartupInfoA(ql: Qiling, address: int, params): - return _GetStartupInfo(ql, address, params) + return _GetStartupInfo(ql, address, params, wide=False) # VOID WINAPI GetStartupInfoW( # _Out_ LPSTARTUPINFO lpStartupInfo @@ -47,7 +67,7 @@ def hook_GetStartupInfoA(ql: Qiling, address: int, params): 'lpStartupInfo' : LPSTARTUPINFOW }) def hook_GetStartupInfoW(ql: Qiling, address: int, params): - return _GetStartupInfo(ql, address, params) + return _GetStartupInfo(ql, address, params, wide=True) # DWORD TlsAlloc(); @winsdkapi(cc=STDCALL, params={}) diff --git a/qiling/os/windows/dlls/kernel32/sysinfoapi.py b/qiling/os/windows/dlls/kernel32/sysinfoapi.py index 8a5f6aa87..173e5bc4e 100644 --- a/qiling/os/windows/dlls/kernel32/sysinfoapi.py +++ b/qiling/os/windows/dlls/kernel32/sysinfoapi.py @@ -10,7 +10,7 @@ from qiling.os.windows.api import * from qiling.os.windows.const import * from qiling.os.windows.fncc import * -from qiling.os.windows.structs import FILETIME, SystemInfo, SystemTime +from qiling.os.windows.structs import FILETIME, SYSTEMTIME, make_system_info # NOT_BUILD_WINDOWS_DEPRECATE DWORD GetVersion( # ); @@ -40,13 +40,24 @@ def hook_GetVersionExW(ql: Qiling, address: int, params): return __GetVersionEx(ql, address, params) def __GetSystemInfo(ql: Qiling, address: int, params): - pointer = params["lpSystemInfo"] - - # FIXME: dll_size no longer reflects the upper bound of used memory; should find a better way to specify max_address - system_info = SystemInfo(ql, 0, ql.mem.pagesize, ql.loader.pe_image_address, - ql.loader.dll_address + ql.loader.dll_size, 0x3, 0x4, 0x24a, ql.mem.pagesize * 10, - 0x6, 0x4601) - system_info.write(pointer) + lpSystemInfo = params['lpSystemInfo'] + + sysinfo_struct = make_system_info(ql.arch.bits) + + # FIXME: + # - load configurable values rather than fixed / bogus ones + # - loader.dll_size no longer reflects the upper bound of used memory; should find a better way to specify max_address + with sysinfo_struct.ref(ql.mem, lpSystemInfo) as si: + si.dwOemId = 0 + si.dwPageSize = ql.mem.pagesize + si.lpMinimumApplicationAddress = ql.loader.pe_image_address + si.lpMaximumApplicationAddress = ql.loader.dll_address + ql.loader.dll_size + si.dwActiveProcessorMask = 0x3 + si.dwNumberOfProcessors = 0x4 + si.dwProcessorType = 0x24a + si.dwAllocationGranularity = ql.mem.pagesize * 10 + si.wProcessorLevel = 0x6 + si.wProcessorRevision = 0x4601 return 0 @@ -66,11 +77,18 @@ def hook_GetSystemInfo(ql: Qiling, address: int, params): 'lpSystemTime' : LPSYSTEMTIME }) def hook_GetLocalTime(ql: Qiling, address: int, params): - ptr = params['lpSystemTime'] - d = datetime.now() - - system_time = SystemTime(ql, d.year, d.month, d.isoweekday(), d.day, d.hour, d.minute, d.second, d.microsecond // 1000) - system_time.write(ptr) + lpSystemTime = params['lpSystemTime'] + now = datetime.now() + + with SYSTEMTIME.ref(ql.mem, lpSystemTime) as st: + st.wYear = now.year + st.wMonth = now.month + st.wDayOfWeek = now.isoweekday() + st.wDay = now.day + st.wHour = now.hour + st.wMinute = now.minute + st.wSecond = now.second + st.wMilliseconds = now.microsecond // 1000 return 0 @@ -91,10 +109,13 @@ def hook_GetSystemTimeAsFileTime(ql: Qiling, address: int, params): hnano = int(elapsed.total_seconds() * (10 ** 7)) mask = (1 << 32) - 1 - hi = (hnano >> 32) & mask - lo = (hnano >> 0) & mask - ql.mem.write(ptr, bytes(FILETIME(lo, hi))) + ftime = FILETIME( + (hnano >> 0) & mask, + (hnano >> 32) & mask + ) + + ftime.save_to(ql.mem, ptr) # DWORD GetTickCount( # ); diff --git a/qiling/os/windows/dlls/kernel32/tlhelp32.py b/qiling/os/windows/dlls/kernel32/tlhelp32.py index c41b0d29c..a31ad669d 100644 --- a/qiling/os/windows/dlls/kernel32/tlhelp32.py +++ b/qiling/os/windows/dlls/kernel32/tlhelp32.py @@ -48,5 +48,12 @@ def hook_Process32FirstW(ql: Qiling, address: int, params): 'lppe' : LPPROCESSENTRY32W }) def hook_Process32NextW(ql: Qiling, address: int, params): - # Return True if more process, 0 else - return int(ql.os.syscall_count["Process32NextW"] < 3) # I don' know how many process the sample want's to cycle + global __call_count + + __call_count += 1 + + # FIXME: this is an undocumented workaround, probably to satisfy one of + # the samples. better implement that as an ad-hoc hook there + return int(__call_count < 3) + +__call_count = 0 diff --git a/qiling/os/windows/dlls/kernel32/winbase.py b/qiling/os/windows/dlls/kernel32/winbase.py index 00f1d9260..f798aed62 100644 --- a/qiling/os/windows/dlls/kernel32/winbase.py +++ b/qiling/os/windows/dlls/kernel32/winbase.py @@ -11,7 +11,7 @@ from qiling.os.windows.api import * from qiling.os.windows.const import * from qiling.os.windows.fncc import * -from qiling.os.windows.structs import OsVersionInfoExA +from qiling.os.windows.structs import make_os_version_info_ex from qiling.os.windows.utils import cmp # HFILE _lclose( @@ -544,20 +544,6 @@ def hook_IsBadWritePtr(ql: Qiling, address: int, params): # Check write permission for size of memory return 0 # ACCESS_TRUE -def compare(p1: int, operator: int, p2: int) -> bool: - op = { - VER_EQUAL : lambda a, b: a == b, - VER_GREATER : lambda a, b: a > b, - VER_GREATER_EQUAL : lambda a, b: a >= b, - VER_LESS : lambda a, b: a < b, - VER_LESS_EQUAL : lambda a, b: a <= b - }.get(operator) - - if not op: - raise QlErrorNotImplemented('') - - return op(p1, p2) - # BOOL VerifyVersionInfoW( # LPOSVERSIONINFOEXW lpVersionInformation, # DWORD dwTypeMask, @@ -569,64 +555,98 @@ def compare(p1: int, operator: int, p2: int) -> bool: 'dwlConditionMask' : DWORDLONG }) def hook_VerifyVersionInfoW(ql: Qiling, address: int, params): - # https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-verifyversioninfow2 - pointer = params["lpVersionInformation"] + return __VerifyVersionInfo(ql, address, params, wide=True) - os_asked = OsVersionInfoExA(ql) - os_asked.read(pointer) +# BOOL VerifyVersionInfoA( +# LPOSVERSIONINFOEXA lpVersionInformation, +# DWORD dwTypeMask, +# DWORDLONG dwlConditionMask +# ); +@winsdkapi(cc=STDCALL, params={ + 'lpVersionInformation' : LPOSVERSIONINFOEXA, + 'dwTypeMask' : DWORD, + 'dwlConditionMask' : DWORDLONG +}) +def hook_VerifyVersionInfoA(ql: Qiling, address: int, params): + return __VerifyVersionInfo(ql, address, params, wide=False) + +def __VerifyVersionInfo(ql: Qiling, address: int, params, *, wide: bool): + # see: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-verifyversioninfow + + lpVersionInformation = params['lpVersionInformation'] + dwTypeMask = params['dwTypeMask'] + dwlConditionMask = params['dwlConditionMask'] + + oviex_struct = make_os_version_info_ex(ql.arch.bits, wide=wide) + + askedOsVersionInfo = oviex_struct.load_from(ql.mem, lpVersionInformation) + + # reading emulated os version info from profile + # FIXME: read the necessary information from KUSER_SHARED_DATA instead + osconfig = ql.os.profile['SYSTEM'] + + emulOsVersionInfo = oviex_struct( + dwMajorVersion = osconfig.getint('majorVersion'), + dwMinorVersion = osconfig.getint('minorVersion'), + dwBuildNumber = 0, + dwPlatformId = 0, + wServicePackMajor = osconfig.getint('VER_SERVICEPACKMAJOR'), + wServicePackMinor = 0, + wSuiteMask = 0, + wProductType = osconfig.getint('productType') + ) + + # check criteria by the order they should be evaluated. the online microsoft + # documentation only specify the first five, so not sure about the other three. + # + # each criteria is associated with the OSVERSIONINFOEX[A|W] it corresponds to. + checks = ( + (1, 'dwMajorVersion'), # VER_MAJORVERSION + (0, 'dwMinorVersion'), # VER_MINORVERSION + (2, 'dwBuildNumber'), # VER_BUILDNUMBER + (5, 'wServicePackMajor'), # VER_SERVICEPACKMAJOR + (4, 'wServicePackMinor'), # VER_SERVICEPACKMINOR + (3, 'dwPlatformId'), # VER_PLATFORMID + (6, 'wSuiteMask'), # VER_SUITENAME + (7, 'wProductType') # VER_PRODUCT_TYPE + ) - ConditionMask: dict = ql.os.hooks_variables["ConditionMask"] res = True - opstr = { - VER_EQUAL : '==', - VER_GREATER : '>', - VER_GREATER_EQUAL : '>=', - VER_LESS : '<', - VER_LESS_EQUAL : '<=' - } - - for key, value in ConditionMask.items(): - if value not in opstr: - raise QlErrorNotImplemented(f'API not implemented with operator {value}') + for bit, field in checks: + if dwTypeMask & (1 << bit): + asked = getattr(askedOsVersionInfo, field) + emuld = getattr(emulOsVersionInfo, field) - # Versions should be compared together - if key in (VER_MAJORVERSION, VER_MINORVERSION, VER_PRODUCT_TYPE): - concat = f'{os_asked.major[0]}{os_asked.minor[0]}{os_asked.product[0]}' + # extract the condition code for the required field + cond = (dwlConditionMask >> (bit * VER_NUM_BITS_PER_CONDITION_MASK)) & VER_CONDITION_MASK - # Just a print for analysts, will remove it from here in the future - if key == VER_MAJORVERSION: - ql.log.debug("The Target is checking the windows Version!") - version_asked = SYSTEMS_VERSION.get(concat, None) + # special case for VER_SUITENAME + if bit == 6: + cond_op = { + VER_AND : lambda a, b: (a & b) == b, # all members of b must be present + VER_OR : lambda a, b: (a & b) != 0 # at least one member of b must be present + }[cond] - if version_asked is None: - raise QlErrorNotImplemented(f'API not implemented for version {concat}') + res &= cond_op(emuld, asked) - ql.log.debug(f'The target asks for version {opstr[value]} {version_asked}') + else: + # cond operates as a bitmask, so multiple 'if' statements are appropriately + # used here. do not turn this into an 'elif' construct. - qiling_os = \ - f'{ql.os.profile.get("SYSTEM", "majorVersion")}' + \ - f'{ql.os.profile.get("SYSTEM", "minorVersion")}' + \ - f'{ql.os.profile.get("SYSTEM", "productType")}' + if (cond & VER_GREATER) and (emuld > asked): + return 1 - # We can finally compare - res = compare(int(qiling_os), value, int(concat)) + if (cond & VER_LESS) and (emuld < asked): + return 1 - elif key == VER_SERVICEPACKMAJOR: - res = compare(ql.os.profile.getint("SYSTEM", "VER_SERVICEPACKMAJOR"), value, os_asked.service_major[0]) + if (cond & VER_EQUAL): + res &= (emuld == asked) - else: - raise QlErrorNotImplemented("API not implemented for key %s" % key) - - # The result is a AND between every value, so if we find a False we just exit from the loop - if not res: - ql.os.last_error = ERROR_OLD_WIN_VERSION - return 0 - - # reset mask - ql.os.hooks_variables["ConditionMask"] = {} + if not res: + ql.os.last_error = ERROR_OLD_WIN_VERSION - return res + return int(res) def __GetUserName(ql: Qiling, address: int, params, wstring: bool): lpBuffer = params["lpBuffer"] diff --git a/qiling/os/windows/dlls/kernel32/winnt.py b/qiling/os/windows/dlls/kernel32/winnt.py index ca59d5a84..d71c56102 100644 --- a/qiling/os/windows/dlls/kernel32/winnt.py +++ b/qiling/os/windows/dlls/kernel32/winnt.py @@ -66,52 +66,17 @@ def hook_InterlockedDecrement(ql: Qiling, address: int, params): 'Condition' : BYTE }) def hook_VerSetConditionMask(ql: Qiling, address: int, params): - # ConditionMask = params["ConditionMask"] - TypeMask = params["TypeMask"] - Condition = params["Condition"] + # see: https://docs.microsoft.com/en-us/windows/win32/sysinfo/verifying-the-system-version - ConditionMask = ql.os.hooks_variables.get("ConditionMask", {}) + ConditionMask = params['ConditionMask'] + TypeMask = params['TypeMask'] + Condition = params['Condition'] - if TypeMask == 0: - ret = ConditionMask - else: - Condition &= VER_CONDITION_MASK + Condition &= VER_CONDITION_MASK - if Condition == 0: - ret = ConditionMask - else: - if TypeMask & VER_PRODUCT_TYPE: - # ConditionMask |= ullCondMask << (7 * VER_NUM_BITS_PER_CONDITION_MASK) - ConditionMask[VER_PRODUCT_TYPE] = Condition - elif TypeMask & VER_SUITENAME: - # ConditionMask |= ullCondMask << (6 * VER_NUM_BITS_PER_CONDITION_MASK) - ConditionMask[VER_SUITENAME] = Condition - elif TypeMask & VER_SERVICEPACKMAJOR: - # ConditionMask |= ullCondMask << (5 * VER_NUM_BITS_PER_CONDITION_MASK) - ConditionMask[VER_SERVICEPACKMAJOR] = Condition - elif TypeMask & VER_SERVICEPACKMINOR: - # ConditionMask |= ullCondMask << (4 * VER_NUM_BITS_PER_CONDITION_MASK) - ConditionMask[VER_SERVICEPACKMINOR] = Condition - elif TypeMask & VER_PLATFORMID: - # ConditionMask |= ullCondMask << (3 * VER_NUM_BITS_PER_CONDITION_MASK) - ConditionMask[VER_PLATFORMID] = Condition - elif TypeMask & VER_BUILDNUMBER: - # ConditionMask |= ullCondMask << (2 * VER_NUM_BITS_PER_CONDITION_MASK) - ConditionMask[VER_BUILDNUMBER] = Condition - elif TypeMask & VER_MAJORVERSION: - # ConditionMask |= ullCondMask << (1 * VER_NUM_BITS_PER_CONDITION_MASK) - ConditionMask[VER_MAJORVERSION] = Condition - elif TypeMask & VER_MINORVERSION: - # ConditionMask |= ullCondMask << (0 * VER_NUM_BITS_PER_CONDITION_MASK) - ConditionMask[VER_MINORVERSION] = Condition + if Condition: + for i in range(8): + if TypeMask & (1 << i): + ConditionMask |= Condition << (i * VER_NUM_BITS_PER_CONDITION_MASK) - ret = 1 - - # ConditionMask should be updated locally - # https://docs.microsoft.com/it-it/windows/win32/sysinfo/verifying-the-system-version - # But since we don't have the pointer to the variable, an hack is to use the environment. - # Feel free to push a better solution - # Since I can't work with bits, and since we had to work with the environment anyway, let's use a dict - ql.os.hooks_variables["ConditionMask"] = ConditionMask - - return ret + return ConditionMask diff --git a/qiling/os/windows/dlls/ntdll.py b/qiling/os/windows/dlls/ntdll.py index ab9b4ae52..164595496 100644 --- a/qiling/os/windows/dlls/ntdll.py +++ b/qiling/os/windows/dlls/ntdll.py @@ -31,41 +31,40 @@ def hook_memcpy(ql: Qiling, address: int, params): src = params['src'] count = params['count'] - try: - data = bytes(ql.mem.read(src, count)) - ql.mem.write(dest, data) - except Exception: - ql.log.exception("") + data = bytes(ql.mem.read(src, count)) + ql.mem.write(dest, data) return dest def _QueryInformationProcess(ql: Qiling, address: int, params): flag = params["ProcessInformationClass"] - dst = params["ProcessInformation"] - pt_res = params["ReturnLength"] + obuf_ptr = params["ProcessInformation"] + obuf_len = params['ProcessInformationLength'] + res_size_ptr = params["ReturnLength"] if flag == ProcessDebugFlags: - value = b"\x01" * 0x4 + res_data = ql.pack32(1) elif flag == ProcessDebugPort: - value = b"\x00" * 0x4 + res_data = ql.pack32(0) elif flag == ProcessDebugObjectHandle: return STATUS_PORT_NOT_SET elif flag == ProcessBasicInformation: - pbi = structs.ProcessBasicInformation(ql, - exitStatus=0, - pebBaseAddress=ql.loader.TEB.PebAddress, - affinityMask=0, - basePriority=0, - uniqueId=ql.os.profile.getint("KERNEL", "pid"), - parentPid=ql.os.profile.getint("KERNEL", "parent_pid") + kconf = ql.os.profile['KERNEL'] + pbi_struct = structs.make_process_basic_info(ql.arch.bits) + + pci_obj = pbi_struct( + ExitStatus=0, + PebBaseAddress=ql.loader.TEB.PebAddress, + AffinityMask=0, + BasePriority=0, + UniqueProcessId=kconf.getint('pid'), + InheritedFromUniqueProcessId=kconf.getint('parent_pid') ) - addr = ql.os.heap.alloc(pbi.size) - pbi.write(addr) - value = ql.pack(addr) + res_data = bytes(pci_obj) else: # TODO: support more info class ("flag") values @@ -73,11 +72,13 @@ def _QueryInformationProcess(ql: Qiling, address: int, params): return STATUS_UNSUCCESSFUL - ql.log.debug("The target is checking the debugger via QueryInformationProcess ") - ql.mem.write(dst, value) + res_size = len(res_data) + + if obuf_len >= res_size: + ql.mem.write(obuf_ptr, res_data) - if pt_res: - ql.mem.write_ptr(pt_res, 8, 1) + if res_size_ptr: + ql.mem.write_ptr(res_size_ptr, res_size) return STATUS_SUCCESS @@ -88,7 +89,7 @@ def _QueryInformationProcess(ql: Qiling, address: int, params): # _In_ ULONG ProcessInformationLength, # _Out_opt_ PULONG ReturnLength # ); -@winsdkapi(cc=CDECL, params={ +@winsdkapi(cc=STDCALL, params={ 'ProcessHandle' : HANDLE, 'ProcessInformationClass' : PROCESSINFOCLASS, 'ProcessInformation' : PVOID, @@ -120,46 +121,43 @@ def hook_NtQueryInformationProcess(ql: Qiling, address: int, params): return _QueryInformationProcess(ql, address, params) def _QuerySystemInformation(ql: Qiling, address: int, params): - siClass = params["SystemInformationClass"] - pt_res = params["ReturnLength"] - dst = params["SystemInformation"] - - if (siClass == SystemBasicInformation): - bufferLength = params["SystemInformationLength"] + SystemInformationClass = params['SystemInformationClass'] + SystemInformation = params['SystemInformation'] + SystemInformationLength = params['SystemInformationLength'] + ReturnLength = params['ReturnLength'] + if SystemInformationClass == SystemBasicInformation: max_uaddr = { QL_ARCH.X86 : 0x7FFEFFFF, QL_ARCH.X8664: 0x7FFFFFFEFFFF }[ql.arch.type] - sbi = structs.SystemBasicInforation( - ql, - Reserved=0, - TimerResolution=156250, - PageSize=ql.mem.pagesize, - NumberOfPhysicalPages=0x003FC38A, - LowestPhysicalPageNumber=1, - HighestPhysicalPageNumber=0x0046DFFF, - AllocationGranularity=1, - MinimumUserModeAddress=0x10000, - MaximumUserModeAddress=max_uaddr, - ActiveProcessorsAffinityMask=0x3F, - NumberOfProcessors=0x6 + sbi_struct = structs.make_system_basic_info(ql.arch.bits) + + # FIXME: retrieve the necessary info from KUSER_SHARED_DATA + sbi_obj = sbi_struct( + TimerResolution = 156250, + PageSize = ql.mem.pagesize, + NumberOfPhysicalPages = 0x003FC38A, + LowestPhysicalPageNumber = 1, + HighestPhysicalPageNumber = 0x0046DFFF, + AllocationGranularity = 1, + MinimumUserModeAddress = 0x10000, + MaximumUserModeAddress = max_uaddr, + ActiveProcessorsAffinityMask = 0x3F, + NumberOfProcessors = 6 ) - if (bufferLength==sbi.size): - sbi.write(dst) - - if pt_res: - ql.mem.write_ptr(pt_res, sbi.size, 1) - else: - if pt_res: - ql.mem.write_ptr(pt_res, sbi.size, 1) + if ReturnLength: + ql.mem.write_ptr(ReturnLength, sbi_struct.sizeof(), 4) + if SystemInformationLength < sbi_struct.sizeof(): return STATUS_INFO_LENGTH_MISMATCH + + sbi_obj.save_to(ql.mem, SystemInformation) + else: - ql.log.debug(str(siClass)) - raise QlErrorNotImplemented("API not implemented") + raise QlErrorNotImplemented(f'not implemented for {SystemInformationClass=}') return STATUS_SUCCESS @@ -232,35 +230,64 @@ def hook_ZwCreateDebugObject(ql: Qiling, address: int, params): 'ReturnLength' : PULONG }) def hook_ZwQueryObject(ql: Qiling, address: int, params): - infoClass = params["ObjectInformationClass"] - dest = params["ObjectInformation"] - size_dest = params["ReturnLength"] - string = "DebugObject".encode("utf-16le") - - string_addr = ql.os.heap.alloc(len(string)) - ql.log.debug(str(string_addr)) - ql.log.debug(str(string)) - ql.mem.write(string_addr, string) - us = structs.UnicodeString(ql, len(string), len(string), string_addr) - - if infoClass == ObjectTypeInformation: - res = structs.ObjectTypeInformation(ql, us, 1, 1) - - elif infoClass == ObjectAllTypesInformation: - # FIXME: there is an error in how these structs are read by al-khaser. Have no idea on where, so we are - # bypassing it - # oti = structs.ObjectTypeInformation(ql, us, 1, 1) - # res = structs.ObjectAllTypesInformation(ql, 2, oti) - return 1 + handle = params['Handle'] + ObjectInformationClass = params['ObjectInformationClass'] + ObjectInformation = params['ObjectInformation'] + ObjectInformationLength = params['ObjectInformationLength'] + ReturnLength = params['ReturnLength'] + + s = 'DebugObject'.encode('utf-16le') + addr = ql.os.heap.alloc(len(s)) + ql.mem.write(addr, s) + + unistr_struct = structs.make_unicode_string(ql.arch.bits) + + unistr_obj = unistr_struct( + Length = len(s), + MaximumLength = len(s), + Buffer = addr + ) + + oti_struct = structs.make_object_type_info(ql.arch.bits) + + oti_obj = oti_struct( + TypeName = unistr_obj, + TotalNumberOfObjects = 1, + TotalNumberOfHandles = 1 + ) + + oati_struct = structs.make_object_all_types_info(ql.arch.bits, 1) + + if ObjectInformationClass == ObjectTypeInformation: + out = oti_obj + + elif ObjectInformationClass == ObjectAllTypesInformation: + # FIXME: al-khaser refers the object named 'DebugObject' twice: the first time it creates a handle + # for it (so number of handles is expected to be higher than 0) and then closes it. the next time + # it accesses it (here), it expects the number of handles to be 0. + # + # ideally we would track the handles for each object, but since we do not - this is a hack to let + # it pass. + oti_obj.TotalNumberOfHandles = 0 + + oati_obj = oati_struct( + NumberOfObjectTypes = 1, + ObjectTypeInformation = (oti_obj,) + ) + + out = oati_obj else: - raise QlErrorNotImplemented("API not implemented") + raise QlErrorNotImplemented(f'API not implemented ({ObjectInformationClass=})') + + if ReturnLength: + ql.mem.write_ptr(ReturnLength, out.sizeof(), 4) - if dest and params["Handle"]: - res.write(dest) + if ObjectInformationLength < out.sizeof(): + return STATUS_INFO_LENGTH_MISMATCH - if size_dest: - ql.mem.write_ptr(size_dest, res.size, 4) + if ObjectInformation and handle: + out.save_to(ql.mem, ObjectInformation) return STATUS_SUCCESS @@ -313,20 +340,20 @@ def _SetInformationProcess(ql: Qiling, address: int, params): ql.mem.write_ptr(dst, 0, 1) elif flag == ProcessBasicInformation: - pbi = structs.ProcessBasicInformation( - ql, - exitStatus=0, - pebBaseAddress=ql.loader.TEB.PebAddress, - affinityMask=0, - basePriority=0, - uniqueId=ql.os.profile.getint("KERNEL", "pid"), - parentPid=ql.os.profile.getint("KERNEL", "parent_pid") + kconf = ql.os.profile['KERNEL'] + pbi_struct = structs.make_process_basic_info(ql.arch.bits) + + pci_obj = pbi_struct( + ExitStatus=0, + PebBaseAddress=ql.loader.TEB.PebAddress, + AffinityMask=0, + BasePriority=0, + UniqueProcessId=kconf.getint('pid'), + InheritedFromUniqueProcessId=kconf.getint('parent_pid') ) ql.log.debug("The target may be attempting to modify the PEB debug flag") - addr = ql.os.heap.alloc(pbi.size) - pbi.write(addr) - value = ql.pack(addr) + value = bytes(pbi_obj) else: # TODO: support more info class ("flag") values diff --git a/qiling/os/windows/dlls/ntoskrnl.py b/qiling/os/windows/dlls/ntoskrnl.py index dcdaf2eb7..59d49da6d 100644 --- a/qiling/os/windows/dlls/ntoskrnl.py +++ b/qiling/os/windows/dlls/ntoskrnl.py @@ -14,15 +14,6 @@ from qiling.os.windows.wdk_const import DO_DEVICE_INITIALIZING, DO_EXCLUSIVE from qiling.utils import verify_ret -# typedef struct _OSVERSIONINFOW { -# ULONG dwOSVersionInfoSize; -# ULONG dwMajorVersion; -# ULONG dwMinorVersion; -# ULONG dwBuildNumber; -# ULONG dwPlatformId; -# WCHAR szCSDVersion[128]; -# } - # NTSYSAPI NTSTATUS RtlGetVersion( # PRTL_OSVERSIONINFOW lpVersionInformation # ); @@ -30,13 +21,16 @@ 'lpVersionInformation' : PRTL_OSVERSIONINFOW }) def hook_RtlGetVersion(ql: Qiling, address: int, params): - pointer = params["lpVersionInformation"] + pointer = params['lpVersionInformation'] + + osverinfo_struct = make_os_version_info(ql.arch.bits, wide=True) + with osverinfo_struct.ref(ql.mem, pointer) as osverinfo_obj: + # read the necessary information from KUSER_SHARED_DATA + kusd_obj = ql.os.KUSER_SHARED_DATA - os = OsVersionInfoW(ql) - os.read(pointer) - os.major[0] = ql.os.profile.getint("SYSTEM", "majorVersion") - os.minor[0] = ql.os.profile.getint("SYSTEM", "minorVersion") - os.write(pointer) + osverinfo_obj.dwOSVersionInfoSize = osverinfo_struct.sizeof() + osverinfo_obj.dwMajorVersion = kusd_obj.NtMajorVersion + osverinfo_obj.dwMinorVersion = kusd_obj.NtMinorVersion ql.log.debug("The target is checking the windows Version!") @@ -77,13 +71,7 @@ def hook_ZwSetInformationThread(ql: Qiling, address: int, params): return STATUS_SUCCESS -# NTSYSAPI NTSTATUS ZwClose( -# HANDLE Handle -# ); -@winsdkapi(cc=STDCALL, params={ - 'Handle' : HANDLE -}) -def hook_ZwClose(ql: Qiling, address: int, params): +def __Close(ql: Qiling, address: int, params): value = params["Handle"] handle = ql.os.handle_manager.get(value) @@ -93,18 +81,20 @@ def hook_ZwClose(ql: Qiling, address: int, params): return STATUS_SUCCESS +# NTSYSAPI NTSTATUS ZwClose( +# HANDLE Handle +# ); @winsdkapi(cc=STDCALL, params={ 'Handle' : HANDLE }) -def hook_NtClose(ql: Qiling, address: int, params): - value = params["Handle"] - - handle = ql.os.handle_manager.get(value) - - if handle is None: - return STATUS_INVALID_HANDLE +def hook_ZwClose(ql: Qiling, address: int, params): + return __Close(ql, address, params) - return STATUS_SUCCESS +@winsdkapi(cc=STDCALL, params={ + 'Handle' : HANDLE +}) +def hook_NtClose(ql: Qiling, address: int, params): + return __Close(ql, address, params) # NTSYSAPI ULONG DbgPrintEx( # ULONG ComponentId, @@ -158,30 +148,27 @@ def __IoCreateDevice(ql: Qiling, address: int, params): DeviceCharacteristics = params['DeviceCharacteristics'] DeviceObject = params['DeviceObject'] - device_object = make_device_object(ql.arch.bits) - device_object.Type = 3 # FILE_DEVICE_CD_ROM_FILE_SYSTEM ? - device_object.DeviceExtension = ql.os.heap.alloc(DeviceExtensionSize) - device_object.Size = ctypes.sizeof(device_object) + DeviceExtensionSize - device_object.ReferenceCount = 1 - device_object.DriverObject.value = DriverObject - device_object.NextDevice.value = 0 - device_object.AttachedDevice.value = 0 - device_object.CurrentIrp.value = 0 - device_object.Timer.value = 0 - device_object.Flags = DO_DEVICE_INITIALIZING - - if params.get('Exclusive'): - device_object.Flags |= DO_EXCLUSIVE - - device_object.Characteristics = DeviceCharacteristics - - addr = ql.os.heap.alloc(ctypes.sizeof(device_object)) - - ql.mem.write(addr, bytes(device_object)) - ql.mem.write_ptr(DeviceObject, addr) + devobj_struct = make_device_object(ql.arch.bits) + devobj_addr = ql.os.heap.alloc(devobj_struct.sizeof()) + + with devobj_struct.ref(ql.mem, devobj_addr) as devobj_obj: + devobj_obj.Type = 3 # FILE_DEVICE_CD_ROM_FILE_SYSTEM ? + devobj_obj.DeviceExtension = ql.os.heap.alloc(DeviceExtensionSize) + devobj_obj.Size = devobj_struct.sizeof() + DeviceExtensionSize + devobj_obj.ReferenceCount = 1 + devobj_obj.DriverObject = DriverObject + devobj_obj.NextDevice = 0 + devobj_obj.AttachedDevice = 0 + devobj_obj.CurrentIrp = 0 + devobj_obj.Timer = 0 + devobj_obj.Flags = DO_DEVICE_INITIALIZING | (DO_EXCLUSIVE if params.get('Exclusive') else 0) + devobj_obj.Characteristics = DeviceCharacteristics + + # update out param + ql.mem.write_ptr(DeviceObject, devobj_addr) # update DriverObject.DeviceObject - ql.loader.driver_object.DeviceObject = addr + ql.loader.driver_object.DeviceObject = devobj_addr return STATUS_SUCCESS @@ -634,12 +621,12 @@ def hook_KeLeaveCriticalRegion(ql: Qiling, address: int, params): def hook_MmMapLockedPagesSpecifyCache(ql: Qiling, address: int, params): MemoryDescriptorList = params['MemoryDescriptorList'] - mdl_class = make_mdl(ql.arch.bits).__class__ + mdl_struct = make_mdl(ql.arch.bits) - mdl_buffer = ql.mem.read(MemoryDescriptorList, ctypes.sizeof(mdl_class)) - mdl = mdl_class.from_buffer(mdl_buffer) + with mdl_struct.ref(ql.mem, MemoryDescriptorList) as mdl_obj: + address = mdl_obj.MappedSystemVa - return mdl.MappedSystemVa.value + return address # void ProbeForRead( # const volatile VOID *Address, @@ -743,43 +730,51 @@ def hook_RtlMultiByteToUnicodeN(ql: Qiling, address: int, params): # OUT PULONG ReturnLength # ); def _NtQuerySystemInformation(ql: Qiling, address: int, params): - if params["SystemInformationClass"] == 0xb: # SystemModuleInformation - # if SystemInformationLength = 0, we return the total size in ReturnLength - NumberOfModules = 1 + # see: https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/query.htm - rpmi_class = make_rtl_process_module_info(ql.arch.bits).__class__ + SystemInformationClass = params['SystemInformationClass'] + ReturnLength = params['ReturnLength'] + SystemInformationLength = params['SystemInformationLength'] + SystemInformation = params['SystemInformation'] + if SystemInformationClass == 0xb: # SystemModuleInformation # only 1 module for ntoskrnl.exe # FIXME: let users customize this? - size = 4 + ctypes.sizeof(rpmi_class) * NumberOfModules + num_modules = 1 + + rpm_struct = make_rtl_process_modules(ql.arch.bits, num_modules) - if params["ReturnLength"] != 0: - ql.mem.write_ptr(params["ReturnLength"], size) + if ReturnLength: + ql.mem.write_ptr(ReturnLength, rpm_struct.sizeof()) - if params["SystemInformationLength"] < size: + # if SystemInformationLength = 0, we return the total size in ReturnLength + if SystemInformationLength < rpm_struct.sizeof(): return STATUS_INFO_LENGTH_MISMATCH - else: # return all the loaded modules - module = make_rtl_process_module_info(ql.arch.bits) - module.Section = 0 - module.MappedBase = 0 + with rpm_struct.ref(ql.mem, SystemInformation) as rpm_obj: + rpm_obj.NumberOfModules = num_modules + + # cycle through all the loaded modules + for i in range(num_modules): + rpmi_obj = rpm_obj.Modules[i] - if ql.loader.is_driver: - image = ql.loader.get_image_by_name("ntoskrnl.exe") - assert image, 'image is a driver, but ntoskrnl.exe was not loaded' + # FIXME: load real values instead of bogus ones + rpmi_obj.Section = 0 + rpmi_obj.MappedBase = 0 - module.ImageBase = image.base + if ql.loader.is_driver: + image = ql.loader.get_image_by_name("ntoskrnl.exe") + assert image, 'image is a driver, but ntoskrnl.exe was not loaded' - module.ImageSize = 0xab000 - module.Flags = 0x8804000 - module.LoadOrderIndex = 0 # order of this module - module.InitOrderIndex = 0 - module.LoadCount = 1 - module.OffsetToFileName = len(b"\\SystemRoot\\system32\\") - module.FullPathName = b"\\SystemRoot\\system32\\ntoskrnl.exe" + rpmi_obj.ImageBase = image.base - process_modules = ql.pack32(NumberOfModules) + bytes(module) - ql.mem.write(params["SystemInformation"], process_modules) + rpmi_obj.ImageSize = 0xab000 + rpmi_obj.Flags = 0x8804000 + rpmi_obj.LoadOrderIndex = 0 # order of this module + rpmi_obj.InitOrderIndex = 0 + rpmi_obj.LoadCount = 1 + rpmi_obj.OffsetToFileName = len(b"\\SystemRoot\\system32\\") + rpmi_obj.FullPathName = b"\\SystemRoot\\system32\\ntoskrnl.exe" return STATUS_SUCCESS @@ -867,8 +862,6 @@ def hook_PsGetCurrentProcess(ql: Qiling, address: int, params): # HANDLE PsGetCurrentProcessId(); @winsdkapi(cc=STDCALL, params={}) def hook_PsGetCurrentProcessId(ql: Qiling, address: int, params): - # current process ID is 101 - # TODO: let user customize this? return ql.os.pid # NTSTATUS diff --git a/qiling/os/windows/dlls/shell32.py b/qiling/os/windows/dlls/shell32.py index 7c6e4dd96..1b448ca6b 100644 --- a/qiling/os/windows/dlls/shell32.py +++ b/qiling/os/windows/dlls/shell32.py @@ -5,7 +5,6 @@ import ntpath import os -from typing import Sequence from qiling import Qiling from qiling.os.windows.api import * @@ -15,7 +14,7 @@ from qiling.os.windows.handle import Handle from qiling.os.windows.thread import QlWindowsThread, THREAD_STATUS from qiling.exception import QlErrorNotImplemented -from qiling.os.windows.structs import ShellExecuteInfoA +from qiling.os.windows.structs import make_shellex_info def _SHGetFileInfo(ql: Qiling, address: int, params) -> int: uFlags = params["uFlags"] @@ -61,18 +60,20 @@ def hook_SHGetFileInfoA(ql: Qiling, address: int, params): def hook_SHGetFileInfoW(ql: Qiling, address: int, params): return _SHGetFileInfo(ql, address, params) -def _ShellExecute(ql: Qiling, obj: ShellExecuteInfoA): - def __wstr(shellex: Sequence): - return ql.os.utils.read_wstring(shellex[0]) if shellex[0] else '' +def _ShellExecute(ql: Qiling, shellex_obj, *, wide: bool): + read_str = ql.os.utils.read_wstring if wide else ql.os.utils.read_cstring + + def __read_str(ptr: int): + return read_str(ptr) if ptr else '' ql.log.debug(f'Target executed a shell command!') - ql.log.debug(f' | Operation : "{__wstr(obj.verb)}"') - ql.log.debug(f' | Parameters : "{__wstr(obj.params)}"') - ql.log.debug(f' | File : "{__wstr(obj.file)}"') - ql.log.debug(f' | Directory : "{__wstr(obj.dir)}"') + ql.log.debug(f' | Operation : "{__read_str(shellex_obj.lpVerb)}"') + ql.log.debug(f' | File : "{__read_str(shellex_obj.lpFile)}"') + ql.log.debug(f' | Parameters : "{__read_str(shellex_obj.lpParameters)}"') + ql.log.debug(f' | Directory : "{__read_str(shellex_obj.lpDirectory)}"') - if obj.show[0] == SW_HIDE: - ql.log.debug(" | With an hidden window") + if shellex_obj.nShow == SW_HIDE: + ql.log.debug(' | With an hidden window') process = QlWindowsThread(ql, status=THREAD_STATUS.READY) handle = Handle(obj=process) @@ -87,17 +88,16 @@ def __wstr(shellex: Sequence): 'pExecInfo' : POINTER }) def hook_ShellExecuteExW(ql: Qiling, address: int, params): - pointer = params["pExecInfo"] + pExecInfo = params['pExecInfo'] - shellInfo = ShellExecuteInfoA(ql) - shellInfo.read(pointer) + shellex_struct = make_shellex_info(ql.arch.bits) - handle = _ShellExecute(ql, shellInfo) + with shellex_struct.ref(ql.mem, pExecInfo) as shellex_obj: + handle = _ShellExecute(ql, shellex_obj, wide=True) - # Write results - shellInfo.instApp[0] = 0x21 - shellInfo.process[0] = handle.id - shellInfo.write(pointer) + # Write results + shellex_obj.hInstApp = 33 + shellex_obj.hProcess = handle.id return 1 @@ -118,15 +118,18 @@ def hook_ShellExecuteExW(ql: Qiling, address: int, params): 'nShowCmd' : INT }) def hook_ShellExecuteW(ql: Qiling, address: int, params): - _ShellExecute(ql, ShellExecuteInfoA( - ql, - hwnd=params["hwnd"], - lpVerb=params["lpOperation"], - lpFile=params["lpFile"], - lpParams=params["lpParameters"], - lpDir=params["lpDirectory"], - show=params["nShowCmd"] - )) + shellex_struct = make_shellex_info(ql.arch.bits) + + shellex_obj = shellex_struct( + hwnd = params['hwnd'], + lpVerb = params['lpOperation'], + lpFile = params['lpFile'], + lpParameters = params['lpParameters'], + lpDirectory = params['lpDirectory'], + nShow = params['nShowCmd'] + ) + + _ShellExecute(ql, shellex_obj, wide=True) return 33 diff --git a/qiling/os/windows/dlls/user32.py b/qiling/os/windows/dlls/user32.py index ef8af547a..3c816646e 100644 --- a/qiling/os/windows/dlls/user32.py +++ b/qiling/os/windows/dlls/user32.py @@ -838,11 +838,11 @@ def hook_MessageBoxA(ql: Qiling, address: int, params): 'lpPoint' : LPPOINT }) def hook_GetCursorPos(ql: Qiling, address: int, params): - dest = params["lpPoint"] + lpPoint = params['lpPoint'] # TODO: maybe we can add it to the profile too - p = Point(ql, 50, 50) - p.write(dest) + p = Point(50, 50) + p.save_to(ql.mem, lpPoint) return 0 diff --git a/qiling/os/windows/dlls/wsock32.py b/qiling/os/windows/dlls/wsock32.py index 3196c3e61..1f303b195 100644 --- a/qiling/os/windows/dlls/wsock32.py +++ b/qiling/os/windows/dlls/wsock32.py @@ -51,22 +51,41 @@ def hook_WSASocketA(ql: Qiling, address: int, params): 'namelen' : INT }) def hook_connect(ql: Qiling, address: int, params): - sin_family = ql.mem.read(params["name"], 1)[0] - sin_port = int.from_bytes(ql.mem.read(params["name"] + 2, 2), byteorder="big") + name = params["name"] + namelen = params['namelen'] + + # sockaddr structure type needs to be defined based on the family field value. + # both types (for ipv4 and ipv6) have it on the same offset, so we assume ipv4 + # by default for convinience. + sockaddr_struct = make_sockaddr_in() + + # sockaddr is not based on BaseStruct, so we have to read it manually + sockaddr_data = ql.mem.read(name, namelen) + sockaddr_obj = sockaddr_struct.from_buffer_copy(sockaddr_data[:ctypes.sizeof(sockaddr_struct)]) + + sin_family = sockaddr_obj.sin_family + sin_port = sockaddr_obj.sin_port if sin_family == 0x17: # IPv6 - segments = list(map("{:02x}".format, ql.mem.read(params["name"] + 8, 16))) - sin_addr = ":".join(["".join(x) for x in zip(segments[0::2], segments[1::2])]) + sockaddr_struct = make_sockaddr_in6() + sockaddr_obj = sockaddr_struct.from_buffer_copy(sockaddr_data[:ctypes.sizeof(sockaddr_struct)]) + + # read address bytes and show them as pairs + segments = [f'{b:02x}' for b in sockaddr_obj.sin6_addr.Byte] + sin_addr = ':'.join(''.join(x) for x in zip(segments[0::2], segments[1::2])) elif sin_family == 0x2: # IPv4 - sin_addr = ".".join((str(octet) for octet in ql.mem.read(params["name"] + 4, 4))) + a = sockaddr_obj.sin_addr + sin_addr = '.'.join((a.s_b1, a.s_b2, a.s_b3, a.s_b4)) + else: ql.log.debug("sockaddr sin_family unhandled variant") return 0 - ql.log.debug(f"{params['name']:#010x}: sockaddr_in{6 if sin_family == 0x17 else ''}", - f"{{sin_family={sin_family:#04x}, sin_port={sin_port}, sin_addr={sin_addr}}}", - sep="") + ql.log.debug(f'{sockaddr_struct.__name__} @ {name:#010x} : {sin_family=:#04x}, {sin_port=}, {sin_addr=}') + + # FIXME: wait.. we just printed stuff out, and did not connect anywhere.. + return 0 # hostent * gethostbyname( @@ -76,14 +95,39 @@ def hook_connect(ql: Qiling, address: int, params): 'name' : POINTER }) def hook_gethostbyname(ql: Qiling, address: int, params): - ip_str = ql.os.profile.get("NETWORK", "dns_response_ip") + name_ptr = params['name'] + + # set string value back to arg to let it show on log + params['name'] = ql.os.utils.read_cstring(name_ptr) + + # prepare the ip address data bytes + ip_str = ql.os.profile.get('NETWORK', 'dns_response_ip') ip = bytes((int(octet) for octet in reversed(ip_str.split('.')))) - name_ptr = params["name"] - # params["name"] = ql.os.utils.read_cstring(name_ptr) + # allocate room for the ip address data bytes + list_item = ql.os.heap.alloc(len(ip)) + ql.mem.write(list_item, ip) + + # allocate room for a list with one item, followed by a nullptr sentinel + addr_list = ql.os.heap.alloc(2 * ql.arch.pointersize) + + ql.mem.write_ptr(addr_list + 0 * ql.arch.pointersize, list_item) + ql.mem.write_ptr(addr_list + 1 * ql.arch.pointersize, 0) + + # we need a pointer to a nullptr; reuse the sentinel address for that + d_nullptr = addr_list + 1 * ql.arch.pointersize + + hostent_struct = make_hostent(ql.arch.bits) + hostnet_addr = ql.os.heap.alloc(hostent_struct.sizeof()) + + hostent_obj = hostent_struct( + h_name = name_ptr, + h_aliases = d_nullptr, + h_addrtype = 2, # AF_INET + h_length = len(ip), + h_addr_list = addr_list + ) - hostnet = Hostent(ql, name_ptr, 0, 2, 4, ip) - hostnet_addr = ql.os.heap.alloc(hostnet.size) - hostnet.write(hostnet_addr) + hostent_obj.save_to(ql.mem, hostnet_addr) return hostnet_addr diff --git a/qiling/os/windows/structs.py b/qiling/os/windows/structs.py index fd30b4b46..433330150 100644 --- a/qiling/os/windows/structs.py +++ b/qiling/os/windows/structs.py @@ -6,60 +6,21 @@ import ctypes from enum import IntEnum +from functools import lru_cache -from qiling import Qiling +from qiling.os import struct +from qiling.os.windows.const import MAX_PATH from qiling.os.windows.handle import Handle from qiling.exception import QlErrorNotImplemented from .wdk_const import IRP_MJ_MAXIMUM_FUNCTION, PROCESSOR_FEATURE_MAX -def __make_struct(archbits: int): - """Provide a ctypes Structure base class based on the underlying - architecture properties. - """ - - class Struct(ctypes.LittleEndianStructure): - _pack_ = archbits // 8 - - return Struct - - -def __select_native_type(archbits: int): - """Select a ctypes integer type with the underlying architecture - native size. - """ - - __type = { - 32 : ctypes.c_uint32, - 64 : ctypes.c_uint64 - } - - return __type[archbits] - -def __select_pointer_type(archbits: int): - """Provide a pointer base class based on the underlying - architecture properties. +def make_teb(archbits: int): + """Generate a TEB structure class. """ - native_type = __select_native_type(archbits) - Struct = __make_struct(archbits) - - class Pointer(Struct): - _fields_ = ( - ('value', native_type), - ) - - return Pointer - - -def make_teb(archbits: int, *args, **kwargs): - """Initialize a TEB structure. - - Additional arguments may be used to initialize specific TEB fields. - """ - - native_type = __select_native_type(archbits) - Struct = __make_struct(archbits) + native_type = struct.get_native_type(archbits) + Struct = struct.get_aligned_struct(archbits) class TEB(Struct): _fields_ = ( @@ -87,17 +48,28 @@ class TEB(Struct): ('ReservedOS', ctypes.c_byte * 216) ) - return TEB(*args, **kwargs) + return TEB -def make_peb(archbits: int, *args, **kwargs): - """Initialize a PEB structure. - - Additional arguments may be used to initialize specific PEB fields. +def make_peb(archbits: int): + """Generate a PEB structure class. """ - native_type = __select_native_type(archbits) - Struct = __make_struct(archbits) + native_type = struct.get_native_type(archbits) + Struct = struct.get_aligned_struct(archbits) + + # expected peb structure size + expected_size = { + 32: 0x47c, + 64: 0x7c8 + }[archbits] + + # pad to expected size based on currently defined set of fields. + # this is not very elegant, but ctypes.resize does not work on classes + padding_size = expected_size - { + 32: 0x70, + 64: 0xc8 + }[archbits] # https://www.geoffchappell.com/studies/windows/win32/ntdll/structs/peb/index.htm class PEB(Struct): @@ -131,37 +103,27 @@ class PEB(Struct): ('UnicodeCaseTableData', native_type), ('NumberOfProcessors', ctypes.c_int32), ('NtGlobalFlag', ctypes.c_int32), - ('CriticalSectionTimeout', native_type) - # ... more - ) + ('CriticalSectionTimeout', native_type), - obj_size = { - 32: 0x047c, - 64: 0x07c8 - }[archbits] + # more fields to be added here. in the meantime, pad the + # structure to the size it is expected to be + ('_padding', ctypes.c_char * padding_size) + ) - obj = PEB(*args, **kwargs) - ctypes.resize(obj, obj_size) + # make sure mismatches in peb size are not overlooked + assert PEB.sizeof() == expected_size - return obj + return PEB # https://docs.microsoft.com/en-us/windows/win32/api/subauth/ns-subauth-unicode_string -# -# typedef struct _UNICODE_STRING { -# USHORT Length; -# USHORT MaximumLength; -# PWSTR Buffer; -# } UNICODE_STRING, *PUNICODE_STRING; - -def make_unicode_string(archbits: int, *args, **kwargs): - """Initialize a Unicode String structure. - - Additional arguments may be used to initialize specific structure fields. +@lru_cache(maxsize=2) +def make_unicode_string(archbits: int): + """Generate a UNICODE_STRING structure class. """ - native_type = __select_native_type(archbits) - Struct = __make_struct(archbits) + native_type = struct.get_native_type(archbits) + Struct = struct.get_aligned_struct(archbits) class UNICODE_STRING(Struct): _fields_ = ( @@ -170,162 +132,116 @@ class UNICODE_STRING(Struct): ('Buffer', native_type) ) - return UNICODE_STRING(*args, **kwargs) + return UNICODE_STRING + # https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_driver_object -# -# typedef struct _DRIVER_OBJECT { -# CSHORT Type; -# CSHORT Size; -# PDEVICE_OBJECT DeviceObject; -# ULONG Flags; -# PVOID DriverStart; -# ULONG DriverSize; -# PVOID DriverSection; -# PDRIVER_EXTENSION DriverExtension; -# UNICODE_STRING DriverName; -# PUNICODE_STRING HardwareDatabase; -# PFAST_IO_DISPATCH FastIoDispatch; -# PDRIVER_INITIALIZE DriverInit; -# PDRIVER_STARTIO DriverStartIo; -# PDRIVER_UNLOAD DriverUnload; -# PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1]; -# } DRIVER_OBJECT, *PDRIVER_OBJECT; - -def make_driver_object(ql: Qiling, base: int, archbits: int): - native_type = __select_native_type(archbits) - pointer_type = __select_pointer_type(archbits) - Struct = __make_struct(archbits) - - ucstrtype = make_unicode_string(archbits).__class__ +def make_driver_object(archbits: int): + """Generate a DRIVER_OBJECT structure class. + """ + + native_type = struct.get_native_type(archbits) + Struct = struct.get_aligned_struct(archbits) + + ucstr_struct = make_unicode_string(archbits) class DRIVER_OBJECT(Struct): _fields_ = ( - ('_Type', ctypes.c_uint16), - ('_Size', ctypes.c_uint16), - ('_DeviceObject', pointer_type), - ('_Flags', ctypes.c_uint32), - ('_DriverStart', pointer_type), - ('_DriverSize', ctypes.c_uint32), - ('_DriverSection', pointer_type), - ('_DriverExtension', pointer_type), - ('_DriverName', ucstrtype), - ('_HardwareDatabase', pointer_type), - ('_FastIoDispatch', pointer_type), - ('_DriverInit', pointer_type), - ('_DriverStartIo', pointer_type), - ('_DriverUnload', pointer_type), - ('_MajorFunction', native_type * (IRP_MJ_MAXIMUM_FUNCTION + 1)) + ('Type', ctypes.c_uint16), + ('Size', ctypes.c_uint16), + ('DeviceObject', native_type), + ('Flags', ctypes.c_uint32), + ('DriverStart', native_type), + ('DriverSize', ctypes.c_uint32), + ('DriverSection', native_type), + ('DriverExtension', native_type), + ('DriverName', ucstr_struct), + ('HardwareDatabase', native_type), + ('FastIoDispatch', native_type), + ('DriverInit', native_type), + ('DriverStartIo', native_type), + ('DriverUnload', native_type), + ('MajorFunction', native_type * (IRP_MJ_MAXIMUM_FUNCTION + 1)) ) - def __read_obj(self) -> 'DRIVER_OBJECT': - data = ql.mem.read(base, ctypes.sizeof(self)) - - return self.__class__.from_buffer(data) - - def __write_obj(self) -> None: - ql.mem.write(base, bytes(self)) - - # get MajorFunction - @property - def MajorFunction(self): - obj = self.__read_obj() - - return getattr(obj, '_MajorFunction') + return DRIVER_OBJECT - @property - def DeviceObject(self): - obj = self.__read_obj() - return getattr(obj, '_DeviceObject').value - - @DeviceObject.setter - def DeviceObject(self, value): - obj = self.__read_obj() - getattr(obj, '_DeviceObject').value = value - - obj.__write_obj() - - @property - def DriverUnload(self): - obj = self.__read_obj() - - return getattr(obj, '_DriverUnload').value - - return DRIVER_OBJECT() - -class KSYSTEM_TIME(ctypes.Structure): +class KSYSTEM_TIME(struct.BaseStruct): _fields_ = ( - ('LowPart', ctypes.c_uint32), + ('LowPart', ctypes.c_uint32), ('High1Time', ctypes.c_int32), ('High2Time', ctypes.c_int32) ) -class LARGE_INTEGER_DUMMYSTRUCTNAME(ctypes.LittleEndianStructure): +class _LARGE_INTEGER(struct.BaseStruct): _fields_ = ( - ('LowPart', ctypes.c_uint32), - ('HighPart', ctypes.c_int32), + ('LowPart', ctypes.c_uint32), + ('HighPart', ctypes.c_int32) ) class LARGE_INTEGER(ctypes.Union): _fields_ = ( - ('u', LARGE_INTEGER_DUMMYSTRUCTNAME), - ('QuadPart', ctypes.c_int64), + ('u', _LARGE_INTEGER), + ('QuadPart', ctypes.c_int64) ) +# see: # https://www.geoffchappell.com/studies/windows/km/ntoskrnl/structs/kuser_shared_data/index.htm -# -# struct information: -# https://doxygen.reactos.org/d8/dae/modules_2rostests_2winetests_2ntdll_2time_8c_source.html +# https://doxygen.reactos.org/d7/deb/xdk_2ketypes_8h_source.html#l01155 -class KUSER_SHARED_DATA(ctypes.LittleEndianStructure): +class KUSER_SHARED_DATA(struct.BaseStruct): _fields_ = ( - ('TickCountLowDeprecated', ctypes.c_uint32), - ('TickCountMultiplier', ctypes.c_uint32), - ('InterruptTime', KSYSTEM_TIME), - ('SystemTime', KSYSTEM_TIME), - ('TimeZoneBias', KSYSTEM_TIME), - ('ImageNumberLow', ctypes.c_uint16), - ('ImageNumberHigh', ctypes.c_uint16), - ('NtSystemRoot', ctypes.c_uint16 * 260), - ('MaxStackTraceDepth', ctypes.c_uint32), - ('CryptoExponent', ctypes.c_uint32), - ('TimeZoneId', ctypes.c_uint32), - ('LargePageMinimum', ctypes.c_uint32), - ('Reserved2', ctypes.c_uint32 * 7), - ('NtProductType', ctypes.c_uint32), - ('ProductTypeIsValid', ctypes.c_uint32), - ('NtMajorVersion', ctypes.c_uint32), - ('NtMinorVersion', ctypes.c_uint32), - ('ProcessorFeatures', ctypes.c_uint8 * PROCESSOR_FEATURE_MAX), - ('Reserved1', ctypes.c_uint32), - ('Reserved3', ctypes.c_uint32), - ('TimeSlip', ctypes.c_uint32), - ('AlternativeArchitecture', ctypes.c_uint32), - ('AltArchitecturePad', ctypes.c_uint32), - ('SystemExpirationDate', LARGE_INTEGER), - ('SuiteMask', ctypes.c_uint32), - ('KdDebuggerEnabled', ctypes.c_uint8), - ('NXSupportPolicy', ctypes.c_uint8), - ('ActiveConsoleId', ctypes.c_uint32), - ('DismountCount', ctypes.c_uint32), - ('ComPlusPackage', ctypes.c_uint32), + ('TickCountLowDeprecated', ctypes.c_uint32), + ('TickCountMultiplier', ctypes.c_uint32), + ('InterruptTime', KSYSTEM_TIME), + ('SystemTime', KSYSTEM_TIME), + ('TimeZoneBias', KSYSTEM_TIME), + ('ImageNumberLow', ctypes.c_uint16), + ('ImageNumberHigh', ctypes.c_uint16), + ('NtSystemRoot', ctypes.c_wchar * MAX_PATH), + ('MaxStackTraceDepth', ctypes.c_uint32), + ('CryptoExponent', ctypes.c_uint32), + ('TimeZoneId', ctypes.c_uint32), + ('LargePageMinimum', ctypes.c_uint32), + ('Reserved2', ctypes.c_uint32 * 7), + ('NtProductType', ctypes.c_uint32), + ('ProductTypeIsValid', ctypes.c_uint32), + ('NtMajorVersion', ctypes.c_uint32), + ('NtMinorVersion', ctypes.c_uint32), + ('ProcessorFeatures', ctypes.c_uint8 * PROCESSOR_FEATURE_MAX), + ('Reserved1', ctypes.c_uint32), + ('Reserved3', ctypes.c_uint32), + ('TimeSlip', ctypes.c_uint32), + ('AlternativeArchitecture', ctypes.c_uint32), + ('AltArchitecturePad', ctypes.c_uint32), + ('SystemExpirationDate', LARGE_INTEGER), + ('SuiteMask', ctypes.c_uint32), + ('KdDebuggerEnabled', ctypes.c_uint8), + ('NXSupportPolicy', ctypes.c_uint8), + ('ActiveConsoleId', ctypes.c_uint32), + ('DismountCount', ctypes.c_uint32), + ('ComPlusPackage', ctypes.c_uint32), ('LastSystemRITEventTickCount', ctypes.c_uint32), - ('NumberOfPhysicalPages', ctypes.c_uint32), - ('SafeBootMode', ctypes.c_uint8), - ('TscQpcData', ctypes.c_uint8), - ('TscQpcFlags', ctypes.c_uint8), - ('TscQpcPad', ctypes.c_uint8 * 3), - ('SharedDataFlags', ctypes.c_uint8), - ('DataFlagsPad', ctypes.c_uint8 * 3), - ('TestRetInstruction', ctypes.c_uint8), - ('_padding0', ctypes.c_uint8 * 0x2F8)) + ('NumberOfPhysicalPages', ctypes.c_uint32), + ('SafeBootMode', ctypes.c_uint8), + ('TscQpcData', ctypes.c_uint8), # also: VirtualizationFlags + ('TscQpcFlags', ctypes.c_uint8), + ('TscQpcPad', ctypes.c_uint8 * 3), + ('SharedDataFlags', ctypes.c_uint8), + ('DataFlagsPad', ctypes.c_uint8 * 3), + ('TestRetInstruction', ctypes.c_uint8), + + # pad structure to expected structure size + ('_padding0', ctypes.c_uint8 * 0x2F8) + ) + def make_list_entry(archbits: int): - native_type = __select_native_type(archbits) - Struct = __make_struct(archbits) + native_type = struct.get_native_type(archbits) + Struct = struct.get_aligned_struct(archbits) class LIST_ENTRY(Struct): _fields_ = ( @@ -335,29 +251,30 @@ class LIST_ENTRY(Struct): return LIST_ENTRY + def make_device_object(archbits: int): - native_type = __select_native_type(archbits) - pointer_type = __select_pointer_type(archbits) - Struct = __make_struct(archbits) + native_type = struct.get_native_type(archbits) + Struct = struct.get_aligned_struct(archbits) + Union = struct.get_aligned_union(archbits) - LIST_ENTRY = make_list_entry(archbits) + pointer_type = native_type + ListEntry = make_list_entry(archbits) class KDEVICE_QUEUE_ENTRY(Struct): _fields_ = ( - ('DeviceListEntry', LIST_ENTRY), + ('DeviceListEntry', ListEntry), ('SortKey', ctypes.c_uint32), ('Inserted', ctypes.c_uint8) ) class WAIT_ENTRY(Struct): _fields_ = ( - ('DmaWaitEntry', LIST_ENTRY), + ('DmaWaitEntry', ListEntry), ('NumberOfChannels', ctypes.c_uint32), ('DmaContext', ctypes.c_uint32) ) - class WAIT_QUEUE_UNION(ctypes.Union): - _pack_ = archbits // 8 + class WAIT_QUEUE_UNION(Union): _fields_ = ( ("WaitQueueEntry", KDEVICE_QUEUE_ENTRY), ("Dma", WAIT_ENTRY) @@ -378,23 +295,18 @@ class KDEVICE_QUEUE(Struct): _fields_ = ( ('Type', ctypes.c_int16), ('Size', ctypes.c_int16), - ('DeviceListHead', LIST_ENTRY), + ('DeviceListHead', ListEntry), ('Lock', ctypes.c_uint32), ('Busy', ctypes.c_uint8) ) - # class SINGLE_LIST_ENTRY(Struct): - # _fields_ = ( - # ('Next', native_type), - # ) - # https://github.com/ntdiff/headers/blob/master/Win10_1507_TS1/x64/System32/hal.dll/Standalone/_KDPC.h class KDPC(Struct): _fields_ = ( ('Type', ctypes.c_uint8), ('Importance', ctypes.c_uint8), ('Number', ctypes.c_uint16), - ('DpcListEntry', LIST_ENTRY), + ('DpcListEntry', ListEntry), ('DeferredRoutine', pointer_type), ('DeferredContext', pointer_type), ('SystemArgument1', pointer_type), @@ -406,7 +318,7 @@ class DISPATCHER_HEADER(Struct): _fields_ = ( ('Lock', ctypes.c_int32), ('SignalState', ctypes.c_int32), - ('WaitListHead', LIST_ENTRY) + ('WaitListHead', ListEntry) ) # https://docs.microsoft.com/vi-vn/windows-hardware/drivers/ddi/wdm/ns-wdm-_device_object @@ -439,36 +351,15 @@ class DEVICE_OBJECT(Struct): ('Reserved', pointer_type) ) - return DEVICE_OBJECT() - + return DEVICE_OBJECT -# struct IO_STATUS_BLOCK { -# union { -# NTSTATUS Status; -# PVOID Pointer; -# } DUMMYUNIONNAME; -# ULONG_PTR Information; -# }; -def make_irp(archbits: int): - pointer_type = __select_pointer_type(archbits) - native_type = __select_native_type(archbits) - Struct = __make_struct(archbits) +def make_io_stack_location(archbits: int): + native_type = struct.get_native_type(archbits) + Struct = struct.get_aligned_struct(archbits) + Union = struct.get_aligned_union(archbits) - LIST_ENTRY = make_list_entry(archbits) - - class IO_STATUS_BLOCK_DUMMY(ctypes.Union): - _pack_ = archbits // 8 - _fields_ = ( - ('Status', ctypes.c_int32), - ('Pointer', pointer_type) - ) - - class IO_STATUS_BLOCK(Struct): - _fields_ = ( - ('Status', IO_STATUS_BLOCK_DUMMY), - ('Information', pointer_type) - ) + pointer_type = native_type class IO_STACK_LOCATION_FILESYSTEMCONTROL(Struct): _fields_ = ( @@ -494,8 +385,7 @@ class IO_STACK_LOCATION_WRITE(Struct): ('ByteOffset', LARGE_INTEGER) ) - class IO_STACK_LOCATION_PARAM(ctypes.Union): - _pack_ = archbits // 8 + class IO_STACK_LOCATION_PARAM(Union): _fields_ = ( ('FileSystemControl', IO_STACK_LOCATION_FILESYSTEMCONTROL), ('DeviceIoControl', IO_STACK_LOCATION_DEVICEIOCONTROL), @@ -512,11 +402,33 @@ class IO_STACK_LOCATION(Struct): ('DeviceObject', pointer_type), ('FileObject', pointer_type), ('CompletionRoutine', pointer_type), - ('Context', pointer_type), + ('Context', pointer_type) + ) + + return IO_STACK_LOCATION + + +def make_irp(archbits: int): + native_type = struct.get_native_type(archbits) + Struct = struct.get_aligned_struct(archbits) + Union = struct.get_aligned_union(archbits) + + pointer_type = native_type + ListEntry = make_list_entry(archbits) + + class IO_STATUS_BLOCK_DUMMY(Union): + _fields_ = ( + ('Status', ctypes.c_int32), + ('Pointer', pointer_type) ) - class AssociatedIrp(ctypes.Union): - _pack_ = archbits // 8 + class IO_STATUS_BLOCK(Struct): + _fields_ = ( + ('Status', IO_STATUS_BLOCK_DUMMY), + ('Information', pointer_type) + ) + + class AssociatedIrp(Union): _fields_ = ( ('MasterIrp', pointer_type), ('IrpCount', ctypes.c_uint32), @@ -532,20 +444,20 @@ class IRP(Struct): ('MdlAddress', pointer_type), ('Flags', ctypes.c_uint32), ('AssociatedIrp', AssociatedIrp), - ('ThreadListEntry', LIST_ENTRY), + ('ThreadListEntry', ListEntry), ('IoStatus', IO_STATUS_BLOCK), - ('_padding1', ctypes.c_char * 8), + ('_padding3', ctypes.c_char * 8), ('UserIosb', pointer_type), ('UserEvent', pointer_type), ('Overlay', ctypes.c_char * (8 * sz_factor)), ('CancelRoutine', pointer_type), ('UserBuffer', pointer_type), ('_padding1', ctypes.c_char * (32 * sz_factor)), - ('irpstack', ctypes.POINTER(IO_STACK_LOCATION)), + ('irpstack', pointer_type), # points to a IO_STACK_LOCATION structure ('_padding2', ctypes.c_char * (8 * sz_factor)) ) - return IRP() + return IRP # typedef struct _MDL { @@ -559,10 +471,11 @@ class IRP(Struct): # ULONG ByteOffset; # } MDL, *PMDL; - def make_mdl(archbits: int): - pointer_type = __select_pointer_type(archbits) - Struct = __make_struct(archbits) + native_type = struct.get_native_type(archbits) + Struct = struct.get_aligned_struct(archbits) + + pointer_type = native_type class MDL(Struct): _fields_ = ( @@ -576,7 +489,7 @@ class MDL(Struct): ('ByteOffset', ctypes.c_uint32) ) - return MDL() + return MDL # NOTE: the following classes are currently not needed # @@ -808,25 +721,33 @@ class MDL(Struct): # USHORT OffsetToFileName; # UCHAR FullPathName[256]; # } RTL_PROCESS_MODULE_INFORMATION, -def make_rtl_process_module_info(archbits: int): - native_type = __select_native_type(archbits) - Struct = __make_struct(archbits) + +def make_rtl_process_modules(archbits: int, num_modules: int): + native_type = struct.get_native_type(archbits) + Struct = struct.get_aligned_struct(archbits) class RTL_PROCESS_MODULE_INFORMATION(Struct): _fields_ = ( - ('Section', native_type), - ('MappedBase', native_type), - ('ImageBase', native_type), - ('ImageSize', ctypes.c_uint32), - ('Flags', ctypes.c_uint32), - ('LoadOrderIndex', ctypes.c_uint16), - ('InitOrderIndex', ctypes.c_uint16), - ('LoadCount', ctypes.c_uint16), + ('Section', native_type), + ('MappedBase', native_type), + ('ImageBase', native_type), + ('ImageSize', ctypes.c_uint32), + ('Flags', ctypes.c_uint32), + ('LoadOrderIndex', ctypes.c_uint16), + ('InitOrderIndex', ctypes.c_uint16), + ('LoadCount', ctypes.c_uint16), ('OffsetToFileName', ctypes.c_uint16), - ('FullPathName', ctypes.c_char * 256) + ('FullPathName', ctypes.c_char * 256) ) - return RTL_PROCESS_MODULE_INFORMATION() + class RTL_PROCESS_MODULES(Struct): + _fields_ = ( + ('NumberOfModules', ctypes.c_uint32), + ('Modules', RTL_PROCESS_MODULE_INFORMATION * num_modules) + ) + + return RTL_PROCESS_MODULES + # struct _EPROCESS { # struct _KPROCESS Pcb; //0x0 @@ -979,58 +900,56 @@ class RTL_PROCESS_MODULE_INFORMATION(Struct): # }; def make_eprocess(archbits: int): - Struct = __make_struct(archbits) - - class EPROCESS(Struct): - _fields_ = ( - ('dummy', ctypes.c_uint8), - ) + Struct = struct.get_aligned_struct(archbits) obj_size = { 32: 0x2c0, 64: 0x4d0 }[archbits] - obj = EPROCESS() - ctypes.resize(obj, obj_size) + class EPROCESS(Struct): + # FIXME: define meaningful fields + _fields_ = ( + ('dummy', ctypes.c_char * obj_size), + ) - return obj + return EPROCESS def make_ldr_data(archbits: int): - native_type = __select_native_type(archbits) - Struct = __make_struct(archbits) + native_type = struct.get_native_type(archbits) + Struct = struct.get_aligned_struct(archbits) ListEntry = make_list_entry(archbits) class PEB_LDR_DATA(Struct): _fields_ = ( - ('Length', ctypes.c_uint32), - ('Initialized', ctypes.c_uint32), - ('SsHandle', native_type), - ('InLoadOrderModuleList', ListEntry), + ('Length', ctypes.c_uint32), + ('Initialized', ctypes.c_uint32), + ('SsHandle', native_type), + ('InLoadOrderModuleList', ListEntry), ('InMemoryOrderModuleList', ListEntry), ('InInitializationOrderModuleList', ListEntry), - ('EntryInProgress', native_type), - ('ShutdownInProgress', native_type), - ('selfShutdownThreadId', native_type) + ('EntryInProgress', native_type), + ('ShutdownInProgress', native_type), + ('selfShutdownThreadId', native_type) ) - return PEB_LDR_DATA() + return PEB_LDR_DATA def make_ldr_data_table_entry(archbits: int): - pointer_type = __select_pointer_type(archbits) - native_type = __select_native_type(archbits) - Struct = __make_struct(archbits) + native_type = struct.get_native_type(archbits) + Struct = struct.get_aligned_struct(archbits) + pointer_type = native_type ListEntry = make_list_entry(archbits) - UniString = make_unicode_string(archbits).__class__ + UniString = make_unicode_string(archbits) class RTL_BALANCED_NODE(Struct): _fields_ = ( - ('Left', pointer_type), - ('Right', pointer_type), + ('Left', pointer_type), + ('Right', pointer_type) ) class LdrDataTableEntry(Struct): @@ -1069,120 +988,100 @@ class LdrDataTableEntry(Struct): ('SigningLevel', ctypes.c_uint8) ) - return LdrDataTableEntry() + return LdrDataTableEntry -class FILETIME(ctypes.LittleEndianStructure): +class FILETIME(struct.BaseStruct): _fields_ = ( ('dwLowDateTime', ctypes.c_uint32), ('dwHighDateTime', ctypes.c_int32) ) +# https://docs.microsoft.com/en-us/windows/console/coord-str +class COORD(struct.BaseStruct): + _fields_ = ( + ('X', ctypes.c_uint16), + ('Y', ctypes.c_uint16) + ) -class WindowsStruct: +# https://docs.microsoft.com/en-us/windows/console/small-rect-str +class SMALL_RECT(struct.BaseStruct): + _fields_ = ( + ('Left', ctypes.c_uint16), + ('Top', ctypes.c_uint16), + ('Right', ctypes.c_uint16), + ('Bottom', ctypes.c_uint16) + ) + +# https://docs.microsoft.com/en-us/windows/console/console-screen-buffer-info-str +class CONSOLE_SCREEN_BUFFER_INFO(struct.BaseStruct): + _fields_ = ( + ('dwSize', COORD), + ('dwCursorPosition', COORD), + ('wAttributes', ctypes.c_uint16), + ('srWindow', SMALL_RECT), + ('dwMaximumWindowSize', COORD) + ) + +# https://docs.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntqueryinformationprocess +def make_process_basic_info(archbits: int): + native_type = struct.get_native_type(archbits) + Struct = struct.get_aligned_struct(archbits) + + class PROCESS_BASIC_INFORMATION(Struct): + _fields_ = ( + ('ExitStatus', ctypes.c_uint32), + ('PebBaseAddress', native_type), + ('AffinityMask', native_type), + ('BasePriority', ctypes.c_uint32), + ('UniqueProcessId', native_type), + ('InheritedFromUniqueProcessId', native_type) + ) + + return PROCESS_BASIC_INFORMATION + +# https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-osversioninfoa +def make_os_version_info(archbits: int, *, wide: bool): + Struct = struct.get_aligned_struct(archbits) + + char_type = (ctypes.c_wchar if wide else ctypes.c_char) + + class OSVERSIONINFO(Struct): + _fields_ = ( + ('dwOSVersionInfoSize', ctypes.c_uint32), + ('dwMajorVersion', ctypes.c_uint32), + ('dwMinorVersion', ctypes.c_uint32), + ('dwBuildNumber', ctypes.c_uint32), + ('dwPlatformId', ctypes.c_uint32), + ('szCSDVersion', char_type * 128) + ) + + return OSVERSIONINFO + + +# https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-osversioninfoexa +def make_os_version_info_ex(archbits: int, *, wide: bool): + Struct = struct.get_aligned_struct(archbits) + + char_type = (ctypes.c_wchar if wide else ctypes.c_char) + + class OSVERSIONINFOEX(Struct): + _fields_ = ( + ('dwOSVersionInfoSize', ctypes.c_uint32), + ('dwMajorVersion', ctypes.c_uint32), + ('dwMinorVersion', ctypes.c_uint32), + ('dwBuildNumber', ctypes.c_uint32), + ('dwPlatformId', ctypes.c_uint32), + ('szCSDVersion', char_type * 128), + ('wServicePackMajor', ctypes.c_uint16), + ('wServicePackMinor', ctypes.c_uint16), + ('wSuiteMask', ctypes.c_uint16), + ('wProductType', ctypes.c_uint8), + ('wReserved', ctypes.c_uint8) + ) + + return OSVERSIONINFOEX - def __init__(self, ql): - self.ql = ql - self.addr = None - self.ULONG_SIZE = 8 - self.LONG_SIZE = 4 - self.POINTER_SIZE = self.ql.arch.pointersize - self.INT_SIZE = 2 - self.DWORD_SIZE = 4 - self.WORD_SIZE = 2 - self.SHORT_SIZE = 2 - self.BYTE_SIZE = 1 - self.USHORT_SIZE = 2 - - def write(self, addr): - # I want to force the subclasses to implement it - raise NotImplementedError - - def read(self, addr): - # I want to force the subclasses to implement it - raise NotImplementedError - - def generic_write(self, addr: int, attributes: list): - self.ql.log.debug("Writing Windows object " + self.__class__.__name__) - already_written = 0 - for elem in attributes: - (val, size, endianness, typ) = elem - if typ == int: - value = val.to_bytes(size, endianness) - self.ql.log.debug("Writing to %#x with value %s" % (addr + already_written, value)) - self.ql.mem.write(addr + already_written, value) - elif typ == bytes: - if isinstance(val, bytearray): - value = bytes(val) - else: - value = val - self.ql.log.debug("Writing at addr %#x value %s" % (addr + already_written, value)) - - self.ql.mem.write(addr + already_written, value) - elif issubclass(typ, WindowsStruct): - val.write(addr) - else: - raise QlErrorNotImplemented("API not implemented") - - already_written += size - self.addr = addr - - def generic_read(self, addr: int, attributes: list): - self.ql.log.debug("Reading Windows object " + self.__class__.__name__) - already_read = 0 - for elem in attributes: - (val, size, endianness, type) = elem - value = self.ql.mem.read(addr + already_read, size) - self.ql.log.debug("Reading from %#x value %s" % (addr + already_read, value)) - if type == int: - elem[0] = int.from_bytes(value, endianness) - elif type == bytes: - elem[0] = value - elif issubclass(type, WindowsStruct): - obj = type(self.ql) - obj.read(addr) - elem[0] = obj - else: - raise QlErrorNotImplemented("API not implemented") - already_read += size - self.addr = addr - -class AlignedWindowsStruct(WindowsStruct): - def __init__(self, ql): - super().__init__(ql) - - def write(self, addr): - super().write(addr) - - def read(self, addr): - super().read(addr) - - def generic_write(self, addr: int, attributes: list): - super().generic_write(addr, attributes) - - def generic_read(self, addr: int, attributes: list): - self.ql.log.debug("Reading unpacked Windows object aligned " + self.__class__.__name__) - already_read = 0 - for elem in attributes: - (val, size, endianness, type, alignment) = elem - if already_read != 0: - modulo = already_read % alignment - already_read = already_read + modulo - - value = self.ql.mem.read(addr + already_read, size) - self.ql.log.debug("Reading from %x value %s" % (addr + already_read, value)) - if type == int: - elem[0] = int.from_bytes(value, endianness) - elif type == bytes: - elem[0] = value - elif issubclass(type, WindowsStruct): - obj = type(self.ql) - obj.read(addr) - elem[0] = obj - else: - raise QlErrorNotImplemented("API not implemented") - already_read += size - self.addr = addr class Token: class TokenInformationClass(IntEnum): @@ -1240,69 +1139,88 @@ class TokenInformationClass(IntEnum): def __init__(self, ql): # We will create them when we need it. There are too many structs self.struct = {} - self.ql = ql + # TODO find a GOOD reference paper for the values - self.struct[Token.TokenInformationClass.TokenUIAccess.value] = self.ql.pack(0x1) - self.struct[Token.TokenInformationClass.TokenGroups.value] = self.ql.pack(0x1) + self.struct[Token.TokenInformationClass.TokenUIAccess.value] = ql.pack(0x1) + self.struct[Token.TokenInformationClass.TokenGroups.value] = ql.pack(0x1) + # still not sure why 0x1234 executes gandcrab as admin, but 544 no. No idea (see sid refs for the values) - sub = 0x1234 if ql.os.profile["SYSTEM"]["permission"] == "root" else 545 - sub = sub.to_bytes(4, "little") - sid = Sid(self.ql, identifier=1, revision=1, subs_count=1, subs=sub) - sid_addr = self.ql.os.heap.alloc(sid.size) - sid.write(sid_addr) - handle = Handle(obj=sid, id=sid_addr) - self.ql.os.handle_manager.append(handle) - self.struct[Token.TokenInformationClass.TokenIntegrityLevel] = self.ql.pack(sid_addr) + subauths = (0x1234 if ql.os.profile["SYSTEM"]["permission"] == "root" else 545,) + + sid_struct = make_sid(auth_count=len(subauths)) + sid_addr = ql.os.heap.alloc(sid_struct.sizeof()) + + sid_obj = sid_struct( + Revision = 1, + SubAuthorityCount = len(subauths), + IdentifierAuthority = (1,), + SubAuthority = subauths + ) + + sid_obj.save_to(ql.mem, sid_addr) + + handle = Handle(obj=sid_obj, id=sid_addr) + ql.os.handle_manager.append(handle) + self.struct[Token.TokenInformationClass.TokenIntegrityLevel] = ql.pack(sid_addr) def get(self, value): res = self.struct[value] + if res is None: raise QlErrorNotImplemented("API not implemented") - else: - return res - - -# typedef struct _SID { -# BYTE Revision; -# BYTE SubAuthorityCount; -# SID_IDENTIFIER_AUTHORITY IdentifierAuthority; -# #if ... -# DWORD *SubAuthority[]; -# #else -# DWORD SubAuthority[ANYSIZE_ARRAY]; -# #endif -# } SID, *PISID; -class Sid(WindowsStruct): - # General Struct - # https://docs.microsoft.com/it-it/windows/win32/api/winnt/ns-winnt-sid - # https://en.wikipedia.org/wiki/Security_Identifier - - # Identf Authority - # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/c6ce4275-3d90-4890-ab3a-514745e4637e - # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/81d92bba-d22b-4a8c-908a-554ab29148ab - - def __init__(self, ql, revision=None, subs_count=None, identifier=None, subs=None): - # TODO find better documentation - super().__init__(ql) - self.revision = [revision, self.BYTE_SIZE, "little", int] - self.subs_count = [subs_count, self.BYTE_SIZE, "little", int] - # FIXME: understand if is correct to set them as big - self.identifier = [identifier, 6, "big", int] - self.subs = [subs, self.subs_count[0] * self.DWORD_SIZE, "little", bytes] - self.size = 2 + 6 + self.subs_count[0] * 4 - - def write(self, addr): - super().generic_write(addr, [self.revision, self.subs_count, self.identifier, self.subs]) - - def read(self, addr): - super().generic_read(addr, [self.revision, self.subs_count, self.identifier, self.subs]) - self.size = 2 + 6 + self.subs_count[0] * 4 - - def __eq__(self, other): - # FIXME - if not isinstance(other, Sid): - return False - return self.subs == other.subs and self.identifier[0] == other.identifier[0] + + return res + + +# Identf Authority +# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/c6ce4275-3d90-4890-ab3a-514745e4637e +# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/81d92bba-d22b-4a8c-908a-554ab29148ab +def make_sid(auth_count: int): + + # this structure should be a 6-bytes big endian integer. this is an attempt + # to approximate that, knowing that in practice only the most significant + # byte is actually used. + # + # see: https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-sid_identifier_authority + class SID_IDENTIFIER_AUTHORITY(ctypes.BigEndianStructure): + _pack_ = 1 + _fields_ = ( + ('Value', ctypes.c_uint32), + ('_pad', ctypes.c_byte * 2) + ) + + assert ctypes.sizeof(SID_IDENTIFIER_AUTHORITY) == 6 + + # https://geoffchappell.com/studies/windows/km/ntoskrnl/api/rtl/sertl/sid.htm + class SID(struct.BaseStruct): + _fields_ = ( + ('Revision', ctypes.c_uint8), + ('SubAuthorityCount', ctypes.c_uint8), + ('IdentifierAuthority', SID_IDENTIFIER_AUTHORITY), + + # note that ctypes does not have a way to define an array whose size is unknown + # or flexible. although the number of items should be reflected in SubAuthorityCount, + # this cannot be implemented. any change to that field will result in an inconsistency + # and should be avoided + + ('SubAuthority', ctypes.c_uint32 * auth_count) + ) + + # the need of a big-endian structure forces us to define a non-BaseStruct structure field + # which breaks the 'volatile_ref' mechanism. we here prevent the user from doing that + @classmethod + def volatile_ref(cls, *args): + raise NotImplementedError(f'{cls.__name__} is not capable of volatile references') + + # let SID structures be comparable + def __eq__(self, other): + if not isinstance(other, SID): + return False + + # since SID structure is not padded, we can simply memcmp the instances + return memoryview(self).cast('B') == memoryview(other).cast('B') + + return SID class Mutex: @@ -1344,525 +1262,281 @@ def isFree(self) -> bool: # ('UniqueThread', ctypes.c_uint64) # ) -# typedef struct tagPOINT { -# LONG x; -# LONG y; -# } POINT, *PPOINT; -class Point(WindowsStruct): - def __init__(self, ql, x=None, y=None): - super().__init__(ql) - self.x = [x, self.LONG_SIZE, "little", int] - self.y = [y, self.LONG_SIZE, "little", int] - self.size = self.LONG_SIZE * 2 - - def write(self, addr): - super().generic_write(addr, [self.x, self.y]) - - def read(self, addr): - super().generic_read(addr, [self.x, self.y]) - -# typedef struct _SYSTEM_BASIC_INFORMATION -# { -# ULONG Reserved; -# ULONG TimerResolution; -# ULONG PageSize; -# ULONG NumberOfPhysicalPages; -# ULONG LowestPhysicalPageNumber; -# ULONG HighestPhysicalPageNumber; -# ULONG AllocationGranularity; -# ULONG_PTR c; -# ULONG_PTR MaximumUserModeAddress; -# ULONG_PTR ActiveProcessorsAffinityMask; -# CCHAR NumberOfProcessors; -# } SYSTEM_BASIC_INFORMATION, * PSYSTEM_BASIC_INFORMATION; - -class SystemBasicInforation(WindowsStruct): - def __init__(self,ql, Reserved,TimerResolution,PageSize=None, NumberOfPhysicalPages=None, LowestPhysicalPageNumber=None, - HighestPhysicalPageNumber=None, AllocationGranularity=None,MinimumUserModeAddress=None, - MaximumUserModeAddress=None,ActiveProcessorsAffinityMask=None,NumberOfProcessors=None): - super().__init__(ql) - self.size=self.BYTE_SIZE * 24 + 5*self.POINTER_SIZE - self.Reserved =[Reserved, self.DWORD_SIZE, "little", int] - self.TimerResolution=[TimerResolution, self.DWORD_SIZE, "little", int] - self.PageSize=[PageSize, self.DWORD_SIZE, "little", int] - self.NumberOfPhysicalPages = [NumberOfPhysicalPages, self.DWORD_SIZE, "little", int] - self.LowestPhysicalPageNumber = [LowestPhysicalPageNumber, self.DWORD_SIZE, "little", int] - self.HighestPhysicalPageNumber = [HighestPhysicalPageNumber, self.DWORD_SIZE, "little", int] - self.AllocationGranularity = [AllocationGranularity, self.DWORD_SIZE, "little", int] - self.MinimumUserModeAddress = [MinimumUserModeAddress, self.POINTER_SIZE, "little", int] - self.MaximumUserModeAddress = [MaximumUserModeAddress, self.POINTER_SIZE, "little", int] - self.ActiveProcessorsAffinityMask = [ActiveProcessorsAffinityMask, self.POINTER_SIZE, "little", int] - self.NumberOfProcessors = [NumberOfProcessors, self.POINTER_SIZE, "little", int] - def write(self, addr): - - super().generic_write(addr, [self.Reserved, self.TimerResolution, self.PageSize, self.NumberOfPhysicalPages, - self.LowestPhysicalPageNumber, self.HighestPhysicalPageNumber ,self.AllocationGranularity, - self.MinimumUserModeAddress,self.MaximumUserModeAddress,self.ActiveProcessorsAffinityMask, - self.NumberOfProcessors]) - - def read(self, addr): - super().generic_read(addr, [self.Reserved, self.TimerResolution, self.PageSize, self.NumberOfPhysicalPages, - self.LowestPhysicalPageNumber, self.HighestPhysicalPageNumber ,self.AllocationGranularity, - self.MinimumUserModeAddress,self.MaximumUserModeAddress,self.ActiveProcessorsAffinityMask, - self.NumberOfProcessors]) - -# typedef struct hostent { -# char *h_name; -# char **h_aliases; -# short h_addrtype; -# short h_length; -# char **h_addr_list; -# } HOSTENT, *PHOSTENT, *LPHOSTENT; -class Hostent(WindowsStruct): - def __init__(self, ql, name=None, aliases=None, addr_type=None, length=None, addr_list=None): - super().__init__(ql) - self.name = [name, self.POINTER_SIZE, "little", int] - self.aliases = [aliases, self.POINTER_SIZE, "little", int] - self.addr_type = [addr_type, self.SHORT_SIZE, "little", int] - self.length = [length, self.SHORT_SIZE, "little", int] - self.addr_list = [addr_list, len(addr_list), "little", bytes] - self.size = self.POINTER_SIZE * 2 + self.SHORT_SIZE * 2 + len(addr_list) - - def write(self, addr): - super().generic_write(addr, [self.name, self.aliases, self.addr_type, self.length, self.addr_list]) - - def read(self, addr): - super().generic_read(addr, [self.name, self.aliases, self.addr_type, self.length, self.addr_list]) - - -# typedef struct _OSVERSIONINFOEXA { -# DWORD dwOSVersionInfoSize; -# DWORD dwMajorVersion; -# DWORD dwMinorVersion; -# DWORD dwBuildNumber; -# DWORD dwPlatformId; -# CHAR szCSDVersion[128]; -# WORD wServicePackMajor; -# WORD wServicePackMinor; -# WORD wSuiteMask; -# BYTE wProductType; -# BYTE wReserved; -# } OSVERSIONINFOEXA, *POSVERSIONINFOEXA, *LPOSVERSIONINFOEXA; -class OsVersionInfoExA(WindowsStruct): - def __init__(self, ql, size=None, major=None, minor=None, build=None, platform=None, version=None, - service_major=None, service_minor=None, suite=None, product=None): - super().__init__(ql) - self.size = [size, self.DWORD_SIZE, "little", int] - self.major = [major, self.DWORD_SIZE, "little", int] - self.minor = [minor, self.DWORD_SIZE, "little", int] - self.build = [build, self.DWORD_SIZE, "little", int] - self.platform_os = [platform, self.DWORD_SIZE, "little", int] - self.version = [version, 128, "little", bytes] - self.service_major = [service_major, self.WORD_SIZE, "little", int] - self.service_minor = [service_minor, self.WORD_SIZE, "little", int] - self.suite = [suite, self.WORD_SIZE, "little", int] - self.product = [product, self.BYTE_SIZE, "little", int] - self.reserved = [0, self.BYTE_SIZE, "little", int] - - def write(self, addr): - super().generic_write(addr, [self.size, self.major, self.minor, self.build, self.platform_os, self.version, - self.service_major, self.service_minor, self.suite, self.product, self.reserved]) - - def read(self, addr): - super().generic_read(addr, [self.size, self.major, self.minor, self.build, self.platform_os, self.version, - self.service_major, self.service_minor, self.suite, self.product, self.reserved]) - - -# typedef struct _OSVERSIONINFOW { -# ULONG dwOSVersionInfoSize; -# ULONG dwMajorVersion; -# ULONG dwMinorVersion; -# ULONG dwBuildNumber; -# ULONG dwPlatformId; -# WCHAR szCSDVersion[128]; -# } -class OsVersionInfoW(WindowsStruct): - def __init__(self, ql, size=None, major=None, minor=None, build=None, platform=None, version=None): - super().__init__(ql) - self.size = [size, self.ULONG_SIZE, "little", int] - self.major = [major, self.ULONG_SIZE, "little", int] - self.minor = [minor, self.ULONG_SIZE, "little", int] - self.build = [build, self.ULONG_SIZE, "little", int] - self.platform_os = [platform, self.ULONG_SIZE, "little", int] - self.version = [version, 128, "little", bytes] - - def write(self, addr): - self.generic_write(addr, [self.size, self.major, self.minor, self.build, self.platform_os, self.version]) - - def read(self, addr): - self.generic_read(addr, [self.size, self.major, self.minor, self.build, self.platform_os, self.version]) - - -# typedef struct _SYSTEM_INFO { -# union { -# DWORD dwOemId; -# struct { -# WORD wProcessorArchitecture; -# WORD wReserved; -# } DUMMYSTRUCTNAME; -# } DUMMYUNIONNAME; -# DWORD dwPageSize; -# LPVOID lpMinimumApplicationAddress; -# LPVOID lpMaximumApplicationAddress; -# DWORD_PTR dwActiveProcessorMask; -# DWORD dwNumberOfProcessors; -# DWORD dwProcessorType; -# DWORD dwAllocationGranularity; -# WORD wProcessorLevel; -# WORD wProcessorRevision; -# } SYSTEM_INFO, *LPSYSTEM_INFO; -class SystemInfo(WindowsStruct): - def __init__(self, ql, dummy=None, page_size=None, min_address=None, max_address=None, mask=None, processors=None, - processor_type=None, allocation=None, processor_level=None, processor_revision=None): - super().__init__(ql) - self.dummy = [dummy, self.DWORD_SIZE, "little", int] - self.page_size = [page_size, self.DWORD_SIZE, "little", int] - self.min_address = [min_address, self.POINTER_SIZE, "little", int] - self.max_address = [max_address, self.POINTER_SIZE, "little", int] - self.mask = [mask, self.POINTER_SIZE, "little", int] - self.processors = [processors, self.DWORD_SIZE, "little", int] - self.processor_type = [processor_type, self.DWORD_SIZE, "little", int] - self.allocation = [allocation, self.DWORD_SIZE, "little", int] - self.processor_level = [processor_level, self.WORD_SIZE, "little", int] - self.processor_revision = [processor_revision, self.WORD_SIZE, "little", int] - self.size = self.DWORD_SIZE * 5 + self.WORD_SIZE * 2 + self.POINTER_SIZE * 3 - - def write(self, addr): - super().generic_write(addr, [self.dummy, self.page_size, self.min_address, self.max_address, self.mask, - self.processors, self.processor_type, self.allocation, self.processor_level, - self.processor_revision]) - - def read(self, addr): - super().generic_read(addr, [self.dummy, self.page_size, self.min_address, self.max_address, self.mask, - self.processors, self.processor_type, self.allocation, self.processor_level, - self.processor_revision]) - - -# typedef struct _SYSTEMTIME { -# WORD wYear; -# WORD wMonth; -# WORD wDayOfWeek; -# WORD wDay; -# WORD wHour; -# WORD wMinute; -# WORD wSecond; -# WORD wMilliseconds; -# } SYSTEMTIME, *PSYSTEMTIME, *LPSYSTEMTIME; - - -class SystemTime(WindowsStruct): - def __init__(self, ql, year=None, month=None, day_week=None, day=None, hour=None, minute=None, seconds=None, - milliseconds=None): - super().__init__(ql) - self.year = [year, self.WORD_SIZE, "little", int] - self.month = [month, self.WORD_SIZE, "little", int] - self.day_week = [day_week, self.WORD_SIZE, "little", int] - self.day = [day, self.WORD_SIZE, "little", int] - self.hour = [hour, self.WORD_SIZE, "little", int] - self.minute = [minute, self.WORD_SIZE, "little", int] - self.seconds = [seconds, self.WORD_SIZE, "little", int] - self.milliseconds = [milliseconds, self.WORD_SIZE, "little", int] - self.size = self.WORD_SIZE * 8 - - def write(self, addr): - super().generic_write(addr, [self.year, self.month, self.day_week, self.day, self.hour, - self.minute, self.seconds, self.milliseconds]) - - def read(self, addr): - super().generic_read(addr, [self.year, self.month, self.day_week, self.day, self.hour, - self.minute, self.seconds, self.milliseconds]) - - -# typedef struct _STARTUPINFO { -# DWORD cb; -# LPTSTR lpReserved; -# LPTSTR lpDesktop; -# LPTSTR lpTitle; -# DWORD dwX; -# DWORD dwY; -# DWORD dwXSize; -# DWORD dwYSize; -# DWORD dwXCountChars; -# DWORD dwYCountChars; -# DWORD dwFillAttribute; -# DWORD dwFlags; -# WORD wShowWindow; -# WORD cbReserved2; -# LPBYTE lpReserved2; -# HANDLE hStdInput; -# HANDLE hStdOutput; -# HANDLE hStdError; -# } STARTUPINFO, *LPSTARTUPINFO; -class StartupInfo(WindowsStruct): - def __init__(self, ql, desktop=None, title=None, x=None, y=None, x_size=None, y_size=None, x_chars=None, - y_chars=None, fill_attribute=None, flags=None, show=None, std_input=None, output=None, error=None): - super().__init__(ql) - self.size = 53 + 3 * self.ql.arch.pointersize - self.cb = [self.size, self.DWORD_SIZE, "little", int] - self.reserved = [0, self.POINTER_SIZE, "little", int] - self.desktop = [desktop, self.POINTER_SIZE, "little", int] - self.title = [title, self.POINTER_SIZE, "little", int] - self.x = [x, self.DWORD_SIZE, "little", int] - self.y = [y, self.DWORD_SIZE, "little", int] - self.x_size = [x_size, self.DWORD_SIZE, "little", int] - self.y_size = [y_size, self.DWORD_SIZE, "little", int] - self.x_chars = [x_chars, self.DWORD_SIZE, "little", int] - self.y_chars = [y_chars, self.DWORD_SIZE, "little", int] - self.fill_attribute = [fill_attribute, self.DWORD_SIZE, "little", int] - self.flags = [flags, self.DWORD_SIZE, "little", int] - self.show = [show, self.WORD_SIZE, "little", int] - self.reserved2 = [0, self.WORD_SIZE, "little", int] - self.reserved3 = [0, self.POINTER_SIZE, "little", int] - self.input = [std_input, self.POINTER_SIZE, "little", int] - self.output = [output, self.POINTER_SIZE, "little", int] - self.error = [error, self.POINTER_SIZE, "little", int] - - def read(self, addr): - super().generic_read(addr, [self.cb, self.reserved, self.desktop, self.title, self.x, self.y, self.x_size, - self.y_size, self.x_chars, self.y_chars, self.fill_attribute, self.flags, self.show, - self.reserved2, self.reserved3, self.input, self.output, self.error]) - self.size = self.cb - - def write(self, addr): - super().generic_write(addr, [self.cb, self.reserved, self.desktop, self.title, self.x, self.y, self.x_size, - self.y_size, self.x_chars, self.y_chars, self.fill_attribute, self.flags, - self.show, - self.reserved2, self.reserved3, self.input, self.output, self.error]) - - -# typedef struct _SHELLEXECUTEINFOA { -# DWORD cbSize; -# ULONG fMask; -# HWND hwnd; -# LPCSTR lpVerb; -# LPCSTR lpFile; -# LPCSTR lpParameters; -# LPCSTR lpDirectory; -# int nShow; -# HINSTANCE hInstApp; -# void *lpIDList; -# LPCSTR lpClass; -# HKEY hkeyClass; -# DWORD dwHotKey; -# union { -# HANDLE hIcon; -# HANDLE hMonitor; -# } DUMMYUNIONNAME; -# HANDLE hProcess; -# } SHELLEXECUTEINFOA, *LPSHELLEXECUTEINFOA; -class ShellExecuteInfoA(WindowsStruct): - def __init__(self, ql, fMask=None, hwnd=None, lpVerb=None, lpFile=None, lpParams=None, lpDir=None, show=None, - instApp=None, lpIDList=None, lpClass=None, hkeyClass=None, - dwHotKey=None, dummy=None, hProcess=None): - super().__init__(ql) - self.size = self.DWORD_SIZE + self.ULONG_SIZE + self.INT_SIZE * 2 + self.POINTER_SIZE * 11 - self.cb = [self.size, self.DWORD_SIZE, "little", int] - # FIXME: check how longs behave, is strange that i have to put big here - self.mask = [fMask, self.ULONG_SIZE, "big", int] - self.hwnd = [hwnd, self.POINTER_SIZE, "little", int] - self.verb = [lpVerb, self.POINTER_SIZE, "little", int] - self.file = [lpFile, self.POINTER_SIZE, "little", int] - self.params = [lpParams, self.POINTER_SIZE, "little", int] - self.dir = [lpDir, self.POINTER_SIZE, "little", int] - self.show = [show, self.INT_SIZE, "little", int] - self.instApp = [instApp, self.POINTER_SIZE, "little", int] - self.id_list = [lpIDList, self.POINTER_SIZE, "little", int] - self.class_name = [lpClass, self.POINTER_SIZE, "little", int] - self.class_key = [hkeyClass, self.POINTER_SIZE, "little", int] - self.hot_key = [dwHotKey, self.INT_SIZE, "little", int] - self.dummy = [dummy, self.POINTER_SIZE, "little", int] - self.process = [hProcess, self.POINTER_SIZE, "little", int] - - def write(self, addr): - super().generic_write(addr, [self.cb, self.mask, self.hwnd, self.verb, self.file, self.params, self.dir, - self.show, self.instApp, self.id_list, self.class_name, self.class_key, - self.hot_key, self.dummy, self.process]) - - def read(self, addr): - super().generic_read(addr, [self.cb, self.mask, self.hwnd, self.verb, self.file, self.params, self.dir, - self.show, self.instApp, self.id_list, self.class_name, self.class_key, - self.hot_key, self.dummy, self.process]) - self.size = self.cb - - -# private struct PROCESS_BASIC_INFORMATION -# { -# public NtStatus ExitStatus; -# public IntPtr PebBaseAddress; -# public UIntPtr AffinityMask; -# public int BasePriority; -# public UIntPtr UniqueProcessId; -# public UIntPtr InheritedFromUniqueProcessId; -# } -class ProcessBasicInformation(WindowsStruct): - def __init__(self, ql, exitStatus=None, pebBaseAddress=None, affinityMask=None, basePriority=None, uniqueId=None, - parentPid=None): - super().__init__(ql) - self.size = self.DWORD_SIZE + self.POINTER_SIZE * 4 + self.INT_SIZE - self.exitStatus = [exitStatus, self.DWORD_SIZE, "little", int] - self.pebBaseAddress = [pebBaseAddress, self.POINTER_SIZE, "little", int] - self.affinityMask = [affinityMask, self.INT_SIZE, "little", int] - self.basePriority = [basePriority, self.POINTER_SIZE, "little", int] - self.pid = [uniqueId, self.POINTER_SIZE, "little", int] - self.parentPid = [parentPid, self.POINTER_SIZE, "little", int] - - def write(self, addr): - super().generic_write(addr, - [self.exitStatus, self.pebBaseAddress, self.affinityMask, self.basePriority, self.pid, - self.parentPid]) - - def read(self, addr): - super().generic_read(addr, - [self.exitStatus, self.pebBaseAddress, self.affinityMask, self.basePriority, self.pid, - self.parentPid]) - - -# typedef struct _UNICODE_STRING { -# USHORT Length; -# USHORT MaximumLength; -# PWSTR Buffer; -# } UNICODE_STRING -class UnicodeString(AlignedWindowsStruct): - def write(self, addr): - super().generic_write(addr, [self.length, self.maxLength, self.buffer]) - - def read(self, addr): - super().generic_read(addr, [self.length, self.maxLength, self.buffer]) - - def __init__(self, ql, length=None, maxLength=None, buffer=None): - super().__init__(ql) - - # on x64, self.buffer is aligned to 8 - if ql.arch.bits == 32: - self.size = self.USHORT_SIZE * 2 + self.POINTER_SIZE - else: - self.size = self.USHORT_SIZE * 2 + 4 + self.POINTER_SIZE - - self.length = [length, self.USHORT_SIZE, "little", int] - self.maxLength = [maxLength, self.USHORT_SIZE, "little", int] - self.buffer = [buffer, self.POINTER_SIZE, "little", int] - - -# typedef struct _OBJECT_TYPE_INFORMATION { -# UNICODE_STRING TypeName; -# ULONG TotalNumberOfObjects; -# ULONG TotalNumberOfHandles; -# } OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION; -class ObjectTypeInformation(WindowsStruct): - def write(self, addr): - super().generic_write(addr, [self.us, self.handles, self.objects]) - - def read(self, addr): - super().generic_read(addr, [self.us, self.handles, self.objects]) - - def __init__(self, ql, typeName: UnicodeString = None, handles=None, objects=None): - super().__init__(ql) - self.size = self.ULONG_SIZE * 2 + (self.USHORT_SIZE * 2 + self.POINTER_SIZE) - self.us = [typeName, self.USHORT_SIZE * 2 + self.POINTER_SIZE, "little", UnicodeString] - # FIXME: understand if is correct to set them as big - self.handles = [handles, self.ULONG_SIZE, "big", int] - self.objects = [objects, self.ULONG_SIZE, "big", int] - - -# typedef struct _OBJECT_ALL_TYPES_INFORMATION { -# ULONG NumberOfObjectTypes; -# OBJECT_TYPE_INFORMATION ObjectTypeInformation[1]; -# } OBJECT_ALL_TYPES_INFORMATION, *POBJECT_ALL_TYPES_INFORMATION; -class ObjectAllTypesInformation(WindowsStruct): - def write(self, addr): - super().generic_write(addr, [self.number, self.typeInfo]) - - def read(self, addr): - super().generic_read(addr, [self.number, self.typeInfo]) - - def __init__(self, ql, objects=None, objectTypeInfo: ObjectTypeInformation = None): - super().__init__(ql) - self.size = self.ULONG_SIZE + (self.ULONG_SIZE * 2 + (self.USHORT_SIZE * 2 + self.POINTER_SIZE)) - # FIXME: understand if is correct to set them as big - self.number = [objects, self.ULONG_SIZE, "big", int] - self.typeInfo = [objectTypeInfo, self.ULONG_SIZE * 2 + (self.USHORT_SIZE * 2 + self.POINTER_SIZE), "little", - ObjectTypeInformation] - - -# typedef struct _WIN32_FIND_DATAA { -# DWORD dwFileAttributes; -# FILETIME ftCreationTime; -# FILETIME ftLastAccessTime; -# FILETIME ftLastWriteTime; -# DWORD nFileSizeHigh; -# DWORD nFileSizeLow; -# DWORD dwReserved0; -# DWORD dwReserved1; -# CHAR cFileName[MAX_PATH]; -# CHAR cAlternateFileName[14]; -# DWORD dwFileType; -# DWORD dwCreatorType; -# WORD wFinderFlags; -# } WIN32_FIND_DATAA, *PWIN32_FIND_DATAA, *LPWIN32_FIND_DATAA; -class Win32FindData(WindowsStruct): - def write(self, addr): - super().generic_write(addr, - [ - self.file_attributes, self.creation_time, - self.last_acces_time, self.last_write_time, - self.file_size_high, self.file_size_low, - self.reserved_0, self.reserved_1, self.file_name, - self.alternate_file_name, self.file_type, - self.creator_type, self.finder_flags - ]) - - def read(self, addr): - super().generic_read(addr, - [ - self.file_attributes, self.creation_time, - self.last_acces_time, self.last_write_time, - self.file_size_high, self.file_size_low, - self.reserved_0, self.reserved_1, self.file_name, - self.alternate_file_name, self.file_type, - self.creator_type, self.finder_flags - ]) - - def __init__(self, - ql, - file_attributes=None, - creation_time=None, - last_acces_time=None, - last_write_time=None, - file_size_high=None, - file_size_low=None, - reserved_0=None, - reserved_1=None, - file_name=None, - alternate_filename=None, - file_type=None, - creator_type=None, - finder_flags=None): - super().__init__(ql) - - # Size of FileTime == 2*(DWORD) - self.size = ( - self.DWORD_SIZE # dwFileAttributes - + (3 * (2 * self.DWORD_SIZE)) # ftCreationTime, ftLastAccessTime, ftLastWriteTime - + self.DWORD_SIZE # nFileSizeHigh - + self.DWORD_SIZE # nFileSizeLow - + self.DWORD_SIZE # dwReservered0 - + self.DWORD_SIZE # dwReservered1 - + (self.BYTE_SIZE * 260) # cFileName[MAX_PATH] - + (self.BYTE_SIZE * 14) # cAlternateFileName[14] - + self.DWORD_SIZE # dwFileType - + self.DWORD_SIZE # dwCreatorType - + self.WORD_SIZE) # wFinderFlags - - self.file_attributes = file_attributes - self.creation_time = creation_time - self.last_acces_time = last_acces_time - self.last_write_time = last_write_time - self.file_size_high = file_size_high - self.file_size_low = file_size_low - self.reserved_0 = reserved_0 - self.reserved_1 = reserved_1 - self.file_name = file_name - self.alternate_file_name = alternate_filename - self.file_type = file_type - self.creator_type = creator_type - self.finder_flags = finder_flags + +class Point(struct.BaseStruct): + _fields_ = ( + ('x', ctypes.c_int32), + ('y', ctypes.c_int32) + ) + + +# https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/basic.htm +def make_system_basic_info(archbits: int): + native_type = struct.get_native_type(archbits) + Struct = struct.get_aligned_struct(archbits) + + pointer_type = native_type + + class SYSTEM_BASIC_INFORMATION(Struct): + _fields_ = ( + ('Reserved', ctypes.c_uint32), + ('TimerResolution', ctypes.c_uint32), + ('PageSize', ctypes.c_uint32), + ('NumberOfPhysicalPages', ctypes.c_uint32), + ('LowestPhysicalPageNumber', ctypes.c_uint32), + ('HighestPhysicalPageNumber', ctypes.c_uint32), + ('AllocationGranularity', ctypes.c_uint32), + ('MinimumUserModeAddress', pointer_type), + ('MaximumUserModeAddress', pointer_type), + ('ActiveProcessorsAffinityMask', pointer_type), + ('NumberOfProcessors', ctypes.c_uint8) + ) + + return SYSTEM_BASIC_INFORMATION + + +# https://docs.microsoft.com/en-us/windows/win32/api/winsock/ns-winsock-hostent +def make_hostent(archbits: int): + native_type = struct.get_native_type(archbits) + Struct = struct.get_aligned_struct(archbits) + + pointer_type = native_type + + class HOSTENT(Struct): + _fields_ = ( + ('h_name', pointer_type), + ('h_aliases', pointer_type), + ('h_addrtype', ctypes.c_int16), + ('h_length', ctypes.c_int16), + ('h_addr_list', pointer_type), + ) + + return HOSTENT + + +# https://docs.microsoft.com/en-us/windows/win32/winsock/sockaddr-2 +def make_sockaddr_in(): + + # https://docs.microsoft.com/en-us/windows/win32/api/winsock2/ns-winsock2-in_addr + class in_addr(ctypes.BigEndianStructure): + _fields_ = ( + ('s_b1', ctypes.c_uint8), + ('s_b2', ctypes.c_uint8), + ('s_b3', ctypes.c_uint8), + ('s_b4', ctypes.c_uint8) + ) + + class sockaddr_in(ctypes.BigEndianStructure): + _fields_ = ( + ('sin_family', ctypes.c_int16), + ('sin_port', ctypes.c_uint16), + ('sin_addr', in_addr), + ('sin_zero', ctypes.c_byte * 8) + ) + + return sockaddr_in + +# https://docs.microsoft.com/en-us/windows/win32/winsock/sockaddr-2 +def make_sockaddr_in6(): + + # https://docs.microsoft.com/en-us/windows/win32/api/in6addr/ns-in6addr-in6_addr + class in6_addr(ctypes.BigEndianStructure): + _fields_ = ( + ('Byte', ctypes.c_uint8 * 16) + ) + + class sockaddr_in6(ctypes.BigEndianStructure): + _fields_ = ( + ('sin6_family', ctypes.c_int16), + ('sin6_port', ctypes.c_uint16), + ('sin6_flowinfo', ctypes.c_uint32), + ('sin6_addr', in6_addr), + ('sin6_scope_id', ctypes.c_uint32) + ) + + return sockaddr_in6 + + +# https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info +def make_system_info(archbits: int): + native_type = struct.get_native_type(archbits) + Struct = struct.get_aligned_struct(archbits) + Union = struct.get_aligned_union(archbits) + + pointer_type = native_type + + class DUMMYSTRUCTNAME(Struct): + _fields_ = ( + ('wProcessorArchitecture', ctypes.c_uint16), + ('wReserved', ctypes.c_uint16) + ) + + class DUMMYUNIONNAME(Union): + _anonymous_ = ('_anon_0',) + + _fields_ = ( + ('dwOemId', ctypes.c_uint32), + ('_anon_0', DUMMYSTRUCTNAME) + ) + + assert ctypes.sizeof(DUMMYUNIONNAME) == 4 + + class SYSTEM_INFO(Struct): + _anonymous_ = ('_anon_1',) + + _fields_ = ( + ('_anon_1', DUMMYUNIONNAME), + ('dwPageSize', ctypes.c_uint32), + ('lpMinimumApplicationAddress', pointer_type), + ('lpMaximumApplicationAddress', pointer_type), + ('dwActiveProcessorMask', pointer_type), + ('dwNumberOfProcessors', ctypes.c_uint32), + ('dwProcessorType', ctypes.c_uint32), + ('dwAllocationGranularity', ctypes.c_uint32), + ('wProcessorLevel', ctypes.c_uint16), + ('wProcessorRevision', ctypes.c_uint16) + ) + + return SYSTEM_INFO + + +class SYSTEMTIME(struct.BaseStruct): + _fields_ = ( + ('wYear', ctypes.c_uint16), + ('wMonth', ctypes.c_uint16), + ('wDayOfWeek', ctypes.c_uint16), + ('wDay', ctypes.c_uint16), + ('wHour', ctypes.c_uint16), + ('wMinute', ctypes.c_uint16), + ('wSecond', ctypes.c_uint16), + ('wMilliseconds', ctypes.c_uint16) + ) + + +# https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfoa +def make_startup_info(archbits: int): + native_type = struct.get_native_type(archbits) + Struct = struct.get_aligned_struct(archbits) + + pointer_type = native_type + + class STARTUPINFO(Struct): + _fields_ = ( + ('cb', ctypes.c_uint32), + ('lpReserved', pointer_type), + ('lpDesktop', pointer_type), + ('lpTitle', pointer_type), + ('dwX', ctypes.c_uint32), + ('dwY', ctypes.c_uint32), + ('dwXSize', ctypes.c_uint32), + ('dwYSize', ctypes.c_uint32), + ('dwXCountChars', ctypes.c_uint32), + ('dwYCountChars', ctypes.c_uint32), + ('dwFillAttribute', ctypes.c_uint32), + ('dwFlags', ctypes.c_uint32), + ('wShowWindow', ctypes.c_uint16), + ('cbReserved2', ctypes.c_uint16), + ('lpReserved2', pointer_type), + ('hStdInput', pointer_type), + ('hStdOutput', pointer_type), + ('hStdError', pointer_type) + ) + + return STARTUPINFO + + +# https://docs.microsoft.com/en-us/windows/win32/api/shellapi/ns-shellapi-shellexecuteinfoa +def make_shellex_info(archbits: int): + native_type = struct.get_native_type(archbits) + Struct = struct.get_aligned_struct(archbits) + Union = struct.get_aligned_union(archbits) + + pointer_type = native_type + + class DUMMYUNIONNAME(Union): + _fields_ = ( + ('hIcon', pointer_type), + ('hMonitor', pointer_type) + ) + + class SHELLEXECUTEINFO(Struct): + _anonymous_ = ('_anon_0',) + + _fields_ = ( + ('cbSize', ctypes.c_uint32), + ('fMask', ctypes.c_uint32), + ('hwnd', pointer_type), + ('lpVerb', pointer_type), + ('lpFile', pointer_type), + ('lpParameters', pointer_type), + ('lpDirectory', pointer_type), + ('nShow', ctypes.c_int32), + ('hInstApp', pointer_type), + ('lpIDList', pointer_type), + ('lpClass', pointer_type), + ('hkeyClass', pointer_type), + ('dwHotKey', ctypes.c_uint32), + ('_anon_0', DUMMYUNIONNAME), + ('hProcess', pointer_type) + ) + + return SHELLEXECUTEINFO + + +# https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ob/obquery/type.htm +@lru_cache(maxsize=2) +def make_object_type_info(archbits: int): + Struct = struct.get_aligned_struct(archbits) + + UniStr = make_unicode_string(archbits) + + # this is only a pratial definition of the structure. + # for some reason, the last two fields are swapped in al-khaser + class OBJECT_TYPE_INFORMATION(Struct): + _fields_ = ( + ('TypeName', UniStr), + ('TotalNumberOfObjects', ctypes.c_uint32), + ('TotalNumberOfHandles', ctypes.c_uint32) + ) + + return OBJECT_TYPE_INFORMATION + + +def make_object_all_types_info(archbits: int, nobjs: int): + Struct = struct.get_aligned_struct(archbits) + + ObjTypeInfo = make_object_type_info(archbits) + + class OBJECT_ALL_TYPES_INFORMATION(Struct): + _fields_ = ( + ('NumberOfObjectTypes', ctypes.c_uint32), + ('ObjectTypeInformation', ObjTypeInfo * nobjs) + ) + + return OBJECT_ALL_TYPES_INFORMATION + + +# https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-win32_find_dataw +def make_win32_find_data(archbits: int, *, wide: bool): + Struct = struct.get_aligned_struct(archbits) + + char_type = (ctypes.c_wchar if wide else ctypes.c_char) + + class WIN32_FIND_DATA(Struct): + _fields_ = ( + ('dwFileAttributes', ctypes.c_uint32), + ('ftCreationTime', FILETIME), + ('ftLastAccessTime', FILETIME), + ('ftLastWriteTime', FILETIME), + ('nFileSizeHigh', ctypes.c_uint32), + ('nFileSizeLow', ctypes.c_uint32), + ('dwReserved0', ctypes.c_uint32), + ('dwReserved1', ctypes.c_uint32), + ('cFileName', char_type * MAX_PATH), + ('cAlternateFileName', char_type * 14), + ('dwFileType', ctypes.c_uint32), + ('dwCreatorType', ctypes.c_uint32), + ('wFinderFlags', ctypes.c_uint16) + ) + + return WIN32_FIND_DATA diff --git a/qiling/os/windows/utils.py b/qiling/os/windows/utils.py index bd13a7630..d55c965e3 100644 --- a/qiling/os/windows/utils.py +++ b/qiling/os/windows/utils.py @@ -3,13 +3,12 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -import ctypes from typing import Iterable, Tuple, TypeVar from unicorn import UcError from qiling import Qiling -from qiling.const import QL_OS +from qiling.exception import QlErrorSyscallError from qiling.os.const import POINTER from qiling.os.windows.fncc import STDCALL from qiling.os.windows.wdk_const import * @@ -29,76 +28,76 @@ def has_lib_ext(name: str) -> bool: return ext in ("dll", "exe", "sys", "drv") -def io_Write(ql: Qiling, in_buffer: bytes): - heap = ql.os.heap +def io_Write(ql: Qiling, in_buffer: bytes) -> int: + major_func = ql.loader.driver_object.MajorFunction[IRP_MJ_WRITE] - if ql.loader.driver_object.MajorFunction[IRP_MJ_WRITE] == 0: - # raise error? - return (False, None) + if not major_func: + raise QlErrorSyscallError('null MajorFunction field') - driver_object_cls = ql.loader.driver_object.__class__ - buf = ql.mem.read(ql.loader.driver_object.DeviceObject, ctypes.sizeof(driver_object_cls)) - device_object = driver_object_cls.from_buffer(buf) + # keep track of all heap allocation within this scope to be able + # to free them when done + allocations = [] - alloc_addr = [] - def build_mdl(buffer_size: int, data=None): - mdl = make_mdl(ql.arch.bits) + def __heap_alloc(size: int) -> int: + address = ql.os.heap.alloc(size) + allocations.append(address) - mapped_address = heap.alloc(buffer_size) - alloc_addr.append(mapped_address) - mdl.MappedSystemVa.value = mapped_address - mdl.StartVa.value = mapped_address - mdl.ByteOffset = 0 - mdl.ByteCount = buffer_size - if data: - written = data if len(data) <= buffer_size else data[:buffer_size] - ql.mem.write(mapped_address, written) + return address - return mdl + def __free_all(allocations: Iterable[int]) -> None: + for address in allocations: + ql.os.heap.free(address) - # allocate memory regions for IRP and IO_STACK_LOCATION - irp = make_irp(ql.arch.bits) - irpstack_class = irp.irpstack._type_ + # allocate memory for IRP + irp_struct = make_irp(ql.arch.bits) + irp_addr = __heap_alloc(irp_struct.sizeof()) + ql.log.info(f'IRP is at {irp_addr:#x}') - irp_addr = heap.alloc(ctypes.sizeof(irp)) - alloc_addr.append(irp_addr) + # populate the structure + with irp_struct.ref(ql.mem, irp_addr) as irp_obj: - irpstack_addr = heap.alloc(ctypes.sizeof(irpstack_class)) - alloc_addr.append(irpstack_addr) + # allocate memory for IO_STACK_LOCATION + irpstack_struct = make_io_stack_location(ql.arch.bits) + irpstack_addr = __heap_alloc(irpstack_struct.sizeof()) + ql.log.info(f'IO_STACK_LOCATION is at {irpstack_addr:#x}') - # setup irp stack parameters - irpstack = irpstack_class() - # setup IRP structure - irp.irpstack = ctypes.cast(irpstack_addr, ctypes.POINTER(irpstack_class)) + # populate the structure + with irpstack_struct.ref(ql.mem, irpstack_addr) as irpstack_obj: + irpstack_obj.MajorFunction = IRP_MJ_WRITE + irpstack_obj.Parameters.Write.Length = len(in_buffer) - irpstack.MajorFunction = IRP_MJ_WRITE - irpstack.Parameters.Write.Length = len(in_buffer) - ql.mem.write(irpstack_addr, bytes(irpstack)) + # load DeviceObject from memory + drvobj_struct = ql.loader.driver_object.__class__ + devobj_obj = drvobj_struct.load_from(ql.loader.driver_object.DeviceObject) - if device_object.Flags & DO_BUFFERED_IO: # BUFFERED_IO - system_buffer_addr = heap.alloc(len(in_buffer)) - alloc_addr.append(system_buffer_addr) - ql.mem.write(system_buffer_addr, bytes(in_buffer)) - irp.AssociatedIrp.SystemBuffer.value = system_buffer_addr - elif device_object.Flags & DO_DIRECT_IO: + if devobj_obj.Flags & DO_BUFFERED_IO: + system_buffer_addr = __heap_alloc(len(in_buffer)) + + ql.mem.write(system_buffer_addr, bytes(in_buffer)) + irp_obj.AssociatedIrp.SystemBuffer = system_buffer_addr + # DIRECT_IO - mdl = build_mdl(len(in_buffer)) - mdl_addr = heap.alloc(ctypes.sizeof(mdl)) - alloc_addr.append(mdl_addr) + elif devobj_obj.Flags & DO_DIRECT_IO: + mdl_struct = make_mdl(ql.arch.bits) + mdl_addr = __heap_alloc(mdl_struct.sizeof()) + + with mdl_struct.ref(ql.mem, mdl_addr) as mdl_obj: + mapped_address = __heap_alloc(len(in_buffer)) + + mdl_obj.MappedSystemVa = mapped_address + mdl_obj.StartVa = mapped_address + mdl_obj.ByteOffset = 0 + mdl_obj.ByteCount = len(in_buffer) + + irp_obj.MdlAddress = mdl_addr - ql.mem.write(mdl_addr, bytes(mdl)) - irp.MdlAddress.value = mdl_addr - else: # NEITHER_IO - input_buffer_size = len(in_buffer) - input_buffer_addr = heap.alloc(input_buffer_size) - alloc_addr.append(input_buffer_addr) - ql.mem.write(input_buffer_addr, bytes(in_buffer)) - irp.UserBuffer.value = input_buffer_addr + else: + input_buffer_addr = __heap_alloc(len(in_buffer)) - # everything is done! Write IRP to memory - ql.mem.write(irp_addr, bytes(irp)) + ql.mem.write(input_buffer_addr, bytes(in_buffer)) + irp_obj.UserBuffer = input_buffer_addr # set function args # TODO: make sure this is indeed STDCALL @@ -108,23 +107,22 @@ def build_mdl(buffer_size: int, data=None): (POINTER, irp_addr) )) + ql.log.info(f'Executing from {major_func:#x}') + try: # now emulate - ql.run(ql.loader.driver_object.MajorFunction[IRP_MJ_WRITE]) + ql.run(major_func) except UcError as err: verify_ret(ql, err) - # read current IRP state - irp_buffer = ql.mem.read(irp_addr, ctypes.sizeof(irp)) - irp = irp.from_buffer(irp_buffer) + # read updated IRP state before releasing resources + with irp_struct.ref(ql.mem, irp_addr) as irp_obj: + info = irp_obj.IoStatus.Information - io_status = irp.IoStatus - # now free all alloc memory - for addr in alloc_addr: - # print("freeing heap memory at 0x%x" %addr) # FIXME: the output is not deterministic?? - heap.free(addr) + # free all allocated memory + __free_all(allocations) - return True, io_status.Information.value + return info # Emulate DeviceIoControl() of Windows # BOOL DeviceIoControl( @@ -136,8 +134,14 @@ def build_mdl(buffer_size: int, data=None): # DWORD nOutBufferSize, # LPDWORD lpBytesReturned, # LPOVERLAPPED lpOverlapped); -def ioctl(ql: Qiling, params: Tuple[Tuple, int, bytes]) -> Tuple: +def ioctl(ql: Qiling, params: Tuple[Tuple, int, bytes]) -> Tuple[int, int, bytes]: + major_func = ql.loader.driver_object.MajorFunction[IRP_MJ_DEVICE_CONTROL] + if not major_func: + raise QlErrorSyscallError('null MajorFunction field') + + # keep track of all heap allocation within this scope to be able + # to free them when done allocations = [] def __heap_alloc(size: int) -> int: @@ -153,27 +157,9 @@ def __free_all(allocations: Iterable[int]) -> None: def ioctl_code(DeviceType: int, Function: int, Method: int, Access: int) -> int: return (DeviceType << 16) | (Access << 14) | (Function << 2) | Method - def build_mdl(buffer_size, data=None): - mdl = make_mdl(ql.arch.bits) - - mapped_address = __heap_alloc(buffer_size) - mdl.MappedSystemVa.value = mapped_address - mdl.StartVa.value = mapped_address - mdl.ByteOffset = 0 - mdl.ByteCount = buffer_size - - if data: - written = data if len(data) <= buffer_size else data[:buffer_size] - ql.mem.write(mapped_address, written) - - return mdl - - if ql.loader.driver_object.MajorFunction[IRP_MJ_DEVICE_CONTROL] == 0: - # raise error? - return (None, None, None) - # create new memory region to store input data _ioctl_code, output_buffer_size, in_buffer = params + # extract data transfer method devicetype, function, ctl_method, access = _ioctl_code @@ -184,52 +170,57 @@ def build_mdl(buffer_size, data=None): # create new memory region to store out data output_buffer_addr = __heap_alloc(output_buffer_size) - # allocate memory regions for IRP and IO_STACK_LOCATION - irp = make_irp(ql.arch.bits) - irpstack_class = irp.irpstack._type_ + # allocate memory for AssociatedIrp.SystemBuffer + # used by IOCTL_METHOD_IN_DIRECT, IOCTL_METHOD_OUT_DIRECT and IOCTL_METHOD_BUFFERED + system_buffer_size = max(input_buffer_size, output_buffer_size) + system_buffer_addr = __heap_alloc(system_buffer_size) + ql.mem.write(system_buffer_addr, bytes(in_buffer)) - irp_addr = __heap_alloc(ctypes.sizeof(irp)) - irpstack_addr = __heap_alloc(ctypes.sizeof(irpstack_class)) + # allocate memory for IRP + irp_struct = make_irp(ql.arch.bits) + irp_addr = __heap_alloc(irp_struct.sizeof()) + ql.log.info(f'IRP is at {irp_addr:#x}') - # setup irp stack parameters - irpstack = irpstack_class() - # setup IRP structure - irp.irpstack = ctypes.cast(irpstack_addr, ctypes.POINTER(irpstack_class)) + # populate the structure + with irp_struct.ref(ql.mem, irp_addr) as irp_obj: - ql.log.info("IRP is at 0x%x, IO_STACK_LOCATION is at 0x%x" %(irp_addr, irpstack_addr)) + # allocate memory for IO_STACK_LOCATION + irpstack_struct = make_io_stack_location(ql.arch.bits) + irpstack_addr = __heap_alloc(irpstack_struct.sizeof()) + ql.log.info(f'IO_STACK_LOCATION is at {irpstack_addr:#x}') - irpstack.Parameters.DeviceIoControl.IoControlCode = ioctl_code(devicetype, function, ctl_method, access) - irpstack.Parameters.DeviceIoControl.OutputBufferLength = output_buffer_size - irpstack.Parameters.DeviceIoControl.InputBufferLength = input_buffer_size - irpstack.Parameters.DeviceIoControl.Type3InputBuffer.value = input_buffer_addr # used by IOCTL_METHOD_NEITHER - ql.mem.write(irpstack_addr, bytes(irpstack)) + # populate the structure + with irpstack_struct.ref(ql.mem, irpstack_addr) as irpstack_obj: + irpstack_obj.Parameters.DeviceIoControl.IoControlCode = ioctl_code(devicetype, function, ctl_method, access) + irpstack_obj.Parameters.DeviceIoControl.OutputBufferLength = output_buffer_size + irpstack_obj.Parameters.DeviceIoControl.InputBufferLength = input_buffer_size + irpstack_obj.Parameters.DeviceIoControl.Type3InputBuffer = input_buffer_addr # used by IOCTL_METHOD_NEITHER - if ctl_method == METHOD_NEITHER: - irp.UserBuffer.value = output_buffer_addr # used by IOCTL_METHOD_NEITHER + irp_obj.irpstack = irpstack_addr - # allocate memory for AssociatedIrp.SystemBuffer - # used by IOCTL_METHOD_IN_DIRECT, IOCTL_METHOD_OUT_DIRECT and IOCTL_METHOD_BUFFERED - system_buffer_size = max(input_buffer_size, output_buffer_size) - system_buffer_addr = __heap_alloc(system_buffer_size) + if ctl_method in (METHOD_IN_DIRECT, METHOD_OUT_DIRECT): + mdl_struct = make_mdl(ql.arch.bits) + mdl_addr = __heap_alloc(mdl_struct.sizeof()) - # init data from input buffer - ql.mem.write(system_buffer_addr, bytes(in_buffer)) - irp.AssociatedIrp.SystemBuffer.value = system_buffer_addr + # Create MDL structure for output data + with mdl_struct.ref(ql.mem, mdl_addr) as mdl_obj: + mapped_address = __heap_alloc(output_buffer_size) - if ctl_method in (METHOD_IN_DIRECT, METHOD_OUT_DIRECT): - # Create MDL structure for output data - # used by both IOCTL_METHOD_IN_DIRECT and IOCTL_METHOD_OUT_DIRECT - mdl = build_mdl(output_buffer_size) - mdl_addr = __heap_alloc(ctypes.sizeof(mdl)) + mdl_obj.MappedSystemVa = mapped_address + mdl_obj.StartVa = mapped_address + mdl_obj.ByteOffset = 0 + mdl_obj.ByteCount = output_buffer_size - ql.mem.write(mdl_addr, bytes(mdl)) - irp.MdlAddress.value = mdl_addr + # used by both IOCTL_METHOD_IN_DIRECT and IOCTL_METHOD_OUT_DIRECT + irp_obj.MdlAddress = mdl_addr - # everything is done! Write IRP to memory - ql.mem.write(irp_addr, bytes(irp)) + elif ctl_method == METHOD_NEITHER: + # used by IOCTL_METHOD_NEITHER + irp_obj.UserBuffer = output_buffer_addr + + irp_obj.AssociatedIrp.SystemBuffer = system_buffer_addr # set function args - ql.log.info("Executing IOCTL with DeviceObject = 0x%x, IRP = 0x%x" %(ql.loader.driver_object.DeviceObject, irp_addr)) # TODO: make sure this is indeed STDCALL ql.os.fcall = ql.os.fcall_select(STDCALL) ql.os.fcall.writeParams(( @@ -237,30 +228,39 @@ def build_mdl(buffer_size, data=None): (POINTER, irp_addr) )) + ql.log.info(f'Executing from {major_func:#x}') + try: - ql.log.info(f"Executing from: {ql.loader.driver_object.MajorFunction[IRP_MJ_DEVICE_CONTROL]:#x}") # now emulate IOCTL's DeviceControl - ql.run(ql.loader.driver_object.MajorFunction[IRP_MJ_DEVICE_CONTROL]) + ql.run(major_func) except UcError as err: verify_ret(ql, err) - # read current IRP state - irp_buffer = ql.mem.read(irp_addr, ctypes.sizeof(irp)) - irp = irp.__class__.from_buffer(irp_buffer) + # read updated IRP state before releasing resources + with irp_struct.ref(ql.mem, irp_addr) as irp_obj: + io_status = irp_obj.IoStatus + mdl_addr = irp_obj.MdlAddress - io_status = irp.IoStatus + info = io_status.Information + status = io_status.Status.Status # read output data output_data = b'' - if io_status.Status.Status >= 0: + + if status >= 0: if ctl_method == METHOD_BUFFERED: - output_data = ql.mem.read(system_buffer_addr, io_status.Information.value) - if ctl_method in (METHOD_IN_DIRECT, METHOD_OUT_DIRECT): - output_data = ql.mem.read(mdl.MappedSystemVa.value, io_status.Information.value) - if ctl_method == METHOD_NEITHER: - output_data = ql.mem.read(output_buffer_addr, io_status.Information.value) + output_data = ql.mem.read(system_buffer_addr, info) + + elif ctl_method in (METHOD_IN_DIRECT, METHOD_OUT_DIRECT): + with mdl_struct.ref(ql.mem, mdl_addr) as mdl_obj: + mapped_va = mdl_obj.MappedSystemVa + + output_data = ql.mem.read(mapped_va, info) + + elif ctl_method == METHOD_NEITHER: + output_data = ql.mem.read(output_buffer_addr, info) # now free all alloc memory __free_all(allocations) - return io_status.Status.Status, io_status.Information.value, output_data + return status, info, output_data diff --git a/qiling/os/windows/windows.py b/qiling/os/windows/windows.py index 6c115d99c..d4ab2f047 100644 --- a/qiling/os/windows/windows.py +++ b/qiling/os/windows/windows.py @@ -65,9 +65,9 @@ def __make_fcall_selector(atype: QL_ARCH) -> Callable[[int], QlFunctionCall]: self.stats = QlWinStats() - ossection = f'OS{self.ql.arch.bits}' - heap_base = self.profile.getint(ossection, 'heap_address') - heap_size = self.profile.getint(ossection, 'heap_size') + ossection = self.profile[f'OS{self.ql.arch.bits}'] + heap_base = ossection.getint('heap_address') + heap_size = ossection.getint('heap_size') self.heap = QlMemoryHeap(self.ql, heap_base, heap_base + heap_size) @@ -82,9 +82,7 @@ def __make_fcall_selector(atype: QL_ARCH) -> Callable[[int], QlFunctionCall]: self.PE_RUN = False self.last_error = 0 - # variables used inside hooks - self.hooks_variables = {} - self.syscall_count = {} + self.argv = self.ql.argv self.env = self.ql.env self.pid = self.profile.getint('KERNEL', 'pid') @@ -191,9 +189,6 @@ def hook_winapi(self, ql: Qiling, address: int, size: int): api_func = getattr(api, f'hook_{api_name}', None) if api_func: - self.syscall_count.setdefault(api_name, 0) - self.syscall_count[api_name] += 1 - try: api_func(ql, address, api_name) except Exception as ex: diff --git a/qiling/utils.py b/qiling/utils.py index dc3703052..3c959b892 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -433,10 +433,15 @@ def profile_setup(ostype: QL_OS, filename: Optional[str]): return config # verify if emulator returns properly -def verify_ret(ql, err): +def verify_ret(ql: 'Qiling', err): + # init_sp location is not consistent; this is here to work around that + if not hasattr(ql.os, 'init_sp'): + ql.os.init_sp = ql.loader.init_sp + ql.log.debug("Got exception %u: init SP = %x, current SP = %x, PC = %x" %(err.errno, ql.os.init_sp, ql.arch.regs.arch_sp, ql.arch.regs.arch_pc)) - ql.os.RUN = False + if hasattr(ql.os, 'RUN'): + ql.os.RUN = False # timeout is acceptable in this case if err.errno in (UC_ERR_READ_UNMAPPED, UC_ERR_FETCH_UNMAPPED): diff --git a/tests/test_pe.py b/tests/test_pe.py index b89bf334c..2cac6e7b4 100644 --- a/tests/test_pe.py +++ b/tests/test_pe.py @@ -355,30 +355,12 @@ def _t(): @unittest.skipIf(IS_FAST_TEST, 'fast test') def test_pe_win_al_khaser(self): def _t(): - 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.arch.regs.ebx == 1: - # print("BAD") - # else: - # print("GOOD ") - # ql.arch.regs.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 = Qiling(["../examples/rootfs/x86_windows/bin/al-khaser.bin"], "../examples/rootfs/x86_windows", verbose=QL_VERBOSE.OFF) - ql.hook_address(end, 0x004016ae) + # ole32 functions are not implemented yet; stop before the binary + # starts using them + ql.run(end=0x004016ae) - ql.run() del ql return True diff --git a/tests/test_pe_sys.py b/tests/test_pe_sys.py index f60c58ff4..08a5b97d8 100644 --- a/tests/test_pe_sys.py +++ b/tests/test_pe_sys.py @@ -11,7 +11,8 @@ sys.path.append("..") from qiling import Qiling from qiling.const import QL_STOP, QL_VERBOSE -from qiling.os.const import POINTER, DWORD, STRING, HANDLE +from qiling.os.const import POINTER, DWORD, HANDLE +from qiling.exception import QlErrorSyscallError from qiling.os.windows import utils from qiling.os.windows.wdk_const import * from qiling.os.windows.api import * @@ -98,8 +99,7 @@ def hook_WriteFile(ql: Qiling, address: int, params): buffer = ql.mem.read(lpBuffer, nNumberOfBytesToWrite) if hFile == 0x13371337: - success, nNumberOfBytesToWrite = utils.io_Write(ql.amsint32_driver, buffer) - r = int(success) + nNumberOfBytesToWrite = utils.io_Write(ql.amsint32_driver, buffer) elif hFile == 0xfffffff5: s = buffer.decode() @@ -203,7 +203,12 @@ def hook_third_stop_address(ql: Qiling, stops: List[bool]): # asmint32 driver should have been initialized by now. otherwise we get an exception amsint32: Qiling = getattr(ql, 'amsint32_driver') - utils.io_Write(amsint32, ql.pack32(0xdeadbeef)) + # asmint32 driver init doesn't get to run far enough to initialize necessary data + # structures. it is expected to fail. + try: + utils.io_Write(amsint32, ql.pack32(0xdeadbeef)) + except QlErrorSyscallError: + pass # TODO: not sure whether this one is really STDCALL fcall = amsint32.os.fcall_select(STDCALL) @@ -264,6 +269,6 @@ def test_pe_win_x8664_driver(self): # - Call DriverUnload del ql - + if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/test_struct.py b/tests/test_struct.py new file mode 100644 index 000000000..9ffa82c30 --- /dev/null +++ b/tests/test_struct.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +import sys, unittest +from typing import Optional + +sys.path.append("..") + +import ctypes + +from qiling import Qiling +from qiling.const import QL_ARCH, QL_OS +from qiling.os.struct import BaseStruct + +class DummyInternalStruct(BaseStruct): + _fields_ = [ + ('X', ctypes.c_uint32) + ] + + # this is defined to let 'assertEqual' work as expected + def __eq__(self, other) -> bool: + return isinstance(other, DummyInternalStruct) and self.X == other.X + + +class DummyStruct(BaseStruct): + _fields_ = [ + ('A', ctypes.c_uint32), + ('B', ctypes.c_uint64), + ('C', DummyInternalStruct), + ('D', ctypes.c_char * 16) + ] + + +# we only need context and not going to run anything anyway, so just use whatever +NOPSLED = b'\x90' * 8 +ROOTFS = r'../examples/rootfs/x8664_linux' + +class StructTest(unittest.TestCase): + + def setUp(self) -> None: + ql = Qiling(code=NOPSLED, rootfs=ROOTFS, archtype=QL_ARCH.X8664, ostype=QL_OS.LINUX) + + self.ptr = 0x100000 + self.mem = ql.mem + + self.expected = { + 'A' : 0xdeadface, + 'B' : 0x1020304050607080, + 'C' : DummyInternalStruct(0x11213141), + 'D' : b'Hello World!', + } + + # create a dummy structure with expected values + dummy = DummyStruct(**self.expected) + + # emit dummy structure to memory + ql.mem.map(self.ptr, ql.mem.align_up(dummy.sizeof())) + ql.mem.write(self.ptr, bytes(dummy)) + + + def __read_data(self, offset: int = 0, size: Optional[int] = None) -> bytearray: + return self.mem.read(self.ptr + offset, size or DummyStruct.sizeof()) + + + def __write_data(self, offset: int, data: bytes) -> None: + self.mem.write(self.ptr + offset, data) + + + @staticmethod + def __to_uint(data: bytearray) -> int: + return int.from_bytes(data, 'little', signed=False) + + + def test_load_from(self): + dummy = DummyStruct.load_from(self.mem, self.ptr) + + self.assertEqual(self.expected['A'], dummy.A) + self.assertEqual(self.expected['B'], dummy.B) + self.assertEqual(self.expected['C'], dummy.C) + self.assertEqual(self.expected['D'], dummy.D) + + + def test_save_to(self): + dummy = DummyStruct( + A=0x0c0a0f0e, + B=0x1828384858687888, + C=DummyInternalStruct(0x19293949), + D=b'Goodbye World!' + ) + + dummy.save_to(self.mem, self.ptr) + + obj_data = bytes(dummy) + mem_data = self.__read_data() + + self.assertEqual(obj_data, mem_data) + + def test_ref_discard(self): + data_before = self.__read_data() + + unused = [] + with DummyStruct.ref(self.mem, self.ptr) as dummy: + print(f'B = {dummy.B:#x}') + print(f'C = {dummy.C}') + + unused.append(dummy.A + 1337) + + data_after = self.__read_data() + + self.assertEqual(data_before, data_after) + + def test_ref_save(self): + expected = 0x10303070 + + with DummyStruct.ref(self.mem, self.ptr) as dummy: + print(f'B = {dummy.B:#x}') + print(f'C = {dummy.C}') + + dummy.A = expected + + data = self.__read_data(DummyStruct.offsetof('A'), 4) + self.assertEqual(expected, StructTest.__to_uint(data)) + + def test_ref_save_internal(self): + expected = 0x16363676 + + with DummyStruct.ref(self.mem, self.ptr) as dummy: + dummy.C.X = expected + + data = self.__read_data(DummyStruct.offsetof('C') + DummyInternalStruct.offsetof('X'), 4) + self.assertEqual(expected, StructTest.__to_uint(data)) + + def test_volatile_ref(self): + dummy = DummyStruct.volatile_ref(self.mem, self.ptr) + + expected = 0x01030307 + dummy.A = expected + data = self.__read_data(DummyStruct.offsetof('A'), 4) + self.assertEqual(expected, StructTest.__to_uint(data)) + + self.assertEqual(self.expected['B'], dummy.B) + self.assertEqual(self.expected['C'], dummy.C) + + expected = b'Volatility Test!' + self.__write_data(DummyStruct.offsetof('D'), expected) + self.assertEqual(expected, dummy.D) + + def test_volatile_ref_internal(self): + dummy = DummyStruct.volatile_ref(self.mem, self.ptr) + + expected = 0x51535357 + dummy.C.X = expected + data = self.__read_data(DummyStruct.offsetof('C') + DummyInternalStruct.offsetof('X'), 4) + self.assertEqual(expected, StructTest.__to_uint(data)) + + +if __name__ == "__main__": + unittest.main()