使用Xposed框架进行hook

xposed创建教程参考 Android Studio Xposed模块编写
没有Android基础都能学会的Xposed基础教程
推荐一个xposed入门的很好的公众号:安卓Xposed框架交流
最后创建完毕就是这个样子 Desktop Preview
这里我已经编译好了,可以直接用我的模块修改就行 https://github.com/la0s/XposedHookDemo
这里针对一个APP进行逆向,打印一下它的某些关键参数和签名
使用JEB进行反编译后

我们使用xposed来hook一下getSign方法 它的参数和返回值
代码解释

public class XposedHook implements IXposedHookLoadPackage {
    @Override
    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {

        if (loadPackageParam.packageName.equals("cn.thecover.www.covermedia")) {    //过滤包名
            Class clasz = loadPackageParam.classLoader.loadClass("cn.thecover.www.covermedia.data.entity.HttpRequestEntity"); //要hook的方法所在的类名

            XposedHelpers.findAndHookMethod(clasz, "getSign",String.class,String.class,String.class, new XC_MethodHook() { //要hook的方法名和参数类型,此处为三个String类型

                @Override //重写XC_MethodHook()的回调方法
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {

                    Log.i("hook before param1:", (String) param.args[0]); //打印第一个参数
                    Log.i("hook before param2:", (String) param.args[1]); 
                    Log.i("hook before param3:", (String) param.args[2]); 

                }

                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {

                    Log.i("hook after result:",param.getResult().toString()); //打印返回值(String类型)
                }
            });
        }
    }
}

上面findAndHookMethod有两种写法

findAndHookMethod(String className,ClassLoader classLoader,String methodName, Object... parameter TypesAndCallback)  
findAndHookMethod(Class<?> clazz, String methodName,Object... parameter TypesAndCallback) //其中clazz使用classLoader.loadClass和 XposedHelpers.findClass两种方法都可以

所以看到后面的代码不要奇怪,两种方法都是可以的。另外也可以先设置为new XC_MethodHook/XC_MethodReplacement的回调参数,然后再调用此参数,这种写法在循环hook中很常见(组合不定的参数列表和回调类参数,前者使用一个list或者array来存储),见TrustMeAlreadyxposed新思路
装上模块后重启,点击应用,打开DDMS过滤

是不是觉得xposed很简单,然而事情并不是想象中这样,这个应用有两个dex,这里的方法所在的类恰好是第一个class.dex(主dex),所以我们能够hook成功,但是如果像下图这个方法是在另一个dex文件里就不这么顺利了(hook会报错经典的ClassNotFound)

原因是Android5.0以下hook一个某个方法,而这个方法不在主dex,而存在分包dex。此时xposed会在没有加载分包dex的时候进行回调handleLoadPackage,类加载器(ClassLoader)并没有加载分包里面的类 会导致XposedHelpers.findAndHookMethod抛出异常。对于multidex的问题,xposed作者给出的建议是hook Appliation的attach方法来解决多dex文件,详情见Multidex support,如下图(其实看到后面会发现有一个更巧妙更简单的办法)

上面这个新闻APP到了最新版4.2.0之后变成了梆梆加壳,那么我们再想使用xposed来hook的话就不能采用上面这种普通的方法了,会报错class not found。其实Hook加固应用Hook不到的原因是,Xposed模块中handleLoadPackage被调用时壳没有启动,所以并没有将应用的类装载进VM(Xposed的实现原理,其只能Hook已经真实存在的方法,如果某个方法在内存中并不存在或者暂时还不存在,Xposed是无法进行预先Hook的),这里介绍三种方法
1.第一种姿势:先以360为例,从manifest.xml中android:name=”com.stub.StubApp”可知壳入口,先从壳里获取到context参数,然后就可以通过context获得到360的classloader,之后只需要用这个classloader就可以hook了,见使用xposed来hook使用360加固的应用

public class XposedHook implements IXposedHookLoadPackage{
    private String TAG = "Hook";

    @Override
    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {

        if (loadPackageParam.packageName.equals("com.peopledailychina.activity")) {

            XposedBridge.log("开始hook....");

            //hook加固后的包,首先hook attachBaseContext这个方法来获取context对象
            XposedHelpers.findAndHookMethod("com.stub.StubApp", loadPackageParam.classLoader, "attachBaseContext", Context.class, new XC_MethodHook() {
                        @Override
                        protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                            super.afterHookedMethod(param);
                            //获取到的参数args[0]就是360的Context对象,通过这个对象来获取classloader
                            Context context = (Context) param.args[0];
                            //获取360的classloader,之后hook加固后的就使用这个classloader
                            ClassLoader classLoader =context.getClassLoader();
                            //下面就是强classloader修改成360的classloader就可以成功的hook了
                            XposedHelpers.findAndHookMethod("com.peopledaily.common.encrtption.MD5Helper", classLoader, "getMD5Str", String.class, new XC_MethodHook() {
                                @Override
                                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                                    XposedBridge.log(param.method + " params: " + Arrays.toString(param.args));
                                    //Log.i("params: " , Arrays.toString(param.args));
                                }
                                @Override
                                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                                    XposedBridge.log(param.method + " return: " + param.getResult());
                                    //Log.i( " return: " , param.method,param.getResult());
                                }
                            });
                        }
                    });
        }}}

腾讯的壳(其他的梆梆和爱加密壳同理,第一步都是先找到壳的attachBaseContext然后hook就好说了。另外不得不提就是阿里的壳,因为类和方法抽取到native层了,这个用xposed是获取不到的,除了frida目前没有别的好办法)

2.第二种姿势:使用ClassLoader的loadClass方法进行类名匹配,Hook loadClass方法,就能得到所有的类,见Xposed Hook Apk不在classes.dex中定义的类,这也是那个xposed公众号里的文章。这是一种通用的方法,multidex,动态加载dex等问题都可以用这种姿势解决!

public class XposedHook implements IXposedHookLoadPackage {
    @Override
    public void handleLoadPackage(LoadPackageParam lpp) throws Throwable {
        if (!"cn.thecover.www.covermedia".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 ("cn.thecover.www.covermedia.data.entity.HttpRequestEntity".equals(name)) {
                    // 所有的类都是通过loadClass方法加载的
                    // 所以这里通过判断全限定类名,查找到目标类
                    // 第二步:Hook目标方法
                    findAndHookMethod(cls, "getSign", String.class, String.class,String.class, new XC_MethodHook() {
                        @Override
                        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                            XposedBridge.log(param.method + " params: " + Arrays.toString(param.args));
                            //Log.i("params: " , Arrays.toString(param.args));
                        }
                        @Override
                        protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                            XposedBridge.log(param.method + " return: " + param.getResult());
                            //Log.i( " return: " , param.method,param.getResult());
                        }
                    });
                }
            }
        });
    } }

装上模块并重启

接着在此基础上再来看结合复杂参数的hook方式

我们要hook上面的方法必须要考虑方法参数的正确写法,否则会报NoSuchMethod错误,感谢看雪前辈提供的思路[原创]安卓Hook函数的复杂参数如何给定?,因为应用是经过加固的,使用XposedHelpers的findClass方法获取类会提示找不到类,这个只要在ClassLoader.loadClass匹配到类名之后方法之后再调用就好了,下面给出我的脚本

public class XposedHook implements IXposedHookLoadPackage {
    @Override
    public void handleLoadPackage(final LoadPackageParam lpp) throws Throwable {
        if (!"com.tima.android.afmpn".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 ("com.timanetworks.timasync.android.base.AsyncSender".equals(name)) {
                    // 所有的类都是通过loadClass方法加载的
                    // 所以这里通过判断全限定类名,查找到目标类
                    // 第二步:Hook目标方法

                    final Class<?> ArgTBase= XposedHelpers.findClass("org.apache.thrift.TBase", lpp.classLoader);//在ClassLoader.loadClass匹配到类名之后方法之后再调用就不会报错了
                    final Class<?> ArgClass= XposedHelpers.findClass("java.lang.Class", lpp.classLoader);

                    findAndHookMethod(cls, "send", ArgTBase , ArgClass, new XC_MethodHook() {
                        @Override
                        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                            XposedBridge.log(param.method + " params: " + Arrays.toString(param.args));
                            //Log.i("params: " , Arrays.toString(param.args));
                        }
                        @Override
                        protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                            XposedBridge.log(param.method + " return: " + param.getResult());
                            //Log.i( " return: " , param.method,param.getResult());
                        }
                    });
                }
            }
        });
    }
}

3.第三种姿势:选择hook时机onNewActivity,即新Activity创建时,一般也代表着相关类早已加载完成,我们就可以成功hook了,这里使用xposed的一个Timing模板来hook加固后的应用,步骤参考三分钟学会Hook加固后应用,最后使用模板创建完了如下图,这里获得加壳应用的方法名可以用TraceView打印或者用FDex2脱壳后逆向查找

同样我把创建好了的AS项目源码放在了我的仓库里XposedShell ,直接下载编译即可,装了之后重启,运行APP,查看xposed日志

可以看到xposed成功hook到了加壳的APP的方法的参数和返回值,最后来看如何利用xposed打印堆栈,参考Xposed打印函数调用堆栈的几种方法