Capt. Meelo

An infosec guy who's constantly seeking for knowledge.

Bypassing Android’s RootBeer Library (Part 1)

29 May 2020 » mobile

Introduction

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.

First Attempt

Before you could modify an application’s code, you first need to 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 (highlighted 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 v0 from 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.

Second Attempt

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
[...]  
    
    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 v1, 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

Conclusion

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.