iOS App Security

Tampering and Reverse Engineering

iOS App Security

Tampering and Reverse Engineering

This article considers key areas in mobile app security and popular anti-reverse engineering protection techniques. There are several approaches to analyzing software:

  1. Data exchange analysis using a packet sniffer to analyze data exchanged over a network.
  2. Software binary code disassembly to get its listing in assembly language.
  3. Decompilation of binary or byte-code to recreate source code in a high-level programming language.

Let’s start with understanding few basic terms used through out the article.

Data tampering is the act of deliberately modifying (destroying, manipulating, or editing) data through unauthorized channels. Data exists in two states: in transit or at rest. In both instances, data could be intercepted and tampered with. Digital communications are all about data transmission.

For example, in the instances where data packets are transmitted unprotected, a hacker can intercept the data packet, modify its contents, and change its destination address. With data at rest, a system application can suffer a security breach and an unauthorized intruder could deploy malicious code that corrupts the data or underlying programming code. In both instances, the intrusion is malicious and the effects on the data always dire. It’s one of the biggest security threats that any application, program, or organization can face.

Reverse engineering a mobile app is the process of analyzing the compiled app to extract information about its source code. The goal of reverse engineering is comprehending the code.

Key Areas in Mobile Application Security

  1. Local Data Storage

The protection of sensitive data, such as user credentials and private information, is crucial to mobile security. If an app uses operating system APIs such as local storage or inter-process communication (IPC) improperly, the app might expose sensitive data to other apps running on the same device. It may also unintentionally leak data to cloud storage, backups, or the keyboard cache. Additionally, mobile devices can be lost or stolen more easily compared to other types of devices, so it’s more likely an individual can gain physical access to the device, making it easier to retrieve the data.

2. Communication with Trusted Endpoints

Mobile devices regularly connect to a variety of networks, including public WiFi networks shared with other (potentially malicious) clients. This creates opportunities for a wide variety of network-based attacks ranging from simple to complicated and old to new. It’s crucial to maintain the confidentiality and integrity of information exchanged between the mobile app and remote service endpoints. As a basic requirement, mobile apps must set up a secure, encrypted channel for network communication using the TLS protocol with appropriate settings.

3. Code Quality and Exploit Mitigation

Traditional injection and memory management issues aren’t often seen in mobile apps due to the smaller attack surface. Mobile apps mostly interact with the trusted backend service and the UI, so even if many buffer overflow vulnerabilities exist in the app, those vulnerabilities usually don’t open up any useful attack vectors.

This protection from injection and memory management issues doesn’t mean that app developers can get away with writing sloppy code. Following security best practices results in hardened (secure) release builds that are resilient against tampering. Free security features offered by compilers and mobile SDKs help increase security and mitigate attacks.

4. Anti-Tampering and Anti-Reversing

There are three things you should never bring up in polite conversations: religion, politics, and code obfuscation. Many security experts dismiss client-side protections outright. However, software protection controls are widely used in the mobile app world, so security testers need ways to deal with these protections. We believe there’s a benefit to client-side protections if they are employed with a clear purpose and realistic expectations in mind and aren’t used to replace security controls.

iOS Application Attack surface

The iOS application attack surface consists of all components of the application, including the supportive material necessary to release the app and to support its functioning. The iOS application may be vulnerable to attack if it does not:

  1. Validate all input by means of IPC communication or URL schemes, see also:

2. Validate all input by the user in input fields.

3. Validate the content loaded inside a WebView, see also:

  • Testing iOS WebViews
  • Determining Whether Native Methods Are Exposed Through WebViews

4. Securely communicate with backend servers or is susceptible to man-in-the-middle (MITM) attacks between the server and the mobile application, see also:

  • Testing Network Communication
  • iOS Network APIs

5. Securely stores all local data, or loads untrusted data from storage, see also:

  • Data Storage on iOS

6. Protect itself against compromised environments, repackaging or other local attacks, see also:

  • iOS Anti-Reversing Defenses

Testing Network Communication

Practically every network-connected mobile app uses the Hypertext Transfer Protocol (HTTP) or HTTP over Transport Layer Security (TLS), HTTPS, to send and receive data to and from remote endpoints. Consequently, network-based attacks (such as packet sniffing and man-in-the-middle-attacks) are a problem.

Intercepting HTTP(S) Traffic

In many cases, it is most practical to configure a system proxy on the mobile device, so that HTTP(S) traffic is redirected through an interception proxy running on your host machine. By monitoring the requests between the mobile app client and the backend, you can easily map the available server-side APIs and gain insight into the communication protocol. Additionally, you can replay and manipulate requests to test for server-side vulnerabilities.

Several free and commercial proxy tools are available. Here are some of the most popular:

Using a proxy breaks SSL certificate verification and the app will usually fail to initiate TLS connections. To work around this issue, you can install your proxy’s CA certificate on the device.

Preventing Man-in-the-Middle Attacks in iOS with SSL Pinning

Testing Local Data Storage

As little sensitive data as possible should be saved in permanent local storage. However, in most practical scenarios, at least some user data must be stored. Fortunately, iOS offers secure storage APIs, which allow developers to use the cryptographic hardware available on every iOS device. If these APIs are used correctly, sensitive data and files can be secured via hardware-backed 256-bit AES encryption.

The Keychain

The iOS Keychain can be used to securely store short, sensitive bits of data, such as encryption keys and session tokens. It is implemented as an SQLite database that can be accessed through the Keychain APIs only.

The Keychain API includes the following main operations:

  • SecItemAdd
  • SecItemUpdate
  • SecItemCopyMatching
  • SecItemDelete

Data stored in the Keychain is protected via a class structure that is similar to the class structure used for file encryption. Items added to the Keychain are encoded as a binary plist and encrypted with a 128-bit AES per-item key in Galois/Counter Mode (GCM). Note that larger blobs of data aren’t meant to be saved directly in the Keychain-that’s what the Data Protection API is for. You can configure data protection for Keychain items by setting the kSecAttrAccessible key in the call to SecItemAdd or SecItemUpdate.

iOS Anti-Reversing Defenses

  1. Jailbreak Detection
  2. Anti-Debugging Checks
  3. File Integrity Checks
  4. Device Bindings

1. Jailbreak Detection

File-based Checks

Check for files and directories typically associated with jailbreaks, such as:

/Applications/Cydia.app
/Applications/FakeCarrier.app
/Applications/Icy.app
/Applications/IntelliScreen.app
/Applications/MxTube.app
/Applications/RockApp.app
/Applications/SBSettings.app
/Applications/WinterBoard.app
/Applications/blackra1n.app
/Library/MobileSubstrate/DynamicLibraries/LiveClock.plist
/Library/MobileSubstrate/DynamicLibraries/Veency.plist
/Library/MobileSubstrate/MobileSubstrate.dylib
/System/Library/LaunchDaemons/com.ikey.bbot.plist
/System/Library/LaunchDaemons/com.saurik.Cydia.Startup.plist

Checking File Permissions

Another way to check for jailbreaking mechanisms is to try to write to a location that’s outside the application’s sandbox. You can do this by having the application attempt to create a file in, for example, the /private directory. If the file is created successfully, the device has been jailbroken.

Checking Protocol Handlers

You can check protocol handlers by attempting to open a Cydia URL. The Cydia app store, which practically every jailbreaking tool installs by default, installs the cydia:// protocol handler.

Calling System APIs

Calling the system function with a "NULL" argument on a non-jailbroken device will return "0"; doing the same thing on a jailbroken device will return "1". This difference is due to the function's checking for access to /bin/sh on jailbroken devices only.

Bypassing Jailbreak Detection

Once you start an application that has jailbreak detection enabled on a jailbroken device, you’ll notice one of the following things:

  1. The application closes immediately, without any notification.
At this point, bypassing jailbreak detection with Cycript is trivial.
Note:
# Cycript allows developers to explore and modify running applications on either iOS or Mac OS X using a hybrid of Objective-C++ and JavaScript syntax through an interactive console that features syntax highlighting and tab completion.
(It also runs standalone on Android and Linux and provides access to Java, but without injection.)
http://www.cycript.org/

2. A pop-up window indicates that the application won’t run on a jailbroken device.

At this point, Frida that we will use to bypass jailbreak detection is so-called early instrumentation, that is, we will replace function implementation at startup.
Note:
#Frida Inject JavaScript to explore native apps on Windows, Mac, Linux, iOS, Android, and QNX.
https://github.com/frida

2. Anti-Debugging Checks

Debugging and exploring applications are helpful during reversing. Using a debugger, a reverse engineer can not only track critical variables but also read and modify memory.

There are several anti-debugging techniques; a few of them are discussed below.

Using ptrace

iOS runs on an XNU kernel. The XNU kernel implements a ptrace system call that's not as powerful as the Unix and Linux implementations. The XNU kernel exposes another interface via Mach IPC to enable debugging. The iOS implementation of ptrace serves an important function: preventing the debugging of processes. This feature is implemented as the PT_DENY_ATTACH option of the ptrace syscall. Using PT_DENY_ATTACH is a fairly well-known anti-debugging technique, so you may encounter it often during iOS pentests.

Using sysctl

Another approach to detecting a debugger that’s attached to the calling process involves sysctl. According to the Apple documentation:

The sysctl function retrieves system information and allows processes with appropriate privileges to set system information.

sysctl can also be used to retrieve information about the current process (such as whether the process is being debugged). The following example implementation is discussed in ["How do I determine if I'm being run under the debugger?"](https://developer.apple.com/library/content/qa/qa1361/_index.html "How do I determine if I'm being run under the debugger?")

3. File Integrity Checks

There are two topics related to file integrity:

  1. Application source code integrity checks: In the “Tampering and Reverse Engineering” chapter, we discussed the iOS IPA application signature check. We also saw that determined reverse engineers can easily bypass this check by re-packaging and re-signing an app using a developer or enterprise certificate. One way to make this harder is to add an internal run-time check that determines whether the signatures still match at run time.
  2. File storage integrity checks: When files are stored by the application, key-value pairs in the Keychain, UserDefaults/NSUserDefaults, a SQLite database, or a Realm database, their integrity should be protected.

When you generate an HMAC with CC:

  1. Get the data as NSMutableData.
  2. Get the data key (from the Keychain if possible).
  3. Calculate the hash value.
  4. Append the hash value to the actual data.
  5. Store the results of step 4.
// Allocate a buffer to hold the digest and perform the digest.
NSMutableData* actualData = [getData];
//get the key from the keychain
NSData* key = [getKey];
NSMutableData* digestBuffer = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH];
CCHmac(kCCHmacAlgSHA256, [actualData bytes], (CC_LONG)[key length], [actualData bytes], (CC_LONG)[actualData length], [digestBuffer mutableBytes]);
[actualData appendData: digestBuffer];

When verifying the HMAC with CC, follow these steps:

  1. Extract the message and the hmacbytes as separate NSData.
  2. Repeat steps 1–3 of the procedure for generating an HMAC on the NSData.
  3. Compare the extracted HMAC bytes to the result of step 1.
NSData* hmac = [data subdataWithRange:NSMakeRange(data.length - CC_SHA256_DIGEST_LENGTH, CC_SHA256_DIGEST_LENGTH)];
NSData* actualData = [data subdataWithRange:NSMakeRange(0, (data.length - hmac.length))];
NSMutableData* digestBuffer = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH];
CCHmac(kCCHmacAlgSHA256, [actualData bytes], (CC_LONG)[key length], [actualData bytes], (CC_LONG)[actualData length], [digestBuffer mutableBytes]);
return [hmac isEqual: digestBuffer];

4. Device Binding

The purpose of device binding is to impede an attacker who tries to copy an app and its state from device A to device B and continue the execution of the app on device B. After device A has been determined trusted, it may have more privileges than device B. This situation shouldn’t change when an app is copied from device A to device B.

Since iOS 7.0, hardware identifiers (such as MAC addresses) are off-limits. The ways to bind an application to a device are based on identifierForVendor, storing something in the Keychain, or using Google's InstanceID for iOS.

For more: App Obfuscator for iOS Apps

References:

  1. https://www.coredump.gr/articles/ios-anti-debugging-protections-part-1/
  2. https://www.coredump.gr/articles/ios-anti-debugging-protections-part-2/
  3. https://mobile-security.gitbook.io/mobile-security-testing-guide/ios-testing-guide/0x06j-testing-resiliency-against-reverse-engineering