"Fixing" ofo's jailbreak detection with their assistance

Rather aggressive jailbreak detection

Previously, I could not use ofo’s app due to their rather aggressive jailbreak detection.

While in Shenzhen, I could not use ofo’s bike-sharing app as they presumed my iPhone to be jailbroken. The app was not strictly wrong - my iPhone once used a non-persistent tethered jailbreak. However, the iPhone’s bootchain integrity was restored after rebooting, and only Apple-approved code were permitted to execute.

I was determined to take a stab at this issue when I returned, as other apps that implemented jailbreak lock-out functions such as WeChat still ran fine on my phone. I had a hunch that the detection code looked for files (or results of files) that shouldn’t exist in a jailed device, as jailbreaking allows mounting of the root partition / as read-write.

Dumping ofo’s binary

This should normally be a routine process, however my preferred dumping tool clutch had difficulties with ofo’s binary as it was getting killed by the iOS sandbox. To get around this, I used stefanesser’s dumpdecrypted (precompiled by AloneMonkey).

dumpdecrypted is used together with DYLD_INSERT_LIBRARIES. Be sure to run it under the mobile user, in a folder it can write to.

DYLD_INSERT_LIBRARIES=/usr/lib/dumpdecrypted.dylib /var/containers/Bundle/Application/*/AppName.app/AppName

When run correctly, a *.decrypted file should be available for further disassembly. Unlike clutch, dumpdecrypted only dumps the binary, leaving the app’s other assets untouched.

Identifying the issue

(or thinking like a developer)

DTTJailbreakDetection Example

As iPhone jailbreaking and the means to detect it was quite a specific niche, it was safe to assume that the ofo developers searched up on “ios jailbreak detection” and selected a library that looked robust enough. Indeed, the first Github result for that query had an example that matched ofo’s jailbreak view, word for word!

I loaded the binary in IDA, and found a very similar implementation of DTTJailbreakDetection’s checks. Interestingly, DTTJailbreakDetection was written in Objective-C, but ofo’s implementation appears to be rewritten in Swift.

ofo file checking

Snippet of the file-checking bits

ofo url scheme and write checking

Snippet of the url scheme check, and its attempt to write outside the sandbox

Issues with DTTJailbreakDetection (and variants)

DTTJailbreakDetection Implementation

The library implements jailbreak detection in 3 parts:

  1. Existence of jailbreak-specific files
  2. Whether the cydia:// url scheme is registered
  3. Whether it is able to write outside of the application sandbox

The checks on jailbreak-specific files are not reliable. Not all jailbreaks install Cydia, and many of the other files are part of optional packages that have to be manually installed (i.e. overlapping and redundant).

Checking the URL scheme is also a bad idea as any developer can register an application with the cydia:// handler and break other apps using this library. Apparently this is a documented issue.

#3 tries to write a test file into a normally-inaccessible folder such as /private/, as most jailbreaks inadvertently enable this action┬╣ to allow other jailbreak-related binaries to run normally. In my opinion, this is the most reliable and sensible check; it verifies the sandbox integrity at runtime, and can coexist with residual files left behind by previous jailbreaks as in my case. The ofo app simply has to remove the first 2 checks which were triggering false positives, and keep the last check.


Fixing it

ofoinsingapore tweets

The very efficient @ofoinsingapore quickly reached out and assisted to connect me with their developers when I tweeted that. 3 days later, I was already communicating with one of their developers to identify sections of their code to modify. They pushed out a new update, pending Apple’s AppStore approval, and on 07/07/18, version 2.12.0 was live with the changes implemented!

ofoinsingapore fix

From a tweet on 1 July to a fix on 7 July - that’s barely a week! I think ofo did a fantastic job here.


Closing thoughts

Implementing countermeasures against jailbroken devices is fundamentally an uphill battle as the app is doing so from within the iOS sandbox, against an adversary with unfettered root privileges.

It may be better to focus that effort on improving the product instead. As a guideline, sensitive materials should be processed on the server side and the client should not be trusted as far as practicable.

There are occasions where jailbreak detection may be helpful. For example, the device’s status can be included when generating support tickets as misbehaving jailbreak extensions sometimes cause unpredictable behaviour outside the scope of the app.

Implementing jailbreak checks to prohibit user actions will likely lead to degraded user experiences, especially when triggering false positives. It is a hindrance for legitimate users, and the checks only act as a minor roadblock for determined adversaries.

Thank you ofo (especially the team behind @ofoinsingapore, and Mr. Zhao) for working on this!


┬╣ My understanding of many popular jailbreaks is that AMFI and sandbox are completely neutered to allow any binary to run with more privileges. A possible improvement would be to preserve existing sandbox rules for binaries executing from /var/containers/Bundle/Application/ unless additional entitlements are specified, as this would satisfy most jailbreak checks.

Prior to the fix, I could only get ofo to run if I re-jailbroke and used a package “Liberty Lite” by Ryley Angus, which attempts to restore some sandbox functionality.