From 4213df850a52dd31ff2a50e1200bd60d57d2e5f4 Mon Sep 17 00:00:00 2001 From: Axel Iota Date: Tue, 1 Nov 2022 14:16:48 -0400 Subject: [PATCH 1/9] Add overview theory section --- .../0x06j-Testing-Resiliency-Against-Reverse-Engineering.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Document/0x06j-Testing-Resiliency-Against-Reverse-Engineering.md b/Document/0x06j-Testing-Resiliency-Against-Reverse-Engineering.md index 5907169e3b..594431f49b 100644 --- a/Document/0x06j-Testing-Resiliency-Against-Reverse-Engineering.md +++ b/Document/0x06j-Testing-Resiliency-Against-Reverse-Engineering.md @@ -1,5 +1,7 @@ # iOS Anti-Reversing Defenses +## Overview + ## Jailbreak Detection (MSTG-RESILIENCE-1) ### Overview From 074ce75cca5729da881c1da7834b5c304e15b4a7 Mon Sep 17 00:00:00 2001 From: Axel Iota Date: Mon, 7 Nov 2022 16:51:30 -0500 Subject: [PATCH 2/9] Extract more theory into overview --- ...-Resiliency-Against-Reverse-Engineering.md | 496 +++++++++--------- 1 file changed, 248 insertions(+), 248 deletions(-) diff --git a/Document/0x06j-Testing-Resiliency-Against-Reverse-Engineering.md b/Document/0x06j-Testing-Resiliency-Against-Reverse-Engineering.md index 594431f49b..0da445c2b9 100644 --- a/Document/0x06j-Testing-Resiliency-Against-Reverse-Engineering.md +++ b/Document/0x06j-Testing-Resiliency-Against-Reverse-Engineering.md @@ -2,9 +2,7 @@ ## Overview -## Jailbreak Detection (MSTG-RESILIENCE-1) - -### Overview +### Jailbreak Detection (MSTG-RESILIENCE-1) Jailbreak detection mechanisms are added to reverse engineering defense to make running the app on a jailbroken device more difficult. This blocks some of the tools and techniques reverse engineers like to use. Like most other types of defense, jailbreak detection is not very effective by itself, but scattering checks throughout the app's source code can improve the effectiveness of the overall anti-tampering scheme. Here's a [list of typical jailbreak detection techniques for iOS](https://www.trustwave.com/Resources/SpiderLabs-Blog/Jailbreak-Detection-Methods/ "Jailbreak Detection Methods"). @@ -104,199 +102,7 @@ if([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"cydia:// } ``` -### Bypassing Jailbreak Detection - -Once you start an application that has jailbreak detection enabled on a jailbroken device, you might notice one of the following things: - -1. The application closes immediately, without any notification. -2. A pop-up window indicates that the application won't run on a jailbroken device. - -In the first case, make sure the application is fully functional on non-jailbroken devices. The application may be crashing or it may have a bug that causes it to terminate. This may happen while you're testing a preproduction version of the application. - -Let's look at bypassing jailbreak detection using the [Damn Vulnerable iOS application](0x08b-Reference-Apps.md#dvia-v2) as an example again. After loading the binary into [Hopper](0x08a-Testing-Tools.md#hopper-commercial-tool) (commercial tool), you need to wait until the application is fully disassembled (look at the top bar to check the status). Then look for the "jail" string in the search box. You'll see two classes: `SFAntiPiracy` and `JailbreakDetectionVC`. You may want to decompile the functions to see what they are doing and, in particular, what they return. - - - - -As you can see, there's a class method (`+[SFAntiPiracy isTheDeviceJailbroken]`) and an instance method (`-[JailbreakDetectionVC isJailbroken]`). The main difference is that we can inject Cycript in the app and call the class method directly, whereas the instance method requires first looking for instances of the target class. The function `choose` will look in the memory heap for known signatures of a given class and return an array of instances. Putting an application into a desired state (so that the class is indeed instantiated) is important. - -Let's inject Cycript into our process (look for your PID with `top`): - -```bash -iOS8-jailbreak:~ root# cycript -p 12345 -cy# [SFAntiPiracy isTheDeviceJailbroken] -true -``` - -As you can see, our class method was called directly, and it returned "true". Now, let's call the `-[JailbreakDetectionVC isJailbroken]` instance method. First, we have to call the `choose` function to look for instances of the `JailbreakDetectionVC` class. - -```bash -cy# a=choose(JailbreakDetectionVC) -[] -``` - -Oops! The return value is an empty array. That means that there are no instances of this class registered in the runtime. In fact, we haven't clicked the second "Jailbreak Test" button, which initializes this class: - -```bash -cy# a=choose(JailbreakDetectionVC) -[#""] -cy# [a[0] isJailbroken] -True -``` - - - -Now you understand why having your application in a desired state is important. At this point, bypassing jailbreak detection with Cycript is trivial. We can see that the function returns a boolean; we just need to replace the return value. We can replace the return value by replacing the function implementation with Cycript. Please note that this will actually replace the function under its given name, so beware of side effects if the function modifies anything in the application: - -```bash -cy# JailbreakDetectionVC.prototype.isJailbroken=function(){return false} -cy# [a[0] isJailbroken] -false -``` - - - -In this case we have bypassed the jailbreak detection of the application! - -Now, imagine that the application is closing immediately after detecting that the device is jailbroken. You don't have time to launch Cycript and replace the function implementation. Instead, you have to use CydiaSubstrate, employ a proper hooking function like `MSHookMessageEx`, and compile the tweak. There are [good sources](https://manualzz.com/doc/26490749/jailbreak-root-detection-evasion-study-on-ios-and-android "Jailbreak/Root Detection Evasion Study on iOS and Android") for how to do this; however, by using Frida, we can more easily perform early instrumentation and we can build on our gathered skills from previous tests. - -One feature of Frida that we will use to bypass jailbreak detection is so-called early instrumentation, that is, we will replace function implementation at startup. - -1. Make sure that `frida-server` is running on your iOS Device. -2. Make sure that `Frida` is [installed](https://www.frida.re/docs/installation/ "Frida Installation") on your host computer. -3. The iOS device must be connected via USB cable. -4. Use `frida-trace` on your host computer: - -```bash -frida-trace -U -f /Applications/DamnVulnerableIOSApp.app/DamnVulnerableIOSApp -m "-[JailbreakDetectionVC isJailbroken]" -``` - -This will start DamnVulnerableIOSApp, trace calls to `-[JailbreakDetectionVC isJailbroken]`, and create a JavaScript hook with the `onEnter` and `onLeave` callback functions. Now, replacing the return value via `value.replace` is trivial, as shown in the following example: - -```javascript - onLeave: function (log, retval, state) { - console.log("Function [JailbreakDetectionVC isJailbroken] originally returned:"+ retval); - retval.replace(0); - console.log("Changing the return value to:"+retval); - } -``` - -This will provide the following output: - -```bash -$ frida-trace -U -f /Applications/DamnVulnerableIOSApp.app/DamnVulnerableIOSApp -m "-[JailbreakDetectionVC isJailbroken]:" - -Instrumenting functions... `... --[JailbreakDetectionVC isJailbroken]: Loaded handler at "./__handlers__/__JailbreakDetectionVC_isJailbroken_.js" -Started tracing 1 function. Press Ctrl+C to stop. -Function [JailbreakDetectionVC isJailbroken] originally returned:0x1 -Changing the return value to:0x0 - /* TID 0x303 */ - 6890 ms -[JailbreakDetectionVC isJailbroken] -Function [JailbreakDetectionVC isJailbroken] originally returned:0x1 -Changing the return value to:0x0 - 22475 ms -[JailbreakDetectionVC isJailbroken] -``` - -Note the two calls to `-[JailbreakDetectionVC isJailbroken]`, which correspond to two physical taps on the app's GUI. - -One more way to bypass Jailbreak detection mechanisms that rely on file system checks is [objection](0x08a-Testing-Tools.md#objection). You can find the implementation of the jailbreak bypass in the [jailbreak.ts script](https://github.com/sensepost/objection/blob/master/agent/src/ios/jailbreak.ts "jailbreak.ts"). - -See below a Python script for hooking Objective-C methods and native functions: - -```python -import frida -import sys - -try: - session = frida.get_usb_device().attach("Target Process") -except frida.ProcessNotFoundError: - print "Failed to attach to the target process. Did you launch the app?" - sys.exit(0) - -script = session.create_script(""" - - // Handle fork() based check - - var fork = Module.findExportByName("libsystem_c.dylib", "fork"); - - Interceptor.replace(fork, new NativeCallback(function () { - send("Intercepted call to fork()."); - return -1; - }, 'int', [])); - - var system = Module.findExportByName("libsystem_c.dylib", "system"); - - Interceptor.replace(system, new NativeCallback(function () { - send("Intercepted call to system()."); - return 0; - }, 'int', [])); - - // Intercept checks for Cydia URL handler - - var canOpenURL = ObjC.classes.UIApplication["- canOpenURL:"]; - - Interceptor.attach(canOpenURL.implementation, { - onEnter: function(args) { - var url = ObjC.Object(args[2]); - send("[UIApplication canOpenURL:] " + path.toString()); - }, - onLeave: function(retval) { - send ("canOpenURL returned: " + retval); - } - - }); - - // Intercept file existence checks via [NSFileManager fileExistsAtPath:] - - var fileExistsAtPath = ObjC.classes.NSFileManager["- fileExistsAtPath:"]; - var hideFile = 0; - - Interceptor.attach(fileExistsAtPath.implementation, { - onEnter: function(args) { - var path = ObjC.Object(args[2]); - // send("[NSFileManager fileExistsAtPath:] " + path.toString()); - - if (path.toString() == "/Applications/Cydia.app" || path.toString() == "/bin/bash") { - hideFile = 1; - } - }, - onLeave: function(retval) { - if (hideFile) { - send("Hiding jailbreak file...");MM - retval.replace(0); - hideFile = 0; - } - - // send("fileExistsAtPath returned: " + retval); - } - }); - - - /* If the above doesn't work, you might want to hook low level file APIs as well - - var openat = Module.findExportByName("libsystem_c.dylib", "openat"); - var stat = Module.findExportByName("libsystem_c.dylib", "stat"); - var fopen = Module.findExportByName("libsystem_c.dylib", "fopen"); - var open = Module.findExportByName("libsystem_c.dylib", "open"); - var faccesset = Module.findExportByName("libsystem_kernel.dylib", "faccessat"); - - */ - -""") - -def on_message(message, data): - if 'payload' in message: - print(message['payload']) - -script.on('message', on_message) -script.load() -sys.stdin.read() -``` - -## Testing Anti-Debugging Detection (MSTG-RESILIENCE-2) - -### Overview +### Anti-Debugging Detection (MSTG-RESILIENCE-2) Exploring applications using a debugger is a very powerful technique during reversing. You can not only track variables containing sensitive data and modify the control flow of the application, but also read and modify memory and registers. @@ -396,21 +202,7 @@ After the instruction at offset 0xC13C, `MOVNE R0, #1` is patched and changed to You can also bypass a `sysctl` check by using the debugger itself and setting a breakpoint at the call to `sysctl`. This approach is demonstrated in [iOS Anti-Debugging Protections #2](https://www.coredump.gr/articles/ios-anti-debugging-protections-part-2/ "iOS Anti-Debugging Protections #2"). -### Using getppid - -Applications on iOS can detect if they have been started by a debugger by checking their parent PID. Normally, an application is started by the [launchd](http://newosxbook.com/articles/Ch07.pdf) process, which is the first process running in the _user mode_ and has PID=1. However, if a debugger starts an application, we can observe that `getppid` returns a PID different than 1. This detection technique can be implemented in native code (via syscalls), using Objective-C or Swift as shown here: - -```default -func AmIBeingDebugged() -> Bool { - return getppid() != 1 -} -``` - -Similarly to the other techniques, this has also a trivial bypass (e.g. by patching the binary or by using Frida hooks). - -## File Integrity Checks (MSTG-RESILIENCE-3 and MSTG-RESILIENCE-11) - -### Overview +### File Integrity Checks (MSTG-RESILIENCE-3 and MSTG-RESILIENCE-11) There are two topics related to file integrity: @@ -531,6 +323,250 @@ When verifying the HMAC with CC, follow these steps: 1. Retrieve the data from the device, as described in the "[Device Binding](#device-binding-mstg-resilience-10 "Device Binding")" section. 2. Alter the retrieved data and return it to storage. +### Reverse Engineering Tools Detection (MSTG-RESILIENCE-4) + +The presence of tools, frameworks and apps commonly used by reverse engineers may indicate an attempt to reverse engineer the app. Some of these tools can only run on a jailbroken device, while others force the app into debugging mode or depend on starting a background service on the mobile phone. Therefore, there are different ways that an app may implement to detect a reverse engineering attack and react to it, e.g. by terminating itself. + +### Emulator Detection (MSTG-RESILIENCE-5) + +The goal of emulator detection is to increase the difficulty of running the app on an emulated device. This forces the reverse engineer to defeat the emulator checks or utilize the physical device, thereby barring the access required for large-scale device analysis. + +As discussed in the section [Testing on the iOS Simulator](0x06b-Basic-Security-Testing.md "Testing on the iOS Simulator") in the basic security testing chapter, the only available simulator is the one that ships with Xcode. Simulator binaries are compiled to x86 code instead of ARM code and apps compiled for a real device (ARM architecture) don't run in the simulator, hence _simulation_ protection was not so much a concern regarding iOS apps in contrast to Android with a wide range of _emulation_ choices available. + +However, since its release, [Corellium](https://www.corellium.com/) (commercial tool) has enabled real emulation, [setting itself apart from the iOS simulator](https://www.corellium.com/compare/ios-simulator). In addition to that, being a SaaS solution, Corellium enables large-scale device analysis with the limiting factor just being available funds. + +With Apple Silicon (ARM) hardware widely available, traditional checks for the presence of x86 / x64 architecture might not suffice. One potential detection strategy is to identify features and limitations available for commonly used emulation solutions. For instance, Corellium doesn't support iCloud, cellular services, camera, NFC, Bluetooth, App Store access or GPU hardware emulation ([Metal](https://developer.apple.com/documentation/metal/gpu_devices_and_work_submission/getting_the_default_gpu)). Therefore, smartly combining checks involving any of these features could be an indicator for the presence of an emulated environment. + +Pairing these results with the ones from 3rd party frameworks such as [iOS Security Suite](https://github.com/securing/IOSSecuritySuite#emulator-detector-module), [Trusteer](https://www.ibm.com/products/trusteer-mobile-sdk/details) or a no-code solution such as [Appdome](https://www.appdome.com/) (commercial solution) will provide a good line of defense against attacks utilizing emulators. + +### Obfuscation (MSTG-RESILIENCE-9) + +The chapter ["Mobile App Tampering and Reverse Engineering"](0x04c-Tampering-and-Reverse-Engineering.md#obfuscation) introduces several well-known obfuscation techniques that can be used in mobile apps in general. + +> Note: All presented techniques below may not stop reverse engineers, but combining all of those techniques will make their job significantly harder. The aim of those techniques is to discourage reverse engineers from performing further analysis. + +The following techniques can be used to obfuscate an application: + +- Name obfuscation +- Instruction substitution +- Control flow flattening +- Dead code injection +- String encryption + +### Device Binding (MSTG-RESILIENCE-10) + +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](https://developer.apple.com/library/content/releasenotes/General/RN-iOSSDK-7.0/index.html "iOS 7 release notes"), 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. See the "[Remediation](#remediation "Remediation")" section for more details. + +## Testing Jailbreak Detection (MSTG-RESILIENCE-1) + +### Bypassing Jailbreak Detection + +Once you start an application that has jailbreak detection enabled on a jailbroken device, you might notice one of the following things: + +1. The application closes immediately, without any notification. +2. A pop-up window indicates that the application won't run on a jailbroken device. + +In the first case, make sure the application is fully functional on non-jailbroken devices. The application may be crashing or it may have a bug that causes it to terminate. This may happen while you're testing a preproduction version of the application. + +Let's look at bypassing jailbreak detection using the [Damn Vulnerable iOS application](0x08b-Reference-Apps.md#dvia-v2) as an example again. After loading the binary into [Hopper](0x08a-Testing-Tools.md#hopper-commercial-tool) (commercial tool), you need to wait until the application is fully disassembled (look at the top bar to check the status). Then look for the "jail" string in the search box. You'll see two classes: `SFAntiPiracy` and `JailbreakDetectionVC`. You may want to decompile the functions to see what they are doing and, in particular, what they return. + + + + +As you can see, there's a class method (`+[SFAntiPiracy isTheDeviceJailbroken]`) and an instance method (`-[JailbreakDetectionVC isJailbroken]`). The main difference is that we can inject Cycript in the app and call the class method directly, whereas the instance method requires first looking for instances of the target class. The function `choose` will look in the memory heap for known signatures of a given class and return an array of instances. Putting an application into a desired state (so that the class is indeed instantiated) is important. + +Let's inject Cycript into our process (look for your PID with `top`): + +```bash +iOS8-jailbreak:~ root# cycript -p 12345 +cy# [SFAntiPiracy isTheDeviceJailbroken] +true +``` + +As you can see, our class method was called directly, and it returned "true". Now, let's call the `-[JailbreakDetectionVC isJailbroken]` instance method. First, we have to call the `choose` function to look for instances of the `JailbreakDetectionVC` class. + +```bash +cy# a=choose(JailbreakDetectionVC) +[] +``` + +Oops! The return value is an empty array. That means that there are no instances of this class registered in the runtime. In fact, we haven't clicked the second "Jailbreak Test" button, which initializes this class: + +```bash +cy# a=choose(JailbreakDetectionVC) +[#""] +cy# [a[0] isJailbroken] +True +``` + + + +Now you understand why having your application in a desired state is important. At this point, bypassing jailbreak detection with Cycript is trivial. We can see that the function returns a boolean; we just need to replace the return value. We can replace the return value by replacing the function implementation with Cycript. Please note that this will actually replace the function under its given name, so beware of side effects if the function modifies anything in the application: + +```bash +cy# JailbreakDetectionVC.prototype.isJailbroken=function(){return false} +cy# [a[0] isJailbroken] +false +``` + + + +In this case we have bypassed the jailbreak detection of the application! + +Now, imagine that the application is closing immediately after detecting that the device is jailbroken. You don't have time to launch Cycript and replace the function implementation. Instead, you have to use CydiaSubstrate, employ a proper hooking function like `MSHookMessageEx`, and compile the tweak. There are [good sources](https://manualzz.com/doc/26490749/jailbreak-root-detection-evasion-study-on-ios-and-android "Jailbreak/Root Detection Evasion Study on iOS and Android") for how to do this; however, by using Frida, we can more easily perform early instrumentation and we can build on our gathered skills from previous tests. + +One feature of Frida that we will use to bypass jailbreak detection is so-called early instrumentation, that is, we will replace function implementation at startup. + +1. Make sure that `frida-server` is running on your iOS Device. +2. Make sure that `Frida` is [installed](https://www.frida.re/docs/installation/ "Frida Installation") on your host computer. +3. The iOS device must be connected via USB cable. +4. Use `frida-trace` on your host computer: + +```bash +frida-trace -U -f /Applications/DamnVulnerableIOSApp.app/DamnVulnerableIOSApp -m "-[JailbreakDetectionVC isJailbroken]" +``` + +This will start DamnVulnerableIOSApp, trace calls to `-[JailbreakDetectionVC isJailbroken]`, and create a JavaScript hook with the `onEnter` and `onLeave` callback functions. Now, replacing the return value via `value.replace` is trivial, as shown in the following example: + +```javascript + onLeave: function (log, retval, state) { + console.log("Function [JailbreakDetectionVC isJailbroken] originally returned:"+ retval); + retval.replace(0); + console.log("Changing the return value to:"+retval); + } +``` + +This will provide the following output: + +```bash +$ frida-trace -U -f /Applications/DamnVulnerableIOSApp.app/DamnVulnerableIOSApp -m "-[JailbreakDetectionVC isJailbroken]:" + +Instrumenting functions... `... +-[JailbreakDetectionVC isJailbroken]: Loaded handler at "./__handlers__/__JailbreakDetectionVC_isJailbroken_.js" +Started tracing 1 function. Press Ctrl+C to stop. +Function [JailbreakDetectionVC isJailbroken] originally returned:0x1 +Changing the return value to:0x0 + /* TID 0x303 */ + 6890 ms -[JailbreakDetectionVC isJailbroken] +Function [JailbreakDetectionVC isJailbroken] originally returned:0x1 +Changing the return value to:0x0 + 22475 ms -[JailbreakDetectionVC isJailbroken] +``` + +Note the two calls to `-[JailbreakDetectionVC isJailbroken]`, which correspond to two physical taps on the app's GUI. + +One more way to bypass Jailbreak detection mechanisms that rely on file system checks is [objection](0x08a-Testing-Tools.md#objection). You can find the implementation of the jailbreak bypass in the [jailbreak.ts script](https://github.com/sensepost/objection/blob/master/agent/src/ios/jailbreak.ts "jailbreak.ts"). + +See below a Python script for hooking Objective-C methods and native functions: + +```python +import frida +import sys + +try: + session = frida.get_usb_device().attach("Target Process") +except frida.ProcessNotFoundError: + print "Failed to attach to the target process. Did you launch the app?" + sys.exit(0) + +script = session.create_script(""" + + // Handle fork() based check + + var fork = Module.findExportByName("libsystem_c.dylib", "fork"); + + Interceptor.replace(fork, new NativeCallback(function () { + send("Intercepted call to fork()."); + return -1; + }, 'int', [])); + + var system = Module.findExportByName("libsystem_c.dylib", "system"); + + Interceptor.replace(system, new NativeCallback(function () { + send("Intercepted call to system()."); + return 0; + }, 'int', [])); + + // Intercept checks for Cydia URL handler + + var canOpenURL = ObjC.classes.UIApplication["- canOpenURL:"]; + + Interceptor.attach(canOpenURL.implementation, { + onEnter: function(args) { + var url = ObjC.Object(args[2]); + send("[UIApplication canOpenURL:] " + path.toString()); + }, + onLeave: function(retval) { + send ("canOpenURL returned: " + retval); + } + + }); + + // Intercept file existence checks via [NSFileManager fileExistsAtPath:] + + var fileExistsAtPath = ObjC.classes.NSFileManager["- fileExistsAtPath:"]; + var hideFile = 0; + + Interceptor.attach(fileExistsAtPath.implementation, { + onEnter: function(args) { + var path = ObjC.Object(args[2]); + // send("[NSFileManager fileExistsAtPath:] " + path.toString()); + + if (path.toString() == "/Applications/Cydia.app" || path.toString() == "/bin/bash") { + hideFile = 1; + } + }, + onLeave: function(retval) { + if (hideFile) { + send("Hiding jailbreak file...");MM + retval.replace(0); + hideFile = 0; + } + + // send("fileExistsAtPath returned: " + retval); + } + }); + + + /* If the above doesn't work, you might want to hook low level file APIs as well + + var openat = Module.findExportByName("libsystem_c.dylib", "openat"); + var stat = Module.findExportByName("libsystem_c.dylib", "stat"); + var fopen = Module.findExportByName("libsystem_c.dylib", "fopen"); + var open = Module.findExportByName("libsystem_c.dylib", "open"); + var faccesset = Module.findExportByName("libsystem_kernel.dylib", "faccessat"); + + */ + +""") + +def on_message(message, data): + if 'payload' in message: + print(message['payload']) + +script.on('message', on_message) +script.load() +sys.stdin.read() +``` + +## Testing Anti-Debugging Detection (MSTG-RESILIENCE-2) + +### Using getppid + +Applications on iOS can detect if they have been started by a debugger by checking their parent PID. Normally, an application is started by the [launchd](http://newosxbook.com/articles/Ch07.pdf) process, which is the first process running in the _user mode_ and has PID=1. However, if a debugger starts an application, we can observe that `getppid` returns a PID different than 1. This detection technique can be implemented in native code (via syscalls), using Objective-C or Swift as shown here: + +```default +func AmIBeingDebugged() -> Bool { + return getppid() != 1 +} +``` + +Similarly to the other techniques, this has also a trivial bypass (e.g. by patching the binary or by using Frida hooks). + +## Testing File Integrity Checks (MSTG-RESILIENCE-3 and MSTG-RESILIENCE-11) + ### Effectiveness Assessment **Application source code integrity checks:** @@ -554,10 +590,6 @@ A similar approach works. Answer the following questions: ## Testing Reverse Engineering Tools Detection (MSTG-RESILIENCE-4) -### Overview - -The presence of tools, frameworks and apps commonly used by reverse engineers may indicate an attempt to reverse engineer the app. Some of these tools can only run on a jailbroken device, while others force the app into debugging mode or depend on starting a background service on the mobile phone. Therefore, there are different ways that an app may implement to detect a reverse engineering attack and react to it, e.g. by terminating itself. - ### Detection Methods You can detect popular reverse engineering tools that have been installed in an unmodified form by looking for associated application packages, files, processes, or other tool-specific modifications and artifacts. In the following examples, we'll discuss different ways to detect the Frida instrumentation framework, which is used extensively in this guide and also in the real world. Other tools, such as Cydia Substrate or Cycript, can be detected similarly. Note that injection, hooking and DBI (Dynamic Binary Instrumentation) tools can often be detected implicitly, through runtime integrity checks, which are discussed below. @@ -633,34 +665,8 @@ Refer to the chapter "[Tampering and Reverse Engineering on iOS](0x06c-Reverse-E ## Testing Emulator Detection (MSTG-RESILIENCE-5) -### Overview - -The goal of emulator detection is to increase the difficulty of running the app on an emulated device. This forces the reverse engineer to defeat the emulator checks or utilize the physical device, thereby barring the access required for large-scale device analysis. - -As discussed in the section [Testing on the iOS Simulator](0x06b-Basic-Security-Testing.md "Testing on the iOS Simulator") in the basic security testing chapter, the only available simulator is the one that ships with Xcode. Simulator binaries are compiled to x86 code instead of ARM code and apps compiled for a real device (ARM architecture) don't run in the simulator, hence _simulation_ protection was not so much a concern regarding iOS apps in contrast to Android with a wide range of _emulation_ choices available. - -However, since its release, [Corellium](https://www.corellium.com/) (commercial tool) has enabled real emulation, [setting itself apart from the iOS simulator](https://www.corellium.com/compare/ios-simulator). In addition to that, being a SaaS solution, Corellium enables large-scale device analysis with the limiting factor just being available funds. - -With Apple Silicon (ARM) hardware widely available, traditional checks for the presence of x86 / x64 architecture might not suffice. One potential detection strategy is to identify features and limitations available for commonly used emulation solutions. For instance, Corellium doesn't support iCloud, cellular services, camera, NFC, Bluetooth, App Store access or GPU hardware emulation ([Metal](https://developer.apple.com/documentation/metal/gpu_devices_and_work_submission/getting_the_default_gpu)). Therefore, smartly combining checks involving any of these features could be an indicator for the presence of an emulated environment. - -Pairing these results with the ones from 3rd party frameworks such as [iOS Security Suite](https://github.com/securing/IOSSecuritySuite#emulator-detector-module), [Trusteer](https://www.ibm.com/products/trusteer-mobile-sdk/details) or a no-code solution such as [Appdome](https://www.appdome.com/) (commercial solution) will provide a good line of defense against attacks utilizing emulators. - ## Testing Obfuscation (MSTG-RESILIENCE-9) -### Overview - -The chapter ["Mobile App Tampering and Reverse Engineering"](0x04c-Tampering-and-Reverse-Engineering.md#obfuscation) introduces several well-known obfuscation techniques that can be used in mobile apps in general. - -> Note: All presented techniques below may not stop reverse engineers, but combining all of those techniques will make their job significantly harder. The aim of those techniques is to discourage reverse engineers from performing further analysis. - -The following techniques can be used to obfuscate an application: - -- Name obfuscation -- Instruction substitution -- Control flow flattening -- Dead code injection -- String encryption - ### Name Obfuscation The standard compiler generates binary symbols based on class and function names from the source code. Therefore, if no obfuscation was applied, symbol names remain meaningful and can be easily read straight from the app binary. For instance, a function which detects a jailbreak can be located by searching for relevant keywords (e.g. "jailbreak"). The listing below shows the disassembled function `JailbreakDetectionViewController.jailbreakTest4Tapped` from the Damn Vulnerable iOS App ([DVIA-v2](0x08b-Reference-Apps.md#dvia-v2)). @@ -784,13 +790,7 @@ Attempt to disassemble the Mach-O in the IPA and any included library files in t For a more detailed assessment, you need a detailed understanding of the relevant threats and the obfuscation methods used. -## Device Binding (MSTG-RESILIENCE-10) - -### Overview - -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](https://developer.apple.com/library/content/releasenotes/General/RN-iOSSDK-7.0/index.html "iOS 7 release notes"), 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. See the "[Remediation](#remediation "Remediation")" section for more details. +## Testing Device Binding (MSTG-RESILIENCE-10) ### Static Analysis From 983e810948722961fc1613e33cb7e527609eb392 Mon Sep 17 00:00:00 2001 From: Axel Iota Date: Tue, 8 Nov 2022 13:01:50 -0500 Subject: [PATCH 3/9] update reference, remove MASVS IDs --- ...ing-Resiliency-Against-Reverse-Engineering.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Document/0x06j-Testing-Resiliency-Against-Reverse-Engineering.md b/Document/0x06j-Testing-Resiliency-Against-Reverse-Engineering.md index 0da445c2b9..01f696c6c7 100644 --- a/Document/0x06j-Testing-Resiliency-Against-Reverse-Engineering.md +++ b/Document/0x06j-Testing-Resiliency-Against-Reverse-Engineering.md @@ -2,7 +2,7 @@ ## Overview -### Jailbreak Detection (MSTG-RESILIENCE-1) +### Jailbreak Detection Jailbreak detection mechanisms are added to reverse engineering defense to make running the app on a jailbroken device more difficult. This blocks some of the tools and techniques reverse engineers like to use. Like most other types of defense, jailbreak detection is not very effective by itself, but scattering checks throughout the app's source code can improve the effectiveness of the overall anti-tampering scheme. Here's a [list of typical jailbreak detection techniques for iOS](https://www.trustwave.com/Resources/SpiderLabs-Blog/Jailbreak-Detection-Methods/ "Jailbreak Detection Methods"). @@ -102,7 +102,7 @@ if([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"cydia:// } ``` -### Anti-Debugging Detection (MSTG-RESILIENCE-2) +### Anti-Debugging Detection Exploring applications using a debugger is a very powerful technique during reversing. You can not only track variables containing sensitive data and modify the control flow of the application, but also read and modify memory and registers. @@ -202,7 +202,7 @@ After the instruction at offset 0xC13C, `MOVNE R0, #1` is patched and changed to You can also bypass a `sysctl` check by using the debugger itself and setting a breakpoint at the call to `sysctl`. This approach is demonstrated in [iOS Anti-Debugging Protections #2](https://www.coredump.gr/articles/ios-anti-debugging-protections-part-2/ "iOS Anti-Debugging Protections #2"). -### File Integrity Checks (MSTG-RESILIENCE-3 and MSTG-RESILIENCE-11) +### File Integrity Checks There are two topics related to file integrity: @@ -320,14 +320,14 @@ When verifying the HMAC with CC, follow these steps: ##### When you're trying to bypass the storage integrity checks -1. Retrieve the data from the device, as described in the "[Device Binding](#device-binding-mstg-resilience-10 "Device Binding")" section. +1. Retrieve the data from the device, as described in the "[Device Binding](#device-binding)" section. 2. Alter the retrieved data and return it to storage. -### Reverse Engineering Tools Detection (MSTG-RESILIENCE-4) +### Reverse Engineering Tools Detection The presence of tools, frameworks and apps commonly used by reverse engineers may indicate an attempt to reverse engineer the app. Some of these tools can only run on a jailbroken device, while others force the app into debugging mode or depend on starting a background service on the mobile phone. Therefore, there are different ways that an app may implement to detect a reverse engineering attack and react to it, e.g. by terminating itself. -### Emulator Detection (MSTG-RESILIENCE-5) +### Emulator Detection The goal of emulator detection is to increase the difficulty of running the app on an emulated device. This forces the reverse engineer to defeat the emulator checks or utilize the physical device, thereby barring the access required for large-scale device analysis. @@ -339,7 +339,7 @@ With Apple Silicon (ARM) hardware widely available, traditional checks for the p Pairing these results with the ones from 3rd party frameworks such as [iOS Security Suite](https://github.com/securing/IOSSecuritySuite#emulator-detector-module), [Trusteer](https://www.ibm.com/products/trusteer-mobile-sdk/details) or a no-code solution such as [Appdome](https://www.appdome.com/) (commercial solution) will provide a good line of defense against attacks utilizing emulators. -### Obfuscation (MSTG-RESILIENCE-9) +### Obfuscation The chapter ["Mobile App Tampering and Reverse Engineering"](0x04c-Tampering-and-Reverse-Engineering.md#obfuscation) introduces several well-known obfuscation techniques that can be used in mobile apps in general. @@ -353,7 +353,7 @@ The following techniques can be used to obfuscate an application: - Dead code injection - String encryption -### Device Binding (MSTG-RESILIENCE-10) +### 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. From 135e7f2d4cdf8c816ed9117180a6029f2556b6b6 Mon Sep 17 00:00:00 2001 From: Carlos Holguera Date: Fri, 25 Nov 2022 11:43:56 +0100 Subject: [PATCH 4/9] add swiftshield to tools --- Document/0x08a-Testing-Tools.md | 66 +++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/Document/0x08a-Testing-Tools.md b/Document/0x08a-Testing-Tools.md index 50f0c51241..c127a0d852 100644 --- a/Document/0x08a-Testing-Tools.md +++ b/Document/0x08a-Testing-Tools.md @@ -1699,6 +1699,72 @@ Blackbox tool to disable SSL certificate validation - including certificate pinn swift-demangle is an Xcode tool that demangles Swift symbols. For more information run `xcrun swift-demangle -help` once installed. +### SwiftShield + +[SwiftShield](https://github.com/rockbruno/swiftshield "SwiftShield") is a tool that generates irreversible, encrypted names for your iOS project's objects (including your Pods and Storyboards). This raises the bar for reverse engineers and will produce less helpful output when using reverse engineering tools such as class-dump and Frida. + +> Warning: SwiftShield irreversibly overwrites all your source files. Ideally, you should have it run only on your CI server, and on release builds. + +A sample Swift project is used to demonstrate the usage of SwiftShield. + +- Check out . +- Open the project in Xcode and make sure that the project is building successfully (Product / Build or Apple-Key + B). +- [Download](https://github.com/rockbruno/swiftshield/releases "SwiftShield Download") the latest release of SwiftShield and unzip it. +- Go to the directory where you downloaded SwiftShield and copy the swiftshield executable to `/usr/local/bin`: + +```bash +cp swiftshield/swiftshield /usr/local/bin/ +``` + +- In your terminal go into the SwiftSecurity directory (which you checked out in step 1) and execute the command swiftshield (which you downloaded in step 3): + +```bash +$ cd SwiftSecurity +$ swiftshield -automatic -project-root . -automatic-project-file SwiftSecurity.xcodeproj -automatic-project-scheme SwiftSecurity +SwiftShield 3.4.0 +Automatic mode +Building project to gather modules and compiler arguments... +-- Indexing ReverseEngineeringToolsChecker.swift -- +Found declaration of ReverseEngineeringToolsChecker (s:13SwiftSecurity30ReverseEngineeringToolsCheckerC) +Found declaration of amIReverseEngineered (s:13SwiftSecurity30ReverseEngineeringToolsCheckerC20amIReverseEngineeredSbyFZ) +Found declaration of checkDYLD (s:13SwiftSecurity30ReverseEngineeringToolsCheckerC9checkDYLD33_D6FE91E9C9AEC4D13973F8ABFC1AC788LLSbyFZ) +Found declaration of checkExistenceOfSuspiciousFiles (s:13SwiftSecurity30ReverseEngineeringToolsCheckerC31checkExistenceOfSuspiciousFiles33_D6FE91E9C9AEC4D13973F8ABFC1AC788LLSbyFZ) +... +``` + +SwiftShield is now detecting class and method names and is replacing their identifier with an encrypted value. + +In the original source code you can see all the class and method identifiers: + + + +SwiftShield was now replacing all of them with encrypted values that leave no trace to their original name or intention of the class/method: + + + +After executing `swiftshield` a new directory will be created called `swiftshield-output`. In this directory another directory is created with a timestamp in the folder name. This directory contains a text file called `conversionMap.txt`, that maps the encrypted strings to their original values. + +```bash +$ cat conversionMap.txt +// +// SwiftShield Conversion Map +// Automatic mode for SwiftSecurity, 2020-01-02 13.51.03 +// Deobfuscate crash logs (or any text file) by running: +// swiftshield -deobfuscate CRASH_FILE -deobfuscate_map THIS_FILE +// + +ViewController ===> hTOUoUmUcEZUqhVHRrjrMUnYqbdqWByU +viewDidLoad ===> DLaNRaFbfmdTDuJCPFXrGhsWhoQyKLnO +sceneDidBecomeActive ===> SUANAnWpkyaIWlGUqwXitCoQSYeVilGe +AppDelegate ===> KftEWsJcctNEmGuvwZGPbusIxEFOVcIb +Deny_Debugger ===> lKEITOpOvLWCFgSCKZdUtpuqiwlvxSjx +Button_Emulator ===> akcVscrZFdBBYqYrcmhhyXAevNdXOKeG +``` + +This is needed for [deobfuscating encrypted crash logs](https://github.com/rockbruno/swiftshield#-deobfuscating-encrypted-crash-logs "Deobfuscating encrypted Crash logs"). + +Another example project is available in SwiftShield's [Github repo](https://github.com/rockbruno/swiftshield/tree/master/ExampleProject "SwiftShieldExample"), that can be used to test the execution of SwiftShield. + ### TablePlus [TablePlus](https://tableplus.io/ "TablePlus") is a tool for Windows and macOS to inspect database files, like Sqlite and others. This can be very useful during iOS engagements when dumping the database files from the iOS device and analyzing the content of them with a GUI tool. From b408280df7639eaa4429f91e6801a3bcb703c2ec Mon Sep 17 00:00:00 2001 From: Carlos Holguera Date: Fri, 25 Nov 2022 11:44:31 +0100 Subject: [PATCH 5/9] second round of refactor --- ...-Resiliency-Against-Reverse-Engineering.md | 720 +++++++----------- 1 file changed, 259 insertions(+), 461 deletions(-) diff --git a/Document/0x06j-Testing-Resiliency-Against-Reverse-Engineering.md b/Document/0x06j-Testing-Resiliency-Against-Reverse-Engineering.md index 01f696c6c7..f2103e47a8 100644 --- a/Document/0x06j-Testing-Resiliency-Against-Reverse-Engineering.md +++ b/Document/0x06j-Testing-Resiliency-Against-Reverse-Engineering.md @@ -2,13 +2,33 @@ ## Overview +This chapter covers defense-in-depth measures recommended for apps that process, or give access to, sensitive data or functionality. Research shows that [many App Store apps often include these measures](https://seredynski.com/articles/a-security-review-of-1300-appstore-applications.html "A security review of 1,300 AppStore applications - 5 April 2020"). + +These measures should be applied as needed, based on an assessment of the risks caused by unauthorized tampering with the app and/or reverse engineering of the code. + +- Apps must never use these measures as a replacement for security controls, and are threfore expected to fulfil other baseline security measures such as the rest of the MASVS security controls. +- Apps should combine these measures cleverly instead of using them individually. The goal is to discourage reverse engineers from performing further analysis. +- Integrating some of the controls into your app might increase the complexity of your app and even have an impact on its performance. + +### General Disclaimer + +The **lack of any of these measures does not cause a vulnerability** - instead, they are meant to increase the app's resilience against reverse engineering and specific client-side attacks. + +None of these measures can assure a 100% effectiveness, as the reverse engineer will always have full access to the device and will therefore always win (given enough time and resources)! + +For example, preventing debugging is virtually impossible. If the app is publicly available, it can be run on an untrusted device, that is under full control of the attacker. A very determined attacker will eventually manage to bypass all the app's anti-debugging controls by patching the app binary or by dynamically modifying the app's behavior at runtime with tools such as Frida. + ### Jailbreak Detection -Jailbreak detection mechanisms are added to reverse engineering defense to make running the app on a jailbroken device more difficult. This blocks some of the tools and techniques reverse engineers like to use. Like most other types of defense, jailbreak detection is not very effective by itself, but scattering checks throughout the app's source code can improve the effectiveness of the overall anti-tampering scheme. Here's a [list of typical jailbreak detection techniques for iOS](https://www.trustwave.com/Resources/SpiderLabs-Blog/Jailbreak-Detection-Methods/ "Jailbreak Detection Methods"). +Jailbreak detection mechanisms are added to reverse engineering defense to make running the app on a jailbroken device more difficult. This blocks some of the tools and techniques reverse engineers like to use. Like most other types of defense, jailbreak detection is not very effective by itself, but scattering checks throughout the app's source code can improve the effectiveness of the overall anti-tampering scheme. + +#### Common Jailbreak Detection Checks + +Here we present three typical jailbreak detection techniques (more information [in this blog post](https://www.trustwave.com/Resources/SpiderLabs-Blog/Jailbreak-Detection-Methods/ "Jailbreak Detection Methods")): -#### File-based Checks +**File-based Checks:** -Check for files and directories typically associated with jailbreaks, such as: +The app might be checking for files and directories typically associated with jailbreaks, such as: ```default /Applications/Cydia.app @@ -49,13 +69,11 @@ Check for files and directories typically associated with jailbreaks, such as: /var/log/syslog ``` -#### Checking File Permissions +**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. +The app might be trying to write to a location that's outside the application's sandbox. For instance, it may attempt to create a file in, for example, the `/private directory`. If the file is created successfully, the app might assume that the device has been jailbroken. -**Swift:** - -```default +```swift do { let pathToFileInRestrictedDirectory = "/private/jailbreak.txt" try "This is a test.".write(toFile: pathToFileInRestrictedDirectory, atomically: true, encoding: String.Encoding.utf8) @@ -66,51 +84,88 @@ do { } ``` -**Objective-C:** +**Checking Protocol Handlers:** -```objectivec -NSError *error; -NSString *stringToBeWritten = @"This is a test."; -[stringToBeWritten writeToFile:@"/private/jailbreak.txt" atomically:YES - encoding:NSUTF8StringEncoding error:&error]; -if(error==nil){ - //Device is jailbroken - return YES; -} else { - //Device is not jailbroken - [[NSFileManager defaultManager] removeItemAtPath:@"/private/jailbreak.txt" error:nil]; +The app might be attempting to call well-known protocol handlers such as `cydia://` (available by default after installing [Cydia](0x08a-Testing-Tools.md#cydia)). + +```swift +if let url = URL(string: "cydia://package/com.example.package"), UIApplication.shared.canOpenURL(url) { + // Device is jailbroken } ``` -#### Checking Protocol Handlers +#### Automated Jailbreak Detection Bypass -You can check protocol handlers by attempting to open a Cydia URL. The [Cydia](0x08a-Testing-Tools.md#cydia) app store, which practically every jailbreaking tool installs by default, installs the cydia:// protocol handler. +The quickest way to bypass common Jailbreak detection mechanisms is [objection](0x08a-Testing-Tools.md#objection). You can find the implementation of the jailbreak bypass in the [jailbreak.ts script](https://github.com/sensepost/objection/blob/master/agent/src/ios/jailbreak.ts "jailbreak.ts"). -**Swift:** +#### Manual Jailbreak Detection Bypass -```default -if let url = URL(string: "cydia://package/com.example.package"), UIApplication.shared.canOpenURL(url) { - // Device is jailbroken -} +If the automated bypasses aren't innefective you need to get your hands dirty and reverse engineer the app binaries until you find the pieces of code responsible for the detection and either patch them by hand or apply runtime hooks to disable them. + +**Step 1: Reverse Engineering:** + +When you need to reverse engineer a binary looking for jailbreak detection, the most obvious way is to search for known strings, such as "jail" or "jailbreak". Note that this won't be always effective, especially when resilience measures are in place or simply when the the developer has avoided such obvious terms. + +Example: Download the [Damn Vulnerable iOS application](0x08b-Reference-Apps.md#dvia-v2) (DVIA-v2), unzip it, load the main binary into [radare2](0x08a-Testing-Tools.md#radare2) and wait for the analysis to complete. + +```sh +r2 -A ./DVIA-v2-swift/Payload/DVIA-v2.app/DVIA-v2 ``` -**Objective-C:** +Now you can list the binary's symbols using the `is` command and apply a case-insensitive grep (`~+`) for the string "jail". -```objectivec -if([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"cydia://package/com.example.package"]]){ - // Device is jailbroken +```sh +[0x1001a9790]> is~+jail +... +2230 0x001949a8 0x1001949a8 GLOBAL FUNC 0 DVIA_v2.JailbreakDetectionViewController.isJailbroken.allocator__Bool +7792 0x0016d2d8 0x10016d2d8 LOCAL FUNC 0 +[JailbreakDetection isJailbroken] +... +``` + +As you can see, there's an instance method with the siganture `-[JailbreakDetectionVC isJailbroken]`. + +**Step 2: Dynamic Hooks:** + +Now you can use Frida to bypass jailbreak detection by performing the so-called early instrumentation, that is, by replacing function implementation right at startup. + +Use `frida-trace` on your host computer: + +```bash +frida-trace -U -f /Applications/DamnVulnerableIOSApp.app/DamnVulnerableIOSApp -m "-[JailbreakDetectionVC isJailbroken]" +``` + +This will start the app, trace calls to `-[JailbreakDetectionVC isJailbroken]`, and create a JavaScript hook for each matching element. +Open `./__handlers__/__JailbreakDetectionVC_isJailbroken_.js` with your favouritte editor and edit the `onLeave` callback function. You can simply replace the return value using `retval.replace()` to always return `0`: + +```javascript +onLeave: function (log, retval, state) { + console.log("Function [JailbreakDetectionVC isJailbroken] originally returned:"+ retval); + retval.replace(0); + console.log("Changing the return value to:"+retval); } ``` +This will provide the following output: + +```bash +$ frida-trace -U -f /Applications/DamnVulnerableIOSApp.app/DamnVulnerableIOSApp -m "-[JailbreakDetectionVC isJailbroken]:" + +Instrumenting functions... `... +-[JailbreakDetectionVC isJailbroken]: Loaded handler at "./__handlers__/__JailbreakDetectionVC_isJailbroken_.js" +Started tracing 1 function. Press Ctrl+C to stop. + +Function [JailbreakDetectionVC isJailbroken] originally returned:0x1 +Changing the return value to:0x0 +``` + ### Anti-Debugging Detection Exploring applications using a debugger is a very powerful technique during reversing. You can not only track variables containing sensitive data and modify the control flow of the application, but also read and modify memory and registers. -There are several anti-debugging techniques applicable to iOS which can be categorized as preventive or as reactive; a few of them are discussed below. As a first line of defense, you can use preventive techniques to impede the debugger from attaching to the application at all. Additionally, you can also apply reactive techniques which allow the application to detect the presence of a debugger and have a chance to diverge from normal behavior. When properly distributed throughout the app, these techniques act as a secondary or supportive measure to increase the overall resilience. - -Application developers of apps processing highly sensitive data should be aware of the fact that preventing debugging is virtually impossible. If the app is publicly available, it can be run on an untrusted device, that is under full control of the attacker. A very determined attacker will eventually manage to bypass all the app's anti-debugging controls by patching the app binary or by dynamically modifying the app's behavior at runtime with tools such as Frida. +There are several anti-debugging techniques applicable to iOS which can be categorized as preventive or as reactive. When properly distributed throughout the app, these techniques act as a supportive measure to increase the overall resilience. -According to Apple, you should "[restrict use of the above code to the debug build of your program](https://developer.apple.com/library/archive/qa/qa1361/_index.html "Detecting the Debugger")". However, research shows that [many App Store apps often include these checks](https://seredynski.com/articles/a-security-review-of-1300-appstore-applications.html "A security review of 1,300 AppStore applications - 5 April 2020"). +- Preventive techniques act as a first line of defense to impede the debugger from attaching to the application at all. +- Reactive techniques allow the application to detect the presence of a debugger and have a chance to diverge from normal behavior. #### Using ptrace @@ -133,7 +188,7 @@ void anti_debug() { } ``` -To demonstrate how to bypass this technique we'll use an example of a disassembled binary that implements this approach: +**Bypass:** To demonstrate how to bypass this technique we'll use an example of a disassembled binary that implements this approach: @@ -149,68 +204,37 @@ Bypasses for other ptrace-based anti-debugging techniques can be found in ["Defe Another approach to detecting a debugger that's attached to the calling process involves `sysctl`. According to the Apple documentation, it allows processes to set system information (if having the appropriate privileges) or simply to retrieve system information (such as whether or not the process is being debugged). However, note that just the fact that an app uses `sysctl` might be an indicator of anti-debugging controls, though this [won't be always be the case](http://www.cocoawithlove.com/blog/2016/03/08/swift-wrapper-for-sysctl.html "Gathering system information in Swift with sysctl"). -The following example from the [Apple Documentation Archive](https://developer.apple.com/library/content/qa/qa1361/_index.html "How do I determine if I\'m being run under the debugger?") checks the `info.kp_proc.p_flag` flag returned by the call to `sysctl` with the appropriate parameters: - -```c -#include -#include -#include -#include -#include - -static bool AmIBeingDebugged(void) - // Returns true if the current process is being debugged (either - // running under the debugger or has a debugger attached post facto). -{ - int junk; - int mib[4]; - struct kinfo_proc info; - size_t size; +The [Apple Documentation Archive](https://developer.apple.com/library/content/qa/qa1361/_index.html "How do I determine if I\'m being run under the debugger?") includes an example which checks the `info.kp_proc.p_flag` flag returned by the call to `sysctl` with the appropriate parameters. According to Apple, you **shouldn't use this code** unless [it's for the debug build of your program](https://developer.apple.com/library/archive/qa/qa1361/_index.html "Detecting the Debugger"). - // Initialize the flags so that, if sysctl fails for some bizarre - // reason, we get a predictable result. +**Bypass:** One way to bypass this check is by patching the binary. When the code above is compiled, the disassembled version of the second half of the code is similar to the following: - info.kp_proc.p_flag = 0; + - // Initialize mib, which tells sysctl the info we want, in this case - // we're looking for information about a specific process ID. +After the instruction at offset 0xC13C, `MOVNE R0, #1` is patched and changed to `MOVNE R0, #0` (0x00 0x20 in in bytecode), the patched code is similar to the following: - mib[0] = CTL_KERN; - mib[1] = KERN_PROC; - mib[2] = KERN_PROC_PID; - mib[3] = getpid(); + - // Call sysctl. +You can also bypass a `sysctl` check by using the debugger itself and setting a breakpoint at the call to `sysctl`. This approach is demonstrated in [iOS Anti-Debugging Protections #2](https://www.coredump.gr/articles/ios-anti-debugging-protections-part-2/ "iOS Anti-Debugging Protections #2"). - size = sizeof(info); - junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0); - assert(junk == 0); +#### Using getppid - // We're being debugged if the P_TRACED flag is set. +Applications on iOS can detect if they have been started by a debugger by checking their parent PID. Normally, an application is started by the [launchd](http://newosxbook.com/articles/Ch07.pdf) process, which is the first process running in the _user mode_ and has PID=1. However, if a debugger starts an application, we can observe that `getppid` returns a PID different than `1`. This detection technique can be implemented in native code (via syscalls), using Objective-C or Swift as shown here: - return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); +```default +func AmIBeingDebugged() -> Bool { + return getppid() != 1 } ``` -One way to bypass this check is by patching the binary. When the code above is compiled, the disassembled version of the second half of the code is similar to the following: - - - -After the instruction at offset 0xC13C, `MOVNE R0, #1` is patched and changed to `MOVNE R0, #0` (0x00 0x20 in in bytecode), the patched code is similar to the following: - - - -You can also bypass a `sysctl` check by using the debugger itself and setting a breakpoint at the call to `sysctl`. This approach is demonstrated in [iOS Anti-Debugging Protections #2](https://www.coredump.gr/articles/ios-anti-debugging-protections-part-2/ "iOS Anti-Debugging Protections #2"). +**Bypass:** Similarly to the other techniques, this has also a trivial bypass (e.g. by patching the binary or by using Frida hooks). ### File Integrity Checks -There are two topics related to file integrity: - - 1. _Application source code integrity checks:_ In the "[Tampering and Reverse Engineering on iOS](0x06c-Reverse-Engineering-and-Tampering.md#debugging)" chapter, we discussed the iOS IPA application signature check. We also saw that determined reverse engineers can 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 a custom check that determines whether the signatures still match at runtime. +There are two common approaches to check file integrity: using application source code integrity checks and using file storage integrity checks. - 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. +#### Application Source Code Integrity Checks -#### Sample Implementation - Application Source Code +In the "[Tampering and Reverse Engineering on iOS](0x06c-Reverse-Engineering-and-Tampering.md#debugging)" chapter, we discussed the iOS IPA application signature check. We also saw that determined reverse engineers can 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 a custom check that determines whether the signatures still match at runtime. Apple takes care of integrity checks with DRM. However, additional controls (such as in the example below) are possible. The `mach_header` is parsed to calculate the start of the instruction data, which is used to generate the signature. Next, the signature is compared to the given signature. Make sure that the generated signature is stored or coded somewhere else. @@ -270,18 +294,17 @@ int xyz(char *dst) { } ``` -#### Sample Implementation - Storage +**Bypass:** -When ensuring the integrity of the application storage itself, you can create an HMAC or signature over either a given key-value pair or a file stored on the device. The CommonCrypto implementation is best for creating an HMAC. -If you need encryption, make sure that you encrypt and then HMAC as described in [Authenticated Encryption](https://web.archive.org/web/20210804035343/https://cseweb.ucsd.edu/~mihir/papers/oem.html "Authenticated Encryption: Relations among notions and analysis of the generic composition paradigm"). +1. Patch the anti-debugging functionality and disable the unwanted behavior by overwriting the associated code with NOP instructions. +2. Patch any stored hash that's used to evaluate the integrity of the code. +3. Use Frida to hook file system APIs and return a handle to the original file instead of the modified file. -When you generate an HMAC with CC: +#### File Storage Integrity Checks -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. +Apps might choose to ensure the integrity of the application storage itself, by creating HMAC or signature over either a given key-value pair or a file stored on the device, e.g. in the Keychain, `UserDefaults`/`NSUserDefaults`, or any database. + +For example, an app might do the following to generate an HMAC with `CommonCrypto`: ```objectivec // Allocate a buffer to hold the digest and perform the digest. @@ -293,13 +316,13 @@ When you generate an HMAC with CC: [actualData appendData: digestBuffer]; ``` -Alternatively, you can use NSData for steps 1 and 3, but you'll need to create a new buffer for step 4. - -When verifying the HMAC with CC, follow these steps: +1. Gets the data as `NSMutableData`. +2. Gets the data key (typically from the Keychain). +3. Calculates the hash value. +4. Appends the hash value to the actual data. +5. Stores the results of step 4. -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. +After that, it might be verifying the HMACs by doing the following: ```objectivec NSData* hmac = [data subdataWithRange:NSMakeRange(data.length - CC_SHA256_DIGEST_LENGTH, CC_SHA256_DIGEST_LENGTH)]; @@ -307,18 +330,15 @@ When verifying the HMAC with CC, follow these steps: 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]; - ``` -#### Bypassing File Integrity Checks - -##### When you're trying to bypass the application-source integrity checks +1. Extracts the message and the hmacbytes as separate `NSData`. +2. Repeats steps 1-3 of the procedure for generating an HMAC on the `NSData`. +3. Compares the extracted HMAC bytes to the result of step 1. -1. Patch the anti-debugging functionality and disable the unwanted behavior by overwriting the associated code with NOP instructions. -2. Patch any stored hash that's used to evaluate the integrity of the code. -3. Use Frida to hook file system APIs and return a handle to the original file instead of the modified file. +Note: if the app also encrypts files, make sure that it encrypts and then calcualtes the HMAC as described in [Authenticated Encryption](https://web.archive.org/web/20210804035343/https://cseweb.ucsd.edu/~mihir/papers/oem.html "Authenticated Encryption: Relations among notions and analysis of the generic composition paradigm"). -##### When you're trying to bypass the storage integrity checks +**Bypass:** 1. Retrieve the data from the device, as described in the "[Device Binding](#device-binding)" section. 2. Alter the retrieved data and return it to storage. @@ -327,274 +347,20 @@ When verifying the HMAC with CC, follow these steps: The presence of tools, frameworks and apps commonly used by reverse engineers may indicate an attempt to reverse engineer the app. Some of these tools can only run on a jailbroken device, while others force the app into debugging mode or depend on starting a background service on the mobile phone. Therefore, there are different ways that an app may implement to detect a reverse engineering attack and react to it, e.g. by terminating itself. -### Emulator Detection - -The goal of emulator detection is to increase the difficulty of running the app on an emulated device. This forces the reverse engineer to defeat the emulator checks or utilize the physical device, thereby barring the access required for large-scale device analysis. - -As discussed in the section [Testing on the iOS Simulator](0x06b-Basic-Security-Testing.md "Testing on the iOS Simulator") in the basic security testing chapter, the only available simulator is the one that ships with Xcode. Simulator binaries are compiled to x86 code instead of ARM code and apps compiled for a real device (ARM architecture) don't run in the simulator, hence _simulation_ protection was not so much a concern regarding iOS apps in contrast to Android with a wide range of _emulation_ choices available. - -However, since its release, [Corellium](https://www.corellium.com/) (commercial tool) has enabled real emulation, [setting itself apart from the iOS simulator](https://www.corellium.com/compare/ios-simulator). In addition to that, being a SaaS solution, Corellium enables large-scale device analysis with the limiting factor just being available funds. - -With Apple Silicon (ARM) hardware widely available, traditional checks for the presence of x86 / x64 architecture might not suffice. One potential detection strategy is to identify features and limitations available for commonly used emulation solutions. For instance, Corellium doesn't support iCloud, cellular services, camera, NFC, Bluetooth, App Store access or GPU hardware emulation ([Metal](https://developer.apple.com/documentation/metal/gpu_devices_and_work_submission/getting_the_default_gpu)). Therefore, smartly combining checks involving any of these features could be an indicator for the presence of an emulated environment. - -Pairing these results with the ones from 3rd party frameworks such as [iOS Security Suite](https://github.com/securing/IOSSecuritySuite#emulator-detector-module), [Trusteer](https://www.ibm.com/products/trusteer-mobile-sdk/details) or a no-code solution such as [Appdome](https://www.appdome.com/) (commercial solution) will provide a good line of defense against attacks utilizing emulators. - -### Obfuscation - -The chapter ["Mobile App Tampering and Reverse Engineering"](0x04c-Tampering-and-Reverse-Engineering.md#obfuscation) introduces several well-known obfuscation techniques that can be used in mobile apps in general. - -> Note: All presented techniques below may not stop reverse engineers, but combining all of those techniques will make their job significantly harder. The aim of those techniques is to discourage reverse engineers from performing further analysis. - -The following techniques can be used to obfuscate an application: - -- Name obfuscation -- Instruction substitution -- Control flow flattening -- Dead code injection -- String encryption - -### 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](https://developer.apple.com/library/content/releasenotes/General/RN-iOSSDK-7.0/index.html "iOS 7 release notes"), 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. See the "[Remediation](#remediation "Remediation")" section for more details. - -## Testing Jailbreak Detection (MSTG-RESILIENCE-1) - -### Bypassing Jailbreak Detection - -Once you start an application that has jailbreak detection enabled on a jailbroken device, you might notice one of the following things: - -1. The application closes immediately, without any notification. -2. A pop-up window indicates that the application won't run on a jailbroken device. - -In the first case, make sure the application is fully functional on non-jailbroken devices. The application may be crashing or it may have a bug that causes it to terminate. This may happen while you're testing a preproduction version of the application. - -Let's look at bypassing jailbreak detection using the [Damn Vulnerable iOS application](0x08b-Reference-Apps.md#dvia-v2) as an example again. After loading the binary into [Hopper](0x08a-Testing-Tools.md#hopper-commercial-tool) (commercial tool), you need to wait until the application is fully disassembled (look at the top bar to check the status). Then look for the "jail" string in the search box. You'll see two classes: `SFAntiPiracy` and `JailbreakDetectionVC`. You may want to decompile the functions to see what they are doing and, in particular, what they return. - - - - -As you can see, there's a class method (`+[SFAntiPiracy isTheDeviceJailbroken]`) and an instance method (`-[JailbreakDetectionVC isJailbroken]`). The main difference is that we can inject Cycript in the app and call the class method directly, whereas the instance method requires first looking for instances of the target class. The function `choose` will look in the memory heap for known signatures of a given class and return an array of instances. Putting an application into a desired state (so that the class is indeed instantiated) is important. - -Let's inject Cycript into our process (look for your PID with `top`): - -```bash -iOS8-jailbreak:~ root# cycript -p 12345 -cy# [SFAntiPiracy isTheDeviceJailbroken] -true -``` - -As you can see, our class method was called directly, and it returned "true". Now, let's call the `-[JailbreakDetectionVC isJailbroken]` instance method. First, we have to call the `choose` function to look for instances of the `JailbreakDetectionVC` class. - -```bash -cy# a=choose(JailbreakDetectionVC) -[] -``` - -Oops! The return value is an empty array. That means that there are no instances of this class registered in the runtime. In fact, we haven't clicked the second "Jailbreak Test" button, which initializes this class: - -```bash -cy# a=choose(JailbreakDetectionVC) -[#""] -cy# [a[0] isJailbroken] -True -``` - - - -Now you understand why having your application in a desired state is important. At this point, bypassing jailbreak detection with Cycript is trivial. We can see that the function returns a boolean; we just need to replace the return value. We can replace the return value by replacing the function implementation with Cycript. Please note that this will actually replace the function under its given name, so beware of side effects if the function modifies anything in the application: - -```bash -cy# JailbreakDetectionVC.prototype.isJailbroken=function(){return false} -cy# [a[0] isJailbroken] -false -``` - - - -In this case we have bypassed the jailbreak detection of the application! - -Now, imagine that the application is closing immediately after detecting that the device is jailbroken. You don't have time to launch Cycript and replace the function implementation. Instead, you have to use CydiaSubstrate, employ a proper hooking function like `MSHookMessageEx`, and compile the tweak. There are [good sources](https://manualzz.com/doc/26490749/jailbreak-root-detection-evasion-study-on-ios-and-android "Jailbreak/Root Detection Evasion Study on iOS and Android") for how to do this; however, by using Frida, we can more easily perform early instrumentation and we can build on our gathered skills from previous tests. - -One feature of Frida that we will use to bypass jailbreak detection is so-called early instrumentation, that is, we will replace function implementation at startup. - -1. Make sure that `frida-server` is running on your iOS Device. -2. Make sure that `Frida` is [installed](https://www.frida.re/docs/installation/ "Frida Installation") on your host computer. -3. The iOS device must be connected via USB cable. -4. Use `frida-trace` on your host computer: - -```bash -frida-trace -U -f /Applications/DamnVulnerableIOSApp.app/DamnVulnerableIOSApp -m "-[JailbreakDetectionVC isJailbroken]" -``` - -This will start DamnVulnerableIOSApp, trace calls to `-[JailbreakDetectionVC isJailbroken]`, and create a JavaScript hook with the `onEnter` and `onLeave` callback functions. Now, replacing the return value via `value.replace` is trivial, as shown in the following example: - -```javascript - onLeave: function (log, retval, state) { - console.log("Function [JailbreakDetectionVC isJailbroken] originally returned:"+ retval); - retval.replace(0); - console.log("Changing the return value to:"+retval); - } -``` - -This will provide the following output: - -```bash -$ frida-trace -U -f /Applications/DamnVulnerableIOSApp.app/DamnVulnerableIOSApp -m "-[JailbreakDetectionVC isJailbroken]:" - -Instrumenting functions... `... --[JailbreakDetectionVC isJailbroken]: Loaded handler at "./__handlers__/__JailbreakDetectionVC_isJailbroken_.js" -Started tracing 1 function. Press Ctrl+C to stop. -Function [JailbreakDetectionVC isJailbroken] originally returned:0x1 -Changing the return value to:0x0 - /* TID 0x303 */ - 6890 ms -[JailbreakDetectionVC isJailbroken] -Function [JailbreakDetectionVC isJailbroken] originally returned:0x1 -Changing the return value to:0x0 - 22475 ms -[JailbreakDetectionVC isJailbroken] -``` - -Note the two calls to `-[JailbreakDetectionVC isJailbroken]`, which correspond to two physical taps on the app's GUI. - -One more way to bypass Jailbreak detection mechanisms that rely on file system checks is [objection](0x08a-Testing-Tools.md#objection). You can find the implementation of the jailbreak bypass in the [jailbreak.ts script](https://github.com/sensepost/objection/blob/master/agent/src/ios/jailbreak.ts "jailbreak.ts"). - -See below a Python script for hooking Objective-C methods and native functions: - -```python -import frida -import sys - -try: - session = frida.get_usb_device().attach("Target Process") -except frida.ProcessNotFoundError: - print "Failed to attach to the target process. Did you launch the app?" - sys.exit(0) - -script = session.create_script(""" - - // Handle fork() based check - - var fork = Module.findExportByName("libsystem_c.dylib", "fork"); - - Interceptor.replace(fork, new NativeCallback(function () { - send("Intercepted call to fork()."); - return -1; - }, 'int', [])); - - var system = Module.findExportByName("libsystem_c.dylib", "system"); - - Interceptor.replace(system, new NativeCallback(function () { - send("Intercepted call to system()."); - return 0; - }, 'int', [])); - - // Intercept checks for Cydia URL handler - - var canOpenURL = ObjC.classes.UIApplication["- canOpenURL:"]; - - Interceptor.attach(canOpenURL.implementation, { - onEnter: function(args) { - var url = ObjC.Object(args[2]); - send("[UIApplication canOpenURL:] " + path.toString()); - }, - onLeave: function(retval) { - send ("canOpenURL returned: " + retval); - } - - }); - - // Intercept file existence checks via [NSFileManager fileExistsAtPath:] - - var fileExistsAtPath = ObjC.classes.NSFileManager["- fileExistsAtPath:"]; - var hideFile = 0; - - Interceptor.attach(fileExistsAtPath.implementation, { - onEnter: function(args) { - var path = ObjC.Object(args[2]); - // send("[NSFileManager fileExistsAtPath:] " + path.toString()); - - if (path.toString() == "/Applications/Cydia.app" || path.toString() == "/bin/bash") { - hideFile = 1; - } - }, - onLeave: function(retval) { - if (hideFile) { - send("Hiding jailbreak file...");MM - retval.replace(0); - hideFile = 0; - } - - // send("fileExistsAtPath returned: " + retval); - } - }); - - - /* If the above doesn't work, you might want to hook low level file APIs as well - - var openat = Module.findExportByName("libsystem_c.dylib", "openat"); - var stat = Module.findExportByName("libsystem_c.dylib", "stat"); - var fopen = Module.findExportByName("libsystem_c.dylib", "fopen"); - var open = Module.findExportByName("libsystem_c.dylib", "open"); - var faccesset = Module.findExportByName("libsystem_kernel.dylib", "faccessat"); - - */ - -""") - -def on_message(message, data): - if 'payload' in message: - print(message['payload']) - -script.on('message', on_message) -script.load() -sys.stdin.read() -``` - -## Testing Anti-Debugging Detection (MSTG-RESILIENCE-2) - -### Using getppid - -Applications on iOS can detect if they have been started by a debugger by checking their parent PID. Normally, an application is started by the [launchd](http://newosxbook.com/articles/Ch07.pdf) process, which is the first process running in the _user mode_ and has PID=1. However, if a debugger starts an application, we can observe that `getppid` returns a PID different than 1. This detection technique can be implemented in native code (via syscalls), using Objective-C or Swift as shown here: - -```default -func AmIBeingDebugged() -> Bool { - return getppid() != 1 -} -``` - -Similarly to the other techniques, this has also a trivial bypass (e.g. by patching the binary or by using Frida hooks). - -## Testing File Integrity Checks (MSTG-RESILIENCE-3 and MSTG-RESILIENCE-11) - -### Effectiveness Assessment - -**Application source code integrity checks:** - -Run the app on the device in an unmodified state and make sure that everything works. Then apply patches to the executable using optool, re-sign the app as described in the chapter "Basic Security Testing", and run it. -The app should detect the modification and respond in some way. At the very least, the app should alert the user and/or terminate the app. Work on bypassing the defenses and answer the following questions: - -- Can the mechanisms be bypassed trivially (e.g., by hooking a single API function)? -- How difficult is identifying the anti-debugging code via static and dynamic analysis? -- Did you need to write custom code to disable the defenses? How much time did you need? -- What is your assessment of the difficulty of bypassing the mechanisms? - -**Storage integrity checks:** +You can detect popular reverse engineering tools that have been installed in an unmodified form by looking for associated application packages, files, processes, or other tool-specific modifications and artifacts. In the following examples, we'll discuss different ways to detect the Frida instrumentation framework, which is used extensively in this guide and also in the real world. Other tools, such as Cydia Substrate or Cycript, can be detected similarly. Note that injection, hooking and DBI (Dynamic Binary Instrumentation) tools can often be detected implicitly, through runtime integrity checks, which are discussed below. -A similar approach works. Answer the following questions: +**Bypass:** -- Can the mechanisms be bypassed trivially (e.g., by changing the contents of a file or a key-value pair)? -- How difficult is obtaining the HMAC key or the asymmetric private key? -- Did you need to write custom code to disable the defenses? How much time did you need? -- What is your assessment of the difficulty of bypassing the mechanisms?? +The following steps should guide you when bypassing detection of reverse engineering tools: -## Testing Reverse Engineering Tools Detection (MSTG-RESILIENCE-4) +1. Patch the anti reverse engineering functionality. Disable the unwanted behavior by patching the binary through usage of radare2/Cutter or Ghidra. +2. Use Frida or Cydia Substrate to hook file system APIs on the Objective-C/Swift or native layers. Return a handle to the original file, not the modified file. -### Detection Methods +Refer to the chapter "[Tampering and Reverse Engineering on iOS](0x06c-Reverse-Engineering-and-Tampering.md)" for examples of patching and code injection. -You can detect popular reverse engineering tools that have been installed in an unmodified form by looking for associated application packages, files, processes, or other tool-specific modifications and artifacts. In the following examples, we'll discuss different ways to detect the Frida instrumentation framework, which is used extensively in this guide and also in the real world. Other tools, such as Cydia Substrate or Cycript, can be detected similarly. Note that injection, hooking and DBI (Dynamic Binary Instrumentation) tools can often be detected implicitly, through runtime integrity checks, which are discussed below. +#### Frida Detection -For instance, Frida runs under the name of frida-server in its default configuration (injected mode) on a jailbroken device. When you explicitly attach to a target app (e.g. via frida-trace or the Frida CLI), Frida injects a frida-agent into the memory of the app. Therefore, you may expect to find it there after attaching to the app (and not before). On Android, verifying this is pretty straightforward as you can simply grep for the string "frida" in the memory maps of the process ID in the `proc` directory (`/proc//maps`). +Frida runs under the name of frida-server in its default configuration (injected mode) on a jailbroken device. When you explicitly attach to a target app (e.g. via frida-trace or the Frida CLI), Frida injects a frida-agent into the memory of the app. Therefore, you may expect to find it there after attaching to the app (and not before). On Android, verifying this is pretty straightforward as you can simply grep for the string "frida" in the memory maps of the process ID in the `proc` directory (`/proc//maps`). However, on iOS the `proc` directory is not available, but you can list the loaded dynamic libraries in an app with the function `_dyld_image_count`. Frida may also run in the so-called embedded mode, which also works for non-jailbroken devices. It consists of embedding a [frida-gadget](https://www.frida.re/docs/gadget/ "Frida Gadget") into the IPA and _forcing_ the app to load it as one of its native libraries. @@ -636,38 +402,23 @@ Please remember that this table is far from exhaustive. For example, two other p Both would _help_ to detect Substrate or Frida's Interceptor but, for example, won't be effective against Frida's Stalker. Remember that the success of each of these detection methods will depend on whether you're using a jailbroken device, the specific version of the jailbreak and method and/or the version of the tool itself. At the end, this is part of the cat and mouse game of protecting data being processed on an uncontrolled environment (the end user's device). -> It is important to note that these controls are only increasing the complexity of the reverse engineering process. If used, the best approach is to combine the controls cleverly instead of using them individually. However, none of them can assure a 100% effectiveness, as the reverse engineer will always have full access to the device and will therefore always win! You also have to consider that integrating some of the controls into your app might increase the complexity of your app and even have an impact on its performance. - -### Effectiveness Assessment - -Launch the app with various reverse engineering tools and frameworks installed on your test device. Include at least the following: Frida, Cydia Substrate, Cycript and SSL Kill Switch. - -The app should respond in some way to the presence of those tools. For example by: - -- Alerting the user and asking for accepting liability. -- Preventing execution by gracefully terminating. -- Securely wiping any sensitive data stored on the device. -- Reporting to a backend server, e.g, for fraud detection. +### Emulator Detection -Next, work on bypassing the detection of the reverse engineering tools and answer the following questions: +The goal of emulator detection is to increase the difficulty of running the app on an emulated device. This forces the reverse engineer to defeat the emulator checks or utilize the physical device, thereby barring the access required for large-scale device analysis. -- Can the mechanisms be bypassed trivially (e.g., by hooking a single API function)? -- How difficult is identifying the anti reverse engineering code via static and dynamic analysis? -- Did you need to write custom code to disable the defenses? How much time did you need? -- What is your assessment of the difficulty of bypassing the mechanisms? +As discussed in the section [Testing on the iOS Simulator](0x06b-Basic-Security-Testing.md#testing-on-the-ios-simulator "Testing on the iOS Simulator") in the basic security testing chapter, the only available simulator is the one that ships with Xcode. Simulator binaries are compiled to x86 code instead of ARM code and apps compiled for a real device (ARM architecture) don't run in the simulator, hence _simulation_ protection was not so much a concern regarding iOS apps in contrast to Android with a wide range of _emulation_ choices available. -The following steps should guide you when bypassing detection of reverse engineering tools: +However, since its release, [Corellium](https://www.corellium.com/) (commercial tool) has enabled real emulation, [setting itself apart from the iOS simulator](https://www.corellium.com/compare/ios-simulator). In addition to that, being a SaaS solution, Corellium enables large-scale device analysis with the limiting factor just being available funds. -1. Patch the anti reverse engineering functionality. Disable the unwanted behavior by patching the binary through usage of radare2/Cutter or Ghidra. -2. Use Frida or Cydia Substrate to hook file system APIs on the Objective-C/Swift or native layers. Return a handle to the original file, not the modified file. +With Apple Silicon (ARM) hardware widely available, traditional checks for the presence of x86 / x64 architecture might not suffice. One potential detection strategy is to identify features and limitations available for commonly used emulation solutions. For instance, Corellium doesn't support iCloud, cellular services, camera, NFC, Bluetooth, App Store access or GPU hardware emulation ([Metal](https://developer.apple.com/documentation/metal/gpu_devices_and_work_submission/getting_the_default_gpu)). Therefore, smartly combining checks involving any of these features could be an indicator for the presence of an emulated environment. -Refer to the chapter "[Tampering and Reverse Engineering on iOS](0x06c-Reverse-Engineering-and-Tampering.md)" for examples of patching and code injection. +Pairing these results with the ones from 3rd party frameworks such as [iOS Security Suite](https://github.com/securing/IOSSecuritySuite#emulator-detector-module), [Trusteer](https://www.ibm.com/products/trusteer-mobile-sdk/details) or a no-code solution such as [Appdome](https://www.appdome.com/) (commercial solution) will provide a good line of defense against attacks utilizing emulators. -## Testing Emulator Detection (MSTG-RESILIENCE-5) +### Obfuscation -## Testing Obfuscation (MSTG-RESILIENCE-9) +The chapter ["Mobile App Tampering and Reverse Engineering"](0x04c-Tampering-and-Reverse-Engineering.md#obfuscation) introduces several well-known obfuscation techniques that can be used in mobile apps in general. -### Name Obfuscation +#### Name Obfuscation The standard compiler generates binary symbols based on class and function names from the source code. Therefore, if no obfuscation was applied, symbol names remain meaningful and can be easily read straight from the app binary. For instance, a function which detects a jailbreak can be located by searching for relevant keywords (e.g. "jailbreak"). The listing below shows the disassembled function `JailbreakDetectionViewController.jailbreakTest4Tapped` from the Damn Vulnerable iOS App ([DVIA-v2](0x08b-Reference-Apps.md#dvia-v2)). @@ -687,11 +438,11 @@ mov rbp, rsp Nevertheless, this only applies to the names of functions, classes and fields. The actual code remains unmodified, so an attacker can still read the disassembled version of the function and try to understand its purpose (e.g. to retrieve the logic of a security algorithm). -### Instruction Substitution +#### Instruction Substitution This technique replaces standard binary operators like addition or subtraction with more complex representations. For example an addition `x = a + b` can be represented as `x = -(-a) - (-b)`. However, using the same replacement representation could be easily reversed, so it is recommended to add multiple substitution techniques for a single case and introduce a random factor. This technique is vulnerable to deobfuscation, but depending on the complexity and depth of the substitutions, applying it can still be time consuming. -### Control Flow Flattening +#### Control Flow Flattening Control flow flattening replaces original code with a more complex representation. The transformation breaks the body of a function into basic blocks and puts them all inside a single infinite loop with a switch statement that controls the program flow. This makes the program flow significantly harder to follow because it removes the natural conditional constructs that usually make the code easier to read. @@ -699,88 +450,143 @@ Control flow flattening replaces original code with a more complex representatio The image shows how control flow flattening alters code (see "[Obfuscating C++ programs via control flow flattening](http://ac.inf.elte.hu/Vol_030_2009/003.pdf)") -### Dead Code Injection +#### Dead Code Injection This technique makes the program's control flow more complex by injecting dead code into the program. Dead code is a stub of code that doesn’t affect the original program’s behaviour but increases the overhead for the reverse engineering process. -### String Encryption +#### String Encryption Applications are often compiled with hardcoded keys, licences, tokens and endpoint URLs. By default, all of them are stored in plaintext in the data section of an application’s binary. This technique encrypts these values and injects stubs of code into the program that will decrypt that data before it is used by the program. -### Recommended Tools +#### Recommended Tools -- [SwiftShield](https://github.com/rockbruno/swiftshield) can be used to perform name obfuscation. It reads the source code of the Xcode project and replaces all names of classes, methods and fields with random values before the compiler is used. +- [SwiftShield](0x08a-Testing-Tools.md#swiftshield) can be used to perform name obfuscation. It reads the source code of the Xcode project and replaces all names of classes, methods and fields with random values before the compiler is used. - [obfuscator-llvm](https://github.com/obfuscator-llvm) operates on the Intermediate Representation (IR) instead of the source code. It can be used for symbol obfuscation, string encryption and control flow flattening. Since it's based on IR, it can hide out significantly more information about the application as compared to SwiftShield. Learn more about iOS obfuscation techniques [here](https://faculty.ist.psu.edu/wu/papers/obf-ii.pdf). -#### How to use SwiftShield +### Device Binding -> Warning: SwiftShield irreversibly overwrites all your source files. Ideally, you should have it run only on your CI server, and on release builds. +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. -[SwiftShield](https://github.com/rockbruno/swiftshield "SwiftShield") is a tool that generates irreversible, encrypted names for your iOS project's objects (including your Pods and Storyboards). This raises the bar for reverse engineers and will produce less helpful output when using reverse engineering tools such as class-dump and Frida. +[Since iOS 7.0](https://developer.apple.com/library/content/releasenotes/General/RN-iOSSDK-7.0/index.html "iOS 7 release notes"), hardware identifiers (such as MAC addresses) are off-limits but there are othre methods for implementing device binding in iOS: -A sample Swift project is used to demonstrate the usage of SwiftShield. +- **`identifierForVendor`**: You can use `[[UIDevice currentDevice] identifierForVendor]` (in Objective-C), `UIDevice.current.identifierForVendor?.uuidString` (in Swift3), or `UIDevice.currentDevice().identifierForVendor?.UUIDString` (in Swift2). The value of `identifierForVendor` may not be the same if you reinstall the app after other apps from the same vendor are installed and it may change when you update your app bundle's name. Therefore it is best to combine it with something in the Keychain. +- **Using the Keychain**: You can store something in the Keychain to identify the application's instance. To make sure that this data is not backed up, use `kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly` (if you want to secure the data and properly enforce a passcode or Touch ID requirement), `kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly`, or `kSecAttrAccessibleWhenUnlockedThisDeviceOnly`. +- **Using Google Instance ID**: see the [implementation for iOS here](https://developers.google.com/instance-id/guides/ios-implementation "iOS implementation Google Instance ID"). -- Check out . -- Open the project in Xcode and make sure that the project is building successfully (Product / Build or Apple-Key + B). -- [Download](https://github.com/rockbruno/swiftshield/releases "SwiftShield Download") the latest release of SwiftShield and unzip it. -- Go to the directory where you downloaded SwiftShield and copy the swiftshield executable to `/usr/local/bin`: +Any scheme based on these methods will be more secure the moment a passcode and/or Touch ID is enabled, the materials stored in the Keychain or filesystem are protected with protection classes (such as `kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly` and `kSecAttrAccessibleWhenUnlockedThisDeviceOnly`), and the `SecAccessControlCreateFlags` is set either with `kSecAccessControlDevicePasscode` (for passcodes), `kSecAccessControlUserPresence` (passcode, Face ID or Touch ID), `kSecAccessControlBiometryAny` (Face ID or Touch ID) or `kSecAccessControlBiometryCurrentSet` (Face ID / Touch ID: but current enrolled biometrics only). -```bash -cp swiftshield/swiftshield /usr/local/bin/ -``` +## Testing Jailbreak Detection (MSTG-RESILIENCE-1) -- In your terminal go into the SwiftSecurity directory (which you checked out in step 1) and execute the command swiftshield (which you downloaded in step 3): +To test for jailbreak detection install the app on a jailbroken device. -```bash -$ cd SwiftSecurity -$ swiftshield -automatic -project-root . -automatic-project-file SwiftSecurity.xcodeproj -automatic-project-scheme SwiftSecurity -SwiftShield 3.4.0 -Automatic mode -Building project to gather modules and compiler arguments... --- Indexing ReverseEngineeringToolsChecker.swift -- -Found declaration of ReverseEngineeringToolsChecker (s:13SwiftSecurity30ReverseEngineeringToolsCheckerC) -Found declaration of amIReverseEngineered (s:13SwiftSecurity30ReverseEngineeringToolsCheckerC20amIReverseEngineeredSbyFZ) -Found declaration of checkDYLD (s:13SwiftSecurity30ReverseEngineeringToolsCheckerC9checkDYLD33_D6FE91E9C9AEC4D13973F8ABFC1AC788LLSbyFZ) -Found declaration of checkExistenceOfSuspiciousFiles (s:13SwiftSecurity30ReverseEngineeringToolsCheckerC31checkExistenceOfSuspiciousFiles33_D6FE91E9C9AEC4D13973F8ABFC1AC788LLSbyFZ) -... -``` +**Launch the app and see what happens:** -SwiftShield is now detecting class and method names and is replacing their identifier with an encrypted value. +If it implements jailbreak detection, you might notice one of the following things: -In the original source code you can see all the class and method identifiers: +- The app crashes and closes immediately, without any notification. +- A pop-up window indicates that the app won't run on a jailbroken device. - +Note that crashes might be an indicator of jailbreak detection but the app may be crashing for any other reasons, e.g. it may have a bug. We recommend to test the app on non-jailbroken device first, especially when you're testing preproduction versions. -SwiftShield was now replacing all of them with encrypted values that leave no trace to their original name or intention of the class/method: +**Launch the app and try to bypass Jailbreak Detection using an automated tool:** - +If it implements jailbreak detection, you might be able to see indicators of that in the output of the tool. See section ["Automated Jailbreak Detection Bypass"](#automated-jailbreak-detection-bypass). -After executing `swiftshield` a new directory will be created called `swiftshield-output`. In this directory another directory is created with a timestamp in the folder name. This directory contains a text file called `conversionMap.txt`, that maps the encrypted strings to their original values. +**Reverse Engineer the app:** -```bash -$ cat conversionMap.txt -// -// SwiftShield Conversion Map -// Automatic mode for SwiftSecurity, 2020-01-02 13.51.03 -// Deobfuscate crash logs (or any text file) by running: -// swiftshield -deobfuscate CRASH_FILE -deobfuscate_map THIS_FILE -// - -ViewController ===> hTOUoUmUcEZUqhVHRrjrMUnYqbdqWByU -viewDidLoad ===> DLaNRaFbfmdTDuJCPFXrGhsWhoQyKLnO -sceneDidBecomeActive ===> SUANAnWpkyaIWlGUqwXitCoQSYeVilGe -AppDelegate ===> KftEWsJcctNEmGuvwZGPbusIxEFOVcIb -Deny_Debugger ===> lKEITOpOvLWCFgSCKZdUtpuqiwlvxSjx -Button_Emulator ===> akcVscrZFdBBYqYrcmhhyXAevNdXOKeG -``` +The app might be using techniques that are not implemented in the automated tools that you've used. If that's the case you must reverse engineer the app to find proofs. See section ["Manual Jailbreak Detection Bypass"](#manual-jailbreak-detection-bypass). -This is needed for [deobfuscating encrypted crash logs](https://github.com/rockbruno/swiftshield#-deobfuscating-encrypted-crash-logs "Deobfuscating encrypted Crash logs"). +## Testing Anti-Debugging Detection (MSTG-RESILIENCE-2) -Another example project is available in SwiftShield's [Github repo](https://github.com/rockbruno/swiftshield/tree/master/ExampleProject "SwiftShieldExample"), that can be used to test the execution of SwiftShield. +In order to test for anti-debugging detection you can try to attach a debugger to the app and see what happens. -### Static Analysis +The app should respond in some way. For example by: + +- Alerting the user and asking for accepting liability. +- Preventing execution by gracefully terminating. +- Securely wiping any sensitive data stored on the device. +- Reporting to a backend server, e.g, for fraud detection. + +Try to hook or reverse engineer the app using the methods from section ["Anti-Debugging Detection"](#anti-debugging-detection). + +Next, work on bypassing the detection and answer the following questions: + +- Can the mechanisms be bypassed trivially (e.g., by hooking a single API function)? +- How difficult is identifying the detection code via static and dynamic analysis? +- Did you need to write custom code to disable the defenses? How much time did you need? +- What is your assessment of the difficulty of bypassing the mechanisms? + +## Testing File Integrity Checks (MSTG-RESILIENCE-3 and MSTG-RESILIENCE-11) + +**Application Source Code Integrity Checks:** + +Run the app on the device in an unmodified state and make sure that everything works. Then apply patches to the executable using optool, re-sign the app as described in the chapter ["iOS Tampering and Reverse Engineering"](0x06c-Reverse-Engineering-and-Tampering.md#patching-repackaging-and-re-signing), and run it. + +The app should respond in some way. For example by: + +- Alerting the user and asking for accepting liability. +- Preventing execution by gracefully terminating. +- Securely wiping any sensitive data stored on the device. +- Reporting to a backend server, e.g, for fraud detection. + +Work on bypassing the defenses and answer the following questions: + +- Can the mechanisms be bypassed trivially (e.g., by hooking a single API function)? +- How difficult is identifying the detection code via static and dynamic analysis? +- Did you need to write custom code to disable the defenses? How much time did you need? +- What is your assessment of the difficulty of bypassing the mechanisms? + +**File Storage Integrity Checks:** + +Go to the app data directories as indicated in section ["Accessing App Data Directories"](0x06b-Basic-Security-Testing.md#accessing-app-data-directories) and modify some files. + +Next, work on bypassing the defenses and answer the following questions: + +- Can the mechanisms be bypassed trivially (e.g., by changing the contents of a file or a key-value pair)? +- How difficult is obtaining the HMAC key or the asymmetric private key? +- Did you need to write custom code to disable the defenses? How much time did you need? +- What is your assessment of the difficulty of bypassing the mechanisms?? + +## Testing Reverse Engineering Tools Detection (MSTG-RESILIENCE-4) + +Launch the app with various reverse engineering tools and frameworks installed on your test device, such as Frida, Cydia Substrate, Cycript or SSL Kill Switch. + +The app should respond in some way to the presence of those tools. For example by: + +- Alerting the user and asking for accepting liability. +- Preventing execution by gracefully terminating. +- Securely wiping any sensitive data stored on the device. +- Reporting to a backend server, e.g, for fraud detection. + +Next, work on bypassing the detection of the reverse engineering tools and answer the following questions: + +- Can the mechanisms be bypassed trivially (e.g., by hooking a single API function)? +- How difficult is identifying the detection code via static and dynamic analysis? +- Did you need to write custom code to disable the defenses? How much time did you need? +- What is your assessment of the difficulty of bypassing the mechanisms? + +## Testing Emulator Detection (MSTG-RESILIENCE-5) + +In order to test for emulator detection you can try to run the app on different emulators as indicated in section ["Emulator Detection"](#emulator-detection) and see what happens. + +The app should respond in some way. For example by: + +- Alerting the user and asking for accepting liability. +- Preventing execution by gracefully terminating. +- Reporting to a backend server, e.g, for fraud detection. + +You can also reverse engineer the app using ideas for strings and methods from section ["Emulator Detection"](#emulator-detection). + +Next, work on bypassing this detection and answer the following questions: + +- Can the mechanisms be bypassed trivially (e.g., by hooking a single API function)? +- How difficult is identifying the detection code via static and dynamic analysis? +- Did you need to write custom code to disable the defenses? How much time did you need? +- What is your assessment of the difficulty of bypassing the mechanisms? + +## Testing Obfuscation (MSTG-RESILIENCE-9) Attempt to disassemble the Mach-O in the IPA and any included library files in the "Frameworks" directory (.dylib or .framework files), and perform static analysis. At the very least, the app's core functionality (i.e., the functionality meant to be obfuscated) shouldn't be easily discerned. Verify that: @@ -794,17 +600,17 @@ For a more detailed assessment, you need a detailed understanding of the relevan ### Static Analysis -When the source code is available, there are a few bad coding practices you can look for, such as +To test for device binding you can look for: - MAC addresses: there are several ways to find the MAC address. When you use `CTL_NET` (a network subsystem) or `NET_RT_IFLIST` (getting the configured interfaces) or when the mac-address gets formatted, you'll often see formatting code for printing, such as `"%x:%x:%x:%x:%x:%x"`. -- using the UDID: `[[[UIDevice currentDevice] identifierForVendor] UUIDString];` and `UIDevice.current.identifierForVendor?.uuidString` in Swift3. +- UDID usage: `[[[UIDevice currentDevice] identifierForVendor] UUIDString];` and `UIDevice.current.identifierForVendor?.uuidString` in Swift3. - Any Keychain- or filesystem-based binding, which isn't protected by `SecAccessControlCreateFlags` or and doesn't use protection classes, such as `kSecAttrAccessibleAlways` and `kSecAttrAccessibleAlwaysThisDeviceOnly`. ### Dynamic Analysis There are several ways to test the application binding. -#### Dynamic Analysis with A Simulator +**Using A Simulator:** Take the following steps when you want to verify app-binding in a simulator: @@ -821,7 +627,7 @@ Take the following steps when you want to verify app-binding in a simulator: We are saying that the binding "may" not be working because not everything is unique in simulators. -#### Dynamic Analysis Using Two Jailbroken Devices +**Using Two Jailbroken Devices:** Take the following steps when you want to verify app-binding with two jailbroken devices: @@ -835,18 +641,10 @@ Take the following steps when you want to verify app-binding with two jailbroken 5. Overwrite the application data extracted during step 3. The Keychain data must be added manually. 6. Can you continue in an authenticated state? If so, then binding may not be working properly. -### Remediation - -Before we describe the usable identifiers, let's quickly discuss how they can be used for binding. There are three methods for device binding in iOS: - -- You can use `[[UIDevice currentDevice] identifierForVendor]` (in Objective-C), `UIDevice.current.identifierForVendor?.uuidString` (in Swift3), or `UIDevice.currentDevice().identifierForVendor?.UUIDString` (in Swift2). The value of `identifierForVendor` may not be the same if you reinstall the app after other apps from the same vendor are installed and it may change when you update your app bundle's name. Therefore it is best to combine it with something in the Keychain. -- You can store something in the Keychain to identify the application's instance. To make sure that this data is not backed up, use `kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly` (if you want to secure the data and properly enforce a passcode or Touch ID requirement), `kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly`, or `kSecAttrAccessibleWhenUnlockedThisDeviceOnly`. -- You can use Google and its Instance ID for [iOS](https://developers.google.com/instance-id/guides/ios-implementation "iOS implementation Google Instance ID"). - -Any scheme based on these methods will be more secure the moment a passcode and/or Touch ID is enabled, the materials stored in the Keychain or filesystem are protected with protection classes (such as `kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly` and `kSecAttrAccessibleWhenUnlockedThisDeviceOnly`), and the `SecAccessControlCreateFlags` is set either with `kSecAccessControlDevicePasscode` (for passcodes), `kSecAccessControlUserPresence` (passcode, Face ID or Touch ID), `kSecAccessControlBiometryAny` (Face ID or Touch ID) or `kSecAccessControlBiometryCurrentSet` (Face ID / Touch ID: but current enrolled biometrics only). - ## References +- OWASP Technical Risks of Reverse Engineering and Unauthorized Code Modification - +- OWASP Architectural Principles That Prevent Code Modification or Reverse Engineering- - [#geist] Dana Geist, Marat Nigmatullin. Jailbreak/Root Detection Evasion Study on iOS and Android - - Jan Seredynski. A security review of 1,300 AppStore applications (5 April 2020) - From 8ebbff6c78aa3b49590fb91faf1ee9a71e28accc Mon Sep 17 00:00:00 2001 From: Carlos Holguera Date: Fri, 25 Nov 2022 11:53:14 +0100 Subject: [PATCH 6/9] fix typo --- .../0x06j-Testing-Resiliency-Against-Reverse-Engineering.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document/0x06j-Testing-Resiliency-Against-Reverse-Engineering.md b/Document/0x06j-Testing-Resiliency-Against-Reverse-Engineering.md index f2103e47a8..e7467e439f 100644 --- a/Document/0x06j-Testing-Resiliency-Against-Reverse-Engineering.md +++ b/Document/0x06j-Testing-Resiliency-Against-Reverse-Engineering.md @@ -122,7 +122,7 @@ Now you can list the binary's symbols using the `is` command and apply a case-in ... ``` -As you can see, there's an instance method with the siganture `-[JailbreakDetectionVC isJailbroken]`. +As you can see, there's an instance method with the signature `-[JailbreakDetectionVC isJailbroken]`. **Step 2: Dynamic Hooks:** From 5d3883e38d4ccbada1b1fc88ccf27927e223532b Mon Sep 17 00:00:00 2001 From: Carlos Holguera Date: Sat, 26 Nov 2022 12:20:17 +0100 Subject: [PATCH 7/9] Apply suggestions from code review Co-authored-by: Jeroen Beckers --- ...-Resiliency-Against-Reverse-Engineering.md | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/Document/0x06j-Testing-Resiliency-Against-Reverse-Engineering.md b/Document/0x06j-Testing-Resiliency-Against-Reverse-Engineering.md index e7467e439f..132e6c5541 100644 --- a/Document/0x06j-Testing-Resiliency-Against-Reverse-Engineering.md +++ b/Document/0x06j-Testing-Resiliency-Against-Reverse-Engineering.md @@ -16,7 +16,7 @@ The **lack of any of these measures does not cause a vulnerability** - instead, None of these measures can assure a 100% effectiveness, as the reverse engineer will always have full access to the device and will therefore always win (given enough time and resources)! -For example, preventing debugging is virtually impossible. If the app is publicly available, it can be run on an untrusted device, that is under full control of the attacker. A very determined attacker will eventually manage to bypass all the app's anti-debugging controls by patching the app binary or by dynamically modifying the app's behavior at runtime with tools such as Frida. +For example, preventing debugging is virtually impossible. If the app is publicly available, it can be run on an untrusted device that is under full control of the attacker. A very determined attacker will eventually manage to bypass all the app's anti-debugging controls by patching the app binary or by dynamically modifying the app's behavior at runtime with tools such as Frida. ### Jailbreak Detection @@ -71,7 +71,7 @@ The app might be checking for files and directories typically associated with ja **Checking File Permissions:** -The app might be trying to write to a location that's outside the application's sandbox. For instance, it may attempt to create a file in, for example, the `/private directory`. If the file is created successfully, the app might assume that the device has been jailbroken. +The app might be trying to write to a location that's outside the application's sandbox. For instance, it may attempt to create a file in, for example, the `/private` directory. If the file is created successfully, the app can assume that the device has been jailbroken. ```swift do { @@ -100,7 +100,7 @@ The quickest way to bypass common Jailbreak detection mechanisms is [objection]( #### Manual Jailbreak Detection Bypass -If the automated bypasses aren't innefective you need to get your hands dirty and reverse engineer the app binaries until you find the pieces of code responsible for the detection and either patch them by hand or apply runtime hooks to disable them. +If the automated bypasses aren't effective you need to get your hands dirty and reverse engineer the app binaries until you find the pieces of code responsible for the detection and either patch them statically or apply runtime hooks to disable them. **Step 1: Reverse Engineering:** @@ -302,9 +302,9 @@ int xyz(char *dst) { #### File Storage Integrity Checks -Apps might choose to ensure the integrity of the application storage itself, by creating HMAC or signature over either a given key-value pair or a file stored on the device, e.g. in the Keychain, `UserDefaults`/`NSUserDefaults`, or any database. +Apps might choose to ensure the integrity of the application storage itself, by creating an HMAC or signature over either a given key-value pair or a file stored on the device, e.g. in the Keychain, `UserDefaults`/`NSUserDefaults`, or any database. -For example, an app might do the following to generate an HMAC with `CommonCrypto`: +For example, an app might contain the following code to generate an HMAC with `CommonCrypto`: ```objectivec // Allocate a buffer to hold the digest and perform the digest. @@ -316,11 +316,12 @@ For example, an app might do the following to generate an HMAC with `CommonCrypt [actualData appendData: digestBuffer]; ``` -1. Gets the data as `NSMutableData`. -2. Gets the data key (typically from the Keychain). -3. Calculates the hash value. -4. Appends the hash value to the actual data. -5. Stores the results of step 4. +This script performs the following steps: +1. Get the data as `NSMutableData`. +2. Get the data key (typically from the Keychain). +3. Calculate the hash value. +4. Append the hash value to the actual data. +5. Store the results of step 4. After that, it might be verifying the HMACs by doing the following: @@ -336,7 +337,7 @@ After that, it might be verifying the HMACs by doing the following: 2. Repeats steps 1-3 of the procedure for generating an HMAC on the `NSData`. 3. Compares the extracted HMAC bytes to the result of step 1. -Note: if the app also encrypts files, make sure that it encrypts and then calcualtes the HMAC as described in [Authenticated Encryption](https://web.archive.org/web/20210804035343/https://cseweb.ucsd.edu/~mihir/papers/oem.html "Authenticated Encryption: Relations among notions and analysis of the generic composition paradigm"). +Note: if the app also encrypts files, make sure that it encrypts and then calculates the HMAC as described in [Authenticated Encryption](https://web.archive.org/web/20210804035343/https://cseweb.ucsd.edu/~mihir/papers/oem.html "Authenticated Encryption: Relations among notions and analysis of the generic composition paradigm"). **Bypass:** @@ -469,7 +470,7 @@ Learn more about iOS obfuscation techniques [here](https://faculty.ist.psu.edu/w 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](https://developer.apple.com/library/content/releasenotes/General/RN-iOSSDK-7.0/index.html "iOS 7 release notes"), hardware identifiers (such as MAC addresses) are off-limits but there are othre methods for implementing device binding in iOS: +[Since iOS 7.0](https://developer.apple.com/library/content/releasenotes/General/RN-iOSSDK-7.0/index.html "iOS 7 release notes"), hardware identifiers (such as MAC addresses) are off-limits but there are other methods for implementing device binding in iOS: - **`identifierForVendor`**: You can use `[[UIDevice currentDevice] identifierForVendor]` (in Objective-C), `UIDevice.current.identifierForVendor?.uuidString` (in Swift3), or `UIDevice.currentDevice().identifierForVendor?.UUIDString` (in Swift2). The value of `identifierForVendor` may not be the same if you reinstall the app after other apps from the same vendor are installed and it may change when you update your app bundle's name. Therefore it is best to combine it with something in the Keychain. - **Using the Keychain**: You can store something in the Keychain to identify the application's instance. To make sure that this data is not backed up, use `kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly` (if you want to secure the data and properly enforce a passcode or Touch ID requirement), `kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly`, or `kSecAttrAccessibleWhenUnlockedThisDeviceOnly`. @@ -547,7 +548,7 @@ Next, work on bypassing the defenses and answer the following questions: - Can the mechanisms be bypassed trivially (e.g., by changing the contents of a file or a key-value pair)? - How difficult is obtaining the HMAC key or the asymmetric private key? - Did you need to write custom code to disable the defenses? How much time did you need? -- What is your assessment of the difficulty of bypassing the mechanisms?? +- What is your assessment of the difficulty of bypassing the mechanisms? ## Testing Reverse Engineering Tools Detection (MSTG-RESILIENCE-4) From 473601a22ac01b355e1e729655da378989944a22 Mon Sep 17 00:00:00 2001 From: Carlos Holguera Date: Sat, 26 Nov 2022 12:32:00 +0100 Subject: [PATCH 8/9] Fix .md lint --- Document/0x06j-Testing-Resiliency-Against-Reverse-Engineering.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Document/0x06j-Testing-Resiliency-Against-Reverse-Engineering.md b/Document/0x06j-Testing-Resiliency-Against-Reverse-Engineering.md index 132e6c5541..013d08952c 100644 --- a/Document/0x06j-Testing-Resiliency-Against-Reverse-Engineering.md +++ b/Document/0x06j-Testing-Resiliency-Against-Reverse-Engineering.md @@ -317,6 +317,7 @@ For example, an app might contain the following code to generate an HMAC with `C ``` This script performs the following steps: + 1. Get the data as `NSMutableData`. 2. Get the data key (typically from the Keychain). 3. Calculate the hash value. From 2182c1ceb60047cbdc8edd03a60ec67c69e691dd Mon Sep 17 00:00:00 2001 From: Carlos Holguera Date: Sat, 26 Nov 2022 12:32:41 +0100 Subject: [PATCH 9/9] Fix link Co-authored-by: Jeroen Beckers --- Document/0x08a-Testing-Tools.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document/0x08a-Testing-Tools.md b/Document/0x08a-Testing-Tools.md index c127a0d852..b059a4f1f1 100644 --- a/Document/0x08a-Testing-Tools.md +++ b/Document/0x08a-Testing-Tools.md @@ -1707,7 +1707,7 @@ swift-demangle is an Xcode tool that demangles Swift symbols. For more informati A sample Swift project is used to demonstrate the usage of SwiftShield. -- Check out . +- Check out [sushi2k/SwiftSecurity](https://github.com/sushi2k/SwiftSecurity). - Open the project in Xcode and make sure that the project is building successfully (Product / Build or Apple-Key + B). - [Download](https://github.com/rockbruno/swiftshield/releases "SwiftShield Download") the latest release of SwiftShield and unzip it. - Go to the directory where you downloaded SwiftShield and copy the swiftshield executable to `/usr/local/bin`: