Cinder27 — iOS 17.0-27.1 vulnerability verification lab: 51 modules, 31 per-CVE POC folders, 11 no-CVE module folders, harness app source

This commit is contained in:
jbjf6666 committed 2026-10-29 02:11:12 +08:00
commit 1ea76ab005
89 files changed
+16485

No files matched your search

+275
View File
@@ -0,0 +1,275 @@
import SwiftUI
// MARK: - Chain
struct ChainStage: Identifiable {
let id: String
let index: Int
let title: String
let goal: String
let moduleIDs: [String]
let unlock: String
}
enum ChainPlan {
static let stages: [ChainStage] = [
ChainStage(id: "s0", index: 0,
title: tl("权限面测绘"),
goal: tl("确定该构建上的实际起点:进程持有哪些 entitlement、沙盒放行到哪一层、mach 命名空间如何组织。后续每一步的收益都要以该基线折算。"),
moduleIDs: ["probe.privset", "kern.baseline"],
unlock: tl("缺少该基线,无法区分「漏洞所得」与「签名所授」。")),
ChainStage(id: "s1", index: 1,
title: tl("信息泄露"),
goal: tl("先取得地址与内容。decmpfs 欠产出将内核页缓冲尾部交回用户态;TIFF 截断条带与 EXIF 短读将进程堆残留交回图像与元数据;CoreMedia bytestream 与 sandbox_extension 越过沙盒边界取回文件内容。"),
moduleIDs: ["kern.decmpfs", "iio.tiff.trunc", "iio.jpeg.comment",
"cm.fdleak", "sbx.toctou"],
unlock: tl("在取得稳定泄露之后,才有条件讨论将溢出写成什么值;否则只能触发崩溃。")),
ChainStage(id: "s2", index: 2,
title: tl("溢出阈值标定"),
goal: tl("Type-5 阴影的写入是值可控的,但短溢出会被相邻 VLA 吸收。逐级抬升 R,确定该构建上栈布局开始失效的维度。"),
moduleIDs: ["cg.shading.sweep"],
unlock: tl("该阈值决定后续所有负载的长度预算。")),
ChainStage(id: "s3", index: 3,
title: tl("值可控写"),
goal: tl("在标定所得长度上,以 PDF 中选定的 double 位模式决定被覆盖的指针。崩溃地址即证据 —— 它证明写入内容由攻击者控制,而非仅证明发生了崩溃。"),
moduleIDs: ["cg.shading"],
unlock: tl("值可控写与地址泄露相加,构成构造可控状态的全部要素;所缺的是将写转为执行的环节。")),
ChainStage(id: "s4", index: 4,
title: tl("内核面"),
goal: tl("用户态可触及的内核入口数量有限。ipc_port 请求表索引竞态无 entitlement 门槛;decmpfs 已在泄露层验证。本层度量的是「能否将用户态的错误转为内核侧的状态变化」。"),
moduleIDs: ["kern.ipc"],
unlock: tl("需达到可复现阈值;低于该阈值时,越狱路径不成立。")),
ChainStage(id: "s5", index: 5,
title: tl("解析器面"),
goal: tl("libarchive 的 2 字节堆越界与 CoreMedia 的 28 字节结构体外写,是整套中仅有的两个「写穿过对象边界」的形状。前者精确但浅,后者深且落在保存寄存器区。同一 CVE 下的 CAB 站点与 GPT 分区名越界读一并标定。"),
moduleIDs: ["arc.iso.stress", "arc.cab", "cm.rpu.sweep", "kext.gpt"],
unlock: tl("两者都需要稳定的堆布局控制,而这正是上一阶段泄露所提供的。")),
ChainStage(id: "s6", index: 6,
title: tl("工具链落地"),
goal: tl("将上述产物转为可用形式:IPA 改包、二进制注入、字节级替换、属性改写、数据库读取。本层不依赖任何漏洞,但它是「漏洞能力」最终交付到手上的形态。"),
moduleIDs: ["tool.ipa", "tool.zip", "tool.hex", "tool.macho", "tool.paths"],
unlock: tl("自检全绿意味着工具链本身可信 —— 此后出现的问题是选择问题,不是工具问题。")),
]
static func modules(for stage: ChainStage) -> [ExploitModule] {
stage.moduleIDs.compactMap { Catalog.byID[$0] }
}
}
struct ChainView: View {
@EnvironmentObject private var engine: LabEngine
var body: some View {
NavigationStack {
ScrollView {
VStack(spacing: 14) {
ForEach(ChainPlan.stages) { stage in
stageCard(stage)
}
}
.padding(16)
}
.background(T.bg.ignoresSafeArea())
.navigationTitle(t("chain.title"))
.navigationBarTitleDisplayMode(.inline)
}
}
private func stageCard(_ stage: ChainStage) -> some View {
Card {
VStack(alignment: .leading, spacing: 10) {
HStack(spacing: 8) {
Text("\(stage.index)")
.font(.system(size: 11, weight: .bold, design: .monospaced))
.foregroundColor(T.ember)
.frame(width: 20, height: 20)
.background(Circle().fill(T.ember.opacity(0.16)))
Text(stage.title)
.font(.system(size: 15, weight: .semibold, design: .rounded))
.foregroundColor(.white)
Spacer()
}
Text(stage.goal)
.font(T.body)
.foregroundColor(.white.opacity(0.66))
.fixedSize(horizontal: false, vertical: true)
ForEach(ChainPlan.modules(for: stage)) { module in
HStack(spacing: 8) {
Circle()
.fill(engine.journal.verdict(for: module.id).tint)
.frame(width: 5, height: 5)
Text(module.title).font(T.monoSm).foregroundColor(.white.opacity(0.55))
.lineLimit(1)
Spacer()
Text(engine.journal.verdict(for: module.id).label)
.font(T.monoSm)
.foregroundColor(engine.journal.verdict(for: module.id).tint)
}
}
Text("→ " + stage.unlock)
.font(T.tiny)
.foregroundColor(.white.opacity(0.34))
.fixedSize(horizontal: false, vertical: true)
RunButton(title: t("chain.stage.run"), systemImage: "play.fill", tint: T.ember) {
engine.runSequence(ChainPlan.modules(for: stage), label: stage.title)
}
}
}
}
}
// MARK: - File preview
struct FilePreviewView: View {
let item: FileItem
@EnvironmentObject private var browser: FileBrowser
@Environment(\.dismiss) private var dismiss
@State private var mode = 0
@State private var text: String?
@State private var hex: String?
@State private var info: [(String, String)] = []
@State private var editing = false
@State private var draft = ""
@State private var status = ""
var body: some View {
NavigationStack {
VStack(spacing: 0) {
Picker("", selection: $mode) {
Text(t("files.info")).tag(0)
Text(t("files.text")).tag(1)
Text(t("files.hex")).tag(2)
Text(t("files.package")).tag(3)
}
.pickerStyle(.segmented)
.padding(.horizontal, 16)
.padding(.vertical, 10)
if !status.isEmpty {
Text(status).font(T.monoSm)
.foregroundColor(status.hasPrefix("✓") ? T.good : T.warn)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 16).padding(.bottom, 6)
}
ScrollView {
VStack(alignment: .leading, spacing: 12) {
switch mode {
case 0:
Card {
VStack(alignment: .leading, spacing: 2) {
ForEach(Array(info.enumerated()), id: \.offset) { _, row in
KeyValueRow(key: row.0, value: row.1)
}
}
}
case 1:
if editing {
Card {
TextEditor(text: $draft)
.font(.system(size: 11, design: .monospaced))
.frame(minHeight: 320)
.scrollContentBackground(.hidden)
.background(T.panelHi)
.cornerRadius(8)
}
} else {
Card {
Text(text ?? "—")
.font(T.monoSm)
.foregroundColor(.white.opacity(0.7))
.textSelection(.enabled)
.frame(maxWidth: .infinity, alignment: .leading)
}
}
case 2:
Card {
Text(hex ?? "—")
.font(.system(size: 9.5, design: .monospaced))
.foregroundColor(.white.opacity(0.7))
.textSelection(.enabled)
.frame(maxWidth: .infinity, alignment: .leading)
}
default:
Card {
let entries = PackageTool.list(item.path)
VStack(alignment: .leading, spacing: 3) {
if entries.isEmpty {
Text("—").font(T.monoSm).foregroundColor(.white.opacity(0.4))
} else {
ForEach(entries.prefix(300)) { e in
Text("\(e.name) \(e.size) B")
.font(T.monoSm)
.foregroundColor(.white.opacity(0.6))
.lineLimit(1)
}
}
}
.frame(maxWidth: .infinity, alignment: .leading)
}
}
}
.padding(16)
}
}
.background(T.bg.ignoresSafeArea())
.navigationTitle(item.name)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .topBarLeading) {
if mode == 1 && browser.writable {
Button(editing ? t("files.cancel") : t("files.save")) {
if editing { save() } else { editing = true; draft = text ?? "" }
}.tint(T.ember)
}
}
ToolbarItem(placement: .topBarTrailing) {
Button(t("module.close")) { dismiss() }.tint(T.ember)
}
}
.onAppear(perform: load)
}
}
private func load() {
info = browser.fileInfo(item)
switch FileLoader.kind(of: item.path) {
case .plist: text = FileLoader.plist(at: item.path) ?? FileLoader.text(at: item.path)
default: text = FileLoader.text(at: item.path)
}
hex = FileLoader.hexDump(at: item.path)
}
private func save() {
// Validate a plist before writing so a typo cannot break a preference file.
if item.name.hasSuffix(".plist") {
guard let data = draft.data(using: .utf8),
(try? PropertyListSerialization.propertyList(from: data, format: nil)) != nil else {
status = tl("plist 语法错误,未写入")
return
}
}
let backup = item.path + ".cinder27.bak"
if !FileManager.default.fileExists(atPath: backup) {
try? FileManager.default.copyItem(atPath: item.path, toPath: backup)
}
do {
try Container.write(Data(draft.utf8), to: item.path)
status = "✓ \(t("files.save")) · \(t("files.backup"))"
text = draft
editing = false
browser.reload()
} catch {
status = Container.describe(errno)
}
}
}