@cap-kit/integrity 8.0.0-next.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CapKitIntegrity.podspec +17 -0
- package/LICENSE +21 -0
- package/Package.swift +26 -0
- package/README.md +1104 -0
- package/android/build.gradle +104 -0
- package/android/src/main/AndroidManifest.xml +21 -0
- package/android/src/main/java/io/capkit/integrity/IntegrityCheckOptions.kt +37 -0
- package/android/src/main/java/io/capkit/integrity/IntegrityConfig.kt +59 -0
- package/android/src/main/java/io/capkit/integrity/IntegrityError.kt +40 -0
- package/android/src/main/java/io/capkit/integrity/IntegrityImpl.kt +319 -0
- package/android/src/main/java/io/capkit/integrity/IntegrityPlugin.kt +475 -0
- package/android/src/main/java/io/capkit/integrity/IntegrityReportBuilder.kt +130 -0
- package/android/src/main/java/io/capkit/integrity/IntegritySignalBuilder.kt +72 -0
- package/android/src/main/java/io/capkit/integrity/emulator/IntegrityEmulatorChecks.kt +38 -0
- package/android/src/main/java/io/capkit/integrity/filesystem/IntegrityFilesystemChecks.kt +51 -0
- package/android/src/main/java/io/capkit/integrity/hook/IntegrityHookChecks.kt +61 -0
- package/android/src/main/java/io/capkit/integrity/remote/IntegrityRemoteAttestor.kt +49 -0
- package/android/src/main/java/io/capkit/integrity/root/IntegrityRootDetector.kt +136 -0
- package/android/src/main/java/io/capkit/integrity/runtime/IntegrityRuntimeChecks.kt +87 -0
- package/android/src/main/java/io/capkit/integrity/ui/IntegrityBlockActivity.kt +173 -0
- package/android/src/main/java/io/capkit/integrity/ui/IntegrityUISignals.kt +57 -0
- package/android/src/main/java/io/capkit/integrity/utils/IntegrityLogger.kt +85 -0
- package/android/src/main/java/io/capkit/integrity/utils/IntegrityUtils.kt +105 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/android/src/main/res/values/styles.xml +5 -0
- package/dist/docs.json +598 -0
- package/dist/esm/definitions.d.ts +554 -0
- package/dist/esm/definitions.js +56 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +15 -0
- package/dist/esm/index.js +16 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +32 -0
- package/dist/esm/web.js +51 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +130 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +133 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/IntegrityPlugin/IntegrityCheckOptions.swift +41 -0
- package/ios/Sources/IntegrityPlugin/IntegrityConfig.swift +135 -0
- package/ios/Sources/IntegrityPlugin/IntegrityEntitlementChecks.swift +58 -0
- package/ios/Sources/IntegrityPlugin/IntegrityError.swift +49 -0
- package/ios/Sources/IntegrityPlugin/IntegrityImpl.swift +397 -0
- package/ios/Sources/IntegrityPlugin/IntegrityPlugin.swift +345 -0
- package/ios/Sources/IntegrityPlugin/IntegrityReportBuilder.swift +184 -0
- package/ios/Sources/IntegrityPlugin/Utils/IntegrityLogger.swift +69 -0
- package/ios/Sources/IntegrityPlugin/Utils/IntegrityUtils.swift +144 -0
- package/ios/Sources/IntegrityPlugin/Version.swift +16 -0
- package/ios/Sources/IntegrityPlugin/filesystem/IntegrityFilesystemChecks.swift +86 -0
- package/ios/Sources/IntegrityPlugin/hook/IntegrityHookChecks.swift +85 -0
- package/ios/Sources/IntegrityPlugin/jailbreak/IntegrityJailbreakDetector.swift +74 -0
- package/ios/Sources/IntegrityPlugin/jailbreak/IntegrityJailbreakUrlSchemeDetector.swift +42 -0
- package/ios/Sources/IntegrityPlugin/remote/IntegrityRemoteAttestor.swift +40 -0
- package/ios/Sources/IntegrityPlugin/runtime/IntegrityRuntimeChecks.swift +63 -0
- package/ios/Sources/IntegrityPlugin/simulator/IntegritySimulatorChecks.swift +20 -0
- package/ios/Sources/IntegrityPlugin/ui/IntegrityBlockViewController.swift +143 -0
- package/ios/Tests/IntegrityPluginTests/IntegrityPluginTests.swift +10 -0
- package/package.json +106 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
Utility helpers for the Integrity plugin (iOS).
|
|
5
|
+
|
|
6
|
+
PURPOSE:
|
|
7
|
+
- Host pure Swift utility functions that are:
|
|
8
|
+
- platform-agnostic
|
|
9
|
+
- side-effect free
|
|
10
|
+
- independent from Capacitor APIs
|
|
11
|
+
|
|
12
|
+
CURRENT STATE:
|
|
13
|
+
- This type is intentionally empty.
|
|
14
|
+
- iOS does not require explicit JSON serialization helpers
|
|
15
|
+
because Capacitor accepts native `[String: Any]` payloads.
|
|
16
|
+
|
|
17
|
+
FUTURE USE CASES:
|
|
18
|
+
- Cross-platform payload normalization
|
|
19
|
+
- Remote attestation artifact encoding
|
|
20
|
+
- Data redaction or canonicalization
|
|
21
|
+
- Shared, testable transformation logic
|
|
22
|
+
|
|
23
|
+
CONTRACT:
|
|
24
|
+
- Utilities defined here MUST NOT:
|
|
25
|
+
- access Capacitor APIs
|
|
26
|
+
- reference Plugin or Impl layers
|
|
27
|
+
- perform I/O or side effects
|
|
28
|
+
*/
|
|
29
|
+
struct IntegrityUtils {
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
Builds a JSON-compatible integrity signal dictionary.
|
|
33
|
+
|
|
34
|
+
- Parameters:
|
|
35
|
+
- id: Stable signal identifier.
|
|
36
|
+
- category: High-level signal category.
|
|
37
|
+
- confidence: Confidence level ("low", "medium", "high").
|
|
38
|
+
- description: Optional human-readable description.
|
|
39
|
+
- metadata: Optional diagnostic metadata.
|
|
40
|
+
- includeDebug: Whether debug fields should be included.
|
|
41
|
+
|
|
42
|
+
- Returns: A `[String: Any]` dictionary safe to cross the JS bridge.
|
|
43
|
+
*/
|
|
44
|
+
static func buildSignal(
|
|
45
|
+
id: String,
|
|
46
|
+
category: String,
|
|
47
|
+
confidence: String,
|
|
48
|
+
description: String?,
|
|
49
|
+
metadata: [String: Any]? = nil,
|
|
50
|
+
includeDebug: Bool
|
|
51
|
+
) -> [String: Any] {
|
|
52
|
+
|
|
53
|
+
var signal: [String: Any] = [
|
|
54
|
+
"id": id,
|
|
55
|
+
"category": category,
|
|
56
|
+
"confidence": confidence
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
if includeDebug, let description {
|
|
60
|
+
signal["description"] = description
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if let metadata {
|
|
64
|
+
signal["metadata"] = metadata
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return signal
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
enum IntegrityCorrelationUtils {
|
|
72
|
+
|
|
73
|
+
static func jailbreakCorrelation(
|
|
74
|
+
from signals: [[String: Any]],
|
|
75
|
+
includeDebug: Bool
|
|
76
|
+
) -> [String: Any]? {
|
|
77
|
+
|
|
78
|
+
let jailbreakSignals = signals.filter {
|
|
79
|
+
($0["category"] as? String) == "jailbreak"
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
guard jailbreakSignals.count >= 2 else {
|
|
83
|
+
return nil
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let sources: Set<String> = Set(
|
|
87
|
+
jailbreakSignals.compactMap {
|
|
88
|
+
($0["metadata"] as? [String: Any])?["source"] as? String
|
|
89
|
+
}
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
guard sources.count >= 2 else {
|
|
93
|
+
return nil
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return [
|
|
97
|
+
"id": "ios_jailbreak_multiple_indicators",
|
|
98
|
+
"category": "jailbreak",
|
|
99
|
+
"confidence": "high",
|
|
100
|
+
"description": includeDebug
|
|
101
|
+
? "Multiple independent jailbreak indicators detected simultaneously"
|
|
102
|
+
: nil,
|
|
103
|
+
"metadata": [
|
|
104
|
+
"indicatorCount": jailbreakSignals.count,
|
|
105
|
+
"sources": Array(sources)
|
|
106
|
+
]
|
|
107
|
+
].compactMapValues { $0 }
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
static func jailbreakAndHookCorrelation(
|
|
111
|
+
from signals: [[String: Any]],
|
|
112
|
+
includeDebug: Bool
|
|
113
|
+
) -> [String: Any]? {
|
|
114
|
+
|
|
115
|
+
let hasJailbreak = signals.contains {
|
|
116
|
+
($0["category"] as? String) == "jailbreak"
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let hasHook = signals.contains {
|
|
120
|
+
($0["category"] as? String) == "hook"
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
guard hasJailbreak && hasHook else {
|
|
124
|
+
return nil
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return [
|
|
128
|
+
"id": "ios_jailbreak_and_hook_detected",
|
|
129
|
+
"category": "tamper",
|
|
130
|
+
"confidence": "high",
|
|
131
|
+
"description": includeDebug
|
|
132
|
+
? "Device appears to be jailbroken and actively instrumented at runtime"
|
|
133
|
+
: nil,
|
|
134
|
+
"metadata": [
|
|
135
|
+
"jailbreakIndicators": signals.filter {
|
|
136
|
+
($0["category"] as? String) == "jailbreak"
|
|
137
|
+
}.count,
|
|
138
|
+
"hookIndicators": signals.filter {
|
|
139
|
+
($0["category"] as? String) == "hook"
|
|
140
|
+
}.count
|
|
141
|
+
]
|
|
142
|
+
].compactMapValues { $0 }
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// This file is automatically generated. Do not modify manually.
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
Container for the plugin's version information.
|
|
6
|
+
This enum provides a centralized, single source of truth for the native
|
|
7
|
+
version string, synchronized directly from the project's 'package.json'.
|
|
8
|
+
It ensures parity across JavaScript, Android, and iOS platforms.
|
|
9
|
+
*/
|
|
10
|
+
public enum PluginVersion {
|
|
11
|
+
/**
|
|
12
|
+
The semantic version string of the plugin.
|
|
13
|
+
Value synchronized from package.json: "8.0.0-next.6"
|
|
14
|
+
*/
|
|
15
|
+
public static let number = "8.0.0-next.6"
|
|
16
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
Filesystem integrity checks related to sandbox escape
|
|
5
|
+
and suspicious redirections.
|
|
6
|
+
*/
|
|
7
|
+
struct IntegrityFilesystemChecks {
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
Attempts to detect sandbox escape by testing write access
|
|
11
|
+
to a restricted filesystem location.
|
|
12
|
+
|
|
13
|
+
Characteristics:
|
|
14
|
+
- Best-effort heuristic
|
|
15
|
+
- Synchronous
|
|
16
|
+
- No persistent side effects (temporary file is removed)
|
|
17
|
+
|
|
18
|
+
IMPORTANT:
|
|
19
|
+
- A successful write strongly suggests a sandbox escape.
|
|
20
|
+
- A failure does NOT guarantee a secure environment.
|
|
21
|
+
*/
|
|
22
|
+
static func canEscapeSandbox() -> Bool {
|
|
23
|
+
|
|
24
|
+
// Target path intentionally chosen inside a protected directory.
|
|
25
|
+
let testPath = "/private/integrity_test.txt"
|
|
26
|
+
let testString = "integrity_test"
|
|
27
|
+
|
|
28
|
+
do {
|
|
29
|
+
// Attempt to write a temporary file outside the app sandbox.
|
|
30
|
+
try testString.write(
|
|
31
|
+
toFile: testPath,
|
|
32
|
+
atomically: true,
|
|
33
|
+
encoding: .utf8
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
// Cleanup if the write unexpectedly succeeds.
|
|
37
|
+
try? FileManager.default.removeItem(atPath: testPath)
|
|
38
|
+
return true
|
|
39
|
+
} catch {
|
|
40
|
+
// Write failed as expected on non-compromised devices.
|
|
41
|
+
return false
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
Detects suspicious symbolic links that may indicate
|
|
47
|
+
filesystem redirection caused by a jailbreak.
|
|
48
|
+
|
|
49
|
+
Characteristics:
|
|
50
|
+
- Read-only inspection
|
|
51
|
+
- No recursion
|
|
52
|
+
- Limited to well-known system paths
|
|
53
|
+
|
|
54
|
+
NOTES:
|
|
55
|
+
- This check intentionally avoids deep filesystem traversal
|
|
56
|
+
to reduce overhead and false positives.
|
|
57
|
+
*/
|
|
58
|
+
static func hasSuspiciousSymlinks() -> Bool {
|
|
59
|
+
|
|
60
|
+
// Well-known system directories that should never be symbolic links.
|
|
61
|
+
let pathsToCheck = [
|
|
62
|
+
"/Applications",
|
|
63
|
+
"/Library",
|
|
64
|
+
"/usr/libexec",
|
|
65
|
+
"/usr/share"
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
for path in pathsToCheck {
|
|
69
|
+
do {
|
|
70
|
+
let attributes =
|
|
71
|
+
try FileManager.default.attributesOfItem(atPath: path)
|
|
72
|
+
|
|
73
|
+
// Any symbolic link at these locations is highly suspicious.
|
|
74
|
+
if attributes[.type] as? FileAttributeType == .typeSymbolicLink {
|
|
75
|
+
return true
|
|
76
|
+
}
|
|
77
|
+
} catch {
|
|
78
|
+
// Ignore inaccessible paths and continue checking others.
|
|
79
|
+
continue
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// No suspicious filesystem redirections detected.
|
|
84
|
+
return false
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import MachO
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
Runtime integrity checks related to instrumentation frameworks (e.g., Frida).
|
|
6
|
+
*/
|
|
7
|
+
struct IntegrityHookChecks {
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
Scans loaded dynamic libraries for suspicious instrumentation frameworks.
|
|
11
|
+
*/
|
|
12
|
+
static func hookingSignal(
|
|
13
|
+
includeDebug: Bool
|
|
14
|
+
) -> [String: Any]? {
|
|
15
|
+
|
|
16
|
+
// Total number of images currently loaded by dyld.
|
|
17
|
+
let imageCount = _dyld_image_count()
|
|
18
|
+
|
|
19
|
+
// Substrings commonly found in instrumentation or hooking frameworks.
|
|
20
|
+
// Matching is intentionally heuristic-based.
|
|
21
|
+
let suspicious = ["frida", "gadget", "substrate", "cydia", "substitute"]
|
|
22
|
+
|
|
23
|
+
for index in 0..<imageCount {
|
|
24
|
+
guard let cName = _dyld_get_image_name(index) else { continue }
|
|
25
|
+
let imageName = String(cString: cName).lowercased()
|
|
26
|
+
|
|
27
|
+
// Optimization: skip standard system libraries to reduce
|
|
28
|
+
// both runtime cost and false positives.
|
|
29
|
+
if imageName.contains("/usr/lib/")
|
|
30
|
+
|| imageName.contains("/system/library/") {
|
|
31
|
+
|
|
32
|
+
// Still inspect non-standard subpaths for suspicious fragments.
|
|
33
|
+
if !suspicious.contains(where: { imageName.contains($0) }) {
|
|
34
|
+
continue
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Emit a hooking signal as soon as a suspicious library is found.
|
|
39
|
+
// Only the first match is reported to limit noise.
|
|
40
|
+
if suspicious.contains(where: { imageName.contains($0) }) {
|
|
41
|
+
return [
|
|
42
|
+
"id": "ios_hooking_library_detected",
|
|
43
|
+
"category": "hook",
|
|
44
|
+
"confidence": "high",
|
|
45
|
+
"description": includeDebug
|
|
46
|
+
? "Detected suspicious instrumentation library: \(imageName)"
|
|
47
|
+
: nil,
|
|
48
|
+
"metadata": ["library_path": imageName]
|
|
49
|
+
].compactMapValues { $0 }
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return nil
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
Detects if a known instrumentation port (Frida) is open on localhost.
|
|
58
|
+
*/
|
|
59
|
+
static func isFridaPortOpen() -> Bool {
|
|
60
|
+
|
|
61
|
+
// Known default Frida server port.
|
|
62
|
+
// This check is intentionally limited to a single, well-known port.
|
|
63
|
+
var serverAddress = sockaddr_in()
|
|
64
|
+
serverAddress.sin_family = sa_family_t(AF_INET)
|
|
65
|
+
serverAddress.sin_addr.s_addr = inet_addr("127.0.0.1")
|
|
66
|
+
serverAddress.sin_port = UInt16(27042).bigEndian
|
|
67
|
+
|
|
68
|
+
// Create a TCP socket to test local connectivity.
|
|
69
|
+
let sock = socket(AF_INET, SOCK_STREAM, 0)
|
|
70
|
+
if sock < 0 { return false }
|
|
71
|
+
defer { close(sock) }
|
|
72
|
+
|
|
73
|
+
// Attempt to connect to the local Frida server port.
|
|
74
|
+
// A successful connection suggests an active instrumentation service.
|
|
75
|
+
return withUnsafePointer(to: &serverAddress) {
|
|
76
|
+
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
|
|
77
|
+
connect(
|
|
78
|
+
sock,
|
|
79
|
+
$0,
|
|
80
|
+
socklen_t(MemoryLayout<sockaddr_in>.size)
|
|
81
|
+
) == 0
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
Performs filesystem-based jailbreak detection.
|
|
5
|
+
All checks are synchronous and deterministic.
|
|
6
|
+
*/
|
|
7
|
+
struct IntegrityJailbreakDetector {
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
Performs filesystem-based jailbreak detection.
|
|
11
|
+
|
|
12
|
+
This detector relies exclusively on the presence of well-known
|
|
13
|
+
filesystem paths commonly introduced by jailbreak tools.
|
|
14
|
+
|
|
15
|
+
Characteristics:
|
|
16
|
+
- Deterministic
|
|
17
|
+
- Synchronous
|
|
18
|
+
- No side effects
|
|
19
|
+
- Safe to execute at application boot time
|
|
20
|
+
|
|
21
|
+
IMPORTANT:
|
|
22
|
+
- This detector intentionally emits at most ONE signal.
|
|
23
|
+
- The first matching path short-circuits further checks
|
|
24
|
+
to minimize noise and overhead.
|
|
25
|
+
*/
|
|
26
|
+
static func detect(
|
|
27
|
+
includeDebug: Bool
|
|
28
|
+
) -> [[String: Any]] {
|
|
29
|
+
|
|
30
|
+
// Well-known filesystem paths associated with common jailbreaks.
|
|
31
|
+
// This list is intentionally conservative and may evolve over time.
|
|
32
|
+
let suspiciousPaths = [
|
|
33
|
+
"/Applications/Cydia.app",
|
|
34
|
+
"/Library/MobileSubstrate/MobileSubstrate.dylib",
|
|
35
|
+
"/bin/bash",
|
|
36
|
+
"/usr/sbin/sshd",
|
|
37
|
+
"/etc/apt",
|
|
38
|
+
"/usr/bin/ssh",
|
|
39
|
+
"/private/var/lib/apt",
|
|
40
|
+
"/private/var/lib/cydia",
|
|
41
|
+
"/usr/libexec/sftp-server",
|
|
42
|
+
"/Applications/Sileo.app",
|
|
43
|
+
"/Applications/Zebra.app"
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
// Iterate through known paths and emit a signal
|
|
47
|
+
// as soon as a single match is found.
|
|
48
|
+
for path in suspiciousPaths
|
|
49
|
+
where FileManager.default.fileExists(atPath: path) {
|
|
50
|
+
|
|
51
|
+
return [[
|
|
52
|
+
"id": "ios_jailbreak_path",
|
|
53
|
+
"category": "jailbreak",
|
|
54
|
+
"confidence": "high",
|
|
55
|
+
|
|
56
|
+
// Description is included only when debug output is enabled.
|
|
57
|
+
"description": includeDebug
|
|
58
|
+
? "Filesystem contains paths commonly associated with jailbroken devices"
|
|
59
|
+
: nil,
|
|
60
|
+
|
|
61
|
+
// Include the matched path for diagnostic purposes only.
|
|
62
|
+
"metadata": [
|
|
63
|
+
"path": path,
|
|
64
|
+
"source": "filesystem",
|
|
65
|
+
// Added explicit detection method to metadata
|
|
66
|
+
"detection_method": "file_exists"
|
|
67
|
+
]
|
|
68
|
+
].compactMapValues { $0 }]
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// No jailbreak-related filesystem indicators detected.
|
|
72
|
+
return []
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
Detects jailbreak-related applications by probing URL schemes.
|
|
5
|
+
|
|
6
|
+
IMPORTANT:
|
|
7
|
+
- This detection is opt-in via native configuration.
|
|
8
|
+
- Requires LSApplicationQueriesSchemes to be declared in Info.plist.
|
|
9
|
+
- This is a LOW confidence heuristic and must never be used alone.
|
|
10
|
+
*/
|
|
11
|
+
struct IntegrityJailbreakUrlSchemeDetector {
|
|
12
|
+
|
|
13
|
+
static func detect(
|
|
14
|
+
schemes: [String],
|
|
15
|
+
includeDebug: Bool
|
|
16
|
+
) -> [String: Any]? {
|
|
17
|
+
|
|
18
|
+
guard !schemes.isEmpty else { return nil }
|
|
19
|
+
|
|
20
|
+
for scheme in schemes {
|
|
21
|
+
let urlString = "\(scheme)://"
|
|
22
|
+
guard let url = URL(string: urlString) else { continue }
|
|
23
|
+
|
|
24
|
+
if UIApplication.shared.canOpenURL(url) {
|
|
25
|
+
return [
|
|
26
|
+
"id": "ios_jailbreak_url_scheme",
|
|
27
|
+
"category": "jailbreak",
|
|
28
|
+
"confidence": "low",
|
|
29
|
+
"description": includeDebug
|
|
30
|
+
? "Detected jailbreak-related application via URL scheme probing"
|
|
31
|
+
: nil,
|
|
32
|
+
"metadata": [
|
|
33
|
+
"scheme": scheme,
|
|
34
|
+
"source": "url_scheme"
|
|
35
|
+
]
|
|
36
|
+
].compactMapValues { $0 }
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return nil
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import DeviceCheck
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
Handles remote attestation signals using Apple's DeviceCheck (App Attest) framework.
|
|
6
|
+
|
|
7
|
+
IMPORTANT:
|
|
8
|
+
- App Attest is NOT implemented yet.
|
|
9
|
+
- This component explicitly reports unavailability instead of failing silently.
|
|
10
|
+
- The emitted signal is observational only and LOW confidence.
|
|
11
|
+
*/
|
|
12
|
+
struct IntegrityRemoteAttestor {
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
Returns a LOW confidence signal indicating that App Attest
|
|
16
|
+
is currently not available or not implemented.
|
|
17
|
+
|
|
18
|
+
This signal is emitted only when strict mode is requested.
|
|
19
|
+
*/
|
|
20
|
+
static func getAppAttestSignal(options: IntegrityCheckOptions) -> [String: Any]? {
|
|
21
|
+
|
|
22
|
+
guard options.level == "strict" else {
|
|
23
|
+
return nil
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return [
|
|
27
|
+
"id": "ios_app_attest_unavailable",
|
|
28
|
+
"category": "environment",
|
|
29
|
+
"confidence": "low",
|
|
30
|
+
"description": options.includeDebugInfo == true
|
|
31
|
+
? "Apple App Attest is not implemented or not available on this device"
|
|
32
|
+
: nil,
|
|
33
|
+
"metadata": [
|
|
34
|
+
"attestation": "unsupported",
|
|
35
|
+
"framework": "DeviceCheck",
|
|
36
|
+
"reason": "not_implemented"
|
|
37
|
+
]
|
|
38
|
+
].compactMapValues { $0 }
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
Runtime integrity checks related to debugging
|
|
5
|
+
and instrumentation frameworks.
|
|
6
|
+
*/
|
|
7
|
+
struct IntegrityRuntimeChecks {
|
|
8
|
+
|
|
9
|
+
static func debugSignals(
|
|
10
|
+
includeDebug: Bool
|
|
11
|
+
) -> [[String: Any]] {
|
|
12
|
+
|
|
13
|
+
// Collected runtime debug-related signals.
|
|
14
|
+
var signals: [[String: Any]] = []
|
|
15
|
+
|
|
16
|
+
// 1. Check if a debugger is attached via sysctl
|
|
17
|
+
var info = kinfo_proc()
|
|
18
|
+
var size = MemoryLayout.size(ofValue: info)
|
|
19
|
+
var mib: [Int32] = [
|
|
20
|
+
CTL_KERN,
|
|
21
|
+
KERN_PROC,
|
|
22
|
+
KERN_PROC_PID,
|
|
23
|
+
getpid()
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
if sysctl(&mib, UInt32(mib.count), &info, &size, nil, 0) == 0 {
|
|
27
|
+
if (info.kp_proc.p_flag & P_TRACED) != 0 {
|
|
28
|
+
signals.append([
|
|
29
|
+
"id": "ios_debugger_attached",
|
|
30
|
+
"category": "debug",
|
|
31
|
+
"confidence": "high",
|
|
32
|
+
"description": includeDebug
|
|
33
|
+
? "A debugger is currently attached to the running process"
|
|
34
|
+
: nil,
|
|
35
|
+
"metadata": ["method": "sysctl_p_traced"]
|
|
36
|
+
].compactMapValues { $0 })
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 2. Check for development/debug provisioning profile
|
|
41
|
+
// This mirrors the Android FLAG_DEBUGGABLE check.
|
|
42
|
+
if let path = Bundle.main.path(forResource: "embedded", ofType: "mobileprovision") {
|
|
43
|
+
do {
|
|
44
|
+
let profileContent = try String(contentsOfFile: path, encoding: .ascii)
|
|
45
|
+
if profileContent.contains("<key>get-task-allow</key>\n\t\t<true/>") {
|
|
46
|
+
signals.append([
|
|
47
|
+
"id": "ios_runtime_debuggable",
|
|
48
|
+
"category": "debug",
|
|
49
|
+
"confidence": "medium",
|
|
50
|
+
"description": includeDebug
|
|
51
|
+
? "Application is signed with a profile that allows debugging"
|
|
52
|
+
: nil,
|
|
53
|
+
"metadata": ["entitlement": "get-task-allow"]
|
|
54
|
+
].compactMapValues { $0 })
|
|
55
|
+
}
|
|
56
|
+
} catch {
|
|
57
|
+
// Ignore errors reading the profile
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return signals
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Detects if the application is running in an iOS simulator environment.
|
|
5
|
+
*/
|
|
6
|
+
struct IntegritySimulatorChecks {
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Determines whether the current process is running under the iOS Simulator.
|
|
10
|
+
*
|
|
11
|
+
* NOTE: This uses compile-time environment checks and is not spoofable at runtime.
|
|
12
|
+
*/
|
|
13
|
+
static func isSimulator() -> Bool {
|
|
14
|
+
#if targetEnvironment(simulator)
|
|
15
|
+
return true
|
|
16
|
+
#else
|
|
17
|
+
return false
|
|
18
|
+
#endif
|
|
19
|
+
}
|
|
20
|
+
}
|