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

+579
View File
@@ -0,0 +1,579 @@
import SwiftUI
import UniformTypeIdentifiers
import CoreText
// MARK: - IPA surgery
struct IPAPane: View {
@State private var source: URL?
@State private var picking = false
@State private var bundleID = ""
@State private var displayName = ""
@State private var version = ""
@State private var buildVersion = ""
@State private var minOS = ""
@State private var dylib = ""
@State private var patchFind = ""
@State private var patchReplace = ""
@State private var stripSig = true
@State private var busy = false
@State private var log: [String] = []
@State private var output: URL?
private var workdir: URL {
let base = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
?? URL(fileURLWithPath: NSTemporaryDirectory())
let dir = base.appendingPathComponent("Cinder27/IPA", isDirectory: true)
try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
return dir
}
var body: some View {
ScrollView {
VStack(spacing: 14) {
Card {
VStack(alignment: .leading, spacing: 10) {
SectionLabel(text: tl("源 IPA"))
Button { picking = true } label: {
HStack(spacing: 8) {
Image(systemName: "shippingbox.fill").font(.system(size: 12))
Text(source?.lastPathComponent ?? tl("选择一个 .ipa")).font(T.body)
Spacer()
Image(systemName: "chevron.right").font(.system(size: 10, weight: .bold))
}
.foregroundColor(source == nil ? .white.opacity(0.4) : T.ember)
.padding(10)
.background(RoundedRectangle(cornerRadius: 9).fill(T.panelHi))
}
Text(tl("工具会解包、改 Info.plist、往主二进制里插 LC_LOAD_DYLIB、做字节级字符串替换、去掉 LC_CODE_SIGNATURE,然后重新打成 zip。产出仍然是未签名的 —— 你用同一套证书签它。"))
.font(T.tiny).foregroundColor(.white.opacity(0.35))
.fixedSize(horizontal: false, vertical: true)
}
}
Card {
VStack(alignment: .leading, spacing: 8) {
SectionLabel(text: "Info.plist")
field("Bundle ID", $bundleID, "com.example.app")
field(tl("显示名称"), $displayName, tl("新名字"))
field(tl("版本"), $version, "2.0")
field(tl("构建号"), $buildVersion, "42")
field(tl("最低系统"), $minOS, "17.0")
}
}
Card {
VStack(alignment: .leading, spacing: 8) {
SectionLabel(text: tl("二进制"))
field(tl("注入 dylib 路径"), $dylib, "@executable_path/Frameworks/Hook.dylib")
field(tl("搜索字符串"), $patchFind, "com.old.bundleid")
field(tl("替换为"), $patchReplace, "com.new.bundleid")
Toggle(isOn: $stripSig) {
Text(tl("移除 LC_CODE_SIGNATURE")).font(T.body).foregroundColor(.white.opacity(0.75))
}.tint(T.ember)
Text(tl("替换串不能比原串长 —— 这是原地改写,不改文件布局,所以不会破坏任何偏移。注入 LC_LOAD_DYLIB 需要头部有空隙,没有就会明确报出来。"))
.font(T.tiny).foregroundColor(.white.opacity(0.32))
.fixedSize(horizontal: false, vertical: true)
}
}
RunButton(title: busy ? tl("处理中…") : tl("生成改机 IPA"),
systemImage: "wand.and.stars", tint: T.ember, disabled: busy || source == nil) {
run()
}
if !log.isEmpty {
Card {
VStack(alignment: .leading, spacing: 4) {
SectionLabel(text: tl("过程"))
ForEach(Array(log.enumerated()), id: \.offset) { _, line in
Text(line).font(T.monoSm).foregroundColor(.white.opacity(0.6))
.fixedSize(horizontal: false, vertical: true)
}
}
}
}
if let output = output {
Card {
VStack(alignment: .leading, spacing: 6) {
SectionLabel(text: tl("产出"))
Text(output.lastPathComponent).font(T.mono).foregroundColor(T.good)
Text(output.path).font(T.monoSm).foregroundColor(.white.opacity(0.35))
.textSelection(.enabled)
if let size = (try? FileManager.default.attributesOfItem(atPath: output.path))?[.size] as? NSNumber {
Text("\(size.int64Value) B").font(T.monoSm).foregroundColor(.white.opacity(0.3))
}
}
}
}
}
.padding(16)
}
.sheet(isPresented: $picking) {
FilePickerSheet(isPresented: $picking) { url in
source = url
log = [tl("已选择 ") + "\(url.lastPathComponent)"]
}
}
}
private func field(_ label: String, _ binding: Binding<String>, _ placeholder: String) -> some View {
HStack(spacing: 10) {
Text(label).font(T.body).foregroundColor(.white.opacity(0.45)).frame(width: 96, alignment: .leading)
TextField(placeholder, text: binding)
.font(T.mono)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
.padding(8)
.background(RoundedRectangle(cornerRadius: 8).fill(T.panelHi))
}
}
private func run() {
guard let source = source else { return }
busy = true
log = []
output = nil
var recipe = IPARecipe()
recipe.bundleID = bundleID.isEmpty ? nil : bundleID
recipe.displayName = displayName.isEmpty ? nil : displayName
recipe.version = version.isEmpty ? nil : version
recipe.buildVersion = buildVersion.isEmpty ? nil : buildVersion
recipe.minOS = minOS.isEmpty ? nil : minOS
recipe.injectedDylib = dylib.isEmpty ? nil : dylib
recipe.patchedString = patchFind.isEmpty ? nil : patchFind
recipe.patchedReplacement = patchReplace.isEmpty ? nil : patchReplace
recipe.stripSignature = stripSig
let dir = workdir
DispatchQueue.global(qos: .userInitiated).async {
var lines: [String] = []
var result: URL?
do {
let r = try IPASurgeon.patch(source: source, recipe: recipe, workdir: dir)
lines = r.notes
lines.append(tl("产出 ") + "\(r.entries)" + tl(" 个文件"))
result = r.outputURL
} catch {
lines.append(tl("失败: ") + "\(error.localizedDescription)")
}
DispatchQueue.main.async {
log = lines
output = result
busy = false
}
}
}
}
// MARK: - Hex editor
struct HexPane: View {
@State private var target: URL?
@State private var picking = false
@State private var dump = ""
@State private var find = ""
@State private var replaceWith = ""
@State private var hits: [Int] = []
@State private var status = ""
@State private var offsetLimit = 8192
var body: some View {
ScrollView {
VStack(spacing: 14) {
Card {
VStack(alignment: .leading, spacing: 10) {
SectionLabel(text: tl("目标"))
Button { picking = true } label: {
HStack(spacing: 8) {
Image(systemName: "doc.text.magnifyingglass").font(.system(size: 12))
Text(target?.lastPathComponent ?? tl("选择一个文件")).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 !status.isEmpty {
Text(status).font(T.monoSm).foregroundColor(.white.opacity(0.6))
}
}
}
Card {
VStack(alignment: .leading, spacing: 8) {
SectionLabel(text: tl("查找 / 替换"))
TextField(tl("文本,或 41 42 43 / 0x414243"), text: $find)
.font(T.mono).textInputAutocapitalization(.never).autocorrectionDisabled()
.padding(8).background(RoundedRectangle(cornerRadius: 8).fill(T.panelHi))
TextField(tl("替换(可选,不能更长)"), text: $replaceWith)
.font(T.mono).textInputAutocapitalization(.never).autocorrectionDisabled()
.padding(8).background(RoundedRectangle(cornerRadius: 8).fill(T.panelHi))
HStack(spacing: 8) {
RunButton(title: tl("查找"), systemImage: "magnifyingglass", tint: T.info,
disabled: target == nil || find.isEmpty) { search() }
RunButton(title: tl("替换并保存"), systemImage: "arrow.triangle.2.circlepath",
tint: T.ember,
disabled: target == nil || find.isEmpty || replaceWith.isEmpty) { replace() }
}
if !hits.isEmpty {
Text(tl("命中 ") + "\(hits.count)" + tl(" 处: ") + hits.prefix(12).map { String(format: "0x%x", $0) }.joined(separator: ", "))
.font(T.monoSm).foregroundColor(T.good)
.fixedSize(horizontal: false, vertical: true)
}
}
}
if !dump.isEmpty {
Card {
VStack(alignment: .leading, spacing: 4) {
HStack {
SectionLabel(text: tl("十六进制"))
Spacer()
Button(tl("更多")) { offsetLimit += 16384; load() }
.font(T.tiny).tint(T.ember)
}
Text(dump)
.font(.system(size: 10, design: .monospaced))
.foregroundColor(.white.opacity(0.72))
.textSelection(.enabled)
.frame(maxWidth: .infinity, alignment: .leading)
}
}
}
}
.padding(16)
}
.sheet(isPresented: $picking) {
FilePickerSheet(isPresented: $picking) { url in
target = url
hits = []
status = ""
load()
}
}
}
private func load() {
guard let target = target else { return }
guard let data = try? Data(contentsOf: target) else {
status = tl("读取失败")
return
}
dump = hexdump(data, limit: offsetLimit)
status = "\(data.count) B"
}
private func search() {
guard let target = target, let data = try? Data(contentsOf: target) else { return }
hits = HexTool.search(data, pattern: find)
status = hits.isEmpty ? tl("未命中") : tl("命中 ") + "\(hits.count)" + tl(" 处")
}
private func replace() {
guard let target = target, let data = try? Data(contentsOf: target) else { return }
let backup = target.path + ".cinder27.bak"
if !FileManager.default.fileExists(atPath: backup) {
try? FileManager.default.copyItem(atPath: target.path, toPath: backup)
}
let (patched, count) = HexTool.replace(data, pattern: find, with: replaceWith)
if count == 0 {
status = tl("未命中,未写入")
return
}
do {
try patched.write(to: target)
status = tl("替换 ") + "\(count)" + tl(" 处并已保存(备份 ") + "\(URL(fileURLWithPath: backup).lastPathComponent)" + tl(")")
load()
hits = HexTool.search(patched, pattern: replaceWith)
} catch {
status = tl("写入失败: ") + "\(error.localizedDescription)"
}
}
}
// MARK: - xattr / decmpfs
struct XattrPane: View {
@State private var target: URL?
@State private var picking = false
@State private var entries: [(String, Int)] = []
@State private var detail: [String] = []
@State private var status = ""
var body: some View {
ScrollView {
VStack(spacing: 14) {
Card {
VStack(alignment: .leading, spacing: 10) {
SectionLabel(text: tl("目标文件"))
Button { picking = true } label: {
HStack(spacing: 8) {
Image(systemName: "tag.fill").font(.system(size: 12))
Text(target?.lastPathComponent ?? tl("选择文件")).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 !status.isEmpty {
Text(status).font(T.monoSm).foregroundColor(.white.opacity(0.6))
}
}
}
Card {
VStack(alignment: .leading, spacing: 6) {
SectionLabel(text: tl("扩展属性"))
if entries.isEmpty {
Text(tl("无")).font(T.monoSm).foregroundColor(.white.opacity(0.3))
} else {
ForEach(Array(entries.enumerated()), id: \.offset) { _, e in
HStack {
Text(e.0).font(T.monoSm).foregroundColor(.white.opacity(0.75))
Spacer()
Text("\(e.1) B").font(T.monoSm).foregroundColor(.white.opacity(0.3))
}
}
}
}
}
if !detail.isEmpty {
Card {
VStack(alignment: .leading, spacing: 4) {
SectionLabel(text: tl("详情"))
ForEach(Array(detail.enumerated()), id: \.offset) { _, line in
Text(line).font(T.monoSm).foregroundColor(.white.opacity(0.65))
.fixedSize(horizontal: false, vertical: true)
}
}
}
}
Card {
VStack(alignment: .leading, spacing: 8) {
SectionLabel(text: tl("decmpfs 操作"))
Text(tl("com.apple.decmpfs 是内核压缩文件系统用的属性。改它等于直接改内核解压的输入 —— CVE-2026-113910 就是这条路径上「解压产出少于预期」时内核把未写满的页缓冲交给用户态。这里可以手动设一个、读回来、再删掉。"))
.font(T.tiny).foregroundColor(.white.opacity(0.38))
.fixedSize(horizontal: false, vertical: true)
HStack(spacing: 8) {
RunButton(title: tl("写入探测属性"), systemImage: "square.and.arrow.down",
tint: T.ember, disabled: target == nil) { plantDecmpfs() }
RunButton(title: tl("移除"), systemImage: "trash", tint: T.warn,
disabled: target == nil) { removeDecmpfs() }
}
}
}
}
.padding(16)
}
.sheet(isPresented: $picking) {
FilePickerSheet(isPresented: $picking) { url in
target = url
reload()
}
}
}
private func reload() {
guard let target = target else { return }
entries = XattrTool.list(target.path)
detail = []
if let data = XattrTool.read(target.path, name: "com.apple.decmpfs") {
detail.append(XattrTool.decmpfsHeader(data))
detail.append(hexdump(data, limit: 128))
}
status = "\(entries.count)" + tl(" 个属性")
}
private func plantDecmpfs() {
guard let target = target else { return }
let payload = Decmpfs.chunkedXattr(uncompressedSize: 65536, chunkCount: 1, producePerChunk: 4096)
if let err = XattrTool.write(target.path, name: "com.apple.decmpfs", value: payload) {
status = tl("写入失败: ") + "\(err)"
} else {
status = tl("已写入 ") + "\(payload.count)" + tl(" B 的 decmpfs 属性")
}
reload()
}
private func removeDecmpfs() {
guard let target = target else { return }
if let err = XattrTool.remove(target.path, name: "com.apple.decmpfs") {
status = tl("移除失败: ") + "\(err)"
} else {
status = tl("已移除")
}
reload()
}
}
// MARK: - SQLite
struct SQLitePane: View {
@State private var target: URL?
@State private var picking = false
@State private var tables: [String] = []
@State private var sql = "SELECT * FROM sqlite_master LIMIT 20;"
@State private var columns: [String] = []
@State private var rows: [[String]] = []
@State private var status = ""
var body: some View {
ScrollView {
VStack(spacing: 14) {
Card {
VStack(alignment: .leading, spacing: 10) {
SectionLabel(text: tl("数据库"))
Button { picking = true } label: {
HStack(spacing: 8) {
Image(systemName: "cylinder.split.1x2").font(.system(size: 12))
Text(target?.lastPathComponent ?? tl("选择 .sqlite / .db")).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 !tables.isEmpty {
Text(tl("表: ") + tables.prefix(12).joined(separator: ", "))
.font(T.monoSm).foregroundColor(.white.opacity(0.45))
.fixedSize(horizontal: false, vertical: true)
}
}
}
Card {
VStack(alignment: .leading, spacing: 8) {
SectionLabel(text: tl("查询(只读)"))
TextEditor(text: $sql)
.font(.system(size: 11.5, design: .monospaced))
.frame(height: 90)
.scrollContentBackground(.hidden)
.background(T.panelHi)
.cornerRadius(8)
RunButton(title: tl("执行"), systemImage: "play.fill", tint: T.info,
disabled: target == nil) { runQuery() }
if !status.isEmpty {
Text(status).font(T.monoSm).foregroundColor(.white.opacity(0.55))
}
}
}
if !columns.isEmpty {
Card {
VStack(alignment: .leading, spacing: 4) {
Text(columns.joined(separator: " | "))
.font(.system(size: 10, weight: .semibold, design: .monospaced))
.foregroundColor(T.emberSoft)
.fixedSize(horizontal: false, vertical: true)
Divider().overlay(T.stroke)
ForEach(Array(rows.enumerated()), id: \.offset) { _, row in
Text(row.joined(separator: " | "))
.font(.system(size: 10, design: .monospaced))
.foregroundColor(.white.opacity(0.7))
.fixedSize(horizontal: false, vertical: true)
}
}
}
}
}
.padding(16)
}
.sheet(isPresented: $picking) {
FilePickerSheet(isPresented: $picking) { url in
target = url
open()
}
}
}
private func open() {
guard let target = target else { return }
#if canImport(SQLite3)
tables = SQLiteBrowser.tables(target.path)
status = "\(tables.count)" + tl(" 张表")
#else
status = tl("SQLite3 不可用")
#endif
}
private func runQuery() {
guard let target = target else { return }
#if canImport(SQLite3)
let result = SQLiteBrowser.query(target.path, sql)
columns = result.columns
rows = result.rows
status = result.error ?? "\(result.rows.count)" + tl(" 行")
#endif
}
}
// MARK: - Fonts
struct FontPane: View {
@State private var picking = false
@State private var log: [String] = []
@State private var names: [String] = []
var body: some View {
ScrollView {
VStack(spacing: 14) {
Card {
VStack(alignment: .leading, spacing: 10) {
SectionLabel(text: tl("字体安装"))
Text(tl("把字体文件注册进本进程的字体域。iOS 允许应用注册用户字体,注册后 CoreText 立刻可见 —— 这是改机里唯一一条完全不需要任何越权就能改「系统观感」的路径,代价是只在本进程生效。"))
.font(T.body).foregroundColor(.white.opacity(0.65))
.fixedSize(horizontal: false, vertical: true)
RunButton(title: tl("选择字体文件"), systemImage: "textformat", tint: T.ember) {
picking = true
}
}
}
if !log.isEmpty {
Card {
VStack(alignment: .leading, spacing: 4) {
SectionLabel(text: tl("结果"))
ForEach(Array(log.enumerated()), id: \.offset) { _, line in
Text(line).font(T.monoSm).foregroundColor(.white.opacity(0.65))
.fixedSize(horizontal: false, vertical: true)
}
}
}
}
Card {
VStack(alignment: .leading, spacing: 4) {
SectionLabel(text: tl("系统可用字体(") + "\(names.count)" + tl(")"))
ForEach(names.prefix(80), id: \.self) { n in
Text(n).font(T.monoSm).foregroundColor(.white.opacity(0.55))
}
}
}
}
.padding(16)
}
.onAppear {
if names.isEmpty {
DispatchQueue.global(qos: .userInitiated).async {
let list = FontInstaller.installed()
DispatchQueue.main.async { names = list }
}
}
}
.sheet(isPresented: $picking) {
FilePickerSheet(isPresented: $picking) { url in
let result = FontInstaller.install(url)
log.append(result)
names = FontInstaller.installed()
}
}
}
}