Мост к DMR-модулю, разбор спектра REFV DualTachyon, карта запчастей прошивки
Баузбенд FM100B: - найден прозрачный мост ПК<->модуль (включение с зажатой МЕНЮ), проверен живьём - расшифрована контрольная сумма кадров (one's complement, BE), сверена с ответом рации - инструмент tools/fm100b.py (ping/send/raw/scan) - cmd 0x25 = запрос версии, модуль отвечает V1.2.0.32 (совпало с офиц. образом) - разобран диспетчер входящих кадров: 193 записи, реальный код у 10 команд - cmd 0x59 = индикация приёма, она же управляет гейтом звука PA14 - исправлено: boot-handshake это cmd 0x84, а не 0x64 (research/re/dmr.md) Спектроанализатор REFV DualTachyon (docs/refw-spectrum.md): - вызывается как функция горячей клавиши №22 Analog Spectrum - вход 0x08009CAA -> обычный 0x080139E4 / по зоне 0x08015318 - спектр это экран №11; тик 0x08013BA4 (автомат на 4 состояния), клавиши 0x08013BFC Декомпозиция (docs/firmware-parts.md): - два процессора + внешний SPI = три канала внедрения - карта ресурсов SPI: шрифты, пиньинь, голос, таблица Unicode - найден штатный загрузчик ресурсов FontVoicePicture (шрифты/голос/картинки) - дерево меню целиком: MIC/SPK Gain, RX/TX Limit, SMS Format уже в стоке - аудио двухступенчатое: PA2 питание УНЧ, PA14 гейт от DMR-модуля Прочее: - везде исправлен режим прошивки: тангента PTT вместо клавиши "*" - устаревший Ru-4D_Flasher.exe удалён из репозитория - добавлены инструменты реверса: xref, refs, gpiomap, gpioscan, schem
Этот коммит содержится в:
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Map every GPIO write in the RT-4D app: find calls to the low-level GPIO
|
||||
helpers and back-resolve (port, mask, state) from the preceding instructions."""
|
||||
import struct, re, capstone, os
|
||||
|
||||
BASE = 0x08002800
|
||||
P = "/home/viktor/claude/rt-4d/stock-fw/rt4d_stock_v3.25_abs_0x08002800.bin"
|
||||
IMG = open(P, "rb").read()
|
||||
md = capstone.Cs(capstone.CS_ARCH_ARM, capstone.CS_MODE_THUMB)
|
||||
|
||||
PORTS = {0x40020000: "GPIOA", 0x40020400: "GPIOB", 0x40020800: "GPIOC",
|
||||
0x40020C00: "GPIOD", 0x40021000: "GPIOE", 0x40021400: "GPIOF",
|
||||
0x40021800: "GPIOG"}
|
||||
# low-level writers discovered by disassembly
|
||||
HELPERS = {0x08021C6E: "gpio_set_state(port,mask,state)",
|
||||
0x08021C56: "gpio_bsrr_write(port,mask)",
|
||||
0x08021C4C: "set_bit0(reg,val)"}
|
||||
|
||||
|
||||
def word(va):
|
||||
o = va - BASE
|
||||
return struct.unpack_from("<I", IMG, o)[0] if 0 <= o <= len(IMG) - 4 else None
|
||||
|
||||
|
||||
def lit_of(ins):
|
||||
m = re.search(r"\[pc, #(?:0x)?([0-9a-fA-F]+)\]", ins.op_str)
|
||||
if m and ins.mnemonic.startswith("ldr"):
|
||||
return word(((ins.address + 4) & ~3) + int(m.group(1), 16))
|
||||
return None
|
||||
|
||||
|
||||
# linear sweep, keep a sliding window to resolve register values
|
||||
def scan():
|
||||
hits = []
|
||||
win = []
|
||||
for ins in md.disasm(IMG, BASE):
|
||||
win.append(ins)
|
||||
if len(win) > 14:
|
||||
win.pop(0)
|
||||
if ins.mnemonic == "bl" and ins.op_str.startswith("#"):
|
||||
tgt = int(ins.op_str[1:], 16)
|
||||
if tgt in HELPERS:
|
||||
regs = {}
|
||||
for p in win[:-1]:
|
||||
mm = re.match(r"(r\d+)", p.op_str)
|
||||
dst = mm.group(1) if mm else None
|
||||
if not dst:
|
||||
continue
|
||||
v = lit_of(p)
|
||||
if v is not None:
|
||||
regs[dst] = v
|
||||
elif p.mnemonic in ("movs", "mov.w", "mov") and "#" in p.op_str:
|
||||
try:
|
||||
regs[dst] = int(p.op_str.split("#")[1], 0)
|
||||
except Exception:
|
||||
pass
|
||||
elif p.mnemonic == "movw" and "#" in p.op_str:
|
||||
try:
|
||||
regs[dst] = int(p.op_str.split("#")[1], 0)
|
||||
except Exception:
|
||||
pass
|
||||
hits.append((ins.address, HELPERS[tgt], regs.get("r0"), regs.get("r1"), regs.get("r2")))
|
||||
return hits
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
rows = scan()
|
||||
print(f"{len(rows)} GPIO-helper call sites\n")
|
||||
print(f"{'site':<12}{'helper':<32}{'r0(port/reg)':<22}{'r1(mask/val)':<14}{'r2'}")
|
||||
for site, h, r0, r1, r2 in rows:
|
||||
pn = PORTS.get(r0, f"0x{r0:08X}" if r0 is not None else "?")
|
||||
mask = f"0x{r1:X}" if r1 is not None else "?"
|
||||
bit = ""
|
||||
if r1 and r1 and (r1 & (r1 - 1)) == 0:
|
||||
bit = f" (bit{r1.bit_length()-1})"
|
||||
print(f"0x{site:08X} {h:<32}{pn:<22}{mask+bit:<14}{r2 if r2 is not None else ''}")
|
||||
Ссылка в новой задаче
Block a user