Skip to content

Commit

Permalink
House keeping pre release of 0.1.1 (#234)
Browse files Browse the repository at this point in the history
* Readme updates as per #189, updated CHANGE_LOG filename and formatting. Plus a version bump in AGP
* Fix insecure binary file #180 thanks to @KaviIDPal for the changes.
* Removed selinux check that cannot be accessed in later Android versions
* Code formatting and removal of unused code
* remove unneeded dependency
* switch to new Circle CI docker images based on date https://circleci.com/developer/images/image/cimg/android
  • Loading branch information
scottyab authored Sep 12, 2024
1 parent e14e074 commit 52470e4
Show file tree
Hide file tree
Showing 34 changed files with 450 additions and 405 deletions.
10 changes: 4 additions & 6 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ version: 2
jobs:
build:
docker:
# https://circleci.com/docs/2.0/circleci-images/#android
- image: circleci/android:api-30
# https://circleci.com/developer/images/image/cimg/android
- image: cimg/android:2024.09-ndk
environment:
GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"'
GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"'
steps:
- checkout
- restore_cache:
Expand All @@ -29,12 +29,10 @@ jobs:
- store_test_results:
path: app/build/test-results

# Compile debug apks
- run:
name: Compile Debug APKs
command: ./gradlew assembleDebug

# Compile release apks
- run:
name: Compile Release APKs
command: ./gradlew assembleRelease
Expand All @@ -51,7 +49,7 @@ jobs:
find . -name "*release.apk" -type f -exec cp {} /tmp/artifacts/ \;
find . -name "*debug.apk" -type f -exec echo {} \;
find . -name "*debug.apk" -type f -exec cp {} /tmp/artifacts/ \;
# Save artifacts
- store_artifacts:
name: Save APK Files
Expand Down
9 changes: 9 additions & 0 deletions .github/ISSUE_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
**Describe the bug/feature request/question**
A clear and concise description of what the bug/feature/question is. Including steps to reproduce and stack trace if applicable.

**Rootbeer Library Version**
Please provide the version of the library where the issue is present (e.g., 0.1.0)

**Device Information**
- Device: [e.g., Pixel 4]
- OS: [e.g., Android 11]
10 changes: 10 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
_Provide a brief description of the goal and implementation. Add comments inline for any parts of this change that you would like to highlight to the reviewer_

### Video (optional)

_Demo of the functionality or walkthrough summary of the code._

### Developer checklist

- [ ] Run the app and tested the new/edited functionality to conform to the expected behaviour
- [ ] Run the following `./gradlew test` and confirmed all tests pass.
25 changes: 18 additions & 7 deletions changes.md → CHANGE_LOG.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,41 @@
Change Log
RootBeer Library Change Log
===========================

# 0.1.1

* Support for 16KB page sizes for native lib (Android 15 support)
* Removed `Util.isSelinuxFlagInEnabled()`. This was never part of the `isRooted()` check but as we are unable to get `ro.build.selinux` in later versions of Android it has been removed from the Util and sample app.

Internal changes:
* #229 Gradle, Build scripts, Maven Plugin, NDK update by @ArtsemKurantsou

# 0.1.0

#0.1.0
* Add fstack protector #136 @slawert

#0.0.9
# 0.0.9

* Support for Android TV devices #129 @deepakpk009
* Add additional dangerous apps packages #145 @Fi5t
* ~Add fstack protector #136 @slawert~ Note this was found in #170 to have not been applied and fixed in 0.1.0

#0.0.8
# 0.0.8

* Removed busybox from the default root checking methods
* Additional root app packages added
* Use PATH environment variable to find places su binaries might be hiding
* Updated sample app to AndroidX

#0.0.7
# 0.0.7

* Added a check to see if the native library is available to prevent crashing
* Automatic building of native binaries if NDK is present

#0.0.6
# 0.0.6

* more su directory checks
* fix crash when native lib load fails
* updated NDK build
* Added method to do root checks but ignoring the busybox due to false positives
* Allow setting of logging level
* UI tweeks to sample app
* UI tweaks to sample app
44 changes: 27 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,43 @@
A tasty root checker library and sample app. We've scoured the internets for different methods of answering that age old question... **Has this device got root?**

# Root checks
These are the current checks/tricks we are using to give an indication of root.
These are the current checks/tricks we are using to give an indication of root.

**Java checks**

* checkRootManagementApps
* checkPotentiallyDangerousApps
* checkRootCloakingApps
* checkTestKeys
* checkForDangerousProps
* checkForBusyBoxBinary
* checkForSuBinary
* checkSuExists
* checkForRWSystem
| Method Name | Description | Limitations |
|-------------------------------|-------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------|
| `checkRootManagementApps` | Checks if any apps for managing root access (like SuperSU or Magisk) are installed. | May not detect newly developed or less popular root management apps. |
| `checkPotentiallyDangerousApps`| Checks if any apps known for facilitating root access are installed. | Limited to a predefined list of apps; cannot detect custom or less-known dangerous apps. |
| `checkRootCloakingApps` | Detects apps that can cloak or hide root access from detection tools. | Root cloaking apps evolve quickly, potentially bypassing detection mechanisms. |
| `checkTestKeys` | Verifies if the device's firmware is signed with Android's test keys, which it would be on AOSP or certain emluators. | Only detects if test keys are used, and may miss rooted devices using production keys. |
| `checkForDangerousProps` | Checks for dangerous properties (`ro.debuggable` and `ro.secure`) that indicate this may not be a genuine Android device | Can be bypassed if properties are reset or hidden by advanced root cloaking techniques. |
| `checkForBusyBoxBinary` | Checks if the BusyBox binary is present, commonly used in rooted devices. | Not all rooted devices use BusyBox, and some device manufacturers may leave busybox on the ROM. |
| `checkForSuBinary` | Checks for the presence of the `su` binary, typically used to elevate privileges. | Su binaries may be renamed or hidden by root cloaking tools, bypassing detection. |
| `checkSuExists` | Another check for the existence of the `su` binary, via 'which su'. | Same as `checkForSuBinary`, can be bypassed by renaming or hiding the binary. |
| `checkForRWSystem` | Verifies if the `/system` partition is mounted as read-write, a sign of rooting. | Some newer root methods do not require RW access to the `/system` partition (e.g., systemless root). |


**Native checks**

We call through to our native root checker to run some of its own checks. Native checks are typically harder to cloak, so some root cloak apps just block the loading of native libraries that contain certain keywords.

* checkForSuBinary
| Method Name | Description | Limitations |
|--------------------|-----------------------------------------------------------------------------|---------------------------------|
| `checkForSuBinary` | Checks for the presence of the `su` binary, typically used to elevate privileges. | Same as Java `checkForSuBinary` |


## Disclaimer and limitations!

We love root! both [Scott](https://github.com/scottyab) and [Mat](https://github.com/stealthcopter) (the creators) own and use rooted devices (albeit not as daily driver). However we appreciate it can be useful to have an indication your app is running on a rooted device. Plus as hackday style project we wanted to see if we could beat the root cloakers at the time in 2015.
One way to think about root checking is that **root==<insert your deity here>**, so there's no 100% guaranteed way to check for root! Please treat the results of this library as an *indication* of root together with other factors which will vary depending on your usecase rather than basing your whole security posture on the result.
A more robust solution would be to use [Google Play Integrity API](https://developer.android.com/google/play/integrity) this will verify requests from your app are coming from your unmodified app binary, installed by Google Play, running on a genuine Android device (i.e not rooted).

Rootbeer can be by-passed, there's several articles that illustrate how to do this. [here](https://medium.com/secarmalabs/bypassing-androids-rootbeer-library-part-1-a5f93918660d) one we found with a quick google search.

Remember **root==god**, so there's no 100% guaranteed way to check for root! treat this as an *indication* of root.
### Background

This started as an hackday project where we wanted to see if we could beat the current root cloakers at the time in 2015.
We love root! both [Scott](https://github.com/scottyab) and [Mat](https://github.com/stealthcopter) (the creators) own and use rooted devices (albeit not as daily driver) and can understand the frustration when apps add root checks and prevent you using their services. However this is not the responsibility of RootBeer please raise these concerns with the app that is implementing the Rootbeer root checks.

<img src="./art/rootbeerjesus.png" width=200 />

Expand Down Expand Up @@ -59,7 +70,7 @@ You can also call each of the checks individually as the sample app does. It is

### False positives

Manufacturers often leave the busybox binary in production builds and this doesn't always mean that a device is root. We have removed the busybox check we used to include as standard in the isRooted() method to avoid these false positives.
Manufacturers sometimes leave the busybox binary in production builds and this doesn't always mean that a device is rooted. We have removed the busybox check we used to include as standard in the isRooted() method to avoid these false positives.

If you want to detect the busybox binary in your app you can use `checkForBinary(BINARY_BUSYBOX)` to detect it alone, or as part of the complete root detection method:

Expand All @@ -74,15 +85,15 @@ The following devices are known the have the busybox binary present on the stock

### Dependency

Available on [maven central](https://search.maven.org/#search%7Cga%7C1%7Ca%3A%22rootbeer-lib%22), to include using Gradle just add the following:
Available on [Maven Central](https://search.maven.org/#search%7Cga%7C1%7Ca%3A%22rootbeer-lib%22), to include using Gradle just add the following:

```java
dependencies {
implementation 'com.scottyab:rootbeer-lib:0.1.0'
}
```

Or use this [Jitpack.io link](https://jitpack.io/#scottyab/rootbeer)
Or use this [Jitpack.io link](https://jitpack.io/#scottyab/rootbeer/releases) - note prefer releases/tags rather than branches.

### Building

Expand All @@ -92,7 +103,6 @@ The native library in this application will now be built via Gradle and the late

The sample app is published on Google play to allow you to quickly and easier test the library. Enjoy! And please do feedback to us if your tests produce different results.


<a href="https://play.google.com/store/apps/details?id=com.scottyab.rootbeer.sample&utm_source=global_co&utm_medium=prtnr&utm_content=Mar2515&utm_campaign=PartBadge&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1"><img width="200" alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/images/generic/en-play-badge.png" /></a>

<img width="200" alt="screenshot" src="./art/ss_got_root_fail.png">
Expand Down
24 changes: 17 additions & 7 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,24 @@ plugins {

android {
namespace = "com.scottyab.rootbeer.sample"
compileSdk = libs.versions.android.compile.sdk.get().toInt()
buildToolsVersion = libs.versions.android.build.tools.get()
compileSdk =
libs.versions.android.compile.sdk
.get()
.toInt()
buildToolsVersion =
libs.versions.android.build.tools
.get()

defaultConfig {
applicationId = "com.scottyab.rootbeer.sample"
minSdk = libs.versions.android.min.sdk.get().toInt()
targetSdk = libs.versions.android.target.sdk.get().toInt()
minSdk =
libs.versions.android.min.sdk
.get()
.toInt()
targetSdk =
libs.versions.android.target.sdk
.get()
.toInt()
versionName = findProperty("VERSION_NAME").toString()
versionCode = findProperty("VERSION_CODE").toString().toInt()
vectorDrawables.useSupportLibrary = true
Expand All @@ -22,10 +33,10 @@ android {
viewBinding = true
}

//check if the keystore details are defined in gradle.properties (this is so the key is not in github)
// check if the keystore details are defined in gradle.properties (this is so the key is not in github)
if (findProperty("ROOTBEER_SAMPLE_STORE") != null) {
signingConfigs {
//from ~/user/.gradle/gradle.properties
// from ~/user/.gradle/gradle.properties
create("release") {
storeFile = file(findProperty("ROOTBEER_SAMPLE_STORE").toString())
keyAlias = findProperty("ROOTBEER_SAMPLE_KEY").toString()
Expand Down Expand Up @@ -68,7 +79,6 @@ dependencies {
implementation(libs.androidx.core.ktx)

implementation(libs.androidx.appcompat)
implementation(libs.androidx.constraintlayout)
implementation(libs.android.google.material)

implementation(libs.nineoldandroids)
Expand Down
36 changes: 18 additions & 18 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
xmlns:tools="http://schemas.android.com/tools">

<application
android:name=".RootSampleApp"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar"
tools:ignore="AllowBackup,GoogleAppIndexingWarning">

<activity
android:name=".MainActivity"
android:exported="true"
<application
android:name=".RootSampleApp"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:screenOrientation="unspecified">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
android:theme="@style/AppTheme.NoActionBar"
tools:ignore="AllowBackup,GoogleAppIndexingWarning">

<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:screenOrientation="unspecified">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,26 @@ package com.scottyab.rootbeer.sample

import android.content.Context
import com.scottyab.rootbeer.RootBeer
import com.scottyab.rootbeer.util.Utils

class CheckForRootWorker(context: Context) {

class CheckForRootWorker(
context: Context,
) {
private val rootBeer = RootBeer(context)

suspend operator fun invoke(): List<RootItemResult> = getRootResults()

private fun getRootResults() = listOf(
RootItemResult("Root Management Apps", rootBeer.detectRootManagementApps()),
RootItemResult("Potentially Dangerous Apps", rootBeer.detectPotentiallyDangerousApps()),
RootItemResult("Root Cloaking Apps", rootBeer.detectRootCloakingApps()),
RootItemResult("TestKeys", rootBeer.detectTestKeys()),
RootItemResult("BusyBoxBinary", rootBeer.checkForBusyBoxBinary()),
RootItemResult("SU Binary", rootBeer.checkForSuBinary()),
RootItemResult("2nd SU Binary check", rootBeer.checkSuExists()),
RootItemResult("For RW Paths", rootBeer.checkForRWPaths()),
RootItemResult("Dangerous Props", rootBeer.checkForDangerousProps()),
RootItemResult("Root via native check", rootBeer.checkForRootNative()),
RootItemResult("SE linux Flag Is Enabled", !Utils.isSelinuxFlagInEnabled()),
RootItemResult("Magisk specific checks", rootBeer.checkForMagiskBinary())
)
private fun getRootResults() =
listOf(
RootItemResult("Root Management Apps", rootBeer.detectRootManagementApps()),
RootItemResult("Potentially Dangerous Apps", rootBeer.detectPotentiallyDangerousApps()),
RootItemResult("Root Cloaking Apps", rootBeer.detectRootCloakingApps()),
RootItemResult("TestKeys", rootBeer.detectTestKeys()),
RootItemResult("BusyBoxBinary", rootBeer.checkForBusyBoxBinary()),
RootItemResult("SU Binary", rootBeer.checkForSuBinary()),
RootItemResult("2nd SU Binary check", rootBeer.checkSuExists()),
RootItemResult("For RW Paths", rootBeer.checkForRWPaths()),
RootItemResult("Dangerous Props", rootBeer.checkForDangerousProps()),
RootItemResult("Root via native check", rootBeer.checkForRootNative()),
RootItemResult("Magisk specific checks", rootBeer.checkForMagiskBinary()),
)
}

Loading

0 comments on commit 52470e4

Please sign in to comment.