This commit is contained in:
327135569 2021-03-29 01:47:42 +08:00
parent 7fd0ad99ac
commit aa5de4e166
14 changed files with 113 additions and 464 deletions

256
README.md
View File

@ -1,247 +1,69 @@
### [**English Version**](https://github.com/WindySha/Xpatch/blob/6ec0f3c16128dda46ab05bdd915d66ebbdaaf9fc/README_en.md) # What is MMPatch
# Android App破解工具Xpatch的使用方法 fork from [Xpatch][11]
## Xpatch概述 MMPatch is a jar tool which is used to repackage the apk file. Then, the new apk can load any Xposed modules installed in the android system.
Xpatch用来重新签名打包Apk文件使重打包后的Apk能加载安装在系统里的Xposed插件从而实现免Root Hook任意App。
## Xpatch基本原理 This is a way to use Xposed modules without root your device.
Xpatch的原理是对Apk文件进行二次打包重新签名并生成一个新的apk文件。
在Apk二次打包过程中插入加载Xposed插件的逻辑这样新的Apk文件就可以加载任意Xposed插件从而实现免Root Hook任意App的Java代码。
1.0~1.4版本Hook框架使用的是Lody的[whale](https://github.com/asLody/whale) It is easy way to modify one app using xposed module. And any apps changed by MMPatch can load every modules downloaded in the [Xposed Module Repository](https://repo.xposed.info/).
2.0版本开始Hook框架底层使用的是ganyao114的[SandHook](https://github.com/ganyao114/SandHook)。
3.0版本开始默认使用SandHook同时兼容切换为whale
## Xpatch工具包下载 # Benefits
[下载最新的Xpatch Jar包][1]
或者进入Releases页面下载指定版本[releases][2]
## Xpatch App版本(Xposed Tool)下载 1. Use xposed modules without your device;
[XposedTool][16] [下载XposedTool Apk][15] 2. Modify any apps without root your device.
## Xpatch使用方法 # How to use
Xpatch项目最终生成物是一个Jar包此Jar使用起来非常简单只需要一行命令一个接入xposed hook功能的apk就生成
1. Download the latest jar file from the [release page](https://github.com/327135569/MMPatch/releases);
2. Run this command in the Windows/Mac console:
``` ```
$ java -jar XpatchJar包路径 apk文件路径 $ java -jar mmpatch.jar source.apk
```
Then, a new apk named `source-xposed-signed.apk` in the same folder as `source.apk`.
For example: More command details can be found when no parameter is added, eg:
$ java -jar ../xpatch.jar ../Test.apk ```
$ java -jar mmpatch.jar
``` ```
这条命令之后在原apk文件(Test.apk)相同的文件夹中,会生成一个名称为`Test-xposed-signed.apk`的新apk这就是重新签名之后的支持xposed插件的apk。 # How to manage Xposed modules
**Note:** 由于签名与原签名不一致因此需要先卸载掉系统上已经安装的原apk才能安装这个Xpatch后的apk When the new apk is installed in the device, It will load all the Xposed modules installed in the device when it's process started.
当然,也可以增加`-o`参数指定新apk生成的路径 But you can manage the installed Xposed modules on/off state by a file in the storage.
``` The file path is `/sdcard/xpmodules.list`.
$ java -jar ../xpatch.jar ../Test.apk -o ../new-Test.apk
```
更多参数类型可以使用--help查看或者不输入任何参数运行jar包 When the new app started, it will search all the installed Xposed modules and write the the module app name and the module application name into this file. (`/sdcard/xpmodules.list`)
``` eg:
$ java -jar ../xpatch.jar --h(可省略)
```
这行命令之后得到结果(v1.0-v2.0)
```
Please choose one apk file you want to process.
options:
-f,--force force overwrite
-h,--help Print this help message
-k,--keep not delete the jar file that is changed by dex2jar
and the apk zip files
-l,--log show all the debug logs
-o,--output <out-apk-file> output .apk file, default is $source_apk_dir/[file
-name]-xposed-signed.apk
```
## Xposed模块开关控制的两种方法
### 1. 手动修改sdcard文件控制模块开关
当新apk安装到系统之后应用启动时默认会加载所有已安装的Xposed插件(Xposed Module)。
一般情况下Xposed插件中都会对包名过滤有些Xposed插件有界面并且在界面上可以设置开关所以默认启用所有的Xposed插件的方式大多数情形下都可行。
但在少数情况可能会出现问题比如同一个应用安装有多个Xposed插件wechat插件就非常多并且都没有独立的开关界面同时启用这些插件可能会产生冲突。
为了解决此问题当应用启动时会查找系统中所有已安装的Xposed插件并在文件目录下生成一个文件
`mnt/sdcard/xposed_config/modules.list`记录这些Xposed插件App。
比如:
``` ```
com.blanke.mdwechat#MDWechat com.blanke.mdwechat#MDWechat
com.example.wx_plug_in3#畅玩微信
liubaoyua.customtext#文本自定义 liubaoyua.customtext#文本自定义
``` ```
记录的方式是:`插件app包名#插件app名称` Each line of this file is Application Name#App Name.
You can disable a Xposed module by add `#` before the Application Name, eg:
需要禁用某个插件,只需要修改此文件,在该插件包名前面增加一个`#`号即可。
比如,需要禁用`畅玩微信`和`文本自定义`两个插件,只需要修改该文本文件,增加一个`#`号即可:
``` ```
com.blanke.mdwechat#MDWechat #com.blanke.mdwechat#MDWechat
#com.example.wx_plug_in3#畅玩微信 liubaoyua.customtext#文本自定义
```
This means the MDWechat Xposed module is disabled.
```
#com.blanke.mdwechat#MDWechat
#liubaoyua.customtext#文本自定义 #liubaoyua.customtext#文本自定义
``` ```
如果需要禁用所有插件,只需在所有的包名前面增加`#`。 This means all Xposed modules are disabled.
**注意:** Note: The target app must have file system access permission. Otherwise this file will not be created, and all xposed modules are enabled.
有些App没有获取到sd卡文件读写权限这会导致无法读取modules.list配置文件此时会默认启用所有插件。这种情况下需要手动打开app的文件读写权限。
### 2. 通过Xposed Tool App控制模块开关
下载并安装Xpatch AppXposed Tool
[点我下载XposedTool Apk][15]
通过`Xposed模块管理`页面来控制模块开关。原理跟方法1一致
![Screenshot.png](https://upload-images.jianshu.io/upload_images/1639238-84d7a1dd814f314a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300)
## 可用的Xposed模块示例
- [MDWechat][8]
- [文本自定义][9]
- [RemoveVideoAdsPlugin](https://github.com/WindySha/RemoveVideoAdsPlugin)
- **你自己编写的Xposed模块**
## 源码解析 # Thanks to
Xpatch源码解析博文已发布到个人技术公众号**Android葵花宝典**上。
扫一扫关注公众号,即可查看:
![](https://upload-images.jianshu.io/upload_images/1639238-ab6e0fceabfffdda.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/180)
## 其他
assets目录下的classes.dex是来加载设备上已安装的Xposed插件其源代码也已经开源
[xposed_module_loader](https://github.com/WindySha/xposed_module_loader)
欢迎star and fork.
## 局限性
Xpatch是基于apk二次打包实现的而且使用到了dex2Jar工具因此也存在不少的局限性。大概有以下几点
1. Hook框架默认使用的是SandHook此框架存在一些不稳定性在少数机型上hook可能会崩溃。
2. 对于校验了文件完整性的app重打包后可能无法启动
3. Xposed Hook框架暂时不支持Dalvik虚拟机。
4. 暂时不支持Xposed插件中的资源Hook。
## Technology Discussion
**QQ Group: 977513757**
or
**Post comments under this article: [Xpatch: 免Root实现App加载Xposed插件的一种方案](https://windysha.github.io/2019/04/18/Xpatch-%E5%85%8DRoot%E5%AE%9E%E7%8E%B0App%E5%8A%A0%E8%BD%BDXposed%E6%8F%92%E4%BB%B6%E7%9A%84%E4%B8%80%E7%A7%8D%E6%96%B9%E6%A1%88/)**
## 功能更新
----
### 1. 2019/4/15 updated
增加自动破解签名检验的功能,此功能默认开启,如果需要关闭可以增加`-c`即可,比如:
```
$ java -jar ../xpatch.jar ../Test.apk -c
```
通过help(-h)可以查看到:
>options:
> -c,--crach disable craching the apk's signature.
### 2. 2019/4/25 updated
增加将Xposed modules打包到apk中的功能
通过help(-h)可以查看到:
>-xm,--xposed-modules <arg> the xposed mpdule files to be packaged into the ap
> k, multi files should be seperated by :(mac) or ;(
> win)
使用方式为在命令后面增加`-xm path`即可,比如:
```
$ java -jar ../xpatch.jar ../source.apk -xm ../module1.apk
```
假如需要将多个Xposed插件打包进去在Mac中使用":"在Windows下使用";",隔开多个文件路径即可,比如:
```
mac
$ java -jar ../xpatch.jar ../source.apk -xm ../module1.apk:../module2.apk
windows
$ java -jar ../xpatch.jar ../source.apk -xm ../module1.apk;../module2.apk
```
**注意:**
1. 多个Xposed modules使用`:`(mac)/`;`(win)分割;
2. 假如此module既被打包到apk中又安装在设备上则只会加载打包到apk中的module不会加载安装的。
这里是通过包名区分是不是同一个module。
----
### 3. 2020/02/09 updated (Version 3.0)
3.0版本增加了不少新功能同时修复了一些用户反馈的Bug。
新增功能:
1. 支持android 10;
2. 支持更改植入的hook框架默认使用Sandhook(支持android10)可更改为whale(暂不支持android10)(-w);
3. 默认使用修改Maniest文件方式植入初始化代码同时支持更改为老版本中的修改dex文件的方式植入代码(-dex)
4. 支持修改apk包名一般不建议使用很多应用会校验包名会导致无法使用
5. 支持修改apk的version code;
6. 支持修改apk的version name;
7. 支持修改apk的debuggable为true或者false;
Bug修复
1. 修复Manifest文件中未定义ApplicationName类导致无法实现Hook的问题;
2. 修复破解无so文件的apk时出现无法找到so的问题;
3. 修复签名可能会失败的问题;
4. 修复dex文件数超过65536的问题
#### 新功能用法
在命令行中输入-h可以看到3.0版本完整的帮助文档:
`$ java -jar ../xpatch-3.0.jar -h`
```
options:
-c,--crach disable craching the apk's signature.
-d,--debuggable <0 or 1> set 1 to make the app debuggable = true, set 0 to
make the app debuggable = false
-dex,--dex insert code into the dex file, not modify manifest
application name attribute
-f,--force force overwrite
-h,--help Print this help message
-k,--keep not delete the jar file that is changed by dex2jar
and the apk zip files
-l,--log show all the debug logs
-o,--output <out-apk-file> output .apk file, default is $source_apk_dir/[file
-name]-xposed-signed.apk
-pkg,--packageName <new package name>modify the apk package name
-vc,--versionCode <new-version-code>set the app version code
-vn,--versionName <new-version-name>set the app version name
-w,--whale Change hook framework to Lody's whale
-xm,--xposed-modules <xposed module file path>
the xposed module files to be packaged into the ap
k, multi files should be seperated by :(mac) or ;(
win)
version: 3.0
```
具体用法:
1. 修改Apk的debuggable = true
`$ java -jar ../xpatch-3.0.jar ../Test.apk -d 1` false改为0
2. 使用老版本的破解dex方法破解apk
`$ java -jar ../xpatch-3.0.jar ../Test.apk -dex`
3. 修改包名,版本号:
`$ java -jar ../xpatch-3.0.jar ../Test.apk -pkg com.test.test -vc 1000 -vn 1.1.1`
2. 更改Hook框架为whale
`$ java -jar ../xpatch-3.0.jar ../Test.apk -w`
### 4. 2021/01/13 updated (Version 4.0)
Update SandHook to the newest version and SandHook support the Android 11.
## Thanks
- [Xpatch][11]
- [Xposed][10] - [Xposed][10]
- [whale][11]
- [dex2jar][12]
- [AXMLPrinter2][13] - [AXMLPrinter2][13]
- [SandHook](https://github.com/ganyao114/SandHook)
- [xposed_module_loader](https://github.com/WindySha/xposed_module_loader)
- [axml](https://github.com/Sable/axml)
[1]: https://github.com/WindySha/Xpatch/releases/download/v3.0/xpatch-3.0.jar
[2]: https://github.com/WindySha/Xpatch/releases [11]: https://github.com/WindySha/Xpatch.git
[3]: https://ibotpeaches.github.io/Apktool/install/
[5]: https://github.com/asLody/whale
[6]: https://repo.xposed.info/module/com.example.wx_plug_in3
[7]: https://github.com/Gh0u1L5/WechatMagician/releases
[8]: https://github.com/Blankeer/MDWechat
[9]: https://repo.xposed.info/module/liubaoyua.customtext
[10]: https://github.com/rovo89/Xposed [10]: https://github.com/rovo89/Xposed
[11]: https://github.com/asLody/whale
[12]: https://github.com/pxb1988/dex2jar
[13]: https://code.google.com/archive/p/android4me/downloads [13]: https://code.google.com/archive/p/android4me/downloads
[14]: http://www.apache.org/licenses/LICENSE-2.0.html
[15]: https://xposed-tool-app.oss-cn-beijing.aliyuncs.com/data/Xposed_Tool_2.0.3.apk
[16]: https://github.com/WindySha/xposed-tool-app

View File

@ -1,98 +0,0 @@
# What is Xpatch
Xpatch is a jar tool which is used to repackage the apk file. Then, the new apk can load any Xposed modules installed in the android system.
This is a way to use Xposed modules without root your device.
It is easy way to modify one app using xposed module. And any apps changed by Xpatch can load every modules downloaded in the [Xposed Module Repository](https://repo.xposed.info/).
# Benefits
1. Use xposed modules without your device;
2. Modify any apps without root your device.
# How to use
1. Download the latest jar file from the [release page](https://github.com/WindySha/Xpatch/releases);
2. Run this command in the Windows/Mac console:
```
$ java -jar ../../xpatch.jar ../../source.apk
```
Then, a new apk named `source-xposed-signed.apk` in the same folder as `source.apk`.
# More commands
1. You can specify the output apk path by add `-o` parameter, eg:
```
$ java -jar ../../xpatch.jar ../../source.apk -o ../../dst.apk
```
2. Show all the building new apk logs, just add `-l`, eg:
```
$ java -jar ../../xpatch.jar ../../source.apk -l
```
3. Not delete the build files, just add `-k`, eg:
```
$ java -jar ../../xpatch.jar ../../source.apk -k
```
4. After the version 1.2, craching app signature verifying is added, if you won't need the function, just add '-c', eg:
```
$ java -jar ../../xpatch.jar ../../source.apk -c
```
5. More command details can be found when no parameter is added, eg:
```
$ java -jar ../../xpatch.jar
```
# How to manage Xposed modules
When the new apk is installed in the device, It will load all the Xposed modules installed in the device when it's process started.
But you can manage the installed Xposed modules on/off state by a file in the storage.
The file path is `mnt/sdcard/xposed_config/modules.list`.
When the new app started, it will search all the installed Xposed modules and write the the module app name and the module application name into this file. (`mnt/sdcard/xposed_config/modules.list`)
eg:
```
com.blanke.mdwechat#MDWechat
liubaoyua.customtext#文本自定义
```
Each line of this file is Application Name#App Name.
You can disable a Xposed module by add `#` before the Application Name, eg:
```
#com.blanke.mdwechat#MDWechat
liubaoyua.customtext#文本自定义
```
This means the MDWechat Xposed module is disabled.
```
#com.blanke.mdwechat#MDWechat
#liubaoyua.customtext#文本自定义
```
This means all Xposed modules are disabled.
Note: The target app must have file system access permission. Otherwise this file will not be created, and all xposed modules are enabled.
# Todo list
1. Support packaging the xposed modules into the source apk;
2. Support loading so library in the xposed modules;
3. Crach apk protections.
# Issues
1. If the apk dex files are protected, dex2jar can not effect on the dexs, then this tool will not work;
2. The hook framework is using [whale](https://github.com/asLody/whale); this framework is not very stable, some hooks may fail;
3. Do not support Davlik VM;
4. Do not support resource hook;
# Discuss
You can discuss with me under this page.
[Xpatch Comments](https://windysha.github.io/2019/04/18/Xpatch-%E5%85%8DRoot%E5%AE%9E%E7%8E%B0App%E5%8A%A0%E8%BD%BDXposed%E6%8F%92%E4%BB%B6%E7%9A%84%E4%B8%80%E7%A7%8D%E6%96%B9%E6%A1%88/)
# Thanks to
- [Xposed][10]
- [whale][11]
- [dex2jar][12]
- [AXMLPrinter2][13]
[10]: https://github.com/rovo89/Xposed
[11]: https://github.com/asLody/whale
[12]: https://github.com/pxb1988/dex2jar
[13]: https://code.google.com/archive/p/android4me/downloads

View File

@ -33,21 +33,10 @@ public class MainCommand extends BaseCommand {
@Opt(opt = "f", longOpt = "force", hasArg = false, description = "force overwrite") @Opt(opt = "f", longOpt = "force", hasArg = false, description = "force overwrite")
private boolean forceOverwrite = false; private boolean forceOverwrite = false;
@Opt(opt = "k", longOpt = "keep", hasArg = false, description = "not delete the jar file " +
"that is changed by dex2jar and the apk zip files")
private boolean keepBuildFiles = false;
@Opt(opt = "l", longOpt = "log", hasArg = false, description = "show all the debug logs")
private boolean showAllLogs = false;
@Opt(opt = "c", longOpt = "crach", hasArg = false, @Opt(opt = "c", longOpt = "crach", hasArg = false,
description = "disable craching the apk's signature.") description = "disable craching the apk's signature.")
private boolean disableCrackSignature = false; private boolean disableCrackSignature = false;
@Opt(opt = "xm", longOpt = "xposed-modules", description = "the xposed module files to be packaged into the apk, " +
"multi files should be seperated by :(mac) or ;(win) ", argName = "xposed module file path")
private String xposedModules;
// 使用dex文件中插入代码的方式修改apk而不是默认的修改Manifest中Application name的方式 // 使用dex文件中插入代码的方式修改apk而不是默认的修改Manifest中Application name的方式
@Opt(opt = "dex", longOpt = "dex", hasArg = false, description = "insert code into the dex file, not modify manifest application name attribute") @Opt(opt = "dex", longOpt = "dex", hasArg = false, description = "insert code into the dex file, not modify manifest application name attribute")
private boolean dexModificationMode = false; private boolean dexModificationMode = false;
@ -107,9 +96,8 @@ public class MainCommand extends BaseCommand {
} }
String currentDir = new File(".").getAbsolutePath(); // 当前命令行所在的目录 String currentDir = new File(".").getAbsolutePath(); // 当前命令行所在的目录
if (showAllLogs) { System.out.println("currentDir: " + currentDir);
System.out.println(" currentDir = " + currentDir + " \n apkPath = " + apkPath); System.out.println("apkPath: " + apkPath);
}
if (output == null || output.length() == 0) { if (output == null || output.length() == 0) {
output = getBaseName(apkPath) + "-xposed-signed.apk"; output = getBaseName(apkPath) + "-xposed-signed.apk";
@ -129,8 +117,8 @@ public class MainCommand extends BaseCommand {
outputApkFileParentPath = absPath.substring(0, index); outputApkFileParentPath = absPath.substring(0, index);
} }
System.out.println(" !!!!! output apk path --> " + output + System.out.println("output apk path: " + output);
" disableCrackSignature --> " + disableCrackSignature); System.out.println("disableCrackSignature: " + disableCrackSignature);
String apkFileName = getBaseName(srcApkFile); String apkFileName = getBaseName(srcApkFile);
@ -141,10 +129,8 @@ public class MainCommand extends BaseCommand {
// apk文件解压的目录 // apk文件解压的目录
unzipApkFilePath = tempFilePath + apkFileName + "-" + UNZIP_APK_FILE_NAME + File.separator; unzipApkFilePath = tempFilePath + apkFileName + "-" + UNZIP_APK_FILE_NAME + File.separator;
if (showAllLogs) { System.out.println("outputApkFileParentPath: " + outputApkFileParentPath);
System.out.println(" !!!!! outputApkFileParentPath = " + outputApkFileParentPath + System.out.println("unzipApkFilePath = " + unzipApkFilePath);
"\n unzipApkFilePath = " + unzipApkFilePath);
}
if (!disableCrackSignature) { if (!disableCrackSignature) {
// save the apk original signature info, to support crach signature. // save the apk original signature info, to support crach signature.
@ -155,16 +141,12 @@ public class MainCommand extends BaseCommand {
long currentTime = System.currentTimeMillis(); long currentTime = System.currentTimeMillis();
FileUtils.decompressZip(apkPath, unzipApkFilePath); FileUtils.decompressZip(apkPath, unzipApkFilePath);
if (showAllLogs) { System.out.println("decompress apk cost time: " + (System.currentTimeMillis() - currentTime) + "ms");
System.out.println(" decompress apk cost time: " + (System.currentTimeMillis() - currentTime));
}
// Get the dex count in the apk zip file // Get the dex count in the apk zip file
dexFileCount = findDexFileCount(unzipApkFilePath); dexFileCount = findDexFileCount(unzipApkFilePath);
if (showAllLogs) { System.out.println("dexFileCount: " + dexFileCount);
System.out.println(" --- dexFileCount = " + dexFileCount);
}
String manifestFilePath = unzipApkFilePath + "AndroidManifest.xml"; String manifestFilePath = unzipApkFilePath + "AndroidManifest.xml";
@ -177,10 +159,8 @@ public class MainCommand extends BaseCommand {
applicationName = pair.applicationName; applicationName = pair.applicationName;
} }
if (showAllLogs) { System.out.println("Get application name cost time: " + (System.currentTimeMillis() - currentTime) + "ms");
System.out.println(" Get application name cost time: " + (System.currentTimeMillis() - currentTime)); System.out.println("Get the application name: " + applicationName);
System.out.println(" Get the application name --> " + applicationName);
}
// modify manifest // modify manifest
File manifestFile = new File(manifestFilePath); File manifestFile = new File(manifestFilePath);
@ -204,16 +184,15 @@ public class MainCommand extends BaseCommand {
// modify the apk dex file to make xposed can run in it // modify the apk dex file to make xposed can run in it
if (dexModificationMode && isNotEmpty(applicationName)) { if (dexModificationMode && isNotEmpty(applicationName)) {
mXpatchTasks.add(new ApkModifyTask(showAllLogs, keepBuildFiles, unzipApkFilePath, applicationName, mXpatchTasks.add(new ApkModifyTask(true, true, unzipApkFilePath, applicationName,
dexFileCount)); dexFileCount));
} }
// copy xposed so and dex files into the unzipped apk // copy xposed so and dex files into the unzipped apk
mXpatchTasks.add(new SoAndDexCopyTask(dexFileCount, unzipApkFilePath, mXpatchTasks.add(new SoAndDexCopyTask(dexFileCount, unzipApkFilePath));
getXposedModules(xposedModules), useWhaleHookFramework));
// compress all files into an apk and then sign it. // compress all files into an apk and then sign it.
mXpatchTasks.add(new BuildAndSignApkTask(keepBuildFiles, unzipApkFilePath, output)); mXpatchTasks.add(new BuildAndSignApkTask(true, unzipApkFilePath, output));
// copy origin apk to assets // copy origin apk to assets
// convenient to bypass some check like CRC // convenient to bypass some check like CRC
@ -226,21 +205,8 @@ public class MainCommand extends BaseCommand {
currentTime = System.currentTimeMillis(); currentTime = System.currentTimeMillis();
executor.run(); executor.run();
if (showAllLogs) {
System.out.println(executor.getClass().getSimpleName() + " cost time: " System.out.println(executor.getClass().getSimpleName() + " cost time: "
+ (System.currentTimeMillis() - currentTime)); + (System.currentTimeMillis() - currentTime) + "ms");
}
}
// 5. delete all the build files that is useless now
File unzipApkFile = new File(unzipApkFilePath);
if (!keepBuildFiles && unzipApkFile.exists()) {
FileUtils.deleteDir(unzipApkFile);
}
File tempFile = new File(tempFilePath);
if (!keepBuildFiles && tempFile.exists()) {
tempFile.delete();
} }
} }

View File

@ -13,7 +13,7 @@ public class SaveApkSignatureTask implements Runnable {
private String apkPath; private String apkPath;
private String dstFilePath; private String dstFilePath;
private final static String SIGNATURE_INFO_ASSET_PATH = "assets/xpatch_asset/original_signature_info.ini"; private final static String SIGNATURE_INFO_ASSET_PATH = "assets/original_signature_info.ini";
public SaveApkSignatureTask(String apkPath, String unzipApkFilePath) { public SaveApkSignatureTask(String apkPath, String unzipApkFilePath) {
this.apkPath = apkPath; this.apkPath = apkPath;
@ -33,7 +33,9 @@ public class SaveApkSignatureTask implements Runnable {
File file = new File(dstFilePath); File file = new File(dstFilePath);
File fileParent = file.getParentFile(); File fileParent = file.getParentFile();
if (!fileParent.exists()) { if (!fileParent.exists()) {
fileParent.mkdirs(); if(!fileParent.mkdirs()){
System.out.println("mkdir fails " + fileParent.getAbsolutePath());
}
} }
FileUtils.writeFile(dstFilePath, originalSignature); FileUtils.writeFile(dstFilePath, originalSignature);

View File

@ -13,7 +13,7 @@ public class SaveOriginalApplicationNameTask implements Runnable {
private final String unzipApkFilePath; private final String unzipApkFilePath;
private String dstFilePath; private String dstFilePath;
private final String APPLICATION_NAME_ASSET_PATH = "assets/xpatch_asset/original_application_name.ini"; private final String APPLICATION_NAME_ASSET_PATH = "assets/original_application_name.ini";
public SaveOriginalApplicationNameTask(String applicationName, String unzipApkFilePath) { public SaveOriginalApplicationNameTask(String applicationName, String unzipApkFilePath) {
this.applcationName = applicationName; this.applcationName = applicationName;

View File

@ -3,52 +3,27 @@ package com.storm.wind.xpatch.task;
import com.storm.wind.xpatch.util.FileUtils; import com.storm.wind.xpatch.util.FileUtils;
import java.io.File; import java.io.File;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
/** /**
* Created by Wind * Created by Wind
*/ */
public class SoAndDexCopyTask implements Runnable { public class SoAndDexCopyTask implements Runnable {
private static final String SANDHOOK_SO_FILE_NAME = "libsandhook";
private static final String WHALE_SO_FILE_NAME = "libwhale";
private static final String SANDHOOK_SO_FILE_NAME_WITH_SUFFIX = "libsandhook.so";
private static final String WHALE_SO_FILE_NAME_WITH_SUFFIX = "libwhale.so";
private static final String XPOSED_MODULE_FILE_NAME_PREFIX = "libxpatch_xp_module_";
private static final String SO_FILE_SUFFIX = ".so";
private final String[] APK_LIB_PATH_ARRAY = { private final String[] APK_LIB_PATH_ARRAY = {
"lib/armeabi-v7a/", "lib/armeabi-v7a/",
"lib/armeabi/", "lib/armeabi/",
"lib/arm64-v8a/" "lib/arm64-v8a/"
}; };
private final HashMap<String, String> mSoFilePathMap = new HashMap<>();
private int dexFileCount; private int dexFileCount;
private String unzipApkFilePath; private String unzipApkFilePath;
private String[] xposedModuleArray;
private boolean useWhaleHookFramework; public SoAndDexCopyTask(int dexFileCount, String unzipApkFilePath) {
public SoAndDexCopyTask(int dexFileCount, String unzipApkFilePath,
String[] xposedModuleArray, boolean useWhaleHookFramework) {
this.dexFileCount = dexFileCount; this.dexFileCount = dexFileCount;
this.unzipApkFilePath = unzipApkFilePath; this.unzipApkFilePath = unzipApkFilePath;
this.xposedModuleArray = xposedModuleArray;
this.useWhaleHookFramework = useWhaleHookFramework;
String soFileName;
if (useWhaleHookFramework) {
soFileName = WHALE_SO_FILE_NAME;
} else {
soFileName = SANDHOOK_SO_FILE_NAME;
}
mSoFilePathMap.put(APK_LIB_PATH_ARRAY[0], "assets/lib/armeabi-v7a/" + soFileName);
mSoFilePathMap.put(APK_LIB_PATH_ARRAY[1], "assets/lib/armeabi-v7a/" + soFileName);
mSoFilePathMap.put(APK_LIB_PATH_ARRAY[2], "assets/lib/arm64-v8a/" + soFileName);
} }
@Override @Override
@ -62,96 +37,74 @@ public class SoAndDexCopyTask implements Runnable {
} }
private void copySoFile() { private void copySoFile() {
String[] existLibPathArray = new String[3]; List<String> existLibPathArray = new ArrayList<>();
int arrayIndex = 0;
for (String libPath : APK_LIB_PATH_ARRAY) { for (String libPath : APK_LIB_PATH_ARRAY) {
String apkSoFullPath = fullLibPath(libPath); String apkSoFullPath = fullLibPath(libPath);
File apkSoFullPathFile = new File(apkSoFullPath); File apkSoFullPathFile = new File(apkSoFullPath);
if (apkSoFullPathFile.exists()) { if (apkSoFullPathFile.exists()) {
existLibPathArray[arrayIndex] = libPath; existLibPathArray.add(libPath);
arrayIndex++; } else {
System.out.println("target app dont have " + libPath + ", skip");
} }
} }
// 不存在lib目录则创建lib/armeabi-v7 文件夹 if (existLibPathArray.isEmpty()) {
if (arrayIndex == 0) { System.out.println("target app dont have any so in \"lib/{eabi}\" dir, so create default \"armeabi-v7a\"");
String libPath = APK_LIB_PATH_ARRAY[0]; String libPath = APK_LIB_PATH_ARRAY[0];
String apkSoFullPath = fullLibPath(libPath); String apkSoFullPath = fullLibPath(libPath);
File apkSoFullPathFile = new File(apkSoFullPath); File apkSoFullPathFile = new File(apkSoFullPath);
apkSoFullPathFile.mkdirs(); if (apkSoFullPathFile.mkdirs()) {
existLibPathArray[arrayIndex] = libPath; throw new IllegalStateException("mkdir fail " + apkSoFullPathFile.getAbsolutePath());
}
existLibPathArray.add(libPath);
} }
for (String libPath : existLibPathArray) { for (String libPath : existLibPathArray) {
if (libPath != null && !libPath.isEmpty()) { if (libPath == null || libPath.isEmpty()) {
String apkSoFullPath = fullLibPath(libPath); throw new IllegalStateException("fail eabi path");
copyLibFile(apkSoFullPath, mSoFilePathMap.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 : existLibPathArray) {
if (libPath != null && !libPath.isEmpty()) {
String apkSoFullPath = fullLibPath(libPath); String apkSoFullPath = fullLibPath(libPath);
String outputModuleName = XPOSED_MODULE_FILE_NAME_PREFIX + index + SO_FILE_SUFFIX; String eabi = libPath.substring(libPath.indexOf("/"));
File outputModuleSoFile = new File(apkSoFullPath, outputModuleName); if (eabi.isEmpty()) {
FileUtils.copyFile(moduleFile, outputModuleSoFile); throw new IllegalStateException("fail find eabi in " + libPath);
} }
File[] files = new File("list-so", eabi).listFiles();
if (files == null) {
System.out.println("Warning: Nothing so file has been copied in " + libPath);
continue;
} }
index++; for (File mySoFile : files) {
File target = new File(apkSoFullPath, mySoFile.getName());
FileUtils.copyFile(mySoFile, target);
System.out.println("Copy " + mySoFile.getAbsolutePath() + " to " + target.getAbsolutePath());
} }
} }
} }
private void copyDexFile(int dexFileCount) { private void copyDexFile(int dexFileCount) {
// copy dex file to root dir, rename it first boolean copyed = false;
String copiedDexFileName = "classes" + (dexFileCount + 1) + ".dex"; // copy all dex files in list-dex
// assets/classes.dex分隔符不能使用File.seperater,否则在windows上无法读取到文件IOException File[] files = new File("list-dex").listFiles();
String dexAssetPath; if (files == null || files.length == 0) {
if (useWhaleHookFramework) { System.out.println("Warning: Nothing dex file has been copied");
dexAssetPath = "assets/dex/whale/classes-1.0.dex"; return;
} else { }
dexAssetPath = "assets/dex/sandhook/classes-2.0.dex"; for (File file : files) {
String copiedDexFileName = "classes" + (dexFileCount + 1) + ".dex";
File target = new File(unzipApkFilePath, copiedDexFileName);
FileUtils.copyFile(file, target);
System.out.println("copy " + file.getAbsolutePath() + " to " + target.getAbsolutePath());
dexFileCount++;
copyed = true;
} }
FileUtils.copyFileFromJar(dexAssetPath, unzipApkFilePath + copiedDexFileName);
} }
private String fullLibPath(String libPath) { private String fullLibPath(String libPath) {
return unzipApkFilePath + libPath.replace("/", File.separator); return unzipApkFilePath + libPath.replace("/", File.separator);
} }
private void copyLibFile(String libFilePath, String srcSoPath) {
File apkSoParentFile = new File(libFilePath);
if (!apkSoParentFile.exists()) {
apkSoParentFile.mkdirs();
}
// get the file name first
// int lastIndex = srcSoPath.lastIndexOf('/');
// int length = srcSoPath.length();
String soFileName;
if (useWhaleHookFramework) {
soFileName = WHALE_SO_FILE_NAME_WITH_SUFFIX;
} else {
soFileName = SANDHOOK_SO_FILE_NAME_WITH_SUFFIX;
}
// do copy
FileUtils.copyFileFromJar(srcSoPath, new File(apkSoParentFile, soFileName).getAbsolutePath());
}
private void deleteMetaInfo() { private void deleteMetaInfo() {
String metaInfoFilePath = "META-INF"; String metaInfoFilePath = "META-INF";
File metaInfoFileRoot = new File(unzipApkFilePath + metaInfoFilePath); File metaInfoFileRoot = new File(unzipApkFilePath + metaInfoFilePath);

View File

@ -72,7 +72,7 @@ public class ApkSignatureHelper {
} }
} }
jarFile.close(); jarFile.close();
System.out.println(" getApkSignInfo result --> " + certs[0]); System.out.println("getApkSignInfo result: " + certs[0]);
return new String(toChars(certs[0].getEncoded())); return new String(toChars(certs[0].getEncoded()));
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();

View File

@ -44,7 +44,9 @@ public class FileUtils {
} }
File pathFile = new File(descDir); File pathFile = new File(descDir);
if (!pathFile.exists()) { if (!pathFile.exists()) {
pathFile.mkdirs(); if(!pathFile.mkdirs()){
throw new IllegalStateException("mkdir fail " + pathFile.getAbsolutePath());
}
} }
ZipFile zip = null; ZipFile zip = null;
@ -67,7 +69,9 @@ public class FileUtils {
File file = new File(outPath.substring(0, outPath.lastIndexOf(File.separator))); File file = new File(outPath.substring(0, outPath.lastIndexOf(File.separator)));
if (!file.exists()) { if (!file.exists()) {
file.mkdirs(); if(!file.mkdirs()){
throw new IllegalStateException("mkdir fail " + file.getAbsolutePath());
}
} }
//判断文件全路径是否为文件夹,如果是上面已经上传,不需要解压 //判断文件全路径是否为文件夹,如果是上面已经上传,不需要解压
if (new File(outPath).isDirectory()) { if (new File(outPath).isDirectory()) {