Files
lumia-emulator/tools/ffu_parse.py
T

106 lines
3.5 KiB
Python

#!/usr/bin/env python3
"""ffu_parse.py — разбор FFU v1 (Lumia): security header, manifest, GPT, store.
Использование: ./tools/ffu_parse.py fw/dl/XXX.ffu [--gpt] [--manifest]
WIP: только чтение, без проверки подписей/hashtable.
"""
import struct
import sys
import uuid
import mmap
ACC = {0: "BEGIN", 1: "SEQ", 2: "END"}
def guid_le(b):
return str(uuid.UUID(bytes_le=b))
def parse(path):
raw = open(path, "rb")
f = mmap.mmap(raw.fileno(), 0, access=mmap.ACCESS_READ)
sec = f.read(32)
cb, sig, chunk_kb, alg, cat_sz, hash_sz = struct.unpack("<I12sIIII", sec)
assert sig == b"SignedImage ", "not an FFU"
chunk = chunk_kb * 1024
off = 32 + cat_sz + hash_sz
off += (chunk - off % chunk) % chunk
f.seek(off)
ih = f.read(24)
mlen, csize = struct.unpack("<II", ih[16:24])
man = f.read(mlen).decode("ascii", errors="replace")
hdr_size = off + 24 + mlen
hdr_size += (chunk - hdr_size % chunk) % chunk
info = {
"chunk": chunk, "catalog": cat_sz, "hashes": hash_sz // 32,
"manifest_len": mlen, "header_size": hdr_size,
}
for tag in ["DevicePlatformId0", "OSVersion", "AntiTheftVersion",
"SectorSize", "MinSectorCount"]:
import re
m = re.search(tag + r"\s*=\s*([^\r\n]+)", man)
info[tag] = m.group(1).strip() if m else "?"
# store header
f.seek(hdr_size)
sh = f.read(264)
bs, wdc = struct.unpack("<I", sh[224:228])[0], struct.unpack("<I", sh[228:232])[0]
info["block_size"] = bs
descs = []
for _ in range(wdc):
loc_cnt, blk_cnt = struct.unpack("<II", f.read(8))
locs = [struct.unpack("<II", f.read(8)) for _ in range(loc_cnt)]
descs.append((blk_cnt, locs))
info["write_descs"] = len(descs)
info["payload_blocks"] = sum(b for b, _ in descs)
# GPT lives somewhere in payload (sparse); find VALID header
gpt_off = -1
o = hdr_size
while True:
o = f.find(b"EFI PART", o)
if o < 0:
break
hdr = bytes(f[o:o + 92])
rev, hsz = struct.unpack("<II", hdr[8:16])
nent0, esz0 = struct.unpack("<II", hdr[80:88])
if rev == 0x10000 and hsz == 92 and nent0 == 128 and esz0 == 128:
gpt_off = o
break
o += 8
assert gpt_off > 0, "no valid GPT"
assert gpt_off > 0, "no GPT"
info["gpt_chunk"] = (gpt_off - hdr_size) // chunk
hdr = bytes(f[gpt_off:gpt_off + 92])
assert hdr[:8] == b"EFI PART", "no GPT"
nent, esz = struct.unpack("<II", hdr[80:88])
parts = []
ent_off = gpt_off + 512 # entries start at next sector after header
for _ in range(nent):
e = bytes(f[ent_off:ent_off + esz])
ent_off += esz
t, u = e[:16], e[16:32]
if t == b"\x00" * 16:
continue
lba0, lba1, attr = struct.unpack("<QQQ", e[32:56])
name = e[56:128].decode("utf-16-le").rstrip("\x00")
parts.append((name, lba0, lba1, guid_le(t)))
f.close()
return info, parts, man
def main():
path = sys.argv[1]
info, parts, man = parse(path)
for k, v in info.items():
if k != "write_descs":
print(f"{k}: {v}")
if "--manifest" in sys.argv:
print("---- manifest ----")
print(man)
if "--gpt" in sys.argv or True:
print(f"---- GPT: {len(parts)} partitions ----")
for name, a, b, t in parts:
print(f"{name:16s} LBA {a:10d}..{b:<10d} size {(b-a+1)*512//1024:7d}K {t}")
if __name__ == "__main__":
main()