The sample code demonstrates an Android app that performs a root detection routine implemented in the Java/Kotlin layer. The app checks whether well-known root manager packages are installed on the device.
packageorg.owasp.mastestappimportandroid.app.Activityimportandroid.content.Contextimportandroid.content.pm.PackageManagerimportandroid.os.Buildimportandroid.util.Log// SUMMARY: This sample demonstrates a Java/Kotlin root detection routine based on root manager package names.classMastgTest(privatevalcontext:Context){// FAIL: [MASTG-TEST-0368] Due to weak code obfuscation, the root detection logic remains identifiable in the decompiled Java/Kotlin code despite minification.valshouldRunInMainThread=trueprivatevalrootManagerPackages=listOf("com.topjohnwu.magisk","eu.chainfire.supersu","me.weishu.kernelsu")funmastgTest():String{valresults=DemoResults("0x51")returntry{valdetectedPackage=rootManagerPackages.firstOrNull(::isPackageInstalled)if(detectedPackage!=null){valmessage="Detected root manager package '$detectedPackage' and triggered the root-detection branch. The test fails because this security-relevant logic remains identifiable in the decompiled Java/Kotlin code despite minification."Log.w("MASTG-DEMO-0132",message)results.add(Status.FAIL,message)closeApp()}else{results.add(Status.PASS,"The root-detection branch was not triggered in this execution.")}results.toJson()}catch(e:Exception){results.add(Status.ERROR,e.toString())results.toJson()}}@Suppress("DEPRECATION")privatefunisPackageInstalled(packageName:String):Boolean{valpackageManager=context.packageManagerreturntry{if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.TIRAMISU){packageManager.getPackageInfo(packageName,PackageManager.PackageInfoFlags.of(0))}else{packageManager.getPackageInfo(packageName,0)}true}catch(_:PackageManager.NameNotFoundException){false}}privatefuncloseApp(){valactivity=contextas?Activity?:returnactivity.finishAffinity()}}
Regardless of whether this protection mechanism is sufficient to detect root in a real-world scenario, the purpose of this demo is to show that the obfuscation applied to the Java/Kotlin layer is not enough to prevent an attacker from reverse engineering the root detection logic with reasonable effort.
The app is obfuscated with R8, which applies identifier renaming and some code shrinking. However, no string encryption or control flow obfuscation is applied. See Obfuscation for reference on common obfuscation techniques.
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
-keepnames class org.owasp.mastestapp.MastgTest
-keepclassmembers class org.owasp.mastestapp.MastgTest {
public boolean getShouldRunInMainThread();
}
The test case fails because when performing reverse engineering on the minified Java/Kotlin layer it is possible to identify and understand the security-relevant root detection logic with little effort.
In MastgTest_reversed.java, lines 21, 24, and 32 show that R8 shortened member names (f7310a, f7311b, and a()), so some identifier renaming was clearly applied. However, lines 29, 47, 50, 52, 59, and 64 still reveal the monitored package names, the PackageManager lookups, the root detection message, and the finishAffinity() call, as the strings are not encrypted.
packageorg.owasp.mastestapp;importa3.i;importa3.m;importandroid.app.Activity;importandroid.content.Context;importandroid.content.pm.PackageManager;importandroid.os.Build;importandroid.util.Log;importh1.AbstractC0489n;importjava.util.Iterator;importjava.util.List;importkotlin.Metadata;importt1.AbstractC0902k;/*JADXINFO:loadedfrom:classes.dex*/@Metadata(d1={"\u0000\u0010\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\u0010\u000b\n\u0002\b\u0005\b\u0007\u0018\u00002\u00020\u0001R\u001a\u0010\u0003\u001a\u00020\u00028\u0006X\u0086D¢\u0006\f\n\u0004\b\u0003\u0010\u0004\u001a\u0004\b\u0005\u0010\u0006¨\u0006\u0007"},d2={"Lorg/owasp/mastestapp/MastgTest;","","","shouldRunInMainThread","Z","getShouldRunInMainThread","()Z","app_release"},k=1,mv={2,0,0},xi=48)publicfinalclassMastgTest{/*JADXINFO:renamedfrom:a,reason:collisionwithrootpackagename*/publicfinalContextf7310a;/*JADXINFO:renamedfrom:b,reason:collisionwithrootpackagename*/publicfinalListf7311b;publicMastgTest(Contextcontext){AbstractC0902k.e(context,"context");this.f7310a=context;this.f7311b=AbstractC0489n.I0("com.topjohnwu.magisk","eu.chainfire.supersu","me.weishu.kernelsu");}publicfinalStringa(){Contextcontext;Objectnext;iiVar=newi();try{Iteratorit=this.f7311b.iterator();while(true){booleanzHasNext=it.hasNext();context=this.f7310a;if(!zHasNext){next=null;break;}next=it.next();Stringstr=(String)next;PackageManagerpackageManager=context.getPackageManager();try{if(Build.VERSION.SDK_INT>=33){packageManager.getPackageInfo(str,PackageManager.PackageInfoFlags.of(0L));}else{packageManager.getPackageInfo(str,0);}}catch(PackageManager.NameNotFoundExceptionunused){}}Stringstr2=(String)next;if(str2!=null){Stringstr3="Detected root manager package '"+str2+"'. The app closes when root-related packages are found.";Log.w("MASTG-DEMO-0132",str3);iVar.a(m.f4493g,str3);Activityactivity=contextinstanceofActivity?(Activity)context:null;if(activity!=null){activity.finishAffinity();}}else{iVar.a(m.f4494h,"No monitored root manager package was found.");}returniVar.d();}catch(Exceptione3){iVar.a(m.f4495i,e3.toString());returniVar.d();}}publicfinalbooleangetShouldRunInMainThread(){returntrue;}}
This means that the reverse-engineered code still makes it straightforward to identify that the app is checking for root manager packages and closing the app when one is found, despite having some obfuscation applied in the form of identifier renaming.
Additional Context:
In AndroidManifest_reversed.xml, lines 14 to 16 show the package visibility queries for the monitored root manager packages.