| Commit message (Collapse) | Author | Age |
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
ASD clean
Rebuilds the retracted v1 without its cause. Function boundaries come from IDA and are
cached, so every instruction decoded starts at a real function entry rather than from a
resynchronising sweep; every writer is re-verified to disassemble inside its own function
to a genuine store of the [base,#imm] form, with reject counts printed so the filtering
is visible; stores through sp and fp are excluded as stack slots rather than fields; and
a source register assigned an immediate now counts as bounded, which v1 missed and which
had it reporting a constant-zero store as an unbounded writer.
The positive control now passes for the right reason: HDCP's site is reported with two
writers that were hand-verified to disassemble to strb.w sb,[r5,#0x38], the very field
whose value the bug loads as its length, with the same base register. The negative
control still holds.
Results: zero sites across all twelve TAs and zero in ASD, with SYS_DRV at 42 still
dominated by offset-keyed conflation.
States the blind spot in that zero. v2 tracks struct-field stores, while the XGMI finding
keeps its length in a stack slot written by a callee through a pointer, so no store of
the tracked form writes it and v2 cannot see it, which is why xgmi reports zero here
though v1 reported that site. The zero therefore means no copy length comes from a struct
field with an unbounded writer, not that no unbounded length exists, and callee-written
in/out length slots remain outside its scope despite being the class that produced the
one real finding in this hunt.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Names alone left Hex-Rays emitting *(_DWORD *)(a1 + 7680) for the most important
expression in this audit, so this adds function prototypes and structure types, and
in the process corrects names that had drifted.
GCP logs AMD_TA_GCP: <Name> ENTER on entry to most handlers, which is ground truth.
fw/ta_name_audit.py checks every derived symbol against it and found 18 mismatches
in GCP, plus two false positives in WSPE where the logged words are ordinary prose.
Combined with a fresh decode of the dispatcher's two tbb tables, giving command to
handler address rather than the stale name-keyed map, this corrects: cmd 3 is
ActivateDRMCertAuthSession and was not defined as a function at all; FE03 to FE06
are unimplemented and share the error arm; FE07 is CreateAuthenticatedSession;
FE0C and FE0D are Create and DestroyCryptoSession; FE0A is QueryAuthenticatedSession
rather than the invented QueryAccessibilityAttributes; FE0E and FE0F likewise. The
hardware sweep independently agrees, since FE03 to FE06 all returned an identical
0x102 while FE07 alone returned 0, which had been sitting in the output mislabelled.
This also corrects a finding's wording: FE0B manages the stride-80 auth array and
FE0D the stride-88 crypto array, so they are two destroy paths over different
arrays rather than a same-type sibling pair. The in-database comment is updated,
including replacing its now-stale 'not yet shown' caveat with the hardware result.
Five structures declared and verified by reading the layout back from IDA and
comparing against the addressing the code performs; the three session arrays tile
the context exactly, which is the check. 179 prototypes applied across all twelve
TAs with no failures. Measured against a pristine database rather than one that
already carries the work: raw casts 81 to 7, anonymous parameters 113 to 0.
Method notes recorded: do not drive the TUI struct editor, since a focus change
types into the filter box and a half-applied redeclare silently truncated three
structs; declare interdependent structs in one parse unit; build prototypes from
the database rather than the stale dump; a type that misrepresents addressing is
worse than none, which is why the lvar retype was reverted; and two metric bugs
were caught only by disbelieving a number that flattered or unfairly punished the
work.
Adds fw/ta_ida.py, fw/ta_name_audit.py, fw/ta_rename_from_logstrings.py,
fw/cmdmaps/gcp_ea.json, fw/audits/name_vs_logstring.json and fw/pseudo/ artifacts.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
misread as empty
The opcode recovery was taking the first stack store in each thunk, which is a
neighbouring field rather than req[0]. XGMI stores 2 to var_78 before storing the
real opcode 0x1067 to var_7C, so every XGMI, AUC and FP opcode came out as 0x2 and
their privilege reach looked empty. The rule is now the immediate stored at [SP],
the store whose var_ offset equals the frame size.
Re-ran all twelve TAs. AUC and FP each reach twelve SYS_DRV opcodes, XGMI thirteen
including the 0x104x and 0x106x families, OC ten. GCP's ASD set is unchanged, so
the 0x202 finding is unaffected.
That also retracts my own claim from two commits ago that AUC, FP and XGMI use a
different kernel ABI whose reach is not enumerable. They use the same svc 0xF2
ABI; my rule was misreading it. The SVC 0xF3 stub is real but is a lazy import
resolver -- XGMI's sub_403CA0 caches a resolved pointer in a global and tail-calls
it, and its callers build an ordinary request struct with the opcode at offset 0 --
so it is another route to the same service RPC rather than a separate privilege
channel.
This supersedes both earlier SVC tables in the document.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
sweep table
Rewrote fw/ta_rpc_names.py so the linear sweep only contributes "a service call
happens near here" and IDA resolves the containing function, service id and
opcode from its own disassembly. The previous table, published one commit ago,
had a shifted opcode column and is now explicitly superseded.
Site confirmation is 100% for every TA except MERIT, where one of thirteen
candidates is rejected as not being an SVC at all -- the sweep misaligning and
the cross-check catching it, which is the reason the tool reports agreement
counts.
Both ASD-reachability findings survive the correction and now rest on IDA's own
analysis rather than my sweep: GCP reaches ASD 0x202, the canary disclosure, and
OPM reaches ASD 0x301, the content-controlled write of up to 5120 bytes. Both had
been dismissed only because no then-known TA emitted them.
Recorded honestly: AUC, FP and XGMI resolve almost nothing because they do not
use the req[0]-opcode form. XGMI caches a handle via SVC 0xF3 in a global and
calls indirectly through it, so their privilege reach is not enumerable by
scanning for opcode immediates. That is flagged as an open item rather than
reported as "no calls".
Every thunk in every TA is now named from IDA-derived values, or reset to
sub_<addr> where unresolved, so no function carries a guessed name.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Added fw/ta_rpc_names.py, which treats the linear sweep as a candidate generator
and IDA as the arbiter: for every svc site it asks IDA what instruction is really
there and which function contains it, and it reports the disagreement count so a
silently misaligned scan cannot pass. On GCP, 25 of 25 candidates are confirmed
by IDA as real SVC instructions.
That check caught a real error. The thunk names first applied from the sweep were
off by one thunk in GCP's table, each function receiving the previous one's
opcode, because the resumable sweep is misaligned in that region. Re-deriving
every opcode from IDA's own disassembly lines gives the corrected service-2
table, with 0x405446 carrying opcode 0x202.
The headline finding is now verified three independent ways: aligned capstone
disassembly from a function boundary, IDA's disassembly of the thunk, and IDA
xrefs for the callers, which are gcp_cmd04_GetContentKey and
gcp_cmdFE02_GetObfKeyCryptoSession. As a control the neighbouring 0x305 thunk
resolves to five different GCP commands, so the caller query discriminates rather
than returning everything.
Also fixed the tooling to use one persistent RPC connection. The first version
spawned a subprocess per call, and since the socket is single-driver a hung
client locked out every later call.
|
|
|
standing negative
Added fw/ta_svc_scan.py, which enumerates every SVC site per TA and recovers
service-2/service-1 RPC opcodes from the store into req[0], per the ABI in
GUARD-INIT-CHAIN.md. Validated against HDCP first: 49 svc 0xF2 sites, matching
the count in the original 89-site audit, with service-1 opcodes matching its
documented list.
The result that matters: VULN-ASD-202-CANARY-LEAK.md rates the ASD canary
disclosure not host-reachable on three axes, the load-bearing one being that no
host-reachable TA forwards opcode 0x202. That scan covered ASD, HDCP, DTM and
RAS, the only TAs known at the time. GCP contains a thunk at 0x405446 that sets
req[0]=0x202 and service 2 and issues svc 0xF2, and it is called from two
host-invokable GCP commands, cmd04 GetContentKey at 0x4037e8 and cmdFE02 at
0x403c42. OPM likewise emits ASD 0x301, the 5120-byte content-controlled write
that was dismissed only because nothing emitted it.
So that axis is now false: GCP and OPM are measured host-invokable on this card
and they reach ASD opcodes the four-TA scan concluded nobody reaches.
Deliberately not claimed: a host-visible leak. ASD 0x202 copies its sixteen
uninitialised stack bytes to a caller-supplied pointer, and the caller here is
GCP, so the canary lands in GCP's buffer; host visibility requires GCP to copy
that region into its response, which is the next step.
Also recorded as a warning attached to the result: the scan's resumable sweep can
fabricate instructions, and an earlier pass reported this very movw from a
misaligned window. Every opcode quoted for GCP and OPM was re-verified by
disassembling from a function boundary, and the call sites by disassembling
around them; the rest of the table is leads until checked the same way.
|