# LEAD: SYS_DRV `sub_E17334` copies to `sp+0x210 + r8` with no upper bound on `r8` **Status: OPEN — the "unreachable" closure was RETRACTED; see "Retraction" at the end.** **Evidence class: [ST] (static only). Not exercised on hardware.** ## The site `sub_E17334` builds a buffer at `sp+0x210` in a `0x6b4` frame, then copies into it: ```asm 0xe17338 subw sp, sp, #0x6b4 0xe17340 ldr.w r8, [sp, #0x6ec] ; a STACKED argument (above the frame) 0xe17344 cmp.w r8, #0xb 0xe17348 blo #0xe17388 ; LOWER bound only: r8 >= 11 0xe1734a sub.w r1, r8, #0xb 0xe1734e cmp sl, r1 0xe17350 bhi #0xe17388 ; sl <= r8 - 11 0xe17352 sub.w r0, r8, sl 0xe1735a sub.w sb, r0, #3 ; sb = r8 - sl - 3 ... loop fills up to sb bytes from r6 = sp+0x210 ... 0xe1739e add.w r0, r6, sb 0xe173aa adds r0, r0, #3 ; dst = sp+0x210 + (r8 - sl) 0xe173a4 mov r2, sl ; len = sl 0xe173b0 blx #0xe018a0 ``` The write therefore ends at **`sp + 0x210 + r8`**. The frame is `0x6b4`, so the available extent from the buffer is `0x4a4` (1188 bytes), and safety requires **`r8 <= 0x4a4`**. **There is no upper bound on `r8` in this function** — only the `>= 0xb` floor. The function pushes 13 registers including `lr`, so an overflow crosses the saved return address. ## The chain above it `sub_E1CDEC` is the only caller. The callee frame is `0x6b4 + 0x34` (13 pushed registers) `= 0x6e8`, so the callee's `[sp+0x6ec]` is the caller's `[sp+4]`, which is `r5` from `strd r0, r5, [sp]` at `0xe1ce82`. And: ```asm 0xe1ce14 ldr r1, [r7, #0x14] 0xe1ce16 lsrs r5, r1, #3 ; r5 = slot[0x14] >> 3 0xe1ce18 cmp r6, r5 0xe1ce1a bls #0xe1ce20 ; r6 <= r5 (r5 used as a CAPACITY for r6) ``` so `r8 = slot[0x14] >> 3`, where `r7 = sub_E0B148(...)` returns a slot of a global table, `dword_E0B188 + 84 * (a1 - 16)` with the index bounded to `[16, 48)`. Overflow therefore requires **`slot[0x14] >= 8 * 0x4a5 = 0x2528`**. ## Why it is not yet a finding `slot[0x14]` has exactly one writer, `sub_E1CC60 @0xe1ccc2`, and it is **guarded**: ```asm 0xe1cc98 ldr r0, [r4, #0x14] 0xe1cc9a ldr r1, [r5, #0x10] 0xe1cc9c cmp r0, r1 0xe1cc9e bhi #0xe1cd16 ; refuse unless src[0x14] <= dst[0x10] 0xe1ccc0 ldr r0, [r4, #0x14] 0xe1ccc2 str r0, [r5, #0x14] ``` This is a slot-to-slot migration that validates the propagated value against the destination slot's capacity field `[+0x10]`. ## Resolved in the following pass: the migration path is NOT the source The table base is the literal value **`0xE575B8`** (IDA's `dword_E0B188` names the *literal's* address, not a variable). Stride 84 is confirmed by the accessor's own arithmetic (`rsb r0,r4,r4,lsl#3` = 7·idx, `add r0,r0,r0,lsl#1` = 21·idx, `lsl #2` = 84·idx), index in `[0,0x20)`. Tainting the accessor's return value across every function, the slot fields that are ever written are `+0x2`, `+0x14`, `+0x1c`, `+0x50` (plus `+0x0`, `+0x20`, `+0x22`, `+0x48`, `+0x4c` written through the base directly). **`slot[+0x10]` has no writer anywhere in SYS_DRV.** So the migration guard in `sub_E1CC60` (`cmp [r4+0x14], [r5+0x10] ; bhi bail`) compares against an **uninitialised** field. If the table is zero-initialised, that path can only ever propagate the value 0, and it is therefore **not** a route to a large `slot[0x14]`. That branch of the lead is closed. > Correction: an earlier draft of this lead was about to cite `sub_E0CA2C @0xe0cc6e` > (`mov.w r0,#0x10001 ; str r0,[r6,#0x10]`) as a writer setting the limit to 65537. That was a > **false attribution** — `r6` there is `0xE256F0`, a different global, not a slot of the > `0xE575B8` table. It was caught by checking the base register's provenance rather than > trusting "this function references the table base". ## The real source, and the remaining question `slot[+0x14]` has two writers, both in `sub_E1D6BC`: ```asm 0xe1d8ca ldr.w r0, [r6, #0x144] 0xe1d8ce lsls r0, r0, #3 0xe1d8d0 str r0, [r4, #0x14] ; slot[0x14] = [r6+0x144] << 3 ``` and `0xe1d8f6`, storing `[[r4+0x4c] + 4]` after a copy bounded by `cmp r2,#0xd0`. The first writer is decisive for this chain: `sub_E1CDEC` computes `r5 = slot[0x14] >> 3`, which exactly undoes the `<< 3`. **So `r8` is literally `[r6+0x144]`** — an element count, stored scaled and recovered unscaled. **Writer 1 (`0xe1d8d0`) is BOUNDED.** `[r6+0x144]`'s only writer anywhere in SYS_DRV is `sub_E18A74 @0xe18b06` (`strd r8, r6, [sb, #0x144]`, with `sb` = the same object), and the stored value `r8 = r4` is set in a 5-arm `tbb` to one of the constants **`0x14, 0x1c, 0x20, 0x30, 0x40`**. Maximum **64**, versus the 1188 needed to overflow. Via this writer the chain is safe by a factor of ~18. ## Writer 2 is the problem: `slot[0x14]` takes a dword of message content `slot[+0x14]`'s other writer is `sub_E1D6BC @0xe1d8f6`: ```asm 0xe1d8e6 ldr r6, [r4, #0x4c] ; r6 = the slot's buffer pointer 0xe1d8e8 cmp r2, #0xd0 0xe1d8ea bhi #0xe1d82c ; copy length bounded to 0xd0 -- the COPY is fine 0xe1d8ec adds r1, #0x70 ; source = message payload + 0x70 0xe1d8f0 blx #0xe0192c ; memcpy(r6, msg+0x70, <=0xd0) 0xe1d8f4 ldr r0, [r6, #4] ; <-- a dword of the JUST-COPIED CONTENT 0xe1d8f6 str r0, [r4, #0x14] ; slot[0x14] = that dword, UNVALIDATED ``` The copy itself is properly bounded. What is not validated is the **value** subsequently read out of the copied buffer and stored into `slot[0x14]`. There is no range test between the load and the store, and no `<< 3` scaling here — unlike writer 1, which stores a count pre-scaled so that the consumer's `>> 3` recovers it. Combined with the consumer: ``` sub_E1CDEC: r5 = slot[0x14] >> 3 ; then used as a CAPACITY, and passed on sub_E17334: r8 = r5, only r8 >= 0xb checked, write ends at sp+0x210+r8 ``` a message dword `V` yields `r8 = V >> 3`, which exceeds the `0x4a4` limit for any `V > 0x2527`. The two writers disagree about the field's units (writer 1 scaled, writer 2 raw), which is itself the smell: the consumer's `>> 3` is correct only for writer 1. ## What must still be established before this is a finding 1. **Is `msg+0x70` host-supplied?** The source derives from `sub_E0B0F4(...)`'s object at `+4`, offset `0x70` — consistent with an RPC payload, but its provenance was not traced to a host-writable buffer. 2. **Is the `0xe1d8e6` arm reachable?** `sub_E1D6BC` selects arms through a chain of comparisons against `dword_E1D900`/`dword_E1D918`/`dword_E1D91C`; which opcode reaches this arm is not yet decoded. 3. **Can the same slot then reach `sub_E1CDEC` -> `sub_E17334`?** Both use the accessor, but no ordering/lifetime argument has been made that the poisoned `slot[0x14]` survives to the consumer. 4. **`sub_E1CDEC`'s own callers** remain untraced, so host reachability of the consumer is equally unestablished. Any one of these failing closes the lead, exactly as happened to the XGMI and `0x109a` leads. ## Honest caveats * Reached only after fixing two defects in my own guard search in the same pass (forward register aliases, and register-register comparisons), so the surrounding "unguarded" labels in `AUDIT-TA-MEMSAFETY-HUNT.md` were unreliable and were re-derived. * The `r8 <= 0x4a4` arithmetic assumes the buffer at `sp+0x210` extends to the frame top; no intervening object was identified, but this was not proven exhaustively. * Reachability from a host-issued RPC has **not** been established. `sub_E1CDEC`'s own callers were not traced, so this may be internal-only, exactly as the XGMI and `0x109a` leads turned out to be. ================================================================================ ## Closure: the consumer is unreachable in sOS/SYS_DRV 0.49.0.54 [RETRACTED — see below] Item 4 of the outstanding list — "is `sub_E1CDEC` reachable from a host RPC at all?" — settles the lead negatively, and it settles it for the whole chain, because `sub_E1CDEC` is the **only** caller of `sub_E17334`. Searching the entire SYS_DRV image: | target | direct `bl`/`blx` callers | inbound `b`/`b.w` from other functions | pointer-table references | |---|---|---|---| | `sub_E1CDEC` (consumer) | **0** | **0** | **none** | | `sub_E17334` (copy site) | 1 — only `sub_E1CDEC` | 0 | none | | `sub_E1D6BC` (poisoner) | 2 (`0xe0c3b8`, `0xe117a0`) | 0 | none | So the poisoning path is live, but nothing in the image can invoke the consumer that turns `slot[0x14]` into an out-of-frame write. The unbounded copy is **dead code in this build**. ### A control that mattered IDA's `xref_query` reported **0 xrefs for every address queried, including `sub_E0B148`** — an accessor my own scanner shows is called from many sites. The xref index is therefore not answering for this database, and any "0 xrefs ⇒ unreachable" conclusion drawn from it would have been worthless. The table above comes from direct disassembly of every function extent, which independently finds `sub_E0B148`'s callers correctly. ### What would reopen this * a build where `sub_E1CDEC` is called (worth re-checking across the harvested SYS_DRV versions — this analysis is specific to 0.49.0.54); * an indirect dispatch mechanism not visible as a `bl`, a branch, or a stored pointer (e.g. an offset-table jump computed at runtime); * reaching `sub_E17334` through some other caller introduced in a later firmware. The defect in `sub_E1D6BC` — storing an unvalidated dword of copied message content into a field whose other writer pre-scales it, and which a consumer shifts by 3 — is real and remains documented above. It is simply not exercisable here. **This is the third lead of this shape to die on reachability**, after the XGMI callee-length copy (SYS_DRV opcode `0xd` unimplemented) and `0x109a` (sole emitter passes a constant zero). ================================================================================ ## RETRACTION: the consumer IS called — the closure above was a false negative The closure rested on "`sub_E1CDEC` has 0 direct callers". **That is wrong.** `sub_E1CDEC` is called from **`0xe1dd34`**, verified by disassembly: ```asm 0xe1dd2e mov r2, sl 0xe1dd30 mov r1, r4 0xe1dd32 mov r0, r5 0xe1dd34 bl #0xe1cdec ; <- the call the earlier scan missed 0xe1dd38 b #0xe1dd04 ``` ### Why the earlier scan missed it It iterated the boundary table and disassembled each function extent linearly, looking for `bl` mnemonics. The call site at `0xe1dd34` **is** inside a defined function (`0xe1dc30`), so this was not a coverage gap in the boundary table — Capstone's linear sweep **desynchronised** within that extent (inline data / a mis-aligned start) and never decoded the instruction. The replacement scan decodes **BL/BLX encodings directly from the halfword stream** over the whole image (`(hw1 & 0xF800) == 0xF000 && (hw2 & 0xC000) == 0xC000`, with the standard `S/J1/J2` offset reconstruction). It is base-independent and does not depend on either function boundaries or sweep synchronisation. Its control: it correctly finds `sub_E17334`'s single caller at `0xe1ce8e`. This is the same failure mode as the retracted ninth pass — **a linear sweep that resynchronises produces both false positives and false negatives** — and it is the second time in this hunt it has cost a wrong conclusion. Any earlier statement of the form "N callers" derived from the extent-walking scan should be treated as a **lower bound only**. ### The call chain as it actually stands ``` sub_E17334 (unbounded copy) <- sub_E1CDEC @0xe1ce8e (sole caller) <- sub_E1DC30 @0xe1dd34 <- sub_E087E4 @0xe0882c <- referenced once as a pointer (0xe087e5, Thumb) from an ARM veneer at 0xe01f2c ``` The veneer context is `ldr r0,[pc,#4] ; ldr r1,[r0] ; ; 0xdeadbeef ; bx pc`, i.e. an entry thunk rather than an ordinary call — consistent with a task or service entry point. ### The question the lead now turns on **How is `sub_E087E4` entered, and can a host-issued request reach it?** It is not called from the `0x10xx` dispatcher (`sub_E105A8`) along any `bl` path; entry appears to be through the veneer at `0xe01f2c`. Until that is answered the lead is **neither confirmed nor closed**. What *is* now established: the poisoning path is live, the copy site is unbounded, and the consumer is genuinely called — so the earlier "dead code" verdict must not be relied upon. ================================================================================ ## Reachability update: the chain head IS the request-handling entry Re-deriving callers three ways (extent-walk, raw encoding scan, and encoding scan verified by disassembly from the owning function's start): | target | raw encoding candidates | verified | note | |---|---|---|---| | `sub_E17334` (copy site) | 1 | 1 | `sub_E1CDEC` | | `sub_E1CDEC` (consumer) | 1 | **0** | `0xe1dd34` — verifier desyncs; call is real (see below) | | `sub_E1DC30` | 1 | 1 | `sub_E087E4` | | `sub_E087E4` (chain head) | 0 | 0 | entered by the OS, not called | | **`sub_E105A8` (dispatcher)** | 1 | 1 | **called by `sub_E087E4`** | The last row is the important one. `sub_E087E4` **calls the `0x10xx` dispatcher**, so it is not orphaned code — it is the SYS_DRV **request-handling entry point**, which is exactly why nothing `bl`s to it. The chain `sub_E087E4 -> sub_E1DC30 -> sub_E1CDEC -> sub_E17334` is therefore anchored at the same entry that services host requests. ### All three scanning methods have failure modes * **extent-walk** (disassemble each function extent, look for `bl`): misses calls when the sweep desynchronises inside the extent — this produced the wrong closure in pass twenty-eight; * **raw encoding scan** (decode BL/BLX patterns from the halfword stream): over-reports, since literal pools and data decode as valid BL encodings; * **encoding + verify from function start**: inherits the desync problem, which is why `sub_E1CDEC` scores 0 here despite a real call. The call at `0xe1dd34` is nonetheless real: disassembling from `0xe1dd20` yields `mov r2,sl ; mov r1,r4 ; mov r0,r5 ; bl #0xe1cdec ; b #0xe1dd04`, and the identical three-move-call-branch arm repeats at `0xe1dd2e` and `0xe1dd3a` — the shape of a switch dispatch, not of misdecoded data. Conversely the 33 `blx #0xe01f24` sites verified 4/4 as real ARM-helper calls, so that helper is genuine too. **Method going forward: a call edge counts only when the raw scan finds it *and* local disassembly shows coherent surrounding code. Neither signal alone is sufficient.** ### Remaining gates (unchanged in kind, now better anchored) 1. is `msg+0x70` host-supplied content? 2. which request type drives `sub_E1DC30`'s switch into the `0xe1dd34` arm, and can the same slot be poisoned via `sub_E1D6BC @0xe1d8f6` first? 3. does the poisoned `slot[0x14]` survive between the two calls? ================================================================================ ## Gate 2 ANSWERED: both ends of the chain are host-reachable opcodes `sub_E1DC30` is the SYS_DRV handler for **opcodes below `0x1000`** — a dispatch family that had never been analysed, because all prior work treated `sub_E105A8` (which serves only `>= 0x1000`) as the whole dispatcher. Its switch (53 opcodes, recovered into `fw/cmdmaps/sysdrv_small_opcodes.json`) contains: | opcode | handler | role in this lead | |---|---|---| | **3** | `sub_E1D6BC` | **the poisoner** — `@0xe1d8f6` stores an unvalidated dword of copied message content into `slot[0x14]` | | **8, 9** | `sub_E1CDEC` | **the consumer** — reads `slot[0x14] >> 3` and passes it to `sub_E17334` as the unbounded length | | 0x37 | `sub_E1CC60` | the guarded slot-to-slot migration examined earlier | | 0x39 | `sub_E1DAF8` | the correctly-bounded `12*n` sibling | | 0x33 | `sub_E1EA54` | the wrapper family with per-callee bounds | So the attack shape is a **two-request sequence on the same slot**: 1. **opcode 3** with a payload whose dword at `+0x74` (i.e. `msg+0x70` plus 4) is large, poisoning `slot[0x14]`; 2. **opcode 8 or 9**, which computes `r8 = slot[0x14] >> 3` and reaches `sub_E17334`, where the only test is `r8 >= 0xb` and the write ends at `sp+0x210+r8` in a `0x6b4` frame that has `lr` pushed. Gate 2 is therefore satisfied: both ends are ordinary, host-issuable SYS_DRV opcodes reached through the same request entry that serves every other command in this audit. ### Remaining gates 1. **is the opcode-3 payload host-supplied?** `msg+0x70` derives from the request buffer copied at `sub_E087E4` (`sub_E0192C(v8, a1)` into a 124-byte local) — strongly suggestive, but the dword's exact offset in the *host-visible* request has not been pinned. 3. **does `slot[0x14]` survive between the two requests?** Both handlers resolve the slot via `sub_E0B148(*(a1+56), a3)`, i.e. the same index/owner pair, and nothing between them was observed to rewrite the field — but this has not been proven, and `sub_E1D6BC` has a second arm (`@0xe1d8d0`) that rewrites it with a bounded value. Neither remaining gate is a reachability question any more; both are provenance/lifetime questions that a hardware experiment would settle directly. ================================================================================ ## TA-side linkage: one command drives both halves The SYS_DRV opcodes are emitted by annotated TA wrappers. For GCP: * `gcp_sysdrv_rpc_3` (`0x405280`) builds `req[0]=3`, `req[1]=2` and `strd r5,r4,[sp,#0x38]` — its two arguments land at **`req+0x38`**, which is precisely the `v9 = a1+14` pointer that `sub_E1DC30` passes to `sub_E1D6BC`. Callers: `gcp_mac_over_host_data_0`, `gcp_cmdFE08_KeyExchangeAuthenticatedSession`, `gcp_cmd04_GetContentKey`, `gcp_cmdFE02_GetObfKeyCryptoSession`, `gcp_cmdFE0A`. * `gcp_sysdrv_rpc_9` (`0x404fe2`) builds `req[0]=9` plus a richer parameter block. Caller: `gcp_cmdFE08_KeyExchangeAuthenticatedSession`. **`gcp_cmdFE08` calls the poisoner (`@0x403554`) and then the consumer (`@0x40357c`)** — a single host-invokable command drives both halves of the chain in the required order. OPM and MERIT have the identical wrapper pair (`opm_sysdrv_rpc_3/_9`, `merit_sysdrv_rpc_3/_9`). ### What this does and does not establish It establishes that the poison-then-consume sequence is **ordinary firmware behaviour on a host-issued command**, not a contrived path — which is also why the value must normally be benign: `slot[0x14]` is written from copied content and consumed as a length on every such call. It does **not** establish that an attacker can make that dword large. The copy source is the channel object at `+0x70` (`*(v9+4) + 112`, `v9 = sub_E0B0F4(...)`), not the TA request buffer directly, so the final gate is unchanged and singular: > **can host-supplied data reach `channel+0x74`, the dword stored into `slot[0x14]`?** ### A practical obstacle worth recording `FE08 KeyExchangeAuthenticatedSession` is one of the **auth-gated** GCP commands — the same class as `FE09`/`FE0A`, which hardware testing showed require an authenticated session in state 3, and `FE02`, which requires a state-2 crypto session. So exercising this chain on silicon likely runs into the **same DRM-session wall** that has blocked the `FE02` key-use oracle throughout this work. `gcp_cmd04_GetContentKey` and `gcp_mac_over_host_data_0` also call the opcode-3 wrapper and may be reachable with less state. ================================================================================ ## Advance: the overflow consumer is dispatched by sub_E1DC30 opcode index 8/9 Decoding sub_E1DC30's dispatch directly from the binary (`psp_out/sys_drv_code.bin`, base 0xE00100): the handler selects arms via `tbb [pc, r1]` at 0xE1DC82 (r1 = sub-opcode, bounded `<= 0x5f`), 96-entry byte table at 0xE1DC86. * **Consumer** (`sub_E1CDEC -> sub_E17334`, arm 0xE1DD2E) is selected by **tbb indices 8 and 9**. So a host request carrying sub-opcode 8 or 9 into this dispatcher reaches the unbounded stack copy. sub_E1DC30 is the crypto dispatcher reached from the SYS_DRV request entry (`sub_E087E4 -> sub_E1DC30`), confirming the consumer is on the host-request path. * **Poisoner** (`sub_E1D6BC`, which does `memcpy(slot_buf, msg+0x70, <=0xd0); slot[0x14] = *(slot_buf+4)`) is NOT a tbb target here (its arm 0xE1DD00 has no table entry); its real callers are 0xE0C3B8 and 0xE117A0 — a SEPARATE opcode/path. So poison and consume are distinct sub-operations. ### The crux now (unchanged conclusion: neither confirmed nor closed) 1. **Same-slot persistence.** Both paths index the `sub_E0B148` slot table (index [16,48)). The overflow needs the slot poisoned by `sub_E1D6BC` (slot[0x14] = a host msg dword > 0x2527) to be the SAME slot the consumer (opcode 8/9) later reads. Whether a host can target the same slot index across the two opcodes, and whether slot[0x14] survives between them, is the open question. 2. **Key independence.** The poisoner's write (slot[0x14] from copied message bytes) and the consumer's stack copy are object/length manipulations; neither obviously needs a *valid* crypto key (unlike the GCP unwrap, which the measured RPC-fails-without-session result killed). If true, this path is NOT behind the DRM-provisioning wall — the key difference that makes it worth pursuing over the refuted GCP unwrap. 3. **Host opcode -> TA mapping.** Which loadable TA issues crypto sub-opcode 8/9 and the poisoner opcode, with host-controlled msg+0x74 — unmapped. Next concrete step: bring `sys_drv2.elf` through the IDA pipeline (per the earlier recommendation) to get `sub_E1DC30`/`sub_E1CDEC`/`sub_E1D6BC` and the slot addressing out of Hex-Rays, resolve same-slot persistence and the key-independence question, then map sub-opcode 8/9 to a host-issuable TA crypto RPC and attempt it on silicon with the fault-oracle. ================================================================================ ## Advance: the consumer chain is KEY-INDEPENDENT (the differentiator from the refuted GCP unwrap) The decisive question for this lead vs. the refuted GCP FE09 unwrap (`LEAD-GCP-FE09-UNBOUNDED-UNWRAP-REACHABLE-VIA-STATE-STAMP.md`, killed because its crypto RPC writes nothing without a provisioned session) is: does the `sub_E1CDEC -> sub_E17334` overflow need a valid crypto key, or just the poisoned slot? Traced in `sys_drv_code.bin` (base 0xE00100): * `sub_E1CDEC` gets a slot via `sub_E0B148(a1[60])`, reads `slot[0x14]` (the poisoned field), forms `r5 = slot[0x14] >> 3`, checks the request count `a2[3] <= r5`, then: * `bl 0xe0fb34` must return 0. **`0xe0fb34` is NOT a crypto op** — it is a pure structure-setup that, by size class `*(slot+4)` in {0x100,0x200,0x300,0x400,0x600,0x800, 0xc00,0x1000}, writes a set of sub-buffer pointers (slot+8/+0x30/+0x50/+0x70/+0x90/+0xd0/ +0x110/...) into the caller's descriptor and returns 0. No `svc`, no key. * The slot-type dispatch then reaches `0xe1ce7a` (for the `0x100` arm), which at `0xe1ce82` does `strd r0,r5,[sp]` — passing `r5 = slot[0x14]>>3` as the extent that `sub_E17334` reads from `[sp+0x6ec]` into `r8`, the unbounded copy length. So the entire consumer path is slot-field checks + a benign descriptor setup + the slot accessor — **no crypto key is required anywhere**. This overflow is therefore NOT behind the DRM-provisioning wall, which is what makes it materially more promising than the GCP unwrap. ## What is still open (and now needs reliable decompilation, not objdump) * **Poisoner same-slot reachability.** `sub_E1D6BC` (slot[0x14] := a host message dword) must write the SAME slot the consumer later reads. Its call sites (reported 0xE0C3B8 / 0xE117A0) do not decode cleanly under a linear Thumb sweep here (desync/mis-decode — the exact failure mode that produced two earlier wrong closures in this file), so the poisoner path and the slot-index relationship are NOT reliably established by hand. * Opcode (crypto sub-opcode 8/9 for the consumer; the poisoner's opcode) -> host-issuable TA crypto RPC mapping. * HW. **Reliable next step (do not hand-decode further):** bring `sys_drv2.elf` through the IDA/Nexus pipeline (`fw/ta_ida.py`) and read `sub_E1CDEC`, `sub_E1D6BC`, `sub_E1DC30` and the `sub_E0B148` slot addressing out of Hex-Rays. That settles same-slot persistence and the opcode map without the sweep-desync errors that this objdump pass hit. The consumer's key-independence (above) is the solid result that justifies the IDA investment: unlike every other corruption lead on this part, this one is not gated by the crypto-session wall.