113 lines
3.4 KiB
Python
Executable File
113 lines
3.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Brute-force checksum hypotheses for cmtd+0x08 against all known images.
|
|
|
|
Tests CRC32/zlib/adler/fletcher/sum/xor variants over multiple spans.
|
|
A hypothesis must match ALL builds to be reported as candidate.
|
|
Stdlib only.
|
|
"""
|
|
import binascii, os, struct, sys, zlib
|
|
|
|
BASE = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
IMAGES = [
|
|
'Lofi-12XT_v1.1.156/Lofi-12XT_v1.1.156/Lofi-12 XT.bin',
|
|
'Lofi-12XT_v1.2.179/Lofi-12XT_v1.2.179/Lofi-12 XT.bin',
|
|
'Lofi-12XT_v1.5.205/Lofi-12XT_v1.5.205/Lofi-12 XT.bin',
|
|
]
|
|
|
|
def fletcher16(data):
|
|
s1 = s2 = 0
|
|
for b in data:
|
|
s1 = (s1 + b) % 255
|
|
s2 = (s2 + s1) % 255
|
|
return (s2 << 8) | s1
|
|
|
|
def fletcher32(data):
|
|
s1 = s2 = 0
|
|
for i in range(0, len(data), 2):
|
|
w = data[i] | (data[i+1] << 8 if i+1 < len(data) else 0)
|
|
s1 = (s1 + w) % 0xFFFF
|
|
s2 = (s2 + s1) % 0xFFFF
|
|
return (s2 << 16) | s1
|
|
|
|
def sum32(data):
|
|
return sum(data) & 0xFFFFFFFF
|
|
|
|
def xor32(data):
|
|
r = 0
|
|
for i in range(0, len(data) - 3, 4):
|
|
(w,) = struct.unpack('<I', data[i:i+4])
|
|
r ^= w
|
|
return r & 0xFFFFFFFF
|
|
|
|
def load():
|
|
out = []
|
|
for rel in IMAGES:
|
|
p = os.path.join(BASE, rel)
|
|
d = open(p, 'rb').read()
|
|
ck = struct.unpack('<I', d[8:12])[0]
|
|
out.append((rel.split('/')[0], d, ck))
|
|
return out
|
|
|
|
def spans(d):
|
|
nsect = struct.unpack('<I', d[0x50:0x54])[0]
|
|
off = 0x54
|
|
payloads = b''
|
|
for _ in range(nsect):
|
|
ln = struct.unpack('<I', d[off+8:off+12])[0]
|
|
payloads += d[off+12:off+12+ln]
|
|
off += 12 + ln
|
|
z8 = bytearray(d); z8[8:12] = b'\0\0\0\0'
|
|
return {
|
|
'full': d,
|
|
'from_0x04': d[0x04:],
|
|
'from_0x0C': d[0x0C:],
|
|
'from_0x30': d[0x30:],
|
|
'from_0x54': d[0x54:],
|
|
'payload_concat': payloads,
|
|
'full_ck_zeroed': bytes(z8),
|
|
'from0x0C_ck_zeroed': bytes(z8)[0x0C:],
|
|
}
|
|
|
|
def main():
|
|
blobs = load()
|
|
for name, d, ck in blobs:
|
|
print(f"{name}: size={len(d)} stored_ck={ck:08X}")
|
|
algs = {
|
|
'crc32_le': lambda b: binascii.crc32(b) & 0xFFFFFFFF,
|
|
'crc32_be_byteswap': lambda b: struct.unpack('>I', struct.pack('<I', binascii.crc32(b) & 0xFFFFFFFF))[0],
|
|
'crc32_complement': lambda b: (~binascii.crc32(b)) & 0xFFFFFFFF,
|
|
'adler32': lambda b: zlib.adler32(b) & 0xFFFFFFFF,
|
|
'fletcher16': fletcher16,
|
|
'fletcher32': fletcher32,
|
|
'sum32': sum32,
|
|
'xor32': xor32,
|
|
}
|
|
cands = []
|
|
total = 0
|
|
for aname, fn in algs.items():
|
|
for sname in spans(blobs[0][1]):
|
|
total += 1
|
|
ok = True
|
|
for _, d, ck in blobs:
|
|
try:
|
|
v = fn(spans(d)[sname]) & 0xFFFFFFFF
|
|
except Exception:
|
|
ok = False
|
|
break
|
|
if v != ck:
|
|
ok = False
|
|
break
|
|
status = 'MATCH-ALL' if ok else 'no'
|
|
if ok:
|
|
cands.append((aname, sname))
|
|
# show near-misses? only print matches to stay concise
|
|
if ok:
|
|
print(f" {aname} x {sname}: {status}")
|
|
print(f"tested {total} hypotheses, {len(cands)} full-match candidates")
|
|
if not cands:
|
|
print("No match: checksum is not plain CRC32/adler/fletcher/sum/xor over obvious spans.")
|
|
print("Next: try seeded CRC, TI-AIS style, per-sect accumulate, or mmtd.flags correlation.")
|
|
|
|
if __name__ == '__main__':
|
|
main()
|