Use tmpdir
This commit is contained in:
parent
2fb8750c75
commit
6a1303f4d6
|
|
@ -1,47 +1,49 @@
|
||||||
package org.lsposed.patch;
|
package org.lsposed.patch;
|
||||||
|
|
||||||
import static org.apache.commons.io.FileUtils.copyDirectory;
|
import com.android.apksigner.ApkSignerTool;
|
||||||
import static org.apache.commons.io.FileUtils.copyFile;
|
|
||||||
|
|
||||||
import com.android.tools.build.apkzlib.zip.StoredEntry;
|
import com.android.tools.build.apkzlib.zip.StoredEntry;
|
||||||
import com.android.tools.build.apkzlib.zip.ZFile;
|
import com.android.tools.build.apkzlib.zip.ZFile;
|
||||||
import com.beust.jcommander.JCommander;
|
import com.beust.jcommander.JCommander;
|
||||||
import com.beust.jcommander.Parameter;
|
import com.beust.jcommander.Parameter;
|
||||||
import com.wind.meditor.base.BaseCommand;
|
import com.wind.meditor.core.ManifestEditor;
|
||||||
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.FilenameUtils;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.lsposed.lspatch.share.Constants;
|
import org.lsposed.lspatch.share.Constants;
|
||||||
import org.lsposed.patch.task.BuildAndSignApkTask;
|
|
||||||
import org.lsposed.patch.util.ApkSignatureHelper;
|
import org.lsposed.patch.util.ApkSignatureHelper;
|
||||||
import org.lsposed.patch.util.ManifestParser;
|
import org.lsposed.patch.util.ManifestParser;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.FilenameFilter;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.Charset;
|
import java.io.InputStream;
|
||||||
import java.text.SimpleDateFormat;
|
import java.io.OutputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
public class LSPatch {
|
public class LSPatch {
|
||||||
|
|
||||||
@Parameter(description = "apk path")
|
static class PatchError extends Error {
|
||||||
private String apkPath;
|
PatchError(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private String unzipApkFilePath;
|
@Parameter(description = "apk")
|
||||||
|
private String apkPath = null;
|
||||||
|
|
||||||
@Parameter(names = "--help", help = true, order = 0)
|
@Parameter(names = {"-h", "--help"}, help = true, order = 0, description = "Print this message")
|
||||||
private boolean help = false;
|
private boolean help = false;
|
||||||
|
|
||||||
@Parameter(names = {"-o", "--output"}, description = "Output apk file")
|
@Parameter(names = {"-o", "--output"}, description = "Output apk file")
|
||||||
|
|
@ -53,17 +55,30 @@ public class LSPatch {
|
||||||
@Parameter(names = {"-p", "--proxyname"}, description = "Special proxy app name with full dot path")
|
@Parameter(names = {"-p", "--proxyname"}, description = "Special proxy app name with full dot path")
|
||||||
private String proxyName = "org.lsposed.lspatch.appstub.LSPApplicationStub";
|
private String proxyName = "org.lsposed.lspatch.appstub.LSPApplicationStub";
|
||||||
|
|
||||||
@Parameter(names = {"-d", "--debuggable"}, description = "Set true to make the app debuggable, otherwise set 0 (default) to make the app non-debuggable")
|
@Parameter(names = {"-d", "--debuggable"}, description = "Set app to be debuggable")
|
||||||
private boolean debuggableFlag = false;
|
private boolean debuggableFlag = false;
|
||||||
|
|
||||||
@Parameter(names = {"-l", "--sigbypasslv"}, description = "Signature bypass level. 0 (disable), 1 (pm), 2 (pm+openat). default 0")
|
@Parameter(names = {"-l", "--sigbypasslv"}, description = "Signature bypass level. 0 (disable), 1 (pm), 2 (pm+openat). default 0")
|
||||||
private int sigbypassLevel = 0;
|
private int sigbypassLevel = 0;
|
||||||
|
|
||||||
|
@Parameter(names = {"--v1"}, description = "Sign with v1 signature")
|
||||||
|
private boolean v1 = true;
|
||||||
|
|
||||||
|
@Parameter(names = {"--v2"}, description = "Sign with v2 signature")
|
||||||
|
private boolean v2 = true;
|
||||||
|
|
||||||
|
@Parameter(names = {"--v3"}, description = "Sign with v3 signature")
|
||||||
|
private boolean v3 = true;
|
||||||
|
|
||||||
|
@Parameter(names = {"-v", "--verbose"}, description = "Verbose output")
|
||||||
|
private boolean verbose = false;
|
||||||
|
|
||||||
private int dexFileCount = 0;
|
private int dexFileCount = 0;
|
||||||
|
|
||||||
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 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 SIGNATURE_INFO_ASSET_PATH = "assets/original_signature_info.ini";
|
||||||
|
private static final String ORIGIN_APK_ASSET_PATH = "assets/origin_apk.bin";
|
||||||
|
private static final String ANDROID_MANIFEST_XML = "AndroidManifest.xml";
|
||||||
private static final String[] APK_LIB_PATH_ARRAY = {
|
private static final String[] APK_LIB_PATH_ARRAY = {
|
||||||
"lib/armeabi-v7a",
|
"lib/armeabi-v7a",
|
||||||
"lib/armeabi",
|
"lib/armeabi",
|
||||||
|
|
@ -80,191 +95,238 @@ public class LSPatch {
|
||||||
.addObject(lsPatch)
|
.addObject(lsPatch)
|
||||||
.build();
|
.build();
|
||||||
jCommander.parse(args);
|
jCommander.parse(args);
|
||||||
lsPatch.doCommandLine();
|
try {
|
||||||
|
lsPatch.doCommandLine();
|
||||||
|
} catch (PatchError e) {
|
||||||
|
System.err.println(e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void doCommandLine() throws IOException {
|
public void doCommandLine() throws PatchError, IOException {
|
||||||
|
if (help) {
|
||||||
|
jCommander.usage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (apkPath == null || apkPath.isEmpty()) {
|
if (apkPath == null || apkPath.isEmpty()) {
|
||||||
jCommander.usage();
|
jCommander.usage();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
File srcApkFile = new File(apkPath);
|
File srcApkFile = new File(apkPath).getAbsoluteFile();
|
||||||
|
|
||||||
if (!srcApkFile.exists()) {
|
if (!srcApkFile.exists())
|
||||||
System.out.println("The source apk file not exsit, please choose another one.");
|
throw new PatchError("The source apk file does not exit. Please provide a correct path.");
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
File finalApk = new File(String.format("%s-%s-unsigned.apk", srcApkFile.getName(),
|
var workingDir = Files.createTempDirectory("LSPatch").toFile();
|
||||||
Constants.CONFIG_NAME_SIGBYPASSLV + sigbypassLevel));
|
try {
|
||||||
FileUtils.copyFile(srcApkFile, finalApk);
|
File tmpApk = new File(workingDir, String.format("%s-%s-unsigned.apk", srcApkFile.getName(),
|
||||||
|
Constants.CONFIG_NAME_SIGBYPASSLV + sigbypassLevel));
|
||||||
|
if (verbose) {
|
||||||
|
System.out.println("work dir: " + workingDir);
|
||||||
|
System.out.println("apk path: " + srcApkFile);
|
||||||
|
}
|
||||||
|
|
||||||
ZFile zFile = ZFile.openReadWrite(finalApk);
|
String apkFileName = srcApkFile.getName();
|
||||||
|
|
||||||
String currentDir = new File(".").getAbsolutePath();
|
if (outputPath == null || outputPath.length() == 0) {
|
||||||
System.out.println("work dir: " + currentDir);
|
outputPath = String.format("%s-lv%s-xposed-signed.apk", FilenameUtils.getBaseName(apkFileName), sigbypassLevel);
|
||||||
System.out.println("apk path: " + apkPath);
|
}
|
||||||
|
|
||||||
if (outputPath == null || outputPath.length() == 0) {
|
File outputFile = new File(outputPath);
|
||||||
String sig = Constants.CONFIG_NAME_SIGBYPASSLV + sigbypassLevel;
|
if (outputFile.exists() && !forceOverwrite)
|
||||||
outputPath = String.format("%s-%s-xposed-signed.apk", new File(apkPath).getName(), sig);
|
throw new PatchError(outputPath + " exists. Use --force to overwrite");
|
||||||
}
|
|
||||||
|
|
||||||
File outputFile = new File(outputPath);
|
System.out.println("Copying to tmp apk...");
|
||||||
if (outputFile.exists() && !forceOverwrite) {
|
|
||||||
System.err.println(outputPath + " exists, use --force to overwrite");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String outputApkFileParentPath = outputFile.getParent();
|
FileUtils.copyFile(srcApkFile, tmpApk);
|
||||||
if (outputApkFileParentPath == null) {
|
|
||||||
String absPath = outputFile.getAbsolutePath();
|
|
||||||
int index = absPath.lastIndexOf(File.separatorChar);
|
|
||||||
outputApkFileParentPath = absPath.substring(0, index);
|
|
||||||
}
|
|
||||||
|
|
||||||
String apkFileName = srcApkFile.getName();
|
System.out.println("Parsing original apk...");
|
||||||
|
ZFile zFile = ZFile.openReadWrite(tmpApk);
|
||||||
|
|
||||||
String tempFilePath = outputApkFileParentPath + File.separator +
|
// save the apk original signature info, to support crach signature.
|
||||||
currentTimeStr() + "-tmp" + File.separator;
|
String originalSignature = ApkSignatureHelper.getApkSignInfo(apkPath);
|
||||||
|
if (originalSignature == null || originalSignature.isEmpty()) {
|
||||||
|
throw new PatchError("get original signature failed");
|
||||||
|
}
|
||||||
|
if (verbose)
|
||||||
|
System.out.println("Original signature\n" + originalSignature);
|
||||||
|
try (var is = new ByteArrayInputStream(originalSignature.getBytes(StandardCharsets.UTF_8))) {
|
||||||
|
zFile.add(SIGNATURE_INFO_ASSET_PATH, is);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new PatchError("Error when saving signature: " + e);
|
||||||
|
}
|
||||||
|
|
||||||
unzipApkFilePath = tempFilePath + apkFileName + "-" + UNZIP_APK_FILE_NAME + File.separator;
|
// get the dex count in the apk zip file
|
||||||
|
dexFileCount = findDexFileCount(zFile);
|
||||||
|
|
||||||
// save the apk original signature info, to support crach signature.
|
if (verbose)
|
||||||
String originalSignature = ApkSignatureHelper.getApkSignInfo(apkPath);
|
System.out.println("dexFileCount: " + dexFileCount);
|
||||||
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));
|
|
||||||
|
|
||||||
// get the dex count in the apk zip file
|
// copy out manifest file from zlib
|
||||||
dexFileCount = findDexFileCount(zFile);
|
var manifestEntry = zFile.get(ANDROID_MANIFEST_XML);
|
||||||
|
if (manifestEntry == null)
|
||||||
|
throw new PatchError("Provided file is not a valid apk");
|
||||||
|
|
||||||
System.out.println("dexFileCount: " + dexFileCount);
|
// parse the app main application full name from the manifest file
|
||||||
|
ManifestParser.Pair pair = ManifestParser.parseManifestFile(manifestEntry.open());
|
||||||
|
if (pair == null)
|
||||||
|
throw new PatchError("Failed to parse AndroidManifest.xml");
|
||||||
|
String applicationName = pair.applicationName == null ? "" : pair.applicationName;
|
||||||
|
|
||||||
// copy out manifest file from zlib
|
if (verbose)
|
||||||
int copySize = IOUtils.copy(zFile.get("AndroidManifest.xml").open(), new FileOutputStream(unzipApkFilePath + "AndroidManifest.xml.bak"));
|
System.out.println("original application name: " + applicationName);
|
||||||
if (copySize <= 0) {
|
|
||||||
throw new IllegalStateException("wtf");
|
|
||||||
}
|
|
||||||
String manifestFilePath = unzipApkFilePath + "AndroidManifest.xml.bak";
|
|
||||||
|
|
||||||
// parse the app main application full name from the manifest file
|
System.out.println("Patching apk...");
|
||||||
ManifestParser.Pair pair = ManifestParser.parseManifestFile(manifestFilePath);
|
// modify manifest
|
||||||
String applicationName = null;
|
try (var is = new ByteArrayInputStream(modifyManifestFile(manifestEntry.open()))) {
|
||||||
if (pair != null && pair.applicationName != null) {
|
zFile.add(APPLICATION_NAME_ASSET_PATH, is);
|
||||||
applicationName = pair.applicationName;
|
} catch (IOException e) {
|
||||||
}
|
throw new PatchError("Error when modifying manifest: " + e);
|
||||||
|
}
|
||||||
|
|
||||||
System.out.println("original application name: " + applicationName);
|
// save original main application name to asset file even its empty
|
||||||
|
try (var is = new ByteArrayInputStream(applicationName.getBytes(StandardCharsets.UTF_8))) {
|
||||||
|
zFile.add(APPLICATION_NAME_ASSET_PATH, is);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new PatchError("Error when saving signature: " + e);
|
||||||
|
}
|
||||||
|
|
||||||
// modify manifest
|
// copy so and dex files into the unzipped apk
|
||||||
modifyManifestFile(manifestFilePath, new File(unzipApkFilePath, "AndroidManifest.xml").getPath());
|
Set<String> apkArchs = new HashSet<>();
|
||||||
|
|
||||||
// save original main application name to asset file even its empty
|
if (verbose)
|
||||||
File oan = new File((unzipApkFilePath + APPLICATION_NAME_ASSET_PATH).replace("/", File.separator));
|
System.out.println("search target apk library arch..");
|
||||||
FileUtils.write(oan, applicationName, Charset.defaultCharset());
|
for (StoredEntry storedEntry : zFile.entries()) {
|
||||||
zFile.add(APPLICATION_NAME_ASSET_PATH, new FileInputStream(oan));
|
for (String arch : APK_LIB_PATH_ARRAY) {
|
||||||
|
if (storedEntry.getCentralDirectoryHeader().getName().startsWith(arch)) {
|
||||||
// copy so and dex files into the unzipped apk
|
apkArchs.add(arch);
|
||||||
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()) {
|
if (apkArchs.isEmpty()) {
|
||||||
apkArchs.add(APK_LIB_PATH_ARRAY[0]);
|
apkArchs.add(APK_LIB_PATH_ARRAY[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (String arch : apkArchs) {
|
for (String arch : apkArchs) {
|
||||||
// lib/armeabi-v7a -> armeabi-v7a
|
// lib/armeabi-v7a -> armeabi-v7a
|
||||||
String justArch = arch.substring(arch.indexOf('/'));
|
String justArch = arch.substring(arch.indexOf('/'));
|
||||||
File sod = new File("list-so", justArch);
|
File sod = new File("list-so", justArch);
|
||||||
File[] files = sod.listFiles();
|
File[] files = sod.listFiles();
|
||||||
if (files == null) {
|
if (files == null) {
|
||||||
System.out.println("Warning: Nothing so file has been copied in " + sod.getPath());
|
System.err.println("Warning: No so file has been copied in " + sod.getPath());
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
for (File file : files) {
|
||||||
|
zFile.add(arch + "/" + file.getName(), new FileInputStream(file));
|
||||||
|
if (verbose)
|
||||||
|
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.err.println("Warning: No dex file has been copied");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
for (File file : files) {
|
for (File file : files) {
|
||||||
zFile.add(arch + "/" + file.getName(), new FileInputStream(file));
|
String copiedDexFileName = "classes" + (dexFileCount + 1) + ".dex";
|
||||||
System.out.println("add " + file.getPath());
|
zFile.add(copiedDexFileName, new FileInputStream(file));
|
||||||
|
dexFileCount++;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// copy all dex files in list-dex
|
// copy origin apk to assets
|
||||||
File[] files = new File("list-dex").listFiles();
|
// convenient to bypass some check like CRC
|
||||||
if (files == null || files.length == 0) {
|
if (sigbypassLevel >= Constants.SIGBYPASS_LV_PM_OPENAT) {
|
||||||
System.out.println("Warning: Nothing dex file has been copied");
|
zFile.add(ORIGIN_APK_ASSET_PATH, new FileInputStream(srcApkFile));
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
for (File file : files) {
|
|
||||||
String copiedDexFileName = "classes" + (dexFileCount + 1) + ".dex";
|
|
||||||
zFile.add(copiedDexFileName, new FileInputStream(file));
|
|
||||||
dexFileCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// copy origin apk to assets
|
File[] listAssets = new File("list-assets").listFiles();
|
||||||
// convenient to bypass some check like CRC
|
if (listAssets == null || listAssets.length == 0) {
|
||||||
if (sigbypassLevel >= Constants.SIGBYPASS_LV_PM_OPENAT) {
|
System.err.println("Warning: No assets file copyied");
|
||||||
zFile.add("assets/origin_apk.bin", new FileInputStream(srcApkFile));
|
} else {
|
||||||
}
|
for (File f : listAssets) {
|
||||||
|
if (f.isDirectory()) {
|
||||||
File[] listAssets = new File("list-assets").listFiles();
|
throw new PatchError("unsupported directory in assets");
|
||||||
if (listAssets == null || listAssets.length == 0) {
|
}
|
||||||
System.out.println("Warning: No assets file copyied");
|
zFile.add("assets/" + f.getName(), new FileInputStream(f));
|
||||||
}
|
|
||||||
else {
|
|
||||||
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..
|
||||||
|
try (var is = new ByteArrayInputStream("42".getBytes(StandardCharsets.UTF_8))) {
|
||||||
|
zFile.add("assets/" + Constants.CONFIG_NAME_SIGBYPASSLV + sigbypassLevel, is);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new PatchError("Error when saving signature: " + e);
|
||||||
|
}
|
||||||
|
|
||||||
|
zFile.update();
|
||||||
|
zFile.close();
|
||||||
|
|
||||||
|
System.out.println("Signing apk...");
|
||||||
|
signApkUsingAndroidApksigner(workingDir, tmpApk, outputFile);
|
||||||
|
|
||||||
|
System.out.println("Done. Output APK: " + outputFile.getAbsolutePath());
|
||||||
|
} finally {
|
||||||
|
FileUtils.deleteDirectory(workingDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
// save lspatch config to asset..
|
|
||||||
File sl = new File(unzipApkFilePath, "tmp");
|
|
||||||
FileUtils.write(sl, "42", Charset.defaultCharset());
|
|
||||||
zFile.add("assets/" + Constants.CONFIG_NAME_SIGBYPASSLV + sigbypassLevel, new FileInputStream(sl));
|
|
||||||
|
|
||||||
zFile.update();
|
|
||||||
zFile.close();
|
|
||||||
|
|
||||||
new BuildAndSignApkTask(true, unzipApkFilePath, finalApk.getAbsolutePath(), outputPath).run();
|
|
||||||
System.out.println("Output APK: " + outputPath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void modifyManifestFile(String filePath, String dstFilePath) {
|
private byte[] modifyManifestFile(InputStream is) {
|
||||||
ModificationProperty property = new ModificationProperty();
|
ModificationProperty property = new ModificationProperty();
|
||||||
|
|
||||||
property.addApplicationAttribute(new AttributeItem(NodeValue.Application.DEBUGGABLE, debuggableFlag));
|
property.addApplicationAttribute(new AttributeItem(NodeValue.Application.DEBUGGABLE, debuggableFlag));
|
||||||
property.addApplicationAttribute(new AttributeItem("extractNativeLibs", true));
|
property.addApplicationAttribute(new AttributeItem("extractNativeLibs", true));
|
||||||
property.addApplicationAttribute(new AttributeItem(NodeValue.Application.NAME, proxyName));
|
property.addApplicationAttribute(new AttributeItem(NodeValue.Application.NAME, proxyName));
|
||||||
|
|
||||||
FileProcesser.processManifestFile(filePath, dstFilePath, property);
|
var os = new ByteArrayOutputStream();
|
||||||
|
(new ManifestEditor(is, os, property)).processManifest();
|
||||||
|
return os.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private int findDexFileCount(ZFile zFile) {
|
private int findDexFileCount(ZFile zFile) {
|
||||||
for (int i = 2; i < 30; i++) {
|
for (int i = 2; ; i++) {
|
||||||
if (zFile.get("classes" + i + ".dex") == null)
|
if (zFile.get("classes" + i + ".dex") == null)
|
||||||
return i - 1;
|
return i - 1;
|
||||||
}
|
}
|
||||||
throw new IllegalStateException("wtf");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the current timestamp as the name of the build file
|
private void signApkUsingAndroidApksigner(File workingDir, File apkPath, File outputPath) throws PatchError, IOException {
|
||||||
@SuppressWarnings("SimpleDateFormat")
|
ArrayList<String> commandList = new ArrayList<>();
|
||||||
private String currentTimeStr() {
|
|
||||||
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
|
var keyStoreFile = new File(workingDir, "keystore");
|
||||||
return df.format(new Date());
|
|
||||||
|
try (InputStream is = getClass().getClassLoader().getResourceAsStream("assets/keystore");
|
||||||
|
FileOutputStream os = new FileOutputStream(keyStoreFile)) {
|
||||||
|
if (is == null)
|
||||||
|
throw new PatchError("Fail to save keystore file");
|
||||||
|
IOUtils.copy(is, os);
|
||||||
|
}
|
||||||
|
|
||||||
|
commandList.add("sign");
|
||||||
|
commandList.add("--ks");
|
||||||
|
commandList.add(keyStoreFile.getAbsolutePath());
|
||||||
|
commandList.add("--ks-key-alias");
|
||||||
|
commandList.add("key0");
|
||||||
|
commandList.add("--ks-pass");
|
||||||
|
commandList.add("pass:" + 123456);
|
||||||
|
commandList.add("--key-pass");
|
||||||
|
commandList.add("pass:" + 123456);
|
||||||
|
commandList.add("--out");
|
||||||
|
commandList.add(outputPath.getAbsolutePath());
|
||||||
|
commandList.add("--v1-signing-enabled");
|
||||||
|
commandList.add(Boolean.toString(v1));
|
||||||
|
commandList.add("--v2-signing-enabled"); // v2签名不兼容android 6
|
||||||
|
commandList.add(Boolean.toString(v2));
|
||||||
|
commandList.add("--v3-signing-enabled"); // v3签名不兼容android 6
|
||||||
|
commandList.add(Boolean.toString(v3));
|
||||||
|
commandList.add(apkPath.getAbsolutePath());
|
||||||
|
|
||||||
|
try {
|
||||||
|
ApkSignerTool.main(commandList.toArray(new String[0]));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new PatchError("Failed to sign apk: " + e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
package org.lsposed.patch.task;
|
|
||||||
|
|
||||||
import com.android.apksigner.ApkSignerTool;
|
|
||||||
|
|
||||||
import com.android.tools.build.apkzlib.zip.ZFile;
|
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
|
||||||
import org.lsposed.patch.LSPatch;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by Wind
|
|
||||||
*/
|
|
||||||
public class BuildAndSignApkTask implements Runnable {
|
|
||||||
|
|
||||||
private final boolean keepUnsignedApkFile;
|
|
||||||
private final String signedApkPath;
|
|
||||||
private final String unzipApkFilePath;
|
|
||||||
private final String unsignedApkPath;
|
|
||||||
|
|
||||||
public BuildAndSignApkTask(boolean keepUnsignedApkFile, String unzipApkFilePath, String unsignedApkPath, String signedApkPath) {
|
|
||||||
this.keepUnsignedApkFile = keepUnsignedApkFile;
|
|
||||||
this.unsignedApkPath = unsignedApkPath;
|
|
||||||
this.unzipApkFilePath = unzipApkFilePath;
|
|
||||||
this.signedApkPath = signedApkPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
|
|
||||||
try {
|
|
||||||
File unzipApkPathFile = new File(unzipApkFilePath);
|
|
||||||
File keyStoreFile = new File(unzipApkPathFile, "keystore");
|
|
||||||
String keyStoreAssetPath;
|
|
||||||
keyStoreAssetPath = "assets/keystore";
|
|
||||||
|
|
||||||
try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(keyStoreAssetPath);
|
|
||||||
FileOutputStream out = new FileOutputStream(keyStoreFile)) {
|
|
||||||
IOUtils.copy(inputStream, out);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean signResult = signApk(unsignedApkPath, keyStoreFile.getAbsolutePath(), signedApkPath);
|
|
||||||
|
|
||||||
File unsignedApkFile = new File(unsignedApkPath);
|
|
||||||
File signedApkFile = new File(signedApkPath);
|
|
||||||
|
|
||||||
// delete unsigned apk file
|
|
||||||
if (!keepUnsignedApkFile && unsignedApkFile.exists() && signedApkFile.exists() && signResult) {
|
|
||||||
if (!unsignedApkFile.delete()) {
|
|
||||||
throw new IllegalStateException("wtf");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception err) {
|
|
||||||
throw new IllegalStateException("wtf", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean signApk(String apkPath, String keyStorePath, String signedApkPath) {
|
|
||||||
return signApkUsingAndroidApksigner(apkPath, keyStorePath, signedApkPath, "123456");
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean signApkUsingAndroidApksigner(String apkPath, String keyStorePath, String signedApkPath, String keyStorePassword) {
|
|
||||||
ArrayList<String> commandList = new ArrayList<>();
|
|
||||||
|
|
||||||
commandList.add("sign");
|
|
||||||
commandList.add("--ks");
|
|
||||||
commandList.add(keyStorePath);
|
|
||||||
commandList.add("--ks-key-alias");
|
|
||||||
commandList.add("key0");
|
|
||||||
commandList.add("--ks-pass");
|
|
||||||
commandList.add("pass:" + keyStorePassword);
|
|
||||||
commandList.add("--key-pass");
|
|
||||||
commandList.add("pass:" + keyStorePassword);
|
|
||||||
commandList.add("--out");
|
|
||||||
commandList.add(signedApkPath);
|
|
||||||
commandList.add("--v1-signing-enabled");
|
|
||||||
commandList.add("true");
|
|
||||||
commandList.add("--v2-signing-enabled"); // v2签名不兼容android 6
|
|
||||||
commandList.add("false");
|
|
||||||
commandList.add("--v3-signing-enabled"); // v3签名不兼容android 6
|
|
||||||
commandList.add("false");
|
|
||||||
commandList.add(apkPath);
|
|
||||||
|
|
||||||
try {
|
|
||||||
ApkSignerTool.main(commandList.toArray(new String[0]));
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -72,7 +72,6 @@ public class ApkSignatureHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jarFile.close();
|
jarFile.close();
|
||||||
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();
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package org.lsposed.patch.util;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
import wind.android.content.res.AXmlResourceParser;
|
import wind.android.content.res.AXmlResourceParser;
|
||||||
import wind.v1.XmlPullParser;
|
import wind.v1.XmlPullParser;
|
||||||
|
|
@ -13,23 +14,12 @@ import wind.v1.XmlPullParserException;
|
||||||
*/
|
*/
|
||||||
public class ManifestParser {
|
public class ManifestParser {
|
||||||
|
|
||||||
/**
|
public static Pair parseManifestFile(InputStream is) throws IOException {
|
||||||
* Get the package name and the main application name from the manifest file
|
|
||||||
* */
|
|
||||||
public static Pair parseManifestFile(String filePath) {
|
|
||||||
AXmlResourceParser parser = new AXmlResourceParser();
|
AXmlResourceParser parser = new AXmlResourceParser();
|
||||||
File file = new File(filePath);
|
|
||||||
String packageName = null;
|
String packageName = null;
|
||||||
String applicationName = null;
|
String applicationName = null;
|
||||||
if (!file.exists()) {
|
|
||||||
System.out.println(" manifest file not exist!!! filePath -> " + filePath);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
FileInputStream inputStream = null;
|
|
||||||
try {
|
try {
|
||||||
inputStream = new FileInputStream(file);
|
parser.open(is);
|
||||||
|
|
||||||
parser.open(inputStream);
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
int type = parser.next();
|
int type = parser.next();
|
||||||
|
|
@ -64,20 +54,21 @@ public class ManifestParser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (XmlPullParserException | IOException e) {
|
} catch (XmlPullParserException | IOException e) {
|
||||||
e.printStackTrace();
|
return null;
|
||||||
System.out.println("parseManifestFile failed, reason --> " + e.getMessage());
|
|
||||||
} finally {
|
|
||||||
if (inputStream != null) {
|
|
||||||
try {
|
|
||||||
inputStream.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return new Pair(packageName, applicationName);
|
return new Pair(packageName, applicationName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the package name and the main application name from the manifest file
|
||||||
|
*/
|
||||||
|
public static Pair parseManifestFile(String filePath) throws IOException {
|
||||||
|
File file = new File(filePath);
|
||||||
|
try (var is = new FileInputStream(file)) {
|
||||||
|
return parseManifestFile(is);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static class Pair {
|
public static class Pair {
|
||||||
public String packageName;
|
public String packageName;
|
||||||
public String applicationName;
|
public String applicationName;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue