mirror of
https://github.com/qilingframework/qiling.git
synced 2026-10-12 02:21:40 +00:00
adding support for raw binary blobs
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
[BLOB_RAW]
|
||||
load_address = 0x10000000
|
||||
image_size = 0xbc
|
||||
image_name = example_raw.bin
|
||||
@@ -0,0 +1,81 @@
|
||||
##############################################################################
|
||||
# Added example for raw binary blob
|
||||
# Kelly Patterson - Cisco Talos
|
||||
# Copyright (C) 2025 Cisco Systems Inc
|
||||
##############################################################################
|
||||
from qiling import Qiling
|
||||
from qiling.const import QL_ARCH, QL_OS, QL_VERBOSE
|
||||
from qiling.extensions.coverage import utils as cov_utils
|
||||
|
||||
BASE_ADDRESS = 0x10000000
|
||||
CHECKSUM_FUNC_ADDR = BASE_ADDRESS + 0x8
|
||||
END_ADDRESS = 0x100000ba
|
||||
DATA_ADDR = 0xa0000000 # Arbitrary address for data
|
||||
STACK_ADDR = 0xb0000000 # Arbitrary address for stack
|
||||
|
||||
# Python implementation of the checksum function being emulated
|
||||
def checksum_function(input_data_buffer: bytes):
|
||||
expected_checksum_python = 0
|
||||
input_data_len = len(input_data_buffer)
|
||||
if input_data_len >= 1 and input_data_buffer[0] == 0xDE: # MAGIC_VALUE_1
|
||||
for i in range(min(input_data_len, 4)):
|
||||
expected_checksum_python += input_data_buffer[i]
|
||||
expected_checksum_python += 0x10
|
||||
elif input_data_len >= 2 and input_data_buffer[1] == 0xAD: # MAGIC_VALUE_2
|
||||
for i in range(input_data_len):
|
||||
expected_checksum_python ^= input_data_buffer[i]
|
||||
expected_checksum_python += 0x20
|
||||
else:
|
||||
for i in range(input_data_len):
|
||||
expected_checksum_python += input_data_buffer[i]
|
||||
expected_checksum_python &= 0xFF # Ensure it's a single byte
|
||||
|
||||
def unmapped_handler(ql, type, addr, size, value):
|
||||
|
||||
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):
|
||||
print(f"\n--- Testing with input: {input_data_buffer.hex()} ---")
|
||||
|
||||
ql = Qiling(archtype=QL_ARCH.ARM, ostype=QL_OS.BLOB, profile="blob_raw.ql", verbose=QL_VERBOSE.DEBUG, thumb=True)
|
||||
|
||||
input_data_len = len(input_data_buffer)
|
||||
|
||||
# Map memory for the binary, data and stack
|
||||
ql.mem.map(BASE_ADDRESS, 0x10000)
|
||||
ql.mem.map(STACK_ADDR, 0x2000)
|
||||
ql.mem.map(DATA_ADDR, ql.mem.align_up(input_data_len + 0x100)) # Map enough space for data
|
||||
|
||||
# Write the binary into memory
|
||||
ql.mem.write(BASE_ADDRESS, open("rootfs/blob/example_raw.bin", "rb").read())
|
||||
|
||||
# Write input data
|
||||
ql.mem.write(DATA_ADDR, input_data_buffer)
|
||||
|
||||
# Set up the stack pointer
|
||||
ql.arch.regs.sp = STACK_ADDR + 0x2000 - 4
|
||||
# Set up argument registers
|
||||
ql.arch.regs.r0 = DATA_ADDR
|
||||
ql.arch.regs.r1 = input_data_len
|
||||
|
||||
# Set the program counter to the function's entry point
|
||||
ql.arch.regs.pc = CHECKSUM_FUNC_ADDR
|
||||
|
||||
# Set the return address (LR) to a dummy address.
|
||||
ql.arch.regs.lr = 0xbebebebe
|
||||
|
||||
ql.hook_mem_unmapped(unmapped_handler)
|
||||
#ql.debugger="gdb:127.0.0.1:9999"
|
||||
|
||||
# Start emulation
|
||||
print(f"Starting emulation at PC: {hex(ql.arch.regs.pc)}")
|
||||
try:
|
||||
ql.run(begin=CHECKSUM_FUNC_ADDR, end=END_ADDRESS)
|
||||
except Exception as e:
|
||||
print(f"Emulation error: {e}")
|
||||
|
||||
print(f"Emulated checksum: {hex(ql.arch.regs.r0)}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
data = b"\x01\x02\x03\x04\x05" # Example input data
|
||||
emulate_checksum_function(data)
|
||||
@@ -0,0 +1,57 @@
|
||||
##############################################################################
|
||||
# Added example for raw binary blob
|
||||
# Kelly Patterson - Cisco Talos
|
||||
# Copyright (C) 2025 Cisco Systems Inc
|
||||
##############################################################################
|
||||
# Makefile for Bare-Metal ARM Hash Calculator
|
||||
|
||||
# --- Toolchain Definitions ---
|
||||
TOOLCHAIN_PREFIX = arm-none-eabi
|
||||
|
||||
# Compiler, Linker, and Objcopy executables
|
||||
CC = $(TOOLCHAIN_PREFIX)-gcc
|
||||
LD = $(TOOLCHAIN_PREFIX)-gcc
|
||||
OBJCOPY = $(TOOLCHAIN_PREFIX)-objcopy
|
||||
|
||||
# --- Source and Output Files ---
|
||||
SRCS = example_raw.c
|
||||
OBJS = $(SRCS:.c=.o) # Convert .c to .o
|
||||
ELF = example_raw.elf
|
||||
BIN = example_raw.bin
|
||||
|
||||
# --- Linker Script ---
|
||||
LDSCRIPT = linker.ld
|
||||
|
||||
# --- Compiler Flags ---
|
||||
CFLAGS = -c -O0 -mcpu=cortex-a7 -mthumb -ffreestanding -nostdlib
|
||||
|
||||
# --- Linker Flags ---
|
||||
LDFLAGS = -T $(LDSCRIPT) -nostdlib
|
||||
|
||||
# --- Objcopy Flags ---
|
||||
OBJCOPYFLAGS = -O binary
|
||||
|
||||
# --- Default Target ---
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(BIN)
|
||||
|
||||
# Rule to build the raw binary (.bin) from the ELF file
|
||||
$(BIN): $(ELF)
|
||||
$(OBJCOPY) $(OBJCOPYFLAGS) $< $@
|
||||
@echo "Successfully created $(BIN)"
|
||||
|
||||
# Rule to link the object file into an ELF executable
|
||||
$(ELF): $(OBJS) $(LDSCRIPT)
|
||||
$(LD) $(LDFLAGS) $(OBJS) -o $@
|
||||
@echo "Successfully linked $(ELF)"
|
||||
|
||||
# Rule to compile the C source file into an object file
|
||||
%.o: %.c
|
||||
$(CC) $(CFLAGS) $< -o $@
|
||||
@echo "Successfully compiled $<"
|
||||
|
||||
# --- Clean Rule ---
|
||||
clean:
|
||||
rm -f $(OBJS) $(ELF) $(BIN)
|
||||
@echo "Cleaned build artifacts."
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Added example for raw binary blob
|
||||
* Kelly Patterson - Cisco Talos
|
||||
* Copyright (C) 2025 Cisco Systems Inc
|
||||
*
|
||||
*/
|
||||
// example_raw.c
|
||||
|
||||
// Define some magic values
|
||||
#define MAGIC_VALUE_1 0xDE
|
||||
#define MAGIC_VALUE_2 0xAD
|
||||
|
||||
// This function calculates a checksum with branches based on input data
|
||||
// It takes a pointer to data and its length
|
||||
// Returns the checksum (unsigned char to fit in a byte)
|
||||
unsigned char calculate_checksum(const unsigned char *data, unsigned int length) {
|
||||
unsigned char checksum = 0;
|
||||
|
||||
// Branch 1: Check for MAGIC_VALUE_1 at the start
|
||||
if (length >= 1 && data[0] == MAGIC_VALUE_1) {
|
||||
// If first byte is MAGIC_VALUE_1, do a simple sum of first 4 bytes
|
||||
// (or up to length if less than 4)
|
||||
for (unsigned int i = 0; i < length && i < 4; i++) {
|
||||
checksum += data[i];
|
||||
}
|
||||
// Add a fixed offset to make this path distinct
|
||||
checksum += 0x10;
|
||||
}
|
||||
// Branch 2: Check for MAGIC_VALUE_2 at the second byte
|
||||
else if (length >= 2 && data[1] == MAGIC_VALUE_2) {
|
||||
// If second byte is MAGIC_VALUE_2, do a XOR sum of all bytes
|
||||
for (unsigned int i = 0; i < length; i++) {
|
||||
checksum ^= data[i];
|
||||
}
|
||||
// Add a fixed offset to make this path distinct
|
||||
checksum += 0x20;
|
||||
}
|
||||
// Default Branch: Standard byte sum checksum
|
||||
else {
|
||||
for (unsigned int i = 0; i < length; i++) {
|
||||
checksum += data[i];
|
||||
}
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
// Minimal entry point for bare-metal.
|
||||
// This function will not be called directly during Qiling emulation,
|
||||
// but it's needed for the linker to have an entry point.
|
||||
__attribute__((section(".text.startup")))
|
||||
void _start() {
|
||||
// In a real bare-metal application, this would initialize hardware,
|
||||
// set up stacks, etc. For this example, it's just a placeholder.
|
||||
// We'll call calculate_checksum directly from our Qiling script.
|
||||
|
||||
while (1) {
|
||||
// Do nothing, or perhaps put the CPU to sleep
|
||||
asm volatile ("wfi"); // Wait For Interrupt (ARM instruction)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/* linker.ld */
|
||||
/*
|
||||
* Added example for raw binary blob
|
||||
* Kelly Patterson - Cisco Talos
|
||||
* Copyright (C) 2025 Cisco Systems Inc
|
||||
*
|
||||
*/
|
||||
|
||||
ENTRY(_start) /* Define the entry point of our program */
|
||||
|
||||
/* Define memory regions - simple RAM region for this example */
|
||||
MEMORY
|
||||
{
|
||||
ram (rwx) : ORIGIN = 0x10000000, LENGTH = 64K /* 64KB of RAM for our program */
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
/* Define the start of our program in memory.
|
||||
*/
|
||||
. = 0x10000000;
|
||||
|
||||
.text : {
|
||||
KEEP(*(.text.startup)) /* Keep the _start function */
|
||||
*(.text) /* All other code */
|
||||
*(.text.*)
|
||||
*(.rodata) /* Read-only data */
|
||||
*(.rodata.*)
|
||||
. = ALIGN(4);
|
||||
} > ram /* Place .text section in the 'ram' region */
|
||||
|
||||
.data : {
|
||||
. = ALIGN(4);
|
||||
*(.data) /* Initialized data */
|
||||
*(.data.*)
|
||||
. = ALIGN(4);
|
||||
} > ram
|
||||
|
||||
.bss : {
|
||||
. = ALIGN(4);
|
||||
*(.bss)
|
||||
*(.bss.*)
|
||||
. = ALIGN(4);
|
||||
} > ram
|
||||
}
|
||||
+26
-18
@@ -2,6 +2,9 @@
|
||||
#
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
# Added support for raw binary blob emulation
|
||||
# Kelly Patterson - Cisco Talos
|
||||
# Copyright (C) 2025 Cisco Systems Inc
|
||||
|
||||
from qiling import Qiling
|
||||
from qiling.loader.loader import QlLoader, Image
|
||||
@@ -12,27 +15,32 @@ class QlLoaderBLOB(QlLoader):
|
||||
def __init__(self, ql: Qiling):
|
||||
super().__init__(ql)
|
||||
|
||||
self.load_address = 0
|
||||
|
||||
def run(self):
|
||||
self.load_address = self.ql.os.load_address
|
||||
self.entry_point = self.ql.os.entry_point
|
||||
if self.ql.os.profile.has_section("BLOB_RAW"):
|
||||
# For raw binary blobs, user will handle memory mapping
|
||||
self.load_address = int(self.ql.os.profile.get("BLOB_RAW", "load_address"), 16)
|
||||
image_size = int(self.ql.os.profile.get("BLOB_RAW", "image_size"), 16)
|
||||
image_name = self.ql.os.profile.get("BLOB_RAW", "image_name", fallback="blob.raw")
|
||||
self.images.append(Image(self.load_address, self.load_address+image_size, image_name)) # used to collect coverage
|
||||
else:
|
||||
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
|
||||
code_ends = code_begins + code_size
|
||||
code_begins = self.load_address
|
||||
code_size = self.ql.os.code_ram_size
|
||||
code_ends = code_begins + code_size
|
||||
|
||||
self.ql.mem.map(code_begins, code_size, info="[code]")
|
||||
self.ql.mem.write(code_begins, self.ql.code)
|
||||
self.ql.mem.map(code_begins, code_size, info="[code]")
|
||||
self.ql.mem.write(code_begins, self.ql.code)
|
||||
|
||||
# allow image-related functionalities
|
||||
self.images.append(Image(code_begins, code_ends, 'blob_code'))
|
||||
# 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
|
||||
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: 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
|
||||
# FIXME: stack pointer should be a configurable profile setting
|
||||
self.ql.arch.regs.arch_sp = code_ends - 0x1000
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
#
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
# Added support for raw binary blob emulation
|
||||
# Kelly Patterson - Cisco Talos
|
||||
# Copyright (C) 2025 Cisco Systems Inc
|
||||
|
||||
from qiling import Qiling
|
||||
from qiling.cc import QlCC, intel, arm, mips, riscv, ppc
|
||||
@@ -44,10 +47,10 @@ class QlOsBlob(QlOs):
|
||||
if self.ql.entry_point is not None:
|
||||
self.entry_point = self.ql.entry_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
|
||||
elif self.ql.code is not None: # self.ql.code might not always be provided
|
||||
self.exit_point = self.load_address + len(self.ql.code)
|
||||
|
||||
self.ql.emu_start(self.entry_point, self.exit_point, self.ql.timeout, self.ql.count)
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
[BLOB_RAW]
|
||||
load_address = 0x10000000
|
||||
image_size = 0xbc
|
||||
image_name = example_raw.bin
|
||||
@@ -2,6 +2,9 @@
|
||||
#
|
||||
# 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
|
||||
|
||||
import unittest
|
||||
|
||||
@@ -82,6 +85,102 @@ class BlobTest(unittest.TestCase):
|
||||
|
||||
del ql
|
||||
|
||||
@unittest.skip("Temporarily disabled")
|
||||
def test_blob_checksum_calculations(self):
|
||||
def run_checksum_emu(input_data_buffer: bytes) -> int:
|
||||
"""
|
||||
Callable function that takes input data buffer and returns the checksum.
|
||||
"""
|
||||
BASE_ADDRESS = 0x10000000
|
||||
CHECKSUM_FUNC_ADDR = BASE_ADDRESS + 0x8
|
||||
END_ADDRESS = 0x100000ba
|
||||
DATA_ADDR = 0xa0000000
|
||||
STACK_ADDR = 0xb0000000
|
||||
|
||||
ql = Qiling(archtype=QL_ARCH.ARM, ostype=QL_OS.BLOB, profile="profiles/blob_raw.ql", verbose=QL_VERBOSE.DEBUG, thumb=True)
|
||||
|
||||
input_data_len = len(input_data_buffer)
|
||||
|
||||
# Map memory for the binary, data and stack
|
||||
ql.mem.map(BASE_ADDRESS, 0x10000)
|
||||
ql.mem.map(STACK_ADDR, 0x2000)
|
||||
ql.mem.map(DATA_ADDR, ql.mem.align_up(input_data_len + 0x100))
|
||||
|
||||
# Write the binary into memory
|
||||
ql.mem.write(BASE_ADDRESS, open("../examples/rootfs/blob/example_raw.bin", "rb").read())
|
||||
|
||||
# Write input data
|
||||
ql.mem.write(DATA_ADDR, input_data_buffer)
|
||||
|
||||
# Set up registers
|
||||
ql.arch.regs.sp = STACK_ADDR + 0x2000 - 4
|
||||
ql.arch.regs.r0 = DATA_ADDR
|
||||
ql.arch.regs.r1 = input_data_len
|
||||
ql.arch.regs.pc = CHECKSUM_FUNC_ADDR
|
||||
ql.arch.regs.lr = 0xbebebebe
|
||||
|
||||
ql.run(begin=CHECKSUM_FUNC_ADDR, end=END_ADDRESS)
|
||||
result = ql.arch.regs.r0
|
||||
|
||||
return result
|
||||
|
||||
def calculate_expected_checksum(input_data_buffer: bytes) -> int:
|
||||
"""
|
||||
Python implementation of the expected checksum calculation.
|
||||
"""
|
||||
input_data_len = len(input_data_buffer)
|
||||
expected_checksum = 0
|
||||
|
||||
if input_data_len >= 1 and input_data_buffer[0] == 0xDE: # MAGIC_VALUE_1
|
||||
for i in range(min(input_data_len, 4)):
|
||||
expected_checksum += input_data_buffer[i]
|
||||
expected_checksum += 0x10
|
||||
elif input_data_len >= 2 and input_data_buffer[1] == 0xAD: # MAGIC_VALUE_2
|
||||
for i in range(input_data_len):
|
||||
expected_checksum ^= input_data_buffer[i]
|
||||
expected_checksum += 0x20
|
||||
else:
|
||||
for i in range(input_data_len):
|
||||
expected_checksum += input_data_buffer[i]
|
||||
|
||||
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
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user