我的应用程序有一个特定的功能,只能在根可用的设备上工作。与其让这个特性在使用时失败(然后向用户显示适当的错误消息),我更希望能够先静默地检查根目录是否可用,如果不可用,则首先隐藏相应的选项。
有办法做到这一点吗?
我的应用程序有一个特定的功能,只能在根可用的设备上工作。与其让这个特性在使用时失败(然后向用户显示适当的错误消息),我更希望能够先静默地检查根目录是否可用,如果不可用,则首先隐藏相应的选项。
有办法做到这一点吗?
当前回答
根据这里的一些答案,我合并了它们,并添加了一个检查是否安装了一些已知的根管理器应用程序:
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
}
清单:
<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>
当然,这仍然是一个猜测,就像所有其他解一样。 例如,用户可以安装Magisk,而无需对设备进行root操作。
其他回答
更新2017
你现在可以用谷歌Safetynet API做到这一点。SafetyNet API提供了认证API,它可以帮助您评估应用程序运行的Android环境的安全性和兼容性。
这种认证可以帮助确定特定设备是否被篡改或修改过。
认证API返回如下所示的JWS响应
{
"nonce": "R2Rra24fVm5xa2Mg",
"timestampMs": 9860437986543,
"apkPackageName": "com.package.name.of.requesting.app",
"apkCertificateDigestSha256": ["base64 encoded, SHA-256 hash of the
certificate used to sign requesting app"],
"apkDigestSha256": "base64 encoded, SHA-256 hash of the app's APK",
"ctsProfileMatch": true,
"basicIntegrity": true,
}
解析此响应可以帮助您确定设备是否已扎根
根设备似乎导致ctsProfileMatch=false。
您可以在客户端执行此操作,但建议在服务器端解析响应。 一个基本的客户端服务器架构与安全网API将看起来像这样
一些用于设置系统属性ro的修改版本。Modversion用于此目的。事情似乎已经发生了变化;我几个月前在TheDude上的构建是这样的:
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]
另一方面,来自1.5 SDK的模拟器,运行1.5镜像,也有根,可能类似于Android Dev Phone 1(你可能想要允许),并有这个:
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]
至于零售构建,我手头没有,但在site:xda developers.com下的各种搜索提供了大量信息。这是荷兰的G1, ro。build。tags没有test-key,我认为这可能是最可靠的属性。
如果你已经在使用Fabric/Firebase Crashlytics,你可以打电话
CommonUtils.isRooted(context)
这是该方法的当前实现:
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;
}
这确实是一个有趣的问题,到目前为止还没有人获奖。我使用以下代码:
boolean isRooted() {
try {
ServerSocket ss = new ServerSocket(81);
ss.close();
return true;
} catch (Exception e) {
// not sure
}
return false;
}
代码当然不是无懈可击的,因为网络可能不可用,所以会出现异常。如果该方法返回true,则99%可以确定,否则只有50%不确定。网络权限也可能破坏解决方案。
根据这里的一些答案,我合并了它们,并添加了一个检查是否安装了一些已知的根管理器应用程序:
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
}
清单:
<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>
当然,这仍然是一个猜测,就像所有其他解一样。 例如,用户可以安装Magisk,而无需对设备进行root操作。