Bypassing Android’s RootBeer Library — Part 1

Elvin Gentiles
Secarma Labs
Published in
6 min readMay 27, 2020

--

In my previous post, I made a comparison between the different well-known and open-source root-detection bypass tools for Android. In that post, I encouraged analysts to learn how to reverse engineer an application and bypass an application’s protection manually.

This post is a follow up on how to bypass the different checks used by RootBeer library by changing the application’s process through code manipulation.

Before you could modify an application’s code, you need to first have a copy of the APK file. After downloading the application (RootBeer Sample) from the Play Store, the following series of commands can be used to extract the APK file from the Android device:

$ adb shell pm path com.scottyab.rootbeer.sample
$ adb pull <apk-path>

If you’re like me and do not like typing series of commands, the easiest way to retrieve an APK file from a device is by using Frida Android Helper.

Retrieving an Application’s APK File with Frida Android Helper

Once you have a copy of the APK file, decompile it using apktool:

Decompiling the APK File with apktool

Tip 1: Sometimes, rebuilding a decompiled application results in an error. One way to fix that error is by removing the file 1.apk (shown above) before rebuilding the app.

Tip 2: If an error related to “resources” shows during the building process, try excluding the resources during the decompilation by using the -r flag (shown below).

Excluding Resources During Decompilation

Reading smali code is not as easy as reading java code. So if you’re not comfortable with smali, you can open the APK file in jadx-gui to view its java equivalent and use it as a reference to gain better understanding:

Using jadx-gui to Analyse an Application’s Code

Looking at the RootBeer (com.scottyab.rootbeer.RootBeer) class, there exists a function called isRooted() which, as per its name, is responsible for identifying whether the device is rooted:

Snippet of the RootBeer Class

The equivalent smali code of this class is located in /decompiled/smali/com/scottyab/rootber/RootBeer.smali. From this smali code, the isRooted() function is located on line 1084 (as shown below):

Snippet of the isRooted Function

On line 1158, it can be seen that the isRooted() function returns whatever value the variable v0 holds. To force this function to return the value of false, just change the value of the variable v0from 0x1 to 0x0 (shown in line 1155):

Forcing isRooted() Function to Return “False”

After doing the necessary changes, rebuild the application using apktool:

Rebuilding the Modified Application

Before installing the application, make sure to sign it using your own certificate or a debug certificate. To sign the application easily, you can use uber-apk-signer:

Signing the Application with a Debug Certificate

Using the modified application, it can be seen that we’ve successfully forced the result to “NOT ROOTED” (see the right image below). However, nothing changed with the different checks used by RootBeer; some of the checks still failed:

Result of Modifying the APK File

This happened because we only modified one function of the application and forced it to return as “NOT ROOTED”. To pass all the checks used by the RootBeer library, we need to modify and bypass all the relevant functions.

So how can we find all these functions? If we look at the RootCheckTask (com.scottyab.rootbeer.sample.CheckRootTask) class, there’s a function called doInBackground() which can be used to identify the different functions/checks used by RootBeer. This list of functions can be used as a reference to determine which smali code/files to modify:

A Snippet of the Different Functions Used by RootBeer

The following lists the changes that were made to the relevant classes and functions to bypass all of RootBeer’s checks.

Note: bold text in the code snippets below signifies the changes that were made.

RootBeer (com.scottyab.rootbeer.RootBeer) Class

  • detectRootManagementApps()inserted return v0.
.method public detectRootManagementApps()Z
.locals 1
const/4 v0, 0x0
return v0
[...]
  • detectPotentiallyDangerousApps() inserted return v0.
.method public detectPotentiallyDangerousApps()Z
.locals 1
const/4 v0, 0x0
return v0
[...]
  • detectTestKeys()changed const/4 v0, 0x1 to const/4 v0, 0x0.
.method public detectTestKeys()Z
[...]
const/4 v0, 0x0 goto :goto_0 :cond_0
const/4 v0, 0x0
:goto_0
return v0
  • checkForBusyBoxBinary()changed move-result v0 to const/4 v0, 0x0.
.method public checkForBusyBoxBinary()Z
[...]
const/4 v0, 0x0 return v0
.end method
  • checkForSuBinary() changed move-result v0 to const/4 v0, 0x0.
.method public checkForSuBinary()Z
[...snipped...]
const/4 v0, 0x0 return v0
.end method
  • checkSuExists()inserted return v0.
.method public checkSuExists()Z
.locals 6
const/4 v0, 0x0
return v0
const/4 v1, 0x0
[...]
  • checkForRWPaths() —inserted return v1.
.method public checkForRWPaths()Z
.locals 16
.line 301
invoke-direct/range {p0 .. p0}, Lcom/scottyab/rootbeer/RootBeer;->mountReader()[Ljava/lang/String;
move-result-object v0 const/4 v1, 0x0
return v1
[...]
  • checkForDangerousProps() — changed const/4 v4, 0x1 to const/4 v4, 0x0.
.method public checkForDangerousProps()Z
[...]
invoke-static {v4}, Lcom/scottyab/rootbeer/util/QLog;->v(Ljava/lang/Object;)V const/4 v4, 0x0 goto :goto_1 :cond_2
add-int/lit8 v2, v2, 0x1
goto :goto_0 :cond_3
return v4
.end method
  • checkForRootNative() — changed const/4 v1, 0x1 to const/4 v1, 0x0.
.method public checkForRootNative()Z
[...]
if-lez v0, :cond_2 const/4 v1, 0x0 :catch_0
:cond_2
return v1
.end method
  • detectRootCloakingApps() — changed const/4 v0, 0x1 to const/4 v0, 0x0.
.method public detectRootCloakingApps()Z
[...]
:cond_1
:goto_0
const/4 v0, 0x0
:goto_1
return v0
.end method
  • checkForMagiskBinary() — changed move-result v0 to const/4 v0, 0x0.
.method public checkForMagiskBinary()Z
[...]
const/4 v0, 0x0 return v0
.end method

Utils (com.scottyab.rootbeer.util) Class

  • isSelinuxFlagInEnabled() — inserted return v0.
.method public static isSelinuxFlagInEnabled()Z
.locals 6
const/4 v0, 0x0
return v0
[...]

After doing the above changes, the last thing to do is to rebuild, resign, and re-install the modified application:

Rebuilding, Resigning, and Re-installing the Modified Application

Though the process of modifying the application’s code took quite some time, it’s worth the effort as it resulted in all the checks being bypassed:

Successfully Bypassed All RootBeer’s Checks

Final Thoughts

The way I modified the application’s code (e.g. inserting return v0 after a few lines of the beginning of a function) is not the only way to do it. We all think differently and each of us could come up with different ways on how to solve a problem. So it doesn’t matter how you do your modifications as long as you’re getting the results that you’re aiming for.

--

--