From 7cf9e889bb3bec81dc935796790f7d0cdb7012a6 Mon Sep 17 00:00:00 2001 From: xwings Date: Mon, 10 Jan 2022 16:52:33 +0800 Subject: [PATCH 001/406] minor tweak for PE loader --- qiling/loader/pe.py | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/qiling/loader/pe.py b/qiling/loader/pe.py index 726fc10a1..e860a0d39 100644 --- a/qiling/loader/pe.py +++ b/qiling/loader/pe.py @@ -461,24 +461,19 @@ def run(self): self.sys_dlls.append(b"ntoskrnl.exe") if self.ql.archtype == QL_ARCH.X86: - self.stack_address = int(self.ql.os.profile.get("OS32", "stack_address"), 16) - self.stack_size = int(self.ql.os.profile.get("OS32", "stack_size"), 16) - self.image_address = int(self.ql.os.profile.get("OS32", "image_address"), 16) - self.dll_address = int(self.ql.os.profile.get("OS32", "dll_address"), 16) - self.entry_point = int(self.ql.os.profile.get("OS32", "entry_point"), 16) - self.ql.os.heap_base_address = int(self.ql.os.profile.get("OS32", "heap_address"), 16) - self.ql.os.heap_base_size = int(self.ql.os.profile.get("OS32", "heap_size"), 16) + WINOSARCH = "OS32" self.structure_last_addr = FS_SEGMENT_ADDR elif self.ql.archtype == QL_ARCH.X8664: - self.stack_address = int(self.ql.os.profile.get("OS64", "stack_address"), 16) - self.stack_size = int(self.ql.os.profile.get("OS64", "stack_size"), 16) - self.image_address = int(self.ql.os.profile.get("OS64", "image_address"), 16) - self.dll_address = int(self.ql.os.profile.get("OS64", "dll_address"), 16) - self.entry_point = int(self.ql.os.profile.get("OS64", "entry_point"), 16) - self.ql.os.heap_base_address = int(self.ql.os.profile.get("OS64", "heap_address"), 16) - self.ql.os.heap_base_size = int(self.ql.os.profile.get("OS64", "heap_size"), 16) + WINOSARCH = "OS64" self.structure_last_addr = GS_SEGMENT_ADDR + self.stack_address = int(self.ql.os.profile.get(WINOSARCH, "stack_address"), 16) + self.stack_size = int(self.ql.os.profile.get(WINOSARCH, "stack_size"), 16) + self.image_address = int(self.ql.os.profile.get(WINOSARCH, "image_address"), 16) + self.dll_address = int(self.ql.os.profile.get(WINOSARCH, "dll_address"), 16) + self.entry_point = int(self.ql.os.profile.get(WINOSARCH, "entry_point"), 16) + self.ql.os.heap_base_address = int(self.ql.os.profile.get(WINOSARCH, "heap_address"), 16) + self.ql.os.heap_base_size = int(self.ql.os.profile.get(WINOSARCH, "heap_size"), 16) self.dlls = {} self.import_symbols = {} self.export_symbols = {} From 2db8d779ce1f3e858bb2ed34ba86b71300001ec1 Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 14 Jan 2022 13:19:17 +0200 Subject: [PATCH 002/406] Turn QlArch.init_uc to an inherited cached property --- qiling/arch/arch.py | 11 +++++++---- qiling/arch/arm.py | 6 ++++-- qiling/arch/arm64.py | 6 ++++-- qiling/arch/cortex_m.py | 8 +++++--- qiling/arch/evm/evm.py | 3 ++- qiling/arch/mips.py | 6 ++++-- qiling/arch/riscv.py | 6 ++++-- qiling/arch/riscv64.py | 6 ++++-- qiling/arch/x86.py | 10 +++++++--- qiling/core.py | 2 +- qiling/os/posix/syscall/unistd.py | 2 +- 11 files changed, 43 insertions(+), 23 deletions(-) diff --git a/qiling/arch/arch.py b/qiling/arch/arch.py index f27925d1f..6958a6a3c 100644 --- a/qiling/arch/arch.py +++ b/qiling/arch/arch.py @@ -3,7 +3,7 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from abc import ABC +from abc import ABC, abstractmethod from typing import Optional from unicorn import Uc @@ -22,10 +22,13 @@ def __init__(self, ql: Qiling): self._disasm: Optional[Cs] = None self._asm: Optional[Ks] = None - # ql.init_Uc - initialized unicorn engine @property - def init_uc(self) -> Uc: - return self.get_init_uc() + @abstractmethod + def uc(self) -> Uc: + """Get unicorn instance bound to arch. + """ + + pass def stack_push(self, value: int) -> int: diff --git a/qiling/arch/arm.py b/qiling/arch/arm.py index 7615d71a2..69c4915d8 100644 --- a/qiling/arch/arm.py +++ b/qiling/arch/arm.py @@ -3,6 +3,8 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # +from functools import cached_property + from unicorn import Uc, UC_ARCH_ARM, UC_MODE_ARM, UC_MODE_THUMB, UC_MODE_BIG_ENDIAN from capstone import Cs, CS_ARCH_ARM, CS_MODE_ARM, CS_MODE_THUMB from keystone import Ks, KS_ARCH_ARM, KS_MODE_ARM, KS_MODE_THUMB @@ -29,8 +31,8 @@ def __init__(self, ql: Qiling): self.arm_get_tls_addr = 0xFFFF0FE0 - # get initialized unicorn engine - def get_init_uc(self) -> Uc: + @cached_property + def uc(self) -> Uc: if self.ql.archendian == QL_ENDIAN.EB: mode = UC_MODE_ARM + UC_MODE_BIG_ENDIAN diff --git a/qiling/arch/arm64.py b/qiling/arch/arm64.py index 6a08a50fc..5f49d64ad 100644 --- a/qiling/arch/arm64.py +++ b/qiling/arch/arm64.py @@ -3,6 +3,8 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # +from functools import cached_property + from unicorn import Uc, UC_ARCH_ARM64, UC_MODE_ARM from capstone import Cs, CS_ARCH_ARM64, CS_MODE_ARM from keystone import Ks, KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN @@ -26,8 +28,8 @@ def __init__(self, ql: Qiling): self.ql.reg.register_sp(reg_map["sp"]) self.ql.reg.register_pc(reg_map["pc"]) - # get initialized unicorn engine - def get_init_uc(self) -> Uc: + @cached_property + def uc(self) -> Uc: return Uc(UC_ARCH_ARM64, UC_MODE_ARM) def create_disassembler(self) -> Cs: diff --git a/qiling/arch/cortex_m.py b/qiling/arch/cortex_m.py index d1b4c0bcd..225916372 100644 --- a/qiling/arch/cortex_m.py +++ b/qiling/arch/cortex_m.py @@ -3,12 +3,13 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # +from functools import cached_property +from contextlib import ContextDecorator + from unicorn import Uc, UC_ARCH_ARM, UC_MODE_ARM, UC_MODE_MCLASS, UC_MODE_THUMB from capstone import Cs, CS_ARCH_ARM, CS_MODE_ARM, CS_MODE_MCLASS, CS_MODE_THUMB from keystone import Ks, KS_ARCH_ARM, KS_MODE_ARM, KS_MODE_THUMB -from contextlib import ContextDecorator - from qiling.const import QL_VERBOSE from qiling.exception import QlErrorNotImplemented @@ -68,7 +69,8 @@ def __init__(self, ql): for reg_maper in reg_maps: self.ql.reg.expand_mapping(reg_maper) - def get_init_uc(self): + @cached_property + def uc(self): return Uc(UC_ARCH_ARM, UC_MODE_ARM + UC_MODE_MCLASS + UC_MODE_THUMB) def create_disassembler(self) -> Cs: diff --git a/qiling/arch/evm/evm.py b/qiling/arch/evm/evm.py index cfcc1abce..97a72bda8 100644 --- a/qiling/arch/evm/evm.py +++ b/qiling/arch/evm/evm.py @@ -28,5 +28,6 @@ def stack_read(self, offset): def stack_write(self, offset, data): return None - def get_init_uc(self): + @property + def uc(self): return None diff --git a/qiling/arch/mips.py b/qiling/arch/mips.py index ee26b6c4b..e57f30e6d 100644 --- a/qiling/arch/mips.py +++ b/qiling/arch/mips.py @@ -3,6 +3,8 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # +from functools import cached_property + from unicorn import Uc, UC_ARCH_MIPS, UC_MODE_MIPS32, UC_MODE_BIG_ENDIAN, UC_MODE_LITTLE_ENDIAN from capstone import Cs, CS_ARCH_MIPS, CS_MODE_MIPS32, CS_MODE_BIG_ENDIAN, CS_MODE_LITTLE_ENDIAN from keystone import Ks, KS_ARCH_MIPS, KS_MODE_MIPS32, KS_MODE_BIG_ENDIAN, KS_MODE_LITTLE_ENDIAN @@ -27,8 +29,8 @@ def __init__(self, ql: Qiling): self.ql.reg.register_sp(reg_map["sp"]) self.ql.reg.register_pc(reg_map["pc"]) - # get initialized unicorn engine - def get_init_uc(self) -> Uc: + @cached_property + def uc(self) -> Uc: endian = { QL_ENDIAN.EB: UC_MODE_BIG_ENDIAN, QL_ENDIAN.EL: UC_MODE_LITTLE_ENDIAN diff --git a/qiling/arch/riscv.py b/qiling/arch/riscv.py index e2a162eae..fbc20860c 100644 --- a/qiling/arch/riscv.py +++ b/qiling/arch/riscv.py @@ -3,6 +3,8 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # +from functools import cached_property + from unicorn import Uc, UC_ARCH_RISCV, UC_MODE_RISCV32 from capstone import Cs from keystone import Ks @@ -28,8 +30,8 @@ def __init__(self, ql: Qiling): self.ql.reg.register_sp(reg_map["sp"]) self.ql.reg.register_pc(reg_map["pc"]) - # get initialized unicorn engine - def get_init_uc(self) -> Uc: + @cached_property + def uc(self) -> Uc: return Uc(UC_ARCH_RISCV, UC_MODE_RISCV32) def create_disassembler(self) -> Cs: diff --git a/qiling/arch/riscv64.py b/qiling/arch/riscv64.py index c7a31d081..493883641 100644 --- a/qiling/arch/riscv64.py +++ b/qiling/arch/riscv64.py @@ -3,6 +3,8 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # +from functools import cached_property + from unicorn import Uc, UC_ARCH_RISCV, UC_MODE_RISCV64 from capstone import Cs from keystone import Ks @@ -18,8 +20,8 @@ class QlArchRISCV64(QlArchRISCV): def __init__(self, ql: Qiling): super().__init__(ql) - # get initialized unicorn engine - def get_init_uc(self) -> Uc: + @cached_property + def uc(self) -> Uc: return Uc(UC_ARCH_RISCV, UC_MODE_RISCV64) def create_disassembler(self) -> Cs: diff --git a/qiling/arch/x86.py b/qiling/arch/x86.py index 34b77cc0b..21eb1a00b 100644 --- a/qiling/arch/x86.py +++ b/qiling/arch/x86.py @@ -4,6 +4,7 @@ # from struct import pack +from functools import cached_property from unicorn import Uc, UC_ARCH_X86, UC_MODE_16, UC_MODE_32, UC_MODE_64 from capstone import Cs, CS_ARCH_X86, CS_MODE_16, CS_MODE_32, CS_MODE_64 @@ -51,7 +52,8 @@ def __init__(self, ql: Qiling): self.ql.reg.register_pc(reg_map_16["sp"]) self.ql.reg.register_sp(reg_map_16["ip"]) - def get_init_uc(self) -> Uc: + @cached_property + def uc(self) -> Uc: return Uc(UC_ARCH_X86, UC_MODE_16) def create_disassembler(self) -> Cs: @@ -85,7 +87,8 @@ def __init__(self, ql: Qiling): self.ql.reg.register_sp(reg_map_32["esp"]) self.ql.reg.register_pc(reg_map_32["eip"]) - def get_init_uc(self) -> Uc: + @cached_property + def uc(self) -> Uc: return Uc(UC_ARCH_X86, UC_MODE_32) def create_disassembler(self) -> Cs: @@ -124,7 +127,8 @@ def __init__(self, ql: Qiling): self.ql.reg.register_sp(reg_map_64["rsp"]) self.ql.reg.register_pc(reg_map_64["rip"]) - def get_init_uc(self) -> Uc: + @cached_property + def uc(self) -> Uc: return Uc(UC_ARCH_X86, UC_MODE_64) def create_disassembler(self) -> Cs: diff --git a/qiling/core.py b/qiling/core.py index 4af14e11f..2b749ee8e 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -204,7 +204,7 @@ def __init__( # Once we finish setting up arch layer, we can init QlCoreHooks. if not self.interpreter: - self.uc = self.arch.init_uc + self.uc = self.arch.uc QlCoreHooks.__init__(self, self.uc) self.arch.utils.setup_output() diff --git a/qiling/os/posix/syscall/unistd.py b/qiling/os/posix/syscall/unistd.py index e16500766..df98d3ad0 100644 --- a/qiling/os/posix/syscall/unistd.py +++ b/qiling/os/posix/syscall/unistd.py @@ -432,7 +432,7 @@ def __read_str_array(addr: int) -> Iterator[str]: if ql.code: return - ql._uc = ql.arch.init_uc + ql._uc = ql.arch.uc QlCoreHooks.__init__(ql, ql._uc) ql.os.load() From 915ce2dcfd582d8ac847ffd2c582ada1b3861d70 Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 14 Jan 2022 13:20:05 +0200 Subject: [PATCH 003/406] Minor changes to QlArch derivatives --- qiling/arch/arch.py | 8 ++++---- qiling/arch/riscv.py | 3 ++- qiling/arch/riscv64.py | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/qiling/arch/arch.py b/qiling/arch/arch.py index 6958a6a3c..d799ad92a 100644 --- a/qiling/arch/arch.py +++ b/qiling/arch/arch.py @@ -3,7 +3,7 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from abc import ABC, abstractmethod +from abc import abstractmethod from typing import Optional from unicorn import Uc @@ -14,7 +14,7 @@ from qiling import Qiling from .utils import QlArchUtils -class QlArch(ABC): +class QlArch: def __init__(self, ql: Qiling): self.ql = ql self.utils = QlArchUtils(ql) @@ -121,14 +121,14 @@ def context_restore(self, saved_context: UcContext): def create_disassembler(self) -> Cs: - """Get disassembler insatnce bound to arch. + """Get disassembler instance bound to arch. """ raise NotImplementedError(self.__class__.__name__) def create_assembler(self) -> Ks: - """Get assembler insatnce bound to arch. + """Get assembler instance bound to arch. """ raise NotImplementedError(self.__class__.__name__) diff --git a/qiling/arch/riscv.py b/qiling/arch/riscv.py index fbc20860c..3de0c2373 100644 --- a/qiling/arch/riscv.py +++ b/qiling/arch/riscv.py @@ -37,9 +37,10 @@ def uc(self) -> Uc: def create_disassembler(self) -> Cs: try: from capstone import CS_ARCH_RISCV, CS_MODE_RISCV32, CS_MODE_RISCVC - return Cs(CS_ARCH_RISCV, CS_MODE_RISCV32 + CS_MODE_RISCVC) except ImportError: raise QlErrorNotImplemented("Capstone does not yet support riscv, upgrade to capstone 5.0") + else: + return Cs(CS_ARCH_RISCV, CS_MODE_RISCV32 + CS_MODE_RISCVC) def create_assembler(self) -> Ks: raise QlErrorNotImplemented("Keystone does not yet support riscv") diff --git a/qiling/arch/riscv64.py b/qiling/arch/riscv64.py index 493883641..494ccdee4 100644 --- a/qiling/arch/riscv64.py +++ b/qiling/arch/riscv64.py @@ -15,7 +15,6 @@ from .riscv import QlArchRISCV - class QlArchRISCV64(QlArchRISCV): def __init__(self, ql: Qiling): super().__init__(ql) @@ -27,9 +26,10 @@ def uc(self) -> Uc: def create_disassembler(self) -> Cs: try: from capstone import CS_ARCH_RISCV, CS_MODE_RISCV64, CS_MODE_RISCVC - return Cs(CS_ARCH_RISCV, CS_MODE_RISCV64 + CS_MODE_RISCVC) except ImportError: raise QlErrorNotImplemented("Capstone does not yet support riscv, upgrade to capstone 5.0") + else: + return Cs(CS_ARCH_RISCV, CS_MODE_RISCV64 + CS_MODE_RISCVC) def create_assembler(self) -> Ks: raise QlErrorNotImplemented("Keystone does not yet support riscv") From acec19f385c0d4fb1a557a3688ccbbb350f4090e Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 14 Jan 2022 13:21:47 +0200 Subject: [PATCH 004/406] Remove QlArch.set_pc --- qiling/arch/arch.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/qiling/arch/arch.py b/qiling/arch/arch.py index d799ad92a..6556f84da 100644 --- a/qiling/arch/arch.py +++ b/qiling/arch/arch.py @@ -90,11 +90,6 @@ def stack_write(self, offset: int, value: int) -> None: self.ql.mem.write(self.ql.reg.arch_sp + offset, self.ql.pack(value)) - # set PC - def set_pc(self, address: int) -> None: - self.ql.reg.arch_pc = address - - # get PC def get_pc(self) -> int: return self.ql.reg.arch_pc From ea7e4f330a9825e2e2f93d78e7f4151fdbb692e9 Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 14 Jan 2022 13:24:45 +0200 Subject: [PATCH 005/406] Remove QlArch.set_sp --- qiling/arch/arch.py | 5 ----- qiling/os/posix/syscall/sched.py | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/qiling/arch/arch.py b/qiling/arch/arch.py index 6556f84da..e5991a189 100644 --- a/qiling/arch/arch.py +++ b/qiling/arch/arch.py @@ -95,11 +95,6 @@ def get_pc(self) -> int: return self.ql.reg.arch_pc - # set stack pointer - def set_sp(self, address: int) -> None: - self.ql.reg.arch_sp = address - - # get stack pointer def get_sp(self) -> int: return self.ql.reg.arch_sp diff --git a/qiling/os/posix/syscall/sched.py b/qiling/os/posix/syscall/sched.py index dbc493f3a..2a48adf62 100644 --- a/qiling/os/posix/syscall/sched.py +++ b/qiling/os/posix/syscall/sched.py @@ -71,7 +71,7 @@ def ql_syscall_clone(ql: Qiling, flags: int, child_stack: int, parent_tidptr: in f_th.set_clear_child_tid_addr(child_tidptr) if child_stack != 0: - ql.arch.set_sp(child_stack) + ql.reg.arch_sp = child_stack # ql.log.debug(f'clone(new_stack = {child_stack:#x}, flags = {flags:#x}, tls = {newtls:#x}, ptidptr = {parent_tidptr:#x}, ctidptr = {child_tidptr:#x}) = {regreturn:d}') ql.emu_stop() From 745e164ff45b97e28b7c721e14da06fa614773a0 Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 14 Jan 2022 13:25:42 +0200 Subject: [PATCH 006/406] Remove QlArch.get_sp --- qiling/arch/arch.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/qiling/arch/arch.py b/qiling/arch/arch.py index e5991a189..8f217aa97 100644 --- a/qiling/arch/arch.py +++ b/qiling/arch/arch.py @@ -95,11 +95,6 @@ def get_pc(self) -> int: return self.ql.reg.arch_pc - # get stack pointer - def get_sp(self) -> int: - return self.ql.reg.arch_sp - - # Unicorn's CPU state save def context_save(self) -> UcContext: return self.ql.uc.context_save() From 75d4b3b58fa4554c16b55ad9efb7278b44bc9786 Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 14 Jan 2022 13:33:40 +0200 Subject: [PATCH 007/406] Replace arch.get_pc with reg.arch_pc where possible --- qiling/arch/riscv.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiling/arch/riscv.py b/qiling/arch/riscv.py index 3de0c2373..0dc194d46 100644 --- a/qiling/arch/riscv.py +++ b/qiling/arch/riscv.py @@ -67,7 +67,7 @@ def soft_interrupt_handler(self, ql, intno): raise QlErrorNotImplemented(f'Unhandled interrupt number ({intno})') def step(self): - self.ql.emu_start(self.get_pc(), 0, count=1) + self.ql.emu_start(self.ql.reg.arch_pc, 0, count=1) self.ql.hw.step() def stop(self): @@ -77,7 +77,7 @@ def run(self, count=-1, end=None): self.runable = True while self.runable and count != 0: - if self.get_pc() == end: + if self.ql.reg.arch_pc == end: break self.step() From a295e0b685ee9ddde77a7d0fbf4d96b5950fe59c Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 14 Jan 2022 13:42:41 +0200 Subject: [PATCH 008/406] Rename create_disassembler and make it a cached property --- qiling/arch/arch.py | 7 ++++--- qiling/arch/arm.py | 3 ++- qiling/arch/arm64.py | 8 +++----- qiling/arch/cortex_m.py | 3 ++- qiling/arch/mips.py | 16 +++++++--------- qiling/arch/riscv.py | 3 ++- qiling/arch/riscv64.py | 3 ++- qiling/arch/x86.py | 24 +++++++++--------------- 8 files changed, 31 insertions(+), 36 deletions(-) diff --git a/qiling/arch/arch.py b/qiling/arch/arch.py index 8f217aa97..9b8df52a1 100644 --- a/qiling/arch/arch.py +++ b/qiling/arch/arch.py @@ -19,7 +19,6 @@ def __init__(self, ql: Qiling): self.ql = ql self.utils = QlArchUtils(ql) - self._disasm: Optional[Cs] = None self._asm: Optional[Ks] = None @property @@ -105,11 +104,13 @@ def context_restore(self, saved_context: UcContext): self.ql.uc.context_restore(saved_context) - def create_disassembler(self) -> Cs: + @property + @abstractmethod + def disassembler(self) -> Cs: """Get disassembler instance bound to arch. """ - raise NotImplementedError(self.__class__.__name__) + pass def create_assembler(self) -> Ks: diff --git a/qiling/arch/arm.py b/qiling/arch/arm.py index 69c4915d8..b5ca7ca4c 100644 --- a/qiling/arch/arm.py +++ b/qiling/arch/arm.py @@ -62,7 +62,8 @@ def __is_thumb(self) -> bool: return bool(self.ql.reg.cpsr & cpsr_v) - def create_disassembler(self) -> Cs: + @property + def disassembler(self) -> Cs: # note: we do not cache the disassembler instance; rather we refresh it # each time to make sure thumb mode is taken into account diff --git a/qiling/arch/arm64.py b/qiling/arch/arm64.py index 5f49d64ad..51b5bff58 100644 --- a/qiling/arch/arm64.py +++ b/qiling/arch/arm64.py @@ -32,11 +32,9 @@ def __init__(self, ql: Qiling): def uc(self) -> Uc: return Uc(UC_ARCH_ARM64, UC_MODE_ARM) - def create_disassembler(self) -> Cs: - if self._disasm is None: - self._disasm = Cs(CS_ARCH_ARM64, CS_MODE_ARM) - - return self._disasm + @cached_property + def disassembler(self) -> Cs: + return Cs(CS_ARCH_ARM64, CS_MODE_ARM) def create_assembler(self) -> Ks: if self._asm is None: diff --git a/qiling/arch/cortex_m.py b/qiling/arch/cortex_m.py index 225916372..02fe2809f 100644 --- a/qiling/arch/cortex_m.py +++ b/qiling/arch/cortex_m.py @@ -73,7 +73,8 @@ def __init__(self, ql): def uc(self): return Uc(UC_ARCH_ARM, UC_MODE_ARM + UC_MODE_MCLASS + UC_MODE_THUMB) - def create_disassembler(self) -> Cs: + @cached_property + def disassembler(self) -> Cs: return Cs(CS_ARCH_ARM, CS_MODE_ARM + CS_MODE_MCLASS + CS_MODE_THUMB) def create_assembler(self) -> Ks: diff --git a/qiling/arch/mips.py b/qiling/arch/mips.py index e57f30e6d..cf1e499f0 100644 --- a/qiling/arch/mips.py +++ b/qiling/arch/mips.py @@ -38,16 +38,14 @@ def uc(self) -> Uc: return Uc(UC_ARCH_MIPS, UC_MODE_MIPS32 + endian) - def create_disassembler(self) -> Cs: - if self._disasm is None: - endian = { - QL_ENDIAN.EL : CS_MODE_LITTLE_ENDIAN, - QL_ENDIAN.EB : CS_MODE_BIG_ENDIAN - }[self.ql.archendian] - - self._disasm = Cs(CS_ARCH_MIPS, CS_MODE_MIPS32 + endian) + @cached_property + def disassembler(self) -> Cs: + endian = { + QL_ENDIAN.EL : CS_MODE_LITTLE_ENDIAN, + QL_ENDIAN.EB : CS_MODE_BIG_ENDIAN + }[self.ql.archendian] - return self._disasm + return Cs(CS_ARCH_MIPS, CS_MODE_MIPS32 + endian) def create_assembler(self) -> Ks: if self._asm is None: diff --git a/qiling/arch/riscv.py b/qiling/arch/riscv.py index 0dc194d46..c62563c8f 100644 --- a/qiling/arch/riscv.py +++ b/qiling/arch/riscv.py @@ -34,7 +34,8 @@ def __init__(self, ql: Qiling): def uc(self) -> Uc: return Uc(UC_ARCH_RISCV, UC_MODE_RISCV32) - def create_disassembler(self) -> Cs: + @cached_property + def disassembler(self) -> Cs: try: from capstone import CS_ARCH_RISCV, CS_MODE_RISCV32, CS_MODE_RISCVC except ImportError: diff --git a/qiling/arch/riscv64.py b/qiling/arch/riscv64.py index 494ccdee4..6554c3dc0 100644 --- a/qiling/arch/riscv64.py +++ b/qiling/arch/riscv64.py @@ -23,7 +23,8 @@ def __init__(self, ql: Qiling): def uc(self) -> Uc: return Uc(UC_ARCH_RISCV, UC_MODE_RISCV64) - def create_disassembler(self) -> Cs: + @cached_property + def disassembler(self) -> Cs: try: from capstone import CS_ARCH_RISCV, CS_MODE_RISCV64, CS_MODE_RISCVC except ImportError: diff --git a/qiling/arch/x86.py b/qiling/arch/x86.py index 21eb1a00b..30351c349 100644 --- a/qiling/arch/x86.py +++ b/qiling/arch/x86.py @@ -56,11 +56,9 @@ def __init__(self, ql: Qiling): def uc(self) -> Uc: return Uc(UC_ARCH_X86, UC_MODE_16) - def create_disassembler(self) -> Cs: - if not self._disasm: - self._disasm = Cs(CS_ARCH_X86, CS_MODE_16) - - return self._disasm + @cached_property + def disassembler(self) -> Cs: + return Cs(CS_ARCH_X86, CS_MODE_16) def create_assembler(self) -> Ks: if not self._asm: @@ -91,11 +89,9 @@ def __init__(self, ql: Qiling): def uc(self) -> Uc: return Uc(UC_ARCH_X86, UC_MODE_32) - def create_disassembler(self) -> Cs: - if not self._disasm: - self._disasm = Cs(CS_ARCH_X86, CS_MODE_32) - - return self._disasm + @cached_property + def disassembler(self) -> Cs: + return Cs(CS_ARCH_X86, CS_MODE_32) def create_assembler(self) -> Ks: if not self._asm: @@ -131,11 +127,9 @@ def __init__(self, ql: Qiling): def uc(self) -> Uc: return Uc(UC_ARCH_X86, UC_MODE_64) - def create_disassembler(self) -> Cs: - if not self._disasm: - self._disasm = Cs(CS_ARCH_X86, CS_MODE_64) - - return self._disasm + @cached_property + def disassembler(self) -> Cs: + return Cs(CS_ARCH_X86, CS_MODE_64) def create_assembler(self) -> Ks: if not self._asm: From da5612db701050fc1eaff9a15a9f668c13b69ff5 Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 14 Jan 2022 13:53:30 +0200 Subject: [PATCH 009/406] Remove disassembler properties from core --- qiling/core.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/qiling/core.py b/qiling/core.py index 2b749ee8e..947e8191f 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -847,15 +847,6 @@ def assembler(self): return self.create_assembler() - @property - def disassembler(self): - return self.create_disassembler() - - - def create_disassembler(self): - return self.arch.create_disassembler() - - def create_assembler(self): return self.arch.create_assembler() From ce5f10f9693c2ed3bab93ff362a54d82af21a73e Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 14 Jan 2022 13:55:43 +0200 Subject: [PATCH 010/406] Adjust all disassembler usages --- examples/fuzzing/qnx_arm/fuzz_arm_qnx.py | 2 +- examples/hello_x8664_linux_disasm.py | 2 +- qiling/arch/riscv.py | 2 +- qiling/arch/utils.py | 2 +- qiling/debugger/disassember.py | 3 ++- qiling/debugger/qdb/utils.py | 2 +- qiling/extensions/trace.py | 4 ++-- qiling/os/posix/syscall/sched.py | 2 +- qiling/os/uefi/uefi.py | 2 +- 9 files changed, 11 insertions(+), 10 deletions(-) diff --git a/examples/fuzzing/qnx_arm/fuzz_arm_qnx.py b/examples/fuzzing/qnx_arm/fuzz_arm_qnx.py index 3aace0d01..2ec27befe 100755 --- a/examples/fuzzing/qnx_arm/fuzz_arm_qnx.py +++ b/examples/fuzzing/qnx_arm/fuzz_arm_qnx.py @@ -50,7 +50,7 @@ def start_afl(_ql: Qiling): if enable_trace: # The following lines are only for `-t` debug output - md = ql.create_disassembler() + md = ql.arch.disassembler count = [0] def spaced_hex(data): diff --git a/examples/hello_x8664_linux_disasm.py b/examples/hello_x8664_linux_disasm.py index adfb22f75..c2173ed0e 100644 --- a/examples/hello_x8664_linux_disasm.py +++ b/examples/hello_x8664_linux_disasm.py @@ -38,7 +38,7 @@ def trace(ql: Qiling, address: int, size: int, md: Cs): if __name__ == "__main__": ql = Qiling(["rootfs/x8664_linux/bin/x8664_hello"], "rootfs/x8664_linux") - md = ql.create_disassembler() + md = ql.arch.disassembler md.detail = True ql.hook_code(trace, user_data=md) diff --git a/qiling/arch/riscv.py b/qiling/arch/riscv.py index c62563c8f..6f4a56c7a 100644 --- a/qiling/arch/riscv.py +++ b/qiling/arch/riscv.py @@ -57,7 +57,7 @@ def soft_interrupt_handler(self, ql, intno): try: address, size = ql.reg.pc - 4, 4 tmp = ql.mem.read(address, size) - qd = ql.arch.create_disassembler() + qd = ql.arch.disassembler insn = '\n> '.join(f'{insn.mnemonic} {insn.op_str}' for insn in qd.disasm(tmp, address)) except QlErrorNotImplemented: diff --git a/qiling/arch/utils.py b/qiling/arch/utils.py index 76149024f..7310c67fe 100644 --- a/qiling/arch/utils.py +++ b/qiling/arch/utils.py @@ -33,7 +33,7 @@ def get_offset_and_name(self, addr: int) -> Tuple[int, str]: def disassembler(self, ql: Qiling, address: int, size: int): tmp = ql.mem.read(address, size) - qd = ql.arch.create_disassembler() + qd = ql.arch.disassembler offset, name = self.get_offset_and_name(address) log_data = f'{address:0{ql.archbit // 4}x} [{name:20s} + {offset:#08x}] {tmp.hex(" "):30s}' diff --git a/qiling/debugger/disassember.py b/qiling/debugger/disassember.py index d04671897..1b1300b40 100644 --- a/qiling/debugger/disassember.py +++ b/qiling/debugger/disassember.py @@ -24,8 +24,9 @@ def disasm_all_lines(self): def disasm_elf(self, seg_name='.text'): def disasm(ql, address, size): - md = ql.create_disassembler() + md = ql.arch.disassembler md.detail = True + return md.disasm(ql.mem.read(address, size), address) disasm_result = [] diff --git a/qiling/debugger/qdb/utils.py b/qiling/debugger/qdb/utils.py index f32003205..bf2e24f34 100644 --- a/qiling/debugger/qdb/utils.py +++ b/qiling/debugger/qdb/utils.py @@ -127,7 +127,7 @@ def is_thumb(bits: int) -> bool: def disasm(ql: Qiling, address: int, detail: bool = False) -> Optional[int]: - md = ql.disassembler + md = ql.arch.disassembler md.detail = detail try: ret = next(md.disasm(_read_inst(ql, address), address)) diff --git a/qiling/extensions/trace.py b/qiling/extensions/trace.py index d2d7692d0..ade5e8f95 100644 --- a/qiling/extensions/trace.py +++ b/qiling/extensions/trace.py @@ -152,7 +152,7 @@ def enable_full_trace(ql: Qiling): """ # enable detailed disassembly info - md = ql.create_disassembler() + md = ql.arch.disassembler md.detail = True assert md.arch == CS_ARCH_X86, 'currently available only for intel architecture' @@ -189,7 +189,7 @@ def enable_history_trace(ql: Qiling, nrecords: int): """ # enable detailed disassembly info - md = ql.create_disassembler() + md = ql.arch.disassembler md.detail = True assert md.arch == CS_ARCH_X86, 'currently available only for intel architecture' diff --git a/qiling/os/posix/syscall/sched.py b/qiling/os/posix/syscall/sched.py index 2a48adf62..4754c7162 100644 --- a/qiling/os/posix/syscall/sched.py +++ b/qiling/os/posix/syscall/sched.py @@ -105,7 +105,7 @@ def ql_syscall_clone(ql: Qiling, flags: int, child_stack: int, parent_tidptr: in # We have to find next pc manually for some archs since the pc is current instruction (like `syscall`). if ql.archtype in (QL_ARCH.X8664, ): - ql.reg.arch_pc += list(ql.disassembler.disasm_lite(bytes(ql.mem.read(ql.reg.arch_pc, 4)), ql.reg.arch_pc))[0][1] + ql.reg.arch_pc += list(ql.arch.disassembler.disasm_lite(bytes(ql.mem.read(ql.reg.arch_pc, 4)), ql.reg.arch_pc))[0][1] ql.log.debug(f"Fix pc for child thread to {hex(ql.reg.arch_pc)}") ql.os.set_syscall_return(0) diff --git a/qiling/os/uefi/uefi.py b/qiling/os/uefi/uefi.py index 1ee19fbb1..d9db3489a 100644 --- a/qiling/os/uefi/uefi.py +++ b/qiling/os/uefi/uefi.py @@ -146,7 +146,7 @@ def emit_hexdump(self, address: int, data: bytearray, num_cols: int = 16): def emit_disasm(self, address: int, data: bytearray, num_insns: int = 8): - md = self.ql.create_disassembler() + md = self.ql.arch.disassembler self.ql.log.error('Disassembly:') From 999891caf2f99d6c769f63264d4660411bb39914 Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 14 Jan 2022 14:09:57 +0200 Subject: [PATCH 011/406] Rename create_assembler and make it a cached property --- qiling/arch/arch.py | 9 ++++----- qiling/arch/arm.py | 3 ++- qiling/arch/arm64.py | 8 +++----- qiling/arch/cortex_m.py | 5 +++-- qiling/arch/mips.py | 16 +++++++--------- qiling/arch/riscv.py | 3 ++- qiling/arch/riscv64.py | 3 ++- qiling/arch/x86.py | 24 +++++++++--------------- 8 files changed, 32 insertions(+), 39 deletions(-) diff --git a/qiling/arch/arch.py b/qiling/arch/arch.py index 9b8df52a1..0473d87f3 100644 --- a/qiling/arch/arch.py +++ b/qiling/arch/arch.py @@ -4,7 +4,6 @@ # from abc import abstractmethod -from typing import Optional from unicorn import Uc from unicorn.unicorn import UcContext @@ -19,8 +18,6 @@ def __init__(self, ql: Qiling): self.ql = ql self.utils = QlArchUtils(ql) - self._asm: Optional[Ks] = None - @property @abstractmethod def uc(self) -> Uc: @@ -113,8 +110,10 @@ def disassembler(self) -> Cs: pass - def create_assembler(self) -> Ks: + @property + @abstractmethod + def assembler(self) -> Ks: """Get assembler instance bound to arch. """ - raise NotImplementedError(self.__class__.__name__) + pass diff --git a/qiling/arch/arm.py b/qiling/arch/arm.py index b5ca7ca4c..eb3ae528f 100644 --- a/qiling/arch/arm.py +++ b/qiling/arch/arm.py @@ -80,7 +80,8 @@ def disassembler(self) -> Cs: return Cs(CS_ARCH_ARM, mode) - def create_assembler(self) -> Ks: + @property + def assembler(self) -> Ks: # note: we do not cache the assembler instance; rather we refresh it # each time to make sure thumb mode is taken into account diff --git a/qiling/arch/arm64.py b/qiling/arch/arm64.py index 51b5bff58..057b19565 100644 --- a/qiling/arch/arm64.py +++ b/qiling/arch/arm64.py @@ -36,11 +36,9 @@ def uc(self) -> Uc: def disassembler(self) -> Cs: return Cs(CS_ARCH_ARM64, CS_MODE_ARM) - def create_assembler(self) -> Ks: - if self._asm is None: - self._asm = Ks(KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN) - - return self._asm + @cached_property + def assembler(self) -> Ks: + return Ks(KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN) def enable_vfp(self): self.ql.reg.cpacr_el1 = self.ql.reg.cpacr_el1 | 0x300000 diff --git a/qiling/arch/cortex_m.py b/qiling/arch/cortex_m.py index 02fe2809f..678455ff9 100644 --- a/qiling/arch/cortex_m.py +++ b/qiling/arch/cortex_m.py @@ -77,9 +77,10 @@ def uc(self): def disassembler(self) -> Cs: return Cs(CS_ARCH_ARM, CS_MODE_ARM + CS_MODE_MCLASS + CS_MODE_THUMB) - def create_assembler(self) -> Ks: + @cached_property + def assembler(self) -> Ks: return Ks(KS_ARCH_ARM, KS_MODE_ARM + KS_MODE_THUMB) - + def check_thumb(self): return UC_MODE_THUMB diff --git a/qiling/arch/mips.py b/qiling/arch/mips.py index cf1e499f0..3164d9ebb 100644 --- a/qiling/arch/mips.py +++ b/qiling/arch/mips.py @@ -47,13 +47,11 @@ def disassembler(self) -> Cs: return Cs(CS_ARCH_MIPS, CS_MODE_MIPS32 + endian) - def create_assembler(self) -> Ks: - if self._asm is None: - endian = { - QL_ENDIAN.EL : KS_MODE_LITTLE_ENDIAN, - QL_ENDIAN.EB : KS_MODE_BIG_ENDIAN - }[self.ql.archendian] - - self._asm = Ks(KS_ARCH_MIPS, KS_MODE_MIPS32 + endian) + @cached_property + def assembler(self) -> Ks: + endian = { + QL_ENDIAN.EL : KS_MODE_LITTLE_ENDIAN, + QL_ENDIAN.EB : KS_MODE_BIG_ENDIAN + }[self.ql.archendian] - return self._asm + return Ks(KS_ARCH_MIPS, KS_MODE_MIPS32 + endian) diff --git a/qiling/arch/riscv.py b/qiling/arch/riscv.py index 6f4a56c7a..41fd74b45 100644 --- a/qiling/arch/riscv.py +++ b/qiling/arch/riscv.py @@ -43,7 +43,8 @@ def disassembler(self) -> Cs: else: return Cs(CS_ARCH_RISCV, CS_MODE_RISCV32 + CS_MODE_RISCVC) - def create_assembler(self) -> Ks: + @cached_property + def assembler(self) -> Ks: raise QlErrorNotImplemented("Keystone does not yet support riscv") def enable_float(self): diff --git a/qiling/arch/riscv64.py b/qiling/arch/riscv64.py index 6554c3dc0..20be202be 100644 --- a/qiling/arch/riscv64.py +++ b/qiling/arch/riscv64.py @@ -32,5 +32,6 @@ def disassembler(self) -> Cs: else: return Cs(CS_ARCH_RISCV, CS_MODE_RISCV64 + CS_MODE_RISCVC) - def create_assembler(self) -> Ks: + @cached_property + def assembler(self) -> Ks: raise QlErrorNotImplemented("Keystone does not yet support riscv") diff --git a/qiling/arch/x86.py b/qiling/arch/x86.py index 30351c349..f55de696b 100644 --- a/qiling/arch/x86.py +++ b/qiling/arch/x86.py @@ -60,11 +60,9 @@ def uc(self) -> Uc: def disassembler(self) -> Cs: return Cs(CS_ARCH_X86, CS_MODE_16) - def create_assembler(self) -> Ks: - if not self._asm: - self._asm = Ks(KS_ARCH_X86, KS_MODE_16) - - return self._asm + @cached_property + def assembler(self) -> Ks: + return Ks(KS_ARCH_X86, KS_MODE_16) class QlArchX86(QlArchIntel): def __init__(self, ql: Qiling): @@ -93,11 +91,9 @@ def uc(self) -> Uc: def disassembler(self) -> Cs: return Cs(CS_ARCH_X86, CS_MODE_32) - def create_assembler(self) -> Ks: - if not self._asm: - self._asm = Ks(KS_ARCH_X86, KS_MODE_32) - - return self._asm + @cached_property + def assembler(self) -> Ks: + return Ks(KS_ARCH_X86, KS_MODE_32) class QlArchX8664(QlArchIntel): def __init__(self, ql: Qiling): @@ -131,11 +127,9 @@ def uc(self) -> Uc: def disassembler(self) -> Cs: return Cs(CS_ARCH_X86, CS_MODE_64) - def create_assembler(self) -> Ks: - if not self._asm: - self._asm = Ks(KS_ARCH_X86, KS_MODE_64) - - return self._asm + @cached_property + def assembler(self) -> Ks: + return Ks(KS_ARCH_X86, KS_MODE_64) class GDTManager: From 0709af45d1255b7e71dce35a3cf999b4292be30a Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 14 Jan 2022 14:10:59 +0200 Subject: [PATCH 012/406] Remove assembler properties from core --- qiling/core.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/qiling/core.py b/qiling/core.py index 947e8191f..28f622f89 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -841,14 +841,6 @@ def stack_read(self, offset): def stack_write(self, offset, data): return self.arch.stack_write(offset, data) - # Assembler/Diassembler API - @property - def assembler(self): - return self.create_assembler() - - - def create_assembler(self): - return self.arch.create_assembler() # stop emulation def emu_stop(self): From 927f8cc0c238443949b1fc5e4a3c7ba8baa6d2ed Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 14 Jan 2022 14:12:02 +0200 Subject: [PATCH 013/406] Adjust all assembler usages --- qiling/extensions/idaplugin/qilingida.py | 2 +- qiling/loader/elf.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qiling/extensions/idaplugin/qilingida.py b/qiling/extensions/idaplugin/qilingida.py index ad6f3709d..0db8e43d2 100644 --- a/qiling/extensions/idaplugin/qilingida.py +++ b/qiling/extensions/idaplugin/qilingida.py @@ -1693,7 +1693,7 @@ def _search_path(self): # assembler implmentation with keystone. def _initialize_keystone(self): if self.ks is None: - self.ks = self.deflatqlemu.ql.create_assembler() + self.ks = self.deflatqlemu.ql.arch.assembler def _asm(self, *args, **kwargs): diff --git a/qiling/loader/elf.py b/qiling/loader/elf.py index 80ab28f01..7844e34ce 100644 --- a/qiling/loader/elf.py +++ b/qiling/loader/elf.py @@ -413,7 +413,7 @@ def __push_str(top: int, s: str) -> int: # each syscall should be 1KiB away self.ql.mem.map(_vsyscall_addr, _vsyscall_size, info="[vsyscall]") self.ql.mem.write(_vsyscall_addr, _vsyscall_size * b'\xcc') - assembler = self.ql.create_assembler() + assembler = self.ql.arch.assembler def __assemble(asm: str) -> bytes: bs, _ = assembler.asm(asm) From 5da41598c11c77b701657f4b837467b7fe6a4ea7 Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 14 Jan 2022 15:11:53 +0200 Subject: [PATCH 014/406] Make QlRegisterManager independent of Qiling --- qiling/arch/register.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/qiling/arch/register.py b/qiling/arch/register.py index 55ec23b4e..b2013ea62 100644 --- a/qiling/arch/register.py +++ b/qiling/arch/register.py @@ -5,7 +5,7 @@ from typing import Any, Mapping, MutableMapping, Union -from qiling import Qiling +from unicorn import Uc class QlRegisterManager: """This class exposes the ql.reg features that allows you to directly access @@ -15,13 +15,13 @@ class QlRegisterManager: arch directories and are mapped to Unicorn Engine's definitions """ - def __init__(self, ql: Qiling): + def __init__(self, uc: Uc): # this funny way of initialization is used to avoid calling self setattr and # getattr upon init. if it did, it would go into an endless recursion self.register_mapping: MutableMapping[str, int] super().__setattr__('register_mapping', {}) - self.ql = ql + self.uc = uc self.uc_pc = 0 self.uc_sp = 0 @@ -29,7 +29,7 @@ def __getattr__(self, name: str) -> Any: name = name.lower() if name in self.register_mapping: - return self.ql.uc.reg_read(self.register_mapping[name]) + return self.uc.reg_read(self.register_mapping[name]) else: return super().__getattribute__(name) @@ -39,7 +39,7 @@ def __setattr__(self, name: str, value: Any): name = name.lower() if name in self.register_mapping: - self.ql.uc.reg_write(self.register_mapping[name], value) + self.uc.reg_write(self.register_mapping[name], value) else: super().__setattr__(name, value) @@ -60,7 +60,7 @@ def read(self, register: Union[str, int]): if type(register) is str: register = self.register_mapping[register.lower()] - return self.ql.uc.reg_read(register) + return self.uc.reg_read(register) def write(self, register: Union[str, int], value: int) -> None: @@ -70,7 +70,7 @@ def write(self, register: Union[str, int], value: int) -> None: if type(register) is str: register = self.register_mapping[register.lower()] - return self.ql.uc.reg_write(register, value) + return self.uc.reg_write(register, value) def msr(self, msr: int, value: int = None): @@ -79,9 +79,9 @@ def msr(self, msr: int, value: int = None): """ if value is None: - return self.ql.uc.msr_read(msr) + return self.uc.msr_read(msr) - self.ql.uc.msr_write(msr, value) + self.uc.msr_write(msr, value) def save(self) -> MutableMapping[str, Any]: @@ -99,6 +99,7 @@ def restore(self, context: MutableMapping[str, Any] = {}) -> None: self.write(reg, val) + # FIXME: this no longer works # TODO: This needs to be implemented for all archs def bit(self, reg: Union[str, int]) -> int: """Get register size in bits. @@ -126,7 +127,7 @@ def arch_pc(self) -> int: """Get the value of the architectural program counter register. """ - return self.ql.uc.reg_read(self.uc_pc) + return self.uc.reg_read(self.uc_pc) @arch_pc.setter @@ -134,7 +135,7 @@ def arch_pc(self, value: int) -> None: """Set the value of the architectural program counter register. """ - return self.ql.uc.reg_write(self.uc_pc, value) + return self.uc.reg_write(self.uc_pc, value) @property def arch_pc_name(self) -> str: @@ -148,7 +149,7 @@ def arch_sp(self) -> int: """Get the value of the architectural stack pointer register. """ - return self.ql.uc.reg_read(self.uc_sp) + return self.uc.reg_read(self.uc_sp) @arch_sp.setter @@ -156,4 +157,4 @@ def arch_sp(self, value: int) -> None: """Set the value of the architectural stack pointer register. """ - return self.ql.uc.reg_write(self.uc_sp, value) + return self.uc.reg_write(self.uc_sp, value) From 0a3bd04ec852b03b7bb998453abef373137ede31 Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 14 Jan 2022 15:22:16 +0200 Subject: [PATCH 015/406] Remove reg from core and make it a cached property of QlArch --- qiling/arch/arch.py | 6 ++++++ qiling/core.py | 8 -------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/qiling/arch/arch.py b/qiling/arch/arch.py index 0473d87f3..b12a6fbb7 100644 --- a/qiling/arch/arch.py +++ b/qiling/arch/arch.py @@ -4,6 +4,7 @@ # from abc import abstractmethod +from functools import cached_property from unicorn import Uc from unicorn.unicorn import UcContext @@ -11,6 +12,7 @@ from keystone import Ks from qiling import Qiling +from .register import QlRegisterManager from .utils import QlArchUtils class QlArch: @@ -26,6 +28,10 @@ def uc(self) -> Uc: pass + @cached_property + def regs(self) -> QlRegisterManager: + return QlRegisterManager(self.uc) + def stack_push(self, value: int) -> int: """Push a value onto the architectural stack. diff --git a/qiling/core.py b/qiling/core.py index 28f622f89..1f3e0dac9 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -235,14 +235,6 @@ def mem(self) -> "QlMemoryManager": """ return self._mem - @property - def reg(self) -> "QlRegisterManager": - """ Qiling register manager. - - Example: ql.reg.eax = 1 - """ - return self._reg - @property def hw(self) -> "QlHwManager": """ Qiling hardware manager. From 3cc3d395a7c0a7e8d1dc4118e93c34629099eed2 Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 14 Jan 2022 15:33:54 +0200 Subject: [PATCH 016/406] Adjust all reg usages --- examples/crackme_x86_linux.py | 2 +- examples/crackme_x86_windows_auto.py | 4 +- examples/crackme_x86_windows_setcallback.py | 4 +- examples/crackme_x86_windows_unpatch.py | 4 +- examples/doogie_8086_crack.py | 10 +-- .../extensions/idaplugin/custom_script.py | 4 +- .../tenda_ac15/saver_tendaac15_httpd.py | 2 +- examples/hello_arm_uboot.py | 22 +++--- examples/hello_linuxx8664_intercept.py | 6 +- examples/hello_x8664_linux_disasm.py | 2 +- examples/mcu/gd32vf103_blink.py | 2 +- examples/mcu/stm32f407_mnist_oled.py | 4 +- examples/mcu/stm32f411_i2c_lcd.py | 2 +- examples/petya_8086_crack.py | 14 ++-- examples/sality.py | 2 +- examples/windows_trace.py | 16 ++--- qiling/arch/arch.py | 16 ++--- qiling/arch/arm.py | 18 ++--- qiling/arch/arm64.py | 8 +-- qiling/arch/cortex_m.py | 38 +++++------ qiling/arch/mips.py | 6 +- qiling/arch/register.py | 2 +- qiling/arch/riscv.py | 16 ++--- qiling/arch/utils.py | 4 +- qiling/arch/x86.py | 48 ++++++------- qiling/cc/__init__.py | 10 +-- qiling/cc/intel.py | 2 +- qiling/cc/mips.py | 2 +- qiling/core.py | 16 ++--- qiling/core_hooks.py | 4 +- qiling/debugger/gdb/gdb.py | 68 +++++++++---------- qiling/debugger/gdb/utils.py | 2 +- qiling/debugger/qdb/frontend.py | 12 ++-- qiling/debugger/qdb/qdb.py | 8 +-- qiling/debugger/qdb/utils.py | 18 ++--- qiling/extensions/idaplugin/qilingida.py | 54 +++++++-------- qiling/extensions/trace.py | 2 +- qiling/hw/peripheral.py | 4 +- qiling/loader/blob.py | 2 +- qiling/loader/dos.py | 14 ++-- qiling/loader/elf.py | 12 ++-- qiling/loader/macho.py | 6 +- qiling/loader/mcu.py | 2 +- qiling/loader/pe.py | 26 +++---- qiling/loader/pe_uefi.py | 4 +- qiling/os/dos/dos.py | 6 +- qiling/os/dos/interrupts/int10.py | 32 ++++----- qiling/os/dos/interrupts/int13.py | 60 ++++++++-------- qiling/os/dos/interrupts/int15.py | 14 ++-- qiling/os/dos/interrupts/int16.py | 14 ++-- qiling/os/dos/interrupts/int19.py | 6 +- qiling/os/dos/interrupts/int1a.py | 26 +++---- qiling/os/dos/interrupts/int20.py | 2 +- qiling/os/dos/interrupts/int21.py | 40 +++++------ qiling/os/dos/utils.py | 2 +- qiling/os/fcall.py | 4 +- qiling/os/freebsd/syscall.py | 4 +- qiling/os/linux/function_hook.py | 44 ++++++------ qiling/os/linux/syscall.py | 12 ++-- qiling/os/linux/thread.py | 60 ++++++++-------- qiling/os/macos/events/macos.py | 14 ++-- qiling/os/macos/macos.py | 42 ++++++------ qiling/os/macos/syscall.py | 4 +- qiling/os/macos/utils.py | 28 ++++---- qiling/os/os.py | 8 +-- qiling/os/posix/posix.py | 16 ++--- qiling/os/posix/syscall/prctl.py | 8 +-- qiling/os/posix/syscall/sched.py | 10 +-- qiling/os/posix/syscall/unistd.py | 4 +- qiling/os/uefi/fncc.py | 2 +- qiling/os/uefi/smm.py | 6 +- qiling/os/uefi/uefi.py | 6 +- qiling/os/uefi/utils.py | 10 +-- .../windows/dlls/kernel32/errhandlingapi.py | 4 +- qiling/os/windows/dlls/ntoskrnl.py | 4 +- qiling/os/windows/fiber.py | 6 +- qiling/os/windows/thread.py | 8 +-- qiling/utils.py | 9 ++- tests/test_blob.py | 24 +++---- tests/test_edl.py | 16 ++--- tests/test_elf.py | 24 +++---- tests/test_mcu.py | 4 +- tests/test_pe.py | 8 +-- tests/test_pe_sys.py | 6 +- 84 files changed, 559 insertions(+), 562 deletions(-) diff --git a/examples/crackme_x86_linux.py b/examples/crackme_x86_linux.py index 37a057c62..e25c87fbf 100644 --- a/examples/crackme_x86_linux.py +++ b/examples/crackme_x86_linux.py @@ -32,7 +32,7 @@ def __init__(self, invalid: bytes): # # since the emulation halted upon entering 'main', its return address is there on # the stack. we use it to limit the emulation till function returns - self.replay_starts = self.ql.reg.arch_pc + self.replay_starts = self.ql.arch.regs.arch_pc self.replay_ends = self.ql.stack_read(0) # instead of restarting the whole program every time a new flag character is guessed, diff --git a/examples/crackme_x86_windows_auto.py b/examples/crackme_x86_windows_auto.py index be5f517b8..66b4ee882 100644 --- a/examples/crackme_x86_windows_auto.py +++ b/examples/crackme_x86_windows_auto.py @@ -11,7 +11,7 @@ def force_call_dialog_func(ql: Qiling): # get DialogFunc address - lpDialogFunc = ql.unpack32(ql.mem.read(ql.reg.esp - 0x8, 4)) + lpDialogFunc = ql.unpack32(ql.mem.read(ql.arch.regs.esp - 0x8, 4)) # setup stack for DialogFunc ql.stack_push(0) ql.stack_push(1001) @@ -19,7 +19,7 @@ def force_call_dialog_func(ql: Qiling): ql.stack_push(0) ql.stack_push(0x0401018) # force EIP to DialogFunc - ql.reg.eip = lpDialogFunc + ql.arch.regs.eip = lpDialogFunc def our_sandbox(path, rootfs): ql = Qiling(path, rootfs, stdin=pipe.SimpleInStream(sys.stdin.fileno())) diff --git a/examples/crackme_x86_windows_setcallback.py b/examples/crackme_x86_windows_setcallback.py index 06856c3e5..f873d7d83 100644 --- a/examples/crackme_x86_windows_setcallback.py +++ b/examples/crackme_x86_windows_setcallback.py @@ -10,7 +10,7 @@ def force_call_dialog_func(ql: Qiling): # get DialogFunc address - lpDialogFunc = ql.unpack32(ql.mem.read(ql.reg.esp - 0x8, 4)) + lpDialogFunc = ql.unpack32(ql.mem.read(ql.arch.regs.esp - 0x8, 4)) # setup stack for DialogFunc ql.stack_push(0) ql.stack_push(1001) @@ -18,7 +18,7 @@ def force_call_dialog_func(ql: Qiling): ql.stack_push(0) ql.stack_push(0x0401018) # force EIP to DialogFunc - ql.reg.eip = lpDialogFunc + ql.arch.regs.eip = lpDialogFunc def my_sandbox(path, rootfs): ql = Qiling(path, rootfs) diff --git a/examples/crackme_x86_windows_unpatch.py b/examples/crackme_x86_windows_unpatch.py index 30bbee29b..1e43e50f6 100644 --- a/examples/crackme_x86_windows_unpatch.py +++ b/examples/crackme_x86_windows_unpatch.py @@ -10,7 +10,7 @@ def force_call_dialog_func(ql: Qiling): # get DialogFunc address - lpDialogFunc = ql.unpack32(ql.mem.read(ql.reg.esp - 0x8, 4)) + lpDialogFunc = ql.unpack32(ql.mem.read(ql.arch.regs.esp - 0x8, 4)) # setup stack for DialogFunc ql.stack_push(0) ql.stack_push(1001) @@ -18,7 +18,7 @@ def force_call_dialog_func(ql: Qiling): ql.stack_push(0) ql.stack_push(0x0401018) # force EIP to DialogFunc - ql.reg.eip = lpDialogFunc + ql.arch.regs.eip = lpDialogFunc def our_sandbox(path, rootfs): ql = Qiling(path, rootfs) diff --git a/examples/doogie_8086_crack.py b/examples/doogie_8086_crack.py index c76bd6f2d..3e501104f 100644 --- a/examples/doogie_8086_crack.py +++ b/examples/doogie_8086_crack.py @@ -119,7 +119,7 @@ def echo_key(ql: Qiling, key): def show_once(ql: Qiling, key): klen = len(key) - ql.reg.ax = klen + ql.arch.regs.ax = klen ql.mem.write(0x87F4, key) # Partial exectution to skip input reading ql.run(begin=0x801B, end=0x803d) @@ -172,10 +172,10 @@ def read_until_zero(ql: Qiling, addr): def set_required_datetime(ql: Qiling): ql.log.info("Setting Feburary 06, 1990") - ql.reg.ch = BIN2BCD(19) - ql.reg.cl = BIN2BCD(1990%100) - ql.reg.dh = BIN2BCD(2) - ql.reg.dl = BIN2BCD(6) + ql.arch.regs.ch = BIN2BCD(19) + ql.arch.regs.cl = BIN2BCD(1990%100) + ql.arch.regs.dh = BIN2BCD(2) + ql.arch.regs.dl = BIN2BCD(6) def stop(ql, addr, data): ql.emu_stop() diff --git a/examples/extensions/idaplugin/custom_script.py b/examples/extensions/idaplugin/custom_script.py index c58cfa5e2..78ffca21d 100644 --- a/examples/extensions/idaplugin/custom_script.py +++ b/examples/extensions/idaplugin/custom_script.py @@ -5,10 +5,10 @@ def __init__(self): pass def _show_context(self, ql:Qiling): - registers = [ k for k in ql.reg.register_mapping.keys() if type(k) is str ] + registers = [ k for k in ql.arch.regs.register_mapping.keys() if type(k) is str ] for idx in range(0, len(registers), 3): regs = registers[idx:idx+3] - s = "\t".join(map(lambda v: f"{v:4}: {ql.reg.__getattr__(v):016x}", regs)) + s = "\t".join(map(lambda v: f"{v:4}: {ql.arch.regs.__getattr__(v):016x}", regs)) ql.log.info(s) def custom_prepare(self, ql:Qiling): diff --git a/examples/fuzzing/tenda_ac15/saver_tendaac15_httpd.py b/examples/fuzzing/tenda_ac15/saver_tendaac15_httpd.py index ec46c6470..ca018af06 100644 --- a/examples/fuzzing/tenda_ac15/saver_tendaac15_httpd.py +++ b/examples/fuzzing/tenda_ac15/saver_tendaac15_httpd.py @@ -59,7 +59,7 @@ def patcher(ql): def check_pc(ql): print("=" * 50) - print("Hit fuzz point, stop at PC = 0x%x" % ql.reg.arch_pc) + print("Hit fuzz point, stop at PC = 0x%x" % ql.arch.regs.arch_pc) print("=" * 50) ql.emu_stop() diff --git a/examples/hello_arm_uboot.py b/examples/hello_arm_uboot.py index 691318df3..2d0224251 100644 --- a/examples/hello_arm_uboot.py +++ b/examples/hello_arm_uboot.py @@ -19,11 +19,11 @@ def my_getenv(ql, *args, **kwargs): value_addr = ql.os.heap.alloc(len(value)) ql.mem.write(value_addr, value) - ql.reg.r0 = value_addr - ql.reg.arch_pc = ql.reg.lr + ql.arch.regs.r0 = value_addr + ql.arch.regs.arch_pc = ql.arch.regs.lr def get_password(ql, *args, **kwargs): - password_raw = ql.mem.read(ql.reg.r0, ql.reg.r2) + password_raw = ql.mem.read(ql.arch.regs.r0, ql.arch.regs.r2) password = '' for item in password_raw: @@ -36,21 +36,21 @@ def get_password(ql, *args, **kwargs): def partial_run_init(ql): # argv prepare - ql.reg.arch_sp -= 0x30 - arg0_ptr = ql.reg.arch_sp + ql.arch.regs.arch_sp -= 0x30 + arg0_ptr = ql.arch.regs.arch_sp ql.mem.write(arg0_ptr, b"kaimendaji") - ql.reg.arch_sp -= 0x10 - arg1_ptr = ql.reg.arch_sp + ql.arch.regs.arch_sp -= 0x10 + arg1_ptr = ql.arch.regs.arch_sp ql.mem.write(arg1_ptr, b"000000") # arbitrary password - ql.reg.arch_sp -= 0x20 - argv_ptr = ql.reg.arch_sp + ql.arch.regs.arch_sp -= 0x20 + argv_ptr = ql.arch.regs.arch_sp ql.mem.write(argv_ptr, ql.pack(arg0_ptr)) ql.mem.write(argv_ptr + ql.pointersize, ql.pack(arg1_ptr)) - ql.reg.r2 = 2 - ql.reg.r3 = argv_ptr + ql.arch.regs.r2 = 2 + ql.arch.regs.r3 = argv_ptr with open("../examples/rootfs/blob/u-boot.bin.img", "rb") as f: diff --git a/examples/hello_linuxx8664_intercept.py b/examples/hello_linuxx8664_intercept.py index 48df92d77..0af739e31 100644 --- a/examples/hello_linuxx8664_intercept.py +++ b/examples/hello_linuxx8664_intercept.py @@ -12,12 +12,12 @@ def write_onenter(ql: Qiling, arg1, arg2, arg3, *args): print("enter write syscall!") - ql.reg.rsi = arg2 + 1 - ql.reg.rdx = arg3 - 1 + ql.arch.regs.rsi = arg2 + 1 + ql.arch.regs.rdx = arg3 - 1 def write_onexit(ql: Qiling, arg1, arg2, arg3, *args): print("exit write syscall!") - ql.reg.rax = arg3 + 1 + ql.arch.regs.rax = arg3 + 1 if __name__ == "__main__": ql = Qiling(["rootfs/x8664_linux/bin/x8664_hello"], "rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG) diff --git a/examples/hello_x8664_linux_disasm.py b/examples/hello_x8664_linux_disasm.py index c2173ed0e..adfc7234c 100644 --- a/examples/hello_x8664_linux_disasm.py +++ b/examples/hello_x8664_linux_disasm.py @@ -29,7 +29,7 @@ def trace(ql: Qiling, address: int, size: int, md: Cs): opcode = ''.join(f'{b:02x}' for b in insn.bytes) # BUG: insn.regs_read doesn't work well, so we use insn.regs_access()[0] instead - reads = (f'{md.reg_name(reg)} = {ql.reg.read(reg):#x}' for reg in insn.regs_access()[0]) + reads = (f'{md.reg_name(reg)} = {ql.arch.regs.read(reg):#x}' for reg in insn.regs_access()[0]) trace_line = f'{insn.address:0{nibbles}x} | {opcode:20s} {insn.mnemonic:10} {insn.op_str:35s} | {", ".join(reads)}' # emit trace line in dark gray so it would be easier to tell trace info from other log entries diff --git a/examples/mcu/gd32vf103_blink.py b/examples/mcu/gd32vf103_blink.py index 8160167ab..ff2e49dc3 100644 --- a/examples/mcu/gd32vf103_blink.py +++ b/examples/mcu/gd32vf103_blink.py @@ -21,7 +21,7 @@ delay_cycles_end = 0x800018c def skip_delay(ql): - ql.reg.pc = delay_cycles_end + ql.arch.regs.pc = delay_cycles_end ql.hook_address(skip_delay, delay_cycles_begin) ql.hw.gpioc.hook_set(13, lambda : print('Set PC13')) diff --git a/examples/mcu/stm32f407_mnist_oled.py b/examples/mcu/stm32f407_mnist_oled.py index 365a0005c..16338b77d 100644 --- a/examples/mcu/stm32f407_mnist_oled.py +++ b/examples/mcu/stm32f407_mnist_oled.py @@ -27,8 +27,8 @@ ## a temporary method def hook_smlabb(ql): - ql.reg.r3 = ql.reg.r2 + ql.reg.r1 * ql.reg.r3 - ql.reg.pc = (ql.reg.pc + 4) | 1 + ql.arch.regs.r3 = ql.arch.regs.r2 + ql.arch.regs.r1 * ql.arch.regs.r3 + ql.arch.regs.pc = (ql.arch.regs.pc + 4) | 1 ql.hook_address(hook_smlabb, 0x8007a12) ql.hook_address(hook_smlabb, 0x8007b60) diff --git a/examples/mcu/stm32f411_i2c_lcd.py b/examples/mcu/stm32f411_i2c_lcd.py index 5411874c6..3e09f11f9 100644 --- a/examples/mcu/stm32f411_i2c_lcd.py +++ b/examples/mcu/stm32f411_i2c_lcd.py @@ -42,7 +42,7 @@ def create(path, lcd): delay_start = 0x8002936 delay_end = 0x8002955 def skip_delay(ql): - ql.reg.pc = delay_end + ql.arch.regs.pc = delay_end ql.hook_address(skip_delay, delay_start) ql.run(count=100000) diff --git a/examples/petya_8086_crack.py b/examples/petya_8086_crack.py index 1735090a5..752202690 100644 --- a/examples/petya_8086_crack.py +++ b/examples/petya_8086_crack.py @@ -28,7 +28,7 @@ def one_round(ql: Qiling, key: bytes, key_address): gkeys = generate_key(key) ql.mem.write(key_address, gkeys) ql.run(begin=verfication_start_ip, end=verfication_start_ip+6) - lba37 = ql.mem.read(ql.reg.sp + 0x220, 0x200) + lba37 = ql.mem.read(ql.arch.regs.sp + 0x220, 0x200) for ch in lba37: if ch != 0x37: return False @@ -62,12 +62,12 @@ def second_stage(ql: Qiling): #nonce = get_nonce(disk) verfication_data = disk.read_sectors(0x37, 1) nonce_data = disk.read_sectors(0x36, 1) - ql.reg.sp -= 0x200 - verification_data_address = ql.reg.sp - ql.reg.sp -= 0x200 - nonce_address = ql.reg.sp + 0x21 - ql.reg.sp -= 0x20 - key_address = ql.reg.sp + ql.arch.regs.sp -= 0x200 + verification_data_address = ql.arch.regs.sp + ql.arch.regs.sp -= 0x200 + nonce_address = ql.arch.regs.sp + 0x21 + ql.arch.regs.sp -= 0x20 + key_address = ql.arch.regs.sp ql.mem.write(verification_data_address, verfication_data) ql.mem.write(nonce_address - 0x21, nonce_data) ql.arch.stack_push(0x200) diff --git a/examples/sality.py b/examples/sality.py index 330decaaf..4a34aa755 100644 --- a/examples/sality.py +++ b/examples/sality.py @@ -170,7 +170,7 @@ def hook_StartServiceA(ql: Qiling, address: int, params): def hook_stop_address(ql): - print(" >>>> Stop address: 0x%08x" % ql.reg.arch_pc) + print(" >>>> Stop address: 0x%08x" % ql.arch.regs.arch_pc) ql.emu_stop() diff --git a/examples/windows_trace.py b/examples/windows_trace.py index c6b40ba13..e218b8331 100644 --- a/examples/windows_trace.py +++ b/examples/windows_trace.py @@ -38,14 +38,14 @@ class colors: def dump_regs(ql: Qiling): regs = { - 'eax': ql.reg.eax, - 'ebx': ql.reg.ebx, - 'ecx': ql.reg.ecx, - 'edx': ql.reg.edx, - 'edi': ql.reg.edi, - 'esi': ql.reg.esi, - 'ebp': ql.reg.ebp, - 'esp': ql.reg.esp + 'eax': ql.arch.regs.eax, + 'ebx': ql.arch.regs.ebx, + 'ecx': ql.arch.regs.ecx, + 'edx': ql.arch.regs.edx, + 'edi': ql.arch.regs.edi, + 'esi': ql.arch.regs.esi, + 'ebp': ql.arch.regs.ebp, + 'esp': ql.arch.regs.esp } if not hasattr(dump_regs, 'regs'): diff --git a/qiling/arch/arch.py b/qiling/arch/arch.py index b12a6fbb7..04f53ec3a 100644 --- a/qiling/arch/arch.py +++ b/qiling/arch/arch.py @@ -42,10 +42,10 @@ def stack_push(self, value: int) -> int: Returns: the top of stack after pushing the value """ - self.ql.reg.arch_sp -= self.ql.pointersize - self.ql.mem.write(self.ql.reg.arch_sp, self.ql.pack(value)) + self.regs.arch_sp -= self.ql.pointersize + self.ql.mem.write(self.regs.arch_sp, self.ql.pack(value)) - return self.ql.reg.arch_sp + return self.regs.arch_sp def stack_pop(self) -> int: @@ -54,8 +54,8 @@ def stack_pop(self) -> int: Returns: the value at the top of stack """ - data = self.ql.unpack(self.ql.mem.read(self.ql.reg.arch_sp, self.ql.pointersize)) - self.ql.reg.arch_sp += self.ql.pointersize + data = self.ql.unpack(self.ql.mem.read(self.regs.arch_sp, self.ql.pointersize)) + self.regs.arch_sp += self.ql.pointersize return data @@ -74,7 +74,7 @@ def stack_read(self, offset: int) -> int: Returns: the value at the specified address """ - return self.ql.unpack(self.ql.mem.read(self.ql.reg.arch_sp + offset, self.ql.pointersize)) + return self.ql.unpack(self.ql.mem.read(self.regs.arch_sp + offset, self.ql.pointersize)) def stack_write(self, offset: int, value: int) -> None: @@ -89,12 +89,12 @@ def stack_write(self, offset: int, value: int) -> None: a 0 value means overwriting the value at the top of the stack """ - self.ql.mem.write(self.ql.reg.arch_sp + offset, self.ql.pack(value)) + self.ql.mem.write(self.regs.arch_sp + offset, self.ql.pack(value)) # get PC def get_pc(self) -> int: - return self.ql.reg.arch_pc + return self.regs.arch_pc # Unicorn's CPU state save diff --git a/qiling/arch/arm.py b/qiling/arch/arm.py index eb3ae528f..6ed7effa1 100644 --- a/qiling/arch/arm.py +++ b/qiling/arch/arm.py @@ -24,10 +24,10 @@ def __init__(self, ql: Qiling): ) for reg_maper in reg_maps: - self.ql.reg.expand_mapping(reg_maper) + self.ql.arch.regs.expand_mapping(reg_maper) - self.ql.reg.register_sp(reg_map["sp"]) - self.ql.reg.register_pc(reg_map["pc"]) + self.ql.arch.regs.register_sp(reg_map["sp"]) + self.ql.arch.regs.register_pc(reg_map["pc"]) self.arm_get_tls_addr = 0xFFFF0FE0 @@ -52,7 +52,7 @@ def uc(self) -> Uc: def get_pc(self) -> int: append = 1 if self.check_thumb() == UC_MODE_THUMB else 0 - return self.ql.reg.pc + append + return self.ql.arch.regs.pc + append def __is_thumb(self) -> bool: cpsr_v = { @@ -60,7 +60,7 @@ def __is_thumb(self) -> bool: QL_ENDIAN.EB : 0b100000 # FIXME: should be: 0b000000 }[self.ql.archendian] - return bool(self.ql.reg.cpsr & cpsr_v) + return bool(self.ql.arch.regs.cpsr & cpsr_v) @property def disassembler(self) -> Cs: @@ -98,13 +98,13 @@ def assembler(self) -> Ks: return Ks(KS_ARCH_ARM, mode) def enable_vfp(self) -> None: - self.ql.reg.c1_c0_2 = self.ql.reg.c1_c0_2 | (0xf << 20) + self.ql.arch.regs.c1_c0_2 = self.ql.arch.regs.c1_c0_2 | (0xf << 20) if self.ql.archendian == QL_ENDIAN.EB: - self.ql.reg.fpexc = 0x40000000 - #self.ql.reg.fpexc = 0x00000040 + self.ql.arch.regs.fpexc = 0x40000000 + #self.ql.arch.regs.fpexc = 0x00000040 else: - self.ql.reg.fpexc = 0x40000000 + self.ql.arch.regs.fpexc = 0x40000000 def check_thumb(self): return UC_MODE_THUMB if self.__is_thumb() else UC_MODE_ARM diff --git a/qiling/arch/arm64.py b/qiling/arch/arm64.py index 057b19565..b2a1025a9 100644 --- a/qiling/arch/arm64.py +++ b/qiling/arch/arm64.py @@ -23,10 +23,10 @@ def __init__(self, ql: Qiling): ) for reg_maper in reg_maps: - self.ql.reg.expand_mapping(reg_maper) + self.ql.arch.regs.expand_mapping(reg_maper) - self.ql.reg.register_sp(reg_map["sp"]) - self.ql.reg.register_pc(reg_map["pc"]) + self.ql.arch.regs.register_sp(reg_map["sp"]) + self.ql.arch.regs.register_pc(reg_map["pc"]) @cached_property def uc(self) -> Uc: @@ -41,4 +41,4 @@ def assembler(self) -> Ks: return Ks(KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN) def enable_vfp(self): - self.ql.reg.cpacr_el1 = self.ql.reg.cpacr_el1 | 0x300000 + self.ql.arch.regs.cpacr_el1 = self.ql.arch.regs.cpacr_el1 | 0x300000 diff --git a/qiling/arch/cortex_m.py b/qiling/arch/cortex_m.py index 678455ff9..5a6b00043 100644 --- a/qiling/arch/cortex_m.py +++ b/qiling/arch/cortex_m.py @@ -23,7 +23,7 @@ def __init__(self, ql): def __enter__(self): for reg in self.reg_context: - val = self.ql.reg.read(reg) + val = self.ql.arch.regs.read(reg) self.ql.arch.stack_push(val) if self.ql.verbose >= QL_VERBOSE.DISASM: @@ -38,22 +38,22 @@ def __exit__(self, *exc): else: # Exit handler mode - self.ql.reg.write('ipsr', 0) + self.ql.arch.regs.write('ipsr', 0) # switch the stack accroding exc_return - old_ctrl = self.ql.reg.read('control') + old_ctrl = self.ql.arch.regs.read('control') if retval & EXC_RETURN.RETURN_SP: - self.ql.reg.write('control', old_ctrl | CONTROL.SPSEL) + self.ql.arch.regs.write('control', old_ctrl | CONTROL.SPSEL) else: - self.ql.reg.write('control', old_ctrl & ~CONTROL.SPSEL) + self.ql.arch.regs.write('control', old_ctrl & ~CONTROL.SPSEL) # Restore stack for reg in reversed(self.reg_context): val = self.ql.arch.stack_pop() if reg == 'xpsr': - self.ql.reg.write('XPSR_NZCVQG', val) + self.ql.arch.regs.write('XPSR_NZCVQG', val) else: - self.ql.reg.write(reg, val) + self.ql.arch.regs.write(reg, val) if self.ql.verbose >= QL_VERBOSE.DISASM: self.ql.log.info('Exit from interrupt') @@ -67,7 +67,7 @@ def __init__(self, ql): ) for reg_maper in reg_maps: - self.ql.reg.expand_mapping(reg_maper) + self.ql.arch.regs.expand_mapping(reg_maper) @cached_property def uc(self): @@ -106,15 +106,15 @@ def run(self, count=-1, end=None): count -= 1 def is_handler_mode(self): - return self.ql.reg.read('ipsr') > 1 + return self.ql.arch.regs.read('ipsr') > 1 def using_psp(self): - return not self.is_handler_mode() and (self.ql.reg.read('control') & CONTROL.SPSEL) > 0 + return not self.is_handler_mode() and (self.ql.arch.regs.read('control') & CONTROL.SPSEL) > 0 def init_context(self): - self.ql.reg.write('lr', 0xffffffff) - self.ql.reg.write('msp', self.ql.mem.read_ptr(0x0)) - self.ql.reg.write('pc' , self.ql.mem.read_ptr(0x4)) + self.ql.arch.regs.write('lr', 0xffffffff) + self.ql.arch.regs.write('msp', self.ql.mem.read_ptr(0x0)) + self.ql.arch.regs.write('pc' , self.ql.mem.read_ptr(0x4)) def soft_interrupt_handler(self, ql, intno): forward_mapper = { @@ -148,14 +148,14 @@ def soft_interrupt_handler(self, ql, intno): raise QlErrorNotImplemented(f'Unhandled interrupt number ({intno})') def hard_interrupt_handler(self, ql, intno): - basepri = self.ql.reg.read('basepri') & 0xf0 + basepri = self.ql.arch.regs.read('basepri') & 0xf0 if basepri and basepri <= ql.hw.nvic.get_priority(intno): return - if intno > IRQ.HARD_FAULT and (ql.reg.read('primask') & 0x1): + if intno > IRQ.HARD_FAULT and (ql.arch.regs.read('primask') & 0x1): return - if intno != IRQ.NMI and (ql.reg.read('faultmask') & 0x1): + if intno != IRQ.NMI and (ql.arch.regs.read('faultmask') & 0x1): return if ql.verbose >= QL_VERBOSE.DISASM: @@ -168,8 +168,8 @@ def hard_interrupt_handler(self, ql, intno): entry = ql.mem.read_ptr(offset) exc_return = 0xFFFFFFFD if self.ql.arch.using_psp() else 0xFFFFFFF9 - self.ql.reg.write('ipsr', isr) - self.ql.reg.write('pc', entry) - self.ql.reg.write('lr', exc_return) + self.ql.arch.regs.write('ipsr', isr) + self.ql.arch.regs.write('pc', entry) + self.ql.arch.regs.write('lr', exc_return) self.ql.emu_start(self.ql.arch.get_pc(), 0, count=0xffffff) diff --git a/qiling/arch/mips.py b/qiling/arch/mips.py index 3164d9ebb..8981d84a8 100644 --- a/qiling/arch/mips.py +++ b/qiling/arch/mips.py @@ -24,10 +24,10 @@ def __init__(self, ql: Qiling): ) for reg_maper in reg_maps: - self.ql.reg.expand_mapping(reg_maper) + self.ql.arch.regs.expand_mapping(reg_maper) - self.ql.reg.register_sp(reg_map["sp"]) - self.ql.reg.register_pc(reg_map["pc"]) + self.ql.arch.regs.register_sp(reg_map["sp"]) + self.ql.arch.regs.register_pc(reg_map["pc"]) @cached_property def uc(self) -> Uc: diff --git a/qiling/arch/register.py b/qiling/arch/register.py index b2013ea62..177bd5c18 100644 --- a/qiling/arch/register.py +++ b/qiling/arch/register.py @@ -8,7 +8,7 @@ from unicorn import Uc class QlRegisterManager: - """This class exposes the ql.reg features that allows you to directly access + """This class exposes the ql.arch.regs features that allows you to directly access or assign values to CPU registers of a particular architecture. Registers exposed are listed in the *_const.py files in the respective diff --git a/qiling/arch/riscv.py b/qiling/arch/riscv.py index 41fd74b45..790582afc 100644 --- a/qiling/arch/riscv.py +++ b/qiling/arch/riscv.py @@ -26,9 +26,9 @@ def __init__(self, ql: Qiling): ) for reg_maper in reg_maps: - self.ql.reg.expand_mapping(reg_maper) - self.ql.reg.register_sp(reg_map["sp"]) - self.ql.reg.register_pc(reg_map["pc"]) + self.ql.arch.regs.expand_mapping(reg_maper) + self.ql.arch.regs.register_sp(reg_map["sp"]) + self.ql.arch.regs.register_pc(reg_map["pc"]) @cached_property def uc(self) -> Uc: @@ -48,15 +48,15 @@ def assembler(self) -> Ks: raise QlErrorNotImplemented("Keystone does not yet support riscv") def enable_float(self): - self.ql.reg.mstatus = self.ql.reg.mstatus | MSTATUS.FS_DIRTY + self.ql.arch.regs.mstatus = self.ql.arch.regs.mstatus | MSTATUS.FS_DIRTY def init_context(self): - self.ql.reg.pc = 0x08000000 + self.ql.arch.regs.pc = 0x08000000 def soft_interrupt_handler(self, ql, intno): if intno == 2: try: - address, size = ql.reg.pc - 4, 4 + address, size = ql.arch.regs.pc - 4, 4 tmp = ql.mem.read(address, size) qd = ql.arch.disassembler @@ -69,7 +69,7 @@ def soft_interrupt_handler(self, ql, intno): raise QlErrorNotImplemented(f'Unhandled interrupt number ({intno})') def step(self): - self.ql.emu_start(self.ql.reg.arch_pc, 0, count=1) + self.ql.emu_start(self.ql.arch.regs.arch_pc, 0, count=1) self.ql.hw.step() def stop(self): @@ -79,7 +79,7 @@ def run(self, count=-1, end=None): self.runable = True while self.runable and count != 0: - if self.ql.reg.arch_pc == end: + if self.ql.arch.regs.arch_pc == end: break self.step() diff --git a/qiling/arch/utils.py b/qiling/arch/utils.py index 7310c67fe..45aecc5e9 100644 --- a/qiling/arch/utils.py +++ b/qiling/arch/utils.py @@ -42,9 +42,9 @@ def disassembler(self, ql: Qiling, address: int, size: int): ql.log.info(log_data + log_insn) if ql.verbose >= QL_VERBOSE.DUMP: - for reg in ql.reg.register_mapping: + for reg in ql.arch.regs.register_mapping: if type(reg) is str: - ql.log.debug(f'{reg}\t: {ql.reg.read(reg):#x}') + ql.log.debug(f'{reg}\t: {ql.arch.regs.read(reg):#x}') def setup_output(self): def ql_hook_block_disasm(ql, address, size): diff --git a/qiling/arch/x86.py b/qiling/arch/x86.py index f55de696b..2a651f39e 100644 --- a/qiling/arch/x86.py +++ b/qiling/arch/x86.py @@ -47,10 +47,10 @@ def __init__(self, ql: Qiling): ) for reg_maper in reg_maps: - self.ql.reg.expand_mapping(reg_maper) + self.ql.arch.regs.expand_mapping(reg_maper) - self.ql.reg.register_pc(reg_map_16["sp"]) - self.ql.reg.register_sp(reg_map_16["ip"]) + self.ql.arch.regs.register_pc(reg_map_16["sp"]) + self.ql.arch.regs.register_sp(reg_map_16["ip"]) @cached_property def uc(self) -> Uc: @@ -78,10 +78,10 @@ def __init__(self, ql: Qiling): ) for reg_maper in reg_maps: - self.ql.reg.expand_mapping(reg_maper) + self.ql.arch.regs.expand_mapping(reg_maper) - self.ql.reg.register_sp(reg_map_32["esp"]) - self.ql.reg.register_pc(reg_map_32["eip"]) + self.ql.arch.regs.register_sp(reg_map_32["esp"]) + self.ql.arch.regs.register_pc(reg_map_32["eip"]) @cached_property def uc(self) -> Uc: @@ -114,10 +114,10 @@ def __init__(self, ql: Qiling): ) for reg_maper in reg_maps: - self.ql.reg.expand_mapping(reg_maper) + self.ql.arch.regs.expand_mapping(reg_maper) - self.ql.reg.register_sp(reg_map_64["rsp"]) - self.ql.reg.register_pc(reg_map_64["rip"]) + self.ql.arch.regs.register_sp(reg_map_64["rsp"]) + self.ql.arch.regs.register_pc(reg_map_64["rip"]) @cached_property def uc(self) -> Uc: @@ -141,7 +141,7 @@ def __init__(self, ql: Qiling, GDT_ADDR = QL_X86_GDT_ADDR, GDT_LIMIT = QL_X86_G ql.mem.map(GDT_ADDR, GDT_LIMIT, info="[GDT]") # setup GDT by writing to GDTR - ql.reg.write(UC_X86_REG_GDTR, (0, GDT_ADDR, GDT_LIMIT, 0x0)) + ql.arch.regs.write(UC_X86_REG_GDTR, (0, GDT_ADDR, GDT_LIMIT, 0x0)) self.ql = ql self.gdt_number = GDT_ENTRY_ENTRIES @@ -208,13 +208,13 @@ def create_selector(self, idx, flags): def ql_x86_register_cs(self): # While debugging the linux kernel segment, the cs segment was found on the third segment of gdt. self.gdtm.register_gdt_segment(3, 0, 0xfffff000, QL_X86_A_PRESENT | QL_X86_A_CODE | QL_X86_A_CODE_READABLE | QL_X86_A_PRIV_3 | QL_X86_A_EXEC | QL_X86_A_DIR_CON_BIT, QL_X86_S_GDT | QL_X86_S_PRIV_3) - self.ql.reg.cs = self.gdtm.create_selector(3, QL_X86_S_GDT | QL_X86_S_PRIV_3) + self.ql.arch.regs.cs = self.gdtm.create_selector(3, QL_X86_S_GDT | QL_X86_S_PRIV_3) def ql_x8664_register_cs(self): # While debugging the linux kernel segment, the cs segment was found on the sixth segment of gdt. self.gdtm.register_gdt_segment(6, 0, 0xfffffffffffff000, QL_X86_A_PRESENT | QL_X86_A_CODE | QL_X86_A_CODE_READABLE | QL_X86_A_PRIV_3 | QL_X86_A_EXEC | QL_X86_A_DIR_CON_BIT, QL_X86_S_GDT | QL_X86_S_PRIV_3) - self.ql.reg.cs = self.gdtm.create_selector(6, QL_X86_S_GDT | QL_X86_S_PRIV_3) + self.ql.arch.regs.cs = self.gdtm.create_selector(6, QL_X86_S_GDT | QL_X86_S_PRIV_3) def ql_x86_register_ds_ss_es(self): @@ -222,44 +222,44 @@ def ql_x86_register_ds_ss_es(self): # While debugging the Linux kernel segment, I found that the three segments DS, SS, and ES all point to the same location in the GDT table. # This position is the fifth segment table of GDT. self.gdtm.register_gdt_segment(5, 0, 0xfffff000, QL_X86_A_PRESENT | QL_X86_A_DATA | QL_X86_A_DATA_WRITABLE | QL_X86_A_PRIV_0 | QL_X86_A_DIR_CON_BIT, QL_X86_S_GDT | QL_X86_S_PRIV_0) - self.ql.reg.ds = self.gdtm.create_selector(5, QL_X86_S_GDT | QL_X86_S_PRIV_0) - self.ql.reg.ss = self.gdtm.create_selector(5, QL_X86_S_GDT | QL_X86_S_PRIV_0) - self.ql.reg.es = self.gdtm.create_selector(5, QL_X86_S_GDT | QL_X86_S_PRIV_0) + self.ql.arch.regs.ds = self.gdtm.create_selector(5, QL_X86_S_GDT | QL_X86_S_PRIV_0) + self.ql.arch.regs.ss = self.gdtm.create_selector(5, QL_X86_S_GDT | QL_X86_S_PRIV_0) + self.ql.arch.regs.es = self.gdtm.create_selector(5, QL_X86_S_GDT | QL_X86_S_PRIV_0) def ql_x8664_register_ds_ss_es(self): # TODO : The section permission here should be QL_X86_A_PRIV_3, but I do n’t know why it can only be set to QL_X86_A_PRIV_0. # When I debug the Linux kernel, I find that only the SS is set to the fifth segment table, and the rest are not set. self.gdtm.register_gdt_segment(5, 0, 0xfffff000, QL_X86_A_PRESENT | QL_X86_A_DATA | QL_X86_A_DATA_WRITABLE | QL_X86_A_PRIV_0 | QL_X86_A_DIR_CON_BIT, QL_X86_S_GDT | QL_X86_S_PRIV_0) - # ql.reg.write(UC_X86_REG_DS, ql.os.gdtm.create_selector(5, QL_X86_S_GDT | QL_X86_S_PRIV_0)) - self.ql.reg.ss = self.gdtm.create_selector(5, QL_X86_S_GDT | QL_X86_S_PRIV_0) - # ql.reg.write(UC_X86_REG_ES, ql.os.gdtm.create_selector(5, QL_X86_S_GDT | QL_X86_S_PRIV_0)) + # ql.arch.regs.write(UC_X86_REG_DS, ql.os.gdtm.create_selector(5, QL_X86_S_GDT | QL_X86_S_PRIV_0)) + self.ql.arch.regs.ss = self.gdtm.create_selector(5, QL_X86_S_GDT | QL_X86_S_PRIV_0) + # ql.arch.regs.write(UC_X86_REG_ES, ql.os.gdtm.create_selector(5, QL_X86_S_GDT | QL_X86_S_PRIV_0)) def ql_x86_register_gs(self): self.gdtm.register_gdt_segment(15, GS_SEGMENT_ADDR, GS_SEGMENT_SIZE, QL_X86_A_PRESENT | QL_X86_A_DATA | QL_X86_A_DATA_WRITABLE | QL_X86_A_PRIV_3 | QL_X86_A_DIR_CON_BIT, QL_X86_S_GDT | QL_X86_S_PRIV_3) - self.ql.reg.gs = self.gdtm.create_selector(15, QL_X86_S_GDT | QL_X86_S_PRIV_0) + self.ql.arch.regs.gs = self.gdtm.create_selector(15, QL_X86_S_GDT | QL_X86_S_PRIV_0) def ql_x86_register_fs(self): self.gdtm.register_gdt_segment(14, FS_SEGMENT_ADDR, FS_SEGMENT_SIZE, QL_X86_A_PRESENT | QL_X86_A_DATA | QL_X86_A_DATA_WRITABLE | QL_X86_A_PRIV_3 | QL_X86_A_DIR_CON_BIT, QL_X86_S_GDT | QL_X86_S_PRIV_3) - self.ql.reg.fs = self.gdtm.create_selector(14, QL_X86_S_GDT | QL_X86_S_PRIV_3) + self.ql.arch.regs.fs = self.gdtm.create_selector(14, QL_X86_S_GDT | QL_X86_S_PRIV_3) def ql_x8664_set_gs(ql: Qiling): if not ql.mem.is_mapped(GS_SEGMENT_ADDR, GS_SEGMENT_SIZE): ql.mem.map(GS_SEGMENT_ADDR, GS_SEGMENT_SIZE, info="[GS]") - ql.reg.msr(GSMSR, GS_SEGMENT_ADDR) + ql.arch.regs.msr(GSMSR, GS_SEGMENT_ADDR) def ql_x8664_get_gs(ql: Qiling): - return ql.reg.msr(GSMSR) + return ql.arch.regs.msr(GSMSR) def ql_x8664_set_fs(ql: Qiling, addr: int): - ql.reg.msr(FSMSR, addr) + ql.arch.regs.msr(FSMSR, addr) def ql_x8664_get_fs(ql: Qiling): - return ql.reg.msr(FSMSR) + return ql.arch.regs.msr(FSMSR) diff --git a/qiling/cc/__init__.py b/qiling/cc/__init__.py index 7d69ed49a..8e4005b75 100644 --- a/qiling/cc/__init__.py +++ b/qiling/cc/__init__.py @@ -151,24 +151,24 @@ def __access_param(self, index: int, stack_access: Callable, reg_access: Callabl return reg_access, reg def getRawParam(self, index: int, argbits: int = None) -> int: - read, loc = self.__access_param(index, self.ql.stack_read, self.ql.reg.read) + read, loc = self.__access_param(index, self.ql.stack_read, self.ql.arch.regs.read) mask = (0 if argbits is None else (1 << argbits)) - 1 return read(loc) & mask def setRawParam(self, index: int, value: int, argbits: int = None) -> None: - write, loc = self.__access_param(index, self.ql.stack_write, self.ql.reg.write) + write, loc = self.__access_param(index, self.ql.stack_write, self.ql.arch.regs.write) mask = (0 if argbits is None else (1 << argbits)) - 1 write(loc, value & mask) def getReturnValue(self) -> int: - return self.ql.reg.read(self._retreg) + return self.ql.arch.regs.read(self._retreg) def setReturnValue(self, value: int) -> None: - self.ql.reg.write(self._retreg, value) + self.ql.arch.regs.write(self._retreg, value) def reserve(self, nslots: int) -> None: assert nslots < len(self._argregs), 'too many slots' @@ -176,4 +176,4 @@ def reserve(self, nslots: int) -> None: # count how many slots should be reserved on the stack si = self._argregs[:nslots].count(None) - self.ql.reg.arch_sp -= (self._shadow + si) * self._asize + self.ql.arch.regs.arch_sp -= (self._shadow + si) * self._asize diff --git a/qiling/cc/intel.py b/qiling/cc/intel.py index 9d90ab54e..1e1161200 100644 --- a/qiling/cc/intel.py +++ b/qiling/cc/intel.py @@ -107,6 +107,6 @@ class stdcall(QlIntel32): def unwind(self, nslots: int) -> int: retaddr = super().unwind(nslots) - self.ql.reg.arch_sp += (nslots * self._asize) + self.ql.arch.regs.arch_sp += (nslots * self._asize) return retaddr diff --git a/qiling/cc/mips.py b/qiling/cc/mips.py index ea69f90d4..1c08f161d 100644 --- a/qiling/cc/mips.py +++ b/qiling/cc/mips.py @@ -21,4 +21,4 @@ def getNumSlots(argbits: int): def unwind(self, nslots: int) -> int: # TODO: stack frame unwiding? - return self.ql.reg.ra + return self.ql.arch.regs.ra diff --git a/qiling/core.py b/qiling/core.py index 1f3e0dac9..e82258606 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -195,16 +195,14 @@ def __init__( ############## # Components # ############## + self._arch = arch_setup(self.archtype, self) + self.uc = self.arch.uc + if not self.interpreter: self._mem = component_setup("os", "memory", self) - self._reg = component_setup("arch", "register", self) - - self._arch = arch_setup(self.archtype, self) - # Once we finish setting up arch layer, we can init QlCoreHooks. if not self.interpreter: - self.uc = self.arch.uc QlCoreHooks.__init__(self, self.uc) self.arch.utils.setup_output() @@ -663,7 +661,7 @@ def _init_stop_guard(self): if self.stop_options.stackpointer: def _check_sp(ql, address, size): if not ql.loader.skip_exit_check: - sp = ql._initial_sp - ql.reg.arch_sp + sp = ql._initial_sp - ql.arch.regs.arch_sp if sp < 0: self.log.info('Process returned from entrypoint (stackpointer)!') ql.emu_stop() @@ -679,7 +677,7 @@ def _exit_trap(ql): self.hook_address(_exit_trap, self._exit_trap_addr) def write_exit_trap(self): - self._initial_sp = self.reg.arch_sp + self._initial_sp = self.arch.regs.arch_sp if self.stop_options.any: if not self.loader.skip_exit_check: self.log.debug(f'Setting up exit trap at 0x{hex(self._exit_trap_addr)}') @@ -738,7 +736,7 @@ def save(self, reg=True, mem=True, fd=False, cpu_context=False, os_context=False saved_states = {} if reg == True: - saved_states.update({"reg": self.reg.save()}) + saved_states.update({"reg": self.arch.regs.save()}) if mem == True: saved_states.update({"mem": self.mem.save()}) @@ -777,7 +775,7 @@ def restore(self, saved_states=None, snapshot=None): self.arch.context_restore(saved_states["cpu_context"]) if "reg" in saved_states: - self.reg.restore(saved_states["reg"]) + self.arch.regs.restore(saved_states["reg"]) if "fd" in saved_states: self.os.fd.restore(saved_states["fd"]) diff --git a/qiling/core_hooks.py b/qiling/core_hooks.py index e00af4b03..7b5f85ac7 100644 --- a/qiling/core_hooks.py +++ b/qiling/core_hooks.py @@ -67,7 +67,7 @@ def _hook_insn_cb(self, uc: Uc, *args): hooks_list = self._insn_hook[hook_type] for hook in hooks_list: - if hook.bound_check(ql.reg.arch_pc): + if hook.bound_check(ql.arch.regs.arch_pc): ret = hook.call(ql, *args[:-1]) if type(ret) is tuple: @@ -87,7 +87,7 @@ def _hook_trace_cb(self, uc: Uc, addr: int, size: int, pack_data) -> None: hooks_list = self._hook[hook_type] for hook in hooks_list: - if hook.bound_check(ql.reg.arch_pc): + if hook.bound_check(ql.arch.regs.arch_pc): ret = hook.call(ql, addr, size) if type(ret) is int and ret & QL_HOOK_BLOCK: diff --git a/qiling/debugger/gdb/gdb.py b/qiling/debugger/gdb/gdb.py index e26ab5176..f4ca7746f 100644 --- a/qiling/debugger/gdb/gdb.py +++ b/qiling/debugger/gdb/gdb.py @@ -180,21 +180,21 @@ def gdbqmark_converter(arch): return adapter.get(arch) idhex, spid, pcid = gdbqmark_converter(self.ql.archtype) - sp = self.addr_to_str(self.ql.reg.arch_sp) - pc = self.addr_to_str(self.ql.reg.arch_pc) + sp = self.addr_to_str(self.ql.arch.regs.arch_sp) + pc = self.addr_to_str(self.ql.arch.regs.arch_pc) nullfill = "0" * int(self.ql.archbit / 4) if self.ql.archtype== QL_ARCH.MIPS: if self.ql.archendian == QL_ENDIAN.EB: - sp = self.addr_to_str(self.ql.reg.arch_sp, endian ="little") - pc = self.addr_to_str(self.ql.reg.arch_pc, endian ="little") + sp = self.addr_to_str(self.ql.arch.regs.arch_sp, endian ="little") + pc = self.addr_to_str(self.ql.arch.regs.arch_pc, endian ="little") self.send('T%.2x%.2x:%s;%.2x:%s;' %(GDB_SIGNAL_TRAP, idhex, sp, pcid, pc)) else: self.send('T%.2x%.2x:%s;%.2x:%s;%.2x:%s;' %(GDB_SIGNAL_TRAP, idhex, nullfill, spid, sp, pcid, pc)) def handle_c(subcmd): - self.gdb.resume_emu(self.ql.reg.arch_pc) + self.gdb.resume_emu(self.ql.arch.regs.arch_pc) if self.gdb.bp_list == [self.entry_point]: self.send("W00") @@ -210,22 +210,22 @@ def handle_g(subcmd): if self.ql.archtype== QL_ARCH.A8086: for reg in self.tables[QL_ARCH.A8086][:16]: - r = self.ql.reg.read(reg) + r = self.ql.arch.regs.read(reg) tmp = self.addr_to_str(r) s += tmp elif self.ql.archtype== QL_ARCH.X86: for reg in self.tables[QL_ARCH.X86][:16]: - r = self.ql.reg.read(reg) + r = self.ql.arch.regs.read(reg) tmp = self.addr_to_str(r) s += tmp elif self.ql.archtype== QL_ARCH.X8664: for reg in self.tables[QL_ARCH.X8664][:24]: - r = self.ql.reg.read(reg) - if self.ql.reg.bit(reg) == 64: + r = self.ql.arch.regs.read(reg) + if self.ql.arch.regs.bit(reg) == 64: tmp = self.addr_to_str(r) - elif self.ql.reg.bit(reg) == 32: + elif self.ql.arch.regs.bit(reg) == 32: tmp = self.addr_to_str(r, short = True) s += tmp @@ -235,20 +235,20 @@ def handle_g(subcmd): # r0-r12,sp,lr,pc,cpsr ,see https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=gdb/arch/arm.h;h=fa589fd0582c0add627a068e6f4947a909c45e86;hb=HEAD#l127 for reg in self.tables[QL_ARCH.ARM][:16] + [self.tables[QL_ARCH.ARM][25]]: # if reg is pc, make sure to take thumb mode into account - r = self.ql.arch.get_pc() if reg == "pc" else self.ql.reg.read(reg) + r = self.ql.arch.get_pc() if reg == "pc" else self.ql.arch.regs.read(reg) tmp = self.addr_to_str(r) s += tmp elif self.ql.archtype == QL_ARCH.ARM64: for reg in self.tables[QL_ARCH.ARM64][:33]: - r = self.ql.reg.read(reg) + r = self.ql.arch.regs.read(reg) tmp = self.addr_to_str(r) s += tmp elif self.ql.archtype == QL_ARCH.MIPS: for reg in self.tables[QL_ARCH.MIPS][:38]: - r = self.ql.reg.read(reg) + r = self.ql.arch.regs.read(reg) if self.ql.archendian == QL_ENDIAN.EL: tmp = self.addr_to_str(r, endian ="little") else: @@ -265,14 +265,14 @@ def handle_G(subcmd): for i in range(0, len(subcmd), 8): reg_data = subcmd[i:i+7] reg_data = int(reg_data, 16) - self.ql.reg.write(self.tables[QL_ARCH.A8086][count], reg_data) + self.ql.arch.regs.write(self.tables[QL_ARCH.A8086][count], reg_data) count += 1 elif self.ql.archtype == QL_ARCH.X86: for i in range(0, len(subcmd), 8): reg_data = subcmd[i:i+7] reg_data = int(reg_data, 16) - self.ql.reg.write(self.tables[QL_ARCH.X86][count], reg_data) + self.ql.arch.regs.write(self.tables[QL_ARCH.X86][count], reg_data) count += 1 @@ -280,33 +280,33 @@ def handle_G(subcmd): for i in range(0, 17*16, 16): reg_data = subcmd[i:i+15] reg_data = int(reg_data, 16) - self.ql.reg.write(self.tables[QL_ARCH.X8664][count], reg_data) + self.ql.arch.regs.write(self.tables[QL_ARCH.X8664][count], reg_data) count += 1 for j in range(17*16, 17*16+15*8, 8): reg_data = subcmd[j:j+7] reg_data = int(reg_data, 16) - self.ql.reg.write(self.tables[QL_ARCH.X8664][count], reg_data) + self.ql.arch.regs.write(self.tables[QL_ARCH.X8664][count], reg_data) count += 1 elif self.ql.archtype == QL_ARCH.ARM: for i in range(0, len(subcmd), 8): reg_data = subcmd[i:i + 7] reg_data = int(reg_data, 16) - self.ql.reg.write(self.tables[QL_ARCH.ARM][count], reg_data) + self.ql.arch.regs.write(self.tables[QL_ARCH.ARM][count], reg_data) count += 1 elif self.ql.archtype == QL_ARCH.ARM64: for i in range(0, len(subcmd), 16): reg_data = subcmd[i:i+15] reg_data = int(reg_data, 16) - self.ql.reg.write(self.tables[QL_ARCH.ARM64][count], reg_data) + self.ql.arch.regs.write(self.tables[QL_ARCH.ARM64][count], reg_data) count += 1 elif self.ql.archtype == QL_ARCH.MIPS: for i in range(0, len(subcmd), 8): reg_data = subcmd[i:i+7] reg_data = int(reg_data, 16) - self.ql.reg.write(self.tables[QL_ARCH.MIPS][count], reg_data) + self.ql.arch.regs.write(self.tables[QL_ARCH.MIPS][count], reg_data) count += 1 self.send('OK') @@ -355,21 +355,21 @@ def handle_p(subcmd): try: if self.ql.archtype== QL_ARCH.A8086: if reg_index <= 9: - reg_value = self.ql.reg.read(self.tables[QL_ARCH.A8086][reg_index-1]) + reg_value = self.ql.arch.regs.read(self.tables[QL_ARCH.A8086][reg_index-1]) else: reg_value = 0 reg_value = self.addr_to_str(reg_value) elif self.ql.archtype== QL_ARCH.X86: if reg_index <= 24: - reg_value = self.ql.reg.read(self.tables[QL_ARCH.X86][reg_index-1]) + reg_value = self.ql.arch.regs.read(self.tables[QL_ARCH.X86][reg_index-1]) else: reg_value = 0 reg_value = self.addr_to_str(reg_value) elif self.ql.archtype== QL_ARCH.X8664: if reg_index <= 32: - reg_value = self.ql.reg.read(self.tables[QL_ARCH.X8664][reg_index-1]) + reg_value = self.ql.arch.regs.read(self.tables[QL_ARCH.X8664][reg_index-1]) else: reg_value = 0 if reg_index <= 17: @@ -379,21 +379,21 @@ def handle_p(subcmd): elif self.ql.archtype== QL_ARCH.ARM: if reg_index < 26: - reg_value = self.ql.reg.read(self.tables[QL_ARCH.ARM][reg_index - 1]) + reg_value = self.ql.arch.regs.read(self.tables[QL_ARCH.ARM][reg_index - 1]) else: reg_value = 0 reg_value = self.addr_to_str(reg_value) elif self.ql.archtype== QL_ARCH.ARM64: if reg_index <= 32: - reg_value = self.ql.reg.read(self.tables[QL_ARCH.ARM64][reg_index - 1]) + reg_value = self.ql.arch.regs.read(self.tables[QL_ARCH.ARM64][reg_index - 1]) else: reg_value = 0 reg_value = self.addr_to_str(reg_value) elif self.ql.archtype== QL_ARCH.MIPS: if reg_index <= 37: - reg_value = self.ql.reg.read(self.tables[QL_ARCH.MIPS][reg_index - 1]) + reg_value = self.ql.arch.regs.read(self.tables[QL_ARCH.MIPS][reg_index - 1]) else: reg_value = 0 if self.ql.archendian == QL_ENDIAN.EL: @@ -418,32 +418,32 @@ def handle_P(subcmd): if self.ql.archtype== QL_ARCH.A8086: reg_data = int(reg_data, 16) reg_data = int.from_bytes(struct.pack(' Write to register %s with %x\n" % (self.tables[self.ql.archtype][reg_index], reg_data)) diff --git a/qiling/debugger/gdb/utils.py b/qiling/debugger/gdb/utils.py index fa4e367af..f1ce057c1 100644 --- a/qiling/debugger/gdb/utils.py +++ b/qiling/debugger/gdb/utils.py @@ -38,7 +38,7 @@ def entry_point_hook(self, ql, *args, **kwargs): self.ql.hook_del(self._tmp_hook) self.ql.hook_code(self.dbg_hook) self.ql.stop() - self.ql.log.info("gdb> Stop at entry point: %#x" % self.ql.reg.arch_pc) + self.ql.log.info("gdb> Stop at entry point: %#x" % self.ql.arch.regs.arch_pc) def dbg_hook(self, ql, address, size): """ diff --git a/qiling/debugger/qdb/frontend.py b/qiling/debugger/qdb/frontend.py index fb93e6e4f..b6c57f150 100644 --- a/qiling/debugger/qdb/frontend.py +++ b/qiling/debugger/qdb/frontend.py @@ -68,8 +68,8 @@ def extract_count(t): items = [] for elem in elems: - if elem in ql.reg.register_mapping.keys(): - items.append(getattr(ql.reg, elem, None)) + if elem in ql.arch.regs.register_mapping.keys(): + items.append(getattr(ql.arch.regs, elem, None)) else: items.append(_parse_int(elem)) @@ -232,14 +232,14 @@ def context_reg(ql: Qiling, saved_states: Optional[Mapping[str, int]] = None, /, lines += line print(lines.format(*_cur_regs.values())) - print(color.GREEN, "[{cpsr[mode]} mode], Thumb: {cpsr[thumb]}, FIQ: {cpsr[fiq]}, IRQ: {cpsr[irq]}, NEG: {cpsr[neg]}, ZERO: {cpsr[zero]}, Carry: {cpsr[carry]}, Overflow: {cpsr[overflow]}".format(cpsr=get_arm_flags(ql.reg.cpsr)), color.END, sep="") + print(color.GREEN, "[{cpsr[mode]} mode], Thumb: {cpsr[thumb]}, FIQ: {cpsr[fiq]}, IRQ: {cpsr[irq]}, NEG: {cpsr[neg]}, ZERO: {cpsr[zero]}, Carry: {cpsr[carry]}, Overflow: {cpsr[overflow]}".format(cpsr=get_arm_flags(ql.arch.regs.cpsr)), color.END, sep="") if ql.archtype != QL_ARCH.CORTEX_M: # context render for Stack, skip this for CORTEX_M with context_printer(ql, "[ STACK ]", ruler="─"): for idx in range(10): - addr = ql.reg.arch_sp + idx * ql.pointersize + addr = ql.arch.regs.arch_sp + idx * ql.pointersize val = ql.mem.read(addr, ql.pointersize) print(f"$sp+0x{idx*ql.pointersize:02x}│ [0x{addr:08x}] —▸ 0x{ql.unpack(val):08x}", end="") @@ -268,11 +268,11 @@ def print_asm(ql: Qiling, insn: CsInsn, to_jump: Optional[bool] = None, address: trace_line = f"0x{insn.address:08x} │ {opcode:10s} {insn.mnemonic:10} {insn.op_str:35s}" cursor = " " - if ql.reg.arch_pc == insn.address: + if ql.arch.regs.arch_pc == insn.address: cursor = "►" jump_sign = " " - if to_jump and address != ql.reg.arch_pc+4: + if to_jump and address != ql.arch.regs.arch_pc+4: jump_sign = f"{color.RED}✓{color.END}" print(f"{jump_sign} {cursor} {color.DARKGRAY}{trace_line}{color.END}") diff --git a/qiling/debugger/qdb/qdb.py b/qiling/debugger/qdb/qdb.py index 819c04b51..36c147599 100644 --- a/qiling/debugger/qdb/qdb.py +++ b/qiling/debugger/qdb/qdb.py @@ -60,7 +60,7 @@ def cur_addr(self: QlQdb) -> int: getter for current address of qiling instance """ - return self.ql.reg.arch_pc + return self.ql.arch.regs.arch_pc @cur_addr.setter def cur_addr(self: QlQdb, address: int) -> None: @@ -68,7 +68,7 @@ def cur_addr(self: QlQdb, address: int) -> None: setter for current address of qiling instance """ - self.ql.reg.arch_pc = address + self.ql.arch.regs.arch_pc = address def _bp_handler(self: QlQdb, *args) -> None: """ @@ -130,7 +130,7 @@ def _run(self: Qldbg, address: int = 0, end: int = 0, count: int = 0) -> None: return - if self.ql.archtype in (QL_ARCH.ARM, QL_ARCH.ARM_THUMB, QL_ARCH.CORTEX_M) and is_thumb(self.ql.reg.cpsr): + if self.ql.archtype in (QL_ARCH.ARM, QL_ARCH.ARM_THUMB, QL_ARCH.CORTEX_M) and is_thumb(self.ql.arch.regs.cpsr): address |= 1 self.ql.emu_start(begin=address, end=end, count=count) @@ -217,7 +217,7 @@ def do_step(self: QlQdb, *args) -> Optional[bool]: else: # save reg dump for data highlighting changes - self._saved_reg_dump = dict(filter(lambda d: isinstance(d[0], str), self.ql.reg.save().items())) + self._saved_reg_dump = dict(filter(lambda d: isinstance(d[0], str), self.ql.arch.regs.save().items())) if self.rr: self._save() diff --git a/qiling/debugger/qdb/utils.py b/qiling/debugger/qdb/utils.py index bf2e24f34..0b4344ce1 100644 --- a/qiling/debugger/qdb/utils.py +++ b/qiling/debugger/qdb/utils.py @@ -46,7 +46,7 @@ def dump_regs(ql: Qiling) -> Mapping[str, int]: "xpsr", "control", "primask", "basepri", "faultmask" ) - return {reg_name: getattr(ql.reg, reg_name) for reg_name in _reg_order} + return {reg_name: getattr(ql.arch.regs, reg_name) for reg_name in _reg_order} def get_arm_flags(bits: int) -> Mapping[str, int]: @@ -143,7 +143,7 @@ def _read_inst(ql: Qiling, addr: int) -> int: result = ql.mem.read(addr, 4) if ql.archtype in (QL_ARCH.ARM, QL_ARCH.ARM_THUMB, QL_ARCH.CORTEX_M): - if is_thumb(ql.reg.cpsr): + if is_thumb(ql.arch.regs.cpsr): first_two = ql.unpack16(ql.mem.read(addr, 2)) result = ql.pack16(first_two) @@ -164,13 +164,13 @@ def _read_inst(ql: Qiling, addr: int) -> int: def handle_bnj_arm(ql: Qiling, cur_addr: str) -> int: def _read_reg_val(regs, _reg): - return getattr(ql.reg, _reg.replace("ip", "r12").replace("fp", "r11")) + return getattr(ql.arch.regs, _reg.replace("ip", "r12").replace("fp", "r11")) def regdst_eq_pc(op_str): return op_str.partition(", ")[0] == "pc" read_inst = partial(_read_inst, ql) - read_reg_val = partial(_read_reg_val, ql.reg) + read_reg_val = partial(_read_reg_val, ql.arch.regs) ARM_INST_SIZE = 4 ARM_THUMB_INST_SIZE = 2 @@ -254,7 +254,7 @@ def regdst_eq_pc(op_str): to_jump = False if line.mnemonic in jump_table: - to_jump = jump_table.get(line.mnemonic)(*get_cpsr(ql.reg.cpsr)) + to_jump = jump_table.get(line.mnemonic)(*get_cpsr(ql.arch.regs.cpsr)) elif line.mnemonic in cb_table: to_jump = cb_table.get(line.mnemonic)(read_reg_val(line.op_str.split(", ")[0])) @@ -284,7 +284,7 @@ def regdst_eq_pc(op_str): "ls": lambda V, C, Z, N: (C == 0 or Z == 1), "le": lambda V, C, Z, N: (Z == 1 or N != V), "hi": lambda V, C, Z, N: (Z == 0 and C == 1), - }.get(line.op_str)(*get_cpsr(ql.reg.cpsr)) + }.get(line.op_str)(*get_cpsr(ql.arch.regs.cpsr)) it_block_range = [each_char for each_char in line.mnemonic[1:]] @@ -315,7 +315,7 @@ def regdst_eq_pc(op_str): ret_addr = ql.unpack32(ql.mem.read(read_reg_val(r), ARM_INST_SIZE)) elif line.mnemonic in ("addls", "addne", "add") and regdst_eq_pc(line.op_str): - V, C, Z, N = get_cpsr(ql.reg.cpsr) + V, C, Z, N = get_cpsr(ql.arch.regs.cpsr) r0, r1, r2, *imm = line.op_str.split(", ") # program counter is awalys 8 bytes ahead when it comes with pc, need to add extra 8 bytes @@ -365,7 +365,7 @@ def regdst_eq_pc(op_str): "pophi": lambda V, C, Z, N: (C == 1), "popge": lambda V, C, Z, N: (N == V), "poplt": lambda V, C, Z, N: (N != V), - }.get(line.mnemonic)(*get_cpsr(ql.reg.cpsr)): + }.get(line.mnemonic)(*get_cpsr(ql.arch.regs.cpsr)): ret_addr = cur_addr + ARM_INST_SIZE @@ -389,7 +389,7 @@ def handle_bnj_mips(ql: Qiling, cur_addr: str) -> int: def _read_reg(regs, _reg): return signed_val(getattr(regs, _reg.strip('$').replace("fp", "s8"))) - read_reg_val = partial(_read_reg, ql.reg) + read_reg_val = partial(_read_reg, ql.arch.regs) line = disasm(ql, cur_addr) diff --git a/qiling/extensions/idaplugin/qilingida.py b/qiling/extensions/idaplugin/qilingida.py index 0db8e43d2..32fa9a342 100644 --- a/qiling/extensions/idaplugin/qilingida.py +++ b/qiling/extensions/idaplugin/qilingida.py @@ -509,7 +509,7 @@ def SetReg(self, addr, ql:Qiling): for regs in reglist: for reg in regs: line += COLSTR(" %4s: " % str(reg), SCOLOR_REG) - regvalue = ql.reg.read(reg) + regvalue = ql.arch.regs.read(reg) if arch in [QL_ARCH.X8664, QL_ARCH.ARM64]: value_format = "0x%.16X" else: @@ -547,7 +547,7 @@ def SetStack(self, ql:Qiling): if ql is None: return - sp = ql.reg.arch_sp + sp = ql.arch.regs.arch_sp self.AddLine('') self.AddLine(COLSTR(' Stack at 0x%X' % sp, SCOLOR_AUTOCMT)) self.AddLine('') @@ -935,12 +935,12 @@ def run(self, begin=None, end=None): def set_reg(self): reglist = QlEmuMisc.get_reg_map(self.ql) - regs = [ [ row, int(self.ql.reg.read(row)), ql_get_arch_bits(self.ql.archtype) ] for row in reglist ] + regs = [ [ row, int(self.ql.arch.regs.read(row)), ql_get_arch_bits(self.ql.archtype) ] for row in reglist ] regs_len = len(regs) RegDig = QlEmuRegDialog(regs) if RegDig.show(): for idx, val in enumerate(RegDig.items[0:regs_len-1]): - self.ql.reg.write(reglist[idx], val[1]) + self.ql.arch.regs.write(reglist[idx], val[1]) return True else: return False @@ -1090,7 +1090,7 @@ def ql_continue(self): self.qlemu.ql.restore(self.qlemu.status) show_wait_box("Qiling is processing ...") try: - self.qlemu.run(begin=self.qlemu.ql.reg.arch_pc, end=self.qlemu.exit_addr) + self.qlemu.run(begin=self.qlemu.ql.arch.regs.arch_pc, end=self.qlemu.exit_addr) finally: hide_wait_box() else: @@ -1103,7 +1103,7 @@ def ql_continue(self): if userhook and userhook is not None: for hook in userhook: self.qlemu.ql.hook_del(hook) - self.ql_update_views(self.qlemu.ql.reg.arch_pc, self.qlemu.ql) + self.ql_update_views(self.qlemu.ql.arch.regs.arch_pc, self.qlemu.ql) else: logging.error('Qiling should be setup firstly.') @@ -1131,17 +1131,17 @@ def ql_run_selection(self): for hook in userhook: self.qlemu.ql.hook_del(hook) self.qlemu.status = self.qlemu.ql.save() - self.ql_update_views(self.qlemu.ql.reg.arch_pc, self.qlemu.ql) + self.ql_update_views(self.qlemu.ql.arch.regs.arch_pc, self.qlemu.ql) else: logging.error('Qiling should be setup firstly.') def ql_set_pc(self): if self.qlinit: ea = IDA.get_current_address() - self.qlemu.ql.reg.arch_pc = ea + self.qlemu.ql.arch.regs.arch_pc = ea logging.info(f"QIling PC set to {hex(ea)}") self.qlemu.status = self.qlemu.ql.save() - self.ql_update_views(self.qlemu.ql.reg.arch_pc, self.qlemu.ql) + self.ql_update_views(self.qlemu.ql.arch.regs.arch_pc, self.qlemu.ql) else: logging.error('Qiling should be setup firstly.') @@ -1153,7 +1153,7 @@ def ql_run_to_here(self): self.qlemu.ql.restore(self.qlemu.status) show_wait_box("Qiling is processing ...") try: - self.qlemu.run(begin=self.qlemu.ql.reg.arch_pc, end=curr_addr+self.qlemu.baseaddr-get_imagebase()) + self.qlemu.run(begin=self.qlemu.ql.arch.regs.arch_pc, end=curr_addr+self.qlemu.baseaddr-get_imagebase()) finally: hide_wait_box() else: @@ -1166,7 +1166,7 @@ def ql_run_to_here(self): set_color(curr_addr, CIC_ITEM, 0x00B3CBFF) self.qlemu.ql.hook_del(untillhook) self.qlemu.status = self.qlemu.ql.save() - self.ql_update_views(self.qlemu.ql.reg.arch_pc, self.qlemu.ql) + self.ql_update_views(self.qlemu.ql.arch.regs.arch_pc, self.qlemu.ql) else: logging.error('Qiling should be setup firstly.') @@ -1178,11 +1178,11 @@ def ql_step(self): self.stephook = self.qlemu.ql.hook_code(callback=self.ql_step_hook) if self.userobj is not None: userhook = self.userobj.custom_step(self.qlemu.ql) - self.qlemu.run(begin=self.qlemu.ql.reg.arch_pc, end=self.qlemu.exit_addr) + self.qlemu.run(begin=self.qlemu.ql.arch.regs.arch_pc, end=self.qlemu.exit_addr) if userhook and userhook is not None: for hook in userhook: self.qlemu.ql.hook_del(hook) - self.ql_update_views(self.qlemu.ql.reg.arch_pc, self.qlemu.ql) + self.ql_update_views(self.qlemu.ql.arch.regs.arch_pc, self.qlemu.ql) else: logging.error('Qiling should be setup firstly.') @@ -1203,7 +1203,7 @@ def ql_load(self): def ql_chang_reg(self): if self.qlinit: self.qlemu.set_reg() - self.ql_update_views(self.qlemu.ql.reg.arch_pc, self.qlemu.ql) + self.ql_update_views(self.qlemu.ql.arch.regs.arch_pc, self.qlemu.ql) self.qlemu.status = self.qlemu.ql.save() else: logging.error('Qiling should be setup firstly.') @@ -1235,7 +1235,7 @@ def ql_show_reg_view(self): self.qlemuregview = QlEmuRegView(self) QlEmuRegView(self) self.qlemuregview.Create() - self.qlemuregview.SetReg(self.qlemu.ql.reg.arch_pc, self.qlemu.ql) + self.qlemuregview.SetReg(self.qlemu.ql.arch.regs.arch_pc, self.qlemu.ql) self.qlemuregview.Show() self.qlemuregview.Refresh() else: @@ -1463,7 +1463,7 @@ def _force_execution_with_microcode(self, ql, ida_addr): imm = first_ins.l.nnn.value reg_name = ida_hexrays.get_mreg_name(first_ins.d.r, ql.pointersize) logging.info(f"Froce set {reg_name} to {hex(imm)}") - ql.reg.__setattr__(reg_name, imm) + ql.arch.regs.__setattr__(reg_name, imm) return True def _ida_address_after_branch(self, ida_addr): @@ -1489,9 +1489,9 @@ def _force_execution_by_parsing_assembly(self, ql, ida_addr): if "x86" in IDA.get_ql_arch_string(): # cmovlg eax, ebx reg1 = IDA.print_operand(ida_addr, 0).lower() reg2 = IDA.print_operand(ida_addr, 1).lower() - reg2_val = ql.reg.__getattribute__(reg2) + reg2_val = ql.arch.regs.__getattribute__(reg2) logging.info(f"Force set {reg1} to {hex(reg2_val)}") - ql.reg.__setattr__(reg1, reg2_val) + ql.arch.regs.__setattr__(reg1, reg2_val) return True elif "arm" in IDA.get_ql_arch_string(): instr = IDA.get_instruction(ida_addr).lower() @@ -1504,14 +1504,14 @@ def _force_execution_by_parsing_assembly(self, ql, ida_addr): reg = IDA.print_operand(ida_addr, 0).lower() val = (high << 16) + low logging.info(f"Force set {reg} to {hex(val)}") - ql.reg.__setattr__(reg, val) + ql.arch.regs.__setattr__(reg, val) return True elif "csel" in instr: # csel dst, src1, src2, cond dst = IDA.print_operand(ida_addr, 0).lower() src = IDA.print_operand(ida_addr, 2).lower() - src_val = ql.reg.__getattribute__(src) + src_val = ql.arch.regs.__getattribute__(src) logging.info(f"Force set {dst} to {hex(src_val)}") - ql.reg.__setattr__(dst, src_val) + ql.arch.regs.__setattr__(dst, src_val) return True return False @@ -1554,12 +1554,12 @@ def _guide_hook(self, ql, addr, size): else: next_ida_addr = ida_addr + IDA.get_instruction_size(ida_addr) logging.info(f"Goto {hex(next_ida_addr)} after branch...") - ql.reg.arch_pc = self.deflatqlemu.ql_addr_from_ida(next_ida_addr) + self.append + ql.arch.regs.arch_pc = self.deflatqlemu.ql_addr_from_ida(next_ida_addr) + self.append ida_addr = next_ida_addr # TODO: Maybe we can detect whether the program will access unmapped # here so that we won't map the memory. if self._has_call_insn(ida_addr): - ql.reg.arch_pc += IDA.get_instruction_size(ida_addr) + self.append + ql.arch.regs.arch_pc += IDA.get_instruction_size(ida_addr) + self.append return if start_bb_id == cur_bb.id: return @@ -1617,10 +1617,10 @@ def _thumb_detect(self, ida_addr): def _log_verbose(self, ql, addr, size): logging.debug(f"addr: {hex(addr)} ida_addr: {hex(self.deflatqlemu.ida_addr_from_ql_addr(addr))}") - registers = [ k for k in ql.reg.register_mapping.keys() if type(k) is str ] + registers = [ k for k in ql.arch.regs.register_mapping.keys() if type(k) is str ] for idx in range(0, len(registers), 3): regs = registers[idx:idx+3] - s = "\t".join(map(lambda v: f"{v:4}: {ql.reg.__getattribute__(v):016x}", regs)) + s = "\t".join(map(lambda v: f"{v:4}: {ql.arch.regs.__getattribute__(v):016x}", regs)) logging.debug(s) # Q: Why we need emulation to help us find real control flow considering there are some @@ -1641,7 +1641,7 @@ def _search_path(self): if self._thumb_detect(first_block.start_ea): logging.info(f"Thumb detected, enable it.") self.deflatqlemu.start(archtype=QL_ARCH.ARM_THUMB) - self.deflatqlemu.ql.reg.cpsr |= 0x20 + self.deflatqlemu.ql.arch.regs.cpsr |= 0x20 self.append = 1 else: self.deflatqlemu.start() @@ -2003,7 +2003,7 @@ def ql_step_hook(self, ql, addr, size): addr = addr - self.qlemu.baseaddr + get_imagebase() if self.stepflag: set_color(addr, CIC_ITEM, 0x00FFD700) - self.ql_update_views(self.qlemu.ql.reg.arch_pc, ql) + self.ql_update_views(self.qlemu.ql.arch.regs.arch_pc, ql) self.qlemu.status = ql.save() ql.os.stop() self.qlemu.ql.hook_del(self.stephook) diff --git a/qiling/extensions/trace.py b/qiling/extensions/trace.py index ade5e8f95..878c6a4b7 100644 --- a/qiling/extensions/trace.py +++ b/qiling/extensions/trace.py @@ -59,7 +59,7 @@ def __get_trace_records(ql: Qiling, address: int, size: int, md: Cs) -> Iterator for insn in md.disasm(buf, address): # BUG: insn.regs_read doesn't work well, so we use insn.regs_access()[0] - state = tuple((reg, ql.reg.read(CS_UC_REGS[reg])) for reg in insn.regs_access()[0]) + state = tuple((reg, ql.arch.regs.read(CS_UC_REGS[reg])) for reg in insn.regs_access()[0]) yield (insn, state) diff --git a/qiling/hw/peripheral.py b/qiling/hw/peripheral.py index b437f229d..55d36a23c 100644 --- a/qiling/hw/peripheral.py +++ b/qiling/hw/peripheral.py @@ -18,7 +18,7 @@ def decorator(func): def read(self, offset: int, size: int) -> int: retval = func(self, offset, size) if self.verbose: - self.ql.log.info(f'[{self.label.upper()}] [{hex(self.ql.reg.pc)}] [R] {self.find_field(offset, size):{width}s} = {hex(retval)}') + self.ql.log.info(f'[{self.label.upper()}] [{hex(self.ql.arch.regs.pc)}] [R] {self.find_field(offset, size):{width}s} = {hex(retval)}') return retval @@ -28,7 +28,7 @@ def write(self, offset: int, size: int, value: int): if field.startswith('DR') and value <= 255: extra = f'({repr(chr(value))})' - self.ql.log.info(f'[{self.label.upper()}] [{hex(self.ql.reg.pc)}] [W] {field:{width}s} = {hex(value)} {extra}') + self.ql.log.info(f'[{self.label.upper()}] [{hex(self.ql.arch.regs.pc)}] [W] {field:{width}s} = {hex(value)} {extra}') return func(self, offset, size, value) diff --git a/qiling/loader/blob.py b/qiling/loader/blob.py index 787ba6a39..382dbb33c 100644 --- a/qiling/loader/blob.py +++ b/qiling/loader/blob.py @@ -23,6 +23,6 @@ def run(self): heap_size = int(self.ql.os.profile.get("CODE", "heap_size"), 16) self.ql.os.heap = QlMemoryHeap(self.ql, heap_address, heap_address + heap_size) - self.ql.reg.arch_sp = heap_address - 0x1000 + self.ql.arch.regs.arch_sp = heap_address - 0x1000 return diff --git a/qiling/loader/dos.py b/qiling/loader/dos.py index 69e8826aa..5e3db8bbd 100644 --- a/qiling/loader/dos.py +++ b/qiling/loader/dos.py @@ -87,16 +87,16 @@ def run(self): self.ql.os.fs_mapper.add_fs_mapping(0x80, QlDisk(path, 0x80)) # 0x80 -> first drive - self.ql.reg.dx = 0x80 + self.ql.arch.regs.dx = 0x80 else: raise NotImplementedError() - self.ql.reg.cs = cs - self.ql.reg.ds = cs - self.ql.reg.es = cs - self.ql.reg.ss = ss - self.ql.reg.ip = ip - self.ql.reg.sp = sp + self.ql.arch.regs.cs = cs + self.ql.arch.regs.ds = cs + self.ql.arch.regs.es = cs + self.ql.arch.regs.ss = ss + self.ql.arch.regs.ip = ip + self.ql.arch.regs.sp = sp self.stack_address = (ss << 4) + sp self.start_address = (cs << 4) + ip diff --git a/qiling/loader/elf.py b/qiling/loader/elf.py index 7844e34ce..77bde7ea0 100644 --- a/qiling/loader/elf.py +++ b/qiling/loader/elf.py @@ -70,7 +70,7 @@ def run(self): 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.reg.arch_sp = self.ql.os.entry_point + self.ql.arch.regs.arch_sp = self.ql.os.entry_point return section = { @@ -115,13 +115,13 @@ def run(self): self.is_driver = (elftype == 'ET_REL') - self.ql.reg.arch_sp = self.stack_address + self.ql.arch.regs.arch_sp = self.stack_address # No idea why. if self.ql.ostype == QL_OS.FREEBSD: - # self.ql.reg.rbp = self.stack_address + 0x40 - self.ql.reg.rdi = self.stack_address - self.ql.reg.r14 = self.stack_address + # self.ql.arch.regs.rbp = self.stack_address + 0x40 + self.ql.arch.regs.rdi = self.stack_address + self.ql.arch.regs.r14 = self.stack_address @staticmethod def seg_perm_to_uc_prot(perm: int) -> int: @@ -393,7 +393,7 @@ def __push_str(top: int, s: str) -> int: self.stack_address = new_stack self.load_address = load_address self.images.append(Image(load_address, load_address + mem_end, self.path)) - self.init_sp = self.ql.reg.arch_sp + self.init_sp = self.ql.arch.regs.arch_sp self.ql.os.entry_point = self.entry_point = entry_point self.ql.os.elf_mem_start = mem_start diff --git a/qiling/loader/macho.py b/qiling/loader/macho.py index 813324abd..97e21d348 100644 --- a/qiling/loader/macho.py +++ b/qiling/loader/macho.py @@ -105,7 +105,7 @@ def run(self): self.ql.mem.write(self.entry_point, self.ql.code) - self.ql.reg.arch_sp = self.ql.os.entry_point + self.ql.arch.regs.arch_sp = self.ql.os.entry_point return self.ql.os.macho_task = MachoTask() @@ -149,8 +149,8 @@ def run(self): else: self.loadMacho() self.stack_address = (int(self.stack_sp)) - self.ql.reg.arch_sp = self.stack_address # self.stack_sp - self.init_sp = self.ql.reg.arch_sp + self.ql.arch.regs.arch_sp = self.stack_address # self.stack_sp + self.init_sp = self.ql.arch.regs.arch_sp self.ql.os.macho_task.min_offset = page_align_end(self.vm_end_addr, PAGE_SIZE) def loadDriver(self, stack_addr, loadbase = -1, argv = [], env = {}): diff --git a/qiling/loader/mcu.py b/qiling/loader/mcu.py index 4cd4b5c7e..ee07b29a4 100644 --- a/qiling/loader/mcu.py +++ b/qiling/loader/mcu.py @@ -107,7 +107,7 @@ def reset(self): self.ql.arch.init_context() - self.entry_point = self.ql.reg.read('pc') + self.entry_point = self.ql.arch.regs.read('pc') def load_profile(self): self.ql.env.update(self.ql.profile) diff --git a/qiling/loader/pe.py b/qiling/loader/pe.py index 726fc10a1..02bc8d978 100644 --- a/qiling/loader/pe.py +++ b/qiling/loader/pe.py @@ -536,8 +536,8 @@ def load(self): sp = self.stack_address + self.stack_size - 0x1000 if self.ql.archtype == QL_ARCH.X86: - self.ql.reg.esp = sp - self.ql.reg.ebp = sp + self.ql.arch.regs.esp = sp + self.ql.arch.regs.ebp = sp if self.pe.is_dll(): self.ql.log.debug('Setting up DllMain args') @@ -550,17 +550,17 @@ def load(self): self.ql.mem.write(sp + 0x8, int(1).to_bytes(length=4, byteorder='little')) elif self.ql.archtype == QL_ARCH.X8664: - self.ql.reg.rsp = sp - self.ql.reg.rbp = sp + self.ql.arch.regs.rsp = sp + self.ql.arch.regs.rbp = sp if self.pe.is_dll(): self.ql.log.debug('Setting up DllMain args') self.ql.log.debug('Setting RCX (arg1) to %16X (IMAGE_BASE)' % (self.pe_image_address)) - self.ql.reg.rcx = self.pe_image_address + self.ql.arch.regs.rcx = self.pe_image_address self.ql.log.debug('Setting RDX (arg2) to 1 (DLL_PROCESS_ATTACH)') - self.ql.reg.rdx = 1 + self.ql.arch.regs.rdx = 1 else: raise QlErrorArch("Unknown ql.arch") @@ -574,10 +574,10 @@ def load(self): super().init_ki_user_shared_data() # setup IRQ Level in CR8 to PASSIVE_LEVEL (0) - self.ql.reg.write(UC_X86_REG_CR8, 0) + self.ql.arch.regs.write(UC_X86_REG_CR8, 0) # setup CR4, some drivers may check this at initialized time - self.ql.reg.write(UC_X86_REG_CR4, 0x6f8) + self.ql.arch.regs.write(UC_X86_REG_CR4, 0x6f8) self.ql.log.debug('Setting up DriverEntry args') self.ql.stop_execution_pattern = 0xDEADC0DE @@ -666,11 +666,11 @@ def load(self): elif self.ql.code: self.filepath = b"" if self.ql.archtype == QL_ARCH.X86: - self.ql.reg.esp = self.stack_address + 0x3000 - self.ql.reg.ebp = self.ql.reg.esp + self.ql.arch.regs.esp = self.stack_address + 0x3000 + self.ql.arch.regs.ebp = self.ql.arch.regs.esp elif self.ql.archtype == QL_ARCH.X8664: - self.ql.reg.rsp = self.stack_address + 0x3000 - self.ql.reg.rbp = self.ql.reg.rsp + self.ql.arch.regs.rsp = self.stack_address + 0x3000 + self.ql.arch.regs.rbp = self.ql.arch.regs.rsp # load shellcode in self.ql.mem.map(self.entry_point, self.ql.os.code_ram_size, info="[shellcode_base]") @@ -687,4 +687,4 @@ def load(self): # move entry_point to ql.os self.ql.os.entry_point = self.entry_point - self.init_sp = self.ql.reg.arch_sp + self.init_sp = self.ql.arch.regs.arch_sp diff --git a/qiling/loader/pe_uefi.py b/qiling/loader/pe_uefi.py index 7bcaec500..e339199ec 100644 --- a/qiling/loader/pe_uefi.py +++ b/qiling/loader/pe_uefi.py @@ -192,8 +192,8 @@ def execute_module(self, path: str, image_base: int, entry_point: int, context: self.ql.os.heap = context.heap # set stack and frame pointers - self.ql.reg.rsp = context.top_of_stack - self.ql.reg.rbp = context.top_of_stack + self.ql.arch.regs.rsp = context.top_of_stack + self.ql.arch.regs.rbp = context.top_of_stack self.ql.os.fcall.call_native(entry_point, ( (POINTER, ImageHandle), diff --git a/qiling/os/dos/dos.py b/qiling/os/dos/dos.py index 170768ef4..40cf5da17 100644 --- a/qiling/os/dos/dos.py +++ b/qiling/os/dos/dos.py @@ -56,10 +56,10 @@ def __del__(self): curses.endwin() def set_flag_value(self, fl: Flags, val: int) -> None: - self.ql.reg.ef = self.ql.reg.ef & (~fl) | (fl * val) + self.ql.arch.regs.ef = self.ql.arch.regs.ef & (~fl) | (fl * val) def test_flags(self, fl): - return self.ql.reg.ef & fl == fl + return self.ql.arch.regs.ef & fl == fl def set_cf(self): self.set_flag_value(Flags.CF, 0b1) @@ -76,7 +76,7 @@ def clear_zf(self): def hook_syscall(self): def cb(ql: Qiling, intno: int): - ah = ql.reg.ah + ah = ql.arch.regs.ah intinfo = (intno, ah) func = self.user_defined_api[QL_INTERCEPT.CALL].get(intinfo) or handlers.get(intno) diff --git a/qiling/os/dos/interrupts/int10.py b/qiling/os/dos/interrupts/int10.py index 837ddad6a..9b887f0b2 100644 --- a/qiling/os/dos/interrupts/int10.py +++ b/qiling/os/dos/interrupts/int10.py @@ -63,7 +63,7 @@ def __leaf_00(ql: Qiling): except: pass - al = ql.reg.al + al = ql.arch.regs.al resolution = { 0x00 : (25, 40), @@ -128,31 +128,31 @@ def __leaf_00(ql: Qiling): def __leaf_01(ql: Qiling): # limited support - ch = ql.reg.ch + ch = ql.arch.regs.ch if (ch & 0x20): curses.curs_set(0) def __leaf_02(ql: Qiling): # page number ignored - dh = ql.reg.dh # row - dl = ql.reg.dl # column + dh = ql.arch.regs.dh # row + dl = ql.arch.regs.dl # column ql.os.stdscr.move(dh, dl) def __leaf_05(ql: Qiling): # No idea how to implement, do nothing here. - ql.reg.al = 0 + ql.arch.regs.al = 0 def __leaf_06(ql: Qiling): stdscr = ql.os.stdscr - al = ql.reg.al # lines to scroll - ch = ql.reg.ch # row of upper-left cornner - cl = ql.reg.cl # column of upper-left corner - dh = ql.reg.dh # row of lower right corner - dl = ql.reg.dl # column of lower righ corner - bh = ql.reg.bh # color + al = ql.arch.regs.al # lines to scroll + ch = ql.arch.regs.ch # row of upper-left cornner + cl = ql.arch.regs.cl # column of upper-left corner + dh = ql.arch.regs.dh # row of lower right corner + dl = ql.arch.regs.dl # column of lower righ corner + bh = ql.arch.regs.bh # color y, x = stdscr.getmaxyx() cy, cx = stdscr.getyx() @@ -193,13 +193,13 @@ def __leaf_08(ql: Qiling): stdscr = ql.os.stdscr if stdscr is None: - ql.reg.ax = 0x0720 + ql.arch.regs.ax = 0x0720 else: cy, cx = stdscr.getyx() inch = stdscr.inch(cy, cx) attr = inch & curses.A_COLOR ch = inch & 0xFF - ql.reg.al = ch + ql.arch.regs.al = ch pair_number = curses.pair_number(attr) fg, bg = curses.pair_content(pair_number) @@ -209,10 +209,10 @@ def __leaf_08(ql: Qiling): if attr & curses.A_BLINK: orig_bg |= 0b1000 - ql.reg.ah = ((orig_bg << 4) & orig_fg) + ql.arch.regs.ah = ((orig_bg << 4) & orig_fg) def __leaf_0e(ql: Qiling): - al = ql.reg.al + al = ql.arch.regs.al ql.log.debug(f'echo: {al:02x} -> {curses.ascii.unctrl(al)}') @@ -247,7 +247,7 @@ def __leaf_0e(ql: Qiling): # https://stanislavs.org/helppc/idx_interrupt.html # implemented by curses def handler(ql: Qiling): - ah = ql.reg.ah + ah = ql.arch.regs.ah leaffunc = { 0x00 : __leaf_00, diff --git a/qiling/os/dos/interrupts/int13.py b/qiling/os/dos/interrupts/int13.py index dc99e6ebe..1714111ab 100644 --- a/qiling/os/dos/interrupts/int13.py +++ b/qiling/os/dos/interrupts/int13.py @@ -45,43 +45,43 @@ def __leaf_00(ql: Qiling): ql.os.clear_cf() def __leaf_02(ql: Qiling): - idx = ql.reg.dl + idx = ql.arch.regs.dl if not ql.os.fs_mapper.has_mapping(idx): ql.log.warning(f'Warning: No such disk: {idx:#x}') - ql.reg.ah = DiskError.BadCommand.value + ql.arch.regs.ah = DiskError.BadCommand.value ql.os.set_cf() return - cylinder = ((ql.reg.cx & 0xff00) >> 8) | ((ql.reg.cx & 0xC0) << 2) - head = ql.reg.dh - sector = ql.reg.cx & 63 - cnt = ql.reg.al + cylinder = ((ql.arch.regs.cx & 0xff00) >> 8) | ((ql.arch.regs.cx & 0xC0) << 2) + head = ql.arch.regs.dh + sector = ql.arch.regs.cx & 63 + cnt = ql.arch.regs.al disk = ql.os.fs_mapper.open(idx, None) content = disk.read_chs(cylinder, head, sector, cnt) - ql.mem.write(utils.linaddr(ql.reg.es, ql.reg.bx), content) + ql.mem.write(utils.linaddr(ql.arch.regs.es, ql.arch.regs.bx), content) ql.os.clear_cf() - ql.reg.ah = 0 - ql.reg.al = sector + ql.arch.regs.ah = 0 + ql.arch.regs.al = sector # @see: https://stanislavs.org/helppc/int_13-8.html def __leaf_08(ql: Qiling): - idx = ql.reg.dl + idx = ql.arch.regs.dl if not ql.os.fs_mapper.has_mapping(idx): ql.log.warning(f'Warning: No such disk: {idx:#x}') - ql.reg.ah = DiskError.BadCommand.value + ql.arch.regs.ah = DiskError.BadCommand.value ql.os.set_cf() return disk = ql.os.fs_mapper.open(idx, None) - ql.reg.dl = ql.os.fs_mapper.mapping_count() - ql.reg.dh = disk.n_heads - 1 - ql.reg.bl = 0x4 - ql.reg.di = 0 - ql.reg.ds = 0 + ql.arch.regs.dl = ql.os.fs_mapper.mapping_count() + ql.arch.regs.dh = disk.n_heads - 1 + ql.arch.regs.bl = 0x4 + ql.arch.regs.di = 0 + ql.arch.regs.ds = 0 n_sectors = min(disk.n_sectors, 63) n_cylinders = min(disk.n_cylinders, 1023) @@ -90,28 +90,28 @@ def __leaf_08(ql: Qiling): cx |= ((n_cylinders & 0b11) << 6) cx |= (((n_cylinders & 0b1111111100) >> 2) << 8) - ql.reg.cx = cx - ql.reg.ah = 0 + ql.arch.regs.cx = cx + ql.arch.regs.ah = 0 ql.os.clear_cf() def __leaf_41(ql: Qiling): - ql.reg.ah = 0 + ql.arch.regs.ah = 0 # 1 -> Device Access using the packet structure. # 2 -> Drive locking and ejecting. # 4 -> Enhanced Disk Drive Support. - ql.reg.bx = 0xaa55 - ql.reg.cx = 7 + ql.arch.regs.bx = 0xaa55 + ql.arch.regs.cx = 7 def __leaf_42(ql: Qiling): - idx = ql.reg.dl + idx = ql.arch.regs.dl if not ql.os.fs_mapper.has_mapping(idx): ql.log.warning(f'Warning: No such disk: {idx:#x}') - ql.reg.ah = DiskError.BadCommand.value + ql.arch.regs.ah = DiskError.BadCommand.value ql.os.set_cf() return - dapbs = ql.mem.read(utils.linaddr(ql.reg.ds, ql.reg.si), 16) + dapbs = ql.mem.read(utils.linaddr(ql.arch.regs.ds, ql.arch.regs.si), 16) _, _, cnt, offset, segment, lba = parse_dap(dapbs) ql.log.info(f'Reading {cnt} sectors from disk {idx:#x} with LBA {lba}') @@ -120,18 +120,18 @@ def __leaf_42(ql: Qiling): ql.mem.write(utils.linaddr(segment, offset), content) ql.os.clear_cf() - ql.reg.ah = 0 + ql.arch.regs.ah = 0 def __leaf_43(ql: Qiling): - idx = ql.reg.dl + idx = ql.arch.regs.dl if not ql.os.fs_mapper.has_mapping(idx): ql.log.info(f"Warning: No such disk: {hex(idx)}") - ql.reg.ah = DiskError.BadCommand.value + ql.arch.regs.ah = DiskError.BadCommand.value ql.os.set_cf() return - dapbs = ql.mem.read(utils.linaddr(ql.reg.ds, ql.reg.si), 16) + dapbs = ql.mem.read(utils.linaddr(ql.arch.regs.ds, ql.arch.regs.si), 16) _, _, cnt, offset, segment, lba = parse_dap(dapbs) ql.log.info(f'Writing {cnt} sectors to disk {idx:#x} with LBA {lba}') @@ -140,11 +140,11 @@ def __leaf_43(ql: Qiling): disk.write_sectors(lba, cnt, buffer) ql.os.clear_cf() - ql.reg.ah = 0 + ql.arch.regs.ah = 0 # @see: https://en.wikipedia.org/wiki/INT_13H def handler(ql: Qiling): - ah = ql.reg.ah + ah = ql.arch.regs.ah leaffunc = { 0x00 : __leaf_00, diff --git a/qiling/os/dos/interrupts/int15.py b/qiling/os/dos/interrupts/int15.py index e237c5f74..87110d28f 100644 --- a/qiling/os/dos/interrupts/int15.py +++ b/qiling/os/dos/interrupts/int15.py @@ -16,23 +16,23 @@ def __leaf_01(ql: Qiling): pass def __leaf_53(ql: Qiling): - al = ql.reg.al + al = ql.arch.regs.al if al == 0x01: ql.os.clear_cf() elif al == 0x0e: - ql.reg.ax = 0x0102 + ql.arch.regs.ax = 0x0102 ql.os.clear_cf() elif al == 0x07: - if (ql.reg.bx == 1) and (ql.reg.cx == 3): + if (ql.arch.regs.bx == 1) and (ql.arch.regs.cx == 3): ql.log.info("Emulation Stop") ql.emu_stop() else: raise NotImplementedError() def __leaf_86(ql: Qiling): - dx = ql.reg.dx - cx = ql.reg.cx + dx = ql.arch.regs.dx + cx = ql.arch.regs.cx full_secs = ((cx << 16) + dx) / 1000000 ql.log.info(f"Goint to sleep for {full_secs} seconds") @@ -41,10 +41,10 @@ def __leaf_86(ql: Qiling): # Note: Since we are in a single thread environment, we assume # that no one will wait at the same time. ql.os.clear_cf() - ql.reg.ah = 0x80 + ql.arch.regs.ah = 0x80 def handler(ql: Qiling): - ah = ql.reg.ah + ah = ql.arch.regs.ah leaffunc = { 0x00 : __leaf_00, diff --git a/qiling/os/dos/interrupts/int16.py b/qiling/os/dos/interrupts/int16.py index 842784122..215d20c2f 100644 --- a/qiling/os/dos/interrupts/int16.py +++ b/qiling/os/dos/interrupts/int16.py @@ -136,10 +136,10 @@ def __leaf_00(ql: Qiling): key = parse_key(ql.os.stdscr.getch()) ql.log.debug(f"Get key: {hex(key)}") if curses.ascii.isascii(key): - ql.reg.al = key + ql.arch.regs.al = key else: - ql.reg.al = 0 - ql.reg.ah = get_scan_code(key) + ql.arch.regs.al = 0 + ql.arch.regs.ah = get_scan_code(key) curses.nl() def __leaf_01(ql: Qiling): @@ -150,11 +150,11 @@ def __leaf_01(ql: Qiling): if key == -1: ql.os.set_zf() - ql.reg.ax = 0 + ql.arch.regs.ax = 0 else: ql.log.debug(f"Has key: {hex(key)} ({curses.ascii.unctrl(key)})") - ql.reg.al = key - ql.reg.ah = get_scan_code(key) + ql.arch.regs.al = key + ql.arch.regs.ah = get_scan_code(key) ql.os.clear_zf() # Buffer shouldn't be removed in this interrupt. curses.ungetch(key) @@ -163,7 +163,7 @@ def __leaf_01(ql: Qiling): curses.nl() def handler(ql: Qiling): - ah = ql.reg.ah + ah = ql.arch.regs.ah leaffunc = { 0x00 : __leaf_00, diff --git a/qiling/os/dos/interrupts/int19.py b/qiling/os/dos/interrupts/int19.py index fe31a3fc3..4513e0200 100644 --- a/qiling/os/dos/interrupts/int19.py +++ b/qiling/os/dos/interrupts/int19.py @@ -7,7 +7,7 @@ def handler(ql: Qiling): # Note: Memory is not cleaned. - dl = ql.reg.dl + dl = ql.arch.regs.dl if ql.os.fs_mapper.has_mapping(dl): disk = ql.os.fs_mapper.open(dl, None) @@ -19,5 +19,5 @@ def handler(ql: Qiling): ql.mem.write(0x7C00, mbr) - ql.reg.cs = 0x07C0 - ql.reg.ip = 0x0000 + ql.arch.regs.cs = 0x07C0 + ql.arch.regs.ip = 0x0000 diff --git a/qiling/os/dos/interrupts/int1a.py b/qiling/os/dos/interrupts/int1a.py index ed301e031..4f7edc1f9 100644 --- a/qiling/os/dos/interrupts/int1a.py +++ b/qiling/os/dos/interrupts/int1a.py @@ -13,13 +13,13 @@ def __set_elapsed_ticks(ql: Qiling): now = datetime.now() ticks = int((now - ql.os.start_time).total_seconds() * ql.os.ticks_per_second) - ql.reg.cx = (ticks >> 16) & 0xffff - ql.reg.dx = (ticks >> 0) & 0xffff + ql.arch.regs.cx = (ticks >> 16) & 0xffff + ql.arch.regs.dx = (ticks >> 0) & 0xffff def __leaf_00(ql: Qiling): __set_elapsed_ticks(ql) - ql.reg.al = 0 + ql.arch.regs.al = 0 def __leaf_01(ql: Qiling): __set_elapsed_ticks(ql) @@ -27,10 +27,10 @@ def __leaf_01(ql: Qiling): def __leaf_02_03(ql: Qiling): now = datetime.now() - ql.reg.ch = utils.BIN2BCD(now.hour) - ql.reg.cl = utils.BIN2BCD(now.minute) - ql.reg.dh = utils.BIN2BCD(now.second) - ql.reg.dl = 0 + ql.arch.regs.ch = utils.BIN2BCD(now.hour) + ql.arch.regs.cl = utils.BIN2BCD(now.minute) + ql.arch.regs.dh = utils.BIN2BCD(now.second) + ql.arch.regs.dl = 0 ql.os.clear_cf() @@ -38,10 +38,10 @@ def __leaf_04_05(ql: Qiling): now = datetime.now() # See https://sites.google.com/site/liangweiqiang/Home/e5006/e5006classnote/jumptiming/int1ahclockservice - ql.reg.ch = utils.BIN2BCD((now.year - 1) // 100) - ql.reg.cl = utils.BIN2BCD(now.year % 100) - ql.reg.dh = utils.BIN2BCD(now.month) - ql.reg.dl = utils.BIN2BCD(now.day) + ql.arch.regs.ch = utils.BIN2BCD((now.year - 1) // 100) + ql.arch.regs.cl = utils.BIN2BCD(now.year % 100) + ql.arch.regs.dh = utils.BIN2BCD(now.month) + ql.arch.regs.dl = utils.BIN2BCD(now.day) ql.os.clear_cf() @@ -55,13 +55,13 @@ def __leaf_08(ql: Qiling): def __leaf_0a(ql: Qiling): now = datetime.now() - ql.reg.cx = (now - datetime(1980, 1, 1)).days + ql.arch.regs.cx = (now - datetime(1980, 1, 1)).days def __leaf_0b(ql: Qiling): pass def handler(ql: Qiling): - ah = ql.reg.ah + ah = ql.arch.regs.ah leaffunc = { 0x00 : __leaf_00, diff --git a/qiling/os/dos/interrupts/int20.py b/qiling/os/dos/interrupts/int20.py index 59480d51d..14bc51fdb 100644 --- a/qiling/os/dos/interrupts/int20.py +++ b/qiling/os/dos/interrupts/int20.py @@ -9,7 +9,7 @@ def __leaf_13(self): pass def handler(ql: Qiling): - ah = ql.reg.ah + ah = ql.arch.regs.ah leaffunc = { 0x13 : __leaf_13 diff --git a/qiling/os/dos/interrupts/int21.py b/qiling/os/dos/interrupts/int21.py index 153bcce26..a87094941 100644 --- a/qiling/os/dos/interrupts/int21.py +++ b/qiling/os/dos/interrupts/int21.py @@ -16,8 +16,8 @@ def __leaf_4c(ql: Qiling): # write a character to screen def __leaf_02(ql: Qiling): - ch = ql.reg.dl - ql.reg.al = ch + ch = ql.arch.regs.dl + ql.arch.regs.al = ch print(f'{ch:c}', end='') @@ -39,7 +39,7 @@ def __leaf_26(ql: Qiling): # get dos version def __leaf_30(ql: Qiling): - ql.reg.ax = ql.os.dos_ver + ql.arch.regs.ax = ql.os.dos_ver # get or set ctrl-break def __leaf_33(ql: Qiling): @@ -56,7 +56,7 @@ def __leaf_3c(ql: Qiling): fpath = ql.os.path.transform_to_real_path(fname) ql.os.handles[ql.os.handle_next] = open(fpath, "wb") - ql.reg.ax = ql.os.handle_next + ql.arch.regs.ax = ql.os.handle_next ql.os.handle_next += 1 ql.os.clear_cf() @@ -66,13 +66,13 @@ def __leaf_3d(ql: Qiling): fpath = ql.os.path.transform_to_real_path(fname) ql.os.handles[ql.os.handle_next] = open(fpath, "rb") - ql.reg.ax = ql.os.handle_next + ql.arch.regs.ax = ql.os.handle_next ql.os.handle_next += 1 ql.os.clear_cf() # close file def __leaf_3e(ql: Qiling): - hd = ql.reg.bx + hd = ql.arch.regs.bx if hd in ql.os.handles: f = ql.os.handles.pop(hd) @@ -80,39 +80,39 @@ def __leaf_3e(ql: Qiling): ql.os.clear_cf() else: - ql.reg.ax = 0x06 + ql.arch.regs.ax = 0x06 ql.os.set_cf() # read from file def __leaf_3f(ql: Qiling): - hd = ql.reg.bx + hd = ql.arch.regs.bx if hd in ql.os.handles: f = ql.os.handles[hd] - buffer = utils.linaddr(ql.reg.ds, ql.reg.dx) - sz = ql.reg.cx + buffer = utils.linaddr(ql.arch.regs.ds, ql.arch.regs.dx) + sz = ql.arch.regs.cx rd = f.read(sz) ql.mem.write(buffer, rd) ql.os.clear_cf() - ql.reg.ax = len(rd) + ql.arch.regs.ax = len(rd) else: - ql.reg.ax = 0x06 + ql.arch.regs.ax = 0x06 ql.os.set_cf() # write to file def __leaf_40(ql: Qiling): - hd = ql.reg.bx + hd = ql.arch.regs.bx if hd in ql.os.handles: f = ql.os.handles[hd] - buffer = utils.linaddr(ql.reg.ds, ql.reg.dx) - sz = ql.reg.cx + buffer = utils.linaddr(ql.arch.regs.ds, ql.arch.regs.dx) + sz = ql.arch.regs.cx rd = ql.mem.read(buffer, sz) f.write(bytes(rd)) ql.os.clear_cf() - ql.reg.ax = len(rd) + ql.arch.regs.ax = len(rd) else: - ql.reg.ax = 0x06 + ql.arch.regs.ax = 0x06 ql.os.set_cf() # delete file @@ -124,15 +124,15 @@ def __leaf_41(ql: Qiling): os.remove(fpath) ql.os.clear_cf() except OSError: - ql.reg.ax = 0x05 + ql.arch.regs.ax = 0x05 ql.os.set_cf() def __leaf_43(ql: Qiling): - ql.reg.cx = 0xffff + ql.arch.regs.cx = 0xffff ql.os.clear_cf() def handler(ql: Qiling): - ah = ql.reg.ah + ah = ql.arch.regs.ah leaffunc = { 0x02 : __leaf_02, diff --git a/qiling/os/dos/utils.py b/qiling/os/dos/utils.py index 0d51dd3f9..fe4d44740 100644 --- a/qiling/os/dos/utils.py +++ b/qiling/os/dos/utils.py @@ -53,6 +53,6 @@ def read_dos_string(ql: Qiling, address: int): return ba.decode('ascii') def read_dos_string_from_ds_dx(ql: Qiling): - address = linaddr(ql.reg.ds, ql.reg.dx) + address = linaddr(ql.arch.regs.ds, ql.arch.regs.dx) return read_dos_string(ql, address) diff --git a/qiling/os/fcall.py b/qiling/os/fcall.py index f8aa1960e..3ae7a563a 100644 --- a/qiling/os/fcall.py +++ b/qiling/os/fcall.py @@ -131,7 +131,7 @@ def call(self, func: CallHook, proto: Mapping[str, Any], params: Mapping[str, An """ ql = self.ql - pc = ql.reg.arch_pc + pc = ql.arch.regs.arch_pc # if set, fire up the on-enter hook and let it override original args set if hook_onenter: @@ -194,4 +194,4 @@ def call_native(self, addr: int, args: Sequence[Tuple[Any, int]], ret: Optional[ self.cc.setReturnAddress(ret) # call - self.ql.reg.arch_pc = addr + self.ql.arch.regs.arch_pc = addr diff --git a/qiling/os/freebsd/syscall.py b/qiling/os/freebsd/syscall.py index 46d9624f0..31e50827b 100644 --- a/qiling/os/freebsd/syscall.py +++ b/qiling/os/freebsd/syscall.py @@ -46,8 +46,8 @@ def ql_syscall_sysarch(ql, op, parms, *args, **kw): #ql.mem.map(ql.GS_SEGMENT_ADDR, ql.GS_SEGMENT_SIZE) - #ql.reg.msr(GSMSR, ql.GS_SEGMENT_ADDR) - ql.reg.msr(FSMSR, parms) + #ql.arch.regs.msr(GSMSR, ql.GS_SEGMENT_ADDR) + ql.arch.regs.msr(FSMSR, parms) #op_buf = ql.pack32(op) #ql.mem.write(parms, op_buf) diff --git a/qiling/os/linux/function_hook.py b/qiling/os/linux/function_hook.py index 608a02e2f..e0b9bcff8 100644 --- a/qiling/os/linux/function_hook.py +++ b/qiling/os/linux/function_hook.py @@ -69,23 +69,23 @@ def add_hook(self, cb, intercept, userdata): def get_ret_pc(self): # ARM if self.ql.archtype== QL_ARCH.ARM: - return self.ql.reg.lr + return self.ql.arch.regs.lr # MIPS32 elif self.ql.archtype== QL_ARCH.MIPS: - return self.ql.reg.ra + return self.ql.arch.regs.ra # ARM64 elif self.ql.archtype== QL_ARCH.ARM64: - return self.ql.reg.x30 + return self.ql.arch.regs.x30 # X86 elif self.ql.archtype== QL_ARCH.X86: - return self.ql.unpack(self.ql.mem.read(self.ql.reg.esp, self.ql.pointersize)) + return self.ql.unpack(self.ql.mem.read(self.ql.arch.regs.esp, self.ql.pointersize)) # X8664 elif self.ql.archtype== QL_ARCH.X8664: - return self.ql.unpack(self.ql.mem.read(self.ql.reg.rsp, self.ql.pointersize)) + return self.ql.unpack(self.ql.mem.read(self.ql.arch.regs.rsp, self.ql.pointersize)) else: raise @@ -104,40 +104,40 @@ def context_fixup(self): # X86 elif self.ql.archtype== QL_ARCH.X86: - self.ql.reg.esp = self.ql.reg.esp + self.ql.pointersize + self.ql.arch.regs.esp = self.ql.arch.regs.esp + self.ql.pointersize # X8664 elif self.ql.archtype== QL_ARCH.X8664: - self.ql.reg.rsp = self.ql.reg.rsp + self.ql.pointersize + self.ql.arch.regs.rsp = self.ql.arch.regs.rsp + self.ql.pointersize else: raise def set_ret(self, addr): # ARM if self.ql.archtype== QL_ARCH.ARM: - self.ql.reg.lr = addr + self.ql.arch.regs.lr = addr # MIPS32 elif self.ql.archtype== QL_ARCH.MIPS: - self.ql.reg.ra = addr + self.ql.arch.regs.ra = addr # ARM64 elif self.ql.archtype== QL_ARCH.ARM64: - self.ql.mem.write(self.ql.reg.sp, self.ql.pack(addr)) + self.ql.mem.write(self.ql.arch.regs.sp, self.ql.pack(addr)) # X86 elif self.ql.archtype== QL_ARCH.X86: - self.ql.mem.write(self.ql.reg.esp, self.ql.pack(addr)) + self.ql.mem.write(self.ql.arch.regs.esp, self.ql.pack(addr)) # X8664 elif self.ql.archtype== QL_ARCH.X8664: - self.ql.mem.write(self.ql.reg.rsp, self.ql.pack(addr)) + self.ql.mem.write(self.ql.arch.regs.rsp, self.ql.pack(addr)) else: raise def call_enter(self): # if self.ql.archtype == QL_ARCH.ARM or self.ql.archtype == QL_ARCH.ARM64: - # self.ql.reg.arch_pc = self.ql.reg.arch_pc + 4 + # self.ql.arch.regs.arch_pc = self.ql.arch.regs.arch_pc + 4 next_pc = self.ql.unpack(self.ql.mem.read(self.hook_data_ptr, self.ql.pointersize)) self.ret_pc = self.get_ret_pc() @@ -168,7 +168,7 @@ def call_enter(self): cb(self.ql, userdata) else: self.set_ret(self.exit_addr) - self.ql.reg.arch_pc = next_pc + self.ql.arch.regs.arch_pc = next_pc else: self.context_fixup() @@ -176,29 +176,29 @@ def call_enter(self): def ret(self): # ARM if self.ql.archtype== QL_ARCH.ARM: - self.ql.reg.arch_pc = self.ret_pc + self.ql.arch.regs.arch_pc = self.ret_pc # MIPS32 elif self.ql.archtype== QL_ARCH.MIPS: - self.ql.reg.arch_pc = self.ret_pc + self.ql.arch.regs.arch_pc = self.ret_pc # ARM64 elif self.ql.archtype== QL_ARCH.ARM64: - self.ql.reg.arch_pc = self.ret_pc + self.ql.arch.regs.arch_pc = self.ret_pc # X86 elif self.ql.archtype== QL_ARCH.X86: - self.ql.reg.arch_pc = self.ret_pc + self.ql.arch.regs.arch_pc = self.ret_pc # X8664 elif self.ql.archtype== QL_ARCH.X8664: - self.ql.reg.arch_pc = self.ret_pc + self.ql.arch.regs.arch_pc = self.ret_pc else: raise def call_exit(self): # if self.ql.archtype == QL_ARCH.ARM or self.ql.archtype == QL_ARCH.ARM64: - # self.ql.reg.arch_pc = self.ql.reg.arch_pc + 4 + # self.ql.arch.regs.arch_pc = self.ql.arch.regs.arch_pc + 4 onexit_cb = None onexit_userdata = None @@ -255,7 +255,7 @@ def __init__(self, ql, fucname, got, gotidx, load_base): self.gotidx = gotidx def _hook_fuc_enter(self, ql): - self.ql.reg.t9 = self.ql.unpack(self.ql.mem.read(self.hook_data_ptr, self.ql.pointersize)) + self.ql.arch.regs.t9 = self.ql.unpack(self.ql.mem.read(self.hook_data_ptr, self.ql.pointersize)) self.call_enter() def _hook_fuc_exit(self, ql): @@ -902,7 +902,7 @@ def show_dynsym_name(self, s, e): self.ql.log.debug('dynsym name ' + str(rel_name)) def _hook_int(self, ql, intno): - idx = (self.ql.reg.arch_pc - self.hook_mem) // 0x10 + idx = (self.ql.arch.regs.arch_pc - self.hook_mem) // 0x10 if idx not in self.use_list.keys(): raise diff --git a/qiling/os/linux/syscall.py b/qiling/os/linux/syscall.py index 840c38ce9..86c3b13e8 100644 --- a/qiling/os/linux/syscall.py +++ b/qiling/os/linux/syscall.py @@ -55,10 +55,10 @@ def ql_syscall_set_thread_area(ql: Qiling, u_info_addr, *args, **kw): elif ql.archtype == QL_ARCH.MIPS: CONFIG3_ULR = (1 << 13) - ql.reg.cp0_config3 = CONFIG3_ULR - ql.reg.cp0_userlocal = u_info_addr - ql.reg.v0 = 0 - ql.reg.a3 = 0 + ql.arch.regs.cp0_config3 = CONFIG3_ULR + ql.arch.regs.cp0_userlocal = u_info_addr + ql.arch.regs.v0 = 0 + ql.arch.regs.a3 = 0 ql.log.debug ("set_thread_area(0x%x)" % u_info_addr) return 0 @@ -66,9 +66,9 @@ def ql_syscall_set_thread_area(ql: Qiling, u_info_addr, *args, **kw): def ql_syscall_set_tls(ql, address, *args, **kw): if ql.archtype == QL_ARCH.ARM: - ql.reg.c13_c0_3 = address + ql.arch.regs.c13_c0_3 = address ql.mem.write(ql.arch.arm_get_tls_addr + 12, ql.pack32(address)) - ql.reg.r0 = address + ql.arch.regs.r0 = address ql.log.debug("settls(0x%x)" % address) def ql_syscall_clock_gettime(ql, clock_gettime_clock_id, clock_gettime_timespec, *args, **kw): diff --git a/qiling/os/linux/thread.py b/qiling/os/linux/thread.py index b4a7f273d..5e3cdc39d 100644 --- a/qiling/os/linux/thread.py +++ b/qiling/os/linux/thread.py @@ -222,7 +222,7 @@ def _run(self): # In this context, we do: # - Call gevent functions to switch threads. # - Forward blocking syscalls to gevent. - self.ql.reg.arch_pc = self.start_address + self.ql.arch.regs.arch_pc = self.start_address if not self._saved_context: self.save() @@ -235,7 +235,7 @@ def _run(self): self.restore() # Sanity check - if self.ql.reg.arch_pc == self.exit_point: + if self.ql.arch.regs.arch_pc == self.exit_point: self.ql.log.warning(f"Nothing to do but still get scheduled!") # Run and log the run event @@ -250,7 +250,7 @@ def _run(self): self.ql.os.emu_error() self.ql.log.exception("") raise e - self.ql.log.debug(f"Suspended at {hex(self.ql.reg.arch_pc)}") + self.ql.log.debug(f"Suspended at {hex(self.ql.arch.regs.arch_pc)}") self.save() # Note that this callback may be set by UC callbacks. @@ -261,7 +261,7 @@ def _run(self): self.ql.log.debug(f"Call sched_cb: {self.sched_cb}") self.sched_cb(self) - if self.status == THREAD_STATUS_TERMINATED or self.ql.reg.arch_pc == self.exit_point: + if self.status == THREAD_STATUS_TERMINATED or self.ql.arch.regs.arch_pc == self.exit_point: break self._on_stop() @@ -312,7 +312,7 @@ def set_start_address(self, addr): # We can't modify UcContext directly. old_context = self.ql.arch.context_save() self.restore_context() - self.ql.reg.arch_pc = addr + self.ql.arch.regs.arch_pc = addr self.save_context() self.ql.arch.context_restore(old_context) @@ -391,17 +391,17 @@ def set_thread_tls(self, tls_addr): self.tls = bytes(self.ql.os.gdtm.get_gdt_buf(12, 14 + 1)) self.ql.os.gdtm.set_gdt_buf(12, 14 + 1, old_tls) - self.ql.log.debug(f"Set tls to index={hex(index)} base={hex(base)} limit={hex(limit)} fs={hex(self.ql.reg.fs)} gs={hex(self.ql.reg.gs)} gdt_buf={self.tls}") + self.ql.log.debug(f"Set tls to index={hex(index)} base={hex(base)} limit={hex(limit)} fs={hex(self.ql.arch.regs.fs)} gs={hex(self.ql.arch.regs.gs)} gdt_buf={self.tls}") def save(self): self.save_context() self.tls = bytes(self.ql.os.gdtm.get_gdt_buf(12, 14 + 1)) - self.ql.log.debug(f"Saved context. fs={hex(self.ql.reg.fs)} gs={hex(self.ql.reg.gs)} gdt_buf={self.tls}") + self.ql.log.debug(f"Saved context. fs={hex(self.ql.arch.regs.fs)} gs={hex(self.ql.arch.regs.gs)} gdt_buf={self.tls}") def restore(self): self.restore_context() self.ql.os.gdtm.set_gdt_buf(12, 14 + 1, self.tls) - self.ql.log.debug(f"Restored context. fs={hex(self.ql.reg.fs)} gs={hex(self.ql.reg.gs)} gdt_buf={self.tls}") + self.ql.log.debug(f"Restored context. fs={hex(self.ql.arch.regs.fs)} gs={hex(self.ql.arch.regs.gs)} gdt_buf={self.tls}") def clone(self): new_thread = super(QlLinuxX86Thread, self).clone() @@ -416,7 +416,7 @@ def __init__(self, ql, start_address, exit_point, context = None, set_child_tid_ def set_thread_tls(self, tls_addr): self.tls = tls_addr - self.ql.reg.msr(FSMSR, self.tls) + self.ql.arch.regs.msr(FSMSR, self.tls) self.ql.log.debug(f"Set fsbase to {hex(tls_addr)} for {str(self)}") # Some notes: @@ -424,13 +424,13 @@ def set_thread_tls(self, tls_addr): # - https://stackoverflow.com/questions/11497563/detail-about-msr-gs-base-in-linux-x86-64 def save(self): self.save_context() - self.tls = self.ql.reg.msr(FSMSR) - self.ql.log.debug(f"Saved context: fs={hex(self.ql.reg.fsbase)} tls={hex(self.tls)}") + self.tls = self.ql.arch.regs.msr(FSMSR) + self.ql.log.debug(f"Saved context: fs={hex(self.ql.arch.regs.fsbase)} tls={hex(self.tls)}") def restore(self): self.restore_context() self.set_thread_tls(self.tls) - self.ql.log.debug(f"Restored context: fs={hex(self.ql.reg.fsbase)} tls={hex(self.tls)}") + self.ql.log.debug(f"Restored context: fs={hex(self.ql.arch.regs.fsbase)} tls={hex(self.tls)}") def clone(self): new_thread = super(QlLinuxX8664Thread, self).clone() @@ -447,19 +447,19 @@ def __init__(self, ql, start_address, exit_point, context = None, set_child_tid_ def set_thread_tls(self, tls_addr): self.tls = tls_addr CONFIG3_ULR = (1 << 13) - self.ql.reg.cp0_config3 = CONFIG3_ULR - self.ql.reg.cp0_userlocal = self.tls - self.ql.log.debug(f"Set cp0 to {hex(self.ql.reg.cp0_userlocal)}") + self.ql.arch.regs.cp0_config3 = CONFIG3_ULR + self.ql.arch.regs.cp0_userlocal = self.tls + self.ql.log.debug(f"Set cp0 to {hex(self.ql.arch.regs.cp0_userlocal)}") def save(self): self.save_context() - self.tls = self.ql.reg.cp0_userlocal - self.ql.log.debug(f"Saved context. cp0={hex(self.ql.reg.cp0_userlocal)}") + self.tls = self.ql.arch.regs.cp0_userlocal + self.ql.log.debug(f"Saved context. cp0={hex(self.ql.arch.regs.cp0_userlocal)}") def restore(self): self.restore_context() self.set_thread_tls(self.tls) - self.ql.log.debug(f"Restored context. cp0={hex(self.ql.reg.cp0_userlocal)}") + self.ql.log.debug(f"Restored context. cp0={hex(self.ql.arch.regs.cp0_userlocal)}") def clone(self): new_thread = super(QlLinuxMIPS32Thread, self).clone() @@ -475,19 +475,19 @@ def __init__(self, ql, start_address, exit_point, context = None, set_child_tid_ def set_thread_tls(self, tls_addr): self.tls = tls_addr - self.ql.reg.c13_c0_3 = self.tls - self.ql.log.debug(f"Set c13_c0_3 to {hex(self.ql.reg.c13_c0_3)}") + self.ql.arch.regs.c13_c0_3 = self.tls + self.ql.log.debug(f"Set c13_c0_3 to {hex(self.ql.arch.regs.c13_c0_3)}") def save(self): self.save_context() - self.tls = self.ql.reg.c13_c0_3 - self.ql.log.debug(f"Saved context. c13_c0_3={hex(self.ql.reg.c13_c0_3)}") + self.tls = self.ql.arch.regs.c13_c0_3 + self.ql.log.debug(f"Saved context. c13_c0_3={hex(self.ql.arch.regs.c13_c0_3)}") def restore(self): self.restore_context() self.set_thread_tls(self.tls) - self.ql.log.debug(f"Restored context. c13_c0_3={hex(self.ql.reg.c13_c0_3)}") + self.ql.log.debug(f"Restored context. c13_c0_3={hex(self.ql.arch.regs.c13_c0_3)}") def clone(self): new_thread = super(QlLinuxARMThread, self).clone() @@ -503,18 +503,18 @@ def __init__(self, ql, start_address, exit_point, context = None, set_child_tid_ def set_thread_tls(self, tls_addr): self.tls = tls_addr - self.ql.reg.tpidr_el0 = self.tls - self.ql.log.debug(f"Set tpidr_el0 to {hex(self.ql.reg.tpidr_el0)}") + self.ql.arch.regs.tpidr_el0 = self.tls + self.ql.log.debug(f"Set tpidr_el0 to {hex(self.ql.arch.regs.tpidr_el0)}") def save(self): self.save_context() - self.tls = self.ql.reg.tpidr_el0 - self.ql.log.debug(f"Saved context. tpidr_el0={hex(self.ql.reg.tpidr_el0)}") + self.tls = self.ql.arch.regs.tpidr_el0 + self.ql.log.debug(f"Saved context. tpidr_el0={hex(self.ql.arch.regs.tpidr_el0)}") def restore(self): self.restore_context() self.set_thread_tls(self.tls) - self.ql.log.debug(f"Restored context. tpidr_el0={hex(self.ql.reg.tpidr_el0)}") + self.ql.log.debug(f"Restored context. tpidr_el0={hex(self.ql.arch.regs.tpidr_el0)}") def clone(self): new_thread = super(QlLinuxARM64Thread, self).clone() @@ -571,8 +571,8 @@ def _prepare_lib_patch(self): self.cur_thread = self.main_thread self._clear_queued_msg() gevent.joinall([self.main_thread], raise_error=True) - if self.ql.reg.arch_pc != entry_address: - self.ql.log.error(f"{self.cur_thread} Expect {hex(self.ql.loader.elf_entry)} but get {hex(self.ql.reg.arch_pc)} when running loader.") + if self.ql.arch.regs.arch_pc != entry_address: + self.ql.log.error(f"{self.cur_thread} Expect {hex(self.ql.loader.elf_entry)} but get {hex(self.ql.arch.regs.arch_pc)} when running loader.") raise QlErrorExecutionStop('Dynamic library .init() failed!') self.ql.enable_lib_patch() self.ql.os.run_function_after_load() diff --git a/qiling/os/macos/events/macos.py b/qiling/os/macos/events/macos.py index 635f706e3..83145f6c0 100644 --- a/qiling/os/macos/events/macos.py +++ b/qiling/os/macos/events/macos.py @@ -269,10 +269,10 @@ def trigger(self): remains = 0 if len(params) <= 6: for idx, p in enumerate(params): - self.ql.reg.write(reg_list[idx], p) + self.ql.arch.regs.write(reg_list[idx], p) else: for idx, p in enumerate(params[:6]): - self.ql.reg.write(reg_list[idx], p) + self.ql.arch.regs.write(reg_list[idx], p) remains = len(params) - 6 for i in range(remains): self.ql.stack_push(params[6 + i]) @@ -298,9 +298,9 @@ def sysctlbyname(self, name, namelen, oldp, oldlenp, new, newlen): uap.newlen = newlen uap.updateToMem() - self.ql.reg.rdi = self.proc_find(0x1337).base - self.ql.reg.rsi = uap_addr # uap - self.ql.reg.rdx = 0 # unused retval + self.ql.arch.regs.rdi = self.proc_find(0x1337).base + self.ql.arch.regs.rsi = uap_addr # uap + self.ql.arch.regs.rdx = 0 # unused retval self.ql.os.savedrip=self.deadcode self.ql.run(self.ql.loader.kernel_extrn_symbols_detail[b"_sysctlbyname"]["n_value"]) @@ -645,9 +645,9 @@ def syscall(self, sysnum, params): sysent = self.ql.loader.kernel_local_symbols_detail[b"_sysent"]["n_value"] system_table = [sysent_t(self.ql, sysent + x * ctypes.sizeof(sysent_t)).loadFromMem() for x in range(nsyscall)] - self.ql.reg.write(UC_X86_REG_RAX, sysnum) + self.ql.arch.regs.write(UC_X86_REG_RAX, sysnum) reg_list = [UC_X86_REG_RDI, UC_X86_REG_RSI, UC_X86_REG_RDX, UC_X86_REG_R10, UC_X86_REG_R8, UC_X86_REG_R9] for idx, p in enumerate(params): - self.ql.reg.write(reg_list[idx], p) + self.ql.arch.regs.write(reg_list[idx], p) self.ql.os.savedrip=self.deadcode self.ql.run(begin=system_table[sysnum].sy_call.value) diff --git a/qiling/os/macos/macos.py b/qiling/os/macos/macos.py index 15fb449e2..2ec99daa8 100644 --- a/qiling/os/macos/macos.py +++ b/qiling/os/macos/macos.py @@ -54,59 +54,59 @@ def load_kext(self): self.ql.stack_push(0) self.savedrip=0xffffff8000a163bd self.ql.run(begin=self.ql.loader.kext_alloc) - self.kext_object = self.ql.reg.rax + self.kext_object = self.ql.arch.regs.rax self.ql.log.debug("Created kext object at 0x%x" % self.kext_object) - self.ql.reg.rdi = self.kext_object - self.ql.reg.rsi = 0 # NULL option + self.ql.arch.regs.rdi = self.kext_object + self.ql.arch.regs.rsi = 0 # NULL option self.savedrip=0xffffff8000a16020 self.ql.run(begin=self.ql.loader.kext_init) - if self.ql.reg.rax == 0: + if self.ql.arch.regs.rax == 0: self.ql.log.debug("Failed to initialize kext object") return self.ql.log.debug("Initialized kext object") - self.ql.reg.rdi = self.kext_object + self.ql.arch.regs.rdi = self.kext_object # FIXME Determine provider for kext - self.ql.reg.rsi = 0 # ? + self.ql.arch.regs.rsi = 0 # ? self.savedrip=0xffffff8000a16102 self.ql.run(begin=self.ql.loader.kext_attach) - if self.ql.reg.rax == 0: + if self.ql.arch.regs.rax == 0: self.ql.log.debug("Failed to attach kext object") return self.ql.log.debug("Attached kext object 1st time") - self.ql.reg.rdi = self.kext_object - self.ql.reg.rdi = 0 + self.ql.arch.regs.rdi = self.kext_object + self.ql.arch.regs.rdi = 0 # FIXME Determine provider for kext - self.ql.reg.rsi = 0 # ? + self.ql.arch.regs.rsi = 0 # ? tmp = self.heap.alloc(8) - self.ql.reg.rdx = tmp + self.ql.arch.regs.rdx = tmp self.savedrip=0xffffff8000a16184 self.ql.run(begin=self.ql.loader.kext_probe) self.heap.free(tmp) self.ql.log.debug("Probed kext object") - self.ql.reg.rdi = self.kext_object + self.ql.arch.regs.rdi = self.kext_object # FIXME Determine provider for kext - self.ql.reg.rsi = 0 # ? + self.ql.arch.regs.rsi = 0 # ? self.savedrip=0xffffff8000a16198 self.ql.run(begin=self.ql.loader.kext_detach) self.ql.log.debug("Detached kext object") - self.ql.reg.rdi = self.kext_object + self.ql.arch.regs.rdi = self.kext_object # FIXME Determine provider for kext - self.ql.reg.rsi = 0 # ? + self.ql.arch.regs.rsi = 0 # ? self.savedrip=0xffffff8000a168a3 self.ql.run(begin=self.ql.loader.kext_attach) - if self.ql.reg.rax == 0: + if self.ql.arch.regs.rax == 0: self.ql.log.debug("Failed to attach kext object") return self.ql.log.debug("Attached kext object 2nd time") - self.ql.reg.rdi = self.kext_object + self.ql.arch.regs.rdi = self.kext_object # FIXME Determine provider for kext - self.ql.reg.rsi = 0 # ? + self.ql.arch.regs.rsi = 0 # ? self.savedrip=0xffffff8000a168ed self.ql.run(begin=self.ql.loader.kext_start) else: @@ -131,8 +131,8 @@ def load_kext(self): kmod_info.updateToMem() self.ql.log.debug("Initialized kmod_info") - self.ql.reg.rdi = kmod_info_addr - self.ql.reg.rsi = 0 + self.ql.arch.regs.rdi = kmod_info_addr + self.ql.arch.regs.rsi = 0 self.savedrip=0xffffff80009c2c16 self.ql.run(begin=self.ql.loader.kext_start) @@ -179,7 +179,7 @@ def run(self): """ self.ql.stack_push(self.savedrip) def callback_ret(ql): - ql.reg.arch_pc = 0 + ql.arch.regs.arch_pc = 0 if self.savedrip not in self.hook_ret: tmp = self.ql.hook_address(callback_ret, self.savedrip) diff --git a/qiling/os/macos/syscall.py b/qiling/os/macos/syscall.py index 36c1fd79b..bbcd1f40e 100644 --- a/qiling/os/macos/syscall.py +++ b/qiling/os/macos/syscall.py @@ -299,7 +299,7 @@ def ql_syscall_shared_region_check_np(ql, p, uap, retvalp, *args, **kw): # 0x150 def ql_syscall_proc_info(ql, callnum, pid, flavor, arg, buff, buffer_size): - retval = struct.unpack(" int: # Ref1: https://marcin.juszkiewicz.com.pl/download/tables/syscalls.html # Ref2: https://github.com/rootkiter/Reverse-bins/blob/master/syscall_header/armv4l_unistd.h # Ref3: https://github.com/unicorn-engine/unicorn/issues/1137 - code_val = self.ql.mem.read_ptr(self.ql.reg.arch_pc-4, 4) + code_val = self.ql.mem.read_ptr(self.ql.arch.regs.arch_pc-4, 4) svc_imm = code_val & 0x00ffffff if (svc_imm >= 0x900000): return svc_imm - 0x900000 - return self.ql.reg.read(self.__syscall_id_reg) + return self.ql.arch.regs.read(self.__syscall_id_reg) def set_syscall_return(self, retval: int): self.__syscall_cc.setReturnValue(retval) diff --git a/qiling/os/posix/syscall/prctl.py b/qiling/os/posix/syscall/prctl.py index 3652c2bd5..f1ec71048 100644 --- a/qiling/os/posix/syscall/prctl.py +++ b/qiling/os/posix/syscall/prctl.py @@ -13,10 +13,10 @@ def ql_syscall_arch_prctl(ql: Qiling, code: int, addr: int): ARCH_GET_GS = 0x1004 handlers = { - ARCH_SET_GS : lambda : ql.reg.msr(GSMSR, addr), - ARCH_SET_FS : lambda : ql.reg.msr(FSMSR, addr), - ARCH_GET_FS : lambda : ql.mem.write(addr, ql.pack64(ql.reg.msr(FSMSR))), - ARCH_GET_GS : lambda : ql.mem.write(addr, ql.pack64(ql.reg.msr(GSMSR))) + ARCH_SET_GS : lambda : ql.arch.regs.msr(GSMSR, addr), + ARCH_SET_FS : lambda : ql.arch.regs.msr(FSMSR, addr), + ARCH_GET_FS : lambda : ql.mem.write(addr, ql.pack64(ql.arch.regs.msr(FSMSR))), + ARCH_GET_GS : lambda : ql.mem.write(addr, ql.pack64(ql.arch.regs.msr(GSMSR))) } if code not in handlers: diff --git a/qiling/os/posix/syscall/sched.py b/qiling/os/posix/syscall/sched.py index 4754c7162..bad39b7b2 100644 --- a/qiling/os/posix/syscall/sched.py +++ b/qiling/os/posix/syscall/sched.py @@ -71,7 +71,7 @@ def ql_syscall_clone(ql: Qiling, flags: int, child_stack: int, parent_tidptr: in f_th.set_clear_child_tid_addr(child_tidptr) if child_stack != 0: - ql.reg.arch_sp = child_stack + ql.arch.regs.arch_sp = child_stack # ql.log.debug(f'clone(new_stack = {child_stack:#x}, flags = {flags:#x}, tls = {newtls:#x}, ptidptr = {parent_tidptr:#x}, ctidptr = {child_tidptr:#x}) = {regreturn:d}') ql.emu_stop() @@ -81,7 +81,7 @@ def ql_syscall_clone(ql: Qiling, flags: int, child_stack: int, parent_tidptr: in if flags & CLONE_CHILD_SETTID == CLONE_CHILD_SETTID: set_child_tid_addr = child_tidptr - th = ql.os.thread_class.spawn(ql, ql.reg.arch_pc + 2, ql.os.exit_point, set_child_tid_addr = set_child_tid_addr) + th = ql.os.thread_class.spawn(ql, ql.arch.regs.arch_pc + 2, ql.os.exit_point, set_child_tid_addr = set_child_tid_addr) th.path = f_th.path ql.log.debug(f'{str(th)} created') @@ -101,12 +101,12 @@ def ql_syscall_clone(ql: Qiling, flags: int, child_stack: int, parent_tidptr: in # (the return value of the child thread is 0, and the return value of the parent thread is the tid of the child thread) # and save the current context. regreturn = 0 - ql.reg.arch_sp = child_stack + ql.arch.regs.arch_sp = child_stack # We have to find next pc manually for some archs since the pc is current instruction (like `syscall`). if ql.archtype in (QL_ARCH.X8664, ): - ql.reg.arch_pc += list(ql.arch.disassembler.disasm_lite(bytes(ql.mem.read(ql.reg.arch_pc, 4)), ql.reg.arch_pc))[0][1] - ql.log.debug(f"Fix pc for child thread to {hex(ql.reg.arch_pc)}") + ql.arch.regs.arch_pc += list(ql.arch.disassembler.disasm_lite(bytes(ql.mem.read(ql.arch.regs.arch_pc, 4)), ql.arch.regs.arch_pc))[0][1] + ql.log.debug(f"Fix pc for child thread to {hex(ql.arch.regs.arch_pc)}") ql.os.set_syscall_return(0) th.save() diff --git a/qiling/os/posix/syscall/unistd.py b/qiling/os/posix/syscall/unistd.py index df98d3ad0..2de9afa3d 100644 --- a/qiling/os/posix/syscall/unistd.py +++ b/qiling/os/posix/syscall/unistd.py @@ -221,7 +221,7 @@ def ql_syscall_pread64(ql: Qiling, fd: int, buf: int, length: int, offt: int): # https://chromium.googlesource.com/linux-syscall-support/+/2c73abf02fd8af961e38024882b9ce0df6b4d19b # https://chromiumcodereview.appspot.com/10910222 if ql.archtype == QL_ARCH.MIPS: - offt = ql.unpack64(ql.mem.read(ql.reg.arch_sp + 0x10, 8)) + offt = ql.unpack64(ql.mem.read(ql.arch.regs.arch_sp + 0x10, 8)) if 0 <= fd < NR_OPEN and ql.os.fd[fd] != 0: try: @@ -512,7 +512,7 @@ def ql_syscall_pipe(ql: Qiling, pipefd: int): ql.os.fd[idx2] = wd if ql.archtype== QL_ARCH.MIPS: - ql.reg.v1 = idx2 + ql.arch.regs.v1 = idx2 regreturn = idx1 else: ql.mem.write(pipefd + 0, ql.pack32(idx1)) diff --git a/qiling/os/uefi/fncc.py b/qiling/os/uefi/fncc.py index 78838a12e..83f999bf3 100644 --- a/qiling/os/uefi/fncc.py +++ b/qiling/os/uefi/fncc.py @@ -11,7 +11,7 @@ def dxeapi(params: Mapping[str, Any] = {}): def decorator(func): def wrapper(ql: Qiling): - pc = ql.reg.arch_pc + pc = ql.arch.regs.arch_pc fname = func.__name__ f = ql.os.user_defined_api[QL_INTERCEPT.CALL].get(fname) or func diff --git a/qiling/os/uefi/smm.py b/qiling/os/uefi/smm.py index c1cee75df..b7ff43b70 100644 --- a/qiling/os/uefi/smm.py +++ b/qiling/os/uefi/smm.py @@ -155,7 +155,7 @@ def enter(self) -> None: # write cpu state to ssa (partially) # that can take place only after smram ranges have been unlocked for ucreg, (width, regidx) in SmmEnv.SSA_REG_MAP.items(): - val = self.ql.reg.read(ucreg) + val = self.ql.arch.regs.read(ucreg) pack = { 8 : self.ql.pack64, @@ -189,7 +189,7 @@ def leave(self) -> None: 1 : self.ql.unpack8 }[width] - self.ql.reg.write(ucreg, unpack(data)) + self.ql.arch.regs.write(ucreg, unpack(data)) # lock smram ranges for access for lbound, ubound in self.__mapped_smram_ranges(): @@ -237,7 +237,7 @@ def __cleanup(ql: Qiling): ql.log.info(f'Leaving SWSMI handler {idx:#04x}') # unwind ms64 shadow space - ql.reg.arch_sp += (4 * ql.pointersize) + ql.arch.regs.arch_sp += (4 * ql.pointersize) # release handler resources heap.free(DispatchHandle) diff --git a/qiling/os/uefi/uefi.py b/qiling/os/uefi/uefi.py index d9db3489a..3b034ade7 100644 --- a/qiling/os/uefi/uefi.py +++ b/qiling/os/uefi/uefi.py @@ -116,7 +116,7 @@ def emit_context(self): p = re.compile(r'^((?:00)+)') def __emit_reg(size: int, reg: str): - val = f'{self.ql.reg.read(reg):0{size * 2}x}' + val = f'{self.ql.arch.regs.read(reg):0{size * 2}x}' padded = p.sub("\x1b[90m\\1\x1b[39m", val, 1) return f'{reg:3s} = {padded}' @@ -169,12 +169,12 @@ def emit_stack(self, nitems: int = 4): else: data = f'{item:0{self.ql.pointersize * 2}x}' - self.ql.log.error(f'{self.ql.reg.arch_sp + offset:08x} : {data}{" <=" if i == 0 else ""}') + self.ql.log.error(f'{self.ql.arch.regs.arch_sp + offset:08x} : {data}{" <=" if i == 0 else ""}') self.ql.log.error('') def emu_error(self): - pc = self.ql.reg.arch_pc + pc = self.ql.arch.regs.arch_pc try: data = self.ql.mem.read(pc, size=64) diff --git a/qiling/os/uefi/utils.py b/qiling/os/uefi/utils.py index 8c66631cb..105c51661 100644 --- a/qiling/os/uefi/utils.py +++ b/qiling/os/uefi/utils.py @@ -30,7 +30,7 @@ def execute_protocol_notifications(ql: Qiling, from_hook: bool = False) -> bool: def __notify_next(ql: Qiling): # discard previous callback's shadow space - ql.reg.arch_sp += (4 * ql.pointersize) + ql.arch.regs.arch_sp += (4 * ql.pointersize) if ql.loader.notify_list: event_id, notify_func, callback_args = ql.loader.notify_list.pop(0) @@ -44,22 +44,22 @@ def __notify_next(ql: Qiling): ql.loader.context.heap.free(next_hook) hret.remove() - ql.reg.rax = EFI_SUCCESS - ql.reg.arch_pc = ql.stack_pop() + ql.arch.regs.rax = EFI_SUCCESS + ql.arch.regs.arch_pc = ql.stack_pop() hret = ql.hook_address(__notify_next, next_hook) # __notify_next unwinds the previous callback shadow space allocated by call_function. however, on its first invocation # there is no such shadow space. to maintain stack consistency we set here a bogus shadow space that may be discarded # safely - ql.reg.arch_sp -= (4 * ql.pointersize) + ql.arch.regs.arch_sp -= (4 * ql.pointersize) # To avoid having two versions of the code the first notify function will also be called from the __notify_next hook. if from_hook: ql.stack_push(next_hook) else: ql.stack_push(ql.loader.context.end_of_execution_ptr) - ql.reg.arch_pc = next_hook + ql.arch.regs.arch_pc = next_hook return True diff --git a/qiling/os/windows/dlls/kernel32/errhandlingapi.py b/qiling/os/windows/dlls/kernel32/errhandlingapi.py index 3926a2730..cc1d4f2da 100644 --- a/qiling/os/windows/dlls/kernel32/errhandlingapi.py +++ b/qiling/os/windows/dlls/kernel32/errhandlingapi.py @@ -116,8 +116,8 @@ def exec_standard_into(ql: Qiling, intno: int, user_data): double_pointer = ql.os.heap.alloc(0x4) ql.mem.write(double_pointer, ql.pack32(pointer)) - ql.reg.eax = double_pointer - ql.reg.esi = user_data + ql.arch.regs.eax = double_pointer + ql.arch.regs.esi = user_data addr = params["Handler"] diff --git a/qiling/os/windows/dlls/ntoskrnl.py b/qiling/os/windows/dlls/ntoskrnl.py index 82eecbea2..7936854e8 100644 --- a/qiling/os/windows/dlls/ntoskrnl.py +++ b/qiling/os/windows/dlls/ntoskrnl.py @@ -898,7 +898,7 @@ def hook_IoCreateDriver(ql: Qiling, address: int, params): # print("\n\n>>> IoCreateDriver at %x, going to execute function at %x, RET = %x\n" %(address, init_func, ret_addr)) # save SP & init_sp - sp = ql.reg.sp + sp = ql.arch.regs.sp init_sp = ql.os.init_sp ql.os.fcall = ql.os.fcall_select(STDCALL) @@ -915,7 +915,7 @@ def hook_IoCreateDriver(ql: Qiling, address: int, params): verify_ret(ql, err) # reset SP since emulated function does not cleanup - ql.reg.sp = sp + ql.arch.regs.sp = sp ql.os.init_sp = init_sp # ret_addr = ql.stack_read(0) diff --git a/qiling/os/windows/fiber.py b/qiling/os/windows/fiber.py index 51715c421..c3c500671 100644 --- a/qiling/os/windows/fiber.py +++ b/qiling/os/windows/fiber.py @@ -32,7 +32,7 @@ def free(self, idx): self.ql.log.debug(f'Skipping emulation of callback function {fiber.cb:#x} for fiber {fiber.idx:#x}') """ - ret_addr = self.ql.reg.read(UC_X86_REG_RIP + 6 ) #FIXME, use capstone to get addr of next instr? + ret_addr = self.ql.arch.regs.read(UC_X86_REG_RIP + 6 ) #FIXME, use capstone to get addr of next instr? # Write Fls data to memory to be accessed by cb addr = self.ql.os.heap.alloc(self.ql.pointersize) @@ -41,12 +41,12 @@ def free(self, idx): # set up params and return address then jump to callback if self.ql.pointersize == 8: - self.ql.reg.write(UC_X86_REG_RCX, addr) + self.ql.arch.regs.write(UC_X86_REG_RCX, addr) else: self.ql.stack_push(ret_addr) self.ql.stack_push(ret_addr) self.ql.log.debug("Jumping to callback @ 0x%X" % fiber.cb) - self.ql.reg.write(UC_X86_REG_RIP, fiber.cb) + self.ql.arch.regs.write(UC_X86_REG_RIP, fiber.cb) # All of this gets overwritten by the rest of the code in fncc.py # Not sure how to actually make unicorn emulate the callback function due to that """ diff --git a/qiling/os/windows/thread.py b/qiling/os/windows/thread.py index c49f166f1..803e01f8d 100644 --- a/qiling/os/windows/thread.py +++ b/qiling/os/windows/thread.py @@ -11,7 +11,7 @@ def thread_scheduler(ql, address, size): - if ql.reg.arch_pc == ql.os.thread_manager.THREAD_RET_ADDR: + if ql.arch.regs.arch_pc == ql.os.thread_manager.THREAD_RET_ADDR: ql.os.thread_manager.cur_thread.stop() ql.os.thread_manager.do_schedule() else: @@ -84,7 +84,7 @@ def create(self, func_addr, func_params, status): stack_size = 1024 new_stack = self.ql.os.heap.alloc(stack_size) + stack_size - self.saved_context = self.ql.reg.save() + self.saved_context = self.ql.arch.regs.save() # set return address, parameters if self.ql.archtype == QL_ARCH.X86: @@ -111,10 +111,10 @@ def create(self, func_addr, func_params, status): return self.id def suspend(self): - self.saved_context = self.ql.reg.save() + self.saved_context = self.ql.arch.regs.save() def resume(self): - self.ql.reg.restore(self.saved_context) + self.ql.arch.regs.restore(self.saved_context) self.status = QlWindowsThread.RUNNING def stop(self): diff --git a/qiling/utils.py b/qiling/utils.py index 887728151..0e9977bfd 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -558,8 +558,7 @@ def ql_setup_logger(ql, log_file: Optional[str], console: bool, filters: Optiona # verify if emulator returns properly def verify_ret(ql, err): - ql.log.debug("Got exception %u: init SP = %x, current SP = %x, PC = %x" %(err.errno, ql.os.init_sp, ql.reg.arch_sp, ql.reg.arch_pc)) - # print("Got exception %u: init SP = %x, current SP = %x, PC = %x" %(err.errno, ql.os.init_sp, self.reg.arch_sp, self.reg.arch_pc)) + 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 @@ -568,20 +567,20 @@ def verify_ret(ql, err): if ql.ostype == QL_OS.MACOS: if ql.loader.kext_name: # FIXME: Should I push saved RIP before every method callings of IOKit object? - if ql.os.init_sp == ql.reg.arch_sp - 8: + if ql.os.init_sp == ql.arch.regs.arch_sp - 8: pass else: raise if ql.archtype == QL_ARCH.X8664: # Win64 - if ql.os.init_sp == ql.reg.arch_sp or ql.os.init_sp + 8 == ql.reg.arch_sp or ql.os.init_sp + 0x10 == ql.reg.arch_sp: # FIXME + if ql.os.init_sp == ql.arch.regs.arch_sp or ql.os.init_sp + 8 == ql.arch.regs.arch_sp or ql.os.init_sp + 0x10 == ql.arch.regs.arch_sp: # FIXME # 0x11626 c3 ret # print("OK, stack balanced!") pass else: raise else: # Win32 - if ql.os.init_sp + 12 == ql.reg.arch_sp: # 12 = 8 + 4 + if ql.os.init_sp + 12 == ql.arch.regs.arch_sp: # 12 = 8 + 4 # 0x114dd c2 08 00 ret 8 pass else: diff --git a/tests/test_blob.py b/tests/test_blob.py index 281c998e2..f4d542a1c 100644 --- a/tests/test_blob.py +++ b/tests/test_blob.py @@ -20,31 +20,31 @@ def my_getenv(ql, *args, **kwargs): value_addr = ql.os.heap.alloc(len(value)) ql.mem.write(value_addr, value) - ql.reg.r0 = value_addr - ql.reg.arch_pc = ql.reg.lr + ql.arch.regs.r0 = value_addr + ql.arch.regs.arch_pc = ql.arch.regs.lr def check_password(ql, *args, **kwargs): - passwd_output = ql.mem.read(ql.reg.r0, ql.reg.r2) - passwd_input = ql.mem.read(ql.reg.r1, ql.reg.r2) + passwd_output = ql.mem.read(ql.arch.regs.r0, ql.arch.regs.r2) + passwd_input = ql.mem.read(ql.arch.regs.r1, ql.arch.regs.r2) self.assertEqual(passwd_output, passwd_input) def partial_run_init(ql): # argv prepare - ql.reg.arch_sp -= 0x30 - arg0_ptr = ql.reg.arch_sp + ql.arch.regs.arch_sp -= 0x30 + arg0_ptr = ql.arch.regs.arch_sp ql.mem.write(arg0_ptr, b"kaimendaji") - ql.reg.arch_sp -= 0x10 - arg1_ptr = ql.reg.arch_sp + ql.arch.regs.arch_sp -= 0x10 + arg1_ptr = ql.arch.regs.arch_sp ql.mem.write(arg1_ptr, b"013f1f") - ql.reg.arch_sp -= 0x20 - argv_ptr = ql.reg.arch_sp + ql.arch.regs.arch_sp -= 0x20 + argv_ptr = ql.arch.regs.arch_sp ql.mem.write(argv_ptr, ql.pack(arg0_ptr)) ql.mem.write(argv_ptr + ql.pointersize, ql.pack(arg1_ptr)) - ql.reg.r2 = 2 - ql.reg.r3 = argv_ptr + ql.arch.regs.r2 = 2 + ql.arch.regs.r3 = argv_ptr print("ARM uboot bin") diff --git a/tests/test_edl.py b/tests/test_edl.py index e14546d7b..7f86845a0 100644 --- a/tests/test_edl.py +++ b/tests/test_edl.py @@ -15,8 +15,8 @@ def replace_function(ql,addr,callback): def runcode(ql): ret=callback(ql) - ql.reg.x0=ret - ql.reg.pc=ql.reg.x30 #lr + ql.arch.regs.x0=ret + ql.arch.regs.pc=ql.arch.regs.x30 #lr ql.hook_address(runcode,addr) def hook_mem_invalid(uc, access, address, size, value, user_data): @@ -55,12 +55,12 @@ def test_edl_arm64(self): def devprg_time_usec(ql): current_milli_time = int(round(time.time() * 1000)) - ql.reg.x0 = current_milli_time + ql.arch.regs.x0 = current_milli_time return current_milli_time def devprg_tx_blocking(ql): - ptr = ql.reg.x0 - plen = ql.reg.x1 + ptr = ql.arch.regs.x0 + plen = ql.arch.regs.x1 data = ql.mem.read(ptr, plen) res=bytes(data) if b"response" in res: @@ -75,13 +75,13 @@ def devprg_tx_blocking(ql): replace_function(ql, 0x148595A0, devprg_time_usec) # Register 0xC221000 replace_function(ql, 0x1485C614, devprg_tx_blocking) # Function being used by UART in DP_LOGI - ql.reg.sp = 0x146B2000 # SP from main + ql.arch.regs.sp = 0x146B2000 # SP from main xml_buffer_addr = 0x14684E80 # We extracted that from devprg_get_xml_buffer device_serial_addr = 0x148A8A8C device_serial = pack("\n\n\x00" - ql.reg.x0 = xml_buffer_addr - ql.reg.x1 = len(uart_data) + ql.arch.regs.x0 = xml_buffer_addr + ql.arch.regs.x1 = len(uart_data) ql.mem.write(xml_buffer_addr, uart_data) ql.mem.write(device_serial_addr, device_serial) handle_xml_addr_start=0x14857C94 diff --git a/tests/test_elf.py b/tests/test_elf.py index 86a35a5f2..0de2d9a23 100644 --- a/tests/test_elf.py +++ b/tests/test_elf.py @@ -81,14 +81,14 @@ def dump(ql): ql.run() # make sure that the ending PC is the same as the hook address because dump stops the emulater - assert ql.reg.arch_pc == hook_address, f"0x{ql.reg.arch_pc:x} != 0x{hook_address:x}" + assert ql.arch.regs.arch_pc == hook_address, f"0x{ql.arch.regs.arch_pc:x} != 0x{hook_address:x}" del ql ql = Qiling(cmdline, rootfs, verbose=QL_VERBOSE.DEBUG) ql.restore(snapshot=snapshot) # ensure that the starting PC is same as the PC we stopped on when taking the snapshot - assert ql.reg.arch_pc == hook_address, f"0x{ql.reg.arch_pc:x} != 0x{hook_address:x}" + assert ql.arch.regs.arch_pc == hook_address, f"0x{ql.arch.regs.arch_pc:x} != 0x{hook_address:x}" ql.run(begin=hook_address) del ql @@ -108,7 +108,7 @@ def test_elf_linux_x8664(self): def my_puts(ql: Qiling): params = ql.os.resolve_fcall_params(ELFTest.PARAMS_PUTS) print(f'puts("{params["s"]}")') - reg = ql.reg.read("rax") + reg = ql.arch.regs.read("rax") print("reg : %#x" % reg) self.set_api = reg @@ -153,7 +153,7 @@ def my_puts_enter(ql: Qiling): self.test_enter_str = params["s"] def my_puts_exit(ql): - self.test_exit_rdi = ql.reg.rdi + self.test_exit_rdi = ql.arch.regs.rdi ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_puts"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG) ql.set_api('puts', my_puts_enter, QL_INTERCEPT.ENTER) @@ -313,9 +313,9 @@ def test_syscall_ftruncate(ql, ftrunc_fd, ftrunc_length, *args): target = False pathname = ql.os.fd[ftrunc_fd].name.split('/')[-1] - reg = ql.reg.read("eax") + reg = ql.arch.regs.read("eax") print("reg : 0x%x" % reg) - ql.reg.eax = reg + ql.arch.regs.eax = reg if pathname == "test_syscall_ftruncate.txt": print("test => ftruncate(%d, 0x%x)" % (ftrunc_fd, ftrunc_length)) @@ -520,9 +520,9 @@ def test_syscall_read(ql, read_fd, read_buf, read_count, *args): target = False pathname = ql.os.fd[read_fd].name.split('/')[-1] - reg = ql.reg.read("x0") + reg = ql.arch.regs.read("x0") print("reg : 0x%x" % reg) - ql.reg.x0 = reg + ql.arch.regs.x0 = reg if pathname == "test_syscall_read.txt": print("test => read(%d, %s, %d)" % (read_fd, pathname, read_count)) @@ -667,9 +667,9 @@ def test_syscall_read(ql, read_fd, read_buf, read_count, *args): target = False pathname = ql.os.fd[read_fd].name.split('/')[-1] - reg = ql.reg.read("v0") + reg = ql.arch.regs.read("v0") print("reg : 0x%x" % reg) - ql.reg.v0 = reg + ql.arch.regs.v0 = reg if pathname == "test_syscall_read.txt": print("test => read(%d, %s, %d)" % (read_fd, pathname, read_count)) @@ -794,9 +794,9 @@ def my_syscall_write(ql, write_fd, write_buf, write_count, *args, **kw): mapaddr = ql.mem.map_anywhere(0x100000) ql.log.info("0x%x" % mapaddr) - reg = ql.reg.read("r0") + reg = ql.arch.regs.read("r0") print("reg : 0x%x" % reg) - ql.reg.r0 = reg + ql.arch.regs.r0 = reg try: diff --git a/tests/test_mcu.py b/tests/test_mcu.py index 01e7bec0d..1b07c5ace 100644 --- a/tests/test_mcu.py +++ b/tests/test_mcu.py @@ -284,7 +284,7 @@ def step(self): delay_start = 0x8002936 delay_end = 0x8002955 def skip_delay(ql): - ql.reg.pc = delay_end + ql.arch.regs.pc = delay_end ql.hook_address(skip_delay, delay_start) @@ -305,7 +305,7 @@ def test_mcu_blink_gd32vf103(self): delay_cycles_end = 0x800018c def skip_delay(ql): - ql.reg.pc = delay_cycles_end + ql.arch.regs.pc = delay_cycles_end count = 0 def counter(): diff --git a/tests/test_pe.py b/tests/test_pe.py index b3e3981b5..cba81057b 100644 --- a/tests/test_pe.py +++ b/tests/test_pe.py @@ -363,11 +363,11 @@ def _t(): # The hooks are to remove the prints to file. It crashes. will debug why in the future def results(ql): - if ql.reg.ebx == 1: + if ql.arch.regs.ebx == 1: print("BAD") else: print("GOOD ") - ql.reg.eip = 0x402ee4 + ql.arch.regs.eip = 0x402ee4 #ql.hook_address(results, 0x00402e66) # the program alloc 4 bytes and then tries to write 0x2cc bytes. @@ -493,7 +493,7 @@ def test_pe_win_x86_crackme(self): def _t(): def force_call_dialog_func(ql): # get DialogFunc address - lpDialogFunc = ql.unpack32(ql.mem.read(ql.reg.esp - 0x8, 4)) + lpDialogFunc = ql.unpack32(ql.mem.read(ql.arch.regs.esp - 0x8, 4)) # setup stack for DialogFunc ql.stack_push(0) ql.stack_push(1001) @@ -501,7 +501,7 @@ def force_call_dialog_func(ql): ql.stack_push(0) ql.stack_push(0x0401018) # force EIP to DialogFunc - ql.reg.eip = lpDialogFunc + ql.arch.regs.eip = lpDialogFunc def our_sandbox(path, rootfs): ql = Qiling(path, rootfs) diff --git a/tests/test_pe_sys.py b/tests/test_pe_sys.py index 56c7f5ae7..73da0742f 100644 --- a/tests/test_pe_sys.py +++ b/tests/test_pe_sys.py @@ -23,7 +23,7 @@ class PETest(unittest.TestCase): def hook_third_stop_address(self, ql): - print(" >>>> Third Stop address: 0x%08x" % ql.reg.arch_pc) + print(" >>>> Third Stop address: 0x%08x" % ql.arch.regs.arch_pc) self.third_stop = True ql.emu_stop() @@ -177,13 +177,13 @@ def hook_StartServiceA(ql: Qiling, address: int, params): def hook_first_stop_address(ql): - print(" >>>> First Stop address: 0x%08x" % ql.reg.arch_pc) + print(" >>>> First Stop address: 0x%08x" % ql.arch.regs.arch_pc) ql.first_stop = True ql.emu_stop() def hook_second_stop_address(ql): - print(" >>>> Second Stop address: 0x%08x" % ql.reg.arch_pc) + print(" >>>> Second Stop address: 0x%08x" % ql.arch.regs.arch_pc) ql.second_stop = True ql.emu_stop() From 92557d000e64f9e49115145d8b0eb77decf94839 Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 14 Jan 2022 16:21:18 +0200 Subject: [PATCH 017/406] Let QlArch derivatives init their own regs manager --- qiling/arch/arch.py | 9 ++- qiling/arch/arm.py | 22 ++++---- qiling/arch/arm64.py | 28 +++++----- qiling/arch/cortex_m.py | 26 +++++---- qiling/arch/mips.py | 30 +++++----- qiling/arch/register.py | 28 ++-------- qiling/arch/riscv.py | 30 +++++----- qiling/arch/riscv64.py | 4 -- qiling/arch/x86.py | 121 +++++++++++++++++++--------------------- 9 files changed, 135 insertions(+), 163 deletions(-) diff --git a/qiling/arch/arch.py b/qiling/arch/arch.py index 04f53ec3a..2c0601edc 100644 --- a/qiling/arch/arch.py +++ b/qiling/arch/arch.py @@ -4,7 +4,6 @@ # from abc import abstractmethod -from functools import cached_property from unicorn import Uc from unicorn.unicorn import UcContext @@ -28,9 +27,13 @@ def uc(self) -> Uc: pass - @cached_property + @property + @abstractmethod def regs(self) -> QlRegisterManager: - return QlRegisterManager(self.uc) + """Architectural registers. + """ + + pass def stack_push(self, value: int) -> int: diff --git a/qiling/arch/arm.py b/qiling/arch/arm.py index 6ed7effa1..dbadc74bf 100644 --- a/qiling/arch/arm.py +++ b/qiling/arch/arm.py @@ -12,23 +12,14 @@ from qiling import Qiling from qiling.const import QL_ARCH, QL_ENDIAN from qiling.arch.arch import QlArch -from qiling.arch.arm_const import * +from qiling.arch import arm_const +from qiling.arch.register import QlRegisterManager from qiling.exception import QlErrorArch class QlArchARM(QlArch): def __init__(self, ql: Qiling): super().__init__(ql) - reg_maps = ( - reg_map, - ) - - for reg_maper in reg_maps: - self.ql.arch.regs.expand_mapping(reg_maper) - - self.ql.arch.regs.register_sp(reg_map["sp"]) - self.ql.arch.regs.register_pc(reg_map["pc"]) - self.arm_get_tls_addr = 0xFFFF0FE0 @cached_property @@ -47,6 +38,13 @@ def uc(self) -> Uc: return Uc(UC_ARCH_ARM, mode) + @cached_property + def regs(self) -> QlRegisterManager: + regs_map = arm_const.reg_map + pc_reg = 'pc' + sp_reg = 'sp' + + return QlRegisterManager(self.uc, regs_map, pc_reg, sp_reg) # get PC def get_pc(self) -> int: @@ -122,7 +120,7 @@ def init_get_tls(self): # if ql.archendian == QL_ENDIAN.EB: # sc = swap_endianess(sc) - self.ql.mem.write(self.ql.arch.arm_get_tls_addr, sc) + self.ql.mem.write(self.arm_get_tls_addr, sc) self.ql.log.debug("Set init_kernel_get_tls") def swap_endianess(self, s: bytes, blksize=4) -> bytes: diff --git a/qiling/arch/arm64.py b/qiling/arch/arm64.py index b2a1025a9..76528906f 100644 --- a/qiling/arch/arm64.py +++ b/qiling/arch/arm64.py @@ -9,28 +9,26 @@ from capstone import Cs, CS_ARCH_ARM64, CS_MODE_ARM from keystone import Ks, KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN -from qiling import Qiling from qiling.arch.arch import QlArch -from qiling.arch.arm64_const import * +from qiling.arch import arm64_const +from qiling.arch.register import QlRegisterManager class QlArchARM64(QlArch): - def __init__(self, ql: Qiling): - super().__init__(ql) + @cached_property + def uc(self) -> Uc: + return Uc(UC_ARCH_ARM64, UC_MODE_ARM) - reg_maps = ( - reg_map, - reg_map_w + @cached_property + def regs(self) -> QlRegisterManager: + regs_map = dict( + **arm64_const.reg_map, + **arm64_const.reg_map_w ) - for reg_maper in reg_maps: - self.ql.arch.regs.expand_mapping(reg_maper) + pc_reg = 'pc' + sp_reg = 'sp' - self.ql.arch.regs.register_sp(reg_map["sp"]) - self.ql.arch.regs.register_pc(reg_map["pc"]) - - @cached_property - def uc(self) -> Uc: - return Uc(UC_ARCH_ARM64, UC_MODE_ARM) + return QlRegisterManager(self.uc, regs_map, pc_reg, sp_reg) @cached_property def disassembler(self) -> Cs: diff --git a/qiling/arch/cortex_m.py b/qiling/arch/cortex_m.py index 5a6b00043..9237f4014 100644 --- a/qiling/arch/cortex_m.py +++ b/qiling/arch/cortex_m.py @@ -13,8 +13,10 @@ from qiling.const import QL_VERBOSE from qiling.exception import QlErrorNotImplemented -from .arm import QlArchARM -from .cortex_m_const import IRQ, EXC_RETURN, CONTROL, EXCP, reg_map +from qiling.arch.arm import QlArchARM +from qiling.arch import arm_const, cortex_m_const +from qiling.arch.register import QlRegisterManager +from qiling.arch.cortex_m_const import IRQ, EXC_RETURN, CONTROL, EXCP class QlInterruptContext(ContextDecorator): def __init__(self, ql): @@ -59,19 +61,21 @@ def __exit__(self, *exc): self.ql.log.info('Exit from interrupt') class QlArchCORTEX_M(QlArchARM): - def __init__(self, ql): - super().__init__(ql) + @cached_property + def uc(self): + return Uc(UC_ARCH_ARM, UC_MODE_ARM + UC_MODE_MCLASS + UC_MODE_THUMB) - reg_maps = ( - reg_map, + @cached_property + def regs(self) -> QlRegisterManager: + regs_map = dict( + **arm_const.reg_map, + **cortex_m_const.reg_map ) - for reg_maper in reg_maps: - self.ql.arch.regs.expand_mapping(reg_maper) + pc_reg = 'pc' + sp_reg = 'sp' - @cached_property - def uc(self): - return Uc(UC_ARCH_ARM, UC_MODE_ARM + UC_MODE_MCLASS + UC_MODE_THUMB) + return QlRegisterManager(self.uc, regs_map, pc_reg, sp_reg) @cached_property def disassembler(self) -> Cs: diff --git a/qiling/arch/mips.py b/qiling/arch/mips.py index 8981d84a8..c1477a36b 100644 --- a/qiling/arch/mips.py +++ b/qiling/arch/mips.py @@ -9,26 +9,12 @@ from capstone import Cs, CS_ARCH_MIPS, CS_MODE_MIPS32, CS_MODE_BIG_ENDIAN, CS_MODE_LITTLE_ENDIAN from keystone import Ks, KS_ARCH_MIPS, KS_MODE_MIPS32, KS_MODE_BIG_ENDIAN, KS_MODE_LITTLE_ENDIAN -from qiling import Qiling from qiling.const import QL_ENDIAN from qiling.arch.arch import QlArch -from qiling.arch.mips_const import * +from qiling.arch import mips_const +from qiling.arch.register import QlRegisterManager class QlArchMIPS(QlArch): - def __init__(self, ql: Qiling): - super().__init__(ql) - - reg_maps = ( - reg_map, - reg_map_afpr128 - ) - - for reg_maper in reg_maps: - self.ql.arch.regs.expand_mapping(reg_maper) - - self.ql.arch.regs.register_sp(reg_map["sp"]) - self.ql.arch.regs.register_pc(reg_map["pc"]) - @cached_property def uc(self) -> Uc: endian = { @@ -38,6 +24,18 @@ def uc(self) -> Uc: return Uc(UC_ARCH_MIPS, UC_MODE_MIPS32 + endian) + @cached_property + def regs(self) -> QlRegisterManager: + regs_map = dict( + **mips_const.reg_map, + **mips_const.reg_map_afpr128 + ) + + pc_reg = 'pc' + sp_reg = 'sp' + + return QlRegisterManager(self.uc, regs_map, pc_reg, sp_reg) + @cached_property def disassembler(self) -> Cs: endian = { diff --git a/qiling/arch/register.py b/qiling/arch/register.py index 177bd5c18..0a0023bc8 100644 --- a/qiling/arch/register.py +++ b/qiling/arch/register.py @@ -15,15 +15,15 @@ class QlRegisterManager: arch directories and are mapped to Unicorn Engine's definitions """ - def __init__(self, uc: Uc): + def __init__(self, uc: Uc, regs_map: Mapping[str, int], pc_reg: str, sp_reg: str): # this funny way of initialization is used to avoid calling self setattr and # getattr upon init. if it did, it would go into an endless recursion - self.register_mapping: MutableMapping[str, int] - super().__setattr__('register_mapping', {}) + self.register_mapping: Mapping[str, int] + super().__setattr__('register_mapping', regs_map) self.uc = uc - self.uc_pc = 0 - self.uc_sp = 0 + self.uc_pc = self.register_mapping[pc_reg] + self.uc_sp = self.register_mapping[sp_reg] def __getattr__(self, name: str) -> Any: name = name.lower() @@ -45,13 +45,6 @@ def __setattr__(self, name: str, value: Any): super().__setattr__(name, value) - def expand_mapping(self, extra: Mapping[str, int]) -> None: - """Expand registers mapping with additional ones. - """ - - self.register_mapping.update(extra) - - # read register def read(self, register: Union[str, int]): """Read a register value. @@ -111,17 +104,6 @@ def bit(self, reg: Union[str, int]) -> int: return self.ql.arch.get_reg_bit(reg) - # Generic methods to get SP and IP across Arch's # - # These functions should only be used if the # - # caller is dealing with multiple Arch's # - def register_sp(self, sp_id: int): - self.uc_sp = sp_id - - - def register_pc(self, pc_id: int): - self.uc_pc = pc_id - - @property def arch_pc(self) -> int: """Get the value of the architectural program counter register. diff --git a/qiling/arch/riscv.py b/qiling/arch/riscv.py index 790582afc..2cfd1609b 100644 --- a/qiling/arch/riscv.py +++ b/qiling/arch/riscv.py @@ -9,30 +9,28 @@ from capstone import Cs from keystone import Ks -from qiling import Qiling from qiling.arch.arch import QlArch -from qiling.arch.riscv_const import * +from qiling.arch.register import QlRegisterManager +from qiling.arch import riscv_const from qiling.exception import QlErrorNotImplemented - class QlArchRISCV(QlArch): - def __init__(self, ql: Qiling): - super().__init__(ql) + @cached_property + def uc(self) -> Uc: + return Uc(UC_ARCH_RISCV, UC_MODE_RISCV32) - reg_maps = ( - reg_map, - reg_csr_map, - reg_float_map, + @cached_property + def regs(self) -> QlRegisterManager: + regs_map = dict( + **riscv_const.reg_map, + **riscv_const.reg_csr_map, + **riscv_const.reg_float_map, ) - for reg_maper in reg_maps: - self.ql.arch.regs.expand_mapping(reg_maper) - self.ql.arch.regs.register_sp(reg_map["sp"]) - self.ql.arch.regs.register_pc(reg_map["pc"]) + pc_reg = 'pc' + sp_reg = 'sp' - @cached_property - def uc(self) -> Uc: - return Uc(UC_ARCH_RISCV, UC_MODE_RISCV32) + return QlRegisterManager(self.uc, regs_map, pc_reg, sp_reg) @cached_property def disassembler(self) -> Cs: diff --git a/qiling/arch/riscv64.py b/qiling/arch/riscv64.py index 20be202be..5ab0493d0 100644 --- a/qiling/arch/riscv64.py +++ b/qiling/arch/riscv64.py @@ -9,16 +9,12 @@ from capstone import Cs from keystone import Ks -from qiling import Qiling from qiling.arch.riscv_const import * from qiling.exception import QlErrorNotImplemented from .riscv import QlArchRISCV class QlArchRISCV64(QlArchRISCV): - def __init__(self, ql: Qiling): - super().__init__(ql) - @cached_property def uc(self) -> Uc: return Uc(UC_ARCH_RISCV, UC_MODE_RISCV64) diff --git a/qiling/arch/x86.py b/qiling/arch/x86.py index 2a651f39e..66341a215 100644 --- a/qiling/arch/x86.py +++ b/qiling/arch/x86.py @@ -12,6 +12,8 @@ from qiling import Qiling from qiling.arch.arch import QlArch +from qiling.arch.register import QlRegisterManager +from qiling.arch import x86_const from qiling.arch.x86_const import * from qiling.exception import QlGDTError @@ -24,37 +26,35 @@ def get_reg_bit(self, register: int) -> int: return 32 regmaps = ( - (reg_map_8, 8), - (reg_map_16, 16), - (reg_map_32, 32), - (reg_map_64, 64), - (reg_map_misc, 16), - (reg_map_cr, 64 if self.ql.archbit == 64 else 32), - (reg_map_st, 32), - (reg_map_seg_base, 64 if self.ql.archbit == 64 else 32), + (x86_const.reg_map_8, 8), + (x86_const.reg_map_16, 16), + (x86_const.reg_map_32, 32), + (x86_const.reg_map_64, 64), + (x86_const.reg_map_misc, 16), + (x86_const.reg_map_cr, 64 if self.ql.archbit == 64 else 32), + (x86_const.reg_map_st, 32), + (x86_const.reg_map_seg_base, 64 if self.ql.archbit == 64 else 32), ) return next((rsize for rmap, rsize in regmaps if register in rmap.values()), 0) class QlArchA8086(QlArchIntel): - def __init__(self, ql: Qiling): - super().__init__(ql) + @cached_property + def uc(self) -> Uc: + return Uc(UC_ARCH_X86, UC_MODE_16) - reg_maps = ( - reg_map_8, - reg_map_16, - reg_map_misc + @cached_property + def regs(self) -> QlRegisterManager: + regs_map = dict( + **x86_const.reg_map_8, + **x86_const.reg_map_16, + **x86_const.reg_map_misc ) - for reg_maper in reg_maps: - self.ql.arch.regs.expand_mapping(reg_maper) + pc_reg = 'ip' + sp_reg = 'sp' - self.ql.arch.regs.register_pc(reg_map_16["sp"]) - self.ql.arch.regs.register_sp(reg_map_16["ip"]) - - @cached_property - def uc(self) -> Uc: - return Uc(UC_ARCH_X86, UC_MODE_16) + return QlRegisterManager(self.uc, regs_map, pc_reg, sp_reg) @cached_property def disassembler(self) -> Cs: @@ -65,28 +65,26 @@ def assembler(self) -> Ks: return Ks(KS_ARCH_X86, KS_MODE_16) class QlArchX86(QlArchIntel): - def __init__(self, ql: Qiling): - super().__init__(ql) - - reg_maps = ( - reg_map_8, - reg_map_16, - reg_map_32, - reg_map_cr, - reg_map_st, - reg_map_misc - ) - - for reg_maper in reg_maps: - self.ql.arch.regs.expand_mapping(reg_maper) - - self.ql.arch.regs.register_sp(reg_map_32["esp"]) - self.ql.arch.regs.register_pc(reg_map_32["eip"]) - @cached_property def uc(self) -> Uc: return Uc(UC_ARCH_X86, UC_MODE_32) + @cached_property + def regs(self) -> QlRegisterManager: + regs_map = dict( + **x86_const.reg_map_8, + **x86_const.reg_map_16, + **x86_const.reg_map_32, + **x86_const.reg_map_cr, + **x86_const.reg_map_st, + **x86_const.reg_map_misc + ) + + pc_reg = 'eip' + sp_reg = 'esp' + + return QlRegisterManager(self.uc, regs_map, pc_reg, sp_reg) + @cached_property def disassembler(self) -> Cs: return Cs(CS_ARCH_X86, CS_MODE_32) @@ -96,34 +94,31 @@ def assembler(self) -> Ks: return Ks(KS_ARCH_X86, KS_MODE_32) class QlArchX8664(QlArchIntel): - def __init__(self, ql: Qiling): - super().__init__(ql) - - reg_maps = ( - reg_map_8, - reg_map_16, - reg_map_32, - reg_map_64, - reg_map_cr, - reg_map_st, - reg_map_misc, - reg_map_64_b, - reg_map_64_w, - reg_map_64_d, - reg_map_seg_base - ) - - for reg_maper in reg_maps: - self.ql.arch.regs.expand_mapping(reg_maper) - - self.ql.arch.regs.register_sp(reg_map_64["rsp"]) - self.ql.arch.regs.register_pc(reg_map_64["rip"]) - @cached_property def uc(self) -> Uc: return Uc(UC_ARCH_X86, UC_MODE_64) @cached_property + def regs(self) -> QlRegisterManager: + regs_map = dict( + **x86_const.reg_map_8, + **x86_const.reg_map_16, + **x86_const.reg_map_32, + **x86_const.reg_map_64, + **x86_const.reg_map_cr, + **x86_const.reg_map_st, + **x86_const.reg_map_misc, + **x86_const.reg_map_64_b, + **x86_const.reg_map_64_w, + **x86_const.reg_map_64_d, + **x86_const.reg_map_seg_base + ) + + pc_reg = 'rip' + sp_reg = 'rsp' + + return QlRegisterManager(self.uc, regs_map, pc_reg, sp_reg) + @cached_property def disassembler(self) -> Cs: return Cs(CS_ARCH_X86, CS_MODE_64) From 5ad585943b9e6e1aa2f05c8141fe7b45bad8d756 Mon Sep 17 00:00:00 2001 From: elicn Date: Sat, 15 Jan 2022 18:30:36 +0200 Subject: [PATCH 018/406] Extract msr from regs and make available only in QlArchIntel --- qiling/arch/msr.py | 25 +++++++++++++++++++++++++ qiling/arch/register.py | 11 ----------- qiling/arch/x86.py | 8 ++++++++ 3 files changed, 33 insertions(+), 11 deletions(-) create mode 100644 qiling/arch/msr.py diff --git a/qiling/arch/msr.py b/qiling/arch/msr.py new file mode 100644 index 000000000..08409cffe --- /dev/null +++ b/qiling/arch/msr.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from unicorn import Uc + +class QlMsrManager: + """Enables access to Intel MSR. + """ + + def __init__(self, uc: Uc) -> None: + self.uc = uc + + def read(self, msr: int) -> int: + """Read a model-specific register value. + """ + + return self.uc.msr_read(msr) + + def write(self, msr: int, value: int): + """Write a model-specific register value. + """ + + self.uc.msr_write(msr, value) diff --git a/qiling/arch/register.py b/qiling/arch/register.py index 0a0023bc8..c1a18c2ef 100644 --- a/qiling/arch/register.py +++ b/qiling/arch/register.py @@ -66,17 +66,6 @@ def write(self, register: Union[str, int], value: int) -> None: return self.uc.reg_write(register, value) - def msr(self, msr: int, value: int = None): - """Read or write a model-specific register (MSR) value. - Intel architecture only - """ - - if value is None: - return self.uc.msr_read(msr) - - self.uc.msr_write(msr, value) - - def save(self) -> MutableMapping[str, Any]: """Save CPU context. """ diff --git a/qiling/arch/x86.py b/qiling/arch/x86.py index 66341a215..2b1aa181b 100644 --- a/qiling/arch/x86.py +++ b/qiling/arch/x86.py @@ -12,6 +12,7 @@ from qiling import Qiling from qiling.arch.arch import QlArch +from qiling.arch.msr import QlMsrManager from qiling.arch.register import QlRegisterManager from qiling.arch import x86_const from qiling.arch.x86_const import * @@ -38,6 +39,13 @@ def get_reg_bit(self, register: int) -> int: return next((rsize for rmap, rsize in regmaps if register in rmap.values()), 0) + @cached_property + def msr(self) -> QlMsrManager: + """Model-Specific Registers. + """ + + return QlMsrManager(self.uc) + class QlArchA8086(QlArchIntel): @cached_property def uc(self) -> Uc: From b2433712cb531d0f5409329cadf696b8a73d3236 Mon Sep 17 00:00:00 2001 From: elicn Date: Sat, 15 Jan 2022 18:31:04 +0200 Subject: [PATCH 019/406] Adjust all msr usages --- qiling/arch/x86.py | 8 ++++---- qiling/os/freebsd/syscall.py | 4 ++-- qiling/os/linux/thread.py | 4 ++-- qiling/os/macos/syscall.py | 2 +- qiling/os/posix/syscall/prctl.py | 8 ++++---- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/qiling/arch/x86.py b/qiling/arch/x86.py index 2b1aa181b..14d291200 100644 --- a/qiling/arch/x86.py +++ b/qiling/arch/x86.py @@ -253,16 +253,16 @@ def ql_x8664_set_gs(ql: Qiling): if not ql.mem.is_mapped(GS_SEGMENT_ADDR, GS_SEGMENT_SIZE): ql.mem.map(GS_SEGMENT_ADDR, GS_SEGMENT_SIZE, info="[GS]") - ql.arch.regs.msr(GSMSR, GS_SEGMENT_ADDR) + ql.arch.msr.write(GSMSR, GS_SEGMENT_ADDR) def ql_x8664_get_gs(ql: Qiling): - return ql.arch.regs.msr(GSMSR) + return ql.arch.msr.read(GSMSR) def ql_x8664_set_fs(ql: Qiling, addr: int): - ql.arch.regs.msr(FSMSR, addr) + ql.arch.msr.write(FSMSR, addr) def ql_x8664_get_fs(ql: Qiling): - return ql.arch.regs.msr(FSMSR) + return ql.arch.msr.read(FSMSR) diff --git a/qiling/os/freebsd/syscall.py b/qiling/os/freebsd/syscall.py index 31e50827b..a5f60c35e 100644 --- a/qiling/os/freebsd/syscall.py +++ b/qiling/os/freebsd/syscall.py @@ -46,8 +46,8 @@ def ql_syscall_sysarch(ql, op, parms, *args, **kw): #ql.mem.map(ql.GS_SEGMENT_ADDR, ql.GS_SEGMENT_SIZE) - #ql.arch.regs.msr(GSMSR, ql.GS_SEGMENT_ADDR) - ql.arch.regs.msr(FSMSR, parms) + #ql.arch.msr.write(GSMSR, ql.GS_SEGMENT_ADDR) + ql.arch.msr.write(FSMSR, parms) #op_buf = ql.pack32(op) #ql.mem.write(parms, op_buf) diff --git a/qiling/os/linux/thread.py b/qiling/os/linux/thread.py index 5e3cdc39d..b994111e2 100644 --- a/qiling/os/linux/thread.py +++ b/qiling/os/linux/thread.py @@ -416,7 +416,7 @@ def __init__(self, ql, start_address, exit_point, context = None, set_child_tid_ def set_thread_tls(self, tls_addr): self.tls = tls_addr - self.ql.arch.regs.msr(FSMSR, self.tls) + self.ql.arch.msr.write(FSMSR, self.tls) self.ql.log.debug(f"Set fsbase to {hex(tls_addr)} for {str(self)}") # Some notes: @@ -424,7 +424,7 @@ def set_thread_tls(self, tls_addr): # - https://stackoverflow.com/questions/11497563/detail-about-msr-gs-base-in-linux-x86-64 def save(self): self.save_context() - self.tls = self.ql.arch.regs.msr(FSMSR) + self.tls = self.ql.arch.msr.read(FSMSR) self.ql.log.debug(f"Saved context: fs={hex(self.ql.arch.regs.fsbase)} tls={hex(self.tls)}") def restore(self): diff --git a/qiling/os/macos/syscall.py b/qiling/os/macos/syscall.py index bbcd1f40e..dc15c9c77 100644 --- a/qiling/os/macos/syscall.py +++ b/qiling/os/macos/syscall.py @@ -431,5 +431,5 @@ def ql_syscall_abort_with_payload(ql, reason_namespace, reason_code, payload, pa # thread_set_tsd_base def ql_syscall_thread_fast_set_cthread_self64(ql, u_info_addr, *args, **kw): ql.log.debug("[mdep] thread fast set cthread self64(tsd_base:0x%x)" % (u_info_addr)) - ql.arch.regs.msr(GSMSR, u_info_addr) + ql.arch.msr.write(GSMSR, u_info_addr) return KERN_SUCCESS diff --git a/qiling/os/posix/syscall/prctl.py b/qiling/os/posix/syscall/prctl.py index f1ec71048..744ef899e 100644 --- a/qiling/os/posix/syscall/prctl.py +++ b/qiling/os/posix/syscall/prctl.py @@ -13,10 +13,10 @@ def ql_syscall_arch_prctl(ql: Qiling, code: int, addr: int): ARCH_GET_GS = 0x1004 handlers = { - ARCH_SET_GS : lambda : ql.arch.regs.msr(GSMSR, addr), - ARCH_SET_FS : lambda : ql.arch.regs.msr(FSMSR, addr), - ARCH_GET_FS : lambda : ql.mem.write(addr, ql.pack64(ql.arch.regs.msr(FSMSR))), - ARCH_GET_GS : lambda : ql.mem.write(addr, ql.pack64(ql.arch.regs.msr(GSMSR))) + ARCH_SET_GS : lambda : ql.arch.msr.write(GSMSR, addr), + ARCH_SET_FS : lambda : ql.arch.msr.write(FSMSR, addr), + ARCH_GET_FS : lambda : ql.mem.write(addr, ql.pack64(ql.arch.msr.read(FSMSR))), + ARCH_GET_GS : lambda : ql.mem.write(addr, ql.pack64(ql.arch.msr.read(GSMSR))) } if code not in handlers: From e6398389a78557541cf5474bd23b91bfbf8c923a Mon Sep 17 00:00:00 2001 From: elicn Date: Sat, 15 Jan 2022 20:51:58 +0200 Subject: [PATCH 020/406] Move bits from reg to QlArchX8664 --- qiling/arch/register.py | 12 --------- qiling/arch/x86.py | 50 +++++++++++++++++++++++--------------- qiling/debugger/gdb/gdb.py | 4 +-- 3 files changed, 32 insertions(+), 34 deletions(-) diff --git a/qiling/arch/register.py b/qiling/arch/register.py index c1a18c2ef..086fb521f 100644 --- a/qiling/arch/register.py +++ b/qiling/arch/register.py @@ -81,18 +81,6 @@ def restore(self, context: MutableMapping[str, Any] = {}) -> None: self.write(reg, val) - # FIXME: this no longer works - # TODO: This needs to be implemented for all archs - def bit(self, reg: Union[str, int]) -> int: - """Get register size in bits. - """ - - if type(reg) is str: - reg = self.register_mapping[reg] - - return self.ql.arch.get_reg_bit(reg) - - @property def arch_pc(self) -> int: """Get the value of the architectural program counter register. diff --git a/qiling/arch/x86.py b/qiling/arch/x86.py index 14d291200..173af493c 100644 --- a/qiling/arch/x86.py +++ b/qiling/arch/x86.py @@ -5,6 +5,7 @@ from struct import pack from functools import cached_property +from typing import Union from unicorn import Uc, UC_ARCH_X86, UC_MODE_16, UC_MODE_32, UC_MODE_64 from capstone import Cs, CS_ARCH_X86, CS_MODE_16, CS_MODE_32, CS_MODE_64 @@ -19,26 +20,6 @@ from qiling.exception import QlGDTError class QlArchIntel(QlArch): - - # TODO: generalize this - def get_reg_bit(self, register: int) -> int: - # all regs in reg_map_misc are 16 bits except of eflags - if register == UC_X86_REG_EFLAGS: - return 32 - - regmaps = ( - (x86_const.reg_map_8, 8), - (x86_const.reg_map_16, 16), - (x86_const.reg_map_32, 32), - (x86_const.reg_map_64, 64), - (x86_const.reg_map_misc, 16), - (x86_const.reg_map_cr, 64 if self.ql.archbit == 64 else 32), - (x86_const.reg_map_st, 32), - (x86_const.reg_map_seg_base, 64 if self.ql.archbit == 64 else 32), - ) - - return next((rsize for rmap, rsize in regmaps if register in rmap.values()), 0) - @cached_property def msr(self) -> QlMsrManager: """Model-Specific Registers. @@ -134,6 +115,35 @@ def disassembler(self) -> Cs: def assembler(self) -> Ks: return Ks(KS_ARCH_X86, KS_MODE_64) + # TODO: generalize this + def __reg_bits(self, register: int) -> int: + # all regs in reg_map_misc are 16 bits except of eflags + if register == UC_X86_REG_EFLAGS: + return 32 + + regmaps = ( + (x86_const.reg_map_8, 8), + (x86_const.reg_map_16, 16), + (x86_const.reg_map_32, 32), + (x86_const.reg_map_64, 64), + (x86_const.reg_map_misc, 16), + (x86_const.reg_map_cr, 64), # 32 bits for x86 + (x86_const.reg_map_st, 32), + (x86_const.reg_map_seg_base, 64), # 32 bits for x86 + ) + + return next((rsize for rmap, rsize in regmaps if register in rmap.values()), 0) + + # note: this method was not generalized for all archs since it requires a bookkeeping + # of all registers, while it is used only by gdb and only for x86-64 + def reg_bits(self, reg: Union[str, int]) -> int: + """Get register size in bits. + """ + + if type(reg) is str: + reg = self.regs.register_mapping[reg] + + return self.__reg_bits(reg) class GDTManager: # Added GDT management module. diff --git a/qiling/debugger/gdb/gdb.py b/qiling/debugger/gdb/gdb.py index f4ca7746f..993951474 100644 --- a/qiling/debugger/gdb/gdb.py +++ b/qiling/debugger/gdb/gdb.py @@ -223,9 +223,9 @@ def handle_g(subcmd): elif self.ql.archtype== QL_ARCH.X8664: for reg in self.tables[QL_ARCH.X8664][:24]: r = self.ql.arch.regs.read(reg) - if self.ql.arch.regs.bit(reg) == 64: + if self.ql.arch.reg_bits(reg) == 64: tmp = self.addr_to_str(r) - elif self.ql.arch.regs.bit(reg) == 32: + elif self.ql.arch.reg_bits(reg) == 32: tmp = self.addr_to_str(r, short = True) s += tmp From 4ff3d7a7300fe97e75293df33bac7eaa15fb3e42 Mon Sep 17 00:00:00 2001 From: elicn Date: Sat, 15 Jan 2022 20:53:43 +0200 Subject: [PATCH 021/406] Properly reset uc on execve --- qiling/os/posix/syscall/unistd.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/qiling/os/posix/syscall/unistd.py b/qiling/os/posix/syscall/unistd.py index 2de9afa3d..1f2cd3b73 100644 --- a/qiling/os/posix/syscall/unistd.py +++ b/qiling/os/posix/syscall/unistd.py @@ -432,8 +432,18 @@ def __read_str_array(addr: int) -> Iterator[str]: if ql.code: return - ql._uc = ql.arch.uc - QlCoreHooks.__init__(ql, ql._uc) + # recreate cached uc + del ql.arch.uc + uc = ql.arch.uc + + # propagate new uc to arch internals + ql.arch.regs.uc = uc + + if hasattr(ql.arch, 'msr'): + ql.arch.msr.uc = uc + + ql.uc = uc + QlCoreHooks.__init__(ql, uc) ql.os.load() ql.loader.run() From 96ef70be815548b3e00b41b3388c9afc68be6474 Mon Sep 17 00:00:00 2001 From: elicn Date: Sat, 15 Jan 2022 20:54:12 +0200 Subject: [PATCH 022/406] Minor changes --- qiling/arch/arch.py | 4 ++-- qiling/arch/register.py | 12 +++++++++--- qiling/arch/riscv.py | 1 + 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/qiling/arch/arch.py b/qiling/arch/arch.py index 2c0601edc..ead0aeb71 100644 --- a/qiling/arch/arch.py +++ b/qiling/arch/arch.py @@ -102,12 +102,12 @@ def get_pc(self) -> int: # Unicorn's CPU state save def context_save(self) -> UcContext: - return self.ql.uc.context_save() + return self.uc.context_save() # Unicorn's CPU state restore method def context_restore(self, saved_context: UcContext): - self.ql.uc.context_restore(saved_context) + self.uc.context_restore(saved_context) @property diff --git a/qiling/arch/register.py b/qiling/arch/register.py index 086fb521f..fab8f3aff 100644 --- a/qiling/arch/register.py +++ b/qiling/arch/register.py @@ -10,12 +10,18 @@ class QlRegisterManager: """This class exposes the ql.arch.regs features that allows you to directly access or assign values to CPU registers of a particular architecture. - - Registers exposed are listed in the *_const.py files in the respective - arch directories and are mapped to Unicorn Engine's definitions """ def __init__(self, uc: Uc, regs_map: Mapping[str, int], pc_reg: str, sp_reg: str): + """Initialize the registers manager. + + Args: + uc: initialized unicorn instance + regs_map: registers names mapped to their corresponding unicorn definitions + pc_reg: name of the architectural program counter register + sp_reg: name of the architectural stack pointer register + """ + # this funny way of initialization is used to avoid calling self setattr and # getattr upon init. if it did, it would go into an endless recursion self.register_mapping: Mapping[str, int] diff --git a/qiling/arch/riscv.py b/qiling/arch/riscv.py index 2cfd1609b..4e9c340ef 100644 --- a/qiling/arch/riscv.py +++ b/qiling/arch/riscv.py @@ -12,6 +12,7 @@ from qiling.arch.arch import QlArch from qiling.arch.register import QlRegisterManager from qiling.arch import riscv_const +from qiling.arch.riscv_const import * from qiling.exception import QlErrorNotImplemented class QlArchRISCV(QlArch): From 9b6e954ce02c26192fe6b6c54440b870be1df2dc Mon Sep 17 00:00:00 2001 From: elicn Date: Sat, 15 Jan 2022 22:03:41 +0200 Subject: [PATCH 023/406] Remove archbit from core and make it a static QlArch property --- qiling/arch/arch.py | 2 ++ qiling/arch/arm.py | 2 ++ qiling/arch/arm64.py | 2 ++ qiling/arch/cortex_m.py | 2 ++ qiling/arch/evm/evm.py | 2 ++ qiling/arch/mips.py | 2 ++ qiling/arch/riscv.py | 2 ++ qiling/arch/riscv64.py | 2 ++ qiling/arch/x86.py | 6 ++++++ qiling/core.py | 16 +++------------- 10 files changed, 25 insertions(+), 13 deletions(-) diff --git a/qiling/arch/arch.py b/qiling/arch/arch.py index ead0aeb71..1f8ed2540 100644 --- a/qiling/arch/arch.py +++ b/qiling/arch/arch.py @@ -15,6 +15,8 @@ from .utils import QlArchUtils class QlArch: + bits: int + def __init__(self, ql: Qiling): self.ql = ql self.utils = QlArchUtils(ql) diff --git a/qiling/arch/arm.py b/qiling/arch/arm.py index dbadc74bf..dac15e468 100644 --- a/qiling/arch/arm.py +++ b/qiling/arch/arm.py @@ -17,6 +17,8 @@ from qiling.exception import QlErrorArch class QlArchARM(QlArch): + bits = 32 + def __init__(self, ql: Qiling): super().__init__(ql) diff --git a/qiling/arch/arm64.py b/qiling/arch/arm64.py index 76528906f..90b089dde 100644 --- a/qiling/arch/arm64.py +++ b/qiling/arch/arm64.py @@ -14,6 +14,8 @@ from qiling.arch.register import QlRegisterManager class QlArchARM64(QlArch): + bits = 64 + @cached_property def uc(self) -> Uc: return Uc(UC_ARCH_ARM64, UC_MODE_ARM) diff --git a/qiling/arch/cortex_m.py b/qiling/arch/cortex_m.py index 9237f4014..7dbb9c77e 100644 --- a/qiling/arch/cortex_m.py +++ b/qiling/arch/cortex_m.py @@ -61,6 +61,8 @@ def __exit__(self, *exc): self.ql.log.info('Exit from interrupt') class QlArchCORTEX_M(QlArchARM): + bits = 32 + @cached_property def uc(self): return Uc(UC_ARCH_ARM, UC_MODE_ARM + UC_MODE_MCLASS + UC_MODE_THUMB) diff --git a/qiling/arch/evm/evm.py b/qiling/arch/evm/evm.py index 97a72bda8..ed87be3d1 100644 --- a/qiling/arch/evm/evm.py +++ b/qiling/arch/evm/evm.py @@ -9,6 +9,8 @@ class QlArchEVM(QlArch): + bits = 1 + def __init__(self, ql) -> None: super(QlArchEVM, self).__init__(ql) self.evm = QlArchEVMEmulator(self.ql) diff --git a/qiling/arch/mips.py b/qiling/arch/mips.py index c1477a36b..406f51f55 100644 --- a/qiling/arch/mips.py +++ b/qiling/arch/mips.py @@ -15,6 +15,8 @@ from qiling.arch.register import QlRegisterManager class QlArchMIPS(QlArch): + bits = 32 + @cached_property def uc(self) -> Uc: endian = { diff --git a/qiling/arch/riscv.py b/qiling/arch/riscv.py index 4e9c340ef..6da356cbb 100644 --- a/qiling/arch/riscv.py +++ b/qiling/arch/riscv.py @@ -16,6 +16,8 @@ from qiling.exception import QlErrorNotImplemented class QlArchRISCV(QlArch): + bits = 32 + @cached_property def uc(self) -> Uc: return Uc(UC_ARCH_RISCV, UC_MODE_RISCV32) diff --git a/qiling/arch/riscv64.py b/qiling/arch/riscv64.py index 5ab0493d0..dcb128edb 100644 --- a/qiling/arch/riscv64.py +++ b/qiling/arch/riscv64.py @@ -15,6 +15,8 @@ from .riscv import QlArchRISCV class QlArchRISCV64(QlArchRISCV): + bits = 64 + @cached_property def uc(self) -> Uc: return Uc(UC_ARCH_RISCV, UC_MODE_RISCV64) diff --git a/qiling/arch/x86.py b/qiling/arch/x86.py index 173af493c..b50624208 100644 --- a/qiling/arch/x86.py +++ b/qiling/arch/x86.py @@ -28,6 +28,8 @@ def msr(self) -> QlMsrManager: return QlMsrManager(self.uc) class QlArchA8086(QlArchIntel): + bits = 16 + @cached_property def uc(self) -> Uc: return Uc(UC_ARCH_X86, UC_MODE_16) @@ -54,6 +56,8 @@ def assembler(self) -> Ks: return Ks(KS_ARCH_X86, KS_MODE_16) class QlArchX86(QlArchIntel): + bits = 32 + @cached_property def uc(self) -> Uc: return Uc(UC_ARCH_X86, UC_MODE_32) @@ -83,6 +87,8 @@ def assembler(self) -> Ks: return Ks(KS_ARCH_X86, KS_MODE_32) class QlArchX8664(QlArchIntel): + bits = 64 + @cached_property def uc(self) -> Uc: return Uc(UC_ARCH_X86, UC_MODE_64) diff --git a/qiling/core.py b/qiling/core.py index e82258606..e013d3a78 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -70,7 +70,6 @@ def __init__( self._ostype = ostype self._archtype = archtype self._archendian = QL_ENDIAN.EL - self._archbit = None self._pointersize = None self._profile = profile self._console = console @@ -155,18 +154,18 @@ def __init__( if not ql_is_valid_arch(self._archtype): raise QlErrorArch("Invalid ARCH: %s" % (self._archtype)) + self._arch = arch_setup(self.archtype, self) ######################## # Archbit & Endianness # ######################## - self._archbit = ql_get_arch_bits(self._archtype) - self._pointersize = (self.archbit // 8) + self._pointersize = (self.arch.bits // 8) if bigendian == True and self._archtype in QL_ARCH_ENDIAN: self._archendian = QL_ENDIAN.EB # Once we finish setting up archendian and arcbit, we can init QlCoreStructs. - QlCoreStructs.__init__(self, self._archendian, self._archbit) + QlCoreStructs.__init__(self, self._archendian, self.arch.bits) ####################################### @@ -195,7 +194,6 @@ def __init__( ############## # Components # ############## - self._arch = arch_setup(self.archtype, self) self.uc = self.arch.uc if not self.interpreter: @@ -402,14 +400,6 @@ def archendian(self) -> QL_ENDIAN: """ return self._archendian - @property - def archbit(self) -> int: - """ The bits of the current architecutre. - - Type: int - """ - return self._archbit - @property def pointersize(self) -> int: """ The pointer size of current architecture. From 4cecedc2bc0d6f00e4490c6b0e214ebf56031011 Mon Sep 17 00:00:00 2001 From: elicn Date: Sat, 15 Jan 2022 22:04:07 +0200 Subject: [PATCH 024/406] Adjust all archbit usages --- examples/hello_x8664_linux_disasm.py | 2 +- qiling/arch/utils.py | 2 +- qiling/cc/intel.py | 2 +- qiling/debugger/gdb/gdb.py | 4 +-- qiling/extensions/idaplugin/qilingida.py | 4 +-- qiling/loader/elf.py | 6 ++-- qiling/os/linux/function_hook.py | 34 +++++++++---------- qiling/os/memory.py | 4 +-- qiling/os/os.py | 2 +- qiling/os/posix/syscall/mman.py | 2 +- qiling/os/posix/syscall/stat.py | 10 +++--- .../protocols/EfiSmmSwDispatch2Protocol.py | 2 +- qiling/os/uefi/uefi.py | 2 +- qiling/os/utils.py | 2 +- .../os/windows/dlls/kernel32/wow64apiset.py | 2 +- qiling/os/windows/dlls/ntoskrnl.py | 6 ++-- qiling/os/windows/utils.py | 2 +- 17 files changed, 44 insertions(+), 44 deletions(-) diff --git a/examples/hello_x8664_linux_disasm.py b/examples/hello_x8664_linux_disasm.py index adfc7234c..9adcfe94c 100644 --- a/examples/hello_x8664_linux_disasm.py +++ b/examples/hello_x8664_linux_disasm.py @@ -20,7 +20,7 @@ def trace(ql: Qiling, address: int, size: int, md: Cs): """ buf = ql.mem.read(address, size) - nibbles = ql.archbit // 4 + nibbles = ql.arch.bits // 4 esc_dgray = "\x1b[90m" esc_reset = "\x1b[39m" diff --git a/qiling/arch/utils.py b/qiling/arch/utils.py index 45aecc5e9..a1b06b981 100644 --- a/qiling/arch/utils.py +++ b/qiling/arch/utils.py @@ -36,7 +36,7 @@ def disassembler(self, ql: Qiling, address: int, size: int): qd = ql.arch.disassembler offset, name = self.get_offset_and_name(address) - log_data = f'{address:0{ql.archbit // 4}x} [{name:20s} + {offset:#08x}] {tmp.hex(" "):30s}' + log_data = f'{address:0{ql.arch.bits // 4}x} [{name:20s} + {offset:#08x}] {tmp.hex(" "):30s}' log_insn = '\n> '.join(f'{insn.mnemonic:20s} {insn.op_str}' for insn in qd.disasm(tmp, address)) ql.log.info(log_data + log_insn) diff --git a/qiling/cc/intel.py b/qiling/cc/intel.py index 1e1161200..892f0fcc1 100644 --- a/qiling/cc/intel.py +++ b/qiling/cc/intel.py @@ -21,7 +21,7 @@ def __init__(self, ql: Qiling): 16: UC_X86_REG_AX, 32: UC_X86_REG_EAX, 64: UC_X86_REG_RAX - }[ql.archbit] + }[ql.arch.bits] super().__init__(ql, retreg) diff --git a/qiling/debugger/gdb/gdb.py b/qiling/debugger/gdb/gdb.py index 993951474..33f10f3e8 100644 --- a/qiling/debugger/gdb/gdb.py +++ b/qiling/debugger/gdb/gdb.py @@ -88,7 +88,7 @@ def __init__(self, ql: Qiling, ip: str = '127.0.01', port: int = 9999): def addr_to_str(self, addr: int, short: bool = False, endian: Literal['little', 'big'] = 'big') -> str: # a hacky way to divide archbits by 2 if short, and leave it unchanged if not - nbits = self.ql.archbit // (int(short) + 1) + nbits = self.ql.arch.bits // (int(short) + 1) if nbits == 64: s = f'{int.from_bytes(self.ql.pack64(addr), byteorder=endian):016x}' @@ -182,7 +182,7 @@ def gdbqmark_converter(arch): idhex, spid, pcid = gdbqmark_converter(self.ql.archtype) sp = self.addr_to_str(self.ql.arch.regs.arch_sp) pc = self.addr_to_str(self.ql.arch.regs.arch_pc) - nullfill = "0" * int(self.ql.archbit / 4) + nullfill = "0" * int(self.ql.arch.bits / 4) if self.ql.archtype== QL_ARCH.MIPS: if self.ql.archendian == QL_ENDIAN.EB: diff --git a/qiling/extensions/idaplugin/qilingida.py b/qiling/extensions/idaplugin/qilingida.py index 32fa9a342..3563d22fe 100644 --- a/qiling/extensions/idaplugin/qilingida.py +++ b/qiling/extensions/idaplugin/qilingida.py @@ -923,9 +923,9 @@ def start(self, *args, **kwargs): if elf_header['e_type'] == 'ET_EXEC': self.baseaddr = self.ql.os.elf_mem_start elif elf_header['e_type'] == 'ET_DYN': - if self.ql.archbit == 32: + if self.ql.arch.bits == 32: self.baseaddr = int(self.ql.os.profile.get("OS32", "load_address"), 16) - elif self.ql.archbit == 64: + elif self.ql.arch.bits == 64: self.baseaddr = int(self.ql.os.profile.get("OS64", "load_address"), 16) else: self.baseaddr = 0x0 diff --git a/qiling/loader/elf.py b/qiling/loader/elf.py index 77bde7ea0..81da87cbd 100644 --- a/qiling/loader/elf.py +++ b/qiling/loader/elf.py @@ -76,7 +76,7 @@ def run(self): section = { 32 : 'OS32', 64 : 'OS64' - }[self.ql.archbit] + }[self.ql.arch.bits] self.profile = self.ql.os.profile[section] @@ -343,9 +343,9 @@ def __push_str(top: int, s: str) -> int: elf_phnum = elffile['e_phnum'] elf_entry = load_address + elffile['e_entry'] - if self.ql.archbit == 64: + if self.ql.arch.bits == 64: elf_hwcap = 0x078bfbfd - elif self.ql.archbit == 32: + elif self.ql.arch.bits == 32: elf_hwcap = 0x1fb8d7 if self.ql.archendian == QL_ENDIAN.EB: diff --git a/qiling/os/linux/function_hook.py b/qiling/os/linux/function_hook.py index e0b9bcff8..6585b9592 100644 --- a/qiling/os/linux/function_hook.py +++ b/qiling/os/linux/function_hook.py @@ -491,7 +491,7 @@ def __init__(self, ql, symtab, endian = 0): self.symtab = symtab self.endian = endian - self.symclass = ELF32_Sym if self.ql.archbit == 32 else ELF64_Sym + self.symclass = ELF32_Sym if self.ql.arch.bits == 32 else ELF64_Sym def __getitem__(self, idx): buf = self.ql.mem.read(self.symtab + idx * self.symclass.Sym_SIZE, self.symclass.Sym_SIZE) @@ -536,19 +536,19 @@ def __init__(self, ql, phoff, phnum, phentsize, load_base, hook_mem): self.strtab_size = None self.symtab = None - self.syment = ELF32_Sym.Sym_SIZE if ql.archbit == 32 else ELF64_Sym.Sym_SIZE + self.syment = ELF32_Sym.Sym_SIZE if ql.arch.bits == 32 else ELF64_Sym.Sym_SIZE self.plt_rel_size = None self.plt_rel = None - self.plt_rel_type = DT_REL if ql.archbit == 32 else DT_RELA + self.plt_rel_type = DT_REL if ql.arch.bits == 32 else DT_RELA self.rela = None self.rela_size = None - self.relaent = ELF32_Rela.Rela_SIZE if ql.archbit == 32 else ELF64_Rela.Rela_SIZE + self.relaent = ELF32_Rela.Rela_SIZE if ql.arch.bits == 32 else ELF64_Rela.Rela_SIZE self.rel = None self.rel_size = None - self.relent = ELF32_Rel.Rel_SIZE if ql.archbit == 32 else ELF64_Rel.Rel_SIZE + self.relent = ELF32_Rel.Rel_SIZE if ql.arch.bits == 32 else ELF64_Rel.Rel_SIZE self.plt_got = None self.mips_local_gotno = None @@ -700,9 +700,9 @@ def parse_program_header64(self): return def parse_program_header(self): - if self.ql.archbit == 64: + if self.ql.arch.bits == 64: return self.parse_program_header64() - elif self.ql.archbit == 32: + elif self.ql.arch.bits == 32: return self.parse_program_header32() def parse_dynamic64(self): @@ -768,9 +768,9 @@ def parse_dynamic32(self): return def parse_dynamic(self): - if self.ql.archbit == 64: + if self.ql.arch.bits == 64: return self.parse_dynamic64() - elif self.ql.archbit == 32: + elif self.ql.arch.bits == 32: return self.parse_dynamic32() def _parse(self): @@ -859,31 +859,31 @@ def _parse(self): if self.rela != None and self.rela_size != None: rela_buf = self.ql.mem.read(self.rela, self.rela_size) rela_ptr = self.rela - if self.ql.archbit == 32: + if self.ql.arch.bits == 32: self.rela = [ELF32_Rela(rela_buf[_ * self.relaent : (_ + 1) * self.relaent], self.endian, rela_ptr + _ * self.relaent) for _ in range(self.rela_size // self.relaent)] - elif self.ql.archbit == 64: + elif self.ql.arch.bits == 64: self.rela = [ELF64_Rela(rela_buf[_ * self.relaent : (_ + 1) * self.relaent], self.endian, rela_ptr + _ * self.relaent) for _ in range(self.rela_size // self.relaent)] if self.rel != None and self.rel_size != None: rel_buf = self.ql.mem.read(self.rel, self.rel_size) rel_ptr = self.rel - if self.ql.archbit == 32: + if self.ql.arch.bits == 32: self.rel = [ELF32_Rel(rel_buf[_ * self.relent : (_ + 1) * self.relent], self.endian, rel_ptr + _ * self.relent) for _ in range(self.rel_size // self.relent)] - elif self.ql.archbit == 64: + elif self.ql.arch.bits == 64: self.rel = [ELF64_Rel(rel_buf[_ * self.relent : (_ + 1) * self.relent], self.endian, rel_ptr + _ * self.relent) for _ in range(self.rel_size // self.relent)] if self.plt_rel != None and self.plt_rel_size != None: plt_rel_buf = self.ql.mem.read(self.plt_rel, self.plt_rel_size) plt_rel_ptr = self.plt_rel if self.plt_rel_type == DT_REL: - if self.ql.archbit == 32: + if self.ql.arch.bits == 32: self.plt_rel = [ELF32_Rel(plt_rel_buf[_ * self.relent : (_ + 1) * self.relent], self.endian, plt_rel_ptr + _ * self.relent) for _ in range(self.plt_rel_size // self.relent)] - elif self.ql.archbit == 64: + elif self.ql.arch.bits == 64: self.plt_rel = [ELF64_Rel(plt_rel_buf[_ * self.relent : (_ + 1) * self.relent], self.endian, plt_rel_ptr + _ * self.relent) for _ in range(self.plt_rel_size // self.relent)] else: - if self.ql.archbit == 32: + if self.ql.arch.bits == 32: self.plt_rel = [ELF32_Rela(plt_rel_buf[_ * self.relaent : (_ + 1) * self.relaent], self.endian, plt_rel_ptr + _ * self.relaent) for _ in range(self.plt_rel_size // self.relaent)] - elif self.ql.archbit == 64: + elif self.ql.arch.bits == 64: self.plt_rel = [ELF64_Rela(plt_rel_buf[_ * self.relaent : (_ + 1) * self.relaent], self.endian, plt_rel_ptr + _ * self.relaent) for _ in range(self.plt_rel_size // self.relaent)] if self.symtab != None: diff --git a/qiling/os/memory.py b/qiling/os/memory.py index 80f292053..42b902bd2 100644 --- a/qiling/os/memory.py +++ b/qiling/os/memory.py @@ -35,10 +35,10 @@ def __init__(self, ql: Qiling): 16 : (1 << 20) - 1 # 20bit address line } - if ql.archbit not in bit_stuff: + if ql.arch.bits not in bit_stuff: raise QlErrorStructConversion("Unsupported Qiling archtecture for memory manager") - max_addr = bit_stuff[ql.archbit] + max_addr = bit_stuff[ql.arch.bits] self.max_addr = max_addr self.max_mem_addr = max_addr diff --git a/qiling/os/os.py b/qiling/os/os.py index 3e2cdefe1..0d2fe8e6f 100644 --- a/qiling/os/os.py +++ b/qiling/os/os.py @@ -61,7 +61,7 @@ def __init__(self, ql: Qiling, resolvers: Mapping[Any, Resolver] = {}): 16: 0xfffff, # 20bit address lane 32: 0x8fffffff, 64: 0xffffffffffffffff - }.get(self.ql.archbit, None) + }.get(self.ql.arch.bits, None) if self.ql.code: self.code_ram_size = int(self.profile.get("CODE", "ram_size"), 16) diff --git a/qiling/os/posix/syscall/mman.py b/qiling/os/posix/syscall/mman.py index 233b229ca..376c10e76 100755 --- a/qiling/os/posix/syscall/mman.py +++ b/qiling/os/posix/syscall/mman.py @@ -65,7 +65,7 @@ def syscall_mmap_impl(ql: Qiling, addr: int, mlen: int, prot: int, flags: int, f 2 : 'mmap2' }[ver] - if ql.archbit == 64: + if ql.arch.bits == 64: fd = ql.unpack64(ql.pack64(fd)) elif ql.archtype == QL_ARCH.MIPS: diff --git a/qiling/os/posix/syscall/stat.py b/qiling/os/posix/syscall/stat.py index 2b8d8fc8d..9e60307ce 100644 --- a/qiling/os/posix/syscall/stat.py +++ b/qiling/os/posix/syscall/stat.py @@ -929,7 +929,7 @@ class QNXARMStat64(ctypes.Structure): _pack_ = 8 def get_stat64_struct(ql: Qiling): - if ql.archbit == 64: + if ql.arch.bits == 64: ql.log.warning(f"Trying to stat64 on a 64bit system with {ql.ostype} and {ql.archtype}!") if ql.ostype == QL_OS.LINUX: if ql.archtype == QL_ARCH.X86: @@ -949,7 +949,7 @@ def get_stat64_struct(ql: Qiling): def get_stat_struct(ql: Qiling): if ql.ostype == QL_OS.FREEBSD: - if ql.archtype == QL_ARCH.X8664 or ql.archbit == 64: + if ql.archtype == QL_ARCH.X8664 or ql.arch.bits == 64: return FreeBSDX8664Stat() else: return FreeBSDX86Stat() @@ -961,7 +961,7 @@ def get_stat_struct(ql: Qiling): elif ql.archtype == QL_ARCH.X86: return LinuxX86Stat() elif ql.archtype == QL_ARCH.MIPS: - if ql.archbit == 64: + if ql.arch.bits == 64: return LinuxMips64Stat() else: return LinuxMips32Stat() @@ -1233,7 +1233,7 @@ def statx_convert_timestamp(tv_sec, tv_nsec): tv_sec = struct.unpack('i', struct.pack('f', tv_sec))[0] tv_nsec = struct.unpack('i', struct.pack('f', tv_nsec))[0] - if ql.archbit == 32: + if ql.arch.bits == 32: return StatxTimestamp32(tv_sec=tv_sec, tv_nsec=tv_nsec) else: return StatxTimestamp64(tv_sec=tv_sec, tv_nsec=tv_nsec) @@ -1253,7 +1253,7 @@ def minor(dev): else: st = Stat(real_path, fd) - if ql.archbit == 32: + if ql.arch.bits == 32: Statx = Statx32 else: Statx = Statx64 diff --git a/qiling/os/uefi/protocols/EfiSmmSwDispatch2Protocol.py b/qiling/os/uefi/protocols/EfiSmmSwDispatch2Protocol.py index cda5e9461..29e78ad08 100644 --- a/qiling/os/uefi/protocols/EfiSmmSwDispatch2Protocol.py +++ b/qiling/os/uefi/protocols/EfiSmmSwDispatch2Protocol.py @@ -61,7 +61,7 @@ def hook_Register(ql: Qiling, address: int, params): # a value of -1 indicates that the swsmi index for this handler is flexible and # should be assigned by the protocol - if idx == ((1 << ql.archbit) - 1): + if idx == ((1 << ql.arch.bits) - 1): idx = next((i for i in range(1, MAXIMUM_SWI_VALUE) if i not in handlers), None) if idx is None: diff --git a/qiling/os/uefi/uefi.py b/qiling/os/uefi/uefi.py index 3b034ade7..b7d22b54a 100644 --- a/qiling/os/uefi/uefi.py +++ b/qiling/os/uefi/uefi.py @@ -33,7 +33,7 @@ def __init__(self, ql: Qiling): cc: QlCC = { 32: intel.cdecl, 64: intel.ms64 - }[ql.archbit](ql) + }[ql.arch.bits](ql) self.fcall = QlFunctionCall(ql, cc) diff --git a/qiling/os/utils.py b/qiling/os/utils.py index a5c3294c3..20d0c69b2 100644 --- a/qiling/os/utils.py +++ b/qiling/os/utils.py @@ -139,7 +139,7 @@ def __assign_arg(name: str, value: str) -> str: # optional prefixes and suffixes fret = f' = {ret}' if ret is not None else '' fpass = f' (PASSTHRU)' if passthru else '' - faddr = f'{address:#0{self.ql.archbit // 4 + 2}x}: ' if self.ql.verbose >= QL_VERBOSE.DEBUG else '' + faddr = f'{address:#0{self.ql.arch.bits // 4 + 2}x}: ' if self.ql.verbose >= QL_VERBOSE.DEBUG else '' log = f'{faddr}{fname}({fargs}){fret}{fpass}' diff --git a/qiling/os/windows/dlls/kernel32/wow64apiset.py b/qiling/os/windows/dlls/kernel32/wow64apiset.py index b5097bb5b..84600e9f3 100644 --- a/qiling/os/windows/dlls/kernel32/wow64apiset.py +++ b/qiling/os/windows/dlls/kernel32/wow64apiset.py @@ -19,7 +19,7 @@ def hook_IsWow64Process(ql: Qiling, address: int, params): Wow64Process = params["Wow64Process"] - if ql.archbit != 32: + if ql.arch.bits != 32: raise QlErrorNotImplemented("API not implemented") false = b'\x00' diff --git a/qiling/os/windows/dlls/ntoskrnl.py b/qiling/os/windows/dlls/ntoskrnl.py index 7936854e8..e1578f4e2 100644 --- a/qiling/os/windows/dlls/ntoskrnl.py +++ b/qiling/os/windows/dlls/ntoskrnl.py @@ -756,7 +756,7 @@ def _NtQuerySystemInformation(ql: Qiling, address: int, params): # if SystemInformationLength = 0, we return the total size in ReturnLength NumberOfModules = 1 - if ql.archbit == 64: + if ql.arch.bits == 64: # only 1 module for ntoskrnl.exe # FIXME: let users customize this? size = 4 + ctypes.sizeof(RTL_PROCESS_MODULE_INFORMATION64) * NumberOfModules @@ -770,7 +770,7 @@ def _NtQuerySystemInformation(ql: Qiling, address: int, params): return STATUS_INFO_LENGTH_MISMATCH else: # return all the loaded modules - if ql.archbit == 64: + if ql.arch.bits == 64: module = RTL_PROCESS_MODULE_INFORMATION64() else: module = RTL_PROCESS_MODULE_INFORMATION32() @@ -1072,7 +1072,7 @@ def hook_PsLookupProcessByProcessId(ql: Qiling, address: int, params): ProcessId = params["ProcessId"] Process = params["Process"] - if ql.archbit == 64: + if ql.arch.bits == 64: obj = EPROCESS64 else: obj = EPROCESS32 diff --git a/qiling/os/windows/utils.py b/qiling/os/windows/utils.py index 6b6cf6a77..c31db51e8 100644 --- a/qiling/os/windows/utils.py +++ b/qiling/os/windows/utils.py @@ -47,7 +47,7 @@ def io_Write(ql: Qiling, in_buffer: bytes): # raise error? return (False, None) - if ql.archbit == 32: + if ql.arch.bits == 32: buf = ql.mem.read(ql.loader.driver_object.DeviceObject, ctypes.sizeof(DEVICE_OBJECT32)) device_object = DEVICE_OBJECT32.from_buffer(buf) else: From b3f37c8fd9c7e6a32176eebc18eefcd26e579cd3 Mon Sep 17 00:00:00 2001 From: elicn Date: Sat, 15 Jan 2022 22:08:46 +0200 Subject: [PATCH 025/406] Cleanup: remove ql_get_arch_bits --- qiling/extensions/idaplugin/qilingida.py | 7 +++---- qiling/utils.py | 15 --------------- 2 files changed, 3 insertions(+), 19 deletions(-) diff --git a/qiling/extensions/idaplugin/qilingida.py b/qiling/extensions/idaplugin/qilingida.py index 3563d22fe..b25faacd1 100644 --- a/qiling/extensions/idaplugin/qilingida.py +++ b/qiling/extensions/idaplugin/qilingida.py @@ -51,7 +51,6 @@ from qiling.arch.arm_const import reg_map as arm_reg_map from qiling.arch.arm64_const import reg_map as arm64_reg_map from qiling.arch.mips_const import reg_map as mips_reg_map -from qiling.utils import ql_get_arch_bits from qiling import __version__ as QLVERSION from qiling.os.filestruct import ql_file from keystone import * @@ -556,7 +555,7 @@ def SetStack(self, ql:Qiling): if arch == "": return - reg_bit_size = ql_get_arch_bits(arch) + reg_bit_size = ql.arch.bits reg_byte_size = reg_bit_size // 8 value_format = '% .16X' if reg_bit_size == 64 else '% .8X' @@ -898,7 +897,7 @@ class QlEmuQiling: def __init__(self): self.path = None self.rootfs = None - self.ql = None + self.ql: Qiling self.status = None self.exit_addr = None self.baseaddr = None @@ -935,7 +934,7 @@ def run(self, begin=None, end=None): def set_reg(self): reglist = QlEmuMisc.get_reg_map(self.ql) - regs = [ [ row, int(self.ql.arch.regs.read(row)), ql_get_arch_bits(self.ql.archtype) ] for row in reglist ] + regs = [ [ row, int(self.ql.arch.regs.read(row)), self.ql.arch.bits ] for row in reglist ] regs_len = len(regs) RegDig = QlEmuRegDialog(regs) if RegDig.show(): diff --git a/qiling/utils.py b/qiling/utils.py index 0e9977bfd..6156b53ec 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -165,21 +165,6 @@ def wrapper(*args, **kw): return decorator -def ql_get_arch_bits(arch: QL_ARCH) -> int: - if arch in QL_ARCH_1BIT: - return 1 - - if arch in QL_ARCH_16BIT: - return 16 - - if arch in QL_ARCH_32BIT: - return 32 - - if arch in QL_ARCH_64BIT: - return 64 - - raise QlErrorArch("Invalid Arch Bit") - def enum_values(e: Type[Enum]) -> Container: return e.__members__.values() From dab44dc96993cca00cf165226b1e7fc12eda71d7 Mon Sep 17 00:00:00 2001 From: elicn Date: Sat, 15 Jan 2022 22:09:52 +0200 Subject: [PATCH 026/406] Cleanup: remove unused definitions --- qiling/const.py | 4 ---- qiling/utils.py | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/qiling/const.py b/qiling/const.py index 415ad8173..cc07e3c2e 100644 --- a/qiling/const.py +++ b/qiling/const.py @@ -55,10 +55,6 @@ class QL_INTERCEPT(IntEnum): QL_DEBUGGER_ALL = (QL_DEBUGGER.IDAPRO, QL_DEBUGGER.GDB, QL_DEBUGGER.QDB) QL_ARCH_ENDIAN = (QL_ARCH.MIPS, QL_ARCH.ARM) -QL_ARCH_1BIT = (QL_ARCH.EVM,) -QL_ARCH_16BIT = (QL_ARCH.A8086,) -QL_ARCH_32BIT = (QL_ARCH.ARM, QL_ARCH.ARM_THUMB, QL_ARCH.MIPS, QL_ARCH.X86, QL_ARCH.CORTEX_M, QL_ARCH.RISCV) -QL_ARCH_64BIT = (QL_ARCH.ARM64, QL_ARCH.X8664, QL_ARCH.RISCV64) QL_OS_NONPID = (QL_OS.DOS, QL_OS.UEFI) QL_OS_POSIX = (QL_OS.LINUX, QL_OS.FREEBSD, QL_OS.MACOS, QL_OS.QNX) diff --git a/qiling/utils.py b/qiling/utils.py index 6156b53ec..b34427ea9 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -18,7 +18,7 @@ from unicorn import UC_ERR_READ_UNMAPPED, UC_ERR_FETCH_UNMAPPED from .exception import * -from .const import QL_VERBOSE, QL_ARCH, QL_ENDIAN, QL_OS, QL_DEBUGGER, QL_ARCH_1BIT, QL_ARCH_16BIT, QL_ARCH_32BIT, QL_ARCH_64BIT +from .const import QL_VERBOSE, QL_ARCH, QL_ENDIAN, QL_OS, QL_DEBUGGER from .const import debugger_map, arch_map, os_map, arch_os_map, loader_map FMT_STR = "%(levelname)s\t%(message)s" From b83310f8976fa829c0e453697360d56b832b9dc5 Mon Sep 17 00:00:00 2001 From: elicn Date: Sat, 15 Jan 2022 22:43:31 +0200 Subject: [PATCH 027/406] Remove pointersize from core and make it a QlArch property --- qiling/arch/arch.py | 3 +++ qiling/core.py | 11 ----------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/qiling/arch/arch.py b/qiling/arch/arch.py index 1f8ed2540..a1fccbb8e 100644 --- a/qiling/arch/arch.py +++ b/qiling/arch/arch.py @@ -37,6 +37,9 @@ def regs(self) -> QlRegisterManager: pass + @property + def pointersize(self) -> int: + return self.bits // 8 def stack_push(self, value: int) -> int: """Push a value onto the architectural stack. diff --git a/qiling/core.py b/qiling/core.py index e013d3a78..3002b0a16 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -70,7 +70,6 @@ def __init__( self._ostype = ostype self._archtype = archtype self._archendian = QL_ENDIAN.EL - self._pointersize = None self._profile = profile self._console = console self._log_file = log_file @@ -159,8 +158,6 @@ def __init__( ######################## # Archbit & Endianness # ######################## - self._pointersize = (self.arch.bits // 8) - if bigendian == True and self._archtype in QL_ARCH_ENDIAN: self._archendian = QL_ENDIAN.EB @@ -400,14 +397,6 @@ def archendian(self) -> QL_ENDIAN: """ return self._archendian - @property - def pointersize(self) -> int: - """ The pointer size of current architecture. - - Type: int - """ - return self._pointersize - @property def code(self) -> bytes: """ The shellcode to execute. From 84ccfe095504b626867eec92d66023a595aeb757 Mon Sep 17 00:00:00 2001 From: elicn Date: Sat, 15 Jan 2022 22:44:07 +0200 Subject: [PATCH 028/406] Adjust all pointersize usages --- examples/hello_arm_uboot.py | 2 +- qiling/arch/arch.py | 8 ++-- qiling/cc/__init__.py | 2 +- qiling/debugger/qdb/frontend.py | 14 +++--- qiling/debugger/qdb/qdb.py | 2 +- qiling/extensions/idaplugin/qilingida.py | 2 +- qiling/loader/elf.py | 22 +++++----- qiling/loader/pe.py | 32 +++++++------- qiling/os/linux/function_hook.py | 44 +++++++++---------- qiling/os/memory.py | 2 +- qiling/os/os.py | 2 +- qiling/os/posix/syscall/net.py | 2 +- qiling/os/posix/syscall/select.py | 2 +- qiling/os/posix/syscall/sendfile.py | 2 +- qiling/os/posix/syscall/socket.py | 2 +- qiling/os/posix/syscall/time.py | 2 +- qiling/os/posix/syscall/uio.py | 4 +- qiling/os/posix/syscall/unistd.py | 4 +- qiling/os/uefi/bs.py | 4 +- .../protocols/EfiSmmSwDispatch2Protocol.py | 2 +- qiling/os/uefi/protocols/common.py | 4 +- qiling/os/uefi/smm.py | 6 +-- qiling/os/uefi/uefi.py | 4 +- qiling/os/uefi/utils.py | 6 +-- qiling/os/utils.py | 2 +- qiling/os/windows/dlls/advapi32.py | 2 +- qiling/os/windows/dlls/kernel32/memoryapi.py | 4 +- qiling/os/windows/dlls/kernel32/synchapi.py | 2 +- qiling/os/windows/dlls/msvcrt.py | 20 ++++----- qiling/os/windows/dlls/ntdll.py | 6 +-- qiling/os/windows/dlls/ntoskrnl.py | 2 +- qiling/os/windows/fiber.py | 6 +-- qiling/os/windows/structs.py | 4 +- tests/test_blob.py | 2 +- tests/test_pe.py | 2 +- 35 files changed, 114 insertions(+), 114 deletions(-) diff --git a/examples/hello_arm_uboot.py b/examples/hello_arm_uboot.py index 2d0224251..aa83f6419 100644 --- a/examples/hello_arm_uboot.py +++ b/examples/hello_arm_uboot.py @@ -47,7 +47,7 @@ def partial_run_init(ql): ql.arch.regs.arch_sp -= 0x20 argv_ptr = ql.arch.regs.arch_sp ql.mem.write(argv_ptr, ql.pack(arg0_ptr)) - ql.mem.write(argv_ptr + ql.pointersize, ql.pack(arg1_ptr)) + ql.mem.write(argv_ptr + ql.arch.pointersize, ql.pack(arg1_ptr)) ql.arch.regs.r2 = 2 ql.arch.regs.r3 = argv_ptr diff --git a/qiling/arch/arch.py b/qiling/arch/arch.py index a1fccbb8e..3ccea732b 100644 --- a/qiling/arch/arch.py +++ b/qiling/arch/arch.py @@ -50,7 +50,7 @@ def stack_push(self, value: int) -> int: Returns: the top of stack after pushing the value """ - self.regs.arch_sp -= self.ql.pointersize + self.regs.arch_sp -= self.pointersize self.ql.mem.write(self.regs.arch_sp, self.ql.pack(value)) return self.regs.arch_sp @@ -62,8 +62,8 @@ def stack_pop(self) -> int: Returns: the value at the top of stack """ - data = self.ql.unpack(self.ql.mem.read(self.regs.arch_sp, self.ql.pointersize)) - self.regs.arch_sp += self.ql.pointersize + data = self.ql.unpack(self.ql.mem.read(self.regs.arch_sp, self.pointersize)) + self.regs.arch_sp += self.pointersize return data @@ -82,7 +82,7 @@ def stack_read(self, offset: int) -> int: Returns: the value at the specified address """ - return self.ql.unpack(self.ql.mem.read(self.regs.arch_sp + offset, self.ql.pointersize)) + return self.ql.unpack(self.ql.mem.read(self.regs.arch_sp + offset, self.pointersize)) def stack_write(self, offset: int, value: int) -> None: diff --git a/qiling/cc/__init__.py b/qiling/cc/__init__.py index 8e4005b75..714c5569c 100644 --- a/qiling/cc/__init__.py +++ b/qiling/cc/__init__.py @@ -115,7 +115,7 @@ def __init__(self, ql: Qiling, retreg: int): super().__init__(ql) # native address size in bytes - self._asize = self.ql.pointersize + self._asize = self.ql.arch.pointersize # return value register self._retreg = retreg diff --git a/qiling/debugger/qdb/frontend.py b/qiling/debugger/qdb/frontend.py index b6c57f150..b326ea93c 100644 --- a/qiling/debugger/qdb/frontend.py +++ b/qiling/debugger/qdb/frontend.py @@ -110,8 +110,8 @@ def unpack(bs, sz): offset = line * sz * 4 print(f"0x{addr+offset:x}:\t", end="") - idx = line * ql.pointersize - for each in mem_read[idx:idx+ql.pointersize]: + idx = line * ql.arch.pointersize + for each in mem_read[idx:idx+ql.arch.pointersize]: data = unpack(each, sz) prefix = "0x" if ft in ("x", "a") else "" pad = '0' + str(sz*2) if ft in ('x', 'a', 't') else '' @@ -239,17 +239,17 @@ def context_reg(ql: Qiling, saved_states: Optional[Mapping[str, int]] = None, /, with context_printer(ql, "[ STACK ]", ruler="─"): for idx in range(10): - addr = ql.arch.regs.arch_sp + idx * ql.pointersize - val = ql.mem.read(addr, ql.pointersize) - print(f"$sp+0x{idx*ql.pointersize:02x}│ [0x{addr:08x}] —▸ 0x{ql.unpack(val):08x}", end="") + addr = ql.arch.regs.arch_sp + idx * ql.arch.pointersize + val = ql.mem.read(addr, ql.arch.pointersize) + print(f"$sp+0x{idx*ql.arch.pointersize:02x}│ [0x{addr:08x}] —▸ 0x{ql.unpack(val):08x}", end="") # try to dereference wether it's a pointer - if (buf := _try_read(ql, addr, ql.pointersize))[0] is not None: + if (buf := _try_read(ql, addr, ql.arch.pointersize))[0] is not None: if (addr := ql.unpack(buf[0])): # try to dereference again - if (buf := _try_read(ql, addr, ql.pointersize))[0] is not None: + if (buf := _try_read(ql, addr, ql.arch.pointersize))[0] is not None: try: s = ql.mem.string(addr) except: diff --git a/qiling/debugger/qdb/qdb.py b/qiling/debugger/qdb/qdb.py index 36c147599..4525b1197 100644 --- a/qiling/debugger/qdb/qdb.py +++ b/qiling/debugger/qdb/qdb.py @@ -323,7 +323,7 @@ def do_disassemble(self: QlQdb, address: Optional[int] = 0, *args) -> None: """ try: - context_asm(self.ql, _parse_int(address), self.ql.pointersize) + context_asm(self.ql, _parse_int(address), self.ql.arch.pointersize) except: print(f"{color.RED}[!] something went wrong ...{color.END}") diff --git a/qiling/extensions/idaplugin/qilingida.py b/qiling/extensions/idaplugin/qilingida.py index b25faacd1..efd01c48e 100644 --- a/qiling/extensions/idaplugin/qilingida.py +++ b/qiling/extensions/idaplugin/qilingida.py @@ -1460,7 +1460,7 @@ def _force_execution_with_microcode(self, ql, ida_addr): ins_list = list(IDA.micro_code_from_mbb(next_mbb)) first_ins = ins_list[0] imm = first_ins.l.nnn.value - reg_name = ida_hexrays.get_mreg_name(first_ins.d.r, ql.pointersize) + reg_name = ida_hexrays.get_mreg_name(first_ins.d.r, ql.arch.pointersize) logging.info(f"Froce set {reg_name} to {hex(imm)}") ql.arch.regs.__setattr__(reg_name, imm) return True diff --git a/qiling/loader/elf.py b/qiling/loader/elf.py index 81da87cbd..0704765bc 100644 --- a/qiling/loader/elf.py +++ b/qiling/loader/elf.py @@ -309,7 +309,7 @@ def __push_str(top: int, s: str) -> int: """ data = (s if isinstance(s, bytes) else s.encode("utf-8")) + b'\x00' - top = QlLoaderELF.align(top - len(data), self.ql.pointersize) + top = QlLoaderELF.align(top - len(data), self.ql.arch.pointersize) self.ql.mem.write(top, data) return top @@ -506,7 +506,7 @@ def __get_symbol(name: str) -> Optional[Symbol]: # we need to lookup from address to symbol, so we can find the right callback # for sys_xxx handler for syscall, the address must be aligned to pointer size if symbol_name.startswith('sys_'): - self.ql.os.hook_addr = QlLoaderELF.align_up(self.ql.os.hook_addr, self.ql.pointersize) + self.ql.os.hook_addr = QlLoaderELF.align_up(self.ql.os.hook_addr, self.ql.arch.pointersize) self.import_symbols[self.ql.os.hook_addr] = symbol_name @@ -518,7 +518,7 @@ def __get_symbol(name: str) -> Optional[Symbol]: # we also need to do reverse lookup from symbol to address rev_reloc_symbols[symbol_name] = self.ql.os.hook_addr sym_offset = self.ql.os.hook_addr - mem_start - self.ql.os.hook_addr += self.ql.pointersize + self.ql.os.hook_addr += self.ql.arch.pointersize else: # local symbol _section = elffile.get_section(_symbol['st_shndx']) @@ -629,7 +629,7 @@ def load_driver(self, elffile: ELFFile, stack_addr: int, loadbase: int = 0) -> N self.ql.os.entry_point = self.entry_point = entry_point self.elf_entry = self.ql.os.elf_entry = self.ql.os.entry_point - self.stack_address = QlLoaderELF.align(stack_addr, self.ql.pointersize) + self.stack_address = QlLoaderELF.align(stack_addr, self.ql.arch.pointersize) self.load_address = loadbase # remember address of syscall table, so external tools can access to it @@ -648,20 +648,20 @@ def load_driver(self, elffile: ELFFile, stack_addr: int, loadbase: int = 0) -> N if hasattr(SYSCALL_NR, tmp_sc): syscall_id = getattr(SYSCALL_NR, tmp_sc).value - dest = SYSCALL_MEM + syscall_id * self.ql.pointersize + dest = SYSCALL_MEM + syscall_id * self.ql.arch.pointersize self.ql.log.debug(f'Writing syscall {tmp_sc} to {dest:#x}') self.ql.mem.write(dest, self.ql.pack(addr)) # write syscall addresses into syscall table - self.ql.mem.write(SYSCALL_MEM + 0 * self.ql.pointersize, self.ql.pack(self.ql.os.hook_addr + 0 * self.ql.pointersize)) - self.ql.mem.write(SYSCALL_MEM + 1 * self.ql.pointersize, self.ql.pack(self.ql.os.hook_addr + 1 * self.ql.pointersize)) - self.ql.mem.write(SYSCALL_MEM + 2 * self.ql.pointersize, self.ql.pack(self.ql.os.hook_addr + 2 * self.ql.pointersize)) + self.ql.mem.write(SYSCALL_MEM + 0 * self.ql.arch.pointersize, self.ql.pack(self.ql.os.hook_addr + 0 * self.ql.arch.pointersize)) + self.ql.mem.write(SYSCALL_MEM + 1 * self.ql.arch.pointersize, self.ql.pack(self.ql.os.hook_addr + 1 * self.ql.arch.pointersize)) + self.ql.mem.write(SYSCALL_MEM + 2 * self.ql.arch.pointersize, self.ql.pack(self.ql.os.hook_addr + 2 * self.ql.arch.pointersize)) # setup hooks for read/write/open syscalls - self.import_symbols[self.ql.os.hook_addr + 0 * self.ql.pointersize] = hook_sys_read - self.import_symbols[self.ql.os.hook_addr + 1 * self.ql.pointersize] = hook_sys_write - self.import_symbols[self.ql.os.hook_addr + 2 * self.ql.pointersize] = hook_sys_open + self.import_symbols[self.ql.os.hook_addr + 0 * self.ql.arch.pointersize] = hook_sys_read + self.import_symbols[self.ql.os.hook_addr + 1 * self.ql.arch.pointersize] = hook_sys_write + self.import_symbols[self.ql.os.hook_addr + 2 * self.ql.arch.pointersize] = hook_sys_open def get_elfdata_mapping(self, elffile: ELFFile) -> bytes: elfdata_mapping = bytearray() diff --git a/qiling/loader/pe.py b/qiling/loader/pe.py index 02bc8d978..f8267fe0c 100644 --- a/qiling/loader/pe.py +++ b/qiling/loader/pe.py @@ -210,12 +210,12 @@ def set_cmdline(self, name, address, memory): if name == b"_acmdln": addr, packed_addr = self._alloc_cmdline(wide=False) cmdline_entry = {"name": name, "address": address} - memory[address:address + self.ql.pointersize] = packed_addr + memory[address:address + self.ql.arch.pointersize] = packed_addr self.ql.mem.write(addr, self.cmdline) elif name == b"_wcmdln": addr, packed_addr = self._alloc_cmdline(wide=True) cmdline_entry = {"name": name, "address": address} - memory[address:address + self.ql.pointersize] = packed_addr + memory[address:address + self.ql.arch.pointersize] = packed_addr encoded = self.cmdline.decode('ascii').encode('UTF-16LE') self.ql.mem.write(addr, encoded) @@ -277,16 +277,16 @@ def init_ldr_data(self): self.ql, base=ldr_addr, in_load_order_module_list={ - 'Flink': ldr_addr + 2 * self.ql.pointersize, - 'Blink': ldr_addr + 2 * self.ql.pointersize + 'Flink': ldr_addr + 2 * self.ql.arch.pointersize, + 'Blink': ldr_addr + 2 * self.ql.arch.pointersize }, in_memory_order_module_list={ - 'Flink': ldr_addr + 4 * self.ql.pointersize, - 'Blink': ldr_addr + 4 * self.ql.pointersize + 'Flink': ldr_addr + 4 * self.ql.arch.pointersize, + 'Blink': ldr_addr + 4 * self.ql.arch.pointersize }, in_initialization_order_module_list={ - 'Flink': ldr_addr + 6 * self.ql.pointersize, - 'Blink': ldr_addr + 6 * self.ql.pointersize + 'Flink': ldr_addr + 6 * self.ql.arch.pointersize, + 'Blink': ldr_addr + 6 * self.ql.arch.pointersize } ) self.ql.mem.write(ldr_addr, ldr_data.bytes()) @@ -316,8 +316,8 @@ def add_ldr_data_table_entry(self, dll_name): ldr_table_entry.InInitializationOrderLinks['Flink'] = flink.InInitializationOrderModuleList['Flink'] flink.InLoadOrderModuleList['Flink'] = ldr_table_entry.base - flink.InMemoryOrderModuleList['Flink'] = ldr_table_entry.base + 2 * self.ql.pointersize - flink.InInitializationOrderModuleList['Flink'] = ldr_table_entry.base + 4 * self.ql.pointersize + flink.InMemoryOrderModuleList['Flink'] = ldr_table_entry.base + 2 * self.ql.arch.pointersize + flink.InInitializationOrderModuleList['Flink'] = ldr_table_entry.base + 4 * self.ql.arch.pointersize else: flink = self.ldr_list[-1] @@ -326,8 +326,8 @@ def add_ldr_data_table_entry(self, dll_name): ldr_table_entry.InInitializationOrderLinks['Flink'] = flink.InInitializationOrderLinks['Flink'] flink.InLoadOrderLinks['Flink'] = ldr_table_entry.base - flink.InMemoryOrderLinks['Flink'] = ldr_table_entry.base + 2 * self.ql.pointersize - flink.InInitializationOrderLinks['Flink'] = ldr_table_entry.base + 4 * self.ql.pointersize + flink.InMemoryOrderLinks['Flink'] = ldr_table_entry.base + 2 * self.ql.arch.pointersize + flink.InInitializationOrderLinks['Flink'] = ldr_table_entry.base + 4 * self.ql.arch.pointersize # Blink blink = self.LDR @@ -336,8 +336,8 @@ def add_ldr_data_table_entry(self, dll_name): ldr_table_entry.InInitializationOrderLinks['Blink'] = blink.InInitializationOrderModuleList['Blink'] blink.InLoadOrderModuleList['Blink'] = ldr_table_entry.base - blink.InMemoryOrderModuleList['Blink'] = ldr_table_entry.base + 2 * self.ql.pointersize - blink.InInitializationOrderModuleList['Blink'] = ldr_table_entry.base + 4 * self.ql.pointersize + blink.InMemoryOrderModuleList['Blink'] = ldr_table_entry.base + 2 * self.ql.arch.pointersize + blink.InInitializationOrderModuleList['Blink'] = ldr_table_entry.base + 4 * self.ql.arch.pointersize self.ql.mem.write(flink.base, flink.bytes()) self.ql.mem.write(blink.base, blink.bytes()) @@ -613,9 +613,9 @@ def load(self): # setup IMAGE_LOAD_CONFIG_DIRECTORY if self.pe.OPTIONAL_HEADER.DATA_DIRECTORY[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG']].VirtualAddress != 0: SecurityCookie_rva = self.pe.DIRECTORY_ENTRY_LOAD_CONFIG.struct.SecurityCookie - self.pe.OPTIONAL_HEADER.ImageBase - SecurityCookie_value = default_security_cookie_value = self.ql.mem.read(self.pe_image_address+SecurityCookie_rva, self.ql.pointersize) + SecurityCookie_value = default_security_cookie_value = self.ql.mem.read(self.pe_image_address+SecurityCookie_rva, self.ql.arch.pointersize) while SecurityCookie_value == default_security_cookie_value: - SecurityCookie_value = secrets.token_bytes(self.ql.pointersize) + SecurityCookie_value = secrets.token_bytes(self.ql.arch.pointersize) # rol rcx, 10h (rcx: cookie) # test cx, 0FFFFh SecurityCookie_value_array = bytearray(SecurityCookie_value) diff --git a/qiling/os/linux/function_hook.py b/qiling/os/linux/function_hook.py index 6585b9592..14c91f1f0 100644 --- a/qiling/os/linux/function_hook.py +++ b/qiling/os/linux/function_hook.py @@ -81,11 +81,11 @@ def get_ret_pc(self): # X86 elif self.ql.archtype== QL_ARCH.X86: - return self.ql.unpack(self.ql.mem.read(self.ql.arch.regs.esp, self.ql.pointersize)) + return self.ql.unpack(self.ql.mem.read(self.ql.arch.regs.esp, self.ql.arch.pointersize)) # X8664 elif self.ql.archtype== QL_ARCH.X8664: - return self.ql.unpack(self.ql.mem.read(self.ql.arch.regs.rsp, self.ql.pointersize)) + return self.ql.unpack(self.ql.mem.read(self.ql.arch.regs.rsp, self.ql.arch.pointersize)) else: raise @@ -104,11 +104,11 @@ def context_fixup(self): # X86 elif self.ql.archtype== QL_ARCH.X86: - self.ql.arch.regs.esp = self.ql.arch.regs.esp + self.ql.pointersize + self.ql.arch.regs.esp = self.ql.arch.regs.esp + self.ql.arch.pointersize # X8664 elif self.ql.archtype== QL_ARCH.X8664: - self.ql.arch.regs.rsp = self.ql.arch.regs.rsp + self.ql.pointersize + self.ql.arch.regs.rsp = self.ql.arch.regs.rsp + self.ql.arch.pointersize else: raise @@ -139,7 +139,7 @@ def call_enter(self): # if self.ql.archtype == QL_ARCH.ARM or self.ql.archtype == QL_ARCH.ARM64: # self.ql.arch.regs.arch_pc = self.ql.arch.regs.arch_pc + 4 - next_pc = self.ql.unpack(self.ql.mem.read(self.hook_data_ptr, self.ql.pointersize)) + next_pc = self.ql.unpack(self.ql.mem.read(self.hook_data_ptr, self.ql.arch.pointersize)) self.ret_pc = self.get_ret_pc() onenter_cb = None onenter_userdata = None @@ -241,7 +241,7 @@ def enable(self): self.ori_offest = self.rel.r_offset self.rel.r_offset = self.hook_data_ptr - self.load_base - self.ori_data = self.ql.mem.read(self.ori_offest + self.load_base, self.ql.pointersize) + self.ori_data = self.ql.mem.read(self.ori_offest + self.load_base, self.ql.arch.pointersize) self.ql.mem.write(self.rel.ptr, self.rel.pack()) self.ql.mem.write(self.ori_offest + self.load_base, self.ql.pack(self.hook_fuc_ptr)) @@ -255,21 +255,21 @@ def __init__(self, ql, fucname, got, gotidx, load_base): self.gotidx = gotidx def _hook_fuc_enter(self, ql): - self.ql.arch.regs.t9 = self.ql.unpack(self.ql.mem.read(self.hook_data_ptr, self.ql.pointersize)) + self.ql.arch.regs.t9 = self.ql.unpack(self.ql.mem.read(self.hook_data_ptr, self.ql.arch.pointersize)) self.call_enter() def _hook_fuc_exit(self, ql): self.call_exit() - tmp = self.ql.unpack(self.ql.mem.read(self.got + self.load_base + self.gotidx * self.ql.pointersize, self.ql.pointersize)) + tmp = self.ql.unpack(self.ql.mem.read(self.got + self.load_base + self.gotidx * self.ql.arch.pointersize, self.ql.arch.pointersize)) if tmp != self.hook_fuc_ptr: - self.ql.mem.write(self.got + self.load_base + self.gotidx * self.ql.pointersize, self.ql.pack(self.hook_fuc_ptr)) + self.ql.mem.write(self.got + self.load_base + self.gotidx * self.ql.arch.pointersize, self.ql.pack(self.hook_fuc_ptr)) self.ql.mem.write(self.hook_data_ptr, self.ql.pack(tmp)) def _hook_got(self): - self.ori_data = self.ql.mem.read(self.got + self.load_base + self.gotidx * self.ql.pointersize, self.ql.pointersize) + self.ori_data = self.ql.mem.read(self.got + self.load_base + self.gotidx * self.ql.arch.pointersize, self.ql.arch.pointersize) - self.ql.mem.write(self.got + self.load_base + self.gotidx * self.ql.pointersize, self.ql.pack(self.hook_fuc_ptr)) + self.ql.mem.write(self.got + self.load_base + self.gotidx * self.ql.arch.pointersize, self.ql.pack(self.hook_fuc_ptr)) self.ql.mem.write(self.hook_data_ptr, bytes(self.ori_data)) def enable(self): @@ -786,19 +786,19 @@ def _parse(self): if d.d_tag == DT_NULL: break elif d.d_tag == DT_HASH: - # self.hash_nbucket = self.ql.unpack(self.ql.mem.read(self.load_base + d.d_un, self.ql.pointersize)) - # self.hash_nchain = self.ql.unpack(self.ql.mem.read(self.load_base + d.d_un + self.ql.pointersize, self.ql.pointersize)) - # self.hash_bucket = self.ql.mem.read(self.load_base + d.d_un + self.ql.pointersize * 2, self.ql.pointersize * self.hash_nbucket) - # self.hash_chain = self.ql.unpack(self.ql.mem.read(self.load_base + d.d_un + self.ql.pointersize * 2 + self.ql.pointersize * self.hash_nbucket, self.ql.pointersize)) + # self.hash_nbucket = self.ql.unpack(self.ql.mem.read(self.load_base + d.d_un, self.ql.arch.pointersize)) + # self.hash_nchain = self.ql.unpack(self.ql.mem.read(self.load_base + d.d_un + self.ql.arch.pointersize, self.ql.arch.pointersize)) + # self.hash_bucket = self.ql.mem.read(self.load_base + d.d_un + self.ql.arch.pointersize * 2, self.ql.arch.pointersize * self.hash_nbucket) + # self.hash_chain = self.ql.unpack(self.ql.mem.read(self.load_base + d.d_un + self.ql.arch.pointersize * 2 + self.ql.arch.pointersize * self.hash_nbucket, self.ql.arch.pointersize)) pass elif d.d_tag == DT_GNU_HASH: - # self.gnu_nbucket = self.ql.unpack(self.ql.mem.read(self.load_base + d.d_un, self.ql.pointersize)) - # self.gnu_symbias = self.ql.unpack(self.ql.mem.read(self.load_base + d.d_un + self.ql.pointersize, self.ql.pointersize)))) - # self.gnu_maskwords = self.ql.unpack(self.ql.mem.read(self.load_base + d.d_un + self.ql.pointersize * 2, self.ql.pointersize)) - # self.gnu_shift2 = self.ql.unpack(self.ql.mem.read(self.load_base + d.d_un + self.ql.pointersize * 3, self.ql.pointersize)) - # self.gnu_bloom_filter = self.ql.mem.read(self.load_base + d.d_un + self.ql.pointersize * 4, self.gnu_maskwords) - # self.gnu_bucket = self.ql.mem.read(self.load_base + d.d_un + self.ql.pointersize * 4 + self.gnu_maskwords, self.ql.pointersize * self.gnu_nbucket) - # self.gnu_chain = self.load_base + d.d_un + self.ql.pointersize * 4 + self.gnu_maskwords + self.ql.pointersize * self.gnu_nbucket - self.ql.pointersize * self.gnu_symbias + # self.gnu_nbucket = self.ql.unpack(self.ql.mem.read(self.load_base + d.d_un, self.ql.arch.pointersize)) + # self.gnu_symbias = self.ql.unpack(self.ql.mem.read(self.load_base + d.d_un + self.ql.arch.pointersize, self.ql.arch.pointersize)))) + # self.gnu_maskwords = self.ql.unpack(self.ql.mem.read(self.load_base + d.d_un + self.ql.arch.pointersize * 2, self.ql.arch.pointersize)) + # self.gnu_shift2 = self.ql.unpack(self.ql.mem.read(self.load_base + d.d_un + self.ql.arch.pointersize * 3, self.ql.arch.pointersize)) + # self.gnu_bloom_filter = self.ql.mem.read(self.load_base + d.d_un + self.ql.arch.pointersize * 4, self.gnu_maskwords) + # self.gnu_bucket = self.ql.mem.read(self.load_base + d.d_un + self.ql.arch.pointersize * 4 + self.gnu_maskwords, self.ql.arch.pointersize * self.gnu_nbucket) + # self.gnu_chain = self.load_base + d.d_un + self.ql.arch.pointersize * 4 + self.gnu_maskwords + self.ql.arch.pointersize * self.gnu_nbucket - self.ql.arch.pointersize * self.gnu_symbias pass elif d.d_tag == DT_STRTAB: diff --git a/qiling/os/memory.py b/qiling/os/memory.py index 42b902bd2..92cff4bd9 100644 --- a/qiling/os/memory.py +++ b/qiling/os/memory.py @@ -297,7 +297,7 @@ def read_ptr(self, addr: int, size: int=None) -> int: """ if not size: - size = self.ql.pointersize + size = self.ql.arch.pointersize __unpack = { 1 : self.ql.unpack8, diff --git a/qiling/os/os.py b/qiling/os/os.py index 0d2fe8e6f..3a74383ba 100644 --- a/qiling/os/os.py +++ b/qiling/os/os.py @@ -249,7 +249,7 @@ def emu_error(self): containing_image = self.find_containing_image(pc) pc_info = f' ({containing_image.path} + {pc - containing_image.base:#x})' if containing_image else '' finally: - self.ql.log.error(f'PC = {pc:#0{self.ql.pointersize * 2 + 2}x}{pc_info}\n') + self.ql.log.error(f'PC = {pc:#0{self.ql.arch.pointersize * 2 + 2}x}{pc_info}\n') self.ql.log.info(f'Memory map:') self.ql.mem.show_mapinfo() diff --git a/qiling/os/posix/syscall/net.py b/qiling/os/posix/syscall/net.py index 151262b4c..01dd7c0e8 100644 --- a/qiling/os/posix/syscall/net.py +++ b/qiling/os/posix/syscall/net.py @@ -54,6 +54,6 @@ def ql_syscall_socketcall(ql: Qiling, call: int, args: int): handler, nargs = handlers[call] # read 'nargs' arguments from the specified base pointer 'args' - params = (ql.unpack(ql.mem.read(args + i * ql.pointersize, ql.pointersize)) for i in range(nargs)) + params = (ql.unpack(ql.mem.read(args + i * ql.arch.pointersize, ql.arch.pointersize)) for i in range(nargs)) return handler(ql, *params) diff --git a/qiling/os/posix/syscall/select.py b/qiling/os/posix/syscall/select.py index 6d1aab718..283680520 100644 --- a/qiling/os/posix/syscall/select.py +++ b/qiling/os/posix/syscall/select.py @@ -49,7 +49,7 @@ def handle_ready_fds(ptr: int, ready_fds: Sequence, fds_map: Mapping): tmp_w_fd, tmp_w_map = parse_fd_set(ql, nfds, writefds) tmp_e_fd, tmp_e_map = parse_fd_set(ql, nfds, exceptfds) - n = ql.pointersize + n = ql.arch.pointersize if timeout: sec = ql.unpack(ql.mem.read(timeout + n * 0, n)) diff --git a/qiling/os/posix/syscall/sendfile.py b/qiling/os/posix/syscall/sendfile.py index 0c8c092e8..4d4e6e46c 100644 --- a/qiling/os/posix/syscall/sendfile.py +++ b/qiling/os/posix/syscall/sendfile.py @@ -20,7 +20,7 @@ def ql_syscall_sendfile(ql: Qiling, out_fd: int, in_fd: int, offset: int, count: in_fd_pos = ql.os.fd[in_fd].tell() if offset: # Handle sendfile64 and sendfile offset_ptr - offset = ql.unpack(ql.mem.read(offset, ql.pointersize)) + offset = ql.unpack(ql.mem.read(offset, ql.arch.pointersize)) else: offset = in_fd_pos diff --git a/qiling/os/posix/syscall/socket.py b/qiling/os/posix/syscall/socket.py index b48152dc5..eb55d17c2 100644 --- a/qiling/os/posix/syscall/socket.py +++ b/qiling/os/posix/syscall/socket.py @@ -542,7 +542,7 @@ def ql_syscall_recvfrom(ql: Qiling, recvfrom_sockfd, recvfrom_buf, recvfrom_len, ql.log.debug("recvfrom() addr is %s:%d" % (tmp_addr[0], tmp_addr[1])) data += struct.pack(">H", tmp_addr[1]) data += ipaddress.ip_address(tmp_addr[0]).packed - addrlen = ql.unpack(ql.mem.read(recvfrom_addrlen, ql.pointersize)) + addrlen = ql.unpack(ql.mem.read(recvfrom_addrlen, ql.arch.pointersize)) data = data[:addrlen] ql.mem.write(recvfrom_addr, data) diff --git a/qiling/os/posix/syscall/time.py b/qiling/os/posix/syscall/time.py index ea63f5c6f..e7819ff29 100644 --- a/qiling/os/posix/syscall/time.py +++ b/qiling/os/posix/syscall/time.py @@ -13,7 +13,7 @@ def ql_syscall_time(ql: Qiling): return int(time.time()) def __sleep_common(ql: Qiling, req: int, rem: int) -> int: - n = ql.pointersize + n = ql.arch.pointersize tv_sec = ql.unpack(ql.mem.read(req, n)) tv_sec += ql.unpack(ql.mem.read(req + n, n)) / 1000000000 diff --git a/qiling/os/posix/syscall/uio.py b/qiling/os/posix/syscall/uio.py index fda88cc84..f07346a04 100644 --- a/qiling/os/posix/syscall/uio.py +++ b/qiling/os/posix/syscall/uio.py @@ -7,7 +7,7 @@ def ql_syscall_writev(ql: Qiling, fd: int, vec: int, vlen: int): regreturn = 0 - size_t_len = ql.pointersize + size_t_len = ql.arch.pointersize iov = ql.mem.read(vec, vlen * size_t_len * 2) ql.log.debug('writev() CONTENT:') @@ -27,7 +27,7 @@ def ql_syscall_writev(ql: Qiling, fd: int, vec: int, vlen: int): def ql_syscall_readv(ql: Qiling, fd: int, vec: int, vlen: int): regreturn = 0 - size_t_len = ql.pointersize + size_t_len = ql.arch.pointersize iov = ql.mem.read(vec, vlen * size_t_len * 2) ql.log.debug('readv() CONTENT:') diff --git a/qiling/os/posix/syscall/unistd.py b/qiling/os/posix/syscall/unistd.py index 1f2cd3b73..377990120 100644 --- a/qiling/os/posix/syscall/unistd.py +++ b/qiling/os/posix/syscall/unistd.py @@ -407,7 +407,7 @@ def __read_str_array(addr: int) -> Iterator[str]: break yield ql.os.utils.read_cstring(elem) - addr += ql.pointersize + addr += ql.arch.pointersize args = [s for s in __read_str_array(argv)] @@ -683,7 +683,7 @@ def _type_mapping(ent): return bytes([t]) if ql.os.fd[fd].tell() == 0: - n = ql.pointersize + n = ql.arch.pointersize total_size = 0 results = os.scandir(ql.os.fd[fd].name) _ent_count = 0 diff --git a/qiling/os/uefi/bs.py b/qiling/os/uefi/bs.py index d80ac385a..b34b07407 100644 --- a/qiling/os/uefi/bs.py +++ b/qiling/os/uefi/bs.py @@ -399,7 +399,7 @@ def hook_LocateHandleBuffer(ql: Qiling, address: int, params): for handle in handles: write_int64(ql, address, handle) - address += ql.pointersize + address += ql.arch.pointersize return EFI_SUCCESS @@ -419,7 +419,7 @@ def hook_InstallMultipleProtocolInterfaces(ql: Qiling, address: int, params): handle = read_int64(ql, params["Handle"]) if handle == 0: - handle = ql.loader.dxe_context.heap.alloc(ql.pointersize) + handle = ql.loader.dxe_context.heap.alloc(ql.arch.pointersize) dic = ql.loader.dxe_context.protocols.get(handle, {}) diff --git a/qiling/os/uefi/protocols/EfiSmmSwDispatch2Protocol.py b/qiling/os/uefi/protocols/EfiSmmSwDispatch2Protocol.py index 29e78ad08..22f92537d 100644 --- a/qiling/os/uefi/protocols/EfiSmmSwDispatch2Protocol.py +++ b/qiling/os/uefi/protocols/EfiSmmSwDispatch2Protocol.py @@ -80,7 +80,7 @@ def hook_Register(ql: Qiling, address: int, params): return EFI_INVALID_PARAMETER # allocate handle and return it through out parameter - Handle = ql.loader.smm_context.heap.alloc(ql.pointersize) + Handle = ql.loader.smm_context.heap.alloc(ql.arch.pointersize) utils.write_int64(ql, DispatchHandle, Handle) args = { diff --git a/qiling/os/uefi/protocols/common.py b/qiling/os/uefi/protocols/common.py index fe582b542..a729f5f4c 100644 --- a/qiling/os/uefi/protocols/common.py +++ b/qiling/os/uefi/protocols/common.py @@ -22,7 +22,7 @@ def LocateHandles(context, params): else: handles = [] - return len(handles) * context.ql.pointersize, handles + return len(handles) * context.ql.arch.pointersize, handles def InstallProtocolInterface(context, params): handle = read_int64(context.ql, params["Handle"]) @@ -100,7 +100,7 @@ def LocateHandle(context, params): for handle in handles: write_int64(context.ql, ptr, handle) - ptr += context.ql.pointersize + ptr += context.ql.arch.pointersize ret = EFI_SUCCESS diff --git a/qiling/os/uefi/smm.py b/qiling/os/uefi/smm.py index b7ff43b70..15e80e485 100644 --- a/qiling/os/uefi/smm.py +++ b/qiling/os/uefi/smm.py @@ -217,7 +217,7 @@ def invoke_swsmi(self, cpu: int, idx: int, entry: int, args: Mapping[str, Any], DispatchHandle = args['DispatchHandle'] Context = heap.alloc(EFI_SMM_SW_REGISTER_CONTEXT.sizeof()) CommBuffer = heap.alloc(EFI_SMM_SW_CONTEXT.sizeof()) - CommBufferSize = heap.alloc(ql.pointersize) + CommBufferSize = heap.alloc(ql.arch.pointersize) # setup Context args['RegisterContext'].saveTo(ql, Context) @@ -237,7 +237,7 @@ def __cleanup(ql: Qiling): ql.log.info(f'Leaving SWSMI handler {idx:#04x}') # unwind ms64 shadow space - ql.arch.regs.arch_sp += (4 * ql.pointersize) + ql.arch.regs.arch_sp += (4 * ql.arch.pointersize) # release handler resources heap.free(DispatchHandle) @@ -256,7 +256,7 @@ def __cleanup(ql: Qiling): onexit(ql) # hook returning from swsmi handler - cleanup_trap = heap.alloc(ql.pointersize) + cleanup_trap = heap.alloc(ql.arch.pointersize) hret = ql.hook_address(__cleanup, cleanup_trap) ql.log.info(f'Entering SWSMI handler {idx:#04x}') diff --git a/qiling/os/uefi/uefi.py b/qiling/os/uefi/uefi.py index b7d22b54a..c777cde29 100644 --- a/qiling/os/uefi/uefi.py +++ b/qiling/os/uefi/uefi.py @@ -160,14 +160,14 @@ def emit_stack(self, nitems: int = 4): self.ql.log.error('Stack:') for i in range(-nitems, nitems + 1): - offset = i * self.ql.pointersize + offset = i * self.ql.arch.pointersize try: item = self.ql.arch.stack_read(offset) except UcError: data = '(unavailable)' else: - data = f'{item:0{self.ql.pointersize * 2}x}' + data = f'{item:0{self.ql.arch.pointersize * 2}x}' self.ql.log.error(f'{self.ql.arch.regs.arch_sp + offset:08x} : {data}{" <=" if i == 0 else ""}') diff --git a/qiling/os/uefi/utils.py b/qiling/os/uefi/utils.py index 105c51661..c280b022a 100644 --- a/qiling/os/uefi/utils.py +++ b/qiling/os/uefi/utils.py @@ -26,11 +26,11 @@ def execute_protocol_notifications(ql: Qiling, from_hook: bool = False) -> bool: if not ql.loader.notify_list: return False - next_hook = ql.loader.context.heap.alloc(ql.pointersize) + next_hook = ql.loader.context.heap.alloc(ql.arch.pointersize) def __notify_next(ql: Qiling): # discard previous callback's shadow space - ql.arch.regs.arch_sp += (4 * ql.pointersize) + ql.arch.regs.arch_sp += (4 * ql.arch.pointersize) if ql.loader.notify_list: event_id, notify_func, callback_args = ql.loader.notify_list.pop(0) @@ -52,7 +52,7 @@ def __notify_next(ql: Qiling): # __notify_next unwinds the previous callback shadow space allocated by call_function. however, on its first invocation # there is no such shadow space. to maintain stack consistency we set here a bogus shadow space that may be discarded # safely - ql.arch.regs.arch_sp -= (4 * ql.pointersize) + ql.arch.regs.arch_sp -= (4 * ql.arch.pointersize) # To avoid having two versions of the code the first notify function will also be called from the __notify_next hook. if from_hook: diff --git a/qiling/os/utils.py b/qiling/os/utils.py index 20d0c69b2..3d5f67ebe 100644 --- a/qiling/os/utils.py +++ b/qiling/os/utils.py @@ -164,7 +164,7 @@ def __common_printf(self, format: str, args: MutableSequence, wstring: bool): def va_list(self, format: str, ptr: int) -> MutableSequence[int]: count = format.count("%") - return [self.ql.unpack(self.ql.mem.read(ptr + i * self.ql.pointersize, self.ql.pointersize)) for i in range(count)] + return [self.ql.unpack(self.ql.mem.read(ptr + i * self.ql.arch.pointersize, self.ql.arch.pointersize)) for i in range(count)] def sprintf(self, buff: int, format: str, args: MutableSequence, wstring: bool = False) -> int: out = self.__common_printf(format, args, wstring) diff --git a/qiling/os/windows/dlls/advapi32.py b/qiling/os/windows/dlls/advapi32.py index b4d244db2..b42d9ba54 100644 --- a/qiling/os/windows/dlls/advapi32.py +++ b/qiling/os/windows/dlls/advapi32.py @@ -494,7 +494,7 @@ def hook_GetSidSubAuthorityCount(ql: Qiling, address: int, params): 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.pointersize * num) + addr_authority = sid.addr + 8 + (ql.arch.pointersize * num) return addr_authority diff --git a/qiling/os/windows/dlls/kernel32/memoryapi.py b/qiling/os/windows/dlls/kernel32/memoryapi.py index b12776769..7b1b8bd03 100644 --- a/qiling/os/windows/dlls/kernel32/memoryapi.py +++ b/qiling/os/windows/dlls/kernel32/memoryapi.py @@ -107,6 +107,6 @@ def hook_VirtualQuery(ql: Qiling, address: int, params): ) for i, v in enumerate(values): - ql.mem.write(mbi + i * ql.pointersize, ql.pack(v)) + ql.mem.write(mbi + i * ql.arch.pointersize, ql.pack(v)) - return ql.pointersize * len(values) + return ql.arch.pointersize * len(values) diff --git a/qiling/os/windows/dlls/kernel32/synchapi.py b/qiling/os/windows/dlls/kernel32/synchapi.py index 47310b058..50ccd5e01 100644 --- a/qiling/os/windows/dlls/kernel32/synchapi.py +++ b/qiling/os/windows/dlls/kernel32/synchapi.py @@ -136,7 +136,7 @@ def hook_WaitForMultipleObjects(ql: Qiling, address: int, params): lpHandles = params["lpHandles"] for i in range(nCount): - handle_value = ql.unpack(ql.mem.read(lpHandles + i * ql.pointersize, ql.pointersize)) + handle_value = ql.unpack(ql.mem.read(lpHandles + i * ql.arch.pointersize, ql.arch.pointersize)) if handle_value: thread = ql.os.handle_manager.get(handle_value).obj diff --git a/qiling/os/windows/dlls/msvcrt.py b/qiling/os/windows/dlls/msvcrt.py index 0b55bba26..01b692bd6 100644 --- a/qiling/os/windows/dlls/msvcrt.py +++ b/qiling/os/windows/dlls/msvcrt.py @@ -41,13 +41,13 @@ def hook___getmainargs(ql: Qiling, address: int, params): # int* __p__fmode(); @winsdkapi(cc=CDECL, params={}) def hook___p__fmode(ql: Qiling, address: int, params): - addr = ql.os.heap.alloc(ql.pointersize) + addr = ql.os.heap.alloc(ql.arch.pointersize) return addr # int* __p__commode(); @winsdkapi(cc=CDECL, params={}) def hook___p__commode(ql: Qiling, address: int, params): - addr = ql.os.heap.alloc(ql.pointersize) + addr = ql.os.heap.alloc(ql.arch.pointersize) return addr # char** __p__acmdln(); @@ -87,17 +87,17 @@ def hook_atexit(ql: Qiling, address: int, params): # char*** __p__environ(void) @winsdkapi(cc=CDECL, params={}) def hook___p__environ(ql: Qiling, address: int, params): - ret = ql.os.heap.alloc(ql.pointersize * len(ql.os.env)) + ret = ql.os.heap.alloc(ql.arch.pointersize * len(ql.os.env)) for i, (k, v) in enumerate(ql.os.env.items()): entry = bytes(f'{k}={v}', 'ascii') + b'\x00' p_entry = ql.os.heap.alloc(len(entry)) ql.mem.write(p_entry, entry) - pp_entry = ql.os.heap.alloc(ql.pointersize) + pp_entry = ql.os.heap.alloc(ql.arch.pointersize) ql.mem.write(pp_entry, ql.pack(p_entry)) - ql.mem.write(ret + i * ql.pointersize, ql.pack(pp_entry)) + ql.mem.write(ret + i * ql.arch.pointersize, ql.pack(pp_entry)) return ret @@ -155,16 +155,16 @@ def hook__initterm_e(ql: Qiling, address: int, params): @winsdkapi(cc=CDECL, params={}) def hook___p___argv(ql: Qiling, address: int, params): # allocate argv pointers array - p_argv = ql.os.heap.alloc(ql.pointersize * len(ql.os.argv)) + p_argv = ql.os.heap.alloc(ql.arch.pointersize * len(ql.os.argv)) for i, each in enumerate(ql.os.argv): entry = bytes(each, 'ascii') + b'\x00' p_entry = ql.os.heap.alloc(len(entry)) ql.mem.write(p_entry, entry) - ql.mem.write(p_argv + i * ql.pointersize, ql.pack(p_entry)) + ql.mem.write(p_argv + i * ql.arch.pointersize, ql.pack(p_entry)) - ret = ql.os.heap.alloc(ql.pointersize) + ret = ql.os.heap.alloc(ql.arch.pointersize) ql.mem.write(ret, ql.pack(p_argv)) return ret @@ -172,7 +172,7 @@ def hook___p___argv(ql: Qiling, address: int, params): # int* __p___argc(void) @winsdkapi(cc=CDECL, params={}) def hook___p___argc(ql: Qiling, address: int, params): - ret = ql.os.heap.alloc(ql.pointersize) + ret = ql.os.heap.alloc(ql.arch.pointersize) ql.mem.write(ret, ql.pack(len(ql.argv))) @@ -440,7 +440,7 @@ def hook_free(ql: Qiling, address: int, params): def hook__onexit(ql: Qiling, address: int, params): function = params['function'] - addr = ql.os.heap.alloc(ql.pointersize) + addr = ql.os.heap.alloc(ql.arch.pointersize) ql.mem.write(addr, ql.pack(function)) return addr diff --git a/qiling/os/windows/dlls/ntdll.py b/qiling/os/windows/dlls/ntdll.py index f506bcf36..8bcb417df 100644 --- a/qiling/os/windows/dlls/ntdll.py +++ b/qiling/os/windows/dlls/ntdll.py @@ -63,7 +63,7 @@ def _QueryInformationProcess(ql: Qiling, address: int, params): addr = ql.os.heap.alloc(pbi.size) pbi.write(addr) - value = addr.to_bytes(ql.pointersize, "little") + value = addr.to_bytes(ql.arch.pointersize, "little") else: ql.log.debug(str(flag)) raise QlErrorNotImplemented("API not implemented") @@ -320,7 +320,7 @@ def _SetInformationProcess(ql: Qiling, address: int, params): 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 = addr.to_bytes(ql.pointersize, "little") + value = addr.to_bytes(ql.arch.pointersize, "little") else: ql.log.debug(str(flag)) raise QlErrorNotImplemented("API not implemented") @@ -367,7 +367,7 @@ def hook_LdrGetProcedureAddress(ql: Qiling, address: int, params): if identifier in ql.loader.import_address_table[dll_name]: addr = ql.loader.import_address_table[dll_name][identifier] - ql.mem.write(addr.to_bytes(length=ql.pointersize, byteorder='little'), FunctionAddress) + ql.mem.write(addr.to_bytes(length=ql.arch.pointersize, byteorder='little'), FunctionAddress) return 0 return 0xFFFFFFFF diff --git a/qiling/os/windows/dlls/ntoskrnl.py b/qiling/os/windows/dlls/ntoskrnl.py index e1578f4e2..955e7da59 100644 --- a/qiling/os/windows/dlls/ntoskrnl.py +++ b/qiling/os/windows/dlls/ntoskrnl.py @@ -1160,7 +1160,7 @@ def hook_PsCreateSystemThread(ql: Qiling, address: int, params): # set lpThreadId if lpThreadId != 0: ql.mem.write(lpThreadId, ql.pack(UniqueProcess)) - ql.mem.write(lpThreadId + ql.pointersize, ql.pack(thread_id)) + ql.mem.write(lpThreadId + ql.arch.pointersize, ql.pack(thread_id)) # set lpThreadId if ThreadHandle != 0: diff --git a/qiling/os/windows/fiber.py b/qiling/os/windows/fiber.py index c3c500671..e8bda865b 100644 --- a/qiling/os/windows/fiber.py +++ b/qiling/os/windows/fiber.py @@ -35,12 +35,12 @@ def free(self, idx): ret_addr = self.ql.arch.regs.read(UC_X86_REG_RIP + 6 ) #FIXME, use capstone to get addr of next instr? # Write Fls data to memory to be accessed by cb - addr = self.ql.os.heap.alloc(self.ql.pointersize) - data = fiber.data.to_bytes(self.ql.pointersize, byteorder='little') + addr = self.ql.os.heap.alloc(self.ql.arch.pointersize) + data = fiber.data.to_bytes(self.ql.arch.pointersize, byteorder='little') self.ql.mem.write(addr, data) # set up params and return address then jump to callback - if self.ql.pointersize == 8: + if self.ql.arch.pointersize == 8: self.ql.arch.regs.write(UC_X86_REG_RCX, addr) else: self.ql.stack_push(ret_addr) diff --git a/qiling/os/windows/structs.py b/qiling/os/windows/structs.py index 9f05a6647..815ca2bc1 100644 --- a/qiling/os/windows/structs.py +++ b/qiling/os/windows/structs.py @@ -1635,7 +1635,7 @@ def __init__(self, ql): self.addr = None self.ULONG_SIZE = 8 self.LONG_SIZE = 4 - self.POINTER_SIZE = self.ql.pointersize + self.POINTER_SIZE = self.ql.arch.pointersize self.INT_SIZE = 2 self.DWORD_SIZE = 4 self.WORD_SIZE = 2 @@ -2145,7 +2145,7 @@ 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.pointersize + 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] diff --git a/tests/test_blob.py b/tests/test_blob.py index f4d542a1c..06da52b19 100644 --- a/tests/test_blob.py +++ b/tests/test_blob.py @@ -41,7 +41,7 @@ def partial_run_init(ql): ql.arch.regs.arch_sp -= 0x20 argv_ptr = ql.arch.regs.arch_sp ql.mem.write(argv_ptr, ql.pack(arg0_ptr)) - ql.mem.write(argv_ptr + ql.pointersize, ql.pack(arg1_ptr)) + ql.mem.write(argv_ptr + ql.arch.pointersize, ql.pack(arg1_ptr)) ql.arch.regs.r2 = 2 ql.arch.regs.r3 = argv_ptr diff --git a/tests/test_pe.py b/tests/test_pe.py index cba81057b..ff0938c50 100644 --- a/tests/test_pe.py +++ b/tests/test_pe.py @@ -462,7 +462,7 @@ def check_print(ql: Qiling, address: int, params): arglist = params['_ArgList'] count = format.count("%") - fargs = [ql.unpack(ql.mem.read(arglist + i * ql.pointersize, ql.pointersize)) for i in range(count)] + fargs = [ql.unpack(ql.mem.read(arglist + i * ql.arch.pointersize, ql.arch.pointersize)) for i in range(count)] target_txt = "" From 38e82ec23a7e3f8e180a7b1b75c29ed998a2621c Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 16 Jan 2022 15:00:05 +0200 Subject: [PATCH 029/406] Fix Cortex-M reg mapping --- qiling/arch/cortex_m.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/qiling/arch/cortex_m.py b/qiling/arch/cortex_m.py index 7dbb9c77e..c54682973 100644 --- a/qiling/arch/cortex_m.py +++ b/qiling/arch/cortex_m.py @@ -14,7 +14,7 @@ from qiling.exception import QlErrorNotImplemented from qiling.arch.arm import QlArchARM -from qiling.arch import arm_const, cortex_m_const +from qiling.arch import cortex_m_const from qiling.arch.register import QlRegisterManager from qiling.arch.cortex_m_const import IRQ, EXC_RETURN, CONTROL, EXCP @@ -69,11 +69,7 @@ def uc(self): @cached_property def regs(self) -> QlRegisterManager: - regs_map = dict( - **arm_const.reg_map, - **cortex_m_const.reg_map - ) - + regs_map = cortex_m_const.reg_map pc_reg = 'pc' sp_reg = 'sp' From 77fbdaac962d24d465d9e17a29b08ebc5d3bd511 Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 16 Jan 2022 19:58:02 +0200 Subject: [PATCH 030/406] Fix arch indirect self referencing --- qiling/arch/arm.py | 12 ++++++------ qiling/arch/arm64.py | 2 +- qiling/arch/cortex_m.py | 22 +++++++++++----------- qiling/arch/riscv.py | 8 ++++---- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/qiling/arch/arm.py b/qiling/arch/arm.py index dac15e468..8245a1cda 100644 --- a/qiling/arch/arm.py +++ b/qiling/arch/arm.py @@ -52,7 +52,7 @@ def regs(self) -> QlRegisterManager: def get_pc(self) -> int: append = 1 if self.check_thumb() == UC_MODE_THUMB else 0 - return self.ql.arch.regs.pc + append + return self.regs.pc + append def __is_thumb(self) -> bool: cpsr_v = { @@ -60,7 +60,7 @@ def __is_thumb(self) -> bool: QL_ENDIAN.EB : 0b100000 # FIXME: should be: 0b000000 }[self.ql.archendian] - return bool(self.ql.arch.regs.cpsr & cpsr_v) + return bool(self.regs.cpsr & cpsr_v) @property def disassembler(self) -> Cs: @@ -98,13 +98,13 @@ def assembler(self) -> Ks: return Ks(KS_ARCH_ARM, mode) def enable_vfp(self) -> None: - self.ql.arch.regs.c1_c0_2 = self.ql.arch.regs.c1_c0_2 | (0xf << 20) + self.regs.c1_c0_2 = self.regs.c1_c0_2 | (0xf << 20) if self.ql.archendian == QL_ENDIAN.EB: - self.ql.arch.regs.fpexc = 0x40000000 - #self.ql.arch.regs.fpexc = 0x00000040 + self.regs.fpexc = 0x40000000 + #self.regs.fpexc = 0x00000040 else: - self.ql.arch.regs.fpexc = 0x40000000 + self.regs.fpexc = 0x40000000 def check_thumb(self): return UC_MODE_THUMB if self.__is_thumb() else UC_MODE_ARM diff --git a/qiling/arch/arm64.py b/qiling/arch/arm64.py index 90b089dde..f19451337 100644 --- a/qiling/arch/arm64.py +++ b/qiling/arch/arm64.py @@ -41,4 +41,4 @@ def assembler(self) -> Ks: return Ks(KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN) def enable_vfp(self): - self.ql.arch.regs.cpacr_el1 = self.ql.arch.regs.cpacr_el1 | 0x300000 + self.regs.cpacr_el1 = self.regs.cpacr_el1 | 0x300000 diff --git a/qiling/arch/cortex_m.py b/qiling/arch/cortex_m.py index c54682973..550bfdebc 100644 --- a/qiling/arch/cortex_m.py +++ b/qiling/arch/cortex_m.py @@ -108,15 +108,15 @@ def run(self, count=-1, end=None): count -= 1 def is_handler_mode(self): - return self.ql.arch.regs.read('ipsr') > 1 + return self.regs.read('ipsr') > 1 def using_psp(self): - return not self.is_handler_mode() and (self.ql.arch.regs.read('control') & CONTROL.SPSEL) > 0 + return not self.is_handler_mode() and (self.regs.read('control') & CONTROL.SPSEL) > 0 def init_context(self): - self.ql.arch.regs.write('lr', 0xffffffff) - self.ql.arch.regs.write('msp', self.ql.mem.read_ptr(0x0)) - self.ql.arch.regs.write('pc' , self.ql.mem.read_ptr(0x4)) + self.regs.write('lr', 0xffffffff) + self.regs.write('msp', self.ql.mem.read_ptr(0x0)) + self.regs.write('pc' , self.ql.mem.read_ptr(0x4)) def soft_interrupt_handler(self, ql, intno): forward_mapper = { @@ -150,7 +150,7 @@ def soft_interrupt_handler(self, ql, intno): raise QlErrorNotImplemented(f'Unhandled interrupt number ({intno})') def hard_interrupt_handler(self, ql, intno): - basepri = self.ql.arch.regs.read('basepri') & 0xf0 + basepri = self.regs.read('basepri') & 0xf0 if basepri and basepri <= ql.hw.nvic.get_priority(intno): return @@ -168,10 +168,10 @@ def hard_interrupt_handler(self, ql, intno): offset = isr * 4 entry = ql.mem.read_ptr(offset) - exc_return = 0xFFFFFFFD if self.ql.arch.using_psp() else 0xFFFFFFF9 + exc_return = 0xFFFFFFFD if self.using_psp() else 0xFFFFFFF9 - self.ql.arch.regs.write('ipsr', isr) - self.ql.arch.regs.write('pc', entry) - self.ql.arch.regs.write('lr', exc_return) + self.regs.write('ipsr', isr) + self.regs.write('pc', entry) + self.regs.write('lr', exc_return) - self.ql.emu_start(self.ql.arch.get_pc(), 0, count=0xffffff) + self.ql.emu_start(self.get_pc(), 0, count=0xffffff) diff --git a/qiling/arch/riscv.py b/qiling/arch/riscv.py index 6da356cbb..b52ee93a7 100644 --- a/qiling/arch/riscv.py +++ b/qiling/arch/riscv.py @@ -49,10 +49,10 @@ def assembler(self) -> Ks: raise QlErrorNotImplemented("Keystone does not yet support riscv") def enable_float(self): - self.ql.arch.regs.mstatus = self.ql.arch.regs.mstatus | MSTATUS.FS_DIRTY + self.regs.mstatus = self.regs.mstatus | MSTATUS.FS_DIRTY def init_context(self): - self.ql.arch.regs.pc = 0x08000000 + self.regs.pc = 0x08000000 def soft_interrupt_handler(self, ql, intno): if intno == 2: @@ -70,7 +70,7 @@ def soft_interrupt_handler(self, ql, intno): raise QlErrorNotImplemented(f'Unhandled interrupt number ({intno})') def step(self): - self.ql.emu_start(self.ql.arch.regs.arch_pc, 0, count=1) + self.ql.emu_start(self.regs.arch_pc, 0, count=1) self.ql.hw.step() def stop(self): @@ -80,7 +80,7 @@ def run(self, count=-1, end=None): self.runable = True while self.runable and count != 0: - if self.ql.arch.regs.arch_pc == end: + if self.regs.arch_pc == end: break self.step() From 8368776ea0fb0ba824704018368a4d67c9020388 Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 16 Jan 2022 20:19:21 +0200 Subject: [PATCH 031/406] Ignore endianess --- qiling/arch/arm.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/qiling/arch/arm.py b/qiling/arch/arm.py index 8245a1cda..5e6d8bffe 100644 --- a/qiling/arch/arm.py +++ b/qiling/arch/arm.py @@ -55,12 +55,7 @@ def get_pc(self) -> int: return self.regs.pc + append def __is_thumb(self) -> bool: - cpsr_v = { - QL_ENDIAN.EL : 0b100000, - QL_ENDIAN.EB : 0b100000 # FIXME: should be: 0b000000 - }[self.ql.archendian] - - return bool(self.regs.cpsr & cpsr_v) + return bool(self.regs.cpsr & (1 << 5)) @property def disassembler(self) -> Cs: @@ -98,13 +93,10 @@ def assembler(self) -> Ks: return Ks(KS_ARCH_ARM, mode) def enable_vfp(self) -> None: - self.regs.c1_c0_2 = self.regs.c1_c0_2 | (0xf << 20) + # set full access to cp10 and cp11 + self.regs.c1_c0_2 = self.regs.c1_c0_2 | (0xb11 << 20) | (0xb11 << 22) - if self.ql.archendian == QL_ENDIAN.EB: - self.regs.fpexc = 0x40000000 - #self.regs.fpexc = 0x00000040 - else: - self.regs.fpexc = 0x40000000 + self.regs.fpexc = (1 << 30) def check_thumb(self): return UC_MODE_THUMB if self.__is_thumb() else UC_MODE_ARM From c9231365ddee27153a35a6bc31dcdc6b3038cd93 Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 16 Jan 2022 20:21:11 +0200 Subject: [PATCH 032/406] Use direct regs access --- qiling/arch/cortex_m.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/qiling/arch/cortex_m.py b/qiling/arch/cortex_m.py index 550bfdebc..8db13f280 100644 --- a/qiling/arch/cortex_m.py +++ b/qiling/arch/cortex_m.py @@ -108,15 +108,15 @@ def run(self, count=-1, end=None): count -= 1 def is_handler_mode(self): - return self.regs.read('ipsr') > 1 + return self.regs.ipsr > 1 def using_psp(self): - return not self.is_handler_mode() and (self.regs.read('control') & CONTROL.SPSEL) > 0 + return not self.is_handler_mode() and (self.regs.control & CONTROL.SPSEL) > 0 def init_context(self): - self.regs.write('lr', 0xffffffff) - self.regs.write('msp', self.ql.mem.read_ptr(0x0)) - self.regs.write('pc' , self.ql.mem.read_ptr(0x4)) + self.regs.lr = 0xffffffff + self.regs.msp = self.ql.mem.read_ptr(0x0) + self.regs.pc = self.ql.mem.read_ptr(0x4) def soft_interrupt_handler(self, ql, intno): forward_mapper = { @@ -150,19 +150,19 @@ def soft_interrupt_handler(self, ql, intno): raise QlErrorNotImplemented(f'Unhandled interrupt number ({intno})') def hard_interrupt_handler(self, ql, intno): - basepri = self.regs.read('basepri') & 0xf0 + basepri = self.regs.basepri & 0xf0 if basepri and basepri <= ql.hw.nvic.get_priority(intno): return - if intno > IRQ.HARD_FAULT and (ql.arch.regs.read('primask') & 0x1): + if intno > IRQ.HARD_FAULT and (self.regs.primask & 0x1): return - - if intno != IRQ.NMI and (ql.arch.regs.read('faultmask') & 0x1): + + if intno != IRQ.NMI and (self.regs.faultmask & 0x1): return if ql.verbose >= QL_VERBOSE.DISASM: ql.log.debug(f'Handle the intno: {intno}') - + with QlInterruptContext(ql): isr = intno + 16 offset = isr * 4 From b8ca6c2bc1ed0fd1cf26bb9ef7f3dbb63c09ffc0 Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 16 Jan 2022 23:50:38 +0200 Subject: [PATCH 033/406] Rename and simplify QlArchARM check_thumb --- qiling/arch/arm.py | 18 +++++++----------- qiling/arch/cortex_m.py | 5 +++-- qiling/debugger/gdb/utils.py | 10 ++++------ 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/qiling/arch/arm.py b/qiling/arch/arm.py index 5e6d8bffe..f85e879db 100644 --- a/qiling/arch/arm.py +++ b/qiling/arch/arm.py @@ -50,12 +50,8 @@ def regs(self) -> QlRegisterManager: # get PC def get_pc(self) -> int: - append = 1 if self.check_thumb() == UC_MODE_THUMB else 0 - - return self.regs.pc + append - - def __is_thumb(self) -> bool: - return bool(self.regs.cpsr & (1 << 5)) + # append 1 to pc if in thumb mode, or 0 otherwise + return self.regs.pc + int(self.is_thumb) @property def disassembler(self) -> Cs: @@ -64,7 +60,7 @@ def disassembler(self) -> Cs: if self.ql.archtype == QL_ARCH.ARM: # FIXME: mode should take endianess into account - mode = CS_MODE_THUMB if self.__is_thumb() else CS_MODE_ARM + mode = CS_MODE_THUMB if self.is_thumb else CS_MODE_ARM elif self.ql.archtype == QL_ARCH.ARM_THUMB: mode = CS_MODE_THUMB @@ -74,7 +70,6 @@ def disassembler(self) -> Cs: return Cs(CS_ARCH_ARM, mode) - @property def assembler(self) -> Ks: # note: we do not cache the assembler instance; rather we refresh it @@ -82,7 +77,7 @@ def assembler(self) -> Ks: if self.ql.archtype == QL_ARCH.ARM: # FIXME: mode should take endianess into account - mode = KS_MODE_THUMB if self.__is_thumb() else KS_MODE_ARM + mode = KS_MODE_THUMB if self.is_thumb else KS_MODE_ARM elif self.ql.archtype == QL_ARCH.ARM_THUMB: mode = KS_MODE_THUMB @@ -98,8 +93,9 @@ def enable_vfp(self) -> None: self.regs.fpexc = (1 << 30) - def check_thumb(self): - return UC_MODE_THUMB if self.__is_thumb() else UC_MODE_ARM + @property + def is_thumb(self) -> bool: + return bool(self.regs.cpsr & (1 << 5)) """ set_tls diff --git a/qiling/arch/cortex_m.py b/qiling/arch/cortex_m.py index 8db13f280..00046bac5 100644 --- a/qiling/arch/cortex_m.py +++ b/qiling/arch/cortex_m.py @@ -83,8 +83,9 @@ def disassembler(self) -> Cs: def assembler(self) -> Ks: return Ks(KS_ARCH_ARM, KS_MODE_ARM + KS_MODE_THUMB) - def check_thumb(self): - return UC_MODE_THUMB + @property + def is_thumb(self) -> bool: + return True def step(self): self.ql.emu_start(self.get_pc(), 0, count=1) diff --git a/qiling/debugger/gdb/utils.py b/qiling/debugger/gdb/utils.py index f1ce057c1..b1d44f5c1 100644 --- a/qiling/debugger/gdb/utils.py +++ b/qiling/debugger/gdb/utils.py @@ -45,10 +45,9 @@ def dbg_hook(self, ql, address, size): Modified this function for qiling.gdbserver by kabeor from https://github.com/iGio90/uDdbg """ try: - if self.ql.archtype == QL_ARCH.ARM: - mode = self.ql.arch.check_thumb() - if mode == UC_MODE_THUMB: - address = address + 1 + if ql.archtype == QL_ARCH.ARM: + if ql.arch.is_thumb: + address += 1 self.mapping.append([(hex(address))]) self.current_address = address @@ -102,8 +101,7 @@ def resume_emu(self, address=None, skip_bp=0): if address is not None: if self.ql.archtype == QL_ARCH.ARM: - mode = self.ql.arch.check_thumb() - if mode == UC_MODE_THUMB: + if self.ql.arch.is_thumb: address += 1 self.current_address = address From 74c674b02817a1d2c49b258ca69be57c3a4dbf6b Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 16 Jan 2022 23:51:55 +0200 Subject: [PATCH 034/406] Prettify QlGdbUtils --- qiling/debugger/gdb/utils.py | 87 +++++++++++++++++++----------------- 1 file changed, 47 insertions(+), 40 deletions(-) diff --git a/qiling/debugger/gdb/utils.py b/qiling/debugger/gdb/utils.py index b1d44f5c1..093f7522d 100644 --- a/qiling/debugger/gdb/utils.py +++ b/qiling/debugger/gdb/utils.py @@ -3,53 +3,59 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from unicorn import * -from qiling.const import * +from qiling import Qiling +from qiling.const import QL_ARCH +PROMPT = r'gdb>' -class QlGdbUtils(object): +class QlGdbUtils: def __init__(self): + self.ql: Qiling + self.current_address = 0x0 self.current_address_size = 0x0 self.last_bp = 0x0 - self.ql = None self.entry_point = None self.exit_point = None self.soft_bp = False self.has_soft_bp = False self.bp_list = [] - self.mapping = [] + # self.mapping = [] self.breakpoint_count = 0x0 self.skip_bp_count = 0x0 - self._tmp_hook = None - def initialize(self, ql, hook_address, exit_point=None, mappings=None): + def initialize(self, ql: Qiling, hook_address: int, exit_point: int, mappings=[]): self.ql = ql - if self.ql.baremetal: + + if ql.baremetal: self.current_address = self.entry_point else: - self.current_address = self.entry_point = self.ql.os.entry_point + self.current_address = self.entry_point = ql.os.entry_point + self.exit_point = exit_point - self.mapping = mappings - self._tmp_hook = self.ql.hook_address(self.entry_point_hook, hook_address) + # self.mapping = mappings - def entry_point_hook(self, ql, *args, **kwargs): - self.ql.hook_del(self._tmp_hook) - self.ql.hook_code(self.dbg_hook) - self.ql.stop() - self.ql.log.info("gdb> Stop at entry point: %#x" % self.ql.arch.regs.arch_pc) + def __entry_point_hook(ql: Qiling): + ql.hook_del(ep_hret) + ql.hook_code(self.dbg_hook) + ql.stop() - def dbg_hook(self, ql, address, size): - """ - Modified this function for qiling.gdbserver by kabeor from https://github.com/iGio90/uDdbg + ql.log.info(f'{PROMPT} Stop at entry point: {ql.arch.regs.arch_pc:#x}') + + ep_hret = ql.hook_address(__entry_point_hook, hook_address) + + + def dbg_hook(self, ql: Qiling, address: int, size: int): + """Modified this function for qiling.gdbserver by kabeor from https://github.com/iGio90/uDdbg """ + try: if ql.archtype == QL_ARCH.ARM: if ql.arch.is_thumb: address += 1 - self.mapping.append([(hex(address))]) + # self.mapping.append([(hex(address))]) self.current_address = address hit_soft_bp = False @@ -63,50 +69,51 @@ def dbg_hook(self, ql, address, size): self.skip_bp_count -= 1 else: self.breakpoint_count += 1 - self.ql.stop() + ql.stop() self.last_bp = address - ql.log.info("gdb> Breakpoint found, stop at address: 0x%x" % address) - + ql.log.info(f'{PROMPT} Breakpoint found, stop at address: {address:#x}') + elif address == self.last_bp: self.last_bp = 0x0 self.has_soft_bp = hit_soft_bp - + if self.current_address + size == self.exit_point: - ql.log.debug("gdb> emulation entrypoint at 0x%x" % (self.entry_point)) - ql.log.debug("gdb> emulation exitpoint at 0x%x" % (self.exit_point)) - - except KeyboardInterrupt as ex: - ql.log.info("gdb> Paused at 0x%x, instruction size = %u" % (address, size)) - self.ql.stop() + ql.log.debug(f'{PROMPT} emulation entrypoint at {self.entry_point:#x}') + ql.log.debug(f'{PROMPT} emulation exitpoint at {self.exit_point:#x}') + + except KeyboardInterrupt: + ql.log.info(f'{PROMPT} Paused at {address:#x}, instruction size = {size:d}') + ql.stop() + except: - raise + raise - def bp_insert(self, addr): + def bp_insert(self, addr: int): if addr not in self.bp_list: self.bp_list.append(addr) - self.ql.log.info('gdb> Breakpoint added at: 0x%x' % addr) + self.ql.log.info(f'{PROMPT} Breakpoint added at: {addr:#x}') - def bp_remove(self, addr, type = None, len = None): + def bp_remove(self, addr: int, type = None, len = None): self.bp_list.remove(addr) - self.ql.log.info('gdb> Breakpoint removed at: 0x%x' % addr) + self.ql.log.info(f'{PROMPT} Breakpoint removed at: {addr:#x}') - def resume_emu(self, address=None, skip_bp=0): - """ - Modified this function for qiling.gdbserver by kabeor from https://github.com/iGio90/uDdbg + def resume_emu(self, address: int = None, skip_bp: int = 0): + """Modified this function for qiling.gdbserver by kabeor from https://github.com/iGio90/uDdbg """ if address is not None: if self.ql.archtype == QL_ARCH.ARM: if self.ql.arch.is_thumb: address += 1 + self.current_address = address self.skip_bp_count = skip_bp + if self.exit_point is not None: - self.ql.log.info('gdb> Resume at: 0x%x' % self.current_address) + self.ql.log.info(f'{PROMPT} Resume at: {self.current_address:#x}') self.ql.emu_start(self.current_address, self.exit_point) - \ No newline at end of file From 6dade43994ff56290ca0f374f0847be61ee88c1e Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 17 Jan 2022 01:42:48 +0200 Subject: [PATCH 035/406] Remove stdin, stdout and stderr from core init --- qiling/core.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/qiling/core.py b/qiling/core.py index 3002b0a16..42a699637 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -49,9 +49,6 @@ def __init__( filter = None, stop_on_stackpointer = False, stop_on_exit_trap = False, - stdin=None, - stdout=None, - stderr=None, ): """ Create a Qiling instance. @@ -199,19 +196,10 @@ def __init__( # Once we finish setting up arch layer, we can init QlCoreHooks. if not self.interpreter: QlCoreHooks.__init__(self, self.uc) - + self.arch.utils.setup_output() self._os = os_setup(self.archtype, self.ostype, self) - if stdin is not None: - self._os.stdin = stdin - - if stdout is not None: - self._os.stdout = stdout - - if stderr is not None: - self._os.stderr = stderr - # Run the loader self.loader.run() self._init_stop_guard() From 58114a2e2bb87ec910b23caf91786353ac2ca62d Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 17 Jan 2022 01:48:09 +0200 Subject: [PATCH 036/406] Adjust all standard streams setups --- examples/crackme_x86_linux.py | 11 +++--- examples/crackme_x86_windows.py | 9 ++--- examples/crackme_x86_windows_auto.py | 8 +++-- .../fuzzing/linux_x8664/fuzz_x8664_linux.py | 12 +++---- examples/fuzzing/qnx_arm/fuzz_arm_qnx.py | 13 +++---- qiling/extensions/idaplugin/qilingida.py | 11 +++--- tests/test_elf.py | 35 ++++++++++++------- tests/test_elf_multithread.py | 6 ++-- tests/test_riscv.py | 14 +++++--- 9 files changed, 60 insertions(+), 59 deletions(-) diff --git a/examples/crackme_x86_linux.py b/examples/crackme_x86_linux.py index e25c87fbf..7c1d16470 100644 --- a/examples/crackme_x86_linux.py +++ b/examples/crackme_x86_linux.py @@ -16,14 +16,11 @@ class Solver: def __init__(self, invalid: bytes): - mock_stdin = pipe.SimpleInStream(sys.stdin.fileno()) - mock_stdout = pipe.NullOutStream(sys.stdout.fileno()) - # create a silent qiling instance - self.ql = Qiling([rf"{ROOTFS}/bin/crackme_linux"], ROOTFS, - verbose=QL_VERBOSE.OFF, # thwart qiling logger output - stdin=mock_stdin, # take over the input to the program using a fake stdin - stdout=mock_stdout) # disregard program output + self.ql = Qiling([rf"{ROOTFS}/bin/crackme_linux"], ROOTFS, verbose=QL_VERBOSE.OFF) + + self.ql.os.stdin = pipe.SimpleInStream(sys.stdin.fileno()) # take over the input to the program using a fake stdin + self.ql.os.stdout = pipe.NullOutStream(sys.stdout.fileno()) # disregard program output # execute program until it reaches the 'main' function self.ql.run(end=0x0804851b) diff --git a/examples/crackme_x86_windows.py b/examples/crackme_x86_windows.py index e256ef730..d60f40c8b 100644 --- a/examples/crackme_x86_windows.py +++ b/examples/crackme_x86_windows.py @@ -14,13 +14,10 @@ def instruction_count(ql: Qiling, address: int, size: int, user_data): user_data[0] += 1 def get_count(flag): - mock_stdin = pipe.SimpleStringBuffer() - mock_stdout = pipe.SimpleStringBuffer() + ql = Qiling(["rootfs/x86_windows/bin/crackme.exe"], "rootfs/x86_windows", verbose=QL_VERBOSE.OFF) - ql = Qiling(["rootfs/x86_windows/bin/crackme.exe"], "rootfs/x86_windows", - verbose=QL_VERBOSE.OFF, - stdin=mock_stdin, - stdout=mock_stdout) + ql.os.stdin = pipe.SimpleStringBuffer() + ql.os.stdout = pipe.SimpleStringBuffer() ql.os.stdin.write(bytes("".join(flag) + "\n", 'utf-8')) count = [0] diff --git a/examples/crackme_x86_windows_auto.py b/examples/crackme_x86_windows_auto.py index 66b4ee882..b8bf1072d 100644 --- a/examples/crackme_x86_windows_auto.py +++ b/examples/crackme_x86_windows_auto.py @@ -21,13 +21,15 @@ def force_call_dialog_func(ql: Qiling): # force EIP to DialogFunc ql.arch.regs.eip = lpDialogFunc -def our_sandbox(path, rootfs): - ql = Qiling(path, rootfs, stdin=pipe.SimpleInStream(sys.stdin.fileno())) +def our_sandbox(path: str, rootfs: str): + ql = Qiling([path], rootfs) + ql.os.stdin = pipe.SimpleInStream(sys.stdin.fileno()) ql.os.stdin.write(b"Ea5yR3versing\n") + ql.hook_address(force_call_dialog_func, 0x00401016) ql.run() if __name__ == "__main__": # Flag is : Ea5yR3versing - our_sandbox(["rootfs/x86_windows/bin/Easy_CrackMe.exe"], "rootfs/x86_windows") + our_sandbox(r"rootfs/x86_windows/bin/Easy_CrackMe.exe", r"rootfs/x86_windows") diff --git a/examples/fuzzing/linux_x8664/fuzz_x8664_linux.py b/examples/fuzzing/linux_x8664/fuzz_x8664_linux.py index 83c8d0032..469237ed2 100755 --- a/examples/fuzzing/linux_x8664/fuzz_x8664_linux.py +++ b/examples/fuzzing/linux_x8664/fuzz_x8664_linux.py @@ -31,14 +31,12 @@ from qiling.extensions.afl import ql_afl_fuzz def main(input_file: str): - mock_stdin = pipe.SimpleInStream(sys.stdin.fileno()) - ql = Qiling(["./x8664_fuzz"], "../../rootfs/x8664_linux", - verbose=QL_VERBOSE.OFF, # keep qiling logging off - console=False, # thwart program output - stdin=mock_stdin, # redirect stdin to our mock to feed it with incoming fuzzed keystrokes - stdout=None, - stderr=None) + verbose=QL_VERBOSE.OFF, # keep qiling logging off + console=False) # thwart program output + + # redirect stdin to our mock to feed it with incoming fuzzed keystrokes + ql.os.stdin = pipe.SimpleInStream(sys.stdin.fileno()) def place_input_callback(ql: Qiling, input: bytes, persistent_round: int) -> Optional[bool]: """Called with every newly generated input. diff --git a/examples/fuzzing/qnx_arm/fuzz_arm_qnx.py b/examples/fuzzing/qnx_arm/fuzz_arm_qnx.py index 2ec27befe..2e13e2294 100755 --- a/examples/fuzzing/qnx_arm/fuzz_arm_qnx.py +++ b/examples/fuzzing/qnx_arm/fuzz_arm_qnx.py @@ -21,16 +21,13 @@ from qiling.extensions.afl import ql_afl_fuzz def main(input_file, enable_trace=False): - mock_stdin = pipe.SimpleInStream(sys.stdin.fileno()) + ql = Qiling(["./arm_fuzz"], "../../rootfs/arm_qnx", console=enable_trace) - ql = Qiling(["./arm_fuzz"], "../../rootfs/arm_qnx", - stdin=mock_stdin, - stdout=1 if enable_trace else None, - stderr=1 if enable_trace else None, - console = True if enable_trace else False) + ql.os.stdin = pipe.SimpleInStream(sys.stdin.fileno()) - # or this for output: - # ... stdout=sys.stdout, stderr=sys.stderr) + if not enable_trace: + ql.os.stdout = pipe.NullOutStream(sys.stdout.fileno()) + ql.os.stderr = pipe.NullOutStream(sys.stderr.fileno()) def place_input_callback(ql: Qiling, input: bytes, _: int): ql.os.stdin.write(input) diff --git a/qiling/extensions/idaplugin/qilingida.py b/qiling/extensions/idaplugin/qilingida.py index efd01c48e..a2739d7b6 100644 --- a/qiling/extensions/idaplugin/qilingida.py +++ b/qiling/extensions/idaplugin/qilingida.py @@ -904,15 +904,12 @@ def __init__(self): self.env = {} def start(self, *args, **kwargs): - if sys.platform != 'win32': - qlstdin = QlEmuMisc.QLStdIO('stdin', sys.__stdin__.fileno()) - qlstdout = QlEmuMisc.QLStdIO('stdout', sys.__stdout__.fileno()) - qlstderr = QlEmuMisc.QLStdIO('stderr', sys.__stderr__.fileno()) + self.ql = Qiling(argv=self.path, rootfs=self.rootfs, verbose=QL_VERBOSE.DEBUG, env=self.env, log_plain=True, *args, **kwargs) if sys.platform != 'win32': - self.ql = Qiling(argv=self.path, rootfs=self.rootfs, verbose=QL_VERBOSE.DEBUG, env=self.env, stdin=qlstdin, stdout=qlstdout, stderr=qlstderr, log_plain=True, *args, **kwargs) - else: - self.ql = Qiling(argv=self.path, rootfs=self.rootfs, verbose=QL_VERBOSE.DEBUG, env=self.env, log_plain=True, *args, **kwargs) + self.ql.os.stdin = QlEmuMisc.QLStdIO('stdin', sys.__stdin__.fileno()) + self.ql.os.stdout = QlEmuMisc.QLStdIO('stdout', sys.__stdout__.fileno()) + self.ql.os.stderr = QlEmuMisc.QLStdIO('stderr', sys.__stderr__.fileno()) self.exit_addr = self.ql.os.exit_point if self.ql.ostype == QL_OS.LINUX: diff --git a/tests/test_elf.py b/tests/test_elf.py index 0de2d9a23..1373f250e 100644 --- a/tests/test_elf.py +++ b/tests/test_elf.py @@ -830,15 +830,18 @@ def my__llseek(ql, *args, **kw): pass def run_one_round(payload): - mock_stdin = pipe.SimpleInStream(sys.stdin.fileno()) - ql = Qiling(["../examples/rootfs/x86_linux/bin/crackme_linux"], "../examples/rootfs/x86_linux", console=False, stdin=mock_stdin) + ql = Qiling(["../examples/rootfs/x86_linux/bin/crackme_linux"], "../examples/rootfs/x86_linux", console=False) + ins_count = [0] ql.hook_code(instruction_count, ins_count) ql.set_syscall("_llseek", my__llseek) + + ql.os.stdin = pipe.SimpleInStream(sys.stdin.fileno()) ql.os.stdin.write(payload) + ql.run() - del mock_stdin del ql + return ins_count[0] @@ -979,19 +982,21 @@ def test_arm_directory_symlink(self): del ql def test_x8664_absolute_path(self): - mock_stdout = pipe.SimpleOutStream(sys.stdout.fileno()) - ql = Qiling(["../examples/rootfs/x8664_linux/bin/absolutepath"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG, stdout=mock_stdout) + ql = Qiling(["../examples/rootfs/x8664_linux/bin/absolutepath"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG) + ql.os.stdout = pipe.SimpleOutStream(sys.stdout.fileno()) ql.run() + self.assertEqual(ql.os.stdout.read(), b'test_complete\n\ntest_complete\n\n') del ql def test_x8664_getcwd(self): - mock_stdout = pipe.SimpleOutStream(sys.stdout.fileno()) - ql = Qiling(["../examples/rootfs/x8664_linux/bin/testcwd"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG, stdout=mock_stdout) + ql = Qiling(["../examples/rootfs/x8664_linux/bin/testcwd"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG) + ql.os.stdout = pipe.SimpleOutStream(sys.stdout.fileno()) ql.run() + self.assertEqual(ql.os.stdout.read(), b'/\n/lib\n/bin\n/\n') del ql @@ -1022,11 +1027,14 @@ def test_arm_stat64(self): del ql def test_elf_linux_x8664_getdents(self): - output = io.BytesIO() - ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_getdents"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG, stdout=output) + ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_getdents"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG) + + ql.os.stdout = io.BytesIO() ql.run() - output.seek(0) - self.assertTrue("bin\n" in output.read().decode("utf-8")) + + ql.os.stdout.seek(0) + self.assertTrue("bin\n" in ql.os.stdout.read().decode("utf-8")) + del ql # TODO: Disable for now @@ -1046,10 +1054,11 @@ def test_armoabi_le_linux_syscall_elf_static(self): del ql def test_elf_linux_x86_getdents64(self): - mock_stdout = pipe.SimpleOutStream(sys.stdout.fileno()) - ql = Qiling(["../examples/rootfs/x86_linux/bin/x86_getdents64"], "../examples/rootfs/x86_linux", verbose=QL_VERBOSE.DEBUG, stdout=mock_stdout) + ql = Qiling(["../examples/rootfs/x86_linux/bin/x86_getdents64"], "../examples/rootfs/x86_linux", verbose=QL_VERBOSE.DEBUG) + ql.os.stdout = pipe.SimpleOutStream(sys.stdout.fileno()) ql.run() + self.assertTrue("bin\n" in ql.os.stdout.read().decode("utf-8")) del ql diff --git a/tests/test_elf_multithread.py b/tests/test_elf_multithread.py index 5b2a40375..a04edeefc 100644 --- a/tests/test_elf_multithread.py +++ b/tests/test_elf_multithread.py @@ -39,13 +39,13 @@ def test_elf_linux_cloexec_x8664(self): with open('../examples/rootfs/x8664_linux/testfile', 'wb') as f: f.write(b'0123456789') - err = ql_file.open('output.txt', os.O_RDWR | os.O_CREAT, 0o777) ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_cloexec_test"], "../examples/rootfs/x8664_linux", - verbose=QL_VERBOSE.DEBUG, - stderr=err, + verbose=QL_VERBOSE.DEBUG, multithread=True) + err = ql_file.open('output.txt', os.O_RDWR | os.O_CREAT, 0o777) + ql.os.stderr = err ql.run() os.close(err.fileno()) with open('output.txt', 'rb') as f: diff --git a/tests/test_riscv.py b/tests/test_riscv.py index 365f2bd42..a51a68bcf 100644 --- a/tests/test_riscv.py +++ b/tests/test_riscv.py @@ -14,11 +14,12 @@ class RISCVTest(unittest.TestCase): def test_riscv32_hello_linux(self): stdout = SimpleOutStream(1) ql = Qiling(['../examples/rootfs/riscv32_linux/bin/hello'], '../examples/rootfs/riscv32_linux/', - verbose=QL_VERBOSE.DEFAULT, stdout=stdout) - + verbose=QL_VERBOSE.DEFAULT) + def close(ql, fd): return 0 ql.set_syscall("close", close, QL_INTERCEPT.CALL) + ql.os.stdout = stdout ql.run() self.assertTrue(stdout.read() == b'Hello, World!\n') @@ -27,11 +28,12 @@ def close(ql, fd): def test_riscv64_hello_linux(self): stdout = SimpleOutStream(1) ql = Qiling(['../examples/rootfs/riscv64_linux/bin/hello'], '../examples/rootfs/riscv64_linux/', - verbose=QL_VERBOSE.DEFAULT, stdout=stdout) + verbose=QL_VERBOSE.DEFAULT) def close(ql, fd): return 0 ql.set_syscall("close", close, QL_INTERCEPT.CALL) + ql.os.stdout = stdout ql.run() self.assertTrue(stdout.read() == b'Hello, World!\n') @@ -40,8 +42,9 @@ def close(ql, fd): def test_riscv64_hello_dyn_linux(self): stdout = SimpleOutStream(1) ql = Qiling(['../examples/rootfs/riscv64_linux/bin/hello-linux'], '../examples/rootfs/riscv64_linux/', - verbose=QL_VERBOSE.DEFAULT, stdout=stdout) + verbose=QL_VERBOSE.DEFAULT) + ql.os.stdout = stdout ql.run() self.assertTrue(stdout.read() == b'Hello, World!\n') @@ -50,8 +53,9 @@ def test_riscv64_hello_dyn_linux(self): def test_riscv32_hello_dyn_linux(self): stdout = SimpleOutStream(1) ql = Qiling(['../examples/rootfs/riscv32_linux/bin/hello-linux'], '../examples/rootfs/riscv32_linux/', - verbose=QL_VERBOSE.DEFAULT, stdout=stdout) + verbose=QL_VERBOSE.DEFAULT) + ql.os.stdout = stdout ql.run() self.assertTrue(stdout.read() == b'Hello, World!\n') From a61f261b7bceb17c9239adf13350de3fe901533e Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 17 Jan 2022 13:03:23 +0200 Subject: [PATCH 037/406] ARM asm / disasm take current endianess and thumb mode into account --- qiling/arch/arm.py | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/qiling/arch/arm.py b/qiling/arch/arm.py index f85e879db..a5eba914b 100644 --- a/qiling/arch/arm.py +++ b/qiling/arch/arm.py @@ -6,8 +6,8 @@ from functools import cached_property from unicorn import Uc, UC_ARCH_ARM, UC_MODE_ARM, UC_MODE_THUMB, UC_MODE_BIG_ENDIAN -from capstone import Cs, CS_ARCH_ARM, CS_MODE_ARM, CS_MODE_THUMB -from keystone import Ks, KS_ARCH_ARM, KS_MODE_ARM, KS_MODE_THUMB +from capstone import Cs, CS_ARCH_ARM, CS_MODE_ARM, CS_MODE_THUMB, CS_MODE_BIG_ENDIAN +from keystone import Ks, KS_ARCH_ARM, KS_MODE_ARM, KS_MODE_THUMB, KS_MODE_BIG_ENDIAN from qiling import Qiling from qiling.const import QL_ARCH, QL_ENDIAN @@ -56,34 +56,32 @@ def get_pc(self) -> int: @property def disassembler(self) -> Cs: # note: we do not cache the disassembler instance; rather we refresh it - # each time to make sure thumb mode is taken into account + # each time to make sure current endianess and thumb mode are taken into + # account - if self.ql.archtype == QL_ARCH.ARM: - # FIXME: mode should take endianess into account - mode = CS_MODE_THUMB if self.is_thumb else CS_MODE_ARM + mode = CS_MODE_ARM - elif self.ql.archtype == QL_ARCH.ARM_THUMB: - mode = CS_MODE_THUMB + if self.endian == QL_ENDIAN.EB: + mode += CS_MODE_BIG_ENDIAN - else: - raise QlErrorArch(f'unexpected arch type {self.ql.archtype}') + if self.is_thumb: + mode += CS_MODE_THUMB return Cs(CS_ARCH_ARM, mode) @property def assembler(self) -> Ks: # note: we do not cache the assembler instance; rather we refresh it - # each time to make sure thumb mode is taken into account + # each time to make sure current endianess and thumb mode are taken into + # account - if self.ql.archtype == QL_ARCH.ARM: - # FIXME: mode should take endianess into account - mode = KS_MODE_THUMB if self.is_thumb else KS_MODE_ARM + mode = KS_MODE_ARM - elif self.ql.archtype == QL_ARCH.ARM_THUMB: - mode = KS_MODE_THUMB + if self.endian == QL_ENDIAN.EB: + mode += KS_MODE_BIG_ENDIAN - else: - raise QlErrorArch(f'unexpected arch type {self.ql.archtype}') + if self.is_thumb: + mode += KS_MODE_THUMB return Ks(KS_ARCH_ARM, mode) From 39dc9307f647731d357f337767a76b35f171a01e Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 17 Jan 2022 13:05:46 +0200 Subject: [PATCH 038/406] Add endian property to ARM-based archs --- qiling/arch/arm.py | 15 ++++++++++++++- qiling/arch/cortex_m.py | 4 ++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/qiling/arch/arm.py b/qiling/arch/arm.py index a5eba914b..cc5fad3d3 100644 --- a/qiling/arch/arm.py +++ b/qiling/arch/arm.py @@ -48,7 +48,20 @@ def regs(self) -> QlRegisterManager: return QlRegisterManager(self.uc, regs_map, pc_reg, sp_reg) - # get PC + @property + def endian(self) -> QL_ENDIAN: + # FIXME: ARM is a bi-endian architecture which allows flipping core endianess + # while running. endianess is tested in runtime through CPSR[9], however unicorn + # doesn't reflect the endianess correctly through that bit. + # @see: https://github.com/unicorn-engine/unicorn/issues/1542 + # + # we work around this by using the initial endianess configuration, even though + # it might have been changed since. + # + # return QL_ENDIAN.EB if self.regs.cpsr & (1 << 9) else QL_ENDIAN.EL + + return self._init_endian + def get_pc(self) -> int: # append 1 to pc if in thumb mode, or 0 otherwise return self.regs.pc + int(self.is_thumb) diff --git a/qiling/arch/cortex_m.py b/qiling/arch/cortex_m.py index 00046bac5..c78daa897 100644 --- a/qiling/arch/cortex_m.py +++ b/qiling/arch/cortex_m.py @@ -87,6 +87,10 @@ def assembler(self) -> Ks: def is_thumb(self) -> bool: return True + @property + def endian(self) -> QL_ENDIAN: + return QL_ENDIAN.EL + def step(self): self.ql.emu_start(self.get_pc(), 0, count=1) self.ql.hw.step() From 947622c64441860f636c6d602f0e6b4a78f2bcd1 Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 17 Jan 2022 13:09:31 +0200 Subject: [PATCH 039/406] Make endianess and thumb configurable on init --- qiling/arch/arm.py | 22 ++++++++++------------ qiling/arch/cortex_m.py | 11 +++++++---- qiling/core.py | 6 +++--- qiling/utils.py | 17 ++++++++++++----- 4 files changed, 32 insertions(+), 24 deletions(-) diff --git a/qiling/arch/arm.py b/qiling/arch/arm.py index cc5fad3d3..c86076786 100644 --- a/qiling/arch/arm.py +++ b/qiling/arch/arm.py @@ -10,33 +10,31 @@ from keystone import Ks, KS_ARCH_ARM, KS_MODE_ARM, KS_MODE_THUMB, KS_MODE_BIG_ENDIAN from qiling import Qiling -from qiling.const import QL_ARCH, QL_ENDIAN +from qiling.const import QL_ENDIAN from qiling.arch.arch import QlArch from qiling.arch import arm_const from qiling.arch.register import QlRegisterManager -from qiling.exception import QlErrorArch class QlArchARM(QlArch): bits = 32 - def __init__(self, ql: Qiling): + def __init__(self, ql: Qiling, endian: QL_ENDIAN, thumb: bool): super().__init__(ql) + self._init_endian = endian + self._init_thumb = thumb + self.arm_get_tls_addr = 0xFFFF0FE0 @cached_property def uc(self) -> Uc: - if self.ql.archendian == QL_ENDIAN.EB: - mode = UC_MODE_ARM + UC_MODE_BIG_ENDIAN - - elif self.ql.archtype == QL_ARCH.ARM_THUMB: - mode = UC_MODE_THUMB + mode = UC_MODE_ARM - elif self.ql.archtype == QL_ARCH.ARM: - mode = UC_MODE_ARM + if self._init_endian == QL_ENDIAN.EB: + mode += UC_MODE_BIG_ENDIAN - else: - raise QlErrorArch(f'unsupported arch type {self.ql.archtype}') + if self._init_thumb: + mode += UC_MODE_THUMB return Uc(UC_ARCH_ARM, mode) diff --git a/qiling/arch/cortex_m.py b/qiling/arch/cortex_m.py index c78daa897..6695be598 100644 --- a/qiling/arch/cortex_m.py +++ b/qiling/arch/cortex_m.py @@ -10,16 +10,16 @@ from capstone import Cs, CS_ARCH_ARM, CS_MODE_ARM, CS_MODE_MCLASS, CS_MODE_THUMB from keystone import Ks, KS_ARCH_ARM, KS_MODE_ARM, KS_MODE_THUMB -from qiling.const import QL_VERBOSE -from qiling.exception import QlErrorNotImplemented - +from qiling import Qiling from qiling.arch.arm import QlArchARM from qiling.arch import cortex_m_const from qiling.arch.register import QlRegisterManager from qiling.arch.cortex_m_const import IRQ, EXC_RETURN, CONTROL, EXCP +from qiling.const import QL_ENDIAN, QL_VERBOSE +from qiling.exception import QlErrorNotImplemented class QlInterruptContext(ContextDecorator): - def __init__(self, ql): + def __init__(self, ql: Qiling): self.ql = ql self.reg_context = ['xpsr', 'pc', 'lr', 'r12', 'r3', 'r2', 'r1', 'r0'] @@ -63,6 +63,9 @@ def __exit__(self, *exc): class QlArchCORTEX_M(QlArchARM): bits = 32 + def __init__(self, ql: Qiling): + super().__init__(ql, endian=QL_ENDIAN.EL, thumb=True) + @cached_property def uc(self): return Uc(UC_ARCH_ARM, UC_MODE_ARM + UC_MODE_MCLASS + UC_MODE_THUMB) diff --git a/qiling/core.py b/qiling/core.py index 42a699637..be751af73 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -150,14 +150,14 @@ def __init__( if not ql_is_valid_arch(self._archtype): raise QlErrorArch("Invalid ARCH: %s" % (self._archtype)) + if bigendian == True and self._archtype in QL_ARCH_ENDIAN: + self._archendian = QL_ENDIAN.EB + self._arch = arch_setup(self.archtype, self) ######################## # Archbit & Endianness # ######################## - if bigendian == True and self._archtype in QL_ARCH_ENDIAN: - self._archendian = QL_ENDIAN.EB - # Once we finish setting up archendian and arcbit, we can init QlCoreStructs. QlCoreStructs.__init__(self, self._archendian, self.arch.bits) diff --git a/qiling/utils.py b/qiling/utils.py index b34427ea9..60fcad3c1 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -420,9 +420,16 @@ def debugger_setup(options, ql): def arch_setup(archtype, ql): if not ql_is_valid_arch(archtype): raise QlErrorArch("Invalid Arch") - - if archtype == QL_ARCH.ARM_THUMB: - archtype = QL_ARCH.ARM + + args = [ql] + + # set endianess and thumb mode for arm-based archs + if archtype == QL_ARCH.ARM: + args.extend((ql.archendian, False)) + + elif archtype == QL_ARCH.ARM_THUMB: + archtype = QL_ARCH.ARM + args.extend((ql.archendian, True)) archmanager = f'QlArch{arch_convert_str(archtype).upper()}' @@ -432,9 +439,9 @@ def arch_setup(archtype, ql): arch_str = arch_convert_str(archtype) if ql.interpreter: - return ql_get_module_function(f"qiling.arch.{arch_str.lower()}.{arch_str.lower()}", archmanager)(ql) + return ql_get_module_function(f"qiling.arch.{arch_str.lower()}.{arch_str.lower()}", archmanager)(*args) else: - return ql_get_module_function(f"qiling.arch.{arch_str.lower()}", archmanager)(ql) + return ql_get_module_function(f"qiling.arch.{arch_str.lower()}", archmanager)(*args) # This function is extracted from os_setup so I put it here. From 63a1d81256aa34a0059f835bbc4a6fc6bdf5bee1 Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 17 Jan 2022 13:50:35 +0200 Subject: [PATCH 040/406] Make ARM thumb mode configurable through core init kwargs --- qiling/core.py | 3 ++- qiling/utils.py | 8 ++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/qiling/core.py b/qiling/core.py index be751af73..0e93cff73 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -49,6 +49,7 @@ def __init__( filter = None, stop_on_stackpointer = False, stop_on_exit_trap = False, + **kwargs ): """ Create a Qiling instance. @@ -153,7 +154,7 @@ def __init__( if bigendian == True and self._archtype in QL_ARCH_ENDIAN: self._archendian = QL_ENDIAN.EB - self._arch = arch_setup(self.archtype, self) + self._arch = arch_setup(self.archtype, self._archendian, kwargs.get('thumb', False), self) ######################## # Archbit & Endianness # diff --git a/qiling/utils.py b/qiling/utils.py index 60fcad3c1..6c012374d 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -417,7 +417,7 @@ def debugger_setup(options, ql): return None -def arch_setup(archtype, ql): +def arch_setup(archtype, endian: QL_ENDIAN, thumb: bool, ql): if not ql_is_valid_arch(archtype): raise QlErrorArch("Invalid Arch") @@ -425,11 +425,7 @@ def arch_setup(archtype, ql): # set endianess and thumb mode for arm-based archs if archtype == QL_ARCH.ARM: - args.extend((ql.archendian, False)) - - elif archtype == QL_ARCH.ARM_THUMB: - archtype = QL_ARCH.ARM - args.extend((ql.archendian, True)) + args.extend((endian, thumb)) archmanager = f'QlArch{arch_convert_str(archtype).upper()}' From 9655b26407b7957d80eacc97b6ff956679e56f68 Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 17 Jan 2022 13:52:32 +0200 Subject: [PATCH 041/406] Consolidate ARM_THUMB into ARM --- qiling/arch/utils.py | 8 +++++--- qiling/const.py | 1 - qiling/debugger/qdb/frontend.py | 4 ++-- qiling/debugger/qdb/qdb.py | 2 +- qiling/debugger/qdb/utils.py | 5 ++--- qiling/extensions/idaplugin/qilingida.py | 2 +- qiling/os/linux/function_hook.py | 2 +- qiling/os/posix/const_mapping.py | 7 +------ qiling/os/posix/syscall/stat.py | 4 ++-- qltool | 3 ++- 10 files changed, 17 insertions(+), 21 deletions(-) diff --git a/qiling/arch/utils.py b/qiling/arch/utils.py index a1b06b981..05b5af97b 100644 --- a/qiling/arch/utils.py +++ b/qiling/arch/utils.py @@ -64,12 +64,13 @@ def ql_hook_block_disasm(ql, address, size): # used by qltool prior to ql instantiation. to get an assembler object # after ql instantiation, use the appropriate ql.arch method -def assembler(arch: QL_ARCH, endianess: QL_ENDIAN) -> Ks: +def assembler(arch: QL_ARCH, endianess: QL_ENDIAN, is_thumb: bool) -> Ks: """Instantiate an assembler object for a specified architecture. Args: arch: architecture type endianess: architecture endianess + is_thumb: thumb mode for ARM (ignored otherwise) Returns: an assembler object """ @@ -79,10 +80,11 @@ def assembler(arch: QL_ARCH, endianess: QL_ENDIAN) -> Ks: QL_ENDIAN.EB : KS_MODE_BIG_ENDIAN }[endianess] + thumb = KS_MODE_THUMB if is_thumb else 0 + asm_map = { - QL_ARCH.ARM : (KS_ARCH_ARM, KS_MODE_ARM), + QL_ARCH.ARM : (KS_ARCH_ARM, KS_MODE_ARM + endian + thumb), QL_ARCH.ARM64 : (KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN), - QL_ARCH.ARM_THUMB : (KS_ARCH_ARM, KS_MODE_THUMB), QL_ARCH.MIPS : (KS_ARCH_MIPS, KS_MODE_MIPS32 + endian), QL_ARCH.A8086 : (KS_ARCH_X86, KS_MODE_16), QL_ARCH.X86 : (KS_ARCH_X86, KS_MODE_32), diff --git a/qiling/const.py b/qiling/const.py index cc07e3c2e..90530cb98 100644 --- a/qiling/const.py +++ b/qiling/const.py @@ -14,7 +14,6 @@ class QL_ARCH(IntEnum): X86 = 101 X8664 = 102 ARM = 103 - ARM_THUMB = 104 ARM64 = 105 MIPS = 106 A8086 = 107 diff --git a/qiling/debugger/qdb/frontend.py b/qiling/debugger/qdb/frontend.py index b326ea93c..87d48538d 100644 --- a/qiling/debugger/qdb/frontend.py +++ b/qiling/debugger/qdb/frontend.py @@ -56,7 +56,7 @@ def extract_count(t): else: rest = _args - if ql.archtype in (QL_ARCH.ARM, QL_ARCH.ARM_THUMB): + if ql.archtype == QL_ARCH.ARM: rest = rest.replace("fp", "r11") elif ql.archtype == QL_ARCH.MIPS: @@ -192,7 +192,7 @@ def context_reg(ql: Qiling, saved_states: Optional[Mapping[str, int]] = None, /, print(lines.format(*_cur_regs.values())) - elif ql.archtype in (QL_ARCH.ARM, QL_ARCH.ARM_THUMB, QL_ARCH.CORTEX_M): + elif ql.archtype in (QL_ARCH.ARM, QL_ARCH.CORTEX_M): _cur_regs.update({"sl": _cur_regs.pop("r10")}) diff --git a/qiling/debugger/qdb/qdb.py b/qiling/debugger/qdb/qdb.py index 4525b1197..98914d9c5 100644 --- a/qiling/debugger/qdb/qdb.py +++ b/qiling/debugger/qdb/qdb.py @@ -130,7 +130,7 @@ def _run(self: Qldbg, address: int = 0, end: int = 0, count: int = 0) -> None: return - if self.ql.archtype in (QL_ARCH.ARM, QL_ARCH.ARM_THUMB, QL_ARCH.CORTEX_M) and is_thumb(self.ql.arch.regs.cpsr): + if self.ql.archtype in (QL_ARCH.ARM, QL_ARCH.CORTEX_M) and is_thumb(self.ql.arch.regs.cpsr): address |= 1 self.ql.emu_start(begin=address, end=end, count=count) diff --git a/qiling/debugger/qdb/utils.py b/qiling/debugger/qdb/utils.py index 0b4344ce1..08e797075 100644 --- a/qiling/debugger/qdb/utils.py +++ b/qiling/debugger/qdb/utils.py @@ -27,7 +27,7 @@ def dump_regs(ql: Qiling) -> Mapping[str, int]: "ra", "k0", "k1", "pc", ) - elif ql.archtype in (QL_ARCH.ARM, QL_ARCH.ARM_THUMB): + elif ql.archtype == QL_ARCH.ARM: _reg_order = ( "r0", "r1", "r2", "r3", @@ -107,7 +107,6 @@ def handle_bnj(ql: Qiling, cur_addr: str) -> Callable[[Qiling, str], int]: return { QL_ARCH.MIPS : handle_bnj_mips, QL_ARCH.ARM : handle_bnj_arm, - QL_ARCH.ARM_THUMB: handle_bnj_arm, QL_ARCH.CORTEX_M : handle_bnj_arm, }.get(ql.archtype)(ql, cur_addr) @@ -142,7 +141,7 @@ def _read_inst(ql: Qiling, addr: int) -> int: result = ql.mem.read(addr, 4) - if ql.archtype in (QL_ARCH.ARM, QL_ARCH.ARM_THUMB, QL_ARCH.CORTEX_M): + if ql.archtype in (QL_ARCH.ARM, QL_ARCH.CORTEX_M): if is_thumb(ql.arch.regs.cpsr): first_two = ql.unpack16(ql.mem.read(addr, 2)) diff --git a/qiling/extensions/idaplugin/qilingida.py b/qiling/extensions/idaplugin/qilingida.py index a2739d7b6..e2d6d7cec 100644 --- a/qiling/extensions/idaplugin/qilingida.py +++ b/qiling/extensions/idaplugin/qilingida.py @@ -1636,7 +1636,7 @@ def _search_path(self): if IDA.get_ql_arch_string() == "arm32": if self._thumb_detect(first_block.start_ea): logging.info(f"Thumb detected, enable it.") - self.deflatqlemu.start(archtype=QL_ARCH.ARM_THUMB) + self.deflatqlemu.start(archtype=QL_ARCH.ARM, thumb=True) self.deflatqlemu.ql.arch.regs.cpsr |= 0x20 self.append = 1 else: diff --git a/qiling/os/linux/function_hook.py b/qiling/os/linux/function_hook.py index 14c91f1f0..cb81c84d6 100644 --- a/qiling/os/linux/function_hook.py +++ b/qiling/os/linux/function_hook.py @@ -559,7 +559,7 @@ def __init__(self, ql, phoff, phnum, phentsize, load_base, hook_mem): self.endian = 0 if ql.archendian == QL_ENDIAN.EL else 1 # ARM - if self.ql.archtype in [QL_ARCH.ARM, QL_ARCH.ARM_THUMB]: + if self.ql.archtype == QL_ARCH.ARM: self.GLOB_DAT = 21 self.JMP_SLOT = 22 # orr r1, r1, r1 diff --git a/qiling/os/posix/const_mapping.py b/qiling/os/posix/const_mapping.py index 42673b304..b14a27150 100644 --- a/qiling/os/posix/const_mapping.py +++ b/qiling/os/posix/const_mapping.py @@ -52,7 +52,7 @@ def flag_mapping(flags, mapping_name, mapping_from, mapping_to): if ql.ostype == QL_OS.LINUX: if ql.archtype in (QL_ARCH.X86, QL_ARCH.X8664): f = linux_x86_open_flags - elif ql.archtype in (QL_ARCH.ARM, QL_ARCH.ARM_THUMB, QL_ARCH.ARM64): + elif ql.archtype in (QL_ARCH.ARM, QL_ARCH.ARM64): f = linux_arm_open_flags elif ql.archtype == QL_ARCH.MIPS: f = linux_mips_open_flags @@ -130,7 +130,6 @@ def socket_type_mapping(t, archtype, ostype): QL_ARCH.X86: linux_x86_socket_types, QL_ARCH.X8664: linux_x86_socket_types, QL_ARCH.ARM: linux_arm_socket_types, - QL_ARCH.ARM_THUMB: linux_arm_socket_types, QL_ARCH.ARM64: linux_arm_socket_types, QL_ARCH.MIPS: linux_mips_socket_types, }[archtype] @@ -150,7 +149,6 @@ def socket_domain_mapping(p, archtype, ostype): QL_ARCH.X86: linux_x86_socket_domain, QL_ARCH.X8664: linux_x86_socket_domain, QL_ARCH.ARM: linux_arm_socket_domain, - QL_ARCH.ARM_THUMB: linux_arm_socket_domain, QL_ARCH.ARM64: linux_arm_socket_domain, QL_ARCH.MIPS: linux_mips_socket_domain, }[archtype] @@ -167,7 +165,6 @@ def socket_level_mapping(t, archtype, ostype): QL_ARCH.X86: linux_x86_socket_level, QL_ARCH.X8664: linux_x86_socket_level, QL_ARCH.ARM: linux_arm_socket_level, - QL_ARCH.ARM_THUMB: linux_arm_socket_level, QL_ARCH.ARM64: linux_arm_socket_level, QL_ARCH.MIPS: linux_mips_socket_level, }[archtype] @@ -185,7 +182,6 @@ def socket_ip_option_mapping(t, archtype, ostype): QL_ARCH.X86: linux_socket_ip_options, QL_ARCH.X8664: linux_socket_ip_options, QL_ARCH.ARM: linux_socket_ip_options, - QL_ARCH.ARM_THUMB: linux_socket_ip_options, QL_ARCH.ARM64: linux_socket_ip_options, QL_ARCH.MIPS: linux_socket_ip_options, }[archtype] @@ -203,7 +199,6 @@ def socket_option_mapping(t, archtype, ostype): QL_ARCH.X86: linux_x86_socket_options, QL_ARCH.X8664: linux_x86_socket_options, QL_ARCH.ARM: linux_arm_socket_options, - QL_ARCH.ARM_THUMB: linux_arm_socket_options, QL_ARCH.ARM64: linux_arm_socket_options, QL_ARCH.MIPS: linux_mips_socket_options, }[archtype] diff --git a/qiling/os/posix/syscall/stat.py b/qiling/os/posix/syscall/stat.py index 9e60307ce..16c4433c1 100644 --- a/qiling/os/posix/syscall/stat.py +++ b/qiling/os/posix/syscall/stat.py @@ -936,7 +936,7 @@ def get_stat64_struct(ql: Qiling): return LinuxX86Stat64() elif ql.archtype == QL_ARCH.MIPS: return LinuxMips32Stat64() - elif ql.archtype in (QL_ARCH.ARM, QL_ARCH.ARM_THUMB): + elif ql.archtype == QL_ARCH.ARM: return LinuxARMStat64() elif ql.archtype in (QL_ARCH.RISCV, QL_ARCH.RISCV64): return LinuxRISCVStat() @@ -965,7 +965,7 @@ def get_stat_struct(ql: Qiling): return LinuxMips64Stat() else: return LinuxMips32Stat() - elif ql.archtype in (QL_ARCH.ARM, QL_ARCH.ARM_THUMB): + elif ql.archtype == QL_ARCH.ARM: if ql.archendian == QL_ENDIAN.EL: return LinuxARMStat() else: diff --git a/qltool b/qltool index 4dbaa1cac..5adbf38dc 100755 --- a/qltool +++ b/qltool @@ -69,7 +69,7 @@ def handle_code(options: argparse.Namespace): 'big' : QL_ENDIAN.EB }[options.endian] - assembler = arch_utils.assembler(archtype, archendian) + assembler = arch_utils.assembler(archtype, archendian, options.thumb) code, _ = assembler.asm(assembly) code = bytes(code) @@ -184,6 +184,7 @@ if __name__ == '__main__': code_parser.add_argument('-f', '--filename', metavar="FILE", help="filename") code_parser.add_argument('-i', '--input', metavar="INPUT", dest="input", help='input hex value') code_parser.add_argument('--arch', required=True, choices=arch_map) + code_parser.add_argument('--thumb', action='store_true', help='specify thumb mode for ARM') code_parser.add_argument('--endian', choices=('little', 'big'), default='little') code_parser.add_argument('--os', required=True, choices=os_map) code_parser.add_argument('--rootfs', help='emulated root filesystem, that is where all libraries reside') From 0f3bc0e96bec2757b49bd0efe8ceebeedf5d790b Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 17 Jan 2022 14:04:16 +0200 Subject: [PATCH 042/406] Make MIPS endianess configurable on init --- qiling/arch/mips.py | 16 +++++++++++++--- qiling/utils.py | 3 +++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/qiling/arch/mips.py b/qiling/arch/mips.py index 406f51f55..324320985 100644 --- a/qiling/arch/mips.py +++ b/qiling/arch/mips.py @@ -9,6 +9,7 @@ from capstone import Cs, CS_ARCH_MIPS, CS_MODE_MIPS32, CS_MODE_BIG_ENDIAN, CS_MODE_LITTLE_ENDIAN from keystone import Ks, KS_ARCH_MIPS, KS_MODE_MIPS32, KS_MODE_BIG_ENDIAN, KS_MODE_LITTLE_ENDIAN +from qiling import Qiling from qiling.const import QL_ENDIAN from qiling.arch.arch import QlArch from qiling.arch import mips_const @@ -17,12 +18,17 @@ class QlArchMIPS(QlArch): bits = 32 + def __init__(self, ql: Qiling, endian: QL_ENDIAN): + super().__init__(ql) + + self._init_endian = endian + @cached_property def uc(self) -> Uc: endian = { QL_ENDIAN.EB: UC_MODE_BIG_ENDIAN, QL_ENDIAN.EL: UC_MODE_LITTLE_ENDIAN - }[self.ql.archendian] + }[self.endian] return Uc(UC_ARCH_MIPS, UC_MODE_MIPS32 + endian) @@ -43,7 +49,7 @@ def disassembler(self) -> Cs: endian = { QL_ENDIAN.EL : CS_MODE_LITTLE_ENDIAN, QL_ENDIAN.EB : CS_MODE_BIG_ENDIAN - }[self.ql.archendian] + }[self.endian] return Cs(CS_ARCH_MIPS, CS_MODE_MIPS32 + endian) @@ -52,6 +58,10 @@ def assembler(self) -> Ks: endian = { QL_ENDIAN.EL : KS_MODE_LITTLE_ENDIAN, QL_ENDIAN.EB : KS_MODE_BIG_ENDIAN - }[self.ql.archendian] + }[self.endian] return Ks(KS_ARCH_MIPS, KS_MODE_MIPS32 + endian) + + @property + def endian(self) -> QL_ENDIAN: + return self._init_endian diff --git a/qiling/utils.py b/qiling/utils.py index 6c012374d..41ecb9e27 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -427,6 +427,9 @@ def arch_setup(archtype, endian: QL_ENDIAN, thumb: bool, ql): if archtype == QL_ARCH.ARM: args.extend((endian, thumb)) + elif archtype == QL_ARCH.MIPS: + args.append(endian) + archmanager = f'QlArch{arch_convert_str(archtype).upper()}' if archtype in (QL_ARCH.X8664, QL_ARCH.A8086): From 6623b617e76c25f6c92e66fec7bbfee4094c3f71 Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 17 Jan 2022 14:33:12 +0200 Subject: [PATCH 043/406] Remove archendian property from core and move it to QlArch --- qiling/arch/arch.py | 9 +++++++++ qiling/arch/arm64.py | 5 +++++ qiling/arch/riscv.py | 5 +++++ qiling/arch/x86.py | 5 +++++ qiling/core.py | 30 ++++++++++-------------------- 5 files changed, 34 insertions(+), 20 deletions(-) diff --git a/qiling/arch/arch.py b/qiling/arch/arch.py index 3ccea732b..cec3cbbce 100644 --- a/qiling/arch/arch.py +++ b/qiling/arch/arch.py @@ -11,6 +11,7 @@ from keystone import Ks from qiling import Qiling +from qiling.const import QL_ENDIAN from .register import QlRegisterManager from .utils import QlArchUtils @@ -131,3 +132,11 @@ def assembler(self) -> Ks: """ pass + + @property + @abstractmethod + def endian(self) -> QL_ENDIAN: + """Get processor endianess. + """ + + pass \ No newline at end of file diff --git a/qiling/arch/arm64.py b/qiling/arch/arm64.py index f19451337..0d59b321f 100644 --- a/qiling/arch/arm64.py +++ b/qiling/arch/arm64.py @@ -12,6 +12,7 @@ from qiling.arch.arch import QlArch from qiling.arch import arm64_const from qiling.arch.register import QlRegisterManager +from qiling.const import QL_ENDIAN class QlArchARM64(QlArch): bits = 64 @@ -32,6 +33,10 @@ def regs(self) -> QlRegisterManager: return QlRegisterManager(self.uc, regs_map, pc_reg, sp_reg) + @property + def endian(self) -> QL_ENDIAN: + return QL_ENDIAN.EL + @cached_property def disassembler(self) -> Cs: return Cs(CS_ARCH_ARM64, CS_MODE_ARM) diff --git a/qiling/arch/riscv.py b/qiling/arch/riscv.py index b52ee93a7..f9948a53a 100644 --- a/qiling/arch/riscv.py +++ b/qiling/arch/riscv.py @@ -13,6 +13,7 @@ from qiling.arch.register import QlRegisterManager from qiling.arch import riscv_const from qiling.arch.riscv_const import * +from qiling.const import QL_ENDIAN from qiling.exception import QlErrorNotImplemented class QlArchRISCV(QlArch): @@ -35,6 +36,10 @@ def regs(self) -> QlRegisterManager: return QlRegisterManager(self.uc, regs_map, pc_reg, sp_reg) + @property + def endian(self) -> QL_ENDIAN: + return QL_ENDIAN.EL + @cached_property def disassembler(self) -> Cs: try: diff --git a/qiling/arch/x86.py b/qiling/arch/x86.py index b50624208..74fa4500b 100644 --- a/qiling/arch/x86.py +++ b/qiling/arch/x86.py @@ -17,9 +17,14 @@ from qiling.arch.register import QlRegisterManager from qiling.arch import x86_const from qiling.arch.x86_const import * +from qiling.const import QL_ENDIAN from qiling.exception import QlGDTError class QlArchIntel(QlArch): + @property + def endian(self) -> QL_ENDIAN: + return QL_ENDIAN.EL + @cached_property def msr(self) -> QlMsrManager: """Model-Specific Registers. diff --git a/qiling/core.py b/qiling/core.py index 0e93cff73..d82ec3021 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -54,8 +54,6 @@ def __init__( """ Create a Qiling instance. For each argument or property, please refer to its docstring. e.g. Qiling.multithread.__doc__ - - The only exception is "bigendian" parameter, see Qiling.archendian.__doc__ for details. """ ################################## @@ -67,7 +65,6 @@ def __init__( self._code = code self._ostype = ostype self._archtype = archtype - self._archendian = QL_ENDIAN.EL self._profile = profile self._console = console self._log_file = log_file @@ -134,11 +131,13 @@ def __init__( if type(self._ostype) is str: self._ostype = ostype_convert(self._ostype) + archendian = None + if self._archtype is None: guessed_archtype, guessed_ostype, guessed_archendian = ql_guess_emu_env(self._path) self._archtype = guessed_archtype - self._archendian = guessed_archendian - + archendian = guessed_archendian + if self._ostype is None: self._ostype = guessed_ostype @@ -151,10 +150,13 @@ def __init__( if not ql_is_valid_arch(self._archtype): raise QlErrorArch("Invalid ARCH: %s" % (self._archtype)) - if bigendian == True and self._archtype in QL_ARCH_ENDIAN: - self._archendian = QL_ENDIAN.EB + if bigendian and self._archtype in QL_ARCH_ENDIAN: + archendian = QL_ENDIAN.EB + + if archendian is None: + archendian = QL_ENDIAN.EL - self._arch = arch_setup(self.archtype, self._archendian, kwargs.get('thumb', False), self) + self._arch = arch_setup(self.archtype, archendian, kwargs.get('thumb', False), self) ######################## # Archbit & Endianness # @@ -374,18 +376,6 @@ def archtype(self) -> QL_ARCH: """ return self._archtype - @property - def archendian(self) -> QL_ENDIAN: - """ The architecure endian. - - Note: Please pass "bigendian=True" or "bingendian=False" to set this property. - This option only takes effect for shellcode. - - Type: int - Example: Qiling(code=b"\x90", ostype="macos", archtype="x8664", bigendian=False) - """ - return self._archendian - @property def code(self) -> bytes: """ The shellcode to execute. From 953dcbabe9b120f0425df7e9dc398712b00cc5a2 Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 17 Jan 2022 14:34:24 +0200 Subject: [PATCH 044/406] Adjust all archendian usages --- qiling/arch/arm.py | 2 +- qiling/core.py | 4 ++-- qiling/debugger/gdb/gdb.py | 8 ++++---- qiling/loader/elf.py | 2 +- qiling/os/linux/function_hook.py | 2 +- qiling/os/posix/syscall/stat.py | 6 +++--- qiling/os/uefi/bs.py | 1 - 7 files changed, 12 insertions(+), 13 deletions(-) diff --git a/qiling/arch/arm.py b/qiling/arch/arm.py index c86076786..9f3e26711 100644 --- a/qiling/arch/arm.py +++ b/qiling/arch/arm.py @@ -116,7 +116,7 @@ def init_get_tls(self): """ sc = b'\x04\x00\x8f\xe2\x00\x00\x90\xe5\x0e\xf0\xa0\xe1\x00\x00\x00\x00' - # if ql.archendian == QL_ENDIAN.EB: + # if self.endian == QL_ENDIAN.EB: # sc = swap_endianess(sc) self.ql.mem.write(self.arm_get_tls_addr, sc) diff --git a/qiling/core.py b/qiling/core.py index d82ec3021..4e5564012 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -162,8 +162,8 @@ def __init__( # Archbit & Endianness # ######################## # Once we finish setting up archendian and arcbit, we can init QlCoreStructs. - QlCoreStructs.__init__(self, self._archendian, self.arch.bits) - + QlCoreStructs.__init__(self, self.arch.endian, self.arch.bits) + ####################################### # Loader and General Purpose OS check # diff --git a/qiling/debugger/gdb/gdb.py b/qiling/debugger/gdb/gdb.py index 33f10f3e8..296f07da4 100644 --- a/qiling/debugger/gdb/gdb.py +++ b/qiling/debugger/gdb/gdb.py @@ -185,7 +185,7 @@ def gdbqmark_converter(arch): nullfill = "0" * int(self.ql.arch.bits / 4) if self.ql.archtype== QL_ARCH.MIPS: - if self.ql.archendian == QL_ENDIAN.EB: + if self.ql.arch.endian == QL_ENDIAN.EB: sp = self.addr_to_str(self.ql.arch.regs.arch_sp, endian ="little") pc = self.addr_to_str(self.ql.arch.regs.arch_pc, endian ="little") self.send('T%.2x%.2x:%s;%.2x:%s;' %(GDB_SIGNAL_TRAP, idhex, sp, pcid, pc)) @@ -249,7 +249,7 @@ def handle_g(subcmd): elif self.ql.archtype == QL_ARCH.MIPS: for reg in self.tables[QL_ARCH.MIPS][:38]: r = self.ql.arch.regs.read(reg) - if self.ql.archendian == QL_ENDIAN.EL: + if self.ql.arch.endian == QL_ENDIAN.EL: tmp = self.addr_to_str(r, endian ="little") else: tmp = self.addr_to_str(r) @@ -396,7 +396,7 @@ def handle_p(subcmd): reg_value = self.ql.arch.regs.read(self.tables[QL_ARCH.MIPS][reg_index - 1]) else: reg_value = 0 - if self.ql.archendian == QL_ENDIAN.EL: + if self.ql.arch.endian == QL_ENDIAN.EL: reg_value = self.addr_to_str(reg_value, endian="little") else: reg_value = self.addr_to_str(reg_value) @@ -447,7 +447,7 @@ def handle_P(subcmd): elif self.ql.archtype== QL_ARCH.MIPS: reg_data = int(reg_data, 16) - if self.ql.archendian == QL_ENDIAN.EL: + if self.ql.arch.endian == QL_ENDIAN.EL: reg_data = int.from_bytes(struct.pack(' int: elif self.ql.arch.bits == 32: elf_hwcap = 0x1fb8d7 - if self.ql.archendian == QL_ENDIAN.EB: + if self.ql.arch.endian == QL_ENDIAN.EB: # FIXME: considering this is a 32 bits value, it is not a big-endian version of the # value above like it is meant to be, since the one above has an implied leading zero # byte (i.e. 0x001fb8d7) which the EB value didn't take into account diff --git a/qiling/os/linux/function_hook.py b/qiling/os/linux/function_hook.py index cb81c84d6..b8dfc815e 100644 --- a/qiling/os/linux/function_hook.py +++ b/qiling/os/linux/function_hook.py @@ -556,7 +556,7 @@ def __init__(self, ql, phoff, phnum, phentsize, load_base, hook_mem): self.mips_gotsym = None self.rel_list = [] - self.endian = 0 if ql.archendian == QL_ENDIAN.EL else 1 + self.endian = 0 if ql.arch.endian == QL_ENDIAN.EL else 1 # ARM if self.ql.archtype == QL_ARCH.ARM: diff --git a/qiling/os/posix/syscall/stat.py b/qiling/os/posix/syscall/stat.py index 16c4433c1..903977dbb 100644 --- a/qiling/os/posix/syscall/stat.py +++ b/qiling/os/posix/syscall/stat.py @@ -966,12 +966,12 @@ def get_stat_struct(ql: Qiling): else: return LinuxMips32Stat() elif ql.archtype == QL_ARCH.ARM: - if ql.archendian == QL_ENDIAN.EL: + if ql.arch.endian == QL_ENDIAN.EL: return LinuxARMStat() else: return LinuxARMEBStat() elif ql.archtype == QL_ARCH.ARM64: - if ql.archendian == QL_ENDIAN.EL: + if ql.arch.endian == QL_ENDIAN.EL: return LinuxARM64Stat() else: return LinuxARM64EBStat() @@ -981,7 +981,7 @@ def get_stat_struct(ql: Qiling): if ql.archtype == QL_ARCH.ARM64: return QNXARM64Stat() elif ql.archtype == QL_ARCH.ARM: - if ql.archendian == QL_ENDIAN.EL: + if ql.arch.endian == QL_ENDIAN.EL: return QNXARMStat() else: return QNXARMEBStat() diff --git a/qiling/os/uefi/bs.py b/qiling/os/uefi/bs.py index b34b07407..3532a159c 100644 --- a/qiling/os/uefi/bs.py +++ b/qiling/os/uefi/bs.py @@ -504,7 +504,6 @@ def hook_SetMem(ql: Qiling, address: int, params): value: int = params["Value"] & 0xff size = params["Size"] - byteorder = 'little' if ql.archendian == QL_ENDIAN.EL else 'big' ql.mem.write(buffer, bytes([value]) * size) @dxeapi(params = { From ac1dd737f85d9d30e561989b1a8750020d82c57f Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 17 Jan 2022 14:35:23 +0200 Subject: [PATCH 045/406] Minor changes and fixes --- qiling/arch/arch.py | 2 +- qiling/arch/arm.py | 11 +++++++---- qiling/arch/arm64.py | 4 ++-- qiling/core.py | 5 ++--- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/qiling/arch/arch.py b/qiling/arch/arch.py index cec3cbbce..f1bfb77f1 100644 --- a/qiling/arch/arch.py +++ b/qiling/arch/arch.py @@ -78,7 +78,7 @@ def stack_read(self, offset: int) -> int: Args: offset: offset in bytes from the top of the stack, not necessarily aligned to the native stack item size. the offset may be either positive or netagive, where - a 0 value means overwriting the value at the top of the stack + a 0 value means retrieving the value at the top of the stack Returns: the value at the specified address """ diff --git a/qiling/arch/arm.py b/qiling/arch/arm.py index 9f3e26711..3cd0a2337 100644 --- a/qiling/arch/arm.py +++ b/qiling/arch/arm.py @@ -46,6 +46,10 @@ def regs(self) -> QlRegisterManager: return QlRegisterManager(self.uc, regs_map, pc_reg, sp_reg) + @property + def is_thumb(self) -> bool: + return bool(self.regs.cpsr & (1 << 5)) + @property def endian(self) -> QL_ENDIAN: # FIXME: ARM is a bi-endian architecture which allows flipping core endianess @@ -61,6 +65,9 @@ def endian(self) -> QL_ENDIAN: return self._init_endian def get_pc(self) -> int: + """Get effective PC value, taking Thumb mode into account. + """ + # append 1 to pc if in thumb mode, or 0 otherwise return self.regs.pc + int(self.is_thumb) @@ -102,10 +109,6 @@ def enable_vfp(self) -> None: self.regs.fpexc = (1 << 30) - @property - def is_thumb(self) -> bool: - return bool(self.regs.cpsr & (1 << 5)) - """ set_tls """ diff --git a/qiling/arch/arm64.py b/qiling/arch/arm64.py index 0d59b321f..1f0afcf10 100644 --- a/qiling/arch/arm64.py +++ b/qiling/arch/arm64.py @@ -7,7 +7,7 @@ from unicorn import Uc, UC_ARCH_ARM64, UC_MODE_ARM from capstone import Cs, CS_ARCH_ARM64, CS_MODE_ARM -from keystone import Ks, KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN +from keystone import Ks, KS_ARCH_ARM64, KS_MODE_ARM from qiling.arch.arch import QlArch from qiling.arch import arm64_const @@ -43,7 +43,7 @@ def disassembler(self) -> Cs: @cached_property def assembler(self) -> Ks: - return Ks(KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN) + return Ks(KS_ARCH_ARM64, KS_MODE_ARM) def enable_vfp(self): self.regs.cpacr_el1 = self.regs.cpacr_el1 | 0x300000 diff --git a/qiling/core.py b/qiling/core.py index 4e5564012..1f8b69ae3 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -144,10 +144,10 @@ def __init__( elif self._ostype == None: self._ostype = arch_os_convert(self._archtype) - if not ql_is_valid_ostype(self._ostype): + if self._ostype is None or not ql_is_valid_ostype(self._ostype): raise QlErrorOsType("Invalid OS: %s" % (self._ostype)) - if not ql_is_valid_arch(self._archtype): + if self._archtype is None or not ql_is_valid_arch(self._archtype): raise QlErrorArch("Invalid ARCH: %s" % (self._archtype)) if bigendian and self._archtype in QL_ARCH_ENDIAN: @@ -369,7 +369,6 @@ def archtype(self) -> QL_ARCH: - "x8664" : x86_64 - "mips" : MIPS - "arm" : ARM - - "arm_thumb" : ARM with thumb mode. - "arm64" : ARM64 - "a8086" : 8086 Example: Qiling(code=b"\x90", ostype="macos", archtype="x8664", bigendian=False) From d7928050febf150761d0deaed25b17335f471516 Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 17 Jan 2022 15:52:31 +0200 Subject: [PATCH 046/406] Add endian property to EVM --- qiling/arch/evm/evm.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/qiling/arch/evm/evm.py b/qiling/arch/evm/evm.py index ed87be3d1..5d2e92430 100644 --- a/qiling/arch/evm/evm.py +++ b/qiling/arch/evm/evm.py @@ -33,3 +33,7 @@ def stack_write(self, offset, data): @property def uc(self): return None + + @property + def endian(self) -> QL_ENDIAN: + return QL_ENDIAN.EL From 24a29a0cbbf2fb07ff1d6da4a8a3f3c7e3c3bb20 Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 21 Jan 2022 13:05:43 +0200 Subject: [PATCH 047/406] Remove get_pc from arch and move it to QlArchARM as "effective_pc" --- qiling/arch/arch.py | 5 ----- qiling/arch/arm.py | 3 ++- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/qiling/arch/arch.py b/qiling/arch/arch.py index f1bfb77f1..f354eb30a 100644 --- a/qiling/arch/arch.py +++ b/qiling/arch/arch.py @@ -101,11 +101,6 @@ def stack_write(self, offset: int, value: int) -> None: self.ql.mem.write(self.regs.arch_sp + offset, self.ql.pack(value)) - # get PC - def get_pc(self) -> int: - return self.regs.arch_pc - - # Unicorn's CPU state save def context_save(self) -> UcContext: return self.uc.context_save() diff --git a/qiling/arch/arm.py b/qiling/arch/arm.py index 3cd0a2337..1b9debc82 100644 --- a/qiling/arch/arm.py +++ b/qiling/arch/arm.py @@ -64,7 +64,8 @@ def endian(self) -> QL_ENDIAN: return self._init_endian - def get_pc(self) -> int: + @property + def effective_pc(self) -> int: """Get effective PC value, taking Thumb mode into account. """ From 916357150682ac1e51ca756a526759b0b8f1906b Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 21 Jan 2022 13:08:02 +0200 Subject: [PATCH 048/406] Adjust all get_pc usages --- examples/mcu/stm32f407_hack_lock.py | 2 +- qiling/arch/cortex_m.py | 9 ++++----- qiling/debugger/gdb/gdb.py | 2 +- qiling/os/linux/thread.py | 2 +- tests/test_mcu.py | 2 +- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/examples/mcu/stm32f407_hack_lock.py b/examples/mcu/stm32f407_hack_lock.py index 64bde4738..17b4addbf 100644 --- a/examples/mcu/stm32f407_hack_lock.py +++ b/examples/mcu/stm32f407_hack_lock.py @@ -48,7 +48,7 @@ def crack(passwd): ql.hw.systick.set_ratio(100) ql.run(count=1000000, end=0x8003225) - if ql.arch.get_pc() == 0x8003225: + if ql.arch.effective_pc == 0x8003225: print('Success, the passwd is', passwd) else: print('Fail, the passwd is not', passwd) diff --git a/qiling/arch/cortex_m.py b/qiling/arch/cortex_m.py index 6695be598..d0cc45985 100644 --- a/qiling/arch/cortex_m.py +++ b/qiling/arch/cortex_m.py @@ -32,8 +32,7 @@ def __enter__(self): self.ql.log.info(f'Enter into interrupt') def __exit__(self, *exc): - retval = self.ql.arch.get_pc() - + retval = self.ql.arch.effective_pc if retval & EXC_RETURN.MASK != EXC_RETURN.MASK: self.ql.log.warning('Interrupt Crash') self.ql.stop() @@ -95,7 +94,7 @@ def endian(self) -> QL_ENDIAN: return QL_ENDIAN.EL def step(self): - self.ql.emu_start(self.get_pc(), 0, count=1) + self.ql.emu_start(self.effective_pc, 0, count=1) self.ql.hw.step() def stop(self): @@ -109,7 +108,7 @@ def run(self, count=-1, end=None): end |= 1 while self.runable and count != 0: - if self.get_pc() == end: + if self.effective_pc == end: break self.step() @@ -182,4 +181,4 @@ def hard_interrupt_handler(self, ql, intno): self.regs.write('pc', entry) self.regs.write('lr', exc_return) - self.ql.emu_start(self.get_pc(), 0, count=0xffffff) + self.ql.emu_start(self.effective_pc, 0, count=0xffffff) diff --git a/qiling/debugger/gdb/gdb.py b/qiling/debugger/gdb/gdb.py index 296f07da4..b4cb437f9 100644 --- a/qiling/debugger/gdb/gdb.py +++ b/qiling/debugger/gdb/gdb.py @@ -235,7 +235,7 @@ def handle_g(subcmd): # r0-r12,sp,lr,pc,cpsr ,see https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=gdb/arch/arm.h;h=fa589fd0582c0add627a068e6f4947a909c45e86;hb=HEAD#l127 for reg in self.tables[QL_ARCH.ARM][:16] + [self.tables[QL_ARCH.ARM][25]]: # if reg is pc, make sure to take thumb mode into account - r = self.ql.arch.get_pc() if reg == "pc" else self.ql.arch.regs.read(reg) + r = self.ql.arch.effective_pc if reg == "pc" else self.ql.arch.regs.read(reg) tmp = self.addr_to_str(r) s += tmp diff --git a/qiling/os/linux/thread.py b/qiling/os/linux/thread.py index b994111e2..7e27052a1 100644 --- a/qiling/os/linux/thread.py +++ b/qiling/os/linux/thread.py @@ -239,7 +239,7 @@ def _run(self): self.ql.log.warning(f"Nothing to do but still get scheduled!") # Run and log the run event - start_address = self.ql.arch.get_pc() # For arm thumb. + start_address = getattr(self.ql.arch, 'effective_pc', self.ql.arch.regs.arch_pc) # For arm thumb. self.sched_cb = QlLinuxThread._default_sched_cb self.ql.log.debug(f"Scheduled from {hex(start_address)}.") diff --git a/tests/test_mcu.py b/tests/test_mcu.py index 1b07c5ace..76f46e849 100644 --- a/tests/test_mcu.py +++ b/tests/test_mcu.py @@ -213,7 +213,7 @@ def crack(passwd): ql.run(count=400000, end=0x8003225) - return ql.arch.get_pc() == 0x8003225 + return ql.arch.effective_pc == 0x8003225 self.assertTrue(crack('618618')) self.assertTrue(crack('778899')) From aacfcec5f39702f08a220bd96ef67b1abaa8006d Mon Sep 17 00:00:00 2001 From: elicn Date: Sat, 22 Jan 2022 20:23:29 +0200 Subject: [PATCH 049/406] Remove platform_os and platform_arch from core and move it to QlHost --- qiling/core.py | 38 +++++++------------------------------- qiling/host.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 31 deletions(-) create mode 100644 qiling/host.py diff --git a/qiling/core.py b/qiling/core.py index 1f8b69ae3..ed9567a85 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -4,7 +4,7 @@ # from configparser import ConfigParser -import ntpath, os, pickle, platform +import ntpath, os, pickle # See https://stackoverflow.com/questions/39740632/python-type-hinting-without-cyclic-imports from typing import Callable, Dict, List, Union @@ -22,6 +22,7 @@ from .const import QL_ARCH_ENDIAN, QL_ENDIAN, QL_VERBOSE, QL_OS_INTERPRETER, QL_OS_BAREMETAL from .exception import QlErrorFileNotFound, QlErrorArch, QlErrorOsType +from .host import QlHost from .utils import * from .core_struct import QlCoreStructs from .core_hooks import QlCoreHooks @@ -74,8 +75,6 @@ def __init__( self._log_override = log_override self._log_plain = log_plain self._filter = filter - self._platform_os = ostype_convert(platform.system().lower()) - self._platform_arch = arch_convert(platform.machine().lower()) self._internal_exception = None self._uc = None self._stop_options = QlStopOptions(stackpointer=stop_on_stackpointer, exit_trap=stop_on_exit_trap) @@ -126,6 +125,8 @@ def __init__( ################# # arch os setup # ################# + self._host = QlHost() + if type(self._archtype) is str: self._archtype = arch_convert(self._archtype) if type(self._ostype) is str: @@ -422,36 +423,11 @@ def baremetal(self) -> bool: return self.ostype in QL_OS_BAREMETAL @property - def platform_os(self): - """ Specify current platform os where Qiling runs on. - - Type: int - Values: All possible values from platform.system() + def host(self) -> QlHost: + """Provide an interface to the hosting platform where Qiling runs on. """ - return self._platform_os - @platform_os.setter - def platform_os(self, value): - if type(value) is str: - self._platform_os = ostype_convert(value.lower()) - else: - self._platform_os = value - - @property - def platform_arch(self): - """ Specify current platform arch where Qiling runs on. - - Type: int - Values: All possible values from platform.system() - """ - return self._platform_arch - - @platform_arch.setter - def platform_arch(self, value): - if type(value) is str: - self._platform_arch = arch_convert(value.lower()) - else: - self._platform_arch = value + return self._host @property def internal_exception(self) -> Exception: diff --git a/qiling/host.py b/qiling/host.py new file mode 100644 index 000000000..5acf50634 --- /dev/null +++ b/qiling/host.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from functools import cached_property +from typing import Optional +import platform + +from qiling import utils +from qiling.const import QL_OS, QL_ARCH + +class QlHost: + """Interface to the hosting platform. + """ + + @cached_property + def os(self) -> Optional[QL_OS]: + """Hosting platform OS type. + """ + + system = platform.system() + + return utils.ostype_convert(system.lower()) + + @cached_property + def arch(self) -> Optional[QL_ARCH]: + """Hosting platform architecture type. + """ + + machine = platform.machine() + + return utils.arch_convert(machine.lower()) From f6453a2227789e9c1d7f177d488a1e4ef99afe3d Mon Sep 17 00:00:00 2001 From: elicn Date: Sat, 22 Jan 2022 20:25:56 +0200 Subject: [PATCH 050/406] Adjust all platform_os usages --- qiling/os/path.py | 2 +- qiling/os/posix/const_mapping.py | 10 +++++----- qiling/os/posix/syscall/sched.py | 2 +- qiling/os/posix/syscall/unistd.py | 2 +- tests/test_elf.py | 30 +++++++++++++++--------------- tests/test_pathutils.py | 2 +- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/qiling/os/path.py b/qiling/os/path.py index 9872e4f30..f354033c7 100644 --- a/qiling/os/path.py +++ b/qiling/os/path.py @@ -107,7 +107,7 @@ def convert_for_native_os(rootfs: Union[str, Path], cwd: str, path: str) -> Path def convert_path(self, rootfs: Union[str, Path], cwd: str, path: str) -> Path: emulated_os = self.ql.ostype - hosting_os = self.ql.platform_os + hosting_os = self.ql.host.os # emulated os and hosting platform are of the same type if (emulated_os == hosting_os) or (emulated_os in QL_OS_POSIX and hosting_os in QL_OS_POSIX): diff --git a/qiling/os/posix/const_mapping.py b/qiling/os/posix/const_mapping.py index b14a27150..1fd2527c5 100644 --- a/qiling/os/posix/const_mapping.py +++ b/qiling/os/posix/const_mapping.py @@ -46,7 +46,7 @@ def flag_mapping(flags, mapping_name, mapping_from, mapping_to): f = {} t = {} - if ql.platform_os == None: + if ql.host.os == None: return flags if ql.ostype == QL_OS.LINUX: @@ -69,13 +69,13 @@ def flag_mapping(flags, mapping_name, mapping_from, mapping_to): elif ql.ostype == QL_OS.QNX: f = qnx_arm64_open_flags - if ql.platform_os == QL_OS.LINUX: + if ql.host.os == QL_OS.LINUX: t = linux_x86_open_flags - elif ql.platform_os == QL_OS.MACOS: + elif ql.host.os == QL_OS.MACOS: t = macos_x86_open_flags - elif ql.platform_os == QL_OS.FREEBSD: + elif ql.host.os == QL_OS.FREEBSD: t = freebsd_x86_open_flags - elif ql.platform_os == QL_OS.WINDOWS: + elif ql.host.os == QL_OS.WINDOWS: t = windows_x86_open_flags if f == t: diff --git a/qiling/os/posix/syscall/sched.py b/qiling/os/posix/syscall/sched.py index bad39b7b2..95b9b9f58 100644 --- a/qiling/os/posix/syscall/sched.py +++ b/qiling/os/posix/syscall/sched.py @@ -49,7 +49,7 @@ def ql_syscall_clone(ql: Qiling, flags: int, child_stack: int, parent_tidptr: in # Shared virtual memory if not (flags & CLONE_VM): # FIXME: need a proper os.fork() for Windows - if ql.platform_os == QL_OS.WINDOWS: + if ql.host.os == QL_OS.WINDOWS: try: pid = Process() pid = 0 diff --git a/qiling/os/posix/syscall/unistd.py b/qiling/os/posix/syscall/unistd.py index 377990120..cbbc9a855 100644 --- a/qiling/os/posix/syscall/unistd.py +++ b/qiling/os/posix/syscall/unistd.py @@ -365,7 +365,7 @@ def ql_syscall_getppid(ql: Qiling): def ql_syscall_vfork(ql: Qiling): - if ql.platform_os == QL_OS.WINDOWS: + if ql.host.os == QL_OS.WINDOWS: try: pid = Process() pid = 0 diff --git a/tests/test_elf.py b/tests/test_elf.py index 1373f250e..811fbb6d6 100644 --- a/tests/test_elf.py +++ b/tests/test_elf.py @@ -233,7 +233,7 @@ def test_syscall_read(ql, read_fd, read_buf, read_count, *args): real_path = ql.os.fd[read_fd].name with open(real_path) as fd: assert fd.read() == ql.mem.read(read_buf, read_count).decode() - if ql.platform_os != QL_OS.WINDOWS: + if ql.host.os != QL_OS.WINDOWS: os.remove(real_path) return regreturn @@ -252,7 +252,7 @@ def test_syscall_write(ql, write_fd, write_buf, write_count, *args): real_path = ql.os.fd[write_fd].name with open(real_path) as fd: assert fd.read() == 'Hello testing\x00' - if ql.platform_os != QL_OS.WINDOWS: + if ql.host.os != QL_OS.WINDOWS: os.remove(real_path) return regreturn @@ -270,7 +270,7 @@ def test_syscall_openat(ql, openat_fd, openat_path, openat_flags, openat_mode, * if target: real_path = ql.os.path.transform_to_real_path(pathname) assert os.path.isfile(real_path) == True - if ql.platform_os != QL_OS.WINDOWS: + if ql.host.os != QL_OS.WINDOWS: os.remove(real_path) return regreturn @@ -304,7 +304,7 @@ def test_syscall_truncate(ql, trunc_pathname, trunc_length, *args): if target: real_path = ql.os.path.transform_to_real_path(pathname) assert os.stat(real_path).st_size == 0 - if ql.platform_os != QL_OS.WINDOWS: + if ql.host.os != QL_OS.WINDOWS: os.remove(real_path) return regreturn @@ -326,7 +326,7 @@ def test_syscall_ftruncate(ql, ftrunc_fd, ftrunc_length, *args): if target: real_path = ql.os.path.transform_to_real_path(pathname) assert os.stat(real_path).st_size == 0x10 - if ql.platform_os != QL_OS.WINDOWS: + if ql.host.os != QL_OS.WINDOWS: os.remove(real_path) return regreturn @@ -534,7 +534,7 @@ def test_syscall_read(ql, read_fd, read_buf, read_count, *args): real_path = ql.os.fd[read_fd].name with open(real_path) as fd: assert fd.read() == ql.mem.read(read_buf, read_count).decode() - if ql.platform_os != QL_OS.WINDOWS: + if ql.host.os != QL_OS.WINDOWS: os.remove(real_path) return regreturn @@ -554,7 +554,7 @@ def test_syscall_write(ql, write_fd, write_buf, write_count, *args): real_path = ql.os.fd[write_fd].name with open(real_path) as fd: assert fd.read() == 'Hello testing\x00' - if ql.platform_os != QL_OS.WINDOWS: + if ql.host.os != QL_OS.WINDOWS: os.remove(real_path) return regreturn @@ -573,7 +573,7 @@ def test_syscall_openat(ql, openat_fd, openat_path, openat_flags, openat_mode, * if target: real_path = ql.os.path.transform_to_real_path(pathname) assert os.path.isfile(real_path) == True - if ql.platform_os != QL_OS.WINDOWS: + if ql.host.os != QL_OS.WINDOWS: os.remove(real_path) return regreturn @@ -609,7 +609,7 @@ def test_syscall_truncate(ql, trunc_pathname, trunc_length, *args): if target: real_path = ql.os.path.transform_to_real_path(pathname) assert os.stat(real_path).st_size == 0 - if ql.platform_os != QL_OS.WINDOWS: + if ql.host.os != QL_OS.WINDOWS: os.remove(real_path) return regreturn @@ -628,7 +628,7 @@ def test_syscall_ftruncate(ql, ftrunc_fd, ftrunc_length, *args): if target: real_path = ql.os.path.transform_to_real_path(pathname) assert os.stat(real_path).st_size == 0x10 - if ql.platform_os != QL_OS.WINDOWS: + if ql.host.os != QL_OS.WINDOWS: os.remove(real_path) return regreturn @@ -681,7 +681,7 @@ def test_syscall_read(ql, read_fd, read_buf, read_count, *args): real_path = ql.os.fd[read_fd].name with open(real_path) as fd: assert fd.read() == ql.mem.read(read_buf, read_count).decode() - if ql.platform_os != QL_OS.WINDOWS: + if ql.host.os != QL_OS.WINDOWS: os.remove(real_path) return regreturn @@ -701,7 +701,7 @@ def test_syscall_write(ql, write_fd, write_buf, write_count, *args): real_path = ql.os.fd[write_fd].name with open(real_path) as fd: assert fd.read() == 'Hello testing\x00' - if ql.platform_os != QL_OS.WINDOWS: + if ql.host.os != QL_OS.WINDOWS: os.remove(real_path) return regreturn @@ -719,7 +719,7 @@ def test_syscall_open(ql, open_pathname, open_flags, open_mode, *args): if target: real_path = ql.os.path.transform_to_real_path(pathname) assert os.path.isfile(real_path) == True - if ql.platform_os != QL_OS.WINDOWS: + if ql.host.os != QL_OS.WINDOWS: os.remove(real_path) return regreturn @@ -753,7 +753,7 @@ def test_syscall_truncate(ql, trunc_pathname, trunc_length, *args): if target: real_path = ql.os.path.transform_to_real_path(pathname) assert os.stat(real_path).st_size == 0 - if ql.platform_os != QL_OS.WINDOWS: + if ql.host.os != QL_OS.WINDOWS: os.remove(real_path) return regreturn @@ -771,7 +771,7 @@ def test_syscall_ftruncate(ql, ftrunc_fd, ftrunc_length, *args): if target: real_path = ql.os.path.transform_to_real_path(pathname) assert os.stat(real_path).st_size == 0x10 - if ql.platform_os != QL_OS.WINDOWS: + if ql.host.os != QL_OS.WINDOWS: os.remove(real_path) return regreturn diff --git a/tests/test_pathutils.py b/tests/test_pathutils.py index 396206fc6..3954203c7 100644 --- a/tests/test_pathutils.py +++ b/tests/test_pathutils.py @@ -70,7 +70,7 @@ def test_convert_posix_to_win32(self): def test_convert_for_native_os(self): ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_hello_static"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG) - if ql.platform_os == QL_OS.WINDOWS: + if ql.host.os == QL_OS.WINDOWS: rootfs = pathlib.Path("../examples/rootfs/x8664_windows").resolve() self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "\\\\.\\PhysicalDrive0\\test"))) self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "\\\\.\\PhysicalDrive0\\..\\test"))) From 5461dc764983b3e777a27aa5885643cb135d37bf Mon Sep 17 00:00:00 2001 From: elicn Date: Sat, 22 Jan 2022 21:34:21 +0200 Subject: [PATCH 051/406] Removed set_syscall forwarding from core --- qiling/core.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/qiling/core.py b/qiling/core.py index ed9567a85..43d08d329 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -720,17 +720,6 @@ def restore(self, saved_states=None, snapshot=None): self.loader.restore(saved_states["loader"]) - # Either hook or replace syscall/api with custom api/syscall - # - if intercept is None, replace syscall with custom function - # - if intercept is ENTER/EXIT, hook syscall at enter/exit with custom function - # If replace function name is needed, first syscall must be available - # - ql.set_syscall(0x04, my_syscall_write) - # - ql.set_syscall("write", my_syscall_write) - # TODO: Add correspoinding API in ql.os! - def set_syscall(self, target_syscall, intercept_function, intercept = None): - self.os.set_syscall(target_syscall, intercept_function, intercept) - - # Either replace or hook API # - if intercept is None, replace API with custom function # - if intercept is ENTER/EXIT, hook API at enter/exit with custom function From 26c0aa157bbc6b39f474430f6d44360ca1298ccd Mon Sep 17 00:00:00 2001 From: elicn Date: Sat, 22 Jan 2022 21:36:39 +0200 Subject: [PATCH 052/406] Adjust all set_syscall usages --- examples/hello_arm_linux_custom_syscall.py | 4 +- examples/hello_linuxx8664_intercept.py | 4 +- examples/netgear_6220_mips32el_linux.py | 2 +- examples/tendaac1518_httpd.py | 2 +- tests/test_elf.py | 68 +++++++++++----------- tests/test_elf_multithread.py | 24 ++++---- tests/test_riscv.py | 4 +- 7 files changed, 54 insertions(+), 54 deletions(-) diff --git a/examples/hello_arm_linux_custom_syscall.py b/examples/hello_arm_linux_custom_syscall.py index 83855b068..ec21c6876 100644 --- a/examples/hello_arm_linux_custom_syscall.py +++ b/examples/hello_arm_linux_custom_syscall.py @@ -30,9 +30,9 @@ def my_syscall_write(ql: Qiling, write_fd, write_buf, write_count, *args, **kw): # Known issue: If the syscall func is not be implemented in qiling, qiling does # not know which func should be replaced. # In that case, you must specify syscall by its number. - ql.set_syscall(0x04, my_syscall_write) + ql.os.set_syscall(0x04, my_syscall_write) # set syscall by syscall name - #ql.set_syscall("write", my_syscall_write) + #ql.os.set_syscall("write", my_syscall_write) ql.run() diff --git a/examples/hello_linuxx8664_intercept.py b/examples/hello_linuxx8664_intercept.py index 0af739e31..532970a47 100644 --- a/examples/hello_linuxx8664_intercept.py +++ b/examples/hello_linuxx8664_intercept.py @@ -22,6 +22,6 @@ def write_onexit(ql: Qiling, arg1, arg2, arg3, *args): if __name__ == "__main__": ql = Qiling(["rootfs/x8664_linux/bin/x8664_hello"], "rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG) - ql.set_syscall(SYSCALL_NR.write, write_onenter, QL_INTERCEPT.ENTER) - ql.set_syscall(SYSCALL_NR.write, write_onexit, QL_INTERCEPT.EXIT) + ql.os.set_syscall(SYSCALL_NR.write, write_onenter, QL_INTERCEPT.ENTER) + ql.os.set_syscall(SYSCALL_NR.write, write_onexit, QL_INTERCEPT.EXIT) ql.run() diff --git a/examples/netgear_6220_mips32el_linux.py b/examples/netgear_6220_mips32el_linux.py index 323094bf6..9e3e92c6b 100644 --- a/examples/netgear_6220_mips32el_linux.py +++ b/examples/netgear_6220_mips32el_linux.py @@ -61,7 +61,7 @@ def my_netgear(path, rootfs): ql.os.root = False ql.add_fs_mapper('/proc', '/proc') - ql.set_syscall(4004, my_syscall_write) + ql.os.set_syscall(4004, my_syscall_write) ql.set_api('bind', my_bind, QL_INTERCEPT.ENTER) # intercepting the bind call on enter ql.run() diff --git a/examples/tendaac1518_httpd.py b/examples/tendaac1518_httpd.py index 0090b9449..819e8310c 100644 --- a/examples/tendaac1518_httpd.py +++ b/examples/tendaac1518_httpd.py @@ -75,7 +75,7 @@ def my_sandbox(path, rootfs): ql.debugger = False if ql.debugger == True: - ql.set_syscall("vfork", myvfork) + ql.os.set_syscall("vfork", myvfork) ql.run() diff --git a/tests/test_elf.py b/tests/test_elf.py index 811fbb6d6..5e7fe7df6 100644 --- a/tests/test_elf.py +++ b/tests/test_elf.py @@ -127,9 +127,9 @@ def write_onexit(ql: Qiling, fd: int, str_ptr: int, str_len: int, retval: int, * return str_len + 1 ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_args","1234test", "12345678", "bin/x8664_hello"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG) - ql.set_syscall(1, write_onEnter, QL_INTERCEPT.ENTER) + ql.os.set_syscall(1, write_onEnter, QL_INTERCEPT.ENTER) ql.set_api('puts', my_puts) - ql.set_syscall(1, write_onexit, QL_INTERCEPT.EXIT) + ql.os.set_syscall(1, write_onexit, QL_INTERCEPT.EXIT) ql.mem.map(0x1000, 0x1000) ql.mem.write(0x1000, b"\xFF\xFE\xFD\xFC\xFB\xFA\xFB\xFC\xFC\xFE\xFD") ql.mem.map(0x2000, 0x1000) @@ -332,12 +332,12 @@ def test_syscall_ftruncate(ql, ftrunc_fd, ftrunc_length, *args): return regreturn ql = Qiling(["../examples/rootfs/x86_linux/bin/x86_posix_syscall"], "../examples/rootfs/x86_linux", verbose=QL_VERBOSE.DEBUG) - ql.set_syscall(0x3, test_syscall_read) - ql.set_syscall(0x4, test_syscall_write) - ql.set_syscall(0x127, test_syscall_openat) - ql.set_syscall(0xa, test_syscall_unlink) - ql.set_syscall(0x5c, test_syscall_truncate) - ql.set_syscall(0x5d, test_syscall_ftruncate) + ql.os.set_syscall(0x3, test_syscall_read) + ql.os.set_syscall(0x4, test_syscall_write) + ql.os.set_syscall(0x127, test_syscall_openat) + ql.os.set_syscall(0xa, test_syscall_unlink) + ql.os.set_syscall(0x5c, test_syscall_truncate) + ql.os.set_syscall(0x5d, test_syscall_ftruncate) ql.run() del ql @@ -458,12 +458,12 @@ def test_elf_linux_arm_static(self): # os.remove(real_path) # ql = Qiling(["../examples/rootfs/arm_linux/bin/arm_posix_syscall"], "../examples/rootfs/arm_linux", verbose=QL_VERBOSE.DEBUG) - # ql.set_syscall(0x3, test_syscall_read) - # ql.set_syscall(0x4, test_syscall_write) - # ql.set_syscall(0x5, test_syscall_open) - # ql.set_syscall(0xa, test_syscall_unlink) - # ql.set_syscall(0x5c, test_syscall_truncate) - # ql.set_syscall(0x5d, test_syscall_ftruncate) + # ql.os.set_syscall(0x3, test_syscall_read) + # ql.os.set_syscall(0x4, test_syscall_write) + # ql.os.set_syscall(0x5, test_syscall_open) + # ql.os.set_syscall(0xa, test_syscall_unlink) + # ql.os.set_syscall(0x5c, test_syscall_truncate) + # ql.os.set_syscall(0x5d, test_syscall_ftruncate) # ql.run() # del ql @@ -634,12 +634,12 @@ def test_syscall_ftruncate(ql, ftrunc_fd, ftrunc_length, *args): return regreturn ql = Qiling(["../examples/rootfs/arm64_linux/bin/arm64_posix_syscall"], "../examples/rootfs/arm64_linux", verbose=QL_VERBOSE.DEBUG) - ql.set_syscall(0x3f, test_syscall_read) - ql.set_syscall(0x40, test_syscall_write) - ql.set_syscall(0x38, test_syscall_openat) - ql.set_syscall(0x402, test_syscall_unlink) - ql.set_syscall(0x2d, test_syscall_truncate) - ql.set_syscall(0x2e, test_syscall_ftruncate) + ql.os.set_syscall(0x3f, test_syscall_read) + ql.os.set_syscall(0x40, test_syscall_write) + ql.os.set_syscall(0x38, test_syscall_openat) + ql.os.set_syscall(0x402, test_syscall_unlink) + ql.os.set_syscall(0x2d, test_syscall_truncate) + ql.os.set_syscall(0x2e, test_syscall_ftruncate) ql.run() del ql @@ -777,12 +777,12 @@ def test_syscall_ftruncate(ql, ftrunc_fd, ftrunc_length, *args): return regreturn ql = Qiling(["../examples/rootfs/mips32el_linux/bin/mips32el_posix_syscall"], "../examples/rootfs/mips32el_linux", verbose=QL_VERBOSE.DEBUG) - ql.set_syscall(4003, test_syscall_read) - ql.set_syscall(4004, test_syscall_write) - ql.set_syscall(4005, test_syscall_open) - ql.set_syscall(4010, test_syscall_unlink) - ql.set_syscall(4092, test_syscall_truncate) - ql.set_syscall(4093, test_syscall_ftruncate) + ql.os.set_syscall(4003, test_syscall_read) + ql.os.set_syscall(4004, test_syscall_write) + ql.os.set_syscall(4005, test_syscall_open) + ql.os.set_syscall(4010, test_syscall_unlink) + ql.os.set_syscall(4092, test_syscall_truncate) + ql.os.set_syscall(4093, test_syscall_ftruncate) ql.run() del ql @@ -813,7 +813,7 @@ def my_syscall_write(ql, write_fd, write_buf, write_count, *args, **kw): return regreturn ql = Qiling(["../examples/rootfs/arm_linux/bin/arm_hello"], "../examples/rootfs/arm_linux") - ql.set_syscall(0x04, my_syscall_write) + ql.os.set_syscall(0x04, my_syscall_write) ql.run() self.assertEqual(1, self.set_syscall) @@ -834,7 +834,7 @@ def run_one_round(payload): ins_count = [0] ql.hook_code(instruction_count, ins_count) - ql.set_syscall("_llseek", my__llseek) + ql.os.set_syscall("_llseek", my__llseek) ql.os.stdin = pipe.SimpleInStream(sys.stdin.fileno()) ql.os.stdin.write(payload) @@ -900,8 +900,8 @@ def check_exit_group_code(ql, exit_code, *args, **kw): def check_exit_code(ql, exit_code, *args, **kw): ql.exit_code = exit_code - ql.set_syscall("exit_group", check_exit_group_code, QL_INTERCEPT.ENTER) - ql.set_syscall("exit", check_exit_code, QL_INTERCEPT.ENTER) + ql.os.set_syscall("exit_group", check_exit_group_code, QL_INTERCEPT.ENTER) + ql.os.set_syscall("exit", check_exit_code, QL_INTERCEPT.ENTER) ql.run() self.assertEqual(0, ql.exit_code) @@ -937,8 +937,8 @@ def check_exit_group_code(ql, exit_code, *args, **kw): def check_exit_code(ql, exit_code, *args, **kw): ql.exit_code = exit_code - ql.set_syscall("exit_group", check_exit_group_code, QL_INTERCEPT.ENTER) - ql.set_syscall("exit", check_exit_code, QL_INTERCEPT.ENTER) + ql.os.set_syscall("exit_group", check_exit_group_code, QL_INTERCEPT.ENTER) + ql.os.set_syscall("exit", check_exit_code, QL_INTERCEPT.ENTER) ql.run() self.assertEqual(0, ql.exit_code) @@ -959,8 +959,8 @@ def check_exit_group_code(ql, exit_code, *args, **kw): def check_exit_code(ql, exit_code, *args, **kw): ql.exit_code = exit_code - ql.set_syscall("exit_group", check_exit_group_code, QL_INTERCEPT.ENTER) - ql.set_syscall("exit", check_exit_code, QL_INTERCEPT.ENTER) + ql.os.set_syscall("exit_group", check_exit_group_code, QL_INTERCEPT.ENTER) + ql.os.set_syscall("exit", check_exit_code, QL_INTERCEPT.ENTER) ql.run() diff --git a/tests/test_elf_multithread.py b/tests/test_elf_multithread.py index a04edeefc..42f38381e 100644 --- a/tests/test_elf_multithread.py +++ b/tests/test_elf_multithread.py @@ -65,7 +65,7 @@ def check_write(ql, write_fd, write_buf, write_count, *args, **kw): pass buf_out = None ql = Qiling(["../examples/rootfs/x86_linux/bin/x86_multithreading"], "../examples/rootfs/x86_linux", multithread=True, verbose=QL_VERBOSE.DEBUG) - ql.set_syscall("write", check_write, QL_INTERCEPT.ENTER) + ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER) ql.run() self.assertTrue("thread 2 ret val is" in buf_out) @@ -84,7 +84,7 @@ def check_write(ql, write_fd, write_buf, write_count, *args, **kw): pass buf_out = None ql = Qiling(["../examples/rootfs/arm64_linux/bin/arm64_multithreading"], "../examples/rootfs/arm64_linux", multithread=True, verbose=QL_VERBOSE.DEBUG) - ql.set_syscall("write", check_write, QL_INTERCEPT.ENTER) + ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER) ql.run() self.assertTrue("thread 2 ret val is" in buf_out) @@ -103,7 +103,7 @@ def check_write(ql, write_fd, write_buf, write_count, *args, **kw): pass buf_out = None ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_multithreading"], "../examples/rootfs/x8664_linux", multithread=True, profile= "profiles/append_test.ql") - ql.set_syscall("write", check_write, QL_INTERCEPT.ENTER) + ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER) ql.run() self.assertTrue("thread 2 ret val is" in buf_out) @@ -122,7 +122,7 @@ def check_write(ql, write_fd, write_buf, write_count, *args, **kw): pass buf_out = None ql = Qiling(["../examples/rootfs/mips32el_linux/bin/mips32el_multithreading"], "../examples/rootfs/mips32el_linux", multithread=True, verbose=QL_VERBOSE.DEBUG) - ql.set_syscall("write", check_write, QL_INTERCEPT.ENTER) + ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER) ql.run() self.assertTrue("thread 2 ret val is" in buf_out) @@ -141,7 +141,7 @@ def check_write(ql, write_fd, write_buf, write_count, *args, **kw): pass buf_out = None ql = Qiling(["../examples/rootfs/arm_linux/bin/arm_multithreading"], "../examples/rootfs/arm_linux", multithread=True, verbose=QL_VERBOSE.DEBUG) - ql.set_syscall("write", check_write, QL_INTERCEPT.ENTER) + ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER) ql.run() self.assertTrue("thread 2 ret val is" in buf_out) @@ -159,7 +159,7 @@ def check_write(ql, write_fd, write_buf, write_count, *args, **kw): except: pass ql = Qiling(["../examples/rootfs/x86_linux/bin/x86_tcp_test","20001"], "../examples/rootfs/x86_linux", multithread=True) - ql.set_syscall("write", check_write, QL_INTERCEPT.ENTER) + ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER) ql.run() self.assertEqual("server send() 14 return 14.\n", ql.buf_out) @@ -177,7 +177,7 @@ def check_write(ql, write_fd, write_buf, write_count, *args, **kw): except: pass ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_tcp_test","20002"], "../examples/rootfs/x8664_linux", multithread=True) - ql.set_syscall("write", check_write, QL_INTERCEPT.ENTER) + ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER) ql.run() self.assertEqual("server send() 14 return 14.\n", ql.buf_out) @@ -195,7 +195,7 @@ def check_write(ql, write_fd, write_buf, write_count, *args, **kw): except: pass ql = Qiling(["../examples/rootfs/arm_linux/bin/arm_tcp_test","20003"], "../examples/rootfs/arm_linux", multithread=True) - ql.set_syscall("write", check_write, QL_INTERCEPT.ENTER) + ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER) ql.run() self.assertEqual("server write() 14 return 14.\n", ql.buf_out) @@ -213,7 +213,7 @@ def check_write(ql, write_fd, write_buf, write_count, *args, **kw): except: pass ql = Qiling(["../examples/rootfs/arm64_linux/bin/arm64_tcp_test","20004"], "../examples/rootfs/arm64_linux", multithread=True) - ql.set_syscall("write", check_write, QL_INTERCEPT.ENTER) + ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER) ql.run() self.assertEqual("server send() 14 return 14.\n", ql.buf_out) @@ -238,7 +238,7 @@ def check_write(ql, write_fd, write_buf, write_count, *args, **kw): pass ql = Qiling(["../examples/rootfs/x86_linux/bin/x86_udp_test","20007"], "../examples/rootfs/x86_linux", multithread=True) - ql.set_syscall("write", check_write, QL_INTERCEPT.ENTER) + ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER) ql.run() self.assertEqual("server sendto() 14 return 14.\n", ql.buf_out) @@ -257,7 +257,7 @@ def check_write(ql, write_fd, write_buf, write_count, *args, **kw): pass ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_udp_test","20008"], "../examples/rootfs/x8664_linux", multithread=True) - ql.set_syscall("write", check_write, QL_INTERCEPT.ENTER) + ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER) ql.run() self.assertEqual("server sendto() 14 return 14.\n", ql.buf_out) @@ -275,7 +275,7 @@ def check_write(ql, write_fd, write_buf, write_count, *args, **kw): pass ql = Qiling(["../examples/rootfs/arm64_linux/bin/arm64_udp_test","20009"], "../examples/rootfs/arm64_linux", multithread=True) - ql.set_syscall("write", check_write, QL_INTERCEPT.ENTER) + ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER) ql.run() self.assertEqual("server sendto() 14 return 14.\n", ql.buf_out) diff --git a/tests/test_riscv.py b/tests/test_riscv.py index a51a68bcf..f1013fbed 100644 --- a/tests/test_riscv.py +++ b/tests/test_riscv.py @@ -18,7 +18,7 @@ def test_riscv32_hello_linux(self): def close(ql, fd): return 0 - ql.set_syscall("close", close, QL_INTERCEPT.CALL) + ql.os.set_syscall("close", close, QL_INTERCEPT.CALL) ql.os.stdout = stdout ql.run() self.assertTrue(stdout.read() == b'Hello, World!\n') @@ -32,7 +32,7 @@ def test_riscv64_hello_linux(self): def close(ql, fd): return 0 - ql.set_syscall("close", close, QL_INTERCEPT.CALL) + ql.os.set_syscall("close", close, QL_INTERCEPT.CALL) ql.os.stdout = stdout ql.run() self.assertTrue(stdout.read() == b'Hello, World!\n') From 468fa8abea1a776e09d9a75654b66885abf5df84 Mon Sep 17 00:00:00 2001 From: elicn Date: Sat, 22 Jan 2022 21:37:37 +0200 Subject: [PATCH 053/406] Properly document POSIX set_syscall --- qiling/os/posix/posix.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/qiling/os/posix/posix.py b/qiling/os/posix/posix.py index 9172d5402..396c3e754 100644 --- a/qiling/os/posix/posix.py +++ b/qiling/os/posix/posix.py @@ -145,14 +145,21 @@ def root(self, enabled: bool) -> None: def syscall(self): return self.get_syscall() - def set_syscall(self, target: Union[int, str], handler: Callable, intercept: QL_INTERCEPT): + 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. + + Args: + target: either syscall name or number. a name may be used only if target syscall is implemented + handler: function to call + intercept: + `QL_INTERCEPT.CALL` : run handler instead of the existing target implementation + `QL_INTERCEPT.ENTER`: run handler before the target syscall is called + `QL_INTERCEPT.EXIT` : run handler after the target syscall is called + """ + if type(target) is str: target = f'{SYSCALL_PREF}{target}' - # BUG: workaround missing arg - if intercept is None: - intercept = QL_INTERCEPT.CALL - self.posix_syscall_hooks[intercept][target] = handler @staticmethod From 9d345cb260907eefb49196d6228b764fe7f386db Mon Sep 17 00:00:00 2001 From: elicn Date: Sat, 22 Jan 2022 21:46:08 +0200 Subject: [PATCH 054/406] Removed set_api forwarding from core --- qiling/core.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/qiling/core.py b/qiling/core.py index 43d08d329..a041ef76b 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -720,13 +720,6 @@ def restore(self, saved_states=None, snapshot=None): self.loader.restore(saved_states["loader"]) - # Either replace or hook API - # - if intercept is None, replace API with custom function - # - if intercept is ENTER/EXIT, hook API at enter/exit with custom function - def set_api(self, api_name, intercept_function, intercept = None): - self.os.set_api(api_name, intercept_function, intercept) - - # Map "ql_path" to any objects which implements QlFsMappedObject. def add_fs_mapper(self, ql_path, real_dest): self.os.fs_mapper.add_fs_mapping(ql_path, real_dest) From 0de44873d9790b3f18d52a85c9fa99b5e6eee481 Mon Sep 17 00:00:00 2001 From: elicn Date: Sat, 22 Jan 2022 21:49:41 +0200 Subject: [PATCH 055/406] Adjust all set_api usages --- examples/doogie_8086_crack.py | 4 ++-- examples/hello_arm_qnx_customapi.py | 6 +++--- examples/hello_mips32el_linux_function_hook.py | 4 ++-- examples/hello_x8664_linux_customapi.py | 2 +- examples/hello_x8664_windows_customapi.py | 6 +++--- examples/netgear_6220_mips32el_linux.py | 2 +- examples/sality.py | 8 ++++---- examples/simple_efi_x8664.py | 4 ++-- tests/test_dos.py | 4 ++-- tests/test_elf.py | 12 ++++++------ tests/test_elf_ko.py | 6 +++--- tests/test_macho_kext.py | 6 +++--- tests/test_pe.py | 10 +++++----- tests/test_pe_sys.py | 8 ++++---- tests/test_qnx.py | 10 +++++----- tests/test_uefi.py | 6 +++--- 16 files changed, 49 insertions(+), 49 deletions(-) diff --git a/examples/doogie_8086_crack.py b/examples/doogie_8086_crack.py index 3e501104f..35dc43b90 100644 --- a/examples/doogie_8086_crack.py +++ b/examples/doogie_8086_crack.py @@ -133,7 +133,7 @@ def third_stage(keys): "rootfs/8086", console=False) ql.add_fs_mapper(0x80, QlDisk("rootfs/8086/doogie/doogie.DOS_MBR", 0x80)) - ql.set_api((0x1a, 4), set_required_datetime, QL_INTERCEPT.EXIT) + ql.os.set_api((0x1a, 4), set_required_datetime, QL_INTERCEPT.EXIT) hk = ql.hook_code(stop, begin=0x8018, end=0x8018) ql.run() ql.hook_del(hk) @@ -187,7 +187,7 @@ def first_stage(): console=False) ql.add_fs_mapper(0x80, QlDisk("rootfs/8086/doogie/doogie.DOS_MBR", 0x80)) # Doogie suggests that the datetime should be 1990-02-06. - ql.set_api((0x1a, 4), set_required_datetime, QL_INTERCEPT.EXIT) + ql.os.set_api((0x1a, 4), set_required_datetime, QL_INTERCEPT.EXIT) # A workaround to stop the program. hk = ql.hook_code(stop, begin=0x8018, end=0x8018) ql.run() diff --git a/examples/hello_arm_qnx_customapi.py b/examples/hello_arm_qnx_customapi.py index 9773450ff..7924afd7c 100644 --- a/examples/hello_arm_qnx_customapi.py +++ b/examples/hello_arm_qnx_customapi.py @@ -28,7 +28,7 @@ def my_puts_onexit(ql: Qiling): if __name__ == "__main__": ql = Qiling(["rootfs/arm_qnx/bin/hello_static"], "rootfs/arm_qnx") - ql.set_api('puts', my_puts_onenter, QL_INTERCEPT.ENTER) - ql.set_api('printf', my_printf_onenter, QL_INTERCEPT.ENTER) - ql.set_api('puts', my_puts_onexit, QL_INTERCEPT.EXIT) + ql.os.set_api('puts', my_puts_onenter, QL_INTERCEPT.ENTER) + ql.os.set_api('printf', my_printf_onenter, QL_INTERCEPT.ENTER) + ql.os.set_api('puts', my_puts_onexit, QL_INTERCEPT.EXIT) ql.run() diff --git a/examples/hello_mips32el_linux_function_hook.py b/examples/hello_mips32el_linux_function_hook.py index 849b55f0a..74d32c49e 100644 --- a/examples/hello_mips32el_linux_function_hook.py +++ b/examples/hello_mips32el_linux_function_hook.py @@ -23,7 +23,7 @@ def my_puts_onexit(ql: Qiling): if __name__ == "__main__": ql = Qiling(["rootfs/mips32el_linux/bin/mips32el_double_hello"], "rootfs/mips32el_linux", verbose=QL_VERBOSE.DEBUG) - ql.set_api('puts', my_puts_onenter, QL_INTERCEPT.ENTER) - ql.set_api('puts', my_puts_onexit, QL_INTERCEPT.EXIT) + ql.os.set_api('puts', my_puts_onenter, QL_INTERCEPT.ENTER) + ql.os.set_api('puts', my_puts_onexit, QL_INTERCEPT.EXIT) ql.run() diff --git a/examples/hello_x8664_linux_customapi.py b/examples/hello_x8664_linux_customapi.py index e624ff337..57892c11e 100644 --- a/examples/hello_x8664_linux_customapi.py +++ b/examples/hello_x8664_linux_customapi.py @@ -16,5 +16,5 @@ def my_puts(ql: Qiling): if __name__ == "__main__": ql = Qiling(["rootfs/x8664_linux/bin/x8664_hello"], "rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG) - ql.set_api('puts', my_puts) + ql.os.set_api('puts', my_puts) ql.run() diff --git a/examples/hello_x8664_windows_customapi.py b/examples/hello_x8664_windows_customapi.py index 3f195aad4..522a8a393 100644 --- a/examples/hello_x8664_windows_customapi.py +++ b/examples/hello_x8664_windows_customapi.py @@ -39,9 +39,9 @@ def my_onexit(ql: Qiling, address: int, params, retval: int): def my_sandbox(path, rootfs): ql = Qiling(path, rootfs, verbose=QL_VERBOSE.DEBUG) - ql.set_api("_cexit", my_onenter, QL_INTERCEPT.ENTER) - ql.set_api("puts", my_puts, QL_INTERCEPT.CALL) - ql.set_api("atexit", my_onexit, QL_INTERCEPT.EXIT) + ql.os.set_api("_cexit", my_onenter, QL_INTERCEPT.ENTER) + ql.os.set_api("puts", my_puts, QL_INTERCEPT.CALL) + ql.os.set_api("atexit", my_onexit, QL_INTERCEPT.EXIT) ql.run() diff --git a/examples/netgear_6220_mips32el_linux.py b/examples/netgear_6220_mips32el_linux.py index 9e3e92c6b..06299773c 100644 --- a/examples/netgear_6220_mips32el_linux.py +++ b/examples/netgear_6220_mips32el_linux.py @@ -62,7 +62,7 @@ def my_netgear(path, rootfs): ql.add_fs_mapper('/proc', '/proc') ql.os.set_syscall(4004, my_syscall_write) - ql.set_api('bind', my_bind, QL_INTERCEPT.ENTER) # intercepting the bind call on enter + ql.os.set_api('bind', my_bind, QL_INTERCEPT.ENTER) # intercepting the bind call on enter ql.run() diff --git a/examples/sality.py b/examples/sality.py index 4a34aa755..87e348162 100644 --- a/examples/sality.py +++ b/examples/sality.py @@ -181,10 +181,10 @@ def hook_stop_address(ql): ql.amsint32_driver = None # emulate some Windows API - ql.set_api("CreateThread", hook_CreateThread) - ql.set_api("CreateFileA", hook_CreateFileA) - ql.set_api("WriteFile", hook_WriteFile) - ql.set_api("StartServiceA", hook_StartServiceA) + ql.os.set_api("CreateThread", hook_CreateThread) + ql.os.set_api("CreateFileA", hook_CreateFileA) + ql.os.set_api("WriteFile", hook_WriteFile) + ql.os.set_api("StartServiceA", hook_StartServiceA) #init sality ql.hook_address(hook_stop_address, 0x40EFFB) ql.run() diff --git a/examples/simple_efi_x8664.py b/examples/simple_efi_x8664.py index 32c21a28d..c6410b0c8 100644 --- a/examples/simple_efi_x8664.py +++ b/examples/simple_efi_x8664.py @@ -40,7 +40,7 @@ def my_onenter(ql: Qiling, address: int, params): ql = Qiling(["rootfs/x8664_efi/bin/TcgPlatformSetupPolicy"], "rootfs/x8664_efi", env=env, verbose=QL_VERBOSE.DEBUG) - ql.set_api("RegisterProtocolNotify", force_notify_RegisterProtocolNotify) - ql.set_api("CopyMem", my_onenter, QL_INTERCEPT.ENTER) + ql.os.set_api("RegisterProtocolNotify", force_notify_RegisterProtocolNotify) + ql.os.set_api("CopyMem", my_onenter, QL_INTERCEPT.ENTER) ql.run() diff --git a/tests/test_dos.py b/tests/test_dos.py index 2c2b0803c..449b19a01 100644 --- a/tests/test_dos.py +++ b/tests/test_dos.py @@ -26,8 +26,8 @@ def onenter(ql: Qiling): def onexit(ql: Qiling): ck.visited_onexit = True - ql.set_api((0x21, 0x09), onexit, QL_INTERCEPT.EXIT) - ql.set_api((0x21, 0x4c), onenter, QL_INTERCEPT.ENTER) + ql.os.set_api((0x21, 0x09), onexit, QL_INTERCEPT.EXIT) + ql.os.set_api((0x21, 0x4c), onenter, QL_INTERCEPT.ENTER) ql.run() diff --git a/tests/test_elf.py b/tests/test_elf.py index 5e7fe7df6..25d3d9e5b 100644 --- a/tests/test_elf.py +++ b/tests/test_elf.py @@ -128,7 +128,7 @@ def write_onexit(ql: Qiling, fd: int, str_ptr: int, str_len: int, retval: int, * ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_args","1234test", "12345678", "bin/x8664_hello"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG) ql.os.set_syscall(1, write_onEnter, QL_INTERCEPT.ENTER) - ql.set_api('puts', my_puts) + ql.os.set_api('puts', my_puts) ql.os.set_syscall(1, write_onexit, QL_INTERCEPT.EXIT) ql.mem.map(0x1000, 0x1000) ql.mem.write(0x1000, b"\xFF\xFE\xFD\xFC\xFB\xFA\xFB\xFC\xFC\xFE\xFD") @@ -156,8 +156,8 @@ def my_puts_exit(ql): self.test_exit_rdi = ql.arch.regs.rdi ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_puts"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG) - ql.set_api('puts', my_puts_enter, QL_INTERCEPT.ENTER) - ql.set_api('puts', my_puts_exit, QL_INTERCEPT.EXIT) + ql.os.set_api('puts', my_puts_enter, QL_INTERCEPT.ENTER) + ql.os.set_api('puts', my_puts_exit, QL_INTERCEPT.EXIT) ql.run() @@ -187,7 +187,7 @@ def onenter_fopen(ql: Qiling): def hook_main(ql: Qiling): # set up fopen hook when reaching main - ql.set_api('fopen', onenter_fopen, QL_INTERCEPT.ENTER) + ql.os.set_api('fopen', onenter_fopen, QL_INTERCEPT.ENTER) ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_fetch_urandom"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEFAULT) @@ -351,7 +351,7 @@ def my_puts(ql): ql.mem.restore(all_mem) ql = Qiling(["../examples/rootfs/arm_linux/bin/arm_hello"], "../examples/rootfs/arm_linux", verbose=QL_VERBOSE.DEBUG, profile='profiles/append_test.ql') - ql.set_api('puts', my_puts) + ql.os.set_api('puts', my_puts) ql.run() del ql @@ -507,7 +507,7 @@ def my_puts_onenter(ql: Qiling): return 2 ql = Qiling(["../examples/rootfs/mips32el_linux/bin/mips32el_double_hello"], "../examples/rootfs/mips32el_linux") - ql.set_api('puts', my_puts_onenter, QL_INTERCEPT.ENTER) + ql.os.set_api('puts', my_puts_onenter, QL_INTERCEPT.ENTER) ql.run() self.assertEqual(4196680, self.my_puts_onenter_addr) diff --git a/tests/test_elf_ko.py b/tests/test_elf_ko.py index b3a80b75e..c7286e14c 100644 --- a/tests/test_elf_ko.py +++ b/tests/test_elf_ko.py @@ -32,7 +32,7 @@ def my_printk(ql, address, params): try: procfile_read_func_begin = ql.loader.load_address + 0x11e0 procfile_read_func_end = ql.loader.load_address + 0x11fa - ql.set_api("printk", my_printk) + ql.os.set_api("printk", my_printk) ql.run(begin=procfile_read_func_begin, end=procfile_read_func_end) except UcError as e: print(e) @@ -55,7 +55,7 @@ def my_onenter(ql, address, params): try: procfile_read_func_begin = ql.loader.load_address + 0x1064 procfile_read_func_end = ql.loader.load_address + 0x107e - ql.set_api("printk", my_onenter, QL_INTERCEPT.ENTER) + 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) @@ -76,7 +76,7 @@ def my_onexit(ql, address, params, retval): 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.set_api("printk", my_onexit, QL_INTERCEPT.EXIT) + 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) diff --git a/tests/test_macho_kext.py b/tests/test_macho_kext.py index eb8bbca8e..879b676f1 100644 --- a/tests/test_macho_kext.py +++ b/tests/test_macho_kext.py @@ -82,9 +82,9 @@ def my__strlen(ql, address, params): return ql = Qiling(["../examples/rootfs/x8664_macos/kext/SuperRootkit.kext"], "../examples/rootfs/x8664_macos", verbose=QL_VERBOSE.DISASM) - ql.set_api("_ipf_addv4", my_onenter, QL_INTERCEPT.ENTER) - ql.set_api("_strncmp", my_onexit, QL_INTERCEPT.EXIT) - ql.set_api("_strlen", my__strlen) + ql.os.set_api("_ipf_addv4", my_onenter, QL_INTERCEPT.ENTER) + ql.os.set_api("_strncmp", my_onexit, QL_INTERCEPT.EXIT) + ql.os.set_api("_strlen", my__strlen) ql.hook_address(hook_stop, 0xffffff8000854800) try: diff --git a/tests/test_pe.py b/tests/test_pe.py index ff0938c50..9cad850ec 100644 --- a/tests/test_pe.py +++ b/tests/test_pe.py @@ -221,7 +221,7 @@ def ThreadId_onEnter(ql, address, params): return address, params ql = Qiling(["../examples/rootfs/x86_windows/bin/MultiThread.exe"], "../examples/rootfs/x86_windows") - ql.set_api("GetCurrentThreadId", ThreadId_onEnter, QL_INTERCEPT.ENTER) + ql.os.set_api("GetCurrentThreadId", ThreadId_onEnter, QL_INTERCEPT.ENTER) ql.run() if not ( 1<= thread_id < 255): @@ -421,9 +421,9 @@ def my_onexit(ql: Qiling, address: int, params, retval: int): def my_sandbox(path, rootfs): nonlocal set_api, set_api_onenter, set_api_onexit ql = Qiling(path, rootfs, verbose=QL_VERBOSE.DEBUG) - ql.set_api("puts", my_onenter, QL_INTERCEPT.ENTER) - ql.set_api("puts", my_puts64, QL_INTERCEPT.CALL) - ql.set_api("puts", my_onexit, QL_INTERCEPT.EXIT) + ql.os.set_api("puts", my_onenter, QL_INTERCEPT.ENTER) + ql.os.set_api("puts", my_puts64, QL_INTERCEPT.CALL) + ql.os.set_api("puts", my_onexit, QL_INTERCEPT.EXIT) ql.run() if 12 != set_api_onenter: @@ -474,7 +474,7 @@ def check_print(ql: Qiling, address: int, params): return address, params ql = Qiling(["../examples/rootfs/x86_windows/bin/argv.exe"], "../examples/rootfs/x86_windows") - ql.set_api('__stdio_common_vfprintf', check_print, QL_INTERCEPT.ENTER) + ql.os.set_api('__stdio_common_vfprintf', check_print, QL_INTERCEPT.ENTER) ql.run() if target_txt.find("argv.exe"): diff --git a/tests/test_pe_sys.py b/tests/test_pe_sys.py index 73da0742f..bc6e1174e 100644 --- a/tests/test_pe_sys.py +++ b/tests/test_pe_sys.py @@ -196,10 +196,10 @@ def hook_second_stop_address(ql): # for this module ql.amsint32_driver = None # emulate some Windows API - ql.set_api("CreateThread", hook_CreateThread) - ql.set_api("CreateFileA", hook_CreateFileA) - ql.set_api("WriteFile", hook_WriteFile) - ql.set_api("StartServiceA", hook_StartServiceA) + ql.os.set_api("CreateThread", hook_CreateThread) + ql.os.set_api("CreateFileA", hook_CreateFileA) + 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.run() diff --git a/tests/test_qnx.py b/tests/test_qnx.py index 43f9b252b..0303c7fb3 100644 --- a/tests/test_qnx.py +++ b/tests/test_qnx.py @@ -58,11 +58,11 @@ def my_printf_onexit(ql: Qiling): return QL_CALL_BLOCK ql = Qiling(["../examples/rootfs/arm_qnx/bin/hello_sqrt"], "../examples/rootfs/arm_qnx", verbose=QL_VERBOSE.DEBUG) - ql.set_api('puts', my_puts_onenter, QL_INTERCEPT.ENTER) - ql.set_api('printf', my_printf_onenter, QL_INTERCEPT.ENTER) - - # ql.set_api('puts', my_puts_onexit, QL_INTERCEPT.EXIT) - ql.set_api('printf', my_printf_onexit, QL_INTERCEPT.EXIT) + ql.os.set_api('puts', my_puts_onenter, QL_INTERCEPT.ENTER) + ql.os.set_api('printf', my_printf_onenter, QL_INTERCEPT.ENTER) + + # ql.os.set_api('puts', my_puts_onexit, QL_INTERCEPT.EXIT) + ql.os.set_api('printf', my_printf_onexit, QL_INTERCEPT.EXIT) ql.run() diff --git a/tests/test_uefi.py b/tests/test_uefi.py index b5180c360..f29420d91 100644 --- a/tests/test_uefi.py +++ b/tests/test_uefi.py @@ -101,9 +101,9 @@ def my_onexit(ql: Qiling, address: int, params, retval: int): ql = Qiling([f'{ROOTFS_UEFI}/bin/TcgPlatformSetupPolicy'], ROOTFS_UEFI, env=env, verbose=QL_VERBOSE.DEBUG) self.ck = Checklist() - ql.set_api("RegisterProtocolNotify", force_notify_RegisterProtocolNotify) - ql.set_api("CopyMem", my_onenter, QL_INTERCEPT.ENTER) - ql.set_api("LocateProtocol", my_onexit, QL_INTERCEPT.EXIT) + ql.os.set_api("RegisterProtocolNotify", force_notify_RegisterProtocolNotify) + ql.os.set_api("CopyMem", my_onenter, QL_INTERCEPT.ENTER) + ql.os.set_api("LocateProtocol", my_onexit, QL_INTERCEPT.EXIT) ql.run() From 27eaac6f2056595d2a82a64286e9612abbbcf0ef Mon Sep 17 00:00:00 2001 From: elicn Date: Sat, 22 Jan 2022 21:50:17 +0200 Subject: [PATCH 056/406] Properly document OS set_api --- qiling/os/os.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/qiling/os/os.py b/qiling/os/os.py index 3a74383ba..0701914b3 100644 --- a/qiling/os/os.py +++ b/qiling/os/os.py @@ -199,14 +199,21 @@ def call(self, pc: int, func: Callable, proto: Mapping[str, Any], onenter: Optio return retval # TODO: separate this method into os-specific functionalities, instead of 'if-else' - def set_api(self, api_name: str, intercept_function: Callable, intercept: QL_INTERCEPT): + def set_api(self, api_name: str, intercept_function: Callable, intercept: QL_INTERCEPT = QL_INTERCEPT.CALL): + """Either replace or hook OS API with a custom one. + + Args: + api_name: target API name + intercept_function: function to call + intercept: + `QL_INTERCEPT.CALL` : run handler instead of the existing target implementation + `QL_INTERCEPT.ENTER`: run handler before the target API is called + `QL_INTERCEPT.EXIT` : run handler after the target API is called + """ + if self.ql.ostype == QL_OS.UEFI: api_name = f'hook_{api_name}' - # BUG: workaround missing arg - if intercept is None: - intercept = QL_INTERCEPT.CALL - if (self.ql.ostype in (QL_OS.WINDOWS, QL_OS.UEFI, QL_OS.DOS)) or (self.ql.ostype in (QL_OS_POSIX) and self.ql.loader.is_driver): self.user_defined_api[intercept][api_name] = intercept_function else: From 296a4a411e5e2e2faab7e26daedf19c6376c1e70 Mon Sep 17 00:00:00 2001 From: elicn Date: Sat, 22 Jan 2022 23:26:25 +0200 Subject: [PATCH 057/406] Simplify arch_setup --- qiling/utils.py | 42 ++++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/qiling/utils.py b/qiling/utils.py index 41ecb9e27..8785af69b 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -417,30 +417,40 @@ def debugger_setup(options, ql): return None -def arch_setup(archtype, endian: QL_ENDIAN, thumb: bool, ql): +def arch_setup(archtype: QL_ARCH, endian: QL_ENDIAN, thumb: bool, ql): if not ql_is_valid_arch(archtype): - raise QlErrorArch("Invalid Arch") - - args = [ql] + raise QlErrorArch(f'Invalid architecture type') # set endianess and thumb mode for arm-based archs if archtype == QL_ARCH.ARM: - args.extend((endian, thumb)) + args = [endian, thumb] + # set endianess for mips arch elif archtype == QL_ARCH.MIPS: - args.append(endian) - - archmanager = f'QlArch{arch_convert_str(archtype).upper()}' + args = [endian] - if archtype in (QL_ARCH.X8664, QL_ARCH.A8086): - arch_str = "x86" else: - arch_str = arch_convert_str(archtype) - - if ql.interpreter: - return ql_get_module_function(f"qiling.arch.{arch_str.lower()}.{arch_str.lower()}", archmanager)(*args) - else: - return ql_get_module_function(f"qiling.arch.{arch_str.lower()}", archmanager)(*args) + args = [] + + module = { + QL_ARCH.A8086 : r'x86', + QL_ARCH.X86 : r'x86', + QL_ARCH.X8664 : r'x86', + QL_ARCH.ARM : r'arm', + QL_ARCH.ARM64 : r'arm64', + QL_ARCH.MIPS : r'mips', + QL_ARCH.EVM : r'evm.evm', + QL_ARCH.CORTEX_M : r'cortex_m', + QL_ARCH.RISCV : r'riscv', + QL_ARCH.RISCV64 : r'riscv64' + }[archtype] + + qlarch_path = f'qiling.arch.{module}' + qlarch_class = f'QlArch{arch_convert_str(archtype).upper()}' + + obj = ql_get_module_function(qlarch_path, qlarch_class) + + return obj(ql, *args) # This function is extracted from os_setup so I put it here. From 8b2f7cff1cf5b8525d4202adb3c82a6a086b6127 Mon Sep 17 00:00:00 2001 From: elicn Date: Sat, 22 Jan 2022 23:46:49 +0200 Subject: [PATCH 058/406] Simplify os_setup --- qiling/core.py | 2 +- qiling/utils.py | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/qiling/core.py b/qiling/core.py index a041ef76b..0f9082b15 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -202,7 +202,7 @@ def __init__( QlCoreHooks.__init__(self, self.uc) self.arch.utils.setup_output() - self._os = os_setup(self.archtype, self.ostype, self) + self._os = os_setup(self.ostype, self) # Run the loader self.loader.run() diff --git a/qiling/utils.py b/qiling/utils.py index 8785af69b..063fbb681 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -459,13 +459,10 @@ def ql_syscall_mapping_function(ostype): return ql_get_module_function(f"qiling.os.{ostype_str.lower()}.map_syscall", "map_syscall") -def os_setup(archtype: QL_ARCH, ostype: QL_OS, ql): +def os_setup(ostype: QL_OS, ql): if not ql_is_valid_ostype(ostype): raise QlErrorOsType("Invalid OSType") - if not ql_is_valid_arch(archtype): - raise QlErrorArch("Invalid Arch %s" % archtype) - ostype_str = ostype_convert_str(ostype) ostype_str = ostype_str.capitalize() function_name = "QlOs" + ostype_str From 28e8d014ce0658aa9f46431ed5572570028ad2cc Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 23 Jan 2022 00:30:24 +0200 Subject: [PATCH 059/406] Allow hooking sooner --- qiling/core.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/qiling/core.py b/qiling/core.py index 0f9082b15..8b55d198d 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -159,12 +159,13 @@ def __init__( self._arch = arch_setup(self.archtype, archendian, kwargs.get('thumb', False), self) - ######################## - # Archbit & Endianness # - ######################## - # Once we finish setting up archendian and arcbit, we can init QlCoreStructs. + self.uc = self.arch.uc + + # Once we finish setting up arch, we can init QlCoreStructs and QlCoreHooks QlCoreStructs.__init__(self, self.arch.endian, self.arch.bits) + if not self.interpreter: + QlCoreHooks.__init__(self, self.uc) ####################################### # Loader and General Purpose OS check # @@ -192,15 +193,8 @@ def __init__( ############## # Components # ############## - self.uc = self.arch.uc - if not self.interpreter: self._mem = component_setup("os", "memory", self) - - # Once we finish setting up arch layer, we can init QlCoreHooks. - if not self.interpreter: - QlCoreHooks.__init__(self, self.uc) - self.arch.utils.setup_output() self._os = os_setup(self.ostype, self) From d1cadc37f7f9bc0173c8194842af3667612c6da5 Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 23 Jan 2022 00:34:03 +0200 Subject: [PATCH 060/406] Allow logging sooner --- qiling/arch/utils.py | 4 +--- qiling/core.py | 34 ++++++++++++++++------------------ qiling/os/os.py | 2 -- qiling/utils.py | 5 +++-- 4 files changed, 20 insertions(+), 25 deletions(-) diff --git a/qiling/arch/utils.py b/qiling/arch/utils.py index 05b5af97b..78db75c1a 100644 --- a/qiling/arch/utils.py +++ b/qiling/arch/utils.py @@ -46,9 +46,7 @@ def disassembler(self, ql: Qiling, address: int, size: int): if type(reg) is str: ql.log.debug(f'{reg}\t: {ql.arch.regs.read(reg):#x}') - def setup_output(self): - def ql_hook_block_disasm(ql, address, size): - self.ql.log.info("\nTracing basic block at 0x%x" % (address)) + def setup_output(self, verbosity: QL_VERBOSE): if self._disasm_hook: self._disasm_hook.remove() diff --git a/qiling/core.py b/qiling/core.py index 8b55d198d..6f0cebc71 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -167,17 +167,9 @@ def __init__( if not self.interpreter: QlCoreHooks.__init__(self, self.uc) - ####################################### - # Loader and General Purpose OS check # - ####################################### - self._loader = loader_setup(self._ostype, self) - - ##################### - # Profile & Logging # - ##################### - self._profile, debugmsg = profile_setup(self) - - # Log's configuration + ########## + # Logger # + ########## self._log_file_fd, self._log_filter = ql_setup_logger(self, self._log_file, self._console, @@ -185,17 +177,23 @@ def __init__( self._log_override, self._log_plain) - self.log.setLevel(ql_resolve_logger_level(self._verbose)) + self.verbose = verbose - # Now that the logger is configured, we can log profile debug msg: - self.log.debug(debugmsg) + ########## + # Loader # + ########## + self._loader = loader_setup(self._ostype, self) + + ########### + # Profile # + ########### + self._profile = profile_setup(self) ############## # Components # ############## if not self.interpreter: self._mem = component_setup("os", "memory", self) - self.arch.utils.setup_output() self._os = os_setup(self.ostype, self) # Run the loader @@ -464,9 +462,9 @@ def verbose(self): @verbose.setter def verbose(self, v): self._verbose = v - self.log.setLevel(ql_resolve_logger_level(self._verbose)) - if self.interpreter: - self.arch.utils.setup_output() + + self.log.setLevel(ql_resolve_logger_level(v)) + self.arch.utils.setup_output(v) @property def patch_bin(self) -> list: diff --git a/qiling/os/os.py b/qiling/os/os.py index 0701914b3..19edb0673 100644 --- a/qiling/os/os.py +++ b/qiling/os/os.py @@ -79,8 +79,6 @@ def __init__(self, ql: Qiling, resolvers: Mapping[Any, Resolver] = {}): # let the user override default resolvers or add custom ones self.resolvers.update(resolvers) - self.ql.arch.utils.setup_output() - def save(self) -> Mapping[str, Any]: return {} diff --git a/qiling/utils.py b/qiling/utils.py index 063fbb681..d83af5a94 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -474,7 +474,8 @@ def profile_setup(ql): if ql.profile != None: _profile = ql.profile - debugmsg = "Profile: %s" % _profile + + ql.log.debug(f'Profile: {_profile}') if ql.baremetal: config = {} @@ -489,7 +490,7 @@ def profile_setup(ql): config = ConfigParser() config.read(profiles) - return config, debugmsg + return config def ql_resolve_logger_level(verbose: QL_VERBOSE) -> int: return { From 7176c8df610a45dfa24f8984a6193bfe6cc1b909 Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 24 Jan 2022 22:20:56 +0200 Subject: [PATCH 061/406] Extract ini_get_tls from QlArchARM --- qiling/arch/arm.py | 21 --------------------- qiling/arch/arm_utils.py | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 21 deletions(-) create mode 100644 qiling/arch/arm_utils.py diff --git a/qiling/arch/arm.py b/qiling/arch/arm.py index 1b9debc82..248f12f80 100644 --- a/qiling/arch/arm.py +++ b/qiling/arch/arm.py @@ -109,24 +109,3 @@ def enable_vfp(self) -> None: self.regs.c1_c0_2 = self.regs.c1_c0_2 | (0xb11 << 20) | (0xb11 << 22) self.regs.fpexc = (1 << 30) - - """ - set_tls - """ - def init_get_tls(self): - self.ql.mem.map(0xFFFF0000, 0x1000, info="[arm_tls]") - """ - 'adr r0, data; ldr r0, [r0]; mov pc, lr; data:.ascii "\x00\x00"' - """ - sc = b'\x04\x00\x8f\xe2\x00\x00\x90\xe5\x0e\xf0\xa0\xe1\x00\x00\x00\x00' - - # if self.endian == QL_ENDIAN.EB: - # sc = swap_endianess(sc) - - self.ql.mem.write(self.arm_get_tls_addr, sc) - self.ql.log.debug("Set init_kernel_get_tls") - - def swap_endianess(self, s: bytes, blksize=4) -> bytes: - blocks = (s[i:i + blksize] for i in range(0, len(s), blksize)) - - return b''.join(bytes(reversed(b)) for b in blocks) \ No newline at end of file diff --git a/qiling/arch/arm_utils.py b/qiling/arch/arm_utils.py new file mode 100644 index 000000000..a85700866 --- /dev/null +++ b/qiling/arch/arm_utils.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from qiling import Qiling + +def init_get_tls(ql: Qiling, address: int) -> None: + # adr r0, data + # ldr r0, [r0] + # mov pc, lr + # + # data: + # .ascii "\x00\x00" + + code = bytes.fromhex(''' + 04 00 8f e2 + 00 00 90 e5 + 0e f0 a0 e1 + 00 00 00 00 + ''') + + # if endian == QL_ENDIAN.EB: + # code = swap_endianess(code) + + base = ql.mem.align(address) + size = ql.mem.align_up(len(code)) + + ql.mem.map(base, size, info="[arm_tls]") + ql.mem.write(address, code) + + ql.log.debug('Set kernel get_tls') + +# def swap_endianess(s: bytes, blksize: int = 4) -> bytes: +# blocks = (s[i:i + blksize] for i in range(0, len(s), blksize)) +# +# return b''.join(bytes(reversed(b)) for b in blocks) \ No newline at end of file From 0802e5abd5f3a84d132ce9c0416893d65ed360ac Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 24 Jan 2022 22:21:26 +0200 Subject: [PATCH 062/406] Adjust init_get_tls usages --- qiling/os/linux/linux.py | 3 ++- qiling/os/qnx/qnx.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/qiling/os/linux/linux.py b/qiling/os/linux/linux.py index 00d1de00c..983b78b2a 100644 --- a/qiling/os/linux/linux.py +++ b/qiling/os/linux/linux.py @@ -9,6 +9,7 @@ from qiling import Qiling from qiling.arch.x86_const import UC_X86_INS_SYSCALL from qiling.arch.x86 import GDTManager, ql_x8664_set_gs, ql_x86_register_cs, ql_x86_register_ds_ss_es +from qiling.arch import arm_utils from qiling.cc import QlCC, intel, arm, mips, riscv from qiling.const import QL_ARCH, QL_INTERCEPT from qiling.os.fcall import QlFunctionCall @@ -55,7 +56,7 @@ def load(self): self.ql.arch.enable_vfp() self.ql.hook_intno(self.hook_syscall, 2) self.thread_class = thread.QlLinuxARMThread - self.ql.arch.init_get_tls() + arm_utils.init_get_tls(self.ql, self.ql.arch.arm_get_tls_addr) # MIPS32 elif self.ql.archtype == QL_ARCH.MIPS: diff --git a/qiling/os/qnx/qnx.py b/qiling/os/qnx/qnx.py index 55bcc10d8..57e595959 100644 --- a/qiling/os/qnx/qnx.py +++ b/qiling/os/qnx/qnx.py @@ -10,6 +10,7 @@ from unicorn import UcError from qiling import Qiling +from qiling.arch import arm_utils from qiling.os.posix.posix import QlOsPosix from qiling.os.qnx.const import NTO_SIDE_CHANNEL, SYSMGR_PID, SYSMGR_CHID, SYSMGR_COID from qiling.os.qnx.helpers import QnxConn @@ -68,7 +69,7 @@ def load(self): self.ql.arch.enable_vfp() self.ql.hook_intno(self.hook_syscall, 2) #self.thread_class = thread.QlLinuxARMThread - self.ql.arch.init_get_tls() + arm_utils.init_get_tls(self.ql, self.ql.arch.arm_get_tls_addr) def hook_syscall(self, intno= None, int = None): From 3493a23a2c87da0d251c1075190e0dd01267fb53 Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 24 Jan 2022 22:22:15 +0200 Subject: [PATCH 063/406] Fix erroneous consts --- qiling/arch/arm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiling/arch/arm.py b/qiling/arch/arm.py index 248f12f80..b87baf0ac 100644 --- a/qiling/arch/arm.py +++ b/qiling/arch/arm.py @@ -106,6 +106,6 @@ def assembler(self) -> Ks: def enable_vfp(self) -> None: # set full access to cp10 and cp11 - self.regs.c1_c0_2 = self.regs.c1_c0_2 | (0xb11 << 20) | (0xb11 << 22) + self.regs.c1_c0_2 = self.regs.c1_c0_2 | (0b11 << 20) | (0b11 << 22) self.regs.fpexc = (1 << 30) From 7bde60b3d6e1eb673e4c380f6864f996638787ef Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 25 Jan 2022 16:35:10 +0200 Subject: [PATCH 064/406] Adjust QlArchUtils --- qiling/arch/utils.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/qiling/arch/utils.py b/qiling/arch/utils.py index 78db75c1a..0a05a52c0 100644 --- a/qiling/arch/utils.py +++ b/qiling/arch/utils.py @@ -24,42 +24,46 @@ def __init__(self, ql: Qiling): self._disasm_hook = None self._block_hook = None - def get_offset_and_name(self, addr: int) -> Tuple[int, str]: + def get_base_and_name(self, addr: int) -> Tuple[int, str]: for begin, end, _, name, _ in self.ql.mem.map_info: if begin <= addr < end: - return addr - begin, basename(name) + return begin, basename(name) return addr, '-' def disassembler(self, ql: Qiling, address: int, size: int): - tmp = ql.mem.read(address, size) - qd = ql.arch.disassembler + data = ql.mem.read(address, size) + ba, name = self.get_base_and_name(address) - offset, name = self.get_offset_and_name(address) - log_data = f'{address:0{ql.arch.bits // 4}x} [{name:20s} + {offset:#08x}] {tmp.hex(" "):30s}' - log_insn = '\n> '.join(f'{insn.mnemonic:20s} {insn.op_str}' for insn in qd.disasm(tmp, address)) + anibbles = ql.arch.bits // 4 - ql.log.info(log_data + log_insn) + for insn in ql.arch.disassembler.disasm(data, address): + offset = insn.address - ba + + ql.log.info(f'{insn.address:0{anibbles}x} [{name:20s} + {offset:#08x}] {insn.bytes.hex(" "):20s} {insn.mnemonic:20s} {insn.op_str}') if ql.verbose >= QL_VERBOSE.DUMP: for reg in ql.arch.regs.register_mapping: - if type(reg) is str: - ql.log.debug(f'{reg}\t: {ql.arch.regs.read(reg):#x}') + ql.log.info(f'{reg:10s} : {ql.arch.regs.read(reg):#x}') def setup_output(self, verbosity: QL_VERBOSE): + def ql_hook_block_disasm(ql: Qiling, address: int, size: int): + self.ql.log.info(f'\nTracing basic block at {address:#x}') if self._disasm_hook: self._disasm_hook.remove() self._disasm_hook = None + if self._block_hook: self._block_hook.remove() self._block_hook = None - if self.ql.verbose >= QL_VERBOSE.DISASM: - if self.ql.verbose >= QL_VERBOSE.DUMP: - self._block_hook = self.ql.hook_block(ql_hook_block_disasm) + if verbosity >= QL_VERBOSE.DISASM: self._disasm_hook = self.ql.hook_code(self.disassembler) + if verbosity >= QL_VERBOSE.DUMP: + self._block_hook = self.ql.hook_block(ql_hook_block_disasm) + # used by qltool prior to ql instantiation. to get an assembler object # after ql instantiation, use the appropriate ql.arch method def assembler(arch: QL_ARCH, endianess: QL_ENDIAN, is_thumb: bool) -> Ks: From 8d7a46748d3761a9a20e78307ac55279dbebaa24 Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 25 Jan 2022 23:53:30 +0200 Subject: [PATCH 065/406] Remove used internal properties --- qiling/core.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/qiling/core.py b/qiling/core.py index 6f0cebc71..cbe1f679e 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -67,13 +67,9 @@ def __init__( self._ostype = ostype self._archtype = archtype self._profile = profile - self._console = console - self._log_file = log_file self._multithread = multithread self._log_file_fd = None self._log_filter = None - self._log_override = log_override - self._log_plain = log_plain self._filter = filter self._internal_exception = None self._uc = None @@ -171,11 +167,11 @@ def __init__( # Logger # ########## self._log_file_fd, self._log_filter = ql_setup_logger(self, - self._log_file, - self._console, + log_file, + console, self._filter, - self._log_override, - self._log_plain) + log_override, + log_plain) self.verbose = verbose From 460146635da817a5a01f330d587cd5221f5cc58d Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 25 Jan 2022 23:54:07 +0200 Subject: [PATCH 066/406] Remove unused properties --- qiling/core.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/qiling/core.py b/qiling/core.py index cbe1f679e..dfde329d8 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -258,24 +258,6 @@ def log(self) -> logging.Logger: # If an option doesn't have a setter, it means that it can be only set during Qiling.__init__ - @property - def console(self) -> bool: - """ Specify whether enabling console output. - - Type: bool - Example: Qiling(console=True) - """ - return self._console - - @property - def log_file(self) -> str: - """ Log to a file. - - Type: str - Example: Qiling(log_file="./ql.log") - """ - return self._log_file - @property def multithread(self) -> bool: """ Specify whether multithread has been enabled. From a2badb936057a32683f6900f3b7bfe43e0e47a10 Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 26 Jan 2022 00:53:34 +0200 Subject: [PATCH 067/406] Adjust libfuzzer example --- examples/fuzzing/linux_x8664/libfuzzer_x8664_linux.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/examples/fuzzing/linux_x8664/libfuzzer_x8664_linux.py b/examples/fuzzing/linux_x8664/libfuzzer_x8664_linux.py index ccb2cea0a..e8e3e56ff 100755 --- a/examples/fuzzing/linux_x8664/libfuzzer_x8664_linux.py +++ b/examples/fuzzing/linux_x8664/libfuzzer_x8664_linux.py @@ -10,11 +10,7 @@ class SimpleFuzzer: def run(self): - ql = Qiling(["./x8664_fuzz"], "../../rootfs/x8664_linux", - console=False, # No output - stdin=None, - stdout=None, - stderr=None) + ql = Qiling(["./x8664_fuzz"], "../../rootfs/x8664_linux", console=False) ba = ql.loader.images[0].base try: # Only instrument the function `fun`, so we don't need to instrument libc and ld From 796e152fe0cb6071fabfbf7669a5b9e197b45770 Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 26 Jan 2022 00:59:41 +0200 Subject: [PATCH 068/406] More commenting --- .../fuzzing/linux_x8664/fuzz_x8664_linux.py | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/examples/fuzzing/linux_x8664/fuzz_x8664_linux.py b/examples/fuzzing/linux_x8664/fuzz_x8664_linux.py index 469237ed2..2f40ee79a 100755 --- a/examples/fuzzing/linux_x8664/fuzz_x8664_linux.py +++ b/examples/fuzzing/linux_x8664/fuzz_x8664_linux.py @@ -17,18 +17,18 @@ $ rm -fr afl_outputs/default/ """ -# No more need for importing unicornafl, try ql.afl_fuzz instead! +# No more need for importing unicornafl, try afl.ql_afl_fuzz instead! import os import sys -from typing import Any, Optional +from typing import Optional sys.path.append("../../..") from qiling import Qiling from qiling.const import QL_VERBOSE from qiling.extensions import pipe -from qiling.extensions.afl import ql_afl_fuzz +from qiling.extensions import afl def main(input_file: str): ql = Qiling(["./x8664_fuzz"], "../../rootfs/x8664_linux", @@ -39,26 +39,31 @@ def main(input_file: str): ql.os.stdin = pipe.SimpleInStream(sys.stdin.fileno()) def place_input_callback(ql: Qiling, input: bytes, persistent_round: int) -> Optional[bool]: - """Called with every newly generated input. + """Feed generated stimuli to the fuzzed target. + + This method is called with every fuzzing iteration. """ + # feed fuzzed input to our mock stdin ql.os.stdin.write(input) + # signal afl to proceed with this input return True - def start_afl(_ql: Qiling): - """Callback from inside. + def start_afl(ql: Qiling): + """Have Unicorn fork and start instrumentation. """ - ql_afl_fuzz(_ql, input_file=input_file, place_input_callback=place_input_callback, exits=[ql.os.exit_point]) + + afl.ql_afl_fuzz(ql, input_file=input_file, place_input_callback=place_input_callback, exits=[ql.os.exit_point]) # get image base address ba = ql.loader.images[0].base - # make process crash whenever __stack_chk_fail@plt is about to be called. + # make the process crash whenever __stack_chk_fail@plt is about to be called. # this way afl will count stack protection violations as crashes ql.hook_address(callback=lambda x: os.abort(), address=ba + 0x1225) - # set a hook on main() to let unicorn fork and start instrumentation + # set afl instrumentation [re]starting point. we set it to 'main' ql.hook_address(callback=start_afl, address=ba + 0x122c) # okay, ready to roll From e7827b21f4d0a741cc811146cf97ddca1f6d4e5f Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 26 Jan 2022 21:50:15 +0200 Subject: [PATCH 069/406] Remove kargs and use named args instead --- qiling/core.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qiling/core.py b/qiling/core.py index dfde329d8..67f01427b 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -50,7 +50,8 @@ def __init__( filter = None, stop_on_stackpointer = False, stop_on_exit_trap = False, - **kwargs + *, + thumb: bool = False ): """ Create a Qiling instance. @@ -153,7 +154,7 @@ def __init__( if archendian is None: archendian = QL_ENDIAN.EL - self._arch = arch_setup(self.archtype, archendian, kwargs.get('thumb', False), self) + self._arch = arch_setup(self.archtype, archendian, thumb, self) self.uc = self.arch.uc From e3853689ed05398f7e52cd39a687dc536798924c Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 26 Jan 2022 21:51:10 +0200 Subject: [PATCH 070/406] Turn endian into an optional named arg --- qiling/core.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/qiling/core.py b/qiling/core.py index 67f01427b..8db5233a0 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -38,7 +38,6 @@ def __init__( code=None, ostype=None, archtype=None, - bigendian=False, verbose=QL_VERBOSE.DEFAULT, profile=None, console=True, @@ -51,6 +50,7 @@ def __init__( stop_on_stackpointer = False, stop_on_exit_trap = False, *, + endian: QL_ENDIAN = None, thumb: bool = False ): """ Create a Qiling instance. @@ -133,24 +133,26 @@ def __init__( if self._archtype is None: guessed_archtype, guessed_ostype, guessed_archendian = ql_guess_emu_env(self._path) + self._archtype = guessed_archtype - archendian = guessed_archendian if self._ostype is None: self._ostype = guessed_ostype + if endian is None: + archendian = guessed_archendian + elif self._ostype == None: self._ostype = arch_os_convert(self._archtype) - + if self._ostype is None or not ql_is_valid_ostype(self._ostype): raise QlErrorOsType("Invalid OS: %s" % (self._ostype)) if self._archtype is None or not ql_is_valid_arch(self._archtype): raise QlErrorArch("Invalid ARCH: %s" % (self._archtype)) - if bigendian and self._archtype in QL_ARCH_ENDIAN: - archendian = QL_ENDIAN.EB - + # if endianess is still undetermined, set it to little-endian. + # this setting is ignored for architectures with predfined endianess if archendian is None: archendian = QL_ENDIAN.EL From 0b4b66fc981e9b120f1ef924a7079673dea24abd Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 26 Jan 2022 21:55:36 +0200 Subject: [PATCH 071/406] Minor imports and comments fixes --- qiling/core.py | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/qiling/core.py b/qiling/core.py index 8db5233a0..3d97bcee2 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -7,13 +7,12 @@ import ntpath, os, pickle # See https://stackoverflow.com/questions/39740632/python-type-hinting-without-cyclic-imports -from typing import Callable, Dict, List, Union +from typing import Dict, List, Union from typing import TYPE_CHECKING from unicorn.unicorn import Uc if TYPE_CHECKING: - from .arch.register import QlRegisterManager from .arch.arch import QlArch from .os.os import QlOs from .os.memory import QlMemoryManager @@ -326,7 +325,7 @@ def ostype(self) -> QL_OS: - "windows" : Windows - "uefi" : UEFI - "dos" : DOS - Example: Qiling(code=b"\x90", ostype="macos", archtype="x8664", bigendian=False) + Example: Qiling(code=b"\x90", ostype="macos", archtype="x8664") """ return self._ostype @@ -345,7 +344,7 @@ def archtype(self) -> QL_ARCH: - "arm" : ARM - "arm64" : ARM64 - "a8086" : 8086 - Example: Qiling(code=b"\x90", ostype="macos", archtype="x8664", bigendian=False) + Example: Qiling(code=b"\x90", ostype="macos", archtype="x8664") """ return self._archtype @@ -356,7 +355,7 @@ def code(self) -> bytes: Note: It can't be used with "argv" parameter. Type: bytes - Example: Qiling(code=b"\x90", ostype="macos", archtype="x8664", bigendian=False) + Example: Qiling(code=b"\x90", ostype="macos", archtype="x8664") """ return self._code @@ -425,23 +424,20 @@ def libcache(self, lc): self._libcache = lc @property - def verbose(self): - """ Set the verbose level. + def verbose(self) -> QL_VERBOSE: + """Set verbosity level. - Type: int - Values: - - 0 : logging.WARNING, almost no additional logs except the program output. - - >=1: logging.INFO, the default logging level. - - >=4: logging.DEBUG. - - >=10: Disasm each executed instruction. - - >=20: The most verbose output, dump registers and disasm the function blocks. - Example: - ql = Qiling(verbose=5) - - ql.verbose = 0 + Values: + `QL_VERBOSE.OFF` : mask off anything below warnings, errors and critical severity + `QL_VERBOSE.DEFAULT` : info logging level: default verbosity + `QL_VERBOSE.DEBUG` : debug logging level: higher verbosity + `QL_VERBOSE.DISASM` : debug verbosity along with disassembly trace (slow!) + `QL_VERBOSE.DUMP` : disassembly trace along with cpu context dump """ return self._verbose @verbose.setter - def verbose(self, v): + def verbose(self, v: QL_VERBOSE): self._verbose = v self.log.setLevel(ql_resolve_logger_level(v)) From d97f84b670730ecec5ae891068cb0c39ba43ab45 Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 26 Jan 2022 22:34:38 +0200 Subject: [PATCH 072/406] Adjust qltool --- qltool | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/qltool b/qltool index 5adbf38dc..2496fc86d 100755 --- a/qltool +++ b/qltool @@ -43,6 +43,11 @@ class __arg_verbose(argparse.Action): setattr(namespace, self.dest, verbose_map[values]) def handle_code(options: argparse.Namespace): + archendian = { + 'little': QL_ENDIAN.EL, + 'big' : QL_ENDIAN.EB + }[options.endian] + if options.format == 'hex': if options.input is not None: print ("Load HEX from ARGV") @@ -64,11 +69,6 @@ def handle_code(options: argparse.Namespace): assembly = read_file(options.filename) archtype = arch_convert(options.arch) - archendian = { - 'little': QL_ENDIAN.EL, - 'big' : QL_ENDIAN.EB - }[options.endian] - assembler = arch_utils.assembler(archtype, archendian, options.thumb) code, _ = assembler.asm(assembly) code = bytes(code) @@ -87,10 +87,10 @@ def handle_code(options: argparse.Namespace): code=code, ostype=options.os, archtype=options.arch, - bigendian=(options.endian == 'big'), verbose=options.verbose, profile=options.profile, - filter=options.filter + filter=options.filter, + endian=archendian, ) return ql @@ -185,7 +185,7 @@ if __name__ == '__main__': code_parser.add_argument('-i', '--input', metavar="INPUT", dest="input", help='input hex value') code_parser.add_argument('--arch', required=True, choices=arch_map) code_parser.add_argument('--thumb', action='store_true', help='specify thumb mode for ARM') - code_parser.add_argument('--endian', choices=('little', 'big'), default='little') + code_parser.add_argument('--endian', choices=('little', 'big'), default='little', help='specify endianess for bi-endian archs') code_parser.add_argument('--os', required=True, choices=os_map) code_parser.add_argument('--rootfs', help='emulated root filesystem, that is where all libraries reside') code_parser.add_argument('--format', choices=('asm', 'hex', 'bin'), default='bin', help='input file format') From be8510cc655ffd546cefac2b62ec1841998113be Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 27 Jan 2022 18:21:45 +0200 Subject: [PATCH 073/406] Tweak profile_setup --- qiling/core.py | 3 +-- qiling/utils.py | 25 +++++++++++++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/qiling/core.py b/qiling/core.py index 3d97bcee2..1c5ce9119 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -66,7 +66,6 @@ def __init__( self._code = code self._ostype = ostype self._archtype = archtype - self._profile = profile self._multithread = multithread self._log_file_fd = None self._log_filter = None @@ -185,7 +184,7 @@ def __init__( ########### # Profile # ########### - self._profile = profile_setup(self) + self._profile = profile_setup(self, self.ostype, profile) ############## # Components # diff --git a/qiling/utils.py b/qiling/utils.py index d83af5a94..4b6fc822f 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -469,23 +469,24 @@ def os_setup(ostype: QL_OS, ql): return ql_get_module_function(f"qiling.os.{ostype_str.lower()}.{ostype_str.lower()}", function_name)(ql) -def profile_setup(ql): - _profile = "Default" - - if ql.profile != None: - _profile = ql.profile - - ql.log.debug(f'Profile: {_profile}') +def profile_setup(ql, ostype: QL_OS, filename: Optional[str]): + ql.log.debug(f'Profile: {filename or "default"}') if ql.baremetal: - config = {} - if ql.profile: - with open(ql.profile) as f: + if filename: + with open(filename) as f: config = yaml.load(f, Loader=yaml.Loader) + else: + config = {} else: - profile_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "profiles", ostype_convert_str(ql.ostype).lower() + ".ql") - profiles = [profile_path, ql.profile] if ql.profile else [profile_path] + qiling_home = os.path.dirname(os.path.abspath(__file__)) + os_profile = os.path.join(qiling_home, 'profiles', f'{ostype_convert_str(ostype).lower()}.ql') + + profiles = [os_profile] + + if filename: + profiles.append(filename) config = ConfigParser() config.read(profiles) From c71d7c49f357b94c7d437d981c53670165f8f82f Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 27 Jan 2022 18:35:28 +0200 Subject: [PATCH 074/406] Tweak core initialization --- qiling/core.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/qiling/core.py b/qiling/core.py index 1c5ce9119..85254966d 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -4,7 +4,7 @@ # from configparser import ConfigParser -import ntpath, os, pickle +import os, pickle # See https://stackoverflow.com/questions/39740632/python-type-hinting-without-cyclic-imports from typing import Dict, List, Union @@ -60,9 +60,7 @@ def __init__( ################################## # Definition during ql=Qiling() # ################################## - self._argv = argv - self._rootfs = rootfs - self._env = env if env else {} + self._env = env self._code = code self._ostype = ostype self._archtype = archtype @@ -77,7 +75,6 @@ def __init__( ################################## # Definition after ql=Qiling() # ################################## - self._verbose = verbose self._libcache = libcache self._patch_bin = [] self._patch_lib = [] @@ -99,23 +96,26 @@ def __init__( ############## # argv setup # ############## - if self._argv is None: - self._argv = ["qilingcode"] + if argv is None: + argv = ['qilingcode'] - elif not os.path.exists(str(self._argv[0])): - raise QlErrorFileNotFound("Target binary not found: %s" % (self._argv[0])) + elif not os.path.exists(argv[0]): + raise QlErrorFileNotFound(f'Target binary not found: "{argv[0]}"') + + self._argv = argv + self._path = self.argv[0] + self._targetname = os.path.basename(self.path) ################ # rootfs setup # ################ - if self._rootfs is None: - self._rootfs = "." - - elif not os.path.exists(self._rootfs): - raise QlErrorFileNotFound("Target rootfs not found: %s" % (self._rootfs)) - - self._path = self._argv[0] - self._targetname = ntpath.basename(self.path) + if rootfs is None: + rootfs = '.' + + elif not os.path.exists(rootfs): + raise QlErrorFileNotFound(f'Target rootfs not found: "{rootfs}"') + + self._rootfs = rootfs ################# # arch os setup # From 93bafbd2718b2e025be5a4965e52de99c11359b4 Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 27 Jan 2022 18:36:27 +0200 Subject: [PATCH 075/406] Annotate init arguments --- qiling/core.py | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/qiling/core.py b/qiling/core.py index 85254966d..d8da114ba 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -7,7 +7,7 @@ import os, pickle # See https://stackoverflow.com/questions/39740632/python-type-hinting-without-cyclic-imports -from typing import Dict, List, Union +from typing import AnyStr, MutableMapping, Sequence, Union from typing import TYPE_CHECKING from unicorn.unicorn import Uc @@ -19,7 +19,7 @@ from .hw.hw import QlHwManager from .loader.loader import QlLoader -from .const import QL_ARCH_ENDIAN, QL_ENDIAN, QL_VERBOSE, QL_OS_INTERPRETER, QL_OS_BAREMETAL +from .const import QL_ARCH, QL_ENDIAN, QL_OS, QL_VERBOSE, QL_OS_INTERPRETER, QL_OS_BAREMETAL from .exception import QlErrorFileNotFound, QlErrorArch, QlErrorOsType from .host import QlHost from .utils import * @@ -28,17 +28,17 @@ from .__version__ import __version__ # Mixin Pattern -class Qiling(QlCoreHooks, QlCoreStructs): +class Qiling(QlCoreHooks, QlCoreStructs): def __init__( self, - argv=None, - rootfs=None, - env=None, - code=None, - ostype=None, - archtype=None, - verbose=QL_VERBOSE.DEFAULT, - profile=None, + argv: Sequence[str] = None, + rootfs: str = None, + env: MutableMapping[AnyStr, AnyStr] = {}, + code: bytes = None, + ostype: Union[str, QL_OS] = None, + archtype: Union[str, QL_ARCH] = None, + verbose: QL_VERBOSE = QL_VERBOSE.DEFAULT, + profile: str = None, console=True, log_file=None, log_override=None, @@ -283,10 +283,9 @@ def profile(self) -> ConfigParser: return self._profile @property - def argv(self) -> List[str]: + def argv(self) -> Sequence[str]: """ The program argv. - Type: List[str] Example: Qiling(argv=['/bin/ls', '-a']) """ return self._argv @@ -301,10 +300,9 @@ def rootfs(self) -> str: return self._rootfs @property - def env(self) -> Dict[str, str]: + def env(self) -> MutableMapping[AnyStr, AnyStr]: """ The program environment variables. - Type: Dict[str, str] Example: Qiling(env={"LC_ALL" : "en_US.UTF-8"}) """ return self._env From 5bbc0c77cb7cf3e8ffadf6bcf055aae314c3fe20 Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 27 Jan 2022 20:43:27 +0200 Subject: [PATCH 076/406] Make libcache a QlLoaderPE arg --- qiling/core.py | 6 +++--- qiling/loader/pe.py | 8 ++------ qiling/utils.py | 14 ++++++++++---- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/qiling/core.py b/qiling/core.py index d8da114ba..c7d8e17b2 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -43,14 +43,14 @@ def __init__( log_file=None, log_override=None, log_plain=False, - libcache = False, multithread = False, filter = None, stop_on_stackpointer = False, stop_on_exit_trap = False, *, endian: QL_ENDIAN = None, - thumb: bool = False + thumb: bool = False, + libcache: bool = False ): """ Create a Qiling instance. @@ -179,7 +179,7 @@ def __init__( ########## # Loader # ########## - self._loader = loader_setup(self._ostype, self) + self._loader = loader_setup(self, self.ostype, libcache) ########### # Profile # diff --git a/qiling/loader/pe.py b/qiling/loader/pe.py index f8267fe0c..5d235339f 100644 --- a/qiling/loader/pe.py +++ b/qiling/loader/pe.py @@ -435,17 +435,13 @@ def init_ki_user_shared_data(self): class QlLoaderPE(QlLoader, Process): - def __init__(self, ql: Qiling): + def __init__(self, ql: Qiling, libcache: bool): super().__init__(ql) self.ql = ql self.path = self.ql.path self.is_driver = False - - if ql.libcache is True: - self.libcache = QlPeCache() - else: - self.libcache = ql.libcache or None + self.libcache = QlPeCache() if libcache else None def run(self): self.init_dlls = [b"ntdll.dll", b"kernel32.dll", b"user32.dll"] diff --git a/qiling/utils.py b/qiling/utils.py index 4b6fc822f..6aa983f40 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -390,10 +390,16 @@ def ql_guess_emu_env(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], Opt return arch, ostype, endian -def loader_setup(ostype: QL_OS, ql): - loadertype_str = loadertype_convert_str(ostype) - function_name = "QlLoader" + loadertype_str - return ql_get_module_function(f"qiling.loader.{loadertype_str.lower()}", function_name)(ql) +def loader_setup(ql, ostype: QL_OS, libcache: bool): + args = [libcache] if ostype == QL_OS.WINDOWS else [] + + qlloader_name = loadertype_convert_str(ostype) + qlloader_path = f'qiling.loader.{qlloader_name.lower()}' + qlloader_class = f'QlLoader{qlloader_name.upper()}' + + obj = ql_get_module_function(qlloader_path, qlloader_class) + + return obj(ql, *args) def component_setup(component_type, component_name, ql): From 5cdc659630beaf025659131a169b5d29fd808f33 Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 27 Jan 2022 20:44:30 +0200 Subject: [PATCH 077/406] Remove libcache property from core --- qiling/core.py | 15 --------------- tests/test_pe_sys.py | 1 - 2 files changed, 16 deletions(-) diff --git a/qiling/core.py b/qiling/core.py index c7d8e17b2..c34e2d43b 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -75,7 +75,6 @@ def __init__( ################################## # Definition after ql=Qiling() # ################################## - self._libcache = libcache self._patch_bin = [] self._patch_lib = [] self._debug_stop = False @@ -406,20 +405,6 @@ def internal_exception(self) -> Exception: """ return self._internal_exception - @property - def libcache(self) -> bool: - """ Whether cache dll files. Only take effect in Windows emulation. - - Type: bool - Example: - ql = Qiling(libcache=False) - - ql.libcache = True - """ - return self._libcache - - @libcache.setter - def libcache(self, lc): - self._libcache = lc - @property def verbose(self) -> QL_VERBOSE: """Set verbosity level. diff --git a/tests/test_pe_sys.py b/tests/test_pe_sys.py index bc6e1174e..c609914bf 100644 --- a/tests/test_pe_sys.py +++ b/tests/test_pe_sys.py @@ -189,7 +189,6 @@ def hook_second_stop_address(ql): ql = Qiling(["../examples/rootfs/x86_windows/bin/sality.dll"], "../examples/rootfs/x86_windows", verbose=QL_VERBOSE.DEBUG) - ql.libcache = False ql.first_stop = False ql.second_stop = False self.third_stop = False From 42f05f2da0176cd8f6d5a0684a85d7b4d12d6195 Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 27 Jan 2022 22:00:14 +0200 Subject: [PATCH 078/406] Tweak patch API --- qiling/core.py | 23 ++++++++++++++++------- tests/test_elf.py | 2 +- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/qiling/core.py b/qiling/core.py index c34e2d43b..4bf1cd94d 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -7,7 +7,7 @@ import os, pickle # See https://stackoverflow.com/questions/39740632/python-type-hinting-without-cyclic-imports -from typing import AnyStr, MutableMapping, Sequence, Union +from typing import AnyStr, List, MutableMapping, Sequence, Union from typing import TYPE_CHECKING from unicorn.unicorn import Uc @@ -426,7 +426,7 @@ def verbose(self, v: QL_VERBOSE): self.arch.utils.setup_output(v) @property - def patch_bin(self) -> list: + def patch_bin(self) -> List[Tuple[int, bytes]]: """ Return the patches for binary. Type: list @@ -434,7 +434,7 @@ def patch_bin(self) -> list: return self._patch_bin @property - def patch_lib(self) -> list: + def patch_lib(self) -> List[Tuple[int, bytes, str]]: """ Return the patches for library. Type: list @@ -608,11 +608,20 @@ def run(self, begin=None, end=None, timeout=0, count=0, code = None): # patch code to memory address - def patch(self, addr, code, file_name=b''): - if file_name == b'': - self.patch_bin.append((addr, code)) + def patch(self, offset: int, data: bytes, target: str = None) -> None: + """Volatilely patch binary and libraries with arbitrary content. + Patching may be done prior to emulation start. + + Args: + offset: offset in target to patch + data: patch data + target: target library name to patch (or `None` for the main executable binary) + """ + + if target is None: + self.patch_bin.append((offset, data)) else: - self.patch_lib.append((addr, code, file_name.decode())) + self.patch_lib.append((offset, data, target)) # save all qiling instance states diff --git a/tests/test_elf.py b/tests/test_elf.py index 25d3d9e5b..98a049717 100644 --- a/tests/test_elf.py +++ b/tests/test_elf.py @@ -19,7 +19,7 @@ class ELFTest(unittest.TestCase): def test_libpatch_elf_linux_x8664(self): ql = Qiling(["../examples/rootfs/x8664_linux/bin/patch_test.bin"], "../examples/rootfs/x8664_linux") - ql.patch(0x0000000000000575, b'qiling\x00', file_name = b'libpatch_test.so') + ql.patch(0x0000000000000575, b'qiling\x00', target='libpatch_test.so') ql.run() del ql From ede65d533e387cddc751a38483ed8ef5292a8d94 Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 27 Jan 2022 23:11:28 +0200 Subject: [PATCH 079/406] Fix default env on qltool --- qltool | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/qltool b/qltool index 2496fc86d..108f0843e 100755 --- a/qltool +++ b/qltool @@ -28,15 +28,13 @@ def read_file(fname: str): class __arg_env(argparse.Action): def __call__(self, parser, namespace, values, option_string): - env = {} - if os.path.exists(values): with open(values, 'rb') as f: env = pickle.load(f) else: env = ast.literal_eval(values) - setattr(namespace, self.dest, env) + setattr(namespace, self.dest, env or {}) class __arg_verbose(argparse.Action): def __call__(self, parser, namespace, values, option_string): @@ -200,7 +198,7 @@ if __name__ == '__main__': # set common options comm_parser.add_argument('-v', '--verbose', choices=verbose_map, default=QL_VERBOSE.DEFAULT, action=__arg_verbose, help='set verbosity level') - comm_parser.add_argument('--env', metavar="FILE", action=__arg_env, help="pickle file containing an environment dictionary") + comm_parser.add_argument('--env', metavar="FILE", action=__arg_env, default={}, help="pickle file containing an environment dictionary") comm_parser.add_argument('-g', '--gdb', nargs='?', metavar='SERVER:PORT', const='gdb', help='enable gdb server') comm_parser.add_argument('--qdb', action='store_true', help='attach Qdb at entry point, it\'s MIPS, ARM(THUMB) supported only for now') comm_parser.add_argument('--rr', action='store_true', help='switch on record and replay feature in qdb, only works with --qdb') From e04fd2ff78a67be3bc677e86af50f7ced25d8b09 Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 30 Jan 2022 12:39:51 +0200 Subject: [PATCH 080/406] Pythonize and annotate QlFsMapper --- qiling/os/mapper.py | 106 ++++++++++++++++++++++++-------------------- qiling/os/os.py | 8 +++- 2 files changed, 64 insertions(+), 50 deletions(-) diff --git a/qiling/os/mapper.py b/qiling/os/mapper.py index e5e9382dd..0b0bb3caf 100644 --- a/qiling/os/mapper.py +++ b/qiling/os/mapper.py @@ -4,11 +4,11 @@ # import inspect +import os +from typing import Any, MutableMapping, Union - +from .path import QlPathManager from .filestruct import ql_file -from .utils import QlOsUtils - # All mapped objects should inherit this class. # Note this object is compatible with ql_file. @@ -58,76 +58,86 @@ def name(self): raise NotImplementedError("QlFsMappedObject property not implemented: name") class QlFsMapper: - - def __init__(self, ql): - self._mapping = {} - self._ql = ql - - @property - def ql(self): - return self._ql - def _open_mapping_ql_file(self, ql_path, openflags, openmode): + def __init__(self, path: QlPathManager): + self._mapping: MutableMapping[str, Any] = {} + self.path = path + + def _open_mapping_ql_file(self, ql_path: str, openflags: int, openmode: int): real_dest = self._mapping[ql_path] + if isinstance(real_dest, str): - qlfile = ql_file.open(real_dest, openflags, openmode) - return qlfile + obj = ql_file.open(real_dest, openflags, openmode) + elif inspect.isclass(real_dest): - new_instance = real_dest() - return new_instance + obj = real_dest() + else: - return real_dest - - def _open_mapping(self, ql_path, openmode): + obj = real_dest + + return obj + + def _open_mapping(self, ql_path: str, openmode: str): real_dest = self._mapping[ql_path] + if isinstance(real_dest, str): - f = open(real_dest, openmode) - return f + obj = open(real_dest, openmode) + elif inspect.isclass(real_dest): - new_instance = real_dest() - return new_instance + obj = real_dest() + else: - return real_dest + obj = real_dest - def has_mapping(self, fm): + return obj + + def has_mapping(self, fm: str) -> bool: return fm in self._mapping - def mapping_count(self): + def mapping_count(self) -> int: return len(self._mapping) - def open_ql_file(self, path, openflags, openmode, dir_fd=None): + def open_ql_file(self, path: str, openflags: int, openmode: int, dir_fd: int = None): if self.has_mapping(path): - self.ql.log.info(f"mapping {path}") return self._open_mapping_ql_file(path, openflags, openmode) - else: - if dir_fd: - return ql_file.open(path, openflags, openmode, dir_fd=dir_fd) - real_path = self.ql.os.path.transform_to_real_path(path) - return ql_file.open(real_path, openflags, openmode) + if dir_fd: + return ql_file.open(path, openflags, openmode, dir_fd=dir_fd) - def open(self, path, openmode): + real_path = self.path.transform_to_real_path(path) + return ql_file.open(real_path, openflags, openmode) + + def open(self, path: str, openmode: str): if self.has_mapping(path): - self.ql.log.info(f"mapping {path}") return self._open_mapping(path, openmode) - else: - real_path = self.ql.os.path.transform_to_real_path(path) - return open(real_path, openmode) - def _parse_path(self, p): - if "__fspath__" in dir(p): # p is a os.PathLike object. - p = p.__fspath__() + real_path = self.path.transform_to_real_path(path) + return open(real_path, openmode) + + def _parse_path(self, p: Union[os.PathLike, str]) -> str: + fspath = getattr(p, '__fspath__', None) + + # p is an `os.PathLike` object + if fspath is not None: + p = fspath() + if isinstance(p, bytes): # os.PathLike.__fspath__ may return bytes. p = p.decode("utf-8") + return p - # ql_path: Emulated path which should be convertable to a string or a hashable object. e.g. pathlib.Path - # real_dest: Mapped object, can be a string, an object or a class. - # string: mapped path in the host machine, e.g. `/dev/urandom` -> `/dev/urandom`. - # object: mapped object, will be returned each time the emulated path has been opened. - # class: mapped class, will be used to create a new instance each time the emulated path has been opened. - def add_fs_mapping(self, ql_path, real_dest): + def add_fs_mapping(self, ql_path: Union[os.PathLike, str], real_dest: Any) -> None: + """Map an object to Qiling emulated file system. + + Args: + ql_path: Emulated path which should be convertable to a string or a hashable object. e.g. pathlib.Path + real_dest: Mapped object, can be a string, an object or a class. + string: mapped path in the host machine, e.g. '/dev/urandom' -> '/dev/urandom' + object: mapped object, will be returned each time the emulated path has been opened + class: mapped class, will be used to create a new instance each time the emulated path has been opened + """ + ql_path = self._parse_path(ql_path) real_dest = self._parse_path(real_dest) + self._mapping[ql_path] = real_dest - \ No newline at end of file diff --git a/qiling/os/os.py b/qiling/os/os.py index 19edb0673..5709d64ac 100644 --- a/qiling/os/os.py +++ b/qiling/os/os.py @@ -31,13 +31,17 @@ def __init__(self, ql: Qiling, resolvers: Mapping[Any, Resolver] = {}): self.utils = QlOsUtils(ql) self.fcall: QlFunctionCall - self.fs_mapper = QlFsMapper(ql) self.child_processes = False self.thread_management = None self.profile = self.ql.profile - self.path = None if self.ql.baremetal else QlPathManager(ql, self.ql.profile.get("MISC", "current_path")) self.exit_code = 0 + if not ql.baremetal: + cwd = self.profile.get("MISC", "current_path") + + self.path = QlPathManager(ql, cwd) + self.fs_mapper = QlFsMapper(self.path) + self.user_defined_api = { QL_INTERCEPT.CALL : {}, QL_INTERCEPT.ENTER: {}, From 86c2ca96cf93ba4f72540b2ef5e07ab5eae88d75 Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 30 Jan 2022 12:43:34 +0200 Subject: [PATCH 081/406] Speed up QL_VERBOSE.DISASM output generation --- qiling/arch/utils.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/qiling/arch/utils.py b/qiling/arch/utils.py index 0a05a52c0..04d830559 100644 --- a/qiling/arch/utils.py +++ b/qiling/arch/utils.py @@ -9,6 +9,7 @@ from typing import Tuple from os.path import basename +from functools import lru_cache from keystone import (Ks, KS_ARCH_ARM, KS_ARCH_ARM64, KS_ARCH_MIPS, KS_ARCH_X86, KS_MODE_ARM, KS_MODE_THUMB, KS_MODE_MIPS32, KS_MODE_16, KS_MODE_32, KS_MODE_64, @@ -24,6 +25,7 @@ def __init__(self, ql: Qiling): self._disasm_hook = None self._block_hook = None + @lru_cache(maxsize=64) def get_base_and_name(self, addr: int) -> Tuple[int, str]: for begin, end, _, name, _ in self.ql.mem.map_info: if begin <= addr < end: @@ -33,7 +35,19 @@ def get_base_and_name(self, addr: int) -> Tuple[int, str]: def disassembler(self, ql: Qiling, address: int, size: int): data = ql.mem.read(address, size) - ba, name = self.get_base_and_name(address) + + # knowing that all binary sections are aligned to page boundary allows + # us to 'cheat' and search for the containing image using the aligned + # address instead of the actual one. + # + # also, the locality property determines that consequent instructions + # are most likely to reside at the same page in memory, so the containing + # page of the current instruction is probably the same as the previous + # one. + # + # both assumptions make it possible to cache the search results and pull + # them off by lru, which provides about 20% speed-up in this case + ba, name = self.get_base_and_name(ql.mem.align(address)) anibbles = ql.arch.bits // 4 From e9a0fd68be79390fbbe924418c884f11b054009a Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 30 Jan 2022 16:25:25 +0200 Subject: [PATCH 082/406] More tweaking of core initialization --- qiling/core.py | 52 +++++++++++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/qiling/core.py b/qiling/core.py index 4bf1cd94d..4e4742211 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -62,8 +62,6 @@ def __init__( ################################## self._env = env self._code = code - self._ostype = ostype - self._archtype = archtype self._multithread = multithread self._log_file_fd = None self._log_filter = None @@ -121,39 +119,49 @@ def __init__( ################# self._host = QlHost() - if type(self._archtype) is str: - self._archtype = arch_convert(self._archtype) - if type(self._ostype) is str: - self._ostype = ostype_convert(self._ostype) + if type(archtype) is str: + archtype = arch_convert(archtype) - archendian = None + if type(ostype) is str: + ostype = ostype_convert(ostype) - if self._archtype is None: - guessed_archtype, guessed_ostype, guessed_archendian = ql_guess_emu_env(self._path) + # if provided arch was invalid or not provided, guess arch and os + if archtype is None: + guessed_archtype, guessed_ostype, guessed_archendian = ql_guess_emu_env(self.path) - self._archtype = guessed_archtype + archtype = guessed_archtype - if self._ostype is None: - self._ostype = guessed_ostype + if ostype is None: + ostype = guessed_ostype if endian is None: - archendian = guessed_archendian + endian = guessed_archendian - elif self._ostype == None: - self._ostype = arch_os_convert(self._archtype) + # if arch was set but os was not, try to guess it by arch + elif ostype is None: + ostype = arch_os_convert(archtype) - if self._ostype is None or not ql_is_valid_ostype(self._ostype): - raise QlErrorOsType("Invalid OS: %s" % (self._ostype)) + # arch should have been determined by now; fail if not + if archtype is None or not ql_is_valid_arch(archtype): + raise QlErrorArch(f'Uknown or unsupported architecture: "{archtype}"') - if self._archtype is None or not ql_is_valid_arch(self._archtype): - raise QlErrorArch("Invalid ARCH: %s" % (self._archtype)) + # os should have been determined by now; fail if not + if ostype is None or not ql_is_valid_ostype(ostype): + raise QlErrorOsType(f'Unknown or unsupported operating system: "{ostype}"') # if endianess is still undetermined, set it to little-endian. # this setting is ignored for architectures with predfined endianess - if archendian is None: - archendian = QL_ENDIAN.EL + if endian is None: + endian = QL_ENDIAN.EL + + # make sure args were canonicalized successfully + assert type(archtype) is QL_ARCH + assert type(ostype) is QL_OS + assert type(endian) is QL_ENDIAN - self._arch = arch_setup(self.archtype, archendian, thumb, self) + self._arch = arch_setup(archtype, endian, thumb, self) + self._archtype = archtype + self._ostype = ostype self.uc = self.arch.uc From e5e2a0f244f8a60866589788f6e171b41e58a382 Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 30 Jan 2022 16:27:30 +0200 Subject: [PATCH 083/406] Fix OS default sekection --- qiling/const.py | 3 ++- qiling/utils.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/qiling/const.py b/qiling/const.py index 90530cb98..b10d41cab 100644 --- a/qiling/const.py +++ b/qiling/const.py @@ -89,5 +89,6 @@ def __reverse_enum(e: Type[Enum]) -> Mapping[str, Any]: } arch_os_map = { - QL_ARCH.EVM: QL_OS.EVM, + QL_ARCH.EVM : QL_OS.EVM, + QL_ARCH.CORTEX_M : QL_OS.MCU } diff --git a/qiling/utils.py b/qiling/utils.py index 6aa983f40..71cdc8b2d 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -204,7 +204,7 @@ def arch_convert(arch: str) -> Optional[QL_ARCH]: return arch_map.get(alias_map.get(arch, arch)) def arch_os_convert(arch: QL_ARCH) -> Optional[QL_OS]: - return arch_os_map.get(arch, QL_OS.MCU) + return arch_os_map.get(arch) def debugger_convert(debugger: str) -> Optional[QL_DEBUGGER]: return debugger_map.get(debugger) From 27796e56895073000830eb653ab27c3d18d59161 Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 30 Jan 2022 16:31:48 +0200 Subject: [PATCH 084/406] Add "type" static property to QlArch --- qiling/arch/arch.py | 3 ++- qiling/arch/arm.py | 3 ++- qiling/arch/arm64.py | 3 ++- qiling/arch/cortex_m.py | 3 ++- qiling/arch/evm/evm.py | 1 + qiling/arch/mips.py | 3 ++- qiling/arch/riscv.py | 3 ++- qiling/arch/riscv64.py | 2 ++ qiling/arch/x86.py | 5 ++++- 9 files changed, 19 insertions(+), 7 deletions(-) diff --git a/qiling/arch/arch.py b/qiling/arch/arch.py index f354eb30a..69a67b9c2 100644 --- a/qiling/arch/arch.py +++ b/qiling/arch/arch.py @@ -11,11 +11,12 @@ from keystone import Ks from qiling import Qiling -from qiling.const import QL_ENDIAN +from qiling.const import QL_ARCH, QL_ENDIAN from .register import QlRegisterManager from .utils import QlArchUtils class QlArch: + type: QL_ARCH bits: int def __init__(self, ql: Qiling): diff --git a/qiling/arch/arm.py b/qiling/arch/arm.py index b87baf0ac..c03d0d8c0 100644 --- a/qiling/arch/arm.py +++ b/qiling/arch/arm.py @@ -10,12 +10,13 @@ from keystone import Ks, KS_ARCH_ARM, KS_MODE_ARM, KS_MODE_THUMB, KS_MODE_BIG_ENDIAN from qiling import Qiling -from qiling.const import QL_ENDIAN from qiling.arch.arch import QlArch from qiling.arch import arm_const from qiling.arch.register import QlRegisterManager +from qiling.const import QL_ARCH, QL_ENDIAN class QlArchARM(QlArch): + type = QL_ARCH.ARM bits = 32 def __init__(self, ql: Qiling, endian: QL_ENDIAN, thumb: bool): diff --git a/qiling/arch/arm64.py b/qiling/arch/arm64.py index 1f0afcf10..0367a8189 100644 --- a/qiling/arch/arm64.py +++ b/qiling/arch/arm64.py @@ -12,9 +12,10 @@ from qiling.arch.arch import QlArch from qiling.arch import arm64_const from qiling.arch.register import QlRegisterManager -from qiling.const import QL_ENDIAN +from qiling.const import QL_ARCH, QL_ENDIAN class QlArchARM64(QlArch): + type = QL_ARCH.ARM64 bits = 64 @cached_property diff --git a/qiling/arch/cortex_m.py b/qiling/arch/cortex_m.py index d0cc45985..a9e0a64ee 100644 --- a/qiling/arch/cortex_m.py +++ b/qiling/arch/cortex_m.py @@ -15,7 +15,7 @@ from qiling.arch import cortex_m_const from qiling.arch.register import QlRegisterManager from qiling.arch.cortex_m_const import IRQ, EXC_RETURN, CONTROL, EXCP -from qiling.const import QL_ENDIAN, QL_VERBOSE +from qiling.const import QL_ARCH, QL_ENDIAN, QL_VERBOSE from qiling.exception import QlErrorNotImplemented class QlInterruptContext(ContextDecorator): @@ -60,6 +60,7 @@ def __exit__(self, *exc): self.ql.log.info('Exit from interrupt') class QlArchCORTEX_M(QlArchARM): + type = QL_ARCH.ARM bits = 32 def __init__(self, ql: Qiling): diff --git a/qiling/arch/evm/evm.py b/qiling/arch/evm/evm.py index 5d2e92430..b7479cd5b 100644 --- a/qiling/arch/evm/evm.py +++ b/qiling/arch/evm/evm.py @@ -9,6 +9,7 @@ class QlArchEVM(QlArch): + type = QL_ARCH.EVM bits = 1 def __init__(self, ql) -> None: diff --git a/qiling/arch/mips.py b/qiling/arch/mips.py index 324320985..8391becc1 100644 --- a/qiling/arch/mips.py +++ b/qiling/arch/mips.py @@ -10,12 +10,13 @@ from keystone import Ks, KS_ARCH_MIPS, KS_MODE_MIPS32, KS_MODE_BIG_ENDIAN, KS_MODE_LITTLE_ENDIAN from qiling import Qiling -from qiling.const import QL_ENDIAN from qiling.arch.arch import QlArch from qiling.arch import mips_const from qiling.arch.register import QlRegisterManager +from qiling.const import QL_ARCH, QL_ENDIAN class QlArchMIPS(QlArch): + type = QL_ARCH.MIPS bits = 32 def __init__(self, ql: Qiling, endian: QL_ENDIAN): diff --git a/qiling/arch/riscv.py b/qiling/arch/riscv.py index f9948a53a..80d614c37 100644 --- a/qiling/arch/riscv.py +++ b/qiling/arch/riscv.py @@ -13,10 +13,11 @@ from qiling.arch.register import QlRegisterManager from qiling.arch import riscv_const from qiling.arch.riscv_const import * -from qiling.const import QL_ENDIAN +from qiling.const import QL_ARCH, QL_ENDIAN from qiling.exception import QlErrorNotImplemented class QlArchRISCV(QlArch): + type = QL_ARCH.RISCV bits = 32 @cached_property diff --git a/qiling/arch/riscv64.py b/qiling/arch/riscv64.py index dcb128edb..295cf1171 100644 --- a/qiling/arch/riscv64.py +++ b/qiling/arch/riscv64.py @@ -10,11 +10,13 @@ from keystone import Ks from qiling.arch.riscv_const import * +from qiling.const import QL_ARCH from qiling.exception import QlErrorNotImplemented from .riscv import QlArchRISCV class QlArchRISCV64(QlArchRISCV): + type = QL_ARCH.RISCV64 bits = 64 @cached_property diff --git a/qiling/arch/x86.py b/qiling/arch/x86.py index 74fa4500b..a47b09dd0 100644 --- a/qiling/arch/x86.py +++ b/qiling/arch/x86.py @@ -17,7 +17,7 @@ from qiling.arch.register import QlRegisterManager from qiling.arch import x86_const from qiling.arch.x86_const import * -from qiling.const import QL_ENDIAN +from qiling.const import QL_ARCH, QL_ENDIAN from qiling.exception import QlGDTError class QlArchIntel(QlArch): @@ -33,6 +33,7 @@ def msr(self) -> QlMsrManager: return QlMsrManager(self.uc) class QlArchA8086(QlArchIntel): + type = QL_ARCH.A8086 bits = 16 @cached_property @@ -61,6 +62,7 @@ def assembler(self) -> Ks: return Ks(KS_ARCH_X86, KS_MODE_16) class QlArchX86(QlArchIntel): + type = QL_ARCH.X86 bits = 32 @cached_property @@ -92,6 +94,7 @@ def assembler(self) -> Ks: return Ks(KS_ARCH_X86, KS_MODE_32) class QlArchX8664(QlArchIntel): + type = QL_ARCH.X8664 bits = 64 @cached_property From 2ca0e5861ea56e15f1f87c36efcf4cba8a479d5e Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 30 Jan 2022 16:52:43 +0200 Subject: [PATCH 085/406] Remove archtype property from core --- qiling/core.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/qiling/core.py b/qiling/core.py index 4e4742211..2e9c2b4c7 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -160,7 +160,6 @@ def __init__( assert type(endian) is QL_ENDIAN self._arch = arch_setup(archtype, endian, thumb, self) - self._archtype = archtype self._ostype = ostype self.uc = self.arch.uc @@ -333,25 +332,6 @@ def ostype(self) -> QL_OS: """ return self._ostype - @property - def archtype(self) -> QL_ARCH: - """ The emulated architecture type. - - Note: Please pass None or one of the strings below to Qiling.__init__. - If you use shellcode, you must specify ostype and archtype manually. - - Type: int - Values: - - "x86" : x86_32 - - "x8664" : x86_64 - - "mips" : MIPS - - "arm" : ARM - - "arm64" : ARM64 - - "a8086" : 8086 - Example: Qiling(code=b"\x90", ostype="macos", archtype="x8664") - """ - return self._archtype - @property def code(self) -> bytes: """ The shellcode to execute. From f693f728607d09e1dea72b70c84c1fbea14f79f8 Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 30 Jan 2022 17:16:45 +0200 Subject: [PATCH 086/406] Adjust all archtype usages --- qiling/debugger/disassember.py | 2 +- qiling/debugger/gdb/gdb.py | 58 ++++++++++----------- qiling/debugger/gdb/utils.py | 4 +- qiling/debugger/qdb/frontend.py | 12 ++--- qiling/debugger/qdb/qdb.py | 12 ++--- qiling/debugger/qdb/utils.py | 11 ++-- qiling/extensions/idaplugin/qilingida.py | 21 +------- qiling/extensions/report/report.py | 2 +- qiling/loader/elf.py | 4 +- qiling/loader/macho.py | 4 +- qiling/loader/macho_parser/parser.py | 2 +- qiling/loader/pe.py | 38 +++++++------- qiling/loader/pe_uefi.py | 4 +- qiling/os/blob/blob.py | 2 +- qiling/os/freebsd/map_syscall.py | 2 +- qiling/os/linux/function_hook.py | 64 ++++++++++++------------ qiling/os/linux/linux.py | 20 ++++---- qiling/os/linux/map_syscall.py | 4 +- qiling/os/linux/syscall.py | 12 ++--- qiling/os/linux/thread.py | 2 +- qiling/os/macos/kernel_func.py | 4 +- qiling/os/macos/macos.py | 4 +- qiling/os/macos/map_syscall.py | 6 +-- qiling/os/macos/syscall.py | 2 +- qiling/os/posix/const_mapping.py | 10 ++-- qiling/os/posix/posix.py | 12 ++--- qiling/os/posix/syscall/fcntl.py | 6 +-- qiling/os/posix/syscall/mman.py | 4 +- qiling/os/posix/syscall/sched.py | 4 +- qiling/os/posix/syscall/socket.py | 30 +++++------ qiling/os/posix/syscall/stat.py | 32 ++++++------ qiling/os/posix/syscall/unistd.py | 4 +- qiling/os/qnx/map_msgtype.py | 2 +- qiling/os/qnx/map_syscall.py | 2 +- qiling/os/qnx/qnx.py | 6 +-- qiling/os/windows/dlls/ntdll.py | 2 +- qiling/os/windows/dlls/ntoskrnl.py | 4 +- qiling/os/windows/structs.py | 8 +-- qiling/os/windows/thread.py | 8 +-- qiling/os/windows/utils.py | 16 +++--- qiling/os/windows/windows.py | 6 +-- qiling/utils.py | 2 +- 42 files changed, 217 insertions(+), 237 deletions(-) diff --git a/qiling/debugger/disassember.py b/qiling/debugger/disassember.py index 1b1300b40..ffcbd5017 100644 --- a/qiling/debugger/disassember.py +++ b/qiling/debugger/disassember.py @@ -30,7 +30,7 @@ def disasm(ql, address, size): return md.disasm(ql.mem.read(address, size), address) disasm_result = [] - if self.ql._archtype == QL_ARCH.X86: + if self.ql.arch.type == QL_ARCH.X86: BASE = int(self.ql.profile.get("OS32", "load_address"), 16) seg_start = 0x0 seg_end = 0x0 diff --git a/qiling/debugger/gdb/gdb.py b/qiling/debugger/gdb/gdb.py index b4cb437f9..ebd2c91e0 100644 --- a/qiling/debugger/gdb/gdb.py +++ b/qiling/debugger/gdb/gdb.py @@ -179,12 +179,12 @@ def gdbqmark_converter(arch): } return adapter.get(arch) - idhex, spid, pcid = gdbqmark_converter(self.ql.archtype) + idhex, spid, pcid = gdbqmark_converter(self.ql.arch.type) sp = self.addr_to_str(self.ql.arch.regs.arch_sp) pc = self.addr_to_str(self.ql.arch.regs.arch_pc) nullfill = "0" * int(self.ql.arch.bits / 4) - if self.ql.archtype== QL_ARCH.MIPS: + if self.ql.arch.type == QL_ARCH.MIPS: if self.ql.arch.endian == QL_ENDIAN.EB: sp = self.addr_to_str(self.ql.arch.regs.arch_sp, endian ="little") pc = self.addr_to_str(self.ql.arch.regs.arch_pc, endian ="little") @@ -208,19 +208,19 @@ def handle_c(subcmd): def handle_g(subcmd): s = '' - if self.ql.archtype== QL_ARCH.A8086: + if self.ql.arch.type == QL_ARCH.A8086: for reg in self.tables[QL_ARCH.A8086][:16]: r = self.ql.arch.regs.read(reg) tmp = self.addr_to_str(r) s += tmp - elif self.ql.archtype== QL_ARCH.X86: + elif self.ql.arch.type == QL_ARCH.X86: for reg in self.tables[QL_ARCH.X86][:16]: r = self.ql.arch.regs.read(reg) tmp = self.addr_to_str(r) s += tmp - elif self.ql.archtype== QL_ARCH.X8664: + elif self.ql.arch.type == QL_ARCH.X8664: for reg in self.tables[QL_ARCH.X8664][:24]: r = self.ql.arch.regs.read(reg) if self.ql.arch.reg_bits(reg) == 64: @@ -229,7 +229,7 @@ def handle_g(subcmd): tmp = self.addr_to_str(r, short = True) s += tmp - elif self.ql.archtype == QL_ARCH.ARM: + elif self.ql.arch.type == QL_ARCH.ARM: # r0-r12,sp,lr,pc,cpsr ,see https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=gdb/arch/arm.h;h=fa589fd0582c0add627a068e6f4947a909c45e86;hb=HEAD#l127 @@ -240,13 +240,13 @@ def handle_g(subcmd): tmp = self.addr_to_str(r) s += tmp - elif self.ql.archtype == QL_ARCH.ARM64: + elif self.ql.arch.type == QL_ARCH.ARM64: for reg in self.tables[QL_ARCH.ARM64][:33]: r = self.ql.arch.regs.read(reg) tmp = self.addr_to_str(r) s += tmp - elif self.ql.archtype == QL_ARCH.MIPS: + elif self.ql.arch.type == QL_ARCH.MIPS: for reg in self.tables[QL_ARCH.MIPS][:38]: r = self.ql.arch.regs.read(reg) if self.ql.arch.endian == QL_ENDIAN.EL: @@ -261,14 +261,14 @@ def handle_g(subcmd): def handle_G(subcmd): count = 0 - if self.ql.archtype == QL_ARCH.A8086: + if self.ql.arch.type == QL_ARCH.A8086: for i in range(0, len(subcmd), 8): reg_data = subcmd[i:i+7] reg_data = int(reg_data, 16) self.ql.arch.regs.write(self.tables[QL_ARCH.A8086][count], reg_data) count += 1 - elif self.ql.archtype == QL_ARCH.X86: + elif self.ql.arch.type == QL_ARCH.X86: for i in range(0, len(subcmd), 8): reg_data = subcmd[i:i+7] reg_data = int(reg_data, 16) @@ -276,7 +276,7 @@ def handle_G(subcmd): count += 1 - elif self.ql.archtype == QL_ARCH.X8664: + elif self.ql.arch.type == QL_ARCH.X8664: for i in range(0, 17*16, 16): reg_data = subcmd[i:i+15] reg_data = int(reg_data, 16) @@ -288,21 +288,21 @@ def handle_G(subcmd): self.ql.arch.regs.write(self.tables[QL_ARCH.X8664][count], reg_data) count += 1 - elif self.ql.archtype == QL_ARCH.ARM: + elif self.ql.arch.type == QL_ARCH.ARM: for i in range(0, len(subcmd), 8): reg_data = subcmd[i:i + 7] reg_data = int(reg_data, 16) self.ql.arch.regs.write(self.tables[QL_ARCH.ARM][count], reg_data) count += 1 - elif self.ql.archtype == QL_ARCH.ARM64: + elif self.ql.arch.type == QL_ARCH.ARM64: for i in range(0, len(subcmd), 16): reg_data = subcmd[i:i+15] reg_data = int(reg_data, 16) self.ql.arch.regs.write(self.tables[QL_ARCH.ARM64][count], reg_data) count += 1 - elif self.ql.archtype == QL_ARCH.MIPS: + elif self.ql.arch.type == QL_ARCH.MIPS: for i in range(0, len(subcmd), 8): reg_data = subcmd[i:i+7] reg_data = int(reg_data, 16) @@ -353,21 +353,21 @@ def handle_p(subcmd): reg_index = int(subcmd, 16) reg_value = None try: - if self.ql.archtype== QL_ARCH.A8086: + if self.ql.arch.type == QL_ARCH.A8086: if reg_index <= 9: reg_value = self.ql.arch.regs.read(self.tables[QL_ARCH.A8086][reg_index-1]) else: reg_value = 0 reg_value = self.addr_to_str(reg_value) - elif self.ql.archtype== QL_ARCH.X86: + elif self.ql.arch.type == QL_ARCH.X86: if reg_index <= 24: reg_value = self.ql.arch.regs.read(self.tables[QL_ARCH.X86][reg_index-1]) else: reg_value = 0 reg_value = self.addr_to_str(reg_value) - elif self.ql.archtype== QL_ARCH.X8664: + elif self.ql.arch.type == QL_ARCH.X8664: if reg_index <= 32: reg_value = self.ql.arch.regs.read(self.tables[QL_ARCH.X8664][reg_index-1]) else: @@ -377,21 +377,21 @@ def handle_p(subcmd): elif 17 < reg_index: reg_value = self.addr_to_str(reg_value, short = True) - elif self.ql.archtype== QL_ARCH.ARM: + elif self.ql.arch.type == QL_ARCH.ARM: if reg_index < 26: reg_value = self.ql.arch.regs.read(self.tables[QL_ARCH.ARM][reg_index - 1]) else: reg_value = 0 reg_value = self.addr_to_str(reg_value) - elif self.ql.archtype== QL_ARCH.ARM64: + elif self.ql.arch.type == QL_ARCH.ARM64: if reg_index <= 32: reg_value = self.ql.arch.regs.read(self.tables[QL_ARCH.ARM64][reg_index - 1]) else: reg_value = 0 reg_value = self.addr_to_str(reg_value) - elif self.ql.archtype== QL_ARCH.MIPS: + elif self.ql.arch.type == QL_ARCH.MIPS: if reg_index <= 37: reg_value = self.ql.arch.regs.read(self.tables[QL_ARCH.MIPS][reg_index - 1]) else: @@ -413,19 +413,19 @@ def handle_p(subcmd): def handle_P(subcmd): reg_index, reg_data = subcmd.split('=') reg_index = int(reg_index, 16) - reg_name = self.tables[self.ql.archtype][reg_index] + reg_name = self.tables[self.ql.arch.type][reg_index] - if self.ql.archtype== QL_ARCH.A8086: + if self.ql.arch.type == QL_ARCH.A8086: reg_data = int(reg_data, 16) reg_data = int.from_bytes(struct.pack(' Write to register %s with %x\n" % (self.tables[self.ql.archtype][reg_index], reg_data)) + self.ql.log.info("gdb> Write to register %s with %x\n" % (self.tables[self.ql.arch.type][reg_index], reg_data)) self.send('OK') @@ -492,7 +492,7 @@ def handle_q(subcmd): elif subcmd.startswith('Xfer:features:read'): xfercmd_file = subcmd.split(':')[3] xfercmd_abspath = os.path.dirname(os.path.abspath(__file__)) - xml_folder = arch_convert_str(self.ql.archtype).lower() + xml_folder = arch_convert_str(self.ql.arch.type).lower() xfercmd_file = os.path.join(xfercmd_abspath,"xml",xml_folder, xfercmd_file) if os.path.exists(xfercmd_file) and self.ql.ostype is not QL_OS.WINDOWS: diff --git a/qiling/debugger/gdb/utils.py b/qiling/debugger/gdb/utils.py index 093f7522d..4c8a5247c 100644 --- a/qiling/debugger/gdb/utils.py +++ b/qiling/debugger/gdb/utils.py @@ -51,7 +51,7 @@ def dbg_hook(self, ql: Qiling, address: int, size: int): """ try: - if ql.archtype == QL_ARCH.ARM: + if ql.arch.type == QL_ARCH.ARM: if ql.arch.is_thumb: address += 1 @@ -106,7 +106,7 @@ def resume_emu(self, address: int = None, skip_bp: int = 0): """ if address is not None: - if self.ql.archtype == QL_ARCH.ARM: + if self.ql.arch.type == QL_ARCH.ARM: if self.ql.arch.is_thumb: address += 1 diff --git a/qiling/debugger/qdb/frontend.py b/qiling/debugger/qdb/frontend.py index 87d48538d..867c4abb6 100644 --- a/qiling/debugger/qdb/frontend.py +++ b/qiling/debugger/qdb/frontend.py @@ -56,10 +56,10 @@ def extract_count(t): else: rest = _args - if ql.archtype == QL_ARCH.ARM: + if ql.arch.type == QL_ARCH.ARM: rest = rest.replace("fp", "r11") - elif ql.archtype == QL_ARCH.MIPS: + elif ql.arch.type == QL_ARCH.MIPS: rest = rest.replace("fp", "s8") # for supporting addition of register with constant value @@ -166,7 +166,7 @@ def context_reg(ql: Qiling, saved_states: Optional[Mapping[str, int]] = None, /, _colors = (color.DARKCYAN, color.BLUE, color.RED, color.YELLOW, color.GREEN, color.PURPLE, color.CYAN, color.WHITE) - if ql.archtype == QL_ARCH.MIPS: + if ql.arch.type == QL_ARCH.MIPS: _cur_regs.update({"fp": _cur_regs.pop("s8")}) @@ -192,7 +192,7 @@ def context_reg(ql: Qiling, saved_states: Optional[Mapping[str, int]] = None, /, print(lines.format(*_cur_regs.values())) - elif ql.archtype in (QL_ARCH.ARM, QL_ARCH.CORTEX_M): + elif ql.arch.type in (QL_ARCH.ARM, QL_ARCH.CORTEX_M): _cur_regs.update({"sl": _cur_regs.pop("r10")}) @@ -200,7 +200,7 @@ def context_reg(ql: Qiling, saved_states: Optional[Mapping[str, int]] = None, /, _cur_regs.update({"fp": _cur_regs.pop("r11")}) regs_in_row = 4 - if ql.archtype == QL_ARCH.CORTEX_M: + if ql.arch.type == QL_ARCH.CORTEX_M: regs_in_row = 3 # for re-order @@ -234,7 +234,7 @@ def context_reg(ql: Qiling, saved_states: Optional[Mapping[str, int]] = None, /, print(lines.format(*_cur_regs.values())) print(color.GREEN, "[{cpsr[mode]} mode], Thumb: {cpsr[thumb]}, FIQ: {cpsr[fiq]}, IRQ: {cpsr[irq]}, NEG: {cpsr[neg]}, ZERO: {cpsr[zero]}, Carry: {cpsr[carry]}, Overflow: {cpsr[overflow]}".format(cpsr=get_arm_flags(ql.arch.regs.cpsr)), color.END, sep="") - if ql.archtype != QL_ARCH.CORTEX_M: + if ql.arch.type != QL_ARCH.CORTEX_M: # context render for Stack, skip this for CORTEX_M with context_printer(ql, "[ STACK ]", ruler="─"): diff --git a/qiling/debugger/qdb/qdb.py b/qiling/debugger/qdb/qdb.py index 98914d9c5..628665d71 100644 --- a/qiling/debugger/qdb/qdb.py +++ b/qiling/debugger/qdb/qdb.py @@ -45,7 +45,7 @@ def dbg_hook(self: QlQdb, init_hook: str): self.cur_addr = self.ql.loader.entry_point - if self.ql.archtype == QL_ARCH.CORTEX_M: + if self.ql.arch.type == QL_ARCH.CORTEX_M: self._run() else: @@ -113,7 +113,7 @@ def _run(self: Qldbg, address: int = 0, end: int = 0, count: int = 0) -> None: if not address: address = self.cur_addr - if self.ql.archtype == QL_ARCH.CORTEX_M and self.ql.count != 0: + if self.ql.arch.type == QL_ARCH.CORTEX_M and self.ql.count != 0: while self.ql.count: @@ -130,7 +130,7 @@ def _run(self: Qldbg, address: int = 0, end: int = 0, count: int = 0) -> None: return - if self.ql.archtype in (QL_ARCH.ARM, QL_ARCH.CORTEX_M) and is_thumb(self.ql.arch.regs.cpsr): + if self.ql.arch.type in (QL_ARCH.ARM, QL_ARCH.CORTEX_M) and is_thumb(self.ql.arch.regs.cpsr): address |= 1 self.ql.emu_start(begin=address, end=end, count=count) @@ -227,7 +227,7 @@ def do_step(self: QlQdb, *args) -> Optional[bool]: if next_stop is CODE_END: return True - if self.ql.archtype == QL_ARCH.CORTEX_M: + if self.ql.arch.type == QL_ARCH.CORTEX_M: self.ql.arch.step() self.ql.count -= 1 @@ -244,7 +244,7 @@ def set_breakpoint(self: QlQdb, address: int, is_temp: bool = False) -> None: bp = TempBreakpoint(address) if is_temp else Breakpoint(address) - if self.ql.archtype != QL_ARCH.CORTEX_M: + if self.ql.arch.type != QL_ARCH.CORTEX_M: # skip hook_address for cortex_m bp.hook = self.ql.hook_address(self._bp_handler, address) @@ -263,7 +263,7 @@ def do_start(self: QlQdb, *args) -> None: restore qiling instance context to initial state """ - if self.ql.archtype != QL_ARCH.CORTEX_M: + if self.ql.arch.type != QL_ARCH.CORTEX_M: self.ql.restore(self._init_state) self.do_context() diff --git a/qiling/debugger/qdb/utils.py b/qiling/debugger/qdb/utils.py index 08e797075..329403207 100644 --- a/qiling/debugger/qdb/utils.py +++ b/qiling/debugger/qdb/utils.py @@ -7,6 +7,7 @@ from typing import Callable, Optional, Mapping from functools import partial +from qiling import Qiling from qiling.const import * CODE_END = True @@ -14,7 +15,7 @@ def dump_regs(ql: Qiling) -> Mapping[str, int]: - if ql.archtype == QL_ARCH.MIPS: + if ql.arch.type == QL_ARCH.MIPS: _reg_order = ( "gp", "at", "v0", "v1", @@ -27,7 +28,7 @@ def dump_regs(ql: Qiling) -> Mapping[str, int]: "ra", "k0", "k1", "pc", ) - elif ql.archtype == QL_ARCH.ARM: + elif ql.arch.type == QL_ARCH.ARM: _reg_order = ( "r0", "r1", "r2", "r3", @@ -36,7 +37,7 @@ def dump_regs(ql: Qiling) -> Mapping[str, int]: "r12", "sp", "lr", "pc", ) - elif ql.archtype == QL_ARCH.CORTEX_M: + elif ql.arch.type == QL_ARCH.CORTEX_M: _reg_order = ( "r0", "r1", "r2", "r3", @@ -108,7 +109,7 @@ def handle_bnj(ql: Qiling, cur_addr: str) -> Callable[[Qiling, str], int]: QL_ARCH.MIPS : handle_bnj_mips, QL_ARCH.ARM : handle_bnj_arm, QL_ARCH.CORTEX_M : handle_bnj_arm, - }.get(ql.archtype)(ql, cur_addr) + }.get(ql.arch.type)(ql, cur_addr) def get_cpsr(bits: int) -> (bool, bool, bool, bool): @@ -141,7 +142,7 @@ def _read_inst(ql: Qiling, addr: int) -> int: result = ql.mem.read(addr, 4) - if ql.archtype in (QL_ARCH.ARM, QL_ARCH.CORTEX_M): + if ql.arch.type in (QL_ARCH.ARM, QL_ARCH.CORTEX_M): if is_thumb(ql.arch.regs.cpsr): first_two = ql.unpack16(ql.mem.read(addr, 2)) diff --git a/qiling/extensions/idaplugin/qilingida.py b/qiling/extensions/idaplugin/qilingida.py index e2d6d7cec..954b9948d 100644 --- a/qiling/extensions/idaplugin/qilingida.py +++ b/qiling/extensions/idaplugin/qilingida.py @@ -487,10 +487,6 @@ def finish_populating_widget_popup(self, widget, popup): return True def SetReg(self, addr, ql:Qiling): - arch = ql.archtype - if arch == "": - return - #clear self.ClearLines() @@ -551,10 +547,6 @@ def SetStack(self, ql:Qiling): self.AddLine(COLSTR(' Stack at 0x%X' % sp, SCOLOR_AUTOCMT)) self.AddLine('') - arch = ql.archtype - if arch == "": - return - reg_bit_size = ql.arch.bits reg_byte_size = reg_bit_size // 8 value_format = '% .16X' if reg_bit_size == 64 else '% .8X' @@ -831,18 +823,7 @@ def get_reg_map(ql:Qiling): QL_ARCH.MIPS : list({**mips_reg_map}.keys()), } - if ql.archtype == QL_ARCH.X86: - return tables[QL_ARCH.X86] - elif ql.archtype == QL_ARCH.X8664: - return tables[QL_ARCH.X8664] - elif ql.archtype == QL_ARCH.ARM: - return tables[QL_ARCH.ARM] - elif ql.archtype == QL_ARCH.ARM64: - return tables[QL_ARCH.ARM64] - elif ql.archtype == QL_ARCH.MIPS: - return tables[QL_ARCH.MIPS] - else: - return [] + return tables.get(ql.arch.type, []) @staticmethod def url_download(url): diff --git a/qiling/extensions/report/report.py b/qiling/extensions/report/report.py index 91b022a9f..f7e4f1ef2 100644 --- a/qiling/extensions/report/report.py +++ b/qiling/extensions/report/report.py @@ -12,7 +12,7 @@ class Report: def __init__(self, ql): self.filename = ql.argv self.rootfs = ql.rootfs - self.arch = list(arch_map.keys())[list(arch_map.values()).index(ql.archtype)] + self.arch = list(arch_map.keys())[list(arch_map.values()).index(ql.arch.type)] self.os = list(os_map.keys())[list(os_map.values()).index(ql.ostype)] self.env = ql.env self.strings = set() diff --git a/qiling/loader/elf.py b/qiling/loader/elf.py index 79141d53f..12a339471 100644 --- a/qiling/loader/elf.py +++ b/qiling/loader/elf.py @@ -216,7 +216,7 @@ def load_with_ld(self, elffile: ELFFile, stack_addr: int, load_address: int, arg # # this workaround unifies such "overlapping" segments, which may apply more permissive # protection flags to that memory region. - if self.ql.archtype == QL_ARCH.ARM64: + if self.ql.arch.type == QL_ARCH.ARM64: load_regions[-1] = (prev_lbound, ubound, prev_perms | perms) continue @@ -404,7 +404,7 @@ def __push_str(top: int, s: str) -> int: self.skip_exit_check = (self.elf_entry != self.entry_point) # map vsyscall section for some specific needs - if self.ql.archtype == QL_ARCH.X8664 and self.ql.ostype == QL_OS.LINUX: + if self.ql.arch.type == QL_ARCH.X8664 and self.ql.ostype == QL_OS.LINUX: _vsyscall_addr = int(self.profile.get('vsyscall_address'), 0) _vsyscall_size = int(self.profile.get('vsyscall_size'), 0) diff --git a/qiling/loader/macho.py b/qiling/loader/macho.py index 97e21d348..5c0e5c21a 100644 --- a/qiling/loader/macho.py +++ b/qiling/loader/macho.py @@ -27,7 +27,7 @@ # commpage is a shared mem space which is in a static address def load_commpage(ql): - if ql.archtype == QL_ARCH.X8664: + if ql.arch.type == QL_ARCH.X8664: COMM_PAGE_START_ADDRESS = X8664_COMM_PAGE_START_ADDRESS else: COMM_PAGE_START_ADDRESS = ARM64_COMM_PAGE_START_ADDRESS @@ -420,7 +420,7 @@ def loadMacho(self, depth=0, isdyld=False): self.load_address = self.macho_entry # load_commpage not wroking with ARM64, yet - if self.ql.archtype== QL_ARCH.X8664: + if self.ql.arch.type == QL_ARCH.X8664: load_commpage(self.ql) return self.proc_entry diff --git a/qiling/loader/macho_parser/parser.py b/qiling/loader/macho_parser/parser.py index a8b0c29b5..2b38914cb 100644 --- a/qiling/loader/macho_parser/parser.py +++ b/qiling/loader/macho_parser/parser.py @@ -20,7 +20,7 @@ def __init__(self, ql, path, arch= None): self.ql = ql self.binary_file = self.readFile(path) self.raw_data = self.binary_file - self.archtype = ql.archtype + self.archtype = ql.arch.type self.parseFile() self.page_zero_size = 0 self.header_address = 0x0 diff --git a/qiling/loader/pe.py b/qiling/loader/pe.py index 5d235339f..d8ddd2568 100644 --- a/qiling/loader/pe.py +++ b/qiling/loader/pe.py @@ -222,7 +222,7 @@ def set_cmdline(self, name, address, memory): return cmdline_entry def init_tib(self): - if self.ql.archtype == QL_ARCH.X86: + if self.ql.arch.type == QL_ARCH.X86: teb_addr = self.structure_last_addr else: gs = self.structure_last_addr @@ -243,7 +243,7 @@ def init_tib(self): self.ql.mem.write(teb_addr, teb_data.bytes()) self.structure_last_addr += teb_size - if self.ql.archtype == QL_ARCH.X8664: + if self.ql.arch.type == QL_ARCH.X8664: # TEB self.ql.mem.write(gs + 0x30, self.ql.pack64(teb_addr)) # PEB @@ -370,9 +370,9 @@ def init_driver_object(self): driver_object_addr = self.structure_last_addr self.ql.log.info("Driver object addr is 0x%x" %driver_object_addr) - if self.ql.archtype == QL_ARCH.X86: + if self.ql.arch.type == QL_ARCH.X86: self.driver_object = DRIVER_OBJECT32(self.ql, driver_object_addr) - elif self.ql.archtype == QL_ARCH.X8664: + elif self.ql.arch.type == QL_ARCH.X8664: self.driver_object = DRIVER_OBJECT64(self.ql, driver_object_addr) driver_object_size = ctypes.sizeof(self.driver_object) @@ -386,9 +386,9 @@ def init_registry_path(self): regitry_path_addr = self.structure_last_addr self.ql.log.info("Registry path addr is 0x%x" %regitry_path_addr) - if self.ql.archtype == QL_ARCH.X86: + if self.ql.arch.type == QL_ARCH.X86: regitry_path_data = UNICODE_STRING32(0, 0, regitry_path_addr) - elif self.ql.archtype == QL_ARCH.X8664: + elif self.ql.arch.type == QL_ARCH.X8664: regitry_path_data = UNICODE_STRING64(0, 0, regitry_path_addr) regitry_path_size = ctypes.sizeof(regitry_path_data) @@ -402,9 +402,9 @@ def init_eprocess(self): self.ql.log.info("EPROCESS is is 0x%x" %addr) - if self.ql.archtype == QL_ARCH.X86: + if self.ql.arch.type == QL_ARCH.X86: self.eprocess_object = EPROCESS32(self.ql, addr) - elif self.ql.archtype == QL_ARCH.X8664: + elif self.ql.arch.type == QL_ARCH.X8664: self.eprocess_object = EPROCESS64(self.ql, addr) size = ctypes.sizeof(self.eprocess_object) @@ -420,9 +420,9 @@ def init_ki_user_shared_data(self): struct information: https://doxygen.reactos.org/d8/dae/modules_2rostests_2winetests_2ntdll_2time_8c_source.html ''' - if self.ql.archtype == QL_ARCH.X86: + if self.ql.arch.type == QL_ARCH.X86: KI_USER_SHARED_DATA = 0xFFDF0000 - elif self.ql.archtype == QL_ARCH.X8664: + elif self.ql.arch.type == QL_ARCH.X8664: KI_USER_SHARED_DATA = 0xFFFFF78000000000 self.ql.log.info("KI_USER_SHARED_DATA is 0x%x" %KI_USER_SHARED_DATA) @@ -456,7 +456,7 @@ def run(self): self.init_dlls.append(b"ntoskrnl.exe") self.sys_dlls.append(b"ntoskrnl.exe") - if self.ql.archtype == QL_ARCH.X86: + if self.ql.arch.type == QL_ARCH.X86: self.stack_address = int(self.ql.os.profile.get("OS32", "stack_address"), 16) self.stack_size = int(self.ql.os.profile.get("OS32", "stack_size"), 16) self.image_address = int(self.ql.os.profile.get("OS32", "image_address"), 16) @@ -465,7 +465,7 @@ def run(self): self.ql.os.heap_base_address = int(self.ql.os.profile.get("OS32", "heap_address"), 16) self.ql.os.heap_base_size = int(self.ql.os.profile.get("OS32", "heap_size"), 16) self.structure_last_addr = FS_SEGMENT_ADDR - elif self.ql.archtype == QL_ARCH.X8664: + elif self.ql.arch.type == QL_ARCH.X8664: self.stack_address = int(self.ql.os.profile.get("OS64", "stack_address"), 16) self.stack_size = int(self.ql.os.profile.get("OS64", "stack_size"), 16) self.image_address = int(self.ql.os.profile.get("OS64", "image_address"), 16) @@ -531,7 +531,7 @@ def load(self): # Stack should not init at the very bottom. Will cause errors with Dlls sp = self.stack_address + self.stack_size - 0x1000 - if self.ql.archtype == QL_ARCH.X86: + if self.ql.arch.type == QL_ARCH.X86: self.ql.arch.regs.esp = sp self.ql.arch.regs.ebp = sp @@ -545,7 +545,7 @@ def load(self): self.ql.log.debug('Writing 0x01 (DLL_PROCESS_ATTACH) to [ESP+8](0x%08X)' % (sp + 0x8)) self.ql.mem.write(sp + 0x8, int(1).to_bytes(length=4, byteorder='little')) - elif self.ql.archtype == QL_ARCH.X8664: + elif self.ql.arch.type == QL_ARCH.X8664: self.ql.arch.regs.rsp = sp self.ql.arch.regs.rbp = sp @@ -578,7 +578,7 @@ def load(self): self.ql.log.debug('Setting up DriverEntry args') self.ql.stop_execution_pattern = 0xDEADC0DE - if self.ql.archtype == QL_ARCH.X86: # Win32 + if self.ql.arch.type == QL_ARCH.X86: # Win32 if not self.ql.stop_options.any: # We know that a driver will return, # so if the user did not configure stop options, write a sentinel return value @@ -586,7 +586,7 @@ def load(self): self.ql.log.debug('Writing 0x%08X (PDRIVER_OBJECT) to [ESP+4](0x%08X)' % (self.ql.loader.driver_object_address, sp+0x4)) self.ql.log.debug('Writing 0x%08X (RegistryPath) to [ESP+8](0x%08X)' % (self.ql.loader.regitry_path_address, sp+0x8)) - elif self.ql.archtype == QL_ARCH.X8664: # Win64 + elif self.ql.arch.type == QL_ARCH.X8664: # Win64 if not self.ql.stop_options.any: # We know that a driver will return, # so if the user did not configure stop options, write a sentinel return value @@ -649,7 +649,7 @@ def load(self): else: addr = self.import_address_table[dll_name][imp.ordinal] - if self.ql.archtype == QL_ARCH.X86: + if self.ql.arch.type == QL_ARCH.X86: address = self.ql.pack32(addr) else: address = self.ql.pack64(addr) @@ -661,10 +661,10 @@ def load(self): elif self.ql.code: self.filepath = b"" - if self.ql.archtype == QL_ARCH.X86: + if self.ql.arch.type == QL_ARCH.X86: self.ql.arch.regs.esp = self.stack_address + 0x3000 self.ql.arch.regs.ebp = self.ql.arch.regs.esp - elif self.ql.archtype == QL_ARCH.X8664: + elif self.ql.arch.type == QL_ARCH.X8664: self.ql.arch.regs.rsp = self.stack_address + 0x3000 self.ql.arch.regs.rbp = self.ql.arch.regs.rsp diff --git a/qiling/loader/pe_uefi.py b/qiling/loader/pe_uefi.py index e339199ec..9414ebf64 100644 --- a/qiling/loader/pe_uefi.py +++ b/qiling/loader/pe_uefi.py @@ -321,11 +321,11 @@ def run(self): ql = self.ql # intel architecture uefi implementation only - if ql.archtype not in (QL_ARCH.X86, QL_ARCH.X8664): + if ql.arch.type not in (QL_ARCH.X86, QL_ARCH.X8664): raise QlErrorArch("Unsupported architecture") # x86-64 arch only - if ql.archtype != QL_ARCH.X8664: + if ql.arch.type != QL_ARCH.X8664: raise QlErrorArch("Only 64-bit modules are supported at the moment") self.loaded_image_protocol_guid = ql.os.profile["LOADED_IMAGE_PROTOCOL"]["Guid"] diff --git a/qiling/os/blob/blob.py b/qiling/os/blob/blob.py index 22562a34c..7ab2a2079 100644 --- a/qiling/os/blob/blob.py +++ b/qiling/os/blob/blob.py @@ -28,7 +28,7 @@ def __init__(self, ql: Qiling): QL_ARCH.ARM : arm.aarch32, QL_ARCH.ARM64 : arm.aarch64, QL_ARCH.MIPS : mips.mipso32 - }[ql.archtype](ql) + }[ql.arch.type](ql) self.fcall = QlFunctionCall(ql, cc) diff --git a/qiling/os/freebsd/map_syscall.py b/qiling/os/freebsd/map_syscall.py index d9b67f6df..b09e7eeec 100644 --- a/qiling/os/freebsd/map_syscall.py +++ b/qiling/os/freebsd/map_syscall.py @@ -7,7 +7,7 @@ from qiling.os.posix.posix import SYSCALL_PREF def map_syscall(ql, syscall_num): - if ql.archtype == QL_ARCH.X8664: + if ql.arch.type == QL_ARCH.X8664: return f'{SYSCALL_PREF}{x8664_syscall_table[syscall_num]}' x8664_syscall_table = { diff --git a/qiling/os/linux/function_hook.py b/qiling/os/linux/function_hook.py index b8dfc815e..6ef9c8a21 100644 --- a/qiling/os/linux/function_hook.py +++ b/qiling/os/linux/function_hook.py @@ -68,75 +68,75 @@ def add_hook(self, cb, intercept, userdata): def get_ret_pc(self): # ARM - if self.ql.archtype== QL_ARCH.ARM: + if self.ql.arch.type == QL_ARCH.ARM: return self.ql.arch.regs.lr # MIPS32 - elif self.ql.archtype== QL_ARCH.MIPS: + elif self.ql.arch.type == QL_ARCH.MIPS: return self.ql.arch.regs.ra # ARM64 - elif self.ql.archtype== QL_ARCH.ARM64: + elif self.ql.arch.type == QL_ARCH.ARM64: return self.ql.arch.regs.x30 # X86 - elif self.ql.archtype== QL_ARCH.X86: + elif self.ql.arch.type == QL_ARCH.X86: return self.ql.unpack(self.ql.mem.read(self.ql.arch.regs.esp, self.ql.arch.pointersize)) # X8664 - elif self.ql.archtype== QL_ARCH.X8664: + elif self.ql.arch.type == QL_ARCH.X8664: return self.ql.unpack(self.ql.mem.read(self.ql.arch.regs.rsp, self.ql.arch.pointersize)) else: raise def context_fixup(self): # ARM - if self.ql.archtype== QL_ARCH.ARM: + if self.ql.arch.type == QL_ARCH.ARM: pass # MIPS32 - elif self.ql.archtype== QL_ARCH.MIPS: + elif self.ql.arch.type == QL_ARCH.MIPS: pass # ARM64 - elif self.ql.archtype== QL_ARCH.ARM64: + elif self.ql.arch.type == QL_ARCH.ARM64: pass # X86 - elif self.ql.archtype== QL_ARCH.X86: + elif self.ql.arch.type == QL_ARCH.X86: self.ql.arch.regs.esp = self.ql.arch.regs.esp + self.ql.arch.pointersize # X8664 - elif self.ql.archtype== QL_ARCH.X8664: + elif self.ql.arch.type == QL_ARCH.X8664: self.ql.arch.regs.rsp = self.ql.arch.regs.rsp + self.ql.arch.pointersize else: raise def set_ret(self, addr): # ARM - if self.ql.archtype== QL_ARCH.ARM: + if self.ql.arch.type == QL_ARCH.ARM: self.ql.arch.regs.lr = addr # MIPS32 - elif self.ql.archtype== QL_ARCH.MIPS: + elif self.ql.arch.type == QL_ARCH.MIPS: self.ql.arch.regs.ra = addr # ARM64 - elif self.ql.archtype== QL_ARCH.ARM64: + elif self.ql.arch.type == QL_ARCH.ARM64: self.ql.mem.write(self.ql.arch.regs.sp, self.ql.pack(addr)) # X86 - elif self.ql.archtype== QL_ARCH.X86: + elif self.ql.arch.type == QL_ARCH.X86: self.ql.mem.write(self.ql.arch.regs.esp, self.ql.pack(addr)) # X8664 - elif self.ql.archtype== QL_ARCH.X8664: + elif self.ql.arch.type == QL_ARCH.X8664: self.ql.mem.write(self.ql.arch.regs.rsp, self.ql.pack(addr)) else: raise def call_enter(self): - # if self.ql.archtype == QL_ARCH.ARM or self.ql.archtype == QL_ARCH.ARM64: + # if self.ql.arch.type == QL_ARCH.ARM or self.ql.arch.type == QL_ARCH.ARM64: # self.ql.arch.regs.arch_pc = self.ql.arch.regs.arch_pc + 4 next_pc = self.ql.unpack(self.ql.mem.read(self.hook_data_ptr, self.ql.arch.pointersize)) @@ -175,29 +175,29 @@ def call_enter(self): def ret(self): # ARM - if self.ql.archtype== QL_ARCH.ARM: + if self.ql.arch.type == QL_ARCH.ARM: self.ql.arch.regs.arch_pc = self.ret_pc # MIPS32 - elif self.ql.archtype== QL_ARCH.MIPS: + elif self.ql.arch.type == QL_ARCH.MIPS: self.ql.arch.regs.arch_pc = self.ret_pc # ARM64 - elif self.ql.archtype== QL_ARCH.ARM64: + elif self.ql.arch.type == QL_ARCH.ARM64: self.ql.arch.regs.arch_pc = self.ret_pc # X86 - elif self.ql.archtype== QL_ARCH.X86: + elif self.ql.arch.type == QL_ARCH.X86: self.ql.arch.regs.arch_pc = self.ret_pc # X8664 - elif self.ql.archtype== QL_ARCH.X8664: + elif self.ql.arch.type == QL_ARCH.X8664: self.ql.arch.regs.arch_pc = self.ret_pc else: raise def call_exit(self): - # if self.ql.archtype == QL_ARCH.ARM or self.ql.archtype == QL_ARCH.ARM64: + # if self.ql.arch.type == QL_ARCH.ARM or self.ql.arch.type == QL_ARCH.ARM64: # self.ql.arch.regs.arch_pc = self.ql.arch.regs.arch_pc + 4 onexit_cb = None onexit_userdata = None @@ -559,7 +559,7 @@ def __init__(self, ql, phoff, phnum, phentsize, load_base, hook_mem): self.endian = 0 if ql.arch.endian == QL_ENDIAN.EL else 1 # ARM - if self.ql.archtype == QL_ARCH.ARM: + if self.ql.arch.type == QL_ARCH.ARM: self.GLOB_DAT = 21 self.JMP_SLOT = 22 # orr r1, r1, r1 @@ -567,7 +567,7 @@ def __init__(self, ql, phoff, phnum, phentsize, load_base, hook_mem): self.add_function_hook = self.add_function_hook_relocation # MIPS32 - elif self.ql.archtype== QL_ARCH.MIPS: + elif self.ql.arch.type == QL_ARCH.MIPS: self.GLOB_DAT = 21 self.JMP_SLOT = 22 # add $t9, $t9, $zero @@ -575,7 +575,7 @@ def __init__(self, ql, phoff, phnum, phentsize, load_base, hook_mem): self.add_function_hook = self.add_function_hook_mips # ARM64 - elif self.ql.archtype== QL_ARCH.ARM64: + elif self.ql.arch.type == QL_ARCH.ARM64: self.GLOB_DAT = 1025 self.JMP_SLOT = 1026 # orr x1,x1,x1 @@ -583,7 +583,7 @@ def __init__(self, ql, phoff, phnum, phentsize, load_base, hook_mem): self.add_function_hook = self.add_function_hook_relocation # X86 - elif self.ql.archtype== QL_ARCH.X86: + elif self.ql.arch.type == QL_ARCH.X86: self.GLOB_DAT = 6 self.JMP_SLOT = 7 # nop @@ -591,14 +591,14 @@ def __init__(self, ql, phoff, phnum, phentsize, load_base, hook_mem): self.add_function_hook = self.add_function_hook_relocation # X8664 - elif self.ql.archtype== QL_ARCH.X8664: + elif self.ql.arch.type == QL_ARCH.X8664: self.GLOB_DAT = 6 self.JMP_SLOT = 7 # nop ins = b'\x90' self.add_function_hook = self.add_function_hook_relocation - elif self.ql.archtype== QL_ARCH.RISCV: + elif self.ql.arch.type == QL_ARCH.RISCV: self.GLOB_DAT = 21 self.JMP_SLOT = 22 @@ -606,7 +606,7 @@ def __init__(self, ql, phoff, phnum, phentsize, load_base, hook_mem): ins = b'\x00\x01' self.add_function_hook = self.add_function_hook_relocation - elif self.ql.archtype== QL_ARCH.RISCV64: + elif self.ql.arch.type == QL_ARCH.RISCV64: self.GLOB_DAT = 21 self.JMP_SLOT = 22 @@ -626,7 +626,7 @@ def __init__(self, ql, phoff, phnum, phentsize, load_base, hook_mem): self.rel_list += self.plt_rel self.show_relocation(self.plt_rel) - if self.ql.archtype == QL_ARCH.MIPS and self.plt_got != None and self.mips_gotsym != None and self.mips_local_gotno != None and self.mips_symtabno != None: + if self.ql.arch.type == QL_ARCH.MIPS and self.plt_got != None and self.mips_gotsym != None and self.mips_local_gotno != None and self.mips_symtabno != None: self.show_dynsym_name(self.mips_gotsym, self.mips_symtabno) self.ql.mem.map(hook_mem, 0x2000, perms=7, info="[hook_mem]") @@ -932,9 +932,9 @@ def _hook_function(self, fn, r, cb, pos, userdata): hf.enable() # if self.hook_int == False: - # if self.ql.archtype == QL_ARCH.X86 or self.ql.archtype == QL_ARCH.X8664: + # if self.ql.arch.type == QL_ARCH.X86 or self.ql.arch.type == QL_ARCH.X8664: # self.ql.hook_intno(self._hook_int, 0xa0) - # elif self.ql.archtype == QL_ARCH.ARM or self.ql.archtype == QL_ARCH.ARM64: + # elif self.ql.arch.type == QL_ARCH.ARM or self.ql.arch.type == QL_ARCH.ARM64: # self.ql.hook_intno(self._hook_int, 7) # self.hook_int = True diff --git a/qiling/os/linux/linux.py b/qiling/os/linux/linux.py index 983b78b2a..437355d1c 100644 --- a/qiling/os/linux/linux.py +++ b/qiling/os/linux/linux.py @@ -34,7 +34,7 @@ def __init__(self, ql: Qiling): QL_ARCH.MIPS : mips.mipso32, QL_ARCH.RISCV : riscv.riscv, QL_ARCH.RISCV64: riscv.riscv, - }[ql.archtype](ql) + }[ql.arch.type](ql) self.fcall = QlFunctionCall(ql, cc) @@ -45,32 +45,32 @@ def __init__(self, ql: Qiling): self.elf_mem_start = 0x0 self.load() - if self.ql.archtype == QL_ARCH.X8664: + if self.ql.arch.type == QL_ARCH.X8664: ql_x8664_set_gs(self.ql) def load(self): self.futexm = futex.QlLinuxFutexManagement() # ARM - if self.ql.archtype == QL_ARCH.ARM: + if self.ql.arch.type == QL_ARCH.ARM: self.ql.arch.enable_vfp() self.ql.hook_intno(self.hook_syscall, 2) self.thread_class = thread.QlLinuxARMThread arm_utils.init_get_tls(self.ql, self.ql.arch.arm_get_tls_addr) # MIPS32 - elif self.ql.archtype == QL_ARCH.MIPS: + elif self.ql.arch.type == QL_ARCH.MIPS: self.ql.hook_intno(self.hook_syscall, 17) self.thread_class = thread.QlLinuxMIPS32Thread # ARM64 - elif self.ql.archtype == QL_ARCH.ARM64: + elif self.ql.arch.type == QL_ARCH.ARM64: self.ql.arch.enable_vfp() self.ql.hook_intno(self.hook_syscall, 2) self.thread_class = thread.QlLinuxARM64Thread # X86 - elif self.ql.archtype == QL_ARCH.X86: + elif self.ql.arch.type == QL_ARCH.X86: self.gdtm = GDTManager(self.ql) ql_x86_register_cs(self) ql_x86_register_ds_ss_es(self) @@ -78,7 +78,7 @@ def load(self): self.thread_class = thread.QlLinuxX86Thread # X8664 - elif self.ql.archtype == QL_ARCH.X8664: + elif self.ql.arch.type == QL_ARCH.X8664: self.gdtm = GDTManager(self.ql) ql_x86_register_cs(self) ql_x86_register_ds_ss_es(self) @@ -87,12 +87,12 @@ def load(self): #self.ql.hook_insn(hook_posix_api, UC_X86_INS_SYSCALL) self.thread_class = thread.QlLinuxX8664Thread - elif self.ql.archtype == QL_ARCH.RISCV: + elif self.ql.arch.type == QL_ARCH.RISCV: self.ql.arch.enable_float() self.ql.hook_intno(self.hook_syscall, 8) self.thread_class = None - elif self.ql.archtype == QL_ARCH.RISCV64: + elif self.ql.arch.type == QL_ARCH.RISCV64: self.ql.arch.enable_float() self.ql.hook_intno(self.hook_syscall, 8) self.thread_class = None @@ -139,7 +139,7 @@ def run(self): elif self.ql.loader.elf_entry != self.ql.loader.entry_point: entry_address = self.ql.loader.elf_entry - if self.ql.archtype == QL_ARCH.ARM and entry_address & 1 == 1: + if self.ql.arch.type == QL_ARCH.ARM and entry_address & 1 == 1: entry_address -= 1 self.ql.emu_start(self.ql.loader.entry_point, entry_address, self.ql.timeout) self.ql.enable_lib_patch() diff --git a/qiling/os/linux/map_syscall.py b/qiling/os/linux/map_syscall.py index a4e4c591a..87bd3552a 100644 --- a/qiling/os/linux/map_syscall.py +++ b/qiling/os/linux/map_syscall.py @@ -20,8 +20,8 @@ def map_syscall(ql, syscall_num): QL_ARCH.MIPS: mips_syscall_table, QL_ARCH.RISCV: riscv32_syscall_table, QL_ARCH.RISCV64: riscv64_syscall_table, - }[ql.archtype] - + }[ql.arch.type] + return f'{SYSCALL_PREF}{syscall_table[syscall_num]}' arm_syscall_table = { diff --git a/qiling/os/linux/syscall.py b/qiling/os/linux/syscall.py index 86c3b13e8..6efee3ae4 100644 --- a/qiling/os/linux/syscall.py +++ b/qiling/os/linux/syscall.py @@ -21,7 +21,7 @@ class timespec(ctypes.Structure): # Temporary dirty fix. -# TODO: Pack ctypes.Structure according to ql.archtype and ql.ostype? +# TODO: Pack ctypes.Structure according to ql.arch.type and ql.ostype? class timespec32(ctypes.Structure): _fields_ = [ ("tv_sec", ctypes.c_uint32), @@ -31,7 +31,7 @@ class timespec32(ctypes.Structure): _pack_ = 4 def ql_syscall_set_thread_area(ql: Qiling, u_info_addr, *args, **kw): - if ql.archtype == QL_ARCH.X86: + if ql.arch.type == QL_ARCH.X86: GDT_ENTRY_TLS_MIN = 12 GDT_ENTRY_TLS_MAX = 14 @@ -53,7 +53,7 @@ def ql_syscall_set_thread_area(ql: Qiling, u_info_addr, *args, **kw): ql.mem.write(u_info_addr, ql.pack32(index)) return 0 - elif ql.archtype == QL_ARCH.MIPS: + elif ql.arch.type == QL_ARCH.MIPS: CONFIG3_ULR = (1 << 13) ql.arch.regs.cp0_config3 = CONFIG3_ULR ql.arch.regs.cp0_userlocal = u_info_addr @@ -65,7 +65,7 @@ def ql_syscall_set_thread_area(ql: Qiling, u_info_addr, *args, **kw): def ql_syscall_set_tls(ql, address, *args, **kw): - if ql.archtype == QL_ARCH.ARM: + if ql.arch.type == QL_ARCH.ARM: ql.arch.regs.c13_c0_3 = address ql.mem.write(ql.arch.arm_get_tls_addr + 12, ql.pack32(address)) ql.arch.regs.r0 = address @@ -75,7 +75,7 @@ def ql_syscall_clock_gettime(ql, clock_gettime_clock_id, clock_gettime_timespec, now = datetime.now().timestamp() tv_sec = floor(now) tv_nsec = floor((now - floor(now)) * 1e6) - if ql.archtype == QL_ARCH.X8664: + if ql.arch.type == QL_ARCH.X8664: tp = timespec(tv_sec= tv_sec, tv_nsec=tv_nsec) else: tp = timespec32(tv_sec= tv_sec, tv_nsec=tv_nsec) @@ -89,7 +89,7 @@ def ql_syscall_gettimeofday(ql, gettimeofday_tv, gettimeofday_tz, *args, **kw): now = datetime.now().timestamp() tv_sec = floor(now) tv_nsec = floor((now - floor(now)) * 1e6) - if ql.archtype == QL_ARCH.X8664: + if ql.arch.type == QL_ARCH.X8664: tp = timespec(tv_sec= tv_sec, tv_nsec=tv_nsec) else: tp = timespec32(tv_sec= tv_sec, tv_nsec=tv_nsec) diff --git a/qiling/os/linux/thread.py b/qiling/os/linux/thread.py index 7e27052a1..3497d1914 100644 --- a/qiling/os/linux/thread.py +++ b/qiling/os/linux/thread.py @@ -565,7 +565,7 @@ def _clear_queued_msg(self): def _prepare_lib_patch(self): if self.ql.loader.elf_entry != self.ql.loader.entry_point: entry_address = self.ql.loader.elf_entry - if self.ql.archtype == QL_ARCH.ARM and entry_address & 1 == 1: + if self.ql.arch.type == QL_ARCH.ARM and entry_address & 1 == 1: entry_address -= 1 self.main_thread = self.ql.os.thread_class.spawn(self.ql, self.ql.loader.entry_point, entry_address) self.cur_thread = self.main_thread diff --git a/qiling/os/macos/kernel_func.py b/qiling/os/macos/kernel_func.py index ff32c17c9..b967e0b40 100644 --- a/qiling/os/macos/kernel_func.py +++ b/qiling/os/macos/kernel_func.py @@ -19,10 +19,10 @@ def vm_shared_region_enter(ql): def map_commpage(ql): - if ql.archtype== QL_ARCH.X8664: + if ql.arch.type == QL_ARCH.X8664: addr_base = X8664_COMM_PAGE_START_ADDRESS addr_size = 0x100000 - elif ql.archtype== QL_ARCH.ARM64: + elif ql.arch.type == QL_ARCH.ARM64: addr_base = ARM64_COMM_PAGE_START_ADDRESS addr_size = 0x1000 ql.mem.map(addr_base, addr_size, info="[commpage]") diff --git a/qiling/os/macos/macos.py b/qiling/os/macos/macos.py index 2ec99daa8..68ea7ce3a 100644 --- a/qiling/os/macos/macos.py +++ b/qiling/os/macos/macos.py @@ -141,12 +141,12 @@ def load(self): if self.ql.code: return - if self.ql.archtype== QL_ARCH.ARM64: + if self.ql.arch.type == QL_ARCH.ARM64: self.ql.arch.enable_vfp() self.ql.hook_intno(self.hook_syscall, 2) self.ql.hook_intno(self.hook_sigtrap, 7) - elif self.ql.archtype== QL_ARCH.X8664: + elif self.ql.arch.type == QL_ARCH.X8664: self.ql.hook_insn(self.hook_syscall, UC_X86_INS_SYSCALL) self.gdtm = GDTManager(self.ql) ql_x86_register_cs(self) diff --git a/qiling/os/macos/map_syscall.py b/qiling/os/macos/map_syscall.py index e7ab802fe..36f57c077 100644 --- a/qiling/os/macos/map_syscall.py +++ b/qiling/os/macos/map_syscall.py @@ -9,15 +9,13 @@ from qiling.os.posix.posix import SYSCALL_PREF def map_syscall(ql, syscall_num): - if ql.archtype == QL_ARCH.X8664: + if ql.arch.type == QL_ARCH.X8664: if syscall_num >= 0x2000000 and syscall_num <= 0x3000000: syscall_num = syscall_num - 0x2000000 - print("") - print(syscall_num) return f'{SYSCALL_PREF}{x8664_syscall_table[syscall_num]}' - elif ql.archtype == QL_ARCH.ARM64: + elif ql.arch.type == QL_ARCH.ARM64: if syscall_num >= 0xffffffffffffff00: syscall_num = syscall_num - 0xffffffffffffff00 diff --git a/qiling/os/macos/syscall.py b/qiling/os/macos/syscall.py index dc15c9c77..861c7bca4 100644 --- a/qiling/os/macos/syscall.py +++ b/qiling/os/macos/syscall.py @@ -367,7 +367,7 @@ def ql_syscall_open_nocancel(ql, filename, flags, mode, *args, **kw): regreturn = -1 else: try: - if ql.archtype== QL_ARCH.ARM: + if ql.arch.type == QL_ARCH.ARM: mode = 0 ql.os.fd[idx] = ql.os.fs_mapper.open_ql_file(path, flags, mode) diff --git a/qiling/os/posix/const_mapping.py b/qiling/os/posix/const_mapping.py index 1fd2527c5..02756b975 100644 --- a/qiling/os/posix/const_mapping.py +++ b/qiling/os/posix/const_mapping.py @@ -50,17 +50,17 @@ def flag_mapping(flags, mapping_name, mapping_from, mapping_to): return flags if ql.ostype == QL_OS.LINUX: - if ql.archtype in (QL_ARCH.X86, QL_ARCH.X8664): + if ql.arch.type in (QL_ARCH.X86, QL_ARCH.X8664): f = linux_x86_open_flags - elif ql.archtype in (QL_ARCH.ARM, QL_ARCH.ARM64): + elif ql.arch.type in (QL_ARCH.ARM, QL_ARCH.ARM64): f = linux_arm_open_flags - elif ql.archtype == QL_ARCH.MIPS: + elif ql.arch.type == QL_ARCH.MIPS: f = linux_mips_open_flags - elif ql.archtype in (QL_ARCH.RISCV, QL_ARCH.RISCV64): + elif ql.arch.type in (QL_ARCH.RISCV, QL_ARCH.RISCV64): f = linux_riscv_open_flags elif ql.ostype == QL_OS.MACOS: - if ql.archtype in (QL_ARCH.X86, QL_ARCH.X8664): + if ql.arch.type in (QL_ARCH.X86, QL_ARCH.X8664): f = macos_x86_open_flags elif ql.ostype == QL_OS.FREEBSD: f = freebsd_x86_open_flags diff --git a/qiling/os/posix/posix.py b/qiling/os/posix/posix.py index 396c3e754..d6f9e9eea 100644 --- a/qiling/os/posix/posix.py +++ b/qiling/os/posix/posix.py @@ -86,13 +86,13 @@ def __init__(self, ql: Qiling): 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.archtype] + QL_ARCH.RISCV64: UC_RISCV_REG_A7 + }[self.ql.arch.type] # handle a special case - if (self.ql.archtype == QL_ARCH.ARM64) and (self.ql.ostype == QL_OS.MACOS): + 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.archtype == QL_ARCH.ARM) and (self.ql.ostype == QL_OS.QNX): + if (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 @@ -104,7 +104,7 @@ def __init__(self, ql: Qiling): QL_ARCH.X8664: intel64, QL_ARCH.RISCV: riscv32, QL_ARCH.RISCV64: riscv64, - }[self.ql.archtype](ql) + }[self.ql.arch.type](ql) self._fd = QlFileDes([0] * NR_OPEN) @@ -278,7 +278,7 @@ def load_syscall(self): raise QlErrorSyscallNotFound(f'Syscall not found: {syscall_name}') def get_syscall(self) -> int: - if self.ql.archtype == QL_ARCH.ARM: + if self.ql.arch.type == QL_ARCH.ARM: # When ARM-OABI # svc_imm = 0x900000 + syscall_nr # syscall_nr = svc_imm - 0x900000 diff --git a/qiling/os/posix/syscall/fcntl.py b/qiling/os/posix/syscall/fcntl.py index e7f515839..72c0f127b 100644 --- a/qiling/os/posix/syscall/fcntl.py +++ b/qiling/os/posix/syscall/fcntl.py @@ -26,7 +26,7 @@ def ql_syscall_open(ql: Qiling, filename: int, flags: int, mode: int): regreturn = -EMFILE else: try: - if ql.archtype== QL_ARCH.ARM and ql.ostype!= QL_OS.QNX: + if ql.arch.type == QL_ARCH.ARM and ql.ostype != QL_OS.QNX: mode = 0 #flags = ql_open_flag_mapping(ql, flags) @@ -62,7 +62,7 @@ def ql_syscall_creat(ql: Qiling, filename: int, mode: int): regreturn = -ENOMEM else: try: - if ql.archtype== QL_ARCH.ARM: + if ql.arch.type == QL_ARCH.ARM: mode = 0 flags = ql_open_flag_mapping(ql, flags) @@ -94,7 +94,7 @@ def ql_syscall_openat(ql: Qiling, fd: int, path: int, flags: int, mode: int): regreturn = -EMFILE else: try: - if ql.archtype== QL_ARCH.ARM: + if ql.arch.type == QL_ARCH.ARM: mode = 0 flags = ql_open_flag_mapping(ql, flags) diff --git a/qiling/os/posix/syscall/mman.py b/qiling/os/posix/syscall/mman.py index 376c10e76..23e4c2546 100755 --- a/qiling/os/posix/syscall/mman.py +++ b/qiling/os/posix/syscall/mman.py @@ -68,12 +68,12 @@ def syscall_mmap_impl(ql: Qiling, addr: int, mlen: int, prot: int, flags: int, f if ql.arch.bits == 64: fd = ql.unpack64(ql.pack64(fd)) - elif ql.archtype == QL_ARCH.MIPS: + elif ql.arch.type == QL_ARCH.MIPS: MAP_ANONYMOUS = 2048 if ver == 2: pgoffset = pgoffset * pagesize - elif ql.archtype == QL_ARCH.ARM and ql.ostype== QL_OS.QNX: + elif ql.arch.type == QL_ARCH.ARM and ql.ostype == QL_OS.QNX: MAP_ANONYMOUS = 0x00080000 fd = ql.unpack32s(ql.pack32s(fd)) diff --git a/qiling/os/posix/syscall/sched.py b/qiling/os/posix/syscall/sched.py index 95b9b9f58..69664dc6b 100644 --- a/qiling/os/posix/syscall/sched.py +++ b/qiling/os/posix/syscall/sched.py @@ -38,7 +38,7 @@ def ql_syscall_clone(ql: Qiling, flags: int, child_stack: int, parent_tidptr: in CLONE_IO = 0x80000000 # X8664 flags, child_stack, parent_tidptr, child_tidptr, newtls - if ql.archtype== QL_ARCH.X8664: + if ql.arch.type == QL_ARCH.X8664: ori_newtls = child_tidptr child_tidptr = newtls newtls = ori_newtls @@ -104,7 +104,7 @@ def ql_syscall_clone(ql: Qiling, flags: int, child_stack: int, parent_tidptr: in ql.arch.regs.arch_sp = child_stack # We have to find next pc manually for some archs since the pc is current instruction (like `syscall`). - if ql.archtype in (QL_ARCH.X8664, ): + if ql.arch.type == QL_ARCH.X8664: ql.arch.regs.arch_pc += list(ql.arch.disassembler.disasm_lite(bytes(ql.mem.read(ql.arch.regs.arch_pc, 4)), ql.arch.regs.arch_pc))[0][1] ql.log.debug(f"Fix pc for child thread to {hex(ql.arch.regs.arch_pc)}") diff --git a/qiling/os/posix/syscall/socket.py b/qiling/os/posix/syscall/socket.py index eb55d17c2..13b78b94a 100644 --- a/qiling/os/posix/syscall/socket.py +++ b/qiling/os/posix/syscall/socket.py @@ -80,7 +80,7 @@ def ql_syscall_socket(ql: Qiling, socket_domain, socket_type, socket_protocol): # ql_socket.open should use host platform based socket_type. try: emu_socket_value = socket_type - emu_socket_type = socket_type_mapping(socket_type, ql.archtype, ql.ostype) + emu_socket_type = socket_type_mapping(socket_type, ql.arch.type, ql.ostype) socket_type = getattr(socket, emu_socket_type) ql.log.debug("Convert emu_socket_type {}:{} to host platform based socket_type {}:{}".format( emu_socket_type, emu_socket_value, emu_socket_type, socket_type)) @@ -106,8 +106,8 @@ def ql_syscall_socket(ql: Qiling, socket_domain, socket_type, socket_protocol): ql.log.exception("") regreturn = -1 - socket_type = socket_type_mapping(socket_type, ql.archtype, ql.ostype) - socket_domain = socket_domain_mapping(socket_domain, ql.archtype, ql.ostype) + socket_type = socket_type_mapping(socket_type, ql.arch.type, ql.ostype) + socket_domain = socket_domain_mapping(socket_domain, ql.arch.type, ql.ostype) ql.log.debug("socket(%s, %s, %s) = %d" % (socket_domain, socket_type, socket_protocol, regreturn)) return regreturn @@ -161,7 +161,7 @@ def ql_syscall_getsockopt(ql: Qiling, sockfd, level, optname, optval_addr, optle try: emu_level = level - emu_level_name = socket_level_mapping(emu_level, ql.archtype, ql.ostype) + emu_level_name = socket_level_mapping(emu_level, ql.arch.type, ql.ostype) level = getattr(socket, emu_level_name) ql.log.debug("Convert emu_level {}:{} to host platform based level {}:{}".format( emu_level_name, emu_level, emu_level_name, level)) @@ -178,15 +178,15 @@ def ql_syscall_getsockopt(ql: Qiling, sockfd, level, optname, optval_addr, optle try: emu_opt = optname - emu_level_name = socket_level_mapping(emu_level, ql.archtype, ql.ostype) + emu_level_name = socket_level_mapping(emu_level, ql.arch.type, ql.ostype) # emu_opt_name is based on level if emu_level_name == "IPPROTO_IP": - emu_opt_name = socket_ip_option_mapping(emu_opt, ql.archtype, ql.ostype) + emu_opt_name = socket_ip_option_mapping(emu_opt, ql.arch.type, ql.ostype) else: - emu_opt_name = socket_option_mapping(emu_opt, ql.archtype, ql.ostype) + emu_opt_name = socket_option_mapping(emu_opt, ql.arch.type, ql.ostype) # Fix for mips - if ql.archtype == QL_ARCH.MIPS: + if ql.arch.type == QL_ARCH.MIPS: if emu_opt_name.endswith("_NEW") or emu_opt_name.endswith("_OLD"): emu_opt_name = emu_opt_name[:-4] @@ -223,7 +223,7 @@ def ql_syscall_setsockopt(ql: Qiling, sockfd, level, optname, optval_addr, optle try: try: emu_level = level - emu_level_name = socket_level_mapping(emu_level, ql.archtype, ql.ostype) + emu_level_name = socket_level_mapping(emu_level, ql.arch.type, ql.ostype) level = getattr(socket, emu_level_name) ql.log.debug("Convert emu_level {}:{} to host platform based level {}:{}".format( emu_level_name, emu_level, emu_level_name, level)) @@ -240,15 +240,15 @@ def ql_syscall_setsockopt(ql: Qiling, sockfd, level, optname, optval_addr, optle try: emu_opt = optname - emu_level_name = socket_level_mapping(emu_level, ql.archtype, ql.ostype) + emu_level_name = socket_level_mapping(emu_level, ql.arch.type, ql.ostype) # emu_opt_name is based on level if emu_level_name == "IPPROTO_IP": - emu_opt_name = socket_ip_option_mapping(emu_opt, ql.archtype, ql.ostype) + emu_opt_name = socket_ip_option_mapping(emu_opt, ql.arch.type, ql.ostype) else: - emu_opt_name = socket_option_mapping(emu_opt, ql.archtype, ql.ostype) + emu_opt_name = socket_option_mapping(emu_opt, ql.arch.type, ql.ostype) # Fix for mips - if ql.archtype == QL_ARCH.MIPS: + if ql.arch.type == QL_ARCH.MIPS: if emu_opt_name.endswith("_NEW") or emu_opt_name.endswith("_OLD"): emu_opt_name = emu_opt_name[:-4] @@ -293,7 +293,7 @@ def ql_syscall_shutdown(ql: Qiling, shutdown_fd, shutdown_how): def ql_syscall_bind(ql: Qiling, bind_fd, bind_addr, bind_addrlen): regreturn = 0 - if ql.archtype == QL_ARCH.X8664: + if ql.arch.type == QL_ARCH.X8664: data = ql.mem.read(bind_addr, 8) else: data = ql.mem.read(bind_addr, bind_addrlen) @@ -566,7 +566,7 @@ def ql_syscall_sendto(ql: Qiling, sendto_sockfd, sendto_buf, sendto_len, sendto_ ql.log.debug("debug sendto() start") tmp_buf = ql.mem.read(sendto_buf, sendto_len) - if ql.archtype== QL_ARCH.X8664: + if ql.arch.type== QL_ARCH.X8664: data = ql.mem.read(sendto_addr, 8) else: data = ql.mem.read(sendto_addr, sendto_addrlen) diff --git a/qiling/os/posix/syscall/stat.py b/qiling/os/posix/syscall/stat.py index 903977dbb..5bd2fe6a4 100644 --- a/qiling/os/posix/syscall/stat.py +++ b/qiling/os/posix/syscall/stat.py @@ -930,62 +930,62 @@ class QNXARMStat64(ctypes.Structure): def get_stat64_struct(ql: Qiling): if ql.arch.bits == 64: - ql.log.warning(f"Trying to stat64 on a 64bit system with {ql.ostype} and {ql.archtype}!") + ql.log.warning(f"Trying to stat64 on a 64bit system with {ql.ostype} and {ql.arch.type}!") if ql.ostype == QL_OS.LINUX: - if ql.archtype == QL_ARCH.X86: + if ql.arch.type == QL_ARCH.X86: return LinuxX86Stat64() - elif ql.archtype == QL_ARCH.MIPS: + elif ql.arch.type == QL_ARCH.MIPS: return LinuxMips32Stat64() - elif ql.archtype == QL_ARCH.ARM: + elif ql.arch.type == QL_ARCH.ARM: return LinuxARMStat64() - elif ql.archtype in (QL_ARCH.RISCV, QL_ARCH.RISCV64): + elif ql.arch.type in (QL_ARCH.RISCV, QL_ARCH.RISCV64): return LinuxRISCVStat() elif ql.ostype == QL_OS.MACOS: return MacOSStat64() elif ql.ostype == QL_OS.QNX: return QNXARMStat64() - ql.log.warning(f"Unrecognized arch && os with {ql.archtype} and {ql.ostype} for stat64! Fallback to Linux x86.") + ql.log.warning(f"Unrecognized arch && os with {ql.arch.type} and {ql.ostype} for stat64! Fallback to Linux x86.") return LinuxX86Stat64() def get_stat_struct(ql: Qiling): if ql.ostype == QL_OS.FREEBSD: - if ql.archtype == QL_ARCH.X8664 or ql.arch.bits == 64: + if ql.arch.type == QL_ARCH.X8664 or ql.arch.bits == 64: return FreeBSDX8664Stat() else: return FreeBSDX86Stat() elif ql.ostype == QL_OS.MACOS: return MacOSStat() elif ql.ostype == QL_OS.LINUX: - if ql.archtype == QL_ARCH.X8664: + if ql.arch.type == QL_ARCH.X8664: return LinuxX8664Stat() - elif ql.archtype == QL_ARCH.X86: + elif ql.arch.type == QL_ARCH.X86: return LinuxX86Stat() - elif ql.archtype == QL_ARCH.MIPS: + elif ql.arch.type == QL_ARCH.MIPS: if ql.arch.bits == 64: return LinuxMips64Stat() else: return LinuxMips32Stat() - elif ql.archtype == QL_ARCH.ARM: + elif ql.arch.type == QL_ARCH.ARM: if ql.arch.endian == QL_ENDIAN.EL: return LinuxARMStat() else: return LinuxARMEBStat() - elif ql.archtype == QL_ARCH.ARM64: + elif ql.arch.type == QL_ARCH.ARM64: if ql.arch.endian == QL_ENDIAN.EL: return LinuxARM64Stat() else: return LinuxARM64EBStat() - elif ql.archtype in (QL_ARCH.RISCV, QL_ARCH.RISCV64): + elif ql.arch.type in (QL_ARCH.RISCV, QL_ARCH.RISCV64): return LinuxRISCVStat() elif ql.ostype == QL_OS.QNX: - if ql.archtype == QL_ARCH.ARM64: + if ql.arch.type == QL_ARCH.ARM64: return QNXARM64Stat() - elif ql.archtype == QL_ARCH.ARM: + elif ql.arch.type == QL_ARCH.ARM: if ql.arch.endian == QL_ENDIAN.EL: return QNXARMStat() else: return QNXARMEBStat() - ql.log.warning(f"Unrecognized arch && os with {ql.archtype} and {ql.ostype} for stat! Fallback to Linux x86.") + ql.log.warning(f"Unrecognized arch && os with {ql.arch.type} and {ql.ostype} for stat! Fallback to Linux x86.") return LinuxX86Stat() def __common_pack_stat_struct(stat, info) -> bytes: diff --git a/qiling/os/posix/syscall/unistd.py b/qiling/os/posix/syscall/unistd.py index cbbc9a855..480fdba0b 100644 --- a/qiling/os/posix/syscall/unistd.py +++ b/qiling/os/posix/syscall/unistd.py @@ -220,7 +220,7 @@ def ql_syscall_close(ql: Qiling, fd: int): def ql_syscall_pread64(ql: Qiling, fd: int, buf: int, length: int, offt: int): # https://chromium.googlesource.com/linux-syscall-support/+/2c73abf02fd8af961e38024882b9ce0df6b4d19b # https://chromiumcodereview.appspot.com/10910222 - if ql.archtype == QL_ARCH.MIPS: + if ql.arch.type == QL_ARCH.MIPS: offt = ql.unpack64(ql.mem.read(ql.arch.regs.arch_sp + 0x10, 8)) if 0 <= fd < NR_OPEN and ql.os.fd[fd] != 0: @@ -521,7 +521,7 @@ def ql_syscall_pipe(ql: Qiling, pipefd: int): ql.os.fd[idx1] = rd ql.os.fd[idx2] = wd - if ql.archtype== QL_ARCH.MIPS: + if ql.arch.type == QL_ARCH.MIPS: ql.arch.regs.v1 = idx2 regreturn = idx1 else: diff --git a/qiling/os/qnx/map_msgtype.py b/qiling/os/qnx/map_msgtype.py index 7208b8515..48f674cd5 100644 --- a/qiling/os/qnx/map_msgtype.py +++ b/qiling/os/qnx/map_msgtype.py @@ -6,7 +6,7 @@ from qiling.const import QL_ARCH def map_msgtype(ql, msgtype): - if ql.archtype == QL_ARCH.ARM: + if ql.arch.type == QL_ARCH.ARM: for k, v in msgtype_table.items(): if v == msgtype: return f'ql_qnx_msg_{k}' diff --git a/qiling/os/qnx/map_syscall.py b/qiling/os/qnx/map_syscall.py index 774ca17bf..d668579f8 100644 --- a/qiling/os/qnx/map_syscall.py +++ b/qiling/os/qnx/map_syscall.py @@ -7,7 +7,7 @@ from qiling.os.posix.posix import SYSCALL_PREF def map_syscall(ql, syscall_num): - if ql.archtype == QL_ARCH.ARM: + if ql.arch.type == QL_ARCH.ARM: return f'{SYSCALL_PREF}{arm_syscall_table[syscall_num]}' # Source: https://github.com/vocho/openqnx diff --git a/qiling/os/qnx/qnx.py b/qiling/os/qnx/qnx.py index 57e595959..daa032c2f 100644 --- a/qiling/os/qnx/qnx.py +++ b/qiling/os/qnx/qnx.py @@ -37,7 +37,7 @@ def __init__(self, ql: Qiling): QL_ARCH.MIPS : mips.mipso32, QL_ARCH.RISCV : riscv.riscv, QL_ARCH.RISCV64: riscv.riscv, - }[ql.archtype](ql) + }[ql.arch.type](ql) self.fcall = QlFunctionCall(ql, cc) @@ -65,7 +65,7 @@ def load(self): return # ARM - if self.ql.archtype == QL_ARCH.ARM: + if self.ql.arch.type == QL_ARCH.ARM: self.ql.arch.enable_vfp() self.ql.hook_intno(self.hook_syscall, 2) #self.thread_class = thread.QlLinuxARMThread @@ -135,7 +135,7 @@ def run(self): else: if self.ql.loader.elf_entry != self.ql.loader.entry_point: entry_address = self.ql.loader.elf_entry - if self.ql.archtype == QL_ARCH.ARM and entry_address & 1 == 1: + if self.ql.arch.type == QL_ARCH.ARM and entry_address & 1 == 1: entry_address -= 1 self.ql.emu_start(self.ql.loader.entry_point, entry_address, self.ql.timeout) self.run_function_after_load() diff --git a/qiling/os/windows/dlls/ntdll.py b/qiling/os/windows/dlls/ntdll.py index 8bcb417df..42728e7a3 100644 --- a/qiling/os/windows/dlls/ntdll.py +++ b/qiling/os/windows/dlls/ntdll.py @@ -125,7 +125,7 @@ def _QuerySystemInformation(ql: Qiling, address: int, params): max_uaddr = { QL_ARCH.X86 : 0x7FFEFFFF, QL_ARCH.X8664: 0x7FFFFFFEFFFF - }[ql.archtype] + }[ql.arch.type] sbi = structs.SystemBasicInforation( ql, diff --git a/qiling/os/windows/dlls/ntoskrnl.py b/qiling/os/windows/dlls/ntoskrnl.py index 955e7da59..b903f61ad 100644 --- a/qiling/os/windows/dlls/ntoskrnl.py +++ b/qiling/os/windows/dlls/ntoskrnl.py @@ -164,7 +164,7 @@ def __IoCreateDevice(ql: Qiling, address: int, params): objcls = { QL_ARCH.X86 : DEVICE_OBJECT32, QL_ARCH.X8664 : DEVICE_OBJECT64 - }[ql.archtype] + }[ql.arch.type] addr = ql.os.heap.alloc(ctypes.sizeof(objcls)) @@ -643,7 +643,7 @@ def hook_MmMapLockedPagesSpecifyCache(ql: Qiling, address: int, params): mdl_class: ctypes.Structure = { QL_ARCH.X8664 : MDL64, QL_ARCH.X86 : MDL32 - }[ql.archtype] + }[ql.arch.type] mdl_buffer = ql.mem.read(MemoryDescriptorList, ctypes.sizeof(mdl_class)) mdl = mdl_class.from_buffer(mdl_buffer) diff --git a/qiling/os/windows/structs.py b/qiling/os/windows/structs.py index 815ca2bc1..4b7705fa5 100644 --- a/qiling/os/windows/structs.py +++ b/qiling/os/windows/structs.py @@ -118,7 +118,7 @@ def __init__(self, self.AtlThunkSListPtr = alt_thunk_s_list_ptr self.IFEOKey = ifeo_key self.numberOfProcessors = number_processors - if self.ql.archtype == 32: + if self.ql.arch.type == 32: self.size = 0x0468 else: self.size = 0x07B0 @@ -1600,12 +1600,12 @@ def bytes(self): s += self.ql.pack(self.SizeOfImage) # 0x20 s += self.ql.pack16(self.FullDllName['Length']) # 0x24 s += self.ql.pack16(self.FullDllName['MaximumLength']) # 0x26 - if self.ql.archtype == QL_ARCH.X8664: + if self.ql.arch.type == QL_ARCH.X8664: s += self.ql.pack32(0) s += self.ql.pack(self.FullDllName['BufferPtr']) # 0x28 s += self.ql.pack16(self.BaseDllName['Length']) s += self.ql.pack16(self.BaseDllName['MaximumLength']) - if self.ql.archtype == QL_ARCH.X8664: + if self.ql.arch.type == QL_ARCH.X8664: s += self.ql.pack32(0) s += self.ql.pack(self.BaseDllName['BufferPtr']) s += self.ql.pack(self.Flags) @@ -2281,7 +2281,7 @@ def __init__(self, ql, length=None, maxLength=None, buffer=None): super().__init__(ql) # on x64, self.buffer is aligned to 8 - if (ql.archtype == 32): + 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 diff --git a/qiling/os/windows/thread.py b/qiling/os/windows/thread.py index 803e01f8d..54320c7aa 100644 --- a/qiling/os/windows/thread.py +++ b/qiling/os/windows/thread.py @@ -87,19 +87,19 @@ def create(self, func_addr, func_params, status): self.saved_context = self.ql.arch.regs.save() # set return address, parameters - if self.ql.archtype == QL_ARCH.X86: + if self.ql.arch.type == QL_ARCH.X86: self.ql.mem.write(new_stack - 4, self.ql.pack32(self.ql.os.thread_manager.THREAD_RET_ADDR)) self.ql.mem.write(new_stack, self.ql.pack32(func_params)) - elif self.ql.archtype == QL_ARCH.X8664: + elif self.ql.arch.type == QL_ARCH.X8664: self.ql.mem.write(new_stack - 8, self.ql.pack64(self.ql.os.thread_manager.THREAD_RET_ADDR)) self.saved_context["rcx"] = func_params # set eip/rip, ebp/rbp, esp/rsp - if self.ql.archtype == QL_ARCH.X86: + if self.ql.arch.type == QL_ARCH.X86: self.saved_context["eip"] = func_addr self.saved_context["ebp"] = new_stack - 4 self.saved_context["esp"] = new_stack - 4 - elif self.ql.archtype == QL_ARCH.X8664: + elif self.ql.arch.type == QL_ARCH.X8664: self.saved_context["rip"] = func_addr self.saved_context["rbp"] = new_stack - 8 self.saved_context["rsp"] = new_stack - 8 diff --git a/qiling/os/windows/utils.py b/qiling/os/windows/utils.py index c31db51e8..f0172b01b 100644 --- a/qiling/os/windows/utils.py +++ b/qiling/os/windows/utils.py @@ -56,7 +56,7 @@ def io_Write(ql: Qiling, in_buffer: bytes): alloc_addr = [] def build_mdl(buffer_size, data=None): - if ql.archtype == QL_ARCH.X8664: + if ql.arch.type == QL_ARCH.X8664: mdl = MDL64() else: mdl = MDL32() @@ -73,7 +73,7 @@ def build_mdl(buffer_size, data=None): return mdl # allocate memory regions for IRP and IO_STACK_LOCATION - if ql.archtype == QL_ARCH.X8664: + if ql.arch.type == QL_ARCH.X8664: irp_addr = heap.alloc(ctypes.sizeof(IRP64)) alloc_addr.append(irp_addr) irpstack_addr = heap.alloc(ctypes.sizeof(IO_STACK_LOCATION64)) @@ -107,7 +107,7 @@ def build_mdl(buffer_size, data=None): elif device_object.Flags & DO_DIRECT_IO: # DIRECT_IO mdl = build_mdl(len(in_buffer)) - if ql.archtype == QL_ARCH.X8664: + if ql.arch.type == QL_ARCH.X8664: mdl_addr = heap.alloc(ctypes.sizeof(MDL64)) else: mdl_addr = heap.alloc(ctypes.sizeof(MDL32)) @@ -139,7 +139,7 @@ def build_mdl(buffer_size, data=None): verify_ret(ql, err) # read current IRP state - if ql.archtype == QL_ARCH.X8664: + if ql.arch.type == QL_ARCH.X8664: irp_buffer = ql.mem.read(irp_addr, ctypes.sizeof(IRP64)) irp = IRP64.from_buffer(irp_buffer) else: @@ -172,7 +172,7 @@ def ioctl_code(DeviceType, Function, Method, Access): alloc_addr = [] def build_mdl(buffer_size, data=None): - if ql.archtype == QL_ARCH.X8664: + if ql.arch.type == QL_ARCH.X8664: mdl = MDL64() else: mdl = MDL32() @@ -211,7 +211,7 @@ def build_mdl(buffer_size, data=None): alloc_addr.append(output_buffer_addr) # allocate memory regions for IRP and IO_STACK_LOCATION - if ql.archtype == QL_ARCH.X8664: + if ql.arch.type == QL_ARCH.X8664: irp_addr = heap.alloc(ctypes.sizeof(IRP64)) alloc_addr.append(irp_addr) irpstack_addr = heap.alloc(ctypes.sizeof(IO_STACK_LOCATION64)) @@ -267,7 +267,7 @@ def build_mdl(buffer_size, data=None): # Create MDL structure for output data # used by both IOCTL_METHOD_IN_DIRECT and IOCTL_METHOD_OUT_DIRECT mdl = build_mdl(output_buffer_size) - if ql.archtype == QL_ARCH.X8664: + if ql.arch.type == QL_ARCH.X8664: mdl_addr = heap.alloc(ctypes.sizeof(MDL64)) else: mdl_addr = heap.alloc(ctypes.sizeof(MDL32)) @@ -293,7 +293,7 @@ def build_mdl(buffer_size, data=None): verify_ret(ql, err) # read current IRP state - if ql.archtype == QL_ARCH.X8664: + if ql.arch.type == QL_ARCH.X8664: irp_buffer = ql.mem.read(irp_addr, ctypes.sizeof(IRP64)) irp = IRP64.from_buffer(irp_buffer) else: diff --git a/qiling/os/windows/windows.py b/qiling/os/windows/windows.py index c7558bde6..5c1e045d3 100644 --- a/qiling/os/windows/windows.py +++ b/qiling/os/windows/windows.py @@ -56,7 +56,7 @@ def __make_fcall_selector(atype: QL_ARCH) -> Callable[[int], QlFunctionCall]: return __selector[atype] - self.fcall_select = __make_fcall_selector(ql.archtype) + self.fcall_select = __make_fcall_selector(ql.arch.type) self.fcall = None self.PE_RUN = True @@ -84,13 +84,13 @@ def load(self): def setupGDT(self): # setup gdt - if self.ql.archtype == QL_ARCH.X86: + if self.ql.arch.type == QL_ARCH.X86: self.gdtm = GDTManager(self.ql) ql_x86_register_cs(self) ql_x86_register_ds_ss_es(self) ql_x86_register_fs(self) ql_x86_register_gs(self) - elif self.ql.archtype == QL_ARCH.X8664: + elif self.ql.arch.type == QL_ARCH.X8664: ql_x8664_set_gs(self.ql) diff --git a/qiling/utils.py b/qiling/utils.py index 71cdc8b2d..5ced18750 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -578,7 +578,7 @@ def verify_ret(ql, err): else: raise - if ql.archtype == QL_ARCH.X8664: # Win64 + if ql.arch.type == QL_ARCH.X8664: # Win64 if ql.os.init_sp == ql.arch.regs.arch_sp or ql.os.init_sp + 8 == ql.arch.regs.arch_sp or ql.os.init_sp + 0x10 == ql.arch.regs.arch_sp: # FIXME # 0x11626 c3 ret # print("OK, stack balanced!") From 0ff17e7ff850ce618563315a120cd19669e7b745 Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 30 Jan 2022 17:33:34 +0200 Subject: [PATCH 087/406] Return an enum rather than numeric value --- qiling/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiling/const.py b/qiling/const.py index b10d41cab..c81ff81e4 100644 --- a/qiling/const.py +++ b/qiling/const.py @@ -68,7 +68,7 @@ def __reverse_enum(e: Type[Enum]) -> Mapping[str, Any]: '''Create a reverse mapping for an enum. ''' - return dict((v.name.lower(), v.value) for v in e.__members__.values()) + return dict((v.name.lower(), v) for v in e.__members__.values()) debugger_map: Mapping[str, QL_DEBUGGER] = __reverse_enum(QL_DEBUGGER) arch_map : Mapping[str, QL_ARCH] = __reverse_enum(QL_ARCH) From 7245e9ce6716bcc15554ef7c112016e9bac505f6 Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 30 Jan 2022 17:52:10 +0200 Subject: [PATCH 088/406] Let loader init use ql.profile --- qiling/core.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/qiling/core.py b/qiling/core.py index 2e9c2b4c7..069f618bb 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -182,16 +182,16 @@ def __init__( self.verbose = verbose - ########## - # Loader # - ########## - self._loader = loader_setup(self, self.ostype, libcache) - ########### # Profile # ########### self._profile = profile_setup(self, self.ostype, profile) + ########## + # Loader # + ########## + self._loader = loader_setup(self, self.ostype, libcache) + ############## # Components # ############## From 0210308df0792544c64af5077df138d81df87d7d Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 30 Jan 2022 17:53:06 +0200 Subject: [PATCH 089/406] Resolve an old workaround --- qiling/utils.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/qiling/utils.py b/qiling/utils.py index 5ced18750..79b868d23 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -336,9 +336,7 @@ def ql_pe_parse_emu_env(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], pefile.MACHINE_TYPE['IMAGE_FILE_MACHINE_AMD64'] : QL_ARCH.X8664, pefile.MACHINE_TYPE['IMAGE_FILE_MACHINE_ARM'] : QL_ARCH.ARM, pefile.MACHINE_TYPE['IMAGE_FILE_MACHINE_THUMB'] : QL_ARCH.ARM, - # pefile.MACHINE_TYPE['IMAGE_FILE_MACHINE_ARM64'] : QL_ARCH.ARM64 #pefile does not have the definition - # for IMAGE_FILE_MACHINE_ARM64 - 0xAA64: QL_ARCH.ARM64 # Temporary workaround for Issues #21 till pefile gets updated + pefile.MACHINE_TYPE['IMAGE_FILE_MACHINE_ARM64'] : QL_ARCH.ARM64 } # get arch From 4935b70164691fa86ea76e413791f10de62f344f Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 30 Jan 2022 22:12:20 +0200 Subject: [PATCH 090/406] Pythonize ql_elf_parse_emu_env --- qiling/utils.py | 135 +++++++++++++++++++++++++++++----------------- tests/test_mcu.py | 4 +- 2 files changed, 89 insertions(+), 50 deletions(-) diff --git a/qiling/utils.py b/qiling/utils.py index 79b868d23..56b00ec04 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -231,66 +231,105 @@ def ql_get_module_function(module_name: str, function_name: str): return module_function def ql_elf_parse_emu_env(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], Optional[QL_ENDIAN]]: - with open(path, "rb") as f: - size = os.fstat(f.fileno()).st_size + # instead of using full-blown elffile parsing, we perform a simple parsing to avoid + # external dependencies for target systems that do not need them. + # + # see: https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html + + # ei_class + ELFCLASS32 = 1 # 32-bit + ELFCLASS64 = 2 # 64-bit + + #ei_data + ELFDATA2LSB = 1 # little-endian + ELFDATA2MSB = 2 # big-endian + + # ei_osabi + ELFOSABI_SYSV = 0 + ELFOSABI_LINUX = 3 + ELFOSABI_FREEBSD = 9 + ELFOSABI_ARM_AEABI = 64 + ELFOSABI_ARM = 97 + ELFOSABI_STANDALONE = 255 + + # e_machine + EM_386 = 3 + EM_MIPS = 8 + EM_ARM = 40 + EM_X86_64 = 62 + EM_AARCH64 = 183 + EM_RISCV = 243 + + endianess = { + ELFDATA2LSB : (QL_ENDIAN.EL, 'little'), + ELFDATA2MSB : (QL_ENDIAN.EB, 'big') + } - ident = f.read(512 if size >= 512 else 20) + machines32 = { + EM_386 : QL_ARCH.X86, + EM_MIPS : QL_ARCH.MIPS, + EM_ARM : QL_ARCH.ARM, + EM_RISCV : QL_ARCH.RISCV + } - arch = None + machines64 = { + EM_X86_64 : QL_ARCH.X8664, + EM_AARCH64 : QL_ARCH.ARM64, + EM_RISCV : QL_ARCH.RISCV64 + } + + classes = { + ELFCLASS32 : machines32, + ELFCLASS64 : machines64 + } + + abis = { + ELFOSABI_SYSV : QL_OS.LINUX, + ELFOSABI_LINUX : QL_OS.LINUX, + ELFOSABI_FREEBSD : QL_OS.FREEBSD, + ELFOSABI_ARM_AEABI : QL_OS.LINUX, + ELFOSABI_ARM : QL_OS.LINUX, + ELFOSABI_STANDALONE : QL_OS.BLOB + } + + archtype = None ostype = None archendian = None - if ident[:4] == b'\x7fELF': - elfbit = ident[0x4] - endian = ident[0x5] - osabi = ident[0x7] - e_machine = ident[0x12:0x14] - - if osabi == 0x09: - ostype = QL_OS.FREEBSD - elif osabi in (0x0, 0x03) or osabi >= 0x11: - if b"ldqnx.so" in ident: - ostype = QL_OS.QNX - else: - ostype = QL_OS.LINUX + with open(path, 'rb') as binfile: + e_ident = binfile.read(16) + e_type = binfile.read(2) + e_machine = binfile.read(2) - if e_machine == b"\x03\x00": - archendian = QL_ENDIAN.EL - arch = QL_ARCH.X86 + # qnx may be detected by the interpreter name: 'ldqnx.so'. + # instead of properly parsing the file to locate the pt_interp + # segment, we detect qnx fuzzily by looking for that string in + # the first portion of the file. + blob = binfile.read(0x200 - 20) - elif e_machine == b"\x08\x00" and endian == 1 and elfbit == 1: - archendian = QL_ENDIAN.EL - arch = QL_ARCH.MIPS + if e_ident[:4] == b'\x7fELF': + ei_class = e_ident[4] # arch bits + ei_data = e_ident[5] # arch endianess + ei_osabi = e_ident[7] - elif e_machine == b"\x00\x08" and endian == 2 and elfbit == 1: - archendian = QL_ENDIAN.EB - arch = QL_ARCH.MIPS + if ei_class in classes: + machines = classes[ei_class] - elif e_machine == b"\x28\x00" and endian == 1 and elfbit == 1: - archendian = QL_ENDIAN.EL - arch = QL_ARCH.ARM + if ei_data in endianess: + archendian, endian = endianess[ei_data] - elif e_machine == b"\x00\x28" and endian == 2 and elfbit == 1: - archendian = QL_ENDIAN.EB - arch = QL_ARCH.ARM + machine = int.from_bytes(e_machine, endian) - elif e_machine == b"\xB7\x00": - archendian = QL_ENDIAN.EL - arch = QL_ARCH.ARM64 + if machine in machines: + archtype = machines[machine] - elif e_machine == b"\x3E\x00": - archendian = QL_ENDIAN.EL - arch = QL_ARCH.X8664 - - elif e_machine == b"\xf3\x00" and elfbit == 1: - archendian = QL_ENDIAN.EL - arch = QL_ARCH.RISCV - - elif e_machine == b"\xf3\x00" and elfbit == 2: - archendian = QL_ENDIAN.EL - arch = QL_ARCH.RISCV64 - - return arch, ostype, archendian + if ei_osabi in abis: + ostype = abis[ei_osabi] + + if blob and b'ldqnx.so' in blob: + ostype = QL_OS.QNX + + return archtype, ostype, archendian def ql_macho_parse_emu_env(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], Optional[QL_ENDIAN]]: macho_macos_sig64 = b'\xcf\xfa\xed\xfe' diff --git a/tests/test_mcu.py b/tests/test_mcu.py index 76f46e849..64bf17e51 100644 --- a/tests/test_mcu.py +++ b/tests/test_mcu.py @@ -294,8 +294,8 @@ def skip_delay(ql): def test_mcu_blink_gd32vf103(self): - ql = Qiling(['../examples/rootfs/mcu/gd32vf103/blink.hex'], archtype="riscv64", - env=gd32vf103, verbose=QL_VERBOSE.DEFAULT) + ql = Qiling(['../examples/rootfs/mcu/gd32vf103/blink.hex'], + ostype="mcu", archtype="riscv64", env=gd32vf103, verbose=QL_VERBOSE.DEFAULT) ql.hw.create('rcu') ql.hw.create('gpioa') From 0584ac2f1f1257bae42c13d6941068091207b79a Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 1 Feb 2022 14:59:42 +0200 Subject: [PATCH 091/406] Code dedpulication: is_thumb --- qiling/debugger/qdb/qdb.py | 4 ++-- qiling/debugger/qdb/utils.py | 8 +------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/qiling/debugger/qdb/qdb.py b/qiling/debugger/qdb/qdb.py index 628665d71..b4294b264 100644 --- a/qiling/debugger/qdb/qdb.py +++ b/qiling/debugger/qdb/qdb.py @@ -13,7 +13,7 @@ from qiling.debugger import QlDebugger from .frontend import context_reg, context_asm, examine_mem -from .utils import _parse_int, handle_bnj, is_thumb, CODE_END, parse_int +from .utils import _parse_int, handle_bnj, CODE_END, parse_int from .utils import Breakpoint, TempBreakpoint from .const import * @@ -130,7 +130,7 @@ def _run(self: Qldbg, address: int = 0, end: int = 0, count: int = 0) -> None: return - if self.ql.arch.type in (QL_ARCH.ARM, QL_ARCH.CORTEX_M) and is_thumb(self.ql.arch.regs.cpsr): + if self.ql.arch.type in (QL_ARCH.ARM, QL_ARCH.CORTEX_M) and self.ql.arch.is_thumb: address |= 1 self.ql.emu_start(begin=address, end=end, count=count) diff --git a/qiling/debugger/qdb/utils.py b/qiling/debugger/qdb/utils.py index 329403207..3705583ff 100644 --- a/qiling/debugger/qdb/utils.py +++ b/qiling/debugger/qdb/utils.py @@ -121,12 +121,7 @@ def get_cpsr(bits: int) -> (bool, bool, bool, bool): ) -def is_thumb(bits: int) -> bool: - return bits & 0x00000020 != 0 - - def disasm(ql: Qiling, address: int, detail: bool = False) -> Optional[int]: - md = ql.arch.disassembler md.detail = detail try: @@ -143,8 +138,7 @@ def _read_inst(ql: Qiling, addr: int) -> int: result = ql.mem.read(addr, 4) if ql.arch.type in (QL_ARCH.ARM, QL_ARCH.CORTEX_M): - if is_thumb(ql.arch.regs.cpsr): - + if ql.arch.is_thumb: first_two = ql.unpack16(ql.mem.read(addr, 2)) result = ql.pack16(first_two) From 498d5a42336ce3e01279463ce59c6a4cd297e17e Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 1 Feb 2022 15:13:02 +0200 Subject: [PATCH 092/406] Use a safer method to obtain tty dimentions --- qiling/debugger/qdb/frontend.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/qiling/debugger/qdb/frontend.py b/qiling/debugger/qdb/frontend.py index 867c4abb6..f0554ac3c 100644 --- a/qiling/debugger/qdb/frontend.py +++ b/qiling/debugger/qdb/frontend.py @@ -123,11 +123,6 @@ def unpack(bs, sz): return True -# get terminal window height and width -def get_terminal_size() -> Iterable: - return map(int, os.popen('stty size', 'r').read().split()) - - # try to read data from ql memory def _try_read(ql: Qiling, address: int, size: int) -> Optional[bytes]: @@ -148,13 +143,15 @@ def _try_read(ql: Qiling, address: int, size: int) -> Optional[bytes]: # divider printer @contextmanager -def context_printer(ql: Qiling, field_name: str, ruler: str = "─") -> None: - height, width = get_terminal_size() - bar = (width - len(field_name)) // 2 - 1 +def context_printer(ql: Qiling, field_name: str, ruler: str = "─"): + cols, _ = os.get_terminal_size() + + bar = (cols - len(field_name)) // 2 - 1 print(ruler * bar, field_name, ruler * bar) yield + if "DISASM" in field_name: - print(ruler * width) + print(ruler * cols) def context_reg(ql: Qiling, saved_states: Optional[Mapping[str, int]] = None, /, *args, **kwargs) -> None: From b35927132f886d024bd8fa8a729fc9f6749016e9 Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 2 Feb 2022 19:34:58 +0200 Subject: [PATCH 093/406] Reimplement GDT and segment managements --- qiling/arch/x86.py | 138 +---------------------------- qiling/arch/x86_utils.py | 187 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 189 insertions(+), 136 deletions(-) create mode 100644 qiling/arch/x86_utils.py diff --git a/qiling/arch/x86.py b/qiling/arch/x86.py index a47b09dd0..b634849fc 100644 --- a/qiling/arch/x86.py +++ b/qiling/arch/x86.py @@ -3,7 +3,6 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from struct import pack from functools import cached_property from typing import Union @@ -11,14 +10,13 @@ from capstone import Cs, CS_ARCH_X86, CS_MODE_16, CS_MODE_32, CS_MODE_64 from keystone import Ks, KS_ARCH_X86, KS_MODE_16, KS_MODE_32, KS_MODE_64 -from qiling import Qiling +from unicorn.x86_const import UC_X86_REG_EFLAGS + from qiling.arch.arch import QlArch from qiling.arch.msr import QlMsrManager from qiling.arch.register import QlRegisterManager from qiling.arch import x86_const -from qiling.arch.x86_const import * from qiling.const import QL_ARCH, QL_ENDIAN -from qiling.exception import QlGDTError class QlArchIntel(QlArch): @property @@ -158,135 +156,3 @@ def reg_bits(self, reg: Union[str, int]) -> int: reg = self.regs.register_mapping[reg] return self.__reg_bits(reg) - -class GDTManager: - # Added GDT management module. - def __init__(self, ql: Qiling, GDT_ADDR = QL_X86_GDT_ADDR, GDT_LIMIT = QL_X86_GDT_LIMIT, GDT_ENTRY_ENTRIES = 16): - ql.log.debug(f"Map GDT at {hex(GDT_ADDR)} with GDT_LIMIT={GDT_LIMIT}") - - if not ql.mem.is_mapped(GDT_ADDR, GDT_LIMIT): - ql.mem.map(GDT_ADDR, GDT_LIMIT, info="[GDT]") - - # setup GDT by writing to GDTR - ql.arch.regs.write(UC_X86_REG_GDTR, (0, GDT_ADDR, GDT_LIMIT, 0x0)) - - self.ql = ql - self.gdt_number = GDT_ENTRY_ENTRIES - # self.gdt_used = [False] * GDT_ENTRY_ENTRIES - self.gdt_addr = GDT_ADDR - self.gdt_limit = GDT_LIMIT - - - def register_gdt_segment(self, index: int, SEGMENT_ADDR: int, SEGMENT_SIZE: int, SPORT, RPORT): - # FIXME: Temp fix for FS and GS - if index in (14, 15): - if not self.ql.mem.is_mapped(SEGMENT_ADDR, SEGMENT_ADDR): - self.ql.mem.map(SEGMENT_ADDR, SEGMENT_ADDR, info="[FS/GS]") - - if index < 0 or index >= self.gdt_number: - raise QlGDTError("Ql GDT register index error!") - # create GDT entry, then write GDT entry into GDT table - gdt_entry = self._create_gdt_entry(SEGMENT_ADDR, SEGMENT_SIZE, SPORT, QL_X86_F_PROT_32) - self.ql.mem.write(self.gdt_addr + (index << 3), gdt_entry) - # self.gdt_used[index] = True - self.ql.log.debug(f"Write to {hex(self.gdt_addr + (index << 3))} for new entry {gdt_entry}") - - - def get_gdt_buf(self, start: int, end: int) -> bytearray: - return self.ql.mem.read(self.gdt_addr + (start << 3), (end << 3) - (start << 3)) - - - def set_gdt_buf(self, start: int, end: int, buf: bytes) -> None: - self.ql.mem.write(self.gdt_addr + (start << 3), buf[ : (end << 3) - (start << 3)]) - - - def get_free_idx(self, start: int = 0, end: int = -1) -> int: - # The Linux kernel determines whether the segment is empty by judging whether the content in the current GDT segment is 0. - if end == -1: - end = self.gdt_number - - for i in range(start, end): - if self.ql.unpack64(self.ql.mem.read(self.gdt_addr + (i << 3), 8)) == 0: - return i - - return -1 - - - def _create_gdt_entry(self, base, limit, access, flags): - to_ret = limit & 0xffff - to_ret |= (base & 0xffffff) << 16 - to_ret |= (access & 0xff) << 40 - to_ret |= ((limit >> 16) & 0xf) << 48 - to_ret |= (flags & 0xff) << 52 - to_ret |= ((base >> 24) & 0xff) << 56 - return pack(' bool: + return (0 < index < self.num_entries) + + def __getitem__(self, index: int) -> bytes: + if not self.__in_bounds(index): + raise QlGDTError('invalid GDT entry index') + + return bytes(self.mem.read(self.base + (index * self.entsize), self.entsize)) + + def __setitem__(self, index: int, data: bytes) -> None: + assert len(data) == self.entsize + + if not self.__in_bounds(index): + raise QlGDTError('invalid GDT entry index') + + self.mem.write(self.base + (index * self.entsize), data) + + def get_next_free(self, start: int = None, end: int = None) -> int: + # The Linux kernel determines whether the segment is empty by judging whether the content in the current GDT segment is 0. + null_entry = b'\x00' * self.entsize + + # first gdt entry is always null, start from 1 + if start is None: + start = 1 + + if end is None: + end = self.num_entries + + return next((i for i in range(start, end) if self[i] == null_entry), -1) + + +class GDTManager: + def __init__(self, ql: Qiling, base = QL_X86_GDT_ADDR, limit = QL_X86_GDT_LIMIT, num_entries = 16): + ql.log.debug(f'Mapping GDT at {base:#x} with limit {limit:#x}') + + if not ql.mem.is_mapped(base, limit): + ql.mem.map(base, limit, info="[GDT]") + + # setup GDT by writing to GDTR + ql.arch.regs.write(UC_X86_REG_GDTR, (0, base, limit, 0x0)) + + self.array = GDTArray(ql.mem, base, num_entries) + + @staticmethod + def make_entry(base: int, limit: int, access: int, flags: int) -> bytes: + """Encode specified arguments into a new GDT entry. + """ + + maxbits = lambda val, nbits: val & ~((1 << nbits) - 1) == 0 + + assert maxbits(base, 32) + assert maxbits(limit, 20) + assert maxbits(access, 8) + assert maxbits(flags, 4) + + # base: 8 + 24 bits + base_hi = (base >> 24) & 0xff + base_lo = base & ((1 << 24) - 1) + + # limit: 4 + 16 bits + limit_hi = (limit >> 16) & 0xf + limit_lo = limit & ((1 << 16) - 1) + + entry = base_hi << 56 | flags << 52 | limit_hi << 48 | access << 40 | base_lo << 16 | limit_lo + + return entry.to_bytes(8, 'little', signed=False) + + @staticmethod + def make_selector(idx: int, rpl: int) -> int: + assert rpl & ~0b11 == 0 + + return (idx << 3) | QL_X86_S_GDT | rpl + + def register_gdt_segment(self, index: int, seg_base: int, seg_limit: int, access: int) -> int: + flags = QL_X86_F_PROT_32 + + # is this a huge segment? + if seg_limit > (1 << 16): + # on 4K granularity the lower 12 bits are implicitly all set + assert seg_limit & ((1 << 12) - 1) == 0xfff + + seg_limit >>= 12 + flags |= QL_X86_F_GRANULARITY + + # create GDT entry, then write GDT entry into GDT table + self.array[index] = GDTManager.make_entry(seg_base, seg_limit, access, flags) + + return GDTManager.make_selector(index, (access >> 5) & 0b11) + + def get_entry(self, index: int) -> bytes: + return self.array[index] + + def set_entry(self, index: int, data: bytes) -> None: + self.array[index] = data + + def get_free_idx(self, start: int = None, end: int = None) -> int: + return self.array.get_next_free(start, end) + + +class SegmentManager: + def __init__(self, arch: QlArchIntel, gdtm: GDTManager): + self.arch = arch + self.gdtm = gdtm + + @abstractmethod + def setup_cs_ds_ss_es(self, base: int, size: int) -> None: + pass + + @abstractmethod + def setup_fs(self, base: int, size: int) -> None: + pass + + @abstractmethod + def setup_gs(self, base: int, size: int) -> None: + pass + + +class SegmentManager86(SegmentManager): + def setup_cs_ds_ss_es(self, base: int, size: int) -> None: + # While debugging the linux kernel segment, the cs segment was found on the third segment of gdt. + access = QL_X86_A_PRESENT | QL_X86_A_CODE | QL_X86_A_CODE_READABLE | QL_X86_A_PRIV_3 | QL_X86_A_EXEC | QL_X86_A_DIR_CON_BIT + selector = self.gdtm.register_gdt_segment(3, base, size - 1, access) + + self.arch.regs.cs = selector + + # TODO : The section permission here should be QL_X86_A_PRIV_3, but I do n’t know why it can only be set to QL_X86_A_PRIV_0. + # While debugging the Linux kernel segment, I found that the three segments DS, SS, and ES all point to the same location in the GDT table. + # This position is the fifth segment table of GDT. + access = QL_X86_A_PRESENT | QL_X86_A_DATA | QL_X86_A_DATA_WRITABLE | QL_X86_A_PRIV_0 | QL_X86_A_DIR_CON_BIT + selector = self.gdtm.register_gdt_segment(5, base, size - 1, access) + + self.arch.regs.ds = selector + self.arch.regs.ss = selector + self.arch.regs.es = selector + + def setup_fs(self, base: int, size: int) -> None: + access = QL_X86_A_PRESENT | QL_X86_A_DATA | QL_X86_A_DATA_WRITABLE | QL_X86_A_PRIV_3 | QL_X86_A_DIR_CON_BIT + selector = self.gdtm.register_gdt_segment(14, base, size - 1, access) + + self.arch.regs.fs = selector + + def setup_gs(self, base: int, size: int) -> None: + access = QL_X86_A_PRESENT | QL_X86_A_DATA | QL_X86_A_DATA_WRITABLE | QL_X86_A_PRIV_3 | QL_X86_A_DIR_CON_BIT + selector = self.gdtm.register_gdt_segment(15, base, size - 1, access) + + self.arch.regs.gs = selector + + +class SegmentManager64(SegmentManager): + def setup_cs_ds_ss_es(self, base: int, size: int) -> None: + # While debugging the linux kernel segment, the cs segment was found on the sixth segment of gdt. + access = QL_X86_A_PRESENT | QL_X86_A_CODE | QL_X86_A_CODE_READABLE | QL_X86_A_PRIV_3 | QL_X86_A_EXEC | QL_X86_A_DIR_CON_BIT + selector = self.gdtm.register_gdt_segment(6, base, size - 1, access) + + self.arch.regs.cs = selector + + # TODO : The section permission here should be QL_X86_A_PRIV_3, but I do n’t know why it can only be set to QL_X86_A_PRIV_0. + # When I debug the Linux kernel, I find that only the SS is set to the fifth segment table, and the rest are not set. + access = QL_X86_A_PRESENT | QL_X86_A_DATA | QL_X86_A_DATA_WRITABLE | QL_X86_A_PRIV_0 | QL_X86_A_DIR_CON_BIT + selector = self.gdtm.register_gdt_segment(5, base, size - 1, access) + + # self.arch.regs.ds = selector + self.arch.regs.ss = selector + # self.arch.regs.es = selector + + def setup_fs(self, base: int, size: int) -> None: + self.arch.msr.write(FSMSR, base) + + def setup_gs(self, base: int, size: int) -> None: + self.arch.msr.write(GSMSR, base) From 86f6ce5e0b0a2f48c3dbfab7936feaae19f3fb49 Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 2 Feb 2022 19:38:28 +0200 Subject: [PATCH 094/406] Adjust all GDT and segments usages --- qiling/os/freebsd/freebsd.py | 13 ++++++----- qiling/os/linux/linux.py | 22 +++++++++++------- qiling/os/linux/syscall.py | 16 ++++++-------- qiling/os/linux/thread.py | 43 ++++++++++++++++++++++-------------- qiling/os/macos/macos.py | 14 +++++++----- qiling/os/windows/windows.py | 30 ++++++++++++++++--------- 6 files changed, 85 insertions(+), 53 deletions(-) diff --git a/qiling/os/freebsd/freebsd.py b/qiling/os/freebsd/freebsd.py index e865416dc..800d31f07 100644 --- a/qiling/os/freebsd/freebsd.py +++ b/qiling/os/freebsd/freebsd.py @@ -4,9 +4,9 @@ # from unicorn import UcError +from unicorn.x86_const import UC_X86_INS_SYSCALL -from qiling.arch.x86 import GDTManager, ql_x86_register_cs, ql_x86_register_ds_ss_es -from qiling.arch.x86_const import UC_X86_INS_SYSCALL +from qiling.arch.x86_utils import GDTManager, SegmentManager86 from qiling.os.posix.posix import QlOsPosix class QlOsFreebsd(QlOsPosix): @@ -18,10 +18,13 @@ def __init__(self, ql): def load(self): + gdtm = GDTManager(self.ql) + + # setup gdt and segments selectors + segm = SegmentManager86(self.ql.arch, gdtm) + segm.setup_cs_ds_ss_es(0, 4 << 30) + self.ql.hook_insn(self.hook_syscall, UC_X86_INS_SYSCALL) - self.gdtm = GDTManager(self.ql) - ql_x86_register_cs(self) - ql_x86_register_ds_ss_es(self) def hook_syscall(self, intno= None): diff --git a/qiling/os/linux/linux.py b/qiling/os/linux/linux.py index 437355d1c..5d2482d0f 100644 --- a/qiling/os/linux/linux.py +++ b/qiling/os/linux/linux.py @@ -5,10 +5,11 @@ from typing import Callable from unicorn import UcError +from unicorn.x86_const import UC_X86_INS_SYSCALL from qiling import Qiling -from qiling.arch.x86_const import UC_X86_INS_SYSCALL -from qiling.arch.x86 import GDTManager, ql_x8664_set_gs, ql_x86_register_cs, ql_x86_register_ds_ss_es +from qiling.arch.x86_const import GS_SEGMENT_ADDR, GS_SEGMENT_SIZE +from qiling.arch.x86_utils import GDTManager, SegmentManager86, SegmentManager64 from qiling.arch import arm_utils from qiling.cc import QlCC, intel, arm, mips, riscv from qiling.const import QL_ARCH, QL_INTERCEPT @@ -45,8 +46,6 @@ def __init__(self, ql: Qiling): self.elf_mem_start = 0x0 self.load() - if self.ql.arch.type == QL_ARCH.X8664: - ql_x8664_set_gs(self.ql) def load(self): self.futexm = futex.QlLinuxFutexManagement() @@ -72,16 +71,23 @@ def load(self): # X86 elif self.ql.arch.type == QL_ARCH.X86: self.gdtm = GDTManager(self.ql) - ql_x86_register_cs(self) - ql_x86_register_ds_ss_es(self) + + # setup gdt and segments selectors + segm = SegmentManager86(self.ql.arch, self.gdtm) + segm.setup_cs_ds_ss_es(0, 4 << 30) + self.ql.hook_intno(self.hook_syscall, 0x80) self.thread_class = thread.QlLinuxX86Thread # X8664 elif self.ql.arch.type == QL_ARCH.X8664: self.gdtm = GDTManager(self.ql) - ql_x86_register_cs(self) - ql_x86_register_ds_ss_es(self) + + # setup gdt and segments selectors + segm = SegmentManager64(self.ql.arch, self.gdtm) + segm.setup_cs_ds_ss_es(0, 4 << 30) + segm.setup_gs(GS_SEGMENT_ADDR, GS_SEGMENT_SIZE) + self.ql.hook_insn(self.hook_syscall, UC_X86_INS_SYSCALL) # Keep test for _cc #self.ql.hook_insn(hook_posix_api, UC_X86_INS_SYSCALL) diff --git a/qiling/os/linux/syscall.py b/qiling/os/linux/syscall.py index 6efee3ae4..ac96d3693 100644 --- a/qiling/os/linux/syscall.py +++ b/qiling/os/linux/syscall.py @@ -30,11 +30,8 @@ class timespec32(ctypes.Structure): _pack_ = 4 -def ql_syscall_set_thread_area(ql: Qiling, u_info_addr, *args, **kw): +def ql_syscall_set_thread_area(ql: Qiling, u_info_addr: int): if ql.arch.type == QL_ARCH.X86: - GDT_ENTRY_TLS_MIN = 12 - GDT_ENTRY_TLS_MAX = 14 - u_info = ql.mem.read(u_info_addr, 4 * 4) index = ql.unpack32s(u_info[0 : 4]) base = ql.unpack32(u_info[4 : 8]) @@ -45,13 +42,14 @@ def ql_syscall_set_thread_area(ql: Qiling, u_info_addr, *args, **kw): if index == -1: index = ql.os.gdtm.get_free_idx(12) - if index == -1 or index < GDT_ENTRY_TLS_MIN or index > GDT_ENTRY_TLS_MAX: + if index in (12, 13, 14): + access = QL_X86_A_PRESENT | QL_X86_A_DATA | QL_X86_A_DATA_WRITABLE | QL_X86_A_PRIV_3 | QL_X86_A_DIR_CON_BIT + + ql.os.gdtm.register_gdt_segment(index, base, limit, access) + ql.mem.write(u_info_addr, ql.pack32(index)) + else: ql.log.warning(f"Wrong index {index} from address {hex(u_info_addr)}") return -1 - else: - ql.os.gdtm.register_gdt_segment(index, base, limit, QL_X86_A_PRESENT | QL_X86_A_DATA | QL_X86_A_DATA_WRITABLE | QL_X86_A_PRIV_3 | QL_X86_A_DIR_CON_BIT, QL_X86_S_GDT | QL_X86_S_PRIV_3) - ql.mem.write(u_info_addr, ql.pack32(index)) - return 0 elif ql.arch.type == QL_ARCH.MIPS: CONFIG3_ULR = (1 << 13) diff --git a/qiling/os/linux/thread.py b/qiling/os/linux/thread.py index 3497d1914..a3c47ea6b 100644 --- a/qiling/os/linux/thread.py +++ b/qiling/os/linux/thread.py @@ -5,7 +5,7 @@ import gevent, os -from typing import Callable +from typing import Callable, Sequence from abc import abstractmethod from unicorn.unicorn import UcError @@ -368,12 +368,18 @@ class QlLinuxX86Thread(QlLinuxThread): """docstring for X86Thread""" def __init__(self, ql, start_address, exit_point, context = None, set_child_tid_addr = None, thread_id = None): super(QlLinuxX86Thread, self).__init__(ql, start_address, exit_point, context, set_child_tid_addr, thread_id) - self.tls = bytes(b'\x00' * (8 * 3)) + self.tls = [b'\x00' * 8] * 3 - def set_thread_tls(self, tls_addr): - old_tls = bytes(self.ql.os.gdtm.get_gdt_buf(12, 14 + 1)) + def __read_tls(self) -> Sequence[bytes]: + return [self.ql.os.gdtm.get_entry(i) for i in (12, 13, 14)] + + def __write_tls(self, tls: Sequence[bytes]) -> None: + for i, entry in zip((12, 13, 14), tls): + self.ql.os.gdtm.set_entry(i, entry) - self.ql.os.gdtm.set_gdt_buf(12, 14 + 1, self.tls) + def set_thread_tls(self, tls_addr): + old_tls = self.__read_tls() + self.__write_tls(self.tls) u_info = self.ql.mem.read(tls_addr, 4 * 4) index = self.ql.unpack32s(u_info[0 : 4]) @@ -383,25 +389,30 @@ def set_thread_tls(self, tls_addr): if index == -1: index = self.ql.os.gdtm.get_free_idx(12) - if index == -1 or index < 12 or index > 14: - raise - else: - self.ql.os.gdtm.register_gdt_segment(index, base, limit, QL_X86_A_PRESENT | QL_X86_A_DATA | QL_X86_A_DATA_WRITABLE | QL_X86_A_PRIV_3 | QL_X86_A_DIR_CON_BIT, QL_X86_S_GDT | QL_X86_S_PRIV_3) + if index in (12, 13, 14): + access = QL_X86_A_PRESENT | QL_X86_A_DATA | QL_X86_A_DATA_WRITABLE | QL_X86_A_PRIV_3 | QL_X86_A_DIR_CON_BIT + + self.ql.os.gdtm.register_gdt_segment(index, base, limit, access) self.ql.mem.write(tls_addr, self.ql.pack32(index)) + else: + raise + + self.tls = self.__read_tls() + self.__write_tls(old_tls) - self.tls = bytes(self.ql.os.gdtm.get_gdt_buf(12, 14 + 1)) - self.ql.os.gdtm.set_gdt_buf(12, 14 + 1, old_tls) - self.ql.log.debug(f"Set tls to index={hex(index)} base={hex(base)} limit={hex(limit)} fs={hex(self.ql.arch.regs.fs)} gs={hex(self.ql.arch.regs.gs)} gdt_buf={self.tls}") + self.ql.log.debug(f'Set TLS to index={index:d} base={base:#x} limit={limit:#x} fs={self.ql.arch.regs.fs:#06x} gs={self.ql.arch.regs.gs:#06x}') def save(self): self.save_context() - self.tls = bytes(self.ql.os.gdtm.get_gdt_buf(12, 14 + 1)) - self.ql.log.debug(f"Saved context. fs={hex(self.ql.arch.regs.fs)} gs={hex(self.ql.arch.regs.gs)} gdt_buf={self.tls}") + self.tls = self.__read_tls() + + self.ql.log.debug(f'Context saved (fs={self.ql.arch.regs.fs:#06x} gs={self.ql.arch.regs.gs:#06x} gdt_buf=[{" ".join(ent.hex() for ent in self.tls)}])') def restore(self): self.restore_context() - self.ql.os.gdtm.set_gdt_buf(12, 14 + 1, self.tls) - self.ql.log.debug(f"Restored context. fs={hex(self.ql.arch.regs.fs)} gs={hex(self.ql.arch.regs.gs)} gdt_buf={self.tls}") + self.__write_tls(self.tls) + + self.ql.log.debug(f'Context restored (fs={self.ql.arch.regs.fs:#06x} gs={self.ql.arch.regs.gs:#06x} gdt_buf=[{" ".join(ent.hex() for ent in self.tls)}])') def clone(self): new_thread = super(QlLinuxX86Thread, self).clone() diff --git a/qiling/os/macos/macos.py b/qiling/os/macos/macos.py index 68ea7ce3a..efd9fc226 100644 --- a/qiling/os/macos/macos.py +++ b/qiling/os/macos/macos.py @@ -4,11 +4,12 @@ # from ctypes import sizeof + from unicorn import UcError +from unicorn.x86_const import UC_X86_INS_SYSCALL from qiling import Qiling -from qiling.arch.x86 import GDTManager, ql_x86_register_cs, ql_x86_register_ds_ss_es -from qiling.arch.x86_const import UC_X86_INS_SYSCALL +from qiling.arch.x86_utils import GDTManager, SegmentManager64 from qiling.cc import intel from qiling.const import QL_ARCH, QL_VERBOSE from qiling.os.fcall import QlFunctionCall @@ -147,10 +148,13 @@ def load(self): self.ql.hook_intno(self.hook_sigtrap, 7) elif self.ql.arch.type == QL_ARCH.X8664: + gdtm = GDTManager(self.ql) + + # setup gdt and segments selectors + segm = SegmentManager64(self.ql.arch, gdtm) + segm.setup_cs_ds_ss_es(0, 4 << 30) + self.ql.hook_insn(self.hook_syscall, UC_X86_INS_SYSCALL) - self.gdtm = GDTManager(self.ql) - ql_x86_register_cs(self) - ql_x86_register_ds_ss_es(self) def hook_syscall(self, intno= None, int = None): diff --git a/qiling/os/windows/windows.py b/qiling/os/windows/windows.py index 5c1e045d3..b5370a1ea 100644 --- a/qiling/os/windows/windows.py +++ b/qiling/os/windows/windows.py @@ -9,7 +9,8 @@ from unicorn import UcError from qiling import Qiling -from qiling.arch.x86 import GDTManager, ql_x86_register_cs, ql_x86_register_ds_ss_es, ql_x86_register_fs, ql_x86_register_gs, ql_x8664_set_gs +from qiling.arch.x86_const import GS_SEGMENT_ADDR, GS_SEGMENT_SIZE, FS_SEGMENT_ADDR, FS_SEGMENT_SIZE +from qiling.arch.x86_utils import GDTManager, SegmentManager86, SegmentManager64 from qiling.cc import intel from qiling.const import QL_ARCH, QL_INTERCEPT from qiling.exception import QlErrorSyscallError, QlErrorSyscallNotFound @@ -83,15 +84,24 @@ def load(self): def setupGDT(self): - # setup gdt - if self.ql.arch.type == QL_ARCH.X86: - self.gdtm = GDTManager(self.ql) - ql_x86_register_cs(self) - ql_x86_register_ds_ss_es(self) - ql_x86_register_fs(self) - ql_x86_register_gs(self) - elif self.ql.arch.type == QL_ARCH.X8664: - ql_x8664_set_gs(self.ql) + gdtm = GDTManager(self.ql) + + segm_class = { + 32 : SegmentManager86, + 64 : SegmentManager64 + }[self.ql.arch.bits] + + # setup gdt and segments selectors + segm = segm_class(self.ql.arch, gdtm) + segm.setup_cs_ds_ss_es(0, 4 << 30) + segm.setup_fs(FS_SEGMENT_ADDR, FS_SEGMENT_SIZE) + segm.setup_gs(GS_SEGMENT_ADDR, GS_SEGMENT_SIZE) + + if not self.ql.mem.is_mapped(FS_SEGMENT_ADDR, FS_SEGMENT_SIZE): + self.ql.mem.map(FS_SEGMENT_ADDR, FS_SEGMENT_SIZE, info='[FS]') + + if not self.ql.mem.is_mapped(GS_SEGMENT_ADDR, GS_SEGMENT_SIZE): + self.ql.mem.map(GS_SEGMENT_ADDR, GS_SEGMENT_SIZE, info='[GS]') def setupComponents(self): From cac7771d94503e904ec450112d9a98dab01f618a Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 4 Feb 2022 12:45:03 +0200 Subject: [PATCH 095/406] Make QlCC depended on QlArch instead of Qiling --- qiling/cc/__init__.py | 24 ++++++++++++------------ qiling/cc/arm.py | 16 ++++++++-------- qiling/cc/intel.py | 16 ++++++++-------- qiling/cc/mips.py | 10 +++++----- qiling/cc/riscv.py | 8 ++++---- qiling/os/blob/blob.py | 2 +- qiling/os/linux/linux.py | 2 +- qiling/os/posix/posix.py | 2 +- qiling/os/qnx/qnx.py | 2 +- qiling/os/uefi/uefi.py | 2 +- 10 files changed, 42 insertions(+), 42 deletions(-) diff --git a/qiling/cc/__init__.py b/qiling/cc/__init__.py index 714c5569c..351072c7c 100644 --- a/qiling/cc/__init__.py +++ b/qiling/cc/__init__.py @@ -4,20 +4,20 @@ from typing import Callable, Tuple -from qiling import Qiling +from qiling.arch.arch import QlArch class QlCC: """Calling convention base class. """ - def __init__(self, ql: Qiling) -> None: + def __init__(self, arch: QlArch) -> None: """Initialize a calling convention instance. Args: - ql: qiling instance + arch: underlying architecture instance """ - self.ql = ql + self.arch = arch @staticmethod def getNumSlots(argbits: int) -> int: @@ -111,11 +111,11 @@ class QlCommonBaseCC(QlCC): _shadow = 0 _retaddr_on_stack = True - def __init__(self, ql: Qiling, retreg: int): - super().__init__(ql) + def __init__(self, arch: QlArch, retreg: int): + super().__init__(arch) # native address size in bytes - self._asize = self.ql.arch.pointersize + self._asize = self.arch.pointersize # return value register self._retreg = retreg @@ -151,24 +151,24 @@ def __access_param(self, index: int, stack_access: Callable, reg_access: Callabl return reg_access, reg def getRawParam(self, index: int, argbits: int = None) -> int: - read, loc = self.__access_param(index, self.ql.stack_read, self.ql.arch.regs.read) + read, loc = self.__access_param(index, self.arch.stack_read, self.arch.regs.read) mask = (0 if argbits is None else (1 << argbits)) - 1 return read(loc) & mask def setRawParam(self, index: int, value: int, argbits: int = None) -> None: - write, loc = self.__access_param(index, self.ql.stack_write, self.ql.arch.regs.write) + write, loc = self.__access_param(index, self.arch.stack_write, self.arch.regs.write) mask = (0 if argbits is None else (1 << argbits)) - 1 write(loc, value & mask) def getReturnValue(self) -> int: - return self.ql.arch.regs.read(self._retreg) + return self.arch.regs.read(self._retreg) def setReturnValue(self, value: int) -> None: - self.ql.arch.regs.write(self._retreg, value) + self.arch.regs.write(self._retreg, value) def reserve(self, nslots: int) -> None: assert nslots < len(self._argregs), 'too many slots' @@ -176,4 +176,4 @@ def reserve(self, nslots: int) -> None: # count how many slots should be reserved on the stack si = self._argregs[:nslots].count(None) - self.ql.arch.regs.arch_sp -= (self._shadow + si) * self._asize + self.arch.regs.arch_sp -= (self._shadow + si) * self._asize diff --git a/qiling/cc/arm.py b/qiling/cc/arm.py index 3d0aaa254..6517ee260 100644 --- a/qiling/cc/arm.py +++ b/qiling/cc/arm.py @@ -8,8 +8,8 @@ UC_ARM64_REG_X4, UC_ARM64_REG_X5, UC_ARM64_REG_X6, UC_ARM64_REG_X7 ) -from qiling import Qiling -from . import QlCommonBaseCC +from qiling.arch.arch import QlArch +from qiling.cc import QlCommonBaseCC class QlArmBaseCC(QlCommonBaseCC): """Calling convention base class for ARM-based systems. @@ -22,20 +22,20 @@ def getNumSlots(argbits: int) -> int: def setReturnAddress(self, addr: int) -> None: # TODO: do we need to update LR? - self.ql.arch.stack_push(addr) + self.arch.stack_push(addr) def unwind(self, nslots: int) -> int: # TODO: cleanup? - return self.ql.arch.stack_pop() + return self.arch.stack_pop() class aarch64(QlArmBaseCC): _argregs = (UC_ARM64_REG_X0, UC_ARM64_REG_X1, UC_ARM64_REG_X2, UC_ARM64_REG_X3, UC_ARM64_REG_X4, UC_ARM64_REG_X5, UC_ARM64_REG_X6, UC_ARM64_REG_X7) + (None, ) * 8 - def __init__(self, ql: Qiling) -> None: - super().__init__(ql, UC_ARM64_REG_X0) + def __init__(self, arch: QlArch) -> None: + super().__init__(arch, UC_ARM64_REG_X0) class aarch32(QlArmBaseCC): _argregs = (UC_ARM_REG_R0, UC_ARM_REG_R1, UC_ARM_REG_R2, UC_ARM_REG_R3) + (None, ) * 12 - def __init__(self, ql: Qiling) -> None: - super().__init__(ql, UC_ARM_REG_R0) + def __init__(self, arch: QlArch) -> None: + super().__init__(arch, UC_ARM_REG_R0) diff --git a/qiling/cc/intel.py b/qiling/cc/intel.py index 892f0fcc1..4ed9f8fec 100644 --- a/qiling/cc/intel.py +++ b/qiling/cc/intel.py @@ -8,29 +8,29 @@ UC_X86_REG_R9, UC_X86_REG_R10 ) -from qiling import Qiling -from . import QlCommonBaseCC +from qiling.arch.arch import QlArch +from qiling.cc import QlCommonBaseCC class QlIntelBaseCC(QlCommonBaseCC): """Calling convention base class for Intel-based systems. Supports arguments passing over registers and stack. """ - def __init__(self, ql: Qiling): + def __init__(self, arch: QlArch): retreg = { 16: UC_X86_REG_AX, 32: UC_X86_REG_EAX, 64: UC_X86_REG_RAX - }[ql.arch.bits] + }[arch.bits] - super().__init__(ql, retreg) + super().__init__(arch, retreg) def setReturnAddress(self, addr: int) -> None: - self.ql.arch.stack_push(addr) + self.arch.stack_push(addr) def unwind(self, nslots: int) -> int: # no cleanup; just pop out the return address - return self.ql.arch.stack_pop() + return self.arch.stack_pop() class QlIntel64(QlIntelBaseCC): """Calling convention base class for Intel-based 64-bit systems. @@ -107,6 +107,6 @@ class stdcall(QlIntel32): def unwind(self, nslots: int) -> int: retaddr = super().unwind(nslots) - self.ql.arch.regs.arch_sp += (nslots * self._asize) + self.arch.regs.arch_sp += (nslots * self._asize) return retaddr diff --git a/qiling/cc/mips.py b/qiling/cc/mips.py index 1c08f161d..9eafc4d0a 100644 --- a/qiling/cc/mips.py +++ b/qiling/cc/mips.py @@ -4,16 +4,16 @@ from unicorn.mips_const import UC_MIPS_REG_V0, UC_MIPS_REG_A0, UC_MIPS_REG_A1, UC_MIPS_REG_A2, UC_MIPS_REG_A3 -from qiling import Qiling -from . import QlCommonBaseCC +from qiling.arch.arch import QlArch +from qiling.cc import QlCommonBaseCC class mipso32(QlCommonBaseCC): _argregs = (UC_MIPS_REG_A0, UC_MIPS_REG_A1, UC_MIPS_REG_A2, UC_MIPS_REG_A3) + (None, ) * 12 _shadow = 4 _retaddr_on_stack = False - def __init__(self, ql: Qiling): - super().__init__(ql, UC_MIPS_REG_V0) + def __init__(self, arch: QlArch): + super().__init__(arch, UC_MIPS_REG_V0) @staticmethod def getNumSlots(argbits: int): @@ -21,4 +21,4 @@ def getNumSlots(argbits: int): def unwind(self, nslots: int) -> int: # TODO: stack frame unwiding? - return self.ql.arch.regs.ra + return self.arch.regs.ra diff --git a/qiling/cc/riscv.py b/qiling/cc/riscv.py index e768f6c4d..7a834ecda 100644 --- a/qiling/cc/riscv.py +++ b/qiling/cc/riscv.py @@ -2,8 +2,8 @@ # # Cross Platform and Multi Architecture Advanced Binary Emulation Framework -from qiling import Qiling -from . import QlCommonBaseCC +from qiling.arch.arch import QlArch +from qiling.cc import QlCommonBaseCC from unicorn.riscv_const import ( UC_RISCV_REG_A0, UC_RISCV_REG_A1, UC_RISCV_REG_A2, @@ -18,8 +18,8 @@ class riscv(QlCommonBaseCC): _argregs = (UC_RISCV_REG_A0, UC_RISCV_REG_A1, UC_RISCV_REG_A2, UC_RISCV_REG_A3, UC_RISCV_REG_A4, UC_RISCV_REG_A5) + (None, ) * 10 - def __init__(self, ql: Qiling): - super().__init__(ql, UC_RISCV_REG_A0) + def __init__(self, arch: QlArch): + super().__init__(arch, UC_RISCV_REG_A0) @staticmethod def getNumSlots(argbits: int): diff --git a/qiling/os/blob/blob.py b/qiling/os/blob/blob.py index 7ab2a2079..09be61e99 100644 --- a/qiling/os/blob/blob.py +++ b/qiling/os/blob/blob.py @@ -28,7 +28,7 @@ def __init__(self, ql: Qiling): QL_ARCH.ARM : arm.aarch32, QL_ARCH.ARM64 : arm.aarch64, QL_ARCH.MIPS : mips.mipso32 - }[ql.arch.type](ql) + }[ql.arch.type](ql.arch) self.fcall = QlFunctionCall(ql, cc) diff --git a/qiling/os/linux/linux.py b/qiling/os/linux/linux.py index 5d2482d0f..558159c66 100644 --- a/qiling/os/linux/linux.py +++ b/qiling/os/linux/linux.py @@ -35,7 +35,7 @@ def __init__(self, ql: Qiling): QL_ARCH.MIPS : mips.mipso32, QL_ARCH.RISCV : riscv.riscv, QL_ARCH.RISCV64: riscv.riscv, - }[ql.arch.type](ql) + }[ql.arch.type](ql.arch) self.fcall = QlFunctionCall(ql, cc) diff --git a/qiling/os/posix/posix.py b/qiling/os/posix/posix.py index d6f9e9eea..65eb9fd1e 100644 --- a/qiling/os/posix/posix.py +++ b/qiling/os/posix/posix.py @@ -104,7 +104,7 @@ def __init__(self, ql: Qiling): QL_ARCH.X8664: intel64, QL_ARCH.RISCV: riscv32, QL_ARCH.RISCV64: riscv64, - }[self.ql.arch.type](ql) + }[self.ql.arch.type](self.ql.arch) self._fd = QlFileDes([0] * NR_OPEN) diff --git a/qiling/os/qnx/qnx.py b/qiling/os/qnx/qnx.py index daa032c2f..72077c963 100644 --- a/qiling/os/qnx/qnx.py +++ b/qiling/os/qnx/qnx.py @@ -37,7 +37,7 @@ def __init__(self, ql: Qiling): QL_ARCH.MIPS : mips.mipso32, QL_ARCH.RISCV : riscv.riscv, QL_ARCH.RISCV64: riscv.riscv, - }[ql.arch.type](ql) + }[ql.arch.type](ql.arch) self.fcall = QlFunctionCall(ql, cc) diff --git a/qiling/os/uefi/uefi.py b/qiling/os/uefi/uefi.py index c777cde29..709000dbb 100644 --- a/qiling/os/uefi/uefi.py +++ b/qiling/os/uefi/uefi.py @@ -33,7 +33,7 @@ def __init__(self, ql: Qiling): cc: QlCC = { 32: intel.cdecl, 64: intel.ms64 - }[ql.arch.bits](ql) + }[ql.arch.bits](ql.arch) self.fcall = QlFunctionCall(ql, cc) From 6ae918120317e6a2f50f3004e8c7b8d2f2e568b3 Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 4 Feb 2022 14:44:22 +0200 Subject: [PATCH 096/406] Fix overlooked QlCC usages --- qiling/os/posix/posix.py | 4 ++-- qiling/os/windows/windows.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/qiling/os/posix/posix.py b/qiling/os/posix/posix.py index 65eb9fd1e..260aa05a3 100644 --- a/qiling/os/posix/posix.py +++ b/qiling/os/posix/posix.py @@ -49,8 +49,8 @@ def setReturnValue(self, value: int): else: a3return = 0 - self.ql.arch.regs.v0 = value - self.ql.arch.regs.a3 = a3return + self.arch.regs.v0 = value + self.arch.regs.a3 = a3return class riscv32(riscv.riscv): pass diff --git a/qiling/os/windows/windows.py b/qiling/os/windows/windows.py index b5370a1ea..9c3b795ca 100644 --- a/qiling/os/windows/windows.py +++ b/qiling/os/windows/windows.py @@ -45,9 +45,9 @@ def __make_fcall_selector(atype: QL_ARCH) -> Callable[[int], QlFunctionCall]: """ __fcall_objs = { - fncc.STDCALL: QlFunctionCall(ql, intel.stdcall(ql)), - fncc.CDECL : QlFunctionCall(ql, intel.cdecl(ql)), - fncc.MS64 : QlFunctionCall(ql, intel.ms64(ql)) + fncc.STDCALL: QlFunctionCall(ql, intel.stdcall(ql.arch)), + fncc.CDECL : QlFunctionCall(ql, intel.cdecl(ql.arch)), + fncc.MS64 : QlFunctionCall(ql, intel.ms64(ql.arch)) } __selector = { From ba809a653e16e0d60a1abed36c488d576e8364fb Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 4 Feb 2022 14:48:29 +0200 Subject: [PATCH 097/406] Extract OS stats from QlOsUtils --- qiling/os/utils.py | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/qiling/os/utils.py b/qiling/os/utils.py index 3d5f67ebe..cb51a65cf 100644 --- a/qiling/os/utils.py +++ b/qiling/os/utils.py @@ -7,25 +7,19 @@ This module is intended for general purpose functions that are only used in qiling.os """ -from typing import Any, MutableMapping, Mapping, Union, Sequence, MutableSequence, Tuple +from typing import Any, List, MutableMapping, Mapping, Set, Union, Sequence, MutableSequence, Tuple from uuid import UUID from qiling import Qiling from qiling.const import QL_VERBOSE -class QlOsUtils: - - ELLIPSIS_PREF = r'__qlva_' - - def __init__(self, ql: Qiling): - self.ql = ql - - # We can save every syscall called - self.syscalls = {} +class QlOsStats: + def __init__(self): + self.syscalls: MutableMapping[str, List] = {} self.syscalls_counter = 0 - self.appeared_strings = {} + self.appeared_strings: MutableMapping[str, Set] = {} - def clear_syscalls(self): + def clear(self): """Reset API and string appearance stats. """ @@ -33,7 +27,7 @@ def clear_syscalls(self): self.syscalls_counter = 0 self.appeared_strings = {} - def _call_api(self, address: int, name: str, params: Mapping, retval: Any, retaddr: int) -> None: + def log_api_call(self, address: int, name: str, params: Mapping, retval: Any, retaddr: int) -> None: """Record API calls along with their details. Args: @@ -48,16 +42,16 @@ def _call_api(self, address: int, name: str, params: Mapping, retval: Any, retad name = name[5:] self.syscalls.setdefault(name, []).append({ - 'params': params, - 'retval': retval, - 'address': address, - 'retaddr': retaddr, - 'position': self.syscalls_counter + 'params' : params, + 'retval' : retval, + 'address' : address, + 'retaddr' : retaddr, + 'position' : self.syscalls_counter }) self.syscalls_counter += 1 - def string_appearance(self, s: str) -> None: + def log_string(self, s: str) -> None: """Record strings appearance as they are encountered during emulation. Args: @@ -67,6 +61,13 @@ def string_appearance(self, s: str) -> None: for token in s.split(' '): self.appeared_strings.setdefault(token, set()).add(self.syscalls_counter) +class QlOsUtils: + + ELLIPSIS_PREF = r'__qlva_' + + def __init__(self, ql: Qiling): + self.ql = ql + @staticmethod def read_string(ql: Qiling, address: int, terminator: bytes) -> str: result = bytearray() From 95369382a952777f160c823eccafe932f790d65c Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 4 Feb 2022 14:52:53 +0200 Subject: [PATCH 098/406] Adjust all OS stats usages --- examples/sality.py | 2 +- qiling/extensions/report/report.py | 2 +- qiling/os/os.py | 5 +++-- qiling/os/posix/posix.py | 6 +++--- qiling/os/utils.py | 4 ++-- qiling/os/windows/dlls/kernel32/fileapi.py | 2 +- qiling/os/windows/registry.py | 2 +- qiling/os/windows/windows.py | 4 ++-- tests/test_pe.py | 4 ++-- tests/test_pe_sys.py | 4 ++-- 10 files changed, 18 insertions(+), 17 deletions(-) diff --git a/examples/sality.py b/examples/sality.py index 87e348162..71cf1028a 100644 --- a/examples/sality.py +++ b/examples/sality.py @@ -89,7 +89,7 @@ def _WriteFile(ql: Qiling, address: int, params): if hFile == 0xfffffff5: s = ql.mem.read(lpBuffer, nNumberOfBytesToWrite) ql.os.stdout.write(s) - ql.os.utils.string_appearance(s.decode()) + ql.os.stats.log_string(s.decode()) ql.mem.write(lpNumberOfBytesWritten, ql.pack(nNumberOfBytesToWrite)) else: f = ql.os.handle_manager.get(hFile) diff --git a/qiling/extensions/report/report.py b/qiling/extensions/report/report.py index f7e4f1ef2..59343165d 100644 --- a/qiling/extensions/report/report.py +++ b/qiling/extensions/report/report.py @@ -16,7 +16,7 @@ def __init__(self, ql): self.os = list(os_map.keys())[list(os_map.values()).index(ql.ostype)] self.env = ql.env self.strings = set() - for string in ql.os.utils.appeared_strings: + for string in ql.os.stats.appeared_strings: strings = string.split(" ") self.strings |= set(strings) self.profile = {} diff --git a/qiling/os/os.py b/qiling/os/os.py index 5709d64ac..e9b84e96b 100644 --- a/qiling/os/os.py +++ b/qiling/os/os.py @@ -15,7 +15,7 @@ from .filestruct import ql_file from .mapper import QlFsMapper -from .utils import QlOsUtils +from .utils import QlOsStats, QlOsUtils from .path import QlPathManager class QlOs: @@ -30,6 +30,7 @@ def __init__(self, ql: Qiling, resolvers: Mapping[Any, Resolver] = {}): self._stderr: TextIO self.utils = QlOsUtils(ql) + self.stats = QlOsStats() self.fcall: QlFunctionCall self.child_processes = False self.thread_management = None @@ -189,7 +190,7 @@ def call(self, pc: int, func: Callable, proto: Mapping[str, Any], onenter: Optio self.utils.print_function(pc, func.__name__, pargs, retval, passthru) # append syscall to list - self.utils._call_api(pc, func.__name__, args, retval, retaddr) + self.stats.log_api_call(pc, func.__name__, args, retval, retaddr) # [Windows and UEFI] if emulation has stopped, do not update the return address if hasattr(self, 'PE_RUN') and not self.PE_RUN: diff --git a/qiling/os/posix/posix.py b/qiling/os/posix/posix.py index 260aa05a3..a95d71532 100644 --- a/qiling/os/posix/posix.py +++ b/qiling/os/posix/posix.py @@ -262,15 +262,15 @@ def load_syscall(self): self.utils.print_function(self.ql.arch.regs.arch_pc, syscall_basename, args, sret, False) # record syscall statistics - self.utils.syscalls.setdefault(syscall_name, []).append({ + self.stats.syscalls.setdefault(syscall_name, []).append({ "params": dict(zip(param_names, params)), "result": retval, "address": self.ql.arch.regs.arch_pc, "return_address": None, - "position": self.utils.syscalls_counter + "position": self.stats.syscalls_counter }) - self.utils.syscalls_counter += 1 + self.stats.syscalls_counter += 1 else: self.ql.log.warning(f'{self.ql.arch.regs.arch_pc:#x}: syscall {syscall_name} number = {syscall_id:#x}({syscall_id:d}) not implemented') diff --git a/qiling/os/utils.py b/qiling/os/utils.py index cb51a65cf..6b942d95c 100644 --- a/qiling/os/utils.py +++ b/qiling/os/utils.py @@ -87,14 +87,14 @@ def read_wstring(self, address: int) -> str: # We need to remove \x00 inside the string. Compares do not work otherwise s = s.replace("\x00", "") - self.string_appearance(s) + self.ql.os.stats.log_string(s) return s def read_cstring(self, address: int) -> str: s = QlOsUtils.read_string(self.ql, address, b'\x00') - self.string_appearance(s) + self.ql.os.stats.log_string(s) return s diff --git a/qiling/os/windows/dlls/kernel32/fileapi.py b/qiling/os/windows/dlls/kernel32/fileapi.py index 514bc7a3f..525f1d251 100644 --- a/qiling/os/windows/dlls/kernel32/fileapi.py +++ b/qiling/os/windows/dlls/kernel32/fileapi.py @@ -203,7 +203,7 @@ def hook_WriteFile(ql: Qiling, address: int, params): if hFile == STD_OUTPUT_HANDLE: s = ql.mem.read(lpBuffer, nNumberOfBytesToWrite) ql.os.stdout.write(s) - ql.os.utils.string_appearance(s.decode()) + ql.os.stats.log_string(s.decode()) ql.mem.write(lpNumberOfBytesWritten, ql.pack32(nNumberOfBytesToWrite)) else: f = ql.os.handle_manager.get(hFile) diff --git a/qiling/os/windows/registry.py b/qiling/os/windows/registry.py index 5a69b81db..abdff9f31 100644 --- a/qiling/os/windows/registry.py +++ b/qiling/os/windows/registry.py @@ -154,7 +154,7 @@ def access(self, key, value_name=None, value=None, type=None): "value_name": value_name, "value": value, "type": type, - "position": self.ql.os.utils.syscalls_counter + "position": self.ql.os.stats.syscalls_counter }) # we don't have to increase the counter since we are technically inside a hook diff --git a/qiling/os/windows/windows.py b/qiling/os/windows/windows.py index 9c3b795ca..5ddc23e24 100644 --- a/qiling/os/windows/windows.py +++ b/qiling/os/windows/windows.py @@ -157,7 +157,7 @@ def hook_winapi(self, ql: Qiling, address: int, size: int): def post_report(self): self.ql.log.debug("Syscalls called:") - for key, values in self.utils.syscalls.items(): + for key, values in self.stats.syscalls.items(): self.ql.log.debug(f'{key}:') for value in values: @@ -171,7 +171,7 @@ def post_report(self): self.ql.log.debug(f' {json.dumps(value):s}') self.ql.log.debug("Strings:") - for key, values in self.utils.appeared_strings.items(): + for key, values in self.stats.appeared_strings.items(): self.ql.log.debug(f'{key}: {" ".join(str(word) for word in values)}') diff --git a/tests/test_pe.py b/tests/test_pe.py index 9cad850ec..88743a91a 100644 --- a/tests/test_pe.py +++ b/tests/test_pe.py @@ -193,7 +193,7 @@ def randomize_config_value(ql, key, subkey): randomize_config_value(ql, "USER", "username") randomize_config_value(ql, "SYSTEM", "computername") randomize_config_value(ql, "VOLUME", "serial_number") - num_syscalls_admin = ql.os.utils.syscalls_counter + num_syscalls_admin = ql.os.stats.syscalls_counter ql.run() del ql @@ -201,7 +201,7 @@ def randomize_config_value(ql, key, subkey): ql = Qiling(["../examples/rootfs/x86_windows/bin/GandCrab502.bin"], "../examples/rootfs/x86_windows", profile="profiles/windows_gandcrab_user.ql") ql.run() - num_syscalls_user = ql.os.utils.syscalls_counter + num_syscalls_user = ql.os.stats.syscalls_counter # let's check that gandcrab behave takes a different path if a different environment is found if num_syscalls_admin == num_syscalls_user: diff --git a/tests/test_pe_sys.py b/tests/test_pe_sys.py index c609914bf..1068cc9c9 100644 --- a/tests/test_pe_sys.py +++ b/tests/test_pe_sys.py @@ -98,7 +98,7 @@ def _WriteFile(ql, address, params): if hFile == 0xfffffff5: s = ql.mem.read(lpBuffer, nNumberOfBytesToWrite) ql.os.stdout.write(s) - ql.os.utils.string_appearance(s.decode()) + ql.os.stats.log_string(s.decode()) ql.mem.write(lpNumberOfBytesWritten, ql.pack(nNumberOfBytesToWrite)) else: f = ql.os.handle_manager.get(hFile) @@ -256,7 +256,7 @@ def test_pe_win_x8664_driver(self): # And a DriverUnload self.assertNotEqual(driver_object.DriverUnload, 0) - ql.os.utils.clear_syscalls() + ql.os.stats.clear() IOCTL_SIOCTL_METHOD_OUT_DIRECT = (40000, 0x901, METHOD_OUT_DIRECT, FILE_ANY_ACCESS) output_buffer_size = 0x1000 From b56d24c1e126627bce914af1a6891afbeb5546ae Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 4 Feb 2022 15:23:23 +0200 Subject: [PATCH 099/406] Fix overlooked QlCC usages #2 --- qiling/os/macos/macos.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiling/os/macos/macos.py b/qiling/os/macos/macos.py index efd9fc226..ec826825c 100644 --- a/qiling/os/macos/macos.py +++ b/qiling/os/macos/macos.py @@ -24,7 +24,7 @@ def __init__(self, ql: Qiling): super(QlOsMacos, self).__init__(ql) self.ql = ql - self.fcall = QlFunctionCall(ql, intel.macosx64(ql)) + self.fcall = QlFunctionCall(ql, intel.macosx64(ql.arch)) self.ql.counter = 0 self.ev_manager = QlMacOSEvManager(self.ql) From 4af354ef2f91eb400cbc8abdbab734cb1f4c4953 Mon Sep 17 00:00:00 2001 From: elicn Date: Sat, 5 Feb 2022 21:04:33 +0200 Subject: [PATCH 100/406] Fix fcall frame staging --- qiling/os/fcall.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiling/os/fcall.py b/qiling/os/fcall.py index 3ae7a563a..4484e8de0 100644 --- a/qiling/os/fcall.py +++ b/qiling/os/fcall.py @@ -187,11 +187,11 @@ def call_native(self, addr: int, args: Sequence[Tuple[Any, int]], ret: Optional[ nslots = self.__count_slots(atype for atype, _ in args) self.cc.reserve(nslots) - # set arguments values - self.writeParams(args) - if ret is not None: self.cc.setReturnAddress(ret) + # set arguments values + self.writeParams(args) + # call self.ql.arch.regs.arch_pc = addr From a3e4c5f5826c7de1d6120bcaccbc22c8bd55f4b5 Mon Sep 17 00:00:00 2001 From: elicn Date: Sat, 5 Feb 2022 21:12:02 +0200 Subject: [PATCH 101/406] Fix arm-oabi handling to handle thumb --- qiling/os/posix/posix.py | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/qiling/os/posix/posix.py b/qiling/os/posix/posix.py index a95d71532..e4d5a9db3 100644 --- a/qiling/os/posix/posix.py +++ b/qiling/os/posix/posix.py @@ -279,16 +279,25 @@ def load_syscall(self): def get_syscall(self) -> int: if self.ql.arch.type == QL_ARCH.ARM: - # When ARM-OABI - # svc_imm = 0x900000 + syscall_nr - # syscall_nr = svc_imm - 0x900000 - # Ref1: https://marcin.juszkiewicz.com.pl/download/tables/syscalls.html - # Ref2: https://github.com/rootkiter/Reverse-bins/blob/master/syscall_header/armv4l_unistd.h - # Ref3: https://github.com/unicorn-engine/unicorn/issues/1137 - code_val = self.ql.mem.read_ptr(self.ql.arch.regs.arch_pc-4, 4) - svc_imm = code_val & 0x00ffffff - if (svc_imm >= 0x900000): - return svc_imm - 0x900000 + # support arm-oabi + # @see: https://marcin.juszkiewicz.com.pl/download/tables/syscalls.html + # @see: https://github.com/rootkiter/Reverse-bins/blob/master/syscall_header/armv4l_unistd.h + # @see: https://github.com/unicorn-engine/unicorn/issues/1137 + + # read the instruction we have just emulated + isize = 2 if self.ql.arch.is_thumb else self.ql.arch.pointersize + ibytes = self.ql.mem.read_ptr(self.ql.arch.regs.arch_pc - isize, isize) + + # mask off the opcode, which is the most significant byte + svc_imm = ibytes & ((1 << ((isize - 1) * 8)) - 1) + + # arm-oabi + if svc_imm >= 0x900000: + return svc_imm - 0x900000 + + if svc_imm > 0: + return svc_imm + return self.ql.arch.regs.read(self.__syscall_id_reg) def set_syscall_return(self, retval: int): From 403b19ade032f3cccc00ec81d0a8566f2c51c485 Mon Sep 17 00:00:00 2001 From: elicn Date: Sat, 5 Feb 2022 21:18:23 +0200 Subject: [PATCH 102/406] Fix ARM64 assembler parameters --- qiling/arch/utils.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/qiling/arch/utils.py b/qiling/arch/utils.py index 04d830559..2764bd72b 100644 --- a/qiling/arch/utils.py +++ b/qiling/arch/utils.py @@ -99,12 +99,12 @@ def assembler(arch: QL_ARCH, endianess: QL_ENDIAN, is_thumb: bool) -> Ks: thumb = KS_MODE_THUMB if is_thumb else 0 asm_map = { - QL_ARCH.ARM : (KS_ARCH_ARM, KS_MODE_ARM + endian + thumb), - QL_ARCH.ARM64 : (KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN), - QL_ARCH.MIPS : (KS_ARCH_MIPS, KS_MODE_MIPS32 + endian), - QL_ARCH.A8086 : (KS_ARCH_X86, KS_MODE_16), - QL_ARCH.X86 : (KS_ARCH_X86, KS_MODE_32), - QL_ARCH.X8664 : (KS_ARCH_X86, KS_MODE_64) + QL_ARCH.ARM : (KS_ARCH_ARM, KS_MODE_ARM + endian + thumb), + QL_ARCH.ARM64 : (KS_ARCH_ARM64, KS_MODE_ARM), + QL_ARCH.MIPS : (KS_ARCH_MIPS, KS_MODE_MIPS32 + endian), + QL_ARCH.A8086 : (KS_ARCH_X86, KS_MODE_16), + QL_ARCH.X86 : (KS_ARCH_X86, KS_MODE_32), + QL_ARCH.X8664 : (KS_ARCH_X86, KS_MODE_64) } if arch in asm_map: From 17d0eca0d97b0a15fdaeeea8b0c1d3c421c6aa9c Mon Sep 17 00:00:00 2001 From: elicn Date: Sat, 5 Feb 2022 21:21:05 +0200 Subject: [PATCH 103/406] Fix and comment examples --- examples/crackme_x86_windows_auto.py | 66 +++++++++++++++++++++------- examples/hello_x8664_linux_disasm.py | 51 ++++++++++++++++----- 2 files changed, 91 insertions(+), 26 deletions(-) diff --git a/examples/crackme_x86_windows_auto.py b/examples/crackme_x86_windows_auto.py index b8bf1072d..8911199de 100644 --- a/examples/crackme_x86_windows_auto.py +++ b/examples/crackme_x86_windows_auto.py @@ -8,28 +8,64 @@ from qiling import Qiling from qiling.extensions import pipe +from qiling.const import QL_INTERCEPT, QL_VERBOSE +from qiling.os.windows.api import HWND, UINT, LONG -def force_call_dialog_func(ql: Qiling): - # get DialogFunc address - lpDialogFunc = ql.unpack32(ql.mem.read(ql.arch.regs.esp - 0x8, 4)) - # setup stack for DialogFunc - ql.stack_push(0) - ql.stack_push(1001) - ql.stack_push(273) - ql.stack_push(0) - ql.stack_push(0x0401018) - # force EIP to DialogFunc - ql.arch.regs.eip = lpDialogFunc +def hook_DialogBoxParamA_onexit(ql: Qiling, address: int, params, retval: int): + # extract lpDialogFunc value + # [see arguments list at 'qiling/os/windows/dlls/user32.py' -> 'hook_DialogBoxParamA'] + lpDialogFunc = params['lpDialogFunc'] + + def call_DialogFunc(ql: Qiling): + # we would like to resume from the exact same address that used to invoke + # this hook. in order to prevent an endless loop of hook invocations, we + # remove the hook through its handle. + hh.remove() + + WM_COMMAND = 0x111 + IDS_APPNAME = 1001 + + # [steps #3 and #4] + # set up the arguments and call the address passed through the lpDialogFunc + # param. make sure it resumes back to where we were. + ql.os.fcall.call_native(lpDialogFunc, ( + (HWND, 0), + (UINT, WM_COMMAND), + (UINT, IDS_APPNAME), + (LONG, 0), + ), ql.arch.regs.arch_pc) + + # get DialogBoxParamA return address; should be the first item on the stack + retaddr = ql.arch.stack_read(0) + + # we would like to call DialogFunc as soon as DialogBoxParamA returns, so we + # hook its return address. once it returns, 'call_DialogFunc' will be invoked. + hh = ql.hook_address(call_DialogFunc, retaddr) def our_sandbox(path: str, rootfs: str): - ql = Qiling([path], rootfs) + ql = Qiling([path], rootfs, verbose=QL_VERBOSE.DEFAULT) + + # this crackme's logic lies within the function passed to DialogBoxParamA through + # the lpDialogFunc parameter. normally DialogBoxParamA would call the function + # passed through that parameter, but Qiling's implementation for it doesn't do + # that. + # + # to solve this crackme and force the "success" dialog to show, we will: + # 1. set up a mock stdin and feed it with the correct flag + # 1. hook DialogBoxParamA to see where its lpDialogFunc param points to + # 2. set up a valid set of arguments DialogFunc expects to see + # 3. call it and see it greets us with a "success" message + # [step #1] + # set up a mock stdin and feed it with mocked keystrokes ql.os.stdin = pipe.SimpleInStream(sys.stdin.fileno()) - ql.os.stdin.write(b"Ea5yR3versing\n") + ql.os.stdin.write(b'Ea5yR3versing\n') + + # [step #2] + # intercept DialogBoxParamA on exit + ql.os.set_api('DialogBoxParamA', hook_DialogBoxParamA_onexit, QL_INTERCEPT.EXIT) - ql.hook_address(force_call_dialog_func, 0x00401016) ql.run() if __name__ == "__main__": - # Flag is : Ea5yR3versing our_sandbox(r"rootfs/x86_windows/bin/Easy_CrackMe.exe", r"rootfs/x86_windows") diff --git a/examples/hello_x8664_linux_disasm.py b/examples/hello_x8664_linux_disasm.py index 9adcfe94c..9719828df 100644 --- a/examples/hello_x8664_linux_disasm.py +++ b/examples/hello_x8664_linux_disasm.py @@ -6,9 +6,29 @@ import sys sys.path.append("..") +from typing import Mapping from capstone import Cs + from qiling import Qiling +def __map_regs() -> Mapping[int, int]: + """Map Capstone x86 regs definitions to Unicorn's. + """ + + from capstone import x86_const as cs_x86_const + from unicorn import x86_const as uc_x86_const + + def __canonicalized_mapping(module, prefix: str) -> Mapping[str, int]: + return dict((k[len(prefix):], getattr(module, k)) for k in dir(module) if k.startswith(prefix)) + + cs_x86_regs = __canonicalized_mapping(cs_x86_const, 'X86_REG') + uc_x86_regs = __canonicalized_mapping(uc_x86_const, 'UC_X86_REG') + + return dict((cs_x86_regs[k], uc_x86_regs[k]) for k in cs_x86_regs if k in uc_x86_regs) + +# capstone to unicorn regs mapping +CS_UC_REGS = __map_regs() + def trace(ql: Qiling, address: int, size: int, md: Cs): """Emit tracing info for each and every instruction that is about to be executed. @@ -19,27 +39,36 @@ def trace(ql: Qiling, address: int, size: int, md: Cs): md: initialized disassembler object """ + # read current instruction bytes and disassemble it buf = ql.mem.read(address, size) - nibbles = ql.arch.bits // 4 + insn = next(md.disasm(buf, address)) - esc_dgray = "\x1b[90m" - esc_reset = "\x1b[39m" + nibbles = ql.arch.bits // 4 + color_faded = '\033[2m' + color_reset = '\033[0m' - for insn in md.disasm(buf, address): - opcode = ''.join(f'{b:02x}' for b in insn.bytes) + # get values of the registers referenced by this instruction. + # + # note: since this method is called before the instruction has been emulated, the 'rip' + # register still points to the current instruction, while the instruction considers it + # as if it was pointing to the next one. that will cause 'rip' to show an incorrect value + reads = (f'{md.reg_name(reg)} = {ql.arch.regs.read(CS_UC_REGS[reg]):#x}' for reg in insn.regs_access()[0]) - # BUG: insn.regs_read doesn't work well, so we use insn.regs_access()[0] instead - reads = (f'{md.reg_name(reg)} = {ql.arch.regs.read(reg):#x}' for reg in insn.regs_access()[0]) - trace_line = f'{insn.address:0{nibbles}x} | {opcode:20s} {insn.mnemonic:10} {insn.op_str:35s} | {", ".join(reads)}' + # construct a human-readable trace line + trace_line = f'{insn.address:0{nibbles}x} | {insn.bytes.hex():24s} {insn.mnemonic:12} {insn.op_str:35s} | {", ".join(reads)}' - # emit trace line in dark gray so it would be easier to tell trace info from other log entries - ql.log.info(f'{esc_dgray}{trace_line}{esc_reset}') + # emit the trace line in a faded color, so it would be easier to tell trace info from other log entries + ql.log.info(f'{color_faded}{trace_line}{color_reset}') if __name__ == "__main__": - ql = Qiling(["rootfs/x8664_linux/bin/x8664_hello"], "rootfs/x8664_linux") + ql = Qiling([r"rootfs/x8664_linux/bin/x8664_hello"], r"rootfs/x8664_linux") + # acquire a disassembler instance bound to arch md = ql.arch.disassembler md.detail = True + # register the trace method to be called before each instruction ql.hook_code(trace, user_data=md) + + # go! ql.run() From 1d6bfbbd87e592438a4bc8b8f2003903b1c85972 Mon Sep 17 00:00:00 2001 From: elicn Date: Sat, 5 Feb 2022 23:58:39 +0200 Subject: [PATCH 104/406] Align QlArch with save / restore naming convension --- qiling/arch/arch.py | 4 ++-- qiling/core.py | 4 ++-- qiling/os/linux/thread.py | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/qiling/arch/arch.py b/qiling/arch/arch.py index 69a67b9c2..e147e182a 100644 --- a/qiling/arch/arch.py +++ b/qiling/arch/arch.py @@ -103,12 +103,12 @@ def stack_write(self, offset: int, value: int) -> None: # Unicorn's CPU state save - def context_save(self) -> UcContext: + def save(self) -> UcContext: return self.uc.context_save() # Unicorn's CPU state restore method - def context_restore(self, saved_context: UcContext): + def restore(self, saved_context: UcContext): self.uc.context_restore(saved_context) diff --git a/qiling/core.py b/qiling/core.py index 069f618bb..781d89a23 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -626,7 +626,7 @@ def save(self, reg=True, mem=True, fd=False, cpu_context=False, os_context=False saved_states.update({"fd": self.os.fd.save()}) if cpu_context == True: - saved_states.update({"cpu_context": self.arch.context_save()}) + saved_states.update({"cpu_context": self.arch.save()}) if os_context == True: saved_states.update({"os_context": self.os.save()}) @@ -653,7 +653,7 @@ def restore(self, saved_states=None, snapshot=None): self.mem.restore(saved_states["mem"]) if "cpu_context" in saved_states: - self.arch.context_restore(saved_states["cpu_context"]) + self.arch.restore(saved_states["cpu_context"]) if "reg" in saved_states: self.arch.regs.restore(saved_states["reg"]) diff --git a/qiling/os/linux/thread.py b/qiling/os/linux/thread.py index a3c47ea6b..26e6979e5 100644 --- a/qiling/os/linux/thread.py +++ b/qiling/os/linux/thread.py @@ -303,18 +303,18 @@ def clone(self): return new_thread def save_context(self): - self.saved_context = self.ql.arch.context_save() + self.saved_context = self.ql.arch.save() def restore_context(self): - self.ql.arch.context_restore(self.saved_context) + self.ql.arch.restore(self.saved_context) def set_start_address(self, addr): # We can't modify UcContext directly. - old_context = self.ql.arch.context_save() + old_context = self.ql.arch.save() self.restore_context() self.ql.arch.regs.arch_pc = addr self.save_context() - self.ql.arch.context_restore(old_context) + self.ql.arch.restore(old_context) def set_clear_child_tid_addr(self, addr): self.clear_child_tid_address = addr From 217743eb4ec8e207d30cc8988365ac74ef2e3850 Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 6 Feb 2022 01:30:33 +0200 Subject: [PATCH 105/406] Tweak core save and restore --- qiling/core.py | 38 +++++++++++++++++++------------------- tests/test_elf.py | 2 +- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/qiling/core.py b/qiling/core.py index 781d89a23..cb35fcba2 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -7,7 +7,7 @@ import os, pickle # See https://stackoverflow.com/questions/39740632/python-type-hinting-without-cyclic-imports -from typing import AnyStr, List, MutableMapping, Sequence, Union +from typing import AnyStr, List, Mapping, MutableMapping, Sequence, Union from typing import TYPE_CHECKING from unicorn.unicorn import Uc @@ -613,39 +613,39 @@ def patch(self, offset: int, data: bytes, target: str = None) -> None: # save all qiling instance states - def save(self, reg=True, mem=True, fd=False, cpu_context=False, os_context=False, loader=False, snapshot=None): + def save(self, reg=True, mem=True, fd=False, cpu_context=False, os=False, loader=False, *, snapshot: str = None): saved_states = {} - if reg == True: - saved_states.update({"reg": self.arch.regs.save()}) + if reg: + saved_states["reg"] = self.arch.regs.save() - if mem == True: - saved_states.update({"mem": self.mem.save()}) + if mem: + saved_states["mem"] = self.mem.save() - if fd == True: - saved_states.update({"fd": self.os.fd.save()}) + if fd: + saved_states["fd"] = self.os.fd.save() - if cpu_context == True: - saved_states.update({"cpu_context": self.arch.save()}) + if cpu_context: + saved_states["cpu_context"] = self.arch.save() - if os_context == True: - saved_states.update({"os_context": self.os.save()}) + if os: + saved_states["os"] = self.os.save() - if loader == True: - saved_states.update({"loader": self.loader.save()}) + if loader: + saved_states["loader"] = self.loader.save() - if snapshot != None: + if snapshot is not None: with open(snapshot, "wb") as save_state: pickle.dump(saved_states, save_state) - else: - return saved_states + + return saved_states # restore states qiling instance from saved_states - def restore(self, saved_states=None, snapshot=None): + def restore(self, saved_states: Mapping[str, Any] = {}, *, snapshot: str = None): # snapshot will be ignored if saved_states is set - if saved_states == None and snapshot != None: + if (not saved_states) and (snapshot is not None): with open(snapshot, "rb") as load_state: saved_states = pickle.load(load_state) diff --git a/tests/test_elf.py b/tests/test_elf.py index 98a049717..80f49bef6 100644 --- a/tests/test_elf.py +++ b/tests/test_elf.py @@ -74,7 +74,7 @@ def dump(ql): nonlocal snapshot nonlocal reg nonlocal ctx - ql.save(reg=reg, cpu_context=ctx, os_context=True, loader=True, snapshot=snapshot) + ql.save(reg=reg, cpu_context=ctx, os=True, loader=True, snapshot=snapshot) ql.emu_stop() ql.hook_address(dump, hook_address) From 685c69330ea766a513306ecf2c46dc41b4059006 Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 9 Feb 2022 16:39:43 +0200 Subject: [PATCH 106/406] Remove old debugging hook --- qiling/os/windows/utils.py | 4 ---- qiling/os/windows/windows.py | 1 - 2 files changed, 5 deletions(-) diff --git a/qiling/os/windows/utils.py b/qiling/os/windows/utils.py index f0172b01b..b3db6cfcb 100644 --- a/qiling/os/windows/utils.py +++ b/qiling/os/windows/utils.py @@ -22,10 +22,6 @@ def cmp(a: Comparable, b: Comparable) -> int: return (a > b) - (a < b) -def ql_x86_windows_hook_mem_error(ql: Qiling, access, addr: int, size: int, value: int): - ql.log.debug(f'ERROR: unmapped memory access at {addr:#x}') - return False - def is_file_library(string: str) -> bool: string = string.lower() diff --git a/qiling/os/windows/windows.py b/qiling/os/windows/windows.py index 5ddc23e24..e7f69108e 100644 --- a/qiling/os/windows/windows.py +++ b/qiling/os/windows/windows.py @@ -68,7 +68,6 @@ def __make_fcall_selector(atype: QL_ARCH) -> Callable[[int], QlFunctionCall]: self.argv = self.ql.argv self.env = self.ql.env self.pid = self.profile.getint("KERNEL","pid") - self.ql.hook_mem_unmapped(utils.ql_x86_windows_hook_mem_error) self.automatize_input = self.profile.getboolean("MISC","automatize_input") self.username = self.profile["USER"]["username"] self.windir = self.profile["PATH"]["systemdrive"] + self.profile["PATH"]["windir"] From 4b5a2dce0311e70e2063daa8a72d24a5426dd210 Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 10 Feb 2022 11:55:12 +0200 Subject: [PATCH 107/406] Misc. styling and code quality improvements --- qiling/const.py | 9 ++------- qiling/core.py | 9 ++------- qiling/os/linux/thread.py | 3 ++- qiling/os/posix/posix.py | 10 +--------- qiling/os/utils.py | 2 +- qiling/utils.py | 42 ++++++++++++++++++++++----------------- 6 files changed, 32 insertions(+), 43 deletions(-) diff --git a/qiling/const.py b/qiling/const.py index c81ff81e4..2bb1fe557 100644 --- a/qiling/const.py +++ b/qiling/const.py @@ -51,13 +51,8 @@ class QL_INTERCEPT(IntEnum): ENTER = 2 EXIT = 3 -QL_DEBUGGER_ALL = (QL_DEBUGGER.IDAPRO, QL_DEBUGGER.GDB, QL_DEBUGGER.QDB) - -QL_ARCH_ENDIAN = (QL_ARCH.MIPS, QL_ARCH.ARM) - -QL_OS_NONPID = (QL_OS.DOS, QL_OS.UEFI) -QL_OS_POSIX = (QL_OS.LINUX, QL_OS.FREEBSD, QL_OS.MACOS, QL_OS.QNX) - +QL_OS_NONPID = (QL_OS.DOS, QL_OS.UEFI) +QL_OS_POSIX = (QL_OS.LINUX, QL_OS.FREEBSD, QL_OS.MACOS, QL_OS.QNX) QL_OS_BAREMETAL = (QL_OS.MCU,) QL_OS_INTERPRETER = (QL_OS.EVM,) diff --git a/qiling/core.py b/qiling/core.py index cb35fcba2..a3e17b330 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -173,12 +173,7 @@ def __init__( ########## # Logger # ########## - self._log_file_fd, self._log_filter = ql_setup_logger(self, - log_file, - console, - self._filter, - log_override, - log_plain) + self._log_file_fd, self._log_filter = ql_setup_logger(self, log_file, console, self._filter, log_override, log_plain) self.verbose = verbose @@ -386,7 +381,7 @@ def host(self) -> QlHost: return self._host @property - def internal_exception(self) -> Exception: + def internal_exception(self) -> Optional[Exception]: """ Internal exception catched during Unicorn callback. Not intended for regular users. Type: Exception diff --git a/qiling/os/linux/thread.py b/qiling/os/linux/thread.py index 26e6979e5..03ef88dfd 100644 --- a/qiling/os/linux/thread.py +++ b/qiling/os/linux/thread.py @@ -10,6 +10,7 @@ from unicorn.unicorn import UcError +from qiling import Qiling from qiling.os.thread import * from qiling.arch.x86_const import * from qiling.exception import QlErrorExecutionStop @@ -25,7 +26,7 @@ THREAD_STATUS_SUSPEND = 5 class QlLinuxThread(QlThread): - def __init__(self, ql, start_address, exit_point, context = None, set_child_tid_addr = None, thread_id = None): + def __init__(self, ql: Qiling, start_address: int, exit_point: int, context = None, set_child_tid_addr = None, thread_id: int = None): super(QlLinuxThread, self).__init__(ql) if not thread_id: self.new_thread_id() diff --git a/qiling/os/posix/posix.py b/qiling/os/posix/posix.py index e4d5a9db3..2d11c871a 100644 --- a/qiling/os/posix/posix.py +++ b/qiling/os/posix/posix.py @@ -262,15 +262,7 @@ def load_syscall(self): self.utils.print_function(self.ql.arch.regs.arch_pc, syscall_basename, args, sret, False) # record syscall statistics - self.stats.syscalls.setdefault(syscall_name, []).append({ - "params": dict(zip(param_names, params)), - "result": retval, - "address": self.ql.arch.regs.arch_pc, - "return_address": None, - "position": self.stats.syscalls_counter - }) - - self.stats.syscalls_counter += 1 + self.stats.log_api_call(self.ql.arch.regs.arch_pc, syscall_name, dict(zip(param_names, params)), retval, None) else: self.ql.log.warning(f'{self.ql.arch.regs.arch_pc:#x}: syscall {syscall_name} number = {syscall_id:#x}({syscall_id:d}) not implemented') diff --git a/qiling/os/utils.py b/qiling/os/utils.py index 6b942d95c..47b206863 100644 --- a/qiling/os/utils.py +++ b/qiling/os/utils.py @@ -165,7 +165,7 @@ def __common_printf(self, format: str, args: MutableSequence, wstring: bool): def va_list(self, format: str, ptr: int) -> MutableSequence[int]: count = format.count("%") - return [self.ql.unpack(self.ql.mem.read(ptr + i * self.ql.arch.pointersize, self.ql.arch.pointersize)) for i in range(count)] + return [self.ql.mem.read_ptr(ptr + i * self.ql.arch.pointersize) for i in range(count)] def sprintf(self, buff: int, format: str, args: MutableSequence, wstring: bool = False) -> int: out = self.__common_printf(format, args, wstring) diff --git a/qiling/utils.py b/qiling/utils.py index 56b00ec04..4e318d902 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -439,9 +439,13 @@ def loader_setup(ql, ostype: QL_OS, libcache: bool): return obj(ql, *args) -def component_setup(component_type, component_name, ql): - function_name = "Ql" + component_name.capitalize() + "Manager" - return ql_get_module_function(f"qiling.{component_type}.{component_name}", function_name)(ql) +def component_setup(component_type: str, component_name: str, ql): + component_path = f'qiling.{component_type}.{component_name}' + component_class = f'Ql{component_name.capitalize()}Manager' + + obj = ql_get_module_function(component_path, component_class) + + return obj(ql) def debugger_setup(options, ql): @@ -461,9 +465,6 @@ def debugger_setup(options, ql): return None def arch_setup(archtype: QL_ARCH, endian: QL_ENDIAN, thumb: bool, ql): - if not ql_is_valid_arch(archtype): - raise QlErrorArch(f'Invalid architecture type') - # set endianess and thumb mode for arm-based archs if archtype == QL_ARCH.ARM: args = [endian, thumb] @@ -496,20 +497,25 @@ def arch_setup(archtype: QL_ARCH, endian: QL_ENDIAN, thumb: bool, ql): return obj(ql, *args) -# This function is extracted from os_setup so I put it here. -def ql_syscall_mapping_function(ostype): - ostype_str = ostype_convert_str(ostype) - return ql_get_module_function(f"qiling.os.{ostype_str.lower()}.map_syscall", "map_syscall") +# This function is extracted from os_setup (QlOsPosix) so I put it here. +def ql_syscall_mapping_function(ostype: QL_OS): + qlos_name = ostype_convert_str(ostype) + qlos_path = f'qiling.os.{qlos_name.lower()}.map_syscall' + qlos_func = 'map_syscall' + + func = ql_get_module_function(qlos_path, qlos_func) + + return func def os_setup(ostype: QL_OS, ql): - if not ql_is_valid_ostype(ostype): - raise QlErrorOsType("Invalid OSType") + qlos_name = ostype_convert_str(ostype) + qlos_path = f'qiling.os.{qlos_name.lower()}.{qlos_name.lower()}' + qlos_class = f'QlOs{qlos_name.capitalize()}' + + obj = ql_get_module_function(qlos_path, qlos_class) - ostype_str = ostype_convert_str(ostype) - ostype_str = ostype_str.capitalize() - function_name = "QlOs" + ostype_str - return ql_get_module_function(f"qiling.os.{ostype_str.lower()}.{ostype_str.lower()}", function_name)(ql) + return obj(ql) def profile_setup(ql, ostype: QL_OS, filename: Optional[str]): @@ -590,10 +596,10 @@ def ql_setup_logger(ql, log_file: Optional[str], console: bool, filters: Optiona # If there aren't any filters, we do add the filters until users specify any. log_filter = None - if filters is not None and len(filters) != 0: + if filters: log_filter = RegexFilter(filters) log.addFilter(log_filter) - + log.setLevel(logging.INFO) return log, log_filter From 6989e5a6bde0e200ff3b0534e4f529d97bdb1bac Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 10 Feb 2022 18:56:07 +0200 Subject: [PATCH 108/406] Pythonize QlFileDes and its usages --- qiling/os/linux/linux.py | 7 +- qiling/os/macos/syscall.py | 9 +- qiling/os/posix/posix.py | 4 +- qiling/os/posix/syscall/fcntl.py | 64 +++--- qiling/os/posix/syscall/mman.py | 33 +-- qiling/os/posix/syscall/sendfile.py | 35 ++- qiling/os/posix/syscall/socket.py | 340 +++++++++++++++------------- qiling/os/posix/syscall/stat.py | 58 +++-- qiling/os/posix/syscall/unistd.py | 198 ++++++++-------- qiling/utils.py | 20 +- 10 files changed, 402 insertions(+), 366 deletions(-) diff --git a/qiling/os/linux/linux.py b/qiling/os/linux/linux.py index 558159c66..75b4f5813 100644 --- a/qiling/os/linux/linux.py +++ b/qiling/os/linux/linux.py @@ -102,10 +102,11 @@ def load(self): self.ql.arch.enable_float() self.ql.hook_intno(self.hook_syscall, 8) self.thread_class = None - - for i in range(NR_OPEN): + + # on fork or execve, do not inherit opened files tagged as 'close on exec' + for i in range(len(self.fd)): if getattr(self.fd[i], 'close_on_exec', 0): - self.fd[i] = 0 + self.fd[i] = None def hook_syscall(self, ql, intno = None): return self.load_syscall() diff --git a/qiling/os/macos/syscall.py b/qiling/os/macos/syscall.py index 861c7bca4..6093d374c 100644 --- a/qiling/os/macos/syscall.py +++ b/qiling/os/macos/syscall.py @@ -204,10 +204,12 @@ def ql_syscall_pread(ql, fd, buf, nbyte, offset, *args, **kw): ql.log.debug("pread(fd: 0x%x, buf: 0x%x, nbyte: 0x%x, offset: 0x%x)" % ( fd, buf, nbyte, offset )) - if fd >= 0 and fd <= MAX_FD_SIZE: + + if fd in range(MAX_FD_SIZE + 1): ql.os.fd[fd].seek(offset) data = ql.os.fd[fd].read(nbyte) ql.mem.write(buf, data) + set_eflags_cf(ql, 0x0) return nbyte @@ -358,10 +360,7 @@ def ql_syscall_open_nocancel(ql, filename, flags, mode, *args, **kw): flags = flags & 0xffffffff mode = mode & 0xffffffff - for i in range(256): - if ql.os.fd[i] == 0: - idx = i - break + idx = next((i for i in range(MAX_FD_SIZE + 1) if ql.os.fd[i] is None), -1) if idx == -1: regreturn = -1 diff --git a/qiling/os/posix/posix.py b/qiling/os/posix/posix.py index 2d11c871a..1c9b12591 100644 --- a/qiling/os/posix/posix.py +++ b/qiling/os/posix/posix.py @@ -17,7 +17,7 @@ from qiling.const import QL_ARCH, QL_OS, QL_INTERCEPT from qiling.exception import QlErrorSyscallNotFound from qiling.os.os import QlOs -from qiling.os.posix.const import errors, NR_OPEN +from qiling.os.posix.const import errors from qiling.utils import QlFileDes, ostype_convert_str, ql_get_module_function, ql_syscall_mapping_function from qiling.os.posix.syscall import * @@ -106,7 +106,7 @@ def __init__(self, ql: Qiling): QL_ARCH.RISCV64: riscv64, }[self.ql.arch.type](self.ql.arch) - self._fd = QlFileDes([0] * NR_OPEN) + self._fd = QlFileDes() # the QlOs constructor cannot assign the standard streams using their designated properties since # it runs before the _fd array is declared. instead, it assigns them to the private members and here diff --git a/qiling/os/posix/syscall/fcntl.py b/qiling/os/posix/syscall/fcntl.py index 72c0f127b..a3ddf1061 100644 --- a/qiling/os/posix/syscall/fcntl.py +++ b/qiling/os/posix/syscall/fcntl.py @@ -20,7 +20,7 @@ def ql_syscall_open(ql: Qiling, filename: int, flags: int, mode: int): flags &= 0xffffffff mode &= 0xffffffff - idx = next((i for i in range(NR_OPEN) if ql.os.fd[i] == 0), -1) + idx = next((i for i in range(NR_OPEN) if ql.os.fd[i] is None), -1) if idx == -1: regreturn = -EMFILE @@ -29,7 +29,6 @@ def ql_syscall_open(ql: Qiling, filename: int, flags: int, mode: int): if ql.arch.type == QL_ARCH.ARM and ql.ostype != QL_OS.QNX: mode = 0 - #flags = ql_open_flag_mapping(ql, flags) flags = ql_open_flag_mapping(ql, flags) ql.os.fd[idx] = ql.os.fs_mapper.open_ql_file(path, flags, mode) regreturn = idx @@ -56,7 +55,7 @@ def ql_syscall_creat(ql: Qiling, filename: int, mode: int): flags &= 0xffffffff mode &= 0xffffffff - idx = next((i for i in range(NR_OPEN) if ql.os.fd[i] == 0), -1) + idx = next((i for i in range(NR_OPEN) if ql.os.fd[i] is None), -1) if idx == -1: regreturn = -ENOMEM @@ -88,7 +87,7 @@ def ql_syscall_openat(ql: Qiling, fd: int, path: int, flags: int, mode: int): flags &= 0xffffffff mode &= 0xffffffff - idx = next((i for i in range(NR_OPEN) if ql.os.fd[i] == 0), -1) + idx = next((i for i in range(NR_OPEN) if ql.os.fd[i] is None), -1) if idx == -1: regreturn = -EMFILE @@ -117,24 +116,26 @@ def ql_syscall_openat(ql: Qiling, fd: int, path: int, flags: int, mode: int): def ql_syscall_fcntl(ql: Qiling, fd: int, cmd: int, arg: int): - if not (0 <= fd < NR_OPEN) or ql.os.fd[fd] == 0: + if fd not in range(NR_OPEN): return -EBADF f = ql.os.fd[fd] + if f is None: + return -EBADF + if cmd == F_DUPFD: - if 0 <= arg < NR_OPEN: - for idx, val in enumerate(ql.os.fd): - if val == 0 and idx >= arg: - new_fd = ql.os.fd[fd].dup() - ql.os.fd[idx] = new_fd - regreturn = idx - break - else: - regreturn = -EMFILE - else: + if arg not in range(NR_OPEN): regreturn = -EINVAL + for idx, val in enumerate(ql.os.fd, arg): + if val is None: + ql.os.fd[idx] = f.dup() + regreturn = idx + break + else: + regreturn = -EMFILE + elif cmd == F_GETFD: regreturn = getattr(f, "close_on_exec", 0) @@ -143,10 +144,10 @@ def ql_syscall_fcntl(ql: Qiling, fd: int, cmd: int, arg: int): regreturn = 0 elif cmd == F_GETFL: - regreturn = ql.os.fd[fd].fcntl(cmd, arg) + regreturn = f.fcntl(cmd, arg) elif cmd == F_SETFL: - ql.os.fd[fd].fcntl(cmd, arg) + f.fcntl(cmd, arg) regreturn = 0 else: @@ -156,28 +157,31 @@ def ql_syscall_fcntl(ql: Qiling, fd: int, cmd: int, arg: int): def ql_syscall_fcntl64(ql: Qiling, fd: int, cmd: int, arg: int): + if fd not in range(NR_OPEN): + return -1 + + f = ql.os.fd[fd] + + if f is None: + return -1 # https://linux.die.net/man/2/fcntl64 if cmd == F_DUPFD: - if 0 <= arg < NR_OPEN and 0 <= fd < NR_OPEN: - if ql.os.fd[fd] != 0: - new_fd = ql.os.fd[fd].dup() - for idx, val in enumerate(ql.os.fd): - if val == 0 and idx >= arg: - ql.os.fd[idx] = new_fd - regreturn = idx - break - else: - regreturn = -1 - else: + if arg not in range(NR_OPEN): regreturn = -1 + for idx, val in enumerate(ql.os.fd, arg): + if val is None: + ql.os.fd[idx] = f.dup() + regreturn = idx + break + elif cmd == F_GETFL: regreturn = 2 elif cmd == F_SETFL: - if isinstance(ql.os.fd[fd], ql_socket): - ql.os.fd[fd].fcntl(cmd, arg) + if isinstance(f, ql_socket): + f.fcntl(cmd, arg) regreturn = 0 elif cmd == F_GETFD: diff --git a/qiling/os/posix/syscall/mman.py b/qiling/os/posix/syscall/mman.py index 23e4c2546..cf7585e9c 100755 --- a/qiling/os/posix/syscall/mman.py +++ b/qiling/os/posix/syscall/mman.py @@ -123,21 +123,24 @@ def syscall_mmap_impl(ql: Qiling, addr: int, mlen: int, prot: int, flags: int, f except Exception as e: raise QlMemoryMappedError("Error: trying to zero memory") - if ((flags & MAP_ANONYMOUS) == 0) and 0 <= fd < NR_OPEN and ql.os.fd[fd] != 0: - ql.os.fd[fd].seek(pgoffset) - data = ql.os.fd[fd].read(mlen) - mem_info = str(ql.os.fd[fd].name) - ql.os.fd[fd]._is_map_shared = flags & MAP_SHARED - ql.os.fd[fd]._mapped_offset = pgoffset - ql.log.debug("mem write : " + hex(len(data))) - ql.log.debug("mem mmap : " + mem_info) - - ql.mem.change_mapinfo(mmap_base, mmap_base + mmap_size, mem_info=("[%s] " % api_name) + mem_info) - try: - ql.mem.write(mmap_base, data) - except Exception as e: - ql.log.debug(e) - raise QlMemoryMappedError("Error: trying to write memory: ") + if ((flags & MAP_ANONYMOUS) == 0) and fd in range(NR_OPEN): + f = ql.os.fd[fd] + + if f is not None: + f.seek(pgoffset) + data = f.read(mlen) + mem_info = str(f.name) + f._is_map_shared = flags & MAP_SHARED + f._mapped_offset = pgoffset + ql.log.debug("mem write : " + hex(len(data))) + ql.log.debug("mem mmap : " + mem_info) + + ql.mem.change_mapinfo(mmap_base, mmap_base + mmap_size, mem_info=("[%s] " % api_name) + mem_info) + try: + ql.mem.write(mmap_base, data) + except Exception as e: + ql.log.debug(e) + raise QlMemoryMappedError("Error: trying to write memory: ") return mmap_base diff --git a/qiling/os/posix/syscall/sendfile.py b/qiling/os/posix/syscall/sendfile.py index 4d4e6e46c..df41141f5 100644 --- a/qiling/os/posix/syscall/sendfile.py +++ b/qiling/os/posix/syscall/sendfile.py @@ -14,26 +14,25 @@ def ql_syscall_sendfile64(ql: Qiling, out_fd: int, in_fd: int, offset: int, coun def ql_syscall_sendfile(ql: Qiling, out_fd: int, in_fd: int, offset: int, count: int): # https://man7.org/linux/man-pages/man2/sendfile.2.html - if 0 <= out_fd < NR_OPEN and 0 <= in_fd < NR_OPEN \ - and ql.os.fd[out_fd] != 0 and ql.os.fd[in_fd] != 0: - in_fd_pos = ql.os.fd[in_fd].tell() - if offset: - # Handle sendfile64 and sendfile offset_ptr - offset = ql.unpack(ql.mem.read(offset, ql.arch.pointersize)) - else: - offset = in_fd_pos + if in_fd not in range(NR_OPEN) or out_fd not in range(NR_OPEN): + return -1 - ql.os.fd[in_fd].lseek(offset) - buf = ql.os.fd[in_fd].read(count) - if offset: - current_offset = ql.os.fd[in_fd].tell() - ql.mem.write(offset, ql.pack(current_offset)) - ql.os.fd[in_fd].lseek(in_fd_pos) + ifile = ql.os.fd[in_fd] + ofile = ql.os.fd[out_fd] - regreturn = ql.os.fd[out_fd].write(buf) + if ifile is None or ofile is None: + return -1 - else: - regreturn = -1 + ifile_pos = ifile.tell() + offset = ql.mem.read_ptr(offset) if offset else ifile_pos - return regreturn + ifile.lseek(offset) + buf = ifile.read(count) + + if offset: + current_offset = ifile.tell() + ql.mem.write(offset, ql.pack(current_offset)) + ifile.lseek(ifile_pos) + + return ofile.write(buf) diff --git a/qiling/os/posix/syscall/socket.py b/qiling/os/posix/syscall/socket.py index 13b78b94a..15556b078 100644 --- a/qiling/os/posix/syscall/socket.py +++ b/qiling/os/posix/syscall/socket.py @@ -68,43 +68,32 @@ def ql_bin_to_ip(ip): def ql_syscall_socket(ql: Qiling, socket_domain, socket_type, socket_protocol): - idx = -1 - for i in range(NR_OPEN): - if ql.os.fd[i] == 0: - idx = i - break - try: - if idx == -1: - regreturn = -1 - else: - # ql_socket.open should use host platform based socket_type. - try: - emu_socket_value = socket_type - emu_socket_type = socket_type_mapping(socket_type, ql.arch.type, ql.ostype) - socket_type = getattr(socket, emu_socket_type) - ql.log.debug("Convert emu_socket_type {}:{} to host platform based socket_type {}:{}".format( - emu_socket_type, emu_socket_value, emu_socket_type, socket_type)) + idx = next((i for i in range(NR_OPEN) if ql.os.fd[i] is None), -1) - except AttributeError: - ql.log.error("Can't convert emu_socket_type {}:{} to host platform based socket_type".format( - emu_socket_type, emu_socket_value)) - raise + if idx == -1: + regreturn = -1 + else: + # ql_socket.open should use host platform based socket_type. + try: + emu_socket_value = socket_type + emu_socket_type = socket_type_mapping(socket_type, ql.arch.type, ql.ostype) + socket_type = getattr(socket, emu_socket_type) + ql.log.debug(f'Convert emu_socket_type {emu_socket_type}:{emu_socket_value} to host platform based socket_type {emu_socket_type}:{socket_type}') - except Exception: - ql.log.error("Can't convert emu_socket_type {} to host platform based socket_type".format( - emu_socket_value)) - raise + except AttributeError: + ql.log.error(f'Cannot convert emu_socket_type {emu_socket_type}:{emu_socket_value} to host platform based socket_type') + raise - if ql.verbose >= QL_VERBOSE.DEBUG: # set REUSEADDR options under debug mode - ql.os.fd[idx] = ql_socket.open(socket_domain, socket_type, socket_protocol, (socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)) - else: - ql.os.fd[idx] = ql_socket.open(socket_domain, socket_type, socket_protocol) + except Exception: + ql.log.error(f'Cannot convert emu_socket_type {emu_socket_value} to host platform based socket_type') + raise - regreturn = (idx) + if ql.verbose >= QL_VERBOSE.DEBUG: # set REUSEADDR options under debug mode + ql.os.fd[idx] = ql_socket.open(socket_domain, socket_type, socket_protocol, (socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)) + else: + ql.os.fd[idx] = ql_socket.open(socket_domain, socket_type, socket_protocol) - except Exception: - ql.log.exception("") - regreturn = -1 + regreturn = idx socket_type = socket_type_mapping(socket_type, ql.arch.type, ql.ostype) socket_domain = socket_domain_mapping(socket_domain, ql.arch.type, ql.ostype) @@ -151,7 +140,7 @@ def ql_syscall_connect(ql: Qiling, connect_sockfd, connect_addr, connect_addrlen def ql_syscall_getsockopt(ql: Qiling, sockfd, level, optname, optval_addr, optlen_addr): - if not (0 <= sockfd < NR_OPEN) or ql.os.fd[sockfd] == 0: + if sockfd not in range(NR_OPEN) or ql.os.fd[sockfd] is None: return -EBADF try: @@ -212,8 +201,7 @@ def ql_syscall_getsockopt(ql: Qiling, sockfd, level, optname, optval_addr, optle def ql_syscall_setsockopt(ql: Qiling, sockfd, level, optname, optval_addr, optlen): - if not (0 <= sockfd < NR_OPEN) or\ - ql.os.fd[sockfd] == 0: + if sockfd not in range(NR_OPEN) or ql.os.fd[sockfd] is None: return -EBADF regreturn = 0 @@ -277,15 +265,17 @@ def ql_syscall_setsockopt(ql: Qiling, sockfd, level, optname, optval_addr, optle return regreturn -def ql_syscall_shutdown(ql: Qiling, shutdown_fd, shutdown_how): - +def ql_syscall_shutdown(ql: Qiling, fd: int, how: int): regreturn = 0 - - if 0 <= shutdown_fd < NR_OPEN and ql.os.fd[shutdown_fd] != 0: - try: - ql.os.fd[shutdown_fd].shutdown(shutdown_how) - except: - regreturn = -1 + + if fd in range(NR_OPEN): + sock = ql.os.fd[fd] + + if sock is not None: + try: + sock.shutdown(how) + except: + regreturn = -1 return regreturn @@ -387,18 +377,24 @@ def ql_syscall_getpeername(ql: Qiling, sockfd: int, addr: int, addrlenptr: int): return regreturn -def ql_syscall_listen(ql: Qiling, listen_sockfd, listen_backlog): - if 0 <= listen_sockfd < NR_OPEN and ql.os.fd[listen_sockfd] != 0: - try: - ql.os.fd[listen_sockfd].listen(listen_backlog) - regreturn = 0 - except: - if ql.verbose >= QL_VERBOSE.DEBUG: - raise - regreturn = -1 - else: - regreturn = -1 - return regreturn +def ql_syscall_listen(ql: Qiling, sockfd: int, backlog: int): + if sockfd not in range(NR_OPEN): + return -1 + + sock = ql.os.fd[sockfd] + + if sock is None: + return -1 + + try: + sock.listen(backlog) + except: + if ql.verbose >= QL_VERBOSE.DEBUG: + raise + + return -1 + + return 0 def ql_syscall_accept(ql: Qiling, accept_sockfd, accept_addr, accept_addrlen): @@ -412,14 +408,12 @@ def inet_addr(ip): return ret try: conn, address = ql.os.fd[accept_sockfd].accept() - if conn == None: + + if conn is None: return -1 - idx = -1 - for i in range(NR_OPEN): - if ql.os.fd[i] == 0: - idx = i - break + idx = next((i for i in range(NR_OPEN) if ql.os.fd[i] is None), -1) + if idx == -1: regreturn = -1 else: @@ -441,44 +435,51 @@ def inet_addr(ip): return regreturn -def ql_syscall_recv(ql: Qiling, recv_sockfd, recv_buf, recv_len, recv_flags): - if 0 <= recv_sockfd < NR_OPEN and ql.os.fd[recv_sockfd] != 0: - tmp_buf = ql.os.fd[recv_sockfd].recv(recv_len, recv_flags) - if tmp_buf: - ql.log.debug("recv() CONTENT:") - ql.log.debug("%s" % tmp_buf) - ql.mem.write(recv_buf, tmp_buf) - regreturn = len(tmp_buf) - else: - regreturn = -1 - return regreturn +def ql_syscall_recv(ql: Qiling, sockfd: int, buf: int, length: int, flags: int): + if sockfd not in range(NR_OPEN): + return -1 + sock = ql.os.fd[sockfd] + + if sock is None: + return -1 + + content = sock.recv(length, flags) + + if content: + ql.log.debug("recv() CONTENT:") + ql.log.debug("%s" % content) + + ql.mem.write(buf, content) + + return len(content) + + +def ql_syscall_send(ql: Qiling, sockfd: int, buf: int, length: int, flags: int): + if sockfd not in range(NR_OPEN): + return -1 + + sock = ql.os.fd[sockfd] + + if sock is None: + return -1 + + try: + content = bytes(ql.mem.read(buf, length)) + regreturn = sock.send(content, flags) + except: + regreturn = 0 + ql.log.info(sys.exc_info()[0]) + + if ql.verbose >= QL_VERBOSE.DEBUG: + raise -def ql_syscall_send(ql: Qiling, send_sockfd, send_buf, send_len, send_flags): - regreturn = 0 - if 0 <= send_sockfd < NR_OPEN and ql.os.fd[send_sockfd] != 0: - try: - ql.log.debug("debug send() start") - tmp_buf = ql.mem.read(send_buf, send_len) - ql.log.debug("fd is " + str(send_sockfd)) - ql.log.debug("send() CONTENT:") - ql.log.debug("%s" % str(tmp_buf)) - ql.log.debug("send() flag is " + str(send_flags)) - ql.log.debug("send() len is " + str(send_len)) - regreturn = ql.os.fd[send_sockfd].send(bytes(tmp_buf), send_flags) - ql.log.debug("debug send end") - except: - ql.log.info(sys.exc_info()[0]) - if ql.verbose >= QL_VERBOSE.DEBUG: - raise - else: - regreturn = -1 return regreturn -def ql_syscall_recvmsg(ql: Qiling, sockfd, msg_addr, flags): +def ql_syscall_recvmsg(ql: Qiling, sockfd: int, msg_addr: int, flags: int): regreturn = 0 - if 0 <= sockfd < NR_OPEN and ql.os.fd[sockfd] != 0: + if sockfd not in range(NR_OPEN) and ql.os.fd[sockfd] is not None: msg = msghdr.load(ql, msg_addr) try: @@ -521,81 +522,96 @@ def ql_syscall_recvmsg(ql: Qiling, sockfd, msg_addr, flags): return regreturn -def ql_syscall_recvfrom(ql: Qiling, recvfrom_sockfd, recvfrom_buf, recvfrom_len, recvfrom_flags, recvfrom_addr, recvfrom_addrlen): - # For x8664, recvfrom() is called finally when calling recv() in TCP communications +def ql_syscall_recvfrom(ql: Qiling, sockfd: int, buf: int, length: int, flags: int, addr: int, addrlen: int): + if sockfd not in range(NR_OPEN): + return -1 + + sock = ql.os.fd[sockfd] + + if sock is None: + return -1 + SOCK_STREAM = 1 - if ql.os.fd[recvfrom_sockfd].socktype == SOCK_STREAM: - return ql_syscall_recv(ql, recvfrom_sockfd, recvfrom_buf, recvfrom_len, recvfrom_flags) + + # For x8664, recvfrom() is called finally when calling recv() in TCP communications + if sock.socktype == SOCK_STREAM: + return ql_syscall_recv(ql, sockfd, buf, length, flags) + + tmp_buf, tmp_addr = sock.recvfrom(length, flags) + + if tmp_buf: + ql.log.debug("recvfrom() CONTENT:") + ql.log.debug("%s" % tmp_buf) + + sin_family = int(sock.family) + data = struct.pack("H", tmp_addr[1]) - data += ipaddress.ip_address(tmp_addr[0]).packed - addrlen = ql.unpack(ql.mem.read(recvfrom_addrlen, ql.arch.pointersize)) - data = data[:addrlen] - ql.mem.write(recvfrom_addr, data) - - ql.mem.write(recvfrom_buf, tmp_buf) - regreturn = len(tmp_buf) - else: - regreturn = -1 + ql.log.debug("recvfrom() addr is %s:%d" % (tmp_addr[0], tmp_addr[1])) + data += struct.pack(">H", tmp_addr[1]) + data += ipaddress.ip_address(tmp_addr[0]).packed + addrlen = ql.mem.read_ptr(addrlen) + data = data[:addrlen] - return regreturn + ql.mem.write(addr, data) + ql.mem.write(buf, tmp_buf) + return len(tmp_buf) + + +def ql_syscall_sendto(ql: Qiling, sockfd: int, sendto_buf, sendto_len, sendto_flags, sendto_addr, sendto_addrlen): + if sockfd not in range(NR_OPEN): + return -1 + + sock = ql.os.fd[sockfd] + + if sock is None: + return -1 -def ql_syscall_sendto(ql: Qiling, sendto_sockfd, sendto_buf, sendto_len, sendto_flags, sendto_addr, sendto_addrlen): - # For x8664, sendto() is called finally when calling send() in TCP communications SOCK_STREAM = 1 - if ql.os.fd[sendto_sockfd].socktype == SOCK_STREAM: - return ql_syscall_send(ql, sendto_sockfd, sendto_buf, sendto_len, sendto_flags) - else: - regreturn = 0 - if 0 <= sendto_sockfd < NR_OPEN and ql.os.fd[sendto_sockfd] != 0: - try: - ql.log.debug("debug sendto() start") - tmp_buf = ql.mem.read(sendto_buf, sendto_len) - if ql.arch.type== QL_ARCH.X8664: - data = ql.mem.read(sendto_addr, 8) - else: - data = ql.mem.read(sendto_addr, sendto_addrlen) - - sin_family, = struct.unpack("HI", data[2:8]) - host = ql_bin_to_ip(host) - - if sin_family == 1: - path = data[2 : ].split(b'\x00')[0] - path = ql.os.path.transform_to_real_path(path.decode()) - - ql.log.debug("fd is " + str(sendto_sockfd)) - ql.log.debug("sendto() CONTENT:") - ql.log.debug("%s" % tmp_buf) - ql.log.debug("sendto() flag is " + str(sendto_flags)) - ql.log.debug("sendto() len is " + str(sendto_len)) - if sin_family == 1: - ql.log.debug("sendto() path is " + str(path)) - regreturn = ql.os.fd[sendto_sockfd].sendto(bytes(tmp_buf), sendto_flags, path) - else: - ql.log.debug("sendto() addr is %s:%d" % (host, port)) - regreturn = ql.os.fd[sendto_sockfd].sendto(bytes(tmp_buf), sendto_flags, (host, port)) - ql.log.debug("debug sendto end") - except: - ql.log.debug(sys.exc_info()[0]) - if ql.verbose >= QL_VERBOSE.DEBUG: - raise + # For x8664, sendto() is called finally when calling send() in TCP communications + if sock.socktype == SOCK_STREAM: + return ql_syscall_send(ql, sockfd, sendto_buf, sendto_len, sendto_flags) + + regreturn = 0 + + try: + ql.log.debug("debug sendto() start") + tmp_buf = ql.mem.read(sendto_buf, sendto_len) + + if ql.arch.type== QL_ARCH.X8664: + data = ql.mem.read(sendto_addr, 8) else: - regreturn = -1 + data = ql.mem.read(sendto_addr, sendto_addrlen) + + sin_family, = struct.unpack("HI", data[2:8]) + host = ql_bin_to_ip(host) + + ql.log.debug("fd is " + str(sockfd)) + ql.log.debug("sendto() CONTENT:") + ql.log.debug("%s" % tmp_buf) + ql.log.debug("sendto() flag is " + str(sendto_flags)) + ql.log.debug("sendto() len is " + str(sendto_len)) + + if sin_family == 1: + path = data[2 : ].split(b'\x00')[0] + path = ql.os.path.transform_to_real_path(path.decode()) - return regreturn + ql.log.debug("sendto() path is " + str(path)) + regreturn = sock.sendto(bytes(tmp_buf), sendto_flags, path) + else: + ql.log.debug("sendto() addr is %s:%d" % (host, port)) + regreturn = sock.sendto(bytes(tmp_buf), sendto_flags, (host, port)) + ql.log.debug("debug sendto end") + except: + ql.log.debug(sys.exc_info()[0]) + + if ql.verbose >= QL_VERBOSE.DEBUG: + raise + + return regreturn diff --git a/qiling/os/posix/syscall/stat.py b/qiling/os/posix/syscall/stat.py index 5bd2fe6a4..0ca5fd6c2 100644 --- a/qiling/os/posix/syscall/stat.py +++ b/qiling/os/posix/syscall/stat.py @@ -1064,7 +1064,7 @@ def ql_syscall_chmod(ql: Qiling, filename: int, mode: int): return 0 def ql_syscall_fchmod(ql: Qiling, fd: int, mode: int): - if not (0 < fd < NR_OPEN) or ql.os.fd[fd] == 0: + if fd not in range(NR_OPEN) or ql.os.fd[fd] is None: return -EBADF return 0 @@ -1095,46 +1095,40 @@ def ql_syscall_newfstatat(ql: Qiling, dirfd: int, path: int, buf_ptr: int, flag: return regreturn -def ql_syscall_fstat64(ql: Qiling, fd, buf_ptr): - if not hasattr(ql.os.fd[fd], "fstat"): - regreturn = -1 - elif ql.os.fd[fd].fstat() == -1: - regreturn = 0 - elif 0 <= fd < NR_OPEN and ql.os.fd[fd] != 0: - buf = pack_stat64_struct(ql, ql.os.fd[fd].fstat()) +def ql_syscall_fstat64(ql: Qiling, fd: int, buf_ptr: int): + if fd not in range(NR_OPEN): + return -1 + + f = ql.os.fd[fd] + + if f is None or not hasattr(f, "fstat"): + return -1 + + fstat = f.fstat() + + if fstat != -1: + buf = pack_stat64_struct(ql, fstat) ql.mem.write(buf_ptr, buf) - regreturn = 0 - else: - regreturn = -1 + return 0 - if regreturn == 0: - ql.log.debug("fstat64 write completed") - else: - ql.log.debug("fstat64 read/write fail") - return regreturn +def ql_syscall_fstat(ql: Qiling, fd: int, buf_ptr: int): + if fd not in range(NR_OPEN): + return -1 + f = ql.os.fd[fd] -def ql_syscall_fstat(ql: Qiling, fd, buf_ptr): - if not hasattr(ql.os.fd[fd], "fstat"): - regreturn = -1 - # elif ql.os.fd[fd].fstat() == -1: - # regreturn = 0 - elif 0 <= fd < NR_OPEN and ql.os.fd[fd] != 0: - buf = pack_stat_struct(ql, ql.os.fd[fd].fstat()) - ql.mem.write(buf_ptr, buf) + if f is None or not hasattr(f, "fstat"): + return -1 - regreturn = 0 - else: - regreturn = -1 + fstat = f.fstat() - if regreturn == 0: - ql.log.debug("fstat write completed") - else: - ql.log.debug("fstat read/write fail") + if fstat != -1: + buf = pack_stat_struct(ql, fstat) + ql.mem.write(buf_ptr, buf) - return regreturn + return 0 # int stat(const char *path, struct stat *buf); diff --git a/qiling/os/posix/syscall/unistd.py b/qiling/os/posix/syscall/unistd.py index 480fdba0b..880cf428f 100644 --- a/qiling/os/posix/syscall/unistd.py +++ b/qiling/os/posix/syscall/unistd.py @@ -137,18 +137,23 @@ def ql_syscall_faccessat(ql: Qiling, dfd: int, filename: int, mode: int): return regreturn -def ql_syscall_lseek(ql: Qiling, fd: int, offset: int, lseek_origin: int): - if 0 <= fd < NR_OPEN and ql.os.fd[fd] != 0: - offset = ql.unpacks(ql.pack(offset)) +def ql_syscall_lseek(ql: Qiling, fd: int, offset: int, origin: int): + if fd not in range(NR_OPEN): + return -EBADF - try: - regreturn = ql.os.fd[fd].seek(offset, lseek_origin) - except OSError: - regreturn = -1 - else: - regreturn = -EBADF + f = ql.os.fd[fd] - # ql.log.debug("lseek(fd = %d, ofset = 0x%x, origin = 0x%x) = %d" % (lseek_fd, lseek_ofset, lseek_origin, regreturn)) + if f is None: + return -EBADF + + offset = ql.unpacks(ql.pack(offset)) + + try: + regreturn = f.seek(offset, origin) + except OSError: + regreturn = -1 + + # ql.log.debug("lseek(fd = %d, ofset = 0x%x, origin = 0x%x) = %d" % (fd, offset, origin, regreturn)) return regreturn @@ -207,53 +212,67 @@ def ql_syscall_access(ql: Qiling, path: int, mode: int): def ql_syscall_close(ql: Qiling, fd: int): - if 0 <= fd < NR_OPEN and ql.os.fd[fd] != 0: - ql.os.fd[fd].close() - ql.os.fd[fd] = 0 - regreturn = 0 - else: - regreturn = -1 + if fd not in range(NR_OPEN): + return -1 - return regreturn + f = ql.os.fd[fd] + + if f is None: + return -1 + + f.close() + ql.os.fd[fd] = None + + return 0 def ql_syscall_pread64(ql: Qiling, fd: int, buf: int, length: int, offt: int): + if fd not in range(NR_OPEN): + return -1 + + f = ql.os.fd[fd] + + if f is None: + return -1 + # https://chromium.googlesource.com/linux-syscall-support/+/2c73abf02fd8af961e38024882b9ce0df6b4d19b # https://chromiumcodereview.appspot.com/10910222 if ql.arch.type == QL_ARCH.MIPS: - offt = ql.unpack64(ql.mem.read(ql.arch.regs.arch_sp + 0x10, 8)) + offt = ql.mem.read_ptr(ql.arch.regs.arch_sp + 0x10, 8) - if 0 <= fd < NR_OPEN and ql.os.fd[fd] != 0: - try: - pos = ql.os.fd[fd].tell() - ql.os.fd[fd].seek(offt) + try: + pos = f.tell() + f.seek(offt) - data = ql.os.fd[fd].read(length) - ql.os.fd[fd].seek(pos) + data = f.read(length) + f.seek(pos) - ql.mem.write(buf, data) - regreturn = len(data) - except: - regreturn = -1 - else: + ql.mem.write(buf, data) + except: regreturn = -1 + else: + regreturn = len(data) return regreturn def ql_syscall_read(ql: Qiling, fd, buf: int, length: int): - if 0 <= fd < NR_OPEN and ql.os.fd[fd] != 0: - try: - data = ql.os.fd[fd].read(length) - ql.mem.write(buf, data) - except: - regreturn = -EBADF - else: - ql.log.debug(f'read() CONTENT: {data!r}') - regreturn = len(data) + if fd not in range(NR_OPEN): + return -EBADF - else: + f = ql.os.fd[fd] + + if f is None: + return -EBADF + + try: + data = f.read(length) + ql.mem.write(buf, data) + except: regreturn = -EBADF + else: + ql.log.debug(f'read() CONTENT: {data!r}') + regreturn = len(data) return regreturn @@ -451,39 +470,50 @@ def __read_str_array(addr: int) -> Iterator[str]: def ql_syscall_dup(ql: Qiling, oldfd: int): - regreturn = -EBADF + if oldfd not in range(NR_OPEN): + return -EBADF - if oldfd in range(256): - if ql.os.fd[oldfd] != 0: - newfd = ql.os.fd[oldfd].dup() + f = ql.os.fd[oldfd] - for i, val in enumerate(ql.os.fd): - if val == 0: - ql.os.fd[i] = newfd - regreturn = i - break - else: - regreturn = -EMFILE + if f is None: + return -EBADF - return regreturn + idx = next((i for i in range(NR_OPEN) if ql.os.fd[i] is None), -1) + + if idx == -1: + return -EMFILE + + ql.os.fd[idx] = f.dup() + + return idx def ql_syscall_dup2(ql: Qiling, fd: int, newfd: int): - if 0 <= fd < NR_OPEN and ql.os.fd[fd] != 0: - if 0 <= newfd < NR_OPEN: - ql.os.fd[newfd] = ql.os.fd[fd].dup() - return newfd + if fd not in range(NR_OPEN) or newfd not in range(NR_OPEN): + return -EBADF + + f = ql.os.fd[fd] + + if f is None: + return -EBADF - return -EBADF + ql.os.fd[newfd] = f.dup() + return newfd -def ql_syscall_dup3(ql: Qiling, fd, newfd: int, flags: int): - if 0 <= fd < NR_OPEN and ql.os.fd[fd] != 0: - if 0 <= newfd < NR_OPEN: - ql.os.fd[newfd] = ql.os.fd[fd].dup() - return newfd - return -1 +def ql_syscall_dup3(ql: Qiling, fd: int, newfd: int, flags: int): + if fd not in range(NR_OPEN) or newfd not in range(NR_OPEN): + return -1 + + f = ql.os.fd[fd] + + if f is None: + return -1 + + ql.os.fd[newfd] = f.dup() + + return newfd def ql_syscall_set_tid_address(ql: Qiling, tidptr: int): if ql.os.thread_management: @@ -499,37 +529,23 @@ def ql_syscall_set_tid_address(ql: Qiling, tidptr: int): def ql_syscall_pipe(ql: Qiling, pipefd: int): rd, wd = ql_pipe.open() - idx1 = -1 - idx2 = -1 + unpopulated_fd = (i for i in range(NR_OPEN) if ql.os.fd[i] is None) + idx1 = next(unpopulated_fd, -1) + idx2 = next(unpopulated_fd, -1) - for i in range(NR_OPEN): - if ql.os.fd[i] == 0: - idx1 = i - break + if (idx1 == -1) or (idx2 == -1): + return -1 - if idx1 == -1: - regreturn = -1 - else: - for i in range(NR_OPEN): - if ql.os.fd[i] == 0 and i != idx1: - idx2 = i - break + ql.os.fd[idx1] = rd + ql.os.fd[idx2] = wd - if idx2 == -1: - regreturn = -1 - else: - ql.os.fd[idx1] = rd - ql.os.fd[idx2] = wd - - if ql.arch.type == QL_ARCH.MIPS: - ql.arch.regs.v1 = idx2 - regreturn = idx1 - else: - ql.mem.write(pipefd + 0, ql.pack32(idx1)) - ql.mem.write(pipefd + 4, ql.pack32(idx2)) - regreturn = 0 - - ql.log.debug("pipe(%x, [%d, %d]) = %d" % (pipefd, idx1, idx2, regreturn)) + if ql.arch.type == QL_ARCH.MIPS: + ql.arch.regs.v1 = idx2 + regreturn = idx1 + else: + ql.mem.write(pipefd + 0, ql.pack32(idx1)) + ql.mem.write(pipefd + 4, ql.pack32(idx2)) + regreturn = 0 return regreturn @@ -589,7 +605,7 @@ def ql_syscall_unlink(ql: Qiling, pathname: int): file_path = ql.os.utils.read_cstring(pathname) real_path = ql.os.path.transform_to_real_path(file_path) - opened_fds = [getattr(ql.os.fd[i], 'name', None) for i in range(NR_OPEN) if ql.os.fd[i] != 0] + opened_fds = [getattr(ql.os.fd[i], 'name', None) for i in range(NR_OPEN) if ql.os.fd[i] is not None] path = pathlib.Path(real_path) if any((real_path not in opened_fds, path.is_block_device(), path.is_fifo(), path.is_socket(), path.is_symlink())): diff --git a/qiling/utils.py b/qiling/utils.py index 4e318d902..0a3b4c8ee 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -12,14 +12,15 @@ from configparser import ConfigParser from logging import LogRecord -from typing import Any, Container, Optional, Sequence, Tuple, Type +from typing import Any, Container, IO, List, Optional, Sequence, TextIO, Tuple, Type from enum import Enum from unicorn import UC_ERR_READ_UNMAPPED, UC_ERR_FETCH_UNMAPPED -from .exception import * -from .const import QL_VERBOSE, QL_ARCH, QL_ENDIAN, QL_OS, QL_DEBUGGER -from .const import debugger_map, arch_map, os_map, arch_os_map, loader_map +from qiling.exception import * +from qiling.const import QL_VERBOSE, QL_ARCH, QL_ENDIAN, QL_OS, QL_DEBUGGER +from qiling.const import debugger_map, arch_map, os_map, arch_os_map, loader_map +from qiling.os.posix.const import NR_OPEN FMT_STR = "%(levelname)s\t%(message)s" @@ -111,13 +112,16 @@ def filter(self, record: LogRecord): return re.match(self._filter, msg) is not None class QlFileDes: - def __init__(self, init): - self.__fds = init + def __init__(self): + self.__fds: List[Optional[IO]] = [None] * NR_OPEN - def __getitem__(self, idx): + def __len__(self): + return len(self.__fds) + + def __getitem__(self, idx: int): return self.__fds[idx] - def __setitem__(self, idx, val): + def __setitem__(self, idx: int, val: Optional[IO]): self.__fds[idx] = val def __iter__(self): From 9808107f79995dbd30faf760e0f186097f1f1fa6 Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 10 Feb 2022 18:59:37 +0200 Subject: [PATCH 109/406] Fix imports --- qiling/os/windows/dlls/ntoskrnl.py | 1 + qiling/os/windows/utils.py | 1 + tests/test_android.py | 2 +- tests/test_elf_multithread.py | 7 +------ 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/qiling/os/windows/dlls/ntoskrnl.py b/qiling/os/windows/dlls/ntoskrnl.py index b903f61ad..38ff1c87a 100644 --- a/qiling/os/windows/dlls/ntoskrnl.py +++ b/qiling/os/windows/dlls/ntoskrnl.py @@ -11,6 +11,7 @@ from qiling.os.windows.const import * from qiling.os.windows.fncc import * from qiling.os.windows.structs import * +from qiling.os.windows.wdk_const import DO_DEVICE_INITIALIZING, DO_EXCLUSIVE from qiling.utils import verify_ret # typedef struct _OSVERSIONINFOW { diff --git a/qiling/os/windows/utils.py b/qiling/os/windows/utils.py index b3db6cfcb..6ec9f6c75 100644 --- a/qiling/os/windows/utils.py +++ b/qiling/os/windows/utils.py @@ -10,6 +10,7 @@ from unicorn import UcError from qiling import Qiling +from qiling.const import QL_OS from qiling.os.const import POINTER from qiling.os.windows.fncc import STDCALL from qiling.os.windows.wdk_const import * diff --git a/tests/test_android.py b/tests/test_android.py index 7d838376d..79d18ea6e 100644 --- a/tests/test_android.py +++ b/tests/test_android.py @@ -6,7 +6,7 @@ import os, platform, sys, unittest sys.path.append("..") -from qiling import * +from qiling import Qiling from qiling.const import QL_VERBOSE diff --git a/tests/test_elf_multithread.py b/tests/test_elf_multithread.py index 42f38381e..3c9a3f438 100644 --- a/tests/test_elf_multithread.py +++ b/tests/test_elf_multithread.py @@ -5,15 +5,10 @@ import platform, sys, unittest, os, threading, time -from unicorn import UcError, UC_ERR_READ_UNMAPPED, UC_ERR_FETCH_UNMAPPED - sys.path.append("..") -from qiling import * +from qiling import Qiling from qiling.const import * from qiling.exception import * -from qiling.os.posix import syscall -from qiling.os.mapper import QlFsMappedObject -from qiling.os.posix.stat import Fstat from qiling.os.filestruct import ql_file class ELFTest(unittest.TestCase): From 7d577d36efcb06d8afd9f20d94f80f7bb15d5810 Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 10 Feb 2022 23:13:21 +0200 Subject: [PATCH 110/406] Define a few common IA32 MSRs --- qiling/arch/x86_const.py | 5 +++-- qiling/arch/x86_utils.py | 4 ++-- qiling/os/freebsd/syscall.py | 13 ++++--------- qiling/os/linux/thread.py | 4 ++-- qiling/os/macos/syscall.py | 2 +- qiling/os/posix/syscall/prctl.py | 10 +++++----- 6 files changed, 17 insertions(+), 21 deletions(-) diff --git a/qiling/arch/x86_const.py b/qiling/arch/x86_const.py index 6e9df157a..798549eea 100644 --- a/qiling/arch/x86_const.py +++ b/qiling/arch/x86_const.py @@ -39,8 +39,9 @@ QL_X86_GDT_ENTRY_SIZE = 0x8 # These msr registers are x86 specific -FSMSR = 0xC0000100 -GSMSR = 0xC0000101 +IA32_FS_BASE_MSR = 0xC0000100 +IA32_GS_BASE_MSR = 0xC0000101 +IA32_APIC_BASE_MSR = 0x1B # WINDOWS SETUP VALUE # Linux also needs these diff --git a/qiling/arch/x86_utils.py b/qiling/arch/x86_utils.py index e0b636086..39573c12d 100644 --- a/qiling/arch/x86_utils.py +++ b/qiling/arch/x86_utils.py @@ -181,7 +181,7 @@ def setup_cs_ds_ss_es(self, base: int, size: int) -> None: # self.arch.regs.es = selector def setup_fs(self, base: int, size: int) -> None: - self.arch.msr.write(FSMSR, base) + self.arch.msr.write(IA32_FS_BASE_MSR, base) def setup_gs(self, base: int, size: int) -> None: - self.arch.msr.write(GSMSR, base) + self.arch.msr.write(IA32_GS_BASE_MSR, base) diff --git a/qiling/os/freebsd/syscall.py b/qiling/os/freebsd/syscall.py index a5f60c35e..77691936a 100644 --- a/qiling/os/freebsd/syscall.py +++ b/qiling/os/freebsd/syscall.py @@ -40,19 +40,14 @@ def ql_syscall_sysarch(ql, op, parms, *args, **kw): wild guess, of cause not working """ - regreturn = 0 - ql.GS_SEGMENT_ADDR = 0x6000 - ql.GS_SEGMENT_SIZE = 0x8000 - - - #ql.mem.map(ql.GS_SEGMENT_ADDR, ql.GS_SEGMENT_SIZE) - #ql.arch.msr.write(GSMSR, ql.GS_SEGMENT_ADDR) - ql.arch.msr.write(FSMSR, parms) + #ql.mem.map(GS_SEGMENT_ADDR, GS_SEGMENT_SIZE) + #ql.arch.msr.write(IA32_GS_BASE_MSR, GS_SEGMENT_ADDR) + ql.arch.msr.write(IA32_FS_BASE_MSR, parms) #op_buf = ql.pack32(op) #ql.mem.write(parms, op_buf) - return regreturn + return 0 def ql_syscall_sigprocmask(ql, how, mask, omask, *args, **kw): ql.log.debug("sigprocmask(how: 0x%x, mask: 0x%x, omask: 0x%x)" % (how, mask, omask)) diff --git a/qiling/os/linux/thread.py b/qiling/os/linux/thread.py index 03ef88dfd..34e4b3d67 100644 --- a/qiling/os/linux/thread.py +++ b/qiling/os/linux/thread.py @@ -428,7 +428,7 @@ def __init__(self, ql, start_address, exit_point, context = None, set_child_tid_ def set_thread_tls(self, tls_addr): self.tls = tls_addr - self.ql.arch.msr.write(FSMSR, self.tls) + self.ql.arch.msr.write(IA32_FS_BASE_MSR, self.tls) self.ql.log.debug(f"Set fsbase to {hex(tls_addr)} for {str(self)}") # Some notes: @@ -436,7 +436,7 @@ def set_thread_tls(self, tls_addr): # - https://stackoverflow.com/questions/11497563/detail-about-msr-gs-base-in-linux-x86-64 def save(self): self.save_context() - self.tls = self.ql.arch.msr.read(FSMSR) + self.tls = self.ql.arch.msr.read(IA32_FS_BASE_MSR) self.ql.log.debug(f"Saved context: fs={hex(self.ql.arch.regs.fsbase)} tls={hex(self.tls)}") def restore(self): diff --git a/qiling/os/macos/syscall.py b/qiling/os/macos/syscall.py index 6093d374c..96cc099f6 100644 --- a/qiling/os/macos/syscall.py +++ b/qiling/os/macos/syscall.py @@ -430,5 +430,5 @@ def ql_syscall_abort_with_payload(ql, reason_namespace, reason_code, payload, pa # thread_set_tsd_base def ql_syscall_thread_fast_set_cthread_self64(ql, u_info_addr, *args, **kw): ql.log.debug("[mdep] thread fast set cthread self64(tsd_base:0x%x)" % (u_info_addr)) - ql.arch.msr.write(GSMSR, u_info_addr) + ql.arch.msr.write(IA32_GS_BASE_MSR, u_info_addr) return KERN_SUCCESS diff --git a/qiling/os/posix/syscall/prctl.py b/qiling/os/posix/syscall/prctl.py index 744ef899e..3229382eb 100644 --- a/qiling/os/posix/syscall/prctl.py +++ b/qiling/os/posix/syscall/prctl.py @@ -4,7 +4,7 @@ # from qiling import Qiling -from qiling.arch.x86_const import FSMSR, GSMSR +from qiling.arch.x86_const import IA32_FS_BASE_MSR, IA32_GS_BASE_MSR def ql_syscall_arch_prctl(ql: Qiling, code: int, addr: int): ARCH_SET_GS = 0x1001 @@ -13,10 +13,10 @@ def ql_syscall_arch_prctl(ql: Qiling, code: int, addr: int): ARCH_GET_GS = 0x1004 handlers = { - ARCH_SET_GS : lambda : ql.arch.msr.write(GSMSR, addr), - ARCH_SET_FS : lambda : ql.arch.msr.write(FSMSR, addr), - ARCH_GET_FS : lambda : ql.mem.write(addr, ql.pack64(ql.arch.msr.read(FSMSR))), - ARCH_GET_GS : lambda : ql.mem.write(addr, ql.pack64(ql.arch.msr.read(GSMSR))) + ARCH_SET_GS : lambda : ql.arch.msr.write(IA32_GS_BASE_MSR, addr), + ARCH_SET_FS : lambda : ql.arch.msr.write(IA32_FS_BASE_MSR, addr), + ARCH_GET_FS : lambda : ql.mem.write(addr, ql.pack64(ql.arch.msr.read(IA32_FS_BASE_MSR))), + ARCH_GET_GS : lambda : ql.mem.write(addr, ql.pack64(ql.arch.msr.read(IA32_GS_BASE_MSR))) } if code not in handlers: From c3b28abd7233b291d16469c3b3585731c9ce4536 Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 11 Feb 2022 00:06:25 +0200 Subject: [PATCH 111/406] Simplify stop options --- qiling/const.py | 7 ++++++- qiling/core.py | 46 ++++++++++++++++++++++------------------------ qiling/utils.py | 19 ------------------- 3 files changed, 28 insertions(+), 44 deletions(-) diff --git a/qiling/const.py b/qiling/const.py index 2bb1fe557..a5ab646da 100644 --- a/qiling/const.py +++ b/qiling/const.py @@ -3,7 +3,7 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from enum import Enum, IntEnum +from enum import Enum, Flag, IntEnum from typing import Any, Mapping, Type class QL_ENDIAN(IntEnum): @@ -51,6 +51,11 @@ class QL_INTERCEPT(IntEnum): ENTER = 2 EXIT = 3 +class QL_STOP(Flag): + NONE = 0 + STACK_POINTER = (1 << 0) + EXIT_TRAP = (1 << 1) + QL_OS_NONPID = (QL_OS.DOS, QL_OS.UEFI) QL_OS_POSIX = (QL_OS.LINUX, QL_OS.FREEBSD, QL_OS.MACOS, QL_OS.QNX) QL_OS_BAREMETAL = (QL_OS.MCU,) diff --git a/qiling/core.py b/qiling/core.py index a3e17b330..4e387ca73 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -19,7 +19,7 @@ from .hw.hw import QlHwManager from .loader.loader import QlLoader -from .const import QL_ARCH, QL_ENDIAN, QL_OS, QL_VERBOSE, QL_OS_INTERPRETER, QL_OS_BAREMETAL +from .const import QL_ARCH, QL_ENDIAN, QL_OS, QL_STOP, QL_VERBOSE, QL_OS_INTERPRETER, QL_OS_BAREMETAL from .exception import QlErrorFileNotFound, QlErrorArch, QlErrorOsType from .host import QlHost from .utils import * @@ -45,8 +45,7 @@ def __init__( log_plain=False, multithread = False, filter = None, - stop_on_stackpointer = False, - stop_on_exit_trap = False, + stop: QL_STOP = QL_STOP.NONE, *, endian: QL_ENDIAN = None, thumb: bool = False, @@ -68,7 +67,7 @@ def __init__( self._filter = filter self._internal_exception = None self._uc = None - self._stop_options = QlStopOptions(stackpointer=stop_on_stackpointer, exit_trap=stop_on_exit_trap) + self._stop_options = stop ################################## # Definition after ql=Qiling() # @@ -490,14 +489,12 @@ def uc(self, u): self._uc = u @property - def stop_options(self) -> "QlStopOptions": - """ The stop options configured: - - stackpointer: Stop execution on a negative stackpointer - - exit_trap: Stop execution when the ip enters a guarded region - - any: Is any of the options enabled? - - Returns: - QlStopOptions: What stop options are configured + def stop_options(self) -> QL_STOP: + """ The stop options configured (multiple options apply): + - `QL_STOP.STACK_POINTER` : Stop execution on a negative stackpointer + - `QL_STOP.EXIT_TRAP` : Stop execution when the pc value enters a guarded region + + Returns: configured options """ return self._stop_options @@ -515,29 +512,28 @@ def enable_lib_patch(self): raise RuntimeError("Fail to patch %s at address 0x%x" % (filename, addr)) def _init_stop_guard(self): - if not self.stop_options.any: + if not self.stop_options: return # Allocate a guard page, we need this in both cases # On a negative stack pointer, we still need a return address (otherwise we end up at 0) # Make sure it is not close to the heap (PE), otherwise the heap cannot grow self._exit_trap_addr = self.mem.find_free_space(0x1000, minaddr=0x9000000, align=0x10) - self.mem.map(self._exit_trap_addr, 0x1000, info='[Stop guard]') + self.mem.map(self._exit_trap_addr, 0x1000, info='[Stop guard page]') # Stop on a negative stack pointer - if self.stop_options.stackpointer: - def _check_sp(ql, address, size): + if QL_STOP.STACK_POINTER in self.stop_options: + def _check_sp(ql: Qiling, address: int, size: int): if not ql.loader.skip_exit_check: - sp = ql._initial_sp - ql.arch.regs.arch_sp - if sp < 0: + if ql._initial_sp < ql.arch.regs.arch_sp: self.log.info('Process returned from entrypoint (stackpointer)!') ql.emu_stop() self.hook_code(_check_sp) # Stop when running to exit trap address - if self.stop_options.exit_trap: - def _exit_trap(ql): + if QL_STOP.EXIT_TRAP in self.stop_options: + def _exit_trap(ql: Qiling): self.log.info('Process returned from entrypoint (exit_trap)!') ql.emu_stop() @@ -545,12 +541,14 @@ def _exit_trap(ql): def write_exit_trap(self): self._initial_sp = self.arch.regs.arch_sp - if self.stop_options.any: + + if self.stop_options: if not self.loader.skip_exit_check: - self.log.debug(f'Setting up exit trap at 0x{hex(self._exit_trap_addr)}') + self.log.debug(f'Setting up exit trap at {self._exit_trap_addr:#x}') self.stack_write(0, self._exit_trap_addr) - elif self.stop_options.exit_trap: - self.log.debug(f'Loader {self.loader} requested to skip exit_trap!') + + elif QL_STOP.EXIT_TRAP in self.stop_options: + self.log.debug(f'Loader requested to skip exit_trap!') ############### diff --git a/qiling/utils.py b/qiling/utils.py index 0a3b4c8ee..d6481bb0d 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -137,25 +137,6 @@ def restore(self, fds): self.__fds = fds -class QlStopOptions(object): - def __init__(self, stackpointer=False, exit_trap=False): - super().__init__() - self._stackpointer = stackpointer - self._exit_trap = exit_trap - - @property - def stackpointer(self) -> bool: - return self._stackpointer - - @property - def exit_trap(self) -> bool: - return self._exit_trap - - @property - def any(self) -> bool: - return self.stackpointer or self.exit_trap - - def catch_KeyboardInterrupt(ql): def decorator(func): def wrapper(*args, **kw): From 8a0c5da3ebeee4fd721747acdbf9903779cd418b Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 11 Feb 2022 00:15:08 +0200 Subject: [PATCH 112/406] Adjust all stop options usages --- qiling/loader/pe.py | 2 +- tests/test_elf.py | 10 +++++----- tests/test_pe.py | 8 ++++---- tests/test_pe_sys.py | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/qiling/loader/pe.py b/qiling/loader/pe.py index d8ddd2568..fbfb3302e 100644 --- a/qiling/loader/pe.py +++ b/qiling/loader/pe.py @@ -579,7 +579,7 @@ def load(self): self.ql.stop_execution_pattern = 0xDEADC0DE if self.ql.arch.type == QL_ARCH.X86: # Win32 - if not self.ql.stop_options.any: + if not self.ql.stop_options: # We know that a driver will return, # so if the user did not configure stop options, write a sentinel return value self.ql.mem.write(sp, self.ql.stop_execution_pattern.to_bytes(length=4, byteorder='little')) diff --git a/tests/test_elf.py b/tests/test_elf.py index 80f49bef6..a9e47848b 100644 --- a/tests/test_elf.py +++ b/tests/test_elf.py @@ -7,7 +7,7 @@ sys.path.append("..") from qiling import Qiling -from qiling.const import QL_OS, QL_INTERCEPT, QL_VERBOSE +from qiling.const import QL_OS, QL_INTERCEPT, QL_STOP, QL_VERBOSE from qiling.exception import * from qiling.extensions import pipe from qiling.os.const import STRING @@ -1002,22 +1002,22 @@ def test_x8664_getcwd(self): del ql def test_elf_linux_x86_return_from_main_stackpointer(self): - ql = Qiling(["../examples/rootfs/x86_linux/bin/x86_return_main"], "../examples/rootfs/x86_linux", stop_on_stackpointer=True) + ql = Qiling(["../examples/rootfs/x86_linux/bin/x86_return_main"], "../examples/rootfs/x86_linux", stop=QL_STOP.STACK_POINTER) ql.run() del ql def test_elf_linux_x86_return_from_main_exit_trap(self): - ql = Qiling(["../examples/rootfs/x86_linux/bin/x86_return_main"], "../examples/rootfs/x86_linux", stop_on_exit_trap=True) + ql = Qiling(["../examples/rootfs/x86_linux/bin/x86_return_main"], "../examples/rootfs/x86_linux", stop=QL_STOP.EXIT_TRAP) ql.run() del ql def test_elf_linux_x8664_return_from_main_stackpointer(self): - ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_return_main"], "../examples/rootfs/x8664_linux", stop_on_stackpointer=True) + ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_return_main"], "../examples/rootfs/x8664_linux", stop=QL_STOP.STACK_POINTER) ql.run() del ql def test_elf_linux_x8664_return_from_main_exit_trap(self): - ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_return_main"], "../examples/rootfs/x8664_linux", stop_on_exit_trap=True) + ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_return_main"], "../examples/rootfs/x8664_linux", stop=QL_STOP.EXIT_TRAP) ql.run() del ql diff --git a/tests/test_pe.py b/tests/test_pe.py index 88743a91a..bf97e50f1 100644 --- a/tests/test_pe.py +++ b/tests/test_pe.py @@ -285,7 +285,7 @@ def _t(): def test_pe_win_x86_return_from_main_stackpointer(self): def _t(): - ql = Qiling(["../examples/rootfs/x86_windows/bin/return_main.exe"], "../examples/rootfs/x86_windows", libcache=True, stop_on_stackpointer=True) + ql = Qiling(["../examples/rootfs/x86_windows/bin/return_main.exe"], "../examples/rootfs/x86_windows", stop=QL_STOP.STACK_POINTER, libcache=True) ql.run() del ql return True @@ -295,7 +295,7 @@ def _t(): def test_pe_win_x86_return_from_main_exit_trap(self): def _t(): - ql = Qiling(["../examples/rootfs/x86_windows/bin/return_main.exe"], "../examples/rootfs/x86_windows", libcache=True, stop_on_exit_trap=True) + ql = Qiling(["../examples/rootfs/x86_windows/bin/return_main.exe"], "../examples/rootfs/x86_windows", stop=QL_STOP.EXIT_TRAP, libcache=True) ql.run() del ql return True @@ -305,7 +305,7 @@ def _t(): def test_pe_win_x8664_return_from_main_stackpointer(self): def _t(): - ql = Qiling(["../examples/rootfs/x8664_windows/bin/x8664_return_main.exe"], "../examples/rootfs/x8664_windows", libcache=True, stop_on_stackpointer=True) + ql = Qiling(["../examples/rootfs/x8664_windows/bin/x8664_return_main.exe"], "../examples/rootfs/x8664_windows", stop=QL_STOP.STACK_POINTER, libcache=True) ql.run() del ql return True @@ -315,7 +315,7 @@ def _t(): def test_pe_win_x8664_return_from_main_exit_trap(self): def _t(): - ql = Qiling(["../examples/rootfs/x8664_windows/bin/x8664_return_main.exe"], "../examples/rootfs/x8664_windows", libcache=True, stop_on_exit_trap=True) + ql = Qiling(["../examples/rootfs/x8664_windows/bin/x8664_return_main.exe"], "../examples/rootfs/x8664_windows", stop=QL_STOP.EXIT_TRAP, libcache=True) ql.run() del ql return True diff --git a/tests/test_pe_sys.py b/tests/test_pe_sys.py index 1068cc9c9..099fbee03 100644 --- a/tests/test_pe_sys.py +++ b/tests/test_pe_sys.py @@ -9,7 +9,7 @@ sys.path.append("..") from qiling import Qiling -from qiling.const import QL_VERBOSE +from qiling.const import QL_STOP, QL_VERBOSE from qiling.os.const import POINTER, DWORD, STRING, HANDLE from qiling.os.windows import utils from qiling.os.windows.wdk_const import * @@ -233,7 +233,7 @@ def hook_second_stop_address(ql): def test_pe_win_x8664_driver(self): # Compiled sample from https://github.com/microsoft/Windows-driver-samples/tree/master/general/ioctl/wdm/sys - ql = Qiling(["../examples/rootfs/x8664_windows/bin/sioctl.sys"], "../examples/rootfs/x8664_windows", libcache=True, stop_on_stackpointer=True) + ql = Qiling(["../examples/rootfs/x8664_windows/bin/sioctl.sys"], "../examples/rootfs/x8664_windows", stop=QL_STOP.STACK_POINTER, libcache=True) driver_object = ql.loader.driver_object From ae9d811bb3b11c2d2367661072daa9f1d13779b6 Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 11 Feb 2022 01:17:07 +0200 Subject: [PATCH 113/406] Tweak core debugger property --- qiling/core.py | 38 +++++++++++++++---------------- qiling/os/posix/syscall/unistd.py | 2 +- qiling/utils.py | 4 ++-- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/qiling/core.py b/qiling/core.py index 4e387ca73..bc7bfc947 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -75,7 +75,7 @@ def __init__( self._patch_bin = [] self._patch_lib = [] self._debug_stop = False - self._debugger = None + self._debugger = False ############################### # Properties configured later # @@ -439,23 +439,23 @@ def debug_stop(self, ds): self._debug_stop = ds @property - def debugger(self) -> Union[str, bool]: + def debugger(self) -> bool: + return bool(self._debugger) + + @debugger.setter + def debugger(self, dbger: Union[str, bool]): """ Enable debugger. - Type: debugger instance Values: - "gdb": enable gdb. - True : an alias to "gdb". - "gdb:0.0.0.0:1234" : gdb which listens on 0.0.0.0:1234 - "qdb": enable qdb. - "qdb:rr": enable qdb with reverse debugging support. + Example: ql.debugger = True ql.debugger = "qdb" """ - return self._debugger - - @debugger.setter - def debugger(self, dbger): self._debugger = dbger @property @@ -557,36 +557,36 @@ def write_exit_trap(self): # Emulate the binary from begin until @end, with timeout in @timeout and # number of emulated instructions in @count - def run(self, begin=None, end=None, timeout=0, count=0, code = None): + def run(self, begin=None, end=None, timeout=0, count=0, code=None): # replace the original entry point, exit point, timeout and count self.entry_point = begin self.exit_point = end self.timeout = timeout self.count = count - # init debugger - if self._debugger != False and self._debugger != None and not self.interpreter: - self._debugger = debugger_setup(self._debugger, self) - if self.interpreter: return self.arch.run(code) - elif self.baremetal: - self.__enable_bin_patch() + # init debugger (if set) + debugger = debugger_setup(self._debugger, self) + + # patch binary + self.__enable_bin_patch() + + if self.baremetal: if self.count <= 0: self.count = -1 + self.arch.run(count=self.count, end=self.exit_point) else: self.write_exit_trap() - # patch binary - self.__enable_bin_patch() # emulate the binary self.os.run() # run debugger - if self._debugger != False and self._debugger != None: - self._debugger.run() - + if debugger and self.debugger: + debugger.run() + # patch code to memory address def patch(self, offset: int, data: bytes, target: str = None) -> None: diff --git a/qiling/os/posix/syscall/unistd.py b/qiling/os/posix/syscall/unistd.py index 880cf428f..669dd5327 100644 --- a/qiling/os/posix/syscall/unistd.py +++ b/qiling/os/posix/syscall/unistd.py @@ -446,7 +446,7 @@ def __read_str_array(addr: int) -> Iterator[str]: ql.clear_ql_hooks() # Clean debugger to prevent port conflicts - ql.debugger = None + # ql.debugger = None if ql.code: return diff --git a/qiling/utils.py b/qiling/utils.py index d6481bb0d..9cd0e32e2 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -12,7 +12,7 @@ from configparser import ConfigParser from logging import LogRecord -from typing import Any, Container, IO, List, Optional, Sequence, TextIO, Tuple, Type +from typing import Any, Container, IO, List, Optional, Sequence, TextIO, Tuple, Type, Union from enum import Enum from unicorn import UC_ERR_READ_UNMAPPED, UC_ERR_FETCH_UNMAPPED @@ -433,7 +433,7 @@ def component_setup(component_type: str, component_name: str, ql): return obj(ql) -def debugger_setup(options, ql): +def debugger_setup(options: Union[str, bool], ql): if options is True: options = 'gdb' From b9eca3c35b5261d58e4e287a801db4a2017eff33 Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 11 Feb 2022 13:02:07 +0200 Subject: [PATCH 114/406] Adjust an overlooked stop options usage --- qiling/loader/pe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiling/loader/pe.py b/qiling/loader/pe.py index fbfb3302e..dcb8d50c5 100644 --- a/qiling/loader/pe.py +++ b/qiling/loader/pe.py @@ -587,7 +587,7 @@ def load(self): self.ql.log.debug('Writing 0x%08X (PDRIVER_OBJECT) to [ESP+4](0x%08X)' % (self.ql.loader.driver_object_address, sp+0x4)) self.ql.log.debug('Writing 0x%08X (RegistryPath) to [ESP+8](0x%08X)' % (self.ql.loader.regitry_path_address, sp+0x8)) elif self.ql.arch.type == QL_ARCH.X8664: # Win64 - if not self.ql.stop_options.any: + if not self.ql.stop_options: # We know that a driver will return, # so if the user did not configure stop options, write a sentinel return value self.ql.mem.write(sp, self.ql.stop_execution_pattern.to_bytes(length=8, byteorder='little')) From fede510068a4f04056b854e02887cce5ff2f29fb Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 11 Feb 2022 14:51:41 +0200 Subject: [PATCH 115/406] Detect logging stream's ability to display colors --- qiling/utils.py | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/qiling/utils.py b/qiling/utils.py index 9cd0e32e2..7246bca6b 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -538,6 +538,43 @@ def ql_resolve_logger_level(verbose: QL_VERBOSE) -> int: QL_INSTANCE_ID = 114514 +def __is_color_terminal(stream: TextIO) -> bool: + """Determine whether standard output is attached to a color terminal. + + see: https://stackoverflow.com/questions/53574442/how-to-reliably-test-color-capability-of-an-output-terminal-in-python3 + """ + + def __handle_nt(fd: int) -> bool: + import ctypes + import msvcrt + + ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4 + + kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) + hstdout = msvcrt.get_osfhandle(handle=fd) + mode = ctypes.c_ulong() + + return kernel32.GetConsoleMode(hstdout, ctypes.byref(mode)) and (mode.value & ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) + + def __handle_posix(fd: int) -> bool: + import curses + + curses.setupterm(fd=fd) + + return curses.tigetnum('colors') > 0 + + def __default(_: int) -> bool: + return True + + handlers = { + 'nt' : __handle_nt, + 'posix' : __handle_posix + } + + handler = handlers.get(os.name, __default) + + return handler(stream.fileno()) + # TODO: qltool compatibility def ql_setup_logger(ql, log_file: Optional[str], console: bool, filters: Optional[Sequence], log_override: Optional[logging.Logger], log_plain: bool): global QL_INSTANCE_ID @@ -560,7 +597,7 @@ def ql_setup_logger(ql, log_file: Optional[str], console: bool, filters: Optiona if console: handler = logging.StreamHandler() - if not log_plain and not sys.platform == "win32": + if not log_plain and __is_color_terminal(handler.stream): formatter = QilingColoredFormatter(ql, FMT_STR) else: formatter = QilingPlainFormatter(ql, FMT_STR) From 724566f212c2a96182e97ab432148af56deaa634 Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 11 Feb 2022 14:52:14 +0200 Subject: [PATCH 116/406] Tweak logger formatters --- qiling/utils.py | 82 +++++++++++++++++++++++-------------------------- 1 file changed, 39 insertions(+), 43 deletions(-) diff --git a/qiling/utils.py b/qiling/utils.py index 7246bca6b..81f1b7002 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -8,7 +8,7 @@ thoughout the qiling framework """ -import importlib, os, copy, re, pefile, logging, sys, yaml +import importlib, os, copy, re, pefile, logging, yaml from configparser import ConfigParser from logging import LogRecord @@ -38,65 +38,60 @@ class COLOR_CODE: CYAN = '\033[96m' ENDC = '\033[0m' -class QilingColoredFormatter(logging.Formatter): +class QlBaseFormatter(logging.Formatter): + __level_tag = { + 'WARNING' : '[!]', + 'INFO' : '[=]', + 'DEBUG' : '[+]', + 'CRITICAL' : '[x]', + 'ERROR' : '[x]' + } + def __init__(self, ql, *args, **kwargs): super().__init__(*args, **kwargs) self.ql = ql - def get_colored_level(self, record: LogRecord) -> str: - LEVEL_NAME = { - 'WARNING' : f"{COLOR_CODE.YELLOW}[!]{COLOR_CODE.ENDC}", - 'INFO' : f"{COLOR_CODE.BLUE}[=]{COLOR_CODE.ENDC}", - 'DEBUG' : f"{COLOR_CODE.MAGENTA}[+]{COLOR_CODE.ENDC}", - 'CRITICAL' : f"{COLOR_CODE.CRIMSON}[x]{COLOR_CODE.ENDC}", - 'ERROR' : f"{COLOR_CODE.RED}[x]{COLOR_CODE.ENDC}" - } + def get_level_tag(self, level: str) -> str: + return self.__level_tag[level] - return LEVEL_NAME[record.levelname] + def get_thread_tag(self, thread: str) -> str: + return thread def format(self, record: LogRecord): # In case we have multiple formatters, we have to keep a copy of the record. record = copy.copy(record) - record.levelname = self.get_colored_level(record) # early logging may access ql.os when it is not yet set try: cur_thread = self.ql.os.thread_management.cur_thread except AttributeError: - pass + tid = f'' else: - record.levelname = f"{record.levelname} {COLOR_CODE.GREEN}{str(cur_thread)}{COLOR_CODE.ENDC}" + tid = self.get_thread_tag(str(cur_thread)) - return super().format(record) + level = self.get_level_tag(record.levelname) + record.levelname = f'{level} {tid}' -class QilingPlainFormatter(logging.Formatter): - def __init__(self, ql, *args, **kwargs): - super().__init__(*args, **kwargs) - self.ql = ql + return super().format(record) - def get_level(self, record: LogRecord) -> str: - LEVEL_NAME = { - 'WARNING' : "[!]", - 'INFO' : "[=]", - 'DEBUG' : "[+]", - 'CRITICAL' : "[x]", - 'ERROR' : "[x]" - } +class QlColoredFormatter(QlBaseFormatter): + __level_color = { + 'WARNING' : COLOR_CODE.YELLOW, + 'INFO' : COLOR_CODE.BLUE, + 'DEBUG' : COLOR_CODE.MAGENTA, + 'CRITICAL' : COLOR_CODE.CRIMSON, + 'ERROR' : COLOR_CODE.RED + } - return LEVEL_NAME[record.levelname] + def get_level_tag(self, level: str) -> str: + s = super().get_level_tag(level) - def format(self, record: LogRecord): - record.levelname = self.get_level(record) + return f'{self.__level_color[level]}{s}{COLOR_CODE.ENDC}' - # early logging may access ql.os when it is not yet set - try: - cur_thread = self.ql.os.thread_management.cur_thread - except AttributeError: - pass - else: - record.levelname = f"{record.levelname} {str(cur_thread)}" + def get_thread_tag(self, tid: str) -> str: + s = super().get_thread_tag(tid) - return super().format(record) + return f'{COLOR_CODE.GREEN}{s}{COLOR_CODE.ENDC}' class RegexFilter(logging.Filter): def __init__(self, regexp): @@ -597,20 +592,21 @@ def ql_setup_logger(ql, log_file: Optional[str], console: bool, filters: Optiona if console: handler = logging.StreamHandler() - if not log_plain and __is_color_terminal(handler.stream): - formatter = QilingColoredFormatter(ql, FMT_STR) + if log_plain or not __is_color_terminal(handler.stream): + formatter = QlBaseFormatter(ql, FMT_STR) else: - formatter = QilingPlainFormatter(ql, FMT_STR) + formatter = QlColoredFormatter(ql, FMT_STR) handler.setFormatter(formatter) log.addHandler(handler) else: - log.setLevel(logging.CRITICAL) + handler = logging.NullHandler() + log.addHandler(handler) # Do we have to write log to a file? if log_file is not None: handler = logging.FileHandler(log_file) - formatter = QilingPlainFormatter(ql, FMT_STR) + formatter = QlBaseFormatter(ql, FMT_STR) handler.setFormatter(formatter) log.addHandler(handler) From 374d984f74f01fa7bdbbd88406c83938de3c03dc Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 11 Feb 2022 15:06:23 +0200 Subject: [PATCH 117/406] Handle errors --- qiling/utils.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/qiling/utils.py b/qiling/utils.py index 81f1b7002..975a450c4 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -546,7 +546,7 @@ def __handle_nt(fd: int) -> bool: ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4 kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) - hstdout = msvcrt.get_osfhandle(handle=fd) + hstdout = msvcrt.get_osfhandle(fd) mode = ctypes.c_ulong() return kernel32.GetConsoleMode(hstdout, ctypes.byref(mode)) and (mode.value & ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) @@ -554,9 +554,12 @@ def __handle_nt(fd: int) -> bool: def __handle_posix(fd: int) -> bool: import curses - curses.setupterm(fd=fd) - - return curses.tigetnum('colors') > 0 + try: + curses.setupterm(fd=fd) + except curses.error: + return True + else: + return curses.tigetnum('colors') > 0 def __default(_: int) -> bool: return True From 7512112ed3b8dd04adb6a2a01d4a6a50fc66a80e Mon Sep 17 00:00:00 2001 From: xwings Date: Sun, 13 Feb 2022 19:00:41 +0800 Subject: [PATCH 118/406] working on 1.4.3-dev --- qiling/__version__.py | 4 ++-- setup.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/qiling/__version__.py b/qiling/__version__.py index a7261f113..94f2af16c 100644 --- a/qiling/__version__.py +++ b/qiling/__version__.py @@ -1,3 +1,3 @@ # NOTE: use "-dev" for dev branch -__version__ = "1.4.2" -#__version__ = "1.4.3" + "-dev" +#__version__ = "1.4.3" +__version__ = "1.4.3" + "-dev" diff --git a/setup.py b/setup.py index 5b5cf2a39..16f83f6ed 100644 --- a/setup.py +++ b/setup.py @@ -77,8 +77,8 @@ # How mature is this project? Common values are # 3 - Alpha # 5 - Production/Stable - 'Development Status :: 5 - Production/Stable', - #'Development Status :: 3 - Alpha', + #'Development Status :: 5 - Production/Stable', + 'Development Status :: 3 - Alpha', # Indicate who your project is intended for 'Intended Audience :: Developers', From a6b06a105a8e512eb4da61bf5d3ce752d6462def Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 13 Feb 2022 13:34:57 +0200 Subject: [PATCH 119/406] Fix merger misalignments --- tests/test_elf.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_elf.py b/tests/test_elf.py index 77b85a786..96bb97029 100644 --- a/tests/test_elf.py +++ b/tests/test_elf.py @@ -1102,10 +1102,11 @@ def test_memory_search(self): del ql def test_elf_linux_x8664_path_traversion(self): - mock_stdout = pipe.SimpleOutStream(sys.stdout.fileno()) - ql = Qiling(["../examples/rootfs/x8664_linux/bin/path_traverse_static"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG, stdout=mock_stdout) + ql = Qiling(["../examples/rootfs/x8664_linux/bin/path_traverse_static"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG) + ql.os.stdout = pipe.SimpleOutStream(sys.stdout.fileno()) ql.run() + self.assertTrue("root\n" not in ql.os.stdout.read().decode("utf-8")) del ql From f7b1d534a452ad77f45133d12e7985690c17642e Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Sun, 13 Feb 2022 21:25:01 +0000 Subject: [PATCH 120/406] fix show_context bug for MIPS and missing function in command examine --- qiling/debugger/qdb/frontend.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/qiling/debugger/qdb/frontend.py b/qiling/debugger/qdb/frontend.py index cf4a1d087..e24c81f68 100644 --- a/qiling/debugger/qdb/frontend.py +++ b/qiling/debugger/qdb/frontend.py @@ -11,7 +11,7 @@ from qiling.const import QL_ARCH -from .utils import disasm, get_x86_eflags, setup_branch_predictor +from .utils import disasm, get_x86_eflags, setup_branch_predictor, read_int from .const import color, SIZE_LETTER, FORMAT_LETTER @@ -60,14 +60,17 @@ def extract_count(t): elif ql.archtype == QL_ARCH.MIPS: rest = rest.replace("fp", "s8") + # for supporting addition of register with constant value elems = rest.split("+") elems = [elem.strip("$") for elem in elems] items = [] + for elem in elems: if elem in ql.reg.register_mapping.keys(): - items.append(getattr(ql.reg, elem, None)) + if (value := getattr(ql.reg, elem, None)): + items.append(value) else: items.append(read_int(elem)) @@ -369,7 +372,7 @@ def context_reg(self, saved_reg_dump): diff = None if saved_reg_dump is not None: reg_dump = copy.deepcopy(saved_reg_dump) - reg_dump.update({"fp": saved_reg_dump.pop("s8")}) + reg_dump.update({"fp": reg_dump.pop("s8")}) diff = [k for k in cur_regs if cur_regs[k] != reg_dump[k]] lines = "" From 5a83e74a518fa588e99b9c38e3a79fbeddca4ac3 Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Sun, 13 Feb 2022 22:35:34 +0000 Subject: [PATCH 121/406] minor refactor add diff snapshot for both commands step-in and step-over --- qiling/debugger/qdb/qdb.py | 44 +++++------- qiling/debugger/qdb/utils.py | 130 ++++++++++++++++++++++++++++++++--- 2 files changed, 138 insertions(+), 36 deletions(-) diff --git a/qiling/debugger/qdb/qdb.py b/qiling/debugger/qdb/qdb.py index d6366254a..3cc21ef0a 100644 --- a/qiling/debugger/qdb/qdb.py +++ b/qiling/debugger/qdb/qdb.py @@ -14,7 +14,7 @@ from .frontend import examine_mem, setup_ctx_manager from .utils import is_thumb, parse_int, setup_branch_predictor, disasm -from .utils import Breakpoint, TempBreakpoint, read_inst +from .utils import Breakpoint, TempBreakpoint, SnapshotManager from .const import color @@ -26,11 +26,8 @@ def __init__(self: QlQdb, ql: Qiling, init_hook: str = "", rr: bool = False) -> self.prompt = f"{color.BOLD}{color.RED}Qdb> {color.END}" self._saved_reg_dump = None self.bp_list = {} - self.rr = rr - - if self.rr: - self._states_list = [] + self.rr = SnapshotManager(ql) if rr else None self.ctx = setup_ctx_manager(ql) self.predictor = setup_branch_predictor(ql) @@ -68,11 +65,14 @@ def bp_handler(ql, address, size, bp_list): self.cur_addr = self.ql.loader.entry_point + if self.rr: + self.rr.save() + if self.ql.archtype == QL_ARCH.CORTEX_M: self._run() else: - self._init_state = self.ql.save() + self.init_state = self.ql.save() self.do_context() self.interactive() @@ -93,20 +93,6 @@ def cur_addr(self: QlQdb, address: int) -> None: self.ql.reg.arch_pc = address - def _save(self: QlQdb, *args) -> None: - """ - internal function for saving state of qiling instance - """ - - self._states_list.append(self.ql.save()) - - def _restore(self: QlQdb, *args) -> None: - """ - internal function for restoring state of qiling instance - """ - - self.ql.restore(self._states_list.pop()) - def _run(self: Qldbg, address: int = 0, end: int = 0, count: int = 0) -> None: """ internal function for emulating instruction @@ -202,12 +188,12 @@ def do_backward(self: QlQdb, *args) -> None: step barkward if it's possible, option rr should be enabled and previous instruction must be executed before """ - if getattr(self, "_states_list", None) is None or len(self._states_list) == 0: + if len(self.rr.layers) == 0: print(f"{color.RED}[!] there is no way back !!!{color.END}") else: print(f"{color.CYAN}[+] step backward ~{color.END}") - self._restore() + self.rr.restore() self.do_context() def update_reg_dump(self: QlQdb) -> None: @@ -227,9 +213,6 @@ def do_step_in(self: QlQdb, *args) -> Optional[bool]: else: self.update_reg_dump() - if self.rr: - self._save() - prophecy = self.predictor.predict() if prophecy.where is True: @@ -240,6 +223,9 @@ def do_step_in(self: QlQdb, *args) -> Optional[bool]: else: self._run(count=1) + if self.rr: + self.rr.save() + self.do_context() def do_step_over(self: QlQdb, *args) -> Option[bool]: @@ -262,7 +248,10 @@ def do_step_over(self: QlQdb, *args) -> Option[bool]: else: self.set_breakpoint(prophecy.where, is_temp=True) - self._run() + self._run() + + if self.rr: + self.rr.save() def set_breakpoint(self: QlQdb, address: int, is_temp: bool = False) -> None: """ @@ -287,7 +276,7 @@ def do_start(self: QlQdb, *args) -> None: if self.ql.archtype != QL_ARCH.CORTEX_M: - self.ql.restore(self._init_state) + self.ql.restore(self.init_state) self.do_context() @parse_int @@ -337,6 +326,7 @@ def do_show(self: QlQdb, *args) -> None: self.ql.mem.show_mapinfo() print(f"Breakpoints: {[hex(addr) for addr in self.bp_list.keys()]}") + print(f"Snaptshots: {len(self.rr.layers)}") @parse_int def do_disassemble(self: QlQdb, address: Optional[int] = 0, *args) -> None: diff --git a/qiling/debugger/qdb/utils.py b/qiling/debugger/qdb/utils.py index 9178a2621..55810eb95 100644 --- a/qiling/debugger/qdb/utils.py +++ b/qiling/debugger/qdb/utils.py @@ -5,10 +5,10 @@ from __future__ import annotations from typing import Callable, Optional, Mapping +import ast, re + from qiling.const import * -from collections import namedtuple -import ast, re # parse unsigned integer from string def read_int(s: str) -> int: @@ -69,7 +69,7 @@ def disasm(ql: Qiling, address: int, detail: bool = False) -> Optional[int]: md = ql.disassembler md.detail = detail try: - ret = next(md.disasm(read_inst(ql, address), address)) + ret = next(md.disasm(read_insn(ql, address), address)) except StopIteration: ret = None @@ -77,7 +77,7 @@ def disasm(ql: Qiling, address: int, detail: bool = False) -> Optional[int]: return ret -def read_inst(ql: Qiling, addr: int) -> int: +def read_insn(ql: Qiling, addr: int) -> int: result = ql.mem.read(addr, 4) if ql.archtype in (QL_ARCH.ARM, QL_ARCH.ARM_THUMB, QL_ARCH.CORTEX_M): @@ -117,6 +117,7 @@ def setup_branch_predictor(ql: Qiling) -> BranchPredictor: QL_ARCH.MIPS: BranchPredictor_MIPS, }.get(ql.archtype)(ql) + class Prophecy(object): def __init__(self): self.going = False @@ -125,6 +126,7 @@ def __init__(self): def __iter__(self): return iter((self.going, self.where)) + class BranchPredictor(object): def __init__(self, ql): self.ql = ql @@ -135,6 +137,7 @@ def read_reg(self, reg_name): def predict(self): return NotImplementedError + class BranchPredictor_ARM(BranchPredictor): def __init__(self, ql): super().__init__(ql) @@ -244,8 +247,8 @@ def predict(self): if self.regdst_eq_pc(line.op_str): next_addr = cur_addr + line.size - n2_addr = next_addr + len(read_inst(next_addr)) - prophecy.where += len(read_inst(n2_addr)) + len(read_inst(next_addr)) + n2_addr = next_addr + len(read_insn(next_addr)) + prophecy.where += len(read_insn(n2_addr)) + len(read_insn(next_addr)) elif line.mnemonic.startswith("it"): # handle IT block here @@ -267,14 +270,14 @@ def predict(self): next_addr = cur_addr + self.THUMB_INST_SIZE for each in it_block_range: - _inst = read_inst(next_addr) + _insn = read_insn(next_addr) n2_addr = handle_bnj_arm(ql, next_addr) if (cond_met and each == "t") or (not cond_met and each == "e"): - if n2_addr != (next_addr+len(_inst)): # branch detected + if n2_addr != (next_addr+len(_insn)): # branch detected break - next_addr += len(_inst) + next_addr += len(_insn) prophecy.where = next_addr @@ -558,6 +561,115 @@ def __init__(self, addr): class ParseError(Exception): pass +class SnapshotManager(object): + """ + for functioning differential snapshot + """ + + class State(object): + """ + internal container for storing snapshot state + """ + + def __init__(self, saved_state): + self.reg, self.ram = SnapshotManager.transform(saved_state) + + @classmethod + def transform(cls, st): + reg = st["reg"] if "reg" in st else st[0] + + if "mem" not in st: + return (reg, st[1]) + + ram = [] + for mem_seg in st["mem"]["ram"]: + lbound, ubound, perms, label, raw_bytes = mem_seg + rb_set = {(idx, val) for idx, val in enumerate(raw_bytes)} + ram.append((lbound, ubound, perms, label, rb_set)) + + return (reg, ram) + + def __init__(self, ql): + self.ql = ql + self.layers = [] + + def diff_reg(self, prev_reg, cur_reg): + diffed_reg = {} + + for reg_name, reg_val in cur_reg.items(): + if prev_reg[reg_name] != reg_val: + diffed_reg[reg_name] = prev_reg[reg_name] + + return diffed_reg + + def diff_ram(self, prev_ram, cur_ram): + if any((cur_ram is None, prev_ram is None, prev_ram == cur_ram)): + return + + ram = [] + paired = zip(prev_ram, cur_ram) + for each in paired: + # lbound, ubound, perm, label, data + *prev_others, prev_rb_set = each[0] + *cur_others, cur_rb_set = each[1] + + if prev_others == cur_others and cur_rb_set != prev_rb_set: + diff_set = prev_rb_set - cur_rb_set + else: + continue + + ram.append((*cur_others, diff_set)) + + return ram + + def diff(self, cur_st): + last_st = self.layers.pop() + diffed_reg = self.diff_reg(last_st.reg, cur_st.reg) + diffed_ram = self.diff_ram(last_st.ram, cur_st.ram) + return self.State((diffed_reg, diffed_ram)) + + def _save(self) -> State: + return self.State(self.ql.save()) + + def save(self): + """ + helper function for saving differential context + """ + + st = self._save() + + if len(self.layers) != 0 and isinstance(self.layers[-1], dict): + st = self.diff(st) + + self.layers.append(st) + + def restore(self): + prev_st = self.layers.pop() + cur_st = self._save() + + for reg_name, reg_value in prev_st.reg.items(): + cur_st.reg[reg_name] = reg_value + + to_be_restored = {"reg": cur_st.reg} + + if getattr(prev_st, "ram", None) and prev_st.ram != cur_st.ram: + + ram = [] + # lbound, ubound, perm, label, data + for each in prev_st.ram: + *prev_others, prev_rb_set = each + for *cur_others, cur_rb_set in cur_st.ram: + if prev_others == cur_others: + cur_rb_dict = dict(cur_rb_set) + for idx, val in prev_rb_set: + cur_rb_dict[idx] = val + + bs = bytes(dict(sorted(cur_rb_dict.items())).values()) + ram.append((*cur_others, bs)) + + to_be_restored.update({"mem": {"ram": ram, "mmio": {}}}) + + self.ql.restore(to_be_restored) if __name__ == "__main__": pass From 28da577c2834f822b5275e9b572bc964c4d47797 Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Tue, 15 Feb 2022 02:04:00 +0800 Subject: [PATCH 122/406] more refactor and comments, separate gadget functions into misc.py --- qiling/debugger/qdb/const.py | 28 +-- qiling/debugger/qdb/frontend.py | 192 +++++------------ qiling/debugger/qdb/misc.py | 156 ++++++++++++++ qiling/debugger/qdb/qdb.py | 49 +++-- qiling/debugger/qdb/utils.py | 353 +++++++++++++++++++++----------- 5 files changed, 481 insertions(+), 297 deletions(-) create mode 100644 qiling/debugger/qdb/misc.py diff --git a/qiling/debugger/qdb/const.py b/qiling/debugger/qdb/const.py index fd8b358ef..562b41d3d 100644 --- a/qiling/debugger/qdb/const.py +++ b/qiling/debugger/qdb/const.py @@ -1,5 +1,7 @@ -# class for colorful prints class color: + """ + class for colorful prints + """ CYAN = '\033[96m' PURPLE = '\033[95m' BLUE = '\033[94m' @@ -14,27 +16,3 @@ class color: BOLD = '\033[1m' END = '\033[0m' RESET = '\x1b[39m' - - - -FORMAT_LETTER = { - "o", # octal - "x", # hex - "d", # decimal - "u", # unsigned decimal - "t", # binary - "f", # float - "a", # address - "i", # instruction - "c", # char - "s", # string - "z", # hex, zero padded on the left - } - - -SIZE_LETTER = { - "b": 1, # 1-byte, byte - "h": 2, # 2-byte, halfword - "w": 4, # 4-byte, word - "g": 8, # 8-byte, giant - } diff --git a/qiling/debugger/qdb/frontend.py b/qiling/debugger/qdb/frontend.py index e24c81f68..6db79e8f2 100644 --- a/qiling/debugger/qdb/frontend.py +++ b/qiling/debugger/qdb/frontend.py @@ -5,146 +5,15 @@ from __future__ import annotations from typing import Optional, Mapping, Iterable, Union -import copy, math, os +import copy import unicorn from qiling.const import QL_ARCH -from .utils import disasm, get_x86_eflags, setup_branch_predictor, read_int -from .const import color, SIZE_LETTER, FORMAT_LETTER - - -# read data from memory of qiling instance -def examine_mem(ql: Qiling, line: str) -> Union[bool, (str, int, int)]: - - _args = line.split() - DEFAULT_FMT = ('x', 4, 1) - - if line.startswith("/"): # followed by format letter and size letter - - def get_fmt(text): - def extract_count(t): - return "".join([s for s in t if s.isdigit()]) - - f, s, c = DEFAULT_FMT - if extract_count(text): - c = int(extract_count(text)) - - for char in text.strip(str(c)): - if char in SIZE_LETTER.keys(): - s = SIZE_LETTER.get(char) - - elif char in FORMAT_LETTER: - f = char - - return (f, s, c) - - - fmt, *rest = line.strip("/").split() - - rest = "".join(rest) - - fmt = get_fmt(fmt) - - elif len(_args) == 1: # only address - rest = _args[0] - fmt = DEFAULT_FMT - - else: - rest = _args - - if ql.archtype in (QL_ARCH.ARM, QL_ARCH.ARM_THUMB): - rest = rest.replace("fp", "r11") - - elif ql.archtype == QL_ARCH.MIPS: - rest = rest.replace("fp", "s8") - - - # for supporting addition of register with constant value - elems = rest.split("+") - elems = [elem.strip("$") for elem in elems] - - items = [] - - for elem in elems: - if elem in ql.reg.register_mapping.keys(): - if (value := getattr(ql.reg, elem, None)): - items.append(value) - else: - items.append(read_int(elem)) - - addr = sum(items) - - def unpack(bs, sz): - return { - 1: lambda x: x[0], - 2: ql.unpack16, - 4: ql.unpack32, - 8: ql.unpack64, - }.get(sz)(bs) - - ft, sz, ct = fmt - - if ft == "i": - - for offset in range(addr, addr+ct*4, 4): - line = disasm(ql, offset) - if line: - print(f"0x{line.address:x}: {line.mnemonic}\t{line.op_str}") - - print() - - else: - lines = 1 if ct <= 4 else math.ceil(ct / 4) - - mem_read = [] - for offset in range(ct): - # append data if read successfully, otherwise return error message - if (data := _try_read(ql, addr+(offset*sz), sz))[0] is not None: - mem_read.append(data[0]) - - else: - return data[1] - - for line in range(lines): - offset = line * sz * 4 - print(f"0x{addr+offset:x}:\t", end="") - - idx = line * ql.pointersize - for each in mem_read[idx:idx+ql.pointersize]: - data = unpack(each, sz) - prefix = "0x" if ft in ("x", "a") else "" - pad = '0' + str(sz*2) if ft in ('x', 'a', 't') else '' - ft = ft.lower() if ft in ("x", "o", "b", "d") else ft.lower().replace("t", "b").replace("a", "x") - print(f"{prefix}{data:{pad}{ft}}\t", end="") - - print() - - return True - - -# get terminal window height and width -def get_terminal_size() -> Iterable: - return map(int, os.popen('stty size', 'r').read().split()) - - -# try to read data from ql memory -def _try_read(ql: Qiling, address: int, size: int) -> Optional[bytes]: - - result = None - err_msg = "" - try: - result = ql.mem.read(address, size) - - except unicorn.unicorn.UcError as err: - if err.errno == 6: # Invalid memory read (UC_ERR_READ_UNMAPPED) - err_msg = f"Can not access memory at address 0x{address:08x}" - - except: - pass - - return (result, err_msg) +from .utils import setup_branch_predictor +from .misc import try_read, read_int, get_terminal_size, disasm, get_x86_eflags +from .const import color """ @@ -155,8 +24,11 @@ def _try_read(ql: Qiling, address: int, size: int) -> Optional[bytes]: COLORS = (color.DARKCYAN, color.BLUE, color.RED, color.YELLOW, color.GREEN, color.PURPLE, color.CYAN, color.WHITE) -# decorator function for printing divider def context_printer(field_name, ruler="─"): + """ + decorator function for printing divider + """ + def decorator(context_dumper): def wrapper(*args, **kwargs): height, width = get_terminal_size() @@ -170,6 +42,10 @@ def wrapper(*args, **kwargs): def setup_ctx_manager(ql: Qiling) -> CtxManager: + """ + setup context manager for corresponding archtype + """ + return { QL_ARCH.X86: CtxManager_X86, QL_ARCH.ARM: CtxManager_ARM, @@ -180,11 +56,19 @@ def setup_ctx_manager(ql: Qiling) -> CtxManager: class CtxManager(object): + """ + base class for context manager + """ + def __init__(self, ql): self.ql = ql self.predictor = setup_branch_predictor(ql) def print_asm(self, insn: CsInsn, to_jump: Optional[bool] = None, address: int = None) -> None: + """ + helper function for printing assembly instructions, indicates where we are and the branch prediction + provided by BranchPredictor + """ opcode = "".join(f"{b:02x}" for b in insn.bytes) if self.ql.archtype in (QL_ARCH.X86, QL_ARCH.X8664): @@ -203,26 +87,37 @@ def print_asm(self, insn: CsInsn, to_jump: Optional[bool] = None, address: int = print(f"{jump_sign} {cursor} {color.DARKGRAY}{trace_line}{color.END}") def dump_regs(self): + """ + dump all registers + """ + return {reg_name: getattr(self.ql.reg, reg_name) for reg_name in self.regs} def context_reg(self, saved_states): + """ + display context registers + """ + return NotImplementedError @context_printer("[ STACK ]") def context_stack(self): + """ + display context stack + """ for idx in range(10): addr = self.ql.reg.arch_sp + idx * self.ql.pointersize - if (val := _try_read(self.ql, addr, self.ql.pointersize)[0]): + if (val := try_read(self.ql, addr, self.ql.pointersize)[0]): print(f"$sp+0x{idx*self.ql.pointersize:02x}│ [0x{addr:08x}] —▸ 0x{self.ql.unpack(val):08x}", end="") # try to dereference wether it's a pointer - if (buf := _try_read(self.ql, addr, self.ql.pointersize))[0] is not None: + if (buf := try_read(self.ql, addr, self.ql.pointersize))[0] is not None: if (addr := self.ql.unpack(buf[0])): # try to dereference again - if (buf := _try_read(self.ql, addr, self.ql.pointersize))[0] is not None: + if (buf := try_read(self.ql, addr, self.ql.pointersize))[0] is not None: try: s = self.ql.mem.string(addr) except: @@ -236,6 +131,10 @@ def context_stack(self): @context_printer("[ DISASM ]") def context_asm(self): + """ + display context assembly + """ + # assembly before current location past_list = [] cur_addr = self.ql.reg.arch_pc @@ -275,6 +174,10 @@ def context_asm(self): class CtxManager_ARM(CtxManager): + """ + context manager for ARM + """ + def __init__(self, ql): super().__init__(ql) @@ -348,6 +251,9 @@ def context_reg(self, saved_reg_dump): class CtxManager_MIPS(CtxManager): + """ + context manager for MIPS + """ def __init__(self, ql): super().__init__(ql) @@ -391,6 +297,9 @@ def context_reg(self, saved_reg_dump): class CtxManager_X86(CtxManager): + """ + context manager for X86 + """ def __init__(self, ql): super().__init__(ql) @@ -400,6 +309,7 @@ def __init__(self, ql): "eip", "ss", "cs", "ds", "es", "fs", "gs", "ef", ) + @context_printer("[ REGISTERS ]") def context_reg(self, saved_reg_dump): cur_regs = self.dump_regs() @@ -453,6 +363,10 @@ def context_asm(self): class CtxManager_CORTEX_M(CtxManager): + """ + context manager for cortex_m + """ + def __init__(self, ql): super().__init__(ql) diff --git a/qiling/debugger/qdb/misc.py b/qiling/debugger/qdb/misc.py new file mode 100644 index 000000000..dbffe5828 --- /dev/null +++ b/qiling/debugger/qdb/misc.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from __future__ import annotations +from typing import Callable, Optional, Mapping +import os + +from qiling.const import QL_ARCH +import unicorn + + + +def get_terminal_size() -> Iterable: + """ + get terminal window height and width + """ + return map(int, os.popen('stty size', 'r').read().split()) + + +def try_read(ql: Qiling, address: int, size: int) -> Optional[bytes]: + """ + try to read data from ql.mem + """ + + result = None + err_msg = "" + try: + result = ql.mem.read(address, size) + + except unicorn.unicorn.UcError as err: + if err.errno == 6: # Invalid memory read (UC_ERR_READ_UNMAPPED) + err_msg = f"Can not access memory at address 0x{address:08x}" + + except: + pass + + return (result, err_msg) + + +def read_int(s: str) -> int: + """ + parse unsigned integer from string + """ + return int(s, 0) + + +def parse_int(func: Callable) -> Callable: + """ + function dectorator for parsing argument as integer + """ + def wrap(qdb, s: str = "") -> int: + assert type(s) is str + try: + ret = read_int(s) + except: + ret = None + return func(qdb, ret) + return wrap + + +def is_negative(i: int) -> int: + """ + check wether negative value or not + """ + return i & (1 << 31) + + +def signed_val(val: int) -> int: + """ + signed value convertion + """ + return (val-1 << 32) if is_negative(val) else val + + +def get_cpsr(bits: int) -> (bool, bool, bool, bool): + """ + get flags from ql.reg.cpsr + """ + return ( + bits & 0x10000000 != 0, # V, overflow flag + bits & 0x20000000 != 0, # C, carry flag + bits & 0x40000000 != 0, # Z, zero flag + bits & 0x80000000 != 0, # N, sign flag + ) + + +def is_thumb(bits: int) -> bool: + """ + helper function for checking thumb mode + """ + + return bits & 0x00000020 != 0 + + +def get_x86_eflags(bits: int) -> Dict[str, bool]: + """ + get flags from ql.reg.ef + """ + + return { + "CF" : bits & 0x0001 != 0, # CF, carry flag + "PF" : bits & 0x0004 != 0, # PF, parity flag + "AF" : bits & 0x0010 != 0, # AF, adjust flag + "ZF" : bits & 0x0040 != 0, # ZF, zero flag + "SF" : bits & 0x0080 != 0, # SF, sign flag + "OF" : bits & 0x0800 != 0, # OF, overflow flag + } + + +def disasm(ql: Qiling, address: int, detail: bool = False) -> Optional[int]: + """ + helper function for disassembling + """ + + md = ql.disassembler + md.detail = detail + try: + ret = next(md.disasm(read_insn(ql, address), address)) + + except StopIteration: + ret = None + + return ret + + +def read_insn(ql: Qiling, addr: int) -> int: + """ + read instruction from running qiling instance + """ + result = ql.mem.read(addr, 4) + + if ql.archtype in (QL_ARCH.ARM, QL_ARCH.ARM_THUMB, QL_ARCH.CORTEX_M): + if is_thumb(ql.reg.cpsr): + + first_two = ql.unpack16(ql.mem.read(addr, 2)) + result = ql.pack16(first_two) + + # to judge whether it's thumb mode or not + if any([ + first_two & 0xf000 == 0xf000, + first_two & 0xf800 == 0xf800, + first_two & 0xe800 == 0xe800, + ]): + + latter_two = ql.unpack16(ql.mem.read(addr+2, 2)) + result += ql.pack16(latter_two) + + elif ql.archtype in (QL_ARCH.X86, QL_ARCH.X8664): + # due to the variadic lengh of x86 instructions ( 1~15 ) + # always assume the maxium size for disassembler to tell + # what is it exactly. + result = ql.mem.read(addr, 15) + + return result diff --git a/qiling/debugger/qdb/qdb.py b/qiling/debugger/qdb/qdb.py index 3cc21ef0a..c069ecba0 100644 --- a/qiling/debugger/qdb/qdb.py +++ b/qiling/debugger/qdb/qdb.py @@ -12,13 +12,17 @@ from qiling.const import QL_ARCH, QL_VERBOSE from qiling.debugger import QlDebugger -from .frontend import examine_mem, setup_ctx_manager -from .utils import is_thumb, parse_int, setup_branch_predictor, disasm -from .utils import Breakpoint, TempBreakpoint, SnapshotManager +from .frontend import setup_ctx_manager +from .utils import Breakpoint, TempBreakpoint +from .utils import setup_branch_predictor, SnapshotManager, MemoryManager +from .misc import disasm, parse_int, is_thumb from .const import color class QlQdb(cmd.Cmd, QlDebugger): + """ + The built-in debugger of Qiling Framework + """ def __init__(self: QlQdb, ql: Qiling, init_hook: str = "", rr: bool = False) -> None: @@ -28,6 +32,7 @@ def __init__(self: QlQdb, ql: Qiling, init_hook: str = "", rr: bool = False) -> self.bp_list = {} self.rr = SnapshotManager(ql) if rr else None + self.mm = MemoryManager(ql) self.ctx = setup_ctx_manager(ql) self.predictor = setup_branch_predictor(ql) @@ -66,6 +71,7 @@ def bp_handler(ql, address, size, bp_list): self.cur_addr = self.ql.loader.entry_point if self.rr: + # initial context save for diff snapshot self.rr.save() if self.ql.archtype == QL_ARCH.CORTEX_M: @@ -188,13 +194,17 @@ def do_backward(self: QlQdb, *args) -> None: step barkward if it's possible, option rr should be enabled and previous instruction must be executed before """ - if len(self.rr.layers) == 0: - print(f"{color.RED}[!] there is no way back !!!{color.END}") + if self.rr: + if len(self.rr.layers) == 0 or not isinstance(self.rr.layers[-1], self.rr.DiffedState): + print(f"{color.RED}[!] there is no way back !!!{color.END}") + else: + print(f"{color.CYAN}[+] step backward ~{color.END}") + self.rr.restore() + self.do_context() else: - print(f"{color.CYAN}[+] step backward ~{color.END}") - self.rr.restore() - self.do_context() + print(f"{color.RED}[!] the option rr not yet been set !!!{color.END}") + def update_reg_dump(self: QlQdb) -> None: """ @@ -224,6 +234,8 @@ def do_step_in(self: QlQdb, *args) -> Optional[bool]: self._run(count=1) if self.rr: + # context save after execution, so we could do diff + # on two states before and after. self.rr.save() self.do_context() @@ -248,10 +260,12 @@ def do_step_over(self: QlQdb, *args) -> Option[bool]: else: self.set_breakpoint(prophecy.where, is_temp=True) - self._run() + self._run() - if self.rr: - self.rr.save() + if self.rr: + # context save after execution, so we could do diff + # on two states before and after. + self.rr.save() def set_breakpoint(self: QlQdb, address: int, is_temp: bool = False) -> None: """ @@ -313,11 +327,11 @@ def do_examine(self: QlQdb, line: str) -> None: e.g. x/4wx 0x41414141 , print 4 word size begin from address 0x41414141 in hex """ - try: - if type(err_msg := examine_mem(self.ql, line)) is str: - print(f"{color.RED}[!] {err_msg} ...{color.END}") - except: - print(f"{color.RED}[!] something went wrong ...{color.END}") + # try: + if type(err_msg := self.mm.parse(line)) is str: + print(f"{color.RED}[!] {err_msg} ...{color.END}") + # except: + # print(f"{color.RED}[!] something went wrong ...{color.END}") def do_show(self: QlQdb, *args) -> None: """ @@ -326,7 +340,8 @@ def do_show(self: QlQdb, *args) -> None: self.ql.mem.show_mapinfo() print(f"Breakpoints: {[hex(addr) for addr in self.bp_list.keys()]}") - print(f"Snaptshots: {len(self.rr.layers)}") + if self.rr: + print(f"Snapshots: {len([st for st in self.rr.layers if isinstance(st, self.rr.DiffedState)])}") @parse_int def do_disassemble(self: QlQdb, address: Optional[int] = 0, *args) -> None: diff --git a/qiling/debugger/qdb/utils.py b/qiling/debugger/qdb/utils.py index 55810eb95..0719f9425 100644 --- a/qiling/debugger/qdb/utils.py +++ b/qiling/debugger/qdb/utils.py @@ -5,110 +5,25 @@ from __future__ import annotations from typing import Callable, Optional, Mapping -import ast, re +import ast, re, math -from qiling.const import * +from qiling.const import QL_ARCH +from .misc import try_read, disasm, get_x86_eflags, read_int -# parse unsigned integer from string -def read_int(s: str) -> int: - return int(s, 0) -# function dectorator for parsing argument as integer -def parse_int(func: Callable) -> Callable: - def wrap(qdb, s: str = "") -> int: - assert type(s) is str - try: - ret = read_int(s) - except: - ret = None - return func(qdb, ret) - return wrap - - -# check wether negative value or not -def is_negative(i: int) -> int: - return i & (1 << 31) - - -# signed value convertion -def signed_val(val: int) -> int: - return (val-1 << 32) if is_negative(val) else val - - -def get_cpsr(bits: int) -> (bool, bool, bool, bool): - return ( - bits & 0x10000000 != 0, # V, overflow flag - bits & 0x20000000 != 0, # C, carry flag - bits & 0x40000000 != 0, # Z, zero flag - bits & 0x80000000 != 0, # N, sign flag - ) - - -def get_x86_eflags(bits: int) -> Dict[str, bool]: - return { - "CF" : bits & 0x0001 != 0, # CF, carry flag - "PF" : bits & 0x0004 != 0, # PF, parity flag - "AF" : bits & 0x0010 != 0, # AF, adjust flag - "ZF" : bits & 0x0040 != 0, # ZF, zero flag - "SF" : bits & 0x0080 != 0, # SF, sign flag - "OF" : bits & 0x0800 != 0, # OF, overflow flag - } - +""" -def is_thumb(bits: int) -> bool: - return bits & 0x00000020 != 0 + Try to predict certian branch will be taken or not based on current context +""" -def disasm(ql: Qiling, address: int, detail: bool = False) -> Optional[int]: +def setup_branch_predictor(ql: Qiling) -> BranchPredictor: """ - helper function for disassembling + setup BranchPredictor for corresponding archtype """ - md = ql.disassembler - md.detail = detail - try: - ret = next(md.disasm(read_insn(ql, address), address)) - - except StopIteration: - ret = None - - return ret - - -def read_insn(ql: Qiling, addr: int) -> int: - result = ql.mem.read(addr, 4) - - if ql.archtype in (QL_ARCH.ARM, QL_ARCH.ARM_THUMB, QL_ARCH.CORTEX_M): - if is_thumb(ql.reg.cpsr): - - first_two = ql.unpack16(ql.mem.read(addr, 2)) - result = ql.pack16(first_two) - - # to judge whether it's thumb mode or not - if any([ - first_two & 0xf000 == 0xf000, - first_two & 0xf800 == 0xf800, - first_two & 0xe800 == 0xe800, - ]): - - latter_two = ql.unpack16(ql.mem.read(addr+2, 2)) - result += ql.pack16(latter_two) - - elif ql.archtype in (QL_ARCH.X86, QL_ARCH.X8664): - # due to the variadic lengh of x86 instructions ( 1~15 ) - # always assume the maxium size for disassembler to tell - # what is it exactly. - result = ql.mem.read(addr, 15) - - return result - -""" - Try to predict certian branch will be taken or not based on current context -""" - -def setup_branch_predictor(ql: Qiling) -> BranchPredictor: return { QL_ARCH.X86: BranchPredictor_X86, QL_ARCH.ARM: BranchPredictor_ARM, @@ -117,8 +32,13 @@ def setup_branch_predictor(ql: Qiling) -> BranchPredictor: QL_ARCH.MIPS: BranchPredictor_MIPS, }.get(ql.archtype)(ql) - class Prophecy(object): + """ + container for storing result of the predictor + @going: indicate the certian branch will be taken or not + @where: where will it go if going is true + """ + def __init__(self): self.going = False self.where = None @@ -126,19 +46,25 @@ def __init__(self): def __iter__(self): return iter((self.going, self.where)) - class BranchPredictor(object): + """ + Base class for predictor + """ + def __init__(self, ql): self.ql = ql def read_reg(self, reg_name): return getattr(self.ql.reg, reg_name) - def predict(self): + def predict(self) -> Prophecy: return NotImplementedError - class BranchPredictor_ARM(BranchPredictor): + """ + predictor for ARM + """ + def __init__(self, ql): super().__init__(ql) @@ -363,6 +289,10 @@ def predict(self): return prophecy class BranchPredictor_MIPS(BranchPredictor): + """ + predictor for MIPS + """ + def __init__(self, ql): super().__init__(ql) self.CODE_END = "break" @@ -424,10 +354,20 @@ def predict(self): return prophecy class BranchPredictor_X86(BranchPredictor): + """ + predictor for X86 + """ + + class ParseError(Exception): + """ + indicate parser error + """ + pass + def __init__(self, ql): super().__init__(ql) - def predict(self): + def predict(self) -> Prophecy: prophecy = Prophecy() cur_addr = self.ql.reg.arch_pc line = disasm(self.ql, cur_addr) @@ -558,24 +498,48 @@ class TempBreakpoint(Breakpoint): def __init__(self, addr): super().__init__(addr) -class ParseError(Exception): - pass +""" -class SnapshotManager(object): + For supporting Qdb features like: + 1. record/replay debugging + 2. memory access in gdb-style + +""" + +class Manager(object): + """ + base class for Manager + """ + def __init__(self, ql): + self.ql = ql + +class SnapshotManager(Manager): """ for functioning differential snapshot """ class State(object): """ - internal container for storing snapshot state + internal container for storing raw state from qiling """ def __init__(self, saved_state): self.reg, self.ram = SnapshotManager.transform(saved_state) + class DiffedState(object): + """ + internal container for storing diffed state + """ + + def __init__(self, diffed_st): + self.reg, self.ram = diffed_st + @classmethod def transform(cls, st): + """ + transform saved context into binary set + """ + reg = st["reg"] if "reg" in st else st[0] if "mem" not in st: @@ -590,19 +554,29 @@ def transform(cls, st): return (reg, ram) def __init__(self, ql): - self.ql = ql + super().__init__(ql) self.layers = [] - def diff_reg(self, prev_reg, cur_reg): - diffed_reg = {} + def _save(self) -> State(): + """ + acquire current State by wrapping saved context from ql.save() + """ + + return self.State(self.ql.save()) - for reg_name, reg_val in cur_reg.items(): - if prev_reg[reg_name] != reg_val: - diffed_reg[reg_name] = prev_reg[reg_name] + def diff_reg(self, prev_reg, cur_reg): + """ + diff two register values + """ - return diffed_reg + diffed = filter(lambda t: t[0] != t[1], zip(prev_reg.items(), cur_reg.items())) + return {prev[0]: prev[1] for prev, _ in diffed} def diff_ram(self, prev_ram, cur_ram): + """ + diff two ram data if needed + """ + if any((cur_ram is None, prev_ram is None, prev_ram == cur_ram)): return @@ -623,13 +597,14 @@ def diff_ram(self, prev_ram, cur_ram): return ram def diff(self, cur_st): - last_st = self.layers.pop() - diffed_reg = self.diff_reg(last_st.reg, cur_st.reg) - diffed_ram = self.diff_ram(last_st.ram, cur_st.ram) - return self.State((diffed_reg, diffed_ram)) + """ + diff between previous and current state + """ - def _save(self) -> State: - return self.State(self.ql.save()) + prev_st = self.layers.pop() + diffed_reg = self.diff_reg(prev_st.reg, cur_st.reg) + diffed_ram = self.diff_ram(prev_st.ram, cur_st.ram) + return self.DiffedState((diffed_reg, diffed_ram)) def save(self): """ @@ -638,12 +613,17 @@ def save(self): st = self._save() - if len(self.layers) != 0 and isinstance(self.layers[-1], dict): + if len(self.layers) > 0 and isinstance(self.layers[-1], self.State): + # merge two context_save to be a diffed state st = self.diff(st) self.layers.append(st) def restore(self): + """ + helper function for restoring running state from an existing incremental snapshot + """ + prev_st = self.layers.pop() cur_st = self._save() @@ -671,5 +651,146 @@ def restore(self): self.ql.restore(to_be_restored) +class MemoryManager(Manager): + """ + memory manager for handing memory access + """ + + def __init__(self, ql): + super().__init__(ql) + + self.DEFAULT_FMT = ('x', 4, 1) + + self.FORMAT_LETTER = { + "o", # octal + "x", # hex + "d", # decimal + "u", # unsigned decimal + "t", # binary + "f", # float + "a", # address + "i", # instruction + "c", # char + "s", # string + "z", # hex, zero padded on the left + } + + self.SIZE_LETTER = { + "b": 1, # 1-byte, byte + "h": 2, # 2-byte, halfword + "w": 4, # 4-byte, word + "g": 8, # 8-byte, giant + } + + def extract_count(self, t): + return "".join([s for s in t if s.isdigit()]) + + def get_fmt(self, text): + + f, s, c = self.DEFAULT_FMT + if self.extract_count(text): + c = int(self.extract_count(text)) + + for char in text.strip(str(c)): + if char in self.SIZE_LETTER.keys(): + s = self.SIZE_LETTER.get(char) + + elif char in self.FORMAT_LETTER: + f = char + + return (f, s, c) + + def unpack(self, bs: bytes, sz: int) -> int: + return { + 1: lambda x: x[0], + 2: self.ql.unpack16, + 4: self.ql.unpack32, + 8: self.ql.unpack64, + }.get(sz)(bs) + + def parse(self, line: str): + args = line.split() + + if line.startswith("/"): # followed by format letter and size letter + + fmt, *rest = line.strip("/").split() + + rest = "".join(rest) + + fmt = self.get_fmt(fmt) + + elif len(args) == 1: # only address + rest = args[0] + fmt = DEFAULT_FMT + + else: + rest = args + + if self.ql.archtype in (QL_ARCH.ARM, QL_ARCH.ARM_THUMB): + rest = rest.replace("fp", "r11") + + elif self.ql.archtype == QL_ARCH.MIPS: + rest = rest.replace("fp", "s8") + + + # for supporting addition of register with constant value + elems = rest.split("+") + elems = [elem.strip("$") for elem in elems] + + items = [] + + for elem in elems: + if elem in self.ql.reg.register_mapping.keys(): + if (value := getattr(self.ql.reg, elem, None)): + items.append(value) + else: + items.append(read_int(elem)) + + addr = sum(items) + + ft, sz, ct = fmt + + if ft == "i": + + for offset in range(addr, addr+ct*4, 4): + line = disasm(self.ql, offset) + if line: + print(f"0x{line.address:x}: {line.mnemonic}\t{line.op_str}") + + print() + + else: + lines = 1 if ct <= 4 else math.ceil(ct / 4) + + mem_read = [] + for offset in range(ct): + # append data if read successfully, otherwise return error message + if (data := try_read(self.ql, addr+(offset*sz), sz))[0] is not None: + mem_read.append(data[0]) + + else: + return data[1] + + for line in range(lines): + offset = line * sz * 4 + print(f"0x{addr+offset:x}:\t", end="") + + idx = line * self.ql.pointersize + for each in mem_read[idx:idx+self.ql.pointersize]: + data = self.unpack(each, sz) + prefix = "0x" if ft in ("x", "a") else "" + pad = '0' + str(sz*2) if ft in ('x', 'a', 't') else '' + ft = ft.lower() if ft in ("x", "o", "b", "d") else ft.lower().replace("t", "b").replace("a", "x") + print(f"{prefix}{data:{pad}{ft}}\t", end="") + + print() + + return True + + def read(self, address: int, size: int): + self.ql.read(address, size) + + + if __name__ == "__main__": pass From 57509d0321ccf2c542cc384578748c6faac0809c Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Tue, 15 Feb 2022 02:42:01 +0800 Subject: [PATCH 123/406] rename ContextManager to a less confusing name ContextRender --- qiling/debugger/qdb/frontend.py | 54 ++++++++++++++++----------------- qiling/debugger/qdb/qdb.py | 12 ++++---- 2 files changed, 32 insertions(+), 34 deletions(-) diff --git a/qiling/debugger/qdb/frontend.py b/qiling/debugger/qdb/frontend.py index 6db79e8f2..6d6c0eefc 100644 --- a/qiling/debugger/qdb/frontend.py +++ b/qiling/debugger/qdb/frontend.py @@ -7,8 +7,6 @@ from typing import Optional, Mapping, Iterable, Union import copy -import unicorn - from qiling.const import QL_ARCH from .utils import setup_branch_predictor @@ -18,13 +16,13 @@ """ - Context Manager for rendering UI + Context Render for rendering UI """ COLORS = (color.DARKCYAN, color.BLUE, color.RED, color.YELLOW, color.GREEN, color.PURPLE, color.CYAN, color.WHITE) -def context_printer(field_name, ruler="─"): +def divider_printer(field_name, ruler="─"): """ decorator function for printing divider """ @@ -41,23 +39,23 @@ def wrapper(*args, **kwargs): return decorator -def setup_ctx_manager(ql: Qiling) -> CtxManager: +def setup_context_render(ql: Qiling) -> ContextRender: """ - setup context manager for corresponding archtype + setup context render for corresponding archtype """ return { - QL_ARCH.X86: CtxManager_X86, - QL_ARCH.ARM: CtxManager_ARM, - QL_ARCH.ARM_THUMB: CtxManager_ARM, - QL_ARCH.CORTEX_M: CtxManager_ARM, - QL_ARCH.MIPS: CtxManager_MIPS, + QL_ARCH.X86: ContextRender_X86, + QL_ARCH.ARM: ContextRender_ARM, + QL_ARCH.ARM_THUMB: ContextRender_ARM, + QL_ARCH.CORTEX_M: ContextRender_ARM, + QL_ARCH.MIPS: ContextRender_MIPS, }.get(ql.archtype)(ql) -class CtxManager(object): +class ContextRender(object): """ - base class for context manager + base class for context render """ def __init__(self, ql): @@ -100,7 +98,7 @@ def context_reg(self, saved_states): return NotImplementedError - @context_printer("[ STACK ]") + @divider_printer("[ STACK ]") def context_stack(self): """ display context stack @@ -129,7 +127,7 @@ def context_stack(self): print(f" ◂— 0x{self.ql.unpack(buf[0]):08x}", end="") print() - @context_printer("[ DISASM ]") + @divider_printer("[ DISASM ]") def context_asm(self): """ display context assembly @@ -173,9 +171,9 @@ def context_asm(self): forward_insn_size += forward_insn.size -class CtxManager_ARM(CtxManager): +class ContextRender_ARM(ContextRender): """ - context manager for ARM + context render for ARM """ def __init__(self, ql): @@ -215,7 +213,7 @@ def _get_mode(bits): "overflow": bits & 0x10000000 != 0, } - @context_printer("[ REGISTERS ]") + @divider_printer("[ REGISTERS ]") def context_reg(self, saved_reg_dump): cur_regs = self.dump_regs() @@ -250,9 +248,9 @@ def context_reg(self, saved_reg_dump): print(color.GREEN, "[{cpsr[mode]} mode], Thumb: {cpsr[thumb]}, FIQ: {cpsr[fiq]}, IRQ: {cpsr[irq]}, NEG: {cpsr[neg]}, ZERO: {cpsr[zero]}, Carry: {cpsr[carry]}, Overflow: {cpsr[overflow]}".format(cpsr=self.get_flags(self.ql.reg.cpsr)), color.END, sep="") -class CtxManager_MIPS(CtxManager): +class ContextRender_MIPS(ContextRender): """ - context manager for MIPS + context render for MIPS """ def __init__(self, ql): super().__init__(ql) @@ -268,7 +266,7 @@ def __init__(self, ql): "ra", "k0", "k1", "pc", ) - @context_printer("[ REGISTERS ]") + @divider_printer("[ REGISTERS ]") def context_reg(self, saved_reg_dump): cur_regs = self.dump_regs() @@ -296,9 +294,9 @@ def context_reg(self, saved_reg_dump): print(lines.format(*cur_regs.values())) -class CtxManager_X86(CtxManager): +class ContextRender_X86(ContextRender): """ - context manager for X86 + context render for X86 """ def __init__(self, ql): super().__init__(ql) @@ -310,7 +308,7 @@ def __init__(self, ql): "fs", "gs", "ef", ) - @context_printer("[ REGISTERS ]") + @divider_printer("[ REGISTERS ]") def context_reg(self, saved_reg_dump): cur_regs = self.dump_regs() @@ -337,7 +335,7 @@ def context_reg(self, saved_reg_dump): print(lines.format(*cur_regs.values())) print(color.GREEN, "EFLAGS: [CF: {flags[CF]}, PF: {flags[PF]}, AF: {flags[AF]}, ZF: {flags[ZF]}, SF: {flags[SF]}, OF: {flags[OF]}]".format(flags=get_x86_eflags(self.ql.reg.ef)), color.END, sep="") - @context_printer("[ DISASM ]") + @divider_printer("[ DISASM ]") def context_asm(self): past_list = [] cur_addr = self.ql.reg.arch_pc @@ -362,9 +360,9 @@ def context_asm(self): self.print_asm(line) -class CtxManager_CORTEX_M(CtxManager): +class ContextRender_CORTEX_M(ContextRender): """ - context manager for cortex_m + context render for cortex_m """ def __init__(self, ql): @@ -378,7 +376,7 @@ def __init__(self, ql): "xpsr", "control", "primask", "basepri", "faultmask" ) - @context_printer("[ REGISTERS ]") + @divider_printer("[ REGISTERS ]") def context_reg(self, saved_reg_dump): cur_regs.update({"sl": cur_regs.pop("r10")}) diff --git a/qiling/debugger/qdb/qdb.py b/qiling/debugger/qdb/qdb.py index c069ecba0..ac4601248 100644 --- a/qiling/debugger/qdb/qdb.py +++ b/qiling/debugger/qdb/qdb.py @@ -12,7 +12,7 @@ from qiling.const import QL_ARCH, QL_VERBOSE from qiling.debugger import QlDebugger -from .frontend import setup_ctx_manager +from .frontend import setup_context_render from .utils import Breakpoint, TempBreakpoint from .utils import setup_branch_predictor, SnapshotManager, MemoryManager from .misc import disasm, parse_int, is_thumb @@ -33,7 +33,7 @@ def __init__(self: QlQdb, ql: Qiling, init_hook: str = "", rr: bool = False) -> self.rr = SnapshotManager(ql) if rr else None self.mm = MemoryManager(ql) - self.ctx = setup_ctx_manager(ql) + self.render = setup_context_render(ql) self.predictor = setup_branch_predictor(ql) super().__init__() @@ -182,12 +182,12 @@ def do_run(self: QlQdb, *args) -> None: def do_context(self: QlQdb, *args) -> None: """ - show context information for current location + display context information for current location """ - self.ctx.context_reg(self._saved_reg_dump) - self.ctx.context_stack() - self.ctx.context_asm() + self.render.context_reg(self._saved_reg_dump) + self.render.context_stack() + self.render.context_asm() def do_backward(self: QlQdb, *args) -> None: """ From 480b67cfc97e7c5e56794279cf12b5e186550c5f Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 14 Feb 2022 23:25:16 +0200 Subject: [PATCH 124/406] Add convinience method mem.write_ptr --- qiling/os/memory.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/qiling/os/memory.py b/qiling/os/memory.py index 92cff4bd9..b2afaafe7 100644 --- a/qiling/os/memory.py +++ b/qiling/os/memory.py @@ -321,6 +321,31 @@ def write(self, addr: int, data: bytes) -> None: self.ql.uc.mem_write(addr, data) + def write_ptr(self, addr: int, value: int, size: int=None) -> None: + """Write an integer value to a memory address. + Bytes written will be packed using emulated architecture properties. + + Args: + addr: target memory address + value: integer value to write + size: pointer size (in bytes): either 1, 2, 4, 8, or None for arch native size + """ + + if not size: + size = self.ql.arch.pointersize + + __pack = { + 1 : self.ql.pack8, + 2 : self.ql.pack16, + 4 : self.ql.pack32, + 8 : self.ql.pack64 + }.get(size) + + if __pack is None: + raise QlErrorStructConversion(f"Unsupported pointer size: {size}") + + self.write(addr, __pack(value)) + def search(self, needle: bytes, begin: int = None, end: int = None) -> Sequence[int]: """Search for a sequence of bytes in memory. From 9dda9c3e6fa38e2f18855dba25995b4b3bdb568c Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 14 Feb 2022 23:34:44 +0200 Subject: [PATCH 125/406] Adjust suitable mem writes to use mem.write_ptr --- examples/hello_arm_uboot.py | 4 +-- examples/sality.py | 6 ++-- qiling/arch/arch.py | 8 ++--- qiling/loader/elf.py | 31 ++++++++++--------- qiling/loader/pe.py | 4 +-- qiling/os/linux/function_hook.py | 14 ++++----- qiling/os/linux/syscall.py | 4 +-- qiling/os/linux/thread.py | 6 ++-- qiling/os/posix/syscall/prctl.py | 4 +-- qiling/os/posix/syscall/sched.py | 2 +- qiling/os/posix/syscall/sendfile.py | 2 +- qiling/os/posix/syscall/socket.py | 2 +- qiling/os/posix/syscall/unistd.py | 6 ++-- qiling/os/posix/syscall/wait.py | 2 +- qiling/os/qnx/message.py | 2 +- qiling/os/qnx/qnx.py | 4 +-- qiling/os/qnx/syscall.py | 4 +-- qiling/os/uefi/utils.py | 8 ++--- qiling/os/windows/dlls/advapi32.py | 10 +++--- .../windows/dlls/kernel32/errhandlingapi.py | 8 ++--- qiling/os/windows/dlls/kernel32/fileapi.py | 12 +++---- qiling/os/windows/dlls/kernel32/handleapi.py | 2 +- .../os/windows/dlls/kernel32/libloaderapi.py | 2 +- qiling/os/windows/dlls/kernel32/memoryapi.py | 2 +- .../dlls/kernel32/processthreadsapi.py | 6 ++-- qiling/os/windows/dlls/kernel32/profileapi.py | 2 +- qiling/os/windows/dlls/kernel32/winbase.py | 8 ++--- qiling/os/windows/dlls/kernel32/winnt.py | 11 +++---- qiling/os/windows/dlls/msvcrt.py | 15 +++++---- qiling/os/windows/dlls/ntoskrnl.py | 16 +++++----- qiling/os/windows/dlls/oleaut32.py | 2 +- qiling/os/windows/thread.py | 6 ++-- tests/test_blob.py | 4 +-- tests/test_pe_sys.py | 13 ++++---- 34 files changed, 115 insertions(+), 117 deletions(-) diff --git a/examples/hello_arm_uboot.py b/examples/hello_arm_uboot.py index aa83f6419..9ed8fba51 100644 --- a/examples/hello_arm_uboot.py +++ b/examples/hello_arm_uboot.py @@ -46,8 +46,8 @@ def partial_run_init(ql): ql.arch.regs.arch_sp -= 0x20 argv_ptr = ql.arch.regs.arch_sp - ql.mem.write(argv_ptr, ql.pack(arg0_ptr)) - ql.mem.write(argv_ptr + ql.arch.pointersize, ql.pack(arg1_ptr)) + ql.mem.write_ptr(argv_ptr, arg0_ptr) + ql.mem.write_ptr(argv_ptr + ql.arch.pointersize, arg1_ptr) ql.arch.regs.r2 = 2 ql.arch.regs.r3 = argv_ptr diff --git a/examples/sality.py b/examples/sality.py index 71cf1028a..6cc548129 100644 --- a/examples/sality.py +++ b/examples/sality.py @@ -90,7 +90,7 @@ def _WriteFile(ql: Qiling, address: int, params): s = ql.mem.read(lpBuffer, nNumberOfBytesToWrite) ql.os.stdout.write(s) ql.os.stats.log_string(s.decode()) - ql.mem.write(lpNumberOfBytesWritten, ql.pack(nNumberOfBytesToWrite)) + ql.mem.write_ptr(lpNumberOfBytesWritten, nNumberOfBytesToWrite) else: f = ql.os.handle_manager.get(hFile) if f is None: @@ -101,7 +101,7 @@ def _WriteFile(ql: Qiling, address: int, params): f = f.obj buffer = ql.mem.read(lpBuffer, nNumberOfBytesToWrite) f.write(bytes(buffer)) - ql.mem.write(lpNumberOfBytesWritten, ql.pack32(nNumberOfBytesToWrite)) + ql.mem.write_ptr(lpNumberOfBytesWritten, nNumberOfBytesToWrite, 4) return ret @winsdkapi(cc=STDCALL, params={ @@ -120,7 +120,7 @@ def hook_WriteFile(ql: Qiling, address: int, params): buffer = ql.mem.read(lpBuffer, nNumberOfBytesToWrite) try: r, nNumberOfBytesToWrite = utils.io_Write(ql.amsint32_driver, buffer) - ql.mem.write(lpNumberOfBytesWritten, ql.pack32(nNumberOfBytesToWrite)) + ql.mem.write_ptr(lpNumberOfBytesWritten, nNumberOfBytesToWrite, 4) except Exception as e: ql.log.exception("") print("Exception = %s" % str(e)) diff --git a/qiling/arch/arch.py b/qiling/arch/arch.py index e147e182a..e1b84d186 100644 --- a/qiling/arch/arch.py +++ b/qiling/arch/arch.py @@ -53,7 +53,7 @@ def stack_push(self, value: int) -> int: """ self.regs.arch_sp -= self.pointersize - self.ql.mem.write(self.regs.arch_sp, self.ql.pack(value)) + self.ql.mem.write_ptr(self.regs.arch_sp, value) return self.regs.arch_sp @@ -64,7 +64,7 @@ def stack_pop(self) -> int: Returns: the value at the top of stack """ - data = self.ql.unpack(self.ql.mem.read(self.regs.arch_sp, self.pointersize)) + data = self.ql.mem.read_ptr(self.regs.arch_sp) self.regs.arch_sp += self.pointersize return data @@ -84,7 +84,7 @@ def stack_read(self, offset: int) -> int: Returns: the value at the specified address """ - return self.ql.unpack(self.ql.mem.read(self.regs.arch_sp + offset, self.pointersize)) + return self.ql.mem.read_ptr(self.regs.arch_sp + offset) def stack_write(self, offset: int, value: int) -> None: @@ -99,7 +99,7 @@ def stack_write(self, offset: int, value: int) -> None: a 0 value means overwriting the value at the top of the stack """ - self.ql.mem.write(self.regs.arch_sp + offset, self.ql.pack(value)) + self.ql.mem.write_ptr(self.regs.arch_sp + offset, value) # Unicorn's CPU state save diff --git a/qiling/loader/elf.py b/qiling/loader/elf.py index 12a339471..efb5e78ce 100644 --- a/qiling/loader/elf.py +++ b/qiling/loader/elf.py @@ -513,7 +513,7 @@ def __get_symbol(name: str) -> Optional[Symbol]: # FIXME: this is for rootkit to scan for syscall table from page_offset_base # write address of syscall table to this slot, so syscall scanner can quickly find it if symbol_name == "page_offset_base": - ql.mem.write(self.ql.os.hook_addr, self.ql.pack(SYSCALL_MEM)) + ql.mem.write_ptr(self.ql.os.hook_addr, SYSCALL_MEM) # we also need to do reverse lookup from symbol to address rev_reloc_symbols[symbol_name] = self.ql.os.hook_addr @@ -539,48 +539,49 @@ def __get_symbol(name: str) -> Optional[Symbol]: if rel['r_addend']: val = sym_offset + rel['r_addend'] val += mem_start - ql.mem.write(loc, ql.pack32(val & 0xFFFFFFFF)) else: - ql.mem.write(loc, ql.pack32(rev_reloc_symbols[symbol_name] & 0xFFFFFFFF)) + val = rev_reloc_symbols[symbol_name] + + ql.mem.write_ptr(loc, (val & 0xFFFFFFFF), 4) elif desc == 'R_X86_64_64': val = sym_offset + rel['r_addend'] val += 0x2000000 # init_module position: FIXME - ql.mem.write(loc, ql.pack64(val)) + ql.mem.write_ptr(loc, val, 8) elif desc == 'R_X86_64_PC64': val = rel['r_addend'] - loc val += rev_reloc_symbols[symbol_name] - ql.mem.write(loc, ql.pack64(val)) + ql.mem.write_ptr(loc, val, 8) elif desc in ('R_X86_64_PC32', 'R_X86_64_PLT32'): val = rel['r_addend'] - loc val += rev_reloc_symbols[symbol_name] - ql.mem.write(loc, ql.pack32(val & 0xFFFFFFFF)) + ql.mem.write_ptr(loc, (val & 0xFFFFFFFF), 4) elif desc in ('R_386_PC32', 'R_386_PLT32'): val = ql.mem.read_ptr(loc, 4) val = rev_reloc_symbols[symbol_name] + val - loc - ql.mem.write(loc, ql.pack32(val & 0xFFFFFFFF)) + ql.mem.write_ptr(loc, (val & 0xFFFFFFFF), 4) elif desc in ('R_386_32', 'R_MIPS_32'): val = ql.mem.read_ptr(loc, 4) val = rev_reloc_symbols[symbol_name] + val - ql.mem.write(loc, ql.pack32(val & 0xFFFFFFFF)) + ql.mem.write_ptr(loc, (val & 0xFFFFFFFF), 4) elif desc == 'R_MIPS_HI16': # actual relocation is done in R_MIPS_LO16 prev_mips_hi16_loc = loc elif desc == 'R_MIPS_LO16': - val = ql.unpack16(ql.mem.read(prev_mips_hi16_loc + 2, 2)) << 16 | ql.unpack16(ql.mem.read(loc + 2, 2)) + val = ql.mem.read_ptr(prev_mips_hi16_loc + 2, 2) << 16 | ql.mem.read_ptr(loc + 2, 2) val = rev_reloc_symbols[symbol_name] + val # *(word)(mips_lo16_loc + 2) is treated as signed if (val & 0xFFFF) >= 0x8000: val += (1 << 16) - ql.mem.write(prev_mips_hi16_loc + 2, ql.pack16(val >> 16)) - ql.mem.write(loc + 2, ql.pack16(val & 0xFFFF)) + ql.mem.write_ptr(prev_mips_hi16_loc + 2, (val >> 16), 2) + ql.mem.write_ptr(loc + 2, (val & 0xFFFF), 2) else: raise NotImplementedError(f'Relocation type {desc} not implemented') @@ -651,12 +652,12 @@ def load_driver(self, elffile: ELFFile, stack_addr: int, loadbase: int = 0) -> N dest = SYSCALL_MEM + syscall_id * self.ql.arch.pointersize self.ql.log.debug(f'Writing syscall {tmp_sc} to {dest:#x}') - self.ql.mem.write(dest, self.ql.pack(addr)) + self.ql.mem.write_ptr(dest, addr) # write syscall addresses into syscall table - self.ql.mem.write(SYSCALL_MEM + 0 * self.ql.arch.pointersize, self.ql.pack(self.ql.os.hook_addr + 0 * self.ql.arch.pointersize)) - self.ql.mem.write(SYSCALL_MEM + 1 * self.ql.arch.pointersize, self.ql.pack(self.ql.os.hook_addr + 1 * self.ql.arch.pointersize)) - self.ql.mem.write(SYSCALL_MEM + 2 * self.ql.arch.pointersize, self.ql.pack(self.ql.os.hook_addr + 2 * self.ql.arch.pointersize)) + self.ql.mem.write_ptr(SYSCALL_MEM + 0 * self.ql.arch.pointersize, self.ql.os.hook_addr + 0 * self.ql.arch.pointersize) + self.ql.mem.write_ptr(SYSCALL_MEM + 1 * self.ql.arch.pointersize, self.ql.os.hook_addr + 1 * self.ql.arch.pointersize) + self.ql.mem.write_ptr(SYSCALL_MEM + 2 * self.ql.arch.pointersize, self.ql.os.hook_addr + 2 * self.ql.arch.pointersize) # setup hooks for read/write/open syscalls self.import_symbols[self.ql.os.hook_addr + 0 * self.ql.arch.pointersize] = hook_sys_read diff --git a/qiling/loader/pe.py b/qiling/loader/pe.py index dcb8d50c5..87251bb9e 100644 --- a/qiling/loader/pe.py +++ b/qiling/loader/pe.py @@ -245,9 +245,9 @@ def init_tib(self): self.structure_last_addr += teb_size if self.ql.arch.type == QL_ARCH.X8664: # TEB - self.ql.mem.write(gs + 0x30, self.ql.pack64(teb_addr)) + self.ql.mem.write_ptr(gs + 0x30, teb_addr) # PEB - self.ql.mem.write(gs + 0x60, self.ql.pack64(teb_addr + teb_size)) + self.ql.mem.write_ptr(gs + 0x60, teb_addr + teb_size) self.TEB = self.ql.TEB = teb_data diff --git a/qiling/os/linux/function_hook.py b/qiling/os/linux/function_hook.py index 6ef9c8a21..7b07c2e58 100644 --- a/qiling/os/linux/function_hook.py +++ b/qiling/os/linux/function_hook.py @@ -123,15 +123,15 @@ def set_ret(self, addr): # ARM64 elif self.ql.arch.type == QL_ARCH.ARM64: - self.ql.mem.write(self.ql.arch.regs.sp, self.ql.pack(addr)) + self.ql.arch.stack_write(0, addr) # X86 elif self.ql.arch.type == QL_ARCH.X86: - self.ql.mem.write(self.ql.arch.regs.esp, self.ql.pack(addr)) + self.ql.arch.stack_write(0, addr) # X8664 elif self.ql.arch.type == QL_ARCH.X8664: - self.ql.mem.write(self.ql.arch.regs.rsp, self.ql.pack(addr)) + self.ql.arch.stack_write(0, addr) else: raise @@ -244,7 +244,7 @@ def enable(self): self.ori_data = self.ql.mem.read(self.ori_offest + self.load_base, self.ql.arch.pointersize) self.ql.mem.write(self.rel.ptr, self.rel.pack()) - self.ql.mem.write(self.ori_offest + self.load_base, self.ql.pack(self.hook_fuc_ptr)) + self.ql.mem.write_ptr(self.ori_offest + self.load_base, self.hook_fuc_ptr) self.ql.mem.write(self.hook_data_ptr, bytes(self.ori_data)) class HookFuncMips(HookFunc): @@ -263,13 +263,13 @@ def _hook_fuc_exit(self, ql): tmp = self.ql.unpack(self.ql.mem.read(self.got + self.load_base + self.gotidx * self.ql.arch.pointersize, self.ql.arch.pointersize)) if tmp != self.hook_fuc_ptr: - self.ql.mem.write(self.got + self.load_base + self.gotidx * self.ql.arch.pointersize, self.ql.pack(self.hook_fuc_ptr)) - self.ql.mem.write(self.hook_data_ptr, self.ql.pack(tmp)) + self.ql.mem.write_ptr(self.got + self.load_base + self.gotidx * self.ql.arch.pointersize, self.hook_fuc_ptr) + self.ql.mem.write_ptr(self.hook_data_ptr, tmp) def _hook_got(self): self.ori_data = self.ql.mem.read(self.got + self.load_base + self.gotidx * self.ql.arch.pointersize, self.ql.arch.pointersize) - self.ql.mem.write(self.got + self.load_base + self.gotidx * self.ql.arch.pointersize, self.ql.pack(self.hook_fuc_ptr)) + self.ql.mem.write_ptr(self.got + self.load_base + self.gotidx * self.ql.arch.pointersize, self.hook_fuc_ptr) self.ql.mem.write(self.hook_data_ptr, bytes(self.ori_data)) def enable(self): diff --git a/qiling/os/linux/syscall.py b/qiling/os/linux/syscall.py index ac96d3693..ae5a465a0 100644 --- a/qiling/os/linux/syscall.py +++ b/qiling/os/linux/syscall.py @@ -46,7 +46,7 @@ def ql_syscall_set_thread_area(ql: Qiling, u_info_addr: int): access = QL_X86_A_PRESENT | QL_X86_A_DATA | QL_X86_A_DATA_WRITABLE | QL_X86_A_PRIV_3 | QL_X86_A_DIR_CON_BIT ql.os.gdtm.register_gdt_segment(index, base, limit, access) - ql.mem.write(u_info_addr, ql.pack32(index)) + ql.mem.write_ptr(u_info_addr, index, 4) else: ql.log.warning(f"Wrong index {index} from address {hex(u_info_addr)}") return -1 @@ -65,7 +65,7 @@ def ql_syscall_set_thread_area(ql: Qiling, u_info_addr: int): def ql_syscall_set_tls(ql, address, *args, **kw): if ql.arch.type == QL_ARCH.ARM: ql.arch.regs.c13_c0_3 = address - ql.mem.write(ql.arch.arm_get_tls_addr + 12, ql.pack32(address)) + ql.mem.write_ptr(ql.arch.arm_get_tls_addr + 12, address, 4) ql.arch.regs.r0 = address ql.log.debug("settls(0x%x)" % address) diff --git a/qiling/os/linux/thread.py b/qiling/os/linux/thread.py index 34e4b3d67..32d15a028 100644 --- a/qiling/os/linux/thread.py +++ b/qiling/os/linux/thread.py @@ -85,7 +85,7 @@ def __init__(self, ql: Qiling, start_address: int, exit_point: int, context = No self._robust_list_head_len = None if self._set_child_tid_address != None: - self.ql.mem.write(self._set_child_tid_address, ql.pack32(self.id)) + self.ql.mem.write_ptr(self._set_child_tid_address, self.id, 4) @property def ql(self): @@ -333,7 +333,7 @@ def _on_stop(self): if self.clear_child_tid_address is not None: self.ql.log.debug(f"Perform CLONE_CHILD_CLEARTID at {hex(self.clear_child_tid_address)}") - self.ql.mem.write(self.clear_child_tid_address, self.ql.pack32(0)) + self.ql.mem.write_ptr(self.clear_child_tid_address, 0, 4) wakes = self.ql.os.futexm.get_futex_wake_list(self.ql, self.clear_child_tid_address, 1) self.clear_child_tid_address = None # When the thread is to stop, we don't have chance for next sched_cb, so @@ -394,7 +394,7 @@ def set_thread_tls(self, tls_addr): access = QL_X86_A_PRESENT | QL_X86_A_DATA | QL_X86_A_DATA_WRITABLE | QL_X86_A_PRIV_3 | QL_X86_A_DIR_CON_BIT self.ql.os.gdtm.register_gdt_segment(index, base, limit, access) - self.ql.mem.write(tls_addr, self.ql.pack32(index)) + self.ql.mem.write_ptr(tls_addr, index, 4) else: raise diff --git a/qiling/os/posix/syscall/prctl.py b/qiling/os/posix/syscall/prctl.py index 3229382eb..c4811a5c6 100644 --- a/qiling/os/posix/syscall/prctl.py +++ b/qiling/os/posix/syscall/prctl.py @@ -15,8 +15,8 @@ def ql_syscall_arch_prctl(ql: Qiling, code: int, addr: int): handlers = { ARCH_SET_GS : lambda : ql.arch.msr.write(IA32_GS_BASE_MSR, addr), ARCH_SET_FS : lambda : ql.arch.msr.write(IA32_FS_BASE_MSR, addr), - ARCH_GET_FS : lambda : ql.mem.write(addr, ql.pack64(ql.arch.msr.read(IA32_FS_BASE_MSR))), - ARCH_GET_GS : lambda : ql.mem.write(addr, ql.pack64(ql.arch.msr.read(IA32_GS_BASE_MSR))) + ARCH_GET_FS : lambda : ql.mem.write_ptr(addr, ql.arch.msr.read(IA32_FS_BASE_MSR), 8), + ARCH_GET_GS : lambda : ql.mem.write_ptr(addr, ql.arch.msr.read(IA32_GS_BASE_MSR), 8) } if code not in handlers: diff --git a/qiling/os/posix/syscall/sched.py b/qiling/os/posix/syscall/sched.py index 69664dc6b..9648182da 100644 --- a/qiling/os/posix/syscall/sched.py +++ b/qiling/os/posix/syscall/sched.py @@ -86,7 +86,7 @@ def ql_syscall_clone(ql: Qiling, flags: int, child_stack: int, parent_tidptr: in ql.log.debug(f'{str(th)} created') if flags & CLONE_PARENT_SETTID == CLONE_PARENT_SETTID: - ql.mem.write(parent_tidptr, ql.pack32(th.id)) + ql.mem.write_ptr(parent_tidptr, th.id, 4) ctx = ql.save(reg=True, mem=False) # Whether to set a new tls diff --git a/qiling/os/posix/syscall/sendfile.py b/qiling/os/posix/syscall/sendfile.py index df41141f5..6d4b81ca7 100644 --- a/qiling/os/posix/syscall/sendfile.py +++ b/qiling/os/posix/syscall/sendfile.py @@ -32,7 +32,7 @@ def ql_syscall_sendfile(ql: Qiling, out_fd: int, in_fd: int, offset: int, count: if offset: current_offset = ifile.tell() - ql.mem.write(offset, ql.pack(current_offset)) + ql.mem.write_ptr(offset, current_offset) ifile.lseek(ifile_pos) return ofile.write(buf) diff --git a/qiling/os/posix/syscall/socket.py b/qiling/os/posix/syscall/socket.py index 15556b078..be01291d7 100644 --- a/qiling/os/posix/syscall/socket.py +++ b/qiling/os/posix/syscall/socket.py @@ -426,7 +426,7 @@ def inet_addr(ip): tmp_buf += inet_addr(address[0]) tmp_buf += b'\x00' * 8 ql.mem.write(accept_addr, tmp_buf) - ql.mem.write(accept_addrlen, ql.pack32(16)) + ql.mem.write_ptr(accept_addrlen, 16, 4) except: if ql.verbose >= QL_VERBOSE.DEBUG: raise diff --git a/qiling/os/posix/syscall/unistd.py b/qiling/os/posix/syscall/unistd.py index 669dd5327..44b1a01b4 100644 --- a/qiling/os/posix/syscall/unistd.py +++ b/qiling/os/posix/syscall/unistd.py @@ -168,7 +168,7 @@ def ql_syscall__llseek(ql: Qiling, fd: int, offset_high: int, offset_low: int, r except OSError: regreturn = -1 else: - ql.mem.write(result, ql.pack64(ret)) + ql.mem.write_ptr(result, ret, 8) regreturn = 0 # ql.log.debug("_llseek(%d, 0x%x, 0x%x, 0x%x) = %d" % (fd, offset_high, offset_low, origin, regreturn)) @@ -543,8 +543,8 @@ def ql_syscall_pipe(ql: Qiling, pipefd: int): ql.arch.regs.v1 = idx2 regreturn = idx1 else: - ql.mem.write(pipefd + 0, ql.pack32(idx1)) - ql.mem.write(pipefd + 4, ql.pack32(idx2)) + ql.mem.write_ptr(pipefd + 0, idx1, 4) + ql.mem.write_ptr(pipefd + 4, idx2, 4) regreturn = 0 return regreturn diff --git a/qiling/os/posix/syscall/wait.py b/qiling/os/posix/syscall/wait.py index 8902f23cf..8315b2946 100644 --- a/qiling/os/posix/syscall/wait.py +++ b/qiling/os/posix/syscall/wait.py @@ -16,7 +16,7 @@ def ql_syscall_wait4(ql: Qiling, pid: int, wstatus: int, options: int, rusage: i spid, status, _ = os.wait4(pid, options) if wstatus: - ql.mem.write(wstatus, ql.pack32(status)) + ql.mem.write_ptr(wstatus, status, 4) retval = spid except ChildProcessError: diff --git a/qiling/os/qnx/message.py b/qiling/os/qnx/message.py index f04373354..e4621fcf6 100644 --- a/qiling/os/qnx/message.py +++ b/qiling/os/qnx/message.py @@ -146,7 +146,7 @@ def ql_qnx_msg_io_lseek(ql:Qiling, coid, smsg, sparts, rmsg, rparts, *args, **kw ql.log.debug(f'msg_io_lseek(coid = {coid} => fd = {fd}, offset = {offset}, whence = {lseek_whence[whence]})') # lseek file regreturn = ql_syscall_lseek(ql, fd, offset, whence) - ql.mem.write(rmsg, ql.pack64(regreturn)) + ql.mem.write_ptr(rmsg, regreturn, 8) return 0 # lib/c/1/fstat.c diff --git a/qiling/os/qnx/qnx.py b/qiling/os/qnx/qnx.py index 72077c963..82a0483a7 100644 --- a/qiling/os/qnx/qnx.py +++ b/qiling/os/qnx/qnx.py @@ -115,7 +115,7 @@ def run(self): self.ql.mem.write(self.syspage_addr, sp.read()) # Address of struct _thread_local_storage for our thread - self.ql.mem.write(self.cpupage_addr, self.ql.pack32(self.cpupage_tls_addr)) + self.ql.mem.write_ptr(self.cpupage_addr, self.cpupage_tls_addr, 4) tls = _thread_local_storage(self.ql, self.cpupage_tls_addr) # Fill TLS structure with proper values @@ -127,7 +127,7 @@ def run(self): tls.updateToMem() # Address of the system page - self.ql.mem.write(self.cpupage_addr + 8, self.ql.pack32(self.syspage_addr)) + self.ql.mem.write_ptr(self.cpupage_addr + 8, self.syspage_addr, 4) try: if self.ql.code: diff --git a/qiling/os/qnx/syscall.py b/qiling/os/qnx/syscall.py index cdf4d9c69..5fe204248 100644 --- a/qiling/os/qnx/syscall.py +++ b/qiling/os/qnx/syscall.py @@ -49,7 +49,7 @@ def ql_syscall_clock_time(ql:Qiling, id, new, old, *args, **kw): if old != 0: clock_old = ql.unpack64(ql.mem.read(old, 8)) ql.log.debug(f'syscall_clock_time(id = {clock_types[id]}, old = {clock_old})') - ql.mem.write(old, ql.pack64(time_ns())) + ql.mem.write_ptr(old, time_ns(), 8) return 0 @@ -118,7 +118,7 @@ def ql_syscall_sys_cpupage_set(ql:Qiling, index, value, *args, **kw): # CPUPAGE_PLS if index == 1: - ql.mem.write(ql.os.cpupage_addr + 4, ql.pack32(value)) + ql.mem.write_ptr(ql.os.cpupage_addr + 4, value, 4) return EOK ql.log.warning(f'ql_syscall_sys_cpupage_get (index {index:d}) not implemented') diff --git a/qiling/os/uefi/utils.py b/qiling/os/uefi/utils.py index c280b022a..0eddab491 100644 --- a/qiling/os/uefi/utils.py +++ b/qiling/os/uefi/utils.py @@ -73,7 +73,7 @@ def ptr_write8(ql: Qiling, addr: int, val: int) -> None: """Write BYTE data to a pointer """ - ql.mem.write(addr, ql.pack8(val)) + ql.mem.write_ptr(addr, val, 1) def ptr_read16(ql: Qiling, addr: int) -> int: """Read WORD data from a pointer @@ -85,7 +85,7 @@ def ptr_write16(ql: Qiling, addr: int, val: int) -> None: """Write WORD data to a pointer """ - ql.mem.write(addr, ql.pack16(val)) + ql.mem.write_ptr(addr, val, 2) def ptr_read32(ql: Qiling, addr: int) -> int: """Read DWORD data from a pointer @@ -97,7 +97,7 @@ def ptr_write32(ql: Qiling, addr: int, val: int) -> None: """Write DWORD data to a pointer """ - ql.mem.write(addr, ql.pack32(val)) + ql.mem.write_ptr(addr, val, 4) def ptr_read64(ql: Qiling, addr: int) -> int: """Read QWORD data from a pointer @@ -109,7 +109,7 @@ def ptr_write64(ql: Qiling, addr: int, val: int) -> None: """Write QWORD data to a pointer """ - ql.mem.write(addr, ql.pack64(val)) + ql.mem.write_ptr(addr, val, 8) # backward comptability read_int8 = ptr_read8 diff --git a/qiling/os/windows/dlls/advapi32.py b/qiling/os/windows/dlls/advapi32.py index b42d9ba54..baf247be2 100644 --- a/qiling/os/windows/dlls/advapi32.py +++ b/qiling/os/windows/dlls/advapi32.py @@ -36,7 +36,7 @@ def __RegOpenKey(ql: Qiling, address: int, params): new_handle = Handle(obj=key) ql.os.handle_manager.append(new_handle) if phkResult != 0: - ql.mem.write(phkResult, ql.pack(new_handle.id)) + ql.mem.write_ptr(phkResult, new_handle.id) return ERROR_SUCCESS def __RegQueryValue(ql: Qiling, address: int, params): @@ -78,7 +78,7 @@ def __RegQueryValue(ql: Qiling, address: int, params): length = ql.os.registry_manager.write_reg_value_into_mem(value, reg_type, lpData) # set lpcbData max_size = int.from_bytes(ql.mem.read(lpcbData, 4), byteorder="little") - ql.mem.write(lpcbData, ql.pack(length)) + ql.mem.write_ptr(lpcbData, length) if max_size < length: ret = ERROR_MORE_DATA @@ -106,7 +106,7 @@ def __RegCreateKey(ql: Qiling, address: int, params): new_handle = Handle(obj=s_hKey + "\\" + lpSubKey) ql.os.handle_manager.append(new_handle) if phkResult != 0: - ql.mem.write(phkResult, ql.pack(new_handle.id)) + ql.mem.write_ptr(phkResult, new_handle.id) else: # elicn: is this even reachable? new_handle = 0 @@ -672,7 +672,7 @@ def hook_AllocateAndInitializeSid(ql: Qiling, address: int, params): handle = Handle(obj=sid, id=sid_addr) ql.os.handle_manager.append(handle) dest = params["pSid"] - ql.mem.write(dest, ql.pack(sid_addr)) + ql.mem.write_ptr(dest, sid_addr) return 1 @@ -744,7 +744,7 @@ def hook_CheckTokenMembership(ql: Qiling, address: int, params): assert False, 'unimplemented' else: assert False, 'unimplemented' - ql.mem.write(params['IsMember'], ql.pack(IsMember)) + ql.mem.write_ptr(params['IsMember'], IsMember) return 1 diff --git a/qiling/os/windows/dlls/kernel32/errhandlingapi.py b/qiling/os/windows/dlls/kernel32/errhandlingapi.py index cc1d4f2da..5dbc32d7a 100644 --- a/qiling/os/windows/dlls/kernel32/errhandlingapi.py +++ b/qiling/os/windows/dlls/kernel32/errhandlingapi.py @@ -97,9 +97,9 @@ def exec_into_0x2d(ql: Qiling, intno: int, start): # https://github.com/LordNoteworthy/al-khaser/wiki/Anti-Debugging-Tricks#interrupt-0x2d pointer = ql.os.heap.alloc(0x4) # the value has just to be different from 0x80000003 - ql.mem.write(pointer, ql.pack32(0)) + ql.mem.write_ptr(pointer, 0, 4) double_pointer = ql.os.heap.alloc(0x4) - ql.mem.write(double_pointer, ql.pack32(pointer)) + ql.mem.write_ptr(double_pointer, pointer, 4) # arg ql.stack_push(double_pointer) @@ -112,9 +112,9 @@ def exec_standard_into(ql: Qiling, intno: int, user_data): # FIXME: probably this works only with al-khaser. pointer = ql.os.heap.alloc(0x4) # the value has just to be different from 0x80000003 - ql.mem.write(pointer, ql.pack32(0)) + ql.mem.write_ptr(pointer, 0, 4) double_pointer = ql.os.heap.alloc(0x4) - ql.mem.write(double_pointer, ql.pack32(pointer)) + ql.mem.write_ptr(double_pointer, pointer, 4) ql.arch.regs.eax = double_pointer ql.arch.regs.esi = user_data diff --git a/qiling/os/windows/dlls/kernel32/fileapi.py b/qiling/os/windows/dlls/kernel32/fileapi.py index 525f1d251..d48110277 100644 --- a/qiling/os/windows/dlls/kernel32/fileapi.py +++ b/qiling/os/windows/dlls/kernel32/fileapi.py @@ -171,12 +171,12 @@ def hook_ReadFile(ql: Qiling, address: int, params): read_len = nNumberOfBytesToRead ql.mem.write(lpBuffer, s) - ql.mem.write(lpNumberOfBytesRead, ql.pack32(read_len)) + ql.mem.write_ptr(lpNumberOfBytesRead, read_len, 4) else: f = ql.os.handle_manager.get(hFile).obj data = f.read(nNumberOfBytesToRead) ql.mem.write(lpBuffer, data) - ql.mem.write(lpNumberOfBytesRead, ql.pack32(len(data))) + ql.mem.write_ptr(lpNumberOfBytesRead, len(data), 4) return 1 @@ -204,7 +204,7 @@ def hook_WriteFile(ql: Qiling, address: int, params): s = ql.mem.read(lpBuffer, nNumberOfBytesToWrite) ql.os.stdout.write(s) ql.os.stats.log_string(s.decode()) - ql.mem.write(lpNumberOfBytesWritten, ql.pack32(nNumberOfBytesToWrite)) + ql.mem.write_ptr(lpNumberOfBytesWritten, nNumberOfBytesToWrite, 4) else: f = ql.os.handle_manager.get(hFile) @@ -217,7 +217,7 @@ def hook_WriteFile(ql: Qiling, address: int, params): buffer = ql.mem.read(lpBuffer, nNumberOfBytesToWrite) nNumberOfBytesWritten = f.write(bytes(buffer)) - ql.mem.write(lpNumberOfBytesWritten, ql.pack32(nNumberOfBytesWritten)) + ql.mem.write_ptr(lpNumberOfBytesWritten, nNumberOfBytesWritten, 4) return 1 @@ -402,7 +402,7 @@ def hook_GetVolumeInformationW(ql: Qiling, address: int, params): lpMaximumComponentLength = params["lpMaximumComponentLength"] if lpMaximumComponentLength != 0: - ql.mem.write(lpMaximumComponentLength, ql.pack16(255)) + ql.mem.write_ptr(lpMaximumComponentLength, 255, 2) pt_serial_number = params["lpVolumeSerialNumber"] if pt_serial_number != 0: @@ -415,7 +415,7 @@ def hook_GetVolumeInformationW(ql: Qiling, address: int, params): if pt_flag != 0: # TODO implement - ql.mem.write(pt_flag, ql.pack32(0x00020000)) + ql.mem.write_ptr(pt_flag, 0x00020000, 4) if pt_system_type != 0: system_type = (ql.os.profile["VOLUME"]["type"] + "\x00").encode("utf-16le") diff --git a/qiling/os/windows/dlls/kernel32/handleapi.py b/qiling/os/windows/dlls/kernel32/handleapi.py index 4caf87ab5..11e7291b7 100644 --- a/qiling/os/windows/dlls/kernel32/handleapi.py +++ b/qiling/os/windows/dlls/kernel32/handleapi.py @@ -31,7 +31,7 @@ def hook_DuplicateHandle(ql: Qiling, address: int, params): content = params["hSourceHandle"] dst = params["lpTargetHandle"] - ql.mem.write(dst, ql.pack(content)) + ql.mem.write_ptr(dst, content) return 1 diff --git a/qiling/os/windows/dlls/kernel32/libloaderapi.py b/qiling/os/windows/dlls/kernel32/libloaderapi.py index bfc415d42..2d229ac85 100644 --- a/qiling/os/windows/dlls/kernel32/libloaderapi.py +++ b/qiling/os/windows/dlls/kernel32/libloaderapi.py @@ -63,7 +63,7 @@ def hook_GetModuleHandleExW(ql: Qiling, address: int, params): res = _GetModuleHandle(ql, address, params) dst = params["phModule"] - ql.mem.write(dst, ql.pack(res)) + ql.mem.write_ptr(dst, res) return res diff --git a/qiling/os/windows/dlls/kernel32/memoryapi.py b/qiling/os/windows/dlls/kernel32/memoryapi.py index 7b1b8bd03..b7a416dad 100644 --- a/qiling/os/windows/dlls/kernel32/memoryapi.py +++ b/qiling/os/windows/dlls/kernel32/memoryapi.py @@ -107,6 +107,6 @@ def hook_VirtualQuery(ql: Qiling, address: int, params): ) for i, v in enumerate(values): - ql.mem.write(mbi + i * ql.arch.pointersize, ql.pack(v)) + ql.mem.write_ptr(mbi + i * ql.arch.pointersize, v) return ql.arch.pointersize * len(values) diff --git a/qiling/os/windows/dlls/kernel32/processthreadsapi.py b/qiling/os/windows/dlls/kernel32/processthreadsapi.py index 6825e0e8b..b01aa1822 100644 --- a/qiling/os/windows/dlls/kernel32/processthreadsapi.py +++ b/qiling/os/windows/dlls/kernel32/processthreadsapi.py @@ -189,7 +189,7 @@ def hook_CreateThread(ql: Qiling, address: int, params): # set lpThreadId # FIXME: Temporary fix for the crash # if lpThreadId != 0: - # ql.mem.write(lpThreadId, ql.pack(thread_id)) + # ql.mem.write_ptr(lpThreadId, thread_id) # set thread handle return new_handle.id @@ -279,7 +279,7 @@ def hook_OpenProcessToken(ql: Qiling, address: int, params): new_handle = Handle(obj=token) ql.os.handle_manager.append(new_handle) - ql.mem.write(token_pointer, ql.pack(new_handle.id)) + ql.mem.write_ptr(token_pointer, new_handle.id) return 1 @@ -313,7 +313,7 @@ def hook_OpenThreadToken(ql: Qiling, address: int, params): new_handle = Handle(obj=token) ql.os.handle_manager.append(new_handle) - ql.mem.write(token_pointer, ql.pack(new_handle.id)) + ql.mem.write_ptr(token_pointer, new_handle.id) return 1 diff --git a/qiling/os/windows/dlls/kernel32/profileapi.py b/qiling/os/windows/dlls/kernel32/profileapi.py index 6aadc12a7..537578510 100644 --- a/qiling/os/windows/dlls/kernel32/profileapi.py +++ b/qiling/os/windows/dlls/kernel32/profileapi.py @@ -25,6 +25,6 @@ def hook_QueryPerformanceCounter(ql: Qiling, address: int, params): def hook_QueryPerformanceFrequency(ql: Qiling, address: int, params): lpFrequency = params['lpFrequency'] - ql.mem.write(lpFrequency, ql.pack64(10000000)) + ql.mem.write_ptr(lpFrequency, 10000000, 8) return 1 diff --git a/qiling/os/windows/dlls/kernel32/winbase.py b/qiling/os/windows/dlls/kernel32/winbase.py index 66fa1d622..00f1d9260 100644 --- a/qiling/os/windows/dlls/kernel32/winbase.py +++ b/qiling/os/windows/dlls/kernel32/winbase.py @@ -635,8 +635,8 @@ def __GetUserName(ql: Qiling, address: int, params, wstring: bool): enc = "utf-16le" if wstring else "utf-8" username = f'{ql.os.profile["USER"]["username"]}\x00'.encode(enc) - max_size = ql.unpack32(ql.mem.read(pcbBuffer, 4)) - ql.mem.write(pcbBuffer, ql.pack32(len(username))) + max_size = ql.mem.read_ptr(pcbBuffer, 4) + ql.mem.write_ptr(pcbBuffer, len(username), 4) if len(username) > max_size: ql.os.last_error = ERROR_INSUFFICIENT_BUFFER @@ -674,8 +674,8 @@ def __GetComputerName(ql: Qiling, address: int, params, wstring: bool): enc = "utf-16le" if wstring else "utf-8" computer = f'{ql.os.profile["SYSTEM"]["computername"]}\x00'.encode(enc) - max_size = ql.unpack32(ql.mem.read(nSize, 4)) - ql.mem.write(nSize, ql.pack32(len(computer))) + max_size = ql.mem.read_ptr(nSize, 4) + ql.mem.write_ptr(nSize, len(computer), 4) if len(computer) > max_size: ql.os.last_error = ERROR_BUFFER_OVERFLOW diff --git a/qiling/os/windows/dlls/kernel32/winnt.py b/qiling/os/windows/dlls/kernel32/winnt.py index 2c8ac1e5f..ca59d5a84 100644 --- a/qiling/os/windows/dlls/kernel32/winnt.py +++ b/qiling/os/windows/dlls/kernel32/winnt.py @@ -21,7 +21,7 @@ def hook_InterlockedExchange(ql: Qiling, address: int, params): Value = params['Value'] old = ql.mem.read_ptr(Target, 4) - ql.mem.write(Target, ql.pack32(Value)) + ql.mem.write_ptr(Target, Value, 4) return old @@ -35,8 +35,8 @@ def hook_InterlockedIncrement(ql: Qiling, address: int, params): Target = params['Target'] Value = ql.mem.read_ptr(Target, 4) - Value = (Value + 1) % (1 << 32) # increment and handle overflow - ql.mem.write(Target, ql.pack32(Value)) + Value = (Value + 1) % (1 << 32) # increase and handle overflow + ql.mem.write_ptr(Target, Value, 4) return Value @@ -50,9 +50,8 @@ def hook_InterlockedDecrement(ql: Qiling, address: int, params): Target = params['Target'] Value = ql.mem.read_ptr(Target, 4) - Value = (Value - 1) % (1 << 32) # increment and handle underflow - - ql.mem.write(Target, ql.pack32(Value)) + Value = (Value - 1) % (1 << 32) # decrease and handle underflow + ql.mem.write_ptr(Target, Value, 4) return Value diff --git a/qiling/os/windows/dlls/msvcrt.py b/qiling/os/windows/dlls/msvcrt.py index 01b692bd6..7fa3ed49b 100644 --- a/qiling/os/windows/dlls/msvcrt.py +++ b/qiling/os/windows/dlls/msvcrt.py @@ -95,9 +95,8 @@ def hook___p__environ(ql: Qiling, address: int, params): ql.mem.write(p_entry, entry) pp_entry = ql.os.heap.alloc(ql.arch.pointersize) - ql.mem.write(pp_entry, ql.pack(p_entry)) - - ql.mem.write(ret + i * ql.arch.pointersize, ql.pack(pp_entry)) + ql.mem.write_ptr(pp_entry, p_entry) + ql.mem.write_ptr(ret + i * ql.arch.pointersize, pp_entry) return ret @@ -162,10 +161,10 @@ def hook___p___argv(ql: Qiling, address: int, params): p_entry = ql.os.heap.alloc(len(entry)) ql.mem.write(p_entry, entry) - ql.mem.write(p_argv + i * ql.arch.pointersize, ql.pack(p_entry)) + ql.mem.write_ptr(p_argv + i * ql.arch.pointersize, p_entry) ret = ql.os.heap.alloc(ql.arch.pointersize) - ql.mem.write(ret, ql.pack(p_argv)) + ql.mem.write_ptr(ret, p_argv) return ret @@ -174,7 +173,7 @@ def hook___p___argv(ql: Qiling, address: int, params): def hook___p___argc(ql: Qiling, address: int, params): ret = ql.os.heap.alloc(ql.arch.pointersize) - ql.mem.write(ret, ql.pack(len(ql.argv))) + ql.mem.write_ptr(ret, len(ql.argv)) return ret @@ -441,7 +440,7 @@ def hook__onexit(ql: Qiling, address: int, params): function = params['function'] addr = ql.os.heap.alloc(ql.arch.pointersize) - ql.mem.write(addr, ql.pack(function)) + ql.mem.write_ptr(addr, function) return addr @@ -532,7 +531,7 @@ def hook__wfopen_s(ql: Qiling, address: int, params): f = ql.os.fs_mapper.open(filename, mode) new_handle = Handle(obj=f) ql.os.handle_manager.append(new_handle) - ql.mem.write(pFile, ql.pack(new_handle.id)) + ql.mem.write_ptr(pFile, new_handle.id) return 1 diff --git a/qiling/os/windows/dlls/ntoskrnl.py b/qiling/os/windows/dlls/ntoskrnl.py index 38ff1c87a..59ef94606 100644 --- a/qiling/os/windows/dlls/ntoskrnl.py +++ b/qiling/os/windows/dlls/ntoskrnl.py @@ -68,7 +68,7 @@ def hook_ZwSetInformationThread(ql: Qiling, address: int, params): ql.log.debug("The target is checking debugger via SetInformationThread") if dst != 0: - ql.mem.write(dst, ql.pack8(0)) + ql.mem.write_ptr(dst, 0, 1) else: raise QlErrorNotImplemented(f'API not implemented {information}') @@ -187,7 +187,7 @@ def __IoCreateDevice(ql: Qiling, address: int, params): device_object.Characteristics = DeviceCharacteristics ql.mem.write(addr, bytes(device_object)[:]) - ql.mem.write(DeviceObject, ql.pack(addr)) + ql.mem.write_ptr(DeviceObject, addr) # update DriverObject.DeviceObject ql.loader.driver_object.DeviceObject = addr @@ -765,7 +765,7 @@ def _NtQuerySystemInformation(ql: Qiling, address: int, params): size = 4 + ctypes.sizeof(RTL_PROCESS_MODULE_INFORMATION32) * NumberOfModules if params["ReturnLength"] != 0: - ql.mem.write(params["ReturnLength"], ql.pack(size)) + ql.mem.write_ptr(params["ReturnLength"], size) if params["SystemInformationLength"] < size: return STATUS_INFO_LENGTH_MISMATCH @@ -1079,7 +1079,7 @@ def hook_PsLookupProcessByProcessId(ql: Qiling, address: int, params): obj = EPROCESS32 addr = ql.os.heap.alloc(ctypes.sizeof(obj)) - ql.mem.write(Process, ql.pack(addr)) + ql.mem.write_ptr(Process, addr) ql.log.info(f'PID = {ProcessId:#x}, addrof(EPROCESS) == {addr:#x}') return STATUS_SUCCESS @@ -1160,12 +1160,12 @@ def hook_PsCreateSystemThread(ql: Qiling, address: int, params): # set lpThreadId if lpThreadId != 0: - ql.mem.write(lpThreadId, ql.pack(UniqueProcess)) - ql.mem.write(lpThreadId + ql.arch.pointersize, ql.pack(thread_id)) + ql.mem.write_ptr(lpThreadId, UniqueProcess) + ql.mem.write_ptr(lpThreadId + ql.arch.pointersize, thread_id) # set lpThreadId if ThreadHandle != 0: - ql.mem.write(ThreadHandle, ql.pack(handle_value)) + ql.mem.write_ptr(ThreadHandle, handle_value) # set thread handle return STATUS_SUCCESS @@ -1272,7 +1272,7 @@ def hook_ObOpenObjectByPointer(ql: Qiling, address: int, params): new_handle = Handle(name=f'p={Object:x}') ql.os.handle_manager.append(new_handle) - ql.mem.write(point_to_new_handle, ql.pack(new_handle.id)) + ql.mem.write_ptr(point_to_new_handle, new_handle.id) ql.log.info(f'New handle of {Object:#x} is {new_handle.id:#x}') return STATUS_SUCCESS diff --git a/qiling/os/windows/dlls/oleaut32.py b/qiling/os/windows/dlls/oleaut32.py index 69e1ff48e..f19cb479d 100644 --- a/qiling/os/windows/dlls/oleaut32.py +++ b/qiling/os/windows/dlls/oleaut32.py @@ -69,7 +69,7 @@ def hook_SysReAllocStringLen(ql: Qiling, address: int, params): addr = ql.os.heap.alloc(size + 1) ql.mem.write(addr, content[:size].encode("utf-16le")) - ql.mem.write(params["pbstr"], ql.pack(addr)) + ql.mem.write_ptr(params["pbstr"], addr) return 1 diff --git a/qiling/os/windows/thread.py b/qiling/os/windows/thread.py index 54320c7aa..cbbb6651f 100644 --- a/qiling/os/windows/thread.py +++ b/qiling/os/windows/thread.py @@ -88,10 +88,10 @@ def create(self, func_addr, func_params, status): # set return address, parameters if self.ql.arch.type == QL_ARCH.X86: - self.ql.mem.write(new_stack - 4, self.ql.pack32(self.ql.os.thread_manager.THREAD_RET_ADDR)) - self.ql.mem.write(new_stack, self.ql.pack32(func_params)) + self.ql.mem.write_ptr(new_stack - 4, self.ql.os.thread_manager.THREAD_RET_ADDR) + self.ql.mem.write_ptr(new_stack, func_params) elif self.ql.arch.type == QL_ARCH.X8664: - self.ql.mem.write(new_stack - 8, self.ql.pack64(self.ql.os.thread_manager.THREAD_RET_ADDR)) + self.ql.mem.write_ptr(new_stack - 8, self.ql.os.thread_manager.THREAD_RET_ADDR) self.saved_context["rcx"] = func_params # set eip/rip, ebp/rbp, esp/rsp diff --git a/tests/test_blob.py b/tests/test_blob.py index 06da52b19..1a702f01c 100644 --- a/tests/test_blob.py +++ b/tests/test_blob.py @@ -40,8 +40,8 @@ def partial_run_init(ql): ql.arch.regs.arch_sp -= 0x20 argv_ptr = ql.arch.regs.arch_sp - ql.mem.write(argv_ptr, ql.pack(arg0_ptr)) - ql.mem.write(argv_ptr + ql.arch.pointersize, ql.pack(arg1_ptr)) + ql.mem.write_ptr(argv_ptr, arg0_ptr) + ql.mem.write_ptr(argv_ptr + ql.arch.pointersize, arg1_ptr) ql.arch.regs.r2 = 2 ql.arch.regs.r3 = argv_ptr diff --git a/tests/test_pe_sys.py b/tests/test_pe_sys.py index 099fbee03..ae2ef4392 100644 --- a/tests/test_pe_sys.py +++ b/tests/test_pe_sys.py @@ -87,7 +87,7 @@ def hook_CreateFileA(ql: Qiling, address: int, params): return ret - def _WriteFile(ql, address, params): + def _WriteFile(ql: Qiling, address: int, params): ret = 1 hFile = params["hFile"] lpBuffer = params["lpBuffer"] @@ -99,18 +99,17 @@ def _WriteFile(ql, address, params): s = ql.mem.read(lpBuffer, nNumberOfBytesToWrite) ql.os.stdout.write(s) ql.os.stats.log_string(s.decode()) - ql.mem.write(lpNumberOfBytesWritten, ql.pack(nNumberOfBytesToWrite)) + ql.mem.write_ptr(lpNumberOfBytesWritten, nNumberOfBytesToWrite) else: f = ql.os.handle_manager.get(hFile) if f is None: # Invalid handle ql.os.last_error = 0xffffffff return 0 - else: - f = f.obj + buffer = ql.mem.read(lpBuffer, nNumberOfBytesToWrite) - f.write(bytes(buffer)) - ql.mem.write(lpNumberOfBytesWritten, ql.pack32(nNumberOfBytesToWrite)) + f.obj.write(bytes(buffer)) + ql.mem.write_ptr(lpNumberOfBytesWritten, nNumberOfBytesToWrite, 4) return ret @winsdkapi(cc=STDCALL, params={ @@ -130,7 +129,7 @@ def hook_WriteFile(ql: Qiling, address: int, params): buffer = ql.mem.read(lpBuffer, nNumberOfBytesToWrite) try: r, nNumberOfBytesToWrite = utils.io_Write(ql.amsint32_driver, buffer) - ql.mem.write(lpNumberOfBytesWritten, ql.pack32(nNumberOfBytesToWrite)) + ql.mem.write_ptr(lpNumberOfBytesWritten, nNumberOfBytesToWrite, 4) except Exception: print("Error") r = 1 From 5616ad6288129a238b4ce06ae5b134ebaf3aa77f Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 14 Feb 2022 23:35:20 +0200 Subject: [PATCH 126/406] Opportunistic adjustments to mem.read_ptr --- qiling/os/memory.py | 7 ++++--- qiling/os/qnx/syscall.py | 2 +- qiling/os/uefi/utils.py | 8 ++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/qiling/os/memory.py b/qiling/os/memory.py index b2afaafe7..394b121c2 100644 --- a/qiling/os/memory.py +++ b/qiling/os/memory.py @@ -288,6 +288,7 @@ def read(self, addr: int, size: int) -> bytearray: def read_ptr(self, addr: int, size: int=None) -> int: """Read an integer value from a memory address. + Bytes read will be unpacked using emulated architecture properties. Args: addr: memory address to read @@ -306,10 +307,10 @@ def read_ptr(self, addr: int, size: int=None) -> int: 8 : self.ql.unpack64 }.get(size) - if __unpack: - return __unpack(self.read(addr, size)) + if __unpack is None: + raise QlErrorStructConversion(f"Unsupported pointer size: {size}") - raise QlErrorStructConversion(f"Unsupported pointer size: {size}") + return __unpack(self.read(addr, size)) def write(self, addr: int, data: bytes) -> None: """Write bytes to a memory. diff --git a/qiling/os/qnx/syscall.py b/qiling/os/qnx/syscall.py index 5fe204248..9a8d0200d 100644 --- a/qiling/os/qnx/syscall.py +++ b/qiling/os/qnx/syscall.py @@ -107,7 +107,7 @@ def ql_syscall_sys_cpupage_get(ql:Qiling, index, *args, **kw): return ql.os.cpupage_addr # CPUPAGE_PLS elif index == 1: - return ql.unpack32(ql.mem.read(ql.os.cpupage_addr + 4, 4)) + return ql.mem.read_ptr(ql.os.cpupage_addr + 4, 4) # CPUPAGE_SYSPAGE elif index == 2: return ql.os.syspage_addr diff --git a/qiling/os/uefi/utils.py b/qiling/os/uefi/utils.py index 0eddab491..fe3576749 100644 --- a/qiling/os/uefi/utils.py +++ b/qiling/os/uefi/utils.py @@ -67,7 +67,7 @@ def ptr_read8(ql: Qiling, addr: int) -> int: """Read BYTE data from a pointer """ - return ql.unpack8(ql.mem.read(addr, 1)) + return ql.mem.read_ptr(addr, 1) def ptr_write8(ql: Qiling, addr: int, val: int) -> None: """Write BYTE data to a pointer @@ -79,7 +79,7 @@ def ptr_read16(ql: Qiling, addr: int) -> int: """Read WORD data from a pointer """ - return ql.unpack16(ql.mem.read(addr, 2)) + return ql.mem.read_ptr(addr, 2) def ptr_write16(ql: Qiling, addr: int, val: int) -> None: """Write WORD data to a pointer @@ -91,7 +91,7 @@ def ptr_read32(ql: Qiling, addr: int) -> int: """Read DWORD data from a pointer """ - return ql.unpack32(ql.mem.read(addr, 4)) + return ql.mem.read_ptr(addr, 4) def ptr_write32(ql: Qiling, addr: int, val: int) -> None: """Write DWORD data to a pointer @@ -103,7 +103,7 @@ def ptr_read64(ql: Qiling, addr: int) -> int: """Read QWORD data from a pointer """ - return ql.unpack64(ql.mem.read(addr, 8)) + return ql.mem.read_ptr(addr, 8) def ptr_write64(ql: Qiling, addr: int, val: int) -> None: """Write QWORD data to a pointer From 5b4613d0372e5f5ef7cce93980ebf7411c7c9dd9 Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 15 Feb 2022 02:13:23 +0200 Subject: [PATCH 127/406] Fix QL_FAST_TEST support --- tests/test_pe.py | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/tests/test_pe.py b/tests/test_pe.py index bf97e50f1..a0848fd0a 100644 --- a/tests/test_pe.py +++ b/tests/test_pe.py @@ -62,6 +62,7 @@ def write(self, string): self.output[key] = value return len(string) +IS_FAST_TEST = 'QL_FAST_TEST' in os.environ class PETest(unittest.TestCase): @@ -111,8 +112,6 @@ def _t(): def test_pe_win_x86_uselessdisk(self): def _t(): - if 'QL_FAST_TEST' in os.environ: - return class Fake_Drive(QlFsMappedObject): def read(self, size): @@ -135,13 +134,11 @@ def close(self): del ql return True - self.assertTrue(QLWinSingleTest(_t).run()) + self.assertTrue(IS_FAST_TEST or QLWinSingleTest(_t).run()) def test_pe_win_x86_gandcrab(self): def _t(): - if 'QL_FAST_TEST' in os.environ: - return def stop(ql, default_values): print("Ok for now") ql.emu_stop() @@ -210,7 +207,7 @@ def randomize_config_value(ql, key, subkey): del ql return True - self.assertTrue(QLWinSingleTest(_t).run()) + self.assertTrue(IS_FAST_TEST or QLWinSingleTest(_t).run()) def test_pe_win_x86_multithread(self): def _t(): @@ -325,8 +322,6 @@ def _t(): def test_pe_win_x86_wannacry(self): def _t(): - if 'QL_FAST_TEST' in os.environ: - return def stop(ql): ql.log.info("killerswtichfound") ql.log.setLevel(logging.CRITICAL) @@ -339,7 +334,7 @@ def stop(ql): del ql return True - self.assertTrue(QLWinSingleTest(_t).run()) + self.assertTrue(IS_FAST_TEST or QLWinSingleTest(_t).run()) def test_pe_win_x86_NtQueryInformationSystem(self): @@ -356,8 +351,6 @@ def _t(): def test_pe_win_al_khaser(self): def _t(): - if 'QL_FAST_TEST' in os.environ: - return 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 @@ -384,7 +377,7 @@ def end(ql): del ql return True - self.assertTrue(QLWinSingleTest(_t).run()) + self.assertTrue(IS_FAST_TEST or QLWinSingleTest(_t).run()) def test_pe_win_x8664_customapi(self): From e5db1c0ea2837b1059de345f490f7724c3f28977 Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Tue, 15 Feb 2022 00:41:15 +0000 Subject: [PATCH 128/406] refactored frontend.py and fix missing function --- qiling/debugger/qdb/frontend.py | 378 ++++++++++++++++---------------- qiling/debugger/qdb/utils.py | 2 +- 2 files changed, 194 insertions(+), 186 deletions(-) diff --git a/qiling/debugger/qdb/frontend.py b/qiling/debugger/qdb/frontend.py index 6d6c0eefc..9b12376de 100644 --- a/qiling/debugger/qdb/frontend.py +++ b/qiling/debugger/qdb/frontend.py @@ -22,22 +22,6 @@ COLORS = (color.DARKCYAN, color.BLUE, color.RED, color.YELLOW, color.GREEN, color.PURPLE, color.CYAN, color.WHITE) -def divider_printer(field_name, ruler="─"): - """ - decorator function for printing divider - """ - - def decorator(context_dumper): - def wrapper(*args, **kwargs): - height, width = get_terminal_size() - bar = (width - len(field_name)) // 2 - 1 - print(ruler * bar, field_name, ruler * bar) - context_dumper(*args, **kwargs) - if "DISASM" in field_name: - print(ruler * width) - return wrapper - return decorator - def setup_context_render(ql: Qiling) -> ContextRender: """ @@ -45,53 +29,96 @@ def setup_context_render(ql: Qiling) -> ContextRender: """ return { - QL_ARCH.X86: ContextRender_X86, - QL_ARCH.ARM: ContextRender_ARM, - QL_ARCH.ARM_THUMB: ContextRender_ARM, - QL_ARCH.CORTEX_M: ContextRender_ARM, - QL_ARCH.MIPS: ContextRender_MIPS, + QL_ARCH.X86: ContextRenderX86, + QL_ARCH.ARM: ContextRenderARM, + QL_ARCH.ARM_THUMB: ContextRenderARM, + QL_ARCH.CORTEX_M: ContextRenderCORTEX_M, + QL_ARCH.MIPS: ContextRenderMIPS, }.get(ql.archtype)(ql) -class ContextRender(object): +class Render(object): """ - base class for context render + base class for rendering related functions """ - def __init__(self, ql): - self.ql = ql - self.predictor = setup_branch_predictor(ql) + def divider_printer(field_name, ruler="─"): + """ + decorator function for printing divider and field name + """ - def print_asm(self, insn: CsInsn, to_jump: Optional[bool] = None, address: int = None) -> None: + def decorator(context_dumper): + def wrapper(*args, **kwargs): + height, width = get_terminal_size() + bar = (width - len(field_name)) // 2 - 1 + print(ruler * bar, field_name, ruler * bar) + context_dumper(*args, **kwargs) + if "DISASM" in field_name: + print(ruler * width) + return wrapper + return decorator + + def __init__(self): + self.regs_a_row = 4 + + def reg_diff(self, cur_regs, saved_reg_dump): """ - helper function for printing assembly instructions, indicates where we are and the branch prediction - provided by BranchPredictor + helper function for highlighting register changed during execution """ - opcode = "".join(f"{b:02x}" for b in insn.bytes) - if self.ql.archtype in (QL_ARCH.X86, QL_ARCH.X8664): - trace_line = f"0x{insn.address:08x} │ {opcode:20s} {insn.mnemonic:10} {insn.op_str:35s}" - else: - trace_line = f"0x{insn.address:08x} │ {opcode:10s} {insn.mnemonic:10} {insn.op_str:35s}" + if saved_reg_dump: + reg_dump = copy.deepcopy(saved_reg_dump) + if getattr(self, "regs_need_swaped", None): + reg_dump = self.swap_reg_name(reg_dump) + return [k for k in cur_regs if cur_regs[k] != reg_dump[k]] + + def render_regs_dump(self, regs, diff_reg=None): + """ + helper function for redering registers dump + """ - cursor = " " - if self.ql.reg.arch_pc == insn.address: - cursor = "►" + lines = "" + for idx, r in enumerate(regs, 1): + line = "{}{}: 0x{{:08x}} {}\t".format(COLORS[(idx-1) // self.regs_a_row], r, color.END) - jump_sign = " " - if to_jump: - jump_sign = f"{color.RED}✓{color.END}" + if diff_reg and r in diff_reg: + line = f"{color.UNDERLINE}{color.BOLD}{line}" - print(f"{jump_sign} {cursor} {color.DARKGRAY}{trace_line}{color.END}") + if idx % self.regs_a_row == 0 and idx != 32: + line += "\n" - def dump_regs(self): + lines += line + + print(lines.format(*regs.values())) + + def swap_reg_name(self, cur_regs: Mapping["str", int], extra_dict=None): """ - dump all registers + swap register name with more readable register name """ - return {reg_name: getattr(self.ql.reg, reg_name) for reg_name in self.regs} + target_items = extra_dict.items() if extra_dict else self.regs_need_swaped.items() - def context_reg(self, saved_states): + for old_reg, new_reg in target_items: + cur_regs.update({old_reg: cur_regs.pop(new_reg)}) + + return cur_regs + + def print_asm(self, insn: CsInsn, to_jump: bool = False) -> None: + """ + helper function for printing assembly instructions, indicates where we are and the branch prediction + provided by BranchPredictor + """ + + opcode = "".join(f"{b:02x}" for b in insn.bytes) + trace_line = f"0x{insn.address:08x} │ {opcode:15s} {insn.mnemonic:10} {insn.op_str:35s}" + + cursor = "►" if self.ql.reg.arch_pc == insn.address else " " + + jump_sign = f"{color.RED}✓{color.END}" if to_jump else " " + + print(f"{jump_sign} {cursor} {color.DARKGRAY}{trace_line}{color.END}") + + def context_reg(self, saved_states) -> None: """ display context registers """ @@ -99,7 +126,7 @@ def context_reg(self, saved_states): return NotImplementedError @divider_printer("[ STACK ]") - def context_stack(self): + def context_stack(self) -> None: """ display context stack """ @@ -128,7 +155,7 @@ def context_stack(self): print() @divider_printer("[ DISASM ]") - def context_asm(self): + def context_asm(self) -> None: """ display context assembly """ @@ -170,15 +197,29 @@ def context_asm(self): self.print_asm(forward_insn) forward_insn_size += forward_insn.size +class ArchMIPS(object): + def __init__(self): -class ContextRender_ARM(ContextRender): - """ - context render for ARM - """ + self.archtype = QL_ARCH.MIPS - def __init__(self, ql): - super().__init__(ql) + self.regs = ( + "gp", "at", "v0", "v1", + "a0", "a1", "a2", "a3", + "t0", "t1", "t2", "t3", + "t4", "t5", "t6", "t7", + "t8", "t9", "sp", "s8", + "s0", "s1", "s2", "s3", + "s4", "s5", "s6", "s7", + "ra", "k0", "k1", "pc", + ) + self.regs_need_swaped = { + "fp": "s8", + } + +class ArchARM(): + def __init__(self): + self.archtype = QL_ARCH.ARM self.regs = ( "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", @@ -186,10 +227,22 @@ def __init__(self, ql): "r12", "sp", "lr", "pc", ) + self.regs_need_swaped = { + "sl": "r10", + "ip": "r12", + "fp": "r11", + } + @staticmethod def get_flags(bits: int) -> Mapping[str, int]: + """ + get flags for ARM + """ - def _get_mode(bits): + def get_mode(bits): + """ + get operating mode for ARM + """ return { 0b10000: "User", 0b10001: "FIQ", @@ -203,7 +256,7 @@ def _get_mode(bits): }.get(bits & 0x00001f) return { - "mode": _get_mode(bits), + "mode": get_mode(bits), "thumb": bits & 0x00000020 != 0, "fiq": bits & 0x00000040 != 0, "irq": bits & 0x00000080 != 0, @@ -213,129 +266,110 @@ def _get_mode(bits): "overflow": bits & 0x10000000 != 0, } - @divider_printer("[ REGISTERS ]") - def context_reg(self, saved_reg_dump): - cur_regs = self.dump_regs() + @staticmethod + def print_mode_info(bits): + print(color.GREEN, "[{cpsr[mode]} mode], Thumb: {cpsr[thumb]}, FIQ: {cpsr[fiq]}, IRQ: {cpsr[irq]}, NEG: {cpsr[neg]}, ZERO: {cpsr[zero]}, Carry: {cpsr[carry]}, Overflow: {cpsr[overflow]}".format(cpsr=ArchARM.get_flags(bits)), color.END, sep="") - cur_regs.update({"sl": cur_regs.pop("r10")}) - cur_regs.update({"ip": cur_regs.pop("r12")}) - cur_regs.update({"fp": cur_regs.pop("r11")}) +class ArchCORTEX_M(ArchARM): + def __init__(self): + super().__init__() + self.archtype = QL_ARCH.CORTEX_M + self.regs += ("xpsr", "control", "primask", "basepri", "faultmask") - regs_in_row = 4 +class ArchX86(): + def __init__(self): + self.regs = ( + "eax", "ebx", "ecx", "edx", + "esp", "ebp", "esi", "edi", + "eip", "ss", "cs", "ds", "es", + "fs", "gs", "ef", + ) - diff = None - if saved_reg_dump is not None: - reg_dump = copy.deepcopy(saved_reg_dump) - reg_dump.update({"sl": reg_dump.pop("r10")}) - reg_dump.update({"ip": reg_dump.pop("r12")}) - reg_dump.update({"fp": reg_dump.pop("r11")}) - diff = [k for k in cur_regs if cur_regs[k] != reg_dump[k]] - lines = "" - for idx, r in enumerate(cur_regs, 1): +class Context(object): + """ + base class for accessing context of running qiling instance + """ - line = "{}{:}: 0x{{:08x}} {} ".format(COLORS[(idx-1) // regs_in_row], r, color.END) + def __init__(self, ql): + self.ql = ql - if diff and r in diff: - line = f"{color.UNDERLINE}{color.BOLD}{line}" + def dump_regs(self) -> Mapping[str, int]: + """ + dump all registers + """ - if idx % regs_in_row == 0: - line += "\n" + return {reg_name: getattr(self.ql.reg, reg_name) for reg_name in self.regs} - lines += line - print(lines.format(*cur_regs.values())) - print(color.GREEN, "[{cpsr[mode]} mode], Thumb: {cpsr[thumb]}, FIQ: {cpsr[fiq]}, IRQ: {cpsr[irq]}, NEG: {cpsr[neg]}, ZERO: {cpsr[zero]}, Carry: {cpsr[carry]}, Overflow: {cpsr[overflow]}".format(cpsr=self.get_flags(self.ql.reg.cpsr)), color.END, sep="") +class ContextRender(Context, Render): + def __init__(self, ql: Qiling): + super().__init__(ql) + Render.__init__(self) + self.predictor = setup_branch_predictor(ql) -class ContextRender_MIPS(ContextRender): +class ContextRenderARM(ContextRender, ArchARM): """ - context render for MIPS + context render for ARM """ - def __init__(self, ql): - super().__init__(ql) - self.regs = ( - "gp", "at", "v0", "v1", - "a0", "a1", "a2", "a3", - "t0", "t1", "t2", "t3", - "t4", "t5", "t6", "t7", - "t8", "t9", "sp", "s8", - "s0", "s1", "s2", "s3", - "s4", "s5", "s6", "s7", - "ra", "k0", "k1", "pc", - ) + def __init__(self, ql: Qiling): + super().__init__(ql) + ArchARM.__init__(self) - @divider_printer("[ REGISTERS ]") + @Render.divider_printer("[ REGISTERS ]") def context_reg(self, saved_reg_dump): + """ + redering context registers + """ cur_regs = self.dump_regs() + cur_regs = self.swap_reg_name(cur_regs) + diff_reg = self.reg_diff(cur_regs, saved_reg_dump) + self.render_regs_dump(cur_regs, diff_reg=diff_reg) + self.print_mode_info(self.ql.reg.cpsr) - cur_regs.update({"fp": cur_regs.pop("s8")}) - - diff = None - if saved_reg_dump is not None: - reg_dump = copy.deepcopy(saved_reg_dump) - reg_dump.update({"fp": reg_dump.pop("s8")}) - diff = [k for k in cur_regs if cur_regs[k] != reg_dump[k]] - - lines = "" - for idx, r in enumerate(cur_regs, 1): - line = "{}{}: 0x{{:08x}} {}\t".format(COLORS[(idx-1) // 4], r, color.END) - if diff and r in diff: - line = f"{color.UNDERLINE}{color.BOLD}{line}" +class ContextRenderMIPS(ContextRender, ArchMIPS): + """ + context render for MIPS + """ - if idx % 4 == 0 and idx != 32: - line += "\n" + def __init__(self, ql): + super().__init__(ql) + ArchMIPS.__init__(self) - lines += line + @Render.divider_printer("[ REGISTERS ]") + def context_reg(self, saved_reg_dump): + """ + redering context registers + """ - print(lines.format(*cur_regs.values())) + cur_regs = self.dump_regs() + cur_regs = self.swap_reg_name(cur_regs) + diff_reg = self.reg_diff(cur_regs, saved_reg_dump) + self.render_regs_dump(cur_regs, diff_reg=diff_reg) -class ContextRender_X86(ContextRender): +class ContextRenderX86(ContextRender, ArchX86): """ context render for X86 """ + def __init__(self, ql): super().__init__(ql) + ArchX86.__init__(self) - self.regs = ( - "eax", "ebx", "ecx", "edx", - "esp", "ebp", "esi", "edi", - "eip", "ss", "cs", "ds", "es", - "fs", "gs", "ef", - ) - @divider_printer("[ REGISTERS ]") + @Render.divider_printer("[ REGISTERS ]") def context_reg(self, saved_reg_dump): cur_regs = self.dump_regs() - - diff = None - if saved_reg_dump is not None: - reg_dump = copy.deepcopy(saved_reg_dump) - diff = [k for k in cur_regs if cur_regs[k] != saved_reg_dump[k]] - - lines = "" - for idx, r in enumerate(cur_regs, 1): - if len(r) == 2: - line = "{}{}: 0x{{:08x}} {}\t\t".format(COLORS[(idx-1) // 4], r, color.END) - else: - line = "{}{}: 0x{{:08x}} {}\t".format(COLORS[(idx-1) // 4], r, color.END) - - if diff and r in diff: - line = f"{color.UNDERLINE}{color.BOLD}{line}" - - if idx % 4 == 0 and idx != 32: - line += "\n" - - lines += line - - print(lines.format(*cur_regs.values())) + diff_reg = self.reg_diff(cur_regs, saved_reg_dump) + self.render_regs_dump(cur_regs, diff_reg=diff_reg) print(color.GREEN, "EFLAGS: [CF: {flags[CF]}, PF: {flags[PF]}, AF: {flags[AF]}, ZF: {flags[ZF]}, SF: {flags[SF]}, OF: {flags[OF]}]".format(flags=get_x86_eflags(self.ql.reg.ef)), color.END, sep="") - @divider_printer("[ DISASM ]") + @Render.divider_printer("[ DISASM ]") def context_asm(self): past_list = [] cur_addr = self.ql.reg.arch_pc @@ -360,61 +394,35 @@ def context_asm(self): self.print_asm(line) -class ContextRender_CORTEX_M(ContextRender): +class ContextRenderCORTEX_M(ContextRender, ArchCORTEX_M): """ context render for cortex_m """ def __init__(self, ql): super().__init__(ql) + ArchCORTEX_M.__init__(self) + self.regs_a_row = 3 - self.regs = ( - "r0", "r1", "r2", "r3", - "r4", "r5", "r6", "r7", - "r8", "r9", "r10", "r11", - "r12", "sp", "lr", "pc", - "xpsr", "control", "primask", "basepri", "faultmask" - ) - - @divider_printer("[ REGISTERS ]") + @Render.divider_printer("[ REGISTERS ]") def context_reg(self, saved_reg_dump): - - cur_regs.update({"sl": cur_regs.pop("r10")}) - cur_regs.update({"ip": cur_regs.pop("r12")}) - cur_regs.update({"fp": cur_regs.pop("r11")}) - - regs_in_row = 3 + cur_regs = self.dump_regs() + cur_regs = self.swap_reg_name(cur_regs) # for re-order - cur_regs.update({"xpsr": cur_regs.pop("xpsr")}) - cur_regs.update({"control": cur_regs.pop("control")}) - cur_regs.update({"primask": cur_regs.pop("primask")}) - cur_regs.update({"faultmask": cur_regs.pop("faultmask")}) - cur_regs.update({"basepri": cur_regs.pop("basepri")}) - - diff = None - if saved_reg_dump is not None: - reg_dump = copy.deepcopy(saved_reg_dump) - reg_dump.update({"sl": reg_dump.pop("r10")}) - reg_dump.update({"ip": reg_dump.pop("r12")}) - reg_dump.update({"fp": reg_dump.pop("r11")}) - diff = [k for k in cur_regs if cur_regs[k] != reg_dump[k]] - - lines = "" - for idx, r in enumerate(_cur_regs, 1): - - line = "{}{:}: 0x{{:08x}} {} ".format(_colors[(idx-1) // regs_in_row], r, color.END) - - if _diff and r in _diff: - line = "{}{}".format(color.UNDERLINE, color.BOLD) + line - - if idx % regs_in_row == 0: - line += "\n" + extra_dict = { + "xpsr": "xpsr", + "control": "control", + "primask": "primask", + "faultmask": "faultmask", + "basepri": "basepri", + } - lines += line + cur_regs = self.swap_reg_name(cur_regs, extra_dict=extra_dict) + diff_reg = self.reg_diff(cur_regs, saved_reg_dump) + self.render_regs_dump(cur_regs, diff_reg=diff_reg) + self.print_mode_info(self.ql.reg.cpsr) - print(lines.format(cur_regs.values())) - print(color.GREEN, "[{cpsr[mode]} mode], Thumb: {cpsr[thumb]}, FIQ: {cpsr[fiq]}, IRQ: {cpsr[irq]}, NEG: {cpsr[neg]}, ZERO: {cpsr[zero]}, Carry: {cpsr[carry]}, Overflow: {cpsr[overflow]}".format(cpsr=get_arm_flags(self.ql.reg.cpsr)), color.END, sep="") if __name__ == "__main__": diff --git a/qiling/debugger/qdb/utils.py b/qiling/debugger/qdb/utils.py index 0719f9425..6f21b6b89 100644 --- a/qiling/debugger/qdb/utils.py +++ b/qiling/debugger/qdb/utils.py @@ -9,7 +9,7 @@ from qiling.const import QL_ARCH -from .misc import try_read, disasm, get_x86_eflags, read_int +from .misc import try_read, disasm, get_x86_eflags, read_int, signed_val, get_cpsr From c0ab79b5789cfae877ccce662d97bdd2ed6128ff Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Tue, 15 Feb 2022 01:40:17 +0000 Subject: [PATCH 129/406] minor refactored and new source code org --- qiling/debugger/qdb/arch.py | 118 ++++++ qiling/debugger/qdb/branch_predictor.py | 502 ++++++++++++++++++++++++ qiling/debugger/qdb/frontend.py | 148 ++----- qiling/debugger/qdb/misc.py | 59 +-- qiling/debugger/qdb/qdb.py | 8 +- qiling/debugger/qdb/utils.py | 500 ++--------------------- 6 files changed, 697 insertions(+), 638 deletions(-) create mode 100644 qiling/debugger/qdb/arch.py create mode 100644 qiling/debugger/qdb/branch_predictor.py diff --git a/qiling/debugger/qdb/arch.py b/qiling/debugger/qdb/arch.py new file mode 100644 index 000000000..f8683d67d --- /dev/null +++ b/qiling/debugger/qdb/arch.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from __future__ import annotations +from typing import Callable, Optional, Mapping + +from qiling.const import QL_ARCH + + +class ArchX86(): + def __init__(self): + + self.archtype = QL_ARCH.X86 + + self.regs = ( + "eax", "ebx", "ecx", "edx", + "esp", "ebp", "esi", "edi", + "eip", "ss", "cs", "ds", "es", + "fs", "gs", "ef", + ) + + @staticmethod + def get_flags(bits: int) -> Mapping[str, bool]: + """ + get flags from ql.reg.ef + """ + + return { + "CF" : bits & 0x0001 != 0, # CF, carry flag + "PF" : bits & 0x0004 != 0, # PF, parity flag + "AF" : bits & 0x0010 != 0, # AF, adjust flag + "ZF" : bits & 0x0040 != 0, # ZF, zero flag + "SF" : bits & 0x0080 != 0, # SF, sign flag + "OF" : bits & 0x0800 != 0, # OF, overflow flag + } + + +class ArchMIPS(object): + def __init__(self): + + self.archtype = QL_ARCH.MIPS + + self.regs = ( + "gp", "at", "v0", "v1", + "a0", "a1", "a2", "a3", + "t0", "t1", "t2", "t3", + "t4", "t5", "t6", "t7", + "t8", "t9", "sp", "s8", + "s0", "s1", "s2", "s3", + "s4", "s5", "s6", "s7", + "ra", "k0", "k1", "pc", + ) + + self.regs_need_swaped = { + "fp": "s8", + } + + +class ArchARM(): + def __init__(self): + self.archtype = QL_ARCH.ARM + self.regs = ( + "r0", "r1", "r2", "r3", + "r4", "r5", "r6", "r7", + "r8", "r9", "r10", "r11", + "r12", "sp", "lr", "pc", + ) + + self.regs_need_swaped = { + "sl": "r10", + "ip": "r12", + "fp": "r11", + } + + @staticmethod + def get_flags(bits: int) -> Mapping[str, int]: + """ + get flags for ARM + """ + + def get_mode(bits): + """ + get operating mode for ARM + """ + return { + 0b10000: "User", + 0b10001: "FIQ", + 0b10010: "IRQ", + 0b10011: "Supervisor", + 0b10110: "Monitor", + 0b10111: "Abort", + 0b11010: "Hypervisor", + 0b11011: "Undefined", + 0b11111: "System", + }.get(bits & 0x00001f) + + return { + "mode": get_mode(bits), + "thumb": bits & 0x00000020 != 0, + "fiq": bits & 0x00000040 != 0, + "irq": bits & 0x00000080 != 0, + "neg": bits & 0x80000000 != 0, + "zero": bits & 0x40000000 != 0, + "carry": bits & 0x20000000 != 0, + "overflow": bits & 0x10000000 != 0, + } + + +class ArchCORTEX_M(ArchARM): + def __init__(self): + super().__init__() + self.archtype = QL_ARCH.CORTEX_M + self.regs += ("xpsr", "control", "primask", "basepri", "faultmask") + +if __name__ == "__main__": + pass diff --git a/qiling/debugger/qdb/branch_predictor.py b/qiling/debugger/qdb/branch_predictor.py new file mode 100644 index 000000000..fe3b4cd1f --- /dev/null +++ b/qiling/debugger/qdb/branch_predictor.py @@ -0,0 +1,502 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from __future__ import annotations +from typing import Callable, Optional, Mapping + +import ast, re + +from .arch import ArchARM, ArchX86 +from .misc import disasm, read_int + +class BranchPredictor(object): + """ + Base class for predictor + """ + + class Prophecy(object): + """ + container for storing result of the predictor + @going: indicate the certian branch will be taken or not + @where: where will it go if going is true + """ + + def __init__(self): + self.going = False + self.where = None + + def __iter__(self): + return iter((self.going, self.where)) + + def __init__(self, ql): + self.ql = ql + + def read_reg(self, reg_name): + """ + read specific register value + """ + + return getattr(self.ql.reg, reg_name) + + def predict(self) -> Prophecy: + """ + Try to predict certian branch will be taken or not based on current context + """ + + return NotImplementedError + +class BranchPredictorARM(BranchPredictor, ArchARM): + """ + predictor for ARM + """ + + def __init__(self, ql): + super().__init__(ql) + + self.INST_SIZE = 4 + self.THUMB_INST_SIZE = 2 + self.CODE_END = "udf" + + def read_reg(self, reg_name): + reg_name = reg_name.replace("ip", "r12").replace("fp", "r11") + return getattr(self.ql.reg, reg_name) + + def regdst_eq_pc(self, op_str): + return op_str.partition(", ")[0] == "pc" + + @staticmethod + def get_cpsr(bits: int) -> (bool, bool, bool, bool): + """ + get flags from ql.reg.cpsr + """ + return ( + bits & 0x10000000 != 0, # V, overflow flag + bits & 0x20000000 != 0, # C, carry flag + bits & 0x40000000 != 0, # Z, zero flag + bits & 0x80000000 != 0, # N, sign flag + ) + + def predict(self): + prophecy = self.Prophecy() + cur_addr = self.ql.reg.arch_pc + line = disasm(self.ql, cur_addr) + prophecy.where = cur_addr + line.size + + if line.mnemonic == self.CODE_END: # indicates program exited + return True + + jump_table = { + # unconditional branch + "b" : (lambda *_: True), + "bl" : (lambda *_: True), + "bx" : (lambda *_: True), + "blx" : (lambda *_: True), + "b.w" : (lambda *_: True), + + # branch on equal, Z == 1 + "beq" : (lambda V, C, Z, N: Z == 1), + "bxeq" : (lambda V, C, Z, N: Z == 1), + "beq.w": (lambda V, C, Z, N: Z == 1), + + # branch on not equal, Z == 0 + "bne" : (lambda V, C, Z, N: Z == 0), + "bxne" : (lambda V, C, Z, N: Z == 0), + "bne.w": (lambda V, C, Z, N: Z == 0), + + # branch on signed greater than, Z == 0 and N == V + "bgt" : (lambda V, C, Z, N: (Z == 0 and N == V)), + "bgt.w": (lambda V, C, Z, N: (Z == 0 and N == V)), + + # branch on signed less than, N != V + "blt" : (lambda V, C, Z, N: N != V), + + # branch on signed greater than or equal, N == V + "bge" : (lambda V, C, Z, N: N == V), + + # branch on signed less than or queal + "ble" : (lambda V, C, Z, N: Z == 1 or N != V), + + # branch on unsigned higher or same (or carry set), C == 1 + "bhs" : (lambda V, C, Z, N: C == 1), + "bcs" : (lambda V, C, Z, N: C == 1), + + # branch on unsigned lower (or carry clear), C == 0 + "bcc" : (lambda V, C, Z, N: C == 0), + "blo" : (lambda V, C, Z, N: C == 0), + "bxlo" : (lambda V, C, Z, N: C == 0), + "blo.w": (lambda V, C, Z, N: C == 0), + + # branch on negative or minus, N == 1 + "bmi" : (lambda V, C, Z, N: N == 1), + + # branch on positive or plus, N == 0 + "bpl" : (lambda V, C, Z, N: N == 0), + + # branch on signed overflow + "bvs" : (lambda V, C, Z, N: V == 1), + + # branch on no signed overflow + "bvc" : (lambda V, C, Z, N: V == 0), + + # branch on unsigned higher + "bhi" : (lambda V, C, Z, N: (Z == 0 and C == 1)), + "bxhi" : (lambda V, C, Z, N: (Z == 0 and C == 1)), + "bhi.w": (lambda V, C, Z, N: (Z == 0 and C == 1)), + + # branch on unsigned lower + "bls" : (lambda V, C, Z, N: (C == 0 or Z == 1)), + "bls.w": (lambda V, C, Z, N: (C == 0 or Z == 1)), + } + + cb_table = { + # branch on equal to zero + "cbz" : (lambda r: r == 0), + + # branch on not equal to zero + "cbnz": (lambda r: r != 0), + } + + if line.mnemonic in jump_table: + prophecy.going = jump_table.get(line.mnemonic)(*self.get_cpsr(self.ql.reg.cpsr)) + + elif line.mnemonic in cb_table: + prophecy.going = cb_table.get(line.mnemonic)(self.read_reg(line.op_str.split(", ")[0])) + + if prophecy.going: + if "#" in line.op_str: + prophecy.where = read_int(line.op_str.split("#")[-1]) + else: + prophecy.where = self.read_reg(line.op_str) + + if self.regdst_eq_pc(line.op_str): + next_addr = cur_addr + line.size + n2_addr = next_addr + len(read_insn(next_addr)) + prophecy.where += len(read_insn(n2_addr)) + len(read_insn(next_addr)) + + elif line.mnemonic.startswith("it"): + # handle IT block here + + cond_met = { + "eq": lambda V, C, Z, N: (Z == 1), + "ne": lambda V, C, Z, N: (Z == 0), + "ge": lambda V, C, Z, N: (N == V), + "hs": lambda V, C, Z, N: (C == 1), + "lo": lambda V, C, Z, N: (C == 0), + "mi": lambda V, C, Z, N: (N == 1), + "pl": lambda V, C, Z, N: (N == 0), + "ls": lambda V, C, Z, N: (C == 0 or Z == 1), + "le": lambda V, C, Z, N: (Z == 1 or N != V), + "hi": lambda V, C, Z, N: (Z == 0 and C == 1), + }.get(line.op_str)(*self.get_cpsr(ql.reg.cpsr)) + + it_block_range = [each_char for each_char in line.mnemonic[1:]] + + next_addr = cur_addr + self.THUMB_INST_SIZE + for each in it_block_range: + _insn = read_insn(next_addr) + n2_addr = handle_bnj_arm(ql, next_addr) + + if (cond_met and each == "t") or (not cond_met and each == "e"): + if n2_addr != (next_addr+len(_insn)): # branch detected + break + + next_addr += len(_insn) + + prophecy.where = next_addr + + elif line.mnemonic in ("ldr",): + + if self.regdst_eq_pc(line.op_str): + _, _, rn_offset = line.op_str.partition(", ") + r, _, imm = rn_offset.strip("[]!").partition(", #") + + if "]" in rn_offset.split(", ")[1]: # pre-indexed immediate + prophecy.where = ql.unpack32(ql.mem.read(read_int(imm) + self.read_reg(r), self.INST_SIZE)) + + else: # post-indexed immediate + # FIXME: weired behavior, immediate here does not apply + prophecy.where = ql.unpack32(ql.mem.read(self.read_reg(r), self.INST_SIZE)) + + elif line.mnemonic in ("addls", "addne", "add") and self.regdst_eq_pc(line.op_str): + V, C, Z, N = self.get_cpsr(ql.reg.cpsr) + r0, r1, r2, *imm = line.op_str.split(", ") + + # program counter is awalys 8 bytes ahead when it comes with pc, need to add extra 8 bytes + extra = 8 if 'pc' in (r0, r1, r2) else 0 + + if imm: + expr = imm[0].split() + # TODO: should support more bit shifting and rotating operation + if expr[0] == "lsl": # logical shift left + n = read_int(expr[-1].strip("#")) * 2 + + if line.mnemonic == "addls" and (C == 0 or Z == 1): + prophecy.where = extra + self.read_reg(r1) + self.read_reg(r2) * n + + elif line.mnemonic == "add" or (line.mnemonic == "addne" and Z == 0): + prophecy.where = extra + self.read_reg(r1) + (self.read_reg(r2) * n if imm else self.read_reg(r2)) + + elif line.mnemonic in ("tbh", "tbb"): + + cur_addr += self.INST_SIZE + r0, r1, *imm = line.op_str.strip("[]").split(", ") + + if imm: + expr = imm[0].split() + if expr[0] == "lsl": # logical shift left + n = read_int(expr[-1].strip("#")) * 2 + + if line.mnemonic == "tbh": + + r1 = self.read_reg(r1) * n + + elif line.mnemonic == "tbb": + + r1 = self.read_reg(r1) + + to_add = int.from_bytes(ql.mem.read(cur_addr+r1, 2 if line.mnemonic == "tbh" else 1), byteorder="little") * n + prophecy.where = cur_addr + to_add + + elif line.mnemonic.startswith("pop") and "pc" in line.op_str: + + prophecy.where = ql.stack_read(line.op_str.strip("{}").split(", ").index("pc") * self.INST_SIZE) + if not { # step to next instruction if cond does not meet + "pop" : lambda *_: True, + "pop.w": lambda *_: True, + "popeq": lambda V, C, Z, N: (Z == 1), + "popne": lambda V, C, Z, N: (Z == 0), + "pophi": lambda V, C, Z, N: (C == 1), + "popge": lambda V, C, Z, N: (N == V), + "poplt": lambda V, C, Z, N: (N != V), + }.get(line.mnemonic)(*get_cpsr(ql.reg.cpsr)): + + prophecy.where = cur_addr + self.INST_SIZE + + elif line.mnemonic == "sub" and self.regdst_eq_pc(line.op_str): + _, r, imm = line.op_str.split(", ") + prophecy.where = self.read_reg(r) - read_int(imm.strip("#")) + + elif line.mnemonic == "mov" and self.regdst_eq_pc(line.op_str): + _, r = line.op_str.split(", ") + prophecy.where = self.read_reg(r) + + if prophecy.where & 1: + prophecy.where -= 1 + + return prophecy + +class BranchPredictorMIPS(BranchPredictor): + """ + predictor for MIPS + """ + + def __init__(self, ql): + super().__init__(ql) + self.CODE_END = "break" + self.INST_SIZE = 4 + + @staticmethod + def signed_val(val: int) -> int: + """ + signed value convertion + """ + + def is_negative(i: int) -> int: + """ + check wether negative value or not + """ + + return i & (1 << 31) + + return (val-1 << 32) if is_negative(val) else val + + def read_reg(self, reg_name): + reg_name = reg_name.strip("$").replace("fp", "s8") + return signed_val(getattr(self.ql.reg, reg_name)) + + def predict(self): + prophecy = self.Prophecy() + cur_addr = self.ql.reg.arch_pc + line = disasm(self.ql, cur_addr) + + if line.mnemonic == self.CODE_END: # indicates program extied + return True + + prophecy.where = cur_addr + self.INST_SIZE + if line.mnemonic.startswith('j') or line.mnemonic.startswith('b'): + + # make sure at least delay slot executed + prophecy.where += self.INST_SIZE + + # get registers or memory address from op_str + targets = [ + self.read_reg(each) + if '$' in each else read_int(each) + for each in line.op_str.split(", ") + ] + + prophecy.going = { + "j" : (lambda _: True), # unconditional jump + "jr" : (lambda _: True), # unconditional jump + "jal" : (lambda _: True), # unconditional jump + "jalr" : (lambda _: True), # unconditional jump + "b" : (lambda _: True), # unconditional branch + "bl" : (lambda _: True), # unconditional branch + "bal" : (lambda _: True), # unconditional branch + "beq" : (lambda r0, r1, _: r0 == r1), # branch on equal + "bne" : (lambda r0, r1, _: r0 != r1), # branch on not equal + "blt" : (lambda r0, r1, _: r0 < r1), # branch on r0 less than r1 + "bgt" : (lambda r0, r1, _: r0 > r1), # branch on r0 greater than r1 + "ble" : (lambda r0, r1, _: r0 <= r1), # brach on r0 less than or equal to r1 + "bge" : (lambda r0, r1, _: r0 >= r1), # branch on r0 greater than or equal to r1 + "beqz" : (lambda r, _: r == 0), # branch on equal to zero + "bnez" : (lambda r, _: r != 0), # branch on not equal to zero + "bgtz" : (lambda r, _: r > 0), # branch on greater than zero + "bltz" : (lambda r, _: r < 0), # branch on less than zero + "bltzal" : (lambda r, _: r < 0), # branch on less than zero and link + "blez" : (lambda r, _: r <= 0), # branch on less than or equal to zero + "bgez" : (lambda r, _: r >= 0), # branch on greater than or equal to zero + "bgezal" : (lambda r, _: r >= 0), # branch on greater than or equal to zero and link + }.get(line.mnemonic)(*targets) + + if prophecy.going: + # target address is always the rightmost one + prophecy.where = targets[-1] + + return prophecy + +class BranchPredictorX86(BranchPredictor, ArchX86): + """ + predictor for X86 + """ + + class ParseError(Exception): + """ + indicate parser error + """ + pass + + def __init__(self, ql): + super().__init__(ql) + ArchX86.__init__(self) + + def predict(self) -> Prophecy: + prophecy = self.Prophecy() + cur_addr = self.ql.reg.arch_pc + line = disasm(self.ql, cur_addr) + + jump_table = { + # conditional jump + + "jo" : (lambda C, P, A, Z, S, O: O == 1), + "jno" : (lambda C, P, A, Z, S, O: O == 0), + + "js" : (lambda C, P, A, Z, S, O: S == 1), + "jns" : (lambda C, P, A, Z, S, O: S == 0), + + "je" : (lambda C, P, A, Z, S, O: Z == 1), + "jz" : (lambda C, P, A, Z, S, O: Z == 1), + + "jne" : (lambda C, P, A, Z, S, O: Z == 0), + "jnz" : (lambda C, P, A, Z, S, O: Z == 0), + + "jb" : (lambda C, P, A, Z, S, O: C == 1), + "jc" : (lambda C, P, A, Z, S, O: C == 1), + "jnae" : (lambda C, P, A, Z, S, O: C == 1), + + "jnb" : (lambda C, P, A, Z, S, O: C == 0), + "jnc" : (lambda C, P, A, Z, S, O: C == 0), + "jae" : (lambda C, P, A, Z, S, O: C == 0), + + "jbe" : (lambda C, P, A, Z, S, O: C == 1 or Z == 1), + "jna" : (lambda C, P, A, Z, S, O: C == 1 or Z == 1), + + "ja" : (lambda C, P, A, Z, S, O: C == 0 and Z == 0), + "jnbe" : (lambda C, P, A, Z, S, O: C == 0 and Z == 0), + + "jl" : (lambda C, P, A, Z, S, O: S != O), + "jnge" : (lambda C, P, A, Z, S, O: S != O), + + "jge" : (lambda C, P, A, Z, S, O: S == O), + "jnl" : (lambda C, P, A, Z, S, O: S == O), + + "jle" : (lambda C, P, A, Z, S, O: Z == 1 or S != O), + "jng" : (lambda C, P, A, Z, S, O: Z == 1 or S != O), + + "jg" : (lambda C, P, A, Z, S, O: Z == 0 or S == O), + "jnle" : (lambda C, P, A, Z, S, O: Z == 0 or S == O), + + "jp" : (lambda C, P, A, Z, S, O: P == 1), + "jpe" : (lambda C, P, A, Z, S, O: P == 1), + + "jnp" : (lambda C, P, A, Z, S, O: P == 0), + "jpo" : (lambda C, P, A, Z, S, O: P == 0), + + # unconditional jump + + "call" : (lambda *_: True), + "jmp" : (lambda *_: True), + + } + + jump_reg_table = { + "jcxz" : (lambda cx: cx == 0), + "jecxz" : (lambda ecx: ecx == 0), + "jrcxz" : (lambda rcx: rcx == 0), + } + + if line.mnemonic in jump_table: + eflags = self.get_flags(self.ql.reg.ef).values() + prophecy.going = jump_table.get(line.mnemonic)(*eflags) + + elif line.mnemonic in jump_reg_table: + prophecy.going = jump_reg_table.get(line.mnemonic)(self.ql.reg.ecx) + + if prophecy.going: + takeaway_list = ["ptr", "dword", "[", "]"] + class AST_checker(ast.NodeVisitor): + def generic_visit(self, node): + if type(node) in (ast.Module, ast.Expr, ast.BinOp, ast.Constant, ast.Add, ast.Mult, ast.Sub): + ast.NodeVisitor.generic_visit(self, node) + else: + raise ParseError("malform or invalid ast node") + + if len(line.op_str.split()) > 1: + new_line = line.op_str.replace(":", "+") + for each in takeaway_list: + new_line = new_line.replace(each, " ") + + new_line = " ".join(new_line.split()) + for each_reg in filter(lambda r: len(r) == 3, self.ql.reg.register_mapping.keys()): + if each_reg in new_line: + new_line = re.sub(each_reg, hex(self.read_reg(each_reg)), new_line) + + for each_reg in filter(lambda r: len(r) == 2, self.ql.reg.register_mapping.keys()): + if each_reg in new_line: + new_line = re.sub(each_reg, hex(self.read_reg(each_reg)), new_line) + + checker = AST_checker() + ast_tree = ast.parse(new_line) + + checker.visit(ast_tree) + + prophecy.where = eval(new_line) + + elif line.op_str in self.ql.reg.register_mapping: + prophecy.where = getattr(self.ql.reg, line.op_str) + + else: + prophecy.where = read_int(line.op_str) + else: + prophecy.where = cur_addr + line.size + + return prophecy + +class BranchPredictorCORTEX_M(BranchPredictorARM): + def __init__(self, ql): + super().__init__(ql) + +if __name__ == "__main__": + pass diff --git a/qiling/debugger/qdb/frontend.py b/qiling/debugger/qdb/frontend.py index 9b12376de..c199dcc96 100644 --- a/qiling/debugger/qdb/frontend.py +++ b/qiling/debugger/qdb/frontend.py @@ -7,10 +7,8 @@ from typing import Optional, Mapping, Iterable, Union import copy -from qiling.const import QL_ARCH - -from .utils import setup_branch_predictor -from .misc import try_read, read_int, get_terminal_size, disasm, get_x86_eflags +from .misc import try_read, get_terminal_size, disasm +from .arch import ArchARM, ArchMIPS, ArchCORTEX_M, ArchX86 from .const import color @@ -22,21 +20,6 @@ COLORS = (color.DARKCYAN, color.BLUE, color.RED, color.YELLOW, color.GREEN, color.PURPLE, color.CYAN, color.WHITE) - -def setup_context_render(ql: Qiling) -> ContextRender: - """ - setup context render for corresponding archtype - """ - - return { - QL_ARCH.X86: ContextRenderX86, - QL_ARCH.ARM: ContextRenderARM, - QL_ARCH.ARM_THUMB: ContextRenderARM, - QL_ARCH.CORTEX_M: ContextRenderCORTEX_M, - QL_ARCH.MIPS: ContextRenderMIPS, - }.get(ql.archtype)(ql) - - class Render(object): """ base class for rendering related functions @@ -58,7 +41,7 @@ def wrapper(*args, **kwargs): return wrapper return decorator - def __init__(self): + def __init__(self) -> Render: self.regs_a_row = 4 def reg_diff(self, cur_regs, saved_reg_dump): @@ -79,7 +62,7 @@ def render_regs_dump(self, regs, diff_reg=None): lines = "" for idx, r in enumerate(regs, 1): - line = "{}{}: 0x{{:08x}} {}\t".format(COLORS[(idx-1) // self.regs_a_row], r, color.END) + line = "{}{}: 0x{{:08x}} {}\t".format(COLORS[(idx-1) // self.regs_a_row], r, color.END) if diff_reg and r in diff_reg: line = f"{color.UNDERLINE}{color.BOLD}{line}" @@ -197,101 +180,13 @@ def context_asm(self) -> None: self.print_asm(forward_insn) forward_insn_size += forward_insn.size -class ArchMIPS(object): - def __init__(self): - - self.archtype = QL_ARCH.MIPS - - self.regs = ( - "gp", "at", "v0", "v1", - "a0", "a1", "a2", "a3", - "t0", "t1", "t2", "t3", - "t4", "t5", "t6", "t7", - "t8", "t9", "sp", "s8", - "s0", "s1", "s2", "s3", - "s4", "s5", "s6", "s7", - "ra", "k0", "k1", "pc", - ) - - self.regs_need_swaped = { - "fp": "s8", - } - -class ArchARM(): - def __init__(self): - self.archtype = QL_ARCH.ARM - self.regs = ( - "r0", "r1", "r2", "r3", - "r4", "r5", "r6", "r7", - "r8", "r9", "r10", "r11", - "r12", "sp", "lr", "pc", - ) - - self.regs_need_swaped = { - "sl": "r10", - "ip": "r12", - "fp": "r11", - } - - @staticmethod - def get_flags(bits: int) -> Mapping[str, int]: - """ - get flags for ARM - """ - - def get_mode(bits): - """ - get operating mode for ARM - """ - return { - 0b10000: "User", - 0b10001: "FIQ", - 0b10010: "IRQ", - 0b10011: "Supervisor", - 0b10110: "Monitor", - 0b10111: "Abort", - 0b11010: "Hypervisor", - 0b11011: "Undefined", - 0b11111: "System", - }.get(bits & 0x00001f) - - return { - "mode": get_mode(bits), - "thumb": bits & 0x00000020 != 0, - "fiq": bits & 0x00000040 != 0, - "irq": bits & 0x00000080 != 0, - "neg": bits & 0x80000000 != 0, - "zero": bits & 0x40000000 != 0, - "carry": bits & 0x20000000 != 0, - "overflow": bits & 0x10000000 != 0, - } - - @staticmethod - def print_mode_info(bits): - print(color.GREEN, "[{cpsr[mode]} mode], Thumb: {cpsr[thumb]}, FIQ: {cpsr[fiq]}, IRQ: {cpsr[irq]}, NEG: {cpsr[neg]}, ZERO: {cpsr[zero]}, Carry: {cpsr[carry]}, Overflow: {cpsr[overflow]}".format(cpsr=ArchARM.get_flags(bits)), color.END, sep="") - -class ArchCORTEX_M(ArchARM): - def __init__(self): - super().__init__() - self.archtype = QL_ARCH.CORTEX_M - self.regs += ("xpsr", "control", "primask", "basepri", "faultmask") - -class ArchX86(): - def __init__(self): - self.regs = ( - "eax", "ebx", "ecx", "edx", - "esp", "ebp", "esi", "edi", - "eip", "ss", "cs", "ds", "es", - "fs", "gs", "ef", - ) - class Context(object): """ base class for accessing context of running qiling instance """ - def __init__(self, ql): + def __init__(self, ql) -> Context: self.ql = ql def dump_regs(self) -> Mapping[str, int]: @@ -303,10 +198,14 @@ def dump_regs(self) -> Mapping[str, int]: class ContextRender(Context, Render): - def __init__(self, ql: Qiling): + """ + base class for context render + """ + + def __init__(self, ql: Qiling, predictor: BranchPredictor): super().__init__(ql) Render.__init__(self) - self.predictor = setup_branch_predictor(ql) + self.predictor = predictor class ContextRenderARM(ContextRender, ArchARM): @@ -314,10 +213,14 @@ class ContextRenderARM(ContextRender, ArchARM): context render for ARM """ - def __init__(self, ql: Qiling): - super().__init__(ql) + def __init__(self, ql: Qiling, predictor: BranchPredictor): + super().__init__(ql, predictor) ArchARM.__init__(self) + @staticmethod + def print_mode_info(bits): + print(color.GREEN, "[{cpsr[mode]} mode], Thumb: {cpsr[thumb]}, FIQ: {cpsr[fiq]}, IRQ: {cpsr[irq]}, NEG: {cpsr[neg]}, ZERO: {cpsr[zero]}, Carry: {cpsr[carry]}, Overflow: {cpsr[overflow]}".format(cpsr=ArchARM.get_flags(bits)), color.END, sep="") + @Render.divider_printer("[ REGISTERS ]") def context_reg(self, saved_reg_dump): """ @@ -336,8 +239,8 @@ class ContextRenderMIPS(ContextRender, ArchMIPS): context render for MIPS """ - def __init__(self, ql): - super().__init__(ql) + def __init__(self, ql: Qiling, predictor: BranchPredictor): + super().__init__(ql, predictor) ArchMIPS.__init__(self) @Render.divider_printer("[ REGISTERS ]") @@ -357,8 +260,8 @@ class ContextRenderX86(ContextRender, ArchX86): context render for X86 """ - def __init__(self, ql): - super().__init__(ql) + def __init__(self, ql: Qiling, predictor: BranchPredictor): + super().__init__(ql, predictor) ArchX86.__init__(self) @@ -367,7 +270,7 @@ def context_reg(self, saved_reg_dump): cur_regs = self.dump_regs() diff_reg = self.reg_diff(cur_regs, saved_reg_dump) self.render_regs_dump(cur_regs, diff_reg=diff_reg) - print(color.GREEN, "EFLAGS: [CF: {flags[CF]}, PF: {flags[PF]}, AF: {flags[AF]}, ZF: {flags[ZF]}, SF: {flags[SF]}, OF: {flags[OF]}]".format(flags=get_x86_eflags(self.ql.reg.ef)), color.END, sep="") + print(color.GREEN, "EFLAGS: [CF: {flags[CF]}, PF: {flags[PF]}, AF: {flags[AF]}, ZF: {flags[ZF]}, SF: {flags[SF]}, OF: {flags[OF]}]".format(flags=self.get_flags(self.ql.reg.ef)), color.END, sep="") @Render.divider_printer("[ DISASM ]") def context_asm(self): @@ -394,13 +297,13 @@ def context_asm(self): self.print_asm(line) -class ContextRenderCORTEX_M(ContextRender, ArchCORTEX_M): +class ContextRenderCORTEX_M(ContextRenderARM, ArchCORTEX_M): """ context render for cortex_m """ - def __init__(self, ql): - super().__init__(ql) + def __init__(self, ql: Qiling, predictor: BranchPredictor): + super().__init__(ql, predictor) ArchCORTEX_M.__init__(self) self.regs_a_row = 3 @@ -427,4 +330,3 @@ def context_reg(self, saved_reg_dump): if __name__ == "__main__": pass - diff --git a/qiling/debugger/qdb/misc.py b/qiling/debugger/qdb/misc.py index dbffe5828..30e2b8e3d 100644 --- a/qiling/debugger/qdb/misc.py +++ b/qiling/debugger/qdb/misc.py @@ -11,6 +11,21 @@ import unicorn +class Breakpoint(object): + """ + dummy class for breakpoint + """ + def __init__(self, addr): + self.addr = addr + self.hitted = False + +class TempBreakpoint(Breakpoint): + """ + dummy class for temporay breakpoint + """ + def __init__(self, addr): + super().__init__(addr) + def get_terminal_size() -> Iterable: """ @@ -60,32 +75,6 @@ def wrap(qdb, s: str = "") -> int: return wrap -def is_negative(i: int) -> int: - """ - check wether negative value or not - """ - return i & (1 << 31) - - -def signed_val(val: int) -> int: - """ - signed value convertion - """ - return (val-1 << 32) if is_negative(val) else val - - -def get_cpsr(bits: int) -> (bool, bool, bool, bool): - """ - get flags from ql.reg.cpsr - """ - return ( - bits & 0x10000000 != 0, # V, overflow flag - bits & 0x20000000 != 0, # C, carry flag - bits & 0x40000000 != 0, # Z, zero flag - bits & 0x80000000 != 0, # N, sign flag - ) - - def is_thumb(bits: int) -> bool: """ helper function for checking thumb mode @@ -94,21 +83,6 @@ def is_thumb(bits: int) -> bool: return bits & 0x00000020 != 0 -def get_x86_eflags(bits: int) -> Dict[str, bool]: - """ - get flags from ql.reg.ef - """ - - return { - "CF" : bits & 0x0001 != 0, # CF, carry flag - "PF" : bits & 0x0004 != 0, # PF, parity flag - "AF" : bits & 0x0010 != 0, # AF, adjust flag - "ZF" : bits & 0x0040 != 0, # ZF, zero flag - "SF" : bits & 0x0080 != 0, # SF, sign flag - "OF" : bits & 0x0800 != 0, # OF, overflow flag - } - - def disasm(ql: Qiling, address: int, detail: bool = False) -> Optional[int]: """ helper function for disassembling @@ -154,3 +128,6 @@ def read_insn(ql: Qiling, addr: int) -> int: result = ql.mem.read(addr, 15) return result + +if __name__ == "__main__": + pass diff --git a/qiling/debugger/qdb/qdb.py b/qiling/debugger/qdb/qdb.py index ac4601248..088ef2ec2 100644 --- a/qiling/debugger/qdb/qdb.py +++ b/qiling/debugger/qdb/qdb.py @@ -12,10 +12,8 @@ from qiling.const import QL_ARCH, QL_VERBOSE from qiling.debugger import QlDebugger -from .frontend import setup_context_render -from .utils import Breakpoint, TempBreakpoint -from .utils import setup_branch_predictor, SnapshotManager, MemoryManager -from .misc import disasm, parse_int, is_thumb +from .utils import setup_context_render, setup_branch_predictor, SnapshotManager, MemoryManager +from .misc import disasm, parse_int, is_thumb, Breakpoint, TempBreakpoint from .const import color @@ -33,8 +31,8 @@ def __init__(self: QlQdb, ql: Qiling, init_hook: str = "", rr: bool = False) -> self.rr = SnapshotManager(ql) if rr else None self.mm = MemoryManager(ql) - self.render = setup_context_render(ql) self.predictor = setup_branch_predictor(ql) + self.render = setup_context_render(ql, self.predictor) super().__init__() diff --git a/qiling/debugger/qdb/utils.py b/qiling/debugger/qdb/utils.py index 6f21b6b89..a3e2a6ee3 100644 --- a/qiling/debugger/qdb/utils.py +++ b/qiling/debugger/qdb/utils.py @@ -5,498 +5,60 @@ from __future__ import annotations from typing import Callable, Optional, Mapping -import ast, re, math +import math from qiling.const import QL_ARCH -from .misc import try_read, disasm, get_x86_eflags, read_int, signed_val, get_cpsr +from .misc import try_read +from .frontend import ( + ContextRenderX86, + ContextRenderARM, + ContextRenderCORTEX_M, + ContextRenderMIPS + ) + +from .branch_predictor import ( + BranchPredictorX86, + BranchPredictorARM, + BranchPredictorCORTEX_M, + BranchPredictorMIPS, + ) """ - Try to predict certian branch will be taken or not based on current context + helper functions for setting proper branch predictor and context render depending on different arch """ def setup_branch_predictor(ql: Qiling) -> BranchPredictor: """ - setup BranchPredictor for corresponding archtype + setup BranchPredictor correspondingly """ return { - QL_ARCH.X86: BranchPredictor_X86, - QL_ARCH.ARM: BranchPredictor_ARM, - QL_ARCH.ARM_THUMB: BranchPredictor_ARM, - QL_ARCH.CORTEX_M: BranchPredictor_CORTEX_M, - QL_ARCH.MIPS: BranchPredictor_MIPS, + QL_ARCH.X86: BranchPredictorX86, + QL_ARCH.ARM: BranchPredictorARM, + QL_ARCH.ARM_THUMB: BranchPredictorARM, + QL_ARCH.CORTEX_M: BranchPredictorCORTEX_M, + QL_ARCH.MIPS: BranchPredictorMIPS, }.get(ql.archtype)(ql) -class Prophecy(object): - """ - container for storing result of the predictor - @going: indicate the certian branch will be taken or not - @where: where will it go if going is true +def setup_context_render(ql: Qiling, predictor: BranchPredictor) -> ContextRender: """ - - def __init__(self): - self.going = False - self.where = None - - def __iter__(self): - return iter((self.going, self.where)) - -class BranchPredictor(object): + setup context render correspondingly """ - Base class for predictor - """ - - def __init__(self, ql): - self.ql = ql - - def read_reg(self, reg_name): - return getattr(self.ql.reg, reg_name) - - def predict(self) -> Prophecy: - return NotImplementedError - -class BranchPredictor_ARM(BranchPredictor): - """ - predictor for ARM - """ - - def __init__(self, ql): - super().__init__(ql) - - self.INST_SIZE = 4 - self.THUMB_INST_SIZE = 2 - self.CODE_END = "udf" - - def read_reg(self, reg_name): - reg_name = reg_name.replace("ip", "r12").replace("fp", "r11") - return getattr(self.ql.reg, reg_name) - - def regdst_eq_pc(self, op_str): - return op_str.partition(", ")[0] == "pc" - - def predict(self): - prophecy = Prophecy() - cur_addr = self.ql.reg.arch_pc - line = disasm(self.ql, cur_addr) - prophecy.where = cur_addr + line.size - - if line.mnemonic == self.CODE_END: # indicates program exited - return True - - jump_table = { - # unconditional branch - "b" : (lambda *_: True), - "bl" : (lambda *_: True), - "bx" : (lambda *_: True), - "blx" : (lambda *_: True), - "b.w" : (lambda *_: True), - - # branch on equal, Z == 1 - "beq" : (lambda V, C, Z, N: Z == 1), - "bxeq" : (lambda V, C, Z, N: Z == 1), - "beq.w": (lambda V, C, Z, N: Z == 1), - - # branch on not equal, Z == 0 - "bne" : (lambda V, C, Z, N: Z == 0), - "bxne" : (lambda V, C, Z, N: Z == 0), - "bne.w": (lambda V, C, Z, N: Z == 0), - - # branch on signed greater than, Z == 0 and N == V - "bgt" : (lambda V, C, Z, N: (Z == 0 and N == V)), - "bgt.w": (lambda V, C, Z, N: (Z == 0 and N == V)), - - # branch on signed less than, N != V - "blt" : (lambda V, C, Z, N: N != V), - - # branch on signed greater than or equal, N == V - "bge" : (lambda V, C, Z, N: N == V), - - # branch on signed less than or queal - "ble" : (lambda V, C, Z, N: Z == 1 or N != V), - - # branch on unsigned higher or same (or carry set), C == 1 - "bhs" : (lambda V, C, Z, N: C == 1), - "bcs" : (lambda V, C, Z, N: C == 1), - - # branch on unsigned lower (or carry clear), C == 0 - "bcc" : (lambda V, C, Z, N: C == 0), - "blo" : (lambda V, C, Z, N: C == 0), - "bxlo" : (lambda V, C, Z, N: C == 0), - "blo.w": (lambda V, C, Z, N: C == 0), - - # branch on negative or minus, N == 1 - "bmi" : (lambda V, C, Z, N: N == 1), - - # branch on positive or plus, N == 0 - "bpl" : (lambda V, C, Z, N: N == 0), - - # branch on signed overflow - "bvs" : (lambda V, C, Z, N: V == 1), - - # branch on no signed overflow - "bvc" : (lambda V, C, Z, N: V == 0), - - # branch on unsigned higher - "bhi" : (lambda V, C, Z, N: (Z == 0 and C == 1)), - "bxhi" : (lambda V, C, Z, N: (Z == 0 and C == 1)), - "bhi.w": (lambda V, C, Z, N: (Z == 0 and C == 1)), - - # branch on unsigned lower - "bls" : (lambda V, C, Z, N: (C == 0 or Z == 1)), - "bls.w": (lambda V, C, Z, N: (C == 0 or Z == 1)), - } - - cb_table = { - # branch on equal to zero - "cbz" : (lambda r: r == 0), - - # branch on not equal to zero - "cbnz": (lambda r: r != 0), - } - - if line.mnemonic in jump_table: - prophecy.going = jump_table.get(line.mnemonic)(*get_cpsr(self.ql.reg.cpsr)) - - elif line.mnemonic in cb_table: - prophecy.going = cb_table.get(line.mnemonic)(self.read_reg(line.op_str.split(", ")[0])) - - if prophecy.going: - if "#" in line.op_str: - prophecy.where = read_int(line.op_str.split("#")[-1]) - else: - prophecy.where = self.read_reg(line.op_str) - - if self.regdst_eq_pc(line.op_str): - next_addr = cur_addr + line.size - n2_addr = next_addr + len(read_insn(next_addr)) - prophecy.where += len(read_insn(n2_addr)) + len(read_insn(next_addr)) - - elif line.mnemonic.startswith("it"): - # handle IT block here - - cond_met = { - "eq": lambda V, C, Z, N: (Z == 1), - "ne": lambda V, C, Z, N: (Z == 0), - "ge": lambda V, C, Z, N: (N == V), - "hs": lambda V, C, Z, N: (C == 1), - "lo": lambda V, C, Z, N: (C == 0), - "mi": lambda V, C, Z, N: (N == 1), - "pl": lambda V, C, Z, N: (N == 0), - "ls": lambda V, C, Z, N: (C == 0 or Z == 1), - "le": lambda V, C, Z, N: (Z == 1 or N != V), - "hi": lambda V, C, Z, N: (Z == 0 and C == 1), - }.get(line.op_str)(*get_cpsr(ql.reg.cpsr)) - - it_block_range = [each_char for each_char in line.mnemonic[1:]] - - next_addr = cur_addr + self.THUMB_INST_SIZE - for each in it_block_range: - _insn = read_insn(next_addr) - n2_addr = handle_bnj_arm(ql, next_addr) - - if (cond_met and each == "t") or (not cond_met and each == "e"): - if n2_addr != (next_addr+len(_insn)): # branch detected - break - - next_addr += len(_insn) - - prophecy.where = next_addr - - elif line.mnemonic in ("ldr",): - - if self.regdst_eq_pc(line.op_str): - _, _, rn_offset = line.op_str.partition(", ") - r, _, imm = rn_offset.strip("[]!").partition(", #") - - if "]" in rn_offset.split(", ")[1]: # pre-indexed immediate - prophecy.where = ql.unpack32(ql.mem.read(read_int(imm) + self.read_reg(r), self.INST_SIZE)) - - else: # post-indexed immediate - # FIXME: weired behavior, immediate here does not apply - prophecy.where = ql.unpack32(ql.mem.read(self.read_reg(r), self.INST_SIZE)) - - elif line.mnemonic in ("addls", "addne", "add") and self.regdst_eq_pc(line.op_str): - V, C, Z, N = get_cpsr(ql.reg.cpsr) - r0, r1, r2, *imm = line.op_str.split(", ") - - # program counter is awalys 8 bytes ahead when it comes with pc, need to add extra 8 bytes - extra = 8 if 'pc' in (r0, r1, r2) else 0 - - if imm: - expr = imm[0].split() - # TODO: should support more bit shifting and rotating operation - if expr[0] == "lsl": # logical shift left - n = read_int(expr[-1].strip("#")) * 2 - - if line.mnemonic == "addls" and (C == 0 or Z == 1): - prophecy.where = extra + self.read_reg(r1) + self.read_reg(r2) * n - - elif line.mnemonic == "add" or (line.mnemonic == "addne" and Z == 0): - prophecy.where = extra + self.read_reg(r1) + (self.read_reg(r2) * n if imm else self.read_reg(r2)) - - elif line.mnemonic in ("tbh", "tbb"): - - cur_addr += self.INST_SIZE - r0, r1, *imm = line.op_str.strip("[]").split(", ") - - if imm: - expr = imm[0].split() - if expr[0] == "lsl": # logical shift left - n = read_int(expr[-1].strip("#")) * 2 - - if line.mnemonic == "tbh": - - r1 = self.read_reg(r1) * n - elif line.mnemonic == "tbb": - - r1 = self.read_reg(r1) - - to_add = int.from_bytes(ql.mem.read(cur_addr+r1, 2 if line.mnemonic == "tbh" else 1), byteorder="little") * n - prophecy.where = cur_addr + to_add - - elif line.mnemonic.startswith("pop") and "pc" in line.op_str: - - prophecy.where = ql.stack_read(line.op_str.strip("{}").split(", ").index("pc") * self.INST_SIZE) - if not { # step to next instruction if cond does not meet - "pop" : lambda *_: True, - "pop.w": lambda *_: True, - "popeq": lambda V, C, Z, N: (Z == 1), - "popne": lambda V, C, Z, N: (Z == 0), - "pophi": lambda V, C, Z, N: (C == 1), - "popge": lambda V, C, Z, N: (N == V), - "poplt": lambda V, C, Z, N: (N != V), - }.get(line.mnemonic)(*get_cpsr(ql.reg.cpsr)): - - prophecy.where = cur_addr + self.INST_SIZE - - elif line.mnemonic == "sub" and self.regdst_eq_pc(line.op_str): - _, r, imm = line.op_str.split(", ") - prophecy.where = self.read_reg(r) - read_int(imm.strip("#")) - - elif line.mnemonic == "mov" and self.regdst_eq_pc(line.op_str): - _, r = line.op_str.split(", ") - prophecy.where = self.read_reg(r) - - if prophecy.where & 1: - prophecy.where -= 1 - - return prophecy - -class BranchPredictor_MIPS(BranchPredictor): - """ - predictor for MIPS - """ - - def __init__(self, ql): - super().__init__(ql) - self.CODE_END = "break" - self.INST_SIZE = 4 - - def read_reg(self, reg_name): - reg_name = reg_name.strip("$").replace("fp", "s8") - return signed_val(getattr(self.ql.reg, reg_name)) - - def predict(self): - prophecy = Prophecy() - cur_addr = self.ql.reg.arch_pc - line = disasm(self.ql, cur_addr) - - if line.mnemonic == self.CODE_END: # indicates program extied - return True - - prophecy.where = cur_addr + self.INST_SIZE - if line.mnemonic.startswith('j') or line.mnemonic.startswith('b'): - - # make sure at least delay slot executed - prophecy.where += self.INST_SIZE - - # get registers or memory address from op_str - targets = [ - self.read_reg(each) - if '$' in each else read_int(each) - for each in line.op_str.split(", ") - ] - - prophecy.going = { - "j" : (lambda _: True), # unconditional jump - "jr" : (lambda _: True), # unconditional jump - "jal" : (lambda _: True), # unconditional jump - "jalr" : (lambda _: True), # unconditional jump - "b" : (lambda _: True), # unconditional branch - "bl" : (lambda _: True), # unconditional branch - "bal" : (lambda _: True), # unconditional branch - "beq" : (lambda r0, r1, _: r0 == r1), # branch on equal - "bne" : (lambda r0, r1, _: r0 != r1), # branch on not equal - "blt" : (lambda r0, r1, _: r0 < r1), # branch on r0 less than r1 - "bgt" : (lambda r0, r1, _: r0 > r1), # branch on r0 greater than r1 - "ble" : (lambda r0, r1, _: r0 <= r1), # brach on r0 less than or equal to r1 - "bge" : (lambda r0, r1, _: r0 >= r1), # branch on r0 greater than or equal to r1 - "beqz" : (lambda r, _: r == 0), # branch on equal to zero - "bnez" : (lambda r, _: r != 0), # branch on not equal to zero - "bgtz" : (lambda r, _: r > 0), # branch on greater than zero - "bltz" : (lambda r, _: r < 0), # branch on less than zero - "bltzal" : (lambda r, _: r < 0), # branch on less than zero and link - "blez" : (lambda r, _: r <= 0), # branch on less than or equal to zero - "bgez" : (lambda r, _: r >= 0), # branch on greater than or equal to zero - "bgezal" : (lambda r, _: r >= 0), # branch on greater than or equal to zero and link - }.get(line.mnemonic)(*targets) - - if prophecy.going: - # target address is always the rightmost one - prophecy.where = targets[-1] - - return prophecy - -class BranchPredictor_X86(BranchPredictor): - """ - predictor for X86 - """ - - class ParseError(Exception): - """ - indicate parser error - """ - pass - - def __init__(self, ql): - super().__init__(ql) - - def predict(self) -> Prophecy: - prophecy = Prophecy() - cur_addr = self.ql.reg.arch_pc - line = disasm(self.ql, cur_addr) - - jump_table = { - # conditional jump - - "jo" : (lambda C, P, A, Z, S, O: O == 1), - "jno" : (lambda C, P, A, Z, S, O: O == 0), - - "js" : (lambda C, P, A, Z, S, O: S == 1), - "jns" : (lambda C, P, A, Z, S, O: S == 0), - - "je" : (lambda C, P, A, Z, S, O: Z == 1), - "jz" : (lambda C, P, A, Z, S, O: Z == 1), - - "jne" : (lambda C, P, A, Z, S, O: Z == 0), - "jnz" : (lambda C, P, A, Z, S, O: Z == 0), - - "jb" : (lambda C, P, A, Z, S, O: C == 1), - "jc" : (lambda C, P, A, Z, S, O: C == 1), - "jnae" : (lambda C, P, A, Z, S, O: C == 1), - - "jnb" : (lambda C, P, A, Z, S, O: C == 0), - "jnc" : (lambda C, P, A, Z, S, O: C == 0), - "jae" : (lambda C, P, A, Z, S, O: C == 0), - - "jbe" : (lambda C, P, A, Z, S, O: C == 1 or Z == 1), - "jna" : (lambda C, P, A, Z, S, O: C == 1 or Z == 1), - - "ja" : (lambda C, P, A, Z, S, O: C == 0 and Z == 0), - "jnbe" : (lambda C, P, A, Z, S, O: C == 0 and Z == 0), - - "jl" : (lambda C, P, A, Z, S, O: S != O), - "jnge" : (lambda C, P, A, Z, S, O: S != O), - - "jge" : (lambda C, P, A, Z, S, O: S == O), - "jnl" : (lambda C, P, A, Z, S, O: S == O), - - "jle" : (lambda C, P, A, Z, S, O: Z == 1 or S != O), - "jng" : (lambda C, P, A, Z, S, O: Z == 1 or S != O), - - "jg" : (lambda C, P, A, Z, S, O: Z == 0 or S == O), - "jnle" : (lambda C, P, A, Z, S, O: Z == 0 or S == O), - - "jp" : (lambda C, P, A, Z, S, O: P == 1), - "jpe" : (lambda C, P, A, Z, S, O: P == 1), - - "jnp" : (lambda C, P, A, Z, S, O: P == 0), - "jpo" : (lambda C, P, A, Z, S, O: P == 0), - - # unconditional jump - - "call" : (lambda *_: True), - "jmp" : (lambda *_: True), - - } - - jump_reg_table = { - "jcxz" : (lambda cx: cx == 0), - "jecxz" : (lambda ecx: ecx == 0), - "jrcxz" : (lambda rcx: rcx == 0), - } - - if line.mnemonic in jump_table: - eflags = get_x86_eflags(self.ql.reg.ef).values() - prophecy.going = jump_table.get(line.mnemonic)(*eflags) - - elif line.mnemonic in jump_reg_table: - prophecy.going = jump_reg_table.get(line.mnemonic)(self.ql.reg.ecx) - - if prophecy.going: - takeaway_list = ["ptr", "dword", "[", "]"] - class AST_checker(ast.NodeVisitor): - def generic_visit(self, node): - if type(node) in (ast.Module, ast.Expr, ast.BinOp, ast.Constant, ast.Add, ast.Mult, ast.Sub): - ast.NodeVisitor.generic_visit(self, node) - else: - raise ParseError("malform or invalid ast node") - - if len(line.op_str.split()) > 1: - new_line = line.op_str.replace(":", "+") - for each in takeaway_list: - new_line = new_line.replace(each, " ") - - new_line = " ".join(new_line.split()) - for each_reg in filter(lambda r: len(r) == 3, self.ql.reg.register_mapping.keys()): - if each_reg in new_line: - new_line = re.sub(each_reg, hex(self.read_reg(each_reg)), new_line) - - for each_reg in filter(lambda r: len(r) == 2, self.ql.reg.register_mapping.keys()): - if each_reg in new_line: - new_line = re.sub(each_reg, hex(self.read_reg(each_reg)), new_line) - - checker = AST_checker() - ast_tree = ast.parse(new_line) - - checker.visit(ast_tree) - - prophecy.where = eval(new_line) - - elif line.op_str in self.ql.reg.register_mapping: - prophecy.where = getattr(self.ql.reg, line.op_str) - - else: - prophecy.where = read_int(line.op_str) - else: - prophecy.where = cur_addr + line.size - - return prophecy - -class BranchPredictor_CORTEX_M(BranchPredictor_ARM): - def __init__(self, ql): - super().__init__(ql) + return { + QL_ARCH.X86: ContextRenderX86, + QL_ARCH.ARM: ContextRenderARM, + QL_ARCH.ARM_THUMB: ContextRenderARM, + QL_ARCH.CORTEX_M: ContextRenderCORTEX_M, + QL_ARCH.MIPS: ContextRenderMIPS, + }.get(ql.archtype)(ql, predictor) -class Breakpoint(object): - """ - dummy class for breakpoint - """ - def __init__(self, addr): - self.addr = addr - self.hitted = False -class TempBreakpoint(Breakpoint): - """ - dummy class for temporay breakpoint - """ - def __init__(self, addr): - super().__init__(addr) """ From 01651606b8bc2fc22e291ad082d5fdc923dc845c Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Tue, 15 Feb 2022 19:46:58 +0800 Subject: [PATCH 130/406] fix snapshot logic --- qiling/debugger/qdb/qdb.py | 92 +++++++++++++++++++----------------- qiling/debugger/qdb/utils.py | 31 +++++++----- 2 files changed, 68 insertions(+), 55 deletions(-) diff --git a/qiling/debugger/qdb/qdb.py b/qiling/debugger/qdb/qdb.py index 088ef2ec2..3dde28918 100644 --- a/qiling/debugger/qdb/qdb.py +++ b/qiling/debugger/qdb/qdb.py @@ -23,6 +23,10 @@ class QlQdb(cmd.Cmd, QlDebugger): """ def __init__(self: QlQdb, ql: Qiling, init_hook: str = "", rr: bool = False) -> None: + """ + @init_hook: the entry to be paused at + @rr: record/replay debugging + """ self.ql = ql self.prompt = f"{color.BOLD}{color.RED}Qdb> {color.END}" @@ -39,6 +43,9 @@ def __init__(self: QlQdb, ql: Qiling, init_hook: str = "", rr: bool = False) -> self.dbg_hook(init_hook) def dbg_hook(self: QlQdb, init_hook: str): + """ + initial hook to prepare everything we need + """ # self.ql.loader.entry_point # ld.so # self.ql.loader.elf_entry # .text of binary @@ -68,10 +75,6 @@ def bp_handler(ql, address, size, bp_list): self.cur_addr = self.ql.loader.entry_point - if self.rr: - # initial context save for diff snapshot - self.rr.save() - if self.ql.archtype == QL_ARCH.CORTEX_M: self._run() @@ -203,67 +206,67 @@ def do_backward(self: QlQdb, *args) -> None: else: print(f"{color.RED}[!] the option rr not yet been set !!!{color.END}") + def save_reg_dump(func) -> None: + """ + decorator function for saving register dump + """ - def update_reg_dump(self: QlQdb) -> None: + def inner(self, *args, **kwargs): + self._saved_reg_dump = dict(filter(lambda d: isinstance(d[0], str), self.ql.reg.save().items())) + func(self, *args, **kwargs) + + return inner + + def check_ql_alive(func) -> None: """ - internal function for updating registers dump + decorator function for checking ql instance is alive """ - self._saved_reg_dump = dict(filter(lambda d: isinstance(d[0], str), self.ql.reg.save().items())) + def inner(self, *args, **kwargs): + if self.ql is None: + print(f"{color.RED}[!] The program is not being run.{color.END}") + else: + func(self, *args, **kwargs) + return inner + + @SnapshotManager.snapshot + @save_reg_dump + @check_ql_alive def do_step_in(self: QlQdb, *args) -> Optional[bool]: """ execute one instruction at a time, will enter subroutine """ - if self.ql is None: - print(f"{color.RED}[!] The program is not being run.{color.END}") - - else: - self.update_reg_dump() - - prophecy = self.predictor.predict() + prophecy = self.predictor.predict() - if prophecy.where is True: - return True + if prophecy.where is True: + return True - if self.ql.archtype == QL_ARCH.CORTEX_M: - self.ql.arch.step() - else: - self._run(count=1) - - if self.rr: - # context save after execution, so we could do diff - # on two states before and after. - self.rr.save() + if self.ql.archtype == QL_ARCH.CORTEX_M: + self.ql.arch.step() + else: + self._run(count=1) - self.do_context() + self.do_context() + @SnapshotManager.snapshot + @save_reg_dump + @check_ql_alive def do_step_over(self: QlQdb, *args) -> Option[bool]: """ execute one instruction at a time, but WON't enter subroutine """ - if self.ql is None: - print(f"{color.RED}[!] The program is not being run.{color.END}") + prophecy = self.predictor.predict() - else: + if prophecy.going: + cur_insn = disasm(self.ql, self.cur_addr) + self.set_breakpoint(self.cur_addr + cur_insn.size, is_temp=True) - prophecy = self.predictor.predict() - self.update_reg_dump() - - if prophecy.going: - cur_insn = disasm(self.ql, self.cur_addr) - self.set_breakpoint(self.cur_addr + cur_insn.size, is_temp=True) - - else: - self.set_breakpoint(prophecy.where, is_temp=True) - - self._run() + else: + self.set_breakpoint(prophecy.where, is_temp=True) - if self.rr: - # context save after execution, so we could do diff - # on two states before and after. - self.rr.save() + self._run() def set_breakpoint(self: QlQdb, address: int, is_temp: bool = False) -> None: """ @@ -304,6 +307,7 @@ def do_breakpoint(self: QlQdb, address: Optional[int] = 0) -> None: print(f"{color.CYAN}[+] Breakpoint at 0x{address:08x}{color.END}") + @SnapshotManager.snapshot @parse_int def do_continue(self: QlQdb, address: Optional[int] = 0) -> None: """ diff --git a/qiling/debugger/qdb/utils.py b/qiling/debugger/qdb/utils.py index a3e2a6ee3..58a7b00da 100644 --- a/qiling/debugger/qdb/utils.py +++ b/qiling/debugger/qdb/utils.py @@ -158,28 +158,37 @@ def diff_ram(self, prev_ram, cur_ram): return ram - def diff(self, cur_st): + def diff(self, before_st, after_st): """ diff between previous and current state """ - prev_st = self.layers.pop() - diffed_reg = self.diff_reg(prev_st.reg, cur_st.reg) - diffed_ram = self.diff_ram(prev_st.ram, cur_st.ram) + # prev_st = self.layers.pop() + diffed_reg = self.diff_reg(before_st.reg, after_st.reg) + diffed_ram = self.diff_ram(before_st.ram, after_st.ram) + # diffed_reg = self.diff_reg(prev_st.reg, cur_st.reg) + # diffed_ram = self.diff_ram(prev_st.ram, cur_st.ram) return self.DiffedState((diffed_reg, diffed_ram)) - def save(self): + def snapshot(func): """ - helper function for saving differential context + decorator function for saving differential context on certian qdb command """ + def magic(self, *args, **kwargs): + # save State before execution + p_st = self.rr._save() + + # certian execution to be snapshot + func(self, *args, **kwargs) - st = self._save() + # save State after execution + q_st = self.rr._save() - if len(self.layers) > 0 and isinstance(self.layers[-1], self.State): - # merge two context_save to be a diffed state - st = self.diff(st) + # merge two saved States into a DiffedState + st = self.rr.diff(p_st, q_st) + self.rr.layers.append(st) - self.layers.append(st) + return magic def restore(self): """ From 55bde274318bd6baa5e77974dc8116a9579dd804 Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Tue, 15 Feb 2022 19:59:03 +0800 Subject: [PATCH 131/406] adjust function order in qdb.py --- qiling/debugger/qdb/qdb.py | 156 +++++++++++++++++------------------ qiling/debugger/qdb/utils.py | 26 +++--- 2 files changed, 93 insertions(+), 89 deletions(-) diff --git a/qiling/debugger/qdb/qdb.py b/qiling/debugger/qdb/qdb.py index 3dde28918..9baef0b70 100644 --- a/qiling/debugger/qdb/qdb.py +++ b/qiling/debugger/qdb/qdb.py @@ -130,6 +130,29 @@ def _run(self: Qldbg, address: int = 0, end: int = 0, count: int = 0) -> None: self.ql.emu_start(begin=address, end=end, count=count) + def save_reg_dump(func) -> None: + """ + decorator function for saving register dump + """ + + def inner(self, *args, **kwargs): + self._saved_reg_dump = dict(filter(lambda d: isinstance(d[0], str), self.ql.reg.save().items())) + func(self, *args, **kwargs) + + return inner + + def check_ql_alive(func) -> None: + """ + decorator function for checking ql instance is alive + """ + + def inner(self, *args, **kwargs): + if self.ql is None: + print(f"{color.RED}[!] The program is not being run.{color.END}") + else: + func(self, *args, **kwargs) + return inner + def parseline(self: QlQdb, line: str) -> Tuple[Optional[str], Optional[str], str]: """ Parse the line into a command name and a string containing @@ -181,54 +204,6 @@ def do_run(self: QlQdb, *args) -> None: self._run() - def do_context(self: QlQdb, *args) -> None: - """ - display context information for current location - """ - - self.render.context_reg(self._saved_reg_dump) - self.render.context_stack() - self.render.context_asm() - - def do_backward(self: QlQdb, *args) -> None: - """ - step barkward if it's possible, option rr should be enabled and previous instruction must be executed before - """ - - if self.rr: - if len(self.rr.layers) == 0 or not isinstance(self.rr.layers[-1], self.rr.DiffedState): - print(f"{color.RED}[!] there is no way back !!!{color.END}") - - else: - print(f"{color.CYAN}[+] step backward ~{color.END}") - self.rr.restore() - self.do_context() - else: - print(f"{color.RED}[!] the option rr not yet been set !!!{color.END}") - - def save_reg_dump(func) -> None: - """ - decorator function for saving register dump - """ - - def inner(self, *args, **kwargs): - self._saved_reg_dump = dict(filter(lambda d: isinstance(d[0], str), self.ql.reg.save().items())) - func(self, *args, **kwargs) - - return inner - - def check_ql_alive(func) -> None: - """ - decorator function for checking ql instance is alive - """ - - def inner(self, *args, **kwargs): - if self.ql is None: - print(f"{color.RED}[!] The program is not being run.{color.END}") - else: - func(self, *args, **kwargs) - return inner - @SnapshotManager.snapshot @save_reg_dump @check_ql_alive @@ -268,6 +243,36 @@ def do_step_over(self: QlQdb, *args) -> Option[bool]: self._run() + @SnapshotManager.snapshot + @parse_int + def do_continue(self: QlQdb, address: Optional[int] = 0) -> None: + """ + continue execution from current address if not specified + """ + + if address is None: + address = self.cur_addr + + print(f"{color.CYAN}continued from 0x{address:08x}{color.END}") + + self._run(address) + + def do_backward(self: QlQdb, *args) -> None: + """ + step barkward if it's possible, option rr should be enabled and previous instruction must be executed before + """ + + if self.rr: + if len(self.rr.layers) == 0 or not isinstance(self.rr.layers[-1], self.rr.DiffedState): + print(f"{color.RED}[!] there is no way back !!!{color.END}") + + else: + print(f"{color.CYAN}[+] step backward ~{color.END}") + self.rr.restore() + self.do_context() + else: + print(f"{color.RED}[!] the option rr yet been set !!!{color.END}") + def set_breakpoint(self: QlQdb, address: int, is_temp: bool = False) -> None: """ internal function for placing breakpoint @@ -284,16 +289,6 @@ def del_breakpoint(self: QlQdb, bp: Union[Breakpoint, TempBreakpoint]) -> None: self.bp_list.pop(bp.addr, None) - def do_start(self: QlQdb, *args) -> None: - """ - restore qiling instance context to initial state - """ - - if self.ql.archtype != QL_ARCH.CORTEX_M: - - self.ql.restore(self.init_state) - self.do_context() - @parse_int def do_breakpoint(self: QlQdb, address: Optional[int] = 0) -> None: """ @@ -307,19 +302,16 @@ def do_breakpoint(self: QlQdb, address: Optional[int] = 0) -> None: print(f"{color.CYAN}[+] Breakpoint at 0x{address:08x}{color.END}") - @SnapshotManager.snapshot @parse_int - def do_continue(self: QlQdb, address: Optional[int] = 0) -> None: + def do_disassemble(self: QlQdb, address: Optional[int] = 0, *args) -> None: """ - continue execution from current address if not specified + disassemble instructions from address specified """ - if address is None: - address = self.cur_addr - - print(f"{color.CYAN}continued from 0x{address:08x}{color.END}") - - self._run(address) + try: + context_asm(self.ql, address) + except: + print(f"{color.RED}[!] something went wrong ...{color.END}") def do_examine(self: QlQdb, line: str) -> None: """ @@ -335,6 +327,25 @@ def do_examine(self: QlQdb, line: str) -> None: # except: # print(f"{color.RED}[!] something went wrong ...{color.END}") + def do_start(self: QlQdb, *args) -> None: + """ + restore qiling instance context to initial state + """ + + if self.ql.archtype != QL_ARCH.CORTEX_M: + + self.ql.restore(self.init_state) + self.do_context() + + def do_context(self: QlQdb, *args) -> None: + """ + display context information for current location + """ + + self.render.context_reg(self._saved_reg_dump) + self.render.context_stack() + self.render.context_asm() + def do_show(self: QlQdb, *args) -> None: """ show some runtime information @@ -345,17 +356,6 @@ def do_show(self: QlQdb, *args) -> None: if self.rr: print(f"Snapshots: {len([st for st in self.rr.layers if isinstance(st, self.rr.DiffedState)])}") - @parse_int - def do_disassemble(self: QlQdb, address: Optional[int] = 0, *args) -> None: - """ - disassemble instructions from address specified - """ - - try: - context_asm(self.ql, address) - except: - print(f"{color.RED}[!] something went wrong ...{color.END}") - def do_shell(self: QlQdb, *command) -> None: """ run python code diff --git a/qiling/debugger/qdb/utils.py b/qiling/debugger/qdb/utils.py index 58a7b00da..24c29eefa 100644 --- a/qiling/debugger/qdb/utils.py +++ b/qiling/debugger/qdb/utils.py @@ -96,8 +96,8 @@ class DiffedState(object): def __init__(self, diffed_st): self.reg, self.ram = diffed_st - @classmethod - def transform(cls, st): + @staticmethod + def transform(st): """ transform saved context into binary set """ @@ -174,19 +174,23 @@ def snapshot(func): """ decorator function for saving differential context on certian qdb command """ + def magic(self, *args, **kwargs): - # save State before execution - p_st = self.rr._save() + if self.rr: + # save State before execution + p_st = self.rr._save() - # certian execution to be snapshot - func(self, *args, **kwargs) + # certian execution to be snapshot + func(self, *args, **kwargs) - # save State after execution - q_st = self.rr._save() + # save State after execution + q_st = self.rr._save() - # merge two saved States into a DiffedState - st = self.rr.diff(p_st, q_st) - self.rr.layers.append(st) + # merge two saved States into a DiffedState + st = self.rr.diff(p_st, q_st) + self.rr.layers.append(st) + else: + func(self, *args, **kwargs) return magic From 426d47e8035d57bf6a1be59b4a4cee42714b5e4a Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Wed, 16 Feb 2022 03:11:03 +0800 Subject: [PATCH 132/406] massive refactor, generalize frontend/backend api --- qiling/debugger/qdb/arch/__init__.py | 11 + qiling/debugger/qdb/arch/arch.py | 24 + .../qdb/{arch.py => arch/arch_arm.py} | 97 ++-- qiling/debugger/qdb/arch/arch_mips.py | 28 + qiling/debugger/qdb/arch/arch_x86.py | 43 ++ qiling/debugger/qdb/branch_predictor.py | 502 ------------------ .../debugger/qdb/branch_predictor/__init__.py | 11 + .../qdb/branch_predictor/branch_predictor.py | 48 ++ .../branch_predictor/branch_predictor_arm.py | 257 +++++++++ .../branch_predictor/branch_predictor_mips.py | 91 ++++ .../branch_predictor/branch_predictor_x86.py | 140 +++++ qiling/debugger/qdb/context.py | 95 ++++ qiling/debugger/qdb/frontend.py | 332 ------------ qiling/debugger/qdb/misc.py | 91 +--- qiling/debugger/qdb/qdb.py | 4 +- qiling/debugger/qdb/render/__init__.py | 11 + qiling/debugger/qdb/render/render.py | 233 ++++++++ qiling/debugger/qdb/render/render_arm.py | 67 +++ qiling/debugger/qdb/render/render_mips.py | 32 ++ qiling/debugger/qdb/render/render_x86.py | 61 +++ qiling/debugger/qdb/utils.py | 26 +- 21 files changed, 1204 insertions(+), 1000 deletions(-) create mode 100644 qiling/debugger/qdb/arch/__init__.py create mode 100644 qiling/debugger/qdb/arch/arch.py rename qiling/debugger/qdb/{arch.py => arch/arch_arm.py} (52%) create mode 100644 qiling/debugger/qdb/arch/arch_mips.py create mode 100644 qiling/debugger/qdb/arch/arch_x86.py delete mode 100644 qiling/debugger/qdb/branch_predictor.py create mode 100644 qiling/debugger/qdb/branch_predictor/__init__.py create mode 100644 qiling/debugger/qdb/branch_predictor/branch_predictor.py create mode 100644 qiling/debugger/qdb/branch_predictor/branch_predictor_arm.py create mode 100644 qiling/debugger/qdb/branch_predictor/branch_predictor_mips.py create mode 100644 qiling/debugger/qdb/branch_predictor/branch_predictor_x86.py create mode 100644 qiling/debugger/qdb/context.py delete mode 100644 qiling/debugger/qdb/frontend.py create mode 100644 qiling/debugger/qdb/render/__init__.py create mode 100644 qiling/debugger/qdb/render/render.py create mode 100644 qiling/debugger/qdb/render/render_arm.py create mode 100644 qiling/debugger/qdb/render/render_mips.py create mode 100644 qiling/debugger/qdb/render/render_x86.py diff --git a/qiling/debugger/qdb/arch/__init__.py b/qiling/debugger/qdb/arch/__init__.py new file mode 100644 index 000000000..0a4bd3f74 --- /dev/null +++ b/qiling/debugger/qdb/arch/__init__.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from .arch_x86 import ArchX86 +from .arch_mips import ArchMIPS +from .arch_arm import ArchARM, ArchCORTEX_M + +if __name__ == "__main__": + pass diff --git a/qiling/debugger/qdb/arch/arch.py b/qiling/debugger/qdb/arch/arch.py new file mode 100644 index 000000000..4a7e5fd4f --- /dev/null +++ b/qiling/debugger/qdb/arch/arch.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from __future__ import annotations + + +class Arch(object): + """ + base class for arch + """ + def __init__(self: Arch): + pass + + @property + def archtype(self: Arch): + return self.ql.archtype + + def read_insn(self: Arch, address: int , insn_size: int = 4): + return self.read_mem(address, insn_size) + +if __name__ == "__main__": + pass diff --git a/qiling/debugger/qdb/arch.py b/qiling/debugger/qdb/arch/arch_arm.py similarity index 52% rename from qiling/debugger/qdb/arch.py rename to qiling/debugger/qdb/arch/arch_arm.py index f8683d67d..bc10a315e 100644 --- a/qiling/debugger/qdb/arch.py +++ b/qiling/debugger/qdb/arch/arch_arm.py @@ -4,63 +4,12 @@ # from __future__ import annotations -from typing import Callable, Optional, Mapping +from typing import Mapping -from qiling.const import QL_ARCH - - -class ArchX86(): - def __init__(self): - - self.archtype = QL_ARCH.X86 +from .arch import Arch - self.regs = ( - "eax", "ebx", "ecx", "edx", - "esp", "ebp", "esi", "edi", - "eip", "ss", "cs", "ds", "es", - "fs", "gs", "ef", - ) - - @staticmethod - def get_flags(bits: int) -> Mapping[str, bool]: - """ - get flags from ql.reg.ef - """ - - return { - "CF" : bits & 0x0001 != 0, # CF, carry flag - "PF" : bits & 0x0004 != 0, # PF, parity flag - "AF" : bits & 0x0010 != 0, # AF, adjust flag - "ZF" : bits & 0x0040 != 0, # ZF, zero flag - "SF" : bits & 0x0080 != 0, # SF, sign flag - "OF" : bits & 0x0800 != 0, # OF, overflow flag - } - - -class ArchMIPS(object): - def __init__(self): - - self.archtype = QL_ARCH.MIPS - - self.regs = ( - "gp", "at", "v0", "v1", - "a0", "a1", "a2", "a3", - "t0", "t1", "t2", "t3", - "t4", "t5", "t6", "t7", - "t8", "t9", "sp", "s8", - "s0", "s1", "s2", "s3", - "s4", "s5", "s6", "s7", - "ra", "k0", "k1", "pc", - ) - - self.regs_need_swaped = { - "fp": "s8", - } - - -class ArchARM(): - def __init__(self): - self.archtype = QL_ARCH.ARM +class ArchARM(Arch): + def __init__(self: ArchARM): self.regs = ( "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", @@ -75,12 +24,12 @@ def __init__(self): } @staticmethod - def get_flags(bits: int) -> Mapping[str, int]: + def get_flags(bits: int) -> Mapping[str, bool]: """ get flags for ARM """ - def get_mode(bits): + def get_mode(bits: int) -> int: """ get operating mode for ARM """ @@ -107,11 +56,41 @@ def get_mode(bits): "overflow": bits & 0x10000000 != 0, } + @property + def thumb_mode(self: ArchARM) -> bool: + """ + helper function for checking thumb mode + """ + + return self.ql.reg.cpsr & 0x00000020 != 0 + + def read_insn(self, address: int) -> bytes: + """ + read instruction depending on current operating mode + """ + + def thumb_read(address: int) -> bytes: + first_two = self.ql.unpack16(self.read_mem(address, 2)) + result = self.ql.pack16(first_two) + + # to judge it's thumb mode or not + if any([ + first_two & 0xf000 == 0xf000, + first_two & 0xf800 == 0xf800, + first_two & 0xe800 == 0xe800, + ]): + + latter_two = self.ql.unpack16(self.read_mem(address+2, 2)) + result += self.ql.pack16(latter_two) + + return result + + return super().read_insn(address) if not self.thumb_mode else thumb_read(address) + class ArchCORTEX_M(ArchARM): - def __init__(self): + def __init__(self: ArchARM): super().__init__() - self.archtype = QL_ARCH.CORTEX_M self.regs += ("xpsr", "control", "primask", "basepri", "faultmask") if __name__ == "__main__": diff --git a/qiling/debugger/qdb/arch/arch_mips.py b/qiling/debugger/qdb/arch/arch_mips.py new file mode 100644 index 000000000..0bd5d478f --- /dev/null +++ b/qiling/debugger/qdb/arch/arch_mips.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from __future__ import annotations + +from .arch import Arch + +class ArchMIPS(Arch): + def __init__(self: ArchMIPS): + self.regs = ( + "gp", "at", "v0", "v1", + "a0", "a1", "a2", "a3", + "t0", "t1", "t2", "t3", + "t4", "t5", "t6", "t7", + "t8", "t9", "sp", "s8", + "s0", "s1", "s2", "s3", + "s4", "s5", "s6", "s7", + "ra", "k0", "k1", "pc", + ) + + self.regs_need_swaped = { + "fp": "s8", + } + +if __name__ == "__main__": + pass diff --git a/qiling/debugger/qdb/arch/arch_x86.py b/qiling/debugger/qdb/arch/arch_x86.py new file mode 100644 index 000000000..e8a5d0519 --- /dev/null +++ b/qiling/debugger/qdb/arch/arch_x86.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from __future__ import annotations +from typing import Mapping + +from .arch import Arch + +class ArchX86(Arch): + def __init__(self: ArchX86): + self.regs = ( + "eax", "ebx", "ecx", "edx", + "esp", "ebp", "esi", "edi", + "eip", "ss", "cs", "ds", "es", + "fs", "gs", "ef", + ) + + def read_insn(self, address: int) -> bytes: + # due to the variadic lengh of x86 instructions ( 1~15 ) + # always assume the maxium size for disassembler to tell + # what is it exactly. + + return self.read_mem(address, 15) + + @staticmethod + def get_flags(bits: int) -> Mapping[str, bool]: + """ + get flags from ql.reg.ef + """ + + return { + "CF" : bits & 0x0001 != 0, # CF, carry flag + "PF" : bits & 0x0004 != 0, # PF, parity flag + "AF" : bits & 0x0010 != 0, # AF, adjust flag + "ZF" : bits & 0x0040 != 0, # ZF, zero flag + "SF" : bits & 0x0080 != 0, # SF, sign flag + "OF" : bits & 0x0800 != 0, # OF, overflow flag + } + +if __name__ == "__main__": + pass diff --git a/qiling/debugger/qdb/branch_predictor.py b/qiling/debugger/qdb/branch_predictor.py deleted file mode 100644 index fe3b4cd1f..000000000 --- a/qiling/debugger/qdb/branch_predictor.py +++ /dev/null @@ -1,502 +0,0 @@ -#!/usr/bin/env python3 -# -# Cross Platform and Multi Architecture Advanced Binary Emulation Framework -# - -from __future__ import annotations -from typing import Callable, Optional, Mapping - -import ast, re - -from .arch import ArchARM, ArchX86 -from .misc import disasm, read_int - -class BranchPredictor(object): - """ - Base class for predictor - """ - - class Prophecy(object): - """ - container for storing result of the predictor - @going: indicate the certian branch will be taken or not - @where: where will it go if going is true - """ - - def __init__(self): - self.going = False - self.where = None - - def __iter__(self): - return iter((self.going, self.where)) - - def __init__(self, ql): - self.ql = ql - - def read_reg(self, reg_name): - """ - read specific register value - """ - - return getattr(self.ql.reg, reg_name) - - def predict(self) -> Prophecy: - """ - Try to predict certian branch will be taken or not based on current context - """ - - return NotImplementedError - -class BranchPredictorARM(BranchPredictor, ArchARM): - """ - predictor for ARM - """ - - def __init__(self, ql): - super().__init__(ql) - - self.INST_SIZE = 4 - self.THUMB_INST_SIZE = 2 - self.CODE_END = "udf" - - def read_reg(self, reg_name): - reg_name = reg_name.replace("ip", "r12").replace("fp", "r11") - return getattr(self.ql.reg, reg_name) - - def regdst_eq_pc(self, op_str): - return op_str.partition(", ")[0] == "pc" - - @staticmethod - def get_cpsr(bits: int) -> (bool, bool, bool, bool): - """ - get flags from ql.reg.cpsr - """ - return ( - bits & 0x10000000 != 0, # V, overflow flag - bits & 0x20000000 != 0, # C, carry flag - bits & 0x40000000 != 0, # Z, zero flag - bits & 0x80000000 != 0, # N, sign flag - ) - - def predict(self): - prophecy = self.Prophecy() - cur_addr = self.ql.reg.arch_pc - line = disasm(self.ql, cur_addr) - prophecy.where = cur_addr + line.size - - if line.mnemonic == self.CODE_END: # indicates program exited - return True - - jump_table = { - # unconditional branch - "b" : (lambda *_: True), - "bl" : (lambda *_: True), - "bx" : (lambda *_: True), - "blx" : (lambda *_: True), - "b.w" : (lambda *_: True), - - # branch on equal, Z == 1 - "beq" : (lambda V, C, Z, N: Z == 1), - "bxeq" : (lambda V, C, Z, N: Z == 1), - "beq.w": (lambda V, C, Z, N: Z == 1), - - # branch on not equal, Z == 0 - "bne" : (lambda V, C, Z, N: Z == 0), - "bxne" : (lambda V, C, Z, N: Z == 0), - "bne.w": (lambda V, C, Z, N: Z == 0), - - # branch on signed greater than, Z == 0 and N == V - "bgt" : (lambda V, C, Z, N: (Z == 0 and N == V)), - "bgt.w": (lambda V, C, Z, N: (Z == 0 and N == V)), - - # branch on signed less than, N != V - "blt" : (lambda V, C, Z, N: N != V), - - # branch on signed greater than or equal, N == V - "bge" : (lambda V, C, Z, N: N == V), - - # branch on signed less than or queal - "ble" : (lambda V, C, Z, N: Z == 1 or N != V), - - # branch on unsigned higher or same (or carry set), C == 1 - "bhs" : (lambda V, C, Z, N: C == 1), - "bcs" : (lambda V, C, Z, N: C == 1), - - # branch on unsigned lower (or carry clear), C == 0 - "bcc" : (lambda V, C, Z, N: C == 0), - "blo" : (lambda V, C, Z, N: C == 0), - "bxlo" : (lambda V, C, Z, N: C == 0), - "blo.w": (lambda V, C, Z, N: C == 0), - - # branch on negative or minus, N == 1 - "bmi" : (lambda V, C, Z, N: N == 1), - - # branch on positive or plus, N == 0 - "bpl" : (lambda V, C, Z, N: N == 0), - - # branch on signed overflow - "bvs" : (lambda V, C, Z, N: V == 1), - - # branch on no signed overflow - "bvc" : (lambda V, C, Z, N: V == 0), - - # branch on unsigned higher - "bhi" : (lambda V, C, Z, N: (Z == 0 and C == 1)), - "bxhi" : (lambda V, C, Z, N: (Z == 0 and C == 1)), - "bhi.w": (lambda V, C, Z, N: (Z == 0 and C == 1)), - - # branch on unsigned lower - "bls" : (lambda V, C, Z, N: (C == 0 or Z == 1)), - "bls.w": (lambda V, C, Z, N: (C == 0 or Z == 1)), - } - - cb_table = { - # branch on equal to zero - "cbz" : (lambda r: r == 0), - - # branch on not equal to zero - "cbnz": (lambda r: r != 0), - } - - if line.mnemonic in jump_table: - prophecy.going = jump_table.get(line.mnemonic)(*self.get_cpsr(self.ql.reg.cpsr)) - - elif line.mnemonic in cb_table: - prophecy.going = cb_table.get(line.mnemonic)(self.read_reg(line.op_str.split(", ")[0])) - - if prophecy.going: - if "#" in line.op_str: - prophecy.where = read_int(line.op_str.split("#")[-1]) - else: - prophecy.where = self.read_reg(line.op_str) - - if self.regdst_eq_pc(line.op_str): - next_addr = cur_addr + line.size - n2_addr = next_addr + len(read_insn(next_addr)) - prophecy.where += len(read_insn(n2_addr)) + len(read_insn(next_addr)) - - elif line.mnemonic.startswith("it"): - # handle IT block here - - cond_met = { - "eq": lambda V, C, Z, N: (Z == 1), - "ne": lambda V, C, Z, N: (Z == 0), - "ge": lambda V, C, Z, N: (N == V), - "hs": lambda V, C, Z, N: (C == 1), - "lo": lambda V, C, Z, N: (C == 0), - "mi": lambda V, C, Z, N: (N == 1), - "pl": lambda V, C, Z, N: (N == 0), - "ls": lambda V, C, Z, N: (C == 0 or Z == 1), - "le": lambda V, C, Z, N: (Z == 1 or N != V), - "hi": lambda V, C, Z, N: (Z == 0 and C == 1), - }.get(line.op_str)(*self.get_cpsr(ql.reg.cpsr)) - - it_block_range = [each_char for each_char in line.mnemonic[1:]] - - next_addr = cur_addr + self.THUMB_INST_SIZE - for each in it_block_range: - _insn = read_insn(next_addr) - n2_addr = handle_bnj_arm(ql, next_addr) - - if (cond_met and each == "t") or (not cond_met and each == "e"): - if n2_addr != (next_addr+len(_insn)): # branch detected - break - - next_addr += len(_insn) - - prophecy.where = next_addr - - elif line.mnemonic in ("ldr",): - - if self.regdst_eq_pc(line.op_str): - _, _, rn_offset = line.op_str.partition(", ") - r, _, imm = rn_offset.strip("[]!").partition(", #") - - if "]" in rn_offset.split(", ")[1]: # pre-indexed immediate - prophecy.where = ql.unpack32(ql.mem.read(read_int(imm) + self.read_reg(r), self.INST_SIZE)) - - else: # post-indexed immediate - # FIXME: weired behavior, immediate here does not apply - prophecy.where = ql.unpack32(ql.mem.read(self.read_reg(r), self.INST_SIZE)) - - elif line.mnemonic in ("addls", "addne", "add") and self.regdst_eq_pc(line.op_str): - V, C, Z, N = self.get_cpsr(ql.reg.cpsr) - r0, r1, r2, *imm = line.op_str.split(", ") - - # program counter is awalys 8 bytes ahead when it comes with pc, need to add extra 8 bytes - extra = 8 if 'pc' in (r0, r1, r2) else 0 - - if imm: - expr = imm[0].split() - # TODO: should support more bit shifting and rotating operation - if expr[0] == "lsl": # logical shift left - n = read_int(expr[-1].strip("#")) * 2 - - if line.mnemonic == "addls" and (C == 0 or Z == 1): - prophecy.where = extra + self.read_reg(r1) + self.read_reg(r2) * n - - elif line.mnemonic == "add" or (line.mnemonic == "addne" and Z == 0): - prophecy.where = extra + self.read_reg(r1) + (self.read_reg(r2) * n if imm else self.read_reg(r2)) - - elif line.mnemonic in ("tbh", "tbb"): - - cur_addr += self.INST_SIZE - r0, r1, *imm = line.op_str.strip("[]").split(", ") - - if imm: - expr = imm[0].split() - if expr[0] == "lsl": # logical shift left - n = read_int(expr[-1].strip("#")) * 2 - - if line.mnemonic == "tbh": - - r1 = self.read_reg(r1) * n - - elif line.mnemonic == "tbb": - - r1 = self.read_reg(r1) - - to_add = int.from_bytes(ql.mem.read(cur_addr+r1, 2 if line.mnemonic == "tbh" else 1), byteorder="little") * n - prophecy.where = cur_addr + to_add - - elif line.mnemonic.startswith("pop") and "pc" in line.op_str: - - prophecy.where = ql.stack_read(line.op_str.strip("{}").split(", ").index("pc") * self.INST_SIZE) - if not { # step to next instruction if cond does not meet - "pop" : lambda *_: True, - "pop.w": lambda *_: True, - "popeq": lambda V, C, Z, N: (Z == 1), - "popne": lambda V, C, Z, N: (Z == 0), - "pophi": lambda V, C, Z, N: (C == 1), - "popge": lambda V, C, Z, N: (N == V), - "poplt": lambda V, C, Z, N: (N != V), - }.get(line.mnemonic)(*get_cpsr(ql.reg.cpsr)): - - prophecy.where = cur_addr + self.INST_SIZE - - elif line.mnemonic == "sub" and self.regdst_eq_pc(line.op_str): - _, r, imm = line.op_str.split(", ") - prophecy.where = self.read_reg(r) - read_int(imm.strip("#")) - - elif line.mnemonic == "mov" and self.regdst_eq_pc(line.op_str): - _, r = line.op_str.split(", ") - prophecy.where = self.read_reg(r) - - if prophecy.where & 1: - prophecy.where -= 1 - - return prophecy - -class BranchPredictorMIPS(BranchPredictor): - """ - predictor for MIPS - """ - - def __init__(self, ql): - super().__init__(ql) - self.CODE_END = "break" - self.INST_SIZE = 4 - - @staticmethod - def signed_val(val: int) -> int: - """ - signed value convertion - """ - - def is_negative(i: int) -> int: - """ - check wether negative value or not - """ - - return i & (1 << 31) - - return (val-1 << 32) if is_negative(val) else val - - def read_reg(self, reg_name): - reg_name = reg_name.strip("$").replace("fp", "s8") - return signed_val(getattr(self.ql.reg, reg_name)) - - def predict(self): - prophecy = self.Prophecy() - cur_addr = self.ql.reg.arch_pc - line = disasm(self.ql, cur_addr) - - if line.mnemonic == self.CODE_END: # indicates program extied - return True - - prophecy.where = cur_addr + self.INST_SIZE - if line.mnemonic.startswith('j') or line.mnemonic.startswith('b'): - - # make sure at least delay slot executed - prophecy.where += self.INST_SIZE - - # get registers or memory address from op_str - targets = [ - self.read_reg(each) - if '$' in each else read_int(each) - for each in line.op_str.split(", ") - ] - - prophecy.going = { - "j" : (lambda _: True), # unconditional jump - "jr" : (lambda _: True), # unconditional jump - "jal" : (lambda _: True), # unconditional jump - "jalr" : (lambda _: True), # unconditional jump - "b" : (lambda _: True), # unconditional branch - "bl" : (lambda _: True), # unconditional branch - "bal" : (lambda _: True), # unconditional branch - "beq" : (lambda r0, r1, _: r0 == r1), # branch on equal - "bne" : (lambda r0, r1, _: r0 != r1), # branch on not equal - "blt" : (lambda r0, r1, _: r0 < r1), # branch on r0 less than r1 - "bgt" : (lambda r0, r1, _: r0 > r1), # branch on r0 greater than r1 - "ble" : (lambda r0, r1, _: r0 <= r1), # brach on r0 less than or equal to r1 - "bge" : (lambda r0, r1, _: r0 >= r1), # branch on r0 greater than or equal to r1 - "beqz" : (lambda r, _: r == 0), # branch on equal to zero - "bnez" : (lambda r, _: r != 0), # branch on not equal to zero - "bgtz" : (lambda r, _: r > 0), # branch on greater than zero - "bltz" : (lambda r, _: r < 0), # branch on less than zero - "bltzal" : (lambda r, _: r < 0), # branch on less than zero and link - "blez" : (lambda r, _: r <= 0), # branch on less than or equal to zero - "bgez" : (lambda r, _: r >= 0), # branch on greater than or equal to zero - "bgezal" : (lambda r, _: r >= 0), # branch on greater than or equal to zero and link - }.get(line.mnemonic)(*targets) - - if prophecy.going: - # target address is always the rightmost one - prophecy.where = targets[-1] - - return prophecy - -class BranchPredictorX86(BranchPredictor, ArchX86): - """ - predictor for X86 - """ - - class ParseError(Exception): - """ - indicate parser error - """ - pass - - def __init__(self, ql): - super().__init__(ql) - ArchX86.__init__(self) - - def predict(self) -> Prophecy: - prophecy = self.Prophecy() - cur_addr = self.ql.reg.arch_pc - line = disasm(self.ql, cur_addr) - - jump_table = { - # conditional jump - - "jo" : (lambda C, P, A, Z, S, O: O == 1), - "jno" : (lambda C, P, A, Z, S, O: O == 0), - - "js" : (lambda C, P, A, Z, S, O: S == 1), - "jns" : (lambda C, P, A, Z, S, O: S == 0), - - "je" : (lambda C, P, A, Z, S, O: Z == 1), - "jz" : (lambda C, P, A, Z, S, O: Z == 1), - - "jne" : (lambda C, P, A, Z, S, O: Z == 0), - "jnz" : (lambda C, P, A, Z, S, O: Z == 0), - - "jb" : (lambda C, P, A, Z, S, O: C == 1), - "jc" : (lambda C, P, A, Z, S, O: C == 1), - "jnae" : (lambda C, P, A, Z, S, O: C == 1), - - "jnb" : (lambda C, P, A, Z, S, O: C == 0), - "jnc" : (lambda C, P, A, Z, S, O: C == 0), - "jae" : (lambda C, P, A, Z, S, O: C == 0), - - "jbe" : (lambda C, P, A, Z, S, O: C == 1 or Z == 1), - "jna" : (lambda C, P, A, Z, S, O: C == 1 or Z == 1), - - "ja" : (lambda C, P, A, Z, S, O: C == 0 and Z == 0), - "jnbe" : (lambda C, P, A, Z, S, O: C == 0 and Z == 0), - - "jl" : (lambda C, P, A, Z, S, O: S != O), - "jnge" : (lambda C, P, A, Z, S, O: S != O), - - "jge" : (lambda C, P, A, Z, S, O: S == O), - "jnl" : (lambda C, P, A, Z, S, O: S == O), - - "jle" : (lambda C, P, A, Z, S, O: Z == 1 or S != O), - "jng" : (lambda C, P, A, Z, S, O: Z == 1 or S != O), - - "jg" : (lambda C, P, A, Z, S, O: Z == 0 or S == O), - "jnle" : (lambda C, P, A, Z, S, O: Z == 0 or S == O), - - "jp" : (lambda C, P, A, Z, S, O: P == 1), - "jpe" : (lambda C, P, A, Z, S, O: P == 1), - - "jnp" : (lambda C, P, A, Z, S, O: P == 0), - "jpo" : (lambda C, P, A, Z, S, O: P == 0), - - # unconditional jump - - "call" : (lambda *_: True), - "jmp" : (lambda *_: True), - - } - - jump_reg_table = { - "jcxz" : (lambda cx: cx == 0), - "jecxz" : (lambda ecx: ecx == 0), - "jrcxz" : (lambda rcx: rcx == 0), - } - - if line.mnemonic in jump_table: - eflags = self.get_flags(self.ql.reg.ef).values() - prophecy.going = jump_table.get(line.mnemonic)(*eflags) - - elif line.mnemonic in jump_reg_table: - prophecy.going = jump_reg_table.get(line.mnemonic)(self.ql.reg.ecx) - - if prophecy.going: - takeaway_list = ["ptr", "dword", "[", "]"] - class AST_checker(ast.NodeVisitor): - def generic_visit(self, node): - if type(node) in (ast.Module, ast.Expr, ast.BinOp, ast.Constant, ast.Add, ast.Mult, ast.Sub): - ast.NodeVisitor.generic_visit(self, node) - else: - raise ParseError("malform or invalid ast node") - - if len(line.op_str.split()) > 1: - new_line = line.op_str.replace(":", "+") - for each in takeaway_list: - new_line = new_line.replace(each, " ") - - new_line = " ".join(new_line.split()) - for each_reg in filter(lambda r: len(r) == 3, self.ql.reg.register_mapping.keys()): - if each_reg in new_line: - new_line = re.sub(each_reg, hex(self.read_reg(each_reg)), new_line) - - for each_reg in filter(lambda r: len(r) == 2, self.ql.reg.register_mapping.keys()): - if each_reg in new_line: - new_line = re.sub(each_reg, hex(self.read_reg(each_reg)), new_line) - - checker = AST_checker() - ast_tree = ast.parse(new_line) - - checker.visit(ast_tree) - - prophecy.where = eval(new_line) - - elif line.op_str in self.ql.reg.register_mapping: - prophecy.where = getattr(self.ql.reg, line.op_str) - - else: - prophecy.where = read_int(line.op_str) - else: - prophecy.where = cur_addr + line.size - - return prophecy - -class BranchPredictorCORTEX_M(BranchPredictorARM): - def __init__(self, ql): - super().__init__(ql) - -if __name__ == "__main__": - pass diff --git a/qiling/debugger/qdb/branch_predictor/__init__.py b/qiling/debugger/qdb/branch_predictor/__init__.py new file mode 100644 index 000000000..038509c81 --- /dev/null +++ b/qiling/debugger/qdb/branch_predictor/__init__.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from .branch_predictor_x86 import BranchPredictorX86 +from .branch_predictor_mips import BranchPredictorMIPS +from .branch_predictor_arm import BranchPredictorARM, BranchPredictorCORTEX_M + +if __name__ == "__main__": + pass diff --git a/qiling/debugger/qdb/branch_predictor/branch_predictor.py b/qiling/debugger/qdb/branch_predictor/branch_predictor.py new file mode 100644 index 000000000..25f273c13 --- /dev/null +++ b/qiling/debugger/qdb/branch_predictor/branch_predictor.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from __future__ import annotations + +from ..context import Context +from ..misc import read_int + +class BranchPredictor(Context): + """ + Base class for predictor + """ + + class Prophecy(object): + """ + container for storing result of the predictor + @going: indicate the certian branch will be taken or not + @where: where will it go if going is true + """ + + def __init__(self): + self.going = False + self.where = None + + def __iter__(self): + return iter((self.going, self.where)) + + def __init__(self, ql): + super().__init__(ql) + + def read_reg(self, reg_name): + """ + read specific register value + """ + + return getattr(self.ql.reg, reg_name) + + def predict(self) -> Prophecy: + """ + Try to predict certian branch will be taken or not based on current context + """ + + return NotImplementedError + +if __name__ == "__main__": + pass diff --git a/qiling/debugger/qdb/branch_predictor/branch_predictor_arm.py b/qiling/debugger/qdb/branch_predictor/branch_predictor_arm.py new file mode 100644 index 000000000..02cc148be --- /dev/null +++ b/qiling/debugger/qdb/branch_predictor/branch_predictor_arm.py @@ -0,0 +1,257 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from __future__ import annotations + +from .branch_predictor import * +from ..arch import ArchARM, ArchCORTEX_M + +class BranchPredictorARM(BranchPredictor, ArchARM): + """ + predictor for ARM + """ + + def __init__(self, ql): + super().__init__(ql) + + self.INST_SIZE = 4 + self.THUMB_INST_SIZE = 2 + self.CODE_END = "udf" + + def read_reg(self, reg_name): + reg_name = reg_name.replace("ip", "r12").replace("fp", "r11") + return getattr(self.ql.reg, reg_name) + + def regdst_eq_pc(self, op_str): + return op_str.partition(", ")[0] == "pc" + + @staticmethod + def get_cpsr(bits: int) -> (bool, bool, bool, bool): + """ + get flags from ql.reg.cpsr + """ + return ( + bits & 0x10000000 != 0, # V, overflow flag + bits & 0x20000000 != 0, # C, carry flag + bits & 0x40000000 != 0, # Z, zero flag + bits & 0x80000000 != 0, # N, sign flag + ) + + def predict(self): + prophecy = self.Prophecy() + cur_addr = self.cur_addr + line = self.disasm(cur_addr) + + prophecy.where = cur_addr + line.size + + if line.mnemonic == self.CODE_END: # indicates program exited + return True + + jump_table = { + # unconditional branch + "b" : (lambda *_: True), + "bl" : (lambda *_: True), + "bx" : (lambda *_: True), + "blx" : (lambda *_: True), + "b.w" : (lambda *_: True), + + # branch on equal, Z == 1 + "beq" : (lambda V, C, Z, N: Z == 1), + "bxeq" : (lambda V, C, Z, N: Z == 1), + "beq.w": (lambda V, C, Z, N: Z == 1), + + # branch on not equal, Z == 0 + "bne" : (lambda V, C, Z, N: Z == 0), + "bxne" : (lambda V, C, Z, N: Z == 0), + "bne.w": (lambda V, C, Z, N: Z == 0), + + # branch on signed greater than, Z == 0 and N == V + "bgt" : (lambda V, C, Z, N: (Z == 0 and N == V)), + "bgt.w": (lambda V, C, Z, N: (Z == 0 and N == V)), + + # branch on signed less than, N != V + "blt" : (lambda V, C, Z, N: N != V), + + # branch on signed greater than or equal, N == V + "bge" : (lambda V, C, Z, N: N == V), + + # branch on signed less than or queal + "ble" : (lambda V, C, Z, N: Z == 1 or N != V), + + # branch on unsigned higher or same (or carry set), C == 1 + "bhs" : (lambda V, C, Z, N: C == 1), + "bcs" : (lambda V, C, Z, N: C == 1), + + # branch on unsigned lower (or carry clear), C == 0 + "bcc" : (lambda V, C, Z, N: C == 0), + "blo" : (lambda V, C, Z, N: C == 0), + "bxlo" : (lambda V, C, Z, N: C == 0), + "blo.w": (lambda V, C, Z, N: C == 0), + + # branch on negative or minus, N == 1 + "bmi" : (lambda V, C, Z, N: N == 1), + + # branch on positive or plus, N == 0 + "bpl" : (lambda V, C, Z, N: N == 0), + + # branch on signed overflow + "bvs" : (lambda V, C, Z, N: V == 1), + + # branch on no signed overflow + "bvc" : (lambda V, C, Z, N: V == 0), + + # branch on unsigned higher + "bhi" : (lambda V, C, Z, N: (Z == 0 and C == 1)), + "bxhi" : (lambda V, C, Z, N: (Z == 0 and C == 1)), + "bhi.w": (lambda V, C, Z, N: (Z == 0 and C == 1)), + + # branch on unsigned lower + "bls" : (lambda V, C, Z, N: (C == 0 or Z == 1)), + "bls.w": (lambda V, C, Z, N: (C == 0 or Z == 1)), + } + + cb_table = { + # branch on equal to zero + "cbz" : (lambda r: r == 0), + + # branch on not equal to zero + "cbnz": (lambda r: r != 0), + } + + if line.mnemonic in jump_table: + prophecy.going = jump_table.get(line.mnemonic)(*self.get_cpsr(self.ql.reg.cpsr)) + + elif line.mnemonic in cb_table: + prophecy.going = cb_table.get(line.mnemonic)(self.read_reg(line.op_str.split(", ")[0])) + + if prophecy.going: + if "#" in line.op_str: + prophecy.where = read_int(line.op_str.split("#")[-1]) + else: + prophecy.where = self.read_reg(line.op_str) + + if self.regdst_eq_pc(line.op_str): + next_addr = cur_addr + line.size + n2_addr = next_addr + len(self.read_insn(next_addr)) + prophecy.where += len(self.read_insn(n2_addr)) + len(self.read_insn(next_addr)) + + elif line.mnemonic.startswith("it"): + # handle IT block here + + cond_met = { + "eq": lambda V, C, Z, N: (Z == 1), + "ne": lambda V, C, Z, N: (Z == 0), + "ge": lambda V, C, Z, N: (N == V), + "hs": lambda V, C, Z, N: (C == 1), + "lo": lambda V, C, Z, N: (C == 0), + "mi": lambda V, C, Z, N: (N == 1), + "pl": lambda V, C, Z, N: (N == 0), + "ls": lambda V, C, Z, N: (C == 0 or Z == 1), + "le": lambda V, C, Z, N: (Z == 1 or N != V), + "hi": lambda V, C, Z, N: (Z == 0 and C == 1), + }.get(line.op_str)(*self.get_cpsr(self.ql.reg.cpsr)) + + it_block_range = [each_char for each_char in line.mnemonic[1:]] + + next_addr = cur_addr + self.THUMB_INST_SIZE + for each in it_block_range: + _insn = self.read_insn(next_addr) + n2_addr = handle_bnj_arm(ql, next_addr) + + if (cond_met and each == "t") or (not cond_met and each == "e"): + if n2_addr != (next_addr+len(_insn)): # branch detected + break + + next_addr += len(_insn) + + prophecy.where = next_addr + + elif line.mnemonic in ("ldr",): + + if self.regdst_eq_pc(line.op_str): + _, _, rn_offset = line.op_str.partition(", ") + r, _, imm = rn_offset.strip("[]!").partition(", #") + + if "]" in rn_offset.split(", ")[1]: # pre-indexed immediate + prophecy.where = self.unpack32(self.read_mem(read_int(imm) + self.read_reg(r), self.INST_SIZE)) + + else: # post-indexed immediate + # FIXME: weired behavior, immediate here does not apply + prophecy.where = self.unpack32(self.read_mem(self.read_reg(r), self.INST_SIZE)) + + elif line.mnemonic in ("addls", "addne", "add") and self.regdst_eq_pc(line.op_str): + V, C, Z, N = self.get_cpsr(ql.reg.cpsr) + r0, r1, r2, *imm = line.op_str.split(", ") + + # program counter is awalys 8 bytes ahead when it comes with pc, need to add extra 8 bytes + extra = 8 if 'pc' in (r0, r1, r2) else 0 + + if imm: + expr = imm[0].split() + # TODO: should support more bit shifting and rotating operation + if expr[0] == "lsl": # logical shift left + n = read_int(expr[-1].strip("#")) * 2 + + if line.mnemonic == "addls" and (C == 0 or Z == 1): + prophecy.where = extra + self.read_reg(r1) + self.read_reg(r2) * n + + elif line.mnemonic == "add" or (line.mnemonic == "addne" and Z == 0): + prophecy.where = extra + self.read_reg(r1) + (self.read_reg(r2) * n if imm else self.read_reg(r2)) + + elif line.mnemonic in ("tbh", "tbb"): + + cur_addr += self.INST_SIZE + r0, r1, *imm = line.op_str.strip("[]").split(", ") + + if imm: + expr = imm[0].split() + if expr[0] == "lsl": # logical shift left + n = read_int(expr[-1].strip("#")) * 2 + + if line.mnemonic == "tbh": + + r1 = self.read_reg(r1) * n + + elif line.mnemonic == "tbb": + + r1 = self.read_reg(r1) + + to_add = int.from_bytes(self.read_mem(cur_addr+r1, 2 if line.mnemonic == "tbh" else 1), byteorder="little") * n + prophecy.where = cur_addr + to_add + + elif line.mnemonic.startswith("pop") and "pc" in line.op_str: + + prophecy.where = self.ql.stack_read(line.op_str.strip("{}").split(", ").index("pc") * self.INST_SIZE) + if not { # step to next instruction if cond does not meet + "pop" : lambda *_: True, + "pop.w": lambda *_: True, + "popeq": lambda V, C, Z, N: (Z == 1), + "popne": lambda V, C, Z, N: (Z == 0), + "pophi": lambda V, C, Z, N: (C == 1), + "popge": lambda V, C, Z, N: (N == V), + "poplt": lambda V, C, Z, N: (N != V), + }.get(line.mnemonic)(*get_cpsr(self.ql.reg.cpsr)): + + prophecy.where = cur_addr + self.INST_SIZE + + elif line.mnemonic == "sub" and self.regdst_eq_pc(line.op_str): + _, r, imm = line.op_str.split(", ") + prophecy.where = self.read_reg(r) - read_int(imm.strip("#")) + + elif line.mnemonic == "mov" and self.regdst_eq_pc(line.op_str): + _, r = line.op_str.split(", ") + prophecy.where = self.read_reg(r) + + if prophecy.where & 1: + prophecy.where -= 1 + + return prophecy + +class BranchPredictorCORTEX_M(BranchPredictorARM): + def __init__(self, ql): + super().__init__(ql) + +if __name__ == "__main__": + pass diff --git a/qiling/debugger/qdb/branch_predictor/branch_predictor_mips.py b/qiling/debugger/qdb/branch_predictor/branch_predictor_mips.py new file mode 100644 index 000000000..edcbed708 --- /dev/null +++ b/qiling/debugger/qdb/branch_predictor/branch_predictor_mips.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from __future__ import annotations + +from .branch_predictor import * +from ..arch import ArchMIPS + +class BranchPredictorMIPS(BranchPredictor, ArchMIPS): + """ + predictor for MIPS + """ + + def __init__(self, ql): + super().__init__(ql) + self.CODE_END = "break" + self.INST_SIZE = 4 + + @staticmethod + def signed_val(val: int) -> int: + """ + signed value convertion + """ + + def is_negative(i: int) -> int: + """ + check wether negative value or not + """ + + return i & (1 << 31) + + return (val-1 << 32) if is_negative(val) else val + + def read_reg(self, reg_name): + reg_name = reg_name.strip("$").replace("fp", "s8") + return self.signed_val(getattr(self.ql.reg, reg_name)) + + def predict(self): + prophecy = self.Prophecy() + line = self.disasm(self.cur_addr) + + if line.mnemonic == self.CODE_END: # indicates program extied + return True + + prophecy.where = self.cur_addr + self.INST_SIZE + if line.mnemonic.startswith('j') or line.mnemonic.startswith('b'): + + # make sure at least delay slot executed + prophecy.where += self.INST_SIZE + + # get registers or memory address from op_str + targets = [ + self.read_reg(each) + if '$' in each else read_int(each) + for each in line.op_str.split(", ") + ] + + prophecy.going = { + "j" : (lambda _: True), # unconditional jump + "jr" : (lambda _: True), # unconditional jump + "jal" : (lambda _: True), # unconditional jump + "jalr" : (lambda _: True), # unconditional jump + "b" : (lambda _: True), # unconditional branch + "bl" : (lambda _: True), # unconditional branch + "bal" : (lambda _: True), # unconditional branch + "beq" : (lambda r0, r1, _: r0 == r1), # branch on equal + "bne" : (lambda r0, r1, _: r0 != r1), # branch on not equal + "blt" : (lambda r0, r1, _: r0 < r1), # branch on r0 less than r1 + "bgt" : (lambda r0, r1, _: r0 > r1), # branch on r0 greater than r1 + "ble" : (lambda r0, r1, _: r0 <= r1), # brach on r0 less than or equal to r1 + "bge" : (lambda r0, r1, _: r0 >= r1), # branch on r0 greater than or equal to r1 + "beqz" : (lambda r, _: r == 0), # branch on equal to zero + "bnez" : (lambda r, _: r != 0), # branch on not equal to zero + "bgtz" : (lambda r, _: r > 0), # branch on greater than zero + "bltz" : (lambda r, _: r < 0), # branch on less than zero + "bltzal" : (lambda r, _: r < 0), # branch on less than zero and link + "blez" : (lambda r, _: r <= 0), # branch on less than or equal to zero + "bgez" : (lambda r, _: r >= 0), # branch on greater than or equal to zero + "bgezal" : (lambda r, _: r >= 0), # branch on greater than or equal to zero and link + }.get(line.mnemonic)(*targets) + + if prophecy.going: + # target address is always the rightmost one + prophecy.where = targets[-1] + + return prophecy + +if __name__ == "__main__": + pass diff --git a/qiling/debugger/qdb/branch_predictor/branch_predictor_x86.py b/qiling/debugger/qdb/branch_predictor/branch_predictor_x86.py new file mode 100644 index 000000000..c278afd68 --- /dev/null +++ b/qiling/debugger/qdb/branch_predictor/branch_predictor_x86.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from __future__ import annotations + +import ast, re + +from .branch_predictor import * +from ..arch import ArchX86 + +class BranchPredictorX86(BranchPredictor, ArchX86): + """ + predictor for X86 + """ + + class ParseError(Exception): + """ + indicate parser error + """ + pass + + def __init__(self, ql): + super().__init__(ql) + ArchX86.__init__(self) + + def predict(self) -> Prophecy: + prophecy = self.Prophecy() + line = self.disasm(self.cur_addr) + + jump_table = { + # conditional jump + + "jo" : (lambda C, P, A, Z, S, O: O == 1), + "jno" : (lambda C, P, A, Z, S, O: O == 0), + + "js" : (lambda C, P, A, Z, S, O: S == 1), + "jns" : (lambda C, P, A, Z, S, O: S == 0), + + "je" : (lambda C, P, A, Z, S, O: Z == 1), + "jz" : (lambda C, P, A, Z, S, O: Z == 1), + + "jne" : (lambda C, P, A, Z, S, O: Z == 0), + "jnz" : (lambda C, P, A, Z, S, O: Z == 0), + + "jb" : (lambda C, P, A, Z, S, O: C == 1), + "jc" : (lambda C, P, A, Z, S, O: C == 1), + "jnae" : (lambda C, P, A, Z, S, O: C == 1), + + "jnb" : (lambda C, P, A, Z, S, O: C == 0), + "jnc" : (lambda C, P, A, Z, S, O: C == 0), + "jae" : (lambda C, P, A, Z, S, O: C == 0), + + "jbe" : (lambda C, P, A, Z, S, O: C == 1 or Z == 1), + "jna" : (lambda C, P, A, Z, S, O: C == 1 or Z == 1), + + "ja" : (lambda C, P, A, Z, S, O: C == 0 and Z == 0), + "jnbe" : (lambda C, P, A, Z, S, O: C == 0 and Z == 0), + + "jl" : (lambda C, P, A, Z, S, O: S != O), + "jnge" : (lambda C, P, A, Z, S, O: S != O), + + "jge" : (lambda C, P, A, Z, S, O: S == O), + "jnl" : (lambda C, P, A, Z, S, O: S == O), + + "jle" : (lambda C, P, A, Z, S, O: Z == 1 or S != O), + "jng" : (lambda C, P, A, Z, S, O: Z == 1 or S != O), + + "jg" : (lambda C, P, A, Z, S, O: Z == 0 or S == O), + "jnle" : (lambda C, P, A, Z, S, O: Z == 0 or S == O), + + "jp" : (lambda C, P, A, Z, S, O: P == 1), + "jpe" : (lambda C, P, A, Z, S, O: P == 1), + + "jnp" : (lambda C, P, A, Z, S, O: P == 0), + "jpo" : (lambda C, P, A, Z, S, O: P == 0), + + # unconditional jump + + "call" : (lambda *_: True), + "jmp" : (lambda *_: True), + + } + + jump_reg_table = { + "jcxz" : (lambda cx: cx == 0), + "jecxz" : (lambda ecx: ecx == 0), + "jrcxz" : (lambda rcx: rcx == 0), + } + + if line.mnemonic in jump_table: + eflags = self.get_flags(self.ql.reg.ef).values() + prophecy.going = jump_table.get(line.mnemonic)(*eflags) + + elif line.mnemonic in jump_reg_table: + prophecy.going = jump_reg_table.get(line.mnemonic)(self.ql.reg.ecx) + + if prophecy.going: + takeaway_list = ["ptr", "dword", "[", "]"] + class AST_checker(ast.NodeVisitor): + def generic_visit(self, node): + if type(node) in (ast.Module, ast.Expr, ast.BinOp, ast.Constant, ast.Add, ast.Mult, ast.Sub): + ast.NodeVisitor.generic_visit(self, node) + else: + raise ParseError("malform or invalid ast node") + + if len(line.op_str.split()) > 1: + new_line = line.op_str.replace(":", "+") + for each in takeaway_list: + new_line = new_line.replace(each, " ") + + new_line = " ".join(new_line.split()) + for each_reg in filter(lambda r: len(r) == 3, self.ql.reg.register_mapping.keys()): + if each_reg in new_line: + new_line = re.sub(each_reg, hex(self.read_reg(each_reg)), new_line) + + for each_reg in filter(lambda r: len(r) == 2, self.ql.reg.register_mapping.keys()): + if each_reg in new_line: + new_line = re.sub(each_reg, hex(self.read_reg(each_reg)), new_line) + + checker = AST_checker() + ast_tree = ast.parse(new_line) + + checker.visit(ast_tree) + + prophecy.where = eval(new_line) + + elif line.op_str in self.ql.reg.register_mapping: + prophecy.where = getattr(self.ql.reg, line.op_str) + + else: + prophecy.where = read_int(line.op_str) + else: + prophecy.where = self.cur_addr + line.size + + return prophecy + +if __name__ == "__main__": + pass diff --git a/qiling/debugger/qdb/context.py b/qiling/debugger/qdb/context.py new file mode 100644 index 000000000..76005f8d4 --- /dev/null +++ b/qiling/debugger/qdb/context.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from __future__ import annotations +from typing import Optional + +import unicorn + +class Context(object): + """ + base class for accessing context of running qiling instance + """ + + def __init__(self, ql) -> Context: + self.ql = ql + self.unpack = ql.unpack + self.unpack16 = ql.unpack16 + self.unpack32 = ql.unpack32 + + @property + def cur_addr(self): + """ + program counter of qiling instance + """ + + return self.ql.reg.arch_pc + + def read_mem(self, address: int, size: int): + """ + read data from memory of qiling instance + """ + + return self.ql.mem.read(address, size) + + def disasm(self, address: int, detail: bool = False) -> Optional[int]: + """ + helper function for disassembling + """ + + md = self.ql.disassembler + md.detail = detail + + try: + ret = next(md.disasm(self.read_insn(address), address)) + + except StopIteration: + ret = None + + return ret + + def try_read(self, address: int, size: int) -> Optional[bytes]: + """ + try to read data from ql.mem + """ + + result = None + err_msg = "" + try: + result = self.read_mem(address, size) + + except unicorn.unicorn.UcError as err: + if err.errno == 6: # Invalid memory read (UC_ERR_READ_UNMAPPED) + err_msg = f"Can not access memory at address 0x{address:08x}" + + except: + pass + + return (result, err_msg) + + def try_read_pointer(self, address: int) -> Optional[bytes]: + """ + try to read pointer size of data from ql.mem + """ + + return self.try_read(address, self.ql.pointersize) + + def read_string(self, address: int) -> Optional[str]: + """ + read string from memory of qiling instance + """ + + return self.ql.mem.string(address) + + def try_read_string(self, address: int) -> Optional[str]: + """ + try to read string from memory of qiling instance + """ + + s = None + try: + s = self.read_string(address) + except: + pass diff --git a/qiling/debugger/qdb/frontend.py b/qiling/debugger/qdb/frontend.py deleted file mode 100644 index c199dcc96..000000000 --- a/qiling/debugger/qdb/frontend.py +++ /dev/null @@ -1,332 +0,0 @@ -#!/usr/bin/env python3 -# -# Cross Platform and Multi Architecture Advanced Binary Emulation Framework -# - -from __future__ import annotations -from typing import Optional, Mapping, Iterable, Union -import copy - -from .misc import try_read, get_terminal_size, disasm -from .arch import ArchARM, ArchMIPS, ArchCORTEX_M, ArchX86 -from .const import color - - -""" - - Context Render for rendering UI - -""" - -COLORS = (color.DARKCYAN, color.BLUE, color.RED, color.YELLOW, color.GREEN, color.PURPLE, color.CYAN, color.WHITE) - -class Render(object): - """ - base class for rendering related functions - """ - - def divider_printer(field_name, ruler="─"): - """ - decorator function for printing divider and field name - """ - - def decorator(context_dumper): - def wrapper(*args, **kwargs): - height, width = get_terminal_size() - bar = (width - len(field_name)) // 2 - 1 - print(ruler * bar, field_name, ruler * bar) - context_dumper(*args, **kwargs) - if "DISASM" in field_name: - print(ruler * width) - return wrapper - return decorator - - def __init__(self) -> Render: - self.regs_a_row = 4 - - def reg_diff(self, cur_regs, saved_reg_dump): - """ - helper function for highlighting register changed during execution - """ - - if saved_reg_dump: - reg_dump = copy.deepcopy(saved_reg_dump) - if getattr(self, "regs_need_swaped", None): - reg_dump = self.swap_reg_name(reg_dump) - return [k for k in cur_regs if cur_regs[k] != reg_dump[k]] - - def render_regs_dump(self, regs, diff_reg=None): - """ - helper function for redering registers dump - """ - - lines = "" - for idx, r in enumerate(regs, 1): - line = "{}{}: 0x{{:08x}} {}\t".format(COLORS[(idx-1) // self.regs_a_row], r, color.END) - - if diff_reg and r in diff_reg: - line = f"{color.UNDERLINE}{color.BOLD}{line}" - - if idx % self.regs_a_row == 0 and idx != 32: - line += "\n" - - lines += line - - print(lines.format(*regs.values())) - - def swap_reg_name(self, cur_regs: Mapping["str", int], extra_dict=None): - """ - swap register name with more readable register name - """ - - target_items = extra_dict.items() if extra_dict else self.regs_need_swaped.items() - - for old_reg, new_reg in target_items: - cur_regs.update({old_reg: cur_regs.pop(new_reg)}) - - return cur_regs - - def print_asm(self, insn: CsInsn, to_jump: bool = False) -> None: - """ - helper function for printing assembly instructions, indicates where we are and the branch prediction - provided by BranchPredictor - """ - - opcode = "".join(f"{b:02x}" for b in insn.bytes) - trace_line = f"0x{insn.address:08x} │ {opcode:15s} {insn.mnemonic:10} {insn.op_str:35s}" - - cursor = "►" if self.ql.reg.arch_pc == insn.address else " " - - jump_sign = f"{color.RED}✓{color.END}" if to_jump else " " - - print(f"{jump_sign} {cursor} {color.DARKGRAY}{trace_line}{color.END}") - - def context_reg(self, saved_states) -> None: - """ - display context registers - """ - - return NotImplementedError - - @divider_printer("[ STACK ]") - def context_stack(self) -> None: - """ - display context stack - """ - - for idx in range(10): - addr = self.ql.reg.arch_sp + idx * self.ql.pointersize - if (val := try_read(self.ql, addr, self.ql.pointersize)[0]): - print(f"$sp+0x{idx*self.ql.pointersize:02x}│ [0x{addr:08x}] —▸ 0x{self.ql.unpack(val):08x}", end="") - - # try to dereference wether it's a pointer - if (buf := try_read(self.ql, addr, self.ql.pointersize))[0] is not None: - - if (addr := self.ql.unpack(buf[0])): - - # try to dereference again - if (buf := try_read(self.ql, addr, self.ql.pointersize))[0] is not None: - try: - s = self.ql.mem.string(addr) - except: - s = None - - if s and s.isprintable(): - print(f" ◂— {self.ql.mem.string(addr)}", end="") - else: - print(f" ◂— 0x{self.ql.unpack(buf[0]):08x}", end="") - print() - - @divider_printer("[ DISASM ]") - def context_asm(self) -> None: - """ - display context assembly - """ - - # assembly before current location - past_list = [] - cur_addr = self.ql.reg.arch_pc - - line = disasm(self.ql, cur_addr-0x10) - - while line: - if line.address == cur_addr: - break - - addr = line.address + line.size - line = disasm(self.ql, addr) - - if not line: - break - - past_list.append(line) - - # print four insns before current location - for line in past_list[:-1]: - self.print_asm(line) - - # assembly for current location - - cur_insn = disasm(self.ql, cur_addr) - prophecy = self.predictor.predict() - self.print_asm(cur_insn, to_jump=prophecy.going) - - # assembly after current location - - forward_insn_size = cur_insn.size - for _ in range(5): - forward_insn = disasm(self.ql, cur_addr+forward_insn_size) - if forward_insn: - self.print_asm(forward_insn) - forward_insn_size += forward_insn.size - - -class Context(object): - """ - base class for accessing context of running qiling instance - """ - - def __init__(self, ql) -> Context: - self.ql = ql - - def dump_regs(self) -> Mapping[str, int]: - """ - dump all registers - """ - - return {reg_name: getattr(self.ql.reg, reg_name) for reg_name in self.regs} - - -class ContextRender(Context, Render): - """ - base class for context render - """ - - def __init__(self, ql: Qiling, predictor: BranchPredictor): - super().__init__(ql) - Render.__init__(self) - self.predictor = predictor - - -class ContextRenderARM(ContextRender, ArchARM): - """ - context render for ARM - """ - - def __init__(self, ql: Qiling, predictor: BranchPredictor): - super().__init__(ql, predictor) - ArchARM.__init__(self) - - @staticmethod - def print_mode_info(bits): - print(color.GREEN, "[{cpsr[mode]} mode], Thumb: {cpsr[thumb]}, FIQ: {cpsr[fiq]}, IRQ: {cpsr[irq]}, NEG: {cpsr[neg]}, ZERO: {cpsr[zero]}, Carry: {cpsr[carry]}, Overflow: {cpsr[overflow]}".format(cpsr=ArchARM.get_flags(bits)), color.END, sep="") - - @Render.divider_printer("[ REGISTERS ]") - def context_reg(self, saved_reg_dump): - """ - redering context registers - """ - - cur_regs = self.dump_regs() - cur_regs = self.swap_reg_name(cur_regs) - diff_reg = self.reg_diff(cur_regs, saved_reg_dump) - self.render_regs_dump(cur_regs, diff_reg=diff_reg) - self.print_mode_info(self.ql.reg.cpsr) - - -class ContextRenderMIPS(ContextRender, ArchMIPS): - """ - context render for MIPS - """ - - def __init__(self, ql: Qiling, predictor: BranchPredictor): - super().__init__(ql, predictor) - ArchMIPS.__init__(self) - - @Render.divider_printer("[ REGISTERS ]") - def context_reg(self, saved_reg_dump): - """ - redering context registers - """ - - cur_regs = self.dump_regs() - cur_regs = self.swap_reg_name(cur_regs) - diff_reg = self.reg_diff(cur_regs, saved_reg_dump) - self.render_regs_dump(cur_regs, diff_reg=diff_reg) - - -class ContextRenderX86(ContextRender, ArchX86): - """ - context render for X86 - """ - - def __init__(self, ql: Qiling, predictor: BranchPredictor): - super().__init__(ql, predictor) - ArchX86.__init__(self) - - - @Render.divider_printer("[ REGISTERS ]") - def context_reg(self, saved_reg_dump): - cur_regs = self.dump_regs() - diff_reg = self.reg_diff(cur_regs, saved_reg_dump) - self.render_regs_dump(cur_regs, diff_reg=diff_reg) - print(color.GREEN, "EFLAGS: [CF: {flags[CF]}, PF: {flags[PF]}, AF: {flags[AF]}, ZF: {flags[ZF]}, SF: {flags[SF]}, OF: {flags[OF]}]".format(flags=self.get_flags(self.ql.reg.ef)), color.END, sep="") - - @Render.divider_printer("[ DISASM ]") - def context_asm(self): - past_list = [] - cur_addr = self.ql.reg.arch_pc - - cur_insn = disasm(self.ql, cur_addr) - prophecy = self.predictor.predict() - self.print_asm(cur_insn, to_jump=prophecy.going) - - # assembly before current location - - line = disasm(self.ql, cur_addr+cur_insn.size) - acc_size = line.size + cur_insn.size - - while line and len(past_list) != 8: - past_list.append(line) - next_start = cur_addr + acc_size - line = disasm(self.ql, next_start) - acc_size += line.size - - # print four insns before current location - for line in past_list[:-1]: - self.print_asm(line) - - -class ContextRenderCORTEX_M(ContextRenderARM, ArchCORTEX_M): - """ - context render for cortex_m - """ - - def __init__(self, ql: Qiling, predictor: BranchPredictor): - super().__init__(ql, predictor) - ArchCORTEX_M.__init__(self) - self.regs_a_row = 3 - - @Render.divider_printer("[ REGISTERS ]") - def context_reg(self, saved_reg_dump): - cur_regs = self.dump_regs() - cur_regs = self.swap_reg_name(cur_regs) - - # for re-order - extra_dict = { - "xpsr": "xpsr", - "control": "control", - "primask": "primask", - "faultmask": "faultmask", - "basepri": "basepri", - } - - cur_regs = self.swap_reg_name(cur_regs, extra_dict=extra_dict) - diff_reg = self.reg_diff(cur_regs, saved_reg_dump) - self.render_regs_dump(cur_regs, diff_reg=diff_reg) - self.print_mode_info(self.ql.reg.cpsr) - - - -if __name__ == "__main__": - pass diff --git a/qiling/debugger/qdb/misc.py b/qiling/debugger/qdb/misc.py index 30e2b8e3d..7d855861b 100644 --- a/qiling/debugger/qdb/misc.py +++ b/qiling/debugger/qdb/misc.py @@ -4,56 +4,26 @@ # from __future__ import annotations -from typing import Callable, Optional, Mapping -import os - -from qiling.const import QL_ARCH -import unicorn +from typing import Callable, Optional class Breakpoint(object): """ dummy class for breakpoint """ - def __init__(self, addr): + def __init__(self, addr: int): self.addr = addr self.hitted = False + class TempBreakpoint(Breakpoint): """ dummy class for temporay breakpoint """ - def __init__(self, addr): + def __init__(self, addr: int): super().__init__(addr) -def get_terminal_size() -> Iterable: - """ - get terminal window height and width - """ - return map(int, os.popen('stty size', 'r').read().split()) - - -def try_read(ql: Qiling, address: int, size: int) -> Optional[bytes]: - """ - try to read data from ql.mem - """ - - result = None - err_msg = "" - try: - result = ql.mem.read(address, size) - - except unicorn.unicorn.UcError as err: - if err.errno == 6: # Invalid memory read (UC_ERR_READ_UNMAPPED) - err_msg = f"Can not access memory at address 0x{address:08x}" - - except: - pass - - return (result, err_msg) - - def read_int(s: str) -> int: """ parse unsigned integer from string @@ -75,59 +45,6 @@ def wrap(qdb, s: str = "") -> int: return wrap -def is_thumb(bits: int) -> bool: - """ - helper function for checking thumb mode - """ - - return bits & 0x00000020 != 0 - - -def disasm(ql: Qiling, address: int, detail: bool = False) -> Optional[int]: - """ - helper function for disassembling - """ - - md = ql.disassembler - md.detail = detail - try: - ret = next(md.disasm(read_insn(ql, address), address)) - - except StopIteration: - ret = None - - return ret - - -def read_insn(ql: Qiling, addr: int) -> int: - """ - read instruction from running qiling instance - """ - result = ql.mem.read(addr, 4) - - if ql.archtype in (QL_ARCH.ARM, QL_ARCH.ARM_THUMB, QL_ARCH.CORTEX_M): - if is_thumb(ql.reg.cpsr): - - first_two = ql.unpack16(ql.mem.read(addr, 2)) - result = ql.pack16(first_two) - - # to judge whether it's thumb mode or not - if any([ - first_two & 0xf000 == 0xf000, - first_two & 0xf800 == 0xf800, - first_two & 0xe800 == 0xe800, - ]): - - latter_two = ql.unpack16(ql.mem.read(addr+2, 2)) - result += ql.pack16(latter_two) - - elif ql.archtype in (QL_ARCH.X86, QL_ARCH.X8664): - # due to the variadic lengh of x86 instructions ( 1~15 ) - # always assume the maxium size for disassembler to tell - # what is it exactly. - result = ql.mem.read(addr, 15) - - return result if __name__ == "__main__": pass diff --git a/qiling/debugger/qdb/qdb.py b/qiling/debugger/qdb/qdb.py index 9baef0b70..b8e4cc591 100644 --- a/qiling/debugger/qdb/qdb.py +++ b/qiling/debugger/qdb/qdb.py @@ -13,7 +13,7 @@ from qiling.debugger import QlDebugger from .utils import setup_context_render, setup_branch_predictor, SnapshotManager, MemoryManager -from .misc import disasm, parse_int, is_thumb, Breakpoint, TempBreakpoint +from .misc import parse_int, Breakpoint, TempBreakpoint from .const import color @@ -125,7 +125,7 @@ def _run(self: Qldbg, address: int = 0, end: int = 0, count: int = 0) -> None: return - if self.ql.archtype in (QL_ARCH.ARM, QL_ARCH.ARM_THUMB, QL_ARCH.CORTEX_M) and is_thumb(self.ql.reg.cpsr): + if self.ql.archtype in (QL_ARCH.ARM, QL_ARCH.ARM_THUMB, QL_ARCH.CORTEX_M) and self.ql.reg.cpsr & 0x00000020: address |= 1 self.ql.emu_start(begin=address, end=end, count=count) diff --git a/qiling/debugger/qdb/render/__init__.py b/qiling/debugger/qdb/render/__init__.py new file mode 100644 index 000000000..f3249aa5f --- /dev/null +++ b/qiling/debugger/qdb/render/__init__.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from .render_x86 import ContextRenderX86 +from .render_mips import ContextRenderMIPS +from .render_arm import ContextRenderARM, ContextRenderCORTEX_M + +if __name__ == "__main__": + pass diff --git a/qiling/debugger/qdb/render/render.py b/qiling/debugger/qdb/render/render.py new file mode 100644 index 000000000..31b121568 --- /dev/null +++ b/qiling/debugger/qdb/render/render.py @@ -0,0 +1,233 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from __future__ import annotations +from typing import Mapping, Iterable +import os, copy + +from ..context import Context +from ..const import color + + +def get_terminal_size() -> Iterable: + """ + get terminal window height and width + """ + + return map(int, os.popen('stty size', 'r').read().split()) + +""" + + Context Render for rendering UI + +""" + +COLORS = (color.DARKCYAN, color.BLUE, color.RED, color.YELLOW, color.GREEN, color.PURPLE, color.CYAN, color.WHITE) + +class Render(object): + """ + base class for rendering related functions + """ + + def divider_printer(field_name, ruler="─"): + """ + decorator function for printing divider and field name + """ + + def decorator(context_dumper): + def wrapper(*args, **kwargs): + height, width = get_terminal_size() + bar = (width - len(field_name)) // 2 - 1 + print(ruler * bar, field_name, ruler * bar) + context_dumper(*args, **kwargs) + if "DISASM" in field_name: + print(ruler * width) + return wrapper + return decorator + + def __init__(self) -> Render: + self.regs_a_row = 4 + self.stack_num = 10 + self.color = color + + def reg_diff(self, cur_regs, saved_reg_dump): + """ + helper function for highlighting register changed during execution + """ + + if saved_reg_dump: + reg_dump = copy.deepcopy(saved_reg_dump) + if getattr(self, "regs_need_swaped", None): + reg_dump = self.swap_reg_name(reg_dump) + + return [k for k in cur_regs if cur_regs[k] != reg_dump[k]] + + def render_regs_dump(self, regs, diff_reg=None): + """ + helper function for redering registers dump + """ + + lines = "" + for idx, r in enumerate(regs, 1): + line = "{}{}: 0x{{:08x}} {}\t".format(COLORS[(idx-1) // self.regs_a_row], r, color.END) + + if diff_reg and r in diff_reg: + line = f"{color.UNDERLINE}{color.BOLD}{line}" + + if idx % self.regs_a_row == 0 and idx != 32: + line += "\n" + + lines += line + + print(lines.format(*regs.values())) + + def render_stack_dump(self, arch_sp: int, pointer_size: int = 4) -> None: + """ + helper function for redering stack dump + """ + + for idx in range(self.stack_num): + addr = arch_sp + idx * pointer_size + if (val := self.try_read_pointer(addr)[0]): + print(f"$sp+0x{idx*pointer_size:02x}│ [0x{addr:08x}] —▸ 0x{self.unpack(val):08x}", end="") + + # try to dereference wether it's a pointer + if (buf := self.try_read_pointer(addr))[0] is not None: + + if (addr := self.ql.unpack(buf[0])): + + # try to dereference again + if (buf := self.try_read_pointer(addr))[0] is not None: + s = self.try_read_string(addr) + + if s and s.isprintable(): + print(f" ◂— {self.read_string(addr)}", end="") + else: + print(f" ◂— 0x{self.unpack(buf[0]):08x}", end="") + print() + + def render_assembly(self, lines) -> None: + """ + helper function for rendering assembly + """ + + # assembly before current location + if (backward := lines.get("backward", None)): + for line in backward: + self.print_asm(line) + + # assembly for current location + if (cur_insn := lines.get("current", None)): + prophecy = self.predictor.predict() + self.print_asm(cur_insn, to_jump=prophecy.going) + + # assembly after current location + if (forward := lines.get("forward", None)): + for line in forward: + self.print_asm(line) + + def swap_reg_name(self, cur_regs: Mapping["str", int], extra_dict=None): + """ + swap register name with more readable register name + """ + + target_items = extra_dict.items() if extra_dict else self.regs_need_swaped.items() + + for old_reg, new_reg in target_items: + cur_regs.update({old_reg: cur_regs.pop(new_reg)}) + + return cur_regs + + def print_asm(self, insn: CsInsn, to_jump: bool = False) -> None: + """ + helper function for printing assembly instructions, indicates where we are and the branch prediction + provided by BranchPredictor + """ + + opcode = "".join(f"{b:02x}" for b in insn.bytes) + trace_line = f"0x{insn.address:08x} │ {opcode:15s} {insn.mnemonic:10} {insn.op_str:35s}" + + cursor = "►" if self.cur_addr == insn.address else " " + + jump_sign = f"{color.RED}✓{color.END}" if to_jump else " " + + print(f"{jump_sign} {cursor} {color.DARKGRAY}{trace_line}{color.END}") + + +class ContextRender(Context, Render): + """ + base class for context render + """ + + def __init__(self, ql: Qiling, predictor: BranchPredictor): + super().__init__(ql) + Render.__init__(self) + self.predictor = predictor + + def dump_regs(self) -> Mapping[str, int]: + """ + dump all registers + """ + + return {reg_name: getattr(self.ql.reg, reg_name) for reg_name in self.regs} + + @Render.divider_printer("[ STACK ]") + def context_stack(self) -> None: + """ + display context stack dump + """ + + self.render_stack_dump(self.ql.reg.arch_sp) + + @Render.divider_printer("[ REGISTERS ]") + def context_reg(self, saved_states: Mapping["str", int]) -> None: + """ + display context registers dump + """ + + return NotImplementedError + + @Render.divider_printer("[ DISASM ]") + def context_asm(self) -> None: + """ + read context assembly and render with render_assembly + """ + + lines = {} + past_list = [] + from_addr = self.cur_addr - 0x10 + to_addr = self.cur_addr + 0x10 + + cur_addr = from_addr + while cur_addr != to_addr: + line = self.disasm(cur_addr) + past_list.append(line) + cur_addr += line.size + + bk_list = [] + fd_list = [] + cur_insn = None + for each in past_list: + if each.address < self.cur_addr: + bk_list.append(each) + + elif each.address > self.cur_addr: + fd_list.append(each) + + elif each.address == self.cur_addr: + cur_insn = each + + lines.update({ + "backward": bk_list, + "forward": fd_list, + "current": cur_insn, + }) + + self.render_assembly(lines) + + + +if __name__ == "__main__": + pass diff --git a/qiling/debugger/qdb/render/render_arm.py b/qiling/debugger/qdb/render/render_arm.py new file mode 100644 index 000000000..8b565ef48 --- /dev/null +++ b/qiling/debugger/qdb/render/render_arm.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from __future__ import annotations + +from .render import * +from ..arch import ArchARM, ArchCORTEX_M + +class ContextRenderARM(ContextRender, ArchARM): + """ + context render for ARM + """ + + def __init__(self, ql: Qiling, predictor: BranchPredictor): + super().__init__(ql, predictor) + ArchARM.__init__(self) + + @staticmethod + def print_mode_info(bits): + print(color.GREEN, "[{cpsr[mode]} mode], Thumb: {cpsr[thumb]}, FIQ: {cpsr[fiq]}, IRQ: {cpsr[irq]}, NEG: {cpsr[neg]}, ZERO: {cpsr[zero]}, Carry: {cpsr[carry]}, Overflow: {cpsr[overflow]}".format(cpsr=ArchARM.get_flags(bits)), color.END, sep="") + + @Render.divider_printer("[ REGISTERS ]") + def context_reg(self, saved_reg_dump): + """ + redering context registers + """ + + cur_regs = self.dump_regs() + cur_regs = self.swap_reg_name(cur_regs) + diff_reg = self.reg_diff(cur_regs, saved_reg_dump) + self.render_regs_dump(cur_regs, diff_reg=diff_reg) + self.print_mode_info(self.ql.reg.cpsr) + + +class ContextRenderCORTEX_M(ContextRenderARM, ArchCORTEX_M): + """ + context render for cortex_m + """ + + def __init__(self, ql: Qiling, predictor: BranchPredictor): + super().__init__(ql, predictor) + ArchCORTEX_M.__init__(self) + self.regs_a_row = 3 + + @Render.divider_printer("[ REGISTERS ]") + def context_reg(self, saved_reg_dump): + cur_regs = self.dump_regs() + cur_regs = self.swap_reg_name(cur_regs) + + # for re-order + extra_dict = { + "xpsr": "xpsr", + "control": "control", + "primask": "primask", + "faultmask": "faultmask", + "basepri": "basepri", + } + + cur_regs = self.swap_reg_name(cur_regs, extra_dict=extra_dict) + diff_reg = self.reg_diff(cur_regs, saved_reg_dump) + self.render_regs_dump(cur_regs, diff_reg=diff_reg) + self.print_mode_info(self.ql.reg.cpsr) + +if __name__ == "__main__": + pass diff --git a/qiling/debugger/qdb/render/render_mips.py b/qiling/debugger/qdb/render/render_mips.py new file mode 100644 index 000000000..6518c03eb --- /dev/null +++ b/qiling/debugger/qdb/render/render_mips.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from __future__ import annotations + +from .render import * +from ..arch import ArchMIPS + +class ContextRenderMIPS(ContextRender, ArchMIPS): + """ + context render for MIPS + """ + + def __init__(self, ql: Qiling, predictor: BranchPredictor): + super().__init__(ql, predictor) + ArchMIPS.__init__(self) + + @Render.divider_printer("[ REGISTERS ]") + def context_reg(self, saved_reg_dump): + """ + redering context registers + """ + + cur_regs = self.dump_regs() + cur_regs = self.swap_reg_name(cur_regs) + diff_reg = self.reg_diff(cur_regs, saved_reg_dump) + self.render_regs_dump(cur_regs, diff_reg=diff_reg) + +if __name__ == "__main__": + pass diff --git a/qiling/debugger/qdb/render/render_x86.py b/qiling/debugger/qdb/render/render_x86.py new file mode 100644 index 000000000..f041253b9 --- /dev/null +++ b/qiling/debugger/qdb/render/render_x86.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from __future__ import annotations + +from .render import * +from ..arch import ArchX86 + +class ContextRenderX86(ContextRender, ArchX86): + """ + context render for X86 + """ + + def __init__(self, ql: Qiling, predictor: BranchPredictor): + super().__init__(ql, predictor) + ArchX86.__init__(self) + + @Render.divider_printer("[ REGISTERS ]") + def context_reg(self, saved_reg_dump): + cur_regs = self.dump_regs() + diff_reg = self.reg_diff(cur_regs, saved_reg_dump) + self.render_regs_dump(cur_regs, diff_reg=diff_reg) + print(color.GREEN, "EFLAGS: [CF: {flags[CF]}, PF: {flags[PF]}, AF: {flags[AF]}, ZF: {flags[ZF]}, SF: {flags[SF]}, OF: {flags[OF]}]".format(flags=self.get_flags(self.ql.reg.ef)), color.END, sep="") + + @Render.divider_printer("[ DISASM ]") + def context_asm(self): + lines = {} + past_list = [] + + cur_addr = self.cur_addr + while len(past_list) < 10: + line = self.disasm(cur_addr) + past_list.append(line) + cur_addr += line.size + + fd_list = [] + cur_insn = None + for each in past_list: + if each.address > self.cur_addr: + fd_list.append(each) + + elif each.address == self.cur_addr: + cur_insn = each + + """ + only forward and current instruction will be printed, + because we don't have a solid method to disasm backward instructions, + since it's x86 instruction length is variadic + """ + + lines.update({ + "current": cur_insn, + "forward": fd_list, + }) + + self.render_assembly(lines) + +if __name__ == "__main__": + pass diff --git a/qiling/debugger/qdb/utils.py b/qiling/debugger/qdb/utils.py index 24c29eefa..f7b9b4c49 100644 --- a/qiling/debugger/qdb/utils.py +++ b/qiling/debugger/qdb/utils.py @@ -9,9 +9,9 @@ from qiling.const import QL_ARCH -from .misc import try_read +from .context import Context -from .frontend import ( +from .render import ( ContextRenderX86, ContextRenderARM, ContextRenderCORTEX_M, @@ -59,7 +59,6 @@ def setup_context_render(ql: Qiling, predictor: BranchPredictor) -> ContextRende }.get(ql.archtype)(ql, predictor) - """ For supporting Qdb features like: @@ -68,14 +67,7 @@ def setup_context_render(ql: Qiling, predictor: BranchPredictor) -> ContextRende """ -class Manager(object): - """ - base class for Manager - """ - def __init__(self, ql): - self.ql = ql - -class SnapshotManager(Manager): +class SnapshotManager(object): """ for functioning differential snapshot """ @@ -226,7 +218,7 @@ def restore(self): self.ql.restore(to_be_restored) -class MemoryManager(Manager): +class MemoryManager(Context): """ memory manager for handing memory access """ @@ -261,7 +253,6 @@ def extract_count(self, t): return "".join([s for s in t if s.isdigit()]) def get_fmt(self, text): - f, s, c = self.DEFAULT_FMT if self.extract_count(text): c = int(self.extract_count(text)) @@ -275,7 +266,7 @@ def get_fmt(self, text): return (f, s, c) - def unpack(self, bs: bytes, sz: int) -> int: + def fmt_unpack(self, bs: bytes, sz: int) -> int: return { 1: lambda x: x[0], 2: self.ql.unpack16, @@ -296,7 +287,7 @@ def parse(self, line: str): elif len(args) == 1: # only address rest = args[0] - fmt = DEFAULT_FMT + fmt = self.DEFAULT_FMT else: rest = args @@ -307,7 +298,6 @@ def parse(self, line: str): elif self.ql.archtype == QL_ARCH.MIPS: rest = rest.replace("fp", "s8") - # for supporting addition of register with constant value elems = rest.split("+") elems = [elem.strip("$") for elem in elems] @@ -340,7 +330,7 @@ def parse(self, line: str): mem_read = [] for offset in range(ct): # append data if read successfully, otherwise return error message - if (data := try_read(self.ql, addr+(offset*sz), sz))[0] is not None: + if (data := self.try_read(addr+(offset*sz), sz))[0] is not None: mem_read.append(data[0]) else: @@ -352,7 +342,7 @@ def parse(self, line: str): idx = line * self.ql.pointersize for each in mem_read[idx:idx+self.ql.pointersize]: - data = self.unpack(each, sz) + data = self.fmt_unpack(each, sz) prefix = "0x" if ft in ("x", "a") else "" pad = '0' + str(sz*2) if ft in ('x', 'a', 't') else '' ft = ft.lower() if ft in ("x", "o", "b", "d") else ft.lower().replace("t", "b").replace("a", "x") From 599c100d1ef58dac998fa7af2e38338041f22638 Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Tue, 15 Feb 2022 23:33:53 +0000 Subject: [PATCH 133/406] remove unneeded import and __name__ guard --- qiling/debugger/qdb/arch/__init__.py | 3 --- qiling/debugger/qdb/arch/arch.py | 4 ---- qiling/debugger/qdb/arch/arch_arm.py | 4 ---- qiling/debugger/qdb/arch/arch_mips.py | 5 +---- qiling/debugger/qdb/arch/arch_x86.py | 4 ---- qiling/debugger/qdb/branch_predictor/__init__.py | 3 --- qiling/debugger/qdb/branch_predictor/branch_predictor.py | 5 +---- .../debugger/qdb/branch_predictor/branch_predictor_arm.py | 5 +---- .../qdb/branch_predictor/branch_predictor_mips.py | 5 +---- .../debugger/qdb/branch_predictor/branch_predictor_x86.py | 5 +---- qiling/debugger/qdb/context.py | 4 +++- qiling/debugger/qdb/misc.py | 1 - qiling/debugger/qdb/qdb.py | 1 - qiling/debugger/qdb/render/__init__.py | 3 --- qiling/debugger/qdb/render/render.py | 8 ++------ qiling/debugger/qdb/render/render_arm.py | 5 +---- qiling/debugger/qdb/render/render_mips.py | 7 ++----- qiling/debugger/qdb/render/render_x86.py | 5 +---- qiling/debugger/qdb/utils.py | 1 - 19 files changed, 14 insertions(+), 64 deletions(-) diff --git a/qiling/debugger/qdb/arch/__init__.py b/qiling/debugger/qdb/arch/__init__.py index 0a4bd3f74..24794a4cb 100644 --- a/qiling/debugger/qdb/arch/__init__.py +++ b/qiling/debugger/qdb/arch/__init__.py @@ -6,6 +6,3 @@ from .arch_x86 import ArchX86 from .arch_mips import ArchMIPS from .arch_arm import ArchARM, ArchCORTEX_M - -if __name__ == "__main__": - pass diff --git a/qiling/debugger/qdb/arch/arch.py b/qiling/debugger/qdb/arch/arch.py index 4a7e5fd4f..3e02bb3ed 100644 --- a/qiling/debugger/qdb/arch/arch.py +++ b/qiling/debugger/qdb/arch/arch.py @@ -3,7 +3,6 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from __future__ import annotations class Arch(object): @@ -19,6 +18,3 @@ def archtype(self: Arch): def read_insn(self: Arch, address: int , insn_size: int = 4): return self.read_mem(address, insn_size) - -if __name__ == "__main__": - pass diff --git a/qiling/debugger/qdb/arch/arch_arm.py b/qiling/debugger/qdb/arch/arch_arm.py index bc10a315e..75b4d3b02 100644 --- a/qiling/debugger/qdb/arch/arch_arm.py +++ b/qiling/debugger/qdb/arch/arch_arm.py @@ -3,7 +3,6 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from __future__ import annotations from typing import Mapping from .arch import Arch @@ -92,6 +91,3 @@ class ArchCORTEX_M(ArchARM): def __init__(self: ArchARM): super().__init__() self.regs += ("xpsr", "control", "primask", "basepri", "faultmask") - -if __name__ == "__main__": - pass diff --git a/qiling/debugger/qdb/arch/arch_mips.py b/qiling/debugger/qdb/arch/arch_mips.py index 0bd5d478f..cf00b16b7 100644 --- a/qiling/debugger/qdb/arch/arch_mips.py +++ b/qiling/debugger/qdb/arch/arch_mips.py @@ -3,7 +3,7 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from __future__ import annotations + from .arch import Arch @@ -23,6 +23,3 @@ def __init__(self: ArchMIPS): self.regs_need_swaped = { "fp": "s8", } - -if __name__ == "__main__": - pass diff --git a/qiling/debugger/qdb/arch/arch_x86.py b/qiling/debugger/qdb/arch/arch_x86.py index e8a5d0519..dc10f16cb 100644 --- a/qiling/debugger/qdb/arch/arch_x86.py +++ b/qiling/debugger/qdb/arch/arch_x86.py @@ -3,7 +3,6 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from __future__ import annotations from typing import Mapping from .arch import Arch @@ -38,6 +37,3 @@ def get_flags(bits: int) -> Mapping[str, bool]: "SF" : bits & 0x0080 != 0, # SF, sign flag "OF" : bits & 0x0800 != 0, # OF, overflow flag } - -if __name__ == "__main__": - pass diff --git a/qiling/debugger/qdb/branch_predictor/__init__.py b/qiling/debugger/qdb/branch_predictor/__init__.py index 038509c81..67c0578fa 100644 --- a/qiling/debugger/qdb/branch_predictor/__init__.py +++ b/qiling/debugger/qdb/branch_predictor/__init__.py @@ -6,6 +6,3 @@ from .branch_predictor_x86 import BranchPredictorX86 from .branch_predictor_mips import BranchPredictorMIPS from .branch_predictor_arm import BranchPredictorARM, BranchPredictorCORTEX_M - -if __name__ == "__main__": - pass diff --git a/qiling/debugger/qdb/branch_predictor/branch_predictor.py b/qiling/debugger/qdb/branch_predictor/branch_predictor.py index 25f273c13..94b438a14 100644 --- a/qiling/debugger/qdb/branch_predictor/branch_predictor.py +++ b/qiling/debugger/qdb/branch_predictor/branch_predictor.py @@ -3,7 +3,7 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from __future__ import annotations + from ..context import Context from ..misc import read_int @@ -43,6 +43,3 @@ def predict(self) -> Prophecy: """ return NotImplementedError - -if __name__ == "__main__": - pass diff --git a/qiling/debugger/qdb/branch_predictor/branch_predictor_arm.py b/qiling/debugger/qdb/branch_predictor/branch_predictor_arm.py index 02cc148be..69b3135b9 100644 --- a/qiling/debugger/qdb/branch_predictor/branch_predictor_arm.py +++ b/qiling/debugger/qdb/branch_predictor/branch_predictor_arm.py @@ -3,7 +3,7 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from __future__ import annotations + from .branch_predictor import * from ..arch import ArchARM, ArchCORTEX_M @@ -252,6 +252,3 @@ def predict(self): class BranchPredictorCORTEX_M(BranchPredictorARM): def __init__(self, ql): super().__init__(ql) - -if __name__ == "__main__": - pass diff --git a/qiling/debugger/qdb/branch_predictor/branch_predictor_mips.py b/qiling/debugger/qdb/branch_predictor/branch_predictor_mips.py index edcbed708..09f6bc687 100644 --- a/qiling/debugger/qdb/branch_predictor/branch_predictor_mips.py +++ b/qiling/debugger/qdb/branch_predictor/branch_predictor_mips.py @@ -3,7 +3,7 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from __future__ import annotations + from .branch_predictor import * from ..arch import ArchMIPS @@ -86,6 +86,3 @@ def predict(self): prophecy.where = targets[-1] return prophecy - -if __name__ == "__main__": - pass diff --git a/qiling/debugger/qdb/branch_predictor/branch_predictor_x86.py b/qiling/debugger/qdb/branch_predictor/branch_predictor_x86.py index c278afd68..487bad287 100644 --- a/qiling/debugger/qdb/branch_predictor/branch_predictor_x86.py +++ b/qiling/debugger/qdb/branch_predictor/branch_predictor_x86.py @@ -3,7 +3,7 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from __future__ import annotations + import ast, re @@ -135,6 +135,3 @@ def generic_visit(self, node): prophecy.where = self.cur_addr + line.size return prophecy - -if __name__ == "__main__": - pass diff --git a/qiling/debugger/qdb/context.py b/qiling/debugger/qdb/context.py index 76005f8d4..38c3b96aa 100644 --- a/qiling/debugger/qdb/context.py +++ b/qiling/debugger/qdb/context.py @@ -3,7 +3,6 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from __future__ import annotations from typing import Optional import unicorn @@ -93,3 +92,6 @@ def try_read_string(self, address: int) -> Optional[str]: s = self.read_string(address) except: pass + +if __name__ == "__main__": + pass diff --git a/qiling/debugger/qdb/misc.py b/qiling/debugger/qdb/misc.py index 7d855861b..449163cdf 100644 --- a/qiling/debugger/qdb/misc.py +++ b/qiling/debugger/qdb/misc.py @@ -3,7 +3,6 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from __future__ import annotations from typing import Callable, Optional diff --git a/qiling/debugger/qdb/qdb.py b/qiling/debugger/qdb/qdb.py index b8e4cc591..805523f4b 100644 --- a/qiling/debugger/qdb/qdb.py +++ b/qiling/debugger/qdb/qdb.py @@ -3,7 +3,6 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from __future__ import annotations from typing import Callable, Optional, Mapping, Tuple, Union import cmd diff --git a/qiling/debugger/qdb/render/__init__.py b/qiling/debugger/qdb/render/__init__.py index f3249aa5f..d36dfa9ae 100644 --- a/qiling/debugger/qdb/render/__init__.py +++ b/qiling/debugger/qdb/render/__init__.py @@ -6,6 +6,3 @@ from .render_x86 import ContextRenderX86 from .render_mips import ContextRenderMIPS from .render_arm import ContextRenderARM, ContextRenderCORTEX_M - -if __name__ == "__main__": - pass diff --git a/qiling/debugger/qdb/render/render.py b/qiling/debugger/qdb/render/render.py index 31b121568..88ff841d6 100644 --- a/qiling/debugger/qdb/render/render.py +++ b/qiling/debugger/qdb/render/render.py @@ -3,7 +3,8 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from __future__ import annotations + + from typing import Mapping, Iterable import os, copy @@ -226,8 +227,3 @@ def context_asm(self) -> None: }) self.render_assembly(lines) - - - -if __name__ == "__main__": - pass diff --git a/qiling/debugger/qdb/render/render_arm.py b/qiling/debugger/qdb/render/render_arm.py index 8b565ef48..8d47fd737 100644 --- a/qiling/debugger/qdb/render/render_arm.py +++ b/qiling/debugger/qdb/render/render_arm.py @@ -3,7 +3,7 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from __future__ import annotations + from .render import * from ..arch import ArchARM, ArchCORTEX_M @@ -62,6 +62,3 @@ def context_reg(self, saved_reg_dump): diff_reg = self.reg_diff(cur_regs, saved_reg_dump) self.render_regs_dump(cur_regs, diff_reg=diff_reg) self.print_mode_info(self.ql.reg.cpsr) - -if __name__ == "__main__": - pass diff --git a/qiling/debugger/qdb/render/render_mips.py b/qiling/debugger/qdb/render/render_mips.py index 6518c03eb..aa177824e 100644 --- a/qiling/debugger/qdb/render/render_mips.py +++ b/qiling/debugger/qdb/render/render_mips.py @@ -3,7 +3,7 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from __future__ import annotations + from .render import * from ..arch import ArchMIPS @@ -19,7 +19,7 @@ def __init__(self, ql: Qiling, predictor: BranchPredictor): @Render.divider_printer("[ REGISTERS ]") def context_reg(self, saved_reg_dump): - """ +t """ redering context registers """ @@ -27,6 +27,3 @@ def context_reg(self, saved_reg_dump): cur_regs = self.swap_reg_name(cur_regs) diff_reg = self.reg_diff(cur_regs, saved_reg_dump) self.render_regs_dump(cur_regs, diff_reg=diff_reg) - -if __name__ == "__main__": - pass diff --git a/qiling/debugger/qdb/render/render_x86.py b/qiling/debugger/qdb/render/render_x86.py index f041253b9..f00f2a9c5 100644 --- a/qiling/debugger/qdb/render/render_x86.py +++ b/qiling/debugger/qdb/render/render_x86.py @@ -3,7 +3,7 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from __future__ import annotations + from .render import * from ..arch import ArchX86 @@ -56,6 +56,3 @@ def context_asm(self): }) self.render_assembly(lines) - -if __name__ == "__main__": - pass diff --git a/qiling/debugger/qdb/utils.py b/qiling/debugger/qdb/utils.py index f7b9b4c49..7cfd24c1a 100644 --- a/qiling/debugger/qdb/utils.py +++ b/qiling/debugger/qdb/utils.py @@ -3,7 +3,6 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from __future__ import annotations from typing import Callable, Optional, Mapping import math From af8f751abd18cbbe75bc9ec6fb12faa37817f5c3 Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Tue, 15 Feb 2022 23:45:52 +0000 Subject: [PATCH 134/406] remove some annotations --- qiling/debugger/qdb/arch/arch.py | 8 +-- qiling/debugger/qdb/arch/arch_arm.py | 6 +-- qiling/debugger/qdb/arch/arch_mips.py | 2 +- qiling/debugger/qdb/arch/arch_x86.py | 2 +- .../qdb/branch_predictor/branch_predictor.py | 2 +- .../branch_predictor/branch_predictor_x86.py | 2 +- qiling/debugger/qdb/context.py | 2 +- qiling/debugger/qdb/qdb.py | 50 +++++++++---------- qiling/debugger/qdb/render/render.py | 6 +-- qiling/debugger/qdb/render/render_arm.py | 4 +- qiling/debugger/qdb/render/render_mips.py | 4 +- qiling/debugger/qdb/render/render_x86.py | 2 +- qiling/debugger/qdb/utils.py | 6 +-- 13 files changed, 48 insertions(+), 48 deletions(-) diff --git a/qiling/debugger/qdb/arch/arch.py b/qiling/debugger/qdb/arch/arch.py index 3e02bb3ed..e69a5aed1 100644 --- a/qiling/debugger/qdb/arch/arch.py +++ b/qiling/debugger/qdb/arch/arch.py @@ -5,16 +5,16 @@ -class Arch(object): +class Arch(): """ base class for arch """ - def __init__(self: Arch): + def __init__(self): pass @property - def archtype(self: Arch): + def archtype(self): return self.ql.archtype - def read_insn(self: Arch, address: int , insn_size: int = 4): + def read_insn(self, address: int , insn_size: int = 4): return self.read_mem(address, insn_size) diff --git a/qiling/debugger/qdb/arch/arch_arm.py b/qiling/debugger/qdb/arch/arch_arm.py index 75b4d3b02..ff5927493 100644 --- a/qiling/debugger/qdb/arch/arch_arm.py +++ b/qiling/debugger/qdb/arch/arch_arm.py @@ -8,7 +8,7 @@ from .arch import Arch class ArchARM(Arch): - def __init__(self: ArchARM): + def __init__(self): self.regs = ( "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", @@ -56,7 +56,7 @@ def get_mode(bits: int) -> int: } @property - def thumb_mode(self: ArchARM) -> bool: + def thumb_mode(self) -> bool: """ helper function for checking thumb mode """ @@ -88,6 +88,6 @@ def thumb_read(address: int) -> bytes: class ArchCORTEX_M(ArchARM): - def __init__(self: ArchARM): + def __init__(self): super().__init__() self.regs += ("xpsr", "control", "primask", "basepri", "faultmask") diff --git a/qiling/debugger/qdb/arch/arch_mips.py b/qiling/debugger/qdb/arch/arch_mips.py index cf00b16b7..b20b03a37 100644 --- a/qiling/debugger/qdb/arch/arch_mips.py +++ b/qiling/debugger/qdb/arch/arch_mips.py @@ -8,7 +8,7 @@ from .arch import Arch class ArchMIPS(Arch): - def __init__(self: ArchMIPS): + def __init__(self): self.regs = ( "gp", "at", "v0", "v1", "a0", "a1", "a2", "a3", diff --git a/qiling/debugger/qdb/arch/arch_x86.py b/qiling/debugger/qdb/arch/arch_x86.py index dc10f16cb..f9562b1b6 100644 --- a/qiling/debugger/qdb/arch/arch_x86.py +++ b/qiling/debugger/qdb/arch/arch_x86.py @@ -8,7 +8,7 @@ from .arch import Arch class ArchX86(Arch): - def __init__(self: ArchX86): + def __init__(self): self.regs = ( "eax", "ebx", "ecx", "edx", "esp", "ebp", "esi", "edi", diff --git a/qiling/debugger/qdb/branch_predictor/branch_predictor.py b/qiling/debugger/qdb/branch_predictor/branch_predictor.py index 94b438a14..686ed7ffc 100644 --- a/qiling/debugger/qdb/branch_predictor/branch_predictor.py +++ b/qiling/debugger/qdb/branch_predictor/branch_predictor.py @@ -37,7 +37,7 @@ def read_reg(self, reg_name): return getattr(self.ql.reg, reg_name) - def predict(self) -> Prophecy: + def predict(self): """ Try to predict certian branch will be taken or not based on current context """ diff --git a/qiling/debugger/qdb/branch_predictor/branch_predictor_x86.py b/qiling/debugger/qdb/branch_predictor/branch_predictor_x86.py index 487bad287..835df6b95 100644 --- a/qiling/debugger/qdb/branch_predictor/branch_predictor_x86.py +++ b/qiling/debugger/qdb/branch_predictor/branch_predictor_x86.py @@ -25,7 +25,7 @@ def __init__(self, ql): super().__init__(ql) ArchX86.__init__(self) - def predict(self) -> Prophecy: + def predict(self): prophecy = self.Prophecy() line = self.disasm(self.cur_addr) diff --git a/qiling/debugger/qdb/context.py b/qiling/debugger/qdb/context.py index 38c3b96aa..2713b444e 100644 --- a/qiling/debugger/qdb/context.py +++ b/qiling/debugger/qdb/context.py @@ -12,7 +12,7 @@ class Context(object): base class for accessing context of running qiling instance """ - def __init__(self, ql) -> Context: + def __init__(self, ql): self.ql = ql self.unpack = ql.unpack self.unpack16 = ql.unpack16 diff --git a/qiling/debugger/qdb/qdb.py b/qiling/debugger/qdb/qdb.py index 805523f4b..a1626fc46 100644 --- a/qiling/debugger/qdb/qdb.py +++ b/qiling/debugger/qdb/qdb.py @@ -21,7 +21,7 @@ class QlQdb(cmd.Cmd, QlDebugger): The built-in debugger of Qiling Framework """ - def __init__(self: QlQdb, ql: Qiling, init_hook: str = "", rr: bool = False) -> None: + def __init__(self, ql: Qiling, init_hook: str = "", rr: bool = False) -> None: """ @init_hook: the entry to be paused at @rr: record/replay debugging @@ -41,7 +41,7 @@ def __init__(self: QlQdb, ql: Qiling, init_hook: str = "", rr: bool = False) -> self.dbg_hook(init_hook) - def dbg_hook(self: QlQdb, init_hook: str): + def dbg_hook(self, init_hook: str): """ initial hook to prepare everything we need """ @@ -84,7 +84,7 @@ def bp_handler(ql, address, size, bp_list): self.interactive() @property - def cur_addr(self: QlQdb) -> int: + def cur_addr(self) -> int: """ getter for current address of qiling instance """ @@ -92,14 +92,14 @@ def cur_addr(self: QlQdb) -> int: return self.ql.reg.arch_pc @cur_addr.setter - def cur_addr(self: QlQdb, address: int) -> None: + def cur_addr(self, address: int) -> None: """ setter for current address of qiling instance """ self.ql.reg.arch_pc = address - def _run(self: Qldbg, address: int = 0, end: int = 0, count: int = 0) -> None: + def _run(self, address: int = 0, end: int = 0, count: int = 0) -> None: """ internal function for emulating instruction """ @@ -152,7 +152,7 @@ def inner(self, *args, **kwargs): func(self, *args, **kwargs) return inner - def parseline(self: QlQdb, line: str) -> Tuple[Optional[str], Optional[str], str]: + def parseline(self, line: str) -> Tuple[Optional[str], Optional[str], str]: """ Parse the line into a command name and a string containing the arguments. Returns a tuple containing (command, args, line). @@ -174,21 +174,21 @@ def parseline(self: QlQdb, line: str) -> Tuple[Optional[str], Optional[str], str cmd, arg = line[:i], line[i:].strip() return cmd, arg, line - def interactive(self: QlQdb, *args) -> None: + def interactive(self, *args) -> None: """ initial an interactive interface """ return self.cmdloop() - def run(self: QlQdb, *args) -> None: + def run(self, *args) -> None: """ internal command for running debugger """ self._run() - def emptyline(self: QlQdb, *args) -> None: + def emptyline(self, *args) -> None: """ repeat last command """ @@ -196,7 +196,7 @@ def emptyline(self: QlQdb, *args) -> None: if (lastcmd := getattr(self, "do_" + self.lastcmd, None)): return lastcmd() - def do_run(self: QlQdb, *args) -> None: + def do_run(self, *args) -> None: """ launch qiling instance """ @@ -206,7 +206,7 @@ def do_run(self: QlQdb, *args) -> None: @SnapshotManager.snapshot @save_reg_dump @check_ql_alive - def do_step_in(self: QlQdb, *args) -> Optional[bool]: + def do_step_in(self, *args) -> Optional[bool]: """ execute one instruction at a time, will enter subroutine """ @@ -226,7 +226,7 @@ def do_step_in(self: QlQdb, *args) -> Optional[bool]: @SnapshotManager.snapshot @save_reg_dump @check_ql_alive - def do_step_over(self: QlQdb, *args) -> Option[bool]: + def do_step_over(self, *args) -> Optional[bool]: """ execute one instruction at a time, but WON't enter subroutine """ @@ -244,7 +244,7 @@ def do_step_over(self: QlQdb, *args) -> Option[bool]: @SnapshotManager.snapshot @parse_int - def do_continue(self: QlQdb, address: Optional[int] = 0) -> None: + def do_continue(self, address: Optional[int] = 0) -> None: """ continue execution from current address if not specified """ @@ -256,7 +256,7 @@ def do_continue(self: QlQdb, address: Optional[int] = 0) -> None: self._run(address) - def do_backward(self: QlQdb, *args) -> None: + def do_backward(self, *args) -> None: """ step barkward if it's possible, option rr should be enabled and previous instruction must be executed before """ @@ -272,7 +272,7 @@ def do_backward(self: QlQdb, *args) -> None: else: print(f"{color.RED}[!] the option rr yet been set !!!{color.END}") - def set_breakpoint(self: QlQdb, address: int, is_temp: bool = False) -> None: + def set_breakpoint(self, address: int, is_temp: bool = False) -> None: """ internal function for placing breakpoint """ @@ -281,7 +281,7 @@ def set_breakpoint(self: QlQdb, address: int, is_temp: bool = False) -> None: self.bp_list.update({address: bp}) - def del_breakpoint(self: QlQdb, bp: Union[Breakpoint, TempBreakpoint]) -> None: + def del_breakpoint(self, bp: Union[Breakpoint, TempBreakpoint]) -> None: """ internal function for removing breakpoint """ @@ -289,7 +289,7 @@ def del_breakpoint(self: QlQdb, bp: Union[Breakpoint, TempBreakpoint]) -> None: self.bp_list.pop(bp.addr, None) @parse_int - def do_breakpoint(self: QlQdb, address: Optional[int] = 0) -> None: + def do_breakpoint(self, address: Optional[int] = 0) -> None: """ set breakpoint on specific address """ @@ -302,7 +302,7 @@ def do_breakpoint(self: QlQdb, address: Optional[int] = 0) -> None: print(f"{color.CYAN}[+] Breakpoint at 0x{address:08x}{color.END}") @parse_int - def do_disassemble(self: QlQdb, address: Optional[int] = 0, *args) -> None: + def do_disassemble(self, address: Optional[int] = 0, *args) -> None: """ disassemble instructions from address specified """ @@ -312,7 +312,7 @@ def do_disassemble(self: QlQdb, address: Optional[int] = 0, *args) -> None: except: print(f"{color.RED}[!] something went wrong ...{color.END}") - def do_examine(self: QlQdb, line: str) -> None: + def do_examine(self, line: str) -> None: """ Examine memory: x/FMT ADDRESS. format letter: o(octal), x(hex), d(decimal), u(unsigned decimal), t(binary), f(float), a(address), i(instruction), c(char), s(string) and z(hex, zero padded on the left) @@ -326,7 +326,7 @@ def do_examine(self: QlQdb, line: str) -> None: # except: # print(f"{color.RED}[!] something went wrong ...{color.END}") - def do_start(self: QlQdb, *args) -> None: + def do_start(self, *args) -> None: """ restore qiling instance context to initial state """ @@ -336,7 +336,7 @@ def do_start(self: QlQdb, *args) -> None: self.ql.restore(self.init_state) self.do_context() - def do_context(self: QlQdb, *args) -> None: + def do_context(self, *args) -> None: """ display context information for current location """ @@ -345,7 +345,7 @@ def do_context(self: QlQdb, *args) -> None: self.render.context_stack() self.render.context_asm() - def do_show(self: QlQdb, *args) -> None: + def do_show(self, *args) -> None: """ show some runtime information """ @@ -355,7 +355,7 @@ def do_show(self: QlQdb, *args) -> None: if self.rr: print(f"Snapshots: {len([st for st in self.rr.layers if isinstance(st, self.rr.DiffedState)])}") - def do_shell(self: QlQdb, *command) -> None: + def do_shell(self, *command) -> None: """ run python code """ @@ -365,7 +365,7 @@ def do_shell(self: QlQdb, *command) -> None: except: print("something went wrong ...") - def do_quit(self: QlQdb, *args) -> bool: + def do_quit(self, *args) -> bool: """ exit Qdb and stop running qiling instance """ @@ -373,7 +373,7 @@ def do_quit(self: QlQdb, *args) -> bool: self.ql.stop() exit() - def do_EOF(self: QlQdb, *args) -> None: + def do_EOF(self, *args) -> None: """ handle Ctrl+D """ diff --git a/qiling/debugger/qdb/render/render.py b/qiling/debugger/qdb/render/render.py index 88ff841d6..b1735fdcc 100644 --- a/qiling/debugger/qdb/render/render.py +++ b/qiling/debugger/qdb/render/render.py @@ -48,7 +48,7 @@ def wrapper(*args, **kwargs): return wrapper return decorator - def __init__(self) -> Render: + def __init__(self): self.regs_a_row = 4 self.stack_num = 10 self.color = color @@ -141,7 +141,7 @@ def swap_reg_name(self, cur_regs: Mapping["str", int], extra_dict=None): return cur_regs - def print_asm(self, insn: CsInsn, to_jump: bool = False) -> None: + def print_asm(self, insn, to_jump: bool = False) -> None: """ helper function for printing assembly instructions, indicates where we are and the branch prediction provided by BranchPredictor @@ -162,7 +162,7 @@ class ContextRender(Context, Render): base class for context render """ - def __init__(self, ql: Qiling, predictor: BranchPredictor): + def __init__(self, ql, predictor): super().__init__(ql) Render.__init__(self) self.predictor = predictor diff --git a/qiling/debugger/qdb/render/render_arm.py b/qiling/debugger/qdb/render/render_arm.py index 8d47fd737..fe6f184ef 100644 --- a/qiling/debugger/qdb/render/render_arm.py +++ b/qiling/debugger/qdb/render/render_arm.py @@ -13,7 +13,7 @@ class ContextRenderARM(ContextRender, ArchARM): context render for ARM """ - def __init__(self, ql: Qiling, predictor: BranchPredictor): + def __init__(self, ql, predictor): super().__init__(ql, predictor) ArchARM.__init__(self) @@ -39,7 +39,7 @@ class ContextRenderCORTEX_M(ContextRenderARM, ArchCORTEX_M): context render for cortex_m """ - def __init__(self, ql: Qiling, predictor: BranchPredictor): + def __init__(self, ql, predictor): super().__init__(ql, predictor) ArchCORTEX_M.__init__(self) self.regs_a_row = 3 diff --git a/qiling/debugger/qdb/render/render_mips.py b/qiling/debugger/qdb/render/render_mips.py index aa177824e..ff67891d8 100644 --- a/qiling/debugger/qdb/render/render_mips.py +++ b/qiling/debugger/qdb/render/render_mips.py @@ -13,13 +13,13 @@ class ContextRenderMIPS(ContextRender, ArchMIPS): context render for MIPS """ - def __init__(self, ql: Qiling, predictor: BranchPredictor): + def __init__(self, ql, predictor): super().__init__(ql, predictor) ArchMIPS.__init__(self) @Render.divider_printer("[ REGISTERS ]") def context_reg(self, saved_reg_dump): -t """ + """ redering context registers """ diff --git a/qiling/debugger/qdb/render/render_x86.py b/qiling/debugger/qdb/render/render_x86.py index f00f2a9c5..bcf17d955 100644 --- a/qiling/debugger/qdb/render/render_x86.py +++ b/qiling/debugger/qdb/render/render_x86.py @@ -13,7 +13,7 @@ class ContextRenderX86(ContextRender, ArchX86): context render for X86 """ - def __init__(self, ql: Qiling, predictor: BranchPredictor): + def __init__(self, ql, predictor): super().__init__(ql, predictor) ArchX86.__init__(self) diff --git a/qiling/debugger/qdb/utils.py b/qiling/debugger/qdb/utils.py index 7cfd24c1a..78b19a182 100644 --- a/qiling/debugger/qdb/utils.py +++ b/qiling/debugger/qdb/utils.py @@ -31,7 +31,7 @@ """ -def setup_branch_predictor(ql: Qiling) -> BranchPredictor: +def setup_branch_predictor(ql): """ setup BranchPredictor correspondingly """ @@ -44,7 +44,7 @@ def setup_branch_predictor(ql: Qiling) -> BranchPredictor: QL_ARCH.MIPS: BranchPredictorMIPS, }.get(ql.archtype)(ql) -def setup_context_render(ql: Qiling, predictor: BranchPredictor) -> ContextRender: +def setup_context_render(ql, predictor): """ setup context render correspondingly """ @@ -110,7 +110,7 @@ def __init__(self, ql): super().__init__(ql) self.layers = [] - def _save(self) -> State(): + def _save(self) -> State: """ acquire current State by wrapping saved context from ql.save() """ From 69e4d326cc67ada95bad3ad6db87d75d3cfa4658 Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Tue, 15 Feb 2022 23:50:52 +0000 Subject: [PATCH 135/406] remove inherit from object --- qiling/debugger/qdb/branch_predictor/branch_predictor.py | 2 +- qiling/debugger/qdb/context.py | 2 +- qiling/debugger/qdb/render/render.py | 2 +- qiling/debugger/qdb/utils.py | 8 ++++---- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/qiling/debugger/qdb/branch_predictor/branch_predictor.py b/qiling/debugger/qdb/branch_predictor/branch_predictor.py index 686ed7ffc..e75325bcf 100644 --- a/qiling/debugger/qdb/branch_predictor/branch_predictor.py +++ b/qiling/debugger/qdb/branch_predictor/branch_predictor.py @@ -13,7 +13,7 @@ class BranchPredictor(Context): Base class for predictor """ - class Prophecy(object): + class Prophecy(): """ container for storing result of the predictor @going: indicate the certian branch will be taken or not diff --git a/qiling/debugger/qdb/context.py b/qiling/debugger/qdb/context.py index 2713b444e..e565b205d 100644 --- a/qiling/debugger/qdb/context.py +++ b/qiling/debugger/qdb/context.py @@ -7,7 +7,7 @@ import unicorn -class Context(object): +class Context(): """ base class for accessing context of running qiling instance """ diff --git a/qiling/debugger/qdb/render/render.py b/qiling/debugger/qdb/render/render.py index b1735fdcc..59edcb5b1 100644 --- a/qiling/debugger/qdb/render/render.py +++ b/qiling/debugger/qdb/render/render.py @@ -27,7 +27,7 @@ def get_terminal_size() -> Iterable: COLORS = (color.DARKCYAN, color.BLUE, color.RED, color.YELLOW, color.GREEN, color.PURPLE, color.CYAN, color.WHITE) -class Render(object): +class Render(): """ base class for rendering related functions """ diff --git a/qiling/debugger/qdb/utils.py b/qiling/debugger/qdb/utils.py index 78b19a182..38b14c85a 100644 --- a/qiling/debugger/qdb/utils.py +++ b/qiling/debugger/qdb/utils.py @@ -66,12 +66,12 @@ def setup_context_render(ql, predictor): """ -class SnapshotManager(object): +class SnapshotManager(): """ for functioning differential snapshot """ - class State(object): + class State(): """ internal container for storing raw state from qiling """ @@ -79,7 +79,7 @@ class State(object): def __init__(self, saved_state): self.reg, self.ram = SnapshotManager.transform(saved_state) - class DiffedState(object): + class DiffedState(): """ internal container for storing diffed state """ @@ -107,7 +107,7 @@ def transform(st): return (reg, ram) def __init__(self, ql): - super().__init__(ql) + self.ql = ql self.layers = [] def _save(self) -> State: From 8d53617983ed542e47b78381636dc6104eefcfa3 Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Tue, 15 Feb 2022 23:56:06 +0000 Subject: [PATCH 136/406] remove inherit from object and unneeded empty parentheses --- qiling/debugger/qdb/arch/arch.py | 2 +- qiling/debugger/qdb/branch_predictor/branch_predictor.py | 2 +- qiling/debugger/qdb/context.py | 2 +- qiling/debugger/qdb/misc.py | 2 +- qiling/debugger/qdb/render/render.py | 2 +- qiling/debugger/qdb/utils.py | 6 +++--- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/qiling/debugger/qdb/arch/arch.py b/qiling/debugger/qdb/arch/arch.py index e69a5aed1..b2ef76fac 100644 --- a/qiling/debugger/qdb/arch/arch.py +++ b/qiling/debugger/qdb/arch/arch.py @@ -5,7 +5,7 @@ -class Arch(): +class Arch: """ base class for arch """ diff --git a/qiling/debugger/qdb/branch_predictor/branch_predictor.py b/qiling/debugger/qdb/branch_predictor/branch_predictor.py index e75325bcf..7cf38c4f1 100644 --- a/qiling/debugger/qdb/branch_predictor/branch_predictor.py +++ b/qiling/debugger/qdb/branch_predictor/branch_predictor.py @@ -13,7 +13,7 @@ class BranchPredictor(Context): Base class for predictor """ - class Prophecy(): + class Prophecy: """ container for storing result of the predictor @going: indicate the certian branch will be taken or not diff --git a/qiling/debugger/qdb/context.py b/qiling/debugger/qdb/context.py index e565b205d..1247d3360 100644 --- a/qiling/debugger/qdb/context.py +++ b/qiling/debugger/qdb/context.py @@ -7,7 +7,7 @@ import unicorn -class Context(): +class Context: """ base class for accessing context of running qiling instance """ diff --git a/qiling/debugger/qdb/misc.py b/qiling/debugger/qdb/misc.py index 449163cdf..9a946c50d 100644 --- a/qiling/debugger/qdb/misc.py +++ b/qiling/debugger/qdb/misc.py @@ -6,7 +6,7 @@ from typing import Callable, Optional -class Breakpoint(object): +class Breakpoint: """ dummy class for breakpoint """ diff --git a/qiling/debugger/qdb/render/render.py b/qiling/debugger/qdb/render/render.py index 59edcb5b1..9db905ce0 100644 --- a/qiling/debugger/qdb/render/render.py +++ b/qiling/debugger/qdb/render/render.py @@ -27,7 +27,7 @@ def get_terminal_size() -> Iterable: COLORS = (color.DARKCYAN, color.BLUE, color.RED, color.YELLOW, color.GREEN, color.PURPLE, color.CYAN, color.WHITE) -class Render(): +class Render: """ base class for rendering related functions """ diff --git a/qiling/debugger/qdb/utils.py b/qiling/debugger/qdb/utils.py index 38b14c85a..d13cda057 100644 --- a/qiling/debugger/qdb/utils.py +++ b/qiling/debugger/qdb/utils.py @@ -66,12 +66,12 @@ def setup_context_render(ql, predictor): """ -class SnapshotManager(): +class SnapshotManager: """ for functioning differential snapshot """ - class State(): + class State: """ internal container for storing raw state from qiling """ @@ -79,7 +79,7 @@ class State(): def __init__(self, saved_state): self.reg, self.ram = SnapshotManager.transform(saved_state) - class DiffedState(): + class DiffedState: """ internal container for storing diffed state """ From 0cb7017a6fa1621da5279b84ac179989592a4ab1 Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Wed, 16 Feb 2022 00:12:00 +0000 Subject: [PATCH 137/406] Optional[int] should default to None, remove unneeded *args --- qiling/debugger/qdb/qdb.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiling/debugger/qdb/qdb.py b/qiling/debugger/qdb/qdb.py index a1626fc46..ff3860e1c 100644 --- a/qiling/debugger/qdb/qdb.py +++ b/qiling/debugger/qdb/qdb.py @@ -244,7 +244,7 @@ def do_step_over(self, *args) -> Optional[bool]: @SnapshotManager.snapshot @parse_int - def do_continue(self, address: Optional[int] = 0) -> None: + def do_continue(self, address: Optional[int] = None) -> None: """ continue execution from current address if not specified """ @@ -289,7 +289,7 @@ def del_breakpoint(self, bp: Union[Breakpoint, TempBreakpoint]) -> None: self.bp_list.pop(bp.addr, None) @parse_int - def do_breakpoint(self, address: Optional[int] = 0) -> None: + def do_breakpoint(self, address: Optional[int] = None) -> None: """ set breakpoint on specific address """ @@ -302,7 +302,7 @@ def do_breakpoint(self, address: Optional[int] = 0) -> None: print(f"{color.CYAN}[+] Breakpoint at 0x{address:08x}{color.END}") @parse_int - def do_disassemble(self, address: Optional[int] = 0, *args) -> None: + def do_disassemble(self, address: Optional[int] = None) -> None: """ disassemble instructions from address specified """ From 22d20d10dc13214d522c2dc32452b1896ac85aa1 Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Wed, 16 Feb 2022 00:19:51 +0000 Subject: [PATCH 138/406] use os.get_terminal_size instead --- qiling/debugger/qdb/render/render.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/qiling/debugger/qdb/render/render.py b/qiling/debugger/qdb/render/render.py index 9db905ce0..f83501c1e 100644 --- a/qiling/debugger/qdb/render/render.py +++ b/qiling/debugger/qdb/render/render.py @@ -12,12 +12,6 @@ from ..const import color -def get_terminal_size() -> Iterable: - """ - get terminal window height and width - """ - - return map(int, os.popen('stty size', 'r').read().split()) """ @@ -39,12 +33,13 @@ def divider_printer(field_name, ruler="─"): def decorator(context_dumper): def wrapper(*args, **kwargs): - height, width = get_terminal_size() + width, height = os.get_terminal_size() bar = (width - len(field_name)) // 2 - 1 print(ruler * bar, field_name, ruler * bar) context_dumper(*args, **kwargs) if "DISASM" in field_name: print(ruler * width) + return wrapper return decorator From 227f85b16784e8f7a55cf21d2536473edf5d0f80 Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Wed, 16 Feb 2022 00:21:37 +0000 Subject: [PATCH 139/406] fix typo --- qiling/debugger/qdb/render/render.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiling/debugger/qdb/render/render.py b/qiling/debugger/qdb/render/render.py index f83501c1e..243f87f0a 100644 --- a/qiling/debugger/qdb/render/render.py +++ b/qiling/debugger/qdb/render/render.py @@ -124,7 +124,7 @@ def render_assembly(self, lines) -> None: for line in forward: self.print_asm(line) - def swap_reg_name(self, cur_regs: Mapping["str", int], extra_dict=None): + def swap_reg_name(self, cur_regs: Mapping[str, int], extra_dict=None): """ swap register name with more readable register name """ From e3c434a5f90ff9310055b1b22194725b4d6ca911 Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Wed, 16 Feb 2022 00:26:16 +0000 Subject: [PATCH 140/406] use default value for next and constant for errno --- qiling/debugger/qdb/context.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/qiling/debugger/qdb/context.py b/qiling/debugger/qdb/context.py index 1247d3360..f9ef7e0e5 100644 --- a/qiling/debugger/qdb/context.py +++ b/qiling/debugger/qdb/context.py @@ -6,6 +6,7 @@ from typing import Optional import unicorn +from unicorn import UC_ERR_READ_UNMAPPED class Context: """ @@ -41,13 +42,8 @@ def disasm(self, address: int, detail: bool = False) -> Optional[int]: md = self.ql.disassembler md.detail = detail - try: - ret = next(md.disasm(self.read_insn(address), address)) - - except StopIteration: - ret = None + return next(md.disasm(self.read_insn(address), address), None) - return ret def try_read(self, address: int, size: int) -> Optional[bytes]: """ @@ -60,7 +56,7 @@ def try_read(self, address: int, size: int) -> Optional[bytes]: result = self.read_mem(address, size) except unicorn.unicorn.UcError as err: - if err.errno == 6: # Invalid memory read (UC_ERR_READ_UNMAPPED) + if err.errno == UC_ERR_READ_UNMAPPED: # Invalid memory read (UC_ERR_READ_UNMAPPED) err_msg = f"Can not access memory at address 0x{address:08x}" except: From fe21dbb9596f16b74c787a176d4f96292b744149 Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Wed, 16 Feb 2022 00:38:18 +0000 Subject: [PATCH 141/406] revis read_insn signature --- qiling/debugger/qdb/arch/arch.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/qiling/debugger/qdb/arch/arch.py b/qiling/debugger/qdb/arch/arch.py index b2ef76fac..57257900d 100644 --- a/qiling/debugger/qdb/arch/arch.py +++ b/qiling/debugger/qdb/arch/arch.py @@ -9,12 +9,13 @@ class Arch: """ base class for arch """ - def __init__(self): - pass + def __init__(self, ql): + self.ql = ql + self.default_insn_size = 4 @property def archtype(self): return self.ql.archtype - def read_insn(self, address: int , insn_size: int = 4): - return self.read_mem(address, insn_size) + def read_insn(self, address: int): + return self.read_mem(address, self.default_insn_size) From 0bed6a8b693bd851e7bbcc77a14f162431267809 Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Thu, 17 Feb 2022 03:53:17 +0000 Subject: [PATCH 142/406] fix inherit and asm rendering issue --- qiling/debugger/qdb/arch/arch.py | 21 ++++++++++++------- qiling/debugger/qdb/arch/arch_arm.py | 3 +++ qiling/debugger/qdb/arch/arch_mips.py | 1 + qiling/debugger/qdb/arch/arch_x86.py | 1 + .../branch_predictor/branch_predictor_arm.py | 8 ++++--- .../branch_predictor/branch_predictor_mips.py | 4 +++- qiling/debugger/qdb/context.py | 1 - qiling/debugger/qdb/render/render.py | 8 ++++--- qiling/debugger/qdb/render/render_arm.py | 1 + qiling/debugger/qdb/utils.py | 3 ++- 10 files changed, 35 insertions(+), 16 deletions(-) diff --git a/qiling/debugger/qdb/arch/arch.py b/qiling/debugger/qdb/arch/arch.py index 57257900d..95ecb9d6f 100644 --- a/qiling/debugger/qdb/arch/arch.py +++ b/qiling/debugger/qdb/arch/arch.py @@ -9,13 +9,20 @@ class Arch: """ base class for arch """ - def __init__(self, ql): - self.ql = ql - self.default_insn_size = 4 - @property - def archtype(self): - return self.ql.archtype + _SUPPORTED_ARCH = ["ArchARM", "ArchCORTEX_M", "ArchMIPS", "ArchX86"] + + # FIXME: this is a dirty hack for setup archtype at initialization phase + @staticmethod + def set_archtype(self): + for archtype in self._SUPPORTED_ARCH: + for each_type in type(self).mro(): + if archtype in str(each_type): + return archtype + + def __init__(self): + self.arch_insn_size = 4 + self.archtype = self.set_archtype(self) def read_insn(self, address: int): - return self.read_mem(address, self.default_insn_size) + return self.read_mem(address, self.arch_insn_size) diff --git a/qiling/debugger/qdb/arch/arch_arm.py b/qiling/debugger/qdb/arch/arch_arm.py index ff5927493..9a08df4e5 100644 --- a/qiling/debugger/qdb/arch/arch_arm.py +++ b/qiling/debugger/qdb/arch/arch_arm.py @@ -9,6 +9,7 @@ class ArchARM(Arch): def __init__(self): + super().__init__() self.regs = ( "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", @@ -69,6 +70,7 @@ def read_insn(self, address: int) -> bytes: """ def thumb_read(address: int) -> bytes: + first_two = self.ql.unpack16(self.read_mem(address, 2)) result = self.ql.pack16(first_two) @@ -87,6 +89,7 @@ def thumb_read(address: int) -> bytes: return super().read_insn(address) if not self.thumb_mode else thumb_read(address) + class ArchCORTEX_M(ArchARM): def __init__(self): super().__init__() diff --git a/qiling/debugger/qdb/arch/arch_mips.py b/qiling/debugger/qdb/arch/arch_mips.py index b20b03a37..437160c0e 100644 --- a/qiling/debugger/qdb/arch/arch_mips.py +++ b/qiling/debugger/qdb/arch/arch_mips.py @@ -9,6 +9,7 @@ class ArchMIPS(Arch): def __init__(self): + super().__init__() self.regs = ( "gp", "at", "v0", "v1", "a0", "a1", "a2", "a3", diff --git a/qiling/debugger/qdb/arch/arch_x86.py b/qiling/debugger/qdb/arch/arch_x86.py index f9562b1b6..562e20b4b 100644 --- a/qiling/debugger/qdb/arch/arch_x86.py +++ b/qiling/debugger/qdb/arch/arch_x86.py @@ -9,6 +9,7 @@ class ArchX86(Arch): def __init__(self): + super().__init__() self.regs = ( "eax", "ebx", "ecx", "edx", "esp", "ebp", "esi", "edi", diff --git a/qiling/debugger/qdb/branch_predictor/branch_predictor_arm.py b/qiling/debugger/qdb/branch_predictor/branch_predictor_arm.py index 69b3135b9..d0d3585a3 100644 --- a/qiling/debugger/qdb/branch_predictor/branch_predictor_arm.py +++ b/qiling/debugger/qdb/branch_predictor/branch_predictor_arm.py @@ -15,6 +15,7 @@ class BranchPredictorARM(BranchPredictor, ArchARM): def __init__(self, ql): super().__init__(ql) + ArchARM.__init__(self) self.INST_SIZE = 4 self.THUMB_INST_SIZE = 2 @@ -47,7 +48,8 @@ def predict(self): prophecy.where = cur_addr + line.size if line.mnemonic == self.CODE_END: # indicates program exited - return True + prophecy.where = True + return prophecy jump_table = { # unconditional branch @@ -182,7 +184,7 @@ def predict(self): prophecy.where = self.unpack32(self.read_mem(self.read_reg(r), self.INST_SIZE)) elif line.mnemonic in ("addls", "addne", "add") and self.regdst_eq_pc(line.op_str): - V, C, Z, N = self.get_cpsr(ql.reg.cpsr) + V, C, Z, N = self.get_cpsr(self.ql.reg.cpsr) r0, r1, r2, *imm = line.op_str.split(", ") # program counter is awalys 8 bytes ahead when it comes with pc, need to add extra 8 bytes @@ -232,7 +234,7 @@ def predict(self): "pophi": lambda V, C, Z, N: (C == 1), "popge": lambda V, C, Z, N: (N == V), "poplt": lambda V, C, Z, N: (N != V), - }.get(line.mnemonic)(*get_cpsr(self.ql.reg.cpsr)): + }.get(line.mnemonic)(*self.get_cpsr(self.ql.reg.cpsr)): prophecy.where = cur_addr + self.INST_SIZE diff --git a/qiling/debugger/qdb/branch_predictor/branch_predictor_mips.py b/qiling/debugger/qdb/branch_predictor/branch_predictor_mips.py index 09f6bc687..e729eabbb 100644 --- a/qiling/debugger/qdb/branch_predictor/branch_predictor_mips.py +++ b/qiling/debugger/qdb/branch_predictor/branch_predictor_mips.py @@ -15,6 +15,7 @@ class BranchPredictorMIPS(BranchPredictor, ArchMIPS): def __init__(self, ql): super().__init__(ql) + ArchMIPS.__init__(self) self.CODE_END = "break" self.INST_SIZE = 4 @@ -42,7 +43,8 @@ def predict(self): line = self.disasm(self.cur_addr) if line.mnemonic == self.CODE_END: # indicates program extied - return True + prophecy.where = True + return prophecy prophecy.where = self.cur_addr + self.INST_SIZE if line.mnemonic.startswith('j') or line.mnemonic.startswith('b'): diff --git a/qiling/debugger/qdb/context.py b/qiling/debugger/qdb/context.py index f9ef7e0e5..5f81a5fc3 100644 --- a/qiling/debugger/qdb/context.py +++ b/qiling/debugger/qdb/context.py @@ -44,7 +44,6 @@ def disasm(self, address: int, detail: bool = False) -> Optional[int]: return next(md.disasm(self.read_insn(address), address), None) - def try_read(self, address: int, size: int) -> Optional[bytes]: """ try to read data from ql.mem diff --git a/qiling/debugger/qdb/render/render.py b/qiling/debugger/qdb/render/render.py index 243f87f0a..382d08dfe 100644 --- a/qiling/debugger/qdb/render/render.py +++ b/qiling/debugger/qdb/render/render.py @@ -198,9 +198,11 @@ def context_asm(self) -> None: cur_addr = from_addr while cur_addr != to_addr: - line = self.disasm(cur_addr) - past_list.append(line) - cur_addr += line.size + insn = self.disasm(cur_addr) + cur_addr += self.arch_insn_size + if not insn: + continue + past_list.append(insn) bk_list = [] fd_list = [] diff --git a/qiling/debugger/qdb/render/render_arm.py b/qiling/debugger/qdb/render/render_arm.py index fe6f184ef..15ba411e5 100644 --- a/qiling/debugger/qdb/render/render_arm.py +++ b/qiling/debugger/qdb/render/render_arm.py @@ -34,6 +34,7 @@ def context_reg(self, saved_reg_dump): self.print_mode_info(self.ql.reg.cpsr) + class ContextRenderCORTEX_M(ContextRenderARM, ArchCORTEX_M): """ context render for cortex_m diff --git a/qiling/debugger/qdb/utils.py b/qiling/debugger/qdb/utils.py index d13cda057..95cd35c40 100644 --- a/qiling/debugger/qdb/utils.py +++ b/qiling/debugger/qdb/utils.py @@ -9,6 +9,7 @@ from qiling.const import QL_ARCH from .context import Context +from .misc import read_int from .render import ( ContextRenderX86, @@ -317,7 +318,7 @@ def parse(self, line: str): if ft == "i": for offset in range(addr, addr+ct*4, 4): - line = disasm(self.ql, offset) + line = self.disasm(offset) if line: print(f"0x{line.address:x}: {line.mnemonic}\t{line.op_str}") From 98be54754c5e92f171b311cc1bc0a996e85aeedc Mon Sep 17 00:00:00 2001 From: lazymio Date: Thu, 17 Feb 2022 19:22:38 +0100 Subject: [PATCH 143/406] Fix fuzzing for tendaac15 --- examples/fuzzing/tenda_ac15/fuzz_tendaac15_httpd.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/fuzzing/tenda_ac15/fuzz_tendaac15_httpd.py b/examples/fuzzing/tenda_ac15/fuzz_tendaac15_httpd.py index 35f142eac..d30d01de4 100644 --- a/examples/fuzzing/tenda_ac15/fuzz_tendaac15_httpd.py +++ b/examples/fuzzing/tenda_ac15/fuzz_tendaac15_httpd.py @@ -34,8 +34,8 @@ def main(input_file, enable_trace=False): fuzz_mem=ql.mem.search(b"CCCCAAAA") target_address = fuzz_mem[0] - def place_input_callback(uc, input, _, data): - ql.mem.write(target_address, input) + def place_input_callback(_ql: Qiling, input: bytes, _): + _ql.mem.write(target_address, input) def start_afl(_ql: Qiling): """ From a02d60bd43e5f5c81c32f7de8f263c0d6bfea1fc Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Thu, 17 Feb 2022 21:22:29 +0000 Subject: [PATCH 144/406] read_ptr instead of ql.unpack + mem.read --- qiling/debugger/qdb/arch/arch_arm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiling/debugger/qdb/arch/arch_arm.py b/qiling/debugger/qdb/arch/arch_arm.py index 9a08df4e5..1d237e54e 100644 --- a/qiling/debugger/qdb/arch/arch_arm.py +++ b/qiling/debugger/qdb/arch/arch_arm.py @@ -71,7 +71,7 @@ def read_insn(self, address: int) -> bytes: def thumb_read(address: int) -> bytes: - first_two = self.ql.unpack16(self.read_mem(address, 2)) + first_two = self.ql.mem.read_ptr(address, 2) result = self.ql.pack16(first_two) # to judge it's thumb mode or not @@ -81,7 +81,7 @@ def thumb_read(address: int) -> bytes: first_two & 0xe800 == 0xe800, ]): - latter_two = self.ql.unpack16(self.read_mem(address+2, 2)) + latter_two = self.ql.mem.read_ptr(address+2, 2)) result += self.ql.pack16(latter_two) return result From 535fe01e3b81d66fc2e309c3b0245f11623c93b5 Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Thu, 17 Feb 2022 21:25:04 +0000 Subject: [PATCH 145/406] remove redundant parentheses --- qiling/debugger/qdb/arch/arch_arm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiling/debugger/qdb/arch/arch_arm.py b/qiling/debugger/qdb/arch/arch_arm.py index 1d237e54e..6ecf1f7a7 100644 --- a/qiling/debugger/qdb/arch/arch_arm.py +++ b/qiling/debugger/qdb/arch/arch_arm.py @@ -81,7 +81,7 @@ def thumb_read(address: int) -> bytes: first_two & 0xe800 == 0xe800, ]): - latter_two = self.ql.mem.read_ptr(address+2, 2)) + latter_two = self.ql.mem.read_ptr(address+2, 2) result += self.ql.pack16(latter_two) return result From 3ea246a55ec2621d9a14fe156b8cb4091d5871df Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Thu, 17 Feb 2022 21:27:11 +0000 Subject: [PATCH 146/406] fix type annotation --- qiling/debugger/qdb/context.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/qiling/debugger/qdb/context.py b/qiling/debugger/qdb/context.py index 5f81a5fc3..7281270bd 100644 --- a/qiling/debugger/qdb/context.py +++ b/qiling/debugger/qdb/context.py @@ -5,8 +5,10 @@ from typing import Optional -import unicorn from unicorn import UC_ERR_READ_UNMAPPED +import unicorn + +from capstone import CsInsn class Context: """ @@ -34,7 +36,7 @@ def read_mem(self, address: int, size: int): return self.ql.mem.read(address, size) - def disasm(self, address: int, detail: bool = False) -> Optional[int]: + def disasm(self, address: int, detail: bool = False) -> Optional[CsInsn]: """ helper function for disassembling """ From f5d13cf80a1c999b41f38919d80284390ea389d5 Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Thu, 17 Feb 2022 21:27:32 +0000 Subject: [PATCH 147/406] use reg.read instead of getattr --- qiling/debugger/qdb/branch_predictor/branch_predictor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiling/debugger/qdb/branch_predictor/branch_predictor.py b/qiling/debugger/qdb/branch_predictor/branch_predictor.py index 7cf38c4f1..d899d7fc8 100644 --- a/qiling/debugger/qdb/branch_predictor/branch_predictor.py +++ b/qiling/debugger/qdb/branch_predictor/branch_predictor.py @@ -35,7 +35,7 @@ def read_reg(self, reg_name): read specific register value """ - return getattr(self.ql.reg, reg_name) + return self.ql.reg.read(reg_name) def predict(self): """ From afa0180b4c15263091f056a3a6fb8d37ab4f3787 Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Thu, 17 Feb 2022 22:18:39 +0000 Subject: [PATCH 148/406] use qdb_print for color printing in qdb.py --- qiling/debugger/qdb/const.py | 6 ++++++ qiling/debugger/qdb/qdb.py | 29 +++++++++++++++-------------- qiling/debugger/qdb/utils.py | 21 +++++++++++++++++++++ 3 files changed, 42 insertions(+), 14 deletions(-) diff --git a/qiling/debugger/qdb/const.py b/qiling/debugger/qdb/const.py index 562b41d3d..74c72d229 100644 --- a/qiling/debugger/qdb/const.py +++ b/qiling/debugger/qdb/const.py @@ -1,3 +1,5 @@ +from enum import IntEnum + class color: """ class for colorful prints @@ -16,3 +18,7 @@ class for colorful prints BOLD = '\033[1m' END = '\033[0m' RESET = '\x1b[39m' + +class QDB_MSG(IntEnum): + ERROR = 10 + INFO = 20 diff --git a/qiling/debugger/qdb/qdb.py b/qiling/debugger/qdb/qdb.py index ff3860e1c..537352e65 100644 --- a/qiling/debugger/qdb/qdb.py +++ b/qiling/debugger/qdb/qdb.py @@ -15,6 +15,7 @@ from .misc import parse_int, Breakpoint, TempBreakpoint from .const import color +from .utils import QDB_MSG, qdb_print class QlQdb(cmd.Cmd, QlDebugger): """ @@ -61,7 +62,7 @@ def bp_handler(ql, address, size, bp_list): if bp.hitted: return - print(f"{color.CYAN}[+] hit breakpoint at 0x{self.cur_addr:08x}{color.END}") + qdb_print(QDB_MSG.INFO, f"hit breakpoint at 0x{self.cur_addr:08x}") bp.hitted = True ql.stop() @@ -115,7 +116,7 @@ def _run(self, address: int = 0, end: int = 0, count: int = 0) -> None: if isinstance(bp, TempBreakpoint): self.del_breakpoint(bp) else: - print(f"{color.CYAN}[+] hit breakpoint at 0x{self.cur_addr:08x}{color.END}") + qdb_print(QDB_MSG.INFO, f"hit breakpoint at 0x{self.cur_addr:08x}") break @@ -147,7 +148,7 @@ def check_ql_alive(func) -> None: def inner(self, *args, **kwargs): if self.ql is None: - print(f"{color.RED}[!] The program is not being run.{color.END}") + qdb_print(QDB_MSG.ERROR, "The program is not being run.") else: func(self, *args, **kwargs) return inner @@ -252,7 +253,7 @@ def do_continue(self, address: Optional[int] = None) -> None: if address is None: address = self.cur_addr - print(f"{color.CYAN}continued from 0x{address:08x}{color.END}") + qdb_print(QDB_MSG.INFO, f"continued from 0x{address:08x}") self._run(address) @@ -263,14 +264,14 @@ def do_backward(self, *args) -> None: if self.rr: if len(self.rr.layers) == 0 or not isinstance(self.rr.layers[-1], self.rr.DiffedState): - print(f"{color.RED}[!] there is no way back !!!{color.END}") + qdb_print(QDB_MSG.ERROR, "there is no way back !!!") else: - print(f"{color.CYAN}[+] step backward ~{color.END}") + qdb_print(QDB_MSG.INFO, "step backward ~") self.rr.restore() self.do_context() else: - print(f"{color.RED}[!] the option rr yet been set !!!{color.END}") + qdb_print(QDB_MSG.ERROR, f"the option rr yet been set !!!") def set_breakpoint(self, address: int, is_temp: bool = False) -> None: """ @@ -299,7 +300,7 @@ def do_breakpoint(self, address: Optional[int] = None) -> None: self.set_breakpoint(address) - print(f"{color.CYAN}[+] Breakpoint at 0x{address:08x}{color.END}") + qdb_print(QDB_MSG.INFO, f"Breakpoint at 0x{address:08x}") @parse_int def do_disassemble(self, address: Optional[int] = None) -> None: @@ -310,7 +311,7 @@ def do_disassemble(self, address: Optional[int] = None) -> None: try: context_asm(self.ql, address) except: - print(f"{color.RED}[!] something went wrong ...{color.END}") + qdb_print(QDB_MSG.ERROR) def do_examine(self, line: str) -> None: """ @@ -322,9 +323,9 @@ def do_examine(self, line: str) -> None: # try: if type(err_msg := self.mm.parse(line)) is str: - print(f"{color.RED}[!] {err_msg} ...{color.END}") + qdb_print(QDB_MSG.ERROR, err_msg) # except: - # print(f"{color.RED}[!] something went wrong ...{color.END}") + # qdb_print(QDB_MSG.ERROR, "something went wrong ...") def do_start(self, *args) -> None: """ @@ -351,9 +352,9 @@ def do_show(self, *args) -> None: """ self.ql.mem.show_mapinfo() - print(f"Breakpoints: {[hex(addr) for addr in self.bp_list.keys()]}") + qdb_print(QDB_MSG.INFO, f"Breakpoints: {[hex(addr) for addr in self.bp_list.keys()]}") if self.rr: - print(f"Snapshots: {len([st for st in self.rr.layers if isinstance(st, self.rr.DiffedState)])}") + qdb_print(QDB_MSG.INFO, f"Snapshots: {len([st for st in self.rr.layers if isinstance(st, self.rr.DiffedState)])}") def do_shell(self, *command) -> None: """ @@ -363,7 +364,7 @@ def do_shell(self, *command) -> None: try: print(eval(*command)) except: - print("something went wrong ...") + qdb_print(QDB_MSG.ERROR, "something went wrong ...") def do_quit(self, *args) -> bool: """ diff --git a/qiling/debugger/qdb/utils.py b/qiling/debugger/qdb/utils.py index 95cd35c40..4c1f2d17e 100644 --- a/qiling/debugger/qdb/utils.py +++ b/qiling/debugger/qdb/utils.py @@ -25,6 +25,27 @@ BranchPredictorMIPS, ) +from .const import color, QDB_MSG + + +def qdb_print(msgtype: QDB_MSG, msg: str) -> None: + """ + color printing + """ + + def print_error(msg): + return f"{color.RED}[!] {msg}{color.END}" + + def print_info(msg): + return f"{color.CYAN}[+] {msg}{color.END}" + + color_coated = { + QDB_MSG.ERROR: print_error, + QDB_MSG.INFO : print_info, + }.get(msgtype)(msg) + + print(color_coated) + """ From 97b0378fabfa9754488fee5f34fdfb8a219c4617 Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Thu, 17 Feb 2022 22:56:30 +0000 Subject: [PATCH 149/406] fix type annotaion and new member for Context --- qiling/debugger/qdb/context.py | 3 ++- qiling/debugger/qdb/render/render.py | 15 ++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/qiling/debugger/qdb/context.py b/qiling/debugger/qdb/context.py index 7281270bd..ba6a06fd3 100644 --- a/qiling/debugger/qdb/context.py +++ b/qiling/debugger/qdb/context.py @@ -17,6 +17,7 @@ class Context: def __init__(self, ql): self.ql = ql + self.pointersize = self.ql.pointersize self.unpack = ql.unpack self.unpack16 = ql.unpack16 self.unpack32 = ql.unpack32 @@ -70,7 +71,7 @@ def try_read_pointer(self, address: int) -> Optional[bytes]: try to read pointer size of data from ql.mem """ - return self.try_read(address, self.ql.pointersize) + return self.try_read(address, self.pointersize) def read_string(self, address: int) -> Optional[str]: """ diff --git a/qiling/debugger/qdb/render/render.py b/qiling/debugger/qdb/render/render.py index 382d08dfe..17f763db1 100644 --- a/qiling/debugger/qdb/render/render.py +++ b/qiling/debugger/qdb/render/render.py @@ -5,7 +5,8 @@ -from typing import Mapping, Iterable +from capstone import CsInsn +from typing import Mapping import os, copy from ..context import Context @@ -79,20 +80,20 @@ def render_regs_dump(self, regs, diff_reg=None): print(lines.format(*regs.values())) - def render_stack_dump(self, arch_sp: int, pointer_size: int = 4) -> None: + def render_stack_dump(self, arch_sp: int) -> None: """ helper function for redering stack dump """ for idx in range(self.stack_num): - addr = arch_sp + idx * pointer_size + addr = arch_sp + idx * self.pointersize if (val := self.try_read_pointer(addr)[0]): - print(f"$sp+0x{idx*pointer_size:02x}│ [0x{addr:08x}] —▸ 0x{self.unpack(val):08x}", end="") + print(f"$sp+0x{idx*self.pointersize:02x}│ [0x{addr:08x}] —▸ 0x{self.unpack(val):08x}", end="") # try to dereference wether it's a pointer if (buf := self.try_read_pointer(addr))[0] is not None: - if (addr := self.ql.unpack(buf[0])): + if (addr := self.unpack(buf[0])): # try to dereference again if (buf := self.try_read_pointer(addr))[0] is not None: @@ -124,7 +125,7 @@ def render_assembly(self, lines) -> None: for line in forward: self.print_asm(line) - def swap_reg_name(self, cur_regs: Mapping[str, int], extra_dict=None): + def swap_reg_name(self, cur_regs: Mapping[str, int], extra_dict=None) -> Mapping[str, int]: """ swap register name with more readable register name """ @@ -136,7 +137,7 @@ def swap_reg_name(self, cur_regs: Mapping[str, int], extra_dict=None): return cur_regs - def print_asm(self, insn, to_jump: bool = False) -> None: + def print_asm(self, insn: CsInsn, to_jump: bool = False) -> None: """ helper function for printing assembly instructions, indicates where we are and the branch prediction provided by BranchPredictor From 7de91612304346aa2fc04618970491682e90cff8 Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Fri, 18 Feb 2022 01:24:27 +0000 Subject: [PATCH 150/406] move MemoryManager to memory.py and make it work again --- qiling/debugger/qdb/context.py | 6 ++ qiling/debugger/qdb/qdb.py | 3 +- qiling/debugger/qdb/utils.py | 136 --------------------------------- 3 files changed, 8 insertions(+), 137 deletions(-) diff --git a/qiling/debugger/qdb/context.py b/qiling/debugger/qdb/context.py index ba6a06fd3..9dfa66e8d 100644 --- a/qiling/debugger/qdb/context.py +++ b/qiling/debugger/qdb/context.py @@ -10,6 +10,8 @@ from capstone import CsInsn +from .misc import read_int + class Context: """ base class for accessing context of running qiling instance @@ -91,5 +93,9 @@ def try_read_string(self, address: int) -> Optional[str]: except: pass + @staticmethod + def read_int(s: str) -> int: + return read_int(s) + if __name__ == "__main__": pass diff --git a/qiling/debugger/qdb/qdb.py b/qiling/debugger/qdb/qdb.py index 537352e65..e4a1a9357 100644 --- a/qiling/debugger/qdb/qdb.py +++ b/qiling/debugger/qdb/qdb.py @@ -11,7 +11,8 @@ from qiling.const import QL_ARCH, QL_VERBOSE from qiling.debugger import QlDebugger -from .utils import setup_context_render, setup_branch_predictor, SnapshotManager, MemoryManager +from .utils import setup_context_render, setup_branch_predictor, SnapshotManager +from .memory import MemoryManager from .misc import parse_int, Breakpoint, TempBreakpoint from .const import color diff --git a/qiling/debugger/qdb/utils.py b/qiling/debugger/qdb/utils.py index 4c1f2d17e..740a597f8 100644 --- a/qiling/debugger/qdb/utils.py +++ b/qiling/debugger/qdb/utils.py @@ -239,142 +239,6 @@ def restore(self): self.ql.restore(to_be_restored) -class MemoryManager(Context): - """ - memory manager for handing memory access - """ - - def __init__(self, ql): - super().__init__(ql) - - self.DEFAULT_FMT = ('x', 4, 1) - - self.FORMAT_LETTER = { - "o", # octal - "x", # hex - "d", # decimal - "u", # unsigned decimal - "t", # binary - "f", # float - "a", # address - "i", # instruction - "c", # char - "s", # string - "z", # hex, zero padded on the left - } - - self.SIZE_LETTER = { - "b": 1, # 1-byte, byte - "h": 2, # 2-byte, halfword - "w": 4, # 4-byte, word - "g": 8, # 8-byte, giant - } - - def extract_count(self, t): - return "".join([s for s in t if s.isdigit()]) - - def get_fmt(self, text): - f, s, c = self.DEFAULT_FMT - if self.extract_count(text): - c = int(self.extract_count(text)) - - for char in text.strip(str(c)): - if char in self.SIZE_LETTER.keys(): - s = self.SIZE_LETTER.get(char) - - elif char in self.FORMAT_LETTER: - f = char - - return (f, s, c) - - def fmt_unpack(self, bs: bytes, sz: int) -> int: - return { - 1: lambda x: x[0], - 2: self.ql.unpack16, - 4: self.ql.unpack32, - 8: self.ql.unpack64, - }.get(sz)(bs) - - def parse(self, line: str): - args = line.split() - - if line.startswith("/"): # followed by format letter and size letter - - fmt, *rest = line.strip("/").split() - - rest = "".join(rest) - - fmt = self.get_fmt(fmt) - - elif len(args) == 1: # only address - rest = args[0] - fmt = self.DEFAULT_FMT - - else: - rest = args - - if self.ql.archtype in (QL_ARCH.ARM, QL_ARCH.ARM_THUMB): - rest = rest.replace("fp", "r11") - - elif self.ql.archtype == QL_ARCH.MIPS: - rest = rest.replace("fp", "s8") - - # for supporting addition of register with constant value - elems = rest.split("+") - elems = [elem.strip("$") for elem in elems] - - items = [] - - for elem in elems: - if elem in self.ql.reg.register_mapping.keys(): - if (value := getattr(self.ql.reg, elem, None)): - items.append(value) - else: - items.append(read_int(elem)) - - addr = sum(items) - - ft, sz, ct = fmt - - if ft == "i": - - for offset in range(addr, addr+ct*4, 4): - line = self.disasm(offset) - if line: - print(f"0x{line.address:x}: {line.mnemonic}\t{line.op_str}") - - print() - - else: - lines = 1 if ct <= 4 else math.ceil(ct / 4) - - mem_read = [] - for offset in range(ct): - # append data if read successfully, otherwise return error message - if (data := self.try_read(addr+(offset*sz), sz))[0] is not None: - mem_read.append(data[0]) - - else: - return data[1] - - for line in range(lines): - offset = line * sz * 4 - print(f"0x{addr+offset:x}:\t", end="") - - idx = line * self.ql.pointersize - for each in mem_read[idx:idx+self.ql.pointersize]: - data = self.fmt_unpack(each, sz) - prefix = "0x" if ft in ("x", "a") else "" - pad = '0' + str(sz*2) if ft in ('x', 'a', 't') else '' - ft = ft.lower() if ft in ("x", "o", "b", "d") else ft.lower().replace("t", "b").replace("a", "x") - print(f"{prefix}{data:{pad}{ft}}\t", end="") - - print() - - return True - - def read(self, address: int, size: int): - self.ql.read(address, size) From 706f0f0d961229e6f1cdc64b272425970555cc16 Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Fri, 18 Feb 2022 01:24:44 +0000 Subject: [PATCH 151/406] add memory.py --- qiling/debugger/qdb/memory.py | 150 ++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 qiling/debugger/qdb/memory.py diff --git a/qiling/debugger/qdb/memory.py b/qiling/debugger/qdb/memory.py new file mode 100644 index 000000000..b57d9c321 --- /dev/null +++ b/qiling/debugger/qdb/memory.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from qiling.utils import ql_get_module_function +from qiling.const import QL_ARCH + +from .context import Context +from .arch import ArchCORTEX_M, ArchARM, ArchMIPS, ArchX86 + +class MemoryManager(Context, ArchX86, ArchCORTEX_M, ArchARM, ArchMIPS): + """ + memory manager for handing memory access + """ + + def __init__(self, ql): + super().__init__(ql) + + for arch in ("ArchARM", "ArchMIPS", "ArchCORTEX_M", "ArchX86"): + if ql.archtype.name in str(arch): + imp_arch = ql_get_module_function("qiling.debugger.qdb.arch", arch) + + imp_arch.__init__(self) + + self.DEFAULT_FMT = ('x', 4, 1) + + self.FORMAT_LETTER = { + "o", # octal + "x", # hex + "d", # decimal + "u", # unsigned decimal + "t", # binary + "f", # float + "a", # address + "i", # instruction + "c", # char + "s", # string + "z", # hex, zero padded on the left + } + + self.SIZE_LETTER = { + "b": 1, # 1-byte, byte + "h": 2, # 2-byte, halfword + "w": 4, # 4-byte, word + "g": 8, # 8-byte, giant + } + + def extract_count(self, t): + return "".join([s for s in t if s.isdigit()]) + + def get_fmt(self, text): + f, s, c = self.DEFAULT_FMT + if self.extract_count(text): + c = int(self.extract_count(text)) + + for char in text.strip(str(c)): + if char in self.SIZE_LETTER.keys(): + s = self.SIZE_LETTER.get(char) + + elif char in self.FORMAT_LETTER: + f = char + + return (f, s, c) + + def fmt_unpack(self, bs: bytes, sz: int) -> int: + return { + 1: lambda x: x[0], + 2: self.ql.unpack16, + 4: self.ql.unpack32, + 8: self.ql.unpack64, + }.get(sz)(bs) + + def parse(self, line: str): + args = line.split() + + if line.startswith("/"): # followed by format letter and size letter + + fmt, *rest = line.strip("/").split() + + rest = "".join(rest) + + fmt = self.get_fmt(fmt) + + elif len(args) == 1: # only address + rest = args[0] + fmt = self.DEFAULT_FMT + + else: + rest = args + + if self.ql.archtype in (QL_ARCH.ARM, QL_ARCH.ARM_THUMB): + rest = rest.replace("fp", "r11") + + elif self.ql.archtype == QL_ARCH.MIPS: + rest = rest.replace("fp", "s8") + + # for supporting addition of register with constant value + elems = rest.split("+") + elems = [elem.strip("$") for elem in elems] + + items = [] + + for elem in elems: + if elem in self.ql.reg.register_mapping.keys(): + if (value := self.ql.reg.read(elem)): + items.append(value) + else: + items.append(self.read_int(elem)) + + addr = sum(items) + + ft, sz, ct = fmt + + if ft == "i": + + for offset in range(addr, addr+ct*4, 4): + line = self.disasm(offset) + if line: + print(f"0x{line.address:x}: {line.mnemonic}\t{line.op_str}") + + print() + + else: + lines = 1 if ct <= 4 else math.ceil(ct / 4) + + mem_read = [] + for offset in range(ct): + # append data if read successfully, otherwise return error message + if (data := self.try_read(addr+(offset*sz), sz))[0] is not None: + mem_read.append(data[0]) + + else: + return data[1] + + for line in range(lines): + offset = line * sz * 4 + print(f"0x{addr+offset:x}:\t", end="") + + idx = line * self.ql.pointersize + for each in mem_read[idx:idx+self.ql.pointersize]: + data = self.fmt_unpack(each, sz) + prefix = "0x" if ft in ("x", "a") else "" + pad = '0' + str(sz*2) if ft in ('x', 'a', 't') else '' + ft = ft.lower() if ft in ("x", "o", "b", "d") else ft.lower().replace("t", "b").replace("a", "x") + print(f"{prefix}{data:{pad}{ft}}\t", end="") + + print() + + return True From 3565a4dafb7a99667c5dc6d42f2828bf92ab3d1a Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Fri, 18 Feb 2022 20:06:33 +0800 Subject: [PATCH 152/406] fix step_over --- qiling/debugger/qdb/qdb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiling/debugger/qdb/qdb.py b/qiling/debugger/qdb/qdb.py index e4a1a9357..d58e19aba 100644 --- a/qiling/debugger/qdb/qdb.py +++ b/qiling/debugger/qdb/qdb.py @@ -236,7 +236,7 @@ def do_step_over(self, *args) -> Optional[bool]: prophecy = self.predictor.predict() if prophecy.going: - cur_insn = disasm(self.ql, self.cur_addr) + cur_insn = self.predictor.disasm(self.cur_addr) self.set_breakpoint(self.cur_addr + cur_insn.size, is_temp=True) else: From 5db39440bc5f0d9b0f013df57caf159305367971 Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Fri, 18 Feb 2022 21:19:14 +0800 Subject: [PATCH 153/406] fix typo --- qiling/debugger/qdb/arch/arch_arm.py | 2 +- qiling/debugger/qdb/arch/arch_mips.py | 2 +- qiling/debugger/qdb/memory.py | 8 +++----- qiling/debugger/qdb/render/render.py | 4 ++-- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/qiling/debugger/qdb/arch/arch_arm.py b/qiling/debugger/qdb/arch/arch_arm.py index 6ecf1f7a7..0b7787aed 100644 --- a/qiling/debugger/qdb/arch/arch_arm.py +++ b/qiling/debugger/qdb/arch/arch_arm.py @@ -17,7 +17,7 @@ def __init__(self): "r12", "sp", "lr", "pc", ) - self.regs_need_swaped = { + self.regs_need_swapped = { "sl": "r10", "ip": "r12", "fp": "r11", diff --git a/qiling/debugger/qdb/arch/arch_mips.py b/qiling/debugger/qdb/arch/arch_mips.py index 437160c0e..db162811d 100644 --- a/qiling/debugger/qdb/arch/arch_mips.py +++ b/qiling/debugger/qdb/arch/arch_mips.py @@ -21,6 +21,6 @@ def __init__(self): "ra", "k0", "k1", "pc", ) - self.regs_need_swaped = { + self.regs_need_swapped = { "fp": "s8", } diff --git a/qiling/debugger/qdb/memory.py b/qiling/debugger/qdb/memory.py index b57d9c321..4e5bbdd91 100644 --- a/qiling/debugger/qdb/memory.py +++ b/qiling/debugger/qdb/memory.py @@ -89,11 +89,9 @@ def parse(self, line: str): else: rest = args - if self.ql.archtype in (QL_ARCH.ARM, QL_ARCH.ARM_THUMB): - rest = rest.replace("fp", "r11") - - elif self.ql.archtype == QL_ARCH.MIPS: - rest = rest.replace("fp", "s8") + if (regs_dict := getattr(self, "regs_need_swapped", None)): + for old_reg, new_reg in regs_dict.items(): + rest = rest.replace(old_reg, new_reg) # for supporting addition of register with constant value elems = rest.split("+") diff --git a/qiling/debugger/qdb/render/render.py b/qiling/debugger/qdb/render/render.py index 17f763db1..c47afcdbf 100644 --- a/qiling/debugger/qdb/render/render.py +++ b/qiling/debugger/qdb/render/render.py @@ -56,7 +56,7 @@ def reg_diff(self, cur_regs, saved_reg_dump): if saved_reg_dump: reg_dump = copy.deepcopy(saved_reg_dump) - if getattr(self, "regs_need_swaped", None): + if getattr(self, "regs_need_swapped", None): reg_dump = self.swap_reg_name(reg_dump) return [k for k in cur_regs if cur_regs[k] != reg_dump[k]] @@ -130,7 +130,7 @@ def swap_reg_name(self, cur_regs: Mapping[str, int], extra_dict=None) -> Mapping swap register name with more readable register name """ - target_items = extra_dict.items() if extra_dict else self.regs_need_swaped.items() + target_items = extra_dict.items() if extra_dict else self.regs_need_swapped.items() for old_reg, new_reg in target_items: cur_regs.update({old_reg: cur_regs.pop(new_reg)}) From 1d7968e5acd4b555b9c081d797074431b714b630 Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Sat, 19 Feb 2022 02:56:58 +0800 Subject: [PATCH 154/406] revised a bit MemoryManager --- .../branch_predictor/branch_predictor_x86.py | 2 +- qiling/debugger/qdb/context.py | 1 + qiling/debugger/qdb/memory.py | 43 +++++++++++-------- 3 files changed, 26 insertions(+), 20 deletions(-) diff --git a/qiling/debugger/qdb/branch_predictor/branch_predictor_x86.py b/qiling/debugger/qdb/branch_predictor/branch_predictor_x86.py index 835df6b95..804a26bc9 100644 --- a/qiling/debugger/qdb/branch_predictor/branch_predictor_x86.py +++ b/qiling/debugger/qdb/branch_predictor/branch_predictor_x86.py @@ -127,7 +127,7 @@ def generic_visit(self, node): prophecy.where = eval(new_line) elif line.op_str in self.ql.reg.register_mapping: - prophecy.where = getattr(self.ql.reg, line.op_str) + prophecy.where = self.ql.reg.read(line.op_str) else: prophecy.where = read_int(line.op_str) diff --git a/qiling/debugger/qdb/context.py b/qiling/debugger/qdb/context.py index 9dfa66e8d..5b630c6d3 100644 --- a/qiling/debugger/qdb/context.py +++ b/qiling/debugger/qdb/context.py @@ -23,6 +23,7 @@ def __init__(self, ql): self.unpack = ql.unpack self.unpack16 = ql.unpack16 self.unpack32 = ql.unpack32 + self.unpack64 = ql.unpack64 @property def cur_addr(self): diff --git a/qiling/debugger/qdb/memory.py b/qiling/debugger/qdb/memory.py index 4e5bbdd91..82bf9930a 100644 --- a/qiling/debugger/qdb/memory.py +++ b/qiling/debugger/qdb/memory.py @@ -4,7 +4,6 @@ # from qiling.utils import ql_get_module_function -from qiling.const import QL_ARCH from .context import Context from .arch import ArchCORTEX_M, ArchARM, ArchMIPS, ArchX86 @@ -66,36 +65,43 @@ def get_fmt(self, text): def fmt_unpack(self, bs: bytes, sz: int) -> int: return { 1: lambda x: x[0], - 2: self.ql.unpack16, - 4: self.ql.unpack32, - 8: self.ql.unpack64, + 2: self.unpack16, + 4: self.unpack32, + 8: self.unpack64, }.get(sz)(bs) def parse(self, line: str): - args = line.split() + + # test case + # x/wx address + # x/i address + # x $sp + # x $sp +0xc + # x $sp+0xc + # x $sp + 0xc if line.startswith("/"): # followed by format letter and size letter fmt, *rest = line.strip("/").split() - rest = "".join(rest) - fmt = self.get_fmt(fmt) - elif len(args) == 1: # only address - rest = args[0] - fmt = self.DEFAULT_FMT - else: - rest = args + args = line.split() + rest = args[0] if len(args) == 1 else args + fmt = self.DEFAULT_FMT if (regs_dict := getattr(self, "regs_need_swapped", None)): - for old_reg, new_reg in regs_dict.items(): - rest = rest.replace(old_reg, new_reg) + for each in rest: + if each in regs_dict: + + # for simple calculation with register and address + new_line = rest - # for supporting addition of register with constant value - elems = rest.split("+") - elems = [elem.strip("$") for elem in elems] + # substitue register name with real value + for each_reg in filter(lambda r: len(r) == 3, self.ql.reg.register_mapping.keys()): + if each_reg in new_line: + new_line = re.sub(each_reg, hex(self.read_reg(each_reg)), new_line) items = [] @@ -117,7 +123,6 @@ def parse(self, line: str): if line: print(f"0x{line.address:x}: {line.mnemonic}\t{line.op_str}") - print() else: lines = 1 if ct <= 4 else math.ceil(ct / 4) @@ -143,6 +148,6 @@ def parse(self, line: str): ft = ft.lower() if ft in ("x", "o", "b", "d") else ft.lower().replace("t", "b").replace("a", "x") print(f"{prefix}{data:{pad}{ft}}\t", end="") - print() + print() return True From a89a338bd0d5e93e5113a8dde4c752719e006b6d Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Sat, 19 Feb 2022 22:40:31 +0000 Subject: [PATCH 155/406] make some arch-sepecific member variable to property --- qiling/debugger/qdb/arch/arch.py | 18 +- qiling/debugger/qdb/arch/arch_arm.py | 9 +- qiling/debugger/qdb/arch/arch_mips.py | 9 +- qiling/debugger/qdb/arch/arch_x86.py | 11 +- qiling/debugger/qdb/memory.py | 226 +++++++++++++++----------- qiling/debugger/qdb/qdb.py | 4 +- 6 files changed, 160 insertions(+), 117 deletions(-) diff --git a/qiling/debugger/qdb/arch/arch.py b/qiling/debugger/qdb/arch/arch.py index 95ecb9d6f..3d0bf9bbe 100644 --- a/qiling/debugger/qdb/arch/arch.py +++ b/qiling/debugger/qdb/arch/arch.py @@ -4,25 +4,19 @@ # +from qiling.const import QL_ARCH class Arch: """ base class for arch """ - _SUPPORTED_ARCH = ["ArchARM", "ArchCORTEX_M", "ArchMIPS", "ArchX86"] - - # FIXME: this is a dirty hack for setup archtype at initialization phase - @staticmethod - def set_archtype(self): - for archtype in self._SUPPORTED_ARCH: - for each_type in type(self).mro(): - if archtype in str(each_type): - return archtype - def __init__(self): - self.arch_insn_size = 4 - self.archtype = self.set_archtype(self) + pass + + @property + def arch_insn_size(self): + return 4 def read_insn(self, address: int): return self.read_mem(address, self.arch_insn_size) diff --git a/qiling/debugger/qdb/arch/arch_arm.py b/qiling/debugger/qdb/arch/arch_arm.py index 0b7787aed..5f11c7aaa 100644 --- a/qiling/debugger/qdb/arch/arch_arm.py +++ b/qiling/debugger/qdb/arch/arch_arm.py @@ -10,14 +10,19 @@ class ArchARM(Arch): def __init__(self): super().__init__() - self.regs = ( + + @property + def regs(self): + return ( "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "sp", "lr", "pc", ) - self.regs_need_swapped = { + @property + def regs_need_swapped(self): + return { "sl": "r10", "ip": "r12", "fp": "r11", diff --git a/qiling/debugger/qdb/arch/arch_mips.py b/qiling/debugger/qdb/arch/arch_mips.py index db162811d..d262b0a90 100644 --- a/qiling/debugger/qdb/arch/arch_mips.py +++ b/qiling/debugger/qdb/arch/arch_mips.py @@ -10,7 +10,10 @@ class ArchMIPS(Arch): def __init__(self): super().__init__() - self.regs = ( + + @property + def regs(self): + return ( "gp", "at", "v0", "v1", "a0", "a1", "a2", "a3", "t0", "t1", "t2", "t3", @@ -21,6 +24,8 @@ def __init__(self): "ra", "k0", "k1", "pc", ) - self.regs_need_swapped = { + @property + def regs_need_swapped(self): + return { "fp": "s8", } diff --git a/qiling/debugger/qdb/arch/arch_x86.py b/qiling/debugger/qdb/arch/arch_x86.py index 562e20b4b..4aae910c8 100644 --- a/qiling/debugger/qdb/arch/arch_x86.py +++ b/qiling/debugger/qdb/arch/arch_x86.py @@ -10,7 +10,14 @@ class ArchX86(Arch): def __init__(self): super().__init__() - self.regs = ( + + @property + def arch_insn_size(self): + return 15 + + @property + def regs(self): + return ( "eax", "ebx", "ecx", "edx", "esp", "ebp", "esi", "edi", "eip", "ss", "cs", "ds", "es", @@ -22,7 +29,7 @@ def read_insn(self, address: int) -> bytes: # always assume the maxium size for disassembler to tell # what is it exactly. - return self.read_mem(address, 15) + return self.read_mem(address, self.arch_insn_size) @staticmethod def get_flags(bits: int) -> Mapping[str, bool]: diff --git a/qiling/debugger/qdb/memory.py b/qiling/debugger/qdb/memory.py index 82bf9930a..a6a85ccb8 100644 --- a/qiling/debugger/qdb/memory.py +++ b/qiling/debugger/qdb/memory.py @@ -3,28 +3,38 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from qiling.utils import ql_get_module_function +from qiling.const import QL_ARCH from .context import Context from .arch import ArchCORTEX_M, ArchARM, ArchMIPS, ArchX86 +import re, ast, math -class MemoryManager(Context, ArchX86, ArchCORTEX_M, ArchARM, ArchMIPS): - """ - memory manager for handing memory access - """ - def __init__(self, ql): - super().__init__(ql) - for arch in ("ArchARM", "ArchMIPS", "ArchCORTEX_M", "ArchX86"): - if ql.archtype.name in str(arch): - imp_arch = ql_get_module_function("qiling.debugger.qdb.arch", arch) +def setup_memory_Manager(ql): - imp_arch.__init__(self) + arch_type = { + QL_ARCH.X86: ArchX86, + QL_ARCH.MIPS: ArchMIPS, + QL_ARCH.ARM: ArchARM, + QL_ARCH.CORTEX_M: ArchCORTEX_M, + }.get(ql.archtype) - self.DEFAULT_FMT = ('x', 4, 1) + class MemoryManager(Context, arch_type): + """ + memory manager for handing memory access + """ - self.FORMAT_LETTER = { + def __init__(self, ql): + super().__init__(ql) + + @property + def get_default_fmt(self): + return ('x', 4, 1) + + @property + def get_format_letter(self): + return { "o", # octal "x", # hex "d", # decimal @@ -38,116 +48,138 @@ def __init__(self, ql): "z", # hex, zero padded on the left } - self.SIZE_LETTER = { - "b": 1, # 1-byte, byte - "h": 2, # 2-byte, halfword - "w": 4, # 4-byte, word - "g": 8, # 8-byte, giant - } - - def extract_count(self, t): - return "".join([s for s in t if s.isdigit()]) + @property + def get_size_letter(self): + return { + "b": 1, # 1-byte, byte + "h": 2, # 2-byte, halfword + "w": 4, # 4-byte, word + "g": 8, # 8-byte, giant + } - def get_fmt(self, text): - f, s, c = self.DEFAULT_FMT - if self.extract_count(text): - c = int(self.extract_count(text)) + def extract_count(self, t): + return "".join([s for s in t if s.isdigit()]) - for char in text.strip(str(c)): - if char in self.SIZE_LETTER.keys(): - s = self.SIZE_LETTER.get(char) + def get_fmt(self, text): + f, s, c = self.get_default_fmt + if self.extract_count(text): + c = int(self.extract_count(text)) - elif char in self.FORMAT_LETTER: - f = char + for char in text.strip(str(c)): + if char in self.get_size_letter.keys(): + s = self.get_size_letter.get(char) - return (f, s, c) + elif char in self.get_format_letter: + f = char - def fmt_unpack(self, bs: bytes, sz: int) -> int: - return { - 1: lambda x: x[0], - 2: self.unpack16, - 4: self.unpack32, - 8: self.unpack64, - }.get(sz)(bs) + return (f, s, c) - def parse(self, line: str): + def fmt_unpack(self, bs: bytes, sz: int) -> int: + return { + 1: lambda x: x[0], + 2: self.unpack16, + 4: self.unpack32, + 8: self.unpack64, + }.get(sz)(bs) - # test case - # x/wx address - # x/i address - # x $sp - # x $sp +0xc - # x $sp+0xc - # x $sp + 0xc + def handle_i(self, addr, ct=1): + result = [] - if line.startswith("/"): # followed by format letter and size letter + for offset in range(addr, addr+ct*4, 4): + if (line := self.disasm(offset)): + result.append(line) - fmt, *rest = line.strip("/").split() + return result - fmt = self.get_fmt(fmt) - else: - args = line.split() - rest = args[0] if len(args) == 1 else args - fmt = self.DEFAULT_FMT + def parse(self, line: str): - if (regs_dict := getattr(self, "regs_need_swapped", None)): - for each in rest: - if each in regs_dict: + # test case + # x/wx address + # x/i address + # x $sp + # x $sp +0xc + # x $sp+0xc + # x $sp + 0xc - # for simple calculation with register and address - new_line = rest + if line.startswith("/"): # followed by format letter and size letter - # substitue register name with real value - for each_reg in filter(lambda r: len(r) == 3, self.ql.reg.register_mapping.keys()): - if each_reg in new_line: - new_line = re.sub(each_reg, hex(self.read_reg(each_reg)), new_line) + fmt, *rest = line.strip("/").split() - items = [] + fmt = self.get_fmt(fmt) - for elem in elems: - if elem in self.ql.reg.register_mapping.keys(): - if (value := self.ql.reg.read(elem)): - items.append(value) else: - items.append(self.read_int(elem)) + args = line.split() - addr = sum(items) + rest = args[0] if len(args) == 1 else args - ft, sz, ct = fmt + fmt = self.get_default_fmt - if ft == "i": + if len(rest) == 0: + return - for offset in range(addr, addr+ct*4, 4): - line = self.disasm(offset) - if line: - print(f"0x{line.address:x}: {line.mnemonic}\t{line.op_str}") + line = [] + if (regs_dict := getattr(self, "regs_need_swapped", None)): + for each in rest: + for reg in regs_dict: + line.append(regs_dict[each] if each in regs_dict else each) + + # for simple calculation with register and address + line = " ".join(line) + # substitue register name with real value + for each_reg in filter(lambda r: len(r) == 2, self.ql.reg.register_mapping): + reg = f"${each_reg}" + if reg in line: + line = re.sub(f"\{reg}", hex(self.ql.reg.read(each_reg)), line) + breakpoint() + + class AST_checker(ast.NodeVisitor): + def generic_visit(self, node): + if type(node) in (ast.Module, ast.Expr, ast.BinOp, ast.Constant, ast.Add, ast.Mult, ast.Sub): + ast.NodeVisitor.generic_visit(self, node) + else: + raise ParseError("malform or invalid ast node") + + ft, sz, ct = fmt + + checker = AST_checker() + ast_tree = ast.parse(line) + checker.visit(ast_tree) + + addr = eval(line) + + if ft == "i": + output = self.handle_i(addr, ct) + for each in output: + print(f"0x{each.address:x}: {each.mnemonic}\t{each.op_str}") + + else: + lines = 1 if ct <= 4 else math.ceil(ct / 4) - else: - lines = 1 if ct <= 4 else math.ceil(ct / 4) + mem_read = [] + for offset in range(ct): + # append data if read successfully, otherwise return error message + if (data := self.try_read(addr+(offset*sz), sz))[0] is not None: + mem_read.append(data[0]) - mem_read = [] - for offset in range(ct): - # append data if read successfully, otherwise return error message - if (data := self.try_read(addr+(offset*sz), sz))[0] is not None: - mem_read.append(data[0]) + else: + return data[1] - else: - return data[1] + for line in range(lines): + offset = line * sz * 4 + print(f"0x{addr+offset:x}:\t", end="") - for line in range(lines): - offset = line * sz * 4 - print(f"0x{addr+offset:x}:\t", end="") + idx = line * self.ql.pointersize + for each in mem_read[idx:idx+self.ql.pointersize]: + data = self.fmt_unpack(each, sz) + prefix = "0x" if ft in ("x", "a") else "" + pad = '0' + str(sz*2) if ft in ('x', 'a', 't') else '' + ft = ft.lower() if ft in ("x", "o", "b", "d") else ft.lower().replace("t", "b").replace("a", "x") + print(f"{prefix}{data:{pad}{ft}}\t", end="") - idx = line * self.ql.pointersize - for each in mem_read[idx:idx+self.ql.pointersize]: - data = self.fmt_unpack(each, sz) - prefix = "0x" if ft in ("x", "a") else "" - pad = '0' + str(sz*2) if ft in ('x', 'a', 't') else '' - ft = ft.lower() if ft in ("x", "o", "b", "d") else ft.lower().replace("t", "b").replace("a", "x") - print(f"{prefix}{data:{pad}{ft}}\t", end="") + print() - print() + return True - return True + return MemoryManager(ql) diff --git a/qiling/debugger/qdb/qdb.py b/qiling/debugger/qdb/qdb.py index d58e19aba..fba72017a 100644 --- a/qiling/debugger/qdb/qdb.py +++ b/qiling/debugger/qdb/qdb.py @@ -12,7 +12,7 @@ from qiling.debugger import QlDebugger from .utils import setup_context_render, setup_branch_predictor, SnapshotManager -from .memory import MemoryManager +from .memory import setup_memory_Manager from .misc import parse_int, Breakpoint, TempBreakpoint from .const import color @@ -35,7 +35,7 @@ def __init__(self, ql: Qiling, init_hook: str = "", rr: bool = False) -> None: self.bp_list = {} self.rr = SnapshotManager(ql) if rr else None - self.mm = MemoryManager(ql) + self.mm = setup_memory_Manager(ql) self.predictor = setup_branch_predictor(ql) self.render = setup_context_render(ql, self.predictor) From da79d895676373f008e163862c454175448280d6 Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Sun, 20 Feb 2022 03:03:27 +0000 Subject: [PATCH 156/406] use ast checker in MemoryManager --- qiling/debugger/qdb/memory.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qiling/debugger/qdb/memory.py b/qiling/debugger/qdb/memory.py index a6a85ccb8..439136073 100644 --- a/qiling/debugger/qdb/memory.py +++ b/qiling/debugger/qdb/memory.py @@ -132,7 +132,6 @@ def parse(self, line: str): reg = f"${each_reg}" if reg in line: line = re.sub(f"\{reg}", hex(self.ql.reg.read(each_reg)), line) - breakpoint() class AST_checker(ast.NodeVisitor): def generic_visit(self, node): From 1aa88428c52b35f35632e89ef5aedc6fdb486890 Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Mon, 21 Feb 2022 01:06:53 +0000 Subject: [PATCH 157/406] MemoryManager should work now and move check_and_eval to misc.py --- .../branch_predictor/branch_predictor_x86.py | 13 ++-------- qiling/debugger/qdb/context.py | 2 +- qiling/debugger/qdb/memory.py | 25 +++++++++---------- qiling/debugger/qdb/misc.py | 20 +++++++++++++++ qiling/debugger/qdb/qdb.py | 3 --- qiling/debugger/qdb/utils.py | 1 + 6 files changed, 36 insertions(+), 28 deletions(-) diff --git a/qiling/debugger/qdb/branch_predictor/branch_predictor_x86.py b/qiling/debugger/qdb/branch_predictor/branch_predictor_x86.py index 804a26bc9..1e9e0e562 100644 --- a/qiling/debugger/qdb/branch_predictor/branch_predictor_x86.py +++ b/qiling/debugger/qdb/branch_predictor/branch_predictor_x86.py @@ -9,6 +9,7 @@ from .branch_predictor import * from ..arch import ArchX86 +from ..misc import check_and_eval class BranchPredictorX86(BranchPredictor, ArchX86): """ @@ -98,12 +99,6 @@ def predict(self): if prophecy.going: takeaway_list = ["ptr", "dword", "[", "]"] - class AST_checker(ast.NodeVisitor): - def generic_visit(self, node): - if type(node) in (ast.Module, ast.Expr, ast.BinOp, ast.Constant, ast.Add, ast.Mult, ast.Sub): - ast.NodeVisitor.generic_visit(self, node) - else: - raise ParseError("malform or invalid ast node") if len(line.op_str.split()) > 1: new_line = line.op_str.replace(":", "+") @@ -119,12 +114,8 @@ def generic_visit(self, node): if each_reg in new_line: new_line = re.sub(each_reg, hex(self.read_reg(each_reg)), new_line) - checker = AST_checker() - ast_tree = ast.parse(new_line) - checker.visit(ast_tree) - - prophecy.where = eval(new_line) + prophecy.where = check_and_eval(new_line) elif line.op_str in self.ql.reg.register_mapping: prophecy.where = self.ql.reg.read(line.op_str) diff --git a/qiling/debugger/qdb/context.py b/qiling/debugger/qdb/context.py index 5b630c6d3..a46f8214f 100644 --- a/qiling/debugger/qdb/context.py +++ b/qiling/debugger/qdb/context.py @@ -74,7 +74,7 @@ def try_read_pointer(self, address: int) -> Optional[bytes]: try to read pointer size of data from ql.mem """ - return self.try_read(address, self.pointersize) + return self.try_read(address, self.arch_insn_size) def read_string(self, address: int) -> Optional[str]: """ diff --git a/qiling/debugger/qdb/memory.py b/qiling/debugger/qdb/memory.py index 439136073..bfb684144 100644 --- a/qiling/debugger/qdb/memory.py +++ b/qiling/debugger/qdb/memory.py @@ -7,7 +7,8 @@ from .context import Context from .arch import ArchCORTEX_M, ArchARM, ArchMIPS, ArchX86 -import re, ast, math +from .misc import check_and_eval +import re, math @@ -122,7 +123,12 @@ def parse(self, line: str): if (regs_dict := getattr(self, "regs_need_swapped", None)): for each in rest: for reg in regs_dict: - line.append(regs_dict[each] if each in regs_dict else each) + if each in regs_dict: + line.append(regs_dict[each]) + else: + line.append(each) + else: + line = rest # for simple calculation with register and address @@ -133,20 +139,13 @@ def parse(self, line: str): if reg in line: line = re.sub(f"\{reg}", hex(self.ql.reg.read(each_reg)), line) - class AST_checker(ast.NodeVisitor): - def generic_visit(self, node): - if type(node) in (ast.Module, ast.Expr, ast.BinOp, ast.Constant, ast.Add, ast.Mult, ast.Sub): - ast.NodeVisitor.generic_visit(self, node) - else: - raise ParseError("malform or invalid ast node") ft, sz, ct = fmt - checker = AST_checker() - ast_tree = ast.parse(line) - checker.visit(ast_tree) - - addr = eval(line) + try: + addr = check_and_eval(line) + except: + return "something went wrong ..." if ft == "i": output = self.handle_i(addr, ct) diff --git a/qiling/debugger/qdb/misc.py b/qiling/debugger/qdb/misc.py index 9a946c50d..bfff5aa8e 100644 --- a/qiling/debugger/qdb/misc.py +++ b/qiling/debugger/qdb/misc.py @@ -5,6 +5,26 @@ from typing import Callable, Optional +import ast + +def check_and_eval(line: str): + """ + This function will valid all type of nodes and evaluate it if nothing went wrong + """ + + class AST_checker(ast.NodeVisitor): + def generic_visit(self, node): + if type(node) in (ast.Module, ast.Expr, ast.BinOp, ast.Constant, ast.Add, ast.Mult, ast.Sub): + ast.NodeVisitor.generic_visit(self, node) + else: + raise ParseError("malform or invalid ast node") + + checker = AST_checker() + ast_tree = ast.parse(line) + checker.visit(ast_tree) + + return eval(line) + class Breakpoint: """ diff --git a/qiling/debugger/qdb/qdb.py b/qiling/debugger/qdb/qdb.py index fba72017a..170453e46 100644 --- a/qiling/debugger/qdb/qdb.py +++ b/qiling/debugger/qdb/qdb.py @@ -322,11 +322,8 @@ def do_examine(self, line: str) -> None: e.g. x/4wx 0x41414141 , print 4 word size begin from address 0x41414141 in hex """ - # try: if type(err_msg := self.mm.parse(line)) is str: qdb_print(QDB_MSG.ERROR, err_msg) - # except: - # qdb_print(QDB_MSG.ERROR, "something went wrong ...") def do_start(self, *args) -> None: """ diff --git a/qiling/debugger/qdb/utils.py b/qiling/debugger/qdb/utils.py index 740a597f8..d4cd68b00 100644 --- a/qiling/debugger/qdb/utils.py +++ b/qiling/debugger/qdb/utils.py @@ -26,6 +26,7 @@ ) from .const import color, QDB_MSG + def qdb_print(msgtype: QDB_MSG, msg: str) -> None: From 63b2e58817110c0bdf119baa7739650d73e2e40b Mon Sep 17 00:00:00 2001 From: lazymio Date: Tue, 22 Feb 2022 19:41:24 +0100 Subject: [PATCH 158/406] Fix wrong arguments for validate_crash_callback --- qiling/extensions/afl/afl.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/qiling/extensions/afl/afl.py b/qiling/extensions/afl/afl.py index 8693145f6..b2f0448d1 100644 --- a/qiling/extensions/afl/afl.py +++ b/qiling/extensions/afl/afl.py @@ -7,7 +7,7 @@ def ql_afl_fuzz(ql: Qiling, input_file: str, place_input_callback: Callable[["Qiling", bytes, int], bool], exits: List[int], - validate_crash_callback: Callable[["Qiling", bytes, int], bool] = None, + validate_crash_callback: Callable[["Qiling", int, bytes, int], bool] = None, always_validate: bool = False, persistent_iters: int = 1): """ Fuzz a range of code with afl++. @@ -28,13 +28,18 @@ def ql_afl_fuzz(ql: Qiling, def _ql_afl_place_input_wrapper(uc, input_bytes, iters, data): (ql, cb, _) = data + if cb: + return cb(ql, input_bytes, iters) + else: + return False - return cb(ql, input_bytes, iters) - - def _ql_afl_validate_wrapper(uc, input_bytes, iters, data): + def _ql_afl_validate_wrapper(uc, result, input_bytes, iters, data): (ql, _, cb) = data - return cb(ql, input_bytes, iters) + if cb: + return cb(ql, result, input_bytes, iters) + else: + return False data = (ql, place_input_callback, validate_crash_callback) try: From c40adca9148b748fa1754568d0256c25b806c14e Mon Sep 17 00:00:00 2001 From: Nguyen Anh Quynh Date: Fri, 25 Feb 2022 12:42:44 +0800 Subject: [PATCH 159/406] Update setup.py bump unicorn version to 2.0-rc6 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 16f83f6ed..398a7fb57 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ requirements = [ "capstone>=4.0.1", - "unicorn>=2.0.0-rc5", + "unicorn>=2.0.0-rc6", "pefile>=2021.9.3", "python-registry>=1.3.1", "keystone-engine>=0.9.2", From 738b79d1447c6d9fd9d1cdc3c1bbc9074500c6dd Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Sun, 27 Feb 2022 00:31:22 +0000 Subject: [PATCH 160/406] add scripting ability and correspoonding test --- qiling/debugger/qdb/arch/arch.py | 4 ++++ qiling/debugger/qdb/context.py | 2 +- qiling/debugger/qdb/memory.py | 9 +++++-- qiling/debugger/qdb/qdb.py | 26 +++++++++++++++++---- qiling/debugger/qdb/utils.py | 15 ++++++++++++ qiling/utils.py | 2 +- tests/qdb_scripts/arm.qdb | 13 +++++++++++ tests/qdb_scripts/mips32el.qdb | 13 +++++++++++ tests/qdb_scripts/x86.qdb | 11 +++++++++ tests/test_onlinux.sh | 1 + tests/test_qdb.py | 40 ++++++++++++++++++++++++++++++++ 11 files changed, 128 insertions(+), 8 deletions(-) create mode 100644 tests/qdb_scripts/arm.qdb create mode 100644 tests/qdb_scripts/mips32el.qdb create mode 100644 tests/qdb_scripts/x86.qdb create mode 100644 tests/test_qdb.py diff --git a/qiling/debugger/qdb/arch/arch.py b/qiling/debugger/qdb/arch/arch.py index 3d0bf9bbe..f84bdcd74 100644 --- a/qiling/debugger/qdb/arch/arch.py +++ b/qiling/debugger/qdb/arch/arch.py @@ -18,5 +18,9 @@ def __init__(self): def arch_insn_size(self): return 4 + @property + def archbit(self): + return 4 + def read_insn(self, address: int): return self.read_mem(address, self.arch_insn_size) diff --git a/qiling/debugger/qdb/context.py b/qiling/debugger/qdb/context.py index a46f8214f..8faa1206d 100644 --- a/qiling/debugger/qdb/context.py +++ b/qiling/debugger/qdb/context.py @@ -74,7 +74,7 @@ def try_read_pointer(self, address: int) -> Optional[bytes]: try to read pointer size of data from ql.mem """ - return self.try_read(address, self.arch_insn_size) + return self.try_read(address, self.archbit) def read_string(self, address: int) -> Optional[str]: """ diff --git a/qiling/debugger/qdb/memory.py b/qiling/debugger/qdb/memory.py index bfb684144..23b66e2bb 100644 --- a/qiling/debugger/qdb/memory.py +++ b/qiling/debugger/qdb/memory.py @@ -112,7 +112,7 @@ def parse(self, line: str): else: args = line.split() - rest = args[0] if len(args) == 1 else args + rest = [args[0]] if len(args) == 1 else args fmt = self.get_default_fmt @@ -134,10 +134,15 @@ def parse(self, line: str): line = " ".join(line) # substitue register name with real value + for each_reg in filter(lambda r: len(r) == 3, self.ql.reg.register_mapping): + reg = f"${each_reg}" + if reg in line: + line = re.sub(f"\\{reg}", hex(self.ql.reg.read(each_reg)), line) + for each_reg in filter(lambda r: len(r) == 2, self.ql.reg.register_mapping): reg = f"${each_reg}" if reg in line: - line = re.sub(f"\{reg}", hex(self.ql.reg.read(each_reg)), line) + line = re.sub(f"\\{reg}", hex(self.ql.reg.read(each_reg)), line) ft, sz, ct = fmt diff --git a/qiling/debugger/qdb/qdb.py b/qiling/debugger/qdb/qdb.py index 170453e46..cd4371250 100644 --- a/qiling/debugger/qdb/qdb.py +++ b/qiling/debugger/qdb/qdb.py @@ -11,7 +11,7 @@ from qiling.const import QL_ARCH, QL_VERBOSE from qiling.debugger import QlDebugger -from .utils import setup_context_render, setup_branch_predictor, SnapshotManager +from .utils import setup_context_render, setup_branch_predictor, SnapshotManager, run_qdb_script from .memory import setup_memory_Manager from .misc import parse_int, Breakpoint, TempBreakpoint from .const import color @@ -23,7 +23,7 @@ class QlQdb(cmd.Cmd, QlDebugger): The built-in debugger of Qiling Framework """ - def __init__(self, ql: Qiling, init_hook: str = "", rr: bool = False) -> None: + def __init__(self, ql: Qiling, init_hook: str = "", rr: bool = False, script: str = "") -> None: """ @init_hook: the entry to be paused at @rr: record/replay debugging @@ -32,6 +32,7 @@ def __init__(self, ql: Qiling, init_hook: str = "", rr: bool = False) -> None: self.ql = ql self.prompt = f"{color.BOLD}{color.RED}Qdb> {color.END}" self._saved_reg_dump = None + self._script = script self.bp_list = {} self.rr = SnapshotManager(ql) if rr else None @@ -82,8 +83,11 @@ def bp_handler(ql, address, size, bp_list): else: self.init_state = self.ql.save() - self.do_context() - self.interactive() + if self._script: + run_qdb_script(self, self._script) + else: + self.do_context() + self.interactive() @property def cur_addr(self) -> int: @@ -354,6 +358,17 @@ def do_show(self, *args) -> None: if self.rr: qdb_print(QDB_MSG.INFO, f"Snapshots: {len([st for st in self.rr.layers if isinstance(st, self.rr.DiffedState)])}") + def do_script(self, filename: str) -> None: + """ + usage: script [filename] + load a script for automate qdb funcitonality, execute qdb command line by line basically + """ + + if filename: + run_qdb_script(self, filename) + else: + qdb_print(QDB_MSG.ERROR, "parameter filename must be specified") + def do_shell(self, *command) -> None: """ run python code @@ -370,12 +385,15 @@ def do_quit(self, *args) -> bool: """ self.ql.stop() + if self._script: + return True exit() def do_EOF(self, *args) -> None: """ handle Ctrl+D """ + if input(f"{color.RED}[!] Are you sure about saying good bye ~ ? [Y/n]{color.END} ").strip() == "Y": self.do_quit() diff --git a/qiling/debugger/qdb/utils.py b/qiling/debugger/qdb/utils.py index d4cd68b00..3f6a3b35a 100644 --- a/qiling/debugger/qdb/utils.py +++ b/qiling/debugger/qdb/utils.py @@ -80,6 +80,21 @@ def setup_context_render(ql, predictor): QL_ARCH.MIPS: ContextRenderMIPS, }.get(ql.archtype)(ql, predictor) +def run_qdb_script(qdb, filename: str) -> None: + with open(filename) as fd: + for line in iter(fd.readline, ""): + + # skip commented and empty line + if line.startswith("#") or line == "\n": + continue + + cmd, arg, _ = qdb.parseline(line) + func = getattr(qdb, f"do_{cmd}") + if arg: + func(arg) + else: + func() + """ diff --git a/qiling/utils.py b/qiling/utils.py index 887728151..a4d78d049 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -587,4 +587,4 @@ def verify_ret(ql, err): else: raise else: - raise \ No newline at end of file + raise diff --git a/tests/qdb_scripts/arm.qdb b/tests/qdb_scripts/arm.qdb new file mode 100644 index 000000000..5bfa261a9 --- /dev/null +++ b/tests/qdb_scripts/arm.qdb @@ -0,0 +1,13 @@ +# This line is demonstrate comment in qdb script + +x/10wx 0x7ff3cee4 +x $sp +x $sp + 0x10 +x/5i 0x047ba9e0 +b 0x047ba9ec +c +s +n +p +p +q diff --git a/tests/qdb_scripts/mips32el.qdb b/tests/qdb_scripts/mips32el.qdb new file mode 100644 index 000000000..0e8342baf --- /dev/null +++ b/tests/qdb_scripts/mips32el.qdb @@ -0,0 +1,13 @@ +# This line is demonstrate comment in qdb script + +x/10wx 0x7ff3cec0 +x $sp +x $sp + 0x10 +x/5i 0x047bac40 +b 0x047bac50 +c +s +n +p +p +q diff --git a/tests/qdb_scripts/x86.qdb b/tests/qdb_scripts/x86.qdb new file mode 100644 index 000000000..d06623328 --- /dev/null +++ b/tests/qdb_scripts/x86.qdb @@ -0,0 +1,11 @@ +# This line is demonstrate comment in qdb script + +x/4wx 0x7ff3cee0 +x $esp +x $esp + 0x4 +x/5i 0x047bac70 +s +n +p +p +q diff --git a/tests/test_onlinux.sh b/tests/test_onlinux.sh index af84612d1..176693cbf 100755 --- a/tests/test_onlinux.sh +++ b/tests/test_onlinux.sh @@ -16,4 +16,5 @@ python3 ./test_android.py && python3 ./test_mcu.py && python3 ./test_evm.py && python3 ./test_blob.py && +python3 ./test_qdb.py && echo "Done Test" diff --git a/tests/test_qdb.py b/tests/test_qdb.py new file mode 100644 index 000000000..37eeac30c --- /dev/null +++ b/tests/test_qdb.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +import unittest +from qiling import Qiling + + +class DebuggerTest(unittest.TestCase): + + # def test_qdb_mips32el_hello(self): + # rootfs = "../examples/rootfs/mips32el_linux" + # path = rootfs + "/bin/mips32el_hello" + + # ql = Qiling([path], rootfs) + # ql.debugger = "qdb::rr:qdb_scripts/mips32el.qdb" + # ql.run() + # del ql + + # def test_qdb_arm_hello(self): + # rootfs = "../examples/rootfs/arm_linux" + # path = rootfs + "/bin/arm_hello" + + # ql = Qiling([path], rootfs) + # ql.debugger = "qdb::rr:qdb_scripts/arm.qdb" + # ql.run() + # del ql + + def test_qdb_x86_hello(self): + rootfs = "../examples/rootfs/x86_linux" + path = rootfs + "/bin/x86_hello" + + ql = Qiling([path], rootfs) + ql.debugger = "qdb::rr:qdb_scripts/x86.qdb" + ql.run() + del ql + +if __name__ == "__main__": + unittest.main() From ef2688a29bf292d26949b4b47ac05c2414b72a0e Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 20 Feb 2022 15:36:57 +0200 Subject: [PATCH 161/406] 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 162/406] 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 163/406] 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 164/406] 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 165/406] 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 166/406] 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 167/406] 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 168/406] 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 169/406] 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 170/406] 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 171/406] 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 172/406] 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 173/406] 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 174/406] 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 175/406] 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 176/406] 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 177/406] 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 178/406] 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 = { From 0beb5499670d9fc3ab0ddf3d1ddc0bd66d6f0ced Mon Sep 17 00:00:00 2001 From: xwings Date: Thu, 3 Mar 2022 21:09:20 +0800 Subject: [PATCH 179/406] fix new style, archtype --- examples/rootfs | 2 +- qiling/loader/pe.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/rootfs b/examples/rootfs index d8a9b0d6c..999e5008a 160000 --- a/examples/rootfs +++ b/examples/rootfs @@ -1 +1 @@ -Subproject commit d8a9b0d6c52a3c5bc627c055d5f711dacbb1a1f6 +Subproject commit 999e5008aa96f34ebb5c4fa90a072070dfca9742 diff --git a/qiling/loader/pe.py b/qiling/loader/pe.py index ff78352e5..2a27fd630 100644 --- a/qiling/loader/pe.py +++ b/qiling/loader/pe.py @@ -457,10 +457,10 @@ def run(self): self.sys_dlls.append(b"ntoskrnl.exe") - if self.ql.archtype == QL_ARCH.X86: + if self.ql.arch.type == QL_ARCH.X86: WINOSARCH = "OS32" self.structure_last_addr = FS_SEGMENT_ADDR - elif self.ql.archtype == QL_ARCH.X8664: + elif self.ql.arch.type == QL_ARCH.X8664: WINOSARCH = "OS64" self.structure_last_addr = GS_SEGMENT_ADDR From 3fad7e24466a4c790847ecf7ad48396e9a190421 Mon Sep 17 00:00:00 2001 From: xwings Date: Thu, 3 Mar 2022 21:10:37 +0800 Subject: [PATCH 180/406] sync rootfs --- examples/rootfs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/rootfs b/examples/rootfs index 999e5008a..3443f9de3 160000 --- a/examples/rootfs +++ b/examples/rootfs @@ -1 +1 @@ -Subproject commit 999e5008aa96f34ebb5c4fa90a072070dfca9742 +Subproject commit 3443f9de35107f98cc5fb20ac17a4cd6480f9ef4 From 7634856143c7c81cdcb036950a0b6cf9cc75c967 Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Fri, 4 Mar 2022 00:38:02 +0000 Subject: [PATCH 181/406] adapt changes according to qiling-next --- qiling/debugger/qdb/arch/arch_arm.py | 2 +- .../qdb/branch_predictor/branch_predictor.py | 2 +- .../branch_predictor/branch_predictor_arm.py | 10 +- .../branch_predictor/branch_predictor_mips.py | 2 +- .../branch_predictor/branch_predictor_x86.py | 12 +- qiling/debugger/qdb/context.py | 6 +- qiling/debugger/qdb/frontend.py | 23 +- qiling/debugger/qdb/memory.py | 319 +++++++++--------- qiling/debugger/qdb/qdb.py | 6 +- qiling/debugger/qdb/render/render.py | 4 +- qiling/debugger/qdb/render/render_arm.py | 4 +- qiling/debugger/qdb/render/render_x86.py | 2 +- qiling/debugger/qdb/utils.py | 6 +- 13 files changed, 201 insertions(+), 197 deletions(-) diff --git a/qiling/debugger/qdb/arch/arch_arm.py b/qiling/debugger/qdb/arch/arch_arm.py index 5f11c7aaa..b18153abb 100644 --- a/qiling/debugger/qdb/arch/arch_arm.py +++ b/qiling/debugger/qdb/arch/arch_arm.py @@ -67,7 +67,7 @@ def thumb_mode(self) -> bool: helper function for checking thumb mode """ - return self.ql.reg.cpsr & 0x00000020 != 0 + return self.ql.arch.regs.cpsr & 0x00000020 != 0 def read_insn(self, address: int) -> bytes: """ diff --git a/qiling/debugger/qdb/branch_predictor/branch_predictor.py b/qiling/debugger/qdb/branch_predictor/branch_predictor.py index d899d7fc8..4b3e891be 100644 --- a/qiling/debugger/qdb/branch_predictor/branch_predictor.py +++ b/qiling/debugger/qdb/branch_predictor/branch_predictor.py @@ -35,7 +35,7 @@ def read_reg(self, reg_name): read specific register value """ - return self.ql.reg.read(reg_name) + return self.ql.arch.regs.read(reg_name) def predict(self): """ diff --git a/qiling/debugger/qdb/branch_predictor/branch_predictor_arm.py b/qiling/debugger/qdb/branch_predictor/branch_predictor_arm.py index d0d3585a3..97f00964c 100644 --- a/qiling/debugger/qdb/branch_predictor/branch_predictor_arm.py +++ b/qiling/debugger/qdb/branch_predictor/branch_predictor_arm.py @@ -23,7 +23,7 @@ def __init__(self, ql): def read_reg(self, reg_name): reg_name = reg_name.replace("ip", "r12").replace("fp", "r11") - return getattr(self.ql.reg, reg_name) + return getattr(self.ql.arch.regs, reg_name) def regdst_eq_pc(self, op_str): return op_str.partition(", ")[0] == "pc" @@ -123,7 +123,7 @@ def predict(self): } if line.mnemonic in jump_table: - prophecy.going = jump_table.get(line.mnemonic)(*self.get_cpsr(self.ql.reg.cpsr)) + prophecy.going = jump_table.get(line.mnemonic)(*self.get_cpsr(self.ql.arch.regs.cpsr)) elif line.mnemonic in cb_table: prophecy.going = cb_table.get(line.mnemonic)(self.read_reg(line.op_str.split(", ")[0])) @@ -153,7 +153,7 @@ def predict(self): "ls": lambda V, C, Z, N: (C == 0 or Z == 1), "le": lambda V, C, Z, N: (Z == 1 or N != V), "hi": lambda V, C, Z, N: (Z == 0 and C == 1), - }.get(line.op_str)(*self.get_cpsr(self.ql.reg.cpsr)) + }.get(line.op_str)(*self.get_cpsr(self.ql.arch.regs.cpsr)) it_block_range = [each_char for each_char in line.mnemonic[1:]] @@ -184,7 +184,7 @@ def predict(self): prophecy.where = self.unpack32(self.read_mem(self.read_reg(r), self.INST_SIZE)) elif line.mnemonic in ("addls", "addne", "add") and self.regdst_eq_pc(line.op_str): - V, C, Z, N = self.get_cpsr(self.ql.reg.cpsr) + V, C, Z, N = self.get_cpsr(self.ql.arch.regs.cpsr) r0, r1, r2, *imm = line.op_str.split(", ") # program counter is awalys 8 bytes ahead when it comes with pc, need to add extra 8 bytes @@ -234,7 +234,7 @@ def predict(self): "pophi": lambda V, C, Z, N: (C == 1), "popge": lambda V, C, Z, N: (N == V), "poplt": lambda V, C, Z, N: (N != V), - }.get(line.mnemonic)(*self.get_cpsr(self.ql.reg.cpsr)): + }.get(line.mnemonic)(*self.get_cpsr(self.ql.arch.regs.cpsr)): prophecy.where = cur_addr + self.INST_SIZE diff --git a/qiling/debugger/qdb/branch_predictor/branch_predictor_mips.py b/qiling/debugger/qdb/branch_predictor/branch_predictor_mips.py index e729eabbb..0dcc9467f 100644 --- a/qiling/debugger/qdb/branch_predictor/branch_predictor_mips.py +++ b/qiling/debugger/qdb/branch_predictor/branch_predictor_mips.py @@ -36,7 +36,7 @@ def is_negative(i: int) -> int: def read_reg(self, reg_name): reg_name = reg_name.strip("$").replace("fp", "s8") - return self.signed_val(getattr(self.ql.reg, reg_name)) + return self.signed_val(getattr(self.ql.arch.regs, reg_name)) def predict(self): prophecy = self.Prophecy() diff --git a/qiling/debugger/qdb/branch_predictor/branch_predictor_x86.py b/qiling/debugger/qdb/branch_predictor/branch_predictor_x86.py index 1e9e0e562..ce92dd9fd 100644 --- a/qiling/debugger/qdb/branch_predictor/branch_predictor_x86.py +++ b/qiling/debugger/qdb/branch_predictor/branch_predictor_x86.py @@ -91,11 +91,11 @@ def predict(self): } if line.mnemonic in jump_table: - eflags = self.get_flags(self.ql.reg.ef).values() + eflags = self.get_flags(self.ql.arch.regs.ef).values() prophecy.going = jump_table.get(line.mnemonic)(*eflags) elif line.mnemonic in jump_reg_table: - prophecy.going = jump_reg_table.get(line.mnemonic)(self.ql.reg.ecx) + prophecy.going = jump_reg_table.get(line.mnemonic)(self.ql.arch.regs.ecx) if prophecy.going: takeaway_list = ["ptr", "dword", "[", "]"] @@ -106,19 +106,19 @@ def predict(self): new_line = new_line.replace(each, " ") new_line = " ".join(new_line.split()) - for each_reg in filter(lambda r: len(r) == 3, self.ql.reg.register_mapping.keys()): + for each_reg in filter(lambda r: len(r) == 3, self.ql.arch.regs.register_mapping.keys()): if each_reg in new_line: new_line = re.sub(each_reg, hex(self.read_reg(each_reg)), new_line) - for each_reg in filter(lambda r: len(r) == 2, self.ql.reg.register_mapping.keys()): + for each_reg in filter(lambda r: len(r) == 2, self.ql.arch.regs.register_mapping.keys()): if each_reg in new_line: new_line = re.sub(each_reg, hex(self.read_reg(each_reg)), new_line) prophecy.where = check_and_eval(new_line) - elif line.op_str in self.ql.reg.register_mapping: - prophecy.where = self.ql.reg.read(line.op_str) + elif line.op_str in self.ql.arch.regs.register_mapping: + prophecy.where = self.ql.arch.regs.read(line.op_str) else: prophecy.where = read_int(line.op_str) diff --git a/qiling/debugger/qdb/context.py b/qiling/debugger/qdb/context.py index 8faa1206d..cd9fbc210 100644 --- a/qiling/debugger/qdb/context.py +++ b/qiling/debugger/qdb/context.py @@ -19,7 +19,7 @@ class Context: def __init__(self, ql): self.ql = ql - self.pointersize = self.ql.pointersize + self.pointersize = self.ql.arch.pointersize self.unpack = ql.unpack self.unpack16 = ql.unpack16 self.unpack32 = ql.unpack32 @@ -31,7 +31,7 @@ def cur_addr(self): program counter of qiling instance """ - return self.ql.reg.arch_pc + return self.ql.arch.regs.arch_pc def read_mem(self, address: int, size: int): """ @@ -45,7 +45,7 @@ def disasm(self, address: int, detail: bool = False) -> Optional[CsInsn]: helper function for disassembling """ - md = self.ql.disassembler + md = self.ql.arch.disassembler md.detail = detail return next(md.disasm(self.read_insn(address), address), None) diff --git a/qiling/debugger/qdb/frontend.py b/qiling/debugger/qdb/frontend.py index 7f638e7f3..d1f53f864 100644 --- a/qiling/debugger/qdb/frontend.py +++ b/qiling/debugger/qdb/frontend.py @@ -165,7 +165,6 @@ def setup_ctx_manager(ql: Qiling) -> CtxManager: return { QL_ARCH.X86: CtxManager_X86, QL_ARCH.ARM: CtxManager_ARM, - QL_ARCH.ARM_THUMB: CtxManager_ARM, QL_ARCH.CORTEX_M: CtxManager_ARM, QL_ARCH.MIPS: CtxManager_MIPS, }.get(ql.arch.type)(ql) @@ -185,7 +184,7 @@ def print_asm(self, insn: CsInsn, to_jump: Optional[bool] = None, address: int = trace_line = f"0x{insn.address:08x} │ {opcode:10s} {insn.mnemonic:10} {insn.op_str:35s}" cursor = " " - if self.ql.reg.arch_pc == insn.address: + if self.ql.arch.regs.arch_pc == insn.address: cursor = "►" jump_sign = " " @@ -195,7 +194,7 @@ def print_asm(self, insn: CsInsn, to_jump: Optional[bool] = None, address: int = print(f"{jump_sign} {cursor} {color.DARKGRAY}{trace_line}{color.END}") def dump_regs(self): - return {reg_name: getattr(self.ql.reg, reg_name) for reg_name in self.regs} + return {reg_name: getattr(self.ql.arch.regs, reg_name) for reg_name in self.regs} def context_reg(self, saved_states): return NotImplementedError @@ -204,12 +203,12 @@ def context_reg(self, saved_states): def context_stack(self): for idx in range(10): - addr = self.ql.reg.arch_sp + idx * self.ql.pointersize - if (val := _try_read(self.ql, addr, self.ql.pointersize)[0]): - print(f"$sp+0x{idx*self.ql.pointersize:02x}│ [0x{addr:08x}] —▸ 0x{self.ql.unpack(val):08x}", end="") + addr = self.ql.arch.regs.arch_sp + idx * self.ql.arch.pointersize + if (val := _try_read(self.ql, addr, self.ql.arch.pointersize)[0]): + print(f"$sp+0x{idx*self.ql.arch.pointersize:02x}│ [0x{addr:08x}] —▸ 0x{self.ql.unpack(val):08x}", end="") # try to dereference wether it's a pointer - if (buf := _try_read(self.ql, addr, self.ql.pointersize))[0] is not None: + if (buf := _try_read(self.ql, addr, self.ql.arch.pointersize))[0] is not None: if (addr := self.ql.unpack(buf[0])): @@ -230,7 +229,7 @@ def context_stack(self): def context_asm(self): # assembly before current location past_list = [] - cur_addr = self.ql.reg.arch_pc + cur_addr = self.ql.arch.regs.arch_pc line = disasm(self.ql, cur_addr-0x10) @@ -336,7 +335,7 @@ def context_reg(self, saved_reg_dump): lines += line print(lines.format(*cur_regs.values())) - print(color.GREEN, "[{cpsr[mode]} mode], Thumb: {cpsr[thumb]}, FIQ: {cpsr[fiq]}, IRQ: {cpsr[irq]}, NEG: {cpsr[neg]}, ZERO: {cpsr[zero]}, Carry: {cpsr[carry]}, Overflow: {cpsr[overflow]}".format(cpsr=self.get_flags(self.ql.reg.cpsr)), color.END, sep="") + print(color.GREEN, "[{cpsr[mode]} mode], Thumb: {cpsr[thumb]}, FIQ: {cpsr[fiq]}, IRQ: {cpsr[irq]}, NEG: {cpsr[neg]}, ZERO: {cpsr[zero]}, Carry: {cpsr[carry]}, Overflow: {cpsr[overflow]}".format(cpsr=self.get_flags(self.ql.arch.regs.cpsr)), color.END, sep="") class CtxManager_MIPS(CtxManager): @@ -417,12 +416,12 @@ def context_reg(self, saved_reg_dump): lines += line print(lines.format(*cur_regs.values())) - print(color.GREEN, "EFLAGS: [CF: {flags[CF]}, PF: {flags[PF]}, AF: {flags[AF]}, ZF: {flags[ZF]}, SF: {flags[SF]}, OF: {flags[OF]}]".format(flags=get_x86_eflags(self.ql.reg.ef)), color.END, sep="") + print(color.GREEN, "EFLAGS: [CF: {flags[CF]}, PF: {flags[PF]}, AF: {flags[AF]}, ZF: {flags[ZF]}, SF: {flags[SF]}, OF: {flags[OF]}]".format(flags=get_x86_eflags(self.ql.arch.regs.ef)), color.END, sep="") @context_printer("[ DISASM ]", footer=True) def context_asm(self): past_list = [] - cur_addr = self.ql.reg.arch_pc + cur_addr = self.ql.arch.regs.arch_pc cur_insn = disasm(self.ql, cur_addr) prophecy = self.predictor.predict() @@ -494,7 +493,7 @@ def context_reg(self, saved_reg_dump): lines += line print(lines.format(cur_regs.values())) - print(color.GREEN, "[{cpsr[mode]} mode], Thumb: {cpsr[thumb]}, FIQ: {cpsr[fiq]}, IRQ: {cpsr[irq]}, NEG: {cpsr[neg]}, ZERO: {cpsr[zero]}, Carry: {cpsr[carry]}, Overflow: {cpsr[overflow]}".format(cpsr=get_arm_flags(self.ql.reg.cpsr)), color.END, sep="") + print(color.GREEN, "[{cpsr[mode]} mode], Thumb: {cpsr[thumb]}, FIQ: {cpsr[fiq]}, IRQ: {cpsr[irq]}, NEG: {cpsr[neg]}, ZERO: {cpsr[zero]}, Carry: {cpsr[carry]}, Overflow: {cpsr[overflow]}".format(cpsr=get_arm_flags(self.ql.arch.regs.cpsr)), color.END, sep="") if __name__ == "__main__": diff --git a/qiling/debugger/qdb/memory.py b/qiling/debugger/qdb/memory.py index 23b66e2bb..9090dafe5 100644 --- a/qiling/debugger/qdb/memory.py +++ b/qiling/debugger/qdb/memory.py @@ -19,170 +19,177 @@ def setup_memory_Manager(ql): QL_ARCH.MIPS: ArchMIPS, QL_ARCH.ARM: ArchARM, QL_ARCH.CORTEX_M: ArchCORTEX_M, - }.get(ql.archtype) + }.get(ql.arch.type) + + ret = type( + "MemoryManager", + (MemoryManager, arch_type), + {} + ) + + return ret(ql) + + +class MemoryManager(Context): + """ + memory manager for handing memory access + """ + + def __init__(self, ql): + super().__init__(ql) + + @property + def get_default_fmt(self): + return ('x', 4, 1) + + @property + def get_format_letter(self): + return { + "o", # octal + "x", # hex + "d", # decimal + "u", # unsigned decimal + "t", # binary + "f", # float + "a", # address + "i", # instruction + "c", # char + "s", # string + "z", # hex, zero padded on the left + } + + @property + def get_size_letter(self): + return { + "b": 1, # 1-byte, byte + "h": 2, # 2-byte, halfword + "w": 4, # 4-byte, word + "g": 8, # 8-byte, giant + } + + def extract_count(self, t): + return "".join([s for s in t if s.isdigit()]) + + def get_fmt(self, text): + f, s, c = self.get_default_fmt + if self.extract_count(text): + c = int(self.extract_count(text)) + + for char in text.strip(str(c)): + if char in self.get_size_letter.keys(): + s = self.get_size_letter.get(char) + + elif char in self.get_format_letter: + f = char + + return (f, s, c) + + def fmt_unpack(self, bs: bytes, sz: int) -> int: + return { + 1: lambda x: x[0], + 2: self.unpack16, + 4: self.unpack32, + 8: self.unpack64, + }.get(sz)(bs) + + def handle_i(self, addr, ct=1): + result = [] + + for offset in range(addr, addr+ct*4, 4): + if (line := self.disasm(offset)): + result.append(line) + + return result + + + def parse(self, line: str): + + # test case + # x/wx address + # x/i address + # x $sp + # x $sp +0xc + # x $sp+0xc + # x $sp + 0xc + + if line.startswith("/"): # followed by format letter and size letter + + fmt, *rest = line.strip("/").split() + + fmt = self.get_fmt(fmt) + + else: + args = line.split() + + rest = [args[0]] if len(args) == 1 else args + + fmt = self.get_default_fmt + + if len(rest) == 0: + return + + line = [] + if (regs_dict := getattr(self, "regs_need_swapped", None)): + for each in rest: + for reg in regs_dict: + if each in regs_dict: + line.append(regs_dict[each]) + else: + line.append(each) + else: + line = rest + + # for simple calculation with register and address + + line = " ".join(line) + # substitue register name with real value + for each_reg in filter(lambda r: len(r) == 3, self.ql.arch.regs.register_mapping): + reg = f"${each_reg}" + if reg in line: + line = re.sub(f"\\{reg}", hex(self.ql.arch.regs.read(each_reg)), line) + + for each_reg in filter(lambda r: len(r) == 2, self.ql.arch.regs.register_mapping): + reg = f"${each_reg}" + if reg in line: + line = re.sub(f"\\{reg}", hex(self.ql.arch.regs.read(each_reg)), line) - class MemoryManager(Context, arch_type): - """ - memory manager for handing memory access - """ - - def __init__(self, ql): - super().__init__(ql) - - @property - def get_default_fmt(self): - return ('x', 4, 1) - - @property - def get_format_letter(self): - return { - "o", # octal - "x", # hex - "d", # decimal - "u", # unsigned decimal - "t", # binary - "f", # float - "a", # address - "i", # instruction - "c", # char - "s", # string - "z", # hex, zero padded on the left - } - - @property - def get_size_letter(self): - return { - "b": 1, # 1-byte, byte - "h": 2, # 2-byte, halfword - "w": 4, # 4-byte, word - "g": 8, # 8-byte, giant - } - - def extract_count(self, t): - return "".join([s for s in t if s.isdigit()]) - - def get_fmt(self, text): - f, s, c = self.get_default_fmt - if self.extract_count(text): - c = int(self.extract_count(text)) - - for char in text.strip(str(c)): - if char in self.get_size_letter.keys(): - s = self.get_size_letter.get(char) - - elif char in self.get_format_letter: - f = char - - return (f, s, c) - - def fmt_unpack(self, bs: bytes, sz: int) -> int: - return { - 1: lambda x: x[0], - 2: self.unpack16, - 4: self.unpack32, - 8: self.unpack64, - }.get(sz)(bs) - - def handle_i(self, addr, ct=1): - result = [] - - for offset in range(addr, addr+ct*4, 4): - if (line := self.disasm(offset)): - result.append(line) - - return result - - - def parse(self, line: str): - - # test case - # x/wx address - # x/i address - # x $sp - # x $sp +0xc - # x $sp+0xc - # x $sp + 0xc - - if line.startswith("/"): # followed by format letter and size letter - - fmt, *rest = line.strip("/").split() - - fmt = self.get_fmt(fmt) - - else: - args = line.split() - - rest = [args[0]] if len(args) == 1 else args - - fmt = self.get_default_fmt - - if len(rest) == 0: - return - - line = [] - if (regs_dict := getattr(self, "regs_need_swapped", None)): - for each in rest: - for reg in regs_dict: - if each in regs_dict: - line.append(regs_dict[each]) - else: - line.append(each) - else: - line = rest - - # for simple calculation with register and address - - line = " ".join(line) - # substitue register name with real value - for each_reg in filter(lambda r: len(r) == 3, self.ql.reg.register_mapping): - reg = f"${each_reg}" - if reg in line: - line = re.sub(f"\\{reg}", hex(self.ql.reg.read(each_reg)), line) - - for each_reg in filter(lambda r: len(r) == 2, self.ql.reg.register_mapping): - reg = f"${each_reg}" - if reg in line: - line = re.sub(f"\\{reg}", hex(self.ql.reg.read(each_reg)), line) + ft, sz, ct = fmt - ft, sz, ct = fmt + try: + addr = check_and_eval(line) + except: + return "something went wrong ..." - try: - addr = check_and_eval(line) - except: - return "something went wrong ..." + if ft == "i": + output = self.handle_i(addr, ct) + for each in output: + print(f"0x{each.address:x}: {each.mnemonic}\t{each.op_str}") - if ft == "i": - output = self.handle_i(addr, ct) - for each in output: - print(f"0x{each.address:x}: {each.mnemonic}\t{each.op_str}") + else: + lines = 1 if ct <= 4 else math.ceil(ct / 4) - else: - lines = 1 if ct <= 4 else math.ceil(ct / 4) + mem_read = [] + for offset in range(ct): + # append data if read successfully, otherwise return error message + if (data := self.try_read(addr+(offset*sz), sz))[0] is not None: + mem_read.append(data[0]) - mem_read = [] - for offset in range(ct): - # append data if read successfully, otherwise return error message - if (data := self.try_read(addr+(offset*sz), sz))[0] is not None: - mem_read.append(data[0]) + else: + return data[1] - else: - return data[1] + for line in range(lines): + offset = line * sz * 4 + print(f"0x{addr+offset:x}:\t", end="") - for line in range(lines): - offset = line * sz * 4 - print(f"0x{addr+offset:x}:\t", end="") + idx = line * self.ql.arch.pointersize + for each in mem_read[idx:idx+self.ql.arch.pointersize]: + data = self.fmt_unpack(each, sz) + prefix = "0x" if ft in ("x", "a") else "" + pad = '0' + str(sz*2) if ft in ('x', 'a', 't') else '' + ft = ft.lower() if ft in ("x", "o", "b", "d") else ft.lower().replace("t", "b").replace("a", "x") + print(f"{prefix}{data:{pad}{ft}}\t", end="") - idx = line * self.ql.pointersize - for each in mem_read[idx:idx+self.ql.pointersize]: - data = self.fmt_unpack(each, sz) - prefix = "0x" if ft in ("x", "a") else "" - pad = '0' + str(sz*2) if ft in ('x', 'a', 't') else '' - ft = ft.lower() if ft in ("x", "o", "b", "d") else ft.lower().replace("t", "b").replace("a", "x") - print(f"{prefix}{data:{pad}{ft}}\t", end="") + print() - print() - - return True - - return MemoryManager(ql) + return True diff --git a/qiling/debugger/qdb/qdb.py b/qiling/debugger/qdb/qdb.py index 0aff7d8f8..ef9fc0758 100644 --- a/qiling/debugger/qdb/qdb.py +++ b/qiling/debugger/qdb/qdb.py @@ -141,7 +141,7 @@ def save_reg_dump(func) -> None: """ def inner(self, *args, **kwargs): - self._saved_reg_dump = dict(filter(lambda d: isinstance(d[0], str), self.ql.reg.save().items())) + self._saved_reg_dump = dict(filter(lambda d: isinstance(d[0], str), self.ql.arch.regs.save().items())) func(self, *args, **kwargs) return inner @@ -222,7 +222,7 @@ def do_step_in(self, *args) -> Optional[bool]: if prophecy.where is True: return True - if self.ql.archtype == QL_ARCH.CORTEX_M: + if self.ql.arch == QL_ARCH.CORTEX_M: self.ql.arch.step() else: self._run(count=1) @@ -334,7 +334,7 @@ def do_start(self, *args) -> None: restore qiling instance context to initial state """ - if self.ql.archtype != QL_ARCH.CORTEX_M: + if self.ql.arch != QL_ARCH.CORTEX_M: self.ql.restore(self.init_state) self.do_context() diff --git a/qiling/debugger/qdb/render/render.py b/qiling/debugger/qdb/render/render.py index c47afcdbf..b7a8f05f9 100644 --- a/qiling/debugger/qdb/render/render.py +++ b/qiling/debugger/qdb/render/render.py @@ -168,7 +168,7 @@ def dump_regs(self) -> Mapping[str, int]: dump all registers """ - return {reg_name: getattr(self.ql.reg, reg_name) for reg_name in self.regs} + return {reg_name: self.ql.arch.regs.read(reg_name) for reg_name in self.regs} @Render.divider_printer("[ STACK ]") def context_stack(self) -> None: @@ -176,7 +176,7 @@ def context_stack(self) -> None: display context stack dump """ - self.render_stack_dump(self.ql.reg.arch_sp) + self.render_stack_dump(self.ql.arch.regs.arch_sp) @Render.divider_printer("[ REGISTERS ]") def context_reg(self, saved_states: Mapping["str", int]) -> None: diff --git a/qiling/debugger/qdb/render/render_arm.py b/qiling/debugger/qdb/render/render_arm.py index 15ba411e5..4e10ac27d 100644 --- a/qiling/debugger/qdb/render/render_arm.py +++ b/qiling/debugger/qdb/render/render_arm.py @@ -31,7 +31,7 @@ def context_reg(self, saved_reg_dump): cur_regs = self.swap_reg_name(cur_regs) diff_reg = self.reg_diff(cur_regs, saved_reg_dump) self.render_regs_dump(cur_regs, diff_reg=diff_reg) - self.print_mode_info(self.ql.reg.cpsr) + self.print_mode_info(self.ql.arch.regs.cpsr) @@ -62,4 +62,4 @@ def context_reg(self, saved_reg_dump): cur_regs = self.swap_reg_name(cur_regs, extra_dict=extra_dict) diff_reg = self.reg_diff(cur_regs, saved_reg_dump) self.render_regs_dump(cur_regs, diff_reg=diff_reg) - self.print_mode_info(self.ql.reg.cpsr) + self.print_mode_info(self.ql.arch.regs.cpsr) diff --git a/qiling/debugger/qdb/render/render_x86.py b/qiling/debugger/qdb/render/render_x86.py index bcf17d955..3597181eb 100644 --- a/qiling/debugger/qdb/render/render_x86.py +++ b/qiling/debugger/qdb/render/render_x86.py @@ -22,7 +22,7 @@ def context_reg(self, saved_reg_dump): cur_regs = self.dump_regs() diff_reg = self.reg_diff(cur_regs, saved_reg_dump) self.render_regs_dump(cur_regs, diff_reg=diff_reg) - print(color.GREEN, "EFLAGS: [CF: {flags[CF]}, PF: {flags[PF]}, AF: {flags[AF]}, ZF: {flags[ZF]}, SF: {flags[SF]}, OF: {flags[OF]}]".format(flags=self.get_flags(self.ql.reg.ef)), color.END, sep="") + print(color.GREEN, "EFLAGS: [CF: {flags[CF]}, PF: {flags[PF]}, AF: {flags[AF]}, ZF: {flags[ZF]}, SF: {flags[SF]}, OF: {flags[OF]}]".format(flags=self.get_flags(self.ql.arch.regs.ef)), color.END, sep="") @Render.divider_printer("[ DISASM ]") def context_asm(self): diff --git a/qiling/debugger/qdb/utils.py b/qiling/debugger/qdb/utils.py index 9eeed2dbe..fdbde9d85 100644 --- a/qiling/debugger/qdb/utils.py +++ b/qiling/debugger/qdb/utils.py @@ -65,10 +65,9 @@ def setup_branch_predictor(ql): return { QL_ARCH.X86: BranchPredictorX86, QL_ARCH.ARM: BranchPredictorARM, - QL_ARCH.ARM_THUMB: BranchPredictorARM, QL_ARCH.CORTEX_M: BranchPredictorCORTEX_M, QL_ARCH.MIPS: BranchPredictorMIPS, - }.get(ql.archtype)(ql) + }.get(ql.arch.type)(ql) def setup_context_render(ql, predictor): """ @@ -78,10 +77,9 @@ def setup_context_render(ql, predictor): return { QL_ARCH.X86: ContextRenderX86, QL_ARCH.ARM: ContextRenderARM, - QL_ARCH.ARM_THUMB: ContextRenderARM, QL_ARCH.CORTEX_M: ContextRenderCORTEX_M, QL_ARCH.MIPS: ContextRenderMIPS, - }.get(ql.archtype)(ql, predictor) + }.get(ql.arch.type)(ql, predictor) def run_qdb_script(qdb, filename: str) -> None: with open(filename) as fd: From e5b1552f5b7517f9fe39e3b67c94f615140dde77 Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Fri, 4 Mar 2022 00:38:48 +0000 Subject: [PATCH 182/406] enable two more tests for qdb --- tests/test_qdb.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/tests/test_qdb.py b/tests/test_qdb.py index 37eeac30c..077ecad9e 100644 --- a/tests/test_qdb.py +++ b/tests/test_qdb.py @@ -9,23 +9,23 @@ class DebuggerTest(unittest.TestCase): - # def test_qdb_mips32el_hello(self): - # rootfs = "../examples/rootfs/mips32el_linux" - # path = rootfs + "/bin/mips32el_hello" - - # ql = Qiling([path], rootfs) - # ql.debugger = "qdb::rr:qdb_scripts/mips32el.qdb" - # ql.run() - # del ql - - # def test_qdb_arm_hello(self): - # rootfs = "../examples/rootfs/arm_linux" - # path = rootfs + "/bin/arm_hello" - - # ql = Qiling([path], rootfs) - # ql.debugger = "qdb::rr:qdb_scripts/arm.qdb" - # ql.run() - # del ql + def test_qdb_mips32el_hello(self): + rootfs = "../examples/rootfs/mips32el_linux" + path = rootfs + "/bin/mips32el_hello" + + ql = Qiling([path], rootfs) + ql.debugger = "qdb::rr:qdb_scripts/mips32el.qdb" + ql.run() + del ql + + def test_qdb_arm_hello(self): + rootfs = "../examples/rootfs/arm_linux" + path = rootfs + "/bin/arm_hello" + + ql = Qiling([path], rootfs) + ql.debugger = "qdb::rr:qdb_scripts/arm.qdb" + ql.run() + del ql def test_qdb_x86_hello(self): rootfs = "../examples/rootfs/x86_linux" From 2e9b1797276de1af3b41e2d1a867f5e3d331478d Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Fri, 4 Mar 2022 00:41:36 +0000 Subject: [PATCH 183/406] use is_thumb instead --- qiling/debugger/qdb/arch/arch_arm.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qiling/debugger/qdb/arch/arch_arm.py b/qiling/debugger/qdb/arch/arch_arm.py index b18153abb..1e24eef19 100644 --- a/qiling/debugger/qdb/arch/arch_arm.py +++ b/qiling/debugger/qdb/arch/arch_arm.py @@ -67,7 +67,8 @@ def thumb_mode(self) -> bool: helper function for checking thumb mode """ - return self.ql.arch.regs.cpsr & 0x00000020 != 0 + return self.ql.arch.is_thumb + def read_insn(self, address: int) -> bytes: """ From d244c1242402717f3cc85727a577fd31dc186b4d Mon Sep 17 00:00:00 2001 From: nullableVoidPtr <30564701+nullableVoidPtr@users.noreply.github.com> Date: Wed, 9 Mar 2022 18:24:41 +0800 Subject: [PATCH 184/406] Use fstatat flags to check for AT_EMPTY_PATH --- qiling/os/posix/const.py | 3 ++- qiling/os/posix/syscall/stat.py | 36 ++++++++++++++++----------------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/qiling/os/posix/const.py b/qiling/os/posix/const.py index ba49a938f..51a03393e 100644 --- a/qiling/os/posix/const.py +++ b/qiling/os/posix/const.py @@ -582,6 +582,7 @@ FD_CLOEXEC = 1 AT_FDCWD = -100 +AT_EMPTY_PATH = 0x1000 # error code EPERM = 1 @@ -856,4 +857,4 @@ SHM_RDONLY = 8**4 SHM_RND = 2*(8**4) SHM_REMAP= 4*(8**4) -SHM_EXEC = 1*(8**5) \ No newline at end of file +SHM_EXEC = 1*(8**5) diff --git a/qiling/os/posix/syscall/stat.py b/qiling/os/posix/syscall/stat.py index 0ca5fd6c2..8c895ac09 100644 --- a/qiling/os/posix/syscall/stat.py +++ b/qiling/os/posix/syscall/stat.py @@ -10,7 +10,7 @@ from qiling import Qiling from qiling.const import QL_OS, QL_ARCH, QL_ENDIAN -from qiling.os.posix.const import NR_OPEN, EBADF, AT_FDCWD +from qiling.os.posix.const import NR_OPEN, EBADF, ENOENT, AT_FDCWD, AT_EMPTY_PATH from qiling.os.posix.stat import Stat, Lstat # Caveat: Never use types like ctypes.c_long whose size differs across platforms. @@ -1026,7 +1026,7 @@ def statFamily(ql: Qiling, path: int, ptr: int, name: str, stat_func, struct_fun ql.log.debug(f'{name}("{file_path}", {ptr:#x}) write completed') return regreturn -def transform_path(ql: Qiling, dirfd: int, path: int): +def transform_path(ql: Qiling, dirfd: int, path: int, flags: int = 0): """ An absolute pathname If pathname begins with a slash, then it is an absolute pathname that identifies the target file. @@ -1054,9 +1054,12 @@ def transform_path(ql: Qiling, dirfd: int, path: int): if dirfd == AT_FDCWD: return None, ql.os.path.transform_to_real_path(path) + if len(path) == 0 and flags & AT_EMPTY_PATH: + return None, ql.os.fd[dirfd].name + if 0 < dirfd < NR_OPEN: return ql.os.fd[dirfd].fileno(), path - + def ql_syscall_chmod(ql: Qiling, filename: int, mode: int): ql.log.debug(f'chmod("{ql.os.utils.read_cstring(filename)}", {mode:d}) = 0') @@ -1069,28 +1072,28 @@ def ql_syscall_fchmod(ql: Qiling, fd: int, mode: int): return 0 -def ql_syscall_fstatat64(ql: Qiling, dirfd: int, path: int, buf_ptr: int, flag: int): - dirfd, real_path = transform_path(ql, dirfd, path) +def ql_syscall_fstatat64(ql: Qiling, dirfd: int, path: int, buf_ptr: int, flags: int): + dirfd, real_path = transform_path(ql, dirfd, path, flags) - if os.path.exists(real_path): + try: buf = pack_stat64_struct(ql, Stat(real_path, dirfd)) ql.mem.write(buf_ptr, buf) regreturn = 0 - else: + except: regreturn = -1 return regreturn -def ql_syscall_newfstatat(ql: Qiling, dirfd: int, path: int, buf_ptr: int, flag: int): - dirfd, real_path = transform_path(ql, dirfd, path) - - if os.path.exists(real_path): +def ql_syscall_newfstatat(ql: Qiling, dirfd: int, path: int, buf_ptr: int, flags: int): + dirfd, real_path = transform_path(ql, dirfd, path, flags) + + try: buf = pack_stat_struct(ql, Stat(real_path, dirfd)) ql.mem.write(buf_ptr, buf) regreturn = 0 - else: + except: regreturn = -1 return regreturn @@ -1239,13 +1242,10 @@ def major(dev): def minor(dev): return (dev & 0xff) | ((dev >> 12) & ~0xff) - fd, real_path = transform_path(ql, dirfd, path) + fd, real_path = transform_path(ql, dirfd, path, flags) try: - if len(real_path) == 0: - st = ql.os.fd[dirfd].fstat() - else: - st = Stat(real_path, fd) + st = Stat(real_path, fd) if ql.arch.bits == 32: Statx = Statx32 @@ -1289,7 +1289,7 @@ def ql_syscall_lstat64(ql: Qiling, path: int, buf_ptr: int): def ql_syscall_mknodat(ql: Qiling, dirfd: int, path: int, mode: int, dev: int): - dirfd, real_path = transform_path(ql, dirfd, path) + dirfd, real_path = transform_path(ql, dirfd, path) try: os.mknod(real_path, mode, dev, dir_fd=dirfd) From e987084378d4e470067c23cad077936968c7d607 Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 6 Mar 2022 16:53:26 +0200 Subject: [PATCH 185/406] Move find_containing_image to QlLoader --- qiling/loader/loader.py | 10 +++++++++- qiling/os/os.py | 7 +------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/qiling/loader/loader.py b/qiling/loader/loader.py index 2afe1a138..472881cbf 100644 --- a/qiling/loader/loader.py +++ b/qiling/loader/loader.py @@ -3,7 +3,7 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from typing import Any, Mapping, MutableSequence, NamedTuple +from typing import Any, Mapping, MutableSequence, NamedTuple, Optional from qiling import Qiling @@ -17,6 +17,14 @@ def __init__(self, ql: Qiling): self.images: MutableSequence[Image] = [] self.skip_exit_check = False + def find_containing_image(self, address: int) -> Optional[Image]: + """Retrieve the image object that contains the specified address. + + Returns: image containing the specified address, or `None` if not found + """ + + return next((image for image in self.images if image.base <= address < image.end), None) + def save(self) -> Mapping[str, Any]: saved_state = { 'images': [tuple(img) for img in self.images] diff --git a/qiling/os/os.py b/qiling/os/os.py index 7654933c7..a06dacb46 100644 --- a/qiling/os/os.py +++ b/qiling/os/os.py @@ -222,11 +222,6 @@ def set_api(self, api_name: str, intercept_function: Callable, intercept: QL_INT else: self.add_function_hook(api_name, intercept_function, intercept) - def find_containing_image(self, pc: int): - for image in self.ql.loader.images: - if image.base <= pc < image.end: - return image - # os main method; derivatives must implement one of their own def run(self) -> None: raise NotImplementedError @@ -256,7 +251,7 @@ def emu_error(self): self.ql.log.error('Disassembly:') self.ql.arch.utils.disassembler(self.ql, pc, 64) - containing_image = self.find_containing_image(pc) + containing_image = self.ql.loader.find_containing_image(pc) pc_info = f' ({containing_image.path} + {pc - containing_image.base:#x})' if containing_image else '' finally: self.ql.log.error(f'PC = {pc:#0{self.ql.arch.pointersize * 2 + 2}x}{pc_info}\n') From 7ea07103a5d57b6cd2b2b573cba2d67fc7948941 Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 6 Mar 2022 16:54:35 +0200 Subject: [PATCH 186/406] Rename and revisit bin_patch and lib_patch --- qiling/core.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/qiling/core.py b/qiling/core.py index 321c72b4e..57073d4e4 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -499,18 +499,20 @@ def stop_options(self) -> QL_STOP: """ return self._stop_options - def __enable_bin_patch(self): - - for addr, code in self.patch_bin: - self.mem.write(self.loader.load_address + addr, code) - - - def enable_lib_patch(self): - for addr, code, filename in self.patch_lib: - try: - self.mem.write(self.mem.get_lib_base(filename) + addr, code) - except: - raise RuntimeError("Fail to patch %s at address 0x%x" % (filename, addr)) + def do_bin_patch(self): + ba = self.loader.load_address + + for offset, code in self.patch_bin: + self.mem.write(ba + offset, code) + + def do_lib_patch(self): + for offset, code, filename in self.patch_lib: + ba = self.mem.get_lib_base(filename) + + if ba is None: + raise RuntimeError(f'Patch failed: there is no loaded library named "{filename}"') + + self.mem.write(ba + offset, code) def _init_stop_guard(self): if not self.stop_options: From e60122b39375e3e1f16351c96b917c322ec6d8ac Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 6 Mar 2022 16:56:46 +0200 Subject: [PATCH 187/406] Adjust bin_patch and lib_patch usages --- qiling/core.py | 2 +- qiling/os/freebsd/freebsd.py | 2 +- qiling/os/linux/linux.py | 2 +- qiling/os/linux/thread.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/qiling/core.py b/qiling/core.py index 57073d4e4..704efae3c 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -574,7 +574,7 @@ def run(self, begin=None, end=None, timeout=0, count=0, code=None): debugger = debugger_setup(self._debugger, self) # patch binary - self.__enable_bin_patch() + self.do_bin_patch() if self.baremetal: if self.count <= 0: diff --git a/qiling/os/freebsd/freebsd.py b/qiling/os/freebsd/freebsd.py index 800d31f07..6b368466e 100644 --- a/qiling/os/freebsd/freebsd.py +++ b/qiling/os/freebsd/freebsd.py @@ -44,7 +44,7 @@ def run(self): else: if self.ql.loader.elf_entry != self.ql.loader.entry_point: self.ql.emu_start(self.ql.loader.entry_point, self.ql.loader.elf_entry, self.ql.timeout) - self.ql.enable_lib_patch() + self.ql.do_lib_patch() self.ql.emu_start(self.ql.loader.elf_entry, self.exit_point, self.ql.timeout, self.ql.count) diff --git a/qiling/os/linux/linux.py b/qiling/os/linux/linux.py index 75b4f5813..21ab6d5dd 100644 --- a/qiling/os/linux/linux.py +++ b/qiling/os/linux/linux.py @@ -149,7 +149,7 @@ def run(self): if self.ql.arch.type == QL_ARCH.ARM and entry_address & 1 == 1: entry_address -= 1 self.ql.emu_start(self.ql.loader.entry_point, entry_address, self.ql.timeout) - self.ql.enable_lib_patch() + self.ql.do_lib_patch() self.run_function_after_load() self.ql.loader.skip_exit_check = False self.ql.write_exit_trap() diff --git a/qiling/os/linux/thread.py b/qiling/os/linux/thread.py index 32d15a028..04a6c1be2 100644 --- a/qiling/os/linux/thread.py +++ b/qiling/os/linux/thread.py @@ -586,7 +586,7 @@ def _prepare_lib_patch(self): if self.ql.arch.regs.arch_pc != entry_address: self.ql.log.error(f"{self.cur_thread} Expect {hex(self.ql.loader.elf_entry)} but get {hex(self.ql.arch.regs.arch_pc)} when running loader.") raise QlErrorExecutionStop('Dynamic library .init() failed!') - self.ql.enable_lib_patch() + self.ql.do_lib_patch() self.ql.os.run_function_after_load() self.ql.loader.skip_exit_check = False self.ql.write_exit_trap() From 6ede240baaf23f49f90ec8a9c09e06b947efe093 Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 6 Mar 2022 16:58:41 +0200 Subject: [PATCH 188/406] Some insignificant changes --- qiling/os/memory.py | 4 ++-- qiling/utils.py | 5 +++-- qltool | 6 +++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/qiling/os/memory.py b/qiling/os/memory.py index 394b121c2..d3cf7e781 100644 --- a/qiling/os/memory.py +++ b/qiling/os/memory.py @@ -192,8 +192,8 @@ def show_mapinfo(self): self.ql.log.info(f'{lbound:08x} - {ubound:08x} {perms:5s} {label:12s} {container or ""}') # TODO: relying on the label string is risky; find a more reliable method - def get_lib_base(self, filename: str) -> int: - return next((s for s, _, _, info, _ in self.map_info if os.path.split(info)[1] == filename), -1) + def get_lib_base(self, filename: str) -> Optional[int]: + return next((s for s, _, _, info, _ in self.map_info if os.path.basename(info) == filename), None) def align(self, value: int, alignment: int = None) -> int: """Align a value down to the specified alignment boundary. diff --git a/qiling/utils.py b/qiling/utils.py index 1952fe70e..fc246c27b 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -502,9 +502,10 @@ def os_setup(ostype: QL_OS, ql): def profile_setup(ql, ostype: QL_OS, filename: Optional[str]): ql.log.debug(f'Profile: {filename or "default"}') - if ql.baremetal: + # mcu uses a yaml-based config + if ostype == QL_OS.MCU: if filename: - with open(filename) as f: + with open(filename) as f: config = yaml.load(f, Loader=yaml.Loader) else: config = {} diff --git a/qltool b/qltool index 266dc0ba2..5467c0dd0 100755 --- a/qltool +++ b/qltool @@ -226,9 +226,6 @@ if __name__ == '__main__': if options.subcommand == 'examples': handle_examples(parser) - if options.debug_stop and options.verbose not in (QL_VERBOSE.DEBUG, QL_VERBOSE.DUMP): - parser.error('the debug_stop option requires verbose to be set to either "debug" or "dump"') - # ql file setup if options.subcommand == 'run': ql = handle_run(options) @@ -247,6 +244,9 @@ if __name__ == '__main__': ql.debugger = argval if options.debug_stop: + if options.verbose not in (QL_VERBOSE.DEBUG, QL_VERBOSE.DUMP): + parser.error('the debug_stop option requires verbose to be set to either "debug" or "dump"') + ql.debug_stop = True if options.root: From 80a7b253461216b9b77e64242573f02336d3726f Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 6 Mar 2022 16:59:53 +0200 Subject: [PATCH 189/406] Revisit PE shellcode test --- tests/test_peshellcode.py | 44 ++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/tests/test_peshellcode.py b/tests/test_peshellcode.py index 2d6ade205..72ed657f4 100644 --- a/tests/test_peshellcode.py +++ b/tests/test_peshellcode.py @@ -5,36 +5,42 @@ import sys, unittest -from binascii import unhexlify - sys.path.append("..") -from qiling import * -from qiling.exception import * -from qiling.const import QL_VERBOSE +from qiling import Qiling -X86_WIN = unhexlify( - 'fce8820000006089e531c0648b50308b520c8b52148b72280fb74a2631ffac3c617c022c20c1cf0d01c7e2f252578b52108b4a3c8b4c1178e34801d1518b592001d38b4918e33a498b348b01d631ffacc1cf0d01c738e075f6037df83b7d2475e4588b582401d3668b0c4b8b581c01d38b048b01d0894424245b5b61595a51ffe05f5f5a8b12eb8d5d6a01eb2668318b6f87ffd5bbf0b5a25668a695bd9dffd53c067c0a80fbe07505bb4713726f6a0053ffd5e8d5ffffff63616c6300' -) +X86_WIN = bytes.fromhex(''' + fce8820000006089e531c0648b50308b520c8b52148b72280fb74a2631ffac3c + 617c022c20c1cf0d01c7e2f252578b52108b4a3c8b4c1178e34801d1518b5920 + 01d38b4918e33a498b348b01d631ffacc1cf0d01c738e075f6037df83b7d2475 + e4588b582401d3668b0c4b8b581c01d38b048b01d0894424245b5b61595a51ff + e05f5f5a8b12eb8d5d6a01eb2668318b6f87ffd5bbf0b5a25668a695bd9dffd5 + 3c067c0a80fbe07505bb4713726f6a0053ffd5e8d5ffffff63616c6300 +''') -X8664_WIN = unhexlify( - 'fc4881e4f0ffffffe8d0000000415141505251564831d265488b52603e488b52183e488b52203e488b72503e480fb74a4a4d31c94831c0ac3c617c022c2041c1c90d4101c1e2ed5241513e488b52203e8b423c4801d03e8b80880000004885c0746f4801d0503e8b48183e448b40204901d0e35c48ffc93e418b34884801d64d31c94831c0ac41c1c90d4101c138e075f13e4c034c24084539d175d6583e448b40244901d0663e418b0c483e448b401c4901d03e418b04884801d0415841585e595a41584159415a4883ec204152ffe05841595a3e488b12e949ffffff5d49c7c1000000003e488d95fe0000003e4c8d850f0100004831c941ba45835607ffd54831c941baf0b5a256ffd548656c6c6f2c2066726f6d204d534621004d657373616765426f7800' -) +X8664_WIN = bytes.fromhex(''' + fc4881e4f0ffffffe8d0000000415141505251564831d265488b52603e488b52 + 183e488b52203e488b72503e480fb74a4a4d31c94831c0ac3c617c022c2041c1 + c90d4101c1e2ed5241513e488b52203e8b423c4801d03e8b80880000004885c0 + 746f4801d0503e8b48183e448b40204901d0e35c48ffc93e418b34884801d64d + 31c94831c0ac41c1c90d4101c138e075f13e4c034c24084539d175d6583e448b + 40244901d0663e418b0c483e448b401c4901d03e418b04884801d0415841585e + 595a41584159415a4883ec204152ffe05841595a3e488b12e949ffffff5d49c7 + c1000000003e488d95fe0000003e4c8d850f0100004831c941ba45835607ffd5 + 4831c941baf0b5a256ffd548656c6c6f2c2066726f6d204d534621004d657373 + 616765426f7800 +''') -POINTER_TEST = unhexlify( - '1122334455667788' -) +POINTER_TEST = bytes.fromhex('1122334455667788') class PEShellcodeTest(unittest.TestCase): def test_windowssc_x86(self): - ql = Qiling(code=X86_WIN, archtype="x86", ostype="windows", rootfs="../examples/rootfs/x86_windows", - verbose=QL_VERBOSE.DEFAULT) + ql = Qiling(code=X86_WIN, archtype="x86", ostype="windows", rootfs="../examples/rootfs/x86_windows") ql.run() del ql def test_windowssc_x64(self): - ql = Qiling(code=X8664_WIN, archtype="x8664", ostype="windows", rootfs="../examples/rootfs/x8664_windows", - verbose=QL_VERBOSE.DEBUG) + ql = Qiling(code=X8664_WIN, archtype="x8664", ostype="windows", rootfs="../examples/rootfs/x8664_windows") ql.run() del ql @@ -50,7 +56,7 @@ def test_read_ptr32(self): del ql def test_read_ptr64(self): - ql = Qiling(code=POINTER_TEST, archtype="x8664", ostype="windows", rootfs="../examples/rootfs/x86_windows") + ql = Qiling(code=POINTER_TEST, archtype="x8664", ostype="windows", rootfs="../examples/rootfs/x8664_windows") addr = ql.loader.entry_point self.assertEqual(0x11, ql.mem.read_ptr(addr, 1)) From 7ddb1eae0bf43748de697ce333522aa9f0eee19b Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 6 Mar 2022 17:28:57 +0200 Subject: [PATCH 190/406] Adjust overlooked find_containing_image usages --- qiling/loader/pe_uefi.py | 2 +- qiling/os/memory.py | 4 ++-- qiling/os/uefi/uefi.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/qiling/loader/pe_uefi.py b/qiling/loader/pe_uefi.py index 9414ebf64..a20bf9444 100644 --- a/qiling/loader/pe_uefi.py +++ b/qiling/loader/pe_uefi.py @@ -119,7 +119,7 @@ def map_and_load(self, path: str, context: UefiContext, exec_now: bool=False): self.install_loaded_image_protocol(image_base, image_size) - # this would be used later be os.find_containing_image + # this would be used later be loader.find_containing_image self.images.append(Image(image_base, image_base + image_size, path)) # update next memory slot to allow sequencial loading. its availability diff --git a/qiling/os/memory.py b/qiling/os/memory.py index d3cf7e781..ba63e095b 100644 --- a/qiling/os/memory.py +++ b/qiling/os/memory.py @@ -170,8 +170,8 @@ def __perms_mapping(ps: int) -> str: def __process(lbound: int, ubound: int, perms: int, label: str, is_mmio: bool) -> Tuple[int, int, str, str, Optional[str]]: perms_str = __perms_mapping(perms) - if hasattr(self.ql, 'os'): - image = self.ql.os.find_containing_image(lbound) + if hasattr(self.ql, 'loader'): + image = self.ql.loader.find_containing_image(lbound) container = image.path if image and not is_mmio else None else: container = None diff --git a/qiling/os/uefi/uefi.py b/qiling/os/uefi/uefi.py index 709000dbb..3923a3c26 100644 --- a/qiling/os/uefi/uefi.py +++ b/qiling/os/uefi/uefi.py @@ -185,7 +185,7 @@ def emu_error(self): self.emit_hexdump(pc, data) self.emit_disasm(pc, data) - containing_image = self.find_containing_image(pc) + containing_image = self.ql.loader.find_containing_image(pc) pc_info = f' ({containing_image.path} + {pc - containing_image.base:#x})' if containing_image else '' finally: self.ql.log.error(f'PC = {pc:#010x}{pc_info}') From fc2f454f5fb1b5adb90263287a22a8f6436f4767 Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 9 Mar 2022 17:00:56 +0200 Subject: [PATCH 191/406] Code deuplication: use mem align methods --- qiling/loader/elf.py | 50 ++++++++------------------------------------ 1 file changed, 9 insertions(+), 41 deletions(-) diff --git a/qiling/loader/elf.py b/qiling/loader/elf.py index 77dba9ffc..5b78ec8b5 100644 --- a/qiling/loader/elf.py +++ b/qiling/loader/elf.py @@ -146,38 +146,6 @@ def seg_perm_to_uc_prot(perm: int) -> int: return prot - @staticmethod - def align(value: int, alignment: int) -> int: - """Align a value down to the specified alignment boundary. If `value` is already - aligned, the same value is returned. Commonly used to determine the base address - of the enclosing page. - - Args: - value: numberic value to align - alignment: alignment boundary; must be a power of 2 - - Returns: - Value aligned down to boundary - """ - - return value & ~(alignment - 1) - - @staticmethod - def align_up(value: int, alignment: int) -> int: - """Align a value up to the specified alignment boundary. If `value` is already - aligned, the same value is returned. Commonly used to determine the end address - of the enlosing page. - - Args: - value: numberic value to align - alignment: alignment boundary; must be a power of 2 - - Returns: - Value aligned up to boundary - """ - - return (value + alignment - 1) & ~(alignment - 1) - def load_with_ld(self, elffile: ELFFile, stack_addr: int, load_address: int, argv: Sequence[str] = [], env: Mapping[str, str] = {}): pagesize = 0x1000 @@ -192,8 +160,8 @@ def load_with_ld(self, elffile: ELFFile, stack_addr: int, load_address: int, arg # iterate over loadable segments by vaddr for seg in sorted(seg_pt_load, key=lambda s: s['p_vaddr']): - lbound = QlLoaderELF.align(load_address + seg['p_vaddr'], pagesize) - ubound = QlLoaderELF.align_up(load_address + seg['p_vaddr'] + seg['p_memsz'], pagesize) + lbound = self.ql.mem.align(load_address + seg['p_vaddr']) + ubound = self.ql.mem.align_up(load_address + seg['p_vaddr'] + seg['p_memsz']) perms = QlLoaderELF.seg_perm_to_uc_prot(seg['p_flags']) if load_regions: @@ -249,8 +217,8 @@ def load_with_ld(self, elffile: ELFFile, stack_addr: int, load_address: int, arg mem_start = min(seg['p_vaddr'] for seg in seg_pt_load) mem_end = max(seg['p_vaddr'] + seg['p_memsz'] for seg in seg_pt_load) - mem_start = QlLoaderELF.align(mem_start, pagesize) - mem_end = QlLoaderELF.align_up(mem_end, pagesize) + mem_start = self.ql.mem.align(mem_start) + mem_end = self.ql.mem.align_up(mem_end) self.ql.log.debug(f'mem_start : {mem_start:#x}') self.ql.log.debug(f'mem_end : {mem_end:#x}') @@ -280,7 +248,7 @@ def load_with_ld(self, elffile: ELFFile, stack_addr: int, load_address: int, arg # determine memory size needed for interpreter interp_mem_size = max((seg['p_vaddr'] + seg['p_memsz']) for seg in interp_seg_pt_load) - interp_mem_size = QlLoaderELF.align_up(interp_mem_size, pagesize) + interp_mem_size = self.ql.mem.align_up(interp_mem_size) self.ql.log.debug(f'Interpreter size: {interp_mem_size:#x}') # map memory for interpreter @@ -314,7 +282,7 @@ def __push_str(top: int, s: str) -> int: """ data = (s if isinstance(s, bytes) else s.encode("utf-8")) + b'\x00' - top = QlLoaderELF.align(top - len(data), self.ql.arch.pointersize) + top = self.ql.mem.align(top - len(data), self.ql.arch.pointersize) self.ql.mem.write(top, data) return top @@ -386,7 +354,7 @@ def __push_str(top: int, s: str) -> int: for key, val in aux_entries: elf_table.extend(self.ql.pack(key) + self.ql.pack(val)) - new_stack = QlLoaderELF.align(new_stack - len(elf_table), 0x10) + new_stack = self.ql.mem.align(new_stack - len(elf_table), 0x10) self.ql.mem.write(new_stack, bytes(elf_table)) # if enabled, gdb would need to retrieve aux vector data. @@ -511,7 +479,7 @@ def __get_symbol(name: str) -> Optional[Symbol]: # we need to lookup from address to symbol, so we can find the right callback # for sys_xxx handler for syscall, the address must be aligned to pointer size if symbol_name.startswith('sys_'): - self.ql.os.hook_addr = QlLoaderELF.align_up(self.ql.os.hook_addr, self.ql.arch.pointersize) + self.ql.os.hook_addr = self.ql.mem.align_up(self.ql.os.hook_addr, self.ql.arch.pointersize) self.import_symbols[self.ql.os.hook_addr] = symbol_name @@ -635,7 +603,7 @@ def load_driver(self, elffile: ELFFile, stack_addr: int, loadbase: int = 0) -> N self.ql.os.entry_point = self.entry_point = entry_point self.elf_entry = self.ql.os.elf_entry = self.ql.os.entry_point - self.stack_address = QlLoaderELF.align(stack_addr, self.ql.arch.pointersize) + 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 From e5963ac0ec284b437f98af1700f9dd4de95fd24e Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 9 Mar 2022 17:01:36 +0200 Subject: [PATCH 192/406] Update align methods documentation --- qiling/os/memory.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/qiling/os/memory.py b/qiling/os/memory.py index ba63e095b..70a82dcd9 100644 --- a/qiling/os/memory.py +++ b/qiling/os/memory.py @@ -196,14 +196,16 @@ def get_lib_base(self, filename: str) -> Optional[int]: return next((s for s, _, _, info, _ in self.map_info if os.path.basename(info) == filename), None) def align(self, value: int, alignment: int = None) -> int: - """Align a value down to the specified alignment boundary. + """Align a value down to the specified alignment boundary. If `value` is already + aligned, the same value is returned. Commonly used to determine the base address + of the enclosing page. Args: value: a value to align - alignment: alignment boundary; must be a power of 2. if not specified - value will be aligned to page size + alignment: alignment boundary; must be a power of 2. if not specified value + will be aligned to page size - Returns: aligned value + Returns: value aligned down to boundary """ if alignment is None: @@ -216,14 +218,16 @@ def align(self, value: int, alignment: int = None) -> int: return value & ~(alignment - 1) def align_up(self, value: int, alignment: int = None) -> int: - """Align a value up to the specified alignment boundary. + """Align a value up to the specified alignment boundary. If `value` is already + aligned, the same value is returned. Commonly used to determine the end address + of the enlosing page. Args: value: value to align - alignment: alignment boundary; must be a power of 2. if not specified - value will be aligned to page size + alignment: alignment boundary; must be a power of 2. if not specified value + will be aligned to page size - Returns: aligned value + Returns: value aligned up to boundary """ if alignment is None: From 89c516d6e403ce4cf8c2b36c41ce8dc0a7f911dc Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 9 Mar 2022 17:02:49 +0200 Subject: [PATCH 193/406] Code deuplication: use mem pagesize --- qiling/loader/elf.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/qiling/loader/elf.py b/qiling/loader/elf.py index 5b78ec8b5..4c84e945c 100644 --- a/qiling/loader/elf.py +++ b/qiling/loader/elf.py @@ -147,8 +147,6 @@ def seg_perm_to_uc_prot(perm: int) -> int: return prot def load_with_ld(self, elffile: ELFFile, stack_addr: int, load_address: int, argv: Sequence[str] = [], env: Mapping[str, str] = {}): - pagesize = 0x1000 - # get list of loadable segments; these segments will be loaded to memory seg_pt_load = tuple(seg for seg in elffile.iter_segments() if seg['p_type'] == 'PT_LOAD') @@ -332,7 +330,7 @@ def __push_str(top: int, s: str) -> int: (AUX.AT_PHDR, elf_phdr + mem_start), (AUX.AT_PHENT, elf_phent), (AUX.AT_PHNUM, elf_phnum), - (AUX.AT_PAGESZ, pagesize), + (AUX.AT_PAGESZ, self.ql.mem.pagesize), (AUX.AT_BASE, interp_address), (AUX.AT_FLAGS, 0), (AUX.AT_ENTRY, elf_entry), From c781b70b14e8b8820c34d9f1c909b858874b3d1a Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 9 Mar 2022 17:03:29 +0200 Subject: [PATCH 194/406] Add interp to loaded images list --- qiling/loader/elf.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/qiling/loader/elf.py b/qiling/loader/elf.py index 4c84e945c..2eadea64b 100644 --- a/qiling/loader/elf.py +++ b/qiling/loader/elf.py @@ -221,6 +221,9 @@ def load_with_ld(self, elffile: ELFFile, stack_addr: int, load_address: int, arg self.ql.log.debug(f'mem_start : {mem_start:#x}') self.ql.log.debug(f'mem_end : {mem_end:#x}') + # by convention the loaded binary is first on the list + self.images.append(Image(load_address + mem_start, load_address + mem_end, os.path.abspath(self.path))) + # note: 0x2000 is the size of [hook_mem] self.brk_address = load_address + mem_end + 0x2000 @@ -250,7 +253,10 @@ def load_with_ld(self, elffile: ELFFile, stack_addr: int, load_address: int, arg self.ql.log.debug(f'Interpreter size: {interp_mem_size:#x}') # map memory for interpreter - self.ql.mem.map(interp_address, interp_mem_size, info=os.path.abspath(interp_local_path)) + self.ql.mem.map(interp_address, interp_mem_size, info=os.path.basename(interp_local_path)) + + # add interpreter to the loaded images list + self.images.append(Image(interp_address, interp_address + interp_mem_size, os.path.abspath(interp_local_path))) # load interpterter segments data to memory for seg in interp_seg_pt_load: @@ -363,7 +369,6 @@ def __push_str(top: int, s: str) -> int: self.elf_entry = elf_entry self.stack_address = new_stack self.load_address = load_address - self.images.append(Image(load_address, load_address + mem_end, self.path)) self.init_sp = self.ql.arch.regs.arch_sp self.ql.os.entry_point = self.entry_point = entry_point From 750c4bfe15e8fc5cbb65269d529ee461d8aff74b Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 9 Mar 2022 17:04:28 +0200 Subject: [PATCH 195/406] Fix meminfo table formatting --- qiling/os/memory.py | 15 ++++++++++++--- qiling/os/posix/syscall/mman.py | 4 ++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/qiling/os/memory.py b/qiling/os/memory.py index 70a82dcd9..0620a6541 100644 --- a/qiling/os/memory.py +++ b/qiling/os/memory.py @@ -184,12 +184,21 @@ def show_mapinfo(self): """Emit memory map info in a nicely formatted table. """ + mapinfo = self.get_mapinfo() + + # determine columns sizes based on the longest value for each field + lengths = ((len(f'{ubound:#x}'), len(label)) for _, ubound, _, label, _ in mapinfo) + grouped = tuple(zip(*lengths)) + + len_addr = max(grouped[0]) + len_label = max(grouped[1]) + # emit title row - self.ql.log.info(f'{"Start":8s} {"End":8s} {"Perm":5s} {"Label":12s} {"Image"}') + self.ql.log.info(f'{"Start":{len_addr}s} {"End":{len_addr}s} {"Perm":5s} {"Label":{len_label}s} {"Image"}') # emit table rows - for lbound, ubound, perms, label, container in self.get_mapinfo(): - self.ql.log.info(f'{lbound:08x} - {ubound:08x} {perms:5s} {label:12s} {container or ""}') + for lbound, ubound, perms, label, container in mapinfo: + self.ql.log.info(f'{lbound:0{len_addr}x} - {ubound:0{len_addr}x} {perms:5s} {label:{len_label}s} {container or ""}') # TODO: relying on the label string is risky; find a more reliable method def get_lib_base(self, filename: str) -> Optional[int]: diff --git a/qiling/os/posix/syscall/mman.py b/qiling/os/posix/syscall/mman.py index cf7585e9c..0a09f1f8e 100755 --- a/qiling/os/posix/syscall/mman.py +++ b/qiling/os/posix/syscall/mman.py @@ -3,7 +3,7 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from unicorn import UC_PROT_ALL +import os from qiling import Qiling from qiling.exception import QlMemoryMappedError @@ -135,7 +135,7 @@ def syscall_mmap_impl(ql: Qiling, addr: int, mlen: int, prot: int, flags: int, f ql.log.debug("mem write : " + hex(len(data))) ql.log.debug("mem mmap : " + mem_info) - ql.mem.change_mapinfo(mmap_base, mmap_base + mmap_size, mem_info=("[%s] " % api_name) + mem_info) + ql.mem.change_mapinfo(mmap_base, mmap_base + mmap_size, mem_info=f'[{api_name}] {os.path.basename(mem_info)}') try: ql.mem.write(mmap_base, data) except Exception as e: From 829ac52ce32af7a9f783bb73b0464827c37b453e Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 10 Mar 2022 12:15:32 +0200 Subject: [PATCH 196/406] Fix image search by label --- qiling/loader/elf.py | 2 +- qiling/os/memory.py | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/qiling/loader/elf.py b/qiling/loader/elf.py index 2eadea64b..a68bc6288 100644 --- a/qiling/loader/elf.py +++ b/qiling/loader/elf.py @@ -199,7 +199,7 @@ def load_with_ld(self, elffile: ELFFile, stack_addr: int, load_address: int, arg # map the memory regions for lbound, ubound, perms in load_regions: try: - self.ql.mem.map(lbound, ubound - lbound, perms, info=self.path) + self.ql.mem.map(lbound, ubound - lbound, perms, info=os.path.basename(self.path)) except QlMemoryMappedError: self.ql.log.exception(f'Failed to map {lbound:#x}-{ubound:#x}') else: diff --git a/qiling/os/memory.py b/qiling/os/memory.py index 0620a6541..843059534 100644 --- a/qiling/os/memory.py +++ b/qiling/os/memory.py @@ -202,7 +202,14 @@ def show_mapinfo(self): # TODO: relying on the label string is risky; find a more reliable method def get_lib_base(self, filename: str) -> Optional[int]: - return next((s for s, _, _, info, _ in self.map_info if os.path.basename(info) == filename), None) + # regex pattern to capture boxed labels prefixes + p = re.compile(r'^\[.+\]\s*') + + # some info labels may be prefixed by boxed label which breaks the search by basename. + # iterate through all info labels and remove all boxed prefixes, if any + stripped = ((lbound, p.sub('', info)) for lbound, _, _, info, _ in self.map_info) + + return next((lbound for lbound, info in stripped if os.path.basename(info) == filename), None) def align(self, value: int, alignment: int = None) -> int: """Align a value down to the specified alignment boundary. If `value` is already From 2b47ec76fcb7fb64cc9c3f562ce64968f4b0303d Mon Sep 17 00:00:00 2001 From: Siger Yang Date: Tue, 15 Mar 2022 12:25:11 +0800 Subject: [PATCH 197/406] Add stat structs for mips big endian --- qiling/os/posix/syscall/stat.py | 61 +++++++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/qiling/os/posix/syscall/stat.py b/qiling/os/posix/syscall/stat.py index 0ca5fd6c2..635108004 100644 --- a/qiling/os/posix/syscall/stat.py +++ b/qiling/os/posix/syscall/stat.py @@ -319,6 +319,57 @@ class LinuxMips64Stat(ctypes.Structure): _pack_ = 8 +class LinuxMips32EBStat(ctypes.BigEndianStructure): + _fields_ = [ + ("st_dev", ctypes.c_uint32), + ("st_pad1", ctypes.c_int32 * 3), + ("st_ino", ctypes.c_uint32), + ("st_mode", ctypes.c_uint32), + ("st_nlink", ctypes.c_uint32), + ("st_uid", ctypes.c_uint32), + ("st_gid", ctypes.c_uint32), + ("st_rdev", ctypes.c_uint32), + ("st_pad2", ctypes.c_uint32 * 2), + ("st_size", ctypes.c_uint32), + ("st_pad3", ctypes.c_uint32), + ("st_atime", ctypes.c_uint32), + ("st_atime_ns", ctypes.c_uint32), + ("st_mtime", ctypes.c_uint32), + ("st_mtime_ns", ctypes.c_uint32), + ("st_ctime", ctypes.c_uint32), + ("st_ctime_ns", ctypes.c_uint32), + ("st_blksize", ctypes.c_uint32), + ("st_blocks", ctypes.c_uint32), + ("st_pad4", ctypes.c_uint32 * 14) + ] + + _pack_ = 4 + +class LinuxMips64EBStat(ctypes.BigEndianStructure): + _fields_ = [ + ("st_dev", ctypes.c_uint32), + ("st_pad0", ctypes.c_uint32 * 3), + ("st_ino", ctypes.c_uint64), + ("st_mode", ctypes.c_uint32), + ("st_nlink", ctypes.c_uint32), + ("st_uid", ctypes.c_uint32), + ("st_gid", ctypes.c_uint32), + ("st_rdev", ctypes.c_uint32), + ("st_pad1", ctypes.c_uint32 * 3), + ("st_size", ctypes.c_uint64), + ("st_atime", ctypes.c_uint32), + ("st_atime_ns", ctypes.c_uint32), + ("st_mtime", ctypes.c_uint32), + ("st_mtime_ns", ctypes.c_uint32), + ("st_ctime", ctypes.c_uint32), + ("st_ctime_ns", ctypes.c_uint32), + ("st_blksize", ctypes.c_uint32), + ("st_pad2", ctypes.c_uint32), + ("st_blocks", ctypes.c_uint64) + ] + + _pack_ = 8 + class LinuxMips32Stat64(ctypes.Structure): _fields_ = [ ("st_dev", ctypes.c_uint32), @@ -962,9 +1013,15 @@ def get_stat_struct(ql: Qiling): return LinuxX86Stat() elif ql.arch.type == QL_ARCH.MIPS: if ql.arch.bits == 64: - return LinuxMips64Stat() + if ql.arch.endian == QL_ENDIAN.EL: + return LinuxMips64Stat() + else: + return LinuxMips64EBStat() else: - return LinuxMips32Stat() + if ql.arch.endian == QL_ENDIAN.EL: + return LinuxMips32Stat() + else: + return LinuxMips32EBStat() elif ql.arch.type == QL_ARCH.ARM: if ql.arch.endian == QL_ENDIAN.EL: return LinuxARMStat() From 9b01024d32cc28e4c1c253581c1831339efc46cd Mon Sep 17 00:00:00 2001 From: Siger Yang Date: Thu, 17 Mar 2022 14:16:55 +0800 Subject: [PATCH 198/406] Fix GDB address endianness for MIPS --- qiling/debugger/gdb/gdb.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/qiling/debugger/gdb/gdb.py b/qiling/debugger/gdb/gdb.py index ebd2c91e0..d3d5d4da3 100644 --- a/qiling/debugger/gdb/gdb.py +++ b/qiling/debugger/gdb/gdb.py @@ -185,9 +185,6 @@ def gdbqmark_converter(arch): nullfill = "0" * int(self.ql.arch.bits / 4) if self.ql.arch.type == QL_ARCH.MIPS: - if self.ql.arch.endian == QL_ENDIAN.EB: - sp = self.addr_to_str(self.ql.arch.regs.arch_sp, endian ="little") - pc = self.addr_to_str(self.ql.arch.regs.arch_pc, endian ="little") self.send('T%.2x%.2x:%s;%.2x:%s;' %(GDB_SIGNAL_TRAP, idhex, sp, pcid, pc)) else: self.send('T%.2x%.2x:%s;%.2x:%s;%.2x:%s;' %(GDB_SIGNAL_TRAP, idhex, nullfill, spid, sp, pcid, pc)) @@ -249,10 +246,7 @@ def handle_g(subcmd): elif self.ql.arch.type == QL_ARCH.MIPS: for reg in self.tables[QL_ARCH.MIPS][:38]: r = self.ql.arch.regs.read(reg) - if self.ql.arch.endian == QL_ENDIAN.EL: - tmp = self.addr_to_str(r, endian ="little") - else: - tmp = self.addr_to_str(r) + tmp = self.addr_to_str(r) s += tmp self.send(s) @@ -396,10 +390,7 @@ def handle_p(subcmd): reg_value = self.ql.arch.regs.read(self.tables[QL_ARCH.MIPS][reg_index - 1]) else: reg_value = 0 - if self.ql.arch.endian == QL_ENDIAN.EL: - reg_value = self.addr_to_str(reg_value, endian="little") - else: - reg_value = self.addr_to_str(reg_value) + reg_value = self.addr_to_str(reg_value) if type(reg_value) is not str: reg_value = self.addr_to_str(reg_value) From 9917a7a011786bd2857334d32a5b98b54121880b Mon Sep 17 00:00:00 2001 From: cq Date: Fri, 18 Mar 2022 10:13:30 +0800 Subject: [PATCH 199/406] fix cacheflush syscall typo --- qiling/os/linux/map_syscall.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiling/os/linux/map_syscall.py b/qiling/os/linux/map_syscall.py index dea619ac9..19bb42695 100644 --- a/qiling/os/linux/map_syscall.py +++ b/qiling/os/linux/map_syscall.py @@ -431,7 +431,7 @@ def __mapper(syscall_num: int) -> str: 445: "landlock_add_rule", 446: "landlock_restrict_self", 448: "process_mrelease", - 983402: "cacheflush", + 983042: "cacheflush", 983045: "set_tls", } From 4c1292cc852d56324f8290f0a2f341ea9068e7b3 Mon Sep 17 00:00:00 2001 From: "kj.xwings.l" Date: Wed, 23 Mar 2022 18:00:04 +0800 Subject: [PATCH 200/406] Add files via upload --- docs/IMG_0022.JPG | Bin 0 -> 131970 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/IMG_0022.JPG diff --git a/docs/IMG_0022.JPG b/docs/IMG_0022.JPG new file mode 100644 index 0000000000000000000000000000000000000000..4d0a09967bf80637bbffe5956d8d5b1d97ae55ea GIT binary patch literal 131970 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 D=jQAM literal 0 HcmV?d00001 From c9071afefd28a60cf465370a8740ad4bb24e95e7 Mon Sep 17 00:00:00 2001 From: "kj.xwings.l" Date: Thu, 24 Mar 2022 15:01:55 +0800 Subject: [PATCH 201/406] Add files via upload From 501f22bf191a91c2cadc0f4c16dd8d6084f05a33 Mon Sep 17 00:00:00 2001 From: xwings Date: Thu, 24 Mar 2022 15:08:40 +0800 Subject: [PATCH 202/406] upload new logo --- docs/bg_page.png | Bin docs/{qiling_big.png => qiling1_logo_big.png} | Bin ...qiling_small.png => qiling1_logo_small.png} | Bin docs/{IMG_0022.JPG => qiling2_logo_big.png} | Bin docs/qiling2_logo_small.png | Bin 0 -> 103067 bytes examples/rootfs | 2 +- 6 files changed, 1 insertion(+), 1 deletion(-) mode change 100755 => 100644 docs/bg_page.png rename docs/{qiling_big.png => qiling1_logo_big.png} (100%) rename docs/{qiling_small.png => qiling1_logo_small.png} (100%) rename docs/{IMG_0022.JPG => qiling2_logo_big.png} (100%) create mode 100644 docs/qiling2_logo_small.png diff --git a/docs/bg_page.png b/docs/bg_page.png old mode 100755 new mode 100644 diff --git a/docs/qiling_big.png b/docs/qiling1_logo_big.png similarity index 100% rename from docs/qiling_big.png rename to docs/qiling1_logo_big.png diff --git a/docs/qiling_small.png b/docs/qiling1_logo_small.png similarity index 100% rename from docs/qiling_small.png rename to docs/qiling1_logo_small.png diff --git a/docs/IMG_0022.JPG b/docs/qiling2_logo_big.png similarity index 100% rename from docs/IMG_0022.JPG rename to docs/qiling2_logo_big.png diff --git a/docs/qiling2_logo_small.png b/docs/qiling2_logo_small.png new file mode 100644 index 0000000000000000000000000000000000000000..b25ed2585f0165b9972c8db84d53eee097dbd300 GIT binary patch literal 103067 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 literal 0 HcmV?d00001 diff --git a/examples/rootfs b/examples/rootfs index 3443f9de3..d8a9b0d6c 160000 --- a/examples/rootfs +++ b/examples/rootfs @@ -1 +1 @@ -Subproject commit 3443f9de35107f98cc5fb20ac17a4cd6480f9ef4 +Subproject commit d8a9b0d6c52a3c5bc627c055d5f711dacbb1a1f6 From 90b2de8e31624c8432a0c3a4e52e661582ffa3e0 Mon Sep 17 00:00:00 2001 From: "kj.xwings.l" Date: Thu, 24 Mar 2022 15:15:40 +0800 Subject: [PATCH 203/406] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 71e45aa17..09207c58b 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ ---

- +

Qiling is an advanced binary emulation framework, with the following features: From aa18b287858689225060fb0b5ffc47bb297d10c5 Mon Sep 17 00:00:00 2001 From: thezero Date: Thu, 24 Mar 2022 11:39:35 +0100 Subject: [PATCH 204/406] pass vm_context as hook's parameter --- qiling/arch/evm/vm/exec.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiling/arch/evm/vm/exec.py b/qiling/arch/evm/vm/exec.py index d2237f4d9..883291261 100644 --- a/qiling/arch/evm/vm/exec.py +++ b/qiling/arch/evm/vm/exec.py @@ -50,13 +50,13 @@ def execute_once(self, opcode:int): if dis_insn.is_hook_code: for h in dis_insn.callback_list.hook_code_list: - h.call(self.vm_context.state.ql) + h.call(self.vm_context.state.ql, self.vm_context) if dis_insn.is_hook_insn: for h in dis_insn.callback_list.hook_insn_list: - h.call(self.vm_context.state.ql) + h.call(self.vm_context.state.ql, self.vm_context) if dis_insn.is_hook_addr: for h in dis_insn.callback_list.hook_addr_dict[pc]: - h.call(self.vm_context.state.ql) + h.call(self.vm_context.state.ql, self.vm_context) except KeyError: opcode_fn = InvalidOpcode(opcode) From 27918c1d893f12871ebf704f147ba562e36ec5d1 Mon Sep 17 00:00:00 2001 From: thezero Date: Thu, 24 Mar 2022 11:52:19 +0100 Subject: [PATCH 205/406] fix evm hooks test --- tests/test_evm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_evm.py b/tests/test_evm.py index c7938ad0c..36d82acd4 100644 --- a/tests/test_evm.py +++ b/tests/test_evm.py @@ -36,7 +36,7 @@ def hookcode_test(ql, *argv): def hookinsn_test(ql, *argv): testcheck.visited_hookinsn = True - def hookaddr_test(ql): + def hookaddr_test(ql, *argv): testcheck.visited_hookaddr = True h0 = ql.hook_code(hookcode_test) From 26fe48dd4c11b5ebd16f8d0f5a9183d33fa8bfb3 Mon Sep 17 00:00:00 2001 From: chinggg <24590067+chinggg@users.noreply.github.com> Date: Mon, 28 Mar 2022 10:02:36 +0800 Subject: [PATCH 206/406] fix(example): mistake in fuzz_x8664_linux binary - Using the result of an assignment as a condition without parentheses - Replace printf with puts --- examples/fuzzing/linux_x8664/fuzz.c | 4 ++-- .../fuzzing/linux_x8664/fuzz_x8664_linux.py | 4 ++-- examples/fuzzing/linux_x8664/x8664_fuzz | Bin 19544 -> 19720 bytes 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/fuzzing/linux_x8664/fuzz.c b/examples/fuzzing/linux_x8664/fuzz.c index 3f7a284a6..068c45bdd 100644 --- a/examples/fuzzing/linux_x8664/fuzz.c +++ b/examples/fuzzing/linux_x8664/fuzz.c @@ -10,13 +10,13 @@ int fun(int i) char *buf = malloc(SIZE); char buf2[SIZE]; - while (*buf = getc(stdin) == 'A') + while ((*buf = getc(stdin)) == 'A') { buf[i++] = *buf; } strncpy(buf2, buf, i); - printf(buf2); + puts(buf2); return 0; } diff --git a/examples/fuzzing/linux_x8664/fuzz_x8664_linux.py b/examples/fuzzing/linux_x8664/fuzz_x8664_linux.py index 2f40ee79a..c7fb84db8 100755 --- a/examples/fuzzing/linux_x8664/fuzz_x8664_linux.py +++ b/examples/fuzzing/linux_x8664/fuzz_x8664_linux.py @@ -61,10 +61,10 @@ def start_afl(ql: Qiling): # make the process crash whenever __stack_chk_fail@plt is about to be called. # this way afl will count stack protection violations as crashes - ql.hook_address(callback=lambda x: os.abort(), address=ba + 0x1225) + ql.hook_address(callback=lambda x: os.abort(), address=ba + 0x126e) # set afl instrumentation [re]starting point. we set it to 'main' - ql.hook_address(callback=start_afl, address=ba + 0x122c) + ql.hook_address(callback=start_afl, address=ba + 0x1275) # okay, ready to roll ql.run() diff --git a/examples/fuzzing/linux_x8664/x8664_fuzz b/examples/fuzzing/linux_x8664/x8664_fuzz index e4028db2e891ffb21bf0e90c5b9db4218469f1b3..8878e1cca5f34bb918f613d552f65abe90154f80 100644 GIT binary patch literal 19720 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 JGqADZe*tHG|0w_f literal 19544 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 Date: Fri, 25 Mar 2022 20:06:07 +0800 Subject: [PATCH 207/406] Load interpreter segments with correct perms and vaddr --- qiling/loader/elf.py | 175 +++++++++++++++++++------------------------ qiling/loader/mcu.py | 5 +- setup.py | 2 +- 3 files changed, 81 insertions(+), 101 deletions(-) diff --git a/qiling/loader/elf.py b/qiling/loader/elf.py index a68bc6288..33e7b922f 100644 --- a/qiling/loader/elf.py +++ b/qiling/loader/elf.py @@ -147,88 +147,86 @@ def seg_perm_to_uc_prot(perm: int) -> int: return prot def load_with_ld(self, elffile: ELFFile, stack_addr: int, load_address: int, argv: Sequence[str] = [], env: Mapping[str, str] = {}): - # get list of loadable segments; these segments will be loaded to memory - seg_pt_load = tuple(seg for seg in elffile.iter_segments() if seg['p_type'] == 'PT_LOAD') - - # determine the memory regions that need to be mapped in order to load the segments. - # note that region boundaries are aligned to page, which means they may be larger than - # the segment they contain. to reduce mapping clutter, adjacent regions with the same - # perms are consolidated into one contigous memory region - load_regions: Sequence[Tuple[int, int, int]] = [] - - # iterate over loadable segments by vaddr - for seg in sorted(seg_pt_load, key=lambda s: s['p_vaddr']): - lbound = self.ql.mem.align(load_address + seg['p_vaddr']) - ubound = self.ql.mem.align_up(load_address + seg['p_vaddr'] + seg['p_memsz']) - perms = QlLoaderELF.seg_perm_to_uc_prot(seg['p_flags']) - - if load_regions: - prev_lbound, prev_ubound, prev_perms = load_regions[-1] - - # new region starts where the previous one ended - if lbound == prev_ubound: - # same perms? extend previous memory region - if perms == prev_perms: - load_regions[-1] = (prev_lbound, ubound, prev_perms) - - # different perms? start a new one - else: + + def load_elf_segments(elffile: ELFFile, load_address: int, info: str): + # get list of loadable segments; these segments will be loaded to memory + load_segments = sorted(elffile.iter_segments(type='PT_LOAD'), key=lambda s: s['p_vaddr']) + + # determine the memory regions that need to be mapped in order to load the segments. + # note that region boundaries are aligned to page, which means they may be larger than + # the segment they contain. to reduce mapping clutter, adjacent regions with the same + # perms are consolidated into one contigous memory region + load_regions: Sequence[Tuple[int, int, int]] = [] + + # iterate over loadable segments + for seg in load_segments: + lbound = self.ql.mem.align(load_address + seg['p_vaddr']) + ubound = self.ql.mem.align_up(load_address + seg['p_vaddr'] + seg['p_memsz']) + perms = QlLoaderELF.seg_perm_to_uc_prot(seg['p_flags']) + + if load_regions: + prev_lbound, prev_ubound, prev_perms = load_regions[-1] + + # new region starts where the previous one ended + if lbound == prev_ubound: + # same perms? extend previous memory region + if perms == prev_perms: + load_regions[-1] = (prev_lbound, ubound, prev_perms) + + # different perms? start a new one + else: + load_regions.append((lbound, ubound, perms)) + + # start a new memory region + elif lbound > prev_ubound: load_regions.append((lbound, ubound, perms)) - # start a new memory region - elif lbound > prev_ubound: + # overlapping segments? something probably went wrong + elif lbound < prev_ubound: + # EDL ELF files use 0x400 bytes pages, which might make some segments look as if they + # start at the same segment as their predecessor. though that is fixable, unicorn + # supports only 0x1000 bytes pages; this becomes problematic when using mem.protect + # + # this workaround unifies such "overlapping" segments, which may apply more permissive + # protection flags to that memory region. + if self.ql.arch.type == QL_ARCH.ARM64: + load_regions[-1] = (prev_lbound, ubound, prev_perms | perms) + continue + + raise RuntimeError + + else: load_regions.append((lbound, ubound, perms)) - # overlapping segments? something probably went wrong - elif lbound < prev_ubound: - # EDL ELF files use 0x400 bytes pages, which might make some segments look as if they - # start at the same segment as their predecessor. though that is fixable, unicorn - # supports only 0x1000 bytes pages; this becomes problematic when using mem.protect - # - # this workaround unifies such "overlapping" segments, which may apply more permissive - # protection flags to that memory region. - if self.ql.arch.type == QL_ARCH.ARM64: - load_regions[-1] = (prev_lbound, ubound, prev_perms | perms) - continue - - raise RuntimeError - - else: - load_regions.append((lbound, ubound, perms)) - - # map the memory regions - for lbound, ubound, perms in load_regions: - try: - self.ql.mem.map(lbound, ubound - lbound, perms, info=os.path.basename(self.path)) - except QlMemoryMappedError: - self.ql.log.exception(f'Failed to map {lbound:#x}-{ubound:#x}') - else: - self.ql.log.debug(f'Mapped {lbound:#x}-{ubound:#x}') - - # load loadable segments contents to memory - for seg in seg_pt_load: - self.ql.mem.write(load_address + seg['p_vaddr'], seg.data()) - - entry_point = load_address + elffile['e_entry'] - - # the memory space on which the program spans - mem_start = min(seg['p_vaddr'] for seg in seg_pt_load) - mem_end = max(seg['p_vaddr'] + seg['p_memsz'] for seg in seg_pt_load) - - mem_start = self.ql.mem.align(mem_start) - mem_end = self.ql.mem.align_up(mem_end) + # map the memory regions + for lbound, ubound, perms in load_regions: + try: + self.ql.mem.map(lbound, ubound - lbound, perms, os.path.basename(info)) + except QlMemoryMappedError: + self.ql.log.exception(f'Failed to map {lbound:#x}-{ubound:#x}') + else: + self.ql.log.debug(f'Mapped {lbound:#x}-{ubound:#x}') + + # load loadable segments contents to memory + for seg in load_segments: + self.ql.mem.write(load_address + seg['p_vaddr'], seg.data()) + + return load_regions[0][0], load_regions[-1][1] + + mem_start, mem_end = load_elf_segments(elffile, load_address, self.path) + self.elf_entry = entry_point = load_address + elffile['e_entry'] self.ql.log.debug(f'mem_start : {mem_start:#x}') self.ql.log.debug(f'mem_end : {mem_end:#x}') # by convention the loaded binary is first on the list - self.images.append(Image(load_address + mem_start, load_address + mem_end, os.path.abspath(self.path))) + self.images.append(Image(mem_start, mem_end, os.path.abspath(self.path))) # note: 0x2000 is the size of [hook_mem] - self.brk_address = load_address + mem_end + 0x2000 + self.brk_address = mem_end + 0x2000 # determine interpreter path - interp_seg = next((seg for seg in elffile.iter_segments() if type(seg) is InterpSegment), None) + interp_seg = next(elffile.iter_segments(type='PT_INTERP'), None) interp_path = str(interp_seg.get_interp_name()) if interp_seg else '' interp_address = 0 @@ -240,30 +238,19 @@ def load_with_ld(self, elffile: ELFFile, stack_addr: int, load_address: int, arg with open(interp_local_path, 'rb') as infile: interp = ELFFile(infile) + min_vaddr = min(seg['p_vaddr'] for seg in interp.iter_segments(type='PT_LOAD')) # determine interpreter base address - interp_address = int(self.profile.get('interp_address'), 0) + # some old interpreters may not be PIE: p_vaddr of the first LOAD segment is not zero + # we should load interpreter at the address p_vaddr specified in such situation + interp_address = int(self.profile.get('interp_address'), 0) if min_vaddr == 0 else 0 self.ql.log.debug(f'Interpreter addr: {interp_address:#x}') - interp_seg_pt_load = tuple(seg for seg in interp.iter_segments() if seg['p_type'] == 'PT_LOAD') - - # determine memory size needed for interpreter - interp_mem_size = max((seg['p_vaddr'] + seg['p_memsz']) for seg in interp_seg_pt_load) - interp_mem_size = self.ql.mem.align_up(interp_mem_size) - self.ql.log.debug(f'Interpreter size: {interp_mem_size:#x}') - - # map memory for interpreter - self.ql.mem.map(interp_address, interp_mem_size, info=os.path.basename(interp_local_path)) + # load interpreter segments data to memory + interp_start, interp_end = load_elf_segments(interp, interp_address, interp_local_path) # add interpreter to the loaded images list - self.images.append(Image(interp_address, interp_address + interp_mem_size, os.path.abspath(interp_local_path))) - - # load interpterter segments data to memory - for seg in interp_seg_pt_load: - addr = interp_address + seg['p_vaddr'] - data = seg.data() - - self.ql.mem.write(addr, data) + self.images.append(Image(interp_start, interp_end, os.path.abspath(interp_local_path))) # determine entry point entry_point = interp_address + interp['e_entry'] @@ -273,7 +260,6 @@ def load_with_ld(self, elffile: ELFFile, stack_addr: int, load_address: int, arg self.ql.log.debug(f'mmap_address is : {mmap_address:#x}') # set info to be used by gdb - self.interp_address = interp_address self.mmap_address = mmap_address # set elf table @@ -315,10 +301,9 @@ def __push_str(top: int, s: str) -> int: new_stack = execfn = __push_str(new_stack, argv[0]) # store aux vector data for gdb use - elf_phdr = load_address + elffile['e_phoff'] + elf_phdr = elffile['e_phoff'] + mem_start elf_phent = elffile['e_phentsize'] elf_phnum = elffile['e_phnum'] - elf_entry = load_address + elffile['e_entry'] if self.ql.arch.bits == 64: elf_hwcap = 0x078bfbfd @@ -333,13 +318,13 @@ def __push_str(top: int, s: str) -> int: # setup aux vector aux_entries = ( - (AUX.AT_PHDR, elf_phdr + mem_start), + (AUX.AT_PHDR, elf_phdr), (AUX.AT_PHENT, elf_phent), (AUX.AT_PHNUM, elf_phnum), (AUX.AT_PAGESZ, self.ql.mem.pagesize), (AUX.AT_BASE, interp_address), (AUX.AT_FLAGS, 0), - (AUX.AT_ENTRY, elf_entry), + (AUX.AT_ENTRY, self.elf_entry), (AUX.AT_UID, self.ql.os.uid), (AUX.AT_EUID, self.ql.os.euid), (AUX.AT_GID, self.ql.os.gid), @@ -361,12 +346,8 @@ def __push_str(top: int, s: str) -> int: new_stack = self.ql.mem.align(new_stack - len(elf_table), 0x10) self.ql.mem.write(new_stack, bytes(elf_table)) - # if enabled, gdb would need to retrieve aux vector data. - # note that gdb needs the AT_PHDR entry to hold the original elf_phdr value self.aux_vec = dict(aux_entries) - self.aux_vec[AUX.AT_PHDR] = elf_phdr - self.elf_entry = elf_entry self.stack_address = new_stack self.load_address = load_address self.init_sp = self.ql.arch.regs.arch_sp @@ -374,7 +355,7 @@ def __push_str(top: int, s: str) -> int: self.ql.os.entry_point = self.entry_point = entry_point self.ql.os.elf_mem_start = mem_start self.ql.os.elf_entry = self.elf_entry - self.ql.os.function_hook = FunctionHook(self.ql, elf_phdr + mem_start, elf_phnum, elf_phent, load_address, load_address + mem_end) + 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 self.skip_exit_check = (self.elf_entry != self.entry_point) diff --git a/qiling/loader/mcu.py b/qiling/loader/mcu.py index a7987e104..ab6028efd 100644 --- a/qiling/loader/mcu.py +++ b/qiling/loader/mcu.py @@ -88,9 +88,8 @@ def guess_filetype(self): def reset(self): if self.filetype == 'elf': - for segment in self.elf.iter_segments(): - if segment['p_type'] == 'PT_LOAD': - self.ql.mem.write(segment['p_paddr'], segment.data()) + for segment in self.elf.iter_segments(type='PT_LOAD'): + self.ql.mem.write(segment['p_paddr'], segment.data()) # TODO: load symbol table diff --git a/setup.py b/setup.py index 398a7fb57..15069f3b6 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ "pefile>=2021.9.3", "python-registry>=1.3.1", "keystone-engine>=0.9.2", - "pyelftools>=0.26", + "pyelftools>=0.28", "gevent>=20.9.0", "multiprocess>=0.70.12.2", "pyyaml>=6.0" From 3b1edaf7db54cef13fb3082467e30ec1152a8b5a Mon Sep 17 00:00:00 2001 From: thezero Date: Sat, 2 Apr 2022 12:41:04 +0200 Subject: [PATCH 208/406] add more ABI encode decode helpers --- qiling/arch/evm/abi.py | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/qiling/arch/evm/abi.py b/qiling/arch/evm/abi.py index 34ff28964..33620217e 100644 --- a/qiling/arch/evm/abi.py +++ b/qiling/arch/evm/abi.py @@ -1,12 +1,18 @@ #!/usr/bin/env python3 -from eth_abi import encode_abi +from eth_abi import encode_abi, decode_abi, encode_single, decode_single +from eth_utils.abi import collapse_if_tuple +from eth_utils import function_signature_to_4byte_selector, function_abi_to_4byte_selector, decode_hex, encode_hex from .vm.utils import bytecode_to_bytes class QlArchEVMABI: @staticmethod def convert(datatypes:list, values:list) -> str: + return QlArchEVMABI.encode_params(datatypes, values) + + @staticmethod + def encode_params(datatypes:list, values:list) -> str: for idx, item in enumerate(datatypes): if item == 'address': if isinstance(values[idx], int): @@ -14,4 +20,27 @@ def convert(datatypes:list, values:list) -> str: elif isinstance(values[idx], str): values[idx] = bytecode_to_bytes(values[idx]) - return encode_abi(datatypes, values).hex() \ No newline at end of file + return encode_abi(datatypes, values).hex() + + @staticmethod + def decode_params(datatypes:list, value:str) -> list: + return decode_abi(datatypes, value) + + @staticmethod + def encode_function_call(abi:str, params:list) -> str: + abi = abi.replace(' ', '') + if '(' not in abi or ')' not in abi: + raise ValueError(f'Function signature must contain "(" and ")": {abi}') + signature = function_signature_to_4byte_selector(abi) + inputs = abi[abi.index('('):] + params = encode_single(inputs, params) + return encode_hex(signature + params) + + @staticmethod + def encode_function_call_abi(abi:dict, params:list) -> str: + signature = function_abi_to_4byte_selector(abi) + inputs = ",".join( + [collapse_if_tuple(abi_input) for abi_input in abi.get("inputs", [])] + ) + params = encode_single(f"({inputs})", params) + return encode_hex(signature + params) \ No newline at end of file From ed2478ec4060d290f4c38a9dfd72b2587f66baf9 Mon Sep 17 00:00:00 2001 From: thezero Date: Sat, 2 Apr 2022 14:31:09 +0200 Subject: [PATCH 209/406] test EVM ABI encoding decoding --- tests/test_evm.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/test_evm.py b/tests/test_evm.py index 36d82acd4..79cf062bc 100644 --- a/tests/test_evm.py +++ b/tests/test_evm.py @@ -111,5 +111,39 @@ def check_balance(sender, destination): result = check_balance(user2, c1) self.assertEqual(int(result.output.hex()[2:], 16), 452312848583266388373324160190187140051835877600158453279131187530910662654) + def test_abi_encoding(self): + ql = Qiling(code="0x608060405234801561001057600080fd5b506101a4806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063ead710c414610030575b600080fd5b6100e96004803603602081101561004657600080fd5b810190808035906020019064010000000081111561006357600080fd5b82018360208201111561007557600080fd5b8035906020019184600183028401116401000000008311171561009757600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050610164565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561012957808201518184015260208101905061010e565b50505050905090810190601f1680156101565780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b606081905091905056fea2646970667358221220cf43353b75256fc42aaffd9632e06963c5c2aad72a91004bfd2f98cd56ae1a0c64736f6c63430006000033",archtype="evm", verbose=4) + + user1 = ql.arch.evm.create_account(balance=100*10**18) + c1 = ql.arch.evm.create_account() + + # Deploy runtime code + msg0 = ql.arch.evm.create_message(user1, b'', contract_address=c1) + ql.run(code=msg0) + + # # SMART CONTRACT DEPENDENT: transform from user1 to user2 + call_param = ['Hello World'] + call_data = ql.arch.evm.abi.encode_function_call('greet(string)', call_param) + + function_abi = { + 'name': 'greet', + 'type': 'function', + 'inputs': [{ + 'type': 'string', + 'name': '' + }] + } + call_data2 = ql.arch.evm.abi.encode_function_call_abi(function_abi, call_param) + call_data3 = '0xead710c4'+ ql.arch.evm.abi.convert(['string'], call_param) + + self.assertEqual(call_data, call_data2) + self.assertEqual(call_data, call_data3) + + msg1 = ql.arch.evm.create_message(user1, c1, data=call_data) + result = ql.run(code=msg1) + + result_data = ql.arch.evm.abi.decode_params(['string'], result.output) + self.assertEqual(call_param[0], result_data[0]) + if __name__ == "__main__": unittest.main() \ No newline at end of file From 113d0fd9698e000e68acf3ee781a3a326641b193 Mon Sep 17 00:00:00 2001 From: thezero Date: Sat, 2 Apr 2022 20:20:24 +0200 Subject: [PATCH 210/406] fix stack view in evm dbg --- qiling/arch/evm/vm/dbgcui.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/qiling/arch/evm/vm/dbgcui.py b/qiling/arch/evm/vm/dbgcui.py index c9db85cff..b1c2daf8e 100644 --- a/qiling/arch/evm/vm/dbgcui.py +++ b/qiling/arch/evm/vm/dbgcui.py @@ -59,10 +59,12 @@ def hexdump(src, length=16, sep='.', minrows=8, start=0, prevsrc="", to_list=Fal return result return '\n'.join(result) -def stackdump(src, length=16, minrows=8, start=0, to_list=False): +def stackdump(src, minrows=8, to_list=False): result = [] - for i in range(start, min(minrows, len(src))): + # only the last N elements should be printed + start = len(src) - min(minrows, len(src)) + for i in range(min(minrows, len(src)) + start - 1, start - 1, -1): v_type = src[i][0] value = src[i][1] if v_type is bytes: From dcf507d109ee2bf5780b4d655e3abee637096145 Mon Sep 17 00:00:00 2001 From: chinggg <24590067+chinggg@users.noreply.github.com> Date: Tue, 5 Apr 2022 19:50:22 +0800 Subject: [PATCH 211/406] fix(posix): handle exception when open socket --- qiling/os/posix/syscall/socket.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/qiling/os/posix/syscall/socket.py b/qiling/os/posix/syscall/socket.py index be01291d7..a27baa552 100644 --- a/qiling/os/posix/syscall/socket.py +++ b/qiling/os/posix/syscall/socket.py @@ -69,10 +69,9 @@ def ql_bin_to_ip(ip): def ql_syscall_socket(ql: Qiling, socket_domain, socket_type, socket_protocol): idx = next((i for i in range(NR_OPEN) if ql.os.fd[i] is None), -1) + regreturn = idx - if idx == -1: - regreturn = -1 - else: + if idx != -1: # ql_socket.open should use host platform based socket_type. try: emu_socket_value = socket_type @@ -88,12 +87,14 @@ def ql_syscall_socket(ql: Qiling, socket_domain, socket_type, socket_protocol): ql.log.error(f'Cannot convert emu_socket_type {emu_socket_value} to host platform based socket_type') raise - if ql.verbose >= QL_VERBOSE.DEBUG: # set REUSEADDR options under debug mode - ql.os.fd[idx] = ql_socket.open(socket_domain, socket_type, socket_protocol, (socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)) - else: - ql.os.fd[idx] = ql_socket.open(socket_domain, socket_type, socket_protocol) - - regreturn = idx + try: + if ql.verbose >= QL_VERBOSE.DEBUG: # set REUSEADDR options under debug mode + ql.os.fd[idx] = ql_socket.open(socket_domain, socket_type, socket_protocol, (socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)) + else: + ql.os.fd[idx] = ql_socket.open(socket_domain, socket_type, socket_protocol) + except OSError as e: # May raise error: Protocol not supported + ql.log.debug(f'{e}: {socket_domain=}, {socket_type=}, {socket_protocol=}') + regreturn = -1 socket_type = socket_type_mapping(socket_type, ql.arch.type, ql.ostype) socket_domain = socket_domain_mapping(socket_domain, ql.arch.type, ql.ostype) From 8c2007d52948ef5893d634e85f884c791aae047a Mon Sep 17 00:00:00 2001 From: kabeor Date: Thu, 7 Apr 2022 14:39:23 +0800 Subject: [PATCH 212/406] update changelog to 1.4.3 --- ChangeLog | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/ChangeLog b/ChangeLog index 078fadbc4..7ef9840d8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,31 @@ This file details the changelog of Qiling Framework. +------------------------------------ +[Version 1.4.3]: April 7th, 2022 + +Improvements: +- Fix fuzzing for tendaac15 (#1096) +- Update unicorn version to 2.0-rc6 (#1100) +- Implemented a few more Windows msvcrt functions (#1102) +- Minor PE Loader fix (#1104) +- Minor quality changes (#1106) +- Fix cacheflush syscall typo (#1115) +- Add vm_context to EVM hooks (#1119) +- Load interpreter segments with correct perms and vaddr (#1120) +- Fix mistakes in fuzz_x8664_linux binary (#1121) +- Add EVM ABI helpers, fix EVM DBG stack view (#1123) +- Fix regression caused by missing exception handling when opening socket (#1124) + +Contributors: +- wtdcode +- aquynh +- elicn +- xwings +- cq674350529 +- TheZ3ro +- bet4it +- chinggg + ------------------------------------ [Version 1.4.2]: Feb 13th, 2022 From 2a344a8da681d15552929974665fd888fe4ede47 Mon Sep 17 00:00:00 2001 From: chfl4gs Date: Sat, 9 Apr 2022 02:40:07 +0800 Subject: [PATCH 213/406] Windows server 2022 runner --- .github/workflows/build-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-ci.yml b/.github/workflows/build-ci.yml index 36523a470..c28d542a3 100644 --- a/.github/workflows/build-ci.yml +++ b/.github/workflows/build-ci.yml @@ -10,7 +10,7 @@ jobs: fail-fast: false matrix: #os: [windows-2019, macos-10.15, ubuntu-18.04, ubuntu-20.04] - os: [windows-2019, ubuntu-18.04, ubuntu-20.04] + os: [windows-latest, ubuntu-18.04, ubuntu-20.04] python-version: [3.8, 3.9] exclude: - os: ubuntu-18.04 From 45cb8bf1e612dc56219ec41fd250b5d09c3cf9f9 Mon Sep 17 00:00:00 2001 From: "kj.xwings.l" Date: Thu, 14 Apr 2022 09:16:51 +0800 Subject: [PATCH 214/406] Update CREDITS.TXT --- CREDITS.TXT | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CREDITS.TXT b/CREDITS.TXT index bfdc28b24..0317aaeba 100644 --- a/CREDITS.TXT +++ b/CREDITS.TXT @@ -28,9 +28,10 @@ CHEN huitao (null) YU tong (sp1ke) -Travis, Website and Documentations +CI, Website, Documentations & Logo ================================== FOO Kevin (chfl4gs) +SU muchen (Mirai Suu) Key Contributors (in no particular order) From f6a7970cdb8808469fab8e31021548c39f068edc Mon Sep 17 00:00:00 2001 From: "kj.xwings.l" Date: Thu, 14 Apr 2022 09:21:35 +0800 Subject: [PATCH 215/406] Update CREDITS.TXT --- CREDITS.TXT | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CREDITS.TXT b/CREDITS.TXT index 0317aaeba..448cbc136 100644 --- a/CREDITS.TXT +++ b/CREDITS.TXT @@ -28,8 +28,8 @@ CHEN huitao (null) YU tong (sp1ke) -CI, Website, Documentations & Logo -================================== +CI, Website,Documentations, Logo & Swags +========================================= FOO Kevin (chfl4gs) SU muchen (Mirai Suu) From db3b37c721fe099a2e055553288d083f23785d1d Mon Sep 17 00:00:00 2001 From: "kj.xwings.l" Date: Thu, 14 Apr 2022 09:25:21 +0800 Subject: [PATCH 216/406] Update giteesync.yml --- .github/workflows/giteesync.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/giteesync.yml b/.github/workflows/giteesync.yml index 8bedfbd56..07f8a7337 100644 --- a/.github/workflows/giteesync.yml +++ b/.github/workflows/giteesync.yml @@ -13,3 +13,4 @@ with: ssh_private_key: ${{ secrets.GITEE_KEY }} target_repo: ssh://git@gitee.com/qilingframework/qiling.git + allow_failure: true From b89572467652fb103b0811daf771630347a1f01e Mon Sep 17 00:00:00 2001 From: "kj.xwings.l" Date: Thu, 14 Apr 2022 09:38:15 +0800 Subject: [PATCH 217/406] Update giteesync.yml --- .github/workflows/giteesync.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/giteesync.yml b/.github/workflows/giteesync.yml index 07f8a7337..5f461382d 100644 --- a/.github/workflows/giteesync.yml +++ b/.github/workflows/giteesync.yml @@ -11,6 +11,7 @@ fetch-depth: 0 - uses: acefei/sync-repo-action@master with: + run: git config --global --add safe.directory * ssh_private_key: ${{ secrets.GITEE_KEY }} target_repo: ssh://git@gitee.com/qilingframework/qiling.git - allow_failure: true + From ab5cc2b70e590006d39b92f92a3c9b6d8eecbd88 Mon Sep 17 00:00:00 2001 From: xwings Date: Thu, 14 Apr 2022 09:42:03 +0800 Subject: [PATCH 218/406] update credits.txt --- CREDITS.TXT | 5 ----- 1 file changed, 5 deletions(-) diff --git a/CREDITS.TXT b/CREDITS.TXT index 090d33caa..448cbc136 100644 --- a/CREDITS.TXT +++ b/CREDITS.TXT @@ -28,13 +28,8 @@ CHEN huitao (null) YU tong (sp1ke) -<<<<<<< HEAD CI, Website,Documentations, Logo & Swags ========================================= -======= -CI, Website, Documentations & Logo -================================== ->>>>>>> 02b7f5ededa103ef230c58a50109e2496f586da8 FOO Kevin (chfl4gs) SU muchen (Mirai Suu) From bf80581d4ad0c11f1063f8d563c188221acd4773 Mon Sep 17 00:00:00 2001 From: "kj.xwings.l" Date: Thu, 14 Apr 2022 09:56:09 +0800 Subject: [PATCH 219/406] Update giteesync.yml --- .github/workflows/giteesync.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/giteesync.yml b/.github/workflows/giteesync.yml index 5f461382d..68c2b65aa 100644 --- a/.github/workflows/giteesync.yml +++ b/.github/workflows/giteesync.yml @@ -11,7 +11,7 @@ fetch-depth: 0 - uses: acefei/sync-repo-action@master with: - run: git config --global --add safe.directory * + run: git config --global --add safe.directory /github/workspace ssh_private_key: ${{ secrets.GITEE_KEY }} target_repo: ssh://git@gitee.com/qilingframework/qiling.git From c43c26d1a8fdf0805f99457b30a5704ad2f2b95d Mon Sep 17 00:00:00 2001 From: "kj.xwings.l" Date: Thu, 14 Apr 2022 09:59:52 +0800 Subject: [PATCH 220/406] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 09207c58b..76be361a9 100644 --- a/README.md +++ b/README.md @@ -220,6 +220,9 @@ Contact us at email info@qiling.io, or via Twitter [@qiling_io](https://twitter. #### Core developers, Key Contributors and etc Please refer to [CREDITS.TXT](https://github.com/qilingframework/qiling/blob/master/CREDITS.TXT) +```{r results="asis"} +cat(readLines('CREDITS.TXT'), sep = '\n') +``` --- From 188a0dc97c7a382bd848c942e21e3c578ce4b7ef Mon Sep 17 00:00:00 2001 From: "kj.xwings.l" Date: Thu, 14 Apr 2022 10:01:07 +0800 Subject: [PATCH 221/406] Update README.md --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 76be361a9..fcb5e0da4 100644 --- a/README.md +++ b/README.md @@ -220,9 +220,7 @@ Contact us at email info@qiling.io, or via Twitter [@qiling_io](https://twitter. #### Core developers, Key Contributors and etc Please refer to [CREDITS.TXT](https://github.com/qilingframework/qiling/blob/master/CREDITS.TXT) -```{r results="asis"} -cat(readLines('CREDITS.TXT'), sep = '\n') -``` + --- From bde231c0750d2c815e51d357cf88383647a58dcb Mon Sep 17 00:00:00 2001 From: "kj.xwings.l" Date: Thu, 14 Apr 2022 10:19:48 +0800 Subject: [PATCH 222/406] Update giteesync.yml --- .github/workflows/giteesync.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/giteesync.yml b/.github/workflows/giteesync.yml index 68c2b65aa..1e45e9b37 100644 --- a/.github/workflows/giteesync.yml +++ b/.github/workflows/giteesync.yml @@ -9,9 +9,10 @@ - uses: actions/checkout@v2 with: fetch-depth: 0 + run: | + git config --global --add safe.directory /github/workspace - uses: acefei/sync-repo-action@master with: - run: git config --global --add safe.directory /github/workspace ssh_private_key: ${{ secrets.GITEE_KEY }} target_repo: ssh://git@gitee.com/qilingframework/qiling.git From 597df614109a5bf74c5ccf47a94a3192756cd6cf Mon Sep 17 00:00:00 2001 From: "kj.xwings.l" Date: Thu, 14 Apr 2022 10:22:55 +0800 Subject: [PATCH 223/406] Update giteesync.yml --- .github/workflows/giteesync.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/giteesync.yml b/.github/workflows/giteesync.yml index 1e45e9b37..b770d37c3 100644 --- a/.github/workflows/giteesync.yml +++ b/.github/workflows/giteesync.yml @@ -9,9 +9,7 @@ - uses: actions/checkout@v2 with: fetch-depth: 0 - run: | - git config --global --add safe.directory /github/workspace - - uses: acefei/sync-repo-action@master + - uses: xwings/sync-repo-action@master with: ssh_private_key: ${{ secrets.GITEE_KEY }} target_repo: ssh://git@gitee.com/qilingframework/qiling.git From f384146d3bae4adde43cb395e0fb6626675edfce Mon Sep 17 00:00:00 2001 From: "kj.xwings.l" Date: Thu, 14 Apr 2022 16:02:29 +0800 Subject: [PATCH 224/406] Update and rename CREDITS.TXT to CREDITS.md --- CREDITS.TXT | 87 ----------------------------------------------------- CREDITS.md | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 87 deletions(-) delete mode 100644 CREDITS.TXT create mode 100644 CREDITS.md diff --git a/CREDITS.TXT b/CREDITS.TXT deleted file mode 100644 index 448cbc136..000000000 --- a/CREDITS.TXT +++ /dev/null @@ -1,87 +0,0 @@ -This file credits all the contributors of the Qiling Framework project. - - -Project Leader -============== -LAU kaijern (xwings) - - -Advisor -======= -NGUYEN Anh Quynh - - -Core Developers Crew -==================== -Earl MARCUS (klks84) klks84@gmail.com -WU chenxu (kabeor) -KONG ziqiao (lazymio) -YU zheng (dataisland) -Eli Cohen Nehemia (elicn) - - -Legacy Core Developers -====================== -DING tianze (D1iv3) -SUN bowen (w1tcher) -CHEN huitao (null) -YU tong (sp1ke) - - -CI, Website,Documentations, Logo & Swags -========================================= -FOO Kevin (chfl4gs) -SU muchen (Mirai Suu) - - -Key Contributors (in no particular order) -========================================= -0ssigeno -liba2k -assafcarlsbad -ucgJhe -jhumble -Mark Jansen (learn-more) -cq674350529 -bkerler (viperbjk) - - -Demigod team (https://groundx.io/demigod) -========================================= -NGUYEN Anh Quynh -NGUYEN Hong Quang -DO Minh Tuan - - -Contributors (in no particular order) -===================================== -WanderingGlitch -Nguyen Hong Quang (quangnh89) -re-fox -t14g0p -Hcamael -hugsy -danielhenrymantilla -iamyeh -alfink -bambu -madprogrammer -danielmoos - - -Alpha testers (in no particular order, named by github id) -========================================================== -klks -ekso -bannsec -ChrisTheCoolHut -domenukk -droberson -hugsy -Masrepus -phdphuc -sashs -knownsec -hwiosec -iamyeh - diff --git a/CREDITS.md b/CREDITS.md new file mode 100644 index 000000000..09fc255b1 --- /dev/null +++ b/CREDITS.md @@ -0,0 +1,87 @@ +### This file credits all the contributors of the Qiling Framework project. + + +#### Founder + +- LAU kaijern (xwings) + + +#### Advisor + +- NGUYEN Anh Quynh + + +#### Core Developers Crew + +- Earl MARCUS (klks84) klks84@gmail.com +- WU chenxu (kabeor) +- KONG ziqiao (lazymio) +- YU zheng (dataisland) +- Eli Cohen Nehemia (elicn) + + +#### CI, Website,Documentations, Logo & Swags + +- FOO Kevin (chfl4gs) +- SU muchen (Mirai Suu) + + +#### Key Contributors (in no particular order) + +- 0ssigeno +- liba2k +- assafcarlsbad +- ucgJhe +- jhumble +- Mark Jansen (learn-more) +- cq674350529 +- bkerler (viperbjk) + + +#### Contributors (in no particular order) + +- WanderingGlitch +- Nguyen Hong Quang (quangnh89) +- re-fox +- t14g0p +- Hcamael +- hugsy +- danielhenrymantilla +- iamyeh +- alfink +- bambu +- madprogrammer +- danielmoos + + +#### Legacy Core Developers + +- DING tianze (D1iv3) +- SUN bowen (w1tcher) +- CHEN huitao (null) +- YU tong (sp1ke) + + +#### Demigod team (https://groundx.io/demigod) + +- NGUYEN Anh Quynh +- NGUYEN Hong Quang +- DO Minh Tuan + + +#### Alpha testers (in no particular order, named by github id) + +- klks +- ekso +- bannsec +- ChrisTheCoolHut +- domenukk +- droberson +- hugsy +- Masrepus +- phdphuc +- sashs +- knownsec +- hwiosec +- iamyeh + From 7811af4e02960a5577a1bae8414b6fad597a4816 Mon Sep 17 00:00:00 2001 From: "kj.xwings.l" Date: Thu, 14 Apr 2022 16:05:01 +0800 Subject: [PATCH 225/406] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fcb5e0da4..c254a0df2 100644 --- a/README.md +++ b/README.md @@ -219,7 +219,7 @@ Contact us at email info@qiling.io, or via Twitter [@qiling_io](https://twitter. #### Core developers, Key Contributors and etc -Please refer to [CREDITS.TXT](https://github.com/qilingframework/qiling/blob/master/CREDITS.TXT) +Please refer to [CREDITS.md](https://github.com/qilingframework/qiling/blob/master/CREDITS.md) --- From 935bfa1a6b64ce17efbbb72d47c26eb37a449720 Mon Sep 17 00:00:00 2001 From: "kj.xwings.l" Date: Thu, 14 Apr 2022 16:06:06 +0800 Subject: [PATCH 226/406] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c254a0df2..604829acc 100644 --- a/README.md +++ b/README.md @@ -219,7 +219,7 @@ Contact us at email info@qiling.io, or via Twitter [@qiling_io](https://twitter. #### Core developers, Key Contributors and etc -Please refer to [CREDITS.md](https://github.com/qilingframework/qiling/blob/master/CREDITS.md) +Please refer to [CREDITS.md](https://github.com/qilingframework/qiling/blob/dev/CREDITS.md) --- From 29c33ab9c0fc2031c8977af204ead321159d8a0d Mon Sep 17 00:00:00 2001 From: Siger Yang Date: Sun, 17 Apr 2022 13:23:26 +0800 Subject: [PATCH 227/406] Support for MIPS / ARM big endian This commit adds * Tests and examples for mips32eb & armeb * Update arm kernel helpers (might be necessary for armeb) --- examples/hello_armeb_linux_customapi.py | 20 +++++ examples/hello_mips32_linux_customapi.py | 20 +++++ examples/multithreading_armeb_linux.py | 17 +++++ examples/multithreading_mips32_linux.py | 17 +++++ qiling/arch/arm_utils.py | 95 +++++++++++++++++------ qiling/os/linux/linux.py | 6 +- qiling/os/linux/syscall.py | 2 +- tests/test_elf.py | 10 +++ tests/test_elf_multithread.py | 96 +++++++++++++++++++++++- 9 files changed, 259 insertions(+), 24 deletions(-) create mode 100644 examples/hello_armeb_linux_customapi.py create mode 100644 examples/hello_mips32_linux_customapi.py create mode 100644 examples/multithreading_armeb_linux.py create mode 100644 examples/multithreading_mips32_linux.py diff --git a/examples/hello_armeb_linux_customapi.py b/examples/hello_armeb_linux_customapi.py new file mode 100644 index 000000000..455955513 --- /dev/null +++ b/examples/hello_armeb_linux_customapi.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +import sys +sys.path.append("..") + +from qiling import Qiling +from qiling.os.const import STRING +from qiling.const import QL_VERBOSE + +def my_puts(ql: Qiling): + params = ql.os.resolve_fcall_params({'s': STRING}) + + print(f'puts("{params["s"]}")') + +if __name__ == "__main__": + ql = Qiling(["rootfs/armeb_linux/bin/armeb_hello"], "rootfs/armeb_linux", verbose=QL_VERBOSE.DEBUG) + ql.run() diff --git a/examples/hello_mips32_linux_customapi.py b/examples/hello_mips32_linux_customapi.py new file mode 100644 index 000000000..286d62faa --- /dev/null +++ b/examples/hello_mips32_linux_customapi.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +import sys +sys.path.append("..") + +from qiling import Qiling +from qiling.os.const import STRING +from qiling.const import QL_VERBOSE + +def my_puts(ql: Qiling): + params = ql.os.resolve_fcall_params({'s': STRING}) + + print(f'puts("{params["s"]}")') + +if __name__ == "__main__": + ql = Qiling(["rootfs/mips32_linux/bin/mips32_hello"], "rootfs/mips32_linux", verbose=QL_VERBOSE.DEBUG) + ql.run() diff --git a/examples/multithreading_armeb_linux.py b/examples/multithreading_armeb_linux.py new file mode 100644 index 000000000..d775be4f1 --- /dev/null +++ b/examples/multithreading_armeb_linux.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +import sys +sys.path.append("..") + +from qiling import Qiling +from qiling.const import QL_VERBOSE + +def my_sandbox(path, rootfs): + ql = Qiling(path, rootfs, verbose=QL_VERBOSE.DEBUG, multithread=True) + ql.run() + +if __name__ == "__main__": + my_sandbox(["rootfs/armeb_linux/bin/armeb_multithreading"], "rootfs/armeb_linux") diff --git a/examples/multithreading_mips32_linux.py b/examples/multithreading_mips32_linux.py new file mode 100644 index 000000000..2c26e039a --- /dev/null +++ b/examples/multithreading_mips32_linux.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +import sys +sys.path.append("..") + +from qiling import Qiling +from qiling.const import QL_VERBOSE + +def my_sandbox(path, rootfs): + ql = Qiling(path, rootfs, verbose=QL_VERBOSE.DEBUG, multithread=True) + ql.run() + +if __name__ == "__main__": + my_sandbox(["rootfs/mips32_linux/bin/mips32_multithreading"], "rootfs/mips32_linux") diff --git a/qiling/arch/arm_utils.py b/qiling/arch/arm_utils.py index a85700866..ce19d0faf 100644 --- a/qiling/arch/arm_utils.py +++ b/qiling/arch/arm_utils.py @@ -4,34 +4,87 @@ # from qiling import Qiling +from qiling.const import QL_ENDIAN -def init_get_tls(ql: Qiling, address: int) -> None: - # adr r0, data - # ldr r0, [r0] - # mov pc, lr +def init_linux_traps(ql: Qiling, address_map: dict[str, int]) -> None: + # If the compiler for the target does not provides some primitives for some + # reasons (e.g. target limitations), the kernel is responsible to assist + # with these operations. # - # data: - # .ascii "\x00\x00" + # The following is some `kuser` helpers, which can be found here: + # https://elixir.bootlin.com/linux/latest/source/arch/arm/kernel/entry-armv.S#L899 - code = bytes.fromhex(''' - 04 00 8f e2 - 00 00 90 e5 + trap_map = { + 'memory_barrier': + # @ 0xffff0fa0 + # mcr p15, 0, r0, c7, c10, 5 + # nop + # mov pc, lr + ''' + ba 0f 07 ee + 00 f0 20 e3 0e f0 a0 e1 + ''', + + 'cmpxchg': + # @ 0xffff0fc0 + # ldr r3, [r2] + # subs r3, r3, r0 + # streq r1, [r2] + # rsbs r0, r3, #0 + # mov pc, lr + ''' + 00 30 92 e5 + 00 30 53 e0 + 00 10 82 05 + 00 00 73 e2 + 0e f0 a0 e1 + ''', + + 'get_tls': + # @ 0xffff0fe0 + # ldr r0, [pc, #(16 - 8)] + # mov pc, lr + # mrc p15, 0, r0, c13, c0, 3 + # padding (e7 fd de f1) + # data: + # "\x00\x00\x00\x00" + # "\x00\x00\x00\x00" + # "\x00\x00\x00\x00" + ''' + 08 00 9f e5 + 0e f0 a0 e1 + 70 0f 1d ee + e7 fd de f1 + 00 00 00 00 00 00 00 00 - ''') + 00 00 00 00 + ''' + } - # if endian == QL_ENDIAN.EB: - # code = swap_endianess(code) + if address_map: + # Find min / max address in address_map + lower_bound = min(address_map.values()) + # Get max address in address_map and its trap name + upper_trap = max(address_map, key=address_map.get) + base = ql.mem.align(lower_bound) + # size to map = start of upper_trap + len of upper_trap - start of lower_trap + size = ql.mem.align_up(address_map[upper_trap] - lower_bound + len(trap_map[upper_trap])) - base = ql.mem.align(address) - size = ql.mem.align_up(len(code)) + ql.mem.map(base, size, info="[arm_traps]") - ql.mem.map(base, size, info="[arm_tls]") - ql.mem.write(address, code) + for trap_name, trap_hex in trap_map.items(): + trap_code = bytes.fromhex(trap_hex) - ql.log.debug('Set kernel get_tls') + if ql.arch.endian == QL_ENDIAN.EB: + trap_code = swap_endianness(trap_code) -# def swap_endianess(s: bytes, blksize: int = 4) -> bytes: -# blocks = (s[i:i + blksize] for i in range(0, len(s), blksize)) -# -# return b''.join(bytes(reversed(b)) for b in blocks) \ No newline at end of file + if trap_name in address_map: + ql.mem.write(address_map[trap_name], trap_code) + + ql.log.debug(f'Set kernel trap: {trap_name} at {address_map[trap_name]:#x}') + +def swap_endianness(s: bytes, blksize: int = 4) -> bytes: + blocks = (s[i:i + blksize] for i in range(0, len(s), blksize)) + + return b''.join(bytes(reversed(b)) for b in blocks) \ No newline at end of file diff --git a/qiling/os/linux/linux.py b/qiling/os/linux/linux.py index 21ab6d5dd..8ce5f4155 100644 --- a/qiling/os/linux/linux.py +++ b/qiling/os/linux/linux.py @@ -55,7 +55,11 @@ def load(self): self.ql.arch.enable_vfp() self.ql.hook_intno(self.hook_syscall, 2) self.thread_class = thread.QlLinuxARMThread - arm_utils.init_get_tls(self.ql, self.ql.arch.arm_get_tls_addr) + arm_utils.init_linux_traps(self.ql, { + 'memory_barrier': 0xffff0fa0, + 'cmpxchg': 0xffff0fc0, + 'get_tls': 0xffff0fe0 + }) # MIPS32 elif self.ql.arch.type == QL_ARCH.MIPS: diff --git a/qiling/os/linux/syscall.py b/qiling/os/linux/syscall.py index ae5a465a0..b3f10a7e7 100644 --- a/qiling/os/linux/syscall.py +++ b/qiling/os/linux/syscall.py @@ -65,7 +65,7 @@ def ql_syscall_set_thread_area(ql: Qiling, u_info_addr: int): def ql_syscall_set_tls(ql, address, *args, **kw): if ql.arch.type == QL_ARCH.ARM: ql.arch.regs.c13_c0_3 = address - ql.mem.write_ptr(ql.arch.arm_get_tls_addr + 12, address, 4) + ql.mem.write_ptr(ql.arch.arm_get_tls_addr + 16, address, 4) ql.arch.regs.r0 = address ql.log.debug("settls(0x%x)" % address) diff --git a/tests/test_elf.py b/tests/test_elf.py index 96bb97029..23864456f 100644 --- a/tests/test_elf.py +++ b/tests/test_elf.py @@ -1037,6 +1037,16 @@ def test_elf_linux_x8664_getdents(self): del ql + def test_elf_linux_armeb(self): + ql = Qiling(["../examples/rootfs/armeb_linux/bin/armeb_hello"], "../examples/rootfs/armeb_linux", verbose=QL_VERBOSE.DEBUG, profile='profiles/append_test.ql') + ql.run() + del ql + + def test_elf_linux_armeb_static(self): + ql = Qiling(["../examples/rootfs/armeb_linux/bin/armeb_hello_static"], "../examples/rootfs/armeb_linux", verbose=QL_VERBOSE.DEFAULT) + ql.run() + del ql + # TODO: Disable for now # def test_armoabi_eb_linux_syscall_elf_static(self): # # src: https://github.com/qilingframework/qiling/blob/1f1e9bc756e59a0bfc112d32735f8882b1afc165/examples/src/linux/posix_syscall.c diff --git a/tests/test_elf_multithread.py b/tests/test_elf_multithread.py index 3c9a3f438..fd909f2f8 100644 --- a/tests/test_elf_multithread.py +++ b/tests/test_elf_multithread.py @@ -106,6 +106,25 @@ def check_write(ql, write_fd, write_buf, write_count, *args, **kw): del ql + def test_multithread_elf_linux_mips32eb(self): + def check_write(ql, write_fd, write_buf, write_count, *args, **kw): + nonlocal buf_out + try: + buf = ql.mem.read(write_buf, write_count) + buf = buf.decode() + buf_out = buf + except: + pass + buf_out = None + ql = Qiling(["../examples/rootfs/mips32_linux/bin/mips32_multithreading"], "../examples/rootfs/mips32_linux", multithread=True, verbose=QL_VERBOSE.DEBUG) + ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER) + ql.run() + + self.assertTrue("thread 2 ret val is" in buf_out) + + del ql + + def test_multithread_elf_linux_mips32el(self): def check_write(ql, write_fd, write_buf, write_count, *args, **kw): nonlocal buf_out @@ -144,6 +163,25 @@ def check_write(ql, write_fd, write_buf, write_count, *args, **kw): del ql + def test_multithread_elf_linux_armeb(self): + def check_write(ql, write_fd, write_buf, write_count, *args, **kw): + nonlocal buf_out + try: + buf = ql.mem.read(write_buf, write_count) + buf = buf.decode() + buf_out = buf + except: + pass + buf_out = None + ql = Qiling(["../examples/rootfs/armeb_linux/bin/armeb_multithreading"], "../examples/rootfs/armeb_linux", multithread=True, verbose=QL_VERBOSE.DEBUG) + ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER) + ql.run() + + self.assertTrue("thread 2 ret val is" in buf_out) + + del ql + + def test_tcp_elf_linux_x86(self): def check_write(ql, write_fd, write_buf, write_count, *args, **kw): try: @@ -216,6 +254,30 @@ def check_write(ql, write_fd, write_buf, write_count, *args, **kw): del ql + def test_tcp_elf_linux_armeb(self): + def check_write(ql, write_fd, write_buf, write_count, *args, **kw): + try: + buf = ql.mem.read(write_buf, write_count) + buf = buf.decode() + if buf.startswith("server send()"): + ql.buf_out = buf + except: + pass + ql = Qiling(["../examples/rootfs/armeb_linux/bin/armeb_tcp_test","20003"], "../examples/rootfs/armeb_linux", multithread=True) + ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER) + ql.run() + + self.assertEqual("server send() 14 return 14.\n", ql.buf_out) + + del ql + + + def test_tcp_elf_linux_mips32eb(self): + ql = Qiling(["../examples/rootfs/mips32_linux/bin/mips32_tcp_test","20005"], "../examples/rootfs/mips32_linux", multithread=True) + ql.run() + del ql + + def test_tcp_elf_linux_mips32el(self): ql = Qiling(["../examples/rootfs/mips32el_linux/bin/mips32el_tcp_test","20005"], "../examples/rootfs/mips32el_linux", multithread=True) ql.run() @@ -276,7 +338,25 @@ def check_write(ql, write_fd, write_buf, write_count, *args, **kw): self.assertEqual("server sendto() 14 return 14.\n", ql.buf_out) del ql - + + def test_udp_elf_linux_armeb(self): + def check_write(ql, write_fd, write_buf, write_count, *args, **kw): + try: + buf = ql.mem.read(write_buf, write_count) + buf = buf.decode() + if buf.startswith("server sendto()"): + ql.buf_out = buf + except: + pass + + ql = Qiling(["../examples/rootfs/armeb_linux/bin/armeb_udp_test","20009"], "../examples/rootfs/armeb_linux", multithread=True) + ql.os.set_syscall("write", check_write, QL_INTERCEPT.ENTER) + ql.run() + + self.assertEqual("server sendto() 14 return 14.\n", ql.buf_out) + + del ql + def test_http_elf_linux_x8664(self): def picohttpd(): ql = Qiling(["../examples/rootfs/x8664_linux/bin/picohttpd"], "../examples/rootfs/x8664_linux", multithread=True, verbose=QL_VERBOSE.DEBUG) @@ -305,6 +385,20 @@ def picohttpd(): f = os.popen("curl http://127.0.0.1:12913") self.assertEqual("httpd_test_successful", f.read()) + def test_http_elf_linux_armeb(self): + def picohttpd(): + ql = Qiling(["../examples/rootfs/armeb_linux/bin/picohttpd"], "../examples/rootfs/armeb_linux", multithread=True, verbose=QL_VERBOSE.DEBUG) + ql.run() + + + picohttpd_thread = threading.Thread(target=picohttpd, daemon=True) + picohttpd_thread.start() + + time.sleep(1) + + f = os.popen("curl http://127.0.0.1:12913") + self.assertEqual("httpd_test_successful", f.read()) + if __name__ == "__main__": unittest.main() From 95aaf2bf47fc881d039a7e057120db795c2adb81 Mon Sep 17 00:00:00 2001 From: Siger Yang Date: Sun, 17 Apr 2022 13:24:08 +0800 Subject: [PATCH 228/406] Allow timespec64 in 32bit architectures --- qiling/os/posix/syscall/time.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/qiling/os/posix/syscall/time.py b/qiling/os/posix/syscall/time.py index e7819ff29..ad0004f04 100644 --- a/qiling/os/posix/syscall/time.py +++ b/qiling/os/posix/syscall/time.py @@ -12,11 +12,22 @@ def ql_syscall_time(ql: Qiling): return int(time.time()) -def __sleep_common(ql: Qiling, req: int, rem: int) -> int: - n = ql.arch.pointersize +def __sleep_common(ql: Qiling, req: int, rem: int, force_timespec64: bool = False) -> int: + tv_sec_size = 8 if force_timespec64 else ql.arch.pointersize + tv_nsec_size = ql.arch.pointersize - tv_sec = ql.unpack(ql.mem.read(req, n)) - tv_sec += ql.unpack(ql.mem.read(req + n, n)) / 1000000000 + # struct timespec { + # long tv_sec; + # long tv_nsec; + # }; + # struct timespec64 { + # time64_t tv_sec; + # long tv_nsec; + # }; + + tv_sec = ql.unpack64(ql.mem.read(req, tv_sec_size)) if force_timespec64 else ql.unpack(ql.mem.read(req, tv_sec_size)) + + tv_sec += ql.unpack(ql.mem.read(req + tv_sec_size, tv_nsec_size)) / 1000000000 if ql.os.thread_management: def _sched_sleep(cur_thread): @@ -33,7 +44,7 @@ def _sched_sleep(cur_thread): return 0 def ql_syscall_clock_nanosleep_time64(ql: Qiling, clk_id: int, flags: int, req: int, rem: int): - return __sleep_common(ql, req, rem) + return __sleep_common(ql, req, rem, True) def ql_syscall_nanosleep(ql: Qiling, req: int, rem: int): return __sleep_common(ql, req, rem) From b0a8152acbf7a6874a3afcdced8343e013a068b9 Mon Sep 17 00:00:00 2001 From: Siger Yang Date: Sun, 17 Apr 2022 14:04:09 +0800 Subject: [PATCH 229/406] Fix typehint problems --- qiling/arch/arm_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiling/arch/arm_utils.py b/qiling/arch/arm_utils.py index ce19d0faf..3f7ff87f5 100644 --- a/qiling/arch/arm_utils.py +++ b/qiling/arch/arm_utils.py @@ -6,7 +6,7 @@ from qiling import Qiling from qiling.const import QL_ENDIAN -def init_linux_traps(ql: Qiling, address_map: dict[str, int]) -> None: +def init_linux_traps(ql: Qiling, address_map) -> None: # If the compiler for the target does not provides some primitives for some # reasons (e.g. target limitations), the kernel is responsible to assist # with these operations. From b6761eb2586214325cf5ba69f8da237eebb93390 Mon Sep 17 00:00:00 2001 From: Siger Yang Date: Sun, 17 Apr 2022 14:33:44 +0800 Subject: [PATCH 230/406] Supplement test for MIPS big endian --- tests/test_elf.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/test_elf.py b/tests/test_elf.py index 23864456f..141c2acc5 100644 --- a/tests/test_elf.py +++ b/tests/test_elf.py @@ -496,6 +496,39 @@ def random_generator(size=6, chars=string.ascii_uppercase + string.digits): del ql + def test_mips32eb_fake_urandom(self): + class Fake_urandom(QlFsMappedObject): + + def read(self, size): + return b"\x01" + + def fstat(self): + return -1 + + def close(self): + return 0 + + ql = Qiling(["../examples/rootfs/mips32_linux/bin/mips32_fetch_urandom"], "../examples/rootfs/mips32_linux") + ql.add_fs_mapper("/dev/urandom", Fake_urandom()) + + ql.exit_code = 0 + ql.exit_group_code = 0 + + def check_exit_group_code(ql, exit_code, *args, **kw): + ql.exit_group_code = exit_code + + def check_exit_code(ql, exit_code, *args, **kw): + ql.exit_code = exit_code + + ql.os.set_syscall("exit_group", check_exit_group_code, QL_INTERCEPT.ENTER) + ql.os.set_syscall("exit", check_exit_code, QL_INTERCEPT.ENTER) + + ql.run() + self.assertEqual(0, ql.exit_code) + self.assertEqual(0, ql.exit_group_code) + del ql + + def test_elf_onEnter_mips32el(self): def my_puts_onenter(ql: Qiling): params = ql.os.resolve_fcall_params(ELFTest.PARAMS_PUTS) From afd2d14700ff82bea5cf59e10d16cec59de520f3 Mon Sep 17 00:00:00 2001 From: Siger Yang Date: Mon, 18 Apr 2022 17:09:21 +0800 Subject: [PATCH 231/406] Update unicorn dependency to 2.0.0-rc7 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 15069f3b6..18519bd08 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ requirements = [ "capstone>=4.0.1", - "unicorn>=2.0.0-rc6", + "unicorn>=2.0.0-rc7", "pefile>=2021.9.3", "python-registry>=1.3.1", "keystone-engine>=0.9.2", From d49e72aa338fd06a40f4757507a9b6d7863e31e9 Mon Sep 17 00:00:00 2001 From: Siger Yang Date: Mon, 18 Apr 2022 17:44:35 +0800 Subject: [PATCH 232/406] Update init_linux_traps in QNX --- qiling/os/qnx/qnx.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/qiling/os/qnx/qnx.py b/qiling/os/qnx/qnx.py index 82a0483a7..403533be6 100644 --- a/qiling/os/qnx/qnx.py +++ b/qiling/os/qnx/qnx.py @@ -69,7 +69,11 @@ def load(self): self.ql.arch.enable_vfp() self.ql.hook_intno(self.hook_syscall, 2) #self.thread_class = thread.QlLinuxARMThread - arm_utils.init_get_tls(self.ql, self.ql.arch.arm_get_tls_addr) + arm_utils.init_linux_traps(self.ql, { + 'memory_barrier': 0xffff0fa0, + 'cmpxchg': 0xffff0fc0, + 'get_tls': 0xffff0fe0 + }) def hook_syscall(self, intno= None, int = None): From a12358ded2c9c43d8732a168db6492e2cbf91b2c Mon Sep 17 00:00:00 2001 From: "kj.xwings.l" Date: Tue, 19 Apr 2022 13:33:09 +0800 Subject: [PATCH 233/406] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 604829acc..cac712a9b 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,9 @@

+Collection of works, presentations, blogpost, etc: +https://github.com/qilingframework/qiling/issues/134 + Qiling is an advanced binary emulation framework, with the following features: - Emulate multi-platforms: Windows, MacOS, Linux, BSD, UEFI, DOS, MBR, Ethereum Virtual Machine From 6a2fec5c317cb19ab57001319a66020d5495878a Mon Sep 17 00:00:00 2001 From: "kj.xwings.l" Date: Tue, 19 Apr 2022 13:33:44 +0800 Subject: [PATCH 234/406] Update README.md --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index cac712a9b..435c86962 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,7 @@

-Collection of works, presentations, blogpost, etc: -https://github.com/qilingframework/qiling/issues/134 +[Collection of works, presentations, blogpost, etc](https://github.com/qilingframework/qiling/issues/134) Qiling is an advanced binary emulation framework, with the following features: From 409d400143aaace5400daf2487f10850e5a243fd Mon Sep 17 00:00:00 2001 From: "kj.xwings.l" Date: Tue, 19 Apr 2022 13:34:27 +0800 Subject: [PATCH 235/406] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 435c86962..cd6483a8d 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@

-[Collection of works, presentations, blogpost, etc](https://github.com/qilingframework/qiling/issues/134) +[Usecase, Blog and related work](https://github.com/qilingframework/qiling/issues/134) Qiling is an advanced binary emulation framework, with the following features: From 20e0ca6cf7b264ebcf4bc3a059cc48f48c25446c Mon Sep 17 00:00:00 2001 From: "kj.xwings.l" Date: Tue, 19 Apr 2022 13:34:47 +0800 Subject: [PATCH 236/406] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cd6483a8d..1f8c4543a 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@

-[Usecase, Blog and related work](https://github.com/qilingframework/qiling/issues/134) +[Qiling's usecase, Blog and related work](https://github.com/qilingframework/qiling/issues/134) Qiling is an advanced binary emulation framework, with the following features: From efdf385a2cea73ecbd52ffc7033c2b2b03b0efdc Mon Sep 17 00:00:00 2001 From: "kj.xwings.l" Date: Tue, 19 Apr 2022 13:39:48 +0800 Subject: [PATCH 237/406] Update CREDITS.md --- CREDITS.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/CREDITS.md b/CREDITS.md index 09fc255b1..394a13f25 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -3,27 +3,27 @@ #### Founder -- LAU kaijern (xwings) +- LAU kaijern (xwings) #### Advisor -- NGUYEN Anh Quynh +- NGUYEN Anh Quynh #### Core Developers Crew -- Earl MARCUS (klks84) klks84@gmail.com -- WU chenxu (kabeor) -- KONG ziqiao (lazymio) -- YU zheng (dataisland) -- Eli Cohen Nehemia (elicn) +- Earl MARCUS (klks84) +- WU chenxu (kabeor) +- KONG ziqiao (lazymio) +- YU zheng (dataisland) +- Eli Cohen Nehemia (elicn) #### CI, Website,Documentations, Logo & Swags -- FOO Kevin (chfl4gs) -- SU muchen (Mirai Suu) +- FOO Kevin (chfl4gs) +- SU muchen (Mirai Suu) #### Key Contributors (in no particular order) @@ -56,10 +56,10 @@ #### Legacy Core Developers -- DING tianze (D1iv3) -- SUN bowen (w1tcher) -- CHEN huitao (null) -- YU tong (sp1ke) +- DING tianze (D1iv3) +- SUN bowen (w1tcher) +- CHEN huitao (null) +- YU tong (sp1ke) #### Demigod team (https://groundx.io/demigod) From 0202fb424d36af3d9ff5d508969350e86fc6cdeb Mon Sep 17 00:00:00 2001 From: "kj.xwings.l" Date: Tue, 19 Apr 2022 13:41:43 +0800 Subject: [PATCH 238/406] Update CREDITS.md --- CREDITS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CREDITS.md b/CREDITS.md index 394a13f25..af8740779 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -23,7 +23,7 @@ #### CI, Website,Documentations, Logo & Swags - FOO Kevin (chfl4gs) -- SU muchen (Mirai Suu) +- SU muchen (miraisuu) #### Key Contributors (in no particular order) From e1cd89dbcce4166eb5f65924b0f3bebaa187ede4 Mon Sep 17 00:00:00 2001 From: "kj.xwings.l" Date: Tue, 19 Apr 2022 13:54:45 +0800 Subject: [PATCH 239/406] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1f8c4543a..48af44c75 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@

-[Qiling's usecase, Blog and related work](https://github.com/qilingframework/qiling/issues/134) +[Qiling's usecase, blog and related work](https://github.com/qilingframework/qiling/issues/134) Qiling is an advanced binary emulation framework, with the following features: From 1c5ef83c68f08b8de4cd9df515656cb1a692ece0 Mon Sep 17 00:00:00 2001 From: Siger Yang Date: Tue, 19 Apr 2022 15:22:57 +0800 Subject: [PATCH 240/406] Add gdb test for MIPS / ARM big endian --- tests/test_debugger.py | 87 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/tests/test_debugger.py b/tests/test_debugger.py index f584e16ed..9003fe1cb 100644 --- a/tests/test_debugger.py +++ b/tests/test_debugger.py @@ -75,6 +75,93 @@ def gdb_test_client(): ql.run() del ql + def test_gdbdebug_mips32(self): + ql = Qiling(["../examples/rootfs/mips32_linux/bin/mips32_hello"], "../examples/rootfs/mips32_linux", verbose=QL_VERBOSE.DEBUG) + ql.debugger = True + + # some random command test just to make sure we covered most of the command + def gdb_test_client(): + # yield to allow ql to launch its gdbserver + time.sleep(1.337 * 2) + + with SimpleGdbClient('127.0.0.1', 9999) as client: + client.send('qSupported:multiprocess+;swbreak+;hwbreak+;qRelocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+;xmlRegisters=i386') + client.send('vMustReplyEmpty') + client.send('QStartNoAckMode') + client.send('Hgp0.0') + client.send('qXfer:auxv:read::0, 1000') + client.send('?') + client.send('qXfer:threads:read::0,fff') + client.send(f'qAttached:{ql.os.pid}') + client.send('qC') + client.send('g') + client.send('m47ccd10,4') + client.send('qXfer:threads:read::0,1000') + client.send('m56555620,4') + client.send('m5655561c,4') + client.send('m56555620,4') + client.send('m5655561c,4') + client.send('m56555620,4') + client.send('qTStatus') + client.send('qTfP') + client.send('m56555600,40') + client.send('m56555620,4') + client.send('Z0,47ccd10,4') + client.send('QPassSignals:e;10;14;17;1a;1b;1c;21;24;25;2c;4c;97;') + client.send('vCont?') + client.send('vCont;c:pa410.-1') + client.send('c') + client.send('k') + + # yield to make sure ql gdbserver has enough time to receive our last command + time.sleep(1.337) + + threading.Thread(target=gdb_test_client, daemon=True).start() + + ql.run() + del ql + + def test_gdbdebug_armeb(self): + ql = Qiling(["../examples/rootfs/armeb_linux/bin/armeb_hello"], "../examples/rootfs/armeb_linux", verbose=QL_VERBOSE.DEBUG) + ql.debugger = True + + # some random command test just to make sure we covered most of the command + def gdb_test_client(): + # yield to allow ql to launch its gdbserver + time.sleep(1.337 * 2) + + with SimpleGdbClient('127.0.0.1', 9999) as client: + client.send('qSupported:multiprocess+;swbreak+;hwbreak+;qRelocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+;xmlRegisters=i386') + client.send('vMustReplyEmpty') + client.send('QStartNoAckMode') + client.send('Hgp0.0') + client.send('qXfer:auxv:read::0, 1000') + client.send('?') + client.send('qXfer:threads:read::0,fff') + client.send(f'qAttached:{ql.os.pid}') + client.send('qC') + client.send('g') + client.send('m47ccd10,4') + client.send('qXfer:threads:read::0,1000') + client.send('z0,47ca5fc,4') + client.send('m0,4') + client.send('mfffffffc,4') + client.send('m0,4') + client.send('mfffffffc,4') + client.send('m0,4') + client.send('p1d') + client.send('qTStatus') + client.send('c') + client.send('k') + + # yield to make sure ql gdbserver has enough time to receive our last command + time.sleep(1.337) + + threading.Thread(target=gdb_test_client, daemon=True).start() + + ql.run() + del ql + def test_gdbdebug_shellcode_server(self): X8664_LIN = bytes.fromhex('31c048bbd19d9691d08c97ff48f7db53545f995257545eb03b0f05') From 6160f1b3e9e26fa2f248420a3ce193127f6671c7 Mon Sep 17 00:00:00 2001 From: Siger Yang Date: Tue, 19 Apr 2022 15:24:10 +0800 Subject: [PATCH 241/406] Remove examples (duplicated in tests) --- examples/hello_armeb_linux_customapi.py | 20 -------------------- examples/multithreading_armeb_linux.py | 17 ----------------- 2 files changed, 37 deletions(-) delete mode 100644 examples/hello_armeb_linux_customapi.py delete mode 100644 examples/multithreading_armeb_linux.py diff --git a/examples/hello_armeb_linux_customapi.py b/examples/hello_armeb_linux_customapi.py deleted file mode 100644 index 455955513..000000000 --- a/examples/hello_armeb_linux_customapi.py +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env python3 -# -# Cross Platform and Multi Architecture Advanced Binary Emulation Framework -# - -import sys -sys.path.append("..") - -from qiling import Qiling -from qiling.os.const import STRING -from qiling.const import QL_VERBOSE - -def my_puts(ql: Qiling): - params = ql.os.resolve_fcall_params({'s': STRING}) - - print(f'puts("{params["s"]}")') - -if __name__ == "__main__": - ql = Qiling(["rootfs/armeb_linux/bin/armeb_hello"], "rootfs/armeb_linux", verbose=QL_VERBOSE.DEBUG) - ql.run() diff --git a/examples/multithreading_armeb_linux.py b/examples/multithreading_armeb_linux.py deleted file mode 100644 index d775be4f1..000000000 --- a/examples/multithreading_armeb_linux.py +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env python3 -# -# Cross Platform and Multi Architecture Advanced Binary Emulation Framework -# - -import sys -sys.path.append("..") - -from qiling import Qiling -from qiling.const import QL_VERBOSE - -def my_sandbox(path, rootfs): - ql = Qiling(path, rootfs, verbose=QL_VERBOSE.DEBUG, multithread=True) - ql.run() - -if __name__ == "__main__": - my_sandbox(["rootfs/armeb_linux/bin/armeb_multithreading"], "rootfs/armeb_linux") From 6c49cded0491730cf4144a77ac6536aa622c35d5 Mon Sep 17 00:00:00 2001 From: Siger Yang Date: Tue, 19 Apr 2022 15:27:21 +0800 Subject: [PATCH 242/406] Update credits --- CREDITS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CREDITS.md b/CREDITS.md index 09fc255b1..396a7076c 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -52,6 +52,7 @@ - bambu - madprogrammer - danielmoos +- sigeryang #### Legacy Core Developers From 9cab7c3cb8b4b16ffd763c7046281ae7b5e44ef4 Mon Sep 17 00:00:00 2001 From: anonymous Date: Thu, 21 Apr 2022 16:25:51 +0800 Subject: [PATCH 243/406] add macho load command 'LC_LOAD_WEAK_DYLIB' support --- qiling/loader/macho_parser/const.py | 1 + qiling/loader/macho_parser/loadcommand.py | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/qiling/loader/macho_parser/const.py b/qiling/loader/macho_parser/const.py index edbd3dedb..0726f374d 100644 --- a/qiling/loader/macho_parser/const.py +++ b/qiling/loader/macho_parser/const.py @@ -45,6 +45,7 @@ LC_LOAD_DYLINKER = 0x0000000E LC_MAIN = 0x80000028 LC_LOAD_DYLIB = 0x0000000C +LC_LOAD_WEAK_DYLIB = 0x80000018 LC_ENCRYPTION_INFO_64 = 0x0000002C LC_BUILD_VERSION = 0x00000032 LC_DYLD_EXPORTS_TRIE = 0x80000033 diff --git a/qiling/loader/macho_parser/loadcommand.py b/qiling/loader/macho_parser/loadcommand.py index e13832bdd..1eb18d362 100644 --- a/qiling/loader/macho_parser/loadcommand.py +++ b/qiling/loader/macho_parser/loadcommand.py @@ -39,6 +39,7 @@ def get_complete(self): LC_LOAD_DYLINKER : LoadDylinker, LC_MAIN : LoadMain, LC_LOAD_DYLIB : LoadDyLib, + LC_LOAD_WEAK_DYLIB : LoadWeakDyLib, LC_ENCRYPTION_INFO_64 : LoadEncryptionInfo64, LC_DYLD_EXPORTS_TRIE : LoadDyldExportTrie, LC_DYLD_CHAINED_FIXUPS : LoadDyldChainedFixups, @@ -255,6 +256,11 @@ def get_complete(self): pass +class LoadWeakDyLib(LoadDyLib): + def __init__(self, data): + super().__init__(data) + + class LoadUnixThread(LoadCommand): def __init__(self, data): From a78db289e304d37f27df4e89c7c2a493bf0f3852 Mon Sep 17 00:00:00 2001 From: chfl4gs Date: Sat, 23 Apr 2022 14:24:35 +0800 Subject: [PATCH 244/406] Compile pefile from source --- Dockerfile | 2 +- setup.py | 15 ++++----------- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/Dockerfile b/Dockerfile index 2a1360b46..99a0a9bf6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,7 +23,7 @@ WORKDIR /qiling RUN apt-get update \ && apt-get install -y --no-install-recommends unzip apt-utils \ && rm -rf /var/lib/apt/lists/* \ - && pip3 install wheels/*.whl \ + && pip3 install --no-deps wheels/*.whl \ && rm -rf wheels ENV HOME /qiling diff --git a/setup.py b/setup.py index 18519bd08..d19877a57 100644 --- a/setup.py +++ b/setup.py @@ -15,12 +15,13 @@ requirements = [ "capstone>=4.0.1", "unicorn>=2.0.0-rc7", - "pefile>=2021.9.3", + "pefile @ https://github.com/erocarrera/pefile/archive/refs/heads/master.zip", "python-registry>=1.3.1", "keystone-engine>=0.9.2", "pyelftools>=0.28", "gevent>=20.9.0", "multiprocess>=0.70.12.2", + "windows-curses>=2.1.0;platform_system=='Windows'", "pyyaml>=6.0" ] @@ -43,22 +44,14 @@ "cmd2" ], "fuzz" : [ - + "unicornafl>=2.0.0;platform_system=='Windows'", + "fuzzercorn>=0.0.1;platform_system=='Linux'" ] } with open("README.md", "r", encoding="utf-8") as ld: long_description = ld.read() -if "win32" in sys.platform: - requirements += ["windows-curses>=2.1.0"] - -if "win32" not in sys.platform: - extras["fuzz"] += ["unicornafl>=2.0.0"] - -if "linux" in sys.platform: - extras["fuzz"] += ["fuzzercorn>=0.0.1"] - setup( name='qiling', version=VERSION, From 6d5689235142863d1bfb95f1c0aa7a9a47636ac2 Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 22 Mar 2022 12:00:35 +0200 Subject: [PATCH 245/406] Introduce get_image_by_name to loader --- qiling/loader/loader.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/qiling/loader/loader.py b/qiling/loader/loader.py index 472881cbf..ce8d40e2b 100644 --- a/qiling/loader/loader.py +++ b/qiling/loader/loader.py @@ -3,6 +3,7 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # +import os from typing import Any, Mapping, MutableSequence, NamedTuple, Optional from qiling import Qiling @@ -25,6 +26,14 @@ def find_containing_image(self, address: int) -> Optional[Image]: return next((image for image in self.images if image.base <= address < image.end), None) + def get_image_by_name(self, name: str) -> Optional[Image]: + """Retrieve an image by its basename. + + Returns: image whose basename was, or `None` if not found + """ + + return next((image for image in self.images if os.path.basename(image.path) == name), None) + def save(self) -> Mapping[str, Any]: saved_state = { 'images': [tuple(img) for img in self.images] From 4adbd09badc535af3e1cae43279b7629542e3bf7 Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 22 Mar 2022 12:26:03 +0200 Subject: [PATCH 246/406] Remove dlls dict in favor of get_image_by_name --- examples/sality.py | 6 +++++- qiling/extensions/report/report.py | 2 +- qiling/loader/pe.py | 13 ++++--------- qiling/os/windows/dlls/kernel32/libloaderapi.py | 16 ++++++++++------ qiling/os/windows/dlls/ntdll.py | 2 +- qiling/os/windows/dlls/ntoskrnl.py | 13 +++++++++---- tests/test_pe_sys.py | 6 +++++- 7 files changed, 35 insertions(+), 23 deletions(-) diff --git a/examples/sality.py b/examples/sality.py index 6cc548129..b07c4273d 100644 --- a/examples/sality.py +++ b/examples/sality.py @@ -151,8 +151,12 @@ 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.DEBUG) - init_unseen_symbols(ql.amsint32_driver, ql.amsint32_driver.loader.dlls["ntoskrnl.exe"]+0xb7695, b"NtTerminateProcess", 0, "ntoskrnl.exe") + ntoskrnl = ql.amsint32_driver.loader.get_image_by_name("ntoskrnl.exe") + assert ntoskrnl, 'ntoskernl.exe was not loaded' + + init_unseen_symbols(ql.amsint32_driver, ntoskrnl.base+0xb7695, b"NtTerminateProcess", 0, "ntoskrnl.exe") #ql.amsint32_driver.debugger= ":9999" try: ql.amsint32_driver.load() diff --git a/qiling/extensions/report/report.py b/qiling/extensions/report/report.py index 59343165d..7c9a076a1 100644 --- a/qiling/extensions/report/report.py +++ b/qiling/extensions/report/report.py @@ -31,7 +31,7 @@ def __init__(self, ql): class WindowsReport(Report): def __init__(self, ql): super().__init__(ql) - self.dlls = ql.loader.dlls + self.teb_address = ql.loader.TEB.base self.peb_address = ql.loader.PEB.base self.ldr_address = ql.loader.LDR.base diff --git a/qiling/loader/pe.py b/qiling/loader/pe.py index 2a27fd630..41e2110e2 100644 --- a/qiling/loader/pe.py +++ b/qiling/loader/pe.py @@ -82,8 +82,10 @@ def load_dll(self, name: bytes, driver: bool = False) -> int: return 0 # If the dll is already loaded - if dll_name in self.dlls: - return self.dlls[dll_name] + image = self.get_image_by_name(dll_name) + + if image is not None: + return image.base self.ql.log.info(f'Loading {path} ...') @@ -464,14 +466,7 @@ def run(self): WINOSARCH = "OS64" self.structure_last_addr = GS_SEGMENT_ADDR - self.stack_address = int(self.ql.os.profile.get(WINOSARCH, "stack_address"), 16) - self.stack_size = int(self.ql.os.profile.get(WINOSARCH, "stack_size"), 16) - self.image_address = int(self.ql.os.profile.get(WINOSARCH, "image_address"), 16) - self.dll_address = int(self.ql.os.profile.get(WINOSARCH, "dll_address"), 16) - self.entry_point = int(self.ql.os.profile.get(WINOSARCH, "entry_point"), 16) - self.ql.os.heap_base_address = int(self.ql.os.profile.get(WINOSARCH, "heap_address"), 16) self.ql.os.heap_base_size = int(self.ql.os.profile.get(WINOSARCH, "heap_size"), 16) - self.dlls = {} self.import_symbols = {} self.export_symbols = {} self.import_address_table = {} diff --git a/qiling/os/windows/dlls/kernel32/libloaderapi.py b/qiling/os/windows/dlls/kernel32/libloaderapi.py index 2d229ac85..812a72351 100644 --- a/qiling/os/windows/dlls/kernel32/libloaderapi.py +++ b/qiling/os/windows/dlls/kernel32/libloaderapi.py @@ -23,10 +23,12 @@ def _GetModuleHandle(ql: Qiling, address: int, params): if not is_file_library(lpModuleName): lpModuleName += ".dll" - if lpModuleName in ql.loader.dlls: - ret = ql.loader.dlls[lpModuleName] + image = ql.loader.get_image_by_name(lpModuleName) + + if image: + ret = image.base else: - ql.log.debug("Library %s not imported" % lpModuleName) + ql.log.debug(f'Library "{lpModuleName}" not imported') ret = 0 return ret @@ -161,12 +163,14 @@ def hook_GetProcAddress(ql: Qiling, address: int, params): return 0 # Check if dll is loaded - try: - dll_name = [key for key, value in ql.loader.dlls.items() if value == params['hModule']][0] - except IndexError as ie: + image = next((image for image in ql.loader.images if image.base == params['hModule']), None) + + if image is None: ql.log.info('Failed to import function "%s" with handle 0x%X' % (lpProcName, params['hModule'])) return 0 + dll_name = os.path.basename(image.path) + # Handle case where module is self if dll_name == os.path.basename(ql.loader.path): for addr, export in ql.loader.export_symbols.items(): diff --git a/qiling/os/windows/dlls/ntdll.py b/qiling/os/windows/dlls/ntdll.py index b077d4b9b..d6743985c 100644 --- a/qiling/os/windows/dlls/ntdll.py +++ b/qiling/os/windows/dlls/ntdll.py @@ -356,7 +356,7 @@ def hook_LdrGetProcedureAddress(ql: Qiling, address: int, params): FunctionAddress = params['FunctionAddress'] # Check if dll is loaded - dll_name = next((key for key, value in ql.loader.dlls.items() if value == ModuleHandle), None) + dll_name = next((os.path.basename(path) for base, _, path in ql.loader.images if base == ModuleHandle), None) if dll_name is None: ql.log.debug(f'Could not find specified handle {ModuleHandle} in loaded DLL') diff --git a/qiling/os/windows/dlls/ntoskrnl.py b/qiling/os/windows/dlls/ntoskrnl.py index 59ef94606..11d669a99 100644 --- a/qiling/os/windows/dlls/ntoskrnl.py +++ b/qiling/os/windows/dlls/ntoskrnl.py @@ -476,9 +476,11 @@ def hook_MmGetSystemRoutineAddress(ql: Qiling, address: int, params): index = hook_only_routine_address.index(SystemRoutineName) # found! for dll_name in ('ntoskrnl.exe', 'ntkrnlpa.exe', 'hal.dll'): - if dll_name in ql.loader.dlls: + image = ql.loader.get_image_by_name(dll_name) + + if image: # create fake address - new_function_address = ql.loader.dlls[dll_name] + index + 1 + new_function_address = image.base + index + 1 # update import address table ql.loader.import_symbols[new_function_address] = { 'name': SystemRoutineName, @@ -779,8 +781,11 @@ def _NtQuerySystemInformation(ql: Qiling, address: int, params): module.Section = 0 module.MappedBase = 0 - if ql.loader.is_driver == True: - module.ImageBase = ql.loader.dlls.get("ntoskrnl.exe") + 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.ImageBase = image.base module.ImageSize = 0xab000 module.Flags = 0x8804000 diff --git a/tests/test_pe_sys.py b/tests/test_pe_sys.py index c608f2053..2d2da22af 100644 --- a/tests/test_pe_sys.py +++ b/tests/test_pe_sys.py @@ -154,8 +154,12 @@ 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.DEBUG) - init_unseen_symbols(ql.amsint32_driver, ql.amsint32_driver.loader.dlls["ntoskrnl.exe"]+0xb7695, b"NtTerminateProcess", 0, "ntoskrnl.exe") + ntoskrnl = ql.amsint32_driver.loader.get_image_by_name("ntoskrnl.exe") + self.assertIsNotNone(ntoskrnl) + + init_unseen_symbols(ql.amsint32_driver, ntoskrnl.base+0xb7695, b"NtTerminateProcess", 0, "ntoskrnl.exe") print("load amsint32_driver") try: From edfe834527242310666b69d8e17fa7e3377ac129 Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 22 Mar 2022 12:33:11 +0200 Subject: [PATCH 247/406] Massive revamp of Windows OS and PE Loader --- qiling/loader/pe.py | 1047 ++++++++------ qiling/os/windows/const.py | 1 + qiling/os/windows/dlls/ntdll.py | 8 +- qiling/os/windows/dlls/ntoskrnl.py | 44 +- qiling/os/windows/structs.py | 2113 ++++++++++------------------ qiling/os/windows/utils.py | 171 +-- qiling/os/windows/windows.py | 31 +- qiling/profiles/windows.ql | 12 +- 8 files changed, 1514 insertions(+), 1913 deletions(-) diff --git a/qiling/loader/pe.py b/qiling/loader/pe.py index 41e2110e2..563ad04af 100644 --- a/qiling/loader/pe.py +++ b/qiling/loader/pe.py @@ -3,54 +3,60 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -import os, pefile, pickle, secrets, traceback +import os, pefile, pickle, secrets, ntpath from typing import Any, MutableMapping, Optional, Mapping, Sequence +from unicorn import UcError +from unicorn.x86_const import UC_X86_REG_CR4, UC_X86_REG_CR8 + from qiling import Qiling -from qiling.arch.x86_const import * -from qiling.const import * +from qiling.arch.x86_const import FS_SEGMENT_ADDR, GS_SEGMENT_ADDR +from qiling.const import QL_ARCH +from qiling.exception import QlErrorArch from qiling.os.const import POINTER -from qiling.os.memory import QlMemoryHeap +from qiling.os.windows.api import HINSTANCE, DWORD, LPVOID from qiling.os.windows.fncc import CDECL -from qiling.os.windows.utils import * +from qiling.os.windows.utils import path_leaf, is_file_library from qiling.os.windows.structs import * from .loader import QlLoader, Image class QlPeCacheEntry: - def __init__(self, ba: int, data: bytearray, cmdlines: Sequence, import_symbols: Mapping, import_table: Mapping): - self. ba = ba + def __init__(self, ba: int, data: bytearray, cmdlines: Sequence, import_symbols: MutableMapping, import_table: MutableMapping): + self.ba = ba self.data = data self.cmdlines = cmdlines self.import_symbols = import_symbols self.import_table = import_table -# A default simple cache implementation + class QlPeCache: - def create_filename(self, path: str) -> str: + @staticmethod + def cache_filename(path: str) -> str: return f'{path}.cache2' def restore(self, path: str) -> Optional[QlPeCacheEntry]: - fcache = self.create_filename(path) + fcache = QlPeCache.cache_filename(path) - # pickle file cannot be outdated + # check whether cache file exists and it is not older than the cached file itself if os.path.exists(fcache) and os.stat(fcache).st_mtime > os.stat(path).st_mtime: with open(fcache, "rb") as fcache_file: return QlPeCacheEntry(*pickle.load(fcache_file)) return None - def save(self, path: str, entry: QlPeCacheEntry): - fcache = self.create_filename(path) + def save(self, path: str, entry: QlPeCacheEntry) -> None: + fcache = QlPeCache.cache_filename(path) data = (entry.ba, entry.data, entry.cmdlines, entry.import_symbols, entry.import_table) # cache this dll file with open(fcache, "wb") as fcache_file: pickle.dump(data, fcache_file) -class Process(): + +class Process: # let linter recognize mixin members dlls: MutableMapping[str, int] - import_address_table: MutableMapping[str, Any] + import_address_table: MutableMapping[str, Mapping] import_symbols: MutableMapping[int, Any] export_symbols: MutableMapping[int, Any] libcache: Optional[QlPeCache] @@ -58,27 +64,21 @@ class Process(): def __init__(self, ql: Qiling): self.ql = ql - def align(self, size: int, unit: int) -> int: - return (size // unit + (1 if size % unit else 0)) * unit - def load_dll(self, name: bytes, driver: bool = False) -> int: - dll_name = name.decode() - - self.ql.dlls = os.path.join("Windows", "System32") + dll_name = name.decode().lower() - if dll_name.upper().startswith('C:\\'): + if dll_name.startswith('c:\\'): path = self.ql.os.path.transform_to_real_path(dll_name) dll_name = path_leaf(dll_name) else: - dll_name = dll_name.lower() - if not is_file_library(dll_name): - dll_name = dll_name + ".dll" + dll_name = f'{dll_name}.dll' - path = os.path.join(self.ql.rootfs, self.ql.dlls, dll_name) + path = os.path.join(self.ql.rootfs, 'Windows', 'System32', dll_name) - if not os.path.exists(path): - self.ql.log.error("Cannot find dll in %s" % path) + if dll_name.startswith('api-ms-win-'): + # Usually we should not reach this point and instead imports from such DLLs should be redirected earlier + self.ql.log.warning(f'Refusing to load virtual DLL {dll_name}') return 0 # If the dll is already loaded @@ -87,7 +87,14 @@ def load_dll(self, name: bytes, driver: bool = False) -> int: if image is not None: return image.base - self.ql.log.info(f'Loading {path} ...') + if not os.path.exists(path): + self.ql.log.error(f'Could not find DLL file: {path}') + return 0 + + self.ql.log.info(f'Loading {dll_name} ...') + + import_symbols = {} + import_table = {} cached = None loaded = False @@ -111,7 +118,7 @@ def load_dll(self, name: bytes, driver: bool = False) -> int: for entry in cached.cmdlines: self.set_cmdline(entry['name'], entry['address'], data) - self.ql.log.info(f'Loaded {path} from cache') + self.ql.log.info(f'Loaded {dll_name} from cache') loaded = True # either file was not cached, or could not be loaded to the same location in memory @@ -121,43 +128,52 @@ def load_dll(self, name: bytes, driver: bool = False) -> int: warnings = dll.get_warnings() if warnings: - self.ql.log.warning(f'Warnings while loading {path}:') + self.ql.log.debug(f'Warnings while loading {dll_name}:') for warning in warnings: - self.ql.log.warning(f' - {warning}') + self.ql.log.debug(f' - {warning}') data = bytearray(dll.get_memory_mapped_image()) image_base = dll.OPTIONAL_HEADER.ImageBase or self.dll_last_address - image_size = self.ql.mem.align_up(len(data)) + image_size = self.ql.mem.align_up(dll.OPTIONAL_HEADER.SizeOfImage) + relocate = False self.ql.log.debug(f'DLL preferred base address: {image_base:#x}') if (image_base + image_size) > self.ql.mem.max_mem_addr: image_base = self.dll_last_address self.ql.log.debug(f'DLL preferred base address exceeds memory upper bound, loading to: {image_base:#x}') + relocate = True if not self.ql.mem.is_available(image_base, image_size): image_base = self.ql.mem.find_free_space(image_size, minaddr=image_base, align=0x10000) self.ql.log.debug(f'DLL preferred base address is taken, loading to: {image_base:#x}') + relocate = True + + if relocate: + with ShowProgress(0.1337): + dll.relocate_image(image_base) + data = bytearray(dll.get_memory_mapped_image()) + + assert image_size >= len(data) cmdlines = [] - import_symbols = {} - import_table = {} - - dll_symbols = getattr(getattr(dll, 'DIRECTORY_ENTRY_EXPORT', None), 'symbols', []) - for entry in dll_symbols: - import_symbols[image_base + entry.address] = { - "name": entry.name, - "ordinal": entry.ordinal, - "dll": dll_name.split('.')[0] + + for sym in dll.DIRECTORY_ENTRY_EXPORT.symbols: + ea = image_base + sym.address + + import_symbols[ea] = { + 'name' : sym.name, + 'ordinal' : sym.ordinal, + 'dll' : dll_name.split('.')[0] } - if entry.name: - import_table[entry.name] = image_base + entry.address + if sym.name: + import_table[sym.name] = ea - import_table[entry.ordinal] = image_base + entry.address - cmdline_entry = self.set_cmdline(entry.name, entry.address, data) + import_table[sym.ordinal] = ea + cmdline_entry = self.set_cmdline(sym.name, sym.address, data) if cmdline_entry: cmdlines.append(cmdline_entry) @@ -165,18 +181,11 @@ def load_dll(self, name: bytes, driver: bool = False) -> int: if self.libcache: cached = QlPeCacheEntry(image_base, data, cmdlines, import_symbols, import_table) self.libcache.save(path, cached) - self.ql.log.info("Cached %s" % path) + self.ql.log.info(f'Cached {dll_name}') # Add dll to IAT - try: - self.import_address_table[dll_name] = import_table - except Exception as ex: - self.ql.log.exception(f'Unable to add {dll_name} to IAT') - - try: - self.import_symbols.update(import_symbols) - except Exception as ex: - self.ql.log.exception(f'Unable to add {dll_name} import symbols') + self.import_address_table[dll_name] = import_table + self.import_symbols.update(import_symbols) dll_base = image_base dll_len = image_size @@ -185,493 +194,675 @@ def load_dll(self, name: bytes, driver: bool = False) -> int: self.ql.mem.map(dll_base, dll_len, info=dll_name) self.ql.mem.write(dll_base, bytes(data)) - self.dlls[dll_name] = dll_base - if dll_base == self.dll_last_address: - self.dll_last_address += dll_len + self.dll_last_address = self.ql.mem.align_up(self.dll_last_address + dll_len, 0x10000) + + # add DLL to coverage images + self.images.append(Image(dll_base, dll_base + dll_len, os.path.realpath(path))) # if this is NOT a driver, add dll to ldr data if not driver: self.add_ldr_data_table_entry(dll_name) - # add DLL to coverage images - self.images.append(Image(dll_base, dll_base + dll_len, path)) - - self.ql.log.info(f'Done with loading {path}') + if not cached or not loaded: + # parse directory entry import + self.ql.log.debug(f'Init imports for {dll_name}') + self.init_imports(dll, driver) + + # calling DllMain is essential for dlls to initialize properly. however + # DllMain of system libraries may fail due to incomplete or inaccurate + # mock implementation. due to unicorn limitations, recovering from such + # errors may be possible only if the function was not invoked from within + # a hook. + # + # in case of a dll loaded from a hooked API call, failures would not be + # recoverable and we have to give up its DllMain. + if not self.ql.os.PE_RUN: + self.call_dll_entrypoint(dll, dll_base, dll_len, dll_name) + + self.ql.log.info(f'Done loading {dll_name}') return dll_base + def call_dll_entrypoint(self, dll: pefile.PE, dll_base: int, dll_len: int, dll_name: str): + entry_address = dll.OPTIONAL_HEADER.AddressOfEntryPoint + + if dll.get_section_by_rva(entry_address) is None: + return + + if dll_name in ('kernelbase.dll', 'kernel32.dll'): + self.ql.log.debug(f'Ignoring {dll_name} entry point') + return + + # DllMain functions often call many APIs that may crash the program if they + # are not implemented correctly (if at all). here we blacklist the problematic + # DLLs whose DllMain functions are known to be crashing. + # + # the blacklist may be revisited from time to time to see if any of the file + # can be safely unlisted. + blacklist = { + 32 : ('gdi32.dll',), + 64 : ('gdi32.dll',) + }[self.ql.arch.bits] + + if dll_name in blacklist: + self.ql.log.debug(f'Ignoring {dll_name} entry point (blacklisted)') + return + + entry_point = dll_base + entry_address + exit_point = dll_base + dll_len - 16 + + args = ( + (HINSTANCE, dll_base), # hinstDLL = base address of DLL + (DWORD, 1), # fdwReason = DLL_PROCESS_ATTACH + (LPVOID, 0) # lpReserved = 0 + ) + + self.ql.log.info(f'Calling {dll_name} DllMain at {entry_point:#x}') + + regs_state = self.ql.arch.regs.save() - def _alloc_cmdline(self, wide): - addr = self.ql.os.heap.alloc(len(self.cmdline) * (2 if wide else 1)) - packed_addr = self.ql.pack(addr) - return addr, packed_addr - - def set_cmdline(self, name, address, memory): - cmdline_entry = None - if name == b"_acmdln": - addr, packed_addr = self._alloc_cmdline(wide=False) - cmdline_entry = {"name": name, "address": address} - memory[address:address + self.ql.arch.pointersize] = packed_addr - self.ql.mem.write(addr, self.cmdline) - elif name == b"_wcmdln": - addr, packed_addr = self._alloc_cmdline(wide=True) - cmdline_entry = {"name": name, "address": address} - memory[address:address + self.ql.arch.pointersize] = packed_addr - encoded = self.cmdline.decode('ascii').encode('UTF-16LE') - self.ql.mem.write(addr, encoded) - - return cmdline_entry - - def init_tib(self): - if self.ql.arch.type == QL_ARCH.X86: - teb_addr = self.structure_last_addr + self.ql.os.fcall = self.ql.os.fcall_select(CDECL) + self.ql.os.fcall.call_native(entry_point, args, exit_point) + + # Execute the call to the entry point + try: + self.ql.emu_start(entry_point, exit_point) + except UcError: + self.ql.log.error(f'Error encountered while running {dll_name} DllMain, bailing') + + self.ql.arch.regs.restore(regs_state) else: - gs = self.structure_last_addr - self.structure_last_addr += 0x30 - teb_addr = self.structure_last_addr + self.ql.os.fcall.cc.unwind(len(args)) + + self.ql.log.info(f'Returned from {dll_name} DllMain') + + def set_cmdline(self, name: bytes, address: int, memory: bytearray): + cmdln = { + b'_acmdln' : 1, + b'_wcmdln' : 2 + } + + clen = cmdln.get(name, None) + + if clen is None: + return None + + addr = self.ql.os.heap.alloc(len(self.cmdline) * clen) + memory[address:address + self.ql.arch.pointersize] = self.ql.pack(addr) + data = self.cmdline + + if clen == 2: + data = data.decode('ascii').encode('UTF-16LE') + + self.ql.mem.write(addr, data) - self.ql.log.info("TEB addr is 0x%x" %teb_addr) + return {"name": name, "address": address} - teb_size = len(TEB(self.ql).bytes()) - teb_data = TEB( - self.ql, - base=teb_addr, - peb_address=teb_addr + teb_size, - stack_base=self.stack_address + self.stack_size, - stack_limit=self.stack_size, - Self=teb_addr) + def init_teb(self): + teb_obj = make_teb(self.ql.arch.bits) - self.ql.mem.write(teb_addr, teb_data.bytes()) + teb_addr = self.structure_last_addr + peb_addr = self.ql.mem.align_up(teb_addr + ctypes.sizeof(teb_obj), 0x10) - self.structure_last_addr += teb_size - if self.ql.arch.type == QL_ARCH.X8664: - # TEB - self.ql.mem.write_ptr(gs + 0x30, teb_addr) - # PEB - self.ql.mem.write_ptr(gs + 0x60, teb_addr + teb_size) + 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.TEB = self.ql.TEB = teb_data + 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_addr = self.structure_last_addr + peb_obj = make_peb(self.ql.arch.bits) - self.ql.log.info("PEB addr is 0x%x" % peb_addr) + peb_addr = self.structure_last_addr + ldr_addr = self.ql.mem.align_up(peb_addr + ctypes.sizeof(peb_obj), 0x10) # we must set an heap, will try to retrieve this value. Is ok to be all \x00 - process_heap = self.ql.os.heap.alloc(0x100) - process_parameters = self.ql.os.heap.alloc(0x100) - peb_data = PEB( - self.ql, - base=peb_addr, - process_heap=process_heap, - process_parameters=process_parameters, - number_processors=self.ql.os.profile.getint("HARDWARE", "number_processors")) - peb_data.LdrAddress = peb_addr + peb_data.size - peb_data.write(peb_addr) - self.structure_last_addr += peb_data.size - self.PEB = self.ql.PEB = peb_data + 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_addr = self.structure_last_addr - ldr_size = len(LdrData(self.ql).bytes()) - ldr_data = LdrData( - self.ql, - base=ldr_addr, - in_load_order_module_list={ - 'Flink': ldr_addr + 2 * self.ql.arch.pointersize, - 'Blink': ldr_addr + 2 * self.ql.arch.pointersize - }, - in_memory_order_module_list={ - 'Flink': ldr_addr + 4 * self.ql.arch.pointersize, - 'Blink': ldr_addr + 4 * self.ql.arch.pointersize - }, - in_initialization_order_module_list={ - 'Flink': ldr_addr + 6 * self.ql.arch.pointersize, - 'Blink': ldr_addr + 6 * self.ql.arch.pointersize - } - ) - self.ql.mem.write(ldr_addr, ldr_data.bytes()) - self.structure_last_addr += ldr_size - self.LDR = self.ql.LDR = ldr_data - - def add_ldr_data_table_entry(self, dll_name): - dll_base = self.dlls[dll_name] - path = "C:\\Windows\\System32\\" + dll_name - ldr_table_entry_size = len(LdrDataTableEntry(self.ql).bytes()) - base = self.ql.os.heap.alloc(ldr_table_entry_size) - ldr_table_entry = LdrDataTableEntry(self.ql, - base=base, - in_load_order_links={'Flink': 0, 'Blink': 0}, - in_memory_order_links={'Flink': 0, 'Blink': 0}, - in_initialization_order_links={'Flink': 0, 'Blink': 0}, - dll_base=dll_base, - entry_point=0, - full_dll_name=path, - base_dll_name=dll_name) + nobj_addr = self.ql.mem.align_up(ldr_addr + ctypes.sizeof(ldr_obj), 0x10) + + ldr_obj.InLoadOrderModuleList.Flink = ldr_addr + ldr_cls.InLoadOrderModuleList.offset + ldr_obj.InLoadOrderModuleList.Blink = ldr_addr + ldr_cls.InLoadOrderModuleList.offset + + ldr_obj.InMemoryOrderModuleList.Flink = ldr_addr + ldr_cls.InMemoryOrderModuleList.offset + ldr_obj.InMemoryOrderModuleList.Blink = ldr_addr + ldr_cls.InMemoryOrderModuleList.offset + + ldr_obj.InInitializationOrderModuleList.Flink = ldr_addr + ldr_cls.InInitializationOrderModuleList.offset + ldr_obj.InInitializationOrderModuleList.Blink = ldr_addr + ldr_cls.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_size = ctypes.sizeof(entry_obj) + entry_addr = self.ql.os.heap.alloc(entry_size) + + def populate_unistr(obj, s: str) -> None: + encoded = s.encode('utf-16le') + ucslen = len(encoded) + ucsbuf = self.ql.os.heap.alloc(ucslen + 2) + + self.ql.mem.write(ucsbuf, encoded + b'\x00\x00') + + obj.Length = ucslen + obj.MaximumLength = ucslen + 2 + obj.Buffer = ucsbuf + + image = self.get_image_by_name(dll_name) + 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.windir, 'System32', dll_name)) + populate_unistr(entry_obj.BaseDllName, dll_name) # Flink - if len(self.ldr_list) == 0: - flink = self.LDR - ldr_table_entry.InLoadOrderLinks['Flink'] = flink.InLoadOrderModuleList['Flink'] - ldr_table_entry.InMemoryOrderLinks['Flink'] = flink.InMemoryOrderModuleList['Flink'] - ldr_table_entry.InInitializationOrderLinks['Flink'] = flink.InInitializationOrderModuleList['Flink'] + if self.ldr_list: + flink_base, flink = self.ldr_list[-1] - flink.InLoadOrderModuleList['Flink'] = ldr_table_entry.base - flink.InMemoryOrderModuleList['Flink'] = ldr_table_entry.base + 2 * self.ql.arch.pointersize - flink.InInitializationOrderModuleList['Flink'] = ldr_table_entry.base + 4 * self.ql.arch.pointersize + 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_cls.InLoadOrderLinks.offset + flink.InMemoryOrderLinks.Flink = entry_addr + entry_cls.InMemoryOrderLinks.offset + flink.InInitializationOrderLinks.Flink = entry_addr + entry_cls.InInitializationOrderLinks.offset else: - flink = self.ldr_list[-1] - ldr_table_entry.InLoadOrderLinks['Flink'] = flink.InLoadOrderLinks['Flink'] - ldr_table_entry.InMemoryOrderLinks['Flink'] = flink.InMemoryOrderLinks['Flink'] - ldr_table_entry.InInitializationOrderLinks['Flink'] = flink.InInitializationOrderLinks['Flink'] + flink_base, flink = (self.PEB.LdrAddress, self.LDR) + + entry_obj.InLoadOrderLinks.Flink = flink.InLoadOrderModuleList.Flink + entry_obj.InMemoryOrderLinks.Flink = flink.InMemoryOrderModuleList.Flink + entry_obj.InInitializationOrderLinks.Flink = flink.InInitializationOrderModuleList.Flink - flink.InLoadOrderLinks['Flink'] = ldr_table_entry.base - flink.InMemoryOrderLinks['Flink'] = ldr_table_entry.base + 2 * self.ql.arch.pointersize - flink.InInitializationOrderLinks['Flink'] = ldr_table_entry.base + 4 * self.ql.arch.pointersize + 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 # Blink + blink_base = self.PEB.LdrAddress blink = self.LDR - ldr_table_entry.InLoadOrderLinks['Blink'] = blink.InLoadOrderModuleList['Blink'] - ldr_table_entry.InMemoryOrderLinks['Blink'] = blink.InMemoryOrderModuleList['Blink'] - ldr_table_entry.InInitializationOrderLinks['Blink'] = blink.InInitializationOrderModuleList['Blink'] - blink.InLoadOrderModuleList['Blink'] = ldr_table_entry.base - blink.InMemoryOrderModuleList['Blink'] = ldr_table_entry.base + 2 * self.ql.arch.pointersize - blink.InInitializationOrderModuleList['Blink'] = ldr_table_entry.base + 4 * self.ql.arch.pointersize + entry_obj.InLoadOrderLinks.Blink = blink.InLoadOrderModuleList.Blink + entry_obj.InMemoryOrderLinks.Blink = blink.InMemoryOrderModuleList.Blink + entry_obj.InInitializationOrderLinks.Blink = blink.InInitializationOrderModuleList.Blink - self.ql.mem.write(flink.base, flink.bytes()) - self.ql.mem.write(blink.base, blink.bytes()) - self.ql.mem.write(ldr_table_entry.base, ldr_table_entry.bytes()) + 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 - self.ldr_list.append(ldr_table_entry) + 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)) - def init_exports(self): - if self.ql.code: + self.ldr_list.append((entry_addr, entry_obj)) + + @staticmethod + def directory_exists(pe: pefile.PE, entry: str) -> bool: + ent = pefile.DIRECTORY_ENTRY[entry] + + return pe.OPTIONAL_HEADER.DATA_DIRECTORY[ent].VirtualAddress != 0 + + def init_imports(self, pe: pefile.PE, is_driver: bool): + if not Process.directory_exists(pe, 'IMAGE_DIRECTORY_ENTRY_IMPORT'): return - if self.pe.OPTIONAL_HEADER.DATA_DIRECTORY[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_EXPORT']].VirtualAddress != 0: - # Do a full load if IMAGE_DIRECTORY_ENTRY_EXPORT is present so we can load the exports - self.pe.full_load() - else: + + pe.full_load() + + for entry in pe.DIRECTORY_ENTRY_IMPORT: + dll_name = entry.dll.decode().lower() + self.ql.log.debug(f'Requesting imports from {dll_name}') + + orig_dll_name = dll_name + redirected = False + + if dll_name.startswith('api-ms-win-'): + # DLLs starting with this prefix contain no actual code. Instead, the windows loader loads the actual + # code from one of the main windows dlls. + # see https://github.com/lucasg/Dependencies for correct replacement dlls + # + # The correct way to find the dll that replaces all symbols from this dll involves using the hashmap + # inside of apisetschema.dll (see https://lucasg.github.io/2017/10/15/Api-set-resolution/ ). + # + # Currently, we use a simpler, more hacky approach, that seems to work in a lot of cases: we just scan + # through some key dlls and hope that we find the requested symbols there. some symbols may appear on + # more than one dll though; in that case we proceed to the next symbol to see which key dll includes it. + # + # Note: You might be tempted to load the actual dll (dll_name), because they also contain a reference to + # the replacement dll. However, chances are, that these dlls do not exist in the rootfs and maybe they + # don't even exist on windows. Therefore this approach is a bad idea. + + # DLLs that seem to contain most of the requested symbols + key_dlls = ( + 'ntdll.dll', + 'kernelbase.dll', + 'ucrtbase.dll' + ) + + imports = iter(entry.imports) + failed = False + fallback = None + + while not redirected and not failed: + # find all possible redirection options by scanning key dlls for the current imported symbol + imp = next(imports, None) + redirection_options = [fallback] if imp is None else [filename for filename in key_dlls if filename in self.import_address_table and imp.name in self.import_address_table[filename]] + + # no redirection options: failed to redirect dll + if not redirection_options: + failed = True + + # exactly one redirection options: use it + elif len(redirection_options) == 1: + key_dll = redirection_options[0] + redirected = True + + # more than one redirection options: remember one of them and proceed to next symbol + else: + fallback = redirection_options[-1] + + if not redirected: + self.ql.log.warning(f'Failed to resolve {dll_name}') + continue + + self.ql.log.debug(f'Redirecting {dll_name} to {key_dll}') + dll_name = key_dll + + unbound_imports = [imp for imp in entry.imports if not imp.bound] + + if unbound_imports: + # Only load dll if encountered unbound symbol + if not redirected: + dll_base = self.load_dll(entry.dll, is_driver) + + if not dll_base: + continue + + for imp in unbound_imports: + iat = self.import_address_table[dll_name] + + if imp.name: + if imp.name not in iat: + self.ql.log.debug(f'Error in loading function {imp.name.decode()} ({orig_dll_name}){", probably misdirected" if redirected else ""}') + continue + + addr = iat[imp.name] + else: + addr = iat[imp.ordinal] + + self.ql.mem.write_ptr(imp.address, addr) + + def init_exports(self, pe: pefile.PE): + if not Process.directory_exists(pe, 'IMAGE_DIRECTORY_ENTRY_EXPORT'): return - try: - # parse directory entry export - dll_name = os.path.basename(self.path) - self.import_address_table[dll_name] = {} - for entry in self.pe.DIRECTORY_ENTRY_EXPORT.symbols: - self.export_symbols[self.pe_image_address + entry.address] = {'name': entry.name, 'ordinal': entry.ordinal} - self.import_address_table[dll_name][entry.name] = self.pe_image_address + entry.address - self.import_address_table[dll_name][entry.ordinal] = self.pe_image_address + entry.address - except: - self.ql.log.info('Failed to load exports for %s:\n%s' % (self.ql.argv, traceback.format_exc())) + # Do a full load if IMAGE_DIRECTORY_ENTRY_EXPORT is present so we can load the exports + pe.full_load() + + iat = {} + + # parse directory entry export + for entry in pe.DIRECTORY_ENTRY_EXPORT.symbols: + ea = self.pe_image_address + entry.address + + self.export_symbols[ea] = { + 'name' : entry.name, + 'ordinal' : entry.ordinal + } + + if entry.name: + iat[entry.name] = ea + + iat[entry.ordinal] = ea + + dll_name = os.path.basename(self.path) + self.import_address_table[dll_name] = iat def init_driver_object(self): - # PDRIVER_OBJECT DriverObject - driver_object_addr = self.structure_last_addr - self.ql.log.info("Driver object addr is 0x%x" %driver_object_addr) + drv_addr = self.structure_last_addr - if self.ql.arch.type == QL_ARCH.X86: - self.driver_object = DRIVER_OBJECT32(self.ql, driver_object_addr) - elif self.ql.arch.type == QL_ARCH.X8664: - self.driver_object = DRIVER_OBJECT64(self.ql, driver_object_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) - driver_object_size = ctypes.sizeof(self.driver_object) - self.ql.mem.write(driver_object_addr, bytes(self.driver_object)) - self.structure_last_addr += driver_object_size - self.driver_object_address = driver_object_addr + self.ql.log.info(f'DriverObject is at {drv_addr:#x}') + self.ql.mem.write(drv_addr, bytes(drv_obj)) + self.structure_last_addr = nobj_addr + self.driver_object_address = drv_addr + self.driver_object = drv_obj def init_registry_path(self): + regpath_addr = self.structure_last_addr + # PUNICODE_STRING RegistryPath - regitry_path_addr = self.structure_last_addr - self.ql.log.info("Registry path addr is 0x%x" %regitry_path_addr) + regpath_obj = make_unicode_string(self.ql.arch.bits, + Length=0, + MaximumLength=0, + Buffer=regpath_addr + ) - if self.ql.arch.type == QL_ARCH.X86: - regitry_path_data = UNICODE_STRING32(0, 0, regitry_path_addr) - elif self.ql.arch.type == QL_ARCH.X8664: - regitry_path_data = UNICODE_STRING64(0, 0, regitry_path_addr) + nobj_addr = self.ql.mem.align_up(regpath_addr + ctypes.sizeof(regpath_obj), 0x10) - regitry_path_size = ctypes.sizeof(regitry_path_data) - self.ql.mem.write(regitry_path_addr, bytes(regitry_path_data)) - self.structure_last_addr += regitry_path_size - self.regitry_path_address = regitry_path_addr + self.ql.log.info(f'RegistryPath is at {regpath_addr:#x}') + self.ql.mem.write(regpath_addr, bytes(regpath_obj)) + self.structure_last_addr = nobj_addr + self.regitry_path_address = regpath_addr def init_eprocess(self): - addr = self.structure_last_addr - self.ql.log.info("EPROCESS is is 0x%x" %addr) - + eproc_obj = make_eprocess(self.ql.arch.bits) - if self.ql.arch.type == QL_ARCH.X86: - self.eprocess_object = EPROCESS32(self.ql, addr) - elif self.ql.arch.type == QL_ARCH.X8664: - self.eprocess_object = EPROCESS64(self.ql, addr) + eproc_addr = self.structure_last_addr + nobj_addr = self.ql.mem.align_up(eproc_addr + ctypes.sizeof(eproc_obj), 0x10) - size = ctypes.sizeof(self.eprocess_object) - self.ql.mem.write(addr, bytes(self.driver_object)) - self.structure_last_addr += size - self.ql.eprocess_address = addr + self.ql.mem.write(eproc_addr, bytes(eproc_obj)) + self.structure_last_addr = nobj_addr + self.eprocess_address = eproc_addr def init_ki_user_shared_data(self): - ''' - https://www.geoffchappell.com/studies/windows/km/ntoskrnl/structs/kuser_shared_data/index.htm + addr = self.ql.os.profile.getint(f'OS{self.ql.arch.bits}', 'KI_USER_SHARED_DATA') - struct information: - https://doxygen.reactos.org/d8/dae/modules_2rostests_2winetests_2ntdll_2time_8c_source.html - ''' - if self.ql.arch.type == QL_ARCH.X86: - KI_USER_SHARED_DATA = 0xFFDF0000 - elif self.ql.arch.type == QL_ARCH.X8664: - KI_USER_SHARED_DATA = 0xFFFFF78000000000 + user_shared_data_obj = KUSER_SHARED_DATA() + user_shared_data_size = ctypes.sizeof(KUSER_SHARED_DATA) - self.ql.log.info("KI_USER_SHARED_DATA is 0x%x" %KI_USER_SHARED_DATA) + self.ql.mem.map(addr, self.ql.mem.align_up(user_shared_data_size)) + self.ql.mem.write(addr, bytes(user_shared_data_obj)) - shared_user_data = KUSER_SHARED_DATA() + def init_security_cookie(self, pe: pefile.PE, image_base: int): + if not Process.directory_exists(pe, 'IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG'): + return + + cookie_rva = pe.DIRECTORY_ENTRY_LOAD_CONFIG.struct.SecurityCookie - pe.OPTIONAL_HEADER.ImageBase - shared_user_data_len = self.align(ctypes.sizeof(KUSER_SHARED_DATA), 0x1000) - self.ql.mem.map(KI_USER_SHARED_DATA, shared_user_data_len) - self.ql.mem.write(KI_USER_SHARED_DATA, bytes(shared_user_data)) + # get a random cookie value but keep the two most significant bytes zeroes + # + # rol rcx, 10h ; rcx = cookie + # test cx, 0FFFFh + cookie = secrets.randbits(self.ql.arch.bits - 16) + self.ql.mem.write_ptr(cookie_rva + image_base, cookie) class QlLoaderPE(QlLoader, Process): def __init__(self, ql: Qiling, libcache: bool): super().__init__(ql) - self.ql = ql - self.path = self.ql.path - self.is_driver = False - self.libcache = QlPeCache() if libcache else None + self.ql = ql + self.path = self.ql.path + self.is_driver = False + self.libcache = QlPeCache() if libcache else None def run(self): - self.init_dlls = [b"ntdll.dll", b"kernel32.dll", b"user32.dll"] - self.sys_dlls = [b"ntdll.dll", b"kernel32.dll"] - self.pe_entry_point = 0 - self.sizeOfStackReserve = 0 - - if not self.ql.code: - self.pe = pefile.PE(self.path, fast_load=True) - self.is_driver = self.pe.is_driver() - if self.is_driver == True: + self.init_dlls = [ + b'ntdll.dll', + b'kernel32.dll', + b'user32.dll' + ] + + self.sys_dlls = [ + b'ntdll.dll', + b'kernel32.dll', + b'ucrtbase.dll' + ] + + if self.ql.code: + pe = None + else: + pe = pefile.PE(self.path, fast_load=True) + self.is_driver = pe.is_driver() + + if self.is_driver: self.init_dlls.append(b"ntoskrnl.exe") self.sys_dlls.append(b"ntoskrnl.exe") - - if self.ql.arch.type == QL_ARCH.X86: - WINOSARCH = "OS32" - self.structure_last_addr = FS_SEGMENT_ADDR - elif self.ql.arch.type == QL_ARCH.X8664: - WINOSARCH = "OS64" - self.structure_last_addr = GS_SEGMENT_ADDR + ossection = f'OS{self.ql.arch.bits}' + + self.stack_address = self.ql.os.profile.getint(ossection, 'stack_address') + self.stack_size = self.ql.os.profile.getint(ossection, 'stack_size') + self.image_address = self.ql.os.profile.getint(ossection, 'image_address') + self.dll_address = self.ql.os.profile.getint(ossection, 'dll_address') + self.entry_point = self.ql.os.profile.getint(ossection, 'entry_point') + + self.structure_last_addr = { + 32 : FS_SEGMENT_ADDR, + 64 : GS_SEGMENT_ADDR + }[self.ql.arch.bits] - self.ql.os.heap_base_size = int(self.ql.os.profile.get(WINOSARCH, "heap_size"), 16) self.import_symbols = {} self.export_symbols = {} self.import_address_table = {} self.ldr_list = [] self.pe_image_address = 0 - self.pe_image_address_size = 0 + self.pe_image_size = 0 self.dll_size = 0 self.dll_last_address = self.dll_address - # compatible with ql.__enable_bin_patch() + # compatible with ql.do_bin_patch() self.load_address = 0 - self.ql.os.heap = QlMemoryHeap(self.ql, self.ql.os.heap_base_address, self.ql.os.heap_base_address + self.ql.os.heap_base_size) - self.ql.os.setupComponents() - self.ql.os.entry_point = self.entry_point - cmdline = (str(self.ql.os.userprofile)) + "Desktop\\" + self.ql.targetname - self.filepath = bytes(cmdline + "\x00", "utf-8") - for arg in self.argv[1:]: - if ' ' in arg: - cmdline += f' "{arg}"' - else: - cmdline += f' {arg}' - cmdline += "\x00" - self.cmdline = bytes(cmdline, "utf-8") + # self.ql.os.setupComponents() - self.load() + cmdline = ntpath.join(self.ql.os.userprofile, 'Desktop', self.ql.targetname) + cmdargs = ' '.join(f'"{arg}"' if ' ' in arg else arg for arg in self.argv[1:]) - def init_thread_information_block(self): - super().init_tib() - super().init_peb() - super().init_ldr_data() - super().init_exports() + self.filepath = bytes(f'{cmdline}\x00', "utf-8") + self.cmdline = bytes(f'{cmdline} {cmdargs}\x00', "utf-8") - def load(self): + self.load(pe) + + def load(self, pe: Optional[pefile.PE]): # set stack pointer self.ql.log.info("Initiate stack address at 0x%x " % self.stack_address) self.ql.mem.map(self.stack_address, self.stack_size, info="[stack]") - if self.path and not self.ql.code: - # for simplicity, no image base relocation - self.pe_image_address = self.pe.OPTIONAL_HEADER.ImageBase - self.pe_image_address_size = self.ql.mem.align_up(self.pe.OPTIONAL_HEADER.SizeOfImage) + if pe is not None: + image_name = os.path.basename(self.path) + image_base = pe.OPTIONAL_HEADER.ImageBase + image_size = self.ql.mem.align_up(pe.OPTIONAL_HEADER.SizeOfImage) - if self.pe_image_address + self.pe_image_address_size > self.ql.os.heap_base_address: - # pe reloc - self.pe_image_address = self.image_address - self.pe.relocate_image(self.image_address) + # if default base address is taken, use the one specified in profile + if not self.ql.mem.is_available(image_base, image_size): + image_base = self.image_address + pe.relocate_image(image_base) - self.entry_point = self.pe_entry_point = self.pe_image_address + self.pe.OPTIONAL_HEADER.AddressOfEntryPoint - self.sizeOfStackReserve = self.pe.OPTIONAL_HEADER.SizeOfStackReserve - self.ql.log.info("Loading %s to 0x%x" % (self.path, self.pe_image_address)) - self.ql.log.info("PE entry point at 0x%x" % self.entry_point) - self.images.append(Image(self.pe_image_address, self.pe_image_address + self.pe.NT_HEADERS.OPTIONAL_HEADER.SizeOfImage, self.path)) + self.entry_point = image_base + pe.OPTIONAL_HEADER.AddressOfEntryPoint + self.pe_image_address = image_base + self.pe_image_size = image_size - # Stack should not init at the very bottom. Will cause errors with Dlls - sp = self.stack_address + self.stack_size - 0x1000 + self.ql.log.info(f'Loading {self.path} to {image_base:#x}') + self.ql.log.info(f'PE entry point at {self.entry_point:#x}') - if self.ql.arch.type == QL_ARCH.X86: - self.ql.arch.regs.esp = sp - self.ql.arch.regs.ebp = sp + self.ql.mem.map(image_base, image_size, info=f'[{image_name}]') + self.images.append(Image(image_base, image_base + pe.NT_HEADERS.OPTIONAL_HEADER.SizeOfImage, os.path.realpath(self.path))) - if self.pe.is_dll(): - self.ql.log.debug('Setting up DllMain args') - load_addr_bytes = self.pe_image_address.to_bytes(length=4, byteorder='little') + if self.is_driver: + self.init_driver_object() + self.init_registry_path() + self.init_eprocess() + self.init_ki_user_shared_data() - self.ql.log.debug('Writing 0x%08X (IMAGE_BASE) to [ESP+4](0x%08X)' % (self.pe_image_address, sp + 0x4)) - self.ql.mem.write(sp + 0x4, load_addr_bytes) + # set IRQ Level in CR8 to PASSIVE_LEVEL + self.ql.arch.regs.write(UC_X86_REG_CR8, 0) - self.ql.log.debug('Writing 0x01 (DLL_PROCESS_ATTACH) to [ESP+8](0x%08X)' % (sp + 0x8)) - self.ql.mem.write(sp + 0x8, int(1).to_bytes(length=4, byteorder='little')) + # setup CR4, enabling: DE, PSE, PAE, MCE, PGE, OSFXSR and OSXMMEXCPT. + # some drivers may check this at initialized + self.ql.arch.regs.write(UC_X86_REG_CR4, 0b0000011011111000) - elif self.ql.arch.type == QL_ARCH.X8664: - self.ql.arch.regs.rsp = sp - self.ql.arch.regs.rbp = sp + else: + # initialize thread information block + self.init_teb() + self.init_peb() + self.init_ldr_data() + self.init_exports(pe) - if self.pe.is_dll(): - self.ql.log.debug('Setting up DllMain args') + # add image to ldr table + self.add_ldr_data_table_entry(image_name) - self.ql.log.debug('Setting RCX (arg1) to %16X (IMAGE_BASE)' % (self.pe_image_address)) - self.ql.arch.regs.rcx = self.pe_image_address + pe.parse_data_directories() - self.ql.log.debug('Setting RDX (arg2) to 1 (DLL_PROCESS_ATTACH)') - self.ql.arch.regs.rdx = 1 - else: - raise QlErrorArch("Unknown ql.arch") - - # if this is NOT a driver, init tib/peb/ldr - if not self.is_driver: # userland program - self.init_thread_information_block() - else: # Windows kernel driver - super().init_driver_object() - super().init_registry_path() - super().init_eprocess() - super().init_ki_user_shared_data() - - # setup IRQ Level in CR8 to PASSIVE_LEVEL (0) - self.ql.arch.regs.write(UC_X86_REG_CR8, 0) + # done manipulating pe file; write its contents into memory + self.ql.mem.write(image_base, bytes(pe.get_memory_mapped_image())) - # setup CR4, some drivers may check this at initialized time - self.ql.arch.regs.write(UC_X86_REG_CR4, 0x6f8) - - self.ql.log.debug('Setting up DriverEntry args') - self.ql.stop_execution_pattern = 0xDEADC0DE - - if self.ql.arch.type == QL_ARCH.X86: # Win32 - if not self.ql.stop_options: - # We know that a driver will return, - # so if the user did not configure stop options, write a sentinel return value - self.ql.mem.write(sp, self.ql.stop_execution_pattern.to_bytes(length=4, byteorder='little')) - - self.ql.log.debug('Writing 0x%08X (PDRIVER_OBJECT) to [ESP+4](0x%08X)' % (self.ql.loader.driver_object_address, sp+0x4)) - self.ql.log.debug('Writing 0x%08X (RegistryPath) to [ESP+8](0x%08X)' % (self.ql.loader.regitry_path_address, sp+0x8)) - elif self.ql.arch.type == QL_ARCH.X8664: # Win64 - if not self.ql.stop_options: - # We know that a driver will return, - # so if the user did not configure stop options, write a sentinel return value - self.ql.mem.write(sp, self.ql.stop_execution_pattern.to_bytes(length=8, byteorder='little')) - - self.ql.log.debug('Setting RCX (arg1) to %16X (PDRIVER_OBJECT)' % (self.ql.loader.driver_object_address)) - self.ql.log.debug('Setting RDX (arg2) to %16X (PUNICODE_STRING)' % (self.ql.loader.regitry_path_address)) - - # setup args for DriverEntry() - self.ql.os.fcall = self.ql.os.fcall_select(CDECL) - self.ql.os.fcall.writeParams(((POINTER, self.ql.loader.driver_object_address), (POINTER, self.ql.loader.regitry_path_address))) - - # mmap PE file into memory - self.ql.mem.map(self.pe_image_address, self.ql.mem.align_up(self.pe_image_address_size), info="[PE]") - self.pe.parse_data_directories() - data = bytearray(self.pe.get_memory_mapped_image()) - self.ql.mem.write(self.pe_image_address, bytes(data)) - if self.is_driver: - # setup IMAGE_LOAD_CONFIG_DIRECTORY - if self.pe.OPTIONAL_HEADER.DATA_DIRECTORY[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG']].VirtualAddress != 0: - SecurityCookie_rva = self.pe.DIRECTORY_ENTRY_LOAD_CONFIG.struct.SecurityCookie - self.pe.OPTIONAL_HEADER.ImageBase - SecurityCookie_value = default_security_cookie_value = self.ql.mem.read(self.pe_image_address+SecurityCookie_rva, self.ql.arch.pointersize) - while SecurityCookie_value == default_security_cookie_value: - SecurityCookie_value = secrets.token_bytes(self.ql.arch.pointersize) - # rol rcx, 10h (rcx: cookie) - # test cx, 0FFFFh - SecurityCookie_value_array = bytearray(SecurityCookie_value) - # Sanity question: We are always little endian, right? - SecurityCookie_value_array[-2:] = b'\x00\x00' - SecurityCookie_value = bytes(SecurityCookie_value_array) - self.ql.mem.write(self.pe_image_address+SecurityCookie_rva, SecurityCookie_value) - - # Add main PE to ldr_data_table - mod_name = os.path.basename(self.path) - self.dlls[mod_name] = self.pe_image_address - # only userland code need LDR table - if not self.is_driver: - super().add_ldr_data_table_entry(mod_name) + # security cookie can be written only after image has been loaded to memory + self.init_security_cookie(pe, image_base) + + # Stack should not init at the very bottom. Will cause errors with Dlls + top_of_stack = self.stack_address + self.stack_size - 0x1000 + + if self.ql.arch.type == QL_ARCH.X86: + bp_reg = 'ebp' + sp_reg = 'esp' + elif self.ql.arch.type == QL_ARCH.X8664: + bp_reg = 'rbp' + sp_reg = 'rsp' + else: + raise QlErrorArch(f'unexpected arch type: {self.ql.arch.type}') + + # we are about to load some dlls and call their DllMain functions. + # the stack should be set first + self.ql.arch.regs.write(bp_reg, top_of_stack) + self.ql.arch.regs.write(sp_reg, top_of_stack) # load system dlls - sys_dlls = self.sys_dlls - for each in sys_dlls: + for each in self.sys_dlls: super().load_dll(each, self.is_driver) + # parse directory entry import - if self.pe.OPTIONAL_HEADER.DATA_DIRECTORY[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_IMPORT']].VirtualAddress != 0: - for entry in self.pe.DIRECTORY_ENTRY_IMPORT: - dll_name = str(entry.dll.lower(), 'utf-8', 'ignore') - super().load_dll(entry.dll, self.is_driver) - for imp in entry.imports: - # fix IAT - # ql.log.info(imp.name) - # ql.log.info(self.import_address_table[imp.name]) - if imp.name: - try: - addr = self.import_address_table[dll_name][imp.name] - except KeyError: - self.ql.log.debug("Error in loading function %s" % imp.name.decode()) - continue - else: - addr = self.import_address_table[dll_name][imp.ordinal] - - if self.ql.arch.type == QL_ARCH.X86: - address = self.ql.pack32(addr) - else: - address = self.ql.pack64(addr) - self.ql.mem.write(imp.address, address) - - self.ql.log.debug("Done with loading %s" % self.path) - self.ql.os.entry_point = self.entry_point - self.ql.os.pid = 101 - - elif self.ql.code: + self.ql.log.debug(f'Init imports for {self.path}') + super().init_imports(pe, self.is_driver) + + self.ql.log.debug(f'Done loading {self.path}') + + if pe.is_driver(): + args = ( + (POINTER, self.driver_object_address), + (POINTER, self.regitry_path_address) + ) + + self.ql.log.debug('Setting up call frame for DriverEntry:') + self.ql.log.debug(f' PDRIVER_OBJECT DriverObject : {args[0][1]:#010x}') + self.ql.log.debug(f' PUNICODE_STRING RegistryPath : {args[1][1]:#010x}') + + # We know that a driver will return, so if the user did not configure stop + # options, write a sentinel return value + ret = None if self.ql.stop_options else self.ql.stack_write(0, 0xdeadc0de) + + # set up call frame for DriverEntry + self.ql.os.fcall.call_native(self.entry_point, args, ret) + + elif pe.is_dll(): + args = ( + (POINTER, image_base), + (DWORD, 1), # DLL_PROCESS_ATTACH + (POINTER, 0) + ) + + self.ql.log.debug('Setting up call frame for DllMain:') + self.ql.log.debug(f' HINSTANCE hinstDLL : {args[0][1]:#010x}') + self.ql.log.debug(f' DWORD fdwReason : {args[1][1]:#010x}') + self.ql.log.debug(f' LPVOID lpReserved : {args[2][1]:#010x}') + + # set up call frame for DllMain + self.ql.os.fcall.call_native(self.entry_point, args, None) + + elif pe is None: self.filepath = b"" + + self.ql.mem.map(self.entry_point, self.ql.os.code_ram_size, info="[shellcode]") + + self.init_teb() + self.init_peb() + self.init_ldr_data() + + top_of_stack = self.stack_address + self.stack_size + if self.ql.arch.type == QL_ARCH.X86: - self.ql.arch.regs.esp = self.stack_address + 0x3000 - self.ql.arch.regs.ebp = self.ql.arch.regs.esp + bp_reg = 'ebp' + sp_reg = 'esp' elif self.ql.arch.type == QL_ARCH.X8664: - self.ql.arch.regs.rsp = self.stack_address + 0x3000 - self.ql.arch.regs.rbp = self.ql.arch.regs.rsp + bp_reg = 'rbp' + sp_reg = 'rsp' + else: + raise QlErrorArch(f'unexpected arch type: {self.ql.arch.type}') - # load shellcode in - self.ql.mem.map(self.entry_point, self.ql.os.code_ram_size, info="[shellcode_base]") - # rewrite entrypoint for windows shellcode - self.ql.os.entry_point = self.entry_point - self.ql.os.pid = 101 + self.ql.arch.regs.write(bp_reg, top_of_stack) + self.ql.arch.regs.write(sp_reg, top_of_stack) - self.ql.mem.write(self.entry_point, self.ql.code) - - self.init_thread_information_block() # load dlls for each in self.init_dlls: super().load_dll(each) + # load shellcode + self.ql.mem.write(self.entry_point, self.ql.code) + # move entry_point to ql.os self.ql.os.entry_point = self.entry_point self.init_sp = self.ql.arch.regs.arch_sp + +class ShowProgress: + """Display a progress animation while performing a time + consuming task. + + Example: + >>> with ShowProgress(0.1): + ... do_some_time_consuming_task() + """ + + def __init__(self, interval: float) -> None: + import sys + from threading import Thread, Event + + # animation frames; any sequence of chars or strings may be used, as long + # as they are of the same length. e.g. ['> ', '>> ', ' >> ', ' >>', ' >', ' '] + frames = r'/-\|' + stream = sys.stderr + + def show_animation(): + i = 0 + + while not self.stopped.wait(interval): + # TODO: find a proper way to use the logger for that + print(f'[{frames[i % len(frames)]}]', end='\r', flush=True, file=stream) + i += 1 + + def show_nothing(): + pass + + # avoid flooding log files with animation frames + action = show_animation if stream.isatty() else show_nothing + + self.stopped = Event() + self.thread = Thread(target=action) + + def __enter__(self): + self.thread.start() + + return self + + def __exit__(self, *args) -> None: + self.stopped.set() diff --git a/qiling/os/windows/const.py b/qiling/os/windows/const.py index 2453319af..13884a299 100644 --- a/qiling/os/windows/const.py +++ b/qiling/os/windows/const.py @@ -612,6 +612,7 @@ # https://docs.microsoft.com/en-us/windows/win32/procthread/zwqueryinformationprocess +# https://www.pinvoke.net/default.aspx/ntdll/PROCESSINFOCLASS.html ProcessBasicInformation = 0 ProcessDebugPort = 7 ProcessExecuteFlags = 0x22 diff --git a/qiling/os/windows/dlls/ntdll.py b/qiling/os/windows/dlls/ntdll.py index d6743985c..1d25e7ee0 100644 --- a/qiling/os/windows/dlls/ntdll.py +++ b/qiling/os/windows/dlls/ntdll.py @@ -3,6 +3,8 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # +import os + from qiling import Qiling from qiling.os.windows.api import * from qiling.os.windows.fncc import * @@ -54,7 +56,8 @@ def _QueryInformationProcess(ql: Qiling, address: int, params): elif flag == ProcessBasicInformation: pbi = structs.ProcessBasicInformation(ql, exitStatus=0, - pebBaseAddress=ql.os.heap_base_address, affinityMask=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") @@ -310,7 +313,8 @@ def _SetInformationProcess(ql: Qiling, address: int, params): pbi = structs.ProcessBasicInformation( ql, exitStatus=0, - pebBaseAddress=ql.os.heap_base_address, affinityMask=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") diff --git a/qiling/os/windows/dlls/ntoskrnl.py b/qiling/os/windows/dlls/ntoskrnl.py index 11d669a99..c54d274e8 100644 --- a/qiling/os/windows/dlls/ntoskrnl.py +++ b/qiling/os/windows/dlls/ntoskrnl.py @@ -162,14 +162,7 @@ def __IoCreateDevice(ql: Qiling, address: int, params): DeviceCharacteristics = params['DeviceCharacteristics'] DeviceObject = params['DeviceObject'] - objcls = { - QL_ARCH.X86 : DEVICE_OBJECT32, - QL_ARCH.X8664 : DEVICE_OBJECT64 - }[ql.arch.type] - - addr = ql.os.heap.alloc(ctypes.sizeof(objcls)) - - device_object = objcls() + 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 @@ -186,7 +179,9 @@ def __IoCreateDevice(ql: Qiling, address: int, params): device_object.Characteristics = DeviceCharacteristics - ql.mem.write(addr, bytes(device_object)[:]) + addr = ql.os.heap.alloc(ctypes.sizeof(device_object)) + + ql.mem.write(addr, bytes(device_object)) ql.mem.write_ptr(DeviceObject, addr) # update DriverObject.DeviceObject @@ -643,10 +638,7 @@ def hook_KeLeaveCriticalRegion(ql: Qiling, address: int, params): def hook_MmMapLockedPagesSpecifyCache(ql: Qiling, address: int, params): MemoryDescriptorList = params['MemoryDescriptorList'] - mdl_class: ctypes.Structure = { - QL_ARCH.X8664 : MDL64, - QL_ARCH.X86 : MDL32 - }[ql.arch.type] + mdl_class = make_mdl(ql.arch.bits).__class__ mdl_buffer = ql.mem.read(MemoryDescriptorList, ctypes.sizeof(mdl_class)) mdl = mdl_class.from_buffer(mdl_buffer) @@ -759,12 +751,11 @@ def _NtQuerySystemInformation(ql: Qiling, address: int, params): # if SystemInformationLength = 0, we return the total size in ReturnLength NumberOfModules = 1 - if ql.arch.bits == 64: - # only 1 module for ntoskrnl.exe - # FIXME: let users customize this? - size = 4 + ctypes.sizeof(RTL_PROCESS_MODULE_INFORMATION64) * NumberOfModules - else: - size = 4 + ctypes.sizeof(RTL_PROCESS_MODULE_INFORMATION32) * NumberOfModules + rpmi_class = make_rtl_process_module_info(ql.arch.bits).__class__ + + # only 1 module for ntoskrnl.exe + # FIXME: let users customize this? + size = 4 + ctypes.sizeof(rpmi_class) * NumberOfModules if params["ReturnLength"] != 0: ql.mem.write_ptr(params["ReturnLength"], size) @@ -773,11 +764,7 @@ def _NtQuerySystemInformation(ql: Qiling, address: int, params): return STATUS_INFO_LENGTH_MISMATCH else: # return all the loaded modules - if ql.arch.bits == 64: - module = RTL_PROCESS_MODULE_INFORMATION64() - else: - module = RTL_PROCESS_MODULE_INFORMATION32() - + module = make_rtl_process_module_info(ql.arch.bits) module.Section = 0 module.MappedBase = 0 @@ -879,7 +866,7 @@ def hook_IoAcquireCancelSpinLock(ql: Qiling, address: int, params): # PEPROCESS PsGetCurrentProcess(); @winsdkapi(cc=STDCALL, params={}) def hook_PsGetCurrentProcess(ql: Qiling, address: int, params): - return ql.eprocess_address + return ql.loader.eprocess_address # HANDLE PsGetCurrentProcessId(); @winsdkapi(cc=STDCALL, params={}) @@ -1078,12 +1065,9 @@ def hook_PsLookupProcessByProcessId(ql: Qiling, address: int, params): ProcessId = params["ProcessId"] Process = params["Process"] - if ql.arch.bits == 64: - obj = EPROCESS64 - else: - obj = EPROCESS32 + eprocess_obj = make_eprocess(ql.arch.bits) + addr = ql.os.heap.alloc(ctypes.sizeof(eprocess_obj)) - addr = ql.os.heap.alloc(ctypes.sizeof(obj)) ql.mem.write_ptr(Process, addr) ql.log.info(f'PID = {ProcessId:#x}, addrof(EPROCESS) == {addr:#x}') diff --git a/qiling/os/windows/structs.py b/qiling/os/windows/structs.py index 4b7705fa5..84412a571 100644 --- a/qiling/os/windows/structs.py +++ b/qiling/os/windows/structs.py @@ -3,486 +3,266 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -import ctypes, struct +import ctypes from enum import IntEnum -from unicorn.x86_const import * +from qiling import Qiling +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. + """ -from qiling.const import * -from qiling.os.windows.handle import * -from qiling.exception import * -from .wdk_const import * + class Struct(ctypes.LittleEndianStructure): + _pack_ = archbits // 8 + return Struct -class POINTER32(ctypes.Structure): - _fields_ = [('value', ctypes.c_uint32)] +def __select_native_type(archbits: int): + """Select a ctypes integer type with the underlying architecture + native size. + """ -class POINTER64(ctypes.Structure): - _fields_ = [('value', ctypes.c_uint64)] + __type = { + 32 : ctypes.c_uint32, + 64 : ctypes.c_uint64 + } + return __type[archbits] -class TEB: - def __init__(self, - ql, - base=0, - exception_list=0, - stack_base=0, - stack_limit=0, - sub_system_tib=0, - fiber_data=0, - arbitrary_user_pointer=0, - Self=0, - environment_pointer=0, - client_id_unique_process=0, - client_id_unique_thread=0, - rpc_handle=0, - tls_storage=0, - peb_address=0, - last_error_value=0, - last_status_value=0, - count_owned_locks=0, - hard_error_mode=0): - self.ql = ql - self.base = base - self.ExceptionList = exception_list - self.StackBase = stack_base - self.StackLimit = stack_limit - self.SubSystemTib = sub_system_tib - self.FiberData = fiber_data - self.ArbitraryUserPointer = arbitrary_user_pointer - self.Self = Self - self.EnvironmentPointer = environment_pointer - self.ClientIdUniqueProcess = client_id_unique_process - self.ClientIdUniqueThread = client_id_unique_thread - self.RpcHandle = rpc_handle - self.Tls_Storage = tls_storage - self.PEB_Address = peb_address - self.LastErrorValue = last_error_value - self.LastStatusValue = last_status_value - self.Count_Owned_Locks = count_owned_locks - self.HardErrorMode = hard_error_mode - - def bytes(self): - s = b'' - s += self.ql.pack(self.ExceptionList) # 0x00 - s += self.ql.pack(self.StackBase) # 0x04 - s += self.ql.pack(self.StackLimit) # 0x08 - s += self.ql.pack(self.SubSystemTib) # 0x0c - s += self.ql.pack(self.FiberData) # 0x10 - s += self.ql.pack(self.ArbitraryUserPointer) # 0x14 - s += self.ql.pack(self.Self) # 0x18 - s += self.ql.pack(self.EnvironmentPointer) # 0x1c - s += self.ql.pack(self.ClientIdUniqueProcess) # 0x20 - s += self.ql.pack(self.ClientIdUniqueThread) # 0x24 - s += self.ql.pack(self.RpcHandle) # 0x28 - s += self.ql.pack(self.Tls_Storage) # 0x2c - s += self.ql.pack(self.PEB_Address) # 0x30 - s += self.ql.pack(self.LastErrorValue) # 0x34 - s += self.ql.pack(self.LastStatusValue) # 0x38 - s += self.ql.pack(self.Count_Owned_Locks) # 0x3c - s += self.ql.pack(self.HardErrorMode) # 0x40 - return s - - -# https://www.geoffchappell.com/studies/windows/win32/ntdll/structs/peb/index.htm - - -class PEB: - def __init__(self, - ql, - base=0, - flag=0, - mutant=0, - image_base_address=0, - ldr_address=0, - process_parameters=0, - sub_system_data=0, - process_heap=0, - fast_peb_lock=0, - alt_thunk_s_list_ptr=0, - ifeo_key=0, - number_processors=0): - self.ql = ql - self.base = base - self.flag = flag - self.ImageBaseAddress = image_base_address - self.Mutant = mutant - self.LdrAddress = ldr_address - self.ProcessParameters = process_parameters - self.SubSystemData = sub_system_data - self.ProcessHeap = process_heap - self.FastPebLock = fast_peb_lock - self.AtlThunkSListPtr = alt_thunk_s_list_ptr - self.IFEOKey = ifeo_key - self.numberOfProcessors = number_processors - if self.ql.arch.type == 32: - self.size = 0x0468 - else: - self.size = 0x07B0 - def write(self, addr): - s = b'' - s += self.ql.pack(self.flag) # 0x0 / 0x0 - s += self.ql.pack(self.Mutant) # 0x4 / 0x8 - s += self.ql.pack(self.ImageBaseAddress) # 0x8 / 0x10 - s += self.ql.pack(self.LdrAddress) # 0xc / 0x18 - s += self.ql.pack(self.ProcessParameters) # 0x10 / 0x20 - s += self.ql.pack(self.SubSystemData) # 0x14 / 0x28 - s += self.ql.pack(self.ProcessHeap) # 0x18 / 0x30 - s += self.ql.pack(self.FastPebLock) # 0x1c / 0x38 - s += self.ql.pack(self.AtlThunkSListPtr) # 0x20 / 0x40 - s += self.ql.pack(self.IFEOKey) # 0x24 / 0x48 - self.ql.mem.write(addr, s) - # FIXME: understand how each attribute of the PEB works before adding it - self.ql.mem.write(addr + 0x64, self.ql.pack(self.numberOfProcessors)) - - -class LDR_DATA: - def __init__(self, - ql, - base=0, - Length=0, - Initialized=0, - SsHandle=0, - InLoadOrderModuleList={ - 'Flink': 0, - 'Blink': 0 - }, - InMemoryOrderModuleList={ - 'Flink': 0, - 'Blink': 0 - }, - InInitializationOrderModuleList={ - 'Flink': 0, - 'Blink': 0 - }, - EntryInProgress=0, - ShutdownInProgress=0, - ShutdownThreadId=0): - self.ql = ql - self.base = base - self.Length = Length - self.Initialized = Initialized - self.SsHandle = SsHandle - self.InLoadOrderModuleList = InLoadOrderModuleList - self.InMemoryOrderModuleList = InMemoryOrderModuleList - self.InInitializationOrderModuleList = InInitializationOrderModuleList - self.EntryInProgress = EntryInProgress - self.ShutdownInProgress = ShutdownInProgress - self.selfShutdownThreadId = ShutdownThreadId - - def bytes(self): - s = b'' - s += self.ql.pack32(self.Length) # 0x0 - s += self.ql.pack32(self.Initialized) # 0x4 - s += self.ql.pack(self.SsHandle) # 0x8 - s += self.ql.pack(self.InLoadOrderModuleList['Flink']) # 0x0c - s += self.ql.pack(self.InLoadOrderModuleList['Blink']) - s += self.ql.pack(self.InMemoryOrderModuleList['Flink']) # 0x14 - s += self.ql.pack(self.InMemoryOrderModuleList['Blink']) - s += self.ql.pack( - self.InInitializationOrderModuleList['Flink']) # 0x1C - s += self.ql.pack(self.InInitializationOrderModuleList['Blink']) - s += self.ql.pack(self.EntryInProgress) - s += self.ql.pack(self.ShutdownInProgress) - s += self.ql.pack(self.selfShutdownThreadId) - - return s - - -class LDR_DATA_TABLE_ENTRY: - def __init__(self, - ql, - base=0, - InLoadOrderLinks={ - 'Flink': 0, - 'Blink': 0 - }, - InMemoryOrderLinks={ - 'Flink': 0, - 'Blink': 0 - }, - InInitializationOrderLinks={ - 'Flink': 0, - 'Blink': 0 - }, - DllBase=0, - EntryPoint=0, - SizeOfImage=0, - FullDllName='', - BaseDllName='', - Flags=0, - LoadCount=0, - TlsIndex=0, - HashLinks=0, - SectionPointer=0, - CheckSum=0, - TimeDateStamp=0, - LoadedImports=0, - EntryPointActivationContext=0, - PatchInformation=0, - ForwarderLinks=0, - ServiceTagLinks=0, - StaticLinks=0, - ContextInformation=0, - OriginalBase=0, - LoadTime=0): - self.ql = ql - self.base = base - self.InLoadOrderLinks = InLoadOrderLinks - self.InMemoryOrderLinks = InMemoryOrderLinks - self.InInitializationOrderLinks = InInitializationOrderLinks - self.DllBase = DllBase - self.EntryPoint = EntryPoint - self.SizeOfImage = SizeOfImage - - FullDllName = FullDllName.encode("utf-16le") - self.FullDllName = {} - self.FullDllName['Length'] = len(FullDllName) - self.FullDllName['MaximumLength'] = len(FullDllName) + 2 - self.FullDllName['BufferPtr'] = ql.heap.alloc( - self.FullDllName['MaximumLength']) - ql.mem.write(self.FullDllName['BufferPtr'], FullDllName + b"\x00\x00") - - BaseDllName = BaseDllName.encode("utf-16le") - self.BaseDllName = {} - self.BaseDllName['Length'] = len(BaseDllName) - self.BaseDllName['MaximumLength'] = len(BaseDllName) + 2 - self.BaseDllName['BufferPtr'] = ql.heap.alloc( - self.BaseDllName['MaximumLength']) - ql.mem.write(self.BaseDllName['BufferPtr'], BaseDllName + b"\x00\x00") - - self.Flags = Flags - self.LoadCount = LoadCount - self.TlsIndex = TlsIndex - self.HashLinks = HashLinks - self.SectionPointer = SectionPointer - self.CheckSum = CheckSum - self.TimeDateStamp = TimeDateStamp - self.LoadedImports = LoadedImports - self.EntryPointActivationContext = EntryPointActivationContext - self.PatchInformation = PatchInformation - self.ForwarderLinks = ForwarderLinks - self.ServiceTagLinks = ServiceTagLinks - self.StaticLinks = StaticLinks - self.ContextInformation = ContextInformation - self.OriginalBase = OriginalBase - self.LoadTime = LoadTime - - def attrs(self): - return ", ".join("{}={}".format(k, getattr(self, k)) - for k in self.__dict__.keys()) - - def print(self): - return "[{}:{}]".format(self.__class__.__name__, self.attrs()) - - def bytes(self): - s = b'' - s += self.ql.pack(self.InLoadOrderLinks['Flink']) # 0x0 - s += self.ql.pack(self.InLoadOrderLinks['Blink']) - s += self.ql.pack(self.InMemoryOrderLinks['Flink']) # 0x8 - s += self.ql.pack(self.InMemoryOrderLinks['Blink']) - s += self.ql.pack(self.InInitializationOrderLinks['Flink']) # 0x10 - s += self.ql.pack(self.InInitializationOrderLinks['Blink']) - s += self.ql.pack(self.DllBase) # 0x18 - s += self.ql.pack(self.EntryPoint) # 0x1c - s += self.ql.pack(self.SizeOfImage) # 0x20 - s += self.ql.pack16(self.FullDllName['Length']) # 0x24 - s += self.ql.pack16(self.FullDllName['MaximumLength']) # 0x26 - - if self.ql.arch == QL_ARCH.X8664: - s += self.ql.pack32(0) - - s += self.ql.pack(self.FullDllName['BufferPtr']) # 0x28 - s += self.ql.pack16(self.BaseDllName['Length']) - s += self.ql.pack16(self.BaseDllName['MaximumLength']) - - if self.ql.arch == QL_ARCH.X8664: - s += self.ql.pack32(0) - - s += self.ql.pack(self.BaseDllName['BufferPtr']) - s += self.ql.pack(self.Flags) - s += self.ql.pack(self.LoadCount) - s += self.ql.pack(self.TlsIndex) - s += self.ql.pack(self.HashLinks) - s += self.ql.pack(self.SectionPointer) - s += self.ql.pack(self.CheckSum) - s += self.ql.pack(self.TimeDateStamp) - s += self.ql.pack(self.LoadedImports) - s += self.ql.pack(self.EntryPointActivationContext) - s += self.ql.pack(self.PatchInformation) - s += self.ql.pack(self.ForwarderLinks) - s += self.ql.pack(self.ServiceTagLinks) - s += self.ql.pack(self.StaticLinks) - s += self.ql.pack(self.ContextInformation) - s += self.ql.pack(self.OriginalBase) - s += self.ql.pack(self.LoadTime) - - return s - - -''' -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; -''' - - -class UNICODE_STRING64(ctypes.Structure): - _pack_ = 8 - _fields_ = (('Length', ctypes.c_uint16), ('MaximumLength', ctypes.c_int16), - ('Buffer', ctypes.c_uint64)) - - -class UNICODE_STRING32(ctypes.Structure): - _pack_ = 4 - _fields_ = (('Length', ctypes.c_uint16), ('MaximumLength', ctypes.c_int16), - ('Buffer', ctypes.c_uint32)) - - -''' -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; -''' - - -class DRIVER_OBJECT64(ctypes.Structure): - _pack_ = 8 - _fields_ = ( - ("_Type", ctypes.c_uint16), - ("_Size", ctypes.c_uint16), - ("_DeviceObject", POINTER64), - ("_Flags", ctypes.c_uint32), - ("_DriverStart", POINTER64), - ("_DriverSize", ctypes.c_uint32), - ("_DriverSection", POINTER64), - ("_DriverExtension", POINTER64), - ("_DriverName", UNICODE_STRING64), - ("_HardwareDatabase", POINTER64), - ("_FastIoDispatch", POINTER64), - ("_DriverInit", POINTER64), - ("_DriverStartIo", POINTER64), - ("_DriverUnload", POINTER64), - ("_MajorFunction", ctypes.c_uint64 * (IRP_MJ_MAXIMUM_FUNCTION + 1))) - - def __init__(self, ql, base): - self.ql = ql - self.base = base - - # get MajorFunction - @property - def MajorFunction(self): - data = self.ql.mem.read(self.base, ctypes.sizeof(self)) - obj = type(self).from_buffer(data) - return obj._MajorFunction - - @property - def DeviceObject(self): - # TODO: improve this code to avoid reading the whole object - data = self.ql.mem.read(self.base, ctypes.sizeof(self)) - obj = type(self).from_buffer(data) - return obj._DeviceObject.value - - @DeviceObject.setter - def DeviceObject(self, value): - # TODO: improve this code to avoid reading/writing the whole object - data = self.ql.mem.read(self.base, ctypes.sizeof(self)) - obj = type(self).from_buffer(data) - obj._DeviceObject.value = value - # update back to memory. - self.ql.mem.write(self.base, bytes(obj)) - - @property - def DriverUnload(self): - data = self.ql.mem.read(self.base, ctypes.sizeof(self)) - obj = type(self).from_buffer(data) - return obj._DriverUnload.value - - -class DRIVER_OBJECT32(ctypes.Structure): - _pack_ = 4 - _fields_ = ( - ("_Type", ctypes.c_uint16), - ("_Size", ctypes.c_uint16), - ("_DeviceObject", POINTER32), - ("_Flags", ctypes.c_uint32), - ("_DriverStart", POINTER32), - ("_DriverSize", ctypes.c_uint32), - ("_DriverSection", POINTER32), - ("_DriverExtension", POINTER32), - ("_DriverName", UNICODE_STRING32), - ("_HardwareDatabase", POINTER32), - ("_FastIoDispatch", POINTER32), - ("_DriverInit", POINTER32), - ("_DriverStartIo", POINTER32), - ("_DriverUnload", POINTER32), - ("_MajorFunction", ctypes.c_uint32 * (IRP_MJ_MAXIMUM_FUNCTION + 1))) - - def __init__(self, ql, base): - self.ql = ql - self.base = base - - # get MajorFunction - @property - def MajorFunction(self): - data = self.ql.mem.read(self.base, ctypes.sizeof(self)) - obj = type(self).from_buffer(data) - return obj._MajorFunction - - @property - def DeviceObject(self): - # TODO: improve this code to avoid reading the whole object - data = self.ql.mem.read(self.base, ctypes.sizeof(self)) - obj = type(self).from_buffer(data) - return obj._DeviceObject.value - - @DeviceObject.setter - def DeviceObject(self, value): - # TODO: improve this code to avoid reading/writing the whole object - data = self.ql.mem.read(self.base, ctypes.sizeof(self)) - obj = type(self).from_buffer(data) - obj._DeviceObject.value = value - # update back to memory. - self.ql.mem.write(self.base, bytes(obj)) - - @property - def DriverUnload(self): - data = self.ql.mem.read(self.base, ctypes.sizeof(self)) - obj = type(self).from_buffer(data) - return obj._DriverUnload.value +def __select_pointer_type(archbits: int): + """Provide a pointer base class based on the underlying + architecture properties. + """ + + 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) + + class TEB(Struct): + _fields_ = ( + ('CurrentSEH', native_type), + ('StackBase', native_type), + ('StackLimit', native_type), + ('SubSystemTib', native_type), + ('FiberData', native_type), + ('ArbitraryDataSlot', native_type), + ('TebAddress', native_type), + ('EnvironmentPointer', native_type), + ('ProcessID', native_type), + ('ThreadID', native_type), + ('RpcHandle', native_type), + ('TlsAddress', native_type), + ('PebAddress', native_type), + ('LastError', ctypes.c_int32), + ('CriticalSectionsCount', ctypes.c_int32), + ('CsrClientThreadAddress', native_type), + ('Win32ThreadInfo', native_type), + ('Win32ClientInfo', ctypes.c_byte * 124), + ('ReservedWow64', native_type), + ('CurrentLocale', ctypes.c_int32), + ('FpSwStatusReg', ctypes.c_int32), + ('ReservedOS', ctypes.c_byte * 216) + ) + + return TEB(*args, **kwargs) + + +def make_peb(archbits: int, *args, **kwargs): + """Initialize a PEB structure. + + Additional arguments may be used to initialize specific PEB fields. + """ + + native_type = __select_native_type(archbits) + Struct = __make_struct(archbits) + + # https://www.geoffchappell.com/studies/windows/win32/ntdll/structs/peb/index.htm + class PEB(Struct): + _fields_ = ( + ('InheritedAddressSpace', ctypes.c_int8), + ('ReadImageFileExecOptions', ctypes.c_int8), + ('BeingDebugged', ctypes.c_int8), + ('BitField', ctypes.c_int8), + ('Mutant', native_type), + ('ImageBaseAddress', native_type), + ('LdrAddress', native_type), + ('ProcessParameters', native_type), + ('SubSystemData', native_type), + ('ProcessHeap', native_type), + ('FastPebLock', native_type), + ('AtlThunkSListPtr', native_type), + ('IFEOKey', native_type), + ('CrossProcessFlags', ctypes.c_int32), + ('KernelCallbackTable', native_type), + ('SystemReserved', ctypes.c_int32), + ('AtlThunkSListPtr32', ctypes.c_int32), + ('ApiSetMap', native_type), + ('TlsExpansionCounter', ctypes.c_int32), + ('TlsBitmap', native_type), + ('TlsBitmapBits', ctypes.c_int32 * 2), + ('ReadOnlySharedMemoryBase', native_type), + ('SharedData', native_type), + ('ReadOnlyStaticServerData', native_type), + ('AnsiCodePageData', native_type), + ('OemCodePageData', native_type), + ('UnicodeCaseTableData', native_type), + ('NumberOfProcessors', ctypes.c_int32), + ('NtGlobalFlag', ctypes.c_int32), + ('CriticalSectionTimeout', native_type) + # ... more + ) + + obj_size = { + 32: 0x047c, + 64: 0x07c8 + }[archbits] + + obj = PEB(*args, **kwargs) + ctypes.resize(obj, obj_size) + + return obj + + +# 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. + """ + + native_type = __select_native_type(archbits) + Struct = __make_struct(archbits) + + class UNICODE_STRING(Struct): + _fields_ = ( + ('Length', ctypes.c_uint16), + ('MaximumLength', ctypes.c_uint16), + ('Buffer', native_type) + ) + + return UNICODE_STRING(*args, **kwargs) + +# 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__ + + 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)) + ) + + 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') + @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): - _fields_ = (('LowPart', ctypes.c_uint32), ('High1Time', ctypes.c_int32), - ('High2Time', ctypes.c_int32)) + _fields_ = ( + ('LowPart', ctypes.c_uint32), + ('High1Time', ctypes.c_int32), + ('High2Time', ctypes.c_int32) + ) -class LARGE_INTEGER_DUMMYSTRUCTNAME(ctypes.Structure): +class LARGE_INTEGER_DUMMYSTRUCTNAME(ctypes.LittleEndianStructure): _fields_ = ( ('LowPart', ctypes.c_uint32), ('HighPart', ctypes.c_int32), @@ -495,8 +275,12 @@ class LARGE_INTEGER(ctypes.Union): ('QuadPart', ctypes.c_int64), ) +# 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 -class KUSER_SHARED_DATA(ctypes.Structure): +class KUSER_SHARED_DATA(ctypes.LittleEndianStructure): _fields_ = ( ('TickCountLowDeprecated', ctypes.c_uint32), ('TickCountMultiplier', ctypes.c_uint32), @@ -539,216 +323,123 @@ class KUSER_SHARED_DATA(ctypes.Structure): ('TestRetInstruction', ctypes.c_uint8), ('_padding0', ctypes.c_uint8 * 0x2F8)) +def make_list_entry(archbits: int): + native_type = __select_native_type(archbits) + Struct = __make_struct(archbits) -class LIST_ENTRY32(ctypes.Structure): - _pack_ = 4 - _fields_ = ( - ('Flink', ctypes.c_uint32), - ('Blink', ctypes.c_uint32), - ) - - -class KDEVICE_QUEUE_ENTRY32(ctypes.Structure): - _pack_ = 4 - _fields_ = ( - ('DeviceListEntry', LIST_ENTRY32), - ('SortKey', ctypes.c_uint32), - ('Inserted', ctypes.c_uint8) + class LIST_ENTRY(Struct): + _fields_ = ( + ('Flink', native_type), + ('Blink', native_type) ) + return LIST_ENTRY -class WAIT_ENTRY32(ctypes.Structure): - _pack_ = 4 - _fields_ = ( - ('DmaWaitEntry', LIST_ENTRY32), - ('NumberOfChannels', ctypes.c_uint32), - ('DmaContext', ctypes.c_uint32) - ) - - -class WAIT_QUEUE_UNION32(ctypes.Union): - _pack_ = 4 - _fields_ = ("WaitQueueEntry", KDEVICE_QUEUE_ENTRY32), ("Dma", WAIT_ENTRY32) - +def make_device_object(archbits: int): + native_type = __select_native_type(archbits) + pointer_type = __select_pointer_type(archbits) + Struct = __make_struct(archbits) -class WAIT_CONTEXT_BLOCK32(ctypes.Structure): - _pack_ = 4 - _fields_ = (('WaitQueue', WAIT_QUEUE_UNION32), - ('DeviceRoutine', POINTER32), - ('DeviceContext', POINTER32), - ('NumberOfMapRegisters', ctypes.c_uint32), - ('DeviceObject', POINTER32), - ('CurrentIrp', POINTER32), - ('BufferChainingDpc', POINTER32)) + LIST_ENTRY = make_list_entry(archbits) + class KDEVICE_QUEUE_ENTRY(Struct): + _fields_ = ( + ('DeviceListEntry', LIST_ENTRY), + ('SortKey', ctypes.c_uint32), + ('Inserted', ctypes.c_uint8) + ) -class KDEVICE_QUEUE32(ctypes.Structure): - _pack_ = 4 - _fields_ = (('Type', ctypes.c_int16), ('Size', ctypes.c_int16), - ('DeviceListHead', LIST_ENTRY32), ('Lock', ctypes.c_uint32), - ('Busy', ctypes.c_uint8)) - - -class SINGLE_LIST_ENTRY32(ctypes.Structure): - _fields_ = [(('Next', ctypes.c_uint32))] - - -# https://github.com/ntdiff/headers/blob/master/Win10_1507_TS1/x64/System32/hal.dll/Standalone/_KDPC.h -class KDPC32(ctypes.Structure): - _pack_ = 4 - _fields_ = ( - ('Type', ctypes.c_uint8), - ('Importance', ctypes.c_uint8), - ('Number', ctypes.c_uint16), - ('DpcListEntry', LIST_ENTRY32), - ('DeferredRoutine', POINTER32), - ('DeferredContext', POINTER32), - ('SystemArgument1', POINTER32), - ('SystemArgument2', POINTER32), - ('DpcData', POINTER32), - ) - - -class DISPATCHER_HEADER32(ctypes.Structure): - _fields_ = ( - ('Lock', ctypes.c_int32), - ('SignalState', ctypes.c_int32), - ('WaitListHead', LIST_ENTRY32), - ) - - -# https://docs.microsoft.com/vi-vn/windows-hardware/drivers/ddi/wdm/ns-wdm-_device_object -class DEVICE_OBJECT32(ctypes.Structure): - _pack_ = 4 - _fields_ = ( - ('Type', ctypes.c_int16), - ('Size', ctypes.c_uint16), - ('ReferenceCount', ctypes.c_int32), - ('DriverObject', POINTER32), - ('NextDevice', POINTER32), - ('AttachedDevice', POINTER32), - ('CurrentIrp', POINTER32), - ('Timer', POINTER32), - ('Flags', ctypes.c_uint32), - ('Characteristics', ctypes.c_uint32), - ('Vpb', POINTER32), - ('DeviceExtension', ctypes.c_uint32), - ('DeviceType', ctypes.c_uint32), - ('StackSize', ctypes.c_int16), - ('Queue', WAIT_CONTEXT_BLOCK32), - ('AlignmentRequirement', ctypes.c_uint32), - ('DeviceQueue', KDEVICE_QUEUE32), - ('Dpc', KDPC32), - ('ActiveThreadCount', ctypes.c_uint32), - ('SecurityDescriptor', POINTER32), - ('DeviceLock', DISPATCHER_HEADER32), - ('SectorSize', ctypes.c_uint16), - ('Spare1', ctypes.c_uint16), - ('DeviceObjectExtension', POINTER32), - ('Reserved', POINTER32), - ) - - -## 64bit structures -class LIST_ENTRY64(ctypes.Structure): - _pack_ = 8 - _fields_ = ( - ('Flink', ctypes.c_uint64), - ('Blink', ctypes.c_uint64), - ) - - -class KDEVICE_QUEUE_ENTRY64(ctypes.Structure): - _pack_ = 8 - _fields_ = (('DeviceListEntry', LIST_ENTRY64), - ('SortKey', ctypes.c_uint32), ('Inserted', ctypes.c_uint8)) - - -class WAIT_ENTRY64(ctypes.Structure): - _pack_ = 8 - _fields_ = (('DmaWaitEntry', LIST_ENTRY64), - ('NumberOfChannels', ctypes.c_uint32), ('DmaContext', - ctypes.c_uint32)) - - -class WAIT_QUEUE_UNION64(ctypes.Union): - _fields_ = ("WaitQueueEntry", KDEVICE_QUEUE_ENTRY64), ("Dma", WAIT_ENTRY64) - - -class WAIT_CONTEXT_BLOCK64(ctypes.Structure): - _pack_ = 8 - _fields_ = (('WaitQueue', WAIT_QUEUE_UNION64), - ('DeviceRoutine', POINTER64), ('DeviceContext', POINTER64), - ('NumberOfMapRegisters', ctypes.c_uint32), ('DeviceObject', - POINTER64), - ('CurrentIrp', POINTER64), ('BufferChainingDpc', POINTER64)) - - -class KDEVICE_QUEUE64(ctypes.Structure): - _pack_ = 8 - _fields_ = (('Type', ctypes.c_int16), ('Size', ctypes.c_int16), - ('DeviceListHead', LIST_ENTRY64), ('Lock', ctypes.c_uint32), - ('Busy', ctypes.c_uint8)) - + class WAIT_ENTRY(Struct): + _fields_ = ( + ('DmaWaitEntry', LIST_ENTRY), + ('NumberOfChannels', ctypes.c_uint32), + ('DmaContext', ctypes.c_uint32) + ) -class SINGLE_LIST_ENTRY64(ctypes.Structure): - _pack_ = 8 - _fields_ = [(('Next', ctypes.c_uint64))] + class WAIT_QUEUE_UNION(ctypes.Union): + _pack_ = archbits // 8 + _fields_ = ( + ("WaitQueueEntry", KDEVICE_QUEUE_ENTRY), + ("Dma", WAIT_ENTRY) + ) + class WAIT_CONTEXT_BLOCK(Struct): + _fields_ = ( + ('WaitQueue', WAIT_QUEUE_UNION), + ('DeviceRoutine', pointer_type), + ('DeviceContext', pointer_type), + ('NumberOfMapRegisters', ctypes.c_uint32), + ('DeviceObject', pointer_type), + ('CurrentIrp', pointer_type), + ('BufferChainingDpc', pointer_type) + ) -class KDPC64(ctypes.Structure): - _pack_ = 8 - _fields_ = ( - ('Type', ctypes.c_uint8), - ('Importance', ctypes.c_uint8), - ('Number', ctypes.c_uint16), - ('DpcListEntry', LIST_ENTRY64), - ('DeferredRoutine', POINTER64), - ('DeferredContext', POINTER64), - ('SystemArgument1', POINTER64), - ('SystemArgument2', POINTER64), - ('DpcData', POINTER64), - ) + class KDEVICE_QUEUE(Struct): + _fields_ = ( + ('Type', ctypes.c_int16), + ('Size', ctypes.c_int16), + ('DeviceListHead', LIST_ENTRY), + ('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), + ('DeferredRoutine', pointer_type), + ('DeferredContext', pointer_type), + ('SystemArgument1', pointer_type), + ('SystemArgument2', pointer_type), + ('DpcData', pointer_type) + ) -class DISPATCHER_HEADER64(ctypes.Structure): - _pack_ = 8 - _fields_ = ( - ('Lock', ctypes.c_int32), - ('SignalState', ctypes.c_int32), - ('WaitListHead', LIST_ENTRY64), - ) + class DISPATCHER_HEADER(Struct): + _fields_ = ( + ('Lock', ctypes.c_int32), + ('SignalState', ctypes.c_int32), + ('WaitListHead', LIST_ENTRY) + ) + # https://docs.microsoft.com/vi-vn/windows-hardware/drivers/ddi/wdm/ns-wdm-_device_object + class DEVICE_OBJECT(Struct): + _fields_ = ( + ('Type', ctypes.c_int16), + ('Size', ctypes.c_uint16), + ('ReferenceCount', ctypes.c_int32), + ('DriverObject', pointer_type), + ('NextDevice', pointer_type), + ('AttachedDevice', pointer_type), + ('CurrentIrp', pointer_type), + ('Timer', pointer_type), + ('Flags', ctypes.c_uint32), + ('Characteristics', ctypes.c_uint32), + ('Vpb', pointer_type), + ('DeviceExtension', native_type), + ('DeviceType', ctypes.c_uint32), + ('StackSize', ctypes.c_int16), + ('Queue', WAIT_CONTEXT_BLOCK), + ('AlignmentRequirement', ctypes.c_uint32), + ('DeviceQueue', KDEVICE_QUEUE), + ('Dpc', KDPC), + ('ActiveThreadCount', ctypes.c_uint32), + ('SecurityDescriptor', pointer_type), + ('DeviceLock', DISPATCHER_HEADER), + ('SectorSize', ctypes.c_uint16), + ('Spare1', ctypes.c_uint16), + ('DeviceObjectExtension', pointer_type), + ('Reserved', pointer_type) + ) -class DEVICE_OBJECT64(ctypes.Structure): - _pack_ = 8 - _fields_ = ( - ('Type', ctypes.c_int16), - ('Size', ctypes.c_uint16), - ('ReferenceCount', ctypes.c_int32), - ('DriverObject', POINTER64), - ('NextDevice', POINTER64), - ('AttachedDevice', POINTER64), - ('CurrentIrp', POINTER64), - ('Timer', POINTER64), - ('Flags', ctypes.c_uint32), - ('Characteristics', ctypes.c_uint32), - ('Vpb', POINTER64), - ('DeviceExtension', ctypes.c_uint64), - ('DeviceType', ctypes.c_uint32), - ('StackSize', ctypes.c_int16), - ('Queue', WAIT_CONTEXT_BLOCK64), - ('AlignmentRequirement', ctypes.c_uint32), - ('DeviceQueue', KDEVICE_QUEUE64), - ('Dpc', KDPC64), - ('ActiveThreadCount', ctypes.c_uint32), - ('SecurityDescriptor', POINTER64), - ('DeviceLock', DISPATCHER_HEADER64), - ('SectorSize', ctypes.c_uint16), - ('Spare1', ctypes.c_uint16), - ('DeviceObjectExtension', POINTER64), - ('Reserved', POINTER64), - ) + return DEVICE_OBJECT() # struct IO_STATUS_BLOCK { @@ -759,234 +450,102 @@ class DEVICE_OBJECT64(ctypes.Structure): # ULONG_PTR Information; # }; +def make_irp(archbits: int): + pointer_type = __select_pointer_type(archbits) + native_type = __select_native_type(archbits) + Struct = __make_struct(archbits) -class IO_STATUS_BLOCK_DUMMY64(ctypes.Union): - _pack_ = 8 - _fields_ = ( - ('Status', ctypes.c_int32), - ('Pointer', POINTER64), - ) - - -class IO_STATUS_BLOCK64(ctypes.Structure): - _pack_ = 8 - _fields_ = (('Status', IO_STATUS_BLOCK_DUMMY64), ('Information', - POINTER64)) - + LIST_ENTRY = make_list_entry(archbits) -class IO_STATUS_BLOCK_DUMMY32(ctypes.Union): - _fields_ = ( - ('Status', ctypes.c_int32), - ('Pointer', POINTER32), - ) - - -class IO_STATUS_BLOCK32(ctypes.Structure): - _pack_ = 4 - _fields_ = (('Status', IO_STATUS_BLOCK_DUMMY32), ('Information', - POINTER32)) - - -# struct IO_STACK_LOCATION { -# UCHAR MajorFunction; -# UCHAR MinorFunction; -# UCHAR Flags; -# UCHAR Control; -# union { -# struct { -# char _padding1[4]; -# ULONG OutputBufferLength; -# char _padding2[4]; -# ULONG POINTER_ALIGNMENT InputBufferLength; -# char _padding3[4]; -# ULONG POINTER_ALIGNMENT FsControlCode; -# char _padding4[4]; -# PVOID Type3InputBuffer; -# } FileSystemControl; -# struct { -# char _padding5[4]; -# ULONG OutputBufferLength; -# ULONG POINTER_ALIGNMENT InputBufferLength; // 10 -# char _padding7[4]; -# ULONG POINTER_ALIGNMENT IoControlCode; // 18 -# char _padding8[4]; -# PVOID Type3InputBuffer; // 20 -# } DeviceIoControl; -# } Parameters; -# }; -class IO_STACK_LOCATION_FILESYSTEMCONTROL64(ctypes.Structure): - _pack_ = 8 - _fields_ = (('OutputBufferLength', ctypes.c_uint32), ('_padding1', - ctypes.c_uint32), - ('InputBufferLength', ctypes.c_uint32), ('_padding2', - ctypes.c_uint32), - ('FsControlCode', ctypes.c_uint32), ('Type3InputBuffer', - POINTER64)) - - -class IO_STACK_LOCATION_FILESYSTEMCONTROL32(ctypes.Structure): - _pack_ = 4 - _fields_ = ( - ('OutputBufferLength', ctypes.c_uint32), - ('InputBufferLength', ctypes.c_uint32), - ('FsControlCode', ctypes.c_uint32), - ('Type3InputBuffer', POINTER32)) - - -class IO_STACK_LOCATION_DEVICEIOCONTROL64(ctypes.Structure): - _pack_ = 8 - _fields_ = ( - ('OutputBufferLength', ctypes.c_uint32), - ('_padding1', ctypes.c_uint32), - ('InputBufferLength', ctypes.c_uint32), - ('_padding2', ctypes.c_uint32), - ('IoControlCode', ctypes.c_uint32), - ('Type3InputBuffer', POINTER64)) - - -class IO_STACK_LOCATION_DEVICEIOCONTROL32(ctypes.Structure): - _pack_ = 4 - _fields_ = ( - ('OutputBufferLength', ctypes.c_uint32), - ('InputBufferLength', ctypes.c_uint32), - ('IoControlCode', ctypes.c_uint32), - ('Type3InputBuffer', POINTER32) + class IO_STATUS_BLOCK_DUMMY(ctypes.Union): + _pack_ = archbits // 8 + _fields_ = ( + ('Status', ctypes.c_int32), + ('Pointer', pointer_type) ) -class IO_STACK_LOCATION_WRITE64(ctypes.Structure): - _pack_ = 8 - _fields_ = ( - ('Length', ctypes.c_uint32), - ('_padding1', ctypes.c_uint32), - ('Key', ctypes.c_uint32), - ('Flags', ctypes.c_uint32), - ('ByteOffset', LARGE_INTEGER) - ) - -class IO_STACK_LOCATION_WRITE32(ctypes.Structure): - _pack_ = 4 - _fields_ = ( - ('Length', ctypes.c_uint32), - ('Key', ctypes.c_uint32), - ('Flags', ctypes.c_uint32), - ('ByteOffset', LARGE_INTEGER) - ) - -class IO_STACK_LOCATION_PARAM64(ctypes.Union): - _pack_ = 8 - _fields_ = (('FileSystemControl', IO_STACK_LOCATION_FILESYSTEMCONTROL64), - ('DeviceIoControl', IO_STACK_LOCATION_DEVICEIOCONTROL64), - ('Write', IO_STACK_LOCATION_WRITE64)) - - -class IO_STACK_LOCATION_PARAM32(ctypes.Union): - _pack_ = 4 - _fields_ = (('FileSystemControl', IO_STACK_LOCATION_FILESYSTEMCONTROL32), - ('DeviceIoControl', IO_STACK_LOCATION_DEVICEIOCONTROL32), - ('Write', IO_STACK_LOCATION_WRITE32)) - - -class IO_STACK_LOCATION64(ctypes.Structure): - _pack_ = 8 - _fields_ = ( - ('MajorFunction', ctypes.c_byte), - ('MinorFunction', ctypes.c_byte), - ('Flags', ctypes.c_byte), - ('Control', ctypes.c_byte), - ('_padding1', ctypes.c_byte * 0x4), - ('Parameters', IO_STACK_LOCATION_PARAM64), - ('DeviceObject', POINTER64), - ('FileObject', POINTER64), - ('CompletionRoutine', POINTER64), - ('Context', POINTER64), - ) - - -class IO_STACK_LOCATION32(ctypes.Structure): - _pack_ = 4 - _fields_ = ( - ('MajorFunction', ctypes.c_byte), - ('MinorFunction', ctypes.c_byte), - ('Flags', ctypes.c_byte), - ('Control', ctypes.c_byte), - ('Parameters', IO_STACK_LOCATION_PARAM32), - ('DeviceObject', POINTER32), - ('FileObject', POINTER32), - ('CompletionRoutine', POINTER32), - ('Context', POINTER32), - ) + class IO_STATUS_BLOCK(Struct): + _fields_ = ( + ('Status', IO_STATUS_BLOCK_DUMMY), + ('Information', pointer_type) + ) + class IO_STACK_LOCATION_FILESYSTEMCONTROL(Struct): + _fields_ = ( + ('OutputBufferLength', native_type), # c_uint32 padded to native size + ('InputBufferLength', native_type), # c_uint32 padded to native size + ('FsControlCode', ctypes.c_uint32), + ('Type3InputBuffer', pointer_type) + ) -# union { -# struct _IRP *MasterIrp; -# __volatile LONG IrpCount; -# PVOID SystemBuffer; -# } AssociatedIrp; + class IO_STACK_LOCATION_DEVICEIOCONTROL(Struct): + _fields_ = ( + ('OutputBufferLength',native_type), # c_uint32 padded to native size + ('InputBufferLength', native_type), # c_uint32 padded to native size + ('IoControlCode', ctypes.c_uint32), + ('Type3InputBuffer', pointer_type) + ) + class IO_STACK_LOCATION_WRITE(Struct): + _fields_ = ( + ('Length', native_type), # c_uint32 padded to native size + ('Key', ctypes.c_uint32), + ('Flags', ctypes.c_uint32), + ('ByteOffset', LARGE_INTEGER) + ) -class AssociatedIrp64(ctypes.Union): - _fields_ = ( - ('MasterIrp', POINTER64), # ('MasterIrp', ctypes.POINTER(IRP64)), - ('IrpCount', ctypes.c_uint32), - ('SystemBuffer', POINTER64)) + class IO_STACK_LOCATION_PARAM(ctypes.Union): + _pack_ = archbits // 8 + _fields_ = ( + ('FileSystemControl', IO_STACK_LOCATION_FILESYSTEMCONTROL), + ('DeviceIoControl', IO_STACK_LOCATION_DEVICEIOCONTROL), + ('Write', IO_STACK_LOCATION_WRITE) + ) + class IO_STACK_LOCATION(Struct): + _fields_ = ( + ('MajorFunction', ctypes.c_byte), + ('MinorFunction', ctypes.c_byte), + ('Flags', ctypes.c_byte), + ('Control', ctypes.c_byte), + ('Parameters', IO_STACK_LOCATION_PARAM), + ('DeviceObject', pointer_type), + ('FileObject', pointer_type), + ('CompletionRoutine', pointer_type), + ('Context', pointer_type), + ) -class AssociatedIrp32(ctypes.Union): - _fields_ = ( - ('MasterIrp', POINTER32), # ('MasterIrp', ctypes.POINTER(IRP32)), - ('IrpCount', ctypes.c_uint32), - ('SystemBuffer', POINTER32)) - - -# struct _IRP { -# char _padding1[0x30]; -# IO_STATUS_BLOCK IoStatus; // distance is 0x30?? -# char _padding2[0x70 - 0x30 - sizeof(io_status_block)]; -# PVOID UserBuffer; // distance is 0x70 from _IRP -# char _padding3[0xB8 - 0x70 - sizeof(PVOID)]; -# IO_STACK_LOCATION *irpstack; -# }; -class IRP64(ctypes.Structure): - _pack_ = 8 - _fields_ = ( - ('Type', ctypes.c_uint16), - ('Size', ctypes.c_uint16), - ('MdlAddress', POINTER64), - ('Flags', ctypes.c_uint32), - ('AssociatedIrp', AssociatedIrp64), - ('ThreadListEntry', LIST_ENTRY64), - ('IoStatus', IO_STATUS_BLOCK64), - ('_padding1', ctypes.c_char * 0x8), - ('UserIosb', POINTER64), - ('UserEvent', POINTER64), - ('Overlay', ctypes.c_char * 0x10), - ('CancelRoutine', POINTER64), - ('UserBuffer', POINTER64), - ('_padding1', ctypes.c_char * 0x40), - ('irpstack', ctypes.POINTER(IO_STACK_LOCATION64)), - ('_padding2', ctypes.c_char * 0x10), - ) + class AssociatedIrp(ctypes.Union): + _pack_ = archbits // 8 + _fields_ = ( + ('MasterIrp', pointer_type), + ('IrpCount', ctypes.c_uint32), + ('SystemBuffer', pointer_type) + ) + sz_factor = archbits // 32 + + class IRP(Struct): + _fields_ = ( + ('Type', ctypes.c_uint16), + ('Size', ctypes.c_uint16), + ('MdlAddress', pointer_type), + ('Flags', ctypes.c_uint32), + ('AssociatedIrp', AssociatedIrp), + ('ThreadListEntry', LIST_ENTRY), + ('IoStatus', IO_STATUS_BLOCK), + ('_padding1', 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)), + ('_padding2', ctypes.c_char * (8 * sz_factor)) + ) -class IRP32(ctypes.Structure): - _fields_ = ( - ('Type', ctypes.c_uint16), - ('Size', ctypes.c_uint16), - ('MdlAddress', POINTER32), - ('Flags', ctypes.c_uint32), - ('AssociatedIrp', AssociatedIrp32), - ('ThreadListEntry', LIST_ENTRY32), - ('IoStatus', IO_STATUS_BLOCK32), - ('_padding1', ctypes.c_char * 0x8), - ('UserIosb', POINTER32), # 0x28 - ('UserEvent', POINTER32), - ('Overlay', ctypes.c_char * 8), - ('CancelRoutine', POINTER32), - ('UserBuffer', POINTER32), - ('_padding1', ctypes.c_char * 0x20), - ('irpstack', ctypes.POINTER(IO_STACK_LOCATION32)), - ('_padding2', ctypes.c_char * 8), - ) + return IRP() # typedef struct _MDL { @@ -1001,24 +560,26 @@ class IRP32(ctypes.Structure): # } MDL, *PMDL; -class MDL64(ctypes.Structure): - _pack_ = 8 - _fields_ = (('Next', POINTER64), ('Size', ctypes.c_uint16), - ('MdlFlags', ctypes.c_uint16), ('Process', POINTER64), - ('MappedSystemVa', POINTER64), ('StartVa', POINTER64), - ('ByteCount', ctypes.c_uint32), ('ByteOffset', - ctypes.c_uint32)) - - -class MDL32(ctypes.Structure): - _fields_ = (('Next', POINTER32), ('Size', ctypes.c_uint16), - ('MdlFlags', ctypes.c_uint16), ('Process', POINTER32), - ('MappedSystemVa', POINTER32), ('StartVa', POINTER32), - ('ByteCount', ctypes.c_uint32), ('ByteOffset', - ctypes.c_uint32)) +def make_mdl(archbits: int): + pointer_type = __select_pointer_type(archbits) + Struct = __make_struct(archbits) + + class MDL(Struct): + _fields_ = ( + ('Next', pointer_type), + ('Size', ctypes.c_uint16), + ('MdlFlags', ctypes.c_uint16), + ('Process', pointer_type), + ('MappedSystemVa', pointer_type), + ('StartVa', pointer_type), + ('ByteCount', ctypes.c_uint32), + ('ByteOffset', ctypes.c_uint32) + ) -#TODO: Repeated and might not be needed + return MDL() +# NOTE: the following classes are currently not needed +# # class DISPATCHER_HEADER64(ctypes.Structure): # _fields_ = ( # ('Lock', ctypes.c_int32), @@ -1029,8 +590,8 @@ class MDL32(ctypes.Structure): # ('SignalState', ctypes.c_int32), # ('WaitListHead', LIST_ENTRY64), # ) - - +# +# # class DISPATCHER_HEADER32(ctypes.Structure): # _fields_ = ( # ('Lock', ctypes.c_int32), @@ -1041,198 +602,198 @@ class MDL32(ctypes.Structure): # ('ThreadControlFlags', ctypes.c_uint8), # ('TimerMiscFlags', ctypes.c_uint8), # ) - - -class KAPC_STATE64(ctypes.Structure): - _fields_ = ( - ('ApcListHead', LIST_ENTRY64 * 2), - ('Process', POINTER64), - ('KernelApcInProgress', ctypes.c_uint8), - ('KernelApcPending', ctypes.c_uint8), - ('UserApcPending', ctypes.c_uint8), - ) - - -class KAPC_STATE32(ctypes.Structure): - _fields_ = ( - ('ApcListHead', LIST_ENTRY32 * 2), - ('Process', POINTER32), - ('KernelApcInProgress', ctypes.c_uint8), - ('KernelApcPending', ctypes.c_uint8), - ('UserApcPending', ctypes.c_uint8), - ) - - -class KTIMER64(ctypes.Structure): - _fields_ = ( - ('Header', DISPATCHER_HEADER64), - ('DueTime', LARGE_INTEGER), - ('TimerListEntry', LIST_ENTRY64), - ('Dpc', POINTER64), - ('Period', ctypes.c_uint32), - ) - - -class KTIMER32(ctypes.Structure): - _fields_ = ( - ('Header', DISPATCHER_HEADER32), - ('DueTime', LARGE_INTEGER), - ('TimerListEntry', LIST_ENTRY32), - ('Dpc', POINTER32), - ('Period', ctypes.c_uint32), - ) - - -class KWAIT_BLOCK64(ctypes.Structure): - _fields_ = ( - ('WaitListEntry', LIST_ENTRY64), - ('Thread', POINTER64), - ('Object', POINTER64), - ('NextWaitBlock', POINTER64), - ('WaitKey', ctypes.c_uint16), - ('WaitType', ctypes.c_uint8), - ('BlockState', ctypes.c_uint8), - ) - - -class KWAIT_BLOCK32(ctypes.Structure): - _fields_ = ( - ('WaitListEntry', LIST_ENTRY32), - ('Thread', POINTER32), - ('Object', POINTER32), - ('NextWaitBlock', POINTER32), - ('WaitKey', ctypes.c_uint16), - ('WaitType', ctypes.c_uint8), - ('BlockState', ctypes.c_uint8), - ) - - -class GROUP_AFFINITY64(ctypes.Structure): - _fields_ = (('Mask', ctypes.c_uint64), ('Group', ctypes.c_uint16), - ('Reserved', ctypes.c_uint16 * 3)) - - -class GROUP_AFFINITY32(ctypes.Structure): - _fields_ = (('Mask', ctypes.c_uint32), ('Group', ctypes.c_uint16), - ('Reserved', ctypes.c_uint16 * 3)) - - -class KAPC64(ctypes.Structure): - _pack_ = 8 - _fields_ = ( - ('Type', ctypes.c_uint8), - ('SpareByte0', ctypes.c_uint8), - ('Size', ctypes.c_uint8), - ('SpareByte1', ctypes.c_uint8), - ('SpareLong0', ctypes.c_uint32), - ('Thread', POINTER64), - ('ApcListEntry', LIST_ENTRY64), - ('KernelRoutine', POINTER64), - ('RundownRoutine', POINTER64), - ('NormalRoutine', POINTER64), - ('NormalContext', POINTER64), - ('SystemArgument1', POINTER64), - ('SystemArgument2', POINTER64), - ('ApcStateIndex', ctypes.c_uint8), - ('ApcMode', ctypes.c_uint8), - ('Inserted', ctypes.c_uint8), - ) - - -class KAPC32(ctypes.Structure): - _fields_ = () - - -class KSEMAPHORE64(ctypes.Structure): - _pack_ = 8 - _fields_ = (("Header", DISPATCHER_HEADER64), ("Limit", ctypes.c_int32)) - - -class COUNTER_READING64(ctypes.Structure): - _pack_ = 8 - _fields_ = ( - ("Type", ctypes.c_uint32), - ("Index", ctypes.c_uint32), - ("Start", ctypes.c_uint64), - ("Total", ctypes.c_uint64), - ) - - -class KTHREAD_COUNTERS64(ctypes.Structure): - _pack_ = 8 - _fields_ = ( - ("WaitReasonBitMap", ctypes.c_int64), - ("UserData", POINTER64), - ("Flags", ctypes.c_uint32), - ("ContextSwitches", ctypes.c_uint32), - ("CycleTimeBias", ctypes.c_uint64), - ("HardwareCounters", ctypes.c_uint64), - ("HwCounter", COUNTER_READING64 * 16), - ) - - -class KTHREAD64(ctypes.Structure): - _pack_ = 8 - _fields_ = ( - ('Header', DISPATCHER_HEADER64), - ('CycleTime', ctypes.c_uint64), - ('QuantumTarget', ctypes.c_uint64), - ('InitialStack', POINTER64), - ('StackLimit', POINTER64), - ('KernelStack', POINTER64), - ('ThreadLock', ctypes.c_uint64), - ('WaitRegister', ctypes.c_uint8), # _KWAIT_STATUS_REGISTER - ('Running', ctypes.c_uint8), - ('Alerted', ctypes.c_uint8 * 2), - ('MiscFlags', ctypes.c_uint32), - ('ApcState', KAPC_STATE64), - ('DeferredProcessor', ctypes.c_uint32), - ('ApcQueueLock', ctypes.c_uint64), - ('WaitStatus', ctypes.c_int64), - ('WaitBlockList', POINTER64), - ('WaitListEntry', LIST_ENTRY64), - ('Queue', POINTER64), - ('Teb', POINTER64), - ('Timer', KTIMER64), - ('ThreadFlags', ctypes.c_int32), - ('Spare0', ctypes.c_uint32), - ('WaitBlock', KWAIT_BLOCK64 * 4), - ('QueueListEntry', LIST_ENTRY64), - ('TrapFrame', POINTER64), - ('FirstArgument', POINTER64), - ('CallbackStack', POINTER64), - ('ApcStateIndex', ctypes.c_uint8), - ('BasePriority', ctypes.c_char), - ('PriorityDecrement', ctypes.c_char), - ('Preempted', ctypes.c_uint8), - ('AdjustReason', ctypes.c_uint8), - ('AdjustIncrement', ctypes.c_char), - ('PreviousMode', ctypes.c_char), - ('Saturation', ctypes.c_char), - ('SystemCallNumber', ctypes.c_uint32), - ('FreezeCount', ctypes.c_uint32), - ('UserAffinity', GROUP_AFFINITY64), - ('Process', POINTER64), - ('Affinity', GROUP_AFFINITY64), - ('IdealProcessor', ctypes.c_uint32), - ('UserIdealProcessor', ctypes.c_uint32), - ('ApcStatePointer', POINTER64 * 2), - ('SavedApcState', KAPC_STATE64), - ('Win32Thread', POINTER64), - ('StackBase', POINTER64), - ('SuspendApc', KAPC64), - ('SuspendSemaphore', KSEMAPHORE64), - ('ThreadListEntry', LIST_ENTRY64), - ('MutantListHead', LIST_ENTRY64), - ('SListFaultAddress', POINTER64), - ('ReadOperationCount', ctypes.c_int64), - ('WriteOperationCount', ctypes.c_int64), - ('OtherOperationCount', ctypes.c_int64), - ('ReadTransferCount', ctypes.c_int64), - ('WriteTransferCount', ctypes.c_int64), - ('OtherTransferCount', ctypes.c_int64), - ('ThreadCounters', POINTER64), - ('XStateSave', POINTER64)) +# +# +# class KAPC_STATE64(ctypes.Structure): +# _fields_ = ( +# ('ApcListHead', LIST_ENTRY64 * 2), +# ('Process', POINTER64), +# ('KernelApcInProgress', ctypes.c_uint8), +# ('KernelApcPending', ctypes.c_uint8), +# ('UserApcPending', ctypes.c_uint8), +# ) +# +# +# class KAPC_STATE32(ctypes.Structure): +# _fields_ = ( +# ('ApcListHead', LIST_ENTRY32 * 2), +# ('Process', POINTER32), +# ('KernelApcInProgress', ctypes.c_uint8), +# ('KernelApcPending', ctypes.c_uint8), +# ('UserApcPending', ctypes.c_uint8), +# ) +# +# +# class KTIMER64(ctypes.Structure): +# _fields_ = ( +# ('Header', DISPATCHER_HEADER64), +# ('DueTime', LARGE_INTEGER), +# ('TimerListEntry', LIST_ENTRY64), +# ('Dpc', POINTER64), +# ('Period', ctypes.c_uint32), +# ) +# +# +# class KTIMER32(ctypes.Structure): +# _fields_ = ( +# ('Header', DISPATCHER_HEADER32), +# ('DueTime', LARGE_INTEGER), +# ('TimerListEntry', LIST_ENTRY32), +# ('Dpc', POINTER32), +# ('Period', ctypes.c_uint32), +# ) +# +# +# class KWAIT_BLOCK64(ctypes.Structure): +# _fields_ = ( +# ('WaitListEntry', LIST_ENTRY64), +# ('Thread', POINTER64), +# ('Object', POINTER64), +# ('NextWaitBlock', POINTER64), +# ('WaitKey', ctypes.c_uint16), +# ('WaitType', ctypes.c_uint8), +# ('BlockState', ctypes.c_uint8), +# ) +# +# +# class KWAIT_BLOCK32(ctypes.Structure): +# _fields_ = ( +# ('WaitListEntry', LIST_ENTRY32), +# ('Thread', POINTER32), +# ('Object', POINTER32), +# ('NextWaitBlock', POINTER32), +# ('WaitKey', ctypes.c_uint16), +# ('WaitType', ctypes.c_uint8), +# ('BlockState', ctypes.c_uint8), +# ) +# +# +# class GROUP_AFFINITY64(ctypes.Structure): +# _fields_ = (('Mask', ctypes.c_uint64), ('Group', ctypes.c_uint16), +# ('Reserved', ctypes.c_uint16 * 3)) +# +# +# class GROUP_AFFINITY32(ctypes.Structure): +# _fields_ = (('Mask', ctypes.c_uint32), ('Group', ctypes.c_uint16), +# ('Reserved', ctypes.c_uint16 * 3)) +# +# +# class KAPC64(ctypes.Structure): +# _pack_ = 8 +# _fields_ = ( +# ('Type', ctypes.c_uint8), +# ('SpareByte0', ctypes.c_uint8), +# ('Size', ctypes.c_uint8), +# ('SpareByte1', ctypes.c_uint8), +# ('SpareLong0', ctypes.c_uint32), +# ('Thread', POINTER64), +# ('ApcListEntry', LIST_ENTRY64), +# ('KernelRoutine', POINTER64), +# ('RundownRoutine', POINTER64), +# ('NormalRoutine', POINTER64), +# ('NormalContext', POINTER64), +# ('SystemArgument1', POINTER64), +# ('SystemArgument2', POINTER64), +# ('ApcStateIndex', ctypes.c_uint8), +# ('ApcMode', ctypes.c_uint8), +# ('Inserted', ctypes.c_uint8), +# ) +# +# +# class KAPC32(ctypes.Structure): +# _fields_ = () +# +# +# class KSEMAPHORE64(ctypes.Structure): +# _pack_ = 8 +# _fields_ = (("Header", DISPATCHER_HEADER64), ("Limit", ctypes.c_int32)) +# +# +# class COUNTER_READING64(ctypes.Structure): +# _pack_ = 8 +# _fields_ = ( +# ("Type", ctypes.c_uint32), +# ("Index", ctypes.c_uint32), +# ("Start", ctypes.c_uint64), +# ("Total", ctypes.c_uint64), +# ) +# +# +# class KTHREAD_COUNTERS64(ctypes.Structure): +# _pack_ = 8 +# _fields_ = ( +# ("WaitReasonBitMap", ctypes.c_int64), +# ("UserData", POINTER64), +# ("Flags", ctypes.c_uint32), +# ("ContextSwitches", ctypes.c_uint32), +# ("CycleTimeBias", ctypes.c_uint64), +# ("HardwareCounters", ctypes.c_uint64), +# ("HwCounter", COUNTER_READING64 * 16), +# ) +# +# +# class KTHREAD64(ctypes.Structure): +# _pack_ = 8 +# _fields_ = ( +# ('Header', DISPATCHER_HEADER64), +# ('CycleTime', ctypes.c_uint64), +# ('QuantumTarget', ctypes.c_uint64), +# ('InitialStack', POINTER64), +# ('StackLimit', POINTER64), +# ('KernelStack', POINTER64), +# ('ThreadLock', ctypes.c_uint64), +# ('WaitRegister', ctypes.c_uint8), # _KWAIT_STATUS_REGISTER +# ('Running', ctypes.c_uint8), +# ('Alerted', ctypes.c_uint8 * 2), +# ('MiscFlags', ctypes.c_uint32), +# ('ApcState', KAPC_STATE64), +# ('DeferredProcessor', ctypes.c_uint32), +# ('ApcQueueLock', ctypes.c_uint64), +# ('WaitStatus', ctypes.c_int64), +# ('WaitBlockList', POINTER64), +# ('WaitListEntry', LIST_ENTRY64), +# ('Queue', POINTER64), +# ('Teb', POINTER64), +# ('Timer', KTIMER64), +# ('ThreadFlags', ctypes.c_int32), +# ('Spare0', ctypes.c_uint32), +# ('WaitBlock', KWAIT_BLOCK64 * 4), +# ('QueueListEntry', LIST_ENTRY64), +# ('TrapFrame', POINTER64), +# ('FirstArgument', POINTER64), +# ('CallbackStack', POINTER64), +# ('ApcStateIndex', ctypes.c_uint8), +# ('BasePriority', ctypes.c_char), +# ('PriorityDecrement', ctypes.c_char), +# ('Preempted', ctypes.c_uint8), +# ('AdjustReason', ctypes.c_uint8), +# ('AdjustIncrement', ctypes.c_char), +# ('PreviousMode', ctypes.c_char), +# ('Saturation', ctypes.c_char), +# ('SystemCallNumber', ctypes.c_uint32), +# ('FreezeCount', ctypes.c_uint32), +# ('UserAffinity', GROUP_AFFINITY64), +# ('Process', POINTER64), +# ('Affinity', GROUP_AFFINITY64), +# ('IdealProcessor', ctypes.c_uint32), +# ('UserIdealProcessor', ctypes.c_uint32), +# ('ApcStatePointer', POINTER64 * 2), +# ('SavedApcState', KAPC_STATE64), +# ('Win32Thread', POINTER64), +# ('StackBase', POINTER64), +# ('SuspendApc', KAPC64), +# ('SuspendSemaphore', KSEMAPHORE64), +# ('ThreadListEntry', LIST_ENTRY64), +# ('MutantListHead', LIST_ENTRY64), +# ('SListFaultAddress', POINTER64), +# ('ReadOperationCount', ctypes.c_int64), +# ('WriteOperationCount', ctypes.c_int64), +# ('OtherOperationCount', ctypes.c_int64), +# ('ReadTransferCount', ctypes.c_int64), +# ('WriteTransferCount', ctypes.c_int64), +# ('OtherTransferCount', ctypes.c_int64), +# ('ThreadCounters', POINTER64), +# ('XStateSave', POINTER64)) # struct _RTL_PROCESS_MODULE_INFORMATION { @@ -1247,36 +808,25 @@ class KTHREAD64(ctypes.Structure): # USHORT OffsetToFileName; # UCHAR FullPathName[256]; # } RTL_PROCESS_MODULE_INFORMATION, -class RTL_PROCESS_MODULE_INFORMATION64(ctypes.Structure): - _pack_ = 8 - _fields_ = ( - ('Section', ctypes.c_uint64), - ('MappedBase', ctypes.c_uint64), - ('ImageBase', ctypes.c_uint64), - ('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) - ) - - -class RTL_PROCESS_MODULE_INFORMATION32(ctypes.Structure): - _fields_ = ( - ('Section', ctypes.c_uint32), - ('MappedBase', ctypes.c_uint32), - ('ImageBase', ctypes.c_uint32), - ('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) - ) +def make_rtl_process_module_info(archbits: int): + native_type = __select_native_type(archbits) + Struct = __make_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), + ('OffsetToFileName', ctypes.c_uint16), + ('FullPathName', ctypes.c_char * 256) + ) + return RTL_PROCESS_MODULE_INFORMATION() # struct _EPROCESS { # struct _KPROCESS Pcb; //0x0 @@ -1427,205 +977,99 @@ class RTL_PROCESS_MODULE_INFORMATION32(ctypes.Structure): # ULONG SmallestTimerResolution; //0x4c0 # struct _PO_DIAG_STACK_RECORD* TimerResolutionStackRecord; //0x4c8 # }; -class EPROCESS64(ctypes.Structure): - _pack_ = 8 - _fields_ = (('dummy', ctypes.c_char * 0x4d0), ) - def __init__(self, ql, base): - self.ql = ql - self.base = base +def make_eprocess(archbits: int): + Struct = __make_struct(archbits) + class EPROCESS(Struct): + _fields_ = ( + ('dummy', ctypes.c_uint8), + ) -class EPROCESS32(ctypes.Structure): - _fields_ = (('dummy', ctypes.c_char * 0x2c0), ) + obj_size = { + 32: 0x2c0, + 64: 0x4d0 + }[archbits] - def __init__(self, ql, base): - self.ql = ql - self.base = base - - -# FIXME: duplicate class -class LdrData: - def __init__(self, - ql, - base=0, - length=0, - initialized=0, - ss_handle=0, - in_load_order_module_list={ - 'Flink': 0, - 'Blink': 0 - }, - in_memory_order_module_list={ - 'Flink': 0, - 'Blink': 0 - }, - in_initialization_order_module_list={ - 'Flink': 0, - 'Blink': 0 - }, - entry_in_progress=0, - shutdown_in_progress=0, - shutdown_thread_id=0): - self.ql = ql - self.base = base - self.Length = length - self.Initialized = initialized - self.SsHandle = ss_handle - self.InLoadOrderModuleList = in_load_order_module_list - self.InMemoryOrderModuleList = in_memory_order_module_list - self.InInitializationOrderModuleList = in_initialization_order_module_list - self.EntryInProgress = entry_in_progress - self.ShutdownInProgress = shutdown_in_progress - self.selfShutdownThreadId = shutdown_thread_id - - def bytes(self): - s = b'' - s += self.ql.pack32(self.Length) # 0x0 - s += self.ql.pack32(self.Initialized) # 0x4 - s += self.ql.pack(self.SsHandle) # 0x8 - s += self.ql.pack(self.InLoadOrderModuleList['Flink']) # 0x0c - s += self.ql.pack(self.InLoadOrderModuleList['Blink']) - s += self.ql.pack(self.InMemoryOrderModuleList['Flink']) # 0x14 - s += self.ql.pack(self.InMemoryOrderModuleList['Blink']) - s += self.ql.pack( - self.InInitializationOrderModuleList['Flink']) # 0x1C - s += self.ql.pack(self.InInitializationOrderModuleList['Blink']) - s += self.ql.pack(self.EntryInProgress) - s += self.ql.pack(self.ShutdownInProgress) - s += self.ql.pack(self.selfShutdownThreadId) - return s - - -class LdrDataTableEntry: - def __init__(self, - ql, - base=0, - in_load_order_links={ - 'Flink': 0, - 'Blink': 0 - }, - in_memory_order_links={ - 'Flink': 0, - 'Blink': 0 - }, - in_initialization_order_links={ - 'Flink': 0, - 'Blink': 0 - }, - dll_base=0, - entry_point=0, - size_of_image=0, - full_dll_name='', - base_dll_name='', - flags=0, - load_count=0, - tls_index=0, - hash_links=0, - section_pointer=0, - check_sum=0, - time_date_stamp=0, - loaded_imports=0, - entry_point_activation_context=0, - patch_information=0, - forwarder_links=0, - service_tag_links=0, - static_links=0, - context_information=0, - original_base=0, - load_time=0): - self.ql = ql - self.base = base - self.InLoadOrderLinks = in_load_order_links - self.InMemoryOrderLinks = in_memory_order_links - self.InInitializationOrderLinks = in_initialization_order_links - self.DllBase = dll_base - self.EntryPoint = entry_point - self.SizeOfImage = size_of_image - - full_dll_name = full_dll_name.encode("utf-16le") - self.FullDllName = { - 'Length': len(full_dll_name), - 'MaximumLength': len(full_dll_name) + 2 - } - self.FullDllName['BufferPtr'] = self.ql.os.heap.alloc( - self.FullDllName['MaximumLength']) - ql.mem.write(self.FullDllName['BufferPtr'], - full_dll_name + b"\x00\x00") - - base_dll_name = base_dll_name.encode("utf-16le") - self.BaseDllName = { - 'Length': len(base_dll_name), - 'MaximumLength': len(base_dll_name) + 2 - } - self.BaseDllName['BufferPtr'] = self.ql.os.heap.alloc( - self.BaseDllName['MaximumLength']) - ql.mem.write(self.BaseDllName['BufferPtr'], - base_dll_name + b"\x00\x00") - - self.Flags = flags - self.LoadCount = load_count - self.TlsIndex = tls_index - self.HashLinks = hash_links - self.SectionPointer = section_pointer - self.CheckSum = check_sum - self.TimeDateStamp = time_date_stamp - self.LoadedImports = loaded_imports - self.EntryPointActivationContext = entry_point_activation_context - self.PatchInformation = patch_information - self.ForwarderLinks = forwarder_links - self.ServiceTagLinks = service_tag_links - self.StaticLinks = static_links - self.ContextInformation = context_information - self.OriginalBase = original_base - self.LoadTime = load_time - - def attrs(self): - return ", ".join("{}={}".format(k, getattr(self, k)) - for k in self.__dict__.keys()) - - def print(self): - return "[{}:{}]".format(self.__class__.__name__, self.attrs()) - - def bytes(self): - s = b'' - s += self.ql.pack(self.InLoadOrderLinks['Flink']) # 0x0 - s += self.ql.pack(self.InLoadOrderLinks['Blink']) - s += self.ql.pack(self.InMemoryOrderLinks['Flink']) # 0x8 - s += self.ql.pack(self.InMemoryOrderLinks['Blink']) - s += self.ql.pack(self.InInitializationOrderLinks['Flink']) # 0x10 - s += self.ql.pack(self.InInitializationOrderLinks['Blink']) - s += self.ql.pack(self.DllBase) # 0x18 - s += self.ql.pack(self.EntryPoint) # 0x1c - s += self.ql.pack(self.SizeOfImage) # 0x20 - s += self.ql.pack16(self.FullDllName['Length']) # 0x24 - s += self.ql.pack16(self.FullDllName['MaximumLength']) # 0x26 - if self.ql.arch.type == QL_ARCH.X8664: - s += self.ql.pack32(0) - s += self.ql.pack(self.FullDllName['BufferPtr']) # 0x28 - s += self.ql.pack16(self.BaseDllName['Length']) - s += self.ql.pack16(self.BaseDllName['MaximumLength']) - if self.ql.arch.type == QL_ARCH.X8664: - s += self.ql.pack32(0) - s += self.ql.pack(self.BaseDllName['BufferPtr']) - s += self.ql.pack(self.Flags) - s += self.ql.pack(self.LoadCount) - s += self.ql.pack(self.TlsIndex) - s += self.ql.pack(self.HashLinks) - s += self.ql.pack(self.SectionPointer) - s += self.ql.pack(self.CheckSum) - s += self.ql.pack(self.TimeDateStamp) - s += self.ql.pack(self.LoadedImports) - s += self.ql.pack(self.EntryPointActivationContext) - s += self.ql.pack(self.PatchInformation) - s += self.ql.pack(self.ForwarderLinks) - s += self.ql.pack(self.ServiceTagLinks) - s += self.ql.pack(self.StaticLinks) - s += self.ql.pack(self.ContextInformation) - s += self.ql.pack(self.OriginalBase) - s += self.ql.pack(self.LoadTime) - - return s + obj = EPROCESS() + ctypes.resize(obj, obj_size) + + return obj + + +def make_ldr_data(archbits: int): + native_type = __select_native_type(archbits) + Struct = __make_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), + ('InMemoryOrderModuleList', ListEntry), + ('InInitializationOrderModuleList', ListEntry), + ('EntryInProgress', native_type), + ('ShutdownInProgress', native_type), + ('selfShutdownThreadId', native_type) + ) + + 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) + + ListEntry = make_list_entry(archbits) + UniString = make_unicode_string(archbits).__class__ + + class RTL_BALANCED_NODE(Struct): + _fields_ = ( + ('Left', pointer_type), + ('Right', pointer_type), + ) + + class LdrDataTableEntry(Struct): + _fields_ = ( + ('InLoadOrderLinks', ListEntry), + ('InMemoryOrderLinks', ListEntry), + ('InInitializationOrderLinks', ListEntry), + ('DllBase', native_type), + ('EntryPoint', native_type), + ('SizeOfImage', native_type), + ('FullDllName', UniString), + ('BaseDllName', UniString), + ('Flags', native_type), + ('ObsoleteLoadCount', ctypes.c_uint16), + ('TlsIndex', ctypes.c_uint16), + ('HashLinks', ListEntry), + ('TimedateStamp', native_type), + ('EntryPointActivationContext', native_type), + ('Lock', native_type), + ('DdagNode', pointer_type), + ('NodeModuleLink', ListEntry), + ('LoadContext', native_type), + ('ParentDllBase', native_type), + ('SwitchBackContext', native_type), + ('BaseAddressIndexNode', RTL_BALANCED_NODE), + ('MappingInfoIndexNode', RTL_BALANCED_NODE), + ('OriginalBase', native_type), + ('LoadTime', LARGE_INTEGER), + ('BaseNameHashValue', native_type), + ('LoadReason', ctypes.c_uint32), + ('ImplicitPathOptions', native_type), + ('ReferenceCount', native_type), + # 1607+ + ('DependentLoadFlags', native_type), + # 1703+ + ('SigningLevel', ctypes.c_uint8) + ) + + return LdrDataTableEntry() class WindowsStruct: @@ -1855,43 +1299,44 @@ def __eq__(self, other): class Mutex: - def __init__(self, name, type): + def __init__(self, name: str, type: str): self.name = name self.locked = False self.type = type - def lock(self): + def lock(self) -> None: self.locked = True - def unlock(self): + def unlock(self) -> None: self.locked = False - def isFree(self): + def isFree(self) -> bool: return not self.locked -class IMAGE_IMPORT_DESCRIPTOR(ctypes.Structure): - _fields_ = ( - ('OriginalFirstThunk', ctypes.c_uint32), - ('TimeDateStamp', ctypes.c_uint32), - ('ForwarderChain', ctypes.c_uint32), - ('Name', ctypes.c_uint32), - ('FirstThunk', ctypes.c_uint32) - ) - - -class CLIENT_ID32(ctypes.Structure): - _fields_ = ( - ('UniqueProcess', ctypes.c_uint32), - ('UniqueThread', ctypes.c_uint32) - ) - +# class IMAGE_IMPORT_DESCRIPTOR(ctypes.Structure): +# _fields_ = ( +# ('OriginalFirstThunk', ctypes.c_uint32), +# ('TimeDateStamp', ctypes.c_uint32), +# ('ForwarderChain', ctypes.c_uint32), +# ('Name', ctypes.c_uint32), +# ('FirstThunk', ctypes.c_uint32) +# ) +# +# +# class CLIENT_ID32(ctypes.Structure): +# _fields_ = ( +# ('UniqueProcess', ctypes.c_uint32), +# ('UniqueThread', ctypes.c_uint32) +# ) +# +# +# class CLIENT_ID64(ctypes.Structure): +# _fields_ = ( +# ('UniqueProcess', ctypes.c_uint64), +# ('UniqueThread', ctypes.c_uint64) +# ) -class CLIENT_ID64(ctypes.Structure): - _fields_ = ( - ('UniqueProcess', ctypes.c_uint64), - ('UniqueThread', ctypes.c_uint64) - ) # typedef struct tagPOINT { # LONG x; # LONG y; diff --git a/qiling/os/windows/utils.py b/qiling/os/windows/utils.py index 6ec9f6c75..32650a487 100644 --- a/qiling/os/windows/utils.py +++ b/qiling/os/windows/utils.py @@ -5,7 +5,7 @@ import ctypes import ntpath -from typing import Tuple, TypeVar +from typing import Iterable, Tuple, TypeVar from unicorn import UcError @@ -44,19 +44,13 @@ def io_Write(ql: Qiling, in_buffer: bytes): # raise error? return (False, None) - if ql.arch.bits == 32: - buf = ql.mem.read(ql.loader.driver_object.DeviceObject, ctypes.sizeof(DEVICE_OBJECT32)) - device_object = DEVICE_OBJECT32.from_buffer(buf) - else: - buf = ql.mem.read(ql.loader.driver_object.DeviceObject, ctypes.sizeof(DEVICE_OBJECT64)) - device_object = DEVICE_OBJECT64.from_buffer(buf) + 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) alloc_addr = [] - def build_mdl(buffer_size, data=None): - if ql.arch.type == QL_ARCH.X8664: - mdl = MDL64() - else: - mdl = MDL32() + def build_mdl(buffer_size: int, data=None): + mdl = make_mdl(ql.arch.bits) mapped_address = heap.alloc(buffer_size) alloc_addr.append(mapped_address) @@ -69,27 +63,21 @@ def build_mdl(buffer_size, data=None): ql.mem.write(mapped_address, written) return mdl + # allocate memory regions for IRP and IO_STACK_LOCATION - if ql.arch.type == QL_ARCH.X8664: - irp_addr = heap.alloc(ctypes.sizeof(IRP64)) - alloc_addr.append(irp_addr) - irpstack_addr = heap.alloc(ctypes.sizeof(IO_STACK_LOCATION64)) - alloc_addr.append(irpstack_addr) - # setup irp stack parameters - irpstack = IO_STACK_LOCATION64() - # setup IRP structure - irp = IRP64() - irp.irpstack = ctypes.cast(irpstack_addr, ctypes.POINTER(IO_STACK_LOCATION64)) - else: - irp_addr = heap.alloc(ctypes.sizeof(IRP32)) - alloc_addr.append(irp_addr) - irpstack_addr = heap.alloc(ctypes.sizeof(IO_STACK_LOCATION32)) - alloc_addr.append(irpstack_addr) - # setup irp stack parameters - irpstack = IO_STACK_LOCATION32() - # setup IRP structure - irp = IRP32() - irp.irpstack = ctypes.cast(irpstack_addr, ctypes.POINTER(IO_STACK_LOCATION32)) + irp = make_irp(ql.arch.bits) + irpstack_class = irp.irpstack._type_ + + irp_addr = heap.alloc(ctypes.sizeof(irp)) + alloc_addr.append(irp_addr) + + irpstack_addr = heap.alloc(ctypes.sizeof(irpstack_class)) + alloc_addr.append(irpstack_addr) + + # setup irp stack parameters + irpstack = irpstack_class() + # setup IRP structure + irp.irpstack = ctypes.cast(irpstack_addr, ctypes.POINTER(irpstack_class)) irpstack.MajorFunction = IRP_MJ_WRITE irpstack.Parameters.Write.Length = len(in_buffer) @@ -104,11 +92,7 @@ def build_mdl(buffer_size, data=None): elif device_object.Flags & DO_DIRECT_IO: # DIRECT_IO mdl = build_mdl(len(in_buffer)) - if ql.arch.type == QL_ARCH.X8664: - mdl_addr = heap.alloc(ctypes.sizeof(MDL64)) - else: - mdl_addr = heap.alloc(ctypes.sizeof(MDL32)) - + mdl_addr = heap.alloc(ctypes.sizeof(mdl)) alloc_addr.append(mdl_addr) ql.mem.write(mdl_addr, bytes(mdl)) @@ -127,7 +111,10 @@ def build_mdl(buffer_size, data=None): # set function args # TODO: make sure this is indeed STDCALL ql.os.fcall = ql.os.fcall_select(STDCALL) - ql.os.fcall.writeParams(((POINTER, ql.loader.driver_object.DeviceObject), (POINTER, irp_addr))) + ql.os.fcall.writeParams(( + (POINTER, ql.loader.driver_object.DeviceObject), + (POINTER, irp_addr) + )) try: # now emulate @@ -136,12 +123,8 @@ def build_mdl(buffer_size, data=None): verify_ret(ql, err) # read current IRP state - if ql.arch.type == QL_ARCH.X8664: - irp_buffer = ql.mem.read(irp_addr, ctypes.sizeof(IRP64)) - irp = IRP64.from_buffer(irp_buffer) - else: - irp_buffer = ql.mem.read(irp_addr, ctypes.sizeof(IRP32)) - irp = IRP32.from_buffer(irp_buffer) + irp_buffer = ql.mem.read(irp_addr, ctypes.sizeof(irp)) + irp = irp.from_buffer(irp_buffer) io_status = irp.IoStatus # now free all alloc memory @@ -162,24 +145,31 @@ def build_mdl(buffer_size, data=None): # LPDWORD lpBytesReturned, # LPOVERLAPPED lpOverlapped); def ioctl(ql: Qiling, params: Tuple[Tuple, int, bytes]) -> Tuple: - heap = ql.os.heap - def ioctl_code(DeviceType, Function, Method, Access): + allocations = [] + + def __heap_alloc(size: int) -> int: + address = ql.os.heap.alloc(size) + allocations.append(address) + + return address + + def __free_all(allocations: Iterable[int]) -> None: + for address in allocations: + ql.os.heap.free(address) + + def ioctl_code(DeviceType: int, Function: int, Method: int, Access: int) -> int: return (DeviceType << 16) | (Access << 14) | (Function << 2) | Method - alloc_addr = [] def build_mdl(buffer_size, data=None): - if ql.arch.type == QL_ARCH.X8664: - mdl = MDL64() - else: - mdl = MDL32() + mdl = make_mdl(ql.arch.bits) - mapped_address = heap.alloc(buffer_size) - alloc_addr.append(mapped_address) + 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) @@ -199,45 +189,23 @@ def build_mdl(buffer_size, data=None): devicetype, function, ctl_method, access = _ioctl_code input_buffer_size = len(in_buffer) - input_buffer_addr = heap.alloc(input_buffer_size) - alloc_addr.append(input_buffer_addr) + input_buffer_addr = __heap_alloc(input_buffer_size) ql.mem.write(input_buffer_addr, bytes(in_buffer)) # create new memory region to store out data - output_buffer_addr = heap.alloc(output_buffer_size) - alloc_addr.append(output_buffer_addr) + output_buffer_addr = __heap_alloc(output_buffer_size) # allocate memory regions for IRP and IO_STACK_LOCATION - if ql.arch.type == QL_ARCH.X8664: - irp_addr = heap.alloc(ctypes.sizeof(IRP64)) - alloc_addr.append(irp_addr) - irpstack_addr = heap.alloc(ctypes.sizeof(IO_STACK_LOCATION64)) - alloc_addr.append(irpstack_addr) - # setup irp stack parameters - irpstack = IO_STACK_LOCATION64() - # setup IRP structure - irp = IRP64() - irp.irpstack = ctypes.cast(irpstack_addr, ctypes.POINTER(IO_STACK_LOCATION64)) - else: - irp_addr = heap.alloc(ctypes.sizeof(IRP32)) - alloc_addr.append(irp_addr) - irpstack_addr = heap.alloc(ctypes.sizeof(IO_STACK_LOCATION32)) - alloc_addr.append(irpstack_addr) - # setup irp stack parameters - irpstack = IO_STACK_LOCATION32() - # setup IRP structure - irp = IRP32() - irp.irpstack = ctypes.cast(irpstack_addr, ctypes.POINTER(IO_STACK_LOCATION32)) - - #print("32 stack location size = 0x%x" %ctypes.sizeof(IO_STACK_LOCATION32)) - #print("32 status block size = 0x%x" %ctypes.sizeof(IO_STATUS_BLOCK32)) - #print("32 irp size = 0x%x" %ctypes.sizeof(IRP32)) - #print("32 IoStatus offset = 0x%x" %IRP32.IoStatus.offset) - #print("32 UserIosb offset = 0x%x" %IRP32.UserIosb.offset) - #print("32 UserEvent offset = 0x%x" %IRP32.UserEvent.offset) - #print("32 UserBuffer offset = 0x%x" %IRP32.UserBuffer.offset) - #print("32 irpstack offset = 0x%x" %IRP32.irpstack.offset) - #print("irp at %x, irpstack at %x" %(irp_addr, irpstack_addr)) + irp = make_irp(ql.arch.bits) + irpstack_class = irp.irpstack._type_ + + irp_addr = __heap_alloc(ctypes.sizeof(irp)) + irpstack_addr = __heap_alloc(ctypes.sizeof(irpstack_class)) + + # setup irp stack parameters + irpstack = irpstack_class() + # setup IRP structure + irp.irpstack = ctypes.cast(irpstack_addr, ctypes.POINTER(irpstack_class)) ql.log.info("IRP is at 0x%x, IO_STACK_LOCATION is at 0x%x" %(irp_addr, irpstack_addr)) @@ -253,8 +221,7 @@ def build_mdl(buffer_size, data=None): # 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) - alloc_addr.append(system_buffer_addr) + system_buffer_addr = __heap_alloc(system_buffer_size) # init data from input buffer ql.mem.write(system_buffer_addr, bytes(in_buffer)) @@ -264,12 +231,7 @@ def build_mdl(buffer_size, data=None): # Create MDL structure for output data # used by both IOCTL_METHOD_IN_DIRECT and IOCTL_METHOD_OUT_DIRECT mdl = build_mdl(output_buffer_size) - if ql.arch.type == QL_ARCH.X8664: - mdl_addr = heap.alloc(ctypes.sizeof(MDL64)) - else: - mdl_addr = heap.alloc(ctypes.sizeof(MDL32)) - - alloc_addr.append(mdl_addr) + mdl_addr = __heap_alloc(ctypes.sizeof(mdl)) ql.mem.write(mdl_addr, bytes(mdl)) irp.MdlAddress.value = mdl_addr @@ -281,21 +243,21 @@ def build_mdl(buffer_size, data=None): 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(((POINTER, ql.loader.driver_object.DeviceObject), (POINTER, irp_addr))) + ql.os.fcall.writeParams(( + (POINTER, ql.loader.driver_object.DeviceObject), + (POINTER, irp_addr) + )) 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]) except UcError as err: verify_ret(ql, err) # read current IRP state - if ql.arch.type == QL_ARCH.X8664: - irp_buffer = ql.mem.read(irp_addr, ctypes.sizeof(IRP64)) - irp = IRP64.from_buffer(irp_buffer) - else: - irp_buffer = ql.mem.read(irp_addr, ctypes.sizeof(IRP32)) - irp = IRP32.from_buffer(irp_buffer) + irp_buffer = ql.mem.read(irp_addr, ctypes.sizeof(irp)) + irp = irp.__class__.from_buffer(irp_buffer) io_status = irp.IoStatus @@ -310,10 +272,7 @@ def build_mdl(buffer_size, data=None): output_data = ql.mem.read(output_buffer_addr, io_status.Information.value) # 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) - #print("\n") + __free_all(allocations) return io_status.Status.Status, io_status.Information.value, output_data else: # TODO: IOCTL for non-Windows. diff --git a/qiling/os/windows/windows.py b/qiling/os/windows/windows.py index e7f69108e..464b8507d 100644 --- a/qiling/os/windows/windows.py +++ b/qiling/os/windows/windows.py @@ -4,6 +4,7 @@ # import json +import ntpath from typing import Callable from unicorn import UcError @@ -15,6 +16,7 @@ from qiling.const import QL_ARCH, QL_INTERCEPT from qiling.exception import QlErrorSyscallError, QlErrorSyscallNotFound from qiling.os.fcall import QlFunctionCall +from qiling.os.memory import QlMemoryHeap from qiling.os.os import QlOs from . import const @@ -24,7 +26,6 @@ from . import clipboard from . import fiber from . import registry -from . import utils import qiling.os.windows.dlls as api @@ -58,20 +59,32 @@ def __make_fcall_selector(atype: QL_ARCH) -> Callable[[int], QlFunctionCall]: return __selector[atype] self.fcall_select = __make_fcall_selector(ql.arch.type) - self.fcall = None + self.fcall = self.fcall_select(fncc.CDECL) - self.PE_RUN = True + ossection = f'OS{self.ql.arch.bits}' + heap_base = self.profile.getint(ossection, 'heap_address') + heap_size = self.profile.getint(ossection, 'heap_size') + + self.heap = QlMemoryHeap(self.ql, heap_base, heap_base + heap_size) + + sysdrv = self.profile.get('PATH', 'systemdrive') + windir = self.profile.get('PATH', 'windir') + username = self.profile.get('USER', 'username') + + self.windir = ntpath.join(sysdrv, windir) + self.userprofile = ntpath.join(sysdrv, 'Users', username) + self.username = username + + 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") + self.pid = self.profile.getint('KERNEL', 'pid') self.automatize_input = self.profile.getboolean("MISC","automatize_input") - self.username = self.profile["USER"]["username"] - self.windir = self.profile["PATH"]["systemdrive"] + self.profile["PATH"]["windir"] - self.userprofile = self.profile["PATH"]["systemdrive"] + "Users\\" + self.profile["USER"]["username"] + "\\" + self.services = {} self.load() @@ -148,7 +161,7 @@ def hook_winapi(self, ql: Qiling, address: int, size: int): raise QlErrorSyscallError("Windows API Implementation Error") else: - ql.log.warning(f'api {api_name} is not implemented') + ql.log.warning(f'api {api_name} ({entry["dll"]}) is not implemented') if ql.debug_stop: raise QlErrorSyscallNotFound("Windows API implementation not found") @@ -181,6 +194,8 @@ def run(self): if self.ql.entry_point is not None: self.ql.loader.entry_point = self.ql.entry_point + self.PE_RUN = True + try: if self.ql.code: self.ql.emu_start(self.ql.loader.entry_point, (self.ql.loader.entry_point + len(self.ql.code)), self.ql.timeout, self.ql.count) diff --git a/qiling/profiles/windows.ql b/qiling/profiles/windows.ql index d8046a125..f26b4934b 100644 --- a/qiling/profiles/windows.ql +++ b/qiling/profiles/windows.ql @@ -3,18 +3,20 @@ heap_address = 0x500000000 heap_size = 0x5000000 stack_address = 0x7ffffffde000 stack_size = 0x40000 -image_address = 0x400000 +image_address = 0x400000 dll_address = 0x7ffff0000000 entry_point = 0x140000000 +KI_USER_SHARED_DATA = 0xfffff78000000000 [OS32] heap_address = 0x5000000 heap_size = 0x5000000 stack_address = 0xfffdd000 stack_size = 0x21000 -image_address = 0x400000 -dll_address = 0x10000000 -entry_point = 0x40000 +image_address = 0x400000 +dll_address = 0x10000000 +entry_point = 0x40000 +KI_USER_SHARED_DATA = 0xffdf0000 [CODE] # ram_size 0xa00000 is 10MB @@ -61,7 +63,7 @@ language = 1093 [PATH] systemdrive = C:\ -windir = Windows\ +windir = Windows [REGISTRY] registry_diff = registry_diff.json From 105916b705d5ceb75d2633e2ba78e5906446e36f Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 22 Mar 2022 12:34:36 +0200 Subject: [PATCH 248/406] Refresh handle module --- qiling/os/windows/handle.py | 62 +++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 33 deletions(-) diff --git a/qiling/os/windows/handle.py b/qiling/os/windows/handle.py index 2a018ba13..b5592ec10 100644 --- a/qiling/os/windows/handle.py +++ b/qiling/os/windows/handle.py @@ -4,68 +4,64 @@ # # A Simple Windows Handle Simulation +from typing import Any, MutableMapping, Optional class Handle: ID = 0xa0000000 - def __init__(self, id=None, obj=None, - name=None, permissions=None): + def __init__(self, id: Optional[int] = None, obj: Any = None, name: Optional[str] = None, permissions: Optional[int] = None): if id is None: - self.id = Handle.ID + id = Handle.ID Handle.ID += 1 - else: - self.id = id + + self.id = id self.obj = obj self.name = name self.permissions = permissions - - # rewrite "=" - def __eq__(self, other): + + # overload "==" + def __eq__(self, other: 'Handle'): return self.id == other.id class HandleManager: # IO - STD_INPUT_HANDLE = Handle(id=0xfffffff6) + STD_INPUT_HANDLE = Handle(id=0xfffffff6) STD_OUTPUT_HANDLE = Handle(id=0xfffffff5) - STD_ERROR_HANDLE = Handle(id=0xfffffff4) + STD_ERROR_HANDLE = Handle(id=0xfffffff4) # Register - HKEY_CLASSES_ROOT = Handle(id=0x80000000) - HKEY_CURRENT_CONFIG = Handle(id=0x80000005) - HKEY_CURRENT_USER = Handle(id=0x80000001) + HKEY_CLASSES_ROOT = Handle(id=0x80000000) + HKEY_CURRENT_CONFIG = Handle(id=0x80000005) + HKEY_CURRENT_USER = Handle(id=0x80000001) HKEY_CURRENT_USER_LOCAL_SETTINGS = Handle(id=0x80000007) - HKEY_LOCAL_MACHINE = Handle(id=0x80000002) - HKEY_PERFORMANCE_DATA = Handle(id=0x80000004) + HKEY_LOCAL_MACHINE = Handle(id=0x80000002) + HKEY_PERFORMANCE_DATA = Handle(id=0x80000004) HKEY_PERFORMANCE_NLSTEXT = Handle(id=0x80000060) - HKEY_PERFORMANCE_TEXT = Handle(id=0x80000050) - HKEY_USERS = Handle(id=0x80000003) + HKEY_PERFORMANCE_TEXT = Handle(id=0x80000050) + HKEY_USERS = Handle(id=0x80000003) def __init__(self): - self.handles = {} + self.handles: MutableMapping[int, Handle] = {} + self.append(HandleManager.STD_INPUT_HANDLE) self.append(HandleManager.STD_OUTPUT_HANDLE) self.append(HandleManager.STD_ERROR_HANDLE) - def append(self, handle): + def append(self, handle: Handle) -> None: self.handles[handle.id] = handle - def get(self, id): + def get(self, id: int) -> Optional[Handle]: return self.handles.get(id, None) - def delete(self, id): - key = self.handles.get(id, None) + def delete(self, id: int) -> None: + key = self.get(id) + if key is not None: del self.handles[id] - def search(self, name): - for handle in self.handles.values(): - if handle.name == name: - return handle - return None - - def search_by_obj(self, obj): - for handle in self.handles.values(): - if handle.obj == obj: - return handle - return None + def search(self, name: str) -> Optional[Handle]: + return next((handle for handle in self.handles.values() if handle.name == name), None) + + def search_by_obj(self, obj: Any) -> Optional[Handle]: + return next((handle for handle in self.handles.values() if handle.obj == obj), None) From f1d1484ac328a385b8abbba3ef221af10dff7b84 Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 22 Mar 2022 12:35:10 +0200 Subject: [PATCH 249/406] Rewrite registry module --- qiling/os/windows/registry.py | 391 +++++++++++++++++++++------------- 1 file changed, 241 insertions(+), 150 deletions(-) diff --git a/qiling/os/windows/registry.py b/qiling/os/windows/registry.py index abdff9f31..bc371f19f 100644 --- a/qiling/os/windows/registry.py +++ b/qiling/os/windows/registry.py @@ -3,13 +3,13 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -import json, os, sys - +import json, os from Registry import Registry +from typing import Any, MutableMapping, Optional, Tuple, Union -from qiling.os.windows.const import * +from qiling import Qiling +from qiling.os.windows.const import REG_TYPES from qiling.exception import * -from qiling.const import * # Registry Manager reads data from two places @@ -22,192 +22,283 @@ # Registry Manager will only write registry changes to config.json # and will not modify the hive file. +class RegConf: + def __init__(self, fname: str): + try: + with open(fname, 'rb') as infile: + data = infile.read() + config = json.loads(data or '{}') + except IOError: + config = {} + + self.conf: MutableMapping[str, dict[str, dict]] = config + + def exists(self, key: str) -> bool: + return key in self.conf + + def create(self, key: str) -> None: + if not self.exists(key): + self.conf[key] = {} + + def delete(self, key: str, subkey: str) -> None: + if self.exists(key): + del self.conf[key][subkey] + + def read(self, key: str, subkey: str, reg_type: int) -> Tuple: + if key in self.conf: + subkeys = self.conf[key] + + if subkey in subkeys: + subkey_item = subkeys[subkey] + + item_type = subkey_item['type'] + item_value = subkey_item['value'] + + if item_type not in REG_TYPES: + raise QlErrorNotImplemented(f'Windows Registry Type {item_type} not implemented') + + return REG_TYPES[item_type], item_value + + return None, None + + def write(self, key: str, subkey: str, reg_type: int, data: Union[str, bytes, int]) -> None: + if not self.exists(key): + self.create(key) + + self.conf[key][subkey] = { + 'type' : REG_TYPES[reg_type], + 'value' : data + } + + def save(self, fname: str): + if self.conf: + with open(fname, 'wb') as ofile: + data = json.dumps(self.conf) + + ofile.write(data.encode('utf-8')) + + +class RegHive: + def __init__(self, hname: str): + def __make_reg(kname: str) -> Registry.Registry: + return Registry.Registry(os.path.join(hname, kname)) + + # hkey local system + self.hklm = { + 'SECURITY' : __make_reg('SECURITY'), + 'SAM' : __make_reg('SAM'), + 'SOFTWARE' : __make_reg('SOFTWARE'), + 'SYSTEM' : __make_reg('SYSTEM'), + 'HARDWARE' : __make_reg('HARDWARE') + } + + # hkey current user + self.hkcu = __make_reg('NTUSER.DAT') + + def __split_reg_path(self, key: str) -> Tuple[Optional[Registry.Registry], Optional[str]]: + regsep = '\\' + keys = key.split(regsep) + + if keys[0] == 'HKEY_LOCAL_MACHINE': + reg = self.hklm[keys[1].upper()] + sub = regsep.join(keys[2:]) + + elif keys[0] == 'HKEY_CURRENT_USER': + reg = self.hkcu + sub = regsep.join(keys[1:]) + + else: + reg = None + sub = None + + return reg, sub + + def exists(self, key: str) -> bool: + reg, sub = self.__split_reg_path(key) + + if reg is None: + return False + + try: + reg.open(sub) + except: + return False + else: + return True + + def create(self, key: str) -> None: + pass + + def delete(self, key: str, subkey: str) -> None: + pass + + def read(self, key: str, subkey: str, reg_type: int) -> Tuple: + reg, sub = self.__split_reg_path(key) + + if reg is None: + raise QlErrorNotImplemented(f'registry root key not implemented') + + v_value = None + v_type = None + + try: + data = reg.open(sub) + except Registry.RegistryKeyNotFoundException: + pass + else: + value = next((v for v in data.values() if v.name() == subkey and reg_type in (Registry.RegNone, v.value_type())), None) + + if value: + v_value = value.value() + v_type = value.value_type() + + return (v_type, v_value) + + def write(self, key: str, subkey: str, reg_type: int, data: Union[str, bytes, int]) -> None: + pass + class RegistryManager: - def __init__(self, ql, hive=None): + def __init__(self, ql: Qiling, hive: Optional[str] = None): self.ql = ql - self.log_registry_dir = self.ql.rootfs - - if self.log_registry_dir == None: - self.log_registry_dir = "qlog" - self.registry_diff = self.ql.targetname + "_diff.json" - self.regdiff = os.path.join(self.log_registry_dir, "registry", self.registry_diff) + log_registry_dir = ql.rootfs or 'qlog' + self.regdiff = os.path.join(log_registry_dir, 'registry', f'{ql.targetname}_diff.json') # hive dir - if hive: - self.hive = hive - else: - self.hive = os.path.join(ql.rootfs, "Windows", "registry") - ql.log.debug("Windows Registry PATH: %s" % self.hive) - if not os.path.exists(self.hive) and not self.ql.code: - raise QlErrorFileNotFound(f"Error: Registry files not found in '{self.hive}'!") + if hive is None: + hive = os.path.join(ql.rootfs, 'Windows', 'registry') + if not os.path.exists(hive) and not ql.code: + raise QlErrorFileNotFound(f'registry files not found in "{hive}"!') + + ql.log.debug(f'Windows Registry PATH: {hive}') + + # if conf file does not exist, create its directory to enable saving later on if not os.path.exists(self.regdiff): - self.registry_config = {} try: - os.makedirs(os.path.join(self.log_registry_dir, "registry"), 0o755) - except Exception: + os.makedirs(os.path.dirname(self.regdiff), 0o755) + except: pass - else: - # read config - # use registry config first - self.f_config = open(self.regdiff, "rb") - data = self.f_config.read() - if data == b"": - self.registry_config = {} - self.f_config.close() - else: - try: - self.registry_config = json.loads(data) - except json.decoder.JSONDecodeError: - raise QlErrorJsonDecode("Windows Registry JSON decode error") - finally: - self.f_config.close() - # hkey local system - self.hklm = {} try: - self.hklm['SECURITY'] = Registry.Registry(os.path.join(self.hive, 'SECURITY')) - self.hklm['SAM'] = Registry.Registry(os.path.join(self.hive, 'SAM')) - self.hklm['SOFTWARE'] = Registry.Registry(os.path.join(self.hive, 'SOFTWARE')) - self.hklm['SYSTEM'] = Registry.Registry(os.path.join(self.hive, 'SYSTEM')) - self.hklm['HARDWARE'] = Registry.Registry(os.path.join(self.hive, 'HARDWARE')) - # hkey current user - self.hkcu = Registry.Registry(os.path.join(self.hive, 'NTUSER.DAT')) + self.reghive = RegHive(hive) except FileNotFoundError: if not ql.code: QlErrorFileNotFound("WARNING: Registry files not found!") + except Exception: if not ql.code: QlErrorFileNotFound("WARNING: Registry files format error") - self.accessed = {} - def exists(self, key): - if key in self.regdiff: - return True - keys = key.split("\\") - self.access(key) try: - if keys[0] == "HKEY_LOCAL_MACHINE": - reg = self.hklm[keys[1]] - sub = "\\".join(keys[2:]) - data = reg.open(sub) - elif keys[0] == "HKEY_CURRENT_USER": - reg = self.hkcu - sub = "\\".join(keys[1:]) - data = reg.open(sub) - else: - raise QlErrorNotImplemented("Windows Registry %s not implemented" % (keys[0])) - except Exception: - return False + self.regconf = RegConf(self.regdiff) + except json.decoder.JSONDecodeError: + raise QlErrorJsonDecode("Windows Registry JSON decode error") + + self.accessed = {} - return True + def exists(self, key: str) -> bool: + self.access(key) - def read(self, key, subkey, reg_type): - # of the key, the subkey is the value checked + return self.regconf.exists(key) or self.reghive.exists(key) - # read reg conf first - if key in self.regdiff and subkey in self.regdiff[key]: - if self.regdiff[key][subkey].type in REG_TYPES: - return REG_TYPES[self.regdiff[key][subkey].type], self.regdiff[key][subkey].value - else: - raise QlErrorNotImplemented( - "Windows Registry Type %s not implemented" % self.regdiff[key][subkey].type) + def read(self, key: str, subkey: str, reg_type: int) -> Tuple: + result = self.regconf.read(key, subkey, reg_type) - # read hive - reg = None - data = None - keys = key.split('\\') - try: - if keys[0] == "HKEY_LOCAL_MACHINE": - reg = self.hklm[keys[1]] - sub = "\\".join(keys[2:]) - data = reg.open(sub) - elif keys[0] == "HKEY_CURRENT_USER": - reg = self.hkcu - sub = "\\".join(keys[1:]) - data = reg.open(sub) - else: - raise QlErrorNotImplemented("Windows Registry %s not implemented" % (keys[0])) - - for value in data.values(): - if value.name() == subkey and (reg_type == Registry.RegNone or - value.value_type() == reg_type): - - self.access(key, value_name=subkey, value=value.value(), type=value.value_type()) - return value.value_type(), value.value() + if result == (None, None): + result = self.reghive.read(key, subkey, reg_type) - except Registry.RegistryKeyNotFoundException: - pass + self.access(key, subkey, *result) - self.access(key, value_name=subkey, value=None, type=None) + return result - return None, None + def access(self, key: str, name: Optional[str] = None, type: Optional[int] = None, value: Any = None): + if key not in self.accessed: + self.accessed[key] = [] - def access(self, key, value_name=None, value=None, type=None): - if value_name is None: - if key not in self.accessed: - self.accessed[key] = [] - else: + if name is not None: self.accessed[key].append({ - "value_name": value_name, + "value_name": name, "value": value, "type": type, "position": self.ql.os.stats.syscalls_counter }) - # we don't have to increase the counter since we are technically inside a hook - def create(self, key): - self.registry_config[key] = dict() + def create(self, key: str) -> None: + self.regconf.create(key) + self.reghive.create(key) - def write(self, key, subkey, reg_type, data): - if key not in self.registry_config: - self.create(key) - # write registry changes to config.json - self.registry_config[key][subkey] = { - "type": REG_TYPES[reg_type], - "value": data - } + def delete(self, key: str, subkey: str) -> None: + self.regconf.delete(key, subkey) + self.reghive.delete(key, subkey) - def delete(self, key, subkey): - del self.registry_config[key][subkey] + def __reg_mem_read(self, data_type: int, data_addr: int, data_size: int, wide: bool) -> Optional[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 - @staticmethod - def _encode_binary_value(data): - # bytes(hex(data), 'ascii') - # TODO - pass + data = read_string(data_addr) + + elif data_type == Registry.RegDWord: + data = self.ql.mem.read_ptr(data_addr, 4) + + elif data_type == Registry.RegQWord: + data = self.ql.mem.read_ptr(data_addr, 8) + + elif data_type == Registry.RegBin: + data = bytes(self.ql.mem.read(data_addr, data_size)) + + else: + data = None + + return data + + def __reg_mem_write(self, data_type: int, data_addr: int, data_val: Union[str, bytes, int], wide: bool) -> Optional[int]: + if data_type in (Registry.RegSZ, Registry.RegExpandSZ): + assert type(data_val) is str + + enc = 'utf-16le' if wide else 'utf-8' + data = f'{data_val}\x00'.encode(enc) + + elif data_type == Registry.RegDWord: + assert type(data_val) is int + + data = self.ql.pack32(data_val) + + elif data_type == Registry.RegQWord: + assert type(data_val) is int + + data = self.ql.pack64(data_val) + + elif data_type == Registry.RegBin: + assert type(data_val) is bytes + + data = data_val - def write_reg_value_into_mem(self, reg_value, reg_type, address): - length = 0 - # string - if reg_type == Registry.RegSZ or reg_type == Registry.RegExpandSZ: - self.ql.mem.write(address, bytes(reg_value, "utf-16le") + b"\x00") - length = len(reg_value) - elif reg_type == Registry.RegBin: - # you can set REG_BINARY like '\x00\x01\x02' in config.json - if type(reg_value) == str: - self.ql.mem.write(address, bytes(reg_value)) - length = len(reg_value) - else: - raise QlErrorNotImplemented("Windows Registry Type not implemented") - elif reg_type == Registry.RegDWord: - data = self.ql.pack32(reg_value) - self.ql.mem.write(address, data) - length = len(data) - elif reg_type == Registry.RegQWord: - data = self.ql.pack64(reg_value) - self.ql.mem.write(address, data) - length = len(data) else: - raise QlErrorNotImplemented( - "Windows Registry Type write to memory %s not implemented" % (REG_TYPES[reg_type])) + return None + + 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 save(self): - # write registry config to config file - if self.registry_config and len(self.registry_config) != 0: - with open(self.regdiff, "wb") as f: - f.write(bytes(json.dumps(self.registry_config), "utf-8")) + self.regconf.save(self.regdiff) From eb0362f7cfa9932b6bac25e617f6eb8b50aef14e Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 22 Mar 2022 12:36:03 +0200 Subject: [PATCH 250/406] Adjust registry module usages --- qiling/os/windows/dlls/advapi32.py | 95 +++++++++++++++--------------- 1 file changed, 49 insertions(+), 46 deletions(-) diff --git a/qiling/os/windows/dlls/advapi32.py b/qiling/os/windows/dlls/advapi32.py index baf247be2..67f1f8afc 100644 --- a/qiling/os/windows/dlls/advapi32.py +++ b/qiling/os/windows/dlls/advapi32.py @@ -13,13 +13,11 @@ def __RegOpenKey(ql: Qiling, address: int, params): hKey = params["hKey"] lpSubKey = params["lpSubKey"] phkResult = params["phkResult"] - ql.log.debug("Key %s %s" % (hKey, lpSubKey)) if hKey not in REG_KEYS: - ql.log.debug("Key %s %s not present" % (hKey, lpSubKey)) return ERROR_FILE_NOT_FOUND - else: - s_hKey = REG_KEYS[hKey] + + s_hKey = REG_KEYS[hKey] key = s_hKey + "\\" + lpSubKey # Keys in the profile are saved as KEY\PARAM = VALUE, so i just want to check that the key is the same @@ -39,48 +37,49 @@ def __RegOpenKey(ql: Qiling, address: int, params): ql.mem.write_ptr(phkResult, new_handle.id) return ERROR_SUCCESS -def __RegQueryValue(ql: Qiling, address: int, params): +def __RegQueryValue(ql: Qiling, address: int, params, wstring: bool): ret = ERROR_SUCCESS hKey = params["hKey"] - s_lpValueName = params["lpValueName"] + lpValueName = params["lpValueName"] lpType = params["lpType"] lpData = params["lpData"] lpcbData = params["lpcbData"] s_hKey = ql.os.handle_manager.get(hKey).obj params["hKey"] = s_hKey # read reg_type - reg_type = Registry.RegNone if lpType == 0 else ql.unpack32(ql.mem.read(lpType, 4)) + reg_type = Registry.RegNone if lpType == 0 else ql.mem.read_ptr(lpType, 4) - try: - # Keys in the profile are saved as KEY\PARAM = VALUE, so i just want to check that the key is the same - value = ql.os.profile["REGISTRY"][s_hKey + "\\" + s_lpValueName] - ql.log.debug("Using profile for value of key %s" % (s_hKey + "\\" + s_lpValueName,)) + # try reading the registry key value from profile first. + # if the key is not specified in profile, proceed to registry manager - # TODO i have no fucking idea on how to set a None value, fucking configparser - if value == "None": - return ERROR_FILE_NOT_FOUND + keyname = f'{s_hKey}\\{lpValueName}' + value = ql.os.profile["REGISTRY"].get(keyname) + + if value is None: + reg_type, value = ql.os.registry_manager.read(s_hKey, lpValueName, reg_type) + + else: + ql.log.debug(f'Value for {keyname} was read from profile') reg_type = Registry.RegSZ # set that the registry has been accessed - ql.os.registry_manager.access(s_hKey, s_lpValueName, value, reg_type) - - except KeyError: - # Read the registry - reg_type, value = ql.os.registry_manager.read(s_hKey, s_lpValueName, reg_type) + ql.os.registry_manager.access(s_hKey, lpValueName, reg_type, value) # error key if reg_type is None or value is None: ql.log.debug("Key value not found") return ERROR_FILE_NOT_FOUND - else: - # set lpData - length = ql.os.registry_manager.write_reg_value_into_mem(value, reg_type, lpData) - # set lpcbData - max_size = int.from_bytes(ql.mem.read(lpcbData, 4), byteorder="little") - ql.mem.write_ptr(lpcbData, length) - if max_size < length: - ret = ERROR_MORE_DATA + + # set lpData + length = ql.os.registry_manager.write_reg_value_into_mem(reg_type, lpData, value, wstring) + + # set lpcbData + max_size = ql.mem.read_ptr(lpcbData, 4) + ql.mem.write_ptr(lpcbData, length, 4) + + if max_size < length: + ret = ERROR_MORE_DATA return ret @@ -91,19 +90,21 @@ def __RegCreateKey(ql: Qiling, address: int, params): lpSubKey = params["lpSubKey"] phkResult = params["phkResult"] - if not (hKey in REG_KEYS): + if hKey not in REG_KEYS: return ERROR_FILE_NOT_FOUND - else: - s_hKey = REG_KEYS[hKey] - params["hKey"] = s_hKey - if not ql.os.registry_manager.exists(s_hKey + "\\" + lpSubKey): - ql.os.registry_manager.create(s_hKey + "\\" + lpSubKey) - ret = ERROR_SUCCESS + s_hKey = REG_KEYS[hKey] + params["hKey"] = s_hKey + + keyname = f'{s_hKey}\\{lpSubKey}' + + if not ql.os.registry_manager.exists(keyname): + ql.os.registry_manager.create(keyname) + ret = ERROR_SUCCESS # new handle if ret == ERROR_SUCCESS: - new_handle = Handle(obj=s_hKey + "\\" + lpSubKey) + new_handle = Handle(obj=keyname) ql.os.handle_manager.append(new_handle) if phkResult != 0: ql.mem.write_ptr(phkResult, new_handle.id) @@ -113,31 +114,33 @@ def __RegCreateKey(ql: Qiling, address: int, params): return ret -def __RegSetValue(ql: Qiling, address: int, params): +def __RegSetValue(ql: Qiling, address: int, params, wstring: bool): hKey = params["hKey"] lpSubKey = params["lpSubKey"] dwType = params["dwType"] lpData = params["lpData"] + 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 - ql.os.registry_manager.write(s_hKey, lpSubKey, dwType, lpData) + # 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) return ERROR_SUCCESS -def __RegSetValueEx(ql: Qiling, address: int, params): +def __RegSetValueEx(ql: Qiling, address: int, params, wstring: bool): hKey = params["hKey"] lpValueName = params["lpValueName"] dwType = params["dwType"] lpData = params["lpData"] + cbData = params["cbData"] s_hKey = ql.os.handle_manager.get(hKey).obj params["hKey"] = s_hKey - # BUG: lpData should be handled according to the value in dwType - ql.os.registry_manager.write(s_hKey, lpValueName, dwType, lpData) + ql.os.registry_manager.write(s_hKey, lpValueName, dwType, lpData, cbData, wstring) return ERROR_SUCCESS @@ -240,7 +243,7 @@ def hook_RegOpenKeyA(ql: Qiling, address: int, params): 'lpcbData' : LPDWORD }) def hook_RegQueryValueExA(ql: Qiling, address: int, params): - return __RegQueryValue(ql, address, params) + return __RegQueryValue(ql, address, params, wstring=False) # LSTATUS RegQueryValueExW( # HKEY hKey, @@ -259,7 +262,7 @@ def hook_RegQueryValueExA(ql: Qiling, address: int, params): 'lpcbData' : LPDWORD }) def hook_RegQueryValueExW(ql: Qiling, address: int, params): - return __RegQueryValue(ql, address, params) + return __RegQueryValue(ql, address, params, wstring=True) # LSTATUS RegCloseKey( # HKEY hKey @@ -340,7 +343,7 @@ def hook_RegCreateKeyExW(ql: Qiling, address: int, params): 'cbData' : DWORD }) def hook_RegSetValueA(ql: Qiling, address: int, params): - return __RegSetValue(ql, address, params) + return __RegSetValue(ql, address, params, wstring=False) @winsdkapi(cc=STDCALL, params={ 'hKey' : HKEY, @@ -350,7 +353,7 @@ def hook_RegSetValueA(ql: Qiling, address: int, params): 'cbData' : DWORD }) def hook_RegSetValueW(ql: Qiling, address: int, params): - return __RegSetValue(ql, address, params) + return __RegSetValue(ql, address, params, wstring=False) # LSTATUS RegSetValueExA( # HKEY hKey, @@ -369,7 +372,7 @@ def hook_RegSetValueW(ql: Qiling, address: int, params): 'cbData' : DWORD }) def hook_RegSetValueExA(ql: Qiling, address: int, params): - return __RegSetValueEx(ql, address, params) + return __RegSetValueEx(ql, address, params, wstring=False) # LSTATUS RegSetValueExW( # HKEY hKey, @@ -388,7 +391,7 @@ def hook_RegSetValueExA(ql: Qiling, address: int, params): 'cbData' : DWORD }) def hook_RegSetValueExW(ql: Qiling, address: int, params): - return __RegSetValueEx(ql, address, params) + return __RegSetValueEx(ql, address, params, wstring=True) # LSTATUS RegDeleteKeyA( # HKEY hKey, From ae0e2f19d11f1e88ed55424d02d4e48875441451 Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 22 Mar 2022 12:36:33 +0200 Subject: [PATCH 251/406] Refresh clipboard module --- qiling/os/windows/clipboard.py | 86 ++++++++++++++++++---------------- 1 file changed, 46 insertions(+), 40 deletions(-) diff --git a/qiling/os/windows/clipboard.py b/qiling/os/windows/clipboard.py index 02c1dc007..f7fecad9d 100644 --- a/qiling/os/windows/clipboard.py +++ b/qiling/os/windows/clipboard.py @@ -4,69 +4,75 @@ # # A Simple Windows Clipboard Simulation -NOT_LOCKED = -1 +from typing import TYPE_CHECKING, Optional +if TYPE_CHECKING: + from qiling.os.windows.windows import QlOsWindows -class Clipboard: +NOT_LOCKED = -1 +ERROR_CLIPBOARD_NOT_OPEN = 0x58a - def __init__(self, os): - #super(Clipboard, self).__init__(ql) +class Clipboard: + def __init__(self, os: 'QlOsWindows'): self.locked_by = NOT_LOCKED self.data = b"Default Clipboard Data" self.os = os + # Valid formats taken from https://doxygen.reactos.org/d8/dd6/base_ # 2applications_2mstsc_2constants_8h_source.html - self.formats = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 128, 129, 130, 131, 142, 512, 767, - 768, 1023] + self.formats = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 128, 129, 130, 131, 142, 512, 767, 768, 1023] - def open(self, h_wnd): - """ - Lock clipboard to hWnd if not already locked. + def open(self, h_wnd: int) -> bool: + """Lock clipboard to hWnd if not already locked. If hWnd is null default to current thead id """ + if h_wnd == 0: - hWnd = self.os.thread_manager.cur_thread.id + h_wnd = self.os.thread_manager.cur_thread.id if self.locked_by != NOT_LOCKED and self.locked_by != h_wnd: - return 0 - else: - self.locked_by = h_wnd - return 1 - - def format_available(self, fmt): - if fmt in self.formats: - return 1 - else: - return 0 + return False + + self.locked_by = h_wnd - def close(self): + return True + + def close(self) -> bool: if self.locked_by == NOT_LOCKED: - self.os.last_error = 0x58A # ERROR_CLIPBOARD_NOT_OPEN + self.os.last_error = ERROR_CLIPBOARD_NOT_OPEN + return False + + self.locked_by = NOT_LOCKED + + return True + + def format_available(self, fmt: int) -> bool: + return fmt in self.formats + + def set_data(self, fmt: int, data: bytes) -> int: + if fmt not in self.formats: return 0 - else: - self.locked_by = NOT_LOCKED - return 1 - def set_data(self, fmt, data): hWnd = self.os.thread_manager.cur_thread.id - + if self.locked_by != hWnd: - self.os.last_error = 0x58A # ERROR_CLIPBOARD_NOT_OPEN + self.os.last_error = ERROR_CLIPBOARD_NOT_OPEN return 0 - else: - if fmt not in self.formats: - return 0 - self.data = data - return 1 - def get_data(self, fmt): + self.data = data + + # BUG: this should be the handle of the clipboard object + return 1 + + def get_data(self, fmt: int) -> Optional[bytes]: if fmt not in self.formats: - return 0 + return None + hWnd = self.os.thread_manager.cur_thread.id - + if self.locked_by != hWnd: - self.os.last_error = 0x58A # ERROR_CLIPBOARD_NOT_OPEN - return 0 - else: - return self.data + self.os.last_error = ERROR_CLIPBOARD_NOT_OPEN + return None + + return self.data From 60ea2b733f2a7ab688709eeb79ab4f19e4b48828 Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 22 Mar 2022 12:37:06 +0200 Subject: [PATCH 252/406] Adjust clipboard module usages --- qiling/os/windows/dlls/user32.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiling/os/windows/dlls/user32.py b/qiling/os/windows/dlls/user32.py index 61c238097..bca944188 100644 --- a/qiling/os/windows/dlls/user32.py +++ b/qiling/os/windows/dlls/user32.py @@ -140,12 +140,12 @@ def hook_GetDesktopWindow(ql: Qiling, address: int, params): 'hWndNewOwner' : HWND }) def hook_OpenClipboard(ql: Qiling, address: int, params): - return ql.os.clipboard.open(params['hWndNewOwner']) + return int(ql.os.clipboard.open(params['hWndNewOwner'])) # BOOL CloseClipboard(); @winsdkapi(cc=STDCALL, params={}) def hook_CloseClipboard(ql: Qiling, address: int, params): - return ql.os.clipboard.close() + return int(ql.os.clipboard.close()) # HANDLE SetClipboardData( # UINT uFormat, @@ -190,7 +190,7 @@ def hook_GetClipboardData(ql: Qiling, address: int, params): 'format' : UINT }) def hook_IsClipboardFormatAvailable(ql: Qiling, address: int, params): - return ql.os.clipboard.format_available(params['uFormat']) + return int(ql.os.clipboard.format_available(params['uFormat'])) # UINT MapVirtualKeyW( # UINT uCode, From a3763db5ca0b13be99a9d0247e4b4b934175b4de Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 22 Mar 2022 12:37:27 +0200 Subject: [PATCH 253/406] Refresh fiber module --- qiling/os/windows/fiber.py | 150 +++++++++++++++++++++++-------------- 1 file changed, 95 insertions(+), 55 deletions(-) diff --git a/qiling/os/windows/fiber.py b/qiling/os/windows/fiber.py index e8bda865b..098620763 100644 --- a/qiling/os/windows/fiber.py +++ b/qiling/os/windows/fiber.py @@ -2,11 +2,16 @@ # # Cross Platform and Multi Architecture Advanced Binary Emulation Framework +from typing import MutableMapping, Optional + from qiling import Qiling +from qiling.const import QL_ARCH +from qiling.os.windows.api import PVOID from qiling.os.windows.const import ERROR_INVALID_PARAMETER +from qiling.os.windows.fncc import CDECL class Fiber: - def __init__(self, idx, cb=None): + def __init__(self, idx: int, cb: Optional[int] = None): self.idx = idx self.data = 0 self.cb = cb @@ -14,61 +19,96 @@ def __init__(self, idx, cb=None): class FiberManager: def __init__(self, ql: Qiling): - self.fibers = {} + self.fibers: MutableMapping[int, Fiber] = {} self.idx = 0 self.ql = ql - def alloc(self, cb=None): - rtn = self.idx - self.fibers[self.idx] = Fiber(self.idx, cb=cb) + def alloc(self, cb: Optional[int] = None) -> int: + idx = self.idx self.idx += 1 - return rtn - - def free(self, idx): - if idx in self.fibers: - fiber = self.fibers[idx] - - if fiber.cb: - self.ql.log.debug(f'Skipping emulation of callback function {fiber.cb:#x} for fiber {fiber.idx:#x}') - - """ - ret_addr = self.ql.arch.regs.read(UC_X86_REG_RIP + 6 ) #FIXME, use capstone to get addr of next instr? - - # Write Fls data to memory to be accessed by cb - addr = self.ql.os.heap.alloc(self.ql.arch.pointersize) - data = fiber.data.to_bytes(self.ql.arch.pointersize, byteorder='little') - self.ql.mem.write(addr, data) - - # set up params and return address then jump to callback - if self.ql.arch.pointersize == 8: - self.ql.arch.regs.write(UC_X86_REG_RCX, addr) - else: - self.ql.stack_push(ret_addr) - self.ql.stack_push(ret_addr) - self.ql.log.debug("Jumping to callback @ 0x%X" % fiber.cb) - self.ql.arch.regs.write(UC_X86_REG_RIP, fiber.cb) - # All of this gets overwritten by the rest of the code in fncc.py - # Not sure how to actually make unicorn emulate the callback function due to that - """ - - else: - del self.fibers[idx] - return 1 - - self.last_error = ERROR_INVALID_PARAMETER - return 0 - - def set(self, idx, data): - if idx in self.fibers: - self.fibers[idx].data = data - return 1 - - self.last_error = ERROR_INVALID_PARAMETER - return 0 - - def get(self, idx): - if idx in self.fibers: - return self.fibers[idx].data - - self.last_error = ERROR_INVALID_PARAMETER - return 0 + + self.fibers[idx] = Fiber(idx, cb) + + return idx + + def free(self, idx: int) -> bool: + if idx not in self.fibers: + self.last_error = ERROR_INVALID_PARAMETER + return False + + fiber = self.fibers[idx] + + if fiber.cb is not None: + self.ql.log.debug(f'Skipping callback function of fiber {fiber.idx} at {fiber.cb:#010x}') + + # TODO: should figure out how to emulate the fiber callback and still return to complete + # the free api hook. + # + # details: normally the emulation flow is diverted by setting the architectural pc reg to + # the desired address. however that would only take effect when all hooks for the current + # address are done. here we want to call a native function and regain control once it is + # done to complete the 'free' api that was started. + # + # one way to do that it to use 'ql.emu_start' and emulate the callback from its entry point + # till it reaches its return address. that would indeed let us regain control and resume the + # 'free' api hook we started here, but doing that will cause uc to abandon the current + # emulation session -- effectively ending it. once the hooks for the current address are done, + # the program will go idle. + # + # if we choose to emulate till 'ql.os.exit_point' instead, the program will continue but the + # hook we are in will not resume and we will never "return" from it. using 'uc.context_save' + # and 'uc.context_restore' to maintain the current emulation properties does not seem to help + # here. + # + # we skip the fiber callback for now. + + # + # self.ql.log.debug(f'Invoking callback function of fiber {fiber.idx} at {fiber.cb:#010x}') + # self.__invoke_callback(fiber) + # self.ql.log.debug(f'Callback function of fiber {fiber.idx} returned gracefully') + # + + del self.fibers[idx] + + return True + + # TODO: this one is unused for now; see above + def __invoke_callback(self, fiber: Fiber): + assert fiber.cb is not None + + # we are in an api hook. extract the return address of the free + # api to know where the callback should be returning to + retaddr = self.ql.stack_read(0) + + # one PVOID arg, set to fiber data + args = ((PVOID, fiber.data),) + + # set up call frame for callback + fcall = self.ql.os.fcall_select(CDECL) + fcall.call_native(fiber.cb, args, retaddr) + + # callback has to be invoked before returning from the free api + self.ql.emu_start(fiber.cb, retaddr) + + # unwind call frame + fcall.cc.unwind(len(args)) + + # ms64 cc needs also to unwind the reserved shadow slots on the stack + if self.ql.arch.type == QL_ARCH.X8664: + self.ql.arch.regs.arch_sp += (4 * self.ql.arch.pointersize) + + def set(self, idx: int, data: int) -> bool: + if idx not in self.fibers: + self.last_error = ERROR_INVALID_PARAMETER + return False + + self.fibers[idx].data = data + + return True + + def get(self, idx: int) -> int: + if idx not in self.fibers: + self.last_error = ERROR_INVALID_PARAMETER + return 0 + + return self.fibers[idx].data From b20543eee18f166b88d81f27a47f2afd6fc606d4 Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 22 Mar 2022 12:37:51 +0200 Subject: [PATCH 254/406] Adjust fiber module usages --- qiling/os/windows/dlls/kernel32/fibersapi.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/qiling/os/windows/dlls/kernel32/fibersapi.py b/qiling/os/windows/dlls/kernel32/fibersapi.py index 36e1b27f4..1623fb115 100644 --- a/qiling/os/windows/dlls/kernel32/fibersapi.py +++ b/qiling/os/windows/dlls/kernel32/fibersapi.py @@ -14,7 +14,7 @@ 'dwFlsIndex' : DWORD }) def hook_FlsFree(ql: Qiling, address: int, params): - return ql.os.fiber_manager.free(params['dwFlsIndex']) + return int(ql.os.fiber_manager.free(params['dwFlsIndex'])) # LPVOID FlsGetValue( # DWORD dwFlsIndex @@ -25,7 +25,7 @@ def hook_FlsFree(ql: Qiling, address: int, params): def hook_FlsGetValue(ql: Qiling, address: int, params): return ql.os.fiber_manager.get(params['dwFlsIndex']) -# LPVOID FlsSetValue( +# BOOL FlsSetValue( # DWORD dwFlsIndex # PVOID lpFlsData # ); @@ -34,7 +34,7 @@ def hook_FlsGetValue(ql: Qiling, address: int, params): 'lpFlsData' : PVOID }) def hook_FlsSetValue(ql: Qiling, address: int, params): - return ql.os.fiber_manager.set(params['dwFlsIndex'], params['lpFlsData']) + return int(ql.os.fiber_manager.set(params['dwFlsIndex'], params['lpFlsData'])) # DWORD FlsAlloc( # PFLS_CALLBACK_FUNCTION lpCallback @@ -43,10 +43,6 @@ def hook_FlsSetValue(ql: Qiling, address: int, params): 'lpCallback' : PFLS_CALLBACK_FUNCTION }) def hook_FlsAlloc(ql: Qiling, address: int, params): - # global cb = params['lpCallback'] cb = params['lpCallback'] - if cb: - return ql.os.fiber_manager.alloc(cb) - else: - return ql.os.fiber_manager.alloc() + return ql.os.fiber_manager.alloc(cb or None) From 324bb727dca08151c8c43089797e544b66f9f972 Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 22 Mar 2022 12:38:16 +0200 Subject: [PATCH 255/406] Refresh thread module --- qiling/os/windows/thread.py | 169 +++++++++++++++++++----------------- 1 file changed, 87 insertions(+), 82 deletions(-) diff --git a/qiling/os/windows/thread.py b/qiling/os/windows/thread.py index cbbb6651f..ca36a4772 100644 --- a/qiling/os/windows/thread.py +++ b/qiling/os/windows/thread.py @@ -3,62 +3,14 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from unicorn.x86_const import * +from typing import TYPE_CHECKING, cast -from qiling.exception import * -from qiling.os.thread import * -from .utils import * - - -def thread_scheduler(ql, address, size): - if ql.arch.regs.arch_pc == ql.os.thread_manager.THREAD_RET_ADDR: - ql.os.thread_manager.cur_thread.stop() - ql.os.thread_manager.do_schedule() - else: - ql.os.thread_manager.ins_count += 1 - ql.os.thread_manager.do_schedule() - - -# Simple Thread Manager -class QlWindowsThreadManagement(QlThread): - TIME_SLICE = 10 - - def __init__(self, ql, cur_thread): - super(QlWindowsThreadManagement, self).__init__(ql) - self.ql = ql - # main thread - self.cur_thread = cur_thread - self.threads = [self.cur_thread] - self.ins_count = 0 - self.THREAD_RET_ADDR = self.ql.os.heap.alloc(8) - # write nop to THREAD_RET_ADDR - self.ql.mem.write(self.THREAD_RET_ADDR, b"\x90"*8) - self.ql.hook_code(thread_scheduler) - - def append(self, thread): - self.threads.append(thread) - - def need_schedule(self): - return self.cur_thread.is_stop() or self.ins_count % QlWindowsThreadManagement.TIME_SLICE == 0 - - def do_schedule(self): - if self.cur_thread.is_stop() or self.ins_count % QlWindowsThreadManagement.TIME_SLICE == 0: - if len(self.threads) <= 1: - return - else: - for i in range(1, len(self.threads)): - next_id = (self.cur_thread.id + i) % len(self.threads) - next_thread = self.threads[next_id] - # find next thread - if next_thread.status == QlWindowsThread.RUNNING and (not next_thread.has_waitfor()): - if self.cur_thread.is_stop(): - pass - else: - self.cur_thread.suspend() - next_thread.resume() - self.cur_thread = next_thread - break +from qiling import Qiling +from qiling.const import QL_ARCH +from qiling.os.thread import QlThread +if TYPE_CHECKING: + from qiling.os.windows.windows import QlOsWindows class QlWindowsThread(QlThread): # static var @@ -67,8 +19,9 @@ class QlWindowsThread(QlThread): RUNNING = 1 TERMINATED = 2 - def __init__(self, ql, status=1, isFake=False): - super(QlWindowsThread, self).__init__(ql) + def __init__(self, ql: Qiling, status: int = 1, isFake: bool = False): + super().__init__(ql) + self.ql = ql self.id = QlWindowsThread.ID QlWindowsThread.ID += 1 @@ -79,55 +32,107 @@ def __init__(self, ql, status=1, isFake=False): self.fake = isFake # create new thread - def create(self, func_addr, func_params, status): + def create(self, func_addr: int, func_params: int, status: int) -> int: + os = cast('QlOsWindows', self.ql.os) + # create new stack stack_size = 1024 - new_stack = self.ql.os.heap.alloc(stack_size) + stack_size - - self.saved_context = self.ql.arch.regs.save() + new_stack = os.heap.alloc(stack_size) + stack_size + + asize = self.ql.arch.pointersize + context = self.ql.arch.regs.save() - # set return address, parameters + # set return address + self.ql.mem.write_ptr(new_stack - asize, os.thread_manager.thread_ret_addr) + + # set parameters if self.ql.arch.type == QL_ARCH.X86: - self.ql.mem.write_ptr(new_stack - 4, self.ql.os.thread_manager.THREAD_RET_ADDR) self.ql.mem.write_ptr(new_stack, func_params) elif self.ql.arch.type == QL_ARCH.X8664: - self.ql.mem.write_ptr(new_stack - 8, self.ql.os.thread_manager.THREAD_RET_ADDR) - self.saved_context["rcx"] = func_params + context["rcx"] = func_params # set eip/rip, ebp/rbp, esp/rsp if self.ql.arch.type == QL_ARCH.X86: - self.saved_context["eip"] = func_addr - self.saved_context["ebp"] = new_stack - 4 - self.saved_context["esp"] = new_stack - 4 + context["eip"] = func_addr + context["ebp"] = new_stack - asize + context["esp"] = new_stack - asize + elif self.ql.arch.type == QL_ARCH.X8664: - self.saved_context["rip"] = func_addr - self.saved_context["rbp"] = new_stack - 8 - self.saved_context["rsp"] = new_stack - 8 + context["rip"] = func_addr + context["rbp"] = new_stack - asize + context["rsp"] = new_stack - asize + self.saved_context = context self.status = status - return self.id - self.status = status return self.id - def suspend(self): + def suspend(self) -> None: self.saved_context = self.ql.arch.regs.save() - def resume(self): + def resume(self) -> None: self.ql.arch.regs.restore(self.saved_context) self.status = QlWindowsThread.RUNNING - def stop(self): + def stop(self) -> None: self.status = QlWindowsThread.TERMINATED - def is_stop(self): + def is_stop(self) -> bool: return self.status == QlWindowsThread.TERMINATED - def waitfor(self, thread): + def waitfor(self, thread: 'QlWindowsThread') -> None: self.waitforthreads.append(thread) - def has_waitfor(self): - for each_thread in self.waitforthreads: - if not each_thread.is_stop(): - return True - return False + def has_waitfor(self) -> bool: + return any(not thread.is_stop() for thread in self.waitforthreads) + + +# Simple Thread Manager +class QlWindowsThreadManagement(QlThread): + TIME_SLICE = 10 + + def __init__(self, ql: Qiling, os: 'QlOsWindows', cur_thread: QlWindowsThread): + super().__init__(ql) + + self.ql = ql + # main thread + self.cur_thread = cur_thread + self.threads = [self.cur_thread] + self.icount = 0 + self.thread_ret_addr = os.heap.alloc(8) + + # write nop to thread_ret_addr + ql.mem.write(self.thread_ret_addr, b'\x90' * 8) + + def __thread_scheduler(ql: Qiling, address: int, size: int): + if ql.arch.regs.arch_pc == self.thread_ret_addr: + self.cur_thread.stop() + else: + self.icount += 1 + + self.do_schedule() + + ql.hook_code(__thread_scheduler) + + def append(self, thread: QlWindowsThread): + self.threads.append(thread) + + def need_schedule(self): + return self.cur_thread.is_stop() or (self.icount % QlWindowsThreadManagement.TIME_SLICE) == 0 + + def do_schedule(self) -> None: + if self.need_schedule(): + # if there is less than one thread, this loop won't run + for i in range(1, len(self.threads)): + next_id = (self.cur_thread.id + i) % len(self.threads) + next_thread = self.threads[next_id] + + # find next thread + if next_thread.status == QlWindowsThread.RUNNING and not next_thread.has_waitfor(): + if not self.cur_thread.is_stop(): + self.cur_thread.suspend() + + next_thread.resume() + self.cur_thread = next_thread + + break From c4593a0d55946b723de59c431ef145122e9cc51d Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 22 Mar 2022 12:54:22 +0200 Subject: [PATCH 256/406] Adjust components initialization --- qiling/os/windows/windows.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/qiling/os/windows/windows.py b/qiling/os/windows/windows.py index 464b8507d..99aed6184 100644 --- a/qiling/os/windows/windows.py +++ b/qiling/os/windows/windows.py @@ -91,6 +91,8 @@ def __make_fcall_selector(atype: QL_ARCH) -> Callable[[int], QlFunctionCall]: def load(self): self.setupGDT() + self.setupComponents() + # hook win api self.ql.hook_code(self.hook_winapi) @@ -122,12 +124,12 @@ def setupComponents(self): # registry manger self.registry_manager = registry.RegistryManager(self.ql) # clipboard - self.clipboard = clipboard.Clipboard(self.ql.os) + self.clipboard = clipboard.Clipboard(self) # fibers self.fiber_manager = fiber.FiberManager(self.ql) # thread manager main_thread = thread.QlWindowsThread(self.ql) - self.thread_manager = thread.QlWindowsThreadManagement(self.ql, main_thread) + self.thread_manager = thread.QlWindowsThreadManagement(self.ql, self, main_thread) # more handle manager new_handle = handle.Handle(obj=main_thread) From 5bd4f39c06fba35efbea2306b7b75743140eb1af Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 22 Mar 2022 12:55:55 +0200 Subject: [PATCH 257/406] Fix some Windows path usages --- qiling/os/windows/dlls/kernel32/fileapi.py | 44 ++++++++++--------- qiling/os/windows/dlls/kernel32/sysinfoapi.py | 10 ++--- qiling/os/windows/dlls/shell32.py | 6 ++- 3 files changed, 33 insertions(+), 27 deletions(-) diff --git a/qiling/os/windows/dlls/kernel32/fileapi.py b/qiling/os/windows/dlls/kernel32/fileapi.py index d48110277..729eeee98 100644 --- a/qiling/os/windows/dlls/kernel32/fileapi.py +++ b/qiling/os/windows/dlls/kernel32/fileapi.py @@ -3,6 +3,7 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # +import ntpath import os from shutil import copyfile @@ -290,6 +291,27 @@ def hook_CreateFileA(ql: Qiling, address: int, params): def hook_CreateFileW(ql: Qiling, address: int, params): return _CreateFile(ql, address, params) +def _GetTempPath(ql: Qiling, address: int, params, wide: bool): + temp_path = ntpath.join(ql.rootfs, 'Windows', 'Temp') + + if not os.path.exists(temp_path): + os.makedirs(temp_path, 0o755) + + nBufferLength = params['nBufferLength'] + lpBuffer = params['lpBuffer'] + + enc = 'utf-16le' if wide else 'utf-8' + + # temp dir path has to end with a path separator + tmpdir = f'{ntpath.join(ql.os.windir, "Temp")}{ntpath.sep}'.encode(enc) + cstr = tmpdir + '\x00'.encode(enc) + + if nBufferLength >= len(cstr): + ql.mem.write(lpBuffer, cstr) + + # returned length does not include the null-terminator + return len(tmpdir) + # DWORD GetTempPathW( # DWORD nBufferLength, # LPWSTR lpBuffer @@ -299,16 +321,7 @@ def hook_CreateFileW(ql: Qiling, address: int, params): 'lpBuffer' : LPWSTR }) def hook_GetTempPathW(ql: Qiling, address: int, params): - temp_path = os.path.join(ql.rootfs, "Windows", "Temp") - - if not os.path.exists(temp_path): - os.makedirs(temp_path, 0o755) - - dest = params["lpBuffer"] - temp = (ql.os.windir + "Temp" + "\\\x00").encode('utf-16le') - ql.mem.write(dest, temp) - - return len(temp) + return _GetTempPath(ql, address, params, wide=True) # DWORD GetTempPathA( # DWORD nBufferLength, @@ -319,16 +332,7 @@ def hook_GetTempPathW(ql: Qiling, address: int, params): 'lpBuffer' : LPSTR }) def hook_GetTempPathA(ql: Qiling, address: int, params): - temp_path = os.path.join(ql.rootfs, "Windows", "Temp") - - if not os.path.exists(temp_path): - os.makedirs(temp_path, 0o755) - - dest = params["lpBuffer"] - temp = (ql.os.windir + "Temp" + "\\\x00").encode('utf-8') - ql.mem.write(dest, temp) - - return len(temp) + return _GetTempPath(ql, address, params, wide=False) # DWORD GetShortPathNameW( # LPCWSTR lpszLongPath, diff --git a/qiling/os/windows/dlls/kernel32/sysinfoapi.py b/qiling/os/windows/dlls/kernel32/sysinfoapi.py index 4b7bb2e30..8eb577206 100644 --- a/qiling/os/windows/dlls/kernel32/sysinfoapi.py +++ b/qiling/os/windows/dlls/kernel32/sysinfoapi.py @@ -3,7 +3,7 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -import os +import ntpath from datetime import datetime from qiling import Qiling @@ -94,7 +94,7 @@ def __GetWindowsDirectory(ql: Qiling, address: int, params, wstring: bool): lpBuffer = params["lpBuffer"] enc = 'utf-16le' if wstring else 'utf-8' - res = os.path.normpath(ql.os.windir) + res = ntpath.normpath(ql.os.windir) ql.mem.write(lpBuffer, f'{res}\x00'.encode(enc)) @@ -104,7 +104,7 @@ def __GetSystemDirectory(ql: Qiling, address: int, params, wstring: bool): lpBuffer = params["lpBuffer"] enc = 'utf-16le' if wstring else 'utf-8' - res = os.path.join(ql.os.windir, 'System32') + res = ntpath.join(ql.os.windir, 'System32') ql.mem.write(lpBuffer, f'{res}\x00'.encode(enc)) @@ -119,14 +119,14 @@ def __GetSystemDirectory(ql: Qiling, address: int, params, wstring: bool): 'uSize' : UINT }) def hook_GetWindowsDirectoryW(ql: Qiling, address: int, params): - return __GetWindowsDirectory(ql, address, params, True) + return __GetWindowsDirectory(ql, address, params, wstring=True) @winsdkapi(cc=STDCALL, params={ 'lpBuffer' : LPSTR, 'uSize' : UINT }) def hook_GetWindowsDirectoryA(ql: Qiling, address: int, params): - return __GetWindowsDirectory(ql, address, params, False) + return __GetWindowsDirectory(ql, address, params, wstring=False) # UINT GetSystemWindowsDirectoryW( # LPWSTR lpBuffer, diff --git a/qiling/os/windows/dlls/shell32.py b/qiling/os/windows/dlls/shell32.py index abeae6eba..18bcc56c1 100644 --- a/qiling/os/windows/dlls/shell32.py +++ b/qiling/os/windows/dlls/shell32.py @@ -3,6 +3,7 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # +import ntpath import os from typing import Sequence @@ -146,7 +147,7 @@ def hook_SHGetSpecialFolderPathW(ql: Qiling, address: int, params): dst = params["pszPath"] if directory_id == CSIDL_COMMON_APPDATA: - path = str(ql.os.userprofile + "AppData\\") + path = ntpath.join(ql.os.userprofile, "AppData\\") # We always create the directory appdata_dir = path.split("C:\\")[1].replace("\\", "/") ql.log.debug("dir path: %s" % path) @@ -160,9 +161,10 @@ def hook_SHGetSpecialFolderPathW(ql: Qiling, address: int, params): if not os.path.exists(path_emulated): try: os.makedirs(path_emulated, 0o755) - ql.log.debug("os.makedirs completed") except OSError: ql.log.debug("os.makedirs failed") + else: + ql.log.debug("os.makedirs completed") else: raise QlErrorNotImplemented("API not implemented") From 458675cfe5452c7112528d2d7610ce3275258f46 Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 23 Mar 2022 15:00:01 +0200 Subject: [PATCH 258/406] Call get_memory_mapped_image only once --- qiling/loader/pe.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/qiling/loader/pe.py b/qiling/loader/pe.py index 563ad04af..bdda9aff0 100644 --- a/qiling/loader/pe.py +++ b/qiling/loader/pe.py @@ -133,8 +133,6 @@ def load_dll(self, name: bytes, driver: bool = False) -> int: for warning in warnings: self.ql.log.debug(f' - {warning}') - data = bytearray(dll.get_memory_mapped_image()) - image_base = dll.OPTIONAL_HEADER.ImageBase or self.dll_last_address image_size = self.ql.mem.align_up(dll.OPTIONAL_HEADER.SizeOfImage) relocate = False @@ -154,9 +152,9 @@ def load_dll(self, name: bytes, driver: bool = False) -> int: if relocate: with ShowProgress(0.1337): dll.relocate_image(image_base) - data = bytearray(dll.get_memory_mapped_image()) - assert image_size >= len(data) + data = bytearray(dll.get_memory_mapped_image()) + assert image_size >= len(data) cmdlines = [] From e77e1f42f452e42bcaf2df5fab00c21618f790da Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 23 Mar 2022 15:03:33 +0200 Subject: [PATCH 259/406] Extract and extend QlOsStats --- qiling/extensions/report/report.py | 2 +- qiling/os/linux/linux.py | 4 + qiling/os/os.py | 3 +- qiling/os/stats.py | 156 +++++++++++++++++++++++++++++ qiling/os/utils.py | 53 +--------- qiling/os/windows/registry.py | 11 +- qiling/os/windows/windows.py | 29 ++---- 7 files changed, 172 insertions(+), 86 deletions(-) create mode 100644 qiling/os/stats.py diff --git a/qiling/extensions/report/report.py b/qiling/extensions/report/report.py index 7c9a076a1..5a0010ded 100644 --- a/qiling/extensions/report/report.py +++ b/qiling/extensions/report/report.py @@ -16,7 +16,7 @@ def __init__(self, ql): self.os = list(os_map.keys())[list(os_map.values()).index(ql.ostype)] self.env = ql.env self.strings = set() - for string in ql.os.stats.appeared_strings: + for string in ql.os.stats.strings: strings = string.split(" ") self.strings |= set(strings) self.profile = {} diff --git a/qiling/os/linux/linux.py b/qiling/os/linux/linux.py index 8ce5f4155..ef6130d1c 100644 --- a/qiling/os/linux/linux.py +++ b/qiling/os/linux/linux.py @@ -167,3 +167,7 @@ def run(self): self.emu_error() raise + + # display summary + for entry in self.stats.summary(): + self.ql.log.debug(entry) diff --git a/qiling/os/os.py b/qiling/os/os.py index a06dacb46..bbb00af80 100644 --- a/qiling/os/os.py +++ b/qiling/os/os.py @@ -15,7 +15,8 @@ from .filestruct import ql_file from .mapper import QlFsMapper -from .utils import QlOsStats, QlOsUtils +from .stats import QlOsStats +from .utils import QlOsUtils from .path import QlPathManager class QlOs: diff --git a/qiling/os/stats.py b/qiling/os/stats.py new file mode 100644 index 000000000..704304ad8 --- /dev/null +++ b/qiling/os/stats.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +import json +from typing import Any, List, MutableMapping, Mapping, Optional, Set + +class QlOsStats: + """Record basic OS statistics, such as API calls and strings. + """ + + def __init__(self): + self.syscalls: MutableMapping[str, List] = {} + self.strings: MutableMapping[str, Set] = {} + + self.position = 0 + + def clear(self): + """Reset collected stats. + """ + + self.syscalls.clear() + self.strings.clear() + + self.position = 0 + + @staticmethod + def _banner(caption: str) -> List[str]: + bar = '-' * 24 + + return ['', caption, bar] + + def summary(self) -> List[str]: + ret = [] + + ret.extend(QlOsStats._banner('syscalls called')) + + for key, values in self.syscalls.items(): + ret.append(f'{key}:') + ret.extend(f' {json.dumps(value):s}' for value in values) + + ret.extend(QlOsStats._banner('strings ocurrences')) + + for key, values in self.strings.items(): + ret.append(f'{key}: {", ".join(str(word) for word in values)}') + + ret.extend(QlOsStats._banner('registry keys accessed')) + + for key, values in self.syscalls.items(): + ret.append(f'{key}:') + ret.extend(f' {json.dumps(value):s}' for value in values) + + return ret + + def log_api_call(self, address: int, name: str, params: Mapping, retval: Any, retaddr: int) -> None: + """Record API calls along with their details. + + Args: + address : location of the calling instruction + name : api function name + params : mapping of the parameters name to their effective values + retval : value returned by the api function + retaddr : address to which the api function returned + """ + + if name.startswith('hook_'): + name = name[5:] + + self.syscalls.setdefault(name, []).append({ + 'params' : params, + 'retval' : retval, + 'address' : address, + 'retaddr' : retaddr, + 'position' : self.position + }) + + self.position += 1 + + def log_string(self, s: str) -> None: + """Record strings appearance as they are encountered during emulation. + + Args: + s : string to record + """ + + for token in s.split(' '): + self.strings.setdefault(token, set()).add(self.position) + + +class QlWinStats(QlOsStats): + """OS statistics object for Windows OS. Includes registry access stats. + """ + + def __init__(self): + super().__init__() + + self.registry: MutableMapping[str, List] = {} + + def clear(self): + super().clear() + + self.registry.clear() + + def summary(self) -> List[str]: + ret = super().summary() + + ret.extend(QlOsStats._banner('registry keys accessed')) + + for key, values in self.syscalls.items(): + ret.append(f'{key}:') + ret.extend(f' {json.dumps(value):s}' for value in values) + + return ret + + def log_reg_access(self, key: str, item: Optional[str], type: Optional[int], value: Any) -> None: + """Record registry access. + + Args: + key : accessed key name + name : sub item name (if provided) + type : sub item type (if provided) + value : value set to item, in case of a registry modification + """ + + self.registry.setdefault(key, []).append({ + 'item' : item, + 'type' : type, + 'value' : value, + 'position' : self.position + }) + + +class QlOsNullStats(QlOsStats): + """Nullified OS statistics object. + """ + + def clear(self): + pass + + def summary(self) -> List[str]: + return [] + + def log_api_call(self, address: int, name: str, params: Mapping, retval: Any, retaddr: int) -> None: + pass + + def log_string(self, s: str) -> None: + pass + + +class QlWinNullStats(QlOsNullStats): + """Nullified Windows statistics object. + """ + + def log_reg_access(self, key: str, item: Optional[str], type: Optional[int], value: Any) -> None: + pass diff --git a/qiling/os/utils.py b/qiling/os/utils.py index 8e8a6e28c..1228df029 100644 --- a/qiling/os/utils.py +++ b/qiling/os/utils.py @@ -7,63 +7,12 @@ This module is intended for general purpose functions that are only used in qiling.os """ -from typing import Any, List, MutableMapping, Mapping, Set, Union, Sequence, MutableSequence, Tuple +from typing import MutableMapping, Union, Sequence, MutableSequence, Tuple from uuid import UUID from qiling import Qiling 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 - self.appeared_strings: MutableMapping[str, Set] = {} - - def clear(self): - """Reset API and string appearance stats. - """ - - self.syscalls = {} - self.syscalls_counter = 0 - self.appeared_strings = {} - - def log_api_call(self, address: int, name: str, params: Mapping, retval: Any, retaddr: int) -> None: - """Record API calls along with their details. - - Args: - address : location of the calling instruction - name : api function name - params : mapping of the parameters name to their effective values - retval : value returned by the api function - retaddr : address to which the api function returned - """ - - if name.startswith('hook_'): - name = name[5:] - - self.syscalls.setdefault(name, []).append({ - 'params' : params, - 'retval' : retval, - 'address' : address, - 'retaddr' : retaddr, - 'position' : self.syscalls_counter - }) - - self.syscalls_counter += 1 - - def log_string(self, s: str) -> None: - """Record strings appearance as they are encountered during emulation. - - Args: - s : string to record - """ - - for token in s.split(' '): - self.appeared_strings.setdefault(token, set()).add(self.syscalls_counter) - class QlOsUtils: ELLIPSIS_PREF = r'__qlva_' diff --git a/qiling/os/windows/registry.py b/qiling/os/windows/registry.py index bc371f19f..8d190b150 100644 --- a/qiling/os/windows/registry.py +++ b/qiling/os/windows/registry.py @@ -214,16 +214,7 @@ def read(self, key: str, subkey: str, reg_type: int) -> Tuple: return result def access(self, key: str, name: Optional[str] = None, type: Optional[int] = None, value: Any = None): - if key not in self.accessed: - self.accessed[key] = [] - - if name is not None: - self.accessed[key].append({ - "value_name": name, - "value": value, - "type": type, - "position": self.ql.os.stats.syscalls_counter - }) + self.ql.os.stats.log_reg_access(key, name, type, value) def create(self, key: str) -> None: self.regconf.create(key) diff --git a/qiling/os/windows/windows.py b/qiling/os/windows/windows.py index 99aed6184..5cadc29db 100644 --- a/qiling/os/windows/windows.py +++ b/qiling/os/windows/windows.py @@ -3,7 +3,6 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -import json import ntpath from typing import Callable @@ -18,6 +17,7 @@ from qiling.os.fcall import QlFunctionCall from qiling.os.memory import QlMemoryHeap from qiling.os.os import QlOs +from qiling.os.stats import QlWinStats from . import const from . import fncc @@ -61,6 +61,8 @@ def __make_fcall_selector(atype: QL_ARCH) -> Callable[[int], QlFunctionCall]: self.fcall_select = __make_fcall_selector(ql.arch.type) self.fcall = self.fcall_select(fncc.CDECL) + 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') @@ -169,26 +171,6 @@ def hook_winapi(self, ql: Qiling, address: int, size: int): raise QlErrorSyscallNotFound("Windows API implementation not found") - def post_report(self): - self.ql.log.debug("Syscalls called:") - for key, values in self.stats.syscalls.items(): - self.ql.log.debug(f'{key}:') - - for value in values: - self.ql.log.debug(f' {json.dumps(value):s}') - - self.ql.log.debug("Registries accessed:") - for key, values in self.registry_manager.accessed.items(): - self.ql.log.debug(f'{key}:') - - for value in values: - self.ql.log.debug(f' {json.dumps(value):s}') - - self.ql.log.debug("Strings:") - for key, values in self.stats.appeared_strings.items(): - self.ql.log.debug(f'{key}: {" ".join(str(word) for word in values)}') - - def run(self): if self.ql.exit_point is not None: self.exit_point = self.ql.exit_point @@ -208,4 +190,7 @@ def run(self): raise self.registry_manager.save() - self.post_report() + + # display summary + for entry in self.stats.summary(): + self.ql.log.debug(entry) From 5a85a162512975f0c071904e085e814a72d8fba7 Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 23 Mar 2022 15:04:09 +0200 Subject: [PATCH 260/406] Refresh gandcrab test --- tests/test_pe.py | 111 +++++++++++++++++++++++++---------------------- 1 file changed, 58 insertions(+), 53 deletions(-) diff --git a/tests/test_pe.py b/tests/test_pe.py index ae6bf04f3..3bee26c0b 100644 --- a/tests/test_pe.py +++ b/tests/test_pe.py @@ -142,73 +142,78 @@ def close(self): def test_pe_win_x86_gandcrab(self): def _t(): - def stop(ql, default_values): - print("Ok for now") + def stop(ql: Qiling): + ql.log.info("Ok for now") + ql.emu_stop() - def randomize_config_value(ql, key, subkey): - # https://en.wikipedia.org/wiki/Volume_serial_number - # https://www.digital-detective.net/documents/Volume%20Serial%20Numbers.pdf - if key == "VOLUME" and subkey == "serial_number": - month = random.randint(0, 12) - day = random.randint(0, 30) - first = hex(month)[2:] + hex(day)[2:] - seconds = random.randint(0, 60) - milli = random.randint(0, 100) - second = hex(seconds)[2:] + hex(milli)[2:] - first_half = int(first, 16) + int(second, 16) - hour = random.randint(0, 24) - minute = random.randint(0, 60) - third = hex(hour)[2:] + hex(minute)[2:] - year = random.randint(2000, 2020) - second_half = int(third, 16) + year - result = int(hex(first_half)[2:] + hex(second_half)[2:], 16) - ql.os.profile[key][subkey] = str(result) - elif key == "USER" and subkey == "username": - length = random.randint(0, 15) - new_name = "" - for i in range(length): - new_name += random.choice(st.ascii_lowercase + st.ascii_uppercase) - old_name = ql.os.profile[key][subkey] - # update paths - ql.os.profile[key][subkey] = new_name - for path in ql.os.profile["PATH"]: - val = ql.os.profile["PATH"][path].replace(old_name, new_name) - ql.os.profile["PATH"][path] = val - elif key == "SYSTEM" and subkey == "computername": - length = random.randint(0, 15) - new_name = "" - for i in range(length): - new_name += random.choice(st.ascii_lowercase + st.ascii_uppercase) - ql.os.profile[key][subkey] = new_name - else: - raise QlErrorNotImplemented("API not implemented") + def __rand_serialnum() -> str: + """ + see: https://en.wikipedia.org/wiki/Volume_serial_number + see: https://www.digital-detective.net/documents/Volume%20Serial%20Numbers.pdf + """ + + mon = random.randint(1, 12) + day = random.randint(0, 30) + word1 = (mon << 8) + day + + sec = random.randint(0, 59) + ms = random.randint(0, 99) + word2 = (sec << 8) + ms + + unified1 = word1 + word2 + + hrs = random.randint(0, 23) + mins = random.randint(0, 59) + word1 = (hrs << 8) + mins + + yr = random.randint(2000, 2020) + word2 = yr + + unified2 = word1 + word2 + + return f'{unified1:04x}-{unified2:04x}' + + def __rand_name(minlen: int, maxlen: int) -> str: + name_len = random.randint(minlen, maxlen) + + return ''.join(random.choices(st.ascii_lowercase + st.ascii_uppercase, k=name_len)) + ql = Qiling(["../examples/rootfs/x86_windows/bin/GandCrab502.bin"], "../examples/rootfs/x86_windows", verbose=QL_VERBOSE.DEBUG, profile="profiles/windows_gandcrab_admin.ql") - default_user = ql.os.profile["USER"]["username"] - default_computer = ql.os.profile["SYSTEM"]["computername"] - - ql.hook_address(stop, 0x40860f, user_data=(default_user, default_computer)) - randomize_config_value(ql, "USER", "username") - randomize_config_value(ql, "SYSTEM", "computername") - randomize_config_value(ql, "VOLUME", "serial_number") - num_syscalls_admin = ql.os.stats.syscalls_counter + + ql.hook_address(stop, 0x40860f) + + # randomize username + old_uname = ql.os.profile['USER']['username'] + new_uname = __rand_name(3, 10) + + # update paths accordingly + path_key = ql.os.profile['PATH'] + + for p in path_key: + path_key[p] = path_key[p].replace(old_uname, new_uname) + + ql.os.profile['USER']['username'] = new_uname + + # randomize computer name and serial number + ql.os.profile['SYSTEM']['computername'] = __rand_name(5, 15) + ql.os.profile['VOLUME']['serial_number'] = __rand_serialnum() + ql.run() + num_syscalls_admin = ql.os.stats.position del ql # RUN AS USER ql = Qiling(["../examples/rootfs/x86_windows/bin/GandCrab502.bin"], "../examples/rootfs/x86_windows", profile="profiles/windows_gandcrab_user.ql") ql.run() - num_syscalls_user = ql.os.stats.syscalls_counter + num_syscalls_user = ql.os.stats.position + del ql # let's check that gandcrab behave takes a different path if a different environment is found - if num_syscalls_admin == num_syscalls_user: - return False - - del ql - return True + return num_syscalls_admin != num_syscalls_user if IS_FAST_TEST: self.skipTest('QL_FAST_TEST') From b3f6b3546cef4cbfb1fb62e96bf1e9f796439cdc Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 23 Mar 2022 15:05:08 +0200 Subject: [PATCH 261/406] Misc. small changes --- qiling/os/linux/linux.py | 4 ---- qiling/os/windows/dlls/msvcrt.py | 2 +- qiling/os/windows/windows.py | 10 +++++----- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/qiling/os/linux/linux.py b/qiling/os/linux/linux.py index ef6130d1c..8e443cbc2 100644 --- a/qiling/os/linux/linux.py +++ b/qiling/os/linux/linux.py @@ -161,10 +161,6 @@ def run(self): self.ql.emu_start(self.ql.loader.elf_entry, self.exit_point, self.ql.timeout, self.ql.count) except UcError: - # TODO: this is bad We need a better approach for this - #if self.ql.output != QL_OUTPUT.DEBUG: - # return - self.emu_error() raise diff --git a/qiling/os/windows/dlls/msvcrt.py b/qiling/os/windows/dlls/msvcrt.py index 52777f5a9..c1747b1ac 100644 --- a/qiling/os/windows/dlls/msvcrt.py +++ b/qiling/os/windows/dlls/msvcrt.py @@ -434,7 +434,7 @@ def hook_malloc(ql: Qiling, address: int, params): def __free(ql: Qiling, address: int, params): address = params['address'] - return ql.os.heap.free(address) + ql.os.heap.free(address) @winsdkapi(cc=CDECL, params={ 'address': POINTER diff --git a/qiling/os/windows/windows.py b/qiling/os/windows/windows.py index 5cadc29db..72e896428 100644 --- a/qiling/os/windows/windows.py +++ b/qiling/os/windows/windows.py @@ -31,7 +31,7 @@ class QlOsWindows(QlOs): def __init__(self, ql: Qiling): - super(QlOsWindows, self).__init__(ql) + super().__init__(ql) self.ql = ql @@ -178,13 +178,13 @@ def run(self): if self.ql.entry_point is not None: self.ql.loader.entry_point = self.ql.entry_point + entry_point = self.ql.loader.entry_point + exit_point = (self.ql.loader.entry_point + len(self.ql.code)) if self.ql.code else self.exit_point + self.PE_RUN = True try: - if self.ql.code: - self.ql.emu_start(self.ql.loader.entry_point, (self.ql.loader.entry_point + len(self.ql.code)), self.ql.timeout, self.ql.count) - else: - self.ql.emu_start(self.ql.loader.entry_point, self.exit_point, self.ql.timeout, self.ql.count) + self.ql.emu_start(entry_point, exit_point, self.ql.timeout, self.ql.count) except UcError: self.emu_error() raise From 1bb0b1dd15bb73231baaf87e84e781254e0551f6 Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 25 Mar 2022 18:20:42 +0300 Subject: [PATCH 262/406] Fix path canonicalization on Windows hosts --- qiling/loader/pe.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiling/loader/pe.py b/qiling/loader/pe.py index bdda9aff0..d275e02da 100644 --- a/qiling/loader/pe.py +++ b/qiling/loader/pe.py @@ -196,7 +196,7 @@ def load_dll(self, name: bytes, driver: bool = False) -> int: self.dll_last_address = self.ql.mem.align_up(self.dll_last_address + dll_len, 0x10000) # add DLL to coverage images - self.images.append(Image(dll_base, dll_base + dll_len, os.path.realpath(path))) + self.images.append(Image(dll_base, dll_base + dll_len, os.path.abspath(path))) # if this is NOT a driver, add dll to ldr data if not driver: @@ -695,7 +695,7 @@ def load(self, pe: Optional[pefile.PE]): self.ql.log.info(f'PE entry point at {self.entry_point:#x}') self.ql.mem.map(image_base, image_size, info=f'[{image_name}]') - self.images.append(Image(image_base, image_base + pe.NT_HEADERS.OPTIONAL_HEADER.SizeOfImage, os.path.realpath(self.path))) + self.images.append(Image(image_base, image_base + pe.NT_HEADERS.OPTIONAL_HEADER.SizeOfImage, os.path.abspath(self.path))) if self.is_driver: self.init_driver_object() From 760dfe9036696aa0e6125d18bc03e6288cd74c29 Mon Sep 17 00:00:00 2001 From: elicn Date: Sat, 26 Mar 2022 22:09:00 +0300 Subject: [PATCH 263/406] Add missing ucrtbase.dll to the dllscollector script --- examples/scripts/dllscollector.bat | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/scripts/dllscollector.bat b/examples/scripts/dllscollector.bat index 40c9e307c..4ac7fcc6e 100644 --- a/examples/scripts/dllscollector.bat +++ b/examples/scripts/dllscollector.bat @@ -51,6 +51,8 @@ if exist %WINDIR%\SysWOW64\comdlg32.dll xcopy /f /y %WINDIR%\SysWOW64\comdlg32.d if exist %WINDIR%\SysWOW64\shell32.dll xcopy /f /y %WINDIR%\SysWOW64\shell32.dll "examples\rootfs\x86_windows\Windows\System32\" if exist %WINDIR%\SysWOW64\oleaut32.dll xcopy /f /y %WINDIR%\SysWOW64\oleaut32.dll "examples\rootfs\x86_windows\Windows\System32\" if exist %WINDIR%\SysWOW64\vcruntime140.dll xcopy /f /y %WINDIR%\SysWOW64\vcruntime140.dll "examples\rootfs\x86_windows\Windows\System32\" +if exist %WINDIR%\SysWOW64\ucrtbased.dll xcopy /f /y %WINDIR%\SysWOW64\ucrtbased.dll "examples\rootfs\x86_windows\Windows\System32\" +if exist %WINDIR%\SysWOW64\ucrtbase.dll xcopy /f /y %WINDIR%\SysWOW64\ucrtbase.dll "examples\rootfs\x86_windows\Windows\System32\" if exist %WINDIR%\SysWOW64\winhttp.dll xcopy /f /y %WINDIR%\SysWOW64\winhttp.dll "examples\rootfs\x86_windows\Windows\System32\" if exist %WINDIR%\SysWOW64\wininet.dll xcopy /f /y %WINDIR%\SysWOW64\wininet.dll "examples\rootfs\x86_windows\Windows\System32\" if exist %WINDIR%\SysWOW64\ws2_32.dll xcopy /f /y %WINDIR%\SysWOW64\ws2_32.dll "examples\rootfs\x86_windows\Windows\System32\" @@ -86,5 +88,6 @@ if exist %WINDIR%\System32\downlevel\api-ms-win-crt-locale-l1-1-0.dll xcopy /f / if exist %WINDIR%\System32\downlevel\api-ms-win-crt-heap-l1-1-0.dll xcopy /f /y %WINDIR%\System32\downlevel\api-ms-win-crt-heap-l1-1-0.dll "examples\rootfs\x8664_windows\Windows\System32\" if exist %WINDIR%\System32\vcruntime140d.dll xcopy /f /y %WINDIR%\System32\vcruntime140d.dll "examples\rootfs\x8664_windows\Windows\System32\" if exist %WINDIR%\System32\ucrtbased.dll xcopy /f /y %WINDIR%\System32\ucrtbased.dll "examples\rootfs\x8664_windows\Windows\System32\" +if exist %WINDIR%\System32\ucrtbase.dll xcopy /f /y %WINDIR%\System32\ucrtbase.dll "examples\rootfs\x8664_windows\Windows\System32\" exit /b From 456c26df4c16d4cf9256b407e8a7e7f571c75f08 Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 27 Mar 2022 15:54:11 +0300 Subject: [PATCH 264/406] Rewrite dllscollector script --- examples/scripts/dllscollector.bat | 235 +++++++++++++++++------------ 1 file changed, 142 insertions(+), 93 deletions(-) diff --git a/examples/scripts/dllscollector.bat b/examples/scripts/dllscollector.bat index 4ac7fcc6e..cd0fffb09 100644 --- a/examples/scripts/dllscollector.bat +++ b/examples/scripts/dllscollector.bat @@ -1,93 +1,142 @@ -@echo off -REM -REM Example batch script to copy windows required DLLs and registry -REM - -mkdir examples\rootfs\x86_windows\Windows\System32 -mkdir examples\rootfs\x86_windows\Windows\System32\drivers -mkdir examples\rootfs\x86_windows\Windows\registry -mkdir examples\rootfs\x8664_windows\Windows\System32 -mkdir examples\rootfs\x8664_windows\Windows\SysWOW64 -mkdir examples\rootfs\x8664_windows\Windows\registry - -REM -REM Registry -REM -echo f | xcopy /f /y C:\Users\Default\NTUSER.DAT examples\rootfs\x8664_windows\Windows\registry\NTUSER.DAT -reg save hklm\system examples\rootfs\x8664_windows\Windows\registry\SYSTEM -reg save hklm\security examples\rootfs\x8664_windows\Windows\registry\SECURITY -reg save hklm\software examples\rootfs\x8664_windows\Windows\registry\SOFTWARE -reg save hklm\hardware examples\rootfs\x8664_windows\Windows\registry\HARDWARE -reg save hklm\SAM examples\rootfs\x8664_windows\Windows\registry\SAM -xcopy /d /y examples\rootfs\x8664_windows\Windows\registry\* examples\rootfs\x86_windows\Windows\registry\ - -REM -REM Dlls -REM -if exist %WINDIR%\SysWOW64\advapi32.dll xcopy /f /y %WINDIR%\SysWOW64\advapi32.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\rpcrt4.dll xcopy /f /y %WINDIR%\SysWOW64\rpcrt4.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\crypt32.dll xcopy /f /y %WINDIR%\SysWOW64\crypt32.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\iphlpapi.dll xcopy /f /y %WINDIR%\SysWOW64\iphlpapi.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\kernel32.dll xcopy /f /y %WINDIR%\SysWOW64\kernel32.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\KernelBase.dll xcopy /f /y %WINDIR%\SysWOW64\KernelBase.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\mpr.dll xcopy /f /y %WINDIR%\SysWOW64\mpr.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\msvcp60.dll xcopy /f /y %WINDIR%\SysWOW64\msvcp60.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\msvcrt.dll xcopy /f /y %WINDIR%\SysWOW64\msvcrt.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\netapi32.dll xcopy /f /y %WINDIR%\SysWOW64\netapi32.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\ntdll.dll xcopy /f /y %WINDIR%\SysWOW64\ntdll.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\ole32.dll xcopy /f /y %WINDIR%\SysWOW64\ole32.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\urlmon.dll xcopy /f /y %WINDIR%\SysWOW64\urlmon.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\user32.dll xcopy /f /y %WINDIR%\SysWOW64\user32.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\wsock32.dll xcopy /f /y %WINDIR%\SysWOW64\wsock32.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\version.dll xcopy /f /y %WINDIR%\SysWOW64\version.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\winmm.dll xcopy /f /y %WINDIR%\SysWOW64\winmm.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\comctl32.dll xcopy /f /y %WINDIR%\SysWOW64\comctl32.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\wininet.dll xcopy /f /y %WINDIR%\SysWOW64\wininet.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\psapi.dll xcopy /f /y %WINDIR%\SysWOW64\psapi.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\userenv.dll xcopy /f /y %WINDIR%\SysWOW64\userenv.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\uxtheme.dll xcopy /f /y %WINDIR%\SysWOW64\uxtheme.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\gdi32.dll xcopy /f /y %WINDIR%\SysWOW64\gdi32.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\comdlg32.dll xcopy /f /y %WINDIR%\SysWOW64\comdlg32.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\shell32.dll xcopy /f /y %WINDIR%\SysWOW64\shell32.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\oleaut32.dll xcopy /f /y %WINDIR%\SysWOW64\oleaut32.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\vcruntime140.dll xcopy /f /y %WINDIR%\SysWOW64\vcruntime140.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\ucrtbased.dll xcopy /f /y %WINDIR%\SysWOW64\ucrtbased.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\ucrtbase.dll xcopy /f /y %WINDIR%\SysWOW64\ucrtbase.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\winhttp.dll xcopy /f /y %WINDIR%\SysWOW64\winhttp.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\wininet.dll xcopy /f /y %WINDIR%\SysWOW64\wininet.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\ws2_32.dll xcopy /f /y %WINDIR%\SysWOW64\ws2_32.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\msvcr120_clr0400.dll echo f | xcopy /f /y %WINDIR%\SysWOW64\msvcr120_clr0400.dll "examples\rootfs\x86_windows\Windows\System32\msvcr110.dll" -if exist %WINDIR%\SysWOW64\downlevel\api-ms-win-crt-stdio-l1-1-0.dll xcopy /f /y %WINDIR%\SysWOW64\downlevel\api-ms-win-crt-stdio-l1-1-0.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\downlevel\api-ms-win-crt-runtime-l1-1-0.dll xcopy /f /y %WINDIR%\SysWOW64\downlevel\api-ms-win-crt-runtime-l1-1-0.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\downlevel\api-ms-win-crt-math-l1-1-0.dll xcopy /f /y %WINDIR%\SysWOW64\downlevel\api-ms-win-crt-math-l1-1-0.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\downlevel\api-ms-win-crt-locale-l1-1-0.dll xcopy /f /y %WINDIR%\SysWOW64\downlevel\api-ms-win-crt-locale-l1-1-0.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\downlevel\api-ms-win-crt-heap-l1-1-0.dll xcopy /f /y %WINDIR%\SysWOW64\downlevel\api-ms-win-crt-heap-l1-1-0.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\downlevel\api-ms-win-core-synch-l1-2-0.dll xcopy /f /y %WINDIR%\SysWOW64\downlevel\api-ms-win-core-synch-l1-2-0.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\downlevel\api-ms-win-core-fibers-l1-1-1.dll xcopy /f /y %WINDIR%\SysWOW64\downlevel\api-ms-win-core-fibers-l1-1-1.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\downlevel\api-ms-win-core-localization-l1-2-1.dll xcopy /f /y %WINDIR%\SysWOW64\downlevel\api-ms-win-core-localization-l1-2-1.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\downlevel\api-ms-win-core-sysinfo-l1-2-1.dll xcopy /f /y %WINDIR%\SysWOW64\downlevel\api-ms-win-core-sysinfo-l1-2-1.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\shlwapi.dll xcopy /f /y %WINDIR%\SysWOW64\shlwapi.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\SysWOW64\setupapi.dll xcopy /f /y %WINDIR%\SysWOW64\setupapi.dll "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\System32\ntoskrnl.exe xcopy /f /y %WINDIR%\System32\ntoskrnl.exe "examples\rootfs\x86_windows\Windows\System32\" -if exist %WINDIR%\winsxs\amd64_microsoft-windows-printing-xpsprint_31bf3856ad364e35_10.0.17763.194_none_20349c5a971eb293\XpsPrint.dll xcopy /f /y %WINDIR%\winsxs\amd64_microsoft-windows-printing-xpsprint_31bf3856ad364e35_10.0.17763.194_none_20349c5a971eb293\XpsPrint.dll "examples\rootfs\x86_windows\Windows\System32\" - -if exist %WINDIR%\System32\ntoskrnl.exe xcopy /f /y %WINDIR%\System32\ntoskrnl.exe "examples\rootfs\x8664_windows\Windows\System32\" -if exist %WINDIR%\System32\advapi32.dll xcopy /f /y %WINDIR%\System32\advapi32.dll "examples\rootfs\x8664_windows\Windows\System32\" -if exist %WINDIR%\System32\kernel32.dll xcopy /f /y %WINDIR%\System32\kernel32.dll "examples\rootfs\x8664_windows\Windows\System32\" -if exist %WINDIR%\System32\KernelBase.dll xcopy /f /y %WINDIR%\System32\KernelBase.dll "examples\rootfs\x8664_windows\Windows\System32\" -if exist %WINDIR%\System32\msvcrt.dll xcopy /f /y %WINDIR%\System32\msvcrt.dll "examples\rootfs\x8664_windows\Windows\System32\" -if exist %WINDIR%\System32\ntdll.dll xcopy /f /y %WINDIR%\System32\ntdll.dll "examples\rootfs\x8664_windows\Windows\System32\" -if exist %WINDIR%\System32\urlmon.dll xcopy /f /y %WINDIR%\System32\urlmon.dll "examples\rootfs\x8664_windows\Windows\System32\" -if exist %WINDIR%\System32\user32.dll xcopy /f /y %WINDIR%\System32\user32.dll "examples\rootfs\x8664_windows\Windows\System32\" -if exist %WINDIR%\System32\ws2_32.dll xcopy /f /y %WINDIR%\System32\ws2_32.dll "examples\rootfs\x8664_windows\Windows\System32\" -if exist %WINDIR%\System32\vcruntime140.dll xcopy /f /y %WINDIR%\System32\vcruntime140.dll "examples\rootfs\x8664_windows\Windows\System32\" -if exist %WINDIR%\System32\downlevel\api-ms-win-crt-stdio-l1-1-0.dll xcopy /f /y %WINDIR%\System32\downlevel\api-ms-win-crt-stdio-l1-1-0.dll "examples\rootfs\x8664_windows\Windows\System32\" -if exist %WINDIR%\System32\downlevel\api-ms-win-crt-runtime-l1-1-0.dll xcopy /f /y %WINDIR%\System32\downlevel\api-ms-win-crt-runtime-l1-1-0.dll "examples\rootfs\x8664_windows\Windows\System32\" -if exist %WINDIR%\System32\downlevel\api-ms-win-crt-math-l1-1-0.dll xcopy /f /y %WINDIR%\System32\downlevel\api-ms-win-crt-math-l1-1-0.dll "examples\rootfs\x8664_windows\Windows\System32\" -if exist %WINDIR%\System32\downlevel\api-ms-win-crt-locale-l1-1-0.dll xcopy /f /y %WINDIR%\System32\downlevel\api-ms-win-crt-locale-l1-1-0.dll "examples\rootfs\x8664_windows\Windows\System32\" -if exist %WINDIR%\System32\downlevel\api-ms-win-crt-heap-l1-1-0.dll xcopy /f /y %WINDIR%\System32\downlevel\api-ms-win-crt-heap-l1-1-0.dll "examples\rootfs\x8664_windows\Windows\System32\" -if exist %WINDIR%\System32\vcruntime140d.dll xcopy /f /y %WINDIR%\System32\vcruntime140d.dll "examples\rootfs\x8664_windows\Windows\System32\" -if exist %WINDIR%\System32\ucrtbased.dll xcopy /f /y %WINDIR%\System32\ucrtbased.dll "examples\rootfs\x8664_windows\Windows\System32\" -if exist %WINDIR%\System32\ucrtbase.dll xcopy /f /y %WINDIR%\System32\ucrtbase.dll "examples\rootfs\x8664_windows\Windows\System32\" - -exit /b +@ECHO OFF + +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:: Create the emulated Windows directory structure and registry :: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + +:: Host system directories +SET SYSDIR32="%WINDIR%\SysWOW64" +SET SYSDIR64="%WINDIR%\System32" + +:: Qiling rootfs directories +SET QL_WINDIR32="examples\rootfs\x86_windows\Windows" +SET QL_WINDIR64="examples\rootfs\x8664_windows\Windows" + +SET QL_SYSDIR32="%QL_WINDIR32%\System32" +SET QL_SYSDIR64="%QL_WINDIR64%\System32" + +SET QL_REGDIR32="%QL_WINDIR32%\registry" +SET QL_REGDIR64="%QL_WINDIR64%\registry" + +:: Create emulated Windows directory structure +MKDIR %QL_SYSDIR32% +MKDIR %QL_REGDIR32% + +MKDIR %QL_SYSDIR64% +MKDIR %QL_REGDIR64% + +:: Generate emulated Windows registry (requires Administrator privileges) +REG SAVE HKLM\SYSTEM %QL_REGDIR64%\SYSTEM /Y +REG SAVE HKLM\SECURITY %QL_REGDIR64%\SECURITY /Y +REG SAVE HKLM\SOFTWARE %QL_REGDIR64%\SOFTWARE /Y +REG SAVE HKLM\HARDWARE %QL_REGDIR64%\HARDWARE /Y +REG SAVE HKLM\SAM %QL_REGDIR64%\SAM /Y +COPY /B /Y C:\Users\Default\NTUSER.DAT "%QL_REGDIR64%\NTUSER.DAT" + +:: Duplicate generated registry +XCOPY /F /D /Y %QL_REGDIR64%\* %QL_REGDIR32% + +:: Collect 32-bit DLL files +CALL :collect_dll32 advapi32.dll +CALL :collect_dll32 bcrypt.dll +CALL :collect_dll32 cfgmgr32.dll +CALL :collect_dll32 combase.dll +CALL :collect_dll32 comctl32.dll +CALL :collect_dll32 comdlg32.dll +CALL :collect_dll32 crypt32.dll +CALL :collect_dll32 gdi32.dll +CALL :collect_dll32 iphlpapi.dll +CALL :collect_dll32 kernel32.dll +CALL :collect_dll32 KernelBase.dll +CALL :collect_dll32 mpr.dll +CALL :collect_dll32 msvcp_win.dll +CALL :collect_dll32 msvcp60.dll +CALL :collect_dll32 msvcr120_clr0400.dll, msvcr110.dll +CALL :collect_dll32 msvcrt.dll +CALL :collect_dll32 netapi32.dll +CALL :collect_dll32 ntdll.dll +CALL :collect_dll32 ole32.dll +CALL :collect_dll32 oleaut32.dll +CALL :collect_dll32 psapi.dll +CALL :collect_dll32 rpcrt4.dll +CALL :collect_dll32 sechost.dll +CALL :collect_dll32 setupapi.dll +CALL :collect_dll32 shell32.dll +CALL :collect_dll32 shlwapi.dll +CALL :collect_dll32 sspicli.dll +CALL :collect_dll32 ucrtbase.dll +CALL :collect_dll32 ucrtbased.dll +CALL :collect_dll32 urlmon.dll +CALL :collect_dll32 user32.dll +CALL :collect_dll32 userenv.dll +CALL :collect_dll32 uxtheme.dll +CALL :collect_dll32 vcruntime140.dll +CALL :collect_dll32 version.dll +CALL :collect_dll32 win32u.dll +CALL :collect_dll32 winhttp.dll +CALL :collect_dll32 wininet.dll +CALL :collect_dll32 wininet.dll +CALL :collect_dll32 winmm.dll +CALL :collect_dll32 ws2_32.dll +CALL :collect_dll32 wsock32.dll + +CALL :collect_dll32 downlevel\api-ms-win-core-fibers-l1-1-1.dll +CALL :collect_dll32 downlevel\api-ms-win-core-localization-l1-2-1.dll +CALL :collect_dll32 downlevel\api-ms-win-core-synch-l1-2-0.dll +CALL :collect_dll32 downlevel\api-ms-win-core-sysinfo-l1-2-1.dll +CALL :collect_dll32 downlevel\api-ms-win-crt-heap-l1-1-0.dll +CALL :collect_dll32 downlevel\api-ms-win-crt-locale-l1-1-0.dll +CALL :collect_dll32 downlevel\api-ms-win-crt-math-l1-1-0.dll +CALL :collect_dll32 downlevel\api-ms-win-crt-runtime-l1-1-0.dll +CALL :collect_dll32 downlevel\api-ms-win-crt-stdio-l1-1-0.dll + +:: Collect 64-bit DLL files +CALL :collect_dll64 advapi32.dll +CALL :collect_dll64 gdi32.dll +CALL :collect_dll64 kernel32.dll +CALL :collect_dll64 KernelBase.dll +CALL :collect_dll64 msvcrt.dll +CALL :collect_dll64 ntdll.dll +CALL :collect_dll64 ntoskrnl.exe +CALL :collect_dll64 ucrtbase.dll +CALL :collect_dll64 ucrtbased.dll +CALL :collect_dll64 urlmon.dll +CALL :collect_dll64 user32.dll +CALL :collect_dll64 vcruntime140.dll +CALL :collect_dll64 vcruntime140d.dll +CALL :collect_dll64 win32u.dll +CALL :collect_dll64 ws2_32.dll + +CALL :collect_dll64 downlevel\api-ms-win-crt-heap-l1-1-0.dll +CALL :collect_dll64 downlevel\api-ms-win-crt-locale-l1-1-0.dll +CALL :collect_dll64 downlevel\api-ms-win-crt-math-l1-1-0.dll +CALL :collect_dll64 downlevel\api-ms-win-crt-runtime-l1-1-0.dll +CALL :collect_dll64 downlevel\api-ms-win-crt-stdio-l1-1-0.dll + +:: Collect extras +CALL :collect %SYSDIR64%, ntoskrnl.exe, %QL_SYSDIR32% + +:: All done! +EXIT /B 0 + +:: Functions definitions +:normpath +SET %1=%~dpfn2 + +:collect +CALL :normpath SRC, %~1\%~2 +CALL :normpath DST, %~3\%~4 + +IF EXIST %SRC% ( + ECHO %SRC% -^> %DST% + COPY /B /Y "%SRC%" "%DST%" >NUL +) +EXIT /B + +:collect_dll64 +CALL :collect %SYSDIR64%, %~1, %QL_SYSDIR64%, %~2 +EXIT /B + +:collect_dll32 +CALL :collect %SYSDIR32%, %~1, %QL_SYSDIR32%, %~2 +EXIT /B From 2954b73cf658ce029279edcb293f2653b0486bd5 Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 27 Mar 2022 16:21:41 +0300 Subject: [PATCH 265/406] Add missing exit command --- examples/scripts/dllscollector.bat | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/scripts/dllscollector.bat b/examples/scripts/dllscollector.bat index cd0fffb09..51a5608ee 100644 --- a/examples/scripts/dllscollector.bat +++ b/examples/scripts/dllscollector.bat @@ -122,6 +122,7 @@ EXIT /B 0 :: Functions definitions :normpath SET %1=%~dpfn2 +EXIT /B :collect CALL :normpath SRC, %~1\%~2 From e9ff3596fe691dd26ecc873fe95052c8c4d00067 Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 27 Mar 2022 19:00:51 +0300 Subject: [PATCH 266/406] Create missing drivers dir --- examples/scripts/dllscollector.bat | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/scripts/dllscollector.bat b/examples/scripts/dllscollector.bat index 51a5608ee..0161e7241 100644 --- a/examples/scripts/dllscollector.bat +++ b/examples/scripts/dllscollector.bat @@ -19,11 +19,13 @@ SET QL_REGDIR32="%QL_WINDIR32%\registry" SET QL_REGDIR64="%QL_WINDIR64%\registry" :: Create emulated Windows directory structure -MKDIR %QL_SYSDIR32% MKDIR %QL_REGDIR32% +MKDIR %QL_SYSDIR32% +MKDIR "%QL_SYSDIR32%\drivers" -MKDIR %QL_SYSDIR64% MKDIR %QL_REGDIR64% +MKDIR %QL_SYSDIR64% +MKDIR "%QL_SYSDIR64%\drivers" :: Generate emulated Windows registry (requires Administrator privileges) REG SAVE HKLM\SYSTEM %QL_REGDIR64%\SYSTEM /Y From 307d9fd545b1b315e362a3df13c042395986bf45 Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 30 Mar 2022 20:52:36 +0300 Subject: [PATCH 267/406] Add winsys property to Windows OS --- qiling/loader/pe.py | 5 +++-- qiling/os/windows/dlls/kernel32/sysinfoapi.py | 2 +- qiling/os/windows/windows.py | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/qiling/loader/pe.py b/qiling/loader/pe.py index d275e02da..31b0db9f3 100644 --- a/qiling/loader/pe.py +++ b/qiling/loader/pe.py @@ -74,7 +74,8 @@ def load_dll(self, name: bytes, driver: bool = False) -> int: if not is_file_library(dll_name): dll_name = f'{dll_name}.dll' - path = os.path.join(self.ql.rootfs, 'Windows', 'System32', dll_name) + path = os.path.join(self.ql.os.winsys, dll_name) + path = self.ql.os.path.transform_to_real_path(path) if dll_name.startswith('api-ms-win-'): # Usually we should not reach this point and instead imports from such DLLs should be redirected earlier @@ -376,7 +377,7 @@ def populate_unistr(obj, s: str) -> None: 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.windir, 'System32', dll_name)) + populate_unistr(entry_obj.FullDllName, ntpath.join(self.ql.os.winsys, dll_name)) populate_unistr(entry_obj.BaseDllName, dll_name) # Flink diff --git a/qiling/os/windows/dlls/kernel32/sysinfoapi.py b/qiling/os/windows/dlls/kernel32/sysinfoapi.py index 8eb577206..fc2073eb4 100644 --- a/qiling/os/windows/dlls/kernel32/sysinfoapi.py +++ b/qiling/os/windows/dlls/kernel32/sysinfoapi.py @@ -104,7 +104,7 @@ def __GetSystemDirectory(ql: Qiling, address: int, params, wstring: bool): lpBuffer = params["lpBuffer"] enc = 'utf-16le' if wstring else 'utf-8' - res = ntpath.join(ql.os.windir, 'System32') + res = ql.os.winsys ql.mem.write(lpBuffer, f'{res}\x00'.encode(enc)) diff --git a/qiling/os/windows/windows.py b/qiling/os/windows/windows.py index 72e896428..35e5afece 100644 --- a/qiling/os/windows/windows.py +++ b/qiling/os/windows/windows.py @@ -74,6 +74,7 @@ def __make_fcall_selector(atype: QL_ARCH) -> Callable[[int], QlFunctionCall]: username = self.profile.get('USER', 'username') self.windir = ntpath.join(sysdrv, windir) + self.winsys = ntpath.join(sysdrv, windir, 'System32') self.userprofile = ntpath.join(sysdrv, 'Users', username) self.username = username From 3c1a8a7255255e2c432e176a45a75e97a131e443 Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 30 Mar 2022 20:57:15 +0300 Subject: [PATCH 268/406] Simplify load_dll signature --- qiling/loader/pe.py | 34 +++++++++---------- .../os/windows/dlls/kernel32/libloaderapi.py | 4 +-- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/qiling/loader/pe.py b/qiling/loader/pe.py index 31b0db9f3..3b876c3cc 100644 --- a/qiling/loader/pe.py +++ b/qiling/loader/pe.py @@ -64,8 +64,8 @@ class Process: def __init__(self, ql: Qiling): self.ql = ql - def load_dll(self, name: bytes, driver: bool = False) -> int: - dll_name = name.decode().lower() + def load_dll(self, name: str, is_driver: bool = False) -> int: + dll_name = name.lower() if dll_name.startswith('c:\\'): path = self.ql.os.path.transform_to_real_path(dll_name) @@ -200,13 +200,13 @@ def load_dll(self, name: bytes, driver: bool = False) -> int: self.images.append(Image(dll_base, dll_base + dll_len, os.path.abspath(path))) # if this is NOT a driver, add dll to ldr data - if not driver: + if not is_driver: self.add_ldr_data_table_entry(dll_name) if not cached or not loaded: # parse directory entry import self.ql.log.debug(f'Init imports for {dll_name}') - self.init_imports(dll, driver) + self.init_imports(dll, is_driver) # calling DllMain is essential for dlls to initialize properly. however # DllMain of system libraries may fail due to incomplete or inaccurate @@ -497,7 +497,7 @@ def init_imports(self, pe: pefile.PE, is_driver: bool): if unbound_imports: # Only load dll if encountered unbound symbol if not redirected: - dll_base = self.load_dll(entry.dll, is_driver) + dll_base = self.load_dll(entry.dll.decode(), is_driver) if not dll_base: continue @@ -618,17 +618,17 @@ def __init__(self, ql: Qiling, libcache: bool): self.libcache = QlPeCache() if libcache else None def run(self): - self.init_dlls = [ - b'ntdll.dll', - b'kernel32.dll', - b'user32.dll' - ] - - self.sys_dlls = [ - b'ntdll.dll', - b'kernel32.dll', - b'ucrtbase.dll' - ] + self.init_dlls = ( + 'ntdll.dll', + 'kernel32.dll', + 'user32.dll' + ) + + self.sys_dlls = ( + 'ntdll.dll', + 'kernel32.dll', + 'ucrtbase.dll' + ) if self.ql.code: pe = None @@ -814,7 +814,7 @@ def load(self, pe: Optional[pefile.PE]): # load dlls for each in self.init_dlls: - super().load_dll(each) + super().load_dll(each, self.is_driver) # load shellcode self.ql.mem.write(self.entry_point, self.ql.code) diff --git a/qiling/os/windows/dlls/kernel32/libloaderapi.py b/qiling/os/windows/dlls/kernel32/libloaderapi.py index 812a72351..739838c78 100644 --- a/qiling/os/windows/dlls/kernel32/libloaderapi.py +++ b/qiling/os/windows/dlls/kernel32/libloaderapi.py @@ -189,12 +189,12 @@ def _LoadLibrary(ql: Qiling, address: int, params): # Loading self return ql.loader.pe_image_address - return ql.loader.load_dll(lpLibFileName.encode()) + return ql.loader.load_dll(lpLibFileName) def _LoadLibraryEx(ql: Qiling, address: int, params): lpLibFileName = params["lpLibFileName"] - return ql.loader.load_dll(lpLibFileName.encode()) + return ql.loader.load_dll(lpLibFileName) # HMODULE LoadLibraryA( # LPCSTR lpLibFileName From 5ff3d9af4b003c0b80d0b221a0fb2d4e9f23cd98 Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 30 Mar 2022 22:03:42 +0300 Subject: [PATCH 269/406] Let OS determine registry hive path --- qiling/os/windows/registry.py | 28 ++++++++++------------------ qiling/os/windows/windows.py | 3 ++- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/qiling/os/windows/registry.py b/qiling/os/windows/registry.py index 8d190b150..907a92be9 100644 --- a/qiling/os/windows/registry.py +++ b/qiling/os/windows/registry.py @@ -159,30 +159,22 @@ def write(self, key: str, subkey: str, reg_type: int, data: Union[str, bytes, in class RegistryManager: - def __init__(self, ql: Qiling, hive: Optional[str] = None): + def __init__(self, ql: Qiling, hivedir: str): self.ql = ql - - log_registry_dir = ql.rootfs or 'qlog' - self.regdiff = os.path.join(log_registry_dir, 'registry', f'{ql.targetname}_diff.json') - - # hive dir - if hive is None: - hive = os.path.join(ql.rootfs, 'Windows', 'registry') - - if not os.path.exists(hive) and not ql.code: - raise QlErrorFileNotFound(f'registry files not found in "{hive}"!') - - ql.log.debug(f'Windows Registry PATH: {hive}') + self.regdiff = os.path.join(ql.rootfs, 'registry', f'{ql.targetname}_diff.json') # if conf file does not exist, create its directory to enable saving later on if not os.path.exists(self.regdiff): - try: - os.makedirs(os.path.dirname(self.regdiff), 0o755) - except: - pass + os.makedirs(os.path.dirname(self.regdiff), 0o755, exist_ok=True) + + if not ql.code: + if not os.path.exists(hivedir): + raise QlErrorFileNotFound(f'Windows registry directory not found: "{hivedir}"!') + + ql.log.debug(f'Loading Windows registry hive from {hivedir}') try: - self.reghive = RegHive(hive) + self.reghive = RegHive(hivedir) except FileNotFoundError: if not ql.code: QlErrorFileNotFound("WARNING: Registry files not found!") diff --git a/qiling/os/windows/windows.py b/qiling/os/windows/windows.py index 35e5afece..5345ecd45 100644 --- a/qiling/os/windows/windows.py +++ b/qiling/os/windows/windows.py @@ -123,9 +123,10 @@ def setupGDT(self): def setupComponents(self): # handle manager + reghive = self.path.transform_to_real_path(ntpath.join(self.windir, 'registry')) self.handle_manager = handle.HandleManager() # registry manger - self.registry_manager = registry.RegistryManager(self.ql) + self.registry_manager = registry.RegistryManager(self.ql, reghive) # clipboard self.clipboard = clipboard.Clipboard(self) # fibers From 3a857e25a942ed72c58750697bdf198b9cf5dead Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 30 Mar 2022 22:10:57 +0300 Subject: [PATCH 270/406] Refreshed path module --- qiling/os/path.py | 84 ++++++++-------- tests/test_pathutils.py | 214 +++++++++++++++++++++------------------- 2 files changed, 157 insertions(+), 141 deletions(-) diff --git a/qiling/os/path.py b/qiling/os/path.py index 85c67d9f1..22cc0c77e 100644 --- a/qiling/os/path.py +++ b/qiling/os/path.py @@ -22,6 +22,25 @@ def __init__(self, ql: Qiling, cwd: str): self.ql = ql self._cwd = cwd + self.convert_path_handler = QlPathManager.__select_convert_path_handler(ql.ostype, ql.host.os) + + @staticmethod + def __select_convert_path_handler(emu_os: QL_OS, host_os: QL_OS): + # emulated os and hosting platform are of the same type + if (emu_os == host_os) or (emu_os in QL_OS_POSIX and host_os in QL_OS_POSIX): + handler = QlPathManager.convert_for_native_os + + elif emu_os in QL_OS_POSIX and host_os == QL_OS.WINDOWS: + handler = QlPathManager.convert_posix_to_win32 + + elif emu_os == QL_OS.WINDOWS and host_os in QL_OS_POSIX: + handler = QlPathManager.convert_win32_to_posix + + else: + handler = QlPathManager.convert_for_native_os + + return handler + @property def cwd(self) -> str: return self._cwd @@ -34,10 +53,7 @@ def cwd(self, c: str) -> None: self._cwd = c @staticmethod - def normalize(path: Union[Path, PurePath]) -> Union[Path, PurePath]: - # expected types: PosixPath, PurePosixPath, WindowsPath, PureWindowsPath - assert isinstance(path, (Path, PurePath)), f'did not expect {type(path).__name__!r} here' - + def normalize(path: PurePath) -> PurePath: normalized_path = type(path)() # remove anchor (necessary for Windows UNC paths) and convert to relative path @@ -57,35 +73,36 @@ def normalize(path: Union[Path, PurePath]) -> Union[Path, PurePath]: return normalized_path @staticmethod - def convert_win32_to_posix(rootfs: Union[str, Path], cwd: str, path: str) -> Path: - _rootfs = Path(rootfs) + def convert_win32_to_posix(rootfs: Union[str, PurePosixPath], cwd: str, path: str) -> PurePosixPath: + _rootfs = PurePosixPath(rootfs) _cwd = PurePosixPath(cwd[1:]) + _path = PureWindowsPath(path) # Things are complicated here. # See https://docs.microsoft.com/zh-cn/windows/win32/fileio/naming-a-file?redirectedfrom=MSDN - if PureWindowsPath(path).is_absolute(): + if _path.is_absolute(): if (len(path) >= 2 and path.startswith(r'\\')) or \ (len(path) >= 3 and path[0].isalpha() and path[1:3] == ':\\'): # \\.\PhysicalDrive0 or \\Server\Share\Directory or X:\ # UNC path should be handled in fs mapping. If not, append it to rootfs directly. - pw = PureWindowsPath(path) - result = _rootfs / QlPathManager.normalize(pw) + + result = _rootfs / QlPathManager.normalize(_path) else: # code should never reach here. - result = _rootfs / QlPathManager.normalize(path) + raise RuntimeError() else: if len(path) >= 3 and path[:3] == r'\\?' or path[:3] == r'\??': # \??\ or \\?\ or \Device\.. # Similair to \\.\, it should be handled in fs mapping. - pw = PureWindowsPath(path) - result = _rootfs / QlPathManager.normalize(_cwd / pw.relative_to(pw.anchor).as_posix()) + + result = _rootfs / QlPathManager.normalize(_cwd / _path.relative_to(_path.anchor).as_posix()) else: # a normal relative path - result = _rootfs / QlPathManager.normalize(_cwd / PureWindowsPath(path).as_posix()) + result = _rootfs / QlPathManager.normalize(_cwd / _path.as_posix()) return result @staticmethod - def convert_posix_to_win32(rootfs: Union[str, Path], cwd: str, path: str) -> Path: - _rootfs = Path(rootfs) + def convert_posix_to_win32(rootfs: Union[str, PureWindowsPath], cwd: str, path: str) -> PureWindowsPath: + _rootfs = PureWindowsPath(rootfs) _cwd = PurePosixPath(cwd[1:]) _path = PurePosixPath(path) @@ -95,52 +112,39 @@ def convert_posix_to_win32(rootfs: Union[str, Path], cwd: str, path: str) -> Pat return _rootfs / QlPathManager.normalize(_cwd / _path) @staticmethod - def convert_for_native_os(rootfs: Union[str, Path], cwd: str, path: str) -> Path: - _rootfs = Path(rootfs) + def convert_for_native_os(rootfs: Union[str, PurePath], cwd: str, path: str) -> PurePath: + _rootfs = PurePath(rootfs) _cwd = PurePosixPath(cwd[1:]) - _path = Path(path) + _path = PurePath(path) if _path.is_absolute(): return _rootfs / QlPathManager.normalize(_path) else: return _rootfs / QlPathManager.normalize(_cwd / _path.as_posix()) - def convert_path(self, rootfs: Union[str, Path], cwd: str, path: str) -> Path: - emulated_os = self.ql.ostype - hosting_os = self.ql.host.os - - # emulated os and hosting platform are of the same type - if (emulated_os == hosting_os) or (emulated_os in QL_OS_POSIX and hosting_os in QL_OS_POSIX): - return QlPathManager.convert_for_native_os(rootfs, cwd, path) - - elif emulated_os in QL_OS_POSIX and hosting_os == QL_OS.WINDOWS: - return QlPathManager.convert_posix_to_win32(rootfs, cwd, path) - - elif emulated_os == QL_OS.WINDOWS and hosting_os in QL_OS_POSIX: - return QlPathManager.convert_win32_to_posix(rootfs, cwd, path) - - else: - return QlPathManager.convert_for_native_os(rootfs, cwd, path) + def convert_path(self, rootfs: str, cwd: str, path: str) -> PurePath: + return self.convert_path_handler(rootfs, cwd, path) def transform_to_link_path(self, path: str) -> str: real_path = self.convert_path(self.ql.rootfs, self.cwd, path) - return str(real_path.absolute()) + return str(Path(real_path).absolute()) def transform_to_real_path(self, path: str) -> str: # TODO: We really need a virtual file system. real_path = self.convert_path(self.ql.rootfs, self.cwd, path) if os.path.islink(real_path): - link_path = Path(os.readlink(real_path)) + link_path = os.readlink(real_path) real_path = self.convert_path(os.path.dirname(real_path), "/", link_path) if os.path.islink(real_path): - real_path = self.transform_to_real_path(real_path) + real_path = self.transform_to_real_path(str(real_path)) # resolve multilevel symbolic link if not os.path.exists(real_path): + link_path = PurePath(link_path) path_dirs = link_path.parts if link_path.is_absolute(): @@ -150,13 +154,13 @@ def transform_to_real_path(self, path: str) -> str: path_prefix = os.path.sep.join(path_dirs[:i+1]) real_path_prefix = self.transform_to_real_path(path_prefix) path_remain = os.path.sep.join(path_dirs[i+1:]) - real_path = Path(os.path.join(real_path_prefix, path_remain)) + real_path = os.path.join(real_path_prefix, path_remain) if os.path.exists(real_path): break - return str(real_path.absolute()) + return str(Path(real_path).absolute()) # The `relative path` here refers to the path which is relative to the rootfs. def transform_to_relative_path(self, path: str) -> str: - return str(Path(self.cwd) / path) + return str(PurePath(self.cwd) / path) diff --git a/tests/test_pathutils.py b/tests/test_pathutils.py index 3954203c7..d78176741 100644 --- a/tests/test_pathutils.py +++ b/tests/test_pathutils.py @@ -3,125 +3,137 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -import pathlib, sys, unittest +import sys, unittest +from pathlib import PurePath, PurePosixPath, PureWindowsPath sys.path.append("..") from qiling import Qiling -from qiling.const import QL_OS, QL_VERBOSE +from qiling.const import QL_OS, QL_OS_POSIX, QL_VERBOSE from qiling.os.path import QlPathManager +# define a few aliases +nt_to_posix = QlPathManager.convert_win32_to_posix +posix_to_nt = QlPathManager.convert_posix_to_win32 +to_native = QlPathManager.convert_for_native_os + class TestPathUtils(unittest.TestCase): def test_convert_win32_to_posix(self): - rootfs = pathlib.Path("../examples/rootfs/x8664_windows").resolve() - - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "\\\\.\\PhysicalDrive0\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "\\\\.\\PhysicalDrive0\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "\\\\.\\PhysicalDrive0\\..\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "\\\\.\\PhysicalDrive0\\..\\xxxx\\..\\test"))) - - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "\\\\hostname\\share\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "\\\\hostname\\share\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "\\\\hostname\\share\\..\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "\\\\hostname\\share\\..\\xxxx\\..\\test"))) - - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "\\\\.\\BootPartition\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "\\\\.\\BootPartition\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "\\\\.\\BootPartition\\..\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "\\\\.\\BootPartition\\..\\xxxx\\..\\test"))) - - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "C:\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "C:\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "C:\\..\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "C:\\..\\xxxx\\..\\test"))) - - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "\\..\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "\\..\\xxxx\\..\\test"))) - - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "..\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/", "..\\xxxx\\..\\test"))) - - self.assertEqual(str(rootfs / "xxxx" / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/xxxx", "test"))) - self.assertEqual(str(rootfs / "xxxx" / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/xxxx/yyyy", "..\\test"))) - self.assertEqual(str(rootfs / "xxxx" / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/xxxx/yyyy/zzzz", "..\\..\\test"))) - self.assertEqual(str(rootfs / "xxxx" / "test"), str(QlPathManager.convert_win32_to_posix(rootfs, "/xxxx/yyyy", "..\\xxxx\\..\\test"))) + rootfs = PurePosixPath(r'../examples/rootfs/x8664_windows') + + self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "\\\\.\\PhysicalDrive0\\test"))) + self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "\\\\.\\PhysicalDrive0\\..\\test"))) + self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "\\\\.\\PhysicalDrive0\\..\\..\\test"))) + self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "\\\\.\\PhysicalDrive0\\..\\xxxx\\..\\test"))) + + self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "\\\\hostname\\share\\test"))) + self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "\\\\hostname\\share\\..\\test"))) + self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "\\\\hostname\\share\\..\\..\\test"))) + self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "\\\\hostname\\share\\..\\xxxx\\..\\test"))) + + self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "\\\\.\\BootPartition\\test"))) + self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "\\\\.\\BootPartition\\..\\test"))) + self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "\\\\.\\BootPartition\\..\\..\\test"))) + self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "\\\\.\\BootPartition\\..\\xxxx\\..\\test"))) + + self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "C:\\test"))) + self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "C:\\..\\test"))) + self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "C:\\..\\..\\test"))) + self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "C:\\..\\xxxx\\..\\test"))) + + self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "\\test"))) + self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "\\..\\test"))) + self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "\\..\\..\\test"))) + self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "\\..\\xxxx\\..\\test"))) + + self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "test"))) + self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "..\\test"))) + self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "..\\..\\test"))) + self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "..\\xxxx\\..\\test"))) + + self.assertEqual(str(rootfs / "xxxx" / "test"), str(nt_to_posix(rootfs, "/xxxx", "test"))) + self.assertEqual(str(rootfs / "xxxx" / "test"), str(nt_to_posix(rootfs, "/xxxx/yyyy", "..\\test"))) + self.assertEqual(str(rootfs / "xxxx" / "test"), str(nt_to_posix(rootfs, "/xxxx/yyyy/zzzz", "..\\..\\test"))) + self.assertEqual(str(rootfs / "xxxx" / "test"), str(nt_to_posix(rootfs, "/xxxx/yyyy", "..\\xxxx\\..\\test"))) def test_convert_posix_to_win32(self): - rootfs = pathlib.Path("../examples/rootfs/x8664_linux").resolve() + rootfs = PureWindowsPath(r'../examples/rootfs/x8664_linux') - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_posix_to_win32(rootfs, "/", "/test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_posix_to_win32(rootfs, "/", "/../test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_posix_to_win32(rootfs, "/", "/../../test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_posix_to_win32(rootfs, "/", "/../xxxx/../test"))) + self.assertEqual(str(rootfs / "test"), str(posix_to_nt(rootfs, "/", "/test"))) + self.assertEqual(str(rootfs / "test"), str(posix_to_nt(rootfs, "/", "/../test"))) + self.assertEqual(str(rootfs / "test"), str(posix_to_nt(rootfs, "/", "/../../test"))) + self.assertEqual(str(rootfs / "test"), str(posix_to_nt(rootfs, "/", "/../xxxx/../test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_posix_to_win32(rootfs, "/", "test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_posix_to_win32(rootfs, "/", "../test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_posix_to_win32(rootfs, "/", "../../test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_posix_to_win32(rootfs, "/", "../xxxx/../test"))) + self.assertEqual(str(rootfs / "test"), str(posix_to_nt(rootfs, "/", "test"))) + self.assertEqual(str(rootfs / "test"), str(posix_to_nt(rootfs, "/", "../test"))) + self.assertEqual(str(rootfs / "test"), str(posix_to_nt(rootfs, "/", "../../test"))) + self.assertEqual(str(rootfs / "test"), str(posix_to_nt(rootfs, "/", "../xxxx/../test"))) - self.assertEqual(str(rootfs / "xxxx" / "test"), str(QlPathManager.convert_posix_to_win32(rootfs, "/xxxx", "test"))) - self.assertEqual(str(rootfs / "xxxx" / "test"), str(QlPathManager.convert_posix_to_win32(rootfs, "/xxxx/yyyy", "../test"))) - self.assertEqual(str(rootfs / "xxxx" / "test"), str(QlPathManager.convert_posix_to_win32(rootfs, "/xxxx/yyyy/zzzz", "../../test"))) - self.assertEqual(str(rootfs / "xxxx" / "test"), str(QlPathManager.convert_posix_to_win32(rootfs, "/xxxx/yyyy", "../xxxx/../test"))) + self.assertEqual(str(rootfs / "xxxx" / "test"), str(posix_to_nt(rootfs, "/xxxx", "test"))) + self.assertEqual(str(rootfs / "xxxx" / "test"), str(posix_to_nt(rootfs, "/xxxx/yyyy", "../test"))) + self.assertEqual(str(rootfs / "xxxx" / "test"), str(posix_to_nt(rootfs, "/xxxx/yyyy/zzzz", "../../test"))) + self.assertEqual(str(rootfs / "xxxx" / "test"), str(posix_to_nt(rootfs, "/xxxx/yyyy", "../xxxx/../test"))) def test_convert_for_native_os(self): ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_hello_static"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG) if ql.host.os == QL_OS.WINDOWS: - rootfs = pathlib.Path("../examples/rootfs/x8664_windows").resolve() - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "\\\\.\\PhysicalDrive0\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "\\\\.\\PhysicalDrive0\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "\\\\.\\PhysicalDrive0\\..\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "\\\\.\\PhysicalDrive0\\..\\xxxx\\..\\test"))) - - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "\\\\hostname\\share\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "\\\\hostname\\share\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "\\\\hostname\\share\\..\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "\\\\hostname\\share\\..\\xxxx\\..\\test"))) - - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "\\\\.\\BootPartition\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "\\\\.\\BootPartition\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "\\\\.\\BootPartition\\..\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "\\\\.\\BootPartition\\..\\xxxx\\..\\test"))) - - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "C:\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "C:\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "C:\\..\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "C:\\..\\xxxx\\..\\test"))) - - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "\\..\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "\\..\\xxxx\\..\\test"))) - - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "..\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "..\\xxxx\\..\\test"))) - - self.assertEqual(str(rootfs / "xxxx" / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/xxxx", "test"))) - self.assertEqual(str(rootfs / "xxxx" / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/xxxx/yyyy", "..\\test"))) - self.assertEqual(str(rootfs / "xxxx" / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/xxxx/yyyy/zzzz", "..\\..\\test"))) - self.assertEqual(str(rootfs / "xxxx" / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/xxxx/yyyy", "..\\xxxx\\..\\test"))) + rootfs = PurePath(r'../examples/rootfs/x8664_windows') + + self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "\\\\.\\PhysicalDrive0\\test"))) + self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "\\\\.\\PhysicalDrive0\\..\\test"))) + self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "\\\\.\\PhysicalDrive0\\..\\..\\test"))) + self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "\\\\.\\PhysicalDrive0\\..\\xxxx\\..\\test"))) + + self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "\\\\hostname\\share\\test"))) + self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "\\\\hostname\\share\\..\\test"))) + self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "\\\\hostname\\share\\..\\..\\test"))) + self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "\\\\hostname\\share\\..\\xxxx\\..\\test"))) + + self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "\\\\.\\BootPartition\\test"))) + self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "\\\\.\\BootPartition\\..\\test"))) + self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "\\\\.\\BootPartition\\..\\..\\test"))) + self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "\\\\.\\BootPartition\\..\\xxxx\\..\\test"))) + + self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "C:\\test"))) + self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "C:\\..\\test"))) + self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "C:\\..\\..\\test"))) + self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "C:\\..\\xxxx\\..\\test"))) + + self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "\\test"))) + self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "\\..\\test"))) + self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "\\..\\..\\test"))) + self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "\\..\\xxxx\\..\\test"))) + + self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "test"))) + self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "..\\test"))) + self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "..\\..\\test"))) + self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "..\\xxxx\\..\\test"))) + + self.assertEqual(str(rootfs / "xxxx" / "test"), str(to_native(rootfs, "/xxxx", "test"))) + self.assertEqual(str(rootfs / "xxxx" / "test"), str(to_native(rootfs, "/xxxx/yyyy", "..\\test"))) + self.assertEqual(str(rootfs / "xxxx" / "test"), str(to_native(rootfs, "/xxxx/yyyy/zzzz", "..\\..\\test"))) + self.assertEqual(str(rootfs / "xxxx" / "test"), str(to_native(rootfs, "/xxxx/yyyy", "..\\xxxx\\..\\test"))) + + elif ql.host.os in QL_OS_POSIX: + rootfs = PurePath(r'../examples/rootfs/x8664_linux') + + self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "/test"))) + self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "/../test"))) + self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "/../../test"))) + self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "/../xxxx/../test"))) + + self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "test"))) + self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "../test"))) + self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "../../test"))) + self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "../xxxx/../test"))) + + self.assertEqual(str(rootfs / "xxxx" / "test"), str(to_native(rootfs, "/xxxx", "test"))) + self.assertEqual(str(rootfs / "xxxx" / "test"), str(to_native(rootfs, "/xxxx/yyyy", "../test"))) + self.assertEqual(str(rootfs / "xxxx" / "test"), str(to_native(rootfs, "/xxxx/yyyy/zzzz", "../../test"))) + self.assertEqual(str(rootfs / "xxxx" / "test"), str(to_native(rootfs, "/xxxx/yyyy", "../xxxx/../test"))) + else: - rootfs = pathlib.Path("../examples/rootfs/x8664_linux").resolve() - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "/test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "/../test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "/../../test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "/../xxxx/../test"))) - - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "../test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "../../test"))) - self.assertEqual(str(rootfs / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/", "../xxxx/../test"))) - - self.assertEqual(str(rootfs / "xxxx" / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/xxxx", "test"))) - self.assertEqual(str(rootfs / "xxxx" / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/xxxx/yyyy", "../test"))) - self.assertEqual(str(rootfs / "xxxx" / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/xxxx/yyyy/zzzz", "../../test"))) - self.assertEqual(str(rootfs / "xxxx" / "test"), str(QlPathManager.convert_for_native_os(rootfs, "/xxxx/yyyy", "../xxxx/../test"))) + raise NotImplementedError('unexpected hosting os') del ql From a7e5dd053f9f669dc11682909dcb5adf39f0a1e0 Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 30 Mar 2022 22:14:11 +0300 Subject: [PATCH 271/406] Some changes to improve clarity --- qiling/loader/pe.py | 12 +++++++----- qiling/os/windows/registry.py | 6 +++--- qiling/os/windows/windows.py | 11 ++++------- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/qiling/loader/pe.py b/qiling/loader/pe.py index 3b876c3cc..8c11712cb 100644 --- a/qiling/loader/pe.py +++ b/qiling/loader/pe.py @@ -65,6 +65,7 @@ def __init__(self, ql: Qiling): self.ql = ql def load_dll(self, name: str, is_driver: bool = False) -> int: + # BUG: that lowers the whole path, which may not be found on a Linux host where paths are case sensitive dll_name = name.lower() if dll_name.startswith('c:\\'): @@ -614,7 +615,6 @@ def __init__(self, ql: Qiling, libcache: bool): self.ql = ql self.path = self.ql.path - self.is_driver = False self.libcache = QlPeCache() if libcache else None def run(self): @@ -632,6 +632,7 @@ def run(self): if self.ql.code: pe = None + self.is_driver = False else: pe = pefile.PE(self.path, fast_load=True) self.is_driver = pe.is_driver() @@ -661,7 +662,8 @@ def run(self): self.pe_image_size = 0 self.dll_size = 0 self.dll_last_address = self.dll_address - # compatible with ql.do_bin_patch() + + # not used, but here to remain compatible with ql.do_bin_patch self.load_address = 0 # self.ql.os.setupComponents() @@ -798,6 +800,9 @@ def load(self, pe: Optional[pefile.PE]): self.init_peb() self.init_ldr_data() + # write shellcode to memory + self.ql.mem.write(self.entry_point, self.ql.code) + top_of_stack = self.stack_address + self.stack_size if self.ql.arch.type == QL_ARCH.X86: @@ -816,9 +821,6 @@ def load(self, pe: Optional[pefile.PE]): for each in self.init_dlls: super().load_dll(each, self.is_driver) - # load shellcode - self.ql.mem.write(self.entry_point, self.ql.code) - # move entry_point to ql.os self.ql.os.entry_point = self.entry_point self.init_sp = self.ql.arch.regs.arch_sp diff --git a/qiling/os/windows/registry.py b/qiling/os/windows/registry.py index 907a92be9..1b923361d 100644 --- a/qiling/os/windows/registry.py +++ b/qiling/os/windows/registry.py @@ -177,16 +177,16 @@ def __init__(self, ql: Qiling, hivedir: str): self.reghive = RegHive(hivedir) except FileNotFoundError: if not ql.code: - QlErrorFileNotFound("WARNING: Registry files not found!") + QlErrorFileNotFound("Windows registry hive not found") except Exception: if not ql.code: - QlErrorFileNotFound("WARNING: Registry files format error") + QlErrorFileNotFound("Windows registry hive format error") try: self.regconf = RegConf(self.regdiff) except json.decoder.JSONDecodeError: - raise QlErrorJsonDecode("Windows Registry JSON decode error") + raise QlErrorJsonDecode("Windows registry JSON decode error") self.accessed = {} diff --git a/qiling/os/windows/windows.py b/qiling/os/windows/windows.py index 5345ecd45..87e3b5ea1 100644 --- a/qiling/os/windows/windows.py +++ b/qiling/os/windows/windows.py @@ -94,7 +94,7 @@ def __make_fcall_selector(atype: QL_ARCH) -> Callable[[int], QlFunctionCall]: def load(self): self.setupGDT() - self.setupComponents() + self.__setup_components() # hook win api self.ql.hook_code(self.hook_winapi) @@ -121,17 +121,14 @@ def setupGDT(self): self.ql.mem.map(GS_SEGMENT_ADDR, GS_SEGMENT_SIZE, info='[GS]') - def setupComponents(self): - # handle manager + def __setup_components(self): reghive = self.path.transform_to_real_path(ntpath.join(self.windir, 'registry')) + self.handle_manager = handle.HandleManager() - # registry manger self.registry_manager = registry.RegistryManager(self.ql, reghive) - # clipboard self.clipboard = clipboard.Clipboard(self) - # fibers self.fiber_manager = fiber.FiberManager(self.ql) - # thread manager + main_thread = thread.QlWindowsThread(self.ql) self.thread_manager = thread.QlWindowsThreadManagement(self.ql, self, main_thread) From e290601350171cb910fd6acf76316489604af025 Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 30 Mar 2022 22:15:02 +0300 Subject: [PATCH 272/406] Some code quality changes --- qiling/loader/pe.py | 19 +++++++------------ qiling/os/windows/registry.py | 7 +++---- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/qiling/loader/pe.py b/qiling/loader/pe.py index 8c11712cb..11be94be7 100644 --- a/qiling/loader/pe.py +++ b/qiling/loader/pe.py @@ -75,7 +75,7 @@ def load_dll(self, name: str, is_driver: bool = False) -> int: if not is_file_library(dll_name): dll_name = f'{dll_name}.dll' - path = os.path.join(self.ql.os.winsys, dll_name) + path = ntpath.join(self.ql.os.winsys, dll_name) path = self.ql.os.path.transform_to_real_path(path) if dll_name.startswith('api-ms-win-'): @@ -262,8 +262,8 @@ def call_dll_entrypoint(self, dll: pefile.PE, dll_base: int, dll_len: int, dll_n regs_state = self.ql.arch.regs.save() - self.ql.os.fcall = self.ql.os.fcall_select(CDECL) - self.ql.os.fcall.call_native(entry_point, args, exit_point) + fcall = self.ql.os.fcall_select(CDECL) + fcall.call_native(entry_point, args, exit_point) # Execute the call to the entry point try: @@ -273,7 +273,7 @@ def call_dll_entrypoint(self, dll: pefile.PE, dll_base: int, dll_len: int, dll_n self.ql.arch.regs.restore(regs_state) else: - self.ql.os.fcall.cc.unwind(len(args)) + fcall.cc.unwind(len(args)) self.ql.log.info(f'Returned from {dll_name} DllMain') @@ -613,9 +613,9 @@ class QlLoaderPE(QlLoader, Process): def __init__(self, ql: Qiling, libcache: bool): super().__init__(ql) - self.ql = ql - self.path = self.ql.path - self.libcache = QlPeCache() if libcache else None + self.ql = ql + self.path = self.ql.path + self.libcache = QlPeCache() if libcache else None def run(self): self.init_dlls = ( @@ -637,10 +637,6 @@ def run(self): pe = pefile.PE(self.path, fast_load=True) self.is_driver = pe.is_driver() - if self.is_driver: - self.init_dlls.append(b"ntoskrnl.exe") - self.sys_dlls.append(b"ntoskrnl.exe") - ossection = f'OS{self.ql.arch.bits}' self.stack_address = self.ql.os.profile.getint(ossection, 'stack_address') @@ -665,7 +661,6 @@ def run(self): # not used, but here to remain compatible with ql.do_bin_patch self.load_address = 0 - # self.ql.os.setupComponents() cmdline = ntpath.join(self.ql.os.userprofile, 'Desktop', self.ql.targetname) cmdargs = ' '.join(f'"{arg}"' if ' ' in arg else arg for arg in self.argv[1:]) diff --git a/qiling/os/windows/registry.py b/qiling/os/windows/registry.py index 1b923361d..70dbff3cc 100644 --- a/qiling/os/windows/registry.py +++ b/qiling/os/windows/registry.py @@ -25,11 +25,12 @@ class RegConf: def __init__(self, fname: str): try: - with open(fname, 'rb') as infile: + with open(fname, 'r') as infile: data = infile.read() - config = json.loads(data or '{}') except IOError: config = {} + else: + config = json.loads(data or '{}') self.conf: MutableMapping[str, dict[str, dict]] = config @@ -188,8 +189,6 @@ def __init__(self, ql: Qiling, hivedir: str): except json.decoder.JSONDecodeError: raise QlErrorJsonDecode("Windows registry JSON decode error") - self.accessed = {} - def exists(self, key: str) -> bool: self.access(key) From 090be8fb4318cd2f8c199ab363f0f9974b1787be Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 30 Mar 2022 22:16:40 +0300 Subject: [PATCH 273/406] Refreshed sality test --- tests/test_pe_sys.py | 154 ++++++++++++++++++++----------------------- 1 file changed, 72 insertions(+), 82 deletions(-) diff --git a/tests/test_pe_sys.py b/tests/test_pe_sys.py index 2d2da22af..f60c58ff4 100644 --- a/tests/test_pe_sys.py +++ b/tests/test_pe_sys.py @@ -72,40 +72,14 @@ def hook_CreateThread(ql: Qiling, address: int, params): }) def hook_CreateFileA(ql: Qiling, address: int, params): lpFileName = params["lpFileName"] + if lpFileName.startswith("\\\\.\\"): - if ql.amsint32_driver: + if hasattr(ql, 'amsint32_driver'): return 0x13371337 - else: - return (-1) - else: - ret = _CreateFile(ql, address, params) - - return ret - def _WriteFile(ql: Qiling, address: int, params): - ret = 1 - hFile = params["hFile"] - lpBuffer = params["lpBuffer"] - nNumberOfBytesToWrite = params["nNumberOfBytesToWrite"] - lpNumberOfBytesWritten = params["lpNumberOfBytesWritten"] - #lpOverlapped = params["lpOverlapped"] - - if hFile == 0xfffffff5: - s = ql.mem.read(lpBuffer, nNumberOfBytesToWrite) - ql.os.stdout.write(s) - ql.os.stats.log_string(s.decode()) - ql.mem.write_ptr(lpNumberOfBytesWritten, nNumberOfBytesToWrite) - else: - f = ql.os.handle_manager.get(hFile) - if f is None: - # Invalid handle - ql.os.last_error = 0xffffffff - return 0 + return -1 - buffer = ql.mem.read(lpBuffer, nNumberOfBytesToWrite) - f.obj.write(bytes(buffer)) - ql.mem.write_ptr(lpNumberOfBytesWritten, nNumberOfBytesToWrite, 4) - return ret + return _CreateFile(ql, address, params) @winsdkapi(cc=STDCALL, params={ 'hFile' : HANDLE, @@ -120,21 +94,31 @@ def hook_WriteFile(ql: Qiling, address: int, params): nNumberOfBytesToWrite = params["nNumberOfBytesToWrite"] lpNumberOfBytesWritten = params["lpNumberOfBytesWritten"] + r = 1 + buffer = ql.mem.read(lpBuffer, nNumberOfBytesToWrite) + if hFile == 0x13371337: - buffer = ql.mem.read(lpBuffer, nNumberOfBytesToWrite) - try: - r, nNumberOfBytesToWrite = utils.io_Write(ql.amsint32_driver, buffer) - ql.mem.write_ptr(lpNumberOfBytesWritten, nNumberOfBytesToWrite, 4) - except Exception: - print("Error") - r = 1 - if r: - return 1 - else: - return 0 + success, nNumberOfBytesToWrite = utils.io_Write(ql.amsint32_driver, buffer) + r = int(success) + + elif hFile == 0xfffffff5: + s = buffer.decode() + + ql.os.stdout.write(s) + ql.os.stats.log_string(s) + else: - return _WriteFile(ql, address, params) + f = ql.os.handle_manager.get(hFile) + if f is None: + ql.os.last_error = 0xffffffff + return 0 + + f.obj.write(bytes(buffer)) + + ql.mem.write_ptr(lpNumberOfBytesWritten, nNumberOfBytesToWrite, 4) + + return r # BOOL StartServiceA( # SC_HANDLE hService, @@ -147,82 +131,88 @@ def hook_WriteFile(ql: Qiling, address: int, params): 'lpServiceArgVectors' : POINTER }) def hook_StartServiceA(ql: Qiling, address: int, params): + ql.test_set_api = True + hService = params["hService"] service_handle = ql.os.handle_manager.get(hService) - ql.test_set_api = True - if service_handle.name == "amsint32": - 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.DEBUG) - ntoskrnl = ql.amsint32_driver.loader.get_image_by_name("ntoskrnl.exe") - self.assertIsNotNone(ntoskrnl) - - init_unseen_symbols(ql.amsint32_driver, ntoskrnl.base+0xb7695, b"NtTerminateProcess", 0, "ntoskrnl.exe") - print("load amsint32_driver") - - try: - ql.amsint32_driver.run() - return 1 - except UcError as e: - print("Load driver error: ", e) - return 0 - else: - return 0 + + if service_handle.name != "amsint32": + return 1 + + if service_handle.name not in ql.os.services: + return 0 + + service_path = ql.os.services[service_handle.name] + service_path = ql.os.path.transform_to_real_path(service_path) + + amsint32 = Qiling([service_path], ql.rootfs, verbose=QL_VERBOSE.DEBUG) + ntoskrnl = amsint32.loader.get_image_by_name("ntoskrnl.exe") + self.assertIsNotNone(ntoskrnl) + + init_unseen_symbols(amsint32, ntoskrnl.base + 0xb7695, b"NtTerminateProcess", 0, "ntoskrnl.exe") + amsint32.log.info('Loading amsint32 driver') + + setattr(ql, 'amsint32_driver', amsint32) + + try: + amsint32.run() + except UcError as e: + print("Load driver error: ", e) + return 0 else: return 1 def hook_first_stop_address(ql: Qiling, stops: List[bool]): - ql.log.info(f' >>>> First Stop address: {ql.arch.regs.arch_pc:#010x}') + 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}') + ql.log.info(f' >>>> Second stop address: {ql.arch.regs.arch_pc:#010x}') stops[1] = True ql.emu_stop() def hook_third_stop_address(ql: Qiling, stops: List[bool]): - ql.log.info(f' >>>> Third Stop address: {ql.arch.regs.arch_pc:#010x}') + 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) - # for this module - ql.amsint32_driver = None + # emulate some Windows API ql.os.set_api("CreateThread", hook_CreateThread) ql.os.set_api("CreateFileA", hook_CreateFileA) ql.os.set_api("WriteFile", hook_WriteFile) ql.os.set_api("StartServiceA", hook_StartServiceA) - #init sality + + # run until first stop ql.hook_address(hook_first_stop_address, 0x40EFFB, stops) ql.run() - # run driver thread # execution is about to resume from 0x4053B2, which essentially jumps to ExitThread (kernel32.dll). # Set ExitThread exit code to 0 - ql.os.fcall = ql.os.fcall_select(STDCALL) - ql.os.fcall.writeParams(((DWORD, 0),)) + fcall = ql.os.fcall_select(STDCALL) + fcall.writeParams(((DWORD, 0),)) + # run until second stop ql.hook_address(hook_second_stop_address, 0x4055FA, stops) ql.run(begin=0x4053B2) - print("test kill thread") - if ql.amsint32_driver: - utils.io_Write(ql.amsint32_driver, ql.pack32(0xdeadbeef)) - # TODO: Should stop at 0x10423, but for now just stop at 0x0001066a - stop_addr = 0x0001066a - ql.amsint32_driver.hook_address(hook_third_stop_address, stop_addr, stops) + # 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)) - # TODO: not sure whether this one is really STDCALL - ql.amsint32_driver.os.fcall = ql.amsint32_driver.os.fcall_select(STDCALL) - ql.amsint32_driver.os.fcall.writeParams(((DWORD, 0),)) + # TODO: not sure whether this one is really STDCALL + fcall = amsint32.os.fcall_select(STDCALL) + fcall.writeParams(((DWORD, 0),)) - ql.amsint32_driver.run(begin=0x102D0) + # run until third stop + # TODO: Should stop at 0x10423, but for now just stop at 0x0001066a + amsint32.hook_address(hook_third_stop_address, 0x0001066a, stops) + amsint32.run(begin=0x102D0) self.assertTrue(stops[0]) self.assertTrue(stops[1]) From fed2b52b227979546ea25be3fedc31873bcc8ece Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 30 Mar 2022 22:16:50 +0300 Subject: [PATCH 274/406] Minor additions to dllscolector --- examples/scripts/dllscollector.bat | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/examples/scripts/dllscollector.bat b/examples/scripts/dllscollector.bat index 0161e7241..ce18c8613 100644 --- a/examples/scripts/dllscollector.bat +++ b/examples/scripts/dllscollector.bat @@ -22,10 +22,12 @@ SET QL_REGDIR64="%QL_WINDIR64%\registry" MKDIR %QL_REGDIR32% MKDIR %QL_SYSDIR32% MKDIR "%QL_SYSDIR32%\drivers" +MKDIR "%QL_WINDIR32%\Temp" MKDIR %QL_REGDIR64% MKDIR %QL_SYSDIR64% MKDIR "%QL_SYSDIR64%\drivers" +MKDIR "%QL_WINDIR64%\Temp" :: Generate emulated Windows registry (requires Administrator privileges) REG SAVE HKLM\SYSTEM %QL_REGDIR64%\SYSTEM /Y @@ -42,12 +44,16 @@ XCOPY /F /D /Y %QL_REGDIR64%\* %QL_REGDIR32% CALL :collect_dll32 advapi32.dll CALL :collect_dll32 bcrypt.dll CALL :collect_dll32 cfgmgr32.dll +CALL :collect_dll32 ci.dll CALL :collect_dll32 combase.dll CALL :collect_dll32 comctl32.dll CALL :collect_dll32 comdlg32.dll CALL :collect_dll32 crypt32.dll +CALL :collect_dll32 cryptbase.dll CALL :collect_dll32 gdi32.dll +CALL :collect_dll32 hal.dll CALL :collect_dll32 iphlpapi.dll +CALL :collect_dll32 kdcom.dll CALL :collect_dll32 kernel32.dll CALL :collect_dll32 KernelBase.dll CALL :collect_dll32 mpr.dll From db8f558f68094e64ff9db5fec27315273c3ec711 Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 31 Mar 2022 14:44:19 +0300 Subject: [PATCH 275/406] Allow searching image name case-insensitive --- qiling/loader/loader.py | 12 +++++++++--- qiling/loader/pe.py | 6 +++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/qiling/loader/loader.py b/qiling/loader/loader.py index ce8d40e2b..1546fc495 100644 --- a/qiling/loader/loader.py +++ b/qiling/loader/loader.py @@ -26,13 +26,19 @@ def find_containing_image(self, address: int) -> Optional[Image]: return next((image for image in self.images if image.base <= address < image.end), None) - def get_image_by_name(self, name: str) -> Optional[Image]: + def get_image_by_name(self, name: str, *, casefold: bool = False) -> Optional[Image]: """Retrieve an image by its basename. - Returns: image whose basename was, or `None` if not found + Args: + name : image base name + casefold : whether name matching should be case-insensitive (default is case-sensitive) + + Returns: image object whose basename match to the one given, or `None` if not found """ - return next((image for image in self.images if os.path.basename(image.path) == name), None) + cf = str.casefold if casefold else lambda s: s + + return next((image for image in self.images if cf(os.path.basename(image.path)) == cf(name)), None) def save(self) -> Mapping[str, Any]: saved_state = { diff --git a/qiling/loader/pe.py b/qiling/loader/pe.py index 11be94be7..4b2140947 100644 --- a/qiling/loader/pe.py +++ b/qiling/loader/pe.py @@ -83,8 +83,8 @@ def load_dll(self, name: str, is_driver: bool = False) -> int: self.ql.log.warning(f'Refusing to load virtual DLL {dll_name}') return 0 - # If the dll is already loaded - image = self.get_image_by_name(dll_name) + # see if this dll was already loaded + image = self.get_image_by_name(dll_name, casefold=True) if image is not None: return image.base @@ -374,7 +374,7 @@ def populate_unistr(obj, s: str) -> None: obj.MaximumLength = ucslen + 2 obj.Buffer = ucsbuf - image = self.get_image_by_name(dll_name) + 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 From 5427ff854d71d5649dbbe8dc292097d1df6cd8a9 Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 31 Mar 2022 14:53:32 +0300 Subject: [PATCH 276/406] Make Image class more linter-friendly --- qiling/loader/loader.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/qiling/loader/loader.py b/qiling/loader/loader.py index 1546fc495..6be0ccf1d 100644 --- a/qiling/loader/loader.py +++ b/qiling/loader/loader.py @@ -8,7 +8,10 @@ from qiling import Qiling -Image = NamedTuple('Image', (('base', int), ('end', int), ('path', str))) +class Image(NamedTuple): + base: int + end: int + path: str class QlLoader: def __init__(self, ql: Qiling): From f0efc6f72391aa1a5565ed9fd938bf34105d91c9 Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 11 Apr 2022 02:46:59 +0300 Subject: [PATCH 277/406] Rewrite OS path module --- qiling/os/path.py | 402 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 284 insertions(+), 118 deletions(-) diff --git a/qiling/os/path.py b/qiling/os/path.py index 22cc0c77e..2cab19913 100644 --- a/qiling/os/path.py +++ b/qiling/os/path.py @@ -3,164 +3,330 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -import os -from typing import Union -from pathlib import Path, PurePath, PurePosixPath, PureWindowsPath +from typing import Optional, Union +from pathlib import Path, PurePosixPath, PureWindowsPath -from qiling import Qiling from qiling.const import QL_OS, QL_OS_POSIX -# OH-MY-WIN32 !!! -# Some codes from cygwin. -# -# Basic guide: -# We should only handle "normal" paths like "C:\Windows\System32" and "bin/a.exe" for users. -# For UNC paths like '\\.\PHYSICALDRIVE0" and "\\Server\Share", they should be implemented -# by users via fs mapping interface. -class QlPathManager: - def __init__(self, ql: Qiling, cwd: str): - self.ql = ql - self._cwd = cwd +AnyPurePath = Union[PurePosixPath, PureWindowsPath] - self.convert_path_handler = QlPathManager.__select_convert_path_handler(ql.ostype, ql.host.os) +class QlOsPath: + """Virtual to host path manipulations helper. + """ - @staticmethod - def __select_convert_path_handler(emu_os: QL_OS, host_os: QL_OS): - # emulated os and hosting platform are of the same type - if (emu_os == host_os) or (emu_os in QL_OS_POSIX and host_os in QL_OS_POSIX): - handler = QlPathManager.convert_for_native_os + def __init__(self, rootfs: str, cwd: str, emulos: QL_OS) -> None: + """Initialize a path manipulation object. + + Args: + rootfs : host path to serve as the virtual root directory + cwd : virtual current working directory + emuls : emulated operating system + """ + + nt_path_os = (QL_OS.WINDOWS, QL_OS.DOS) + posix_path_os = QL_OS_POSIX + + # rootfs is a local directory on the host, and expected to exist + self._rootfs_path = Path(rootfs).resolve(strict=True) - elif emu_os in QL_OS_POSIX and host_os == QL_OS.WINDOWS: - handler = QlPathManager.convert_posix_to_win32 + # determine how virtual paths should be handled + if emulos in nt_path_os: + self.PureVirtualPath = PureWindowsPath - elif emu_os == QL_OS.WINDOWS and host_os in QL_OS_POSIX: - handler = QlPathManager.convert_win32_to_posix + elif emulos in posix_path_os: + self.PureVirtualPath = PurePosixPath else: - handler = QlPathManager.convert_for_native_os + raise ValueError(f'unexpected os type: {emulos}') + + self.cwd = cwd + + # + self.transform_to_relative_path = self.virtual_abspath + self.transform_to_real_path = self.virtual_to_host_path + self.transform_to_link_path = self.virtual_to_host_path + # + + @staticmethod + def __strip_parent_refs(path: AnyPurePath) -> AnyPurePath: + """Strip leading parent dir references, if any. + """ + + if path.parts: + pardir = r'..' - return handler + while path.parts[0] == pardir: + path = path.relative_to(pardir) + + return path @property def cwd(self) -> str: - return self._cwd + return str(self._cwd_anchor / self._cwd_vpath) @cwd.setter - def cwd(self, c: str) -> None: - if not c.startswith('/'): - self.ql.log.warning(f'Sanity check: path does not start with a forward slash "/"') - - self._cwd = c + def cwd(self, virtpath: str) -> None: + vpath = self.PureVirtualPath(virtpath) - @staticmethod - def normalize(path: PurePath) -> PurePath: - normalized_path = type(path)() + if not vpath.is_absolute(): + raise ValueError(f'current working directory must be an absolute path: {virtpath}') - # remove anchor (necessary for Windows UNC paths) and convert to relative path - if path.is_absolute(): - path = path.relative_to(path.anchor) + # extract the virtual path anchor so we can append cwd path to rootfs later on. + # however, we will still need the anchor to provide full virtual paths + cwd_anchor = self.PureVirtualPath(vpath.anchor) + cwd_vpath = vpath.relative_to(cwd_anchor) - for p in path.parts: - if p == '.': - continue + cwd_vpath = QlOsPath.__strip_parent_refs(cwd_vpath) + + self._cwd_anchor = cwd_anchor + self._cwd_vpath = cwd_vpath + + def __virtual_abspath(self, virtpath: Union[str, AnyPurePath]) -> AnyPurePath: + """Get the absolute virtual path representation of a virtual path. + This method does not follow symbolic links or parent directory references. + + Args: + virtpath: virtual path to resolve, either relative or absolute - if p == '..': - normalized_path = normalized_path.parent - continue + Returns: An absolute virtual path + """ + + vpath = self.PureVirtualPath(virtpath) + + if vpath.is_absolute(): + return vpath + + # rebase on top the current working directory. in case vpath is an absolute + # path, cwd will be discarded + absvpath = self._cwd_vpath / vpath + + # referencing root's parent directory should circle back to root. + # remove any leading parent dir references absvpath might have + absvpath = QlOsPath.__strip_parent_refs(absvpath) + + return self._cwd_anchor / absvpath + + def __resolved_vsymlink(self, basepath: Path, name: str): + """Attempt to resolve a virtual symbolic link. + + A virtual symbolic link points to a location within the virtual file + system, not to be confused with paths on the host file system. + + For example: + a vsymlink that points to '/var/tmp' should be resolved to + 'my/rootfs/path/var/tmp' on the host + """ + + fullpath = self._rootfs_path / basepath / name + vpath = None + + if fullpath.is_symlink(): + resolved = fullpath.resolve(strict=False) + + try: + # the resolve method turns fullpath into an absolute path on the host, + # but we need it to be an absolute virtual path. to convert the host + # path to virtual we have to make sure it resides within rootfs. + # + # if the host path is indeed under rootfs, we can rebase the virtual + # portion on top of rootfs and return an absolute virtual path. + # + # returning an absolute path will discard the accumulated vpath. + + vpath = self._cwd_anchor / resolved.relative_to(self._rootfs_path) + except ValueError: + # failing to convert the host path into a virtual one means that either + # the symbolic link is already an absolute virtual path, or it is pointing + # to an external directory on the host file system, which means it does + # not reflect a virtual path. + # + # on both cases we may return the path as-is, discarding the accumulated + # vpath. + # + # that, however, may not work in case the host filesystem and the virtual + # one are not of the same type (e.g. a virtual Windows path on top of a + # Linux host): the absolute path will not discard the accumulated vpath + # and will be appended - which is a corner case we do not know how to + # handle efficiently. + + vpath = resolved + + return vpath + + # this will work only if hosting os = virtual os + def __virtual_resolve(self, virtpath: Union[str, AnyPurePath]) -> AnyPurePath: + """Resolve a virtual path, including symbolic links and directory + references it might include. Path must not include circular symbolic + links. + + Args: + virtpath: virtual path to resolve, either relative or absolute + + Returns: An absolute virtual path + """ + + vpath = self.PureVirtualPath(virtpath) - normalized_path /= p + # if not already, turn vpath into an absolute path + if not vpath.is_absolute(): + vpath = self.__virtual_abspath(vpath) - return normalized_path + # accumulate paths as we progress through the resolution process. + # + # since symlink inspection and resolution can only be done on an + # actual file system, each step in the progress has to be translated + # into its correpsonding host path. that is the reason we keep track + # on the acumulated host path in parallel to the virtual one + # + # note: the reason we do not set acc_hpath to rootfs is to prevent + # parent dir refs from traversing beyond rootfs directory. + + acc_hpath = Path() + acc_vpath = self.PureVirtualPath(vpath.anchor) + + # eliminate virtual path's anchor to allow us accumulate its + # parts on top of rootfs + vpath = vpath.relative_to(vpath.anchor) + + for part in vpath.parts: + if part == '..': + acc_hpath = acc_hpath.parent + acc_vpath = acc_vpath.parent - @staticmethod - def convert_win32_to_posix(rootfs: Union[str, PurePosixPath], cwd: str, path: str) -> PurePosixPath: - _rootfs = PurePosixPath(rootfs) - _cwd = PurePosixPath(cwd[1:]) - _path = PureWindowsPath(path) - - # Things are complicated here. - # See https://docs.microsoft.com/zh-cn/windows/win32/fileio/naming-a-file?redirectedfrom=MSDN - if _path.is_absolute(): - if (len(path) >= 2 and path.startswith(r'\\')) or \ - (len(path) >= 3 and path[0].isalpha() and path[1:3] == ':\\'): # \\.\PhysicalDrive0 or \\Server\Share\Directory or X:\ - # UNC path should be handled in fs mapping. If not, append it to rootfs directly. - - result = _rootfs / QlPathManager.normalize(_path) else: - # code should never reach here. - raise RuntimeError() - else: - if len(path) >= 3 and path[:3] == r'\\?' or path[:3] == r'\??': # \??\ or \\?\ or \Device\.. - # Similair to \\.\, it should be handled in fs mapping. + # if this is a symlink attempt to resolve it + vtemp = self.__resolved_vsymlink(acc_hpath, part) - result = _rootfs / QlPathManager.normalize(_cwd / _path.relative_to(_path.anchor).as_posix()) - else: - # a normal relative path - result = _rootfs / QlPathManager.normalize(_cwd / _path.as_posix()) + # not a symlink; accumulate path part + if vtemp is None: + acc_hpath = acc_hpath / part + acc_vpath = acc_vpath / part - return result + else: + # rebase it on top of the accumulated virtual path + new_vpath = acc_vpath / vtemp - @staticmethod - def convert_posix_to_win32(rootfs: Union[str, PureWindowsPath], cwd: str, path: str) -> PureWindowsPath: - _rootfs = PureWindowsPath(rootfs) - _cwd = PurePosixPath(cwd[1:]) - _path = PurePosixPath(path) + # recursively resolve the new virtual path we got + vres = self.__virtual_resolve(new_vpath) - if _path.is_absolute(): - return _rootfs / QlPathManager.normalize(_path) - else: - return _rootfs / QlPathManager.normalize(_cwd / _path) + acc_hpath = Path(vres) + acc_vpath = vres - @staticmethod - def convert_for_native_os(rootfs: Union[str, PurePath], cwd: str, path: str) -> PurePath: - _rootfs = PurePath(rootfs) - _cwd = PurePosixPath(cwd[1:]) - _path = PurePath(path) + return acc_vpath + + def __virtual_to_host_path(self, virtpath: Union[str, AnyPurePath]) -> Path: + """Convert a virtual path to its corresponding path on the host. + + This method partialy normalizes the virtual path and does not resolve + references to parent directories neither virtual symbolic links + """ + + absvpath = self.__virtual_abspath(virtpath) + + # remove path anchor to allow path to be rebased + vpath = absvpath.relative_to(absvpath.anchor) + + # rebase virtual path on top of rootfs to get the host path + return self._rootfs_path / vpath + + def __is_safe_host_path(self, hostpath: Path, strict: bool = False) -> bool: + """Sanitize the specified host path and make sure it does not traverse out + of the rootfs directory hierarchy. + + Args: + hostpath : a local path to sanitize + strict : whether to raise an error if target path does not exist + + Returns: whether the path is safe to use + """ + + # canonicalization before assertion: resolve any relative path references and + # symbolic links that may exist. + # + # in case strict is set to True and the path does not exist, a FileNotFoundError + # is raised. this error is left for the user to catch and handle + hostpath = hostpath.resolve(strict=strict) + + try: + # to prevent path-traversal issues we have to make sure hostpath ended up + # as a subpath of rootfs. the following method will fail if that is not + # the case + _ = hostpath.relative_to(self._rootfs_path) + except ValueError: + return False - if _path.is_absolute(): - return _rootfs / QlPathManager.normalize(_path) else: - return _rootfs / QlPathManager.normalize(_cwd / _path.as_posix()) + return True + + def virtual_abspath(self, virtpath: str) -> str: + """Convert a relative virtual path to an absolute virtual path based + on the current working directory. - def convert_path(self, rootfs: str, cwd: str, path: str) -> PurePath: - return self.convert_path_handler(rootfs, cwd, path) + Args: + virtpath : relative virtual path - def transform_to_link_path(self, path: str) -> str: - real_path = self.convert_path(self.ql.rootfs, self.cwd, path) + Returns: the absolute virtual path + """ - return str(Path(real_path).absolute()) + absvpath = self.__virtual_abspath(virtpath) + + return str(absvpath) + + def virtual_to_host_path(self, virtpath: str) -> str: + """Convert a virtual path to its corresponding path on the hosting system. + + Args: + virtpath : path on the emulated system. the path may be either absolute + or relative + + Returns: the corresponding path on the hosting system + """ + + absvpath = self.__virtual_resolve(virtpath) + hostpath = self.__virtual_to_host_path(absvpath) + + return str(hostpath) + + def is_safe_host_path(self, hostpath: str) -> bool: + hpath = Path(hostpath) + + return self.__is_safe_host_path(hpath, strict=False) + + @staticmethod + def __host_casefold_path(hostpath: str) -> Optional[str]: + # assuming posix host + p = PurePosixPath(hostpath) + norm = Path(p.anchor) - def transform_to_real_path(self, path: str) -> str: - # TODO: We really need a virtual file system. - real_path = self.convert_path(self.ql.rootfs, self.cwd, path) + for elem in p.relative_to(norm).parts: + folded = elem.casefold() - if os.path.islink(real_path): - link_path = os.readlink(real_path) + try: + norm = next(entry for entry in norm.iterdir() if entry.name.casefold() == folded) + except StopIteration: + return None - real_path = self.convert_path(os.path.dirname(real_path), "/", link_path) + return str(norm) - if os.path.islink(real_path): - real_path = self.transform_to_real_path(str(real_path)) + def host_casefold_path(self, hostpath: str) -> Optional[str]: + """As opposed to POSIX paths, NT paths are case insensitive and may be specified + in multiple ways. When emulating an NT file system on top of POSIX one, virtual + NT paths and files might not be found because they are specified in a different + case than the one that is actually used on the hosting POSIX system. - # resolve multilevel symbolic link - if not os.path.exists(real_path): - link_path = PurePath(link_path) - path_dirs = link_path.parts + This method translates a case insensitive path into the actual case sensitive + name that is used on the hosting POSIX file system. - if link_path.is_absolute(): - path_dirs = path_dirs[1:] + Args: + hostpath: a path on the host, case insensitive - for i in range(len(path_dirs) - 1): - path_prefix = os.path.sep.join(path_dirs[:i+1]) - real_path_prefix = self.transform_to_real_path(path_prefix) - path_remain = os.path.sep.join(path_dirs[i+1:]) - real_path = os.path.join(real_path_prefix, path_remain) + Returns: the corresponding path on the host system, or None if the path does not + exist + """ - if os.path.exists(real_path): - break + # only relevant if the emulated file system is NT-based + if self.PureVirtualPath is PureWindowsPath: + return QlOsPath.__host_casefold_path(hostpath) - return str(Path(real_path).absolute()) + return hostpath - # The `relative path` here refers to the path which is relative to the rootfs. - def transform_to_relative_path(self, path: str) -> str: - return str(PurePath(self.cwd) / path) From eefdb286f7bf7a6c01a0638f177f7829260b9fe0 Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 11 Apr 2022 02:49:08 +0300 Subject: [PATCH 278/406] Adjust path usages --- qiling/os/linux/thread.py | 6 +++--- qiling/os/mapper.py | 4 ++-- qiling/os/os.py | 6 +++--- qiling/profiles/dos.ql | 2 +- qiling/profiles/windows.ql | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/qiling/os/linux/thread.py b/qiling/os/linux/thread.py index 04a6c1be2..154a709f5 100644 --- a/qiling/os/linux/thread.py +++ b/qiling/os/linux/thread.py @@ -14,7 +14,7 @@ from qiling.os.thread import * from qiling.arch.x86_const import * from qiling.exception import QlErrorExecutionStop -from qiling.os.path import QlPathManager +from qiling.os.path import QlOsPath LINUX_THREAD_ID = 2000 @@ -140,8 +140,8 @@ def path(self): return self._path @path.setter - def path(self, p): - self._path = QlPathManager(self._ql, p.cwd) + def path(self, p: QlOsPath): + self._path = QlOsPath(self.ql.rootfs, p.cwd, self.ql.ostype) @property def log_file_fd(self): diff --git a/qiling/os/mapper.py b/qiling/os/mapper.py index ed99baf9d..6b79524fb 100644 --- a/qiling/os/mapper.py +++ b/qiling/os/mapper.py @@ -7,7 +7,7 @@ import os from typing import Any, MutableMapping, Union -from .path import QlPathManager +from .path import QlOsPath from .filestruct import ql_file # All mapped objects should inherit this class. @@ -59,7 +59,7 @@ def name(self): class QlFsMapper: - def __init__(self, path: QlPathManager): + def __init__(self, path: QlOsPath): self._mapping: MutableMapping[str, Any] = {} self.path = path diff --git a/qiling/os/os.py b/qiling/os/os.py index bbb00af80..2c0e11812 100644 --- a/qiling/os/os.py +++ b/qiling/os/os.py @@ -17,7 +17,7 @@ from .mapper import QlFsMapper from .stats import QlOsStats from .utils import QlOsUtils -from .path import QlPathManager +from .path import QlOsPath class QlOs: Resolver = Callable[[int], Any] @@ -38,10 +38,10 @@ def __init__(self, ql: Qiling, resolvers: Mapping[Any, Resolver] = {}): self.profile = self.ql.profile self.exit_code = 0 - if not ql.baremetal: + if ql.ostype in QL_OS_POSIX + (QL_OS.WINDOWS, QL_OS.DOS): cwd = self.profile.get("MISC", "current_path") - self.path = QlPathManager(ql, cwd) + self.path = QlOsPath(ql.rootfs, cwd, ql.ostype) self.fs_mapper = QlFsMapper(self.path) self.user_defined_api = { diff --git a/qiling/profiles/dos.ql b/qiling/profiles/dos.ql index 04542dd00..c1bf3edc0 100644 --- a/qiling/profiles/dos.ql +++ b/qiling/profiles/dos.ql @@ -22,4 +22,4 @@ base_address = 0x7000 # usage: append = test1 append = automatize_input = False -current_path = / \ No newline at end of file +current_path = A:\ \ No newline at end of file diff --git a/qiling/profiles/windows.ql b/qiling/profiles/windows.ql index f26b4934b..d0ab9a384 100644 --- a/qiling/profiles/windows.ql +++ b/qiling/profiles/windows.ql @@ -41,7 +41,7 @@ split = False # usage: append = test1 append = automatize_input = False -current_path = / +current_path = C:\ [SYSTEM] # Major Minor ProductType From 1951a127a27e393f3d08fa444c0c68ddd6f0c8d3 Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 11 Apr 2022 02:50:04 +0300 Subject: [PATCH 279/406] Adjust path tests --- tests/test_elf.py | 6 - tests/test_pathutils.py | 293 +++++++++++++++++++++++----------------- 2 files changed, 169 insertions(+), 130 deletions(-) diff --git a/tests/test_elf.py b/tests/test_elf.py index 141c2acc5..49407a310 100644 --- a/tests/test_elf.py +++ b/tests/test_elf.py @@ -1008,12 +1008,6 @@ def test_x8664_symlink(self): ql.run() del ql - def test_arm_directory_symlink(self): - ql = Qiling(["../examples/rootfs/arm_linux/bin/arm_hello"], "../examples/rootfs/arm_linux", verbose=QL_VERBOSE.DEBUG) - real_path = ql.os.path.transform_to_real_path("/lib/libsymlink_test.so") - self.assertTrue(real_path.endswith("/examples/rootfs/arm_linux/tmp/media/nand/symlink_test/libsymlink_test.so")) - del ql - def test_x8664_absolute_path(self): ql = Qiling(["../examples/rootfs/x8664_linux/bin/absolutepath"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG) diff --git a/tests/test_pathutils.py b/tests/test_pathutils.py index d78176741..cf9edbe9c 100644 --- a/tests/test_pathutils.py +++ b/tests/test_pathutils.py @@ -4,138 +4,183 @@ # import sys, unittest -from pathlib import PurePath, PurePosixPath, PureWindowsPath +from pathlib import Path, PurePath, PurePosixPath, PureWindowsPath -sys.path.append("..") -from qiling import Qiling -from qiling.const import QL_OS, QL_OS_POSIX, QL_VERBOSE -from qiling.os.path import QlPathManager +sys.path.append('..') +from qiling.const import QL_OS +from qiling.os.path import QlOsPath + +is_nt_host = PurePath() == PureWindowsPath() +is_posix_host = PurePath() == PurePosixPath() + +def realpath(path: PurePath) -> Path: + return Path(path).resolve() + +def nt_to_native(rootfs: str, cwd: str, path: str) -> str: + p = QlOsPath(rootfs, cwd, QL_OS.WINDOWS) + + return p.virtual_to_host_path(path) + +def posix_to_native(rootfs: str, cwd: str, path: str) -> str: + p = QlOsPath(rootfs, cwd, QL_OS.LINUX) + + return p.virtual_to_host_path(path) -# define a few aliases -nt_to_posix = QlPathManager.convert_win32_to_posix -posix_to_nt = QlPathManager.convert_posix_to_win32 -to_native = QlPathManager.convert_for_native_os class TestPathUtils(unittest.TestCase): - def test_convert_win32_to_posix(self): - rootfs = PurePosixPath(r'../examples/rootfs/x8664_windows') - - self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "\\\\.\\PhysicalDrive0\\test"))) - self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "\\\\.\\PhysicalDrive0\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "\\\\.\\PhysicalDrive0\\..\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "\\\\.\\PhysicalDrive0\\..\\xxxx\\..\\test"))) - - self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "\\\\hostname\\share\\test"))) - self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "\\\\hostname\\share\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "\\\\hostname\\share\\..\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "\\\\hostname\\share\\..\\xxxx\\..\\test"))) - - self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "\\\\.\\BootPartition\\test"))) - self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "\\\\.\\BootPartition\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "\\\\.\\BootPartition\\..\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "\\\\.\\BootPartition\\..\\xxxx\\..\\test"))) - - self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "C:\\test"))) - self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "C:\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "C:\\..\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "C:\\..\\xxxx\\..\\test"))) - - self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "\\test"))) - self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "\\..\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "\\..\\xxxx\\..\\test"))) - - self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "test"))) - self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "..\\test"))) - self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "..\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(nt_to_posix(rootfs, "/", "..\\xxxx\\..\\test"))) - - self.assertEqual(str(rootfs / "xxxx" / "test"), str(nt_to_posix(rootfs, "/xxxx", "test"))) - self.assertEqual(str(rootfs / "xxxx" / "test"), str(nt_to_posix(rootfs, "/xxxx/yyyy", "..\\test"))) - self.assertEqual(str(rootfs / "xxxx" / "test"), str(nt_to_posix(rootfs, "/xxxx/yyyy/zzzz", "..\\..\\test"))) - self.assertEqual(str(rootfs / "xxxx" / "test"), str(nt_to_posix(rootfs, "/xxxx/yyyy", "..\\xxxx\\..\\test"))) - - def test_convert_posix_to_win32(self): - rootfs = PureWindowsPath(r'../examples/rootfs/x8664_linux') - - self.assertEqual(str(rootfs / "test"), str(posix_to_nt(rootfs, "/", "/test"))) - self.assertEqual(str(rootfs / "test"), str(posix_to_nt(rootfs, "/", "/../test"))) - self.assertEqual(str(rootfs / "test"), str(posix_to_nt(rootfs, "/", "/../../test"))) - self.assertEqual(str(rootfs / "test"), str(posix_to_nt(rootfs, "/", "/../xxxx/../test"))) - - self.assertEqual(str(rootfs / "test"), str(posix_to_nt(rootfs, "/", "test"))) - self.assertEqual(str(rootfs / "test"), str(posix_to_nt(rootfs, "/", "../test"))) - self.assertEqual(str(rootfs / "test"), str(posix_to_nt(rootfs, "/", "../../test"))) - self.assertEqual(str(rootfs / "test"), str(posix_to_nt(rootfs, "/", "../xxxx/../test"))) - - self.assertEqual(str(rootfs / "xxxx" / "test"), str(posix_to_nt(rootfs, "/xxxx", "test"))) - self.assertEqual(str(rootfs / "xxxx" / "test"), str(posix_to_nt(rootfs, "/xxxx/yyyy", "../test"))) - self.assertEqual(str(rootfs / "xxxx" / "test"), str(posix_to_nt(rootfs, "/xxxx/yyyy/zzzz", "../../test"))) - self.assertEqual(str(rootfs / "xxxx" / "test"), str(posix_to_nt(rootfs, "/xxxx/yyyy", "../xxxx/../test"))) + def test_convert_nt_to_posix(self): + # test only on a POSIX host + if not is_posix_host: + self.skipTest('POSIX host only') + + rootfs = PurePosixPath(r'../examples/rootfs/x86_windows') + + expected = str(realpath(rootfs) / 'test') + + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\.\\PhysicalDrive0\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\.\\PhysicalDrive0\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\.\\PhysicalDrive0\\..\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\.\\PhysicalDrive0\\..\\xxxx\\..\\test')) + + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\hostname\\share\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\hostname\\share\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\hostname\\share\\..\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\hostname\\share\\..\\xxxx\\..\\test')) + + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\.\\BootPartition\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\.\\BootPartition\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\.\\BootPartition\\..\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\.\\BootPartition\\..\\xxxx\\..\\test')) + + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', 'C:\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', 'C:\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', 'C:\\..\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', 'C:\\..\\xxxx\\..\\test')) + + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\..\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\..\\xxxx\\..\\test')) + + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', 'test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '..\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '..\\xxxx\\..\\test')) + + expected = str(realpath(rootfs) / 'Windows' / 'test') + + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\Windows', 'test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\Windows\\System32', '..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\Windows\\System32\\drivers', '..\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\Windows\\System32', '..\\xxxx\\..\\test')) + + def test_convert_posix_to_nt(self): + # test only on a Windows host + if not is_nt_host: + self.skipTest('NT host only') + + rootfs = PureWindowsPath(r'../examples/rootfs/x86_linux') + + expected = str(realpath(rootfs) / 'test') + + self.assertEqual(expected, posix_to_native(str(rootfs), '/', '/test')) + self.assertEqual(expected, posix_to_native(str(rootfs), '/', '/../test')) + self.assertEqual(expected, posix_to_native(str(rootfs), '/', '/../../test')) + self.assertEqual(expected, posix_to_native(str(rootfs), '/', '/../xxxx/../test')) + + self.assertEqual(expected, posix_to_native(str(rootfs), '/', 'test')) + self.assertEqual(expected, posix_to_native(str(rootfs), '/', '../test')) + self.assertEqual(expected, posix_to_native(str(rootfs), '/', '../../test')) + self.assertEqual(expected, posix_to_native(str(rootfs), '/', '../xxxx/../test')) + + expected = str(realpath(rootfs) / 'proc' / 'test') + + self.assertEqual(expected, posix_to_native(str(rootfs), '/proc', 'test')) + self.assertEqual(expected, posix_to_native(str(rootfs), '/proc/sys', '../test')) + self.assertEqual(expected, posix_to_native(str(rootfs), '/proc/sys/net', '../../test')) + self.assertEqual(expected, posix_to_native(str(rootfs), '/proc/sys', '../xxxx/../test')) def test_convert_for_native_os(self): - ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_hello_static"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG) - - if ql.host.os == QL_OS.WINDOWS: - rootfs = PurePath(r'../examples/rootfs/x8664_windows') - - self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "\\\\.\\PhysicalDrive0\\test"))) - self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "\\\\.\\PhysicalDrive0\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "\\\\.\\PhysicalDrive0\\..\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "\\\\.\\PhysicalDrive0\\..\\xxxx\\..\\test"))) - - self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "\\\\hostname\\share\\test"))) - self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "\\\\hostname\\share\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "\\\\hostname\\share\\..\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "\\\\hostname\\share\\..\\xxxx\\..\\test"))) - - self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "\\\\.\\BootPartition\\test"))) - self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "\\\\.\\BootPartition\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "\\\\.\\BootPartition\\..\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "\\\\.\\BootPartition\\..\\xxxx\\..\\test"))) - - self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "C:\\test"))) - self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "C:\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "C:\\..\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "C:\\..\\xxxx\\..\\test"))) - - self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "\\test"))) - self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "\\..\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "\\..\\xxxx\\..\\test"))) - - self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "test"))) - self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "..\\test"))) - self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "..\\..\\test"))) - self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "..\\xxxx\\..\\test"))) - - self.assertEqual(str(rootfs / "xxxx" / "test"), str(to_native(rootfs, "/xxxx", "test"))) - self.assertEqual(str(rootfs / "xxxx" / "test"), str(to_native(rootfs, "/xxxx/yyyy", "..\\test"))) - self.assertEqual(str(rootfs / "xxxx" / "test"), str(to_native(rootfs, "/xxxx/yyyy/zzzz", "..\\..\\test"))) - self.assertEqual(str(rootfs / "xxxx" / "test"), str(to_native(rootfs, "/xxxx/yyyy", "..\\xxxx\\..\\test"))) - - elif ql.host.os in QL_OS_POSIX: - rootfs = PurePath(r'../examples/rootfs/x8664_linux') - - self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "/test"))) - self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "/../test"))) - self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "/../../test"))) - self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "/../xxxx/../test"))) - - self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "test"))) - self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "../test"))) - self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "../../test"))) - self.assertEqual(str(rootfs / "test"), str(to_native(rootfs, "/", "../xxxx/../test"))) - - self.assertEqual(str(rootfs / "xxxx" / "test"), str(to_native(rootfs, "/xxxx", "test"))) - self.assertEqual(str(rootfs / "xxxx" / "test"), str(to_native(rootfs, "/xxxx/yyyy", "../test"))) - self.assertEqual(str(rootfs / "xxxx" / "test"), str(to_native(rootfs, "/xxxx/yyyy/zzzz", "../../test"))) - self.assertEqual(str(rootfs / "xxxx" / "test"), str(to_native(rootfs, "/xxxx/yyyy", "../xxxx/../test"))) + + if is_nt_host: + rootfs = PureWindowsPath(r'../examples/rootfs/x86_windows') + + expected = str(realpath(rootfs) / 'test') + + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\.\\PhysicalDrive0\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\.\\PhysicalDrive0\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\.\\PhysicalDrive0\\..\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\.\\PhysicalDrive0\\..\\xxxx\\..\\test')) + + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\hostname\\share\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\hostname\\share\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\hostname\\share\\..\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\hostname\\share\\..\\xxxx\\..\\test')) + + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\.\\BootPartition\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\.\\BootPartition\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\.\\BootPartition\\..\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\\\.\\BootPartition\\..\\xxxx\\..\\test')) + + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', 'C:\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', 'C:\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', 'C:\\..\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', 'C:\\..\\xxxx\\..\\test')) + + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\..\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '\\..\\xxxx\\..\\test')) + + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', 'test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '..\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\', '..\\xxxx\\..\\test')) + + expected = str(realpath(rootfs) / 'Windows' / 'test') + + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\Windows', 'test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\Windows\\System32', '..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\Windows\\System32\\drivers', '..\\..\\test')) + self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\Windows\\System32', '..\\xxxx\\..\\test')) + + elif is_posix_host: + rootfs = PurePosixPath(r'../examples/rootfs/x86_linux') + + expected = str(realpath(rootfs) / 'test') + + self.assertEqual(expected, posix_to_native(str(rootfs), '/', '/test')) + self.assertEqual(expected, posix_to_native(str(rootfs), '/', '/../test')) + self.assertEqual(expected, posix_to_native(str(rootfs), '/', '/../../test')) + self.assertEqual(expected, posix_to_native(str(rootfs), '/', '/../xxxx/../test')) + + self.assertEqual(expected, posix_to_native(str(rootfs), '/', 'test')) + self.assertEqual(expected, posix_to_native(str(rootfs), '/', '../test')) + self.assertEqual(expected, posix_to_native(str(rootfs), '/', '../../test')) + self.assertEqual(expected, posix_to_native(str(rootfs), '/', '../xxxx/../test')) + + expected = str(realpath(rootfs) / 'proc' / 'test') + + self.assertEqual(expected, posix_to_native(str(rootfs), '/proc', 'test')) + self.assertEqual(expected, posix_to_native(str(rootfs), '/proc/sys', '../test')) + self.assertEqual(expected, posix_to_native(str(rootfs), '/proc/sys/net', '../../test')) + self.assertEqual(expected, posix_to_native(str(rootfs), '/proc/sys', '../xxxx/../test')) + + # test virtual symlink: absolute virtual path + rootfs = PurePosixPath(r'../examples/rootfs/arm_linux') + expected = str(realpath(rootfs) / 'tmp' / 'media' / 'nand' / 'symlink_test' / 'libsymlink_test.so') + + self.assertEqual(expected, posix_to_native(str(rootfs), '/', '/lib/libsymlink_test.so')) + + # test virtual symlink: relative virtual path + rootfs = PurePosixPath(r'../examples/rootfs/arm_qnx') + expected = str(realpath(rootfs) / 'lib' / 'libm.so.2') + + self.assertEqual(expected, posix_to_native(str(rootfs), '/', '/usr/lib/libm.so.2')) else: - raise NotImplementedError('unexpected hosting os') + self.fail('unexpected host os') - del ql -if __name__ == "__main__": +if __name__ == '__main__': unittest.main() From e6f78b37fc3be2b3a031a73d78962cf2ea159a3c Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 11 Apr 2022 02:52:41 +0300 Subject: [PATCH 280/406] Handle NT case-insensitive filenames on POSIX hosts --- qiling/loader/pe.py | 69 +++++++++++++------ .../os/windows/dlls/kernel32/libloaderapi.py | 6 +- qiling/os/windows/utils.py | 12 +--- 3 files changed, 54 insertions(+), 33 deletions(-) diff --git a/qiling/loader/pe.py b/qiling/loader/pe.py index 4b2140947..d9d105f9e 100644 --- a/qiling/loader/pe.py +++ b/qiling/loader/pe.py @@ -4,7 +4,7 @@ # import os, pefile, pickle, secrets, ntpath -from typing import Any, MutableMapping, Optional, Mapping, Sequence +from typing import Any, MutableMapping, Optional, Mapping, Sequence, Tuple from unicorn import UcError from unicorn.x86_const import UC_X86_REG_CR4, UC_X86_REG_CR8 @@ -16,7 +16,7 @@ from qiling.os.const import POINTER from qiling.os.windows.api import HINSTANCE, DWORD, LPVOID from qiling.os.windows.fncc import CDECL -from qiling.os.windows.utils import path_leaf, is_file_library +from qiling.os.windows.utils import has_lib_ext from qiling.os.windows.structs import * from .loader import QlLoader, Image @@ -32,6 +32,11 @@ def __init__(self, ba: int, data: bytearray, cmdlines: Sequence, import_symbols: class QlPeCache: @staticmethod def cache_filename(path: str) -> str: + dirname, basename = ntpath.split(path) + + # canonicalize basename while preserving the path + path = ntpath.join(dirname, basename.casefold()) + return f'{path}.cache2' def restore(self, path: str) -> Optional[QlPeCacheEntry]: @@ -64,23 +69,36 @@ class Process: def __init__(self, ql: Qiling): self.ql = ql - def load_dll(self, name: str, is_driver: bool = False) -> int: - # BUG: that lowers the whole path, which may not be found on a Linux host where paths are case sensitive - dll_name = name.lower() + def __get_path_elements(self, name: str) -> Tuple[str, str]: + """Translate DLL virtual path into host path. - if dll_name.startswith('c:\\'): - path = self.ql.os.path.transform_to_real_path(dll_name) - dll_name = path_leaf(dll_name) - else: - if not is_file_library(dll_name): - dll_name = f'{dll_name}.dll' + Args: + name: dll virtual path; either absolute or relative + + Returns: dll path on the host and dll basename in a canonicalized form + """ + + dirname, basename = ntpath.split(name) + + if not has_lib_ext(basename): + basename = f'{basename}.dll' + + # if only filename was specified assume it is located at the + # system32 folder to prevent potential dll hijacking + if not dirname: + dirname = self.ql.os.winsys - path = ntpath.join(self.ql.os.winsys, dll_name) - path = self.ql.os.path.transform_to_real_path(path) + # reconstruct the dll virtual path + vpath = ntpath.join(dirname, basename) + + return self.ql.os.path.virtual_to_host_path(vpath), basename.casefold() + + def load_dll(self, name: str, is_driver: bool = False) -> int: + dll_path, dll_name = self.__get_path_elements(name) if dll_name.startswith('api-ms-win-'): # Usually we should not reach this point and instead imports from such DLLs should be redirected earlier - self.ql.log.warning(f'Refusing to load virtual DLL {dll_name}') + self.ql.log.debug(f'Refusing to load virtual DLL {dll_name}') return 0 # see if this dll was already loaded @@ -89,9 +107,18 @@ def load_dll(self, name: str, is_driver: bool = False) -> int: if image is not None: return image.base - if not os.path.exists(path): - self.ql.log.error(f'Could not find DLL file: {path}') - return 0 + if not os.path.exists(dll_path): + # posix hosts may not find the requested filename if it was saved under a different case. + # For example, 'KernelBase.dll' may not be found because it is stored as 'kernelbase.dll'. + # + # try to locate the requested file while ignoring the case of its path elements. + dll_casefold_path = self.ql.os.path.host_casefold_path(dll_path) + + if dll_casefold_path is None: + self.ql.log.error(f'Could not find DLL file: {dll_path}') + return 0 + + dll_path = dll_casefold_path self.ql.log.info(f'Loading {dll_name} ...') @@ -102,7 +129,7 @@ def load_dll(self, name: str, is_driver: bool = False) -> int: loaded = False if self.libcache: - cached = self.libcache.restore(path) + cached = self.libcache.restore(dll_path) if cached: data = cached.data @@ -125,7 +152,7 @@ def load_dll(self, name: str, is_driver: bool = False) -> int: # either file was not cached, or could not be loaded to the same location in memory if not cached or not loaded: - dll = pefile.PE(path, fast_load=True) + dll = pefile.PE(dll_path, fast_load=True) dll.parse_data_directories() warnings = dll.get_warnings() @@ -180,7 +207,7 @@ def load_dll(self, name: str, is_driver: bool = False) -> int: if self.libcache: cached = QlPeCacheEntry(image_base, data, cmdlines, import_symbols, import_table) - self.libcache.save(path, cached) + self.libcache.save(dll_path, cached) self.ql.log.info(f'Cached {dll_name}') # Add dll to IAT @@ -198,7 +225,7 @@ def load_dll(self, name: str, is_driver: bool = False) -> int: self.dll_last_address = self.ql.mem.align_up(self.dll_last_address + dll_len, 0x10000) # add DLL to coverage images - self.images.append(Image(dll_base, dll_base + dll_len, os.path.abspath(path))) + self.images.append(Image(dll_base, dll_base + dll_len, dll_path)) # if this is NOT a driver, add dll to ldr data if not is_driver: diff --git a/qiling/os/windows/dlls/kernel32/libloaderapi.py b/qiling/os/windows/dlls/kernel32/libloaderapi.py index 739838c78..f44e2c9b6 100644 --- a/qiling/os/windows/dlls/kernel32/libloaderapi.py +++ b/qiling/os/windows/dlls/kernel32/libloaderapi.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.utils import * +from qiling.os.windows.utils import has_lib_ext def _GetModuleHandle(ql: Qiling, address: int, params): lpModuleName = params["lpModuleName"] @@ -20,8 +20,8 @@ def _GetModuleHandle(ql: Qiling, address: int, params): else: lpModuleName = lpModuleName.lower() - if not is_file_library(lpModuleName): - lpModuleName += ".dll" + if not has_lib_ext(lpModuleName): + lpModuleName = f'{lpModuleName}.dll' image = ql.loader.get_image_by_name(lpModuleName) diff --git a/qiling/os/windows/utils.py b/qiling/os/windows/utils.py index 32650a487..600642802 100644 --- a/qiling/os/windows/utils.py +++ b/qiling/os/windows/utils.py @@ -4,7 +4,6 @@ # import ctypes -import ntpath from typing import Iterable, Tuple, TypeVar from unicorn import UcError @@ -24,15 +23,10 @@ def cmp(a: Comparable, b: Comparable) -> int: return (a > b) - (a < b) -def is_file_library(string: str) -> bool: - string = string.lower() - extension = string.rpartition('.')[-1] - return extension in ("dll", "exe", "sys", "drv") +def has_lib_ext(name: str) -> bool: + ext = name.lower().rpartition('.')[-1] - -def path_leaf(path): - head, tail = ntpath.split(path) - return tail or ntpath.basename(head) + return ext in ("dll", "exe", "sys", "drv") def io_Write(ql: Qiling, in_buffer: bytes): From 0e5a31cfcc30f0409b08c0785ece1000564d5fef Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 11 Apr 2022 14:18:20 +0300 Subject: [PATCH 281/406] Improve case insensitive filenames handling --- qiling/loader/pe.py | 4 +-- .../os/windows/dlls/kernel32/libloaderapi.py | 25 +++++++++++-------- qiling/os/windows/dlls/ntdll.py | 8 +++--- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/qiling/loader/pe.py b/qiling/loader/pe.py index d9d105f9e..01f9d9d11 100644 --- a/qiling/loader/pe.py +++ b/qiling/loader/pe.py @@ -462,7 +462,7 @@ def init_imports(self, pe: pefile.PE, is_driver: bool): pe.full_load() for entry in pe.DIRECTORY_ENTRY_IMPORT: - dll_name = entry.dll.decode().lower() + dll_name = entry.dll.decode().casefold() self.ql.log.debug(f'Requesting imports from {dll_name}') orig_dll_name = dll_name @@ -568,7 +568,7 @@ def init_exports(self, pe: pefile.PE): iat[entry.ordinal] = ea dll_name = os.path.basename(self.path) - self.import_address_table[dll_name] = iat + self.import_address_table[dll_name.casefold()] = iat def init_driver_object(self): drv_addr = self.structure_last_addr diff --git a/qiling/os/windows/dlls/kernel32/libloaderapi.py b/qiling/os/windows/dlls/kernel32/libloaderapi.py index f44e2c9b6..658107135 100644 --- a/qiling/os/windows/dlls/kernel32/libloaderapi.py +++ b/qiling/os/windows/dlls/kernel32/libloaderapi.py @@ -150,35 +150,38 @@ def hook_GetModuleFileNameW(ql: Qiling, address: int, params): 'lpProcName' : POINTER # LPCSTR }) def hook_GetProcAddress(ql: Qiling, address: int, params): - if params["lpProcName"] > MAXUSHORT: + hModule = params['hModule'] + lpProcName = params['lpProcName'] + + if lpProcName > MAXUSHORT: # Look up by name - params["lpProcName"] = ql.os.utils.read_cstring(params["lpProcName"]) + params["lpProcName"] = ql.os.utils.read_cstring(lpProcName) lpProcName = bytes(params["lpProcName"], "ascii") else: # Look up by ordinal lpProcName = params["lpProcName"] # TODO fix for gandcrab - if params["lpProcName"] == "RtlComputeCrc32": + if lpProcName == "RtlComputeCrc32": return 0 # Check if dll is loaded - image = next((image for image in ql.loader.images if image.base == params['hModule']), None) + dll_name = next((os.path.basename(image.path).casefold() for image in ql.loader.images if image.base == hModule), None) - if image is None: - ql.log.info('Failed to import function "%s" with handle 0x%X' % (lpProcName, params['hModule'])) + if dll_name is None: + ql.log.info('Failed to import function "%s" with handle 0x%X' % (lpProcName, hModule)) return 0 - dll_name = os.path.basename(image.path) - # Handle case where module is self - if dll_name == os.path.basename(ql.loader.path): + if dll_name == os.path.basename(ql.loader.path).casefold(): for addr, export in ql.loader.export_symbols.items(): if export['name'] == lpProcName: return addr - if lpProcName in ql.loader.import_address_table[dll_name]: - return ql.loader.import_address_table[dll_name][lpProcName] + iat = ql.loader.import_address_table[dll_name] + + if lpProcName in iat: + return iat[lpProcName] return 0 diff --git a/qiling/os/windows/dlls/ntdll.py b/qiling/os/windows/dlls/ntdll.py index 1d25e7ee0..3e8b838f2 100644 --- a/qiling/os/windows/dlls/ntdll.py +++ b/qiling/os/windows/dlls/ntdll.py @@ -360,17 +360,17 @@ def hook_LdrGetProcedureAddress(ql: Qiling, address: int, params): FunctionAddress = params['FunctionAddress'] # Check if dll is loaded - dll_name = next((os.path.basename(path) for base, _, path in ql.loader.images if base == ModuleHandle), None) + dll_name = next((os.path.basename(path).casefold() for base, _, path in ql.loader.images if base == ModuleHandle), None) if dll_name is None: ql.log.debug(f'Could not find specified handle {ModuleHandle} in loaded DLL') return 0 identifier = bytes(FunctionName, 'ascii') if FunctionName else Ordinal + iat = ql.loader.import_address_table[dll_name] - if identifier in ql.loader.import_address_table[dll_name]: - addr = ql.loader.import_address_table[dll_name][identifier] - ql.mem.write(addr.to_bytes(length=ql.arch.pointersize, byteorder='little'), FunctionAddress) + if identifier in iat: + ql.mem.write_ptr(FunctionAddress, iat[identifier]) return 0 return 0xFFFFFFFF From 15969a8e43f53009816971faa9a7c27c11f7ba52 Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 12 Apr 2022 23:14:43 +0300 Subject: [PATCH 282/406] Make pack / unpack more linter-friendly --- qiling/core.py | 3 +-- qiling/core_struct.py | 36 ++++++++++++++++++------------------ 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/qiling/core.py b/qiling/core.py index 704efae3c..8daae5732 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -164,9 +164,8 @@ def __init__( self.uc = self.arch.uc # Once we finish setting up arch, we can init QlCoreStructs and QlCoreHooks - QlCoreStructs.__init__(self, self.arch.endian, self.arch.bits) - if not self.interpreter: + QlCoreStructs.__init__(self, self.arch.endian, self.arch.bits) QlCoreHooks.__init__(self, self.uc) ########## diff --git a/qiling/core_struct.py b/qiling/core_struct.py index 47fd0cee4..853a765c3 100644 --- a/qiling/core_struct.py +++ b/qiling/core_struct.py @@ -10,6 +10,7 @@ ############################################## import struct +from _typeshed import ReadableBuffer from .const import QL_ENDIAN from .exception import QlErrorStructConversion @@ -30,12 +31,11 @@ def __init__(self, endian: QL_ENDIAN, bit: int): self._fmt32s = f'{modifier}i' self._fmt64 = f'{modifier}Q' self._fmt64s = f'{modifier}q' - + handlers = { 64 : (self.pack64, self.pack64s, self.unpack64, self.unpack64s), 32 : (self.pack32, self.pack32s, self.unpack32, self.unpack32s), 16 : (self.pack16, self.pack16s, self.unpack16, self.unpack16s), - 1 : ( None, None, None, None) } if bit not in handlers: @@ -48,50 +48,50 @@ def __init__(self, endian: QL_ENDIAN, bit: int): self.unpack = up self.unpacks = ups - def pack64(self, x): + def pack64(self, x: int, /) -> bytes: return struct.pack(self._fmt64, x) - def pack64s(self, x): + def pack64s(self, x: int, /) -> bytes: return struct.pack(self._fmt64s, x) - def unpack64(self, x): + def unpack64(self, x: ReadableBuffer, /) -> int: return struct.unpack(self._fmt64, x)[0] - def unpack64s(self, x): + def unpack64s(self, x: ReadableBuffer, /) -> int: return struct.unpack(self._fmt64s, x)[0] - def pack32(self, x): + def pack32(self, x: int, /) -> bytes: return struct.pack(self._fmt32, x) - def pack32s(self, x): + def pack32s(self, x: int, /) -> bytes: return struct.pack(self._fmt32s, x) - def unpack32(self, x): + def unpack32(self, x: ReadableBuffer, /) -> int: return struct.unpack(self._fmt32, x)[0] - def unpack32s(self, x): + def unpack32s(self, x: ReadableBuffer, /) -> int: return struct.unpack(self._fmt32s, x)[0] - def pack16(self, x): + def pack16(self, x: int, /) -> bytes: return struct.pack(self._fmt16, x) - def pack16s(self, x): + def pack16s(self, x: int, /) -> bytes: return struct.pack(self._fmt16s, x) - def unpack16(self, x): + def unpack16(self, x: ReadableBuffer, /) -> int: return struct.unpack(self._fmt16, x)[0] - def unpack16s(self, x): + def unpack16s(self, x: ReadableBuffer, /) -> int: return struct.unpack(self._fmt16s, x)[0] - def pack8(self, x): + def pack8(self, x: int, /) -> bytes: return struct.pack(self._fmt8, x) - def pack8s(self, x): + def pack8s(self, x: int, /) -> bytes: return struct.pack(self._fmt8s, x) - def unpack8(self, x): + def unpack8(self, x: ReadableBuffer, /) -> int: return struct.unpack(self._fmt8, x)[0] - def unpack8s(self, x): + def unpack8s(self, x: ReadableBuffer, /) -> int: return struct.unpack(self._fmt8s, x)[0] From bc108928de37fcf7e90d87085e4742aeaf869ac9 Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 12 Apr 2022 23:16:50 +0300 Subject: [PATCH 283/406] Misc code quality fixes --- qiling/os/windows/dlls/advapi32.py | 42 +++++++++++++--------- qiling/os/windows/dlls/cng.py | 56 ------------------------------ qiling/os/windows/dlls/crypt32.py | 14 +++++--- qiling/os/windows/dlls/msvcrt.py | 4 +-- qiling/os/windows/dlls/ntdll.py | 30 ++++++++-------- qiling/os/windows/dlls/user32.py | 4 +-- 6 files changed, 54 insertions(+), 96 deletions(-) delete mode 100644 qiling/os/windows/dlls/cng.py diff --git a/qiling/os/windows/dlls/advapi32.py b/qiling/os/windows/dlls/advapi32.py index 67f1f8afc..e98779897 100644 --- a/qiling/os/windows/dlls/advapi32.py +++ b/qiling/os/windows/dlls/advapi32.py @@ -460,8 +460,10 @@ def hook_GetTokenInformation(ql: Qiling, address: int, params): token = ql.os.handle_manager.get(TokenHandle).obj information_value = token.get(TokenInformationClass) - ql.mem.write(ReturnLength, len(information_value).to_bytes(4, byteorder="little")) - return_size = int.from_bytes(ql.mem.read(ReturnLength, 4), byteorder="little") + + ql.mem.write_ptr(ReturnLength, len(information_value), 4) + return_size = ql.mem.read_ptr(ReturnLength, 4) + ql.log.debug("The target is checking for its permissions") if return_size > TokenInformationLength: @@ -662,13 +664,9 @@ def hook_StartServiceA(ql: Qiling, address: int, params): }) def hook_AllocateAndInitializeSid(ql: Qiling, address: int, params): count = params["nSubAuthorityCount"] - subs = b"" - - for i in range(count): - sub = params[f"nSubAuthority{i}"] - subs += sub.to_bytes(4, "little") + subs = b''.join(ql.pack32(params[f'nSubAuthority{i}']) for i in range(count)) - sid = Sid(ql, revision=1, identifier=5, subs=subs, subs_count=count) + sid = Sid(ql, revision=1, subs_count=count, identifier=5, subs=subs) sid_addr = ql.os.heap.alloc(sid.size) sid.write(sid_addr) @@ -688,34 +686,46 @@ def hook_AllocateAndInitializeSid(ql: Qiling, address: int, params): def get_adminsid(ql): global __adminsid - if __adminsid == None: - # nSubAuthority0 = SECURITY_BUILTIN_DOMAIN_RID[0x20], nSubAuthority1 = DOMAIN_ALIAS_RID_ADMINS[0x220] + + 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) + return __adminsid def get_userssid(ql): global __userssid - if __userssid == None: - # nSubAuthority0 = SECURITY_BUILTIN_DOMAIN_RID[0x20], nSubAuthority1 = DOMAIN_ALIAS_RID_USERS[0x221] + + 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 __userssid def get_guestssid(ql): global __guestssid - if __guestssid == None: - # nSubAuthority0 = SECURITY_BUILTIN_DOMAIN_RID[0x20], nSubAuthority1 = DOMAIN_ALIAS_RID_GUESTS[0x222] + + 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) + return __guestssid def get_poweruserssid(ql): global __poweruserssid - if __poweruserssid == None: - # nSubAuthority0 = SECURITY_BUILTIN_DOMAIN_RID[0x20], nSubAuthority1 = DOMAIN_ALIAS_RID_POWER_USERS[0x223] + + 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 __poweruserssid diff --git a/qiling/os/windows/dlls/cng.py b/qiling/os/windows/dlls/cng.py deleted file mode 100644 index 96c4c5412..000000000 --- a/qiling/os/windows/dlls/cng.py +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env python3 -# -# Cross Platform and Multi Architecture Advanced Binary Emulation Framework -# - - -from qiling.os.windows.const import * -from qiling.os.windows.fncc import * -from qiling.os.const import * -from qiling.os.windows.utils import * -from qiling.os.windows.thread import * -from qiling.os.windows.handle import * -from qiling.exception import * - -dllname = 'cng_dll' - -# typedef struct _OSVERSIONINFOW { -# ULONG dwOSVersionInfoSize; -# ULONG dwMajorVersion; -# ULONG dwMinorVersion; -# ULONG dwBuildNumber; -# ULONG dwPlatformId; -# WCHAR szCSDVersion[128]; -# } -# NTSYSAPI NTSTATUS RtlGetVersion( -# PRTL_OSVERSIONINFOW lpVersionInformation -# ); -@winsdkapi(cc=CDECL, dllname=dllname, replace_params={"lpVersionInformation": POINTER}) -def hook_RtlGetVersion(ql, address, params): - pointer = params["lpVersionInformation"] - size = int.from_bytes(ql.mem.read(pointer, 4), byteorder="little") - os_version_info_asked = { - "dwOSVersionInfoSize": - size, - VER_MAJORVERSION: - int.from_bytes(ql.mem.read(pointer + 4, 4), byteorder="little"), - VER_MINORVERSION: - int.from_bytes(ql.mem.read(pointer + 8, 4), byteorder="little"), - VER_BUILDNUMBER: - int.from_bytes(ql.mem.read(pointer + 12, 4), byteorder="little"), - VER_PLATFORMID: - int.from_bytes(ql.mem.read(pointer + 16, 4), byteorder="little"), - "szCSDVersion": - int.from_bytes(ql.mem.read(pointer + 20, 128), byteorder="little"), - } - ql.mem.write( - pointer + 4, - ql.os.profile.getint("SYSTEM", - "majorVersion").to_bytes(4, byteorder="little")) - ql.mem.write( - pointer + 8, - ql.os.profile.getint("SYSTEM", - "minorVersion").to_bytes(4, byteorder="little")) - - ql.log.debug("The sample is checking the windows Version!") - return STATUS_SUCCESS diff --git a/qiling/os/windows/dlls/crypt32.py b/qiling/os/windows/dlls/crypt32.py index c1fb589fd..7efa91191 100644 --- a/qiling/os/windows/dlls/crypt32.py +++ b/qiling/os/windows/dlls/crypt32.py @@ -19,9 +19,11 @@ def _CryptStringToBinary(ql: Qiling, address: int, params) -> int: string_dst = params["pbBinary"] flag_dst = params["pdwFlags"] - size_dst = int.from_bytes(ql.mem.read(size_dst_pointer, 4), byteorder="little") - if size_dst != 0 and size_dst < size_src: + size_dst = ql.mem.read_ptr(size_dst_pointer, 4) + + if size_dst and size_dst < size_src: raise QlErrorNotImplemented("API not implemented") + if flag_src == CRYPT_STRING_BASE64: # Had a padding error, hope this always works add_pad = 4 - (len(string_src) % 4) @@ -37,11 +39,13 @@ def _CryptStringToBinary(ql: Qiling, address: int, params) -> int: # Only wants the length return len(output) else: - if flag_dst != 0: + if flag_dst: # Is optional - ql.mem.write(flag_dst, flag_src.to_bytes(length=4, byteorder='little')) + ql.mem.write_ptr(flag_dst, flag_src, 4) + # Write size - ql.mem.write(size_dst_pointer, len(output).to_bytes(length=4, byteorder='little')) + ql.mem.write_ptr(size_dst_pointer, len(output), 4) + # Write result ql.mem.write(string_dst, bytes(output, encoding="utf-16le")) return 1 diff --git a/qiling/os/windows/dlls/msvcrt.py b/qiling/os/windows/dlls/msvcrt.py index c1747b1ac..73b84b3c4 100644 --- a/qiling/os/windows/dlls/msvcrt.py +++ b/qiling/os/windows/dlls/msvcrt.py @@ -573,7 +573,7 @@ def hook__time64(ql: Qiling, address: int, params): time_wasted = int(time.time()) - if dst != 0: - ql.mem.write(dst, time_wasted.to_bytes(8, "little")) + if dst: + ql.mem.write_ptr(dst, time_wasted, 8) return time_wasted diff --git a/qiling/os/windows/dlls/ntdll.py b/qiling/os/windows/dlls/ntdll.py index 3e8b838f2..5f424f777 100644 --- a/qiling/os/windows/dlls/ntdll.py +++ b/qiling/os/windows/dlls/ntdll.py @@ -34,7 +34,7 @@ def hook_memcpy(ql: Qiling, address: int, params): try: data = bytes(ql.mem.read(src, count)) ql.mem.write(dest, data) - except Exception as e: + except Exception: ql.log.exception("") return dest @@ -65,7 +65,7 @@ def _QueryInformationProcess(ql: Qiling, address: int, params): addr = ql.os.heap.alloc(pbi.size) pbi.write(addr) - value = addr.to_bytes(ql.arch.pointersize, "little") + value = ql.pack(addr) else: ql.log.debug(str(flag)) raise QlErrorNotImplemented("API not implemented") @@ -73,8 +73,8 @@ def _QueryInformationProcess(ql: Qiling, address: int, params): ql.log.debug("The target is checking the debugger via QueryInformationProcess ") ql.mem.write(dst, value) - if pt_res != 0: - ql.mem.write(pt_res, 0x8.to_bytes(1, byteorder="little")) + if pt_res: + ql.mem.write_ptr(pt_res, 8, 1) return STATUS_SUCCESS @@ -147,11 +147,11 @@ def _QuerySystemInformation(ql: Qiling, address: int, params): if (bufferLength==sbi.size): sbi.write(dst) - if pt_res != 0: - ql.mem.write(pt_res, sbi.size.to_bytes(1, byteorder="little")) + if pt_res: + ql.mem.write_ptr(pt_res, sbi.size, 1) else: - if pt_res != 0: - ql.mem.write(pt_res, sbi.size.to_bytes(1, byteorder="little")) + if pt_res: + ql.mem.write_ptr(pt_res, sbi.size, 1) return STATUS_INFO_LENGTH_MISMATCH else: @@ -253,11 +253,11 @@ def hook_ZwQueryObject(ql: Qiling, address: int, params): else: raise QlErrorNotImplemented("API not implemented") - if dest != 0 and params["Handle"] != 0: + if dest and params["Handle"]: res.write(dest) - if size_dest != 0: - ql.mem.write(size_dest, res.size.to_bytes(4, "little")) + if size_dest: + ql.mem.write_ptr(size_dest, res.size, 4) return STATUS_SUCCESS @@ -303,11 +303,11 @@ def _SetInformationProcess(ql: Qiling, address: int, params): elif flag == ProcessBreakOnTermination: ql.log.debug("The target may be attempting modify a the 'critical' flag of the process") - elif flag == ProcessExecuteFlags: + elif flag == ProcessExecuteFlags: ql.log.debug("The target may be attempting to modify DEP for the process") - if dst != 0: - ql.mem.write(dst, 0x0.to_bytes(1, byteorder="little")) + if dst: + ql.mem.write_ptr(dst, 0, 1) elif flag == ProcessBasicInformation: pbi = structs.ProcessBasicInformation( @@ -323,7 +323,7 @@ def _SetInformationProcess(ql: Qiling, address: int, params): 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 = addr.to_bytes(ql.arch.pointersize, "little") + value = ql.pack(addr) else: ql.log.debug(str(flag)) raise QlErrorNotImplemented("API not implemented") diff --git a/qiling/os/windows/dlls/user32.py b/qiling/os/windows/dlls/user32.py index bca944188..a761ded42 100644 --- a/qiling/os/windows/dlls/user32.py +++ b/qiling/os/windows/dlls/user32.py @@ -866,8 +866,8 @@ def hook_GetWindowThreadProcessId(ql: Qiling, address: int, params): dst = params["lpdwProcessId"] - if dst != 0: - ql.mem.write(dst, pid.to_bytes(4, "little")) + if dst: + ql.mem.write_ptr(dst, pid, 4) return pid From 56c0bf7bdf774b15a42f4a7a5f96c6b2b55873a6 Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 12 Apr 2022 23:21:03 +0300 Subject: [PATCH 284/406] Turn cache entry into a named tuple --- qiling/loader/pe.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/qiling/loader/pe.py b/qiling/loader/pe.py index 01f9d9d11..095cdb469 100644 --- a/qiling/loader/pe.py +++ b/qiling/loader/pe.py @@ -4,7 +4,7 @@ # import os, pefile, pickle, secrets, ntpath -from typing import Any, MutableMapping, Optional, Mapping, Sequence, Tuple +from typing import Any, MutableMapping, NamedTuple, Optional, Mapping, Sequence, Tuple, Union from unicorn import UcError from unicorn.x86_const import UC_X86_REG_CR4, UC_X86_REG_CR8 @@ -20,13 +20,12 @@ from qiling.os.windows.structs import * from .loader import QlLoader, Image -class QlPeCacheEntry: - def __init__(self, ba: int, data: bytearray, cmdlines: Sequence, import_symbols: MutableMapping, import_table: MutableMapping): - self.ba = ba - self.data = data - self.cmdlines = cmdlines - self.import_symbols = import_symbols - self.import_table = import_table +class QlPeCacheEntry(NamedTuple): + ba: int + data: bytearray + cmdlines: Sequence + import_symbols: MutableMapping[int, dict] + import_table: MutableMapping[Union[str, int], int] class QlPeCache: @@ -52,10 +51,9 @@ def restore(self, path: str) -> Optional[QlPeCacheEntry]: def save(self, path: str, entry: QlPeCacheEntry) -> None: fcache = QlPeCache.cache_filename(path) - data = (entry.ba, entry.data, entry.cmdlines, entry.import_symbols, entry.import_table) # cache this dll file with open(fcache, "wb") as fcache_file: - pickle.dump(data, fcache_file) + pickle.dump(entry, fcache_file) class Process: From b172ba1b7635d474035c3e4de64e744de9c0fe51 Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 12 Apr 2022 23:22:04 +0300 Subject: [PATCH 285/406] Slightly better typing annotations on PE loader --- qiling/loader/pe.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/qiling/loader/pe.py b/qiling/loader/pe.py index 095cdb469..5366623f8 100644 --- a/qiling/loader/pe.py +++ b/qiling/loader/pe.py @@ -4,7 +4,7 @@ # import os, pefile, pickle, secrets, ntpath -from typing import Any, MutableMapping, NamedTuple, Optional, Mapping, Sequence, Tuple, Union +from typing import Any, Dict, MutableMapping, NamedTuple, Optional, Mapping, Sequence, Tuple, Union from unicorn import UcError from unicorn.x86_const import UC_X86_REG_CR4, UC_X86_REG_CR8 @@ -58,10 +58,15 @@ def save(self, path: str, entry: QlPeCacheEntry) -> None: class Process: # let linter recognize mixin members + cmdline: bytes + pe_image_address: int + stack_address: int + stack_size: int + dlls: MutableMapping[str, int] import_address_table: MutableMapping[str, Mapping] - import_symbols: MutableMapping[int, Any] - export_symbols: MutableMapping[int, Any] + import_symbols: MutableMapping[int, Dict[str, Any]] + export_symbols: MutableMapping[int, Dict[str, Any]] libcache: Optional[QlPeCache] def __init__(self, ql: Qiling): From 958361c1e1e4c77d1716bb8a3f7168576fb22bf4 Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 12 Apr 2022 23:38:00 +0300 Subject: [PATCH 286/406] Get rid of the problematic _typeshed import --- qiling/core_struct.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qiling/core_struct.py b/qiling/core_struct.py index 853a765c3..08c325a07 100644 --- a/qiling/core_struct.py +++ b/qiling/core_struct.py @@ -10,11 +10,13 @@ ############################################## import struct -from _typeshed import ReadableBuffer +from typing import Union from .const import QL_ENDIAN from .exception import QlErrorStructConversion +ReadableBuffer = Union[bytes, bytearray, memoryview] + # Don't assume self is Qiling. class QlCoreStructs: def __init__(self, endian: QL_ENDIAN, bit: int): From 0484e7332507312cf77318e62f4876884a6c526c Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 13 Apr 2022 02:11:16 +0300 Subject: [PATCH 287/406] Move EVM hooks handling to EVM arch --- qiling/arch/evm/evm.py | 4 +++- qiling/arch/evm/hooks.py | 33 ++++++++++++++++++++++++++++++++- qiling/core_hooks.py | 16 ---------------- 3 files changed, 35 insertions(+), 18 deletions(-) diff --git a/qiling/arch/evm/evm.py b/qiling/arch/evm/evm.py index b7479cd5b..a402e0d35 100644 --- a/qiling/arch/evm/evm.py +++ b/qiling/arch/evm/evm.py @@ -6,7 +6,7 @@ from qiling.const import * from ..arch import QlArch from .vm.evm import QlArchEVMEmulator - +from .hooks import monkeypath_core_hooks class QlArchEVM(QlArch): type = QL_ARCH.EVM @@ -16,6 +16,8 @@ def __init__(self, ql) -> None: super(QlArchEVM, self).__init__(ql) self.evm = QlArchEVMEmulator(self.ql) + monkeypath_core_hooks(self.ql) + def run(self, msg): return self.evm.vm.execute_message(msg) diff --git a/qiling/arch/evm/hooks.py b/qiling/arch/evm/hooks.py index f796929ca..02ec83672 100644 --- a/qiling/arch/evm/hooks.py +++ b/qiling/arch/evm/hooks.py @@ -2,6 +2,7 @@ # # Cross Platform and Multi Architecture Advanced Binary Emulation Framework +import types from qiling.core_hooks_types import Hook, HookAddr, HookIntr, HookRet @@ -68,4 +69,34 @@ def evm_hook_del(hook_type, h): if hook_type in ["HOOK_CODE"]: evm_hooks_info.hook_code_list.remove(h) elif hook_type in ["HOOK_INSN"]: - evm_hooks_info.hook_insn_list.remove(h) \ No newline at end of file + evm_hooks_info.hook_insn_list.remove(h) + +def monkeypath_core_hooks(ql): + """Monkeypath core hooks for evm + """ + + def __evm_hook_code(self, callback, user_data=None, begin=1, end=0): + return ql_evm_hooks(self, 'HOOK_CODE', callback, user_data, begin, end) + + def __evm_hook_address(self, callback, address, user_data=None): + hook = HookAddr(callback, address, user_data) + + return evm_hook_address(self, 'HOOK_ADDR', hook, address) + + def __evm_hook_insn(self, callback, arg1, user_data=None, begin=1, end=0): + return evm_hook_insn(self, 'HOOK_INSN', callback, arg1, user_data, begin, end) + + def __evm_hook_del(self, *args): + if len(args) != 1 and len(args) != 2: + return + + if isinstance(args[0], HookRet): + args[0].remove() + return + + return evm_hook_del(*args) + + ql.hook_code = types.MethodType(__evm_hook_code, ql) + ql.hook_address = types.MethodType(__evm_hook_address, ql) + ql.hook_insn = types.MethodType(__evm_hook_insn, ql) + ql.hook_del = types.MethodType(__evm_hook_del, ql) diff --git a/qiling/core_hooks.py b/qiling/core_hooks.py index 7b5f85ac7..8a8133811 100644 --- a/qiling/core_hooks.py +++ b/qiling/core_hooks.py @@ -240,10 +240,6 @@ def ql_hook(self, hook_type: int, callback: Callable, user_data=None, begin=1, e def hook_code(self, callback, user_data=None, begin=1, end=0): - if self.interpreter: - from .arch.evm.hooks import ql_evm_hooks - return ql_evm_hooks(self, 'HOOK_CODE', callback, user_data, begin, end) - return self.ql_hook(UC_HOOK_CODE, callback, user_data, begin, end) @@ -283,10 +279,6 @@ def hook_mem_invalid(self, callback, user_data=None, begin=1, end=0): def hook_address(self, callback, address, user_data=None): hook = HookAddr(callback, address, user_data) - if self.interpreter: - from .arch.evm.hooks import evm_hook_address - return evm_hook_address(self, 'HOOK_ADDR', hook, address) - if address not in self._addr_hook_fuc: self._addr_hook_fuc[address] = self._ql_hook_addr_internal(self._hook_addr_cb, address) @@ -322,10 +314,6 @@ def hook_mem_fetch(self, callback, user_data=None, begin=1, end=0): def hook_insn(self, callback, arg1, user_data=None, begin=1, end=0): - if self.interpreter: - from .arch.evm.hooks import evm_hook_insn - return evm_hook_insn(self, 'HOOK_INSN', callback, arg1, user_data, begin, end) - return self.ql_hook(UC_HOOK_INSN, callback, user_data, begin, end, arg1) @@ -339,10 +327,6 @@ def hook_del(self, *args): hook_type, h = args - if self.interpreter: - from .arch.evm.hooks import evm_hook_del - return evm_hook_del(hook_type, h) - def __handle_common(t: int) -> None: if t in self._hook: if h in self._hook[t]: From 6d8ada500678eead8b09ca7353afd7339f31eb73 Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 13 Apr 2022 02:52:24 +0300 Subject: [PATCH 288/406] Tidy up EVM hooks --- qiling/arch/evm/hooks.py | 94 ++++++++++++++++------------------------ 1 file changed, 38 insertions(+), 56 deletions(-) diff --git a/qiling/arch/evm/hooks.py b/qiling/arch/evm/hooks.py index 02ec83672..237a33753 100644 --- a/qiling/arch/evm/hooks.py +++ b/qiling/arch/evm/hooks.py @@ -3,88 +3,70 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework import types +from typing import MutableMapping, MutableSequence from qiling.core_hooks_types import Hook, HookAddr, HookIntr, HookRet - class QlArchEVMHooks: def __init__(self) -> None: - super().__init__() - self.hook_code_list = [] - self.hook_insn_list = [] - self.hook_addr_dict = {} + self.hook_code_list: MutableSequence[Hook] = [] + self.hook_insn_list: MutableSequence[HookIntr] = [] + self.hook_addr_dict: MutableMapping[int, MutableSequence[HookAddr]] = {} evm_hooks_info = QlArchEVMHooks() -def _ql_evm_hook(ql, hook_type, h, *args): - base_type = [ - "HOOK_CODE", - "HOOK_INSN", - "HOOK_ADDR" - ] +def evm_hook_code(ql, callback, user_data=None, begin=1, end=0, *args): + h = Hook(callback, user_data, begin, end) + evm_hooks_info.hook_code_list.append(h) + + return HookRet(ql, 'HOOK_CODE', h) - if hook_type in base_type: - if hook_type in ["HOOK_CODE"]: - evm_hooks_info.hook_code_list.append(h) - elif hook_type in ["HOOK_INSN"]: - evm_hooks_info.hook_insn_list.append(h) - elif hook_type in ["HOOK_ADDR"]: - address = args[0] +def evm_hook_insn(ql, callback, intno, user_data=None, begin=1, end=0): + h = HookIntr(callback, intno, user_data) + evm_hooks_info.hook_insn_list.append(h) - if address not in evm_hooks_info.hook_addr_dict.keys(): - evm_hooks_info.hook_addr_dict[address] = [] - - evm_hooks_info.hook_addr_dict[address].append(h) + return HookRet(ql, 'HOOK_INSN', h) -def ql_evm_hooks(ql, hook_type, callback, user_data=None, begin=1, end=0, *args): - h = Hook(callback, user_data, begin, end) - _ql_evm_hook(ql, hook_type, h, *args) - return HookRet(ql, hook_type, h) +def evm_hook_address(ql, callback, address, user_data): + h = HookAddr(callback, address, user_data) -def evm_hook_insn(ql, hook_type, callback, intno, user_data=None, begin=1, end=0): - h = HookIntr(callback, intno, user_data) - _ql_evm_hook(ql, hook_type, h) - return HookRet(ql, hook_type, h) + if address not in evm_hooks_info.hook_addr_dict: + evm_hooks_info.hook_addr_dict[address] = [] -def evm_hook_address(ql, hook_type, h, address): - _ql_evm_hook(ql, hook_type, h, address) - return HookRet(ql, hook_type, h) + evm_hooks_info.hook_addr_dict[address].append(h) + + return HookRet(ql, 'HOOK_ADDR', h) def evm_hook_del(hook_type, h): - base_type = [ - "HOOK_CODE", - "HOOK_INSN", - "HOOK_ADDR" - ] - - if isinstance(h, HookAddr): - if h.addr in evm_hooks_info.hook_addr_dict.keys(): - if h in evm_hooks_info.hook_addr_dict[h.addr]: - evm_hooks_info.hook_addr_dict[h.addr].remove(h) - if len(evm_hooks_info.hook_addr_dict[h.addr]) == 0: - del evm_hooks_info.hook_addr_dict[h.addr] - - if hook_type in base_type: - if hook_type in ["HOOK_CODE"]: - evm_hooks_info.hook_code_list.remove(h) - elif hook_type in ["HOOK_INSN"]: - evm_hooks_info.hook_insn_list.remove(h) + if hook_type == "HOOK_CODE": + evm_hooks_info.hook_code_list.remove(h) + + elif hook_type == "HOOK_INSN": + evm_hooks_info.hook_insn_list.remove(h) + + elif hook_type == 'HOOK_ADDR': + if h.addr in evm_hooks_info.hook_addr_dict: + hooks_list = evm_hooks_info.hook_addr_dict[h.addr] + + if h in hooks_list: + hooks_list.remove(h) + + if not hooks_list: + del evm_hooks_info.hook_addr_dict[h.addr] def monkeypath_core_hooks(ql): """Monkeypath core hooks for evm """ def __evm_hook_code(self, callback, user_data=None, begin=1, end=0): - return ql_evm_hooks(self, 'HOOK_CODE', callback, user_data, begin, end) + return evm_hook_code(self, callback, user_data, begin, end) def __evm_hook_address(self, callback, address, user_data=None): - hook = HookAddr(callback, address, user_data) - - return evm_hook_address(self, 'HOOK_ADDR', hook, address) + return evm_hook_address(self, callback, address, user_data) def __evm_hook_insn(self, callback, arg1, user_data=None, begin=1, end=0): - return evm_hook_insn(self, 'HOOK_INSN', callback, arg1, user_data, begin, end) + return evm_hook_insn(self, callback, arg1, user_data, begin, end) def __evm_hook_del(self, *args): if len(args) != 1 and len(args) != 2: From 8796fcbf0218d3352ca9c7f2fbfeb8a718700abc Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 14 Apr 2022 00:11:26 +0300 Subject: [PATCH 289/406] Remove ostype property from core and move to OS --- qiling/core.py | 19 ------------------- qiling/os/blob/blob.py | 5 ++++- qiling/os/dos/dos.py | 4 +++- qiling/os/freebsd/freebsd.py | 3 +++ qiling/os/linux/linux.py | 4 +++- qiling/os/macos/macos.py | 4 +++- qiling/os/mcu/mcu.py | 6 ++---- qiling/os/os.py | 2 ++ qiling/os/qnx/qnx.py | 4 +++- qiling/os/uefi/uefi.py | 3 +++ qiling/os/windows/windows.py | 4 +++- 11 files changed, 29 insertions(+), 29 deletions(-) diff --git a/qiling/core.py b/qiling/core.py index 8daae5732..0bdf8c666 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -306,25 +306,6 @@ def env(self) -> MutableMapping[AnyStr, AnyStr]: """ return self._env - @property - def ostype(self) -> QL_OS: - """ The emulated os type. - - Note: Please pass None or one of the strings below to Qiling.__init__. - If you use shellcode, you must specify ostype and archtype manually. - - Type: int. - Values: - - "macos" : macOS. - - "darwin" : an alias to "macos". - - "freebsd" : FreeBSD - - "windows" : Windows - - "uefi" : UEFI - - "dos" : DOS - Example: Qiling(code=b"\x90", ostype="macos", archtype="x8664") - """ - return self._ostype - @property def code(self) -> bytes: """ The shellcode to execute. diff --git a/qiling/os/blob/blob.py b/qiling/os/blob/blob.py index 09be61e99..3f1b6fff5 100644 --- a/qiling/os/blob/blob.py +++ b/qiling/os/blob/blob.py @@ -5,7 +5,7 @@ from qiling import Qiling from qiling.cc import QlCC, intel, arm, mips -from qiling.const import QL_ARCH +from qiling.const import QL_ARCH, QL_OS from qiling.os.fcall import QlFunctionCall from qiling.os.os import QlOs @@ -17,6 +17,9 @@ class QlOsBlob(QlOs): resolve_fcall_params(), heap or add_fs_mapper() are based on os. To keep the consistence of api usage, QlOsBlob is introduced and placed at its loader temporarily. """ + + type = QL_OS.BLOB + def __init__(self, ql: Qiling): super(QlOsBlob, self).__init__(ql) diff --git a/qiling/os/dos/dos.py b/qiling/os/dos/dos.py index 40cf5da17..6fb4d330c 100644 --- a/qiling/os/dos/dos.py +++ b/qiling/os/dos/dos.py @@ -10,7 +10,7 @@ from unicorn import UcError from qiling import Qiling -from qiling.const import QL_INTERCEPT +from qiling.const import QL_OS, QL_INTERCEPT from qiling.os.os import QlOs from .interrupts import handlers @@ -29,6 +29,8 @@ class Flags(IntEnum): IOPL = (3 << 12) # io privilege class QlOsDos(QlOs): + type = QL_OS.DOS + def __init__(self, ql: Qiling): super(QlOsDos, self).__init__(ql) diff --git a/qiling/os/freebsd/freebsd.py b/qiling/os/freebsd/freebsd.py index 6b368466e..78fd09b78 100644 --- a/qiling/os/freebsd/freebsd.py +++ b/qiling/os/freebsd/freebsd.py @@ -7,9 +7,12 @@ from unicorn.x86_const import UC_X86_INS_SYSCALL from qiling.arch.x86_utils import GDTManager, SegmentManager86 +from qiling.const import QL_OS from qiling.os.posix.posix import QlOsPosix class QlOsFreebsd(QlOsPosix): + type = QL_OS.FREEBSD + def __init__(self, ql): super(QlOsFreebsd, self).__init__(ql) diff --git a/qiling/os/linux/linux.py b/qiling/os/linux/linux.py index 8e443cbc2..fb63a1037 100644 --- a/qiling/os/linux/linux.py +++ b/qiling/os/linux/linux.py @@ -12,7 +12,7 @@ from qiling.arch.x86_utils import GDTManager, SegmentManager86, SegmentManager64 from qiling.arch import arm_utils from qiling.cc import QlCC, intel, arm, mips, riscv -from qiling.const import QL_ARCH, QL_INTERCEPT +from qiling.const import QL_ARCH, QL_OS, QL_INTERCEPT from qiling.os.fcall import QlFunctionCall from qiling.os.const import * from qiling.os.posix.const import NR_OPEN @@ -22,6 +22,8 @@ from . import thread class QlOsLinux(QlOsPosix): + type = QL_OS.LINUX + def __init__(self, ql: Qiling): super(QlOsLinux, self).__init__(ql) diff --git a/qiling/os/macos/macos.py b/qiling/os/macos/macos.py index ec826825c..276fed90a 100644 --- a/qiling/os/macos/macos.py +++ b/qiling/os/macos/macos.py @@ -11,7 +11,7 @@ from qiling import Qiling from qiling.arch.x86_utils import GDTManager, SegmentManager64 from qiling.cc import intel -from qiling.const import QL_ARCH, QL_VERBOSE +from qiling.const import QL_ARCH, QL_OS, QL_VERBOSE from qiling.os.fcall import QlFunctionCall from qiling.os.posix.posix import QlOsPosix from qiling.os.macos.events.macos import QlMacOSEvManager @@ -20,6 +20,8 @@ from qiling.os.macos.structs import kmod_info_t, POINTER64 class QlOsMacos(QlOsPosix): + type = QL_OS.MACOS + def __init__(self, ql: Qiling): super(QlOsMacos, self).__init__(ql) diff --git a/qiling/os/mcu/mcu.py b/qiling/os/mcu/mcu.py index cb3c64818..a2f8279a8 100644 --- a/qiling/os/mcu/mcu.py +++ b/qiling/os/mcu/mcu.py @@ -3,13 +3,11 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from unicorn import UcError - +from qiling.const import QL_OS from qiling.os.os import QlOs class QlOsMcu(QlOs): - def __init__(self, ql): - super(QlOsMcu, self).__init__(ql) + type = QL_OS.MCU def run(self): pass diff --git a/qiling/os/os.py b/qiling/os/os.py index 2c0e11812..ea84748b4 100644 --- a/qiling/os/os.py +++ b/qiling/os/os.py @@ -20,6 +20,8 @@ from .path import QlOsPath class QlOs: + type: QL_OS + Resolver = Callable[[int], Any] def __init__(self, ql: Qiling, resolvers: Mapping[Any, Resolver] = {}): diff --git a/qiling/os/qnx/qnx.py b/qiling/os/qnx/qnx.py index 403533be6..7ab81952a 100644 --- a/qiling/os/qnx/qnx.py +++ b/qiling/os/qnx/qnx.py @@ -17,13 +17,15 @@ from qiling.os.qnx.structs import _thread_local_storage from qiling.cc import QlCC, intel, arm, mips, riscv -from qiling.const import QL_ARCH, QL_INTERCEPT +from qiling.const import QL_ARCH, QL_OS, QL_INTERCEPT from qiling.os.fcall import QlFunctionCall from qiling.os.const import * from qiling.os.posix.const import NR_OPEN from qiling.os.posix.posix import QlOsPosix class QlOsQnx(QlOsPosix): + type = QL_OS.QNX + def __init__(self, ql: Qiling): super(QlOsQnx, self).__init__(ql) diff --git a/qiling/os/uefi/uefi.py b/qiling/os/uefi/uefi.py index 3923a3c26..1eac75363 100644 --- a/qiling/os/uefi/uefi.py +++ b/qiling/os/uefi/uefi.py @@ -9,6 +9,7 @@ from qiling import Qiling from qiling.cc import QlCC, intel +from qiling.const import QL_OS from qiling.os.const import * from qiling.os.memory import QlMemoryHeap from qiling.os.os import QlOs, QlOsUtils @@ -18,6 +19,8 @@ from qiling.os.uefi.smm import SmmEnv class QlOsUefi(QlOs): + type = QL_OS.UEFI + def __init__(self, ql: Qiling): super().__init__(ql) diff --git a/qiling/os/windows/windows.py b/qiling/os/windows/windows.py index 87e3b5ea1..e9d2d0b0b 100644 --- a/qiling/os/windows/windows.py +++ b/qiling/os/windows/windows.py @@ -12,7 +12,7 @@ from qiling.arch.x86_const import GS_SEGMENT_ADDR, GS_SEGMENT_SIZE, FS_SEGMENT_ADDR, FS_SEGMENT_SIZE from qiling.arch.x86_utils import GDTManager, SegmentManager86, SegmentManager64 from qiling.cc import intel -from qiling.const import QL_ARCH, QL_INTERCEPT +from qiling.const import QL_ARCH, QL_OS, QL_INTERCEPT from qiling.exception import QlErrorSyscallError, QlErrorSyscallNotFound from qiling.os.fcall import QlFunctionCall from qiling.os.memory import QlMemoryHeap @@ -30,6 +30,8 @@ import qiling.os.windows.dlls as api class QlOsWindows(QlOs): + type = QL_OS.WINDOWS + def __init__(self, ql: Qiling): super().__init__(ql) From f0c16da89be0b69c673cd6318261421797976610 Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 14 Apr 2022 00:20:01 +0300 Subject: [PATCH 290/406] Adjust ostype usages --- qiling/core.py | 15 +- qiling/debugger/disassember.py | 2 +- qiling/debugger/gdb/gdb.py | 12 +- qiling/extensions/idaplugin/qilingida.py | 2 +- qiling/extensions/report/report.py | 4 +- qiling/loader/elf.py | 4 +- qiling/os/linux/thread.py | 2 +- qiling/os/os.py | 8 +- qiling/os/posix/const_mapping.py | 25 ++-- qiling/os/posix/posix.py | 8 +- qiling/os/posix/syscall/fcntl.py | 2 +- qiling/os/posix/syscall/mman.py | 4 +- qiling/os/posix/syscall/socket.py | 22 +-- qiling/os/posix/syscall/stat.py | 20 +-- qiling/os/windows/utils.py | 179 +++++++++++------------ qiling/utils.py | 2 +- 16 files changed, 153 insertions(+), 158 deletions(-) diff --git a/qiling/core.py b/qiling/core.py index 0bdf8c666..d32c14052 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -159,12 +159,11 @@ def __init__( assert type(endian) is QL_ENDIAN self._arch = arch_setup(archtype, endian, thumb, self) - self._ostype = ostype self.uc = self.arch.uc # Once we finish setting up arch, we can init QlCoreStructs and QlCoreHooks - if not self.interpreter: + if ostype not in QL_OS_INTERPRETER: QlCoreStructs.__init__(self, self.arch.endian, self.arch.bits) QlCoreHooks.__init__(self, self.uc) @@ -178,19 +177,19 @@ def __init__( ########### # Profile # ########### - self._profile = profile_setup(self, self.ostype, profile) + self._profile = profile_setup(self, ostype, profile) ########## # Loader # ########## - self._loader = loader_setup(self, self.ostype, libcache) + self._loader = loader_setup(self, ostype, libcache) ############## # Components # ############## - if not self.interpreter: + if ostype not in QL_OS_INTERPRETER: self._mem = component_setup("os", "memory", self) - self._os = os_setup(self.ostype, self) + self._os = os_setup(ostype, self) # Run the loader self.loader.run() @@ -341,7 +340,7 @@ def interpreter(self) -> bool: Type: bool """ - return self.ostype in QL_OS_INTERPRETER + return self.os.type in QL_OS_INTERPRETER @property def baremetal(self) -> bool: @@ -350,7 +349,7 @@ def baremetal(self) -> bool: Type: bool """ - return self.ostype in QL_OS_BAREMETAL + return self.os.type in QL_OS_BAREMETAL @property def host(self) -> QlHost: diff --git a/qiling/debugger/disassember.py b/qiling/debugger/disassember.py index ffcbd5017..fea90563a 100644 --- a/qiling/debugger/disassember.py +++ b/qiling/debugger/disassember.py @@ -17,7 +17,7 @@ def __init__(self, ql:Qiling): def disasm_all_lines(self): disasm_result = [] - if self.ql.ostype == QL_OS.LINUX: + if self.ql.os.type == QL_OS.LINUX: disasm_result = self.disasm_elf() return disasm_result diff --git a/qiling/debugger/gdb/gdb.py b/qiling/debugger/gdb/gdb.py index d3d5d4da3..c07a9699f 100644 --- a/qiling/debugger/gdb/gdb.py +++ b/qiling/debugger/gdb/gdb.py @@ -63,7 +63,7 @@ def __init__(self, ql: Qiling, ip: str = '127.0.01', port: int = 9999): if self.ql.baremetal: self.entry_point = self.ql.loader.entry_point - elif self.ql.ostype in (QL_OS.LINUX, QL_OS.FREEBSD) and not self.ql.code: + elif self.ql.os.type in (QL_OS.LINUX, QL_OS.FREEBSD) and not self.ql.code: self.entry_point = self.ql.os.elf_entry else: self.entry_point = self.ql.os.entry_point @@ -486,7 +486,7 @@ def handle_q(subcmd): xml_folder = arch_convert_str(self.ql.arch.type).lower() xfercmd_file = os.path.join(xfercmd_abspath,"xml",xml_folder, xfercmd_file) - if os.path.exists(xfercmd_file) and self.ql.ostype is not QL_OS.WINDOWS: + if os.path.exists(xfercmd_file) and self.ql.os.type is not QL_OS.WINDOWS: with open(xfercmd_file, 'r') as f: file_contents = f.read() self.send("l%s" % file_contents) @@ -496,7 +496,7 @@ def handle_q(subcmd): elif subcmd.startswith('Xfer:threads:read::0,'): - if self.ql.ostype in QL_OS_NONPID or self.ql.baremetal: + if self.ql.os.type in QL_OS_NONPID or self.ql.baremetal: self.send("l") else: file_contents = ("\r\n\r\n") @@ -506,7 +506,7 @@ def handle_q(subcmd): if self.ql.code: return - if self.ql.ostype in (QL_OS.LINUX, QL_OS.FREEBSD): + if self.ql.os.type in (QL_OS.LINUX, QL_OS.FREEBSD): def __read_auxv() -> Iterator[int]: auxv_entries = ( AUX.AT_HWCAP, @@ -549,7 +549,7 @@ def __read_auxv() -> Iterator[int]: elif subcmd.startswith('Xfer:libraries-svr4:read:'): - if self.ql.ostype in (QL_OS.LINUX, QL_OS.FREEBSD): + if self.ql.os.type in (QL_OS.LINUX, QL_OS.FREEBSD): xml_addr_mapping=("") """ FIXME: need to find out when do we need this @@ -611,7 +611,7 @@ def handle_v(subcmd): self.send("") elif subcmd.startswith('File:open'): - if self.ql.ostype == QL_OS.UEFI or self.ql.baremetal: + if self.ql.os.type == QL_OS.UEFI or self.ql.baremetal: self.send("F-1") return diff --git a/qiling/extensions/idaplugin/qilingida.py b/qiling/extensions/idaplugin/qilingida.py index 954b9948d..7a17f6328 100644 --- a/qiling/extensions/idaplugin/qilingida.py +++ b/qiling/extensions/idaplugin/qilingida.py @@ -893,7 +893,7 @@ def start(self, *args, **kwargs): self.ql.os.stderr = QlEmuMisc.QLStdIO('stderr', sys.__stderr__.fileno()) self.exit_addr = self.ql.os.exit_point - if self.ql.ostype == QL_OS.LINUX: + if self.ql.os.type == QL_OS.LINUX: f = open(self.ql.path, 'rb') elffile = ELFFile(f) elf_header = elffile.header diff --git a/qiling/extensions/report/report.py b/qiling/extensions/report/report.py index 5a0010ded..ea46aaa6f 100644 --- a/qiling/extensions/report/report.py +++ b/qiling/extensions/report/report.py @@ -13,7 +13,7 @@ def __init__(self, ql): self.filename = ql.argv self.rootfs = ql.rootfs self.arch = list(arch_map.keys())[list(arch_map.values()).index(ql.arch.type)] - self.os = list(os_map.keys())[list(os_map.values()).index(ql.ostype)] + self.os = list(os_map.keys())[list(os_map.values()).index(ql.os.type)] self.env = ql.env self.strings = set() for string in ql.os.stats.strings: @@ -54,7 +54,7 @@ def __init__(self, ql): def generate_report(ql, pretty_print=False) -> dict: - if ql.ostype == QL_OS.WINDOWS: + if ql.os.type == QL_OS.WINDOWS: report = WindowsReport(ql) else: report = Report(ql) diff --git a/qiling/loader/elf.py b/qiling/loader/elf.py index 33e7b922f..c6ba9573f 100644 --- a/qiling/loader/elf.py +++ b/qiling/loader/elf.py @@ -123,7 +123,7 @@ def run(self): self.ql.arch.regs.arch_sp = self.stack_address # No idea why. - if self.ql.ostype == QL_OS.FREEBSD: + if self.ql.os.type == QL_OS.FREEBSD: # self.ql.arch.regs.rbp = self.stack_address + 0x40 self.ql.arch.regs.rdi = self.stack_address self.ql.arch.regs.r14 = self.stack_address @@ -361,7 +361,7 @@ def __push_str(top: int, s: str) -> int: self.skip_exit_check = (self.elf_entry != self.entry_point) # map vsyscall section for some specific needs - if self.ql.arch.type == QL_ARCH.X8664 and self.ql.ostype == QL_OS.LINUX: + if self.ql.arch.type == QL_ARCH.X8664 and self.ql.os.type == QL_OS.LINUX: _vsyscall_addr = int(self.profile.get('vsyscall_address'), 0) _vsyscall_size = int(self.profile.get('vsyscall_size'), 0) diff --git a/qiling/os/linux/thread.py b/qiling/os/linux/thread.py index 154a709f5..a5d0d8d24 100644 --- a/qiling/os/linux/thread.py +++ b/qiling/os/linux/thread.py @@ -141,7 +141,7 @@ def path(self): @path.setter def path(self, p: QlOsPath): - self._path = QlOsPath(self.ql.rootfs, p.cwd, self.ql.ostype) + self._path = QlOsPath(self.ql.rootfs, p.cwd, self.ql.os.type) @property def log_file_fd(self): diff --git a/qiling/os/os.py b/qiling/os/os.py index ea84748b4..dedca2d60 100644 --- a/qiling/os/os.py +++ b/qiling/os/os.py @@ -40,10 +40,10 @@ def __init__(self, ql: Qiling, resolvers: Mapping[Any, Resolver] = {}): self.profile = self.ql.profile self.exit_code = 0 - if ql.ostype in QL_OS_POSIX + (QL_OS.WINDOWS, QL_OS.DOS): + if self.type in QL_OS_POSIX + (QL_OS.WINDOWS, QL_OS.DOS): cwd = self.profile.get("MISC", "current_path") - self.path = QlOsPath(ql.rootfs, cwd, ql.ostype) + self.path = QlOsPath(ql.rootfs, cwd, self.type) self.fs_mapper = QlFsMapper(self.path) self.user_defined_api = { @@ -217,10 +217,10 @@ def set_api(self, api_name: str, intercept_function: Callable, intercept: QL_INT `QL_INTERCEPT.EXIT` : run handler after the target API is called """ - if self.ql.ostype == QL_OS.UEFI: + if self.ql.os.type == QL_OS.UEFI: api_name = f'hook_{api_name}' - if (self.ql.ostype in (QL_OS.WINDOWS, QL_OS.UEFI, QL_OS.DOS)) or (self.ql.ostype in (QL_OS_POSIX) and self.ql.loader.is_driver): + if (self.ql.os.type in (QL_OS.WINDOWS, QL_OS.UEFI, QL_OS.DOS)) or (self.ql.os.type in (QL_OS_POSIX) and self.ql.loader.is_driver): self.user_defined_api[intercept][api_name] = intercept_function else: self.add_function_hook(api_name, intercept_function, intercept) diff --git a/qiling/os/posix/const_mapping.py b/qiling/os/posix/const_mapping.py index 02756b975..2dd15a21f 100644 --- a/qiling/os/posix/const_mapping.py +++ b/qiling/os/posix/const_mapping.py @@ -46,10 +46,13 @@ def flag_mapping(flags, mapping_name, mapping_from, mapping_to): f = {} t = {} - if ql.host.os == None: + host_os = ql.host.os + virt_os = ql.os.type + + if host_os is None: return flags - - if ql.ostype == QL_OS.LINUX: + + if virt_os == QL_OS.LINUX: if ql.arch.type in (QL_ARCH.X86, QL_ARCH.X8664): f = linux_x86_open_flags elif ql.arch.type in (QL_ARCH.ARM, QL_ARCH.ARM64): @@ -59,23 +62,23 @@ def flag_mapping(flags, mapping_name, mapping_from, mapping_to): elif ql.arch.type in (QL_ARCH.RISCV, QL_ARCH.RISCV64): f = linux_riscv_open_flags - elif ql.ostype == QL_OS.MACOS: + elif virt_os == QL_OS.MACOS: if ql.arch.type in (QL_ARCH.X86, QL_ARCH.X8664): f = macos_x86_open_flags - elif ql.ostype == QL_OS.FREEBSD: + elif virt_os == QL_OS.FREEBSD: f = freebsd_x86_open_flags - elif ql.ostype == QL_OS.WINDOWS: + elif virt_os == QL_OS.WINDOWS: f = windows_x86_open_flags - elif ql.ostype == QL_OS.QNX: + elif virt_os == QL_OS.QNX: f = qnx_arm64_open_flags - if ql.host.os == QL_OS.LINUX: + if host_os == QL_OS.LINUX: t = linux_x86_open_flags - elif ql.host.os == QL_OS.MACOS: + elif host_os == QL_OS.MACOS: t = macos_x86_open_flags - elif ql.host.os == QL_OS.FREEBSD: + elif host_os == QL_OS.FREEBSD: t = freebsd_x86_open_flags - elif ql.host.os == QL_OS.WINDOWS: + elif host_os == QL_OS.WINDOWS: t = windows_x86_open_flags if f == t: diff --git a/qiling/os/posix/posix.py b/qiling/os/posix/posix.py index 651324dff..d66e68161 100644 --- a/qiling/os/posix/posix.py +++ b/qiling/os/posix/posix.py @@ -90,10 +90,10 @@ def __init__(self, ql: Qiling): }[self.ql.arch.type] # handle some special cases - if (self.ql.arch.type == QL_ARCH.ARM64) and (self.ql.ostype == QL_OS.MACOS): + if (self.ql.arch.type == QL_ARCH.ARM64) and (self.type == QL_OS.MACOS): self.__syscall_id_reg = UC_ARM64_REG_X16 - elif (self.ql.arch.type == QL_ARCH.ARM) and (self.ql.ostype == QL_OS.QNX): + elif (self.ql.arch.type == QL_ARCH.ARM) and (self.type == 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 @@ -108,7 +108,7 @@ def __init__(self, ql: Qiling): }[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.syscall_mapper = ql_syscall_mapping_function(self.type, self.ql.arch.type) self._fd = QlFileDes() @@ -197,7 +197,7 @@ def load_syscall(self): 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)) + os_syscalls = __get_os_module(ostype_convert_str(self.type)) posix_syscalls = __get_os_module('posix') # look in os-specific and posix syscall hooks diff --git a/qiling/os/posix/syscall/fcntl.py b/qiling/os/posix/syscall/fcntl.py index 97681f548..9c1182105 100644 --- a/qiling/os/posix/syscall/fcntl.py +++ b/qiling/os/posix/syscall/fcntl.py @@ -27,7 +27,7 @@ def ql_syscall_open(ql: Qiling, filename: int, flags: int, mode: int): regreturn = -EMFILE else: try: - if ql.arch.type == QL_ARCH.ARM and ql.ostype != QL_OS.QNX: + if ql.arch.type == QL_ARCH.ARM and ql.os.type != QL_OS.QNX: mode = 0 flags = ql_open_flag_mapping(ql, flags) diff --git a/qiling/os/posix/syscall/mman.py b/qiling/os/posix/syscall/mman.py index 0a09f1f8e..5e3eae0bf 100755 --- a/qiling/os/posix/syscall/mman.py +++ b/qiling/os/posix/syscall/mman.py @@ -73,7 +73,7 @@ def syscall_mmap_impl(ql: Qiling, addr: int, mlen: int, prot: int, flags: int, f if ver == 2: pgoffset = pgoffset * pagesize - elif ql.arch.type == QL_ARCH.ARM and ql.ostype == QL_OS.QNX: + elif ql.arch.type == QL_ARCH.ARM and ql.os.type == QL_OS.QNX: MAP_ANONYMOUS = 0x00080000 fd = ql.unpack32s(ql.pack32s(fd)) @@ -86,7 +86,7 @@ def syscall_mmap_impl(ql: Qiling, addr: int, mlen: int, prot: int, flags: int, f mmap_base = addr mmap_size = ql.mem.align_up(mlen - (addr & (pagesize - 1))) - if ql.ostype != QL_OS.QNX: + if ql.os.type != QL_OS.QNX: mmap_base = ql.mem.align(mmap_base) if (flags & MAP_FIXED) and mmap_base != addr: diff --git a/qiling/os/posix/syscall/socket.py b/qiling/os/posix/syscall/socket.py index a27baa552..a80c7b998 100644 --- a/qiling/os/posix/syscall/socket.py +++ b/qiling/os/posix/syscall/socket.py @@ -75,7 +75,7 @@ def ql_syscall_socket(ql: Qiling, socket_domain, socket_type, socket_protocol): # ql_socket.open should use host platform based socket_type. try: emu_socket_value = socket_type - emu_socket_type = socket_type_mapping(socket_type, ql.arch.type, ql.ostype) + emu_socket_type = socket_type_mapping(socket_type, ql.arch.type, ql.os.type) socket_type = getattr(socket, emu_socket_type) ql.log.debug(f'Convert emu_socket_type {emu_socket_type}:{emu_socket_value} to host platform based socket_type {emu_socket_type}:{socket_type}') @@ -96,8 +96,8 @@ def ql_syscall_socket(ql: Qiling, socket_domain, socket_type, socket_protocol): ql.log.debug(f'{e}: {socket_domain=}, {socket_type=}, {socket_protocol=}') regreturn = -1 - socket_type = socket_type_mapping(socket_type, ql.arch.type, ql.ostype) - socket_domain = socket_domain_mapping(socket_domain, ql.arch.type, ql.ostype) + socket_type = socket_type_mapping(socket_type, ql.arch.type, ql.os.type) + socket_domain = socket_domain_mapping(socket_domain, ql.arch.type, ql.os.type) ql.log.debug("socket(%s, %s, %s) = %d" % (socket_domain, socket_type, socket_protocol, regreturn)) return regreturn @@ -151,7 +151,7 @@ def ql_syscall_getsockopt(ql: Qiling, sockfd, level, optname, optval_addr, optle try: emu_level = level - emu_level_name = socket_level_mapping(emu_level, ql.arch.type, ql.ostype) + emu_level_name = socket_level_mapping(emu_level, ql.arch.type, ql.os.type) level = getattr(socket, emu_level_name) ql.log.debug("Convert emu_level {}:{} to host platform based level {}:{}".format( emu_level_name, emu_level, emu_level_name, level)) @@ -168,12 +168,12 @@ def ql_syscall_getsockopt(ql: Qiling, sockfd, level, optname, optval_addr, optle try: emu_opt = optname - emu_level_name = socket_level_mapping(emu_level, ql.arch.type, ql.ostype) + emu_level_name = socket_level_mapping(emu_level, ql.arch.type, ql.os.type) # emu_opt_name is based on level if emu_level_name == "IPPROTO_IP": - emu_opt_name = socket_ip_option_mapping(emu_opt, ql.arch.type, ql.ostype) + emu_opt_name = socket_ip_option_mapping(emu_opt, ql.arch.type, ql.os.type) else: - emu_opt_name = socket_option_mapping(emu_opt, ql.arch.type, ql.ostype) + emu_opt_name = socket_option_mapping(emu_opt, ql.arch.type, ql.os.type) # Fix for mips if ql.arch.type == QL_ARCH.MIPS: @@ -212,7 +212,7 @@ def ql_syscall_setsockopt(ql: Qiling, sockfd, level, optname, optval_addr, optle try: try: emu_level = level - emu_level_name = socket_level_mapping(emu_level, ql.arch.type, ql.ostype) + emu_level_name = socket_level_mapping(emu_level, ql.arch.type, ql.os.type) level = getattr(socket, emu_level_name) ql.log.debug("Convert emu_level {}:{} to host platform based level {}:{}".format( emu_level_name, emu_level, emu_level_name, level)) @@ -229,12 +229,12 @@ def ql_syscall_setsockopt(ql: Qiling, sockfd, level, optname, optval_addr, optle try: emu_opt = optname - emu_level_name = socket_level_mapping(emu_level, ql.arch.type, ql.ostype) + emu_level_name = socket_level_mapping(emu_level, ql.arch.type, ql.os.type) # emu_opt_name is based on level if emu_level_name == "IPPROTO_IP": - emu_opt_name = socket_ip_option_mapping(emu_opt, ql.arch.type, ql.ostype) + emu_opt_name = socket_ip_option_mapping(emu_opt, ql.arch.type, ql.os.type) else: - emu_opt_name = socket_option_mapping(emu_opt, ql.arch.type, ql.ostype) + emu_opt_name = socket_option_mapping(emu_opt, ql.arch.type, ql.os.type) # Fix for mips if ql.arch.type == QL_ARCH.MIPS: diff --git a/qiling/os/posix/syscall/stat.py b/qiling/os/posix/syscall/stat.py index d13873aef..1c7130161 100644 --- a/qiling/os/posix/syscall/stat.py +++ b/qiling/os/posix/syscall/stat.py @@ -981,8 +981,8 @@ class QNXARMStat64(ctypes.Structure): def get_stat64_struct(ql: Qiling): if ql.arch.bits == 64: - ql.log.warning(f"Trying to stat64 on a 64bit system with {ql.ostype} and {ql.arch.type}!") - if ql.ostype == QL_OS.LINUX: + ql.log.warning(f"Trying to stat64 on a 64bit system with {ql.os.type} and {ql.arch.type}!") + if ql.os.type == QL_OS.LINUX: if ql.arch.type == QL_ARCH.X86: return LinuxX86Stat64() elif ql.arch.type == QL_ARCH.MIPS: @@ -991,22 +991,22 @@ def get_stat64_struct(ql: Qiling): return LinuxARMStat64() elif ql.arch.type in (QL_ARCH.RISCV, QL_ARCH.RISCV64): return LinuxRISCVStat() - elif ql.ostype == QL_OS.MACOS: + elif ql.os.type == QL_OS.MACOS: return MacOSStat64() - elif ql.ostype == QL_OS.QNX: + elif ql.os.type == QL_OS.QNX: return QNXARMStat64() - ql.log.warning(f"Unrecognized arch && os with {ql.arch.type} and {ql.ostype} for stat64! Fallback to Linux x86.") + ql.log.warning(f"Unrecognized arch && os with {ql.arch.type} and {ql.os.type} for stat64! Fallback to Linux x86.") return LinuxX86Stat64() def get_stat_struct(ql: Qiling): - if ql.ostype == QL_OS.FREEBSD: + if ql.os.type == QL_OS.FREEBSD: if ql.arch.type == QL_ARCH.X8664 or ql.arch.bits == 64: return FreeBSDX8664Stat() else: return FreeBSDX86Stat() - elif ql.ostype == QL_OS.MACOS: + elif ql.os.type == QL_OS.MACOS: return MacOSStat() - elif ql.ostype == QL_OS.LINUX: + elif ql.os.type == QL_OS.LINUX: if ql.arch.type == QL_ARCH.X8664: return LinuxX8664Stat() elif ql.arch.type == QL_ARCH.X86: @@ -1034,7 +1034,7 @@ def get_stat_struct(ql: Qiling): return LinuxARM64EBStat() elif ql.arch.type in (QL_ARCH.RISCV, QL_ARCH.RISCV64): return LinuxRISCVStat() - elif ql.ostype == QL_OS.QNX: + elif ql.os.type == QL_OS.QNX: if ql.arch.type == QL_ARCH.ARM64: return QNXARM64Stat() elif ql.arch.type == QL_ARCH.ARM: @@ -1042,7 +1042,7 @@ def get_stat_struct(ql: Qiling): return QNXARMStat() else: return QNXARMEBStat() - ql.log.warning(f"Unrecognized arch && os with {ql.arch.type} and {ql.ostype} for stat! Fallback to Linux x86.") + ql.log.warning(f"Unrecognized arch && os with {ql.arch.type} and {ql.os.type} for stat! Fallback to Linux x86.") return LinuxX86Stat() def __common_pack_stat_struct(stat, info) -> bytes: diff --git a/qiling/os/windows/utils.py b/qiling/os/windows/utils.py index 600642802..bd13a7630 100644 --- a/qiling/os/windows/utils.py +++ b/qiling/os/windows/utils.py @@ -32,11 +32,9 @@ def has_lib_ext(name: str) -> bool: def io_Write(ql: Qiling, in_buffer: bytes): heap = ql.os.heap - if ql.ostype == QL_OS.WINDOWS: - - if ql.loader.driver_object.MajorFunction[IRP_MJ_WRITE] == 0: - # raise error? - return (False, None) + if ql.loader.driver_object.MajorFunction[IRP_MJ_WRITE] == 0: + # raise error? + return (False, None) driver_object_cls = ql.loader.driver_object.__class__ buf = ql.mem.read(ql.loader.driver_object.DeviceObject, ctypes.sizeof(driver_object_cls)) @@ -170,104 +168,99 @@ def build_mdl(buffer_size, data=None): return mdl - # quick simple way to manage all alloc memory - if ql.ostype == QL_OS.WINDOWS: - # print("DeviceControl callback is at 0x%x" % ql.loader.driver_object.MajorFunction[IRP_MJ_DEVICE_CONTROL]) - if ql.loader.driver_object.MajorFunction[IRP_MJ_DEVICE_CONTROL] == 0: - # raise error? - return (None, None, None) + 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 + # 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 - input_buffer_size = len(in_buffer) - input_buffer_addr = __heap_alloc(input_buffer_size) - ql.mem.write(input_buffer_addr, bytes(in_buffer)) + input_buffer_size = len(in_buffer) + input_buffer_addr = __heap_alloc(input_buffer_size) + ql.mem.write(input_buffer_addr, bytes(in_buffer)) - # create new memory region to store out data - output_buffer_addr = __heap_alloc(output_buffer_size) + # 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 regions for IRP and IO_STACK_LOCATION + irp = make_irp(ql.arch.bits) + irpstack_class = irp.irpstack._type_ - irp_addr = __heap_alloc(ctypes.sizeof(irp)) - irpstack_addr = __heap_alloc(ctypes.sizeof(irpstack_class)) + irp_addr = __heap_alloc(ctypes.sizeof(irp)) + irpstack_addr = __heap_alloc(ctypes.sizeof(irpstack_class)) - # setup irp stack parameters - irpstack = irpstack_class() - # setup IRP structure - irp.irpstack = ctypes.cast(irpstack_addr, ctypes.POINTER(irpstack_class)) + # setup irp stack parameters + irpstack = irpstack_class() + # setup IRP structure + irp.irpstack = ctypes.cast(irpstack_addr, ctypes.POINTER(irpstack_class)) - ql.log.info("IRP is at 0x%x, IO_STACK_LOCATION is at 0x%x" %(irp_addr, irpstack_addr)) + ql.log.info("IRP is at 0x%x, IO_STACK_LOCATION is at 0x%x" %(irp_addr, irpstack_addr)) - 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)) + 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)) - if ctl_method == METHOD_NEITHER: - irp.UserBuffer.value = output_buffer_addr # used by IOCTL_METHOD_NEITHER + if ctl_method == METHOD_NEITHER: + irp.UserBuffer.value = output_buffer_addr # used by IOCTL_METHOD_NEITHER - # 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) + # 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) - # init data from input buffer - ql.mem.write(system_buffer_addr, bytes(in_buffer)) - irp.AssociatedIrp.SystemBuffer.value = system_buffer_addr + # init data from input buffer + ql.mem.write(system_buffer_addr, bytes(in_buffer)) + irp.AssociatedIrp.SystemBuffer.value = system_buffer_addr + + 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)) + + ql.mem.write(mdl_addr, bytes(mdl)) + irp.MdlAddress.value = mdl_addr + + # everything is done! Write IRP to memory + ql.mem.write(irp_addr, bytes(irp)) + # 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(( + (POINTER, ql.loader.driver_object.DeviceObject), + (POINTER, irp_addr) + )) + + 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]) + 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) + + io_status = irp.IoStatus + + # read output data + output_data = b'' + if io_status.Status.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): - # 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)) - - ql.mem.write(mdl_addr, bytes(mdl)) - irp.MdlAddress.value = mdl_addr - - # everything is done! Write IRP to memory - ql.mem.write(irp_addr, bytes(irp)) - - # 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(( - (POINTER, ql.loader.driver_object.DeviceObject), - (POINTER, irp_addr) - )) - - 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]) - 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) - - io_status = irp.IoStatus - - # read output data - output_data = b'' - if io_status.Status.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) - - # now free all alloc memory - __free_all(allocations) - - return io_status.Status.Status, io_status.Information.value, output_data - else: # TODO: IOCTL for non-Windows. - raise NotImplementedError + 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) + + # now free all alloc memory + __free_all(allocations) + + return io_status.Status.Status, io_status.Information.value, output_data diff --git a/qiling/utils.py b/qiling/utils.py index 580647580..18979475c 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -640,7 +640,7 @@ def verify_ret(ql, err): # timeout is acceptable in this case if err.errno in (UC_ERR_READ_UNMAPPED, UC_ERR_FETCH_UNMAPPED): - if ql.ostype == QL_OS.MACOS: + if ql.os.type == QL_OS.MACOS: if ql.loader.kext_name: # FIXME: Should I push saved RIP before every method callings of IOKit object? if ql.os.init_sp == ql.arch.regs.arch_sp - 8: From 88a7836677e8ffcdeb234bc91644b9751075762e Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 14 Apr 2022 23:29:16 +0300 Subject: [PATCH 291/406] Properly handle OS set_api --- qiling/os/linux/linux.py | 7 +------ qiling/os/os.py | 19 ++++++------------- qiling/os/posix/posix.py | 7 +++++++ qiling/os/qnx/qnx.py | 8 +------- qiling/os/uefi/uefi.py | 5 ++++- 5 files changed, 19 insertions(+), 27 deletions(-) diff --git a/qiling/os/linux/linux.py b/qiling/os/linux/linux.py index fb63a1037..35d74d118 100644 --- a/qiling/os/linux/linux.py +++ b/qiling/os/linux/linux.py @@ -3,7 +3,6 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from typing import Callable from unicorn import UcError from unicorn.x86_const import UC_X86_INS_SYSCALL @@ -12,7 +11,7 @@ from qiling.arch.x86_utils import GDTManager, SegmentManager86, SegmentManager64 from qiling.arch import arm_utils from qiling.cc import QlCC, intel, arm, mips, riscv -from qiling.const import QL_ARCH, QL_OS, QL_INTERCEPT +from qiling.const import QL_ARCH, QL_OS from qiling.os.fcall import QlFunctionCall from qiling.os.const import * from qiling.os.posix.const import NR_OPEN @@ -118,10 +117,6 @@ def hook_syscall(self, ql, intno = None): return self.load_syscall() - def add_function_hook(self, fn: str, cb: Callable, intercept: QL_INTERCEPT): - self.ql.os.function_hook.add_function_hook(fn, cb, intercept) - - def register_function_after_load(self, function): if function not in self.function_after_load_list: self.function_after_load_list.append(function) diff --git a/qiling/os/os.py b/qiling/os/os.py index dedca2d60..0b4a3c3a5 100644 --- a/qiling/os/os.py +++ b/qiling/os/os.py @@ -4,7 +4,7 @@ # import sys -from typing import Any, Iterable, Optional, Callable, Mapping, Sequence, TextIO, Tuple +from typing import Any, Hashable, Iterable, Optional, Callable, Mapping, Sequence, TextIO, Tuple from unicorn import UcError @@ -204,26 +204,19 @@ def call(self, pc: int, func: Callable, proto: Mapping[str, Any], onenter: Optio return retval - # TODO: separate this method into os-specific functionalities, instead of 'if-else' - def set_api(self, api_name: str, intercept_function: Callable, intercept: QL_INTERCEPT = QL_INTERCEPT.CALL): - """Either replace or hook OS API with a custom one. + def set_api(self, target: Hashable, handler: Callable, intercept: QL_INTERCEPT = QL_INTERCEPT.CALL): + """Either hook or replace an OS API with a custom one. Args: - api_name: target API name - intercept_function: function to call + target: target API identifier + handler: function to call intercept: `QL_INTERCEPT.CALL` : run handler instead of the existing target implementation `QL_INTERCEPT.ENTER`: run handler before the target API is called `QL_INTERCEPT.EXIT` : run handler after the target API is called """ - if self.ql.os.type == QL_OS.UEFI: - api_name = f'hook_{api_name}' - - if (self.ql.os.type in (QL_OS.WINDOWS, QL_OS.UEFI, QL_OS.DOS)) or (self.ql.os.type in (QL_OS_POSIX) and self.ql.loader.is_driver): - self.user_defined_api[intercept][api_name] = intercept_function - else: - self.add_function_hook(api_name, intercept_function, intercept) + self.user_defined_api[intercept][target] = handler # os main method; derivatives must implement one of their own def run(self) -> None: diff --git a/qiling/os/posix/posix.py b/qiling/os/posix/posix.py index d66e68161..d3c7a9a62 100644 --- a/qiling/os/posix/posix.py +++ b/qiling/os/posix/posix.py @@ -162,6 +162,13 @@ def set_syscall(self, target: Union[int, str], handler: Callable, intercept: QL_ self.posix_syscall_hooks[intercept][target] = handler + def set_api(self, target: str, handler: Callable, intercept: QL_INTERCEPT = QL_INTERCEPT.CALL): + if self.ql.loader.is_driver: + super().set_api(target, handler, intercept) + else: + self.function_hook.add_function_hook(target, handler, intercept) + + @staticmethod def getNameFromErrorCode(ret: int) -> str: """Return the hex representation of a return value and if possible diff --git a/qiling/os/qnx/qnx.py b/qiling/os/qnx/qnx.py index 7ab81952a..8d53f7917 100644 --- a/qiling/os/qnx/qnx.py +++ b/qiling/os/qnx/qnx.py @@ -3,10 +3,8 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from typing import Callable import os -from typing import Callable from unicorn import UcError from qiling import Qiling @@ -17,7 +15,7 @@ from qiling.os.qnx.structs import _thread_local_storage from qiling.cc import QlCC, intel, arm, mips, riscv -from qiling.const import QL_ARCH, QL_OS, QL_INTERCEPT +from qiling.const import QL_ARCH, QL_OS from qiling.os.fcall import QlFunctionCall from qiling.os.const import * from qiling.os.posix.const import NR_OPEN @@ -82,10 +80,6 @@ def hook_syscall(self, intno= None, int = None): return self.load_syscall() - def add_function_hook(self, fn: str, cb: Callable, intercept: QL_INTERCEPT): - self.ql.os.function_hook.add_function_hook(fn, cb, intercept) - - def register_function_after_load(self, function): if function not in self.function_after_load_list: self.function_after_load_list.append(function) diff --git a/qiling/os/uefi/uefi.py b/qiling/os/uefi/uefi.py index 1eac75363..fe55cc0f3 100644 --- a/qiling/os/uefi/uefi.py +++ b/qiling/os/uefi/uefi.py @@ -9,7 +9,7 @@ from qiling import Qiling from qiling.cc import QlCC, intel -from qiling.const import QL_OS +from qiling.const import QL_INTERCEPT, QL_OS from qiling.os.const import * from qiling.os.memory import QlMemoryHeap from qiling.os.os import QlOs, QlOsUtils @@ -199,6 +199,9 @@ def emu_error(self): self.ql.log.error(f'Memory map:') self.ql.mem.show_mapinfo() + def set_api(self, target: str, handler: Callable, intercept: QL_INTERCEPT = QL_INTERCEPT.CALL): + super().set_api(f'hook_{target}', handler, intercept) + def run(self): # TODO: this is not the right place for this self.smm = SmmEnv(self.ql) From bd7d20ebc00e76b73ff8aa01de66403bb14ad0c9 Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 14 Apr 2022 23:33:14 +0300 Subject: [PATCH 292/406] Make interpreter predicate based on arch rather than OS --- qiling/const.py | 3 ++- qiling/core.py | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/qiling/const.py b/qiling/const.py index 7e21ae25f..08097643d 100644 --- a/qiling/const.py +++ b/qiling/const.py @@ -57,10 +57,11 @@ class QL_STOP(Flag): STACK_POINTER = (1 << 0) EXIT_TRAP = (1 << 1) +QL_ARCH_INTERPRETER = (QL_ARCH.EVM,) + QL_OS_NONPID = (QL_OS.DOS, QL_OS.UEFI) QL_OS_POSIX = (QL_OS.LINUX, QL_OS.FREEBSD, QL_OS.MACOS, QL_OS.QNX) QL_OS_BAREMETAL = (QL_OS.MCU,) -QL_OS_INTERPRETER = (QL_OS.EVM,) QL_HOOK_BLOCK = 0b0001 QL_CALL_BLOCK = 0b0010 diff --git a/qiling/core.py b/qiling/core.py index d32c14052..10043dc2a 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -19,7 +19,7 @@ from .hw.hw import QlHwManager from .loader.loader import QlLoader -from .const import QL_ARCH, QL_ENDIAN, QL_OS, QL_STOP, QL_VERBOSE, QL_OS_INTERPRETER, QL_OS_BAREMETAL +from .const import QL_ARCH, QL_ENDIAN, QL_OS, QL_STOP, QL_VERBOSE, QL_ARCH_INTERPRETER, QL_OS_BAREMETAL from .exception import QlErrorFileNotFound, QlErrorArch, QlErrorOsType from .host import QlHost from .utils import * @@ -163,7 +163,7 @@ def __init__( self.uc = self.arch.uc # Once we finish setting up arch, we can init QlCoreStructs and QlCoreHooks - if ostype not in QL_OS_INTERPRETER: + if not self.interpreter: QlCoreStructs.__init__(self, self.arch.endian, self.arch.bits) QlCoreHooks.__init__(self, self.uc) @@ -187,7 +187,7 @@ def __init__( ############## # Components # ############## - if ostype not in QL_OS_INTERPRETER: + if not self.interpreter: self._mem = component_setup("os", "memory", self) self._os = os_setup(ostype, self) @@ -340,7 +340,7 @@ def interpreter(self) -> bool: Type: bool """ - return self.os.type in QL_OS_INTERPRETER + return self.arch.type in QL_ARCH_INTERPRETER @property def baremetal(self) -> bool: From 0d34a2793fcd2a2cbd18dbfe4a698a2becc0369e Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 14 Apr 2022 23:34:18 +0300 Subject: [PATCH 293/406] Misc insignificant changes --- qiling/os/freebsd/freebsd.py | 2 +- qiling/os/linux/linux.py | 1 - qiling/os/macos/macos.py | 4 ++-- qiling/os/qnx/qnx.py | 11 ++--------- 4 files changed, 5 insertions(+), 13 deletions(-) diff --git a/qiling/os/freebsd/freebsd.py b/qiling/os/freebsd/freebsd.py index 78fd09b78..e0218d4da 100644 --- a/qiling/os/freebsd/freebsd.py +++ b/qiling/os/freebsd/freebsd.py @@ -30,7 +30,7 @@ def load(self): self.ql.hook_insn(self.hook_syscall, UC_X86_INS_SYSCALL) - def hook_syscall(self, intno= None): + def hook_syscall(self, ql): return self.load_syscall() diff --git a/qiling/os/linux/linux.py b/qiling/os/linux/linux.py index 35d74d118..34a2a733e 100644 --- a/qiling/os/linux/linux.py +++ b/qiling/os/linux/linux.py @@ -14,7 +14,6 @@ from qiling.const import QL_ARCH, QL_OS from qiling.os.fcall import QlFunctionCall from qiling.os.const import * -from qiling.os.posix.const import NR_OPEN from qiling.os.posix.posix import QlOsPosix from . import futex diff --git a/qiling/os/macos/macos.py b/qiling/os/macos/macos.py index 276fed90a..eb7f38ee2 100644 --- a/qiling/os/macos/macos.py +++ b/qiling/os/macos/macos.py @@ -159,11 +159,11 @@ def load(self): self.ql.hook_insn(self.hook_syscall, UC_X86_INS_SYSCALL) - def hook_syscall(self, intno= None, int = None): + def hook_syscall(self, ql, intno = None): return self.load_syscall() - def hook_sigtrap(self, intno= None, int = None): + def hook_sigtrap(self, ql, intno): self.ql.log.info("Trap Found") self.emu_error() exit(1) diff --git a/qiling/os/qnx/qnx.py b/qiling/os/qnx/qnx.py index 8d53f7917..8c1b89c15 100644 --- a/qiling/os/qnx/qnx.py +++ b/qiling/os/qnx/qnx.py @@ -18,7 +18,6 @@ from qiling.const import QL_ARCH, QL_OS from qiling.os.fcall import QlFunctionCall from qiling.os.const import * -from qiling.os.posix.const import NR_OPEN from qiling.os.posix.posix import QlOsPosix class QlOsQnx(QlOsPosix): @@ -75,8 +74,8 @@ def load(self): 'get_tls': 0xffff0fe0 }) - - def hook_syscall(self, intno= None, int = None): + + def hook_syscall(self, ql, intno): return self.load_syscall() @@ -90,12 +89,6 @@ def run_function_after_load(self): f() - def hook_sigtrap(self, intno= None, int = None): - self.ql.log.info("Trap Found") - self.emu_error() - exit(1) - - def run(self): if self.ql.exit_point is not None: self.exit_point = self.ql.exit_point From 9f3e8e5152b85dfd437d222e7fb298612ce0af27 Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 14 Apr 2022 23:58:52 +0300 Subject: [PATCH 294/406] Simplify loader_setup --- qiling/const.py | 13 ------------- qiling/utils.py | 23 ++++++++++++++++------- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/qiling/const.py b/qiling/const.py index 08097643d..e9a9448b6 100644 --- a/qiling/const.py +++ b/qiling/const.py @@ -77,19 +77,6 @@ def __reverse_enum(e: Type[Enum]) -> Mapping[str, Any]: os_map : Mapping[str, QL_OS] = __reverse_enum(QL_OS) verbose_map : Mapping[str, QL_VERBOSE] = __reverse_enum(QL_VERBOSE) -loader_map = { - QL_OS.LINUX : "ELF", - QL_OS.FREEBSD : "ELF", - QL_OS.QNX : "ELF", - QL_OS.MACOS : "MACHO", - QL_OS.WINDOWS : "PE", - QL_OS.UEFI : "PE_UEFI", - QL_OS.DOS : "DOS", - QL_OS.EVM : "EVM", - QL_OS.MCU : "MCU", - QL_OS.BLOB : "BLOB" -} - arch_os_map = { QL_ARCH.EVM : QL_OS.EVM, QL_ARCH.CORTEX_M : QL_OS.MCU diff --git a/qiling/utils.py b/qiling/utils.py index 18979475c..cc7f638c8 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -20,7 +20,7 @@ from qiling.exception import * from qiling.const import QL_VERBOSE, QL_ARCH, QL_ENDIAN, QL_OS, QL_DEBUGGER -from qiling.const import debugger_map, arch_map, os_map, arch_os_map, loader_map +from qiling.const import debugger_map, arch_map, os_map, arch_os_map from qiling.os.posix.const import NR_OPEN FMT_STR = "%(levelname)s\t%(message)s" @@ -155,9 +155,6 @@ def ql_is_valid_ostype(ostype: QL_OS) -> bool: def ql_is_valid_arch(arch: QL_ARCH) -> bool: return arch in enum_values(QL_ARCH) -def loadertype_convert_str(ostype: QL_OS) -> Optional[str]: - return loader_map.get(ostype) - def __value_to_key(e: Type[Enum], val: Any) -> Optional[str]: key = e._value2member_map_[val] @@ -411,9 +408,21 @@ def ql_guess_emu_env(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], Opt def loader_setup(ql, ostype: QL_OS, libcache: bool): args = [libcache] if ostype == QL_OS.WINDOWS else [] - qlloader_name = loadertype_convert_str(ostype) - qlloader_path = f'qiling.loader.{qlloader_name.lower()}' - qlloader_class = f'QlLoader{qlloader_name.upper()}' + module = { + QL_OS.LINUX : r'elf', + QL_OS.FREEBSD : r'elf', + QL_OS.QNX : r'elf', + QL_OS.MACOS : r'macho', + QL_OS.WINDOWS : r'pe', + QL_OS.UEFI : r'pe_uefi', + QL_OS.DOS : r'dos', + QL_OS.EVM : r'evm', + QL_OS.MCU : r'mcu', + QL_OS.BLOB : r'blob' + }[ostype] + + qlloader_path = f'qiling.loader.{module}' + qlloader_class = f'QlLoader{module.upper()}' obj = ql_get_module_function(qlloader_path, qlloader_class) From bc65ff345f9057a621eaf8a8e0b9d5d90a877478 Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 15 Apr 2022 01:31:23 +0300 Subject: [PATCH 295/406] Extract logging logic from utils --- qiling/core.py | 24 +++--- qiling/log.py | 193 +++++++++++++++++++++++++++++++++++++++++++++++ qiling/utils.py | 196 +----------------------------------------------- 3 files changed, 210 insertions(+), 203 deletions(-) create mode 100644 qiling/log.py diff --git a/qiling/core.py b/qiling/core.py index 10043dc2a..715e3b7f1 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -13,6 +13,7 @@ from unicorn.unicorn import Uc if TYPE_CHECKING: + from logging import Logger from .arch.arch import QlArch from .os.os import QlOs from .os.memory import QlMemoryManager @@ -22,6 +23,7 @@ from .const import QL_ARCH, QL_ENDIAN, QL_OS, QL_STOP, QL_VERBOSE, QL_ARCH_INTERPRETER, QL_OS_BAREMETAL from .exception import QlErrorFileNotFound, QlErrorArch, QlErrorOsType from .host import QlHost +from .log import * from .utils import * from .core_struct import QlCoreStructs from .core_hooks import QlCoreHooks @@ -62,9 +64,7 @@ def __init__( self._env = env self._code = code self._multithread = multithread - self._log_file_fd = None self._log_filter = None - self._filter = filter self._internal_exception = None self._uc = None self._stop_options = stop @@ -170,8 +170,9 @@ def __init__( ########## # Logger # ########## - self._log_file_fd, self._log_filter = ql_setup_logger(self, log_file, console, self._filter, log_override, log_plain) + self._log_file_fd = setup_logger(self, log_file, console, log_override, log_plain) + self.filter = filter self.verbose = verbose ########### @@ -241,7 +242,7 @@ def os(self) -> "QlOs": return self._os @property - def log(self) -> logging.Logger: + def log(self) -> "Logger": """ Returns the logger this Qiling instance uses. You can override this log by passing `log_override=your_log` to Qiling.__init__ @@ -384,7 +385,7 @@ def verbose(self) -> QL_VERBOSE: def verbose(self, v: QL_VERBOSE): self._verbose = v - self.log.setLevel(ql_resolve_logger_level(v)) + self.log.setLevel(resolve_logger_level(v)) self.arch.utils.setup_output(v) @property @@ -445,16 +446,19 @@ def filter(self) -> str: Example: - Qiling(filter=r'^exit') - ql.filter = r'^open' """ - return self._filter + + lf = self._log_filter + + return '' if lf is None else lf._filter.pattern @filter.setter - def filter(self, ft): - self._filter = ft + def filter(self, regex: str): if self._log_filter is None: - self._log_filter = RegexFilter(ft) + self._log_filter = RegexFilter(regex) + self.log.addFilter(self._log_filter) else: - self._log_filter.update_filter(ft) + self._log_filter.update_filter(regex) @property def uc(self) -> Uc: diff --git a/qiling/log.py b/qiling/log.py new file mode 100644 index 000000000..611bdcaf3 --- /dev/null +++ b/qiling/log.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +import copy +import logging +import os +import re + +from enum import Enum +from typing import Optional, TextIO + +from qiling.const import QL_VERBOSE + +QL_INSTANCE_ID = 114514 + +FMT_STR = '%(levelname)s\t%(message)s' + +class COLOR(Enum): + WHITE = '\033[37m' + CRIMSON = '\033[31m' + RED = '\033[91m' + GREEN = '\033[92m' + YELLOW = '\033[93m' + BLUE = '\033[94m' + MAGENTA = '\033[95m' + CYAN = '\033[96m' + ENDC = '\033[0m' + +class QlBaseFormatter(logging.Formatter): + __level_tag = { + 'WARNING' : '[!]', + 'INFO' : '[=]', + 'DEBUG' : '[+]', + 'CRITICAL' : '[x]', + 'ERROR' : '[x]' + } + + def __init__(self, ql, *args, **kwargs): + super().__init__(*args, **kwargs) + self.ql = ql + + def get_level_tag(self, level: str) -> str: + return self.__level_tag[level] + + def get_thread_tag(self, thread: str) -> str: + return thread + + def format(self, record: logging.LogRecord): + # In case we have multiple formatters, we have to keep a copy of the record. + record = copy.copy(record) + + # early logging may access ql.os when it is not yet set + try: + cur_thread = self.ql.os.thread_management.cur_thread + except AttributeError: + tid = f'' + else: + tid = self.get_thread_tag(str(cur_thread)) + + level = self.get_level_tag(record.levelname) + record.levelname = f'{level} {tid}' + + return super().format(record) + +class QlColoredFormatter(QlBaseFormatter): + __level_color = { + 'WARNING' : COLOR.YELLOW, + 'INFO' : COLOR.BLUE, + 'DEBUG' : COLOR.MAGENTA, + 'CRITICAL' : COLOR.CRIMSON, + 'ERROR' : COLOR.RED + } + + def get_level_tag(self, level: str) -> str: + s = super().get_level_tag(level) + + return f'{self.__level_color[level]}{s}{COLOR.ENDC}' + + def get_thread_tag(self, tid: str) -> str: + s = super().get_thread_tag(tid) + + return f'{COLOR.GREEN}{s}{COLOR.ENDC}' + +class RegexFilter(logging.Filter): + def __init__(self, regexp: str): + super().__init__() + + self.update_filter(regexp) + + def update_filter(self, regexp: str): + self._filter = re.compile(regexp) + + def filter(self, record: logging.LogRecord): + msg = record.getMessage() + + return self._filter.match(msg) is not None + +def resolve_logger_level(verbose: QL_VERBOSE) -> int: + return { + QL_VERBOSE.DISABLED : logging.CRITICAL, + QL_VERBOSE.OFF : logging.WARNING, + QL_VERBOSE.DEFAULT : logging.INFO, + QL_VERBOSE.DEBUG : logging.DEBUG, + QL_VERBOSE.DISASM : logging.DEBUG, + QL_VERBOSE.DUMP : logging.DEBUG + }[verbose] + +def __is_color_terminal(stream: TextIO) -> bool: + """Determine whether standard output is attached to a color terminal. + + see: https://stackoverflow.com/questions/53574442/how-to-reliably-test-color-capability-of-an-output-terminal-in-python3 + """ + + def __handle_nt(fd: int) -> bool: + import ctypes + import msvcrt + + ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4 + + kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) + hstdout = msvcrt.get_osfhandle(fd) + mode = ctypes.c_ulong() + + return kernel32.GetConsoleMode(hstdout, ctypes.byref(mode)) and (mode.value & ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) + + def __handle_posix(fd: int) -> bool: + import curses + + try: + curses.setupterm(fd=fd) + except curses.error: + return True + else: + return curses.tigetnum('colors') > 0 + + def __default(_: int) -> bool: + return True + + handlers = { + 'nt' : __handle_nt, + 'posix' : __handle_posix + } + + handler = handlers.get(os.name, __default) + + return handler(stream.fileno()) + +def setup_logger(ql, log_file: Optional[str], console: bool, log_override: Optional[logging.Logger], log_plain: bool): + global QL_INSTANCE_ID + + # If there is an override for our logger, then use it. + if log_override is not None: + log = log_override + else: + # We should leave the root logger untouched. + log = logging.getLogger(f'qiling{QL_INSTANCE_ID}') + QL_INSTANCE_ID += 1 + + # Disable propagation to avoid duplicate output. + log.propagate = False + # Clear all handlers and filters. + log.handlers = [] + log.filters = [] + + # Do we have console output? + if console: + handler = logging.StreamHandler() + + if log_plain or not __is_color_terminal(handler.stream): + formatter = QlBaseFormatter(ql, FMT_STR) + else: + formatter = QlColoredFormatter(ql, FMT_STR) + + handler.setFormatter(formatter) + log.addHandler(handler) + else: + handler = logging.NullHandler() + log.addHandler(handler) + + # Do we have to write log to a file? + if log_file is not None: + handler = logging.FileHandler(log_file) + formatter = QlBaseFormatter(ql, FMT_STR) + handler.setFormatter(formatter) + log.addHandler(handler) + + log.setLevel(logging.INFO) + + return log + +__all__ = ['RegexFilter', 'setup_logger', 'resolve_logger_level'] diff --git a/qiling/utils.py b/qiling/utils.py index cc7f638c8..2e16e36b3 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -9,104 +9,19 @@ """ from functools import partial -import importlib, os, copy, re, pefile, logging, yaml +import importlib, os, pefile, yaml from configparser import ConfigParser -from logging import LogRecord -from typing import Any, Container, IO, List, Optional, Sequence, TextIO, Tuple, Type, Union +from typing import Any, Container, IO, List, Optional, Tuple, Type, Union from enum import Enum from unicorn import UC_ERR_READ_UNMAPPED, UC_ERR_FETCH_UNMAPPED from qiling.exception import * -from qiling.const import QL_VERBOSE, QL_ARCH, QL_ENDIAN, QL_OS, QL_DEBUGGER +from qiling.const import QL_ARCH, QL_ENDIAN, QL_OS, QL_DEBUGGER from qiling.const import debugger_map, arch_map, os_map, arch_os_map from qiling.os.posix.const import NR_OPEN -FMT_STR = "%(levelname)s\t%(message)s" - -# \033 -> ESC -# ESC [ -> CSI -# CSI %d;%d;... m -> SGR -class COLOR_CODE: - WHITE = '\033[37m' - CRIMSON = '\033[31m' - RED = '\033[91m' - GREEN = '\033[92m' - YELLOW = '\033[93m' - BLUE = '\033[94m' - MAGENTA = '\033[95m' - CYAN = '\033[96m' - ENDC = '\033[0m' - -class QlBaseFormatter(logging.Formatter): - __level_tag = { - 'WARNING' : '[!]', - 'INFO' : '[=]', - 'DEBUG' : '[+]', - 'CRITICAL' : '[x]', - 'ERROR' : '[x]' - } - - def __init__(self, ql, *args, **kwargs): - super().__init__(*args, **kwargs) - self.ql = ql - - def get_level_tag(self, level: str) -> str: - return self.__level_tag[level] - - def get_thread_tag(self, thread: str) -> str: - return thread - - def format(self, record: LogRecord): - # In case we have multiple formatters, we have to keep a copy of the record. - record = copy.copy(record) - - # early logging may access ql.os when it is not yet set - try: - cur_thread = self.ql.os.thread_management.cur_thread - except AttributeError: - tid = f'' - else: - tid = self.get_thread_tag(str(cur_thread)) - - level = self.get_level_tag(record.levelname) - record.levelname = f'{level} {tid}' - - return super().format(record) - -class QlColoredFormatter(QlBaseFormatter): - __level_color = { - 'WARNING' : COLOR_CODE.YELLOW, - 'INFO' : COLOR_CODE.BLUE, - 'DEBUG' : COLOR_CODE.MAGENTA, - 'CRITICAL' : COLOR_CODE.CRIMSON, - 'ERROR' : COLOR_CODE.RED - } - - def get_level_tag(self, level: str) -> str: - s = super().get_level_tag(level) - - return f'{self.__level_color[level]}{s}{COLOR_CODE.ENDC}' - - def get_thread_tag(self, tid: str) -> str: - s = super().get_thread_tag(tid) - - return f'{COLOR_CODE.GREEN}{s}{COLOR_CODE.ENDC}' - -class RegexFilter(logging.Filter): - def __init__(self, regexp): - super(RegexFilter, self).__init__() - self.update_filter(regexp) - - def update_filter(self, regexp): - self._filter = re.compile(regexp) - - def filter(self, record: LogRecord): - msg = record.getMessage() - - return re.match(self._filter, msg) is not None - class QlFileDes: def __init__(self): self.__fds: List[Optional[IO]] = [None] * NR_OPEN @@ -536,111 +451,6 @@ def profile_setup(ql, ostype: QL_OS, filename: Optional[str]): return config -def ql_resolve_logger_level(verbose: QL_VERBOSE) -> int: - return { - QL_VERBOSE.DISABLED: logging.CRITICAL, - QL_VERBOSE.OFF : logging.WARNING, - QL_VERBOSE.DEFAULT : logging.INFO, - QL_VERBOSE.DEBUG : logging.DEBUG, - QL_VERBOSE.DISASM : logging.DEBUG, - QL_VERBOSE.DUMP : logging.DEBUG - }[verbose] - -QL_INSTANCE_ID = 114514 - -def __is_color_terminal(stream: TextIO) -> bool: - """Determine whether standard output is attached to a color terminal. - - see: https://stackoverflow.com/questions/53574442/how-to-reliably-test-color-capability-of-an-output-terminal-in-python3 - """ - - def __handle_nt(fd: int) -> bool: - import ctypes - import msvcrt - - ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4 - - kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) - hstdout = msvcrt.get_osfhandle(fd) - mode = ctypes.c_ulong() - - return kernel32.GetConsoleMode(hstdout, ctypes.byref(mode)) and (mode.value & ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) - - def __handle_posix(fd: int) -> bool: - import curses - - try: - curses.setupterm(fd=fd) - except curses.error: - return True - else: - return curses.tigetnum('colors') > 0 - - def __default(_: int) -> bool: - return True - - handlers = { - 'nt' : __handle_nt, - 'posix' : __handle_posix - } - - handler = handlers.get(os.name, __default) - - return handler(stream.fileno()) - -# TODO: qltool compatibility -def ql_setup_logger(ql, log_file: Optional[str], console: bool, filters: Optional[Sequence], log_override: Optional[logging.Logger], log_plain: bool): - global QL_INSTANCE_ID - - # If there is an override for our logger, then use it. - if log_override is not None: - log = log_override - else: - # We should leave the root logger untouched. - log = logging.getLogger(f"qiling{QL_INSTANCE_ID}") - QL_INSTANCE_ID += 1 - - # Disable propagation to avoid duplicate output. - log.propagate = False - # Clear all handlers and filters. - log.handlers = [] - log.filters = [] - - # Do we have console output? - if console: - handler = logging.StreamHandler() - - if log_plain or not __is_color_terminal(handler.stream): - formatter = QlBaseFormatter(ql, FMT_STR) - else: - formatter = QlColoredFormatter(ql, FMT_STR) - - handler.setFormatter(formatter) - log.addHandler(handler) - else: - handler = logging.NullHandler() - log.addHandler(handler) - - # Do we have to write log to a file? - if log_file is not None: - handler = logging.FileHandler(log_file) - formatter = QlBaseFormatter(ql, FMT_STR) - handler.setFormatter(formatter) - log.addHandler(handler) - - # Remeber to add filters if necessary. - # If there aren't any filters, we do add the filters until users specify any. - log_filter = None - - if filters: - log_filter = RegexFilter(filters) - log.addFilter(log_filter) - - log.setLevel(logging.INFO) - - return log, log_filter - - # verify if emulator returns properly def verify_ret(ql, err): 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)) From 68b704dfc8fd0f5b81c11d9e0fa206d5ac6faee7 Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 15 Apr 2022 01:37:21 +0300 Subject: [PATCH 296/406] Move QlFileDes definition to POSIX --- qiling/os/posix/posix.py | 31 +++++++++++++++++++++++++++++-- qiling/utils.py | 29 +---------------------------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/qiling/os/posix/posix.py b/qiling/os/posix/posix.py index d3c7a9a62..002a583a0 100644 --- a/qiling/os/posix/posix.py +++ b/qiling/os/posix/posix.py @@ -4,7 +4,7 @@ # from inspect import signature, Parameter -from typing import TextIO, Union, Callable +from typing import TextIO, Union, Callable, IO, List, Optional from unicorn.arm64_const import UC_ARM64_REG_X8, UC_ARM64_REG_X16 from unicorn.arm_const import UC_ARM_REG_R7 @@ -18,7 +18,7 @@ from qiling.exception import QlErrorSyscallNotFound from qiling.os.os import QlOs from qiling.os.posix.const import errors -from qiling.utils import QlFileDes, ostype_convert_str, ql_get_module_function, ql_syscall_mapping_function +from qiling.utils import ostype_convert_str, ql_get_module_function, ql_syscall_mapping_function from qiling.os.posix.syscall import * from qiling.os.linux.syscall import * @@ -58,6 +58,33 @@ class riscv32(riscv.riscv): class riscv64(riscv.riscv): pass + +class QlFileDes: + def __init__(self): + self.__fds: List[Optional[IO]] = [None] * NR_OPEN + + def __len__(self): + return len(self.__fds) + + def __getitem__(self, idx: int): + return self.__fds[idx] + + def __setitem__(self, idx: int, val: Optional[IO]): + self.__fds[idx] = val + + def __iter__(self): + return iter(self.__fds) + + def __repr__(self): + return repr(self.__fds) + + def save(self): + return self.__fds + + def restore(self, fds): + self.__fds = fds + + class QlOsPosix(QlOs): def __init__(self, ql: Qiling): diff --git a/qiling/utils.py b/qiling/utils.py index 2e16e36b3..756f2276d 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -12,7 +12,7 @@ import importlib, os, pefile, yaml from configparser import ConfigParser -from typing import Any, Container, IO, List, Optional, Tuple, Type, Union +from typing import Any, Container, Optional, Tuple, Type, Union from enum import Enum from unicorn import UC_ERR_READ_UNMAPPED, UC_ERR_FETCH_UNMAPPED @@ -20,33 +20,6 @@ from qiling.exception import * from qiling.const import QL_ARCH, QL_ENDIAN, QL_OS, QL_DEBUGGER from qiling.const import debugger_map, arch_map, os_map, arch_os_map -from qiling.os.posix.const import NR_OPEN - -class QlFileDes: - def __init__(self): - self.__fds: List[Optional[IO]] = [None] * NR_OPEN - - def __len__(self): - return len(self.__fds) - - def __getitem__(self, idx: int): - return self.__fds[idx] - - def __setitem__(self, idx: int, val: Optional[IO]): - self.__fds[idx] = val - - def __iter__(self): - return iter(self.__fds) - - def __repr__(self): - return repr(self.__fds) - - def save(self): - return self.__fds - - def restore(self, fds): - self.__fds = fds - def catch_KeyboardInterrupt(ql): def decorator(func): From 46020d62778357c0b62b384f1530be5c4102084c Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 15 Apr 2022 03:42:49 +0300 Subject: [PATCH 297/406] Allow deleting log filter --- qiling/core.py | 13 +++++++++---- qiling/log.py | 5 ----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/qiling/core.py b/qiling/core.py index 715e3b7f1..6eb2b05aa 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -452,12 +452,17 @@ def filter(self) -> str: return '' if lf is None else lf._filter.pattern @filter.setter - def filter(self, regex: str): - if self._log_filter is None: - self._log_filter = RegexFilter(regex) + def filter(self, regex: Optional[str]): + if regex is None: + if self._log_filter is not None: + self.log.removeFilter(self._log_filter) - self.log.addFilter(self._log_filter) else: + if self._log_filter is None: + self._log_filter = RegexFilter(regex) + + self.log.addFilter(self._log_filter) + self._log_filter.update_filter(regex) @property diff --git a/qiling/log.py b/qiling/log.py index 611bdcaf3..30d0901bd 100644 --- a/qiling/log.py +++ b/qiling/log.py @@ -84,11 +84,6 @@ def get_thread_tag(self, tid: str) -> str: return f'{COLOR.GREEN}{s}{COLOR.ENDC}' class RegexFilter(logging.Filter): - def __init__(self, regexp: str): - super().__init__() - - self.update_filter(regexp) - def update_filter(self, regexp: str): self._filter = re.compile(regexp) From 048c1363cfe6f723b51ea2229efd251fd51cba70 Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 15 Apr 2022 03:44:21 +0300 Subject: [PATCH 298/406] Simplify catch_KeyboardInterrupt --- qiling/core_hooks.py | 6 +++--- qiling/utils.py | 21 +++++++++------------ 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/qiling/core_hooks.py b/qiling/core_hooks.py index 8a8133811..afa0440f9 100644 --- a/qiling/core_hooks.py +++ b/qiling/core_hooks.py @@ -149,14 +149,14 @@ def _hook_addr_cb(self, uc: Uc, addr: int, size: int, pack_data): # Class Hooks # ############### def _ql_hook_internal(self, hook_type, callback, user_data=None, *args) -> int: - _callback = (catch_KeyboardInterrupt(self))(callback) + _callback = catch_KeyboardInterrupt(self, callback) # pack user_data & callback for wrapper _callback return self._h_uc.hook_add(hook_type, _callback, (self, user_data), 1, 0, *args) def _ql_hook_addr_internal(self, callback: Callable, address: int) -> int: - _callback = (catch_KeyboardInterrupt(self))(callback) - # pack user_data & callback for wrapper _callback + _callback = catch_KeyboardInterrupt(self, callback) + return self._h_uc.hook_add(UC_HOOK_CODE, _callback, self, address, address) diff --git a/qiling/utils.py b/qiling/utils.py index 756f2276d..48f78b0a1 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -21,18 +21,15 @@ from qiling.const import QL_ARCH, QL_ENDIAN, QL_OS, QL_DEBUGGER from qiling.const import debugger_map, arch_map, os_map, arch_os_map -def catch_KeyboardInterrupt(ql): - def decorator(func): - def wrapper(*args, **kw): - try: - return func(*args, **kw) - except BaseException as e: - ql.stop() - ql._internal_exception = e - - return wrapper - - return decorator +def catch_KeyboardInterrupt(ql, func): + def wrapper(*args, **kw): + try: + return func(*args, **kw) + except BaseException as e: + ql.stop() + ql._internal_exception = e + + return wrapper def enum_values(e: Type[Enum]) -> Container: return e.__members__.values() From bafc859f60c9b33d16c919c08fde71a9bb0a3321 Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 15 Apr 2022 03:45:21 +0300 Subject: [PATCH 299/406] Simplify hook_del --- qiling/core_hooks.py | 45 ++++++++++++-------------------------- qiling/core_hooks_types.py | 12 +++++----- 2 files changed, 20 insertions(+), 37 deletions(-) diff --git a/qiling/core_hooks.py b/qiling/core_hooks.py index afa0440f9..a389a9469 100644 --- a/qiling/core_hooks.py +++ b/qiling/core_hooks.py @@ -317,42 +317,25 @@ def hook_insn(self, callback, arg1, user_data=None, begin=1, end=0): return self.ql_hook(UC_HOOK_INSN, callback, user_data, begin, end, arg1) - def hook_del(self, *args): - if len(args) != 1 and len(args) != 2: - return + def hook_del(self, hret: HookRet): + h = hret.obj + hook_type = hret.type - if isinstance(args[0], HookRet): - args[0].remove() - return + def __remove(hooks_map, handles_map, key: int) -> None: + if key in hooks_map: + hooks_list = hooks_map[key] - hook_type, h = args + if h in hooks_list: + hooks_list.remove(h) - def __handle_common(t: int) -> None: - if t in self._hook: - if h in self._hook[t]: - del self._hook[t][self._hook[t].index(h)] + if not hooks_list: + uc_handle = handles_map.pop(key) - if len(self._hook[t]) == 0: - self._h_uc.hook_del(self._hook_fuc[t]) - del self._hook_fuc[t] + self._h_uc.hook_del(uc_handle) - def __handle_insn(t: int) -> None: - if t in self._insn_hook: - if h in self._insn_hook[t]: - del self._insn_hook[t][self._insn_hook[t].index(h)] - - if len(self._insn_hook[t]) == 0: - self._h_uc.hook_del(self._insn_hook_fuc[t]) - del self._insn_hook_fuc[t] - - def __handle_addr(t: int) -> None: - if t in self._addr_hook: - if h in self._addr_hook[t]: - del self._addr_hook[t][self._addr_hook[t].index(h)] - - if len(self._addr_hook[t]) == 0: - self._h_uc.hook_del(self._addr_hook_fuc[t]) - del self._addr_hook_fuc[t] + __handle_common = lambda k: __remove(self._hook, self._hook_fuc, k) + __handle_insn = lambda i: __remove(self._insn_hook, self._insn_hook_fuc, i) + __handle_addr = lambda a: __remove(self._addr_hook, self._addr_hook_fuc, a) type_handlers = ( (UC_HOOK_INTR, __handle_common), diff --git a/qiling/core_hooks_types.py b/qiling/core_hooks_types.py index cd11c931a..896d70a0e 100644 --- a/qiling/core_hooks_types.py +++ b/qiling/core_hooks_types.py @@ -46,11 +46,11 @@ def check(self, intno: int) -> bool: class HookRet: - def __init__(self, ql, t, h): - self._ql = ql - self._t = t - self._h = h + def __init__(self, ql, hook_type: int, hook_obj: Hook): + self.type = hook_type + self.obj = hook_obj + self.__remove = ql.hook_del - def remove(self): - self._ql.hook_del(self._t, self._h) \ No newline at end of file + def remove(self) -> None: + self.__remove(self) From 512a6e7c7f42805b237e08074e1876c87c2a389f Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 15 Apr 2022 03:46:12 +0300 Subject: [PATCH 300/406] Tidy up EVM hooks (round 2) --- qiling/arch/evm/hooks.py | 49 ++++++++++++++++------------------------ 1 file changed, 19 insertions(+), 30 deletions(-) diff --git a/qiling/arch/evm/hooks.py b/qiling/arch/evm/hooks.py index 237a33753..2c429a6a5 100644 --- a/qiling/arch/evm/hooks.py +++ b/qiling/arch/evm/hooks.py @@ -3,10 +3,16 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework import types +from enum import IntEnum from typing import MutableMapping, MutableSequence from qiling.core_hooks_types import Hook, HookAddr, HookIntr, HookRet +class EVM_HOOK(IntEnum): + CODE = (1 << 0) + ADDR = (1 << 1) + INSN = (1 << 2) + class QlArchEVMHooks: def __init__(self) -> None: self.hook_code_list: MutableSequence[Hook] = [] @@ -15,20 +21,19 @@ def __init__(self) -> None: evm_hooks_info = QlArchEVMHooks() - -def evm_hook_code(ql, callback, user_data=None, begin=1, end=0, *args): +def __evm_hook_code(ql, callback, user_data=None, begin=1, end=0): h = Hook(callback, user_data, begin, end) evm_hooks_info.hook_code_list.append(h) - return HookRet(ql, 'HOOK_CODE', h) + return HookRet(ql, EVM_HOOK.CODE, h) -def evm_hook_insn(ql, callback, intno, user_data=None, begin=1, end=0): +def __evm_hook_insn(ql, callback, intno, user_data=None, begin=1, end=0): h = HookIntr(callback, intno, user_data) evm_hooks_info.hook_insn_list.append(h) - return HookRet(ql, 'HOOK_INSN', h) + return HookRet(ql, EVM_HOOK.INSN, h) -def evm_hook_address(ql, callback, address, user_data): +def __evm_hook_address(ql, callback, address, user_data=None): h = HookAddr(callback, address, user_data) if address not in evm_hooks_info.hook_addr_dict: @@ -36,16 +41,19 @@ def evm_hook_address(ql, callback, address, user_data): evm_hooks_info.hook_addr_dict[address].append(h) - return HookRet(ql, 'HOOK_ADDR', h) + return HookRet(ql, EVM_HOOK.ADDR, h) + +def __evm_hook_del(ql, hret): + h = hret.obj + hook_type = hret.type -def evm_hook_del(hook_type, h): - if hook_type == "HOOK_CODE": + if hook_type == EVM_HOOK.CODE: evm_hooks_info.hook_code_list.remove(h) - elif hook_type == "HOOK_INSN": + elif hook_type == EVM_HOOK.INSN: evm_hooks_info.hook_insn_list.remove(h) - elif hook_type == 'HOOK_ADDR': + elif hook_type == EVM_HOOK.ADDR: if h.addr in evm_hooks_info.hook_addr_dict: hooks_list = evm_hooks_info.hook_addr_dict[h.addr] @@ -59,25 +67,6 @@ def monkeypath_core_hooks(ql): """Monkeypath core hooks for evm """ - def __evm_hook_code(self, callback, user_data=None, begin=1, end=0): - return evm_hook_code(self, callback, user_data, begin, end) - - def __evm_hook_address(self, callback, address, user_data=None): - return evm_hook_address(self, callback, address, user_data) - - def __evm_hook_insn(self, callback, arg1, user_data=None, begin=1, end=0): - return evm_hook_insn(self, callback, arg1, user_data, begin, end) - - def __evm_hook_del(self, *args): - if len(args) != 1 and len(args) != 2: - return - - if isinstance(args[0], HookRet): - args[0].remove() - return - - return evm_hook_del(*args) - ql.hook_code = types.MethodType(__evm_hook_code, ql) ql.hook_address = types.MethodType(__evm_hook_address, ql) ql.hook_insn = types.MethodType(__evm_hook_insn, ql) From 4c20ca80f647d7300adfa2f35b34f1c00075f4cd Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 17 Apr 2022 13:03:11 +0300 Subject: [PATCH 301/406] Fix log colors bug --- qiling/log.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qiling/log.py b/qiling/log.py index 30d0901bd..3249116df 100644 --- a/qiling/log.py +++ b/qiling/log.py @@ -8,7 +8,6 @@ import os import re -from enum import Enum from typing import Optional, TextIO from qiling.const import QL_VERBOSE @@ -17,7 +16,7 @@ FMT_STR = '%(levelname)s\t%(message)s' -class COLOR(Enum): +class COLOR: WHITE = '\033[37m' CRIMSON = '\033[31m' RED = '\033[91m' From fa804dc44ed78d71cf62031bed2f93467a96d9eb Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 17 Apr 2022 19:55:04 +0300 Subject: [PATCH 302/406] Skip tests properly --- tests/test_android.py | 4 +--- tests/test_elf_ko.py | 4 +--- tests/test_elf_multithread.py | 4 +--- tests/test_macho_kext.py | 5 ++++- tests/test_pe.py | 16 ++++------------ 5 files changed, 11 insertions(+), 22 deletions(-) diff --git a/tests/test_android.py b/tests/test_android.py index 79d18ea6e..10a8b53ff 100644 --- a/tests/test_android.py +++ b/tests/test_android.py @@ -11,10 +11,8 @@ class TestAndroid(unittest.TestCase): + @unittest.skipUnless(platform.system() == 'Linux', 'run only on Linux') def test_android_arm64(self): - if platform.system() != "Linux": - return - test_binary = "../examples/rootfs/arm64_android/bin/arm64_android_hello" rootfs = "../examples/rootfs/arm64_android" diff --git a/tests/test_elf_ko.py b/tests/test_elf_ko.py index a9cc56c0a..69c61a059 100644 --- a/tests/test_elf_ko.py +++ b/tests/test_elf_ko.py @@ -17,10 +17,8 @@ class ELF_KO_Test(unittest.TestCase): + @unittest.skipIf(IS_FAST_TEST, 'fast test') def test_demigod_m0hamed_x86(self): - if IS_FAST_TEST: - self.skipTest('QL_FAST_TEST') - checklist = {} @linux_kernel_api(params={ diff --git a/tests/test_elf_multithread.py b/tests/test_elf_multithread.py index fd909f2f8..5e0e338a6 100644 --- a/tests/test_elf_multithread.py +++ b/tests/test_elf_multithread.py @@ -13,10 +13,8 @@ class ELFTest(unittest.TestCase): + @unittest.skipIf(platform.system() == "Darwin" and platform.machine() == "arm64", 'darwin host') def test_elf_linux_execve_x8664(self): - if platform.system() == "Darwin" and platform.machine() == "arm64": - return - ql = Qiling(["../examples/rootfs/x8664_linux/bin/posix_syscall_execve"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG) ql.run() diff --git a/tests/test_macho_kext.py b/tests/test_macho_kext.py index 879b676f1..2bb23ccf2 100644 --- a/tests/test_macho_kext.py +++ b/tests/test_macho_kext.py @@ -4,7 +4,7 @@ # -import sys, unittest +import os, sys, unittest from pathlib import Path from unicorn import UcError @@ -16,7 +16,10 @@ from qiling.os.macos.structs import * from qiling.os.macos.fncc import macos_kernel_api +IS_FAST_TEST = 'QL_FAST_TEST' in os.environ + class MACHOTest(unittest.TestCase): + @unittest.skipIf(IS_FAST_TEST, 'fast test') def test_macho_macos_superrootkit(self): # https://developer.apple.com/download/more # to download kernel.developmment diff --git a/tests/test_pe.py b/tests/test_pe.py index 3bee26c0b..176bb894b 100644 --- a/tests/test_pe.py +++ b/tests/test_pe.py @@ -110,6 +110,7 @@ def _t(): self.assertTrue(QLWinSingleTest(_t).run()) + @unittest.skipIf(IS_FAST_TEST, 'fast test') def test_pe_win_x86_uselessdisk(self): def _t(): class Fake_Drive(QlFsMappedObject): @@ -134,12 +135,10 @@ def close(self): del ql return True - if IS_FAST_TEST: - self.skipTest('QL_FAST_TEST') - self.assertTrue(QLWinSingleTest(_t).run()) + @unittest.skipIf(IS_FAST_TEST, 'fast test') def test_pe_win_x86_gandcrab(self): def _t(): def stop(ql: Qiling): @@ -215,9 +214,6 @@ def __rand_name(minlen: int, maxlen: int) -> str: # let's check that gandcrab behave takes a different path if a different environment is found return num_syscalls_admin != num_syscalls_user - if IS_FAST_TEST: - self.skipTest('QL_FAST_TEST') - self.assertTrue(QLWinSingleTest(_t).run()) def test_pe_win_x86_multithread(self): @@ -331,6 +327,7 @@ def _t(): self.assertTrue(QLWinSingleTest(_t).run()) + @unittest.skipIf(IS_FAST_TEST, 'fast test') def test_pe_win_x86_wannacry(self): def _t(): def stop(ql): @@ -345,9 +342,6 @@ def stop(ql): del ql return True - if IS_FAST_TEST: - self.skipTest('QL_FAST_TEST') - self.assertTrue(QLWinSingleTest(_t).run()) @@ -363,6 +357,7 @@ def _t(): self.assertTrue(QLWinSingleTest(_t).run()) + @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") @@ -392,9 +387,6 @@ def end(ql): del ql return True - if IS_FAST_TEST: - self.skipTest('QL_FAST_TEST') - self.assertTrue(QLWinSingleTest(_t).run()) From 6e7407734f79e8920f4c05c0e946feeb481b5616 Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 17 Apr 2022 19:55:38 +0300 Subject: [PATCH 303/406] Improve and comment examples --- examples/crackme_x86_linux.py | 25 ++-- examples/crackme_x86_windows.py | 146 +++++++++++++++------ examples/hello_arm_linux_custom_syscall.py | 41 +++--- examples/hello_arm_linux_debug.py | 11 +- 4 files changed, 151 insertions(+), 72 deletions(-) diff --git a/examples/crackme_x86_linux.py b/examples/crackme_x86_linux.py index 7c1d16470..9373d133c 100644 --- a/examples/crackme_x86_linux.py +++ b/examples/crackme_x86_linux.py @@ -89,21 +89,26 @@ def replay(self, input: bytes) -> bool: return False +def progress(msg: str) -> None: + print(msg, end='\r', file=sys.stderr, flush=True) + def main(): - idx_list = (1, 4, 2, 0, 3) - flag = [0] * len(idx_list) + flag = bytearray(b'*****') + indices = (1, 4, 2, 0, 3) - solver = Solver(bytes(flag)) + # all possible flag characters (may be reduced to uppercase and digits to save time) + charset = string.printable - for idx in idx_list: + progress('Initializing...') + solver = Solver(flag) - # bruteforce all possible flag characters - for ch in string.printable: - flag[idx] = ord(ch) + for i in indices: + for ch in charset: + flag[i] = ord(ch) - print(f'Guessing... [{"".join(chr(ch) if ch else "_" for ch in flag)}]', end='\r', file=sys.stderr, flush=True) + progress(f'Guessing... {flag.decode()}') - if solver.replay(bytes(flag)): + if solver.replay(flag): break else: @@ -113,3 +118,5 @@ def main(): if __name__ == "__main__": main() + +# expected flag: L1NUX diff --git a/examples/crackme_x86_windows.py b/examples/crackme_x86_windows.py index d60f40c8b..c47da07eb 100644 --- a/examples/crackme_x86_windows.py +++ b/examples/crackme_x86_windows.py @@ -10,47 +10,111 @@ from qiling.const import QL_VERBOSE from qiling.extensions import pipe -def instruction_count(ql: Qiling, address: int, size: int, user_data): - user_data[0] += 1 - -def get_count(flag): - ql = Qiling(["rootfs/x86_windows/bin/crackme.exe"], "rootfs/x86_windows", verbose=QL_VERBOSE.OFF) - - ql.os.stdin = pipe.SimpleStringBuffer() - ql.os.stdout = pipe.SimpleStringBuffer() - - ql.os.stdin.write(bytes("".join(flag) + "\n", 'utf-8')) - count = [0] - - ql.hook_code(instruction_count, count) - ql.run() - - print(ql.os.stdout.read().decode('utf-8'), end='') - print(" ============ count: %d ============ " % count[0]) - - return count[0] - -def solve(): - # BJWXB_CTF{C5307D46-E70E-4038-B6F9-8C3F698B7C53} - prefix = list("BJWXB_CTF{C5307D46-E70E-4038-B6F9-8C3F698B7C") - flag = list("\x00" * 100) - base = get_count(prefix + flag) - i = 0 - - try: - for i in range(len(flag)): - for j in "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-{}": - flag[i] = j - data = get_count(prefix + flag) - if data > base: - base = data - print("\n\n\n>>> FLAG: " + "".join(prefix + flag) + "\n\n\n") - break - if flag[i] == "}": +ROOTFS = r"rootfs/x86_windows" + +class Solver: + def __init__(self, invalid: bytes): + # create a silent qiling instance + self.ql = Qiling([rf"{ROOTFS}/bin/crackme.exe"], ROOTFS, verbose=QL_VERBOSE.OFF) + + self.ql.os.stdin = pipe.SimpleInStream(sys.stdin.fileno()) # take over the input to the program using a fake stdin + self.ql.os.stdout = pipe.NullOutStream(sys.stdout.fileno()) # disregard program output + + # execute program until it reaches the part that asks for input + self.ql.run(end=0x401030) + + # record replay starting and ending points. + # + # since the emulation halted upon entering a function, its return address is there on + # the stack. we use it to limit the emulation till function returns + self.replay_starts = self.ql.arch.regs.arch_pc + self.replay_ends = self.ql.stack_read(0) + + # instead of restarting the whole program every time a new flag character is guessed, + # we will restore its state to the latest point possible, fast-forwarding a good + # amount of start-up code that is not affected by the input. + # + # here we save the state just when the read input function is about to be called so we + # could use it to jumpstart the initialization part and get to the read input immediately + self.jumpstart = self.ql.save() or {} + + # calibrate the replay instruction count by running the code with an invalid input + # first. the instruction count returned from the calibration process will be then + # used as a baseline for consequent replays + self.best_icount = self.__run(invalid) + + def __run(self, input: bytes) -> int: + icount = [0] + + def __count_instructions(ql: Qiling, address: int, size: int): + icount[0] += 1 + + # set a hook to fire up every time an instruction is about to execute + hobj = self.ql.hook_code(__count_instructions) + + # feed stdin with input + self.ql.os.stdin.write(input + b'\n') + + # resume emulation till function returns + self.ql.run(begin=self.replay_starts, end=self.replay_ends) + + hobj.remove() + + return icount[0] + + def replay(self, input: bytes) -> bool: + """Restore state and replay with a new input. + + Returns an indication to execution progress: `True` if a progress + was made, `False` otherwise + """ + + # restore program's state back to the starting point + self.ql.restore(self.jumpstart) + + # resume emulation and count emulated instructions + curr_icount = self.__run(input) + + # the larger part of the input is correct, the more instructions are expected to be executed. this is true + # for traditional loop-based validations like strcmp or memcmp which bails as soon as a mismatch is found: + # more correct characters mean more loop iterations - thus more executed instructions. + # + # if we got a higher instruction count, it means we made a progress in the right direction + if curr_icount > self.best_icount: + self.best_icount = curr_icount + + return True + + return False + +def progress(msg: str) -> None: + print(msg, end='\r', file=sys.stderr, flush=True) + +def main(): + flag = bytearray(b'BJWXB_CTF{********-****-****-****-************}') + indices = (i for i, ch in enumerate(flag) if ch == ord('*')) + + # uppercase hex digits + charset = '0123456789ABCDEF' + + progress('Initializing...') + solver = Solver(flag) + + for i in indices: + for ch in charset: + flag[i] = ord(ch) + + progress(f'Guessing... {flag.decode()}') + + if solver.replay(flag): break - print("SOLVED!!!") - except KeyboardInterrupt: - print("STOP: KeyboardInterrupt") + + else: + raise RuntimeError('no match found') + + print(f'\nFlag found!') if __name__ == "__main__": - solve() + main() + +# expected flag: BJWXB_CTF{C5307D46-E70E-4038-B6F9-8C3F698B7C53} diff --git a/examples/hello_arm_linux_custom_syscall.py b/examples/hello_arm_linux_custom_syscall.py index ec21c6876..6ae06db2c 100644 --- a/examples/hello_arm_linux_custom_syscall.py +++ b/examples/hello_arm_linux_custom_syscall.py @@ -9,30 +9,37 @@ from qiling import Qiling from qiling.const import QL_VERBOSE -def my_syscall_write(ql: Qiling, write_fd, write_buf, write_count, *args, **kw): - regreturn = 0 - +# customized system calls always use the same arguments list as the original ones, +# but with a Qiling instance as first argument +def my_syscall_write(ql: Qiling, fd: int, buf: int, count: int): try: - buf = ql.mem.read(write_buf, write_count) - ql.log.info("\n+++++++++\nmy write(%d,%x,%i) = %d\n+++++++++" % (write_fd, write_buf, write_count, regreturn)) - ql.os.fd[write_fd].write(buf) - regreturn = write_count + # read data from emulated memory + data = ql.mem.read(buf, count) + + # select the emulated file object that corresponds to the requested + # file descriptor + fobj = ql.os.fd[fd] + + # write the data into the file object, if it supports write operations + if hasattr(fobj, 'write'): + fobj.write(data) except: - regreturn = -1 - ql.log.info("\n+++++++++\nmy write(%d,%x,%i) = %d\n+++++++++" % (write_fd, write_buf, write_count, regreturn)) + ret = -1 + else: + ret = count - return regreturn + ql.log.info(f'my_syscall_write({fd}, {buf:#x}, {count}) = {ret}') + return ret if __name__ == "__main__": - ql = Qiling(["rootfs/arm_linux/bin/arm_hello"], "rootfs/arm_linux", verbose=QL_VERBOSE.DEBUG) - # Custom syscall handler by syscall name or syscall number. - # Known issue: If the syscall func is not be implemented in qiling, qiling does - # not know which func should be replaced. - # In that case, you must specify syscall by its number. + ql = Qiling([r'rootfs/arm_linux/bin/arm_hello'], r'rootfs/arm_linux', verbose=QL_VERBOSE.DEBUG) + + # replacing a system call with a custom implementation. + # note that system calls may be referred to either by their name or number. ql.os.set_syscall(0x04, my_syscall_write) - # set syscall by syscall name - #ql.os.set_syscall("write", my_syscall_write) + # an possible alternative: refer to a syscall by its name + #ql.os.set_syscall('write', my_syscall_write) ql.run() diff --git a/examples/hello_arm_linux_debug.py b/examples/hello_arm_linux_debug.py index a77b639f4..4c7e502c2 100644 --- a/examples/hello_arm_linux_debug.py +++ b/examples/hello_arm_linux_debug.py @@ -9,12 +9,13 @@ from qiling import Qiling from qiling.const import QL_VERBOSE -def run_sandbox(path, rootfs, verbose): - ql = Qiling(path, rootfs, verbose = verbose) +if __name__ == "__main__": + ql = Qiling([r'rootfs/arm_linux/bin/arm_hello'], r'rootfs/arm_linux', verbose=QL_VERBOSE.DEBUG) + ql.debugger = "qdb" # enable qdb without options + + # other possible alternatives: # ql.debugger = "qdb::rr" # switch on record and replay with rr # ql.debugger = "qdb:0x1030c" # enable qdb and setup breakpoin at 0x1030c - ql.run() -if __name__ == "__main__": - run_sandbox(["rootfs/arm_linux/bin/arm_hello"], "rootfs/arm_linux", QL_VERBOSE.DEBUG) + ql.run() From fc2c8c8966d521b90d8fb498d8fba7a5235c5fb3 Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 18 Apr 2022 01:33:20 +0300 Subject: [PATCH 304/406] Rearrange guess_emu_env --- qiling/utils.py | 57 ++++++++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/qiling/utils.py b/qiling/utils.py index 48f78b0a1..2255b180c 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -93,7 +93,19 @@ def ql_get_module_function(module_name: str, function_name: str): return module_function -def ql_elf_parse_emu_env(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], Optional[QL_ENDIAN]]: +def __emu_env_from_pathname(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], Optional[QL_ENDIAN]]: + if os.path.isdir(path) and path.endswith('.kext'): + return QL_ARCH.X8664, QL_OS.MACOS, QL_ENDIAN.EL + + if os.path.isfile(path): + _, ext = os.path.splitext(path) + + if ext in ('.DOS_COM', '.DOS_MBR', '.DOS_EXE'): + return QL_ARCH.A8086, QL_OS.DOS, QL_ENDIAN.EL + + return None, None, None + +def __emu_env_from_elf(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], Optional[QL_ENDIAN]]: # instead of using full-blown elffile parsing, we perform a simple parsing to avoid # external dependencies for target systems that do not need them. # @@ -194,7 +206,7 @@ def ql_elf_parse_emu_env(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], return archtype, ostype, archendian -def ql_macho_parse_emu_env(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], Optional[QL_ENDIAN]]: +def __emu_env_from_macho(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], Optional[QL_ENDIAN]]: macho_macos_sig64 = b'\xcf\xfa\xed\xfe' macho_macos_sig32 = b'\xce\xfa\xed\xfe' macho_macos_fat = b'\xca\xfe\xba\xbe' # should be header for FAT @@ -222,8 +234,9 @@ def ql_macho_parse_emu_env(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS return arch, ostype, endian +def __emu_env_from_pe(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], Optional[QL_ENDIAN]]: + import pefile -def ql_pe_parse_emu_env(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], Optional[QL_ENDIAN]]: try: pe = pefile.PE(path, fast_load=True) except: @@ -261,31 +274,21 @@ def ql_pe_parse_emu_env(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], return arch, ostype, archendian - def ql_guess_emu_env(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], Optional[QL_ENDIAN]]: - arch = None - ostype = None - endian = None - - if os.path.isdir(path) and path.endswith('.kext'): - return QL_ARCH.X8664, QL_OS.MACOS, QL_ENDIAN.EL - - if os.path.isfile(path) and path.endswith('.DOS_COM'): - return QL_ARCH.A8086, QL_OS.DOS, QL_ENDIAN.EL - - if os.path.isfile(path) and path.endswith('.DOS_MBR'): - return QL_ARCH.A8086, QL_OS.DOS, QL_ENDIAN.EL - - if os.path.isfile(path) and path.endswith('.DOS_EXE'): - return QL_ARCH.A8086, QL_OS.DOS, QL_ENDIAN.EL - - arch, ostype, endian = ql_elf_parse_emu_env(path) - - if arch is None or ostype is None or endian is None: - arch, ostype, endian = ql_macho_parse_emu_env(path) - - if arch is None or ostype is None or endian is None: - arch, ostype, endian = ql_pe_parse_emu_env(path) + guessing_methods = ( + __emu_env_from_pathname, + __emu_env_from_elf, + __emu_env_from_macho, + __emu_env_from_pe + ) + + for gm in guessing_methods: + arch, ostype, endian = gm(path) + + if None not in (arch, ostype, endian): + break + else: + arch, ostype, endian = (None, ) * 3 return arch, ostype, endian From dc9aa8a82175e838d8614a830193ac31f4053ac5 Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 18 Apr 2022 01:44:34 +0300 Subject: [PATCH 305/406] Rearrange enum convertion methods --- qiling/const.py | 17 +++++++------ qiling/core.py | 11 +++----- qiling/debugger/gdb/gdb.py | 2 +- qiling/host.py | 4 +-- qiling/os/posix/posix.py | 4 +-- qiling/utils.py | 51 ++++++++++++-------------------------- 6 files changed, 33 insertions(+), 56 deletions(-) diff --git a/qiling/const.py b/qiling/const.py index e9a9448b6..9548a7219 100644 --- a/qiling/const.py +++ b/qiling/const.py @@ -4,7 +4,7 @@ # from enum import Enum, Flag, IntEnum -from typing import Any, Mapping, Type +from typing import Mapping, Type, TypeVar class QL_ENDIAN(IntEnum): EL = 1 @@ -66,16 +66,17 @@ class QL_STOP(Flag): QL_HOOK_BLOCK = 0b0001 QL_CALL_BLOCK = 0b0010 -def __reverse_enum(e: Type[Enum]) -> Mapping[str, Any]: - '''Create a reverse mapping for an enum. +T = TypeVar('T', bound=Enum) +def __casefold_enum(e: Type[T]) -> Mapping[str, T]: + '''Create a casefolded mapping of an enum to allow case-insensitive lookup. ''' - return dict((v.name.lower(), v) for v in e.__members__.values()) + return dict((k.casefold(), v) for k, v in e._member_map_.items()) -debugger_map: Mapping[str, QL_DEBUGGER] = __reverse_enum(QL_DEBUGGER) -arch_map : Mapping[str, QL_ARCH] = __reverse_enum(QL_ARCH) -os_map : Mapping[str, QL_OS] = __reverse_enum(QL_OS) -verbose_map : Mapping[str, QL_VERBOSE] = __reverse_enum(QL_VERBOSE) +debugger_map = __casefold_enum(QL_DEBUGGER) +arch_map = __casefold_enum(QL_ARCH) +os_map = __casefold_enum(QL_OS) +verbose_map = __casefold_enum(QL_VERBOSE) arch_os_map = { QL_ARCH.EVM : QL_OS.EVM, diff --git a/qiling/core.py b/qiling/core.py index 6eb2b05aa..c3d7d4608 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -122,7 +122,7 @@ def __init__( archtype = arch_convert(archtype) if type(ostype) is str: - ostype = ostype_convert(ostype) + ostype = os_convert(ostype) # if provided arch was invalid or not provided, guess arch and os if archtype is None: @@ -141,11 +141,11 @@ def __init__( ostype = arch_os_convert(archtype) # arch should have been determined by now; fail if not - if archtype is None or not ql_is_valid_arch(archtype): + if type(archtype) is not QL_ARCH: raise QlErrorArch(f'Uknown or unsupported architecture: "{archtype}"') # os should have been determined by now; fail if not - if ostype is None or not ql_is_valid_ostype(ostype): + if type(ostype) is not QL_OS: raise QlErrorOsType(f'Unknown or unsupported operating system: "{ostype}"') # if endianess is still undetermined, set it to little-endian. @@ -153,11 +153,6 @@ def __init__( if endian is None: endian = QL_ENDIAN.EL - # make sure args were canonicalized successfully - assert type(archtype) is QL_ARCH - assert type(ostype) is QL_OS - assert type(endian) is QL_ENDIAN - self._arch = arch_setup(archtype, endian, thumb, self) self.uc = self.arch.uc diff --git a/qiling/debugger/gdb/gdb.py b/qiling/debugger/gdb/gdb.py index c07a9699f..8b3416663 100644 --- a/qiling/debugger/gdb/gdb.py +++ b/qiling/debugger/gdb/gdb.py @@ -483,7 +483,7 @@ def handle_q(subcmd): elif subcmd.startswith('Xfer:features:read'): xfercmd_file = subcmd.split(':')[3] xfercmd_abspath = os.path.dirname(os.path.abspath(__file__)) - xml_folder = arch_convert_str(self.ql.arch.type).lower() + xml_folder = self.ql.arch.type.name.lower() xfercmd_file = os.path.join(xfercmd_abspath,"xml",xml_folder, xfercmd_file) if os.path.exists(xfercmd_file) and self.ql.os.type is not QL_OS.WINDOWS: diff --git a/qiling/host.py b/qiling/host.py index 5acf50634..89ffd2f77 100644 --- a/qiling/host.py +++ b/qiling/host.py @@ -21,7 +21,7 @@ def os(self) -> Optional[QL_OS]: system = platform.system() - return utils.ostype_convert(system.lower()) + return utils.os_convert(system) @cached_property def arch(self) -> Optional[QL_ARCH]: @@ -30,4 +30,4 @@ def arch(self) -> Optional[QL_ARCH]: machine = platform.machine() - return utils.arch_convert(machine.lower()) + return utils.arch_convert(machine) diff --git a/qiling/os/posix/posix.py b/qiling/os/posix/posix.py index 002a583a0..2fe2602ef 100644 --- a/qiling/os/posix/posix.py +++ b/qiling/os/posix/posix.py @@ -18,7 +18,7 @@ from qiling.exception import QlErrorSyscallNotFound from qiling.os.os import QlOs from qiling.os.posix.const import errors -from qiling.utils import ostype_convert_str, ql_get_module_function, ql_syscall_mapping_function +from qiling.utils import ql_get_module_function, ql_syscall_mapping_function from qiling.os.posix.syscall import * from qiling.os.linux.syscall import * @@ -231,7 +231,7 @@ def load_syscall(self): 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.type)) + os_syscalls = __get_os_module(self.type.name) posix_syscalls = __get_os_module('posix') # look in os-specific and posix syscall hooks diff --git a/qiling/utils.py b/qiling/utils.py index 2255b180c..de3f70fd5 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -12,8 +12,7 @@ import importlib, os, pefile, yaml from configparser import ConfigParser -from typing import Any, Container, Optional, Tuple, Type, Union -from enum import Enum +from typing import Any, Mapping, Optional, Tuple, Type, Union from unicorn import UC_ERR_READ_UNMAPPED, UC_ERR_FETCH_UNMAPPED @@ -31,49 +30,31 @@ def wrapper(*args, **kw): return wrapper -def enum_values(e: Type[Enum]) -> Container: - return e.__members__.values() +def __name_to_enum(name: str, mapping: Mapping[str, T], aliases: Mapping[str, str] = {}) -> Optional[T]: + key = name.casefold() -def ql_is_valid_ostype(ostype: QL_OS) -> bool: - return ostype in enum_values(QL_OS) + return mapping.get(aliases.get(key) or key) -def ql_is_valid_arch(arch: QL_ARCH) -> bool: - return arch in enum_values(QL_ARCH) - -def __value_to_key(e: Type[Enum], val: Any) -> Optional[str]: - key = e._value2member_map_[val] - - return None if key is None else key.name - -def ostype_convert_str(ostype: QL_OS) -> Optional[str]: - return __value_to_key(QL_OS, ostype) - -def ostype_convert(ostype: str) -> Optional[QL_OS]: +def os_convert(os: str) -> Optional[QL_OS]: alias_map = { - "darwin": "macos", + 'darwin' : 'macos' } - return os_map.get(alias_map.get(ostype, ostype)) - -def arch_convert_str(arch: QL_ARCH) -> Optional[str]: - return __value_to_key(QL_ARCH, arch) + return __name_to_enum(os, os_map, alias_map) def arch_convert(arch: str) -> Optional[QL_ARCH]: alias_map = { - "x86_64": "x8664", - "riscv32": "riscv", + 'x86_64' : 'x8664', + 'riscv32' : 'riscv' } - - return arch_map.get(alias_map.get(arch, arch)) -def arch_os_convert(arch: QL_ARCH) -> Optional[QL_OS]: - return arch_os_map.get(arch) + return __name_to_enum(arch, arch_map, alias_map) def debugger_convert(debugger: str) -> Optional[QL_DEBUGGER]: - return debugger_map.get(debugger) + return __name_to_enum(debugger, debugger_map) -def debugger_convert_str(debugger_id: QL_DEBUGGER) -> Optional[str]: - return __value_to_key(QL_DEBUGGER, debugger_id) +def arch_os_convert(arch: QL_ARCH) -> Optional[QL_OS]: + return arch_os_map.get(arch) # Call `function_name` in `module_name`. # e.g. map_syscall in qiling.os.linux.map_syscall @@ -368,7 +349,7 @@ def arch_setup(archtype: QL_ARCH, endian: QL_ENDIAN, thumb: bool, ql): }[archtype] qlarch_path = f'qiling.arch.{module}' - qlarch_class = f'QlArch{arch_convert_str(archtype).upper()}' + qlarch_class = f'QlArch{archtype.name.upper()}' obj = ql_get_module_function(qlarch_path, qlarch_class) @@ -377,7 +358,7 @@ 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, archtype: QL_ARCH): - qlos_name = ostype_convert_str(ostype) + qlos_name = ostype.name qlos_path = f'qiling.os.{qlos_name.lower()}.map_syscall' qlos_func = 'get_syscall_mapper' @@ -409,7 +390,7 @@ def profile_setup(ql, ostype: QL_OS, filename: Optional[str]): else: qiling_home = os.path.dirname(os.path.abspath(__file__)) - os_profile = os.path.join(qiling_home, 'profiles', f'{ostype_convert_str(ostype).lower()}.ql') + os_profile = os.path.join(qiling_home, 'profiles', f'{ostype.name.lower()}.ql') profiles = [os_profile] From b868ecd655979a2c783d9e999ea20d02530bfb9c Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 18 Apr 2022 01:46:27 +0300 Subject: [PATCH 306/406] Use lazy imports to reduce unnecessary deps --- qiling/utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qiling/utils.py b/qiling/utils.py index de3f70fd5..d76438c7d 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -9,7 +9,7 @@ """ from functools import partial -import importlib, os, pefile, yaml +import importlib, os from configparser import ConfigParser from typing import Any, Mapping, Optional, Tuple, Type, Union @@ -382,6 +382,8 @@ def profile_setup(ql, ostype: QL_OS, filename: Optional[str]): # mcu uses a yaml-based config if ostype == QL_OS.MCU: + import yaml + if filename: with open(filename) as f: config = yaml.load(f, Loader=yaml.Loader) From 3ef51376eac340bde2e32a03784ec3da222964f7 Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 18 Apr 2022 01:56:52 +0300 Subject: [PATCH 307/406] Rearrange components setup methods --- qiling/core.py | 19 ++++++++---- qiling/loader/mcu.py | 3 -- qiling/utils.py | 70 ++++++++++++++++++++++++++++---------------- 3 files changed, 58 insertions(+), 34 deletions(-) diff --git a/qiling/core.py b/qiling/core.py index c3d7d4608..af46152b9 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -153,7 +153,7 @@ def __init__( if endian is None: endian = QL_ENDIAN.EL - self._arch = arch_setup(archtype, endian, thumb, self) + self._arch = select_arch(archtype, endian, thumb)(self) self.uc = self.arch.uc @@ -173,19 +173,23 @@ def __init__( ########### # Profile # ########### - self._profile = profile_setup(self, ostype, profile) + self.log.debug(f'Profile: {profile or "default"}') + self._profile = profile_setup(ostype, profile) ########## # Loader # ########## - self._loader = loader_setup(self, ostype, libcache) + self._loader = select_loader(ostype, libcache)(self) ############## # Components # ############## if not self.interpreter: - self._mem = component_setup("os", "memory", self) - self._os = os_setup(ostype, self) + self._mem = select_component('os', 'memory')(self) + self._os = select_os(ostype)(self) + + if self.baremetal: + self._hw = select_component('hw', 'hw')(self) # Run the loader self.loader.run() @@ -554,7 +558,10 @@ def run(self, begin=None, end=None, timeout=0, count=0, code=None): return self.arch.run(code) # init debugger (if set) - debugger = debugger_setup(self._debugger, self) + debugger = select_debugger(self._debugger) + + if debugger: + debugger = debugger(self) # patch binary self.do_bin_patch() diff --git a/qiling/loader/mcu.py b/qiling/loader/mcu.py index ab6028efd..2daf8a21a 100644 --- a/qiling/loader/mcu.py +++ b/qiling/loader/mcu.py @@ -9,7 +9,6 @@ from qiling.const import * from qiling.core import Qiling -from qiling.utils import component_setup from .loader import QlLoader @@ -62,8 +61,6 @@ def __init__(self, ql:Qiling): self.load_address = 0 self.filetype = self.guess_filetype() - self.ql._hw = component_setup("hw", "hw", self.ql) - if self.filetype == 'elf': with open(self.ql.path, 'rb') as infile: self.elf = ELFFile(io.BytesIO(infile.read())) diff --git a/qiling/utils.py b/qiling/utils.py index d76438c7d..7739a77ef 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -12,7 +12,7 @@ import importlib, os from configparser import ConfigParser -from typing import Any, Mapping, Optional, Tuple, Type, Union +from typing import TYPE_CHECKING, Any, Callable, Mapping, Optional, Tuple, TypeVar, Union from unicorn import UC_ERR_READ_UNMAPPED, UC_ERR_FETCH_UNMAPPED @@ -20,6 +20,16 @@ from qiling.const import QL_ARCH, QL_ENDIAN, QL_OS, QL_DEBUGGER from qiling.const import debugger_map, arch_map, os_map, arch_os_map +if TYPE_CHECKING: + from qiling import Qiling + from qiling.arch.arch import QlArch + from qiling.debugger.debugger import QlDebugger + from qiling.loader.loader import QlLoader + from qiling.os.os import QlOs + +T = TypeVar('T') +QlClassInit = Callable[['Qiling'], T] + def catch_KeyboardInterrupt(ql, func): def wrapper(*args, **kw): try: @@ -273,9 +283,12 @@ def ql_guess_emu_env(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], Opt return arch, ostype, endian +def select_loader(ostype: QL_OS, libcache: bool) -> QlClassInit['QlLoader']: + if ostype == QL_OS.WINDOWS: + kwargs = {'libcache' : libcache} -def loader_setup(ql, ostype: QL_OS, libcache: bool): - args = [libcache] if ostype == QL_OS.WINDOWS else [] + else: + kwargs = {} module = { QL_OS.LINUX : r'elf', @@ -295,45 +308,57 @@ def loader_setup(ql, ostype: QL_OS, libcache: bool): obj = ql_get_module_function(qlloader_path, qlloader_class) - return obj(ql, *args) - + return partial(obj, **kwargs) -def component_setup(component_type: str, component_name: str, ql): +def select_component(component_type: str, component_name: str, **kwargs) -> QlClassInit[Any]: component_path = f'qiling.{component_type}.{component_name}' component_class = f'Ql{component_name.capitalize()}Manager' obj = ql_get_module_function(component_path, component_class) - return obj(ql) + return partial(obj, **kwargs) - -def debugger_setup(options: Union[str, bool], ql): +def select_debugger(options: Union[str, bool]) -> Optional[QlClassInit['QlDebugger']]: if options is True: options = 'gdb' if type(options) is str: objname, *args = options.split(':') + dbgtype = debugger_convert(objname) + + if dbgtype == QL_DEBUGGER.GDB: + kwargs = dict(zip(('ip', 'port'), args)) + + elif dbgtype == QL_DEBUGGER.QDB: + kwargs = {} + + if 'rr' in args: + kwargs['rr'] = True + args.remove('rr') - if debugger_convert(objname) not in enum_values(QL_DEBUGGER): + if args: + kwargs['init_hook'] = args[0] + + else: raise QlErrorOutput('Debugger not supported') obj = ql_get_module_function(f'qiling.debugger.{objname}.{objname}', f'Ql{str.capitalize(objname)}') - return obj(ql, *args) + return partial(obj, **kwargs) return None -def arch_setup(archtype: QL_ARCH, endian: QL_ENDIAN, thumb: bool, ql): +def select_arch(archtype: QL_ARCH, endian: QL_ENDIAN, thumb: bool) -> QlClassInit['QlArch']: # set endianess and thumb mode for arm-based archs if archtype == QL_ARCH.ARM: - args = [endian, thumb] + kwargs = {'endian' : endian, 'thumb' : thumb} # set endianess for mips arch elif archtype == QL_ARCH.MIPS: - args = [endian] + kwargs = {'endian' : endian} else: - args = [] + kwargs = {} module = { QL_ARCH.A8086 : r'x86', @@ -353,8 +378,7 @@ def arch_setup(archtype: QL_ARCH, endian: QL_ENDIAN, thumb: bool, ql): obj = ql_get_module_function(qlarch_path, qlarch_class) - return obj(ql, *args) - + return partial(obj, **kwargs) # This function is extracted from os_setup (QlOsPosix) so I put it here. def ql_syscall_mapping_function(ostype: QL_OS, archtype: QL_ARCH): @@ -366,20 +390,16 @@ def ql_syscall_mapping_function(ostype: QL_OS, archtype: QL_ARCH): return func(archtype) - -def os_setup(ostype: QL_OS, ql): - qlos_name = ostype_convert_str(ostype) +def select_os(ostype: QL_OS) -> QlClassInit['QlOs']: + qlos_name = ostype.name qlos_path = f'qiling.os.{qlos_name.lower()}.{qlos_name.lower()}' qlos_class = f'QlOs{qlos_name.capitalize()}' obj = ql_get_module_function(qlos_path, qlos_class) - return obj(ql) - - -def profile_setup(ql, ostype: QL_OS, filename: Optional[str]): - ql.log.debug(f'Profile: {filename or "default"}') + return partial(obj) +def profile_setup(ostype: QL_OS, filename: Optional[str]): # mcu uses a yaml-based config if ostype == QL_OS.MCU: import yaml From 457ca168588f984c7369a9b68ca0ab7ee4525d4f Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 18 Apr 2022 02:00:00 +0300 Subject: [PATCH 308/406] Misc insignificant fixes --- qiling/core.py | 22 +++++++++++----------- qiling/utils.py | 22 +++++++++++----------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/qiling/core.py b/qiling/core.py index af46152b9..46f5799e8 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -3,16 +3,15 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from configparser import ConfigParser import os, pickle # See https://stackoverflow.com/questions/39740632/python-type-hinting-without-cyclic-imports from typing import AnyStr, List, Mapping, MutableMapping, Sequence, Union from typing import TYPE_CHECKING - -from unicorn.unicorn import Uc +from configparser import ConfigParser if TYPE_CHECKING: + from unicorn.unicorn import Uc from logging import Logger from .arch.arch import QlArch from .os.os import QlOs @@ -142,14 +141,14 @@ def __init__( # arch should have been determined by now; fail if not if type(archtype) is not QL_ARCH: - raise QlErrorArch(f'Uknown or unsupported architecture: "{archtype}"') + raise QlErrorArch(f'Unknown or unsupported architecture: "{archtype}"') # os should have been determined by now; fail if not if type(ostype) is not QL_OS: raise QlErrorOsType(f'Unknown or unsupported operating system: "{ostype}"') # if endianess is still undetermined, set it to little-endian. - # this setting is ignored for architectures with predfined endianess + # this setting is ignored for architectures with predefined endianess if endian is None: endian = QL_ENDIAN.EL @@ -193,7 +192,8 @@ def __init__( # Run the loader self.loader.run() - self._init_stop_guard() + + self._init_stop_guard() ##################### # Qiling Components # @@ -373,8 +373,8 @@ def verbose(self) -> QL_VERBOSE: Values: `QL_VERBOSE.DISABLED`: turn off logging `QL_VERBOSE.OFF` : mask off anything below warnings, errors and critical severity - `QL_VERBOSE.DEFAULT` : info logging level: default verbosity - `QL_VERBOSE.DEBUG` : debug logging level: higher verbosity + `QL_VERBOSE.DEFAULT` : info logging level; default verbosity + `QL_VERBOSE.DEBUG` : debug logging level; higher verbosity `QL_VERBOSE.DISASM` : debug verbosity along with disassembly trace (slow!) `QL_VERBOSE.DUMP` : disassembly trace along with cpu context dump """ @@ -465,15 +465,15 @@ def filter(self, regex: Optional[str]): self._log_filter.update_filter(regex) @property - def uc(self) -> Uc: + def uc(self) -> 'Uc': """ Raw uc instance. - Type: Ucgit + Type: Uc """ return self._uc @uc.setter - def uc(self, u): + def uc(self, u: 'Uc'): self._uc = u @property diff --git a/qiling/utils.py b/qiling/utils.py index 7739a77ef..14f105644 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -30,7 +30,7 @@ T = TypeVar('T') QlClassInit = Callable[['Qiling'], T] -def catch_KeyboardInterrupt(ql, func): +def catch_KeyboardInterrupt(ql: 'Qiling', func: Callable): def wrapper(*args, **kw): try: return func(*args, **kw) @@ -84,6 +84,16 @@ def ql_get_module_function(module_name: str, function_name: str): return module_function +# This function is extracted from os_setup (QlOsPosix) so I put it here. +def ql_syscall_mapping_function(ostype: QL_OS, archtype: QL_ARCH): + qlos_name = ostype.name + qlos_path = f'qiling.os.{qlos_name.lower()}.map_syscall' + qlos_func = 'get_syscall_mapper' + + func = ql_get_module_function(qlos_path, qlos_func) + + return func(archtype) + def __emu_env_from_pathname(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], Optional[QL_ENDIAN]]: if os.path.isdir(path) and path.endswith('.kext'): return QL_ARCH.X8664, QL_OS.MACOS, QL_ENDIAN.EL @@ -380,16 +390,6 @@ def select_arch(archtype: QL_ARCH, endian: QL_ENDIAN, thumb: bool) -> QlClassIni return partial(obj, **kwargs) -# This function is extracted from os_setup (QlOsPosix) so I put it here. -def ql_syscall_mapping_function(ostype: QL_OS, archtype: QL_ARCH): - qlos_name = ostype.name - qlos_path = f'qiling.os.{qlos_name.lower()}.map_syscall' - qlos_func = 'get_syscall_mapper' - - func = ql_get_module_function(qlos_path, qlos_func) - - return func(archtype) - def select_os(ostype: QL_OS) -> QlClassInit['QlOs']: qlos_name = ostype.name qlos_path = f'qiling.os.{qlos_name.lower()}.{qlos_name.lower()}' From 829a9240d9f85628a9a96830d58d69385bf63c34 Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 18 Apr 2022 16:25:38 +0300 Subject: [PATCH 309/406] Fix forgotten var rename --- qiling/arch/evm/vm/debug.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiling/arch/evm/vm/debug.py b/qiling/arch/evm/vm/debug.py index bf209ba77..319aba92d 100644 --- a/qiling/arch/evm/vm/debug.py +++ b/qiling/arch/evm/vm/debug.py @@ -6,7 +6,7 @@ import os, time import cmd2 from rich import print as rprint -from ..hooks import evm_hook_insn +from ..hooks import evm_hooks_info from .disassembler import EVMDisasm from ..analysis.signatures import analysis_func_sign from .utils import analysis_bytecode, bytecode_to_bytes @@ -38,7 +38,7 @@ def init(self, executor): load_bytecode, runtime_code, aux_data, constructor_args = analysis_bytecode(self.executor.vm_context.msg.code) - insns = EVMDisasm().disasm(bytecode_to_bytes(runtime_code), evm_hook_insn) + insns = EVMDisasm().disasm(bytecode_to_bytes(runtime_code), evm_hooks_info) self.func_sign = analysis_func_sign(insns, engine_num=2) self.cli_output() From 3d0b0aec7df5a43273c1858014fa25db6cca25de Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 19 Apr 2022 16:00:30 +0300 Subject: [PATCH 310/406] Make argreg a class member --- qiling/cc/__init__.py | 10 ++++------ qiling/cc/arm.py | 9 ++------- qiling/cc/intel.py | 20 +++++++------------- qiling/cc/mips.py | 5 +---- qiling/cc/riscv.py | 12 ++++-------- 5 files changed, 18 insertions(+), 38 deletions(-) diff --git a/qiling/cc/__init__.py b/qiling/cc/__init__.py index 351072c7c..124d7086e 100644 --- a/qiling/cc/__init__.py +++ b/qiling/cc/__init__.py @@ -2,7 +2,7 @@ # # Cross Platform and Multi Architecture Advanced Binary Emulation Framework -from typing import Callable, Tuple +from typing import Callable, Sequence, Tuple from qiling.arch.arch import QlArch @@ -107,19 +107,17 @@ class QlCommonBaseCC(QlCC): of the QlCC interface. """ - _argregs = () + _retreg: int + _argregs: Sequence _shadow = 0 _retaddr_on_stack = True - def __init__(self, arch: QlArch, retreg: int): + def __init__(self, arch: QlArch): super().__init__(arch) # native address size in bytes self._asize = self.arch.pointersize - # return value register - self._retreg = retreg - def __access_param(self, index: int, stack_access: Callable, reg_access: Callable) -> Tuple[Callable, int]: """[private] Generic accessor to function call parameters by their index. diff --git a/qiling/cc/arm.py b/qiling/cc/arm.py index 6517ee260..2457a537b 100644 --- a/qiling/cc/arm.py +++ b/qiling/cc/arm.py @@ -8,7 +8,6 @@ UC_ARM64_REG_X4, UC_ARM64_REG_X5, UC_ARM64_REG_X6, UC_ARM64_REG_X7 ) -from qiling.arch.arch import QlArch from qiling.cc import QlCommonBaseCC class QlArmBaseCC(QlCommonBaseCC): @@ -29,13 +28,9 @@ def unwind(self, nslots: int) -> int: return self.arch.stack_pop() class aarch64(QlArmBaseCC): + _retreg = UC_ARM64_REG_X0 _argregs = (UC_ARM64_REG_X0, UC_ARM64_REG_X1, UC_ARM64_REG_X2, UC_ARM64_REG_X3, UC_ARM64_REG_X4, UC_ARM64_REG_X5, UC_ARM64_REG_X6, UC_ARM64_REG_X7) + (None, ) * 8 - def __init__(self, arch: QlArch) -> None: - super().__init__(arch, UC_ARM64_REG_X0) - class aarch32(QlArmBaseCC): + _retreg = UC_ARM_REG_R0 _argregs = (UC_ARM_REG_R0, UC_ARM_REG_R1, UC_ARM_REG_R2, UC_ARM_REG_R3) + (None, ) * 12 - - def __init__(self, arch: QlArch) -> None: - super().__init__(arch, UC_ARM_REG_R0) diff --git a/qiling/cc/intel.py b/qiling/cc/intel.py index 4ed9f8fec..fb8405047 100644 --- a/qiling/cc/intel.py +++ b/qiling/cc/intel.py @@ -3,12 +3,11 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework from unicorn.x86_const import ( - UC_X86_REG_AX, UC_X86_REG_EAX, UC_X86_REG_RAX, UC_X86_REG_RCX, - UC_X86_REG_RDI, UC_X86_REG_RDX, UC_X86_REG_RSI, UC_X86_REG_R8, - UC_X86_REG_R9, UC_X86_REG_R10 + UC_X86_REG_EAX, UC_X86_REG_RAX, UC_X86_REG_RCX, UC_X86_REG_RDI, + UC_X86_REG_RDX, UC_X86_REG_RSI, UC_X86_REG_R8, UC_X86_REG_R9, + UC_X86_REG_R10 ) -from qiling.arch.arch import QlArch from qiling.cc import QlCommonBaseCC class QlIntelBaseCC(QlCommonBaseCC): @@ -16,15 +15,6 @@ class QlIntelBaseCC(QlCommonBaseCC): Supports arguments passing over registers and stack. """ - def __init__(self, arch: QlArch): - retreg = { - 16: UC_X86_REG_AX, - 32: UC_X86_REG_EAX, - 64: UC_X86_REG_RAX - }[arch.bits] - - super().__init__(arch, retreg) - def setReturnAddress(self, addr: int) -> None: self.arch.stack_push(addr) @@ -36,6 +26,8 @@ class QlIntel64(QlIntelBaseCC): """Calling convention base class for Intel-based 64-bit systems. """ + _retreg = UC_X86_REG_RAX + @staticmethod def getNumSlots(argbits: int) -> int: return max(argbits, 64) // 64 @@ -44,6 +36,8 @@ class QlIntel32(QlIntelBaseCC): """Calling convention base class for Intel-based 32-bit systems. """ + _retreg = UC_X86_REG_EAX + @staticmethod def getNumSlots(argbits: int) -> int: return max(argbits, 32) // 32 diff --git a/qiling/cc/mips.py b/qiling/cc/mips.py index 9eafc4d0a..c1b3a0897 100644 --- a/qiling/cc/mips.py +++ b/qiling/cc/mips.py @@ -4,17 +4,14 @@ from unicorn.mips_const import UC_MIPS_REG_V0, UC_MIPS_REG_A0, UC_MIPS_REG_A1, UC_MIPS_REG_A2, UC_MIPS_REG_A3 -from qiling.arch.arch import QlArch from qiling.cc import QlCommonBaseCC class mipso32(QlCommonBaseCC): + _retreg = UC_MIPS_REG_V0 _argregs = (UC_MIPS_REG_A0, UC_MIPS_REG_A1, UC_MIPS_REG_A2, UC_MIPS_REG_A3) + (None, ) * 12 _shadow = 4 _retaddr_on_stack = False - def __init__(self, arch: QlArch): - super().__init__(arch, UC_MIPS_REG_V0) - @staticmethod def getNumSlots(argbits: int): return 1 diff --git a/qiling/cc/riscv.py b/qiling/cc/riscv.py index 7a834ecda..60bcf21af 100644 --- a/qiling/cc/riscv.py +++ b/qiling/cc/riscv.py @@ -2,25 +2,21 @@ # # Cross Platform and Multi Architecture Advanced Binary Emulation Framework -from qiling.arch.arch import QlArch from qiling.cc import QlCommonBaseCC from unicorn.riscv_const import ( - UC_RISCV_REG_A0, UC_RISCV_REG_A1, UC_RISCV_REG_A2, - UC_RISCV_REG_A3, UC_RISCV_REG_A4, UC_RISCV_REG_A5, + UC_RISCV_REG_A0, UC_RISCV_REG_A1, UC_RISCV_REG_A2, + UC_RISCV_REG_A3, UC_RISCV_REG_A4, UC_RISCV_REG_A5 ) - class riscv(QlCommonBaseCC): """Default calling convention for RISCV - First 6 arguments are passed in regs, the rest are passed on the stack. + First 6 arguments are passed in regs, the rest are passed on the stack. """ + _retreg = UC_RISCV_REG_A0 _argregs = (UC_RISCV_REG_A0, UC_RISCV_REG_A1, UC_RISCV_REG_A2, UC_RISCV_REG_A3, UC_RISCV_REG_A4, UC_RISCV_REG_A5) + (None, ) * 10 - def __init__(self, arch: QlArch): - super().__init__(arch, UC_RISCV_REG_A0) - @staticmethod def getNumSlots(argbits: int): return 1 \ No newline at end of file From a52bd23d18f7a1546a650ad6f461f0d63d1f395a Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 19 Apr 2022 16:03:33 +0300 Subject: [PATCH 311/406] set getRawParam and setRawParam default argbits to 0 --- qiling/cc/__init__.py | 12 ++++++------ qiling/cc/intel.py | 2 +- qiling/os/fcall.py | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/qiling/cc/__init__.py b/qiling/cc/__init__.py index 124d7086e..5f7216c38 100644 --- a/qiling/cc/__init__.py +++ b/qiling/cc/__init__.py @@ -26,7 +26,7 @@ def getNumSlots(argbits: int) -> int: raise NotImplementedError - def getRawParam(self, slot: int, argbits: int = None) -> int: + def getRawParam(self, slot: int, argbits: int = 0) -> int: """Read a value of native size from the specified argument slot. Note that argument slots and argument indexes are not the same. Though they often correlate @@ -41,7 +41,7 @@ def getRawParam(self, slot: int, argbits: int = None) -> int: raise NotImplementedError - def setRawParam(self, slot: int, value: int, argbits: int = None) -> None: + def setRawParam(self, slot: int, value: int, argbits: int = 0) -> None: """Replace the value in the specified argument slot. Note that argument slots and argument indexes are not the same. Though they often correlate @@ -148,17 +148,17 @@ def __access_param(self, index: int, stack_access: Callable, reg_access: Callabl else: return reg_access, reg - def getRawParam(self, index: int, argbits: int = None) -> int: + def getRawParam(self, index: int, argbits: int = 0) -> int: read, loc = self.__access_param(index, self.arch.stack_read, self.arch.regs.read) - mask = (0 if argbits is None else (1 << argbits)) - 1 + mask = (argbits and (1 << argbits)) - 1 return read(loc) & mask - def setRawParam(self, index: int, value: int, argbits: int = None) -> None: + def setRawParam(self, index: int, value: int, argbits: int = 0) -> None: write, loc = self.__access_param(index, self.arch.stack_write, self.arch.regs.write) - mask = (0 if argbits is None else (1 << argbits)) - 1 + mask = (argbits and (1 << argbits)) - 1 write(loc, value & mask) diff --git a/qiling/cc/intel.py b/qiling/cc/intel.py index fb8405047..9c9aded49 100644 --- a/qiling/cc/intel.py +++ b/qiling/cc/intel.py @@ -42,7 +42,7 @@ class QlIntel32(QlIntelBaseCC): def getNumSlots(argbits: int) -> int: return max(argbits, 32) // 32 - def getRawParam(self, slot: int, nbits: int = None) -> int: + def getRawParam(self, slot: int, nbits: int = 0) -> int: __super_getparam = super().getRawParam if nbits == 64: diff --git a/qiling/os/fcall.py b/qiling/os/fcall.py index 4484e8de0..c9bd95d2c 100644 --- a/qiling/os/fcall.py +++ b/qiling/os/fcall.py @@ -32,8 +32,8 @@ def __init__(self, ql: Qiling, cc: QlCC, accessors: Mapping[int, Accessor] = {}) self.cc = cc def __make_accessor(nbits: int) -> Accessor: - reader = lambda si: cc.getRawParam(si, nbits or None) - writer = lambda si, val: cc.setRawParam(si, val, nbits or None) + reader = lambda si: cc.getRawParam(si, nbits) + writer = lambda si, val: cc.setRawParam(si, val, nbits) nslots = cc.getNumSlots(nbits) return (reader, writer, nslots) From 8ad4a1c49f080838477c599caa354b6797e00ac6 Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 19 Apr 2022 17:00:27 +0300 Subject: [PATCH 312/406] Remove unnecessary imports --- qiling/os/posix/posix.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/qiling/os/posix/posix.py b/qiling/os/posix/posix.py index 2fe2602ef..c58a1a54e 100644 --- a/qiling/os/posix/posix.py +++ b/qiling/os/posix/posix.py @@ -7,9 +7,17 @@ from typing import TextIO, Union, Callable, IO, List, Optional from unicorn.arm64_const import UC_ARM64_REG_X8, UC_ARM64_REG_X16 -from unicorn.arm_const import UC_ARM_REG_R7 +from unicorn.arm_const import ( + UC_ARM_REG_R0, UC_ARM_REG_R1, UC_ARM_REG_R2, UC_ARM_REG_R3, + UC_ARM_REG_R4, UC_ARM_REG_R5, UC_ARM_REG_R7, UC_ARM_REG_R12 +) from unicorn.mips_const import UC_MIPS_REG_V0 -from unicorn.x86_const import UC_X86_REG_EAX, UC_X86_REG_RAX +from unicorn.x86_const import ( + UC_X86_REG_EAX, UC_X86_REG_EBX, UC_X86_REG_ECX, UC_X86_REG_EDX, + UC_X86_REG_ESI, UC_X86_REG_EDI, UC_X86_REG_EBP, UC_X86_REG_RDI, + UC_X86_REG_RSI, UC_X86_REG_RDX, UC_X86_REG_R10, UC_X86_REG_R8, + UC_X86_REG_R9, UC_X86_REG_RAX +) from unicorn.riscv_const import UC_RISCV_REG_A7 from qiling import Qiling @@ -17,15 +25,9 @@ from qiling.const import QL_ARCH, QL_OS, QL_INTERCEPT from qiling.exception import QlErrorSyscallNotFound from qiling.os.os import QlOs -from qiling.os.posix.const import errors +from qiling.os.posix.const import NR_OPEN, errors from qiling.utils import ql_get_module_function, ql_syscall_mapping_function -from qiling.os.posix.syscall import * -from qiling.os.linux.syscall import * -from qiling.os.macos.syscall import * -from qiling.os.freebsd.syscall import * -from qiling.os.qnx.syscall import * - SYSCALL_PREF: str = f'ql_syscall_' class intel32(intel.QlIntel32): From e2dc1e8f5ec3c29b7e42cee275582b55c11c6be1 Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 19 Apr 2022 17:03:54 +0300 Subject: [PATCH 313/406] Make dynamic imports relative to qiling package --- qiling/hw/hw.py | 2 +- qiling/os/posix/posix.py | 2 +- qiling/os/qnx/syscall.py | 2 +- qiling/utils.py | 12 ++++++------ 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/qiling/hw/hw.py b/qiling/hw/hw.py index 040f4d732..701822b84 100644 --- a/qiling/hw/hw.py +++ b/qiling/hw/hw.py @@ -31,7 +31,7 @@ def create(self, label: str, struct: "QlPeripheral"=None, base: int=None) -> "Ql try: - entity = ql_get_module_function('qiling.hw', struct)(self.ql, label, **kwargs) + entity = ql_get_module_function('.hw', struct)(self.ql, label, **kwargs) setattr(self, label, entity) self.entity[label] = entity self.region[label] = [(lbound + base, rbound + base) for (lbound, rbound) in entity.region] diff --git a/qiling/os/posix/posix.py b/qiling/os/posix/posix.py index c58a1a54e..ae6318d8c 100644 --- a/qiling/os/posix/posix.py +++ b/qiling/os/posix/posix.py @@ -231,7 +231,7 @@ def load_syscall(self): if not syscall_hook: def __get_os_module(osname: str): - return ql_get_module_function(f'qiling.os.{osname.lower()}', 'syscall') + return ql_get_module(f'.os.{osname.lower()}.syscall') os_syscalls = __get_os_module(self.type.name) posix_syscalls = __get_os_module('posix') diff --git a/qiling/os/qnx/syscall.py b/qiling/os/qnx/syscall.py index 9a8d0200d..2d22c9685 100644 --- a/qiling/os/qnx/syscall.py +++ b/qiling/os/qnx/syscall.py @@ -198,7 +198,7 @@ def _msg_sendv(ql:Qiling, coid, smsg, sparts, rmsg, rparts, *args, **kw): type_ = ql.unpack16(sbody[:2]) msg_name = map_msgtype(ql, type_) - _msg_handler = ql_get_module_function(f"qiling.os.qnx", "message") + _msg_handler = ql_get_module_function(f".os.qnx", "message") if msg_name in dir(_msg_handler): msg_hook = eval(msg_name) diff --git a/qiling/utils.py b/qiling/utils.py index 14f105644..e4c7cd970 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -71,7 +71,7 @@ def arch_os_convert(arch: QL_ARCH) -> Optional[QL_OS]: def ql_get_module_function(module_name: str, function_name: str): try: - imp_module = importlib.import_module(module_name) + imp_module = importlib.import_module(module_name, 'qiling') except ModuleNotFoundError: raise QlErrorModuleNotFound(f'Unable to import module {module_name}') except KeyError: @@ -313,7 +313,7 @@ def select_loader(ostype: QL_OS, libcache: bool) -> QlClassInit['QlLoader']: QL_OS.BLOB : r'blob' }[ostype] - qlloader_path = f'qiling.loader.{module}' + qlloader_path = f'.loader.{module}' qlloader_class = f'QlLoader{module.upper()}' obj = ql_get_module_function(qlloader_path, qlloader_class) @@ -321,7 +321,7 @@ def select_loader(ostype: QL_OS, libcache: bool) -> QlClassInit['QlLoader']: return partial(obj, **kwargs) def select_component(component_type: str, component_name: str, **kwargs) -> QlClassInit[Any]: - component_path = f'qiling.{component_type}.{component_name}' + component_path = f'.{component_type}.{component_name}' component_class = f'Ql{component_name.capitalize()}Manager' obj = ql_get_module_function(component_path, component_class) @@ -352,7 +352,7 @@ def select_debugger(options: Union[str, bool]) -> Optional[QlClassInit['QlDebugg else: raise QlErrorOutput('Debugger not supported') - obj = ql_get_module_function(f'qiling.debugger.{objname}.{objname}', f'Ql{str.capitalize(objname)}') + obj = ql_get_module_function(f'.debugger.{objname}.{objname}', f'Ql{str.capitalize(objname)}') return partial(obj, **kwargs) @@ -383,7 +383,7 @@ def select_arch(archtype: QL_ARCH, endian: QL_ENDIAN, thumb: bool) -> QlClassIni QL_ARCH.RISCV64 : r'riscv64' }[archtype] - qlarch_path = f'qiling.arch.{module}' + qlarch_path = f'.arch.{module}' qlarch_class = f'QlArch{archtype.name.upper()}' obj = ql_get_module_function(qlarch_path, qlarch_class) @@ -392,7 +392,7 @@ def select_arch(archtype: QL_ARCH, endian: QL_ENDIAN, thumb: bool) -> QlClassIni def select_os(ostype: QL_OS) -> QlClassInit['QlOs']: qlos_name = ostype.name - qlos_path = f'qiling.os.{qlos_name.lower()}.{qlos_name.lower()}' + qlos_path = f'.os.{qlos_name.lower()}.{qlos_name.lower()}' qlos_class = f'QlOs{qlos_name.capitalize()}' obj = ql_get_module_function(qlos_path, qlos_class) From f04e18185c04443b7fc7c8c21bacfabd75a33496 Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 19 Apr 2022 17:07:52 +0300 Subject: [PATCH 314/406] Move get_syscall_mapper to POSIX --- qiling/os/posix/posix.py | 12 ++++++++++-- qiling/utils.py | 9 --------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/qiling/os/posix/posix.py b/qiling/os/posix/posix.py index ae6318d8c..496d0bad7 100644 --- a/qiling/os/posix/posix.py +++ b/qiling/os/posix/posix.py @@ -26,7 +26,7 @@ from qiling.exception import QlErrorSyscallNotFound from qiling.os.os import QlOs from qiling.os.posix.const import NR_OPEN, errors -from qiling.utils import ql_get_module_function, ql_syscall_mapping_function +from qiling.utils import ql_get_module_function SYSCALL_PREF: str = f'ql_syscall_' @@ -137,7 +137,7 @@ def __init__(self, ql: Qiling): }[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.type, self.ql.arch.type) + self.syscall_mapper = self.__get_syscall_mapper(self.ql.arch.type) self._fd = QlFileDes() @@ -150,6 +150,14 @@ def __init__(self, ql: Qiling): self._shms = {} + def __get_syscall_mapper(self, archtype: QL_ARCH): + qlos_path = f'.os.{self.type.name.lower()}.map_syscall' + qlos_func = 'get_syscall_mapper' + + func = ql_get_module_function(qlos_path, qlos_func) + + return func(archtype) + @QlOs.stdin.setter def stdin(self, stream: TextIO) -> None: self._stdin = stream diff --git a/qiling/utils.py b/qiling/utils.py index e4c7cd970..68815734e 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -84,15 +84,6 @@ def ql_get_module_function(module_name: str, function_name: str): return module_function -# This function is extracted from os_setup (QlOsPosix) so I put it here. -def ql_syscall_mapping_function(ostype: QL_OS, archtype: QL_ARCH): - qlos_name = ostype.name - qlos_path = f'qiling.os.{qlos_name.lower()}.map_syscall' - qlos_func = 'get_syscall_mapper' - - func = ql_get_module_function(qlos_path, qlos_func) - - return func(archtype) def __emu_env_from_pathname(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], Optional[QL_ENDIAN]]: if os.path.isdir(path) and path.endswith('.kext'): From 11ebcd2ebe6361764f732553711c6c293c62a118 Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 19 Apr 2022 17:09:01 +0300 Subject: [PATCH 315/406] Allow dynamic import of an entire module --- qiling/os/posix/posix.py | 2 +- qiling/utils.py | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/qiling/os/posix/posix.py b/qiling/os/posix/posix.py index 496d0bad7..d71069ba0 100644 --- a/qiling/os/posix/posix.py +++ b/qiling/os/posix/posix.py @@ -26,7 +26,7 @@ from qiling.exception import QlErrorSyscallNotFound from qiling.os.os import QlOs from qiling.os.posix.const import NR_OPEN, errors -from qiling.utils import ql_get_module_function +from qiling.utils import ql_get_module, ql_get_module_function SYSCALL_PREF: str = f'ql_syscall_' diff --git a/qiling/utils.py b/qiling/utils.py index 68815734e..0f1fe42ed 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -12,6 +12,7 @@ import importlib, os from configparser import ConfigParser +from types import ModuleType from typing import TYPE_CHECKING, Any, Callable, Mapping, Optional, Tuple, TypeVar, Union from unicorn import UC_ERR_READ_UNMAPPED, UC_ERR_FETCH_UNMAPPED @@ -66,24 +67,23 @@ def debugger_convert(debugger: str) -> Optional[QL_DEBUGGER]: def arch_os_convert(arch: QL_ARCH) -> Optional[QL_OS]: return arch_os_map.get(arch) -# Call `function_name` in `module_name`. -# e.g. map_syscall in qiling.os.linux.map_syscall -def ql_get_module_function(module_name: str, function_name: str): - +def ql_get_module(module_name: str) -> ModuleType: try: - imp_module = importlib.import_module(module_name, 'qiling') - except ModuleNotFoundError: - raise QlErrorModuleNotFound(f'Unable to import module {module_name}') - except KeyError: + module = importlib.import_module(module_name, 'qiling') + except (ModuleNotFoundError, KeyError): raise QlErrorModuleNotFound(f'Unable to import module {module_name}') + return module + +def ql_get_module_function(module_name: str, member_name: str): + module = ql_get_module(module_name) + try: - module_function = getattr(imp_module, function_name) + member = getattr(module, member_name) except AttributeError: - raise QlErrorModuleFunctionNotFound(f'Unable to import {function_name} from {imp_module}') - - return module_function + raise QlErrorModuleFunctionNotFound(f'Unable to import {member_name} from {module_name}') + return member def __emu_env_from_pathname(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], Optional[QL_ENDIAN]]: if os.path.isdir(path) and path.endswith('.kext'): From eb6666ef410fde70004a22e1ef96ff26e2327a1f Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 19 Apr 2022 19:08:02 +0300 Subject: [PATCH 316/406] Have baremetal work around nonexistant OS --- qiling/core.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/qiling/core.py b/qiling/core.py index 46f5799e8..6c9d0ebad 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -349,6 +349,11 @@ def baremetal(self) -> bool: Type: bool """ + + # os is not initialized for interpreter archs + if self.interpreter: + return False + return self.os.type in QL_OS_BAREMETAL @property From 63737fa558edcbac140c8045379c15762cf09fc3 Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 21 Apr 2022 00:19:32 +0300 Subject: [PATCH 317/406] Fix QDB init args enumeration --- qiling/utils.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/qiling/utils.py b/qiling/utils.py index 0f1fe42ed..4bd089faf 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -333,12 +333,22 @@ def select_debugger(options: Union[str, bool]) -> Optional[QlClassInit['QlDebugg elif dbgtype == QL_DEBUGGER.QDB: kwargs = {} - if 'rr' in args: - kwargs['rr'] = True - args.remove('rr') + def __int_nothrow(v: str, /) -> Optional[int]: + try: + return int(v, 0) + except ValueError: + return None - if args: - kwargs['init_hook'] = args[0] + # qdb init args are independent and may include any combination of: rr enable, init hook and script + for a in args: + if a == 'rr': + kwargs['rr'] = True + + elif __int_nothrow(a) is not None: + kwargs['init_hook'] = a + + else: + kwargs['script'] = a else: raise QlErrorOutput('Debugger not supported') From 10165efc778d546a40943d123c10eaeb9b35d3fe Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 21 Apr 2022 00:19:52 +0300 Subject: [PATCH 318/406] Fix QDB test import --- tests/test_qdb.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_qdb.py b/tests/test_qdb.py index 077ecad9e..0a0da506c 100644 --- a/tests/test_qdb.py +++ b/tests/test_qdb.py @@ -3,9 +3,10 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -import unittest -from qiling import Qiling +import sys, unittest +sys.path.append("..") +from qiling import Qiling class DebuggerTest(unittest.TestCase): From 85dfb3cb58af14ef203da0118873504631ff1ae8 Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 21 Apr 2022 00:20:46 +0300 Subject: [PATCH 319/406] Handle calls to get_terminal_size from non-tty --- qiling/debugger/qdb/render/render.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/qiling/debugger/qdb/render/render.py b/qiling/debugger/qdb/render/render.py index b7a8f05f9..b8d7eb08f 100644 --- a/qiling/debugger/qdb/render/render.py +++ b/qiling/debugger/qdb/render/render.py @@ -34,7 +34,11 @@ def divider_printer(field_name, ruler="─"): def decorator(context_dumper): def wrapper(*args, **kwargs): - width, height = os.get_terminal_size() + try: + width, _ = os.get_terminal_size() + except OSError: + width = 130 + bar = (width - len(field_name)) // 2 - 1 print(ruler * bar, field_name, ruler * bar) context_dumper(*args, **kwargs) From 55a0a4440a8c39fd94753043b7b8301885b159a9 Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 21 Apr 2022 13:54:51 +0300 Subject: [PATCH 320/406] Reduce imports clutter --- qiling/__init__.py | 4 +++- qiling/core.py | 9 +++------ qiling/utils.py | 18 ++++++++++++++++++ qltool | 7 ++++--- 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/qiling/__init__.py b/qiling/__init__.py index 5a5ddbac9..375f0373b 100644 --- a/qiling/__init__.py +++ b/qiling/__init__.py @@ -1,2 +1,4 @@ from .core import Qiling -from .__version__ import __version__ \ No newline at end of file +from .__version__ import __version__ + +__all__ = ['Qiling'] diff --git a/qiling/core.py b/qiling/core.py index 6c9d0ebad..d72f0952a 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -4,14 +4,12 @@ # import os, pickle +from typing import TYPE_CHECKING, Any, AnyStr, List, Mapping, MutableMapping, Optional, Sequence, Tuple, Union # See https://stackoverflow.com/questions/39740632/python-type-hinting-without-cyclic-imports -from typing import AnyStr, List, Mapping, MutableMapping, Sequence, Union -from typing import TYPE_CHECKING -from configparser import ConfigParser - if TYPE_CHECKING: from unicorn.unicorn import Uc + from configparser import ConfigParser from logging import Logger from .arch.arch import QlArch from .os.os import QlOs @@ -26,7 +24,6 @@ from .utils import * from .core_struct import QlCoreStructs from .core_hooks import QlCoreHooks -from .__version__ import __version__ # Mixin Pattern class Qiling(QlCoreHooks, QlCoreStructs): @@ -269,7 +266,7 @@ def multithread(self) -> bool: return self._multithread @property - def profile(self) -> ConfigParser: + def profile(self) -> "ConfigParser": """ Program profile. See qiling/profiles/*.ql for details. Note: Please pass None or the path string to Qiling.__init__. diff --git a/qiling/utils.py b/qiling/utils.py index 4bd089faf..08573a008 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -459,3 +459,21 @@ def verify_ret(ql, err): raise else: raise + +__all__ = [ + 'catch_KeyboardInterrupt', + 'os_convert', + 'arch_convert', + 'debugger_convert', + 'arch_os_convert', + 'ql_get_module', + 'ql_get_module_function', + 'ql_guess_emu_env', + 'select_os', + 'select_arch', + 'select_loader', + 'select_debugger', + 'select_component', + 'profile_setup', + 'verify_ret' +] diff --git a/qltool b/qltool index 5467c0dd0..d6aae3935 100755 --- a/qltool +++ b/qltool @@ -8,14 +8,15 @@ import os import sys import ast import pickle -import unicorn + +from unicorn import __version__ as uc_ver +from qiling import __version__ as ql_ver from qiling import Qiling from qiling.arch import utils as arch_utils from qiling.debugger.qdb import QlQdb from qiling.utils import arch_convert from qiling.const import QL_VERBOSE, QL_ENDIAN, os_map, arch_map, verbose_map -from qiling.__version__ import __version__ as ql_version from qiling.extensions.coverage import utils as cov_utils from qiling.extensions import report @@ -167,7 +168,7 @@ def handle_examples(parser: argparse.ArgumentParser): if __name__ == '__main__': parser = argparse.ArgumentParser() - parser.add_argument('--version', action='version', version=f'qltool for Qiling {ql_version}, using Unicorn {unicorn.__version__}') + parser.add_argument('--version', action='version', version=f'qltool for Qiling {ql_ver}, using Unicorn {uc_ver}') commands = parser.add_subparsers(title='sub commands', description='select execution mode', dest='subcommand') From 913a79a3fdafa45b3a5a24774e8511906538be1f Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 21 Apr 2022 14:00:12 +0300 Subject: [PATCH 321/406] Have core uc property reference arch uc instance --- qiling/core.py | 9 +-------- qiling/os/posix/syscall/unistd.py | 1 - 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/qiling/core.py b/qiling/core.py index d72f0952a..684d8e74d 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -62,7 +62,6 @@ def __init__( self._multithread = multithread self._log_filter = None self._internal_exception = None - self._uc = None self._stop_options = stop ################################## @@ -151,8 +150,6 @@ def __init__( self._arch = select_arch(archtype, endian, thumb)(self) - self.uc = self.arch.uc - # Once we finish setting up arch, we can init QlCoreStructs and QlCoreHooks if not self.interpreter: QlCoreStructs.__init__(self, self.arch.endian, self.arch.bits) @@ -472,11 +469,7 @@ def uc(self) -> 'Uc': Type: Uc """ - return self._uc - - @uc.setter - def uc(self, u: 'Uc'): - self._uc = u + return self.arch.uc @property def stop_options(self) -> QL_STOP: diff --git a/qiling/os/posix/syscall/unistd.py b/qiling/os/posix/syscall/unistd.py index 44b1a01b4..ce1f9577c 100644 --- a/qiling/os/posix/syscall/unistd.py +++ b/qiling/os/posix/syscall/unistd.py @@ -461,7 +461,6 @@ def __read_str_array(addr: int) -> Iterator[str]: if hasattr(ql.arch, 'msr'): ql.arch.msr.uc = uc - ql.uc = uc QlCoreHooks.__init__(ql, uc) ql.os.load() From 3f03eb4f984d146c827753feb30fc7bc7446b3e5 Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 21 Apr 2022 14:01:42 +0300 Subject: [PATCH 322/406] Minor robustness fixes to POSIX syscalls --- qiling/os/posix/syscall/unistd.py | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/qiling/os/posix/syscall/unistd.py b/qiling/os/posix/syscall/unistd.py index ce1f9577c..fc23cb44a 100644 --- a/qiling/os/posix/syscall/unistd.py +++ b/qiling/os/posix/syscall/unistd.py @@ -12,7 +12,7 @@ from multiprocessing import Process from qiling import Qiling -from qiling.const import QL_ARCH, QL_OS, QL_VERBOSE +from qiling.const import QL_ARCH, QL_OS from qiling.os.posix.filestruct import ql_pipe from qiling.os.posix.const import * from qiling.os.posix.stat import Stat @@ -159,12 +159,20 @@ def ql_syscall_lseek(ql: Qiling, fd: int, offset: int, origin: int): def ql_syscall__llseek(ql: Qiling, fd: int, offset_high: int, offset_low: int, result: int, whence: int): + if fd not in range(NR_OPEN): + return -EBADF + + f = ql.os.fd[fd] + + if f is None: + return -EBADF + # treat offset as a signed value offset = ql.unpack64s(ql.pack64((offset_high << 32) | offset_low)) origin = whence try: - ret = ql.os.fd[fd].seek(offset, origin) + ret = f.seek(offset, origin) except OSError: regreturn = -1 else: @@ -278,6 +286,14 @@ def ql_syscall_read(ql: Qiling, fd, buf: int, length: int): def ql_syscall_write(ql: Qiling, fd: int, buf: int, count: int): + if fd not in range(NR_OPEN): + return -EBADF + + f = ql.os.fd[fd] + + if f is None: + return -EBADF + try: data = ql.mem.read(buf, count) except: @@ -285,12 +301,14 @@ def ql_syscall_write(ql: Qiling, fd: int, buf: int, count: int): else: ql.log.debug(f'write() CONTENT: {bytes(data)}') - if hasattr(ql.os.fd[fd], 'write'): - ql.os.fd[fd].write(data) + if hasattr(f, 'write'): + f.write(data) + + regreturn = count else: ql.log.warning(f'write failed since fd {fd:d} does not have a write method') + regreturn = -1 - regreturn = count return regreturn From 2bd8c93d5a5c87193322b997c83b5e6cff3f3233 Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 21 Apr 2022 14:02:38 +0300 Subject: [PATCH 323/406] Deduplicate Linux timespec struct --- qiling/os/linux/syscall.py | 75 +++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 42 deletions(-) diff --git a/qiling/os/linux/syscall.py b/qiling/os/linux/syscall.py index b3f10a7e7..c61951c97 100644 --- a/qiling/os/linux/syscall.py +++ b/qiling/os/linux/syscall.py @@ -11,24 +11,29 @@ from math import floor import ctypes -class timespec(ctypes.Structure): - _fields_ = [ - ("tv_sec", ctypes.c_uint64), - ("tv_nsec", ctypes.c_int64) - ] +def __get_timespec_struct(archbits: int): + long = getattr(ctypes, f'c_int{archbits}') + ulong = getattr(ctypes, f'c_uint{archbits}') - _pack_ = 8 + class timespec(ctypes.Structure): + _pack_ = archbits // 8 + _fields_ = ( + ('tv_sec', ulong), + ('tv_nsec', long) + ) -# Temporary dirty fix. -# TODO: Pack ctypes.Structure according to ql.arch.type and ql.ostype? -class timespec32(ctypes.Structure): - _fields_ = [ - ("tv_sec", ctypes.c_uint32), - ("tv_nsec", ctypes.c_int32) - ] + return timespec + +def __get_timespec_obj(archbits: int): + now = datetime.now().timestamp() + + tv_sec = floor(now) + tv_nsec = floor((now - floor(now)) * 1e6) + ts_cls = __get_timespec_struct(archbits) + + return ts_cls(tv_sec=tv_sec, tv_nsec=tv_nsec) - _pack_ = 4 def ql_syscall_set_thread_area(ql: Qiling, u_info_addr: int): if ql.arch.type == QL_ARCH.X86: @@ -62,39 +67,25 @@ def ql_syscall_set_thread_area(ql: Qiling, u_info_addr: int): return 0 -def ql_syscall_set_tls(ql, address, *args, **kw): +def ql_syscall_set_tls(ql: Qiling, address: int): if ql.arch.type == QL_ARCH.ARM: ql.arch.regs.c13_c0_3 = address ql.mem.write_ptr(ql.arch.arm_get_tls_addr + 16, address, 4) ql.arch.regs.r0 = address ql.log.debug("settls(0x%x)" % address) -def ql_syscall_clock_gettime(ql, clock_gettime_clock_id, clock_gettime_timespec, *args, **kw): - now = datetime.now().timestamp() - tv_sec = floor(now) - tv_nsec = floor((now - floor(now)) * 1e6) - if ql.arch.type == QL_ARCH.X8664: - tp = timespec(tv_sec= tv_sec, tv_nsec=tv_nsec) - else: - tp = timespec32(tv_sec= tv_sec, tv_nsec=tv_nsec) - ql.mem.write(clock_gettime_timespec, bytes(tp)) - - ql.log.debug("clock_gettime(clock_id = %d, timespec = 0x%x)" % (clock_gettime_clock_id, clock_gettime_timespec)) - +def ql_syscall_clock_gettime(ql: Qiling, clock_id: int, tp: int): + ts_obj = __get_timespec_obj(ql.arch.bits) + ql.mem.write(tp, bytes(ts_obj)) + return 0 -def ql_syscall_gettimeofday(ql, gettimeofday_tv, gettimeofday_tz, *args, **kw): - now = datetime.now().timestamp() - tv_sec = floor(now) - tv_nsec = floor((now - floor(now)) * 1e6) - if ql.arch.type == QL_ARCH.X8664: - tp = timespec(tv_sec= tv_sec, tv_nsec=tv_nsec) - else: - tp = timespec32(tv_sec= tv_sec, tv_nsec=tv_nsec) - - if gettimeofday_tv != 0: - ql.mem.write(gettimeofday_tv, bytes(tp)) - if gettimeofday_tz != 0: - ql.mem.write(gettimeofday_tz, b'\x00' * 8) - regreturn = 0 - return regreturn +def ql_syscall_gettimeofday(ql: Qiling, tv: int, tz: int): + if tv: + ts_obj = __get_timespec_obj(ql.arch.bits) + ql.mem.write(tv, bytes(ts_obj)) + + if tz: + ql.mem.write(tz, b'\x00' * 8) + + return 0 From 573ce1fafeec66e010b818da102d9fe0bf733a79 Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 21 Apr 2022 14:03:35 +0300 Subject: [PATCH 324/406] Remove Python 3.6 workaround --- qltool | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/qltool b/qltool index d6aae3935..2ce8fa3c2 100755 --- a/qltool +++ b/qltool @@ -170,7 +170,7 @@ if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('--version', action='version', version=f'qltool for Qiling {ql_ver}, using Unicorn {uc_ver}') - commands = parser.add_subparsers(title='sub commands', description='select execution mode', dest='subcommand') + commands = parser.add_subparsers(title='sub commands', description='select execution mode', dest='subcommand', required=True) # set "run" subcommand options run_parser = commands.add_parser('run', help='run a program') @@ -219,11 +219,6 @@ if __name__ == '__main__': 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 - # manually check whether the user did not specify a subcommand (execution mode) - if not options.subcommand: - parser.error('please select execution mode') - if options.subcommand == 'examples': handle_examples(parser) From eec7db537964c14e61934a79e47d4e3bfbca03df Mon Sep 17 00:00:00 2001 From: Bet4 <0xbet4@gmail.com> Date: Sat, 23 Apr 2022 23:50:46 +0800 Subject: [PATCH 325/406] Introduce PowerPC architecture to qiling --- CREDITS.md | 1 + README.md | 2 +- examples/rootfs | 2 +- qiling/arch/ppc.py | 51 ++++ qiling/arch/ppc_const.py | 128 ++++++++++ qiling/arch/utils.py | 7 +- qiling/cc/ppc.py | 23 ++ qiling/const.py | 1 + qiling/os/blob/blob.py | 15 +- qiling/os/linux/function_hook.py | 45 ++-- qiling/os/linux/linux.py | 22 +- qiling/os/linux/map_syscall.py | 423 ++++++++++++++++++++++++++++++- qiling/os/posix/const.py | 18 ++ qiling/os/posix/const_mapping.py | 2 + qiling/os/posix/posix.py | 12 +- qiling/os/posix/syscall/stat.py | 106 ++++++++ qiling/os/qnx/qnx.py | 17 +- qiling/utils.py | 7 +- tests/test_elf.py | 6 + 19 files changed, 830 insertions(+), 58 deletions(-) create mode 100644 qiling/arch/ppc.py create mode 100644 qiling/arch/ppc_const.py create mode 100644 qiling/cc/ppc.py diff --git a/CREDITS.md b/CREDITS.md index b1cba9447..b4e32331e 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -53,6 +53,7 @@ - madprogrammer - danielmoos - sigeryang +- bet4it #### Legacy Core Developers diff --git a/README.md b/README.md index 48af44c75..ce7bee4b5 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Qiling is an advanced binary emulation framework, with the following features: - Emulate multi-platforms: Windows, MacOS, Linux, BSD, UEFI, DOS, MBR, Ethereum Virtual Machine -- Emulate multi-architectures: X86, X86_64, Arm, Arm64, MIPS, 8086 +- Emulate multi-architectures: 8086, X86, X86_64, ARM, ARM64, MIPS, RISCV, PowerPC - Support multiple file formats: PE, MachO, ELF, COM, MBR - Support Windows Driver (.sys), Linux Kernel Module (.ko) & MacOS Kernel (.kext) via [Demigod](https://groundx.io/demigod/) - Emulates & sandbox code in an isolated environment diff --git a/examples/rootfs b/examples/rootfs index d8a9b0d6c..76780c9e9 160000 --- a/examples/rootfs +++ b/examples/rootfs @@ -1 +1 @@ -Subproject commit d8a9b0d6c52a3c5bc627c055d5f711dacbb1a1f6 +Subproject commit 76780c9e91471db1820b160d3b1d4a9ed6b13325 diff --git a/qiling/arch/ppc.py b/qiling/arch/ppc.py new file mode 100644 index 000000000..4e6d099db --- /dev/null +++ b/qiling/arch/ppc.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from functools import cached_property + +from unicorn import Uc, UC_ARCH_PPC, UC_MODE_PPC32, UC_MODE_BIG_ENDIAN +from capstone import Cs, CS_ARCH_PPC, CS_MODE_32, CS_MODE_BIG_ENDIAN +from keystone import Ks, KS_ARCH_PPC, KS_MODE_PPC32, KS_MODE_BIG_ENDIAN + +from qiling import Qiling +from qiling.arch.arch import QlArch +from qiling.arch import ppc_const +from qiling.arch.register import QlRegisterManager +from qiling.const import QL_ARCH, QL_ENDIAN + +class QlArchPPC(QlArch): + type = QL_ARCH.PPC + bits = 32 + + @cached_property + def uc(self) -> Uc: + return Uc(UC_ARCH_PPC, UC_MODE_PPC32 + UC_MODE_BIG_ENDIAN) + + @cached_property + def regs(self) -> QlRegisterManager: + regs_map = dict( + **ppc_const.reg_map, + **ppc_const.reg_float_map + ) + + pc_reg = 'pc' + sp_reg = 'r1' + + return QlRegisterManager(self.uc, regs_map, pc_reg, sp_reg) + + @cached_property + def disassembler(self) -> Cs: + return Cs(CS_ARCH_PPC, CS_MODE_32 + CS_MODE_BIG_ENDIAN) + + @cached_property + def assembler(self) -> Ks: + return Ks(KS_ARCH_PPC, KS_MODE_PPC32 + KS_MODE_BIG_ENDIAN) + + @property + def endian(self) -> QL_ENDIAN: + return QL_ENDIAN.EB + + def enable_float(self): + self.regs.msr = self.regs.msr | ppc_const.MSR.FP diff --git a/qiling/arch/ppc_const.py b/qiling/arch/ppc_const.py new file mode 100644 index 000000000..732cba1c9 --- /dev/null +++ b/qiling/arch/ppc_const.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from unicorn.ppc_const import * +from enum import IntEnum + +reg_map = { + "r0": UC_PPC_REG_0, + "r1": UC_PPC_REG_1, + "r2": UC_PPC_REG_2, + "r3": UC_PPC_REG_3, + "r4": UC_PPC_REG_4, + "r5": UC_PPC_REG_5, + "r6": UC_PPC_REG_6, + "r7": UC_PPC_REG_7, + "r8": UC_PPC_REG_8, + "r9": UC_PPC_REG_9, + "r10": UC_PPC_REG_10, + "r11": UC_PPC_REG_11, + "r12": UC_PPC_REG_12, + "r13": UC_PPC_REG_13, + "r14": UC_PPC_REG_14, + "r15": UC_PPC_REG_15, + "r16": UC_PPC_REG_16, + "r17": UC_PPC_REG_17, + "r18": UC_PPC_REG_18, + "r19": UC_PPC_REG_19, + "r20": UC_PPC_REG_20, + "r21": UC_PPC_REG_21, + "r22": UC_PPC_REG_22, + "r23": UC_PPC_REG_23, + "r24": UC_PPC_REG_24, + "r25": UC_PPC_REG_25, + "r26": UC_PPC_REG_26, + "r27": UC_PPC_REG_27, + "r28": UC_PPC_REG_28, + "r29": UC_PPC_REG_29, + "r30": UC_PPC_REG_30, + "r31": UC_PPC_REG_31, + "pc": UC_PPC_REG_PC, + "msr": UC_PPC_REG_MSR, + "cr": UC_PPC_REG_CR0, + "lr": UC_PPC_REG_LR, + "ctr": UC_PPC_REG_CTR, + "xer": UC_PPC_REG_XER, +} + +reg_float_map = { + "f0": UC_PPC_REG_FPR0, + "f1": UC_PPC_REG_FPR1, + "f2": UC_PPC_REG_FPR2, + "f3": UC_PPC_REG_FPR3, + "f4": UC_PPC_REG_FPR4, + "f5": UC_PPC_REG_FPR5, + "f6": UC_PPC_REG_FPR6, + "f7": UC_PPC_REG_FPR7, + "f8": UC_PPC_REG_FPR8, + "f9": UC_PPC_REG_FPR9, + "f10": UC_PPC_REG_FPR10, + "f11": UC_PPC_REG_FPR11, + "f12": UC_PPC_REG_FPR12, + "f13": UC_PPC_REG_FPR13, + "f14": UC_PPC_REG_FPR14, + "f15": UC_PPC_REG_FPR15, + "f16": UC_PPC_REG_FPR16, + "f17": UC_PPC_REG_FPR17, + "f18": UC_PPC_REG_FPR18, + "f19": UC_PPC_REG_FPR19, + "f20": UC_PPC_REG_FPR20, + "f21": UC_PPC_REG_FPR21, + "f22": UC_PPC_REG_FPR22, + "f23": UC_PPC_REG_FPR23, + "f24": UC_PPC_REG_FPR24, + "f25": UC_PPC_REG_FPR25, + "f26": UC_PPC_REG_FPR26, + "f27": UC_PPC_REG_FPR27, + "f28": UC_PPC_REG_FPR28, + "f29": UC_PPC_REG_FPR29, + "f30": UC_PPC_REG_FPR30, + "f31": UC_PPC_REG_FPR31, +} + +class MSR(IntEnum): + SF = 1 << 63 + TAG = 1 << 62 + ISF = 1 << 61 + HV = 1 << 60 + TS0 = 1 << 34 + TS1 = 1 << 33 + TM = 1 << 32 + CM = 1 << 31 + ICM = 1 << 30 + GS = 1 << 28 + UCLE = 1 << 26 + VR = 1 << 25 + SPE = 1 << 25 + AP = 1 << 23 + VSX = 1 << 23 + SA = 1 << 22 + KEY = 1 << 19 + POW = 1 << 18 + TGPR = 1 << 17 + CE = 1 << 17 + ILE = 1 << 16 + EE = 1 << 15 + PR = 1 << 14 + FP = 1 << 13 + ME = 1 << 12 + FE0 = 1 << 11 + SE = 1 << 10 + DWE = 1 << 10 + UBLE = 1 << 10 + BE = 1 << 9 + DE = 1 << 9 + FE1 = 1 << 8 + AL = 1 << 7 + EP = 1 << 6 + IR = 1 << 5 + DR = 1 << 4 + IS = 1 << 5 + DS = 1 << 4 + PE = 1 << 3 + PX = 1 << 2 + PMM = 1 << 2 + RI = 1 << 1 + LE = 1 << 0 diff --git a/qiling/arch/utils.py b/qiling/arch/utils.py index 2764bd72b..ab9583fb7 100644 --- a/qiling/arch/utils.py +++ b/qiling/arch/utils.py @@ -11,8 +11,8 @@ from os.path import basename from functools import lru_cache -from keystone import (Ks, KS_ARCH_ARM, KS_ARCH_ARM64, KS_ARCH_MIPS, KS_ARCH_X86, - KS_MODE_ARM, KS_MODE_THUMB, KS_MODE_MIPS32, KS_MODE_16, KS_MODE_32, KS_MODE_64, +from keystone import (Ks, KS_ARCH_ARM, KS_ARCH_ARM64, KS_ARCH_MIPS, KS_ARCH_X86, KS_ARCH_PPC, + KS_MODE_ARM, KS_MODE_THUMB, KS_MODE_MIPS32, KS_MODE_PPC32, KS_MODE_16, KS_MODE_32, KS_MODE_64, KS_MODE_LITTLE_ENDIAN, KS_MODE_BIG_ENDIAN) from qiling import Qiling @@ -104,7 +104,8 @@ def assembler(arch: QL_ARCH, endianess: QL_ENDIAN, is_thumb: bool) -> Ks: QL_ARCH.MIPS : (KS_ARCH_MIPS, KS_MODE_MIPS32 + endian), QL_ARCH.A8086 : (KS_ARCH_X86, KS_MODE_16), QL_ARCH.X86 : (KS_ARCH_X86, KS_MODE_32), - QL_ARCH.X8664 : (KS_ARCH_X86, KS_MODE_64) + QL_ARCH.X8664 : (KS_ARCH_X86, KS_MODE_64), + QL_ARCH.PPC : (KS_ARCH_PPC, KS_MODE_PPC32 + KS_MODE_BIG_ENDIAN) } if arch in asm_map: diff --git a/qiling/cc/ppc.py b/qiling/cc/ppc.py new file mode 100644 index 000000000..9f19e8818 --- /dev/null +++ b/qiling/cc/ppc.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework + +from qiling.cc import QlCommonBaseCC + +from unicorn.ppc_const import ( + UC_PPC_REG_3, UC_PPC_REG_4, UC_PPC_REG_5, + UC_PPC_REG_6, UC_PPC_REG_7, UC_PPC_REG_8, +) + + +class ppc(QlCommonBaseCC): + """Default calling convention for PPC + First 6 arguments are passed in regs, the rest are passed on the stack. + """ + + _retreg = UC_PPC_REG_3 + _argregs = (UC_PPC_REG_3, UC_PPC_REG_4, UC_PPC_REG_5, UC_PPC_REG_6, UC_PPC_REG_7, UC_PPC_REG_8) + (None, ) * 10 + + @staticmethod + def getNumSlots(argbits: int): + return 1 diff --git a/qiling/const.py b/qiling/const.py index 9548a7219..1f97e4cbb 100644 --- a/qiling/const.py +++ b/qiling/const.py @@ -21,6 +21,7 @@ class QL_ARCH(IntEnum): CORTEX_M = 109 RISCV = 110 RISCV64 = 111 + PPC = 112 class QL_OS(IntEnum): LINUX = 201 diff --git a/qiling/os/blob/blob.py b/qiling/os/blob/blob.py index 3f1b6fff5..02e6f94d3 100644 --- a/qiling/os/blob/blob.py +++ b/qiling/os/blob/blob.py @@ -4,7 +4,7 @@ # from qiling import Qiling -from qiling.cc import QlCC, intel, arm, mips +from qiling.cc import QlCC, intel, arm, mips, riscv, ppc from qiling.const import QL_ARCH, QL_OS from qiling.os.fcall import QlFunctionCall from qiling.os.os import QlOs @@ -26,11 +26,14 @@ def __init__(self, ql: Qiling): self.ql = ql cc: QlCC = { - QL_ARCH.X86 : intel.cdecl, - QL_ARCH.X8664 : intel.amd64, - QL_ARCH.ARM : arm.aarch32, - QL_ARCH.ARM64 : arm.aarch64, - QL_ARCH.MIPS : mips.mipso32 + QL_ARCH.X86 : intel.cdecl, + QL_ARCH.X8664 : intel.amd64, + QL_ARCH.ARM : arm.aarch32, + QL_ARCH.ARM64 : arm.aarch64, + QL_ARCH.MIPS : mips.mipso32, + QL_ARCH.RISCV : riscv.riscv, + QL_ARCH.RISCV64 : riscv.riscv, + QL_ARCH.PPC : ppc.ppc, }[ql.arch.type](ql.arch) self.fcall = QlFunctionCall(ql, cc) diff --git a/qiling/os/linux/function_hook.py b/qiling/os/linux/function_hook.py index 7b07c2e58..aa2e6cf64 100644 --- a/qiling/os/linux/function_hook.py +++ b/qiling/os/linux/function_hook.py @@ -79,6 +79,10 @@ def get_ret_pc(self): elif self.ql.arch.type == QL_ARCH.ARM64: return self.ql.arch.regs.x30 + # PPC + elif self.ql.arch.type== QL_ARCH.PPC: + return self.ql.arch.regs.lr + # X86 elif self.ql.arch.type == QL_ARCH.X86: return self.ql.unpack(self.ql.mem.read(self.ql.arch.regs.esp, self.ql.arch.pointersize)) @@ -98,6 +102,10 @@ def context_fixup(self): elif self.ql.arch.type == QL_ARCH.MIPS: pass + # PPC + elif self.ql.arch.type== QL_ARCH.PPC: + pass + # ARM64 elif self.ql.arch.type == QL_ARCH.ARM64: pass @@ -121,6 +129,10 @@ def set_ret(self, addr): elif self.ql.arch.type == QL_ARCH.MIPS: self.ql.arch.regs.ra = addr + # PPC + elif self.ql.arch.type== QL_ARCH.PPC: + self.ql.arch.regs.lr = addr + # ARM64 elif self.ql.arch.type == QL_ARCH.ARM64: self.ql.arch.stack_write(0, addr) @@ -173,29 +185,6 @@ def call_enter(self): else: self.context_fixup() - def ret(self): - # ARM - if self.ql.arch.type == QL_ARCH.ARM: - self.ql.arch.regs.arch_pc = self.ret_pc - - # MIPS32 - elif self.ql.arch.type == QL_ARCH.MIPS: - self.ql.arch.regs.arch_pc = self.ret_pc - - # ARM64 - elif self.ql.arch.type == QL_ARCH.ARM64: - self.ql.arch.regs.arch_pc = self.ret_pc - - # X86 - elif self.ql.arch.type == QL_ARCH.X86: - self.ql.arch.regs.arch_pc = self.ret_pc - - # X8664 - elif self.ql.arch.type == QL_ARCH.X8664: - self.ql.arch.regs.arch_pc = self.ret_pc - else: - raise - def call_exit(self): # if self.ql.arch.type == QL_ARCH.ARM or self.ql.arch.type == QL_ARCH.ARM64: # self.ql.arch.regs.arch_pc = self.ql.arch.regs.arch_pc + 4 @@ -211,7 +200,7 @@ def call_exit(self): else: onexit_cb(self.ql, onexit_userdata) - self.ret() + self.ql.arch.regs.arch_pc = self.ret_pc class HookFuncRel(HookFunc): @@ -614,6 +603,14 @@ def __init__(self, ql, phoff, phnum, phentsize, load_base, hook_mem): ins = b'\x00\x01' self.add_function_hook = self.add_function_hook_relocation + # PowerPC + elif self.ql.arch.type== QL_ARCH.PPC: + self.GLOB_DAT = 21 + self.JMP_SLOT = 22 + # nop + ins = b'\x60\x00\x00\x00' + self.add_function_hook = self.add_function_hook_relocation + self._parse() if self.rel != None: self.show_relocation(self.rel) diff --git a/qiling/os/linux/linux.py b/qiling/os/linux/linux.py index 34a2a733e..ed95a7ecd 100644 --- a/qiling/os/linux/linux.py +++ b/qiling/os/linux/linux.py @@ -10,7 +10,7 @@ from qiling.arch.x86_const import GS_SEGMENT_ADDR, GS_SEGMENT_SIZE from qiling.arch.x86_utils import GDTManager, SegmentManager86, SegmentManager64 from qiling.arch import arm_utils -from qiling.cc import QlCC, intel, arm, mips, riscv +from qiling.cc import QlCC, intel, arm, mips, riscv, ppc from qiling.const import QL_ARCH, QL_OS from qiling.os.fcall import QlFunctionCall from qiling.os.const import * @@ -28,13 +28,14 @@ def __init__(self, ql: Qiling): self.ql = ql cc: QlCC = { - QL_ARCH.X86 : intel.cdecl, - QL_ARCH.X8664 : intel.amd64, - QL_ARCH.ARM : arm.aarch32, - QL_ARCH.ARM64 : arm.aarch64, - QL_ARCH.MIPS : mips.mipso32, - QL_ARCH.RISCV : riscv.riscv, - QL_ARCH.RISCV64: riscv.riscv, + QL_ARCH.X86 : intel.cdecl, + QL_ARCH.X8664 : intel.amd64, + QL_ARCH.ARM : arm.aarch32, + QL_ARCH.ARM64 : arm.aarch64, + QL_ARCH.MIPS : mips.mipso32, + QL_ARCH.RISCV : riscv.riscv, + QL_ARCH.RISCV64 : riscv.riscv, + QL_ARCH.PPC : ppc.ppc, }[ql.arch.type](ql.arch) self.fcall = QlFunctionCall(ql, cc) @@ -107,6 +108,11 @@ def load(self): self.ql.hook_intno(self.hook_syscall, 8) self.thread_class = None + elif self.ql.arch.type == QL_ARCH.PPC: + self.ql.arch.enable_float() + self.ql.hook_intno(self.hook_syscall, 8) + self.thread_class = None + # on fork or execve, do not inherit opened files tagged as 'close on exec' for i in range(len(self.fd)): if getattr(self.fd[i], 'close_on_exec', 0): diff --git a/qiling/os/linux/map_syscall.py b/qiling/os/linux/map_syscall.py index 19bb42695..3d0bcead8 100644 --- a/qiling/os/linux/map_syscall.py +++ b/qiling/os/linux/map_syscall.py @@ -18,8 +18,9 @@ def get_syscall_mapper(archtype: QL_ARCH): 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.RISCV : riscv32_syscall_table, + QL_ARCH.RISCV64 : riscv64_syscall_table, + QL_ARCH.PPC : ppc_syscall_table }[archtype] def __mapper(syscall_num: int) -> str: @@ -2550,3 +2551,421 @@ def __mapper(syscall_num: int) -> str: 446: "landlock_restrict_self", 448: "process_mrelease", } + +ppc_syscall_table = { + 0: "restart_syscall", + 1: "exit", + 2: "fork", + 3: "read", + 4: "write", + 5: "open", + 6: "close", + 7: "waitpid", + 8: "creat", + 9: "link", + 10: "unlink", + 11: "execve", + 12: "chdir", + 13: "time", + 14: "mknod", + 15: "chmod", + 16: "lchown", + 17: "break", + 18: "oldstat", + 19: "lseek", + 20: "getpid", + 21: "mount", + 22: "umount", + 23: "setuid", + 24: "getuid", + 25: "stime", + 26: "ptrace", + 27: "alarm", + 28: "oldfstat", + 29: "pause", + 30: "utime", + 31: "stty", + 32: "gtty", + 33: "access", + 34: "nice", + 35: "ftime", + 36: "sync", + 37: "kill", + 38: "rename", + 39: "mkdir", + 40: "rmdir", + 41: "dup", + 42: "pipe", + 43: "times", + 44: "prof", + 45: "brk", + 46: "setgid", + 47: "getgid", + 48: "signal", + 49: "geteuid", + 50: "getegid", + 51: "acct", + 52: "umount2", + 53: "lock", + 54: "ioctl", + 55: "fcntl", + 56: "mpx", + 57: "setpgid", + 58: "ulimit", + 59: "oldolduname", + 60: "umask", + 61: "chroot", + 62: "ustat", + 63: "dup2", + 64: "getppid", + 65: "getpgrp", + 66: "setsid", + 67: "sigaction", + 68: "sgetmask", + 69: "ssetmask", + 70: "setreuid", + 71: "setregid", + 72: "sigsuspend", + 73: "sigpending", + 74: "sethostname", + 75: "setrlimit", + 76: "getrlimit", + 77: "getrusage", + 78: "gettimeofday", + 79: "settimeofday", + 80: "getgroups", + 81: "setgroups", + 82: "select", + 83: "symlink", + 84: "oldlstat", + 85: "readlink", + 86: "uselib", + 87: "swapon", + 88: "reboot", + 89: "readdir", + 90: "mmap", + 91: "munmap", + 92: "truncate", + 93: "ftruncate", + 94: "fchmod", + 95: "fchown", + 96: "getpriority", + 97: "setpriority", + 98: "profil", + 99: "statfs", + 100: "fstatfs", + 101: "ioperm", + 102: "socketcall", + 103: "syslog", + 104: "setitimer", + 105: "getitimer", + 106: "stat", + 107: "lstat", + 108: "fstat", + 109: "olduname", + 110: "iopl", + 111: "vhangup", + 112: "idle", + 113: "vm86", + 114: "wait4", + 115: "swapoff", + 116: "sysinfo", + 117: "ipc", + 118: "fsync", + 119: "sigreturn", + 120: "clone", + 121: "setdomainname", + 122: "uname", + 123: "modify_ldt", + 124: "adjtimex", + 125: "mprotect", + 126: "sigprocmask", + 127: "create_module", + 128: "init_module", + 129: "delete_module", + 130: "get_kernel_syms", + 131: "quotactl", + 132: "getpgid", + 133: "fchdir", + 134: "bdflush", + 135: "sysfs", + 136: "personality", + 137: "afs_syscall", + 138: "setfsuid", + 139: "setfsgid", + 140: "_llseek", + 141: "getdents", + 142: "_newselect", + 143: "flock", + 144: "msync", + 145: "readv", + 146: "writev", + 147: "getsid", + 148: "fdatasync", + 149: "_sysctl", + 150: "mlock", + 151: "munlock", + 152: "mlockall", + 153: "munlockall", + 154: "sched_setparam", + 155: "sched_getparam", + 156: "sched_setscheduler", + 157: "sched_getscheduler", + 158: "sched_yield", + 159: "sched_get_priority_max", + 160: "sched_get_priority_min", + 161: "sched_rr_get_interval", + 162: "nanosleep", + 163: "mremap", + 164: "setresuid", + 165: "getresuid", + 166: "query_module", + 167: "poll", + 168: "nfsservctl", + 169: "setresgid", + 170: "getresgid", + 171: "prctl", + 172: "rt_sigreturn", + 173: "rt_sigaction", + 174: "rt_sigprocmask", + 175: "rt_sigpending", + 176: "rt_sigtimedwait", + 177: "rt_sigqueueinfo", + 178: "rt_sigsuspend", + 179: "pread64", + 180: "pwrite64", + 181: "chown", + 182: "getcwd", + 183: "capget", + 184: "capset", + 185: "sigaltstack", + 186: "sendfile", + 187: "getpmsg", + 188: "putpmsg", + 189: "vfork", + 190: "ugetrlimit", + 191: "readahead", + 192: "mmap2", + 193: "truncate64", + 194: "ftruncate64", + 195: "stat64", + 196: "lstat64", + 197: "fstat64", + 198: "pciconfig_read", + 199: "pciconfig_write", + 200: "pciconfig_iobase", + 201: "multiplexer", + 202: "getdents64", + 203: "pivot_root", + 204: "fcntl64", + 205: "madvise", + 206: "mincore", + 207: "gettid", + 208: "tkill", + 209: "setxattr", + 210: "lsetxattr", + 211: "fsetxattr", + 212: "getxattr", + 213: "lgetxattr", + 214: "fgetxattr", + 215: "listxattr", + 216: "llistxattr", + 217: "flistxattr", + 218: "removexattr", + 219: "lremovexattr", + 220: "fremovexattr", + 221: "futex", + 222: "sched_setaffinity", + 223: "sched_getaffinity", + 225: "tuxcall", + 226: "sendfile64", + 227: "io_setup", + 228: "io_destroy", + 229: "io_getevents", + 230: "io_submit", + 231: "io_cancel", + 232: "set_tid_address", + 233: "fadvise64", + 234: "exit_group", + 235: "lookup_dcookie", + 236: "epoll_create", + 237: "epoll_ctl", + 238: "epoll_wait", + 239: "remap_file_pages", + 240: "timer_create", + 241: "timer_settime", + 242: "timer_gettime", + 243: "timer_getoverrun", + 244: "timer_delete", + 245: "clock_settime", + 246: "clock_gettime", + 247: "clock_getres", + 248: "clock_nanosleep", + 249: "swapcontext", + 250: "tgkill", + 251: "utimes", + 252: "statfs64", + 253: "fstatfs64", + 254: "fadvise64_64", + 255: "rtas", + 256: "sys_debug_setcontext", + 258: "migrate_pages", + 259: "mbind", + 260: "get_mempolicy", + 261: "set_mempolicy", + 262: "mq_open", + 263: "mq_unlink", + 264: "mq_timedsend", + 265: "mq_timedreceive", + 266: "mq_notify", + 267: "mq_getsetattr", + 268: "kexec_load", + 269: "add_key", + 270: "request_key", + 271: "keyctl", + 272: "waitid", + 273: "ioprio_set", + 274: "ioprio_get", + 275: "inotify_init", + 276: "inotify_add_watch", + 277: "inotify_rm_watch", + 278: "spu_run", + 279: "spu_create", + 280: "pselect6", + 281: "ppoll", + 282: "unshare", + 283: "splice", + 284: "tee", + 285: "vmsplice", + 286: "openat", + 287: "mkdirat", + 288: "mknodat", + 289: "fchownat", + 290: "futimesat", + 291: "fstatat64", + 292: "unlinkat", + 293: "renameat", + 294: "linkat", + 295: "symlinkat", + 296: "readlinkat", + 297: "fchmodat", + 298: "faccessat", + 299: "get_robust_list", + 300: "set_robust_list", + 301: "move_pages", + 302: "getcpu", + 303: "epoll_pwait", + 304: "utimensat", + 305: "signalfd", + 306: "timerfd_create", + 307: "eventfd", + 308: "sync_file_range2", + 309: "fallocate", + 310: "subpage_prot", + 311: "timerfd_settime", + 312: "timerfd_gettime", + 313: "signalfd4", + 314: "eventfd2", + 315: "epoll_create1", + 316: "dup3", + 317: "pipe2", + 318: "inotify_init1", + 319: "perf_event_open", + 320: "preadv", + 321: "pwritev", + 322: "rt_tgsigqueueinfo", + 323: "fanotify_init", + 324: "fanotify_mark", + 325: "prlimit64", + 326: "socket", + 327: "bind", + 328: "connect", + 329: "listen", + 330: "accept", + 331: "getsockname", + 332: "getpeername", + 333: "socketpair", + 334: "send", + 335: "sendto", + 336: "recv", + 337: "recvfrom", + 338: "shutdown", + 339: "setsockopt", + 340: "getsockopt", + 341: "sendmsg", + 342: "recvmsg", + 343: "recvmmsg", + 344: "accept4", + 345: "name_to_handle_at", + 346: "open_by_handle_at", + 347: "clock_adjtime", + 348: "syncfs", + 349: "sendmmsg", + 350: "setns", + 351: "process_vm_readv", + 352: "process_vm_writev", + 353: "finit_module", + 354: "kcmp", + 355: "sched_setattr", + 356: "sched_getattr", + 357: "renameat2", + 358: "seccomp", + 359: "getrandom", + 360: "memfd_create", + 361: "bpf", + 362: "execveat", + 363: "switch_endian", + 364: "userfaultfd", + 365: "membarrier", + 378: "mlock2", + 379: "copy_file_range", + 380: "preadv2", + 381: "pwritev2", + 382: "kexec_file_load", + 383: "statx", + 384: "pkey_alloc", + 385: "pkey_free", + 386: "pkey_mprotect", + 387: "rseq", + 388: "io_pgetevents", + 393: "semget", + 394: "semctl", + 395: "shmget", + 396: "shmctl", + 397: "shmat", + 398: "shmdt", + 399: "msgget", + 400: "msgsnd", + 401: "msgrcv", + 402: "msgctl", + 403: "clock_gettime64", + 404: "clock_settime64", + 405: "clock_adjtime64", + 406: "clock_getres_time64", + 407: "clock_nanosleep_time64", + 408: "timer_gettime64", + 409: "timer_settime64", + 410: "timerfd_gettime64", + 411: "timerfd_settime64", + 412: "utimensat_time64", + 413: "pselect6_time64", + 414: "ppoll_time64", + 416: "io_pgetevents_time64", + 417: "recvmmsg_time64", + 418: "mq_timedsend_time64", + 419: "mq_timedreceive_time64", + 420: "semtimedop_time64", + 421: "rt_sigtimedwait_time64", + 422: "futex_time64", + 423: "sched_rr_get_interval_time64", + 424: "pidfd_send_signal", + 425: "io_uring_setup", + 426: "io_uring_enter", + 427: "io_uring_register", + 428: "open_tree", + 429: "move_mount", + 430: "fsopen", + 431: "fsconfig", + 432: "fsmount", + 433: "fspick", +} diff --git a/qiling/os/posix/const.py b/qiling/os/posix/const.py index 51a03393e..7340a758a 100644 --- a/qiling/os/posix/const.py +++ b/qiling/os/posix/const.py @@ -515,6 +515,24 @@ 'O_LARGEFILE': None, } +linux_ppc_open_flags = { + 'O_RDONLY': 0x0, + 'O_WRONLY': 0x1, + 'O_RDWR': 0x2, + 'O_NONBLOCK': 0x800, + 'O_APPEND': 0x400, + 'O_ASYNC': 0x2000, + 'O_SYNC': 0x101000, + 'O_NOFOLLOW': 0x8000, + 'O_CREAT': 0x40, + 'O_TRUNC': 0x200, + 'O_EXCL': 0x80, + 'O_NOCTTY': 0x100, + 'O_DIRECTORY': 0x4000, + 'O_BINARY' : None, + 'O_LARGEFILE': 0x10000, +} + freebsd_x86_open_flags = { 'O_RDONLY': 0x0, 'O_WRONLY': 0x1, diff --git a/qiling/os/posix/const_mapping.py b/qiling/os/posix/const_mapping.py index 2dd15a21f..e04030c34 100644 --- a/qiling/os/posix/const_mapping.py +++ b/qiling/os/posix/const_mapping.py @@ -61,6 +61,8 @@ def flag_mapping(flags, mapping_name, mapping_from, mapping_to): f = linux_mips_open_flags elif ql.arch.type in (QL_ARCH.RISCV, QL_ARCH.RISCV64): f = linux_riscv_open_flags + elif ql.arch.type == QL_ARCH.PPC: + f = linux_ppc_open_flags elif virt_os == QL_OS.MACOS: if ql.arch.type in (QL_ARCH.X86, QL_ARCH.X8664): diff --git a/qiling/os/posix/posix.py b/qiling/os/posix/posix.py index d71069ba0..73278057a 100644 --- a/qiling/os/posix/posix.py +++ b/qiling/os/posix/posix.py @@ -19,9 +19,10 @@ UC_X86_REG_R9, UC_X86_REG_RAX ) from unicorn.riscv_const import UC_RISCV_REG_A7 +from unicorn.ppc_const import UC_PPC_REG_0 from qiling import Qiling -from qiling.cc import QlCC, intel, arm, mips, riscv +from qiling.cc import QlCC, intel, arm, mips, riscv, ppc from qiling.const import QL_ARCH, QL_OS, QL_INTERCEPT from qiling.exception import QlErrorSyscallNotFound from qiling.os.os import QlOs @@ -60,6 +61,9 @@ class riscv32(riscv.riscv): class riscv64(riscv.riscv): pass +class ppc(ppc.ppc): + pass + class QlFileDes: def __init__(self): @@ -115,7 +119,8 @@ def __init__(self, ql: Qiling): 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.RISCV64 : UC_RISCV_REG_A7, + QL_ARCH.PPC : UC_PPC_REG_0 }[self.ql.arch.type] # handle some special cases @@ -133,7 +138,8 @@ def __init__(self, ql: Qiling): QL_ARCH.X86 : intel32, QL_ARCH.X8664 : intel64, QL_ARCH.RISCV : riscv32, - QL_ARCH.RISCV64 : riscv64 + QL_ARCH.RISCV64 : riscv64, + QL_ARCH.PPC : ppc }[self.ql.arch.type](self.ql.arch) # select syscall mapping function based on emulated OS and architecture diff --git a/qiling/os/posix/syscall/stat.py b/qiling/os/posix/syscall/stat.py index 1c7130161..01efd092d 100644 --- a/qiling/os/posix/syscall/stat.py +++ b/qiling/os/posix/syscall/stat.py @@ -819,6 +819,108 @@ class LinuxRISCVStat(ctypes.Structure): _pack_ = 8 +# Srouce: https://elixir.bootlin.com/linux/latest/source/arch/powerpc/include/uapi/asm/stat.h#L30 +# struct stat { +# unsigned long st_dev; +# ino_t st_ino; +# #ifdef __powerpc64__ +# unsigned long st_nlink; +# mode_t st_mode; +# #else +# mode_t st_mode; +# unsigned short st_nlink; +# #endif +# uid_t st_uid; +# gid_t st_gid; +# unsigned long st_rdev; +# long st_size; +# unsigned long st_blksize; +# unsigned long st_blocks; +# unsigned long st_atime; +# unsigned long st_atime_nsec; +# unsigned long st_mtime; +# unsigned long st_mtime_nsec; +# unsigned long st_ctime; +# unsigned long st_ctime_nsec; +# unsigned long __unused4; +# unsigned long __unused5; +# #ifdef __powerpc64__ +# unsigned long __unused6; +# #endif +# }; + +class LinuxPPCStat(ctypes.BigEndianStructure): + _fields_ = [ + ("st_dev", ctypes.c_uint32), + ("st_ino", ctypes.c_uint32), + ("st_mode", ctypes.c_uint32), + ("st_nlink", ctypes.c_uint16), + ("st_uid", ctypes.c_uint32), + ("st_gid", ctypes.c_uint32), + ("st_rdev", ctypes.c_uint32), + ("st_size", ctypes.c_uint32), + ("st_blksize", ctypes.c_uint32), + ("st_blocks", ctypes.c_uint32), + ("st_atime", ctypes.c_uint32), + ("st_atime_ns", ctypes.c_uint32), + ("st_mtime", ctypes.c_uint32), + ("st_mtime_ns", ctypes.c_uint32), + ("st_ctime", ctypes.c_uint32), + ("st_ctime_ns", ctypes.c_uint32), + ("__unused4", ctypes.c_uint32), + ("__unused5", ctypes.c_uint32) + ] + + _pack_ = 8 + +# Srouce: https://elixir.bootlin.com/linux/latest/source/arch/powerpc/include/uapi/asm/stat.h#L60 +# struct stat64 { +# unsigned long long st_dev; /* Device. */ +# unsigned long long st_ino; /* File serial number. */ +# unsigned int st_mode; /* File mode. */ +# unsigned int st_nlink; /* Link count. */ +# unsigned int st_uid; /* User ID of the file's owner. */ +# unsigned int st_gid; /* Group ID of the file's group. */ +# unsigned long long st_rdev; /* Device number, if device. */ +# unsigned short __pad2; +# long long st_size; /* Size of file, in bytes. */ +# int st_blksize; /* Optimal block size for I/O. */ +# long long st_blocks; /* Number 512-byte blocks allocated. */ +# int st_atime; /* Time of last access. */ +# unsigned int st_atime_nsec; +# int st_mtime; /* Time of last modification. */ +# unsigned int st_mtime_nsec; +# int st_ctime; /* Time of last status change. */ +# unsigned int st_ctime_nsec; +# unsigned int __unused4; +# unsigned int __unused5; +# }; + +class LinuxPPCStat64(ctypes.BigEndianStructure): + _fields_ = [ + ("st_dev", ctypes.c_uint64), + ("st_ino", ctypes.c_uint64), + ("st_mode", ctypes.c_uint32), + ("st_nlink", ctypes.c_uint32), + ("st_uid", ctypes.c_uint32), + ("st_gid", ctypes.c_uint32), + ("st_rdev", ctypes.c_uint64), + ("__pad2", ctypes.c_uint16), + ("st_size", ctypes.c_uint64), + ("st_blksize", ctypes.c_uint32), + ("st_blocks", ctypes.c_uint64), + ("st_atime", ctypes.c_uint32), + ("st_atime_ns", ctypes.c_uint32), + ("st_mtime", ctypes.c_uint32), + ("st_mtime_ns", ctypes.c_uint32), + ("st_ctime", ctypes.c_uint32), + ("st_ctime_ns", ctypes.c_uint32), + ("__unused4", ctypes.c_uint32), + ("__unused5", ctypes.c_uint32) + ] + + _pack_ = 8 + # Source: openqnx lib/c/public/sys/stat.h # # struct stat { @@ -991,6 +1093,8 @@ def get_stat64_struct(ql: Qiling): return LinuxARMStat64() elif ql.arch.type in (QL_ARCH.RISCV, QL_ARCH.RISCV64): return LinuxRISCVStat() + elif ql.arch.type == QL_ARCH.PPC: + return LinuxPPCStat64() elif ql.os.type == QL_OS.MACOS: return MacOSStat64() elif ql.os.type == QL_OS.QNX: @@ -1034,6 +1138,8 @@ def get_stat_struct(ql: Qiling): return LinuxARM64EBStat() elif ql.arch.type in (QL_ARCH.RISCV, QL_ARCH.RISCV64): return LinuxRISCVStat() + elif ql.archtype == QL_ARCH.PPC: + return LinuxPPCStat() elif ql.os.type == QL_OS.QNX: if ql.arch.type == QL_ARCH.ARM64: return QNXARM64Stat() diff --git a/qiling/os/qnx/qnx.py b/qiling/os/qnx/qnx.py index 8c1b89c15..9f79f1d95 100644 --- a/qiling/os/qnx/qnx.py +++ b/qiling/os/qnx/qnx.py @@ -14,7 +14,7 @@ from qiling.os.qnx.helpers import QnxConn from qiling.os.qnx.structs import _thread_local_storage -from qiling.cc import QlCC, intel, arm, mips, riscv +from qiling.cc import QlCC, intel, arm, mips, riscv, ppc from qiling.const import QL_ARCH, QL_OS from qiling.os.fcall import QlFunctionCall from qiling.os.const import * @@ -29,13 +29,14 @@ def __init__(self, ql: Qiling): self.ql = ql cc: QlCC = { - QL_ARCH.X86 : intel.cdecl, - QL_ARCH.X8664 : intel.amd64, - QL_ARCH.ARM : arm.aarch32, - QL_ARCH.ARM64 : arm.aarch64, - QL_ARCH.MIPS : mips.mipso32, - QL_ARCH.RISCV : riscv.riscv, - QL_ARCH.RISCV64: riscv.riscv, + QL_ARCH.X86 : intel.cdecl, + QL_ARCH.X8664 : intel.amd64, + QL_ARCH.ARM : arm.aarch32, + QL_ARCH.ARM64 : arm.aarch64, + QL_ARCH.MIPS : mips.mipso32, + QL_ARCH.RISCV : riscv.riscv, + QL_ARCH.RISCV64 : riscv.riscv, + QL_ARCH.PPC : ppc.ppc, }[ql.arch.type](ql.arch) self.fcall = QlFunctionCall(ql, cc) diff --git a/qiling/utils.py b/qiling/utils.py index 08573a008..68607fe60 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -126,6 +126,7 @@ def __emu_env_from_elf(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], O EM_X86_64 = 62 EM_AARCH64 = 183 EM_RISCV = 243 + EM_PPC = 20 endianess = { ELFDATA2LSB : (QL_ENDIAN.EL, 'little'), @@ -136,7 +137,8 @@ def __emu_env_from_elf(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], O EM_386 : QL_ARCH.X86, EM_MIPS : QL_ARCH.MIPS, EM_ARM : QL_ARCH.ARM, - EM_RISCV : QL_ARCH.RISCV + EM_RISCV : QL_ARCH.RISCV, + EM_PPC : QL_ARCH.PPC } machines64 = { @@ -381,7 +383,8 @@ def select_arch(archtype: QL_ARCH, endian: QL_ENDIAN, thumb: bool) -> QlClassIni QL_ARCH.EVM : r'evm.evm', QL_ARCH.CORTEX_M : r'cortex_m', QL_ARCH.RISCV : r'riscv', - QL_ARCH.RISCV64 : r'riscv64' + QL_ARCH.RISCV64 : r'riscv64', + QL_ARCH.PPC : r'ppc' }[archtype] qlarch_path = f'.arch.{module}' diff --git a/tests/test_elf.py b/tests/test_elf.py index 49407a310..8ce4712a4 100644 --- a/tests/test_elf.py +++ b/tests/test_elf.py @@ -820,6 +820,12 @@ def test_syscall_ftruncate(ql, ftrunc_fd, ftrunc_length, *args): del ql + def test_elf_linux_powerpc(self): + ql = Qiling(["../examples/rootfs/powerpc_linux/bin/powerpc_hello"], "../examples/rootfs/powerpc_linux", verbose=QL_VERBOSE.DEBUG) + ql.run() + del ql + + def test_elf_linux_arm_custom_syscall(self): def my_syscall_write(ql, write_fd, write_buf, write_count, *args, **kw): regreturn = 0 From 59d31c22525f03cc38d61a7ea9908faa2d079d22 Mon Sep 17 00:00:00 2001 From: Felipe Custodio Date: Mon, 25 Apr 2022 11:33:03 +0200 Subject: [PATCH 326/406] Add missing __init__.py to os blob --- qiling/os/blob/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 qiling/os/blob/__init__.py diff --git a/qiling/os/blob/__init__.py b/qiling/os/blob/__init__.py new file mode 100644 index 000000000..e69de29bb From 513bdeeb651c23db7cf494347d6a89ea950cc4d0 Mon Sep 17 00:00:00 2001 From: Nobutaka Mantani <2285962+nmantani@users.noreply.github.com> Date: Sat, 30 Apr 2022 00:02:03 +0900 Subject: [PATCH 327/406] Use binary mode on Windows host for emulation of non-Windows operatins systems --- qiling/os/posix/const_mapping.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/qiling/os/posix/const_mapping.py b/qiling/os/posix/const_mapping.py index e04030c34..41036eb16 100644 --- a/qiling/os/posix/const_mapping.py +++ b/qiling/os/posix/const_mapping.py @@ -34,13 +34,15 @@ def _constant_mapping(bits: int, d_map: Mapping[str, int], ret: MutableSequence[ def ql_open_flag_mapping(ql: Qiling, flags): - def flag_mapping(flags, mapping_name, mapping_from, mapping_to): + def flag_mapping(flags, mapping_name, mapping_from, mapping_to, host_os, virt_os): ret = 0 for n in mapping_name: if mapping_from[n] is None or mapping_to[n] is None: continue if (flags & mapping_from[n]) == mapping_from[n]: ret = ret | mapping_to[n] + if (host_os == QL_OS.WINDOWS and virt_os != QL_OS.WINDOWS): + ret = ret | mapping_to['O_BINARY'] return ret f = {} @@ -86,7 +88,7 @@ def flag_mapping(flags, mapping_name, mapping_from, mapping_to): if f == t: return flags - return flag_mapping(flags, open_flags_name, f, t) + return flag_mapping(flags, open_flags_name, f, t, host_os, virt_os) def mmap_flag_mapping(flags): From fe76d4fc0b1874f25db4878ae66f4d15970d6d83 Mon Sep 17 00:00:00 2001 From: chinggg <24590067+chinggg@users.noreply.github.com> Date: Sun, 1 May 2022 09:17:09 +0800 Subject: [PATCH 328/406] fix(memory): remove misused region bound check of unmap_all --- qiling/os/memory.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qiling/os/memory.py b/qiling/os/memory.py index 843059534..5357623ef 100644 --- a/qiling/os/memory.py +++ b/qiling/os/memory.py @@ -419,8 +419,7 @@ def unmap_all(self): """ for begin, end, _ in self.ql.uc.mem_regions(): - if begin and end: - self.unmap(begin, end - begin + 1) + self.unmap(begin, end - begin + 1) def is_available(self, addr: int, size: int) -> bool: """Query whether the memory range starting at `addr` and is of length of `size` bytes From 9940ae8b12613246534fbeb83fd2d63969f43cd1 Mon Sep 17 00:00:00 2001 From: machinewu Date: Sun, 1 May 2022 16:07:50 +0800 Subject: [PATCH 329/406] change deprecated interfaces of IDA --- qiling/extensions/idaplugin/qilingida.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiling/extensions/idaplugin/qilingida.py b/qiling/extensions/idaplugin/qilingida.py index ad6f3709d..b82de01ea 100644 --- a/qiling/extensions/idaplugin/qilingida.py +++ b/qiling/extensions/idaplugin/qilingida.py @@ -334,7 +334,7 @@ def get_filetype(): @staticmethod def get_ql_arch_string(): info = IDA.get_info_structure() - proc = info.get_procName().lower() + proc = info.procname.lower() result = None if proc == "metapc": result = "x86" @@ -814,7 +814,7 @@ def __init__(self, handler, action): self.action_type = action def activate(self, ctx): - if ctx.form_type == BWN_DISASM: + if ctx.widget_type == BWN_DISASM: self.action_handler.ql_handle_menu_action(self.action_type) return 1 From 6c2aa3089a212f23e4b878d65703517feb6b4ecd Mon Sep 17 00:00:00 2001 From: nullableVoidPtr <30564701+nullableVoidPtr@users.noreply.github.com> Date: Sun, 1 May 2022 16:40:26 +0800 Subject: [PATCH 330/406] Use importlib to retrieve package version --- qiling/__init__.py | 7 ++++++- qiling/__version__.py | 3 --- setup.py | 9 ++------- 3 files changed, 8 insertions(+), 11 deletions(-) delete mode 100644 qiling/__version__.py diff --git a/qiling/__init__.py b/qiling/__init__.py index 375f0373b..96f113377 100644 --- a/qiling/__init__.py +++ b/qiling/__init__.py @@ -1,4 +1,9 @@ +import importlib.metadata from .core import Qiling -from .__version__ import __version__ + +try: + __version__ = importlib.metadata.version(__package__ or __name__) +except importlib.metadata.PackageNotFoundError: + __version__ = "0.0.0" __all__ = ['Qiling'] diff --git a/qiling/__version__.py b/qiling/__version__.py deleted file mode 100644 index 94f2af16c..000000000 --- a/qiling/__version__.py +++ /dev/null @@ -1,3 +0,0 @@ -# NOTE: use "-dev" for dev branch -#__version__ = "1.4.3" -__version__ = "1.4.3" + "-dev" diff --git a/setup.py b/setup.py index d19877a57..5496da6cd 100644 --- a/setup.py +++ b/setup.py @@ -2,15 +2,10 @@ # # Python setup for Qiling framework -import sys, os from setuptools import setup, find_packages -here = os.path.abspath(os.path.dirname(__file__)) -gb = {} -with open(os.path.join(here, "qiling", "__version__.py"), "r+") as f: - exec(f.read(), gb) - -VERSION = gb['__version__'] +# NOTE: use "-dev" for dev branch +VERSION = "1.4.3" + "-dev" requirements = [ "capstone>=4.0.1", From aff5f2e8b54b53d226b4828fdaa3651e3cc9f4a9 Mon Sep 17 00:00:00 2001 From: "kj.xwings.l" Date: Tue, 3 May 2022 17:34:46 +0800 Subject: [PATCH 331/406] Update test_android.py --- tests/test_android.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_android.py b/tests/test_android.py index 10a8b53ff..187de1601 100644 --- a/tests/test_android.py +++ b/tests/test_android.py @@ -13,8 +13,8 @@ class TestAndroid(unittest.TestCase): @unittest.skipUnless(platform.system() == 'Linux', 'run only on Linux') def test_android_arm64(self): - test_binary = "../examples/rootfs/arm64_android/bin/arm64_android_hello" - rootfs = "../examples/rootfs/arm64_android" + test_binary = "../examples/rootfs/arm64_android6.0/bin/arm64_android_hello" + rootfs = "../examples/rootfs/arm64_android6.0" # FUTURE FIX: at this stage, need a file called /proc/self/exe in the rootfs - Android linker calls stat against /proc/self/exe and bails if it can't find it # qiling handles readlink against /proc/self/exe, but doesn't handle it in stat From c9b3922f679bfe114fb62647dbd5b313899291be Mon Sep 17 00:00:00 2001 From: xwings Date: Tue, 3 May 2022 18:24:34 +0800 Subject: [PATCH 332/406] fix android test --- examples/rootfs | 2 +- tests/test_android.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/rootfs b/examples/rootfs index 76780c9e9..15c8d8428 160000 --- a/examples/rootfs +++ b/examples/rootfs @@ -1 +1 @@ -Subproject commit 76780c9e91471db1820b160d3b1d4a9ed6b13325 +Subproject commit 15c8d842876d709f013e4e42643d5d605cddbac9 diff --git a/tests/test_android.py b/tests/test_android.py index 187de1601..10a8b53ff 100644 --- a/tests/test_android.py +++ b/tests/test_android.py @@ -13,8 +13,8 @@ class TestAndroid(unittest.TestCase): @unittest.skipUnless(platform.system() == 'Linux', 'run only on Linux') def test_android_arm64(self): - test_binary = "../examples/rootfs/arm64_android6.0/bin/arm64_android_hello" - rootfs = "../examples/rootfs/arm64_android6.0" + test_binary = "../examples/rootfs/arm64_android/bin/arm64_android_hello" + rootfs = "../examples/rootfs/arm64_android" # FUTURE FIX: at this stage, need a file called /proc/self/exe in the rootfs - Android linker calls stat against /proc/self/exe and bails if it can't find it # qiling handles readlink against /proc/self/exe, but doesn't handle it in stat From 253a298d8fd72fcf8dabe564403b58cacd70a4ac Mon Sep 17 00:00:00 2001 From: Bet4 <0xbet4@gmail.com> Date: Tue, 3 May 2022 10:28:40 +0800 Subject: [PATCH 333/406] Implement some syscalls to support Android Runtime --- README.md | 2 +- examples/rootfs | 2 +- qiling/os/linux/futex.py | 13 +++---- qiling/os/posix/syscall/__init__.py | 3 +- qiling/os/posix/syscall/personality.py | 14 ++++++++ qiling/os/posix/syscall/resource.py | 37 +++++++++++++------- qiling/os/posix/syscall/stat.py | 25 ++++++++++---- qiling/os/posix/syscall/unistd.py | 23 ++++++++++-- qiling/profiles/linux.ql | 4 +-- tests/test_android.py | 48 +++++++++++++++++++++----- 10 files changed, 131 insertions(+), 40 deletions(-) create mode 100644 qiling/os/posix/syscall/personality.py diff --git a/README.md b/README.md index ce7bee4b5..8abbe0ef6 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Qiling is an advanced binary emulation framework, with the following features: -- Emulate multi-platforms: Windows, MacOS, Linux, BSD, UEFI, DOS, MBR, Ethereum Virtual Machine +- Emulate multi-platforms: Windows, MacOS, Linux, Android, BSD, UEFI, DOS, MBR, Ethereum Virtual Machine - Emulate multi-architectures: 8086, X86, X86_64, ARM, ARM64, MIPS, RISCV, PowerPC - Support multiple file formats: PE, MachO, ELF, COM, MBR - Support Windows Driver (.sys), Linux Kernel Module (.ko) & MacOS Kernel (.kext) via [Demigod](https://groundx.io/demigod/) diff --git a/examples/rootfs b/examples/rootfs index 76780c9e9..15c8d8428 160000 --- a/examples/rootfs +++ b/examples/rootfs @@ -1 +1 @@ -Subproject commit 76780c9e91471db1820b160d3b1d4a9ed6b13325 +Subproject commit 15c8d842876d709f013e4e42643d5d605cddbac9 diff --git a/qiling/os/linux/futex.py b/qiling/os/linux/futex.py index 1f17b9b23..ef78db4f3 100644 --- a/qiling/os/linux/futex.py +++ b/qiling/os/linux/futex.py @@ -10,24 +10,25 @@ from queue import Queue class QlLinuxFutexManagement: - + FUTEX_BITSET_MATCH_ANY = 0xffffffff - + def __init__(self): self._wait_list = {} - + @property def wait_list(self): return self._wait_list - + def futex_wait(self, ql, uaddr, t, val, bitset=FUTEX_BITSET_MATCH_ANY): + EAGAIN = 11 def _sched_wait_event(cur_thread): ql.log.debug(f"Wait for notifications.") event.wait() uaddr_value = ql.unpack32(ql.mem.read(uaddr, 4)) if uaddr_value != val: ql.log.debug(f"uaddr: {hex(uaddr_value)} != {hex(val)}") - return -1 + return -EAGAIN ql.emu_stop() if uaddr not in self.wait_list.keys(): self.wait_list[uaddr] = Queue() @@ -35,7 +36,7 @@ def _sched_wait_event(cur_thread): self.wait_list[uaddr].put((bitset, t, event)) t.sched_cb = _sched_wait_event return 0 - + def get_futex_wake_list(self, ql, addr, number, bitset=FUTEX_BITSET_MATCH_ANY): wakes = [] if addr not in self.wait_list or number == 0: diff --git a/qiling/os/posix/syscall/__init__.py b/qiling/os/posix/syscall/__init__.py index ef20cef56..4e87d9b65 100644 --- a/qiling/os/posix/syscall/__init__.py +++ b/qiling/os/posix/syscall/__init__.py @@ -7,9 +7,11 @@ from .ioctl import * from .mman import * from .net import * +from .personality import * from .poll import * from .prctl import * from .ptrace import * +from .random import * from .resource import * from .sched import * from .select import * @@ -25,4 +27,3 @@ from .unistd import * from .utsname import * from .wait import * -from .random import * diff --git a/qiling/os/posix/syscall/personality.py b/qiling/os/posix/syscall/personality.py new file mode 100644 index 000000000..91a869255 --- /dev/null +++ b/qiling/os/posix/syscall/personality.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +import os + + +from qiling import Qiling +from qiling.const import * + +def ql_syscall_personality(ql: Qiling, persona: int): + regreturn = 0 + return regreturn diff --git a/qiling/os/posix/syscall/resource.py b/qiling/os/posix/syscall/resource.py index c244ad558..f04251bcc 100644 --- a/qiling/os/posix/syscall/resource.py +++ b/qiling/os/posix/syscall/resource.py @@ -19,9 +19,16 @@ def setrlimit(self, resource, rlim): from qiling import Qiling def __getrlimit_common(ql: Qiling, res: int, rlim: int) -> int: - rlimit = resource.getrlimit(res) - ql.mem.write(rlim, ql.pack32s(rlimit[0]) + ql.pack32s(rlimit[1])) - + RLIMIT_STACK = 3 + if res == RLIMIT_STACK: + if ql.arch.bits == 64: + stack_size = int(ql.os.profile.get("OS64", "stack_size"), 16) + elif ql.arch.bits == 32: + stack_size = int(ql.os.profile.get("OS32", "stack_size"), 16) + rlimit = (stack_size, -1) + else: + rlimit = resource.getrlimit(res) + ql.mem.write(rlim, ql.pack64s(rlimit[0]) + ql.pack64s(rlimit[1])) return 0 def ql_syscall_ugetrlimit(ql: Qiling, res: int, rlim: int): @@ -30,23 +37,29 @@ def ql_syscall_ugetrlimit(ql: Qiling, res: int, rlim: int): def ql_syscall_getrlimit(ql: Qiling, res: int, rlim: int): return __getrlimit_common(ql, res, rlim) -def ql_syscall_setrlimit(ql: Qiling, setrlimit_resource: int, setrlimit_rlim: int): +def ql_syscall_setrlimit(ql: Qiling, res: int, rlim: int): # maybe we can nop the setrlimit - tmp_rlim = (ql.unpack32s(ql.mem.read(setrlimit_rlim, 4)), ql.unpack32s(ql.mem.read(setrlimit_rlim + 4, 4))) - resource.setrlimit(setrlimit_resource, tmp_rlim) + tmp_rlim = (ql.unpack32s(ql.mem.read(rlim, 4)), ql.unpack32s(ql.mem.read(rlim + 4, 4))) + resource.setrlimit(res, tmp_rlim) return 0 def ql_syscall_prlimit64(ql: Qiling, pid: int, res: int, new_limit: int, old_limit: int): # setrlimit() and getrlimit() if pid == 0 and new_limit == 0: - rlim = resource.getrlimit(res) - ql.mem.write(old_limit, ql.packs(rlim[0]) + ql.packs(rlim[1])) - - return 0 + try: + rlim = resource.getrlimit(res) + ql.mem.write(old_limit, ql.packs(rlim[0]) + ql.packs(rlim[1])) + return 0 + except: + return -1 # set other process which pid != 0 return -1 -def ql_syscall_getpriority(ql: Qiling, getpriority_which: int, getpriority_who: int): - return os.getpriority(getpriority_which, getpriority_who) +def ql_syscall_getpriority(ql: Qiling, which: int, who: int): + try: + regreturn = os.getpriority(which, who) + except: + regreturn = -1 + return regreturn diff --git a/qiling/os/posix/syscall/stat.py b/qiling/os/posix/syscall/stat.py index 01efd092d..efc809119 100644 --- a/qiling/os/posix/syscall/stat.py +++ b/qiling/os/posix/syscall/stat.py @@ -1208,11 +1208,11 @@ def transform_path(ql: Qiling, dirfd: int, path: int, flags: int = 0): then the target file is the one referred to by the file descriptor dirfd. """ - dirfd = ql.unpacks(ql.pack(dirfd)) + dirfd = ql.unpack32s(ql.pack32(dirfd & (1<<32)-1)) path = ql.os.utils.read_cstring(path) if path.startswith('/'): - return None, os.path.join(ql.rootfs, path) + return None, os.path.join(ql.rootfs, path.lstrip('/')) if dirfd == AT_FDCWD: return None, ql.os.path.transform_to_real_path(path) @@ -1225,15 +1225,26 @@ def transform_path(ql: Qiling, dirfd: int, path: int, flags: int = 0): def ql_syscall_chmod(ql: Qiling, filename: int, mode: int): + file_path = ql.os.utils.read_cstring(filename) + real_path = ql.os.path.transform_to_real_path(file_path) + try: + os.chmod(real_path, mode) + regreturn = 0 + except: + regreturn = -1 ql.log.debug(f'chmod("{ql.os.utils.read_cstring(filename)}", {mode:d}) = 0') - - return 0 + return regreturn def ql_syscall_fchmod(ql: Qiling, fd: int, mode: int): if fd not in range(NR_OPEN) or ql.os.fd[fd] is None: return -EBADF - - return 0 + try: + os.fchmod(ql.os.fd[fd].fileno(), mode) + regreturn = 0 + except: + regreturn = -1 + ql.log.debug("fchmod(%d, %d) = %d" % (fd, mode, regreturn)) + return regreturn def ql_syscall_fstatat64(ql: Qiling, dirfd: int, path: int, buf_ptr: int, flags: int): dirfd, real_path = transform_path(ql, dirfd, path, flags) @@ -1460,6 +1471,7 @@ def ql_syscall_mknodat(ql: Qiling, dirfd: int, path: int, mode: int, dev: int): except: regreturn = -1 + ql.log.debug("mknodat(%d, %s, 0%o, %d) = %d" % (dirfd, real_path, mode, dev, regreturn)) return regreturn @@ -1474,6 +1486,7 @@ def ql_syscall_mkdir(ql: Qiling, pathname: int, mode: int): except: regreturn = -1 + ql.log.debug("mkdir(%s, 0%o) = %d" % (real_path, mode, regreturn)) return regreturn def ql_syscall_rmdir(ql: Qiling, pathname: int): diff --git a/qiling/os/posix/syscall/unistd.py b/qiling/os/posix/syscall/unistd.py index fc23cb44a..942ac4140 100644 --- a/qiling/os/posix/syscall/unistd.py +++ b/qiling/os/posix/syscall/unistd.py @@ -116,6 +116,27 @@ def ql_syscall_capset(ql: Qiling, hdrp: int, datap: int): def ql_syscall_kill(ql: Qiling, pid: int, sig: int): return 0 + +def ql_syscall_fsync(ql: Qiling, fd: int): + try: + os.fsync(ql.os.fd[fd].fileno()) + regreturn = 0 + except: + regreturn = -1 + ql.log.debug("fsync(%d) = %d" % (fd, regreturn)) + return regreturn + + +def ql_syscall_fdatasync(ql: Qiling, fd: int): + try: + os.fdatasync(ql.os.fd[fd].fileno()) + regreturn = 0 + except: + regreturn = -1 + ql.log.debug("fdatasync(%d) = %d" % (fd, regreturn)) + return regreturn + + def ql_syscall_faccessat(ql: Qiling, dfd: int, filename: int, mode: int): access_path = ql.os.utils.read_cstring(filename) real_path = ql.os.path.transform_to_real_path(access_path) @@ -534,8 +555,6 @@ def ql_syscall_dup3(ql: Qiling, fd: int, newfd: int, flags: int): def ql_syscall_set_tid_address(ql: Qiling, tidptr: int): if ql.os.thread_management: - ql.os.thread_management.cur_thread.set_clear_child_tid_addr(tidptr) - regreturn = ql.os.thread_management.cur_thread.id else: regreturn = os.getpid() diff --git a/qiling/profiles/linux.ql b/qiling/profiles/linux.ql index 5b53d982a..f810f94b0 100644 --- a/qiling/profiles/linux.ql +++ b/qiling/profiles/linux.ql @@ -19,7 +19,7 @@ stack_address = 0x7ff0d000 stack_size = 0x30000 load_address = 0x56555000 interp_address = 0x047ba000 -mmap_address = 0x774bf000 +mmap_address = 0x90000000 [KERNEL] @@ -48,4 +48,4 @@ current_path = / # To use IPv6 or not, to avoid binary double bind. ipv6 and ipv4 bind the same port at the same time bindtolocalhost = True # Bind to localhost -ipv6 = False \ No newline at end of file +ipv6 = False diff --git a/tests/test_android.py b/tests/test_android.py index 187de1601..f7faee994 100644 --- a/tests/test_android.py +++ b/tests/test_android.py @@ -4,26 +4,56 @@ # import os, platform, sys, unittest +from collections import defaultdict sys.path.append("..") from qiling import Qiling -from qiling.const import QL_VERBOSE +from qiling.os.mapper import QlFsMappedObject +from qiling.os.posix import syscall + + +class Fake_maps(QlFsMappedObject): + def __init__(self, ql): + self.ql = ql + def read(self, size): + stack = next(filter(lambda x : x[3]=='[stack]', self.ql.mem.map_info)) + return ('%x-%x %s\n' % (stack[0], stack[1], stack[3])).encode() + def fstat(self): + return defaultdict(int) + def close(self): + return 0 + +def my_syscall_close(ql, fd): + if fd in [0, 1, 2]: + return 0 + return syscall.ql_syscall_close(ql, fd) class TestAndroid(unittest.TestCase): @unittest.skipUnless(platform.system() == 'Linux', 'run only on Linux') def test_android_arm64(self): - test_binary = "../examples/rootfs/arm64_android6.0/bin/arm64_android_hello" + test_binary = "../examples/rootfs/arm64_android6.0/bin/arm64_android_jniart" rootfs = "../examples/rootfs/arm64_android6.0" + env = {"ANDROID_DATA":"/data", "ANDROID_ROOT":"/system"} - # FUTURE FIX: at this stage, need a file called /proc/self/exe in the rootfs - Android linker calls stat against /proc/self/exe and bails if it can't find it - # qiling handles readlink against /proc/self/exe, but doesn't handle it in stat - # https://cs.android.com/android/platform/superproject/+/master:bionic/linker/linker_main.cpp;l=221 - self.assertTrue(os.path.isfile(os.path.join(rootfs, "proc", "self", "exe")), rootfs + - "/proc/self/exe not found, Android linker will bail. Need a file at that location (empty is fine)") - - ql = Qiling([test_binary], rootfs, verbose=QL_VERBOSE.DEBUG, multithread=True) + ql = Qiling([test_binary], rootfs, env, multithread=True) + ql.os.set_syscall("close", my_syscall_close) + ql.add_fs_mapper("/proc/self/task/2000/maps", Fake_maps(ql)) ql.run() + del ql + + + #@unittest.skipUnless(platform.system() == 'Linux', 'run only on Linux') + #def test_android_arm(self): + # test_binary = "../examples/rootfs/arm64_android6.0/bin/arm_android_jniart" + # rootfs = "../examples/rootfs/arm64_android6.0" + # env = {"ANDROID_DATA":"/data", "ANDROID_ROOT":"/system"} + + # ql = Qiling([test_binary], rootfs, env, multithread=True) + # ql.os.set_syscall("close", my_syscall_close) + # ql.add_fs_mapper("/proc/self/task/2000/maps", Fake_maps(ql)) + # ql.run() + # del ql if __name__ == "__main__": From 69409d75372ce4bb0a66de4a1de0e56ed28c20b3 Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 4 May 2022 22:40:46 +0300 Subject: [PATCH 334/406] Complete rewrite of gdbserver --- qiling/debugger/gdb/gdb.py | 1521 +++++++++++++++++--------------- qiling/debugger/gdb/utils.py | 121 +-- qiling/debugger/gdb/xmlregs.py | 110 +++ 3 files changed, 973 insertions(+), 779 deletions(-) create mode 100644 qiling/debugger/gdb/xmlregs.py diff --git a/qiling/debugger/gdb/gdb.py b/qiling/debugger/gdb/gdb.py index 8b3416663..574341a46 100644 --- a/qiling/debugger/gdb/gdb.py +++ b/qiling/debugger/gdb/gdb.py @@ -3,47 +3,69 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -# gdbserver --remote-debug 0.0.0.0:9999 /path/to binary -# documentation: according to https://sourceware.org/gdb/current/onlinedocs/gdb/Remote-Protocol.html#Remote-Protocol +# for watching actual protocol messages: +# server: gdbserver --remote-debug 127.0.0.1:9999 /path/to/exec +# client: gdb -q -ex "target remote 127.0.0.1:9999" +# +# also, run this command on the gdb client: +# (gdb) set debug remote 1 +# +# gdb remote protocol: +# https://sourceware.org/gdb/current/onlinedocs/gdb/Remote-Protocol.html + +import os, socket, re +from logging import Logger +from typing import Iterator, Optional, Union -import struct, os, socket -from binascii import unhexlify -from typing import Iterator, Literal +from unicorn import UcError +from unicorn.unicorn_const import ( + UC_ERR_READ_UNMAPPED, UC_ERR_WRITE_UNMAPPED, UC_ERR_FETCH_UNMAPPED, + UC_ERR_READ_PROT, UC_ERR_WRITE_PROT, UC_ERR_FETCH_PROT, + UC_ERR_READ_UNALIGNED, UC_ERR_WRITE_UNALIGNED, UC_ERR_FETCH_UNALIGNED, + UC_ERR_INSN_INVALID +) from qiling import Qiling -from qiling.const import * -from qiling.utils import * +from qiling.const import QL_ARCH, QL_ENDIAN, QL_OS from qiling.debugger import QlDebugger -from qiling.arch.x86_const import reg_map_16 as x86_reg_map_16 -from qiling.arch.x86_const import reg_map_32 as x86_reg_map_32 -from qiling.arch.x86_const import reg_map_64 as x86_reg_map_64 -from qiling.arch.x86_const import reg_map_misc as x86_reg_map_misc -from qiling.arch.x86_const import reg_map_st as x86_reg_map_st -from qiling.arch.arm_const import reg_map as arm_reg_map -from qiling.arch.arm64_const import reg_map as arm64_reg_map -from qiling.arch.mips_const import reg_map as mips_reg_map -from qiling.loader.elf import AUX +from qiling.debugger.gdb import xmlregs from .utils import QlGdbUtils -GDB_SIGNAL_INT = 2 -GDB_SIGNAL_SEGV = 11 -GDB_SIGNAL_GILL = 4 -GDB_SIGNAL_STOP = 17 -GDB_SIGNAL_TRAP = 5 -GDB_SIGNAL_BUS = 10 +# gdb logging prompt +PROMPT = r'gdb>' + +# default string encoding +ENCODING = 'latin' + +# define a few handy linux signals +SIGINT = 2 +SIGILL = 4 +SIGTRAP = 5 +SIGABRT = 6 +SIGBUS = 7 +SIGKILL = 9 +SIGSEGV = 11 +SIGALRM = 14 +SIGTERM = 15 +SIGCHLD = 16 +SIGCONT = 17 +SIGSTOP = 18 + +# common replies +REPLY_ACK = b'+' +REPLY_EMPTY = b'' +REPLY_OK = b'OK' + +# reply type +Reply = Union[bytes, str] + +class QlGdb(QlDebugger): + """A simple gdbserver implementation. + """ - -class QlGdb(QlDebugger, object): - """docstring for Debugsession""" def __init__(self, ql: Qiling, ip: str = '127.0.01', port: int = 9999): - super(QlGdb, self).__init__(ql) - - self.ql = ql - self.last_pkt = None - self.exe_abspath = os.path.abspath(self.ql.argv[0]) - self.rootfs_abspath = os.path.abspath(self.ql.rootfs) - self.gdb = QlGdbUtils() + super().__init__(ql) if type(port) is str: port = int(port, 0) @@ -51,763 +73,866 @@ def __init__(self, ql: Qiling, ip: str = '127.0.01', port: int = 9999): self.ip = ip self.port = port - if self.ql.baremetal: - load_address = self.ql.loader.load_address + if ql.baremetal: + load_address = ql.loader.load_address exit_point = load_address + os.path.getsize(ql.path) - elif self.ql.code: - load_address = self.ql.os.entry_point + elif ql.code: + load_address = ql.os.entry_point exit_point = load_address + len(ql.code) else: load_address = ql.loader.load_address exit_point = load_address + os.path.getsize(ql.path) - if self.ql.baremetal: - self.entry_point = self.ql.loader.entry_point - elif self.ql.os.type in (QL_OS.LINUX, QL_OS.FREEBSD) and not self.ql.code: - self.entry_point = self.ql.os.elf_entry + if ql.baremetal: + 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 else: - self.entry_point = self.ql.os.entry_point + entry_point = ql.os.entry_point # Only part of the binary file will be debugged. - if self.ql.entry_point is not None and self.ql.exit_point is not None: - self.entry_point = self.ql.entry_point - exit_point = self.ql.exit_point - - self.gdb.initialize(self.ql, self.entry_point, exit_point=exit_point, mappings=[(hex(load_address))]) - - #Setup register tables, order of tables is important - self.tables = { - QL_ARCH.A8086 : list({**x86_reg_map_16, **x86_reg_map_misc}.keys()), - QL_ARCH.X86 : list({**x86_reg_map_32, **x86_reg_map_misc, **x86_reg_map_st}.keys()), - QL_ARCH.X8664 : list({**x86_reg_map_64, **x86_reg_map_misc, **x86_reg_map_st}.keys()), - QL_ARCH.ARM : list({**arm_reg_map}.keys()), - QL_ARCH.CORTEX_M : list({**arm_reg_map}.keys()), - QL_ARCH.ARM64 : list({**arm64_reg_map}.keys()), - QL_ARCH.MIPS : list({**mips_reg_map}.keys()), - } + if ql.entry_point is not None: + entry_point = ql.entry_point - def addr_to_str(self, addr: int, short: bool = False, endian: Literal['little', 'big'] = 'big') -> str: - # a hacky way to divide archbits by 2 if short, and leave it unchanged if not - nbits = self.ql.arch.bits // (int(short) + 1) + if ql.exit_point is not None: + exit_point = ql.exit_point - if nbits == 64: - s = f'{int.from_bytes(self.ql.pack64(addr), byteorder=endian):016x}' + self.gdb = QlGdbUtils(ql, entry_point, exit_point) - elif nbits == 32: - s = f'{int.from_bytes(self.ql.pack32(addr), byteorder=endian):08x}' + self.regsmap = xmlregs.load_regsmap(self.ql.arch.type) - elif nbits == 16: - s = f'{int.from_bytes(self.ql.pack16(addr), byteorder=endian):04x}' + def run(self): + server = GdbSerialConn(self.ip, self.port, self.ql.log) + killed = False - else: - raise RuntimeError + def __hexstr(value: int, nibbles: int = 0) -> str: + length = (nibbles or self.ql.arch.bits // 4) // 2 + byteorder = 'little' if self.ql.arch.endian == QL_ENDIAN.EL else 'big' - return s + return value.to_bytes(length, byteorder).hex() - def bin_to_escstr(self, rawbin): - rawbin_escape = "" + def __get_reg_value(reg: Optional[int], pos: int, nibbles: int) -> str: + # reg is either None or uc reg invalid + if reg: + value = self.ql.arch.regs.read(reg) + assert type(value) is int - def incomplete_hex_check(hexchar): - if len(hexchar) == 1: - hexchar = "0" + hexchar - return hexchar + hexstr = __hexstr(value, nibbles) + else: + hexstr = 'x' * nibbles - for a in rawbin: + return hexstr - # The binary data representation uses 7d (ASCII ‘}’) as an escape character. - # Any escaped byte is transmitted as the escape character followed by the original character XORed with 0x20. - # For example, the byte 0x7d would be transmitted as the two bytes 0x7d 0x5d. The bytes 0x23 (ASCII ‘#’), 0x24 (ASCII ‘$’), and 0x7d (ASCII ‘}’) - # must always be escaped. Responses sent by the stub must also escape 0x2a (ASCII ‘*’), - # so that it is not interpreted as the start of a run-length encoded sequence (described next). + def __set_reg_value(reg: Optional[int], pos: int, nibbles: int, hexval: str) -> None: + # reg is neither None nor uc reg invalid + if reg: + assert len(hexval) == nibbles - if a in (42,35,36, 125): - a = a ^ 0x20 - a = (str(hex(a)[2:])) - a = incomplete_hex_check(a) - a = str("7d%s" % a) - else: - a = (str(hex(a)[2:])) - a = incomplete_hex_check(a) + val = int(hexval, 16) - rawbin_escape += a + if self.ql.arch.endian == QL_ENDIAN.EL: + val = __swap_endianess(val) - return unhexlify(rawbin_escape) + self.ql.arch.regs.write(reg, val) - def setup_server(self): - self.ql.log.info("gdb> Listening on %s:%u" % (self.ip, self.port)) + def __swap_endianess(value: int) -> int: + length = (value.bit_length() + 7) // 8 + raw = value.to_bytes(length, 'little') - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - sock.bind((self.ip, self.port)) - sock.listen(1) - clientsocket, addr = sock.accept() + return int.from_bytes(raw, 'big') - self.sock = sock - self.clientsocket = clientsocket - self.netin = clientsocket.makefile('r') - self.netout = clientsocket.makefile('w') - def close(self): - self.netin.close() - self.netout.close() - self.clientsocket.close() - self.sock.close() + def handle_exclaim(subcmd: str) -> Reply: + return REPLY_OK - def run(self): - self.setup_server() - - while self.receive() == 'Good': - pkt = self.last_pkt - self.send_raw('+') - - def handle_qmark(subcmd): - def gdbqmark_converter(arch): - """ - MIPS32_EL : gdbserver response ("$T051d:00e7ff7f;25:40ccfc77;#65") - MIPS32_EB : gdbserver response ("$T051d:7fff6dc0;25:77fc4880;thread:28fa;core:0;"); - ARM64: gdbserver response "$T051d:0*,;1f:80f6f*"ff0* ;20:c02cfdb7f* 0* ;thread:p1f9.1f9;core:0;#56"); - ARM: gdbserver $T050b:0*"00;0d:e0f6ffbe;0f:8079fdb6;#ae" - """ - adapter = { - QL_ARCH.A8086 : [ 0x05, 0x04, 0x08 ], - QL_ARCH.X86 : [ 0x05, 0x04, 0x08 ], - QL_ARCH.X8664 : [ 0x06, 0x07, 0x10 ], - QL_ARCH.MIPS : [ 0x1d, 0x00, 0x25 ], - QL_ARCH.ARM : [ 0x0b, 0x0d, 0x0f ], - QL_ARCH.CORTEX_M : [ 0x0b, 0x0d, 0x0f ], - QL_ARCH.ARM64 : [ 0x1d, 0xf1, 0x20 ] - } - return adapter.get(arch) - - idhex, spid, pcid = gdbqmark_converter(self.ql.arch.type) - sp = self.addr_to_str(self.ql.arch.regs.arch_sp) - pc = self.addr_to_str(self.ql.arch.regs.arch_pc) - nullfill = "0" * int(self.ql.arch.bits / 4) - - if self.ql.arch.type == QL_ARCH.MIPS: - self.send('T%.2x%.2x:%s;%.2x:%s;' %(GDB_SIGNAL_TRAP, idhex, sp, pcid, pc)) - else: - self.send('T%.2x%.2x:%s;%.2x:%s;%.2x:%s;' %(GDB_SIGNAL_TRAP, idhex, nullfill, spid, sp, pcid, pc)) - - - def handle_c(subcmd): - self.gdb.resume_emu(self.ql.arch.regs.arch_pc) - - if self.gdb.bp_list == [self.entry_point]: - self.send("W00") + + def handle_qmark(subcmd: str) -> Reply: + # MIPS32_EL : $T051d:00e7ff7f;25:40ccfc77;#65 + # MIPS32_EB : $T051d:7fff6dc0;25:77fc4880;thread:28fa;core:0; + # ARM64 : $T051d:0*,;1f:80f6f*"ff0* ;20:c02cfdb7f* 0* ;thread:p1f9.1f9;core:0;#56 + # ARM : $T050b:0*"00;0d:e0f6ffbe;0f:8079fdb6;#ae + + response = { + QL_ARCH.X86 : ( 0x05, 0x04, 0x08 ), + QL_ARCH.X8664 : ( 0x06, 0x07, 0x10 ), + QL_ARCH.ARM : ( 0x0b, 0x0d, 0x0f ), + QL_ARCH.ARM64 : ( 0x1d, 0xf1, 0x20 ), + QL_ARCH.MIPS : ( 0x1d, 0x00, 0x25 ), + QL_ARCH.A8086 : ( 0x05, 0x04, 0x08 ), + QL_ARCH.CORTEX_M : ( 0x0b, 0x0d, 0x0f ) + } + + idhex, spid, pcid = response[self.ql.arch.type] + sp = __hexstr(self.ql.arch.regs.arch_sp) + pc = __hexstr(self.ql.arch.regs.arch_pc) + zfill = __hexstr(0) + + info = '' if self.ql.arch.type == QL_ARCH.MIPS else f':{zfill};{spid:02x}' + return f'T{SIGTRAP:02x}{idhex:02x}{info}:{sp};{pcid:02x}:{pc};' + + + def handle_c(subcmd: str) -> Reply: + try: + self.gdb.resume_emu() + except UcError as err: + sigmap = { + UC_ERR_READ_UNMAPPED : SIGSEGV, + UC_ERR_WRITE_UNMAPPED : SIGSEGV, + UC_ERR_FETCH_UNMAPPED : SIGSEGV, + UC_ERR_WRITE_PROT : SIGSEGV, + UC_ERR_READ_PROT : SIGSEGV, + UC_ERR_FETCH_PROT : SIGSEGV, + UC_ERR_READ_UNALIGNED : SIGBUS, + UC_ERR_WRITE_UNALIGNED : SIGBUS, + UC_ERR_FETCH_UNALIGNED : SIGBUS, + UC_ERR_INSN_INVALID : SIGILL + } + + # determine signal from uc error; default to SIGTERM + reply = f'S{sigmap.get(err.errno, SIGTERM):02x}' + + except KeyboardInterrupt: + # emulation was interrupted with ctrl+c + reply = f'S{SIGINT:02x}' + + else: + if self.ql.arch.regs.arch_pc == self.gdb.last_bp: + # emulation stopped because it hit a breakpoint + reply = f'S{SIGTRAP:02x}' else: - self.send(('S%.2x' % GDB_SIGNAL_TRAP)) - - - handle_C = handle_c - - - def handle_g(subcmd): - s = '' - - if self.ql.arch.type == QL_ARCH.A8086: - for reg in self.tables[QL_ARCH.A8086][:16]: - r = self.ql.arch.regs.read(reg) - tmp = self.addr_to_str(r) - s += tmp - - elif self.ql.arch.type == QL_ARCH.X86: - for reg in self.tables[QL_ARCH.X86][:16]: - r = self.ql.arch.regs.read(reg) - tmp = self.addr_to_str(r) - s += tmp - - elif self.ql.arch.type == QL_ARCH.X8664: - for reg in self.tables[QL_ARCH.X8664][:24]: - r = self.ql.arch.regs.read(reg) - if self.ql.arch.reg_bits(reg) == 64: - tmp = self.addr_to_str(r) - elif self.ql.arch.reg_bits(reg) == 32: - tmp = self.addr_to_str(r, short = True) - s += tmp - - elif self.ql.arch.type == QL_ARCH.ARM: - - - # r0-r12,sp,lr,pc,cpsr ,see https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=gdb/arch/arm.h;h=fa589fd0582c0add627a068e6f4947a909c45e86;hb=HEAD#l127 - for reg in self.tables[QL_ARCH.ARM][:16] + [self.tables[QL_ARCH.ARM][25]]: - # if reg is pc, make sure to take thumb mode into account - r = self.ql.arch.effective_pc if reg == "pc" else self.ql.arch.regs.read(reg) - - tmp = self.addr_to_str(r) - s += tmp - - elif self.ql.arch.type == QL_ARCH.ARM64: - for reg in self.tables[QL_ARCH.ARM64][:33]: - r = self.ql.arch.regs.read(reg) - tmp = self.addr_to_str(r) - s += tmp - - elif self.ql.arch.type == QL_ARCH.MIPS: - for reg in self.tables[QL_ARCH.MIPS][:38]: - r = self.ql.arch.regs.read(reg) - tmp = self.addr_to_str(r) - s += tmp - - self.send(s) - - - def handle_G(subcmd): - count = 0 - - if self.ql.arch.type == QL_ARCH.A8086: - for i in range(0, len(subcmd), 8): - reg_data = subcmd[i:i+7] - reg_data = int(reg_data, 16) - self.ql.arch.regs.write(self.tables[QL_ARCH.A8086][count], reg_data) - count += 1 - - elif self.ql.arch.type == QL_ARCH.X86: - for i in range(0, len(subcmd), 8): - reg_data = subcmd[i:i+7] - reg_data = int(reg_data, 16) - self.ql.arch.regs.write(self.tables[QL_ARCH.X86][count], reg_data) - count += 1 - - - elif self.ql.arch.type == QL_ARCH.X8664: - for i in range(0, 17*16, 16): - reg_data = subcmd[i:i+15] - reg_data = int(reg_data, 16) - self.ql.arch.regs.write(self.tables[QL_ARCH.X8664][count], reg_data) - count += 1 - for j in range(17*16, 17*16+15*8, 8): - reg_data = subcmd[j:j+7] - reg_data = int(reg_data, 16) - self.ql.arch.regs.write(self.tables[QL_ARCH.X8664][count], reg_data) - count += 1 - - elif self.ql.arch.type == QL_ARCH.ARM: - for i in range(0, len(subcmd), 8): - reg_data = subcmd[i:i + 7] - reg_data = int(reg_data, 16) - self.ql.arch.regs.write(self.tables[QL_ARCH.ARM][count], reg_data) - count += 1 - - elif self.ql.arch.type == QL_ARCH.ARM64: - for i in range(0, len(subcmd), 16): - reg_data = subcmd[i:i+15] - reg_data = int(reg_data, 16) - self.ql.arch.regs.write(self.tables[QL_ARCH.ARM64][count], reg_data) - count += 1 - - elif self.ql.arch.type == QL_ARCH.MIPS: - for i in range(0, len(subcmd), 8): - reg_data = subcmd[i:i+7] - reg_data = int(reg_data, 16) - self.ql.arch.regs.write(self.tables[QL_ARCH.MIPS][count], reg_data) - count += 1 - - self.send('OK') - - - def handle_H(subcmd): - if subcmd.startswith('g'): - self.send('OK') - if subcmd.startswith('c'): - self.send('OK') - - - def handle_m(subcmd): - addr, size = subcmd.split(',') - addr = int(addr, 16) - size = int(size, 16) + # emulation has completed successfully + reply = f'W{self.ql.os.exit_code:02x}' - try: - tmp = '' - for s in range(size): - mem = self.ql.mem.read(addr + s, 1) - mem = "".join( - [str("{:02x}".format(ord(c))) for c in mem.decode('latin1')]) - tmp += mem - self.send(tmp) - - except: - self.send('E14') - - - def handle_M(subcmd): - addr, data = subcmd.split(',') - size, data = data.split(':') - addr = int(addr, 16) - data = bytes.fromhex(data) - try: - self.ql.mem.write(addr, data) - self.send('OK') - except: - self.send('E01') + return reply - def handle_p(subcmd): - reg_index = int(subcmd, 16) - reg_value = None - try: - if self.ql.arch.type == QL_ARCH.A8086: - if reg_index <= 9: - reg_value = self.ql.arch.regs.read(self.tables[QL_ARCH.A8086][reg_index-1]) - else: - reg_value = 0 - reg_value = self.addr_to_str(reg_value) + def handle_g(subcmd: str) -> Reply: + # TODO: several obsolete regs cause arm to have a gap just before cpsr. the nonexistant regs + # are represented as None entries just to make sure cpsr stays at index 25. however, it is + # not clear whether its value should follow a series of 'x' (for the non-existant regs) or + # not. the original code suggests not (perhaps because fpa is not specified as an xml feature..?) + # + # non-existant regs are f0-f7 (96 bits each) and fps (32 bits). + # see: ./xml/arm/arm-fpa.xml + # see: https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=gdb/arch/arm.h;h=fa589fd0582c0add627a068e6f4947a909c45e86;hb=HEAD#l127 - elif self.ql.arch.type == QL_ARCH.X86: - if reg_index <= 24: - reg_value = self.ql.arch.regs.read(self.tables[QL_ARCH.X86][reg_index-1]) - else: - reg_value = 0 - reg_value = self.addr_to_str(reg_value) - - elif self.ql.arch.type == QL_ARCH.X8664: - if reg_index <= 32: - reg_value = self.ql.arch.regs.read(self.tables[QL_ARCH.X8664][reg_index-1]) - else: - reg_value = 0 - if reg_index <= 17: - reg_value = self.addr_to_str(reg_value) - elif 17 < reg_index: - reg_value = self.addr_to_str(reg_value, short = True) - - elif self.ql.arch.type == QL_ARCH.ARM: - if reg_index < 26: - reg_value = self.ql.arch.regs.read(self.tables[QL_ARCH.ARM][reg_index - 1]) - else: - reg_value = 0 - reg_value = self.addr_to_str(reg_value) + data = ''.join(__get_reg_value(*entry) for entry in self.regsmap) - elif self.ql.arch.type == QL_ARCH.ARM64: - if reg_index <= 32: - reg_value = self.ql.arch.regs.read(self.tables[QL_ARCH.ARM64][reg_index - 1]) - else: - reg_value = 0 - reg_value = self.addr_to_str(reg_value) + return data - elif self.ql.arch.type == QL_ARCH.MIPS: - if reg_index <= 37: - reg_value = self.ql.arch.regs.read(self.tables[QL_ARCH.MIPS][reg_index - 1]) - else: - reg_value = 0 - reg_value = self.addr_to_str(reg_value) - - if type(reg_value) is not str: - reg_value = self.addr_to_str(reg_value) - - self.send(reg_value) - except: - self.close() - raise - - - def handle_P(subcmd): - reg_index, reg_data = subcmd.split('=') - reg_index = int(reg_index, 16) - reg_name = self.tables[self.ql.arch.type][reg_index] - - if self.ql.arch.type == QL_ARCH.A8086: - reg_data = int(reg_data, 16) - reg_data = int.from_bytes(struct.pack(' Reply: + data = subcmd + + for reg, pos, nibbles in self.regsmap: + if reg: + hexval = data[pos : pos + nibbles] + + if hexval != 'x' * nibbles: + val = int(hexval, 16) + + # TODO: should we swap val's endianess for big-endian targets? + self.ql.arch.regs.write(reg, val) + + return REPLY_OK + + + def handle_H(subcmd: str) -> Reply: + op = subcmd[0] + + if op in ('c', 'g'): + return REPLY_OK + + return REPLY_EMPTY + + + def handle_k(subcmd: str) -> Reply: + global killed + + killed = True + return REPLY_OK - self.ql.log.info("gdb> Write to register %s with %x\n" % (self.tables[self.ql.arch.type][reg_index], reg_data)) - self.send('OK') + def handle_m(subcmd: str) -> Reply: + """Read target memory. + """ - def handle_Q(subcmd): - if subcmd.startswith( 'StartNoAckMode'): - self.send('OK') + addr, size = (int(p, 16) for p in subcmd.split(',')) - elif subcmd.startswith( 'DisableRandomization'): - self.send('OK') + try: + data = self.ql.mem.read(addr, size).hex() + except UcError: + return 'E14' + else: + return data + + + def handle_M(subcmd: str) -> Reply: + """Write target memory. + """ - elif subcmd.startswith( 'ProgramSignals'): - self.send('OK') + addr, data = subcmd.split(',') + size, data = data.split(':') - elif subcmd.startswith( 'NonStop'): - self.send('OK') + addr = int(addr, 16) + data = bytes.fromhex(data) - elif subcmd.startswith('PassSignals'): - self.send('OK') + assert len(data) == size - elif subcmd.startswith('qemu'): - self.send('') + try: + self.ql.mem.write(addr, data) + except UcError: + return 'E01' + else: + return REPLY_OK + + + def handle_p(subcmd: str) -> Reply: + """Read register value by index. + """ + + idx = int(subcmd, 16) + + return __get_reg_value(*self.regsmap[idx]) + + + def handle_P(subcmd: str) -> Reply: + """Write register value by index. + """ + + idx, data = subcmd.split('=') + idx = int(idx, 16) + + if idx < len(self.regsmap): + __set_reg_value(*self.regsmap[idx], hexval=data) + + return REPLY_OK + + return 'E00' + + + def handle_Q(subcmd: str) -> Reply: + """General queries. + + @see: https://sourceware.org/gdb/onlinedocs/gdb/General-Query-Packets.html + """ + + feature, *data = subcmd.split(':', maxsplit=1) + + supported = ( + 'DisableRandomization', + 'NonStop', + 'PassSignals', + 'ProgramSignals', + 'StartNoAckMode' + ) + + if feature == 'StartNoAckMode': + server.ack_mode = False + + return REPLY_OK if feature in supported else REPLY_EMPTY + + + def handle_D(subcmd: str) -> Reply: + """Detach. + """ + + return REPLY_OK + + + def handle_q(subcmd: str) -> Reply: + query, *data = subcmd.split(':') + + # qSupported command + # + # @see: https://sourceware.org/gdb/onlinedocs/gdb/General-Query-Packets.html#qSupported + + if query == 'Supported': + # list of supported features excluding the multithreading-related ones + common = ( + 'BreakpointCommands+', + 'ConditionalBreakpoints+', + 'ConditionalTracepoints+', + 'DisconnectedTracing+', + 'EnableDisableTracepoints+', + 'InstallInTrace+', + 'QAgent+', + 'QCatchSyscalls+', + 'QDisableRandomization+', + 'QEnvironmentHexEncoded+', + 'QEnvironmentReset+', + 'QEnvironmentUnset+', + 'QNonStop+', + 'QPassSignals+', + 'QProgramSignals+', + 'QSetWorkingDir+', + 'QStartNoAckMode+', + 'QStartupWithShell+', + 'QTBuffer:size+', + 'StaticTracepoints+', + 'TraceStateVariables+', + 'TracepointSource+', + # 'augmented-libraries-svr4-read+', + 'exec-events+', + 'fork-events+', + 'hwbreak+', + 'multiprocess+', + 'no-resumed+', + 'qXfer:auxv:read+', + 'qXfer:exec-file:read+', + 'qXfer:features:read+', + # 'qXfer:libraries-svr4:read+', + # 'qXfer:osdata:read+', + 'qXfer:siginfo:read+', + 'qXfer:siginfo:write+', + 'qXfer:statictrace:read+', + 'qXfer:threads:read+', + 'qXfer:traceframe-info:read+', + 'swbreak+', + 'tracenz+', + 'vfork-events+' + ) + + # might or might not need for multi thread + if self.ql.multithread: + features = ( + 'PacketSize=47ff', + 'FastTracepoints+', + 'QThreadEvents+', + 'Qbtrace-conf:bts:size+', + 'Qbtrace-conf:pt:size+', + 'Qbtrace:bts+', + 'Qbtrace:off+', + 'Qbtrace:pt+', + 'qXfer:btrace-conf:read+', + 'qXfer:btrace:read+', + 'vContSupported+' + ) + + else: + features = ( + 'PacketSize=3fff', + 'qXfer:spu:read+', + 'qXfer:spu:write+' + ) - def handle_D(subcmd): - self.send('OK') + return ';'.join(common + features) - def handle_q(subcmd): - if subcmd.startswith('Supported:'): - # might or might not need for multi thread - if self.ql.multithread == False: - self.send("PacketSize=3fff;QPassSignals+;QProgramSignals+;QStartupWithShell+;QEnvironmentHexEncoded+;QEnvironmentReset+;QEnvironmentUnset+;QSetWorkingDir+;QCatchSyscalls+;qXfer:libraries-svr4:read+;augmented-libraries-svr4-read+;qXfer:auxv:read+;qXfer:spu:read+;qXfer:spu:write+;qXfer:siginfo:read+;qXfer:siginfo:write+;qXfer:features:read+;QStartNoAckMode+;qXfer:osdata:read+;multiprocess+;fork-events+;vfork-events+;exec-events+;QNonStop+;QDisableRandomization+;qXfer:threads:read+;ConditionalTracepoints+;TraceStateVariables+;TracepointSource+;DisconnectedTracing+;StaticTracepoints+;InstallInTrace+;qXfer:statictrace:read+;qXfer:traceframe-info:read+;EnableDisableTracepoints+;QTBuffer:size+;tracenz+;ConditionalBreakpoints+;BreakpointCommands+;QAgent+;swbreak+;hwbreak+;qXfer:exec-file:read+;no-resumed+") - else: - self.send("PacketSize=47ff;QPassSignals+;QProgramSignals+;QStartupWithShell+;QEnvironmentHexEncoded+;QEnvironmentReset+;QEnvironmentUnset+;QSetWorkingDir+;QCatchSyscalls+;qXfer:libraries-svr4:read+;augmented-libraries-svr4-read+;qXfer:auxv:read+;qXfer:siginfo:read+;qXfer:siginfo:write+;qXfer:features:read+;QStartNoAckMode+;qXfer:osdata:read+;multiprocess+;fork-events+;vfork-events+;exec-events+;QNonStop+;QDisableRandomization+;qXfer:threads:read+;ConditionalTracepoints+;TraceStateVariables+;TracepointSource+;DisconnectedTracing+;FastTracepoints+;StaticTracepoints+;InstallInTrace+;qXfer:statictrace:read+;qXfer:traceframe-info:read+;EnableDisableTracepoints+;QTBuffer:size+;tracenz+;ConditionalBreakpoints+;BreakpointCommands+;QAgent+;Qbtrace:bts+;Qbtrace-conf:bts:size+;Qbtrace:pt+;Qbtrace-conf:pt:size+;Qbtrace:off+;qXfer:btrace:read+;qXfer:btrace-conf:read+;swbreak+;hwbreak+;qXfer:exec-file:read+;vContSupported+;QThreadEvents+;no-resumed+") - elif subcmd.startswith('Xfer:features:read'): - xfercmd_file = subcmd.split(':')[3] + elif query == 'Xfer': + feature, op, annex, params = data + offset, length = (int(p, 16) for p in params.split(',')) + + if feature == 'features' and op == 'read': xfercmd_abspath = os.path.dirname(os.path.abspath(__file__)) xml_folder = self.ql.arch.type.name.lower() - xfercmd_file = os.path.join(xfercmd_abspath,"xml",xml_folder, xfercmd_file) + xfercmd_file = os.path.join(xfercmd_abspath, 'xml', xml_folder, annex) + + if self.ql.os.type == QL_OS.WINDOWS: + self.ql.log.info(f'{PROMPT} Qiling does not support XML for this platform yet') + content = '' + + elif not os.path.exists(xfercmd_file): + self.ql.log.info(f'{PROMPT} XML file not found: "{xfercmd_file}"') + content = '' - if os.path.exists(xfercmd_file) and self.ql.os.type is not QL_OS.WINDOWS: - with open(xfercmd_file, 'r') as f: - file_contents = f.read() - self.send("l%s" % file_contents) else: - self.ql.log.info("gdb> Platform is not supported by xml or xml file not found: %s\n" % (xfercmd_file)) - self.send("l") - - - elif subcmd.startswith('Xfer:threads:read::0,'): - if self.ql.os.type in QL_OS_NONPID or self.ql.baremetal: - self.send("l") - else: - file_contents = ("\r\n\r\n") - self.send("l" + file_contents) - - elif subcmd.startswith('Xfer:auxv:read::'): - if self.ql.code: - return - - if self.ql.os.type in (QL_OS.LINUX, QL_OS.FREEBSD): - def __read_auxv() -> Iterator[int]: - auxv_entries = ( - AUX.AT_HWCAP, - AUX.AT_PAGESZ, - AUX.AT_CLKTCK, - AUX.AT_PHDR, - AUX.AT_PHENT, - AUX.AT_PHNUM, - AUX.AT_BASE, - AUX.AT_FLAGS, - AUX.AT_ENTRY, - AUX.AT_UID, - AUX.AT_EUID, - AUX.AT_GID, - AUX.AT_EGID, - AUX.AT_SECURE, - AUX.AT_RANDOM, - AUX.AT_HWCAP2, - AUX.AT_EXECFN, - AUX.AT_PLATFORM, - AUX.AT_NULL - ) - - for e in auxv_entries: - yield e.value - yield self.ql.loader.aux_vec[e] - - annex = self.addr_to_str(0)[:-2] - sysinfo_ehdr = self.addr_to_str(0) - - auxvdata_c = unhexlify(''.join([annex, sysinfo_ehdr] + [self.addr_to_str(val) for val in __read_auxv()])) - auxvdata = self.bin_to_escstr(auxvdata_c) + with open(xfercmd_file, 'r') as f: + f.seek(offset, os.SEEK_SET) + content = f.read(length) + + return f'l{content}' + + elif feature == 'threads' and op == 'read': + if not self.ql.baremetal and hasattr(self.ql.os, 'pid'): + content = '\r\n'.join(( + '', + f'', + '' + )) + else: - auxvdata = b"" + content = '' - self.send(b'l!%s' % auxvdata) + return f'l{content}' - elif subcmd.startswith('Xfer:exec-file:read:'): - self.send("l%s" % str(self.exe_abspath)) + elif feature == 'auxv' and op == 'read': + auxv_data = bytearray() + if hasattr(self.ql.loader, 'auxv'): + nbytes = self.ql.arch.bits // 8 - elif subcmd.startswith('Xfer:libraries-svr4:read:'): - if self.ql.os.type in (QL_OS.LINUX, QL_OS.FREEBSD): - xml_addr_mapping=("") - """ - FIXME: need to find out when do we need this - """ - #for s, e, info in self.ql.map_info: - # addr_mapping += ("" %(info, e, s)) - xml_addr_mapping += ("") - self.send("l%s" % xml_addr_mapping) - else: - self.send("l") + auxv_addr = self.ql.loader.auxv + offset + null_entry = bytes(nbytes * 2) - elif subcmd.startswith("Xfer:btrace-conf:read:"): - self.send("E.Btrace not enabled.") + # keep reading until AUXV.AT_NULL is reached + while not auxv_data.endswith(null_entry): + auxv_data.extend(self.ql.mem.read(auxv_addr, nbytes)) + auxv_addr += nbytes - elif subcmd == "Attached": - self.send("") + auxv_data.extend(self.ql.mem.read(auxv_addr, nbytes)) + auxv_addr += nbytes - elif subcmd.startswith("C"): - self.send("") + return b'l' + auxv_data[:length] - elif subcmd.startswith("L:"): - self.send("M001") + elif feature == 'exec-file' and op == 'read': + return f'l{os.path.abspath(self.ql.path)}' - elif subcmd == "fThreadInfo": - self.send("m0") + elif feature == 'libraries-svr4' and op == 'read': + # TODO: this one requires information of loaded libraries which currently not provided + # by the ELF loader. until we gather that information, we cannot fulfill this request + # + # see: https://sourceware.org/gdb/current/onlinedocs/gdb/Library-List-Format-for-SVR4-Targets.html + return REPLY_EMPTY - elif subcmd == "sThreadInfo": - self.send("l") + # if self.ql.os.type in (QL_OS.LINUX, QL_OS.FREEBSD): + # tag = 'library-list-svr4' + # xml_lib_entries = (f'' for lbnd, ubnd, _, _, path in self.ql.mem.get_mapinfo() if path) + # + # xml = '\r\n'.join((f'<{tag} version="1.0">', *xml_lib_entries, f'')) + # + # return f'l{xml}' + # else: + # return f'' - elif subcmd == ("TStatus"): - self.send("T0;tnotrun:0;tframes:0;tcreated:0;tfree:50*!;tsize:50*!;circular:0;disconn:0;starttime:0;stoptime:0;username:;notes::") + elif feature == 'btrace-conf' and op == 'read': + return 'E.Btrace not enabled.' - elif subcmd == ("TfV"): - self.send("l") + elif query == 'Attached': + return REPLY_EMPTY - elif subcmd == ("TsV"): - self.send("l") + elif query == 'C': + return REPLY_EMPTY - elif subcmd == ("TfP"): - self.send("l") + elif query == 'L': + return 'M001' - elif subcmd == ("TsP"): - self.send("l") + elif query == 'fThreadInfo': + return 'm0' + elif query == 'sThreadInfo': + return 'l' - elif subcmd.startswith("Symbol"): - self.send("") + elif query == 'TStatus': + fields = ( + 'T0', + 'tnotrun:0', + 'tframes:0', + 'tcreated:0', + 'tfree:500000', + 'tsize:500000', + 'circular:0', + 'disconn:0', + 'starttime:0', + 'stoptime:0', + 'username:', + 'notes::' + ) - elif subcmd.startswith("Attached"): - self.send("") + return ';'.join(fields) - elif subcmd == "Offsets": - self.send("Text=0;Data=0;Bss=0") + elif query in ('TfV', 'TsV', 'TfP', 'TsP'): + return 'l' + elif query == 'Symbol': + return REPLY_OK - def handle_v(subcmd): + elif query == 'Offsets': + fields = ('Text=0', 'Data=0', 'Bss=0') - if subcmd == 'MustReplyEmpty': - self.send("") + return ';'.join(fields) - elif subcmd.startswith('File:open'): - if self.ql.os.type == QL_OS.UEFI or self.ql.baremetal: - self.send("F-1") - return + return REPLY_EMPTY - (file_path, flags, mode) = subcmd.split(':')[-1].split(',') - file_path = unhexlify(file_path).decode(encoding='UTF-8') - flags = int(flags, base=16) - mode = int(mode, base=16) - if file_path.startswith(self.rootfs_abspath): - file_abspath = file_path - else: - file_abspath = self.ql.os.path.transform_to_real_path(file_path) - - self.ql.log.debug("gdb> target file: %s" % (file_abspath)) - if os.path.exists(file_abspath) and not (file_path).startswith("/proc"): - fd = os.open(file_abspath, flags, mode) - self.send("F%x" % fd) - else: - self.send("F-1") - return - elif subcmd.startswith('File:pread:'): - (fd, count, offset) = subcmd.split(':')[-1].split(',') + def handle_v(subcmd: str) -> Reply: + if subcmd == 'MustReplyEmpty': + return REPLY_EMPTY + + elif subcmd.startswith('File'): + _, op, data = subcmd.split(':', maxsplit=2) + params = data.split(',') + + if op == 'open': + fd = -1 + + # files can be opened only where there is an os that supports filesystem + if not self.ql.interpreter and hasattr(self.ql.os, 'path'): + path, flags, mode = params + + path = bytes.fromhex(path).decode(encoding='utf-8') + flags = int(flags, 16) + mode = int(mode, 16) + + # try to guess whether this is an emulated path or real one + if path.startswith(os.path.abspath(self.ql.rootfs)): + host_path = path + else: + host_path = self.ql.os.path.virtual_to_host_path(path) + + self.ql.log.debug(f'{PROMPT} target file: {host_path}') + + if os.path.exists(host_path) and not path.startswith(r'/proc'): + fd = os.open(host_path, flags, mode) + + return f'F{fd}' - fd = int(fd, base=16) - offset = int(offset, base=16) - count = int(count, base=16) + elif op == 'pread': + fd, count, offset = (int(p, 16) for p in params) data = os.pread(fd, count, offset) - size = len(data) - data = self.bin_to_escstr(data) - if data: - self.send(("F%x;" % size).encode() + (data)) - else: - self.send("F0;") + return f'F{len(data):x};'.encode() + data + + elif op == 'close': + fd, *_ = params + fd = int(fd, 16) - elif subcmd.startswith('File:close'): - fd = subcmd.split(':')[-1] - fd = int(fd, base=16) os.close(fd) - self.send("F0") - - elif subcmd.startswith('Kill'): - self.send('OK') - - elif subcmd.startswith('Cont'): - self.ql.log.debug("gdb> Cont command received: %s" % subcmd) - if subcmd == 'Cont?': - self.send('vCont;c;C;t;s;S;r') - elif subcmd.startswith ("Cont;"): - subcmd = subcmd.split(';') - subcmd = subcmd[1].split(':') - if subcmd[0] in ('c', 'C05'): - handle_c(subcmd) - elif subcmd[0] in ('S', 's', 'S05'): - handle_s(subcmd) - else: - self.send("") + return 'F0' + return REPLY_EMPTY - def handle_s(subcmd): - current_address = self.gdb.current_address - if current_address is None: - entry_point = self.gdb.entry_point - if entry_point is not None: - self.gdb.soft_bp = True - self.gdb.resume_emu(entry_point) - else: - self.gdb.soft_bp = True - self.gdb.resume_emu() - self.send('S%.2x' % GDB_SIGNAL_TRAP) - - - def handle_X(subcmd): - self.send('') - - - def handle_Z(subcmd): - data = subcmd - ztype = data[data.find('Z') + 1:data.find(',')] - if ztype == '0': - ztype, address, value = data.split(',') - address = int(address, 16) - try: - self.gdb.bp_insert(address) - self.send('OK') - except: - self.send('E22') - else: - self.send('E22') + elif subcmd.startswith('Kill'): + return handle_k('') + + elif subcmd.startswith('Cont'): + # remove 'Cont' prefix + data = subcmd[len('Cont'):] + + if data == '?': + # note 't' and 'r' are currently not supported + return ';'.join(('vCont', 'c', 'C', 's', 'S')) + + elif data.startswith(';'): + groups = subcmd.split(';')[1:] + + for grp in groups: + cmd, tid = grp.split(':', maxsplit=1) + + if cmd in ('c', f'C{SIGTRAP:02x}'): + return handle_c('') + + elif cmd in ('s', f'S{SIGTRAP:02x}'): + return handle_s('') + + # FIXME: not sure how to handle multiple command + # groups, so handling just the first one + break + + return REPLY_EMPTY + + + def handle_s(subcmd: str) -> Reply: + """Perform a single step. + """ + + self.gdb.resume_emu(steps=1) + + return f'S{SIGTRAP:02x}' + + + def handle_X(subcmd: str) -> Reply: + """Write data to memory. + """ + params, data = subcmd.split(':', maxsplit=1) + addr, length = (int(p, 16) for p in params.split(',')) - def handle_z(subcmd): - data = subcmd.split(',') - if len(data) != 3: - self.send('E22') + if length != len(data): + return 'E00' + + try: + if data: + self.ql.mem.write(addr, data.encode(ENCODING)) + except UcError: + return 'E01' + else: + return REPLY_OK + + + def handle_Z(subcmd: str) -> Reply: + """Insert breakpoints or watchpoints. + """ + + params, *conds = subcmd.split(';') + type, addr, kind = (int(p, 16) for p in params.split(',')) + + # type values: + # 0 = sw breakpoint + # 1 = hw breakpoint + # 2 = write watchpoint + # 3 = read watchpoint + # 4 = access watchpoint + + if type == 0: + self.gdb.bp_insert(addr) + return REPLY_OK + + return REPLY_EMPTY + + + def handle_z(subcmd: str) -> Reply: + """Remove breakpoints or watchpoints. + """ + + type, addr, kind = (int(p, 16) for p in subcmd.split(',')) + + if type == 0: try: - type = data[0] - addr = int(data[1], 16) - length = data[2] - self.gdb.bp_remove(addr, type, length) - self.send('OK') - except: - self.send('E22') - - - def handle_exclaim(subcmd): - self.send('OK') - - commands = { - '!': handle_exclaim, - '?': handle_qmark, - 'c': handle_c, - 'C': handle_C, - 'D': handle_D, - 'g': handle_g, - 'G': handle_G, - 'H': handle_H, - 'm': handle_m, - 'M': handle_M, - 'p': handle_p, - 'P': handle_P, - 'q': handle_q, - 'Q': handle_Q, - 's': handle_s, - 'v': handle_v, - 'X': handle_X, - 'Z': handle_Z, - 'z': handle_z - } + self.gdb.bp_remove(addr) + except ValueError: + return 'E22' + else: + return REPLY_OK + + return REPLY_EMPTY + + + handlers = { + '!': handle_exclaim, + '?': handle_qmark, + 'c': handle_c, + 'C': handle_c, # this is intentional; not a typo + 'D': handle_D, + 'g': handle_g, + 'G': handle_G, + 'H': handle_H, + 'k': handle_k, + 'm': handle_m, + 'M': handle_M, + 'p': handle_p, + 'P': handle_P, + 'q': handle_q, + 'Q': handle_Q, + 's': handle_s, + 'v': handle_v, + 'X': handle_X, + 'Z': handle_Z, + 'z': handle_z + } + + # main server loop + for packet in server.readpackets(): + if server.ack_mode: + server.send(REPLY_ACK, raw=True) + + cmd, subcmd = packet[0], packet[1:] + handler = handlers.get(f'{cmd:c}') + + if handler: + reply = handler(subcmd.decode(ENCODING)) + server.send(reply) + + if killed: + break + else: + self.ql.log.info(f'{PROMPT} command not supported') + server.send(REPLY_EMPTY) + + server.close() + - cmd, subcmd = pkt[0], pkt[1:] - if cmd == 'k': +class GdbSerialConn: + """Serial connection handler. + """ + + # default recieve buffer size + BUFSIZE = 4096 + + def __init__(self, ipaddr: str, port: int, logger: Logger) -> None: + """Create a new gdb serial connection handler. + + Args: + ipaddr : ip address to bind the socket to + port : port number to listen on + logger : logger instance to use + """ + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.bind((ipaddr, port)) + sock.listen() + + self.log = logger + self.log.info(f'{PROMPT} listening on {ipaddr}:{port:d}') + + client, _ = sock.accept() + + self.sock = sock + self.client = client + + # ack mode should be turend on by default + self.ack_mode = True + + def close(self): + """Close the gdb serial connection handler and release its resources. + """ + + self.client.close() + self.sock.close() + + def readpackets(self) -> Iterator[bytes]: + """Iterate through incoming packets in an active connection until + it is terminated. + """ + + pattern = re.compile(br'^\$(?P[^#]*)#(?P[0-9a-fA-F]{2})') + buffer = bytearray() + + while True: + try: + incoming = self.client.recv(self.BUFSIZE) + except ConnectionError: break - if cmd not in commands: - self.send('') - self.ql.log.info("gdb> Command not supported: %s\n" %(cmd)) + # remote connection closed + if not incoming: + break + + buffer += incoming + + # discard incoming acks + if buffer[0:1] == REPLY_ACK: + del buffer[0] + + packet = pattern.match(buffer) + + # if there is no match, the rest of the packet might be missing + if not packet: continue - self.ql.log.debug("gdb> received: %s%s" % (cmd, subcmd)) - commands[cmd](subcmd) - - self.close() - - - def receive(self): - '''Receive a packet from a GDB client''' - csum = 0 - state = 'Finding SOP' - packet = '' - try: - while True: - c = self.netin.read(1) - if c == '\x03': - return 'Error: CTRL+C' - - if len(c) != 1: - return 'Error: EOF' - - if state == 'Finding SOP': - if c == '$': - state = 'Finding EOP' - elif state == 'Finding EOP': - if c == '#': - if csum != int(self.netin.read(2), 16): - raise Exception('invalid checksum') - self.last_pkt = packet - return 'Good' - else: - packet += c - csum = (csum + ord(c)) & 0xff - else: - raise Exception('should not be here') - except: - self.close() - raise - - def checksum(self, data): - checksum = 0 - for c in data: - if type(c) == str: - checksum += (ord(c)) - else: - checksum += c - return checksum & 0xff - def send(self, msg): - """Send a packet to the GDB client""" - if type(msg) == str: - self.send_raw('$%s#%.2x' % (msg, self.checksum(msg))) + data = packet['data'] + read_csum = int(packet['checksum'], 16) + calc_csum = GdbSerialConn.checksum(data) + + if read_csum != calc_csum: + raise IOError(f'checksum error: expected {calc_csum:02x} but got {read_csum:02x}') + + # follow gdbserver debug output format + self.log.debug(f'getpkt ("{GdbSerialConn.__printable_prefix(data).decode(ENCODING)}");') + + data = GdbSerialConn.rle_decode(data) + data = GdbSerialConn.unescape(data) + + del buffer[:packet.endpos] + yield data + + def send(self, data: Reply, raw: bool = False) -> None: + """Send out a packet. + + Args: + data : data to send out + raw : whether to encapsulate the data with standard header and + checksum or leave it raw + """ + + if type(data) is str: + data = data.encode(ENCODING) + + assert type(data) is bytes + + if raw: + packet = data else: - self.clientsocket.send(b'$%s#%.2x' % (msg, self.checksum(msg))) - self.netout.flush() + data = GdbSerialConn.escape(data) + data = GdbSerialConn.rle_encode(data) + + packet = b'$' + data + b'#' + f'{GdbSerialConn.checksum(data):02x}'.encode() + + # follow gdbserver debug output format + self.log.debug(f'putpkt ("{GdbSerialConn.__printable_prefix(data).decode(ENCODING)}");') + + self.client.sendall(packet) + + @staticmethod + def __printable_prefix(data: bytes) -> bytes: + """Follow the gnu gdbserver debug message format which emits only the + printable prefix of a packet (either incoming or outgoing). Note that + despite of its name, it includes non-printable characters as well. + + Args: + data : packet data to scan + + Returns: a prefix of the specified data buffer + """ + + def __isascii(ch: int) -> bool: + return 0 < ch < 0x80 + + if data.isascii(): + return data + + return data[:next((i for i, ch in enumerate(data) if not __isascii(ch)), len(data))] + + @staticmethod + def escape(data: bytes) -> bytes: + """Escape data according to gdb protocol escaping rules. + """ + + def __repl(m: 're.Match[bytes]') -> bytes: + ch, *_ = m[0] + + return bytes([ord('}'), ch ^ 0x20]) + + return re.sub(br'[*#$}]', __repl, data, flags=re.DOTALL) + + @staticmethod + def unescape(data: bytes) -> bytes: + """Unescape data according to gdb protocol escaping rules. + """ + + def __repl(m: 're.Match[bytes]') -> bytes: + _, ch = m[0] + + return bytes([ch ^ 0x20]) + + return re.sub(br'}.', __repl, data, flags=re.DOTALL) + + @staticmethod + def rle_encode(data: bytes) -> bytes: + """Compact data using run-length encoding. + """ + + def __simple_rep(b: bytes, times: int) -> bytes: + return b * times + + def __runlen_rep(b: bytes, times: int) -> bytes: + return b + b'*' + bytes([times - 1 + 29]) + + def __encode_rep(b: bytes, times: int) -> bytes: + assert times > 0, 'time should be a positive value' + + if 0 < times < 4: + return __simple_rep(b, times) + + elif times == 6+1 or times == 7+1: + return __runlen_rep(b, 6) + __encode_rep(b, times - 6) + + else: + return __runlen_rep(b, times) + + def __repl(m: 're.Match[bytes]') -> bytes: + repetition = m[0] + + ch = repetition[0:1] + times = len(repetition) + + return __encode_rep(ch, times) + + return re.sub(br'(.)\1{3,96}', __repl, data, flags=re.DOTALL) + + @staticmethod + def rle_decode(data: bytes) -> bytes: + """Expand run-length encoded data. + """ + + def __repl(m: 're.Match[bytes]') -> bytes: + ch, _, times = m[0] + + return bytes([ch] * times) - self.ql.log.debug("gdb> send: $%s#%.2x" % (msg, self.checksum(msg))) + return re.sub(br'.\*.', __repl, data, flags=re.DOTALL) - def send_raw(self, r): - self.netout.write(r) - self.netout.flush() + @staticmethod + def checksum(data: bytes) -> int: + return sum(data) & 0xff diff --git a/qiling/debugger/gdb/utils.py b/qiling/debugger/gdb/utils.py index 4c8a5247c..30bba47e8 100644 --- a/qiling/debugger/gdb/utils.py +++ b/qiling/debugger/gdb/utils.py @@ -3,117 +3,76 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # +from typing import Optional + from qiling import Qiling from qiling.const import QL_ARCH +# this code is partially based on uDbg +# @see: https://github.com/iGio90/uDdbg + PROMPT = r'gdb>' class QlGdbUtils: - def __init__(self): - self.ql: Qiling - - self.current_address = 0x0 - self.current_address_size = 0x0 - self.last_bp = 0x0 - self.entry_point = None - self.exit_point = None - self.soft_bp = False - self.has_soft_bp = False - self.bp_list = [] - # self.mapping = [] - self.breakpoint_count = 0x0 - self.skip_bp_count = 0x0 - - - def initialize(self, ql: Qiling, hook_address: int, exit_point: int, mappings=[]): + def __init__(self, ql: Qiling, entry_point: int, exit_point: int): self.ql = ql - if ql.baremetal: - self.current_address = self.entry_point - else: - self.current_address = self.entry_point = ql.os.entry_point - self.exit_point = exit_point - # self.mapping = mappings + self.bp_list = [] + self.last_bp = None def __entry_point_hook(ql: Qiling): ql.hook_del(ep_hret) ql.hook_code(self.dbg_hook) - ql.stop() - ql.log.info(f'{PROMPT} Stop at entry point: {ql.arch.regs.arch_pc:#x}') + ql.log.info(f'{PROMPT} stopped at entry point: {ql.arch.regs.arch_pc:#x}') + ql.stop() - ep_hret = ql.hook_address(__entry_point_hook, hook_address) + # set a one-time hook to be dispatched upon reaching program entry point. + # that hook will be used to set up the breakpoint handling hook + ep_hret = ql.hook_address(__entry_point_hook, entry_point) def dbg_hook(self, ql: Qiling, address: int, size: int): - """Modified this function for qiling.gdbserver by kabeor from https://github.com/iGio90/uDdbg - """ - - try: - if ql.arch.type == QL_ARCH.ARM: - if ql.arch.is_thumb: - address += 1 - - # self.mapping.append([(hex(address))]) - self.current_address = address - hit_soft_bp = False - - if self.soft_bp == True: - self.soft_bp = False - hit_soft_bp = True - - # Breakpoints are always added without the LSB, even in Thumb, so they should be checked like this as well - if ((address & ~1) in self.bp_list and (address & ~1) != self.last_bp) or self.has_soft_bp == True: - if self.skip_bp_count > 0: - self.skip_bp_count -= 1 - else: - self.breakpoint_count += 1 - ql.stop() - self.last_bp = address - ql.log.info(f'{PROMPT} Breakpoint found, stop at address: {address:#x}') - - elif address == self.last_bp: - self.last_bp = 0x0 - - self.has_soft_bp = hit_soft_bp - - if self.current_address + size == self.exit_point: - ql.log.debug(f'{PROMPT} emulation entrypoint at {self.entry_point:#x}') - ql.log.debug(f'{PROMPT} emulation exitpoint at {self.exit_point:#x}') - - except KeyboardInterrupt: - ql.log.info(f'{PROMPT} Paused at {address:#x}, instruction size = {size:d}') + if ql.arch.type == QL_ARCH.ARM and ql.arch.is_thumb: + address += 1 + + # resuming emulation after hitting a breakpoint will re-enter this hook. + # avoid an endless hooking loop by detecting and skipping this case + if address == self.last_bp: + self.last_bp = None + + elif address in self.bp_list: + self.last_bp = address + + ql.log.info(f'{PROMPT} breakpoint hit, stopped at {address:#x}') ql.stop() - except: - raise + # # TODO: not sure what this is about + # if address + size == self.exit_point: + # ql.log.debug(f'{PROMPT} emulation entrypoint at {self.entry_point:#x}') + # ql.log.debug(f'{PROMPT} emulation exitpoint at {self.exit_point:#x}') def bp_insert(self, addr: int): if addr not in self.bp_list: self.bp_list.append(addr) - self.ql.log.info(f'{PROMPT} Breakpoint added at: {addr:#x}') + self.ql.log.info(f'{PROMPT} breakpoint added at {addr:#x}') - def bp_remove(self, addr: int, type = None, len = None): + def bp_remove(self, addr: int): self.bp_list.remove(addr) - self.ql.log.info(f'{PROMPT} Breakpoint removed at: {addr:#x}') - + self.ql.log.info(f'{PROMPT} breakpoint removed from {addr:#x}') - def resume_emu(self, address: int = None, skip_bp: int = 0): - """Modified this function for qiling.gdbserver by kabeor from https://github.com/iGio90/uDdbg - """ - if address is not None: - if self.ql.arch.type == QL_ARCH.ARM: - if self.ql.arch.is_thumb: - address += 1 + def resume_emu(self, address: Optional[int] = None, steps: int = 0): + if address is None: + address = self.ql.arch.regs.arch_pc - self.current_address = address + if self.ql.arch.type == QL_ARCH.ARM and self.ql.arch.is_thumb: + address += 1 - self.skip_bp_count = skip_bp + op = f'stepping {steps} instructions' if steps else 'resuming' + self.ql.log.info(f'{PROMPT} {op} from {address:#x}') - if self.exit_point is not None: - self.ql.log.info(f'{PROMPT} Resume at: {self.current_address:#x}') - self.ql.emu_start(self.current_address, self.exit_point) + self.ql.emu_start(address, self.exit_point, count=steps) diff --git a/qiling/debugger/gdb/xmlregs.py b/qiling/debugger/gdb/xmlregs.py new file mode 100644 index 000000000..53199eafa --- /dev/null +++ b/qiling/debugger/gdb/xmlregs.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from typing import Iterator, Mapping, Optional, Sequence, Tuple +from pathlib import PurePath + +from qiling.arch.arm_const import reg_map as arm_regs +from qiling.arch.arm64_const import reg_map as arm64_regs +from qiling.arch.mips_const import reg_map as mips_regs +from qiling.arch.x86_const import reg_map_16 as x86_regs_16 +from qiling.arch.x86_const import reg_map_32 as x86_regs_32 +from qiling.arch.x86_const import reg_map_64 as x86_regs_64 +from qiling.arch.x86_const import reg_map_misc as x86_regs_misc +from qiling.arch.x86_const import reg_map_cr as x86_regs_cr +from qiling.arch.x86_const import reg_map_st as x86_regs_st +from qiling.arch.x86_const import reg_map_xmm as x86_regs_xmm +from qiling.arch.x86_const import reg_map_ymm as x86_regs_ymm + +from qiling.const import QL_ARCH + +RegEntry = Tuple[Optional[int], int, int] + +# define a local dummy function to let us reference this module +__anchor__ = lambda x: x + +def __get_xml_path(archtype: QL_ARCH) -> Tuple[str, PurePath]: + import inspect + + p = PurePath(inspect.getfile(__anchor__)) + basedir = p.parent / 'xml' / archtype.name.lower() + filename = basedir / 'target.xml' + + return str(filename), basedir + +def __walk_xml_regs(filename: str, base_url: PurePath) -> Iterator[Tuple[int, str, int]]: + from xml.etree import ElementTree, ElementInclude + + tree = ElementTree.parse(filename) + root = tree.getroot() + + # NOTE: this is needed to load xinclude hrefs relative to the main xml file. starting + # from python 3.9 ElementInclude.include has an argument for that called 'base_url'. + # this is a workaround for earlier python versions such as 3.8 + + def my_loader(base: PurePath): + def __wrapped(href: str, parse, encoding=None): + abshref = base / href + + return ElementInclude.default_loader(str(abshref), parse, encoding) + + return __wrapped + + ElementInclude.include(root, loader=my_loader(base_url)) + + regnum = -1 + + for reg in root.iter('reg'): + # if regnum is not specified, assume it follows the previous one + regnum = int(reg.get('regnum', regnum + 1)) + + name = reg.attrib['name'] + bitsize = reg.attrib['bitsize'] + + yield regnum, name, int(bitsize) + +def load_regsmap(archtype: QL_ARCH) -> Sequence[RegEntry]: + """Initialize registers map using available target XML files. + + Args: + archtype: target architecture type + + Returns: a list representing registers data + """ + + # retreive the relevant set of registers; their order of appearance is not + # important as it is determined by the info read from the xml files + ucregs: Mapping[str, int] = { + QL_ARCH.A8086 : dict(**x86_regs_16, **x86_regs_misc, **x86_regs_cr, **x86_regs_st), + QL_ARCH.X86 : dict(**x86_regs_32, **x86_regs_misc, **x86_regs_cr, **x86_regs_st, **x86_regs_xmm), + QL_ARCH.X8664 : dict(**x86_regs_64, **x86_regs_misc, **x86_regs_cr, **x86_regs_st, **x86_regs_xmm, **x86_regs_ymm), + QL_ARCH.ARM : arm_regs, + QL_ARCH.CORTEX_M : arm_regs, + QL_ARCH.ARM64 : arm64_regs, + QL_ARCH.MIPS : mips_regs + }[archtype] + + regmap = [] + pos = 0 + + xmlpath = __get_xml_path(archtype) + + for regnum, name, bitsize in __walk_xml_regs(*xmlpath): + # regs indices might not be consecutive. + # extend regmap with null entries if needed + if len(regmap) < regnum + 1: + regmap.extend([(None, 0, 0)] * (regnum + 1 - len(regmap))) + + # reg value size in nibbles + nibbles = bitsize // 4 + + regmap[regnum] = (ucregs.get(name), pos, nibbles) + + # value position of next reg + pos += nibbles + + return regmap + +__all__ = ['RegEntry', 'load_regsmap'] From 0a37fbf63f3ff6d9600bd371fca909b86db9bd8c Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 4 May 2022 22:43:41 +0300 Subject: [PATCH 335/406] Add XInclude namespace to allow correct parsing --- qiling/debugger/gdb/xml/a8086/target.xml | 7 ++++++- qiling/debugger/gdb/xml/arm/target.xml | 12 ++++++------ qiling/debugger/gdb/xml/arm64/target.xml | 8 ++++---- qiling/debugger/gdb/xml/mips/target.xml | 19 ++++++++++--------- qiling/debugger/gdb/xml/x86/target.xml | 18 +++++++++--------- qiling/debugger/gdb/xml/x8664/target.xml | 14 +++++++++++++- 6 files changed, 48 insertions(+), 30 deletions(-) diff --git a/qiling/debugger/gdb/xml/a8086/target.xml b/qiling/debugger/gdb/xml/a8086/target.xml index 71daae8cd..43bc64d7f 100644 --- a/qiling/debugger/gdb/xml/a8086/target.xml +++ b/qiling/debugger/gdb/xml/a8086/target.xml @@ -1 +1,6 @@ -i8086 + + + + i8086 + + \ No newline at end of file diff --git a/qiling/debugger/gdb/xml/arm/target.xml b/qiling/debugger/gdb/xml/arm/target.xml index 579e84b04..89826d474 100644 --- a/qiling/debugger/gdb/xml/arm/target.xml +++ b/qiling/debugger/gdb/xml/arm/target.xml @@ -6,9 +6,9 @@ *!notice and this notice are preserved. --> - - arm - - - - + + arm + + + + \ No newline at end of file diff --git a/qiling/debugger/gdb/xml/arm64/target.xml b/qiling/debugger/gdb/xml/arm64/target.xml index d4dbac662..b91fd952d 100644 --- a/qiling/debugger/gdb/xml/arm64/target.xml +++ b/qiling/debugger/gdb/xml/arm64/target.xml @@ -7,8 +7,8 @@ *!notice and this notice are preserved. --> - - aarch64 - - + + aarch64 + + \ No newline at end of file diff --git a/qiling/debugger/gdb/xml/mips/target.xml b/qiling/debugger/gdb/xml/mips/target.xml index a585dffd3..0a6a1b628 100644 --- a/qiling/debugger/gdb/xml/mips/target.xml +++ b/qiling/debugger/gdb/xml/mips/target.xml @@ -6,14 +6,15 @@ *!notice and this notice are preserved. --> - - mips - GNU/Linux - - - + + mips + GNU/Linux - - * - + + + + + + + \ No newline at end of file diff --git a/qiling/debugger/gdb/xml/x86/target.xml b/qiling/debugger/gdb/xml/x86/target.xml index a664320d9..502703234 100644 --- a/qiling/debugger/gdb/xml/x86/target.xml +++ b/qiling/debugger/gdb/xml/x86/target.xml @@ -1,11 +1,11 @@ -i386 - GNU/Linux - - - - - - - + +i386 + GNU/Linux + + + + + + \ No newline at end of file diff --git a/qiling/debugger/gdb/xml/x8664/target.xml b/qiling/debugger/gdb/xml/x8664/target.xml index d58e543ea..d68705f67 100644 --- a/qiling/debugger/gdb/xml/x8664/target.xml +++ b/qiling/debugger/gdb/xml/x8664/target.xml @@ -1 +1,13 @@ -i386:x86-64GNU/Linux + + + + i386:x86-64 + GNU/Linux + + + + + + + + \ No newline at end of file From d2092bb07518cb6c79fa2cfdd57029ecc509e6fd Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 4 May 2022 22:44:42 +0300 Subject: [PATCH 336/406] Reformat gdb xml files --- qiling/debugger/gdb/xml/x86/32bit-core.xml | 104 ++++++++++---------- qiling/debugger/gdb/xml/x86/32bit-mpx.xml | 64 ++++++------ qiling/debugger/gdb/xml/x86/32bit-pkeys.xml | 6 +- qiling/debugger/gdb/xml/x86/32bit-sse.xml | 84 ++++++++-------- 4 files changed, 130 insertions(+), 128 deletions(-) diff --git a/qiling/debugger/gdb/xml/x86/32bit-core.xml b/qiling/debugger/gdb/xml/x86/32bit-core.xml index 0958032a8..702ae3b0b 100644 --- a/qiling/debugger/gdb/xml/x86/32bit-core.xml +++ b/qiling/debugger/gdb/xml/x86/32bit-core.xml @@ -7,59 +7,59 @@ - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - - + + + + + + + + + \ No newline at end of file diff --git a/qiling/debugger/gdb/xml/x86/32bit-mpx.xml b/qiling/debugger/gdb/xml/x86/32bit-mpx.xml index 7772954d3..1798dbd80 100644 --- a/qiling/debugger/gdb/xml/x86/32bit-mpx.xml +++ b/qiling/debugger/gdb/xml/x86/32bit-mpx.xml @@ -7,39 +7,41 @@ - - - - + + + + - - - - + + + + - - - - + + + + - - - - - - - + + + + + + + - - - - + + + + - - - - - - - + + + + + + + \ No newline at end of file diff --git a/qiling/debugger/gdb/xml/x86/32bit-pkeys.xml b/qiling/debugger/gdb/xml/x86/32bit-pkeys.xml index 6f3c65a87..71aa7dc12 100644 --- a/qiling/debugger/gdb/xml/x86/32bit-pkeys.xml +++ b/qiling/debugger/gdb/xml/x86/32bit-pkeys.xml @@ -7,7 +7,5 @@ - - - - + + \ No newline at end of file diff --git a/qiling/debugger/gdb/xml/x86/32bit-sse.xml b/qiling/debugger/gdb/xml/x86/32bit-sse.xml index c57a2d846..e28536e5d 100644 --- a/qiling/debugger/gdb/xml/x86/32bit-sse.xml +++ b/qiling/debugger/gdb/xml/x86/32bit-sse.xml @@ -7,46 +7,48 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + - - - - - - - - + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 4198033ae96c1fdef339fa70dbb76fad7c3f5755 Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 4 May 2022 22:46:34 +0300 Subject: [PATCH 337/406] Rename x86 eflags reg to match gdb name --- qiling/arch/x86_const.py | 2 +- qiling/debugger/qdb/arch/arch_x86.py | 4 ++-- qiling/debugger/qdb/branch_predictor/branch_predictor_x86.py | 2 +- qiling/debugger/qdb/frontend.py | 4 ++-- qiling/debugger/qdb/render/render_x86.py | 2 +- qiling/os/dos/dos.py | 4 ++-- qiling/os/macos/utils.py | 4 ++-- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/qiling/arch/x86_const.py b/qiling/arch/x86_const.py index 798549eea..1e60479fb 100644 --- a/qiling/arch/x86_const.py +++ b/qiling/arch/x86_const.py @@ -177,7 +177,7 @@ } reg_map_misc = { - "ef": UC_X86_REG_EFLAGS, + "eflags": UC_X86_REG_EFLAGS, "cs": UC_X86_REG_CS, "ss": UC_X86_REG_SS, "ds": UC_X86_REG_DS, diff --git a/qiling/debugger/qdb/arch/arch_x86.py b/qiling/debugger/qdb/arch/arch_x86.py index 4aae910c8..10617cbd1 100644 --- a/qiling/debugger/qdb/arch/arch_x86.py +++ b/qiling/debugger/qdb/arch/arch_x86.py @@ -21,7 +21,7 @@ def regs(self): "eax", "ebx", "ecx", "edx", "esp", "ebp", "esi", "edi", "eip", "ss", "cs", "ds", "es", - "fs", "gs", "ef", + "fs", "gs", "eflags", ) def read_insn(self, address: int) -> bytes: @@ -34,7 +34,7 @@ def read_insn(self, address: int) -> bytes: @staticmethod def get_flags(bits: int) -> Mapping[str, bool]: """ - get flags from ql.reg.ef + get flags from ql.reg.eflags """ return { diff --git a/qiling/debugger/qdb/branch_predictor/branch_predictor_x86.py b/qiling/debugger/qdb/branch_predictor/branch_predictor_x86.py index ce92dd9fd..656c64daa 100644 --- a/qiling/debugger/qdb/branch_predictor/branch_predictor_x86.py +++ b/qiling/debugger/qdb/branch_predictor/branch_predictor_x86.py @@ -91,7 +91,7 @@ def predict(self): } if line.mnemonic in jump_table: - eflags = self.get_flags(self.ql.arch.regs.ef).values() + eflags = self.get_flags(self.ql.arch.regs.eflags).values() prophecy.going = jump_table.get(line.mnemonic)(*eflags) elif line.mnemonic in jump_reg_table: diff --git a/qiling/debugger/qdb/frontend.py b/qiling/debugger/qdb/frontend.py index d1f53f864..ed243446b 100644 --- a/qiling/debugger/qdb/frontend.py +++ b/qiling/debugger/qdb/frontend.py @@ -389,7 +389,7 @@ def __init__(self, ql): "eax", "ebx", "ecx", "edx", "esp", "ebp", "esi", "edi", "eip", "ss", "cs", "ds", "es", - "fs", "gs", "ef", + "fs", "gs", "eflags", ) @context_printer("[ REGISTERS ]") def context_reg(self, saved_reg_dump): @@ -416,7 +416,7 @@ def context_reg(self, saved_reg_dump): lines += line print(lines.format(*cur_regs.values())) - print(color.GREEN, "EFLAGS: [CF: {flags[CF]}, PF: {flags[PF]}, AF: {flags[AF]}, ZF: {flags[ZF]}, SF: {flags[SF]}, OF: {flags[OF]}]".format(flags=get_x86_eflags(self.ql.arch.regs.ef)), color.END, sep="") + print(color.GREEN, "EFLAGS: [CF: {flags[CF]}, PF: {flags[PF]}, AF: {flags[AF]}, ZF: {flags[ZF]}, SF: {flags[SF]}, OF: {flags[OF]}]".format(flags=get_x86_eflags(self.ql.arch.regs.eflags)), color.END, sep="") @context_printer("[ DISASM ]", footer=True) def context_asm(self): diff --git a/qiling/debugger/qdb/render/render_x86.py b/qiling/debugger/qdb/render/render_x86.py index 3597181eb..5c43d0e55 100644 --- a/qiling/debugger/qdb/render/render_x86.py +++ b/qiling/debugger/qdb/render/render_x86.py @@ -22,7 +22,7 @@ def context_reg(self, saved_reg_dump): cur_regs = self.dump_regs() diff_reg = self.reg_diff(cur_regs, saved_reg_dump) self.render_regs_dump(cur_regs, diff_reg=diff_reg) - print(color.GREEN, "EFLAGS: [CF: {flags[CF]}, PF: {flags[PF]}, AF: {flags[AF]}, ZF: {flags[ZF]}, SF: {flags[SF]}, OF: {flags[OF]}]".format(flags=self.get_flags(self.ql.arch.regs.ef)), color.END, sep="") + print(color.GREEN, "EFLAGS: [CF: {flags[CF]}, PF: {flags[PF]}, AF: {flags[AF]}, ZF: {flags[ZF]}, SF: {flags[SF]}, OF: {flags[OF]}]".format(flags=self.get_flags(self.ql.arch.regs.eflags)), color.END, sep="") @Render.divider_printer("[ DISASM ]") def context_asm(self): diff --git a/qiling/os/dos/dos.py b/qiling/os/dos/dos.py index 6fb4d330c..fb7f55c17 100644 --- a/qiling/os/dos/dos.py +++ b/qiling/os/dos/dos.py @@ -58,10 +58,10 @@ def __del__(self): curses.endwin() def set_flag_value(self, fl: Flags, val: int) -> None: - self.ql.arch.regs.ef = self.ql.arch.regs.ef & (~fl) | (fl * val) + self.ql.arch.regs.eflags = self.ql.arch.regs.eflags & (~fl) | (fl * val) def test_flags(self, fl): - return self.ql.arch.regs.ef & fl == fl + return self.ql.arch.regs.eflags & fl == fl def set_cf(self): self.set_flag_value(Flags.CF, 0b1) diff --git a/qiling/os/macos/utils.py b/qiling/os/macos/utils.py index 351f0f0b0..808fbe29c 100644 --- a/qiling/os/macos/utils.py +++ b/qiling/os/macos/utils.py @@ -188,5 +188,5 @@ def page_align_end(addr, page_size): def set_eflags_cf(ql, target_cf): - ql.arch.regs.ef = ( ql.arch.regs.ef & 0xfffffffe ) | target_cf - return ql.arch.regs.ef + ql.arch.regs.eflags = ( ql.arch.regs.eflags & 0xfffffffe ) | target_cf + return ql.arch.regs.eflags From 546cbc986430dbfd291efa841ef532ca5b147bf6 Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 4 May 2022 22:47:31 +0300 Subject: [PATCH 338/406] Rename AUX enum --- qiling/loader/elf.py | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/qiling/loader/elf.py b/qiling/loader/elf.py index c6ba9573f..cb62d7c96 100644 --- a/qiling/loader/elf.py +++ b/qiling/loader/elf.py @@ -29,7 +29,7 @@ # auxiliary vector types # see: https://man7.org/linux/man-pages/man3/getauxval.3.html -class AUX(IntEnum): +class AUXV(IntEnum): AT_NULL = 0 AT_IGNORE = 1 AT_EXECFD = 2 @@ -317,26 +317,26 @@ def __push_str(top: int, s: str) -> int: elf_hwcap = 0xd7b81f # setup aux vector - aux_entries = ( - (AUX.AT_PHDR, elf_phdr), - (AUX.AT_PHENT, elf_phent), - (AUX.AT_PHNUM, elf_phnum), - (AUX.AT_PAGESZ, self.ql.mem.pagesize), - (AUX.AT_BASE, interp_address), - (AUX.AT_FLAGS, 0), - (AUX.AT_ENTRY, self.elf_entry), - (AUX.AT_UID, self.ql.os.uid), - (AUX.AT_EUID, self.ql.os.euid), - (AUX.AT_GID, self.ql.os.gid), - (AUX.AT_EGID, self.ql.os.egid), - (AUX.AT_HWCAP, elf_hwcap), - (AUX.AT_CLKTCK, 100), - (AUX.AT_RANDOM, randstraddr), - (AUX.AT_HWCAP2, 0), - (AUX.AT_EXECFN, execfn), - (AUX.AT_PLATFORM, cpustraddr), - (AUX.AT_SECURE, 0), - (AUX.AT_NULL, 0) + auxv_entries = ( + (AUXV.AT_HWCAP, elf_hwcap), + (AUXV.AT_PAGESZ, self.ql.mem.pagesize), + (AUXV.AT_CLKTCK, 100), + (AUXV.AT_PHDR, elf_phdr), + (AUXV.AT_PHENT, elf_phent), + (AUXV.AT_PHNUM, elf_phnum), + (AUXV.AT_BASE, interp_address), + (AUXV.AT_FLAGS, 0), + (AUXV.AT_ENTRY, self.elf_entry), + (AUXV.AT_UID, self.ql.os.uid), + (AUXV.AT_EUID, self.ql.os.euid), + (AUXV.AT_GID, self.ql.os.gid), + (AUXV.AT_EGID, self.ql.os.egid), + (AUXV.AT_SECURE, 0), + (AUXV.AT_RANDOM, randstraddr), + (AUXV.AT_HWCAP2, 0), + (AUXV.AT_EXECFN, execfn), + (AUXV.AT_PLATFORM, cpustraddr), + (AUXV.AT_NULL, 0) ) # add all aux entries From 7258e4cc153c873e10772ed79372c9b244157b49 Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 4 May 2022 22:48:36 +0300 Subject: [PATCH 339/406] Have auxv hold mem address instead of dict values --- qiling/loader/elf.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/qiling/loader/elf.py b/qiling/loader/elf.py index cb62d7c96..a1be0ef63 100644 --- a/qiling/loader/elf.py +++ b/qiling/loader/elf.py @@ -339,14 +339,17 @@ def __push_str(top: int, s: str) -> int: (AUXV.AT_NULL, 0) ) - # add all aux entries - for key, val in aux_entries: - elf_table.extend(self.ql.pack(key) + self.ql.pack(val)) + bytes_before_auxv = len(elf_table) + + # add all auxv entries + for key, val in auxv_entries: + elf_table.extend(self.ql.pack(key)) + elf_table.extend(self.ql.pack(val)) new_stack = self.ql.mem.align(new_stack - len(elf_table), 0x10) self.ql.mem.write(new_stack, bytes(elf_table)) - self.aux_vec = dict(aux_entries) + self.auxv = new_stack + bytes_before_auxv self.stack_address = new_stack self.load_address = load_address From 964fc356d5354cdde047f169d0f7b395b5862ec9 Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 4 May 2022 22:50:17 +0300 Subject: [PATCH 340/406] Clean up --- qiling/arch/arm_const.py | 1 - qiling/arch/x86.py | 33 --------------------------------- qiling/const.py | 5 ++--- 3 files changed, 2 insertions(+), 37 deletions(-) diff --git a/qiling/arch/arm_const.py b/qiling/arch/arm_const.py index e93393665..a58d0d1ea 100644 --- a/qiling/arch/arm_const.py +++ b/qiling/arch/arm_const.py @@ -4,7 +4,6 @@ # from unicorn.arm_const import * -from enum import IntEnum reg_map = { "r0": UC_ARM_REG_R0, diff --git a/qiling/arch/x86.py b/qiling/arch/x86.py index b634849fc..97850ef66 100644 --- a/qiling/arch/x86.py +++ b/qiling/arch/x86.py @@ -4,14 +4,11 @@ # from functools import cached_property -from typing import Union from unicorn import Uc, UC_ARCH_X86, UC_MODE_16, UC_MODE_32, UC_MODE_64 from capstone import Cs, CS_ARCH_X86, CS_MODE_16, CS_MODE_32, CS_MODE_64 from keystone import Ks, KS_ARCH_X86, KS_MODE_16, KS_MODE_32, KS_MODE_64 -from unicorn.x86_const import UC_X86_REG_EFLAGS - from qiling.arch.arch import QlArch from qiling.arch.msr import QlMsrManager from qiling.arch.register import QlRegisterManager @@ -126,33 +123,3 @@ def disassembler(self) -> Cs: @cached_property def assembler(self) -> Ks: return Ks(KS_ARCH_X86, KS_MODE_64) - - # TODO: generalize this - def __reg_bits(self, register: int) -> int: - # all regs in reg_map_misc are 16 bits except of eflags - if register == UC_X86_REG_EFLAGS: - return 32 - - regmaps = ( - (x86_const.reg_map_8, 8), - (x86_const.reg_map_16, 16), - (x86_const.reg_map_32, 32), - (x86_const.reg_map_64, 64), - (x86_const.reg_map_misc, 16), - (x86_const.reg_map_cr, 64), # 32 bits for x86 - (x86_const.reg_map_st, 32), - (x86_const.reg_map_seg_base, 64), # 32 bits for x86 - ) - - return next((rsize for rmap, rsize in regmaps if register in rmap.values()), 0) - - # note: this method was not generalized for all archs since it requires a bookkeeping - # of all registers, while it is used only by gdb and only for x86-64 - def reg_bits(self, reg: Union[str, int]) -> int: - """Get register size in bits. - """ - - if type(reg) is str: - reg = self.regs.register_mapping[reg] - - return self.__reg_bits(reg) diff --git a/qiling/const.py b/qiling/const.py index 1f97e4cbb..2b38d71f6 100644 --- a/qiling/const.py +++ b/qiling/const.py @@ -60,9 +60,8 @@ class QL_STOP(Flag): QL_ARCH_INTERPRETER = (QL_ARCH.EVM,) -QL_OS_NONPID = (QL_OS.DOS, QL_OS.UEFI) -QL_OS_POSIX = (QL_OS.LINUX, QL_OS.FREEBSD, QL_OS.MACOS, QL_OS.QNX) -QL_OS_BAREMETAL = (QL_OS.MCU,) +QL_OS_POSIX = (QL_OS.LINUX, QL_OS.FREEBSD, QL_OS.MACOS, QL_OS.QNX) +QL_OS_BAREMETAL = (QL_OS.MCU,) QL_HOOK_BLOCK = 0b0001 QL_CALL_BLOCK = 0b0010 From 01a1941af1c8fe06971824e594f56856964ce1fc Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 4 May 2022 22:52:50 +0300 Subject: [PATCH 341/406] Reduce imports clutter --- qiling/const.py | 5 +++++ qiling/os/memory.py | 3 +-- qiling/os/posix/const_mapping.py | 7 ++++--- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/qiling/const.py b/qiling/const.py index 2b38d71f6..d7ac9f8db 100644 --- a/qiling/const.py +++ b/qiling/const.py @@ -82,3 +82,8 @@ def __casefold_enum(e: Type[T]) -> Mapping[str, T]: QL_ARCH.EVM : QL_OS.EVM, QL_ARCH.CORTEX_M : QL_OS.MCU } + +__all__ = [ + 'QL_ENDIAN', 'QL_ARCH', 'QL_OS', 'QL_VERBOSE', 'QL_DEBUGGER', 'QL_INTERCEPT', 'QL_STOP', + 'QL_ARCH_INTERPRETER', 'QL_OS_POSIX', 'QL_OS_BAREMETAL', 'QL_HOOK_BLOCK', 'QL_CALL_BLOCK' +] diff --git a/qiling/os/memory.py b/qiling/os/memory.py index 5357623ef..2354f74f9 100644 --- a/qiling/os/memory.py +++ b/qiling/os/memory.py @@ -4,12 +4,11 @@ # import os, re -from typing import Any, Callable, List, MutableSequence, Optional, Sequence, Tuple +from typing import Any, Callable, List, Mapping, MutableSequence, Optional, Sequence, Tuple from unicorn import UC_PROT_NONE, UC_PROT_READ, UC_PROT_WRITE, UC_PROT_EXEC, UC_PROT_ALL from qiling import Qiling -from qiling.const import * from qiling.exception import * # tuple: range start, range end, permissions mask, range label, is mmio? diff --git a/qiling/os/posix/const_mapping.py b/qiling/os/posix/const_mapping.py index 41036eb16..99fb31ca5 100644 --- a/qiling/os/posix/const_mapping.py +++ b/qiling/os/posix/const_mapping.py @@ -3,11 +3,12 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from typing import MutableSequence -from .const import * +from typing import Mapping, MutableSequence from qiling import Qiling -from qiling.const import * +from qiling.const import QL_ARCH, QL_OS + +from .const import * def _invert_dict(d: Mapping) -> Mapping: return { v:k for k, v in d.items()} From 5f4c3dc9919608e35aa2f58817454e5fe28aeccb Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 4 May 2022 22:53:49 +0300 Subject: [PATCH 342/406] Fix missing imports --- qiling/extensions/report/report.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiling/extensions/report/report.py b/qiling/extensions/report/report.py index ea46aaa6f..1189e698c 100644 --- a/qiling/extensions/report/report.py +++ b/qiling/extensions/report/report.py @@ -12,8 +12,8 @@ class Report: def __init__(self, ql): self.filename = ql.argv self.rootfs = ql.rootfs - self.arch = list(arch_map.keys())[list(arch_map.values()).index(ql.arch.type)] - self.os = list(os_map.keys())[list(os_map.values()).index(ql.os.type)] + self.arch = ql.arch.type.name + self.os = ql.os.type.name self.env = ql.env self.strings = set() for string in ql.os.stats.strings: From baaf9cc5c7a166b4072cc73a955444051b069301 Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 4 May 2022 22:54:59 +0300 Subject: [PATCH 343/406] Reformat arm regs map --- qiling/arch/arm_const.py | 65 ++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/qiling/arch/arm_const.py b/qiling/arch/arm_const.py index a58d0d1ea..d8def1220 100644 --- a/qiling/arch/arm_const.py +++ b/qiling/arch/arm_const.py @@ -6,36 +6,37 @@ from unicorn.arm_const import * reg_map = { - "r0": UC_ARM_REG_R0, - "r1": UC_ARM_REG_R1, - "r2": UC_ARM_REG_R2, - "r3": UC_ARM_REG_R3, - "r4": UC_ARM_REG_R4, - "r5": UC_ARM_REG_R5, - "r6": UC_ARM_REG_R6, - "r7": UC_ARM_REG_R7, - "r8": UC_ARM_REG_R8, - "r9": UC_ARM_REG_R9, - "r10": UC_ARM_REG_R10, - "r11": UC_ARM_REG_R11, - "r12": UC_ARM_REG_R12, - "sp": UC_ARM_REG_SP, - "lr": UC_ARM_REG_LR, - "pc": UC_ARM_REG_PC, - - # CPSR needs to be at offset 25 for GDB, see https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=gdb/arch/arm.h;h=fa589fd0582c0add627a068e6f4947a909c45e86;hb=HEAD#l34 - # The fp registers inbetween have become obsolete - "f0": UC_ARM_REG_INVALID, - "f1": UC_ARM_REG_INVALID, - "f2": UC_ARM_REG_INVALID, - "f3": UC_ARM_REG_INVALID, - "f4": UC_ARM_REG_INVALID, - "f5": UC_ARM_REG_INVALID, - "f6": UC_ARM_REG_INVALID, - "f7": UC_ARM_REG_INVALID, - "fps": UC_ARM_REG_INVALID, - "cpsr": UC_ARM_REG_CPSR, - "c1_c0_2": UC_ARM_REG_C1_C0_2, - "c13_c0_3": UC_ARM_REG_C13_C0_3, - "fpexc": UC_ARM_REG_FPEXC, + "r0": UC_ARM_REG_R0, + "r1": UC_ARM_REG_R1, + "r2": UC_ARM_REG_R2, + "r3": UC_ARM_REG_R3, + "r4": UC_ARM_REG_R4, + "r5": UC_ARM_REG_R5, + "r6": UC_ARM_REG_R6, + "r7": UC_ARM_REG_R7, + "r8": UC_ARM_REG_R8, + "r9": UC_ARM_REG_R9, + "r10": UC_ARM_REG_R10, + "r11": UC_ARM_REG_R11, + "r12": UC_ARM_REG_R12, + "sp": UC_ARM_REG_SP, + "lr": UC_ARM_REG_LR, + "pc": UC_ARM_REG_PC, + + # CPSR needs to be at offset 25 for GDB, see https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=gdb/arch/arm.h;h=fa589fd0582c0add627a068e6f4947a909c45e86;hb=HEAD#l34 + # The fp registers inbetween have become obsolete + "f0": UC_ARM_REG_INVALID, + "f1": UC_ARM_REG_INVALID, + "f2": UC_ARM_REG_INVALID, + "f3": UC_ARM_REG_INVALID, + "f4": UC_ARM_REG_INVALID, + "f5": UC_ARM_REG_INVALID, + "f6": UC_ARM_REG_INVALID, + "f7": UC_ARM_REG_INVALID, + "fps": UC_ARM_REG_INVALID, + + "cpsr": UC_ARM_REG_CPSR, + "c1_c0_2": UC_ARM_REG_C1_C0_2, + "c13_c0_3": UC_ARM_REG_C13_C0_3, + "fpexc": UC_ARM_REG_FPEXC } From 897f2a85e7d90e9c3e605ed44512279969f24e23 Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 5 May 2022 18:29:23 +0300 Subject: [PATCH 344/406] Dummy commit to re-initiate tests --- qiling/debugger/gdb/gdb.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qiling/debugger/gdb/gdb.py b/qiling/debugger/gdb/gdb.py index 574341a46..67d7d7d2b 100644 --- a/qiling/debugger/gdb/gdb.py +++ b/qiling/debugger/gdb/gdb.py @@ -29,8 +29,7 @@ from qiling.const import QL_ARCH, QL_ENDIAN, QL_OS from qiling.debugger import QlDebugger from qiling.debugger.gdb import xmlregs - -from .utils import QlGdbUtils +from qiling.debugger.gdb.utils import QlGdbUtils # gdb logging prompt PROMPT = r'gdb>' From d49beed1afbc73114a903d639f3ee0c3b9dfbc6e Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 5 May 2022 21:08:41 +0300 Subject: [PATCH 345/406] Add missing regs for MIPS --- qiling/arch/mips.py | 3 +- qiling/arch/mips_const.py | 70 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/qiling/arch/mips.py b/qiling/arch/mips.py index 8391becc1..9368634f2 100644 --- a/qiling/arch/mips.py +++ b/qiling/arch/mips.py @@ -37,7 +37,8 @@ def uc(self) -> Uc: def regs(self) -> QlRegisterManager: regs_map = dict( **mips_const.reg_map, - **mips_const.reg_map_afpr128 + **mips_const.reg_map_afpr128, + **mips_const.reg_map_fpu ) pc_reg = 'pc' diff --git a/qiling/arch/mips_const.py b/qiling/arch/mips_const.py index 8dd8d48a9..8e3141f4f 100644 --- a/qiling/arch/mips_const.py +++ b/qiling/arch/mips_const.py @@ -6,6 +6,39 @@ from unicorn.mips_const import * reg_map = { + "r0" : UC_MIPS_REG_0, + "r1" : UC_MIPS_REG_1, + "r2" : UC_MIPS_REG_2, + "r3" : UC_MIPS_REG_3, + "r4" : UC_MIPS_REG_4, + "r5" : UC_MIPS_REG_5, + "r6" : UC_MIPS_REG_6, + "r7" : UC_MIPS_REG_7, + "r8" : UC_MIPS_REG_8, + "r9" : UC_MIPS_REG_9, + "r10" : UC_MIPS_REG_10, + "r11" : UC_MIPS_REG_11, + "r12" : UC_MIPS_REG_12, + "r13" : UC_MIPS_REG_13, + "r14" : UC_MIPS_REG_14, + "r15" : UC_MIPS_REG_15, + "r16" : UC_MIPS_REG_16, + "r17" : UC_MIPS_REG_17, + "r18" : UC_MIPS_REG_18, + "r19" : UC_MIPS_REG_19, + "r20" : UC_MIPS_REG_20, + "r21" : UC_MIPS_REG_21, + "r22" : UC_MIPS_REG_22, + "r23" : UC_MIPS_REG_23, + "r24" : UC_MIPS_REG_24, + "r25" : UC_MIPS_REG_25, + "r26" : UC_MIPS_REG_26, + "r27" : UC_MIPS_REG_27, + "r28" : UC_MIPS_REG_28, + "r29" : UC_MIPS_REG_29, + "r30" : UC_MIPS_REG_30, + "r31" : UC_MIPS_REG_31, + "zero": UC_MIPS_REG_ZERO, "at": UC_MIPS_REG_AT, "v0": UC_MIPS_REG_V0, @@ -49,4 +82,39 @@ reg_map_afpr128 = { "cp0_config3" : UC_MIPS_REG_CP0_CONFIG3, "cp0_userlocal": UC_MIPS_REG_CP0_USERLOCAL, -} \ No newline at end of file +} + +reg_map_fpu = { + "f0" : UC_MIPS_REG_F0, + "f1" : UC_MIPS_REG_F1, + "f2" : UC_MIPS_REG_F2, + "f3" : UC_MIPS_REG_F3, + "f4" : UC_MIPS_REG_F4, + "f5" : UC_MIPS_REG_F5, + "f6" : UC_MIPS_REG_F6, + "f7" : UC_MIPS_REG_F7, + "f8" : UC_MIPS_REG_F8, + "f9" : UC_MIPS_REG_F9, + "f10" : UC_MIPS_REG_F10, + "f11" : UC_MIPS_REG_F11, + "f12" : UC_MIPS_REG_F12, + "f13" : UC_MIPS_REG_F13, + "f14" : UC_MIPS_REG_F14, + "f15" : UC_MIPS_REG_F15, + "f16" : UC_MIPS_REG_F16, + "f17" : UC_MIPS_REG_F17, + "f18" : UC_MIPS_REG_F18, + "f19" : UC_MIPS_REG_F19, + "f20" : UC_MIPS_REG_F20, + "f21" : UC_MIPS_REG_F21, + "f22" : UC_MIPS_REG_F22, + "f23" : UC_MIPS_REG_F23, + "f24" : UC_MIPS_REG_F24, + "f25" : UC_MIPS_REG_F25, + "f26" : UC_MIPS_REG_F26, + "f27" : UC_MIPS_REG_F27, + "f28" : UC_MIPS_REG_F28, + "f29" : UC_MIPS_REG_F29, + "f30" : UC_MIPS_REG_F30, + "f31" : UC_MIPS_REG_F31 +} From 03c47b4f1f7eb751c19fc4343e9ea46d832d178e Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 5 May 2022 21:09:35 +0300 Subject: [PATCH 346/406] Fix MIPS regs gdb definition --- qiling/debugger/gdb/xmlregs.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qiling/debugger/gdb/xmlregs.py b/qiling/debugger/gdb/xmlregs.py index 53199eafa..63084128c 100644 --- a/qiling/debugger/gdb/xmlregs.py +++ b/qiling/debugger/gdb/xmlregs.py @@ -8,7 +8,8 @@ from qiling.arch.arm_const import reg_map as arm_regs from qiling.arch.arm64_const import reg_map as arm64_regs -from qiling.arch.mips_const import reg_map as mips_regs +from qiling.arch.mips_const import reg_map as mips_regs_gpr +from qiling.arch.mips_const import reg_map_fpu as mips_regs_fpu from qiling.arch.x86_const import reg_map_16 as x86_regs_16 from qiling.arch.x86_const import reg_map_32 as x86_regs_32 from qiling.arch.x86_const import reg_map_64 as x86_regs_64 @@ -83,7 +84,7 @@ def load_regsmap(archtype: QL_ARCH) -> Sequence[RegEntry]: QL_ARCH.ARM : arm_regs, QL_ARCH.CORTEX_M : arm_regs, QL_ARCH.ARM64 : arm64_regs, - QL_ARCH.MIPS : mips_regs + QL_ARCH.MIPS : dict(**mips_regs_gpr, **mips_regs_fpu) }[archtype] regmap = [] From 3b9059562957e1a79e06a90db033033c0eb04595 Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 5 May 2022 21:10:44 +0300 Subject: [PATCH 347/406] Make qmark handler generic rather than hardcoded --- qiling/debugger/gdb/gdb.py | 53 ++++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/qiling/debugger/gdb/gdb.py b/qiling/debugger/gdb/gdb.py index 67d7d7d2b..b8c53ff43 100644 --- a/qiling/debugger/gdb/gdb.py +++ b/qiling/debugger/gdb/gdb.py @@ -146,28 +146,49 @@ def handle_exclaim(subcmd: str) -> Reply: def handle_qmark(subcmd: str) -> Reply: - # MIPS32_EL : $T051d:00e7ff7f;25:40ccfc77;#65 - # MIPS32_EB : $T051d:7fff6dc0;25:77fc4880;thread:28fa;core:0; - # ARM64 : $T051d:0*,;1f:80f6f*"ff0* ;20:c02cfdb7f* 0* ;thread:p1f9.1f9;core:0;#56 - # ARM : $T050b:0*"00;0d:e0f6ffbe;0f:8079fdb6;#ae + from unicorn.x86_const import UC_X86_REG_EIP, UC_X86_REG_ESP + from unicorn.x86_const import UC_X86_REG_RIP, UC_X86_REG_RSP + from unicorn.arm_const import UC_ARM_REG_PC, UC_ARM_REG_SP + from unicorn.arm64_const import UC_ARM64_REG_PC, UC_ARM64_REG_SP + from unicorn.mips_const import UC_MIPS_REG_PC, UC_MIPS_REG_SP + + # X86 : T0505:00000000;04:c0d3ffff;08:2021fdf7;thread:p15c6.15c6;core:6 + # X8664 : T0506:0000000000000000;07:b0e2ffffff7f0000;10:0001fdf7ff7f0000;thread:p15a2.15a2;core:6; + # MIPS32_EL : T051d:00e7ff7f;25:40ccfc77; + # MIPS32_EB : T051d:7fff6dc0;25:77fc4880;thread:28fa;core:0; + # ARM64 : T051d:0000000000000000;1f:80f6ffffffff0000;20:c02cfdb7ffff0000;thread:p1f9.1f9;core:0; + # ARM : T050b:00000000;0d:e0f6ffbe;0f:8079fdb6; response = { - QL_ARCH.X86 : ( 0x05, 0x04, 0x08 ), - QL_ARCH.X8664 : ( 0x06, 0x07, 0x10 ), - QL_ARCH.ARM : ( 0x0b, 0x0d, 0x0f ), - QL_ARCH.ARM64 : ( 0x1d, 0xf1, 0x20 ), - QL_ARCH.MIPS : ( 0x1d, 0x00, 0x25 ), - QL_ARCH.A8086 : ( 0x05, 0x04, 0x08 ), - QL_ARCH.CORTEX_M : ( 0x0b, 0x0d, 0x0f ) + QL_ARCH.X86 : ( 0x05, UC_X86_REG_ESP, UC_X86_REG_EIP ), + QL_ARCH.X8664 : ( 0x06, UC_X86_REG_RSP, UC_X86_REG_RIP ), + QL_ARCH.ARM : ( 0x0b, UC_ARM_REG_SP, UC_ARM_REG_PC ), + QL_ARCH.ARM64 : ( 0x1d, UC_ARM64_REG_SP, UC_ARM64_REG_PC ), + QL_ARCH.MIPS : ( 0x1d, UC_MIPS_REG_SP, UC_MIPS_REG_PC ), + QL_ARCH.A8086 : ( 0x05, UC_X86_REG_ESP, UC_X86_REG_EIP ), + QL_ARCH.CORTEX_M : ( 0x0b, UC_ARM_REG_SP, UC_ARM_REG_PC ) } - idhex, spid, pcid = response[self.ql.arch.type] - sp = __hexstr(self.ql.arch.regs.arch_sp) - pc = __hexstr(self.ql.arch.regs.arch_pc) + idhex, sp_reg, pc_reg = response[self.ql.arch.type] + + def __get_reg_idx(ucreg: int) -> int: + """Get the index of a uc reg whithin the regsmap array. + + Returns: array index where this reg's info is stored, or -1 if not found + """ + + return next((i for i, (regnum, _, _) in enumerate(self.regsmap) if regnum == ucreg), -1) + + sp_idx = __get_reg_idx(sp_reg) + pc_idx = __get_reg_idx(pc_reg) + + sp_val = __get_reg_value(*self.regsmap[sp_idx]) + pc_val = __get_reg_value(*self.regsmap[pc_idx]) + zfill = __hexstr(0) - info = '' if self.ql.arch.type == QL_ARCH.MIPS else f':{zfill};{spid:02x}' - return f'T{SIGTRAP:02x}{idhex:02x}{info}:{sp};{pcid:02x}:{pc};' + info = '' if self.ql.arch.type == QL_ARCH.MIPS else f':{zfill};{sp_idx:02x}' + return f'T{SIGTRAP:02x}{idhex:02x}{info}:{sp_val};{pc_idx:02x}:{pc_val};' def handle_c(subcmd: str) -> Reply: From da820236717e247dd67959a3b6f6199c4ff6b5de Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 5 May 2022 21:11:21 +0300 Subject: [PATCH 348/406] Add a few gdbserver debug messages --- qiling/debugger/gdb/gdb.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qiling/debugger/gdb/gdb.py b/qiling/debugger/gdb/gdb.py index b8c53ff43..e2f00bd8d 100644 --- a/qiling/debugger/gdb/gdb.py +++ b/qiling/debugger/gdb/gdb.py @@ -236,9 +236,7 @@ def handle_g(subcmd: str) -> Reply: # see: ./xml/arm/arm-fpa.xml # see: https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=gdb/arch/arm.h;h=fa589fd0582c0add627a068e6f4947a909c45e86;hb=HEAD#l127 - data = ''.join(__get_reg_value(*entry) for entry in self.regsmap) - - return data + return ''.join(__get_reg_value(*entry) for entry in self.regsmap) def handle_G(subcmd: str) -> Reply: @@ -349,6 +347,7 @@ def handle_Q(subcmd: str) -> Reply: if feature == 'StartNoAckMode': server.ack_mode = False + server.log.debug('[noack mode enabled]') return REPLY_OK if feature in supported else REPLY_EMPTY @@ -735,6 +734,7 @@ def handle_z(subcmd: str) -> Reply: for packet in server.readpackets(): if server.ack_mode: server.send(REPLY_ACK, raw=True) + server.log.debug('[sent ack]') cmd, subcmd = packet[0], packet[1:] handler = handlers.get(f'{cmd:c}') @@ -949,7 +949,7 @@ def rle_decode(data: bytes) -> bytes: def __repl(m: 're.Match[bytes]') -> bytes: ch, _, times = m[0] - return bytes([ch] * times) + return bytes([ch] * (1 + times - 29)) return re.sub(br'.\*.', __repl, data, flags=re.DOTALL) From f72bfb87d14037d6c4697f0f064be240041ed876 Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 6 May 2022 17:48:40 +0300 Subject: [PATCH 349/406] Support arbitrary regs definition order --- qiling/debugger/gdb/xmlregs.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/qiling/debugger/gdb/xmlregs.py b/qiling/debugger/gdb/xmlregs.py index 63084128c..56d08dd28 100644 --- a/qiling/debugger/gdb/xmlregs.py +++ b/qiling/debugger/gdb/xmlregs.py @@ -87,17 +87,16 @@ def load_regsmap(archtype: QL_ARCH) -> Sequence[RegEntry]: QL_ARCH.MIPS : dict(**mips_regs_gpr, **mips_regs_fpu) }[archtype] - regmap = [] - pos = 0 - xmlpath = __get_xml_path(archtype) + regsinfo = sorted(__walk_xml_regs(*xmlpath)) - for regnum, name, bitsize in __walk_xml_regs(*xmlpath): - # regs indices might not be consecutive. - # extend regmap with null entries if needed - if len(regmap) < regnum + 1: - regmap.extend([(None, 0, 0)] * (regnum + 1 - len(regmap))) + # pre-allocate regmap and occupy it with null entries + last_regnum = regsinfo[-1][0] + regmap: Sequence[RegEntry] = [(None, 0, 0)] * (last_regnum + 1) + + pos = 0 + for regnum, name, bitsize in sorted(regsinfo): # reg value size in nibbles nibbles = bitsize // 4 From 2ee4ecc6d3d49cdd6a3c34f80dd7c4d6d79ef9c1 Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 6 May 2022 17:50:20 +0300 Subject: [PATCH 350/406] Add ARM D* registers --- qiling/arch/arm_const.py | 36 ++++++++++++++++++++++++++++++++++ qiling/debugger/gdb/xmlregs.py | 3 ++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/qiling/arch/arm_const.py b/qiling/arch/arm_const.py index d8def1220..e3dfaf1bb 100644 --- a/qiling/arch/arm_const.py +++ b/qiling/arch/arm_const.py @@ -40,3 +40,39 @@ "c13_c0_3": UC_ARM_REG_C13_C0_3, "fpexc": UC_ARM_REG_FPEXC } + +reg_vfp = { + "d0" : UC_ARM_REG_D0, + "d1" : UC_ARM_REG_D1, + "d2" : UC_ARM_REG_D2, + "d3" : UC_ARM_REG_D3, + "d4" : UC_ARM_REG_D4, + "d5" : UC_ARM_REG_D5, + "d6" : UC_ARM_REG_D6, + "d7" : UC_ARM_REG_D7, + "d8" : UC_ARM_REG_D8, + "d9" : UC_ARM_REG_D9, + "d10" : UC_ARM_REG_D10, + "d11" : UC_ARM_REG_D11, + "d12" : UC_ARM_REG_D12, + "d13" : UC_ARM_REG_D13, + "d14" : UC_ARM_REG_D14, + "d15" : UC_ARM_REG_D15, + "d16" : UC_ARM_REG_D16, + "d17" : UC_ARM_REG_D17, + "d18" : UC_ARM_REG_D18, + "d19" : UC_ARM_REG_D19, + "d20" : UC_ARM_REG_D20, + "d21" : UC_ARM_REG_D21, + "d22" : UC_ARM_REG_D22, + "d23" : UC_ARM_REG_D23, + "d24" : UC_ARM_REG_D24, + "d25" : UC_ARM_REG_D25, + "d26" : UC_ARM_REG_D26, + "d27" : UC_ARM_REG_D27, + "d28" : UC_ARM_REG_D28, + "d29" : UC_ARM_REG_D29, + "d30" : UC_ARM_REG_D30, + "d31" : UC_ARM_REG_D31, + "fpscr" : UC_ARM_REG_FPSCR +} diff --git a/qiling/debugger/gdb/xmlregs.py b/qiling/debugger/gdb/xmlregs.py index 56d08dd28..bafe03d6d 100644 --- a/qiling/debugger/gdb/xmlregs.py +++ b/qiling/debugger/gdb/xmlregs.py @@ -7,6 +7,7 @@ from pathlib import PurePath from qiling.arch.arm_const import reg_map as arm_regs +from qiling.arch.arm_const import reg_vfp as arm_regs_vfp from qiling.arch.arm64_const import reg_map as arm64_regs from qiling.arch.mips_const import reg_map as mips_regs_gpr from qiling.arch.mips_const import reg_map_fpu as mips_regs_fpu @@ -81,7 +82,7 @@ def load_regsmap(archtype: QL_ARCH) -> Sequence[RegEntry]: QL_ARCH.A8086 : dict(**x86_regs_16, **x86_regs_misc, **x86_regs_cr, **x86_regs_st), QL_ARCH.X86 : dict(**x86_regs_32, **x86_regs_misc, **x86_regs_cr, **x86_regs_st, **x86_regs_xmm), QL_ARCH.X8664 : dict(**x86_regs_64, **x86_regs_misc, **x86_regs_cr, **x86_regs_st, **x86_regs_xmm, **x86_regs_ymm), - QL_ARCH.ARM : arm_regs, + QL_ARCH.ARM : dict(**arm_regs, **arm_regs_vfp), QL_ARCH.CORTEX_M : arm_regs, QL_ARCH.ARM64 : arm64_regs, QL_ARCH.MIPS : dict(**mips_regs_gpr, **mips_regs_fpu) From e3f7db21479079edabbcb7f6099fd9b7bcaa8181 Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 6 May 2022 17:53:25 +0300 Subject: [PATCH 351/406] Better handling of EB reg values --- qiling/debugger/gdb/gdb.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/qiling/debugger/gdb/gdb.py b/qiling/debugger/gdb/gdb.py index e2f00bd8d..da1c3421a 100644 --- a/qiling/debugger/gdb/gdb.py +++ b/qiling/debugger/gdb/gdb.py @@ -110,6 +110,12 @@ def __hexstr(value: int, nibbles: int = 0) -> str: return value.to_bytes(length, byteorder).hex() + def __unkown_reg_value(nibbles: int) -> str: + """Encode the hex string for unknown regsiter value. + """ + + return 'x' * nibbles + def __get_reg_value(reg: Optional[int], pos: int, nibbles: int) -> str: # reg is either None or uc reg invalid if reg: @@ -118,13 +124,13 @@ def __get_reg_value(reg: Optional[int], pos: int, nibbles: int) -> str: hexstr = __hexstr(value, nibbles) else: - hexstr = 'x' * nibbles + hexstr = __unkown_reg_value(nibbles) return hexstr def __set_reg_value(reg: Optional[int], pos: int, nibbles: int, hexval: str) -> None: # reg is neither None nor uc reg invalid - if reg: + if reg and hexval != __unkown_reg_value(nibbles): assert len(hexval) == nibbles val = int(hexval, 16) @@ -243,14 +249,9 @@ def handle_G(subcmd: str) -> Reply: data = subcmd for reg, pos, nibbles in self.regsmap: - if reg: - hexval = data[pos : pos + nibbles] - - if hexval != 'x' * nibbles: - val = int(hexval, 16) + hexval = data[pos : pos + nibbles] - # TODO: should we swap val's endianess for big-endian targets? - self.ql.arch.regs.write(reg, val) + __set_reg_value(reg, pos, nibbles, hexval) return REPLY_OK From bf083906b40d54a9c3feec73982474660572be48 Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 6 May 2022 17:53:58 +0300 Subject: [PATCH 352/406] Update explanatory comment --- qiling/debugger/gdb/gdb.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/qiling/debugger/gdb/gdb.py b/qiling/debugger/gdb/gdb.py index da1c3421a..5592b157f 100644 --- a/qiling/debugger/gdb/gdb.py +++ b/qiling/debugger/gdb/gdb.py @@ -105,6 +105,9 @@ def run(self): killed = False def __hexstr(value: int, nibbles: int = 0) -> str: + """Encode a value into a hex string. + """ + length = (nibbles or self.ql.arch.bits // 4) // 2 byteorder = 'little' if self.ql.arch.endian == QL_ENDIAN.EL else 'big' @@ -233,14 +236,16 @@ def handle_c(subcmd: str) -> Reply: def handle_g(subcmd: str) -> Reply: - # TODO: several obsolete regs cause arm to have a gap just before cpsr. the nonexistant regs - # are represented as None entries just to make sure cpsr stays at index 25. however, it is - # not clear whether its value should follow a series of 'x' (for the non-existant regs) or - # not. the original code suggests not (perhaps because fpa is not specified as an xml feature..?) + # NOTE: in the past the 'g' reply packet for arm included the f0-f7 and fps registers between pc + # and cpsr, which placed cpsr at index (regnum) 25. as the f-registers became obsolete the cpsr + # index decreased. in order to maintain backward compatibility with older gdb versions, the gap + # between pc and cpsr that used to represent the f-registers (96 bits each + 32 bits for fps) is + # filled with unknown reg values. + # + # gdb clients that follow the xml definitions no longer need these placeholders, as registers + # indices are flexible and may be defined arbitrarily though xml. # - # non-existant regs are f0-f7 (96 bits each) and fps (32 bits). # see: ./xml/arm/arm-fpa.xml - # see: https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=gdb/arch/arm.h;h=fa589fd0582c0add627a068e6f4947a909c45e86;hb=HEAD#l127 return ''.join(__get_reg_value(*entry) for entry in self.regsmap) From c0a657fcc96369c2f49dd9f6f7429faa2b6b23c3 Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 6 May 2022 17:54:33 +0300 Subject: [PATCH 353/406] Remove unnecessary ARM F* regs --- qiling/arch/arm_const.py | 12 ------ .../gdb/xml/arm/arm-m-profile-with-fpa.xml | 39 ------------------- 2 files changed, 51 deletions(-) delete mode 100644 qiling/debugger/gdb/xml/arm/arm-m-profile-with-fpa.xml diff --git a/qiling/arch/arm_const.py b/qiling/arch/arm_const.py index e3dfaf1bb..2a268cdfd 100644 --- a/qiling/arch/arm_const.py +++ b/qiling/arch/arm_const.py @@ -23,18 +23,6 @@ "lr": UC_ARM_REG_LR, "pc": UC_ARM_REG_PC, - # CPSR needs to be at offset 25 for GDB, see https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=gdb/arch/arm.h;h=fa589fd0582c0add627a068e6f4947a909c45e86;hb=HEAD#l34 - # The fp registers inbetween have become obsolete - "f0": UC_ARM_REG_INVALID, - "f1": UC_ARM_REG_INVALID, - "f2": UC_ARM_REG_INVALID, - "f3": UC_ARM_REG_INVALID, - "f4": UC_ARM_REG_INVALID, - "f5": UC_ARM_REG_INVALID, - "f6": UC_ARM_REG_INVALID, - "f7": UC_ARM_REG_INVALID, - "fps": UC_ARM_REG_INVALID, - "cpsr": UC_ARM_REG_CPSR, "c1_c0_2": UC_ARM_REG_C1_C0_2, "c13_c0_3": UC_ARM_REG_C13_C0_3, diff --git a/qiling/debugger/gdb/xml/arm/arm-m-profile-with-fpa.xml b/qiling/debugger/gdb/xml/arm/arm-m-profile-with-fpa.xml deleted file mode 100644 index dd30abe87..000000000 --- a/qiling/debugger/gdb/xml/arm/arm-m-profile-with-fpa.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From de3afe94f58b6ed694a6a24d62288af36d42219a Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 6 May 2022 17:55:00 +0300 Subject: [PATCH 354/406] Reformat ARM xml files --- qiling/debugger/gdb/xml/arm/arm-core.xml | 48 +++++++++---------- qiling/debugger/gdb/xml/arm/arm-m-profile.xml | 3 +- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/qiling/debugger/gdb/xml/arm/arm-core.xml b/qiling/debugger/gdb/xml/arm/arm-core.xml index a2c0a65b2..5032c5039 100644 --- a/qiling/debugger/gdb/xml/arm/arm-core.xml +++ b/qiling/debugger/gdb/xml/arm/arm-core.xml @@ -1,31 +1,31 @@ - + Copying and distribution of this file, with or without modification, + are permitted in any medium without royalty provided the copyright + notice and this notice are preserved. --> - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - + + \ No newline at end of file diff --git a/qiling/debugger/gdb/xml/arm/arm-m-profile.xml b/qiling/debugger/gdb/xml/arm/arm-m-profile.xml index 5319d764e..f0584a206 100644 --- a/qiling/debugger/gdb/xml/arm/arm-m-profile.xml +++ b/qiling/debugger/gdb/xml/arm/arm-m-profile.xml @@ -7,7 +7,7 @@ - + @@ -23,5 +23,6 @@ + From 8549aa7d6d67b9950d819143de15acdbcb544afe Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 6 May 2022 18:36:07 +0300 Subject: [PATCH 355/406] Add aarch64 B*, D*, H*, Q*, S* and V* regs --- qiling/arch/arm64.py | 8 +- qiling/arch/arm64_const.py | 348 ++++++++++++++++++++++++++------- qiling/debugger/gdb/xmlregs.py | 3 +- 3 files changed, 288 insertions(+), 71 deletions(-) diff --git a/qiling/arch/arm64.py b/qiling/arch/arm64.py index 0367a8189..659496694 100644 --- a/qiling/arch/arm64.py +++ b/qiling/arch/arm64.py @@ -26,7 +26,13 @@ def uc(self) -> Uc: def regs(self) -> QlRegisterManager: regs_map = dict( **arm64_const.reg_map, - **arm64_const.reg_map_w + **arm64_const.reg_map_b, + **arm64_const.reg_map_d, + **arm64_const.reg_map_h, + **arm64_const.reg_map_q, + **arm64_const.reg_map_s, + **arm64_const.reg_map_w, + **arm64_const.reg_map_v ) pc_reg = 'pc' diff --git a/qiling/arch/arm64_const.py b/qiling/arch/arm64_const.py index c706d10c9..ee2b55167 100644 --- a/qiling/arch/arm64_const.py +++ b/qiling/arch/arm64_const.py @@ -1,79 +1,289 @@ #!/usr/bin/env python3 -# +# # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # from unicorn.arm64_const import * reg_map = { - "x0": UC_ARM64_REG_X0, - "x1": UC_ARM64_REG_X1, - "x2": UC_ARM64_REG_X2, - "x3": UC_ARM64_REG_X3, - "x4": UC_ARM64_REG_X4, - "x5": UC_ARM64_REG_X5, - "x6": UC_ARM64_REG_X6, - "x7": UC_ARM64_REG_X7, - "x8": UC_ARM64_REG_X8, - "x9": UC_ARM64_REG_X9, - "x10": UC_ARM64_REG_X10, - "x11": UC_ARM64_REG_X11, - "x12": UC_ARM64_REG_X12, - "x13": UC_ARM64_REG_X13, - "x14": UC_ARM64_REG_X14, - "x15": UC_ARM64_REG_X15, - "x16": UC_ARM64_REG_X16, - "x17": UC_ARM64_REG_X17, - "x18": UC_ARM64_REG_X18, - "x19": UC_ARM64_REG_X19, - "x20": UC_ARM64_REG_X20, - "x21": UC_ARM64_REG_X21, - "x22": UC_ARM64_REG_X22, - "x23": UC_ARM64_REG_X23, - "x24": UC_ARM64_REG_X24, - "x25": UC_ARM64_REG_X25, - "x26": UC_ARM64_REG_X26, - "x27": UC_ARM64_REG_X27, - "x28": UC_ARM64_REG_X28, - "x29": UC_ARM64_REG_X29, - "x30": UC_ARM64_REG_X30, - "sp": UC_ARM64_REG_SP, - "pc": UC_ARM64_REG_PC, - "lr": UC_ARM64_REG_LR, - "cpacr_el1": UC_ARM64_REG_CPACR_EL1, - "tpidr_el0": UC_ARM64_REG_TPIDR_EL0, + "x0": UC_ARM64_REG_X0, + "x1": UC_ARM64_REG_X1, + "x2": UC_ARM64_REG_X2, + "x3": UC_ARM64_REG_X3, + "x4": UC_ARM64_REG_X4, + "x5": UC_ARM64_REG_X5, + "x6": UC_ARM64_REG_X6, + "x7": UC_ARM64_REG_X7, + "x8": UC_ARM64_REG_X8, + "x9": UC_ARM64_REG_X9, + "x10": UC_ARM64_REG_X10, + "x11": UC_ARM64_REG_X11, + "x12": UC_ARM64_REG_X12, + "x13": UC_ARM64_REG_X13, + "x14": UC_ARM64_REG_X14, + "x15": UC_ARM64_REG_X15, + "x16": UC_ARM64_REG_X16, + "x17": UC_ARM64_REG_X17, + "x18": UC_ARM64_REG_X18, + "x19": UC_ARM64_REG_X19, + "x20": UC_ARM64_REG_X20, + "x21": UC_ARM64_REG_X21, + "x22": UC_ARM64_REG_X22, + "x23": UC_ARM64_REG_X23, + "x24": UC_ARM64_REG_X24, + "x25": UC_ARM64_REG_X25, + "x26": UC_ARM64_REG_X26, + "x27": UC_ARM64_REG_X27, + "x28": UC_ARM64_REG_X28, + "x29": UC_ARM64_REG_X29, + "x30": UC_ARM64_REG_X30, + "sp": UC_ARM64_REG_SP, + "pc": UC_ARM64_REG_PC, + "lr": UC_ARM64_REG_LR, + "cpacr_el1": UC_ARM64_REG_CPACR_EL1, + "tpidr_el0": UC_ARM64_REG_TPIDR_EL0 +} + +reg_map_b = { + "b0" : UC_ARM64_REG_B0, + "b1" : UC_ARM64_REG_B1, + "b2" : UC_ARM64_REG_B2, + "b3" : UC_ARM64_REG_B3, + "b4" : UC_ARM64_REG_B4, + "b5" : UC_ARM64_REG_B5, + "b6" : UC_ARM64_REG_B6, + "b7" : UC_ARM64_REG_B7, + "b8" : UC_ARM64_REG_B8, + "b9" : UC_ARM64_REG_B9, + "b10" : UC_ARM64_REG_B10, + "b11" : UC_ARM64_REG_B11, + "b12" : UC_ARM64_REG_B12, + "b13" : UC_ARM64_REG_B13, + "b14" : UC_ARM64_REG_B14, + "b15" : UC_ARM64_REG_B15, + "b16" : UC_ARM64_REG_B16, + "b17" : UC_ARM64_REG_B17, + "b18" : UC_ARM64_REG_B18, + "b19" : UC_ARM64_REG_B19, + "b20" : UC_ARM64_REG_B20, + "b21" : UC_ARM64_REG_B21, + "b22" : UC_ARM64_REG_B22, + "b23" : UC_ARM64_REG_B23, + "b24" : UC_ARM64_REG_B24, + "b25" : UC_ARM64_REG_B25, + "b26" : UC_ARM64_REG_B26, + "b27" : UC_ARM64_REG_B27, + "b28" : UC_ARM64_REG_B28, + "b29" : UC_ARM64_REG_B29, + "b30" : UC_ARM64_REG_B30, + "b31" : UC_ARM64_REG_B31 +} + +reg_map_d = { + "d0" : UC_ARM64_REG_D0, + "d1" : UC_ARM64_REG_D1, + "d2" : UC_ARM64_REG_D2, + "d3" : UC_ARM64_REG_D3, + "d4" : UC_ARM64_REG_D4, + "d5" : UC_ARM64_REG_D5, + "d6" : UC_ARM64_REG_D6, + "d7" : UC_ARM64_REG_D7, + "d8" : UC_ARM64_REG_D8, + "d9" : UC_ARM64_REG_D9, + "d10" : UC_ARM64_REG_D10, + "d11" : UC_ARM64_REG_D11, + "d12" : UC_ARM64_REG_D12, + "d13" : UC_ARM64_REG_D13, + "d14" : UC_ARM64_REG_D14, + "d15" : UC_ARM64_REG_D15, + "d16" : UC_ARM64_REG_D16, + "d17" : UC_ARM64_REG_D17, + "d18" : UC_ARM64_REG_D18, + "d19" : UC_ARM64_REG_D19, + "d20" : UC_ARM64_REG_D20, + "d21" : UC_ARM64_REG_D21, + "d22" : UC_ARM64_REG_D22, + "d23" : UC_ARM64_REG_D23, + "d24" : UC_ARM64_REG_D24, + "d25" : UC_ARM64_REG_D25, + "d26" : UC_ARM64_REG_D26, + "d27" : UC_ARM64_REG_D27, + "d28" : UC_ARM64_REG_D28, + "d29" : UC_ARM64_REG_D29, + "d30" : UC_ARM64_REG_D30, + "d31" : UC_ARM64_REG_D31 +} + +reg_map_h = { + "h0" : UC_ARM64_REG_H0, + "h1" : UC_ARM64_REG_H1, + "h2" : UC_ARM64_REG_H2, + "h3" : UC_ARM64_REG_H3, + "h4" : UC_ARM64_REG_H4, + "h5" : UC_ARM64_REG_H5, + "h6" : UC_ARM64_REG_H6, + "h7" : UC_ARM64_REG_H7, + "h8" : UC_ARM64_REG_H8, + "h9" : UC_ARM64_REG_H9, + "h10" : UC_ARM64_REG_H10, + "h11" : UC_ARM64_REG_H11, + "h12" : UC_ARM64_REG_H12, + "h13" : UC_ARM64_REG_H13, + "h14" : UC_ARM64_REG_H14, + "h15" : UC_ARM64_REG_H15, + "h16" : UC_ARM64_REG_H16, + "h17" : UC_ARM64_REG_H17, + "h18" : UC_ARM64_REG_H18, + "h19" : UC_ARM64_REG_H19, + "h20" : UC_ARM64_REG_H20, + "h21" : UC_ARM64_REG_H21, + "h22" : UC_ARM64_REG_H22, + "h23" : UC_ARM64_REG_H23, + "h24" : UC_ARM64_REG_H24, + "h25" : UC_ARM64_REG_H25, + "h26" : UC_ARM64_REG_H26, + "h27" : UC_ARM64_REG_H27, + "h28" : UC_ARM64_REG_H28, + "h29" : UC_ARM64_REG_H29, + "h30" : UC_ARM64_REG_H30, + "h31" : UC_ARM64_REG_H31 +} + +reg_map_q = { + "q0" : UC_ARM64_REG_Q0, + "q1" : UC_ARM64_REG_Q1, + "q2" : UC_ARM64_REG_Q2, + "q3" : UC_ARM64_REG_Q3, + "q4" : UC_ARM64_REG_Q4, + "q5" : UC_ARM64_REG_Q5, + "q6" : UC_ARM64_REG_Q6, + "q7" : UC_ARM64_REG_Q7, + "q8" : UC_ARM64_REG_Q8, + "q9" : UC_ARM64_REG_Q9, + "q10" : UC_ARM64_REG_Q10, + "q11" : UC_ARM64_REG_Q11, + "q12" : UC_ARM64_REG_Q12, + "q13" : UC_ARM64_REG_Q13, + "q14" : UC_ARM64_REG_Q14, + "q15" : UC_ARM64_REG_Q15, + "q16" : UC_ARM64_REG_Q16, + "q17" : UC_ARM64_REG_Q17, + "q18" : UC_ARM64_REG_Q18, + "q19" : UC_ARM64_REG_Q19, + "q20" : UC_ARM64_REG_Q20, + "q21" : UC_ARM64_REG_Q21, + "q22" : UC_ARM64_REG_Q22, + "q23" : UC_ARM64_REG_Q23, + "q24" : UC_ARM64_REG_Q24, + "q25" : UC_ARM64_REG_Q25, + "q26" : UC_ARM64_REG_Q26, + "q27" : UC_ARM64_REG_Q27, + "q28" : UC_ARM64_REG_Q28, + "q29" : UC_ARM64_REG_Q29, + "q30" : UC_ARM64_REG_Q30, + "q31" : UC_ARM64_REG_Q31 +} + +reg_map_s = { + "s0" : UC_ARM64_REG_S0, + "s1" : UC_ARM64_REG_S1, + "s2" : UC_ARM64_REG_S2, + "s3" : UC_ARM64_REG_S3, + "s4" : UC_ARM64_REG_S4, + "s5" : UC_ARM64_REG_S5, + "s6" : UC_ARM64_REG_S6, + "s7" : UC_ARM64_REG_S7, + "s8" : UC_ARM64_REG_S8, + "s9" : UC_ARM64_REG_S9, + "s10" : UC_ARM64_REG_S10, + "s11" : UC_ARM64_REG_S11, + "s12" : UC_ARM64_REG_S12, + "s13" : UC_ARM64_REG_S13, + "s14" : UC_ARM64_REG_S14, + "s15" : UC_ARM64_REG_S15, + "s16" : UC_ARM64_REG_S16, + "s17" : UC_ARM64_REG_S17, + "s18" : UC_ARM64_REG_S18, + "s19" : UC_ARM64_REG_S19, + "s20" : UC_ARM64_REG_S20, + "s21" : UC_ARM64_REG_S21, + "s22" : UC_ARM64_REG_S22, + "s23" : UC_ARM64_REG_S23, + "s24" : UC_ARM64_REG_S24, + "s25" : UC_ARM64_REG_S25, + "s26" : UC_ARM64_REG_S26, + "s27" : UC_ARM64_REG_S27, + "s28" : UC_ARM64_REG_S28, + "s29" : UC_ARM64_REG_S29, + "s30" : UC_ARM64_REG_S30, + "s31" : UC_ARM64_REG_S31 } reg_map_w = { - "w0" : UC_ARM64_REG_W0, - "w1" : UC_ARM64_REG_W1, - "w2" : UC_ARM64_REG_W2, - "w3" : UC_ARM64_REG_W3, - "w4" : UC_ARM64_REG_W4, - "w5" : UC_ARM64_REG_W5, - "w6" : UC_ARM64_REG_W6, - "w7" : UC_ARM64_REG_W7, - "w8" : UC_ARM64_REG_W8, - "w9" : UC_ARM64_REG_W9, - "w10" : UC_ARM64_REG_W10, - "w11" : UC_ARM64_REG_W11, - "w12" : UC_ARM64_REG_W12, - "w13" : UC_ARM64_REG_W13, - "w14" : UC_ARM64_REG_W14, - "w15" : UC_ARM64_REG_W15, - "w16" : UC_ARM64_REG_W16, - "w17" : UC_ARM64_REG_W17, - "w18" : UC_ARM64_REG_W18, - "w19" : UC_ARM64_REG_W19, - "w20" : UC_ARM64_REG_W20, - "w21" : UC_ARM64_REG_W21, - "w22" : UC_ARM64_REG_W22, - "w23" : UC_ARM64_REG_W23, - "w24" : UC_ARM64_REG_W24, - "w25" : UC_ARM64_REG_W25, - "w26" : UC_ARM64_REG_W26, - "w27" : UC_ARM64_REG_W27, - "w28" : UC_ARM64_REG_W28, - "w29" : UC_ARM64_REG_W29, - "w30" : UC_ARM64_REG_W30, -} \ No newline at end of file + "w0" : UC_ARM64_REG_W0, + "w1" : UC_ARM64_REG_W1, + "w2" : UC_ARM64_REG_W2, + "w3" : UC_ARM64_REG_W3, + "w4" : UC_ARM64_REG_W4, + "w5" : UC_ARM64_REG_W5, + "w6" : UC_ARM64_REG_W6, + "w7" : UC_ARM64_REG_W7, + "w8" : UC_ARM64_REG_W8, + "w9" : UC_ARM64_REG_W9, + "w10" : UC_ARM64_REG_W10, + "w11" : UC_ARM64_REG_W11, + "w12" : UC_ARM64_REG_W12, + "w13" : UC_ARM64_REG_W13, + "w14" : UC_ARM64_REG_W14, + "w15" : UC_ARM64_REG_W15, + "w16" : UC_ARM64_REG_W16, + "w17" : UC_ARM64_REG_W17, + "w18" : UC_ARM64_REG_W18, + "w19" : UC_ARM64_REG_W19, + "w20" : UC_ARM64_REG_W20, + "w21" : UC_ARM64_REG_W21, + "w22" : UC_ARM64_REG_W22, + "w23" : UC_ARM64_REG_W23, + "w24" : UC_ARM64_REG_W24, + "w25" : UC_ARM64_REG_W25, + "w26" : UC_ARM64_REG_W26, + "w27" : UC_ARM64_REG_W27, + "w28" : UC_ARM64_REG_W28, + "w29" : UC_ARM64_REG_W29, + "w30" : UC_ARM64_REG_W30 +} + +reg_map_v = { + "v0" : UC_ARM64_REG_V0, + "v1" : UC_ARM64_REG_V1, + "v2" : UC_ARM64_REG_V2, + "v3" : UC_ARM64_REG_V3, + "v4" : UC_ARM64_REG_V4, + "v5" : UC_ARM64_REG_V5, + "v6" : UC_ARM64_REG_V6, + "v7" : UC_ARM64_REG_V7, + "v8" : UC_ARM64_REG_V8, + "v9" : UC_ARM64_REG_V9, + "v10" : UC_ARM64_REG_V10, + "v11" : UC_ARM64_REG_V11, + "v12" : UC_ARM64_REG_V12, + "v13" : UC_ARM64_REG_V13, + "v14" : UC_ARM64_REG_V14, + "v15" : UC_ARM64_REG_V15, + "v16" : UC_ARM64_REG_V16, + "v17" : UC_ARM64_REG_V17, + "v18" : UC_ARM64_REG_V18, + "v19" : UC_ARM64_REG_V19, + "v20" : UC_ARM64_REG_V20, + "v21" : UC_ARM64_REG_V21, + "v22" : UC_ARM64_REG_V22, + "v23" : UC_ARM64_REG_V23, + "v24" : UC_ARM64_REG_V24, + "v25" : UC_ARM64_REG_V25, + "v26" : UC_ARM64_REG_V26, + "v27" : UC_ARM64_REG_V27, + "v28" : UC_ARM64_REG_V28, + "v29" : UC_ARM64_REG_V29, + "v30" : UC_ARM64_REG_V30, + "v31" : UC_ARM64_REG_V31 +} diff --git a/qiling/debugger/gdb/xmlregs.py b/qiling/debugger/gdb/xmlregs.py index bafe03d6d..bf2b77cfa 100644 --- a/qiling/debugger/gdb/xmlregs.py +++ b/qiling/debugger/gdb/xmlregs.py @@ -9,6 +9,7 @@ from qiling.arch.arm_const import reg_map as arm_regs from qiling.arch.arm_const import reg_vfp as arm_regs_vfp from qiling.arch.arm64_const import reg_map as arm64_regs +from qiling.arch.arm64_const import reg_map_v as arm64_regs_v from qiling.arch.mips_const import reg_map as mips_regs_gpr from qiling.arch.mips_const import reg_map_fpu as mips_regs_fpu from qiling.arch.x86_const import reg_map_16 as x86_regs_16 @@ -84,7 +85,7 @@ def load_regsmap(archtype: QL_ARCH) -> Sequence[RegEntry]: QL_ARCH.X8664 : dict(**x86_regs_64, **x86_regs_misc, **x86_regs_cr, **x86_regs_st, **x86_regs_xmm, **x86_regs_ymm), QL_ARCH.ARM : dict(**arm_regs, **arm_regs_vfp), QL_ARCH.CORTEX_M : arm_regs, - QL_ARCH.ARM64 : arm64_regs, + QL_ARCH.ARM64 : dict(**arm64_regs, **arm64_regs_v), QL_ARCH.MIPS : dict(**mips_regs_gpr, **mips_regs_fpu) }[archtype] From 0ac80ef1d4c93b897edb75d13ba44668434508ac Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 6 May 2022 18:36:21 +0300 Subject: [PATCH 356/406] Reformat ARM64 xml files --- .../debugger/gdb/xml/arm64/aarch64-core.xml | 110 ++++++------ qiling/debugger/gdb/xml/arm64/aarch64-fpu.xml | 165 +++++++++--------- qiling/debugger/gdb/xml/arm64/target.xml | 9 +- 3 files changed, 146 insertions(+), 138 deletions(-) diff --git a/qiling/debugger/gdb/xml/arm64/aarch64-core.xml b/qiling/debugger/gdb/xml/arm64/aarch64-core.xml index 5b2706e03..599d92b3e 100644 --- a/qiling/debugger/gdb/xml/arm64/aarch64-core.xml +++ b/qiling/debugger/gdb/xml/arm64/aarch64-core.xml @@ -1,67 +1,67 @@ + Copying and distribution of this file, with or without modification, + are permitted in any medium without royalty provided the copyright + notice and this notice are preserved. --> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - * - * - * - * - * - * - * - * - * + + + + + + + + + + - * - * + + - * - * - * - * - - + + + + + + \ No newline at end of file diff --git a/qiling/debugger/gdb/xml/arm64/aarch64-fpu.xml b/qiling/debugger/gdb/xml/arm64/aarch64-fpu.xml index 0b37e15fa..0cdf73e5e 100644 --- a/qiling/debugger/gdb/xml/arm64/aarch64-fpu.xml +++ b/qiling/debugger/gdb/xml/arm64/aarch64-fpu.xml @@ -1,86 +1,93 @@ + Copying and distribution of this file, with or without modification, + are permitted in any medium without royalty provided the copyright + notice and this notice are preserved. --> - - - - - - - - - - - - - - * - * - * - - - * - * - * - - - * - * - - - * - * - - - * - * - - - * - * - * - * - * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/qiling/debugger/gdb/xml/arm64/target.xml b/qiling/debugger/gdb/xml/arm64/target.xml index b91fd952d..c741efa61 100644 --- a/qiling/debugger/gdb/xml/arm64/target.xml +++ b/qiling/debugger/gdb/xml/arm64/target.xml @@ -1,14 +1,15 @@ + Copying and distribution of this file, with or without modification, + are permitted in any medium without royalty provided the copyright + notice and this notice are preserved. --> aarch64 + \ No newline at end of file From a03f6ce28d0ac5dc56a475bf99bbd7529c2543b4 Mon Sep 17 00:00:00 2001 From: nullableVoidPtr <30564701+nullableVoidPtr@users.noreply.github.com> Date: Sat, 7 May 2022 17:46:42 +0800 Subject: [PATCH 357/406] Remove nonexistent syspage.bin from MANIFEST --- MANIFEST.in | 1 - 1 file changed, 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index ead0863c9..3a680e281 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,4 +2,3 @@ recursive-include qiling/debugger/gdb/xml * recursive-include qiling/extensions/windows_sdk/defs * recursive-include qiling/profiles * include qiling/os/uefi/guids.csv -include qiling/os/qnx/syspage.bin From d3336291fe8df40be468b25bec46d1eb80a4b9e0 Mon Sep 17 00:00:00 2001 From: nullableVoidPtr <30564701+nullableVoidPtr@users.noreply.github.com> Date: Sat, 7 May 2022 19:17:30 +0800 Subject: [PATCH 358/406] Use pkgutil instead of absolute paths --- MANIFEST.in | 1 + qiling/arch/evm/analysis/signatures.py | 7 +++---- qiling/debugger/gdb/gdb.py | 17 +++++++++-------- qiling/os/uefi/__init__.py | 11 ++++------- qiling/utils.py | 17 +++++++---------- 5 files changed, 24 insertions(+), 29 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 3a680e281..295f846d0 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,3 +2,4 @@ recursive-include qiling/debugger/gdb/xml * recursive-include qiling/extensions/windows_sdk/defs * recursive-include qiling/profiles * include qiling/os/uefi/guids.csv +include qiling/arch/evm/analysis/signatures.json diff --git a/qiling/arch/evm/analysis/signatures.py b/qiling/arch/evm/analysis/signatures.py index aebe55f0d..321260fe9 100644 --- a/qiling/arch/evm/analysis/signatures.py +++ b/qiling/arch/evm/analysis/signatures.py @@ -1,4 +1,4 @@ -import os +import pkgutil import re import logging import json @@ -92,9 +92,8 @@ def analysis_func_sign(insns:list, engine_num=1): class signatures_engine_1: @staticmethod def find_signature(sign): - path = os.path.split(os.path.realpath(__file__))[0] + '/signatures.json' - with open(path) as data_file: - data = json.load(data_file) + data = pkgutil.get_data(__package__, 'signatures.json').decode() + data = json.load(data) list_name = [name for name, hexa in data.items() if hexa == sign] diff --git a/qiling/debugger/gdb/gdb.py b/qiling/debugger/gdb/gdb.py index 8b3416663..6bccf5b0c 100644 --- a/qiling/debugger/gdb/gdb.py +++ b/qiling/debugger/gdb/gdb.py @@ -7,6 +7,7 @@ # documentation: according to https://sourceware.org/gdb/current/onlinedocs/gdb/Remote-Protocol.html#Remote-Protocol import struct, os, socket +import pkgutil from binascii import unhexlify from typing import Iterator, Literal @@ -481,15 +482,15 @@ def handle_q(subcmd): else: self.send("PacketSize=47ff;QPassSignals+;QProgramSignals+;QStartupWithShell+;QEnvironmentHexEncoded+;QEnvironmentReset+;QEnvironmentUnset+;QSetWorkingDir+;QCatchSyscalls+;qXfer:libraries-svr4:read+;augmented-libraries-svr4-read+;qXfer:auxv:read+;qXfer:siginfo:read+;qXfer:siginfo:write+;qXfer:features:read+;QStartNoAckMode+;qXfer:osdata:read+;multiprocess+;fork-events+;vfork-events+;exec-events+;QNonStop+;QDisableRandomization+;qXfer:threads:read+;ConditionalTracepoints+;TraceStateVariables+;TracepointSource+;DisconnectedTracing+;FastTracepoints+;StaticTracepoints+;InstallInTrace+;qXfer:statictrace:read+;qXfer:traceframe-info:read+;EnableDisableTracepoints+;QTBuffer:size+;tracenz+;ConditionalBreakpoints+;BreakpointCommands+;QAgent+;Qbtrace:bts+;Qbtrace-conf:bts:size+;Qbtrace:pt+;Qbtrace-conf:pt:size+;Qbtrace:off+;qXfer:btrace:read+;qXfer:btrace-conf:read+;swbreak+;hwbreak+;qXfer:exec-file:read+;vContSupported+;QThreadEvents+;no-resumed+") elif subcmd.startswith('Xfer:features:read'): - xfercmd_file = subcmd.split(':')[3] - xfercmd_abspath = os.path.dirname(os.path.abspath(__file__)) - xml_folder = self.ql.arch.type.name.lower() - xfercmd_file = os.path.join(xfercmd_abspath,"xml",xml_folder, xfercmd_file) - - if os.path.exists(xfercmd_file) and self.ql.os.type is not QL_OS.WINDOWS: - with open(xfercmd_file, 'r') as f: - file_contents = f.read() + if self.ql.os.type is not QL_OS.WINDOWS: + try: + xfercmd_file = subcmd.split(':')[3] + xml_folder = self.ql.arch.type.name.lower() + file_contents = pkgutil.get_data(__package__, f"xml/{xml_folder}/{xfercmd_file}").decode() self.send("l%s" % file_contents) + except: + self.ql.log.info("gdb> Platform is not supported by xml or xml file not found: %s\n" % (xfercmd_file)) + self.send("l") else: self.ql.log.info("gdb> Platform is not supported by xml or xml file not found: %s\n" % (xfercmd_file)) self.send("l") diff --git a/qiling/os/uefi/__init__.py b/qiling/os/uefi/__init__.py index 5b8faeab6..aa3902544 100644 --- a/qiling/os/uefi/__init__.py +++ b/qiling/os/uefi/__init__.py @@ -1,17 +1,14 @@ import csv from typing import Mapping -from os import path +import pkgutil def __init_guids_db() -> Mapping[str, str]: """Initialize GUIDs dictionary from a local database. """ - csv_path = path.dirname(path.abspath(__file__)) - csv_path = path.join(csv_path, 'guids.csv') + guids_file = pkgutil.get_data(__package__, 'guids.csv').decode() + guids_reader = csv.reader(guids_file.splitlines()) - with open(csv_path) as guids_file: - guids_reader = csv.reader(guids_file) - - return dict(tuple(entry) for entry in guids_reader) + return dict(tuple(entry) for entry in guids_reader) guids_db = __init_guids_db() diff --git a/qiling/utils.py b/qiling/utils.py index 68607fe60..b3180ed1a 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -9,7 +9,7 @@ """ from functools import partial -import importlib, os +import importlib, pkgutil, os from configparser import ConfigParser from types import ModuleType @@ -415,19 +415,16 @@ def profile_setup(ostype: QL_OS, filename: Optional[str]): config = {} else: - qiling_home = os.path.dirname(os.path.abspath(__file__)) - os_profile = os.path.join(qiling_home, 'profiles', f'{ostype.name.lower()}.ql') - - profiles = [os_profile] - - if filename: - profiles.append(filename) - # patch 'getint' to convert integers of all bases int_converter = partial(int, base=0) config = ConfigParser(converters={'int': int_converter}) - config.read(profiles) + + os_profile = pkgutil.get_data(__package__, f'profiles/{ostype.name.lower()}.ql').decode() + config.read_string(os_profile) + + if filename: + config.read(filename) return config From 84798b19455f24c3cebce114d877e5b748ccecd9 Mon Sep 17 00:00:00 2001 From: nullableVoidPtr <30564701+nullableVoidPtr@users.noreply.github.com> Date: Sat, 7 May 2022 19:45:07 +0800 Subject: [PATCH 359/406] Fix for nonexistent profiles --- qiling/utils.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/qiling/utils.py b/qiling/utils.py index b3180ed1a..09bb68a32 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -420,8 +420,11 @@ def profile_setup(ostype: QL_OS, filename: Optional[str]): config = ConfigParser(converters={'int': int_converter}) - os_profile = pkgutil.get_data(__package__, f'profiles/{ostype.name.lower()}.ql').decode() - config.read_string(os_profile) + try: + os_profile = pkgutil.get_data(__package__, f'profiles/{ostype.name.lower()}.ql').decode() + config.read_string(os_profile) + except FileNotFoundError as e: + pass if filename: config.read(filename) From d166a984cdef163c3809847afb48f472a1ddcf42 Mon Sep 17 00:00:00 2001 From: nullableVoidPtr <30564701+nullableVoidPtr@users.noreply.github.com> Date: Sat, 7 May 2022 19:52:48 +0800 Subject: [PATCH 360/406] Run giteesync only on main repository --- .github/workflows/giteesync.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/giteesync.yml b/.github/workflows/giteesync.yml index 4e7104ba4..dbf797497 100644 --- a/.github/workflows/giteesync.yml +++ b/.github/workflows/giteesync.yml @@ -5,6 +5,7 @@ jobs: deploy: runs-on: ubuntu-latest + if: github.repository_owner == 'qilingframework' steps: - uses: actions/checkout@v2 with: From d051174810e716936f27831144c2a43ceaf9844b Mon Sep 17 00:00:00 2001 From: nullableVoidPtr <30564701+nullableVoidPtr@users.noreply.github.com> Date: Sat, 7 May 2022 20:14:00 +0800 Subject: [PATCH 361/406] Replace MANIFEST.in with package_data --- MANIFEST.in | 5 ----- setup.py | 7 ++++++- 2 files changed, 6 insertions(+), 6 deletions(-) delete mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 295f846d0..000000000 --- a/MANIFEST.in +++ /dev/null @@ -1,5 +0,0 @@ -recursive-include qiling/debugger/gdb/xml * -recursive-include qiling/extensions/windows_sdk/defs * -recursive-include qiling/profiles * -include qiling/os/uefi/guids.csv -include qiling/arch/evm/analysis/signatures.json diff --git a/setup.py b/setup.py index 5496da6cd..3987cc252 100644 --- a/setup.py +++ b/setup.py @@ -84,7 +84,12 @@ packages=find_packages(), scripts=['qltool'], - include_package_data=True, + package_data={ + 'qiling': ['profiles/*.ql'], + 'qiling.debugger.gdb': ['xml/*/*'], + 'qiling.os.uefi': ['guids.csv'], + 'qiling.arch.evm.analysis': ['signatures.json'] + }, install_requires=requirements, extras_require=extras, ) From 719b98a61c6a996916f7982b09aac8514944616a Mon Sep 17 00:00:00 2001 From: nullableVoidPtr <30564701+nullableVoidPtr@users.noreply.github.com> Date: Sat, 7 May 2022 20:22:39 +0800 Subject: [PATCH 362/406] Add to credits --- CREDITS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CREDITS.md b/CREDITS.md index b4e32331e..9ea9f0edf 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -54,6 +54,7 @@ - danielmoos - sigeryang - bet4it +- nullableVoidPtr #### Legacy Core Developers From d05446539c0c0a89683f11ac9b38cef4975f4042 Mon Sep 17 00:00:00 2001 From: elicn Date: Sat, 7 May 2022 23:31:57 +0300 Subject: [PATCH 363/406] More generic less hardcoded values --- qiling/debugger/gdb/gdb.py | 53 +++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/qiling/debugger/gdb/gdb.py b/qiling/debugger/gdb/gdb.py index 5592b157f..6f5cc7a67 100644 --- a/qiling/debugger/gdb/gdb.py +++ b/qiling/debugger/gdb/gdb.py @@ -155,30 +155,21 @@ def handle_exclaim(subcmd: str) -> Reply: def handle_qmark(subcmd: str) -> Reply: - from unicorn.x86_const import UC_X86_REG_EIP, UC_X86_REG_ESP - from unicorn.x86_const import UC_X86_REG_RIP, UC_X86_REG_RSP - from unicorn.arm_const import UC_ARM_REG_PC, UC_ARM_REG_SP - from unicorn.arm64_const import UC_ARM64_REG_PC, UC_ARM64_REG_SP - from unicorn.mips_const import UC_MIPS_REG_PC, UC_MIPS_REG_SP - - # X86 : T0505:00000000;04:c0d3ffff;08:2021fdf7;thread:p15c6.15c6;core:6 - # X8664 : T0506:0000000000000000;07:b0e2ffffff7f0000;10:0001fdf7ff7f0000;thread:p15a2.15a2;core:6; - # MIPS32_EL : T051d:00e7ff7f;25:40ccfc77; - # MIPS32_EB : T051d:7fff6dc0;25:77fc4880;thread:28fa;core:0; - # ARM64 : T051d:0000000000000000;1f:80f6ffffffff0000;20:c02cfdb7ffff0000;thread:p1f9.1f9;core:0; - # ARM : T050b:00000000;0d:e0f6ffbe;0f:8079fdb6; - - response = { - QL_ARCH.X86 : ( 0x05, UC_X86_REG_ESP, UC_X86_REG_EIP ), - QL_ARCH.X8664 : ( 0x06, UC_X86_REG_RSP, UC_X86_REG_RIP ), - QL_ARCH.ARM : ( 0x0b, UC_ARM_REG_SP, UC_ARM_REG_PC ), - QL_ARCH.ARM64 : ( 0x1d, UC_ARM64_REG_SP, UC_ARM64_REG_PC ), - QL_ARCH.MIPS : ( 0x1d, UC_MIPS_REG_SP, UC_MIPS_REG_PC ), - QL_ARCH.A8086 : ( 0x05, UC_X86_REG_ESP, UC_X86_REG_EIP ), - QL_ARCH.CORTEX_M : ( 0x0b, UC_ARM_REG_SP, UC_ARM_REG_PC ) - } - - idhex, sp_reg, pc_reg = response[self.ql.arch.type] + from unicorn.x86_const import UC_X86_REG_EBP + from unicorn.x86_const import UC_X86_REG_RBP + from unicorn.arm_const import UC_ARM_REG_R11 + from unicorn.arm64_const import UC_ARM64_REG_X29 + from unicorn.mips_const import UC_MIPS_REG_29 + + arch_uc_bp = { + QL_ARCH.X86 : UC_X86_REG_EBP, + QL_ARCH.X8664 : UC_X86_REG_RBP, + QL_ARCH.ARM : UC_ARM_REG_R11, + QL_ARCH.ARM64 : UC_ARM64_REG_X29, + QL_ARCH.MIPS : UC_MIPS_REG_29, + QL_ARCH.A8086 : UC_X86_REG_EBP, + QL_ARCH.CORTEX_M : UC_ARM_REG_R11 + }[self.ql.arch.type] def __get_reg_idx(ucreg: int) -> int: """Get the index of a uc reg whithin the regsmap array. @@ -188,16 +179,20 @@ def __get_reg_idx(ucreg: int) -> int: return next((i for i, (regnum, _, _) in enumerate(self.regsmap) if regnum == ucreg), -1) - sp_idx = __get_reg_idx(sp_reg) - pc_idx = __get_reg_idx(pc_reg) + # FIXME: a8086 should use 'esp' and 'eip' here instead of 'sp' and 'ip' set by its arch instance + bp_idx = __get_reg_idx(arch_uc_bp) + sp_idx = __get_reg_idx(self.ql.arch.regs.uc_sp) + pc_idx = __get_reg_idx(self.ql.arch.regs.uc_pc) + bp_val = __get_reg_value(*self.regsmap[bp_idx]) sp_val = __get_reg_value(*self.regsmap[sp_idx]) pc_val = __get_reg_value(*self.regsmap[pc_idx]) - zfill = __hexstr(0) + bp_info = f'{bp_idx:02x}:{bp_val};' + sp_info = f'{sp_idx:02x}:{sp_val};' + pc_info = f'{pc_idx:02x}:{pc_val};' - info = '' if self.ql.arch.type == QL_ARCH.MIPS else f':{zfill};{sp_idx:02x}' - return f'T{SIGTRAP:02x}{idhex:02x}{info}:{sp_val};{pc_idx:02x}:{pc_val};' + return f'T{SIGTRAP:02x}{"" if self.ql.arch.type == QL_ARCH.MIPS else bp_info}{sp_info}{pc_info}' def handle_c(subcmd: str) -> Reply: From b3b55110a2e8f9efa1b43f02a0b78579c276b2b5 Mon Sep 17 00:00:00 2001 From: elicn Date: Sat, 7 May 2022 23:32:25 +0300 Subject: [PATCH 364/406] Fix transmission of large files --- qiling/debugger/gdb/gdb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiling/debugger/gdb/gdb.py b/qiling/debugger/gdb/gdb.py index 6f5cc7a67..25dcf545e 100644 --- a/qiling/debugger/gdb/gdb.py +++ b/qiling/debugger/gdb/gdb.py @@ -460,7 +460,7 @@ def handle_q(subcmd: str) -> Reply: f.seek(offset, os.SEEK_SET) content = f.read(length) - return f'l{content}' + return f'{"l" if len(content) < length else "m"}{content}' elif feature == 'threads' and op == 'read': if not self.ql.baremetal and hasattr(self.ql.os, 'pid'): From c375b572b77cbfcc539e77bbd318c48b29a34ff2 Mon Sep 17 00:00:00 2001 From: elicn Date: Sat, 7 May 2022 23:32:44 +0300 Subject: [PATCH 365/406] Fix a8086 regs mapping for gdb --- qiling/debugger/gdb/xmlregs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiling/debugger/gdb/xmlregs.py b/qiling/debugger/gdb/xmlregs.py index bf2b77cfa..f872ee0fb 100644 --- a/qiling/debugger/gdb/xmlregs.py +++ b/qiling/debugger/gdb/xmlregs.py @@ -80,7 +80,7 @@ def load_regsmap(archtype: QL_ARCH) -> Sequence[RegEntry]: # retreive the relevant set of registers; their order of appearance is not # important as it is determined by the info read from the xml files ucregs: Mapping[str, int] = { - QL_ARCH.A8086 : dict(**x86_regs_16, **x86_regs_misc, **x86_regs_cr, **x86_regs_st), + QL_ARCH.A8086 : dict(**x86_regs_32, **x86_regs_misc, **x86_regs_cr, **x86_regs_st), QL_ARCH.X86 : dict(**x86_regs_32, **x86_regs_misc, **x86_regs_cr, **x86_regs_st, **x86_regs_xmm), QL_ARCH.X8664 : dict(**x86_regs_64, **x86_regs_misc, **x86_regs_cr, **x86_regs_st, **x86_regs_xmm, **x86_regs_ymm), QL_ARCH.ARM : dict(**arm_regs, **arm_regs_vfp), From 9bfbba1fdf637e7cb9b9fd894263613f65d60946 Mon Sep 17 00:00:00 2001 From: elicn Date: Sat, 7 May 2022 23:33:14 +0300 Subject: [PATCH 366/406] Remove method that is no longer used --- qiling/arch/register.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/qiling/arch/register.py b/qiling/arch/register.py index fab8f3aff..368864f6f 100644 --- a/qiling/arch/register.py +++ b/qiling/arch/register.py @@ -102,12 +102,6 @@ def arch_pc(self, value: int) -> None: return self.uc.reg_write(self.uc_pc, value) - @property - def arch_pc_name(self) -> str: - """Get the architectural program counter register name. - """ - - return next(k for k, v in self.register_mapping.items() if v == self.uc_pc) @property def arch_sp(self) -> int: From 0a06dd4906abbc28a82567eddcd6300e0c56d8d6 Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 8 May 2022 12:45:50 +0300 Subject: [PATCH 367/406] Simplify qmark_handler further --- qiling/debugger/gdb/gdb.py | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/qiling/debugger/gdb/gdb.py b/qiling/debugger/gdb/gdb.py index 25dcf545e..5f3287fbe 100644 --- a/qiling/debugger/gdb/gdb.py +++ b/qiling/debugger/gdb/gdb.py @@ -155,18 +155,23 @@ def handle_exclaim(subcmd: str) -> Reply: def handle_qmark(subcmd: str) -> Reply: + """Request status. + + @see: https://sourceware.org/gdb/current/onlinedocs/gdb/Stop-Reply-Packets.html + """ + from unicorn.x86_const import UC_X86_REG_EBP from unicorn.x86_const import UC_X86_REG_RBP from unicorn.arm_const import UC_ARM_REG_R11 from unicorn.arm64_const import UC_ARM64_REG_X29 - from unicorn.mips_const import UC_MIPS_REG_29 + from unicorn.mips_const import UC_MIPS_REG_INVALID arch_uc_bp = { QL_ARCH.X86 : UC_X86_REG_EBP, QL_ARCH.X8664 : UC_X86_REG_RBP, QL_ARCH.ARM : UC_ARM_REG_R11, QL_ARCH.ARM64 : UC_ARM64_REG_X29, - QL_ARCH.MIPS : UC_MIPS_REG_29, + QL_ARCH.MIPS : UC_MIPS_REG_INVALID, # skipped QL_ARCH.A8086 : UC_X86_REG_EBP, QL_ARCH.CORTEX_M : UC_ARM_REG_R11 }[self.ql.arch.type] @@ -179,20 +184,23 @@ def __get_reg_idx(ucreg: int) -> int: return next((i for i, (regnum, _, _) in enumerate(self.regsmap) if regnum == ucreg), -1) - # FIXME: a8086 should use 'esp' and 'eip' here instead of 'sp' and 'ip' set by its arch instance - bp_idx = __get_reg_idx(arch_uc_bp) - sp_idx = __get_reg_idx(self.ql.arch.regs.uc_sp) - pc_idx = __get_reg_idx(self.ql.arch.regs.uc_pc) + def __get_reg_info(ucreg: int) -> str: + """Retrieve register info and pack it as a pair. + """ + + regnum = __get_reg_idx(ucreg) + hexval = __get_reg_value(*self.regsmap[regnum]) - bp_val = __get_reg_value(*self.regsmap[bp_idx]) - sp_val = __get_reg_value(*self.regsmap[sp_idx]) - pc_val = __get_reg_value(*self.regsmap[pc_idx]) + return f'{regnum:02x}:{hexval};' - bp_info = f'{bp_idx:02x}:{bp_val};' - sp_info = f'{sp_idx:02x}:{sp_val};' - pc_info = f'{pc_idx:02x}:{pc_val};' + # mips targets skip this reg info pair + bp_info = '' if self.ql.arch.type == QL_ARCH.MIPS else __get_reg_info(arch_uc_bp) + + # FIXME: a8086 should use 'esp' and 'eip' here instead of 'sp' and 'ip' set by its arch instance + sp_info = __get_reg_info(self.ql.arch.regs.uc_sp) + pc_info = __get_reg_info(self.ql.arch.regs.uc_pc) - return f'T{SIGTRAP:02x}{"" if self.ql.arch.type == QL_ARCH.MIPS else bp_info}{sp_info}{pc_info}' + return f'T{SIGTRAP:02x}{bp_info}{sp_info}{pc_info}' def handle_c(subcmd: str) -> Reply: From f82ca0f918131b828f217973490f35010e331ba5 Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 8 May 2022 12:46:41 +0300 Subject: [PATCH 368/406] Use proper value endian --- qiling/debugger/gdb/gdb.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/qiling/debugger/gdb/gdb.py b/qiling/debugger/gdb/gdb.py index 5f3287fbe..b20f8b4be 100644 --- a/qiling/debugger/gdb/gdb.py +++ b/qiling/debugger/gdb/gdb.py @@ -541,13 +541,15 @@ def handle_q(subcmd: str) -> Reply: return 'l' elif query == 'TStatus': + tsize = __hexstr(0x500000) + fields = ( 'T0', 'tnotrun:0', 'tframes:0', 'tcreated:0', - 'tfree:500000', - 'tsize:500000', + f'tfree:{tsize}', + f'tsize:{tsize}', 'circular:0', 'disconn:0', 'starttime:0', From 945f32d3f90be88317b8ea94f9afe6b12e53c99e Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 8 May 2022 12:47:05 +0300 Subject: [PATCH 369/406] Document uc stepping bug --- qiling/debugger/gdb/gdb.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/qiling/debugger/gdb/gdb.py b/qiling/debugger/gdb/gdb.py index b20f8b4be..a8aeceb18 100644 --- a/qiling/debugger/gdb/gdb.py +++ b/qiling/debugger/gdb/gdb.py @@ -656,6 +656,12 @@ def handle_s(subcmd: str) -> Reply: """Perform a single step. """ + # BUG: a known unicorn caching issue causes it to emulate more + # steps than requestes. until that issue is fixed, single stepping + # is essentially broken. + # + # @see: https://github.com/unicorn-engine/unicorn/issues/1606 + self.gdb.resume_emu(steps=1) return f'S{SIGTRAP:02x}' From 9c6140de0b151afb1e7c4e80b4053474e16d0f43 Mon Sep 17 00:00:00 2001 From: nullableVoidPtr <30564701+nullableVoidPtr@users.noreply.github.com> Date: Sun, 8 May 2022 18:44:05 +0800 Subject: [PATCH 370/406] pkgutil -> inspect + Path As per elicn's comments. TODO: If Qiling allows 3.9 standard Libs, move everything to importlib.resources.files --- qiling/arch/evm/analysis/signatures.py | 8 +++++--- qiling/debugger/gdb/gdb.py | 20 ++++++++++---------- qiling/os/uefi/__init__.py | 11 +++++++---- qiling/utils.py | 21 +++++++++++---------- 4 files changed, 33 insertions(+), 27 deletions(-) diff --git a/qiling/arch/evm/analysis/signatures.py b/qiling/arch/evm/analysis/signatures.py index 321260fe9..e7eed5092 100644 --- a/qiling/arch/evm/analysis/signatures.py +++ b/qiling/arch/evm/analysis/signatures.py @@ -1,4 +1,5 @@ -import pkgutil +import inspect +from pathlib import Path import re import logging import json @@ -92,8 +93,9 @@ def analysis_func_sign(insns:list, engine_num=1): class signatures_engine_1: @staticmethod def find_signature(sign): - data = pkgutil.get_data(__package__, 'signatures.json').decode() - data = json.load(data) + path = Path(inspect.getfile(inspect.getframe())).parent / 'signatures.json' + with path.open('r') as data_file: + data = json.load(data_file) list_name = [name for name, hexa in data.items() if hexa == sign] diff --git a/qiling/debugger/gdb/gdb.py b/qiling/debugger/gdb/gdb.py index 6bccf5b0c..768e2e0a8 100644 --- a/qiling/debugger/gdb/gdb.py +++ b/qiling/debugger/gdb/gdb.py @@ -7,7 +7,8 @@ # documentation: according to https://sourceware.org/gdb/current/onlinedocs/gdb/Remote-Protocol.html#Remote-Protocol import struct, os, socket -import pkgutil +import inspect +from pathlib import Path from binascii import unhexlify from typing import Iterator, Literal @@ -482,15 +483,14 @@ def handle_q(subcmd): else: self.send("PacketSize=47ff;QPassSignals+;QProgramSignals+;QStartupWithShell+;QEnvironmentHexEncoded+;QEnvironmentReset+;QEnvironmentUnset+;QSetWorkingDir+;QCatchSyscalls+;qXfer:libraries-svr4:read+;augmented-libraries-svr4-read+;qXfer:auxv:read+;qXfer:siginfo:read+;qXfer:siginfo:write+;qXfer:features:read+;QStartNoAckMode+;qXfer:osdata:read+;multiprocess+;fork-events+;vfork-events+;exec-events+;QNonStop+;QDisableRandomization+;qXfer:threads:read+;ConditionalTracepoints+;TraceStateVariables+;TracepointSource+;DisconnectedTracing+;FastTracepoints+;StaticTracepoints+;InstallInTrace+;qXfer:statictrace:read+;qXfer:traceframe-info:read+;EnableDisableTracepoints+;QTBuffer:size+;tracenz+;ConditionalBreakpoints+;BreakpointCommands+;QAgent+;Qbtrace:bts+;Qbtrace-conf:bts:size+;Qbtrace:pt+;Qbtrace-conf:pt:size+;Qbtrace:off+;qXfer:btrace:read+;qXfer:btrace-conf:read+;swbreak+;hwbreak+;qXfer:exec-file:read+;vContSupported+;QThreadEvents+;no-resumed+") elif subcmd.startswith('Xfer:features:read'): - if self.ql.os.type is not QL_OS.WINDOWS: - try: - xfercmd_file = subcmd.split(':')[3] - xml_folder = self.ql.arch.type.name.lower() - file_contents = pkgutil.get_data(__package__, f"xml/{xml_folder}/{xfercmd_file}").decode() - self.send("l%s" % file_contents) - except: - self.ql.log.info("gdb> Platform is not supported by xml or xml file not found: %s\n" % (xfercmd_file)) - self.send("l") + xfercmd_file = subcmd.split(':')[3] + xfercmd_abspath = Path(inspect.getfile(inspect.currentframe())).parent + xml_folder = self.ql.arch.type.name.lower() + xfercmd_file = xfercmd_abspath / 'xml' / xml_folder / xfercmd_file + + if xfercmd_file.exists() and self.ql.os.type is not QL_OS.WINDOWS: + with xfercmd_file.open('r') as f: + file_contents = f.read() else: self.ql.log.info("gdb> Platform is not supported by xml or xml file not found: %s\n" % (xfercmd_file)) self.send("l") diff --git a/qiling/os/uefi/__init__.py b/qiling/os/uefi/__init__.py index aa3902544..0e68ec3c5 100644 --- a/qiling/os/uefi/__init__.py +++ b/qiling/os/uefi/__init__.py @@ -1,14 +1,17 @@ import csv from typing import Mapping -import pkgutil +import inspect +from pathlib import Path def __init_guids_db() -> Mapping[str, str]: """Initialize GUIDs dictionary from a local database. """ - guids_file = pkgutil.get_data(__package__, 'guids.csv').decode() - guids_reader = csv.reader(guids_file.splitlines()) + csv_path = Path(inspect.getfile(inspect.currentframe())).parent / 'guids.csv' - return dict(tuple(entry) for entry in guids_reader) + with csv_path.open('r') as guids_file: + guids_reader = csv.reader(guids_file) + + return dict(tuple(entry) for entry in guids_reader) guids_db = __init_guids_db() diff --git a/qiling/utils.py b/qiling/utils.py index 09bb68a32..dc3703052 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -9,7 +9,8 @@ """ from functools import partial -import importlib, pkgutil, os +from pathlib import Path +import importlib, inspect, os from configparser import ConfigParser from types import ModuleType @@ -415,19 +416,19 @@ def profile_setup(ostype: QL_OS, filename: Optional[str]): config = {} else: + qiling_home = Path(inspect.getfile(inspect.currentframe())).parent + os_profile = qiling_home / 'profiles' / f'{ostype.name.lower()}.ql' + + profiles = [os_profile] + + if filename: + profiles.append(filename) + # patch 'getint' to convert integers of all bases int_converter = partial(int, base=0) config = ConfigParser(converters={'int': int_converter}) - - try: - os_profile = pkgutil.get_data(__package__, f'profiles/{ostype.name.lower()}.ql').decode() - config.read_string(os_profile) - except FileNotFoundError as e: - pass - - if filename: - config.read(filename) + config.read(profiles) return config From 7dc2e9713a362c1812d10ee1a73dee765961d3d1 Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 12 May 2022 21:55:46 +0300 Subject: [PATCH 371/406] Remove method that is no longer used --- qiling/os/memory.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/qiling/os/memory.py b/qiling/os/memory.py index 2354f74f9..42fdf2391 100644 --- a/qiling/os/memory.py +++ b/qiling/os/memory.py @@ -444,25 +444,6 @@ def is_mapped(self, addr: int, size: int) -> bool: return not self.is_available(addr, size) - def is_free(self, address, size): - ''' - The main function of is_free first must fufull is_mapped condition. - then, check for is the mapped range empty, either fill with 0xFF or 0x00 - Returns true if mapped range is empty else return Flase - If not not mapped, map it and return true - ''' - if self.is_mapped(address, size) == True: - address_end = (address + size) - while address < address_end: - mem_read = self.ql.mem.read(address, 0x1) - if (mem_read[0] != 0x00) and (mem_read[0] != 0xFF): - return False - address += 1 - return True - else: - return True - - def find_free_space(self, size: int, minaddr: int = None, maxaddr: int = None, align: int = None) -> int: """Locate an unallocated memory that is large enough to contain a range in size of `size` and based at `minaddr`. From 6f3ccb1f2fdad930e4d6fa6a12e469ad14ee68e4 Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 12 May 2022 22:19:38 +0300 Subject: [PATCH 372/406] Change is_mapped semantics --- qiling/arch/x86_utils.py | 8 ++++--- qiling/loader/elf.py | 2 +- qiling/os/memory.py | 42 ++++++++++++++++++++++++++++++------ qiling/os/windows/windows.py | 14 +++++++----- 4 files changed, 51 insertions(+), 15 deletions(-) diff --git a/qiling/arch/x86_utils.py b/qiling/arch/x86_utils.py index 39573c12d..de6e24f24 100644 --- a/qiling/arch/x86_utils.py +++ b/qiling/arch/x86_utils.py @@ -4,7 +4,7 @@ from qiling import Qiling from qiling.arch.x86 import QlArchIntel from qiling.arch.x86_const import * -from qiling.exception import QlGDTError +from qiling.exception import QlGDTError, QlMemoryMappedError from qiling.os.memory import QlMemoryManager class GDTArray: @@ -50,8 +50,10 @@ class GDTManager: def __init__(self, ql: Qiling, base = QL_X86_GDT_ADDR, limit = QL_X86_GDT_LIMIT, num_entries = 16): ql.log.debug(f'Mapping GDT at {base:#x} with limit {limit:#x}') - if not ql.mem.is_mapped(base, limit): - ql.mem.map(base, limit, info="[GDT]") + if not ql.mem.is_available(base, limit): + raise QlMemoryMappedError('cannot map GDT, memory location is taken') + + ql.mem.map(base, limit, info="[GDT]") # setup GDT by writing to GDTR ql.arch.regs.write(UC_X86_REG_GDTR, (0, base, limit, 0x0)) diff --git a/qiling/loader/elf.py b/qiling/loader/elf.py index a1be0ef63..ada002294 100644 --- a/qiling/loader/elf.py +++ b/qiling/loader/elf.py @@ -368,7 +368,7 @@ def __push_str(top: int, s: str) -> int: _vsyscall_addr = int(self.profile.get('vsyscall_address'), 0) _vsyscall_size = int(self.profile.get('vsyscall_size'), 0) - if not self.ql.mem.is_mapped(_vsyscall_addr, _vsyscall_size): + if self.ql.mem.is_available(_vsyscall_addr, _vsyscall_size): # initialize with int3 instructions then insert syscall entry # each syscall should be 1KiB away self.ql.mem.map(_vsyscall_addr, _vsyscall_size, info="[vsyscall]") diff --git a/qiling/os/memory.py b/qiling/os/memory.py index 42fdf2391..19c1ea921 100644 --- a/qiling/os/memory.py +++ b/qiling/os/memory.py @@ -280,7 +280,7 @@ def restore(self, mem_dict): self.ql.log.debug(f'restoring memory range: {lbound:#08x} {ubound:#08x} {label}') size = ubound - lbound - if not self.is_mapped(lbound, size): + if self.is_available(lbound, size): self.ql.log.debug(f'mapping {lbound:#08x} {ubound:#08x}, mapsize = {size:#x}') self.map(lbound, size, perms, label) @@ -420,9 +420,34 @@ def unmap_all(self): for begin, end, _ in self.ql.uc.mem_regions(): self.unmap(begin, end - begin + 1) + def __mapped_regions(self) -> Iterator[Tuple[int, int]]: + """Iterate through all mapped memory regions, consolidating adjacent regions + together to a continuous one. Protection bits and labels are ignored. + """ + + if not self.map_info: + return + + iter_memmap = iter(self.map_info) + + p_lbound, p_ubound, _, _, _ = next(iter_memmap) + + # map_info is assumed to contain non-overlapping regions sorted by lbound + for lbound, ubound, _, _, _ in iter_memmap: + if lbound == p_ubound: + p_ubound = ubound + else: + yield (p_lbound, p_ubound) + + p_lbound = lbound + p_ubound = ubound + + yield (p_lbound, p_ubound) + + def is_available(self, addr: int, size: int) -> bool: """Query whether the memory range starting at `addr` and is of length of `size` bytes - can be allocated. + is available for allocation. Returns: True if it can be allocated, False otherwise """ @@ -433,16 +458,21 @@ def is_available(self, addr: int, size: int) -> bool: end = addr + size # make sure neither begin nor end are enclosed within a mapped range, or entirely enclosing one - return not any((lbound <= begin < ubound) or (lbound < end <= ubound) or (begin <= lbound < ubound <= end) for lbound, ubound, _, _, _ in self.map_info) + return not any((lbound <= begin < ubound) or (lbound < end <= ubound) or (begin <= lbound < ubound <= end) for lbound, ubound in self.__mapped_regions()) def is_mapped(self, addr: int, size: int) -> bool: """Query whether the memory range starting at `addr` and is of length of `size` bytes - is mapped, either partially or entirely. + is fully mapped. - Returns: True if any part of the specified memory range is taken, False otherwise + Returns: True if the specified memory range is taken fully, False otherwise """ - return not self.is_available(addr, size) + assert size > 0, 'expected a positive size value' + + begin = addr + end = addr + size + + return any((lbound <= begin < end <= ubound) for lbound, ubound in self.__mapped_regions()) def find_free_space(self, size: int, minaddr: int = None, maxaddr: int = None, align: int = None) -> int: """Locate an unallocated memory that is large enough to contain a range in size of diff --git a/qiling/os/windows/windows.py b/qiling/os/windows/windows.py index e9d2d0b0b..db9b29b21 100644 --- a/qiling/os/windows/windows.py +++ b/qiling/os/windows/windows.py @@ -13,7 +13,7 @@ from qiling.arch.x86_utils import GDTManager, SegmentManager86, SegmentManager64 from qiling.cc import intel from qiling.const import QL_ARCH, QL_OS, QL_INTERCEPT -from qiling.exception import QlErrorSyscallError, QlErrorSyscallNotFound +from qiling.exception import QlErrorSyscallError, QlErrorSyscallNotFound, QlMemoryMappedError from qiling.os.fcall import QlFunctionCall from qiling.os.memory import QlMemoryHeap from qiling.os.os import QlOs @@ -116,11 +116,15 @@ def setupGDT(self): segm.setup_fs(FS_SEGMENT_ADDR, FS_SEGMENT_SIZE) segm.setup_gs(GS_SEGMENT_ADDR, GS_SEGMENT_SIZE) - if not self.ql.mem.is_mapped(FS_SEGMENT_ADDR, FS_SEGMENT_SIZE): - self.ql.mem.map(FS_SEGMENT_ADDR, FS_SEGMENT_SIZE, info='[FS]') + if not self.ql.mem.is_available(FS_SEGMENT_ADDR, FS_SEGMENT_SIZE): + raise QlMemoryMappedError('cannot map FS segment, memory location is taken') - if not self.ql.mem.is_mapped(GS_SEGMENT_ADDR, GS_SEGMENT_SIZE): - self.ql.mem.map(GS_SEGMENT_ADDR, GS_SEGMENT_SIZE, info='[GS]') + self.ql.mem.map(FS_SEGMENT_ADDR, FS_SEGMENT_SIZE, info='[FS]') + + if not self.ql.mem.is_available(GS_SEGMENT_ADDR, GS_SEGMENT_SIZE): + raise QlMemoryMappedError('cannot map GS segment, memory location is taken') + + self.ql.mem.map(GS_SEGMENT_ADDR, GS_SEGMENT_SIZE, info='[GS]') def __setup_components(self): From 835d88944e0128d19b8e1f64e873c5e2dd1e19f7 Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 12 May 2022 22:22:36 +0300 Subject: [PATCH 373/406] Rename and change how show_mapinfo is used --- qiling/debugger/qdb/qdb.py | 4 +++- qiling/os/memory.py | 29 +++++++++++++++++------------ qiling/os/os.py | 5 +++-- qiling/os/uefi/uefi.py | 4 +++- 4 files changed, 26 insertions(+), 16 deletions(-) diff --git a/qiling/debugger/qdb/qdb.py b/qiling/debugger/qdb/qdb.py index ef9fc0758..d8810ff29 100644 --- a/qiling/debugger/qdb/qdb.py +++ b/qiling/debugger/qdb/qdb.py @@ -353,7 +353,9 @@ def do_show(self, *args) -> None: show some runtime information """ - self.ql.mem.show_mapinfo() + for info_line in self.ql.mem.get_formatted_mapinfo(): + self.ql.log.info(info_line) + qdb_print(QDB_MSG.INFO, f"Breakpoints: {[hex(addr) for addr in self.bp_list.keys()]}") if self.rr: qdb_print(QDB_MSG.INFO, f"Snapshots: {len([st for st in self.rr.layers if isinstance(st, self.rr.DiffedState)])}") diff --git a/qiling/os/memory.py b/qiling/os/memory.py index 19c1ea921..c72ce35fc 100644 --- a/qiling/os/memory.py +++ b/qiling/os/memory.py @@ -149,12 +149,12 @@ def change_mapinfo(self, mem_s: int, mem_e: int, mem_p: int = None, mem_info: st if mem_info is not None: self.map_info[info_idx] = (tmp_map_info[0], tmp_map_info[1], tmp_map_info[2], mem_info, tmp_map_info[4]) - def get_mapinfo(self) -> Sequence[Tuple[int, int, str, str, Optional[str]]]: + def get_mapinfo(self) -> Sequence[Tuple[int, int, str, str, str]]: """Get memory map info. Returns: A sequence of 5-tuples representing the memory map entries. Each tuple contains range start, range end, permissions, range label and path of - containing image (or None if not contained by any image) + containing image (or an empty string if not contained by any image) """ def __perms_mapping(ps: int) -> str: @@ -166,21 +166,21 @@ def __perms_mapping(ps: int) -> str: return ''.join(val if idx & ps else '-' for idx, val in perms_d.items()) - def __process(lbound: int, ubound: int, perms: int, label: str, is_mmio: bool) -> Tuple[int, int, str, str, Optional[str]]: + def __process(lbound: int, ubound: int, perms: int, label: str, is_mmio: bool) -> Tuple[int, int, str, str, str]: perms_str = __perms_mapping(perms) if hasattr(self.ql, 'loader'): image = self.ql.loader.find_containing_image(lbound) - container = image.path if image and not is_mmio else None + container = image.path if image and not is_mmio else '' else: - container = None + container = '' return (lbound, ubound, perms_str, label, container) return tuple(__process(*entry) for entry in self.map_info) - def show_mapinfo(self): - """Emit memory map info in a nicely formatted table. + def get_formatted_mapinfo(self) -> Sequence[str]: + """Get memory map info in a nicely formatted table. """ mapinfo = self.get_mapinfo() @@ -192,12 +192,17 @@ def show_mapinfo(self): len_addr = max(grouped[0]) len_label = max(grouped[1]) - # emit title row - self.ql.log.info(f'{"Start":{len_addr}s} {"End":{len_addr}s} {"Perm":5s} {"Label":{len_label}s} {"Image"}') + # pre-allocate table + table = [''] * (len(mapinfo) + 1) - # emit table rows - for lbound, ubound, perms, label, container in mapinfo: - self.ql.log.info(f'{lbound:0{len_addr}x} - {ubound:0{len_addr}x} {perms:5s} {label:{len_label}s} {container or ""}') + # add title row + table[0] = f'{"Start":{len_addr}s} {"End":{len_addr}s} {"Perm":5s} {"Label":{len_label}s} {"Image"}' + + # add table rows + for i, (lbound, ubound, perms, label, container) in enumerate(mapinfo, 1): + table[i] = f'{lbound:0{len_addr}x} - {ubound:0{len_addr}x} {perms:5s} {label:{len_label}s} {container}' + + return table # TODO: relying on the label string is risky; find a more reliable method def get_lib_base(self, filename: str) -> Optional[int]: diff --git a/qiling/os/os.py b/qiling/os/os.py index 0b4a3c3a5..93c4c012e 100644 --- a/qiling/os/os.py +++ b/qiling/os/os.py @@ -252,5 +252,6 @@ def emu_error(self): finally: self.ql.log.error(f'PC = {pc:#0{self.ql.arch.pointersize * 2 + 2}x}{pc_info}\n') - self.ql.log.info(f'Memory map:') - self.ql.mem.show_mapinfo() + self.ql.log.error(f'Memory map:') + for info_line in self.ql.mem.get_formatted_mapinfo(): + self.ql.log.error(info_line) diff --git a/qiling/os/uefi/uefi.py b/qiling/os/uefi/uefi.py index fe55cc0f3..67163e90b 100644 --- a/qiling/os/uefi/uefi.py +++ b/qiling/os/uefi/uefi.py @@ -197,7 +197,9 @@ def emu_error(self): self.emit_stack() self.ql.log.error(f'Memory map:') - self.ql.mem.show_mapinfo() + for info_line in self.ql.mem.get_formatted_mapinfo(): + self.ql.log.error(info_line) + def set_api(self, target: str, handler: Callable, intercept: QL_INTERCEPT = QL_INTERCEPT.CALL): super().set_api(f'hook_{target}', handler, intercept) From 0d37928aa6d33ab7cb527c9991956aefc610b1c0 Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 12 May 2022 22:29:15 +0300 Subject: [PATCH 374/406] Support plain and regex memory searches --- qiling/os/memory.py | 12 ++++++++---- tests/test_elf.py | 16 ++++++++-------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/qiling/os/memory.py b/qiling/os/memory.py index c72ce35fc..27ad97d76 100644 --- a/qiling/os/memory.py +++ b/qiling/os/memory.py @@ -4,7 +4,7 @@ # import os, re -from typing import Any, Callable, List, Mapping, MutableSequence, Optional, Sequence, Tuple +from typing import Any, Callable, Iterator, List, Mapping, MutableSequence, Optional, Pattern, Sequence, Tuple, Union from unicorn import UC_PROT_NONE, UC_PROT_READ, UC_PROT_WRITE, UC_PROT_EXEC, UC_PROT_ALL @@ -371,18 +371,18 @@ def write_ptr(self, addr: int, value: int, size: int=None) -> None: self.write(addr, __pack(value)) - def search(self, needle: bytes, begin: int = None, end: int = None) -> Sequence[int]: + def search(self, needle: Union[bytes, Pattern[bytes]], begin: int = None, end: int = None) -> Sequence[int]: """Search for a sequence of bytes in memory. Args: - needle: bytes sequence to look for + needle: bytes sequence or regex pattern to look for begin: search starting address (or None to start at lowest avaiable address) end: search ending address (or None to end at highest avaiable address) Returns: addresses of all matches """ - # if starting point not set, search from the first mapped region + # if starting point not set, search from the first mapped region if begin is None: begin = self.map_info[0][0] @@ -396,6 +396,10 @@ def search(self, needle: bytes, begin: int = None, end: int = None) -> Sequence[ ranges = [(max(begin, lbound), min(ubound, end)) for lbound, ubound, _, _, is_mmio in self.map_info if not (end < lbound or ubound < begin or is_mmio)] results = [] + # if needle is a bytes sequence use it verbatim, not as a pattern + if type(needle) is bytes: + needle = re.escape(needle) + for lbound, ubound in ranges: haystack = self.read(lbound, ubound - lbound) local_results = (match.start(0) + lbound for match in re.finditer(needle, haystack)) diff --git a/tests/test_elf.py b/tests/test_elf.py index 8ce4712a4..96ae3bd6d 100644 --- a/tests/test_elf.py +++ b/tests/test_elf.py @@ -1119,28 +1119,28 @@ def test_memory_search(self): ql.mem.write(0x1FFB, b"\x1f\x00\x07\x53\x03\x06\x07\x1f\x1b") # Needle not in haystack - self.assertEqual([], ql.mem.search(re.escape(b"\x3a\x01\x0b\x03\x53\x29\x1b\x1c\x04\x0d\x11"))) + self.assertEqual([], ql.mem.search(b"\x3a\x01\x0b\x03\x53\x29\x1b\x1c\x04\x0d\x11")) # Needle appears several times in haystack - self.assertEqual([0x1000 + 24, 0x2000 + 38, 0x3000 + 24], ql.mem.search(re.escape(b"\x4f\x53\x06\x0d\x1e\x0d\x1a"))) + self.assertEqual([0x1000 + 24, 0x2000 + 38, 0x3000 + 24], ql.mem.search(b"\x4f\x53\x06\x0d\x1e\x0d\x1a")) # Needle inside haystack - self.assertEqual([0x1000 + 13], ql.mem.search(re.escape(b"\x0f\x01\x1e\x0d\x53\x11\x07\x1d\x53\x1d\x18"), begin=0x1000 + 10, end=0x1000 + 30)) + self.assertEqual([0x1000 + 13], ql.mem.search(b"\x0f\x01\x1e\x0d\x53\x11\x07\x1d\x53\x1d\x18", begin=0x1000 + 10, end=0x1000 + 30)) # Needle before haystack - self.assertEqual([], ql.mem.search(re.escape(b"\x04\x0d\x1c\x53\x11\x07\x1d\x53\x0c\x07\x1f\x06"), begin=0x1337)) + self.assertEqual([], ql.mem.search(b"\x04\x0d\x1c\x53\x11\x07\x1d\x53\x0c\x07\x1f\x06", begin=0x1337)) # Needle after haystack - self.assertEqual([], ql.mem.search(re.escape(b"\x1b\x09\x11\x53\x0f\x07\x07\x0c\x0a\x11\x0d"), end=0x3000 + 13)) + self.assertEqual([], ql.mem.search(b"\x1b\x09\x11\x53\x0f\x07\x07\x0c\x0a\x11\x0d", end=0x3000 + 13)) # Needle exactly inside haystack - self.assertEqual([0x2000 + 13], ql.mem.search(re.escape(b"\x1a\x1d\x06\x53\x09\x1a\x07\x1d\x06\x0c"), begin=0x2000 + 13, end=0x2000 + 23)) + self.assertEqual([0x2000 + 13], ql.mem.search(b"\x1a\x1d\x06\x53\x09\x1a\x07\x1d\x06\x0c", begin=0x2000 + 13, end=0x2000 + 23)) # Needle 'tears' two mapped regions - self.assertEqual([], ql.mem.search(re.escape(b"\x1f\x00\x07\x53\x03\x06\x07\x1f\x1b"), begin=0x1F00, end=0x200F)) + self.assertEqual([], ql.mem.search(b"\x1f\x00\x07\x53\x03\x06\x07\x1f\x1b", begin=0x1F00, end=0x200F)) # Needle is a regex - self.assertEqual([0x1000 + 11, 0x2000 + 11, 0x3000 + 43], ql.mem.search(b"\x09\x53(\x0f|\x1a|\x04)[^\x0d]")) + self.assertEqual([0x1000 + 11, 0x2000 + 11, 0x3000 + 43], ql.mem.search(re.compile(b"\x09\x53(\x0f|\x1a|\x04)[^\x0d]"))) del ql From ab06b4bcaa53da0bb35b38abc6ab0373819eb890 Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 12 May 2022 22:33:53 +0300 Subject: [PATCH 375/406] Improving typing annotations --- qiling/core.py | 6 +++--- qiling/os/memory.py | 28 ++++++++++++++-------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/qiling/core.py b/qiling/core.py index 684d8e74d..0c5cb929e 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -45,7 +45,7 @@ def __init__( filter = None, stop: QL_STOP = QL_STOP.NONE, *, - endian: QL_ENDIAN = None, + endian: Optional[QL_ENDIAN] = None, thumb: bool = False, libcache: bool = False ): @@ -414,8 +414,8 @@ def debug_stop(self) -> bool: return self._debug_stop @debug_stop.setter - def debug_stop(self, ds): - self._debug_stop = ds + def debug_stop(self, enabled: bool): + self._debug_stop = enabled @property def debugger(self) -> bool: diff --git a/qiling/os/memory.py b/qiling/os/memory.py index 27ad97d76..228d07d0a 100644 --- a/qiling/os/memory.py +++ b/qiling/os/memory.py @@ -127,8 +127,8 @@ def del_mapinfo(self, mem_s: int, mem_e: int): self.map_info = tmp_map_info - def change_mapinfo(self, mem_s: int, mem_e: int, mem_p: int = None, mem_info: str = None): - tmp_map_info: MapInfoEntry = None + def change_mapinfo(self, mem_s: int, mem_e: int, mem_p: Optional[int] = None, mem_info: Optional[str] = None): + tmp_map_info: Optional[MapInfoEntry] = None info_idx: int = None for idx, map_info in enumerate(self.map_info): @@ -215,7 +215,7 @@ def get_lib_base(self, filename: str) -> Optional[int]: return next((lbound for lbound, info in stripped if os.path.basename(info) == filename), None) - def align(self, value: int, alignment: int = None) -> int: + def align(self, value: int, alignment: Optional[int] = None) -> int: """Align a value down to the specified alignment boundary. If `value` is already aligned, the same value is returned. Commonly used to determine the base address of the enclosing page. @@ -237,7 +237,7 @@ def align(self, value: int, alignment: int = None) -> int: # round down to nearest alignment return value & ~(alignment - 1) - def align_up(self, value: int, alignment: int = None) -> int: + def align_up(self, value: int, alignment: Optional[int] = None) -> int: """Align a value up to the specified alignment boundary. If `value` is already aligned, the same value is returned. Commonly used to determine the end address of the enlosing page. @@ -310,13 +310,13 @@ def read(self, addr: int, size: int) -> bytearray: return self.ql.uc.mem_read(addr, size) - def read_ptr(self, addr: int, size: int=None) -> int: + def read_ptr(self, addr: int, size: int = 0) -> int: """Read an integer value from a memory address. Bytes read will be unpacked using emulated architecture properties. Args: addr: memory address to read - size: pointer size (in bytes): either 1, 2, 4, 8, or None for arch native size + size: pointer size (in bytes): either 1, 2, 4, 8, or 0 for arch native size Returns: integer value stored at the specified memory address """ @@ -346,14 +346,14 @@ def write(self, addr: int, data: bytes) -> None: self.ql.uc.mem_write(addr, data) - def write_ptr(self, addr: int, value: int, size: int=None) -> None: + def write_ptr(self, addr: int, value: int, size: int = 0) -> None: """Write an integer value to a memory address. Bytes written will be packed using emulated architecture properties. Args: addr: target memory address value: integer value to write - size: pointer size (in bytes): either 1, 2, 4, 8, or None for arch native size + size: pointer size (in bytes): either 1, 2, 4, 8, or 0 for arch native size """ if not size: @@ -371,7 +371,7 @@ def write_ptr(self, addr: int, value: int, size: int=None) -> None: self.write(addr, __pack(value)) - def search(self, needle: Union[bytes, Pattern[bytes]], begin: int = None, end: int = None) -> Sequence[int]: + def search(self, needle: Union[bytes, Pattern[bytes]], begin: Optional[int] = None, end: Optional[int] = None) -> Sequence[int]: """Search for a sequence of bytes in memory. Args: @@ -422,7 +422,7 @@ def unmap(self, addr: int, size: int) -> None: if (addr, addr + size) in self.mmio_cbs: del self.mmio_cbs[(addr, addr+size)] - def unmap_all(self): + def unmap_all(self) -> None: """Reclaim the entire memory space. """ @@ -483,7 +483,7 @@ def is_mapped(self, addr: int, size: int) -> bool: return any((lbound <= begin < end <= ubound) for lbound, ubound in self.__mapped_regions()) - def find_free_space(self, size: int, minaddr: int = None, maxaddr: int = None, align: int = None) -> int: + def find_free_space(self, size: int, minaddr: Optional[int] = None, maxaddr: Optional[int] = None, align: Optional[int] = None) -> int: """Locate an unallocated memory that is large enough to contain a range in size of `size` and based at `minaddr`. @@ -528,7 +528,7 @@ def find_free_space(self, size: int, minaddr: int = None, maxaddr: int = None, a raise QlOutOfMemory('Out Of Memory') - def map_anywhere(self, size: int, minaddr: int = None, maxaddr: int = None, align: int = None, perms: int = UC_PROT_ALL, info: str = None) -> int: + def map_anywhere(self, size: int, minaddr: Optional[int] = None, maxaddr: Optional[int] = None, align: Optional[int] = None, perms: int = UC_PROT_ALL, info: Optional[str] = None) -> int: """Map a region anywhere in memory. Args: @@ -563,7 +563,7 @@ def protect(self, addr: int, size: int, perms): self.change_mapinfo(aligned_address, aligned_address + aligned_size, mem_p = perms) - def map(self, addr: int, size: int, perms: int = UC_PROT_ALL, info: str = None): + def map(self, addr: int, size: int, perms: int = UC_PROT_ALL, info: Optional[str] = None): """Map a new memory range. Args: @@ -742,7 +742,7 @@ def clear(self): self.current_alloc = 0 self.current_use = 0 - def _find(self, addr: int, inuse: bool = None) -> Optional[Chunk]: + def _find(self, addr: int, inuse: Optional[bool] = None) -> Optional[Chunk]: """Find a chunk starting at a specified address. Args: From 3c531d0ae2b27fd238859d3552173a1b62a357a3 Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 12 May 2022 22:34:31 +0300 Subject: [PATCH 376/406] Fix stats --- qiling/os/stats.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/qiling/os/stats.py b/qiling/os/stats.py index 704304ad8..0755872fe 100644 --- a/qiling/os/stats.py +++ b/qiling/os/stats.py @@ -45,12 +45,6 @@ def summary(self) -> List[str]: for key, values in self.strings.items(): ret.append(f'{key}: {", ".join(str(word) for word in values)}') - ret.extend(QlOsStats._banner('registry keys accessed')) - - for key, values in self.syscalls.items(): - ret.append(f'{key}:') - ret.extend(f' {json.dumps(value):s}' for value in values) - return ret def log_api_call(self, address: int, name: str, params: Mapping, retval: Any, retaddr: int) -> None: @@ -107,7 +101,7 @@ def summary(self) -> List[str]: ret.extend(QlOsStats._banner('registry keys accessed')) - for key, values in self.syscalls.items(): + for key, values in self.registry.items(): ret.append(f'{key}:') ret.extend(f' {json.dumps(value):s}' for value in values) From d69d03b5b89577c4d2e95a713d2432d02ac96a9f Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 12 May 2022 22:44:17 +0300 Subject: [PATCH 377/406] Annotate and comment the heck of core_hooks --- qiling/core_hooks.py | 376 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 345 insertions(+), 31 deletions(-) diff --git a/qiling/core_hooks.py b/qiling/core_hooks.py index a389a9469..0a8e4cb83 100644 --- a/qiling/core_hooks.py +++ b/qiling/core_hooks.py @@ -8,7 +8,8 @@ # handling hooks # ############################################## -from typing import Callable, MutableMapping, MutableSequence +from typing import Any, Callable, MutableMapping, MutableSequence, Protocol +from typing import TYPE_CHECKING from unicorn import Uc from unicorn.unicorn_const import * @@ -18,6 +19,73 @@ from .const import QL_HOOK_BLOCK from .exception import QlErrorCoreHook +if TYPE_CHECKING: + from qiling import Qiling + +class MemHookCallback(Protocol): + def __call__(self, __ql: 'Qiling', __access: int, __address: int, __size: int, __value: int, *__context: Any) -> Any: + """Memory access hook callback. + + Args: + __ql : the associated qiling instance + __access : the intercepted memory access type, one of UC_HOOK_MEM_* constants + __addr : the target memory location + __size : size of intercepted memory access + __value : the value to write, for write operations, 0 for others + __context : additional context passed on hook creation. if no context was passed, this argument should be omitted + + Returns: + an integer with `QL_HOOK_BLOCK` mask set to block execution of remaining hooks + (if any) or `None` + """ + pass + +class TraceHookCalback(Protocol): + def __call__(self, __ql: 'Qiling', __address: int, __size: int, *__context: Any) -> Any: + """Execution hook callback. + + Args: + __ql : the associated qiling instance + __address : address of the instruction to be executed + __size : instruction size + __context : additional context passed on hook creation. if no context was passed, this argument should be omitted + + Returns: + an integer with `QL_HOOK_BLOCK` mask set to block execution of remaining hooks + (if any) or `None` + """ + pass + +class AddressHookCallback(Protocol): + def __call__(self, __ql: 'Qiling', *__context: Any) -> Any: + """Address hook callback. + + Args: + __ql : the associated qiling instance + __context : additional context passed on hook creation. if no context was passed, this argument should be omitted + + Returns: + an integer with `QL_HOOK_BLOCK` mask set to block execution of remaining hooks + (if any) or `None` + """ + pass + +class InterruptHookCallback(Protocol): + def __call__(self, __ql: 'Qiling', intno: int, *__context: Any) -> Any: + """Interrupt hook callback. + + Args: + __ql : the associated qiling instance + __intno : the intercepted interrupt number + __context : additional context passed on hook creation. if no context was passed, this argument should be omitted + + Returns: + an integer with `QL_HOOK_BLOCK` mask set to block execution of remaining hooks + (if any) or `None` + """ + pass + + # Don't assume self is Qiling. class QlCoreHooks: def __init__(self, uc: Uc): @@ -37,6 +105,9 @@ def __init__(self, uc: Uc): # Callback definitions # ######################## def _hook_intr_cb(self, uc: Uc, intno: int, pack_data) -> None: + """Interrupt hooks dispatcher. + """ + ql, hook_type = pack_data handled = False @@ -60,15 +131,18 @@ def _hook_intr_cb(self, uc: Uc, intno: int, pack_data) -> None: def _hook_insn_cb(self, uc: Uc, *args): - ql, hook_type = args[-1] + """Instruction hooks dispatcher. + """ + + *hook_args, (ql, insn_type) = args retval = None - if hook_type in self._insn_hook: - hooks_list = self._insn_hook[hook_type] + if insn_type in self._insn_hook: + hooks_list = self._insn_hook[insn_type] for hook in hooks_list: if hook.bound_check(ql.arch.regs.arch_pc): - ret = hook.call(ql, *args[:-1]) + ret = hook.call(ql, *hook_args) if type(ret) is tuple: ret, retval = ret @@ -81,13 +155,16 @@ def _hook_insn_cb(self, uc: Uc, *args): def _hook_trace_cb(self, uc: Uc, addr: int, size: int, pack_data) -> None: + """Code and block hooks dispatcher. + """ + ql, hook_type = pack_data if hook_type in self._hook: hooks_list = self._hook[hook_type] for hook in hooks_list: - if hook.bound_check(ql.arch.regs.arch_pc): + if hook.bound_check(addr, size): ret = hook.call(ql, addr, size) if type(ret) is int and ret & QL_HOOK_BLOCK: @@ -95,6 +172,9 @@ def _hook_trace_cb(self, uc: Uc, addr: int, size: int, pack_data) -> None: def _hook_mem_cb(self, uc: Uc, access: int, addr: int, size: int, value: int, pack_data): + """Memory access hooks dispatcher. + """ + ql, hook_type = pack_data handled = False @@ -116,6 +196,9 @@ def _hook_mem_cb(self, uc: Uc, access: int, addr: int, size: int, value: int, pa def _hook_insn_invalid_cb(self, uc: Uc, pack_data) -> None: + """Invalid instruction hooks dispatcher. + """ + ql, hook_type = pack_data handled = False @@ -134,6 +217,9 @@ def _hook_insn_invalid_cb(self, uc: Uc, pack_data) -> None: def _hook_addr_cb(self, uc: Uc, addr: int, size: int, pack_data): + """Address hooks dispatcher. + """ + ql = pack_data if addr in self._addr_hook: @@ -148,10 +234,10 @@ def _hook_addr_cb(self, uc: Uc, addr: int, size: int, pack_data): ############### # Class Hooks # ############### - def _ql_hook_internal(self, hook_type, callback, user_data=None, *args) -> int: + def _ql_hook_internal(self, hook_type: int, callback: Callable, context: Any, *args) -> int: _callback = catch_KeyboardInterrupt(self, callback) - # pack user_data & callback for wrapper _callback - return self._h_uc.hook_add(hook_type, _callback, (self, user_data), 1, 0, *args) + + return self._h_uc.hook_add(hook_type, _callback, (self, context), 1, 0, *args) def _ql_hook_addr_internal(self, callback: Callable, address: int) -> int: @@ -175,7 +261,7 @@ def __handle_insn(t: int) -> None: ins_t = args[0] if ins_t not in self._insn_hook_fuc: - self._insn_hook_fuc[ins_t] = self._ql_hook_internal(t, self._hook_insn_cb, ins_t, *args) + self._insn_hook_fuc[ins_t] = self._ql_hook_internal(t, self._hook_insn_cb, ins_t, ins_t) if ins_t not in self._insn_hook: self._insn_hook[ins_t] = [] @@ -232,51 +318,202 @@ def __handle_invalid_insn(t: int) -> None: handler(t) - def ql_hook(self, hook_type: int, callback: Callable, user_data=None, begin=1, end=0, *args) -> HookRet: + def ql_hook(self, hook_type: int, callback: Callable, user_data: Any = None, begin: int = 1, end: int = 0, *args) -> HookRet: + """Intercept certain emulation events within a specified range. + + Args: + hook_type : event type to intercept; this argument is used as a bitmap and may encode multiple + events to hook with the same calback. see UC_HOOK_* constants for available events + callback : a method to call upon interception; callback signature may vary + depending on the hooked event type + user_data : an additional context to pass to callback (default: `None`) + begin : start of memory range to watch + end : end of memory range to watch + + Notes: + If `begin` and `end` are not specified, the entire memory space will be watched. + + Returns: + Hook handle + """ + hook = Hook(callback, user_data, begin, end) self._ql_hook(hook_type, hook, *args) return HookRet(self, hook_type, hook) - def hook_code(self, callback, user_data=None, begin=1, end=0): + def hook_code(self, callback: TraceHookCalback, user_data: Any = None, begin: int = 1, end: int = 0) -> HookRet: + """Intercept assembly instructions before they get executed. + + Args: + callback : a method to call upon interception + user_data : an additional context to pass to callback (default: `None`) + begin : start of memory range to watch + end : end of memory range to watch + + Notes: + If `begin` and `end` are not specified, the entire memory space will be watched. + + Returns: + Hook handle + """ + return self.ql_hook(UC_HOOK_CODE, callback, user_data, begin, end) + # TODO: remove; this is a special case of hook_intno(-1) def hook_intr(self, callback, user_data=None, begin=1, end=0): return self.ql_hook(UC_HOOK_INTR, callback, user_data, begin, end) - def hook_block(self, callback, user_data=None, begin=1, end=0): + def hook_block(self, callback: TraceHookCalback, user_data: Any = None, begin: int = 1, end: int = 0) -> HookRet: + """Intercept landings in new basic blocks in a specified range. + + Args: + callback : a method to call upon interception + user_data : an additional context to pass to callback (default: `None`) + begin : start of memory range to watch + end : end of memory range to watch + + Notes: + If `begin` and `end` are not specified, the entire memory space will be watched. + + Returns: + Hook handle + """ + return self.ql_hook(UC_HOOK_BLOCK, callback, user_data, begin, end) - def hook_mem_unmapped(self, callback, user_data=None, begin=1, end=0): + def hook_mem_unmapped(self, callback: MemHookCallback, user_data: Any = None, begin: int = 1, end: int = 0) -> HookRet: + """Intercept illegal accesses to unmapped memory in a specified range. + + Args: + callback : a method to call upon interception + user_data : an additional context to pass to callback (default: `None`) + begin : start of memory range to watch + end : end of memory range to watch + + Notes: + If `begin` and `end` are not specified, the entire memory space will be watched. + + Returns: + Hook handle + """ + return self.ql_hook(UC_HOOK_MEM_UNMAPPED, callback, user_data, begin, end) - def hook_mem_read_invalid(self, callback, user_data=None, begin=1, end=0): + def hook_mem_read_invalid(self, callback: MemHookCallback, user_data: Any = None, begin: int = 1, end: int = 0) -> HookRet: + """Intercept illegal reading attempts from a specified range. + + Args: + callback : a method to call upon interception + user_data : an additional context to pass to callback (default: `None`) + begin : start of memory range to watch + end : end of memory range to watch + + Notes: + If `begin` and `end` are not specified, the entire memory space will be watched. + + Returns: + Hook handle + """ + return self.ql_hook(UC_HOOK_MEM_READ_INVALID, callback, user_data, begin, end) - def hook_mem_write_invalid(self, callback, user_data=None, begin=1, end=0): + def hook_mem_write_invalid(self, callback: MemHookCallback, user_data: Any = None, begin: int = 1, end: int = 0) -> HookRet: + """Intercept illegal writing attempts to a specified range. + + Args: + callback : a method to call upon interception + user_data : an additional context to pass to callback (default: `None`) + begin : start of memory range to watch + end : end of memory range to watch + + Notes: + If `begin` and `end` are not specified, the entire memory space will be watched. + + Returns: + Hook handle + """ + return self.ql_hook(UC_HOOK_MEM_WRITE_INVALID, callback, user_data, begin, end) - def hook_mem_fetch_invalid(self, callback, user_data=None, begin=1, end=0): + def hook_mem_fetch_invalid(self, callback: MemHookCallback, user_data: Any = None, begin: int = 1, end: int = 0) -> HookRet: + """Intercept illegal code fetching attempts from a specified range. + + Args: + callback : a method to call upon interception + user_data : an additional context to pass to callback (default: `None`) + begin : start of memory range to watch + end : end of memory range to watch + + Notes: + If `begin` and `end` are not specified, the entire memory space will be watched. + + Returns: + Hook handle + """ + return self.ql_hook(UC_HOOK_MEM_FETCH_INVALID, callback, user_data, begin, end) - def hook_mem_valid(self, callback, user_data=None, begin=1, end=0): + def hook_mem_valid(self, callback: MemHookCallback, user_data: Any = None, begin: int = 1, end: int = 0) -> HookRet: + """Intercept benign memory accesses within a specified range. + This is equivalent to hooking memory reads, writes and fetches. + + Args: + callback : a method to call upon interception + user_data : an additional context to pass to callback (default: `None`) + begin : start of memory range to watch + end : end of memory range to watch + + Notes: + If `begin` and `end` are not specified, the entire memory space will be watched. + + Returns: + Hook handle + """ + return self.ql_hook(UC_HOOK_MEM_VALID, callback, user_data, begin, end) - def hook_mem_invalid(self, callback, user_data=None, begin=1, end=0): + def hook_mem_invalid(self, callback: MemHookCallback, user_data: Any = None, begin: int = 1, end: int = 0) -> HookRet: + """Intercept invalid memory accesses within a specified range. + This is equivalent to hooking invalid memory reads, writes and fetches. + + Args: + callback : a method to call upon interception + user_data : an additional context to pass to callback (default: `None`) + begin : start of memory range to watch + end : end of memory range to watch + + Notes: + If `begin` and `end` are not specified, the entire memory space will be watched. + + Returns: + Hook handle + """ + return self.ql_hook(UC_HOOK_MEM_INVALID, callback, user_data, begin, end) - # a convenient API to set callback for a single address - def hook_address(self, callback, address, user_data=None): + def hook_address(self, callback: AddressHookCallback, address: int, user_data: Any = None) -> HookRet: + """Intercept execution from a certain memory address. + + Args: + callback : a method to call upon interception + address : memory location to watch + user_data : an additional context to pass to callback (default: `None`) + + Returns: + Hook handle + """ + hook = HookAddr(callback, address, user_data) if address not in self._addr_hook_fuc: @@ -287,37 +524,114 @@ def hook_address(self, callback, address, user_data=None): self._addr_hook[address].append(hook) - return HookRet(self, None, hook) + # note: assuming 0 is not a valid hook type + return HookRet(self, 0, hook) - def get_hook_address(self, address): - return self._addr_hook.get(address, []) + def hook_intno(self, callback: InterruptHookCallback, intno: int, user_data: Any = None) -> HookRet: + """Intercept interrupts. + Args: + callback : a method to call upon interception + intono : interrupt vector number to intercept, or -1 for any + user_data : an additional context to pass to callback (default: `None`) + + Returns: + Hook handle + """ - def hook_intno(self, callback, intno, user_data=None): hook = HookIntr(callback, intno, user_data) self._ql_hook(UC_HOOK_INTR, hook) return HookRet(self, UC_HOOK_INTR, hook) - def hook_mem_read(self, callback, user_data=None, begin=1, end=0): + def hook_mem_read(self, callback: MemHookCallback, user_data: Any = None, begin: int = 1, end: int = 0) -> HookRet: + """Intercept benign memory reads from a specified range. + + Args: + callback : a method to call upon interception + user_data : an additional context to pass to callback (default: `None`) + begin : start of memory range to watch + end : end of memory range to watch + + Notes: + If `begin` and `end` are not specified, the entire memory space will be watched. + + Returns: + Hook handle + """ + return self.ql_hook(UC_HOOK_MEM_READ, callback, user_data, begin, end) - def hook_mem_write(self, callback, user_data=None, begin=1, end=0): + def hook_mem_write(self, callback: MemHookCallback, user_data: Any = None, begin: int = 1, end: int = 0) -> HookRet: + """Intercept benign memory writes to a specified range. + + Args: + callback : a method to call upon interception + user_data : an additional context to pass to callback (default: `None`) + begin : start of memory range to watch + end : end of memory range to watch + + Notes: + If `begin` and `end` are not specified, the entire memory space will be watched. + + Returns: + Hook handle + """ + return self.ql_hook(UC_HOOK_MEM_WRITE, callback, user_data, begin, end) - def hook_mem_fetch(self, callback, user_data=None, begin=1, end=0): + def hook_mem_fetch(self, callback: MemHookCallback, user_data: Any = None, begin: int = 1, end: int = 0) -> HookRet: + """Intercept benign code fetches from a specified range. + + Args: + callback : a method to call upon interception + user_data : an additional context to pass to callback (default: `None`) + begin : start of memory range to watch + end : end of memory range to watch + + Notes: + If `begin` and `end` are not specified, the entire memory space will be watched. + + Returns: + Hook handle + """ + return self.ql_hook(UC_HOOK_MEM_FETCH, callback, user_data, begin, end) - def hook_insn(self, callback, arg1, user_data=None, begin=1, end=0): - return self.ql_hook(UC_HOOK_INSN, callback, user_data, begin, end, arg1) + def hook_insn(self, callback, insn_type: int, user_data: Any = None, begin: int = 1, end: int = 0) -> HookRet: + """Intercept execution of a certain instruction type within a specified range. + + Args: + callback : a method to call upon interception; the callback arguments list differs + based on the instruction type + insn_type : instruction type to intercept + user_data : an additional context to pass to callback (default: `None`) + begin : start of memory range to watch + end : end of memory range to watch + + Notes: + - The set of supported instruction types is very limited and defined by unicorn. + - If `begin` and `end` are not specified, the entire memory space will be watched. + + Returns: + Hook handle + """ + + return self.ql_hook(UC_HOOK_INSN, callback, user_data, begin, end, insn_type) + + + def hook_del(self, hret: HookRet) -> None: + """Unregister an existing hook and release its resources. + Args: + hret : hook handle + """ - def hook_del(self, hret: HookRet): h = hret.obj hook_type = hret.type From c2d657f181313c8cc950101c5392a8ba9113c710 Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 12 May 2022 22:45:34 +0300 Subject: [PATCH 378/406] Skip tests the unittest way --- tests/test_pathutils.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/tests/test_pathutils.py b/tests/test_pathutils.py index cf9edbe9c..de56694ed 100644 --- a/tests/test_pathutils.py +++ b/tests/test_pathutils.py @@ -28,11 +28,8 @@ def posix_to_native(rootfs: str, cwd: str, path: str) -> str: class TestPathUtils(unittest.TestCase): + @unittest.skipUnless(is_posix_host, 'POSIX host only') def test_convert_nt_to_posix(self): - # test only on a POSIX host - if not is_posix_host: - self.skipTest('POSIX host only') - rootfs = PurePosixPath(r'../examples/rootfs/x86_windows') expected = str(realpath(rootfs) / 'test') @@ -74,11 +71,8 @@ def test_convert_nt_to_posix(self): self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\Windows\\System32\\drivers', '..\\..\\test')) self.assertEqual(expected, nt_to_native(str(rootfs), 'C:\\Windows\\System32', '..\\xxxx\\..\\test')) + @unittest.skipUnless(is_nt_host, 'NT host only') def test_convert_posix_to_nt(self): - # test only on a Windows host - if not is_nt_host: - self.skipTest('NT host only') - rootfs = PureWindowsPath(r'../examples/rootfs/x86_linux') expected = str(realpath(rootfs) / 'test') From 508173090f92b3f96ba64ed526fbd0d13baa9c85 Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 18 May 2022 18:40:47 +0300 Subject: [PATCH 379/406] Rename handle objects to avoid ambiguity with constants --- qiling/os/windows/handle.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/qiling/os/windows/handle.py b/qiling/os/windows/handle.py index b5592ec10..8eb817148 100644 --- a/qiling/os/windows/handle.py +++ b/qiling/os/windows/handle.py @@ -26,9 +26,9 @@ def __eq__(self, other: 'Handle'): class HandleManager: # IO - STD_INPUT_HANDLE = Handle(id=0xfffffff6) - STD_OUTPUT_HANDLE = Handle(id=0xfffffff5) - STD_ERROR_HANDLE = Handle(id=0xfffffff4) + STDIN = Handle(id=0xfffffff6) + STDOUT = Handle(id=0xfffffff5) + STDERR = Handle(id=0xfffffff4) # Register HKEY_CLASSES_ROOT = Handle(id=0x80000000) @@ -44,9 +44,9 @@ class HandleManager: def __init__(self): self.handles: MutableMapping[int, Handle] = {} - self.append(HandleManager.STD_INPUT_HANDLE) - self.append(HandleManager.STD_OUTPUT_HANDLE) - self.append(HandleManager.STD_ERROR_HANDLE) + self.append(HandleManager.STDIN) + self.append(HandleManager.STDOUT) + self.append(HandleManager.STDERR) def append(self, handle: Handle) -> None: self.handles[handle.id] = handle From 60105fe61f27934e812869081932eb2cc6993019 Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 18 May 2022 18:42:26 +0300 Subject: [PATCH 380/406] Replace magic values with constants --- qiling/os/windows/handle.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/qiling/os/windows/handle.py b/qiling/os/windows/handle.py index 8eb817148..b5776c961 100644 --- a/qiling/os/windows/handle.py +++ b/qiling/os/windows/handle.py @@ -6,6 +6,8 @@ from typing import Any, MutableMapping, Optional +from qiling.os.windows.const import STD_ERROR_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE + class Handle: ID = 0xa0000000 @@ -26,9 +28,9 @@ def __eq__(self, other: 'Handle'): class HandleManager: # IO - STDIN = Handle(id=0xfffffff6) - STDOUT = Handle(id=0xfffffff5) - STDERR = Handle(id=0xfffffff4) + STDIN = Handle(id=STD_INPUT_HANDLE) + STDOUT = Handle(id=STD_OUTPUT_HANDLE) + STDERR = Handle(id=STD_ERROR_HANDLE) # Register HKEY_CLASSES_ROOT = Handle(id=0x80000000) From 6bf4673159330e6397ca8a1f049d93d21a6315ec Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 18 May 2022 18:44:31 +0300 Subject: [PATCH 381/406] Better handling of standard streams on Windows --- qiling/os/windows/windows.py | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/qiling/os/windows/windows.py b/qiling/os/windows/windows.py index db9b29b21..cc11e49ee 100644 --- a/qiling/os/windows/windows.py +++ b/qiling/os/windows/windows.py @@ -4,7 +4,7 @@ # import ntpath -from typing import Callable +from typing import Callable, TextIO from unicorn import UcError @@ -93,6 +93,39 @@ def __make_fcall_selector(atype: QL_ARCH) -> Callable[[int], QlFunctionCall]: self.services = {} self.load() + # only after handle manager has been set up we can assign the standard streams + self.stdin = self._stdin + self.stdout = self._stdout + self.stderr = self._stderr + + + @QlOs.stdin.setter + def stdin(self, stream: TextIO) -> None: + self._stdin = stream + + handle = self.handle_manager.get(const.STD_INPUT_HANDLE) + assert handle is not None + + handle.obj = stream + + @QlOs.stdout.setter + def stdout(self, stream: TextIO) -> None: + self._stdout = stream + + handle = self.handle_manager.get(const.STD_OUTPUT_HANDLE) + assert handle is not None + + handle.obj = stream + + @QlOs.stderr.setter + def stderr(self, stream: TextIO) -> None: + self._stderr = stream + + handle = self.handle_manager.get(const.STD_ERROR_HANDLE) + assert handle is not None + + handle.obj = stream + def load(self): self.setupGDT() From 5546c7cc994ca385f03aa8c2c63525beb55ec24e Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 18 May 2022 18:46:23 +0300 Subject: [PATCH 382/406] Simplify files handling as std streams can be accessed by handles now --- qiling/os/windows/dlls/kernel32/fileapi.py | 71 ++++++++-------------- 1 file changed, 25 insertions(+), 46 deletions(-) diff --git a/qiling/os/windows/dlls/kernel32/fileapi.py b/qiling/os/windows/dlls/kernel32/fileapi.py index 729eeee98..0ccf37e0e 100644 --- a/qiling/os/windows/dlls/kernel32/fileapi.py +++ b/qiling/os/windows/dlls/kernel32/fileapi.py @@ -26,18 +26,13 @@ def hook_GetFileType(ql: Qiling, address: int, params): hFile = params["hFile"] - if hFile in (STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, STD_ERROR_HANDLE): - ret = FILE_TYPE_CHAR - else: - obj = ql.os.handle_manager.get(hFile) + handle = ql.os.handle_manager.get(hFile) - if obj is None: - raise QlErrorNotImplemented("API not implemented") - else: - # technically is not always a type_char but.. almost - ret = FILE_TYPE_CHAR + if handle is None: + raise QlErrorNotImplemented("API not implemented") - return ret + # technically is not always a type_char but.. almost + return FILE_TYPE_CHAR # HANDLE FindFirstFileA( # LPCSTR lpFileName, @@ -156,28 +151,16 @@ def hook_ReadFile(ql: Qiling, address: int, params): nNumberOfBytesToRead = params["nNumberOfBytesToRead"] lpNumberOfBytesRead = params["lpNumberOfBytesRead"] - if hFile == STD_INPUT_HANDLE: - if ql.os.automatize_input: - # TODO maybe insert a good random generation input - s = (b"A" * (nNumberOfBytesToRead - 1)) + b"\x00" - else: - ql.log.debug("Insert input") - s = ql.os.stdin.read(nNumberOfBytesToRead) + handle = ql.os.handle_manager.get(hFile) - slen = len(s) - read_len = slen + if handle is None: + ql.os.last_error = ERROR_INVALID_HANDLE + return 0 - if slen > nNumberOfBytesToRead: - s = s[:nNumberOfBytesToRead] - read_len = nNumberOfBytesToRead + data = handle.obj.read(nNumberOfBytesToRead) - ql.mem.write(lpBuffer, s) - ql.mem.write_ptr(lpNumberOfBytesRead, read_len, 4) - else: - f = ql.os.handle_manager.get(hFile).obj - data = f.read(nNumberOfBytesToRead) - ql.mem.write(lpBuffer, data) - ql.mem.write_ptr(lpNumberOfBytesRead, len(data), 4) + ql.mem.write(lpBuffer, data) + ql.mem.write_ptr(lpNumberOfBytesRead, len(data), 4) return 1 @@ -201,24 +184,20 @@ def hook_WriteFile(ql: Qiling, address: int, params): nNumberOfBytesToWrite = params["nNumberOfBytesToWrite"] lpNumberOfBytesWritten = params["lpNumberOfBytesWritten"] + handle = ql.os.handle_manager.get(hFile) + + if handle is None: + ql.os.last_error = ERROR_INVALID_HANDLE + return 0 + + fobj = handle.obj + data = ql.mem.read(lpBuffer, nNumberOfBytesToWrite) + if hFile == STD_OUTPUT_HANDLE: - s = ql.mem.read(lpBuffer, nNumberOfBytesToWrite) - ql.os.stdout.write(s) - ql.os.stats.log_string(s.decode()) - ql.mem.write_ptr(lpNumberOfBytesWritten, nNumberOfBytesToWrite, 4) - else: - f = ql.os.handle_manager.get(hFile) - - if f is None: - # Invalid handle - ql.os.last_error = ERROR_INVALID_HANDLE - return 0 - else: - f = f.obj - - buffer = ql.mem.read(lpBuffer, nNumberOfBytesToWrite) - nNumberOfBytesWritten = f.write(bytes(buffer)) - ql.mem.write_ptr(lpNumberOfBytesWritten, nNumberOfBytesWritten, 4) + ql.os.stats.log_string(data.decode()) + + written = fobj.write(bytes(data)) + ql.mem.write_ptr(lpNumberOfBytesWritten, written, 4) return 1 From 5b9c666684141ab295a48b3b8df9d13a33f08eea Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 18 May 2022 18:49:57 +0300 Subject: [PATCH 383/406] Remove the obsolete "authomize output" option --- examples/windows_trace.py | 1 - qiling/os/windows/windows.py | 1 - qiling/profiles/windows.ql | 1 - 3 files changed, 3 deletions(-) diff --git a/examples/windows_trace.py b/examples/windows_trace.py index e218b8331..8394e3f9d 100644 --- a/examples/windows_trace.py +++ b/examples/windows_trace.py @@ -115,7 +115,6 @@ def emulate(path, rootfs, verbose=QL_VERBOSE.DEBUG, enable_trace=False): parser.add_argument("-t", "--trace", help="Enable full trace", action='store_true', default=False) parser.add_argument("-R", "--root", help="rootfs", default=None) parser.add_argument("-d", "--dump", help="Directory to dump memory regions to", default="dump") - #parser.add_argument("-a", "--automatize_input", help="Automatize writes on standard input", default=False) parser.add_argument("-p ", "--profile", help="customized profile", default="qiling/profiles/windows.ql") parser.add_argument('input', nargs='*') diff --git a/qiling/os/windows/windows.py b/qiling/os/windows/windows.py index cc11e49ee..a0e615ac2 100644 --- a/qiling/os/windows/windows.py +++ b/qiling/os/windows/windows.py @@ -88,7 +88,6 @@ def __make_fcall_selector(atype: QL_ARCH) -> Callable[[int], QlFunctionCall]: self.argv = self.ql.argv self.env = self.ql.env self.pid = self.profile.getint('KERNEL', 'pid') - self.automatize_input = self.profile.getboolean("MISC","automatize_input") self.services = {} self.load() diff --git a/qiling/profiles/windows.ql b/qiling/profiles/windows.ql index d0ab9a384..17488330a 100644 --- a/qiling/profiles/windows.ql +++ b/qiling/profiles/windows.ql @@ -40,7 +40,6 @@ split = False # maily for multiple times Ql run with one file # usage: append = test1 append = -automatize_input = False current_path = C:\ [SYSTEM] From e8b8ca57d2137819dfcfc38291fc4f9bde37a7a2 Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 18 May 2022 19:22:52 +0300 Subject: [PATCH 384/406] Reduce tests suite side effects --- tests/test_elf.py | 2 ++ tests/test_elf_multithread.py | 3 --- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/test_elf.py b/tests/test_elf.py index 96ae3bd6d..7eb18fecc 100644 --- a/tests/test_elf.py +++ b/tests/test_elf.py @@ -93,6 +93,8 @@ def dump(ql): ql.run(begin=hook_address) del ql + os.remove(snapshot) + def test_elf_linux_x86_snapshot_restore_reg(self): self._test_elf_linux_x86_snapshot_restore_common(reg=True, ctx=False) diff --git a/tests/test_elf_multithread.py b/tests/test_elf_multithread.py index 5e0e338a6..482a30256 100644 --- a/tests/test_elf_multithread.py +++ b/tests/test_elf_multithread.py @@ -29,9 +29,6 @@ def test_elf_linux_execve_x8664(self): def test_elf_linux_cloexec_x8664(self): - with open('../examples/rootfs/x8664_linux/testfile', 'wb') as f: - f.write(b'0123456789') - ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_cloexec_test"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG, From e633e62567a921d1659d79a53f85aa3eb0d42ae9 Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 19 May 2022 01:41:17 +0300 Subject: [PATCH 385/406] Rearrange as a utility class to allow patching XML --- qiling/debugger/gdb/gdb.py | 23 ++--- qiling/debugger/gdb/xmlregs.py | 160 ++++++++++++++++++++------------- 2 files changed, 106 insertions(+), 77 deletions(-) diff --git a/qiling/debugger/gdb/gdb.py b/qiling/debugger/gdb/gdb.py index a8aeceb18..0d3d1b6cd 100644 --- a/qiling/debugger/gdb/gdb.py +++ b/qiling/debugger/gdb/gdb.py @@ -28,7 +28,7 @@ from qiling import Qiling from qiling.const import QL_ARCH, QL_ENDIAN, QL_OS from qiling.debugger import QlDebugger -from qiling.debugger.gdb import xmlregs +from qiling.debugger.gdb.xmlregs import QlGdbFeatures from qiling.debugger.gdb.utils import QlGdbUtils # gdb logging prompt @@ -98,7 +98,8 @@ def __init__(self, ql: Qiling, ip: str = '127.0.01', port: int = 9999): self.gdb = QlGdbUtils(ql, entry_point, exit_point) - self.regsmap = xmlregs.load_regsmap(self.ql.arch.type) + self.features = QlGdbFeatures(self.ql.arch.type, self.ql.os.type) + self.regsmap = self.features.regsmap def run(self): server = GdbSerialConn(self.ip, self.port, self.ql.log) @@ -451,22 +452,12 @@ def handle_q(subcmd: str) -> Reply: offset, length = (int(p, 16) for p in params.split(',')) if feature == 'features' and op == 'read': - xfercmd_abspath = os.path.dirname(os.path.abspath(__file__)) - xml_folder = self.ql.arch.type.name.lower() - xfercmd_file = os.path.join(xfercmd_abspath, 'xml', xml_folder, annex) - - if self.ql.os.type == QL_OS.WINDOWS: - self.ql.log.info(f'{PROMPT} Qiling does not support XML for this platform yet') - content = '' - - elif not os.path.exists(xfercmd_file): - self.ql.log.info(f'{PROMPT} XML file not found: "{xfercmd_file}"') - content = '' + if annex == r'target.xml': + content = self.features.tostring()[offset:offset + length] else: - with open(xfercmd_file, 'r') as f: - f.seek(offset, os.SEEK_SET) - content = f.read(length) + self.ql.log.info(f'{PROMPT} did not expect "{annex}" here') + content = '' return f'{"l" if len(content) < length else "m"}{content}' diff --git a/qiling/debugger/gdb/xmlregs.py b/qiling/debugger/gdb/xmlregs.py index f872ee0fb..7385a303f 100644 --- a/qiling/debugger/gdb/xmlregs.py +++ b/qiling/debugger/gdb/xmlregs.py @@ -5,6 +5,7 @@ from typing import Iterator, Mapping, Optional, Sequence, Tuple from pathlib import PurePath +from xml.etree import ElementTree, ElementInclude from qiling.arch.arm_const import reg_map as arm_regs from qiling.arch.arm_const import reg_vfp as arm_regs_vfp @@ -12,7 +13,6 @@ from qiling.arch.arm64_const import reg_map_v as arm64_regs_v from qiling.arch.mips_const import reg_map as mips_regs_gpr from qiling.arch.mips_const import reg_map_fpu as mips_regs_fpu -from qiling.arch.x86_const import reg_map_16 as x86_regs_16 from qiling.arch.x86_const import reg_map_32 as x86_regs_32 from qiling.arch.x86_const import reg_map_64 as x86_regs_64 from qiling.arch.x86_const import reg_map_misc as x86_regs_misc @@ -21,92 +21,130 @@ from qiling.arch.x86_const import reg_map_xmm as x86_regs_xmm from qiling.arch.x86_const import reg_map_ymm as x86_regs_ymm -from qiling.const import QL_ARCH +from qiling.const import QL_ARCH, QL_OS RegEntry = Tuple[Optional[int], int, int] -# define a local dummy function to let us reference this module -__anchor__ = lambda x: x +class QlGdbFeatures: + def __init__(self, archtype: QL_ARCH, ostype: QL_OS): + xmltree = QlGdbFeatures.__load_target_xml(archtype, ostype) + regsmap = QlGdbFeatures.__load_regsmap(archtype, xmltree) -def __get_xml_path(archtype: QL_ARCH) -> Tuple[str, PurePath]: - import inspect + self.xmltree = xmltree + self.regsmap = regsmap - p = PurePath(inspect.getfile(__anchor__)) - basedir = p.parent / 'xml' / archtype.name.lower() - filename = basedir / 'target.xml' + def tostring(self) -> str: + root = self.xmltree.getroot() - return str(filename), basedir + return ElementTree.tostring(root, encoding='unicode', xml_declaration=True) -def __walk_xml_regs(filename: str, base_url: PurePath) -> Iterator[Tuple[int, str, int]]: - from xml.etree import ElementTree, ElementInclude + @staticmethod + def __get_xml_path(archtype: QL_ARCH) -> Tuple[str, PurePath]: + import inspect - tree = ElementTree.parse(filename) - root = tree.getroot() + p = PurePath(inspect.getfile(QlGdbFeatures)) + basedir = p.parent / 'xml' / archtype.name.lower() + filename = basedir / 'target.xml' - # NOTE: this is needed to load xinclude hrefs relative to the main xml file. starting - # from python 3.9 ElementInclude.include has an argument for that called 'base_url'. - # this is a workaround for earlier python versions such as 3.8 + return str(filename), basedir - def my_loader(base: PurePath): - def __wrapped(href: str, parse, encoding=None): - abshref = base / href + @staticmethod + def __load_target_xml(archtype: QL_ARCH, ostype: QL_OS) -> ElementTree.ElementTree: + filename, base_url = QlGdbFeatures.__get_xml_path(archtype) - return ElementInclude.default_loader(str(abshref), parse, encoding) + tree = ElementTree.parse(filename) - return __wrapped + # NOTE: this is needed to load xinclude hrefs relative to the main xml file. starting + # from python 3.9 ElementInclude.include has an argument for that called 'base_url'. + # this is a workaround for earlier python versions such as 3.8 - ElementInclude.include(root, loader=my_loader(base_url)) + # + def my_loader(base: PurePath): + def __wrapped(href: str, parse, encoding=None): + abshref = base / href - regnum = -1 + return ElementInclude.default_loader(str(abshref), parse, encoding) - for reg in root.iter('reg'): - # if regnum is not specified, assume it follows the previous one - regnum = int(reg.get('regnum', regnum + 1)) + return __wrapped + # - name = reg.attrib['name'] - bitsize = reg.attrib['bitsize'] + # inline all xi:include elements + ElementInclude.include(tree.getroot(), loader=my_loader(base_url)) - yield regnum, name, int(bitsize) + # patch xml osabi element with the appropriate abi tag + osabi = tree.find('osabi') -def load_regsmap(archtype: QL_ARCH) -> Sequence[RegEntry]: - """Initialize registers map using available target XML files. + if osabi is not None: + # NOTE: the 'Windows' abi tag is supported starting from gdb 10. + # earlier gdb versions use 'Cygwin' instead - Args: - archtype: target architecture type + abitag = { + QL_OS.LINUX : 'GNU/Linux', + QL_OS.FREEBSD : 'FreeBSD', + QL_OS.MACOS : 'Darwin', + QL_OS.WINDOWS : 'Windows', + QL_OS.UEFI : 'Windows', + QL_OS.DOS : 'Windows', + QL_OS.QNX : 'QNX-Neutrino' + }.get(ostype, 'unknown') - Returns: a list representing registers data - """ + osabi.text = abitag - # retreive the relevant set of registers; their order of appearance is not - # important as it is determined by the info read from the xml files - ucregs: Mapping[str, int] = { - QL_ARCH.A8086 : dict(**x86_regs_32, **x86_regs_misc, **x86_regs_cr, **x86_regs_st), - QL_ARCH.X86 : dict(**x86_regs_32, **x86_regs_misc, **x86_regs_cr, **x86_regs_st, **x86_regs_xmm), - QL_ARCH.X8664 : dict(**x86_regs_64, **x86_regs_misc, **x86_regs_cr, **x86_regs_st, **x86_regs_xmm, **x86_regs_ymm), - QL_ARCH.ARM : dict(**arm_regs, **arm_regs_vfp), - QL_ARCH.CORTEX_M : arm_regs, - QL_ARCH.ARM64 : dict(**arm64_regs, **arm64_regs_v), - QL_ARCH.MIPS : dict(**mips_regs_gpr, **mips_regs_fpu) - }[archtype] + return tree - xmlpath = __get_xml_path(archtype) - regsinfo = sorted(__walk_xml_regs(*xmlpath)) + @staticmethod + def __walk_xml_regs(xmltree: ElementTree.ElementTree) -> Iterator[Tuple[int, str, int]]: + regnum = -1 - # pre-allocate regmap and occupy it with null entries - last_regnum = regsinfo[-1][0] - regmap: Sequence[RegEntry] = [(None, 0, 0)] * (last_regnum + 1) + for reg in xmltree.iter('reg'): + # if regnum is not specified, assume it follows the previous one + regnum = int(reg.get('regnum', regnum + 1)) - pos = 0 + name = reg.attrib['name'] + bitsize = reg.attrib['bitsize'] - for regnum, name, bitsize in sorted(regsinfo): - # reg value size in nibbles - nibbles = bitsize // 4 + yield regnum, name, int(bitsize) - regmap[regnum] = (ucregs.get(name), pos, nibbles) + @staticmethod + def __load_regsmap(archtype: QL_ARCH, xmltree: ElementTree.ElementTree) -> Sequence[RegEntry]: + """Initialize registers map using available target XML files. - # value position of next reg - pos += nibbles + Args: + archtype: target architecture type - return regmap + Returns: a list representing registers data + """ -__all__ = ['RegEntry', 'load_regsmap'] + # retreive the relevant set of registers; their order of appearance is not + # important as it is determined by the info read from the xml files + ucregs: Mapping[str, int] = { + QL_ARCH.A8086 : dict(**x86_regs_32, **x86_regs_misc, **x86_regs_cr, **x86_regs_st), + QL_ARCH.X86 : dict(**x86_regs_32, **x86_regs_misc, **x86_regs_cr, **x86_regs_st, **x86_regs_xmm), + QL_ARCH.X8664 : dict(**x86_regs_64, **x86_regs_misc, **x86_regs_cr, **x86_regs_st, **x86_regs_xmm, **x86_regs_ymm), + QL_ARCH.ARM : dict(**arm_regs, **arm_regs_vfp), + QL_ARCH.CORTEX_M : arm_regs, + QL_ARCH.ARM64 : dict(**arm64_regs, **arm64_regs_v), + QL_ARCH.MIPS : dict(**mips_regs_gpr, **mips_regs_fpu) + }[archtype] + + regsinfo = sorted(QlGdbFeatures.__walk_xml_regs(xmltree)) + + # pre-allocate regmap and occupy it with null entries + last_regnum = regsinfo[-1][0] + regmap: Sequence[RegEntry] = [(None, 0, 0)] * (last_regnum + 1) + + pos = 0 + + for regnum, name, bitsize in sorted(regsinfo): + # reg value size in nibbles + nibbles = bitsize // 4 + + regmap[regnum] = (ucregs.get(name), pos, nibbles) + + # value position of next reg + pos += nibbles + + return regmap + + +__all__ = ['RegEntry', 'QlGdbFeatures'] From 36b45efbf026103f15a3a2c8b3ccb3d890228df4 Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 19 May 2022 01:42:21 +0300 Subject: [PATCH 386/406] Fix incorrect fd representation --- qiling/debugger/gdb/gdb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiling/debugger/gdb/gdb.py b/qiling/debugger/gdb/gdb.py index 0d3d1b6cd..f559a2a83 100644 --- a/qiling/debugger/gdb/gdb.py +++ b/qiling/debugger/gdb/gdb.py @@ -595,7 +595,7 @@ def handle_v(subcmd: str) -> Reply: if os.path.exists(host_path) and not path.startswith(r'/proc'): fd = os.open(host_path, flags, mode) - return f'F{fd}' + return f'F{fd:x}' elif op == 'pread': fd, count, offset = (int(p, 16) for p in params) From 38769e68fc895ea19bf077696ea7e2371e412972 Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 19 May 2022 01:43:24 +0300 Subject: [PATCH 387/406] Fix DLL cache filename on POSIX hosts --- qiling/loader/pe.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiling/loader/pe.py b/qiling/loader/pe.py index 5366623f8..1b05e6d13 100644 --- a/qiling/loader/pe.py +++ b/qiling/loader/pe.py @@ -31,10 +31,10 @@ class QlPeCacheEntry(NamedTuple): class QlPeCache: @staticmethod def cache_filename(path: str) -> str: - dirname, basename = ntpath.split(path) + dirname, basename = os.path.split(path) # canonicalize basename while preserving the path - path = ntpath.join(dirname, basename.casefold()) + path = os.path.join(dirname, basename.casefold()) return f'{path}.cache2' From 600e7ada22a4d3fa02c42c735b61a71266653af5 Mon Sep 17 00:00:00 2001 From: Sebastiano Mariani Date: Wed, 25 May 2022 09:49:12 -0700 Subject: [PATCH 388/406] Fix logger memory exhaustion Signed-off-by: Sebastiano Mariani --- qiling/log.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qiling/log.py b/qiling/log.py index 3249116df..427b44d32 100644 --- a/qiling/log.py +++ b/qiling/log.py @@ -7,6 +7,7 @@ import logging import os import re +import weakref from typing import Optional, TextIO @@ -38,7 +39,7 @@ class QlBaseFormatter(logging.Formatter): def __init__(self, ql, *args, **kwargs): super().__init__(*args, **kwargs) - self.ql = ql + self.ql = weakref.ref(ql) def get_level_tag(self, level: str) -> str: return self.__level_tag[level] From d2df83b3e7d6a9e2027e67f0d01d8f79c1b388bd Mon Sep 17 00:00:00 2001 From: Sebastiano Mariani Date: Wed, 25 May 2022 10:17:56 -0700 Subject: [PATCH 389/406] Use proxy instead of ref Signed-off-by: Sebastiano Mariani --- qiling/log.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiling/log.py b/qiling/log.py index 427b44d32..d085266ed 100644 --- a/qiling/log.py +++ b/qiling/log.py @@ -39,7 +39,7 @@ class QlBaseFormatter(logging.Formatter): def __init__(self, ql, *args, **kwargs): super().__init__(*args, **kwargs) - self.ql = weakref.ref(ql) + self.ql = weakref.proxy(ql) def get_level_tag(self, level: str) -> str: return self.__level_tag[level] From 3b265bbe1fded695b5ff82af4901db25f73ccc5c Mon Sep 17 00:00:00 2001 From: kabeor Date: Fri, 27 May 2022 15:50:45 +0800 Subject: [PATCH 390/406] Update changelog --- ChangeLog | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 7ef9840d8..ca3a3d8f2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,7 +1,10 @@ This file details the changelog of Qiling Framework. ------------------------------------ -[Version 1.4.3]: April 7th, 2022 +[Version 1.4.3]: May 27th, 2022 + +New features: +- Introduce PowerPC architecture support (#1140) Improvements: - Fix fuzzing for tendaac15 (#1096) @@ -15,6 +18,16 @@ Improvements: - Fix mistakes in fuzz_x8664_linux binary (#1121) - Add EVM ABI helpers, fix EVM DBG stack view (#1123) - Fix regression caused by missing exception handling when opening socket (#1124) +- CI improvement (#1128 #1134) +- Add macho load command 'LC_LOAD_WEAK_DYLIB' support (#1133) +- Fix breakage of non-Windows binary emulation on Windows host (#1143) +- Remove misused region bound check of unmap_all (#1144) +- Change deprecated interfaces of IDA (#1145) +- Use importlib to retrieve package version (#1146) +- New and improved gdbserver (#1148) +- Rewrite package data reading (#1150) +- Misc improvements (#1154) +- Fix memory exhaustion problem caused by the logger (#1161) Contributors: - wtdcode @@ -25,6 +38,14 @@ Contributors: - TheZ3ro - bet4it - chinggg +- kabeor +- chfl4gs +- profiles +- OlfillasOdikno +- nmantan +- machinewu +- nullableVoidPtr +- Phat3 ------------------------------------ From baf35d571d3d7ab3482a2056c1eb7785e4d253fb Mon Sep 17 00:00:00 2001 From: kabeor Date: Fri, 27 May 2022 19:22:36 +0800 Subject: [PATCH 391/406] Update changelog --- ChangeLog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ChangeLog b/ChangeLog index ca3a3d8f2..b647597a7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -13,6 +13,7 @@ Improvements: - Minor PE Loader fix (#1104) - Minor quality changes (#1106) - Fix cacheflush syscall typo (#1115) +- Improvements and fixes for Windows and PE (#1118) - Add vm_context to EVM hooks (#1119) - Load interpreter segments with correct perms and vaddr (#1120) - Fix mistakes in fuzz_x8664_linux binary (#1121) @@ -35,6 +36,7 @@ Contributors: - elicn - xwings - cq674350529 +- elicn - TheZ3ro - bet4it - chinggg From 369200eba66bce3d6181657cf4e2b1b5019ddb53 Mon Sep 17 00:00:00 2001 From: chfl4gs Date: Tue, 31 May 2022 18:02:40 +0800 Subject: [PATCH 392/406] bump pefile to 2022.5.30 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 3987cc252..5e895622e 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ requirements = [ "capstone>=4.0.1", "unicorn>=2.0.0-rc7", - "pefile @ https://github.com/erocarrera/pefile/archive/refs/heads/master.zip", + "pefile>=2022.5.30", "python-registry>=1.3.1", "keystone-engine>=0.9.2", "pyelftools>=0.28", From 50f4027c9908c18420574fbdcbf4ca343761607e Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 22 May 2022 19:45:28 +0300 Subject: [PATCH 393/406] Fix #1157 --- qiling/os/posix/posix.py | 2 +- qiling/os/posix/syscall/fcntl.py | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/qiling/os/posix/posix.py b/qiling/os/posix/posix.py index 73278057a..6f5041c9e 100644 --- a/qiling/os/posix/posix.py +++ b/qiling/os/posix/posix.py @@ -72,7 +72,7 @@ def __init__(self): def __len__(self): return len(self.__fds) - def __getitem__(self, idx: int): + def __getitem__(self, idx: Union[slice, int]): return self.__fds[idx] def __setitem__(self, idx: int, val: Optional[IO]): diff --git a/qiling/os/posix/syscall/fcntl.py b/qiling/os/posix/syscall/fcntl.py index 9c1182105..54b00555e 100644 --- a/qiling/os/posix/syscall/fcntl.py +++ b/qiling/os/posix/syscall/fcntl.py @@ -131,8 +131,8 @@ def ql_syscall_fcntl(ql: Qiling, fd: int, cmd: int, arg: int): if arg not in range(NR_OPEN): regreturn = -EINVAL - for idx, val in enumerate(ql.os.fd, arg): - if val is None: + for idx in range(arg, len(ql.os.fd)): + if ql.os.fd[idx] is None: ql.os.fd[idx] = f.dup() regreturn = idx break @@ -173,11 +173,13 @@ def ql_syscall_fcntl64(ql: Qiling, fd: int, cmd: int, arg: int): if arg not in range(NR_OPEN): regreturn = -1 - for idx, val in enumerate(ql.os.fd, arg): - if val is None: + for idx in range(arg, len(ql.os.fd)): + if ql.os.fd[idx] is None: ql.os.fd[idx] = f.dup() regreturn = idx break + else: + regreturn = -1 elif cmd == F_GETFL: regreturn = 2 From efade022fbbb1b324569a0288f12cd6c8ea4123f Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 22 May 2022 19:47:24 +0300 Subject: [PATCH 394/406] Tweak brk syscall --- qiling/os/posix/syscall/unistd.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/qiling/os/posix/syscall/unistd.py b/qiling/os/posix/syscall/unistd.py index 942ac4140..fe0c01ca6 100644 --- a/qiling/os/posix/syscall/unistd.py +++ b/qiling/os/posix/syscall/unistd.py @@ -210,13 +210,14 @@ def ql_syscall_brk(ql: Qiling, inp: int): # otherwise, just return current brk_address if inp: - new_brk_addr = ((inp + 0xfff) // 0x1000) * 0x1000 + cur_brk_addr = ql.loader.brk_address + new_brk_addr = ql.mem.align_up(inp) - if inp > ql.loader.brk_address: # increase current brk_address if inp is greater - ql.mem.map(ql.loader.brk_address, new_brk_addr - ql.loader.brk_address, info="[brk]") + if inp > cur_brk_addr: # increase current brk_address if inp is greater + ql.mem.map(cur_brk_addr, new_brk_addr - cur_brk_addr, info="[brk]") - elif inp < ql.loader.brk_address: # shrink current bkr_address to inp if its smaller - ql.mem.unmap(new_brk_addr, ql.loader.brk_address - new_brk_addr) + elif inp < cur_brk_addr: # shrink current bkr_address to inp if its smaller + ql.mem.unmap(new_brk_addr, cur_brk_addr - new_brk_addr) ql.loader.brk_address = new_brk_addr From 1f0755d877048f709857570c8612b0b64990c964 Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 24 May 2022 00:39:17 +0300 Subject: [PATCH 395/406] Correct typos --- qiling/arch/evm/evm.py | 4 ++-- qiling/arch/evm/hooks.py | 4 ++-- qiling/os/memory.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/qiling/arch/evm/evm.py b/qiling/arch/evm/evm.py index a402e0d35..2d77956d1 100644 --- a/qiling/arch/evm/evm.py +++ b/qiling/arch/evm/evm.py @@ -6,7 +6,7 @@ from qiling.const import * from ..arch import QlArch from .vm.evm import QlArchEVMEmulator -from .hooks import monkeypath_core_hooks +from .hooks import monkeypatch_core_hooks class QlArchEVM(QlArch): type = QL_ARCH.EVM @@ -16,7 +16,7 @@ def __init__(self, ql) -> None: super(QlArchEVM, self).__init__(ql) self.evm = QlArchEVMEmulator(self.ql) - monkeypath_core_hooks(self.ql) + monkeypatch_core_hooks(self.ql) def run(self, msg): return self.evm.vm.execute_message(msg) diff --git a/qiling/arch/evm/hooks.py b/qiling/arch/evm/hooks.py index 2c429a6a5..c2354c713 100644 --- a/qiling/arch/evm/hooks.py +++ b/qiling/arch/evm/hooks.py @@ -63,8 +63,8 @@ def __evm_hook_del(ql, hret): if not hooks_list: del evm_hooks_info.hook_addr_dict[h.addr] -def monkeypath_core_hooks(ql): - """Monkeypath core hooks for evm +def monkeypatch_core_hooks(ql): + """Monkeypatch core hooks for evm """ ql.hook_code = types.MethodType(__evm_hook_code, ql) diff --git a/qiling/os/memory.py b/qiling/os/memory.py index 228d07d0a..21d461e65 100644 --- a/qiling/os/memory.py +++ b/qiling/os/memory.py @@ -35,7 +35,7 @@ def __init__(self, ql: Qiling): } if ql.arch.bits not in bit_stuff: - raise QlErrorStructConversion("Unsupported Qiling archtecture for memory manager") + raise QlErrorStructConversion("Unsupported Qiling architecture for memory manager") max_addr = bit_stuff[ql.arch.bits] From 2062a72754cc12d25ae0cbfa7241f5b5e3f55ddf Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 24 May 2022 00:44:50 +0300 Subject: [PATCH 396/406] Let EVM patch ql.run --- qiling/arch/evm/evm.py | 14 ++++++++++++++ qiling/core.py | 5 +---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/qiling/arch/evm/evm.py b/qiling/arch/evm/evm.py index 2d77956d1..bd8245177 100644 --- a/qiling/arch/evm/evm.py +++ b/qiling/arch/evm/evm.py @@ -3,6 +3,9 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework +import types + +from qiling.arch.evm.vm.message import Message from qiling.const import * from ..arch import QlArch from .vm.evm import QlArchEVMEmulator @@ -17,6 +20,7 @@ def __init__(self, ql) -> None: self.evm = QlArchEVMEmulator(self.ql) monkeypatch_core_hooks(self.ql) + monkeypatch_core_methods(self.ql) def run(self, msg): return self.evm.vm.execute_message(msg) @@ -40,3 +44,13 @@ def uc(self): @property def endian(self) -> QL_ENDIAN: return QL_ENDIAN.EL + + +def __evm_run(self, code: Message): + return self.arch.run(code) + +def monkeypatch_core_methods(ql): + """Monkeypatch core methods for evm + """ + + ql.run = types.MethodType(__evm_run, ql) diff --git a/qiling/core.py b/qiling/core.py index 0c5cb929e..5dec03418 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -542,16 +542,13 @@ def write_exit_trap(self): # Emulate the binary from begin until @end, with timeout in @timeout and # number of emulated instructions in @count - def run(self, begin=None, end=None, timeout=0, count=0, code=None): + def run(self, begin=None, end=None, timeout=0, count=0): # replace the original entry point, exit point, timeout and count self.entry_point = begin self.exit_point = end self.timeout = timeout self.count = count - if self.interpreter: - return self.arch.run(code) - # init debugger (if set) debugger = select_debugger(self._debugger) From 1adf29784239964afb0f7065839a62eb5814e083 Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 24 May 2022 00:52:48 +0300 Subject: [PATCH 397/406] Annotate and document ql.run --- qiling/core.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/qiling/core.py b/qiling/core.py index 5dec03418..2e7513c3c 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -540,14 +540,21 @@ def write_exit_trap(self): # Qiling APIS # ############### - # Emulate the binary from begin until @end, with timeout in @timeout and - # number of emulated instructions in @count - def run(self, begin=None, end=None, timeout=0, count=0): + def run(self, begin: Optional[int] = None, end: Optional[int] = None, timeout: int = 0, icount: int = 0): + """Start binary emulation. + + Args: + begin : emulation starting address + end : emulation ending address + timeout : limit emulation to a specific amount of time (microseconds); unlimited by default + icount : limit emulation to a specific amount of instructions; unlimited by default + """ + # replace the original entry point, exit point, timeout and count self.entry_point = begin self.exit_point = end self.timeout = timeout - self.count = count + self.count = icount # init debugger (if set) debugger = select_debugger(self._debugger) @@ -562,7 +569,7 @@ def run(self, begin=None, end=None, timeout=0, count=0): if self.count <= 0: self.count = -1 - self.arch.run(count=self.count, end=self.exit_point) + self.arch.run(count=self.count, end=self.exit_point) else: self.write_exit_trap() # emulate the binary From e01ce973bab825a4344d2764f3791162e2b87379 Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 24 May 2022 00:54:04 +0300 Subject: [PATCH 398/406] Annotations and import fixes --- qiling/arch/evm/evm.py | 6 +++--- qiling/os/windows/windows.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/qiling/arch/evm/evm.py b/qiling/arch/evm/evm.py index bd8245177..81ce642c5 100644 --- a/qiling/arch/evm/evm.py +++ b/qiling/arch/evm/evm.py @@ -5,11 +5,11 @@ import types +from qiling.arch.arch import QlArch +from qiling.arch.evm.hooks import monkeypatch_core_hooks +from qiling.arch.evm.vm.evm import QlArchEVMEmulator from qiling.arch.evm.vm.message import Message from qiling.const import * -from ..arch import QlArch -from .vm.evm import QlArchEVMEmulator -from .hooks import monkeypatch_core_hooks class QlArchEVM(QlArch): type = QL_ARCH.EVM diff --git a/qiling/os/windows/windows.py b/qiling/os/windows/windows.py index a0e615ac2..6c115d99c 100644 --- a/qiling/os/windows/windows.py +++ b/qiling/os/windows/windows.py @@ -4,13 +4,13 @@ # import ntpath -from typing import Callable, TextIO +from typing import Callable, TextIO, Type from unicorn import UcError from qiling import Qiling from qiling.arch.x86_const import GS_SEGMENT_ADDR, GS_SEGMENT_SIZE, FS_SEGMENT_ADDR, FS_SEGMENT_SIZE -from qiling.arch.x86_utils import GDTManager, SegmentManager86, SegmentManager64 +from qiling.arch.x86_utils import GDTManager, SegmentManager, SegmentManager86, SegmentManager64 from qiling.cc import intel from qiling.const import QL_ARCH, QL_OS, QL_INTERCEPT from qiling.exception import QlErrorSyscallError, QlErrorSyscallNotFound, QlMemoryMappedError @@ -137,7 +137,7 @@ def load(self): def setupGDT(self): gdtm = GDTManager(self.ql) - segm_class = { + segm_class: Type[SegmentManager] = { 32 : SegmentManager86, 64 : SegmentManager64 }[self.ql.arch.bits] From 59953e10e6eda484525a50afd4ad41634bd21409 Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 24 May 2022 16:42:45 +0300 Subject: [PATCH 399/406] Better handling of rootfs default --- qiling/core.py | 7 ++----- qltool | 4 ++-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/qiling/core.py b/qiling/core.py index 2e7513c3c..e8bf7f468 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -30,7 +30,7 @@ class Qiling(QlCoreHooks, QlCoreStructs): def __init__( self, argv: Sequence[str] = None, - rootfs: str = None, + rootfs: str = r'.', env: MutableMapping[AnyStr, AnyStr] = {}, code: bytes = None, ostype: Union[str, QL_OS] = None, @@ -100,10 +100,7 @@ def __init__( ################ # rootfs setup # ################ - if rootfs is None: - rootfs = '.' - - elif not os.path.exists(rootfs): + if not os.path.exists(rootfs): raise QlErrorFileNotFound(f'Target rootfs not found: "{rootfs}"') self._rootfs = rootfs diff --git a/qltool b/qltool index 2ce8fa3c2..861bbbae3 100755 --- a/qltool +++ b/qltool @@ -187,7 +187,7 @@ if __name__ == '__main__': code_parser.add_argument('--thumb', action='store_true', help='specify thumb mode for ARM') code_parser.add_argument('--endian', choices=('little', 'big'), default='little', help='specify endianess for bi-endian archs') code_parser.add_argument('--os', required=True, choices=os_map) - code_parser.add_argument('--rootfs', help='emulated root filesystem, that is where all libraries reside') + code_parser.add_argument('--rootfs', default='.', help='emulated root filesystem, that is where all libraries reside') code_parser.add_argument('--format', choices=('asm', 'hex', 'bin'), default='bin', help='input file format') # set "examples" subcommand @@ -223,7 +223,7 @@ if __name__ == '__main__': handle_examples(parser) # ql file setup - if options.subcommand == 'run': + elif options.subcommand == 'run': ql = handle_run(options) # ql code setup From aab3cb2993cc5a6049123d1e54c24a957c429e87 Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 24 May 2022 16:45:37 +0300 Subject: [PATCH 400/406] A few more annotations --- qiling/core.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiling/core.py b/qiling/core.py index e8bf7f468..701b64820 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -37,11 +37,11 @@ def __init__( archtype: Union[str, QL_ARCH] = None, verbose: QL_VERBOSE = QL_VERBOSE.DEFAULT, profile: str = None, - console=True, + console: bool = True, log_file=None, log_override=None, - log_plain=False, - multithread = False, + log_plain: bool = False, + multithread: bool = False, filter = None, stop: QL_STOP = QL_STOP.NONE, *, From 8e9b21e600ff4ddf8a6afa3fa2c1ff418ece59c5 Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 24 May 2022 17:03:13 +0300 Subject: [PATCH 401/406] Reverting arg name change --- qiling/core.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiling/core.py b/qiling/core.py index 701b64820..1a42d3e7e 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -537,21 +537,21 @@ def write_exit_trap(self): # Qiling APIS # ############### - def run(self, begin: Optional[int] = None, end: Optional[int] = None, timeout: int = 0, icount: int = 0): + def run(self, begin: Optional[int] = None, end: Optional[int] = None, timeout: int = 0, count: int = 0): """Start binary emulation. Args: begin : emulation starting address end : emulation ending address timeout : limit emulation to a specific amount of time (microseconds); unlimited by default - icount : limit emulation to a specific amount of instructions; unlimited by default + count : limit emulation to a specific amount of instructions; unlimited by default """ # replace the original entry point, exit point, timeout and count self.entry_point = begin self.exit_point = end self.timeout = timeout - self.count = icount + self.count = count # init debugger (if set) debugger = select_debugger(self._debugger) From c01471af81b5098a3f863f9cf26da4e8029ab647 Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 30 May 2022 18:30:21 +0300 Subject: [PATCH 402/406] Add missing ARM regs --- qiling/arch/arm.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/qiling/arch/arm.py b/qiling/arch/arm.py index c03d0d8c0..130d93e8e 100644 --- a/qiling/arch/arm.py +++ b/qiling/arch/arm.py @@ -41,7 +41,11 @@ def uc(self) -> Uc: @cached_property def regs(self) -> QlRegisterManager: - regs_map = arm_const.reg_map + regs_map = dict( + **arm_const.reg_map, + **arm_const.reg_vfp + ) + pc_reg = 'pc' sp_reg = 'sp' From 0287c9636fe0328cc572a959e3ef04e964e6e36e Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 30 May 2022 18:30:49 +0300 Subject: [PATCH 403/406] Remove ARM endian workaround --- qiling/arch/arm.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/qiling/arch/arm.py b/qiling/arch/arm.py index 130d93e8e..394258f79 100644 --- a/qiling/arch/arm.py +++ b/qiling/arch/arm.py @@ -57,17 +57,7 @@ def is_thumb(self) -> bool: @property def endian(self) -> QL_ENDIAN: - # FIXME: ARM is a bi-endian architecture which allows flipping core endianess - # while running. endianess is tested in runtime through CPSR[9], however unicorn - # doesn't reflect the endianess correctly through that bit. - # @see: https://github.com/unicorn-engine/unicorn/issues/1542 - # - # we work around this by using the initial endianess configuration, even though - # it might have been changed since. - # - # return QL_ENDIAN.EB if self.regs.cpsr & (1 << 9) else QL_ENDIAN.EL - - return self._init_endian + return QL_ENDIAN.EB if self.regs.cpsr & (1 << 9) else QL_ENDIAN.EL @property def effective_pc(self) -> int: From 5ec2c96adc2364680dd4875634627c78181abf55 Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 30 May 2022 18:56:44 +0300 Subject: [PATCH 404/406] Add missing x86-64 regs --- qiling/arch/x86.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/qiling/arch/x86.py b/qiling/arch/x86.py index 97850ef66..0a787e8b9 100644 --- a/qiling/arch/x86.py +++ b/qiling/arch/x86.py @@ -109,7 +109,10 @@ def regs(self) -> QlRegisterManager: **x86_const.reg_map_64_b, **x86_const.reg_map_64_w, **x86_const.reg_map_64_d, - **x86_const.reg_map_seg_base + **x86_const.reg_map_seg_base, + **x86_const.reg_map_xmm, + **x86_const.reg_map_ymm, + **x86_const.reg_map_zmm ) pc_reg = 'rip' From 85d9528249b538fabe44b9c85818f6254ff0dd67 Mon Sep 17 00:00:00 2001 From: "kj.xwings.l" Date: Wed, 1 Jun 2022 12:47:57 +0800 Subject: [PATCH 405/406] Update ChangeLog --- ChangeLog | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index b647597a7..b178267bc 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,7 +1,11 @@ This file details the changelog of Qiling Framework. ------------------------------------ -[Version 1.4.3]: May 27th, 2022 +[Version 1.4.4]: July XX, 2022 + + +------------------------------------ +[Version 1.4.3]: June 1st, 2022 New features: - Introduce PowerPC architecture support (#1140) From f9a2b8e74e72f1ef07f6a952e94ff108fecdac13 Mon Sep 17 00:00:00 2001 From: xwings Date: Wed, 1 Jun 2022 12:51:07 +0800 Subject: [PATCH 406/406] get ready for release --- setup.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 5e895622e..ca07eec14 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,8 @@ from setuptools import setup, find_packages # NOTE: use "-dev" for dev branch -VERSION = "1.4.3" + "-dev" +#VERSION = "1.4.3" + "-dev" +VERSION = "1.4.3" requirements = [ "capstone>=4.0.1", @@ -65,8 +66,8 @@ # How mature is this project? Common values are # 3 - Alpha # 5 - Production/Stable - #'Development Status :: 5 - Production/Stable', - 'Development Status :: 3 - Alpha', + 'Development Status :: 5 - Production/Stable', + #'Development Status :: 3 - Alpha', # Indicate who your project is intended for 'Intended Audience :: Developers',