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

Port MASTG-TEST-0045: Testing Root Detection (android) #3136

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
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 passes because root detection checks are implemented in the app.
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) {
$_;
}
45 changes: 45 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,45 @@
---
title: Root Indicators Detection
platform: android
id: MASTG-TEST-0245
type: [static]
weakness: MASWE-0097
best-practices: []
false_negative_prone: true
---

## Overview

This test verifies that a mobile app can accurately detect if the Android device it is running on is rooted.

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, suspicious files or directories, and modified system properties on the device itself. It does so by statically analyzing the device for [common root detection checks](../../../Document/0x05j-Testing-Resiliency-Against-Reverse-Engineering.md#root-etection-and-common-root-detection-methods).

## Steps

1. **Check for root detection indicators:**

- Apps may check for the presence of files commonly associated with rooted devices (e.g., /system/xbin/su, /data/data/com.superuser.android.id) or for root management apps (e.g., SuperSU, Magisk).
- Run a static analysis tool such as @MASTG-TOOL-0002 or @MASTG-TOOL-0011 on the app binary to look for common root detection checks.

2. **Non-standard system behavior detection:**

- Check if the app monitors processes that shouldn’t normally be running, such as su or sh, which are typically associated with root management tools.

Check failure on line 26 in tests-beta/android/MASVS-RESILIENCE/MASTG-TEST-0245.md

View workflow job for this annotation

GitHub Actions / markdown-lint-check

Custom rule

tests-beta/android/MASVS-RESILIENCE/MASTG-TEST-0245.md:26:54 search-replace Custom rule [curly-single-quotes: Don't use curly single quotes] [Context: "column: 54 text:'’'"] https://github.com/OnkarRuikar/markdownlint-rule-search-replace
- Reviewing the app’s smali or assembler code can reveal whether the app checks for or interacts with such processes.

Check failure on line 27 in tests-beta/android/MASVS-RESILIENCE/MASTG-TEST-0245.md

View workflow job for this annotation

GitHub Actions / markdown-lint-check

Custom rule

tests-beta/android/MASVS-RESILIENCE/MASTG-TEST-0245.md:27:23 search-replace Custom rule [curly-single-quotes: Don't use curly single quotes] [Context: "column: 23 text:'’'"] https://github.com/OnkarRuikar/markdownlint-rule-search-replace

3. **System properties modification detection:**

- Apps may monitor system properties (e.g., ro.debuggable, ro.secure) for changes, adding another layer to the root detection process.

4. **Critical system directories modification detection:**

- Check if the app attempts to modify files or settings in critical system directories, such as /data or /system, which should remain immutable on unrooted devices.

## Observation

The output should include any instances of common root detection checks in the app binary.

## Evaluation

The test passes if the root detection mechanisms are correctly implemented to identify indicators of root access.

The test is considered unsuccessful if the app does not implement root detection mechanisms.This test is not exhaustive and may not identify all possible root detection checks. More advanced techniques, such as manual reverse engineering or deobfuscation, might be necessary to uncover additional, more sophisticated root detection methods.
32 changes: 32 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,32 @@
---
title: Root Detection Resilience Testing
platform: android
id: MASTG-TEST-0246
type: [dynamic]
weakness: MASWE-0097
best-practices: []
---

## Overview

This test is designed to evaluate whether a mobile app attempts to detect if the Android device it is running on is rooted. The goal is to confirm that the app actively checks for signs of root access, in order to mitigate the security risks associated with rooted devices.

The test is conducted by dynamically analyzing the app binary for [common root detection checks](../../../Document/0x05j-Testing-Resiliency-Against-Reverse-Engineering.md#root-etection-and-common-root-detection-methods), ensuring that the app performs relevant checks to identify potential root access.

## 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 passes if the automated root detection bypass confirms that the application actively checks for known root artifacts.

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 @@ -7,6 +7,9 @@ platform: android
title: Testing Root Detection
masvs_v1_levels:
- 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