[Android 逆向整理笔记] Xposed

终于在暑假的尾巴闲下来了🤧但是驾照还是没拿到,啥 b 教练给我拖了 2 个月了还没考科目三,私募了😅

好久没更 blog 了(居然已经一年半了,悲)期间写的一些零零散散的 wp/笔记啥的也懒得整理发出来了,直接开新坑吧

以前 Android 方面都是碰到啥就学点啥,比较杂碎,开个坑从基础的部分开始做一下系统性的整理总结,顺便在这个过程中查漏补缺学点新东西。然后立个 flag,以后会经常看 Google 推的安全补丁然后写 blog 记录下来(希望真不是 flag 吧)

这篇先写一下 Xposed 的,后面 frida 还有一坨,估计得写一会了。抓包也要写一下,以前一直是 wireshark + fiddler 用到死,遇到不走代理的就寄,趁此机会看看有没有其它优雅的方法能学一学。然后看有没有空学一学一直声称实际上一点没碰过的 flutter 逆向

机:Google Pixel 6 Pro, Android 13

简介

Xposed 框架是一套开放源代码的、在Android高权限模式下运行的框架服务,可以在不修改APK文件的情况下修改程序的运行,基于它可以制作出许多功能强大的模块,且在功能不冲突的情况下同时运作。比较常用的就是用 Xposed 来搞 Hook。

Xposed 用自己实现的 app_process 模块替换了系统本身的 app_process,加载了一个新的 jar 包,这样就劫持了 zygote 进程,替换成 Xposed 自己的虚拟机,这就是大概的原理。

配置

放一个 xml 板子

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.xposeddemo">

    <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/Theme.XposedDemo">
        <meta-data
            android:name="xposedmodule"
            android:value="true" />
        <meta-data
            android:name="xposeddescription"
            android:value="This is my Xposed module" />
        <meta-data
            android:name="xposedminversion"
            android:value="89" />
    </application>

</manifest>

libs内导入 XposedBridgeAPI,build.gradle 要把这个 libs 的 implementation 改成 compileOnly,不参与打包

然后在 main 下新建一个 assets 目录,创建文件 xposed_init,用它去声明入口,就一行代码,比如说包名是com.example.xposeddemo,那就写一行 com.example.xposeddemo.hook

hook 类的框架板子

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

public class Hook implements IXposedHookLoadPackage {
    @Override
    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
        if (!loadPackageParam.packageName.equals("包名")) {
            return;
        }
    }
}

基本的 Hook

就按这个板子,套在之前那个框架里面就行

XposedHelpers.findAndHookMethod("类名", loadPackageParam.classLoader, "方法名", String.class, new XC_MethodHook() { // String.class 是参数类型,就是说有一个 String 类型参数
    @Override
    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
        // Hook 的方法执行之前,这里一般会对传入的参数修改
        super.beforeHookedMethod(param);
        XposedBridge.log("修改前的参数: " + param.args[0].toString()); // 修改前正常传入的参数是什么
        String a = "自定义参数";
        param.args[0] = a; // 修改参数
        XposedBridge.log("修改后的参数: " + param.args[0].toString()); // 检查修改后的参数是什么
        // 用 log.e 也行,在 logcat 里面好像还更好看一些
    }
    @Override
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
        // Hook 的方法执行之后,这里一般就是修改一下返回值
        super.afterHookedMethod(param);
        XposedBridge.log("修改前的返回值: " + param.getResult().toString()); // 修改前正常的返回值
        param.setResult("自定义返回值");
        XposedBridge.log("修改后的返回值: " + param.getResult().toString()); // 修改后的返回值
    }
});
});

注意一下,XposedBridge 的 filter 标识是 LSPosed-Bridge,logcat 里面用这个过滤即可

不用填参数类型的写法

如果参数不是普通的 int, String 这种,而是比较复杂的玩意,可以用这个板子

Class a = loadPackageParam.classLoader.loadClass("类名");
XposedBridge.hookAllMethods(a, "方法名", new XC_MethodHook() {
    @Override
    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
        super.beforeHookedMethod(param);
        XposedBridge.log(param.args[0].toString());

    }
});

其实就是用了下 hookAllMethods 这个 api,不用填参数类型了,区别不大,后续修改操作和之前一样的

Hook 函数

Class a = loadPackageParam.classLoader.loadClass("类名");
XposedBridge.hookAllMethods(a, "方法名", new XC_MethodReplacement() {
    @Override
    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
        super.beforeHookedMethod(param);
    }
    @Override
    protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable {
        return "";
    }
    @Override
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
        super.afterHookedMethod(param);
    }
});

在这里面写就行

然后如果是构造函数的话也差不多,就换了个 api

无参的

XposedHelpers.findAndHookConstructor("类名", loadPackageParam.classLoader, new XC_MethodHook() {
    @Override
    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
        super.beforeHookedMethod(param);
    }
    @Override
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
        super.afterHookedMethod(param);
    }
});

有参的加个参数类型即可

XposedHelpers.findAndHookConstructor("类名", classLoader, String.class, new XC_MethodHook() {
    @Override
    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
        super.beforeHookedMethod(param);
    }
    @Override
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
        super.afterHookedMethod(param);
    }
});

简单加固的处理

XposedHelpers.findAndHookMethod(Application.class, "attach", Context.class, new XC_MethodHook() {  
    @Override  
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {  
        Context context = (Context) param.args[0];  
        ClassLoader classLoader = context.getClassLoader();
        // 然后是 Hook 逻辑
    }  
});

简单来讲就是尝试把 ClassLoader 的点找到,然后再用这个 ClassLoader 去进行后续的操作。当然大部分情况下还是得自己去处理前面加固的逻辑,这个只能说是一个可能的例子,不能当板子用

Hook 变量

如果是静态变量,static,val 这种

final Class temp = XposedHelpers.findClass("类名", loadPackageParam.classLoader);
XposedHelpers.setStaticIntField(temp, "变量名", 233);

然后,由于没有 String 的 api,所以如果是改 string 这里就用 setStaticObjectField,反正都是对象

这里我突然比较好奇:这静态变量 hook 是怎么能生效的,按理来说应该编译的时候就定死了这个静态变量永不改变了。后面有空可以试着读读源码看是怎么操作的

如果是实例变量

final Class temp = XposedHelpers.findClass("类名", loadPackageParam.classLoader);
XposedBridge.hookAllConstructors(temp, new XC_MethodHook() {
    @Override
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
        super.afterHookedMethod(param);
        // param.thisObject 获取当前所属的对象
        Object ob = param.thisObject;
        XposedHelpers.setIntField(ob, "变量名", 2333);
    }
});

就是先找到字节码,然后在函数构造完毕后再去 Hook

Hook (多个) Dex 文件

和前面那个对加固的处理差不多的过程,遍历找到了后改就行

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("类名");
        } catch (Exception e) {
            Log.e("filter", "未找到类 ", e);
            return;
        }
        XposedHelpers.findAndHookMethod(hookclass, "方法名", new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                super.afterHookedMethod(param);
            }
        });
    }
});

主动调用方法

静态和实例方法其实差不多,只不过一个用的是 callStaticMethod 一个是 callMethod

Class temp = XposedHelpers.findClass("类名", loadPackageParam.classLoader);
XposedHelpers.callMethod(temp.newInstance(), "方法名"); // 有参数的话在方法名后面继续写参数,就 String.class 那种

也可以试一下反射的手法

Class temp = XposedHelpers.findClass("类名", loadPackageParam.classLoader);
Class temp_class = Class.forName("类名", false, loadPackageParam.classLoader); // 第一步找到类
Method temp_method = temp_class.getDeclaredMethod("方法名"); // 找到方法
temp_method.setAccessible(true); // 如果是私有方法就要 setAccessible 设置访问权限
temp_method.invoke(temp.newInstance()); // invoke 主动调用,或者 set 修改值(变量)

Hook 内部类

XposedHelpers.findAndHookMethod("类名$内部类名", loadPackageParam.classLoader, "内部方法名", String.class, new XC_MethodHook() { // 放了个 String 类型的参数的例子
    @Override
    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
        super.beforeHookedMethod(param);
    }
});

其实就是多一个 $ 符号再把内部类的名字放上去,剩下的和之前一样

遍历方法

XposedHelpers.findAndHookMethod(ClassLoader.class, "loadClass", String.class, new XC_MethodHook() {
    @Override
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
        super.afterHookedMethod(param);
        Class temp = (Class) param.getResult();
        String name = temp.getName();
        // 选择该包名的类
        if (name.contains("包名")) {
            Method[] arr = temp.getDeclaredMethods();
            for (int i = 0; i < arr.length; ++i) {
                final Method md = arr[i];
                int mod = arr[i].getModifiers();
                // 排除抽象、native、接口方法,这几个一般不 Hook
                if (!Modifier.isAbstract(mod) && !Modifier.isNative(mod) && !Modifier.isAbstract(mod)) {
                    XposedBridge.hookMethod(arr[i], new XC_MethodHook() {
                        @Override
                        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                            super.beforeHookedMethod(param);
                            XposedBridge.log("Find Method: " + md.toString());
                        }
                    });
                }

            }
        }

    }
});

这个可以一边操作一边看,每次操作的时候用到了的方法都会打印出来。把 log 写细致一点可以辅助 debug,并且它的打印顺序就是堆栈顺序,总体来说还是挺好用的

trick

字符串赋值的定位

XposedHelpers.findAndHookMethod("android.widget.TextView", loadPackageParam.classLoader, "setText", CharSequence.class, new XC_MethodHook() {
    @Override
    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
        super.beforeHookedMethod(param);
        Log.d("StackTrace", param.args[0].toString());
        if (param.args[0].equals("写你想看的变量的值")) {
            printStackTrace();
        }
    }
});

private static void printStackTrace() {
    Throwable ex = new Throwable();
    StackTraceElement[] stackElements = ex.getStackTrace();
    for (int i = 0; i < stackElements.length; i++) {
        StackTraceElement element = stackElements[i];
        Log.d("StackTrace", "at " + element.getClassName() + "." + element.getMethodName() + "(" + element.getFileName() + ":" + element.getLineNumber() + ")");
    }
}

这个可以打印堆栈,然后去看什么时候进行字符串赋值了(setText)

监听点击事件

Class temp = XposedHelpers.findClass("android.view.View", loadPackageParam.classLoader);
XposedBridge.hookAllMethods(temp, "performClick", new XC_MethodHook() {
    @Override
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
        super.afterHookedMethod(param);
        Object listenerInfoObject = XposedHelpers.getObjectField(param.thisObject, "mListenerInfo");
        Object mOnClickListenerObject = XposedHelpers.getObjectField(listenerInfoObject, "mOnClickListener");
        String callbackType = mOnClickListenerObject.getClass().getName();
        Log.d("test", callbackType);
    }
});

这个会打印出来你点击按钮后该按钮触发了哪些方法,也是跟踪逻辑的好板子

改写布局

XposedHelpers.findAndHookMethod("类名", lpparam.classLoader,  
        "onCreate", Bundle.class, new XC_MethodHook() {  
    @Override  
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {  
        super.afterHookedMethod(param);  
        View img = (View)XposedHelpers.callMethod(param.thisObject,  
                "findViewById", 控件的 16 进制值);  
        img.setVisibility(View.GONE);  

    }  
});

强制改控件的布局,可以去掉一些图片啥的

剩下的大概可能也许是读一下 Xposed 的源码...?以前还没读过这个的源码,前面那个改静态变量的实现方法还挺感兴趣的,然后就是找一个能用上 Xposed 的逆向题再练练手。

先把后面的部分整理一下再议,frida 还有一坨东西要写

posted @ 2024-08-16 17:12  iPlayForSG  阅读(6)  评论(0编辑  收藏  举报