欢迎光临本站!

App免Root加载Xposed插件工具Xpatch源码解析

来源:原创    更新时间:2019-08-25 20:17:27    编辑:星谷下载    浏览:422

Xpatch是笔者开发的一款破解Android App工具,使用Xpatch破解后的App可以加载安装在设备上的Xposed插件,从而实现了免Root也能Xposed。

App免Root加载Xposed插件工具Xpatch源码解析

本文是Xpatch源码解析的第二篇,第一篇源码分析请参阅个人技术〉〉〉www.xingguxiazai.com


查找插件Apk



加载Xposed插件之前,首先需要遍历所有安装的应用,根据Xposed插件的特征,找到其中的Xposed插件。

 

那什么样的应用才是Xposed插件呢?


根据Xposed插件的书写规范中要求,插件Apk的Manifest文件中需要包含android:name="xposedmodule"这样的meta-data信息:


<application
<meta-data
android:name="xposedmodule"
android:value="true"/>
</application>


根据此特征,我们获取App PackageInfo中的meta data,从而过滤出插件Apk,具体实现源码如下:


private static List<String> loadAllInstalledModule(Context context) {
PackageManager pm = context.getPackageManager();
List<String> modulePathList = new ArrayList<>();
// modulePathList.add("mnt/sdcard/app-debug.apk");

List<String> packageNameList = loadPackageNameListFromFile(true);
List<Pair<String, String>> installedModuleList = new ArrayList<>();

boolean configFileExist = configFileExist();

for (PackageInfo pkg : pm.getInstalledPackages(PackageManager.GET_META_DATA)) {
ApplicationInfo app = pkg.applicationInfo;
if (!app.enabled)
continue;
if (app.metaData != null && app.metaData.containsKey("xposedmodule")) {
String apkPath = pkg.applicationInfo.publicSourceDir;
String apkName = context.getPackageManager().getApplicationLabel(pkg.applicationInfo).toString();
if (TextUtils.isEmpty(apkPath)) {
apkPath = pkg.applicationInfo.sourceDir;
}
if (!TextUtils.isEmpty(apkPath) && (!configFileExist || packageNameList == null || packageNameList
.contains(app.packageName))) {
XLog.d(TAG, " query installed module path -> " + apkPath);
modulePathList.add(apkPath);
}
installedModuleList.add(Pair.create(pkg.applicationInfo.packageName, apkName));
}
}

final List<Pair<String, String>> installedModuleListFinal = installedModuleList;

// ...
// ...
return modulePathList;
}



>>>>

加载插件Apk


找到了插件Apk之后,就可以得到此Apk的路径(data/app/包名 目录下面),然后就是根据此路径加载插件。


加载插件的方法是:

com.wind.xposed.entry.XposedModuleLoader.loadModule()


其主要流程参考了原版Xposed框架中的实现,过程如下:

1. 根据插件Apk文件路径构造DexClassLoader

2. 读取Apk asset目录下''assets/xposed_init'文件中所有的类名;

3. 根据类名和Classloader构造入口类,并执行类的入口方法handleLoadPackage

流程源码和注释:


public static int loadModule(final String moduleApkPath, String moduleOdexDir, String moduleLibPath,
final ApplicationInfo currentApplicationInfo, ClassLoader appClassLoader) {
// ...

// 创建DexClassLoader
ClassLoader mcl = new DexClassLoader(moduleApkPath, moduleOdexDir, moduleLibPath, appClassLoader);
// 读取asset目录中文件里写入的所有类名
InputStream is = mcl.getResourceAsStream("assets/xposed_init");
// ...

BufferedReader moduleClassesReader = new BufferedReader(new InputStreamReader(is));
try {
String moduleClassName;
while ((moduleClassName = moduleClassesReader.readLine()) != null) {
moduleClassName = moduleClassName.trim();
if (moduleClassName.isEmpty() || moduleClassName.startsWith("#"))
continue;

try {
XLog.i(TAG, " Loading class " + moduleClassName);
// 构造对象
Class<?> moduleClass = mcl.loadClass(moduleClassName);

if (!XposedHelper.isIXposedMod(moduleClass)) {
Log.i(TAG, " This class doesn't implement any sub-interface of IXposedMod, skipping it");
continue;
} else if                                                                      
Log.i(TAG, " This class requires resource-related hooks (which are disabled), skipping it.");
continue;
}

final Object moduleInstance = moduleClass.newInstance();
if (moduleInstance instanceof IXposedHookZygoteInit) {
XposedHelper.callInitZygote(moduleApkPath, moduleInstance);
}

// 执行对象中的`handleLoadPackage`入口方法,实现hook流程
if (moduleInstance instanceof IXposedHookLoadPackage) {
// hookLoadPackage(new IXposedHookLoadPackage.Wrapper((IXposedHookLoadPackage) moduleInstance));
IXposedHookLoadPackage.Wrapper wrapper = new IXposedHookLoadPackage.Wrapper((IXposedHookLoadPackage) moduleInstance);
XposedBridge.CopyOnWriteSortedSet<XC_LoadPackage> xc_loadPackageCopyOnWriteSortedSet = new XposedBridge.CopyOnWriteSortedSet<>();
xc_loadPackageCopyOnWriteSortedSet.add(wrapper);
XC_LoadPackage.LoadPackageParam lpparam = new XC_LoadPackage.LoadPackageParam(xc_loadPackageCopyOnWriteSortedSet);
lpparam.packageName = currentApplicationInfo.packageName;
lpparam.processName = currentApplicationInfo.processName;
lpparam.classLoader = appClassLoader;
lpparam.appInfo = currentApplicationInfo;
lpparam.isFirstApplication = true;
XC_LoadPackage.callAll(lpparam);
}
} catch (Throwable t) {
}
}
} catch (IOException e) {
} finally {
}




Apk中注入代码的实现


往Apk中注入代码,一般来说,有两种主流方法:


1. 最常用的方法,使用ApkTool将Apk反编译为smali代码,修改smali文件,然后再将修改后的文件使用ApkTool打包,从而实现代码的修改;

2. 修改dex2jar工程源码,使得在dex转换为jar过程中能够插入java代码,然后再使用jar2dex工具将修改后的jar转换为dex文件,从而实现代码修改和回编。

这里,我们选取了第二种方法。第二种方法的难点是如何修改dex2jar工程源码实现代码的插入。
 
为此,需要先分析其实现原理。

Claud大神开源的dex2jar工具大致原理是,先根据dex文件格式规则解析dex文件中的所有类信息,然后再利用ASM工具根据这些信息生成Class文件。
 

对Java开发比较熟悉的人,应该很熟悉ASM。ASM是一个Java字节码操作框架。它可以直接对class文件进行增删改的操作,能被用来动态生成类或者增强既有类的功能。


Java中许多的框架的实现是基于ASM,比如Java AOP的实现,JavaWeb开发中的Spring框架的实现等等。可以说ASM就是一把利剑,是深入Java必须学习的一个点。

 

这里,我们就不讲解ASM的原理和用法,只讲解如何利用ASM修改dex2jar工程源码,从而实现代码的注入。



>>>>

ASM代码生成


在上一篇源码解析文章中,我们说过,破解Apk,只需要在其Application类中注入这样一段静态代码块:


package com.test;
import android.app.Application;
import com.wind.xposed.entry.XposedModuleEntry;

public class MyApplication extends Application {
static {
XposedModuleEntry.init();
}
}


那这样的一段代码,如何用ASM工具生成呢?


假如对ASM的API熟悉的话,其实很容易就能实现这样一小段代码的生成。


假如不熟悉的话,也没关系,我们可以利用Android Studio中的一个插件,查看这段代码的ASM的实现。这个插件的名字是:ASM Bytecode Viewer

 

通过这个插件,我们可以清晰的看到生成这段代码的ASM代码的实现:


public class MyApplicationDump implements Opcodes {

public static byte[] dump() throws Exception {

ClassWriter cw = new ClassWriter(0);
FieldVisitor fv;
MethodVisitor mv;
AnnotationVisitor av0;

cw.visit(V1_7, ACC_PUBLIC + ACC_SUPER, "com/test/MyApplication", null, "android/app/Application", null);

cw.visitSource("MyApplication.java", null);

{
mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitLineNumber(7, l0);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "android/app/Application", "<init>", "()V", false);
mv.visitInsn(RETURN);
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitLocalVariable("this", "Lcom/test/MyApplication;", null, l0, l1, 0);
mv.visitMaxs(1, 1);
mv.visitEnd();
}
{
mv = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitLineNumber(11, l0);
mv.visitMethodInsn(INVOKESTATIC, "com/wind/xposed/entry/XposedModuleEntry", "init", "()V", false);
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitLineNumber(12, l1);
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
cw.visitEnd();

return cw.toByteArray();
}
}


这段代码中,第一个花括号中代码用来生成这个类的默认构造方法,第二个花括号中是用来生成静态代码块方法,去掉生成标签行数等无关代码后,最终需要的代码仅仅是:


mv = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
mv.visitCode();
mv.visitMethodInsn(INVOKESTATIC, "com/wind/xposed/entry/XposedModuleEntry", "init", "()V", false);
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();


下面再分析如何将这段ASM代码加到dex2jar工程中,从而实现代码植入。



>>>>

修改dex2jar源码


通过不断调试dex2jar源码,我们可以找到使用ASM生成字节码的代码位置,在Dex2jar.java文件的doTranslate ()方法中:


// dex2jar项目源码
// com.googlecode.d2j.dex.Dex2jar.java
private void doTranslate(final Path dist) throws IOException {
// ...
new ExDex2Asm(exceptionHandler) {
public void convertCode(DexMethodNode methodNode, MethodVisitor mv) {
if ((readerConfig & DexFileReader.SKIP_CODE) != 0 && methodNode.method.getName().equals("<clinit>")) {
// also skip clinit
return;
}
super.convertCode(methodNode, mv);
}
@Override
public void optimize(IrMethod irMethod) {
// ...
// ...
}
@Override
public void ir2j(IrMethod irMethod, MethodVisitor mv) {
new IR2JConverter(0 != (V3.OPTIMIZE_SYNCHRONIZED & v3Config)).convert(irMethod, mv);
}
}.convertDex(fileNode, cvf);
// ...
}


ExDex2Asm方法convertCode是其父类中对外暴露的方法,用于处理每个方法生成。


在这里,我们可以判断当前类是不是应用的Application类,以及方法是不是静态代码块方法<clinit>。


是的话,通过visitMethodInsn加上XposedModuleEntry.init();方法,代码如下:


if (methodNode.method.getOwner().equals(applicationName) && methodNode.method.getName().equals("<clinit>")) {
isApplicationClassFounded = true;
mv.visitMethodInsn(Opcodes.INVOKESTATIC, XPOSED_ENTRY_CLASS_NAME, "init", "()V", false);
}


还有另外一种情形,也需要处理,就是当前应用自定义的Application类没有方法静态方法块的情形。


对于这种情形的处理,仅修改ExDex2Asm类中的代码,显然无法实现。我们需要在其父类Dex2Asm中增加一个非私有的空方法,暴露给子类ExDex2Asm。

评论区

表情

共0条评论
  • 这篇文章还没有收到评论,赶紧来抢沙发吧~

相关内容

点击排行

随机新闻

评论排行榜