Skip to content
Open
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
31 changes: 31 additions & 0 deletions demos/android/MASVS-RESILIENCE/MASTG-DEMO-0033/MASTG-DEMO-0033.md
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.
Copy link
Collaborator

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?

Copy link
Collaborator Author

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.

97 changes: 97 additions & 0 deletions demos/android/MASVS-RESILIENCE/MASTG-DEMO-0033/MastgTest.kt
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
}
}
}
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;
}
}
12 changes: 12 additions & 0 deletions demos/android/MASVS-RESILIENCE/MASTG-DEMO-0033/output.txt
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"});

1 change: 1 addition & 0 deletions demos/android/MASVS-RESILIENCE/MASTG-DEMO-0033/run.sh
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
27 changes: 27 additions & 0 deletions rules/mastg-android-root-detection.yml
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) {
$_;
}
38 changes: 38 additions & 0 deletions tests-beta/android/MASVS-RESILIENCE/MASTG-TEST-0245.md
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
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.
28 changes: 28 additions & 0 deletions tests-beta/android/MASVS-RESILIENCE/MASTG-TEST-0246.md
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.
3 changes: 3 additions & 0 deletions tests/android/MASVS-RESILIENCE/MASTG-TEST-0045.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ title: Testing Root Detection
masvs_v1_levels:
- R
profiles: [R]
status: deprecated
covered_by: [MASTG-TEST-0245, MASTG-TEST-0246]
deprecation_note: New version available in MASTG V2
---

## Bypassing Root Detection
Expand Down
Loading