From ef2688a29bf292d26949b4b47ac05c2414b72a0e Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 20 Feb 2022 15:36:57 +0200 Subject: [PATCH 01/18] Implement msvcrt _malloc_base --- qiling/os/windows/dlls/msvcrt.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/qiling/os/windows/dlls/msvcrt.py b/qiling/os/windows/dlls/msvcrt.py index 7fa3ed49b..3a7bf54b6 100644 --- a/qiling/os/windows/dlls/msvcrt.py +++ b/qiling/os/windows/dlls/msvcrt.py @@ -411,6 +411,17 @@ def hook_strncmp(ql: Qiling, address: int, params): return result +def __malloc(ql: Qiling, address: int, params): + size = params['size'] + + return ql.os.heap.alloc(size) + +@winsdkapi(cc=CDECL, params={ + 'size' : UINT +}) +def hook__malloc_base(ql: Qiling, address: int, params): + return __malloc(ql, address, params) + # void* malloc(unsigned int size) @winsdkapi(cc=CDECL, params={ 'size' : UINT From 13d7cc978189ad965789b936771f90e86976ed0e Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 20 Feb 2022 15:37:20 +0200 Subject: [PATCH 02/18] Implement msvcrt _free_base --- qiling/os/windows/dlls/msvcrt.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/qiling/os/windows/dlls/msvcrt.py b/qiling/os/windows/dlls/msvcrt.py index 3a7bf54b6..f65680cf3 100644 --- a/qiling/os/windows/dlls/msvcrt.py +++ b/qiling/os/windows/dlls/msvcrt.py @@ -431,15 +431,23 @@ def hook_malloc(ql: Qiling, address: int, params): return ql.os.heap.alloc(size) +def __free(ql: Qiling, address: int, params): + address = params['address'] + + return ql.os.heap.free(address) -# void* void* free(void *address) +@winsdkapi(cc=CDECL, params={ + 'address': POINTER +}) +def hook__free_base(ql: Qiling, address: int, params): + return __free(ql, address, params) + +# void* free(void *address) @winsdkapi(cc=CDECL, params={ 'address': POINTER }) def hook_free(ql: Qiling, address: int, params): - address = params['address'] - - return ql.os.heap.free(address) + return __free(ql, address, params) # _onexit_t _onexit( # _onexit_t function From 64f13d890e3bca1b4a2bb4dbff131fb322c0015b Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 20 Feb 2022 15:37:44 +0200 Subject: [PATCH 03/18] Implement msvcrt _calloc_base --- qiling/os/windows/dlls/msvcrt.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/qiling/os/windows/dlls/msvcrt.py b/qiling/os/windows/dlls/msvcrt.py index f65680cf3..52777f5a9 100644 --- a/qiling/os/windows/dlls/msvcrt.py +++ b/qiling/os/windows/dlls/msvcrt.py @@ -482,6 +482,23 @@ def hook_memset(ql: Qiling, address: int, params): return dest +def __calloc(ql: Qiling, address: int, params): + num = params['num'] + size = params['size'] + + count = num * size + ret = ql.os.heap.alloc(count) + ql.mem.write(ret, bytes([0] * count)) + + return ret + +@winsdkapi(cc=CDECL, params={ + 'num' : SIZE_T, + 'size' : SIZE_T +}) +def hook__calloc_base(ql: Qiling, address: int, params): + return __calloc(ql, address, params) + # void *calloc( # size_t num, # size_t size @@ -491,14 +508,7 @@ def hook_memset(ql: Qiling, address: int, params): 'size' : SIZE_T }) def hook_calloc(ql: Qiling, address: int, params): - num = params['num'] - size = params['size'] - - count = num * size - ret = ql.os.heap.alloc(count) - ql.mem.write(ret, bytes([0] * count)) - - return ret + return __calloc(ql, address, params) # void * memmove( # void *dest, From e43ac7a8c72513e0fe3db845cb08b5d4bb9fb39d Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 24 Feb 2022 01:54:54 +0200 Subject: [PATCH 04/18] Patch profile getint method to convert integers of all bases --- qiling/utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/qiling/utils.py b/qiling/utils.py index 47f27a594..5e21c78f2 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -8,6 +8,7 @@ thoughout the qiling framework """ +from functools import partial import importlib, os, copy, re, pefile, logging, yaml from configparser import ConfigParser @@ -517,7 +518,10 @@ def profile_setup(ql, ostype: QL_OS, filename: Optional[str]): if filename: profiles.append(filename) - config = ConfigParser() + # patch 'getint' to convert integers of all bases + int_converter = partial(int, base=0) + + config = ConfigParser(converters={'int': int_converter}) config.read(profiles) return config From fcc8ed50dc4973c5f61e7b6ac9ffbf37638c64d5 Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 28 Feb 2022 13:38:02 +0200 Subject: [PATCH 05/18] Patch sality test --- tests/test_pe_sys.py | 44 ++++++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/tests/test_pe_sys.py b/tests/test_pe_sys.py index ae2ef4392..c608f2053 100644 --- a/tests/test_pe_sys.py +++ b/tests/test_pe_sys.py @@ -4,6 +4,7 @@ # import platform, sys, unittest +from typing import List from unicorn import UcError @@ -22,12 +23,6 @@ class PETest(unittest.TestCase): - def hook_third_stop_address(self, ql): - print(" >>>> Third Stop address: 0x%08x" % ql.arch.regs.arch_pc) - self.third_stop = True - ql.emu_stop() - - def test_pe_win_x86_sality(self): def init_unseen_symbols(ql, address, name, ordinal, dll_name): @@ -159,7 +154,7 @@ def hook_StartServiceA(ql: Qiling, address: int, params): if service_handle.name in ql.os.services: service_path = ql.os.services[service_handle.name] service_path = ql.os.path.transform_to_real_path(service_path) - ql.amsint32_driver = Qiling([service_path], ql.rootfs, verbose=QL_VERBOSE.DISASM) + ql.amsint32_driver = Qiling([service_path], ql.rootfs, verbose=QL_VERBOSE.DEBUG) init_unseen_symbols(ql.amsint32_driver, ql.amsint32_driver.loader.dlls["ntoskrnl.exe"]+0xb7695, b"NtTerminateProcess", 0, "ntoskrnl.exe") print("load amsint32_driver") @@ -174,23 +169,24 @@ def hook_StartServiceA(ql: Qiling, address: int, params): else: return 1 - - def hook_first_stop_address(ql): - print(" >>>> First Stop address: 0x%08x" % ql.arch.regs.arch_pc) - ql.first_stop = True + def hook_first_stop_address(ql: Qiling, stops: List[bool]): + ql.log.info(f' >>>> First Stop address: {ql.arch.regs.arch_pc:#010x}') + stops[0] = True ql.emu_stop() + def hook_second_stop_address(ql: Qiling, stops: List[bool]): + ql.log.info(f' >>>> Second Stop address: {ql.arch.regs.arch_pc:#010x}') + stops[1] = True + ql.emu_stop() - def hook_second_stop_address(ql): - print(" >>>> Second Stop address: 0x%08x" % ql.arch.regs.arch_pc) - ql.second_stop = True + def hook_third_stop_address(ql: Qiling, stops: List[bool]): + ql.log.info(f' >>>> Third Stop address: {ql.arch.regs.arch_pc:#010x}') + stops[2] = True ql.emu_stop() + stops = [False, False, False] ql = Qiling(["../examples/rootfs/x86_windows/bin/sality.dll"], "../examples/rootfs/x86_windows", verbose=QL_VERBOSE.DEBUG) - ql.first_stop = False - ql.second_stop = False - self.third_stop = False # for this module ql.amsint32_driver = None # emulate some Windows API @@ -199,7 +195,7 @@ def hook_second_stop_address(ql): ql.os.set_api("WriteFile", hook_WriteFile) ql.os.set_api("StartServiceA", hook_StartServiceA) #init sality - ql.hook_address(hook_first_stop_address, 0x40EFFB) + ql.hook_address(hook_first_stop_address, 0x40EFFB, stops) ql.run() # run driver thread @@ -208,7 +204,7 @@ def hook_second_stop_address(ql): ql.os.fcall = ql.os.fcall_select(STDCALL) ql.os.fcall.writeParams(((DWORD, 0),)) - ql.hook_address(hook_second_stop_address, 0x4055FA) + ql.hook_address(hook_second_stop_address, 0x4055FA, stops) ql.run(begin=0x4053B2) print("test kill thread") if ql.amsint32_driver: @@ -216,7 +212,7 @@ def hook_second_stop_address(ql): # TODO: Should stop at 0x10423, but for now just stop at 0x0001066a stop_addr = 0x0001066a - ql.amsint32_driver.hook_address(self.hook_third_stop_address, stop_addr) + ql.amsint32_driver.hook_address(hook_third_stop_address, stop_addr, stops) # TODO: not sure whether this one is really STDCALL ql.amsint32_driver.os.fcall = ql.amsint32_driver.os.fcall_select(STDCALL) @@ -224,10 +220,10 @@ def hook_second_stop_address(ql): ql.amsint32_driver.run(begin=0x102D0) - self.assertEqual(True, ql.first_stop) - self.assertEqual(True, ql.second_stop) - self.assertEqual(True, self.third_stop) - self.assertEqual(True, ql.test_set_api) + self.assertTrue(stops[0]) + self.assertTrue(stops[1]) + self.assertTrue(stops[2]) + self.assertTrue(ql.test_set_api) def test_pe_win_x8664_driver(self): From 4fa1155ca1d905d041c6f95d7f836d54d31df0a8 Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 28 Feb 2022 13:38:48 +0200 Subject: [PATCH 06/18] Minor fixes to PE tests --- tests/test_pe.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/tests/test_pe.py b/tests/test_pe.py index a0848fd0a..8f79fd057 100644 --- a/tests/test_pe.py +++ b/tests/test_pe.py @@ -230,9 +230,9 @@ def ThreadId_onEnter(ql, address, params): self.assertTrue(QLWinSingleTest(_t).run()) - def test_pe_win_x86_clipboard(self): + def test_pe_win_x8664_clipboard(self): def _t(): - ql = Qiling(["../examples/rootfs/x8664_windows/bin//x8664_clipboard_test.exe"], "../examples/rootfs/x8664_windows") + ql = Qiling(["../examples/rootfs/x8664_windows/bin/x8664_clipboard_test.exe"], "../examples/rootfs/x8664_windows") ql.run() del ql return True @@ -240,7 +240,7 @@ def _t(): self.assertTrue(QLWinSingleTest(_t).run()) - def test_pe_win_x86_tls(self): + def test_pe_win_x8664_tls(self): def _t(): ql = Qiling(["../examples/rootfs/x8664_windows/bin/x8664_tls.exe"], "../examples/rootfs/x8664_windows") ql.run() @@ -455,14 +455,12 @@ def check_print(ql: Qiling, address: int, params): arglist = params['_ArgList'] count = format.count("%") - fargs = [ql.unpack(ql.mem.read(arglist + i * ql.arch.pointersize, ql.arch.pointersize)) for i in range(count)] - - target_txt = "" + fargs = [ql.mem.read_ptr(arglist + i * ql.arch.pointersize) for i in range(count)] try: target_txt = ql.mem.string(fargs[1]) except: - pass + target_txt = "" return address, params From 35773c4695d2c3a071a1bf7bf0a9e53bf6c4e9b7 Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 28 Feb 2022 13:42:59 +0200 Subject: [PATCH 07/18] Patch DOS EXE test to expect a NotImplementedError --- tests/test_dos_exe.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_dos_exe.py b/tests/test_dos_exe.py index 34e385a1a..d98b17303 100644 --- a/tests/test_dos_exe.py +++ b/tests/test_dos_exe.py @@ -10,10 +10,12 @@ class DOSTest(unittest.TestCase): - # TODO: missing implemention of INT 3Ch and INT 03h def test_dos_8086_hello(self): ql = Qiling(["../examples/rootfs/8086/dos/ARKA.DOS_EXE"], "../examples/rootfs/8086/dos") - ql.run() + + # TODO: missing implemention of INT 3Ch and INT 03h + with self.assertRaises(NotImplementedError): + ql.run() if __name__ == "__main__": unittest.main() From 5eb99edb615410c3043401f92a241a3cc99a9ba4 Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 28 Feb 2022 13:44:44 +0200 Subject: [PATCH 08/18] Bugfix in SetInformationProcess --- qiling/os/windows/dlls/ntdll.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qiling/os/windows/dlls/ntdll.py b/qiling/os/windows/dlls/ntdll.py index 42728e7a3..b077d4b9b 100644 --- a/qiling/os/windows/dlls/ntdll.py +++ b/qiling/os/windows/dlls/ntdll.py @@ -11,7 +11,6 @@ from qiling.exception import * from qiling.os.const import * from qiling.os.windows.const import * -from qiling.os.windows.utils import * from qiling.os.windows.handle import * from qiling.os.windows import structs @@ -314,7 +313,7 @@ def _SetInformationProcess(ql: Qiling, address: int, params): pebBaseAddress=ql.os.heap_base_address, affinityMask=0, basePriority=0, uniqueId=ql.os.profile.getint("KERNEL", "pid"), - parentPid=ql.os.profile.geting("KERNEL", "parent_pid") + parentPid=ql.os.profile.getint("KERNEL", "parent_pid") ) ql.log.debug("The target may be attempting to modify the PEB debug flag") From aeee41fe98e1d50b702ccf752baf866f13f2794b Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 28 Feb 2022 13:47:10 +0200 Subject: [PATCH 09/18] Add libcache option to qltool run --- qltool | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qltool b/qltool index 108f0843e..266dc0ba2 100755 --- a/qltool +++ b/qltool @@ -117,7 +117,8 @@ def handle_run(options: argparse.Namespace): log_file=options.log_file, log_plain=options.log_plain, multithread=options.multithread, - filter=options.filter + filter=options.filter, + libcache=options.libcache ) # attach Qdb at entry point @@ -214,6 +215,7 @@ if __name__ == '__main__': comm_parser.add_argument('-c', '--coverage-file', default=None, help='code coverage file name') comm_parser.add_argument('--coverage-format', default='drcov', choices=cov_utils.factory.formats, help='code coverage file format') comm_parser.add_argument('--json', action='store_true', help='print a json report of the emulation') + comm_parser.add_argument('--libcache', action='store_true', help='enable dll caching for windows') options = parser.parse_args() # subparser argument required=True is not supported in python 3.6 From 42f678cf0e6921d73abc7c485a64ad150c31f2ef Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 1 Mar 2022 14:08:26 +0200 Subject: [PATCH 10/18] Adjust README examples --- README.md | 52 ++++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index fb6f69663..71e45aa17 100644 --- a/README.md +++ b/README.md @@ -88,55 +88,55 @@ Please see [setup guide](https://docs.qiling.io/en/latest/install/) file for how #### Examples -- Below example shows how to use Qiling framework to emulate a Windows EXE on a Linux machine +- The example below shows how to use Qiling framework in the most striaghtforward way to emulate a Windows executable. ```python -from qiling import * - -# sandbox to emulate the EXE -def my_sandbox(path, rootfs): - # setup Qiling engine - ql = Qiling(path, rootfs) - # now emulate the EXE - ql.run() +from qiling import Qiling if __name__ == "__main__": - # execute Windows EXE under our rootfs - my_sandbox(["examples/rootfs/x86_windows/bin/x86_hello.exe"], "examples/rootfs/x86_windows") + # initialize Qiling instance, specifying the executable to emulate and the emulated system root. + # note that the current working directory is assumed to be Qiling home + ql = Qiling([r'examples/rootfs/x86_windows/bin/x86_hello.exe'], r'examples/rootfs/x86_windows') + + # start emulation + ql.run() ``` -- Below example shows how to use Qiling framework to dynamically patch a Windows crackme, make it always display "Congratulation" dialog +- The following example shows how a Windows crackme may be patched dynamically to make it always display the "Congratulation" dialog. ```python -from qiling import * +from qiling import Qiling + +def force_call_dialog_func(ql: Qiling): + # get DialogFunc address from current stack frame + lpDialogFunc = ql.stack_read(-8) -def force_call_dialog_func(ql): - # get DialogFunc address - lpDialogFunc = ql.unpack32(ql.mem.read(ql.reg.esp - 0x8, 4)) # setup stack memory for DialogFunc ql.stack_push(0) - ql.stack_push(1001) - ql.stack_push(273) + ql.stack_push(1001) # IDS_APPNAME + ql.stack_push(0x111) # WM_COMMAND ql.stack_push(0) + + # push return address ql.stack_push(0x0401018) - # force EIP to DialogFunc - ql.reg.eip = lpDialogFunc + + # resume emulation from DialogFunc address + ql.arch.regs.eip = lpDialogFunc -def my_sandbox(path, rootfs): - ql = Qiling(path, rootfs) +if __name__ == "__main__": + # initialize Qiling instance + ql = Qiling([r'rootfs/x86_windows/bin/Easy_CrackMe.exe'], r'rootfs/x86_windows') + # NOP out some code ql.patch(0x004010B5, b'\x90\x90') ql.patch(0x004010CD, b'\x90\x90') ql.patch(0x0040110B, b'\x90\x90') ql.patch(0x00401112, b'\x90\x90') + # hook at an address with a callback ql.hook_address(force_call_dialog_func, 0x00401016) ql.run() - - -if __name__ == "__main__": - my_sandbox(["rootfs/x86_windows/bin/Easy_CrackMe.exe"], "rootfs/x86_windows") ``` The below Youtube video shows how the above example works. From 00fdb2cd10da8279f144f801df9e8eef17a555e6 Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 1 Mar 2022 14:10:06 +0200 Subject: [PATCH 11/18] Minor opportunistic changes to some POSIX syscalls --- qiling/os/posix/syscall/select.py | 6 +++--- qiling/os/posix/syscall/signal.py | 8 +++----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/qiling/os/posix/syscall/select.py b/qiling/os/posix/syscall/select.py index 283680520..2e61222a0 100644 --- a/qiling/os/posix/syscall/select.py +++ b/qiling/os/posix/syscall/select.py @@ -21,7 +21,7 @@ def parse_fd_set(ql: Qiling, max_fd: int, struct_addr: int) -> Tuple[Sequence[in for i in range(max_fd): if i % 32 == 0: - tmp = ql.unpack32(ql.mem.read(struct_addr + i, 4)) + tmp = ql.mem.read_ptr(struct_addr + i, 4) if tmp & 0x1: fileno = ql.os.fd[i].fileno() @@ -52,8 +52,8 @@ def handle_ready_fds(ptr: int, ready_fds: Sequence, fds_map: Mapping): n = ql.arch.pointersize if timeout: - sec = ql.unpack(ql.mem.read(timeout + n * 0, n)) - usec = ql.unpack(ql.mem.read(timeout + n * 1, n)) + sec = ql.mem.read_ptr(timeout + n * 0) + usec = ql.mem.read_ptr(timeout + n * 1) timeout_total = sec + float(usec) / 1000000 else: diff --git a/qiling/os/posix/syscall/signal.py b/qiling/os/posix/syscall/signal.py index e8c06cbe3..c0e4583a7 100644 --- a/qiling/os/posix/syscall/signal.py +++ b/qiling/os/posix/syscall/signal.py @@ -7,15 +7,13 @@ def ql_syscall_rt_sigaction(ql: Qiling, signum: int, act: int, oldact: int): if oldact: - if ql.os.sigaction_act[signum] == 0: - data = b'\x00' * 20 - else: - data = b''.join(ql.pack32(key) for key in ql.os.sigaction_act[signum]) + arr = ql.os.sigaction_act[signum] or [0] * 5 + data = b''.join(ql.pack32(key) for key in arr) ql.mem.write(oldact, data) if act: - ql.os.sigaction_act[signum] = [ql.unpack32(ql.mem.read(act + 4 * key, 4)) for key in range(5)] + ql.os.sigaction_act[signum] = [ql.mem.read_ptr(act + 4 * i, 4) for i in range(5)] return 0 From 8482f6f22bcf27e9041a441078c30a374fbc48d6 Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 1 Mar 2022 14:45:56 +0200 Subject: [PATCH 12/18] Various minor opportunistic changes --- qiling/core.py | 17 +++++++++++++---- qiling/os/os.py | 4 ++-- qiling/os/utils.py | 3 +++ 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/qiling/core.py b/qiling/core.py index 9d250731e..aad184cad 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -53,7 +53,7 @@ def __init__( ): """ Create a Qiling instance. - For each argument or property, please refer to its docstring. e.g. Qiling.multithread.__doc__ + For each argument or property, please refer to its help. e.g. help(Qiling.multithread) """ ################################## @@ -705,8 +705,17 @@ def stop(self): self.uc.emu_stop() # start emulation - def emu_start(self, begin, end, timeout=0, count=0): - self.uc.emu_start(begin, end, timeout, count) + def emu_start(self, begin: int, end: int, timeout: int = 0, icount: int = 0): + """Start emulation. - if self._internal_exception != None: + Args: + begin : emulation starting address + end : emulation ending address + timeout : max emulation time (in microseconds); unlimited by default + icount : max emulation steps (instructions count); unlimited by default + """ + + self.uc.emu_start(begin, end, timeout, icount) + + if self._internal_exception is not None: raise self._internal_exception diff --git a/qiling/os/os.py b/qiling/os/os.py index e9b84e96b..7654933c7 100644 --- a/qiling/os/os.py +++ b/qiling/os/os.py @@ -69,10 +69,10 @@ def __init__(self, ql: Qiling, resolvers: Mapping[Any, Resolver] = {}): }.get(self.ql.arch.bits, None) if self.ql.code: - self.code_ram_size = int(self.profile.get("CODE", "ram_size"), 16) # this shellcode entrypoint does not work for windows # windows shellcode entry point will comes from pe loader - self.entry_point = int(self.profile.get("CODE", "entry_point"), 16) + self.entry_point = self.profile.getint('CODE', 'entry_point') + self.code_ram_size = self.profile.getint('CODE', 'ram_size') # default fcall paramters resolving methods self.resolvers = { diff --git a/qiling/os/utils.py b/qiling/os/utils.py index 47b206863..8e8a6e28c 100644 --- a/qiling/os/utils.py +++ b/qiling/os/utils.py @@ -14,6 +14,9 @@ from qiling.const import QL_VERBOSE class QlOsStats: + """Record basic OS statistics, such as API calls and strings. + """ + def __init__(self): self.syscalls: MutableMapping[str, List] = {} self.syscalls_counter = 0 From c2b85a9fd739ab6ec062cc08e7877e0d80c0a21c Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 1 Mar 2022 15:28:22 +0200 Subject: [PATCH 13/18] Revert count arg name --- qiling/core.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiling/core.py b/qiling/core.py index aad184cad..321c72b4e 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -705,17 +705,17 @@ def stop(self): self.uc.emu_stop() # start emulation - def emu_start(self, begin: int, end: int, timeout: int = 0, icount: int = 0): + def emu_start(self, begin: int, end: int, timeout: int = 0, count: int = 0): """Start emulation. Args: begin : emulation starting address end : emulation ending address timeout : max emulation time (in microseconds); unlimited by default - icount : max emulation steps (instructions count); unlimited by default + count : max emulation steps (instructions count); unlimited by default """ - self.uc.emu_start(begin, end, timeout, icount) + self.uc.emu_start(begin, end, timeout, count) if self._internal_exception is not None: raise self._internal_exception From 6b66a97d1a5964400fc6435bdfece1192d65d8c5 Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 2 Mar 2022 14:10:44 +0200 Subject: [PATCH 14/18] Cache POSIX syscall mapper --- qiling/os/freebsd/map_syscall.py | 12 +++++-- qiling/os/linux/map_syscall.py | 23 +++++++------ qiling/os/macos/map_syscall.py | 23 +++++++------ qiling/os/posix/posix.py | 58 +++++++++++++++----------------- qiling/os/qnx/map_syscall.py | 12 +++++-- qiling/utils.py | 6 ++-- 6 files changed, 74 insertions(+), 60 deletions(-) diff --git a/qiling/os/freebsd/map_syscall.py b/qiling/os/freebsd/map_syscall.py index b09e7eeec..a9265f7da 100644 --- a/qiling/os/freebsd/map_syscall.py +++ b/qiling/os/freebsd/map_syscall.py @@ -6,9 +6,15 @@ from qiling.const import QL_ARCH from qiling.os.posix.posix import SYSCALL_PREF -def map_syscall(ql, syscall_num): - if ql.arch.type == QL_ARCH.X8664: - return f'{SYSCALL_PREF}{x8664_syscall_table[syscall_num]}' +def get_syscall_mapper(archtype: QL_ARCH): + syscall_table = { + QL_ARCH.X8664 : x8664_syscall_table + }[archtype] + + def __mapper(syscall_num: int) -> str: + return f'{SYSCALL_PREF}{syscall_table[syscall_num]}' + + return __mapper x8664_syscall_table = { 0: 'syscall', diff --git a/qiling/os/linux/map_syscall.py b/qiling/os/linux/map_syscall.py index 87bd3552a..dea619ac9 100644 --- a/qiling/os/linux/map_syscall.py +++ b/qiling/os/linux/map_syscall.py @@ -11,18 +11,21 @@ from qiling.const import QL_ARCH from qiling.os.posix.posix import SYSCALL_PREF -def map_syscall(ql, syscall_num): +def get_syscall_mapper(archtype: QL_ARCH): syscall_table = { - QL_ARCH.ARM64: arm64_syscall_table, - QL_ARCH.ARM: arm_syscall_table, - QL_ARCH.X8664: x8664_syscall_table, - QL_ARCH.X86: x86_syscall_table, - QL_ARCH.MIPS: mips_syscall_table, - QL_ARCH.RISCV: riscv32_syscall_table, - QL_ARCH.RISCV64: riscv64_syscall_table, - }[ql.arch.type] + QL_ARCH.ARM64 : arm64_syscall_table, + QL_ARCH.ARM : arm_syscall_table, + QL_ARCH.X8664 : x8664_syscall_table, + QL_ARCH.X86 : x86_syscall_table, + QL_ARCH.MIPS : mips_syscall_table, + QL_ARCH.RISCV : riscv32_syscall_table, + QL_ARCH.RISCV64 : riscv64_syscall_table + }[archtype] - return f'{SYSCALL_PREF}{syscall_table[syscall_num]}' + def __mapper(syscall_num: int) -> str: + return f'{SYSCALL_PREF}{syscall_table[syscall_num]}' + + return __mapper arm_syscall_table = { 0: "restart_syscall", diff --git a/qiling/os/macos/map_syscall.py b/qiling/os/macos/map_syscall.py index 36f57c077..feb1cd38b 100644 --- a/qiling/os/macos/map_syscall.py +++ b/qiling/os/macos/map_syscall.py @@ -3,23 +3,24 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -# cols = ("arm64", "x8664") - from qiling.const import QL_ARCH from qiling.os.posix.posix import SYSCALL_PREF -def map_syscall(ql, syscall_num): - if ql.arch.type == QL_ARCH.X8664: - if syscall_num >= 0x2000000 and syscall_num <= 0x3000000: - syscall_num = syscall_num - 0x2000000 +def get_syscall_mapper(archtype: QL_ARCH): + syscall_table = { + QL_ARCH.X8664 : x8664_syscall_table, + QL_ARCH.ARM64 : arm64_syscall_table + }[archtype] - return f'{SYSCALL_PREF}{x8664_syscall_table[syscall_num]}' + syscall_fixup = { + QL_ARCH.X8664 : lambda n: (n - 0x2000000) if 0x2000000 <= n <= 0x3000000 else n, + QL_ARCH.ARM64 : lambda n: (n - 0xffffffffffffff00) if n >= 0xffffffffffffff00 else n + }[archtype] - elif ql.arch.type == QL_ARCH.ARM64: - if syscall_num >= 0xffffffffffffff00: - syscall_num = syscall_num - 0xffffffffffffff00 + def __mapper(syscall_num: int) -> str: + return f'{SYSCALL_PREF}{syscall_table[syscall_fixup(syscall_num)]}' - return f'{SYSCALL_PREF}{arm64_syscall_table[syscall_num]}' + return __mapper arm64_syscall_table = { diff --git a/qiling/os/posix/posix.py b/qiling/os/posix/posix.py index 1c9b12591..651324dff 100644 --- a/qiling/os/posix/posix.py +++ b/qiling/os/posix/posix.py @@ -80,32 +80,36 @@ def __init__(self, ql: Qiling): } self.__syscall_id_reg = { - QL_ARCH.ARM64: UC_ARM64_REG_X8, - QL_ARCH.ARM : UC_ARM_REG_R7, - QL_ARCH.MIPS : UC_MIPS_REG_V0, - QL_ARCH.X86 : UC_X86_REG_EAX, - QL_ARCH.X8664: UC_X86_REG_RAX, - QL_ARCH.RISCV: UC_RISCV_REG_A7, - QL_ARCH.RISCV64: UC_RISCV_REG_A7 + QL_ARCH.ARM64 : UC_ARM64_REG_X8, + QL_ARCH.ARM : UC_ARM_REG_R7, + QL_ARCH.MIPS : UC_MIPS_REG_V0, + QL_ARCH.X86 : UC_X86_REG_EAX, + QL_ARCH.X8664 : UC_X86_REG_RAX, + QL_ARCH.RISCV : UC_RISCV_REG_A7, + QL_ARCH.RISCV64 : UC_RISCV_REG_A7 }[self.ql.arch.type] - # handle a special case + # handle some special cases if (self.ql.arch.type == QL_ARCH.ARM64) and (self.ql.ostype == QL_OS.MACOS): self.__syscall_id_reg = UC_ARM64_REG_X16 - if (self.ql.arch.type == QL_ARCH.ARM) and (self.ql.ostype == QL_OS.QNX): + + elif (self.ql.arch.type == QL_ARCH.ARM) and (self.ql.ostype == QL_OS.QNX): self.__syscall_id_reg = UC_ARM_REG_R12 # TODO: use abstract to access __syscall_cc and __syscall_id_reg by defining a system call class self.__syscall_cc: QlCC = { - QL_ARCH.ARM64: aarch64, - QL_ARCH.ARM : aarch32, - QL_ARCH.MIPS : mipso32, - QL_ARCH.X86 : intel32, - QL_ARCH.X8664: intel64, - QL_ARCH.RISCV: riscv32, - QL_ARCH.RISCV64: riscv64, + QL_ARCH.ARM64 : aarch64, + QL_ARCH.ARM : aarch32, + QL_ARCH.MIPS : mipso32, + QL_ARCH.X86 : intel32, + QL_ARCH.X8664 : intel64, + QL_ARCH.RISCV : riscv32, + QL_ARCH.RISCV64 : riscv64 }[self.ql.arch.type](self.ql.arch) + # select syscall mapping function based on emulated OS and architecture + self.syscall_mapper = ql_syscall_mapping_function(self.ql.ostype, self.ql.arch.type) + self._fd = QlFileDes() # the QlOs constructor cannot assign the standard streams using their designated properties since @@ -141,10 +145,6 @@ def root(self, enabled: bool) -> None: self.euid = 0 if enabled else self.uid self.egid = 0 if enabled else self.gid - @property - def syscall(self): - return self.get_syscall() - def set_syscall(self, target: Union[int, str], handler: Callable, intercept: QL_INTERCEPT=QL_INTERCEPT.CALL): """Either hook or replace a system call with a custom one. @@ -178,10 +178,8 @@ def getNameFromErrorCode(ret: int) -> str: return f'{ret:#x}{f" ({errors[-ret]})" if -ret in errors else f""}' def load_syscall(self): - # import syscall mapping function - map_syscall = ql_syscall_mapping_function(self.ql.ostype) - syscall_id = self.syscall - syscall_name = map_syscall(self.ql, syscall_id) + syscall_id = self.get_syscall() + syscall_name = self.syscall_mapper(syscall_id) # get syscall on-enter hook (if any) hooks_dict = self.posix_syscall_hooks[QL_INTERCEPT.ENTER] @@ -196,14 +194,14 @@ def load_syscall(self): syscall_hook = hooks_dict.get(syscall_name) or hooks_dict.get(syscall_id) if not syscall_hook: - osname = ostype_convert_str(self.ql.ostype) - os_syscalls = ql_get_module_function(f"qiling.os.{osname.lower()}", "syscall") - posix_syscalls = ql_get_module_function(f"qiling.os.posix", "syscall") + def __get_os_module(osname: str): + return ql_get_module_function(f'qiling.os.{osname.lower()}', 'syscall') + + os_syscalls = __get_os_module(ostype_convert_str(self.ql.ostype)) + posix_syscalls = __get_os_module('posix') # look in os-specific and posix syscall hooks - if syscall_name: - self.ql.log.debug("syscall hooked 0x%x: %s()" % (self.ql.arch.regs.arch_pc, syscall_name)) - syscall_hook = getattr(os_syscalls, syscall_name, None) or getattr(posix_syscalls, syscall_name, None) + syscall_hook = getattr(os_syscalls, syscall_name, None) or getattr(posix_syscalls, syscall_name, None) if syscall_hook: syscall_name = syscall_hook.__name__ diff --git a/qiling/os/qnx/map_syscall.py b/qiling/os/qnx/map_syscall.py index d668579f8..66c586337 100644 --- a/qiling/os/qnx/map_syscall.py +++ b/qiling/os/qnx/map_syscall.py @@ -6,9 +6,15 @@ from qiling.const import QL_ARCH from qiling.os.posix.posix import SYSCALL_PREF -def map_syscall(ql, syscall_num): - if ql.arch.type == QL_ARCH.ARM: - return f'{SYSCALL_PREF}{arm_syscall_table[syscall_num]}' +def get_syscall_mapper(archtype: QL_ARCH): + syscall_table = { + QL_ARCH.ARM : arm_syscall_table + }[archtype] + + def __mapper(syscall_num: int) -> str: + return f'{SYSCALL_PREF}{syscall_table[syscall_num]}' + + return __mapper # Source: https://github.com/vocho/openqnx # trunk/services/system/public/sys/kercalls.h diff --git a/qiling/utils.py b/qiling/utils.py index 5e21c78f2..1952fe70e 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -479,14 +479,14 @@ def arch_setup(archtype: QL_ARCH, endian: QL_ENDIAN, thumb: bool, ql): # This function is extracted from os_setup (QlOsPosix) so I put it here. -def ql_syscall_mapping_function(ostype: QL_OS): +def ql_syscall_mapping_function(ostype: QL_OS, archtype: QL_ARCH): qlos_name = ostype_convert_str(ostype) qlos_path = f'qiling.os.{qlos_name.lower()}.map_syscall' - qlos_func = 'map_syscall' + qlos_func = 'get_syscall_mapper' func = ql_get_module_function(qlos_path, qlos_func) - return func + return func(archtype) def os_setup(ostype: QL_OS, ql): From 7153f7de68335b6c202c5d8ffa47ca8a43906e5f Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 2 Mar 2022 15:00:38 +0200 Subject: [PATCH 15/18] Revisit test_elf_ko --- tests/test_elf_ko.py | 102 +++++++++++++++++++------------------------ 1 file changed, 46 insertions(+), 56 deletions(-) diff --git a/tests/test_elf_ko.py b/tests/test_elf_ko.py index c7286e14c..a1b1bbfea 100644 --- a/tests/test_elf_ko.py +++ b/tests/test_elf_ko.py @@ -3,84 +3,74 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -import sys, unittest +import os, sys, unittest from unicorn import UcError sys.path.append("..") from qiling import Qiling from qiling.const import QL_INTERCEPT, QL_VERBOSE -from qiling.os.const import STRING -from qiling.os.linux.fncc import linux_kernel_api + +IS_FAST_TEST = 'QL_FAST_TEST' in os.environ class ELF_KO_Test(unittest.TestCase): def test_demigod_m0hamed_x86(self): - @linux_kernel_api(params={ - "format": STRING - }) - def my_printk(ql, address, params): - print("\n") - print("=" * 40) - print(" Enter into my_printk mode") - print("=" * 40) - print("\n") - self.set_api_myprintk = params["format"] + if IS_FAST_TEST: + self.skipTest('QL_FAST_TEST') + + checklist = {} + + def my_printk(ql: Qiling, address: int, params): + ql.log.info(f'oncall printk: params = {params}') + + checklist['oncall'] = params['format'] + return 0 - ql = Qiling(["../examples/rootfs/x86_linux/kernel/m0hamed_rootkit.ko"], "../examples/rootfs/x86_linux", verbose=QL_VERBOSE.DISASM) + ql = Qiling(["../examples/rootfs/x86_linux/kernel/m0hamed_rootkit.ko"], "../examples/rootfs/x86_linux", verbose=QL_VERBOSE.DEBUG) + ql.os.set_api("printk", my_printk) + + ba = ql.loader.load_address + try: - procfile_read_func_begin = ql.loader.load_address + 0x11e0 - procfile_read_func_end = ql.loader.load_address + 0x11fa - ql.os.set_api("printk", my_printk) - ql.run(begin=procfile_read_func_begin, end=procfile_read_func_end) + ql.run(ba + 0x11e0, ba + 0x11fa) except UcError as e: - print(e) - sys.exit(-1) - self.assertEqual("DONT YOU EVER TRY TO READ THIS FILE OR I AM GOING TO DESTROY YOUR MOST SECRET DREAMS", self.set_api_myprintk) - del ql + self.fail(e) + else: + self.assertEqual("DONT YOU EVER TRY TO READ THIS FILE OR I AM GOING TO DESTROY YOUR MOST SECRET DREAMS", checklist['oncall']) def test_demigod_hello_x8664(self): - def my_onenter(ql, address, params): - print("\n") - print("=" * 40) - print(" Enter into my_onenter mode") - print("params: %s" % params) - print("=" * 40) - print("\n") - self.set_api_onenter = params["format"] - return address, params - - ql = Qiling(["../examples/rootfs/x8664_linux/kernel/hello.ko"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DISASM) - try: - procfile_read_func_begin = ql.loader.load_address + 0x1064 - procfile_read_func_end = ql.loader.load_address + 0x107e - ql.os.set_api("printk", my_onenter, QL_INTERCEPT.ENTER) - ql.run(begin=procfile_read_func_begin, end=procfile_read_func_end) - except UcError as e: - print(e) - sys.exit(-1) - self.assertEqual("\x016Hello, World: %p!\n", self.set_api_onenter) - del ql + checklist = {} + + def my_onenter(ql: Qiling, address: int, params): + ql.log.info(f'onenter printk: params = {params}') + + checklist['onenter'] = params['format'] + + ql = Qiling(["../examples/rootfs/x8664_linux/kernel/hello.ko"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG) + ql.os.set_api("printk", my_onenter, QL_INTERCEPT.ENTER) + + ba = ql.loader.load_address + ql.run(ba + 0x1064, ba + 0x107e) + + self.assertEqual("\x016Hello, World: %p!\n", checklist['onenter']) def test_demigod_hello_mips32(self): - def my_onexit(ql, address, params, retval): - print("\n") - print("=" * 40) - print(" Enter into my_exit mode") - print("params: %s" % params) - print("=" * 40) - print("\n") - self.set_api_onexit = params["format"] + checklist = {} + + def my_onexit(ql: Qiling, address: int, params, retval: int): + ql.log.info(f'onexit printk: params = {params}') + + checklist['onexit'] = params['format'] ql = Qiling(["../examples/rootfs/mips32_linux/kernel/hello.ko"], "../examples/rootfs/mips32_linux", verbose=QL_VERBOSE.DEBUG) - begin = ql.loader.load_address + 0x1060 - end = ql.loader.load_address + 0x1084 ql.os.set_api("printk", my_onexit, QL_INTERCEPT.EXIT) - ql.run(begin=begin, end=end) - self.assertEqual("\x016Hello, World!\n", self.set_api_onexit) - del ql + ba = ql.loader.load_address + ql.run(ba + 0x1060, ba + 0x1084) + + self.assertEqual("\x016Hello, World!\n", checklist['onexit']) if __name__ == "__main__": unittest.main() From 293f9855283f90fb2dc8b027bcfeb8994c60d801 Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 2 Mar 2022 15:03:31 +0200 Subject: [PATCH 16/18] Revisit test_pe --- tests/test_pe.py | 43 ++++++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/tests/test_pe.py b/tests/test_pe.py index 8f79fd057..ae6bf04f3 100644 --- a/tests/test_pe.py +++ b/tests/test_pe.py @@ -133,8 +133,11 @@ def close(self): ql.run() del ql return True - - self.assertTrue(IS_FAST_TEST or QLWinSingleTest(_t).run()) + + if IS_FAST_TEST: + self.skipTest('QL_FAST_TEST') + + self.assertTrue(QLWinSingleTest(_t).run()) def test_pe_win_x86_gandcrab(self): @@ -206,8 +209,11 @@ def randomize_config_value(ql, key, subkey): del ql return True - - self.assertTrue(IS_FAST_TEST or QLWinSingleTest(_t).run()) + + if IS_FAST_TEST: + self.skipTest('QL_FAST_TEST') + + self.assertTrue(QLWinSingleTest(_t).run()) def test_pe_win_x86_multithread(self): def _t(): @@ -333,8 +339,11 @@ def stop(ql): ql.run() del ql return True - - self.assertTrue(IS_FAST_TEST or QLWinSingleTest(_t).run()) + + if IS_FAST_TEST: + self.skipTest('QL_FAST_TEST') + + self.assertTrue(QLWinSingleTest(_t).run()) def test_pe_win_x86_NtQueryInformationSystem(self): @@ -354,15 +363,16 @@ 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 - + # 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') @@ -377,7 +387,10 @@ def end(ql): del ql return True - self.assertTrue(IS_FAST_TEST or QLWinSingleTest(_t).run()) + if IS_FAST_TEST: + self.skipTest('QL_FAST_TEST') + + self.assertTrue(QLWinSingleTest(_t).run()) def test_pe_win_x8664_customapi(self): From df3dcc9e35549c1a73466c223fdfb0a05cf25a22 Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 2 Mar 2022 16:00:13 +0200 Subject: [PATCH 17/18] Revert some changes made to m0hamed_rootkit test --- tests/test_elf_ko.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_elf_ko.py b/tests/test_elf_ko.py index a1b1bbfea..a9cc56c0a 100644 --- a/tests/test_elf_ko.py +++ b/tests/test_elf_ko.py @@ -10,6 +10,8 @@ sys.path.append("..") from qiling import Qiling from qiling.const import QL_INTERCEPT, QL_VERBOSE +from qiling.os.const import STRING +from qiling.os.linux.fncc import linux_kernel_api IS_FAST_TEST = 'QL_FAST_TEST' in os.environ @@ -21,6 +23,9 @@ def test_demigod_m0hamed_x86(self): checklist = {} + @linux_kernel_api(params={ + "format": STRING + }) def my_printk(ql: Qiling, address: int, params): ql.log.info(f'oncall printk: params = {params}') From 5e5df87230dba144369939b6f7d26548d8630f43 Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 2 Mar 2022 19:18:56 +0200 Subject: [PATCH 18/18] Fix code patching scenario on Linux --- qiling/loader/elf.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/qiling/loader/elf.py b/qiling/loader/elf.py index efb5e78ce..77dba9ffc 100644 --- a/qiling/loader/elf.py +++ b/qiling/loader/elf.py @@ -68,9 +68,14 @@ def __init__(self, ql: Qiling): def run(self): if self.ql.code: self.ql.mem.map(self.ql.os.entry_point, self.ql.os.code_ram_size, info="[shellcode_stack]") - self.ql.os.entry_point = (self.ql.os.entry_point + 0x200000 - 0x1000) - self.ql.mem.write(self.ql.os.entry_point, self.ql.code) - self.ql.arch.regs.arch_sp = self.ql.os.entry_point + + shellcode_base = self.ql.os.entry_point + 0x200000 - 0x1000 + self.ql.mem.write(shellcode_base, self.ql.code) + + self.ql.arch.regs.arch_sp = shellcode_base + self.ql.os.entry_point = shellcode_base + self.load_address = shellcode_base + return section = {