use apkzlib manipulate apk
This commit is contained in:
parent
e3c998b36c
commit
2b0a830c55
|
|
@ -12,12 +12,11 @@ compileJava.options.encoding = "UTF-8"
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
implementation project(':axmlprinter')
|
implementation project(':axmlprinter')
|
||||||
//implementation project(':apksigner')
|
|
||||||
implementation group: 'commons-io', name: 'commons-io', version: '2.8.0'
|
|
||||||
implementation project(':share')
|
implementation project(':share')
|
||||||
|
implementation 'commons-io:commons-io:2.10.0'
|
||||||
|
implementation 'com.android.tools.build:apkzlib:4.2.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets.main.java.srcDirs += "$rootProject.projectDir/apksigner/src/main/java"
|
|
||||||
sourceSets.main.java.srcDirs += "$rootProject.projectDir/apksigner/src/apksigner/java"
|
sourceSets.main.java.srcDirs += "$rootProject.projectDir/apksigner/src/apksigner/java"
|
||||||
|
|
||||||
jar {
|
jar {
|
||||||
|
|
|
||||||
|
|
@ -3,26 +3,34 @@ package org.lsposed.patch;
|
||||||
import static org.apache.commons.io.FileUtils.copyDirectory;
|
import static org.apache.commons.io.FileUtils.copyDirectory;
|
||||||
import static org.apache.commons.io.FileUtils.copyFile;
|
import static org.apache.commons.io.FileUtils.copyFile;
|
||||||
|
|
||||||
|
import com.android.tools.build.apkzlib.zip.StoredEntry;
|
||||||
|
import com.android.tools.build.apkzlib.zip.ZFile;
|
||||||
import com.wind.meditor.core.FileProcesser;
|
import com.wind.meditor.core.FileProcesser;
|
||||||
import com.wind.meditor.property.AttributeItem;
|
import com.wind.meditor.property.AttributeItem;
|
||||||
import com.wind.meditor.property.ModificationProperty;
|
import com.wind.meditor.property.ModificationProperty;
|
||||||
import com.wind.meditor.utils.NodeValue;
|
import com.wind.meditor.utils.NodeValue;
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.lsposed.lspatch.share.Constants;
|
import org.lsposed.lspatch.share.Constants;
|
||||||
import org.lsposed.patch.base.BaseCommand;
|
import org.lsposed.patch.base.BaseCommand;
|
||||||
import org.lsposed.patch.task.BuildAndSignApkTask;
|
import org.lsposed.patch.task.BuildAndSignApkTask;
|
||||||
import org.lsposed.patch.task.SaveApkSignatureTask;
|
import org.lsposed.patch.util.ApkSignatureHelper;
|
||||||
import org.lsposed.patch.task.SaveOriginalApplicationNameTask;
|
|
||||||
import org.lsposed.patch.task.SoAndDexCopyTask;
|
|
||||||
import org.lsposed.patch.util.ZipUtils;
|
import org.lsposed.patch.util.ZipUtils;
|
||||||
import org.lsposed.patch.util.ManifestParser;
|
import org.lsposed.patch.util.ManifestParser;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.FilenameFilter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class LSPatch extends BaseCommand {
|
public class LSPatch extends BaseCommand {
|
||||||
|
|
@ -50,6 +58,15 @@ public class LSPatch extends BaseCommand {
|
||||||
private int dexFileCount = 0;
|
private int dexFileCount = 0;
|
||||||
|
|
||||||
private static final String UNZIP_APK_FILE_NAME = "apk-unzip-files";
|
private static final String UNZIP_APK_FILE_NAME = "apk-unzip-files";
|
||||||
|
private static final String APPLICATION_NAME_ASSET_PATH = "assets/original_application_name.ini";
|
||||||
|
private final static String SIGNATURE_INFO_ASSET_PATH = "assets/original_signature_info.ini";
|
||||||
|
private static final String[] APK_LIB_PATH_ARRAY = {
|
||||||
|
"lib/armeabi-v7a",
|
||||||
|
"lib/armeabi",
|
||||||
|
"lib/arm64-v8a",
|
||||||
|
"lib/x86",
|
||||||
|
"lib/x86_64"
|
||||||
|
};
|
||||||
|
|
||||||
public static void main(String... args) {
|
public static void main(String... args) {
|
||||||
new LSPatch().doMain(args);
|
new LSPatch().doMain(args);
|
||||||
|
|
@ -85,12 +102,18 @@ public class LSPatch extends BaseCommand {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
File finalApk = new File(String.format("%s-%s-unsigned.apk", getBaseName(srcApkFile.getAbsolutePath()),
|
||||||
|
Constants.CONFIG_NAME_SIGBYPASSLV + sigbypassLevel));
|
||||||
|
FileUtils.copyFile(srcApkFile, finalApk);
|
||||||
|
|
||||||
|
ZFile zFile = new ZFile(finalApk);
|
||||||
|
|
||||||
String currentDir = new File(".").getAbsolutePath();
|
String currentDir = new File(".").getAbsolutePath();
|
||||||
System.out.println("currentDir: " + currentDir);
|
System.out.println("currentDir: " + currentDir);
|
||||||
System.out.println("apkPath: " + apkPath);
|
System.out.println("apkPath: " + apkPath);
|
||||||
|
|
||||||
if (outputPath == null || outputPath.length() == 0) {
|
if (outputPath == null || outputPath.length() == 0) {
|
||||||
String sig = "sigbypasslv" + sigbypassLevel;
|
String sig = Constants.CONFIG_NAME_SIGBYPASSLV + sigbypassLevel;
|
||||||
outputPath = String.format("%s-%s-xposed-signed.apk", getBaseName(apkPath), sig);
|
outputPath = String.format("%s-%s-xposed-signed.apk", getBaseName(apkPath), sig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -108,8 +131,6 @@ public class LSPatch extends BaseCommand {
|
||||||
outputApkFileParentPath = absPath.substring(0, index);
|
outputApkFileParentPath = absPath.substring(0, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
System.out.println("output apk path: " + outputPath);
|
|
||||||
|
|
||||||
String apkFileName = getBaseName(srcApkFile);
|
String apkFileName = getBaseName(srcApkFile);
|
||||||
|
|
||||||
String tempFilePath = outputApkFileParentPath + File.separator +
|
String tempFilePath = outputApkFileParentPath + File.separator +
|
||||||
|
|
@ -117,25 +138,26 @@ public class LSPatch extends BaseCommand {
|
||||||
|
|
||||||
unzipApkFilePath = tempFilePath + apkFileName + "-" + UNZIP_APK_FILE_NAME + File.separator;
|
unzipApkFilePath = tempFilePath + apkFileName + "-" + UNZIP_APK_FILE_NAME + File.separator;
|
||||||
|
|
||||||
System.out.println("outputApkFileParentPath: " + outputApkFileParentPath);
|
|
||||||
System.out.println("unzipApkFilePath = " + unzipApkFilePath);
|
|
||||||
|
|
||||||
// save the apk original signature info, to support crach signature.
|
// save the apk original signature info, to support crach signature.
|
||||||
new SaveApkSignatureTask(apkPath, unzipApkFilePath).run();
|
String originalSignature = ApkSignatureHelper.getApkSignInfo(apkPath);
|
||||||
|
if (originalSignature == null || originalSignature.isEmpty()) {
|
||||||
|
throw new IllegalStateException("get original signature failed");
|
||||||
|
}
|
||||||
|
File osi = new File((unzipApkFilePath + SIGNATURE_INFO_ASSET_PATH).replace("/", File.separator));
|
||||||
|
FileUtils.write(osi, originalSignature, Charset.defaultCharset());
|
||||||
|
zFile.add(SIGNATURE_INFO_ASSET_PATH, new FileInputStream(osi));
|
||||||
|
|
||||||
long currentTime = System.currentTimeMillis();
|
// get the dex count in the apk zip file
|
||||||
ZipUtils.decompressZip(apkPath, unzipApkFilePath);
|
dexFileCount = findDexFileCount(zFile);
|
||||||
|
|
||||||
System.out.println("decompress apk cost time: " + (System.currentTimeMillis() - currentTime) + "ms");
|
|
||||||
|
|
||||||
// Get the dex count in the apk zip file
|
|
||||||
dexFileCount = findDexFileCount(unzipApkFilePath);
|
|
||||||
|
|
||||||
System.out.println("dexFileCount: " + dexFileCount);
|
System.out.println("dexFileCount: " + dexFileCount);
|
||||||
|
|
||||||
String manifestFilePath = unzipApkFilePath + "AndroidManifest.xml";
|
// copy out manifest file from zlib
|
||||||
|
int copySize = IOUtils.copy(zFile.get("AndroidManifest.xml").open(), new FileOutputStream(unzipApkFilePath + "AndroidManifest.xml.bak"));
|
||||||
currentTime = System.currentTimeMillis();
|
if (copySize <= 0) {
|
||||||
|
throw new IllegalStateException("wtf");
|
||||||
|
}
|
||||||
|
String manifestFilePath = unzipApkFilePath + "AndroidManifest.xml.bak";
|
||||||
|
|
||||||
// parse the app main application full name from the manifest file
|
// parse the app main application full name from the manifest file
|
||||||
ManifestParser.Pair pair = ManifestParser.parseManifestFile(manifestFilePath);
|
ManifestParser.Pair pair = ManifestParser.parseManifestFile(manifestFilePath);
|
||||||
|
|
@ -147,31 +169,60 @@ public class LSPatch extends BaseCommand {
|
||||||
System.out.println("original application name: " + applicationName);
|
System.out.println("original application name: " + applicationName);
|
||||||
|
|
||||||
// modify manifest
|
// modify manifest
|
||||||
File manifestFile = new File(manifestFilePath);
|
modifyManifestFile(manifestFilePath, new File(unzipApkFilePath, "AndroidManifest.xml").getPath());
|
||||||
String manifestFilePathNew = unzipApkFilePath + "AndroidManifest" + "-" + currentTimeStr() + ".xml";
|
|
||||||
File manifestFileNew = new File(manifestFilePathNew);
|
|
||||||
fuckIfFail(manifestFile.renameTo(manifestFileNew));
|
|
||||||
|
|
||||||
modifyManifestFile(manifestFilePathNew, manifestFilePath);
|
|
||||||
|
|
||||||
// new manifest may not exist
|
|
||||||
if (manifestFile.exists() && manifestFile.length() > 0) {
|
|
||||||
fuckIfFail(manifestFileNew.delete());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
fuckIfFail(manifestFileNew.renameTo(manifestFile));
|
|
||||||
}
|
|
||||||
|
|
||||||
// save original main application name to asset file even its empty
|
// save original main application name to asset file even its empty
|
||||||
new SaveOriginalApplicationNameTask(applicationName, unzipApkFilePath).run();
|
File oan = new File((unzipApkFilePath + APPLICATION_NAME_ASSET_PATH).replace("/", File.separator));
|
||||||
|
FileUtils.write(oan, applicationName, Charset.defaultCharset());
|
||||||
|
zFile.add(APPLICATION_NAME_ASSET_PATH, new FileInputStream(oan));
|
||||||
|
|
||||||
// copy so and dex files into the unzipped apk
|
// copy so and dex files into the unzipped apk
|
||||||
new SoAndDexCopyTask(dexFileCount, unzipApkFilePath).run();
|
Set<String> apkArchs = new HashSet<>();
|
||||||
|
|
||||||
|
System.out.println("search target apk library arch..");
|
||||||
|
for (StoredEntry storedEntry : zFile.entries()) {
|
||||||
|
for (String arch : APK_LIB_PATH_ARRAY) {
|
||||||
|
if (storedEntry.getCentralDirectoryHeader().getName().startsWith(arch)) {
|
||||||
|
apkArchs.add(arch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (apkArchs.isEmpty()) {
|
||||||
|
apkArchs.add(APK_LIB_PATH_ARRAY[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String arch : apkArchs) {
|
||||||
|
// lib/armeabi-v7a -> armeabi-v7a
|
||||||
|
String justArch = arch.substring(arch.indexOf('/'));
|
||||||
|
File sod = new File("list-so", justArch);
|
||||||
|
File[] files = sod.listFiles();
|
||||||
|
if (files == null) {
|
||||||
|
System.out.println("Warning: Nothing so file has been copied in " + sod.getPath());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (File file : files) {
|
||||||
|
zFile.add(arch + "/" + file.getName(), new FileInputStream(file));
|
||||||
|
System.out.println("add " + file.getPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy all dex files in list-dex
|
||||||
|
File[] files = new File("list-dex").listFiles();
|
||||||
|
if (files == null || files.length == 0) {
|
||||||
|
System.out.println("Warning: Nothing dex file has been copied");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (File file : files) {
|
||||||
|
String copiedDexFileName = "classes" + (dexFileCount + 1) + ".dex";
|
||||||
|
zFile.add(copiedDexFileName, new FileInputStream(file));
|
||||||
|
dexFileCount++;
|
||||||
|
}
|
||||||
|
|
||||||
// copy origin apk to assets
|
// copy origin apk to assets
|
||||||
// convenient to bypass some check like CRC
|
// convenient to bypass some check like CRC
|
||||||
if (sigbypassLevel >= Constants.SIGBYPASS_LV_PM) {
|
if (sigbypassLevel >= Constants.SIGBYPASS_LV_PM_OPENAT) {
|
||||||
copyFile(srcApkFile, new File(unzipApkFilePath, "assets/origin_apk.bin"));
|
zFile.add("assets/origin_apk.bin", new FileInputStream(srcApkFile));
|
||||||
}
|
}
|
||||||
|
|
||||||
File[] listAssets = new File("list-assets").listFiles();
|
File[] listAssets = new File("list-assets").listFiles();
|
||||||
|
|
@ -179,16 +230,23 @@ public class LSPatch extends BaseCommand {
|
||||||
System.out.println("Warning: No assets file copyied");
|
System.out.println("Warning: No assets file copyied");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
copyDirectory(new File("list-assets"), new File(unzipApkFilePath, "assets"));
|
for (File f : listAssets) {
|
||||||
|
if (f.isDirectory()) {
|
||||||
|
throw new IllegalStateException("unsupport directory in assets");
|
||||||
|
}
|
||||||
|
zFile.add("assets/" + f.getName(), new FileInputStream(f));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// save lspatch config to asset..
|
// save lspatch config to asset..
|
||||||
FileUtils.write(new File(unzipApkFilePath, "assets" + File.separator + Constants.CONFIG_NAME_SIGBYPASSLV + sigbypassLevel), "lspatch",
|
File sl = new File(unzipApkFilePath, "tmp");
|
||||||
Charset.defaultCharset());
|
FileUtils.write(sl, "42", Charset.defaultCharset());
|
||||||
|
zFile.add("assets/" + Constants.CONFIG_NAME_SIGBYPASSLV + sigbypassLevel, new FileInputStream(sl));
|
||||||
|
|
||||||
// compress all files into an apk and then sign it.
|
zFile.update();
|
||||||
new BuildAndSignApkTask(true, unzipApkFilePath, outputPath).run();
|
zFile.close();
|
||||||
|
|
||||||
|
new BuildAndSignApkTask(true, unzipApkFilePath, finalApk.getAbsolutePath(), outputPath).run();
|
||||||
System.out.println("Output APK: " + outputPath);
|
System.out.println("Output APK: " + outputPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -205,23 +263,12 @@ public class LSPatch extends BaseCommand {
|
||||||
FileProcesser.processManifestFile(filePath, dstFilePath, property);
|
FileProcesser.processManifestFile(filePath, dstFilePath, property);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int findDexFileCount(String unzipApkFilePath) {
|
private int findDexFileCount(ZFile zFile) {
|
||||||
File zipfileRoot = new File(unzipApkFilePath);
|
for (int i = 2; i < 30; i++) {
|
||||||
if (!zipfileRoot.exists()) {
|
if (zFile.get("classes" + i + ".dex") == null)
|
||||||
return 0;
|
return i - 1;
|
||||||
}
|
}
|
||||||
File[] childFiles = zipfileRoot.listFiles();
|
throw new IllegalStateException("wtf");
|
||||||
if (childFiles == null || childFiles.length == 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
int count = 0;
|
|
||||||
for (File file : childFiles) {
|
|
||||||
String fileName = file.getName();
|
|
||||||
if (Pattern.matches("classes.*\\.dex", fileName)) {
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the current timestamp as the name of the build file
|
// Use the current timestamp as the name of the build file
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ package org.lsposed.patch.task;
|
||||||
|
|
||||||
import com.android.apksigner.ApkSignerTool;
|
import com.android.apksigner.ApkSignerTool;
|
||||||
|
|
||||||
|
import com.android.tools.build.apkzlib.zip.ZFile;
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.lsposed.patch.LSPatch;
|
import org.lsposed.patch.LSPatch;
|
||||||
import org.lsposed.patch.util.ZipUtils;
|
import org.lsposed.patch.util.ZipUtils;
|
||||||
|
|
@ -17,14 +19,14 @@ import java.util.ArrayList;
|
||||||
*/
|
*/
|
||||||
public class BuildAndSignApkTask implements Runnable {
|
public class BuildAndSignApkTask implements Runnable {
|
||||||
|
|
||||||
private boolean keepUnsignedApkFile;
|
private final boolean keepUnsignedApkFile;
|
||||||
|
private final String signedApkPath;
|
||||||
|
private final String unzipApkFilePath;
|
||||||
|
private final String unsignedApkPath;
|
||||||
|
|
||||||
private String signedApkPath;
|
public BuildAndSignApkTask(boolean keepUnsignedApkFile, String unzipApkFilePath, String unsignedApkPath, String signedApkPath) {
|
||||||
|
|
||||||
private String unzipApkFilePath;
|
|
||||||
|
|
||||||
public BuildAndSignApkTask(boolean keepUnsignedApkFile, String unzipApkFilePath, String signedApkPath) {
|
|
||||||
this.keepUnsignedApkFile = keepUnsignedApkFile;
|
this.keepUnsignedApkFile = keepUnsignedApkFile;
|
||||||
|
this.unsignedApkPath = unsignedApkPath;
|
||||||
this.unzipApkFilePath = unzipApkFilePath;
|
this.unzipApkFilePath = unzipApkFilePath;
|
||||||
this.signedApkPath = signedApkPath;
|
this.signedApkPath = signedApkPath;
|
||||||
}
|
}
|
||||||
|
|
@ -33,43 +35,30 @@ public class BuildAndSignApkTask implements Runnable {
|
||||||
public void run() {
|
public void run() {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
File unzipApkFile = new File(unzipApkFilePath);
|
File unzipApkPathFile = new File(unzipApkFilePath);
|
||||||
|
File keyStoreFile = new File(unzipApkPathFile, "keystore");
|
||||||
String unsignedApkPath = unzipApkFile.getParent() + File.separator + "unsigned.apk";
|
|
||||||
ZipUtils.compressToZip(unzipApkFilePath, unsignedApkPath);
|
|
||||||
|
|
||||||
String keyStoreFilePath = unzipApkFile.getParent() + File.separator + "keystore";
|
|
||||||
|
|
||||||
File keyStoreFile = new File(keyStoreFilePath);
|
|
||||||
// assets/keystore分隔符不能使用File.separator,否则在windows上抛出IOException !!!
|
|
||||||
String keyStoreAssetPath;
|
String keyStoreAssetPath;
|
||||||
if (isAndroid()) {
|
if (isAndroid()) {
|
||||||
// BKS-V1 类型
|
|
||||||
keyStoreAssetPath = "assets/android.keystore";
|
keyStoreAssetPath = "assets/android.keystore";
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// BKS 类型
|
|
||||||
keyStoreAssetPath = "assets/keystore";
|
keyStoreAssetPath = "assets/keystore";
|
||||||
}
|
}
|
||||||
|
|
||||||
try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(keyStoreAssetPath);
|
try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(keyStoreAssetPath);
|
||||||
FileOutputStream out = new FileOutputStream(keyStoreFilePath)) {
|
FileOutputStream out = new FileOutputStream(keyStoreFile)) {
|
||||||
IOUtils.copy(inputStream, out);
|
IOUtils.copy(inputStream, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean signResult = signApk(unsignedApkPath, keyStoreFilePath, signedApkPath);
|
boolean signResult = signApk(unsignedApkPath, keyStoreFile.getAbsolutePath(), signedApkPath);
|
||||||
|
|
||||||
File unsignedApkFile = new File(unsignedApkPath);
|
File unsignedApkFile = new File(unsignedApkPath);
|
||||||
File signedApkFile = new File(signedApkPath);
|
File signedApkFile = new File(signedApkPath);
|
||||||
|
|
||||||
// delete unsigned apk file
|
// delete unsigned apk file
|
||||||
if (!keepUnsignedApkFile && unsignedApkFile.exists() && signedApkFile.exists() && signResult) {
|
if (!keepUnsignedApkFile && unsignedApkFile.exists() && signedApkFile.exists() && signResult) {
|
||||||
LSPatch.fuckIfFail(unsignedApkFile.delete());
|
LSPatch.fuckIfFail(unsignedApkFile.delete());
|
||||||
}
|
}
|
||||||
|
|
||||||
// delete the keystore file
|
|
||||||
if (keyStoreFile.exists()) {
|
|
||||||
LSPatch.fuckIfFail(keyStoreFile.delete());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception err) {
|
catch (Exception err) {
|
||||||
throw new IllegalStateException("wtf", err);
|
throw new IllegalStateException("wtf", err);
|
||||||
|
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
package org.lsposed.patch.task;
|
|
||||||
|
|
||||||
import org.lsposed.patch.util.ApkSignatureHelper;
|
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by Wind
|
|
||||||
*/
|
|
||||||
public class SaveApkSignatureTask implements Runnable {
|
|
||||||
|
|
||||||
private String apkPath;
|
|
||||||
private String dstFilePath;
|
|
||||||
|
|
||||||
private final static String SIGNATURE_INFO_ASSET_PATH = "assets/original_signature_info.ini";
|
|
||||||
|
|
||||||
public SaveApkSignatureTask(String apkPath, String unzipApkFilePath) {
|
|
||||||
this.apkPath = apkPath;
|
|
||||||
this.dstFilePath = (unzipApkFilePath + SIGNATURE_INFO_ASSET_PATH).replace("/", File.separator);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
// First, get the original signature
|
|
||||||
String originalSignature = ApkSignatureHelper.getApkSignInfo(apkPath);
|
|
||||||
if (originalSignature == null || originalSignature.isEmpty()) {
|
|
||||||
System.out.println("Get original signature failed");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then, save the signature chars to the asset file
|
|
||||||
File file = new File(dstFilePath);
|
|
||||||
try {
|
|
||||||
FileUtils.write(file, originalSignature, StandardCharsets.UTF_8);
|
|
||||||
}
|
|
||||||
catch (Exception err) {
|
|
||||||
// just crash now
|
|
||||||
// todo: pass result to caller
|
|
||||||
throw new IllegalStateException("wtf", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
package org.lsposed.patch.task;
|
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by xiawanli on 2019/4/6
|
|
||||||
*/
|
|
||||||
public class SaveOriginalApplicationNameTask implements Runnable {
|
|
||||||
|
|
||||||
private final String applcationName;
|
|
||||||
private final String unzipApkFilePath;
|
|
||||||
private String dstFilePath;
|
|
||||||
|
|
||||||
private final String APPLICATION_NAME_ASSET_PATH = "assets/original_application_name.ini";
|
|
||||||
|
|
||||||
public SaveOriginalApplicationNameTask(String applicationName, String unzipApkFilePath) {
|
|
||||||
this.applcationName = applicationName;
|
|
||||||
this.unzipApkFilePath = unzipApkFilePath;
|
|
||||||
|
|
||||||
this.dstFilePath = (unzipApkFilePath + APPLICATION_NAME_ASSET_PATH).replace("/", File.separator);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
FileUtils.write(new File(dstFilePath), applcationName, Charset.defaultCharset());
|
|
||||||
}
|
|
||||||
catch (Exception err) {
|
|
||||||
// just crash
|
|
||||||
// todo: pass result to caller
|
|
||||||
throw new IllegalStateException("wtf", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,132 +0,0 @@
|
||||||
package org.lsposed.patch.task;
|
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by Wind
|
|
||||||
*/
|
|
||||||
public class SoAndDexCopyTask implements Runnable {
|
|
||||||
|
|
||||||
private final String[] APK_LIB_PATH_ARRAY = {
|
|
||||||
"lib/armeabi-v7a/",
|
|
||||||
"lib/armeabi/",
|
|
||||||
"lib/arm64-v8a/",
|
|
||||||
"lib/x86",
|
|
||||||
"lib/x86_64"
|
|
||||||
};
|
|
||||||
|
|
||||||
private int dexFileCount;
|
|
||||||
private String unzipApkFilePath;
|
|
||||||
|
|
||||||
public SoAndDexCopyTask(int dexFileCount, String unzipApkFilePath) {
|
|
||||||
this.dexFileCount = dexFileCount;
|
|
||||||
this.unzipApkFilePath = unzipApkFilePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
// 复制xposed兼容层的dex文件以及so文件到当前目录下
|
|
||||||
copySoFile();
|
|
||||||
copyDexFile(dexFileCount);
|
|
||||||
|
|
||||||
// 删除签名信息
|
|
||||||
deleteMetaInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void copySoFile() {
|
|
||||||
List<String> existLibPathArray = new ArrayList<>();
|
|
||||||
for (String libPath : APK_LIB_PATH_ARRAY) {
|
|
||||||
String apkSoFullPath = fullLibPath(libPath);
|
|
||||||
File apkSoFullPathFile = new File(apkSoFullPath);
|
|
||||||
if (apkSoFullPathFile.exists()) {
|
|
||||||
existLibPathArray.add(libPath);
|
|
||||||
} else {
|
|
||||||
System.out.println("Target app dont have " + libPath + ", skip");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (existLibPathArray.isEmpty()) {
|
|
||||||
System.out.println("Target app dont have any so in \"lib/\" dir, so create default \"armeabi-v7a\"");
|
|
||||||
String libPath = APK_LIB_PATH_ARRAY[0];
|
|
||||||
String apkSoFullPath = fullLibPath(libPath);
|
|
||||||
File apkSoFullPathFile = new File(apkSoFullPath);
|
|
||||||
if (!apkSoFullPathFile.mkdirs()) {
|
|
||||||
throw new IllegalStateException("mkdir fail " + apkSoFullPathFile.getAbsolutePath());
|
|
||||||
}
|
|
||||||
existLibPathArray.add(libPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (String libPath : existLibPathArray) {
|
|
||||||
if (libPath == null || libPath.isEmpty()) {
|
|
||||||
throw new IllegalStateException("fail eabi path");
|
|
||||||
}
|
|
||||||
|
|
||||||
String apkSoFullPath = fullLibPath(libPath);
|
|
||||||
String eabi = libPath.substring(libPath.indexOf("/"));
|
|
||||||
if (eabi.isEmpty()) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
for (File mySoFile : files) {
|
|
||||||
File target = new File(apkSoFullPath, mySoFile.getName());
|
|
||||||
try {
|
|
||||||
FileUtils.copyFile(mySoFile, target);
|
|
||||||
} catch (Exception err) {
|
|
||||||
throw new IllegalStateException("wtf", err);
|
|
||||||
}
|
|
||||||
System.out.println("Copy " + mySoFile.getAbsolutePath() + " to " + target.getAbsolutePath());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void copyDexFile(int dexFileCount) {
|
|
||||||
try {
|
|
||||||
// copy all dex files in list-dex
|
|
||||||
File[] files = new File("list-dex").listFiles();
|
|
||||||
if (files == null || files.length == 0) {
|
|
||||||
System.out.println("Warning: Nothing dex file has been copied");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
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++;
|
|
||||||
}
|
|
||||||
} catch (Exception err) {
|
|
||||||
throw new IllegalStateException("wtf", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String fullLibPath(String libPath) {
|
|
||||||
return unzipApkFilePath + libPath.replace("/", File.separator);
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue