From 2e8a7039471c78f4dc6767dd4e09aa7440e893ad Mon Sep 17 00:00:00 2001 From: lazymio Date: Mon, 9 May 2022 00:12:52 +0200 Subject: [PATCH 1/8] Support memory slicing --- qiling/os/memory.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/qiling/os/memory.py b/qiling/os/memory.py index 5357623ef..fe9470b01 100644 --- a/qiling/os/memory.py +++ b/qiling/os/memory.py @@ -3,6 +3,7 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # +from ctypes import Union import os, re from typing import Any, Callable, List, MutableSequence, Optional, Sequence, Tuple @@ -63,6 +64,29 @@ def __read_string(self, addr: int) -> str: def __write_string(self, addr: int, s: str, encoding: str): self.write(addr, bytes(s, encoding) + b'\x00') + def __getitem__(self, key: Union[slice, int]) -> bytearray: + if isinstance(key, slice): + start = key.start + stop = key.stop + step = key.step + + if step and step != 1: + # step != 1 means we have to do copy, don't allow it. + raise IndexError("Only support slicing continous memory") + + return self.ql.mem.read(start, stop - start) + elif isinstance(key, int): + return self.ql.mem.read(key, 1) + else: + raise KeyError("Wrong type of key") + + def __setitem__(self, key: Union[slice, int], value: Union[bytes, bytearray]): + if isinstance(key, int): + self.ql.mem.write(key, value) + else: + # Slicing doesn't make sense in writing. + raise KeyError("Wrong type of key") + # TODO: this is an obsolete utility method that should not be used anymore # and here for backward compatibility. use QlOsUtils.read_cstring instead def string(self, addr: int, value=None, encoding='utf-8') -> Optional[str]: From 11224c3cbfd1c82ecf756955c7660d872a357c3e Mon Sep 17 00:00:00 2001 From: lazymio Date: Mon, 9 May 2022 00:19:46 +0200 Subject: [PATCH 2/8] Fix typo --- 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 fe9470b01..35742ed84 100644 --- a/qiling/os/memory.py +++ b/qiling/os/memory.py @@ -3,9 +3,8 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from ctypes import Union import os, re -from typing import Any, Callable, List, MutableSequence, Optional, Sequence, Tuple +from typing import Any, Callable, List, MutableSequence, Optional, Sequence, Tuple, Union from unicorn import UC_PROT_NONE, UC_PROT_READ, UC_PROT_WRITE, UC_PROT_EXEC, UC_PROT_ALL From 9f78b696fc304200fc4c481fa97a20aeaccb800d Mon Sep 17 00:00:00 2001 From: lazymio Date: Mon, 9 May 2022 00:29:17 +0200 Subject: [PATCH 3/8] Check None correctly --- qiling/os/memory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiling/os/memory.py b/qiling/os/memory.py index 35742ed84..eca30ec0d 100644 --- a/qiling/os/memory.py +++ b/qiling/os/memory.py @@ -69,7 +69,7 @@ def __getitem__(self, key: Union[slice, int]) -> bytearray: stop = key.stop step = key.step - if step and step != 1: + if step is not None and step != 1: # step != 1 means we have to do copy, don't allow it. raise IndexError("Only support slicing continous memory") From 41d6d30b9afab54d4701dd0d6d11baff311c2afc Mon Sep 17 00:00:00 2001 From: lazymio Date: Mon, 9 May 2022 14:23:57 +0200 Subject: [PATCH 4/8] mimic bytearray --- qiling/os/memory.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/qiling/os/memory.py b/qiling/os/memory.py index eca30ec0d..d6ea555f6 100644 --- a/qiling/os/memory.py +++ b/qiling/os/memory.py @@ -73,17 +73,31 @@ def __getitem__(self, key: Union[slice, int]) -> bytearray: # step != 1 means we have to do copy, don't allow it. raise IndexError("Only support slicing continous memory") - return self.ql.mem.read(start, stop - start) + return self.ql.mem.read(start, max(0, stop - start)) elif isinstance(key, int): - return self.ql.mem.read(key, 1) + return self.ql.mem.read(key, 1)[0] else: raise KeyError("Wrong type of key") - def __setitem__(self, key: Union[slice, int], value: Union[bytes, bytearray]): + def __setitem__(self, key: Union[slice, int], value: bytes): if isinstance(key, int): - self.ql.mem.write(key, value) + self.ql.mem.write(key, bytes([value])) + elif isinstance(key, slice): + start = key.start + stop = key.stop + step = key.step + + if step is not None and step != 1: + raise IndexError("Only support slicing continous memory") + + if start is None: + raise IndexError("The start of memory is not supplied") + + if len(value) > stop - start: + raise IndexError("Bytes to write are more than sliced memory") + + self.ql.mem.write(start, value) else: - # Slicing doesn't make sense in writing. raise KeyError("Wrong type of key") # TODO: this is an obsolete utility method that should not be used anymore From 295ea603f9cfe55b1436ce6acf631463ae0c1221 Mon Sep 17 00:00:00 2001 From: lazymio Date: Tue, 10 May 2022 20:41:35 +0200 Subject: [PATCH 5/8] Enforce ql.mem[addr:] to be more intuitive --- qiling/os/memory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiling/os/memory.py b/qiling/os/memory.py index d6ea555f6..9526f90ab 100644 --- a/qiling/os/memory.py +++ b/qiling/os/memory.py @@ -93,7 +93,7 @@ def __setitem__(self, key: Union[slice, int], value: bytes): if start is None: raise IndexError("The start of memory is not supplied") - if len(value) > stop - start: + if stop is not None and len(value) > stop - start: raise IndexError("Bytes to write are more than sliced memory") self.ql.mem.write(start, value) From 6915bc4ff6a35740d55d3054bf700de0696701ae Mon Sep 17 00:00:00 2001 From: lazymio Date: Tue, 10 May 2022 21:25:31 +0200 Subject: [PATCH 6/8] Support creating shellcode after creating Qiling instance --- examples/shellcode_run.py | 8 ++++++++ qiling/core.py | 6 +++--- qiling/debugger/gdb/gdb.py | 4 ++-- qiling/loader/blob.py | 5 +++++ qiling/loader/dos.py | 5 +++++ qiling/loader/elf.py | 5 ++++- qiling/loader/evm.py | 2 +- qiling/loader/macho.py | 5 +++++ qiling/loader/mcu.py | 5 +++++ qiling/loader/pe.py | 2 +- qiling/loader/pe_uefi.py | 5 +++++ qiling/os/linux/linux.py | 2 ++ qiling/os/macos/macos.py | 5 +++-- qiling/os/os.py | 2 +- qiling/os/posix/syscall/unistd.py | 2 +- qiling/os/qnx/qnx.py | 4 +++- 16 files changed, 54 insertions(+), 13 deletions(-) diff --git a/examples/shellcode_run.py b/examples/shellcode_run.py index c45f9ed27..2799f3952 100644 --- a/examples/shellcode_run.py +++ b/examples/shellcode_run.py @@ -25,6 +25,14 @@ ql = Qiling(code=ARM64_LIN, archtype="arm64", ostype="linux", verbose=QL_VERBOSE.DEBUG) ql.run() + print("\nCreate shellcode after creating Qiling instance") + ql = Qiling(archtype="arm64", ostype="linux", verbose=QL_VERBOSE.DEBUG) + addr = ql.mem.map_anywhere(0x1000) + ql.mem[addr:] = ARM64_LIN + stack = ql.mem.map_anywhere(0x1000) + ql.arch.regs.arch_sp = stack + ql.run(begin=addr, end=addr + len(ARM64_LIN)) + print("\nLinux ARM 32bit Shellcode") ql = Qiling(code=ARM_LIN, archtype="arm", ostype="linux", verbose=QL_VERBOSE.DEBUG) ql.run() diff --git a/qiling/core.py b/qiling/core.py index 684d8e74d..4ba98e5e5 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -88,14 +88,14 @@ def __init__( # argv setup # ############## if argv is None: - argv = ['qilingcode'] + argv = [] 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) + self._path = self.argv[0] if len(self.argv) != 0 else "" + self._targetname = os.path.basename(self.path) if self.path is not None else "" ################ # rootfs setup # diff --git a/qiling/debugger/gdb/gdb.py b/qiling/debugger/gdb/gdb.py index 8b3416663..67f7ba401 100644 --- a/qiling/debugger/gdb/gdb.py +++ b/qiling/debugger/gdb/gdb.py @@ -54,7 +54,7 @@ def __init__(self, ql: Qiling, ip: str = '127.0.01', port: int = 9999): if self.ql.baremetal: load_address = self.ql.loader.load_address exit_point = load_address + os.path.getsize(ql.path) - elif self.ql.code: + elif self.ql.code or len(self.ql.argv) == 0: load_address = self.ql.os.entry_point exit_point = load_address + len(ql.code) else: @@ -503,7 +503,7 @@ def handle_q(subcmd): self.send("l" + file_contents) elif subcmd.startswith('Xfer:auxv:read::'): - if self.ql.code: + if self.ql.code or len(self.ql.argv) == 0: return if self.ql.os.type in (QL_OS.LINUX, QL_OS.FREEBSD): diff --git a/qiling/loader/blob.py b/qiling/loader/blob.py index 382dbb33c..56449a8d5 100644 --- a/qiling/loader/blob.py +++ b/qiling/loader/blob.py @@ -14,6 +14,11 @@ def __init__(self, ql: Qiling): self.load_address = 0 def run(self): + + # Shellcode case. + if len(self.ql.argv) == 0: + return + self.load_address = self.ql.os.entry_point # for consistency self.ql.mem.map(self.ql.os.entry_point, self.ql.os.code_ram_size, info="[code]") diff --git a/qiling/loader/dos.py b/qiling/loader/dos.py index 5e3db8bbd..80406f8ee 100644 --- a/qiling/loader/dos.py +++ b/qiling/loader/dos.py @@ -41,6 +41,11 @@ def excepthook(self, tp, value, tb): self.old_excepthook(tp, value, tb) def run(self): + + # Shellcode case. + if len(self.ql.argv) == 0: + return + path = self.ql.path profile = self.ql.profile diff --git a/qiling/loader/elf.py b/qiling/loader/elf.py index c6ba9573f..06c268477 100644 --- a/qiling/loader/elf.py +++ b/qiling/loader/elf.py @@ -69,7 +69,7 @@ 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]") - shellcode_base = self.ql.os.entry_point + 0x200000 - 0x1000 + shellcode_base = self.ql.os.entry_point + self.ql.os.code_ram_size // 2 self.ql.mem.write(shellcode_base, self.ql.code) self.ql.arch.regs.arch_sp = shellcode_base @@ -77,6 +77,9 @@ def run(self): self.load_address = shellcode_base return + elif len(self.ql.argv) == 0: + self.load_address = 0 + return section = { 32 : 'OS32', diff --git a/qiling/loader/evm.py b/qiling/loader/evm.py index a416c372d..89d85c37d 100644 --- a/qiling/loader/evm.py +++ b/qiling/loader/evm.py @@ -14,7 +14,7 @@ def __init__(self, ql:Qiling): super(QlLoaderEVM, self).__init__(ql) self.ql = ql - if self.ql.code is None: + if len(self.ql.argv) == 0: with open(self.ql.path) as f: self.code = f.read() else: diff --git a/qiling/loader/macho.py b/qiling/loader/macho.py index 5c0e5c21a..23110e546 100644 --- a/qiling/loader/macho.py +++ b/qiling/loader/macho.py @@ -90,6 +90,11 @@ def __init__(self, ql, dyld_path=None): self.kext_name = None def run(self): + + # Shellcode case. + if len(self.ql.argv) == 0: + return + self.profile = self.ql.profile stack_address = int(self.profile.get("OS64", "stack_address"), 16) stack_size = int(self.profile.get("OS64", "stack_size"), 16) diff --git a/qiling/loader/mcu.py b/qiling/loader/mcu.py index 2daf8a21a..45e948421 100644 --- a/qiling/loader/mcu.py +++ b/qiling/loader/mcu.py @@ -134,6 +134,11 @@ def load_env(self): self.ql.hw.create(name.lower()) def run(self): + + # Shellcode case. + if len(self.ql.argv) == 0: + return + self.load_profile() self.load_env() diff --git a/qiling/loader/pe.py b/qiling/loader/pe.py index 5366623f8..0d6e3421b 100644 --- a/qiling/loader/pe.py +++ b/qiling/loader/pe.py @@ -660,7 +660,7 @@ def run(self): 'ucrtbase.dll' ) - if self.ql.code: + if self.ql.code or len(self.ql.argv) == 0: pe = None self.is_driver = False else: diff --git a/qiling/loader/pe_uefi.py b/qiling/loader/pe_uefi.py index a20bf9444..52b96deb9 100644 --- a/qiling/loader/pe_uefi.py +++ b/qiling/loader/pe_uefi.py @@ -318,6 +318,11 @@ def __init_smm_environment(self, ql: Qiling) -> SmmContext: return context def run(self): + + # Shellcode case. + if len(self.ql.argv) == 0: + return + ql = self.ql # intel architecture uefi implementation only diff --git a/qiling/os/linux/linux.py b/qiling/os/linux/linux.py index ed95a7ecd..487c4a370 100644 --- a/qiling/os/linux/linux.py +++ b/qiling/os/linux/linux.py @@ -139,6 +139,8 @@ def run(self): try: if self.ql.code: self.ql.emu_start(self.entry_point, (self.entry_point + len(self.ql.code)), self.ql.timeout, self.ql.count) + elif len(self.ql.argv) == 0: + self.ql.emu_start(self.ql.entry_point, self.ql.exit_point, self.ql.timeout, self.ql.count) else: if self.ql.multithread == True: # start multithreading diff --git a/qiling/os/macos/macos.py b/qiling/os/macos/macos.py index eb7f38ee2..d0c43c557 100644 --- a/qiling/os/macos/macos.py +++ b/qiling/os/macos/macos.py @@ -141,7 +141,7 @@ def load_kext(self): def load(self): - if self.ql.code: + if self.ql.code or len(self.ql.argv) == 0: return if self.ql.arch.type == QL_ARCH.ARM64: @@ -202,7 +202,8 @@ def callback_ret(ql): try: if self.ql.code: self.ql.emu_start(self.entry_point, (self.entry_point + len(self.ql.code)), self.ql.timeout, self.ql.count) - + elif len(self.ql.argv) == 0: + self.ql.emu_start(self.ql.entry_point, self.ql.exit_point, 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) except UcError: diff --git a/qiling/os/os.py b/qiling/os/os.py index 0b4a3c3a5..e6c55dce5 100644 --- a/qiling/os/os.py +++ b/qiling/os/os.py @@ -71,7 +71,7 @@ def __init__(self, ql: Qiling, resolvers: Mapping[Any, Resolver] = {}): 64: 0xffffffffffffffff }.get(self.ql.arch.bits, None) - if self.ql.code: + if self.ql.code or len(self.ql.argv) == 0: # this shellcode entrypoint does not work for windows # windows shellcode entry point will comes from pe loader self.entry_point = self.profile.getint('CODE', 'entry_point') diff --git a/qiling/os/posix/syscall/unistd.py b/qiling/os/posix/syscall/unistd.py index 942ac4140..90316f21b 100644 --- a/qiling/os/posix/syscall/unistd.py +++ b/qiling/os/posix/syscall/unistd.py @@ -487,7 +487,7 @@ def __read_str_array(addr: int) -> Iterator[str]: # Clean debugger to prevent port conflicts # ql.debugger = None - if ql.code: + if ql.code or len(ql.argv) == 0: return # recreate cached uc diff --git a/qiling/os/qnx/qnx.py b/qiling/os/qnx/qnx.py index 9f79f1d95..9be65eaae 100644 --- a/qiling/os/qnx/qnx.py +++ b/qiling/os/qnx/qnx.py @@ -61,7 +61,7 @@ def __init__(self, ql: Qiling): self.connections[SYSMGR_COID] = QnxConn(SYSMGR_PID, SYSMGR_CHID) def load(self): - if self.ql.code: + if self.ql.code or len(self.ql.argv) == 0: return # ARM @@ -126,6 +126,8 @@ def run(self): try: if self.ql.code: self.ql.emu_start(self.entry_point, (self.entry_point + len(self.ql.code)), self.ql.timeout, self.ql.count) + elif len(self.ql.argv == 0): + self.ql.emu_start(self.ql.entry_point, self.ql.exit_point, self.ql.timeout, self.ql.count) else: if self.ql.loader.elf_entry != self.ql.loader.entry_point: entry_address = self.ql.loader.elf_entry From 8a2709eedf94f251e7a81cb412e142cd5087761d Mon Sep 17 00:00:00 2001 From: lazymio Date: Tue, 10 May 2022 21:41:43 +0200 Subject: [PATCH 7/8] Check if shellcode for gdb --- qiling/debugger/gdb/gdb.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qiling/debugger/gdb/gdb.py b/qiling/debugger/gdb/gdb.py index 67f7ba401..b16b4c63b 100644 --- a/qiling/debugger/gdb/gdb.py +++ b/qiling/debugger/gdb/gdb.py @@ -8,6 +8,7 @@ import struct, os, socket from binascii import unhexlify +from sys import argv from typing import Iterator, Literal from qiling import Qiling @@ -41,7 +42,7 @@ def __init__(self, ql: Qiling, ip: str = '127.0.01', port: int = 9999): self.ql = ql self.last_pkt = None - self.exe_abspath = os.path.abspath(self.ql.argv[0]) + self.exe_abspath = os.path.abspath(self.ql.argv[0] if len(self.ql.argv) else "shellcode") self.rootfs_abspath = os.path.abspath(self.ql.rootfs) self.gdb = QlGdbUtils() From 1991934881114b3d8097bb1f8a9f2dc46ad77321 Mon Sep 17 00:00:00 2001 From: lazymio Date: Tue, 10 May 2022 22:12:57 +0200 Subject: [PATCH 8/8] Fix typo --- qiling/os/qnx/qnx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiling/os/qnx/qnx.py b/qiling/os/qnx/qnx.py index 9be65eaae..fb499fdc8 100644 --- a/qiling/os/qnx/qnx.py +++ b/qiling/os/qnx/qnx.py @@ -126,7 +126,7 @@ def run(self): try: if self.ql.code: self.ql.emu_start(self.entry_point, (self.entry_point + len(self.ql.code)), self.ql.timeout, self.ql.count) - elif len(self.ql.argv == 0): + elif len(self.ql.argv) == 0: self.ql.emu_start(self.ql.entry_point, self.ql.exit_point, self.ql.timeout, self.ql.count) else: if self.ql.loader.elf_entry != self.ql.loader.entry_point: