mirror of
https://github.com/Lakr233/vphone-cli.git
synced 2026-09-06 01:34:27 +00:00
- Update firmware variant table (CLAUDE.md, AGENTS.md): correct patch totals to 51/64/126, add VPhoneMenuBattery.swift, setup_venv_linux.sh, tail_jb_patch_logs.sh to architecture tree - kernel_patcher_verification.md: 25→26 patches (patch_apfs_get_dev_by_role) - kernel_patch_sandbox.py: docstring 16-25→17-26 - kernel_patch_sandbox_hooks: consolidate to single 17-26 validation file - txm_jb_patches.md: fix txm_jb.py references → txm_dev.py - 0_binary_patch_comparison.md: split kernel counts (28 base + 59 JB methods) - kernel_jb_patch_notes.md: add 2026-03-06 retarget notes for bsd_init_auth, io_secure_bsd_root, vm_fault_enter_prepare - Remove 7 boot investigation process notes (boot_*.md)
712 lines
27 KiB
Markdown
712 lines
27 KiB
Markdown
# 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)
|