Chapter 5

Sandboxing

iOS provides multiple layers of exploitation mitigation. Data Execution Prevention (DEP) and Address Space Layout Randomization (ASLR) increase the investment required to gain code execution, but other mitigations are necessary to limit damage in case code execution is realized. Apple's iOS sandbox, descending from a similar system found in OS X, provides one method to limit the actions performed by a process.

The goal of the sandbox is to limit post-code-execution actions by providing an interface for bounding the behavior of a process. Imagine a PDF rendering application: One subsystem of the application parses the opened file to produce an internal representation. Another subsystem, in charge of rendering this document to the screen, consumes this internal representation. Because the parsing subsystem is most vulnerable to attack when it processes user-supplied input, it needs access to the input file and little else. By preventing this subsystem from opening other files, executing other programs, or using the network, an attacker's actions post-code-execution are limited. In theory, this is straightforward and easy to implement; in practice, bounding the expected behavior of a process is difficult and prone to error.

This chapter discusses the design and implementation of the iOS sandbox. By stepping through the code used to configure and enforce the profile for a given process, you gain the knowledge needed to perform more advanced audits of the iOS sandbox enforcement system. Most of the chapter is spent discussing the undocumented parts of the system.

Understanding the Sandbox

Originally codenamed “Seatbelt,” the Apple sandbox first existed on OS X. Just like AMFI, discussed in Chapter 4, it is implemented as a policy module for the TrustedBSD mandatory access control (MAC) framework. TrustedBSD was ported from FreeBSD to the XNU kernel. The sandbox framework adds significant value by providing a user space configurable, per-process profile on top of the TrustedBSD system call hooking and policy management engine. In other words, TrustedBSD provides the hooking, but the sandbox provides the brains to enforce a configured profile.

The sandbox is made up of the following components:

  • A set of user space library functions for initializing and configuring the sandbox
  • A Mach server for handling logging from the kernel and holding prebuilt configurations
  • A kernel extension using the TrustedBSD API for enforcing individual policies
  • A kernel support extension providing a regular expression engine for evaluating some policy restrictions during enforcement

Figure 5.1 shows how these components are related.

Figure 5.1 Components of the iOS sandbox

5.1

Sandboxing an application begins with a call to the libSystem function sandbox_init. This function uses the libsandbox.dylib library to turn a human-readable policy definition (describing rules similar to “don't allow access to files under /opt/sekret”) into a binary format that the kernel expects. This binary format is passed to the mac_syscall system call handled by the TrustedBSD subsystem. TrustedBSD passes the sandbox initialization request to the Sandbox.kext kernel extension for processing. The kernel extension installs the sandbox profile rules for the current process. Upon completion, a successful return value is passed back out of the kernel.

Once the sandbox is initialized, many of the function calls hooked by the TrustedBSD layer pass through Sandbox.kext for policy enforcement. Depending on the system call, the extension consults the list of rules for the current process. Some rules (such as the example given previously of denying access to files under the /opt/sekret path) require pattern-matching support. Sandbox.kext imports functions from AppleMatch.kext to perform regular expression matching on the system call arguments against the patterns used in the policy rules. For example, does the path passed to open() match the denied path /opt/sekret/.*? The final component, sandboxd, listens for Mach messages used to carry tracing and logging information (such as which operations are being checked) and requests for prebuilt profiles (such as “block all network usage” or “don't allow anything but computation”), which are hard-coded into the kernel.

The following sections step you through each component just discussed in greater detail. You start in userspace and work your way down to the kernel components. Throughout the discussion, you'll be using the binaries unpacked from the iPhone3,1_5.0_9A334 firmware. For details on unpacking the kernelcache and root filesystem (for the dyld cache), see Chapter 10. Any discussion of the XNU kernel should use both analysis of the binary firmware and the open source code available in xnu-1699.24.8. This is the closest available version of the xnu source to the firmware in question. Also, you can download any sample code throughout this chapter at the book's companion website at www.wiley.com/go/ioshackershandbook.

Sandboxing Your Apps

With the creation of the App Store and the release of OS X 10.7 Lion, the sandbox extensions used by iOS have received more documentation. Prior to 10.7, the iOS sandbox included more features than the versions shipped with OS X, but with little information publicly available. The concepts discussed in the Application Sandbox Design Guide (https://developer.apple.com/library/mac/#documentation/Security/Conceptual/AppSandboxDesignGuide/AboutAppSandbox/AboutAppSandbox.html) complement this chapter, and Apple has taken care to note many of the iOS differences. The Apple Sandbox Design Guide is higher level, but the concepts introduced remain useful.

The iPhone 5.0 SDK contains the sandbox.h header exposing the userspace interface of the sandbox. The example begins by looking at the three functions used for initializing a sandbox: sandbox_init, sandbox_init_with_parameters, and sandbox_init_with_extensions.

sandbox_init configures the sandbox of the calling process given a profile. sandbox_init takes a profile, a set of flags, and an output argument for storing a pointer to an error message. The profile, or set of rules for restricting a process, can be provided in a few different ways depending on the flags passed to the function. The only publicly supported flag, SANDBOX_NAMED, expects a string passed in the profile argument selecting a built-in profile such as “no-internet.” The sample program here uses this option to restrict a spawned shell from using the Internet:

#include <stdio.h>
#include <sandbox.h>

int main(int argc, char *argv[]) {
    int rv;
    char *errbuff;

    //rv = sandbox_init(kSBXProfileNoInternet, SANDBOX_NAMED_BUILTIN,
 &errbuff);
    rv = sandbox_init("nointernet", SANDBOX_NAMED_BUILTIN, &errbuff);
    if (rv != 0) {
        fprintf(stderr, "sandbox_init failed: %s
", errbuff);
        sandbox_free_error(errbuff);
    } else {
        printf("pid: %d
", getpid());
        putenv("PS1=[SANDBOXED] \h:\w \u\$ ");
        execl("/bin/sh", "sh", NULL);
    }

    return 0;
}

Before running this example, ensure that your jailbroken device has installed ping from the inetutils package. The /bin/ping executable will also need the sticky bit removed using the command /chmod –s /bin/ping. The following is a transcript of the preceding program showing the sandbox blocking a ping request as expected:

iFauxn:∼/ioshh root# ./sb1
pid: 5169
[SANDBOXED] iFauxn:∼/ioshh root# ping eff.org
PING eff.org (69.50.232.52): 56 data bytes
ping: sendto: Operation not permitted
ˆC--- eff.org ping statistics ---
0 packets transmitted, 0 packets received,
[SANDBOXED] iFauxn:∼/ioshh root# exit
iFauxn:∼/ioshh root# ping eff.org
PING eff.org (69.50.232.52): 56 data bytes
64 bytes from 69.50.232.52: icmp_seq=0 ttl=46 time=191.426 ms
ˆC--- eff.org ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max/stddev = 191.426/191.426/191.426/0.000 ms
iFauxn:∼/ioshh root#

One thing to note about this sample program is the commented-out line using a documented constant, kSBXProfileNoInternet, as the name for the profile. The constants defined in the header are not compatible with the iOS sandbox. For example, the kSBXProfileNoInternet constant will resolve to “no-internet” on both iOS and OS X. Unfortunately, on iOS, the profile name should be “nointernet”.

In addition to named built-in profiles, sandbox_init supports the specification of custom fine-grained restrictions using a Scheme-embedded domain-specific language called Sandbox Profile Language (SBPL). Using the flag SANDBOX_NAMED_EXTERNAL, sandbox_init expects a path to a sandbox profile language script file to be passed as an argument. If the path is not absolute, three different locations are tried as base paths prefixing the relative path given:

_cstring:368FB90A aLibrarySandbox DCB "/Library/Sandbox/Profiles",0
_cstring:368FB924 aSystemLibraryS DCB "/System/Library/Sandbox/Profiles",0
_cstring:368FB945 aUsrShareSandbo DCB "/usr/share/sandbox",0

In addition to SANDBOX_NAMED_EXTERNAL, a flag's value of 0 may be passed to sandbox_init along with the SBPL script in the profile argument directly. Apple has provided no documentation on the SBPL, but the full definition of the language, a Scheme script itself, is easily extractable from libsandbox.dylib (available from the dyld cache in the firmware). Fortunately, fG! has created the Apple Sandbox Guide (http://reverse.put.as/2011/09/14/apple-sandbox-guide-v1-0/) to document the SBPL as implemented in OS X. Much of this guide is applicable to iOS but it does not include some of the newer features of SBPL (such as extension filters).

There is also one example of an .sb SBPL script in the firmware we're using: ftp-proxy.sb is found in /usr/share/sandbox. Following is an excerpt of this profile to give you an idea of the format before continuing on to a full example:

(deny default)
...
(allow file-read-data
    (literal "/dev/pf")
    (literal "/dev/random")
    (literal "/private/etc/master.passwd"))

(allow file-read-metadata
    (literal "/etc"))

(allow file-write-data
    (literal "/dev/pf"))

The profile language is mostly intuitive. This script sets the default action to deny any access, locking down the process applying this profile. After removing all privileges, specific actions are explicitly allowed, such as reads from the password file (presumably for authentication actions required by the FTP proxy). To try out your own sandbox profile, create a small profile to restrict access to two specific files under /tmp:

(version 1)

(allow default)

(deny file-read-data
    (literal "/private/var/tmp/can_w"))

(deny file-write-data
    (literal "/private/var/tmp/can_r"))

To test this profile, copy the previous example that denied Internet access and change the sandbox_init call to use the SANDBOX_NAMED_EXTERNAL option:

    rv = sandbox_init("sb2", SANDBOX_NAMED_EXTERNAL, &errbuff);

You'll also need to copy the.sb script presented earlier to /usr/share/sandbox (or a similar directory in the search path) or give an absolute path in the sandbox_init argument. Here, the transcript shows the custom SBPL restricting access to files based on path:

iFauxn:∼/ioshh root# echo "w" > /private/var/tmp/can_w
iFauxn:∼/ioshh root# echo "r" > /private/var/tmp/can_r
iFauxn:∼/ioshh root# ./sb2
pid: 5435
[SANDBOXED] iFauxn:∼/ioshh root# cat /private/var/tmp/can_w
cat: /private/var/tmp/can_w: Operation not permitted
[SANDBOXED] iFauxn:∼/ioshh root# cat /private/var/tmp/can_r
r
[SANDBOXED] iFauxn:∼/ioshh root# echo "IOSHH" >> /private/var/tmp/can_w
[SANDBOXED] iFauxn:∼/ioshh root# echo "IOSHH" >> /private/var/tmp/can_r
sh: /private/var/tmp/can_r: Operation not permitted
[SANDBOXED] iFauxn:∼/ioshh root# exit
iFauxn:∼/ioshh root#

As expected, read access to can_w is blocked, but write access is allowed. can_r is flipped; you can read but not write.

Like sandbox_init, the other two functions used for initialization take the same three parameters. They also take a fourth parameter that points to an array of strings. init_sandbox_with_parameters is used to pass a list of parameters to the Scheme interpreter when evaluating the SBPL script. This feature is useful in a similar manner as the C preprocessor. All parameters must be specified at initialization time.

The extensions passed into the final initialization function, via init_sandbox_with_extensions, are quite different from the parameters mentioned previously. Extensions are commonly base paths and may be dynamically added to a process. Unlike parameters, the extension logic is built into the kernel enforcement; each process maintains a list of extension strings currently held and the sandbox consults this list when certain SBPL filters are encountered in the profile rules. init_sandbox_with_extensions is used to specify a list of extensions needed by the process immediately.

You use a two-step procedure to dynamically add an extension to a process. First, you issue an extension by calling sandbox_issue_extension with the path to add and a pointer to hold an output token. This token is then consumed using sandbox_consume_extension to install this extension in a process. The issuing process need not be the same as the consuming process. A parent process that is communicating with a sandboxed child may issue extensions to the child based on an internal policy, for example. The SBPL provides a way to restrict the sandbox_issue_extension operation. Without this restriction, a sandboxed child process would be able to issue itself any extension it wanted, rendering this feature useless.

Take a look at another example to illustrate the use of extensions:

#include <stdio.h>
#include <sandbox.h>

int main(int argc, char *argv[]) {
        int rv;
        char sb[] =
                "(version 1)
"
                "(allow default)
"
                "(deny file-issue-extension*)
"
                "(deny file-read-data
"
                "       (regex #"/private/var/tmp/container/"
                                 "([0-9]+)/.*"))
"
                "(allow file-read-data
"
                "       (require-all
"
                "           (extension)
"
                "           (regex #"/private/var/tmp/container/"
                                     "([0-9]+)/.*")))
";
        char *errbuff;

        char *token;
        token = NULL;
        rv = sandbox_issue_extension(
                        "/private/var/tmp/container/1337", &token);
        if (rv == 0 && token) {
                printf("Issued extension token for "
                       ""/private/var/tmp/container/1337":
");
                printf("  %s
", token);
        } else {
                printf("sandbox_issue_extension failed
");
        }

        const char *exts[] = { argv[1] };
        printf("Applying sandbox profile:
");
        printf("%s", sb);
        printf("
");
        printf("With extensions: { "%s" }
", exts[0]);
        printf("
");
       
        rv = sandbox_init_with_extensions(sb, 0, exts, &errbuff);
        if (rv != 0) {
                fprintf(stderr, "sandbox_init failed: %s
", errbuff);
                sandbox_free_error(errbuff);
        } else {
                putenv("PS1=[SANDBOXED] \h:\w \u\$ ");

                printf("Attempting to issue another extension after"
                       "applying the sandbox profile...
");
                char *token2 = NULL;
                rv = sandbox_issue_extension(
                        "/private/var/tmp/container/1337",
                        &token2);
                if (rv == 0 && token) {
                        printf("Issued extension token for "
                               ""/private/var/tmp/container/1337":
");
                        printf("  %s
", token);
                } else {
                        printf("sandbox_issue_extension failed
");
                }

                system("/bin/sh");
                printf("
Consuming the extension, then starting another "
                       "shell...

");
                sandbox_consume_extension(
                        "/private/var/tmp/container/1337", token);
                system("/bin/sh");
        }

        return 0;
}

In this example, the goal is to create a profile that enables you to add allowed subpaths at run time. To accomplish this, you first deny all read-data access to paths under /private/var/tmp/container containing 1 or more digits. Following the denial of read-data, you add an allow read-data that applies only if the target path is both under one of the processes extensions and under /private/var/tmp/container. You also deny access to the sandbox_issue_extension function. Before initializing the sandbox, the first extension is issued for the 1337 subdirectory. The returned token is saved. The sandbox is then initialized with a single extension taken from the first command-line argument. Before launching a shell, you try to issue an extension from under the sandbox to prove sandbox_issue_extension has been denied by the profile. After the first shell is exited, the 1337 extension is consumed and a new shell is launched. Following is a transcript of this program:

iFauxn:∼/ioshh root# ./sb4 /private/var/tmp/container/5678

Issued extension token for "/private/var/tmp/container/1337": 
000508000d0000000000000000021f002f707269766174652f7661722f746d70
2f636f6e7461696e65722f31333337000114007d00c6523ef92e76c9c0017fe8
f74ad772348e00

Applying sandbox profile:
(version 1)
(allow default)
(deny file-issue-extension*)
(deny file-read-data
        (regex #"/private/var/tmp/container/([0-9]+)/.*"))
(allow file-read-data
        (require-all
           (extension)
           (regex #"/private/var/tmp/container/([0-9]+)/.*")))

With extensions: { "/private/var/tmp/container/5678" }

Attempting to issue another extension after applying the sandbox profile...
sandbox_issue_extension failed

sh-4.0# cat / private/var/tmp/container/1234/secret
cat: ./container/1234/secret: Operation not permitted
sh-4.0# cat /private/var/tmp/container/5678/secret
Dr. Peter Venkman: Human sacrifice, dogs and cats living together
... mass hysteria!
sh-4.0# cat /private/var/tmp/container/1337/secret
cat: ./container/1337/secret: Operation not permitted
sh-4.0# exit

Consuming the extension, then starting another shell...

sh-4.0# cat /private/var/tmp/container/1234/secret
cat: ./container/1234/secret: Operation not permitted
sh-4.0# cat /private/var/tmp/container/5678/secret
Dr. Peter Venkman: Human sacrifice, dogs and cats living together... mass
hysteria!
sh-4.0# cat /private/var/tmp/container/1337/secret
Dr. Peter Venkman: You're not gonna lose the house, everybody has three
mortgages nowadays.
sh-4.0# exit
iFauxn:∼/ioshh root#
iFauxn:∼/ioshh root# cat /private/var/tmp/container/1234/secret
Dr. Ray Stantz: Total protonic reversal.
iFauxn:∼/ioshh root#

What has occurred in the transcript and how does it relate to the profile that was created? In the transcript, the program is started with the command-line argument /private/var/tmp/container/5678. This is used in the sandbox_init_with_extensions call. The first output you see is the result of a sandbox_issue_extension. The extension is issued for the 1337 subdirectory and occurs prior to sandbox initialization. After the sandbox_init_with_extension output confirms which profile is used, you see that the sandbox_issue_extension fails as expected. Inside the first shell, the only successful read of the three attempted is the one under the 5678 subdirectory added as an extension during initialization. The second shell is executed after consuming the 1337 extension. As expected, both the 1337 and 5678 reads are allowed. After exiting the sandbox, you verify that the 1234 file exists and is readable. This example illustrates how extensions are used to modify the sandbox profile dynamically after initialization. If this isn't completely clear, it will make more sense when you learn how the App Store applications are sandboxed in the “How Sandboxing Impacts App Store versus Platform Applications” section later in this chapter.

The examples here demonstrated the exposed functions for initializing and manipulating the configuration of a sandbox. The first example illustrated the use of a prebuilt named profile. You also looked at the SBPL and the construction of a custom sandbox profile. The last example demonstrated the use of extensions for dynamically modifying access after initializing a sandbox. Later in this chapter, you discover how App Store applications and platform applications (such as MobileSafari) interact with the sandbox system; surprisingly, neither class of application uses the interfaces enumerated so far! Before discussing these applications, the next section gives you a more detailed understanding of the implementation of the sandbox enforcement mechanisms.

Understanding the Sandbox Implementation

The sandbox is composed of kernel and user space components. The previous section discussed the library calls used in the initialization of the sandbox. This section explains the process that ties together the function calls discussed earlier and the system call interface exposed by the sandbox kernel extension while it resides in the kernel. In addition to exposing a configuration interface, the kernel module also plays the role of gatekeeper. It inspects the operations requested by a process and evaluates these against the sandbox profile associated with the process. You'll examine this kernel extension to understand how the TrustedBSD component of the XNU kernel is used. Finally, you'll walk through the processing of a system call as handled by the sandbox TrustedBSD policy.

Understanding User Space Library Implementation

To explain the user space library implementation, you trace the processing path from the exposed functions to the system call in libSystem. Gaining a handhold to begin isn't difficult. You use the dyldinfo utility from the iPhone SDK (the OS X version will also work). You can determine which shared library is linked for the sandbox_init symbol and start reversing from there. The output when you run the first example of the chapter is shown here:

pitfall:sb1 dion$ dyldinfo -lazy_bind sb1
lazy binding information (from section records and indirect symbol table):
segment section          address    index  dylib            symbol
_DATA  _la_symbol_ptr  0x00003028 0x000B libSystem        _execl
_DATA  _la_symbol_ptr  0x0000302C 0x000D libSystem        _fprintf
_DATA  _la_symbol_ptr  0x00003030 0x000E libSystem        _getpid
_DATA  _la_symbol_ptr  0x00003034 0x000F libSystem        _printf
_DATA  _la_symbol_ptr  0x00003038 0x0010 libSystem        _putenv
_DATA  _la_symbol_ptr  0x0000303C 0x0011 libSystem        _sandbox_free_error
_DATA  _la_symbol_ptr  0x00003040 0x0012 libSystem        _sandbox_init

Predictably, sandbox_init is linked via libSystem. iOS uses a prelinked version of most of the shared libraries used by the system. To analyze the system libraries, you need to extract each from this cache. You can access the cache either by unencrypting the root filesystem image in the firmware package (the IPSW) or by copying it from a previously jailbroken phone. You can find the shared cache at /System/Library/Caches/com.apple.dyld/dyld_shared_cache_armv7. Recent versions of IDA Pro can parse this file directly and extract the target library for analysis. If you don't have access to a recent IDA Pro or would rather not use it, there is an open source tool for extracting libraries called dyld_decache available at https://github.com/kennytm/Miscellaneous/blob/master/dyld_decache.cpp. Other options exist; check http://theiphonewiki.com/wiki/ for details.

If you're playing along at home, try extracting the following libraries: /usr/lib/system/libsystem_sandbox.dylib, /usr/lib/system/libsystem_kernel.dylib, and /usr/lib/libsandbox.1.dylib. The first, libsystem_sandbox.dylib, is what you start with. Figure 5.2 shows the exported symbols defined in libsystem_sandbox. Those match the sandbox.h definitions exactly. Confident that you've found the right library, you can start digging into the disassembly for sandbox_init and its child functions to find how data enters into the kernel.

Figure 5.2 libsystem_sandbox.dylib exported functions

5.2

A quick inspection of sandbox_init reveals that it is just a proxy function to sandbox_init_internal. Examining sandbox_init_with_params and sandbox_init_with_extensions reveals the same thing; these three functions share a common implementation. sandbox_init_internal shows a much more interesting call graph. The prototype for sandbox_init_internal looks like this:

int sandbox_init_internal(const char *profile, uint64_t flags, const char* const
        parameters[], const char* const extensions[], char **errorbuf);

First, this function converts the string arrays representing the parameters and extensions into the libsandbox format. To do this, sandbox_init_internal dynamically loads the libsandbox.1.dylib library and resolves function calls (sandbox_create_params, sandbox_set_param, sandbox_create_extensions, and sandbox_add_extension) as needed. Following these two conversions, the function multiplexes on the flags value:

  • If flags == 0, sandbox_compile_string is called, followed by sandbox_apply and sandbox_free_profile. This functionality is not documented in the sandbox.h header.
  • If flags == SANDBOX_NAMED, sandbox_compile_named is called, followed by sandbox_apply and sandbox_free_profile.
  • If flags == SANDBOX_NAMED_BUILTIN, _sandbox_ms is called directly.
  • If flags == SANDBOX_NAMED_EXTERNAL, sandbox_compile_file is called followed by sandbox_apply and sandbox_free_profile.

Again, the needed functions (excluding _sandbox_ms) are dynamically loaded from libsandbox.1.dylib. Under most circumstances, sandbox_init_internal sets up the parameters for a call to sandbox_compile_* and then sandbox_apply. The SANDBOX_NAMED_BUILTIN case is slightly different. It calls _sandbox_ms instead of a function from libsandbox. _sandbox_ms, found in libsystem_kernel.dylib, is the end of the line for user space. It traps to the kernel using the mac_syscall syscall. This is a system call defined by the TrustedBSD subsystem (more about this later):

_text:31D5DBA8                 EXPORT _sandbox_ms
_text:31D5DBA8 _sandbox_ms
_text:31D5DBA8                 MOV             R12, 0x17D ; _mac_syscall
_text:31D5DBA8                                         ; _sandbox_ms
_text:31D5DBA8                                         ; _mac_syscall
_text:31D5DBB0                 SVC             0x80

So far, you've found the kernel entry point for one of the possible paths from sandbox_init. Now, you examine the libsandbox library to determine what the other paths look like and how they enter the kernel. You focus on the sandbox_compile_* and sandbox_apply functions. The sandbox_create_extensions and sandbox_create_parameters functions are just managing list structures (that is, they're boring).

Both sandbox_compile_string and sandbox_compile_file end with a call to compile, an internal function. sandbox_compile_string is a straight proxy to the compile function, but sandbox_compile_file first checks an on-disk cache. On iOS, the base path for the cache is left undefined and the caching code is never utilized. On OS X, where this feature is used, if the file exists and is found in the cache, the compiled profile is loaded and the function returns. For the purposes of this book (since we are only concerned with iOS), compile is always called on the file contents.

sandbox_compile_named searches a list of built-in names. If the argument matches one of them, it is copied to the structure to be passed to sandbox_apply. If the passed-in name doesn't match a known profile, sandbox_compile_file is tried before failing. That covers the sandbox_compile_* functions called by the initialization functions.

The compile function turns a sandbox profile into a data structure to send to the kernel. Most of the meaningful processing on the user space side of the sandbox is done via this function. compile uses TinyScheme, an open source Scheme interpreter, to evaluate the SBPL scripts. Prior to loading the SBPL to compile, three different Scheme scripts are loaded. The first is the TinyScheme initialization script. Scheme is known for its small core language with much of the traditional runtime language built on top of the core. The second script, sbpl1_scm, defines version 1 (and the only public version) of the SBPL language. This script is what you want to read if you have any questions regarding the details of the SBPL. The third script, sbpl_scm, is a stub to allow for multiple versions of the SBPL to be loaded; currently, it defines the version function used at the top of any SBPL scripts to load the correct SBPL prelude (like sbpl1_scm). This stub script contains a header comment describing the result of the SBPL evaluation. This script is easy to find in the libsandbox.dylib IDA disassembly; even easier is running strings on the dylib. The three Scheme scripts will be easy to spot:

;;;;;; Sandbox Profile Language stub

;;; This stub is loaded before the sandbox profile is evaluated.  When version
;;; is called, the SBPL prelude and the appropriate SBPL version library are
;;; loaded, which together implement the profile language.  These modules build
;;; a *rules* table that maps operation codes to lists of rules of the form
;;;   RULE -> TEST | JUMP
;;;   TEST -> (filter action . modifiers)
;;;   JUMP -> (#f . operation)
;;; The result of an operation is decided by the first test with a filter that
;;; matches.  Filter can be #t, in which case the test always matches.  A jump
;;; causes evaluation to continue with the rules for another operation.  The
;;; last rule in the list must either be a test that always matches or a jump.

The end result is a list of rules stored in the *rules* vector. To check if an operation is permitted, the kernel enforcement module consults the *rules* vector. The index checked corresponds to the operation being tested. For example, for iOS 5.0, the file-read-data operation is 15. If the 16th entry in *rules* is (#f . 0), any check for the operation file-read-data would cascade to the default rule (the default operation is index 0). This corresponds to the JUMP case described in the comment. An entry can contain a list of rules. In this case, each rule is evaluated in order until one is matched. The end of the list always contains a JUMP rule with no filter in case no rule has matched. The SBPL is a language designed to compile down to this decision tree. Once this tree is derived, the compile function in libsandbox flattens it and emits it as the profile bytecode to be delivered to the kernel.

sandbox_apply is the other main function called via the initialization functions in libsystem. sandbox_apply is passed the structure created by the compile functions. This structure contains the name of a built-in profile or the compiled bytecode of a custom profile. It also might contain a path to store a trace of the operations as they are checked. Looking at sandbox_apply, you see two main paths both ending with a call to _sandbox_ms. One path opens the trace file and looks up the Mach port for com.apple.sandboxd. The other jumps right to the call to the kernel. Now, all initialization flows through the same kernel entry point.

The other configuration functions discussed in the first part of the chapter, such as the extension issue/consume functions, call _sandbox_ms directly. At this point, you can be confident that all user data enters the kernel via mac_syscall.

Into the Kernel

The sandbox kernel extension is implemented as a TrustedBSD policy extension. Both the configuration and profile enforcement systems are implemented in this kernel extension. First, you learn about the TrustedBSD system and what it provides. Next, you learn how to connect the mac_syscall to the sandbox kernel extension, revealing the path it takes through the kernel and where it is handled in the sandbox. Finally, the path of an everyday syscall is highlighted and the sandbox enforcement mechanism is explained.

If you plan to follow along at home, you should extract and decrypt the kernelcache from the firmware package. Full instructions on how to accomplish this are included in Chapter 10. Predictably, this chapter focuses on the com.apple.security.sandbox kernel extension. (In iPhone3,1_5.0_9A334, this extension starts at 0x805F6000.)

Implementing a TrustedBSD Policy

TrustedBSD is a framework for implementing pluggable, composable access control policies in the kernel. The framework is composed of the inspection points placed throughout the kernel and the logic to register a policy to react to these events. TrustedBSD is called during many system calls and, if the policy has requested it, will check for permission before allowing further execution of the system call. Recall that this is the way code signing is enforced as well (see Chapter 4). The framework also provides a method for labeling objects with policy-specific information. As you will see, this mechanism is used to store the sandbox profile for each process. Only portions of this extensive framework are used by the sandbox policy extension.

The kernel source implementing TrustedBSD in XNU is located under xnu-1699.24.8/security. The interface for implementing a new policy module is exposed via mac_policy.h:

/**
  @file mac_policy.h
  @brief Kernel Interfaces for MAC policy modules

  This header defines the list of operations that are defined by the
  TrustedBSD MAC Framwork on Darwin.  MAC Policy modules register
  with the framework to declare interest in a specific set of
  operations.  If interest in an entry point is not declared, then
  the policy will be ignored when the Framework evaluates that entry
  point.
*/

This header contains thorough documentation and you should read it over if you're interested in understanding the full capabilities of a TrustedBSD policy. For this example, you should skip to the registration function, mac_policy_register:

/**
   @brief MAC policy module registration routine

   This function is called to register a policy with the
   MAC framework.  A policy module will typically call this from the
   Darwin KEXT registration routine.
 */
int     mac_policy_register(struct mac_policy_conf *mpc,
    mac_policy_handle_t *handlep, void *xd);

As noted in the comment, this function is usually called from the kext_start function of a policy extension module. Indeed, the sandbox extension in iOS calls mac_policy_register on start:

_text:805F6DD0 sub_805F6DD0
_text:805F6DD0         PUSH            {R7,LR} ; Push registers
_text:805F6DD2         MOV             R7, SP  ; Rd = Op2
_text:805F6DD4         LDR             R0, =(sub_805FC498+1) ; Load from Memory
_text:805F6DD6         BLX             R0 ; sub_805FC498
_text:805F6DD8         CMP             R0, #0  ; Set cond. codes on Op1 - Op2
_text:805F6DDA         IT NE                   ; If Then
_text:805F6DDC         POPNE           {R7,PC} ; Pop registers
_text:805F6DDE         LDR             R0, =off_805FE090 ; Load from Memory
_text:805F6DE0         MOVS            R2, #0  ; xd
_text:805F6DE2         LDR             R1, =dword_805FE6C0 ; Load from Memory
_text:805F6DE4         ADDS            R0, #4  ; mpc
_text:805F6DE6         LDR             R3, =(_mac_policy_register+1)
_text:805F6DE8         ADDS            R1, #4  ; handlep
_text:805F6DEA         BLX             R3 ; _mac_policy_register
_text:805F6DEC         POP             {R7,PC} ; Pop registers
_text:805F6DEC ; End of function sub_805F6DD0

The first argument of the register call is a pointer to a structure, mac_policy_conf, configuring the policy:

struct mac_policy_conf {
        const char              *mpc_name;           /** policy name */
        const char              *mpc_fullname;       /** full name */
        const char              **mpc_labelnames;    /** managed label
namespaces */
        unsigned int             mpc_labelname_count;  
                                           /** number of managed label
namespaces */
        struct mac_policy_ops   *mpc_ops;            /** operation vector */
        int                      mpc_loadtime_flags;  /** load time flags */
        int                     *mpc_field_off;       /** label slot */
        int                      mpc_runtime_flags;   /** run time flags */
        mpc_t                    mpc_list;           /** List reference */
        void                    *mpc_data;           /** module data */
};

In the iOS extension, this structure is located at off_805FE094, as shown in the call to mac_policy_register. If you want to try this yourself, you should import the mac_policy_conf and mac_policy_ops structures into IDA Pro. Following is the mac_policy_conf structure found in my firmware:

_data:805FE094 sbx_mac_policy_conf DCD aSandbox_0      ; mpc_name ;
"Sandbox"
_data:805FE094                 DCD aSeatbeltSandbo     ; mpc_fullname
_data:805FE094                 DCD off_805FE090        ; mpc_labelnames
_data:805FE094                 DCD 1                   ; mpc_labelname_count
_data:805FE094                 DCD sbx_mac_policy_ops  ; mpc_ops
_data:805FE094                 DCD 0                   ; mpc_loadtime_flags
_data:805FE094                 DCD dword_805FE6C0      ; mpc_field_off
_data:805FE094                 DCD 0                   ; mpc_runtime_flags
_data:805FE094                 DCD 0                   ; mpc_list
_data:805FE094                 DCD 0                   ; mpc_data

The configuration contains a unique name to use for the TrustedBSD policy (“Sandbox”) along with a longer description (“Seatbelt sandbox policy”). It also contains a pointer to another structure containing a list of function pointers. This structure, mac_policy_ops, is used to request callbacks for various events TrustedBSD is monitoring. You can find the full structure definition at xnu-1699.24.8/security/mac_policy.h:5971. As defined in the previous mac_policy_conf, the iOS mac_policy_ops structure is found at 0x805FE0BC (defined as sbx_mac_policy_ops in my IDB). The policy operations structure gives all of the entry points into the sandbox policy extension. In the next two subsections, you look at two functions in this structure: the mpo_policy_syscall function, used to configure a process, and one of the mpo_xxx_check_yyy calls used to validate an operation prior to allowing it.

Handling Configuration from User Space

You previously looked at the interface TrustedBSD exposes to a policy extension. Now, you look at the interface TrustedBSD exposes to user space. This interface is defined in xnu-1699.24.8/security/mac.h and is exposed via xnu-1699.24.8/bsd/kern/syscalls.master:

380     AUE_MAC_EXECVE    ALL     { int _mac_execve(char *fname, char **argp,
                                          char **envp, struct mac *mac_p); }
381     AUE_MAC_SYSCALL   ALL     { int _mac_syscall(char *policy, int call,
                                          user_addr_t arg); }
382     AUE_MAC_GET_FILE  ALL     { int _mac_get_file(char *path_p,
                                         struct mac *mac_p); }
383     AUE_MAC_SET_FILE  ALL     { int _mac_set_file(char *path_p,
                                         struct mac *mac_p); }
384     AUE_MAC_GET_LINK  ALL     { int _mac_get_link(char *path_p,
                                         struct mac *mac_p); }
385     AUE_MAC_SET_LINK  ALL     { int _mac_set_link(char *path_p,
                                         struct mac *mac_p); }
386     AUE_MAC_GET_PROC  ALL     { int _mac_get_proc(struct mac *mac_p); }
387     AUE_MAC_SET_PROC  ALL     { int _mac_set_proc(struct mac *mac_p); }
388     AUE_MAC_GET_FD    ALL     { int _mac_get_fd(int fd, struct mac *mac_p); }
389     AUE_MAC_SET_FD    ALL     { int _mac_set_fd(int fd, struct mac *mac_p); }
390     AUE_MAC_GET_PID   ALL     { int _mac_get_pid(pid_t pid,
                                         struct mac *mac_p); }
391     AUE_MAC_GET_LCID  ALL     { int _mac_get_lcid(pid_t lcid,
                                         struct mac *mac_p); }
392     AUE_MAC_GET_LCTX  ALL     { int _mac_get_lctx(struct mac *mac_p); }
393     AUE_MAC_SET_LCTX  ALL     { int _mac_set_lctx(struct mac *mac_p); }

In this example, you're interested in how mac_syscall is handled; this is the syscall all of the user space functions discussed earlier in libsandbox ended up calling. This call is provided for policy extensions to dynamically add syscalls of their own. The first parameter is used to select the policy extension by mpc_name (for the Sandbox, this will always be the NUL terminated string “Sandbox”). The second parameter is used to select which subsyscall is called in the policy. The last argument is a void * representing any arguments passed to the policy subsyscall.

After looking up the policy by name, TrustedBSD calls the mpo_policy_syscall function defined by that policy. In our firmware, the mpo_policy_syscall function pointer for the “Sandbox” policy points to sub_805F70B4. This function handles all configuration of the sandbox for a given process. This function is where any audit of the syscall handling and parsing should begin; most untrusted user space data is copied into the kernel here.

At this point, the two sides, kernel and user, have met. You can follow a call to sandbox_init from the example programs through libsandbox to the mac_syscall trap into TrustedBSD and finally meet the sandbox kernel extension. At this point, you've accumulated enough knowledge of the system to audit the path of untrusted data from user space if you're looking for a kernel bug. On the other hand, this is not the place to begin looking for a sandbox escape. The next section addresses this goal by examining the path a normal system call takes through the sandbox and discussing how the operation is evaluated against the process's profile.

Policy Enforcement

In the previous subsection, the mac_policy_ops structure was consulted as a direct result of a TrustedBSD-specific system call. Many of the fields in this structure are used under the normal operation of a process. The TrustedBSD hooks have been carefully inserted all over the kernel. For example, in xnu-1699.24.8/bsd/kern/uipc_syscalls.c, the bind syscall will invoke the mac_socket_check_bind function before proceeding to process the bind operation:

int
bind(_unused proc_t p, struct bind_args *uap, _unused int32_t *retval)
{
...
#if CONFIG_MACF_SOCKET_SUBSET
        if ((error = mac_socket_check_bind(kauth_cred_get(), so, sa)) == 0)
                error = sobind(so, sa);
#else
                error = sobind(so, sa);
#endif /* MAC_SOCKET_SUBSET */

The function mac_socket_check_bind is defined in xnu-1699.24.8/security/mac_socket.c. This function uses the MAC_CHECK macro discussed in Chapter 4, where it iterates over each registered policy and calls the mpo_socket_check_bind function if it has been defined in the mac_policy_ops structure for the policy:

int
mac_socket_check_bind(kauth_cred_t ucred, struct socket *so,
    struct sockaddr *sockaddr)
{
        int error;

        if (!mac_socket_enforce)
                return 0;

        MAC_CHECK(socket_check_bind, ucred,
                  (socket_t)so, so->so_label, sockaddr);
        return (error);
}

The sandbox extension defines a function to handle invocations of the bind() syscall. Our version of the firmware defines mpo_socket_check_bind as sub_805F8D54 (the +1 is an indication to switch to Thumb mode):

_data:805FE0BC                 DCD sub_805F8D54+1      ; mpo_socket_check_bind

_text:805F8D54 sub_805F8D54                            ; DATA XREF:
com.apple.security.sandbox:_data:sbx_mac_policy_opso
_text:805F8D54
_text:805F8D54 var_C           = -0xC
_text:805F8D54
_text:805F8D54                 PUSH            {R7,LR} ; Push registers
_text:805F8D56                 MOV             R7, SP  ; Rd = Op2
_text:805F8D58                 SUB             SP, SP, #4 ; Rd = Op1 - Op2
_text:805F8D5A                 MOV             R2, R1  ; Rd = Op2
_text:805F8D5C                 MOVS            R1, #0  ; Rd = Op2
_text:805F8D5E                 STR             R1, [SP,#0xC+var_C] ; Store to
Memory
_text:805F8D60                 MOVS            R1, #0x37 ; Rd = Op2
_text:805F8D62                 LDR.W           R12, =(sub_805FA5D4+1) ;
Load from Memory
_text:805F8D66                 BLX             R12 ; sub_805FA5D4
_text:805F8D68                 ADD             SP, SP, #4 ; Rd = Op1 + Op2
_text:805F8D6A                 POP             {R7,PC} ; Pop registers
_text:805F8D6A ; End of function sub_805F8D54

This function makes a single call to sub_805FA5D4 while passing the constant 0x37. This value is an index into the SBPL *rules* vector and corresponds to the operation network-bind. The value 0x37 as it corresponds to network-bind is defined in the sbpl1_scm script embedded in libsandbox. sub_805FA5D4 is checking the network-bind operation against the current process's profile. (Soon, you'll look at how this check is actually carried out.) The code to check an operation against a profile is tied tightly to the format of the profile, so the next subsection discusses the details of the profile bytecode format.

How the Profile Bytecode Works

While discussing the SBPL, you learned about the *rules* vector and how the decision tree was used to encode the profile logic. This decision tree is flattened and stored along with the strings and regular expressions to make up the profile bytecode that is passed to the kernel for a custom (that is, not built-in) sandbox. The built-in profiles are in precompiled form in the sandboxd daemon. When a process is sandboxed with a built-in profile, the kernel sends a Mach message to sandboxd asking for the bytecode. Recall that custom profiles are compiled by libsandbox prior to the system call used to initialize the sandbox.

When the kernel receives the profile in bytecode form, it parses the header to extract the regular expressions used in some of the filters. After parsing the regular expressions and storing them for easy access, this regular expression cache and the bytecode are stored in the TrustedBSD process label reserved for the sandbox extension. When an operation check callback is entered via the TrustedBSD framework, the sandbox first checks if there is a profile associated with the current process. If the process has a profile, the bytecode is retrieved and a number of SBPL operations are evaluated.

The enforcement module starts this evaluation in the decision tree at the node corresponding to the operation being checked. The tree is walked and each transition is chosen based on the filter associated with the node. Continuing the previous bind example, the decision node at offset 0x37 would be the starting node. For the socket operations, a filter matching a range of port numbers is available. This filter operation is checked and the appropriate transition is taken, depending on whether the filter is met or not (a next node is provided for both possibilities). Any node in the decision tree may be terminal; upon entry, no filter is applied and a decision of allow or deny is made.

Now that you have an overview of how the evaluation is processed by the kernel, you can continue tracing the bind call. The ongoing example ended with a call to sub_805FA5D4. This function loads the sandbox from the process label and then calls sb_evaluate. sb_evaluate is at 0x805FB0EC in the version of the kernelcache we are using. This function walks the decision tree and performs the operation evaluation as described earlier. This function is large and complex, but if you really want to understand how a profile is interpreted, this is a good starting point. This is also a good function to use as an anchor for finding out which kernel operations map to which SBPL operations. The mapping is not one-to-one.

The final piece of the puzzle is the binary format used to deliver the profile to the kernel. This can be derived from either the user space portion creating the bytecode for custom profiles (compile from libsandbox) or the kernel code that processes the profile. On the kernel side, this parsing is split between the regular expression parsing code and the sb_evaluate code. We've included a pseudo-C description of the format. The profile is logically arranged as a decision tree; evaluation of the profile is done under a given operation (“Can this process read a file at path X?”). The op_table provides the node to start at for each operation. Given the current node and the operation attempted, evaluation continues depending on the type of the current node. If the node is a result node, the evaluation has produced a result (either allow or deny.) Otherwise, the node is a decision node and a number of predicate filters may be applied to the operation. If the filter accepts or matches the attempted operation, the current node is set to the node identified by the match_next value. Otherwise, the current node is set to the nomatch_next value. These nodes form a binary decision tree:

struct node;

struct sb_profile {
  union {
    struct {
      uint16_t re_table_offset;
      uint16_t re_table_count;
      uint16_t op_table[SB_OP_TABLE_COUNT];
    } body;
    struct node nodes[1];
  } u;
};

// Two different types of nodes in the decision tree. The result node is a
// terminal node; it produces a decision to allow or deny the operation.  The
// decision node applies a filter to the attempted operations ("Does the path
// match ‘/var/awesome’?") and will transition to one of two nodes depending
// on the result of the filter operation.

struct result;
#define NODE_TAG_DECISION  0
#define NODE_TAG_RESULT    1

// Each filter type uses the argument value differently.  For example, the path
// literal argument is a filter offset (8 byte block offset from the start of the // file). At that offset, there is a uint32_t length, a uint8_t padding byte, and
// an ASCII path of length bytes. The path regex filter argument is an index into
// the regex table. The filters correspond directly to those described in the
// Scheme SBPL script embedded in libsandbox.  More details are available in the
// sbdis.py script included in the source package.

struct decision;
#define DECISION_TYPE_PATH_LITERAL     1
#define DECISION_TYPE_PATH_REGEX       0x81
#define DECISION_TYPE_MOUNT_RELATIVE   2
#define DECISION_TYPE_XATTR            3
#define DECISION_TYPE_FILE_MODE        4
#define DECISION_TYPE_IPC_POSIX        5
#define DECISION_TYPE_GLOBAL_NAME      6
#define DECISION_TYPE_LOCAL            8
#define DECISION_TYPE_REMOTE           9
#define DECISION_TYPE_CONTROL          10
#define DECISION_TYPE_TARGET           14
#define DECISION_TYPE_IOKIT            15
#define DECISION_TYPE_EXTENSION        18

struct node {
  uint8_t tag;
  union {
    struct result terminal;
    struct decision filter;
    uint8_t raw[7];
  } u;
};

struct result {
  uint8_t padding;
  uint16_t allow_or_deny;
};

struct decision {
  uint8_t type;
  uint16_t arg;
  uint16_t match_next;
  uint16_t nomatch_next;
};

Included in the accompanying software package are tools to extract the compiled sandboxes from sandboxd. Also included are tools to extract all compiled regular expressions, decompile a regex blob to something approximating regular expression syntax, and a tool to extract a readable profile from a full binary sandbox profile. An example of the output produced by this tool is included here; this profile is the racoon IPSec daemon profile:

([‘default’], [‘deny (with report)’])
([‘file*’,
  ‘file-chroot’,
  ‘file-issue-extension*’,
  ‘file-issue-extension-read’,
  ‘file-issue-extension-write’,
  ‘file-mknod’,
  ‘file-revoke’,
  ‘file-search’],
 [(‘allow’, ‘path == "/private/var/log/racoon.log"’),
  (‘allow’, ‘path == "/Library/Keychains/System.keychain"’),
  (‘allow’, ‘path == "/private/var/db/mds/system/mdsDirectory.db"’),
  (‘allow’, ‘path == "/private/var/db/mds/system/mds.lock"’),
  (‘allow’, ‘path == "/private/var/db/mds/system/mdsObject.db"’),
  (‘allow’, ‘path == "/var/log/racoon.log"’),
  ‘deny (with report)’])
([‘file-ioctl’],
 [(‘allow’, ‘path == "/private/var/run/racoon"’),
  (‘allow’,   ‘path ==
"/private/var/preferences/SystemConfiguration/com.apple.ipsec.plist"’),
  (‘allow’, ‘path == "/private/etc/racoon"’),
  (‘allow’, ‘path == "/dev/aes_0"’),
  (‘allow’, ‘path == "/dev/dtracehelper"’),
  (‘allow’, ‘path == "/dev/sha1_0"’),
  (‘allow’, ‘path == "/private/etc/master.passwd"’),
  (‘allow’, ‘path == "/private/var/log/racoon.log"’),
  (‘allow’, ‘path == "/Library/Keychains/System.keychain"’),
  (‘allow’, ‘path == "/private/var/db/mds/system/mdsDirectory.db"’),
  (‘allow’, ‘path == "/private/var/db/mds/system/mds.lock"’),
  (‘allow’, ‘path == "/private/var/db/mds/system/mdsObject.db"’),
  (‘allow’, ‘path == "/var/log/racoon.log"’),
  ‘deny (with report)’])
([‘file-read-xattr’, ‘file-read*’, ‘file-read-data’],
 [(‘allow’, ‘path == "/private/var/run/racoon"’),
  (‘allow’,   ‘path ==
"/private/var/preferences/SystemConfiguration/com.apple.ipsec.plist"’),
  (‘allow’, ‘path == "/private/etc/racoon"’),
  (‘allow’, ‘path == "/Library/Managed Preferences"’),
  (‘allow’, ‘path == "/private/var/db/mds/messages/se_SecurityMessages"’),
  (‘allow’, ‘path == "/private/var/root"’),
  (‘allow’, ‘path == "/Library/Preferences"’),
  (‘if’,
   ‘file-mode == 4’,
   [(‘allow’, ‘path == "/usr/sbin"’),
    (‘allow’, ‘path == "/usr/lib"’),
    (‘allow’, ‘path == "/System"’),
    (‘allow’, ‘path == "/usr/share"’),]),
  (‘allow’, ‘path == "/private/var/db/timezone/localtime"’),
  (‘allow’, ‘path == "/dev/urandom"’),
  (‘allow’, ‘path == "/dev/random"’),
  (‘allow’, ‘path == "/dev/null"’),
  (‘allow’, ‘path == "/dev/zero"’),
  (‘allow’, ‘path == "/dev/aes_0"’),
  (‘allow’, ‘path == "/dev/dtracehelper"’),
  (‘allow’, ‘path == "/dev/sha1_0"’),
  (‘allow’, ‘path == "/private/etc/master.passwd"’),
  (‘allow’, ‘path == "/private/var/log/racoon.log"’),
  (‘allow’, ‘path == "/Library/Keychains/System.keychain"’),
  (‘allow’, ‘path == "/private/var/db/mds/system/mdsDirectory.db"’),
  (‘allow’, ‘path == "/private/var/db/mds/system/mds.lock"’),
  (‘allow’, ‘path == "/private/var/db/mds/system/mdsObject.db"’),
  (‘allow’, ‘path == "/var/log/racoon.log"’),
  ‘deny (with report)’])
([‘file-read-metadata’],
 [(‘allow’, ‘path == "/tmp"’),
  (‘allow’, ‘path == "/var"’),
  (‘allow’, ‘path == "/etc"’),
  (‘allow’, ‘path == "/private/var/run/racoon"’),
  (‘allow’,   ‘path ==
"/private/var/preferences/SystemConfiguration/com.apple.ipsec.plist"’),
  (‘allow’, ‘path == "/private/etc/racoon"’),
  (‘allow’, ‘path == "/Library/Managed Preferences"’),
  (‘allow’, ‘path == "/private/var/db/mds/messages/se_SecurityMessages"’),
  (‘allow’, ‘path == "/private/var/root"’),
  (‘allow’, ‘path == "/Library/Preferences"’),
  (‘if’,
   ‘file-mode == 4’,
   [(‘allow’, ‘path == "/usr/sbin"’),
    (‘allow’, ‘path == "/usr/lib"’),
    (‘allow’, ‘path == "/System"’),
    (‘allow’, ‘path == "/usr/share"’),]),
  (‘allow’, ‘path == "/private/var/db/timezone/localtime"’),
  (‘allow’, ‘path == "/dev/urandom"’),
  (‘allow’, ‘path == "/dev/random"’),
  (‘allow’, ‘path == "/dev/null"’),
  (‘allow’, ‘path == "/dev/zero"’),
  (‘allow’, ‘path == "/dev/aes_0"’),
  (‘allow’, ‘path == "/dev/dtracehelper"’),
  (‘allow’, ‘path == "/dev/sha1_0"’),
  (‘allow’, ‘path == "/private/etc/master.passwd"’),
  (‘allow’, ‘path == "/private/var/log/racoon.log"’),
  (‘allow’, ‘path == "/Library/Keychains/System.keychain"’),
  (‘allow’, ‘path == "/private/var/db/mds/system/mdsDirectory.db"’),
  (‘allow’, ‘path == "/private/var/db/mds/system/mds.lock"’),
  (‘allow’, ‘path == "/private/var/db/mds/system/mdsObject.db"’),
  (‘allow’, ‘path == "/var/log/racoon.log"’),
  ‘deny (with report)’])])
([‘file-write*’,
  ‘file-write-create’,
  ‘file-write-flags’,
  ‘file-write-mode’,
  ‘file-write-mount’,
  ‘file-write-owner’,
  ‘file-write-setugid’,
  ‘file-write-times’,
  ‘file-write-unlink’,
  ‘file-write-unmount’,
  ‘file-write-xattr’],
 [(‘allow’, ‘path == "/private/var/run/racoon.pid"’),
  (‘allow’, ‘path == "/private/var/run/racoon.sock"’),
  (‘allow’, ‘path == "/private/var/log/racoon.log"’),
  (‘allow’, ‘path == "/Library/Keychains/System.keychain"’),
  (‘allow’, ‘path == "/private/var/db/mds/system/mdsDirectory.db"’),
  (‘allow’, ‘path == "/private/var/db/mds/system/mds.lock"’),
  (‘allow’, ‘path == "/private/var/db/mds/system/mdsObject.db"’),
  (‘allow’, ‘path == "/var/log/racoon.log"’),
  ‘deny (with report)’])
([‘file-write-data’],
 [(‘allow’, ‘path == "/dev/zero"’),
  (‘allow’, ‘path == "/dev/aes_0"’),
  (‘allow’, ‘path == "/dev/dtracehelper"’),
  (‘allow’, ‘path == "/dev/sha1_0"’),
  (‘allow’, ‘path == "/dev/null"’),
  (‘allow’, ‘path == "/private/var/run/racoon.pid"’),
  (‘allow’, ‘path == "/private/var/run/racoon.sock"’),
  (‘allow’, ‘path == "/private/var/log/racoon.log"’),
  (‘allow’, ‘path == "/Library/Keychains/System.keychain"’),
  (‘allow’, ‘path == "/private/var/db/mds/system/mdsDirectory.db"’),
  (‘allow’, ‘path == "/private/var/db/mds/system/mds.lock"’),
  (‘allow’, ‘path == "/private/var/db/mds/system/mdsObject.db"’),
  (‘allow’, ‘path == "/var/log/racoon.log"’),
  ‘deny (with report)’])
([‘iokit-open’],
 [(‘allow’, ‘iokit-user-client-class == "RootDomainUserClient"’),
  ‘deny (with report)’])
([‘ipc-posix*’, ‘ipc-posix-sem’],
 [(‘allow’, ‘ipc-posix-name == "com.apple.securityd"’), ‘deny (with report)’])
([‘ipc-posix-shm’],
 [(‘allow’, ‘ipc-posix-name == "com.apple.AppleDatabaseChanged"’),
  (‘allow’, ‘ipc-posix-name == "apple.shm.notification_center"’),
  (‘allow’, ‘ipc-posix-name == "com.apple.securityd"’),
  ‘deny (with report)’])
(['sysctl*’,
  ‘sysctl-read’,
  ‘sysctl-write’,
  ‘mach-bootstrap’,
  ‘system-socket’,
  ‘priv*’,
  ‘priv-adjtime’,
  ‘priv-netinet*’,
  ‘priv-netinet-reservedport’],
 [‘allow’])
([‘mach-issue-extension’, ‘mach-lookup’],
 [(‘allow’, ‘mach-global-name == "com.apple.ocspd"’),
  (‘allow’, ‘mach-global-name == "com.apple.securityd"’),
  (‘allow’, ‘mach-global-name == "com.apple.system.notification_center"’),
  (‘allow’, ‘mach-global-name == "com.apple.system.logger"’),
  (‘allow’,
   ‘mach-global-name == "com.apple.system.DirectoryService.membership_v1"’),
  (‘allow’,
   ‘mach-global-name == "com.apple.system.DirectoryService.libinfo_v1"’),
  (‘allow’, ‘mach-global-name == "com.apple.bsd.dirhelper"’),
  (‘allow’, ‘mach-global-name == "com.apple.SecurityServer"’),
  ‘deny (with report)’])
([‘network*’, ‘network-inbound’, ‘network-bind’],
 [(‘allow’, ‘local.match(udp:*:500)’),
  (‘allow’, ‘remote.match(udp:*:*)’),
  (‘allow’, ‘path == "/private/var/run/racoon.sock"’),
  (‘allow’, ‘local.match(udp:*:4500)’),
  ‘deny (with report)’])
([‘network-outbound’],
 [(‘deny (with report)’,
   ‘path.match("ˆ/private/tmp/launchd-([0-9])+\.([ˆ/])+/sock$")’),
  (‘deny (with report)’, ‘path == "/private/var/tmp/launchd/sock"’),
  (‘allow’, ‘path == "/private/var/run/asl_input"’),
  (‘allow’, ‘path == "/private/var/run/syslog"’),
  (‘allow’, ‘path == "/private/var/tmp/launchd"’),
  (‘allow’, ‘local.match(udp:*:500)’),
  (‘allow’, ‘remote.match(udp:*:*)’),
  (‘allow’, ‘path == "/private/var/run/racoon.sock"’),
  (‘allow’, ‘local.match(udp:*:4500)’),
  ‘deny (with report)’])
([‘signal’], [(‘allow’, ‘target == self’), ‘deny (with report)’])

The only thing not covered here are the details of the regular expression format. The AppleMatch kernel extension performs this matching and dictates the binary format, while the user space libMatch does the compilation from regular expression to regex blob embedded in the sandbox profile. The compiled regular expression format is slightly different from the one described in www.semantiscope.com/research/BHDC2011/BHDC2011-Paper.pdf but the differences are mostly cosmetic. As with the bytecode format of the profiles, the best documentation for this is in the included software package. There is a script, redis.py, that converts compiled regex blobs into the equivalent regular expression.

How Sandboxing Impacts App Store versus Platform Applications

Having looked at the implementation of the sandbox in extreme detail, you should ask how this feature is currently used. The details of the profiles used are not well documented, but it is well known that the sandbox restricts those applications downloaded from the App Store. Additionally, many of the platform applications like MobileSafari and MobileMail are also placed into a sandbox. How are these applications launched under the sandbox? How is each App Store application restricted to its own container directory? These are the questions answered in this section.

Surprisingly, neither App Store applications nor platform applications call sandbox_init or friends directly. Also, though there is an option to launch an application through launchd with a sandbox profile, we found no built-in applications using this functionality. Fortunately, some strings in the kernel extension point the way to the answer:

_cstring:805FDA21 aPrivateVarMobi DCB "/private/var/mobile/Applications/",0
...
_cstring:805FDB6F aSandboxIgnorin DCB "Sandbox: ignoring builtin profile for
platform app: %s",0xA,0

Following cross-references to these strings show that they both are used in the function sbx_cred_label_update_execve. This function is called whenever a new executable image is loaded. Remember, the TrustedBSD functions are called regardless of whether the current process has initialized the sandbox. If the sandbox has not yet been initialized, most functions return early with no check. In this case, sbx_cred_label_update_execve first calculates the path for the loaded executable image. If the executable is under /private/var/mobile/Applications, the built-in sandbox profile, “container,” will be loaded and the path under the above directory will be added as an extension. This extension is what enables the same container profile to be used for all the App Store applications despite the fact that they reside in different subdirectories. It mirrors the example given in the first section of this chapter.

Platform applications, such as MobileSafari, are not placed under the App Store directory structure. For these applications, a sandbox profile can be specified in the embedded entitlements portion of the code signing load command of a Mach-O executable. Following is a transcript dumping the embedded entitlements of MobileSafari:

pitfall:entitlements dion$ ./grab_entitlements.py MobileSafari
<?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.coreaudio.allow-amr-decode</key>
        <true/>
        <key>com.apple.coremedia.allow-protected-content-playback</key>
        <true/>
        <key>com.apple.managedconfiguration.profiled-access</key>
        <true/>
        <key>com.apple.springboard.opensensitiveurl</key>
        <true/>
        <key>dynamic-codesigning</key>
        <true/>
        <key>keychain-access-groups</key>
        <array>
               <string>com.apple.cfnetwork</string>
               <string>com.apple.identities</string>
               <string>com.apple.mobilesafari</string>
               <string>com.apple.certificates</string>
        </array>
        <key>platform-application</key>
        <true/>
        <key>seatbelt-profiles</key>
        <array>
               <string>MobileSafari</string>
        </array>
        <key>vm-pressure-level</key>
        <true/>
</dict>
</plist>

In the package of scripts available from this book's website, grab_entitlements.py will pull embedded entitlements from a binary. By searching for the seatbelt-profiles key in the embedded entitlements of a platform application, you can determine which sandbox profile is applied by the kernel (currently, more than one profile is not supported). This profile initialization occurs in the same function as the App Store version. The AppleMobileFileIntegrity extension is called to load the embedded profile name. This name is used to initialize the sandbox profile just as the container was used previously.

To illustrate their use, this example attempts to create an application that initializes its sandbox in each of these possible ways. One executable will be placed in /tmp with no embedded entitlements, one executable will be placed under the App Store directory, and one will have an embedded entitlement specifying a built-in profile.

To trigger each of these paths, create a test executable to try reading a single file under /private/var/tmp. This path is restricted by the App Store container profile. The source is given here:

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[]) {
        FILE *f = fopen("/private/var/tmp/can_you_see_me", "r");
        if (f != NULL) {
                char buff[80];
                memset(buff, 0, 80);
                fgets(buff, 80, f);
                printf("%s", buff);
                fclose(f);
        } else {
                perror("fopen failed");
        }
        return 0;
}

The first test is to verify the operation outside the presence of any sandbox. You can execute this from /tmp. The following transcript shows the expected output:

iFauxn:∼ root# /tmp/sb5
This is /tmp/can_you_see_me

As expected, an unsandboxed application may read the file. To test the second path through sbx_cred_label_update_execve, you copy the binary executed earlier to a subdirectory under /private/var/mobile/Applications (such as /private/var/mobile/Applications/DDDDDDDD-DDDD-DDDD-DDDD-DDDDDDDDDDDD/). By executing it under this directory, the sandbox kernel extension will automatically set the profile for the process to the container built-in profile. The following code shows this and verifies the container profile further by looking at dmesg:

iFauxn:∼ root# cp ∼/ioshh/sb5 /private/var/mobile/Applications
/DDDDDDDD-DDDD-DDDD-DDDD-DDDDDDDDDDDD/
iFauxn:∼ root# /private/var/mobile/Applications/DDDDDDDD-DDDD-DDDD-DDDD-
DDDDDDDDDDDD/sb5
fopen failed: Operation not permitted
iFauxn:∼ root# dmesg | tail
...
bash[15427] Builtin profile: container (sandbox)
bash[15427] Container: /private/var/mobile/Applications/DDDDDDDD-DDDD-DDDD-DDDD-
DDDDDDDDDDDD [69] (sandbox)

The dmesg output also verifies the sandbox extension used (called a “Container” when used by the App Store logic). The last thing to try is the platform application profile via an embedded entitlement (the MobileSafari method). To do this, you need to embed an entitlement plist during the code signing step:

pitfall:sb5 dion$ cat sb5.entitlements
<?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>seatbelt-profiles</key>
       <array>
               <string>container</string>
       </array>
</dict>
</plist>

pitfall:sb5 dion$ make sb5-ee
/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/gcc -arch armv6
-isysroot
/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.0.sdk sb5.c
-o sb5-ee
export
CODESIGN_ALLOCATE=
/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/codesign_
allocate
codesign -fs "dion" --entitlements sb5.entitlements sb5-ee
pitfall:sb5 dion$

The code sign tool signs the binary and places this signature in the LC_CODE_SIGNATURE Mach-O load command. The format of the data in the LC_CODE_SIGNATURE block is described in xnu-1699.24.8/bsd/kern/ubc_subr.c. The embedded plist is placed into this block and is queried by the sandbox kernel extension as explained previously. Once this binary is executed, the kernel should initialize the profile to container (in this case, no extension would be set). The file shouldn't be readable. Unfortunately, at least with redsn0w 0.9.9b7 patching an iPhone 4 running iOS 5.0, this example fails:

iFauxn:∼ root# cp ∼/ioshh/sb5-ee /tmp
iFauxn:∼ root# /tmp/sb5-ee
This is /tmp/can_you_see_me
iFauxn:∼ root# dmesg | grep Sandbox
Sandbox: ignoring builtin profile for platform app:
/private/var/stash/Applications.D1YevH/MobileMail.app/MobileMail
Sandbox: ignoring builtin profile for platform app:
/private/var/stash/Applications.D1YevH/MobileSafari.app/MobileSafari
Sandbox: ignoring builtin profile for platform app: /private/var/tmp/sb5-ee
iFauxn:∼ root#

In the dmesg output, you see that all platform applications are run outside their sandboxes with that version of the jailbreak. Despite this, we've illustrated the correct path; the embedded entitlement would have been used. Before moving on, you can figure out how the current jailbreak patches break platform application sandboxing. The “Sandbox: ignoring builtin profile …” string is easy to find in the kernelcache and leads right to one of the patches. Figure 5.3 shows one of the patched basic blocks before (left) and after (right) the jailbreak patch is applied.

Figure 5.3 redsn0w 0.9.9b7 cred_label_update_execve

5.3

This comparison shows the patched bytes, 01 23 01 23, used to force a debug mode sysctl check and to ensure that the conditional always falls to the side where the sandbox profile is ignored for applications that aren't under the App Store directory. This kind of exception is important to keep in mind while working with a jailbroken phone for exploit or payload development.

Summary

The iOS sandbox intends to limit post-code-execution exploitation and malware from the App Store by imposing limits on a process based on what permissions it would normally need for operation. The App Store applications are isolated using this feature, and more than 40 of the shipped platform applications (for example, MobileSafari and MobileMail) have custom profiles limiting the operations available to them. The main component of the sandbox system is implemented through a kernel extension exposing a TrustedBSD policy. The kernel extension places a process into a sandbox described by a Scheme script written under a domain-specific language. This profile is distilled into decision tree filtering operations based on their attributes (for example vnode path, or port number) terminating in a decision of allow or deny. The profile may be extended in a limited way at run time.

By now, you should be able to write a syscall fuzzer targeting the mac_syscall(“Sandbox”, . . .) sub-syscalls. The kernel entry point for the sandbox extension was given as a starting point for a manual audit. For an attacker looking for a bypass, this chapter discussed the format and evaluation of a binary profile and the code that consumes it. It also discussed using the evaluation function as a point of reference to map kernel operations to SBPL operations. This is another path of interest for any attackers looking for a sandbox escape.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset