[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 还有一坨东西要写