0% found this document useful (0 votes)
82 views15 pages

Jailbreak iOS

jailbreak iOS
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
82 views15 pages

Jailbreak iOS

jailbreak iOS
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 15

//

// JailbreakChecker.swift
// IOSSecuritySuite
//
// Created by wregula on 23/04/2019.
// Copyright © 2019 wregula. All rights reserved.
//
// swiftlint:disable cyclomatic_complexity function_body_length
type_body_length trailing_whitespace

import Foundation
import UIKit
import Darwin // fork
import MachO // dyld
import ObjectiveC // NSObject and Selector

internal class JailbreakChecker {


typealias CheckResult = (passed: Bool, failMessage: String)

struct JailbreakStatus {
let passed: Bool
let failMessage: String // Added for backwards compatibility
let failedChecks: [FailedCheckType]
}

static func amIJailbroken() -> Bool {


return !performChecks().passed
}

static func amIJailbrokenWithFailMessage() -> (jailbroken: Bool,


failMessage: String) {
let status = performChecks()
return (!status.passed, status.failMessage)
}

static func amIJailbrokenWithFailedChecks() -> (jailbroken: Bool,


failedChecks: [FailedCheckType]) {
let status = performChecks()
return (!status.passed, status.failedChecks)
}

private static func performChecks() -> JailbreakStatus {


var passed = true
var failMessage = ""
var result: CheckResult = (true, "")
var failedChecks: [FailedCheckType] = []

for check in FailedCheck.allCases {


switch check {
case .urlSchemes:
result = checkURLSchemes()
case .existenceOfSuspiciousFiles:
result = checkExistenceOfSuspiciousFiles()
case .suspiciousFilesCanBeOpened:
result = checkSuspiciousFilesCanBeOpened()
case .restrictedDirectoriesWriteable:
result = checkRestrictedDirectoriesWriteable()
case .fork:
if !EmulatorChecker.amIRunInEmulator() {
result = checkFork()
} else {
print("App run in the emulator, skipping the fork check.")
result = (true, "")
}
case .symbolicLinks:
result = checkSymbolicLinks()
case .dyld:
result = checkDYLD()
case .suspiciousObjCClasses:
result = checkSuspiciousObjCClasses()
default:
continue
}

passed = passed && result.passed

if !result.passed {
failedChecks.append((check: check, failMessage:
result.failMessage))

if !failMessage.isEmpty {
failMessage += ", "
}
}

failMessage += result.failMessage
}

return JailbreakStatus(passed: passed, failMessage: failMessage,


failedChecks: failedChecks)
}

private static func canOpenUrlFromList(urlSchemes: [String]) ->


CheckResult {
for urlScheme in urlSchemes {
if let url = URL(string: urlScheme) {
if UIApplication.shared.canOpenURL(url) {
return(false, "\(urlScheme) URL scheme detected")
}
}
}
return (true, "")
}

// "cydia://" URL scheme has been removed. Turns out there is app in
the official App Store
// that has the cydia:// URL scheme registered, so it may cause false
positive
private static func checkURLSchemes() -> CheckResult {
let urlSchemes = [
"undecimus://",
"sileo://",
"zbra://",
"filza://",
"activator://"
]
return canOpenUrlFromList(urlSchemes: urlSchemes)
}

private static func checkExistenceOfSuspiciousFiles() -> CheckResult


{
var paths = [
"/var/mobile/Library/Preferences/ABPattern", // A-Bypass
"/usr/lib/ABDYLD.dylib", // A-Bypass,
"/usr/lib/ABSubLoader.dylib", // A-Bypass
"/usr/sbin/frida-server", // frida
"/etc/apt/sources.list.d/electra.list", // electra
"/etc/apt/sources.list.d/sileo.sources", // electra
"/.bootstrapped_electra", // electra
"/usr/lib/libjailbreak.dylib", // electra
"/jb/lzma", // electra
"/.cydia_no_stash", // unc0ver
"/.installed_unc0ver", // unc0ver
"/jb/offsets.plist", // unc0ver
"/usr/share/jailbreak/injectme.plist", // unc0ver
"/etc/apt/undecimus/undecimus.list", // unc0ver
"/var/lib/dpkg/info/mobilesubstrate.md5sums", // unc0ver
"/Library/MobileSubstrate/MobileSubstrate.dylib",
"/jb/jailbreakd.plist", // unc0ver
"/jb/amfid_payload.dylib", // unc0ver
"/jb/libjailbreak.dylib", // unc0ver
"/usr/libexec/cydia/firmware.sh",
"/var/lib/cydia",
"/etc/apt",
"/private/var/lib/apt",
"/private/var/Users/",
"/var/log/apt",
"/Applications/Cydia.app",
"/private/var/stash",
"/private/var/lib/apt/",
"/private/var/lib/cydia",
"/private/var/cache/apt/",
"/private/var/log/syslog",
"/private/var/tmp/cydia.log",
"/Applications/Icy.app",
"/Applications/MxTube.app",
"/Applications/RockApp.app",
"/Applications/blackra1n.app",
"/Applications/SBSettings.app",
"/Applications/FakeCarrier.app",
"/Applications/WinterBoard.app",
"/Applications/IntelliScreen.app",
"/private/var/mobile/Library/SBSettings/Themes",
"/Library/MobileSubstrate/CydiaSubstrate.dylib",
"/System/Library/LaunchDaemons/com.ikey.bbot.plist",
"/Library/MobileSubstrate/DynamicLibraries/Veency.plist",
"/Library/MobileSubstrate/DynamicLibraries/LiveClock.plist",
"/System/Library/LaunchDaemons/com.saurik.Cydia.Startup.plist",
"/Applications/Sileo.app",
"/var/binpack",
"/Library/PreferenceBundles/LibertyPref.bundle",
"/Library/PreferenceBundles/ShadowPreferences.bundle",
"/Library/PreferenceBundles/ABypassPrefs.bundle",
"/Library/PreferenceBundles/FlyJBPrefs.bundle",
"/Library/PreferenceBundles/Cephei.bundle",
"/Library/PreferenceBundles/SubstitutePrefs.bundle",
"/Library/PreferenceBundles/libhbangprefs.bundle",
"/usr/lib/libhooker.dylib",
"/usr/lib/libsubstitute.dylib",
"/usr/lib/substrate",
"/usr/lib/TweakInject",
"/var/binpack/Applications/loader.app", // checkra1n
"/Applications/FlyJB.app", // Fly JB X
"/Applications/Zebra.app", // Zebra
"/Library/BawAppie/ABypass", // ABypass
"/Library/MobileSubstrate/DynamicLibraries/SSLKillSwitch2.plist", //
SSL Killswitch

"/Library/MobileSubstrate/DynamicLibraries/PreferenceLoader.plist", //
PreferenceLoader

"/Library/MobileSubstrate/DynamicLibraries/PreferenceLoader.dylib", //
PreferenceLoader
"/Library/MobileSubstrate/DynamicLibraries", // DynamicLibraries
directory in general
"/var/mobile/Library/Preferences/me.jjolano.shadow.plist"
]

// These files can give false positive in the emulator


if !EmulatorChecker.amIRunInEmulator() {
paths += [
"/bin/bash",
"/usr/sbin/sshd",
"/usr/libexec/ssh-keysign",
"/bin/sh",
"/etc/ssh/sshd_config",
"/usr/libexec/sftp-server",
"/usr/bin/ssh"
]
}

for path in paths {


if FileManager.default.fileExists(atPath: path) {
return (false, "Suspicious file exists: \(path)")
} else if let result =
FileChecker.checkExistenceOfSuspiciousFilesViaStat(path: path) {
return result
} else if let result =
FileChecker.checkExistenceOfSuspiciousFilesViaFOpen(path: path,
mode: .readable) {
return result
} else if let result =
FileChecker.checkExistenceOfSuspiciousFilesViaAccess(path: path,
mode: .readable) {
return result
}
}

return (true, "")


}

private static func checkSuspiciousFilesCanBeOpened() ->


CheckResult {

var paths = [
"/.installed_unc0ver",
"/.bootstrapped_electra",
"/Applications/Cydia.app",
"/Library/MobileSubstrate/MobileSubstrate.dylib",
"/etc/apt",
"/var/log/apt"
]

// These files can give false positive in the emulator


if !EmulatorChecker.amIRunInEmulator() {
paths += [
"/bin/bash",
"/usr/sbin/sshd",
"/usr/bin/ssh"
]
}

for path in paths {

if FileManager.default.isReadableFile(atPath: path) {
return (false, "Suspicious file can be opened: \(path)")
} else if let result =
FileChecker.checkExistenceOfSuspiciousFilesViaFOpen(path: path,
mode: .writable) {
return result
} else if let result =
FileChecker.checkExistenceOfSuspiciousFilesViaAccess(path: path,
mode: .writable) {
return result
}
}

return (true, "")


}

private static func checkRestrictedDirectoriesWriteable() ->


CheckResult {

let paths = [
"/",
"/root/",
"/private/",
"/jb/"
]

if FileChecker.checkRestrictedPathIsReadonlyViaStatvfs(path: "/") ==
false {
return (false, "Restricted path '/' is not Read-Only")
} else if FileChecker.checkRestrictedPathIsReadonlyViaStatfs(path:
"/") == false {
return (false, "Restricted path '/' is not Read-Only")
} else if
FileChecker.checkRestrictedPathIsReadonlyViaGetfsstat(name: "/") ==
false {
return (false, "Restricted path '/' is not Read-Only")
}

// If library won't be able to write to any restricted directory the


return(false, ...) is never reached
// because of catch{} statement
for path in paths {
do {
let pathWithSomeRandom = path+UUID().uuidString
try "AmIJailbroken?".write(toFile: pathWithSomeRandom,
atomically: true, encoding: String.Encoding.utf8)
try FileManager.default.removeItem(atPath:
pathWithSomeRandom) // clean if succesfully written
return (false, "Wrote to restricted path: \(path)")
} catch {}
}

return (true, "")


}

private static func checkFork() -> CheckResult {

let pointerToFork = UnsafeMutableRawPointer(bitPattern: -2)


let forkPtr = dlsym(pointerToFork, "fork")
typealias ForkType = @convention(c) () -> pid_t
let fork = unsafeBitCast(forkPtr, to: ForkType.self)
let forkResult = fork()

if forkResult >= 0 {
if forkResult > 0 {
kill(forkResult, SIGTERM)
}
return (false, "Fork was able to create a new process (sandbox
violation)")
}

return (true, "")


}

private static func checkSymbolicLinks() -> CheckResult {

let paths = [
"/var/lib/undecimus/apt", // unc0ver
"/Applications",
"/Library/Ringtones",
"/Library/Wallpaper",
"/usr/arm-apple-darwin9",
"/usr/include",
"/usr/libexec",
"/usr/share"
]

for path in paths {


do {
let result = try
FileManager.default.destinationOfSymbolicLink(atPath: path)
if !result.isEmpty {
return (false, "Non standard symbolic link detected: \(path)
points to \(result)")
}
} catch {}
}

return (true, "")


}

private static func checkDYLD() -> CheckResult {

let suspiciousLibraries: Set<String> = [


"SubstrateLoader.dylib",
"SSLKillSwitch2.dylib",
"SSLKillSwitch.dylib",
"MobileSubstrate.dylib",
"TweakInject.dylib",
"CydiaSubstrate",
"cynject",
"CustomWidgetIcons",
"PreferenceLoader",
"RocketBootstrap",
"WeeLoader",
"/.file", // HideJB (2.1.1) changes full paths of the suspicious
libraries to "/.file"
"libhooker",
"SubstrateInserter",
"SubstrateBootstrap",
"ABypass",
"FlyJB",
"Substitute",
"Cephei",
"Electra",
"AppSyncUnified-FrontBoard.dylib",
"Shadow",
"FridaGadget",
"frida",
"libcycript"
]

for index in 0..<_dyld_image_count() {

let imageName = String(cString: _dyld_get_image_name(index))

// The fastest case insensitive contains check.


for library in suspiciousLibraries where
imageName.localizedCaseInsensitiveContains(library) {
return (false, "Suspicious library loaded: \(imageName)")
}
}

return (true, "")


}

private static func checkSuspiciousObjCClasses() -> CheckResult {

if let shadowRulesetClass = objc_getClass("ShadowRuleset") as?


NSObject.Type {
let selector = Selector(("internalDictionary"))
if class_getInstanceMethod(shadowRulesetClass, selector) != nil {
return (false, "Shadow anti-anti-jailbreak detector
detected :-)")
}
}
return (true, "")
Most of the libraries are already added in this list but as I can see most of the files which are
added in the list are the ones which are related to Cydia, Uncover, Electra but please add the
files for palera1n, Filza and sileo in the application part & for once please check if the files
which are added in the system files are actually checking in properly for the existence of the
system files bin/sh etc.

For the most part, jailbreak detection procedures are a lot less sophisticated that one might
imagine. While there are countless ways apps can implement checks for jailbroken devices,
they typically boil down to the following:

 Existence of directories - Rogue apps love to check your file system for paths
like /Applications/Cydia.app/ and /private/var/stash, amongst a handful of others.
Most often, these are checked using the -(BOOL)fileExistsAtPath:
(NSString*)path method in NSFileManager, but more sneaky apps like to use
lower-level C functions like fopen(), stat(), or access().
 Directory permissions - Similar to checking existence of directories, but checks
the Unix file permissions of specific files and directories on the system using
NSFileManager methods as well as C functions like statfs(). Far more directories
have write access on a jailbroken device than on one still in jail.
 Process forking - sandboxd does not deny App Store applications the ability to
use fork(), popen(), or any other C functions to create child processes on devices
out of jail. sandboxd explicitly denies process forking on devices in jail. By
checking the returned pid on fork(), a rogue app can tell if it has successfully
forked or not, at which point it can determine a device's jailbreak status.
 SSH loopback connections - Only a very small number of applications
implement this (as it is not nearly as effective as the others). Due to the very large
portion of jailbroken devices that have OpenSSH installed, some rogue apps will
attempt to make a connection to 127.0.0.1 on port 22. If the connection succeeds,
it means OpenSSH is installed and running on the device, which obviously
indicates that it is jailbroken.
 system() - Calling the system() function with a NULL argument on a device in
jail will return 0; doing the same on a jailbroken device will return 1. This is since
the function will check whether /bin/sh exists, and this is only the case on
jailbroken devices.[1]
 dyld functions - By far the hardest to get around. Calling functions like
_dyld_image_count() and _dyld_get_image_name() to see which dylibs are
currently loaded. Very difficult to patch, as patches are themselves part of dylibs.

Please refer to the articles also for properly implementing the checks.

False Sense of Security: A Study on the Effectivity of Jailbreak Detection in Banking Apps (tu-bs.de)

[Discussion] Jailbreak Detection: Defragging the methodology : r/jailbreak (reddit.com)

You might also like