如何检测iOS应用程序是否在越狱的手机上运行?

182
如果我希望我的应用在越狱的iPhone上表现不同,我该如何确定呢?

你将会追踪一个移动的目标,但是你可以尝试跟踪这些资源的进展,以查看你的技术效果如何:
  • https://www.theiphonewiki.com/wiki/Bypassing_Jailbreak_Detection
  • https://www.theiphonewiki.com/wiki/XCon
- Michael Bishop
18个回答

94

这要看你所说的越狱是指什么。在简单情况下,你应该能够查看是否安装了Cydia并据此判断 - 类似于:

NSString *filePath = @"/Applications/Cydia.app";
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath])
{
   // do something useful
}

对于被黑客修改过的内核,涉及到的步骤会比较复杂。


18
在你的沙盒之外寻找 任何 文件/目录,比如 /etc ,不就足够了吗? - Rhythmic Fistman
62
请注意,并非所有用户都安装了Cydia -- 这不是一个好的检查方法,你应该检查类似于/bin/bash这样所有用户都将拥有的内容。 - Grant Paul
2
apt存储它的信息在哪里?或者我可以直接调用 - conradev
2
@RazorSharp 目前几乎所有用户都有 Cydia。现在检查这个可能已经足够了。但是,如果你想要一个100%可靠的检查,你需要使用以下基于内核的检查。 - Grant Paul
1
就此而言,文件检查很容易被绕过。可以使用MobileSubstrate来挂钩fileExistsAtPath:并使其返回特定路径的NO - toasted_flakes
显示剩余5条评论

61

这是一个结合了我找到的一些答案的代码,它将为您提供更高的成功率:

BOOL isJailbroken()
{
#if !(TARGET_IPHONE_SIMULATOR)

   if ([[NSFileManager defaultManager] fileExistsAtPath:@"/Applications/Cydia.app"] ||
       [[NSFileManager defaultManager] fileExistsAtPath:@"/Library/MobileSubstrate/MobileSubstrate.dylib"] ||
       [[NSFileManager defaultManager] fileExistsAtPath:@"/bin/bash"] ||
       [[NSFileManager defaultManager] fileExistsAtPath:@"/usr/sbin/sshd"] ||
       [[NSFileManager defaultManager] fileExistsAtPath:@"/etc/apt"] ||
       [[NSFileManager defaultManager] fileExistsAtPath:@"/private/var/lib/apt/"] ||
       [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"cydia://package/com.example.package"]])  {
         return YES;
   }

   FILE *f = NULL ;
   if ((f = fopen("/bin/bash", "r")) ||
      (f = fopen("/Applications/Cydia.app", "r")) ||
      (f = fopen("/Library/MobileSubstrate/MobileSubstrate.dylib", "r")) ||
      (f = fopen("/usr/sbin/sshd", "r")) ||
      (f = fopen("/etc/apt", "r")))  {
         fclose(f);
         return YES;
   }
   fclose(f);

   NSError *error;
   NSString *stringToBeWritten = @"This is a test.";
   [stringToBeWritten writeToFile:@"/private/jailbreak.txt" atomically:YES encoding:NSUTF8StringEncoding error:&error];
   [[NSFileManager defaultManager] removeItemAtPath:@"/private/jailbreak.txt" error:nil];
   if(error == nil)
   {
      return YES;
   }

#endif

   return NO;
}

1
@Porizm,这只是我找到的一些答案的组合。其中一些对我无效,但对其他人有效...所以使用此代码,您不会冒险。如果您使用此代码,则可以99%确定。关于性能,您只能在每次运行时运行它一次并将答案保存在某个地方,您不必每次都运行它。 - Yossi
3
@yossi和porizm,苹果已经批准了包含上述代码的您的应用程序吗?请回复。 - karthikPrabhu Alagu
4
这个方法应该是一个内联的 C 函数,不是 Objective-C。Objective-C 方法很容易被发现并绕过,特别是当您将其命名为 isJailbroken 时。 - progrmr
2
@Yossi 这个包括使用太极越狱的设备吗? - Lakshay Dulani
3
@Lakshay 我不知道... 你完全可以来这里查看并添加答案 :) - Yossi
显示剩余2条评论

53
+(BOOL)isJailbroken {
    NSURL* url = [NSURL URLWithString:@"cydia://package/com.example.package"];
    return [[UIApplication sharedApplication] canOpenURL:url];
}

如何检查文件路径/Applications/Cydia.app在普通手机上是否被允许?我从未听说过苹果会因此检测并拒绝应用程序,但苹果是不可预测的。Cydia具有URL方案cydia://,可以通过UIApplication的canOpenURL:合法地进行检查。


1
这是一个很好的检查方式,而且它不会超出你的沙盒。当然,如果越狱者没有安装Cydia,它将返回NO,但我认为大多数越狱都会安装Cydia。 - Wim Haanstra
当应用程序被破解时,这个字符串能否不被更改? - NSRover
9
对于iOS9.0及以上版本,您还需要在应用程序的plist文件中添加LSApplicationQueriesSchemes键。否则,canOpenURL函数将始终返回false。 - David V
1
如果用户安装了符合cydia://协议的应用程序,例如InstantTV,则会提供错误的正面结果。 - thattyson
@thattyson 谢谢!我想确认一下为什么我会得到错误的结果。 - rickrvo

52

检查内核是否破解其实并不需要太多的步骤。

越狱会让内核签名检查始终报告签名正确,未破解的手机不能运行带有错误签名的代码。

因此,在应用程序中包含一个带有错误签名的独立可执行文件。它可以只是一个具有main()和返回值的三行程序。在编译时关闭代码签名(在“项目设置->构建”中关闭),然后使用“codesign”命令行实用程序使用不同的密钥进行签名。

让你的应用程序执行独立的可执行文件。如果你的程序无法在运行带有错误签名的独立可执行文件时获取返回值,那么它肯定受到限制。如果独立的可执行文件返回A-OK,则手机肯定已经越狱。


14
你能否通过App Store获取一个签名无效的(子)可执行文件? - fbrereto
36
也许情况已经改变了,但执行一个单独的可执行文件是否会防止你被App Store批准? - Peter Zich
4
可以有人回答前面的评论吗?这些是重要的问题。 - Petr
没有人真正会阻止你用无效的签名将 Mach-O 写入磁盘。然而,我不同意这个答案,认为检查内核是否损坏并不涉及其,也不是任何意义上的决定性检查。 - saagarjha

20
BOOL isJailbroken()
{
#if TARGET_IPHONE_SIMULATOR
    return NO;
#else
    FILE *f = fopen("/bin/bash", "r");

    if (errno == ENOENT)
    {
        // device is NOT jailbroken
        fclose(f);
        return NO;
    }
    else {
        // device IS jailbroken
        fclose(f);
        return YES;
    }
#endif
}

这是一个不错的解决方案,但是像xCon和其他类似工具很容易绕过这个检查。因此,我正在寻找更好的解决方案。 - Alexei Robsky
7
@AlexeiRobsky,没有完美的解决方案。总会有人会找到绕过你的保护措施的方法,这是事实。 - Richard J. Ross III

16

我在Swift 2.3中重新制定了@Yossi提供的解决方案。

public static func jailbroken(application: UIApplication) -> Bool {
    guard let cydiaUrlScheme = NSURL(string: "cydia://package/com.example.package") else { return isJailbroken() }
    return application.canOpenURL(cydiaUrlScheme) || isJailbroken()
}


static func isJailbroken() -> Bool {

    if isSimulator {
        return false
    }

    let fileManager = NSFileManager.defaultManager()
    if fileManager.fileExistsAtPath("/Applications/Cydia.app") ||
        fileManager.fileExistsAtPath("/Library/MobileSubstrate/MobileSubstrate.dylib") ||
        fileManager.fileExistsAtPath("/bin/bash") ||
        fileManager.fileExistsAtPath("/usr/sbin/sshd") ||
        fileManager.fileExistsAtPath("/etc/apt") ||
        fileManager.fileExistsAtPath("/usr/bin/ssh") {
        return true
    }

    if canOpen("/Applications/Cydia.app") ||
        canOpen("/Library/MobileSubstrate/MobileSubstrate.dylib") ||
        canOpen("/bin/bash") ||
        canOpen("/usr/sbin/sshd") ||
        canOpen("/etc/apt") ||
        canOpen("/usr/bin/ssh") {
        return true
    }

    let path = "/private/" + NSUUID().UUIDString
    do {
        try "anyString".writeToFile(path, atomically: true, encoding: NSUTF8StringEncoding)
        try fileManager.removeItemAtPath(path)
        return true
    } catch {
        return false
    }
}

static func canOpen(path: String) -> Bool {
    let file = fopen(path, "r")
    guard file != nil else { return false }
    fclose(file)
    return true
}

你做得很好,但如果你在那些URL上加上注释,我们会更加感激。 - Naresh
canOpen(path: ... 的意思是什么,你在哪里使用它? - Naresh
你的代码在最新版本的Xcode中无法工作,能否更新一下呢?谢谢。 - Naresh

14
您可以通过检查以下内容来检测设备是否已越狱:
  • 安装Cydia
  • 验证某些系统路径
  • 执行沙盒完整性检查
  • 执行符号链接验证
  • 验证是否在 Sandbox 外创建和写入文件
我从各种文章和书籍中创建了一个开源库。 请在 GitHub 上尝试一下

6
我所知道最复杂的方法是使用objc_copyImageNames()函数。它返回当前加载的库列表,由于大多数越狱设备上都有MobileSubstrate,并且大多数iAP破解工具都依赖它,因此至少会显示一些MobileSubstrate库。

你有MobileSubstrate/CydiaSubstrate库的样子链接吗?我没有越狱的手机可以玩耍,所以我是在“盲目”驾驶,而谷歌搜索基本上只能找到你上面的评论。 - chadbag
@chadbag 我也没有,但你可以寻找MobileSubstrate的deb文件,解压它并将其打包的(几乎)所有.dylib文件列入黑名单。 - Maxthon Chan
谢谢,我已经理解了一些代码,并可能根据您的评论添加更多内容。非常感谢! - chadbag

5
请使用以下代码适用于 Swift 4 及以上版本: 将以下代码添加到 appdelegate 中:
private func getJailbrokenStatus() -> Bool {
    if TARGET_IPHONE_SIMULATOR != 1 {
        // Check 1 : existence of files that are common for jailbroken devices
        if FileManager.default.fileExists(atPath: "/Applications/Cydia.app")
            || FileManager.default.fileExists(atPath: "/Library/MobileSubstrate/MobileSubstrate.dylib")
            || FileManager.default.fileExists(atPath: "/bin/bash")
            || FileManager.default.fileExists(atPath: "/usr/sbin/sshd")
            || FileManager.default.fileExists(atPath: "/etc/apt")
            || FileManager.default.fileExists(atPath: "/private/var/lib/apt/")
            || UIApplication.shared.canOpenURL(URL(string:"cydia://package/com.example.package")!) {
            return true
        }
        // Check 2 : Reading and writing in system directories (sandbox violation)
        let stringToWrite = "Jailbreak Test"
        do {
            try stringToWrite.write(toFile:"/private/JailbreakTest.txt", atomically:true, encoding:String.Encoding.utf8)
            //Device is jailbroken
            return true
        } catch {
            return false
        }
    }
    else {
        return false
    }
}

在Appdelegate方法中,编写以下代码:

func applicationDidBecomeActive (_ application: UIApplication) {
    
    if getJailbrokenStatus() {
        let alert = UIAlertController(title: LocalizedKeys.Errors.jailbreakError, message: LocalizedKeys.Errors.jailbreakErrorMessage, preferredStyle: UIAlertController.Style.alert)
        let jailBrokenView = UIViewController()
        
        jailBrokenView.view.frame = UIScreen.main.bounds
        jailBrokenView.view.backgroundColor = .white
        self.window?.rootViewController = jailBrokenView
        jailBrokenView.present(alert, animated: true, completion: nil)
    }
    
    if #available(iOS 11.0, *) {
        if !UIScreen.main.isCaptured {
            DispatchQueue.main.async {
                self.blockImageView.removeFromSuperview()
            }
        }
    }
}

self.blockImageView.removeFromSuperview() 出现错误,请建议应该怎么做。 - Arshad Shaik

4

尝试通过您的应用程序执行未签名的代码。

越狱设备通常具有以下特征:

  • 运行未签名的代码
  • 已安装Cydia
  • 已安装越狱文件
  • 完全访问整个文件系统的读写权限
  • 一些系统文件将已被修改(内容以及sha1与原始文件不匹配)
  • 固定在特定版本上(可越狱版本)

仅检查越狱检测文件是否存在注定会失败。这些检查很容易被绕过。


4
尝试执行未签名的代码会导致您的应用被App Store拒绝。 - Frederic Yesid Peña Sánchez

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接