Android Java层的anti-hooking技巧

标签: 技术分享 | 发表时间:2016-06-07 08:00 | 作者:路人甲
出处:http://drops.wooyun.org

原文: http://d3adend.org/blog/?p=589

0x00 前言


一个最近关于检测native hook框架的方法让我开始思考一个Android应用如何在Java层检测Cydia Substrate或者Xposed框架。

声明:

下文所有的anti-hooking技巧很容易就可以被有经验的逆向人员绕过,这里只是展示几个检测的方法。在最近DexGuard和GuardIT等工具中还没有这类anti-hooking检测功能,不过我相信不久就会增加这个功能。

0x01 检测安装的应用


一个最直接的想法就是检测设备上有没有安装Substrate或者Xposed框架,可以直接调用PackageManager显示所有安装的应用,然后看是否安装了Substrate或者Xposed。

PackageManager packageManager = context.getPackageManager();
List applicationInfoList  = packageManager.getInstalledApplications(PackageManager.GET_META_DATA);

for(ApplicationInfo applicationInfo : applicationInfoList) {
    if(applicationInfo.packageName.equals("de.robv.android.xposed.installer")) {
        Log.wtf("HookDetection", "Xposed found on the system.");
    }
    if(applicationInfo.packageName.equals("com.saurik.substrate")) {
        Log.wtf("HookDetection", "Substrate found on the system.");
    }
}       

0x02 检查调用栈里的可疑方法


另一个想到的方法是检查Java调用栈里的可疑方法,主动抛出一个异常,然后打印方法的调用栈。代码如下:

public class DoStuff {
    public static String getSecret() {
        try {
            throw new Exception("blah");
        }
        catch(Exception e) {
            for(StackTraceElement stackTraceElement : e.getStackTrace()) {
                Log.wtf("HookDetection", stackTraceElement.getClassName() + "->" + stackTraceElement.getMethodName());
            }
        }
        return "ChangeMePls!!!";
    }
}

当应用没有被hook的时候,正常的调用栈是这样的:

com.example.hookdetection.DoStuff->getSecret
com.example.hookdetection.MainActivity->onCreate
android.app.Activity->performCreate
android.app.Instrumentation->callActivityOnCreate
android.app.ActivityThread->performLaunchActivity
android.app.ActivityThread->handleLaunchActivity
android.app.ActivityThread->access$800
android.app.ActivityThread$H->handleMessage
android.os.Handler->dispatchMessage
android.os.Looper->loop
android.app.ActivityThread->main
java.lang.reflect.Method->invokeNative
java.lang.reflect.Method->invoke
com.android.internal.os.ZygoteInit$MethodAndArgsCaller->run
com.android.internal.os.ZygoteInit->main
dalvik.system.NativeStart->main

但是假如有Xposed框架hook了com.example.hookdetection.DoStuff.getSecret方法,那么调用栈会有2个变化:

  • 在dalvik.system.NativeStart.main方法后出现de.robv.android.xposed.XposedBridge.main调用
  • 如果Xposed hook了调用栈里的一个方法,还会有de.robv.android.xposed.XposedBridge.handleHookedMethod 和de.robv.android.xposed.XposedBridge.invokeOriginalMethodNative调用

所以如果hook了getSecret方法,调用栈就会如下:

com.example.hookdetection.DoStuff->getSecret

de.robv.android.xposed.XposedBridge->invokeOriginalMethodNative
de.robv.android.xposed.XposedBridge->handleHookedMethod

com.example.hookdetection.DoStuff->getSecret
com.example.hookdetection.MainActivity->onCreate
android.app.Activity->performCreate
android.app.Instrumentation->callActivityOnCreate
android.app.ActivityThread->performLaunchActivity
android.app.ActivityThread->handleLaunchActivity
android.app.ActivityThread->access$800
android.app.ActivityThread$H->handleMessage
android.os.Handler->dispatchMessage
android.os.Looper->loop
android.app.ActivityThread->main
java.lang.reflect.Method->invokeNative
java.lang.reflect.Method->invoke
com.android.internal.os.ZygoteInit$MethodAndArgsCaller->run
com.android.internal.os.ZygoteInit->main

de.robv.android.xposed.XposedBridge->main

dalvik.system.NativeStart->main

下面看下Substrate hook com.example.hookdetection.DoStuff.getSecret方法后,调用栈会有什么变化:

  • dalvik.system.NativeStart.main调用后会出现2次com.android.internal.os.ZygoteInit.main,而不是一次。
  • 如果Substrate hook了调用栈里的一个方法,还会出现com.saurik.substrate.MS$2.invoked,com.saurik.substrate.MS$MethodPointer.invoke还有跟Substrate扩展相关的方法(这里是com.cigital.freak.Freak$1$1.invoked)。

所以如果hook了getSecret方法,调用栈就会如下:

com.example.hookdetection.DoStuff->getSecret

com.saurik.substrate._MS$MethodPointer->invoke
com.saurik.substrate.MS$MethodPointer->invoke
com.cigital.freak.Freak$1$1->invoked
com.saurik.substrate.MS$2->invoked

com.example.hookdetection.DoStuff->getSecret
com.example.hookdetection.MainActivity->onCreate
android.app.Activity->performCreate
android.app.Instrumentation->callActivityOnCreate
android.app.ActivityThread->performLaunchActivity
android.app.ActivityThread->handleLaunchActivity
android.app.ActivityThread->access$800
android.app.ActivityThread$H->handleMessage
android.os.Handler->dispatchMessage
android.os.Looper->loop
android.app.ActivityThread->main
java.lang.reflect.Method->invokeNative
java.lang.reflect.Method->invoke
com.android.internal.os.ZygoteInit$MethodAndArgsCaller->run
com.android.internal.os.ZygoteInit->main

com.android.internal.os.ZygoteInit->main

dalvik.system.NativeStart->main

在知道了调用栈的变化之后,就可以在Java层写代码进行检测:

try {
    throw new Exception("blah");
}
catch(Exception e) {
    int zygoteInitCallCount = 0;
    for(StackTraceElement stackTraceElement : e.getStackTrace()) {
        if(stackTraceElement.getClassName().equals("com.android.internal.os.ZygoteInit")) {
            zygoteInitCallCount++;
            if(zygoteInitCallCount == 2) {
                Log.wtf("HookDetection", "Substrate is active on the device.");
            }
        }
        if(stackTraceElement.getClassName().equals("com.saurik.substrate.MS$2") && 
                stackTraceElement.getMethodName().equals("invoked")) {
            Log.wtf("HookDetection", "A method on the stack trace has been hooked using Substrate.");
        }
        if(stackTraceElement.getClassName().equals("de.robv.android.xposed.XposedBridge") && 
                stackTraceElement.getMethodName().equals("main")) {
            Log.wtf("HookDetection", "Xposed is active on the device.");
        }
        if(stackTraceElement.getClassName().equals("de.robv.android.xposed.XposedBridge") && 
                stackTraceElement.getMethodName().equals("handleHookedMethod")) {
            Log.wtf("HookDetection", "A method on the stack trace has been hooked using Xposed.");
        }

    }
}

0x03 检测并不应该native的native方法


Xposed框架会把hook的Java方法类型改为"native",然后把原来的方法替换成自己的代码(调用hookedMethodCallback)。可以查看 XposedBridge_hookMethodNative的实现,是修改后app_process里的方法。

利用Xposed改变hook方法的这个特性(Substrate也使用类似的原理),就可以用来检测是否被hook了。注意这不能用来检测ART运行时的Xposed,因为没必要把方法的类型改为native。

假设有下面这个方法:

public class DoStuff {
    public static String getSecret() {
        return "ChangeMePls!!!";
    }
}

如果getSecret方法被hook了,在运行的时候就会像下面的定义:

public class DoStuff {
        // calls hookedMethodCallback if hooked using Xposed
    public native static String getSecret(); 
}

基于上面的原理,检测的步骤如下:

  • 定位到应用的DEX文件
  • 枚举所有的class
  • 通过反射机制判断运行时不应该是native的方法

下面的Java展示了这个技巧。这里假设了应用本身没有通过JNI调用本地代码,大多数应用都不需要调用本地方法。不过如果有JNI调用的话,只需要把这些native方法添加到一个白名单中即可。理论上这个方法也可以用于检测Java库或者第三方库,不过需要把第三方库的native方法添加到一个白名单。检测代码如下:

for (ApplicationInfo applicationInfo : applicationInfoList) {
    if (applicationInfo.processName.equals("com.example.hookdetection")) {      
        Set classes = new HashSet();
        DexFile dex;
        try {
            dex = new DexFile(applicationInfo.sourceDir);
            Enumeration entries = dex.entries();
            while(entries.hasMoreElements()) {
                String entry = entries.nextElement();
                classes.add(entry);
            }
            dex.close();
        } 
        catch (IOException e) {
            Log.e("HookDetection", e.toString());
        }
        for(String className : classes) {
            if(className.startsWith("com.example.hookdetection")) {
                try {
                    Class clazz = HookDetection.class.forName(className);
                    for(Method method : clazz.getDeclaredMethods()) {
                        if(Modifier.isNative(method.getModifiers())){
                            Log.wtf("HookDetection", "Native function found (could be hooked by Substrate or Xposed): " + clazz.getCanonicalName() + "->" + method.getName());
                        }
                    }
                }
                catch(ClassNotFoundException e) {
                    Log.wtf("HookDetection", e.toString());
                }
            }
        }
    }
}

0x04 通过/proc/[pid]/maps检测可疑的共享对象或者JAR


/proc/[pid]/maps记录了内存映射的区域和访问权限,首先查看Android应用的映像,第一列是起始地址和结束地址,第六列是映射文件的路径。

#cat /proc/5584/maps

40027000-4002c000 r-xp 00000000 103:06 2114      /system/bin/app_process
4002c000-4002d000 r--p 00004000 103:06 2114      /system/bin/app_process
4002d000-4002e000 rw-p 00005000 103:06 2114      /system/bin/app_process
4002e000-4003d000 r-xp 00000000 103:06 246       /system/bin/linker
4003d000-4003e000 r--p 0000e000 103:06 246       /system/bin/linker
4003e000-4003f000 rw-p 0000f000 103:06 246       /system/bin/linker
4003f000-40042000 rw-p 00000000 00:00 0 
40042000-40043000 r--p 00000000 00:00 0 
40043000-40044000 rw-p 00000000 00:00 0 
40044000-40047000 r-xp 00000000 103:06 1176      /system/lib/libNimsWrap.so
40047000-40048000 r--p 00002000 103:06 1176      /system/lib/libNimsWrap.so
40048000-40049000 rw-p 00003000 103:06 1176      /system/lib/libNimsWrap.so
40049000-40091000 r-xp 00000000 103:06 1237      /system/lib/libc.so
... Lots of other memory regions here ...

因此可以写代码检测加载到当前内存区域中的可疑文件:

try {
    Set libraries = new HashSet();
    String mapsFilename = "/proc/" + android.os.Process.myPid() + "/maps";
    BufferedReader reader = new BufferedReader(new FileReader(mapsFilename));
    String line;
    while((line = reader.readLine()) != null) {
        if (line.endsWith(".so") || line.endsWith(".jar")) {
            int n = line.lastIndexOf(" ");
            libraries.add(line.substring(n + 1));
        }
    }
    for (String library : libraries) {
        if(library.contains("com.saurik.substrate")) {
            Log.wtf("HookDetection", "Substrate shared object found: " + library);
        }
        if(library.contains("XposedBridge.jar")) {
            Log.wtf("HookDetection", "Xposed JAR found: " + library);
        }
    }
    reader.close();
}
catch (Exception e) {
    Log.wtf("HookDetection", e.toString());
}

Substrate会用到几个so:

Substrate shared object found: /data/app-lib/com.saurik.substrate-1/libAndroidBootstrap0.so
Substrate shared object found: /data/app-lib/com.saurik.substrate-1/libAndroidCydia.cy.so
Substrate shared object found: /data/app-lib/com.saurik.substrate-1/libDalvikLoader.cy.so
Substrate shared object found: /data/app-lib/com.saurik.substrate-1/libsubstrate.so
Substrate shared object found: /data/app-lib/com.saurik.substrate-1/libsubstrate-dvm.so
Substrate shared object found: /data/app-lib/com.saurik.substrate-1/libAndroidLoader.so

Xposed会用到一个Jar:

Xposed JAR found: /data/data/de.robv.android.xposed.installer/bin/XposedBridge.jar

0x05 绕过检测的方法


上面讨论了几个anti-hooking的方法,不过相信也会有人提出绕过的方法,这里对应每个检测方法如下:

  • hook PackageManager的getInstalledApplications,把Xposed或者Substrate的包名去掉
  • hook Exception的getStackTrace,把自己的方法去掉
  • hook getModifiers,把flag改成看起来不是native
  • hook 打开的文件的操作,返回/dev/null或者修改的map文件

相关 [android java anti] 推荐:

Android Java层的anti-hooking技巧

- - WooYun知识库
原文: http://d3adend.org/blog/?p=589. 一个最近关于检测native hook框架的方法让我开始思考一个Android应用如何在Java层检测Cydia Substrate或者Xposed框架. 下文所有的anti-hooking技巧很容易就可以被有经验的逆向人员绕过,这里只是展示几个检测的方法.

The Open Session In View Anti-Pattern - Vlad Mihalcea

- -
An anti-pattern (or antipattern) is a common response to a recurring problem that is usually ineffective and risks being highly counterproductive.. When using JPA and Hibernate, the Fetching policy can have one of the biggest impacts on application performance, and, as explained in my.

Java/Android编码规范

- - CSDN博客推荐文章
1.        为什么需要编码规范?. 编码规范对于程序员而言尤为重要,有以下几个原因:. l        一个软件的生命周期中,80%的花费在于维护. l        几乎没有任何一个软件,在其整个生命周期中,均由最初的开发人员来维护. l        编码规范可以改善软件的可读性,可以让程序员尽快而彻底地理解新的代码.

Android中Java和JavaScript交互

- - 极客521 | 极客521
Android提供了一个很强大的WebView控件用来处理Web网页,而在网页中,JavaScript又是一个很举足轻重的脚本. 本文将介绍如何实现Java代码和Javascript代码的相互调用. 实现Java和js交互十分便捷. WebView开启JavaScript脚本执行. WebView设置供JavaScript调用的交互接口.

android 或者java SAX解析XML

- - ITeye博客
本实例解析xml使用的是继承DefaultHandler类,此类就是java中封装好的xml解析器类,下面看代码详细说明:. 第一步:首先说明下xml文件,Java Sax解析是按照xml文件的顺序一步一步的来解析,在解析xml文件之前,我们要先了解xml文件的节点的种类,一种是ElementNode,一种是TextNode.

在Android上实现Java和Js交互

- - ITeye博客
  其实webview加载资源的速度并不慢,但是如果资源多了,当然就很慢. 图片、 css 、 js 、 html 这些资源每个大概需要 10-200ms ,一般都是 30ms 就 ok 了. 不过webview是必须等到全部资源都完成加载,才会进行渲染的,所以加载的速度很重要. 从Google上我们了解到,webview 加载页面的顺序是:先加载 html ,然后从里面解析出 css 、 js 文件和页面上的图片资源进行加载.

产品经理,为什么应该尽早考虑Anti策略?

- - 雷锋网
Anti策略是针对互联网产品内注册用户可能产生的一切不符合网站定位和规则的行为的审核规则,旨在更高效地通过非人工的方式干掉非法的内容、用户以在某种程度上保证产品的“调性”和用户使用体验. 比如一个刚上线的社区产品,当刚有一点用户的时候就开始有大量的水军来灌水发招手打字员或网络兼职的信息. 这很有可能就让这个产品一时间没办法用而运营手工又人工忙不过来导致这个产品“元气大伤”.

Oracle和谷歌Java Android官司将推迟

- Dennis Lee - cnBeta.COM
Oracle和谷歌之间一场等待已久的官司可能会推迟至10月17日以后进行,这场官司的内容是Android操作系统所谓的Java专利权之争. 本案法官William Alsup称根据专利权专家Florian Mueller的预测,谷歌Oracle案很可能会被推迟.

欧朋浏览器6.2 For Android/Symbian/Java发布更新

- 洞箫 - cnBeta.COM
新一代浏览器界面,简约、精致、时尚,摆脱拥挤,复杂的手机浏览器界面,打造个性化的浏览器. 快速拨号“快速拨号”将喜欢的网站添加到首页,实现一键点击访问. 通过定制“快速拨号”,你可以打造一个个性化的浏览器首页. 体积小巧、功能强大,欧朋浏览器结合了强大的内核和领先的云端转码技术,可使手机访问网站速度提升5到10倍,使上网流量节省最高达90%.

基于 PhoneGap 与 Java 开发的 Android 应用的性能对比

- jinn - 搜索研发部官方博客
        此次的调研的重点是针对一个Android应用的基础需求,用phonegap与Java实现的应用在性能及开发成本等方面的对比.        开发一个应用的最基本需求应该是浏览性需求,而在Android开发中ListView比较常用的控件,广泛被用于数据列表的展现上,而且也比较灵活. 所以本次选择用phonegap和Java各自实现一个ListView的内容展现功能的应用;同时引入另外一个常用组件GridView来实现图片浏览的功能应用.