Rooted device

You often want to restrict user on a rooted device for doing any specific operation, like making an online payment in a Banking app or e-commerce App.

If you google around, there are tons of code which might help you determine whether a device is rooted or not. Hence, in order to make your life a little simpler, you can use the following code to determine the same.

  public boolean isRooted() {
        boolean isRootManagementApplicationInstalled = detectRootManagementApps();
        boolean isPotentiallyDangerousAppsInstalled = detectPotentiallyDangerousApps();
        boolean isRootCloakingApplicationInstalled = detectRootCloakingApps();
        boolean isSuBinaryFileExists = isSuBinaryFileExists();
        boolean isDangerousPropsExists = checkForDangerousProps();
        boolean isRWPaths = checkForRWPaths();
        boolean isTestKeysAvailable = detectTestKeys();
        boolean isSuCommandExecutes = isSuCommandExecutes();
        boolean isMagiskBinaryFileExists = isMagiskBinaryFileExists();
        boolean isThirdPartyApp = isRootManagementApplicationInstalled || isPotentiallyDangerousAppsInstalled
                || isRootCloakingApplicationInstalled;
        boolean isSuOrDangerousPath = isSuBinaryFileExists || isSuCommandExecutes || isDangerousPropsExists;
        boolean isTestOrMagiskOrRW = isRWPaths || isTestKeysAvailable || isMagiskBinaryFileExists;
        return isThirdPartyApp || isSuOrDangerousPath || isTestOrMagiskOrRW;
    }

    /**
     * this function checks whether the kernel is signed with custom key generated by a third-party developer.
     *
     * @return true if signed with test-keys
     */
    private boolean detectTestKeys() {
        String buildTags = android.os.Build.TAGS;
        return buildTags != null && buildTags.contains(TEST_KEYS);
    }

    /**
     * This methods check for root apps using PackageManager. 
     *
     * @return true if one of the rooting app is installed else false
     */
    private boolean detectRootManagementApps() {
        ArrayList<String> packages = new ArrayList<>();
        packages.addAll(Arrays.asList(ROOT_APPS_PACKAGES));
        return isAnyPackageFromListInstalled(packages);
    }

    /**
     * check for the apps which require roo
     *
     * @return true if root requiring app installed else false
     */
    private boolean detectPotentiallyDangerousApps() {
        ArrayList<String> packages = new ArrayList<>();
        packages.addAll(Arrays.asList(DANGEROUS_APPS_PACKAGES));
        return isAnyPackageFromListInstalled(packages);
    }

    /**
     * This function check for the root cloaking apps using PackageManager.
     *
     * @return true if one of the apps it's installed
     */
    private boolean detectRootCloakingApps() {
        ArrayList<String> packages = new ArrayList<>();
        packages.addAll(Arrays.asList(ROOT_CLOAKING_PACKAGES));
        return isAnyPackageFromListInstalled(packages);
    }

    /**
     * This method check for common locations of SU binary
     *
     * @return true if found su binary else false
     */
    private boolean isSuBinaryFileExists() {
        return checkForBinary(SU);
    }

    /**
     *
     * @return true if @link {MAGISK} binary found else false
     */
    private boolean isMagiskBinaryFileExists() {
        return checkForBinary(MAGISK);
    }

    /**
     * checks for binary in common su_Paths 
     *
     * filename - check for this existence of this file
     * @return true if found else false
     */
    private boolean checkForBinary(String filename) {

        boolean result = false;

        for (String path : SU_PATHS) {
            File f = new File(path, filename);
            boolean fileExists = f.exists();
            if (fileExists) {
                result = true;
            }
        }
        return result;
    }

    /**
     * @return array of getProp splitted using "\\A"
     */
    private String[] propsReader() {
        try {
            InputStream inputstream = Runtime.getRuntime().exec(GETPROP).getInputStream();
            if (inputstream == null) {
                return new String[0];
            }
            String propVal = new Scanner(inputstream).useDelimiter(DELIMETER_A).next();
            return propVal.split("\n");
        } catch (IOException | NoSuchElementException exception) {
            Log.e(TAG, "RootHelper.propsReader : exception -  " + exception);
            return new String[0];
        }
    }

    /**
     * @return array of mount command splitted using "\\A" and "\n"
     */
    private String[] mountReader() {
        try {
            InputStream inputstream = Runtime.getRuntime().exec(MOUNT).getInputStream();
            if (inputstream == null) {
                return new String[0];
            }
            String propVal = new Scanner(inputstream).useDelimiter(DELIMETER_A).next();
            return propVal.split("\n");
        } catch (IOException | NoSuchElementException exception) {
            Log.e(TAG, "RootHelper.mountReader : exception -  " + exception);
            return new String[0];
        }
    }

    /**
     * Check if any package in the list is installed
     *
     *  packages - list of packages to search for
     * @return true if any of the packages are installed
     */
    private boolean isAnyPackageFromListInstalled(List<String> packages) {
        boolean result = false;

        PackageManager pm = BaseApplication.getInstance().getPackageManager();

        for (String packageName : packages) {
            try {
                pm.getPackageInfo(packageName, 0);
                result = true;
            } catch (PackageManager.NameNotFoundException exception) {
                Log.e(TAG, "RootHelper.isAnyPackageFromListInstalled : exception -  " + exception);
            }
        }
        return result;
    }

    /**
     *  This method check for system's dangerous properties
     *
     * @return - true if dangerous props found else false
     */
    private boolean checkForDangerousProps() {

        final Map<String, String> dangerousProps = new ArrayMap<>();
        dangerousProps.put(RO_DEBUGGABLE, "1");
        dangerousProps.put(RO_SECURE, "0");

        boolean result = false;

        String[] lines = propsReader();

        if (lines.length == 0) {
            return false;
        }

        for (String line : lines) {
            for (Map.Entry<String, String> entry : dangerousProps.entrySet()) {
                result = isDangerousProps(line, entry);
            }
        }
        return result;
    }

    /**
     * line  line
     *  entry map entry set
     * @return rue if dangerous props found else false
     */
    private boolean isDangerousProps(String line, Map.Entry<String, String> entry) {
        if (line.contains(entry.getKey())) {
            StringBuilder badValue = new StringBuilder(entry.getValue());
            badValue.append(OPEN_BRACKET).append(badValue).append(CLOSING_BRACKET);
            if (line.contains(badValue)) {
                return true;
            }
        }
        return false;
    }

    /**
     *  this method checks
     * if any of the paths are writable.
     *
     * @return true any of the non writable directory is writable, else false
     */
    public boolean checkForRWPaths() {
        boolean result = false;
        String[] lines = mountReader();
        if (lines.length == 0) {
            return false;
        }
        for (String line : lines) {
            String[] args = line.split(" ");
            if (args.length < 4) {
                continue;
            }
            String mountPoint = args[1];
            String mountOptions = args[3];
            for (String pathToCheck : PATHS_THAT_SHOULD_NOT_BE_WRITABLE) {
                result = checkForRWPathsExtended(mountPoint, mountOptions, pathToCheck);
            }
        }
        return result;
    }

    /**
     * checks if directory is writable
     *
     *  mountPoint   mount point
     *  mountOptions mount options
     *  pathToCheck  path to check
     * @return true if directory is writable else false
     */
    private boolean checkForRWPathsExtended(String mountPoint, String mountOptions,
                                            String pathToCheck) {
        if (mountPoint.equalsIgnoreCase(pathToCheck)) {
            for (String option : mountOptions.split(",")) {
                if (option.equalsIgnoreCase(RW)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     *
     * @return true if 'which su' command executes else false
     */
    private boolean isSuCommandExecutes() {
        Process process = null;
        try {
            process = Runtime.getRuntime().exec(new String[]{WHICH, SU});
            BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
            return in.readLine() != null;
        } catch (IOException exception) {
            Log.e(TAG, "RootHelper.isSuCommandExecutes : exception -  " + exception);
        } finally {
            if (process != null) {
                process.destroy();
            }
        }
        return false;
    }
}

Note: If you have any method which can be part of this Util class, feel free to leave a comment. You can download the sample app from here.

1 thought on “Rooted device”

Leave a Reply

Your email address will not be published. Required fields are marked *