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")
|
@Parameter(names = {"-f", "--force"}, description = "Force overwrite exists output file")
|
||||||
private boolean forceOverwrite = false;
|
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")
|
@Parameter(names = {"-d", "--debuggable"}, description = "Set app to be debuggable")
|
||||||
private boolean debuggableFlag = false;
|
private boolean debuggableFlag = false;
|
||||||
|
|
@ -78,9 +79,10 @@ public class LSPatch {
|
||||||
@Parameter(names = {"-m", "--embed"}, description = "Embed provided modules to apk")
|
@Parameter(names = {"-m", "--embed"}, description = "Embed provided modules to apk")
|
||||||
private List<String> modules = new ArrayList<>();
|
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 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 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 ANDROID_MANIFEST_XML = "AndroidManifest.xml";
|
||||||
private static final String RESOURCES_ARSC = "resources.arsc";
|
private static final String RESOURCES_ARSC = "resources.arsc";
|
||||||
private static final HashSet<String> APK_LIB_PATH_ARRAY = new HashSet<>(Arrays.asList(
|
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(
|
final var alignmentRule = AlignmentRules.compose(
|
||||||
AlignmentRules.constantForSuffix(RESOURCES_ARSC, 4),
|
AlignmentRules.constantForSuffix(RESOURCES_ARSC, 4),
|
||||||
AlignmentRules.constantForSuffix(ORIGIN_APK_ASSET_PATH, 4096)
|
AlignmentRules.constantForSuffix(ORIGINAL_APK_ASSET_PATH, 4096)
|
||||||
);
|
);
|
||||||
zFileOptions.setAlignmentRule(alignmentRule);
|
zFileOptions.setAlignmentRule(alignmentRule);
|
||||||
try (ZFile zFile = ZFile.openReadWrite(tmpApk, zFileOptions)) {
|
try (ZFile zFile = ZFile.openReadWrite(tmpApk, zFileOptions)) {
|
||||||
// copy origin apk to assets
|
// 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
|
// remove unnecessary files
|
||||||
for (StoredEntry storedEntry : zFile.entries()) {
|
for (StoredEntry storedEntry : zFile.entries()) {
|
||||||
|
|
@ -186,13 +188,16 @@ public class LSPatch {
|
||||||
throw new PatchError("Provided file is not a valid apk");
|
throw new PatchError("Provided file is not a valid apk");
|
||||||
|
|
||||||
// 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(manifestEntry.open());
|
ManifestParser.Triple triple = ManifestParser.parseManifestFile(manifestEntry.open());
|
||||||
if (pair == null)
|
if (triple == null)
|
||||||
throw new PatchError("Failed to parse AndroidManifest.xml");
|
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 application name: " + applicationName);
|
||||||
|
System.out.println("original appComponentFactory class: " + appComponentFactory);
|
||||||
|
}
|
||||||
|
|
||||||
System.out.println("Patching apk...");
|
System.out.println("Patching apk...");
|
||||||
// modify manifest
|
// modify manifest
|
||||||
|
|
@ -202,6 +207,13 @@ public class LSPatch {
|
||||||
throw new PatchError("Error when modifying manifest: " + e);
|
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
|
// save original main application name to asset file even its empty
|
||||||
try (var is = new ByteArrayInputStream(applicationName.getBytes(StandardCharsets.UTF_8))) {
|
try (var is = new ByteArrayInputStream(applicationName.getBytes(StandardCharsets.UTF_8))) {
|
||||||
zFile.add(APPLICATION_NAME_ASSET_PATH, is);
|
zFile.add(APPLICATION_NAME_ASSET_PATH, is);
|
||||||
|
|
@ -301,7 +313,7 @@ public class LSPatch {
|
||||||
|
|
||||||
System.out.print("Embedding module ");
|
System.out.print("Embedding module ");
|
||||||
|
|
||||||
ManifestParser.Pair pair = null;
|
ManifestParser.Triple triple = null;
|
||||||
try (JarFile jar = new JarFile(file)) {
|
try (JarFile jar = new JarFile(file)) {
|
||||||
var manifest = jar.getEntry(ANDROID_MANIFEST_XML);
|
var manifest = jar.getEntry(ANDROID_MANIFEST_XML);
|
||||||
if (manifest == null) {
|
if (manifest == null) {
|
||||||
|
|
@ -309,22 +321,22 @@ public class LSPatch {
|
||||||
System.err.println(file.getAbsolutePath() + " is not a valid apk file.");
|
System.err.println(file.getAbsolutePath() + " is not a valid apk file.");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
pair = ManifestParser.parseManifestFile(jar.getInputStream(manifest));
|
triple = ManifestParser.parseManifestFile(jar.getInputStream(manifest));
|
||||||
if (pair == null) {
|
if (triple == null) {
|
||||||
System.out.println();
|
System.out.println();
|
||||||
System.err.println(file.getAbsolutePath() + " is not a valid apk file.");
|
System.err.println(file.getAbsolutePath() + " is not a valid apk file.");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
System.out.println(pair.packageName);
|
System.out.println(triple.packageName);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
System.out.println();
|
System.out.println();
|
||||||
System.err.println(e.getMessage());
|
System.err.println(e.getMessage());
|
||||||
}
|
}
|
||||||
if (pair != null) {
|
if (triple != null) {
|
||||||
try (var is = new FileInputStream(file)) {
|
try (var is = new FileInputStream(file)) {
|
||||||
zFile.add("assets/modules/" + pair.packageName, is);
|
zFile.add("assets/modules/" + triple.packageName, is);
|
||||||
} catch (Throwable e) {
|
} 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();
|
ModificationProperty property = new ModificationProperty();
|
||||||
|
|
||||||
property.addApplicationAttribute(new AttributeItem(NodeValue.Application.DEBUGGABLE, debuggableFlag));
|
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();
|
var os = new ByteArrayOutputStream();
|
||||||
(new ManifestEditor(is, os, property)).processManifest();
|
(new ManifestEditor(is, os, property)).processManifest();
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,11 @@ import wind.v1.XmlPullParserException;
|
||||||
*/
|
*/
|
||||||
public class ManifestParser {
|
public class ManifestParser {
|
||||||
|
|
||||||
public static Pair parseManifestFile(InputStream is) throws IOException {
|
public static Triple parseManifestFile(InputStream is) throws IOException {
|
||||||
AXmlResourceParser parser = new AXmlResourceParser();
|
AXmlResourceParser parser = new AXmlResourceParser();
|
||||||
String packageName = null;
|
String packageName = null;
|
||||||
String applicationName = null;
|
String applicationName = null;
|
||||||
|
String appComponentFactory = null;
|
||||||
try {
|
try {
|
||||||
parser.open(is);
|
parser.open(is);
|
||||||
|
|
||||||
|
|
@ -43,10 +44,15 @@ public class ManifestParser {
|
||||||
if ("name".equals(attrName)) {
|
if ("name".equals(attrName)) {
|
||||||
applicationName = parser.getAttributeValue(i);
|
applicationName = parser.getAttributeValue(i);
|
||||||
}
|
}
|
||||||
|
if ("appComponentFactory".equals(attrName)) {
|
||||||
|
appComponentFactory = parser.getAttributeValue(i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packageName != null && packageName.length() > 0 && applicationName != null && applicationName.length() > 0) {
|
if (packageName != null && packageName.length() > 0 &&
|
||||||
return new Pair(packageName, applicationName);
|
applicationName != null && applicationName.length() > 0 &&
|
||||||
|
appComponentFactory != null && appComponentFactory.length() > 0) {
|
||||||
|
return new Triple(packageName, applicationName, appComponentFactory);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (type == XmlPullParser.END_TAG) {
|
} else if (type == XmlPullParser.END_TAG) {
|
||||||
|
|
@ -56,26 +62,28 @@ public class ManifestParser {
|
||||||
} catch (XmlPullParserException | IOException e) {
|
} catch (XmlPullParserException | IOException e) {
|
||||||
return null;
|
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
|
* 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);
|
File file = new File(filePath);
|
||||||
try (var is = new FileInputStream(file)) {
|
try (var is = new FileInputStream(file)) {
|
||||||
return parseManifestFile(is);
|
return parseManifestFile(is);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Pair {
|
public static class Triple {
|
||||||
public String packageName;
|
public String packageName;
|
||||||
public String applicationName;
|
public String applicationName;
|
||||||
|
public String appComponentFactory;
|
||||||
|
|
||||||
public Pair(String packageName, String applicationName) {
|
public Triple(String packageName, String applicationName, String appComponentFactory) {
|
||||||
this.packageName = packageName;
|
this.packageName = packageName;
|
||||||
this.applicationName = applicationName;
|
this.applicationName = applicationName;
|
||||||
|
this.appComponentFactory = appComponentFactory;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue