在Android领域,XPosed框架被广泛认可为一种强大的工具,能够突破Android系统的限制,提供无与伦比的自定义能力。本文将介绍XPosed框架的技术原理和应用。
XPosed框架基于Android系统的运行时替换(Runtime Replacement)机制,通过在系统层注入代码来修改应用程序的行为。它通过在应用程序进程中加载一个特殊的类加载器,拦截并修改应用程序和系统的方法调用。
XPosed框架使用Xposed API与开发者的模块建立连接,它提供了一系列的接口和工具,帮助开发者编写自己的XPosed模块。开发者可以使用Java语言编写模块,通过hook的方式拦截和修改目标应用程序的方法调用。通过这种方式,开发者可以实现诸如修改应用程序的界面、增强应用程序的功能、禁用广告等功能
01
XPosed框架的应用场景
主题定制:XPosed框架可以实现更改系统UI主题、图标、字体等,让用户个性化定制Android设备的外观。
功能增强:通过XPosed框架,开发者可以对应用程序进行功能增强,如增加快捷操作、自定义通知等,提升用户体验。
广告屏蔽:XPosed模块可以拦截应用程序的广告请求,从而实现广告屏蔽的效果,让用户享受无广告的应用体验。
隐私保护:XPosed模块可以监控应用程序的数据流量和权限请求,并根据用户设定的规则进行拦截和过滤,保护用户的隐私安全。
02
XPosed安装步骤
从https://xposed-installer.com/官网下载的XPosed APP已经无法安装XPosed框架,APP中的XPosed框架下载地址已经无法访问。可以通过https://github.com/KhanhNguyen9872/xposed/tree/main 下载经过修改的APP,它可以通过github渠道下载框架核心,并且提供了框架核心:full.xml.gz。
安装后显示Xposed框架未安装,按照Android系统架构在下方选择合适的框架。
选择Install安装。
如果显示下图这样表示安装的系统架构没选对。
重启Android系统,显示框架已激活就表示安装成功了。
之后需要通过Xposed Bridge API来开发Xposed模块。
03
Xposed模块开发
首先需要安装好Android Studio 与Android SDK。
随后新建一个标准的Android项目:
选择java代码,并自定义项目名称与存储位置:
等待Android gradle同步完成。如果同步失败需要挂梯子后重试。
由于接下来编写的不是普通的应用程序,而是一个xposed插件,所以需要对app的manifest文件做一些特殊的修改。
正如wiki上所说,我们需要添加三个meta-data来标识app为xposed插件:
https://github.com/rovo89/XposedBridge/wiki/Development-tutorial
<meta-data
android:name=
"xposedmodule"
android:value=
"true"
/>
<meta-data
android:name=
"xposeddescription"
android:value=
"Your module description"
/>
<meta-data
android:name=
"xposedminversion"
android:value=
"53"
/>
除此之外还需要导入api才能使用xposed的所有功能:
对于Android studio可以通过gradle导入。参考wiki:https://github.com/rovo89/XposedBridge/wiki/Using-the-Xposed-Framework-API
1. 在settings.gradle文件中添加仓库jcenter()
2.其次在app:build.gradle中添加implementation 'de.robv.android.xposed:api:82'
3.同步gradle:
此时Hook环境就已经搭建好了,接下来完成一些简单的模块来演示xposed的用法。
04
xposed用法演示
首先,我们有三个hook点可以使用:
分别是:
-
IXposedHookInitPackageResources:用来劫持资源包 可以用来替换资源包。
-
IXposedHookLoadPackage:用来劫持应用程序启动,这也是最常用的Hook点
-
IXposedHookZygoteInit:用来劫持孵化器的启动,可以获得更早的控制权来进行系统级Hook操作
HOOK DexClassLoader
之前有介绍模拟加壳程序的dex加载器动态加载dex。现在通过xposed来尝试模拟脱壳程序来提取dex。在这之前需要找一个比较不错的hook点,这样才能将dex给劫持下来。所以需要先了解DexClassLoader在底层是怎么实现的。
有一个网站可以很方便的查看Android核心代码:https://cs.android.com/android/platform/superproject/main/+/main:libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java;l=55?q=DexClassLoader
可以看到DexClassLoader的实现:
super就来到了BaseDexClassLoader:
经过几个重载的跳转来到这里,dexPath被传入了DexPathList构造来初始化pathList参数,先继续追踪DexPathList:
dexPath被按照路径分隔符切割成多个文件后传入了makeDexElements:
跟入makeDexElements,将目录直接传入elements,如果是文件则调用loadDexFile加载,并且会得到一个DexFile:
继续跟踪loadDexFile,调用 new DexFile或者DexFile.loadDex。实际上DexFile.loadDex也调用了new DexFile:
最后调用到 openDexFile:
随后进入native:
这里演示从这个方法进行hook来提取dex 文件。
安装之前的DexLoader应用程序,并启动。
停留在此界面后通过adb shell 进入命令行并输入am stack list查看当前窗口栈,可以得到顶层应用程序的包名和Activity名。
随后实现IXposedHookLoadPackage来劫持这个应用:
通过handleLoadPackage来处理所有应用程序的加载,并通过lpparam.packageName过滤掉除dexloader以外的应用程序。
public
class
HookImpl
implements
IXposedHookLoadPackage
{
@
Override
public
void
handleLoadPackage
(XC_LoadPackage.LoadPackageParam lpparam)
throws Throwable
{
if
(!lpparam.packageName.equals(
"com.example.dexloader"
))
return
;
}
}
然后通过XposedHelpers.findAndHookMethod()来Hook方法,首先需要了解这个方法的原型,然后通过这个方法来hook DexClassLoader。
XposedHelpers.findAndHookMethod有两个重载:
printf
(
"hello world!"
);
public
static
XC_MethodHook.
Unhook
findAndHookMethod
(Class<?> clazz, String methodName, Object... parameterTypesAndCallback)
public
static
XC_MethodHook.Unhook
findAndHookMethod
(String className, ClassLoader classLoader, String methodName, Object... parameterTypesAndCallback)
一个使用类名+类加载器,一个使用类对象。
第一个参数为将要hook的类,第二个参数是方法名,在其之后的所有参数除了最后一个以外都是这个方法的参数类型实例,排在最后一个的参数必须是一个可用的回调类,如XC_MethodHook,XC_MethodReplacement。
XC_MethodHook中包含afterHookedMethod与beforeHookedMethod方法需要重写,一个是在调用前执行,一个是之后执行。
还有一个XC_MethodReplacement,通过replaceHookedMethod可以直接重写原来的方法。
知道了所有参数的含义后我们可以开始编写如下代码。
首先我们通过源码知道所属包路径:dalvik.system,然后是类名DexFile。那么第一个参数为dalvik.system.DexFile;第二个参数可以直接使用lpparam的类加载器;第三个参数是需要hook的方法名openDexFileNative。
其次是方法的参数列表:String.class, String.class, int.class, ClassLoader.class, DexPathList.Element[];由于DexPathList没有在Android sdk中,所以需要通过反射才能获取。
在反射时它的名字为dalvik.system.DexPathList$Element,获得它的数组的类:Array.newInstance(dexPathList, 0).getClass()。
参数补齐后,最后一个参数填写hook代码:使用afterHookedMethod在方法被调用之后执行一个打印操作,把调用的第一个参数数值打印到logcat上。
public
class
HookImpl
implements
IXposedHookLoadPackage
{
@
Override
public
void
handleLoadPackage
(XC_LoadPackage.LoadPackageParam lpparam)
throws Throwable
{
if
(!lpparam.packageName.equals(
"com.example.dexloader"
))
return
;
Class<?> dexPathList = lpparam.classLoader.loadClass(
"dalvik.system.DexPathList$Element"
);
Class<?> dexPathListArrayType = Array.newInstance(dexPathList,
0
).getClass();
Log.e(
"HOOK"
, lpparam.packageName);
XposedHelpers.findAndHookMethod(
"dalvik.system.DexFile"
,
lpparam.classLoader,
"openDexFileNative"
,
String.class, String.class,
int
.class, ClassLoader.class,dexPathListArrayType,
new
XC_MethodHook(){
@Override
protected
void
afterHookedMethod(MethodHookParam param) throws Throwable {
Log.e(
"HOOK"
,
"afterHookedMethod:"
+ param.args[
0
]);
}
}
);
}
}
已经能成功打印出dex了,剩下的就是将这个dex取出来。
写一个文件拷贝的代码,将dex导出到模拟器外部:
@
Override
protected
void
afterHookedMethod
(MethodHookParam param)
throws Throwable
{
Log.e(
"HOOK"
,
"afterHookedMethod:"
+ param.args[
0
]);
try
(FileOutputStream outFile =
new
FileOutputStream(
"/mnt/shared/Pictures/out.dex"
))
{
try
(FileInputStream dexFile =
new
FileInputStream((String)param.args[
0
])){
byte[] buf =
new
byte[
1024
];
while
(
true
) {
int
readCount = dexFile.read(buf);
if
(readCount <=
0
)
break
;
outFile.write(buf,
0
, readCount);
}
}
}
}
用jadx查看代码发现dex完整导出了:
本文作为一个xposed入门教程,介绍了什么是xposed,xposed的安装使用以及如何通过Xposed来完成一些实际问题。比如模拟通过hook dex加载器来模拟脱壳提取dex。在示例中了解到如何找到一个好的hook点,如何hook一个方法,使用什么方式hook。
原文始发于微信公众号(锋刃科技):XPosed 介绍与实践
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论