# TXM Jailbreak Patch Analysis

Analysis of 6 logical TXM jailbreak patches (11 instruction modifications) applied by `txm_dev.py` on the RESEARCH variant
of TXM from iPhone17,3 / PCC-CloudOS 26.x.

## TXM Execution Model

TXM runs at a guested exception level (GL) under SPTM's supervision:

```
SPTM (GL2) — Secure Page Table Monitor
  ↕ svc #0
TXM (GL1) — Trusted Execution Monitor
  ↕ trap
Kernel (EL1/GL0)
```

SPTM dispatches selector calls into TXM. TXM **cannot** execute SPTM code
(instruction fetch permission fault). TXM must return to SPTM via `svc #0`.

---

## Return Path: TXM → SPTM

All TXM functions return through this chain:

```
TXM function
  → bl return_helper (0x26c04)
    → bl return_trap_stub (0x49b40)
      → movk x16, ... (set SPTM return code)
      → b trampoline (0x60000)
        → pacibsp
        → svc #0          ← traps to SPTM
        → retab            ← resumes after SPTM returns
```

### Trampoline at 0x60000 (`__TEXT_BOOT_EXEC`)

```asm
0x060000: pacibsp
0x060004: svc    #0           ; supervisor call → SPTM handles the trap
0x060008: retab               ; return after SPTM gives control back
```

### Return Trap Stub at 0x49B40 (`__TEXT_EXEC`)

```asm
0x049B40: bti    c
0x049B44: movk   x16, #0, lsl #48
0x049B48: movk   x16, #0xfd, lsl #32    ; x16 = 0x000000FD00000000
0x049B4C: movk   x16, #0, lsl #16       ; (SPTM return code identifier)
0x049B50: movk   x16, #0
0x049B54: b      #0x60000               ; → trampoline → svc #0
```

x16 carries a return code that SPTM uses to identify which TXM operation completed.

### Return Helper at 0x26C04 (`__TEXT_EXEC`)

```asm
0x026C04: pacibsp
0x026C08: stp    x20, x19, [sp, #-0x20]!
0x026C0C: stp    x29, x30, [sp, #0x10]
0x026C10: add    x29, sp, #0x10
0x026C14: mov    x19, x0              ; save result code
0x026C18: bl     #0x29024             ; get TXM context
0x026C1C: ldrb   w8, [x0]            ; check context flag
0x026C20: cbz    w8, #0x26c30
0x026C24: mov    x20, x0
0x026C28: bl     #0x29010             ; cleanup if flag set
0x026C2C: strb   wzr, [x20, #0x58]
0x026C30: mov    x0, x19              ; restore result
0x026C34: bl     #0x49b40             ; → svc #0 → SPTM
```

---

## Error Handler

### Error Handler at 0x25924

```asm
0x025924: pacibsp
...
0x025978: stp    x19, x20, [sp]       ; x19=error_code, x20=param
0x02597C: adrp   x0, #0x1000
0x025980: add    x0, x0, #0x8d8       ; format string
0x025984: bl     #0x25744             ; log error → eventually svc #0
```

### Panic Format

`TXM [Error]: CodeSignature: selector: 24 | 0xA1 | 0x30 | 1`

This is a **kernel-side** message (not in TXM binary). The kernel receives the
non-zero return from the `svc #0` trap and formats the error:
`selector: <selector_num> | <low_byte> | <mid_byte> | <high_byte>`

For `0x000130A1`: low=`0xA1`, mid=`0x30`, high=`0x1` → `| 0xA1 | 0x30 | 1`

---

## Why `ret`/`retab` Fails

> **Historical reference:** These three failed attempts document why patching TXM
> functions to return early via normal return instructions does not work. The only
> viable path is to let the function proceed through the normal `svc #0` return chain
> (see [Return Path](#return-path-txm--sptm) above).

### Attempt 1: `mov x0, #0; retab` replacing PACIBSP

```
0x026C80: mov x0, #0     ; (was pacibsp)
0x026C84: retab           ; verify PAC on LR → FAIL
```

**Result**: `[TXM] Unhandled synchronous exception at pc 0x...6C84`

RETAB tries to verify the PAC signature on LR. Since PACIBSP was replaced,
LR was never signed. RETAB detects the invalid PAC → exception.

### Attempt 2: `mov x0, #0; ret` replacing PACIBSP

```
0x026C80: mov x0, #0     ; (was pacibsp)
0x026C84: ret             ; jump to LR (SPTM address)
```

**Result**: `[TXM] Unhandled synchronous exception at pc 0x...FA88` (SPTM space)

`ret` strips PAC and jumps to clean LR, which points to SPTM code (the caller).
TXM cannot execute SPTM code → **instruction fetch permission fault** (ESR EC=0x20, IFSC=0xF).

### Attempt 3: `pacibsp; mov x0, #0; retab`

```
0x026C80: pacibsp         ; signs LR correctly
0x026C84: mov x0, #0
0x026C88: retab           ; verifies PAC (OK), jumps to LR (SPTM address)
```

**Result**: Same permission fault — RETAB succeeds (PAC valid), but the return
address is in SPTM space. TXM still cannot execute there.

**Conclusion**: No form of `ret`/`retab` works because the **caller is SPTM**
and TXM cannot return to SPTM via normal returns. The only way back is `svc #0`.

---

## Address Mapping

| Segment            | VM Address           | File Offset | Size      |
| ------------------ | -------------------- | ----------- | --------- |
| `__TEXT_EXEC`      | `0xFFFFFFF017020000` | `0x1c000`   | `0x44000` |
| `__TEXT_BOOT_EXEC` | `0xFFFFFFF017064000` | `0x60000`   | `0xc000`  |

Conversion: `VA = file_offset - 0x1c000 + 0xFFFFFFF017020000` (for `__TEXT_EXEC`)

---

## TXM Selector Dispatch

All TXM operations enter through a single dispatch function (`sub_FFFFFFF01702AE80`),
a large switch on the selector number (1-51). Each case validates arguments and calls
a dedicated handler. Relevant selectors:

| Selector | Handler                                   | Purpose                                           |
| -------- | ----------------------------------------- | ------------------------------------------------- |
| 24       | `sub_FFFFFFF017024834` → validation chain | CodeSignature validation                          |
| 41       | `sub_FFFFFFF017023558`                    | Process entitlement setup (get-task-allow)        |
| 42       | `sub_FFFFFFF017023368`                    | Debug memory mapping                              |
| ---      | `sub_FFFFFFF017023A20`                    | Developer mode configuration (called during init) |

The dispatcher passes raw page pointers through `sub_FFFFFFF0170280A4` (a bounds
validator that returns the input pointer unchanged) before calling handlers.

---

## Patch 1-2: CodeSignature Hash Comparison Bypass (selector 24)

**Error**: `TXM [Error]: CodeSignature: selector: 24 | 0xA1 | 0x30 | 1`

### Addresses

| File Offset | VA                   | Original Instruction      | Patch |
| ----------- | -------------------- | ------------------------- | ----- |
| `0x313ec`   | `0xFFFFFFF0170353EC` | `LDR X1, [X20, #0x38]`    | NOP   |
| `0x313f4`   | `0xFFFFFFF0170353F4` | `BL sub_FFFFFFF0170335F8` | NOP   |

### selector24 Full Control Flow (0x026C80 - 0x026E7C)

The selector 24 handler is the entry point for all CodeSignature validation.
Understanding its full control flow is essential context for why the NOP approach
was chosen over earlier redirect-based patches.

```
0x026C80: pacibsp                          ; prologue
0x026C84: sub sp, sp, #0x70
0x026C88-0x026C98: save x19-x30, setup fp

0x026CA0-0x026CB4: save args to x20-x25
0x026CB8: bl #0x29024                      ; get TXM context → x0
0x026CBC: adrp x8, #0x6c000               ; flag address (page)
0x026CC0: add x8, x8, #0x5c0              ; flag address (offset)
0x026CC4: ldrb w8, [x8]                   ; flag check
0x026CC8: cbnz w8, #0x26cfc               ; if flag → error 0xA0
0x026CCC: mov x19, x0                     ; save context

0x026CD4: cmp w25, #2                     ; switch on sub-selector (arg0)
         b.gt → check 3,4,5
0x026CDC: cbz w25 → case 0
0x026CE4: b.eq → case 1
0x026CEC: b.ne → default (0xA1 error)

         case 0: setup, b 0x26dc0
         case 1: flag check, setup, b 0x26dfc
         case 2: bl 0x1e0e8, b 0x26db8
         case 3: bl 0x1e148, b 0x26db8
         case 4: bl 0x1e568, b 0x26db8
         case 5: flag → { mov x0,#0; b 0x26db8 } or { bl 0x1e70c; b 0x26db8 }
         default: mov w0, #0xa1; b 0x26d00 (error path)

0x026DB8: and w8, w0, #0xffff             ; result processing
0x026DBC: and x9, x0, #0xffffffffffff0000
0x026DC0: mov w10, w8
0x026DC4: orr x9, x9, x10
0x026DC8: str x9, [x19, #8]              ; store result to context
0x026DCC: cmp w8, #0
0x026DD0: csetm x0, ne                   ; x0 = 0 (success) or -1 (error)
0x026DD4: bl #0x26c04                    ; return via svc #0 ← SUCCESS RETURN

ERROR PATH:
0x026D00: mov w1, #0
0x026D04: bl #0x25924                    ; error handler → svc #0
```

#### Existing Success Path

The function already has a success path at `0x026D30` (reached by case 5 when flag is set):

```asm
0x026D30: mov    x0, #0         ; success result
0x026D34: b      #0x26db8       ; → process result → str [x19,#8] → bl return_helper
```

> **Historical note:** An earlier approach tried redirecting to this success path by
> patching 2 instructions at `0x26CBC` (`mov x19, x0` / `b #0x26D30`). This was
> replaced with the more surgical NOP approach below because the redirect did not
> properly handle the hash validation return value.

#### Error Codes

| Return Value | Meaning                                   |
| ------------ | ----------------------------------------- |
| `0x00`       | Success (only via case 5 flag path)       |
| `0xA0`       | Early flag check failure                  |
| `0xA1`       | Unknown sub-selector / validation failure |
| `0x130A1`    | Hash mismatch (hash presence != flag)     |
| `0x22DA1`    | Version-dependent validation failure      |

### Function: `sub_FFFFFFF0170353B8` --- CS hash flags validator

**Call chain**: selector 24 → `sub_FFFFFFF017024834` (CS handler) →
`sub_FFFFFFF0170356F8` (CS validation pipeline) → `sub_FFFFFFF017035A00`
(multi-step validation, step 4 of 8) → `sub_FFFFFFF0170353B8`

### Decompiled (pre-patch)

```c
// sub_FFFFFFF0170353B8(manifest_ptr, version)
__int64 __fastcall sub_FFFFFFF0170353B8(__int64 **a1, unsigned int a2)
{
    __int64 v4 = **a1;
    __int64 v7 = 0;    // hash data pointer
    int v6 = 0;        // hash flags

    // Patch 1: NOP removes arg load (LDR X1, [X20, #0x38])
    // Patch 2: NOP removes this call entirely:
    sub_FFFFFFF0170335F8(a1[6], a1[7], &v6);   // extract hash flags from CS blob
    sub_FFFFFFF017033718(a1[6], a1[7], &v7);    // extract hash data pointer

    if ( a2 >= 6 && *(v4 + 8) )
        return 0xA1;                            // 161

    // Critical comparison: does hash presence match flags?
    if ( (v7 != 0) == ((v6 & 2) >> 1) )
        return 0x130A1;                         // 77985 — hash mismatch

    // ... further version-dependent checks return 0xA1 or 0x22DA1
}
```

### What `sub_FFFFFFF0170335F8` does

Extracts hash flags from the CodeSignature blob header. Reads `bswap32(*(blob + 12))`
into the output parameter (the flags bitmask). Bit 1 of the flags indicates whether
a code hash is present.

### What `sub_FFFFFFF017033718` does

Locates the hash data within the CodeSignature blob. Validates blob header version
(`bswap32(*(blob+8)) >> 9 >= 0x101`), then follows a length-prefixed string pointer
at offset 48 to find the hash data. Returns the hash data pointer via output param.

### Effect of NOP

With `sub_FFFFFFF0170335F8` NOPed, `v6` stays at its initialized value of **0**.
This means `(v6 & 2) >> 1 = 0` (hash-present flag is cleared). As long as
`sub_FFFFFFF017033718` returns a non-null hash pointer (`v7 != 0`), the comparison
becomes `(1 == 0)` → **false**, so the `0x130A1` error is skipped. The function
falls through to the version checks which return success for version <= 5.

This effectively bypasses CodeSignature hash validation --- the hash data exists
in the blob but the hash-present flag is suppressed, so the consistency check passes.

### `txm_dev.py` dynamic finder: `patch_selector24_force_pass()`

Scans for `mov w0, #0xa1` as a unique anchor to locate the CS hash validator function,
finds PACIBSP to determine function start, then matches the pattern
`LDR X1,[Xn,#0x38]` / `ADD X2,...` / `BL` / `LDP` within it. NOPs the `LDR X1`
(arg setup) and the `BL hash_flags_extract` (call).

### UUID Canary Verification

To confirm which TXM variant is loaded during boot, XOR the last byte of `LC_UUID`:

|                            | UUID                                   |
| -------------------------- | -------------------------------------- |
| Original research          | `0FFA437D-376F-3F8E-AD26-317E2111655D` |
| Original release           | `3C1E0E65-BFE2-3113-9C65-D25926C742B4` |
| Canary (research XOR 0x01) | `0FFA437D-376F-3F8E-AD26-317E2111655C` |

Panic log `TXM UUID:` line confirmed canary `...655C` → **patched research TXM IS loaded**.
The problem was exclusively in the selector24 patch logic (the earlier redirect approach
did not properly handle the hash validation return value).

---

## Patch 3: get-task-allow Force True (selector 41)

**Error**: `TXM [Error]: selector: 41 | 29`

### Address

| File Offset | VA                   | Original Instruction      | Patch        |
| ----------- | -------------------- | ------------------------- | ------------ |
| `0x1f5d4`   | `0xFFFFFFF0170235D4` | `BL sub_FFFFFFF017022A30` | `MOV X0, #1` |

### Function: `sub_FFFFFFF017023558` --- selector 41 handler

**Call chain**: selector 41 → `sub_FFFFFFF0170280A4` (ptr validation) →
`sub_FFFFFFF017023558`

### Decompiled (pre-patch)

```c
// sub_FFFFFFF017023558(manifest)
__int64 __fastcall sub_FFFFFFF017023558(__int64 a1)
{
    // Check developer mode is enabled (byte_FFFFFFF017070F24)
    if ( (byte_FFFFFFF017070F24 & 1) == 0 )
        return 27;    // developer mode not enabled

    // Check license-to-operate entitlement (always first)
    sub_FFFFFFF017022A30(0, "research.com.apple.license-to-operate", 0);

    // Lock manifest
    sub_FFFFFFF017027074(a1, 0, 0);

    if ( *(a1 + 36) == 1 )     // special manifest type
        goto error_path;        // return via panic(0x81)

    // === PATCHED INSTRUCTION ===
    // Original: BL sub_FFFFFFF017022A30  — entitlement_lookup(manifest, "get-task-allow", 0)
    // Patched:  MOV X0, #1
    if ( (sub_FFFFFFF017022A30(a1, "get-task-allow", 0) & 1) != 0 )  // TBNZ w0, #0
    {
        v3 = 0;                 // success
        *(a1 + 0x30) = 1;      // set get-task-allow flag on manifest
    }
    else
    {
        v3 = 29;               // ERROR 29: no get-task-allow entitlement
    }

    sub_FFFFFFF01702717C(a1, 0);    // unlock manifest
    return v3;
}
```

### Assembly at patch site

```asm
FFFFFFF0170235C4  ADRL  X1, "get-task-allow"
FFFFFFF0170235CC  MOV   X0, X19              ; manifest object
FFFFFFF0170235D0  MOV   X2, #0
FFFFFFF0170235D4  BL    sub_FFFFFFF017022A30  ; <-- PATCHED to MOV X0, #1
FFFFFFF0170235D8  TBNZ  W0, #0, loc_...       ; always taken when x0=1
```

### Effect

Replaces the entitlement lookup call with a constant `1`. The subsequent `TBNZ W0, #0`
always takes the branch to the success path, which sets `*(manifest + 0x30) = 1`
(the get-task-allow flag byte). Every process now has get-task-allow, enabling
debugging via `task_for_pid` and LLDB attach.

### What `sub_FFFFFFF017022A30` does

Universal entitlement lookup function. When `a1 != 0`, it resolves the manifest's
entitlement dictionary and searches for the named key via `sub_FFFFFFF017036294`.
Returns a composite status word where bit 0 indicates the entitlement was found.

### `txm_dev.py` dynamic finder: `patch_get_task_allow_force_true()`

Searches for string refs to `"get-task-allow"`, then scans forward for the pattern
`BL X / TBNZ w0, #0, Y`. Patches the BL to `MOV X0, #1`.

---

## Patch 4: selector 42|29 Shellcode (Debug Mapping Gate)

**Error**: `TXM [Error]: selector: 42 | 29`

### Addresses

| File Offset | VA                   | Patch                      |
| ----------- | -------------------- | -------------------------- |
| `0x2717c`   | `0xFFFFFFF01702B17C` | `B #0x36238` (→ shellcode) |
| `0x5d3b4`   | `0xFFFFFFF0170613B4` | `NOP` (pad)                |
| `0x5d3b8`   | `0xFFFFFFF0170613B8` | `MOV X0, #1`               |
| `0x5d3bc`   | `0xFFFFFFF0170613BC` | `STRB W0, [X20, #0x30]`    |
| `0x5d3c0`   | `0xFFFFFFF0170613C0` | `MOV X0, X20`              |
| `0x5d3c4`   | `0xFFFFFFF0170613C4` | `B #-0x36244` (→ 0xB180)   |

### Context: Dispatcher case 42

```asm
; jumptable case 42 entry in sub_FFFFFFF01702AE80:
FFFFFFF01702B178  BTI   j
FFFFFFF01702B17C  MOV   X0, X20                ; <-- PATCHED to B shellcode
FFFFFFF01702B180  BL    sub_FFFFFFF0170280A4    ; validate pointer
FFFFFFF01702B184  MOV   X1, X21
FFFFFFF01702B188  MOV   X2, X22
FFFFFFF01702B18C  BL    sub_FFFFFFF017023368    ; selector 42 handler
FFFFFFF01702B190  B     loc_FFFFFFF01702B344    ; return result
```

### Shellcode (at zero-filled code cave in `__TEXT_EXEC`)

```asm
; 0xFFFFFFF0170613B4 — cave was all zeros
NOP                           ; pad (original 0x00000000)
MOV   X0, #1                  ; value to store
STRB  W0, [X20, #0x30]        ; force manifest->get_task_allow = 1
MOV   X0, X20                 ; restore original instruction (was at 0xB17C)
B     #-0x36244                ; jump back to 0xFFFFFFF01702B180 (BL validate)
```

### Why this is needed

Selector 42's handler `sub_FFFFFFF017023368` checks the get-task-allow byte early:

```c
// sub_FFFFFFF017023368(manifest, addr, size)
// ... after debugger entitlement check ...
v8 = atomic_load((unsigned __int8 *)(a1 + 48));  // offset 0x30
if ( (v8 & 1) == 0 )
{
    v6 = 29;    // ERROR 29: get-task-allow not set
    goto unlock_and_return;
}
// ... proceed with debug memory mapping ...
```

Selector 41 (patch 3) sets this byte during entitlement validation, but
there are code paths where selector 42 can be called before selector 41 has run
for a given manifest. The shellcode ensures the flag is always set at the dispatch
level before the handler even sees it.

### `sub_FFFFFFF0170280A4` --- pointer validator

```c
// Validates page alignment and bounds, returns input pointer unchanged
unsigned __int64 sub_FFFFFFF0170280A4(unsigned __int64 a1) {
    if ( (a1 & ~0x3FFF) == 0 ) panic(64);
    if ( a1 >= 0xFFFFFFFFFFFFC000 ) panic(66);
    // ... bounds checks ...
    return (a1 & ~0x3FFF) + (a1 & 0x3FFF);  // == a1
}
```

Since the validator returns the pointer unchanged, `x20` (raw arg) and the validated
pointer both refer to the same object. The shellcode's `STRB W0, [X20, #0x30]`
writes to the correct location.

### `txm_dev.py` dynamic finder: `patch_selector42_29_shellcode()`

1. Finds the "debugger gate function" via string refs to `"com.apple.private.cs.debugger"`
2. Locates the dispatch stub by matching `BTI j / MOV X0, X20 / BL / MOV X1, X21 / MOV X2, X22 / BL debugger_gate / B`
3. Finds a zero-filled code cave via `_find_udf_cave()` near the stub
4. Emits the branch + shellcode + branch-back

---

## Patch 5: Debugger Entitlement Force True (selector 42)

**Error**: `TXM [Error]: selector: 42 | 37`

### Address

| File Offset | VA                   | Original Instruction      | Patch        |
| ----------- | -------------------- | ------------------------- | ------------ |
| `0x1f3b8`   | `0xFFFFFFF0170233B8` | `BL sub_FFFFFFF017022A30` | `MOV W0, #1` |

### Function: `sub_FFFFFFF017023368` --- selector 42 handler (debug memory mapping)

### Assembly at patch site

```asm
; Check com.apple.private.cs.debugger entitlement
FFFFFFF0170233A8  ADRL  X1, "com.apple.private.cs.debugger"
FFFFFFF0170233B0  MOV   X0, #0                 ; check global manifest (a1=0)
FFFFFFF0170233B4  MOV   X2, #0
FFFFFFF0170233B8  BL    sub_FFFFFFF017022A30    ; <-- PATCHED to MOV W0, #1
FFFFFFF0170233BC  TBNZ  W0, #0, loc_...         ; always taken when w0=1
FFFFFFF0170233C0  ADRL  X8, fallback_flag       ; secondary check (also bypassed)
FFFFFFF0170233C8  LDRB  W8, [X8, #offset]
FFFFFFF0170233CC  TBNZ  W8, #0, loc_...         ; secondary bypass path
FFFFFFF0170233D0  ADRL  X0, "disallowed non-debugger initiated debug mapping"
FFFFFFF0170233D8  BL    sub_FFFFFFF017025B7C    ; log error
FFFFFFF0170233DC  MOV   W20, #0x25              ; error 37
FFFFFFF0170233E0  B     unlock_return
```

### Decompiled (pre-patch)

```c
// First check in sub_FFFFFFF017023368 after input validation:
if ( (sub_FFFFFFF017022A30(0, "com.apple.private.cs.debugger", 0) & 1) == 0 )
{
    // Fallback: check a static byte flag
    if ( (fallback_flag & 1) == 0 )
    {
        log("disallowed non-debugger initiated debug mapping");
        return 37;   // 0x25
    }
}
// Continue with debug mapping...
```

### Effect

Replaces the entitlement lookup with `MOV W0, #1`. The `TBNZ W0, #0` always
branches to the success path, bypassing both the entitlement check and the
fallback flag check. This allows any process to create debug memory mappings
regardless of whether it has `com.apple.private.cs.debugger`.

### `txm_dev.py` dynamic finder: `patch_debugger_entitlement_force_true()`

Searches for string refs to `"com.apple.private.cs.debugger"`, then matches
the pattern: `mov x0, #0 / mov x2, #0 / bl X / tbnz w0, #0, Y`. Patches the BL
to `MOV W0, #1`.

---

## Patch 6: Developer Mode Bypass

### Address

| File Offset | VA                   | Original Instruction                | Patch |
| ----------- | -------------------- | ----------------------------------- | ----- |
| `0x1FA58`   | `0xFFFFFFF017023A58` | `TBNZ W9, #0, loc_FFFFFFF017023A6C` | NOP   |

### Function: `sub_FFFFFFF017023A20` --- developer mode configuration

Called during TXM initialization to determine and store the developer mode state.
The result is stored in `byte_FFFFFFF017070F24`, which is the gate flag checked by
selector 41 (`sub_FFFFFFF017023558`).

### Assembly at patch site

```asm
; Check system policy configuration
FFFFFFF017023A50  LDR   X9, [X8, #off_FFFFFFF0170146C0]
FFFFFFF017023A54  LDRB  W9, [X9, #0x4D]         ; load system policy byte
FFFFFFF017023A58  TBNZ  W9, #0, loc_FFFFFFF017023A6C  ; <-- PATCHED to NOP
; Fall through to force-enable:
FFFFFFF017023A5C  MOV   W20, #1                  ; developer_mode = ENABLED
FFFFFFF017023A60  ADRL  X0, "developer mode enabled due to system policy configuration"
FFFFFFF017023A68  B     log_and_store
```

### Decompiled (pre-patch)

```c
__int64 sub_FFFFFFF017023A20(__int64 manifest)
{
    char devmode;

    // Check 1: PCC research variant flag
    if ( pcc_research_flag )
    {
        devmode = 1;
        goto apply;
    }

    // Check 2: System policy (patched here)
    byte policy = *(system_config_ptr + 0x4D);
    if ( (policy & 1) != 0 )        // <-- TBNZ jumps past force-enable
        goto normal_path;            //     to xART / user-config checks

    // Force-enable path (reached by NOPing the TBNZ):
    devmode = 1;
    log("developer mode enabled due to system policy configuration");
    goto apply;

normal_path:
    // ... xART availability check ...
    // ... user configuration check ...
    // May set devmode = 0 (disabled) based on config

apply:
    byte_FFFFFFF017070F24 = devmode;  // global developer mode state
    return result;
}
```

### Effect

NOPing the `TBNZ` makes execution always fall through to `MOV W20, #1`, forcing
developer mode enabled regardless of the system policy byte. Without this:

- The `TBNZ` would jump to `loc_FFFFFFF017023A6C` (the normal path)
- The normal path checks xART availability, device tree flags, and user configuration
- On PCC VMs, this can result in developer mode being **disabled**

Developer mode is a **prerequisite** for selectors 41 and 42 --- the selector 41
handler returns error 27 immediately if `byte_FFFFFFF017070F24` is not set:

```c
// In sub_FFFFFFF017023558 (selector 41):
if ( (byte_FFFFFFF017070F24 & 1) == 0 )
    return 27;    // developer mode not enabled
```

### `txm_dev.py` dynamic finder: `patch_developer_mode_bypass()`

Searches for string refs to `"developer mode enabled due to system policy
configuration"`, then scans backwards for a `tbz/tbnz/cbz/cbnz` instruction
matching `w9, #0`. NOPs it.

---

## Patch Dependency Chain

The patches have a logical ordering --- later patches depend on earlier ones:

```
Patch 6: Developer Mode Bypass
  |  Forces byte_FFFFFFF017070F24 = 1
  |
  |---> Patch 3: get-task-allow Force True (selector 41)
  |      Requires developer mode (checks byte_FFFFFFF017070F24)
  |      Forces manifest[0x30] = 1
  |
  |---> Patch 4: selector 42|29 Shellcode
  |      Forces manifest[0x30] = 1 at dispatch level
  |      Safety net for Patch 3 (covers cases where sel 42 runs before sel 41)
  |
  |---> Patch 5: Debugger Entitlement Force True (selector 42)
  |      Bypasses com.apple.private.cs.debugger check
  |      Allows debug memory mapping for all processes
  |
  └---> Patches 1-2: CodeSignature Hash Bypass (selector 24)
         Independent — bypasses CS hash validation in the signature chain
```

### Boot-time flow

1. TXM initializes → `sub_FFFFFFF017023A20` runs → **Patch 6** forces devmode ON
2. Process loads → selector 24 validates CodeSignature → **Patches 1-2** skip hash check
3. Process requests entitlements → selector 41 → **Patch 3** grants get-task-allow
4. Debugger attaches → selector 42 → **Patch 4** pre-sets flag + **Patch 5** grants debugger ent
5. Debug mapping succeeds → LLDB can attach to any process

---

## Summary Table

| #   | File Offset | VA                   | Function                                     | Patch                  | Purpose                           |
| --- | ----------- | -------------------- | -------------------------------------------- | ---------------------- | --------------------------------- |
| 1   | `0x313ec`   | `0xFFFFFFF0170353EC` | `sub_FFFFFFF0170353B8` (CS hash validator)   | NOP                    | Remove hash flag load             |
| 2   | `0x313f4`   | `0xFFFFFFF0170353F4` | `sub_FFFFFFF0170353B8` (CS hash validator)   | NOP                    | Skip hash flag extraction call    |
| 3   | `0x1f5d4`   | `0xFFFFFFF0170235D4` | `sub_FFFFFFF017023558` (selector 41)         | `MOV X0, #1`           | Force get-task-allow = true       |
| 4   | `0x2717c`   | `0xFFFFFFF01702B17C` | `sub_FFFFFFF01702AE80` (dispatcher, case 42) | `B shellcode`          | Redirect to shellcode cave        |
| 4a  | `0x5d3b4`   | `0xFFFFFFF0170613B4` | code cave (zeros)                            | `NOP`                  | Shellcode padding                 |
| 4b  | `0x5d3b8`   | `0xFFFFFFF0170613B8` | code cave                                    | `MOV X0, #1`           | Set value for flag                |
| 4c  | `0x5d3bc`   | `0xFFFFFFF0170613BC` | code cave                                    | `STRB W0, [X20,#0x30]` | Force get-task-allow flag         |
| 4d  | `0x5d3c0`   | `0xFFFFFFF0170613C0` | code cave                                    | `MOV X0, X20`          | Restore original instruction      |
| 4e  | `0x5d3c4`   | `0xFFFFFFF0170613C4` | code cave                                    | `B back`               | Return to dispatcher              |
| 5   | `0x1f3b8`   | `0xFFFFFFF0170233B8` | `sub_FFFFFFF017023368` (selector 42)         | `MOV W0, #1`           | Force debugger entitlement = true |
| 6   | `0x1FA58`   | `0xFFFFFFF017023A58` | `sub_FFFFFFF017023A20` (devmode init)        | NOP                    | Force developer mode ON           |

**Total**: 6 logical patches, 11 instruction modifications (counting shellcode), enabling:

- CodeSignature bypass (patches 1-2)
- Universal get-task-allow (patches 3-4)
- Universal debugger entitlement (patch 5)
- Forced developer mode (patch 6)
