672 lines
14 KiB
Python
672 lines
14 KiB
Python
#!/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', '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', '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', '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', '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).
|
|
# Also NOPs the FULL bl idiom at 0x8006682 (b17 NOPed only its 2nd
|
|
# half, leaving a dangling prefix that fused with the NOP into a
|
|
# bogus odd-address store -> alignment fault in QEMU).
|
|
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'),
|
|
|
|
(0x8017AFC, 'bde8fc87', 'eef74fb8'),
|
|
|
|
(0x802F690, '9847fee7', 'c046c046'),
|
|
|
|
(0x8013BAC, 'f8bd0a4a', 'f3f730b8'),
|
|
|
|
]:
|
|
|
|
assert hx(d, va) == exp, (hex(va), hx(d, va))
|
|
|
|
set4(d, va, new)
|
|
|
|
# Full bl idiom at 0x8006682 (bl + pop, 6B) -> NOPs. (b17 NOPed only
|
|
# its 2nd half, leaving a dangling prefix that fused with the NOP
|
|
# into a bogus odd-address store -> alignment fault in QEMU.)
|
|
o = off(0x8006682)
|
|
|
|
assert d[o:o + 6].hex() == '00f001f807bd', hx(d, 0x8006682, 6)
|
|
|
|
d[o:o + 6] = bytes.fromhex('c046c046c046')
|
|
|
|
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()
|
|
|