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

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 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 @@ -7,6 +7,10 @@
"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.unprotectedDevice.title" = "Unprotected device";
"threat.unprotectedDevice.description" = "Indicates if current device is password-protected. 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.threatUnprotectedDeviceTitle(),
description: R.string.localizable.threatUnprotectedDeviceDescription(),
isDetected: ThreatDetectionCenter.isUnprotectedDeviceDetected
),
ThreatStatus(
title: R.string.localizable.threatHardwareTitle(),
description: R.string.localizable.threatHardwareDescription(),
isDetected: ThreatDetectionCenter.isHardwareProtectionUnavailable
),
]
}
Expand Down
93 changes: 84 additions & 9 deletions Sources/ThreatDetectionCenter.swift
Original file line number Diff line number Diff line change
@@ -1,33 +1,98 @@
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
///
/// ## Notes
/// 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/
///
/// ## Notes
/// By the nature of dynamic hooks, this checks should be made on a regular
theDeniZ marked this conversation as resolved.
Show resolved Hide resolved
/// 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
theDeniZ marked this conversation as resolved.
Show resolved Hide resolved
/// 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 isUnprotectedDeviceDetected: 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
///
/// ## Notes
/// 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 +103,8 @@ public final class ThreatDetectionCenter {
case hooks
case simulator
case debugger
case unprotectedDevice
case hardwareProtectionUnavailable
}

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

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

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 e = error {
return e.code != LAError.passcodeNotSet.rawValue
theDeniZ marked this conversation as resolved.
Show resolved Hide resolved
} else {
return false
}
}
}
39 changes: 39 additions & 0 deletions Sources/internal/HardwareSecurityDetection.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
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 e = error {
theDeniZ marked this conversation as resolved.
Show resolved Hide resolved
if #available(iOS 11, *) {
theDeniZ marked this conversation as resolved.
Show resolved Hide resolved
return e.code != LAError.biometryNotAvailable.rawValue
} else {
return e.code != LAError.touchIDNotAvailable.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.