P1-C: WA dispatch table located (65-entry MCU_EXT_CMD @0x10201304, ~30 ABI-named handlers); scan_tables.py structural method; ForceDisasmPost; findings F7
This commit is contained in:
parent
b9c46bc11d
commit
4042aeda6c
7 changed files with 251 additions and 3 deletions
39
tools/ghidra_scripts/ForceDisasmPost.py
Normal file
39
tools/ghidra_scripts/ForceDisasmPost.py
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
# Headless post-script: force-disassemble all executable blocks and
|
||||||
|
# re-run auto-analysis. Run with:
|
||||||
|
# analyzeHeadless <proj_dir> <proj> -process <prog> -noanalysis \
|
||||||
|
# -scriptPath tools/ghidra_scripts -postScript ForceDisasmPost.py
|
||||||
|
#@category Analysis
|
||||||
|
|
||||||
|
from ghidra.app.cmd.disassemble import DisassembleCommand
|
||||||
|
from ghidra.app.plugin.core.analysis import AutoAnalysisManager
|
||||||
|
from ghidra.program.model.address import AddressSet
|
||||||
|
|
||||||
|
listing = currentProgram.getListing()
|
||||||
|
mem = currentProgram.getMemory()
|
||||||
|
monitor.setMessage('collecting executable blocks')
|
||||||
|
|
||||||
|
total = AddressSet()
|
||||||
|
for b in mem.getBlocks():
|
||||||
|
if b.isExecute() and 'elf' not in b.getName():
|
||||||
|
total.addRange(b.getStart(), b.getEnd())
|
||||||
|
|
||||||
|
before_ins = listing.getNumInstructions()
|
||||||
|
before_fun = currentProgram.getFunctionManager().getFunctionCount()
|
||||||
|
print('ForceDisasm before: %d instructions, %d functions'
|
||||||
|
% (before_ins, before_fun))
|
||||||
|
|
||||||
|
cmd = DisassembleCommand(total, total, False)
|
||||||
|
tx = currentProgram.startTransaction('force disasm')
|
||||||
|
try:
|
||||||
|
ok = cmd.applyTo(currentProgram, monitor)
|
||||||
|
finally:
|
||||||
|
currentProgram.endTransaction(tx, True)
|
||||||
|
print('ForceDisasm disasm apply: %s' % ok)
|
||||||
|
|
||||||
|
mgr = AutoAnalysisManager.getAnalysisManager(currentProgram)
|
||||||
|
mgr.reAnalyzeAll(None)
|
||||||
|
mgr.startAnalysis(monitor)
|
||||||
|
|
||||||
|
print('ForceDisasm after: %d instructions, %d functions'
|
||||||
|
% (listing.getNumInstructions(),
|
||||||
|
currentProgram.getFunctionManager().getFunctionCount()))
|
||||||
|
|
@ -17,9 +17,9 @@ from ghidra.util.task import ConsoleTaskMonitor # noqa: E402
|
||||||
elf, proj_dir, proj_name = sys.argv[1], sys.argv[2], sys.argv[3]
|
elf, proj_dir, proj_name = sys.argv[1], sys.argv[2], sys.argv[3]
|
||||||
mode = sys.argv[4] if len(sys.argv) > 4 else 'entry_only'
|
mode = sys.argv[4] if len(sys.argv) > 4 else 'entry_only'
|
||||||
|
|
||||||
with pyghidra.open_program(elf, project_location=proj_dir,
|
with pyghidra.program_context(pyghidra.open_project(
|
||||||
project_name=proj_name, analyze=True) as flat:
|
__import__('os').path.abspath(proj_dir), proj_name),
|
||||||
prog = flat.getCurrentProgram()
|
'/' + __import__('os').path.basename(elf)) as prog:
|
||||||
fm = prog.getFunctionManager()
|
fm = prog.getFunctionManager()
|
||||||
|
|
||||||
def addr(a):
|
def addr(a):
|
||||||
|
|
|
||||||
46
tools/ghidra_scripts/find_string_xref.py
Executable file
46
tools/ghidra_scripts/find_string_xref.py
Executable file
|
|
@ -0,0 +1,46 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Find strings matching a regex and decompile their referencing functions.
|
||||||
|
|
||||||
|
Usage: dump-like launcher args: <elf> <proj_dir> <proj_name> <regex> [max_funcs]
|
||||||
|
"""
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import pyghidra
|
||||||
|
|
||||||
|
pyghidra.start()
|
||||||
|
|
||||||
|
from ghidra.app.decompiler import DecompInterface # noqa: E402
|
||||||
|
from ghidra.util.task import ConsoleTaskMonitor # noqa: E402
|
||||||
|
|
||||||
|
elf, proj_dir, proj_name, pat = sys.argv[1:5]
|
||||||
|
maxf = int(sys.argv[5]) if len(sys.argv) > 5 else 3
|
||||||
|
|
||||||
|
with pyghidra.program_context(pyghidra.open_project(
|
||||||
|
__import__('os').path.abspath(proj_dir), proj_name),
|
||||||
|
'/' + __import__('os').path.basename(elf)) as prog:
|
||||||
|
listing = prog.getListing()
|
||||||
|
fm = prog.getFunctionManager()
|
||||||
|
rm = prog.getReferenceManager()
|
||||||
|
rx = re.compile(pat)
|
||||||
|
dec = DecompInterface()
|
||||||
|
dec.openProgram(prog)
|
||||||
|
seen = set()
|
||||||
|
for d in listing.getDefinedData(True):
|
||||||
|
dt = d.getDataType().getName().lower()
|
||||||
|
if 'string' not in dt and 'char' not in dt:
|
||||||
|
continue
|
||||||
|
v = d.getValue()
|
||||||
|
if v is None or not rx.search(str(v)):
|
||||||
|
continue
|
||||||
|
print('STRING %s %r' % (d.getAddress(), str(v)[:100]))
|
||||||
|
for ref in rm.getReferencesTo(d.getAddress()):
|
||||||
|
f = fm.getFunctionContaining(ref.getFromAddress())
|
||||||
|
if f is None or f.getEntryPoint() in seen:
|
||||||
|
continue
|
||||||
|
seen.add(f.getEntryPoint())
|
||||||
|
print(' <- %s %s' % (f.getEntryPoint(), f.getName()))
|
||||||
|
if len(seen) <= maxf:
|
||||||
|
r = dec.decompileFunction(f, 90, ConsoleTaskMonitor())
|
||||||
|
if r.decompileCompleted():
|
||||||
|
print(r.getDecompiledFunction().getC()[:6000])
|
||||||
50
tools/ghidra_scripts/force_disasm.py
Executable file
50
tools/ghidra_scripts/force_disasm.py
Executable file
|
|
@ -0,0 +1,50 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Force-disassemble every executable block, then re-analyze.
|
||||||
|
|
||||||
|
Firmware images have no entry graph into most code, so default analysis
|
||||||
|
leaves large undisassembled regions (no xrefs to their strings).
|
||||||
|
|
||||||
|
Usage: <proj_dir> <proj_name> <program_path_in_project>
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import pyghidra
|
||||||
|
|
||||||
|
pyghidra.start()
|
||||||
|
|
||||||
|
from ghidra.app.cmd.disassemble import DisassembleCommand # noqa: E402
|
||||||
|
from ghidra.app.plugin.core.analysis import AutoAnalysisManager # noqa: E402
|
||||||
|
from ghidra.program.model.address import AddressSet # noqa: E402
|
||||||
|
from ghidra.util.task import ConsoleTaskMonitor # noqa: E402
|
||||||
|
|
||||||
|
proj_dir, proj_name, prog_path = sys.argv[1], sys.argv[2], sys.argv[3]
|
||||||
|
monitor = ConsoleTaskMonitor()
|
||||||
|
|
||||||
|
proj = pyghidra.open_project(os.path.abspath(proj_dir), proj_name)
|
||||||
|
with pyghidra.program_context(proj, prog_path) as prog:
|
||||||
|
mem = prog.getMemory()
|
||||||
|
listing = prog.getListing()
|
||||||
|
total = AddressSet()
|
||||||
|
for b in mem.getBlocks():
|
||||||
|
if b.isExecute() and 'elf' not in b.getName():
|
||||||
|
total.addRange(b.getStart(), b.getEnd())
|
||||||
|
before_ins = listing.getNumInstructions()
|
||||||
|
before_fun = prog.getFunctionManager().getFunctionCount()
|
||||||
|
print('before: %d instructions, %d functions'
|
||||||
|
% (before_ins, before_fun))
|
||||||
|
cmd = DisassembleCommand(total, total, False)
|
||||||
|
with pyghidra.transaction(prog, 'force disasm'):
|
||||||
|
ok = cmd.applyTo(prog, monitor)
|
||||||
|
print('disasm apply:', ok)
|
||||||
|
from ghidra.app.script import GhidraScriptUtil
|
||||||
|
from ghidra.program.flatapi import FlatProgramAPI
|
||||||
|
GhidraScriptUtil.acquireBundleHostReference()
|
||||||
|
try:
|
||||||
|
FlatProgramAPI(prog).analyzeAll(prog)
|
||||||
|
finally:
|
||||||
|
GhidraScriptUtil.releaseBundleHostReference()
|
||||||
|
print('after: %d instructions, %d functions'
|
||||||
|
% (listing.getNumInstructions(),
|
||||||
|
prog.getFunctionManager().getFunctionCount()))
|
||||||
|
proj.close()
|
||||||
54
tools/ref/mcu_ext_cmd.enum
Normal file
54
tools/ref/mcu_ext_cmd.enum
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
# MCU_EXT_CMD_* ids from mt76_connac_mcu.h (torvalds master)
|
||||||
|
# Same enum serves WM and WA: MCU_WA_EXT_CMD(t) = MCU_EXT_CMD(t) | WA-routing bit.
|
||||||
|
enum {
|
||||||
|
MCU_EXT_CMD_EFUSE_ACCESS = 0x01,
|
||||||
|
MCU_EXT_CMD_RF_REG_ACCESS = 0x02,
|
||||||
|
MCU_EXT_CMD_RF_TEST = 0x04,
|
||||||
|
MCU_EXT_CMD_ID_RADIO_ON_OFF_CTRL = 0x05,
|
||||||
|
MCU_EXT_CMD_PM_STATE_CTRL = 0x07,
|
||||||
|
MCU_EXT_CMD_CHANNEL_SWITCH = 0x08,
|
||||||
|
MCU_EXT_CMD_SET_TX_POWER_CTRL = 0x11,
|
||||||
|
MCU_EXT_CMD_FW_LOG_2_HOST = 0x13,
|
||||||
|
MCU_EXT_CMD_TXBF_ACTION = 0x1e,
|
||||||
|
MCU_EXT_CMD_EFUSE_BUFFER_MODE = 0x21,
|
||||||
|
MCU_EXT_CMD_THERMAL_PROT = 0x23,
|
||||||
|
MCU_EXT_CMD_STA_REC_UPDATE = 0x25,
|
||||||
|
MCU_EXT_CMD_BSS_INFO_UPDATE = 0x26,
|
||||||
|
MCU_EXT_CMD_EDCA_UPDATE = 0x27,
|
||||||
|
MCU_EXT_CMD_DEV_INFO_UPDATE = 0x2A,
|
||||||
|
MCU_EXT_CMD_THERMAL_CTRL = 0x2c,
|
||||||
|
MCU_EXT_CMD_WTBL_UPDATE = 0x32,
|
||||||
|
MCU_EXT_CMD_SET_DRR_CTRL = 0x36,
|
||||||
|
MCU_EXT_CMD_SET_RDD_CTRL = 0x3a,
|
||||||
|
MCU_EXT_CMD_ATE_CTRL = 0x3d,
|
||||||
|
MCU_EXT_CMD_PROTECT_CTRL = 0x3e,
|
||||||
|
MCU_EXT_CMD_DBDC_CTRL = 0x45,
|
||||||
|
MCU_EXT_CMD_MAC_INIT_CTRL = 0x46,
|
||||||
|
MCU_EXT_CMD_RX_HDR_TRANS = 0x47,
|
||||||
|
MCU_EXT_CMD_MUAR_UPDATE = 0x48,
|
||||||
|
MCU_EXT_CMD_BCN_OFFLOAD = 0x49,
|
||||||
|
MCU_EXT_CMD_RX_AIRTIME_CTRL = 0x4a,
|
||||||
|
MCU_EXT_CMD_SET_RX_PATH = 0x4e,
|
||||||
|
MCU_EXT_CMD_EFUSE_FREE_BLOCK = 0x4f,
|
||||||
|
MCU_EXT_CMD_TX_POWER_FEATURE_CTRL = 0x58,
|
||||||
|
MCU_EXT_CMD_RXDCOC_CAL = 0x59,
|
||||||
|
MCU_EXT_CMD_GET_MIB_INFO = 0x5a,
|
||||||
|
MCU_EXT_CMD_TXDPD_CAL = 0x60,
|
||||||
|
MCU_EXT_CMD_CAL_CACHE = 0x67,
|
||||||
|
MCU_EXT_CMD_RED_ENABLE = 0x68,
|
||||||
|
MCU_EXT_CMD_CP_SUPPORT = 0x75,
|
||||||
|
MCU_EXT_CMD_SET_RADAR_TH = 0x7c,
|
||||||
|
MCU_EXT_CMD_SET_RDD_PATTERN = 0x7d,
|
||||||
|
MCU_EXT_CMD_MWDS_SUPPORT = 0x80,
|
||||||
|
MCU_EXT_CMD_SET_SER_TRIGGER = 0x81,
|
||||||
|
MCU_EXT_CMD_TWT_AGRT_UPDATE = 0x94,
|
||||||
|
MCU_EXT_CMD_FW_DBG_CTRL = 0x95,
|
||||||
|
MCU_EXT_CMD_OFFCH_SCAN_CTRL = 0x9a,
|
||||||
|
MCU_EXT_CMD_SET_RDD_TH = 0x9d,
|
||||||
|
MCU_EXT_CMD_MURU_CTRL = 0x9f,
|
||||||
|
MCU_EXT_CMD_SET_SPR = 0xa8,
|
||||||
|
MCU_EXT_CMD_GROUP_PRE_CAL_INFO = 0xab,
|
||||||
|
MCU_EXT_CMD_DPD_PRE_CAL_INFO = 0xac,
|
||||||
|
MCU_EXT_CMD_PHY_STAT_INFO = 0xad,
|
||||||
|
MCU_EXT_CMD_WF_RF_PIN_CTRL = 0xbd,
|
||||||
|
};
|
||||||
0
tools/ref/mcu_wa_ext_cmd.enum
Normal file
0
tools/ref/mcu_wa_ext_cmd.enum
Normal file
59
tools/scan_tables.py
Executable file
59
tools/scan_tables.py
Executable file
|
|
@ -0,0 +1,59 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Scan firmware regions for consecutive function-pointer tables.
|
||||||
|
|
||||||
|
Finds runs of >= min_run consecutive little-endian dwords that all fall in a
|
||||||
|
given code range — the shape of MCU command-handler dispatch tables.
|
||||||
|
|
||||||
|
Usage: scan_tables.py <region.bin> <code_lo> <code_hi> [min_run] [tolerance]
|
||||||
|
tolerance = max absolute non-code dwords allowed inside a run (0/NULL counted).
|
||||||
|
"""
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
path = Path(sys.argv[1])
|
||||||
|
lo, hi = int(sys.argv[2], 0), int(sys.argv[3], 0)
|
||||||
|
min_run = int(sys.argv[4]) if len(sys.argv) > 4 else 8
|
||||||
|
tol = int(sys.argv[5]) if len(sys.argv) > 5 else 0
|
||||||
|
|
||||||
|
d = path.read_bytes()
|
||||||
|
base = 0
|
||||||
|
# If the region file corresponds to a load address, pass that as lo bound
|
||||||
|
# via argv; region offsets here are file offsets, so also print file offset.
|
||||||
|
words = struct.unpack_from('<%dI' % (len(d) // 4), d)
|
||||||
|
run_start = None
|
||||||
|
run = []
|
||||||
|
tables = []
|
||||||
|
|
||||||
|
|
||||||
|
def flush(end):
|
||||||
|
global run, run_start
|
||||||
|
code = sum(1 for w in run if lo <= w < hi)
|
||||||
|
if len(run) >= min_run and code >= min_run:
|
||||||
|
tables.append((run_start, end, len(run), code))
|
||||||
|
run = []
|
||||||
|
run_start = None
|
||||||
|
|
||||||
|
|
||||||
|
for i, w in enumerate(words):
|
||||||
|
ok = lo <= w < hi or w == 0
|
||||||
|
if ok:
|
||||||
|
if run_start is None:
|
||||||
|
run_start = i * 4
|
||||||
|
run.append(w)
|
||||||
|
else:
|
||||||
|
if run_start is not None and tol:
|
||||||
|
run.append(w) # tolerate, next check trims
|
||||||
|
if sum(1 for x in run[-3:] if not (lo <= x < hi or x == 0)) >= 2:
|
||||||
|
flush(i * 4)
|
||||||
|
else:
|
||||||
|
flush(i * 4)
|
||||||
|
|
||||||
|
for t in tables:
|
||||||
|
start_off, end_off, n, code = t
|
||||||
|
print('table @ file+0x%x..0x%x (%d words, %d code ptrs)'
|
||||||
|
% (start_off, end_off, n, code))
|
||||||
|
vals = words[start_off // 4:end_off // 4]
|
||||||
|
for j, w in enumerate(vals[:64]):
|
||||||
|
mark = '' if lo <= w < hi else ('(0)' if w == 0 else '(?)')
|
||||||
|
print(' [%2d] 0x%08x %s' % (j, w, mark))
|
||||||
Loading…
Reference in a new issue