Detect if a device is Jailbreak/ Jailbroken in Swift (iOS) programmatically

Sachin Sabat
5 min readAug 1, 2020

--

Detect Jailbreak device in iOS/ Swift | Avoid Attackers to intrude in your application by all means possible in a single page| Supported to Swift (world first Protocol Oriented Language 🤘)

There are many ways to bypass the JailBreak implementation namely Liberty- Lite, A- Bypass, KernByPass (kernel level root change using MTerminal) from the Cydia Directory

But, we will detect it by all means possible and successfully pass our security check list for our application.

Github Link:- https://github.com/SachinSabat/CheckJailBreakDevice

Why JailBreak a device?

— Attackers JailBreak a device to get unrestricted access (root privileges)of the application, making it accessible to source code, and crucial files of the application. All the application downloaded from the app store is downloaded in /var/mobile/Applications/directory.

So in JailBreak Device there is always a Cydia app Installed.

We will Identify a JailBreak device in 5 ways -

  1. We will check if we can open Cydia App:-
func isCydiaAppInstalled() -> Bool {
return UIApplication.shared.canOpenURL(URL(string: "cydia://")!)
}

Add this in your plist file-

<key>LSApplicationQueriesSchemes</key><array><string>cydia</string></array>

Call this function from didFinishLaunchingWithOptions and applicationDidBecomeActive, This works 90% for every application which detects Cydia app in rooted device, If in case the root directory is being changed then we can go by 2nd option for JailBreak detection.

2. Detecting File from the System:-

Many files are created when we JailBreak a device. Detecting it in the file system method will let one know if the device is JailBroken or not. I have list down important files to check in the file system.

               ["/private/var/lib/apt",
"/Applications/Cydia.app",
"/private/var/lib/cydia",
"/private/var/tmp/cydia.log",
"/Applications/RockApp.app",
"/Applications/Icy.app",
"/Applications/WinterBoard.app",
"/Applications/SBSetttings.app",
"/Applications/blackra1n.app",
"/Applications/IntelliScreen.app",
"/Applications/Snoop-itConfig.app",
"/usr/libexec/cydia/",
"/usr/sbin/frida-server",
"/usr/bin/cycript",
"/usr/local/bin/cycript",
"/usr/lib/libcycript.dylib",
"/bin/sh",
"/usr/libexec/sftp-server",
"/usr/libexec/ssh-keysign",
"/Library/MobileSubstrate/MobileSubstrate.dylib",
"/bin/bash",
"/usr/sbin/sshd",
"/etc/apt",
"/usr/bin/ssh",
"/bin.sh",
"/var/checkra1n.dmg",
"/System/Library/LaunchDaemons/com.saurik.Cydia.Startup.plist",
"/System/Library/LaunchDaemons/com.ikey.bbot.plist",
"/Library/MobileSubstrate/DynamicLibraries/LiveClock.plist",
"/Library/MobileSubstrate/DynamicLibraries/Veency.plist",
"/etc/apt/sources.list.d/electra.list",
"/etc/apt/sources.list.d/sileo.sources",
"/.bootstrapped_electra",
"/usr/lib/libjailbreak.dylib",
"/jb/lzma",
"/.cydia_no_stash",
"/.installed_unc0ver",
"/jb/offsets.plist",
"/usr/share/jailbreak/injectme.plist",
"/etc/apt/undecimus/undecimus.list",
"/var/lib/dpkg/info/mobilesubstrate.md5sums",
"/jb/jailbreakd.plist",
"/jb/amfid_payload.dylib",
"/jb/libjailbreak.dylib",
"/usr/libexec/cydia/firmware.sh",
"/var/lib/cydia",
"/private/var/Users/",
"/var/log/apt",
"/private/var/stash",
"/private/var/cache/apt/",
"/private/var/log/syslog",
"/Applications/FakeCarrier.app",
"/Applications/MxTube.app",
"/Applications/SBSettings.app",
"/private/var/mobile/Library/SBSettings/Themes",
"/Library/MobileSubstrate/CydiaSubstrate.dylib"]

Create a function that checks all those files in the root directory. Like shown below;

func isJailBrokenFilesPresentInTheDirectory() -> Bool {
let fm = FileManager.default
if(fm.fileExists(atPath: "/private/var/lib/apt")) || (fm.fileExists(atPath: "/Applications/Cydia.app"))
{
// This Device is jailbroken
return true
} else {
// Continue the device is not jailbroken
return false
}
}

Other method is to try to write into system files.

3. Writing into System Files:-

All application are being given the root privilege and can modify the files outside the sandbox. If the app can write to files outside its sandbox then the app is being run on a JailBreak device.


static func canEditSandboxFilesForJailBreakDetecttion() -> Bool {
let jailBreakTestText = "Test for JailBreak"
do {
try jailBreakTestText.write(toFile:"/private/jailBreakTestText.txt", atomically:true, encoding:String.Encoding.utf8)
return true
} catch {
return false
}
}

GitHub Link:- https://github.com/SachinSabat/CheckJailBreakDevice

4. Detecting suspicious library and Frida Environment variables:-

To check suspicious library-

 private func isSuspiciousLibraryLoaded() -> Bool {
// Array of known suspicious libraries or patterns to check for
let suspiciousLibraryPatterns = [
"frida", // Example: Frida related libraries
"libinjector" // Example: Other common library names
]

// It returns the total number of dynamic libraries (images) currently loaded into the process. This count is used to iterate through the libraries.
let libraryCount = _dyld_image_count()
// This loop iterates over the range from 0 to libraryCount - 1, accessing each loaded library by its index.
for index in 0..<libraryCount {
// It retrieves the name of the library at the specified index. Since this function returns an optional UnsafePointer<CChar>?, you use guard let to safely unwrap the optional. If the name cannot be retrieved (i.e., imageName is nil), the loop continues to the next index.
guard let imageName = _dyld_get_image_name(index) else {
continue
}
// Convert the C string to a Swift string
let libraryName = String(cString: imageName)

// This nested loop iterates over each pattern in suspiciousLibraryPatterns. It converts both libraryName and pattern to lowercase to perform a case-insensitive comparison.
for pattern in suspiciousLibraryPatterns {
if libraryName.lowercased().contains(pattern.lowercased()) {
return true
}
}
}

return false
}

It aims to detect if any dynamically loaded libraries contain names matching known suspicious patterns. Needs adjustment to properly retrieve and check library names.

Steps:

  1. Define suspicious patterns.
  2. Get the count of loaded libraries.
  3. Iterate through each library, obtain its name, and check against patterns.
  4. Return true if a match is found, otherwise return false.

To check Frida environment variables-

    private func isFridaEnvironmentVariablePresent() -> Bool {
// This array contains names of environment variables that are commonly set by Frida or related tools.
let environmentVariables = ["FRIDA", "FRIDA_SERVER"]
// Fetches the current environment variables of the process.
let environment = ProcessInfo.processInfo.environment
// Iterates over the list of environment variables you are interested in.
for variable in environmentVariables {
// Checks if the environment variable is set (i.e., not nil).
if environment[variable] != nil {
return true
}
}
return false
}

Checks if specific environment variables, which might indicate the presence of Frida, are set in the current process.

Steps:

  1. Define a list of environment variable names to check.
  2. Fetch the current environment variables of the process.
  3. Iterate through the list of suspicious environment variables.
  4. Return true if any of these variables are found, otherwise return false.

GitHub Link:- https://github.com/SachinSabat/CheckJailBreakDevice

5. Calling System API functions:-

In a sandbox environment which has root privilege does not allow call to system API. We can make a call to those system API and recognise if we are running our app in JailBroken device.

  • Fork() API call -
let pid = fork()
if(!pid)
{
return true
}
else if(pid >= 0)
{
return false
}
  • System() API call -
let system= system()
if(system == 1)
{
return true
}
else if(system == 0)
{
return false
}

This both fucntion call will make our job done to check for JailBreak device. Calling _dyld_image_name function takes a lot of time to execute and give us the result, which won’t be suitable in a application to call and execute for JailBreak detection.

In all above, function calls do check for if(TARGET_IPHONE_SIMULATOR) || arch(i386) || arch(x86_64) and return false accordingly. All three are checks to identify if the app is being run on simulator.

Call the JailBreak function from didFinishLaunchingWithOptions and applicationDidBecomeActive. Also, we can call from the viewDidLoad() function of important ViewController page.

Exit the application if found on JailBroken device by two ways:-

// Exit the app (Recommended first option)
UIControl().sendAction(#selector(URLSessionTask.suspend), to: UIApplication.shared, for: nil)
---------------------- OR ------------------------
exit(-1)

--

--