(Lazily) Making our own entitlements dumper
Goals
- Give a VERY, VERY basic rundown of Entitlements, won’t be super in-depth of how the system works
- Create our own dumper using existing System components
Introduction: The XNU Entitlements System
Apple’s Systems are known for being locked down & (literally) sandboxed, where no mere process can simply access the filesystem outside of that process’ own container directory which is created by the System (See: Sandbox.kext), or do stuff like accessing Camera/Microphone functions without user permissions, etc etc. One of the many ways this system of restrictions is enforced by is called ‘Entitlements’.
In simple terms, Entitlements describe the capabilities of a binary to the System by embedding a Property List (A serializable format, often being XML) into the binary itself. This concept is familiar to iOS & macOS developers who sometimes have to deal with it due to certain developer functionalities being locked being entitlements, and those entitlements are often (but not always) locked to paid Apple Developer accounts (ie, if you want to use Push Notifications in your iOS app, you must add an Entitlement through the Xcode IDE, available only if you have a paid Apple Developer Account).
The following image shows a list of Entitlements available to add to an application through through Xcode
Each Entitlement is named in Reverse domain name notation (ie, com.apple.whatever), and can have a value of a String, Bool, Integer, Float, Dictionary, or an Array. For example, here are the Entitlements of macOS’ TextEdit.app
, macOS’ built in text editor app, as a basic example:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.application-identifier</key>
<string>com.apple.TextEdit</string>
<key>com.apple.developer.ubiquity-container-identifiers</key>
<array>
<string>com.apple.TextEdit</string>
</array>
<key>com.apple.private.hid.client.event-dispatch.internal</key>
<true/>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.executable</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.print</key>
<true/>
</dict>
</plist>
Just to go over some Entitlements here:
com.apple.security.files.user-selected.read-write
: Since this value is set totrue
, it allowsTextEdit.app
to read and write files that were selected by the user to open in the app (ie, if you select test.txt to open in TextEdit, due to this Entitlement, TextEdit can read and write to that file, despite test.txt not being in TextEdit’s sandbox container directory, see Entitlement below.)com.apple.developer.ubiquity-container-identifiers
: This Entitlement describes the identifiers for iCloud containers that the application can manage
com.apple.security.app-sandbox
: Since this value is set totrue
, it states that the application’s read/write ability to the filesystem is confined to a directory created by the System and given to TextEdit, therefore, for security purposes, TextEdit cannot read/write outside of it’s given directory (Exclusions apply, see the other Entitlements explained.)com.apple.security.print
: Since this value is set totrue
, it allowsTextEdit.app
to access and call System printing APIs
Some Developer accounts are more (Entitled) than others
As mentioned above, some Entitlements are restricted only to Apple Developer paid accounts, which you must pay 100$ a year for to get access to, meaning that ultimately some API are just gated by paid Apple Developer Account (Apple MusicKit, Push Notification APIs).
Developers can (and have to) also apply for certain Entitlements, for example, creating a CarPlay application requires you submit an application form to Apple to get the required entitlements to create your CarPlay app.
However, above all others lies the one who has (and creates) all Entitlements: Apple themselves, as they can and do sign their applications with their undocumented Entitlements freely, their applications have the ability to do just about anything without the same restrictions that other apps face (ie, being unsandboxed & contacting System Daemons), something that no normal app on iOS could ever dream of doing.
The Dumper
Okay, we understand what Entitlements are and their purpose, now lets make our dumper that takes in a path to an executable and prints out the entitlements of that executable. First, let’s understand how where Entitlements even are in the first place
First, we need to understand that the Mach-O executable format is divided into 3 parts:
- Header
- Load Commands
- Segments
To understand where entitlements are located, we only need to understand the header and load commands, specifically, Entitlements are located in a specific load command, which we will go over.
Understanding (some of) the Mach-O Format
The beginning of a Mach-O starts with the header. The header consists of basic metadata necessary for dyld to process the executable, the layout can be seen here:
struct mach_header {
uint32_t magic; /* mach magic number identifier */
cpu_type_t cputype; /* cpu specifier */
cpu_subtype_t cpusubtype; /* machine specifier */
uint32_t filetype; /* type of file */
uint32_t ncmds; /* number of load commands */
uint32_t sizeofcmds; /* the size of all the load commands */
uint32_t flags; /* flags */
};
The header has the necessary information for us to cycle through the load commands of a Mach-O, of which one of them contains the Entitlements of the binary, being LC_CODE_SIGNATURE
After the header comes the Load Commands, which describe various metadata about the Mach-O executable, such as:
- Offset of the
main
entry point function of the program (LC_MAIN
) - Libraries to load (
LC_LOAD_DYLIB
) - Code signing info (
LC_CODE_SIGNATURE
)
A Load Command is garaunteed to have 2 properties:
struct load_command {
uint32_t cmd; /* type of load command */
uint32_t cmdsize; /* total size of command in bytes */
};
Entitlements are located in the code signing Load Command, LC_CODE_SIGNATURE
, meaning that we’d have to iterate over all Load Commands of the Mach-O till we find the one marked as LC_CODE_SIGNATURE
(by checking for if the cmd
property is equal to LC_CODE_SIGNATURE
), and find the Entitlement there by going through that Load Command’s layout.
Creating the dumper
Usually, here we would write a parser for Mach-O to go over the Mach-O header and Load Commands etc etc. However, as a wise Chinese man once said:
If there’s a will, there is most probably a private API to the way. (~Sun Tzu, The Art of War)
Sun Tzu was correct. One day I happened to be scrolling past the PrivateFrameworks folder on macOS (/System/Library/PrivateFrameworks
), where all private system frameworks are listed, and came across one by the name of AppSandbox.framework
, which caught my eye and later dropped it into IDA to check that it had various classes, but the one that caught my eye the most was AppSandboxEntitlements
, with an interface of:
// Reverse engineered from Apple's private AppSandbox.framework
@interface AppSandboxEntitlements : NSObject
+ (AppSandboxEntitlements *)entitlementsForCodeAtURL:(NSURL *)appURL error:(NSError **)error; // Constructor
- (NSDictionary *)allEntitlements;
@end
So, one would think that you could just create an instance of AppSandboxEntitlements
with the entitlementsForCodeAtURL
method and then use the allEntitlements
method to get the entitlements, right? Right?
Well, yes! As a matter of fact, this hidden API makes it as easy as just:
// lets pick a random system application which contains some special entitlements..
// say, Safari
NSURL *url = [NSURL fileURLWithPath: @"/Applications/Safari.app"];
AppSandboxEntitlements *grabber = [AppSandboxEntitlements entitlementsForCodeAtURL: url error: nil];
NSDictionary *dict = [grabber allEntitlements];
And that’s it! Now we have the Entitlements in the form of a dictionary and can dump the Entitlements of any binary.
A demonstration project is available on Github .