CloneCheck.swift 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. //
  2. // CloneCheck.swift
  3. // Pods
  4. //
  5. // Created by Maronakins on 25/08/25.
  6. //
  7. import UIKit
  8. import Darwin
  9. final class CloneCheck {
  10. // MARK: - Expected values (replace with your own)
  11. private static let expectedBundleId = "com.yourcompany.yourapp"
  12. private static let expectedTeamId = "ABCDE12345" // <-- Your real Apple Team ID
  13. // MARK: - Public
  14. /// Run this at startup, e.g. in AppDelegate didFinishLaunching
  15. static func enforceAllChecks(window: inout UIWindow?) {
  16. if isJailbroken() {
  17. showBlockAlertAndExit(window: &window)
  18. } else {
  19. if !isValidOrigin() {
  20. showWarningAlert(window: &window,
  21. message: "This app build may not be official. Please use the version from the App Store.")
  22. } else if !isFromAppStore() {
  23. showWarningAlert(window: &window,
  24. message: "This app was not installed from the App Store. For security, we recommend using the official App Store version.")
  25. }
  26. }
  27. }
  28. // MARK: - Jailbreak detection
  29. private static func isJailbroken() -> Bool {
  30. return hasSuspiciousFiles() ||
  31. canWriteOutsideSandbox()
  32. }
  33. private static func hasSuspiciousFiles() -> Bool {
  34. let suspiciousPaths = [
  35. "/Applications/Cydia.app",
  36. "/Library/MobileSubstrate/MobileSubstrate.dylib",
  37. "/bin/bash",
  38. "/usr/sbin/sshd",
  39. "/etc/apt"
  40. ]
  41. for path in suspiciousPaths {
  42. if FileManager.default.fileExists(atPath: path) {
  43. return true
  44. }
  45. }
  46. return false
  47. }
  48. private static func canWriteOutsideSandbox() -> Bool {
  49. let testPath = "/private/jb_test.txt"
  50. do {
  51. try "test".write(toFile: testPath, atomically: true, encoding: .utf8)
  52. try FileManager.default.removeItem(atPath: testPath)
  53. return true
  54. } catch {
  55. return false
  56. }
  57. }
  58. // MARK: - Build origin checks
  59. private static func isValidOrigin() -> Bool {
  60. return isBundleIdValid() && isTeamIdValid()
  61. }
  62. private static func isBundleIdValid() -> Bool {
  63. let bundleId = Bundle.main.bundleIdentifier ?? ""
  64. return bundleId == expectedBundleId
  65. }
  66. private static func isTeamIdValid() -> Bool {
  67. guard let dict = Bundle.main.infoDictionary,
  68. let teamId = dict["com.apple.developer.team-identifier"] as? String else {
  69. return false
  70. }
  71. return teamId == expectedTeamId
  72. }
  73. // MARK: - App Store check
  74. private static func isFromAppStore() -> Bool {
  75. // App Store builds don’t have a provisioning profile
  76. if Bundle.main.path(forResource: "embedded", ofType: "mobileprovision") != nil {
  77. return false
  78. }
  79. // App Store builds have "Store" receipt
  80. if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL?.lastPathComponent {
  81. return appStoreReceiptURL == "receipt"
  82. }
  83. return false
  84. }
  85. // MARK: - Alerts
  86. private static func showBlockAlertAndExit(window: inout UIWindow?) {
  87. let alert = UIAlertController(
  88. title: "Security Warning",
  89. message: "This app cannot run on jailbroken devices.",
  90. preferredStyle: .alert
  91. )
  92. window = UIWindow(frame: UIScreen.main.bounds)
  93. window?.rootViewController = UIViewController()
  94. window?.makeKeyAndVisible()
  95. window?.rootViewController?.present(alert, animated: true) {
  96. DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
  97. exit(0)
  98. }
  99. }
  100. }
  101. private static func showWarningAlert(window: inout UIWindow?, message: String) {
  102. let alert = UIAlertController(
  103. title: "Notice",
  104. message: message,
  105. preferredStyle: .alert
  106. )
  107. alert.addAction(UIAlertAction(title: "OK", style: .default))
  108. window = UIWindow(frame: UIScreen.main.bounds)
  109. window?.rootViewController = UIViewController()
  110. window?.makeKeyAndVisible()
  111. window?.rootViewController?.present(alert, animated: true)
  112. }
  113. }