Jan Seredynski | 05 APR 2020
There are 2 million apps in AppStore and this number keeps increasing by hundreds every day. These applications include our banking apps, email inboxes and messengers. However, how many of them really care about our security? I've tested 1,300 most popular applications from 13 different categories and verified if they implement essential protections such as:
iOS is repeatedly compromised with jailbreak exploits, that are published just months after the release of a new iOS versions. However, many exploits are kept in secret and become public only after a successful attack:
Moreover, there are many companies that trade with jailbreak exploits and help attackers to gain root privileges. However, an application can still protect itself against these exploits. Such protection is commonly called Runtime Application Self-Protection and comprises of jailbreak, debugger and instrumentation detections. Having detected a malicious activity, it notifies the app's administrator, and removes all user's data from the infected device. This technique has proved its value to a secure banking app, that Governments of Turkey and Singaporeenforced fintech industry to implement Runtime Application Self-Protections in banking applications before they are published in AppStore.
Attackers can hack into our devices without leaving any trace on the system and we may not be aware of malware residing in our phones at all!
Below, I present a list of possible ways of being hacked along with market prices for it:
Example — we downloaded an app from AppStore and its author used the code snippet purchased online for the local privilege escalation, so that she can control our operating system. If our banking application, does not protect itself with Runtime Application Self-Protection, the attacker can easily extract user's data, inject a keylogger or modify the recipient of a money transfer.
It's hardly possible to scan all applications from AppStore, so I have limited the research only to the most popular applications, by the number of downloads and category.
I tested applications statically and dynamically with Radare2 and Frida. The static analysis allowed to reveal hardly reachable detections at the runtime, whereas the dynamic instrumentation enabled to view encrypted and obfuscated text strings in their decrypted form by hooking into system functions and observing arguments passed to them. However, the dynamic analysis doesn’t simulate the user's interaction, so all protections that appear after login screen are only detected with static analysis.
The detailed description of harvesting applications from AppStore and scheduling them for an automated analysis will follow in another article.
Over 73% of the most popular applications use at least one Runtime Application Self-Protection technique. However, the majority seek only for jailbreak artefacts on the filesystem, such as “/Applications/Cydia.app” and “/usr/bin/sshd”, which is an easy-to-bypass protection. In contrast, instrumentation and debugging detections are found in nearly 55% and 10% applications respectively. Nevertheless, it's worth noting that these values are averaged out and significantly differ among different categories.
The figure below shows an overview of applications using at least one Runtime Application Self-Protection technique disregarding its advancement.
We can see that, not all of the protections are executed at the first screen of the application and can only be detected by the static analysis. Mostly, these applications perform security checks after the login screen. Games and Entertainment apps seems to outnumber other categories in number of protected apps. However, game manufactures must protect their apps from being cracked and sold for free or tweaked for an easier win. Netflix and Amazon Prime Video also need to protect their movies from being copied.
Glossary: *Jailbreak — an exploit that escalates system privileges to obtain *root user
A jailbroken system and new programs that are installed along often leave artefacts on the filesystem. Here is the list of the most popular files that can be found on an infected system:
Moreover, during the jailbreaking process, many files are moved away from the “/” partition and replaced with soft links to save up space for new applications that come with the jailbreak. Mostly, whole folders containing big files are replaced:
The figure below shows the proportion of applications in the AppStore that search for the these artefacts on the filesystem. Obfuscated columns describe the number of strings, which are obfuscated inside the binary and can’t be found with static analysis but have been revealed with the dynamic analysis at the runtime.
We can see that on average over 20% percent of apps try to hide out their detections by encrypting the file paths. Nevertheless, protections that check for filesystem artefacts are easy to bypass. Unfortunately, it seems to be the most popular detection technique and many applications limit their runtime protection to only this one.
This figure also shows the great advantage of combining static and dynamic analysis:
The complete list of dynamically hooked functions can be found here.
Glossary: Sandbox — a kernel based mechanism that confines an app to its data and bundle folders; prevents an app from using restricted system functions.
Another way of detecting a jailbroken system is testing for a corrupted sandbox. A corrupted sandbox allows an app to access files outside of its container and use restricted functions such as:
Normally, when an application runs any of these functions, an error is returned. So, an application can test a few restricted function and if they succeed, we can assume that the sandbox is corrupted. This indicates that the device is jailbroken.
Checking for a corrupted sandbox is significantly less popular than searching for filesystem artefacts. The study has shown that this technique is barely used at the runtime and only implemented by the top applications from each category.
An application can determine if other application is installed on the same device by calling UIApplication.canOpenURL(..) method. Therefore, it can also check for an existence of applications installed from Cydia. The presence of these applications indicates that the system is jailbroken because Cydia is only available on a jailbroken device. The figure below the percentage of applications using this detection technique:
We can observe that checking for the presence of Cydia’s applications is mostly performed in banking applications.
Filesystem permissions of a jailbroken system differ as compared to a healthy device. The root filesystem “/” on a jailbroken system is mounted as read and write, while on a non-jailbroken, it is readonly. The difference can be seen by verifying fstab file at “/private/etc/fstab” or using system’s API — statfs, getfsstat, getmntinfo.
This is the first detection that raises the bar of application security. Not only there are a lot of little known APIs to query filesystem permissions but also the returned value is in form of complex structures, which are harder to intercept and replace at the runtime.
The dynamic analysis on this figure excludes the usage of *statfs because, it is also used internally by iOS SDK and it generates many false-positives. The real percentage of apps using this technique at the first screen is probably much higher.
A debuggable application is vulnerable to dynamic analysis, which means that an attacker can read and modify user’s data and patch the application integrity at the runtime. During the analysis, I examined the following functions:
The equal values for static and dynamic analysis of anti-debugging protections mean that applications always performs this check at the start of the process. This is a reasonable approach as a late detection would give an attacker more time to study the app before an attack.
Instrumentation enables to modify application’s behaviour at the runtime. An attacker can potentially inject a malicious code into an application and control it remotely or tweak its functionality. Frida and MobileSubstrate are leading instrumentation frameworks on mobile platforms that allow e.g. to install a keylogger or modify a game for a financial gain. The figure below shows the number of applications checking for fingerprints of these frameworks:
Many applications allow to be run on jailbroken systems but perform strongly obfuscated instrumentation detection. They assume that the user willingly might have jailbroken his device, so they protect the app only against tampering. We can see that such apps perform periodical binary checksums and use Dynamic Linker Inspection to verify that no third-party frameworks are attached to our application at the runtime. These applications often only ask the user if he is aware of the jailbroken system and allow to proceed at own risk. Below, I present a screenshot of a leading banking application(Bunq), that has this policy.
A screenshot of Bunq banking app
Applications pin certificates or public keys to protect against man-in-the-middle attack. Developers often use third-party frameworks for SSL pinning, as a custom cryptographic implementation can introduce many vulnerabilities. During the study, I tested the binary for presence of TrustKit framework and imported symbols of Alamofire, that are used for pinning.
We can see that the applications that store users’ sensitive data, tend to feature SSL pinning more often.
In addition to writing an iOS app with Swift and Objective-C, developers can also include Assembly snippets with system call instructions. This is used to improve code obfuscation and perform more robust integrity checks. System calls can be used to implement all the techniques mentioned before in this article. Their main advantage is the direct contact with the kernel, which makes them more resistant to hooking.
Having tested 1,300 applications, I was able to determine the most common purposes of system calls in applications from AppStore.
We can see that a big part of the top applications use Runtime Application Self-Protection, but unfortunately most of them implement only easy-to-bypass protections like searching for filesystem artefacts. Developers should focus on implementing more advanced protections that really improve the security of our apps. A good approach would be to combine system calls with debugger and instrumentation checks.
Below, I highlighted the key takeaways from this article about using Runtime Application Self-Protection along with its business justification:
Thanks for reading!