From 5c1a66b0f45096bc8d3833adb835cce62c72f3bc Mon Sep 17 00:00:00 2001 From: "Halder, Jannis" Date: Mon, 10 Feb 2025 13:21:27 +0100 Subject: [PATCH 1/3] add DebuggerDetection --- .../securitytoolkit/ThreatDetectionCenter.kt | 9 +++ .../internal/DebuggerDetection.kt | 65 +++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 securitytoolkit/src/main/java/com/exxeta/securitytoolkit/internal/DebuggerDetection.kt diff --git a/securitytoolkit/src/main/java/com/exxeta/securitytoolkit/ThreatDetectionCenter.kt b/securitytoolkit/src/main/java/com/exxeta/securitytoolkit/ThreatDetectionCenter.kt index daba9a8..4f89361 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() == true) { + 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..600c473 --- /dev/null +++ b/securitytoolkit/src/main/java/com/exxeta/securitytoolkit/internal/DebuggerDetection.kt @@ -0,0 +1,65 @@ +package com.exxeta.securitytoolkit.internal + +import android.os.Debug +import java.io.FileReader +import java.io.IOException +import java.net.InetSocketAddress +import java.net.Socket + + +/** + * A Detector object for debugger + */ +internal object DebuggerDetection { + + private val adbPorts = (5555..5585 step 2).map { it.toString() } + private val adbConsolePorts = (5554..5584 step 2).map { it.toString() } + + /** + * Exposes public API to detect debugger + * + * @return + * - `true` if debugger is detected. + * - `false` if no debugger is detected. + * - `null` 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. + */ + fun threatDetected(): Boolean? { + val isTracerPidSet = isTracerPidSet() ?: return null + return Debug.isDebuggerConnected() || Debug.waitingForDebugger() || isAdbPortListening() || isTracerPidSet + } + + private fun isTracerPidSet(): Boolean? { + var isTracerPidSet: Boolean? = false + try { + FileReader("/proc/self/status").buffered().use { br -> + isTracerPidSet = br.lines().anyMatch { + it.startsWith("TracerPid:") && it.split(":")[1].trim() != "0" + } + } + } catch (e: IOException) { + isTracerPidSet = null + } + return isTracerPidSet + } + + private fun isAdbPortListening(): Boolean { + return adbPorts.any { isPortInUse(it.toInt()) } + } + + private fun isPortInUse(port: Int): Boolean { + var isPortInUse = false + try { + val address = InetSocketAddress("127.0.0.1", port) + Socket().use { socket -> + socket.connect(address, 200) + isPortInUse = true + } + } catch (e: Exception) { + isPortInUse = false + } + return isPortInUse + } + +} From 62af639a318a6b6d421520138a6e7eef35b248e3 Mon Sep 17 00:00:00 2001 From: "Halder, Jannis" Date: Mon, 10 Feb 2025 13:21:54 +0100 Subject: [PATCH 2/3] add DebuggerDetection to example app --- .../exxeta/mobilesecuritytoolkitexample/ThreatStatusList.kt | 5 +++++ 1 file changed, 5 insertions(+) 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", From db7a940203586d19e2b13329f21dc3b8df01f12e Mon Sep 17 00:00:00 2001 From: "Halder, Jannis" Date: Tue, 11 Feb 2025 09:38:11 +0100 Subject: [PATCH 3/3] simplify checks, remove unused code - threatDetected() condition fixed and simplified. - private isTracerPidSet() simplified as a single-expression function. - private isPortInUse() simplified as a single-expression function. - removed unnecessary string casting from val adbPorts list. - removed val adbConsolePorts (not yet used). --- .../securitytoolkit/ThreatDetectionCenter.kt | 4 +- .../internal/DebuggerDetection.kt | 47 +++++++------------ 2 files changed, 18 insertions(+), 33 deletions(-) diff --git a/securitytoolkit/src/main/java/com/exxeta/securitytoolkit/ThreatDetectionCenter.kt b/securitytoolkit/src/main/java/com/exxeta/securitytoolkit/ThreatDetectionCenter.kt index 4f89361..3d5f75c 100644 --- a/securitytoolkit/src/main/java/com/exxeta/securitytoolkit/ThreatDetectionCenter.kt +++ b/securitytoolkit/src/main/java/com/exxeta/securitytoolkit/ThreatDetectionCenter.kt @@ -54,7 +54,7 @@ class ThreatDetectionCenter(private val context: Context) { val isSimulatorDetected: Boolean get() = EmulatorDetector.threatDetected() - val isDebuggerDetected: Boolean? + val isDebuggerDetected: Boolean get() = DebuggerDetection.threatDetected() /** @@ -107,7 +107,7 @@ class ThreatDetectionCenter(private val context: Context) { if (EmulatorDetector.threatDetected()) { emit(Threat.SIMULATOR) } - if (DebuggerDetection.threatDetected() == true) { + if (DebuggerDetection.threatDetected()) { emit(Threat.DEBUGGER) } if (DevicePasscodeDetection.threatDetected(context)) { diff --git a/securitytoolkit/src/main/java/com/exxeta/securitytoolkit/internal/DebuggerDetection.kt b/securitytoolkit/src/main/java/com/exxeta/securitytoolkit/internal/DebuggerDetection.kt index 600c473..b77dc6a 100644 --- a/securitytoolkit/src/main/java/com/exxeta/securitytoolkit/internal/DebuggerDetection.kt +++ b/securitytoolkit/src/main/java/com/exxeta/securitytoolkit/internal/DebuggerDetection.kt @@ -2,7 +2,6 @@ package com.exxeta.securitytoolkit.internal import android.os.Debug import java.io.FileReader -import java.io.IOException import java.net.InetSocketAddress import java.net.Socket @@ -12,8 +11,7 @@ import java.net.Socket */ internal object DebuggerDetection { - private val adbPorts = (5555..5585 step 2).map { it.toString() } - private val adbConsolePorts = (5554..5584 step 2).map { it.toString() } + private val adbPorts = (5555..5585 step 2) /** * Exposes public API to detect debugger @@ -21,45 +19,32 @@ internal object DebuggerDetection { * @return * - `true` if debugger is detected. * - `false` if no debugger is detected. - * - `null` 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. */ - fun threatDetected(): Boolean? { - val isTracerPidSet = isTracerPidSet() ?: return null - return Debug.isDebuggerConnected() || Debug.waitingForDebugger() || isAdbPortListening() || isTracerPidSet + fun threatDetected(): Boolean { + return Debug.isDebuggerConnected() || Debug.waitingForDebugger() || isAdbPortListening() || isTracerPidSet() } - private fun isTracerPidSet(): Boolean? { - var isTracerPidSet: Boolean? = false - try { - FileReader("/proc/self/status").buffered().use { br -> - isTracerPidSet = br.lines().anyMatch { - it.startsWith("TracerPid:") && it.split(":")[1].trim() != "0" - } + 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: IOException) { - isTracerPidSet = null } - return isTracerPidSet + } catch (e: Throwable) { + false } private fun isAdbPortListening(): Boolean { - return adbPorts.any { isPortInUse(it.toInt()) } + return adbPorts.any { isPortInUse(it) } } - private fun isPortInUse(port: Int): Boolean { - var isPortInUse = false - try { - val address = InetSocketAddress("127.0.0.1", port) - Socket().use { socket -> - socket.connect(address, 200) - isPortInUse = true - } - } catch (e: Exception) { - isPortInUse = false + private fun isPortInUse(port: Int): Boolean = try { + Socket().use { socket -> + socket.connect(InetSocketAddress("127.0.0.1", port), 200) } - return isPortInUse + true + } catch (e: Throwable) { + false } }