Skip to content

Write-up: MobileHackingLab - No Escape

Updated: at 03:40 PM

Introduction

No Escape is an iOS lab from MobileHackingLab where you need to bypass jailbreak detection in order to access restricted content in the application.


Methodology

Explore the application

When we open the app the first time, we are greeted with a message stating that we are using a jailbroken device and that the app will exit now for security reasons.

No Escape jailbroken error screen

There is nothing more to explore in the app for now; we will need to bypass these restrictions in order to see the rest of the functionality the app offers.


Review source code

We will be using Ghidra to reverse engineer the binary and search for the jailbreak detection.

In Ghidra, we navigate to the Symbol Tree and search for the word “Jail” to see if there are any results.

You will notice that it found a few results, the bottom isJailbroken is the one that we want to check.

No Escape Ghidra symbol tree

/* No_Escape.isJailbroken() -> Swift.Bool */

bool No_Escape::isJailbroken(void)

{
  bool bVar1;
  uint uVar2;
  uint local_18;
  uint local_14;

  uVar2 = $$No_Escape.(checkForJailbreakFiles_in__BCE8F13474E5A52C60853EA803F80A81)()_->_Swift.Bool
                    ();
  if ((uVar2 & 1) == 0) {
    local_14 = $$No_Escape.(checkForWritableSystemDirectories_in__BCE8F13474E5A52C60853EA803F80A81)( )_->_Swift.Bool
                         ();
  }
  else {
    local_14 = 1;
  }
  if ((local_14 & 1) == 0) {
    local_18 = $$No_Escape.(canOpenCydia_in__BCE8F13474E5A52C60853EA803F80A81)()_->_Swift.Bool();
  }
  else {
    local_18 = 1;
  }
  if ((local_18 & 1) == 0) {
    bVar1 = $$No_Escape.(checkSandboxViolation_in__BCE8F13474E5A52C60853EA803F80A81)()_->_Swift.Bool
                      ();
  }
  else {
    bVar1 = true;
  }
  return bVar1 != false;
}

As we can see, this function performs various tests to determine if a device is jailbroken:

Based on these checks, it will return a final result, as a boolean, to indicate whether the device is jailbroken or not.

From here we have two options to bypass the hooking:

  1. We can hook and bypass each of the individual checks and return false for each one
  2. We can ignore all the other checks and only hook the final result and change its value before it returns it to the sender

For this specific case, we are going with the latter solution.


Create bypass script

Since Ghidra demangles the Swift function names we need to use Frida to find the mangled name of the function that we want to hook:

[iPhone::com.mobilehackinglab.No-Escape ]-> Module.enumerateExportsSync("No Escape").filter((module) => module.name.includes("Jail"));
[
    {
        "address": "0x10058e068",
        "name": "$s9No_Escape12isJailbrokenSbyF",
        "type": "function"
    }
]
[iPhone::com.mobilehackinglab.No-Escape ]->

Now that we have the function name we can write a hook to intercept it, modify the return value, and then let it continue:

var isJailBroken = Module.findExportByName(
  "No Escape",
  "$s9No_Escape12isJailbrokenSbyF"
);

if (isJailBroken) {
  console.log("Hooking success!");
  Interceptor.attach(isJailBroken, {
    onEnter: function (args) {
      console.log("Entered isJailBroken!");
    },
    onLeave: function (retval) {
      // Modify the return value to always return 0x0 (false)
      retval.replace(0x0);
      console.log("Exited isJailBroken, retval:" + retval);
    },
  });
} else {
  console.log("Hooking failed!");
}

Our hook modifies the return value of the function to always return 0x0 (false)


Execute bypass script

Now we launch the app using Frida and include our hooking script and observe the results:

frida -U -f "com.mobilehackinglab.No-Escape" -l no-escape-bypass-jailbreak.js
Spawning `com.mobilehackinglab.No-Escape`...
Hooking success!
Spawned `com.mobilehackinglab.No-Escape`. Resuming main thread!
[iPhone::com.mobilehackinglab.No-Escape ]-> Entered isJailBroken!
Exited isJailBroken, retval:0x0
Entered isJailBroken!
Exited isJailBroken, retval:0x0
No Escape jailbreak bypass

We have successfully bypassed the jailbreak detection and can see the flag in the application!


Final thoughts

In this lab we saw multiple checks that work great in combination with each other to detect jailbroken devices.

The only problem is that it lacked some checks to prevent hooking and thus was bypassed altogether.

Security checks are only as strong as your weakest check.