2020-12-29 01:44:02 -08:00
|
|
|
package com.topjohnwu.magisk;
|
|
|
|
|
2022-02-02 04:58:31 -08:00
|
|
|
import static com.topjohnwu.magisk.BuildConfig.APPLICATION_ID;
|
|
|
|
|
2020-12-29 01:44:02 -08:00
|
|
|
import android.app.AppComponentFactory;
|
|
|
|
import android.app.Application;
|
2022-02-13 06:22:42 -08:00
|
|
|
import android.app.job.JobService;
|
2020-12-29 01:44:02 -08:00
|
|
|
import android.content.Context;
|
2021-01-26 03:40:25 -08:00
|
|
|
import android.content.ContextWrapper;
|
2022-02-13 06:22:42 -08:00
|
|
|
import android.content.pm.ActivityInfo;
|
2021-04-17 22:14:54 -07:00
|
|
|
import android.content.pm.ApplicationInfo;
|
2022-02-13 06:22:42 -08:00
|
|
|
import android.content.pm.PackageInfo;
|
2021-04-17 22:14:54 -07:00
|
|
|
import android.content.pm.PackageManager;
|
2022-02-13 06:22:42 -08:00
|
|
|
import android.content.pm.ServiceInfo;
|
2021-01-26 03:40:25 -08:00
|
|
|
import android.os.Build;
|
2022-02-01 22:43:44 -08:00
|
|
|
import android.os.Environment;
|
2020-12-29 01:44:02 -08:00
|
|
|
import android.util.Log;
|
|
|
|
|
2022-02-13 06:22:42 -08:00
|
|
|
import com.topjohnwu.magisk.dummy.DummyProvider;
|
|
|
|
import com.topjohnwu.magisk.dummy.DummyReceiver;
|
|
|
|
import com.topjohnwu.magisk.dummy.DummyService;
|
2021-12-15 03:18:23 +08:00
|
|
|
import com.topjohnwu.magisk.utils.APKInstall;
|
|
|
|
|
2020-12-29 01:44:02 -08:00
|
|
|
import java.io.File;
|
2021-12-12 23:52:59 -08:00
|
|
|
import java.io.FileInputStream;
|
2020-12-29 01:44:02 -08:00
|
|
|
import java.io.FileOutputStream;
|
2021-12-12 23:52:59 -08:00
|
|
|
import java.io.IOException;
|
2021-01-26 03:40:25 -08:00
|
|
|
import java.lang.reflect.Field;
|
2022-02-01 22:43:44 -08:00
|
|
|
import java.lang.reflect.Method;
|
2022-02-13 06:22:42 -08:00
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.Map;
|
2020-12-29 01:44:02 -08:00
|
|
|
|
2021-09-02 21:31:33 -07:00
|
|
|
import io.michaelrocks.paranoid.Obfuscate;
|
|
|
|
|
|
|
|
@Obfuscate
|
2021-12-15 03:18:23 +08:00
|
|
|
@SuppressWarnings("ResultOfMethodCallIgnored")
|
2021-12-13 03:57:27 -08:00
|
|
|
public class DynLoad {
|
2020-12-29 01:44:02 -08:00
|
|
|
|
2022-02-01 22:43:44 -08:00
|
|
|
// The current active classloader
|
2022-02-13 06:22:42 -08:00
|
|
|
static ClassLoader loader = DynLoad.class.getClassLoader();
|
2021-04-17 22:14:54 -07:00
|
|
|
static Object componentFactory;
|
2022-02-13 06:22:42 -08:00
|
|
|
static Map<String, String> componentMap = new HashMap<>();
|
2021-04-17 22:14:54 -07:00
|
|
|
|
2022-02-01 22:43:44 -08:00
|
|
|
private static boolean loadedApk = false;
|
|
|
|
|
2022-02-13 03:32:11 -08:00
|
|
|
static StubApk.Data createApkData() {
|
|
|
|
var data = new StubApk.Data();
|
|
|
|
data.setVersion(BuildConfig.STUB_VERSION);
|
2022-02-13 06:22:42 -08:00
|
|
|
Map<String, String> map = new HashMap<>();
|
|
|
|
for (var e : componentMap.entrySet()) {
|
|
|
|
map.put(e.getValue(), e.getKey());
|
|
|
|
}
|
|
|
|
data.setClassToComponent(map);
|
2022-02-13 03:32:11 -08:00
|
|
|
data.setRootService(DelegateRootService.class);
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
2022-02-01 22:43:44 -08:00
|
|
|
static void attachContext(Object o, Context context) {
|
|
|
|
if (!(o instanceof ContextWrapper))
|
|
|
|
return;
|
|
|
|
try {
|
|
|
|
Method m = ContextWrapper.class.getDeclaredMethod("attachBaseContext", Context.class);
|
|
|
|
m.setAccessible(true);
|
|
|
|
m.invoke(o, context);
|
|
|
|
} catch (Exception ignored) { /* Impossible */ }
|
|
|
|
}
|
|
|
|
|
|
|
|
// Dynamically load APK from internal or external storage
|
2022-02-02 02:50:27 -08:00
|
|
|
static void loadApk(ApplicationInfo info) {
|
2022-02-01 22:43:44 -08:00
|
|
|
if (loadedApk)
|
|
|
|
return;
|
|
|
|
loadedApk = true;
|
|
|
|
|
2022-02-02 02:50:27 -08:00
|
|
|
File apk = StubApk.current(info);
|
|
|
|
File update = StubApk.update(info);
|
2021-12-12 23:52:59 -08:00
|
|
|
|
|
|
|
if (update.exists()) {
|
|
|
|
// Rename from update
|
2020-12-29 01:44:02 -08:00
|
|
|
update.renameTo(apk);
|
2021-12-12 23:52:59 -08:00
|
|
|
}
|
|
|
|
|
2022-02-01 22:43:44 -08:00
|
|
|
// Copy from external for easier development
|
|
|
|
if (BuildConfig.DEBUG) copy_from_ext: {
|
|
|
|
final File dir;
|
|
|
|
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");
|
2021-12-12 23:52:59 -08:00
|
|
|
if (external.exists()) {
|
|
|
|
try {
|
2021-12-15 03:18:23 +08:00
|
|
|
var in = new FileInputStream(external);
|
|
|
|
var out = new FileOutputStream(apk);
|
|
|
|
try (in; out) {
|
|
|
|
APKInstall.transfer(in, out);
|
|
|
|
}
|
2021-12-12 23:52:59 -08:00
|
|
|
} catch (IOException e) {
|
2021-12-13 03:57:27 -08:00
|
|
|
Log.e(DynLoad.class.getSimpleName(), "", e);
|
2021-12-12 23:52:59 -08:00
|
|
|
apk.delete();
|
|
|
|
} finally {
|
|
|
|
external.delete();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-01 22:43:44 -08:00
|
|
|
if (apk.exists()) {
|
|
|
|
loader = new InjectedClassLoader(apk);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-13 03:32:11 -08:00
|
|
|
// Dynamically load APK from internal, external storage, or previous app
|
|
|
|
static boolean loadApk(Context context) {
|
2022-02-01 22:43:44 -08:00
|
|
|
// Trigger folder creation
|
|
|
|
context.getExternalFilesDir(null);
|
|
|
|
|
2022-02-02 05:06:12 -08:00
|
|
|
loadApk(context.getApplicationInfo());
|
|
|
|
|
|
|
|
// If no APK is loaded, attempt to copy from previous app
|
2022-02-02 04:58:31 -08:00
|
|
|
if (!isDynLoader() && !context.getPackageName().equals(APPLICATION_ID)) {
|
2022-02-13 03:32:11 -08:00
|
|
|
File apk = StubApk.current(context);
|
2021-12-12 23:52:59 -08:00
|
|
|
try {
|
2022-02-02 04:58:31 -08:00
|
|
|
var info = context.getPackageManager().getApplicationInfo(APPLICATION_ID, 0);
|
|
|
|
var src = new FileInputStream(info.sourceDir);
|
|
|
|
var out = new FileOutputStream(apk);
|
|
|
|
try (src; out) {
|
|
|
|
APKInstall.transfer(src, out);
|
2020-12-29 01:44:02 -08:00
|
|
|
}
|
2022-02-02 04:58:31 -08:00
|
|
|
loader = new InjectedClassLoader(apk);
|
|
|
|
} catch (PackageManager.NameNotFoundException ignored) {
|
2021-12-12 23:52:59 -08:00
|
|
|
} catch (IOException e) {
|
2021-12-13 03:57:27 -08:00
|
|
|
Log.e(DynLoad.class.getSimpleName(), "", e);
|
2021-12-12 23:52:59 -08:00
|
|
|
apk.delete();
|
|
|
|
}
|
2020-12-29 01:44:02 -08:00
|
|
|
}
|
2021-12-12 23:52:59 -08:00
|
|
|
|
2022-02-13 03:32:11 -08:00
|
|
|
return isDynLoader();
|
2021-12-13 03:57:27 -08:00
|
|
|
}
|
|
|
|
|
2022-02-13 03:32:11 -08:00
|
|
|
// Dynamically load APK and create the Application instance from the loaded APK
|
2022-02-01 22:43:44 -08:00
|
|
|
static Application createAndSetupApp(Application context) {
|
2022-02-13 03:32:11 -08:00
|
|
|
// On API >= 29, AppComponentFactory will replace the ClassLoader for us
|
2022-02-01 22:43:44 -08:00
|
|
|
if (Build.VERSION.SDK_INT < 29)
|
|
|
|
replaceClassLoader(context);
|
2021-04-17 22:14:54 -07:00
|
|
|
|
2022-02-13 06:22:42 -08:00
|
|
|
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));
|
2022-02-13 03:32:11 -08:00
|
|
|
return null;
|
2022-02-13 06:22:42 -08:00
|
|
|
}
|
2022-02-13 03:32:11 -08:00
|
|
|
|
|
|
|
File apk = StubApk.current(context);
|
|
|
|
PackageManager pm = context.getPackageManager();
|
|
|
|
try {
|
2022-02-13 06:22:42 -08:00
|
|
|
var pkgInfo = pm.getPackageArchiveInfo(apk.getPath(), flags);
|
|
|
|
var appInfo = pkgInfo.applicationInfo;
|
|
|
|
|
|
|
|
updateComponentMap(info, pkgInfo);
|
2022-02-13 03:32:11 -08:00
|
|
|
|
|
|
|
// Create the receiver Application
|
|
|
|
var data = createApkData();
|
2022-02-13 06:22:42 -08:00
|
|
|
var app = (Application) loader.loadClass(appInfo.className)
|
2022-02-13 03:32:11 -08:00
|
|
|
.getConstructor(Object.class)
|
|
|
|
.newInstance(data.getObject());
|
|
|
|
|
|
|
|
// Create the receiver component factory
|
|
|
|
if (Build.VERSION.SDK_INT >= 28 && componentFactory != null) {
|
2022-02-13 06:22:42 -08:00
|
|
|
Object factory = loader.loadClass(appInfo.appComponentFactory).newInstance();
|
2022-02-13 03:32:11 -08:00
|
|
|
var delegate = (DelegateComponentFactory) componentFactory;
|
|
|
|
delegate.receiver = (AppComponentFactory) factory;
|
|
|
|
}
|
|
|
|
|
2022-02-01 22:43:44 -08:00
|
|
|
// Send real application to attachBaseContext
|
|
|
|
attachContext(app, context);
|
2022-02-13 03:32:11 -08:00
|
|
|
|
|
|
|
return app;
|
|
|
|
} catch (Exception e) {
|
|
|
|
Log.e(DynLoad.class.getSimpleName(), "", e);
|
|
|
|
apk.delete();
|
2020-12-29 01:44:02 -08:00
|
|
|
}
|
2022-02-13 03:32:11 -08:00
|
|
|
|
|
|
|
return null;
|
2022-02-01 22:43:44 -08:00
|
|
|
}
|
2021-04-17 22:14:54 -07:00
|
|
|
|
2022-02-13 06:22:42 -08:00
|
|
|
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() {
|
2022-02-01 22:43:44 -08:00
|
|
|
return loader instanceof InjectedClassLoader;
|
2021-01-26 03:40:25 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Replace LoadedApk mClassLoader
|
2022-02-01 22:43:44 -08:00
|
|
|
private static void replaceClassLoader(Context context) {
|
2021-12-13 03:57:27 -08:00
|
|
|
// Get ContextImpl
|
|
|
|
while (context instanceof ContextWrapper) {
|
|
|
|
context = ((ContextWrapper) context).getBaseContext();
|
|
|
|
}
|
|
|
|
|
2022-02-01 22:43:44 -08:00
|
|
|
try {
|
|
|
|
Field mInfo = context.getClass().getDeclaredField("mPackageInfo");
|
|
|
|
mInfo.setAccessible(true);
|
|
|
|
Object loadedApk = mInfo.get(context);
|
|
|
|
Field mcl = loadedApk.getClass().getDeclaredField("mClassLoader");
|
|
|
|
mcl.setAccessible(true);
|
|
|
|
mcl.set(loadedApk, new DelegateClassLoader());
|
|
|
|
} catch (Exception e) {
|
|
|
|
// Actually impossible as this method is only called on API < 29,
|
2022-02-02 05:06:12 -08:00
|
|
|
// and API 21 - 28 do not restrict access to these fields.
|
2022-02-01 22:43:44 -08:00
|
|
|
Log.e(DynLoad.class.getSimpleName(), "", e);
|
|
|
|
}
|
2020-12-29 01:44:02 -08:00
|
|
|
}
|
|
|
|
}
|