├── .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 | 
6 |
7 |
8 | ## ISS Description
9 |
10 | 
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 |
--------------------------------------------------------------------------------