Files
2026-09-30 22:48:06 +02:00

99 lines
3.4 KiB
Python
Executable File

#!/usr/bin/env python3
"""xref + slack scanner: LE32 pointers into code/rodata, NUL strings, zero gaps.
Usage: lofi_xref.py "Lofi-12 XT.bin" [--find TEXT] [--strings-min 6]
"""
import argparse, os, struct, sys
sys.path.insert(0, os.path.dirname(__file__))
from lofi_image import parse_image, find_sect_by_role
def le_words(payload):
for i in range(0, len(payload) - 3, 4):
yield i, struct.unpack('<I', payload[i:i+4])[0]
def zero_runs(payload, minlen=32):
out, i, n = [], 0, len(payload)
while i < n:
if payload[i] == 0:
j = i
while j < n and payload[j] == 0:
j += 1
if j - i >= minlen:
out.append((i, j - i))
i = j
else:
i += 1
return out
def c_strings(payload, minlen=6):
out, i, n = [], 0, len(payload)
while i < n:
if 32 <= payload[i] < 127:
j = i
while j < n and 32 <= payload[j] < 127:
j += 1
if j - i >= minlen and j < n and payload[j] == 0:
out.append((i, payload[i:j].decode()))
i = max(j, i + 1)
else:
i += 1
return out
def main():
ap = argparse.ArgumentParser()
ap.add_argument('image')
ap.add_argument('--find', default=None)
ap.add_argument('--strings-min', type=int, default=6)
ap.add_argument('--slack-min', type=int, default=64)
a = ap.parse_args()
d = open(a.image, 'rb').read()
info = parse_image(d)
code = find_sect_by_role(info, 'code')
ro = find_sect_by_role(info, 'rodata')
print(f"code: sect{code['index']} {code['addr']:08X}..{code['end']:08X} len={code['len']}")
print(f"rodata: sect{ro['index']} {ro['addr']:08X}..{ro['end']:08X} len={ro['len']}")
# pointers in rodata -> code, and rodata -> self
to_code = to_self = 0
code_hits = []
for off, w in le_words(ro['payload']):
if code['addr'] <= w < code['end']:
to_code += 1
code_hits.append((off, w))
elif ro['addr'] <= w < ro['end']:
to_self += 1
print(f"rodata xrefs: {to_code} -> code, {to_self} -> self")
gaps = zero_runs(ro['payload'], a.slack_min)
gaps.sort(key=lambda t: -t[1])
print(f"top zero gaps in rodata (min {a.slack_min}):")
for off, ln in gaps[:10]:
print(f" +{off:#x} (file {ro['off']+12+off:#x}) len={ln}")
if a.find:
needle = a.find.encode()
hits = []
p = ro['payload']
s = 0
while True:
i = p.find(needle, s)
if i < 0:
break
hits.append(i)
s = i + 1
print(f"'{a.find}': {len(hits)} hit(s) in rodata")
for h in hits:
faddr = ro['addr'] + h
refs = [off for off, w in code_hits if False] # placeholder
# find pointers TO this string: scan rodata words == faddr
# (vtable-adjacent tables) — cheap exact scan
ptrs = []
for off, w in le_words(ro['payload']):
if w == faddr:
ptrs.append(ro['off'] + 12 + off)
print(f" +{h:#x} file={ro['off']+12+h:#x} load={faddr:08X} "
f"referenced_by_{len(ptrs)}={['%#x' % x for x in ptrs[:8]]}")
else:
strs = c_strings(ro['payload'], a.strings_min)
print(f"NUL strings len>={a.strings_min} in rodata: {len(strs)}")
if __name__ == '__main__':
main()