내 응용 프로그램에는 루트를 사용할 수있는 장치에서만 작동하는 특정 기능이 있습니다. 이 기능을 사용할 때 실패한 다음 사용자에게 적절한 오류 메시지를 표시하는 대신 루트를 먼저 사용할 수 있는지 자동으로 확인하고 그렇지 않은 경우 먼저 해당 옵션을 숨기는 기능을 선호합니다 .
이 방법이 있습니까?
내 응용 프로그램에는 루트를 사용할 수있는 장치에서만 작동하는 특정 기능이 있습니다. 이 기능을 사용할 때 실패한 다음 사용자에게 적절한 오류 메시지를 표시하는 대신 루트를 먼저 사용할 수 있는지 자동으로 확인하고 그렇지 않은 경우 먼저 해당 옵션을 숨기는 기능을 선호합니다 .
이 방법이 있습니까?
답변:
다음은 세 가지 방법 중 하나를 루트로 검사하는 클래스입니다.
/** @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
놀람; 내 응용 프로그램에 대한 많은 프로세스가있었습니다 (내 응용 프로그램의 프로세스 태그가 매니페스트에 있음). 그들 중 일부는 좀비 였고 일부는 그렇지 않았습니다.
단일 액티비티가 있고 "su"명령 만 실행하는 샘플 응용 프로그램을 사용하면 응용 프로그램을 시작할 때마다 좀비 프로세스가 생성되고 있음을 깨달았습니다. 처음 에이 좀비는 0KB를 할당하지만 무언가가 발생하는 것보다 좀비 프로세스는 내 응용 프로그램의 주요 프로세스와 거의 동일한 KB를 보유하고 있으며 표준 프로세스가되었습니다.
bugs.sun.com에 동일한 문제에 대한 버그 보고서가 있습니다. http://bugs.sun.com/view_bug.do?bug_id=6474073 명령을 찾을 수없는 좀비 간부 만들 수 위하여려고하는 경우에이 설명 () 메소드 . 그러나 나는 왜 왜 그리고 어떻게 그들이 표준 프로세스가되고 중요한 KB를 보유 할 수 있는지 이해하지 못합니다. (이것은 항상 일어나지 않습니다)
아래 코드 샘플로 원한다면 시도해 볼 수 있습니다.
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 ()은 동일한 문제를 일으 킵니다.
여기에 나열된 많은 답변에는 고유 한 문제가 있습니다.
Stericson 의 RootTools 라이브러리는 더 합법적으로 루트를 확인하는 것 같습니다. 또한 추가 도구와 유틸리티가 많이 있으므로 강력히 권장합니다. 그러나 루트를 구체적으로 확인하는 방법에 대한 설명은 없으며 대부분의 앱이 실제로 필요한 것보다 약간 무거울 수 있습니다.
RootTools 라이브러리에 기반을 둔 몇 가지 유틸리티 메소드를 만들었습니다. "su"실행 파일이 장치에 있는지 간단히 확인하려면 다음 방법을 사용할 수 있습니다.
public static boolean isRootAvailable(){
for(String pathDir : System.getenv("PATH").split(":")){
if(new File(pathDir, "su").exists()) {
return true;
}
}
return false;
}
이 방법은 단순히 "PATH"환경 변수에 나열된 디렉토리를 반복하고 그 중 하나에 "su"파일이 있는지 확인합니다.
루트 액세스를 확인하려면 "su"명령을 실제로 실행해야합니다. SuperUser와 같은 앱이 설치되어있는 경우이 시점에서 루트 액세스를 요청하거나 이미 부여 / 거부 된 경우 액세스가 부여 / 거부되었는지를 나타내는 토스트가 표시 될 수 있습니다. 실행하기에 좋은 명령은 "id"이므로 사용자 ID가 실제로 0 (루트)인지 확인할 수 있습니다.
루트 액세스 권한이 부여되었는지 확인하는 샘플 방법은 다음과 같습니다.
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"실행 파일이 사전 설치되어 있지만 특정 사용자 만 adb 셸과 같이 액세스 할 수 있으므로 "su"명령 실행을 실제로 테스트하는 것이 중요합니다.
안 드 로이드는 누락 된 명령을 실행하려고하는 프로세스를 제대로 처리하지 않는 것으로 알려져 있기 때문에 실행하기 전에 "su"실행 파일이 있는지 확인하는 것이 중요합니다. 이러한 고스트 프로세스는 시간이 지남에 따라 메모리 소비를 증가시킬 수 있습니다.
2017 업데이트
이제 Google Safetynet API로 할 수 있습니다 . SafetyNet API는 앱이 실행되는 Android 환경의 보안 및 호환성을 평가하는 데 도움이되는 증명 API를 제공합니다.
이 증명은 특정 장치가 변조되었거나 다른 방식으로 수정되었는지 여부를 확인하는 데 도움이됩니다.
증명 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과 같은 앱이 없으면 Kevin의 답변이 작동합니다. 전화가 루팅되면 이러한 앱에 Java API 처리 기능이 있으며 전화가 루팅되지 않도록 이러한 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");
}
== true
는 아무것도 추가하지 않고 잘 보이지 않는, 부울에.
if (isRooted())
명시 적으로 true를 쓰지 말고 사용하십시오 . 코드 작성 패턴을 따르는 것이 좋습니다
isRootAvailable ()을 사용하는 대신 isAccessGiven ()을 사용할 수 있습니다. RootTools 위키 에서 직접 :
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의 루트 검사 안드로이드 라이브러리입니다. 다양한 검사를 통해 장치가 루팅되었는지 여부를 나타냅니다.
자바 검사
CheckRootManagementApps
잠재적으로 위험한 앱 확인
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
세 가지 방법 모두에 대해 반환되는 것을 발견했습니다 - 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;
}
}
이것은 약간입니다 적게 는 SuperSU는 SU에 액세스 할 수없는 장치에 설치하기에 완전하게 가능으로 (당신이 보장 루트 액세스를 필요로하는 경우)에 유용 상황이다.
그것을 가지고하는 것이 가능하기 때문에, 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);
}
사용자가 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;
}
그리고 당신은 다음과 같이 자바 코드에서 함수를 호출 할 것입니다
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
Google Play 서비스 에는 Safety Net Attestation API 가 있어 기기를 평가하고 루팅 / 훼손 여부를 판단 할 수 있습니다.
루팅 된 장치를 다루기 위해 내 대답을 따르십시오 : https :
//.com/a/58304556/3908895
루트 앱과 su 바이너리를 탐지하는 모든 것을 잊어 버려라. 루트 데몬 프로세스를 확인하십시오. 이것은 터미널에서 수행 할 수 있으며 앱 내에서 터미널 명령을 실행할 수 있습니다. 이 하나의 라이너를 사용해보십시오.
if [ ! -z "$(/system/bin/ps -A | grep -v grep | grep -c daemonsu)" ]; then echo "device is rooted"; else echo "device is not rooted"; fi
이것을 달성하기 위해 루트 권한이 필요하지 않습니다.
실제로 그것은 흥미로운 질문이며 지금까지 아무도 상을받을 자격이 없습니다. 다음 코드를 사용합니다.
boolean isRooted() {
try {
ServerSocket ss = new ServerSocket(81);
ss.close();
return true;
} catch (Exception e) {
// not sure
}
return false;
}
네트워크를 사용할 수 없으므로 예외가 발생하기 때문에 코드는 확실히 방탄되지 않습니다. 이 메소드가 true를 리턴하면 99 %를 확신 할 수 있습니다. 그렇지 않으면 50 % 만 그렇지 않습니다. 네트워킹 권한도 솔루션을 망칠 수 있습니다.
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.");