Skip to content

Commit 1d734cd

Browse files
committed
Add app list viewer and share sheet exploit
1 parent 553f226 commit 1d734cd

File tree

6 files changed

+137
-29
lines changed

6 files changed

+137
-29
lines changed

Makefile

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -34,30 +34,7 @@ SparseBox_FILES = \
3434
include/minimuxer.swift \
3535
include/em_proxy.swift \
3636
include/SwiftBridgeCore.swift \
37-
Sources/AnyCodable/AnyCodable.swift \
38-
Sources/AnyCodable/AnyDecodable.swift \
39-
Sources/AnyCodable/AnyEncodable.swift \
40-
Sources/SparseRestore/MBDB.swift \
41-
Sources/SparseRestore/Backup.swift \
42-
Sources/SparseRestore/Restore.swift \
43-
Sources/LogView.swift \
44-
Sources/MyApp.swift \
45-
Sources/MobileDevice/MobileDevice.swift \
46-
Sources/SwiftNIO/NIOFoundationCompat/ByteBuffer-foundation.swift \
47-
Sources/SwiftNIO/_NIOBase64/Base64.swift \
48-
Sources/SwiftNIO/NIOCore/ByteBuffer-lengthPrefix.swift \
49-
Sources/SwiftNIO/NIOCore/ByteBuffer-conversions.swift \
50-
Sources/SwiftNIO/NIOCore/IntegerTypes.swift \
51-
Sources/SwiftNIO/NIOCore/ByteBuffer-multi-int.swift \
52-
Sources/SwiftNIO/NIOCore/ByteBuffer-views.swift \
53-
Sources/SwiftNIO/NIOCore/ByteBuffer-hexdump.swift \
54-
Sources/SwiftNIO/NIOCore/IntegerBitPacking.swift \
55-
Sources/SwiftNIO/NIOCore/ByteBuffer-aux.swift \
56-
Sources/SwiftNIO/NIOCore/ByteBuffer-core.swift \
57-
Sources/SwiftNIO/NIOCore/CircularBuffer.swift \
58-
Sources/SwiftNIO/NIOCore/ByteBuffer-int.swift \
59-
Sources/SwiftNIO/NIOPosix/PointerHelpers.swift \
60-
Sources/ContentView.swift
37+
$(shell find Sources -type f -name "*.swift")
6138
SparseBox_FRAMEWORKS = UIKit
6239
SparseBox_CFLAGS = -fcommon -fobjc-arc
6340
SparseBox_SWIFTFLAGS = -Iinclude -import-objc-header include/minimuxer-Bridging-Header.h

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@ Before opening SparseBox, you have to close SideStore from app switcher. This is
1515
- @SideStore: em_proxy and minimuxer
1616
- @JJTech0130: SparseRestore and backup exploit
1717
- @PoomSmart: MobileGestalt dump
18+
- @Lakr233: BBackupp
1819
- @libimobiledevice
1920
- [the sneakyf1shy apple intelligence tutorial](https://gist.github.com/f1shy-dev/23b4a78dc283edd30ae2b2e6429129b5#file-best_sae_trick-md)

Sources/AppListView.swift

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import SwiftUI
2+
3+
struct AppItemView: View {
4+
let appDetails: [String : AnyCodable]
5+
var body: some View {
6+
Form {
7+
Section {
8+
ForEach(Array(appDetails.keys), id: \.self) { k in
9+
let v = appDetails[k]?.value as? String
10+
Text(k)
11+
.badge("\(v ?? "(null)" )")
12+
.textSelection(.enabled)
13+
}
14+
}
15+
Section {
16+
if let bundlePath = appDetails["Path"] {
17+
ShareLink(item: URL(string: "file://a\(bundlePath)")!) {
18+
Text("Share app bundle folder")
19+
}
20+
}
21+
if let containerPath = appDetails["Container"] {
22+
ShareLink(item: URL(string: "file://a\(containerPath)")!) {
23+
Text("Share app data folder")
24+
}
25+
}
26+
} header: {
27+
Text("Arbitrary read exploit")
28+
} footer: {
29+
Text("Only supported on iOS 18.2b2 and older. For this exploit, folders can only be shared via AirDrop.\nIf you're sharing App Store apps, please note that it will still be encrypted.")
30+
}
31+
}
32+
}
33+
}
34+
35+
struct AppListView: View {
36+
@State var apps: [String : AnyCodable] = [:]
37+
@State var searchString: String = ""
38+
var results: [String] {
39+
Array(searchString.isEmpty ? apps.keys : apps.filter {
40+
let appDetails = $0.value.value as? [String: AnyCodable]
41+
let appName = (appDetails!["CFBundleName"]?.value as! String?)!
42+
let appPath = (appDetails!["Path"]?.value as! String?)!
43+
return appName.contains(searchString) || appPath.contains(searchString)
44+
}.keys)
45+
}
46+
var body: some View {
47+
List {
48+
ForEach(results, id: \.self) { bundleID in
49+
let value = apps[bundleID]
50+
let appDetails = value?.value as? [String: AnyCodable]
51+
let appImage = appDetails!["PlaceholderIcon"]?.value as! Data?
52+
let appName = (appDetails!["CFBundleName"]?.value as! String?)!
53+
let appPath = (appDetails!["Path"]?.value as! String?)!
54+
NavigationLink {
55+
AppItemView(appDetails: appDetails!)
56+
} label: {
57+
Image(uiImage: UIImage(data: appImage!)!)
58+
.resizable()
59+
.frame(width: 40, height: 40)
60+
VStack(alignment: .leading) {
61+
Text(appName)
62+
Text(appPath).font(Font.footnote)
63+
}
64+
}
65+
}
66+
}
67+
.onAppear {
68+
if apps.count == 0 {
69+
Task {
70+
let deviceList = MobileDevice.deviceList()
71+
guard deviceList.count == 1 else {
72+
print("Invalid device count: \(deviceList.count)")
73+
return
74+
}
75+
let udid = deviceList.first!
76+
apps = MobileDevice.listApplications(udid: udid)!
77+
}
78+
}
79+
}
80+
.searchable(text: $searchString)
81+
.navigationTitle("App list")
82+
}
83+
}

Sources/ContentView.swift

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ struct ContentView: View {
5454
}
5555
}
5656
Section {
57+
Button("List installed apps") {
58+
testListApps()
59+
}
5760
Button("Bypass 3 app limit") {
5861
testBypassAppLimit()
5962
}
@@ -159,8 +162,10 @@ Thanks to:
159162
.navigationDestination(for: String.self) { view in
160163
if view == "ApplyChanges" {
161164
LogView(mbdb: mbdb!, reboot: reboot)
162-
} else if view == "BypassAppLimit" {
165+
} else if view == "ApplyNoReboot" {
163166
LogView(mbdb: mbdb!, reboot: false)
167+
} else if view == "ListApps" {
168+
AppListView()
164169
}
165170
}
166171
.navigationTitle("SparseBox")
@@ -194,13 +199,13 @@ Thanks to:
194199
let origMethod = class_getInstanceMethod(UIDocumentPickerViewController.self, #selector(UIDocumentPickerViewController.init(forOpeningContentTypes:asCopy:)))!
195200
method_exchangeImplementations(origMethod, fixMethod)
196201
}
197-
202+
198203
func testBypassAppLimit() {
199204
Task {
200205
taskRunning = true
201206
if ready() {
202207
mbdb = Restore.createBypassAppLimit()
203-
path.append("BypassAppLimit")
208+
path.append("ApplyNoReboot")
204209
} else {
205210
lastError = "minimuxer is not ready. Ensure you have WiFi and WireGuard VPN set up."
206211
showErrorAlert.toggle()
@@ -209,11 +214,21 @@ Thanks to:
209214
}
210215
}
211216

217+
func testListApps() {
218+
if ready() {
219+
path.append("ListApps")
220+
} else {
221+
lastError = "minimuxer is not ready. Ensure you have WiFi and WireGuard VPN set up."
222+
showErrorAlert.toggle()
223+
}
224+
}
225+
212226
func applyChanges() {
213227
Task {
214228
taskRunning = true
215229
if ready() {
216-
mbdb = Restore.createBackupFiles(files: generateFilesToRestore())
230+
mbdb = Restore.createMobileGestalt(file: FileToRestore(from: modMGURL, to: URL(filePath: "/var/containers/Shared/SystemGroup/systemgroup.com.apple.mobilegestaltcache/Library/Caches/com.apple.MobileGestalt.plist"), owner: 501, group: 501))
231+
//Restore.createBackupFiles(files: generateFilesToRestore())
217232
path.append("ApplyChanges")
218233
} else {
219234
lastError = "minimuxer is not ready. Ensure you have WiFi and WireGuard VPN set up."

Sources/MobileDevice/MobileDevice.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,10 +145,13 @@ class MobileDevice {
145145
requireInstallProxyService(device: device) { inst_client in
146146
guard let inst_client else { return }
147147
let options: [String: Any] = [
148-
"ApplicationType": "User",
148+
//"ApplicationType": "User",
149149
"ReturnAttributes": [
150+
"ApplicationType",
150151
"CFBundleIdentifier",
152+
"CFBundleName",
151153
"Path",
154+
"Container"
152155
],
153156
]
154157
let data = try! PropertyListEncoder().encode(AnyCodable(options))

Sources/SparseRestore/Restore.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import Foundation
22

3+
enum PathTraversalCapability: Int {
4+
case unsupported = 0 // 18.2b3+, 17.7.2
5+
case dotOnly // 18.1b5-18.2b2, 17.7.1
6+
case dotAndSlashes // up to 18.1b4, 17.7
7+
}
8+
39
class FileToRestore {
410
var contents: Data
511
var to: URL
@@ -21,6 +27,14 @@ class FileToRestore {
2127
}
2228

2329
struct Restore {
30+
static func supportedExploitLevel() -> PathTraversalCapability {
31+
if #available(iOS 18.1, *) {
32+
return .dotOnly
33+
} else {
34+
return .dotAndSlashes
35+
}
36+
}
37+
2438
static func createBypassAppLimit() -> Backup {
2539
let deviceList = MobileDevice.deviceList()
2640
guard deviceList.count == 1 else {
@@ -53,6 +67,21 @@ struct Restore {
5367
return Backup(files: files)
5468
}
5569

70+
static func createMobileGestalt(file: FileToRestore) -> Backup {
71+
Backup(files: [
72+
Directory(path: "", domain: "SysSharedContainerDomain-systemgroup.com.apple.mobilegestaltcachf"),
73+
Directory(path: "Library", domain: "SysSharedContainerDomain-systemgroup.com.apple.mobilegestaltcachf"),
74+
Directory(path: "Library/Caches", domain: "SysSharedContainerDomain-systemgroup.com.apple.mobilegestaltcachf"),
75+
ConcreteFile(
76+
path: "Library/Caches/com.apple.MobileGestalt.plist",
77+
domain: "SysSharedContainerDomain-systemgroup.com.apple.mobilegestaltcachf",
78+
contents: file.contents,
79+
owner: file.owner,
80+
group: file.group),
81+
SymbolicLink(path: "", domain: "SysSharedContainerDomain-systemgroup.com.apple.mobilegestaltcache", target: "systemgroup.com.apple.mobilegestaltcachf")
82+
])
83+
}
84+
5685
static func createBackupFiles(files: [FileToRestore]) -> Backup {
5786
// create the files to be backed up
5887
var filesList : [BackupFile] = [

0 commit comments

Comments
 (0)