import Foundation import UIKit // MARK: - Self-tests for the modification tooling // // These run inside the app so the tooling proves itself on the device it will be used on, // rather than only on the build machine. Each one builds its own fixture, exercises one // tool, and reports the artifact. extension Catalog { static let tooling: [ExploitModule] = [ ExploitModule( id: "tool.ipa", titleZh: "IPA 手术自检(改包名 / 注入 / 重签名准备)", cve: "—", component: "IPASurgeon · MachO · ZipWriter", primitive: .probe, reach: .inProcess, tier: 1, summaryZh: "现场造一个最小 IPA(Info.plist + 一个真的 Mach-O),跑完整的改机流程:解包 → 改 CFBundleIdentifier → 注入 LC_LOAD_DYLIB → 去掉 LC_CODE_SIGNATURE → 用自己写的 ZIP 写入器重新打包。产出留在 Documents/Cinder27/IPA,可以直接拿去签名。", notesZh: "这条不是漏洞,是工具链的证明。它跑通说明 IPA 面板在你的设备上能用,而不是只在构建机上能用。", run: { ctx in ctx.arm("tool.ipa", "fixture + patch") var lines: [String] = [] // ---- fixture: a real arm64 Mach-O from this process guard let exec = Bundle.main.executablePath, let macho = try? Data(contentsOf: URL(fileURLWithPath: exec)) else { ctx.fail("tool.ipa", tl("无法读取自身二进制")) throw LabError(message: tl("无法读取自身二进制")) } let header = MachO.header(macho) lines.append(tl("取自本进程的 Mach-O:") + "\(macho.count)" + tl(" B · 64 位=") + "\(header?.is64 ?? false)" + tl(" · ncmds=") + "\(header?.ncmds ?? 0)") // ---- signature strip first, so injection writes into a clean header var working = macho if let (stripped, removed) = try? MachO.stripSignature(macho) { working = stripped lines.append(tl("移除 LC_CODE_SIGNATURE:") + "\(removed)" + tl(" 条")) } // ---- exercise the load-command injector, and carry the result forward do { let injected = try MachO.injectLoad(working, dylibPath: "@executable_path/Frameworks/CinderHook.dylib") let after = MachO.header(injected) let cmds = MachO.commands(injected) let hasHook = cmds.contains { $0.cmd == 0x0C } lines.append(tl("注入 LC_LOAD_DYLIB:ncmds ") + "\(MachO.header(working)?.ncmds ?? 0)" + tl(" → ") + "\(after?.ncmds ?? 0)" + tl(",sizeofcmds ") + "\(MachO.header(working)?.sizeofcmds ?? 0)" + tl(" → ") + "\(after?.sizeofcmds ?? 0)" + tl(",命令数 ") + "\(cmds.count)" + tl(",含 LC_LOAD_DYLIB=") + "\(hasHook)") working = injected } catch { lines.append(tl("注入未成功(头部无空隙):") + "\(error.localizedDescription)") } // ---- exercise the string patcher on a string the binary really contains do { let (patched, hits) = try MachO.patchStrings(working, find: "Cinder27", replace: "Cind3r27") lines.append(tl("字符串替换 Cinder27 → Cind3r27:") + "\(hits)" + tl(" 处")) working = patched } catch { lines.append(tl("字符串替换失败:") + "\(error.localizedDescription)") } // ---- build the fixture IPA and run the full surgeon var zip = ZipWriter() var info: [String: Any] = [ "CFBundleIdentifier": "com.cinder.fixture", "CFBundleExecutable": "Fixture", "CFBundleName": "Fixture", "CFBundleDisplayName": "Fixture", "CFBundleShortVersionString": "1.0", "CFBundleVersion": "1", "MinimumOSVersion": "17.0", "CFBundlePackageType": "APPL", ] if let data = try? PropertyListSerialization.data(fromPropertyList: info, format: .binary, options: 0) { zip.add("Payload/Fixture.app/Info.plist", data) } zip.add("Payload/Fixture.app/Fixture", working) let fixture = zip.build() let fixtureURL = ctx.write("fixture.ipa", fixture) lines.append(tl("自造 IPA:") + "\(fixture.count)" + tl(" B(ZipWriter 产出,stored 条目)")) var recipe = IPARecipe() recipe.bundleID = "com.cinder.patched" recipe.displayName = "Patched" recipe.version = "9.9" recipe.injectedDylib = "@executable_path/Frameworks/CinderHook.dylib" recipe.patchedString = "com.cinder.fixture" recipe.patchedReplacement = "com.cinder.patched" recipe.stripSignature = true var output: URL? do { let result = try IPASurgeon.patch(source: fixtureURL, recipe: recipe, workdir: ctx.workdir) output = result.outputURL lines.append(tl("手术完成:") + "\(result.entries)" + tl(" 个文件 → ") + "\(result.outputURL.lastPathComponent)") for note in result.notes { lines.append(" · \(note)") } } catch { lines.append(tl("手术失败:") + "\(error.localizedDescription)") } ctx.disarm("tool.ipa", "done") var artifacts: [URL] = [fixtureURL] if let o = output { artifacts.append(o) } return ModuleOutcome(headline: output == nil ? tl("手术未完成") : tl("IPA 手术链路可用"), detail: lines.joined(separator: "\n"), artifacts: artifacts) }), ExploitModule( id: "tool.zip", titleZh: "ZIP 写入器往返自检", cve: "—", component: "ZipWriter + libarchive", primitive: .probe, reach: .inProcess, tier: 1, summaryZh: "用自己写的 ZIP 写入器造一个含随机内容的归档,再用 libarchive 解回来,逐字节比对。这条证明重打包不会悄悄损坏文件 —— 改机工具最怕的就是这个。", notesZh: "64 个文件,每个 4 KB 随机内容,比对 CRC 与长度。", run: { ctx in ctx.arm("tool.zip", "roundtrip") var zip = ZipWriter() var expected: [String: Data] = [:] var seed: UInt64 = 0x9E3779B97F4A7C15 for i in 0..<64 { var bytes = [UInt8]() for _ in 0..<4096 { seed ^= seed << 13; seed ^= seed >> 7; seed ^= seed << 17 bytes.append(UInt8(truncatingIfNeeded: seed)) } let name = "dir\(i % 8)/file\(i).bin" let data = Data(bytes) expected[name] = data zip.add(name, data) } let archive = zip.build() let url = ctx.write("zip_roundtrip.zip", archive) let dir = ctx.workdir.appendingPathComponent("zipcheck", isDirectory: true) try? FileManager.default.removeItem(at: dir) let result = Runners.Archive.extractAll(archive, to: dir) var matched = 0 var mismatched = 0 for (name, data) in expected { let path = dir.appendingPathComponent(name) if let back = try? Data(contentsOf: path), back == data { matched += 1 } else { mismatched += 1 } } ctx.disarm("tool.zip", "matched=\(matched)") return ModuleOutcome( headline: mismatched == 0 && matched == expected.count ? "\(matched)" + tl("/") + "\(expected.count)" + tl(" 个文件逐字节一致") : "\(matched)" + tl(" 一致 / ") + "\(mismatched)" + tl(" 不一致"), detail: tl("归档 ") + "\(archive.count)" + tl(" B · 解包 ") + "\(result.count)" + tl(" 项 · ") + "\(result.status)", evidence: [tl("64 × 4 KB 随机内容"), tl("自己写 ZIP → libarchive 读回")], artifacts: [url]) }), ExploitModule( id: "tool.macho", titleZh: "Mach-O 解析器自检", cve: "—", component: "MachO · MachOInspector", primitive: .probe, reach: .inProcess, tier: 1, summaryZh: "对系统卷上的几个真实二进制跑一遍头解析:架构、类型、平台、最低系统、SDK、加密状态、链接库、rpath。加密状态是这一整套里最有信息量的字段 —— 系统框架 cryptid 为 0,第三方 App 的二进制不是。", notesZh: "同时跑我自己写的解析器和 MachOInspector,两个结果应该一致。", run: { ctx in ctx.arm("tool.macho", "parse") let targets: [(String, String)] = [ ("CoreGraphics", "/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics"), ("CoreMedia", "/System/Library/Frameworks/CoreMedia.framework/CoreMedia"), ("libarchive", "/usr/lib/libarchive.2.dylib"), ("ImageIO", "/System/Library/Frameworks/ImageIO.framework/ImageIO"), (tl("本进程"), Bundle.main.executablePath ?? ""), ] var lines: [String] = [] var parsed = 0 for (label, path) in targets { guard FileManager.default.fileExists(atPath: path) else { lines.append("\(label)" + tl(": 不可见")) continue } guard let data = try? Data(contentsOf: URL(fileURLWithPath: path)) else { lines.append("\(label)" + tl(": 读取被拒绝(沙盒)")) continue } parsed += 1 if let h = MachO.header(data) { let cmds = MachO.commands(data) lines.append("\(label)" + tl(": ") + "\(data.count)" + tl(" B · ncmds=") + "\(h.ncmds)" + tl(" 解析出 ") + "\(cmds.count)" + tl(" 条 · type=") + "\(h.filetype)" + tl(" · 64=") + "\(h.is64)") } if let info = MachOInspector.inspect(path) { lines.append(tl(" ") + "\(info.arch)" + tl(" · ") + "\(info.platform)" + tl(" · minOS ") + "\(info.minOS)" + tl(" · SDK ") + "\(info.sdk)" + tl(" · 加密=") + "\(info.isEncrypted)" + tl(" · 库 ") + "\(info.linkedLibraries.count)") } } ctx.disarm("tool.macho", "parsed=\(parsed)") return ModuleOutcome(headline: tl("解析 ") + "\(parsed)" + tl(" 个二进制"), detail: lines.joined(separator: "\n")) }), ExploitModule( id: "tool.hex", titleZh: "十六进制查找替换自检", cve: "—", component: "HexTool", primitive: .probe, reach: .inProcess, tier: 1, summaryZh: "在随机缓冲里植入已知模式,用十六进制与文本两种写法查找、替换、再查找,确认命中数与偏移都对。", notesZh: "这条守的是「改机工具最不该出错的地方」。", run: { ctx in ctx.arm("tool.hex", "roundtrip") var bytes = [UInt8](repeating: 0x00, count: 65536) let needle: [UInt8] = [0xDE, 0xAD, 0xBE, 0xEF] var planted: [Int] = [] for i in stride(from: 1000, to: 60000, by: 4096) { for k in 0..