#!/usr/bin/env python3 """SUPERSEDED 2026-09-30: checksum solved (see lofi_image.compute_checksum). Kept for methodology only — do not run expecting new results. Long-running checksum cracker for Lofi-12 XT cmtd+0x08. Strategy: 3 known (image, checksum) pairs let us test each hypothesis fast with early exit, plus detect CONSTANT-XOR-masked CRCs (stored = crc ^ mask). Phases: A (seconds): zlib-speed hashes (crc32/adler/word-sums/fnv) x spans x field-modes. B (long): generic table-driven CRC32 parameter search (poly x init x xorout x refin x span x field-mode), multiprocessed, checkpointed, resumable. Usage (long run): python3 tools/lofi_checksum_crack.py --jobs $(nproc) --out /tmp/opencode/ck-results.txt Quick smoke test: python3 tools/lofi_checksum_crack.py --quick-only python3 tools/lofi_checksum_crack.py --jobs 2 --limit 40 --out /tmp/opencode/ck-smoke.txt Stdlib only. Safe: read-only on stock .bin files. """ import argparse, binascii, itertools, json, multiprocessing as mp import os, struct, sys, time, zlib BASE = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) IMAGES = [ ('1.1.156', 'Lofi-12XT_v1.1.156/Lofi-12XT_v1.1.156/Lofi-12 XT.bin'), ('1.2.179', 'Lofi-12XT_v1.2.179/Lofi-12XT_v1.2.179/Lofi-12 XT.bin'), ('1.5.205', 'Lofi-12XT_v1.5.205/Lofi-12XT_v1.5.205/Lofi-12 XT.bin'), ] POLYS = [ # normal (non-reflected) form 0x04C11DB7, # IEEE / PKZIP 0x1EDC6F41, # CRC-32C (Castagnoli) 0x741B8CD7, # CRC-32K (Koopman) 0x1A2B3E55, # spare / nonstandard probes 0x814141AB, # CRC-32Q 0x000000AF, # tiny-poly probe (catches nibble-CRC schemes fast-fail) ] INITS = [0x00000000, 0xFFFFFFFF] XOROUTS = [0x00000000, 0xFFFFFFFF] REFINS = [True, False] FIELDMODES = ['asis', 'zeroed', 'ff'] # how cmtd+0x08 bytes are treated during hashing SPANS = ['full', 'from_0x0C', 'from_0x30', 'from_0x54', 'payload_concat', 'headers_only', 'sect_headers_mixed'] def load_images(): blobs = [] for ver, rel in IMAGES: p = os.path.join(BASE, rel) d = open(p, 'rb').read() assert d[:4] == b'cmtd' and d[0x30:0x34] == b'mmtd', p ck = struct.unpack('> 1) ^ rpoly if c & 1 else c >> 1 t.append(c & 0xFFFFFFFF) else: for i in range(256): c = i << 24 for _ in range(8): c = ((c << 1) ^ poly) & 0xFFFFFFFF if c & 0x80000000 else (c << 1) & 0xFFFFFFFF t.append(c) _crc_tables[key] = t return t def crc_generic(buf: bytes, poly, init, refin, xorout): tab = crc_table(poly, refin) crc = init if refin: for b in buf: crc = tab[(crc ^ b) & 0xFF] ^ (crc >> 8) else: for b in buf: crc = tab[((crc >> 24) ^ b) & 0xFF] ^ ((crc << 8) & 0xFFFFFFFF) return (crc ^ xorout) & 0xFFFFFFFF def fnv1a32(buf: bytes): h = 0x811C9DC5 for b in buf: h = ((h ^ b) * 0x01000193) & 0xFFFFFFFF return h def wordsum_le(buf: bytes): s = 0 for i in range(0, len(buf) - 3, 4): (w,) = struct.unpack('I', struct.pack('bufs, ck); pickled once per worker def _worker(param): poly, init, xorout, refin, span, fmode = param idx = _FIELDMODE_IDX[fmode] try: r = [] for ver, sbufs, ck in _G_BLOBS: v = crc_generic(sbufs[span][idx], poly, init, refin, xorout) r.append((ver, ck, v)) if r[0][2] == r[0][1] and r[1][2] == r[1][1] and r[2][2] == r[2][1]: return ('EXACT', param, 0) m0, m1, m2 = r[0][1] ^ r[0][2], r[1][1] ^ r[1][2], r[2][1] ^ r[2][2] if m0 == m1 == m2: return ('MASK', param, m0) except Exception: pass return None def phase_b(blobs, jobs, out, limit=None, resume_from=0): params = list(all_params()) if limit: params = params[:limit] total = len(params) print(f'=== Phase B: generic CRC search: {total} combos, jobs={jobs} ===', flush=True) # pack span buffers once (pickle to workers a single time) packed = [(ver, span_bufs(d), ck) for ver, d, ck in blobs] done = resume_from t0 = time.time() found = [] with mp.Pool(jobs, initializer=_init_worker, initargs=(packed,)) as pool: CH = 8 for i, res in enumerate(pool.imap_unordered(_worker, params, chunksize=CH), start=1): if i <= done: continue if res is not None: kind, param, mask = res poly, init, xorout, refin, span, fmode = param line = (f'{kind} poly={poly:08X} init={init:08X} xorout={xorout:08X} ' f'refin={int(refin)} span={span} field={fmode} mask={mask:08X}') print(f' *** {line}', flush=True) found.append(line) with open(out, 'a') as fh: fh.write(line + '\n') if i % 50 == 0 or i == total: el = time.time() - t0 rate = i / max(el, 1e-6) print(f' [{i}/{total}] {rate:.1f} combos/s elapsed={el:.0f}s', flush=True) with open(out + '.progress', 'w') as fh: fh.write(json.dumps({'done': i, 'total': total, 'elapsed': el, 'found': found}) + '\n') el = time.time() - t0 print(f'Phase B done: {total} combos in {el:.0f}s, {len(found)} candidates.', flush=True) return found _G_SEED_BUFS = None _G_SEED_CKS = None def _init_seed_worker(bufs, cks): global _G_SEED_BUFS, _G_SEED_CKS _G_SEED_BUFS = bufs _G_SEED_CKS = cks def _seed_scan(task): lo, hi = task b1, b2, b3 = _G_SEED_BUFS s1, s2, s3 = _G_SEED_CKS hits = [] for seed in range(lo, hi): if (binascii.crc32(b1, seed) & 0xFFFFFFFF) == s1: if ((binascii.crc32(b2, seed) & 0xFFFFFFFF) == s2 and (binascii.crc32(b3, seed) & 0xFFFFFFFF) == s3): hits.append(seed) return (lo, hi, hits) def _seed_worker(args): lo, hi, span, fmode = args idx = _FIELDMODE_IDX[fmode] b1, b2, b3 = _G_SEED[0][span][idx], _G_SEED[1][span][idx], _G_SEED[2][span][idx] s1, s2, s3 = _G_SEED[3] hits = [] for seed in range(lo, hi): c1 = binascii.crc32(b1, seed) & 0xFFFFFFFF if c1 != s1: # xor-mask path: derive mask from image 1, confirm on 2+3 # (costs 2 more CRCs only on the rare near-hit; here c1!=s1 always # so check mask constancy cheaply only every step? skip: exact-only # in seed phase for speed; mask search lives in Phase B) continue c2 = binascii.crc32(b2, seed) & 0xFFFFFFFF c3 = binascii.crc32(b3, seed) & 0xFFFFFFFF if c2 == s2 and c3 == s3: hits.append(f'EXACT seed={seed:08X} span={span} field={fmode}') return hits def phase_c(blobs, jobs, out, seed_max=1 << 24, seed_span='full', seed_chunk=4096): print(f'=== Phase C: seeded CRC32-IEEE brute force: seeds 0..{seed_max} ' f'span={seed_span} jobs={jobs} ===', flush=True) print(' (C-speed crc32; exact-match only; this is the hours-long phase)', flush=True) # NOTE: 'zeroed' = cmtd+0x08 treated as 00s during hashing (standard scheme); # 'asis' re-check is cheap relative to seed space, so do zeroed first. t0 = time.time() found = [] for fmode in ('zeroed', 'asis'): idx = _FIELDMODE_IDX[fmode] bufs = [span_bufs(d)[seed_span][idx] for _, d, _ in blobs] cks = [ck for _, _, ck in blobs] found += _phase_c_loop(bufs, cks, seed_span, fmode, seed_max, seed_chunk, jobs, out, t0) return found def _phase_c_loop(bufs, cks, span, fmode, seed_max, chunk, jobs, out, t0): b1, b2, b3 = bufs s1, s2, s3 = cks found = [] # shard seed space across workers: each task scans [lo,hi) tasks = [(lo, min(lo + chunk, seed_max)) for lo in range(0, seed_max, chunk)] total = len(tasks) done = 0 with mp.Pool(jobs, initializer=_init_seed_worker, initargs=(bufs, cks)) as pool: for lo, hi, hits in pool.imap_unordered(_seed_scan, tasks, chunksize=4): done += 1 for seed in hits: line = f'EXACT seed={seed:08X} span={span} field={fmode}' print(f' *** {line}', flush=True) found.append(line) with open(out, 'a') as fh: fh.write(line + '\n') if done % max(1, total // 20) == 0 or done == total: el = time.time() - t0 print(f' Phase C [{done}/{total} chunks] seeds~{done*chunk}/{seed_max} ' f'elapsed={el:.0f}s', flush=True) el = time.time() - t0 print(f'Phase C done: {seed_max} seeds in {el:.0f}s, {len(found)} hits.', flush=True) return found def main(): ap = argparse.ArgumentParser() ap.add_argument('--jobs', type=int, default=max(1, (os.cpu_count() or 4) - 1)) ap.add_argument('--out', default='/tmp/opencode/ck-results.txt') ap.add_argument('--quick-only', action='store_true') ap.add_argument('--skip-phase-a', action='store_true') ap.add_argument('--skip-phase-b', action='store_true') ap.add_argument('--skip-phase-c', action='store_true') ap.add_argument('--limit', type=int, default=None, help='test only first N combos (smoke test)') ap.add_argument('--resume-from', type=int, default=0) ap.add_argument('--seed-max', type=int, default=1 << 24, help='seed brute-force range 0..SEED_MAX (default 2^24 ≈ hours)') ap.add_argument('--seed-span', default='full', choices=SPANS) ap.add_argument('--seed-chunk', type=int, default=4096) a = ap.parse_args() blobs = load_images() for ver, d, ck in blobs: print(f'{ver}: size={len(d)} stored_ck={ck:08X}', flush=True) if not a.skip_phase_a: phase_a(blobs) if a.quick_only: print('quick-only: stopping before Phase B/C.', flush=True) return open(a.out, 'a').write(f'# run {time.ctime()} jobs={a.jobs} limit={a.limit} seed_max={a.seed_max}\n') if not a.skip_phase_b: phase_b(blobs, a.jobs, a.out, limit=a.limit, resume_from=a.resume_from) if not a.skip_phase_c and not a.limit: phase_c(blobs, a.jobs, a.out, seed_max=a.seed_max, seed_span=a.seed_span, seed_chunk=a.seed_chunk) if __name__ == '__main__': main()