1. Xpatch概述

Xpatch是一款利用重打包的方式,使得被处理的Apk启动时自动加载Xposed模块,来实现应用内Hook的工具。

项目地址:https://github.com/WindySha/Xpatch

2. Xpatch处理apk分析

Xpatch修改apk,主要有三个步骤,代码在MainCommand类的doCommandLine方法:

protected void doCommandLine() {
......
if (!disableCrackSignature) {
// save the apk original signature info, to support crach signature.
new SaveApkSignatureTask(apkPath, unzipApkFilePath).run();
}
FileUtils.decompressZip(apkPath, unzipApkFilePath);
......
// 1. modify the apk dex file to make xposed can run in it
mXpatchTasks.add(new ApkModifyTask(showAllLogs, keepBuildFiles, unzipApkFilePath, applicationName,
dexFileCount)); // 2. copy xposed so and dex files into the unzipped apk
mXpatchTasks.add(new SoAndDexCopyTask(dexFileCount, unzipApkFilePath, getXposedModules(xposedModules))); // 3. compress all files into an apk and then sign it.
mXpatchTasks.add(new BuildAndSignApkTask(keepBuildFiles, unzipApkFilePath, output));
......
for (Runnable executor : mXpatchTasks) {
executor.run();
}
......
}

(1) 第一步

在Xpatch的源码中,第一步对应的是ApkModifyTask类,实现的是Runnable接口,它的任务是修改Dex文件,使得被处理的apk在启动时能够执行指定的代码。

如果反编译被Xpatch处理过的Apk,查看App中Application的子类,会发现其中多了以下的代码:

static {
XposedModuleEntry.init();
}

我们大胆的猜测,这就是Xpatch给注入进去的入口代码。我们回到Xpatch的源码,来看看它是如何注入的。查看ApkModifyTask类,一步步进行跟踪:

ApkModifyTask类的run方法,在任务被启动时调用,它的代码:

public void run() {
......
String targetDexFileName = dumpJarFile(dexFileCount, unzipApkFilePath, jarOutputPath, applicationName);
......
}

dumpJarFile方法:

private String dumpJarFile(int dexFileCount, String dexFilePath, String jarOutputPath, String applicationName) {
......
boolean isApplicationClassFound = dex2JarCmd(filePath, jarOutputPath, applicationName);
......
}

继续跟踪到dex2JarCmd方法:

private boolean dex2JarCmd(String dexPath, String jarOutputPath, String applicationName) {
Dex2jarCmd cmd = new Dex2jarCmd();
String[] args = new String[]{
dexPath,
"-o",
jarOutputPath,
"-app",
applicationName,
"--force"
};
cmd.doMain(args); boolean isApplicationClassFounded = cmd.isApplicationClassFounded();
if (showAllLogs) {
System.out.println("isApplicationClassFounded -> " + isApplicationClassFounded + "the dexPath is " +
dexPath);
}
return isApplicationClassFounded;
}

看到了它创建了一个com.googlecode.dex2jar.tools.Dex2jarCmd类实例,这个类在名为dex-tools的外部库里,并调用了Dex2jarCmd的doMain方法,给他传进去一些类似于命令行参数的东西,令我们比较提得起精神的是-app参数,它传进去一个applicationName,这个applicationName的值来自MainCommand类的doCommandLine方法,逻辑是从解压的apk中读取AndroidManifest.xml,并读取application节点下的name属性的值,最后将值赋予applicatioName

protected void doCommandLine() {
......
ManifestParser.Pair pair = ManifestParser.parseManifestFile(manifestFilePath);
String applicationName;
if (pair != null && pair.applicationName != null) {
applicationName = pair.applicationName;
} else {
System.out.println(" Application name not found error !!!!!! ");
applicationName = DEFAULT_APPLICATION_NAME;
}
......
}

exm?就这样?并没有发现任何注入的代码啊,不急,继续跟踪,看到applicationName传进去了,一定能跟踪到有用的信息。接下来就是进入dex-tools外部库了,代码都是反编译出来的

com.googlecode.dex2jar.tools.BaseCmd的doMain方法:

public void doMain(String... args) {
try {
this.initOptions();
this.parseSetArgs(args);
this.doCommandLine();
} catch (BaseCmd.HelpException var4) {
String msg = var4.getMessage();
if (msg != null && msg.length() > 0) {
System.err.println("ERROR: " + msg);
}
this.usage();
} catch (Exception var5) {
var5.printStackTrace(System.err);
}
}

主要看doCommandLine方法,doCommandLine是个抽象方法,它的真正实现是在Dex2jarCmd类里

protected void doCommandLine() throws Exception {
......
for(var4 = 0; var4 < var3; ++var4) {
......
BaseDexFileReader reader = MultiDexFileReader.open(Files.readAllBytes((new File(fileName)).toPath()));
BaksmaliBaseDexExceptionHandler handler = this.notHandleException ? null : new BaksmaliBaseDexExceptionHandler();
this.dex2jar = Dex2jar.from(reader);
this.dex2jar.withExceptionHandler(handler)
.reUseReg(this.reuseReg)
.topoLogicalSort()
.skipDebug(!this.debugInfo)
.optimizeSynchronized(this.optmizeSynchronized)
.printIR(this.printIR)
.noCode(this.noCode)
.skipExceptions(this.skipExceptions)
.setApplicationName(this.applicationName)
.to(file);
......
}
}

跳转到com.googlecode.d2j.dex.Dex2jar类的to方法

public void to(Path file) throws IOException {
if (Files.exists(file, new LinkOption[0]) && Files.isDirectory(file, new LinkOption[0])) {
this.doTranslate(file);
} else {
FileSystem fs = createZip(file);
Throwable var3 = null; try {
this.doTranslate(fs.getPath("/"));
} catch (Throwable var12) {
var3 = var12;
throw var12;
} finally {
if (fs != null) {
if (var3 != null) {
try {
fs.close();
} catch (Throwable var11) {
var3.addSuppressed(var11);
}
} else {
fs.close();
}
} }
} }

to方法调用doTranslate方法

private void doTranslate(final Path dist) throws IOException {

        ......
(new ExDex2Asm(this.exceptionHandler) {
public void convertCode(DexMethodNode methodNode, MethodVisitor mv) {
if (methodNode.method.getOwner().equals(Dex2jar.this.applicationName) && methodNode.method.getName().equals("<clinit>")) {
Dex2jar.this.isApplicationClassFounded = true;
mv.visitMethodInsn(184, "com/wind/xposed/entry/XposedModuleEntry", "init", "()V", false);
} if ((Dex2jar.this.readerConfig & 4) == 0 || !methodNode.method.getName().equals("<clinit>")) {
super.convertCode(methodNode, mv);
}
} public void addMethod(DexClassNode classNode, ClassVisitor cv) {
if (classNode.className.equals(Dex2jar.this.applicationName)) {
Dex2jar.this.isApplicationClassFounded = true;
boolean hasFoundClinitMethod = false;
if (classNode.methods != null) {
Iterator var4 = classNode.methods.iterator(); while(var4.hasNext()) {
DexMethodNode methodNode = (DexMethodNode)var4.next();
if (methodNode.method.getName().equals("<clinit>")) {
hasFoundClinitMethod = true;
break;
}
}
} if (!hasFoundClinitMethod) {
MethodVisitor mv = cv.visitMethod(8, "<clinit>", "()V", (String)null, (String[])null);
mv.visitCode();
mv.visitMethodInsn(184, "com/wind/xposed/entry/XposedModuleEntry", "init", "()V", false);
mv.visitInsn(177);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
} } ......
}).convertDex(fileNode, cvf);
}

doTranslate方法很长,但是我们很容易就能看到了很敏感的字符串:com/wind/xposed/entry/XposedModuleEntry,这就是Xpatch插入自己初始化的代码的地方。visitMethodInsn方法用于在函数内插入一条指令,看到两处调用visitMethodInsn来插入调用 com.wind.xposed.entry.XposedModuleEntry类的init方法的指令。

  • convertCode函数中的visitMethodInsn,逻辑是如果要处理的Application类中存在clinit方法,即存在静态代码段,就直接插入调用 com.wind.xposed.entry.XposedModuleEntry类的init方法的指令
  • addMethod函数中的visitMethodInsn,如果要处理的Application类中不存在clinit方法,即不存在静态代码段,就创建一个静态代码段,并在其中插入调用 com.wind.xposed.entry.XposedModuleEntry类的init方法的指令,最后返回void

注:上面的操作码,184代表invoke-static,177代表return-void。这些操作码定义在org.objectweb.asm.Opcodes类中。

到这里,第一步我们已经搞清楚了。

(2) 第二步

对应的是SoAndDexCopyTask类,从名字可以看出它的任务是复制so和dex的,具体是怎样的,我们看代码。

SoAndDexCopyTask类,它也实现了Runnable接口,run方法在任务被启动时调用:

@Override
public void run() {
copySoFile();
copyDexFile(dexFileCount);
deleteMetaInfo();
}

这个类主要就做这三个动作:复制so文件,复制dex文件,删除Meta信息。

我们先看copySoFile代码:

private void copySoFile() {
for (String libPath : APK_LIB_PATH_ARRAY) {
String apkSoFullPath = fullLibPath(libPath);
if(new File(apkSoFullPath).exists()) {
copyLibFile(apkSoFullPath, SO_FILE_PATH_MAP.get(libPath));
}
}
// copy xposed modules into the lib path
if (xposedModuleArray != null && xposedModuleArray.length > 0) {
int index = 0;
for (String modulePath : xposedModuleArray) {
modulePath = modulePath.trim();
if (modulePath == null || modulePath.length() == 0) {
continue;
}
File moduleFile = new File(modulePath);
if (!moduleFile.exists()) {
continue;
}
for (String libPath : APK_LIB_PATH_ARRAY) {
String apkSoFullPath = fullLibPath(libPath);
String outputModuleName= XPOSED_MODULE_FILE_NAME_PREFIX + index + SO_FILE_SUFFIX;
if(new File(apkSoFullPath).exists()) {
File outputModuleSoFile = new File(apkSoFullPath, outputModuleName);
FileUtils.copyFile(moduleFile, outputModuleSoFile);
} }
index++;
}
}
}

看代码可以知道它的任务是把Xpatch.jar中assets目录下的libxpatch_wl.so复制到apk解压目录的lib/<架构文件夹>下。这个libxpatch_wl.so是whale框架提供so文件,为Hook提供可能。

除了复制so,如果我们在用Xpatch时使用-xm参数来将Xposed模块集成到apk中,那么模块会被就会被重命名成:以libxpatch_xp_module_为前缀,后面接着模块序号,最后再以so为后缀。最终这个模块被复制到apk的lib目录下。

copyDexFile方法:

private void copyDexFile(int dexFileCount) {
String copiedDexFileName = "classes" + (dexFileCount + 1) + ".dex";
FileUtils.copyFileFromJar("assets/classes.dex", unzipApkFilePath + copiedDexFileName);
}

逻辑也很明了,把assets下的classes.dex复制到apk解压目录下,根据原来apk中的dex个数来给复制进去的dex重命名。

deleteMetaInfo方法:

private void deleteMetaInfo() {
String metaInfoFilePath = "META-INF";
File metaInfoFileRoot = new File(unzipApkFilePath + metaInfoFilePath);
if (!metaInfoFileRoot.exists()) {
return;
}
File[] childFileList = metaInfoFileRoot.listFiles();
if (childFileList == null || childFileList.length == 0) {
return;
}
for (File file : childFileList) {
String fileName = file.getName().toUpperCase();
if (fileName.endsWith(".MF") || fileName.endsWith(".RAS") || fileName.endsWith(".SF")) {
file.delete();
}
}
}

没什么好说的,就是删除<apk解压目录>/META-INF下的指定文件。

(3) 第三步

对应的是BuildAndSignApkTask类,从名字可以看出它的任务是构建和对apk签名的。

这个BuildAndSignApkTask类也是实现Runnable接口,我们来看run方法:

public void run() {
......
FileUtils.compressToZip(unzipApkFilePath, unsignedApkPath);
......
signApk(unsignedApkPath, keyStoreFilePath, signedApkPath, false);
......
}

这个方法做了两件重要的事,把apk解压目录给压缩成zip,并给压缩成的文件签名,这里就不细讲了。

3. 被集成进apk中的dex分析

我们在上面提到过,Xpatch把assets目录下的classes.dex文件复制进了目标apk里,这个dex是不开源的,那么这个dex里面究竟有什么呢,我们把dex解压出来,拖进jadx中反编译。

既然Xpatch将初始化代码注入到应用的Application类,初始化代码调用com.wind.xposed.entry.XposedModuleEntry类的init方法,那么我们从init方法开始看起。

public static void init() {
if (b.compareAndSet(false, true)) {
Context createAppContext = XpatchUtils.createAppContext();//1
if (createAppContext == null) {
Log.e(a, "try to init XposedModuleEntry, but create app context failed !!!!");
return;
}
d = createAppContext;
if (VERSION.SDK_INT > 21 && !FileUtils.isFilePermissionGranted(createAppContext)) {
Log.e(a, "File permission is not granted, can not control xposed module by file ->xposed_config/modules.list");
}
XposedHelper.initSeLinux(createAppContext.getApplicationInfo().processName);
SharedPrefUtils.init(createAppContext);
ClassLoader classLoader = createAppContext.getClassLoader();
b.a(createAppContext.getApplicationInfo(), classLoader);//2
List<String> arrayList = new ArrayList();
List<String> a = a(createAppContext);//3
a(createAppContext, (List) arrayList);//4
if (a.size() > 0) {
String a2;
String a3;
List list = null;
for (String a32 : arrayList) {
if (list == null) {
list = new ArrayList();
}
a2 = a(createAppContext, a32);
String str = a;
StringBuilder stringBuilder = new StringBuilder("Current packed module path ----> ");
stringBuilder.append(a32);
stringBuilder.append(" packageName = ");
stringBuilder.append(a2);
XLog.d(str, stringBuilder.toString());
list.add(a2);
}
if (list == null || list.size() == 0) {
arrayList.addAll(a);
} else {
for (String str2 : a) {
a32 = a(createAppContext, str2);
a2 = a;
StringBuilder stringBuilder2 = new StringBuilder("Current installed module path ----> ");
stringBuilder2.append(str2);
stringBuilder2.append(" packageName = ");
stringBuilder2.append(a32);
XLog.d(a2, stringBuilder2.toString());
if (!list.contains(a32)) {
arrayList.add(str2);
}
}
}
}
for (String str3 : arrayList) {
String absolutePath = createAppContext.getDir("xposed_plugin_dex", 0).getAbsolutePath();
if (!TextUtils.isEmpty(str3)) {
Log.d(a, "Current truely loaded module path ----> ".concat(String.valueOf(str3)));
b.a(str3, absolutePath, createAppContext.getApplicationInfo(), classLoader);//5
}
}
}
}

init方法代码比较多,上面标注释的地方是比较值得关注的,根据这些地方展开

注释1: 这里主要通过反射来创建Context,作为这么早执行的代码,作者也通过很巧妙的方式创建了Context,有了Context后,很多事就好办多了,XpatchUtils.createAppContext()的代码如下:

public static Context createAppContext() {
try {
Class cls = Class.forName("android.app.ActivityThread");
Method declaredMethod = cls.getDeclaredMethod("currentActivityThread", new Class[0]);
declaredMethod.setAccessible(true);
Object invoke = declaredMethod.invoke(null, new Object[0]);
Field declaredField = cls.getDeclaredField("mBoundApplication");
declaredField.setAccessible(true);
Object obj = declaredField.get(invoke);
Field declaredField2 = obj.getClass().getDeclaredField("info");
declaredField2.setAccessible(true);
obj = declaredField2.get(obj);
Method declaredMethod2 = Class.forName("android.app.ContextImpl").getDeclaredMethod("createAppContext", new Class[]{cls, obj.getClass()});
declaredMethod2.setAccessible(true);
Object invoke2 = declaredMethod2.invoke(null, new Object[]{invoke, obj});
if (invoke2 instanceof Context) {
return (Context) invoke2;
}
} catch (ClassNotFoundException | IllegalAccessException | NoSuchFieldException | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
return null;
}

注释2:调用com.wind.xposed.entry.b类的a方法,并将当前App的ApplicationInfo和ClassLoader传过去,从这里开始就开始碰到XposedBridge的代码了

public static void a(ApplicationInfo applicationInfo, ClassLoader classLoader) {
Wrapper wrapper = new Wrapper(a.a());
CopyOnWriteSortedSet copyOnWriteSortedSet = new CopyOnWriteSortedSet();
copyOnWriteSortedSet.add(wrapper);
LoadPackageParam loadPackageParam = new LoadPackageParam(copyOnWriteSortedSet);
loadPackageParam.packageName = applicationInfo.packageName;
loadPackageParam.processName = applicationInfo.processName;
loadPackageParam.classLoader = classLoader;
loadPackageParam.appInfo = applicationInfo;
loadPackageParam.isFirstApplication = true;
XCallback.callAll(loadPackageParam);
}

方法第一行把a.a()传给了Wrapper的构造函数,a类完整类名是com.wind.xposed.entry.a,该类实现IXposedHookLoadPackage接口,a静态方法返回a类实例,那么Wrapper的构造函数得到的就是IXposedHookLoadPackage接口的类实例。接着Wrapper类实例被添加到一个CopyOnWriteSortedSet中,这个CopyOnWriteSortedSet类是一个操作Object数组的类,CopyOnWriteSortedSet被传到LoadPackageParam类的构造函数中,调用这个构造函数就是在给它父类(Param类)中的callbacks字段赋值。

public static abstract class Param {
public final Object[] callbacks;
......
protected Param(CopyOnWriteSortedSet<? extends XCallback> copyOnWriteSortedSet) {
this.callbacks = copyOnWriteSortedSet.getSnapshot();
}
......
}

接下来就是给LoadPackageParam的字段赋值,这些字段存储着当前应用包名,进程名,ApplicationInfo,ClassLoader等等信息。

com.wind.xposed.entry.b.a(ApplicationInfo applicationInfo, ClassLoader classLoader)方法的最后,调用XCallback类的callAll方法

public static void callAll(Param param) {
if (param.callbacks != null) {
int i = 0;
while (true) {
Object[] objArr = param.callbacks;
if (i < objArr.length) {
try {
((XCallback) objArr[i]).call(param);
} catch (Throwable th) {
XposedBridge.log(th);
}
i++;
} else {
return;
}
}
}
......
}

callAll方法遍历Param类中的所有callback,调用它们的call方法

public void call(Param param) {
if (param instanceof LoadPackageParam) {
handleLoadPackage((LoadPackageParam) param);
}
}

饶了半天,就是调用传进Wrapper类构造函数的类的handleLoadPackage方法,那就是调用com.wind.xposed.entry.a类的handleLoadPackage方法,而com.wind.xposed.entry.a类的handleLoadPackage方法又去调用com.wind.xposed.entry.a.a类的handleLoadPackage方法,那我们去看com.wind.xposed.entry.a.a类的handleLoadPackage的实现

public final void handleLoadPackage(LoadPackageParam loadPackageParam) {
Context a = XposedModuleEntry.a();
String readTextFromAssets = FileUtils.readTextFromAssets(a, "xpatch_asset/original_signature_info.ini");
Log.d("PackageSignatureHooker", "Get the original signature --> ".concat(String.valueOf(readTextFromAssets)));
if (!(readTextFromAssets == null || readTextFromAssets.isEmpty())) {
try {
WhaleRuntime.reserved2();
Class cls = Class.forName("android.app.ActivityThread");
Object invoke = cls.getDeclaredMethod("currentActivityThread", new Class[0]).invoke(null, new Object[0]);
Method declaredMethod = cls.getDeclaredMethod("getPackageManager", new Class[0]);
declaredMethod.setAccessible(true);
Object invoke2 = declaredMethod.invoke(invoke, new Object[0]);
Object newProxyInstance = Proxy.newProxyInstance(Class.forName("android.content.pm.IPackageManager").getClassLoader(), new Class[]{r7}, new a(invoke2, loadPackageParam.packageName, readTextFromAssets));
Field declaredField = cls.getDeclaredField("sPackageManager");
declaredField.setAccessible(true);
declaredField.set(invoke, newProxyInstance);
PackageManager packageManager = a.getPackageManager();
declaredField = packageManager.getClass().getDeclaredField("mPM");
declaredField.setAccessible(true);
declaredField.set(packageManager, newProxyInstance);
} catch (Exception e) {
Log.e("PackageSignatureHooker", " hookSignatureByProxy failed !!", e);
}
}
}

这个方法的作用是Hook相关的函数,将被处理的apk的签名替换成原来的,防止某些App检测到自己的Apk被修改。apk在被Xpatch处理之前,签名的信息的被保存了下来,对应的任务类是SaveApkSignatureTask,上文没有讲到,感兴趣可以去看一下。

注释3:调用本类中的a方法,这个方法的参数只有一个参数Context

private static List<String> a(Context context) {
PackageManager packageManager = context.getPackageManager();
ArrayList arrayList = new ArrayList();
List a = a(true);
final ArrayList arrayList2 = new ArrayList();
boolean exists = new File(c, "xposed_config/modules.list").exists();
for (PackageInfo packageInfo : packageManager.getInstalledPackages(128)) {
ApplicationInfo applicationInfo = packageInfo.applicationInfo;
if (applicationInfo.enabled) {
Bundle bundle = applicationInfo.metaData;
if (bundle != null && bundle.containsKey("xposedmodule")) {
CharSequence charSequence = packageInfo.applicationInfo.publicSourceDir;
String charSequence2 = context.getPackageManager().getApplicationLabel(packageInfo.applicationInfo).toString();
if (TextUtils.isEmpty(charSequence)) {
charSequence = packageInfo.applicationInfo.sourceDir;
}
if (!TextUtils.isEmpty(charSequence) && (!exists || a == null || a.contains(applicationInfo.packageName))) {
XLog.d(a, " query installed module path -> ".concat(String.valueOf(charSequence)));
arrayList.add(charSequence);
}
arrayList2.add(Pair.create(packageInfo.applicationInfo.packageName, charSequence2));
}
}
}
new Thread(new Runnable() {
public final void run() {
List b = XposedModuleEntry.a(false);
if (b == null) {
b = new ArrayList();
}
List arrayList = new ArrayList();
for (Pair pair : arrayList2) {
if (!b.contains(pair.first)) {
XLog.d(XposedModuleEntry.a, " addPackageList packgagePair -> ".concat(String.valueOf(pair)));
arrayList.add(pair);
}
}
XposedModuleEntry.a(arrayList);
}
}).start();
return arrayList;
}

这个函数是读取设备中已安装的Apk,根据meta信息判断它们是否属于Xposed模块,如果是并且外部存储不存在xposed_config/modules.list把它们的安装位置添加到列表中。并且开启一个线程,如果xposed_config/modules.list存在则读取,xposed_config/modules.list文件记录着模块加载规则,具体可以去查看Xpatch项目的README。最后,将读取到的Xposed模块安装位置列表返回

注释4:调用本类中的a方法,这个方法的参数是一个Context和List

private static void a(Context context, List<String> list) {
String str = context.getApplicationInfo().nativeLibraryDir;
XLog.d(a, "Current loaded module libPath ----> ".concat(String.valueOf(str)));
File file = new File(str);
if (file.exists()) {
File[] listFiles = file.listFiles();
if (listFiles != null && listFiles.length > 0) {
for (File file2 : listFiles) {
if (file2.getName().startsWith("libxpatch_xp_module_")) {
XLog.d(a, "add xposed modules from libPath, this lib path is --> ".concat(String.valueOf(file2)));
list.add(file2.getAbsolutePath());
}
}
}
}
}

这个方法的目的是获取所有打包进apk中的Xposed模块的路径添加到传进来的List中

注释5:调用com.wind.xposed.entry.b类的a(String str, String str2, ApplicationInfo applicationInfo, ClassLoader classLoader)方法

public static int a(String str, String str2, ApplicationInfo applicationInfo, ClassLoader classLoader) {
XLog.i("XposedModuleLoader", "Loading modules from ".concat(String.valueOf(str)));
if (new File(str).exists()) {
DexClassLoader dexClassLoader = new DexClassLoader(str, str2, null, classLoader);
InputStream resourceAsStream = dexClassLoader.getResourceAsStream("assets/xposed_init");
if (resourceAsStream == null) {
Log.i("XposedModuleLoader", "assets/xposed_init not found in the APK");
return 4;
}
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(resourceAsStream));
while (true) {
try {
String readLine = bufferedReader.readLine();
if (readLine != null) {
readLine = readLine.trim();
if (!(readLine.isEmpty() || readLine.startsWith("#"))) {
try {
String str3;
XLog.i("XposedModuleLoader", " Loading class ".concat(String.valueOf(readLine)));
Class loadClass = dexClassLoader.loadClass(readLine);
if (!XposedHelper.isIXposedMod(loadClass)) {
readLine = "XposedModuleLoader";
str3 = " This class doesn't implement any sub-interface of IXposedMod, skipping it";
} else if (IXposedHookInitPackageResources.class.isAssignableFrom(loadClass)) {
readLine = "XposedModuleLoader";
str3 = " This class requires resource-related hooks (which are disabled), skipping it.";
} else {
Object newInstance = loadClass.newInstance();
if (newInstance instanceof IXposedHookZygoteInit) {
XposedHelper.callInitZygote(str, newInstance);
}
if (newInstance instanceof IXposedHookLoadPackage) {
Wrapper wrapper = new Wrapper((IXposedHookLoadPackage) newInstance);
CopyOnWriteSortedSet copyOnWriteSortedSet = new CopyOnWriteSortedSet();
copyOnWriteSortedSet.add(wrapper);
LoadPackageParam loadPackageParam = new LoadPackageParam(copyOnWriteSortedSet);
loadPackageParam.packageName = applicationInfo.packageName;
loadPackageParam.processName = applicationInfo.processName;
loadPackageParam.classLoader = classLoader;
loadPackageParam.appInfo = applicationInfo;
loadPackageParam.isFirstApplication = true;
XCallback.callAll(loadPackageParam);
}
try {
resourceAsStream.close();
} catch (IOException unused) {
}
return 8;
}
Log.i(readLine, str3);
} catch (Throwable th) {
Log.e("XposedModuleLoader", " error ", th);
}
}
}
} catch (IOException e) {
Log.e("XposedModuleLoader", " error ", e);
} catch (Throwable th2) {
try {
resourceAsStream.close();
} catch (IOException unused2) {
}
}
try {
resourceAsStream.close();
} catch (IOException unused3) {
}
return 16;
}
}
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(str);
stringBuilder.append(" does not exist");
Log.e("XposedModuleLoader", stringBuilder.toString());
return 2;
}

这个函数读取传进来的Xposed模块的信息,获取DexClassLoader,读取模块assets下的xposed_init文件,得到其中的类名并根据实例类型(IXposedHookZygoteInit或者IXposedHookLoadPackage)分别实例化它,是IXposedHookZygoteInit实例就callInitZygote,是IXposedHookLoadPackage实例就像上面的注释2所讲的一样调用模块的handleLoadPackage方法。

讲到这里好像并没有涉及到whale框架,我们编写模块的时候,Hook的代码都是写在handleLoadPackage方法中,比如我们在handleLoadPackage方法内,写个findAndHookMethod,最终就会调用WhaleRuntime.hookMethodNative本地方法,来实现应用内的Hook

4. 总结

Xpatch思路很好,不需要ROOT,不用担心Xposed在某些设备上的兼容性,不用每次调试Xposed模块都重启手机,很方便的就可以使用Xposed模块,实现应用内的Hook。但是在使用的过程中也发现了一个小问题,要处理的Apk如果没有手动继承Application类并在AndroidManifest.xml中指定,那么Xpatch就注入不了代码,也就无法正常使用。本文也只讲了Xpatch的基本流程,具体whale是怎么Hook的,能力有限,没能展开。

Xposed的新打开方式--Xpatch工作流程分析的更多相关文章

  1. 第2章 rsync算法原理和工作流程分析

    本文通过示例详细分析rsync算法原理和rsync的工作流程,是对rsync官方技术报告和官方推荐文章的解释. 以下是本文的姊妹篇: 1.rsync(一):基本命令和用法 2.rsync(二):ino ...

  2. rsync算法原理和工作流程分析

    本文通过示例详细分析rsync算法原理和rsync的工作流程,是对rsync官方技术报告和官方推荐文章的解释.本文不会介绍如何使用rsync命令(见rsync基本用法),而是详细解释它如何实现高效的增 ...

  3. Mysql工作流程分析

    Mysql工作流程图 工作流程分析 1. 所有的用户连接请求都先发往连接管理器 2. 连接管理器    (1)一直处于侦听状态    (2)用于侦听用户请求 3. 线程管理器    (1)因为每个用户 ...

  4. rsync(三)算法原理和工作流程分析

    在开始分析算法原理之前,简单说明下rsync的增量传输功能. 假设待传输文件为A,如果目标路径下没有文件A,则rsync会直接传输文件A,如果目标路径下已存在文件A,则发送端视情况决定是否要传输文件A ...

  5. Kafka工作流程分析

    Kafka工作流程分析 生产过程分析 写入方式 producer采用推(push)模式将消息发布到broker,每条消息都被追加(append)到分区(patition)中,属于顺序写磁盘(顺序写磁盘 ...

  6. [国嵌笔记][030][U-Boot工作流程分析]

    uboot工作流程分析 程序入口 1.打开顶层目录的Makefile,找到目标smdk2440_config的命令中的第三项(smdk2440) 2.进入目录board/samsung/smdk244 ...

  7. u-boot分析(二)----工作流程分析

    u-boot分析(二) 由于这两天家里有点事,所以耽误了点时间,没有按时更新,今天我首先要跟大家说说我对于u-boot分析的整体的思路,然后呢我以后的博客会按照这个内容更新,希望大家关注. 言归正传, ...

  8. Struts2的工作流程分析

    Struts2的工作流程分析 Posted on 2011-02-22 09:32 概述 本章讲述Struts2的工作原理. 读者如果曾经学习过Struts1.x或者有过Struts1.x的开发经验, ...

  9. Kafka之工作流程分析

    Kafka之工作流程分析 kafka核心组成 一.Kafka生产过程分析 1.1 写入方式 producer采用推(push)模式将消息发布到broker,每条消息都被追加(append)到分区(pa ...

随机推荐

  1. 【Android - 自定义View】之View的工作过程简介

    View的工作过程分为三个过程: View的measure过程: View的layout过程: View的draw过程. 我们知道,一个Activity就是一个窗口,这个窗口中包含一个Window.一 ...

  2. 【Android - 组件】之IntentFilter的匹配规则

    我们知道,Activity的启动模式分为两种,分别是显式启动和隐式启动.显式启动需要明确的指定被启动的对象的组件信息,包括包名和类名:而隐式启动需要 Intent 能够匹配目标组件的 IntentFi ...

  3. 【Android - 组件】之Activity生命周期的全面分析

    Activity是Android四大组件之首,其重要性不言而喻,Activity的生命周期更是我们了解Android工作机制的重中之重.我们一般将Activty的生命周期做两种情况下的理解,即正常情况 ...

  4. Spring Boot应用启动的三种方式

    Spring Boot应用HelloWorld的三种启动方式: 项目的创建可以在http://start.spring.io/网站中进行项目的创建. 首先项目结构: 1.  通过main方法的形式启动 ...

  5. jQuery九宫格抽奖

    <div id="box"> <div class="content content-1">1</div> <div ...

  6. [UWP]使用CompositionGeometricClip裁剪复杂图形及进行动画

    1. UWP中的其它裁剪方案 之前在 这篇文章 里,我介绍了如何使用UIElement.Clip裁剪UIElement的内容,使用代码如下: <Canvas> <Image Sour ...

  7. 华为“方舟编译器”到底是啥?一文看懂TA如何让手机性能再突破【华为云技术分享】

    版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/devcloud/article/detai ...

  8. DRF Django REST framework 之 解析器(二)

    引入 Django Rest framework帮助我们实现了处理application/json协议请求的数据,如果不使用DRF,直接从 request.body 里面拿到原始的客户端请求的字节数据 ...

  9. 浅议Grpc传输机制和WCF中的回调机制的代码迁移

    浅议Grpc传输机制和WCF中的回调机制的代码迁移 一.引子 如您所知,gRPC是目前比较常见的rpc框架,可以方便的作为服务与服务之间的通信基础设施,为构建微服务体系提供非常强有力的支持. 而基于. ...

  10. Python中 * 与 **, *args 与 **kwargs的用法

    * 用于传递位置参数(positional argument) ** 用于传递关键字参数(keyword argument) 首先,先通过一个简单的例子来介绍 * 的用法: def add_funct ...