Create AppComponentFactory stub

This commit is contained in:
Nullptr 2021-07-16 15:32:56 +08:00 committed by LoveSy
parent 2d41905b22
commit 6a814e571b
4 changed files with 145 additions and 23 deletions

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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();

View File

@ -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;
} }
} }