Code refactoring

This commit is contained in:
topjohnwu 2022-02-13 07:24:34 -08:00
parent c09b4dabc4
commit b7fc15d399
5 changed files with 142 additions and 176 deletions

View File

@ -1,33 +1,124 @@
package com.topjohnwu.magisk; package com.topjohnwu.magisk;
import android.app.job.JobService;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.ServiceInfo;
import com.topjohnwu.magisk.dummy.DummyProvider;
import com.topjohnwu.magisk.dummy.DummyReceiver;
import com.topjohnwu.magisk.dummy.DummyService;
import com.topjohnwu.magisk.utils.DynamicClassLoader; import com.topjohnwu.magisk.utils.DynamicClassLoader;
import java.io.File; import java.io.File;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import io.michaelrocks.paranoid.Obfuscate;
// Wrap the actual classloader as we only want to resolve classname // Wrap the actual classloader as we only want to resolve classname
// mapping when loading from platform (via LoadedApk.mClassLoader) // mapping when loading from platform (via LoadedApk.mClassLoader)
class InjectedClassLoader extends ClassLoader { @Obfuscate
class AppClassLoader extends ClassLoader {
InjectedClassLoader(File apk) { final Map<String, String> mapping = new HashMap<>();
AppClassLoader(File apk) {
super(new DynamicClassLoader(apk)); super(new DynamicClassLoader(apk));
} }
@Override @Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
String clz = DynLoad.componentMap.get(name); String clz = mapping.get(name);
name = clz != null ? clz : name; name = clz != null ? clz : name;
return super.loadClass(name, resolve); return super.loadClass(name, resolve);
} }
void updateComponentMap(PackageInfo stub, PackageInfo app) {
{
var src = stub.activities;
var dest = app.activities;
final ActivityInfo sa;
final ActivityInfo da;
final ActivityInfo sb;
final ActivityInfo db;
if (src[0].exported) {
sa = src[0];
sb = src[1];
} else {
sa = src[1];
sb = src[0];
}
if (dest[0].exported) {
da = dest[0];
db = dest[1];
} else {
da = dest[1];
db = dest[0];
}
mapping.put(sa.name, da.name);
mapping.put(sb.name, db.name);
}
{
var src = stub.services;
var dest = app.services;
final ServiceInfo sa;
final ServiceInfo da;
final ServiceInfo sb;
final ServiceInfo db;
if (JobService.PERMISSION_BIND.equals(src[0].permission)) {
sa = src[0];
sb = src[1];
} else {
sa = src[1];
sb = src[0];
}
if (JobService.PERMISSION_BIND.equals(dest[0].permission)) {
da = dest[0];
db = dest[1];
} else {
da = dest[1];
db = dest[0];
}
mapping.put(sa.name, da.name);
mapping.put(sb.name, db.name);
}
{
var src = stub.receivers;
var dest = app.receivers;
mapping.put(src[0].name, dest[0].name);
}
{
var src = stub.providers;
var dest = app.providers;
mapping.put(src[0].name, dest[0].name);
}
}
} }
class RedirectClassLoader extends ClassLoader { class StubClassLoader extends ClassLoader {
private final Map<String, Class<?>> mapping; private final Map<String, Class<?>> mapping = new HashMap<>();
RedirectClassLoader(Map<String, Class<?>> m) { StubClassLoader(PackageInfo info) {
super(RedirectClassLoader.class.getClassLoader()); super(StubClassLoader.class.getClassLoader());
mapping = m; for (var c : info.activities) {
mapping.put(c.name, DownloadActivity.class);
}
for (var c : info.services) {
mapping.put(c.name, DummyService.class);
}
for (var c : info.providers) {
mapping.put(c.name, DummyProvider.class);
}
for (var c : info.receivers) {
mapping.put(c.name, DummyReceiver.class);
}
} }
@Override @Override
@ -39,12 +130,15 @@ class RedirectClassLoader extends ClassLoader {
class DelegateClassLoader extends ClassLoader { class DelegateClassLoader extends ClassLoader {
// The active classloader
static ClassLoader cl = DelegateClassLoader.class.getClassLoader();
DelegateClassLoader() { DelegateClassLoader() {
super(null); super(null);
} }
@Override @Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
return DynLoad.loader.loadClass(name); return cl.loadClass(name);
} }
} }

View File

@ -9,7 +9,6 @@ import android.content.BroadcastReceiver;
import android.content.ContentProvider; import android.content.ContentProvider;
import android.content.Intent; import android.content.Intent;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.os.Process;
import com.topjohnwu.magisk.dummy.DummyProvider; import com.topjohnwu.magisk.dummy.DummyProvider;
import com.topjohnwu.magisk.dummy.DummyReceiver; import com.topjohnwu.magisk.dummy.DummyReceiver;
@ -26,11 +25,6 @@ public class DelegateComponentFactory extends AppComponentFactory {
@Override @Override
public ClassLoader instantiateClassLoader(ClassLoader cl, ApplicationInfo info) { public ClassLoader instantiateClassLoader(ClassLoader cl, ApplicationInfo info) {
if (Process.myUid() == 0) {
// Do not do anything in root process
return cl;
}
DynLoad.loadApk(info);
return new DelegateClassLoader(); return new DelegateClassLoader();
} }
@ -43,7 +37,7 @@ public class DelegateComponentFactory extends AppComponentFactory {
public Activity instantiateActivity(ClassLoader cl, String className, Intent intent) public Activity instantiateActivity(ClassLoader cl, String className, Intent intent)
throws ClassNotFoundException, IllegalAccessException, InstantiationException { throws ClassNotFoundException, IllegalAccessException, InstantiationException {
if (receiver != null) if (receiver != null)
return receiver.instantiateActivity(DynLoad.loader, className, intent); return receiver.instantiateActivity(DelegateClassLoader.cl, className, intent);
return create(className, DownloadActivity.class); return create(className, DownloadActivity.class);
} }
@ -51,7 +45,7 @@ public class DelegateComponentFactory extends AppComponentFactory {
public BroadcastReceiver instantiateReceiver(ClassLoader cl, String className, Intent intent) public BroadcastReceiver instantiateReceiver(ClassLoader cl, String className, Intent intent)
throws ClassNotFoundException, IllegalAccessException, InstantiationException { throws ClassNotFoundException, IllegalAccessException, InstantiationException {
if (receiver != null) if (receiver != null)
return receiver.instantiateReceiver(DynLoad.loader, className, intent); return receiver.instantiateReceiver(DelegateClassLoader.cl, className, intent);
return create(className, DummyReceiver.class); return create(className, DummyReceiver.class);
} }
@ -59,7 +53,7 @@ public class DelegateComponentFactory extends AppComponentFactory {
public Service instantiateService(ClassLoader cl, String className, Intent intent) public Service instantiateService(ClassLoader cl, String className, Intent intent)
throws ClassNotFoundException, IllegalAccessException, InstantiationException { throws ClassNotFoundException, IllegalAccessException, InstantiationException {
if (receiver != null) if (receiver != null)
return receiver.instantiateService(DynLoad.loader, className, intent); return receiver.instantiateService(DelegateClassLoader.cl, className, intent);
return create(className, DummyService.class); return create(className, DummyService.class);
} }
@ -67,7 +61,7 @@ public class DelegateComponentFactory extends AppComponentFactory {
public ContentProvider instantiateProvider(ClassLoader cl, String className) public ContentProvider instantiateProvider(ClassLoader cl, String className)
throws ClassNotFoundException, IllegalAccessException, InstantiationException { throws ClassNotFoundException, IllegalAccessException, InstantiationException {
if (receiver != null) if (receiver != null)
return receiver.instantiateProvider(DynLoad.loader, className); return receiver.instantiateProvider(DelegateClassLoader.cl, className);
return create(className, DummyProvider.class); return create(className, DummyProvider.class);
} }
@ -75,7 +69,7 @@ public class DelegateComponentFactory extends AppComponentFactory {
throws IllegalAccessException, InstantiationException { throws IllegalAccessException, InstantiationException {
try { try {
// noinspection unchecked // noinspection unchecked
return (T) DynLoad.loader.loadClass(name).newInstance(); return (T) DelegateClassLoader.cl.loadClass(name).newInstance();
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
return fallback.newInstance(); return fallback.newInstance();
} }

View File

@ -20,7 +20,8 @@ public class DelegateRootService extends ContextWrapper {
@Override @Override
protected void attachBaseContext(Context base) { protected void attachBaseContext(Context base) {
if (!DynLoad.loadApk(base)) ClassLoader loader = DynLoad.loadApk(base);
if (loader == null)
return; return;
try { try {
@ -29,7 +30,7 @@ public class DelegateRootService extends ContextWrapper {
File apk = StubApk.current(base); File apk = StubApk.current(base);
PackageManager pm = base.getPackageManager(); PackageManager pm = base.getPackageManager();
PackageInfo pkgInfo = pm.getPackageArchiveInfo(apk.getPath(), 0); PackageInfo pkgInfo = pm.getPackageArchiveInfo(apk.getPath(), 0);
DynLoad.loader.loadClass(pkgInfo.applicationInfo.className) loader.loadClass(pkgInfo.applicationInfo.className)
.getConstructor(Object.class) .getConstructor(Object.class)
.newInstance(data.getObject()); .newInstance(data.getObject());

View File

@ -49,7 +49,7 @@ public class DownloadActivity extends Activity {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if (DynLoad.isDynLoader()) { if (DelegateClassLoader.cl instanceof AppClassLoader) {
// For some reason activity is created before Application.attach(), // For some reason activity is created before Application.attach(),
// relaunch the activity using the same intent // relaunch the activity using the same intent
finishAffinity(); finishAffinity();

View File

@ -4,21 +4,13 @@ import static com.topjohnwu.magisk.BuildConfig.APPLICATION_ID;
import android.app.AppComponentFactory; import android.app.AppComponentFactory;
import android.app.Application; import android.app.Application;
import android.app.job.JobService;
import android.content.Context; import android.content.Context;
import android.content.ContextWrapper; import android.content.ContextWrapper;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.os.Build; import android.os.Build;
import android.os.Environment;
import android.util.Log; import android.util.Log;
import com.topjohnwu.magisk.dummy.DummyProvider;
import com.topjohnwu.magisk.dummy.DummyReceiver;
import com.topjohnwu.magisk.dummy.DummyService;
import com.topjohnwu.magisk.utils.APKInstall; import com.topjohnwu.magisk.utils.APKInstall;
import java.io.File; import java.io.File;
@ -28,7 +20,6 @@ import java.io.IOException;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map;
import io.michaelrocks.paranoid.Obfuscate; import io.michaelrocks.paranoid.Obfuscate;
@ -36,21 +27,12 @@ import io.michaelrocks.paranoid.Obfuscate;
@SuppressWarnings("ResultOfMethodCallIgnored") @SuppressWarnings("ResultOfMethodCallIgnored")
public class DynLoad { public class DynLoad {
// The current active classloader
static ClassLoader loader = DynLoad.class.getClassLoader();
static Object componentFactory; static Object componentFactory;
static Map<String, String> componentMap = new HashMap<>();
private static boolean loadedApk = false;
static StubApk.Data createApkData() { static StubApk.Data createApkData() {
var data = new StubApk.Data(); var data = new StubApk.Data();
data.setVersion(BuildConfig.STUB_VERSION); data.setVersion(BuildConfig.STUB_VERSION);
Map<String, String> map = new HashMap<>(); data.setClassToComponent(new HashMap<>());
for (var e : componentMap.entrySet()) {
map.put(e.getValue(), e.getKey());
}
data.setClassToComponent(map);
data.setRootService(DelegateRootService.class); data.setRootService(DelegateRootService.class);
return data; return data;
} }
@ -65,14 +47,10 @@ public class DynLoad {
} catch (Exception ignored) { /* Impossible */ } } catch (Exception ignored) { /* Impossible */ }
} }
// Dynamically load APK from internal or external storage // Dynamically load APK from internal, external storage, or previous app
static void loadApk(ApplicationInfo info) { static AppClassLoader loadApk(Context context) {
if (loadedApk) File apk = StubApk.current(context);
return; File update = StubApk.update(context);
loadedApk = true;
File apk = StubApk.current(info);
File update = StubApk.update(info);
if (update.exists()) { if (update.exists()) {
// Rename from update // Rename from update
@ -80,20 +58,8 @@ public class DynLoad {
} }
// Copy from external for easier development // Copy from external for easier development
if (BuildConfig.DEBUG) copy_from_ext: { if (BuildConfig.DEBUG) {
final File dir; File external = new File(context.getExternalFilesDir(null), "magisk.apk");
try {
var dirs = (File[]) Environment.class
.getMethod("buildExternalStorageAppFilesDirs", String.class)
.invoke(null, info.packageName);
if (dirs == null)
break copy_from_ext;
dir = dirs[0];
} catch (ReflectiveOperationException e) {
Log.e(DynLoad.class.getSimpleName(), "", e);
break copy_from_ext;
}
File external = new File(dir, "magisk.apk");
if (external.exists()) { if (external.exists()) {
try { try {
var in = new FileInputStream(external); var in = new FileInputStream(external);
@ -111,20 +77,11 @@ public class DynLoad {
} }
if (apk.exists()) { if (apk.exists()) {
loader = new InjectedClassLoader(apk); return new AppClassLoader(apk);
} }
}
// Dynamically load APK from internal, external storage, or previous app
static boolean loadApk(Context context) {
// Trigger folder creation
context.getExternalFilesDir(null);
loadApk(context.getApplicationInfo());
// If no APK is loaded, attempt to copy from previous app // If no APK is loaded, attempt to copy from previous app
if (!isDynLoader() && !context.getPackageName().equals(APPLICATION_ID)) { if (!context.getPackageName().equals(APPLICATION_ID)) {
File apk = StubApk.current(context);
try { try {
var info = context.getPackageManager().getApplicationInfo(APPLICATION_ID, 0); var info = context.getPackageManager().getApplicationInfo(APPLICATION_ID, 0);
var src = new FileInputStream(info.sourceDir); var src = new FileInputStream(info.sourceDir);
@ -132,7 +89,7 @@ public class DynLoad {
try (src; out) { try (src; out) {
APKInstall.transfer(src, out); APKInstall.transfer(src, out);
} }
loader = new InjectedClassLoader(apk); return new AppClassLoader(apk);
} catch (PackageManager.NameNotFoundException ignored) { } catch (PackageManager.NameNotFoundException ignored) {
} catch (IOException e) { } catch (IOException e) {
Log.e(DynLoad.class.getSimpleName(), "", e); Log.e(DynLoad.class.getSimpleName(), "", e);
@ -140,7 +97,7 @@ public class DynLoad {
} }
} }
return isDynLoader(); return null;
} }
// Dynamically load APK and create the Application instance from the loaded APK // Dynamically load APK and create the Application instance from the loaded APK
@ -161,32 +118,37 @@ public class DynLoad {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
if (!loadApk(context)) {
loader = new RedirectClassLoader(createInternalMap(info));
return null;
}
File apk = StubApk.current(context); File apk = StubApk.current(context);
PackageManager pm = context.getPackageManager();
try { final var cl = loadApk(context);
var pkgInfo = pm.getPackageArchiveInfo(apk.getPath(), flags); if (cl != null) try {
var pkgInfo = context.getPackageManager()
.getPackageArchiveInfo(apk.getPath(), flags);
cl.updateComponentMap(info, pkgInfo);
var appInfo = pkgInfo.applicationInfo; var appInfo = pkgInfo.applicationInfo;
updateComponentMap(info, pkgInfo); var data = createApkData();
var map = data.getClassToComponent();
// Create the inverse mapping (class to component name)
for (var e : cl.mapping.entrySet()) {
map.put(e.getValue(), e.getKey());
}
// Create the receiver Application // Create the receiver Application
var data = createApkData(); var app = (Application) cl.loadClass(appInfo.className)
var app = (Application) loader.loadClass(appInfo.className)
.getConstructor(Object.class) .getConstructor(Object.class)
.newInstance(data.getObject()); .newInstance(data.getObject());
// Create the receiver component factory // Create the receiver component factory
if (Build.VERSION.SDK_INT >= 28 && componentFactory != null) { if (Build.VERSION.SDK_INT >= 28 && componentFactory != null) {
Object factory = loader.loadClass(appInfo.appComponentFactory).newInstance(); Object factory = cl.loadClass(appInfo.appComponentFactory).newInstance();
var delegate = (DelegateComponentFactory) componentFactory; var delegate = (DelegateComponentFactory) componentFactory;
delegate.receiver = (AppComponentFactory) factory; delegate.receiver = (AppComponentFactory) factory;
} }
DelegateClassLoader.cl = cl;
// Send real application to attachBaseContext // Send real application to attachBaseContext
attachContext(app, context); attachContext(app, context);
@ -196,97 +158,12 @@ public class DynLoad {
apk.delete(); apk.delete();
} }
DelegateClassLoader.cl = new StubClassLoader(info);
return null; return null;
} }
private static Map<String, Class<?>> createInternalMap(PackageInfo info) {
Map<String, Class<?>> map = new HashMap<>();
for (var c : info.activities) {
map.put(c.name, DownloadActivity.class);
}
for (var c : info.services) {
map.put(c.name, DummyService.class);
}
for (var c : info.providers) {
map.put(c.name, DummyProvider.class);
}
for (var c : info.receivers) {
map.put(c.name, DummyReceiver.class);
}
return map;
}
private static void updateComponentMap(PackageInfo from, PackageInfo to) {
{
var src = from.activities;
var dest = to.activities;
final ActivityInfo sa;
final ActivityInfo da;
final ActivityInfo sb;
final ActivityInfo db;
if (src[0].exported) {
sa = src[0];
sb = src[1];
} else {
sa = src[1];
sb = src[0];
}
if (dest[0].exported) {
da = dest[0];
db = dest[1];
} else {
da = dest[1];
db = dest[0];
}
componentMap.put(sa.name, da.name);
componentMap.put(sb.name, db.name);
}
{
var src = from.services;
var dest = to.services;
final ServiceInfo sa;
final ServiceInfo da;
final ServiceInfo sb;
final ServiceInfo db;
if (JobService.PERMISSION_BIND.equals(src[0].permission)) {
sa = src[0];
sb = src[1];
} else {
sa = src[1];
sb = src[0];
}
if (JobService.PERMISSION_BIND.equals(dest[0].permission)) {
da = dest[0];
db = dest[1];
} else {
da = dest[1];
db = dest[0];
}
componentMap.put(sa.name, da.name);
componentMap.put(sb.name, db.name);
}
{
var src = from.receivers;
var dest = to.receivers;
componentMap.put(src[0].name, dest[0].name);
}
{
var src = from.providers;
var dest = to.providers;
componentMap.put(src[0].name, dest[0].name);
}
}
static boolean isDynLoader() {
return loader instanceof InjectedClassLoader;
}
// Replace LoadedApk mClassLoader // Replace LoadedApk mClassLoader
@SuppressWarnings("ConstantConditions")
private static void replaceClassLoader(Context context) { private static void replaceClassLoader(Context context) {
// Get ContextImpl // Get ContextImpl
while (context instanceof ContextWrapper) { while (context instanceof ContextWrapper) {