├── .swift-version ├── logo.png ├── iase_bg.png ├── .gitattributes ├── FrameworkClientApp ├── Assets.xcassets │ ├── Contents.json │ └── AppIcon.appiconset │ │ └── Contents.json ├── FrameworkClientApp.entitlements ├── AppDelegate.swift ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── ViewController.swift └── FishHook.swift ├── IOSSecuritySuite.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ └── IDEWorkspaceChecks.plist ├── xcshareddata │ └── xcschemes │ │ ├── IOSSecuritySuite.xcscheme │ │ └── FrameworkClientApp.xcscheme └── project.pbxproj ├── IOSSecuritySuite ├── ModesChecker.swift ├── IOSSecuritySuite.h ├── EmulatorChecker.swift ├── FailedChecks.swift ├── Info.plist ├── Resources │ └── PrivacyInfo.xcprivacy ├── ProxyChecker.swift ├── RuntimeHookChecker.swift ├── ReverseEngineeringToolsChecker.swift ├── DebuggerChecker.swift ├── FileChecker.swift ├── MSHookFunctionChecker.swift ├── IntegrityChecker.swift ├── JailbreakChecker.swift ├── IOSSecuritySuite.swift └── FishHookChecker.swift ├── Package.swift ├── IOSSecuritySuite.podspec ├── .gitignore ├── LICENSE └── README.md /.swift-version: -------------------------------------------------------------------------------- 1 | 5.0 -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/securing/IOSSecuritySuite/HEAD/logo.png -------------------------------------------------------------------------------- /iase_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/securing/IOSSecuritySuite/HEAD/iase_bg.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /FrameworkClientApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /FrameworkClientApp/FrameworkClientApp.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /IOSSecuritySuite.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /IOSSecuritySuite.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /IOSSecuritySuite.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /FrameworkClientApp/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // FrameworkClientApp 4 | // 5 | // Created by wregula on 23/04/2019. 6 | // Copyright © 2019 wregula. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | internal class AppDelegate: UIResponder, UIApplicationDelegate { 13 | var window: UIWindow? 14 | } 15 | -------------------------------------------------------------------------------- /IOSSecuritySuite/ModesChecker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ModesChecker.swift 3 | // IOSSecuritySuite 4 | // 5 | // Created by Wojciech Reguła on 28/03/2024. 6 | // Copyright © 2024 wregula. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | internal class ModesChecker { 12 | 13 | static func amIInLockdownMode() -> Bool { 14 | return UserDefaults.standard.bool(forKey: "LDMGlobalEnabled") 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "IOSSecuritySuite", 7 | platforms: [ 8 | .iOS(.v11) 9 | ], 10 | products: [ 11 | .library(name: "IOSSecuritySuite", targets: ["IOSSecuritySuite"]) 12 | ], 13 | targets: [ 14 | .target( 15 | name: "IOSSecuritySuite", 16 | path: "./IOSSecuritySuite", 17 | exclude: ["IOSSecuritySuite.h", "Info.plist"], 18 | resources: [.copy("Resources/PrivacyInfo.xcprivacy")] 19 | ) 20 | ], 21 | swiftLanguageVersions: [.v4_2, .v5] 22 | ) 23 | -------------------------------------------------------------------------------- /IOSSecuritySuite/IOSSecuritySuite.h: -------------------------------------------------------------------------------- 1 | // 2 | // IOSSecuritySuite.h 3 | // IOSSecuritySuite 4 | // 5 | // Created by wregula on 23/04/2019. 6 | // Copyright © 2019 wregula. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for IOSSecuritySuite. 12 | FOUNDATION_EXPORT double IOSSecuritySuiteVersionNumber; 13 | 14 | //! Project version string for IOSSecuritySuite. 15 | FOUNDATION_EXPORT const unsigned char IOSSecuritySuiteVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /IOSSecuritySuite/EmulatorChecker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmulatorChecker.swift 3 | // IOSSecuritySuite 4 | // 5 | // Created by wregula on 23/04/2019. 6 | // Copyright © 2019 wregula. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | internal class EmulatorChecker { 12 | static func amIRunInEmulator() -> Bool { 13 | return checkCompile() || checkRuntime() 14 | } 15 | 16 | private static func checkRuntime() -> Bool { 17 | return ProcessInfo.processInfo.environment["SIMULATOR_DEVICE_NAME"] != nil 18 | } 19 | 20 | private static func checkCompile() -> Bool { 21 | #if targetEnvironment(simulator) 22 | return true 23 | #else 24 | return false 25 | #endif 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /IOSSecuritySuite/FailedChecks.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FailedChecks.swift 3 | // IOSSecuritySuite 4 | // 5 | // Created by im on 06/02/23. 6 | // Copyright © 2023 wregula. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Tuple with check (``FailedCheck``) and failMessage (String) 12 | public typealias FailedCheckType = (check: FailedCheck, failMessage: String) 13 | 14 | /// A list of possible checks made by this library 15 | public enum FailedCheck: CaseIterable { 16 | case urlSchemes 17 | case existenceOfSuspiciousFiles 18 | case suspiciousFilesCanBeOpened 19 | case restrictedDirectoriesWriteable 20 | case fork 21 | case symbolicLinks 22 | case dyld 23 | case openedPorts 24 | case pSelectFlag 25 | case suspiciousObjCClasses 26 | } 27 | -------------------------------------------------------------------------------- /IOSSecuritySuite/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /IOSSecuritySuite.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "IOSSecuritySuite" 3 | s.version = "2.2.0" 4 | s.summary = "iOS platform security & anti-tampering Swift library" 5 | s.homepage = "https://github.com/securing/IOSSecuritySuite" 6 | s.license = "custom EULA" 7 | s.author = "Wojciech Reguła" 8 | s.social_media_url = "https://x.com/_r3ggi" 9 | s.platform = :ios, "12.0" 10 | s.ios.frameworks = 'UIKit', 'Foundation' 11 | s.source = { :git => "https://github.com/securing/IOSSecuritySuite.git", :tag => "#{s.version}" } 12 | s.source_files = "IOSSecuritySuite/*.swift" 13 | s.resource_bundles = {'IOSSecuritySuitePrivacy' => ['IOSSecuritySuite/Resources/PrivacyInfo.xcprivacy']} 14 | s.swift_version = '5.0' 15 | s.requires_arc = true 16 | s.pod_target_xcconfig = { 'SWIFT_VERSION' => '5.0' } 17 | end 18 | -------------------------------------------------------------------------------- /IOSSecuritySuite/Resources/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyTrackingDomains 6 | 7 | NSPrivacyAccessedAPITypes 8 | 9 | 10 | NSPrivacyAccessedAPIType 11 | NSPrivacyAccessedAPICategoryFileTimestamp 12 | NSPrivacyAccessedAPITypeReasons 13 | 14 | 3B52.1 15 | 16 | 17 | 18 | NSPrivacyAccessedAPIType 19 | NSPrivacyAccessedAPICategoryDiskSpace 20 | NSPrivacyAccessedAPITypeReasons 21 | 22 | E174.1 23 | 24 | 25 | 26 | NSPrivacyCollectedDataTypes 27 | 28 | NSPrivacyTracking 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /IOSSecuritySuite/ProxyChecker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProxyChecker.swift 3 | // IOSSecuritySuite 4 | // 5 | // Created by Wojciech Reguła on 07/12/2020. 6 | // Copyright © 2020 wregula. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | internal class ProxyChecker { 12 | static func amIProxied(considerVPNConnectionAsProxy: Bool = false) -> Bool { 13 | guard let unmanagedSettings = CFNetworkCopySystemProxySettings() else { 14 | return false 15 | } 16 | 17 | let settingsOptional = unmanagedSettings.takeRetainedValue() as? [String: Any] 18 | 19 | guard let settings = settingsOptional else { 20 | return false 21 | } 22 | 23 | if(considerVPNConnectionAsProxy) { 24 | if let scoped = settings["__SCOPED__"] as? [String: Any] { 25 | for interface in scoped.keys { 26 | 27 | let names = [ 28 | "tap", 29 | "tun", 30 | "ppp", 31 | "ipsec", 32 | "utun" 33 | ] 34 | 35 | for name in names { 36 | if(interface.contains(name)) { 37 | print("detected: \(interface)") 38 | return true 39 | } 40 | } 41 | } 42 | } 43 | } 44 | 45 | return (settings.keys.contains("HTTPProxy") || settings.keys.contains("HTTPSProxy")) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /FrameworkClientApp/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | .swiftlint.yml 25 | .code 26 | .DS_Store 27 | 28 | ## Obj-C/Swift specific 29 | *.hmap 30 | *.ipa 31 | *.dSYM.zip 32 | *.dSYM 33 | 34 | ## Playgrounds 35 | timeline.xctimeline 36 | playground.xcworkspace 37 | 38 | # Swift Package Manager 39 | # 40 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 41 | # Packages/ 42 | # Package.pins 43 | # Package.resolved 44 | .build/ 45 | .swiftpm/xcode 46 | 47 | # CocoaPods 48 | # 49 | # We recommend against adding the Pods directory to your .gitignore. However 50 | # you should judge for yourself, the pros and cons are mentioned at: 51 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 52 | # 53 | # Pods/ 54 | 55 | # Carthage 56 | # 57 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 58 | # Carthage/Checkouts 59 | 60 | Carthage/Build 61 | 62 | # fastlane 63 | # 64 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 65 | # screenshots whenever they are needed. 66 | # For more information about the recommended setup visit: 67 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 68 | 69 | fastlane/report.xml 70 | fastlane/Preview.html 71 | fastlane/screenshots/**/*.png 72 | fastlane/test_output 73 | -------------------------------------------------------------------------------- /FrameworkClientApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSApplicationQueriesSchemes 22 | 23 | zbra 24 | cydia 25 | undecimus 26 | sileo 27 | filza 28 | activator 29 | 30 | LSRequiresIPhoneOS 31 | 32 | UILaunchStoryboardName 33 | LaunchScreen 34 | UIMainStoryboardFile 35 | Main 36 | UIRequiredDeviceCapabilities 37 | 38 | armv7 39 | 40 | UISupportedInterfaceOrientations 41 | 42 | UIInterfaceOrientationPortrait 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | UISupportedInterfaceOrientations~ipad 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationPortraitUpsideDown 50 | UIInterfaceOrientationLandscapeLeft 51 | UIInterfaceOrientationLandscapeRight 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /IOSSecuritySuite/RuntimeHookChecker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RuntimeHookChecker.swift 3 | // IOSSecuritySuite 4 | // 5 | // Created by jintao on 2020/4/24. 6 | // Copyright © 2020 wregula. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import MachO 11 | 12 | internal class RuntimeHookChecker { 13 | static private let swiftOnceDenyFishHooK: Void = { 14 | #if arch(arm64) 15 | FishHookChecker.denyFishHook("dladdr") 16 | #endif 17 | }() 18 | 19 | static func amIRuntimeHook( 20 | dyldAllowList: [String], 21 | detectionClass: AnyClass, 22 | selector: Selector, 23 | isClassMethod: Bool 24 | ) -> Bool { 25 | var method: Method? 26 | if isClassMethod { 27 | method = class_getClassMethod(detectionClass, selector) 28 | } else { 29 | method = class_getInstanceMethod(detectionClass, selector) 30 | } 31 | 32 | guard let method = method else { 33 | // method not found 34 | return true 35 | } 36 | 37 | let imp = method_getImplementation(method) 38 | var info = Dl_info() 39 | 40 | _ = swiftOnceDenyFishHooK 41 | 42 | // dladdr will look through vm range of allImages for vm range of an Image that contains pointer 43 | // of method and return info of the Image 44 | if dladdr(UnsafeRawPointer(imp), &info) != 1 { 45 | return false 46 | } 47 | 48 | let impDyldPath = String(cString: info.dli_fname).lowercased() 49 | 50 | // at system framework 51 | if impDyldPath.contains("/System/Library".lowercased()) { 52 | return false 53 | } 54 | 55 | // at binary of app 56 | let binaryPath = String(cString: _dyld_get_image_name(0)).lowercased() 57 | if impDyldPath.contains(binaryPath) { 58 | return false 59 | } 60 | 61 | // at whiteList 62 | if let impFramework = impDyldPath.components(separatedBy: "/").last { 63 | return !dyldAllowList.map({ $0.lowercased() }).contains(impFramework) 64 | } 65 | 66 | // at injected framework 67 | return true 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /FrameworkClientApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /IOSSecuritySuite.xcodeproj/xcshareddata/xcschemes/IOSSecuritySuite.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 52 | 53 | 59 | 60 | 66 | 67 | 68 | 69 | 71 | 72 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /IOSSecuritySuite.xcodeproj/xcshareddata/xcschemes/FrameworkClientApp.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /FrameworkClientApp/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // FrameworkClientApp 4 | // 5 | // Created by wregula on 23/04/2019. 6 | // Copyright © 2019 wregula. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import IOSSecuritySuite 11 | 12 | class RuntimeClass { 13 | @objc dynamic func runtimeModifiedFunction() -> Int { 14 | return 1 15 | } 16 | } 17 | 18 | internal class ViewController: UIViewController { 19 | @IBOutlet weak var result: UITextView! 20 | 21 | override func viewDidAppear(_ animated: Bool) { 22 | var message = "" 23 | 24 | #if arch(arm64) 25 | message += executeChecksForArm64() 26 | #endif 27 | 28 | // Runtime Check 29 | let test = RuntimeClass.init() 30 | _ = test.runtimeModifiedFunction() 31 | let dylds = ["UIKit"] 32 | let amIRuntimeHooked = IOSSecuritySuite.amIRuntimeHooked( 33 | dyldAllowList: dylds, 34 | detectionClass: RuntimeClass.self, 35 | selector: #selector(RuntimeClass.runtimeModifiedFunction), 36 | isClassMethod: false 37 | ) 38 | 39 | message += """ 40 | Jailbreak? \(IOSSecuritySuite.amIJailbroken()) 41 | Jailbreak with fail msg? \(IOSSecuritySuite.amIJailbrokenWithFailMessage()) 42 | Jailbreak with failedChecks? \(IOSSecuritySuite.amIJailbrokenWithFailedChecks()) 43 | Run in emulator? \(IOSSecuritySuite.amIRunInEmulator()) 44 | Debugged? \(IOSSecuritySuite.amIDebugged()) 45 | Unexpected Launcher? \(IOSSecuritySuite.isParentPidUnexpected()) 46 | Am I tempered with? \(IOSSecuritySuite.amITampered( 47 | [.bundleID("biz.securing.FrameworkClientApp")]) 48 | ) 49 | Reversed? \(IOSSecuritySuite.amIReverseEngineered()) 50 | Reversed with failedChecks? \(IOSSecuritySuite.amIReverseEngineeredWithFailedChecks()) 51 | Am I runtime hooked? \(amIRuntimeHooked) 52 | Am I proxied? \(IOSSecuritySuite.amIProxied()) 53 | """ 54 | 55 | result.text = message 56 | } 57 | } 58 | 59 | #if arch(arm64) 60 | extension ViewController { 61 | func executeChecksForArm64() -> String { 62 | // executeAntiHook() 63 | 64 | // MSHook Check 65 | func msHookReturnFalse(takes: Int) -> Bool { 66 | return false /// add breakpoint at here to test `IOSSecuritySuite.hasBreakpointAt` 67 | } 68 | 69 | typealias FunctionType = @convention(thin) (Int) -> (Bool) 70 | func getSwiftFunctionAddr(_ function: @escaping FunctionType) -> UnsafeMutableRawPointer { 71 | return unsafeBitCast(function, to: UnsafeMutableRawPointer.self) 72 | } 73 | 74 | let funcAddr = getSwiftFunctionAddr(msHookReturnFalse) 75 | 76 | return """ 77 | Am I MSHooked? \(IOSSecuritySuite.amIMSHooked(funcAddr)) 78 | Application executable file hash value? \(IOSSecuritySuite.getMachOFileHashValue() ?? "") 79 | IOSSecuritySuite executable file hash value? \( 80 | IOSSecuritySuite.getMachOFileHashValue(.custom("IOSSecuritySuite")) ?? "" 81 | ) 82 | Loaded libs? \(IOSSecuritySuite.findLoadedDylibs() ?? []) 83 | HasBreakpoint? \(IOSSecuritySuite.hasBreakpointAt(funcAddr, functionSize: nil)) 84 | Watchpoint? \(testWatchpoint()) 85 | """ 86 | } 87 | 88 | func testWatchpoint() -> Bool { 89 | 90 | // Uncomment these \/ and set a watch point to check the feature 91 | // var ptr = malloc(9) 92 | // var count = 3 93 | return IOSSecuritySuite.hasWatchpoint() 94 | } 95 | 96 | func executeAntiHook() { 97 | typealias MyPrint = @convention(thin) (Any..., String, String) -> Void 98 | func myPrint(_ items: Any..., separator: String = " ", terminator: String = "\n") { 99 | print("print has been hooked") 100 | } 101 | 102 | let myprint: MyPrint = myPrint 103 | let myPrintPointer = unsafeBitCast(myprint, to: UnsafeMutableRawPointer.self) 104 | var oldMethod: UnsafeMutableRawPointer? 105 | 106 | // simulating hook 107 | replaceSymbol( 108 | "$ss5print_9separator10terminatoryypd_S2StF", 109 | newMethod: myPrintPointer, 110 | oldMethod: &oldMethod 111 | ) 112 | 113 | print("print hasn't been hooked") 114 | 115 | // antiHook 116 | IOSSecuritySuite.denySymbolHook("$ss5print_9separator10terminatoryypd_S2StF") 117 | print("print has been antiHooked") 118 | } 119 | } 120 | #endif 121 | -------------------------------------------------------------------------------- /FrameworkClientApp/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /IOSSecuritySuite/ReverseEngineeringToolsChecker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ReverseEngineeringToolsChecker.swift 3 | // IOSSecuritySuite 4 | // 5 | // Created by wregula on 24/04/2019. 6 | // Copyright © 2019 wregula. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import MachO // dyld 11 | 12 | internal class ReverseEngineeringToolsChecker { 13 | typealias CheckResult = (passed: Bool, failMessage: String) 14 | 15 | struct ReverseEngineeringToolsStatus { 16 | let passed: Bool 17 | let failedChecks: [FailedCheckType] 18 | } 19 | 20 | static func amIReverseEngineered() -> Bool { 21 | return !performChecks().passed 22 | } 23 | 24 | static func amIReverseEngineeredWithFailedChecks() -> (reverseEngineered: Bool, 25 | failedChecks: [FailedCheckType]) { 26 | let status = performChecks() 27 | return (!status.passed, status.failedChecks) 28 | } 29 | 30 | private static func performChecks() -> ReverseEngineeringToolsStatus { 31 | var passed = true 32 | var result: CheckResult = (true, "") 33 | var failedChecks: [FailedCheckType] = [] 34 | 35 | for check in FailedCheck.allCases { 36 | switch check { 37 | case .existenceOfSuspiciousFiles: 38 | result = checkExistenceOfSuspiciousFiles() 39 | case .dyld: 40 | result = checkDYLD() 41 | case .openedPorts: 42 | result = checkOpenedPorts() 43 | case .pSelectFlag: 44 | result = checkPSelectFlag() 45 | default: 46 | continue 47 | } 48 | 49 | passed = passed && result.passed 50 | 51 | if !result.passed { 52 | failedChecks.append((check: check, failMessage: result.failMessage)) 53 | } 54 | } 55 | 56 | return ReverseEngineeringToolsStatus(passed: passed, failedChecks: failedChecks) 57 | } 58 | 59 | private static func checkDYLD() -> CheckResult { 60 | let suspiciousLibraries: Set = [ 61 | "FridaGadget", 62 | "frida", // Needle injects frida-somerandom.dylib 63 | "cynject", 64 | "libcycript" 65 | ] 66 | 67 | for index in 0..<_dyld_image_count() { 68 | let imageName = String(cString: _dyld_get_image_name(index)) 69 | 70 | // The fastest case insensitive contains check. 71 | for library in suspiciousLibraries where imageName.localizedCaseInsensitiveContains(library) { 72 | return (false, "Suspicious library loaded: \(imageName)") 73 | } 74 | } 75 | 76 | return (true, "") 77 | } 78 | 79 | private static func checkExistenceOfSuspiciousFiles() -> CheckResult { 80 | let paths = [ 81 | "/usr/sbin/frida-server" 82 | ] 83 | 84 | for path in paths where FileManager.default.fileExists(atPath: path) { 85 | return (false, "Suspicious file found: \(path)") 86 | } 87 | 88 | return (true, "") 89 | } 90 | 91 | private static func checkOpenedPorts() -> CheckResult { 92 | let ports = [ 93 | 27042, // default Frida 94 | 4444, // default Needle 95 | 22, // OpenSSH 96 | 44 // checkra1n 97 | ] 98 | 99 | for port in ports where canOpenLocalConnection(port: port) { 100 | return (false, "Port \(port) is open") 101 | } 102 | 103 | return (true, "") 104 | } 105 | 106 | private static func canOpenLocalConnection(port: Int) -> Bool { 107 | func swapBytesIfNeeded(port: in_port_t) -> in_port_t { 108 | let littleEndian = Int(OSHostByteOrder()) == OSLittleEndian 109 | return littleEndian ? _OSSwapInt16(port) : port 110 | } 111 | 112 | var serverAddress = sockaddr_in() 113 | serverAddress.sin_family = sa_family_t(AF_INET) 114 | serverAddress.sin_addr.s_addr = inet_addr("127.0.0.1") 115 | serverAddress.sin_port = swapBytesIfNeeded(port: in_port_t(port)) 116 | let sock = socket(AF_INET, SOCK_STREAM, 0) 117 | 118 | let result = withUnsafePointer(to: &serverAddress) { 119 | $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { 120 | connect(sock, $0, socklen_t(MemoryLayout.stride)) 121 | } 122 | } 123 | 124 | defer { 125 | close(sock) 126 | } 127 | 128 | if result != -1 { 129 | return true // Port is opened 130 | } 131 | 132 | return false 133 | } 134 | 135 | // EXPERIMENTAL 136 | private static func checkPSelectFlag() -> CheckResult { 137 | var kinfo = kinfo_proc() 138 | var mib: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()] 139 | var size = MemoryLayout.stride 140 | let sysctlRet = sysctl(&mib, UInt32(mib.count), &kinfo, &size, nil, 0) 141 | 142 | if sysctlRet != 0 { 143 | print("Error occurred when calling sysctl(). This check may not be reliable") 144 | } 145 | 146 | if (kinfo.kp_proc.p_flag & P_SELECT) != 0 { 147 | return (false, "Suspicious PFlag value") 148 | } 149 | 150 | return (true, "") 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /IOSSecuritySuite/DebuggerChecker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DebuggerChecker.swift 3 | // IOSSecuritySuite 4 | // 5 | // Created by wregula on 23/04/2019. 6 | // Copyright © 2019 wregula. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | internal class DebuggerChecker { 12 | // https://developer.apple.com/library/archive/qa/qa1361/_index.html 13 | static func amIDebugged() -> Bool { 14 | var kinfo = kinfo_proc() 15 | var mib: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()] 16 | var size = MemoryLayout.stride 17 | let sysctlRet = sysctl(&mib, UInt32(mib.count), &kinfo, &size, nil, 0) 18 | 19 | if sysctlRet != 0 { 20 | print("Error occurred when calling sysctl(). The debugger check may not be reliable") 21 | } 22 | 23 | return (kinfo.kp_proc.p_flag & P_TRACED) != 0 24 | } 25 | 26 | static func denyDebugger() { 27 | // bind ptrace() 28 | let pointerToPtrace = UnsafeMutableRawPointer(bitPattern: -2) 29 | let ptracePtr = dlsym(pointerToPtrace, "ptrace") 30 | typealias PtraceType = @convention(c) (CInt, pid_t, CInt, CInt) -> CInt 31 | let ptrace = unsafeBitCast(ptracePtr, to: PtraceType.self) 32 | 33 | // PT_DENY_ATTACH == 31 34 | let ptraceRet = ptrace(31, 0, 0, 0) 35 | 36 | if ptraceRet != 0 { 37 | print("Error occured when calling ptrace(). Denying debugger may not be reliable") 38 | } 39 | } 40 | 41 | #if arch(arm64) 42 | static func hasBreakpointAt( 43 | _ functionAddr: UnsafeRawPointer, 44 | functionSize: vm_size_t? 45 | ) -> Bool { 46 | let funcAddr = vm_address_t(UInt(bitPattern: functionAddr)) 47 | 48 | var vmStart: vm_address_t = funcAddr 49 | var vmSize: vm_size_t = 0 50 | let vmRegionInfo = UnsafeMutablePointer.allocate( 51 | capacity: MemoryLayout.size/4 52 | ) 53 | 54 | defer { 55 | vmRegionInfo.deallocate() 56 | } 57 | 58 | var vmRegionInfoCount: mach_msg_type_number_t = mach_msg_type_number_t(VM_REGION_BASIC_INFO_64) 59 | var objectName: mach_port_t = 0 60 | 61 | let ret = vm_region_64( 62 | mach_task_self_, &vmStart, 63 | &vmSize, VM_REGION_BASIC_INFO_64, 64 | vmRegionInfo, &vmRegionInfoCount, 65 | &objectName 66 | ) 67 | 68 | if ret != KERN_SUCCESS { 69 | return false 70 | } 71 | 72 | let vmRegion = vmRegionInfo.withMemoryRebound( 73 | to: vm_region_basic_info_64.self, capacity: 1, { $0 } 74 | ) 75 | 76 | if vmRegion.pointee.protection == (VM_PROT_READ | VM_PROT_EXECUTE) { 77 | let armBreakpointOpcode = 0xe7ffdefe 78 | let arm64BreakpointOpcode = 0xd4200000 79 | let instructionBegin = functionAddr.bindMemory(to: UInt32.self, capacity: 1) 80 | var judgeSize = (vmSize - (funcAddr - vmStart)) 81 | if let size = functionSize, size < judgeSize { 82 | judgeSize = size 83 | } 84 | 85 | for valueToOffset in 0..<(judgeSize / 4) { 86 | if (instructionBegin.advanced( 87 | by: Int(valueToOffset) 88 | ).pointee == armBreakpointOpcode) || (instructionBegin.advanced( 89 | by: Int(valueToOffset) 90 | ).pointee == arm64BreakpointOpcode) { 91 | return true 92 | } 93 | } 94 | } 95 | 96 | return false 97 | } 98 | 99 | static func hasWatchpoint() -> Bool { 100 | var threads: thread_act_array_t? 101 | var threadCount: mach_msg_type_number_t = 0 102 | var hasWatchpoint = false 103 | 104 | if task_threads(mach_task_self_, &threads, &threadCount) == KERN_SUCCESS { 105 | var threadStat = arm_debug_state64_t() 106 | let capacity = MemoryLayout.size/MemoryLayout.size 107 | 108 | let threadStatPointer = withUnsafeMutablePointer(to: &threadStat, { 109 | $0.withMemoryRebound(to: natural_t.self, capacity: capacity, { $0 }) 110 | }) 111 | 112 | var count = mach_msg_type_number_t( 113 | MemoryLayout.size/MemoryLayout.size 114 | ) 115 | 116 | guard let threads = threads else { 117 | return false 118 | } 119 | 120 | for threadIndex in 0...size)) 136 | ) 137 | } 138 | 139 | return hasWatchpoint 140 | } 141 | #endif 142 | 143 | static func isParentPidUnexpected() -> Bool { 144 | let parentPid: pid_t = getppid() 145 | 146 | return parentPid != 1 // LaunchD is pid 1 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /FrameworkClientApp/FishHook.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FishHook.swift 3 | // FishHookProtect 4 | // 5 | // Created by jintao on 2019/3/28. 6 | // Copyright © 2019 jintao. All rights reserved. 7 | // 8 | // swiftlint:disable all 9 | 10 | import Foundation 11 | import MachO 12 | 13 | #if arch(arm64) 14 | @inline(__always) // just for Swift 15 | public func replaceSymbol( 16 | _ symbol: String, 17 | newMethod: UnsafeMutableRawPointer, 18 | oldMethod: inout UnsafeMutableRawPointer? 19 | ) { 20 | for idx in 0..<_dyld_image_count() { 21 | if let image = _dyld_get_image_header(idx) { 22 | replaceSymbol(symbol, at: image, imageSlide: _dyld_get_image_vmaddr_slide(idx), newMethod: newMethod, oldMethod: &oldMethod) 23 | } 24 | } 25 | } 26 | 27 | private func replaceSymbol( 28 | _ symbol: String, 29 | at image: UnsafePointer, 30 | imageSlide slide: Int, 31 | newMethod: UnsafeMutableRawPointer, 32 | oldMethod: inout UnsafeMutableRawPointer? 33 | ) { 34 | replaceSymbolAtImage(image, imageSlide: slide, symbol: symbol, newMethod: newMethod, oldMethod: &oldMethod) 35 | } 36 | 37 | @inline(__always) 38 | private func replaceSymbolAtImage( 39 | _ image: UnsafePointer, 40 | imageSlide slide: Int, 41 | symbol: String, 42 | newMethod: UnsafeMutableRawPointer, 43 | oldMethod: inout UnsafeMutableRawPointer? 44 | ) { 45 | var linkeditCmd: UnsafeMutablePointer! 46 | var dataCmd: UnsafeMutablePointer! 47 | var symtabCmd: UnsafeMutablePointer! 48 | var dynamicSymtabCmd: UnsafeMutablePointer! 49 | 50 | guard var curCmdPointer = UnsafeMutableRawPointer(bitPattern: UInt(bitPattern: image)+UInt(MemoryLayout.size)) else { return } 51 | 52 | for _ in 0..(OpaquePointer(curCmd)) 66 | } else if curCmd.pointee.cmd == LC_DYSYMTAB { 67 | dynamicSymtabCmd = UnsafeMutablePointer(OpaquePointer(curCmd)) 68 | } 69 | 70 | curCmdPointer += Int(curCmd.pointee.cmdsize) 71 | } 72 | 73 | if linkeditCmd == nil || symtabCmd == nil || dynamicSymtabCmd == nil || dataCmd == nil { 74 | return 75 | } 76 | 77 | let linkedBase = slide + Int(linkeditCmd.pointee.vmaddr) - Int(linkeditCmd.pointee.fileoff) 78 | let symtab = UnsafeMutablePointer(bitPattern: linkedBase + Int(symtabCmd.pointee.symoff)) 79 | let strtab = UnsafeMutablePointer(bitPattern: linkedBase + Int(symtabCmd.pointee.stroff)) 80 | let indirectsym = UnsafeMutablePointer(bitPattern: linkedBase + Int(dynamicSymtabCmd.pointee.indirectsymoff)) 81 | 82 | if symtab == nil || strtab == nil || indirectsym == nil { 83 | return 84 | } 85 | 86 | for tmp in 0...size + MemoryLayout.size*Int(tmp)).assumingMemoryBound(to: section_64.self) 88 | 89 | // symbol_pointers sections 90 | if curSection.pointee.flags == S_LAZY_SYMBOL_POINTERS { 91 | replaceSymbolPointerAtSection(curSection, symtab: symtab!, strtab: strtab!, indirectsym: indirectsym!, slide: slide, symbolName: symbol, newMethod: newMethod, oldMethod: &oldMethod) 92 | } 93 | if curSection.pointee.flags == S_NON_LAZY_SYMBOL_POINTERS { 94 | replaceSymbolPointerAtSection(curSection, symtab: symtab!, strtab: strtab!, indirectsym: indirectsym!, slide: slide, symbolName: symbol, newMethod: newMethod, oldMethod: &oldMethod) 95 | } 96 | } 97 | } 98 | 99 | @inline(__always) 100 | private func replaceSymbolPointerAtSection( 101 | _ section: UnsafeMutablePointer, 102 | symtab: UnsafeMutablePointer, 103 | strtab: UnsafeMutablePointer, 104 | indirectsym: UnsafeMutablePointer, 105 | slide: Int, 106 | symbolName: String, 107 | newMethod: UnsafeMutableRawPointer, 108 | oldMethod: inout UnsafeMutableRawPointer? 109 | ) { 110 | let indirectSymVmAddr = indirectsym.advanced(by: Int(section.pointee.reserved1)) 111 | let sectionVmAddr = UnsafeMutablePointer(bitPattern: slide+Int(section.pointee.addr)) 112 | 113 | if sectionVmAddr == nil { 114 | return 115 | } 116 | 117 | for tmp in 0...size { 118 | let curIndirectSym = indirectSymVmAddr.advanced(by: tmp) 119 | if curIndirectSym.pointee == INDIRECT_SYMBOL_ABS || curIndirectSym.pointee == INDIRECT_SYMBOL_LOCAL { 120 | continue 121 | } 122 | let curStrTabOff = symtab.advanced(by: Int(curIndirectSym.pointee)).pointee.n_un.n_strx 123 | let curSymbolName = strtab.advanced(by: Int(curStrTabOff+1)) 124 | if String(cString: curSymbolName) == symbolName { 125 | oldMethod = sectionVmAddr!.advanced(by: tmp).pointee 126 | sectionVmAddr!.advanced(by: tmp).initialize(to: newMethod) 127 | break 128 | } 129 | } 130 | } 131 | #endif 132 | // swiftlint:enable all 133 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | IOSSecuritySuite v.2.0 2 | End-user license agreement (EULA) 3 | 4 | I. General Provisions. 5 | 6 | 1. This End-User License Agreement (“EULA” ; “License”) is a legal agreement between “you” (either as an individual or on behalf of an entity) and SecuRing spółka z ograniczoną odpowiedzialnością, general partnership with its registered office at ul. Kalwaryjska 65/6, 30-504 Kraków (“SecuRing”), regarding your use of IOSSecuritySuite v.2.0 (the "Software"). IF YOU DO NOT AGREE TO ALL OF THE TERMS OF THIS EULA, DO NOT USE OR COPY THE SOFTWARE. 7 | 2. This Software is provided "as-is" with no warranties, and you agree that SecuRing is not liable for anything you do with it. 8 | 9 | II. The License 10 | 11 | 1. The License applies to corrections and patches. The license does not apply to (a) updates made available by SecuRing; (b) new versions (upgrades) of the Software. The appropriate license terms described with the new version apply. 12 | 2. Securing grants a non-exclusive license to you on the following terms: (a) You may use the Software free of charge and without territorial restrictions under the "Free" plan, provided that the Software is used or is part of a program used by a company that employs no more than 100 people in a given year of use of the Software. Under this permission, you may not sell a solution that includes the Software or charge fees for the use of such a program that includes the Software. You may implement the Software as part of another program, for which no fees may be charged by you or your company; (b) You may use the Software for a fee and without territorial restrictions under the "Standard" plan, provided that the Software is used or is part of a program used by a company that employs between 100 and 1 000 people in a given year of use of the Software. Under this permission, you may not sell a solution that includes the Software or charge fees for the use of such a program that includes the Software. You may implement the Software as part of another program, for which no fees may be charged by you or your company; (c) You may use the Software for a fee and without territorial restrictions under the "Standard +" plan, provided that the Software is used or is part of a program used by a company that employs more than 1 000 people in a given year of use of the Software. Under this permission, you may not sell a solution that includes the Software or charge fees for the use of such a program that includes the Software. You may implement the Software as part of another program, for which no fees may be charged by you or your company; (d) You may use the Software for a fee and without territorial restrictions under the “Enterprise” plan for the purposes of using the Software to create your own programs that are then commercialized and distributed to customers. Under this permission, you may sell solutions that include the Software or charge fees for the use of such a program that includes the Software. You may implement the Software as part of another program, for which fees may be charged by you or your company. 13 | 3. The "Free" license is granted automatically for a period of one (1) year from the date you download the Software. You can use the Software, but remember to check after one year whether you are still subject to the license terms of the Free plan. The license is automatically extended if you meet the conditions. If you do not meet the conditions, the license is revoked. We have the right to write information if we verify a violation of the License Terms. 14 | 4. A paid license in the "Standard" or "Standard +" or "Enterprise" plan is granted for a period of one (1) year from the date of payment. After payment, you will receive an e-mail confirmation with a document confirming that the license has been granted to you/your company for a specific period and plan. If you want the License to be renewed and granted for another year, you must complete the form again and pay the appropriate fee. Before choosing the appropriate plan, verify the number of people employed in your company in a given year and the way you want to use the Software). 15 | 5. The price list and payment are made on the website available at the following URL: 16 | * “Standard": https://iss-under-1k.securing.pl 17 | * “Standard +”: https://iss-over-1k.securing.pl 18 | * “Enterprise”: https://iss-over-1k.securing.pl 19 | 6. The paid license is granted for a period of 1 year and automatically renews for another year if you have agreed to the subscription, i.e. renewing the payment. Renewing your payments means you will be charged once a year. If the amount of fees is to change, you will be informed at least 30 days in advance so that you can give your consent. If you do not agree to the new prices, the subscription will be automatically canceled. Payments are processed by Stripe Inc. in accordance with the regulations: https://stripe.com/en-pl/legal/consumer. SecuRing does not have access to card data and is not a payment processor. 20 | 7. The License does not authorize you and you may not do this, i.e. (a) use the Software for implementation purposes in infringing programs; (b) transfer your or your company's rights to use the Software in accordance with a given License plan to a third party (assignment prohibited). 21 | 8. The License may be revoked for you or your company at any time if SecuRing finds that you are violating the terms of the License. You and your company for which the License is granted are not entitled to any compensation for lost profits or damages incurred in connection with this. 22 | 23 | III. Miscellanea 24 | 25 | 1. The Software is licensed, it is not sold, i.e. no copyrights or ownership of media are transferred. 26 | 2. If you want to develop the Software and introduce a correction, supplement or patch, please contact SecuRing. 27 | 3. You must comply with all domestic and international export laws and regulations as they apply to your program or your company's program of which the Software is a component. You make the assessment yourself, provided that you make the assessment yourself. 28 | 29 | IV. Applicable law and disputes 30 | 31 | 1. If you use the Software in connection with your business or profession or the License is granted to a company, the interpretation of the License is subject to the law applicable to the Republic of Poland. The court competent to resolve disputes is the court having jurisdiction over the seat of SecuRing. 32 | 2. If you use the Software as a consumer, the interpretation of the License is subject to the law applicable to the Republic of Poland. The court competent to resolve disputes is the court having jurisdiction over the consumer's place of residence. If the provisions of consumer law, due to his place of residence, provide further guarantees and protection, these provisions shall apply. 33 | 34 | V. Disclaimers and Limitations on Liability 35 | 36 | 1. YOU ASSUME THE FULL RISK AS TO THE RESULTS AND PERFORMANCE OF THE SOFTWARE. YOU EXPRESSLY UNDERSTAND AND AGREE THAT SECURING SHALL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL OR EXEMPLARY DAMAGES, INCLUDING BUT NOT LIMITED TO, DAMAGES FOR LOSS OF PROFITS, GOODWILL, USE, DATA OR OTHER INTANGIBLE LOSSES (EVEN IF SECURING HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES) RELATED TO THE SOFTWARE, including, for example: (i) the use or the inability to use the Software; (ii) the cost of procurement of substitute goods and services resulting from any goods, data, information or services purchased or obtained or messages received or transactions entered into through or from the Software; (iii) unauthorized access to or alteration of your transmissions or data; (iv) statements or conduct of any third-party on the Software; (v) or any other matter relating to the Software. 37 | 2. All warranties are disclaimed to the fullest extent permitted by law. SecuRing does not provide you with any guarantees or assurances that the Software will meet your expectations. 38 | 3. IN NO EVENT WILL SECURING BE LIABLE TO YOU FOR ANY EXTRAORDINARY, CONSEQUENTIAL, INCIDENTAL OR INDIRECT DAMAGES, INCLUDING, BUT NOT LIMITED TO, IN TORT OR IN CONTRACT. FOR LOSS OF INCOME OR PROFIT, LOSS OR DAMAGE DATA OR FILES, FAILURES OR MALFUNCTION OF DEVICES, PROGRAMS OR APPLICATIONS, OR OTHER COMMERCIAL OR ECONOMIC LOSSES RESULTING FROM THE USE OR INABILITY TO USE SOFTWARE OR THIRD-PARTY SERVICES. 39 | 4. Some jurisdictions do not allow the limitation or exclusion of liability, so the above limitation or exclusion may not apply to you, or may only apply in part. None of the provisions listed above affect your statutory rights. 40 | 41 | VI. Final Provisions. 42 | 43 | 1. After the expiration of the License, you are obliged to remove the Software from your resources no later than on the date of expiration of the License. Remember to extend the License if you want to continue using the Software. SecuRing has the right to supervise whether you continue to use the Software despite the expiration of the License. If SecuRing finds that you are using the Software without the required License or fee, it has the right to send you information or a request for payment and an appropriate proforma invoice. If, despite three contact attempts, you do not legally regulate the use of the Software and you continue to actually use it, SecuRing has the right to publicly present such information and demand compensation for the damage from you or your company in court. 44 | -------------------------------------------------------------------------------- /IOSSecuritySuite/FileChecker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MountedVolumes.swift 3 | // IOSSecuritySuite 4 | // 5 | // Created by Mario Sepulveda on 6/29/23. 6 | // Copyright © 2023 wregula. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | internal class FileChecker { 12 | typealias CheckResult = (passed: Bool, failMessage: String) 13 | 14 | /** 15 | Used to store some information provided by statfs() 16 | */ 17 | struct MountedVolumeInfo { 18 | let fileSystemName: String 19 | let directoryName: String 20 | let isRoot: Bool 21 | let isReadOnly: Bool 22 | } 23 | 24 | /** 25 | Used to determine if a file access check should be in Write or Read-Only mode. 26 | */ 27 | enum FileMode { 28 | case readable 29 | case writable 30 | } 31 | 32 | /** 33 | Given a path, this method provides information about the associated volume. 34 | - Parameters: 35 | - path: path is the pathname of any file within the mounted file system. 36 | - Returns: Returns nil, if statfs() gives a non-zero result. 37 | */ 38 | private static func getMountedVolumeInfoViaStatfs( 39 | path: String, 40 | encoding: String.Encoding = .utf8 41 | ) -> MountedVolumeInfo? { 42 | guard let path: [CChar] = path.cString(using: encoding) else { 43 | assertionFailure("Failed to create a cString with path=\(path) encoding=\(encoding)") 44 | return nil 45 | } 46 | 47 | var statBuffer = statfs() 48 | /** 49 | Upon successful completion, the value 0 is returned; otherwise the 50 | value -1 is returned and the global variable errno is set to indicate 51 | the error. 52 | */ 53 | let resultCode: Int32 = statfs(path, &statBuffer) 54 | 55 | if resultCode == 0 { 56 | let mntFromName: String = withUnsafePointer(to: statBuffer.f_mntfromname) { ptr -> String in 57 | return String(cString: UnsafeRawPointer(ptr).assumingMemoryBound(to: CChar.self)) 58 | } 59 | let mntOnName: String = withUnsafePointer(to: statBuffer.f_mntonname) { ptr -> String in 60 | return String(cString: UnsafeRawPointer(ptr).assumingMemoryBound(to: CChar.self)) 61 | } 62 | 63 | return MountedVolumeInfo(fileSystemName: mntFromName, 64 | directoryName: mntOnName, 65 | isRoot: (Int32(statBuffer.f_flags) & MNT_ROOTFS) != 0, 66 | isReadOnly: (Int32(statBuffer.f_flags) & MNT_RDONLY) != 0) 67 | } else { 68 | return nil 69 | } 70 | } 71 | 72 | /** 73 | This method provides information about all mounted volumes. 74 | - Returns: Returns nil, if getfsstat() does not return any filesystem statistics. 75 | */ 76 | private static func getMountedVolumesViaGetfsstat() -> [MountedVolumeInfo]? { 77 | // If buf is NULL, getfsstat() returns just the number of mounted file systems. 78 | let count: Int32 = getfsstat(nil, 0, MNT_NOWAIT) 79 | 80 | guard count >= 0 else { 81 | assertionFailure("getfsstat() failed to return the number of mounted file systems.") 82 | return nil 83 | } 84 | 85 | var statBuffer: [statfs] = .init(repeating: .init(), count: Int(count)) 86 | let size: Int = MemoryLayout.size * statBuffer.count 87 | /** 88 | Upon successful completion, the number of statfs structures is 89 | returned. Otherwise, -1 is returned and the global variable errno is 90 | set to indicate the error. 91 | */ 92 | let resultCode: Int32 = getfsstat(&statBuffer, Int32(size), MNT_NOWAIT) 93 | 94 | if resultCode > -1 { 95 | if count != resultCode { 96 | assertionFailure("Unexpected a resultCode=\(resultCode), was expecting=\(count).") 97 | } 98 | 99 | var result: [MountedVolumeInfo] = [] 100 | 101 | for entry: statfs in statBuffer { 102 | let mntFromName: String = withUnsafePointer(to: entry.f_mntfromname) { ptr -> String in 103 | return String(cString: UnsafeRawPointer(ptr).assumingMemoryBound(to: CChar.self)) 104 | } 105 | let mntOnName: String = withUnsafePointer(to: entry.f_mntonname) { ptr -> String in 106 | return String(cString: UnsafeRawPointer(ptr).assumingMemoryBound(to: CChar.self)) 107 | } 108 | 109 | let info = MountedVolumeInfo(fileSystemName: mntFromName, 110 | directoryName: mntOnName, 111 | isRoot: (Int32(entry.f_flags) & MNT_ROOTFS) != 0, 112 | isReadOnly: (Int32(entry.f_flags) & MNT_RDONLY) != 0) 113 | result.append(info) 114 | } 115 | 116 | if count != result.count { 117 | assertionFailure("Unexpected filesystems count=\(result.count), was expecting=\(count).") 118 | } 119 | 120 | return result 121 | } else { 122 | assertionFailure( 123 | "getfsstat() failed. resultCode=\(resultCode), expected count=\(count) filesystems." 124 | ) 125 | return nil 126 | } 127 | } 128 | 129 | /** 130 | Loops through the mounted volumes provided by Getfsstat() and searches for a match. 131 | - Parameters: 132 | - name: The filesystem name or mounted directory name to search for. 133 | - Returns: Returns nil, if a matching mounted volume is not found. 134 | */ 135 | private static func getMountedVolumesViaGetfsstat(withName name: String) -> MountedVolumeInfo? { 136 | if let list = getMountedVolumesViaGetfsstat() { 137 | if list.count == 0 { 138 | assertionFailure("Expected to a non-empty list of mounted volumes.") 139 | } else { 140 | return list.first(where: { $0.directoryName == name || $0.fileSystemName == name }) 141 | } 142 | } else { 143 | assertionFailure("Expected a non-nil list of mounted volumes.") 144 | } 145 | return nil 146 | } 147 | 148 | /** 149 | Uses fopen() to check if an file exists and attempts to open it, in either Read-Only or Read-Write mode. 150 | - Parameters: 151 | - path: The file path to open. 152 | - mode: Determines if the file will be opened in Writable or Read-Only mode. 153 | - returns: Returns nil, if the file does not exist. Returns true if it can be opened with the given mode. 154 | */ 155 | static func checkExistenceOfSuspiciousFilesViaFOpen(path: String, 156 | mode: FileMode) -> CheckResult? { 157 | // the 'a' or 'w' modes, create the file if it does not exist. 158 | let mode: String = FileMode.writable == mode ? "r+" : "r" 159 | 160 | if let filePointer: UnsafeMutablePointer = fopen(path, mode) { 161 | fclose(filePointer) 162 | return (false, "Suspicious file exists: \(path)") 163 | } else { 164 | return nil 165 | } 166 | } 167 | 168 | /** 169 | Uses stat() to check if a file exists. 170 | - returns: Returns nil, if stat() returns a non-zero result code. 171 | */ 172 | static func checkExistenceOfSuspiciousFilesViaStat(path: String) -> CheckResult? { 173 | var statbuf: stat = stat() 174 | let resultCode = stat((path as NSString).fileSystemRepresentation, &statbuf) 175 | 176 | if resultCode == 0 { 177 | return (false, "Suspicious file exists: \(path)") 178 | } else { 179 | return nil 180 | } 181 | } 182 | 183 | /** 184 | Uses access() to check whether the calling process can access the file path, in either Read-Only or Write mode. 185 | - Parameters: 186 | - path: The file path to open. 187 | - mode: Determines if the file will be accessed in Write mode or Read-Only mode. 188 | - returns: Returns nil, if access() returns a non-zero result code. 189 | */ 190 | static func checkExistenceOfSuspiciousFilesViaAccess( 191 | path: String, 192 | mode: FileMode 193 | ) -> CheckResult? { 194 | let resultCode = access( 195 | (path as NSString).fileSystemRepresentation, 196 | FileMode.writable == mode ? W_OK : R_OK 197 | ) 198 | 199 | if resultCode == 0 { 200 | return (false, "Suspicious file exists: \(path)") 201 | } else { 202 | return nil 203 | } 204 | } 205 | 206 | /** 207 | Checks if statvfs() considers the given path to be Read-Only. 208 | - Returns: Returns nil, if statvfs() gives a non-zero result. 209 | */ 210 | static func checkRestrictedPathIsReadonlyViaStatvfs( 211 | path: String, 212 | encoding: String.Encoding = .utf8 213 | ) -> Bool? { 214 | guard let path: [CChar] = path.cString(using: encoding) else { 215 | assertionFailure("Failed to create a cString with path=\(path) encoding=\(encoding)") 216 | return nil 217 | } 218 | 219 | var statBuffer = statvfs() 220 | let resultCode: Int32 = statvfs(path, &statBuffer) 221 | 222 | if resultCode == 0 { 223 | return Int32(statBuffer.f_flag) & ST_RDONLY != 0 224 | } else { 225 | return nil 226 | } 227 | } 228 | 229 | /** 230 | Checks if statvs() considers the volume associated with given path to be Read-Only. 231 | - Returns: Returns nil, if statfs() does not find the mounted volume. 232 | */ 233 | static func checkRestrictedPathIsReadonlyViaStatfs( 234 | path: String, 235 | encoding: String.Encoding = .utf8 236 | ) -> Bool? { 237 | return getMountedVolumeInfoViaStatfs(path: path, encoding: encoding)?.isReadOnly 238 | } 239 | 240 | /** 241 | Checks if Getfsstat() considers the volume to be Read-Only. 242 | - Parameters: 243 | - name: The filesystem name or mounted directory name to search for. 244 | - Returns: Returns nil, if a matching mounted volume is not found. 245 | */ 246 | static func checkRestrictedPathIsReadonlyViaGetfsstat(name: String) -> Bool? { 247 | return self.getMountedVolumesViaGetfsstat(withName: name)?.isReadOnly 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /IOSSecuritySuite/MSHookFunctionChecker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MSHookFunctionChecker.swift 3 | // IOSSecuritySuite 4 | // 5 | // Created by jintao on 2020/4/24. 6 | // Modified by Ant-Tree on 2023/01/25. 7 | // 8 | // Copyright © 2020 wregula. All rights reserved. 9 | // https://github.com/TannerJin/AntiMSHookFunction 10 | 11 | // swiftlint:disable cyclomatic_complexity function_body_length identifier_name 12 | 13 | import Foundation 14 | 15 | /* 16 | Original: 17 | 18 | * original function address (example) 19 | stp x22, x21, [sp, #-0x10] 20 | stp x20, x19, [sp, #-0x20] 21 | stp x29, x30, [sp, #-0x30] 22 | . 23 | . 24 | . 25 | 26 | * vm_regions 27 | 28 | vm_region_0 vm_region_n 29 | *-----------* *-----------* 30 | | | | | 31 | | | -----> ... -----> | | 32 | | | <----- <----- | | 33 | *-----------* *-----------* 34 | | 35 | | 36 | V 37 | 38 | After MSHookFunction(mmap): 39 | 40 | * original function address 41 | ldr x16 #8 (4 bytes for arm64) 42 | br x16 (4 bytes for arm64) 43 | address (8 bytes for arm64) address = hook_function_address 44 | *-> . 45 | | . 46 | | . 47 | | 48 | | * vm_regions 49 | | 50 | | vm_region_0 vm_region_new vm_region_n+1 51 | | *-----------* *-----------* *-----------* 52 | | | | | | | | 53 | | | | -----> ... -----> | | -----> ... -----> | | 54 | | | | <----- <----- | | <----- <----- | | 55 | | *-----------* *-----------* *-----------* 56 | | 57 | | 58 | | 1. The vm_region_new is created by MSHookFunction with VM_PROT that is VM_PROT_READ and VM_PROT_EXECUTE 59 | | 60 | | 2. Instructions that can call original function are stored at the beginning of vm_region_new 61 | | 62 | | 3. The beginning of vm_region_new should look like instructions below 63 | | ... (>= 16 bytes for arm64) >=4 hooked instructions 64 | | ldr x16 #8 (4 bytes for arm64) 65 | | br x16 (4 bytes for arm64) 66 | *------ address (8 bytes for arm64) address = original_function_address + 16 67 | 68 | (For Cydia Substrate, the code block above doesn't guaranteed to be at the beginning of a region) 69 | */ 70 | 71 | #if arch(arm64) 72 | internal class MSHookFunctionChecker { 73 | // come from ARM® Architecture Reference Manual, ARMv8 for ARMv8-A architecture profile 74 | private enum MSHookInstruction { 75 | case ldr_x16 76 | case br_x16 77 | case adrp_x17(pageBase: UInt64) 78 | case add_x17(pageOffset: UInt64) 79 | case br_x17 80 | 81 | @inline(__always) 82 | static fileprivate func translateInstruction( 83 | at functionAddr: UnsafeMutableRawPointer 84 | ) -> MSHookInstruction? { 85 | let arm = functionAddr.assumingMemoryBound(to: UInt32.self).pointee 86 | // ldr xt, #imm (C4.4.5 and C6.2.84) 87 | let ldr_register_litetal = (arm & (255 << 24)) >> 24 88 | if ldr_register_litetal == 0b01011000 { 89 | let rt = arm & 31 90 | let imm19 = (arm & ((1 << 19 - 1) << 5)) >> 5 91 | // ldr x16, #8 92 | if rt == 16 && (imm19 << 2) == 8 { 93 | return ldr_x16 94 | } 95 | } 96 | // br 97 | let br = arm >> 10 98 | if br == 0b1101011000011111000000 { 99 | let br_rn = (arm & (31 << 5)) >> 5 100 | if br_rn == 16 { 101 | return .br_x16 102 | } 103 | if br_rn == 17 { 104 | return .br_x17 105 | } 106 | } 107 | // adrp (C6.2.10) 108 | let adrp_op = arm >> 31 109 | let adrp = (arm & (31 << 24)) >> 24 110 | let rd = arm & (31 << 0) 111 | if adrp_op == 1 && adrp == 16 { 112 | let pageBase = getAdrpPageBase(functionAddr) 113 | // adrp x17, pageBase 114 | if rd == 17 { 115 | return .adrp_x17(pageBase: pageBase) 116 | } 117 | } 118 | // add (C4.2.1 and C6.2.4) 119 | let add = arm >> 24 120 | if add == 0b10010001 { // 32-bit: 0b00010001 121 | let add_rn = (arm & (31 << 5)) >> 5 122 | let add_rd = arm & 31 123 | let add_imm12 = UInt32((arm & ((1 << 12 - 1) << 10)) >> 10) 124 | var imm = UInt64(add_imm12) 125 | let shift = (arm & (3 << 22)) >> 22 126 | if shift == 0 { 127 | imm = UInt64(add_imm12) 128 | } else if shift == 1 { 129 | imm = UInt64(add_imm12 << 12) 130 | } else { 131 | // AArch64.UndefinedFault 132 | return nil 133 | } 134 | // add x17, x17, add_im 135 | if add_rn == 17 && add_rd == 17 { 136 | return .add_x17(pageOffset: imm) 137 | } 138 | } 139 | return nil 140 | } 141 | 142 | // pageBase 143 | @inline(__always) 144 | static private func getAdrpPageBase(_ functionAddr: UnsafeMutableRawPointer) -> UInt64 { 145 | let arm = functionAddr.assumingMemoryBound(to: UInt32.self).pointee 146 | func singExtend(_ value: Int64) -> Int64 { 147 | var result = value 148 | let sing = value >> (33 - 1) == 1 149 | if sing { 150 | result = ((1 << 31 - 1) << 33) | value 151 | } 152 | return result 153 | } 154 | // +/- 4GB 155 | let immlo = (arm >> 29) & 3 156 | let immhiMask = UInt32((1 << 19 - 1) << 5) 157 | let immhi = (arm & immhiMask) >> 5 158 | let imm = (Int64((immhi << 2 | immlo)) << 12) 159 | let pcBase = (UInt(bitPattern: functionAddr) >> 12) << 12 160 | return UInt64(Int64(pcBase) + singExtend(imm)) 161 | } 162 | } 163 | 164 | @inline(__always) 165 | static func amIMSHooked(_ functionAddr: UnsafeMutableRawPointer) -> Bool { 166 | guard let firstInstruction = MSHookInstruction.translateInstruction(at: functionAddr) else { 167 | return false 168 | } 169 | switch firstInstruction { 170 | case .ldr_x16: 171 | let secondInstructionAddr = functionAddr + 4 172 | if case .br_x16 = MSHookInstruction.translateInstruction(at: secondInstructionAddr) { 173 | return true 174 | } 175 | return false 176 | case .adrp_x17: 177 | let secondInstructionAddr = functionAddr + 4 178 | let thridInstructionAddr = functionAddr + 8 179 | if case .add_x17 = MSHookInstruction.translateInstruction(at: secondInstructionAddr), 180 | case .br_x17 = MSHookInstruction.translateInstruction(at: thridInstructionAddr) { 181 | return true 182 | } 183 | return false 184 | default: 185 | return false 186 | } 187 | } 188 | 189 | @inline(__always) 190 | static func denyMSHook(_ functionAddr: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer? { 191 | if !amIMSHooked(functionAddr) { 192 | return nil 193 | } 194 | // size of replaced instructions 195 | guard let firstInstruction = MSHookInstruction.translateInstruction( 196 | at: functionAddr 197 | ) else { 198 | assert(false, "amIMSHookFunction has judged") 199 | return nil 200 | } 201 | var origFunctionBeginAddr = functionAddr 202 | switch firstInstruction { 203 | case .ldr_x16: 204 | origFunctionBeginAddr += 16 205 | case .adrp_x17: 206 | origFunctionBeginAddr += 12 207 | default: 208 | assert(false, "amIMSHookFunction has judged") 209 | return nil 210 | } 211 | // look up vm_region 212 | let vmRegionInfo = UnsafeMutablePointer.allocate( 213 | capacity: MemoryLayout.size/4 214 | ) 215 | defer { 216 | vmRegionInfo.deallocate() 217 | } 218 | var vmRegionAddress: vm_address_t = 1 219 | var vmRegionSize: vm_size_t = 0 220 | var vmRegionInfoCount: mach_msg_type_number_t = mach_msg_type_number_t(VM_REGION_BASIC_INFO_64) 221 | var objectName: mach_port_t = 0 222 | 223 | while true { 224 | if vmRegionAddress == 0 { 225 | // False address 226 | return nil 227 | } 228 | 229 | // Get VM region of designated address 230 | if vm_region_64( 231 | mach_task_self_, 232 | &vmRegionAddress, 233 | &vmRegionSize, 234 | VM_REGION_BASIC_INFO_64, 235 | vmRegionInfo, 236 | &vmRegionInfoCount, 237 | &objectName 238 | ) != KERN_SUCCESS { 239 | // End of vm_regions or something wrong 240 | return nil 241 | } 242 | 243 | let regionInfo = UnsafeMutableRawPointer(vmRegionInfo).assumingMemoryBound( 244 | to: vm_region_basic_info_64.self 245 | ) 246 | 247 | // vm region of code 248 | if regionInfo.pointee.protection != (VM_PROT_READ | VM_PROT_EXECUTE) { 249 | // Memory protection level of executable region is always READ + EXECUTE 250 | vmRegionAddress += vmRegionSize 251 | continue 252 | } 253 | 254 | // ldr (Mobile Substrate) 255 | if case .ldr_x16 = firstInstruction { 256 | // Current vm region instruction address 257 | var vmRegionProcedureAddr = vmRegionAddress 258 | // Current vm region instruction address 259 | var vmRegionInstAddr = vmRegionAddress 260 | // Last address of current vm region 261 | let vmRegionEndAddress = vmRegionAddress + vmRegionSize 262 | 263 | // Unlike substitute, When using substrate, branching address may resides anywhere in vm region. 264 | // So every region must be investigated to check whether it contains original function address. 265 | while vmRegionEndAddress >= vmRegionInstAddr { 266 | vmRegionInstAddr += 4 267 | guard let instructionAddr = UnsafeMutablePointer( 268 | bitPattern: Int(vmRegionInstAddr) 269 | ) else { 270 | continue 271 | } 272 | 273 | if UInt(bitPattern: instructionAddr.pointee) == 0 { 274 | vmRegionProcedureAddr = vmRegionInstAddr + 4 275 | continue 276 | } 277 | 278 | if case .ldr_x16 = MSHookInstruction.translateInstruction( 279 | at: instructionAddr 280 | ), case .br_x16 = MSHookInstruction.translateInstruction( 281 | at: UnsafeMutableRawPointer(instructionAddr) + 4 282 | ), (instructionAddr + 1).pointee == origFunctionBeginAddr { 283 | return UnsafeMutableRawPointer( 284 | bitPattern: Int(vmRegionProcedureAddr + 4) 285 | ) 286 | } 287 | } 288 | } 289 | // adrp (Substitute) 290 | if case .adrp_x17 = firstInstruction { 291 | // 20: max_buffer_insered_Instruction 292 | for i in 3..<20 { 293 | if let instructionAddr = UnsafeMutableRawPointer( 294 | bitPattern: Int(vmRegionAddress) + i * 4 295 | ), case let .adrp_x17( 296 | pageBase: pageBase 297 | ) = MSHookInstruction.translateInstruction( 298 | at: instructionAddr 299 | ), case let .add_x17( 300 | pageOffset: pageOffset 301 | ) = MSHookInstruction.translateInstruction( 302 | at: instructionAddr + 4 303 | ), case .br_x17 = MSHookInstruction.translateInstruction( 304 | at: instructionAddr + 8 305 | ), pageBase + pageOffset == UInt(bitPattern: origFunctionBeginAddr) { 306 | return UnsafeMutableRawPointer(bitPattern: Int(vmRegionAddress)) 307 | } 308 | } 309 | } 310 | vmRegionAddress += vmRegionSize 311 | } 312 | } 313 | } 314 | #endif 315 | // swiftlint:enable cyclomatic_complexity function_body_length identifier_name 316 | -------------------------------------------------------------------------------- /IOSSecuritySuite/IntegrityChecker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IntegrityChecker.swift 3 | // IOSSecuritySuite 4 | // 5 | // Created by NikoXu on 2020/8/21. 6 | // Copyright © 2020 wregula. All rights reserved. 7 | // 8 | // swiftlint:disable line_length large_tuple force_cast 9 | 10 | import Foundation 11 | import MachO 12 | import CommonCrypto 13 | 14 | protocol Explainable { 15 | var description: String { get } 16 | } 17 | 18 | /// Possible checks made during ``amITampered`` analysis 19 | public enum FileIntegrityCheck { 20 | /// Compare current bundleID with a specified bundleID. 21 | case bundleID(String) 22 | 23 | /// Compare current hash value(SHA256 hex string) of `embedded.mobileprovision` with a specified hash value. 24 | /// Use command `"shasum -a 256 /path/to/embedded.mobileprovision"` to get SHA256 value on your macOS. 25 | case mobileProvision(String) 26 | 27 | /// Compare current hash value(SHA256 hex string) of executable file with a specified (Image Name, Hash Value). 28 | /// Only work on dynamic library and arm64. 29 | case machO(String, String) 30 | } 31 | 32 | extension FileIntegrityCheck: Explainable { 33 | public var description: String { 34 | switch self { 35 | case .bundleID(let exceptedBundleID): 36 | return "The expected bundle identify was \(exceptedBundleID)" 37 | case .mobileProvision(let expectedSha256Value): 38 | return "The expected hash value of Mobile Provision file was \(expectedSha256Value)" 39 | case .machO(let imageName, let expectedSha256Value): 40 | return "The expected hash value of \"__TEXT.__text\" data of \(imageName) Mach-O file was \(expectedSha256Value)" 41 | } 42 | } 43 | } 44 | 45 | /// Tuple with the result of integrity checks and a list of failed checks 46 | public typealias FileIntegrityCheckResult = (result: Bool, hitChecks: [FileIntegrityCheck]) 47 | 48 | internal class IntegrityChecker { 49 | // Check if the application has been tampered with the specified checks 50 | static func amITampered(_ checks: [FileIntegrityCheck]) -> FileIntegrityCheckResult { 51 | var hitChecks: [FileIntegrityCheck] = [] 52 | var result = false 53 | 54 | for check in checks { 55 | switch check { 56 | case .bundleID(let exceptedBundleID): 57 | if checkBundleID(exceptedBundleID) { 58 | result = true 59 | hitChecks.append(check) 60 | } 61 | case .mobileProvision(let expectedSha256Value): 62 | if checkMobileProvision(expectedSha256Value.lowercased()) { 63 | result = true 64 | hitChecks.append(check) 65 | } 66 | case .machO(let imageName, let expectedSha256Value): 67 | if checkMachO(imageName, with: expectedSha256Value.lowercased()) { 68 | result = true 69 | hitChecks.append(check) 70 | } 71 | } 72 | } 73 | 74 | return (result, hitChecks) 75 | } 76 | 77 | private static func checkBundleID(_ expectedBundleID: String) -> Bool { 78 | if expectedBundleID != Bundle.main.bundleIdentifier { 79 | return true 80 | } 81 | 82 | return false 83 | } 84 | 85 | private static func checkMobileProvision(_ expectedSha256Value: String) -> Bool { 86 | guard let path = Bundle.main.path( 87 | forResource: "embedded", ofType: "mobileprovision" 88 | ) else { 89 | return false 90 | } 91 | 92 | let url = URL(fileURLWithPath: path) 93 | 94 | if FileManager.default.fileExists(atPath: url.path) { 95 | if let data = FileManager.default.contents(atPath: url.path) { 96 | // Hash: SHA256 97 | var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH)) 98 | data.withUnsafeBytes { 99 | _ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash) 100 | } 101 | 102 | if Data(hash).hexEncodedString() != expectedSha256Value { 103 | return true 104 | } 105 | } 106 | } 107 | 108 | return false 109 | } 110 | 111 | private static func checkMachO(_ imageName: String, with expectedSha256Value: String) -> Bool { 112 | #if arch(arm64) 113 | if let hashValue = getMachOFileHashValue(.custom(imageName)), hashValue != expectedSha256Value { 114 | return true 115 | } 116 | #endif 117 | return false 118 | } 119 | } 120 | 121 | #if arch(arm64) 122 | /// Possible target images that will be checked by IntegrityChecker 123 | public enum IntegrityCheckerImageTarget { 124 | /// Default image 125 | case `default` 126 | 127 | /// Custom image with a specified name 128 | case custom(String) 129 | } 130 | 131 | extension IntegrityChecker { 132 | // Get hash value of Mach-O "__TEXT.__text" data with a specified image target 133 | static func getMachOFileHashValue(_ target: IntegrityCheckerImageTarget = .default) -> String? { 134 | switch target { 135 | case .custom(let imageName): 136 | return MachOParse(imageName: imageName).getTextSectionDataSHA256Value() 137 | case .default: 138 | return MachOParse().getTextSectionDataSHA256Value() 139 | } 140 | } 141 | 142 | // Find loaded dylib with a specified image target 143 | static func findLoadedDylibs(_ target: IntegrityCheckerImageTarget = .default) -> [String]? { 144 | switch target { 145 | case .custom(let imageName): 146 | return MachOParse(imageName: imageName).findLoadedDylibs() 147 | case .default: 148 | return MachOParse().findLoadedDylibs() 149 | } 150 | } 151 | } 152 | 153 | // MARK: - MachOParse 154 | private struct SectionInfo { 155 | var section: UnsafePointer 156 | var addr: UInt64 157 | } 158 | 159 | private struct SegmentInfo { 160 | var segment: UnsafePointer 161 | var addr: UInt64 162 | } 163 | 164 | // Convert (Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8) to String 165 | @inline(__always) 166 | private func convert16BitInt8TupleToString(int8Tuple: (Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8)) -> String { 167 | let mirror = Mirror(reflecting: int8Tuple) 168 | 169 | return mirror.children.map { 170 | String(UnicodeScalar(UInt8($0.value as! Int8))) 171 | }.joined().replacingOccurrences(of: "\0", with: "") 172 | } 173 | 174 | private class MachOParse { 175 | private var base: UnsafePointer? 176 | private var slide: Int? 177 | 178 | init() { 179 | base = _dyld_get_image_header(0) 180 | slide = _dyld_get_image_vmaddr_slide(0) 181 | } 182 | 183 | init(header: UnsafePointer, slide: Int) { 184 | self.base = header 185 | self.slide = slide 186 | } 187 | 188 | init(imageName: String) { 189 | for index in 0..<_dyld_image_count() { 190 | if let cImgName = _dyld_get_image_name(index), String(cString: cImgName).contains(imageName), 191 | let header = _dyld_get_image_header(index) { 192 | self.base = header 193 | self.slide = _dyld_get_image_vmaddr_slide(index) 194 | } 195 | } 196 | } 197 | 198 | private func vm2real(_ vmaddr: UInt64) -> UInt64? { 199 | guard let slide = slide else { 200 | return nil 201 | } 202 | 203 | return UInt64(slide) + vmaddr 204 | } 205 | 206 | func findLoadedDylibs() -> [String]? { 207 | guard let header = base else { 208 | return nil 209 | } 210 | 211 | guard var curCmd = UnsafeMutablePointer(bitPattern: UInt(bitPattern: header) + UInt(MemoryLayout.size)) else { 212 | return nil 213 | } 214 | 215 | var array: [String] = Array() 216 | var segCmd: UnsafeMutablePointer! 217 | 218 | for _ in 0.. SegmentInfo? { 235 | guard let header = base else { 236 | return nil 237 | } 238 | 239 | guard var curCmd = UnsafeMutablePointer( 240 | bitPattern: UInt(bitPattern: header) + UInt(MemoryLayout.size) 241 | ) else { 242 | return nil 243 | } 244 | 245 | var segCmd: UnsafeMutablePointer! 246 | 247 | for _ in 0.. SectionInfo? { 266 | guard let header = base else { 267 | return nil 268 | } 269 | 270 | guard var curCmd = UnsafeMutablePointer( 271 | bitPattern: UInt(bitPattern: header) + UInt(MemoryLayout.size) 272 | ) else { 273 | return nil 274 | } 275 | 276 | var segCmd: UnsafeMutablePointer! 277 | 278 | for _ in 0..( 286 | bitPattern: UInt( 287 | bitPattern: curCmd 288 | ) + UInt( 289 | MemoryLayout.size 290 | ) + UInt(sectionID) 291 | ) else { 292 | return nil 293 | } 294 | 295 | let secName = convert16BitInt8TupleToString(int8Tuple: sect.pointee.sectname) 296 | 297 | if secName == secname, 298 | let addr = vm2real(sect.pointee.addr) { 299 | let sectionInfo = SectionInfo(section: sect, addr: addr) 300 | return sectionInfo 301 | } 302 | } 303 | } 304 | } 305 | 306 | curCmd = UnsafeMutableRawPointer(curCmd).advanced(by: Int(curCmd.pointee.cmdsize)).assumingMemoryBound(to: segment_command_64.self) 307 | } 308 | 309 | return nil 310 | } 311 | 312 | func getTextSectionDataSHA256Value() -> String? { 313 | guard let sectionInfo = findSection(SEG_TEXT, secname: SECT_TEXT) else { 314 | return nil 315 | } 316 | 317 | guard let startAddr = UnsafeMutablePointer(bitPattern: Int(sectionInfo.addr)) else { 318 | return nil 319 | } 320 | 321 | let size = sectionInfo.section.pointee.size 322 | 323 | // Hash: SHA256 324 | var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH)) 325 | _ = CC_SHA256(startAddr, CC_LONG(size), &hash) 326 | 327 | return Data(hash).hexEncodedString() 328 | } 329 | } 330 | 331 | #endif 332 | 333 | extension Data { 334 | fileprivate func hexEncodedString() -> String { 335 | return map { String(format: "%02hhx", $0) }.joined() 336 | } 337 | } 338 | // swiftlint:enable line_length large_tuple force_cast 339 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## ⭐️ Do you want to become a certified iOS Application Security Engineer? ⭐️ 2 | 3 | Check out our practical & fully online course at: [https://courses.securing.pl/courses/iase](https://courses.securing.pl/courses/iase?utm_source=githubiosss&utm_medium=githubiosss&utm_campaign=githubiosss&utm_id=githubiosss) 4 | 5 | ![iASE logo](./iase_bg.png) 6 | 7 | 8 | ## ISS Description 9 | 10 | ![ISS logo](./logo.png) 11 | ### by [@_r3ggi](https://twitter.com/_r3ggi) 12 | 13 | 🌏 iOS Security Suite is an advanced and easy-to-use platform security & anti-tampering library written in pure Swift! If you are developing for iOS and you want to protect your app according to the OWASP [MASVS](https://github.com/OWASP/owasp-masvs) standard, chapter v8, then this library could save you a lot of time. 🚀 14 | 15 | What ISS detects: 16 | 17 | * Jailbreak 🧨 18 | * Attached debugger 👨🏻‍🚀 19 | * If an app was run in an emulator 👽 20 | * Common reverse engineering tools running on the device 🔭 21 | 22 | ## Setup 23 | There are 4 ways you can start using IOSSecuritySuite 24 | 25 | ### 1. Add source 26 | Add `IOSSecuritySuite/*.swift` files to your project 27 | 28 | ### 2. Setup with CocoaPods 29 | `pod 'IOSSecuritySuite'` 30 | 31 | ### 3. Setup with Carthage 32 | `github "securing/IOSSecuritySuite"` 33 | 34 | ### 4. Setup with Swift Package Manager 35 | ```swift 36 | .package(url: "https://github.com/securing/IOSSecuritySuite.git", from: "1.5.0") 37 | ``` 38 | 39 | ### Update Info.plist 40 | After adding ISS to your project, you will also need to update your main Info.plist. There is a check in jailbreak detection module that uses ```canOpenURL(_:)``` method and [requires](https://developer.apple.com/documentation/uikit/uiapplication/1622952-canopenurl) specifying URLs that will be queried. 41 | 42 | ```xml 43 | LSApplicationQueriesSchemes 44 | 45 | undecimus 46 | sileo 47 | zbra 48 | filza 49 | 50 | ``` 51 | 52 | ### Pricing 53 | 54 | Check our EULA license for the details. 55 | 56 | TLDR: 57 | If your company employs: 58 | * 0-99 people - **free to use** 59 | * 100-1000 - 3k EUR/year 60 | * 1000+ - 10k EUR/year 61 | 62 | If you want to sell a module that uses the iOS Security Suite (it is not used directly in your app) - 10k EUR/year 63 | 64 | ### Notice 65 | 66 | iOS Security Suite is meant to be used on iOS/iPadOS. It should not be used on Macs with Apple Silicon. 67 | 68 | ## How to use 69 | 70 | ### Jailbreak detector module 71 | 72 | * **The simplest method** returns True/False if you just want to know if the device is jailbroken or jailed 73 | 74 | ```Swift 75 | if IOSSecuritySuite.amIJailbroken() { 76 | print("This device is jailbroken") 77 | } else { 78 | print("This device is not jailbroken") 79 | } 80 | ``` 81 | 82 | * **Verbose**, if you also want to know what indicators were identified 83 | 84 | ```Swift 85 | let jailbreakStatus = IOSSecuritySuite.amIJailbrokenWithFailMessage() 86 | if jailbreakStatus.jailbroken { 87 | print("This device is jailbroken") 88 | print("Because: \(jailbreakStatus.failMessage)") 89 | } else { 90 | print("This device is not jailbroken") 91 | } 92 | ``` 93 | The failMessage is a String containing comma-separated indicators as shown on the example below: 94 | `sileo:// URL scheme detected, Suspicious file exists: /Library/MobileSubstrate/MobileSubstrate.dylib, Fork was able to create a new process` 95 | 96 | * **Verbose & filterable**, if you also want to for example identify devices that were jailbroken in the past, but now are jailed 97 | 98 | ```Swift 99 | let jailbreakStatus = IOSSecuritySuite.amIJailbrokenWithFailedChecks() 100 | if jailbreakStatus.jailbroken { 101 | if (jailbreakStatus.failedChecks.contains { $0.check == .existenceOfSuspiciousFiles }) && (jailbreakStatus.failedChecks.contains { $0.check == .suspiciousFilesCanBeOpened }) { 102 | print("This is real jailbroken device") 103 | } 104 | } 105 | ``` 106 | 107 | ### Debugger detector module 108 | ```Swift 109 | let amIDebugged: Bool = IOSSecuritySuite.amIDebugged() 110 | ``` 111 | 112 | ### Deny debugger at all 113 | ```Swift 114 | IOSSecuritySuite.denyDebugger() 115 | ``` 116 | 117 | ### Emulator detector module 118 | ```Swift 119 | let runInEmulator: Bool = IOSSecuritySuite.amIRunInEmulator() 120 | ``` 121 | 122 | ### Reverse engineering tools detector module 123 | 124 | * **The simplest method** returns True/False if you just want to know if the device has evidence of reverse engineering 125 | 126 | ```Swift 127 | if IOSSecuritySuite.amIReverseEngineered() { 128 | print("This device has evidence of reverse engineering") 129 | } else { 130 | print("This device hasn't evidence of reverse engineering") 131 | } 132 | ``` 133 | 134 | * **Verbose & filterable**, if you also want the list of checks done 135 | 136 | ```Swift 137 | let reverseStatus = IOSSecuritySuite.amIReverseEngineeredWithFailedChecks() 138 | if reverseStatus.reverseEngineered { 139 | // check for reverseStatus.failedChecks for more details 140 | } 141 | ``` 142 | 143 | ### System proxy detector module 144 | 145 | Now you can also detect if an app is connected to VPN 146 | ```Swift 147 | let amIProxied: Bool = IOSSecuritySuite.amIProxied(considerVPNConnectionAsProxy: true) 148 | ``` 149 | 150 | ### Lockdown mode detector module 151 | ```Swift 152 | let amIInLockdownMode: Bool = IOSSecuritySuite.amIInLockdownMode() 153 | ``` 154 | 155 | ## Experimental features 156 | 157 | ### Runtime hook detector module 158 | ```Swift 159 | let amIRuntimeHooked: Bool = amIRuntimeHook(dyldWhiteList: dylds, detectionClass: SomeClass.self, selector: #selector(SomeClass.someFunction), isClassMethod: false) 160 | ``` 161 | ### Symbol hook deny module 162 | ```Swift 163 | // If we want to deny symbol hook of Swift function, we have to pass mangled name of that function 164 | denySymbolHook("$s10Foundation5NSLogyySS_s7CVarArg_pdtF") // denying hooking for the NSLog function 165 | NSLog("Hello Symbol Hook") 166 | 167 | denySymbolHook("abort") 168 | abort() 169 | ``` 170 | 171 | ### MSHook detector module 172 | ```Swift 173 | // Function declaration 174 | func someFunction(takes: Int) -> Bool { 175 | return false 176 | } 177 | 178 | // Defining FunctionType : @convention(thin) indicates a “thin” function reference, which uses the Swift calling convention with no special “self” or “context” parameters. 179 | typealias FunctionType = @convention(thin) (Int) -> (Bool) 180 | 181 | // Getting pointer address of function we want to verify 182 | func getSwiftFunctionAddr(_ function: @escaping FunctionType) -> UnsafeMutableRawPointer { 183 | return unsafeBitCast(function, to: UnsafeMutableRawPointer.self) 184 | } 185 | 186 | let funcAddr = getSwiftFunctionAddr(someFunction) 187 | let amIMSHooked = IOSSecuritySuite.amIMSHooked(funcAddr) 188 | ``` 189 | 190 | ### MSHook deny module 191 | ```Swift 192 | // Function declaration 193 | func denyDebugger(value: Int) { 194 | } 195 | 196 | // Defining FunctionType : @convention(thin) indicates a “thin” function reference, which uses the Swift calling convention with no special “self” or “context” parameters. 197 | typealias FunctionType = @convention(thin) (Int)->() 198 | 199 | // Getting original function address 200 | let funcDenyDebugger: FunctionType = denyDebugger 201 | let funcAddr = unsafeBitCast(funcDenyDebugger, to: UnsafeMutableRawPointer.self) 202 | 203 | 204 | if let originalDenyDebugger = denyMSHook(funcAddr) { 205 | // Call the original function with 1337 as Int argument 206 | unsafeBitCast(originalDenyDebugger, to: FunctionType.self)(1337) 207 | } else { 208 | denyDebugger() 209 | } 210 | ``` 211 | 212 | ### File integrity verifier module 213 | 214 | ```Swift 215 | // Determine if application has been tampered with 216 | if IOSSecuritySuite.amITampered([.bundleID("biz.securing.FrameworkClientApp"), 217 | .mobileProvision("2976c70b56e9ae1e2c8e8b231bf6b0cff12bbbd0a593f21846d9a004dd181be3"), 218 | .machO("IOSSecuritySuite", "6d8d460b9a4ee6c0f378e30f137cebaf2ce12bf31a2eef3729c36889158aa7fc")]).result { 219 | print("I have been Tampered.") 220 | } 221 | else { 222 | print("I have not been Tampered.") 223 | } 224 | 225 | // Manually verify SHA256 hash value of a loaded dylib 226 | if let hashValue = IOSSecuritySuite.getMachOFileHashValue(.custom("IOSSecuritySuite")), hashValue == "6d8d460b9a4ee6c0f378e30f137cebaf2ce12bf31a2eef3729c36889158aa7fc" { 227 | print("I have not been Tampered.") 228 | } 229 | else { 230 | print("I have been Tampered.") 231 | } 232 | 233 | // Check SHA256 hash value of the main executable 234 | // Tip: Your application may retrieve this value from the server 235 | if let hashValue = IOSSecuritySuite.getMachOFileHashValue(.default), hashValue == "your-application-executable-hash-value" { 236 | print("I have not been Tampered.") 237 | } 238 | else { 239 | print("I have been Tampered.") 240 | } 241 | ``` 242 | 243 | ### Breakpoint detection module 244 | 245 | ```Swift 246 | func denyDebugger() { 247 | // Set breakpoint here 248 | } 249 | 250 | typealias FunctionType = @convention(thin) ()->() 251 | let func_denyDebugger: FunctionType = denyDebugger // `: FunctionType` is a must 252 | let func_addr = unsafeBitCast(func_denyDebugger, to: UnsafeMutableRawPointer.self) 253 | let hasBreakpoint = IOSSecuritySuite.hasBreakpointAt(func_addr, functionSize: nil) 254 | 255 | if hasBreakpoint { 256 | print("Breakpoint found in the specified function") 257 | } else { 258 | print("Breakpoint not found in the specified function") 259 | } 260 | ``` 261 | 262 | ### Watchpoint detection module 263 | 264 | ```Swift 265 | // Set a breakpoint at the testWatchpoint function 266 | func testWatchpoint() -> Bool{ 267 | // lldb: watchpoint set expression ptr 268 | var ptr = malloc(9) 269 | // lldb: watchpoint set variable count 270 | var count = 3 271 | return IOSSecuritySuite.hasWatchpoint() 272 | } 273 | ``` 274 | 275 | 276 | 277 | ## Security considerations 278 | 279 | Before using this and other platform security checkers, you have to understand that: 280 | 281 | * Including this tool in your project is not the only thing you should do in order to improve your app security! You can read a general mobile security whitepaper [here](https://www.securing.biz/en/mobile-application-security-best-practices/index.html). 282 | * Detecting if a device is jailbroken is done locally on the device. It means that every jailbreak detector may be bypassed (even this)! 283 | * Swift code is considered to be harder to manipulate dynamically than Objective-C. Since this library was written in pure Swift, the IOSSecuritySuite methods shouldn't be exposed to Objective-C runtime (which makes it more difficult to bypass ✅). You have to know that attacker is still able to MSHookFunction/MSFindSymbol Swift symbols and dynamically change Swift code execution flow. 284 | 285 | ## Contribution ❤️ 286 | Yes, please! If you have a better idea or you just want to improve this project, please text me on [Twitter](https://twitter.com/_r3ggi) or [LinkedIn](https://www.linkedin.com/in/wojciech-regula/). Pull requests are more than welcome! 287 | 288 | ### Special thanks: 👏🏻 289 | 290 | * [kubajakowski](https://github.com/kubajakowski) for pointing out the problem with ```canOpenURL(_:)``` method 291 | * [olbartek](https://github.com/olbartek) for code review and pull request 292 | * [benbahrenburg](https://github.com/benbahrenburg) for various ISS improvements 293 | * [fotiDim](https://github.com/fotiDim) for adding new file paths to check 294 | * [gcharita](https://github.com/gcharita) for adding the Swift Package Manager support 295 | * [rynaardb](https://github.com/rynaardb) for creating the `amIJailbrokenWithFailedChecks()` method 296 | * [undeaDD](https://github.com/undeaDD) for various ISS improvements 297 | * [fnxpt](https://github.com/fnxpt) for adding multiple JB detections 298 | * [TannerJin](https://github.com/TannerJin) for MSHook, RuntimeHook, SymbolHook and Watchpoint Detection modules 299 | * [NikoXu](https://github.com/NikoXu) for adding file integrity module 300 | * [hellpf](https://github.com/hellpf) for fixing a dangling socket problem 301 | * [Ant-tree](https://github.com/Ant-tree) for improving hooking resistence 302 | * [izmcm](https://github.com/izmcm) for implementing the `amIReverseEngineeredWithFailedChecks()` method 303 | * [sanu](https://github.com/sanu) for new providing new file checks 304 | * [marsepu](https://github.com/marsepu) for a well-done PR with new improvements 305 | * [mkj-is](https://github.com/mkj-is) for a PR improving ISS performance 🚄 306 | * [LongXiangGuo](https://github.com/LongXiangGuo) for a PR adding the privacy manifest 307 | 308 | 309 | ## TODO 310 | 311 | * [ ] Research Installer5 and Zebra Package Manager detection ( Cydia Alternatives ) 312 | 313 | ## License 314 | See the LICENSE file. 315 | 316 | ## References 317 | While creating this tool I used: 318 | 319 | * 🔗 https://github.com/TheSwiftyCoder/JailBreak-Detection 320 | * 🔗 https://github.com/abhinashjain/jailbreakdetection 321 | * 🔗 https://gist.github.com/ddrccw/8412847 322 | * 🔗 https://gist.github.com/bugaevc/4307eaf045e4b4264d8e395b5878a63b 323 | * 📚 "iOS Application Security" by David Thiel 324 | -------------------------------------------------------------------------------- /IOSSecuritySuite/JailbreakChecker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JailbreakChecker.swift 3 | // IOSSecuritySuite 4 | // 5 | // Created by wregula on 23/04/2019. 6 | // Copyright © 2019 wregula. All rights reserved. 7 | // 8 | // swiftlint:disable function_body_length type_body_length line_length 9 | 10 | import Foundation 11 | import UIKit 12 | import Darwin // fork 13 | import MachO // dyld 14 | import ObjectiveC // NSObject and Selector 15 | 16 | internal class JailbreakChecker { 17 | typealias CheckResult = (passed: Bool, failMessage: String) 18 | 19 | struct JailbreakStatus { 20 | let passed: Bool 21 | let failMessage: String // Added for backwards compatibility 22 | let failedChecks: [FailedCheckType] 23 | } 24 | 25 | static func amIJailbroken() -> Bool { 26 | return !performChecks().passed 27 | } 28 | 29 | static func amIJailbrokenWithFailMessage() -> (jailbroken: Bool, failMessage: String) { 30 | let status = performChecks() 31 | return (!status.passed, status.failMessage) 32 | } 33 | 34 | static func amIJailbrokenWithFailedChecks() -> (jailbroken: Bool, 35 | failedChecks: [FailedCheckType]) { 36 | let status = performChecks() 37 | return (!status.passed, status.failedChecks) 38 | } 39 | 40 | private static func performChecks() -> JailbreakStatus { 41 | var passed = true 42 | var failMessage = "" 43 | var failedChecks: [FailedCheckType] = [] 44 | 45 | for check in FailedCheck.allCases { 46 | let result = getResult(from: check) 47 | 48 | passed = passed && result.passed 49 | 50 | if !result.passed { 51 | failedChecks.append((check: check, failMessage: result.failMessage)) 52 | 53 | if !failMessage.isEmpty { 54 | failMessage += ", " 55 | } 56 | } 57 | 58 | failMessage += result.failMessage 59 | } 60 | 61 | return JailbreakStatus(passed: passed, failMessage: failMessage, failedChecks: failedChecks) 62 | 63 | func getResult(from check: FailedCheck) -> CheckResult { 64 | switch check { 65 | case .urlSchemes: 66 | return checkURLSchemes() 67 | case .existenceOfSuspiciousFiles: 68 | return checkExistenceOfSuspiciousFiles() 69 | case .suspiciousFilesCanBeOpened: 70 | return checkSuspiciousFilesCanBeOpened() 71 | case .restrictedDirectoriesWriteable: 72 | return checkRestrictedDirectoriesWriteable() 73 | case .fork: 74 | if !EmulatorChecker.amIRunInEmulator() { 75 | return checkFork() 76 | } else { 77 | print("App run in the emulator, skipping the fork check.") 78 | return (true, "") 79 | } 80 | case .symbolicLinks: 81 | return checkSymbolicLinks() 82 | case .dyld: 83 | return checkDYLD() 84 | case .suspiciousObjCClasses: 85 | return checkSuspiciousObjCClasses() 86 | default: 87 | return (true, "") 88 | } 89 | } 90 | } 91 | 92 | private static func canOpenUrlFromList(urlSchemes: [String]) -> CheckResult { 93 | for urlScheme in urlSchemes { 94 | if let url = URL(string: urlScheme) { 95 | if UIApplication.shared.canOpenURL(url) { 96 | return(false, "\(urlScheme) URL scheme detected") 97 | } 98 | } 99 | } 100 | return (true, "") 101 | } 102 | 103 | // "cydia://" URL scheme has been removed. Turns out there is app in the official App Store 104 | // that has the cydia:// URL scheme registered, so it may cause false positive 105 | // "activator://" URL scheme has been removed for the same reason. 106 | private static func checkURLSchemes() -> CheckResult { 107 | let urlSchemes = [ 108 | "undecimus://", 109 | "sileo://", 110 | "zbra://", 111 | "filza://" 112 | ] 113 | return canOpenUrlFromList(urlSchemes: urlSchemes) 114 | } 115 | 116 | private static func checkExistenceOfSuspiciousFiles() -> CheckResult { 117 | var paths = [ 118 | "/var/mobile/Library/Preferences/ABPattern", // A-Bypass 119 | "/usr/lib/ABDYLD.dylib", // A-Bypass, 120 | "/usr/lib/ABSubLoader.dylib", // A-Bypass 121 | "/usr/sbin/frida-server", // frida 122 | "/etc/apt/sources.list.d/electra.list", // electra 123 | "/etc/apt/sources.list.d/sileo.sources", // electra 124 | "/.bootstrapped_electra", // electra 125 | "/usr/lib/libjailbreak.dylib", // electra 126 | "/jb/lzma", // electra 127 | "/.cydia_no_stash", // unc0ver 128 | "/.installed_unc0ver", // unc0ver 129 | "/jb/offsets.plist", // unc0ver 130 | "/usr/share/jailbreak/injectme.plist", // unc0ver 131 | "/etc/apt/undecimus/undecimus.list", // unc0ver 132 | "/var/lib/dpkg/info/mobilesubstrate.md5sums", // unc0ver 133 | "/Library/MobileSubstrate/MobileSubstrate.dylib", 134 | "/jb/jailbreakd.plist", // unc0ver 135 | "/jb/amfid_payload.dylib", // unc0ver 136 | "/jb/libjailbreak.dylib", // unc0ver 137 | "/usr/libexec/cydia/firmware.sh", 138 | "/var/lib/cydia", 139 | "/etc/apt", 140 | "/private/var/lib/apt", 141 | "/var/log/apt", 142 | "/Applications/Cydia.app", 143 | "/private/var/stash", 144 | "/private/var/lib/apt/", 145 | "/private/var/lib/cydia", 146 | "/private/var/cache/apt/", 147 | "/private/var/log/syslog", 148 | "/private/var/tmp/cydia.log", 149 | "/Applications/Icy.app", 150 | "/Applications/MxTube.app", 151 | "/Applications/RockApp.app", 152 | "/Applications/blackra1n.app", 153 | "/Applications/SBSettings.app", 154 | "/Applications/FakeCarrier.app", 155 | "/Applications/WinterBoard.app", 156 | "/Applications/IntelliScreen.app", 157 | "/private/var/mobile/Library/SBSettings/Themes", 158 | "/Library/MobileSubstrate/CydiaSubstrate.dylib", 159 | "/System/Library/LaunchDaemons/com.ikey.bbot.plist", 160 | "/Library/MobileSubstrate/DynamicLibraries/Veency.plist", 161 | "/Library/MobileSubstrate/DynamicLibraries/LiveClock.plist", 162 | "/System/Library/LaunchDaemons/com.saurik.Cydia.Startup.plist", 163 | "/Applications/Sileo.app", 164 | "/var/binpack", 165 | "/Library/PreferenceBundles/LibertyPref.bundle", 166 | "/Library/PreferenceBundles/ShadowPreferences.bundle", 167 | "/Library/PreferenceBundles/ABypassPrefs.bundle", 168 | "/Library/PreferenceBundles/FlyJBPrefs.bundle", 169 | "/Library/PreferenceBundles/Cephei.bundle", 170 | "/Library/PreferenceBundles/SubstitutePrefs.bundle", 171 | "/Library/PreferenceBundles/libhbangprefs.bundle", 172 | "/usr/lib/libhooker.dylib", 173 | "/usr/lib/libsubstitute.dylib", 174 | "/usr/lib/substrate", 175 | "/usr/lib/TweakInject", 176 | "/var/binpack/Applications/loader.app", // checkra1n 177 | "/Applications/FlyJB.app", // Fly JB X 178 | "/Applications/Zebra.app", // Zebra 179 | "/Library/BawAppie/ABypass", // ABypass 180 | "/Library/MobileSubstrate/DynamicLibraries/SSLKillSwitch2.plist", // SSL Killswitch 181 | "/Library/MobileSubstrate/DynamicLibraries/PreferenceLoader.plist", // PreferenceLoader 182 | "/Library/MobileSubstrate/DynamicLibraries/PreferenceLoader.dylib", // PreferenceLoader 183 | "/Library/MobileSubstrate/DynamicLibraries", // DynamicLibraries directory in general 184 | "/var/mobile/Library/Preferences/me.jjolano.shadow.plist" 185 | ] 186 | 187 | // These files can give false positive in the emulator 188 | if !EmulatorChecker.amIRunInEmulator() { 189 | paths += [ 190 | "/bin/bash", 191 | "/usr/sbin/sshd", 192 | "/usr/libexec/ssh-keysign", 193 | "/bin/sh", 194 | "/etc/ssh/sshd_config", 195 | "/usr/libexec/sftp-server", 196 | "/usr/bin/ssh" 197 | ] 198 | } 199 | 200 | for path in paths { 201 | if FileManager.default.fileExists(atPath: path) { 202 | return (false, "Suspicious file exists: \(path)") 203 | } else if let result = FileChecker.checkExistenceOfSuspiciousFilesViaStat(path: path) { 204 | return result 205 | } else if let result = FileChecker.checkExistenceOfSuspiciousFilesViaFOpen( 206 | path: path, 207 | mode: .readable 208 | ) { 209 | return result 210 | } else if let result = FileChecker.checkExistenceOfSuspiciousFilesViaAccess( 211 | path: path, 212 | mode: .readable 213 | ) { 214 | return result 215 | } 216 | } 217 | 218 | return (true, "") 219 | } 220 | 221 | private static func checkSuspiciousFilesCanBeOpened() -> CheckResult { 222 | var paths = [ 223 | "/.installed_unc0ver", 224 | "/.bootstrapped_electra", 225 | "/Applications/Cydia.app", 226 | "/Library/MobileSubstrate/MobileSubstrate.dylib", 227 | "/etc/apt", 228 | "/var/log/apt" 229 | ] 230 | 231 | // These files can give false positive in the emulator 232 | if !EmulatorChecker.amIRunInEmulator() { 233 | paths += [ 234 | "/bin/bash", 235 | "/usr/sbin/sshd", 236 | "/usr/bin/ssh" 237 | ] 238 | } 239 | 240 | for path in paths { 241 | if FileManager.default.isReadableFile(atPath: path) { 242 | return (false, "Suspicious file can be opened: \(path)") 243 | } else if let result = FileChecker.checkExistenceOfSuspiciousFilesViaFOpen( 244 | path: path, 245 | mode: .writable 246 | ) { 247 | return result 248 | } else if let result = FileChecker.checkExistenceOfSuspiciousFilesViaAccess( 249 | path: path, 250 | mode: .writable 251 | ) { 252 | return result 253 | } 254 | } 255 | 256 | return (true, "") 257 | } 258 | 259 | private static func checkRestrictedDirectoriesWriteable() -> CheckResult { 260 | let paths = [ 261 | "/", 262 | "/root/", 263 | "/private/", 264 | "/jb/" 265 | ] 266 | 267 | if FileChecker.checkRestrictedPathIsReadonlyViaStatvfs(path: "/") == false { 268 | return (false, "Restricted path '/' is not Read-Only") 269 | } else if FileChecker.checkRestrictedPathIsReadonlyViaStatfs(path: "/") == false { 270 | return (false, "Restricted path '/' is not Read-Only") 271 | } else if FileChecker.checkRestrictedPathIsReadonlyViaGetfsstat(name: "/") == false { 272 | return (false, "Restricted path '/' is not Read-Only") 273 | } 274 | 275 | // If library won't be able to write to any restricted directory the return(false, ...) is never reached 276 | // because of catch{} statement 277 | for path in paths { 278 | do { 279 | let pathWithSomeRandom = path + UUID().uuidString 280 | try "AmIJailbroken?".write( 281 | toFile: pathWithSomeRandom, 282 | atomically: true, 283 | encoding: String.Encoding.utf8 284 | ) 285 | // clean if successfully written 286 | try FileManager.default.removeItem(atPath: pathWithSomeRandom) 287 | return (false, "Wrote to restricted path: \(path)") 288 | } catch {} 289 | } 290 | 291 | return (true, "") 292 | } 293 | 294 | private static func checkFork() -> CheckResult { 295 | let pointerToFork = UnsafeMutableRawPointer(bitPattern: -2) 296 | let forkPtr = dlsym(pointerToFork, "fork") 297 | typealias ForkType = @convention(c) () -> pid_t 298 | let fork = unsafeBitCast(forkPtr, to: ForkType.self) 299 | let forkResult = fork() 300 | 301 | if forkResult >= 0 { 302 | if forkResult > 0 { 303 | kill(forkResult, SIGTERM) 304 | } 305 | return (false, "Fork was able to create a new process (sandbox violation)") 306 | } 307 | 308 | return (true, "") 309 | } 310 | 311 | private static func checkSymbolicLinks() -> CheckResult { 312 | let paths = [ 313 | "/var/lib/undecimus/apt", // unc0ver 314 | "/Applications", 315 | "/Library/Ringtones", 316 | "/Library/Wallpaper", 317 | "/usr/arm-apple-darwin9", 318 | "/usr/include", 319 | "/usr/libexec", 320 | "/usr/share" 321 | ] 322 | 323 | for path in paths { 324 | do { 325 | let result = try FileManager.default.destinationOfSymbolicLink(atPath: path) 326 | if !result.isEmpty { 327 | return (false, "Non standard symbolic link detected: \(path) points to \(result)") 328 | } 329 | } catch {} 330 | } 331 | 332 | return (true, "") 333 | } 334 | 335 | private static func checkDYLD() -> CheckResult { 336 | let suspiciousLibraries: Set = [ 337 | "systemhook.dylib", // Dopamine - hide jailbreak detection https://github.com/opa334/Dopamine/blob/dc1a1a3486bb5d74b8f2ea6ada782acdc2f34d0a/Application/Dopamine/Jailbreak/DOEnvironmentManager.m#L498 338 | "roothideinit.dylib", 339 | "SubstrateLoader.dylib", 340 | "SSLKillSwitch2.dylib", 341 | "SSLKillSwitch.dylib", 342 | "MobileSubstrate.dylib", 343 | "TweakInject.dylib", 344 | "CydiaSubstrate", 345 | "cynject", 346 | "CustomWidgetIcons", 347 | "PreferenceLoader", 348 | "RocketBootstrap", 349 | "WeeLoader", 350 | "/.file", // HideJB (2.1.1) changes full paths of the suspicious libraries to "/.file" 351 | "libhooker", 352 | "SubstrateInserter", 353 | "SubstrateBootstrap", 354 | "ABypass", 355 | "FlyJB", 356 | "Substitute", 357 | "Cephei", 358 | "Electra", 359 | "AppSyncUnified-FrontBoard.dylib", 360 | "Shadow", 361 | "FridaGadget", 362 | "frida", 363 | "libcycript" 364 | ] 365 | 366 | for index in 0..<_dyld_image_count() { 367 | let imageName = String(cString: _dyld_get_image_name(index)) 368 | 369 | // The fastest case insensitive contains check. 370 | for library in suspiciousLibraries where imageName.localizedCaseInsensitiveContains(library) { 371 | return (false, "Suspicious library loaded: \(imageName)") 372 | } 373 | } 374 | 375 | return (true, "") 376 | } 377 | 378 | private static func checkSuspiciousObjCClasses() -> CheckResult { 379 | if let shadowRulesetClass = objc_getClass("ShadowRuleset") as? NSObject.Type { 380 | let selector = Selector(("internalDictionary")) 381 | if class_getInstanceMethod(shadowRulesetClass, selector) != nil { 382 | return (false, "Shadow anti-anti-jailbreak detector detected :-)") 383 | } 384 | } 385 | return (true, "") 386 | } 387 | } 388 | // swiftlint:enable function_body_length type_body_length 389 | -------------------------------------------------------------------------------- /IOSSecuritySuite/IOSSecuritySuite.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IOSSecuritySuite.swift 3 | // IOSSecuritySuite 4 | // 5 | // Created by wregula on 23/04/2019. 6 | // Copyright © 2019 wregula. All rights reserved. 7 | // 8 | // swiftlint:disable inclusive_language 9 | 10 | import Foundation 11 | import MachO 12 | 13 | /// Main class that encompasses library functionalities 14 | @available(iOSApplicationExtension, unavailable) 15 | public class IOSSecuritySuite { 16 | /// This type method is used to determine the true/false jailbreak status 17 | /// 18 | /// Usage example 19 | /// ```swift 20 | /// let isDeviceJailbroken: Bool = IOSSecuritySuite.amIJailbroken() 21 | /// ``` 22 | /// 23 | /// - Returns: Bool indicating if the device has jailbreak (true) or not (false) 24 | public static func amIJailbroken() -> Bool { 25 | return JailbreakChecker.amIJailbroken() 26 | } 27 | 28 | /// This type method is used to determine the jailbreak status with a message which jailbreak indicator was detected 29 | /// 30 | /// Usage example 31 | /// ```swift 32 | /// let jailbreakStatus = IOSSecuritySuite.amIJailbrokenWithFailMessage() 33 | /// if jailbreakStatus.jailbroken { 34 | /// print("This device is jailbroken") 35 | /// print("Because: \(jailbreakStatus.failMessage)") 36 | /// } else { 37 | /// print("This device is not jailbroken") 38 | /// } 39 | /// ``` 40 | /// 41 | /// - Returns: Tuple with with the jailbreak status (Bool) and failMessage (String) 42 | public static func amIJailbrokenWithFailMessage() -> (jailbroken: Bool, failMessage: String) { 43 | return JailbreakChecker.amIJailbrokenWithFailMessage() 44 | } 45 | 46 | /// This type method is used to determine the jailbreak status with a list of failed checks 47 | /// 48 | /// Usage example 49 | /// ```swift 50 | /// let jailbreakStatus = IOSSecuritySuite.amIJailbrokenWithFailedChecks() 51 | /// if jailbreakStatus.jailbroken { 52 | /// print("This device is jailbroken") 53 | /// print("The following checks failed: \(jailbreakStatus.failedChecks)") 54 | /// } 55 | /// ``` 56 | /// 57 | /// - Returns: Tuple with with the jailbreak status (Bool) and a list of ``FailedCheckType`` 58 | public static func amIJailbrokenWithFailedChecks() -> (jailbroken: Bool, 59 | failedChecks: [FailedCheckType]) { 60 | return JailbreakChecker.amIJailbrokenWithFailedChecks() 61 | } 62 | 63 | /// This type method is used to determine if application is run in emulator 64 | /// 65 | /// Usage example 66 | /// ```swift 67 | /// let runInEmulator: Bool = IOSSecuritySuite.amIRunInEmulator() 68 | /// ``` 69 | /// - Returns: Bool indicating if the device is an emulator (true) or not (false) 70 | public static func amIRunInEmulator() -> Bool { 71 | return EmulatorChecker.amIRunInEmulator() 72 | } 73 | 74 | /// This type method is used to determine if application is being debugged 75 | /// 76 | /// Usage example 77 | /// ```swift 78 | /// let amIDebugged: Bool = IOSSecuritySuite.amIDebugged() 79 | /// ``` 80 | /// - Returns: Bool indicating if the device is being debugged (true) or not (false) 81 | public static func amIDebugged() -> Bool { 82 | return DebuggerChecker.amIDebugged() 83 | } 84 | 85 | /// This type method is used to deny debugger and improve the application resiliency 86 | /// 87 | /// Usage example 88 | /// ```swift 89 | /// IOSSecuritySuite.denyDebugger() 90 | /// ``` 91 | public static func denyDebugger() { 92 | return DebuggerChecker.denyDebugger() 93 | } 94 | 95 | /// This method is used to determine if application was launched by something 96 | /// other than LaunchD (i.e. the app was launched by a debugger) 97 | /// 98 | /// Usage example 99 | /// ```swift 100 | /// let isNotLaunchD: Bool = IOSSecuritySuite.isParentPidUnexpected() 101 | /// ``` 102 | /// - Returns: Bool indicating if application was launched by something other than LaunchD (true) or not (false) 103 | public static func isParentPidUnexpected() -> Bool { 104 | return DebuggerChecker.isParentPidUnexpected() 105 | } 106 | 107 | /// This type method is used to determine if application has been tampered with 108 | /// 109 | /// Usage example 110 | /// ```swift 111 | /// if IOSSecuritySuite.amITampered( 112 | /// [.bundleID("biz.securing.FrameworkClientApp"), 113 | /// .mobileProvision("your-mobile-provision-sha256-value")] 114 | /// ).result { 115 | /// print("I have been Tampered.") 116 | /// } else { 117 | /// print("I have not been Tampered.") 118 | /// } 119 | /// ``` 120 | /// 121 | /// - Parameter checks: The file Integrity checks you want 122 | /// - Returns: The file Integrity checker result 123 | public static func amITampered(_ checks: [FileIntegrityCheck]) -> FileIntegrityCheckResult { 124 | return IntegrityChecker.amITampered(checks) 125 | } 126 | 127 | /// This type method is used to determine if there are any popular reverse engineering tools installed on the device 128 | /// 129 | /// Usage example 130 | /// ```swift 131 | /// let amIReverseEngineered: Bool = IOSSecuritySuite.amIReverseEngineered() 132 | /// ``` 133 | /// - Returns: Bool indicating if device has reverse engineering tools (true) or not (false) 134 | public static func amIReverseEngineered() -> Bool { 135 | return ReverseEngineeringToolsChecker.amIReverseEngineered() 136 | } 137 | 138 | /// This type method is used to determine the reverse engineered status with a list of failed checks 139 | /// 140 | /// Usage example 141 | /// ```swift 142 | /// let reStatus = IOSSecuritySuite.amIReverseEngineeredWithFailedChecks() 143 | /// if reStatus.reverseEngineered { 144 | /// print("This device has evidence of reverse engineering") 145 | /// print("The following checks failed: \(reStatus.failedChecks)") 146 | /// } 147 | /// ``` 148 | /// 149 | /// - Returns: Tuple with with the reverse engineered status (Bool) and a list of ``FailedCheckType`` 150 | public static func amIReverseEngineeredWithFailedChecks() -> (reverseEngineered: Bool, 151 | failedChecks: [FailedCheckType]) { 152 | return ReverseEngineeringToolsChecker.amIReverseEngineeredWithFailedChecks() 153 | } 154 | 155 | /// This type method is used to determine if `objc call` has been RuntimeHooked by for example `Flex` 156 | /// 157 | /// Usage example 158 | /// ```swift 159 | /// class SomeClass { 160 | /// @objc dynamic func someFunction() { ... } 161 | /// } 162 | /// 163 | /// let dylds = ["IOSSecuritySuite", ...] 164 | /// 165 | /// let amIRuntimeHook: Bool = amIRuntimeHook( 166 | /// dyldWhiteList: dylds, 167 | /// detectionClass: SomeClass.self, 168 | /// selector: #selector(SomeClass.someFunction), 169 | /// isClassMethod: false 170 | /// ) 171 | /// ``` 172 | /// 173 | /// - Returns: Bool indicating if the method is being hooked (true) or not (false) 174 | @available( 175 | *, deprecated, 176 | renamed: "amIRuntimeHooked(dyldAllowList:detectionClass:selector:isClassMethod:)" 177 | ) 178 | public static func amIRuntimeHooked( 179 | dyldWhiteList: [String], 180 | detectionClass: AnyClass, 181 | selector: Selector, 182 | isClassMethod: Bool 183 | ) -> Bool { 184 | return RuntimeHookChecker.amIRuntimeHook( 185 | dyldAllowList: dyldWhiteList, 186 | detectionClass: detectionClass, 187 | selector: selector, 188 | isClassMethod: isClassMethod 189 | ) 190 | } 191 | 192 | /// This type method is used to determine if `objc call` has been RuntimeHooked by for example `Flex` 193 | /// 194 | /// Usage example 195 | /// ```swift 196 | /// class SomeClass { 197 | /// @objc dynamic func someFunction() { ... } 198 | /// } 199 | /// 200 | /// let dylds = ["IOSSecuritySuite", ...] 201 | /// 202 | /// let amIRuntimeHook: Bool = amIRuntimeHook( 203 | /// dyldAllowList: dylds, 204 | /// detectionClass: SomeClass.self, 205 | /// selector: #selector(SomeClass.someFunction), 206 | /// isClassMethod: false 207 | /// ) 208 | /// ``` 209 | /// 210 | /// - Returns: Bool indicating if the method is being hooked (true) or not (false) 211 | public static func amIRuntimeHooked( 212 | dyldAllowList: [String], 213 | detectionClass: AnyClass, 214 | selector: Selector, 215 | isClassMethod: Bool 216 | ) -> Bool { 217 | return RuntimeHookChecker.amIRuntimeHook( 218 | dyldAllowList: dyldAllowList, 219 | detectionClass: detectionClass, 220 | selector: selector, 221 | isClassMethod: isClassMethod 222 | ) 223 | } 224 | 225 | /// This type method is used to determine if HTTP proxy was set in the iOS Settings. 226 | /// 227 | /// Usage example 228 | /// ```swift 229 | /// let amIProxied: Bool = IOSSecuritySuite.amIProxied() 230 | /// ``` 231 | /// - Returns: Bool indicating if the device has a proxy setted (true) or not (false) 232 | public static func amIProxied() -> Bool { 233 | return ProxyChecker.amIProxied() 234 | } 235 | 236 | /// This type method is used to determine if the iDevice has lockdown mode turned on. 237 | /// 238 | /// Usage example 239 | /// ```swift 240 | /// let amIInLockdownMode: Bool = IOSSecuritySuite.amIInLockdownMode() 241 | /// ``` 242 | /// - Returns: Bool indicating if the device has lockdown mode turned on (true) or not (false) 243 | @available(iOS 16, *) 244 | public static func amIInLockdownMode() -> Bool { 245 | return ModesChecker.amIInLockdownMode() 246 | } 247 | } 248 | 249 | #if arch(arm64) 250 | @available(iOSApplicationExtension, unavailable) 251 | public extension IOSSecuritySuite { 252 | /// This type method is used to determine if `function_address` has been hooked by `MSHook` 253 | /// 254 | /// Usage example 255 | /// ```swift 256 | /// func denyDebugger() { ... } 257 | /// 258 | /// typealias FunctionType = @convention(thin) ()->() 259 | /// 260 | /// let func_denyDebugger: FunctionType = denyDebugger // `: FunctionType` is must 261 | /// let func_addr = unsafeBitCast(func_denyDebugger, to: UnsafeMutableRawPointer.self) 262 | /// let amIMSHookFunction: Bool = amIMSHookFunction(func_addr) 263 | /// ``` 264 | /// - Returns: Bool indicating if the function has been hooked (true) or not (false) 265 | static func amIMSHooked(_ functionAddress: UnsafeMutableRawPointer) -> Bool { 266 | return MSHookFunctionChecker.amIMSHooked(functionAddress) 267 | } 268 | 269 | /// This type method is used to get original `function_address` which has been hooked by `MSHook` 270 | /// 271 | /// Usage example 272 | /// ```swift 273 | /// func denyDebugger(value: Int) { ... } 274 | /// 275 | /// typealias FunctionType = @convention(thin) (Int)->() 276 | /// 277 | /// let funcDenyDebugger: FunctionType = denyDebugger 278 | /// let funcAddr = unsafeBitCast(funcDenyDebugger, to: UnsafeMutableRawPointer.self) 279 | /// 280 | /// if let originalDenyDebugger = denyMSHook(funcAddr) { 281 | /// // Call original function with 1337 as Int argument 282 | /// unsafeBitCast(originalDenyDebugger, to: FunctionType.self)(1337) 283 | /// } else { 284 | /// denyDebugger() 285 | /// } 286 | /// ``` 287 | static func denyMSHook(_ functionAddress: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer? { 288 | return MSHookFunctionChecker.denyMSHook(functionAddress) 289 | } 290 | 291 | /// This type method is used to rebind `symbol` which has been hooked by `fishhook` 292 | /// 293 | /// Usage example 294 | /// ```swift 295 | /// denySymbolHook("$s10Foundation5NSLogyySS_s7CVarArg_pdtF") // Foundation's NSlog of Swift 296 | /// NSLog("Hello Symbol Hook") 297 | /// 298 | /// denySymbolHook("abort") 299 | /// abort() 300 | /// ``` 301 | static func denySymbolHook(_ symbol: String) { 302 | FishHookChecker.denyFishHook(symbol) 303 | } 304 | 305 | /// This type method is used to rebind `symbol` which has been hooked at one of image by `fishhook` 306 | /// 307 | /// Usage example 308 | /// ``` 309 | /// for i in 0..<_dyld_image_count() { 310 | /// if let imageName = _dyld_get_image_name(i) { 311 | /// let name = String(cString: imageName) 312 | /// if name.contains("IOSSecuritySuite"), let image = _dyld_get_image_header(i) { 313 | /// denySymbolHook("dlsym", at: image, imageSlide: _dyld_get_image_vmaddr_slide(i)) 314 | /// break 315 | /// } 316 | /// } 317 | /// } 318 | /// ``` 319 | static func denySymbolHook( 320 | _ symbol: String, 321 | at image: UnsafePointer, 322 | imageSlide slide: Int 323 | ) { 324 | FishHookChecker.denyFishHook(symbol, at: image, imageSlide: slide) 325 | } 326 | 327 | /// This type method is used to get the SHA256 hash value of the executable file in a specified image 328 | /// 329 | /// - Attention: **Dylib only.** This means you should set Mach-O type as `Dynamic Library` in your *Build Settings*. 330 | /// 331 | /// Calculate the hash value of the `__TEXT.__text` data of the specified image Mach-O file. 332 | /// 333 | /// Usage example 334 | /// ```swift 335 | /// // Manually verify SHA256 hash value of a loaded dylib 336 | /// if let hashValue = IOSSecuritySuite.getMachOFileHashValue(.custom("IOSSecuritySuite")), 337 | /// hashValue == "6d8d460b9a4ee6c0f378e30f137cebaf2ce12bf31a2eef3729c36889158aa7fc" { 338 | /// print("I have not been Tampered.") 339 | /// } else { 340 | /// print("I have been Tampered.") 341 | /// } 342 | /// ``` 343 | /// 344 | /// - Parameter target: The target image 345 | /// - Returns: A hash value of the executable file. 346 | static func getMachOFileHashValue(_ target: IntegrityCheckerImageTarget = .default) -> String? { 347 | return IntegrityChecker.getMachOFileHashValue(target) 348 | } 349 | 350 | /// This type method is used to find all loaded dylibs in the specified image 351 | /// 352 | /// - Attention: **Dylib only.** This means you should set Mach-O type as `Dynamic Library` in your /*Build Settings*. 353 | /// 354 | /// Usage example 355 | /// ```swift 356 | /// if let loadedDylib = IOSSecuritySuite.findLoadedDylibs() { 357 | /// print("Loaded dylibs: \(loadedDylib)") 358 | /// } 359 | /// ``` 360 | /// 361 | /// - Parameter target: The target image 362 | /// - Returns: An Array with all loaded dylib names 363 | static func findLoadedDylibs(_ target: IntegrityCheckerImageTarget = .default) -> [String]? { 364 | return IntegrityChecker.findLoadedDylibs(target) 365 | } 366 | 367 | /// This type method is used to determine if there are any breakpoints at the function 368 | /// 369 | /// Usage example 370 | /// ```swift 371 | /// func denyDebugger() { 372 | /// // add a breakpoint at here to test 373 | /// } 374 | /// 375 | /// typealias FunctionType = @convention(thin) ()->() 376 | /// 377 | /// let func_denyDebugger: FunctionType = denyDebugger // `: FunctionType` is a must 378 | /// let func_addr = unsafeBitCast(func_denyDebugger, to: UnsafeMutableRawPointer.self) 379 | /// let hasBreakpoint: Bool = IOSSecuritySuite.hasBreakpointAt(func_addr, functionSize: nil) 380 | /// ``` 381 | /// - Returns: Bool indicating if the function has a breakpoint (true) or not (false) 382 | static func hasBreakpointAt(_ functionAddr: UnsafeRawPointer, functionSize: vm_size_t?) -> Bool { 383 | return DebuggerChecker.hasBreakpointAt(functionAddr, functionSize: functionSize) 384 | } 385 | 386 | /// This type method is used to detect if a watchpoint is being used. 387 | /// A watchpoint is a type of breakpoint that 'watches' an area of memory associated with a data item. 388 | /// 389 | /// Usage example 390 | /// ```swift 391 | /// // Set a breakpoint at the testWatchpoint function 392 | /// func testWatchpoint() -> Bool{ 393 | /// // lldb: watchpoint set expression ptr 394 | /// var ptr = malloc(9) 395 | /// // lldb: watchpoint set variable count 396 | /// var count = 3 397 | /// return IOSSecuritySuite.hasWatchpoint() 398 | /// } 399 | /// ``` 400 | /// - Returns: Bool indicating if has a watchpoint setted (true) or not (false) 401 | static func hasWatchpoint() -> Bool { 402 | return DebuggerChecker.hasWatchpoint() 403 | } 404 | } 405 | #endif 406 | // swiftlint:enable inclusive_language 407 | -------------------------------------------------------------------------------- /IOSSecuritySuite/FishHookChecker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FishHookChecker.swift 3 | // IOSSecuritySuite 4 | // 5 | // Created by jintao on 2020/4/24. 6 | // Copyright © 2020 wregula. All rights reserved. 7 | // https://github.com/TannerJin/anti-fishhook 8 | 9 | // swiftlint:disable all 10 | 11 | import Foundation 12 | import MachO 13 | 14 | /* 15 | Lazy_Symbol_Ptr: 16 | 17 | call symbol2 18 | | 19 | | 20 | | stubs(TEXT) 21 | | *--------------* stub_symbol: 22 | | | stub_symbol1 | ldr x16 ptr (ptr = pointer of lazy_symbol_ptr) 23 | | | | br x16 24 | *---> stub_symbol2 | 25 | | ... | 26 | *--------------* 27 | 28 | 29 | lazy_symbol_ptr(DATA) stub_helper(TEXT) 30 | *--------------* *---------------------------* 31 | | ptr1 | | br dyld_stub_binder | <-------------------* 32 | | ptr2 ---------* | symbol_binder_code_1 | | 33 | | ptr3 | *-------------------> symbol_binder_code_2 | | 34 | | ... | | ... | | 35 | *--------------* *---------------------------* | 36 | | 37 | symbol_binder_code: | 38 | ldr w16, #8(.byte) | 39 | b br_dyld_stub_binder ---* 40 | .byte 41 | 42 | 43 | .byte of the symbol is offset from beginning of lazy_binding_info to beginning of symbol_info 44 | 45 | lazy_binding_info(LINKEDIT -> DYLD_INFO -> LazyBindingInfo) 46 | *-----------------* 47 | | symbol_info_1 | symbol_info: 48 | | symbol_info_2 | bind_opcode_done 49 | | symbol_info_3 | bind_opcode_set_segment_and_offset_uleb 50 | | ... | uleb128 51 | *-----------------* BIND_OPCODE_SET_DYLIB_ORDINAL_IMM 52 | BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM 53 | **SymbolName** 54 | bind_opcode_do_bind 55 | 56 | 57 | 58 | The `denyFishHook` will look for code of `symbol_binder_code` of the symbol, and then make `lazy_symbol_ptr` of the symbol pointee to it 59 | 60 | Non_Lazy_Symbol_Ptr: 61 | wait to do based on export_info and binding_info 62 | */ 63 | 64 | #if arch(arm64) 65 | @inline(__always) 66 | private func readUleb128(ptr: inout UnsafeMutablePointer, end: UnsafeMutablePointer) -> UInt64 { 67 | var result: UInt64 = 0 68 | var bit = 0 69 | var readNext = true 70 | 71 | repeat { 72 | if ptr == end { 73 | assert(false, "malformed uleb128") 74 | } 75 | let slice = UInt64(ptr.pointee & 0x7f) 76 | if bit > 63 { 77 | assert(false, "uleb128 too big for uint64") 78 | } else { 79 | result |= (slice << bit) 80 | bit += 7 81 | } 82 | readNext = ((ptr.pointee & 0x80) >> 7) == 1 83 | ptr += 1 84 | } while (readNext) 85 | return result 86 | } 87 | 88 | @inline(__always) 89 | private func readSleb128(ptr: inout UnsafeMutablePointer, end: UnsafeMutablePointer) -> Int64 { 90 | var result: Int64 = 0 91 | var bit: Int = 0 92 | var byte: UInt8 93 | 94 | repeat { 95 | if (ptr == end) { 96 | assert(false, "malformed sleb128") 97 | } 98 | byte = ptr.pointee 99 | result |= (((Int64)(byte & 0x7f)) << bit) 100 | bit += 7 101 | ptr += 1 102 | } while (byte & 0x80) == 1 103 | 104 | // sign extend negative numbers 105 | if ( (byte & 0x40) != 0 ) { 106 | result |= -1 << bit 107 | } 108 | return result 109 | } 110 | 111 | internal class FishHookChecker { 112 | @inline(__always) 113 | static func denyFishHook(_ symbol: String) { 114 | for imgIndex in 0..<_dyld_image_count() { 115 | if let image = _dyld_get_image_header(imgIndex) { 116 | denyFishHook(symbol, at: image, imageSlide: _dyld_get_image_vmaddr_slide(imgIndex)) 117 | } 118 | } 119 | } 120 | 121 | @inline(__always) 122 | static func denyFishHook(_ symbol: String, at image: UnsafePointer, imageSlide slide: Int) { 123 | var symbolAddress: UnsafeMutableRawPointer? 124 | 125 | if SymbolFound.lookSymbol(symbol, at: image, imageSlide: slide, symbolAddress: &symbolAddress), let symbolPointer = symbolAddress { 126 | var oldMethod: UnsafeMutableRawPointer? 127 | FishHook.replaceSymbol(symbol, at: image, imageSlide: slide, newMethod: symbolPointer, oldMethod: &oldMethod) 128 | } 129 | } 130 | } 131 | 132 | // MARK: - SymbolFound 133 | internal class SymbolFound { 134 | static private let BindTypeThreadedRebase = 102 135 | 136 | @inline(__always) 137 | static func lookSymbol(_ symbol: String, at image: UnsafePointer, imageSlide slide: Int, symbolAddress: inout UnsafeMutableRawPointer?) -> Bool { 138 | // target cmd 139 | var linkeditCmd: UnsafeMutablePointer! 140 | var dyldInfoCmd: UnsafeMutablePointer! 141 | var allLoadDylds = [String]() 142 | 143 | guard var curCmdPointer = UnsafeMutableRawPointer(bitPattern: UInt(bitPattern: image)+UInt(MemoryLayout.size)) else { 144 | return false 145 | } 146 | // all cmd 147 | for _ in 0.. 0) { 178 | if let lazyBindInfoCmd = UnsafeMutablePointer(bitPattern: UInt(linkeditBase + UInt64(dyldInfoCmd.pointee.lazy_bind_off))), 179 | lookLazyBindSymbol(symbol, symbolAddr: &symbolAddress, lazyBindInfoCmd: lazyBindInfoCmd, lazyBindInfoSize: lazyBindSize, allLoadDylds: allLoadDylds) { 180 | return true 181 | } 182 | } 183 | 184 | // look by NonLazyBindInfo 185 | let bindSize = Int(dyldInfoCmd.pointee.bind_size) 186 | if (bindSize > 0) { 187 | if let bindCmd = UnsafeMutablePointer(bitPattern: UInt(linkeditBase + UInt64(dyldInfoCmd.pointee.bind_off))), 188 | lookBindSymbol(symbol, symbolAddr: &symbolAddress, bindInfoCmd: bindCmd, bindInfoSize: bindSize, allLoadDylds: allLoadDylds) { 189 | return true 190 | } 191 | } 192 | 193 | return false 194 | } 195 | 196 | // LazySymbolBindInfo 197 | @inline(__always) 198 | private static func lookLazyBindSymbol(_ symbol: String, symbolAddr: inout UnsafeMutableRawPointer?, lazyBindInfoCmd: UnsafeMutablePointer, lazyBindInfoSize: Int, allLoadDylds: [String]) -> Bool { 199 | var ptr = lazyBindInfoCmd 200 | let lazyBindingInfoEnd = lazyBindInfoCmd.advanced(by: Int(lazyBindInfoSize)) 201 | var ordinal: Int = -1 202 | var foundSymbol = false 203 | var addend = 0 204 | var type: Int32 = 0 205 | 206 | Label: while ptr < lazyBindingInfoEnd { 207 | let immediate = Int32(ptr.pointee) & BIND_IMMEDIATE_MASK 208 | let opcode = Int32(ptr.pointee) & BIND_OPCODE_MASK 209 | ptr += 1 210 | 211 | switch opcode { 212 | case BIND_OPCODE_DONE: 213 | continue 214 | // ORDINAL DYLIB 215 | case BIND_OPCODE_SET_DYLIB_ORDINAL_IMM: 216 | ordinal = Int(immediate) 217 | case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB: 218 | ordinal = Int(readUleb128(ptr: &ptr, end: lazyBindingInfoEnd)) 219 | case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM: 220 | if immediate == 0 { 221 | ordinal = 0 222 | } else { 223 | ordinal = Int(BIND_OPCODE_MASK | immediate) 224 | } 225 | // symbol 226 | case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM: 227 | let symbolName = String(cString: ptr + 1) 228 | if (symbolName == symbol) { 229 | foundSymbol = true 230 | } 231 | while ptr.pointee != 0 { 232 | ptr += 1 233 | } 234 | ptr += 1 // '00' 235 | case BIND_OPCODE_SET_TYPE_IMM: 236 | type = immediate 237 | continue 238 | // sleb 239 | case BIND_OPCODE_SET_ADDEND_SLEB: 240 | addend = Int(readSleb128(ptr: &ptr, end: lazyBindingInfoEnd)) 241 | // uleb 242 | case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB, BIND_OPCODE_ADD_ADDR_ULEB: 243 | _ = readUleb128(ptr: &ptr, end: lazyBindingInfoEnd) 244 | // bind action 245 | case BIND_OPCODE_DO_BIND: 246 | if (foundSymbol) { 247 | break Label 248 | } else { 249 | continue 250 | } 251 | default: 252 | assert(false, "bad lazy bind opcode") 253 | return false 254 | } 255 | } 256 | 257 | assert(ordinal <= allLoadDylds.count) 258 | 259 | if (foundSymbol && ordinal >= 0 && allLoadDylds.count > 0), ordinal <= allLoadDylds.count, type != BindTypeThreadedRebase { 260 | let imageName = allLoadDylds[ordinal-1] 261 | var tmpSymbolAddress: UnsafeMutableRawPointer? 262 | if lookExportedSymbol(symbol, exportImageName: imageName, symbolAddress: &tmpSymbolAddress), let symbolPointer = tmpSymbolAddress { 263 | symbolAddr = symbolPointer + addend 264 | return true 265 | } 266 | } 267 | 268 | return false 269 | } 270 | 271 | // NonLazySymbolBindInfo 272 | @inline(__always) 273 | private static func lookBindSymbol(_ symbol: String, symbolAddr: inout UnsafeMutableRawPointer?, bindInfoCmd: UnsafeMutablePointer, bindInfoSize: Int, allLoadDylds: [String]) -> Bool { 274 | var ptr = bindInfoCmd 275 | let bindingInfoEnd = bindInfoCmd.advanced(by: Int(bindInfoSize)) 276 | var ordinal: Int = -1 277 | var foundSymbol = false 278 | var addend = 0 279 | var type: Int32 = 0 280 | 281 | Label: while ptr < bindingInfoEnd { 282 | let immediate = Int32(ptr.pointee) & BIND_IMMEDIATE_MASK 283 | let opcode = Int32(ptr.pointee) & BIND_OPCODE_MASK 284 | ptr += 1 285 | 286 | switch opcode { 287 | case BIND_OPCODE_DONE: 288 | break Label 289 | // ORDINAL DYLIB 290 | case BIND_OPCODE_SET_DYLIB_ORDINAL_IMM: 291 | ordinal = Int(immediate) 292 | case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB: 293 | ordinal = Int(readUleb128(ptr: &ptr, end: bindingInfoEnd)) 294 | case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM: 295 | if immediate == 0 { 296 | ordinal = 0 297 | } else { 298 | ordinal = Int(Int8(BIND_OPCODE_MASK | immediate)) 299 | } 300 | // symbol 301 | case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM: 302 | let symbolName = String(cString: ptr + 1) 303 | if (symbolName == symbol) { 304 | foundSymbol = true 305 | } 306 | while ptr.pointee != 0 { 307 | ptr += 1 308 | } 309 | ptr += 1 // '00' 310 | case BIND_OPCODE_SET_TYPE_IMM: 311 | type = immediate 312 | continue 313 | // sleb 314 | case BIND_OPCODE_SET_ADDEND_SLEB: 315 | addend = Int(readSleb128(ptr: &ptr, end: bindingInfoEnd)) 316 | // uleb 317 | case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB, BIND_OPCODE_ADD_ADDR_ULEB: 318 | _ = readUleb128(ptr: &ptr, end: bindingInfoEnd) 319 | // do bind action 320 | case BIND_OPCODE_DO_BIND, BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED: 321 | if (foundSymbol) { 322 | break Label 323 | } 324 | case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB: 325 | if (foundSymbol) { 326 | break Label 327 | } else { 328 | _ = readUleb128(ptr: &ptr, end: bindingInfoEnd) 329 | } 330 | case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB: 331 | if (foundSymbol) { 332 | break Label 333 | } else { 334 | _ = readUleb128(ptr: &ptr, end: bindingInfoEnd) // count 335 | _ = readUleb128(ptr: &ptr, end: bindingInfoEnd) // skip 336 | } 337 | case BIND_OPCODE_THREADED: 338 | switch immediate { 339 | case BIND_SUBOPCODE_THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB: 340 | _ = readUleb128(ptr: &ptr, end: bindingInfoEnd) 341 | case BIND_SUBOPCODE_THREADED_APPLY: 342 | if (foundSymbol) { 343 | // ImageLoaderMachO::bindLocation case BIND_TYPE_THREADED_REBASE 344 | assert(false, "maybe bind_type is BIND_TYPE_THREADED_REBASE, don't handle") 345 | return false 346 | } 347 | continue Label 348 | default: 349 | assert(false, "bad bind subopcode") 350 | return false 351 | } 352 | default: 353 | assert(false, "bad bind opcode") 354 | return false 355 | } 356 | } 357 | 358 | assert(ordinal <= allLoadDylds.count) 359 | if (foundSymbol && ordinal >= 0 && allLoadDylds.count > 0), ordinal <= allLoadDylds.count, type != BindTypeThreadedRebase { 360 | let imageName = allLoadDylds[ordinal-1] 361 | var tmpSymbolAddress: UnsafeMutableRawPointer? 362 | if lookExportedSymbol(symbol, exportImageName: imageName, symbolAddress: &tmpSymbolAddress), let symbolPointer = tmpSymbolAddress { 363 | symbolAddr = symbolPointer + addend 364 | return true 365 | } 366 | } 367 | 368 | return false 369 | } 370 | 371 | // ExportSymbol 372 | @inline(__always) 373 | private static func lookExportedSymbol(_ symbol: String, exportImageName: String, symbolAddress: inout UnsafeMutableRawPointer?) -> Bool { 374 | var rpathImage: String? 375 | // @rpath 376 | if (exportImageName.contains("@rpath")) { 377 | rpathImage = exportImageName.components(separatedBy: "/").last 378 | } 379 | 380 | for index in 0..<_dyld_image_count() { 381 | // imageName 382 | let currentImageName = String(cString: _dyld_get_image_name(index)) 383 | if let tmpRpathImage = rpathImage { 384 | if (!currentImageName.contains(tmpRpathImage)) { 385 | continue 386 | } 387 | } else if (String(cString: _dyld_get_image_name(index)) != exportImageName) { 388 | continue 389 | } 390 | 391 | if let pointer = _lookExportedSymbol(symbol, image: _dyld_get_image_header(index), imageSlide: _dyld_get_image_vmaddr_slide(index)) { 392 | // found 393 | symbolAddress = UnsafeMutableRawPointer(mutating: pointer) 394 | return true 395 | } else { 396 | // not found, look at ReExport dylibs 397 | var allReExportDylibs = [String]() 398 | 399 | if let currentImage = _dyld_get_image_header(index), 400 | var curCmdPointer = UnsafeMutableRawPointer(bitPattern: UInt(bitPattern: currentImage)+UInt(MemoryLayout.size)) { 401 | 402 | for _ in 0.., imageSlide slide: Int) -> UnsafeMutableRawPointer? { 429 | // target cmd 430 | var linkeditCmd: UnsafeMutablePointer! 431 | var dyldInfoCmd: UnsafeMutablePointer! 432 | var exportCmd: UnsafeMutablePointer! 433 | 434 | guard var curCmdPointer = UnsafeMutableRawPointer(bitPattern: UInt(bitPattern: image)+UInt(MemoryLayout.size)) else { 435 | return nil 436 | } 437 | // cmd 438 | for _ in 0.. UnsafeMutableRawPointer in 480 | let machO = image.withMemoryRebound(to: Int8.self, capacity: 1, { $0 }) 481 | let symbolAddress = machO.advanced(by: Int(readUleb128(ptr: &symbolLocation, end: end))) 482 | return UnsafeMutableRawPointer(mutating: symbolAddress) 483 | } 484 | 485 | switch flags & UInt64(EXPORT_SYMBOL_FLAGS_KIND_MASK) { 486 | case UInt64(EXPORT_SYMBOL_FLAGS_KIND_REGULAR): 487 | // runResolver is false by bind or lazyBind 488 | return returnSymbolAddress() 489 | case UInt64(EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL): 490 | if (flags & UInt64(EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER) != 0) { 491 | return nil 492 | } 493 | return returnSymbolAddress() 494 | case UInt64(EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE): 495 | if (flags & UInt64(EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER) != 0) { 496 | return nil 497 | } 498 | return UnsafeMutableRawPointer(bitPattern: UInt(readUleb128(ptr: &symbolLocation, end: end))) 499 | default: 500 | break 501 | } 502 | } 503 | 504 | return nil 505 | } 506 | 507 | // ExportSymbol 508 | @inline(__always) 509 | static private func lookExportedSymbolByTrieWalk(targetSymbol: String, start: UnsafeMutablePointer, end: UnsafeMutablePointer, currentLocation location: UnsafeMutablePointer, currentSymbol: String) -> UnsafeMutablePointer? { 510 | var ptr = location 511 | 512 | while ptr <= end { 513 | // terminalSize 514 | var terminalSize = UInt64(ptr.pointee) 515 | ptr += 1 516 | if terminalSize > 127 { 517 | ptr -= 1 518 | terminalSize = readUleb128(ptr: &ptr, end: end) 519 | } 520 | if terminalSize != 0 { 521 | return currentSymbol == targetSymbol ? ptr : nil 522 | } 523 | 524 | // children 525 | let children = ptr.advanced(by: Int(terminalSize)) 526 | if children >= end { 527 | // end 528 | return nil 529 | } 530 | let childrenCount = children.pointee 531 | ptr = children + 1 532 | 533 | // nodes 534 | for _ in 0.., 568 | imageSlide slide: Int, 569 | newMethod: UnsafeMutableRawPointer, 570 | oldMethod: inout UnsafeMutableRawPointer?) { 571 | replaceSymbolAtImage(image, imageSlide: slide, symbol: symbol, newMethod: newMethod, oldMethod: &oldMethod) 572 | } 573 | 574 | @inline(__always) 575 | private static func replaceSymbolAtImage(_ image: UnsafePointer, 576 | imageSlide slide: Int, 577 | symbol: String, 578 | newMethod: UnsafeMutableRawPointer, 579 | oldMethod: inout UnsafeMutableRawPointer?) { 580 | var linkeditCmd: UnsafeMutablePointer! 581 | var dataCmd: UnsafeMutablePointer! 582 | var dataConstCmd: UnsafeMutablePointer! 583 | var symtabCmd: UnsafeMutablePointer! 584 | var dynamicSymtabCmd: UnsafeMutablePointer! 585 | 586 | guard var curCmdPointer = UnsafeMutableRawPointer(bitPattern: UInt(bitPattern: image)+UInt(MemoryLayout.size)) else { return } 587 | 588 | for _ in 0..(OpaquePointer(curCmd)) 606 | } else if curCmd.pointee.cmd == LC_DYSYMTAB { 607 | dynamicSymtabCmd = UnsafeMutablePointer(OpaquePointer(curCmd)) 608 | } 609 | 610 | curCmdPointer += Int(curCmd.pointee.cmdsize) 611 | } 612 | 613 | if linkeditCmd == nil || symtabCmd == nil || dynamicSymtabCmd == nil || (dataCmd == nil && dataConstCmd == nil) { 614 | return 615 | } 616 | 617 | let linkedBase = slide + Int(linkeditCmd.pointee.vmaddr) - Int(linkeditCmd.pointee.fileoff) 618 | let symtab = UnsafeMutablePointer(bitPattern: linkedBase + Int(symtabCmd.pointee.symoff)) 619 | let strtab = UnsafeMutablePointer(bitPattern: linkedBase + Int(symtabCmd.pointee.stroff)) 620 | let indirectsym = UnsafeMutablePointer(bitPattern: linkedBase + Int(dynamicSymtabCmd.pointee.indirectsymoff)) 621 | 622 | if symtab == nil || strtab == nil || indirectsym == nil { 623 | return 624 | } 625 | 626 | for segment in [dataCmd, dataConstCmd] { 627 | guard let segment else { continue } 628 | for tmp in 0...size + MemoryLayout.size*Int(tmp)).assumingMemoryBound(to: section_64.self) 630 | 631 | // symbol_pointers sections 632 | if curSection.pointee.flags == S_LAZY_SYMBOL_POINTERS { 633 | replaceSymbolPointerAtSection(curSection, symtab: symtab!, strtab: strtab!, indirectsym: indirectsym!, slide: slide, symbolName: symbol, newMethod: newMethod, oldMethod: &oldMethod) 634 | } 635 | if curSection.pointee.flags == S_NON_LAZY_SYMBOL_POINTERS { 636 | replaceSymbolPointerAtSection(curSection, symtab: symtab!, strtab: strtab!, indirectsym: indirectsym!, slide: slide, symbolName: symbol, newMethod: newMethod, oldMethod: &oldMethod) 637 | } 638 | } 639 | } 640 | } 641 | 642 | @inline(__always) 643 | private static func replaceSymbolPointerAtSection(_ section: UnsafeMutablePointer, 644 | symtab: UnsafeMutablePointer, 645 | strtab: UnsafeMutablePointer, 646 | indirectsym: UnsafeMutablePointer, 647 | slide: Int, 648 | symbolName: String, 649 | newMethod: UnsafeMutableRawPointer, 650 | oldMethod: inout UnsafeMutableRawPointer?) { 651 | let indirectSymVmAddr = indirectsym.advanced(by: Int(section.pointee.reserved1)) 652 | let sectionVmAddr = UnsafeMutablePointer(bitPattern: slide+Int(section.pointee.addr)) 653 | 654 | if sectionVmAddr == nil { 655 | return 656 | } 657 | 658 | for tmp in 0...size { 659 | let curIndirectSym = indirectSymVmAddr.advanced(by: tmp) 660 | if curIndirectSym.pointee == INDIRECT_SYMBOL_ABS || curIndirectSym.pointee == INDIRECT_SYMBOL_LOCAL { 661 | continue 662 | } 663 | let curStrTabOff = symtab.advanced(by: Int(curIndirectSym.pointee)).pointee.n_un.n_strx 664 | let curSymbolName = strtab.advanced(by: Int(curStrTabOff+1)) 665 | 666 | if String(cString: curSymbolName) == symbolName { 667 | oldMethod = sectionVmAddr!.advanced(by: tmp).pointee 668 | let err = vm_protect( 669 | mach_task_self_, 670 | .init(bitPattern: sectionVmAddr), 671 | numericCast(section.pointee.size), 672 | 0, 673 | VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY 674 | ) 675 | if err == KERN_SUCCESS { 676 | sectionVmAddr!.advanced(by: tmp).initialize(to: newMethod) 677 | } 678 | break 679 | } 680 | } 681 | } 682 | } 683 | #endif 684 | // swiftlint:enable all 685 | -------------------------------------------------------------------------------- /IOSSecuritySuite.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 60; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 703F74E222704E0F000635D8 /* ReverseEngineeringToolsChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 703F74E122704E0F000635D8 /* ReverseEngineeringToolsChecker.swift */; }; 11 | 706B0E2A226F445D0059AEA9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 706B0E29226F445D0059AEA9 /* AppDelegate.swift */; }; 12 | 706B0E2F226F445D0059AEA9 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 706B0E2D226F445D0059AEA9 /* Main.storyboard */; }; 13 | 706B0E31226F445F0059AEA9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 706B0E30226F445F0059AEA9 /* Assets.xcassets */; }; 14 | 706B0E34226F445F0059AEA9 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 706B0E32226F445F0059AEA9 /* LaunchScreen.storyboard */; }; 15 | 706B0E39226F44830059AEA9 /* IOSSecuritySuite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 70B0BBBC226F3A4D000CFB39 /* IOSSecuritySuite.framework */; }; 16 | 706B0E3B226F59AA0059AEA9 /* IOSSecuritySuite.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 70B0BBBC226F3A4D000CFB39 /* IOSSecuritySuite.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 17 | 70AB2CBB2BB59BA900511093 /* ModesChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70AB2CBA2BB59BA900511093 /* ModesChecker.swift */; }; 18 | 70B0BBC1226F3A4D000CFB39 /* IOSSecuritySuite.h in Headers */ = {isa = PBXBuildFile; fileRef = 70B0BBBF226F3A4D000CFB39 /* IOSSecuritySuite.h */; settings = {ATTRIBUTES = (Public, ); }; }; 19 | 70B0BBC9226F3A74000CFB39 /* DebuggerChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70B0BBC8226F3A74000CFB39 /* DebuggerChecker.swift */; }; 20 | 70B0BBCB226F3A86000CFB39 /* JailbreakChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70B0BBCA226F3A86000CFB39 /* JailbreakChecker.swift */; }; 21 | 70B0BBCD226F3A90000CFB39 /* EmulatorChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70B0BBCC226F3A90000CFB39 /* EmulatorChecker.swift */; }; 22 | 70B0BBCF226F3AB2000CFB39 /* IOSSecuritySuite.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70B0BBCE226F3AB2000CFB39 /* IOSSecuritySuite.swift */; }; 23 | 70B8E16C257E528D00917097 /* ProxyChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70B8E16B257E528D00917097 /* ProxyChecker.swift */; }; 24 | 7A12583D24EFA8D40071460D /* IntegrityChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A12583C24EFA8D40071460D /* IntegrityChecker.swift */; }; 25 | 890685F829912FCF00EEC5A6 /* FailedChecks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 890685F729912FCF00EEC5A6 /* FailedChecks.swift */; }; 26 | 89FDACDF2B5ACDAD00809636 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89FDACDE2B5ACDAD00809636 /* ViewController.swift */; }; 27 | A140C7EB2BC80E9C0054A4C9 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = A140C7EA2BC80E9C0054A4C9 /* PrivacyInfo.xcprivacy */; }; 28 | A90FD5FE24528925007212BF /* MSHookFunctionChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90FD5FD24528925007212BF /* MSHookFunctionChecker.swift */; }; 29 | A90FD60024528A94007212BF /* FishHookChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90FD5FF24528A94007212BF /* FishHookChecker.swift */; }; 30 | A90FD60224528FD1007212BF /* RuntimeHookChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90FD60124528FD1007212BF /* RuntimeHookChecker.swift */; }; 31 | E2814AD72A4E388100AC9E54 /* FileChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2814AD62A4E388100AC9E54 /* FileChecker.swift */; }; 32 | FF2FF25D2499F7590050D02F /* FishHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2FF25C2499F7590050D02F /* FishHook.swift */; }; 33 | /* End PBXBuildFile section */ 34 | 35 | /* Begin PBXCopyFilesBuildPhase section */ 36 | 706B0E3A226F59900059AEA9 /* CopyFiles */ = { 37 | isa = PBXCopyFilesBuildPhase; 38 | buildActionMask = 2147483647; 39 | dstPath = ""; 40 | dstSubfolderSpec = 10; 41 | files = ( 42 | 706B0E3B226F59AA0059AEA9 /* IOSSecuritySuite.framework in CopyFiles */, 43 | ); 44 | runOnlyForDeploymentPostprocessing = 0; 45 | }; 46 | /* End PBXCopyFilesBuildPhase section */ 47 | 48 | /* Begin PBXFileReference section */ 49 | 703F74E122704E0F000635D8 /* ReverseEngineeringToolsChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReverseEngineeringToolsChecker.swift; sourceTree = ""; }; 50 | 706B0E27226F445D0059AEA9 /* FrameworkClientApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FrameworkClientApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 51 | 706B0E29226F445D0059AEA9 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 52 | 706B0E2E226F445D0059AEA9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 53 | 706B0E30226F445F0059AEA9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 54 | 706B0E33226F445F0059AEA9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 55 | 706B0E35226F445F0059AEA9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 56 | 70AB2CBA2BB59BA900511093 /* ModesChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModesChecker.swift; sourceTree = ""; }; 57 | 70B0BBBC226F3A4D000CFB39 /* IOSSecuritySuite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = IOSSecuritySuite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 58 | 70B0BBBF226F3A4D000CFB39 /* IOSSecuritySuite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IOSSecuritySuite.h; sourceTree = ""; }; 59 | 70B0BBC0226F3A4D000CFB39 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 60 | 70B0BBC8226F3A74000CFB39 /* DebuggerChecker.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = DebuggerChecker.swift; sourceTree = ""; tabWidth = 2; }; 61 | 70B0BBCA226F3A86000CFB39 /* JailbreakChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JailbreakChecker.swift; sourceTree = ""; }; 62 | 70B0BBCC226F3A90000CFB39 /* EmulatorChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmulatorChecker.swift; sourceTree = ""; }; 63 | 70B0BBCE226F3AB2000CFB39 /* IOSSecuritySuite.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = IOSSecuritySuite.swift; sourceTree = ""; tabWidth = 2; }; 64 | 70B8E16B257E528D00917097 /* ProxyChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyChecker.swift; sourceTree = ""; }; 65 | 70DDFD5A2284AA9600D95EE7 /* FrameworkClientApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = FrameworkClientApp.entitlements; sourceTree = ""; }; 66 | 7A12583C24EFA8D40071460D /* IntegrityChecker.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = IntegrityChecker.swift; sourceTree = ""; tabWidth = 2; }; 67 | 890685F729912FCF00EEC5A6 /* FailedChecks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FailedChecks.swift; sourceTree = ""; }; 68 | 89FDACDE2B5ACDAD00809636 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 69 | A140C7EA2BC80E9C0054A4C9 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = Resources/PrivacyInfo.xcprivacy; sourceTree = ""; }; 70 | A90FD5FD24528925007212BF /* MSHookFunctionChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSHookFunctionChecker.swift; sourceTree = ""; }; 71 | A90FD5FF24528A94007212BF /* FishHookChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FishHookChecker.swift; sourceTree = ""; }; 72 | A90FD60124528FD1007212BF /* RuntimeHookChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeHookChecker.swift; sourceTree = ""; }; 73 | E2814AD62A4E388100AC9E54 /* FileChecker.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = FileChecker.swift; sourceTree = ""; tabWidth = 2; }; 74 | FF2FF25C2499F7590050D02F /* FishHook.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FishHook.swift; sourceTree = ""; }; 75 | /* End PBXFileReference section */ 76 | 77 | /* Begin PBXFrameworksBuildPhase section */ 78 | 706B0E24226F445D0059AEA9 /* Frameworks */ = { 79 | isa = PBXFrameworksBuildPhase; 80 | buildActionMask = 2147483647; 81 | files = ( 82 | 706B0E39226F44830059AEA9 /* IOSSecuritySuite.framework in Frameworks */, 83 | ); 84 | runOnlyForDeploymentPostprocessing = 0; 85 | }; 86 | 70B0BBB9226F3A4D000CFB39 /* Frameworks */ = { 87 | isa = PBXFrameworksBuildPhase; 88 | buildActionMask = 2147483647; 89 | files = ( 90 | ); 91 | runOnlyForDeploymentPostprocessing = 0; 92 | }; 93 | /* End PBXFrameworksBuildPhase section */ 94 | 95 | /* Begin PBXGroup section */ 96 | 706B0E28226F445D0059AEA9 /* FrameworkClientApp */ = { 97 | isa = PBXGroup; 98 | children = ( 99 | 89FDACDE2B5ACDAD00809636 /* ViewController.swift */, 100 | FF2FF25C2499F7590050D02F /* FishHook.swift */, 101 | 70DDFD5A2284AA9600D95EE7 /* FrameworkClientApp.entitlements */, 102 | 706B0E29226F445D0059AEA9 /* AppDelegate.swift */, 103 | 706B0E2D226F445D0059AEA9 /* Main.storyboard */, 104 | 706B0E30226F445F0059AEA9 /* Assets.xcassets */, 105 | 706B0E32226F445F0059AEA9 /* LaunchScreen.storyboard */, 106 | 706B0E35226F445F0059AEA9 /* Info.plist */, 107 | ); 108 | path = FrameworkClientApp; 109 | sourceTree = ""; 110 | }; 111 | 70B0BBB2226F3A4D000CFB39 = { 112 | isa = PBXGroup; 113 | children = ( 114 | 70B0BBBE226F3A4D000CFB39 /* IOSSecuritySuite */, 115 | 706B0E28226F445D0059AEA9 /* FrameworkClientApp */, 116 | 70B0BBBD226F3A4D000CFB39 /* Products */, 117 | 70F861BF226F3F2F00B01041 /* Frameworks */, 118 | ); 119 | indentWidth = 2; 120 | sourceTree = ""; 121 | tabWidth = 2; 122 | }; 123 | 70B0BBBD226F3A4D000CFB39 /* Products */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | 70B0BBBC226F3A4D000CFB39 /* IOSSecuritySuite.framework */, 127 | 706B0E27226F445D0059AEA9 /* FrameworkClientApp.app */, 128 | ); 129 | name = Products; 130 | sourceTree = ""; 131 | }; 132 | 70B0BBBE226F3A4D000CFB39 /* IOSSecuritySuite */ = { 133 | isa = PBXGroup; 134 | children = ( 135 | 70B0BBBF226F3A4D000CFB39 /* IOSSecuritySuite.h */, 136 | 70B0BBC0226F3A4D000CFB39 /* Info.plist */, 137 | 70B0BBC8226F3A74000CFB39 /* DebuggerChecker.swift */, 138 | A140C7EA2BC80E9C0054A4C9 /* PrivacyInfo.xcprivacy */, 139 | 70B0BBCA226F3A86000CFB39 /* JailbreakChecker.swift */, 140 | 890685F729912FCF00EEC5A6 /* FailedChecks.swift */, 141 | 70B0BBCC226F3A90000CFB39 /* EmulatorChecker.swift */, 142 | 7A12583C24EFA8D40071460D /* IntegrityChecker.swift */, 143 | 70B0BBCE226F3AB2000CFB39 /* IOSSecuritySuite.swift */, 144 | 703F74E122704E0F000635D8 /* ReverseEngineeringToolsChecker.swift */, 145 | A90FD5FD24528925007212BF /* MSHookFunctionChecker.swift */, 146 | A90FD5FF24528A94007212BF /* FishHookChecker.swift */, 147 | A90FD60124528FD1007212BF /* RuntimeHookChecker.swift */, 148 | 70B8E16B257E528D00917097 /* ProxyChecker.swift */, 149 | E2814AD62A4E388100AC9E54 /* FileChecker.swift */, 150 | 70AB2CBA2BB59BA900511093 /* ModesChecker.swift */, 151 | ); 152 | indentWidth = 2; 153 | path = IOSSecuritySuite; 154 | sourceTree = ""; 155 | tabWidth = 2; 156 | }; 157 | 70F861BF226F3F2F00B01041 /* Frameworks */ = { 158 | isa = PBXGroup; 159 | children = ( 160 | ); 161 | name = Frameworks; 162 | sourceTree = ""; 163 | }; 164 | /* End PBXGroup section */ 165 | 166 | /* Begin PBXHeadersBuildPhase section */ 167 | 70B0BBB7226F3A4D000CFB39 /* Headers */ = { 168 | isa = PBXHeadersBuildPhase; 169 | buildActionMask = 2147483647; 170 | files = ( 171 | 70B0BBC1226F3A4D000CFB39 /* IOSSecuritySuite.h in Headers */, 172 | ); 173 | runOnlyForDeploymentPostprocessing = 0; 174 | }; 175 | /* End PBXHeadersBuildPhase section */ 176 | 177 | /* Begin PBXNativeTarget section */ 178 | 706B0E26226F445D0059AEA9 /* FrameworkClientApp */ = { 179 | isa = PBXNativeTarget; 180 | buildConfigurationList = 706B0E38226F445F0059AEA9 /* Build configuration list for PBXNativeTarget "FrameworkClientApp" */; 181 | buildPhases = ( 182 | 706B0E23226F445D0059AEA9 /* Sources */, 183 | 706B0E24226F445D0059AEA9 /* Frameworks */, 184 | 706B0E25226F445D0059AEA9 /* Resources */, 185 | 706B0E3A226F59900059AEA9 /* CopyFiles */, 186 | ); 187 | buildRules = ( 188 | ); 189 | dependencies = ( 190 | ); 191 | name = FrameworkClientApp; 192 | productName = FrameworkClientApp; 193 | productReference = 706B0E27226F445D0059AEA9 /* FrameworkClientApp.app */; 194 | productType = "com.apple.product-type.application"; 195 | }; 196 | 70B0BBBB226F3A4D000CFB39 /* IOSSecuritySuite */ = { 197 | isa = PBXNativeTarget; 198 | buildConfigurationList = 70B0BBC4226F3A4D000CFB39 /* Build configuration list for PBXNativeTarget "IOSSecuritySuite" */; 199 | buildPhases = ( 200 | 70B0BBB7226F3A4D000CFB39 /* Headers */, 201 | 70B0BBB8226F3A4D000CFB39 /* Sources */, 202 | 70B0BBB9226F3A4D000CFB39 /* Frameworks */, 203 | 70B0BBBA226F3A4D000CFB39 /* Resources */, 204 | 70B0BBC7226F3A5F000CFB39 /* SwiftLint */, 205 | ); 206 | buildRules = ( 207 | ); 208 | dependencies = ( 209 | ); 210 | name = IOSSecuritySuite; 211 | productName = IOSSecuritySuite; 212 | productReference = 70B0BBBC226F3A4D000CFB39 /* IOSSecuritySuite.framework */; 213 | productType = "com.apple.product-type.framework"; 214 | }; 215 | /* End PBXNativeTarget section */ 216 | 217 | /* Begin PBXProject section */ 218 | 70B0BBB3226F3A4D000CFB39 /* Project object */ = { 219 | isa = PBXProject; 220 | attributes = { 221 | BuildIndependentTargetsInParallel = YES; 222 | LastSwiftUpdateCheck = 1010; 223 | LastUpgradeCheck = 1520; 224 | ORGANIZATIONNAME = wregula; 225 | TargetAttributes = { 226 | 706B0E26226F445D0059AEA9 = { 227 | CreatedOnToolsVersion = 10.1; 228 | LastSwiftMigration = 1130; 229 | SystemCapabilities = { 230 | com.apple.Multipath = { 231 | enabled = 0; 232 | }; 233 | }; 234 | }; 235 | 70B0BBBB226F3A4D000CFB39 = { 236 | CreatedOnToolsVersion = 10.1; 237 | LastSwiftMigration = 1110; 238 | }; 239 | }; 240 | }; 241 | buildConfigurationList = 70B0BBB6226F3A4D000CFB39 /* Build configuration list for PBXProject "IOSSecuritySuite" */; 242 | compatibilityVersion = "Xcode 15.0"; 243 | developmentRegion = en; 244 | hasScannedForEncodings = 0; 245 | knownRegions = ( 246 | en, 247 | Base, 248 | ); 249 | mainGroup = 70B0BBB2226F3A4D000CFB39; 250 | productRefGroup = 70B0BBBD226F3A4D000CFB39 /* Products */; 251 | projectDirPath = ""; 252 | projectRoot = ""; 253 | targets = ( 254 | 70B0BBBB226F3A4D000CFB39 /* IOSSecuritySuite */, 255 | 706B0E26226F445D0059AEA9 /* FrameworkClientApp */, 256 | ); 257 | }; 258 | /* End PBXProject section */ 259 | 260 | /* Begin PBXResourcesBuildPhase section */ 261 | 706B0E25226F445D0059AEA9 /* Resources */ = { 262 | isa = PBXResourcesBuildPhase; 263 | buildActionMask = 2147483647; 264 | files = ( 265 | 706B0E34226F445F0059AEA9 /* LaunchScreen.storyboard in Resources */, 266 | 706B0E31226F445F0059AEA9 /* Assets.xcassets in Resources */, 267 | 706B0E2F226F445D0059AEA9 /* Main.storyboard in Resources */, 268 | ); 269 | runOnlyForDeploymentPostprocessing = 0; 270 | }; 271 | 70B0BBBA226F3A4D000CFB39 /* Resources */ = { 272 | isa = PBXResourcesBuildPhase; 273 | buildActionMask = 2147483647; 274 | files = ( 275 | A140C7EB2BC80E9C0054A4C9 /* PrivacyInfo.xcprivacy in Resources */, 276 | ); 277 | runOnlyForDeploymentPostprocessing = 0; 278 | }; 279 | /* End PBXResourcesBuildPhase section */ 280 | 281 | /* Begin PBXShellScriptBuildPhase section */ 282 | 70B0BBC7226F3A5F000CFB39 /* SwiftLint */ = { 283 | isa = PBXShellScriptBuildPhase; 284 | buildActionMask = 2147483647; 285 | files = ( 286 | ); 287 | inputFileListPaths = ( 288 | ); 289 | inputPaths = ( 290 | ); 291 | name = SwiftLint; 292 | outputFileListPaths = ( 293 | ); 294 | outputPaths = ( 295 | ); 296 | runOnlyForDeploymentPostprocessing = 0; 297 | shellPath = /bin/sh; 298 | shellScript = "if [[ \"$(uname -m)\" == arm64 ]]; then\n export PATH=\"/opt/homebrew/bin:$PATH\"\nfi\n\nif which swiftlint > /dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; 299 | }; 300 | /* End PBXShellScriptBuildPhase section */ 301 | 302 | /* Begin PBXSourcesBuildPhase section */ 303 | 706B0E23226F445D0059AEA9 /* Sources */ = { 304 | isa = PBXSourcesBuildPhase; 305 | buildActionMask = 2147483647; 306 | files = ( 307 | FF2FF25D2499F7590050D02F /* FishHook.swift in Sources */, 308 | 89FDACDF2B5ACDAD00809636 /* ViewController.swift in Sources */, 309 | 706B0E2A226F445D0059AEA9 /* AppDelegate.swift in Sources */, 310 | ); 311 | runOnlyForDeploymentPostprocessing = 0; 312 | }; 313 | 70B0BBB8226F3A4D000CFB39 /* Sources */ = { 314 | isa = PBXSourcesBuildPhase; 315 | buildActionMask = 2147483647; 316 | files = ( 317 | 70B0BBC9226F3A74000CFB39 /* DebuggerChecker.swift in Sources */, 318 | 890685F829912FCF00EEC5A6 /* FailedChecks.swift in Sources */, 319 | E2814AD72A4E388100AC9E54 /* FileChecker.swift in Sources */, 320 | 70B0BBCD226F3A90000CFB39 /* EmulatorChecker.swift in Sources */, 321 | 70B8E16C257E528D00917097 /* ProxyChecker.swift in Sources */, 322 | A90FD60224528FD1007212BF /* RuntimeHookChecker.swift in Sources */, 323 | A90FD5FE24528925007212BF /* MSHookFunctionChecker.swift in Sources */, 324 | 70B0BBCF226F3AB2000CFB39 /* IOSSecuritySuite.swift in Sources */, 325 | 703F74E222704E0F000635D8 /* ReverseEngineeringToolsChecker.swift in Sources */, 326 | A90FD60024528A94007212BF /* FishHookChecker.swift in Sources */, 327 | 70AB2CBB2BB59BA900511093 /* ModesChecker.swift in Sources */, 328 | 7A12583D24EFA8D40071460D /* IntegrityChecker.swift in Sources */, 329 | 70B0BBCB226F3A86000CFB39 /* JailbreakChecker.swift in Sources */, 330 | ); 331 | runOnlyForDeploymentPostprocessing = 0; 332 | }; 333 | /* End PBXSourcesBuildPhase section */ 334 | 335 | /* Begin PBXVariantGroup section */ 336 | 706B0E2D226F445D0059AEA9 /* Main.storyboard */ = { 337 | isa = PBXVariantGroup; 338 | children = ( 339 | 706B0E2E226F445D0059AEA9 /* Base */, 340 | ); 341 | name = Main.storyboard; 342 | sourceTree = ""; 343 | }; 344 | 706B0E32226F445F0059AEA9 /* LaunchScreen.storyboard */ = { 345 | isa = PBXVariantGroup; 346 | children = ( 347 | 706B0E33226F445F0059AEA9 /* Base */, 348 | ); 349 | name = LaunchScreen.storyboard; 350 | sourceTree = ""; 351 | }; 352 | /* End PBXVariantGroup section */ 353 | 354 | /* Begin XCBuildConfiguration section */ 355 | 706B0E36226F445F0059AEA9 /* Debug */ = { 356 | isa = XCBuildConfiguration; 357 | buildSettings = { 358 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 359 | CODE_SIGN_IDENTITY = "Apple Development"; 360 | CODE_SIGN_STYLE = Automatic; 361 | DEVELOPMENT_TEAM = ""; 362 | INFOPLIST_FILE = FrameworkClientApp/Info.plist; 363 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 364 | LD_RUNPATH_SEARCH_PATHS = ( 365 | "$(inherited)", 366 | "@executable_path/Frameworks", 367 | ); 368 | PRODUCT_BUNDLE_IDENTIFIER = biz.securing.FrameworkClientApp; 369 | PRODUCT_NAME = "$(TARGET_NAME)"; 370 | PROVISIONING_PROFILE_SPECIFIER = ""; 371 | SWIFT_VERSION = 5.0; 372 | TARGETED_DEVICE_FAMILY = "1,2"; 373 | }; 374 | name = Debug; 375 | }; 376 | 706B0E37226F445F0059AEA9 /* Release */ = { 377 | isa = XCBuildConfiguration; 378 | buildSettings = { 379 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 380 | CODE_SIGN_IDENTITY = "Apple Development"; 381 | CODE_SIGN_STYLE = Automatic; 382 | DEVELOPMENT_TEAM = ""; 383 | INFOPLIST_FILE = FrameworkClientApp/Info.plist; 384 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 385 | LD_RUNPATH_SEARCH_PATHS = ( 386 | "$(inherited)", 387 | "@executable_path/Frameworks", 388 | ); 389 | PRODUCT_BUNDLE_IDENTIFIER = biz.securing.FrameworkClientApp; 390 | PRODUCT_NAME = "$(TARGET_NAME)"; 391 | PROVISIONING_PROFILE_SPECIFIER = ""; 392 | SWIFT_VERSION = 5.0; 393 | TARGETED_DEVICE_FAMILY = "1,2"; 394 | }; 395 | name = Release; 396 | }; 397 | 70B0BBC2226F3A4D000CFB39 /* Debug */ = { 398 | isa = XCBuildConfiguration; 399 | buildSettings = { 400 | ALWAYS_SEARCH_USER_PATHS = NO; 401 | CLANG_ANALYZER_NONNULL = YES; 402 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 403 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 404 | CLANG_CXX_LIBRARY = "libc++"; 405 | CLANG_ENABLE_MODULES = YES; 406 | CLANG_ENABLE_OBJC_ARC = YES; 407 | CLANG_ENABLE_OBJC_WEAK = YES; 408 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 409 | CLANG_WARN_BOOL_CONVERSION = YES; 410 | CLANG_WARN_COMMA = YES; 411 | CLANG_WARN_CONSTANT_CONVERSION = YES; 412 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 413 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 414 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 415 | CLANG_WARN_EMPTY_BODY = YES; 416 | CLANG_WARN_ENUM_CONVERSION = YES; 417 | CLANG_WARN_INFINITE_RECURSION = YES; 418 | CLANG_WARN_INT_CONVERSION = YES; 419 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 420 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 421 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 422 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 423 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 424 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 425 | CLANG_WARN_STRICT_PROTOTYPES = YES; 426 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 427 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 428 | CLANG_WARN_UNREACHABLE_CODE = YES; 429 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 430 | CODE_SIGN_IDENTITY = "iPhone Developer"; 431 | COPY_PHASE_STRIP = NO; 432 | CURRENT_PROJECT_VERSION = 1; 433 | DEBUG_INFORMATION_FORMAT = dwarf; 434 | ENABLE_STRICT_OBJC_MSGSEND = YES; 435 | ENABLE_TESTABILITY = YES; 436 | ENABLE_USER_SCRIPT_SANDBOXING = NO; 437 | GCC_C_LANGUAGE_STANDARD = gnu11; 438 | GCC_DYNAMIC_NO_PIC = NO; 439 | GCC_NO_COMMON_BLOCKS = YES; 440 | GCC_OPTIMIZATION_LEVEL = 0; 441 | GCC_PREPROCESSOR_DEFINITIONS = ( 442 | "DEBUG=1", 443 | "$(inherited)", 444 | ); 445 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 446 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 447 | GCC_WARN_UNDECLARED_SELECTOR = YES; 448 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 449 | GCC_WARN_UNUSED_FUNCTION = YES; 450 | GCC_WARN_UNUSED_VARIABLE = YES; 451 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 452 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 453 | MTL_FAST_MATH = YES; 454 | ONLY_ACTIVE_ARCH = YES; 455 | SDKROOT = iphoneos; 456 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 457 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 458 | VERSIONING_SYSTEM = "apple-generic"; 459 | VERSION_INFO_PREFIX = ""; 460 | }; 461 | name = Debug; 462 | }; 463 | 70B0BBC3226F3A4D000CFB39 /* Release */ = { 464 | isa = XCBuildConfiguration; 465 | buildSettings = { 466 | ALWAYS_SEARCH_USER_PATHS = NO; 467 | CLANG_ANALYZER_NONNULL = YES; 468 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 469 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 470 | CLANG_CXX_LIBRARY = "libc++"; 471 | CLANG_ENABLE_MODULES = YES; 472 | CLANG_ENABLE_OBJC_ARC = YES; 473 | CLANG_ENABLE_OBJC_WEAK = YES; 474 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 475 | CLANG_WARN_BOOL_CONVERSION = YES; 476 | CLANG_WARN_COMMA = YES; 477 | CLANG_WARN_CONSTANT_CONVERSION = YES; 478 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 479 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 480 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 481 | CLANG_WARN_EMPTY_BODY = YES; 482 | CLANG_WARN_ENUM_CONVERSION = YES; 483 | CLANG_WARN_INFINITE_RECURSION = YES; 484 | CLANG_WARN_INT_CONVERSION = YES; 485 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 486 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 487 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 488 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 489 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 490 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 491 | CLANG_WARN_STRICT_PROTOTYPES = YES; 492 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 493 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 494 | CLANG_WARN_UNREACHABLE_CODE = YES; 495 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 496 | CODE_SIGN_IDENTITY = "iPhone Developer"; 497 | COPY_PHASE_STRIP = NO; 498 | CURRENT_PROJECT_VERSION = 1; 499 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 500 | ENABLE_NS_ASSERTIONS = NO; 501 | ENABLE_STRICT_OBJC_MSGSEND = YES; 502 | ENABLE_USER_SCRIPT_SANDBOXING = NO; 503 | GCC_C_LANGUAGE_STANDARD = gnu11; 504 | GCC_NO_COMMON_BLOCKS = YES; 505 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 506 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 507 | GCC_WARN_UNDECLARED_SELECTOR = YES; 508 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 509 | GCC_WARN_UNUSED_FUNCTION = YES; 510 | GCC_WARN_UNUSED_VARIABLE = YES; 511 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 512 | MTL_ENABLE_DEBUG_INFO = NO; 513 | MTL_FAST_MATH = YES; 514 | SDKROOT = iphoneos; 515 | SWIFT_COMPILATION_MODE = wholemodule; 516 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 517 | VALIDATE_PRODUCT = YES; 518 | VERSIONING_SYSTEM = "apple-generic"; 519 | VERSION_INFO_PREFIX = ""; 520 | }; 521 | name = Release; 522 | }; 523 | 70B0BBC5226F3A4D000CFB39 /* Debug */ = { 524 | isa = XCBuildConfiguration; 525 | buildSettings = { 526 | CLANG_ENABLE_MODULES = YES; 527 | CODE_SIGN_IDENTITY = ""; 528 | CODE_SIGN_STYLE = Manual; 529 | DEFINES_MODULE = YES; 530 | DEVELOPMENT_TEAM = ""; 531 | DYLIB_COMPATIBILITY_VERSION = 1; 532 | DYLIB_CURRENT_VERSION = 1; 533 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 534 | ENABLE_MODULE_VERIFIER = YES; 535 | INFOPLIST_FILE = IOSSecuritySuite/Info.plist; 536 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 537 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 538 | LD_RUNPATH_SEARCH_PATHS = ( 539 | "$(inherited)", 540 | "@executable_path/Frameworks", 541 | "@loader_path/Frameworks", 542 | ); 543 | MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; 544 | MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14"; 545 | PRODUCT_BUNDLE_IDENTIFIER = biz.securing.IOSSecuritySuite; 546 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 547 | PROVISIONING_PROFILE_SPECIFIER = ""; 548 | "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; 549 | SKIP_INSTALL = YES; 550 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 551 | SWIFT_VERSION = 5.0; 552 | TARGETED_DEVICE_FAMILY = "1,2"; 553 | }; 554 | name = Debug; 555 | }; 556 | 70B0BBC6226F3A4D000CFB39 /* Release */ = { 557 | isa = XCBuildConfiguration; 558 | buildSettings = { 559 | CLANG_ENABLE_MODULES = YES; 560 | CODE_SIGN_IDENTITY = ""; 561 | CODE_SIGN_STYLE = Manual; 562 | DEFINES_MODULE = YES; 563 | DEVELOPMENT_TEAM = ""; 564 | DYLIB_COMPATIBILITY_VERSION = 1; 565 | DYLIB_CURRENT_VERSION = 1; 566 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 567 | ENABLE_MODULE_VERIFIER = YES; 568 | INFOPLIST_FILE = IOSSecuritySuite/Info.plist; 569 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 570 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 571 | LD_RUNPATH_SEARCH_PATHS = ( 572 | "$(inherited)", 573 | "@executable_path/Frameworks", 574 | "@loader_path/Frameworks", 575 | ); 576 | MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; 577 | MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14"; 578 | PRODUCT_BUNDLE_IDENTIFIER = biz.securing.IOSSecuritySuite; 579 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 580 | PROVISIONING_PROFILE_SPECIFIER = ""; 581 | "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; 582 | SKIP_INSTALL = YES; 583 | SWIFT_VERSION = 5.0; 584 | TARGETED_DEVICE_FAMILY = "1,2"; 585 | }; 586 | name = Release; 587 | }; 588 | /* End XCBuildConfiguration section */ 589 | 590 | /* Begin XCConfigurationList section */ 591 | 706B0E38226F445F0059AEA9 /* Build configuration list for PBXNativeTarget "FrameworkClientApp" */ = { 592 | isa = XCConfigurationList; 593 | buildConfigurations = ( 594 | 706B0E36226F445F0059AEA9 /* Debug */, 595 | 706B0E37226F445F0059AEA9 /* Release */, 596 | ); 597 | defaultConfigurationIsVisible = 0; 598 | defaultConfigurationName = Release; 599 | }; 600 | 70B0BBB6226F3A4D000CFB39 /* Build configuration list for PBXProject "IOSSecuritySuite" */ = { 601 | isa = XCConfigurationList; 602 | buildConfigurations = ( 603 | 70B0BBC2226F3A4D000CFB39 /* Debug */, 604 | 70B0BBC3226F3A4D000CFB39 /* Release */, 605 | ); 606 | defaultConfigurationIsVisible = 0; 607 | defaultConfigurationName = Release; 608 | }; 609 | 70B0BBC4226F3A4D000CFB39 /* Build configuration list for PBXNativeTarget "IOSSecuritySuite" */ = { 610 | isa = XCConfigurationList; 611 | buildConfigurations = ( 612 | 70B0BBC5226F3A4D000CFB39 /* Debug */, 613 | 70B0BBC6226F3A4D000CFB39 /* Release */, 614 | ); 615 | defaultConfigurationIsVisible = 0; 616 | defaultConfigurationName = Release; 617 | }; 618 | /* End XCConfigurationList section */ 619 | }; 620 | rootObject = 70B0BBB3226F3A4D000CFB39 /* Project object */; 621 | } 622 | --------------------------------------------------------------------------------