Skip to content

Commit d3cc47d

Browse files
committed
adding support for raw binary blobs
1 parent 2f604f4 commit d3cc47d

File tree

9 files changed

+387
-25
lines changed

9 files changed

+387
-25
lines changed

examples/blob_raw.ql

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[BLOB_RAW]
2+
load_address = 0x10000000
3+
image_size = 0xbc
4+
image_name = example_raw.bin

examples/hello_arm_blob_raw.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
##############################################################################
2+
# Added example for raw binary blob
3+
# Kelly Patterson - Cisco Talos
4+
# Copyright (C) 2025 Cisco Systems Inc
5+
##############################################################################
6+
from qiling import Qiling
7+
from qiling.const import QL_ARCH, QL_OS, QL_VERBOSE
8+
from qiling.extensions.coverage import utils as cov_utils
9+
10+
BASE_ADDRESS = 0x10000000
11+
CHECKSUM_FUNC_ADDR = BASE_ADDRESS + 0x8
12+
END_ADDRESS = 0x100000ba
13+
DATA_ADDR = 0xa0000000 # Arbitrary address for data
14+
STACK_ADDR = 0xb0000000 # Arbitrary address for stack
15+
16+
# Python implementation of the checksum function being emulated
17+
def checksum_function(input_data_buffer: bytes):
18+
expected_checksum_python = 0
19+
input_data_len = len(input_data_buffer)
20+
if input_data_len >= 1 and input_data_buffer[0] == 0xDE: # MAGIC_VALUE_1
21+
for i in range(min(input_data_len, 4)):
22+
expected_checksum_python += input_data_buffer[i]
23+
expected_checksum_python += 0x10
24+
elif input_data_len >= 2 and input_data_buffer[1] == 0xAD: # MAGIC_VALUE_2
25+
for i in range(input_data_len):
26+
expected_checksum_python ^= input_data_buffer[i]
27+
expected_checksum_python += 0x20
28+
else:
29+
for i in range(input_data_len):
30+
expected_checksum_python += input_data_buffer[i]
31+
expected_checksum_python &= 0xFF # Ensure it's a single byte
32+
33+
def unmapped_handler(ql, type, addr, size, value):
34+
35+
print(f"Unmapped Memory R/W, trying to access {hex(size)} bytes at {hex(addr)} from {hex(ql.arch.regs.pc)}")
36+
37+
def emulate_checksum_function(input_data_buffer: bytes):
38+
print(f"\n--- Testing with input: {input_data_buffer.hex()} ---")
39+
40+
ql = Qiling(archtype=QL_ARCH.ARM, ostype=QL_OS.BLOB, profile="blob_raw.ql", verbose=QL_VERBOSE.DEBUG, thumb=True)
41+
42+
input_data_len = len(input_data_buffer)
43+
44+
# Map memory for the binary, data and stack
45+
ql.mem.map(BASE_ADDRESS, 0x10000)
46+
ql.mem.map(STACK_ADDR, 0x2000)
47+
ql.mem.map(DATA_ADDR, ql.mem.align_up(input_data_len + 0x100)) # Map enough space for data
48+
49+
# Write the binary into memory
50+
ql.mem.write(BASE_ADDRESS, open("rootfs/blob/example_raw.bin", "rb").read())
51+
52+
# Write input data
53+
ql.mem.write(DATA_ADDR, input_data_buffer)
54+
55+
# Set up the stack pointer
56+
ql.arch.regs.sp = STACK_ADDR + 0x2000 - 4
57+
# Set up argument registers
58+
ql.arch.regs.r0 = DATA_ADDR
59+
ql.arch.regs.r1 = input_data_len
60+
61+
# Set the program counter to the function's entry point
62+
ql.arch.regs.pc = CHECKSUM_FUNC_ADDR
63+
64+
# Set the return address (LR) to a dummy address.
65+
ql.arch.regs.lr = 0xbebebebe
66+
67+
ql.hook_mem_unmapped(unmapped_handler)
68+
#ql.debugger="gdb:127.0.0.1:9999"
69+
70+
# Start emulation
71+
print(f"Starting emulation at PC: {hex(ql.arch.regs.pc)}")
72+
try:
73+
ql.run(begin=CHECKSUM_FUNC_ADDR, end=END_ADDRESS)
74+
except Exception as e:
75+
print(f"Emulation error: {e}")
76+
77+
print(f"Emulated checksum: {hex(ql.arch.regs.r0)}")
78+
79+
if __name__ == "__main__":
80+
data = b"\x01\x02\x03\x04\x05" # Example input data
81+
emulate_checksum_function(data)

examples/src/blob/Makefile

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
##############################################################################
2+
# Added example for raw binary blob
3+
# Kelly Patterson - Cisco Talos
4+
# Copyright (C) 2025 Cisco Systems Inc
5+
##############################################################################
6+
# Makefile for Bare-Metal ARM Hash Calculator
7+
8+
# --- Toolchain Definitions ---
9+
TOOLCHAIN_PREFIX = arm-none-eabi
10+
11+
# Compiler, Linker, and Objcopy executables
12+
CC = $(TOOLCHAIN_PREFIX)-gcc
13+
LD = $(TOOLCHAIN_PREFIX)-gcc
14+
OBJCOPY = $(TOOLCHAIN_PREFIX)-objcopy
15+
16+
# --- Source and Output Files ---
17+
SRCS = example_raw.c
18+
OBJS = $(SRCS:.c=.o) # Convert .c to .o
19+
ELF = example_raw.elf
20+
BIN = example_raw.bin
21+
22+
# --- Linker Script ---
23+
LDSCRIPT = linker.ld
24+
25+
# --- Compiler Flags ---
26+
CFLAGS = -c -O0 -mcpu=cortex-a7 -mthumb -ffreestanding -nostdlib
27+
28+
# --- Linker Flags ---
29+
LDFLAGS = -T $(LDSCRIPT) -nostdlib
30+
31+
# --- Objcopy Flags ---
32+
OBJCOPYFLAGS = -O binary
33+
34+
# --- Default Target ---
35+
.PHONY: all clean
36+
37+
all: $(BIN)
38+
39+
# Rule to build the raw binary (.bin) from the ELF file
40+
$(BIN): $(ELF)
41+
$(OBJCOPY) $(OBJCOPYFLAGS) $< $@
42+
@echo "Successfully created $(BIN)"
43+
44+
# Rule to link the object file into an ELF executable
45+
$(ELF): $(OBJS) $(LDSCRIPT)
46+
$(LD) $(LDFLAGS) $(OBJS) -o $@
47+
@echo "Successfully linked $(ELF)"
48+
49+
# Rule to compile the C source file into an object file
50+
%.o: %.c
51+
$(CC) $(CFLAGS) $< -o $@
52+
@echo "Successfully compiled $<"
53+
54+
# --- Clean Rule ---
55+
clean:
56+
rm -f $(OBJS) $(ELF) $(BIN)
57+
@echo "Cleaned build artifacts."

examples/src/blob/example_raw.c

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Added example for raw binary blob
3+
* Kelly Patterson - Cisco Talos
4+
* Copyright (C) 2025 Cisco Systems Inc
5+
*
6+
*/
7+
// example_raw.c
8+
9+
// Define some magic values
10+
#define MAGIC_VALUE_1 0xDE
11+
#define MAGIC_VALUE_2 0xAD
12+
13+
// This function calculates a checksum with branches based on input data
14+
// It takes a pointer to data and its length
15+
// Returns the checksum (unsigned char to fit in a byte)
16+
unsigned char calculate_checksum(const unsigned char *data, unsigned int length) {
17+
unsigned char checksum = 0;
18+
19+
// Branch 1: Check for MAGIC_VALUE_1 at the start
20+
if (length >= 1 && data[0] == MAGIC_VALUE_1) {
21+
// If first byte is MAGIC_VALUE_1, do a simple sum of first 4 bytes
22+
// (or up to length if less than 4)
23+
for (unsigned int i = 0; i < length && i < 4; i++) {
24+
checksum += data[i];
25+
}
26+
// Add a fixed offset to make this path distinct
27+
checksum += 0x10;
28+
}
29+
// Branch 2: Check for MAGIC_VALUE_2 at the second byte
30+
else if (length >= 2 && data[1] == MAGIC_VALUE_2) {
31+
// If second byte is MAGIC_VALUE_2, do a XOR sum of all bytes
32+
for (unsigned int i = 0; i < length; i++) {
33+
checksum ^= data[i];
34+
}
35+
// Add a fixed offset to make this path distinct
36+
checksum += 0x20;
37+
}
38+
// Default Branch: Standard byte sum checksum
39+
else {
40+
for (unsigned int i = 0; i < length; i++) {
41+
checksum += data[i];
42+
}
43+
}
44+
45+
return checksum;
46+
}
47+
48+
// Minimal entry point for bare-metal.
49+
// This function will not be called directly during Qiling emulation,
50+
// but it's needed for the linker to have an entry point.
51+
__attribute__((section(".text.startup")))
52+
void _start() {
53+
// In a real bare-metal application, this would initialize hardware,
54+
// set up stacks, etc. For this example, it's just a placeholder.
55+
// We'll call calculate_checksum directly from our Qiling script.
56+
57+
while (1) {
58+
// Do nothing, or perhaps put the CPU to sleep
59+
asm volatile ("wfi"); // Wait For Interrupt (ARM instruction)
60+
}
61+
}

examples/src/blob/linker.ld

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/* linker.ld */
2+
/*
3+
* Added example for raw binary blob
4+
* Kelly Patterson - Cisco Talos
5+
* Copyright (C) 2025 Cisco Systems Inc
6+
*
7+
*/
8+
9+
ENTRY(_start) /* Define the entry point of our program */
10+
11+
/* Define memory regions - simple RAM region for this example */
12+
MEMORY
13+
{
14+
ram (rwx) : ORIGIN = 0x10000000, LENGTH = 64K /* 64KB of RAM for our program */
15+
}
16+
17+
SECTIONS
18+
{
19+
/* Define the start of our program in memory.
20+
*/
21+
. = 0x10000000;
22+
23+
.text : {
24+
KEEP(*(.text.startup)) /* Keep the _start function */
25+
*(.text) /* All other code */
26+
*(.text.*)
27+
*(.rodata) /* Read-only data */
28+
*(.rodata.*)
29+
. = ALIGN(4);
30+
} > ram /* Place .text section in the 'ram' region */
31+
32+
.data : {
33+
. = ALIGN(4);
34+
*(.data) /* Initialized data */
35+
*(.data.*)
36+
. = ALIGN(4);
37+
} > ram
38+
39+
.bss : {
40+
. = ALIGN(4);
41+
*(.bss)
42+
*(.bss.*)
43+
. = ALIGN(4);
44+
} > ram
45+
}

qiling/loader/blob.py

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
#
33
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
44
#
5+
# Added support for raw binary blob emulation
6+
# Kelly Patterson - Cisco Talos
7+
# Copyright (C) 2025 Cisco Systems Inc
58

69
from qiling import Qiling
710
from qiling.loader.loader import QlLoader, Image
@@ -12,27 +15,32 @@ class QlLoaderBLOB(QlLoader):
1215
def __init__(self, ql: Qiling):
1316
super().__init__(ql)
1417

15-
self.load_address = 0
16-
1718
def run(self):
18-
self.load_address = self.ql.os.load_address
19-
self.entry_point = self.ql.os.entry_point
20-
21-
code_begins = self.load_address
22-
code_size = self.ql.os.code_ram_size
23-
code_ends = code_begins + code_size
24-
25-
self.ql.mem.map(code_begins, code_size, info="[code]")
26-
self.ql.mem.write(code_begins, self.ql.code)
27-
28-
# allow image-related functionalities
29-
self.images.append(Image(code_begins, code_ends, 'blob_code'))
30-
31-
# FIXME: heap starts above end of ram??
32-
# FIXME: heap should be allocated by OS, not loader
33-
heap_base = code_ends
34-
heap_size = int(self.ql.os.profile.get("CODE", "heap_size"), 16)
35-
self.ql.os.heap = QlMemoryHeap(self.ql, heap_base, heap_base + heap_size)
36-
37-
# FIXME: stack pointer should be a configurable profile setting
38-
self.ql.arch.regs.arch_sp = code_ends - 0x1000
19+
if self.ql.os.profile.has_section("BLOB_RAW"):
20+
# For raw binary blobs, user will handle memory mapping
21+
self.load_address = int(self.ql.os.profile.get("BLOB_RAW", "load_address"), 16)
22+
image_size = int(self.ql.os.profile.get("BLOB_RAW", "image_size"), 16)
23+
image_name = self.ql.os.profile.get("BLOB_RAW", "image_name", fallback="blob.raw")
24+
self.images.append(Image(self.load_address, self.load_address+image_size, image_name)) # used to collect coverage
25+
else:
26+
self.load_address = self.ql.os.load_address
27+
self.entry_point = self.ql.os.entry_point
28+
29+
code_begins = self.load_address
30+
code_size = self.ql.os.code_ram_size
31+
code_ends = code_begins + code_size
32+
33+
self.ql.mem.map(code_begins, code_size, info="[code]")
34+
self.ql.mem.write(code_begins, self.ql.code)
35+
36+
# allow image-related functionalities
37+
self.images.append(Image(code_begins, code_ends, 'blob_code'))
38+
39+
# FIXME: heap starts above end of ram??
40+
# FIXME: heap should be allocated by OS, not loader
41+
heap_base = code_ends
42+
heap_size = int(self.ql.os.profile.get("CODE", "heap_size"), 16)
43+
self.ql.os.heap = QlMemoryHeap(self.ql, heap_base, heap_base + heap_size)
44+
45+
# FIXME: stack pointer should be a configurable profile setting
46+
self.ql.arch.regs.arch_sp = code_ends - 0x1000

qiling/os/blob/blob.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
#
33
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
44
#
5+
# Added support for raw binary blob emulation
6+
# Kelly Patterson - Cisco Talos
7+
# Copyright (C) 2025 Cisco Systems Inc
58

69
from qiling import Qiling
710
from qiling.cc import QlCC, intel, arm, mips, riscv, ppc
@@ -44,10 +47,10 @@ def run(self):
4447
if self.ql.entry_point is not None:
4548
self.entry_point = self.ql.entry_point
4649

47-
self.exit_point = self.load_address + len(self.ql.code)
48-
4950
# if exit point was set explicitly, override the default one
5051
if self.ql.exit_point is not None:
5152
self.exit_point = self.ql.exit_point
53+
elif self.ql.code is not None: # self.ql.code might not always be provided
54+
self.exit_point = self.load_address + len(self.ql.code)
5255

5356
self.ql.emu_start(self.entry_point, self.exit_point, self.ql.timeout, self.ql.count)

tests/profiles/blob_raw.ql

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[BLOB_RAW]
2+
load_address = 0x10000000
3+
image_size = 0xbc
4+
image_name = example_raw.bin

0 commit comments

Comments
 (0)