mirror of
https://github.com/Lakr233/vphone-cli.git
synced 2026-09-05 17:14:28 +00:00
fix TXM selector24 bypass: return 0xA1 (PASS) instead of NOP/fail
The original selector24 patches (NOP ldr + NOP bl) broke the hash flags extraction, causing the consistency check to fail. The second attempt (return 0x130A1) also failed because the return code semantics were inverted — byte 1 != 0 means FAIL, not success. Correct approach: insert `mov w0, #0xa1; b <epilogue>` after the prologue. 0xA1 has byte 1 = 0 which the caller checks via `tst w0, #0xff00` as PASS. Update AGENTS.md move selector24 bypass from txm_jb.py to txm_dev.py, delete TXMJBPatcher Selector24 CS validation bypass now applies to both dev and JB variants via txm_dev.py. The separate txm_jb.py patcher is removed since it had no other patches. Dev boot chain: 47→49 patches. Create txm_fullchain_analysis.md
This commit is contained in:
@@ -58,13 +58,14 @@ def _find_asm_pattern(data, asm_str):
|
||||
|
||||
|
||||
class TXMPatcher:
|
||||
"""Dev-only dynamic patcher for TXM images.
|
||||
"""Dev/JB dynamic patcher for TXM images.
|
||||
|
||||
Patches (dev-specific only — base trustcache bypass is in txm.py):
|
||||
1. get-task-allow entitlement check BL → mov x0, #1
|
||||
2. Selector42|29: shellcode hook + manifest flag force
|
||||
3. debugger entitlement check BL → mov w0, #1
|
||||
4. developer-mode guard branch → nop
|
||||
Patches (base trustcache bypass is in txm.py):
|
||||
1. Selector24: force PASS return (mov w0, #0xa1 + b epilogue)
|
||||
2. get-task-allow entitlement check BL → mov x0, #1
|
||||
3. Selector42|29: shellcode hook + manifest flag force
|
||||
4. debugger entitlement check BL → mov w0, #1
|
||||
5. developer-mode guard branch → nop
|
||||
"""
|
||||
|
||||
def __init__(self, data, verbose=True):
|
||||
@@ -105,6 +106,7 @@ class TXMPatcher:
|
||||
|
||||
def find_all(self):
|
||||
self.patches = []
|
||||
self.patch_selector24_force_pass()
|
||||
self.patch_get_task_allow_force_true()
|
||||
self.patch_selector42_29_shellcode()
|
||||
self.patch_debugger_entitlement_force_true()
|
||||
@@ -285,6 +287,90 @@ class TXMPatcher:
|
||||
|
||||
self._log(" [-] TXM: binary search pattern not found in function")
|
||||
|
||||
def patch_selector24_force_pass(self):
|
||||
"""Force selector24 handler to return 0xA1 (PASS) immediately.
|
||||
|
||||
Return code semantics (checked by caller via `tst w0, #0xff00`):
|
||||
- 0xA1 (byte 1 = 0x00) → PASS
|
||||
- 0x130A1 (byte 1 = 0x30) → FAIL
|
||||
- 0x22DA1 (byte 1 = 0x2D) → FAIL
|
||||
|
||||
We insert `mov w0, #0xa1 ; b <epilogue>` right after the prologue,
|
||||
skipping all validation logic while preserving the stack frame for
|
||||
clean register restore via the existing epilogue.
|
||||
"""
|
||||
for off in range(0, self.size - 4, 4):
|
||||
ins = _disasm_one(self.raw, off)
|
||||
if not (ins and ins.mnemonic == "mov" and ins.op_str == "w0, #0xa1"):
|
||||
continue
|
||||
|
||||
func_start = self._find_func_start(off)
|
||||
if func_start is None:
|
||||
continue
|
||||
|
||||
# Verify this is the selector24 handler by checking for the
|
||||
# characteristic pattern: LDR X1,[Xn,#0x38] / ADD X2,... / BL / LDP
|
||||
for scan in range(func_start, off, 4):
|
||||
i0 = _disasm_one(self.raw, scan)
|
||||
i1 = _disasm_one(self.raw, scan + 4)
|
||||
i2 = _disasm_one(self.raw, scan + 8)
|
||||
i3 = _disasm_one(self.raw, scan + 12)
|
||||
if not all((i0, i1, i2, i3)):
|
||||
continue
|
||||
if not (
|
||||
i0.mnemonic == "ldr"
|
||||
and "x1," in i0.op_str
|
||||
and "#0x38]" in i0.op_str
|
||||
):
|
||||
continue
|
||||
if not (i1.mnemonic == "add" and i1.op_str.startswith("x2,")):
|
||||
continue
|
||||
if i2.mnemonic != "bl":
|
||||
continue
|
||||
if i3.mnemonic != "ldp":
|
||||
continue
|
||||
|
||||
# Find prologue end: scan for `add x29, sp, #imm`
|
||||
body_start = None
|
||||
for p in range(func_start + 4, func_start + 0x30, 4):
|
||||
pi = _disasm_one(self.raw, p)
|
||||
if pi and pi.mnemonic == "add" and pi.op_str.startswith("x29, sp,"):
|
||||
body_start = p + 4
|
||||
break
|
||||
if body_start is None:
|
||||
self._log(" [-] TXM: selector24 prologue end not found")
|
||||
return False
|
||||
|
||||
# Find epilogue: scan for retab/ret, walk back to first ldp x29
|
||||
epilogue = None
|
||||
for r in range(off, min(off + 0x200, self.size), 4):
|
||||
ri = _disasm_one(self.raw, r)
|
||||
if ri and ri.mnemonic in ("retab", "ret"):
|
||||
for e in range(r - 4, max(r - 0x20, func_start), -4):
|
||||
ei = _disasm_one(self.raw, e)
|
||||
if ei and ei.mnemonic == "ldp" and "x29, x30" in ei.op_str:
|
||||
epilogue = e
|
||||
break
|
||||
break
|
||||
if epilogue is None:
|
||||
self._log(" [-] TXM: selector24 epilogue not found")
|
||||
return False
|
||||
|
||||
self.emit(
|
||||
body_start,
|
||||
_asm("mov w0, #0xa1"),
|
||||
"selector24 bypass: mov w0, #0xa1 (PASS)",
|
||||
)
|
||||
self.emit(
|
||||
body_start + 4,
|
||||
self._asm_at(f"b #0x{epilogue:x}", body_start + 4),
|
||||
"selector24 bypass: b epilogue",
|
||||
)
|
||||
return True
|
||||
|
||||
self._log(" [-] TXM: selector24 handler not found")
|
||||
return False
|
||||
|
||||
def patch_get_task_allow_force_true(self):
|
||||
"""Force get-task-allow entitlement call to return true."""
|
||||
refs = self._find_string_refs(b"get-task-allow")
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
txm_jb.py — Jailbreak extension patcher for TXM images.
|
||||
|
||||
Reuses shared TXM logic from txm_dev.py and adds the selector24 CodeSignature
|
||||
hash-extraction bypass used only by the JB variant.
|
||||
"""
|
||||
|
||||
from .txm_dev import TXMPatcher as TXMDevPatcher, _asm, _disasm_one
|
||||
|
||||
|
||||
NOP = _asm("nop")
|
||||
|
||||
|
||||
class TXMJBPatcher(TXMDevPatcher):
|
||||
"""JB-only TXM patcher: selector24 CS hash-extraction bypass.
|
||||
|
||||
Dev patches are applied separately by txm_dev.py; this class only
|
||||
adds the JB-exclusive selector24 extension.
|
||||
"""
|
||||
|
||||
def apply(self):
|
||||
self.find_all()
|
||||
for off, pb, _ in self.patches:
|
||||
self.data[off : off + len(pb)] = pb
|
||||
if self.verbose and self.patches:
|
||||
self._log(f"\n [{len(self.patches)} TXM JB patches applied]")
|
||||
return len(self.patches)
|
||||
|
||||
def find_all(self):
|
||||
self.patches = []
|
||||
self.patch_selector24_hash_extraction_nop()
|
||||
return self.patches
|
||||
|
||||
def patch_selector24_hash_extraction_nop(self):
|
||||
"""NOP hash-flags extraction setup/call in selector24 path."""
|
||||
for off in range(0, self.size - 4, 4):
|
||||
ins = _disasm_one(self.raw, off)
|
||||
if not (ins and ins.mnemonic == "mov" and ins.op_str == "w0, #0xa1"):
|
||||
continue
|
||||
|
||||
func_start = self._find_func_start(off)
|
||||
if func_start is None:
|
||||
continue
|
||||
|
||||
# Scan function for: LDR X1,[Xn,#0x38] / ADD X2,... / BL / LDP
|
||||
for scan in range(func_start, off, 4):
|
||||
i0 = _disasm_one(self.raw, scan)
|
||||
i1 = _disasm_one(self.raw, scan + 4)
|
||||
i2 = _disasm_one(self.raw, scan + 8)
|
||||
i3 = _disasm_one(self.raw, scan + 12)
|
||||
if not all((i0, i1, i2, i3)):
|
||||
continue
|
||||
if not (
|
||||
i0.mnemonic == "ldr"
|
||||
and "x1," in i0.op_str
|
||||
and "#0x38]" in i0.op_str
|
||||
):
|
||||
continue
|
||||
if not (i1.mnemonic == "add" and i1.op_str.startswith("x2,")):
|
||||
continue
|
||||
if i2.mnemonic != "bl":
|
||||
continue
|
||||
if i3.mnemonic != "ldp":
|
||||
continue
|
||||
|
||||
self.emit(scan, NOP, "selector24 CS: nop ldr x1,[xN,#0x38]")
|
||||
self.emit(scan + 8, NOP, "selector24 CS: nop bl hash_flags_extract")
|
||||
return True
|
||||
|
||||
self._log(" [-] TXM JB: selector24 hash extraction site not found")
|
||||
return False
|
||||
Reference in New Issue
Block a user