Reputation: 32037
My app has a certain piece of functionality that will only work on a device where root is available. Rather than having this feature fail when it is used (and then show an appropriate error message to the user), I'd prefer an ability to silently check if root is available first, and if not,hide the respective options in the first place.
Is there a way to do this?
Upvotes: 370
Views: 265935
Reputation: 11
I think it is not possible to silently check
if phone is rooted or not.
it is only possible if phone is not rooted. The Runtime.getRuntime().exec("su -c ls") will send an exception, without prompting anything.
If phone is rooted, every Runtime.getRuntime().exec("su -c ls") will promt a demand for granting application if not granted, and will show a Toast if granted.
I dont't know if there is a way to avoid prompting demand or Toast.
For the best solution to test if Phone is rooted or not, and if SuperUser Access granted or not, only Lexip's solution with Scanner works fine.
To complete his response:
boolean isRooted = false;
boolean isRootGranted = false;
try {
Process process = Runtime.getRuntime().exec("su -c ls"); // Ask for su and list
Scanner scanner = new Scanner(process.getInputStream()).useDelimiter("\\A");
if (scanner.hasNext()) isRootGranted = true;
scanner.close();
process.waitFor();
process.getInputStream().close();
process.destroy();
isRooted = true;
if (isRootGranted) Toast.makeText(MainActivity.this, "ROOT granted", Toast.LENGTH_SHORT).show();
else Toast.makeText(MainActivity.this, "ROOT not granted", Toast.LENGTH_SHORT).show();
} catch (Exception e) {
isRooted = false;
Toast.makeText(MainActivity.this, "ERROR #SU AUTORISATION\nPHONE NON ROOTED", Toast.LENGTH_SHORT).show();
}
isRooted = true
on a rooted phone, and false if not.
isRootGranted = true
if superUser Access is granted, and false if not.
Working everytime you call it.
Regards.
Upvotes: 0
Reputation: 186
fun isDeviceRooted(): Boolean {
val buildTags = android.os.Build.TAGS
if (buildTags != null && buildTags.contains("test-keys")) {
return true
}
// Check for the existence of known superuser APKs
val superUserApkPaths = arrayOf(
"/system/app/Superuser.apk",
"/system/app/SuperSU.apk",
"/system/app/MagiskManager.apk",
"/sbin/su",
"/system/bin/su",
"/system/xbin/su",
"/data/local/xbin/su",
"/data/local/bin/su",
"/system/sd/xbin/su",
"/system/bin/failsafe/su",
"/data/local/su"
)
for (path in superUserApkPaths) {
if (File(path).exists()) {
return true
}
}
// Check for the presence of the "su" binary
try {
val process = Runtime.getRuntime().exec(arrayOf("/system/xbin/which", "su"))
val bufferedReader = BufferedReader(InputStreamReader(process.inputStream))
val line = bufferedReader.readLine()
bufferedReader.close()
if (line != null) {
return true
}
} catch (e: Exception) {
// Exception occurred, indicating "su" is not available
}
return false
}
Upvotes: 0
Reputation: 2299
Using google SafetyNet Attestation API you can easily check whether your device is rooted or not :
Add dependency in build.gradle(:app)
implementation 'com.google.android.gms:play-services-safetynet:17.0.0'
Get Api key and enable Android Device Verification API using link
public static void sendSafetyNetRequest(Activity context) {
if(GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context, 13000000) == ConnectionResult.SUCCESS) {
Log.e(TAG, "The SafetyNet Attestation API is available");
// TODO(developer): Change the nonce generation to include your own, used once value,
// ideally from your remote server.
String nonceData = "Safety Net Sample: " + System.currentTimeMillis();
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
Random mRandom = new SecureRandom();
byte[] bytes = new byte[24];
mRandom.nextBytes(bytes);
try {
byteStream.write(bytes);
byteStream.write(nonceData.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
byte[] nonce = byteStream.toByteArray();
SafetyNetClient client = SafetyNet.getClient(context);
Task<SafetyNetApi.AttestationResponse> task = client.attest(nonce, API_KEY_FROM_STEP_2_LINK);
task.addOnSuccessListener(context, attestationResponse -> {
/*
TODO(developer): Forward this result to your server together with
the nonce for verification.
You can also parse the JwsResult locally to confirm that the API
returned a response by checking for an 'error' field first and before
retrying the request with an exponential backoff.
NOTE: Do NOT rely on a local, client-side only check for security, you
must verify the response on a remote server!
*/
String jwsResult = attestationResponse.getJwsResult();
Log.e(TAG, "Success! SafetyNet result:\n" + jwsResult + "\n");
if (jwsResult == null) {
Log.e(TAG, "jwsResult Null");
}
final String[] jwtParts = jwsResult.split("\\.");
if (jwtParts.length == 3) {
String decodedPayload = new String(Base64.decode(jwtParts[1], Base64.DEFAULT));
Log.e(TAG, "decodedPayload : " + decodedPayload);
}
});
task.addOnFailureListener(context, e -> {
// An error occurred while communicating with the service.
String mResult = null;
if (e instanceof ApiException) {
// An error with the Google Play Services API contains some additional details.
ApiException apiException = (ApiException) e;
Util.showLog(TAG, "Error: " +
CommonStatusCodes.getStatusCodeString(apiException.getStatusCode()) + ": " +
apiException.getStatusMessage());
} else {
// A different, unknown type of error occurred.
Log.e(TAG, "ERROR! " + e.getMessage());
}
});
} else {
Log.e(TAG, "Prompt user to update Google Play services.";
}
} `
Check your logs for decodedPayload if ctsProfileMatch and basicIntegrity both are false it means your device is rooted . The Attestation API returns a JWS response which looks like:
{ "nonce": "6pLrr9zWyl6TNzj+kpbR4LZcfPY3U2FmZXR5IE5ldCBTYW1wbGU6IDE2MTQ2NzkwMTIzNjc=", "timestampMs": 9860437986543, "apkPackageName": " your package name will be displayed here", "ctsProfileMatch": true, "apkDigestSha256": [ "base64 encoded, SHA-256 hash of the certificate used to sign requesting app" ], "basicIntegrity": true, "evaluationType": "BASIC" }
For more info check this link.
Upvotes: 2
Reputation: 810
As last quarter of 2021 today, I tried to use SafetyNet regarding @HimanshiThakur 's answer. But I got an issue and opened a question in here. Still no answer.
So I decided to use RootBeer. It works perfectly but when Magisk hides the root, it doesn't work.
If you don't care this case (and many bank apps can't solve this issue too), you can use these steps:
implementation 'com.scottyab:rootbeer-lib:0.1.0'
RootBeer rootBeer = new RootBeer(context);
if (rootBeer.isRooted()) {
//we found indication of root
} else {
//we didn't find indication of root
}
Upvotes: 3
Reputation: 11419
If you are already using Fabric/Firebase Crashlytics you can call
CommonUtils.isRooted(context)
This is the current implementation of that method:
public static boolean isRooted(Context context) {
boolean isEmulator = isEmulator(context);
String buildTags = Build.TAGS;
if (!isEmulator && buildTags != null && buildTags.contains("test-keys")) {
return true;
} else {
File file = new File("/system/app/Superuser.apk");
if (file.exists()) {
return true;
} else {
file = new File("/system/xbin/su");
return !isEmulator && file.exists();
}
}
}
public static boolean isEmulator(Context context) {
String androidId = Secure.getString(context.getContentResolver(), "android_id");
return "sdk".equals(Build.PRODUCT) || "google_sdk".equals(Build.PRODUCT) || androidId == null;
}
Upvotes: 112
Reputation: 567
Forget all that detecting root apps and su binaries. Check for the root daemon process. This can be done from the terminal and you can run terminal commands within an app. Try this one-liner.
if [ ! "$(/system/bin/ps -A | grep -v grep | grep -c daemonsu)" = "0" ]; then echo "device is rooted"; else echo "device is not rooted"; fi
You don't need root permission to achieve this either.
Edit: Now using this method for better detection!
if [ $(ps -A | grep -e ^shell -e ^root | grep -v "\[" | tr -s ' ' | cut -d ' ' -f 9 | grep -c su) ] || [ $(which su) ]; then echo 'rooted'; else echo 'not rooted'; fi
Upvotes: 2
Reputation: 116362
Based on some of the answers here, I merged them and also added a check if some known root-manager app is installed:
fun isProbablyRooted(context: Context, alsoIncludeCheckingRootManagerApp: Boolean = false): Boolean {
return hasRootManagerSystemApp(context) || (alsoIncludeCheckingRootManagerApp && hasRootManagerSystemApp(context))
}
fun hasRootManagerSystemApp(context: Context): Boolean {
val rootAppsPackageNames = arrayOf("com.topjohnwu.magisk", "eu.chainfire.supersu", "com.koushikdutta.superuser", "com.noshufou.android.su", "me.phh.superuser")
rootAppsPackageNames.forEach { rootAppPackageName ->
try {
context.packageManager.getApplicationInfo(rootAppPackageName, 0)
return true
} catch (e: Exception) {
}
}
return false
}
fun hasSuBinary(): Boolean {
return try {
findBinary("su")
} catch (e: Exception) {
e.printStackTrace()
false
}
}
private fun findBinary(binaryName: String): Boolean {
val paths = System.getenv("PATH")
if (!paths.isNullOrBlank()) {
val systemPlaces: List<String> = paths.split(":")
return systemPlaces.firstOrNull { File(it, binaryName).exists() } != null
}
val places = arrayOf("/sbin/", "/system/bin/", "/system/xbin/", "/data/local/xbin/", "/data/local/bin/",
"/system/sd/xbin/", "/system/bin/failsafe/", "/data/local/")
return places.firstOrNull { File(it, binaryName).exists() } != null
}
manifest:
<queries>
<package android:name="com.topjohnwu.magisk" />
<package android:name="eu.chainfire.supersu" />
<package android:name="com.koushikdutta.superuser" />
<package android:name="com.noshufou.android.su" />
<package android:name="me.phh.superuser" />
</queries>
Of course, this is still a guess, like all the other solutions. Users can install Magisk without having the device rooted, for example.
Upvotes: 3
Reputation: 131
As of 2021 (today), looks like there's no any reliable way or method for detecting root, especially when powerful hiding tool such as MagiskHide is enabled. Most of the answers here are no longer relevant, so don't use it in production. Rely on proven check like SafetyNet, and instead of going the extra miles to detect root, I suggest to protect your app at both runtime, such as prevent debugger/instrumentation and make sure to use obfuscation.
Upvotes: 2
Reputation: 768
You can do this by following code :
public boolean getRootInfo() {
if (checkRootFiles() || checkTags()) {
return true;
}
return false;
}
private boolean checkRootFiles() {
boolean root = false;
String[] paths = {"/system/app/Superuser.apk", "/sbin/su", "/system/bin/su", "/system/xbin/su", "/data/local/xbin/su", "/data/local/bin/su", "/system/sd/xbin/su",
"/system/bin/failsafe/su", "/data/local/su", "/su/bin/su"};
for (String path : paths) {
root = new File(path).exists();
if (root)
break;
}
return root;
}
private boolean checkTags() {
String tag = Build.TAGS;
return tag != null && tag.trim().contains("test-keys");
}
You can also check this library RootBeer.
Upvotes: 0
Reputation: 1580
if you don't want to use any 3rd party library or any random solution then just use google lib for detecting it.
Android Device Verification
response :
{
"timestampMs": 9860437986543,
"nonce": "R2Rra24fVm5xa2Mg",
"apkPackageName": "com.package.name.of.requesting.app",
"apkCertificateDigestSha256": ["base64 encoded, SHA-256 hash of the
certificate used to sign requesting app"],
"ctsProfileMatch": true,
"basicIntegrity": true,
}
ctsProfileMatch it gives false if the device is rooted.
ref link: [1]: https://developer.android.com/training/safetynet/attestation
Upvotes: 1
Reputation: 1297
There is Safety Net Attestation API of Google play services by which we can assess the device and determine if it is rooted/tampered.
Please go through my answer to deal with rooted devices:
https://stackoverflow.com/a/58304556/3908895
Upvotes: 1
Reputation: 3732
I suggest using native code for root detection. Here is a full working example.
package com.kozhevin.rootchecks.util;
import android.support.annotation.NonNull;
import com.kozhevin.rootchecks.BuildConfig;
public class MeatGrinder {
private final static String LIB_NAME = "native-lib";
private static boolean isLoaded;
private static boolean isUnderTest = false;
private MeatGrinder() {
}
public boolean isLibraryLoaded() {
if (isLoaded) {
return true;
}
try {
if(isUnderTest) {
throw new UnsatisfiedLinkError("under test");
}
System.loadLibrary(LIB_NAME);
isLoaded = true;
} catch (UnsatisfiedLinkError e) {
if (BuildConfig.DEBUG) {
e.printStackTrace();
}
}
return isLoaded;
}
public native boolean isDetectedDevKeys();
public native boolean isDetectedTestKeys();
public native boolean isNotFoundReleaseKeys();
public native boolean isFoundDangerousProps();
public native boolean isPermissiveSelinux();
public native boolean isSuExists();
public native boolean isAccessedSuperuserApk();
public native boolean isFoundSuBinary();
public native boolean isFoundBusyboxBinary();
public native boolean isFoundXposed();
public native boolean isFoundResetprop();
public native boolean isFoundWrongPathPermission();
public native boolean isFoundHooks();
@NonNull
public static MeatGrinder getInstance() {
return InstanceHolder.INSTANCE;
}
private static class InstanceHolder {
private static final MeatGrinder INSTANCE = new MeatGrinder();
}
}
JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isDetectedTestKeys(
JNIEnv *env,
jobject this ) {
return (jboolean) isDetectedTestKeys();
}
JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isDetectedDevKeys(
JNIEnv *env,
jobject this ) {
return (jboolean) isDetectedDevKeys();
}
JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isNotFoundReleaseKeys(
JNIEnv *env,
jobject this ) {
return (jboolean) isNotFoundReleaseKeys();
}
JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isFoundDangerousProps(
JNIEnv *env,
jobject this ) {
return (jboolean) isFoundDangerousProps();
}
JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isPermissiveSelinux(
JNIEnv *env,
jobject this ) {
return (jboolean) isPermissiveSelinux();
}
JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isSuExists(
JNIEnv *env,
jobject this ) {
return (jboolean) isSuExists();
}
JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isAccessedSuperuserApk(
JNIEnv *env,
jobject this ) {
return (jboolean) isAccessedSuperuserApk();
}
JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isFoundSuBinary(
JNIEnv *env,
jobject this ) {
return (jboolean) isFoundSuBinary();
}
JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isFoundBusyboxBinary(
JNIEnv *env,
jobject this ) {
return (jboolean) isFoundBusyboxBinary();
}
JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isFoundXposed(
JNIEnv *env,
jobject this ) {
return (jboolean) isFoundXposed();
}
JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isFoundResetprop(
JNIEnv *env,
jobject this ) {
return (jboolean) isFoundResetprop();
}
JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isFoundWrongPathPermission(
JNIEnv *env,
jobject this ) {
return (jboolean) isFoundWrongPathPermission();
}
JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isFoundHooks(
JNIEnv *env,
jobject this ) {
return (jboolean) isFoundHooks();
}
constants:
// Comma-separated tags describing the build, like= "unsigned,debug".
const char *const ANDROID_OS_BUILD_TAGS = "ro.build.tags";
// A string that uniquely identifies this build. 'BRAND/PRODUCT/DEVICE:RELEASE/ID/VERSION.INCREMENTAL:TYPE/TAGS'.
const char *const ANDROID_OS_BUILD_FINGERPRINT = "ro.build.fingerprint";
const char *const ANDROID_OS_SECURE = "ro.secure";
const char *const ANDROID_OS_DEBUGGABLE = "ro.debuggable";
const char *const ANDROID_OS_SYS_INITD = "sys.initd";
const char *const ANDROID_OS_BUILD_SELINUX = "ro.build.selinux";
//see https://android.googlesource.com/platform/system/core/+/master/adb/services.cpp#86
const char *const SERVICE_ADB_ROOT = "service.adb.root";
const char * const MG_SU_PATH[] = {
"/data/local/",
"/data/local/bin/",
"/data/local/xbin/",
"/sbin/",
"/system/bin/",
"/system/bin/.ext/",
"/system/bin/failsafe/",
"/system/sd/xbin/",
"/su/xbin/",
"/su/bin/",
"/magisk/.core/bin/",
"/system/usr/we-need-root/",
"/system/xbin/",
0
};
const char * const MG_EXPOSED_FILES[] = {
"/system/lib/libxposed_art.so",
"/system/lib64/libxposed_art.so",
"/system/xposed.prop",
"/cache/recovery/xposed.zip",
"/system/framework/XposedBridge.jar",
"/system/bin/app_process64_xposed",
"/system/bin/app_process32_xposed",
"/magisk/xposed/system/lib/libsigchain.so",
"/magisk/xposed/system/lib/libart.so",
"/magisk/xposed/system/lib/libart-disassembler.so",
"/magisk/xposed/system/lib/libart-compiler.so",
"/system/bin/app_process32_orig",
"/system/bin/app_process64_orig",
0
};
const char * const MG_READ_ONLY_PATH[] = {
"/system",
"/system/bin",
"/system/sbin",
"/system/xbin",
"/vendor/bin",
"/sbin",
"/etc",
0
};
root detections from native code:
struct mntent *getMntent(FILE *fp, struct mntent *e, char *buf, int buf_len) {
while (fgets(buf, buf_len, fp) != NULL) {
// Entries look like "/dev/block/vda /system ext4 ro,seclabel,relatime,data=ordered 0 0".
// That is: mnt_fsname mnt_dir mnt_type mnt_opts mnt_freq mnt_passno.
int fsname0, fsname1, dir0, dir1, type0, type1, opts0, opts1;
if (sscanf(buf, " %n%*s%n %n%*s%n %n%*s%n %n%*s%n %d %d",
&fsname0, &fsname1, &dir0, &dir1, &type0, &type1, &opts0, &opts1,
&e->mnt_freq, &e->mnt_passno) == 2) {
e->mnt_fsname = &buf[fsname0];
buf[fsname1] = '\0';
e->mnt_dir = &buf[dir0];
buf[dir1] = '\0';
e->mnt_type = &buf[type0];
buf[type1] = '\0';
e->mnt_opts = &buf[opts0];
buf[opts1] = '\0';
return e;
}
}
return NULL;
}
bool isPresentMntOpt(const struct mntent *pMnt, const char *pOpt) {
char *token = pMnt->mnt_opts;
const char *end = pMnt->mnt_opts + strlen(pMnt->mnt_opts);
const size_t optLen = strlen(pOpt);
while (token != NULL) {
const char *tokenEnd = token + optLen;
if (tokenEnd > end) break;
if (memcmp(token, pOpt, optLen) == 0 &&
(*tokenEnd == '\0' || *tokenEnd == ',' || *tokenEnd == '=')) {
return true;
}
token = strchr(token, ',');
if (token != NULL) {
token++;
}
}
return false;
}
static char *concat2str(const char *pString1, const char *pString2) {
char *result;
size_t lengthBuffer = 0;
lengthBuffer = strlen(pString1) +
strlen(pString2) + 1;
result = malloc(lengthBuffer);
if (result == NULL) {
GR_LOGW("malloc failed\n");
return NULL;
}
memset(result, 0, lengthBuffer);
strcpy(result, pString1);
strcat(result, pString2);
return result;
}
static bool
isBadPropertyState(const char *key, const char *badValue, bool isObligatoryProperty, bool isExact) {
if (badValue == NULL) {
GR_LOGE("badValue may not be NULL");
return false;
}
if (key == NULL) {
GR_LOGE("key may not be NULL");
return false;
}
char value[PROP_VALUE_MAX + 1];
int length = __system_property_get(key, value);
bool result = false;
/* A length 0 value indicates that the property is not defined */
if (length > 0) {
GR_LOGI("property:[%s]==[%s]", key, value);
if (isExact) {
if (strcmp(value, badValue) == 0) {
GR_LOGW("bad value[%s] equals to [%s] in the property [%s]", value, badValue, key);
result = true;
}
} else {
if (strlen(value) >= strlen(badValue) && strstr(value, badValue) != NULL) {
GR_LOGW("bad value[%s] found in [%s] in the property [%s]", value, badValue, key);
result = true;
}
}
} else {
GR_LOGI("[%s] property not found", key);
if (isObligatoryProperty) {
result = true;
}
}
return result;
}
bool isDetectedTestKeys() {
const char *TEST_KEYS_VALUE = "test-keys";
return isBadPropertyState(ANDROID_OS_BUILD_TAGS, TEST_KEYS_VALUE, true, false);
}
bool isDetectedDevKeys() {
const char *DEV_KEYS_VALUE = "dev-keys";
return isBadPropertyState(ANDROID_OS_BUILD_TAGS, DEV_KEYS_VALUE, true, false);
}
bool isNotFoundReleaseKeys() {
const char *RELEASE_KEYS_VALUE = "release-keys";
return !isBadPropertyState(ANDROID_OS_BUILD_TAGS, RELEASE_KEYS_VALUE, false, true);
}
bool isFoundWrongPathPermission() {
bool result = false;
FILE *file = fopen("/proc/mounts", "r");
char mntent_strings[BUFSIZ];
if (file == NULL) {
GR_LOGE("setmntent");
return result;
}
struct mntent ent = {0};
while (NULL != getMntent(file, &ent, mntent_strings, sizeof(mntent_strings))) {
for (size_t i = 0; MG_READ_ONLY_PATH[i]; i++) {
if (strcmp((&ent)->mnt_dir, MG_READ_ONLY_PATH[i]) == 0 &&
isPresentMntOpt(&ent, "rw")) {
GR_LOGI("%s %s %s %s\n", (&ent)->mnt_fsname, (&ent)->mnt_dir, (&ent)->mnt_opts,
(&ent)->mnt_type);
result = true;
break;
}
}
memset(&ent, 0, sizeof(ent));
}
fclose(file);
return result;
}
bool isFoundDangerousProps() {
const char *BAD_DEBUGGABLE_VALUE = "1";
const char *BAD_SECURE_VALUE = "0";
const char *BAD_SYS_INITD_VALUE = "1";
const char *BAD_SERVICE_ADB_ROOT_VALUE = "1";
bool result = isBadPropertyState(ANDROID_OS_DEBUGGABLE, BAD_DEBUGGABLE_VALUE, true, true) ||
isBadPropertyState(SERVICE_ADB_ROOT, BAD_SERVICE_ADB_ROOT_VALUE, false, true) ||
isBadPropertyState(ANDROID_OS_SECURE, BAD_SECURE_VALUE, true, true) ||
isBadPropertyState(ANDROID_OS_SYS_INITD, BAD_SYS_INITD_VALUE, false, true);
return result;
}
bool isPermissiveSelinux() {
const char *BAD_VALUE = "0";
return isBadPropertyState(ANDROID_OS_BUILD_SELINUX, BAD_VALUE, false, false);
}
bool isSuExists() {
char buf[BUFSIZ];
char *str = NULL;
char *temp = NULL;
size_t size = 1; // start with size of 1 to make room for null terminator
size_t strlength;
FILE *pipe = popen("which su", "r");
if (pipe == NULL) {
GR_LOGI("pipe is null");
return false;
}
while (fgets(buf, sizeof(buf), pipe) != NULL) {
strlength = strlen(buf);
temp = realloc(str, size + strlength); // allocate room for the buf that gets appended
if (temp == NULL) {
// allocation error
GR_LOGE("Error (re)allocating memory");
pclose(pipe);
if (str != NULL) {
free(str);
}
return false;
} else {
str = temp;
}
strcpy(str + size - 1, buf);
size += strlength;
}
pclose(pipe);
GR_LOGW("A size of the result from pipe is [%zu], result:\n [%s] ", size, str);
if (str != NULL) {
free(str);
}
return size > 1 ? true : false;
}
static bool isAccessedFile(const char *path) {
int result = access(path, F_OK);
GR_LOGV("[%s] has been accessed with result: [%d]", path, result);
return result == 0 ? true : false;
}
static bool isFoundBinaryFromArray(const char *const *array, const char *binary) {
for (size_t i = 0; array[i]; ++i) {
char *checkedPath = concat2str(array[i], binary);
if (checkedPath == NULL) { // malloc failed
return false;
}
bool result = isAccessedFile(checkedPath);
free(checkedPath);
if (result) {
return result;
}
}
return false;
}
bool isAccessedSuperuserApk() {
return isAccessedFile("/system/app/Superuser.apk");
}
bool isFoundResetprop() {
return isAccessedFile("/data/magisk/resetprop");
}
bool isFoundSuBinary() {
return isFoundBinaryFromArray(MG_SU_PATH, "su");
}
bool isFoundBusyboxBinary() {
return isFoundBinaryFromArray(MG_SU_PATH, "busybox");
}
bool isFoundXposed() {
for (size_t i = 0; MG_EXPOSED_FILES[i]; ++i) {
bool result = isAccessedFile(MG_EXPOSED_FILES[i]);
if (result) {
return result;
}
}
return false;
}
bool isFoundHooks() {
bool result = false;
pid_t pid = getpid();
char maps_file_name[512];
sprintf(maps_file_name, "/proc/%d/maps", pid);
GR_LOGI("try to open [%s]", maps_file_name);
const size_t line_size = BUFSIZ;
char *line = malloc(line_size);
if (line == NULL) {
return result;
}
FILE *fp = fopen(maps_file_name, "r");
if (fp == NULL) {
free(line);
return result;
}
memset(line, 0, line_size);
const char *substrate = "com.saurik.substrate";
const char *xposed = "XposedBridge.jar";
while (fgets(line, line_size, fp) != NULL) {
const size_t real_line_size = strlen(line);
if ((real_line_size >= strlen(substrate) && strstr(line, substrate) != NULL) ||
(real_line_size >= strlen(xposed) && strstr(line, xposed) != NULL)) {
GR_LOGI("found in [%s]: [%s]", maps_file_name, line);
result = true;
break;
}
}
free(line);
fclose(fp);
return result;
}
Upvotes: 11
Reputation: 1451
http://code.google.com/p/roottools/
If you do not want to use the jar file just use the code:
public static boolean findBinary(String binaryName) {
boolean found = false;
if (!found) {
String[] places = { "/sbin/", "/system/bin/", "/system/xbin/",
"/data/local/xbin/", "/data/local/bin/",
"/system/sd/xbin/", "/system/bin/failsafe/", "/data/local/" };
for (String where : places) {
if (new File(where + binaryName).exists()) {
found = true;
break;
}
}
}
return found;
}
Program will try to find su folder:
private static boolean isRooted() {
return findBinary("su");
}
Example:
if (isRooted()) {
textView.setText("Device Rooted");
} else {
textView.setText("Device Unrooted");
}
Upvotes: 23
Reputation: 16952
The RootTools library offers simple methods to check for root:
RootTools.isRootAvailable()
Upvotes: 61
Reputation: 4634
Instead of using isRootAvailable() you can use isAccessGiven(). Direct from RootTools wiki:
if (RootTools.isAccessGiven()) {
// your app has been granted root access
}
RootTools.isAccessGiven() not only checks that a device is rooted, it also calls su for your app, requests permission, and returns true if your app was successfully granted root permissions. This can be used as the first check in your app to make sure that you will be granted access when you need it.
Upvotes: 13
Reputation: 2153
Root check at Java level is not a safe solution. If your app has Security Concerns to run on a Rooted device , then please use this solution.
Kevin's answer works unless the phone also has an app like RootCloak . Such apps have a Handle over Java APIs once phone is rooted and they mock these APIs to return phone is not rooted.
I have written a native level code based on Kevin's answer , it works even with RootCloak ! Also it does not cause any memory leak issues.
#include <string.h>
#include <jni.h>
#include <time.h>
#include <sys/stat.h>
#include <stdio.h>
#include "android_log.h"
#include <errno.h>
#include <unistd.h>
#include <sys/system_properties.h>
JNIEXPORT int JNICALL Java_com_test_RootUtils_checkRootAccessMethod1(
JNIEnv* env, jobject thiz) {
//Access function checks whether a particular file can be accessed
int result = access("/system/app/Superuser.apk",F_OK);
ANDROID_LOGV( "File Access Result %d\n", result);
int len;
char build_tags[PROP_VALUE_MAX]; // PROP_VALUE_MAX from <sys/system_properties.h>.
len = __system_property_get(ANDROID_OS_BUILD_TAGS, build_tags); // On return, len will equal (int)strlen(model_id).
if(strcmp(build_tags,"test-keys") == 0){
ANDROID_LOGV( "Device has test keys\n", build_tags);
result = 0;
}
ANDROID_LOGV( "File Access Result %s\n", build_tags);
return result;
}
JNIEXPORT int JNICALL Java_com_test_RootUtils_checkRootAccessMethod2(
JNIEnv* env, jobject thiz) {
//which command is enabled only after Busy box is installed on a rooted device
//Outpput of which command is the path to su file. On a non rooted device , we will get a null/ empty path
//char* cmd = const_cast<char *>"which su";
FILE* pipe = popen("which su", "r");
if (!pipe) return -1;
char buffer[128];
std::string resultCmd = "";
while(!feof(pipe)) {
if(fgets(buffer, 128, pipe) != NULL)
resultCmd += buffer;
}
pclose(pipe);
const char *cstr = resultCmd.c_str();
int result = -1;
if(cstr == NULL || (strlen(cstr) == 0)){
ANDROID_LOGV( "Result of Which command is Null");
}else{
result = 0;
ANDROID_LOGV( "Result of Which command %s\n", cstr);
}
return result;
}
JNIEXPORT int JNICALL Java_com_test_RootUtils_checkRootAccessMethod3(
JNIEnv* env, jobject thiz) {
int len;
char build_tags[PROP_VALUE_MAX]; // PROP_VALUE_MAX from <sys/system_properties.h>.
int result = -1;
len = __system_property_get(ANDROID_OS_BUILD_TAGS, build_tags); // On return, len will equal (int)strlen(model_id).
if(len >0 && strstr(build_tags,"test-keys") != NULL){
ANDROID_LOGV( "Device has test keys\n", build_tags);
result = 0;
}
return result;
}
In your Java code , you need to create wrapper class RootUtils to make the native calls
public boolean checkRooted() {
if( rootUtils.checkRootAccessMethod3() == 0 || rootUtils.checkRootAccessMethod1() == 0 || rootUtils.checkRootAccessMethod2() == 0 )
return true;
return false;
}
Upvotes: 31
Reputation: 15741
Using C++ with the ndk is the best approach to detect root even if the user is using applications that hide his root such as RootCloak. I tested this code with RootCloak and I was able to detect the root even if the user is trying to hide it. So your cpp file would like:
#include <jni.h>
#include <string>
/**
*
* function that checks for the su binary files and operates even if
* root cloak is installed
* @return integer 1: device is rooted, 0: device is not
*rooted
*/
extern "C"
JNIEXPORT int JNICALL
Java_com_example_user_root_1native_rootFunction(JNIEnv *env,jobject thiz){
const char *paths[] ={"/system/app/Superuser.apk", "/sbin/su", "/system/bin/su",
"/system/xbin/su", "/data/local/xbin/su", "/data/local/bin/su", "/system/sd/xbin/su",
"/system/bin/failsafe/su", "/data/local/su", "/su/bin/su"};
int counter =0;
while (counter<9){
if(FILE *file = fopen(paths[counter],"r")){
fclose(file);
return 1;
}
counter++;
}
return 0;
}
And you will call the function from your java code as follows
public class Root_detect {
/**
*
* function that calls a native function to check if the device is
*rooted or not
* @return boolean: true if the device is rooted, false if the
*device is not rooted
*/
public boolean check_rooted(){
int checker = rootFunction();
if(checker==1){
return true;
}else {
return false;
}
}
static {
System.loadLibrary("cpp-root-lib");//name of your cpp file
}
public native int rootFunction();
}
Upvotes: 4
Reputation: 9643
RootBeer is a root checking Android library by Scott and Matthew. It uses various checks to indicate whether device is rooted or not.
Java checks
CheckRootManagementApps
CheckPotentiallyDangerousAppss
CheckRootCloakingApps
CheckTestKeys
checkForDangerousProps
checkForBusyBoxBinary
checkForSuBinary
checkSuExists
checkForRWSystem
Native checks
We call through to our native root checker to run some of it's own checks. Native checks are typically harder to cloak, so some root cloak apps just block the loading of native libraries that contain certain key words.
- checkForSuBinary
Upvotes: 14
Reputation: 831
Many of the answers listed here have inherent issues:
The RootTools library from Stericson seems to be checking for root more legitimately. It also has lots of extra tools and utilities so I highly recommend it. However, there's no explanation of how it specifically checks for root, and it may be a bit heavier than most apps really need.
I've made a couple of utility methods that are loosely based on the RootTools library. If you simply want to check if the "su" executable is on the device you can use the following method:
public static boolean isRootAvailable(){
for(String pathDir : System.getenv("PATH").split(":")){
if(new File(pathDir, "su").exists()) {
return true;
}
}
return false;
}
This method simply loops through the directories listed in the "PATH" environment variable and checks if a "su" file exists in one of them.
In order to truly check for root access the "su" command must actually be run. If an app like SuperUser is installed, then at this point it may ask for root access, or if its already been granted/denied a toast may be shown indicating whether access was granted/denied. A good command to run is "id" so that you can verify that the user id is in fact 0 (root).
Here's a sample method to determine whether root access has been granted:
public static boolean isRootGiven(){
if (isRootAvailable()) {
Process process = null;
try {
process = Runtime.getRuntime().exec(new String[]{"su", "-c", "id"});
BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
String output = in.readLine();
if (output != null && output.toLowerCase().contains("uid=0"))
return true;
} catch (Exception e) {
e.printStackTrace();
} finally {
if (process != null)
process.destroy();
}
}
return false;
}
It's important to actually test running the "su" command because some emulators have the "su" executable pre-installed, but only allow certain users to access it like the adb shell.
It's also important to check for the existence of the "su" executable before trying to run it, because android has been known to not properly dispose of processes that try to run missing commands. These ghost processes can run up memory consumption over time.
Upvotes: 41
Reputation: 17206
Here is a class that will check for Root one of three ways.
/** @author Kevin Kowalewski */
public class RootUtil {
public static boolean isDeviceRooted() {
return checkRootMethod1() || checkRootMethod2() || checkRootMethod3();
}
private static boolean checkRootMethod1() {
String buildTags = android.os.Build.TAGS;
return buildTags != null && buildTags.contains("test-keys");
}
private static boolean checkRootMethod2() {
String[] paths = { "/system/app/Superuser.apk", "/sbin/su", "/system/bin/su", "/system/xbin/su", "/data/local/xbin/su", "/data/local/bin/su", "/system/sd/xbin/su",
"/system/bin/failsafe/su", "/data/local/su", "/su/bin/su"};
for (String path : paths) {
if (new File(path).exists()) return true;
}
return false;
}
private static boolean checkRootMethod3() {
Process process = null;
try {
process = Runtime.getRuntime().exec(new String[] { "/system/xbin/which", "su" });
BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
if (in.readLine() != null) return true;
return false;
} catch (Throwable t) {
return false;
} finally {
if (process != null) process.destroy();
}
}
}
Upvotes: 295
Reputation: 31
if [[ "`adb shell which su | grep -io "permission denied"`" != "permission denied" ]]; then
echo "Yes. Rooted device."
else
echo "No. Device not rooted. Only limited tasks can be performed. Done."
zenity --warning --title="Device Not Rooted" --text="The connected Android Device is <b>NOT ROOTED</b>. Only limited tasks can be performed." --no-wrap
fi
Upvotes: 2
Reputation: 1726
public static boolean isRootAvailable(){
Process p = null;
try{
p = Runtime.getRuntime().exec(new String[] {"su"});
writeCommandToConsole(p,"exit 0");
int result = p.waitFor();
if(result != 0)
throw new Exception("Root check result with exit command " + result);
return true;
} catch (IOException e) {
Log.e(LOG_TAG, "Su executable is not available ", e);
} catch (Exception e) {
Log.e(LOG_TAG, "Root is unavailable ", e);
}finally {
if(p != null)
p.destroy();
}
return false;
}
private static String writeCommandToConsole(Process proc, String command, boolean ignoreError) throws Exception{
byte[] tmpArray = new byte[1024];
proc.getOutputStream().write((command + "\n").getBytes());
proc.getOutputStream().flush();
int bytesRead = 0;
if(proc.getErrorStream().available() > 0){
if((bytesRead = proc.getErrorStream().read(tmpArray)) > 1){
Log.e(LOG_TAG,new String(tmpArray,0,bytesRead));
if(!ignoreError)
throw new Exception(new String(tmpArray,0,bytesRead));
}
}
if(proc.getInputStream().available() > 0){
bytesRead = proc.getInputStream().read(tmpArray);
Log.i(LOG_TAG, new String(tmpArray,0,bytesRead));
}
return new String(tmpArray);
}
Upvotes: 5
Reputation: 738
Using my library at rootbox, it is pretty easy. Check the required code below:
//Pass true to <Shell>.start(...) call to run as superuser
Shell shell = null;
try {
shell = Shell.start(true);
} catch (IOException exception) {
exception.printStackTrace();
}
if (shell == null)
// We failed to execute su binary
return;
if (shell.isRoot()) {
// Verified running as uid 0 (root), can continue with commands
...
} else
throw Exception("Unable to gain root access. Make sure you pressed Allow/Grant in superuser prompt.");
Upvotes: -3
Reputation: 469
Indeed it is interesting question and so far nobody has deserved award. I use the following code:
boolean isRooted() {
try {
ServerSocket ss = new ServerSocket(81);
ss.close();
return true;
} catch (Exception e) {
// not sure
}
return false;
}
The code is certainly not bulletproof, because network can be not available so you get an exception. If this method returns true then 99% you can be sure, otherwise just 50% that not. Networking permission can also spoil the solution.
Upvotes: 0
Reputation: 25864
Further to @Kevins answer, I've recently found while using his system, that the Nexus 7.1 was returning false
for all three methods - No which
command, no test-keys
and SuperSU
was not installed in /system/app
.
I added this:
public static boolean checkRootMethod4(Context context) {
return isPackageInstalled("eu.chainfire.supersu", context);
}
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;
}
}
This is slightly less useful in some situations (if you need guaranteed root access) as it's completely possible for SuperSU to be installed on devices which don't have SU access.
However, since it's possible to have SuperSU installed and working but not in the /system/app
directory, this extra case will root (haha) out such cases.
Upvotes: 7
Reputation: 15533
In my application I was checking if device is rooted or not by executing "su" command. But today I've removed this part of my code. Why?
Because my application became a memory killer. How? Let me tell you my story.
There were some complaints that my application was slowing down devices(Of course I thought that can not be true). I tried to figure out why. So I used MAT to get heap dumps and analyze, and everything seemed perfect. But after relaunching my app many times I realized that device is really getting slower and stopping my application didn't make it faster (unless I restart device). I analyzed dump files again while device is very slow. But everything was still perfect for dump file. Then I did what must be done at first. I listed processes.
$ adb shell ps
Surprize; there were many processes for my application (with my application's process tag at manifest). Some of them was zombie some of them not.
With a sample application which has a single Activity and executes just "su" command, I realized that a zombie process is being created on every launch of application. At first these zombies allocate 0KB but than something happens and zombie processes are holding nearly same KBs as my application's main process and they became standart processes.
There is a bug report for same issue on bugs.sun.com: https://bugs.java.com/bugdatabase/view_bug?bug_id=6474073 this explains if command is not found zombies are going to be created with exec() method. But I still don't understand why and how can they become standart processes and hold significant KBs. (This is not happening all the time)
You can try if you want with code sample below;
String commandToExecute = "su";
executeShellCommand(commandToExecute);
Simple command execution method;
private boolean executeShellCommand(String command){
Process process = null;
try{
process = Runtime.getRuntime().exec(command);
return true;
} catch (Exception e) {
return false;
} finally{
if(process != null){
try{
process.destroy();
}catch (Exception e) {
}
}
}
}
To sum up; I have no advice for you to determine if device is rooted or not. But if I were you I would not use Runtime.getRuntime().exec().
By the way; RootTools.isRootAvailable() causes same problem.
Upvotes: 61
Reputation: 109
Two additional ideas, if you want to check if a device is root capable from your app:
Runtime.getRuntime().exec()
/system/app/Superuser.apk
locationUpvotes: 4
Reputation: 17557
Here is my code based on some answers here:
/**
* Checks if the phone is rooted.
*
* @return <code>true</code> if the phone is rooted, <code>false</code>
* otherwise.
*/
public static boolean isPhoneRooted() {
// get from build info
String buildTags = android.os.Build.TAGS;
if (buildTags != null && buildTags.contains("test-keys")) {
return true;
}
// check if /system/app/Superuser.apk is present
try {
File file = new File("/system/app/Superuser.apk");
if (file.exists()) {
return true;
}
} catch (Throwable e1) {
// ignore
}
return false;
}
Upvotes: 9
Reputation: 11551
Some modified builds used to set the system property ro.modversion
for this purpose. Things seem to have moved on; my build from TheDude a few months ago has this:
cmb@apollo:~$ adb -d shell getprop |grep build
[ro.build.id]: [CUPCAKE]
[ro.build.display.id]: [htc_dream-eng 1.5 CUPCAKE eng.TheDudeAbides.20090427.235325 test-keys]
[ro.build.version.incremental]: [eng.TheDude.2009027.235325]
[ro.build.version.sdk]: [3]
[ro.build.version.release]: [1.5]
[ro.build.date]: [Mon Apr 20 01:42:32 CDT 2009]
[ro.build.date.utc]: [1240209752]
[ro.build.type]: [eng]
[ro.build.user]: [TheDude]
[ro.build.host]: [ender]
[ro.build.tags]: [test-keys]
[ro.build.product]: [dream]
[ro.build.description]: [kila-user 1.1 PLAT-RC33 126986 ota-rel-keys,release-keys]
[ro.build.fingerprint]: [tmobile/kila/dream/trout:1.1/PLAT-RC33/126986:user/ota-rel-keys,release-keys]
[ro.build.changelist]: [17615# end build properties]
The emulator from the 1.5 SDK on the other hand, running the 1.5 image, also has root, is probably similar to the Android Dev Phone 1 (which you presumably want to allow) and has this:
cmb@apollo:~$ adb -e shell getprop |grep build
[ro.build.id]: [CUPCAKE]
[ro.build.display.id]: [sdk-eng 1.5 CUPCAKE 148875 test-keys]
[ro.build.version.incremental]: [148875]
[ro.build.version.sdk]: [3]
[ro.build.version.release]: [1.5]
[ro.build.date]: [Thu May 14 18:09:10 PDT 2009]
[ro.build.date.utc]: [1242349750]
[ro.build.type]: [eng]
[ro.build.user]: [android-build]
[ro.build.host]: [undroid16.mtv.corp.google.com]
[ro.build.tags]: [test-keys]
[ro.build.product]: [generic]
[ro.build.description]: [sdk-eng 1.5 CUPCAKE 148875 test-keys]
[ro.build.fingerprint]: [generic/sdk/generic/:1.5/CUPCAKE/148875:eng/test-keys]
As for the retail builds, I don't have one to hand, but various searches under site:xda-developers.com
are informative. Here is a G1 in the Netherlands, you can see that ro.build.tags
does not have test-keys
, and I think that's probably the most reliable property to use.
Upvotes: 12