dissection: real RM-1127 GPT33/RKH/P6236, ffu_parse.py mmap, edp 10 ELFs

This commit is contained in:
SashegDev
2026-09-07 08:34:35 +00:00
parent 989763ee72
commit 777d0ef076
3 changed files with 165 additions and 0 deletions
+58
View File
@@ -0,0 +1,58 @@
# Разбор реальных файлов RM-1127 (zern, fw/dl/)
Файлы лежат на zern `~/lumia-emu/fw/dl/` (в гит не входят):
- `RM1127_059X5R5_orig.ffu` 1800142848 B, CRC32 `a41e0775` = VPL — ЦЕЛ
- `RM1085_donor.ffu` 2995126272 B (донор WPInternals)
- `MPRG8909_fh.ede` 146180 B ELF32 ARM entry `0x8008700` (идентичен на lumiadb и archive.org)
- `RM1127_fh.edp` 3530240 B Emergency Payload, 10 ELF внутри
- `0x000940e100420050.mbn` 292392 B generic firehose (Nokia 8110, то же семейство)
- `.vpl/.dcp/.nvi/.xml` companions из archive.org
## Ключи
- `RKH: 56BDC3746226E080146D4D30C8C8432657A5282F`
- `MSM_ID: 009600E1, OEM_ID: 0045, MODEL_ID: 0000, SW_ID: 1A`
- `PlatformID: Microsoft.MSM8909.P6236`, OS `8.15.13333.494`, AntiTheft 1.1
- FFU: `SignedImage, chunk 128K, SHA256, catalog 2501, hashes 13730`, header `0xA0000`
## .edp: 10 ELF (смещение → что)
```
0: 22016 SBL1 (SBL1 Start/End, pm_sbl_boot.c), entry 0x8006b80
1: 293376 RPM.BF.2.1.1 (Oct 23 2015)
2: 461312 TZ.BF.2.5 (QSEE)
3: 1006080 UEFI EDK2 (Dec 29 2015, PCD strings)
4: 2237440 WINSECAPP (qcom.tz.winsecapp)
5: 2660864 UEFISecApp (qcom.tz.uefisecapp)
6-9: модемные блобы (Oct 15 2015, без строк)
```
`.ede` — маленький Sahara-загрузчик (577 строк, в основном серты), сам Firehose-XML живет дальше по цепочке. В SBL1-блобе XML-команд не видно в открытом виде.
## Реальная GPT 550 (Final, 33 раздела!)
Отличается от шаблона 950: `SBL1 LBA 90112` (не 32768!), `QSEE+QHEE` вместо TZ, есть `BOOTMODE`, `TZAPPS 16M`, нет APDP/MSADP/DPO/LIMITS/HYP.
| Раздел | LBA | Размер |
|---|---|---|
| DPP | 4096..20479 | 8M |
| MODEM_FSG/FS1/FS2 | 20480/24576/28672 | 1.5M x3 |
| MODEM_FSC | 32768..32799 | 16K |
| DDR | 36864..36927 | 32K |
| LogFS | 40960..73727 | 16M |
| SSD | 73728..73759 | 16K |
| UEFI_BS_NV / UEFI_RT_NV | 77824 / 81920 | 256K x2 |
| BOOTMODE | 86016..86047 | 16K |
| SBL1 | 90112..91135 | 512K |
| DBI | 94208..94271 | 32K |
| UEFI | 98304..102399 | 2M |
| RPM | 102400..103423 | 512K |
| QSEE | 106496..108543 | 1M |
| QHEE | 110592..111615 | 512K |
| WINSECAPP | 114688..115711 | 512K |
| TZAPPS | 118784..151551 | 16M |
| BACKUP_SBL1/DBI/UEFI/RPM/QSEE/QHEE/WINSECAPP/TZAPPS | 151552..212991 | — |
| PLAT (pconf.bin!) | 212992..229375 | 8M |
| EFIESP | 229376..360447 | 64M |
| MMOS | 360448..372735 | 6M |
| SEC | 372736..372767 | 16K |
| MainOS | 376832..4089855 | 1813M NTFS |
| Data | 4096000..15150847 | 5397M NTFS |
MinSectorCount 15154944 = eMMC 7400M. Парсер: `tools/ffu_parse.py` (mmap, ищет валидный GPT по rev/nent).
+2
View File
@@ -13,5 +13,7 @@
*.vpl
*.rar
*.zip
*.log
*.aria2
!*.md
!*.txt
+105
View File
@@ -0,0 +1,105 @@
#!/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()