1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
|
#!/usr/bin/env python3
"""Audit every derived function name against the firmware's own log strings.
The TAs log `AMD_TA_<X>: <FunctionName> ENTER` on entry to most command handlers. That
string is ground truth for what AMD calls the function, so it is a mechanical check on
names that were derived by hand -- and it catches drift that no amount of re-reading the
disassembly would.
Reports three classes:
MISMATCH the handler logs a name that our symbol does not contain
OK our symbol contains the logged name
NOSTRING no ENTER-style string in the function (nothing to check against)
Usage: ./ta_name_audit.py [--prefix gcp] [--fix-json]
"""
from __future__ import annotations
import argparse
import json
import re
import struct
import sys
from pathlib import Path
import capstone as cs
HERE = Path(__file__).resolve().parent
BASE = 0x400100
def load(prefix: str):
meta = json.loads((HERE / "syms" / ("%s.meta.json" % prefix)).read_text())
body = Path(meta["path"])
if not body.is_absolute():
body = HERE.parent / body
return json.loads((HERE / "libdumps" / ("%s.json" % prefix)).read_text()), body.read_bytes()
def cstr(b: bytes, addr: int) -> str | None:
o = addr - BASE
if not (0 <= o < len(b)):
return None
e = b.find(b"\x00", o)
if e < 0 or e - o > 200:
return None
try:
s = b[o:e].decode("ascii")
except Exception: # noqa: BLE001
return None
return s if s.isprintable() or "\n" in s else None
def strings_in(b: bytes, ea: int, size: int) -> list[str]:
md = cs.Cs(cs.CS_ARCH_ARM, cs.CS_MODE_THUMB)
out = []
off = ea - BASE
if off < 0 or size <= 0 or off + size > len(b):
return out
for i in md.disasm(b[off:off + size], ea):
addr = None
if i.mnemonic == "adr":
m = re.search(r"#(-?0x[0-9a-f]+|-?\d+)", i.op_str)
if m:
v = m.group(1)
addr = ((i.address + 4) & ~3) + (int(v, 16) if v.startswith(("0x", "-0x")) else int(v))
elif i.mnemonic.startswith("ldr") and "pc" in i.op_str:
m = re.search(r"#(0x[0-9a-f]+)", i.op_str)
if m:
a = ((i.address + 4) & ~3) + int(m.group(1), 16)
try:
addr = struct.unpack_from("<I", b, a - BASE)[0]
except Exception: # noqa: BLE001
addr = None
if addr is None:
continue
s = cstr(b, addr)
if s and len(s) > 4:
out.append(s)
return out
NAME_RE = re.compile(r"AMD_TA_[A-Za-z0-9_]+:\s*([A-Za-z_][A-Za-z0-9_]*)")
def logged_name(strs: list[str]) -> str | None:
"""Prefer the ENTER string; fall back to the most common logged identifier."""
for s in strs:
m = NAME_RE.search(s)
if m and "ENTER" in s:
return m.group(1)
counts: dict[str, int] = {}
for s in strs:
m = NAME_RE.search(s)
if m:
counts[m.group(1)] = counts.get(m.group(1), 0) + 1
if not counts:
return None
return max(counts, key=lambda k: counts[k])
def norm(s: str) -> str:
return re.sub(r"[^a-z0-9]", "", s.lower())
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--prefix")
ap.add_argument("--json", default=None, help="write findings to this path")
args = ap.parse_args()
prefixes = [args.prefix] if args.prefix else sorted(
p.name.split(".")[0] for p in (HERE / "libdumps").glob("*.json")
)
allrows = []
for prefix in prefixes:
try:
fns, b = load(prefix)
except Exception as exc: # noqa: BLE001
print("%-10s skipped (%s)" % (prefix, exc))
continue
mism, ok, nostr = [], 0, 0
for f in fns:
nm, ea, sz = f["name"], f["ea"], f.get("size", 0)
if not sz:
nostr += 1
continue
ln = logged_name(strings_in(b, ea, sz))
if not ln:
nostr += 1
continue
if norm(ln) in norm(nm):
ok += 1
else:
mism.append({"prefix": prefix, "ea": hex(ea), "name": nm, "logged": ln})
print("%-10s checked-against-strings: %3d OK, %3d MISMATCH, %3d no string"
% (prefix, ok, len(mism), nostr))
for m in mism:
print(" MISMATCH %-12s %-46s logs %r" % (m["ea"], m["name"], m["logged"]))
allrows += mism
if args.json:
Path(args.json).write_text(json.dumps(allrows, indent=2))
print("\nwrote %s (%d mismatches)" % (args.json, len(allrows)))
return 0
if __name__ == "__main__":
raise SystemExit(main())
|