diff --git a/example/app/src/main/java/com/exxeta/mobilesecuritytoolkitexample/ThreatStatusList.kt b/example/app/src/main/java/com/exxeta/mobilesecuritytoolkitexample/ThreatStatusList.kt index 52c3660..de42d5e 100644 --- a/example/app/src/main/java/com/exxeta/mobilesecuritytoolkitexample/ThreatStatusList.kt +++ b/example/app/src/main/java/com/exxeta/mobilesecuritytoolkitexample/ThreatStatusList.kt @@ -59,6 +59,11 @@ fun ThreatStatusList() { "Running the application in an Emulator", reportedThreats.contains(ThreatDetectionCenter.Threat.SIMULATOR), ), + ThreatStatus( + "Debugger", + "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", + reportedThreats.contains(ThreatDetectionCenter.Threat.DEBUGGER), + ), ThreatStatus( "Passcode", "Indicates if current device is unprotected with a passcode. Biometric protection requires a passcode to be set up", diff --git a/securitytoolkit/src/main/java/com/exxeta/securitytoolkit/ThreatDetectionCenter.kt b/securitytoolkit/src/main/java/com/exxeta/securitytoolkit/ThreatDetectionCenter.kt index daba9a8..3d5f75c 100644 --- a/securitytoolkit/src/main/java/com/exxeta/securitytoolkit/ThreatDetectionCenter.kt +++ b/securitytoolkit/src/main/java/com/exxeta/securitytoolkit/ThreatDetectionCenter.kt @@ -2,6 +2,7 @@ package com.exxeta.securitytoolkit import android.content.Context import com.exxeta.securitytoolkit.internal.AppSignatureDetection +import com.exxeta.securitytoolkit.internal.DebuggerDetection import com.exxeta.securitytoolkit.internal.DevicePasscodeDetection import com.exxeta.securitytoolkit.internal.EmulatorDetector import com.exxeta.securitytoolkit.internal.HardwareSecurityDetection @@ -17,6 +18,7 @@ import kotlinx.coroutines.flow.flow * - [areHooksDetected]: to check for injection (hooking) tweaks * - [isSimulatorDetected]: to check if the app is running in a simulated * environment + * - [isDebuggerDetected]: to check if the app is being traced by a debugger * - [isDeviceWithoutPasscodeDetected]: to check if device is protected with a * passcode * - [isHardwareProtectionUnavailable]: to check if device can use @@ -52,6 +54,9 @@ class ThreatDetectionCenter(private val context: Context) { val isSimulatorDetected: Boolean get() = EmulatorDetector.threatDetected() + val isDebuggerDetected: Boolean + get() = DebuggerDetection.threatDetected() + /** * Performs check for Device Passcode presence * Returns `false`, when device is **unprotected** @@ -102,6 +107,9 @@ class ThreatDetectionCenter(private val context: Context) { if (EmulatorDetector.threatDetected()) { emit(Threat.SIMULATOR) } + if (DebuggerDetection.threatDetected()) { + emit(Threat.DEBUGGER) + } if (DevicePasscodeDetection.threatDetected(context)) { emit(Threat.DEVICE_WITHOUT_PASSCODE) } @@ -122,6 +130,7 @@ class ThreatDetectionCenter(private val context: Context) { ROOT_PRIVILEGES, HOOKS, SIMULATOR, + DEBUGGER, DEVICE_WITHOUT_PASSCODE, HARDWARE_PROTECTION_UNAVAILABLE, } diff --git a/securitytoolkit/src/main/java/com/exxeta/securitytoolkit/internal/DebuggerDetection.kt b/securitytoolkit/src/main/java/com/exxeta/securitytoolkit/internal/DebuggerDetection.kt new file mode 100644 index 0000000..b77dc6a --- /dev/null +++ b/securitytoolkit/src/main/java/com/exxeta/securitytoolkit/internal/DebuggerDetection.kt @@ -0,0 +1,50 @@ +package com.exxeta.securitytoolkit.internal + +import android.os.Debug +import java.io.FileReader +import java.net.InetSocketAddress +import java.net.Socket + + +/** + * A Detector object for debugger + */ +internal object DebuggerDetection { + + private val adbPorts = (5555..5585 step 2) + + /** + * Exposes public API to detect debugger + * + * @return + * - `true` if debugger is detected. + * - `false` if no debugger is detected. + */ + fun threatDetected(): Boolean { + return Debug.isDebuggerConnected() || Debug.waitingForDebugger() || isAdbPortListening() || isTracerPidSet() + } + + private fun isTracerPidSet(): Boolean = try { + FileReader("/proc/self/status").buffered().use { br -> + br.lines().anyMatch { + it.startsWith("TracerPid:") && it.split(":")[1].trim() != "0" + } + } + } catch (e: Throwable) { + false + } + + private fun isAdbPortListening(): Boolean { + return adbPorts.any { isPortInUse(it) } + } + + private fun isPortInUse(port: Int): Boolean = try { + Socket().use { socket -> + socket.connect(InetSocketAddress("127.0.0.1", port), 200) + } + true + } catch (e: Throwable) { + false + } + +}