The sample code demonstrates an Android app that performs a root detection routine implemented in a native library.
The Java/Kotlin layer loads librootcheck.so and calls findRootArtifactPath(), a native function that checks common su binary paths. 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 native layer is not enough to prevent an attacker from reverse engineering the root detection logic with reasonable effort.
packageorg.owasp.mastestappimportandroid.app.Activityimportandroid.content.Contextimportandroid.util.Log// SUMMARY: This sample demonstrates a native root detection routine based on common su binary paths.classMastgTest(privatevalcontext:Context){// FAIL: [MASTG-TEST-0369] Due to weak code obfuscation, the root detection logic remains identifiable in the decompiled library.valshouldRunInMainThread=truefunmastgTest():String{valresults=DemoResults("0x52")returntry{valdetectedPath=findRootArtifactPath()if(detectedPath!=null){valmessage="Detected root artifact path '$detectedPath' and triggered the root-detection branch. The test fails because this security-relevant native logic remains identifiable through exported JNI symbols, cleartext su paths, and disassembly despite basic obfuscation."Log.w("MASTG-DEMO-0133",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()}}privatefuncloseApp(){valactivity=contextas?Activity?:returnactivity.finishAffinity()}privateexternalfunfindRootArtifactPath():String?companionobject{init{System.loadLibrary("rootcheck")}}}
#include<jni.h>#include<cerrno>#include<string>#include<unistd.h>namespace{constexprconstchar*kRootPaths[]={"/system/bin/su","/system/xbin/su","/sbin/su",};}// namespaceextern"C"JNIEXPORTjstringJNICALLJava_org_owasp_mastestapp_MastgTest_findRootArtifactPath(JNIEnv*env,jobject/* this */){for(constchar*path:kRootPaths){constintresult=access(path,F_OK);if(result==0||errno==EACCES){returnenv->NewStringUTF(path);}}returnnullptr;}
The app is not obfuscated at the Java/Kotlin layer, and the native library is included in the APK as-is without any protection. See Obfuscation for reference on common obfuscation techniques.
Note: Filtering strings with izz~su is a simplification used here for demonstration purposes. In a real-world scenario, the set of root-related strings and paths to look for is broader. Refer to Root Detection for a comprehensive list of root detection indicators.
The test case fails because the native library can be reverse engineered with little effort and the security-relevant root detection logic remains easy to identify.
The output reveals the paths /system/bin/su, /sbin/su, and /system/xbin/su stored as plaintext in .rodata, showing that the root detection indicators are not encoded or encrypted. Finding these strings alone is sufficient to demonstrate the test failure: they tell an attacker exactly what artifacts the library is looking for.
Note: For the purpose of this demo we stop here. In a real assessment you would go further and trace where these strings are used — for example by following cross-references or pivoting through file-access imports like access() — to fully map the detection logic and understand how the result is returned to the Java/Kotlin layer.
Additional Context:
If we use Decompiling Java Code to decompile the app, we'll also see that the Java/Kotlin layer isn't obfuscated, and the code helps correlate the native code with the security-relevant logic. For example, in MastgTest_reversed.java, lines 17, 29 to 37, 48 to 54, and 57 to 58 show that the Java/Kotlin layer loads librootcheck.so, calls the native findRootArtifactPath() function, logs the detected path, and closes the app through finishAffinity() when the native code reports a match.
packageorg.owasp.mastestapp;importandroid.app.Activity;importandroid.content.Context;importandroid.util.Log;importkotlin.Metadata;importkotlin.jvm.internal.Intrinsics;/*JADXINFO:compiledfrom:MastgTest.kt*//*JADXINFO:loadedfrom:classes2.dex*/@Metadata(d1={"\u0000(\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0003\n\u0002\u0010\u000b\n\u0002\b\u0003\n\u0002\u0010\u000e\n\u0000\n\u0002\u0010\u0002\n\u0002\b\u0003\b\u0007\u0018\u0000\u000f2\u00020\u0001:\u0001\u000fB\u000f\u0012\u0006\u0010\u0002\u001a\u00020\u0003¢\u0006\u0004\b\u0004\u0010\u0005J\u0006\u0010\n\u001a\u00020\u000bJ\b\u0010\f\u001a\u00020\rH\u0002J\u000b\u0010\u000e\u001a\u0004\u0018\u00010\u000bH\u0082 R\u000e\u0010\u0002\u001a\u00020\u0003X\u0082\u0004¢\u0006\u0002\n\u0000R\u0014\u0010\u0006\u001a\u00020\u0007X\u0086D¢\u0006\b\n\u0000\u001a\u0004\b\b\u0010\t¨\u0006\u0010"},d2={"Lorg/owasp/mastestapp/MastgTest;","","context","Landroid/content/Context;","<init>","(Landroid/content/Context;)V","shouldRunInMainThread","","getShouldRunInMainThread","()Z","mastgTest","","closeApp","","findRootArtifactPath","Companion","app_release"},k=1,mv={2,0,0},xi=48)publicfinalclassMastgTest{privatefinalContextcontext;privatefinalbooleanshouldRunInMainThread;publicstaticfinalint$stable=8;privatefinalnativeStringfindRootArtifactPath();publicMastgTest(Contextcontext){Intrinsics.checkNotNullParameter(context,"context");this.context=context;this.shouldRunInMainThread=true;}publicfinalbooleangetShouldRunInMainThread(){returnthis.shouldRunInMainThread;}publicfinalStringmastgTest(){DemoResultsdemoResults=newDemoResults("0x52");try{StringstrFindRootArtifactPath=findRootArtifactPath();if(strFindRootArtifactPath!=null){Stringstr="Detected root artifact path '"+strFindRootArtifactPath+"'. The app closes when a monitored su path is found.";Log.w("MASTG-DEMO-0133",str);demoResults.add(Status.FAIL,str);closeApp();}else{demoResults.add(Status.PASS,"No monitored su path was found.");}returndemoResults.toJson();}catch(Exceptione){demoResults.add(Status.ERROR,e.toString());returndemoResults.toJson();}}privatefinalvoidcloseApp(){Contextcontext=this.context;Activityactivity=contextinstanceofActivity?(Activity)context:null;if(activity==null){return;}activity.finishAffinity();}static{System.loadLibrary("rootcheck");}}