Files
vphone-cli/sources/FirmwarePatcher/Kernel/JBPatches/KernelJBPatchTaskConversion.swift
Lakr 08eb9d260f 🐦 iBSS iBEC LLB TXM
update

update
2026-03-11 04:22:50 +08:00

161 lines
6.5 KiB
Swift

// KernelJBPatchTaskConversion.swift JB kernel patch: Task conversion eval bypass
//
// Python source: scripts/patchers/kernel_jb_patch_task_conversion.py
//
// Strategy (fast raw scanner):
// Locate the unique guard site in _task_conversion_eval_internal that performs:
// ADRP Xn, <global> ; [off - 8] loads global task-conversion table
// LDR Xn, [Xn] ; [off - 4] dereferences it
// CMP Xn, X0 ; [off + 0] compare task pointer against X0
// B.EQ <skip1> ; [off + 4]
// CMP Xn, X1 ; [off + 8] compare against X1
// B.EQ <skip2> ; [off + 12]
// MOV X19, X0 ; [off + 16]
// MOV X0, X1 ; [off + 20]
// BL <callee> ; [off + 24]
// CBZ/CBNZ W0, ... ; [off + 28]
// Patch: replace CMP Xn, X0 with CMP XZR, XZR so the equality check always passes.
import Foundation
extension KernelJBPatcher {
/// Task conversion eval bypass: patch the guard CMP to always be equal.
@discardableResult
func patchTaskConversionEvalInternal() -> Bool {
log("\n[JB] task_conversion_eval_internal: cmp xzr,xzr")
guard let codeRange = codeRanges.first else { return false }
let (ks, ke) = (codeRange.start, codeRange.end)
let candidates = collectTaskConversionCandidates(start: ks, end: ke)
guard candidates.count == 1 else {
log(" [-] expected 1 task-conversion guard site, found \(candidates.count)")
return false
}
let site = candidates[0]
let va = fileOffsetToVA(site)
emit(site, ARM64.cmpXzrXzr,
patchID: "task_conversion_eval",
virtualAddress: va,
description: "cmp xzr,xzr [_task_conversion_eval_internal]")
return true
}
// MARK: - Private scanner
private func collectTaskConversionCandidates(start: Int, end: Int) -> [Int] {
// Derived masks no hardcoded opcode bytes:
// CMP Xn, X0 = SUBS XZR, Xn, X0 bits [31:21]=1110_1011_000, [20:16]=X0=00000,
// [15:10]=000000, [9:5]=Rn, [4:0]=11111(XZR)
// Mask covers the fixed opcode and X0 operand; leaves Rn free.
let cmpXnX0Mask: UInt32 = 0xFFE0_FC1F
let cmpXnX0Val: UInt32 = 0xEB00_001F // cmp Xn, X0 Rn wildcard
// CMP Xn, X1 = SUBS XZR, Xn, X1 Rm=X1=00001
let cmpXnX1Mask: UInt32 = 0xFFE0_FC1F
let cmpXnX1Val: UInt32 = 0xEB01_001F // cmp Xn, X1 Rn wildcard
// B.EQ #offset bits[31:24]=0101_0100, bit[4]=0, bits[3:0]=0000 (EQ cond)
let beqMask: UInt32 = 0xFF00_001F
let beqVal: UInt32 = 0x5400_0000 // b.eq with any imm19
// LDR Xd, [Xn] (unsigned offset, size=3):
// bits [31:22] fixed = 0xF94 (size=11, V=0, opc=01, class=01);
// bits [21:10] = imm12, bits [9:5] = Rn, bits [4:0] = Rt all variable.
let ldrXUnsignedMask: UInt32 = 0xFFC0_0000 // leaves imm12, Rn, Rt free
let ldrXUnsignedVal: UInt32 = 0xF940_0000
// ADRP: bit[31]=1, bits[28:24]=10000
let adrpMask: UInt32 = 0x9F00_0000
let adrpVal: UInt32 = 0x9000_0000
// MOV X19, X0 = ORR X19, XZR, X0
let movX19X0: UInt32 = 0xAA00_03F3
// MOV X0, X1 = ORR X0, XZR, X1
let movX0X1: UInt32 = 0xAA01_03E0
// BL mask
let blMask: UInt32 = 0xFC00_0000
let blVal: UInt32 = 0x9400_0000
// CBZ/CBNZ W (32-bit): bits[31]=0, bits[30:25]=011010 / 011011
let cbzWMask: UInt32 = 0x7F00_0000
let cbzWVal: UInt32 = 0x3400_0000 // CBZ W
let cbnzWVal: UInt32 = 0x3500_0000 // CBNZ W
var out: [Int] = []
var off = start + 8
while off < end - 28 {
defer { off += 4 }
// [off]: CMP Xn, X0
let i0 = buffer.readU32(at: off)
guard (i0 & cmpXnX0Mask) == cmpXnX0Val else { continue }
let cmpRn = (i0 >> 5) & 0x1F // the register being compared
// [off - 4]: LDR Xn, [Xn] (load into cmpRn from cmpRn)
let prev = buffer.readU32(at: off - 4)
guard (prev & ldrXUnsignedMask) == ldrXUnsignedVal else { continue }
let pRt = prev & 0x1F
let pRn = (prev >> 5) & 0x1F
guard pRt == cmpRn, pRn == cmpRn else { continue }
// [off + 4]: B.EQ
let i1 = buffer.readU32(at: off + 4)
guard (i1 & beqMask) == beqVal else { continue }
// [off + 8]: CMP Xn, X1 (same register)
let i2 = buffer.readU32(at: off + 8)
guard (i2 & cmpXnX1Mask) == cmpXnX1Val else { continue }
guard ((i2 >> 5) & 0x1F) == cmpRn else { continue }
// [off + 12]: B.EQ
let i3 = buffer.readU32(at: off + 12)
guard (i3 & beqMask) == beqVal else { continue }
// Context safety: ADRP at [off - 8] for same register
let p2 = buffer.readU32(at: off - 8)
guard (p2 & adrpMask) == adrpVal else { continue }
guard (p2 & 0x1F) == cmpRn else { continue }
// [off + 16]: MOV X19, X0
guard buffer.readU32(at: off + 16) == movX19X0 else { continue }
// [off + 20]: MOV X0, X1
guard buffer.readU32(at: off + 20) == movX0X1 else { continue }
// [off + 24]: BL
let i6 = buffer.readU32(at: off + 24)
guard (i6 & blMask) == blVal else { continue }
// [off + 28]: CBZ or CBNZ W0
let i7 = buffer.readU32(at: off + 28)
let op7 = i7 & cbzWMask
guard op7 == cbzWVal || op7 == cbnzWVal else { continue }
guard (i7 & 0x1F) == 0 else { continue } // must be W0
// B.EQ targets must be forward and nearby (within same function)
guard let t1 = decodeBEQTarget(insn: i1, at: off + 4) else { continue }
guard let t2 = decodeBEQTarget(insn: i3, at: off + 12) else { continue }
guard t1 > off, t2 > off else { continue }
guard (t1 - off) <= 0x200, (t2 - off) <= 0x200 else { continue }
out.append(off)
}
return out
}
/// Decode a B.cond target offset from an instruction at `pc`.
private func decodeBEQTarget(insn: UInt32, at pc: Int) -> Int? {
// B.cond: bits[31:24] = 0x54, bits[23:5] = imm19, bits[4] = 0, bits[3:0] = cond
guard (insn & 0xFF00_001E) == 0x5400_0000 else { return nil }
let imm19 = (insn >> 5) & 0x7FFFF
// Sign-extend 19 bits
let signedImm = Int32(bitPattern: imm19 << 13) >> 13
return pc + Int(signedImm) * 4
}
}