xposedhook部分记录

https://blog.coderstory.cn/?s=xposed

之前看到有人发了关于使用xposed屏蔽抖音检测xposed的思路(https://www.52pojie.cn/thread-684757-1-1.html),贴出了部分伪代码,
但觉抖音写的蛮有意思的,自己对这方面也不是很清楚,毕竟Android我没怎么学习。借这个机会,了解一下。写的不是很清楚,大家多多抱哈啊!~~
整理了一下文档,我发现抖音主要使用了以下的手段检测xposed。


环境:      win10 x64
使用的工具:apkdb & jeb 2.2.7

1.尝试加载xposed的类,如果能加载则表示已经安装了。

XposedHelpers类中存在fieldCache methodCache constructorCache 这三个静态成员,都是hashmap类型,凡是需要被hook的且已经被找到的对象都会被缓存到这三个map里面。
我们通过便利这三个map来找到相关hook信息。
备注:方法a是检测xposed到底改了什么东西存放到a中。抖音似乎会收集相关信息并上报。

    public void b()
    {
        try
        {
            Object localObject = ClassLoader.getSystemClassLoader()
                    .loadClass("de.robv.android.xposed.XposedHelpers").newInstance();
            // 如果加载类失败 则表示当前环境没有xposed 
            if (localObject != null)
            {
                a(localObject, "fieldCache");
                a(localObject, "methodCache");
                a(localObject, "constructorCache");
            }
            return;
        }
        catch (Throwable localThrowable) {}
    }

    private void a(Object arg5, String arg6) {
        try {
            // 从XposedHelpers中读取相关的hook信息
            Field v0_1 = arg5.getClass().getDeclaredField(arg6);
            v0_1.setAccessible(true);
            Set v0_2 = v0_1.get(arg5).keySet();
            if(v0_2 == null) {
                return;
            }
            if(v0_2.isEmpty()) {
                return;
            }
            Iterator v1 = v0_2.iterator();
            // 排除无关紧要的类
            while(v1.hasNext()) {
                Object v0_3 = v1.next();
                if(v0_3 == null) {
                    continue;
                }
                if(((String)v0_3).length() <= 0) {
                    continue;
                }
                if(((String)v0_3).toLowerCase().startsWith("android.support")) {
                    continue;
                }
                if(((String)v0_3).toLowerCase().startsWith("javax.")) {
                    continue;
                }
                if(((String)v0_3).toLowerCase().startsWith("android.webkit")) {
                    continue;
                }
                if(((String)v0_3).toLowerCase().startsWith("java.util")) {
                    continue;
                }
                if(((String)v0_3).toLowerCase().startsWith("android.widget")) {
                    continue;
                }
                if(((String)v0_3).toLowerCase().startsWith("sun.")) {
                    continue;
                }
                this.a.add(v0_3);
            }
        }
        catch(Throwable v0) {
            v0.printStackTrace();
        }
    }

2.检测xposed相关文件

检测XposedBridge.jar这个文件和de.robv.android.xposed.XposedBridge
XposedBridge.jar存放在framework里面,de.robv.android.xposed.XposedBridge是开发xposed框架使用的主要接口。
在这个方法里读取了maps这个文件,在linux内核中,这个文件存储了进程映射了的内存区域和访问权限。在这里我们可以看到这个进程加载了那些文件。
如果进程加载了xposed相关的so库或者jar则表示xposed框架已注入。

 public static boolean a(String paramString)
  {
    try
    {
      Object localObject = new HashSet();
      // 读取maps文件信息
      BufferedReader localBufferedReader = 
                new BufferedReader(new FileReader("/proc/" + Process.myPid() + "/maps"));
      for (;;)
      {
        // 遍历查询关键词
        String str = localBufferedReader.readLine();
        if (str == null) {
          break;
        }
        if ((str.endsWith(".so")) || (str.endsWith(".jar"))) {
          ((Set)localObject).add(str.substring(str.lastIndexOf(" ") + 1));
        }
      }
      localBufferedReader.close();
      localObject = ((Set)localObject).iterator();
      while (((Iterator)localObject).hasNext())
      {
        boolean bool = ((String)((Iterator)localObject).next()).contains(paramString);
        if (bool) {
          return true;
        }
      }
    }
    catch (Exception paramString) {}
    return false;
  }

3.检测堆栈信息

如果你的手机安装了xposed框架,那么你在查看app崩溃时候的堆栈信息,一定能在顶层找到xposed的类信息,
既然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调用

    try {
            throw new Exception("");
        } catch (Exception localException) {
            StackTraceElement[] arrayOfStackTraceElement = localException.getStackTrace();
            int m = arrayOfStackTraceElement.length;
            int i = 0;
            int j;
            // 遍历整个堆栈查询xposed相关信息 检测 ZygoteInit 是否出现了2次
            for (int k = 0; i < m; k = j) {
                StackTraceElement localStackTraceElement = arrayOfStackTraceElement[i];
                j = k;
                if (localStackTraceElement.getClassName()
                        .equals("com.android.internal.os.ZygoteInit")) {
                    k += 1;
                    j = k;
                    if (k == 2) {
                        return true;
                    }
                }
                if (localStackTraceElement.getClassName().equals(e)) {
                    return true;
                }
                i += 1;
            }
        }
        return false;
    }

      try

    {
        throw new Exception("");
    }
    catch(
    Exception localException)

    {
        arrayOfStackTraceElement = localException.getStackTrace();
        j = arrayOfStackTraceElement.length;
        i = 0;
    }
    for(;;)

    {
        boolean bool1 = bool2;
        if (i < j) {
            if (arrayOfStackTraceElement[i].getClassName()
                    .equals("de.robv.android.xposed.XposedBridge")) {
                bool1 = true;
            }
        } else {
            return bool1;
        }
        i += 1;
    }
  

4.修改hook方法的查询结果

数组c包含了抖音里比较重要的及各类, this.a指的是检测出的被修改的方法,字段。他执行了两者的匹配,将结果存放到了一个JSONObject里面,里面包含了是否使用模拟器,系统的详细信息,是否是双开xpp,是否适用了xp,hook了那些方法等信息上报到https://xlog.snssdk.com/do/y?ver=0.4&ts=

  
    private String[] c = {"android.os.Build#SERIAL", 
            "android.os.Build#PRODUCT",
            "android.os.Build#DEVICE", 
            "android.os.Build#FINGERPRINT", 
            "android.os.Build#MODEL", 
            "android.os.Build#BOARD", 
            "android.os.Build#BRAND", 
            "android.os.Build.BOOTLOADER", 
            "android.os.Build#HARDWARE", 
            "android.os.SystemProperties#get(java.lang.String,java.lang.String)", 
            "android.os.SystemProperties#get(java.lang.String)", 
            "java.lang.System#getProperty(java.lang.String)", 
            "android.telephony.TelephonyManager#getDeviceId()", 
            "android.telephony.TelephonyManager#getSubscriberId()", 
            "android.net.wifi.WifiInfo#getMacAddress()", 
            "android.os.Debug#isDebuggerConnected()", 
            "android.app.activitymanager#isUserAMonkey()", 
            "com.ss."};


    public ArrayList c() {
        ArrayList localArrayList = new ArrayList();
        Iterator localIterator = this.a.iterator();
        while (localIterator.hasNext()) {
            String str1 = (String) localIterator.next();
            String[] arrayOfString = this.c;
            int j = arrayOfString.length;
            int i = 0;
            while (i < j) {
                if (true == str1.startsWith(arrayOfString[i])) {
                    String str2 = str1.replace(")#exact", ")").replace(")#bestmatch", ")").replace("#", ".");
                    if (str2.length() > 0) {
                        localArrayList.add(str2);
                    }
                }
                i += 1;
            }
        }
        return localArrayList;
    }

5.检测方法是否被篡改

Modifier.isNative方法判断是否是native修饰的方法,xposedhook方法的时候,会把方法转位native方法,我们检测native方法中不该为native的方法来达到检测的目的,比如getDeviceId,getMacAddress这些方法如果是native则表示已经被篡改了。

 public static boolean a(String paramString1, String paramString2, Class... paramVarArgs)
  {
    try
    {
      // 判断方法是不是用native修饰的
      boolean bool = Modifier.isNative(Class.forName(paramString1)
                   .getDeclaredMethod(paramString2, paramVarArgs).getModifiers());
      if (bool) {
        return true;
      }
    }
    catch (Exception paramString1)
    {
      paramString1.printStackTrace();
    }
    return false;
  }

6.检测包名

这个是最简单也是最不靠谱的方法,因为只要禁止app读取应用列表就没办法了。

  if (((ApplicationInfo)localObject).packageName.equals("de.robv.android.xposed.installer"))
      {
        Log.wtf("HookDetection", "Xposed found on the system.");
      }
  }

反制xposed

1.通过反射重写xposed设置,直接禁用

这个方法是酷安里中抄来的。重写application,在onCreate中写入

  try {
       Field v0_1 = ClassLoader.getSystemClassLoader()
                   .loadClass("de.robv.android.xposed.XposedBridge")
                   .getDeclaredField("disableHooks");
       v0_1.setAccessible(true);
       v0_1.set(null, Boolean.valueOf(true));
   }
     catch(Throwable v0) {
   }
未完待续。。。

文章分页

4 条评论

  • ootom    2018年5月3日    回复

    很有参考意义
    有些检测方式,有应对办法吗?
    例如“检测堆栈信息”等几种不好处理的方式

    • coderstory    2018年5月4日    回复    作者

      getStackTrace方法直接hook 过滤掉xposed信息不就好了么

  • didi    2018年4月2日    回复

    测试了反制xposed,没有作用呢

  • bcagaybc    2018年3月3日    回复

    还有方法 不过暂时不会检测自身是否被hook 可以检测是否存在xposed

    /proc/pid/maps 找xposed关键词


HOOK 多dex

Xposed-10-Hook动态加载的dex中方法

原创  安卓Xposed框架交流  2018-03-05

作者 fooree

前面Xposed-08-Hook dex加载小节演示了获取动态加载的dex文件路径的方法。

本小节演示如何Hook动态加载的dex中的方法。

温馨提示:文中包含代码,建议横屏阅读,体验更佳

没有实际操作过的话,肯定会以为这个与之前演示的方法的Hook应该没有区别。如果真是这样的话,看到这里,可以先动手去操作一下。

本小节演示还以Xposed-08-Hook dex加载小节的样本APK为例,具体地址:https://github.com/fooree/fooXposed/blob/master/files/Foox_4th_02.apk。Hook其中的解密方法,并打印参数和返回值。

0x01

我们通过查看代码,知道样本应用中解密方法是foo.ree.demos.x4th01.Base64Util#decrypt(java.lang.String)( 解密方法源码地址:https://github.com/fooree/fooXposed/blob/master/Foox_4th_01/src/main/java/foo/ree/demos/x4th01/Base64Util.java)。

按照之前演示,对该方法进行Hook的代码应该如下:

public class FooxMain implements IXposedHookLoadPackage {
@Override
   public void handleLoadPackage(LoadPackageParam lpp) throws Throwable {
if (!"foo.ree.demos.x4th02".equals(lpp.packageName)) return;
       findAndHookMethod("foo.ree.demos.x4th01.Base64Util", lpp.classLoader, "decrypt", String.class, new XC_MethodHook() {
@Override
           protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
XposedBridge.log(param.method + " params: " + Arrays.toString(param.args));
           }
@Override
           protected void afterHookedMethod(MethodHookParam param) throws Throwable {
XposedBridge.log(param.method + " return: " + param.getResult());
           }
});
   }
}


实际上,以上代码什么都Hook不到,而且会在findAndHookMethod的时候,抛出异常de.robv.android.xposed.XposedHelpers.ClassNotFoundError

0x02

为什么以上代码会抛出异常?

因为handleLoadPackage(LoadPackageParam lpp)方法是在应用程序进程启动时,被Xposed框架调用执行的;而此时,还没有动态加载dex,要Hook的目标方法foo.ree.demos.x4th01.Base64Util#decrypt(java.lang.String)在当前进程中,还不存在。只有主页面上的按钮被点击时,才会加载dex,加载目标方法。所以出现ClassNotFoundError是正常的。

那我们如何才能Hook目标方法foo.ree.demos.x4th01.Base64Util#decrypt(java.lang.String)呢?

0x03

我们知道,在Android中,所有类的加载,都是通过java.lang.ClassLoader#loadClass(java.lang.String)方法进行的(除非该方法被重载自定义),foo.ree.demos.x4th01.Base64Util也不例外。

因此我们可以在DexClassLoader调用loadClass(java.lang.String)方法加载类foo.ree.demos.x4th01.Base64Util时,得到该类,从而可以实现对其进行Hook。

通过分析,Hook目标方法foo.ree.demos.x4th01.Base64Util#decrypt(java.lang.String),需要两步:

  1. 首先Hookjava.lang.ClassLoader#loadClass(java.lang.String)方法。

  2. 然后Hookfoo.ree.demos.x4th01.Base64Util#decrypt(java.lang.String)方法。

具体代码如下:

public class FooxMain implements IXposedHookLoadPackage {
@Override
   public void handleLoadPackage(LoadPackageParam lpp) throws Throwable {
if (!"foo.ree.demos.x4th02".equals(lpp.packageName)) return;

       // 第一步:Hook方法ClassLoader#loadClass(String)
       findAndHookMethod(ClassLoader.class, "loadClass", String.class, new XC_MethodHook() {
@Override
           protected void afterHookedMethod(MethodHookParam param) throws Throwable {
if (param.hasThrowable()) return;
               Class cls = (Class) param.getResult();
               String name = cls.getName();
               if ("foo.ree.demos.x4th01.Base64Util".equals(name)) {
// 所有的类都是通过loadClass方法加载的
                   // 所以这里通过判断全限定类名,查找到目标类
                   // 第二步:Hook目标方法
                   findAndHookMethod(cls, "decrypt", String.class, new XC_MethodHook() {
@Override
                       protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
XposedBridge.log(param.method + " params: " + Arrays.toString(param.args));
                       }

@Override
                       protected void afterHookedMethod(MethodHookParam param) throws Throwable {
XposedBridge.log(param.method + " return: " + param.getResult());
                       }
});
               }
}
});
   }
}


若是还没有实践过,建议动动手,检验以上两种Hook方法,哪一种可行。

目前,安卓应用程序越来越大,包含的dex越来越多。只要不是主dex(classes.dex)中定义的类和方法,都可以通过以上代码进行Hook。

找个多dex的安卓应用程序,动手试一下吧。

完整代码地址:https://github.com/fooree/fooXposed/tree/master/Foox_05th




现在很多的app都有多个dex文件,因为单个dex文件顶多存放60000多个方法,如果代码太多的话必须拆分dex。如果用xposed去hook非默认dex文件的类就会发生ClassNotFoundError,要解决这个问题,我们需要拿到对应dex文件的上下文环境。
android在加载dex文件后会创建一个application类,然后会调用attach方法,attach方法的参数就是上下文context,而且attach方法是final方法,不会因为被覆盖而hook不到,拿到这个context就可以获取对应的classloader,然后可以顺利hook到你需要的类

 XposedHelpers.findAndHookMethod(Application.class, "attach", 
                         Context.class, new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    ClassLoader cl = ((Context)param.args[0]).getClassLoader();
                    Class hookclass = null;
                    try {
                        hookclass = cl.loadClass("xxx.xxx.xxx");
                    } catch (Exception e) {
                        Log.e("DEBUG", "load class error", e);
                        return;
                    }
                    Log.i("DEBUG", "load success");
                    XposedHelpers.findAndHookMethod(hookclass, "some_method",
                                  new XC_MethodHook(){
                        //TODO: 相关hook操作
                    });
                }
            });



文章分页







你可能感兴趣的:(android)