From bfcb955761fba8ae866ede0a314dcada3dc471f9 Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 26 Sep 2023 13:55:58 +0300 Subject: [PATCH 1/7] Better spec compliance for Windows Registery --- qiling/os/windows/dlls/advapi32.py | 26 +++++++++++++++--- qiling/os/windows/registry.py | 42 ++++++++++++++---------------- 2 files changed, 41 insertions(+), 27 deletions(-) diff --git a/qiling/os/windows/dlls/advapi32.py b/qiling/os/windows/dlls/advapi32.py index 77eaa9b5b..8ace3c6da 100644 --- a/qiling/os/windows/dlls/advapi32.py +++ b/qiling/os/windows/dlls/advapi32.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# +# # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # @@ -81,12 +81,23 @@ def __RegQueryValue(ql: Qiling, address: int, params, wstring: bool): ql.log.debug("Key value not found") return ERROR_FILE_NOT_FOUND + # read how many bytes we are allowed to write into lpData, however this arg is optional + if lpcbData: + max_size = ql.mem.read_ptr(lpcbData, 4) + else: + max_size = 0 + + # lpcbData may be null only if lpData is also null. if lpData is allocated, but lpcbData is + # set to null, it means we have an out buffer without knowing its size + if lpData: + return ERROR_INVALID_PARAMETER + # set lpData - length = ql.os.registry_manager.write_reg_value_into_mem(reg_type, lpData, value, wstring) + length = ql.os.registry_manager.write_reg_value_into_mem(reg_type, lpData, value, max_size, wstring) # set lpcbData - max_size = ql.mem.read_ptr(lpcbData, 4) - ql.mem.write_ptr(lpcbData, length, 4) + if lpcbData: + ql.mem.write_ptr(lpcbData, length, 4) if max_size < length: ret = ERROR_MORE_DATA @@ -134,6 +145,9 @@ def __RegSetValue(ql: Qiling, address: int, params, wstring: bool): # this is done so the print_function would print the correct value params["hKey"] = s_hKey + if not lpData: + return ERROR_INVALID_PARAMETER + # dwType is expected to be REG_SZ and lpData to point to a null-terminated string ql.os.registry_manager.write(s_hKey, lpSubKey, dwType, lpData, cbData, wstring) @@ -147,8 +161,12 @@ def __RegSetValueEx(ql: Qiling, address: int, params, wstring: bool): cbData = params["cbData"] s_hKey = ql.os.handle_manager.get(hKey).obj + # this is done so the print_function would print the correct value params["hKey"] = s_hKey + if not lpData: + return ERROR_INVALID_PARAMETER + ql.os.registry_manager.write(s_hKey, lpValueName, dwType, lpData, cbData, wstring) return ERROR_SUCCESS diff --git a/qiling/os/windows/registry.py b/qiling/os/windows/registry.py index 9cc3ac813..af0912a46 100644 --- a/qiling/os/windows/registry.py +++ b/qiling/os/windows/registry.py @@ -1,9 +1,11 @@ #!/usr/bin/env python3 -# +# # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -import json, os +import json +import os + from Registry import Registry from typing import Any, MutableMapping, Optional, Tuple, Union @@ -15,11 +17,11 @@ # Registry Manager reads data from two places # 1. config.json # if you want to modify the registry key/value, you can modify config.json -# If there is a registry entry in config.json that needs to be read, +# If there is a registry entry in config.json that needs to be read, # Registry Manager will read from config.json first. # 2. windows hive files -# Registry Manager will only write registry changes to config.json +# Registry Manager will only write registry changes to config.json # and will not modify the hive file. class RegConf: @@ -208,14 +210,14 @@ def access(self, key: str, name: Optional[str] = None, type: Optional[int] = Non self.ql.os.stats.log_reg_access(key, name, type, value) def create(self, key: str) -> None: - self.regconf.create(key) - self.reghive.create(key) + self.regconf.create(key) + self.reghive.create(key) def delete(self, key: str, subkey: str) -> None: - self.regconf.delete(key, subkey) - self.reghive.delete(key, subkey) + self.regconf.delete(key, subkey) + self.reghive.delete(key, subkey) - def __reg_mem_read(self, data_type: int, data_addr: int, data_size: int, wide: bool) -> Optional[Union[str, bytes, int]]: + def __reg_mem_read(self, data_type: int, data_addr: int, data_size: int, wide: bool) -> Union[str, bytes, int]: if data_type in (Registry.RegSZ, Registry.RegExpandSZ): os_utils = self.ql.os.utils read_string = os_utils.read_wstring if wide else os_utils.read_cstring @@ -232,11 +234,11 @@ def __reg_mem_read(self, data_type: int, data_addr: int, data_size: int, wide: b data = bytes(self.ql.mem.read(data_addr, data_size)) else: - data = None + raise QlErrorNotImplemented(f'registry type {REG_TYPES[data_type]} not implemented') return data - def __reg_mem_write(self, data_type: int, data_addr: int, data_val: Union[str, bytes, int], wide: bool) -> Optional[int]: + def __reg_mem_write(self, data_type: int, data_addr: int, data_val: Union[str, bytes, int], max_size: int, wide: bool) -> int: if data_type in (Registry.RegSZ, Registry.RegExpandSZ): assert type(data_val) is str @@ -259,28 +261,22 @@ def __reg_mem_write(self, data_type: int, data_addr: int, data_val: Union[str, b data = data_val else: - return None + raise QlErrorNotImplemented(f'registry type {REG_TYPES[data_type]} not implemented') - self.ql.mem.write(data_addr, data) + # in case the out buffer is set to null or it is too small, skip data writing + if data_addr and max_size >= len(data): + self.ql.mem.write(data_addr, data) return len(data) def write(self, key: str, subkey: str, reg_type: int, data_addr: int, data_size: int, wide: bool) -> None: data = self.__reg_mem_read(reg_type, data_addr, data_size, wide) - if data is None: - raise QlErrorNotImplemented(f'registry type {REG_TYPES[reg_type]} not implemented') - self.regconf.write(key, subkey, reg_type, data) self.reghive.write(key, subkey, reg_type, data) - def write_reg_value_into_mem(self, data_type: int, data_addr: int, data_val: Union[str, bytes, int], wide: bool) -> int: - length = self.__reg_mem_write(data_type, data_addr, data_val, wide) - - if length is None: - raise QlErrorNotImplemented(f'registry type {REG_TYPES[data_type]} not implemented') - - return length + def write_reg_value_into_mem(self, data_type: int, data_addr: int, data_val: Union[str, bytes, int], max_size: int, wide: bool) -> int: + return self.__reg_mem_write(data_type, data_addr, data_val, max_size, wide) def save(self): self.regconf.save(self.regdiff) From 889798290666fd2e9f84c28165c9acf96139461c Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 8 Oct 2023 20:08:43 +0300 Subject: [PATCH 2/7] Fix save / restore keys inconsistency --- qiling/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiling/core.py b/qiling/core.py index 929c2465c..4a98430b8 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -694,8 +694,8 @@ def restore(self, saved_states: Mapping[str, Any] = {}, *, snapshot: Optional[st if "fd" in saved_states: self.os.fd.restore(saved_states["fd"]) - if "os_context" in saved_states: - self.os.restore(saved_states["os_context"]) + if "os" in saved_states: + self.os.restore(saved_states["os"]) if "loader" in saved_states: self.loader.restore(saved_states["loader"]) From c4cba3694744fe1d81e87270d1fa477d8f702384 Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 18 Oct 2023 22:27:45 +0300 Subject: [PATCH 3/7] Replace magic numbers with constants --- qiling/loader/elf.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/qiling/loader/elf.py b/qiling/loader/elf.py index c02a97111..5bf8d8ba7 100644 --- a/qiling/loader/elf.py +++ b/qiling/loader/elf.py @@ -58,9 +58,11 @@ class AUXV(IntEnum): # start area memory for API hooking # we will reserve 0x1000 bytes for this (which contains multiple slots of 4/8 bytes, each for one api) API_HOOK_MEM = 0x1000000 +API_HOOK_SIZE = 0x1000 # memory for syscall table SYSCALL_MEM = API_HOOK_MEM + 0x1000 +SYSCALL_SIZE = 0x1000 class QlLoaderELF(QlLoader): @@ -579,7 +581,7 @@ def load_driver(self, elffile: ELFFile, stack_addr: int, loadbase: int = 0) -> N mem_end = mem_start + self.ql.mem.align_up(len(elfdata_mapping), 0x1000) # map some memory to intercept external functions of Linux kernel - self.ql.mem.map(API_HOOK_MEM, 0x1000, info="[api_mem]") + self.ql.mem.map(API_HOOK_MEM, API_HOOK_SIZE, info="[api_mem]") self.ql.log.debug(f'loadbase : {loadbase:#x}') self.ql.log.debug(f'mem_start : {mem_start:#x}') @@ -608,8 +610,8 @@ def load_driver(self, elffile: ELFFile, stack_addr: int, loadbase: int = 0) -> N # self.ql.os.syscall_addr = SYSCALL_MEM # setup syscall table - self.ql.mem.map(SYSCALL_MEM, 0x1000, info="[syscall_mem]") - self.ql.mem.write(SYSCALL_MEM, b'\x00' * 0x1000) + self.ql.mem.map(SYSCALL_MEM, SYSCALL_SIZE, info="[syscall_mem]") + self.ql.mem.write(SYSCALL_MEM, b'\x00' * SYSCALL_SIZE) rev_reloc_symbols = self.lkm_dynlinker(elffile, mem_start + loadbase) From 740dc6930505b7664beca2e6df0c9ea54d2728a5 Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 18 Oct 2023 22:29:50 +0300 Subject: [PATCH 4/7] Use a meaningful variable for a calculation --- qiling/loader/elf.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/qiling/loader/elf.py b/qiling/loader/elf.py index 5bf8d8ba7..fd7e61c3d 100644 --- a/qiling/loader/elf.py +++ b/qiling/loader/elf.py @@ -87,6 +87,7 @@ def run(self): # setup program stack stack_address = self.profile.getint('stack_address') stack_size = self.profile.getint('stack_size') + top_of_stack = stack_address + stack_size self.ql.mem.map(stack_address, stack_size, info='[stack]') self.path = self.ql.path @@ -99,20 +100,20 @@ def run(self): # is it a driver? if elftype == 'ET_REL': - self.load_driver(elffile, stack_address + stack_size, loadbase=0x8000000) + self.load_driver(elffile, top_of_stack, loadbase=0x8000000) self.ql.hook_code(hook_kernel_api) # is it an executable? elif elftype == 'ET_EXEC': load_address = 0 - self.load_with_ld(elffile, stack_address + stack_size, load_address, self.argv, self.env) + self.load_with_ld(elffile, top_of_stack, load_address, self.argv, self.env) # is it a shared object? elif elftype == 'ET_DYN': load_address = self.profile.getint('load_address') - self.load_with_ld(elffile, stack_address + stack_size, load_address, self.argv, self.env) + self.load_with_ld(elffile, top_of_stack, load_address, self.argv, self.env) else: raise QlErrorELFFormat(f'unexpected elf type value (e_type = {elftype})') From 99e0cfc79d187ceaacbe556d5751a69506ef2adf Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 18 Oct 2023 22:31:06 +0300 Subject: [PATCH 5/7] Fix ELF KO loading details --- qiling/loader/elf.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/qiling/loader/elf.py b/qiling/loader/elf.py index fd7e61c3d..318a29e84 100644 --- a/qiling/loader/elf.py +++ b/qiling/loader/elf.py @@ -577,24 +577,24 @@ def __get_symbol(name: str) -> Optional[Symbol]: def load_driver(self, elffile: ELFFile, stack_addr: int, loadbase: int = 0) -> None: elfdata_mapping = self.get_elfdata_mapping(elffile) - # FIXME: determine true memory boundaries, taking relocation into account (if requested) - mem_start = 0 - mem_end = mem_start + self.ql.mem.align_up(len(elfdata_mapping), 0x1000) + mem_start = self.ql.mem.align(loadbase) + mem_end = self.ql.mem.align_up(loadbase + len(elfdata_mapping)) # map some memory to intercept external functions of Linux kernel self.ql.mem.map(API_HOOK_MEM, API_HOOK_SIZE, info="[api_mem]") - self.ql.log.debug(f'loadbase : {loadbase:#x}') self.ql.log.debug(f'mem_start : {mem_start:#x}') self.ql.log.debug(f'mem_end : {mem_end:#x}') - self.ql.mem.map(loadbase + mem_start, mem_end - mem_start, info=self.ql.path) - self.ql.mem.write(loadbase + mem_start, elfdata_mapping) + self.ql.mem.map(mem_start, mem_end - mem_start, info=os.path.basename(self.ql.path)) + self.ql.mem.write(loadbase, elfdata_mapping) - init_module = self.lkm_get_init(elffile) + loadbase + mem_start + self.images.append(Image(mem_start, mem_end, os.path.abspath(self.path))) + + init_module = loadbase + self.lkm_get_init(elffile) self.ql.log.debug(f'init_module : {init_module:#x}') - self.brk_address = mem_end + loadbase + self.brk_address = mem_end # Set MMAP addr mmap_address = self.profile.getint('mmap_address') @@ -607,14 +607,11 @@ def load_driver(self, elffile: ELFFile, stack_addr: int, loadbase: int = 0) -> N self.stack_address = self.ql.mem.align(stack_addr, self.ql.arch.pointersize) self.load_address = loadbase - # remember address of syscall table, so external tools can access to it - # self.ql.os.syscall_addr = SYSCALL_MEM - # setup syscall table self.ql.mem.map(SYSCALL_MEM, SYSCALL_SIZE, info="[syscall_mem]") self.ql.mem.write(SYSCALL_MEM, b'\x00' * SYSCALL_SIZE) - rev_reloc_symbols = self.lkm_dynlinker(elffile, mem_start + loadbase) + rev_reloc_symbols = self.lkm_dynlinker(elffile, loadbase) # iterate over relocatable symbols, but pick only those who start with 'sys_' for sc, addr in rev_reloc_symbols.items(): From e11770fd98966e1b4bff60c52fe39a5ef54435e1 Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 18 Oct 2023 22:32:01 +0300 Subject: [PATCH 6/7] Remove redundant os.elf_entry --- examples/fuzzing/dlink_dir815/dir815_mips32el_linux.py | 2 +- qiling/debugger/gdb/gdb.py | 2 +- qiling/loader/elf.py | 7 +++---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/examples/fuzzing/dlink_dir815/dir815_mips32el_linux.py b/examples/fuzzing/dlink_dir815/dir815_mips32el_linux.py index 8505c8e03..28afce921 100644 --- a/examples/fuzzing/dlink_dir815/dir815_mips32el_linux.py +++ b/examples/fuzzing/dlink_dir815/dir815_mips32el_linux.py @@ -42,7 +42,7 @@ def start_afl(_ql: Qiling): addr = ql.mem.search("HTTP_COOKIE=uid=1234&password=".encode()) ql.target_addr = addr[0] - main_addr = ql.os.elf_entry + main_addr = ql.loader.elf_entry ql.hook_address(callback=start_afl, address=main_addr) try: diff --git a/qiling/debugger/gdb/gdb.py b/qiling/debugger/gdb/gdb.py index e8c501181..a1648f368 100644 --- a/qiling/debugger/gdb/gdb.py +++ b/qiling/debugger/gdb/gdb.py @@ -83,7 +83,7 @@ def __get_attach_addr() -> int: entry_point = ql.loader.entry_point elif ql.os.type in (QL_OS.LINUX, QL_OS.FREEBSD) and not ql.code: - entry_point = ql.os.elf_entry + entry_point = ql.loader.elf_entry else: entry_point = ql.os.entry_point diff --git a/qiling/loader/elf.py b/qiling/loader/elf.py index 318a29e84..ddb050a57 100644 --- a/qiling/loader/elf.py +++ b/qiling/loader/elf.py @@ -376,7 +376,6 @@ def __push_str(top: int, s: str) -> int: self.init_sp = self.ql.arch.regs.arch_sp self.ql.os.entry_point = self.entry_point = entry_point - self.ql.os.elf_entry = self.elf_entry self.ql.os.function_hook = FunctionHook(self.ql, elf_phdr, elf_phnum, elf_phent, load_address, mem_end) # If there is a loader, we ignore exit @@ -600,9 +599,9 @@ def load_driver(self, elffile: ELFFile, stack_addr: int, loadbase: int = 0) -> N mmap_address = self.profile.getint('mmap_address') self.ql.log.debug(f'mmap_address is : {mmap_address:#x}') - # self.ql.os.elf_entry = self.elf_entry = loadbase + elfhead['e_entry'] - self.ql.os.entry_point = self.entry_point = init_module - self.elf_entry = self.ql.os.elf_entry = self.ql.os.entry_point + # there is no interperter so emulation entry point is also elf entry + self.elf_entry = self.entry_point = init_module + self.ql.os.entry_point = self.entry_point self.stack_address = self.ql.mem.align(stack_addr, self.ql.arch.pointersize) self.load_address = loadbase From 61a9dd7975f33cb77b1f8b10b029dfc8e0c73331 Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 4 Dec 2023 16:18:51 +0200 Subject: [PATCH 7/7] sendto and recvfrom can handle NULL addr arg --- qiling/os/posix/syscall/socket.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/qiling/os/posix/syscall/socket.py b/qiling/os/posix/syscall/socket.py index d02b8cfec..c0774b590 100644 --- a/qiling/os/posix/syscall/socket.py +++ b/qiling/os/posix/syscall/socket.py @@ -19,6 +19,9 @@ AF_INET = 2 AF_INET6 = 10 +SOCK_STREAM = 1 +SOCK_DGRAM = 2 +SOCK_SEQPACKET = 5 def inet_aton(ipaddr: str) -> int: # ipdata = bytes(int(a, 0) for a in ipaddr.split('.', 4)) @@ -765,10 +768,9 @@ def ql_syscall_recvfrom(ql: Qiling, sockfd: int, buf: int, length: int, flags: i if sock is None: return -1 - SOCK_STREAM = 1 - # For x8664, recvfrom() is called finally when calling recv() in TCP communications - if sock.socktype == SOCK_STREAM: + # calling recvfrom with a NULL addr argument is identical to calling recv, which is normally used only on a connected socket + if sock.socktype == SOCK_STREAM or (addr == 0 and addrlen == 0): return ql_syscall_recv(ql, sockfd, buf, length, flags) data_buf, address = sock.recvfrom(length, flags) @@ -842,10 +844,9 @@ def ql_syscall_sendto(ql: Qiling, sockfd: int, buf: int, length: int, flags: int if sock is None: return -1 - SOCK_STREAM = 1 - - # For x8664, sendto() is called finally when calling send() in TCP communications - if sock.socktype == SOCK_STREAM: + # if sendto is used on a connection-mode socket, the arguments addr and addrlen are ignored. + # also, calling sendto(sockfd, buf, length, flags, NULL, 0) is equivalent to send(sockfd, buf, length, flags) + if sock.socktype in (SOCK_STREAM, SOCK_SEQPACKET) or (addr == 0 and addrlen == 0): return ql_syscall_send(ql, sockfd, buf, length, flags) tmp_buf = ql.mem.read(buf, length)