iOS App Security:

Yağız Erbay
adessoTurkey
Published in
10 min readDec 25, 2023

A Detailed Guide on iOS App Security

Nowadays, security stands as a top-priority subject within the IT industry. Both individual users and corporations are displaying an increasing awareness of the concepts of data security and privacy. This emphasis extends to mobile applications, largely due to their proximity to end-users. The frequent usage and user-friendly nature of mobile applications often result in the storage of significant volumes of sensitive personal data.

While iOS is recognized as a highly secure mobile operating system, this doesn’t imply that you should forego additional security measures when creating an iOS application.

In this article, our primary focus will be learning the fundamental aspects of iOS security.

1) App Switcher Snapshot Controls

Sensitive information can potentially be revealed in the snapshot images that iOS displays during app switching. Additionally, iOS captures a screenshot each time an app is moved to the background, and this image is stored in the app’s sandbox, making it susceptible to unauthorized access by potential attackers.

Blocking the screen view

If your application showcases sensitive information, it’s essential to conceal the content when the app enters the background.

This can be achieved by making direct adjustments to the SceneDelegate file:

  • Create an imageView.
  • Insert it when your app enters the foreground.
  • Remove it when your app becomes active again.
// App Delegate
private lazy var coverView: UIImageView = {
let imageView = UIImageView(frame: self.window!.frame)
imageView.backgroundColor = UIColor.black
return imageView
}()
// …
func applicationWillResignActive(_ application: UIApplication) {
self.window?.addSubview(coverView)
}
// …
func applicationWillEnterForeground(_ application: UIApplication) {
coverView.removeFromSuperview()
}

2) Printing Logs

Developers frequently call the print() function for debugging purposes. However, a potential issue may arise when these calls are left in the code, as they may contain sensitive information that could be exploited by an attacker to compromise your security.

#if DEBUG
print("log")
#endif

It is a good practice to use compiler flags; by doing this, print() functions only work in debug mode.

3) Jailbreak Prevention Methods

iOS is engineered to provide reliability and security right from the moment your device is powered on. It incorporates built-in security mechanisms to safeguard against malware, viruses, and ensures the protection of personal and corporate data.

However, unauthorized alterations to iOS, commonly referred to as “jailbreaking,” bypass these security features and can lead to various problems, including security vulnerabilities, instability, and reduced battery life for the compromised iPhone.

There are several ways to detect and prevent jailbreaks in iOS apps:

Verifying the presence of particular files or directories: An approach to identifying jailbreak activity is by inspecting the existence of specific files or directories that are typically associated with jailbreaking tools.

if FileManager.default.fileExists(atPath: "/Applications/Cydia.app") {
// Device is jailbroken
} else {
// Device is not jailbroken
}

The directory at /Applications/Cydia.app is frequently found on jailbroken devices, as it serves as the installation location for the Cydia app. Cydia is a well-known package manager used on jailbroken devices, enabling users to explore and install third-party apps and customizations not accessible through the official App Store.

Leveraging Apple’s Provided APIs: Apple offers a range of APIs that are helpful in identifying jailbreaking activities. For instance, the canOpenURL() function within the UIApplication class is a valuable tool for verifying whether the device can access a URL known to be associated with jailbreak tools.

In the following Swift code snippet, we determine if the device can open the Cydia URL, a common indicator of jailbreaking tools:

if UIApplication.shared.canOpenURL(URL(string: "cydia://package/com.example.package")!) {
// Device is jailbroken
} else {
// Device is not jailbroken
}

The functionality of the canOpenURL() function serves as an effective technique for identifying jailbreak activity. Jailbreak tools often make alterations to the operating system, enabling the installation and operation of unauthorized applications and customizations.

Consequently, jailbroken devices possess the capability to launch URLs that are typically restricted on non-jailbroken devices. Through the evaluation of the device’s ability to open the cydia://package/com.example.package URL, developers can deduce the presence of a jailbroken device with potential system-level modifications.

Implementing Custom Verification Procedures: Developers also have the option to create their own checks to identify jailbreak activities. One approach is to examine specific system libraries or frameworks that are typically absent on non-jailbroken devices. The following Swift code example illustrates how to verify the existence of the libsubstrate.dylib library, a component commonly utilized by jailbreak tools:

let libraryPath = "/Library/MobileSubstrate/MobileSubstrate.dylib"
if FileManager.default.fileExists(atPath: libraryPath) {
// Device is jailbroken
} else {
// Device is not jailbroken
}

Beyond all that, several files and directories can be examined by developers to identify jailbreak indications in on iOS devices. Here are a few instances:

static var suspiciousSystemPathsToCheck: [String] {
return ["/Library/MobileSubstrate/DynamicLibraries/LiveClock.plist",
"/Library/MobileSubstrate/DynamicLibraries/Veency.plist",
"/private/var/lib/apt",
"/private/var/lib/apt/",
"/private/var/lib/cydia",
"/private/var/mobile/Library/SBSettings/Themes",
"/private/var/stash",
"/private/var/tmp/cydia.log",
"/System/Library/LaunchDaemons/com.ikey.bbot.plist",
"/System/Library/LaunchDaemons/com.saurik.Cydia.Startup.plist",
"/usr/bin/sshd",
"/usr/libexec/sftp-server",
"/usr/sbin/sshd",
"/etc/apt",
"/bin/bash",
"/Library/MobileSubstrate/MobileSubstrate.dylib"]
}

static func isSuspiciousSystemPathsExists() -> Bool {
for path in suspiciousSystemPathsToCheck {
if FileManager.default.fileExists(atPath: path) { return true }
}
return false
}

4) UserDefaults and Keychain

UserDefaults are saved as a property list file within the Preferences directory of your app, making them accessible in a similar way to app snapshots. The lack of security or encryption is intentional because UserDefaults aren’t intended for the storage of sensitive information. Instead, they serve as an interface for adjusting an app’s behavior in alignment with user preferences. For instance, they are commonly used for settings like preferred units of measurement or feature toggle values.

There is also the option of using property lists.
A property list file, commonly referred to as a plist file, is a specialized file format used for storing data in key-value pairs. In fact, plist files are essentially XML files with a distinct naming style within the Apple ecosystem. To draw a parallel, plist files share similarities with Swift dictionaries, which are also a collection type capable of holding key-value pairs of data. It contains a large number of default settings, configurations, and information regarding the app, but more entries can be added on demand.

But storing data in plist files in Swift can pose various security risks, including:

  1. Data Exposure: Plist files are stored in plain text and can be easily accessed. Sensitive information, such as passwords or API keys, can be exposed if not properly encrypted.
  2. Lack of Encryption: Plist files lack built-in encryption. Without encryption, the data stored in these files is vulnerable to unauthorized access.
  3. Tampering: Plist files can be modified by anyone with access to the file system. This can lead to data tampering and potentially compromise the integrity of your app’s data.
  4. No Access Control: Plist files do not offer access control mechanisms. Any process or app running on the device may access these files.
  5. Data Leaks: Improper handling of plist files can result in data leaks, where sensitive information is inadvertently exposed to other apps or to external sources.

On the other hand, we have the Keychain, specifically designed for keeping user secrets secured. The Keychain services API offers a secure method for storing small portions of users’ sensitive data within an encrypted database known as a Keychain.

In order to save and retrieve the user data, you can just use the following code.

func save(key: String, data: NSData) -> OSStatus {
let query = [
kSecClass as String : kSecClassGenericPassword as String,
kSecAttrAccount as String : key,
kSecValueData as String : data ] as [String : Any]

SecItemDelete(query as CFDictionary)
return SecItemAdd(query as CFDictionary, nil)
}

func load(key: String) -> NSData? {
let query = [
kSecClass as String : kSecClassGenericPassword,
kSecAttrAccount as String : key,
kSecReturnData as String : kCFBooleanTrue ?? false,
kSecMatchLimit as String : kSecMatchLimitOne ] as [String : Any]

var dataTypeRef:AnyObject? = nil
let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)

if let data = dataTypeRef {
if status == noErr {
return (data as? NSData)
}
}
return nil
}

Within the Keychain, you have the ability to securely store items like passwords and other sensitive information that users explicitly value.

5) Data Protection

Nevertheless, the Keychain isn’t a suitable choice when dealing with more extensive sensitive data beyond passwords or tokens. In such cases, Data Protection comes into play. Data Protection is an iOS feature designed to safeguard an app’s files and prevent unauthorized access to them. It’s automatically activated on devices secured with a password. This means that files remain unencrypted when the device is unlocked. However, even if someone attempts to access the data directly from the device’s storage, it remains inaccessible.

Data Protection secures the data stored in the flash memory. The encryption and decryption procedures are seamless, accelerated by hardware, and remain inconspicuous to the user.

Enabling data protection is not a prerequisite, since beginning with iOS 7, all application files are automatically encrypted by default. The default protection level is “complete until first user authentication”.

Usually , there are four levels of protection available:

  • No protection. The file is always accessible.
  • Complete until first user authentication. The file remains inaccessible until the user unlocks the device for the first time. Following the initial device unlock, the file stays accessible until the device is powered down or undergoes a reboot.
  • Complete unless open. You can exclusively access existing files when the device is in an unlocked state. Once a file is open, you may continue to access that file, even if the user locks the device.
  • Complete. The file is accessible only when the device is unlocked.

The protection level is selected for all the files within the app’s file system, but you have the option to programmatically override the specific file protection level.

You can encrypt a new file like in the following script:

do {
try data.write(to: fileURL, options: .completeFileProtection) // Specify protection level
}
catch {
// Handle error.
}

You can also encrypt an existing file:

do {
try (fileURL as NSURL).setResourceValue(
URLFileProtection.complete,
forKey: .fileProtectionKey) // Set URLFileProtection
}
catch {
// Handle error.
}

6) SSL (Secure Socket Layer)

SSL, which stands for Secure Socket Layer, is a protocol produced to facilitate secure communication exclusively between two devices or networks via the internet.

This protocol is used to encrypt data that is transmitted between a web server and a web browser, making sure that the confidentiality and security of the data.

For applications and websites dealing with sensitive information such as credit card details, login credentials, and personal data, SSL is crucial. SSL allows these applications and websites to effectively safeguard their users’ data, preventing interception by malicious entities.

What Is SSL Pinning?

SSL pinning is a security method that matches a specific SSL certificate with a particular web server. Its primary objective is to prevent man-in-the-middle (MITM) attacks.

SSL pinning achieves this by allowing only trusted SSL certificates to establish secure connections between a client application (e.g., an iOS app) and the server.

In an iOS app, SSL pinning guarantees that the client exclusively accepts the SSL certificate that has been pinned or its associated public key during the initiation of a secure connection with the server.

In the event that the server provides a different SSL certificate for any reason, the connection will be immediately terminated. Meaning, the client will be unable to advance with the request, even if the certificate is authorized by a trusted certificate authority (CA).

SSL pinning steps can be listed as shown below:

1. The client device initiates a connection request to the server, server listens for the incoming request.

2. Server returns with the response, including the public key and certificate.

3. Client verifies the certificate and sends an encrypted key to the server.

4. Server decrypts the key and sends encrypted data back to the client machine.

5. Client receives and decrypts the encrypted data.

Types of SSL Pinning

  • Pin the certificate: You download the server’s certificate and embed this in your app bundle. During runtime, the app compares the server’s certificate to the one you’ve embedded.
  • Pin the public key: You retrieve the certificate’s public key and embed it in your code as a string. During runtime, the app compares the certificate’s public key to the one embedded hash string in your code.

7) Code Obfuscation

Code obfuscation in iOS involves transforming the source code of an application to make it more challenging for reverse engineers to understand or decompile. This process aims to enhance the security of an iOS app by making it harder for attackers to analyze the code and discover vulnerabilities.

iXGuard

iXGuard is designed to enhance the security of your Swift and Objective-C applications and SDKs by decreasing the risks of reverse engineering and hacking. It enhances the robustness of their code, allowing for vigilant monitoring of both their integrity and the security.

Some of iXGuard’s features are listed below:

  • Configuring iXGuard is a simple process. With a single configuration file, it can be easily set up to protect entire applications or specific functions.
  • iXGuard offers built-in support for both native iOS (Objective-C, Swift) and cross-platform applications (Cordova, Ionic, React Native, Unity).
  • iXGuard provides functionality to help you efficiently and effectively protect your application or SDK: Its In-App Assistant autonomously produces configuration for your application and its Protection Report helps you improve your protection before release.
  • iXGuard integrates seamlessly with Guardsquare’s real-time threat monitoring platform, ThreatCast. ThreatCast provides visibility into the real threats possible to compromise your app and enables you to prepare your security configs for the constantly evolving threat possibilities.

Code hardening

iXGuard denies attackers from gaining access into your source code and modifying it, or extracting valuable information from it.

  1. Obfuscation of names of classes, fields, and methods, of arithmetic instructions, control flows and method calls.
  2. Encryption of sensitive strings, assets, and resources.

Runtime application self-protection (RASP)

iXGuard enables applications and SDKs to monitor the integrity of their code and that of the environment in which they are running.

  1. Environment integrity checks, including jailbreak detection and debugger detection.
  2. Application integrity checks, including repacking detection.
  3. Code integrity checks, including hook detection, method swizzling prevention, and code tracing detection.

Conclusion

Leaving your app’s security to chance is never a good idea. An unsecured application can be lucrative for potential attackers to gain access to the app’s files and compromise sensitive information. Using secure coding practices is essential in protecting users’ data from theft or tampering.

As developers, our aim is to minimize the risk of data or information compromise within our app. This can be achieved by following the established best practices for app security.

If you have any questions, please feel free to ask in the comments.

Thanks for reading! 🚀

References:

https://www.guardsquare.com/ixguard

Source Code:

https://github.com/yagizerbay92/iosAppSecurity

A Detailed Gguide on iOS App Security

--

--