• 基于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:android="http://schemas.android.com/apk/res/android"
        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模块

    安装完成之后,Xposed Installer下就会出现我们开发的模块了。

    打勾启用,然后软重启手机。

    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: 劫持结束


    最终我们拦截了参数,使其固定为54,并且改写了tv_hook的值

    对于加壳的情况

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

    第一次hook

    需要分析壳代码实际加载app的方法,然后注入获取到真正的ClassLoader

            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

  • 相关阅读:
    javascript--Date
    PL/SQL的命令行窗口中执行脚本
    关于资产新增接口问题描述
    ORA-00600: internal error code, arguments: [kqludp2], [0x08D226918], [0], [], [], [], [], [], [], [], [], []
    Oracle 表空间扩容
    EBS 创建会计科目 小结
    EBS AP 创建会计科目失败
    EBS GL 日记账行“账户说明”段说明显示不全
    EBS 修改系统名称
    EBS 修改系统颜色
  • 原文地址:https://www.cnblogs.com/leestar54/p/12882658.html
Copyright © 2020-2023  润新知