mirror of
https://github.com/Lakr233/vphone-cli.git
synced 2026-10-02 08:04:32 +08:00
Patches were scattered across the patchers that applied them: nothing listed what a run would do, and nothing could turn one off. This adds the manifest layer that names them and the preset layer that chooses. VPhonePatchKit is a distribution framework (library evolution on) holding the model: VPhoneVersion, VPhoneVersionRequirement, VPhonePatchDeclaration, VPhonePatchSetManifest, VPhonePatchPreset, VPhonePatchPlan and the gate a patcher consults before each write. Capstone and the ARM64 encoder moved in behind an internal import, so nothing downstream sees the package. Ten bundled sets in FirmwarePatcher/PatchSets declare every existing patch, checked against the patchers by FirmwarePatchSetCatalogTests. Two presets ship in VPhone.bundle: standard, and extended for the experimental sets. A version-pinned patch is present in the manifest but off unless a preset or a per-VM checkmark asks for it, and neither can widen its version gate. Patch sets also load from outside the tool. A .vphonepatchset is a macOS loadable bundle whose Contents/Resources/Manifest.plist is read before any of its code is mapped, and whose executable exports one symbol, vphone_patch_set_principal, returning a VPhonePatchSetPrincipal that hands the pipeline one BufferedPatcher per component the plan enabled. Not NSPrincipalClass, which is how a loadable bundle normally names its entry point: library evolution makes VPhonePatchSetPrincipal a resilient superclass, so a subclass of it needs runtime metadata initialization and is not registered with the ObjC runtime when the image is mapped. NSClassFromString cannot find it, and Bundle.principalClass then silently returns whichever class was registered — the example set's patcher rather than its principal. A @_cdecl symbol found with dlsym has none of that. External sets are boot-chain only, because root cfw install loads no external set; a preset that names one lives in ~/.vphone/patches_presets and cannot shadow a shipped identifier. `vphone-cli patchset import` copies a set into ~/.vphone/patchsets and ad hoc signs it if it arrived unsigned, so a bundle straight out of Xcode loads: the linker signs its Mach-O but seals no resources, which codesign rejects until one pass over the bundle fixes it. The signature is re-checked from disk at every load, and PatchSetLoaderTests proves that over the example set — inspect, validate, tamper, load, patch, gate off. BufferedPatcher replaces the nine concrete downcasts the pipeline used to read patched bytes back with, which is what lets an out-of-tree patcher return any. Launchpad gains a patch table per machine, `vphone-cli fw set-patches` writes the selection, and Skills/authoring-patch-sets documents the whole flow. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
126 lines
5.7 KiB
Swift
126 lines
5.7 KiB
Swift
// KernelJailbreakPatcher.swift — JB kernel patcher orchestrator.
|
|
//
|
|
// Historical note: derived from the legacy Python firmware patcher during the Swift migration.
|
|
|
|
import Foundation
|
|
import VPhonePatchKit
|
|
|
|
/// JB kernel patcher across 3 groups. Variant- and feature-gated methods can
|
|
/// change the emitted record count; iOS-27-only patches are gated by `applyIOS27`
|
|
/// and Frida Stalker relaxations by `applyFrida`, which the pipeline sets from the
|
|
/// plan — the `com.vphone.patchset.kernel.frida` patches, off in `standard`.
|
|
///
|
|
/// Group A: Core gate-bypass methods
|
|
/// Group B: Pattern/string anchored methods
|
|
/// Group C: Shellcode/trampoline heavy methods
|
|
public final class KernelJailbreakPatcher: KernelJailbreakPatcherBase, BufferedPatcher {
|
|
public let component = "kernelcache_jb"
|
|
|
|
/// Gates the iOS-27-only kernel patches. These target an iOS-27 userland running
|
|
/// on the 26.4 kernel; on a 26.x base they are unnecessary and some are actively
|
|
/// harmful (e.g. the IOMFB SwapEnd size gate would reject 26.x's native 0x588 swap
|
|
/// struct → dead display, the 26.5 regression). The pipeline sets this from the
|
|
/// iPhone base ProductVersion (false for 18.x/26.x → byte-identical to pre-branch);
|
|
/// standalone patch-component defaults it true so the dev tool exercises the full
|
|
/// set (override with --target-os).
|
|
public var applyIOS27 = false
|
|
|
|
/// Opt-in Frida Stalker kernel relaxations. Set from the plan: `standard` blocks
|
|
/// both, `extended` and a per-VM checkmark turn them on, and their own
|
|
/// cloudOS 26.4+ gate then decides whether they land. Baseline JB/EXP firmware is
|
|
/// byte-identical when false.
|
|
public var applyFrida = false
|
|
|
|
public func findAll() throws -> [PatchRecord] {
|
|
parseMachO()
|
|
buildADRPIndex()
|
|
buildBLIndex()
|
|
buildSymbolTable()
|
|
findPanic()
|
|
|
|
// Group A
|
|
patchAmfiCdhashInTrustcache()
|
|
patchTaskConversionEvalInternal()
|
|
patchSandboxHooksExtended()
|
|
patchIoucFailedMacf()
|
|
|
|
// iOS-27-only (gated — a 26.x base skips these entirely). Both target a 27
|
|
// userland on the 26.4 kernel:
|
|
// - IOUC sandbox gate bypass: the IOKit user-client open path's Sandbox gate
|
|
// (separate from the MACF gate above) spuriously denies backboardd its
|
|
// IOMFB/IOSurface/HID user clients → no present + nil main display →
|
|
// SpringBoard crash-loop. Mirrors the MACF gate.
|
|
// - DiskImages2 DDI ABI (kernel driver v9 vs iOS-27 controller/daemon v11) +
|
|
// RegisterNotificationPort off-by-one, so the personalized DDI attaches
|
|
// (/System/Developer auto-mount). Pairs with the sandbox ops[124] allow
|
|
// and the diskimagesiod isMountComplete→YES userland patch (cfw_install).
|
|
if applyIOS27 {
|
|
patchIoucFailedSandbox()
|
|
patchDiskImages2ClientAbi()
|
|
}
|
|
|
|
// Group B
|
|
patchPostValidationAdditional()
|
|
patchProcSecurityPolicy()
|
|
patchProcPidinfo()
|
|
patchConvertPortToMap()
|
|
patchBsdInitAuth()
|
|
patchDounmount()
|
|
patchIoSecureBsdRoot()
|
|
patchLoadDylinker()
|
|
patchMacMount()
|
|
patchNvramVerifyPermission()
|
|
patchSharedRegionMap()
|
|
patchSpawnValidatePersona()
|
|
patchTaskForPid()
|
|
patchThidShouldCrash()
|
|
patchVmFaultEnterPrepare()
|
|
patchVmMapProtect()
|
|
|
|
// Opt-in Frida Stalker support, from the kernel.frida patch set:
|
|
// existing-thread follow (thread_set_state) + repeated VM_PROT_COPY
|
|
// overwrite (vm_map_delete).
|
|
if applyFrida {
|
|
patchThreadSetStateEntitlementFlag()
|
|
patchVmMapDeleteImmutableCode()
|
|
}
|
|
|
|
// Group C
|
|
patchCredLabelUpdateExecve()
|
|
patchHookCredLabelUpdateExecve()
|
|
patchKcall10()
|
|
patchSyscallmaskApplyToProc()
|
|
|
|
// iOS-27-only (gated — a 26.x base skips these entirely). All target a 27
|
|
// userland on the 26.4 kernel and are unnecessary or actively harmful on 26.x:
|
|
// - exec ip_mac_return SECURITY_POLICY kill bypass: AMFI's exec hooks reject a
|
|
// userland newer than the kernel (27 binaries' validation category) →
|
|
// ip_mac_return != 0 → core daemons die at exec → boot deadlock. 26.x
|
|
// binaries pass (ip_mac_return == 0), so it is not needed there.
|
|
// - container-manager exec-upcall force-success: iOS 27 deleted the kernel-side
|
|
// containermanagerd upcall, so on the 26.4 kernel it fails for every 27 app →
|
|
// autoboxed into temporary-sandbox → Campo/intelligencetasksd/feedbackd
|
|
// crash-loop. On 26.x the upcall succeeds, so it is not needed.
|
|
// - IOMFB SwapEnd size gates: 27's force-kern present (cfw_patch_iomfb_force_kern)
|
|
// sends a 0x6e0 SwapEnd struct; the 26.4 userclient exact-checks 0x588 in two
|
|
// places, so both gates are relaxed to accept 0x6e0. HARMFUL on 26.x — it
|
|
// sends the native 0x588, which the retargeted handler gate would then reject
|
|
// → every framebuffer swap fails → dead display (the 26.5 regression).
|
|
if applyIOS27 {
|
|
patchExecSecurityPolicyKill()
|
|
patchContainerManagerUpcall()
|
|
patchIomfbSwapEndVariableSize() // dispatch checkStructureInputSize → variable
|
|
patchIomfbSwapEndHandlerSize() // handler cmp w2,#0x588 → 0x6e0
|
|
patchFpfsScopedVnodeOpen() // ops[267] → FileProvider-scoped trampoline (fpfs respring fix)
|
|
}
|
|
|
|
return patches
|
|
}
|
|
|
|
public func apply() throws -> Int {
|
|
// `emit()` already wrote every record through to `buffer.data`.
|
|
let records = try (patches.isEmpty ? findAll() : patches)
|
|
return records.count
|
|
}
|
|
}
|