Skip to main content

Data Protection

iOS and iPadOS leverage an API called "Data Protection" which is used to encrypt and protect each individual file of the app. The encryption keys are stored in the Secure Enclave Processor (SEP).

Data Protection Classes

Data Protection Classes:

ClassAPI Name
Class A: Complete ProtectionNSFileProtectionComplete
Class B: Protected Unless OpenNSFileProtectionCompleteUnlessOpen
Class C: Protected Until First User AuthenticationNSFileProtectionCompleteUntilFirstUserAuthentication
Class D: No ProtectionNSFileProtectionNone

For detailed information on each of these classes, see Apple's Security Guide:

https://support.apple.com/guide/security/data-protection-classes-secb010e978a/web

Dynamic Analysis Testing

Using Frida, we can interrogate the running the application to determine the Data Protection class of every file in the application. There is a Frida script available on BitBucket to assist with the evaluation.

  • Run Frida with the "-l" option and pass the ios-data-protection.js script

      frida -U -l ios-data-protection.js -f com.example.app
  • Once Frida is connected to the app, and the script has been injected, run the getDataProtectionKeysForAllPaths() function

    [iPhone::Mobile]-> getDataProtectionKeysForAllPaths()
  • This function will produce a lot of input, as it will evaluate every single file associated with the application

    % frida -U -l iosDataProtection.js -F
    ____
    / _ | Frida 12.11.17 - A world-class dynamic instrumentation toolkit
    | (_| |
    > _ | Commands:
    /_/ |_| help -> Displays the help system
    . . . . object? -> Display information about 'object'
    . . . . exit/quit -> Exit
    . . . .
    . . . . More info at https://www.frida.re/docs/home/
    [iPhone::Mobile]-> getDataProtectionKeysForAllPaths()
    [
    {
    "fileProtectionKey": "NSFileProtectionNone",
    "path": "/private/var/mobile/Containers/Data/Application/7A2A3930-A2A4-4F8B-B162-3DC35E5FA59B/StoreKit/receipt"
    },
    {
    "fileProtectionKey": "NSFileProtectionCompleteUntilFirstUserAuthentication",
    "path": "/private/var/mobile/Containers/Data/Application/7A2A3930-A2A4-4F8B-B162-3DC35E5FA59B/Documents/FSCalendar.fid"
    },

    etc…
  • Evaluate the output to determine if the established Data Protection class is appropriate for the file type that is displayed.

    • If the file contains data, such as PLIST's or SQLite databases, then there should be a protection class set.

    • If the files are benign, such as images, receipt, etc., then not having a Data Protection class set is fine.

Using the dataprotection Utility

Using the dataprotection binary, you can pull the data protection level for each file in the app, directly on the iOS device.

Usage:

iPad6-17:~ root# dataprotection 
Data Protection v1.1.0
Usage: dataprotection <bundle_id>

Example:

iPad6-17:~ root# dataprotection com.example.appname
-------------------------------------------------------------
Determining data protection for: com.example.appname
-------------------------------------------------------------
Logging to: /var/mobile/Documents/DataProtection_com.example.appname.log

Color Legend:
🟢 NSFileProtectionComplete
🟡 NSFileProtectionCompleteUntilFirstUserAuthentication
🟠 NSFileProtectionCompleteUnlessOpen
🔴 NSFileProtectionNone
⚪️ <none> or unknown


---- App Bundle ----
AppIcon76x76@2x~ipad.png -> NSFileProtectionNone
_CodeSignature -> NSFileProtectionNone
_CodeSignature/CodeResources -> NSFileProtectionNone
__preview.dylib -> NSFileProtectionNone
Info.plist -> NSFileProtectionNone
AppName -> NSFileProtectionNone
PkgInfo -> NSFileProtectionNone
Assets.car -> NSFileProtectionNone
embedded.mobileprovision -> NSFileProtectionNone
AppIcon60x60@2x.png -> NSFileProtectionNone
Bypasses.debug.dylib -> NSFileProtectionNone
[*] Brute-forcing container match for: io.stinger.Bypasses
[+] Found via loose match in: /var/mobile/Containers/Data/Application/ED7A6E80-E478-41F7-8EB7-E5C1E8706EC5/Library/Caches/io.stinger.Bypasses

---- Data: Documents ----

---- Data: Library ----
Caches -> NSFileProtectionComplete
Caches/io.stinger.Bypasses -> NSFileProtectionComplete
Caches/io.stinger.Bypasses/com.apple.metalfe -> NSFileProtectionComplete
Caches/com.apple.WebKit.GPU -> NSFileProtectionComplete
Caches/com.apple.WebKit.GPU/libraries.list -> NSFileProtectionComplete
Caches/com.apple.WebKit.GPU/functions.data -> NSFileProtectionNone
Caches/com.apple.WebKit.GPU/functions.list -> NSFileProtectionComplete
Caches/com.apple.WebKit.GPU/libraries.data -> NSFileProtectionNone
Caches/com.apple.WebKit.WebContent -> NSFileProtectionComplete
Caches/WebKit -> NSFileProtectionComplete
Caches/WebKit/NetworkCache -> NSFileProtectionComplete

. . . (truncated output)

This information is printed to the terminal screen, and also stored in a text file on the device. This location is shown at the end of the output. Additionally, there is a summary of each data protection level and the number of files using each level.

=== Summary ===
NSFileProtectionCompleteUntilFirstUserAuthentication: 15
NSFileProtectionCompleteUnlessOpen: 22
NSFileProtectionComplete: 131
NSFileProtectionNone: 13
<none>: 1

Done.
Log saved to: /var/mobile/Documents/DataProtection_com.example.appname.log