-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Port MASTG-TEST-0045: Testing Root Detection (android) #3136
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
Open
martinzigrai
wants to merge
13
commits into
OWASP:master
Choose a base branch
from
martinzigrai:MASTG-TEST-0045
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
c90235a
MASTG-TEST-0045 test v2 (by @talsec)
eb6bdf3
Merge branch 'OWASP:master' into MASTG-TEST-0045
martinzigrai 4bce8c7
chore: resolve markdown linting issues and the file name conflict
06ac6fa
Merge branch 'OWASP:master' into MASTG-TEST-0045
martinzigrai e90acee
chore: tests + demo update.
martinzigrai c38d1de
Merge branch 'OWASP:master' into MASTG-TEST-0045
martinzigrai 92f08e5
fix: correct markdown formatting
martinzigrai 784ce8a
Merge branch 'master' into MASTG-TEST-0045
cpholguera ec18132
Apply suggestions from code review
martinzigrai ddb81e9
Merge branch 'OWASP:master' into MASTG-TEST-0045
martinzigrai 953fde8
fix: correct markdown formatting
martinzigrai b25548c
Merge branch 'OWASP:master' into MASTG-TEST-0045
martinzigrai 1baff0c
refactor: Replace SuperSU with KernelSU
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
31 changes: 31 additions & 0 deletions
31
demos/android/MASVS-RESILIENCE/MASTG-DEMO-0033/MASTG-DEMO-0033.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| --- | ||
| platform: android | ||
| title: Verifying root detection techniques in applications via static analysis | ||
| code: [kotlin] | ||
| id: MASTG-DEMO-0033 | ||
| test: MASTG-TEST-0245 | ||
| --- | ||
|
|
||
| ### Sample | ||
|
|
||
| The code snippet below shows sample code that performs root detection checks on the device. | ||
|
|
||
| {{ MastgTest.kt }} | ||
|
|
||
| ### Steps | ||
|
|
||
| 1. Let's run our @MASTG-TOOL-0110 rule against the reversed java code. | ||
|
|
||
| {{ ../../../../rules/mastg-android-root-detection.yml }} | ||
|
|
||
| {{ run.sh }} | ||
|
|
||
| ### Observation | ||
|
|
||
| The output reveals the presence of root detection mechanisms in the app, including the use of `Runtime.getRuntime().exec` to check for the `su` command. | ||
|
|
||
| {{ output.txt }} | ||
|
|
||
| ### Evaluation | ||
|
|
||
| The test fails because the app relies on only one root detection method. | ||
97 changes: 97 additions & 0 deletions
97
demos/android/MASVS-RESILIENCE/MASTG-DEMO-0033/MastgTest.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| package org.owasp.mastestapp | ||
|
|
||
| import android.util.Log | ||
| import android.content.Context | ||
| import java.io.BufferedReader | ||
| import java.io.File | ||
| import java.io.IOException | ||
| import java.io.InputStreamReader | ||
|
|
||
| class MastgTest(private val context: Context) { | ||
|
|
||
| companion object { | ||
| private const val TAG = "RootCheck" | ||
| } | ||
|
|
||
| fun mastgTest(): String { | ||
| return when { | ||
| checkRootFiles() || checkSuperUserApk() || checkSuCommand() || checkDangerousProperties() -> { | ||
| "Device is rooted" | ||
| } | ||
| else -> { | ||
| "Device is not rooted" | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private fun checkRootFiles(): Boolean { | ||
| val rootPaths = setOf( | ||
| "/system/app/Superuser.apk", | ||
| "/system/xbin/su", | ||
| "/system/bin/su", | ||
| "/sbin/su", | ||
| "/system/sd/xbin/su", | ||
| "/system/bin/.ext/.su", | ||
| "/system/usr/we-need-root/su-backup", | ||
| "/system/xbin/mu" | ||
| ) | ||
| rootPaths.forEach { path -> | ||
| if (File(path).exists()) { | ||
| Log.d(TAG, "Found root file: $path") | ||
| } | ||
| } | ||
| return rootPaths.any { path -> File(path).exists() } | ||
| } | ||
|
|
||
| private fun checkSuperUserApk(): Boolean { | ||
| val superUserApk = File("/system/app/Superuser.apk") | ||
| val exists = superUserApk.exists() | ||
| if (exists) { | ||
| Log.d(TAG, "Found Superuser.apk") | ||
| } | ||
| return exists | ||
| } | ||
|
|
||
| private fun checkSuCommand(): Boolean { | ||
| return try { | ||
| val process = Runtime.getRuntime().exec(arrayOf("which", "su")) | ||
| val reader = BufferedReader(InputStreamReader(process.inputStream)) | ||
| val result = reader.readLine() | ||
| if (result != null) { | ||
| Log.d(TAG, "su command found at: $result") | ||
| true | ||
| } else { | ||
| Log.d(TAG, "su command not found") | ||
| false | ||
| } | ||
| } catch (e: IOException) { | ||
| Log.e(TAG, "Error checking su command: ${e.message}", e) | ||
| false | ||
| } | ||
| } | ||
|
|
||
| private fun checkDangerousProperties(): Boolean { | ||
| val dangerousProps = arrayOf("ro.debuggable", "ro.secure", "ro.build.tags") | ||
| dangerousProps.forEach { prop -> | ||
| val value = getSystemProperty(prop) | ||
| if (value != null) { | ||
| Log.d(TAG, "Dangerous property $prop: $value") | ||
| if (value.contains("debug")) { | ||
| return true | ||
| } | ||
| } | ||
| } | ||
| return false | ||
| } | ||
|
|
||
| private fun getSystemProperty(prop: String): String? { | ||
| return try { | ||
| val process = Runtime.getRuntime().exec(arrayOf("getprop", prop)) | ||
| val reader = BufferedReader(InputStreamReader(process.inputStream)) | ||
| reader.readLine() | ||
| } catch (e: IOException) { | ||
| Log.e(TAG, "Error checking system property $prop: ${e.message}", e) | ||
| null | ||
| } | ||
| } | ||
| } |
79 changes: 79 additions & 0 deletions
79
demos/android/MASVS-RESILIENCE/MASTG-DEMO-0033/MastgTest_reversed.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| package org.owasp.mastestapp; | ||
|
|
||
| import android.content.Context; | ||
| import android.util.Log; | ||
| import java.io.BufferedReader; | ||
| import java.io.File; | ||
| import java.io.IOException; | ||
| import java.io.InputStreamReader; | ||
| import java.util.Collection; | ||
| import kotlin.Metadata; | ||
| import kotlin.collections.CollectionsKt; | ||
| import kotlin.jvm.internal.Intrinsics; | ||
|
|
||
| /* compiled from: MastgTest.kt */ | ||
| @Metadata(d1 = {"\u0000 \n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0002\n\u0002\u0010\u000b\n\u0002\b\u0005\n\u0002\u0010\u000e\n\u0000\b\u0007\u0018\u00002\u00020\u0001B\r\u0012\u0006\u0010\u0002\u001a\u00020\u0003¢\u0006\u0002\u0010\u0004J\r\u0010\u0005\u001a\u00020\u0006H\u0000¢\u0006\u0002\b\u0007J\r\u0010\b\u001a\u00020\u0006H\u0000¢\u0006\u0002\b\tJ\b\u0010\n\u001a\u00020\u0006H\u0002J\u0006\u0010\u000b\u001a\u00020\fR\u000e\u0010\u0002\u001a\u00020\u0003X\u0082\u0004¢\u0006\u0002\n\u0000¨\u0006\r"}, d2 = {"Lorg/owasp/mastestapp/MastgTest;", "", "context", "Landroid/content/Context;", "(Landroid/content/Context;)V", "checkRootFiles", "", "checkRootFiles$app_debug", "checkSuCommand", "checkSuCommand$app_debug", "checkSuperUserApk", "mastgTest", "", "app_debug"}, k = 1, mv = {1, 9, 0}, xi = 48) | ||
| /* loaded from: classes4.dex */ | ||
| public final class MastgTest { | ||
| public static final int $stable = 8; | ||
| private final Context context; | ||
|
|
||
| public MastgTest(Context context) { | ||
| Intrinsics.checkNotNullParameter(context, "context"); | ||
| this.context = context; | ||
| } | ||
|
|
||
| public final String mastgTest() { | ||
| if (checkRootFiles$app_debug() || checkSuperUserApk() || checkSuCommand$app_debug()) { | ||
| return "Device is rooted"; | ||
| } | ||
| return "Device is not rooted"; | ||
| } | ||
|
|
||
| public final boolean checkRootFiles$app_debug() { | ||
| Iterable rootPaths = CollectionsKt.listOf((Object[]) new String[]{"/system/app/Superuser.apk", "/system/xbin/su", "/system/bin/su", "/sbin/su", "/system/sd/xbin/su", "/system/bin/.ext/.su", "/system/usr/we-need-root/su-backup", "/system/xbin/mu"}); | ||
| Iterable $this$forEach$iv = rootPaths; | ||
| for (Object element$iv : $this$forEach$iv) { | ||
| String path = (String) element$iv; | ||
| if (new File(path).exists()) { | ||
| Log.d("RootCheck", "Found root file: " + path); | ||
| } | ||
| } | ||
| Iterable $this$any$iv = rootPaths; | ||
| if (($this$any$iv instanceof Collection) && ((Collection) $this$any$iv).isEmpty()) { | ||
| return false; | ||
| } | ||
| for (Object element$iv2 : $this$any$iv) { | ||
| if (new File((String) element$iv2).exists()) { | ||
| return true; | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| private final boolean checkSuperUserApk() { | ||
| File superUserApk = new File("/system/app/Superuser.apk"); | ||
| if (superUserApk.exists()) { | ||
| Log.d("RootCheck", "Found Superuser.apk"); | ||
| } | ||
| return superUserApk.exists(); | ||
| } | ||
|
|
||
| public final boolean checkSuCommand$app_debug() { | ||
| boolean z = false; | ||
| try { | ||
| Process process = Runtime.getRuntime().exec(new String[]{"which", "su"}); | ||
| BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); | ||
| String result = reader.readLine(); | ||
| if (result != null) { | ||
| Log.d("RootCheck", "su command found at: " + result); | ||
| z = true; | ||
| } else { | ||
| Log.d("RootCheck", "su command not found"); | ||
| } | ||
| } catch (IOException e) { | ||
| Log.d("RootCheck", "Error checking su command: " + e.getMessage()); | ||
| } | ||
| return z; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
|
|
||
|
|
||
| ┌────────────────┐ | ||
| │ 1 Code Finding │ | ||
| └────────────────┘ | ||
|
|
||
| MastgTest_reversed.java | ||
| ❱ rules.mastg-android-root-detection | ||
| Root detection mechanisms have been identified in this application. | ||
|
|
||
| 65┆ Process process = Runtime.getRuntime().exec(new String[]{"which", "su"}); | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| NO_COLOR=true semgrep -c ../../../../rules/mastg-android-root-detection.yml ./MastgTest_reversed.java --text > output.txt |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| rules: | ||
| - id: mastg-android-root-detection | ||
| languages: [java, kotlin] | ||
| severity: INFO | ||
| message: Root detection mechanisms have been identified in this application. | ||
| patterns: | ||
| - pattern-either: | ||
| - pattern: File("/system/app/Superuser.apk").exists() | ||
| - pattern: File("/system/xbin/su").exists() | ||
| - pattern: File("/system/bin/su").exists() | ||
| - pattern: File("/sbin/su").exists() | ||
| - pattern: File("/system/sd/xbin/su").exists() | ||
| - pattern: File("/system/bin/.ext/.su").exists() | ||
| - pattern: File("/system/usr/we-need-root/su-backup").exists() | ||
| - pattern: File("/system/xbin/mu").exists() | ||
| - pattern: Runtime.getRuntime().exec("which su") | ||
| - pattern: Runtime.getRuntime().exec(arrayOf("which", "su")) | ||
| - pattern: Runtime.getRuntime().exec(arrayOf("getprop", "ro.debuggable")) | ||
| - pattern: Runtime.getRuntime().exec(arrayOf("getprop", "ro.secure")) | ||
| - pattern: Runtime.getRuntime().exec(arrayOf("getprop", "ro.build.tags")) | ||
| - pattern: Runtime.getRuntime().exec($_) | ||
| - pattern-not: | | ||
| try { | ||
| Runtime.getRuntime().exec($_); | ||
| } catch (Exception e) { | ||
| $_; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| --- | ||
| title: References to APIs for Root Detection | ||
| platform: android | ||
| id: MASTG-TEST-0245 | ||
| type: [static] | ||
| weakness: MASWE-0097 | ||
| best-practices: [] | ||
| false_negative_prone: true | ||
martinzigrai marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| apis: [Runtime.exec] | ||
| --- | ||
|
|
||
| ## Overview | ||
|
|
||
| This test checks if the app tries to detect whether the device is rooted. It does not guarantee that the device is secure because some rooting tools might bypass the detection techniques described below. You can use this test as an indicator that the app includes some root detection. | ||
|
|
||
| The testing process involves analyzing the device environment to identify common indicators of root access. This includes checking for the presence of: | ||
|
|
||
| - root management tools - e.g. Magisk, KernelSU | ||
| - suspicious files or directories - e.g `/system/bin/su`, `/system/xbin/su` | ||
| - modified system properties - e.g. `ro.debuggable`, `ro.secure` | ||
|
|
||
| ## Steps | ||
|
|
||
| 1. Run @MASTG-TECH-0014 with a tool such as @MASTG-TOOL-0110 on the app binary to detect root detections that use `Runtime.exec`, `File.exists()` and `getprop` APIs. | ||
|
|
||
| ## Observation | ||
|
|
||
| The output should include any instances of common root detection checks in the app binary. | ||
|
|
||
| ## Evaluation | ||
|
|
||
| The test fails if the app does not implement root detection mechanisms. This test is not exhaustive and may not identify all possible root detection checks because the detections may: | ||
|
|
||
| - be written in the native part of the app | ||
| - use different API than covered by this test | ||
| - be obfuscated | ||
|
|
||
| Even if the test uncovers root detections, they might not be sufficient against more advanced rooting tools. The most effective way is to test an app against a set of rooting tools. This test should only verify that the developer included the intended detection mechanisms. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| --- | ||
| title: Runtime Use Of Root Detection | ||
| platform: android | ||
| id: MASTG-TEST-0246 | ||
| type: [dynamic] | ||
| weakness: MASWE-0097 | ||
| best-practices: [] | ||
| --- | ||
|
|
||
| ## Overview | ||
|
|
||
| This test is the dynamic counterpart to @MASTG-TEST-0245. | ||
|
|
||
| ## Steps | ||
|
|
||
| 1. **Monitor Application Behaviour:** | ||
| - Use tools like strace or similar utilities to trace how the app checks for root access. Look for interactions with the system, such as attempts to open su, check running processes, or read root-specific files. This analysis helps uncover how the app performs root detection and may reveal potential weaknesses. | ||
|
|
||
| 2. **Bypassing Root Detection Mechanisms** | ||
| - Run a dynamic analysis tool such as @MASTG-TOOL-0038 to attempt automated root detection bypass. Use commands to manipulate root checks and observe whether the app still correctly detects root access or if its security mechanisms can be bypassed. | ||
|
|
||
| ## Observation | ||
|
|
||
| The output should include any observed instances of common root detection checks performed by the app and the results of the automated root detection bypass attempts. | ||
|
|
||
| ## Evaluation | ||
|
|
||
| The test fails if no root detection mechanisms are identified, indicating that the app does not attempt to detect root access. However, this test is not exhaustive, as it relies on predefined bypass techniques that may not cover all possible root detection methods or may be outdated. Additionally, some applications may use more advanced detection mechanisms that automated tools cannot easily identify, requiring manual reverse engineering and deobfuscation to fully assess their effectiveness. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The output.txt does indeed only include one hit, but the application checks for root using Runtime.exec and File.exists. So the detection rule should be improved?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a fair question. The detection rule is actually working as intended. It uses a pattern-either logic, so it's designed to report success as soon as it finds the first match in the code, which is why you only see one hit.