Many of the writeups reverse engineering the iOS backup system are outdated and do not necessarily represent how it works in modern day. They also tended to describe the structure of a backup file, but do not detail the on-device process of restoring a backup behind the scenes. The goal of this writeup is to document my findings on the backup system over the years and what I learned while developing Cowabunga Lite and Nugget. I will also be covering the Sparserestore exploit.
There is not a lot of documentation of how the backup domains map to the iOS file system, so I am compiling what I found here.
In a backup, domains tell iOS what files to restore and where. It is both a way of organization and a security measure. Having domains prevents attackers from using restores to write anywhere in the file system.
There are 2 different kinds of domains. The main type of domain is simply mapped to a file path. For example, HomeDomain
is equivalent to /var/mobile
but with some restrictions for what can and cannot be restored (specified in the iOS backend). There are not much to these, they just act like a container.
The second type of domain requires an extension. There are 3 domains that utilize this, SysContainerDomain
, SysSharedContainerDomain
, and AppDomain
. They have a trailing -
where the extension is added. For SysContainerDomain
and SysSharedContainerDomain
, the container name goes after the -
character. This is also the name of the subfolder in the file system, which makes it strange for why it needs to be a special kind of domain. This way of handling it is ultimately what made it vulnerable (see Sparserestore). For AppDomain
, the app bundle id goes after the -
character. This makes sense because the file system uses a unique identifier or UUID to hash app folder names, so the restore system needs to specifically handle this scenario. Outside of the domain itself, the path will act the same as the regular domains.
There used to be a file in /System/Library/Backup/domains.plist
that listed many of the paths and what could be backed up, but it was removed in iOS 17, seemingly correlated to separating the /var
and /var/mobile
partitions (see more at iOS 17 and mobile partitioning). The file did not include the domains SysContainerDomain-
, SysSharedContainerDomain-
, or AppDomain-
due to their different handling of the subpaths. A markdown version of the file can be found in Domains.md if you would like to learn more about what can and cannot be restored in each domain.
Note: Domains will not restore to cache folders even when told to backup everything. Certain caches or files (i.e. eligibility.plist) will get reset upon the invokation of a restore, even if it does not wipe the device.
Backups are structured as a folder containing 4 main files and the contents of the files being restored. The main files are Info.plist
, Manifest.mbdb
, Manifest.plist
, and Status.plist
. The file contents have no extension and are titled by the SHA1 hashes of the file.
The Status.plist
file contains values about the status of the backup. One of these values is IsFullBackup
, which is set to false for partial restores.
Also in Status.plist
is the Version
value, which is the version of backups being used. For partial restores, the version key should be set to 2.4
since we are using the Manifest.mbdb
file. Though version 3.2 backups can be created using Manifest.db
instead, they are not useful for partial restores like I am covering here.
The UUID
field does not matter much, so I just set it to 00000000-0000-0000-0000-000000000000
. The Date
field denotes when the backup was taken, but it can just be set to 1970-01-01T00:00:00Z
.
The last two fields are BackupState
and SnapshotState
, they should be set to new
and finished
respectively for our partial restore.
Manifest.plist
provides some basic information about the backup itself.
The Lockdown
field can just be an empty dictionary.
For the SystemDomainsVersion
and Version
, I just used the values 20.0
and 9.1
respectively. I am not sure what the importance of these values are but they are required to finish the backup.
Finally, there is the BackupKeyBag
value. This stores KeyBag data in base64. You can find the full value I used in Cowabunga Lite Py's create_backup.py code.
Info.plist
isn't as important, so I will not cover what's in it. For the sake of a partial restore, an empty plist file can be used.
The main file in a backup is Manifest.mbdb
. It stores the domains, file locations, SHA1 hashes, and many more details about what is being restored. The structure of the file itself has already been well documented, you can find that here. For this writeup, I will only be covering certain fields that I deem important and the structure of the files/directories prior to being placed in the manifest.
File paths must list out every directory in sorted order. If 2 files are in the same directory, it should only list the file without relisting the directory. Same goes with directories. Separate directories are listed after all the files in the previous directory have been listed. Here is an example:
HomeDomain (domain/directory)
HomeDomain Library (directory)
HomeDomain Library/Preferences (directory)
HomeDomain Library/Preferences/com.apple.springboard.plist (file)
HomeDomain Library/Preferences/com.apple.UIKit.plist (file)
HomeDomain Library/SpringBoard (directory)
HomeDomain Library/SpringBoard/statusBarOverrides (file)
For both files and directories, owner and group permissions need to be specified. In testing, the value put usually did not seem to impact the outcome (aside from one time), but its usually safer to just use the mobile permission of 501
for most.
Only files need to be hashed, directories do not. The hash is calculated as sha1(domain + '-' + file_path)
. This hash will be placed both in Manifest.mbdb
and used as the name for the file's contents inside the backup's folder.
Another file-specific value is the inode. It provides an index for attributes about the file. No two files should have the same inode unless they are being linked. Linking should not happen in a regular partial restore. Doing so may cause the file to not restore at all. You can read about the specific use case of inodes in the Sparserestore section.
As I stated earlier, permissions usually do not seem to matter. There was one time where it did impact iOS in a weird way though. This is not super important to the writeup, I just wanted to share this story.
Early on in Cowabunga Lite's development, there was a bug in the backup generator's code where the permissions were incorrectly set due to a missing 0
.
In 64 bit systems, integers are 4 bytes long. The permissions are integer values being written to the file as hexadecimal. The bytes that are supposed to be used for the permissions are 00 00 01 F5
, which is 501
in decimal. However, in the restore code, the missing 0
caused the permissions to be set to 50 00 01 F5
, which is a very large (clearly incorrect) number. It took a 5
from the end of the previous byte. You can see the issue...
This was not noticable at first. Many of the files modified by Cowabunga Lite were not impacted by this mistake. After implementing icon theming using webclips, the issue became apparent. The webclips would show up on the home screen as usual and functioned like normal. They can also be deleted like normal, or so I thought. When deleting them, they would disappear from the home screen, but as soon as the device was rebooted, they would come back. The permissions prevented iOS from being able to delete the webclips from the file system.
Luckily, this was realized before making it to the public and a fix was made.
To prevent this or other weird issues from happening to you if you are writing your own Manifest.mbdb
generator, I recommend just using the mobile permission of 501
unless the file you are modifying needs a different permission.
In iOS 17, Apple made many modifications to the backup system. Notably, /var/mobile
was moved to its own partition to separate it from /var
. Attempting to traverse from one to the other will usually lead to a bootloop or restore error.
Traditionally, backup files were stored in /var/backup
as they are being restored. With the introduction of the mobile partition in iOS 17, a new backup folder was created for domains in the mobile partition, /var/mobile/backup
. All domains that map to the mobile folder now get placed there upon restore.
When tweaking files using partial restores, the setup screen can be very annoying. It can also be risky sometimes and start trying to overwrite your data with an iCloud backup. This is why finding methods to skip the setup are crucial.
There are 2 files that Cowabunga Lite uses to fully skip the setup.
The first and primary skip setup file is CloudConfigurationDetails.plist
. This is used by the Apple Configurator app to skip the setup when making changes to supervised devices.
Conveniently, this file has a key called SkipSetup
. This key contains a list of which menu screens should be skipped on setup. In Apple Configurator, the user is able to select which screens get skipped.
To get the list of values for this key, you need a mac with Apple Configurator and a jailbreakable device. On Apple Configurator, prepare the device and select all the screens to skip during setup. This process will wipe the device so be ready to set it up again. After it is done, jailbreak the device and get the file from the following file path:
/var/containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles/Library/ConfigurationProfiles/SharedDeviceConfiguration.plist
You should now have every key you need to skip the setup process.
Important: You also need to set the value CloudConfigurationUIComplete
to true
otherwise you could get the device stuck on a bugged setup screen.
The file can then be restored using the following domain and path:
Domain: SysSharedContainerDomain-systemgroup.com.apple.configurationprofiles
Path: Library/ConfigurationProfiles/SharedDeviceConfiguration.plist
It should be noted that this may mess with existing configuration profiles. This should not be done on enterprise supervised devices.
In iOS 17.2, Apple introduced a new setup screen to deter the user from partial restores. This was presumably added because of Cowabunga Lite 😒
The screen was a warning about the security of partial restores, contained a giant blue shiny button with the words "Erase and Start Over" and tiny blue text below it saying "Continue with Partial Setup". Obviously not ideal for a customization tool like Cowabunga Lite where a user could accidentally wipe their entire phone.
On top of ruining Cowabunga Lite, Apple also ruined their enterprise app by doing this. They added no way to skip this screen, even for their own software. Sometimes, this screen would even show up after doing a full restore from iTunes. What's worse, the screen sometimes would never go away, even after a fresh wipe or restore. This was so bad that on occasion, it would get stuck in a loop on this screen and you would have to erase anyway. They felt so threatened over a goofy cow app that they made a mess of a half baked screen plagued with issues.
Apologies for the rant, but I did manage to find a way to bypass it. Using Managed Preferences, the setup information can be force overwritten and make iOS think the setup was already complete. This does not skip the other setup screens like CloudConfigurationDetails.plist
does but it still manages to skip the partial setup screen for some reason.
There are 2 keys in the plist that need to be set to true
: SetupDone
and SetupFinishedAllSteps
. After that, the plist should be restored to the following domain and path:
Domain: ManagedPreferencesDomain
Path: mobile/com.apple.purplebuddy.plist
This should now skip the partial setup screen.
The main domain for writing to /var/mobile
is HomeDomain
. While there are others that write to the mobile folder, HomeDomain
is where most of the files are backed up or restored to. It is where the preference files are stored and has some interesting modifications that can be done with it.
Cowabunga Lite utilizes this domain for both its webclip theming and changing the status bar overrides. Cowabunga Lite used to use this domain to modify preference files, but it has since changed to using Managed Preferences for its many benefits listed in the next section.
This is the most interesting domain. It takes priority of preference files stored in /var/mobile/Library/Preferences
. Values in a preference file will always choose the one in Managed Preferences over the one from the mobile file. In addition, Managed Preferences appends to the version of the file in mobile rather than overriding it. This means that changes can be set without having to read user preferences or reset them back to default, which would happen when using HomeDomain
.
Seems great, right? But that's not all it's capable of. Unlike HomeDomain
, ManagedPreferencesDomain
has no file restrictions when it comes to restoring, which means many files that were not intended to be modifiable can be changed.
One example is com.apple.iokit.IOMobileGraphicsFamily.plist
. This file contains the resolution of the device. This was clearly not meant to be touched, and Apple even prevented it from being restorable in HomeDomain
(which you can see if you look at RelativePathsNotToRestore
in Domains.md). Since there are no restrictions in ManagedPreferencesDomain
, the file can be restored there and the resolution of the device can be changed.
Disclaimer: I do not recommend changing this file unless you really know what you are doing. You can really mess up your device this way and it could lead to potential data loss.
As covered in Skipping the setup, com.apple.purplebuddy.plist
is another useful preference we can modify. It too is unable to be restored to HomeDomain
, but it is able to be restored to ManagedPreferencesDomain
without restriction.
All of these properties combined make this a very powerful domain for modifying files. It is probably the most powerful domain for restoring outside of exploits like Sparserestore.
The Sparserestore exploit is a very simple exploit at its core. It uses backpathing to continuously go back to the parent until it reaches root, allowing to write anywhere in the file system on the /var
partition that is not SSV protected.
Sparserestore works on 2 domains, SysContainerDomain
and SysSharedContainerDomain
. As you know from the "What is a domain?" section, these are special domains that utilize the -
character to determine the subdirectory. After the -
character, a backpath (..
) can be used to traverse up the tree. Using the /
character after the backpath, you can continuously traverse upwards until you hit root.
Take the following domain:
SysContainerDomain-../../../../../../../..
After -
, you are currently in the directory:
/private/var/.backup.i/var/root/Library/Backup/SystemContainers/Data/
Note: In the SysSharedContainersDomain
, you will be in the /Shared/
folder instead of the /Data/
folder.
This is your starting path. The first ../
means that you will be in the SystemContainers
folder. The second ../
will leave you in the Backup
folder. If you continue backpathing, you will end up in the directory /private
. This is its own partition separate from /var
, so we cannot exit this. However, we do not need to leave this as we can do everything we need in /private/var
anyway.
Once you reach root, you can place any file path afterwards. Though you can go straight to the file directly, it is better to restore to /var/backup
instead. This will have the same effect as restoring to the file directly, but will be less likely to fail since there will not already be a file at that path.
At the end of the restore, we can crash the restore by restoring a file directly to /private
. Normally, a crash would cancel the backup and no files would change. However, using the exploit, the files will still be restored to. This has the added benefit of skipping the setup entirely without having to modify files like Cowabunga Lite, which can break configuration profiles.
Since it takes place in the domain, listing every directory in the path is not necessary. All that is needed is the domain as a directory and the file.
Though not necessary for everything, inodes can be used to create a hard link to a different file. In Sparserestore, a temporary file is created containing the data contents that are intended to be restored. The inode value for this temporary file will be set intentionally when creating the restore. The file you are intending to restore should be added to the manifest next. The contents of the file should be null data, implemented as b""
in Python. The inode of this file should be set to the same value as the temporary file's inode. After this, the hard link needs to be broken. To break it, Sparserestore is used to navigate to the file location of the temp file in /var/.backup.i
and break it by setting the contents to null data. This is not needed for the majority of files. It is only needed when replacing an executable binary, as done by TrollRestore. You can see how this is implemented in TrollRestore's code here.
Sparserestore was "patched" in iOS 18.1 developer beta 5 (iOS 18.1 public beta 2).
To patch it, Apple restricted domains from containing a /
character. While the backpath still works, it can only go back a single path to the SystemContainers
folder. This is not useful for escaping the scope of the domain.
However, directory operations can still be useful for Nugget. While a normal restore to the domain SysSharedContainers-systemgroup.com.apple.mobilegestaltcache
will not allow restoring files to the Caches
folder, using directory operations in the domain seem to bypass this restriction.
There are two directory operations that work. The first is backpathing just a single directory, SysSharedContainers-..
, then restoring to the Shared/systemgroup.com.apple.mobilegestaltcache
folder from there. The better method is to just simply call the current directory, SysSharedContainers-.
, then restoring to systemgroup.com.apple.mobilegestaltcache
like normal.
Everything else Nugget does after the patch aside from mobilegestalt is just using regular domains just like Cowabunga Lite.
Apple fully patched sparserestore including the method above in iOS 18.2 developer beta 3.
This time, Apple fully restricted all types of path operations, making it impossible to backpath or call the current directory. This effectively stops mobilegestalt modifications from being applied because of the Cache folder exception.
This patch was at the same time that Apple Intelligence on unsupported devices was patched, a very popular feature in Nugget on 18.1. This patch was separate from the sparserestore patch, so even with mobilegestalt edit access, Apple Intelligence on unsupported devices cannot be achieved.
I hope to read this entirely eventually, nice.