Create AppComponentFactory stub
This commit is contained in:
parent
2d41905b22
commit
6a814e571b
|
|
@ -0,0 +1,79 @@
|
|||
package org.lsposed.lspatch.appstub;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.AppComponentFactory;
|
||||
import android.app.Application;
|
||||
import android.app.Service;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContentProvider;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.util.Log;
|
||||
|
||||
import org.lsposed.lspatch.util.FileUtils;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import dalvik.system.PathClassLoader;
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
public class LSPAppComponentFactoryStub extends AppComponentFactory {
|
||||
private static final String TAG = "LSPatch";
|
||||
private static final String ORIGINAL_APK_ASSET_PATH = "assets/origin_apk.bin";
|
||||
private static final String ORIGINAL_APP_COMPONENT_FACTORY_ASSET_PATH = "assets/original_app_component_factory.ini";
|
||||
|
||||
private AppComponentFactory originalAppComponentFactory = null;
|
||||
|
||||
// Proxy appComponentFactory to load the original one
|
||||
private void initOrigin(ClassLoader cl, ApplicationInfo aInfo) {
|
||||
final String cacheApkPath = aInfo.dataDir + "/cache/origin_apk.bin";
|
||||
final String originalAppComponentFactoryClass = FileUtils.readTextFromInputStream(cl.getResourceAsStream(ORIGINAL_APP_COMPONENT_FACTORY_ASSET_PATH));
|
||||
|
||||
try {
|
||||
try (InputStream inputStream = cl.getResourceAsStream(ORIGINAL_APK_ASSET_PATH)) {
|
||||
Files.copy(inputStream, Paths.get(cacheApkPath));
|
||||
} catch (FileAlreadyExistsException ignored) {
|
||||
}
|
||||
ClassLoader appClassLoader = new PathClassLoader(cacheApkPath, cl.getParent());
|
||||
originalAppComponentFactory = (AppComponentFactory) appClassLoader.loadClass(originalAppComponentFactoryClass).newInstance();
|
||||
Log.d(TAG, "appComponentFactory is now switched to " + originalAppComponentFactory);
|
||||
} catch (Throwable e) {
|
||||
Log.e(TAG, "initOrigin", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassLoader instantiateClassLoader(ClassLoader cl, ApplicationInfo aInfo) {
|
||||
if (originalAppComponentFactory == null) initOrigin(cl, aInfo);
|
||||
return originalAppComponentFactory.instantiateClassLoader(cl, aInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Application instantiateApplication(ClassLoader cl, String className) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
|
||||
return originalAppComponentFactory.instantiateApplication(cl, className);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Activity instantiateActivity(ClassLoader cl, String className, Intent intent) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
|
||||
return originalAppComponentFactory.instantiateActivity(cl, className, intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BroadcastReceiver instantiateReceiver(ClassLoader cl, String className, Intent intent) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
|
||||
return originalAppComponentFactory.instantiateReceiver(cl, className, intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Service instantiateService(ClassLoader cl, String className, Intent intent) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
|
||||
return originalAppComponentFactory.instantiateService(cl, className, intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContentProvider instantiateProvider(ClassLoader cl, String className) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
|
||||
return originalAppComponentFactory.instantiateProvider(cl, className);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package org.lsposed.lspatch.util;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class FileUtils {
|
||||
|
||||
public static String readTextFromInputStream(InputStream is) {
|
||||
try (InputStreamReader reader = new InputStreamReader(is, StandardCharsets.UTF_8); BufferedReader bufferedReader = new BufferedReader(reader)) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
String str;
|
||||
while ((str = bufferedReader.readLine()) != null) {
|
||||
builder.append(str);
|
||||
}
|
||||
return builder.toString();
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -55,7 +55,8 @@ public class LSPatch {
|
|||
@Parameter(names = {"-f", "--force"}, description = "Force overwrite exists output file")
|
||||
private boolean forceOverwrite = false;
|
||||
|
||||
private String proxyName = "org.lsposed.lspatch.appstub.LSPApplicationStub";
|
||||
private String proxyApplication = "org.lsposed.lspatch.appstub.LSPApplicationStub";
|
||||
private String proxyAppComponentFactory = "org.lsposed.lspatch.appstub.LSPAppComponentFactoryStub";
|
||||
|
||||
@Parameter(names = {"-d", "--debuggable"}, description = "Set app to be debuggable")
|
||||
private boolean debuggableFlag = false;
|
||||
|
|
@ -78,9 +79,10 @@ public class LSPatch {
|
|||
@Parameter(names = {"-m", "--embed"}, description = "Embed provided modules to apk")
|
||||
private List<String> modules = new ArrayList<>();
|
||||
|
||||
private static final String APP_COMPONENT_FACTORY_ASSET_PATH = "assets/original_app_component_factory.ini";
|
||||
private static final String APPLICATION_NAME_ASSET_PATH = "assets/original_application_name.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 ORIGINAL_APK_ASSET_PATH = "assets/origin_apk.bin";
|
||||
private static final String ANDROID_MANIFEST_XML = "AndroidManifest.xml";
|
||||
private static final String RESOURCES_ARSC = "resources.arsc";
|
||||
private static final HashSet<String> APK_LIB_PATH_ARRAY = new HashSet<>(Arrays.asList(
|
||||
|
|
@ -152,12 +154,12 @@ public class LSPatch {
|
|||
|
||||
final var alignmentRule = AlignmentRules.compose(
|
||||
AlignmentRules.constantForSuffix(RESOURCES_ARSC, 4),
|
||||
AlignmentRules.constantForSuffix(ORIGIN_APK_ASSET_PATH, 4096)
|
||||
AlignmentRules.constantForSuffix(ORIGINAL_APK_ASSET_PATH, 4096)
|
||||
);
|
||||
zFileOptions.setAlignmentRule(alignmentRule);
|
||||
try (ZFile zFile = ZFile.openReadWrite(tmpApk, zFileOptions)) {
|
||||
// copy origin apk to assets
|
||||
zFile.add(ORIGIN_APK_ASSET_PATH, new FileInputStream(srcApkFile), false);
|
||||
zFile.add(ORIGINAL_APK_ASSET_PATH, new FileInputStream(srcApkFile), false);
|
||||
|
||||
// remove unnecessary files
|
||||
for (StoredEntry storedEntry : zFile.entries()) {
|
||||
|
|
@ -186,13 +188,16 @@ public class LSPatch {
|
|||
throw new PatchError("Provided file is not a valid apk");
|
||||
|
||||
// parse the app main application full name from the manifest file
|
||||
ManifestParser.Pair pair = ManifestParser.parseManifestFile(manifestEntry.open());
|
||||
if (pair == null)
|
||||
ManifestParser.Triple triple = ManifestParser.parseManifestFile(manifestEntry.open());
|
||||
if (triple == null)
|
||||
throw new PatchError("Failed to parse AndroidManifest.xml");
|
||||
String applicationName = pair.applicationName == null ? "" : pair.applicationName;
|
||||
String applicationName = triple.applicationName == null ? "" : triple.applicationName;
|
||||
String appComponentFactory = triple.appComponentFactory == null ? "" : triple.appComponentFactory;
|
||||
|
||||
if (verbose)
|
||||
if (verbose) {
|
||||
System.out.println("original application name: " + applicationName);
|
||||
System.out.println("original appComponentFactory class: " + appComponentFactory);
|
||||
}
|
||||
|
||||
System.out.println("Patching apk...");
|
||||
// modify manifest
|
||||
|
|
@ -202,6 +207,13 @@ public class LSPatch {
|
|||
throw new PatchError("Error when modifying manifest: " + e);
|
||||
}
|
||||
|
||||
// save original appComponentFactory name to asset file even its empty
|
||||
try (var is = new ByteArrayInputStream(appComponentFactory.getBytes(StandardCharsets.UTF_8))) {
|
||||
zFile.add(APP_COMPONENT_FACTORY_ASSET_PATH, is);
|
||||
} catch (Throwable e) {
|
||||
throw new PatchError("Error when saving appComponentFactory class: " + e);
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
|
@ -301,7 +313,7 @@ public class LSPatch {
|
|||
|
||||
System.out.print("Embedding module ");
|
||||
|
||||
ManifestParser.Pair pair = null;
|
||||
ManifestParser.Triple triple = null;
|
||||
try (JarFile jar = new JarFile(file)) {
|
||||
var manifest = jar.getEntry(ANDROID_MANIFEST_XML);
|
||||
if (manifest == null) {
|
||||
|
|
@ -309,22 +321,22 @@ public class LSPatch {
|
|||
System.err.println(file.getAbsolutePath() + " is not a valid apk file.");
|
||||
continue;
|
||||
}
|
||||
pair = ManifestParser.parseManifestFile(jar.getInputStream(manifest));
|
||||
if (pair == null) {
|
||||
triple = ManifestParser.parseManifestFile(jar.getInputStream(manifest));
|
||||
if (triple == null) {
|
||||
System.out.println();
|
||||
System.err.println(file.getAbsolutePath() + " is not a valid apk file.");
|
||||
continue;
|
||||
}
|
||||
System.out.println(pair.packageName);
|
||||
System.out.println(triple.packageName);
|
||||
} catch (Throwable e) {
|
||||
System.out.println();
|
||||
System.err.println(e.getMessage());
|
||||
}
|
||||
if (pair != null) {
|
||||
if (triple != null) {
|
||||
try (var is = new FileInputStream(file)) {
|
||||
zFile.add("assets/modules/" + pair.packageName, is);
|
||||
zFile.add("assets/modules/" + triple.packageName, is);
|
||||
} catch (Throwable e) {
|
||||
System.err.println("Embed " + pair.packageName + " with error: " + e.getMessage());
|
||||
System.err.println("Embed " + triple.packageName + " with error: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -335,7 +347,8 @@ public class LSPatch {
|
|||
ModificationProperty property = new ModificationProperty();
|
||||
|
||||
property.addApplicationAttribute(new AttributeItem(NodeValue.Application.DEBUGGABLE, debuggableFlag));
|
||||
property.addApplicationAttribute(new AttributeItem(NodeValue.Application.NAME, proxyName));
|
||||
property.addApplicationAttribute(new AttributeItem(NodeValue.Application.NAME, proxyApplication));
|
||||
property.addApplicationAttribute(new AttributeItem("appComponentFactory", proxyAppComponentFactory));
|
||||
|
||||
var os = new ByteArrayOutputStream();
|
||||
(new ManifestEditor(is, os, property)).processManifest();
|
||||
|
|
|
|||
|
|
@ -14,10 +14,11 @@ import wind.v1.XmlPullParserException;
|
|||
*/
|
||||
public class ManifestParser {
|
||||
|
||||
public static Pair parseManifestFile(InputStream is) throws IOException {
|
||||
public static Triple parseManifestFile(InputStream is) throws IOException {
|
||||
AXmlResourceParser parser = new AXmlResourceParser();
|
||||
String packageName = null;
|
||||
String applicationName = null;
|
||||
String appComponentFactory = null;
|
||||
try {
|
||||
parser.open(is);
|
||||
|
||||
|
|
@ -43,10 +44,15 @@ public class ManifestParser {
|
|||
if ("name".equals(attrName)) {
|
||||
applicationName = parser.getAttributeValue(i);
|
||||
}
|
||||
if ("appComponentFactory".equals(attrName)) {
|
||||
appComponentFactory = parser.getAttributeValue(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (packageName != null && packageName.length() > 0 && applicationName != null && applicationName.length() > 0) {
|
||||
return new Pair(packageName, applicationName);
|
||||
if (packageName != null && packageName.length() > 0 &&
|
||||
applicationName != null && applicationName.length() > 0 &&
|
||||
appComponentFactory != null && appComponentFactory.length() > 0) {
|
||||
return new Triple(packageName, applicationName, appComponentFactory);
|
||||
}
|
||||
}
|
||||
} else if (type == XmlPullParser.END_TAG) {
|
||||
|
|
@ -56,26 +62,28 @@ public class ManifestParser {
|
|||
} catch (XmlPullParserException | IOException e) {
|
||||
return null;
|
||||
}
|
||||
return new Pair(packageName, applicationName);
|
||||
return new Triple(packageName, applicationName, appComponentFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the package name and the main application name from the manifest file
|
||||
*/
|
||||
public static Pair parseManifestFile(String filePath) throws IOException {
|
||||
public static Triple 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 Triple {
|
||||
public String packageName;
|
||||
public String applicationName;
|
||||
public String appComponentFactory;
|
||||
|
||||
public Pair(String packageName, String applicationName) {
|
||||
public Triple(String packageName, String applicationName, String appComponentFactory) {
|
||||
this.packageName = packageName;
|
||||
this.applicationName = applicationName;
|
||||
this.appComponentFactory = appComponentFactory;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue