newly released iTunesStored and BookAssetD SBX Escape Exploit Allows us to modify the MobileGestalt.Plist file to change the values inside it.
This file is very important as it contains all the details about the device. Capabilities like its type, color, model, dynamic island, stage manager, multitasking, etc. are all present inside that file.
Naturally, Apple encrypts key-value pairs, but people have managed to figure out most of them over the years.
Modification of the MobileGestalt file has allowed many tweaking applications to exist over the years, such as Nuget, Misaka, and Picasso.
Recently, developer Du Tran posted an interesting video of his iPhone with iPad features like real app windows, iPadOS dock, Stage Manager, etc. This was done with a new exploit that uses maliciously crafted download.28.sqlitedb Database for writing to paths normally protected by the sandbox.
Luckily, MobileGestalt.Plist is one of these paths, and you can actually modify your iPhone for iPadOS features.
First glimpse of iPadOS on iPhone 17 Pro Max pic.twitter.com/PMynlGLVFw
– Duy Tran (@khanhduytran0) 15 November 2025
Supported iOS versions and devices
The new itunesstored & Bookassetd sandbox escape exploit supports all devices on iOS up to iOS 26.1 and iOS 26.2 beta 1.
This exploit circulated on the Internet for some time and was used for iCloud bypass purposes as it can write and hack paths.
This will probably be used to update tools like Nuget, Misaka, etc.
This is quite a powerful feat. It can write to most paths controlled/owned by mobile User. Cannot write paths owned by Root User.
Getting MobileGestalt.Plist file from device
There are many ways to go about this. Some shortcuts still allow you to get plist, although some of these have been moved around.
I didn’t face any problems. I just created a new Xcode application and read the file here /private/var/containers/Shared/SystemGroup/ systemgroup.com.apple.mobilegestaltcache/Library/Caches/com.apple.MobileGestalt.plist
Its as simple as that:
import SwiftUI
import UniformTypeIdentifiers
struct ContentView: View {
@State private var plistData: Any?
@StateObject private var coordinator = DocumentPickerCoordinator()
let plistPath = "/private/var/containers/Shared/SystemGroup/systemgroup.com.apple.mobilegestaltcache/Library/Caches/com.apple.MobileGestalt.plist"
var body: some View {
VStack(spacing: 20) {
Button("Load Plist") {
loadPlist()
}
if plistData != nil {
Button("Save to Files") {
savePlist()
}
}
}
}
func loadPlist() {
if let data = try? Data(contentsOf: URL(fileURLWithPath: plistPath)),
let plist = try? PropertyListSerialization.propertyList(from: data, options: [], format: nil) {
plistData = plist
}
}
func savePlist() {
guard let plist = plistData,
let data = try? PropertyListSerialization.data(fromPropertyList: plist, format: .xml, options: 0) else { return }
let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent("MobileGestalt.plist")
try? data.write(to: tempURL)
let picker = UIDocumentPickerViewController(forExporting: [tempURL], asCopy: true)
picker.delegate = coordinator
if let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let window = scene.windows.first,
let root = window.rootViewController {
var top = root
while let presented = top.presentedViewController {
top = presented
}
top.present(picker, animated: true)
}
}
}
class DocumentPickerCoordinator: NSObject, UIDocumentPickerDelegate, ObservableObject {
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {}
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {}
}This will save your MobileGestalt.Plist file without any problems even on iOS 26.1 because Apple still allows iOS apps to read this path, no problem. You can’t write it that way, but reading works.
Once you have this file inside the application, you can AirDrop it to your computer.
Finding appropriate MobileGestalt keys for typing
There are hundreds of MobileGestalt keys, each of which controls something else. These keys are encrypted and look like 1tvy6WfYKVGumYi6Y8E5Og and /bSMNaIuUT58N/BN1nYUjw, etc.
For example:
- uKc7FPnEO++lVhHWHFlGbQ = The device is an iPad
- HV7WDiidgMf7lwAu++Lk5w = The device has TouchID functionality.
- s2UwZpwDQcywU3de47/ilw = The device has a microphone.
And so on. There’s a good article on TheAppleWiki detailing all the available MobileGestalt Keys people have managed to decrypt over the years.
You have to find the right keys to add to your iPhone’s MobileGestalt that will first think it’s an iPad, and then enable iPad features like Stage Manager, multitasking, etc.
I’ve done the research for you, and You need the following keys:
- uKc7FPnEO++lVhHWHFlGbQ = The device is an iPad.
- mG0AnH/Vy1veoqoLRAIgTA = Device supports Medusa floating live apps
- UCG5MkVahJxG1YULbbd5Bg = Device supports Medusa overlay apps
- ZYqko/XM5zD3XBfN5RmaXA = Device supports Medusa pinned apps
- nVh/gwNpy7Jv1NOk00CMrw = Device supports MedusaPIP mirroring
- qeaj75wk3HF4DwQ8qbIi7g=Device is able to enable Stage Manager
Those are all the keys we need right now.
uKc7FPnEO++lVhHWHFlGbQ The value is what tells the device it’s an iPad rather than an iPhone. This must be added to cachedata Section of MobileGestalt plist, no cashextra, otherwise The device will be bootlooped!
However, we have a problem. CacheData looks like this:


So how can we add the necessary keys to it? These are all distorted characters. Looks encrypted.
Well, you need to find the offset of the key you want to change inside the libmobilegestalt.dylib file. Let me explain.
uKc7FPnEO++lVhHWHFlGbQ Need to add value (iPad) mtrAoWJ3gsq+I90ZnQ0vQwwhich is deviceclassnumberLike this:
mtrAoWJ3gsq+I90ZnQ0vQw , uKc7FPnEO++lVhHWHFlGbQ
which translates to deviceclassnumber :3 (iPad)
But how can you add it if the CacheData section is malformed?
Finding the correct offset inside libmobilegestalt.dylib
/usr/lib/libMobileGestalt.dylib Not accessible from the sandbox, so we can’t read it through the Xcode-built app like before, but we can open libmobilegestalt.dylib and parse its segments. If we can do that, we can look for the encrypted key mtrAoWJ3gsq+I90ZnQ0vQw And find the offset.
Then we will know where to place our modified keys.
You can pre-opt a SwiftUI app dlopen Dilib quite easily. This is what I have brought. Quick and dirty based on Duy’s old code from SparseBox:
func findCacheDataOffset() -> Int? {
guard let handle = dlopen("/usr/lib/libMobileGestalt.dylib", RTLD_GLOBAL) else { return nil }
defer { dlclose(handle) }
var headerPtr: UnsafePointer?
var imageSlide: Int = 0
for i in 0..<_dyld_image_count() {
if let imageName = _dyld_get_image_name(i),
String(cString: imageName) == "/usr/lib/libMobileGestalt.dylib",
let imageHeader = _dyld_get_image_header(i) {
headerPtr = UnsafeRawPointer(imageHeader).assumingMemoryBound(to: mach_header_64.self)
imageSlide = _dyld_get_image_vmaddr_slide(i)
break
}
}
guard let header = headerPtr else { return nil }
var textCStringAddr: UInt64 = 0
var textCStringSize: UInt64 = 0
var constAddr: UInt64 = 0
var constSize: UInt64 = 0
var curCmd = UnsafeRawPointer(header).advanced(by: MemoryLayout.size)
for _ in 0...size)
for _ in 0...size)
}
}
curCmd = curCmd.advanced(by: Int(cmd.pointee.cmdsize))
}
guard textCStringAddr != 0, constAddr != 0 else { return nil }
let textCStringPtr = UnsafeRawPointer(bitPattern: Int(textCStringAddr) + imageSlide)!
var keyPtr: UnsafePointer?
var offset = 0
while offset < Int(textCStringSize) {
let currentPtr = textCStringPtr.advanced(by: offset).assumingMemoryBound(to: CChar.self)
let currentString = String(cString: currentPtr)
if currentString == "mtrAoWJ3gsq+I90ZnQ0vQw" {
keyPtr = currentPtr
break
}
offset += currentString.utf8.count + 1
}
guard let keyPtr = keyPtr else { return nil }
let constSectionPtr = UnsafeRawPointer(bitPattern: Int(constAddr) + imageSlide)!.assumingMemoryBound(to: UnsafeRawPointer.self)
var structPtr: UnsafeRawPointer?
for i in 0.. This will compensate you deviceclassnumber So now you can write your new value in it.
For this, you can simply modify Duy’s Python script, which is based on hana kimOriginal files of.
I added something like this:
IPAD_KEYS = [
"uKc7FPnEO++lVhHWHFlGbQ",
"mG0AnH/Vy1veoqoLRAIgTA",
"UCG5MkVahJxG1YULbbd5Bg",
"ZYqko/XM5zD3XBfN5RmaXA",
"nVh/gwNpy7Jv1NOk00CMrw",
"qeaj75wk3HF4DwQ8qbIi7g"
]
def write_ipad_to_device_class_with_offset(self, mg_plist, offset):
cache_data = mg_plist.get('CacheData')
cache_extra = mg_plist.get('CacheExtra', {})
if cache_data is None:
self.log("[!] Error: CacheData not found in MobileGestalt", "error")
return False
if not isinstance(cache_data, bytes):
cache_data = bytes(cache_data)
if offset >= len(cache_data) - 8:
self.log(f"[!] Error: Offset {offset} is beyond CacheData bounds ({len(cache_data)} bytes)", "error")
return False
cache_data_array = bytearray(cache_data)
current_value = struct.unpack_from('That’s it. These are all the modifications I made to Duy’s original bl_sbx Python script.


going along with it Python3 In venv I got permission to change the device to iPad and enable iPad features.
The success rate of this experiment is not very good, so you may have to try it again and again until it succeeds. Once this happens, reboot the device. You should be able to access iPad features in Settings.






Setting up the environment for Python3 on macOS
To properly run Python scripts and install dependencies on recent macOS, you must first set up a virtual environment. To do this, you need to run:
cd bl_sbx
python3 -m venv venv
source venv/bin/activate
pip install click requests packaging pymobiledevice3Once the environment is set up and the prerequisites are installed, you can simply run the script:
python3 run.py DEVICE UDID /path/to/MobileGestalt.plisti used ideviceinfopart of libimobiledevice, To get the device UDID, but it is also available in Finder, 3uTools on Windows, etc.
