Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/additional checks #11

Merged
merged 4 commits into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ Already implemented Features are:
- [x] Jailbreak or Root Detection
- [x] Hooks Detection
- [x] Simulator Detection
- [x] Debugger Detection
- [x] Device Passcode Check
- [x] Hardware Security Check

You can see them in action with the [Example App](./SecurityToolkitExample) we've provided

Expand Down Expand Up @@ -62,10 +65,8 @@ Use Async Stream API to get detected threats asynchronously:

Next features to be implemented:
- [ ] App Signature Check
- [ ] Debugger Detection
- [ ] Device Passcode Check
- [ ] Integrity Check
- [ ] Hardware Security Check


## Contributing

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
"threat.simulator.title" = "Simulator";
"threat.simulator.description" = "Running the application in a simulator";
"threat.debugger.title" = "Debugger";
"threat.debugger.description" = "A tool that allows developers to inspect and modify the execution of a program in real-time, potentially exposing sensitive data or allowing unauthorized control.";
"threat.debugger.description" = "A tool that allows developers to inspect and modify the execution of a program in real-time, potentially exposing sensitive data or allowing unauthorized control";
"threat.passcodeUnprotectedDevice.title" = "Passcode";
"threat.passcodeUnprotectedDevice.description" = "Indicates if current device is unprotected with a passcode. Biometric protection requires a passcode to be set up";
"threat.hardware.title" = "Hardware protection";
"threat.hardware.description" = "Refers to hardware capabilities of current device, specific to Secure Enclave layer. If not available, no additional hardware security layer can be used when working with keys, certificates and keychain";

// MARK: Threat List
"threat.list.title" = "Protection";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,17 @@ struct ThreatStatus: Hashable {
ThreatStatus(
title: R.string.localizable.threatDebuggerTitle(),
description: R.string.localizable.threatDebuggerDescription(),
isDetected: ThreatDetectionCenter.isDebuggerDetected
isDetected: ThreatDetectionCenter.isDebuggerDetected ?? false
),
ThreatStatus(
title: R.string.localizable.threatPasscodeUnprotectedDeviceTitle(),
description: R.string.localizable.threatPasscodeUnprotectedDeviceDescription(),
isDetected: ThreatDetectionCenter.isDeviceWithoutPasscodeDetected
),
ThreatStatus(
title: R.string.localizable.threatHardwareTitle(),
description: R.string.localizable.threatHardwareDescription(),
isDetected: ThreatDetectionCenter.isHardwareProtectionUnavailable
),
]
}
Expand Down
92 changes: 82 additions & 10 deletions Sources/ThreatDetectionCenter.swift
Original file line number Diff line number Diff line change
@@ -1,33 +1,95 @@
import Foundation

public final class ThreatDetectionCenter {


/// Will check if jailbreak is present
///
/// - Returns:
/// `true`, if device is / was jailbroken;
/// `false` otherwise
///
/// More about jailbreak: https://wikipedia.org/wiki/Jailbreak_%28iOS%29
///
/// > Should also detect jailbreak, even if the device is in a "safe" mode or
/// jailbreak mode is not active / was not properly removed
public static var areRootPrivilegesDetected: Bool {
JailbreakDetection.threatDetected()
}


/// Will check for an injection tool like Frida
///
/// - Returns:
/// `true`, if dynamic hooks are loaded at the time;
/// `false` otherwise
///
/// More: https://fingerprint.com/blog/exploring-frida-dynamic-instrumentation-tool-kit/
///
/// > By the nature of dynamic hooks, this checks should be made on a regular
/// basis, given the attacker may chose to hook a function at a later time
/// after the app started
///
/// > Important: with a sufficient reverse engineering skills, this check can
/// be disabled. Use always in combination with another threats detections.
public static var areHooksDetected: Bool {
HooksDetection.threatDetected()
}


/// Will check, if the app runs in a emulated / simulated environment
///
/// - Returns:
/// `true`, if simulator environment is detected;
/// `false` otherwise
public static var isSimulatorDetected: Bool {
SimulatorDetection.threatDetected()
}

/// Will check if your application is being traced by a debugger.
/// Will check, if the application is being traced by a debugger.
///
/// - Returns:
/// `true`: If a debugger is detected.
/// `false`: If no debugger is detected.
/// `nil`: The detection process did not produce a definitive result. This could happen due to system limitations, lack of required permissions, or other undefined conditions.
/// `true`, if a debugger is detected;
/// `false`, if no debugger is detected;
/// `nil`, if the detection process did not produce a definitive result.
/// This could happen due to system limitations, lack of required
/// permissions, or other undefined conditions.
///
/// A debugger is a tool that allows developers to inspect and modify the execution of a program in real-time, potentially exposing sensitive data or allowing unauthorized control.
/// A debugger is a tool that allows developers to inspect and modify the
/// execution of a program in real-time, potentially exposing sensitive data
/// or allowing unauthorized control.
///
/// ## Notes
/// Please note that Apple itself may require a debugger for the app review process.
/// > Please note that Apple itself may require a debugger for the app review
/// process.
public static var isDebuggerDetected: Bool? {
DebuggerDetection.threatDetected()
}

/// Will check, if current device is protected with at least a passcode
///
/// - Returns:
/// `true`, if device is unprotected;
/// `false`, if device is protected with at least a passcode
public static var isDeviceWithoutPasscodeDetected: Bool {
DevicePasscodeDetection.threatDetected()
}

/// Will check, if current device has hardware protection layer
/// (Secure Enclave)
///
/// More: https://support.apple.com/en-us/guide/security/secf020d1074/web
///
/// More: https://developer.apple.com/documentation/security/protecting-keys-with-the-secure-enclave
///
/// - Returns:
/// `true`, if device has no hardware protection;
/// `false` otherwise
///
/// > Should be evaluated on a real device. Should only be used as an
/// indicator, if current device is capable of hardware protection. Does not
/// automatically mean, that encryption operations (keys, certificates,
/// keychain) are always backed by hardware. You should make sure, such
/// operations are implemented correctly with hardware layer
public static var isHardwareProtectionUnavailable: Bool {
HardwareSecurityDetection.threatDetected()
}


// MARK: - Async Threat Detection
Expand All @@ -38,6 +100,8 @@ public final class ThreatDetectionCenter {
case hooks
case simulator
case debugger
case deviceWithoutPasscode
case hardwareProtectionUnavailable
}

/// Stream that contains possible threats that could be detected
Expand All @@ -59,7 +123,15 @@ public final class ThreatDetectionCenter {
if DebuggerDetection.threatDetected() ?? false {
continuation.yield(.debugger)
}

if DevicePasscodeDetection.threatDetected() {
continuation.yield(.deviceWithoutPasscode)
}

if HardwareSecurityDetection.threatDetected() {
continuation.yield(.hardwareProtectionUnavailable)
}

continuation.finish()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ fileprivate extension DebuggerDetection {
/// if the process is traced
private static func hasTracerFlagSet() -> Bool? {
var info = kinfo_proc()
var mib: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()] // Kernel info, process info, specific process by PID, get current process ID
// Kernel info, process info, specific process by PID, get current process ID
var mib: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()]
var size = MemoryLayout.stride(ofValue: info)

let unixStatusCode = sysctl(&mib, u_int(mib.count), &info, &size, nil, 0)
Expand Down
31 changes: 31 additions & 0 deletions Sources/internal/DevicePasscodeDetection.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import Foundation
import LocalAuthentication

// MARK: - Internal
internal final class DevicePasscodeDetection {

static func threatDetected() -> Bool {
!hasDevicePasscode()
}
}

// MARK: - Private
fileprivate extension DevicePasscodeDetection {

/// Will check if the user can perform authentication with a biometrics or
/// a passcode
private static func hasDevicePasscode() -> Bool {
var error: NSError?
let result = LAContext().canEvaluatePolicy(
.deviceOwnerAuthentication,
error: &error
)
if result {
return true
} else if let error {
return error.code != LAError.passcodeNotSet.rawValue
} else {
return false
}
}
}
35 changes: 35 additions & 0 deletions Sources/internal/HardwareSecurityDetection.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import Foundation
import LocalAuthentication

// MARK: - Internal
internal final class HardwareSecurityDetection {

static func threatDetected() -> Bool {
!isSecureEnclaveAvailable()
}
}

// MARK: - Private
fileprivate extension HardwareSecurityDetection {

/// See https://stackoverflow.com/a/49318485/7484013
private static func isSecureEnclaveAvailable() -> Bool {
var error: NSError?

/// Policies can have certain requirements which, when not satisfied,
/// would always cause the policy evaluation to fail - e.g. a passcode
/// set, a fingerprint enrolled with Touch ID or a face set up with
/// Face ID. This method allows easy checking for such conditions.
let result = LAContext().canEvaluatePolicy(
.deviceOwnerAuthenticationWithBiometrics,
error: &error
)
if result {
return true
} else if let error {
return error.code != LAError.biometryNotAvailable.rawValue
} else {
return false
}
}
}
File renamed without changes.
Binary file modified docs/1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.