|
@@ -0,0 +1,138 @@
|
|
|
+//
|
|
|
+// CloneCheck.swift
|
|
|
+// Pods
|
|
|
+//
|
|
|
+// Created by Maronakins on 25/08/25.
|
|
|
+//
|
|
|
+
|
|
|
+import UIKit
|
|
|
+import Darwin
|
|
|
+
|
|
|
+final class CloneCheck {
|
|
|
+
|
|
|
+ // MARK: - Expected values (replace with your own)
|
|
|
+ private static let expectedBundleId = "com.yourcompany.yourapp"
|
|
|
+ private static let expectedTeamId = "ABCDE12345" // <-- Your real Apple Team ID
|
|
|
+
|
|
|
+ // MARK: - Public
|
|
|
+
|
|
|
+ /// Run this at startup, e.g. in AppDelegate didFinishLaunching
|
|
|
+ static func enforceAllChecks(window: inout UIWindow?) {
|
|
|
+ if isJailbroken() {
|
|
|
+ showBlockAlertAndExit(window: &window)
|
|
|
+ } else {
|
|
|
+ if !isValidOrigin() {
|
|
|
+ showWarningAlert(window: &window,
|
|
|
+ message: "This app build may not be official. Please use the version from the App Store.")
|
|
|
+ } else if !isFromAppStore() {
|
|
|
+ showWarningAlert(window: &window,
|
|
|
+ message: "This app was not installed from the App Store. For security, we recommend using the official App Store version.")
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // MARK: - Jailbreak detection
|
|
|
+
|
|
|
+ private static func isJailbroken() -> Bool {
|
|
|
+ return hasSuspiciousFiles() ||
|
|
|
+ canWriteOutsideSandbox()
|
|
|
+ }
|
|
|
+
|
|
|
+ private static func hasSuspiciousFiles() -> Bool {
|
|
|
+ let suspiciousPaths = [
|
|
|
+ "/Applications/Cydia.app",
|
|
|
+ "/Library/MobileSubstrate/MobileSubstrate.dylib",
|
|
|
+ "/bin/bash",
|
|
|
+ "/usr/sbin/sshd",
|
|
|
+ "/etc/apt"
|
|
|
+ ]
|
|
|
+
|
|
|
+ for path in suspiciousPaths {
|
|
|
+ if FileManager.default.fileExists(atPath: path) {
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ private static func canWriteOutsideSandbox() -> Bool {
|
|
|
+ let testPath = "/private/jb_test.txt"
|
|
|
+ do {
|
|
|
+ try "test".write(toFile: testPath, atomically: true, encoding: .utf8)
|
|
|
+ try FileManager.default.removeItem(atPath: testPath)
|
|
|
+ return true
|
|
|
+ } catch {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // MARK: - Build origin checks
|
|
|
+
|
|
|
+ private static func isValidOrigin() -> Bool {
|
|
|
+ return isBundleIdValid() && isTeamIdValid()
|
|
|
+ }
|
|
|
+
|
|
|
+ private static func isBundleIdValid() -> Bool {
|
|
|
+ let bundleId = Bundle.main.bundleIdentifier ?? ""
|
|
|
+ return bundleId == expectedBundleId
|
|
|
+ }
|
|
|
+
|
|
|
+ private static func isTeamIdValid() -> Bool {
|
|
|
+ guard let dict = Bundle.main.infoDictionary,
|
|
|
+ let teamId = dict["com.apple.developer.team-identifier"] as? String else {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ return teamId == expectedTeamId
|
|
|
+ }
|
|
|
+
|
|
|
+ // MARK: - App Store check
|
|
|
+
|
|
|
+ private static func isFromAppStore() -> Bool {
|
|
|
+ // App Store builds don’t have a provisioning profile
|
|
|
+ if Bundle.main.path(forResource: "embedded", ofType: "mobileprovision") != nil {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ // App Store builds have "Store" receipt
|
|
|
+ if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL?.lastPathComponent {
|
|
|
+ return appStoreReceiptURL == "receipt"
|
|
|
+ }
|
|
|
+
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ // MARK: - Alerts
|
|
|
+
|
|
|
+ private static func showBlockAlertAndExit(window: inout UIWindow?) {
|
|
|
+ let alert = UIAlertController(
|
|
|
+ title: "Security Warning",
|
|
|
+ message: "This app cannot run on jailbroken devices.",
|
|
|
+ preferredStyle: .alert
|
|
|
+ )
|
|
|
+
|
|
|
+ window = UIWindow(frame: UIScreen.main.bounds)
|
|
|
+ window?.rootViewController = UIViewController()
|
|
|
+ window?.makeKeyAndVisible()
|
|
|
+
|
|
|
+ window?.rootViewController?.present(alert, animated: true) {
|
|
|
+ DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
|
|
|
+ exit(0)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static func showWarningAlert(window: inout UIWindow?, message: String) {
|
|
|
+ let alert = UIAlertController(
|
|
|
+ title: "Notice",
|
|
|
+ message: message,
|
|
|
+ preferredStyle: .alert
|
|
|
+ )
|
|
|
+ alert.addAction(UIAlertAction(title: "OK", style: .default))
|
|
|
+
|
|
|
+ window = UIWindow(frame: UIScreen.main.bounds)
|
|
|
+ window?.rootViewController = UIViewController()
|
|
|
+ window?.makeKeyAndVisible()
|
|
|
+
|
|
|
+ window?.rootViewController?.present(alert, animated: true)
|
|
|
+ }
|
|
|
+}
|