mirror of
https://github.com/topjohnwu/Magisk.git
synced 2024-12-28 10:17:38 +00:00
Support stub APK loading down to Android 5.0
This commit is contained in:
parent
f1295cb7d6
commit
fba83e2330
@ -13,7 +13,7 @@
|
||||
tools:ignore="GoogleAppIndexingWarning,MissingApplicationIcon,UnusedAttribute">
|
||||
|
||||
<!-- Splash -->
|
||||
<activity android:name="a.u7">
|
||||
<activity android:name="f.u7">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
@ -42,7 +42,7 @@
|
||||
|
||||
<!-- Receiver -->
|
||||
<receiver
|
||||
android:name="a.r"
|
||||
android:name="yy.E"
|
||||
android:directBootAware="true"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
@ -61,7 +61,7 @@
|
||||
|
||||
<!-- FileProvider -->
|
||||
<provider
|
||||
android:name="a.ii"
|
||||
android:name="fxQ.lk"
|
||||
android:authorities="${applicationId}.provider"
|
||||
android:directBootAware="true"
|
||||
android:exported="false"
|
||||
|
@ -1,5 +0,0 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.FileProvider;
|
||||
|
||||
public class ii extends FileProvider {}
|
@ -1,5 +0,0 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.dummy.DummyReceiver;
|
||||
|
||||
public class r extends DummyReceiver {}
|
@ -1,5 +0,0 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.DownloadActivity;
|
||||
|
||||
public class u7 extends DownloadActivity {}
|
33
stub/src/main/java/com/topjohnwu/magisk/ClassLoaders.java
Normal file
33
stub/src/main/java/com/topjohnwu/magisk/ClassLoaders.java
Normal file
@ -0,0 +1,33 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import com.topjohnwu.magisk.utils.DynamicClassLoader;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
class InjectedClassLoader extends DynamicClassLoader {
|
||||
|
||||
InjectedClassLoader(File apk) {
|
||||
super(apk,
|
||||
/* Use the base classloader as we do not want stub
|
||||
* APK classes accessible from the main app */
|
||||
Object.class.getClassLoader());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
|
||||
return super.loadClass(Mapping.get(name), resolve);
|
||||
}
|
||||
}
|
||||
|
||||
class RedirectClassLoader extends ClassLoader {
|
||||
|
||||
RedirectClassLoader() {
|
||||
super(RedirectClassLoader.class.getClassLoader());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
|
||||
Class<?> clz = Mapping.internalMap.get(name);
|
||||
return clz == null ? super.loadClass(name, resolve) : clz;
|
||||
}
|
||||
}
|
@ -10,32 +10,31 @@ import java.lang.reflect.Method;
|
||||
|
||||
public class DelegateApplication extends Application {
|
||||
|
||||
private Application delegate;
|
||||
static boolean dynLoad = false;
|
||||
|
||||
private Application receiver;
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(Context base) {
|
||||
super.attachBaseContext(base);
|
||||
|
||||
// Only dynamic load full APK if hidden and possible
|
||||
dynLoad = Build.VERSION.SDK_INT >= 28 &&
|
||||
// Only dynamic load full APK if hidden and supported
|
||||
dynLoad = Build.VERSION.SDK_INT >= 21 &&
|
||||
!base.getPackageName().equals(BuildConfig.APPLICATION_ID);
|
||||
if (!dynLoad)
|
||||
return;
|
||||
|
||||
delegate = InjectAPK.setup(this);
|
||||
if (delegate != null) try {
|
||||
receiver = InjectAPK.setup(this);
|
||||
if (receiver != null) try {
|
||||
// Call attachBaseContext without ContextImpl to show it is being wrapped
|
||||
Method m = ContextWrapper.class.getDeclaredMethod("attachBaseContext", Context.class);
|
||||
m.setAccessible(true);
|
||||
m.invoke(delegate, this);
|
||||
m.invoke(receiver, this);
|
||||
} catch (Exception ignored) { /* Impossible */ }
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
if (delegate != null)
|
||||
delegate.onConfigurationChanged(newConfig);
|
||||
if (receiver != null)
|
||||
receiver.onConfigurationChanged(newConfig);
|
||||
}
|
||||
}
|
||||
|
@ -9,22 +9,15 @@ import android.content.BroadcastReceiver;
|
||||
import android.content.ContentProvider;
|
||||
import android.content.Intent;
|
||||
|
||||
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 {
|
||||
|
||||
static DelegateComponentFactory INSTANCE;
|
||||
ClassLoader loader;
|
||||
AppComponentFactory delegate;
|
||||
|
||||
interface DummyFactory<T> {
|
||||
T create();
|
||||
}
|
||||
AppComponentFactory receiver;
|
||||
|
||||
public DelegateComponentFactory() {
|
||||
InjectAPK.factory = this;
|
||||
INSTANCE = this;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -36,45 +29,39 @@ public class DelegateComponentFactory extends AppComponentFactory {
|
||||
@Override
|
||||
public Activity instantiateActivity(ClassLoader cl, String className, Intent intent)
|
||||
throws ClassNotFoundException, IllegalAccessException, InstantiationException {
|
||||
if (delegate != null)
|
||||
return delegate.instantiateActivity(loader, Mapping.get(className), intent);
|
||||
return create(className, DownloadActivity::new);
|
||||
if (receiver != null)
|
||||
return receiver.instantiateActivity(loader, className, intent);
|
||||
return create(className);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BroadcastReceiver instantiateReceiver(ClassLoader cl, String className, Intent intent)
|
||||
throws ClassNotFoundException, IllegalAccessException, InstantiationException {
|
||||
if (delegate != null)
|
||||
return delegate.instantiateReceiver(loader, Mapping.get(className), intent);
|
||||
return create(className, DummyReceiver::new);
|
||||
if (receiver != null)
|
||||
return receiver.instantiateReceiver(loader, className, intent);
|
||||
return create(className);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Service instantiateService(ClassLoader cl, String className, Intent intent)
|
||||
throws ClassNotFoundException, IllegalAccessException, InstantiationException {
|
||||
if (delegate != null)
|
||||
return delegate.instantiateService(loader, Mapping.get(className), intent);
|
||||
return create(className, DummyService::new);
|
||||
if (receiver != null)
|
||||
return receiver.instantiateService(loader, className, intent);
|
||||
return create(className);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContentProvider instantiateProvider(ClassLoader cl, String className)
|
||||
throws ClassNotFoundException, IllegalAccessException, InstantiationException {
|
||||
if (loader == null) loader = cl;
|
||||
if (delegate != null)
|
||||
return delegate.instantiateProvider(loader, Mapping.get(className));
|
||||
return create(className, DummyProvider::new);
|
||||
if (receiver != null)
|
||||
return receiver.instantiateProvider(loader, className);
|
||||
return create(className);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the class or dummy implementation if creation failed
|
||||
*/
|
||||
private <T> T create(String name, DummyFactory<T> factory) {
|
||||
try {
|
||||
return (T) loader.loadClass(name).newInstance();
|
||||
} catch (Exception ignored) {
|
||||
return factory.create();
|
||||
}
|
||||
private <T> T create(String name)
|
||||
throws ClassNotFoundException, IllegalAccessException, InstantiationException{
|
||||
return (T) loader.loadClass(name).newInstance();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,65 +4,97 @@ import android.app.AppComponentFactory;
|
||||
import android.app.Application;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import com.topjohnwu.magisk.utils.DynamicClassLoader;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
public class InjectAPK {
|
||||
|
||||
static DelegateComponentFactory factory;
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
static Application setup(Context context) {
|
||||
// Get ContextImpl
|
||||
while (context instanceof ContextWrapper) {
|
||||
context = ((ContextWrapper) context).getBaseContext();
|
||||
}
|
||||
|
||||
File apk = DynAPK.current(context);
|
||||
File update = DynAPK.update(context);
|
||||
if (update.exists())
|
||||
update.renameTo(apk);
|
||||
Application delegate = null;
|
||||
Application result = null;
|
||||
if (!apk.exists()) {
|
||||
// Try copying APK
|
||||
Uri uri = new Uri.Builder().scheme("content")
|
||||
.authority("com.topjohnwu.magisk.provider")
|
||||
.encodedPath("apk_file").build();
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
try (InputStream is = resolver.openInputStream(uri)) {
|
||||
if (is != null) {
|
||||
try (InputStream src = resolver.openInputStream(uri)) {
|
||||
if (src != null) {
|
||||
try (OutputStream out = new FileOutputStream(apk)) {
|
||||
byte[] buf = new byte[4096];
|
||||
for (int read; (read = is.read(buf)) >= 0;) {
|
||||
for (int read; (read = src.read(buf)) >= 0;) {
|
||||
out.write(buf, 0, read);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(InjectAPK.class.getSimpleName(), "", e);
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
if (apk.exists()) {
|
||||
ClassLoader cl = new DynamicClassLoader(apk, factory.loader);
|
||||
ClassLoader cl = new InjectedClassLoader(apk);
|
||||
try {
|
||||
// Create the delegate AppComponentFactory
|
||||
AppComponentFactory df = (AppComponentFactory)
|
||||
cl.loadClass("androidx.core.app.CoreComponentFactory").newInstance();
|
||||
|
||||
// Create the delegate Application
|
||||
delegate = (Application) cl.loadClass("a.e").getConstructor(Object.class)
|
||||
// Create the receiver Application
|
||||
Object app = cl.loadClass(Mapping.get("APP")).getConstructor(Object.class)
|
||||
.newInstance(DynAPK.pack(dynData()));
|
||||
|
||||
// If everything went well, set our loader and delegate
|
||||
factory.delegate = df;
|
||||
factory.loader = cl;
|
||||
// Create the receiver component factory
|
||||
Object factory = null;
|
||||
if (Build.VERSION.SDK_INT >= 28) {
|
||||
factory = cl.loadClass(Mapping.get("ACF")).newInstance();
|
||||
}
|
||||
|
||||
setClassLoader(context, cl);
|
||||
|
||||
// Finally, set variables
|
||||
result = (Application) app;
|
||||
if (Build.VERSION.SDK_INT >= 28) {
|
||||
DelegateComponentFactory.INSTANCE.loader = cl;
|
||||
DelegateComponentFactory.INSTANCE.receiver = (AppComponentFactory) factory;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(InjectAPK.class.getSimpleName(), "", e);
|
||||
apk.delete();
|
||||
}
|
||||
} else {
|
||||
ClassLoader cl = new RedirectClassLoader();
|
||||
try {
|
||||
setClassLoader(context, cl);
|
||||
if (Build.VERSION.SDK_INT >= 28) {
|
||||
DelegateComponentFactory.INSTANCE.loader = cl;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Should never happen
|
||||
Log.e(InjectAPK.class.getSimpleName(), "", e);
|
||||
}
|
||||
}
|
||||
return delegate;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Replace LoadedApk mClassLoader
|
||||
private static void setClassLoader(Context impl, ClassLoader cl)
|
||||
throws NoSuchFieldException, IllegalAccessException {
|
||||
Field mInfo = impl.getClass().getDeclaredField("mPackageInfo");
|
||||
mInfo.setAccessible(true);
|
||||
Object loadedApk = mInfo.get(impl);
|
||||
Field mcl = loadedApk.getClass().getDeclaredField("mClassLoader");
|
||||
mcl.setAccessible(true);
|
||||
mcl.set(loadedApk, cl);
|
||||
}
|
||||
|
||||
private static DynAPK.Data dynData() {
|
||||
|
@ -1,27 +1,42 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import com.topjohnwu.magisk.dummy.DummyReceiver;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* These are just some random class names hardcoded as an example.
|
||||
* For the actual release builds, these mappings will be auto generated.
|
||||
*/
|
||||
public class Mapping {
|
||||
|
||||
private static Map<String, String> map = new HashMap<>();
|
||||
public static Map<String, String> inverseMap;
|
||||
private static final Map<String, String> map = new HashMap<>();
|
||||
public static final Map<String, Class<?>> internalMap = new HashMap<>();
|
||||
public static final Map<String, String> inverseMap;
|
||||
|
||||
static {
|
||||
map.put("a.Qzw", "a.e");
|
||||
map.put("a.u7", "a.c");
|
||||
map.put("a.ii", "a.p");
|
||||
map.put("a.r", "a.h");
|
||||
map.put("xt.R", "a.b");
|
||||
map.put("lt5.a", "a.m");
|
||||
map.put("d.s", "a.j");
|
||||
map.put("a.Q", "com.topjohnwu.magisk.core.App");
|
||||
map.put("f.u7", "com.topjohnwu.magisk.core.SplashActivity");
|
||||
map.put("fxQ.lk", "com.topjohnwu.magisk.core.Provider");
|
||||
map.put("yy.E", "com.topjohnwu.magisk.core.Receiver");
|
||||
map.put("xt.R", "com.topjohnwu.magisk.ui.MainActivity");
|
||||
map.put("lt5.a", "com.topjohnwu.magisk.ui.surequest.SuRequestActivity");
|
||||
map.put("d.s", "com.topjohnwu.magisk.core.download.DownloadService");
|
||||
map.put("w.d", "androidx.work.impl.background.systemjob.SystemJobService");
|
||||
|
||||
internalMap.put("a.Q", DelegateApplication.class);
|
||||
internalMap.put("f.u7", DownloadActivity.class);
|
||||
internalMap.put("fxQ.lk", FileProvider.class);
|
||||
internalMap.put("yy.E", DummyReceiver.class);
|
||||
|
||||
inverseMap = new HashMap<>(map.size());
|
||||
for (Map.Entry<String, String> e : map.entrySet()) {
|
||||
inverseMap.put(e.getValue(), e.getKey());
|
||||
}
|
||||
|
||||
map.put("APP", "com.topjohnwu.magisk.core.App");
|
||||
map.put("ACF", "androidx.core.app.CoreComponentFactory");
|
||||
}
|
||||
|
||||
public static String get(String name) {
|
||||
|
@ -1,13 +0,0 @@
|
||||
package com.topjohnwu.magisk.dummy;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
|
||||
public class DummyActivity extends Activity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
finish();
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
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) {
|
||||
stopSelf();
|
||||
return null;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user