干货 - 关于Android root绕过的知识,你知道多少?


也许每个人出生的时候都以为这世界都是为他一个人而存在的,当他发现自己错的时候,他便开始长大

少走了弯路,也就错过了风景,无论如何,感谢经历


本篇文章遇到排版混乱的地方,可点击文末阅读原文或前往该地址:https://orangey.blog.csdn.net/article/details/126219953

更多关于Android安全的知识,可前往:https://blog.csdn.net/ananasorangey/category11955914.html

0x01 前言

Android 是基于 Linux 多用户机制的访问控制。应用程序在默认的情况下不可以执行其他应用程序,包括读写用户的私有数据。一个应用程序的进程就是一个安全的沙盒(在受限的安全环境中运行应用程序,在沙盒中的所有改动对操作系统不会造成任何危害)

每一个Android应用程序都会在安装时就分配一个独有的Linux用户ID,这就为它建立了一个沙盒,使其不能与其他应用程序进行接触。这个用户ID会在安装时分配给它,并在该设备上一直保持同一个数值

所有的Android应用程序必须用证书进行签名认证,而这个证书的私钥是由开发者保有的。该证书可以用以识别应用程序的作者。签名影响安全性的最重要的方式是通过决定谁可以进入基于签名的permisssions,以及谁可以share 用户IDs。通过这样的机制,在不考虑root用户的情况下,每个应用都是相互隔离的,实现了一定的安全

在Linux操作系统中,root的权限是最高的,也被称为超级权限的拥有者。在系统中,每个文件、目录和进程,都归属于某一个用户,没有用户许可其它普通用户是无法操作的,但对root除外

在渗透测试 Android 应用程序时候,大多数技术都需要 root 权限才能安装各种工具,从而危及应用程序的安全性。就有了攻防双方对抗的现象产生,Android 开发人员又是使用了哪些技术来检测运行应用程序的设备是否已Root呢?接下来会介绍一些Android 开发人员在检查设备是否已经root的几种常用方法和一些绕过技术

出于安全原因,多应用程序不允许在已经root的设备上运行,因为手机Root后给攻击者(用户)带来了非常大的自主操作权利,让攻击者(用户)可以删除系统应用、安全或删除应用程序、查看并修改程序的运行信息,但与此同时,也给恶意软件打开方便之门,给设备信息安全带来了极大的挑战。目前许多APP在启动时会进行Root环境监测,防止APP在已经Root的手机环境中运行,如果发现设备已被Root,会向用户弹窗提示运行环境存在安全风险不让APP继续运行下一步操作或禁止安装运行(出现APK闪退现象)

上面说的检测到手机被Root,主要有两种处置手段:

两种Root处置手段进行简单的比较:

Root:获取手机超级管理员权限,android系统是基于linux内核,默认情况下并不提供超级管理员权限,所有获取su的权限就是所谓root

Root用户的特权性:root可以超越任何用户和用户组来对文件或目录进行读取、修改或删除(在系统正常的许可范围内);对可执行程序的执行、终止;对硬件设备的添加、创建和移除等;也可以对文件和目录进行属主和权限进行修改,以适合系统管理的需要(因为root是系统中权限最高的特权用户);root是超越任何用户和用户组的,基于用户ID的权限机制的沙盒是隔离不了它的

除了检测(test-keys(测试版)、release-keys(发布版))系统是否测试版、检测Root工具的安装路径,包名(特有刷root工具的包名称)是否带有su、activity、busybox、supersu或superuser等关键词外,还有如下检测方法:

目前看来绝大多数的应用都是对上面几个检测方法组合使用,甚至有很多只会实现其中的一项或者两项。另外,上面的方法也没有囊括所有的可能,比如有些应用还会检测“Magisk”或者"Superuser"等是否存在来进行Root检测。总结来说,目前主流的Root检测就是对Root之后的手机独有的一些特征进行验证,如特征文件是否存在、是否存在越权、关键属性是否被修改等等


补充知识1)标准文件权限

普通文件权限是通过十位进行表示的,如下图

第一位代表的“ 文件” 类型,其可能的值有:-(文件)、d(目录)、b(块设备)、l (link文件)、c (字符设备,如串口)、s(socket套接字)。

linux系统内有档案有三种身份

u:拥有者(user,文件的属主)
g:群组 (group,文件的属主所在的组,属组)
o:其他人(other,其它的用户)
a:包括属主、属组、其它用户(all,以上所有人)

2)rwx权限设置

文件或目录每三位用rwx表示相应的权限值,其中r值为4、w值为2、x值为1 ,可以使用chmod 对文件或目录进行权限更改。对于文档常用的有下面权限:

权限的设置有两种表示方式:

权限值表示法,如下:
# chmod 777 file1

字母值表示法,如下:
# chmod a+x file1

命令

结果

含义

chmod a-x myfile

rw- rw- rw-

收回所有用户执行权限

chmod og-w myfile

rw- r-- r--

收回同组用户和其它用户的写权限

chmod g+w myfile

rw- rw- r--

赋予同组用户写权限

chmod u+x myfile

rwx rw- r--

赋予文件属主执行权限

chmod go+x myfile

rwx rwx r-x

赋予同组用户和其它用户执行权限

3)umask

在/etc/profile文件有umask值的默认设置,默认值为022,该值对应的是默认文件和目录创建后的权限值:

目录的默认权限是:777-umask
文件的默认权限是:666-umask 

所以umask为022的情况下,默认创建的目录的权限是755 ,默认创建的文件权限为644

4)特殊权限

除了上面的提到的rwx权限外,除了读写执行权限外,系统还有三个特殊权限 s s t 权限(冒险位与粘滞位) 【强制位(s权限)和粘滞位(t权限)】,具体描述如下:

权限

对文件的影响

对目录的影响

suid

以文件的所属目录身份执行,而非执行文件的用户

sgid

以文件所属组身份执行

该目录中创建的任意新文件的所属组与该目录的所属组相同

sticky

对目录拥有写入权限的用户仅可以删除其拥有的文件,无法删除其它用户所拥有的文件

这三个特殊权限也可以用字母和数字表示,具体如下:

设置suid:    chmodu+stestchmod  u+s  testchmodu+stest
设置sgid:设置sgid:设置sgid: chmod  g+s  test
设置sticky:  $ chmod  o+t  test
suid:4
sgid:2
sticky:1

s即(SUID,Set UID)设置使文件在执行阶段具有文件所有者的权限,相当于临时拥有文件所有者的身份. 典型的文件是passwd. 如果一般用户执行该文件, 则在执行过程中, 该文件可以获得root权限, 从而可以更改用户的密码

在一些特殊情况下会用到特殊权限位,如passwd命令,如果没有s权限,其他用户会无法使用passwd命令修改自己的密码

# ls -l /usr/bin/passwd
-rwsr-xr-x. 1 root root 27832 Jun 10  2014 /usr/bin/passwd

给一个增加了s s t 权限的示例:

# touch test
# ll test
-rw-r--r--. 1 root root   0 Aug  5 01:03 test
# chmod 7777 test
# ll test
-rwsrwsrwt. 1 root root   0 Aug  5 01:03 test
# su - usera

上面由于增加了t权限,所以普通用户usera,可以通文件写入和更改,无法删除文件


备注:BusyBox是很多标准 Linux 工具的一个单个可执行实现。BusyBox 包含了一些简单的工具,例如 cat 和 echo,还包含了一些更大、更复杂的工具,例如 grep、find、moun)

root方式分为两种:

目前获取Android root 权限常用方法是通过各种系统漏洞,替换或添加SU程序到设备,获取Root权限,而在获取root权限以后,会装一个程序用以提醒用户是否给予程序最高权限,可以一定程度上防止恶意软件,通常会使用Superuser或者 SuperSU ,这种方法通常叫做“不完全Root”。

而 “完全Root”是指,替换设备原有的ROM,以实现取消secure设置

如何绕过Root检测机制呢?这里提供两个思路,其一是对应用下手,干预应用的Root检测行为;另外一个思路则是对系统下手,隐藏系统自身Root相关的特征。我们可以借助jadx等逆向工具对应用源码进行分析,Hook相关的实现函数绕过;也可以获取AOSP源码,通过定制ROM来隐藏Root的特征

目前主流的Hook框架有frida和xposed,可以用frida的可见框架RMS进行注入。相对来说,通过Hook的方式来绕过Root检测机制操作比较简单、方便,但Hook本身会受到很多的约束。一方面,受限于应用自身的加固手段,可能难以定位ROOT检测的实现函数;另一方面,Hook框架自身也会具备一些易于被检测到的特征,可能会受到这些特征的约束而难以完成工作。

定制ROM的手段有很多种,可以通过对官方包进行解包、修改后重打包。我推荐的方式是获取AOSP源码,自己编译后制作ROM包。这样可以实现更高程度的定制化,与基于现有包修改的方式相比该方式的操作空间更大,但是同样的它会带来更好的编译成本、修改难度也更大

热修复实现的本质就是将修复bug后的代码生成的dex放置在该数组的头部

基于设备的作弊检测:

基于行为的作弊检测:

APP的反作弊策略和思路:

APP的反作弊工具和策略:

0x02 绕过root检测实验

1)检查su命令是否存在

通常要获取Root权限,是使用su命令来实现的,因此可以通过检查这个命令是否存在,来判断运行环境是否Root

2)检查Android属性

检查ro.debuggable、ro.secure 两个属性是否为true,为true的话APP所运行环境很可能是Root环境

3)检查特定路径是否有写权限

具体路径包括:/system、/system/bin、/system/sbin、/system/xbin、/vendor/bin、/sys、/sbin、/etc、/proc、/dev

通过mount命令确认对应分区的权限是否为"rw"

adb shell mount | grep -w /sysfs on /sys type sysfs (rw,seclabel,relatime)

下面从几种被应用程序广泛使用的检测技术开始说起,如果设备已经root,会增加一些新的文件,所以可以通过检测这些文件是否存在来判断,还有一些开发者通过检查能否执行只有root权限下才能运行的命令来判断,当然还有一些其他的手段

  1. 之前几年最流行(现在已停止维护许久了)的root工具是Superuser.apk,是一个被广泛使用的用来root安卓设备的软件,所以可以检查这个app是否存在,但在检测之前,我们先解决安装包错的情况

解决方案

SuperSU 包含一个 su 可执行文件和一个 Superuser.apk,只需要把 SuperSU 提供的 su 可执行文件替换系统的 su 文件,并且给予权限 -rwsr-sr-x (6755) 即可。

一般在实际设备上有两种方式替换文件:

手动将 SuperSU 的 su 文件替换系统文件,需要 Root 权限;
通过 Recovery 模式直接将 su 文件已补丁包的形式刷入。
对于模拟器来说,它没有 Recovery 模式,
是直接使用 img 镜像启动的,所以只能使用第一种方法

官网下载:https://supersuroot.org/download/,选择 Recovery V2.82 Flashable.zip 进行下载,里面包含各个架构所需的 su 文件,以及 Superuser.apk 安装包

adb shell
which su
adb root
adb remount
adb push su /system/bin/su

备注:对于 Android 5.0 版本及之上的设备来说,需要使用 su.pie 文件,它是使用 -fPIE 标记编译的位置无关的可执行文件,具有地址空间随机化特性

adb shell
chmod 6755 /system/bin/su
ls -al /system/bin/su
su --install
su --daemon&
setenforce 0

上面命令解释如下:





点击重启按钮可能会卡住,直接关闭模拟器重启即可,再次打开不会再提示su被占用的情况

adb shell
ls -l /system/app/ | grep 'Super'

SuperSU 工作原理:

SuperSU 工作原理时序图如下:

SuperSu,通过recovery将其文件刷入系统即可使用。然而现在安卓会对系统的完整性进行验证,这一方法就未必行得通了。更何况很多厂商会对bootloader进行封锁,这意味着安卓各分区无法被私自修改,不能通过fastboot来刷入Su文件,也无法使用第三方的recovery,su的刷入也就无从谈起,root权限成为了遥不可及的传说。

大家可以尝试Magisk工具,跟SuperSu一样的,但比SuperSu要香多了,而且SuperSu在2021年时已经不在进行维护了。

下载地址:https://github.com/topjohnwu/Magisk

Magisk 是一套用于定制 Android 的开源软件,支持高于 Android 5.0 的设备。 一些突出特点:

  1. 还可以搜索一些特殊的package,比如下图所展示的
pm list packages | grep 'shell'

  1. 有一些应用程序只能在root的设备上运行,所以检查他们是否存在也是一个不错的方法。比如众所周知的Busybox:
busybox pwd

运行“su”和“id”,然后查看uid来检查

su
id

  1. 正式绕过root检测

安装了SuperSU的检测:



没安装SuperSU的检测:

  1. CheckRoot.java代码

package com.example.testpoc4;

import android.util.Log;
import java.io.File;

public class CheckRoot {
    // 定义TAG常量
    private  static String TAG = CheckRoot.class.getName();


    // 检查判断是否存在SuperSU.apk文件,存在的话就是root,并在日志里打印一行信息,若不是返回false
    public  static boolean checkSuperuserApk() {
        try{
            File file = new File("/system/app/SuperSU/SuperSU.apk");
            if (file.exists()){
                Log.w(TAG, "/system/app/SuperSU/SuperSU.apk exist");
                return  true;
            }
        }catch (Exception e){
        }
        return false;
    }

}

  1. MainActivity.java代码

package com.example.testpoc4;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        boolean root = CheckRoot.checkSuperuserApk();
        ((TextView) findViewById(R.id.text)).setText("Device Root:"+root);
    }
}
  1. activity_main.xm代码

<?xml version="1.0" encoding="utf-8"?>


    


为了绕过这个检查,让我们将应用程序“Superuser.apk”重命名为“Superuser0.apk”,先将/system目录从可读变成可写先使用remout,如下图所示

将其名称改变即可绕过,再次运行检测该软件的程序时已不再提示为true

0x03 常用root检测方法

常规检测方法:检测(test-keys(测试版)、release-keys(发布版))系统是否测试版、检测提权为root的应用包名、检测常用或非常用su存在的目录、检测是否使用which 查找su、检测Busybox是否存在、检测/data目录,是否有读写权限等,下面列出常用的一些检测方法的介绍:

可以查看发布的系统版本,是test-keys(测试版),还是release-keys(发布版)。

    public static boolean checkDeviceDebuggable() {
        String buildTags = android.os.Build.TAGS;
        if (buildTags != null && buildTags.contains("test-keys")) {
            Log.i(TAG, "buildTags=" + buildTags);
            return true;
        }
        return false;
    }

实际情况下,某些厂家的正式发布版本,也是test-keys,可能大家对这个标识也不是特别注意吧。所以具体是否使用,要多考虑考虑。

Superuser.apk是一个被广泛使用的用来root安卓设备的软件,所以可以检查这个app是否存在。

检测方法如下:

    public  static boolean checkSuperuserApk() {
        try{
            File file = new File("/system/app/SuperSU/SuperSU.apk");
            if (file.exists()){
                Log.w(TAG, "/system/app/SuperSU/SuperSU.apk exist");
                return  true;
            }
        }catch (Exception e){
        }
        return false;
    }

su是Linux下切换用户的命令,在使用时不带参数,就是切换到超级用户。通常我们获取root权限,就是使用su命令来实现的,所以可以检查这个命令是否存在。

检测在常用目录下是否存在su:

    public static boolean checkRootPathSU() {
        File f = null;
        final String kSuSearchPaths[] = {"/system/bin/", "/system/xbin/", "/system/sbin/", "/sbin/", "/vendor/bin/"};
        try {
            for (int i = 0; i < kSuSearchPaths.length; i++) {
                f = new File(kSuSearchPaths[i] + "su");
                if (f != null && f.exists()) {
                    Log.i(TAG, "find su in : " + kSuSearchPaths[i]);
                    return true;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

执行这个命令su。这样,系统就会在PATH路径中搜索su,如果找到,就会执行,执行成功后,就是获取到真正的超级权限了。

    public static synchronized boolean checkGetRootAuth() {
        Process process = null;
        DataOutputStream os = null;
        try {
            Log.i(TAG, "to exec su");
            process = Runtime.getRuntime().exec("su");
            os = new DataOutputStream(process.getOutputStream());
            os.writeBytes("exit
");
            os.flush();
            int exitValue = process.waitFor();
            Log.i(TAG, "exitValue=" + exitValue);
            if (exitValue == 0) {
                return true;
            } else {
                return false;
            }
        } catch (Exception e) {
            Log.i(TAG, "Unexpected error - Here is what I know: "
                    + e.getMessage());
            return false;
        } finally {
            try {
                if (os != null) {
                    os.close();
                }
                process.destroy();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

在Android系统中,有些目录是普通用户不能访问的,例如 /data、/system、/etc 等。 我们就已/data为例,来进行读写访问。本着谨慎的态度,我是先写入一个文件,然后读出,查看内容是否匹配,若匹配,才认为系统已经root了。

    public static synchronized boolean checkAccessRootData() {
        try {
            Log.i(TAG, "to write /data");
            String fileContent = "test_ok";
            Boolean writeFlag = writeFile("/data/su_test", fileContent);
            if (writeFlag) {
                Log.i(TAG, "write ok");
            } else {
                Log.i(TAG, "write failed");
            }

            Log.i(TAG, "to read /data");
            String strRead = readFile("/data/su_test");
            Log.i(TAG, "strRead=" + strRead);
            if (fileContent.equals(strRead)) {
                return true;
            } else {
                return false;
            }
        } catch (Exception e) {
            Log.i(TAG, "Unexpected error - Here is what I know: "
                    + e.getMessage());
            return false;
        }
    }

    //写文件
    public static Boolean writeFile(String fileName, String message) {
        try {
            FileOutputStream fout = new FileOutputStream(fileName);
            byte[] bytes = message.getBytes();
            fout.write(bytes);
            fout.close();
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    //读文件
    public static String readFile(String fileName) {
        File file = new File(fileName);
        try {
            FileInputStream fis = new FileInputStream(file);
            byte[] bytes = new byte[1024];
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            int len;
            while ((len = fis.read(bytes)) > 0) {
                bos.write(bytes, 0, len);
            }
            String result = new String(bos.toByteArray());
            Log.i(TAG, result);
            return result;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

将上述说的检测弄成代码做为检测,此处就不演示了,大家自行操作:

CheckRoot.java代码:

package com.example.testpoc4;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.ArrayList;

import android.util.Log;


public class CheckRoot {
    // 定义TAG常量
    private  static String TAG = CheckRoot.class.getName();

    public static boolean isDeviceRooted() {
        if (checkDeviceDebuggable()) {
            return true;
        }//check buildTags
        if (checkSuperuserApk()) {
            return true;
        }//Superuser.apk
        if (checkRootPathSU()) {
            return true;
        }//find su in some path
        if (checkRootWhichSU()) {
            return true;
        }//find su use 'which'
        if (checkBusybox()) {
            return true;
        }//find su use 'which'
        if (checkAccessRootData()) {
            return true;
        }//find su use 'which'
        if (checkGetRootAuth()) {
            return true;
        }//exec su

        return false;
    }


    // 检查判断是否存在SuperSU.apk文件,存在的话就是root
    public  static boolean checkSuperuserApk() {
        try{
            File file = new File("/system/app/SuperSU/SuperSU.apk");
            if (file.exists()){
                Log.w(TAG, "/system/app/SuperSU/SuperSU.apk exist");
                return  true;
            }
        }catch (Exception e){
        }
        return false;
    }


    public static boolean checkDeviceDebuggable() {
        String buildTags = android.os.Build.TAGS;
        if (buildTags != null && buildTags.contains("test-keys")) {
            Log.i(TAG, "buildTags=" + buildTags);
            return true;
        }
        return false;
    }

    public static boolean checkRootPathSU() {
        File f = null;
        final String kSuSearchPaths[] = {"/system/bin/", "/system/xbin/", "/system/sbin/", "/sbin/", "/vendor/bin/"};
        try {
            for (int i = 0; i < kSuSearchPaths.length; i++) {
                f = new File(kSuSearchPaths[i] + "su");
                if (f != null && f.exists()) {
                    Log.i(TAG, "find su in : " + kSuSearchPaths[i]);
                    return true;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    public static boolean checkRootWhichSU() {
        String[] strCmd = new String[]{"/system/xbin/which", "su"};
        ArrayList execResult = executeCommand(strCmd);
        if (execResult != null) {
            Log.i(TAG, "execResult=" + execResult.toString());
            return true;
        } else {
            Log.i(TAG, "execResult=null");
            return false;
        }
    }

    public static ArrayList executeCommand(String[] shellCmd) {
        String line = null;
        ArrayList fullResponse = new ArrayList();
        Process localProcess = null;
        try {
            Log.i(TAG, "to shell exec which for find su :");
            localProcess = Runtime.getRuntime().exec(shellCmd);
        } catch (Exception e) {
            return null;
        }
        BufferedWriter out = new BufferedWriter(new OutputStreamWriter(localProcess.getOutputStream()));
        BufferedReader in = new BufferedReader(new InputStreamReader(localProcess.getInputStream()));
        try {
            while ((line = in.readLine()) != null) {
                Log.i(TAG, "–> Line received: " + line);
                fullResponse.add(line);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        Log.i(TAG, "–> Full response was: " + fullResponse);
        return fullResponse;
    }

    public static synchronized boolean checkGetRootAuth() {
        Process process = null;
        DataOutputStream os = null;
        try {
            Log.i(TAG, "to exec su");
            process = Runtime.getRuntime().exec("su");
            os = new DataOutputStream(process.getOutputStream());
            os.writeBytes("exit
");
            os.flush();
            int exitValue = process.waitFor();
            Log.i(TAG, "exitValue=" + exitValue);
            if (exitValue == 0) {
                return true;
            } else {
                return false;
            }
        } catch (Exception e) {
            Log.i(TAG, "Unexpected error - Here is what I know: "
                    + e.getMessage());
            return false;
        } finally {
            try {
                if (os != null) {
                    os.close();
                }
                process.destroy();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public static synchronized boolean checkBusybox() {
        try {
            Log.i(TAG, "to exec busybox df");
            String[] strCmd = new String[]{"busybox", "df"};
            ArrayList execResult = executeCommand(strCmd);
            if (execResult != null) {
                Log.i(TAG, "execResult=" + execResult.toString());
                return true;
            } else {
                Log.i(TAG, "execResult=null");
                return false;
            }
        } catch (Exception e) {
            Log.i(TAG, "Unexpected error - Here is what I know: "
                    + e.getMessage());
            return false;
        }
    }

    public static synchronized boolean checkAccessRootData() {
        try {
            Log.i(TAG, "to write /data");
            String fileContent = "test_ok";
            Boolean writeFlag = writeFile("/data/su_test", fileContent);
            if (writeFlag) {
                Log.i(TAG, "write ok");
            } else {
                Log.i(TAG, "write failed");
            }

            Log.i(TAG, "to read /data");
            String strRead = readFile("/data/su_test");
            Log.i(TAG, "strRead=" + strRead);
            if (fileContent.equals(strRead)) {
                return true;
            } else {
                return false;
            }
        } catch (Exception e) {
            Log.i(TAG, "Unexpected error - Here is what I know: "
                    + e.getMessage());
            return false;
        }
    }

    //写文件
    public static Boolean writeFile(String fileName, String message) {
        try {
            FileOutputStream fout = new FileOutputStream(fileName);
            byte[] bytes = message.getBytes();
            fout.write(bytes);
            fout.close();
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    //读文件
    public static String readFile(String fileName) {
        File file = new File(fileName);
        try {
            FileInputStream fis = new FileInputStream(file);
            byte[] bytes = new byte[1024];
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            int len;
            while ((len = fis.read(bytes)) > 0) {
                bos.write(bytes, 0, len);
            }
            String result = new String(bos.toByteArray());
            Log.i(TAG, result);
            return result;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

MainActivity.java代码

package com.example.testpoc4;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        boolean deviceRoot = CheckRoot.isDeviceRooted();
        ((TextView) findViewById(R.id.text)).setText("Device Root:" +deviceRoot);
    }
}

参考链接

https://blog.csdn.net/weixin_47883636/article/details/108687059

https://www.jianshu.com/p/8a9b84df5018

https://blog.chrxw.com/archives/2020/07/18/1301.html

https://bbs.pediy.com/thread-263203.htm

https://github.com/yunshuipiao/Potato/issues/53

https://juejin.cn/post/6844903733248131079

https://github.com/DeFuture/Superuser

https://blog.csdn.net/quanshui540/article/details/48242459

https://github.com/Labmem003/anti-counterfeit-android

https://github.com/t0thkr1s/allsafe


你以为你有很多路可以选择,其实你只有一条路可以走


展开阅读全文

页面更新:2024-03-11

标签:干货   应用程序   命令   权限   代码   文件   目录   工具   知识   用户   设备   系统

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top