AI手机网,短视频直播 硬改改机 一键新机 群控软件 刷机定制

 找回密码
 立即注册
搜索
查看: 3472|回复: 5

Xposed那些事儿 — xposed框架的检测和反制

[复制链接]
发表于 2019-7-14 17:44:33 | 显示全部楼层 |阅读模式
  1. 之前看到有人发了关于使用xposed屏蔽抖音检测xposed的思路(https://www.52pojie.cn/thread-684757-1-1.html),贴出了部分伪代码,
  2. 但觉抖音写的蛮有意思的,自己对这方面也不是很清楚,毕竟Android我没怎么学习。借这个机会,了解一下。写的不是很清楚,大家多多抱哈啊!~~
  3. 整理了一下文档,我发现抖音主要使用了以下的手段检测xposed。

  4. 环境:      win10 x64
  5. 使用的工具:apkdb & jeb 2.2.7
  6. 1.尝试加载xposed的类,如果能加载则表示已经安装了。
  7. XposedHelpers类中存在fieldCache methodCache constructorCache 这三个静态成员,都是hashmap类型,凡是需要被hook的且已经被找到的对象都会被缓存到这三个map里面。
  8. 我们通过便利这三个map来找到相关hook信息。
  9. 备注:方法a是检测xposed到底改了什么东西存放到a中。抖音似乎会收集相关信息并上报。
  10.     public void b()
  11.     {
  12.         try
  13.         {
  14.             Object localObject = ClassLoader.getSystemClassLoader()
  15.                     .loadClass("de.robv.android.xposed.XposedHelpers").newInstance();
  16.             // 如果加载类失败 则表示当前环境没有xposed
  17.             if (localObject != null)
  18.             {
  19.                 a(localObject, "fieldCache");
  20.                 a(localObject, "methodCache");
  21.                 a(localObject, "constructorCache");
  22.             }
  23.             return;
  24.         }
  25.         catch (Throwable localThrowable) {}
  26.     }

  27.     private void a(Object arg5, String arg6) {
  28.         try {
  29.             // 从XposedHelpers中读取相关的hook信息
  30.             Field v0_1 = arg5.getClass().getDeclaredField(arg6);
  31.             v0_1.setAccessible(true);
  32.             Set v0_2 = v0_1.get(arg5).keySet();
  33.             if(v0_2 == null) {
  34.                 return;
  35.             }
  36.             if(v0_2.isEmpty()) {
  37.                 return;
  38.             }
  39.             Iterator v1 = v0_2.iterator();
  40.             // 排除无关紧要的类
  41.             while(v1.hasNext()) {
  42.                 Object v0_3 = v1.next();
  43.                 if(v0_3 == null) {
  44.                     continue;
  45.                 }
  46.                 if(((String)v0_3).length() <= 0) {
  47.                     continue;
  48.                 }
  49.                 if(((String)v0_3).toLowerCase().startsWith("android.support")) {
  50.                     continue;
  51.                 }
  52.                 if(((String)v0_3).toLowerCase().startsWith("javax.")) {
  53.                     continue;
  54.                 }
  55.                 if(((String)v0_3).toLowerCase().startsWith("android.webkit")) {
  56.                     continue;
  57.                 }
  58.                 if(((String)v0_3).toLowerCase().startsWith("java.util")) {
  59.                     continue;
  60.                 }
  61.                 if(((String)v0_3).toLowerCase().startsWith("android.widget")) {
  62.                     continue;
  63.                 }
  64.                 if(((String)v0_3).toLowerCase().startsWith("sun.")) {
  65.                     continue;
  66.                 }
  67.                 this.a.add(v0_3);
  68.             }
  69.         }
  70.         catch(Throwable v0) {
  71.             v0.printStackTrace();
  72.         }
  73.     }

  74. 2.检测xposed相关文件
  75. 检测XposedBridge.jar这个文件和de.robv.android.xposed.XposedBridge
  76. XposedBridge.jar存放在framework里面,de.robv.android.xposed.XposedBridge是开发xposed框架使用的主要接口。
  77. 在这个方法里读取了maps这个文件,在linux内核中,这个文件存储了进程映射了的内存区域和访问权限。在这里我们可以看到这个进程加载了那些文件。
  78. 如果进程加载了xposed相关的so库或者jar则表示xposed框架已注入。
  79. public static boolean a(String paramString)
  80.   {
  81.     try
  82.     {
  83.       Object localObject = new HashSet();
  84.       // 读取maps文件信息
  85.       BufferedReader localBufferedReader =
  86.                 new BufferedReader(new FileReader("/proc/" + Process.myPid() + "/maps"));
  87.       for (;;)
  88.       {
  89.         // 遍历查询关键词
  90.         String str = localBufferedReader.readLine();
  91.         if (str == null) {
  92.           break;
  93.         }
  94.         if ((str.endsWith(".so")) || (str.endsWith(".jar"))) {
  95.           ((Set)localObject).add(str.substring(str.lastIndexOf(" ") + 1));
  96.         }
  97.       }
  98.       localBufferedReader.close();
  99.       localObject = ((Set)localObject).iterator();
  100.       while (((Iterator)localObject).hasNext())
  101.       {
  102.         boolean bool = ((String)((Iterator)localObject).next()).contains(paramString);
  103.         if (bool) {
  104.           return true;
  105.         }
  106.       }
  107.     }
  108.     catch (Exception paramString) {}
  109.     return false;
  110.   }
  111. 3.检测堆栈信息
  112. 如果你的手机安装了xposed框架,那么你在查看app崩溃时候的堆栈信息,一定能在顶层找到xposed的类信息,
  113. 既然xposed想改你的信息,那么在调用栈里面肯定是有它的身影的。比如出现de.robv.android.xposed.XposedBridge这个类,方法被hook的时候调用栈里会出现de.robv.android.xposed.XposedBridge.handleHookedMethod 和de.robv.android.xposed.XposedBridge.invokeOriginalMethodNative这些xposed的方法,在dalvik.system.NativeStart.main方法后出现de.robv.android.xposed.XposedBridge.main调用
  114.     try {
  115.             throw new Exception("");
  116.         } catch (Exception localException) {
  117.             StackTraceElement[] arrayOfStackTraceElement = localException.getStackTrace();
  118.             int m = arrayOfStackTraceElement.length;
  119.             int i = 0;
  120.             int j;
  121.             // 遍历整个堆栈查询xposed相关信息 检测 ZygoteInit 是否出现了2次
  122.             for (int k = 0; i < m; k = j) {
  123.                 StackTraceElement localStackTraceElement = arrayOfStackTraceElement[i];
  124.                 j = k;
  125.                 if (localStackTraceElement.getClassName()
  126.                         .equals("com.android.internal.os.ZygoteInit")) {
  127.                     k += 1;
  128.                     j = k;
  129.                     if (k == 2) {
  130.                         return true;
  131.                     }
  132.                 }
  133.                 if (localStackTraceElement.getClassName().equals(e)) {
  134.                     return true;
  135.                 }
  136.                 i += 1;
  137.             }
  138.         }
  139.         return false;
  140.     }

  141.       try

  142.     {
  143.         throw new Exception("");
  144.     }
  145.     catch(
  146.     Exception localException)

  147.     {
  148.         arrayOfStackTraceElement = localException.getStackTrace();
  149.         j = arrayOfStackTraceElement.length;
  150.         i = 0;
  151.     }
  152.     for(;;)

  153.     {
  154.         boolean bool1 = bool2;
  155.         if (i < j) {
  156.             if (arrayOfStackTraceElement[i].getClassName()
  157.                     .equals("de.robv.android.xposed.XposedBridge")) {
  158.                 bool1 = true;
  159.             }
  160.         } else {
  161.             return bool1;
  162.         }
  163.         i += 1;
  164.     }
  165.   
  166. 4.修改hook方法的查询结果
  167. 数组c包含了抖音里比较重要的及各类, this.a指的是检测出的被修改的方法,字段。他执行了两者的匹配,将结果存放到了一个JSONObject里面,里面包含了是否使用模拟器,系统的详细信息,是否是双开xpp,是否适用了xp,hook了那些方法等信息上报到https://xlog.snssdk.com/do/y?ver=0.4&ts=

  168.   
  169.     private String[] c = {"android.os.Build#SERIAL",
  170.             "android.os.Build#PRODUCT",
  171.             "android.os.Build#DEVICE",
  172.             "android.os.Build#FINGERPRINT",
  173.             "android.os.Build#MODEL",
  174.             "android.os.Build#BOARD",
  175.             "android.os.Build#BRAND",
  176.             "android.os.Build.BOOTLOADER",
  177.             "android.os.Build#HARDWARE",
  178.             "android.os.SystemProperties#get(java.lang.String,java.lang.String)",
  179.             "android.os.SystemProperties#get(java.lang.String)",
  180.             "java.lang.System#getProperty(java.lang.String)",
  181.             "android.telephony.TelephonyManager#getDeviceId()",
  182.             "android.telephony.TelephonyManager#getSubscriberId()",
  183.             "android.net.wifi.WifiInfo#getMacAddress()",
  184.             "android.os.Debug#isDebuggerConnected()",
  185.             "android.app.activitymanager#isUserAMonkey()",
  186.             "com.ss."};


  187.     public ArrayList c() {
  188.         ArrayList localArrayList = new ArrayList();
  189.         Iterator localIterator = this.a.iterator();
  190.         while (localIterator.hasNext()) {
  191.             String str1 = (String) localIterator.next();
  192.             String[] arrayOfString = this.c;
  193.             int j = arrayOfString.length;
  194.             int i = 0;
  195.             while (i < j) {
  196.                 if (true == str1.startsWith(arrayOfString[i])) {
  197.                     String str2 = str1.replace(")#exact", ")").replace(")#bestmatch", ")").replace("#", ".");
  198.                     if (str2.length() > 0) {
  199.                         localArrayList.add(str2);
  200.                     }
  201.                 }
  202.                 i += 1;
  203.             }
  204.         }
  205.         return localArrayList;
  206.     }
  207. 5.检测方法是否被篡改
  208. Modifier.isNative方法判断是否是native修饰的方法,xposedhook方法的时候,会把方法转位native方法,我们检测native方法中不该为native的方法来达到检测的目的,比如getDeviceId,getMacAddress这些方法如果是native则表示已经被篡改了。

  209. public static boolean a(String paramString1, String paramString2, Class... paramVarArgs)
  210.   {
  211.     try
  212.     {
  213.       // 判断方法是不是用native修饰的
  214.       boolean bool = Modifier.isNative(Class.forName(paramString1)
  215.                    .getDeclaredMethod(paramString2, paramVarArgs).getModifiers());
  216.       if (bool) {
  217.         return true;
  218.       }
  219.     }
  220.     catch (Exception paramString1)
  221.     {
  222.       paramString1.printStackTrace();
  223.     }
  224.     return false;
  225.   }
  226. 6.检测包名
  227. 这个是最简单也是最不靠谱的方法,因为只要禁止app读取应用列表就没办法了。
  228.   if (((ApplicationInfo)localObject).packageName.equals("de.robv.android.xposed.installer"))
  229.       {
  230.         Log.wtf("HookDetection", "Xposed found on the system.");
  231.       }
  232.   }
  233. 反制xposed
  234. 1.通过反射重写xposed设置,直接禁用
  235. 这个方法是酷安里中抄来的。重写application,在onCreate中写入
  236.   try {
  237.        Field v0_1 = ClassLoader.getSystemClassLoader()
  238.                    .loadClass("de.robv.android.xposed.XposedBridge")
  239.                    .getDeclaredField("disableHooks");
  240.        v0_1.setAccessible(true);
  241.        v0_1.set(null, Boolean.valueOf(true));
  242.    }
  243.      catch(Throwable v0) {
  244.    }
复制代码


 楼主| 发表于 2019-7-14 17:46:01 | 显示全部楼层
本来这个帖子是申请账号写的,申请到之后一段时间后没登给我收回去了,然后一年半过去了,我又看到了这篇帖子,发现前天还有人回复(好感动),于是我抱着死马当活马医的心态又尝试登了一下账号,结果登上了,在这里说明一下,回复我不一定看,评论区留给你们自己讨论吧

以下是原文:
不知道从哪个版本开始抖音加入了 Xposed 检测,但是这个检测并不影响 APP 的使用,只是简单的用 Toast 提示(但是每次进去看着也很不舒服),如果没有安装 Xposed 自然不会有任何的提示,那既然他是检测 Xposed 的,那我就用 Xposed 解决他,下面是分析思路:

用到的工具:
apk:抖音短视频_v1.6.8.apk
IDE:Android Studio 3.0
反编译工具:Android killer 1.3.1.0、MT 管理器 2.4.4(需要 VIP)


1. 首先先看一下 hook 前的原图:



2. 找关键字:

Xposed 为关键字(突破点)在工程(抖音短视频_v1.6.8.apk)中搜索,最后确定最有嫌疑的位置为:classes2/com.ss.android.ugc.aweme.app.i.smali


3. 分析代码:

smali 源码不好分析所以我转换成 Java 代码分析:
转换完可以看到 com.ss.android.ugc.aweme.app.i 这个类中有 4 个 boolean 返回值的方法:
private static boolean a() *
public static boolean a(Context paramContext)
private static boolean b() *
private static boolean b(Context paramContext) *
其中带 * 号的方法里面可以搜到 Xposed 关键字:
private static boolean a():

private static boolean b():

private static boolean b(Context paramContext):


但是并没有像上面图中那样完整的一句话(基本都在 Log 中)。

4. 首次试水:

[Java] [color=rgb(51, 102, 153) !important]纯文本查看 [color=rgb(51, 102, 153) !important]复制代码
[backcolor=rgb(27, 36, 38) !important][color=white !important]
[color=white !important]?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

package me.zpp0196.ss.xpatch;

import android.content.Context;
import android.util.Log;

import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodReplacement;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;

/**
* Created by zpp0196 on 2018/1/8.
*/

public class Hook implements IXposedHookLoadPackage{
        @Override
        public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable{
                if(!lpparam.packageName.equals("com.ss.android.ugc.aweme")){
                        return;
                }
                 
                findAndHookMethod("com.ss.android.ugc.aweme.app.i", lpparam.classLoader, "a", new XC_MethodReplacement(){
                        @Override
                        protected Object replaceHookedMethod(MethodHookParam param) throws Throwable{
                                logI("com.ss.android.ugc.aweme.app.i.a()");
                                return false;
                        }
                });
                 
                findAndHookMethod("com.ss.android.ugc.aweme.app.i", lpparam.classLoader, "a", Context.class, new XC_MethodReplacement(){
                        @Override
                        protected Object replaceHookedMethod(MethodHookParam param) throws Throwable{
                                logI("com.ss.android.ugc.aweme.app.i.a(Context param)");
                                return false;
                        }
                });
                 
                findAndHookMethod("com.ss.android.ugc.aweme.app.i", lpparam.classLoader, "b", new XC_MethodReplacement(){
                        @Override
                        protected Object replaceHookedMethod(MethodHookParam param) throws Throwable{
                                logI("com.ss.android.ugc.aweme.app.i.b()");
                                return false;
                        }
                });
                 
                findAndHookMethod("com.ss.android.ugc.aweme.app.i", lpparam.classLoader, "b", Context.class, new XC_MethodReplacement(){
                        @Override
                        protected Object replaceHookedMethod(MethodHookParam param) throws Throwable{
                                logI("com.ss.android.ugc.aweme.app.i.b(Context param)");
                                return false;
                        }
                });
        }
         
        private void findAndHookMethod(String p1, ClassLoader lpparam, String p2, Object... parameterTypesAndCallback){
                try{
                        XposedHelpers.findAndHookMethod(p1, lpparam, p2, parameterTypesAndCallback);
                }catch(Throwable throwable){
                        logE(throwable.getMessage());
                }
        }
         
        private void logI(String msg){
                if(BuildConfig.DEBUG)
                        Log.i(getTAG(), msg);
        }
         
        private void logE(String msg){
                if(BuildConfig.DEBUG)
                        Log.e(getTAG(), msg);
        }
         
        private String getTAG(){
                return getClass().getSimpleName();
        }
}





编译后重启手机运行抖音 APP,依然会有提示,而且日志里面也没有打印任何有用的信息,证明这些方法都没有被调用过(起码目前是这样)。

5. 入手 strings.xml

既然在 java 代码里面没找到,Xposed 这个关键字也没有在 strings.xml 里面出现,那还有一种可能就是 Toast 中的字符串是拼接得到的,这次换一种搜索方式:
这次只搜前面的几个字「检测到」:

果然搜到了,将 "%s" 换成 "Xposed" 不就是上面 Toast 的内容了么,接着在 public.xml 中搜索他的 id(a_v)

很明显用到的是第二个 type="string" 的,接着将他的 id(0x7f09020d) 转换为 10 进制(2131296781) 再次搜索结果没有搜到任何东西,以前就遇到过这种坑,然后我选择换工具继续找。

6. 换工具继续找:

PS:电脑端有其他工具应该也可以查出来,不过我不常用,用的多的还是手机上的 MT 管理器,照猫画虎,按照个人习好来。
这个 apk 有 3 个 dex 文件,除过 classes2 中的 R$string 以外,能搜到的唯一一个结果在 classes1/com.ss.android.ugc.aweme.app.b.a.i 里面:

反编译(需要会员)直接查看 java 源码得到:
[Java] [color=rgb(51, 102, 153) !important]纯文本查看 [color=rgb(51, 102, 153) !important]复制代码
[backcolor=rgb(27, 36, 38) !important][color=white !important]
[color=white !important]?

01

02

03

04

05

06

07

08

09

10

11

12

public class i implements a {
    public static ChangeQuickRedirect a;
  
    public void a() {
        if (PatchProxy.isSupport(new Object[0], this, a, false, 4264, new Class[0], Void.TYPE)) {
            PatchProxy.accessDispatch(new Object[0], this, a, false, 4264, new Class[0], Void.TYPE);
        } else if (com.ss.android.ugc.aweme.app.i.a(AwemeApplication.t())) {
            n.a(AwemeApplication.t(), String.format(AwemeApplication.t().getString(0x7f09020d), new Object[]{com.ss.android.ugc.aweme.app.i.b}));
            c.a("find_hook", com.ss.android.ugc.aweme.app.i.b, null);
        }
    }
}





看到 format()getString(0x7f09020d) 就可以猜到这一行应该就是显示 Toast 的方法,他没有返回值,应该不是判断的方法而是显示 Toast 的方法。

7. 再次试水:

这个相对于上面的可能性很大,所以直接 hook 打印日志测试:
[Java] [color=rgb(51, 102, 153) !important]纯文本查看 [color=rgb(51, 102, 153) !important]复制代码
[backcolor=rgb(27, 36, 38) !important][color=white !important]
[color=white !important]?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

package me.zpp0196.ss.xpatch;

import android.util.Log;

import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodReplacement;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;

/**
* Created by zpp0196 on 2018/1/8.
*/

public class Hook implements IXposedHookLoadPackage{
        @Override
        public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable{
                if(!lpparam.packageName.equals("com.ss.android.ugc.aweme")){
                        return;
                }
                 
                logI("start hook ShakeSound");
                 
                findAndHookMethod("com.ss.android.ugc.aweme.app.b.a.i", lpparam.classLoader, "a", new XC_MethodReplacement(){
                        @Override
                        protected Object replaceHookedMethod(MethodHookParam param) throws Throwable{
                                logI( "com.ss.android.ugc.aweme.app.b.a.i.a()");
                                return null;
                        }
                });
        }
         
        private void findAndHookMethod(String p1, ClassLoader lpparam, String p2, Object... parameterTypesAndCallback){
                try{
                        XposedHelpers.findAndHookMethod(p1, lpparam, p2, parameterTypesAndCallback);
                }catch(Throwable throwable){
                        logE(throwable.getMessage());
                }
        }
         
        private void logI(String msg){
                if(BuildConfig.DEBUG)
                        Log.i(getTAG(), msg);
        }
         
        private void logE(String msg){
                if(BuildConfig.DEBUG)
                        Log.e(getTAG(), msg);
        }
         
        private String getTAG(){
                return getClass().getSimpleName();
        }
}





编译运行,结果就是那个烦人的 Toast 没有了:


控制台的 log:
01-08 18:13:59.469 7209-7209/? I/Hook: start hook ShakeSound
01-08 18:13:59.607 7209-7209/? I/Hook: com.ss.android.ugc.aweme.app.b.a.i.a()
01-08 18:14:00.047 7242-7242/? I/Hook: start hook ShakeSound
01-08 18:14:00.096 7242-7242/? I/Hook: com.ss.android.ugc.aweme.app.b.a.i.a()
01-08 18:14:00.737 7305-7305/? I/Hook: start hook ShakeSound
01-08 18:14:00.770 7305-7305/? I/Hook: com.ss.android.ugc.aweme.app.b.a.i.a()


一共连续打印了 3 次(至于为什么是三次这个要问抖音开发者),并且已经达到了预期的结果并且也不影响 APP 的使用,大功告成。

解析到此结束,感谢观看。
需要此模块的机友可以下载附件中的 zip 解压得到模块打钩重启手机即可(测试1.6.6、1.6.8、1.6.9、1.7.0、1.7.1、1.7.2 可用)。
抖音反 Xposed 检测.zip (76.37 KB, 下载次数: 813)
第一个版本发布的时候忽略了系统版本,导致 5.0 以下的系统不能用,已经更新了。
如果有什么 Bug 影响到了软件的正常使用,请见谅,我平常只是看看视频,目前还没发现有什么影响。
发表于 2019-8-6 09:19:24 | 显示全部楼层
这个有用,,,
回复

使用道具 举报

发表于 2019-8-7 12:29:50 | 显示全部楼层
嗯,这个岁万想学习的M
发表于 2019-8-7 15:51:37 | 显示全部楼层
学习了~~~~~~~~~~~~~~U
回复

使用道具 举报

发表于 2019-8-7 22:08:36 | 显示全部楼层
大神指导下啊O
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

技术交流售后群

QQ|小黑屋|手机版|站点找错-建议|AI手机网 |Sitemap



GMT+8, 2024-5-20 06:57 , Processed in 0.290468 second(s), 27 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表