胡凯莉 发表于 2023-3-24 12:27

常见Java层反调试技术之root检测方式总结---之用Shamiko能过绕过多少

# 常见Java层反调试技术之root检测方式总结---之用Shamiko能过绕过多少

测试设备:红米Note7

安卓版本:9

环境:Magisk+Shamiko模块

## 1、检测which su----------可绕过

- java层代码实现

- ```java
public class MainActivity extends AppCompatActivity {

      @Override
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);
          if(checkSuExists()){
            Toast.makeText(getApplicationContext(), "检测到su命令", Toast.LENGTH_SHORT).show();
          }else {
            Toast.makeText(getApplicationContext(), "没有检测到su命令", Toast.LENGTH_SHORT).show();
          }
      }
      public boolean checkSuExists(){
          Process process = null;

          try {
            process = Runtime.getRuntime().exec(new String[] {"which","su"});
            BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
            returnin.readLine() != null;
          } catch (Throwable t) {
             return false;
          }finally {
            if (process != null) process.destroy();
          }
      }

}
```

- 未使用Shamiko

- !(https://img-pool-own.oss-cn-shanghai.aliyuncs.com/img/image-20230324084423862.png)       

- 使用Shamiko进行排除

- !(https://img-pool-own.oss-cn-shanghai.aliyuncs.com/img/image-20230324084531728.png)
- !(https://img-pool-own.oss-cn-shanghai.aliyuncs.com/img/image-20230324084602825.png)



## 2、检测在常用目录下是否存在非法的二进制文件---可绕过

- root过红米Note7存在的root目录

```
1|lavender:/ $ which magisk
/sbin/magisk
lavender:/ $ which su
/sbin/su
lavender:/ $
```

- 检测方法---遍历系统文件 存在sbin/的就是非法的

- ```
lavender:/ $ echo $PATH | grep /system/bin
/sbin:/system/sbin:/system/bin:/system/xbin:/odm/bin:/vendor/bin:/vendor/xbin
lavender:/ $
```

- ```
   //      checkForBinary("magisk");
//      checkForBinary("su");
//      checkForBinary("busybox");
   
   
   
   
   
   
   
   
   
   
   
   
   
   public void checkForBinary(String filename){
          String[] pathsArray = this.getPaths();

          boolean flag = false;
          for (String path : pathsArray) {

            String fullPath = path + filename;
            Log.e("fullPath",fullPath);
            File f = new File(path, filename);
            boolean fileExists = f.exists();
            if (fileExists){
                  Log.e("fullPath","检测到非法二进制文件");
                  Toast.makeText(getApplicationContext(), "检测到非法二进制文件", Toast.LENGTH_SHORT).show();
            }
          }
      }
private String[] getPaths() {
      ArrayList<String> paths = new ArrayList<>(Arrays.asList(suPaths));

      String sysPaths = System.getenv("PATH");
      //如果获取不到这个系统路径变量返回静态路径
      if (sysPaths == null || "".equals(sysPaths)){
          //创建一个类型为String的空数组,长度为0。这可能会用作占位符或初始化一个变量,稍后会用字符串值填充该变量。
          return paths.toArray(new String);
      }
      for (String path : sysPaths.split(":")) {
          if (!path.endsWith("/")){
            path = path + '/';
          }
          if (!paths.contains(path)){
            paths.add(path);
          }
      }


      return paths.toArray(new String);
}
```

-

- !(https://img-pool-own.oss-cn-shanghai.aliyuncs.com/img/image-20230324093111743.png)

- - !(https://img-pool-own.oss-cn-shanghai.aliyuncs.com/img/image-20230324093111743.png)

- Shamiko隐藏后

- !(https://img-pool-own.oss-cn-shanghai.aliyuncs.com/img/image-20230324093204945.png)

## 3、判断SELinux是否开启(过时)

- 用java反射获取ro.build.selinux值判断

- ```java
   private boolean isSelinuxFlagInEnabled(){

          try {
            Class<?> c = Class.forName("android.os.SystemProperties");
            Method get = c.getMethod("get", String.class);
            String selinux = (String) get.invoke(c,"ro.build.selinux");
            return "1".equals(selinux);
          } catch (Exception e) {
            e.printStackTrace();
          }
          return false;
      }
```

- 没开Shamiko

!(C:\Users\李世林\AppData\Roaming\Typora\typora-user-images\image-20230324094117527.png)

## 4、检测ro.debuggable和ro.secure值----未知

- ro.debuggable-------------是否开启Android Debug Bridge功能

- 在生产环境中,该属性通常应该被设置为 "0" 以增加设备的安全性。如果该属性设置为 "1",则任何人都可以使用 ADB 命令访问设备,从而可能会导致设备受到攻击或其他安全问题

- ```
    lavender:/ $ getprop ro.debuggable
    0
    ```

- ro.secure---------Android中的系统属性 是否开启安全性强化措施1开启0 未开启

- 不可信我的红米note7 root过

- ```
    lavender:/ $ getprop ro.secure
    1
    ```

- ```java
      private void checkForDangerousProps(){
          final Map<String,String> dangerousProps = new HashMap<>();
          dangerousProps.put("ro.debuggable","1");
          boolean result = false;

          String[] lines = propsReader();
          if (lines == null){
            return;
          }
          for (String line : lines) {
            for (String key : dangerousProps.keySet()){
                  if (line.contains(key)){
                      String badValue = dangerousProps.get(key);
                      badValue = "["+ badValue + "]";
                      if (line.contains(badValue)){
                        Toast.makeText(getApplicationContext(), "检测到危险值 "+key+": "+badValue,
                                  Toast.LENGTH_LONG).show();
                      }else {
                        Toast.makeText(getApplicationContext(), "未检测到危险值 "+line,
                                  Toast.LENGTH_LONG).show();
                      }
                  }
            }
          }
      }

      private String[] propsReader() {

          try {
            InputStream inputStream = Runtime.getRuntime().exec("getprop").getInputStream();
            if (inputStream == null) return null;
            /*
          这行代码的意思是从输入流(InputStream)中获取文本内容并将其以字符串(String)的形式存储在propVal变量中。具体来说,代码使用Scanner类的构造函数将InputStream对象inputstream作为参数,创建了一个Scanner对象。

然后,代码使用useDelimiter("\A")方法来设置Scanner对象的分隔符,该分隔符表示使用"\A"正则表达式,即匹配输入的开始处,即输入流的开头。这样设置分隔符的目的是让Scanner将整个输入流作为一个标记读取。

紧接着,代码使用Scanner的next()方法来读取Scanner对象的下一个标记,即整个输入流。将读取到的标记以字符串形式存储在propVal变量中。

可以使用这行代码读取文件或网络流中的文本内容,也可以读取控制台输入的文本内容。
         */
            String propVal = new Scanner(inputStream).useDelimiter("\\A").next();
            return propVal.split("\n");
          } catch (IOException e) {
            e.printStackTrace();
          }
return null;
      }
```

> 很奇怪 我开不开这个Shamiko这个值都是0   估计是把面具隐藏了?

!(https://img-pool-own.oss-cn-shanghai.aliyuncs.com/img/image-20230324103636322.png)

## 5、检查特定路径是否有写权限-----绕过失败

- Java代码将使用mount命令检查这些路径的读写权限,如果可读写,说明设备可能被root了

- ```
         /system
            /system/bin
            /system/sbin
            /system/xbin
            /vendor/bin
            /sbin
            /etc
            /sys
            /proc
            /dev
```

- !(https://img-pool-own.oss-cn-shanghai.aliyuncs.com/img/image-20230324105942286.png)

- 以空格隔开了

- ```java
   private String[] pathsThatShouldNotBeWritable = {
            "/system",
            "/system/bin",
            "/system/sbin",
            "/system/xbin",
            "/vendor/bin",
            "/sbin",
            "/etc",
            "/sys",
            "/proc",
            "/dev"
      };
      public String[] mountReader(){
          /*
          在安卓中,mount命令用于挂载文件系统或卸载文件系统,具体功能包括但不限于以下几种:

      挂载存储设备:使用mount命令可以挂载已经插入的SD卡、U盘等存储设备,以便在系统中访问其内容。

      系统分区读写:挂载系统分区后,可以访问和修改Linux系统下的各种配置文件、启动文件以及用户数据等。

      安装软件包:在安卓系统中,软件包安装文件通常安装在系统分区中,如果要正常安装软件包,就需要先挂载系统分区。

      卸载文件系统:展开程序运行时不同的功能模块时,可能需要动态地加载或卸载文件系统。卸载文件系统时,可以利用mount命令将文件系统卸载,以免占用过多的系统资源。

总之,mount命令在安卓中是一个非常重要的命令,对于维护安卓系统的稳定性和正常运行有非常重要的作用。
         */

          try {
            InputStream inputStream = Runtime.getRuntime().exec("mount").getInputStream();

            if(inputStream == null) return null;
            String propVal = new Scanner(inputStream).useDelimiter("\\A").next();
            return propVal.split("\n");
          } catch (IOException e) {
            Toast.makeText(getApplicationContext(), e.getMessage(),
                      Toast.LENGTH_LONG).show();
            return null;
          }

      }

      public void checkForRWPaths(){
          //Run the command "mount" to retrieve all mounted directories
          String[] lines = mountReader();
          if (lines == null){
            return;
          }

          int sdkVersion = Build.VERSION.SDK_INT;
          for (String line : lines) {
            String[] args = line.split(" ");
            if ((sdkVersion <= android.os.Build.VERSION_CODES.M && args.length < 4)
                      || (sdkVersion > android.os.Build.VERSION_CODES.M && args.length < 6)) {
                  // If we don't have enough options per line, skip this and log an error
                  Toast.makeText(getApplicationContext(), "Error formatting mount line: "+line+line,
                        Toast.LENGTH_LONG).show();
                  continue;
            }
            String mountPoint;
            String mountOptions;

            /**
               * To check if the device is running Android version higher than Marshmallow or not
               */
            if (sdkVersion > android.os.Build.VERSION_CODES.M) {
                  mountPoint = args;//就是挂载路径
                  mountOptions = args;
            } else {
                  mountPoint = args;
                  mountOptions = args;
            }
            for (String pathToCheck: this.pathsThatShouldNotBeWritable){
                  if (mountPoint.equalsIgnoreCase(pathToCheck)){
                      /**
                     * If the device is running an Android version above Marshmallow,
                     * need to remove parentheses from options parameter;
                     */
                      if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.M) {
                        mountOptions = mountOptions.replace("(", "");
                        mountOptions = mountOptions.replace(")", "");

                      }
                      // Split options out and compare against "rw" to avoid false positives
                      for (String option : mountOptions.split(",")){

                        if (option.equalsIgnoreCase("rw")){
                              Toast.makeText(getApplicationContext(), pathToCheck+" 路径以rw权限挂载! "+line,
                                    Toast.LENGTH_LONG).show();
                        }
                      }
                  }

            }
          }
      }
```

- !(https://img-pool-own.oss-cn-shanghai.aliyuncs.com/img/image-20230324110803163.png)
- !(https://img-pool-own.oss-cn-shanghai.aliyuncs.com/img/image-20230324111800507.png)

- **开启Shamiko**

- !(https://img-pool-own.oss-cn-shanghai.aliyuncs.com/img/image-20230324110846207.png)
- !(https://img-pool-own.oss-cn-shanghai.aliyuncs.com/img/image-20230324111854846.png)
- 只隐藏了一个路径sbin

## 6、检测test-keys

- ro.build.tags 参数值为"test-key" 说明是测试版的数字签名构造的 有root的风险

- ro.build.tags 参数值为"release-keys"正式发布的版本 这种密钥是由厂家发布的数字签名,更难被攻击,因此是相对安全的。

- ```
lavender:/ $getprop ro.build.tags
release-keys
```

- 我的红米Note7root过但是还是release-keys

- 换模拟器试试也一样

- ```
   getprop ro.build.tags
release-keys
```

> 这个可能是编译了源码 变成的test-key

- ```
      public boolean detectTestKeys() {
          String buildTags = android.os.Build.TAGS;
          return buildTags != null && buildTags.contains("test-keys");
      }
```

## 7、检测非法应用----可绕过

- ```
lavender:/ $ pm list packages | findstr magisk
/system/bin/sh: findstr: not found
127|lavender:/ $
```

- 我的migisk被我隐藏了 应该是这样 检测不出来

- ```java
   static final String[] knownRootAppsPackages = {
            "com.topjohnwu.magisk"
            //add....
      };
      public void detectPotentiallyDangerousApps(){
          ArrayList<String> packages = new ArrayList<>();
          packages.addAll(Arrays.asList(this.knownRootAppsPackages));
          sAnyPackageFromListInstalled(packages);
      }

      private void sAnyPackageFromListInstalled(List<String> packages) {
          PackageManager pm = getApplicationContext().getPackageManager();
          for (String packageName : packages) {
            try {
                  pm.getPackageInfo(packageName,0);
                  Toast.makeText(getApplicationContext(), "检测到非法应用: "+packageName,
                        Toast.LENGTH_LONG).show();
            } catch (PackageManager.NameNotFoundException e) {
                  Toast.makeText(getApplicationContext(), "没有检测到非法应用: "+packageName,
                        Toast.LENGTH_LONG).show();
            }
          }

      }
```

- 先取消隐藏

- !(https://img-pool-own.oss-cn-shanghai.aliyuncs.com/img/image-20230324122022349.png)
- Shamiko隐藏一下 重启.
- !(https://img-pool-own.oss-cn-shanghai.aliyuncs.com/img/image-20230324122259804.png)

## 总结

- Magisk + Shamiko 基本上除了挂载文件的rw权限搞不定其他的都OK的
- java层上的ROOT检测 以这篇帖子为参考来测试
- https://www.52pojie.cn/thread-1763111-1-1.html
- 以后有其他的检测点 在做测试

## 附录代码

```java
package com.example.rootcheck;

import androidx.appcompat.app.AppCompatActivity;

import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
//      if(checkSuExists()){
//            Toast.makeText(getApplicationContext(), "检测到su命令", Toast.LENGTH_SHORT).show();
//      }else {
//            Toast.makeText(getApplicationContext(), "没有检测到su命令", Toast.LENGTH_SHORT).show();
//      }
//      checkForBinary("magisk");
//      checkForBinary("su");
//      checkForBinary("busybox");
//
//      if(isSelinuxFlagInEnabled())
//      {
//            Toast.makeText(getApplicationContext(), "SELinux开启",
//                  Toast.LENGTH_SHORT).show();
//      }
//      else
//      {
//            Toast.makeText(getApplicationContext(), "SELinux没有开启",
//                  Toast.LENGTH_SHORT).show();
//      }
      //checkForDangerousProps();
      //checkForRWPaths();
//      if(detectTestKeys())
//            Toast.makeText(getApplicationContext(), "Test Keys",
//                  Toast.LENGTH_SHORT).show();
//      else
//            Toast.makeText(getApplicationContext(), "Relese Keys",
//                  Toast.LENGTH_SHORT).show();
      detectPotentiallyDangerousApps();
    }
    public boolean detectTestKeys() {
      String buildTags = android.os.Build.TAGS;
      return buildTags != null && buildTags.contains("test-keys");
    }
    private static final String[] suPaths = {
            "/data/local/",
            "/data/local/bin/",
            "/data/local/xbin/",
            "/sbin/",
            "/su/bin/",
            "/system/bin/",
            "/system/bin/.ext/",
            "/system/bin/failsafe/",
            "/system/sd/xbin/",
            "/system/usr/we-need-root/",
            "/system/xbin/",
            "/cache/",
            "/data/",
            "/dev/"
    };
    private String[] pathsThatShouldNotBeWritable = {
            "/system",
            "/system/bin",
            "/system/sbin",
            "/system/xbin",
            "/vendor/bin",
            "/sbin",
            "/etc",
            "/sys",
            "/proc",
            "/dev"
    };
    static final String[] knownRootAppsPackages = {
            "com.topjohnwu.magisk"
            //add....
    };
    public void detectPotentiallyDangerousApps(){
      ArrayList<String> packages = new ArrayList<>();
      packages.addAll(Arrays.asList(this.knownRootAppsPackages));
      sAnyPackageFromListInstalled(packages);
    }

    private void sAnyPackageFromListInstalled(List<String> packages) {
      PackageManager pm = getApplicationContext().getPackageManager();
      for (String packageName : packages) {
            try {
                pm.getPackageInfo(packageName,0);
                Toast.makeText(getApplicationContext(), "检测到非法应用: "+packageName,
                        Toast.LENGTH_LONG).show();
            } catch (PackageManager.NameNotFoundException e) {
                Toast.makeText(getApplicationContext(), "没有检测到非法应用: "+packageName,
                        Toast.LENGTH_LONG).show();
            }
      }

    }

    public String[] mountReader(){
      /*
      在安卓中,mount命令用于挂载文件系统或卸载文件系统,具体功能包括但不限于以下几种:

    挂载存储设备:使用mount命令可以挂载已经插入的SD卡、U盘等存储设备,以便在系统中访问其内容。

    系统分区读写:挂载系统分区后,可以访问和修改Linux系统下的各种配置文件、启动文件以及用户数据等。

    安装软件包:在安卓系统中,软件包安装文件通常安装在系统分区中,如果要正常安装软件包,就需要先挂载系统分区。

    卸载文件系统:展开程序运行时不同的功能模块时,可能需要动态地加载或卸载文件系统。卸载文件系统时,可以利用mount命令将文件系统卸载,以免占用过多的系统资源。

总之,mount命令在安卓中是一个非常重要的命令,对于维护安卓系统的稳定性和正常运行有非常重要的作用。
         */

      try {
            InputStream inputStream = Runtime.getRuntime().exec("mount").getInputStream();

            if(inputStream == null) return null;
            String propVal = new Scanner(inputStream).useDelimiter("\\A").next();
            return propVal.split("\n");
      } catch (IOException e) {
            Toast.makeText(getApplicationContext(), e.getMessage(),
                  Toast.LENGTH_LONG).show();
            return null;
      }

    }

    public void checkForRWPaths(){
      //Run the command "mount" to retrieve all mounted directories
      String[] lines = mountReader();
      if (lines == null){
            return;
      }

      int sdkVersion = Build.VERSION.SDK_INT;
      for (String line : lines) {
            String[] args = line.split(" ");
            if ((sdkVersion <= android.os.Build.VERSION_CODES.M && args.length < 4)
                  || (sdkVersion > android.os.Build.VERSION_CODES.M && args.length < 6)) {
                // If we don't have enough options per line, skip this and log an error
                Toast.makeText(getApplicationContext(), "Error formatting mount line: "+line+line,
                        Toast.LENGTH_LONG).show();
                continue;
            }
            String mountPoint;
            String mountOptions;

            /**
             * To check if the device is running Android version higher than Marshmallow or not
             */
            if (sdkVersion > android.os.Build.VERSION_CODES.M) {
                mountPoint = args;//就是挂载路径
                mountOptions = args;
            } else {
                mountPoint = args;
                mountOptions = args;
            }
            for (String pathToCheck: this.pathsThatShouldNotBeWritable){
                if (mountPoint.equalsIgnoreCase(pathToCheck)){
                  /**
                     * If the device is running an Android version above Marshmallow,
                     * need to remove parentheses from options parameter;
                     */
                  if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.M) {
                        mountOptions = mountOptions.replace("(", "");
                        mountOptions = mountOptions.replace(")", "");

                  }
                  // Split options out and compare against "rw" to avoid false positives
                  for (String option : mountOptions.split(",")){

                        if (option.equalsIgnoreCase("rw")){
                            Log.e("挂载路径",pathToCheck+" 路径以rw权限挂载! "+line);
                            Toast.makeText(getApplicationContext(), pathToCheck+" 路径以rw权限挂载! "+line,
                                    Toast.LENGTH_LONG).show();
                        }
                  }
                }

            }
      }
    }

    public void checkForBinary(String filename) {
      String[] pathsArray = this.getPaths();

      boolean flag = false;
      for (String path : pathsArray) {

            String fullPath = path + filename;
            Log.e("fullPath", fullPath);
            File f = new File(path, filename);
            boolean fileExists = f.exists();
            if (fileExists) {
                Log.e("fullPath", "检测到非法二进制文件");
                Toast.makeText(getApplicationContext(), "检测到非法二进制文件", Toast.LENGTH_SHORT).show();
            }
      }
    }

    private String[] getPaths() {
      ArrayList<String> paths = new ArrayList<>(Arrays.asList(suPaths));

      String sysPaths = System.getenv("PATH");
      //如果获取不到这个系统路径变量返回静态路径
      if (sysPaths == null || "".equals(sysPaths)) {
            //创建一个类型为String的空数组,长度为0。这可能会用作占位符或初始化一个变量,稍后会用字符串值填充该变量。
            return paths.toArray(new String);
      }
      for (String path : sysPaths.split(":")) {
            if (!path.endsWith("/")) {
                path = path + '/';
            }
            if (!paths.contains(path)) {
                paths.add(path);
            }
      }


      return paths.toArray(new String);
    }

    public boolean checkSuExists() {
      Process process = null;

      try {
            process = Runtime.getRuntime().exec(new String[]{"which", "su"});
            BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
            return in.readLine() != null;
      } catch (Throwable t) {
            return false;
      } finally {
            if (process != null) process.destroy();
      }
    }

    private boolean isSelinuxFlagInEnabled() {

      try {
            Class<?> c = Class.forName("android.os.SystemProperties");
            Method get = c.getMethod("get", String.class);
            String selinux = (String) get.invoke(c, "ro.build.selinux");
            return "1".equals(selinux);
      } catch (Exception e) {
            e.printStackTrace();
      }
      return false;
    }

    private void checkForDangerousProps() {

      final Map<String, String> dangerousProps = new HashMap<>();
      dangerousProps.put("ro.debuggable", "1");

      boolean result = false;

      String[] lines = propsReader();


      if (lines == null){
            // Could not read, assume false;
            return;
      }

      for (String line : lines) {
            Log.e("line",line);
            for (String key : dangerousProps.keySet()) {
                //Log.e("key",key);
                if (line.contains(key)) {
                  String badValue = dangerousProps.get(key);
                  badValue = "[" + badValue + "]";
                  //Log.e("badValue",badValue);
                  if (line.contains(badValue)) {
                        Toast.makeText(getApplicationContext(), "检测到危险值 "+key+": "+badValue,
                              Toast.LENGTH_LONG).show();
                  }else {
                        Toast.makeText(getApplicationContext(), "未检测到危险值 "+line,
                              Toast.LENGTH_LONG).show();
                  }
                }
            }
      }
    }

    private String[] propsReader() {

      try {
            InputStream inputStream = Runtime.getRuntime().exec("getprop").getInputStream();
            if (inputStream == null) return null;
          /*
      这行代码的意思是从输入流(InputStream)中获取文本内容并将其以字符串(String)的形式存储在propVal变量中。具体来说,代码使用Scanner类的构造函数将InputStream对象inputstream作为参数,创建了一个Scanner对象。

然后,代码使用useDelimiter("\A")方法来设置Scanner对象的分隔符,该分隔符表示使用"\A"正则表达式,即匹配输入的开始处,即输入流的开头。这样设置分隔符的目的是让Scanner将整个输入流作为一个标记读取。

紧接着,代码使用Scanner的next()方法来读取Scanner对象的下一个标记,即整个输入流。将读取到的标记以字符串形式存储在propVal变量中。

可以使用这行代码读取文件或网络流中的文本内容,也可以读取控制台输入的文本内容。
         */
            String propVal = new Scanner(inputStream).useDelimiter("\\A").next();
            return propVal.split("\n");
      } catch (IOException e) {
            e.printStackTrace();
      }
      return null;
    }

}


```

正己 发表于 2023-3-24 13:04

内容重复了吧
《常见Java层反调试技术之root检测方式总结》
https://www.52pojie.cn/thread-1763111-1-1.html
(出处: 吾爱破解论坛)

胡凯莉 发表于 2023-3-24 14:15

正己 发表于 2023-3-24 13:04
内容重复了吧
《常见Java层反调试技术之root检测方式总结》
https://www.52pojie.cn/thread-1763111-1-1. ...

就是对着来的呀就是想复现然后看看Shamiko能过这些反调不能   最后我总结有写哦

YRay 发表于 2023-3-24 12:39

感谢分享 学到了不少东西

ponghaaz11 发表于 2023-3-24 12:54

感谢分享

15705107305 发表于 2023-3-24 14:03

强啊!!!

Easonll 发表于 2023-3-24 14:28

感谢分享学习学习

geyutong 发表于 2023-3-24 14:36

技术提升了不少

Js_Aaron 发表于 2023-3-24 14:39

这个写的太厉害了!学习了,谢谢

ryanu 发表于 2023-3-24 14:56

感谢分享
页: [1] 2
查看完整版本: 常见Java层反调试技术之root检测方式总结---之用Shamiko能过绕过多少