b18: S7 exit router + dstub trap + docs

This commit is contained in:
SashegDev
2026-09-12 10:30:18 +00:00
parent 67539f7351
commit 049f98122b
6 changed files with 1097 additions and 50 deletions
+23
View File
@@ -41,3 +41,26 @@
1. Добить баннер (см. свежий in_asm-хвост).
2. Заменить стаблеты настоящим PBL (нужны дампы с живого тела!).
3. UEFI (img3, EDK2) — отдельный фронт после SBL1.
## b18: S7 и цикл delay-helper (2026-09-12)
Механизм зацикливания (b17, доказан Unicorn-harness `tools/sbl_uni.py`):
- sbl_main → delay-функция `0x8013B78` (256-итераций table-walk, `bl 0x8017AF4`
каждую итерацию) → b14 (`0x8017AFC: b.w 0x8005B9E`) → helper `0x8005BB0`
→ выход `ldr pc,[sp],#0x14` (0x8005BCA) → S3-слот (0x8013BA5, рестарт).
- Эпилог delay (`0x8013BAC: pop {r3-r7,pc}`) снимает 0x18 без push; каждый
S3-рестарт съедает 0x18 живого стека. Маршрут: слоты → fill (STUBV →
pop pc = maze) → pristine IMEM (нули → null-jump → hook_null → maze →
stale lr) → переполнение IMEM (0x8610008).
- Heap SBL (stm в 0x8006684) затирает maze/S6/banner (0, 0x7a, STUBV).
QEMU: dstub-overlay `[0x08006B80,0x08006C50)` (read = снапшот ELF+стабы,
write = игнор). Uni: deferred-restore guard (MEM_WRITE-хук срабатывает
ДО записи, restore надо делать в hook_code!).
- Maze-blob: `movs r0,#0; lsr ip,lr,#24; cmp #8; bne hang; bx lr`
(возврат 0 только для lr из DDRLOW 0x08..., иначе hang).
Фикс b18: патч BAC (`f8bd0a4a``f3f730b8`, `b.w S7`) + S7-стаблет
(52B @ 0x08006C10): `cmp r4,#0xff` → iterate (`0x8013BA5`) или done
(restore r4/r5/sl/fp, возврат в sbl_main `0x802f681`). BAE-путь
(`[r7]==2`) принесён в жертву (r7=0, недостижим).
Фронтир b18: sbl_main возвращается, новый код `0x801Bxxx`
(`strb [r7,#2]`-паттерн, b17-NOP на 0x801B71E), падеж pop'ом пустого
стека у верха IMEM — следующий фронт.
+8 -14
View File
@@ -1,22 +1,16 @@
.syntax unified
.cpu cortex-a7
.thumb
@ maze entry logger: ring buffer of 8 (lr,sp) at MAILBOX, return 0.
@ Preserves all regs except r0 (return value) and r12 (IP, clobber-ok).
@ maze entry: return 0. Guard: bad lr (high byte != 0x08) spins
@ with IRQs off instead of jumping into the void.
.text
.global maze_stub
maze_stub:
push {r0-r3}
movw r12, #:lower16:0x08006890
movt r12, #:upper16:0x08006890
ldr r0, [r12, #32]
add r0, #1
and r0, #7
str r0, [r12, #32]
lsl r0, #3
add r0, r12
str lr, [r0]
str sp, [r0, #4]
pop {r0-r3}
movs r0, #0
lsr r12, lr, #24
cmp r12, #8
bne maze_hang
bx lr
maze_hang:
cpsid i
b maze_hang
+22
View File
@@ -0,0 +1,22 @@
.syntax unified
.cpu cortex-a7
.thumb
@ Minimal PBL stub (position-independent): BLX to SBL1 entry (seg1),
@ then canonical regs + jump to sbl_main. Mirrors PBL call-then-jump.
.text
.global pbl_stub
pbl_stub:
push {r4-r7, lr}
movw r12, #0x6b81
movt r12, #0x800
blx r12 @ SBL1 entry (odd: stay Thumb)
movw r0, #0x0000
movt r0, #0x0022 @ r0 = pbl_shared 0x00220000
mov r5, r0
movw sl, #0x0000
movt sl, #0x0806 @ sl = table base 0x08060000
movw fp, #0x1000
movt fp, #0x0806 @ fp = 0x08061000
movw lr, #0xf63f
movt lr, #0x0802 @ lr = sbl_main odd (noreturn marker)
bx lr
+135 -31
View File
@@ -68,10 +68,13 @@ struct SaimaaTmrState {
static uint64_t saimaa_tmr_read(void *opaque, hwaddr off, unsigned size)
{
/* Deterministic tick (+1/read): advances delay loops but keeps runs
* reproducible (qemu_clock would diverge run to run). */
static uint64_t tick;
(void)opaque;
(void)off;
(void)size;
return (uint64_t)(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) >> 12);
return ++tick;
}
static void saimaa_tmr_write(void *opaque, hwaddr off, uint64_t val,
@@ -101,6 +104,54 @@ static void saimaa_tmr_init(Object *obj)
/* Stubs region handle for set_readonly after scaffold implant. */
static MemoryRegion *saimaa_stubs_mr;
/* DDRLOW stub trap [0x08006B80,0x08006C50): serves the scaffolded bytes
* (S/maze/S6/banner/S7 stubs) on read/fetch, ignores SBL heap-clobber
* writes. Uni parity: deferred-restore guard in sbl_uni.py (wider:
* [0x080068C0,0x08006C50), also covers PBL stub).
* Snapshot is taken AFTER ELF load + all stub writes (readback).
* PBL stub [0x080068C0,0x080068F4) and the ELF-text gap below stay plain
* RAM: PBL runs once (if at all), gap holds real hot SBL code. */
#define SAIMAA_DSTUB_BASE 0x08006B80
#define SAIMAA_DSTUB_SIZE 0x000000D0
static uint8_t saimaa_dstub_mem[SAIMAA_DSTUB_SIZE];
static uint64_t saimaa_dstub_read(void *opaque, hwaddr off, unsigned size)
{
uint64_t v = 0;
unsigned i;
(void)opaque;
if (off >= SAIMAA_DSTUB_SIZE) {
return 0;
}
if (off + size > SAIMAA_DSTUB_SIZE) {
size = SAIMAA_DSTUB_SIZE - off;
}
for (i = 0; i < size; i++) {
v |= (uint64_t)saimaa_dstub_mem[off + i] << (8 * i);
}
return v;
}
static void saimaa_dstub_write(void *opaque, hwaddr off, uint64_t val,
unsigned size)
{
static int n = 0;
(void)opaque;
(void)val;
(void)size;
if (n < 8) {
fprintf(stderr, "saimaa-dstub: ignored heap write @0x%x\n",
SAIMAA_DSTUB_BASE + (unsigned)off);
n++;
}
}
static const MemoryRegionOps saimaa_dstub_ops = {
.read = saimaa_dstub_read,
.write = saimaa_dstub_write,
.endianness = DEVICE_NATIVE_ENDIAN,
};
/* ---- minimal MSM UARTDM stub: TX prints, status always ready ---- */
#define TYPE_SAIMAA_VEC "saimaa-vec"
#define TYPE_SAIMAA_UART "saimaa-uart"
@@ -120,17 +171,9 @@ static uint64_t saimaa_vec_read(void *opaque, hwaddr off, unsigned size)
ARMCPU *cpu = ARM_CPU(current_cpu);
uint64_t pc = cpu ? cpu->env.regs[15] : 0;
int th = cpu ? cpu->env.thumb : 0;
uint32_t mb0 = 0, mb1 = 0;
if (saimaa_vec_n < 3) {
cpu_physical_memory_read(0x08006890, &mb0, 4);
cpu_physical_memory_read(0x08006894, &mb1, 4);
}
fprintf(stderr, "saimaa-vec: %s off=0x%x pc=0x%lx",
size == 2 ? "fetch?" : "read",
(unsigned)off, (unsigned long)pc - (th ? 4 : 8));
if (saimaa_vec_n < 3) {
fprintf(stderr, " maze-mailbox: lr=0x%x sp=0x%x", mb0, mb1);
}
if (cpu) {
int b, r;
fprintf(stderr, " regs=");
@@ -402,6 +445,12 @@ static void saimaa_machine_init(MachineState *machine)
ARM_CPU(first_cpu)->env.banked_r13[b] = 0x085FFF00;
}
ARM_CPU(first_cpu)->env.regs[13] = 0x085FFF00;
/* Stale lr (never bl'd on synthetic paths) returns into maze
* (returns 0 = SBL "empty" convention) instead of jumping to 0. */
ARM_CPU(first_cpu)->env.regs[14] = 0x08006B8D;
for (int b = 0; b < 8; b++) {
ARM_CPU(first_cpu)->env.banked_r14[b] = 0x08006B8D;
}
ARM_CPU(first_cpu)->env.regs[0] = 0x00220000; /* fake pbl_shared */
/* sbl_main expects r5 = mem-table pointer (PBL trampoline does
* mov r5, r0 before jumping here); point it at pbl_shared too. */
@@ -447,7 +496,10 @@ static void saimaa_machine_init(MachineState *machine)
tgt & ~1u, (tgt & 1) ? " thumb" : " arm",
(unsigned long)entry);
} else {
cpu_set_pc(first_cpu, entry);
/* Default: start at PBL stub (faithful call-then-jump flow) */
cpu_set_pc(first_cpu, 0x080068C1);
printf("saimaa: PBL-stub start -> entry 0x%lx then sbl_main\n",
(unsigned long)entry);
}
/* Boot scaffolding for SBL1 BringUp (WIP stub-driven PBL):
* static continuation slots for shared-epilogue pops.
@@ -455,14 +507,14 @@ static void saimaa_machine_init(MachineState *machine)
* (sp values from cpu traces, b10 image) */
{
uint32_t magic = 0x00000000; /* IMEM poll spins WHILE equal */
uint32_t S = 0x08006820, STUB = 0x08006830; /* in-code zeros, safe from heap */
uint32_t STUBV = 0x08006831; /* odd: blx stays Thumb */
uint32_t S = 0x08006B80, STUB = 0x08006B8C; /* dead PBL-entry code */
uint32_t STUBV = 0x08006B8D; /* odd: blx stays Thumb */
uint32_t w;
uint16_t h0 = 0x2000, h1 = 0x4770; /* movs r0,#0; bx lr */
uint32_t v;
/* pointer maze in owned RAM for SBL1 struct chains.
* STUB = mailbox logger + return-0: records lr/sp at entry
* to 0x08006964 for post-mortem, then movs r0,#0; bx lr. */
* STUB = minimal logger + return-0 (movs r0,#0; bx lr);
* mailbox at 0x08006BD0 records last (lr,sp). */
w = S + 8;
cpu_physical_memory_write(S, &w, 4);
/* [struct+4] is another fn ptr (e.g. 0x801b72e ldr r3,[r0,#4]
@@ -471,16 +523,13 @@ static void saimaa_machine_init(MachineState *machine)
cpu_physical_memory_write(S + 4, &STUBV, 4);
cpu_physical_memory_write(S + 8, &STUBV, 4);
{
/* from qemu/maze.S: mailbox logger + lr-range guard.
* Bad lr (not 0x08xxxxxx) spins with IRQs off instead of
* jumping into the void. 36 bytes. */
/* Minimal maze: movs r0,#0; bx lr (4B). No mailbox, no
* guard — vec trap already reports fault state. Less
* surface for SBL heap clobbers. */
static const uint8_t mb[] = {
0x0f, 0xb4, 0x46, 0xf6, 0x90, 0x0c, 0xc0, 0xf6,
0x00, 0x0c, 0xdc, 0xf8, 0x20, 0x00, 0x00, 0xf1,
0x01, 0x00, 0x00, 0xf0, 0x07, 0x00, 0xcc, 0xf8,
0x20, 0x00, 0x4f, 0xea, 0xc0, 0x00, 0x60, 0x44,
0xc0, 0xf8, 0x00, 0xe0, 0xc0, 0xf8, 0x04, 0xd0,
0x0f, 0xbc, 0x00, 0x20, 0x70, 0x47,
0x00, 0x20, 0x4f, 0xea, 0x1e, 0x6c, 0xbc, 0xf1,
0x08, 0x0f, 0x00, 0xd1, 0x70, 0x47, 0x72, 0xb6,
0xfd, 0xe7,
};
size_t k;
for (k = 0; k < sizeof(mb); k += 4) {
@@ -493,7 +542,7 @@ static void saimaa_machine_init(MachineState *machine)
cpu_physical_memory_write(0x087c29d18, &S, 4);
cpu_physical_memory_write(0x08050b28, &S, 4);
cpu_physical_memory_write(0x08070000, &S, 4);
/* S6 stublet at 0x0800685e: rebuild popped frame regs,
/* S6 stublet at 0x08006B9E: rebuild popped frame regs,
* return to 0x8013ba5 (b14 redirects 0x8017afc pop here) */
{
static const uint8_t s6[] = {
@@ -507,10 +556,10 @@ static void saimaa_machine_init(MachineState *machine)
uint32_t word = 0;
size_t n = sizeof(s6) - k < 4 ? sizeof(s6) - k : 4;
__builtin_memcpy(&word, &s6[k], n);
cpu_physical_memory_write(0x0800685e + k, &word, 4);
cpu_physical_memory_write(0x08006B9E + k, &word, 4);
}
}
/* Banner stub at 0x0800686c: print r0 string via UARTDM
/* Banner stub at 0x08006BAC: print r0 string via UARTDM
* (b16 redirects 0x802f690 blx here). Built from qemu/banner.S:
* push frame, print loop, ldr pc,=0x802f694 (callee noreturn).
* 28 bytes total. */
@@ -527,13 +576,54 @@ static void saimaa_machine_init(MachineState *machine)
uint32_t word = 0;
size_t n = sizeof(bn) - k < 4 ? sizeof(bn) - k : 4;
__builtin_memcpy(&word, &bn[k], n);
cpu_physical_memory_write(0x0800686c + k, &word, 4);
cpu_physical_memory_write(0x08006BAC + k, &word, 4);
}
}
/* stack zone defaults to plain-stub addr: every register
* popped from untouched stack becomes callable (returns 0). */
/* PBL stub at 0x080068C0 (52B, from qemu/pbl.S): BLX to SBL1
* entry, then canonical regs + jump to sbl_main. Used when
* sblmain prop is unset (default): faithful PBL call flow. */
{
uint32_t fill = 0x08006831;
static const uint8_t pbl[] = {
0xf0, 0xb5, 0x46, 0xf6, 0x81, 0x3c, 0xc0, 0xf6,
0x00, 0x0c, 0xe0, 0x47, 0x40, 0xf2, 0x00, 0x00,
0xc0, 0xf2, 0x22, 0x00, 0x05, 0x46, 0x40, 0xf2,
0x00, 0x0a, 0xc0, 0xf6, 0x06, 0x0a, 0x41, 0xf2,
0x00, 0x0b, 0xc0, 0xf6, 0x06, 0x0b, 0x4f, 0xf2,
0x3f, 0x6e, 0xc0, 0xf6, 0x02, 0x0e, 0x70, 0x47,
0x00, 0x00, 0x00, 0x00,
};
size_t k;
for (k = 0; k < sizeof(pbl); k += 4) {
uint32_t word = 0;
size_t n = sizeof(pbl) - k < 4 ? sizeof(pbl) - k : 4;
__builtin_memcpy(&word, &pbl[k], n);
cpu_physical_memory_write(0x080068C0 + k, &word, 4);
}
}
/* S7 stublet at 0x08006C10 (52B, b18): delay-epilogue exit
* router. cmp r4,#0xff: iterate (r4<=0xff -> 0x8013ba5) or
* done (restore r4/r5/sl/fp, return to sbl_main 0x802f681).
* Assembled with keystone (see docs/sbl-bringup.md). */
{
static const uint8_t s7[] = {
0xff, 0x2c, 0x13, 0xd9, 0x40, 0xf2, 0x00, 0x04,
0xc0, 0xf2, 0x22, 0x04, 0x40, 0xf2, 0x00, 0x05,
0xc0, 0xf2, 0x22, 0x05, 0x40, 0xf2, 0x00, 0x0a,
0xc0, 0xf6, 0x06, 0x0a, 0x41, 0xf2, 0x00, 0x0b,
0xc0, 0xf6, 0x06, 0x0b, 0xdf, 0xf8, 0x00, 0xf0,
0x81, 0xf6, 0x02, 0x08, 0xdf, 0xf8, 0x00, 0xf0,
0xa5, 0x3b, 0x01, 0x08,
};
size_t k;
for (k = 0; k < sizeof(s7); k += 4) {
uint32_t word = 0;
size_t n = sizeof(s7) - k < 4 ? sizeof(s7) - k : 4;
__builtin_memcpy(&word, &s7[k], n);
cpu_physical_memory_write(0x08006C10 + k, &word, 4);
}
}
{
uint32_t fill = 0x08006B8D;
uint32_t a;
for (a = 0x085F0000; a < 0x08600000; a += 4) {
cpu_physical_memory_write(a, &fill, 4);
@@ -612,6 +702,20 @@ static void saimaa_machine_init(MachineState *machine)
printf("saimaa: uefi tables live\n");
}
printf("saimaa: slots live\n");
/* DDRLOW stub trap overlay: snapshot ELF+stubs, serve on
* read/fetch, ignore heap writes (uni parity). Must come
* AFTER all stub writes above. Priority 2 > RAMs' 1. */
cpu_physical_memory_read(SAIMAA_DSTUB_BASE, saimaa_dstub_mem,
SAIMAA_DSTUB_SIZE);
{
MemoryRegion *dstub = g_new(MemoryRegion, 1);
memory_region_init_io(dstub, NULL, &saimaa_dstub_ops,
NULL, "saimaa-dstub",
SAIMAA_DSTUB_SIZE);
memory_region_add_subregion_overlap(sysmem,
SAIMAA_DSTUB_BASE,
dstub, 2);
}
/* Freeze stubs: any SBL write here now faults loudly
* (data abort with DFAR) instead of silent corruption. */
memory_region_set_readonly(saimaa_stubs_mr, true);
@@ -624,7 +728,7 @@ static void saimaa_machine_init(MachineState *machine)
static void saimaa_post_reset(void *opaque)
{
uint32_t S = 0x08006820;
uint32_t S = 0x08006B80;
(void)opaque;
/* Re-apply pokes that live inside ROM-covered ranges (rom_reset
* restores file bytes over them). */
+403 -5
View File
@@ -1,263 +1,661 @@
#!/usr/bin/env python3
"""sbl_patch.py — reproduce all WIP file patches for SBL1 bring-up.
Usage: ./tools/sbl_patch.py fw/sbl/img0.elf out-dir/
Produces img0_bl.elf, img0_b2..b8.elf (cumulative variants, see STEPS).
Each step is documented with WHY (all WIP, to be replaced by real PBL).
"""
import os
import sys
SEG_OFF, SEG_VA = 0x3000, 0x8005800
BLX2BL = { # blx-imm -> bl (stay Thumb): set bit12 of 2nd halfword
0x802F658: ('d8f7c2ed', 'd8f7c2fd'),
0x802F660: ('d8f702ec', 'd8f702fc'),
}
NOPS = { # call -> NOP NOP (skip unmodellable init/assert; r0/r5 ignored)
0x802F664: 'e4f77efc', # bl walker (result ignored by caller)
0x802F672: 'e6f7bbfa', # bl 0x8015bec (assert-hang)
0x802F678: None, # bl 0x8018a78, see REDIRECT (installer runs!)
0x801B71E: '9847fee7', # blx r3 + b.n self (terminal assert, 4B)
0x8016184: 'fdf790fc', # bl 0x8013aa8 (subtree nopped, see b8)
}
REDIRECT = {
# bl target off-by-N fixes (verified by disasm + QEMU traces):
0x8013AA8: ('07f026be', '07f027be'), # jump-table case0: +2
0x802F678: ('e9f7fef9', 'e9f700fa'), # bl 0x8018a78 -> 0x8018a7c (+4)
}
POOLS = {
# literal pool words redirected to owned scratch (orig struct garbage):
0x801B76C: ('280b0508', '00000708'), # -> 0x08070000
}
def off(va):
return SEG_OFF + (va - SEG_VA)
def hx(d, va, n=4):
return d[off(va):off(va) + n].hex()
def set4(d, va, hexbytes):
d[off(va):off(va) + 4] = bytes.fromhex(hexbytes)
def main():
src, outdir = sys.argv[1], sys.argv[2]
os.makedirs(outdir, exist_ok=True)
base = bytearray(open(src, 'rb').read())
def save(d, name):
p = os.path.join(outdir, name)
open(p, 'wb').write(bytes(d))
print('wrote', p, len(d))
d = bytearray(base)
for va, (a, b) in BLX2BL.items():
assert hx(d, va) == a, (hex(va), hx(d, va))
set4(d, va, b)
save(d, 'img0_bl.elf')
# b2 = bl + walker NOP
o = off(0x802F664)
assert d[o:o + 4].hex() == 'e4f77efc'
d[o:o + 4] = bytes.fromhex('c046c046')
save(d, 'img0_b2.elf')
# b3 = b2 + 0x8015bec NOP
o = off(0x802F672)
assert d[o:o + 4].hex() == 'e6f7bbfa', hx(d, 0x802F672)
d[o:o + 4] = bytes.fromhex('c046c046')
save(d, 'img0_b3.elf')
# b4 = b3 + 0x8018a78 NOP
o = off(0x802F678)
assert d[o:o + 4].hex() == 'e9f7fef9', hx(d, 0x802F678)
d[o:o + 4] = bytes.fromhex('c046c046')
save(d, 'img0_b4.elf')
# b5 = b4 + jump-table +2
o = off(0x8013AA8)
assert d[o:o + 4].hex() == '07f026be'
d[o:o + 4] = bytes.fromhex('07f027be')
save(d, 'img0_b5.elf')
# b6 = b5 + terminal assert NOP (4B)
o = off(0x801B71E)
assert d[o:o + 4].hex() == '9847fee7'
d[o:o + 4] = bytes.fromhex('c046c046')
save(d, 'img0_b6.elf')
# b7 = b6 + redirect bl to installer entry (undoes b4 NOP)
o = off(0x802F678)
assert d[o:o + 4].hex() == 'c046c046'
d[o:o + 4] = bytes.fromhex('e9f700fa')
save(d, 'img0_b7.elf')
# b8 = b7 + subtree NOP + pool redirect
o = off(0x8016184)
assert d[o:o + 4].hex() == 'fdf790fc', hx(d, 0x8016184)
d[o:o + 4] = bytes.fromhex('c046c046')
o = off(0x801B76C)
assert d[o:o + 4].hex() == '280b0508'
d[o:o + 4] = bytes.fromhex('00000708')
save(d, 'img0_b8.elf')
# b9 = b8 + hole-call NOP (EDL-dead padding slide at 0x80068FC)
o = off(0x8008396)
assert d[o:o + 4].hex() == 'fef780f9', hx(d, 0x8008396)
d[o:o + 4] = bytes.fromhex('c046c046')
save(d, 'img0_b9.elf')
# b10 = b9 with installer-bl reverted to NOP (S2 lands mid-NOPs safely,
# falls into the 0x8013b7c call; installer skipped, structs via maze)
o = off(0x802F678)
assert d[o:o + 4].hex() == 'e9f700fa', hx(d, 0x802F678)
d[o:o + 4] = bytes.fromhex('c046c046')
save(d, 'img0_b10.elf')
# b11 = b10 with installer-bl restored (redirect, installer runs again)
o = off(0x802F678)
assert d[o:o + 4].hex() == 'c046c046'
d[o:o + 4] = bytes.fromhex('e9f700fa')
save(d, 'img0_b11.elf')
# b12: b10-variant with jump-table entry0 NOP (fall into entry1/case-1);
# rebuilt from b10 base (installer stays skipped)
d = bytearray(base)
for va, (a, b) in BLX2BL.items():
set4(d, va, b)
for va, exp, new in [
(0x802F664, 'e4f77efc', 'c046c046'),
(0x802F672, 'e6f7bbfa', 'c046c046'),
(0x802F678, 'e9f7fef9', 'c046c046'),
(0x801B71E, '9847fee7', 'c046c046'),
(0x8008396, 'fef780f9', 'c046c046'),
(0x8016184, 'fdf790fc', 'c046c046'),
(0x801B76C, '280b0508', '00000708'),
]:
assert hx(d, va) == exp, (hex(va), hx(d, va))
set4(d, va, new)
o = off(0x8013AA8)
assert d[o:o + 4].hex() == '07f026be', hx(d, 0x8013AA8)
d[o:o + 4] = bytes.fromhex('c046c046')
save(d, 'img0_b12.elf')
# b15 = b14 + force table-loop count (0x8013ba6 ldrh r0,[r7,#4] ->
# movs r0,#0xff): exit pop reads clobbered slot (->0); looping builds
# tables via helpers instead. 2-byte patch.
d = bytearray(base)
for va, (a, b) in BLX2BL.items():
set4(d, va, b)
for va, exp, new in [
(0x802F664, 'e4f77efc', 'c046c046'),
(0x802F672, 'e6f7bbfa', 'c046c046'),
(0x802F678, 'e9f7fef9', 'c046c046'),
(0x801B71E, '9847fee7', 'c046c046'),
(0x8008396, 'fef780f9', 'c046c046'),
(0x8016184, 'fdf790fc', 'c046c046'),
(0x801B76C, '280b0508', '00000708'),
(0x8006684, '01f807bd', '46c046c0'),
(0x8017AFC, 'bde8fc87', 'eef7afbe'),
(0x8017AFC, 'bde8fc87', 'eef74fb8'),
]:
assert hx(d, va) == exp, (hex(va), hx(d, va))
set4(d, va, new)
o = off(0x8013AA8)
assert d[o:o + 4].hex() == '07f026be', hx(d, 0x8013AA8)
d[o:o + 4] = bytes.fromhex('c046c046')
o = off(0x8013BA6)
assert d[o:o + 2].hex() == 'b888', (hex(0x8013BA6), hx(d, 0x8013BA6, 2))
d[o:o + 2] = bytes.fromhex('ff20')
save(d, 'img0_b15.elf')
# b16 = b15 + banner call redirect (0x802f690 blx r3 -> bl banner stub
# at 0x08062150; table[0] is garbage 0x690ce0ee). Stub prints r0 string
# via UARTDM, returns 0 -> falls into cbz path (banner done).
d = bytearray(base)
for va, (a, b) in BLX2BL.items():
set4(d, va, b)
for va, exp, new in [
(0x802F664, 'e4f77efc', 'c046c046'),
(0x802F672, 'e6f7bbfa', 'c046c046'),
(0x802F678, 'e9f7fef9', 'c046c046'),
(0x801B71E, '9847fee7', 'c046c046'),
(0x8008396, 'fef780f9', 'c046c046'),
(0x8016184, 'fdf790fc', 'c046c046'),
(0x801B76C, '280b0508', '00000708'),
(0x8006684, '01f807bd', '46c046c0'),
(0x8017AFC, 'bde8fc87', 'eef7afbe'),
(0x802F690, '9847fee7', 'd7f7ecb8'),
(0x8017AFC, 'bde8fc87', 'eef74fb8'),
(0x802F690, '9847fee7', 'd7f7ba8c'),
]:
assert hx(d, va) == exp, (hex(va), hx(d, va))
set4(d, va, new)
o = off(0x8013AA8)
assert d[o:o + 4].hex() == '07f026be', hx(d, 0x8013AA8)
d[o:o + 4] = bytes.fromhex('c046c046')
o = off(0x8013BA6)
assert d[o:o + 2].hex() == 'b888', (hex(0x8013BA6), hx(d, 0x8013BA6, 2))
d[o:o + 2] = bytes.fromhex('ff20')
save(d, 'img0_b16.elf')
# b14 = b13 + redirect S6 pop (0x8017afc ldmia.w sp!,{r2-r9,sl,pc})
# to S6 stublet at 0x08062140 (pc slot clobbered to 0 at runtime)
d = bytearray(base)
for va, (a, b) in BLX2BL.items():
set4(d, va, b)
for va, exp, new in [
(0x802F664, 'e4f77efc', 'c046c046'),
(0x802F672, 'e6f7bbfa', 'c046c046'),
(0x802F678, 'e9f7fef9', 'c046c046'),
(0x801B71E, '9847fee7', 'c046c046'),
(0x8008396, 'fef780f9', 'c046c046'),
(0x8016184, 'fdf790fc', 'c046c046'),
(0x801B76C, '280b0508', '00000708'),
(0x8006684, '01f807bd', '46c046c0'),
(0x8017AFC, 'bde8fc87', 'eef7afbe'),
(0x8017AFC, 'bde8fc87', 'eef74fb8'),
]:
assert hx(d, va) == exp, (hex(va), hx(d, va))
set4(d, va, new)
o = off(0x8013AA8)
assert d[o:o + 4].hex() == '07f026be', hx(d, 0x8013AA8)
d[o:o + 4] = bytes.fromhex('c046c046')
save(d, 'img0_b14.elf')
# b13 = b12 + NOP strb.w fp,[r1,#-7]! at 0x8006684 (helper stores
# fp into struct r1, but r1 arrives NULL from 0x8007e82 chain;
# store to [0-7]=0xfffffff9 faults; struct unrecoverable here)
d = bytearray(base)
for va, (a, b) in BLX2BL.items():
set4(d, va, b)
for va, exp, new in [
(0x802F664, 'e4f77efc', 'c046c046'),
(0x802F672, 'e6f7bbfa', 'c046c046'),
(0x802F678, 'e9f7fef9', 'c046c046'),
(0x801B71E, '9847fee7', 'c046c046'),
(0x8008396, 'fef780f9', 'c046c046'),
(0x8016184, 'fdf790fc', 'c046c046'),
(0x801B76C, '280b0508', '00000708'),
(0x8006684, '01f807bd', '46c046c0'),
]:
assert hx(d, va) == exp, (hex(va), hx(d, va))
set4(d, va, new)
o = off(0x8013AA8)
assert d[o:o + 4].hex() == '07f026be', hx(d, 0x8013AA8)
d[o:o + 4] = bytes.fromhex('c046c046')
save(d, 'img0_b13.elf')
# b17 = b16 with banner call NOPed (blx r3 + b.n self -> NOP NOP):
# logger is non-essential; banner stub tail gets heap-clobbered and
# the 4B bl return lands mid-insn. Fall through to 0x802f694.
d = bytearray(base)
for va, (a, b) in BLX2BL.items():
set4(d, va, b)
for va, exp, new in [
(0x802F664, 'e4f77efc', 'c046c046'),
(0x802F672, 'e6f7bbfa', 'c046c046'),
(0x802F678, 'e9f7fef9', 'c046c046'),
(0x801B71E, '9847fee7', 'c046c046'),
(0x8008396, 'fef780f9', 'c046c046'),
(0x8016184, 'fdf790fc', 'c046c046'),
(0x801B76C, '280b0508', '00000708'),
(0x8006684, '01f807bd', '46c046c0'),
(0x8017AFC, 'bde8fc87', 'eef7afbe'),
(0x8017AFC, 'bde8fc87', 'eef74fb8'),
(0x802F690, '9847fee7', 'c046c046'),
]:
assert hx(d, va) == exp, (hex(va), hx(d, va))
set4(d, va, new)
o = off(0x8013AA8)
assert d[o:o + 4].hex() == '07f026be', hx(d, 0x8013AA8)
d[o:o + 4] = bytes.fromhex('c046c046')
o = off(0x8013BA6)
assert d[o:o + 2].hex() == 'b888', (hex(0x8013BA6), hx(d, 0x8013BA6, 2))
d[o:o + 2] = bytes.fromhex('ff20')
save(d, 'img0_b17.elf')
# b18 = b17 + S7 exit router (delay-epilogue leak fix):
# 0x8013BAC pop{r3-r7,pc} pops 0x18 with no matching push; S3-slot
# restarts (0x8013BA5) multiply it into a stack-eating cycle
# (slots->fill->zeros->null-loop->IMEM overflow). Route the epilogue
# to S7 (0x08006C10, scaffold stublet): r4<=0xff -> restart loop,
# else restore r4/r5/sl/fp and return to sbl_main (0x802f681).
# NOTE: overwrites BAE-path head (ldr r2,[pc,#0x28], only used when
# [r7]==2 — never in our flow, r7=0).
d = bytearray(base)
for va, (a, b) in BLX2BL.items():
set4(d, va, b)
for va, exp, new in [
(0x802F664, 'e4f77efc', 'c046c046'),
(0x802F672, 'e6f7bbfa', 'c046c046'),
(0x802F678, 'e9f7fef9', 'c046c046'),
(0x801B71E, '9847fee7', 'c046c046'),
(0x8008396, 'fef780f9', 'c046c046'),
(0x8016184, 'fdf790fc', 'c046c046'),
(0x801B76C, '280b0508', '00000708'),
(0x8006684, '01f807bd', '46c046c0'),
(0x8017AFC, 'bde8fc87', 'eef74fb8'),
(0x802F690, '9847fee7', 'c046c046'),
(0x8013BAC, 'f8bd0a4a', 'f3f730b8'),
]:
assert hx(d, va) == exp, (hex(va), hx(d, va))
set4(d, va, new)
o = off(0x8013AA8)
assert d[o:o + 4].hex() == '07f026be', hx(d, 0x8013AA8)
d[o:o + 4] = bytes.fromhex('c046c046')
o = off(0x8013BA6)
assert d[o:o + 2].hex() == 'b888', (hex(0x8013BA6), hx(d, 0x8013BA6, 2))
d[o:o + 2] = bytes.fromhex('ff20')
save(d, 'img0_b18.elf')
# b19 = b17 + 0x80065d6 ldrb r0,[r1] -> movs r0,#2 (NULL table entry:
# type 2 takes exit-ish path instead of faulting on bx lr=0)
d = bytearray(base)
for va, (a, b) in BLX2BL.items():
set4(d, va, b)
for va, exp, new in [
(0x802F664, 'e4f77efc', 'c046c046'),
(0x802F672, 'e6f7bbfa', 'c046c046'),
(0x802F678, 'e9f7fef9', 'c046c046'),
(0x801B71E, '9847fee7', 'c046c046'),
(0x8008396, 'fef780f9', 'c046c046'),
(0x8016184, 'fdf790fc', 'c046c046'),
(0x801B76C, '280b0508', '00000708'),
(0x8006684, '01f807bd', '46c046c0'),
(0x8017AFC, 'bde8fc87', 'eef74fb8'),
(0x802F690, '9847fee7', 'c046c046'),
]:
assert hx(d, va) == exp, (hex(va), hx(d, va))
set4(d, va, new)
o = off(0x8013AA8)
assert d[o:o + 4].hex() == '07f026be', hx(d, 0x8013AA8)
d[o:o + 4] = bytes.fromhex('c046c046')
o = off(0x8013BA6)
assert d[o:o + 2].hex() == 'b888', (hex(0x8013BA6), hx(d, 0x8013BA6, 2))
d[o:o + 2] = bytes.fromhex('ff20')
o = off(0x80065D6)
assert d[o:o + 2].hex() == '0878', (hex(0x80065D6), d[o:o+2].hex())
d[o:o + 2] = bytes.fromhex('0220')
save(d, 'img0_b19.elf')
# b20 = b19 with 0x80065d6 movs#2 -> movs#1 (helper returns 1:
# caller cmp/beq falls through to cmp #0x25 path instead of skip)
o = off(0x80065D6)
assert d[o:o + 2].hex() == '0220'
d[o:o + 2] = bytes.fromhex('0120')
save(d, 'img0_b20.elf')
if __name__ == '__main__':
main()
+506
View File
@@ -0,0 +1,506 @@
#!/usr/bin/env python3
"""sbl_uni.py — быстрый harness SBL1 на Unicorn (без пересборки QEMU).
Карта RAM как в saimaa; ELF-сегменты; UARTDM/TF в stdout; MMIO->0.
Использование:
./tools/sbl_uni.py fw/img/patch/img0_b17.elf --thumb 0x802f63f [--regs r0=0x220000,...]
[--fill 0x085F0000:0x10000:0x08006831] [--poke 0xADDR=0xVAL] [--limit 1000000]
Печатает: выполнено инстр., последний PC, fault-адрес, UART.
"""
import struct
import sys
from unicorn import (Uc, UC_ARCH_ARM, UC_MODE_THUMB, UC_HOOK_MEM_UNMAPPED,
UC_HOOK_MEM_WRITE, UC_HOOK_CODE)
from unicorn.arm_const import (UC_ARM_REG_R0, UC_ARM_REG_R1, UC_ARM_REG_R2,
UC_ARM_REG_R3, UC_ARM_REG_R4, UC_ARM_REG_R5,
UC_ARM_REG_R6, UC_ARM_REG_R7, UC_ARM_REG_SP,
UC_ARM_REG_LR, UC_ARM_REG_PC,
UC_ARM_REG_CPSR)
_REGS = (UC_ARM_REG_R0, UC_ARM_REG_R1, UC_ARM_REG_R2, UC_ARM_REG_R3,
UC_ARM_REG_R4, UC_ARM_REG_R5, UC_ARM_REG_R6, UC_ARM_REG_R7)
RAMS = [
(0x00000000, 0x00001000), # zero page (QEMU-compat: [0] writes succeed)
(0x00200000, 0x00100000), # OCIMEM
(0x00400000, 0x07400000), # RPM range (heap-ish)
(0x08000000, 0x00600000), # DDRLOW
(0x08600000, 0x00010000), # IMEM
(0x80000000, 0x07C00000), # DDRHIGH_A
(0x87C00000, 0x00800000), # SMEM(+SBL BSS)
(0x078AF000, 0x00001000), # UARTDM page
(0x13000000, 0x00002000), # UEFI tables (already above, keep)
]
UART_TF = 0x078AF070
UART_SR = 0x078AF008
def load_elf(mu, path):
d = open(path, "rb").read()
assert d[:4] == b"\x7fELF"
entry = struct.unpack("<I", d[24:28])[0]
phoff = struct.unpack("<I", d[28:32])[0]
phnum = struct.unpack("<H", d[44:46])[0]
for k in range(phnum):
p = d[phoff + k * 32:phoff + (k + 1) * 32]
t, off, v, pa, fsz, msz, fl, al = struct.unpack("<IIIIIIII", p)
if t != 1:
continue
if fsz and v != 0x00220000: # QEMU parity: skip OCIMEM seg file-bytes
mu.mem_write(v, d[off:off + fsz])
for z in range(v + fsz, v + msz, 4):
mu.mem_write(z, b"\x00\x00\x00\x00")
return entry
def main():
path = sys.argv[1]
thumb = int(sys.argv[sys.argv.index("--thumb") + 1], 0)
mu = Uc(UC_ARCH_ARM, UC_MODE_THUMB)
for base, size in RAMS:
mu.mem_map(base, size)
uart_out = []
unmapped = {"n": 0, "first": []}
def hook_unmapped(mu, access, addr, size, value, data):
unmapped["n"] += 1
if len(unmapped["first"]) < 5:
unmapped["first"].append((hex(addr), size, access))
return False
mu.hook_add(UC_HOOK_MEM_UNMAPPED, hook_unmapped)
# SR reads return TX-ready
mu.mem_write(UART_SR, struct.pack("<I", 0xA0))
mu.mem_write(0x13000000, struct.pack("<II", 0x49535953, 0x54535953))
def hook_uw(mu, access, addr, size, value, data):
if addr == UART_TF:
uart_out.append(chr(value & 0xFF))
mu.hook_add(UC_HOOK_MEM_WRITE, hook_uw, begin=UART_TF, end=UART_TF)
entry = load_elf(mu, path)
for r in range(13):
mu.reg_write(UC_ARM_REG_R0 + r, 0)
if "--regs" in sys.argv:
for kv in sys.argv[sys.argv.index("--regs") + 1].split(","):
r, v = kv.split("=")
mu.reg_write(UC_ARM_REG_R0 + int(r[1:]), int(v, 0))
mu.reg_write(UC_ARM_REG_SP, 0x085FFF00)
from unicorn.arm_const import UC_ARM_REG_LR as _LR
mu.reg_write(_LR, 0x08006B8D) # QEMU parity: stale lr -> maze
mu.reg_write(UC_ARM_REG_CPSR, 0x13)
if "--fill" in sys.argv:
for spec in sys.argv[sys.argv.index("--fill") + 1].split(","):
a, sz, v = spec.split(":")
a, sz, v = int(a, 0), int(sz, 0), int(v, 0)
mu.mem_write(a, struct.pack("<I", v) * (sz // 4))
if "--poke" in sys.argv:
for spec in sys.argv[sys.argv.index("--poke") + 1].split(","):
a, v = spec.split("=")
mu.mem_write(int(a, 0), struct.pack("<I", int(v, 0)))
count = [0]
last = [0]
from collections import deque
hist = deque(maxlen=20000)
calls = [] if "--calltrace" in sys.argv else None
rets = [] if "--retrace" in sys.argv else None
qlog = [] if "--querylog" in sys.argv else None
_spmax = [0x085FFF00]
_splog = [] if "--spytrack" in sys.argv else None
elog = [] if "--entrylog" in sys.argv else None
_prev = [0]
_eranges = []
_inrng = []
if "--entrylog" in sys.argv:
for _spec in sys.argv[sys.argv.index("--entrylog") + 1].split(","):
_lo, _sz = _spec.split(":")
_lo, _sz = int(_lo, 0), int(_sz, 0)
_eranges.append((_lo, _lo + _sz))
_inrng.append(False)
from unicorn.arm_const import UC_ARM_REG_CPSR as _CPSR
def hook_code(mu, addr, size, data):
count[0] += 1
last[0] = addr
if _splog is not None:
try:
sp = mu.reg_read(UC_ARM_REG_SP)
except Exception:
sp = 0
if sp > _spmax[0] and len(_splog) < 40:
_spmax[0] = sp
_splog.append((count[0], addr, sp))
if _dirty[0]:
# deferred restore: MEM_WRITE hooks fire BEFORE the write lands,
# so in-hook restore gets overwritten by the instruction itself
_inww[0] = True
try:
for a, b in _dirty[1]:
mu.mem_write(a, stub_snap[a - STUB_LO:b - STUB_LO])
finally:
_inww[0] = False
_dirty[0] = False
del _dirty[1][:]
try:
thumb = (mu.reg_read(_CPSR) >> 5) & 1
except Exception:
thumb = 1
hist.append((addr, thumb))
if qlog is not None and addr in (0x8017EA4, 0x8017C60):
try:
vals = [mu.reg_read(_REGS[i]) for i in range(8)]
except Exception:
vals = []
qlog.append((addr, vals,
mu.reg_read(UC_ARM_REG_SP),
mu.reg_read(UC_ARM_REG_LR)))
if elog is not None:
for _i, (_elo, _ehi) in enumerate(_eranges):
inside = _elo <= addr < _ehi
if inside and not _inrng[_i]:
if len(elog) < 20000:
elog.append((count[0], _prev[0], addr))
_inrng[_i] = inside
_prev[0] = addr
if calls is not None and size in (2, 4):
try:
if size == 2:
h = struct.unpack("<H", mu.mem_read(addr & ~1, 2))[0]
is_blx = (h & 0xFF80) == 0x4780
rm = (h >> 3) & 7 if is_blx else -1
# bx Rm (jump, no link): 010001110 Rm:000
is_bx = (h & 0xFF80) == 0x4700
if is_bx:
bxrm = (h >> 3) & 15
try:
if bxrm < 8:
bxt = mu.reg_read(_REGS[bxrm])
elif bxrm == 14:
bxt = mu.reg_read(_LR)
else:
bxt = -1
except Exception:
bxt = -1
if len(calls) < 8000:
calls.append((addr, 100 + bxrm, bxt))
if rets is not None and 0x8017E00 <= addr < 0x8018000:
# pop {..,pc} (0xBD00+) or bx lr (0x4770)
if h == 0x4770 or (h & 0xFF00) == 0xBD00:
try:
rv = mu.reg_read(_REGS[0])
except Exception:
rv = -1
if len(rets) < 200:
rets.append((addr, rv))
else:
w = struct.unpack("<I", mu.mem_read(addr & ~3, 4))[0]
# ARM bx/blx Rm: cond(4) 0001001011111111111100x1 Rm(4)
is_blx = (w & 0x0FFFFFF0) in (0x012FFF10, 0x012FFF30)
rm = w & 15 if is_blx else -1
tgt = -1
if not is_blx and calls is not None:
# Thumb32 BL: h0=11110S+imm10, h1=1111x+J+imm11
h0 = w & 0xFFFF
h1 = (w >> 16) & 0xFFFF
if (h0 & 0xF800) == 0xF000 and (h1 & 0xE000) == 0xE000:
S = (h0 >> 10) & 1
J1 = (h1 >> 13) & 1
J2 = (h1 >> 11) & 1
I1 = 1 - (J1 ^ S)
I2 = 1 - (J2 ^ S)
off = (I1 << 23) | (I2 << 22) | ((h0 & 0x3FF) << 12) | ((h1 & 0x7FF) << 1)
if S:
off -= 0x1000000
if len(calls) < 8000:
calls.append((addr, 200, (addr + 4 + off) & 0xFFFFFFFF))
if rets is not None and 0x8017E00 <= addr < 0x8018000:
# pop.w {...,pc}: 0xE8BDxxxx with bit15 set
if (w & 0xFFFF8000) == 0xE8BD8000:
try:
rv = mu.reg_read(_REGS[0])
except Exception:
rv = -1
if len(rets) < 200:
rets.append((addr, rv))
if is_blx:
try:
tgt = mu.reg_read(_REGS[rm])
except Exception:
tgt = -1
if len(calls) < 4000:
calls.append((addr, rm, tgt))
except Exception:
return
mu.hook_add(UC_HOOK_CODE, hook_code)
# null-fetch redirect: branch-to-0 lands in maze (movs r0,#0; bx lr),
# immune to [0]-writes that clobber a static maze copy
from unicorn.arm_const import UC_ARM_REG_PC as _PC
_nulllog = []
def hook_null(mu, addr, size, data):
if len(_nulllog) < 10:
try:
r = [mu.reg_read(_REGS[i]) for i in range(4)]
lr = mu.reg_read(_LR)
sp = mu.reg_read(UC_ARM_REG_SP)
except Exception:
r, lr, sp = [], -1, -1
# walk back: find the branch source from hist
_nulllog.append((count[0], r, lr, sp))
mu.reg_write(_PC, STUB + 1)
mu.hook_add(UC_HOOK_CODE, hook_null, begin=0x0, end=0xFFF)
if "--watchcode" in sys.argv:
from unicorn.arm_const import UC_ARM_REG_PC as _PC2
wlog = []
def hook_ww(mu, access, addr, size, value, data):
if len(wlog) < 30:
try:
pc = mu.reg_read(_PC2)
except Exception:
pc = -1
wlog.append((pc, addr, size, value))
mu.hook_add(UC_HOOK_MEM_WRITE, hook_ww,
begin=0x8005800, end=0x8039254)
import atexit
def _dump():
print(f"code-writes: {len(wlog)}")
for pc, a, sz, v in wlog[:30]:
print(f" pc={pc:#x} [{a:#x}] sz={sz} val={v:#x}")
atexit.register(_dump)
# --- machine scaffold parity (saimaa.c values, verbatim) ---
def W32(a, v):
mu.mem_write(a, struct.pack("<I", v & 0xFFFFFFFF))
S, STUB, STUBV = 0x08006B80, 0x08006B8C, 0x08006B8D
W32(S, S + 8)
W32(S + 4, STUBV)
W32(S + 8, STUBV)
# maze blob (18B)
mu.mem_write(STUB, bytes([
0x00, 0x20, 0x4f, 0xea, 0x1e, 0x6c, 0xbc, 0xf1,
0x08, 0x0f, 0x00, 0xd1, 0x70, 0x47, 0x72, 0xb6,
0xfd, 0xe7]))
print("maze-rb:", bytes(mu.mem_read(STUB, 18)).hex())
for a in (0x087c29d18, 0x08050b28, 0x08070000):
W32(a, S)
# S6 stublet (14B @0x08006B9E)
mu.mem_write(0x08006B9E, bytes([
0xbd, 0xe8, 0xfc, 0x47, 0x43, 0xf6, 0xa5, 0x3e,
0xc0, 0xf6, 0x01, 0x0e, 0x70, 0x47]))
# banner stub (34B @0x08006BAC, prints r0 via UARTDM)
mu.mem_write(0x08006BAC, bytes([
0x10, 0xb5, 0x4f, 0xf2, 0x00, 0x03, 0xc0, 0xf2,
0x8a, 0x73, 0x21, 0x21, 0x19, 0x67, 0x10, 0xf8,
0x01, 0x1b, 0x09, 0xb1, 0x19, 0x67, 0xfa, 0xe7,
0x4f, 0xf2, 0x94, 0x6e, 0xc0, 0xf6, 0x02, 0x0e,
0x70, 0x47]))
# PBL stub (52B @0x080068C0)
mu.mem_write(0x080068C0, bytes([
0xf0, 0xb5, 0x46, 0xf6, 0x81, 0x3c, 0xc0, 0xf6,
0x00, 0x0c, 0xe0, 0x47, 0x40, 0xf2, 0x00, 0x00,
0xc0, 0xf2, 0x22, 0x00, 0x05, 0x46, 0x40, 0xf2,
0x00, 0x0a, 0xc0, 0xf6, 0x06, 0x0a, 0x41, 0xf2,
0x00, 0x0b, 0xc0, 0xf6, 0x06, 0x0b, 0x4f, 0xf2,
0x3f, 0x6e, 0xc0, 0xf6, 0x02, 0x0e, 0x70, 0x47,
0x00, 0x00, 0x00, 0x00]))
# S7 stublet (52B @0x08006C10, b18): delay-epilogue exit router.
# cmp r4,#0xff: iterate (r4<=0xff -> restart 0x8013ba5) or done
# (restore r4/r5/sl/fp, return to sbl_main 0x802f681).
mu.mem_write(0x08006C10, bytes.fromhex(
"ff2c13d940f20004c0f2220440f20005c0f2220540f2000a"
"c0f6060a41f2000bc0f6060bdff800f081f60208dff800f0"
"a53b0108"))
# stack zone fill (STUBV odd)
mu.mem_write(0x085F0000, struct.pack("<I", STUBV) * (0x10000 // 4))
# pop-site slots (sp values from traces)
for a, v in [(0x085FFF14, 0x802f65d), (0x085FFF1C, 0x802f665),
(0x085FFF44, 0x8013ba5), (0x085FFF5C, 0x802f681),
(0x085FFF74, 0x8016183)]:
W32(a, v)
W32(0x085FFF58, 0)
W32(0x085FFF34, 0)
W32(0x085FFF54, 0)
W32(0x085FFF6C, 0)
W32(0x08600944, 0) # IMEM poll magic
# minimal UEFI tables (ST/BS/RS/CO + ret0 stubs)
ST, BS, RS, CO, STUBS, LI, HEAP = (0x13000000, 0x13000100, 0x13000200,
0x13000300, 0x13000400, 0x13000500,
0x13001000)
for i in range(24):
mu.mem_write(STUBS + i * 8, bytes([0x00, 0x20, 0x70, 0x47,
0x00, 0x00, 0x00, 0x00]))
W32(BS + 24 + i * 4, STUBS + i * 8 + 1)
W32(BS, 0x56524553)
W32(ST, 0x49535953)
W32(ST + 4, 0x54535953)
W32(ST + 12, 0x00020000)
W32(ST + 16, 0x13000048)
W32(ST + 40, CO)
W32(ST + 44, CO)
W32(ST + 56, RS)
W32(ST + 60, BS)
W32(LI, 0x3130414d)
W32(HEAP - 4, HEAP)
# NOTE: no vendor string bytes (QEMU leaves zeros at 0x13000048)
# map UEFI table region (already in RAMS? add 0x13000000)
# --- end parity ---
# stub range is readonly on HW/QEMU: ignore SBL heap-clobber writes,
# keep maze/stublets intact (restore-on-write with reentrancy guard)
STUB_LO, STUB_HI = 0x080068C0, 0x08006C50 # +S7 + banner tail
stub_snap = bytes(mu.mem_read(STUB_LO, STUB_HI - STUB_LO))
_inww = [False]
_swn = [0]
_swlog = []
_dirty = [False, []]
print("stub-snap:", stub_snap[:24].hex())
print("stub-snap-maze:", stub_snap[0x2CC:0x2CC + 18].hex())
def hook_stubw(mu, access, addr, size, value, data):
if _inww[0]:
return
if addr + size <= STUB_LO or addr >= STUB_HI:
return
_swn[0] += 1
if len(_swlog) < 8:
try:
_pc = mu.reg_read(_PC)
except Exception:
_pc = -1
_swlog.append((_pc, addr, size))
# record for deferred restore (see hook_code); in-hook restore
# would be overwritten by the faulting instruction itself
a = max(addr, STUB_LO)
b = min(addr + size, STUB_HI)
if b > a and len(_dirty[1]) < 16:
_dirty[1].append((a, b))
_dirty[0] = True
#hook all DDRLOW, filter in python (catches writes starting below LO)
mu.hook_add(UC_HOOK_MEM_WRITE, hook_stubw,
begin=0x08000000, end=0x085FFFFF)
if "--watchmaze" in sys.argv:
from unicorn.arm_const import UC_ARM_REG_PC as _PC3
mzlog = []
def hook_mz(mu, access, addr, size, value, data):
if len(mzlog) < 40:
try:
pc = mu.reg_read(_PC3)
except Exception:
pc = -1
mzlog.append((pc, addr, size, value))
mu.hook_add(UC_HOOK_MEM_WRITE, hook_mz,
begin=0x08006B8C, end=0x08006B9D)
import atexit as _ax
def _dmz():
print(f"maze-writes: {len(mzlog)}")
for pc, a, sz, v in mzlog[:40]:
print(f" pc={pc:#x} [{a:#x}] sz={sz} val={v:#x}")
_ax.register(_dmz)
if "--watchlow" in sys.argv:
_spec = sys.argv[sys.argv.index("--watchlow") + 1].split(":")
_wlo, _whi = int(_spec[0], 0), int(_spec[0], 0) + int(_spec[1], 0)
from unicorn.arm_const import UC_ARM_REG_PC as _PC4
wlolog = []
def hook_wlo(mu, access, addr, size, value, data):
if len(wlolog) < 60:
try:
pc = mu.reg_read(_PC4)
except Exception:
pc = -1
wlolog.append((pc, addr, size, value))
mu.hook_add(UC_HOOK_MEM_WRITE, hook_wlo,
begin=0x08000000, end=0x085FFFFF)
import atexit as _ax2
def _dwlo():
inn = [(pc, a, sz, v) for pc, a, sz, v in wlolog
if not (a + sz <= _wlo or a >= _whi)]
print(f"low-writes-in-range: {len(inn)}")
for pc, a, sz, v in inn[:60]:
print(f" pc={pc:#x} [{a:#x}] sz={sz} val={v:#x}")
_ax2.register(_dwlo)
limit = 2000000
if "--limit" in sys.argv:
limit = int(sys.argv[sys.argv.index("--limit") + 1])
try:
mu.emu_start(thumb | 1, 0xFFFFFFFF, 0, limit)
except Exception as e:
print("EMU-EXC:", str(e)[:120])
print("insns:", count[0], "last-pc:", hex(last[0]))
print("unmapped:", unmapped["n"], unmapped["first"][:4])
print("regs:",
" ".join(f"r{i}={mu.reg_read(_REGS[i]):#x}" for i in range(8)),
f"sp={mu.reg_read(UC_ARM_REG_SP):#x}",
f"lr={mu.reg_read(UC_ARM_REG_LR):#x}",
f"pc={mu.reg_read(UC_ARM_REG_PC):#x}")
try:
from capstone import Cs, CS_ARCH_ARM, CS_MODE_THUMB, CS_MODE_ARM
md_t = Cs(CS_ARCH_ARM, CS_MODE_THUMB)
md_a = Cs(CS_ARCH_ARM, CS_MODE_ARM)
real = [(pc, th) for pc, th in hist
if not (0 <= (pc & ~1) < 0x1000)][-48:]
for pc, thumb in real:
try:
base = pc & ~1
code = mu.mem_read(base, 8 if thumb else 12)
md = md_t if thumb else md_a
for ins in md.disasm(code, base):
print(f" {base:#x}{'t' if thumb else 'a'}: {ins.mnemonic} {ins.op_str}")
break
except Exception:
print(f" {pc:#x}: <unreadable>")
except ImportError:
print("hist:", [hex(pc) for pc, _ in list(hist)[-12:]])
if elog is not None:
print(f"entries: {len(elog)}")
for n, prev, cur in elog[-30:]:
print(f" insn#{n} {prev:#x} -> {cur:#x}")
if _splog is not None:
print(f"sp-highs: {len(_splog)}")
for n, pc, sp in _splog:
print(f" insn#{n} {pc:#x} sp={sp:#x}")
if _nulllog:
print(f"null-entries: {len(_nulllog)}")
for n, r, lr, sp in _nulllog:
print(f" insn#{n} r0-r3={[hex(x) for x in r]} lr={lr:#x} sp={sp:#x}")
if qlog is not None:
print(f"querylog: {len(qlog)}")
for pc, r, sp, lr in qlog[:20]:
print(f" {pc:#x} r0-r7={[hex(x) for x in r]} sp={sp:#x} lr={lr:#x}")
if rets is not None:
print(f"rets-in-range: {len(rets)}")
for pc, rv in rets[-20:]:
print(f" ret {pc:#x} r0={rv:#x}" if rv >= 0 else f" ret {pc:#x} r0=?")
if calls is not None:
print(f"blx-calls: {len(calls)}")
bls = [(pc, tgt) for pc, rm, tgt in calls if rm == 200]
print(f"bl-immed: {len(bls)}")
for pc, tgt in bls[-30:]:
print(f" {pc:#x}: bl #{tgt:#x}")
for pc, rm, tgt in calls[-12:]:
if rm == 200:
continue
tag = f"bx r{rm - 100}" if rm >= 100 else f"blx r{rm}"
print(f" {pc:#x}: {tag} -> {tgt:#x}" if tgt >= 0 else f" {pc:#x}: {tag} -> ?")
if "--dumpmem" in sys.argv:
for spec in sys.argv[sys.argv.index("--dumpmem") + 1].split(","):
a, sz = spec.split(":")
a, sz = int(a, 0), int(sz, 0)
print(f"mem[{a:#x}+{sz:#x}]:")
print(bytes(mu.mem_read(a, sz)).hex())
print("UART:", "".join(uart_out)[:160])
print(f"stubguard-hits: {_swn[0]}")
for _pc, _a, _s in _swlog:
print(f" guarded pc={_pc:#x} [{_a:#x}] sz={_s}")
if __name__ == "__main__":
main()