Beken CRC16-формат образа + де-интерливинг -> чистая прошивка
Образы FM100B = сырой Beken-флеш (32 данных + 2 CRC16 = блоки 34Б; подтверждено периодом 34). tools/bk_deinterleave.py снимает CRC -> research/module-fw/FM100B_V1.2.0.32_clean.bin (чисто, строки целые). Окончательно подтверждает стандартный Beken-флеш -> ltchiptool/bk7231tools подойдут для дампа живого модуля (вариант A). docs/fm100b-firmware-re.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NikMGoqQWWk9wy2ww2vJAr
Этот коммит содержится в:
@@ -105,6 +105,18 @@ Bootloader | Application | RF Firmware | NET info
|
|||||||
Вывод: модуль — универсальный Beken-378x DMR/intercom SoC, используемый разными брендами
|
Вывод: модуль — универсальный Beken-378x DMR/intercom SoC, используемый разными брендами
|
||||||
(Radtel/Kirisun/iRadio). Многое из этого можно дёргать по 0x68 через мост (осторожно — только Query/Read вслепую).
|
(Radtel/Kirisun/iRadio). Многое из этого можно дёргать по 0x68 через мост (осторожно — только Query/Read вслепую).
|
||||||
|
|
||||||
|
## ⭐ Формат образа: Beken CRC16-каждые-32-байта (де-интерливинг)
|
||||||
|
Образы `FM100B_*.bin` — сырой дамп Beken-флеша: **32 байта данных + 2 байта CRC16 = блоки по 34 байта**
|
||||||
|
(подтверждено: непечатные пары строго через 34 байта). Те «битые» 2 байта в строках (`ef 10`,
|
||||||
|
`a8 2c`) — это CRC, не порча. Снятие CRC даёт ЧИСТУЮ прошивку:
|
||||||
|
- `tools/bk_deinterleave.py raw.bin clean.bin` (выкинуть 2 CRC из каждых 34 байт, фаза 0).
|
||||||
|
- Чистый образ: `research/module-fw/FM100B_V1.2.0.32_clean.bin` (1 437 938 Б, снято 89 870 CRC).
|
||||||
|
- После снятия строки целые («RF Firmware», «check failed» и т.д.). **Для будущего реверса — брать
|
||||||
|
чистый образ.**
|
||||||
|
|
||||||
|
Это **окончательно подтверждает стандартный Beken-флеш** → для дампа живого модуля подойдут
|
||||||
|
**ltchiptool / bk7231tools** (они читают этот формат и сами снимают CRC). Путь варианта A ниже.
|
||||||
|
|
||||||
## Достоверность образов и версии
|
## Достоверность образов и версии
|
||||||
- Оба образа (.13 2025-04, .32 2026-01) — plaintext, надёжны (в обоих `beken378`/`EasyFlash`/партиции/
|
- Оба образа (.13 2025-04, .32 2026-01) — plaintext, надёжны (в обоих `beken378`/`EasyFlash`/партиции/
|
||||||
`check failed`). Встречаются одиночные не-ASCII байты в строках (в разных местах у .13 и .32) —
|
`check failed`). Встречаются одиночные не-ASCII байты в строках (в разных местах у .13 и .32) —
|
||||||
|
|||||||
Двоичные данные
Двоичный файл не отображается.
@@ -0,0 +1,62 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Снять CRC16 из сырого дампа Beken-флеша (FM100B/HF6853 модуль RT-4D).
|
||||||
|
|
||||||
|
Формат Beken (bootloader/app партиции): каждые 32 байта данных сопровождаются 2 байтами
|
||||||
|
CRC16 -> физические блоки по 34 байта. Сырой дамп/образ (FM100B_*.bin) содержит эти CRC,
|
||||||
|
что «рвёт» строки и код. Этот тул выкидывает 2 CRC-байта из каждого 34-байтного блока ->
|
||||||
|
чистая прошивка для реверса.
|
||||||
|
|
||||||
|
Подтверждено на FM100B_V1.2.0.32: непечатные пары строго через 34 байта; после снятия
|
||||||
|
строки («RF Firmware», «check failed», «beken378\\intercom») становятся целыми.
|
||||||
|
|
||||||
|
python3 bk_deinterleave.py FM100B_raw.bin FM100B_clean.bin
|
||||||
|
python3 bk_deinterleave.py --crc FM100B_raw.bin # только проверить период CRC
|
||||||
|
|
||||||
|
Совместимо по формату с ltchiptool/bk7231tools (они делают то же при чтении флеша).
|
||||||
|
"""
|
||||||
|
import sys, argparse
|
||||||
|
|
||||||
|
BLK = 34 # 32 данных + 2 CRC16
|
||||||
|
DATA = 32
|
||||||
|
|
||||||
|
def deinterleave(raw: bytes, phase: int = 0) -> bytes:
|
||||||
|
out = bytearray(raw[:phase])
|
||||||
|
body = raw[phase:]
|
||||||
|
for i in range(0, len(body), BLK):
|
||||||
|
out += body[i:i+DATA]
|
||||||
|
return bytes(out)
|
||||||
|
|
||||||
|
def find_crc_period(raw: bytes, start=0, length=0x400):
|
||||||
|
"""вернуть наиболее частый шаг между непечатными байтами (ожидаем 34)"""
|
||||||
|
from collections import Counter
|
||||||
|
poss = [i for i in range(start, min(start+length, len(raw)))
|
||||||
|
if not (32 <= raw[i] < 127 or raw[i] in (0, 10, 13))]
|
||||||
|
steps = Counter(b-a for a, b in zip(poss, poss[1:]))
|
||||||
|
return steps.most_common(5)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ap = argparse.ArgumentParser()
|
||||||
|
ap.add_argument("infile")
|
||||||
|
ap.add_argument("outfile", nargs="?")
|
||||||
|
ap.add_argument("--phase", type=int, default=0, help="смещение начала блоков (обычно 0)")
|
||||||
|
ap.add_argument("--crc", action="store_true", help="только показать период CRC")
|
||||||
|
a = ap.parse_args()
|
||||||
|
raw = open(a.infile, "rb").read()
|
||||||
|
if a.crc or not a.outfile:
|
||||||
|
# поискать текстовую область для оценки периода
|
||||||
|
for s in (0x15c000, 0x100000, 0x80000, 0):
|
||||||
|
if s < len(raw):
|
||||||
|
p = find_crc_period(raw, s)
|
||||||
|
if p and p[0][0] == BLK:
|
||||||
|
print(f"CRC-период {BLK} подтверждён @0x{s:x}: {p}")
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print("период 34 не найден — возможно, образ уже чистый или другой формат")
|
||||||
|
if not a.outfile:
|
||||||
|
return
|
||||||
|
clean = deinterleave(raw, a.phase)
|
||||||
|
open(a.outfile, "wb").write(clean)
|
||||||
|
print(f"{a.infile} ({len(raw)}) -> {a.outfile} ({len(clean)}), снято {len(raw)-len(clean)} CRC-байт")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Ссылка в новой задаче
Block a user