From 5ec4a3b57a0cdf6e638026e04ab94cc60b1aabb5 Mon Sep 17 00:00:00 2001 From: technikelly <11539105+technikelly@users.noreply.github.com> Date: Mon, 25 Aug 2025 13:34:45 -0400 Subject: [PATCH] addressing review comments, removing copyright notices --- examples/hello_arm_blob_raw.py | 47 ++++++++++++++++++++------------- examples/src/blob/Makefile | 8 +----- examples/src/blob/example_raw.c | 8 +----- examples/src/blob/linker.ld | 7 ----- examples/uboot_bin.ql | 1 + qiling/loader/blob.py | 16 ----------- qiling/os/blob/blob.py | 9 ++++++- tests/profiles/uboot_bin.ql | 1 + tests/test_blob.py | 43 +++--------------------------- 9 files changed, 43 insertions(+), 97 deletions(-) diff --git a/examples/hello_arm_blob_raw.py b/examples/hello_arm_blob_raw.py index 6096bbd9..4c257166 100644 --- a/examples/hello_arm_blob_raw.py +++ b/examples/hello_arm_blob_raw.py @@ -1,13 +1,13 @@ ############################################################################## -# Added example for raw binary blob -# Kelly Patterson - Cisco Talos -# Copyright (C) 2025 Cisco Systems Inc -# Licensed under the GNU General Public License v2.0 or later +# This example is meant to demonstrate the modifications necessary +# to enable code coverage when emulating small code snippets or bare-metal +# code. ############################################################################## from qiling import Qiling from qiling.const import QL_ARCH, QL_OS, QL_VERBOSE from qiling.extensions.coverage import utils as cov_utils from qiling.loader.loader import Image +import os BASE_ADDRESS = 0x10000000 CHECKSUM_FUNC_ADDR = BASE_ADDRESS + 0x8 @@ -16,6 +16,8 @@ DATA_ADDR = 0xa0000000 # Arbitrary address for data STACK_ADDR = 0xb0000000 # Arbitrary address for stack # Python implementation of the checksum function being emulated +# This checksum function is intended to have different code paths based on the input +# which is useful for observing code coverage def checksum_function(input_data_buffer: bytes): expected_checksum_python = 0 input_data_len = len(input_data_buffer) @@ -33,26 +35,34 @@ def checksum_function(input_data_buffer: bytes): expected_checksum_python &= 0xFF # Ensure it's a single byte return expected_checksum_python -def unmapped_handler(ql, type, addr, size, value): +def unmapped_handler(ql: Qiling, type: int, addr: int, size: int, value: int) -> None: + print(f"Unmapped Memory R/W, trying to access {size:d} bytes at {addr:#010x} from {ql.arch.regs.pc:#010x}") - print(f"Unmapped Memory R/W, trying to access {hex(size)} bytes at {hex(addr)} from {hex(ql.arch.regs.pc)}") - -def emulate_checksum_function(input_data_buffer: bytes): +def emulate_checksum_function(input_data_buffer: bytes) -> None: print(f"\n--- Testing with input: {input_data_buffer.hex()} ---") - with open("rootfs/blob/example_raw.bin", "rb") as f: - raw_code = f.read() + test_file = "rootfs/blob/example_raw.bin" - ql = Qiling(code=raw_code, archtype=QL_ARCH.ARM, ostype=QL_OS.BLOB, profile="blob_raw.ql", verbose=QL_VERBOSE.DEBUG, thumb=True) + with open(test_file, "rb") as f: + raw_code: bytes = f.read() - # monkeypatch - Correcting the loader image name, used for coverage collection - # Remove all images with name 'blob_code' that were created by the blob loader + ql: Qiling = Qiling( + code=raw_code, + archtype=QL_ARCH.ARM, + ostype=QL_OS.BLOB, + profile="blob_raw.ql", + verbose=QL_VERBOSE.DEBUG, + thumb=True + ) + + ''' monkeypatch - Correcting the loader image name, used for coverage collection + removing all images with name 'blob_code' that were created by the blob loader. + This is necessary because some code coverage visualization tools require the + module name to match that of the input file ''' ql.loader.images = [img for img in ql.loader.images if img.path != 'blob_code'] - # Add image back with correct info - ql.loader.images.append(Image(ql.loader.load_address, ql.loader.load_address + ql.os.code_ram_size, 'example_raw.bin')) + ql.loader.images.append(Image(ql.loader.load_address, ql.loader.load_address + ql.os.code_ram_size, os.path.basename(test_file))) - - input_data_len = len(input_data_buffer) + input_data_len: int = len(input_data_buffer) # Map memory for the data and stack ql.mem.map(STACK_ADDR, 0x2000) @@ -88,5 +98,4 @@ def emulate_checksum_function(input_data_buffer: bytes): if __name__ == "__main__": data = b"\x01\x02\x03\x04\x05" # Example input data - emulate_checksum_function(data) - print(hex(checksum_function(data))) \ No newline at end of file + emulate_checksum_function(data) \ No newline at end of file diff --git a/examples/src/blob/Makefile b/examples/src/blob/Makefile index 66fe0b43..74966f26 100644 --- a/examples/src/blob/Makefile +++ b/examples/src/blob/Makefile @@ -1,10 +1,4 @@ -############################################################################## -# Added example for raw binary blob -# Kelly Patterson - Cisco Talos -# Copyright (C) 2025 Cisco Systems Inc -# Licensed under the GNU General Public License v2.0 or later -############################################################################## -# Makefile for Bare-Metal ARM Hash Calculator +# Makefile for Bare-Metal ARM Checksum Calculator # --- Toolchain Definitions --- TOOLCHAIN_PREFIX = arm-none-eabi diff --git a/examples/src/blob/example_raw.c b/examples/src/blob/example_raw.c index eae56b2b..13cd7077 100644 --- a/examples/src/blob/example_raw.c +++ b/examples/src/blob/example_raw.c @@ -1,10 +1,4 @@ - /* - * Added example for raw binary blob - * Kelly Patterson - Cisco Talos - * Copyright (C) 2025 Cisco Systems Inc - * Licensed under the GNU General Public License v2.0 or later - * - */ +// example checksum algorithm to demonstrate raw binary code coverage in qiling // example_raw.c // Define some magic values diff --git a/examples/src/blob/linker.ld b/examples/src/blob/linker.ld index 9c9e7485..ae31f2fa 100644 --- a/examples/src/blob/linker.ld +++ b/examples/src/blob/linker.ld @@ -1,11 +1,4 @@ /* linker.ld */ - /* - * Added example for raw binary blob - * Kelly Patterson - Cisco Talos - * Copyright (C) 2025 Cisco Systems Inc - * Licensed under the GNU General Public License v2.0 or later - * - */ ENTRY(_start) /* Define the entry point of our program */ diff --git a/examples/uboot_bin.ql b/examples/uboot_bin.ql index c33a7d23..1e95311f 100644 --- a/examples/uboot_bin.ql +++ b/examples/uboot_bin.ql @@ -2,6 +2,7 @@ ram_size = 0xa00000 load_address = 0x80800000 entry_point = 0x80800000 +heap_address = 0xa0000000 heap_size = 0x300000 diff --git a/qiling/loader/blob.py b/qiling/loader/blob.py index 7a7e3325..72844339 100644 --- a/qiling/loader/blob.py +++ b/qiling/loader/blob.py @@ -2,15 +2,9 @@ # # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -# Heaps are optional for blobs -# Kelly Patterson - Cisco Talos -# Copyright (C) 2025 Cisco Systems Inc -# Licensed under the GNU General Public License v2.0 or later from qiling import Qiling from qiling.loader.loader import QlLoader, Image -from qiling.os.memory import QlMemoryHeap -import configparser class QlLoaderBLOB(QlLoader): @@ -33,15 +27,5 @@ class QlLoaderBLOB(QlLoader): # allow image-related functionalities 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 - # if heap_size is defined, create the heap - try: - 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) - except (configparser.NoSectionError, configparser.NoOptionError): - pass # heap_size is not required - # 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 e4a02256..af52fa74 100644 --- a/qiling/os/blob/blob.py +++ b/qiling/os/blob/blob.py @@ -8,6 +8,7 @@ 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 +from qiling.os.memory import QlMemoryHeap class QlOsBlob(QlOs): @@ -49,5 +50,11 @@ class QlOsBlob(QlOs): # 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 - + + # if heap info is provided in profile, create heap + heap_base = self.profile.getint('CODE', 'heap_address', fallback=None) + heap_size = self.profile.getint('CODE', 'heap_size', fallback=None) + if heap_base is not None and heap_size is not None: + self.heap = QlMemoryHeap(self.ql, heap_base, heap_base + heap_size) + self.ql.emu_start(self.entry_point, self.exit_point, self.ql.timeout, self.ql.count) diff --git a/tests/profiles/uboot_bin.ql b/tests/profiles/uboot_bin.ql index c33a7d23..1e95311f 100644 --- a/tests/profiles/uboot_bin.ql +++ b/tests/profiles/uboot_bin.ql @@ -2,6 +2,7 @@ ram_size = 0xa00000 load_address = 0x80800000 entry_point = 0x80800000 +heap_address = 0xa0000000 heap_size = 0x300000 diff --git a/tests/test_blob.py b/tests/test_blob.py index 310d202a..753e0ef5 100644 --- a/tests/test_blob.py +++ b/tests/test_blob.py @@ -2,10 +2,6 @@ # # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -# Added test for raw binary blob emulation -# Kelly Patterson - Cisco Talos -# Copyright (C) 2025 Cisco Systems Inc -# Licensed under the GNU General Public License v2.0 or later import unittest @@ -87,7 +83,7 @@ class BlobTest(unittest.TestCase): del ql @unittest.skip("Temporarily disabled") - def test_blob_checksum_calculations(self): + def test_blob_raw(self): def run_checksum_emu(input_data_buffer: bytes) -> int: """ Callable function that takes input data buffer and returns the checksum. @@ -145,41 +141,8 @@ class BlobTest(unittest.TestCase): return expected_checksum & 0xFF - # Test cases with descriptions - test_cases = { - "default_path": { - "data": b"\x01\x02\x03\x04\x05", - "description": "Default path - simple sum of all bytes" - }, - "magic_value_1": { - "data": b"\xDE\x01\x02\x03\x04\x05", - "description": "Magic Value 1 path (0xDE at data[0]) - sum first 4 bytes + 0x10" - }, - "magic_value_2": { - "data": b"\x01\xAD\x02\x03\x04\x05", - "description": "Magic Value 2 path (0xAD at data[1]) - XOR all bytes + 0x20" - }, - "edge_magic1_short": { - "data": b"\xDE\x01", - "description": "Edge case: Magic Value 1, but short data (only 2 bytes)" - }, - "edge_magic2_too_short": { - "data": b"\xAD", - "description": "Edge case: Magic Value 2, but too short (fallback to default)" - }, - "both_magic_values": { - "data": b"\xDE\xAD\x01\x02", - "description": "Both magic values present, DE at [0] takes precedence" - } - } - - # Assertions with descriptions - directly call functions with test data - self.assertEqual(run_checksum_emu(test_cases["default_path"]["data"]), calculate_expected_checksum(test_cases["default_path"]["data"])) # Default path - self.assertEqual(run_checksum_emu(test_cases["magic_value_1"]["data"]), calculate_expected_checksum(test_cases["magic_value_1"]["data"])) # Magic Value 1 path - self.assertEqual(run_checksum_emu(test_cases["magic_value_2"]["data"]), calculate_expected_checksum(test_cases["magic_value_2"]["data"])) # Magic Value 2 path - self.assertEqual(run_checksum_emu(test_cases["edge_magic1_short"]["data"]), calculate_expected_checksum(test_cases["edge_magic1_short"]["data"])) # Edge case: Magic Value 1, short data - self.assertEqual(run_checksum_emu(test_cases["edge_magic2_too_short"]["data"]), calculate_expected_checksum(test_cases["edge_magic2_too_short"]["data"])) # Edge case: Magic Value 2, too short - self.assertEqual(run_checksum_emu(test_cases["both_magic_values"]["data"]), calculate_expected_checksum(test_cases["both_magic_values"]["data"])) # Both magic values, DE takes precedence + test_input = b"\x01\x02\x03\x04\x05" + self.assertEqual(run_checksum_emu(test_input), calculate_expected_checksum(test_input)) if __name__ == "__main__":