CHAPTER 2
Analyzing iOS Applications

Apple’s iOS, the platform used by today’s iPhone, iPad, and iPod touch devices, is one of the most popular mobile operating systems available. For this reason, and with the possible exception of Android, it is the platform that is targeted the most by hackers and comes under the greatest scrutiny for application layer vulnerabilities.

With more than one million applications in Apple’s App Store, the attack surface is significant. Numerous examples of application-based security flaws have been documented, affecting a wide range of applications including but not limited to those used in banking, retail, and enterprise environments.

This chapter introduces the iOS platform and the ecosystem and provides an introduction to iOS applications. It sets out in detail the practical steps you can follow to build an environment suitable for testing and exploiting iOS applications. Finally, it describes the ways in which you can begin to analyze and modify iOS applications to identify security flaws.

Understanding the Security Model

Before delving into the inner working of iOS applications and the techniques you can use to attack them, understanding the fundamental security features of the iOS platform itself is important. This not only provides context to application-based vulnerabilities, but also highlights some of the opt-in features that applications can take advantage of to improve security.

The core security features of the iOS platform are summarized here:

Apple combines these security technologies, which are implemented as either hardware or software components, to improve the overall security of iPhone, iPad, and iPod devices. These security features are present on all non-jailbroken devices and you should take them into consideration when you are assigning risk ratings to application-based vulnerabilities. Some of these features are documented in the blog post by MDSec at http://blog.mdsec.co.uk/2012/05/introduction-to-ios-platform-security.html.

Initializing iOS with Secure Boot Chain

The Secure Boot Chain is the term used to describe the process by which the firmware is initialized and loaded on iOS devices at boot time, and it can be considered the first layer of defense for the security of the platform. In each step of the Secure Boot Chain, each of the relevant components that have been cryptographically signed by Apple is verified to ensure that it has not been modified.

When an iOS device is turned on, the processor executes the boot ROM, which is a read-only portion of code that is contained within the processor and is implicitly trusted by the device; it is burned onto the chip during manufacturing. The boot ROM contains the public key for Apple’s Root CA, which is used to verify the integrity of the next step of the Secure Boot Chain, the low-level bootloader (LLB).

The LLB performs a number of setup routines, including locating the iBoot image in flash memory before booting from it. The LLB looks to maintain the Secure Boot Chain, shown in Figure 2.1, by verifying the signature of the iBoot image, and if the signature does not match the expected value, the device boots into recovery mode. iBoot, which is the second-stage bootloader, is responsible for verifying and loading the iOS kernel, which in turn goes on to load the usermode environment and the OS which you will no doubt be familiar with.

images

Figure 2.1 The secure boot chain

Introducing the Secure Enclave

The Secure Enclave is a coprocessor shipped with A7 and A8 chip devices (iPhone 6, iPhone 5s, iPad Air, and iPad Mini second generation at the time of writing) that uses its own secure boot and software update processes, independent from the main application processor. The Secure Enclave handles cryptographic operations on the device, specifically the key management for the Data Protection API and Touch ID fingerprint data. The Secure Enclave uses a customized version of the ARM TrustZone (http://www.arm.com/products/processors/technologies/trustzone/index.php) to partition itself from the main processor and provide data integrity even if the device’s kernel becomes compromised. In short, this means that if the device is jailbroken or otherwise compromised, extracting cryptographic material such as biometric fingerprint data from the device should be impossible. For further information about the Secure Enclave, please refer to the whitepaper release by Apple (http://www.apple.com/ca/ipad/business/docs/iOS_Security_Feb14.pdf).

Restricting Application Processes with Code Signing

Code signing is perhaps one of the most important security features of the iOS platform. It is a runtime security feature of the platform that attempts to prevent unauthorized applications from running on the device by validating the application signature each time it is executed. Additionally, code signing ensures that applications may execute only code signed by a valid, trusted signature; for example, any attempt made to execute pages in memory from unsigned sources will be rejected by the kernel.

For an application to run on an iOS device, it must first be signed by a trusted certificate. Developers can install trusted certificates on a device through a provisioning profile that has been signed by Apple. The provisioning profile contains the embedded developer certificate and set of entitlements that the developer may grant to applications. In production applications, all code must be signed by Apple, a process initiated by performing an App Store submission. This process allows Apple some control over applications and the APIs and functionality used by developers. For example, Apple looks to prevent applications that use private APIs or applications that download and install executable code, thus preventing applications from upgrading themselves. Other actions that Apple deems as banned or potentially malicious will similarly result in application submissions being rejected from the App Store.

Isolating Applications with Process-Level Sandboxing

All third-party applications on iOS run within a sandbox, a self-contained environment that isolates applications not only from other applications but also from the operating system. Sandboxing introduces significant security to the platform and limits the damage that malware can do, assuming a malicious application has subverted the App Store review process.

Although all applications run as the mobile operating system user, each application is contained within its own unique directory on the filesystem and separation is maintained by the XNU Sandbox kernel extension. The seat belt profile governs the operations that can be performed in the sandbox. Third-party applications are assigned the container profile, which generally limits file access to the application home tree (top-level and all subsequent directories), and with some exceptions, unrestricted access to outbound network connections. Since iOS7, the seat belt container profile has been made much more prohibitive and for an application to access things like media, the microphone, and the address book, it must request the relevant permissions from the user. This means that assuming a piece of malware has bypassed the App Store review process, it would not be able to steal your contacts and photos unless you grant it the relevant permissions.

Protecting Information with Data-at-Rest Encryption

By default, all data on the iOS filesystem is encrypted using block-based encryption (AES) with the filesystem key, which is generated on first boot and stored in block 1 of the NAND flash storage. The device uses this key during the startup process to decrypt the partition table and the system partition. The filesystem is encrypted only at rest; when the device is turned on, the hardware-based crypto accelerator unlocks the filesystem. iOS leverages this key to implement the device’s remote wipe capability because destroying the filesystem key causes the filesystem to become unreadable.

In addition to the hardware encryption, individual files and keychain items can be encrypted using the Data Protection API, which uses a key derived from the device passcode. Consequently, when the device is locked, items encrypted using the Data Protection API in this way will be inaccessible, and upon unlocking the device by entering the passcode, protected content becomes available.

Third-party applications needing to encrypt sensitive data should use the Data Protection API to do so. However, consideration should be given for background processes in how they will behave if necessary files become unavailable due to the device becoming locked. For in-depth details on how the Data Protection API works consult the later section in this chapter, “Understanding the Data Protection API.”

Protecting Against Attacks with Exploit Mitigation Features

The iOS platform employs a number of modern-day exploit mitigation technologies to increase the complexity of attacks against the device.

Perhaps one of the most important of these protections is the implementation of the write but not execute (W^X) memory policy, which states that memory pages cannot be marked as writeable and executable at the same time. This protection mechanism is applied by taking advantage of the ARM processor’s Execute Never (XN) feature. As part of this policy, executable memory pages that are marked as writeable cannot also be later reverted to executable. In many ways this is similar to the Data Execution Protection (DEP) features implemented in Microsoft Windows, Linux, and Mac OS X desktop OSs.

Although non-executable memory protections alone can be easily bypassed using return-oriented programming (ROP)–based payloads, the complexity of exploitation is significantly increased when compounded with ASLR and mandatory code signing.

Address space layout randomization (ASLR) is an integral part of the platform’s exploit mitigation features and looks to randomize where data and code are mapped in a process’ address space. By randomizing code locations, exploitation of memory corruption vulnerabilities becomes significantly more complex. This makes techniques to bypass non-executable memory like ROP difficult because attackers are unlikely to know the location of the portions of code that they want to reuse in their ROP gadget chain.

ASLR was first introduced to iOS in version beta 4.3 and since its implementation it has gradually improved with each release. The primary weakness in the early ASLR implementations was the lack of relocation of the dynamic linker (dyld); this was addressed with the release of iOS 5.0. However, a number of techniques can weaken its effectiveness, the most common of which is making use of memory disclosure bugs. This generally involves using a separate vulnerability to leak the contents or confirm memory layout in an effort to make exploitation attempts much more likely to succeed.

Applications can have ASLR applied in two different flavors: either partial ASLR or full ASLR, depending on whether they have been compiled with support for position-independent execution (PIE). In a full ASLR scenario, all the application memory regions are randomized and iOS will load a PIE-enabled binary at a random address each time it is executed. An application with partial ASLR will load the base binary at a fixed address and use a static location for the dyld. Although now dated, an in-depth assessment of ASLR in iOS has been conducted by Stefan Esser and is recommended reading for those looking to gain a greater understanding (http://antid0te.com/CSW2012_StefanEsser_iOS5_An_Exploitation_Nightmare_FINAL.pdf).

A further protection mechanism that iOS applications can take advantage of is “stack-smashing” protection. This compiler-based exploit mitigation offers some defense against traditional stack-based overflow exploits by introducing stack canaries. Stack canaries are pseudo-random DWORD values that are inserted behind local variables. Stack canaries are checked upon return of the function. If an overflow has occurred and the canary has been corrupted or overwritten entirely, the application will forcibly terminate to prevent any unintended behavior that may be brought on by the memory corruption.

Understanding iOS Applications

Although more than a million iOS applications exist in the App Store alone, at a high level one can categorize all iOS applications into three main groups:

Traditional standard native applications are the most common of iOS applications, and these are developed in Objective-C or more recently in Swift. Objective-C is an object-orientated programming language that adds Smalltalk-style messaging to the C programming language, whereas Swift is Apple’s new multi-paradigm programming language that is likely to replace Objective-C in the long term. Both are discussed in greater detail later in this chapter. Because Objective-C is a strict superset of C, seeing native applications developed in a mixture of Objective-C, C, or even C++ is not uncommon. These applications are compiled to native code and linked against the iOS SDK and Cocoa Touch frameworks. Programming in Objective-C and Swift is beyond the scope of this book; however, knowledge of these languages and their basic principles will be beneficial to your understanding. If you have never seen any Objective-C or Swift code before, we recommend that you familiarize yourself with these languages; the documentation provided by the Apple developer program is a useful starting point (specifically https://developer.apple.com/library/prerelease/mac/documentation/Swift/Conceptual/Swift_Programming_Language/index.html#//apple_ref/doc/uid/TP40014097-CH3-XID_0 and https://developer.apple.com/library/mac/documentation/cocoa/conceptual/ProgrammingWithObjectiveC/Introduction/Introduction.html).

Browser-based applications are the “mobile-friendly” clone of a web application. These applications are specifically customized to render in iOS web views and are typically loaded via MobileSafari. Browser-based applications use traditional web technologies, including HTML, JavaScript, and Cascading Style Sheets. You should approach browser-based applications using traditional web application security methodologies; they are not covered in any great detail within this book.

Hybrid applications are a cross between standard native and browser-based applications. Typically, hybrid applications are deployed with a native wrapper that is used to display one or more browser-based applications through use of a mobile web view. Hybrid applications also include those used as part of a Mobile Enterprise Application Platform deployment and are discussed in greater detail in Chapter 18. Hybrid applications offer the advantages of both native and browser-based applications; these include the flexibility for real-time updates, because HTML and JavaScript applications are not constrained by code signing, as well as native device functionality such as camera access, through JavaScript to Objective-C bridge APIs.

Distribution of iOS Applications

This section covers the different official methods by which developers can distribute iOS applications to devices; namely the Apple App Store and the official Apple developer program.

Apple App Store

The Apple App Store has been mentioned on several occasions so far in this book and aside from being the standard method of application distribution, it’s also the one with which most people are familiar.

The App Store is the official distribution market for iOS applications where users can search and browse for downloadable applications. Applications in the App Store are developed using Apple’s iOS SDK and are targeted for iPhone and iPod touch or iPad devices. The majority of applications in the App Store are created by third-party publishers and can be downloaded for free or a fixed cost.

Before developers can publish an application they must have an Apple Developer account and be a member of the iOS Developer Program. Being a member of this program entitles you to obtain a developer certificate that can be used to code sign applications and run them on up to 100 different iOS devices using an ad hoc provisioning profile. Apple permits ad hoc distribution in this way to provide third-party developers a means to test their applications on real devices. Developers wanting to distribute their application can submit a copy signed using their certificate to Apple, who will validate the application based on their App Store approval process. Although the exact details of this process are unknown, it is believed to contain both manual and automated testing of the application to identify functional and usability defects and ensure the application conforms with the App Store review guidelines (https://developer.apple.com/appstore/resources/approval/guidelines.html). As part of this process the application is strictly vetted for malicious content such as attempting to steal the address book or using private APIs that are reserved for system applications; such behavior would result in App Store rejection.

Enterprise Distribution

The iOS enterprise developer program allows organizations to develop and distribute in-house applications to their employees. This is typically used by organizations that have internal applications that they do not want to be available in the App Store. Users in the enterprise developer program can obtain and use a code signing certificate in a similar way to that used for ad hoc distribution. However, the significant difference between enterprise distribution and ad hoc distribution is that there is no limitation on the number of devices that an application can be code signed for. This has obvious possibilities for abuse and therefore Apple performs additional validation of users wanting to enter this program: A developer must have a legitimate business along with a Dun and Bradsheet number to enroll.

However, some cases exist where enterprise certificates have been abused, the most notable being the GBA4iOS application, a Game Boy Advanced emulator (http://www.gba4iosapp.com/). This application uses an expired enterprise certificate to allow users to install an application that would not normally be accepted by the App Store. Although the certificate has since been revoked by Apple, a loophole exists whereby setting the device’s date back to before the date of revocation will allow it to be installed. This technique was also used by the Pangu jailbreak (http://en.pangu.io/) as a means of side loading the jailbreak application to the device to gain initial code execution.

Application Structure

iOS applications are distributed as an iOS App Store package (IPA) archive, a compressed package containing the necessary compiled application code, resources, and application metadata required to define a complete application. These packages are nothing more than a Zip file and can be decompressed to reveal the expected structure, as shown here:

Payload 
Payload/Application.app 
iTunesArtwork 
iTunesMetadata.plist

The Payload folder is where all the application data is located, including the compiled application code and any associated static resources, all stored within a folder named after the application and suffixed with the .app extension. The iTunesArtwork file is a 512 x 512-pixel Portable Network Graphics (PNG) image used as the application’s icon in iTunes and the App Store. The iTunesMetadata.plist contains the relevant application metadata, including details such as the developer’s name, bundle identifier, and copyright information.

Installing Applications

A number of methods can be used to install the IPA package on the device, the most common and the one you are most likely familiar with is by using iTunes. iTunes is the Apple media player that you can use to manage your application and media library for OS X and Microsoft Windows operating systems as well as to synchronize content from your iOS device. Using iTunes you can download applications from the App Store and synchronize them to your device. You can also use it for installing enterprise or ad hoc builds, where the latter assumes the corresponding provisioning profile is installed. iOS application developers are likely to have used Apple’s Xcode integrated development environment (IDE) to build and install applications. When compiling an application from source, you can use Xcode to build, install, and debug an application on a device. It also provides a drag-and-drop interface for installing IPA packages similarly to iTunes, within the Xcode organizer or devices view depending on which version of Xcode you are running. Both of these implementations are proprietary to Apple and do not support Linux. However, libimobiledevice, the open source library available for Linux users, provides support for communicating with iOS devices natively. A suite of tools has been built upon this library and provides Linux users with the necessary software to interact with iOS devices. To install IPA packages to a device, Linux users can use the ideviceinstaller command.

The application installation process occurs over the USB connection, and the relevant installer software is required to use Apple’s proprietary USB networking system as a transport mechanism. This communication transport is implemented using the USB multiplexing daemon usbmuxd, which provides a TCP-like transport for multiplexing many connections over one USB pipe. An open source implementation is available at https://github.com/libimobiledevice/usbmuxd, and the iPhone Dev Team has documented an overview of the protocol at http://wikee.iphwn.org/usb:usbmux. On the device, the installd daemon handles the actual installation process. This daemon is responsible for both unpacking and installing applications as well as compressing and packaging applications transferred to iTunes as part of the device synchronization. Before performing either of these tasks, installd validates the code signature for the application. On jailbroken devices you can circumvent this process using tweaks such as AppSync and using ipainstaller (https://github.com/autopear/ipainstaller) to directly install the IPA from the filesystem on the device.

Prior to 1OS8, when you installed an application, it was placed in the /var/mobile/Applications/ folder using a universally unique identifier (UUID) to identify the application container. However, the filesystem layout in iOS8 has changed: the static bundle and the application data folders are stored in separate locations. An application will now typically adhere to the following format:

/var/mobile/Containers/Bundle/Application/<UUID>/Application.app/ 
/var/mobile/Containers/Data/Application/<UUID>/Documents/ 
/var/mobile/Containers/Data/Application/<UUID>/Library/ 
/var/mobile/Containers/Data/Application/<UUID>/tmp/

Each of these directories has a unique function within the sandboxed container:

  • Application.app—This folder represents the folder detailed in the “Application Structure” section and stores the static content of the application and the compiled binary. This folder should not be written to: Doing so invalidates the code signature.
  • Documents—This folder is the persistent data store for the application. The contents of this folder are backed up by iTunes.
  • Library—This folder contains the application support files; that is, files that are not user data files. Examples include configurations, preferences, caches, and cookies. iTunes backs up the contents of this directory, with the exception of the Caches subdirectory.
  • tmp—This folder is used to store temporary files; that is, files that do not need to persist between application launches.

Understanding Application Permissions

The introduction of iOS 6 brought a number of new privacy and permission improvements that have been refined with each new release since. Before iOS 6, any iOS application that had undergone App Store approval was able to access your contact lists, photos, and other sensitive data without your knowledge as was the case with the Path application (http://www.wired.com/2012/02/path-social-media-app-uploads-ios-address-books-to-its-servers/).

The permission model on iOS works a little differently than on other mobile platforms: Data is segregated into classes and an application must request permissions from the user to access data from that class. Data is broadly segregated into the following classes:

  • Location services
  • Contacts
  • Calendar
  • Photos
  • Reminders
  • Microphone access
  • Motion activity
  • Bluetooth access
  • Social media data

When an application requires access to data protected by these privacy classes it must prompt the user to allow or deny access. For example, if an application wants access to the device’s address book it must request permission from the user as shown here:

ABAddressBookRef addressBookRef = ABAddressBookCreateWithOptions(NULL, 
NULL);
 
if (ABAddressBookGetAuthorizationStatus()== 
kABAuthorizationStatusNotDetermined) { 
ABAddressBookRequestAccessWithCompletion(addressBookRef, ^(bool granted, 
CFErrorRef error) { 
    if (granted) { 
        // access is granted 
    } 
    else { 
        // access is denied 
    } 
});

This code causes the application to display a privacy prompt as shown in Figure 2.2.

images

Figure 2.2 The user sees this privacy prompt when an application tries to access the address book.

At this stage the user can either allow or deny the application access to the requested resource. If the request is granted then the application will be allowed access to the resource indefinitely or until the user revokes it via the Privacy settings menu, an example of which is shown in Figure 2.3.

images

Figure 2.3 Users can access Privacy settings if they want to grant access to a resource.

As you can probably imagine, the privilege model is highly dependent upon user awareness; if the user knowingly grants permissions to the application then the application is able to abuse them. One such example of this was the “Find and Call” malware (http://securelist.com/blog/incidents/33544/find-and-call-leak-and-spam-57/), which evaded the App Store vetting process and after prompting users to allow access to their address books, proceeded to upload the information to a centralized server.

The release of iOS 8 saw refinements to the privacy settings, and introduced a new feature that allows the user to control when an application can access location information. The possible values are

  • The application is never allowed access to location information.
  • The app is allowed access only while the app is in the foreground and in use.
  • The app can access location information all the time.

This additional granularity can prevent a malicious application acting as a tracking device, monitoring a user’s movements in the background, and perhaps shows how Apple may refine access to other data classes in the future.

Jailbreaking Explained

On iOS, access to the device is tightly locked down; a user is unable to get interactive access to the device or operating system. In addition, the ecosystem is to an extent governed by Apple and the guidelines of the App Store. For this reason, an online community has focused on alleviating these constraints by releasing jailbreaks to the public. In a nutshell, jailbreaking removes the limitations in iOS by providing users with root-level access to their device. Many misconceptions exist about what jailbreaking your device entails technically. This section provides an insight into jailbreaking, the various terminologies you will encounter, and briefly explains some of the previous public jailbreaks. For an in-depth analysis of the jailbreaking process, review the iOS Hacker’s Handbook 10(ISBN 978-1118204122, Miller et al; 2012).

Reasons for Jailbreaking

Perhaps the most common reason for users to jailbreak a device is to get access to a host of applications that would not meet the compliance checks imposed by the App Store. Jailbreaking your device allows you to install applications from unofficial marketplaces such as Cydia. These applications are not restricted by Apple’s compliance vetting and can therefore use banned APIs or perform powerful customization or personalization of the interface.

A slightly darker side to jailbreaking also exists: piracy. Piracy is a powerful driver for many users. Jailbreaking your device allows you to circumvent the code signing restrictions that prohibit running applications other than those signed by Apple. Jailbroken devices have no such restrictions meaning that you can download and run cracked applications that you would normally have to pay for if acquired via Apple’s App Store.

In the past, jailbreaking has also given users access to functionality or features that they may not otherwise be able to access or be required to pay the carrier for. A good example of this is tethering, which up until the personal hotspot feature was introduced in to iOS was a feature that had to be enabled by the carrier. Indeed, this feature is still only supported on a subset of devices. Furthermore, in the past jailbreaking also provided some users with the ability to carrier unlock their device using utilities such as ultrasn0w (http://theiphonewiki.com/wiki/Ultrasn0w).

Accessing such utilities can be an appealing prospect for many users so it is understandable why people choose to jailbreak their devices. However, downsides exist to jailbreaking. By performing a jailbreak the user fundamentally weakens the security of the operating system. Jailbreaks create the ability for unsigned code—that is, code that has not been vetted by Apple—to run on the device. The installation of tweaks such as AppSync facilitates the installation of unsigned IPA packages, where the integrity of the creator cannot always be validated. From a security perspective this is clearly a concern as it opens the device to a number of potential risks, the most obvious being malware. By courtesy of the rigorous vetting performed as part of the App Store submission process, iOS users have been relatively unaffected by malware to date. There have been few examples of malware affecting non-jailbroken devices. The majority of the identified iOS malware samples have affected jailbroken devices only:

  • iKee—This was the first iPhone worm; it targeted jailbroken devices that had the SSH service running and where the users had not changed the default credentials for the device. In this instance the malware was relatively benign and simply changed the lock screen background to an image of Rick Astley (http://theiphonewiki.com/wiki/Ikee-virus).
  • iKee.B—This malware compromised devices via the Secure Shell (SSH) service in a similar way as the iKee malware did. However, the intentions of this variant were much more malicious; the malware turned the device into a bot, communicating back to a Lithuanian-hosted Command and Control (C&C) server. The malware was also reported to redirect Dutch ING Direct customers to a malicious phishing site in order to steal user account information (http://www.f-secure.com/weblog/archives/00001822.html).
  • Unflod Baby Panda—In April 2014 a piece of malware believed to have Chinese origins was identified. This malware, nicknamed “Unflod Baby Panda” due to the name of the library, took the form of a Cydia Substrate tweak and hooked key functions from the security framework to steal users’ Apple ID and password. Stefan Esser provides a brief analysis of this malware at https://www.sektioneins.de/en/blog/14-04-18-iOS-malware-campaign-unflod-baby-panda.html.

Types of Jailbreaks

Shortly after the release of the original iPhone in July 2007, people began to focus on jailbreaks. The majority of the released jailbreaks have relied on physical access to the device to achieve initial code execution. These jailbreaks have required a USB connection and are therefore less likely to be used against an unwitting victim. Examples of these types of jailbreaks include the evasi0n jailbreak (http://evasi0n.com/iOS6/), which initially exploited an issue in the MobileBackup service, and the Pangu (http://en.pangu.io/) jailbreak that used an expired enterprise certificate to install an application and get initial userland code execution on the device. Although much less common, several other userland exploits can be triggered remotely, without the knowledge of the user—namely the three JailbreakMe exploits, released by comex (https://github.com/comex).

At a high level, jailbreaks can be categorized in three ways depending on the type of persistence they provide. The jailbreak community has coined the terms untethered, tethered, and semi-tethered to describe the level of persistence on the device a jailbreak affords:

  • Untethered jailbreak—This type of jailbreak is the most desirable for users and also the most difficult to achieve. It persists on the device across reboots, which has historically been achieved using one of two techniques. The first technique involves the use of a low level bootloader image that is modified to perform no validation of the iBoot image, which in turn allows an unsigned kernel to be loaded. This is the same technique used by jailbreaks that leverage the 0x24000 Segment Overflow vulnerability detailed in http://theiphonewiki.com/wiki/0x24000_Segment_Overflow. The second technique first uses a userland exploit, such as that used by the Corona exploit (http://theiphonewiki.com/wiki/Racoon_String_Format_Overflow_Exploit) to initially get arbitrary code execution; a kernel exploit is then subsequently used to patch the kernel and place it into a jailbroken state. As previously noted, an untethered jailbreak persists each time a device is rebooted without the need of any additional exploitation or assistance from a connected computer.
  • Tethered jailbreaks—This type of jailbreak is not persistent across reboots and requires the assistance of a computer to start the device. In a tethered jailbreak the kernel is not persistently patched or patched on the fly and if the device attempts to boot on its own it can get stuck in recovery mode. Essentially, the device must be re-jailbroken each time it is rebooted or turned on and without this it is essentially useless. An example of a tethered jailbreak is the limera1n exploit by geohot (http://www.limera1n.com/), which affects the device firmware upgrade (DFU) boot ROM in pre-A5 devices by exploiting a heap overflow in the USB stack. This jailbreak was particularly powerful because it required a hardware fix to resolve and therefore provided the platform upon which many other untethered jailbreaks were based, such as redsn0w or limera1n untether, which used comex’s packet filter kernel exploit (http://theiphonewiki.com/wiki/Packet_Filter_Kernel_Exploit).
  • Semi-tethered jailbreaks—These jailbreaks are halfway between untethered and tethered in that although they require the assistance of a computer to start the device into a jailbroken state, rebooting or starting the device without this assistance is possible, but only into a non-jailbroken state.

Building a Test Environment

After you have a jailbroken device, you are likely to want to set up your environment to build, test, and explore iOS applications. This section details some of the tools you can use to build a basic test environment, gain access to the device as well as to the various locations of interest on the device, and the types of files that you may encounter.

Accessing the Device

You will need to log on to your jailbroken device to explore both the device and its applications and build your testing environment. The fastest way to access your device is to install the OpenSSH package (http://cydia.saurik.com/ package/openssh/) through Cydia (detailed in the following section). Predictably this causes the OpenSSH service to be installed to the device, listening on all interfaces. To connect to the service you can either join the device to your Wi-Fi network and SSH directly to it using the Wi-Fi interface, or connect to the device over the USB using the USB multiplexing daemon. If your host operating system is not OS X, the latter of these options requires the usbmuxd service to be installed, as detailed in the “Installing Applications” section of this chapter. To forward a local TCP port over the USB connection, you can use the tcprelay .py script in the usbmuxd python client or alternatively using iproxy if your host operating system is Linux, as shown in the following examples.

To forward local port 2222 to port 22 on the iOS device using tcprelay.py:

$ ./tcprelay.py 22:2222 
Forwarding local port 2222 to remote port 22

To forward local port 2222 to port 22 on the iOS device using iproxy:

$ iproxy 2222 22

When the port forwarding is enabled, you can connect to the device simply by using SSH to connect to the port being forwarded on the localhost:

$ ssh -p 2222 root@localhost

Every iOS device comes with a default password of “alpine” for the root and mobile user accounts, which you can use to access the device over SSH. To avoid someone inadvertently accessing your device, you should change these passwords after your first logon.

Building a Basic Toolkit

Tools are an important part of any security professional’s arsenal and when assessing an iOS application, installing some basic tools can make your life a little easier. Some of these are relatively unique to iOS, whereas others you may be more familiar with if you have had exposure to other UNIX-like systems.

Cydia

Cydia (https://cydia.saurik.com/) is an alternative to Apple’s App Store for jailbroken devices and is installed with many of the jailbreak applications. Cydia comes in the form of an iOS application that provides a graphic user interface to the popular Advanced Packaging Tool (APT). You may be familiar with APT as it is widely used for package management in other UNIX-like systems such as Linux’s Debian distribution. Cydia allows you to install a variety of precompiled packages for your iOS device, including applications, extensions, and command-line tools. Software packages are bundled in the deb file format; you can download them from any Cydia repository. Repositories can be configured using the Sources option within the Cydia user interface. Cydia provides a window to install many of the other tools that you can use in your test environment, as detailed in the following sections.

BigBoss Recommended Tools

When you first log on to your iOS device you will discover that many of the command-line tools that you may be used to finding on other UNIX-like systems are missing. This is because iOS is stripped back to the bare bones and includes only necessary tools used by the operating system and associated services. To make iOS a little more user friendly you can install the BigBoss recommended tools package from http://apt.thebigboss.org/onepackage.php?bundleid=bigbosshackertools. This package does nothing itself but has a number of useful dependencies registered against it, which means that these all get installed in one fell swoop. The package contains essential command-line utilities such as those included in the coreutils, system-cmds, and adv-cmds packages, all created as part of saurik’s Telesphoreo project (http://www.saurik.com/id/1). The BigBoss package also forces the install of the apt package; for those familiar with Debian’s package management system, this provides the command-line tools to install, update, and remove other packages.

Apple’s CC Tools

During the course of an iOS application assessment, you likely will need to analyze or manipulate the application binary. Apple’s CC Tools project (http://www.opensource.apple.com/source/cctools/) provides an open source toolkit to do exactly that, containing a number of utilities to parse, assemble, and link Mach-O binaries (the file format used by iOS/OS X applications). If you do any development on a Mac you are likely familiar with many of these utilities because they come as part of the iOS and OS X development toolchain. CC Tools can also be compiled under Linux when used as part of the iPhone-Dev project’s toolchain (https://code.google.com/p/iphone-dev/). The following sections briefly describe some of the tools contained in the toolchain, along with practical examples.

otool

otool, the object file-displaying tool, is the Swiss army knife of Mach-O binary analysis. It contains the necessary functionality to parse the Mach-O file format and inspect the relevant properties of a binary or library. The following examples describe how to use otool to extract assessment-relevant information from a decrypted application binary (outputs truncated for brevity):

  • Inspect the Objective-C segment to reveal class and method names:

    $ otool -oV MAHHApp 
    MAHHApp (architecture armv7): 
    Contents of (__DATA,__objc_classlist) section 
    0000c034 0xc5cc _OBJC_CLASS_$_ViewController 
               isa 0xc5e0 _OBJC_METACLASS_$_ViewController 
        superclass 0x0 
             cache 0x0 
            vtable 0x0 
              data 0xc098 (struct class_ro_t *) 
                        flags 0x80 
                instanceStart 158 
                 instanceSize 158 
                   ivarLayout 0x0 
                         name 0xbab9 ViewController 
                  baseMethods 0xc078 (struct method_list_t *) 
                    entsize 12 
                      count 2 
                       name 0xb3f8 viewDidLoad 
                      types 0xbaff v8@0:4 
                        imp 0xafd1 
                       name 0xb404 didReceiveMemoryWarning 
                      types 0xbaff v8@0:4 
                        imp 0xb015 
    baseProtocols 0x0 
                        ivars 0x0 
               weakIvarLayout 0x0 
               baseProperties 0x0
  • List the libraries used by the binary:

    $ otool -L MAHHApp 
    MAHHApp (architecture armv7): 
      /System/Library/Frameworks/CoreGraphics.framework/CoreGraphics 
    (compatibility version 64.0.0, current version 600.0.0) 
      /System/Library/Frameworks/UIKit.framework/UIKit (compatibility version 
    1.0.0, current version 2935.137.0) 
      /System/Library/Frameworks/Foundation.framework/Foundation 
    (compatibility version 300.0.0, current version 1047.25.0) 
      /usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 
    228.0.0) 
      /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 
    1198.0.0)
  • List the symbols exported by a binary:

    $ otool -IV MAHHApp 
    MAHHApp (architecture armv7): 
    Indirect symbols for (__TEXT,__symbolstub1) 9 entries 
    address    index name 
    0x0000bfdc   111 _UIApplicationMain 
    0x0000bfe0   103 _NSStringFromClass 
    0x0000bfe4   113 _objc_autoreleasePoolPop 
    0x0000bfe8   114 _objc_autoreleasePoolPush 
    0x0000bfec   116 _objc_msgSendSuper2 
    0x0000bff0   117 _objc_release 
    0x0000bff4   118 _objc_retain 
    0x0000bff8   119 _objc_retainAutoreleasedReturnValue 
    0x0000bffc   120 _objc_storeStrong
  • Display the short-form header information:

    $ otool -hV MAHHApp 
    MAHHApp (architecture armv7): 
    Mach header 
          magic cputype cpusubtype  caps    filetype ncmds sizeofcmds 
    flags 
       MH_MAGIC     ARM         V7  0x00     EXECUTE    22       2212 
    NOUNDEFS DYLDLINK TWOLEVEL PIE 
    MAHHApp (architecture armv7s): 
    Mach header 
          magic cputype cpusubtype  caps    filetype ncmds sizeofcmds 
    flags 
       MH_MAGIC     ARM        V7S  0x00     EXECUTE    22       2212 
    NOUNDEFS DYLDLINK TWOLEVEL PIE 
    MAHHApp (architecture cputype (16777228) cpusubtype (0)): 
    Mach header 
          magic cputype cpusubtype  caps    filetype ncmds sizeofcmds 
    flags 
    MH_MAGIC_64 16777228          0  0x00     EXECUTE    22       2608 
    NOUNDEFS DYLDLINK TWOLEVEL PIE
  • Display the binary load commands:

    $ otool -l MAHHApp 
    MAHHApp (architecture armv7): 
    Load command 0 
          cmd LC_SEGMENT 
      cmdsize 56 
      segname __PAGEZERO 
       vmaddr 0x00000000 
       vmsize 0x00004000 
      fileoff 0 
     filesize 0 
      maxprot 0x00000000 
     initprot 0x00000000 
       nsects 0 
        flags 0x0
nm

The nm utility can be used to display the symbol table of a binary or object file. When you use it against an unencrypted iOS application, it reveals the class and method names of the application, preceded by a + for class methods and – for instance methods:

$ nm MAHHApp 
MAHHApp (for architecture armv7): 
0000b368 s  stub helpers 
0000b1f0 t -[AppDelegate .cxx_destruct] 
0000b058 t -[AppDelegate application:didFinishLaunchingWithOptions:] 
0000b148 t -[AppDelegate applicationDidBecomeActive:] 
0000b0e8 t -[AppDelegate applicationDidEnterBackground:] 
0000b118 t -[AppDelegate applicationWillEnterForeground:] 
0000b0b8 t -[AppDelegate applicationWillResignActive:] 
0000b178 t -[AppDelegate applicationWillTerminate:] 
0000b1c4 t -[AppDelegate setWindow:] 
0000b1a8 t -[AppDelegate window] 
0000b2c4 t -[MAHHClass dummyMethod] 
0000b21c t -[MAHHClass initWithFrame:] 
0000b014 t -[ViewController didReceiveMemoryWarning] 
0000afd0 t -[ViewController viewDidLoad]
lipo

On occasion, you may be required to manipulate the architectures that are compiled into a binary. lipo allows you to combine or remove architecture types from an application. This is discussed in greater detail within the “Analyzing iOS Binaries” section of this chapter. Here are a couple brief examples of how to use lipo:

  • Print the architectures in a binary:

    $ lipo -info MAHHApp 
    Architectures in the fat file: MAHHApp are: armv7 armv7s (cputype 
    (16777228) cpusubtype (0))
  • Remove all but the listed architecture types from a binary:

    $ lipo -thin <arch_type> -output MAHHApp-v7 MAHHApp

Debuggers

When you’re assessing an application, attaching a debugger can be a powerful technique for understanding the application’s inner workings. A couple of debuggers work on iOS, and the one that works best for you will depend upon what you are trying to debug and the resources available to you. If you have done any debugging on UNIX-like platforms or debugged an iOS application under Xcode, you are likely familiar with the tools used for debugging: gdb or lldb. We briefly discuss how to set up these debuggers under iOS as opposed to detailing how to extensively use them.

The version of gdb in the default Cydia repositories does not work well with newer versions of iOS; indeed, it is somewhat broken and not maintained. However, alternate repositories with custom compiled versions of gdb are available. The one we have had the most success with is maintained by pancake of radare and can be installed by adding radare’s Cydia repository as a source (http://cydia.radare.org).

If you do not have success with this version of gdb you can use Apple’s version that is distributed with Xcode, as documented by pod2g (http://www.pod2g.org/2012/02/working-gnu-debugger-on-ios-43.html). However, because Apple has transitioned to lldb, you must retrieve a copy from a previous version of Xcode, which you can find in the iOS developer portal. The caveat is that these versions of gdb are limited to 32-bit devices. After you have the required gdb binary, usually found under /Developer/Platforms/iPhoneOS .platform/Developer/usr/libexec/gdb/gdb-arm-apple-darwin, you must thin the binary to the required architecture, which you can do using lipo:

$ lipo -thin armv7 gdb-arm-apple-darwin -output gdb-arm7

Tools for Signing Binaries

All code running on an iOS device must be signed. Unless this requirement is explicitly disabled, it still applies to jailbroken devices to some extent. However, in the case of jailbroken devices the code signing verification has been relaxed to allow self-signed certificates. Therefore, when you modify a binary or build or upload tools to the device, you must ensure that they are code signed to satisfy this requirement. To achieve this you can use a couple of tools, namely codesign and ldid.

Apple provided the codesign tool and it is likely to be the one most OS X users are familiar with as it comes bundled with OS X. You can use this multi-purpose tool for creating, checking, or displaying the status of a code-signed binary.

  • To sign or replace an existing signature, use the following command:

    $ codesign -v -fs "CodeSignIdentity" MAHHApp.app/ 
    MAHHApp.app/: replacing existing signature 
    MAHHApp.app/: signed bundle with Mach-O universal (armv7 armv7s 
    (16777228:0)) [com.mdsec.MAHHApp]
  • To display the code signature of an application:

    $ codesign -v -d MAHHApp.app 
    Executable=/MAHHApp.app/MAHHApp 
    Identifier=com.mdsec.MAHHApp 
    Format=bundle with Mach-O universal (armv7 armv7s (16777228:0)) 
    CodeDirectory v=20100 size=406 flags=0x0(none) hashes=14+3 
    location=embedded 
    Signature size=1557 
    Signed Time=20 Jul 2014 22:29:52 
    Info.plist entries=30 
    TeamIdentifier=not set 
    Sealed Resources version=2 rules=5 files=8 
    Internal requirements count=2 size=296

If you do not have access to OS X fear not; saurik developed ldid as a pseudo-signing alternative (http://www.saurik.com/id/8) to codesign. ldid generates and applies the SHA1 hashes that are verified by the iOS kernel when checking a code-signed binary, and it can be compiled for a number of platforms. To sign a binary with ldid use the following command:

$ ldid –S MAHHApp

Installipa

The normal process of installing an application to the device involves using the installd service, which independently verifies the code signature of the application. During an application assessment, you may need to install an IPA package that isn’t code signed or where the signature has been invalidated. You can, however, circumvent this process on jailbroken devices using ipainstaller (https://github.com/autopear/ipainstaller). Note that this requires the installation of AppSync, available from the Cydia repository http://cydia.appaddict.org, a Cydia substrate tweak that disables code signing within installd by hooking the MISValidateSignatureAndCopyInfo function where the signature verification is performed. (Similar techniques will be detailed in Chapter 3, Attacking iOS Applications.) To install an application, simply run ipainstaller against the IPA file from a root shell on the device:

# ipainstaller Lab1.1a.ipa 
Analyzing Lab1.1a.ipa... 
Installing lab1.1a (v1.0)... 
Installed lab1.1a (v1.0) successfully.

Exploring the Filesystem

Although performing a mobile application assessment using a jailbroken device is always recommended, for various reasons this may not always be possible. On non-jailbroken devices you can still access certain portions of the filesystem, including the sandboxed area where applications are installed; this can facilitate some basic investigations into what, if any, persistent storage is being performed by the application. To access the filesystem, the device must first be paired with a host computer, although this is relatively seamless to you we briefly describe the process next.

To prevent unauthorized access to the device, iOS requires you to pair it with a desktop first. Without this process, you could connect a locked device to your computer using the USB connection and extract sensitive user data. This would clearly be a huge security issue and would leave personal data at risk on lost or stolen devices. The pairing process works by creating a trust relationship between the device and the client; this is achieved by the desktop and device exchanging a set of keys and certificates that are later used to establish and authenticate an SSL channel through which subsequent communication is performed. Before iOS 7 the pairing process could be instigated simply by plugging the device into a compatible device, which need not necessarily be a desktop, but also includes things like media players. iOS 7 introduced some added security by prompting the user to trust the plugged-in device, thereby removing the likelihood of a user unwittingly pairing to an unknown device such as a public charging point. If the user trusts the desktop and then goes on to unlock the device, the aforementioned key exchange is initiated and creates a pairing record. This record is then stored on the desktop and the device. The pairing record is never deleted from the device, which means that any previously paired devices will always have access to the device’s filesystem and if the pairing record is compromised, the attacker will also be afforded the same level of access. The pairing record also contains an escrow keybag, which is generated by the device and passed to the host during the first unlock. It contains a copy of the protection class keys used by the device to encrypt data using the Data Protection API (discussed later in this chapter). However, at a high level you should realize that the pairing record is a powerful resource that can be used to access even encrypted files on the device. For further information on how this process works, refer to the presentation by Mathieu Renard at http://2013.hackitoergosum.org/presentations/Day3-04.Hacking%20apple%20accessories%20to%20pown%20iDevices%20%E2%80%93%20Wake%20up%20Neo!%20Your%20phone%20got%20pwnd%20!%20by%20Mathieu%20GoToHack%20RENARD.pdf.

After the pairing completes, you will be able to mount the /dev/disk0s1s2 device, which gives you access to the third-party resources such as applications, media, the SMS database, and other data stored under the /private/var mount point. You can use a number of tools to mount this filesystem on non-jailbroken devices; popular solutions include iExplorer (http://www.macroplant.com/iexplorer/) and iFunBox (http://www.i-funbox.com/).

If you are using a jailbroken device the easiest way to get access to the whole of the device’s filesystem is to install SSH and log in as the root user as noted earlier in this chapter. During your explorations of the filesystem, a number of locations are likely to be of interest, some of which are listed in Table 2.1.

Table 2.1 Interesting Filesystem Locations

DIRECTORY DESCRIPTION
/Applications System applications
/var/mobile/Applications Third-party applications
/private/var/mobile/Library/Voicemail Voicemails
/private/var/mobile/Library/SMS SMS data
/private/var/mobile/Media/DCIM Photos
/private/var/mobile/Media/Videos Videos
/var/mobile/Library/AddressBook/AddressBook .sqlitedb Contacts database

During your adventures in exploring the iOS filesystem, you’re likely to encounter a number of different file types, some of which you may be familiar with, and others that may be more alien or Apple specific.

Property Lists

Property lists are used as a form of data storage and are commonly used in the Apple ecosystem under the .plist file extension. The format is similar to XML and can be used to store serialized objects and key value pairs. Application preferences are often stored in the /Library/Preferences directory (relative to the application’s data directory) as property lists using the NSDefaults class.

Property lists can be parsed using the plutil utility as shown here:

# plutil com.google.Authenticator.plist 
{ 
    OTPKeychainEntries =     ( 
    ); 
    OTPVersionNumber = "2.1.0"; 
}

You can store the property list file in a binary format; however, you can convert this to XML to allow easy editing using the following:

$ plutil -convert xml1 com.google.Authenticator.plist

To convert the file back to the binary plist format, simply use the binary1 format:

$ plutil -convert binary1 com.google.Authenticator.plist

Binary Cookies

Binary cookies can be created by the URL loading system or webview as part of an HTTP request in a similar way to standard desktop browsers. The cookies get stored on the device’s filesystem in a cookie jar and are found in the /Library/Cookies directory (relative to the application sandbox) in the Cookies.binarycookies file. As the name suggests, the cookies are stored in a binary format but can be parsed using the BinaryCookieReader .py script (http://securitylearn.net/wp-content/uploads/tools/iOS/BinaryCookieReader.py).

SQLite Databases

SQLite is widely used for client-side storage of data in mobile applications and you are almost certain to use it at some point. SQLite allows developers to create a lightweight client-side database that can be queried using SQL, in a similar way to other mainstream databases such as MySQL and Oracle.

You can query SQLite databases using the sqlite3 client, available in saurik’s Cydia repository:

# sqlite3 ./Databases.db 
SQLite version 3.7.13 
Enter ".help" for instructions 
sqlite> .tables 
Databases  Origins

Understanding the Data Protection API

The protection of data stored on a mobile device is perhaps one of the most important issues that an application developer has to deal with. Protecting sensitive data stored client-side in a secure manner is imperative. Apple has recognized this requirement and to facilitate secure storage it has provided developers with an API that uses the built-in hardware encryption. Unfortunately, finding applications (even from large multinationals) that store their sensitive data in cleartext is still common. The Register highlighted a good example of this in 2010 when vulnerabilities in the Citigroup online banking application caused it to be pulled from the App Store:

“In a letter, the U.S. banking giant said the Citi Mobile app saved user information in a hidden file that could be used by attackers to gain unauthorized access to online accounts. Personal information stored in the file could include account numbers, bill payments and security access codes. . . . ”

Citigroup says its iPhone app puts customers at risk
(http://www.theregister.co.uk/2010/07/27/
citi_iphone_app_weakness/
)

At a basic level, file encryption in iOS is achieved by generating a per-file encryption key. Each file encryption key is then locked with a protection class that is assigned to it by the developer. The protection classes govern when the class keys are kept in memory and can be used to encrypt/decrypt the file encryption keys and by consequence, the individual files. In devices with an A7 or later chip, the key management is performed by the Secure Enclave, maintaining the integrity of the data protection even if the kernel has been compromised. The Data Protection system uses a Password-Based Key Derivation Function 2 (PBKDF2) algorithm to generate a passcode key, which uses a device-specific key known as the UID key and the user’s passcode as input. The UID key itself cannot be accessed by software on the device; instead it is embedded in the device’s hardware-based crypto accelerator. The UID key is also used to encrypt a static byte string to generate the device key; this key is then used to encrypt all the protection class keys along with, in some cases, the passcode key. The passcode key is held in memory until the device is locked meaning that the keys that it encrypts are available only while the device is unlocked. Figure 2.4 summarizes this process, courtesy of the iOS Hackers Handbook.

images

Figure 2.4 The data protection key hierarchy

You can assign the relevant protection class to individual files using the Data Protection API, which allows four levels of filesystem protection. The classes are configurable by passing an extended attribute to the NSData or NSFileManager classes. The possible levels of protection are listed here:

As of iOS 7, files are created with the Complete Until First User unlock protection class by default. To apply one of the levels of protection, you must pass one of the extended attributes from Table 2.2 to either the NSData or NSFileManager class.

Table 2.2 File Protection Classes

NSDATA NSFILEMANAGER
NSDataWritingFileProtectionNone  NSFileProtectionNone 
NSDataWritingFileProtectionComplete  NSFileProtectionComplete 
NSDataWritingFileProtectionCompleteUnlessOpen  NSFileProtectionCompleteUnlessOpen 
NSDataWritingFileProtectionCompleteUntilFirstUserAuthentication  NSFileProtectionCompleteUntilFirstUserAuthentication 

The following code shows an example of how to set the protection class attribute on a file that is downloaded and stored in the documents directory:

-(BOOL) getFile 
{ 
    NSString *fileURL = @"https://www.mdsec.co.uk/pdfs/wahh-live.pdf"; 
    NSURL  *url = [NSURL URLWithString:fileURL]; 
    NSData *urlData = [NSData dataWithContentsOfURL:url]; 
    if ( urlData ) 
    { 
        NSArray     *paths = 
NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, 
 NSUserDomainMask, 
YES); 
        NSString    *documentsDirectory = [paths objectAtIndex:0]; 
        NSString  *filePath = [NSString stringWithFormat:@"%@/%@", 
documentsDirectory,@"wahh-live.pdf"]; 
        NSError *error = nil; 
        [urlData writeToFile:filePath 
options:NSDataWritingFileProtectionComplete error:&error]; 
        return YES; 
    } 
    return NO; 
}

In this example the document is accessible only while the device is unlocked. The OS provides a 10-second window between locking the device and this file being unavailable. The following shows an attempt to access the file while the device is locked:

$ ls -al Documents/ total 372 
drwxr-xr-x 2 mobile mobile    102 Jul 20 15:24 ./ 
drwxr-xr-x 6 mobile mobile    204 Jul 20 15:23 ../ 
-rw-r--r-- 1 mobile mobile 379851 Jul 20 15:24 wahh-live.pdf 
$ strings Documents/wahh-live.pdf 
strings: can't open file: Documents/wahh-live.pdf 
(Operation not permitted)

You apply a protection class to data stored on the device in a similar manner to the preceding example by passing the relevant attribute that best fits the requirement for file access.

Understanding the iOS Keychain

The iOS keychain is an encrypted container used for storing sensitive data such as credentials, encryption keys, or certificates. In a similar way to the encryption of files, you can apply a protection level to keychain items using the Data Protection API. The following list describes the available accessibility protection classes for keychain items:

You can add keychain items using the SecItemAdd or update them using the SecItemUpdate methods, which accept one of the preceding attributes to define the protection class to apply. As of iOS 7, all keychain items are created with a protection class of kSecAttrAccessibleWhenUnlocked by default, which allows access to the keychain item only when the device is unlocked. If a protection class is marked as ThisDeviceOnly, the keychain item is nonmigratable; that is, it will not be synchronized to other devices or to iTunes backups. iOS 8 introduced a new protection class, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, that allows you to create keychain items that are accessible only when a passcode is set and the user has authenticated to the device. If a keychain item is stored using this protection class and the user later removes the passcode, the key protecting these items is destroyed from the Secure Enclave, which prevents these items being decrypted again.

To prevent any application on the device from accessing the keychain items of other applications, access is restricted by the entitlements they are granted. The keychain uses application identifiers stored in the keychain-access-group entitlement of the provisioning profile for the application; a sample provisioning profile that allows keychain access only to that specific application’s keychain is shown here:

<?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>keychain-access-group</key> 
    <array> 
        <string>$(AppIdentifierPrefix)com.mdsec.mahhapp</string> 
    </array> 
</dict> 
</plist>

Sometimes applications need to share keychain items; a good example of this would be an organization with a suite of applications that require single sign-on. This can be done by using a shared keychain group. Each of the applications must just simply have the same value set keychain group. As previously noted, the keychain uses application identifiers to set the access groups; these are configured by the provisioning portal on the iOS developer center, must be unique to that organization, and typically are done using a reverse top-level domain (TLD) format. As such, this control prevents a malicious developer attempting to create an App Store application with another application’s keychain access group.

An application can add an item to the keychain using the SecItemAdd method; consider the following example app that wants to store a license key in the keychain and only requires access to the item when the device is unlocked:

- (NSMutableDictionary *)getkeychainDict:(NSString *)service { 
    return [NSMutableDictionary dictionaryWithObjectsAndKeys: 
            (id)kSecClassGenericPassword, (id)kSecClass, 
service,(id)kSecAttrService, service, (id)kSecAttrAccount, 
(id)kSecAttrAccessibleWhenUnlocked, (id)kSecAttrAccessible, nil]; 
} 
 
- (BOOL) saveLicense:(NSString*)licenseKey { 
    static NSString *serviceName = @"com.mdsec.mahhapp"; 
    NSMutableDictionary *myDict = [self getkeychainDict:serviceName]; 
    SecItemDelete((CFDictionaryRef)myDict); 
    NSData *licenseData = [licenseKey dataUsingEncoding: 
NSUTF8StringEncoding]; 
    [myDict setObject:[NSKeyedArchiver archivedDataWithRootObject: 
licenseData] forKey:(id)kSecValueData]; 
    OSStatus status = SecItemAdd((CFDictionaryRef)myDict, NULL); 
    if (status == errSecSuccess) return YES; 
    return NO; 
}

The application creates a dictionary of key-value pairs that are the configuration attributes for the keychain. In this instance the application sets the kSecAttrAccessibleWhenUnlocked attribute to allow access to the keychain item whenever the device is unlocked. The application then sets the kSecValueData attribute to the value of the data that it wants to store in the keychain—in this instance the license key data—and adds the item to the keychain using the SecItemAdd method.

Access Control and Authentication Policies in iOS 8

In addition to the accessibility protection classes for keychain items, Apple introduced the concept of access control and authentication policies for iOS 8 applications. This new authentication policy controls what happens when a keychain item is accessed. Developers can now force the user to perform authentication by passcode or Touch ID before the keychain item can be accessed. This prompts the user with an authentication screen when the keychain item is being accessed and by virtue should only be used for keychain items that require the device to be unlocked, as the user interface must be accessible. The access control policy is set by a new keychain attribute, kSecAttrAccessControl that is represented by the SecAccessControlRef object. To create the access control policy for the keychain item, this object must be populated with the options that define the authentication and accessibility that is required.

The authentication policy in iOS 8 defines what has to be done before the keychain item is decrypted and returned to the application. Currently the only available authentication policy is the user presence (kSecAccessControlUserPresence) policy, which uses the Secure Enclave to determine which type of authentication must be done. This policy prevents access to items when no passcode is set on the device, and requires entry of the passcode. If a device passcode is set for devices supporting Touch ID and fingerprints are enrolled, this authentication method is preferred. If Touch ID is unavailable then a backup mechanism using the device’s passcode is available. Table 2.3 summarizes the user presence policy.

Table 2.3 User Presence Policy

DEVICE CONFIGURATION POLICY EVALUATION BACKUP MECHANISM
Device without passcode No access No backup
Device with passcode Requires passcode entry No backup
Device with Touch ID Prefers Touch ID entry Allows passcode entry

The following code shows an example of how to add a keychain item using an access control policy. In this example the keychain item is accessible only when the device has a passcode set and the user enters the device’s passcode or authenticates via Touch ID:

CFErrorRef error = NULL; 
SecAccessControlRef sacObject = 
SecAccessControlCreateWithFlags(kCFAllocatorDefault, 
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, 
kSecAccessControlUserPresence, &error); 
 
NSDictionary *attributes = @{ 
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, 
(__bridge id)kSecAttrService: @"MAHHService", 
 (__bridge id)kSecValueData: [@"secretpassword" dataUsingEncoding: 
NSUTF8StringEncoding], (__bridge id)kSecUseNoAuthenticationUI: @YES, 
(__bridge id)kSecAttrAccessControl: (__bridge id)sacObject 
}; 
 
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 
0), ^(void){ 
    OSStatus status =  SecItemAdd((__bridge CFDictionaryRef)attributes, 
nil); 
});

First the SecAccessControlRef object is populated with the accessibility and access control options; this is then added to the keychain using the methods previously described and using the global queue.

Accessing the iOS Keychain

Under the hood, the keychain is simply a SQLite database stored in the /var/Keychains directory, and it can be queried like any other database. For example, to find the list of the keychain groups execute the following query:

# sqlite3 keychain-2.db "select agrp from genp" 
com.apple.security.sos 
apple 
apple 
apple 
apple 
ichat 
com.mdsec.mahhapp 
mdsecios:/var/Keychains root#

On a jailbroken phone, you can dump all the keychain items for any application under the same caveats previously detailed with the Data Protection API. You do it by creating an application that is assigned a wildcard keychain-access-groups and querying the keychain service to retrieve the protected items. This is the technique used by the keychain_dumper tool (https://github.com/ptoomey3/Keychain-Dumper), which uses the “*” wildcard for the keychain-access-groups value of the entitlements file. Here is a sample usage showing the items that keychain_dumper can retrieve:

# ./keychain_dumper -h 
Usage: keychain_dumper [-e]|[-h]|[-agnick] 
<no flags>: Dump Password Keychain Items (Generic Password, Internet 
Passwords) 
-a: Dump All Keychain Items (Generic Passwords, Internet Passwords, 
Identities, Certificates, and Keys) 
-e: Dump Entitlements 
-g: Dump Generic Passwords 
-n: Dump Internet Passwords 
-i: Dump Identities 
-c: Dump Certificates 
-k: Dump Keys 
mdsecios:~ root#

Using keychain_dumper to access the generic passwords, keychain items can sometimes reveal application credentials, as shown in the following example:

Generic Password 
---------------- 
Service: 
Account: admin 
Entitlement Group: com.mdsec.mahhapp 
Label: 
Generic Field: mahhapp 
Keychain Data: secret

Because the keychain is simply a SQLite database, reading the encrypted data directly from the database and then decrypting it using the AppleKeyStore service, which is exposed via the MobileKeyBag private framework, is also possible. This is the approach taken by the keychain_dump tool developed by Jean-Baptiste Bedrune and Jean Sigwald (https://code.google.com/p/iphone-dataprotection/source/browse/?repo=keychainviewe). Simply running the keychain_dump tool causes it to generate a number of plist files that provide a verbose description on each of the keychain items:

#  ./keychain_dump 
Writing 7 passwords to genp.plist 
Writing 0 internet passwords to inet.plist 
Writing 0 certificates to cert.plist 
Writing 4 keys to keys.plist

Understanding Touch ID

Touch ID is a fingerprint recognition feature that was introduced with the iPhone 5s; you access it by pressing the home button on the device. The Touch ID sensor provides the user with an alternative means of authentication to entering the device passcode and can be used to unlock the device, approve App Store and iBooks purchases, and—as of iOS 8—be integrated as a means of authentication to third-party applications.

The Secure Enclave holds cryptographic material such as the data protection class keys. When a device is locked the key material for the complete protection class is discarded, meaning that these items cannot be accessed until the user unlocks the device again. On a device with Touch ID enabled, however, the keys are not discarded but held in memory, wrapped using a key that is available only to the Touch ID subsystem. When the user attempts to unlock the device using Touch ID, if the fingerprint is matched, the Touch ID subsystem provides the key for unwrapping the complete data protection class and by proxy the device. Through this simplistic process, the Touch ID system is able to unlock the device and provide access to data-protected resources. Note, however, that the Touch ID system is not infallible and has indeed been proven to be breakable by an attacker who is able to procure fingerprints and has physical access to the device (http://www.ccc.de/en/updates/2013/ccc-breaks-apple-touchid).

Earlier in this chapter you learned how Touch ID authentication can be used with the keychain. However, using the Touch ID sensor as a form of authentication using the LocalAuthentication framework is also possible. Some subtle differences exist in how these implementations work—primarily the trust relationship is between the application and the OS as opposed to the Secure Enclave as is with the keychain; applications have no direct access to the Secure Enclave or the registered fingerprints. If this was not the case it could give rise to a malicious application extracting and exfiltrating device fingerprints, which would clearly be a huge security concern.

The LocalAuthentication framework API implements two key methods relevant to Touch ID:

Similarly to the keychain, a policy is available on which to base the authentication: LAPolicyDeviceOwnerAuthenticationWithBiometrics. This policy, however, has no passcode-based fallback authentication mechanism, and you should implement your own within the application.

The following example demonstrates how you can implement Touch ID authentication using the LocalAuthentication framework:

LAContext *myCxt = [[LAContext alloc] init]; 
NSError * authErr = nil; 
NSString *myLocalizedReasonString = @"Please Authenticate"; 
if ([myCxt canEvaluatePolicy: 
LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&authErr]) { 
    [myCxt evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics 
localizedReason:myLocalizedReasonString reply:^(BOOL success, NSError 
*error) { 
            if (success) { 
                NSLog(@"Fingerprint recognised"); 
            } else { 
                  switch (error.code) { 
                       case LAErrorAuthenticationFailed: 
                           NSLog(@"Fingerprint unrecognised"); 
                           break; 
 
                       case LAErrorUserCancel: 
                           NSLog(@"User cancelled authentication"); 
                           break; 
 
                       case LAErrorUserFallback: 
                           NSLog(@"User requested fallback authentication"); 
                           break; 
 
                       default: 
                           NSLog(@"Touch ID is not enabled"); 
                           break; 
                    } 
                NSLog(@"Authentication failed"); 
            } 
        }]; 
    } else { 
   NSLog(@"Touch ID not enabled"); 
}

You should be aware that because the trust relationship is with the OS as opposed to the Secure Enclave (and as with any client-side authentication), it can be bypassed in situations whereby an attacker has compromised the device.

Reverse Engineering iOS Binaries

A blackbox assessment of any iOS application will almost certainly require some degree of reverse engineering to gain the necessary understanding of the inner workings of the application. In this section we review the different types of iOS binaries that you may encounter, how to get these binaries into a format that you can work with, and how to identify some security-relevant features in these binaries.

Analyzing iOS Binaries

As documented in earlier sections, iOS applications compile to native code using the Mach-O file format, similar to that used in the OS X operating system. Multiple Mach-O files can be archived in one binary to provide support for different architectures; these are known as fat binaries. Applications that are downloaded from the App Store will also be encrypted and later decrypted at run time, on-device by the loader. A brief introduction to the Mach-O file format appears in the following section. If, however, you prefer an in-depth analysis then we recommend you refer to the file format reference as documented by Apple (https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/MachORuntime/Reference/reference.html).

At a high-level the Mach-O file format is composed of three major regions (graphically illustrated in Figure 2.5):

  • Header—This is the first region of a Mach-O. It is used to identify the file format, and details the architecture and other basic information that can be used to interpret the rest of the file.
  • Load commands—Directly following the header are a number of load commands that detail the layout and linkage specifications for the file. The load commands specify, among other things, the location of the symbol table, information on the encrypted segments of the file, names of shared libraries, and the initial layout of the file in virtual memory.
  • Data—Following the load commands are one or more segments consisting of a number of sections; these contain the code or data that subsequently gets mapped to virtual memory by the dynamic linker.
images

Figure 2.5 The Mach-O file format

Fat binaries exist to provide support for many devices because the CPU can differ between iOS hardware. Currently, the latest Apple CPU is the A8 Cyclone chip, which supports armv8, otherwise known as arm64 instructions. This chip is present only in the iPhone 6 and iPhone 6 Plus devices. An application compiled with only arm64 support would therefore only work on these and A7 chip devices and as you can see from Table 2.4, architecture support across devices can vary significantly. Without fat binaries an organization would need to submit device-specific releases of an application to the App Store. The architectures that you are most likely to encounter during your assessments are arm7, armv7s, and arm64; these provide support for the devices shown in Table 2.4.

Table 2.4 Architecture Support in Modern iOS Devices

ARCHITECTURE IPHONE IPOD TOUCH IPAD IPAD MINI
Armv7 3GS, 4, 4S, 5, 5C, 5S 3rd, 4th, 5th generation All versions All versions
Armv7s 5, 5C, 5S No support 4th generation, Air 2nd generation
Arm64 5S, 6, 6 Plus No support Air 2nd generation

To identify the architectures compiled into a fat binary you can use otool to print the Mach-O header information, as shown here:

mdsecmbp:mahhswiftapp.app shell$ otool -hv mahhswiftapp 
mahhswiftapp (architecture armv7): 
Mach header 
      magic cputype cpusubtype  caps    filetype ncmds sizeofcmds 
flags 
   MH_MAGIC     ARM         V7  0x00     EXECUTE    31       2908 
NOUNDEFS DYLDLINK TWOLEVEL BINDS_TO_WEAK PIE 
mahhswiftapp (architecture armv7s): 
Mach header 
      magic cputype cpusubtype  caps    filetype ncmds sizeofcmds 
flags 
   MH_MAGIC     ARM        V7S  0x00     EXECUTE    31       2908 
NOUNDEFS DYLDLINK TWOLEVEL BINDS_TO_WEAK PIE 
mahhswiftapp (architecture cputype (16777228) cpusubtype (0)): 
Mach header 
      magic cputype cpusubtype  caps    filetype ncmds sizeofcmds 
flags 
MH_MAGIC_64 16777228          0  0x00     EXECUTE    31       3376 
NOUNDEFS DYLDLINK TWOLEVEL BINDS_TO_WEAK PIE 
mdsecmbp:mahhswiftapp.app shell$

In this example, the mahhswitftapp binary archive contains three architectures: armv7, armv7s, and arm64. On occasion, otool is unable to determine the architecture correctly, as in the previous example where it doesn’t explicitly display the arm64 CPU type. You can use Table 2.5 as a point of reference to identify unknown architectures.

Table 2.5 ARM Architectures

ARCHITECTURE CPU TYPE CPU SUBTYPE
ARMv6 12 6
ARMv7 12 9
ARMv7S 12 11
ARM64 16777228 0

You may find that you need to remove one or more architectures from a binary. For example, many of the current tools for manipulating and attacking iOS applications lack arm64 support because it’s a relatively new introduction to the iOS device family. You can, however, remove whole architectures from a fat binary using lipo. The following example extracts the armv7 architecture from the previous archive and saves it in a new binary:

$ lipo -thin armv7 mahhswiftapp -output mahhswiftappv7

If you print the header output on the newly created binary, you can see it only contains the armv7 slice:

$ otool -hv mahhswiftappv7 
mahhswiftappv7: 
Mach header 
      magic cputype cpusubtype  caps    filetype ncmds sizeofcmds 
flags 
   MH_MAGIC     ARM         V7  0x00     EXECUTE    31       2908 
NOUNDEFS DYLDLINK TWOLEVEL BINDS_TO_WEAK PIE 
$

Identifying Security-Related Features

Earlier in this chapter we described some of the platform security features that exist in the iOS operating system. However, a number of other security configurations exist that applications can optionally take advantage of to further increase their built-in protection against memory corruption vulnerabilities, as detailed in the following sections.

Position-Independent Executable

Position-Independent Executable (PIE) is an exploit mitigation security feature that allows an application to take full advantage of ASLR. For this to happen, the application must be compiled using the —fPIC —pie flag; using XCode this can be enabled/disabled by setting the value of the Generate Position-Dependent Code option from the Compiler Code Generation Build setting. An application compiled without PIE loads the executable at a fixed address. Consider the following simple example that prints the address of the main function:

int main(int argc, const char* argv[]) 
{ 
    NSLog(@"Main: %p\n", main); 
    return 0; 
}

If you compile this without PIE and run it on an iOS device, despite systemwide ASLR, the main executable remains loaded at a fixed address:

# for i in 'seq 1 5'; do ./nopie-main;done 
2014-03-01 16:56:17.772 nopie-main[8943:707] Main: 0x2f3d 
2014-03-01 16:56:17.805 nopie-main[8944:707] Main: 0x2f3d 
2014-03-01 16:56:17.837 nopie-main[8945:707] Main: 0x2f3d 
2014-03-01 16:56:17.870 nopie-main[8946:707] Main: 0x2f3d 
2014-03-01 16:56:17.905 nopie-main[8947:707] Main: 0x2f3d

If you recompile the same application with PIE enabled, the application loads the main executable at a dynamic address:

# for i in 'seq 1 5'; do ./pie-main;done 
2014-03-01 16:57:32.175 pie-main[8949:707] Main: 0x2af39 
2014-03-01 16:57:32.208 pie-main[8950:707] Main: 0x3bf39 
2014-03-01 16:57:32.241 pie-main[8951:707] Main: 0x3f39 
2014-03-01 16:57:32.277 pie-main[8952:707] Main: 0x8cf39 
2014-03-01 16:57:32.310 pie-main[8953:707] Main: 0x30f39

From a blackbox perspective, you can verify the presence of PIE using the otool application, which provides functionality to inspect the Mach-O header as shown in earlier examples. For the two test applications, you can use otool to compare the headers of the two binaries and the output:

# otool -hv pie-main nopie-main 
pie-main: 
Mach header 
      magic cputype cpusubtype  caps    filetype ncmds sizeofcmds 
flags 
   MH_MAGIC     ARM          9  0x00     EXECUTE    18       1948 
NOUNDEFS DYLDLINK TWOLEVEL PIE 
 
nopie-main: 
Mach header 
      magic cputype cpusubtype  caps    filetype ncmds sizeofcmds 
flags 
   MH_MAGIC     ARM          9  0x00     EXECUTE    18       1948 
NOUNDEFS DYLDLINK TWOLEVEL

Since iOS 5, all the built-in Apple applications are compiled with PIE by default; however, in practice many third-party applications do not take advantage of this protection feature.

Stack-Smashing Protection

A further binary protection that iOS application can apply at compile time is stack-smashing protection. Enabling stack-smashing protection causes a known value or “canary” to be placed on the stack directly before the local variables to protect the saved base pointer, saved instruction pointer, and function arguments. The value of the canary is then verified when the function returns to see whether it has been overwritten. The LLVM compiler uses a heuristic to intelligently apply stack protection to a function, typically functions using character arrays. Stack-smashing protection is enabled by default for applications compiled with recent versions of Xcode.

From a black box perspective you can identify the presence of stack canaries by examining the symbol table of the binary. If stack-smashing protection is compiled into the application, two undefined symbols will be present: ___stack_chk_fail and ___stack_chk_guard. You can observe the symbol table using otool:

$ otool -I -v simpleapp | grep stack 
0x00001e48    97 ___stack_chk_fail 
0x00003008    98 ___stack_chk_guard 
0x0000302c    97 ___stack_chk_fail 
$

Automatic Reference Counting

Automatic Reference Counting (ARC) was introduced in iOS SDK version 5.0 to move the responsibility of memory management and reference counting from the developer to the compiler. As a side effect ARC also offers some security benefits because it reduces the likelihood of developers’ introducing memory corruption (specifically, object use-after-free and double-free) vulnerabilities into applications.

ARC can be enabled globally within an Objective-C application within Xcode by setting the compiler option Objective-C Automatic Reference Counting to Yes. ARC can also be enabled or disabled on a per-object file basis using the —fobjc-arc or —fno-objc-arc compiler flags. Swift applications require ARC, a setting enabled by default when you create a Swift application project in Xcode.

To identify the presence of ARC in a blackbox review of a compiled application, you can look for the presence of ARC-related symbols in the symbol table, as shown here:

$  otool -I -v test-swift | grep release 
0x0000ffa4   551 _objc_autoreleaseReturnValue 
0x0000ffcc   562 _objc_release

A number of runtime support functions exist for ARC; however, some common ones that you are likely to observe are:

  • objc_retainAutoreleaseReturnValue
  • objc_autoreleaseReturnValue
  • objc_storeStrong
  • objc_retain
  • objc_release
  • objc_retainAutoreleasedReturnValue

Be aware that because ARC can be applied on a per-object file basis, identifying the presence of these symbols does not necessarily guarantee that ARC is used globally across all application classes. For more information on the ARC run time, consult the LLVM documentation http://clang.llvm.org/docs/AutomaticReferenceCounting.html#runtime-support.

2.8.3 Decrypting App Store Binaries

When an application is released to the App Store, Apple applies its FairPlay Digital Rights Management (DRM) copy scheme to protect the application against piracy. The result of this is an encrypted application where the internal code structures are not immediately visible to someone attempting to reverse the application. In this section you learn how to bypass this protection, providing a platform for you to go on and reverse engineer the application.

Decrypting iOS Binaries Using a Debugger

Applications originating from the App Store are protected by Apple’s binary encryption scheme. These apps are decrypted at run time by the kernel’s Mach-O loader; as such recovering the decrypted files is a relatively straightforward process. Removing this encryption allows the attacker to get a greater understanding of how the binary works, the internal class structure, and how to get the binary in a suitable state for reverse engineering. You can remove the App Store encryption by letting the loader decrypt the application, then using lldb or gdb attach to the process and dump the cleartext application from memory.

You can identify encrypted binaries by the value in the cryptid field of the LC_ENCRYPTION_INFO load command. We will now walk you through an example of decrypting the ProgCalc calculator application (https://itunes.apple.com/gb/app/progcalc-rpn-programmer-calculator/id294256032?mt=8):

# otool -l ProgCalc | grep -A 4 LC_ENCRYPTION_INFO 
          cmd LC_ENCRYPTION_INFO 
      cmdsize 20 
 cryptoff  4096 
 cryptsize 53248 
 cryptid   0
  1. To retrieve the decrypted segment of the ProgCalc application, you must first let the loader run and perform its decryption routines, and then attach to the application. You can do this by running the application on the device and using the attach command in gdb:

    (gdb) attach 963 
    Attaching to process 963. 
    Reading symbols for shared libraries . done 
    Reading symbols for shared libraries 
    ........................................................... 
    ........................................................... 
    .....................................................  done 
    Reading symbols for shared libraries + done 
    0x3ac22a58 in mach_msg_trap () 
    (gdb)
    

    At this stage, the loader has decrypted the application and you can dump the cleartext segments directly from memory. The location of the encrypted segment is specified by the cryptoff value in the LC_ENCRYPTION_INFO load command, which gives the offset relative to the header. You will need to take this value and add it to the base address of the application.

  2. To find the base address you can use the following command:

    (gdb) info sharedlibrary 
    The DYLD shared library state has not yet been initialized. 
                                                 Requested State Current State 
    Num Basename                   Type Address         Reason | | Source 
      | |                             | |                    | | | | 
      1 ProgCalc                      - 0x1000            exec Y Y 
    /private/var/mobile/Applications/659087B4-510A-475D-A50F- 
    F4476464DB79/ProgCalc.app/ProgCalc (offset 0x0)

    In this example, the ProgCalc image is loaded at a base address of 0x1000. Consequently, the encrypted segment begins at offset 0x2000 or 8192 decimal (base address of 0x1000 plus the cryptoff of 0x1000). The address range to extract from memory is simply the address of the start of the encrypted segment, plus the size of the encrypted segment that is specified by the cryptsize variable (53248 or 0xD000 hex), resulting in an end address of 0xF000 (0x2000 + 0xD000).

  3. You can retrieve the decrypted segment using the dump memory GDB command:

    (gdb) dump memory ProgCalc.decrypted 8192 61440 
    (gdb)

    The resultant file should be exactly the same size as your cryptsize value.

  4. The decrypted section can then be written to the original binary, replacing the original encrypted segment:

    # dd seek=4096 bs=1 conv=notrunc if=ProgCalc.decrypted of=ProgCalc 
    53248+0 records in 
    53248+0 records out 
    53248 bytes (53 kB) copied, 1.05688 s, 50.4 kB/s

    Finally, the cryptid value must be set to 0 to denote that the file is no longer encrypted and the loader should not attempt to decrypt it. Using a hex editor such as vbindiff (available in saurik’s Cydia repository), you must search for the location of the LC_ENCRYPTION_INFO command; find it by searching for the hex bytes 2100000014000000. From this location, flip the cryptid value to 0, which is located 16 bytes in advance of the cmdsize (0x21000000). At this stage your binary should be decrypted, and you can view the internal class structure, which is covered in greater detail in the following section of this chapter.

Automating the Decryption Process

Manually decrypting an application as described in the previous section can be quite a laborious and potentially error-prone task. This is why a number of researchers have developed tools to automate this process; some common examples include Clutch and the now defunct Crackulous application. However, our solution of choice is the dumpdecrypted tool developed by Stefan Esser (https://github.com/stefanesser/dumpdecrypted). This solution works by using the dynamic linker to inject a constructor into the application, which goes on to automatically parse the LC_ENCRYPTION_INFO load command and extract the decrypted segment in a similar way to the method described in the previous section.

To use dumpdecrypted simply run the application and use the DYLD_INSERT_LIBRARIES environment variable to inject the dumpdecrypted dynamic library, as shown here:

# DYLD_INSERT_LIBRARIES=dumpdecrypted.dylib 
/var/mobile/Applications/C817EEF7-D01F-4E70-BE17- 
07C28B8D28E5/ProgCalc.app/ProgCalc 
mach-o decryption dumper 
 
DISCLAIMER: This tool is only meant for security research purposes, 
not for application crackers. 
 
[+] offset to cryptid found: @0x1680(from 0x1000) = 680 
[+] Found encrypted data at address 00001000 of length 53248 bytes - type 
1. 
[+] Opening /private/var/mobile/Applications/C817EEF7-D01F-4E70-BE17- 
07C28B8D28E5/ProgCalc.app/ProgCalc for reading. 
[+] Reading header 
[+] Detecting header type 
[+] Executable is a plain MACH-O image 
[+] Opening ProgCalc.decrypted for writing. 
[+] Copying the not encrypted start of the file 
[+] Dumping the decrypted data into the file 
[+] Copying the not encrypted remainder of the file 
[+] Setting the LC_ENCRYPTION_INFO->cryptid to 0 at offset 680 
[+] Closing original file 
[+] Closing dump file

The tool generates a decrypted copy in the current working directory. You can verify that the application has been decrypted by checking the value of the cryptid variable, which should now be set to 0:

# otool -l ProgCalc.decrypted | grep -A 4 LC_ENCRYPT 
          cmd LC_ENCRYPTION_INFO 
      cmdsize 20 
 cryptoff  4096 
 cryptsize 53248 
 cryptid   0

Inspecting Decrypted Binaries

Now that you are comfortable with the methods for decrypting iOS applications, we now detail how to use the decrypted application to discover more about its inner workings.

Inspecting Objective-C Applications

Within a decrypted Objective-C binary, a wealth of information exists in the __OBJC segment that can be useful to a reverse engineer. The __OBJC segment provides details on the internal classes, methods, and variables used in the application; this information is particularly useful for understanding how the application functions, when patching it or hooking its methods at run time.

You can parse the __OBJC segment using the class-dump-z (https://code.google.com/p/networkpx/wiki/class_dump_z) application. For example, running the previously decrypted ProgCalc application through class-dump-z yields details on the internal class structure, including the following:

@interface RootViewController : 
{ 
    ProgCalcViewController *progcalcViewController; 
    ProgCalcDriver *driver; 
    AboutViewController *aboutViewController; 
    EditTableViewController *editTableViewController; 
    UIBarButtonItem *doneButton; 
    UIBarButtonItem *upgradeButton; 
    UIBarButtonItem *saveButton; 
} 
 
- (void)dealloc; 
- (void)loadView; 
- (void)viewDidLoad; 
- (void)loadAboutViewController; 
- (void)upgrade; 
- (void)toggleAbout; 
- (void)loadEditViewController; 
- (void)toggleEdit; 
- (void)writeState; 
- (BOOL)shouldAutorotateToInterfaceOrientation:(int)fp8; 
- (void)didReceiveMemoryWarning; 
- (id)driver; 
- (void)setDriver:(id)fp8; 
- (id)editTableViewController; 
- (void)setEditTableViewController:(id)fp8; 
- (id)aboutViewController; 
- (void)setAboutViewController:(id)fp8; 
- (id)progcalcViewController; 
- (void)setProgcalcViewController:(id)fp8; 
 
@end

In the previous snippet class-dump-z identifies a number of methods in the RootViewController class, which gives you a fantastic insight into the application’s internals. In Chapter 3 you learn how by using this information you can invoke, modify, and tamper with these methods at run time.

Inspecting Swift Applications

As has been previously mentioned, Apple announced the release of Swift, a new programming language for use alongside iOS 8. At the time of writing iOS 8 is still in beta and little research has been released on the format or structure of Swift binaries, nor are many tools available to parse them in a similar way to Objective-C applications. At the 2014 World Wide Developer Conference Apple suggested that the Swift language and syntax might change in the future; the information presented within this section is accurate at the time of writing but could potentially be affected by future changes to the language.

Unlike Objective-C applications, Swift not only uses the traditional message passing system; this is only used for Swift classes that inherit from Objective-C classes. Swift classes use a mixture of two approaches: direct function calls and vtables. Where the compiler does not necessarily have enough information to form a direct function call or inline the function, Swift classes use vtables to handle dynamic dispatch; those of you familiar with C++ may be aware of this approach. In this instance, the vtable acts as an array of function pointers. The vtable is constructed during compilation and the function’s pointers are inserted into the vtable array in the order that they are declared. The compiler converts any method calls into a vtable lookup by index during the compilation process. This has some side effects: the most obvious being the impact on method swizzling, which Chapter 3 covers.

Consider the following simple Swift class:

class MAHH { 
    func sayHello(personName: String) -> String { 
        return "Hello " + personName + "!" 
    } 
 
    func helloMAHH() 
    { 
        println(sayHello("MAHH reader")) 
    } 
}

If you compile this class in a Swift application and use the latest version of class-dump to parse it (taken from swift-binaries branch of https://github.com/0xced/class-dump/tree/swift-binaries), you will see that the MAHH Swift class is actually an Objective-C object and has a superclass of SwiftObject, which is a new root class introduced with the Swift run time:

__attribute__((visibility("hidden"))) 
@interface MAHH : SwiftObject 
{ 
 
} 
@end

You can then modify your Swift class to subclass an Objective-C class, in this case NSObject, by making the following alteration,

class MAHH : NSObject {

then rerunning the class-dump of the application will produce a more familiar result, and in this instance you can see the class methods:

__attribute__((visibility("hidden"))) 
@interface MAHH : NSObject 
{ 
} 
 
- (id)init; 
- (void)helloMAHH; 
- (id)sayHello:(id)arg1; 
 
@end

As you can see Swift is adaptable and may use different approaches for dynamic dispatch depending upon the use case. But what about the methods for Swift classes that do not inherit from Objective-C? If you compile the first example again as a debug build, you can inspect the symbol table of the application using nm to find the following:

$ nm mahh-swift | grep -i mahh 
0000b710 T __TFC10mahh_swift4MAHH8sayHellofS0_FSSSS 
0000b824 T __TFC10mahh_swift4MAHH9helloMAHHfS0_FT_T_

Swift uses C++–like name-mangled functions for methods. The naming convention for the function carries metadata about the function, attributes, and more. Using the helloMAHH function from the earlier example, the mangled name can be broken down as follows:

__TFC10mahh_swift4MAHH9helloMAHHfS0_FT_T_
  • _T is the prefix indicating that it is a Swift symbol.
  • F indicates that it is a function.
  • C indicates that it is a function belonging to a class.
  • 10mahh_swift is the module name prefixed with a length.
  • 4MAHH is the class name prefixed with a length.
  • 9helloMAHH is the function name prefixed with a length.
  • f is the function attribute; in this case, it indicates it’s a normal function.
  • S0_FT is currently not publicly documented.
  • _ separates the argument types from the return type; because this function takes no arguments, it comes directly after the S0_FT.
  • T_ is the return type; in this case it specifies a void return. If S is used it specifies a Swift built-in type.

You can find a number of other values for this metadata detailed in http://www.eswick.com/2014/06/inside-swift/; some possible values for function attributes and Swift built-in types are listed in Table 2.6 and Table 2.7.

Table 2.6 Function Attributes

CHARACTER TYPE
f Normal Function
s Setter
g Getter
d Destructor
D Deallocator
c Constructor
C Allocator

Table 2.7 Swift Built-in Types

CHARACTER TYPE
a Array
b Boolean
c UnicodeScalar
d Double
f Float
i Integer
u Unsigned Integer
Q ImplicitlyUnwrappedOptional
S String

Xcode also ships with the swift-demangle tool, which you can use to demangle a mangled symbol:

$ swift-demangle -expand __TFC10mahh_swift4MAHH9helloMAHHfS0_FT_T_ 
Demangling for _TFC10mahh_swift4MAHH9helloMAHHfS0_FT_T_ 
kind=Global 
  kind=Function 
    kind=Class 
      kind=Module, text="mahh_swift" 
      kind=Identifier, text="MAHH" 
    kind=Identifier, text="helloMAHH" 
    kind=Type 
      kind=UncurriedFunctionType 
        kind=Class 
          kind=Module, text="mahh_swift" 
          kind=Identifier, text="MAHH" 
        kind=ReturnType 
          kind=Type 
            kind=FunctionType 
              kind=ArgumentTuple 
                kind=Type 
                  kind=NonVariadicTuple 
              kind=ReturnType 
                kind=Type 
                  kind=NonVariadicTuple 
_TFC10mahh_swift4MAHH9helloMAHHfS0_FT_T_ —> 
mahh_swift.MAHH.helloMAHH (mahh_swift.MAHH)() -> ()

Release builds are likely to be stripped, which will discard the name mangled symbols from the binary and make reverse engineering a much more time-consuming task.

2.8.5 Disassembling and Decompiling iOS Applications

As you will now no doubt be aware, iOS applications compile to native code. This means that to reverse engineer them, you must disassemble and decompile your target application. This level of in-depth reverse engineering is beyond the scope of this book; indeed whole publications are dedicated to this topic alone. However, you should be aware of a couple of tools that will help get you started in reverse engineering a native code application, both of which have excellent support for pseudo-code generation of ARM assembler:

  • IDA Pro is the weapon of choice for many reverse engineers and is capable of parsing the Objective-C segment to provide accurate class and method names. When armed with the Hex-Rays decompiler, IDA is capable of giving a quite accurate pseudo-code representation of the target application.
  • Hopper is similar to IDA but has support for Linux and OS X. It has equivalent functionality for parsing and accurately renaming Objective-C functions as well as an excellent pseudo-code generator.

For further information on how to use Hopper and an introduction to static binary analysis, review the blog post by @0xabad1dea (http://abad1dea.tumblr.com/post/23487860422/analyzing-binaries-with-hoppers-decompiler).

Summary

Having studied this chapter you should now have a good understanding of how iOS applications work and are distributed. You should also have familiarity with the iOS security model, including the many security features that come with the platform. This will allow you to apply context to any vulnerabilities that you find when assessing an app.

Furthermore, this chapter provided you with the necessary background information so that you may build your own test environment, using your own device. Armed with this knowledge, you will be able to install applications to begin exploring and start to spot basic vulnerabilities.

This chapter also introduced how iOS applications operate at a binary level, including the various compiled based defenses that can be applied to applications, as well as how the Mach-O file format itself is structured. You were also introduced to the App Store encryption mechanism and how to remove it from a production binary, allowing you to obtain the internal class and method definitions from the app.

In summary this chapter has given you the foundation knowledge required to start practically looking at iOS applications and is essential stepping-stone to attacking them, a skill you will now learn in Chapter 3.