Мост к 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,119 @@
|
||||
#!/usr/bin/env python3
|
||||
"""RT-4D firmware xref/disasm helper.
|
||||
|
||||
python xref.py dis <vaddr> [n] disassemble n bytes at vaddr
|
||||
python xref.py callers <vaddr> who bl/blx's this address
|
||||
python xref.py lit <value> where this 32-bit literal appears (pool refs)
|
||||
python xref.py imm <value> movw/movt pairs building this constant
|
||||
python xref.py find <hexbytes> raw byte search
|
||||
"""
|
||||
import sys, struct, capstone
|
||||
|
||||
BASE = 0x08002800
|
||||
import os
|
||||
_CANDS = [
|
||||
"/home/viktor/claude/rt-4d/stock-fw/rt4d_stock_v3.25_abs_0x08002800.bin",
|
||||
"/home/viktor/claude/rt-4d-repo/firmware/rt4d_stock_v3.25_abs_0x08002800.bin",
|
||||
os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "firmware",
|
||||
"rt4d_stock_v3.25_abs_0x08002800.bin"),
|
||||
]
|
||||
IMG = next(open(p, "rb").read() for p in _CANDS if os.path.exists(p))
|
||||
|
||||
md = capstone.Cs(capstone.CS_ARCH_ARM, capstone.CS_MODE_THUMB)
|
||||
md.detail = True
|
||||
|
||||
|
||||
def _word(va):
|
||||
o = va - BASE
|
||||
if 0 <= o <= len(IMG) - 4:
|
||||
return struct.unpack_from("<I", IMG, o)[0]
|
||||
return None
|
||||
|
||||
|
||||
def dis(va, n=0x60):
|
||||
"""disassemble, annotating PC-relative literal loads with the resolved value"""
|
||||
off = va - BASE
|
||||
import re as _re
|
||||
for i in md.disasm(IMG[off:off + n], va):
|
||||
ann = ""
|
||||
m = _re.search(r"\[pc, #(?:0x)?([0-9a-fA-F]+)\]", i.op_str)
|
||||
if m and i.mnemonic.startswith("ldr"):
|
||||
pcv = ((i.address + 4) & ~3) + int(m.group(1), 16)
|
||||
w = _word(pcv)
|
||||
if w is not None:
|
||||
ann = f" ; @0x{pcv:08X} = 0x{w:08X}"
|
||||
print(f" 0x{i.address:08X}: {i.bytes.hex():<10} {i.mnemonic:8} {i.op_str}{ann}")
|
||||
|
||||
|
||||
def _all_calls():
|
||||
"""Linear sweep collecting bl/blx targets -> {target: [sites]}."""
|
||||
xr = {}
|
||||
for start in (0, 2): # both alignments, dedupe by site
|
||||
for i in md.disasm(IMG[start:], BASE + start):
|
||||
if i.mnemonic in ("bl", "blx") and i.op_str.startswith("#"):
|
||||
t = int(i.op_str[1:], 16)
|
||||
xr.setdefault(t, set()).add(i.address)
|
||||
return {k: sorted(v) for k, v in xr.items()}
|
||||
|
||||
|
||||
def callers(va):
|
||||
xr = _all_calls()
|
||||
hits = xr.get(va, []) + xr.get(va | 1, []) + xr.get(va & ~1, [])
|
||||
hits = sorted(set(hits))
|
||||
print(f"callers of 0x{va:08X}: {len(hits)}")
|
||||
for h in hits:
|
||||
print(f" 0x{h:08X}")
|
||||
return hits
|
||||
|
||||
|
||||
def lit(val):
|
||||
hits = []
|
||||
for off in range(0, len(IMG) - 4, 4):
|
||||
if struct.unpack_from("<I", IMG, off)[0] == val:
|
||||
hits.append(BASE + off)
|
||||
print(f"literal 0x{val:08X}: {len(hits)} hit(s)")
|
||||
for h in hits[:60]:
|
||||
print(f" 0x{h:08X}")
|
||||
return hits
|
||||
|
||||
|
||||
def imm(val):
|
||||
"""find movw/movt pairs that build `val` (constant loaded into a reg)."""
|
||||
lo, hi = val & 0xFFFF, (val >> 16) & 0xFFFF
|
||||
out = []
|
||||
for i in md.disasm(IMG, BASE):
|
||||
if i.mnemonic == "movw" and i.op_str.endswith(f"#{hex(lo)}"):
|
||||
out.append(i.address)
|
||||
print(f"movw #{hex(lo)} (for 0x{val:08X}): {len(out)} site(s)")
|
||||
for a in out[:60]:
|
||||
print(f" 0x{a:08X}")
|
||||
return out
|
||||
|
||||
|
||||
def find(hexs):
|
||||
pat = bytes.fromhex(hexs)
|
||||
off = 0
|
||||
hits = []
|
||||
while True:
|
||||
j = IMG.find(pat, off)
|
||||
if j < 0:
|
||||
break
|
||||
hits.append(BASE + j); off = j + 1
|
||||
print(f"bytes {hexs}: {len(hits)} hit(s)")
|
||||
for h in hits[:60]:
|
||||
print(f" 0x{h:08X}")
|
||||
return hits
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
cmd = sys.argv[1]
|
||||
if cmd == "dis":
|
||||
dis(int(sys.argv[2], 16), int(sys.argv[3], 0) if len(sys.argv) > 3 else 0x60)
|
||||
elif cmd == "callers":
|
||||
callers(int(sys.argv[2], 16))
|
||||
elif cmd == "lit":
|
||||
lit(int(sys.argv[2], 16))
|
||||
elif cmd == "imm":
|
||||
imm(int(sys.argv[2], 16))
|
||||
elif cmd == "find":
|
||||
find(sys.argv[2])
|
||||
Ссылка в новой задаче
Block a user