patcher: Adapt AVPBooter and IBoot to match macOS 27 beta 3+

* fix AVPBooter DGST patch: restrict search to movz/movk instructions

The DGST constant search matched any instruction string containing
"0x4447", including branch targets like `bl #0x4447c`. On macOS 27
beta 3+ firmware, such a branch appeared before the real DGST movk,
causing a false match and subsequent "x0 setter not found" failure.

Restrict the search to movz/movk/mov mnemonics only, which are the
actual constant-loading instructions.

Fixes #373

* fix iBSS JB nonce patch: use tolerance-based ADRP+ADD search

findRefsToOffset required exact match between ADRP+ADD computed address
and the "boot-nonce" string file offset. On macOS 27 beta 3 firmware,
code references a structure header 2 bytes before the string, causing
the exact match to fail.

Add findRefsNear() which allows up to 16 bytes of negative delta,
matching references to the containing structure rather than the string
itself. This makes the patch resilient to minor layout changes across
firmware versions.
This commit is contained in:
Kila2
2026-07-26 09:42:12 +03:00
committed by GitHub
parent 31772b3818
commit f92d82cd75
2 changed files with 58 additions and 3 deletions
@@ -74,9 +74,13 @@ public final class AVPBooterPatcher: Patcher {
throw PatcherError.invalidFormat("AVPBooter: disassembly produced no instructions")
}
// Step 1 locate the first instruction that references the DGST constant.
// Step 1 locate the first movz/movk instruction that loads the DGST constant.
// Only match immediate-loading mnemonics to avoid false positives from branch
// targets whose address happens to contain "4447" (e.g. `bl #0x4447c`).
guard let hitIdx = insns.firstIndex(where: { insn in
"\(insn.mnemonic) \(insn.operandString)".contains(Self.dgstSearch)
let mn = insn.mnemonic
guard mn == "movz" || mn == "movk" || mn == "mov" else { return false }
return insn.operandString.contains(Self.dgstSearch)
}) else {
throw PatcherError.patchSiteNotFound("AVPBooter DGST: constant 0x4447 not found in binary")
}
@@ -32,9 +32,11 @@ public final class IBootJBPatcher: IBootPatcher {
}
// Collect all ADRP+ADD sites that reference any "boot-nonce" occurrence.
// Allow a small negative delta (up to 16 bytes before the string) because
// code may reference the containing structure rather than the string itself.
var addOffsets: [Int] = []
for strOff in stringOffsets {
let refs = findRefsToOffset(strOff)
let refs = findRefsNear(strOff, tolerance: 16)
for (_, addOff) in refs {
addOffsets.append(addOff)
}
@@ -136,6 +138,55 @@ public final class IBootJBPatcher: IBootPatcher {
// MARK: - Reference Search Helpers
/// Find all ADRP+ADD pairs that point within `tolerance` bytes before `targetOff`.
///
/// This handles the common case where code references a structure header
/// a few bytes before the actual string (e.g. a length-prefixed property).
private func findRefsNear(_ targetOff: Int, tolerance: Int) -> [(adrpOff: Int, addOff: Int)] {
let data = buffer.data
let size = buffer.count
var refs: [(Int, Int)] = []
var off = 0
while off + 8 <= size {
guard
let a = disasm.disassembleOne(in: data, at: off),
let b = disasm.disassembleOne(in: data, at: off + 4)
else {
off += 4
continue
}
guard
a.mnemonic == "adrp",
b.mnemonic == "add",
let detA = a.aarch64,
let detB = b.aarch64,
detA.operands.count >= 2,
detB.operands.count >= 3,
detA.operands[0].reg.rawValue == detB.operands[1].reg.rawValue,
detA.operands[1].type == AARCH64_OP_IMM,
detB.operands[2].type == AARCH64_OP_IMM
else {
off += 4
continue
}
let pageAddr = detA.operands[1].imm
let addImm = detB.operands[2].imm
let computed = Int(pageAddr + addImm)
let delta = targetOff - computed
if delta >= 0, delta <= tolerance {
refs.append((off, off + 4))
}
off += 4
}
return refs
}
/// Find all ADRP+ADD pairs in the binary that point to `targetOff`.
///
/// Scans the entire buffer in 4-byte steps, checking consecutive instruction