基于xposed框架hook安卓app 创建测试项目 创建xposed模块 安装测试app 安装xposed模块 hook测试app 对于加壳的情况 其他示例 总结 参考

TOC
Xposed:https://github.com/rovo89/Xposed
Xposed Installer:https://forum.xda-developers.com/showthread.php?t=3034811
api文档:https://api.xposed.info/reference/packages.html
Maven包:https://mvnrepository.com/artifact/de.robv.android.xposed/api

注:安装原生xposed框架需root,若无法root可以考虑使用virtualXposed
https://github.com/android-hacker/VirtualXposed

为了便于实践,我们直接创建一个名为test的Empty Activity项目即可。
接着在MainActivity.java中,新增一个Log方法,我们后面用xposed框架来hook这个方法。

package com.example.test;


import androidx.appcompat.app.AppCompatActivity;


import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;


import java.util.Random;


public class MainActivity extends AppCompatActivity {
    private static final String TAG = "xposedText";
    private TextView tv_hook;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv_hook = (TextView) findViewById(R.id.tv_hook);
        tv_hook.setText("快来hook我");
        Log("init", new Random().nextInt(100));
    }


    public void Log(String msg, int code) {
        Log.i(TAG, "Log: " + msg + code);
    }
}

创建xposed模块

新增默认带Empty Activity的Module

在Module下的build.gradle文件添加依赖包

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])


    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
    //xposed依赖包

    compileOnly 'de.robv.android.xposed:api:82'
}

然后sync,如果比较慢建议科学·上网。

在Module下的AndroidManifest.xml文件添加xposed相关meta标签,用于框架识别

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:andro
    package="com.example.xposed">


    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <!--模块申明,true表示申明为xposed模块-->
        <meta-data
            android:name="xposedmodule"
            android:value="true" />


        <!--模块说明,一般为模块的功能描述-->
        <meta-data
            android:name="xposeddescription"
            android:value="这个模块是用来劫持登录的" />


        <!--模块兼容版本-->
        <meta-data
            android:name="xposedminversion"
            android:value="54" />

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />


                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>


</manifest>

添加hook类,继承IXposedHookLoadPackage实现hook方法

package com.example.xposed;


import android.content.Context;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;


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


public class HookLog implements IXposedHookLoadPackage {
    private static final String TAG = "XposedHook";
    private Context hookContext = null;


    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) {


        //过滤非目标包
        if (lpparam == null) {
            return;
        }
        Log.e(TAG, "Load app packageName:" + lpparam.packageName);
        //目标app的包名,既目标apk的AndroidManifest.xml文件manifest标签中的package属性值
        if (!"com.example.test".equals(lpparam.packageName)) {
            return;
        }


        //获取context,用于Toast提示
        XposedHelpers.findAndHookMethod(
                //填写目标方法所在的完整类名
                "android.content.ContextWrapper",
                //默认classLoader
                lpparam.classLoader,
                //目标方法
                "getApplicationContext",
                // Hook回调
                new XC_MethodHook() {
                    protected void afterHookedMethod(MethodHookParam param) {
                        if (hookContext != null)
                            return;
                        hookContext = (Context) param.getResult();
                        Log.i(TAG + "hookContext", hookContext.getPackageCodePath());
                    }
                }
        );


        //进行hook
        XposedHelpers.findAndHookMethod(
                //填写目标方法所在的完整类名
                "com.example.test.MainActivity",
                //默认classLoader
                lpparam.classLoader,
                //目标方法
                "Log",
                //方法参数,有几个写几个
                String.class,
                // 注意,要做到与目标方法参数对应,这里不能用Integer.class。
                int.class,
                // Hook回调
                new XC_MethodHook() {


                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) throws Exception {
                        Log.i(TAG, "劫持开始");
                        String msg = (String) param.args[0];
                        int code = (int) param.args[1];
                        Log.i(TAG, "原code:" + code);
                        Toast.makeText(hookContext, "原code:" + code, Toast.LENGTH_LONG).show();
                        //修改方法参数,设为54,这样就不随机了。
                        param.args[1] = 54;
                        TextView tv = (TextView) XposedHelpers.findField(param.thisObject.getClass(), "tv_hook").get(param.thisObject);
                        tv.setText("随机数被我固定为54了");
                    }


                    protected void afterHookedMethod(MethodHookParam param) {
                        Log.i(TAG, "劫持结束");
                    }
                }
        );


    }
}

在模块下的src/assets/xposed_init(没有则新建)文件添加完整的hook类名

com.example.xposed.HookLog
用于提示XposedBridge加载哪一个类。

安装测试app

正常情况下,长这样。
基于xposed框架hook安卓app
创建测试项目
创建xposed模块
安装测试app
安装xposed模块
hook测试app
对于加壳的情况
其他示例
总结
参考

安装xposed模块

安装完成之后,Xposed Installer下就会出现我们开发的模块了。
基于xposed框架hook安卓app
创建测试项目
创建xposed模块
安装测试app
安装xposed模块
hook测试app
对于加壳的情况
其他示例
总结
参考
打勾启用,然后软重启手机。

hook测试app

接着我们直接运行测试app,xposed将启用我们写好的模块进行hook

05-13 14:49:17.429 25902-25902/? E/XposedHook: Load app packageName:com.example.xposed
05-13 14:52:00.527 820-820/? I/XposedHookhookContext: /data/app/com.example.test-1.apk
05-13 14:49:37.069 23232-23232/? I/XposedHook: 劫持开始

05-13 14:49:37.069 23232-23232/? I/XposedHook: 原code:22
05-13 14:49:37.069 23232-23232/? I/xposedText: Log: init54
05-13 14:49:37.069 23232-23232/? I/XposedHook: 劫持结束

基于xposed框架hook安卓app
创建测试项目
创建xposed模块
安装测试app
安装xposed模块
hook测试app
对于加壳的情况
其他示例
总结
参考
最终我们拦截了参数,使其固定为54,并且改写了tv_hook的值

对于加壳的情况

若需目标app加壳,我们就不能直接findAndHookMethod了,因为真正app代码是在壳内动态加载的,所以要经过两次hook。
我们以360的壳为例。

第一次hook

需要分析壳代码实际加载app的方法,然后注入获取到真正的ClassLoader
基于xposed框架hook安卓app
创建测试项目
创建xposed模块
安装测试app
安装xposed模块
hook测试app
对于加壳的情况
其他示例
总结
参考

        XposedHelpers.findAndHookMethod(
                "com.stub.StubApp",
                lpparam.classLoader,
                "attachBaseContext",
                Context.class,
                // Hook回调
                new XC_MethodHook() {


                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) throws Exception {
                        Log.e(TAG, "劫持开始");
                    }


                    protected void afterHookedMethod(MethodHookParam param) {
                        //获取到Context对象,通过这个对象来获取classloader
                        Context context = (Context) param.args[0];
                        //获取classloader,之后hook加固后的就使用这个classloader
                        ClassLoader realClassLoader = context.getClassLoader();
                        //下面就是将classloader修改成壳的classloader就可以成功的hook了
                        realXposedHook(realClassLoader);
                        Log.e(TAG, "劫持结束");
                    }
                }
        );

第二次hook

取到真正的ClassLoader,我们就可以愉快的hook了

    private void realXposedHook(final ClassLoader classLoader) {
        //固定格式
        XposedHelpers.findAndHookMethod(
                "com.target.class",
                classLoader,
                "targetMethod",
                // Hook回调
                new XC_MethodHook() {


                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) throws Exception {
                        Log.e(TAG, "real劫持开始");
                    }


                    protected void afterHookedMethod(MethodHookParam param) {
                        Log.e(TAG, "real劫持结束");
                    }
                }
        );
    }

其他示例

目标代码

abstract class Animal{
    int anonymoutInt = 500;
    public abstract void eatFunc(String value);
}


public class HookDemo {
    private String Tag = "HookDemo";
    private static  int staticInt = 100;
    public  int publicInt = 200;
    private int privateInt = 300;


    public HookDemo(){
        this("NOHook");
        Log.d(Tag, "HookDemo() was called|||");
    }


    private HookDemo(String str){
        Log.d(Tag, "HookDemo(String str) was called|||" + str);
    }


    public void hookDemoTest(){
        Log.d(Tag, "staticInt = " + staticInt);
        Log.d(Tag, "PublicInt = " + publicInt);
        Log.d(Tag, "privateInt = " + privateInt);
        publicFunc("NOHook");
        Log.d(Tag, "PublicInt = " + publicInt);
        Log.d(Tag, "privateInt = " + privateInt);
        privateFunc("NOHook");
        staticPrivateFunc("NOHook");


        String[][] str = new String[1][2];
        Map map = new HashMap<String, String>();
        map.put("key", "value");
        ArrayList arrayList = new ArrayList();
        arrayList.add("listValue");
        complexParameterFunc("NOHook", str, map, arrayList);


        repleaceFunc();
        anonymousInner(new Animal() {
            @Override
            public void eatFunc(String value) {
                Log.d(Tag, "eatFunc(String value)  was called|||" + value);
                Log.d(Tag, "anonymoutInt =  " + anonymoutInt);
            }
        }, "NOHook");


        InnerClass innerClass = new InnerClass();
        innerClass.InnerFunc("NOHook");
    }


    public void publicFunc(String value){
        Log.d(Tag, "publicFunc(String value) was called|||" + value);
    }


    private void privateFunc(String value){
        Log.d(Tag, "privateFunc(String value) was called|||" + value);
    }


    static private void staticPrivateFunc(String value){
        Log.d("HookDemo", "staticPrivateFunc(Strin value) was called|||" + value);
    }


    private void complexParameterFunc(String value, String[][] str, Map<String,String> map, ArrayList arrayList)
    {
        Log.d("HookDemo", "complexParameter(Strin value) was called|||" + value);
    }


    private void repleaceFunc(){
        Log.d(Tag, "repleaceFunc will be replace|||");
    }


    public void anonymousInner(Animal dog, String value){
        Log.d(Tag, "anonymousInner was called|||" + value);
        dog.eatFunc("NOHook");
    }


    private void hideFunc(String value){
        Log.d(Tag, "hideFunc was called|||" + value);
    }


    class InnerClass{
        public int innerPublicInt = 10;
        private int innerPrivateInt = 20;
        public InnerClass(){
            Log.d(Tag, "InnerClass constructed func was called");
        }
        public void InnerFunc(String value){
            Log.d(Tag, "InnerFunc(String value) was called|||" + value);
            Log.d(Tag, "innerPublicInt = " + innerPublicInt);
            Log.d(Tag, "innerPrivateInt = " + innerPrivateInt);
        }
    }
}

hook模块

public class XposedHook implements IXposedHookLoadPackage {


    @Override
    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
        if (loadPackageParam.packageName.equals("com.example.xposedhooktarget")) {
            final Class<?> clazz = XposedHelpers.findClass("com.example.xposedhooktarget.HookDemo", loadPackageParam.classLoader);
            //getClassInfo(clazz);


            //不需要获取类对象,即可直接修改类中的私有静态变量staticInt
            XposedHelpers.setStaticIntField(clazz, "staticInt", 99);


            //Hook无参构造函数,啥也不干。。。。
            XposedHelpers.findAndHookConstructor(clazz, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    XposedBridge.log("Haha, HookDemo constructed was hooked" );
                    //大坑,此时对象还没有建立,即不能获取对象,也不能修改非静态变量的值
                    //XposedHelpers.setIntField(param.thisObject, "publicInt", 199);
                    //XposedHelpers.setIntField(param.thisObject, "privateInt", 299);
                }
            });


            //Hook有参构造函数,修改参数
            XposedHelpers.findAndHookConstructor(clazz, String.class,  new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    param.args[0] = "Haha, HookDemo(str) are hooked";
                }
            });


            //Hook有参构造函数,修改参数------不能使用XC_MethodReplacement()替换构造函数内容,
            //XposedHelpers.findAndHookConstructor(clazz, String.class, new XC_MethodReplacement() {
            //    @Override
            //    protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable {
            //        Log.d("HookDemo" , "HookDemo(str) was replace");
            //    }
            //});


            //Hook公有方法publicFunc,
            // 1、修改参数
            // 2、修改下publicInt和privateInt的值
            // 3、再顺便调用一下隐藏函数hideFunc
            //XposedHelpers.findAndHookMethod("com.example.xposedhooktarget.HookDemo", clazz.getClassLoader(), "publicFunc", String.class, new XC_MethodHook()
            XposedHelpers.findAndHookMethod(clazz, "publicFunc", String.class, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    param.args[0] = "Haha, publicFunc are hooked";
                    XposedHelpers.setIntField(param.thisObject, "publicInt", 199);
                    XposedHelpers.setIntField(param.thisObject, "privateInt", 299);
                    // 让hook的对象本身去执行流程
                    Method md = clazz.getDeclaredMethod("hideFunc", String.class);
                    md.setAccessible(true);
                    //md.invoke(param.thisObject, "Haha, hideFunc was hooked");
                    XposedHelpers.callMethod(param.thisObject, "hideFunc", "Haha, hideFunc was hooked");


                    //实例化对象,然后再调用HideFunc方法
                    //Constructor constructor = clazz.getConstructor();
                    //XposedHelpers.callMethod(constructor.newInstance(), "hideFunc", "Haha, hideFunc was hooked");
                }
            });


            //Hook私有方法privateFunc,修改参数
            //XposedHelpers.findAndHookMethod("com.example.xposedhooktarget.HookDemo", clazz.getClassLoader(), "privateFunc", String.class, new XC_MethodHook()
            XposedHelpers.findAndHookMethod(clazz, "privateFunc", String.class, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    param.args[0] = "Haha, privateFunc are hooked";
                }
            });


            //Hook私有静态方法staticPrivateFunc, 修改参数
            XposedHelpers.findAndHookMethod(clazz, "staticPrivateFunc", String.class, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    param.args[0] = "Haha, staticPrivateFunc are hooked";
                }
            });


            //Hook复杂参数函数complexParameterFunc
            Class fclass1 = XposedHelpers.findClass("java.util.Map", loadPackageParam.classLoader);
            Class fclass2 = XposedHelpers.findClass("java.util.ArrayList", loadPackageParam.classLoader);
            XposedHelpers.findAndHookMethod(clazz, "complexParameterFunc", String.class,
                    "[[Ljava.lang.String;", fclass1, fclass2, new XC_MethodHook() {
                        @Override
                        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                            param.args[0] = "Haha, complexParameterFunc are hooked";
                        }
                    });


            //Hook私有方法repleaceFunc, 替换打印内容
            XposedHelpers.findAndHookMethod(clazz, "repleaceFunc", new XC_MethodReplacement() {
                @Override
                protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable {
                    Log.d("HookDemo", "Haha, repleaceFunc are replaced");
                    return null;
                }
            });
            //Hook方法, anonymousInner, 参数是抽象类,先加载所需要的类即可
            Class animalClazz  = loadPackageParam.classLoader.loadClass("com.example.xposedhooktarget.Animal");
            XposedHelpers.findAndHookMethod(clazz, "anonymousInner", animalClazz, String.class, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    XposedBridge.log("HookDemo This is test");
                    param.args[1] = "Haha, anonymousInner are hooked";
                }
            });


            //Hook匿名类的eatFunc方法,修改参数,顺便修改类中的anonymoutInt变量
            XposedHelpers.findAndHookMethod("com.example.xposedhooktarget.HookDemo$1", clazz.getClassLoader(),
                    "eatFunc", String.class, new XC_MethodHook() {
                        @Override
                        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                            param.args[0] = "Haha, eatFunc are hooked";
                            XposedHelpers.setIntField(param.thisObject, "anonymoutInt", 499);
                        }
                    });


            //hook内部类的构造方法失败,且会导致hook内部类的InnerFunc方法也失败,原因不明
//            XposedHelpers.findAndHookConstructor(clazz1, new XC_MethodHook() {
//                        @Override
//                        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
//                            XposedBridge.log("Haha, InnerClass constructed was hooked" );
//                        }
//                    });


            //Hook内部类InnerClass的InnerFunc方法,修改参数,顺便修改类中的innerPublicInt和innerPrivateInt变量
            final Class<?> clazz1 = XposedHelpers.findClass("com.example.xposedhooktarget.HookDemo$InnerClass", loadPackageParam.classLoader);
            XposedHelpers.findAndHookMethod(clazz1, "InnerFunc", String.class, new XC_MethodHook() {
                        @Override
                        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                            param.args[0] = "Haha, InnerFunc was hooked";
                            XposedHelpers.setIntField(param.thisObject, "innerPublicInt", 9);
                            XposedHelpers.setIntField(param.thisObject, "innerPrivateInt", 19);
                        }
                    });
        }
    }


    private void getClassInfo(Class clazz) {
        //getFields()与getDeclaredFields()区别:getFields()只能访问类中声明为公有的字段,私有的字段它无法访问,
        //能访问从其它类继承来的公有方法.getDeclaredFields()能访问类中所有的字段,与public,private,protect无关,
        //不能访问从其它类继承来的方法
        //getMethods()与getDeclaredMethods()区别:getMethods()只能访问类中声明为公有的方法,私有的方法它无法访问,
        //能访问从其它类继承来的公有方法.getDeclaredFields()能访问类中所有的字段,与public,private,protect无关,
        //不能访问从其它类继承来的方法
        //getConstructors()与getDeclaredConstructors()区别:getConstructors()只能访问类中声明为public的构造函数
        //getDeclaredConstructors()能访问类中所有的构造函数,与public,private,protect无关


        //XposedHelpers.setStaticObjectField(clazz,"sMoney",110);
        //Field sMoney = clazz.getDeclaredField("sMoney");
        //sMoney.setAccessible(true);
        Field[] fs;
        Method[] md;
        Constructor[] cl;
        fs = clazz.getFields();
        for (int i = 0; i < fs.length; i++) {
            XposedBridge.log("HookDemo getFiled: " + Modifier.toString(fs[i].getModifiers()) + " " +
                    fs[i].getType().getName() + " " + fs[i].getName());
        }
        fs = clazz.getDeclaredFields();
        for (int i = 0; i < fs.length; i++) {
            XposedBridge.log("HookDemo getDeclaredFields: " + Modifier.toString(fs[i].getModifiers()) + " " +
                    fs[i].getType().getName() + " " + fs[i].getName());
        }
        md = clazz.getMethods();
        for (int i = 0; i < md.length; i++) {
            Class<?> returnType = md[i].getReturnType();
            XposedBridge.log("HookDemo getMethods: " + Modifier.toString(md[i].getModifiers()) + " " +
                    returnType.getName() + " " + md[i].getName());
            //获取参数
            //Class<?> para[] = md[i].getParameterTypes();
            //for (int j = 0; j < para.length; ++j) {
            //System.out.print(para[j].getName() + " " + "arg" + j);
            //if (j < para.length - 1) {
            //    System.out.print(",");
            //}
            //}
        }
        md = clazz.getDeclaredMethods();
        for (int i = 0; i < md.length; i++) {
            Class<?> returnType = md[i].getReturnType();
            XposedBridge.log("HookDemo getDeclaredMethods: " + Modifier.toString(md[i].getModifiers()) + " " +
                    returnType.getName() + " " + md[i].getName());
        }
        cl = clazz.getConstructors();
        for (int i = 0; i < cl.length; i++) {
            XposedBridge.log("HookDemo getConstructors: " + Modifier.toString(cl[i].getModifiers()) + " " +
                    md[i].getName());
        }
        cl = clazz.getDeclaredConstructors();
        for (int i = 0; i < cl.length; i++) {
            XposedBridge.log("HookDemo getDeclaredConstructors: " + Modifier.toString(cl[i].getModifiers()) + " " +
                    md[i].getName());
        }
    }
}

总结

1、hook的难点在于逆向分析,花的时间比较多。
2、理论上可以基于xposed做任何事。
3、若目标app改版升级什么的,需要重新适配,很麻烦。

demo地址:
链接:https://pan.baidu.com/s/1g8dEy3OgE-vcSDUj7CHyaQ 密码:bvww

参考

https://github.com/rovo89/XposedBridge/wiki/Development-tutorial
https://www.cnblogs.com/gordon0918/p/6732100.html