BLOG
- Posted on: Apr 27, 2022
- By Ajay Pandita
- 6 Mins Read
- Last updated on: Oct 3, 2024
Phone manufacturers and mobile network operators often implement stringent software restrictions for security reasons. However, these constraints can be circumvented by rooting your Android phone.
Rooting is the process of gaining access to more administrative-level controls on an Android device. Despite its benefits, attackers often use rooting to target sensitive user and business data.
Security experts say 36 out of 1000 Android devices are rooted globally. Because a rooted device is considerably more vulnerable to attack, it's critical to use tools to detect this vulnerability. To ensure that your apps only run where, when, and how you want them to, you must first determine whether the device is rooted.
What is Android rooting?
Android is very similar to Linux because it runs on the Linux kernel. With access control similar to Linux, regular users of Android devices have very limited permissions compared to users who have rooted their devices.
Without rooting, users cannot access or modify system files and folders. Once rooted, the user has full access to the device. Rooting allows users to make changes to everything on the device, allowing users to do previously impossible things, like removing bloatware, customization, custom ROMs, etc.
What do we need root detection for?
Rooting Android devices has many benefits but also many security issues. Once you have root privileges, you have full control to make changes across the device. However, this also means your device is now an open target for threat actors.
Rooted devices may contain many apps that process sensitive information, such as banking, payment, social media, and cloud storage apps. Malicious downloads can expose your device to hackers. For these reasons, the apps installed on a device need to ensure that it isn't rooted. This is a precautionary measure to protect critical user and business information data.
Top 6 methods for Android root detection
Hackers frequently root devices to acquire access to sensitive user data and other insights housed in an app's source code or take advantage of devices that the owner has rooted. Detecting this vulnerability is critical to keeping apps safe because it is difficult to guarantee system security and other measures after the root.
Let us take a look at some of the most commonly used root detection methods:
1) SU binary files and packages
Many files and packages on Android devices can be checked to determine if the device is rooted or not. This might include the packages for the apps that are used to root the device, such as "eu.chainfire.supersu" and "com.topjohnwu.magick." The presence of apps with such package names shows the presence of root on the Android device.
The below piece of code detects whether or not any of the mentioned packages are present on the device.
static final String[] knownRootAppsPackages = { "com.noshufou.android.su.elite", "eu.chainfire.supersu", "com.koushikdutta.superuser", "com.thirdparty.superuser", "com.yellowes.su", "com.topjohnwu.magisk", "com.kingroot.kinguser", "com.kingo.root", "com.smedialink.oneclickroot", "com.zhiqupk.root.global", "com.alephzain.framaroot" }; public static boolean checkRootFilesAndPackages(Context context) { boolean result = false; for(String string1: knownRootAppsPackages) { if(isPackageInstalled(string1, context)) { result = true; break; } } return result; } private static boolean isPackageInstalled(String packagename, Context context){ PackageManager pm = context.getPackageManager(); try { pm.getPackageInfo(packagename, PackageManager.GET_ACTIVITIES); return true; } catch (NameNotFoundException e) { return false; } } |
The below piece of code tries to find the SU binary in commonly found locations.
static final String[] knownSUPaths = { "/system/bin/su", "/system/xbin/su", "/sbin/su", "/system/su", "/system/bin/.ext/.su", "/system/usr/we-need-root/su-backup", "/system/xbin/mu" }; public static boolean checkSUPaths() { boolean result = false; for(String string1: knownSUPaths) { File f = new File(string1); boolean fileExists = f.exists(); if (fileExists) { result = true; } } return result; } |
2) Custom ROMs
a) Build tags
A check can be implemented to test against custom ROMs on the Android device. We can achieve this by checking build tags. Usually, when a kernel is compiled by a third party, instead of the release keys, we can find test keys in the build tags. Being compiled by a third party shows the presence of a custom ROM on the Android device.
The following code can be used to detect if the build has test keys or release keys.
public boolean checkCustomOS(){ String buildTags = android.os.Build.TAGS; if (buildTags != null && buildTags.contains("test-keys")) { return true; } return false; } |
jasmine_sprout:/ # cat /system/build.prop | grep ro.build.tags ro.build.tags=release-keys jasmine_sprout:/ # |
b) Google OTA certificates
Along with the build tags, looking for Google Over-The-Air certificates is another way of detecting whether the device is a stock Android build or a custom ROM. The following command can detect whether the OTA certificates are present on the device.
public static boolean checkOTACerts() { String OTAPath = "/etc/security/otacerts.zip"; File f = new File(OTAPath); boolean fileExists = f.exists(); if (fileExists) { return true; } else { return false; } } |
jasmine_sprout:/ # ls -l /etc/security/otacerts.zip -rw-r--r-- 1 root root 1536 2009-01-01 05:30 /etc/security/otacerts.zip jasmine_sprout:/ # |
3) Checking for dangerous props
The build.prop file on an Android device contains system properties and build information applied throughout the operating device. Properties are stored in key-value pairs. The file is stored in the /system folder and is loaded every time the device is booted.
Some custom builds/rooting apps modify the build.prop on the Android device and change some of the properties that can only be done by a root user.
We can use the following code in our app to detect if the properties are modified. Some properties that are usually modified are ro.debuggable and ro.secure.
private String[] propsReader() { try { InputStream inputstream = Runtime.getRuntime().exec("getprop").getInputStream(); if (inputstream == null) return null; String propVal = new Scanner(inputstream).useDelimiter("\\A").next(); return propVal.split("\n"); } catch (IOException | NoSuchElementException e) { QLog.e(e); return null; } } public boolean checkForDangerousProps() { final Map<String, String> dangerousProps = new HashMap<>(); dangerousProps.put("ro.debuggable", "1"); dangerousProps.put("ro.secure", "0"); boolean result = false; String[] lines = propsReader(); if (lines == null){ return false; } for (String line : lines) { for (String key : dangerousProps.keySet()) { if (line.contains(key)) { String badValue = dangerousProps.get(key); badValue = "[" + badValue + "]"; if (line.contains(badValue)) { QLog.v(key + " = " + badValue + " detected!"); result = true; } } } } return result; } |
We can also use the following command to check the same from a shell.
adb -e shell getprop | grep -E "secure|dashboard" |
4) Common apps found on a rooted device
Another common method security experts use for root detection is to search for applications commonly found on a rooted device. Some examples are Busyboy, Titanium Backup, Xposed Manager, and Luckypatcher.
public static final String[] knownDangerousAppsPackages = { "com.koushikdutta.rommanager", "com.koushikdutta.rommanager.license", "com.dimonvideo.luckypatcher", "com.chelpus.lackypatch", "com.ramdroid.appquarantine", "com.ramdroid.appquarantinepro", "com.android.vending.billing.InAppBillingService.COIN", "com.android.vending.billing.InAppBillingService.LUCK", "com.chelpus.luckypatcher", "com.blackmartalpha", "org.blackmart.market", "com.allinone.free", "com.repodroid.app", "org.creeplays.hack", "com.baseappfull.fwd", "com.zmapp", "com.dv.marketmod.installer", "org.mobilism.android", "com.android.wp.net.log", "com.android.camera.update", "cc.madkite.freedom", "com.solohsu.android.edxp.manager", "org.meowcat.edxposed.manager", "com.xmodgame", "com.cih.game_cih", "com.charles.lpoqasert", "catch_.me_.if_.you_.can_" }; public boolean detectPotentiallyDangerousApps(String[] additionalDangerousApps) { ArrayList<String> packages = new ArrayList<>(); packages.addAll(Arrays.asList(Const.knownDangerousAppsPackages)); if (additionalDangerousApps!=null && additionalDangerousApps.length>0){ packages.addAll(Arrays.asList(additionalDangerousApps)); } return isAnyPackageFromListInstalled(packages); } |
Similarly, we can also use the shell to confirm some of the apps. For example, the command below can be run to see if Busybox is present.
busybox pwd |
5) Unusual Permissions For Partitions And System Directories
Sometimes, when a device is rooted, the permissions on some of the system directories change. By default, the system files and folders on Android devices are mounted as read-only. Once the device is rooted, we can find some files and folders with read-write permissions. We can check the permissions on the system directories to determine whether the device might have been rooted.
Following are some of the directories that we can check for permissions.
/data / /system /system/bin /system/sbin /system/xbin /vendor/bin /sys /sbin /etc /proc /dev |
Below is a perfect piece of code that checks for permissions on specific folders based on the SDK version of the software.
public boolean checkForRWPaths() { boolean result = false; String[] lines = mountReader(); if (lines == null){ return false; } int sdkVersion = android.os.Build.VERSION.SDK_INT; for (String line : lines) { String[] args = line.split(" "); if ((sdkVersion <= android.os.Build.VERSION_CODES.M && args.length < 4) || (sdkVersion > android.os.Build.VERSION_CODES.M && args.length < 6)) { QLog.e("Error formatting mount line: "+line); continue; } String mountPoint; String mountOptions; if (sdkVersion > android.os.Build.VERSION_CODES.M) { mountPoint = args[2]; mountOptions = args[5]; } else { mountPoint = args[1]; mountOptions = args[3]; } for(String pathToCheck: Const.pathsThatShouldNotBeWritable) { if (mountPoint.equalsIgnoreCase(pathToCheck)) { if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.M) { mountOptions = mountOptions.replace("(", ""); mountOptions = mountOptions.replace(")", ""); } for (String option : mountOptions.split(",")){ if (option.equalsIgnoreCase("rw")){ QLog.v(pathToCheck+" path is mounted with rw permissions! "+line); result = true; break; } } } } } return result; } |
6) Checking for commonly used root-cloaking apps
There are many root-cloaking apps available that can hide an Android device's root status so that other apps cannot detect whether it is rooted. We can list all the packages and search for the commonly used root-cloaking apps.
The code below is an example that can detect root-cloaking apps installed on the device.
public static final String[] knownRootCloakingPackages = { "com.devadvance.rootcloak", "com.devadvance.rootcloakplus", "de.robv.android.xposed.installer", "com.saurik.substrate", "com.zachspong.temprootremovejb", "com.amphoras.hidemyroot", "com.amphoras.hidemyrootadfree", "com.formyhm.hiderootPremium", "com.formyhm.hideroot" }; public boolean detectRootCloakingApps(String[] additionalRootCloakingApps){ ArrayList<String> packages = new ArrayList<> (Arrays.asList(Const.knownRootCloakingPackages)); if (additionalRootCloakingApps!=null && additionalRootCloakingApps.length>0){ packages.addAll(Arrays.asList(additionalRootCloakingApps)); } return isAnyPackageFromListInstalled(packages); } private boolean isAnyPackageFromListInstalled(List<String> packages){ boolean result = false; PackageManager pm = mContext.getPackageManager(); for (String packageName : packages) { try { pm.getPackageInfo(packageName, 0); QLog.e(packageName + " ROOT management app detected!"); result = true; } catch (PackageManager.NameNotFoundException e) { // Exception thrown, package is not installed into the system } } return result; } |
Final thoughts
From a security standpoint, developers that prevent users from running their apps on rooted devices may be a bright idea, but this becomes highly inconvenient for users who cannot execute the app only because they rooted their devices.
The majority of the time, rooting techniques can be readily bypassed, hence it is highly advised that developers utilize sophisticated techniques for root detection and prevent attackers from bypassing their validation measures.
Ajay Pandita
Subscribe now for growth-boosting insights from Appknox
We have so many ideas for new features that can help your mobile app security even more efficiently. We promise you that we wont mail bomb you, just once in a month.