抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

介绍

frida 和 objection 对于App 的修改仅仅只是单次性的,App重启之后,我们就又需要手动使用 frida 或 objection 对 App 进行注入。不过我们有 Xposed 框架,基于此框架开发插件,则可以对 App 实现永久性修改。

原理

  1. ⼿机启动时init进程会启动所有Android进程的父进程——Zygote(孵化)进程。该进程的启动配置在/init.rc脚本中,而Zygote进程对应的执行文件是/system/bin/app_process,该文件完成类库的加载以及一些函数的调用工作。在Zygote进程创建后,再fork出SystemServer进程和其他进程。而Xposed Framework呢,就是用自己实现的app_process替换掉了系统原本提供的app_process,加载一个额外的jar包,然后入口从原来的com.android.internal.osZygoteInit.main()被替换成了de.robv.android.xposed.XposedBridge.main(),然后创建的Zygote进程就变成Hook的Zygote进程了,而后面Fork出来的进程也是被Hook过的。这个Jar包在/data/data/de.rbov.android.xposed.installer/bin/XposedBridge.jar
  2. Xposed_zygote进程启动后会初始化⼀些so⽂件(system/lib system/lib64),然后进⼊XposedBridge.jar中的XposedBridge.main中初始化jar包完成对⼀些关键Android系统函数的hook。
  3. Hook则是利⽤修改过的虚拟机将函数注册为native函数。
  4. 然后再返回zygote中完成原本zygote需要做的⼯作。

Xposed组成:

Xposed→C++音分,Xposed版的zygote,用于替换原生zygote,并为XposedBridge提供JNI方法,需由XposedInstaller在root后放到/system/bin目录下
XposedBridge→Java部分,编译后会生成一个jar包,负责在Native层与Framework层法行交互;
XposedInstaller→Xposed插件管理及功能控制的APP,包括启用、下载、禁用插仁等功能;
XposedTools→用于译Xposed及XposedBridge;

衍生品

太极Taichi

Edxposed

VA(virtual app)(2B)

平头哥

  • Riru: 提供了一种将代码注入到 zygote 进程的方法。

  • Zygisk: 是 Magisk 的一个模块,用于在系统启动时注入代码。

  • 对比表格

    特征/框架 Xposed Framework EdXposed Framework LSPosed Framework
    开发者 rovo89 多个开发者维护 乐星
    支持系统 Android 7 及之前版本 Android 8 到 Android 11 Androids 10 和 Android 11、12、13
    状态 停更,最后一版支持 Android 7 可能陷入停更危机,目前未能支持 Android 12 停更 2021.2.15 最后一次更新 活跃,可能成为 EdXposed 的替代品 停更 2024.1.8日宣布
    特点 通过修改系统的 Dalvik 运行时实现 作为 Magisk 模块的一部分,在 Magisk 环境中安装和管理 对 Android 10 和 11、12和13的更好支持和优化,更好的兼容性,可以与 Magisk 一起使用
    基于 原创 Xposed Framework Xposed Framework
    代际 第一代 第二代 第三代

安装

真机

基本条件:手机已经root并且已经刷入第三方recovery 注意fastboot版本匹配
首先下载并安装好XposedInstaller
然后下载和手机cpu对应的Framework,并放在手机存储卡上
然后重启到recovery模式,刷入Framework
再重启即可

模拟器

下载并安装好XposedInstaller 3.1.5

搬移:Xposed所有版本下载地址 - 吾爱破解 - 52pojie.cn下载对应的framework xposed-v89-sdk25-x86

文件不存在就创建一下,(先看存不存在再去推入)

去雷神模拟器下使用它的adb

1
adb push xposed-v89-sdk25-x86.zip /sdcard/Android/data/de.robv.android.xposed.installer/cache/downloads/framework

永久记住超级用户访问权限

xposed已激活

Xposed插件开发

Xposed Framework API

初始化配置

Xposed 插件也是以 App 的形式安装在系统中的,只是区别于普通 App 的开发,Xposed 插件的开发还需要一些特别的配置。

我们选择创建一个No Activity,语言选择Java,SDK选默认的API 25就可。

  1. 在 AndroidManifest.xml 中的 application 节点中增加如下 3 个meta-data属性,分别用于表示是不是 Xposed 模块、Xposed 模块的介绍以及支持最低的 Xposed 版本。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <application
    ...>
    <meta-data
    android:name="xposedmodule"
    android:value="true" /><!--是不是Xposed模块-->
    <meta-data
    android:name="xposeddescription"
    android:value="这是一个Xposed模块" /> <!--Xposed模块介绍-->
    <meta-data
    android:name="xposedminversion"
    android:value="82" /> <!--最低的Xposed版本-->
    </application>
  2. 在 App 工程的 settings.gradle 文件中进行如下添加,引入 API 语法提示。

1
2
3
4
5
6
7
8
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven { url 'https://api.xposed.info/' } // 添加这一行即可
}
}

进入我们app目录下的build.gradle,引入xposed的依赖

1
2
3
4
dependencies {
compileOnly 'de.robv.android.xposed:api:82' //添加我
// compileOnly 'de.robv.android.xposed:api:82:sources' // 不要导入源码,这会导致idea无法索引文件,从而让语法提示失效
}

还要在./app/src/main/res/values目录下创建arrays.xml,填入下面的内容,这一步主要是指定模块的作用域包名,效果就是在Lsposed中勾选作用域时会在应用下提示推荐应用。

1
2
3
4
5
6
7
8
<resources>
<string-array name="xposedscope" >
<!-- 这里填写模块的作用域应用的包名,可以填多个。 -->
<item>ceui.lisa.pixiv</item>
<item>com.xjs.ehviewer</item>
<item>com.picacomic.fregata</item>
</string-array>
</resources>

在 app/src/main ,新建一个assets文件夹。

目录下新建一个 xposed_init 文件用于指定 Xposed 模块入口类的完整类名。

在assets文件夹下新建文件xposed_init,文件类型选择text,文件内容填上你要新建的xposed类的名字。这个文件标记了你的xposed模块的入口。

模板:

对于在 xposed_init 文件中指定的 Hook 入口类,我们需要让该类实现 IXposedHookLoadPackage 接口,用于引入在安装 Xposed 框架的系统中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;


public class Xhook implements IXposedHookLoadPackage {

public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
if(lpparam.packageName.equals("包名")) {
XposedHelpers.findAndHookMethod("类名", lpparam.classLoader,"函数名", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param)
throws Throwable {


Boolean result = true;//默认返回true
param.setResult(result);
}
});
};
}
}

由于每个 Zygote 孵化出来的 App 进程在启动时都会调用函数 handleLoadPackage(),此时如果想要 Hook 指定进程,就需要通过 handleLoadPackage 函数的参数 lpparam 进行过滤。lpparam 参数是一个 XC_LoadPackage.LoadPackageParam 类型的参数,它提供了一些有用的成员变量,用于表示应用进程的一些信息,其中主要成员类型信息如表:

成员变量类型 成员变量名 含义
String packageName 包名
String processName 进程名
ClassLoader classLoader 类加载器
ApplicationInfo appInfo 应用的更多信息

因此我们想要 Hook 指定进程,就可以通过 packageName 成员变量来进行筛选,之后再对进程进行 Hook 操作,这就涉及到 XposedHelpers 类,它本质上是封装了反射函数。

findAndHookMethod 是用于寻找并 Hook 指定函数的函数,它有如下两个重载函数:

  • findAndHookMethod(Class<?> clazz, String methodName, Object… parameterTypesAndCallback)
  • findAndHookMethod(String className, ClassLoader classLoader, String methodName, Object… parameterTypesAndCallback)

它们之间的区别仅在于类是否自动获取。值得一提的是,当存在重载函数时,可以额外在参数 methodName 后面添加参数的类型来指定某一重载函数的 Hook。

findAndHookMethod 函数中的参数 Callback 可以是一个抽象函数 XC_MethodHook,也可以是一个抽象函数XC_MethodReplacement。

在使用 XC_MethodHook 抽象函数时根据需求可实现如下抽象回调函数:

  • beforeHookedMethod() :通常用于获取和修改目标函数的参数类型和值。
  • afterHookedMethod():通常用于获取和修改目标函数的返回值。

在使用 XC_MethodReplacement 抽象函数时需要实现如下抽象回调函数:

  • replaceHookedMethod():完全替换目标函数的功能。

常用API和写法

类反射

1
Class clazz = XposedHelpers.findClass("类名" ,lpparam.classLoader)

实例对象的获取

1
2
3
XposedHelpers.newInstance(Class<?> clazz, Object... args) 
XposedHelpers.newInstance(Class<?> clazz, Class<?>[] parameterTypes, Object... args)
通过 3.2.3 Hook变量获取

Hook 变量

静态变量Hook

1
2
XposedHelpers.getStatic<type>Field(clazz, "变量名")//获取变量值
XposedHelpers.setStatic<type>Field(clazz, "变量名", value)//修改变量值

实例变量Hook

1
2
XposedHelpers.get<type>Field(obj, "变量名")//获取obj对象中的指定变量的值
XposedHelpers.set<type>Field(obj, "变量名", value)//修改obj对象中的指定变量的值

Hook 函数

1
2
3
4
5
6
//Hook普通函数(包括匿名函数和内部类函数)
XposedHelpers.findAndHookMethod(Class<?> clazz, String methodName, Object... parameterTypesAndCallback)
XposedHelpers.findAndHookMethod(String className, ClassLoader classLoader, String methodName, Object... parameterTypesAndCallback)
//Hook构造函数
XposedHelpers.findAndHookConstructor(Class<?> clazz, Object... parameterTypesAndCallback)
XposedHelpers.findAndHookConstructor(String className, ClassLoader classLoader, Object... parameterTypesAndCallback)

主动调用

静态函数调用

1
2
3
//调用clazz类中的methodName方法,如果有参数则追加参数值,如果methodName方法是重载方法,则追加参数类型来指定目标函数
XposedHelpers.callStaticMethod(Class<?> clazz, String methodName, Object... args);
XposedHelpers.callStaticMethod(Class<?> clazz, String methodName, Class<?>[] parameterTypes, Object... args)

实例函数调用

1
2
3
//调用obj对象中的methodName方法,如果有参数则追加参数值,如果methodName方法是重载方法,则追加参数类型来指定目标函数
XposedHelpers.callMethod(Object obj, String methodName, Object... args)
XposedHelpers.callMethod(Object obj, String methodName, Class<?>[] parameterTypes, Object... args);

Hook加固App的真实逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Class activityThreadClass = XposedHelpers.findClass("android.app.ActivityThread", lpparam.classLoader);
XposedHelpers.findAndHookMethod(activityThreadClass, "performLaunchActivity", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
Application mInitialApplication = (Application) XposedHelpers.getObjectField(param.thisObject, "mInitialApplication");
ClassLoader finalLoader = mInitialApplication.getClassLoader();
XposedBridge.log("found classloader => " + finalLoader.toString());
}
});
//或者
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 multiDex方法

某些 App 对应的APK中可能是多dex的形式,以下是如何hook某一dex中的函数的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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){
XposedBridge.log("未找到类" + e.toString());
return;
}

XposedHelpers.findAndHookMethod(hookclass, "方法名", new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
//do something
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
//do something
}
});
}
});

遍历所有类下的所有方法

首先需要 Hook loadClass() 方法获取反射得到的类,然后通过类的 getDeclaredMethods() 方法获取到类中的所有函数,接着就可以对目标函数进行 Hook。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
XposedHelpers.findAndHookMethod(ClassLoader.class, "loadClass", String.class, new XC_MethodHook() {  
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
//获取反射得到的类
Class clazz = (Class) param.getResult();
String clazzName = clazz.getName();
//如果类名是目标类
if(clazzName.contains("类名")){
Method[] mds = clazz.getDeclaredMethods();
for(int i =0;i<mds.length;i++){
//获取方法的修饰符(pulic, static, final等)
int mod = mds[i].getModifiers();
//去除抽象、native、接口方法
if(!Modifier.isAbstract(mod) && !Modifier.isNative(mod) &&!Modifier.isAbstract(mod)){
XposedBridge.hookMethod(mds[i], new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
//do something
}
});
}

}
}

}
});

日志输出

1
2
XposedBridge.log("message");
XposedBridge.log(new Exception());//打印堆栈

检测和绕过

检测

Java层检测:
(1)通过PackageManager查看安装列表过滤
(2)自造异常读取堆栈
(3)检查关键字Java方法变成NativeJNI方法
(4)反射读取XposedHelper类字段
(5)检测方法是否被算改
(6)检测包名
native层检测:
(1)解析/proc/self/maps,搜检App自身加载的库中是否存在XposedBridge.jar、相关的Dex、Jar和so库等
(2)XposedCheck的实现参考

绕过

(1)Hook绕过
绕过jar Class检测
绕过堆栈检测
绕过包名检测
绕过jar文件检测
绕过maps检测
绕过vxp检测
绕过S0检测
绕过classPath检测
检测缓存
(2)定制源码绕过
修改Xposed源码特征
XposedInstaller、XposedBridge、Xposed、XposedTools全部修改指纹

评论