// // SymHandler.swift // Pocket Poster // // Created by lemin on 5/31/25. // import Foundation class SymHandler { // MARK: URL Getter Operations static func getDocumentsDirectory() -> URL { let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) let documentsDirectory = paths[0] return documentsDirectory } static func getLCDocumentsDirectory() -> URL { let lcPath = ProcessInfo.processInfo.environment["LC_HOME_PATH"] if let lcPath = lcPath { return URL(fileURLWithPath: "\(lcPath)/Documents") } return getDocumentsDirectory() } static func getPosterBoardHashURL() -> URL { return getLCDocumentsDirectory().appendingPathComponent("NuggetPosterBoardHash") } static func getCarPlayHashURL() -> URL { return getLCDocumentsDirectory().appendingPathComponent("NuggetCarPlayWallpaperHash") } private static func getSymlinkURL() -> URL { return getLCDocumentsDirectory().appendingPathComponent(".Trash", conformingTo: .symbolicLink) } /// Prefer worse_query (iOS 26/27 sandbox escape); fall back to .Trash symlink exploit. static var prefersSandboxEscape: Bool { SandboxEscape.isAvailable } // MARK: Symlink Creation (legacy exploit for older iOS) static func createSymlink(to path: String) throws -> URL { // returns the url of the symlink let symURL = getSymlinkURL() cleanup() // create the symlink to the hashed app folder try FileManager.default.createSymbolicLink(at: symURL, withDestinationURL: URL(fileURLWithPath: path, isDirectory: true)) return symURL } static func createAppSymlink(for appHash: String) throws -> URL { return try createSymlink(to: "/var/mobile/Containers/Data/Application/\(appHash)") } static func getExtensionVersion() -> String { if #available(iOS 17.0, *) { return "61" } return "59" } static func createDescriptorsSymlink(appHash: String, ext: String) throws -> URL { // create a symlink directly to the descriptors let extVer = SymHandler.getExtensionVersion() print("linking to \(appHash)/Library/Application Support/PRBPosterExtensionDataStore/\(extVer)/Extensions/\(ext)/descriptors") return try createAppSymlink(for: "\(appHash)/Library/Application Support/PRBPosterExtensionDataStore/\(extVer)/Extensions/\(ext)/descriptors") } // MARK: Direct write via worse_query /// Copy descriptor folders into PosterBoard descriptors using the sandbox escape. static func writeDescriptorsViaEscape(appHash: String, ext: String, descriptorFolders: [URL]) throws { let destPath = SandboxEscape.descriptorsPath(appHash: appHash, ext: ext) print("worse_query writing to \(destPath)") // Make sure the extension data store exists (PosterBoard creates it on boot). let containerRoot = SandboxEscape.applicationContainerPath(appHash: appHash) try SandboxEscape.ensureDirectory(at: destPath, containerRoot: containerRoot) let handle = try SandboxEscape.consume(path: destPath, create: true) defer { handle.release() } let fm = FileManager.default for descr in descriptorFolders { guard descr.lastPathComponent != "__MACOSX" else { continue } let destName = UUID().uuidString let destURL = URL(fileURLWithPath: destPath).appendingPathComponent(destName) if fm.fileExists(atPath: destURL.path) { try fm.removeItem(at: destURL) } try fm.copyItem(at: descr, to: destURL) } } /// Copy files into an absolute directory under an app container via worse_query. static func writeFilesViaEscape(toDirectory destPath: String, files: [URL]) throws { try SandboxEscape.ensureDirectory(at: destPath) let handle = try SandboxEscape.consume(path: destPath, create: true) defer { handle.release() } let fm = FileManager.default for file in files { let destURL = URL(fileURLWithPath: destPath).appendingPathComponent(file.lastPathComponent) if fm.fileExists(atPath: destURL.path) { try fm.removeItem(at: destURL) } try fm.copyItem(at: file, to: destURL) } } static func cleanup() { // remove the symlink if it exists let symURL = getSymlinkURL() // remove existing symlink try? FileManager.default.removeItem(at: symURL) } }