1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
|
"""Host-side model of the PSP's pre-auth IFWI parser.
WARNING -- THIS MODEL DIVERGES FROM THE FIRMWARE. DO NOT CRAFT WITH IT.
Differentially tested against the real parser under Unicorn
(`psp_emu/harness/tsu_parser_diff.py`, `fuzz_ifwi_parser.py`): over 400
mutations it disagrees with the firmware on **84**, in both directions:
firmware FAULTS, model rejects 42 model is safer, hides the bug
firmware ACCEPTS, model rejects 34 model is stricter
firmware REJECTS, model accepts 8 model is too lenient -- the
dangerous direction for crafting
The stricter class comes from `fw_tail_scan`: this model raises on the first
slot's failure, while the firmware marks that slot invalid and carries on.
An attempted fix (make per-slot failure non-fatal) was MEASURED AND MADE
THINGS WORSE -- agreement fell 316 -> 305 and "model accepts, firmware
rejects" jumped 8 -> 53 -- so it was reverted rather than shipped.
The real semantics, read from `fw_tail_scan` (0xE0F950): an error register
accumulates across both slots and is never cleared, so a slot-0 failure
leaves it set even when slot 1 parses cleanly -- and `rom_parse_directory`
may still succeed on the valid slot regardless. Modelling that faithfully
needs the whole interaction, not a one-line change.
Until someone does that, use the firmware itself via the harnesses above.
Mirrors, step for step, what rom_parse_directory and its callees do in
sys_drv2.elf. Purpose: validate the model against a KNOWN-GOOD image before
using it to craft anything. If this cannot parse the real BIOS, the model is
wrong and nothing built on it can be trusted.
Firmware references:
rom_parse_directory 0xE16964
fw_tail_scan 0xE0F950
param_list_scan 0xE0F8A8
ring_entry_validate 0xE0FAEC
fw_tail_record_check 0xE0FC6C
"""
import struct, sys
M32 = 0xFFFFFFFF
IFWI_MAGIC = 0x55AA55AA
PSP_MAGIC = 0x50535024 # '$PSP'
SGN_MAGIC = 0x4E475324 # '$SGN'
class Fail(Exception): pass
def u32(b, off):
if off + 4 > len(b) or off < 0: raise Fail("read out of image at %#x" % off)
return struct.unpack_from('<I', b, off)[0]
def fw_tail_scan(img, ctx, trace):
"""v4 = *(img+20) then *(img+44); bound (v4+16)&M32 <= 0x200000; expect '$PSP'."""
for slot, hdr_off in ((0, 20), (1, 44)):
v4 = u32(img, hdr_off)
trace.append(" fw_tail_scan[%d]: v4 = *(img+%d) = %#x" % (slot, hdr_off, v4))
if ((v4 + 16) & M32) > 0x200000:
raise Fail("slot %d: bound check failed (v4+16 = %#x)" % (slot, (v4+16)&M32))
magic = u32(img, v4)
if magic != PSP_MAGIC:
raise Fail("slot %d: expected $PSP at %#x, got %#010x" % (slot, v4, magic))
count = u32(img, v4 + 8)
if count < 2:
raise Fail("slot %d: count %d < 2 (-> 0xFFFF000B)" % (slot, count))
ctx[slot] = {'dir_off': v4, 'len': 16*count + 16, 'valid': 1, 'count': count}
trace.append(" -> $PSP ok, count=%d, len=%#x" % (count, 16*count+16))
return ctx
def param_list_scan(img, ptr, count, ctx, trace):
"""entries of 4 dwords; type 316/317; 64-bit bound; low dword of v9 must == 32."""
seen = 0
for i in range(count):
base = ptr + i*16
t = u32(img, base)
if t not in (316, 317):
continue
v9 = u32(img, base+4) | (u32(img, base+12) << 32)
size = u32(img, base+8)
if not (size + v9 <= 0x200000 and (v9 & M32) == 32):
raise Fail("param entry %d (type %d): bound/offset check failed (size=%#x v9=%#x)" % (i, t, size, v9))
idx = 0 if t == 316 else 1
# a1[2] is the OFFSET stored at ctx+40 and used by ring_entry_validate;
# a1[1] is a fixed marker that must equal 32.
ctx[idx].update({'p_offset': size, 'p_marker': u32(img, base+4), 'p_valid': 1})
trace.append(" param_list_scan: type=%d -> offset=%#x marker=%d" % (t, size, u32(img, base+4)))
seen += 1
if seen == 2: break
if seen != 2:
raise Fail("param_list_scan: found %d of 2 required entries (types 316/317)" % seen)
def ring_entry_validate(img, ctx, idx, trace):
e = ctx[idx]
if not e.get('p_valid'): raise Fail("ring_entry_validate[%d]: slot not marked valid" % idx)
v4 = e['p_offset'] # ctx+40 == a1[2]
v5 = u32(img, v4 + 16)
ln = u32(img, v4 + 24)
if ((v5 + ln) & M32) > 0x200000:
raise Fail("ring_entry_validate[%d]: (v5+len) bound failed" % idx)
e.update({'r_off': v5, 'r_len': ln, 'r_valid': 1})
trace.append(" ring_entry_validate[%d]: off=%#x len=%#x" % (idx, v5, ln))
def fw_tail_record_check(img, ctx, trace):
"""v3 = *(img+4096); bound; expect '$SGN'; walk count records of 16 bytes."""
v3 = u32(img, 4096)
trace.append(" fw_tail_record_check: v3 = *(img+0x1000) = %#x" % v3)
if ((v3 + 16) & M32) > 0x200000: raise Fail("$SGN bound check failed")
if (m := u32(img, v3)) != SGN_MAGIC:
raise Fail("expected $SGN at %#x, got %#010x" % (v3, m))
count = u32(img, v3 + 8)
trace.append(" -> $SGN ok, %d records" % count)
recs = {}
for i in range(1, 8):
for j in range(count):
base = v3 + 16 + j*16
if u32(img, base) == i:
recs[i] = tuple(u32(img, base + k*4) for k in range(4))
break
ctx['records'] = recs
trace.append(" record types present: %s" % sorted(recs))
def parse(img):
trace = []
ctx = {0: {}, 1: {}}
if (m := u32(img, 0)) != IFWI_MAGIC:
raise Fail("IFWI magic: got %#010x want %#010x" % (m, IFWI_MAGIC))
trace.append("rom_parse_directory: IFWI magic ok")
fw_tail_scan(img, ctx, trace)
slot = next((s for s in (0,1) if ctx[s].get('valid')), None)
if slot is None: raise Fail("no valid directory slot")
d = ctx[slot]['dir_off']
param_list_scan(img, d + 16, u32(img, d + 8), ctx, trace)
ring_entry_validate(img, ctx, 0, trace)
ring_entry_validate(img, ctx, 1, trace)
fw_tail_record_check(img, ctx, trace)
return ctx, trace
if __name__ == '__main__':
for path in sys.argv[1:]:
img = open(path, 'rb').read()
print("=== %s (%d bytes) ===" % (path, len(img)))
try:
ctx, trace = parse(img)
for t in trace: print(t)
print(" RESULT: PARSE SUCCEEDED -> would reach tsu_write_active_slot")
except Fail as e:
print(" RESULT: PARSE FAILED -> %s" % e)
print(" (returns before the writer; flash untouched)")
print()
|