hacktricks/mobile-apps-pentesting/ios-pentesting/ios-basics.md

18 KiB
Raw Blame History

iOS Basics

Privilege Separation and Sandbox

Applications the user can access run as the mobile user while critical system processes run as root.
However, the sandbox allows better control over actions that processes and applications can perform.

For example, even if two processes run as the same user (mobile), they are not allowed to access or modify each other's data.

Each application is installed under private/var/mobile/Applications/{random ID}
Once installed, applications have limited read access to some system areas and functions (SMS, phone call...). If an application wants to access a protected area, a pop-up requesting permission appears.

Data Protection

App developers can leverage the iOS Data Protection APIs to implement **fine-grained access control **for user data stored in flash memory. The APIs are built on top of the Secure Enclave Processor (SEP). The SEP is a coprocessor that provides cryptographic operations for data protection and key management. A device-specific hardware key-the device UID (Unique ID)-is embedded in the secure enclave, ensuring the integrity of data protection even when the operating system kernel is compromised.

When a file is created on the disk, a new **256-bit AES key is generated **with the help of secure enclave's hardware based random number generator. The content of the file is then encrypted with the generated key. And then, this key is saved encrypted with a class key along with **the class ID, with both data encrypted by the system's key, **inside the metadata of the file.

For decrypting the file, the metadata is decrypted using the system's key. Then using the class ID the class key is retrieved to decrypt the per-file key and decrypt the file.

Files can be assigned to one of four different protection classes, which are explained in more detail in the iOS Security Guide:

  • Complete Protection (NSFileProtectionComplete): A key derived from the user passcode and the device UID protects this class key. The derived key is wiped from memory shortly after the device is locked, making the data inaccessible until the user unlocks the device.
  • Protected Unless Open (NSFileProtectionCompleteUnlessOpen): This protection class is similar to Complete Protection, but, if the file is opened when unlocked, the app can continue to access the file even if the user locks the device. This protection class is used when, for example, a mail attachment is downloading in the background.
  • Protected Until First User Authentication (NSFileProtectionCompleteUntilFirstUserAuthentication): The file can be accessed as soon as the user unlocks the device for the first time after booting. It can be accessed even if the user subsequently locks the device and the class key is not removed from memory.
  • No Protection (NSFileProtectionNone): The key for this protection class is protected with the UID only. The class key is stored in "Effaceable Storage", which is a region of flash memory on the iOS device that allows the storage of small amounts of data. This protection class exists for fast remote wiping (immediate deletion of the class key, which makes the data inaccessible).

All class keys except NSFileProtectionNone are encrypted with a key derived from the device UID and the user's passcode. As a result, decryption can happen only on the device itself and requires the correct passcode.

Since iOS 7, the default data protection class is "Protected Until First User Authentication".

****FileDP is a program that you can upload and use inside the IPhone to inspect the data protection class of each file.

The Keychain

A keychain is an encrypted container where every application can store sensitive pieces of information and only the same app (or authorised apps) can retrieve the contents.
The iOS generated its own password for the keychain and stores an encrypted version of this key in the device. This password is encrypted with AES using an AES key created by a PBKDF2 function of the user's passcode + salt (the 256 bit device UID only accessible to the secure enclave chipset on the device). Due to the use of this device UID as salt, a device won't be able to decrypt the keychain of a different device even knowing the users passcode.

Access to the Keychain is managed by the securityd daemon, which grants access according to the app's Keychain-access-groups, application-identifier, and application-group entitlements.

The Keychain API includes the following main operations:

  • SecItemAdd
  • SecItemUpdate
  • SecItemCopyMatching
  • SecItemDelete

The only ways to try to BF this password is dumping the encrypted key and BF the passcode + salt (the pbkdf2 function uses at least 10000 iterations). Or trying to BF inside the device to avoids BFing the salt, however, secure enclave ensures there is at least a 5s delay between 2 failed password attempts.

You can configure data protection for Keychain items by setting the kSecAttrAccessible key in the call to SecItemAdd or SecItemUpdate.The following configurable accessibility values for kSecAttrAccessible are the Keychain Data Protection classes:

  • kSecAttrAccessibleAlways: The data in the Keychain item can always be accessed, regardless of whether the device is locked.
  • kSecAttrAccessibleAlwaysThisDeviceOnly: The data in the Keychain item can always be accessed, regardless of whether the device is locked. The data won't be included in an iCloud or local backup.
  • kSecAttrAccessibleAfterFirstUnlock: The data in the Keychain item can't be accessed after a restart until the device has been unlocked once by the user.
  • kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly: The data in the Keychain item can't be accessed after a restart until the device has been unlocked once by the user. Items with this attribute do not migrate to a new device. Thus, after restoring from a backup of a different device, these items will not be present.
  • kSecAttrAccessibleWhenUnlocked: The data in the Keychain item can be accessed only while the device is unlocked by the user.
  • kSecAttrAccessibleWhenUnlockedThisDeviceOnly: The data in the Keychain item can be accessed only while the device is unlocked by the user. The data won't be included in an iCloud or local backup.
  • kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly: The data in the Keychain can be accessed only when the device is unlocked. This protection class is only available if a passcode is set on the device. The data won't be included in an iCloud or local backup.

AccessControlFlags define the mechanisms with which users can authenticate the key (SecAccessControlCreateFlags):

  • kSecAccessControlDevicePasscode: Access the item via a passcode.
  • kSecAccessControlBiometryAny: Access the item via one of the fingerprints registered to Touch ID. Adding or removing a fingerprint won't invalidate the item.
  • kSecAccessControlBiometryCurrentSet: Access the item via one of the fingerprints registered to Touch ID. Adding or removing a fingerprint will invalidate the item.
  • kSecAccessControlUserPresence: Access the item via either one of the registered fingerprints (using Touch ID) or default to the passcode.

Please note that keys secured by Touch ID (via kSecAccessControlBiometryAny or kSecAccessControlBiometryCurrentSet) are protected by the Secure Enclave: The Keychain holds a token only, not the actual key. The key resides in the Secure Enclave.

The iPhone uses the passcode introduced by the user unlocking the device to decrypt the secrets in the keychain.

iOS uses the AppIdentifierPrefix (Team ID) and the BundleIdentifier (provided by the dev) to enforce access control oven keychain items. Then, the same team can configure 2 apps to share keychain items.

When a backup process is initiated the keychain data backed up remains encrypted and the keychain password isn't included in the backup.

{% hint style="warning" %} In a jailbroken device the keychain isn't protected. {% endhint %}

Keychain Data Persistence

On iOS, when an application is uninstalled, the Keychain data used by the application is retained by the device, unlike the data stored by the application sandbox which is wiped. In the event that a user sells their device without performing a factory reset, the buyer of the device may be able to gain access to the previous user's application accounts and data by reinstalling the same applications used by the previous user. This would require no technical ability to perform.

There's no iOS API that developers can use to force wipe data when an application is uninstalled. Instead, developers should take the following steps to prevent Keychain data from persisting between application installations:

  • When an application is first launched after installation, wipe all Keychain data associated with the application. This will prevent a device's second user from accidentally gaining access to the previous user's accounts. The following Swift example is a basic demonstration of this wiping procedure:
let userDefaults = UserDefaults.standard

if userDefaults.bool(forKey: "hasRunBefore") == false {
    // Remove Keychain items here

    // Update the flag indicator
    userDefaults.set(true, forKey: "hasRunBefore")
    userDefaults.synchronize() // Forces the app to update UserDefaults
}
  • When developing logout functionality for an iOS application, make sure that the Keychain data is wiped as part of account logout. This will allow users to clear their accounts before uninstalling an application.

App Capabilities

Each app has a unique home directory and is sandboxed, so that they cannot access protected system resources or files stored by the system or by other apps. These restrictions are implemented via sandbox policies (aka. profiles), which are enforced by the Trusted BSD (MAC) Mandatory Access Control Framework via a kernel extension.

Some capabilities/permissions can be configured by the app's developers (e.g. Data Protection or Keychain Sharing) and will directly take effect after the installation. However, for others, the user will be explicitly asked the first time the app attempts to access a protected resource.

Purpose strings or usage description strings are custom texts that are offered to users in the system's permission request alert when requesting permission to access protected data or resources.

If having the original source code, you can verify the permissions included in the Info.plist file:

  • Open the project with Xcode.
  • Find and open the Info.plist file in the default editor and search for the keys starting with "Privacy -".

You may switch the view to display the raw values by right-clicking and selecting "Show Raw Keys/Values" (this way for example "Privacy - Location When In Use Usage Description" will turn into NSLocationWhenInUseUsageDescription).

If only having the IPA:

  • Unzip the IPA.

  • The Info.plist is located in Payload/<appname>.app/Info.plist.

  • Convert it if needed (e.g. plutil -convert xml1 Info.plist) as explained in the chapter "iOS Basic Security Testing", section "The Info.plist File".

  • Inspect all purpose strings Info.plist keys, usually ending with UsageDescription:

      <plist version="1.0">
      <dict>
          <key>NSLocationWhenInUseUsageDescription</key>
          <string>Your location is used to provide turn-by-turn directions to your destination.</string>
    

Device Capabilities

Device capabilities are used by the App Store to ensure that only compatible devices are listed and therefore are allowed to download the app. They are specified in the Info.plist file of the app under the UIRequiredDeviceCapabilities key.

<key>UIRequiredDeviceCapabilities</key>
<array>
    <string>armv7</string>
</array>

Typically you'll find the armv7 capability, meaning that the app is compiled only for the armv7 instruction set, or if its a 32/64-bit universal app.

For example, an app might be completely dependent on NFC to work (e.g. a "NFC Tag Reader" app). According to the archived iOS Device Compatibility Reference, NFC is only available starting on the iPhone 7 (and iOS 11). A developer might want to exclude all incompatible devices by setting the nfc device capability.

Entitlements

Entitlements are key value pairs that are signed in to an app and allow authentication beyond runtime factors, like UNIX user ID. Since entitlements are digitally signed, they cant be changed. Entitlements are used extensively by system apps and daemons to** perform specific privileged operations that would otherwise require the process to run as root**. This greatly reduces the potential for privilege escalation by a compromised system app or daemon.

For example, if you want to set the "Default Data Protection" capability, you would need to go to the Capabilities tab in Xcode and enable Data Protection. This is directly written by Xcode to the <appname>.entitlements file as the com.apple.developer.default-data-protection entitlement with default value NSFileProtectionComplete. In the IPA we might find this in the embedded.mobileprovision as:

<key>Entitlements</key>
<dict>
    ...
    <key>com.apple.developer.default-data-protection</key>
    <string>NSFileProtectionComplete</string>
</dict>

For other capabilities such as HealthKit, the user has to be asked for permission, therefore it is not enough to add the entitlements, special keys and strings have to be added to the Info.plist file of the app.

Objective-C and Swift Basics

Objecttive-C has a dynamic runtime, so when an Objective-C program is executed in iOS, it calls libraries whose address are resolved at runtime by comparing the name of the function sent in the message against a list of all the function names available.

At the beginning, only apps created by Apple run the iPhones, so they had access to everything as they were trusted. However, when Apple allowed **third party applications, **Apple just removed the headers files of the powerful functions to "hide" them to developers. However, developers found that "safe" functions needed a few of these undocumented functions and just creating a **custom header file with the names of the undocumented functions, it was possible to invoke this powerful hidden functions. **Actually, Apple, before allowing an app to be published, check if the app calls any of these prohibited functions.

Then, Swift appeared. As Swift is statically bound (it doesn't resolve the address of the functions in runtime like Objective-C), it can be checked more easily the calls a Swift program is going to make via static code analysis.

Device Management

From iOS version 6, there is **built-in support for device management **capability with fine grain controls that allows an organisation to control the corporate apple devices.
The enrolment can be initiated by the user installing an agent in order to access the corporate apps. In this case the device usually belongs to the user.
Or the **company can indicate the serial numbers **of the bought devices or the purchase order ID and specify the MDM profile to install on those devices. Note that Apple doesn't allow to enrol a particular device this way twice. Once the first profile is deleted the user needs to give consent to install another one.

The user can see the installed policies in Settings --> General --> Profile and Device Management

As these MDM policies are checking and limiting other applications, they are running with more privileges.
A MDM policy can enforce users to have a passcode set with a minimun password complexity.
The profiles are tied to the deviceID, signed and encrypted by the MDM server and tamper proof. They cannot be removed without losing all the corporate data.
MDM profiles allow to wipe all the data if there are X failed password attempts. Also, the admin can remote wipe the iPhone whenever via the MDM interface.

MDM agents will check also for possible jailbreaks of the device, as this is very dangerous state for an iPhone.