mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-12-02 22:51:49 +00:00
Generate class mapping at runtime
This commit is contained in:
@@ -3,6 +3,7 @@ package com.topjohnwu.magisk;
|
||||
import com.topjohnwu.magisk.utils.DynamicClassLoader;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
|
||||
// Wrap the actual classloader as we only want to resolve classname
|
||||
// mapping when loading from platform (via LoadedApk.mClassLoader)
|
||||
@@ -14,19 +15,24 @@ class InjectedClassLoader extends ClassLoader {
|
||||
|
||||
@Override
|
||||
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
|
||||
return super.loadClass(Mapping.get(name), resolve);
|
||||
String clz = DynLoad.componentMap.get(name);
|
||||
name = clz != null ? clz : name;
|
||||
return super.loadClass(name, resolve);
|
||||
}
|
||||
}
|
||||
|
||||
class RedirectClassLoader extends ClassLoader {
|
||||
|
||||
RedirectClassLoader() {
|
||||
private final Map<String, Class<?>> mapping;
|
||||
|
||||
RedirectClassLoader(Map<String, Class<?>> m) {
|
||||
super(RedirectClassLoader.class.getClassLoader());
|
||||
mapping = m;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
|
||||
Class<?> clz = Mapping.internalMap.get(name);
|
||||
Class<?> clz = mapping.get(name);
|
||||
return clz == null ? super.loadClass(name, resolve) : clz;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,10 @@ import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.os.Process;
|
||||
|
||||
import com.topjohnwu.magisk.dummy.DummyProvider;
|
||||
import com.topjohnwu.magisk.dummy.DummyReceiver;
|
||||
import com.topjohnwu.magisk.dummy.DummyService;
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
public class DelegateComponentFactory extends AppComponentFactory {
|
||||
|
||||
@@ -40,7 +44,7 @@ public class DelegateComponentFactory extends AppComponentFactory {
|
||||
throws ClassNotFoundException, IllegalAccessException, InstantiationException {
|
||||
if (receiver != null)
|
||||
return receiver.instantiateActivity(DynLoad.loader, className, intent);
|
||||
return create(className);
|
||||
return create(className, DownloadActivity.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -48,7 +52,7 @@ public class DelegateComponentFactory extends AppComponentFactory {
|
||||
throws ClassNotFoundException, IllegalAccessException, InstantiationException {
|
||||
if (receiver != null)
|
||||
return receiver.instantiateReceiver(DynLoad.loader, className, intent);
|
||||
return create(className);
|
||||
return create(className, DummyReceiver.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -56,7 +60,7 @@ public class DelegateComponentFactory extends AppComponentFactory {
|
||||
throws ClassNotFoundException, IllegalAccessException, InstantiationException {
|
||||
if (receiver != null)
|
||||
return receiver.instantiateService(DynLoad.loader, className, intent);
|
||||
return create(className);
|
||||
return create(className, DummyService.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -64,12 +68,17 @@ public class DelegateComponentFactory extends AppComponentFactory {
|
||||
throws ClassNotFoundException, IllegalAccessException, InstantiationException {
|
||||
if (receiver != null)
|
||||
return receiver.instantiateProvider(DynLoad.loader, className);
|
||||
return create(className);
|
||||
return create(className, DummyProvider.class);
|
||||
}
|
||||
|
||||
private <T> T create(String name)
|
||||
throws ClassNotFoundException, IllegalAccessException, InstantiationException{
|
||||
return (T) DynLoad.loader.loadClass(name).newInstance();
|
||||
private <T> T create(String name, Class<T> fallback)
|
||||
throws IllegalAccessException, InstantiationException {
|
||||
try {
|
||||
// noinspection unchecked
|
||||
return (T) DynLoad.loader.loadClass(name).newInstance();
|
||||
} catch (ClassNotFoundException e) {
|
||||
return fallback.newInstance();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -49,6 +49,14 @@ public class DownloadActivity extends Activity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (DynLoad.isDynLoader()) {
|
||||
// For some reason activity is created before Application.attach(),
|
||||
// relaunch the activity using the same intent
|
||||
finishAffinity();
|
||||
startActivity(getIntent());
|
||||
return;
|
||||
}
|
||||
|
||||
themed = new ContextThemeWrapper(this, android.R.style.Theme_DeviceDefault);
|
||||
|
||||
// Only download and dynamic load full APK if hidden
|
||||
|
||||
@@ -4,14 +4,21 @@ import static com.topjohnwu.magisk.BuildConfig.APPLICATION_ID;
|
||||
|
||||
import android.app.AppComponentFactory;
|
||||
import android.app.Application;
|
||||
import android.app.job.JobService;
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
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 java.io.File;
|
||||
@@ -20,6 +27,8 @@ import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import io.michaelrocks.paranoid.Obfuscate;
|
||||
|
||||
@@ -28,15 +37,20 @@ import io.michaelrocks.paranoid.Obfuscate;
|
||||
public class DynLoad {
|
||||
|
||||
// The current active classloader
|
||||
static ClassLoader loader = new RedirectClassLoader();
|
||||
static ClassLoader loader = DynLoad.class.getClassLoader();
|
||||
static Object componentFactory;
|
||||
static Map<String, String> componentMap = new HashMap<>();
|
||||
|
||||
private static boolean loadedApk = false;
|
||||
|
||||
static StubApk.Data createApkData() {
|
||||
var data = new StubApk.Data();
|
||||
data.setVersion(BuildConfig.STUB_VERSION);
|
||||
data.setClassToComponent(Mapping.inverseMap);
|
||||
Map<String, String> map = new HashMap<>();
|
||||
for (var e : componentMap.entrySet()) {
|
||||
map.put(e.getValue(), e.getKey());
|
||||
}
|
||||
data.setClassToComponent(map);
|
||||
data.setRootService(DelegateRootService.class);
|
||||
return data;
|
||||
}
|
||||
@@ -135,23 +149,40 @@ public class DynLoad {
|
||||
if (Build.VERSION.SDK_INT < 29)
|
||||
replaceClassLoader(context);
|
||||
|
||||
if (!loadApk(context))
|
||||
int flags = PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES
|
||||
| PackageManager.GET_PROVIDERS | PackageManager.GET_RECEIVERS;
|
||||
|
||||
final PackageInfo info;
|
||||
try {
|
||||
info = context.getPackageManager()
|
||||
.getPackageInfo(context.getPackageName(), flags);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
// Impossible
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
if (!loadApk(context)) {
|
||||
loader = new RedirectClassLoader(createInternalMap(info));
|
||||
return null;
|
||||
}
|
||||
|
||||
File apk = StubApk.current(context);
|
||||
PackageManager pm = context.getPackageManager();
|
||||
try {
|
||||
var info = pm.getPackageArchiveInfo(apk.getPath(), 0).applicationInfo;
|
||||
var pkgInfo = pm.getPackageArchiveInfo(apk.getPath(), flags);
|
||||
var appInfo = pkgInfo.applicationInfo;
|
||||
|
||||
updateComponentMap(info, pkgInfo);
|
||||
|
||||
// Create the receiver Application
|
||||
var data = createApkData();
|
||||
var app = (Application) loader.loadClass(info.className)
|
||||
var app = (Application) loader.loadClass(appInfo.className)
|
||||
.getConstructor(Object.class)
|
||||
.newInstance(data.getObject());
|
||||
|
||||
// Create the receiver component factory
|
||||
if (Build.VERSION.SDK_INT >= 28 && componentFactory != null) {
|
||||
Object factory = loader.loadClass(info.appComponentFactory).newInstance();
|
||||
Object factory = loader.loadClass(appInfo.appComponentFactory).newInstance();
|
||||
var delegate = (DelegateComponentFactory) componentFactory;
|
||||
delegate.receiver = (AppComponentFactory) factory;
|
||||
}
|
||||
@@ -168,7 +199,90 @@ public class DynLoad {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isDynLoader() {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.topjohnwu.magisk.dummy;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
|
||||
public class DummyService extends Service {
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user