私のアプリには、ルートが利用可能なデバイスでのみ機能する特定の機能があります。この機能を使用すると失敗するのではなく(ユーザーに適切なエラーメッセージを表示する)、rootが最初に使用可能かどうかを静かに確認し、そうでない場合は、最初にそれぞれのオプションを非表示にする機能を使用します。
これを行う方法はありますか?
私のアプリには、ルートが利用可能なデバイスでのみ機能する特定の機能があります。この機能を使用すると失敗するのではなく(ユーザーに適切なエラーメッセージを表示する)、rootが最初に使用可能かどうかを静かに確認し、そうでない場合は、最初にそれぞれのオプションを非表示にする機能を使用します。
これを行う方法はありますか?
回答:
3つの方法のいずれかでルートをチェックするクラスを次に示します。
/** @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();
}
}
}
su
できません。一部の電話には、ルート化されていないバイナリが含まれているためです。
すでに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();
}
}
}
私のアプリケーションでは、「su」コマンドを実行して、デバイスがルート化されているかどうかを確認していました。しかし、今日私はコードのこの部分を削除しました。どうして?
私のアプリケーションがメモリキラーになったからです。どうやって?私の話をしましょう。
私のアプリケーションがデバイスを遅くしているという不満がいくつかありました(もちろん、私はそれが本当であるはずがないと思いました)。その理由を理解しようとしました。したがって、MATを使用してヒープダンプを取得し、分析したところ、すべてが完璧に見えました。しかし、何度もアプリを再起動した後、デバイスが実際に遅くなっていて、アプリケーションを停止してもそれが速くならないことに気付きました(デバイスを再起動しない限り)。デバイスが非常に遅いときに、ダンプファイルを再度分析しました。しかし、すべてがまだダンプファイルに最適でした。それから私は最初にしなければならないことをしました。プロセスをリストしました。
$ adb shell ps
サプライズ; 私のアプリケーションには多くのプロセスがありました(私のアプリケーションのプロセスタグがマニフェストにあります)。ゾンビだった人もいれば、ゾンビだった人もいました。
アクティビティが1つで「su」コマンドだけを実行するサンプルアプリケーションを使用すると、アプリケーションを起動するたびにゾンビプロセスが作成されていることに気付きました。最初、これらのゾンビは0KBを割り当てますが、何かが起こり、ゾンビプロセスが私のアプリケーションのメインプロセスとほぼ同じKBを保持していて、それらは標準的なプロセスになりました。
bugs.sun.comに同じ問題のバグレポートがあります:http ://bugs.sun.com/view_bug.do?bug_id=6474073 これは、コマンドが見つからない場合に、exec()メソッドでゾンビが作成されることを説明しています。しかし、なぜ、どのようにして標準的なプロセスになり、重要なナレッジベースを保持できるのか、私にはまだわかりません。(これは常に発生しているわけではありません)
必要に応じて、以下のコードサンプルを試すことができます。
String commandToExecute = "su";
executeShellCommand(commandToExecute);
簡単なコマンド実行方法。
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) {
}
}
}
}
総括する; デバイスがルート化されているかどうかを判断するためのアドバイスはありません。しかし、私があなただったら、Runtime.getRuntime()。exec()は使用しません。
ところで; RootTools.isRootAvailable()でも同じ問題が発生します。
ここにリストされている回答の多くには固有の問題があります。
RootToolsの Stericsonからライブラリは、より多くの合法的ルートをチェックしているようです。また、追加のツールやユーティリティがたくさんあるので、強くお勧めします。ただし、ルートを具体的にチェックする方法についての説明はなく、ほとんどのアプリが実際に必要とするよりも少し重いかもしれません。
RootToolsライブラリに大まかに基づいたいくつかのユーティリティメソッドを作成しました。「su」実行可能ファイルがデバイス上にあるかどうかを確認するだけの場合は、次の方法を使用できます。
public static boolean isRootAvailable(){
for(String pathDir : System.getenv("PATH").split(":")){
if(new File(pathDir, "su").exists()) {
return true;
}
}
return false;
}
このメソッドは、「PATH」環境変数にリストされているディレクトリをループし、そのうちの1つに「su」ファイルが存在するかどうかを確認します。
rootアクセスを本当にチェックするためには、 "su"コマンドを実際に実行する必要があります。SuperUserのようなアプリがインストールされている場合、この時点でルートアクセスを要求するか、すでに許可/拒否されている場合は、アクセスが許可/拒否されたかどうかを示すトーストが表示されます。実行に適したコマンドは「id」です。これにより、ユーザーIDが実際に0(ルート)であることを確認できます。
rootアクセスが許可されているかどうかを判断するサンプルメソッドを次に示します。
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;
}
一部のエミュレータには「su」実行可能ファイルがプリインストールされているため、実際に「su」コマンドの実行をテストすることが重要ですが、特定のユーザーのみがadbシェルのようにそれにアクセスできます。
Androidは、不足しているコマンドを実行しようとするプロセスを適切に処理しないことが知られているため、実行する前に「su」実行可能ファイルの存在を確認することも重要です。これらのゴーストプロセスは、時間の経過とともにメモリ消費量を増やす可能性があります。
2017年の更新
Google Safetynet APIを使用して今すぐ実行できます。SafetyNet APIは、アプリが実行されるAndroid環境のセキュリティと互換性を評価するのに役立つAttestation APIを提供します。
この証明書は、特定のデバイスが改ざんされているか、変更されているかどうかを判断するのに役立ちます。
Attestation 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を備えた基本的なクライアントサーバーアーキテクチャは次のようになります。
Javaレベルでのルートチェックは安全なソリューションではありません。ルート権限を取得したデバイス上で実行するアプリにセキュリティ上の懸念がある場合は、このソリューションを使用してください。
ケビンの答えは、電話にRootCloakのようなアプリもない限り機能します。このようなアプリは、電話がルート化され、これらのAPIをモックして電話がルート化されないように戻すと、Java APIを介したハンドルがあります。
Kevinの回答に基づいてネイティブレベルのコードを記述しましたが、RootCloakでも動作します!また、メモリリークの問題も発生しません。
#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;
}
Javaコードで、ネイティブ呼び出しを行うには、ラッパークラスRootUtilsを作成する必要があります。
public boolean checkRooted() {
if( rootUtils.checkRootAccessMethod3() == 0 || rootUtils.checkRootAccessMethod1() == 0 || rootUtils.checkRootAccessMethod2() == 0 )
return true;
return false;
}
http://code.google.com/p/roottools/
jarファイルを使用したくない場合は、次のコードを使用してください。
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;
}
プログラムはsuフォルダーを見つけようとします:
private static boolean isRooted() {
return findBinary("su");
}
例:
if (isRooted()) {
textView.setText("Device Rooted");
} else {
textView.setText("Device Unrooted");
}
checkRootMethod4()
をKevin's Answerと同じように使用しています。
== true
ブールに、それは何も加えず、良い見ていません。
if (isRooted())
明示的にtrueを記述する代わりに、使用、チェックします。コード記述パターンに従うほうがよい
isRootAvailable()を使用する代わりに、isAccessGiven()を使用できます。RootTools wikiから直接:
if (RootTools.isAccessGiven()) {
// your app has been granted root access
}
RootTools.isAccessGiven()は、デバイスがルート化されていることを確認するだけでなく、アプリのsuを呼び出し、権限を要求し、アプリにルート権限が正常に付与されている場合はtrueを返します。これは、必要なときにアクセスが許可されることを確認するためのアプリの最初のチェックとして使用できます。
この目的でシステムプロパティ を設定するために使用されたいくつかの変更されたビルド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イメージを実行する1.5 SDKのエミュレーターにもルートがあり、おそらく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-keys
ます。おそらく、これが最も信頼できるプロパティだと思います。
RootBeerは、ScottとMatthewによるルートチェックAndroidライブラリです。さまざまなチェックを使用して、デバイスがルート化されているかどうかを示します。
Javaチェック
CheckRootManagementApps
CheckPotentiallyDangerousAppss
CheckRootCloakingApps
CheckTestKeys
checkForDangerousProps
checkForBusyBoxBinary
checkForSuBinary
checkSuExists
checkForRWSystem
ネイティブチェック
ネイティブルートチェッカーを呼び出して、独自のチェックの一部を実行します。通常、ネイティブチェックはクローキングが難しいため、一部のルートクロークアプリは、特定のキーワードを含むネイティブライブラリの読み込みをブロックするだけです。
- checkForSuBinary
ルート検出にはネイティブコードを使用することをお勧めします。 以下は完全に機能する例です。
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();
}
定数:
// 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
};
ネイティブコードからのルート検出:
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;
}
ここにいくつかの答えに基づいた私のコードがあります:
/**
* 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;
}
@Kevinsの回答に加えて、最近彼のシステムを使用しているときに、Nexus 7.1がfalse
3つの方法すべてに戻ってきたことがわかりました- which
コマンドなし、いいえtest-keys
、SuperSU
にインストールされていません/system/app
。
私はこれを追加しました:
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;
}
}
これはわずかである少なく、それはSUへのアクセスを持っていないデバイスにインストールされるようにSuperSUのために完全に可能だとして(あなたが保証rootアクセスが必要な場合は)いくつかの状況で役に立ちます。
ただし、SuperSUをインストールして機能させることは可能ですが、ディレクトリにはないため/system/app
、この追加のケースはそのようなケースを根絶します(笑)。
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);
}
アプリからデバイスがルート対応であるかどうかを確認する場合の2つの追加のアイデア:
Runtime.getRuntime().exec()
/system/app/Superuser.apk
場所でSuperUser.apkを探しますユーザーがRootCloakなどのルートを非表示にするアプリケーションを使用している場合でも、ndkでC ++を使用することは、ルートを検出するための最良のアプローチです。このコードをRootCloakでテストしたところ、ユーザーがルートを隠そうとしても、ルートを検出できました。したがって、cppファイルは次のようになります。
#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;
}
そして、あなたはあなたのJavaコードから次のように関数を呼び出すでしょう
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();
}
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
あるセーフティネットアテステーションAPIの、Googleのプレイサービス我々は、デバイスを評価し、それが改ざん/根ざしているかどうかを判断することができたことでは。
ルート権限を取得されたデバイスに対処するには、私の答えに目を通してください:https :
//stackoverflow.com/a/58304556/3908895
実際、それは興味深い質問であり、これまでのところ誰も賞に値するものはありません。次のコードを使用します。
boolean isRooted() {
try {
ServerSocket ss = new ServerSocket(81);
ss.close();
return true;
} catch (Exception e) {
// not sure
}
return false;
}
ネットワークが利用できないため例外が発生する可能性があるため、コードは完全ではありません。このメソッドがtrueを返した場合は99%であると確信できます。ネットワーク権限もソリューションを台無しにする可能性があります。
rootboxで私のライブラリを使用すると、それは非常に簡単です。以下の必要なコードを確認してください:
//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.");