From 327f475ba3d7fb9bed89c3c516b3ad1c8bd10d86 Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 6 Jul 2025 18:23:07 +0300 Subject: [PATCH 1/9] Decouple BLOB entry point and load address --- examples/uboot_bin.ql | 1 + qiling/loader/blob.py | 5 ++++- qiling/os/blob/blob.py | 12 ++++++++---- qiling/os/os.py | 1 + tests/profiles/uboot_bin.ql | 1 + 5 files changed, 15 insertions(+), 5 deletions(-) diff --git a/examples/uboot_bin.ql b/examples/uboot_bin.ql index b7f7216c8..c33a7d238 100644 --- a/examples/uboot_bin.ql +++ b/examples/uboot_bin.ql @@ -1,5 +1,6 @@ [CODE] ram_size = 0xa00000 +load_address = 0x80800000 entry_point = 0x80800000 heap_size = 0x300000 diff --git a/qiling/loader/blob.py b/qiling/loader/blob.py index b8831a552..f17b80a9d 100644 --- a/qiling/loader/blob.py +++ b/qiling/loader/blob.py @@ -15,7 +15,8 @@ def __init__(self, ql: Qiling): self.load_address = 0 def run(self): - self.load_address = self.ql.os.entry_point # for consistency + self.load_address = self.ql.os.load_address + self.entry_point = self.ql.os.entry_point code_begins = self.load_address code_size = self.ql.os.code_ram_size @@ -28,8 +29,10 @@ def run(self): self.images.append(Image(code_begins, code_ends, 'blob_code')) # FIXME: heap starts above end of ram?? + # FIXME: heap should be allocated by OS, not loader heap_base = code_ends heap_size = int(self.ql.os.profile.get("CODE", "heap_size"), 16) self.ql.os.heap = QlMemoryHeap(self.ql, heap_base, heap_base + heap_size) + # FIXME: stack pointer should be a configurable profile setting self.ql.arch.regs.arch_sp = code_ends - 0x1000 diff --git a/qiling/os/blob/blob.py b/qiling/os/blob/blob.py index 02e6f94d3..e4a022562 100644 --- a/qiling/os/blob/blob.py +++ b/qiling/os/blob/blob.py @@ -9,6 +9,7 @@ from qiling.os.fcall import QlFunctionCall from qiling.os.os import QlOs + class QlOsBlob(QlOs): """ QlOsBlob for bare barines. @@ -21,7 +22,7 @@ class QlOsBlob(QlOs): type = QL_OS.BLOB def __init__(self, ql: Qiling): - super(QlOsBlob, self).__init__(ql) + super().__init__(ql) self.ql = ql @@ -39,11 +40,14 @@ def __init__(self, ql: Qiling): self.fcall = QlFunctionCall(ql, cc) def run(self): - if self.ql.entry_point: + # if entry point was set explicitly, override the default one + if self.ql.entry_point is not None: self.entry_point = self.ql.entry_point - self.exit_point = self.ql.loader.load_address + len(self.ql.code) - if self.ql.exit_point: + self.exit_point = self.load_address + len(self.ql.code) + + # if exit point was set explicitly, override the default one + if self.ql.exit_point is not None: self.exit_point = self.ql.exit_point self.ql.emu_start(self.entry_point, self.exit_point, self.ql.timeout, self.ql.count) diff --git a/qiling/os/os.py b/qiling/os/os.py index 636e089c4..dd9f38564 100644 --- a/qiling/os/os.py +++ b/qiling/os/os.py @@ -89,6 +89,7 @@ def __init__(self, ql: Qiling, resolvers: Mapping[Any, Resolver] = {}): if self.ql.code: # this shellcode entrypoint does not work for windows # windows shellcode entry point will comes from pe loader + self.load_address = self.profile.getint('CODE', 'load_address') self.entry_point = self.profile.getint('CODE', 'entry_point') self.code_ram_size = self.profile.getint('CODE', 'ram_size') diff --git a/tests/profiles/uboot_bin.ql b/tests/profiles/uboot_bin.ql index b7f7216c8..c33a7d238 100644 --- a/tests/profiles/uboot_bin.ql +++ b/tests/profiles/uboot_bin.ql @@ -1,5 +1,6 @@ [CODE] ram_size = 0xa00000 +load_address = 0x80800000 entry_point = 0x80800000 heap_size = 0x300000 From d2b4867f21c9c913cce38da1248a61834132d9b7 Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 6 Jul 2025 18:23:36 +0300 Subject: [PATCH 2/9] Adjust test and example --- examples/hello_arm_uboot.py | 104 ++++++++++++++++++++---------------- tests/test_blob.py | 40 +++++++++----- 2 files changed, 87 insertions(+), 57 deletions(-) diff --git a/examples/hello_arm_uboot.py b/examples/hello_arm_uboot.py index 9544fe0ee..f97ff6eff 100644 --- a/examples/hello_arm_uboot.py +++ b/examples/hello_arm_uboot.py @@ -8,68 +8,82 @@ from qiling.core import Qiling from qiling.const import QL_ARCH, QL_OS, QL_VERBOSE -from qiling.os.const import STRING +from qiling.os.const import STRING, SIZE_T, POINTER -def get_kaimendaji_password(): - def my_getenv(ql: Qiling): - env = { - "ID" : b"000000000000000", - "ethaddr" : b"11:22:33:44:55:66" - } +def my_getenv(ql: Qiling): + env = { + "ID" : b"000000000000000", + "ethaddr" : b"11:22:33:44:55:66" + } - params = ql.os.resolve_fcall_params({'key': STRING}) - value = env.get(params["key"], b"") + params = ql.os.resolve_fcall_params({'key': STRING}) + value = env.get(params["key"], b"") - value_addr = ql.os.heap.alloc(len(value)) - ql.mem.write(value_addr, value) + value_addr = ql.os.heap.alloc(len(value)) + ql.mem.write(value_addr, value) - ql.arch.regs.r0 = value_addr - ql.arch.regs.arch_pc = ql.arch.regs.lr + ql.arch.regs.r0 = value_addr + ql.arch.regs.arch_pc = ql.arch.regs.lr - def get_password(ql: Qiling): - password_raw = ql.mem.read(ql.arch.regs.r0, ql.arch.regs.r2) - password = '' - for item in password_raw: - if 0 <= item <= 9: - password += chr(item + 48) - else: - password += chr(item + 87) +def get_password(ql: Qiling): + # we land on a memcmp call, where the real password is being compared to + # the one provided by the user. we can follow the arguments to read the + # real password - print("The password is: %s" % password) + params = ql.os.resolve_fcall_params({ + 'ptr1': POINTER, # points to real password + 'ptr2': POINTER, # points to user provided password + 'size': SIZE_T # comparison length + }) - def partial_run_init(ql: Qiling): - # argv prepare - ql.arch.regs.arch_sp -= 0x30 - arg0_ptr = ql.arch.regs.arch_sp - ql.mem.write(arg0_ptr, b"kaimendaji") + ptr1 = params['ptr1'] + size = params['size'] - ql.arch.regs.arch_sp -= 0x10 - arg1_ptr = ql.arch.regs.arch_sp - ql.mem.write(arg1_ptr, b"000000") # arbitrary password + password_raw = ql.mem.read(ptr1, size) - ql.arch.regs.arch_sp -= 0x20 - argv_ptr = ql.arch.regs.arch_sp - ql.mem.write_ptr(argv_ptr, arg0_ptr) - ql.mem.write_ptr(argv_ptr + ql.arch.pointersize, arg1_ptr) + def __hex_digit(ch: int) -> str: + off = ord('0') if ch in range(10) else ord('a') - 10 - ql.arch.regs.r2 = 2 - ql.arch.regs.r3 = argv_ptr + return chr(ch + off) - with open("../examples/rootfs/blob/u-boot.bin.img", "rb") as f: - uboot_code = f.read() + # should be: "013f1f" + password = "".join(__hex_digit(ch) for ch in password_raw) - ql = Qiling(code=uboot_code[0x40:], archtype=QL_ARCH.ARM, ostype=QL_OS.BLOB, profile="uboot_bin.ql", verbose=QL_VERBOSE.OFF) + print(f'The password is: {password}') - image_base_addr = ql.loader.load_address - ql.hook_address(my_getenv, image_base_addr + 0x13AC0) - ql.hook_address(get_password, image_base_addr + 0x48634) - partial_run_init(ql) +def partial_run_init(ql: Qiling): + # argv prepare + ql.arch.regs.arch_sp -= 0x30 + arg0_ptr = ql.arch.regs.arch_sp + ql.mem.write(arg0_ptr, b"kaimendaji") + + ql.arch.regs.arch_sp -= 0x10 + arg1_ptr = ql.arch.regs.arch_sp + ql.mem.write(arg1_ptr, b"000000") # arbitrary password - ql.run(image_base_addr + 0x486B4, image_base_addr + 0x48718) + ql.arch.regs.arch_sp -= 0x20 + argv_ptr = ql.arch.regs.arch_sp + 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 if __name__ == "__main__": - get_kaimendaji_password() + with open("../examples/rootfs/blob/u-boot.bin.img", "rb") as f: + uboot_code = f.read() + + ql = Qiling(code=uboot_code[0x40:], archtype=QL_ARCH.ARM, ostype=QL_OS.BLOB, profile="uboot_bin.ql", verbose=QL_VERBOSE.DEBUG) + + imgbase = ql.loader.images[0].base + + ql.hook_address(my_getenv, imgbase + 0x13AC0) + ql.hook_address(get_password, imgbase + 0x48634) + + partial_run_init(ql) + + ql.run(imgbase + 0x486B4, imgbase + 0x48718) diff --git a/tests/test_blob.py b/tests/test_blob.py index bc191dc16..33e35751a 100644 --- a/tests/test_blob.py +++ b/tests/test_blob.py @@ -10,13 +10,17 @@ from qiling.core import Qiling from qiling.const import QL_ARCH, QL_OS, QL_VERBOSE -from qiling.os.const import STRING +from qiling.os.const import STRING, POINTER, SIZE_T class BlobTest(unittest.TestCase): def test_uboot_arm(self): - def my_getenv(ql, *args, **kwargs): - env = {"ID": b"000000000000000", "ethaddr": b"11:22:33:44:55:66"} + def my_getenv(ql: Qiling): + env = { + "ID": b"000000000000000", + "ethaddr": b"11:22:33:44:55:66" + } + params = ql.os.resolve_fcall_params({'key': STRING}) value = env.get(params["key"], b"") @@ -26,12 +30,23 @@ def my_getenv(ql, *args, **kwargs): 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.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 check_password(ql: Qiling): + params = ql.os.resolve_fcall_params({ + 'ptr1': POINTER, # points to real password + 'ptr2': POINTER, # points to user provided password + 'size': SIZE_T # comparison length + }) + + ptr1 = params['ptr1'] + ptr2 = params['ptr2'] + size = params['size'] + + real_password = ql.mem.read(ptr1, size) + user_password = ql.mem.read(ptr2, size) - def partial_run_init(ql): + self.assertSequenceEqual(real_password, user_password, seq_type=bytearray) + + def partial_run_init(ql: Qiling): # argv prepare ql.arch.regs.arch_sp -= 0x30 arg0_ptr = ql.arch.regs.arch_sp @@ -56,13 +71,14 @@ def partial_run_init(ql): ql = Qiling(code=uboot_code[0x40:], archtype=QL_ARCH.ARM, ostype=QL_OS.BLOB, profile="profiles/uboot_bin.ql", verbose=QL_VERBOSE.DEBUG) - image_base_addr = ql.loader.load_address - ql.hook_address(my_getenv, image_base_addr + 0x13AC0) - ql.hook_address(check_password, image_base_addr + 0x48634) + imgbase = ql.loader.images[0].base + + ql.hook_address(my_getenv, imgbase + 0x13AC0) + ql.hook_address(check_password, imgbase + 0x48634) partial_run_init(ql) - ql.run(image_base_addr + 0x486B4, image_base_addr + 0x48718) + ql.run(imgbase + 0x486B4, imgbase + 0x48718) del ql From dacc8e001201471fb1e4ff0d8dcfbb56c0e6cac0 Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 6 Jul 2025 18:24:01 +0300 Subject: [PATCH 3/9] Remove redundant BLOB case --- qiling/debugger/qdb/qdb.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/qiling/debugger/qdb/qdb.py b/qiling/debugger/qdb/qdb.py index ae942139e..7182b46da 100644 --- a/qiling/debugger/qdb/qdb.py +++ b/qiling/debugger/qdb/qdb.py @@ -136,10 +136,7 @@ def __bp_handler(ql: Qiling, address: int, size: int): with self.__set_temp(self.ql, 'verbose', QL_VERBOSE.DISABLED): self.ql.os.run() - if self.ql.os.type is QL_OS.BLOB: - self.ql.loader.entry_point = self.ql.loader.load_address - - elif init_hook: + if init_hook: for each_hook in init_hook: self.do_breakpoint(each_hook) From bb21658e32eeb1ceaa45894126282ffbb1ff3601 Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 6 Jul 2025 18:25:00 +0300 Subject: [PATCH 4/9] Fix QDB crash on allocation boundaries --- qiling/debugger/qdb/context.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiling/debugger/qdb/context.py b/qiling/debugger/qdb/context.py index 349197544..344f7563a 100644 --- a/qiling/debugger/qdb/context.py +++ b/qiling/debugger/qdb/context.py @@ -57,7 +57,7 @@ def disasm(self, address: int, detail: bool = False) -> InsnLike: """Helper function for disassembling. """ - insn_bytes = self.read_insn(address) + insn_bytes = self.read_insn(address) or b'' insn = None if insn_bytes: @@ -75,7 +75,7 @@ def disasm_lite(self, address: int) -> Tuple[int, int, str, str]: A tuple of: instruction address, size, mnemonic and operands """ - insn_bytes = self.read_insn(address) + insn_bytes = self.read_insn(address) or b'' insn = None if insn_bytes: From e68c923abd685aeeb5212bff6ef204a0b6ad7c10 Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 6 Jul 2025 18:42:59 +0300 Subject: [PATCH 5/9] Patch profiles to meet new required key --- qiling/profiles/linux.ql | 1 + qiling/profiles/windows.ql | 1 + 2 files changed, 2 insertions(+) diff --git a/qiling/profiles/linux.ql b/qiling/profiles/linux.ql index eac82348b..de828a32d 100644 --- a/qiling/profiles/linux.ql +++ b/qiling/profiles/linux.ql @@ -1,6 +1,7 @@ [CODE] # ram_size 0xa00000 is 10MB ram_size = 0xa00000 +load_address = 0x1000000 entry_point = 0x1000000 diff --git a/qiling/profiles/windows.ql b/qiling/profiles/windows.ql index 15cc2f39b..ac2cc6684 100644 --- a/qiling/profiles/windows.ql +++ b/qiling/profiles/windows.ql @@ -23,6 +23,7 @@ KI_USER_SHARED_DATA = 0x7ffe0000 [CODE] # ram_size 0xa00000 is 10MB ram_size = 0xa00000 +load_address = 0x1000000 entry_point = 0x1000000 [KERNEL] From 7088d22a73eb165ac196cffdefab9ed68099de79 Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 6 Jul 2025 18:44:59 +0300 Subject: [PATCH 6/9] Allow tests to import relatively --- tests/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/__init__.py diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 000000000..e69de29bb From 13e4569e276fdd9d1fe8f1394f9ef57b82a54083 Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 8 Jul 2025 18:08:33 +0300 Subject: [PATCH 7/9] Fix gdb regs reference for Cortex-M --- .../gdb/xml/{arm => cortex_m}/arm-m-profile.xml | 8 +++++++- qiling/debugger/gdb/xml/cortex_m/target.xml | 12 ++++++++++++ qiling/debugger/gdb/xmlregs.py | 9 ++++++++- 3 files changed, 27 insertions(+), 2 deletions(-) rename qiling/debugger/gdb/xml/{arm => cortex_m}/arm-m-profile.xml (81%) create mode 100644 qiling/debugger/gdb/xml/cortex_m/target.xml diff --git a/qiling/debugger/gdb/xml/arm/arm-m-profile.xml b/qiling/debugger/gdb/xml/cortex_m/arm-m-profile.xml similarity index 81% rename from qiling/debugger/gdb/xml/arm/arm-m-profile.xml rename to qiling/debugger/gdb/xml/cortex_m/arm-m-profile.xml index f0584a206..a07071502 100644 --- a/qiling/debugger/gdb/xml/arm/arm-m-profile.xml +++ b/qiling/debugger/gdb/xml/cortex_m/arm-m-profile.xml @@ -25,4 +25,10 @@ - + + + + + + + \ No newline at end of file diff --git a/qiling/debugger/gdb/xml/cortex_m/target.xml b/qiling/debugger/gdb/xml/cortex_m/target.xml new file mode 100644 index 000000000..635912398 --- /dev/null +++ b/qiling/debugger/gdb/xml/cortex_m/target.xml @@ -0,0 +1,12 @@ + + + + + + armv7-m + + \ No newline at end of file diff --git a/qiling/debugger/gdb/xmlregs.py b/qiling/debugger/gdb/xmlregs.py index f569cd22c..983ac6efa 100644 --- a/qiling/debugger/gdb/xmlregs.py +++ b/qiling/debugger/gdb/xmlregs.py @@ -13,14 +13,21 @@ reg_map_q as arm_regs_q, reg_map_s as arm_regs_s ) + +from qiling.arch.cortex_m_const import ( + reg_map as conretx_m_regs +) + from qiling.arch.arm64_const import ( reg_map as arm64_regs, reg_map_v as arm64_regs_v, reg_map_fp as arm64_reg_map_fp ) + from qiling.arch.mips_const import ( reg_map as mips_regs_gpr ) + from qiling.arch.x86_const import ( reg_map_32 as x86_regs_32, reg_map_64 as x86_regs_64, @@ -133,7 +140,7 @@ def __load_regsmap(archtype: QL_ARCH, xmltree: ElementTree.ElementTree) -> Seque 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, **arm_regs_q, **arm_regs_s), - QL_ARCH.CORTEX_M: arm_regs, + QL_ARCH.CORTEX_M: dict(**conretx_m_regs), QL_ARCH.ARM64: dict(**arm64_regs, **arm64_regs_v, **arm64_reg_map_fp), QL_ARCH.MIPS: dict(**mips_regs_gpr) }[archtype] From 66f0fa366964b25dcd21f135aa8554fc9599d33d Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 9 Jul 2025 13:11:14 +0300 Subject: [PATCH 8/9] Typo fix --- qiling/debugger/gdb/xmlregs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiling/debugger/gdb/xmlregs.py b/qiling/debugger/gdb/xmlregs.py index 983ac6efa..1477d6932 100644 --- a/qiling/debugger/gdb/xmlregs.py +++ b/qiling/debugger/gdb/xmlregs.py @@ -15,7 +15,7 @@ ) from qiling.arch.cortex_m_const import ( - reg_map as conretx_m_regs + reg_map as coretx_m_regs ) from qiling.arch.arm64_const import ( @@ -140,7 +140,7 @@ def __load_regsmap(archtype: QL_ARCH, xmltree: ElementTree.ElementTree) -> Seque 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, **arm_regs_q, **arm_regs_s), - QL_ARCH.CORTEX_M: dict(**conretx_m_regs), + QL_ARCH.CORTEX_M: dict(**coretx_m_regs), QL_ARCH.ARM64: dict(**arm64_regs, **arm64_regs_v, **arm64_reg_map_fp), QL_ARCH.MIPS: dict(**mips_regs_gpr) }[archtype] From a2542f1dbca0c5761853a6a618cb95dd7cb7d174 Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 9 Jul 2025 14:50:11 +0300 Subject: [PATCH 9/9] Typo fix --- qiling/debugger/gdb/xmlregs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiling/debugger/gdb/xmlregs.py b/qiling/debugger/gdb/xmlregs.py index 1477d6932..e1abf08d5 100644 --- a/qiling/debugger/gdb/xmlregs.py +++ b/qiling/debugger/gdb/xmlregs.py @@ -15,7 +15,7 @@ ) from qiling.arch.cortex_m_const import ( - reg_map as coretx_m_regs + reg_map as cortex_m_regs ) from qiling.arch.arm64_const import ( @@ -140,7 +140,7 @@ def __load_regsmap(archtype: QL_ARCH, xmltree: ElementTree.ElementTree) -> Seque 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, **arm_regs_q, **arm_regs_s), - QL_ARCH.CORTEX_M: dict(**coretx_m_regs), + QL_ARCH.CORTEX_M: dict(**cortex_m_regs), QL_ARCH.ARM64: dict(**arm64_regs, **arm64_regs_v, **arm64_reg_map_fp), QL_ARCH.MIPS: dict(**mips_regs_gpr) }[archtype]