#!/usr/bin/env python3 """Linear-sweep Thumb disassembly of DA14585 firmware. Code region = file[0x40:], mapped base 0x07FC0000. - 16KB-aligned linear sweep; on decode failure emit .word and resync (+2). - Function prologue detection: PUSH {...,lr} (encoding 0xB5xx). Output: analysis/firmware/fw.asm (addr: bytes mnemonic [;;FUNC markers]) """ from capstone import Cs, CS_ARCH_ARM, CS_MODE_THUMB, CS_MODE_LITTLE_ENDIAN IMG = "app/firmware-PP_da14585_4.2_CH.img" OUT = "analysis/firmware/fw.asm" BASE = 0x07FC0000 HDR = 0x40 def main(): data = open(IMG, "rb").read()[HDR:] md = Cs(CS_ARCH_ARM, CS_MODE_THUMB | CS_MODE_LITTLE_ENDIAN) md.detail = False out = open(OUT, "w") out.write(f"; DA14585 firmware linear sweep, base 0x{BASE:08x}, {len(data)} bytes\n") off = 0 n_ins = 0 n_bad = 0 funcs = [] while off < len(data): chunk = data[off:off+0x2000] addr = BASE + off consumed = 0 for ins in md.disasm(chunk, addr): # skip if disassembler resync'd mid-chunk oddly if ins.address < addr + consumed: continue # fill any gap (undecodable bytes) with .word gap = ins.address - (addr + consumed) while gap > 0: g = addr + consumed raw = data[off+consumed:off+consumed+2] if len(raw) == 2: w = raw[0] | raw[1] << 8 out.write(f"0x{g:08x}: {w:04x} .word 0x{w:04x}\n") else: out.write(f"0x{g:08x}: {raw.hex():<9s} .byte {raw.hex()}\n") consumed += 2 gap -= 2 n_bad += 1 b = ins.bytes # prologue: PUSH {...,lr} => 0xB5xx ; also push {lr} alone? (B500) mark = "" if len(b) == 2 and b[1] == 0xB5: mark = " ;;FUNC" funcs.append(ins.address) out.write(f"0x{ins.address:08x}: {b.hex():<9s} {ins.mnemonic} {ins.op_str}{mark}\n") consumed = ins.address + ins.size - addr n_ins += 1 off += consumed if consumed else 2 if consumed == 0: n_bad += 1 out.write(f"; instructions={n_ins} badwords={n_bad} functions={len(funcs)}\n") out.close() print(f"instructions={n_ins} badwords={n_bad} prologues={len(funcs)}") with open("analysis/firmware/funcs.txt", "w") as f: for a in funcs: f.write(f"0x{a:08x}\n") if __name__ == "__main__": main()