639 lines
27 KiB
Python
639 lines
27 KiB
Python
#!/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
|
|
# frozen-maze breaker: maze entries with identical lr and no bl
|
|
# between them mean a zero-state-change spin (maze->mid-fn->maze).
|
|
# After K, redirect once to poll resumption (fresh bls unfreeze lr).
|
|
_fmlr = [None]
|
|
_fmct = [0]
|
|
_FMK = 50
|
|
_FMPC = 0x8007779
|
|
_traps = []
|
|
_trap_n = [0]
|
|
if "--trap" in sys.argv:
|
|
_traps = [int(x, 0) for x in sys.argv[sys.argv.index("--trap") + 1].split(",")]
|
|
if "--trapn" in sys.argv:
|
|
_trap_n = [int(sys.argv[sys.argv.index("--trapn") + 1])]
|
|
_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 0x08006B8C <= addr < 0x08006B9E:
|
|
try:
|
|
_lr = mu.reg_read(_LR)
|
|
except Exception:
|
|
_lr = None
|
|
if _lr == _fmlr[0]:
|
|
_fmct[0] += 1
|
|
if _fmct[0] >= _FMK:
|
|
_fmct[0] = 0
|
|
_fmlr[0] = None
|
|
mu.reg_write(_PC, _FMPC)
|
|
return
|
|
else:
|
|
_fmlr[0] = _lr
|
|
_fmct[0] = 0
|
|
elif _fmlr[0] is not None:
|
|
# any non-maze fetch: reset only on bl (lr change means
|
|
# fresh calls; other insns keep the freeze assessment)
|
|
pass
|
|
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 _traps and addr in _traps:
|
|
try:
|
|
vals = [mu.reg_read(_REGS[i]) for i in range(8)]
|
|
except Exception:
|
|
vals = []
|
|
print(f"TRAP {addr:#x} insn#{count[0]} r0-r7={[hex(x) for x in vals]} sp={mu.reg_read(UC_ARM_REG_SP):#x} lr={mu.reg_read(_LR):#x}")
|
|
try:
|
|
print(f" [73A100]={bytes(mu.mem_read(0x073A100, 4)).hex()}")
|
|
except Exception as e:
|
|
print(f" [73A100] unreadable: {e}")
|
|
_trap_n[0] -= 1
|
|
if _trap_n[0] <= 0 and "--trapn" in sys.argv:
|
|
mu.emu_stop()
|
|
return
|
|
if "--trapn" not in sys.argv:
|
|
mu.emu_stop()
|
|
return
|
|
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:
|
|
try:
|
|
_th = (mu.reg_read(_CPSR) >> 5) & 1
|
|
except Exception:
|
|
_th = -1
|
|
elog.append((count[0], _prev[0], addr, _th))
|
|
if not inside and _inrng[_i]:
|
|
if len(elog) < 20000:
|
|
elog.append((count[0], _prev[0], -addr, 2))
|
|
_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))
|
|
# pop {..,pc} (0xBD00+): log stacked target
|
|
if calls is not None and (h & 0xFF00) == 0xBD00:
|
|
try:
|
|
_sp = mu.reg_read(UC_ARM_REG_SP)
|
|
_tgt = struct.unpack("<I", mu.mem_read(_sp, 4))[0]
|
|
except Exception:
|
|
_tgt = -1
|
|
if len(calls) < 12000:
|
|
calls.append((addr, 300, _tgt))
|
|
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 = []
|
|
_halt_on_null = "--haltonnull" in sys.argv
|
|
|
|
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))
|
|
if _halt_on_null and len(_nulllog) == 1:
|
|
return False
|
|
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"))
|
|
# S9 stublet (62B @0x08006C48, b22): labyrinth-epilogue exit router.
|
|
# ldr r3,[sp,#0x14]: nonzero -> original pop.w (legit return);
|
|
# zero (empty scaffold frames) -> restore r0/r4/r5/sl/fp, reset sp
|
|
# to 0x085FFF00 (fresh STUBV-fill stack), jump to sbl_main 0x802f69d
|
|
# via ip (no literal loads).
|
|
mu.mem_write(0x08006C48, bytes.fromhex(
|
|
"059b0bb1bde8f081002040f20004c0f2220440f20005c0f2"
|
|
"220540f2000ac0f6060a41f2000bc0f6060b4ff60073c0f6"
|
|
"5f039d464ff29d6cc0f6020c6047"))
|
|
# S10 stublet (10B @0x08006C88, b23): rollback-exit router.
|
|
# movw/movt ip + bx ip to sbl_main 0x802f6a3.
|
|
mu.mem_write(0x08006C88, bytes.fromhex("4ff2a36cc0f6020c6047"))
|
|
# S17 safe-logger (50B @0x08006CF0, b26): print r1-string via
|
|
# UARTDM-TF only if r1 in rodata [0x804B800,0x8058000); pop-return.
|
|
mu.mem_write(0x08006CF0, bytes.fromhex(
|
|
"4bf60003c0f60403994211d348f20003c0f605039942"
|
|
"0bd210b54ff20003c0f28a730c4614f8010b08b11867fae710bd7047"))
|
|
# PBL version-table fabrication (b23): [0x080528D0] = STUBV so the
|
|
# rollback query's blx calls maze (returns 0 = versions OK).
|
|
W32(0x080528D0, STUBV)
|
|
# HW-ready bit fabrication (b25): [0x073A100] bits 0,1,2 = peripheral
|
|
# status done-bits (poll#1 checks bit2 (lsls#29), poll#2 bit1 (lsls#30),
|
|
# post-poll gate bit0 (lsls#31)). SBL only reads it, never writes.
|
|
W32(0x073A100, 0x00000007)
|
|
# PBL world-switch Thumb-stub (b24): the ARM trampoline at 0x221EF8
|
|
# is entered in Thumb (mode confusion at dispatch); real ARM would
|
|
# save regs/CPS to MON and call TZ (not loaded yet). Stub returns 0
|
|
# to the dispatcher via stale lr (TZ bringup deferred).
|
|
# NOTE: overwrites 4B of real ARM trampoline (recoverable from ELF).
|
|
mu.mem_write(0x00221EF8, bytes([0x00, 0x20, 0x70, 0x47]))
|
|
# S12-stub (b27, 92B @0x08006964, ARM): blx-to-padding becomes
|
|
# smart dispatcher: ldr ip,[sp,#4] (=pushed lr); ip==STUBV/even/
|
|
# non-code => ip=poll resumption 0x8007779; else keep pushed-lr;
|
|
# restore r7 (peripheral base, S16 dormant); ACK peripheral bit1
|
|
# ([literal 0x073A100]); bx lr. PBL would place the real helper.
|
|
mu.mem_write(0x08006964, bytes.fromhex(
|
|
"04c09de58d3b06e3003840e303005ce10700000a0100"
|
|
"1ce30500000a2c3ca0e1080053e30400000a2c3ea0e1"
|
|
"080053e30100000a79c707e300c840e300700ae37370"
|
|
"40e30c309fe5002093e5022082e3002083e51eff2fe1"
|
|
"00a17300"))
|
|
# S16-stublet (b27, 64B @0x08006CC0): smart return for B66 bx ip.
|
|
# ip==STUBV (fill/maze, never a legit return) => default;
|
|
# ip odd + top 8 (code) => add sp,#8 (pop the helper frame B66
|
|
# skipped!) + bx ip (legit pushed-lr return, un-leaks the stack);
|
|
# else default (restore r7, jump poll resumption 0x8007779).
|
|
mu.mem_write(0x08006CC0, bytes.fromhex(
|
|
"ddf804c046f68d33c0f600039c450ed01cf0010f0bd0"
|
|
"5fea1c63082b01d102b060475fea1c73082b01d102b0"
|
|
"60474af20007c0f2730747f2797cc0f6000c6047"))
|
|
# 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, 0x08006D30 # +S7/S9/S10/S16/S17
|
|
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=0x00200000, 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, th in elog[:10]:
|
|
if cur < 0:
|
|
print(f" FIRST EXIT insn#{n} {prev:#x} -> {(-cur):#x}")
|
|
else:
|
|
print(f" FIRST insn#{n} {prev:#x} -> {cur:#x}{'t' if th == 1 else ('a' if th == 0 else '?')}")
|
|
for n, prev, cur, th in elog[-10:]:
|
|
if cur < 0:
|
|
print(f" LAST EXIT insn#{n} {prev:#x} -> {(-cur):#x}")
|
|
else:
|
|
print(f" LAST insn#{n} {prev:#x} -> {cur:#x}{'t' if th == 1 else ('a' if th == 0 else '?')}")
|
|
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}")
|
|
pops = [(pc, tgt) for pc, rm, tgt in calls if rm == 300]
|
|
print(f"pop-pc: {len(pops)}")
|
|
for pc, tgt in pops[:15]:
|
|
print(f" FIRST {pc:#x}: pop-> {tgt:#x}" if tgt >= 0 else f" FIRST {pc:#x}: pop-> ?")
|
|
for pc, tgt in pops[-15:]:
|
|
print(f" LAST {pc:#x}: pop-> {tgt:#x}" if tgt >= 0 else f" LAST {pc:#x}: pop-> ?")
|
|
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()
|