Introduction

This blog post provides a walktrough on dynamically bypassing anti-debugging and anti-reversing defences in iOS applications. Furthermore, this blog post is using resources from OWASP MASTG and provides a comprehensive guide that discusses about mobile applications security testing and reverse engineering.

For the purpose of this blog post the ios-challenge-2 application is used to showcase the identification of the anti-debugging and anti-reversing techniques and also to present specific ways to bypass these security measures.

All the exercises in this blog post are accomplished using the radare2 tool in order to perform static analysis as well as the r2frida and r2ghidra plugins that used to perform dynamic analysis and runtime hooking on the target appication.

Further details about defense-in-depth measures such as code obfuscation, anti-debugging, anti-tampering, etc. can also be found at OWASP MASVS-RESILIENCE article.

Moreover, this blog post focuses on methods to bypass the following anti-debugging and anti-reversing techniques presented on the latest article Testing Resiliency Against Reverse Engineering provided by OWASP Mobile Application Security Testing Guide (MASTG)

    • ptrace
    • sysctl
    • getppid
    • dynamic RE protections
    • Jailbreak detection

After installing the application on the iOS device, we run frida in order to identify the name of the installed application.

At this point we run the ios-challenge-2 application in order to have an overview of the application’s behaviour.

r_con

As seen at the image above, after installing and running the application it exits immediately. Using r2frida, the following command spawns the application, and after it exits, the detach reason eventually appears on the output. The detach reason indicates that the application performs anti-debugging checks that prevent it from running.

Bypassing ptrace syscall anti-debugging defence

This section discusses about the ptrace syscall that is used as a defence in order to prevent iOS mobile applications from entering into a debugging state. Furthermore, it showcsases a way to bypass such security measure.

The ptrace syscall can be found in several *nix operating systems. It is generally used for debugging breakpoints and tracing system calls. It is used from native debuggers to keep track. Also, this blog post covers only one feature of the ptrace syscall, the 'PT_DENY_ATTACH'.

PT_DENY_ATTACH: This request is the other operation used by the traced process; it allows a process that is not currently being traced to deny future traces by its parent. All other arguments are ignored. If the process is currently being traced, it will exit with the exit status of ENOTSUP; otherwise, it sets a flag that denies future traces. An attempt by the parent to trace a process which has set this flag will result in a segmentation violation in the parent.

For further reading about ptrace check out this link

Now that we already runnning r2frida, lets spawn the application again but this time using the :dtf command which used to trace the address of the ptrace syscall.

As we see above, the application terminated again and from the args value (31) we are able to determine that the feature of the ptrace syscall is the 'PT_DENY_ATTACH'.

According with OWASP-MASTG and iOS Anti-Reversing Defenses, the ptrace syscall is not part of the public iOS API. Non-public APIs are prohibited, and the App Store may reject apps that include them. Because of this, ptrace is not directly called in the code; it’s called when a ptrace function pointer is obtained via dlsym. The following code snippet represents the above logic.

Now that we know that the application immediately exits after running it with r2frida, we are in position to further check about anti-debugging / anti-reversing tecniques using static analysis. For this reason, we run the application using radare2 in order to perform static analysis to the revrersed code.

As seen previously, the ptrace syscall is generally invoked via dlsym so we search for it as follows.

At this point we continue using radare2 in order to visualize the execution flow and to examine some assembly instructions in order to have insights of the validation checks in a lower level.

As we see at the screenshot below we have obtained a lot of information regarding the ptrace implementation. Specifically we see that the ptrace is called by Challenge1.viewDidLoad and also we are able to determine the feature of the ptrace from the 0xf1 hex value which is 31 in decimal indicating the 'PT_DENY_ATTACH' feature.

dlsym-ptrace

At this point we are able to examine the viewDidLoad method as we know it implements the ptrace syscall.

We can see that the viewDidLoad method is located at 0x100008a4c address as seen above, so lets further check the validations on radare2

viewDidLoad-1

If we type ob which refers to sym.func.100008864 at radare2 we can then step inside the viewDidLoad low level assembly code block and from there we can use the r2ghidra plugin to decompile the code and have a higher level view of the viewDidLoad implementation.

From the decompiled code above, the first check is implemented using the ptrace ( sym.func.100008864 ) syscall. At this point we can bypass ptrace syscall using r2frida

As we saw earlier the argument passed to ptrace is 0xf1 in hex which indicates the ptrace feature. In order to disable ptrace syscall we can change this value to a non existing identifier, for example passing the value -1. The following code snippet can be used to dynamically manipulate the argument passed to ptrace

The following output indicates that the ptrace syscall has been disabled

Unfortunately if we run the application again it will exit immediately showing the same detach reason as before indicating that other defences might be enabled that should also be bypassed.

Bypassing getppid() anti-debugging defence

This section discusses about the getppid() method that is commonly used as a defence technique in order to prevent iOS mobile applications from entering into a debugging state. In detail by checking the parent PID using getppid(), iOS applications can detect if they have been started by a debugger. In circumnstances where the iOS application has been started by a debugger, the getppid() returns a PID different than 1. If the PID=1 indicates that the application has been started from launchd process which is the first process running in user mode. Furthermore, this blog post will also showcsase a way to bypass such security measure. For further reading about getppid() refer to iOS Manual Pages

At this point we are in position to perform further analysis in order to check for getppid() and then try to bypass it. As seen in the previous section we have accomplished to bypass the ptrace defence and now we should be able to continue with the second defence which is getppid().

If we look closely at the program flow at radare2 we see that after the sym.func.100008864 which indicates the call of the ptrace function, the sym.func.100008a30 is also called that indicates the call of the getppid() method

viewDidLoad-2

We can also see this in the following high level view of the decompiled code snippet using r2ghidra.

If we type oc which refers to sym.func.100008a30 at radare2 we can then step inside the low level assembly code block of viewDidLoad as seen at the screenshot below.

                       8a30

From there we can use the r2ghidra plugin to decompile the code and have a higher level view of the viewDidLoad implementation

As seen from the decompiled function above it evaluates the expression iVar1 != 1. If the iVar1 is 1 it indicates that the the application has started from the launchd process. On the contrary, if the iVar1 is not 1 the function returns true which indicates the presence of a debugger.

In order to bypass getppid() defence we should intercept the getppid() module and focre the return value to be 0x01 which indicates that the application has been launched using the launchd process.

The following .js code snippet can be used in r2frida to dynamically manipulate the getppid() return value.

At this point we add the above snippet to a file named bypasses.js along with the previous snippet that bypasses the ptrace syscall technique as seen blow.

If we run the application again using r2frida we see that the application exits again but the :dtf command shows that the return value of the getppid() is 0x1 which indicates the successful bypass.

Nevertheless, the sudden application termination depicts that there are still defences that need to be bypassed.

ppid_bypass

Bypassing sysctl syscall anti-debugging defence

This section discusses about a technique that is commonly used to detect the presence of a debugger. It should be noted that unlike the ptrace technique, this method doesn’t prevent a debugger from attaching to a process. Instead, it uses the sysctl function to retrieve information about the process and determine whether it is being debugged. Further information about the sysctl can be found in the sysctl man page. As for the sysctl bypass technique, further information can be found in iOS Anti-Debugging Protections #2

At this point we are in position to perform further analysis in order to check for the sysctl syscall and then try to bypass it. As seen in the previous section we have accomplished to bypass the ptrace and the getppid() defences and now we should be able to continue with the third defence which is the sysctl syscall.

If we look closely at the program flow at radare2 we see that after the sym.func.100008864 which indicates the call of the ptrace syscall, the sym.func.100008a30 is also called that indicates the call of the getppid() method, and after these validations, we see that the sym.func.10000898c is called that indicates the call of the sysctl syscall

sysctl

If we now step into the sym.func.10000898c we see the call of the sysctl syscall as seen at the decompiled code snippet below.

According with Apple documentation archive, a code example is provided which checks the info.kp_proc.p_flag flag returned by the call to sysctl with the appropriate parameters. In case the sysctl syscall returns -1 it suppose to have detect that the application is being debugged, otherwise if any other value returned such as 0x0, the application is not being debugged.

In order to bypass the use of the sysctl syscall defence we should intercept the sysctl module and focre the return value to be 0x00 which indicates that the application has not been debugged.

The following .js code snippet can be used in r2frida to dynamically manipulate the return value of the sysctl syscall.

At this point we add the above snippet to the previous created file bypasses.js along with the previous snippets that bypass the ptrace syscall and the getppid techniques as seen blow.

we see again that the application terminates immediately even after these bypasses with the same detach reason as previously seen.

The immediate termination can also be seen at the following screenshot.

sysctl

Bypassing dynamic RE protections

Mobile applications can be analyzed by dynamic instrumentation tools inspecting application’s behaviour at runtime. In terms of iOS application security there are tools such as Cydia SubstrateCycript and Frida that used for such purpose. Other tools such as SSLKillSwitch2 and SSLKillSwitch might also be used in order to disable SSL/TLS certificate validation including certificate pinning.

As also seen at the disassembled code below, there is a validation refering to the function sym.func.1000088b4

88b4

If we move further and step into the sym.func.1000088b4 we see a call to the _dyld_image_count() function which is used to get the number of loaded dylibs.

88b4_dis

Moreover, we can also see that there are further checks regarding several instrumentation tools.

                  88d4

                                 strstr2

As seen from the decompiled code below, we have a clear view about the logic of the decompiled code where as also mentioned the _dyld_image_count() is first called to get the number of loaded dylibs. Then, for each index the _dyld_get_image_name()called to retrieve the name that will be verified. The verification is performed against several instrumentation tools and other tools that are used for TLS/SSL bypass as mentioned before. The application tries to detect the following list of tools

  • Substrate
  • cycript
  • SSLKillSwitch
  • SSLKillSwitch2
  • frida

For further reading about the dylibs visit the iOS Manual Pages.

One way to bypass all the checks is to use a quick and dirty technique which intercepts the _dyld_get_image_name() function and manipulates the return value of the function, pointing to a non-existent string e.g. “null”.

At this point we add the code snippet above to the bypasses.js script as seen below.

If we run the bypasses.js script all the anti-debugging and anti-reversing techniques will be defeated. Although this is quick workarround to bypass RE checks, a drawback with this technique is that it causes errors as it actually breaks the functionality of the detected tool and eventually leads to exceptions.

RE_detections

A more elegant solution is to hook to the strstr() functions and manipulate the return value in order to disable all the checks. According with the iOS Manual Pages regarding the strstr(), the following code will be used.

As seen at the code above we initially use a const array with all the names of the intrumentation tools of our interest. Then after hooking to the strstr() function we give as input the names of the instrumentation tools one by one and then if the tool is detected the return value will be replaced to 0x00 thus the check will be disabled.

The modified bypasses.js script will be as follows.

After running the above script with r2frida we see the following output

strstr_bypass

Bypassing Jailbreak detection

At this point we have disabled all the anti-debugging checks at the target application. There is one more validation that we have to bypass, and this is the jailbreak detection. Jailbreak detection is any technique implemented by developers to identify whether the application is running on a jailbroken device. It is crucial for applications to be able to detect if they are running in a Jailbroken device as the absent of this security mechanism could leave the application susceptible to security issues. Jailbreaking in iOS and Rooting in Android devices involves running a privilege escalation on the device. Also, the process of Jailbreaking gives the ability to alter or replace system applications, files, and settings, removing pre-installed applications, and running applications that require administrator-level permissions.

Moreover to ensure security of corporate data, it is recommended that jailbroken devices must not be used in organizations. Mobile Device Managment (MDM) solutions allow organizations to detect jailbroken devices in the network and also remove these devices once they are detected. After detection these devices cannot be enrolled into Moble Device Managment (MDM) and thus lose access to corporate data.

Checking at the Challenge1 class we identify two methods as seen at the snippet below

Using the above knowledge we are able to further examine the jailbreakTest1Tapped method to see the arguments it accepts

At the following screenshot we see the implementation of the jailbreakTest1Tapped method which gets the return value from isJailbroken method and raises the messages “Device is NOT Jailbroken” or “Device is Jailbroken” accordingly.

                           jailbreakTest1Tapped

If we examine the code further we see the NSFileManager which is an interface to the contents of the file system and shares methods that can be called by multiple threads. As we see at the code, there are multiple checks that have to be exemined further. We should try to search for all possible strings in the target application in order to have all the paths and URLs that we try further to bypass. For further reading about the NSFileManager and its methods, refer to this link

We shall also get a closer look to isJailbroken method to see the arguments it accepts.

Furthermore we do a more comprehensive search to find all possible implementations regarding the methods that might perform Jailbroken checks.

At a first glance, we see that at address 0x10000c140 at isJailbroken method, the URL cstr.cydia:__r2con_re.murphy.jailbreak used. Furtermore we need to search for more strings in order to find possible URLs and suspicious system paths.

Jailbreak detection works by looking for differences between a non-Jailbroken and a Jailbroken device. Some common techniques are to search for some evidences that prove the existence of a Jailbroken system including the following searches:

  • Search for Jailbreak-related applications like Cydia
  • Search for .dylib files related to popular jailbreak tools and tweaks
  • Search for .plist files related to popular jailbreak tools and tweaks
  • Search for Read/write access to directories not normally accessible
  • Search for SSH service on the device

Checking at the disassembled code below we see a reference to fileExistsAtPath which we know that it is a method shared from the NSFileManager interface. We also see the instruction addr x2, str.cstr._Applications_Cydia.app which refers to the String "/Applications/Cydia.app".

                                  fileExistsAtPath

Reversing this code portion until now, allows us to understand that application checks exist. Also this code portion lead us to understand that specific application checks exist regarding the Jailbreak detection. if the Cydia application is installed on the operating system is one method to indicate that the device is Jailbroken. We should also try to search for all possible strings in the target application in order to have all the paths and URLs that we further try to examine if they are used to identify if the deviced is Jailbroken.

Isolating the lines regarding the method Challenge1.isJailbroken we see all the possible paths used to identify if the device is Jailbroken. The following paths have been identified

  • “/Applications/Cydia.app”
  • “/bin/bash”
  • “/Library/MobileSubstrate/MobileSubstrate.dylib”
  • “/usr/sbin/sshd”
  • “/etc/apt”
  • “/private/etc/apt/sources.list.d_electra.list”

Now we are able to write a frida script to hook to the NSFileManager.fileExistsAtPath in order to manipulate the return values when checking the above list of paths.

Apart from the previous paths mentioned before if we dig deeper we see that at 0x100008dcc the NSURL class is referenced which performs further checks using the URLWithString method that creates and returns an NSURL object initialized with a provided URL string. At this point we have identified the URL string cydia:__r2con_re.murphy.jailbreak which allows us to understand that the device is Jailbroken

8dcc

The following code snippet used to bypass this check.

Adding the previous scripts used to bypass Jailbreak detection to the bypasses.js script, we are able to bypass all the checks regarding the detected anti-debugging and anti-reversing techniques

Share This

Share this post with your friends!