| Commit message (Collapse) | Author | Age |
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
ta_entry_points read the trampoline's +0xA0 (Create) and +0xB0 (Invoke) slots and threw away the
three between them. Those are TA_DestroyEntryPoint, TA_OpenSessionEntryPoint and
TA_CloseSessionEntryPoint. That is the whole reason TMZ sat at 50% named: it has six functions and
three of the unnamed ones were entry stubs the header identifies exactly.
The slot order is read off GCP rather than assumed from the GP spec. GCP has all five named
independently and its table reads Create, nullsub(destroy), OpenSession, CloseSession, Invoke, with
WSPE and HDCP agreeing and the types agreeing too -- slots 0, 2, 4 return a status, slots 1 and 3 are
void. Running the new derivation against GCP reproduces GCP's own names exactly, which is the check
that makes this evidence rather than convention.
Applied to every module, only where porting left the address unnamed. Per-module: tmz 50 -> 100,
rap 76 -> 98, csu 70 -> 92, oc 90 -> 97, dtm 75 -> 89. Corpus 19,038 -> 19,126 meaningful names
(65.8% -> 66.1%), field share 75.5% -> 75.8%.
RAS disagrees and is deliberately left alone. Its libdump names TA_CreateEntryPoint and
TA_DestroyEntryPoint at addresses the header assigns to OpenSession and CloseSession, a systematic
two-slot shift across all 18 builds. My first run wrote the header names anyway, which left RAS
asserting two different Create entry points -- a contradiction I introduced and then caught by
checking for duplicated roles. A guard now refuses to add a second holder of a role, so the conflict
is reported on every build and nothing is written. No build has a duplicated entry-point role.
Which source is right for RAS stays open: the obvious tiebreaker, argument count, is not decisive on
functions IDA never prototyped.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Matching prototypes to functions by NAME reached only 8 of GCP's 24, and the reason turned out
to be a correctness problem rather than a matching one: this tree carries two naming
generations that disagree about which command a handler serves. fw/pseudo calls the handler at
0x402f90 gcp_cmdFE0C_CreateCryptoSession; fw/libdumps calls the same function
gcp_cmdFE0C_CreateAuthenticatedSession. The hardware findings settle it --
FINDING-GCP-ACQUIRE-GATE-BYPASS records 0xFE0C as CreateCryptoSession and
FINDING-GCP-FE0D-REFCOUNT-DESYNC agrees -- so the libdump name is wrong and had propagated
into every artifact generated so far.
Keying on the command id and resolving the address through the dispatcher avoids the
disagreement entirely, because the dispatcher is the only authority on which function serves
which command. GCP goes from 5-9 applied prototypes per build to 14-17 and from 28 struct-typed
signatures to 172, and the handler at 0x402e94 is relabelled from gcp_cmdFE03_* to
gcp_cmdFE07_* on every build -- the single conflict the cross-check had been reporting since it
was built.
Two more request structures are derived and verified the same way as xgmi_req. OC and WSPE both
dispatch with if/else chains rather than a switch, so the switch-based dispatcher recovery finds
nothing for them and their invoke entry has to be used directly; both work on the host buffer in
place rather than staging a copy, so their offsets are host offsets by construction. oc_req
closes at 0x1C (handler blocks at +0x0C and +0x16, the latter running 6 bytes to the end of the
validated size) and wspe_req at 0x14 (handler block at +0x10). Both sizes are the ones the
dispatchers' own size floors enforce. Applied, OC's entry reads 'v5->cmd_id == 2' and the type
propagates outward to 'struct oc_req **a4'.
Recorded against this work rather than hidden: applying a pseudo-file prototype by name can
introduce a WRONG signature. OC's dispatcher calls oc_cmd01(ctx: v5 + 3, params:
(struct ta_params *)7) -- the second argument is the literal 7, so the inherited
'struct ta_params *' misdescribes it, and the cast in the output is the tell. Prototypes should
be checked against what their call sites actually pass before being applied.
Measured from the artifacts rather than from effort: struct-typed signatures 309/10070 (3.1%)
-> 456/10070 (4.5%), modules with a derived request struct 0 -> 3, modules with zero struct-typed
signatures 6 -> 4. The rate stays low for a structural reason worth stating plainly: ASD is 6763
of the 10070 functions and has no recorded type information at all, so work on the small
components cannot move the global figure. ASD is where the remaining value is.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The decompiled artifacts were missing almost all type information. Three things were
available in the tree and only one was being used:
* declare_type was never called, so no type definitions were parsed at all;
* set_type ran only for the four handover types, because fw/libdumps records just
{ea,hash,name,size} and the twelve Windows TA types therefore had no signatures to port;
* the recorded comments were read into a variable and discarded without ever being applied.
All three are now applied, in dependency order, and counted per build so the result is
measurable instead of something you have to notice by reading the output. Across 99 builds:
7727 prototypes (was 1232, all of them ASD), 6068 comments (was zero), 309 struct-typed
signatures (was zero).
The reason prototypes were failing was NOT the missing types, which is what I assumed when I
reported "202 of 1434 rejected" without diagnosing it. Declaring the types first changed the
rejection count by nothing. 205 of the 239 failures were prototypes recorded in the bare
shape 'int()', with no __fastcall for the name-insertion to latch onto, so they were emitted
as the unparsable 'int();'. The name now goes before the first parenthesis whatever the
shape, and prototypes land 100%: 1434/1434 on the ASD reference, 99/99 HDCP, 9/9 DTM.
Worth recording so nobody repeats the detour: the 43 'local_types' in each handover symbol
set are ARM NEON and SVE vector typedefs plus va_list and BOOL -- compiler boilerplate with
no domain structs in it. Declaring them is harmless and buys nothing. The only real PSP
structs in this tree are the seven hand-written ones in fw/ta_ida.py (gcp_ctx, ta_params,
gcp_drm_session, gcp_auth_session, gcp_crypto_session, merit_slot, merit_table), which were
sitting unused and are now applied: GCP command handlers finally read as
'(struct gcp_ctx *ctx, struct ta_params *params)' rather than '(int, int)'.
So the remaining type gap is a data gap, not a pipeline one. Thirteen of the sixteen
components have no struct definitions anywhere in the repo to apply, and ASD's 6800
prototypes are almost entirely primitive-typed, which is why it shows only 30 struct-typed
signatures against 1439 named functions. Closing that needs structs to be defined by hand,
component by component.
|
|
|
twelve types
Byte and mnemonic similarity cannot name a handler whose code diverged, and that failure
landed exactly where it hurts: gcp_cmdFE0C, the function carrying the acquire-gate
refcount bypass, was unnamed on six of eleven GCP builds. The dispatch table does not care
about similarity -- whatever sits in the 0xFE0C arm is the 0xFE0C handler by construction.
So the handler names now come from the image itself: the header's trampoline gives
TA_InvokeCommandEntryPoint, Hex-Rays recovers the switch, and each case arm is attributed
to the function it calls. Parsing the decompiler's switch output beats hand-rolling Thumb
jump-table decoding, and callee tokens resolve through the live function list rather than
by assuming they look like sub_<addr>, so it works whether or not names were applied first.
Validated against the one authoritative map that already existed: all 14 commands this
recovers for GCP 22.0.4.50 match fw/cmdmaps/gcp_ea.json address for address, with none
spurious. The three it misses (0x3, 0xFE08, 0xFE0E) are precisely the arms with no callee
-- inlined into the dispatcher, and recorded in the old map as loc_ labels that were never
function starts. FE0C is now named on all eleven builds.
Two bugs the first attempt had, both caught by its own conflict reporting. Walking three
levels deep from the invoke entry swept up nested sub-command switches inside individual
handlers and invented a GCP command 0x0F that does not exist; the walk is now limited to
the invoke entry and its direct callees, with a depth-2 fallback only when depth 1 finds no
switch at all. And one handler commonly serves several commands -- GCP routes 0xFE03-0xFE07
to one function -- so commands are grouped by handler address before naming or judging
agreement.
The self-port gate was measuring the wrong quantity. Dividing EXACT matches by every
recorded symbol conflated a broken matcher with IDA simply not defining a function at a
recorded address, and failed five sound types on that basis -- TMZ scored 17%. The
denominator is now the reference symbols that are function starts in the reference build;
all twelve types then gate at 100%.
That fix made the gate blind to a different error, so it gained a second check. MERIT ships
two distinct 24.0.4.42 builds, the reference was being chosen by version alone, and the
libdump had been recorded from 444eceee while bc1706f8 was loaded: 24 of 31 symbols landed
on nothing and every MERIT build sat near 30% named. The reference is now pinned by hash
from fw/syms/<ta>.meta.json, and an orphan-symbol rate above 10% is reported as a suspected
build/symbol mismatch. MERIT went to 58-83% named.
62 builds across twelve types, zero decompile failures. The remaining conflicts are real
and worth reading rather than suppressing: GCP's 0xFE07 handler carries the inherited name
gcp_cmdFE03_CreateAuthenticatedSession, because the older name-keyed cmdmap collapsed
0xFE03-0xFE07 onto one label while the dispatcher shows only 0xFE07 reaching that function.
|