mirror of
https://github.com/topjohnwu/Magisk.git
synced 2024-11-27 20:15:29 +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">
|
tools:ignore="GoogleAppIndexingWarning,MissingApplicationIcon,UnusedAttribute">
|
||||||
|
|
||||||
<!-- Splash -->
|
<!-- Splash -->
|
||||||
<activity android:name="a.u7">
|
<activity android:name="f.u7">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
@ -42,7 +42,7 @@
|
|||||||
|
|
||||||
<!-- Receiver -->
|
<!-- Receiver -->
|
||||||
<receiver
|
<receiver
|
||||||
android:name="a.r"
|
android:name="yy.E"
|
||||||
android:directBootAware="true"
|
android:directBootAware="true"
|
||||||
android:exported="false">
|
android:exported="false">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
@ -61,7 +61,7 @@
|
|||||||
|
|
||||||
<!-- FileProvider -->
|
<!-- FileProvider -->
|
||||||
<provider
|
<provider
|
||||||
android:name="a.ii"
|
android:name="fxQ.lk"
|
||||||
android:authorities="${applicationId}.provider"
|
android:authorities="${applicationId}.provider"
|
||||||
android:directBootAware="true"
|
android:directBootAware="true"
|
||||||
android:exported="false"
|
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 {
|
public class DelegateApplication extends Application {
|
||||||
|
|
||||||
private Application delegate;
|
|
||||||
static boolean dynLoad = false;
|
static boolean dynLoad = false;
|
||||||
|
|
||||||
|
private Application receiver;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void attachBaseContext(Context base) {
|
protected void attachBaseContext(Context base) {
|
||||||
super.attachBaseContext(base);
|
super.attachBaseContext(base);
|
||||||
|
|
||||||
// Only dynamic load full APK if hidden and possible
|
// Only dynamic load full APK if hidden and supported
|
||||||
dynLoad = Build.VERSION.SDK_INT >= 28 &&
|
dynLoad = Build.VERSION.SDK_INT >= 21 &&
|
||||||
!base.getPackageName().equals(BuildConfig.APPLICATION_ID);
|
!base.getPackageName().equals(BuildConfig.APPLICATION_ID);
|
||||||
if (!dynLoad)
|
|
||||||
return;
|
|
||||||
|
|
||||||
delegate = InjectAPK.setup(this);
|
receiver = InjectAPK.setup(this);
|
||||||
if (delegate != null) try {
|
if (receiver != null) try {
|
||||||
// Call attachBaseContext without ContextImpl to show it is being wrapped
|
// Call attachBaseContext without ContextImpl to show it is being wrapped
|
||||||
Method m = ContextWrapper.class.getDeclaredMethod("attachBaseContext", Context.class);
|
Method m = ContextWrapper.class.getDeclaredMethod("attachBaseContext", Context.class);
|
||||||
m.setAccessible(true);
|
m.setAccessible(true);
|
||||||
m.invoke(delegate, this);
|
m.invoke(receiver, this);
|
||||||
} catch (Exception ignored) { /* Impossible */ }
|
} catch (Exception ignored) { /* Impossible */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onConfigurationChanged(Configuration newConfig) {
|
public void onConfigurationChanged(Configuration newConfig) {
|
||||||
super.onConfigurationChanged(newConfig);
|
super.onConfigurationChanged(newConfig);
|
||||||
if (delegate != null)
|
if (receiver != null)
|
||||||
delegate.onConfigurationChanged(newConfig);
|
receiver.onConfigurationChanged(newConfig);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,22 +9,15 @@ import android.content.BroadcastReceiver;
|
|||||||
import android.content.ContentProvider;
|
import android.content.ContentProvider;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.dummy.DummyProvider;
|
|
||||||
import com.topjohnwu.magisk.dummy.DummyReceiver;
|
|
||||||
import com.topjohnwu.magisk.dummy.DummyService;
|
|
||||||
|
|
||||||
@SuppressLint("NewApi")
|
@SuppressLint("NewApi")
|
||||||
public class DelegateComponentFactory extends AppComponentFactory {
|
public class DelegateComponentFactory extends AppComponentFactory {
|
||||||
|
|
||||||
|
static DelegateComponentFactory INSTANCE;
|
||||||
ClassLoader loader;
|
ClassLoader loader;
|
||||||
AppComponentFactory delegate;
|
AppComponentFactory receiver;
|
||||||
|
|
||||||
interface DummyFactory<T> {
|
|
||||||
T create();
|
|
||||||
}
|
|
||||||
|
|
||||||
public DelegateComponentFactory() {
|
public DelegateComponentFactory() {
|
||||||
InjectAPK.factory = this;
|
INSTANCE = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -36,45 +29,39 @@ public class DelegateComponentFactory extends AppComponentFactory {
|
|||||||
@Override
|
@Override
|
||||||
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 (delegate != null)
|
if (receiver != null)
|
||||||
return delegate.instantiateActivity(loader, Mapping.get(className), intent);
|
return receiver.instantiateActivity(loader, className, intent);
|
||||||
return create(className, DownloadActivity::new);
|
return create(className);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
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 (delegate != null)
|
if (receiver != null)
|
||||||
return delegate.instantiateReceiver(loader, Mapping.get(className), intent);
|
return receiver.instantiateReceiver(loader, className, intent);
|
||||||
return create(className, DummyReceiver::new);
|
return create(className);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
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 (delegate != null)
|
if (receiver != null)
|
||||||
return delegate.instantiateService(loader, Mapping.get(className), intent);
|
return receiver.instantiateService(loader, className, intent);
|
||||||
return create(className, DummyService::new);
|
return create(className);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ContentProvider instantiateProvider(ClassLoader cl, String className)
|
public ContentProvider instantiateProvider(ClassLoader cl, String className)
|
||||||
throws ClassNotFoundException, IllegalAccessException, InstantiationException {
|
throws ClassNotFoundException, IllegalAccessException, InstantiationException {
|
||||||
if (loader == null) loader = cl;
|
if (loader == null) loader = cl;
|
||||||
if (delegate != null)
|
if (receiver != null)
|
||||||
return delegate.instantiateProvider(loader, Mapping.get(className));
|
return receiver.instantiateProvider(loader, className);
|
||||||
return create(className, DummyProvider::new);
|
return create(className);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private <T> T create(String name)
|
||||||
* Create the class or dummy implementation if creation failed
|
throws ClassNotFoundException, IllegalAccessException, InstantiationException{
|
||||||
*/
|
|
||||||
private <T> T create(String name, DummyFactory<T> factory) {
|
|
||||||
try {
|
|
||||||
return (T) loader.loadClass(name).newInstance();
|
return (T) loader.loadClass(name).newInstance();
|
||||||
} catch (Exception ignored) {
|
|
||||||
return factory.create();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,65 +4,97 @@ import android.app.AppComponentFactory;
|
|||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.ContextWrapper;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.utils.DynamicClassLoader;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
public class InjectAPK {
|
public class InjectAPK {
|
||||||
|
|
||||||
static DelegateComponentFactory factory;
|
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||||
|
|
||||||
static Application setup(Context context) {
|
static Application setup(Context context) {
|
||||||
|
// Get ContextImpl
|
||||||
|
while (context instanceof ContextWrapper) {
|
||||||
|
context = ((ContextWrapper) context).getBaseContext();
|
||||||
|
}
|
||||||
|
|
||||||
File apk = DynAPK.current(context);
|
File apk = DynAPK.current(context);
|
||||||
File update = DynAPK.update(context);
|
File update = DynAPK.update(context);
|
||||||
if (update.exists())
|
if (update.exists())
|
||||||
update.renameTo(apk);
|
update.renameTo(apk);
|
||||||
Application delegate = null;
|
Application result = null;
|
||||||
if (!apk.exists()) {
|
if (!apk.exists()) {
|
||||||
// Try copying APK
|
// Try copying APK
|
||||||
Uri uri = new Uri.Builder().scheme("content")
|
Uri uri = new Uri.Builder().scheme("content")
|
||||||
.authority("com.topjohnwu.magisk.provider")
|
.authority("com.topjohnwu.magisk.provider")
|
||||||
.encodedPath("apk_file").build();
|
.encodedPath("apk_file").build();
|
||||||
ContentResolver resolver = context.getContentResolver();
|
ContentResolver resolver = context.getContentResolver();
|
||||||
try (InputStream is = resolver.openInputStream(uri)) {
|
try (InputStream src = resolver.openInputStream(uri)) {
|
||||||
if (is != null) {
|
if (src != null) {
|
||||||
try (OutputStream out = new FileOutputStream(apk)) {
|
try (OutputStream out = new FileOutputStream(apk)) {
|
||||||
byte[] buf = new byte[4096];
|
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);
|
out.write(buf, 0, read);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception ignored) {}
|
||||||
Log.e(InjectAPK.class.getSimpleName(), "", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (apk.exists()) {
|
if (apk.exists()) {
|
||||||
ClassLoader cl = new DynamicClassLoader(apk, factory.loader);
|
ClassLoader cl = new InjectedClassLoader(apk);
|
||||||
try {
|
try {
|
||||||
// Create the delegate AppComponentFactory
|
// Create the receiver Application
|
||||||
AppComponentFactory df = (AppComponentFactory)
|
Object app = cl.loadClass(Mapping.get("APP")).getConstructor(Object.class)
|
||||||
cl.loadClass("androidx.core.app.CoreComponentFactory").newInstance();
|
|
||||||
|
|
||||||
// Create the delegate Application
|
|
||||||
delegate = (Application) cl.loadClass("a.e").getConstructor(Object.class)
|
|
||||||
.newInstance(DynAPK.pack(dynData()));
|
.newInstance(DynAPK.pack(dynData()));
|
||||||
|
|
||||||
// If everything went well, set our loader and delegate
|
// Create the receiver component factory
|
||||||
factory.delegate = df;
|
Object factory = null;
|
||||||
factory.loader = cl;
|
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) {
|
} catch (Exception e) {
|
||||||
Log.e(InjectAPK.class.getSimpleName(), "", e);
|
Log.e(InjectAPK.class.getSimpleName(), "", e);
|
||||||
apk.delete();
|
apk.delete();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
ClassLoader cl = new RedirectClassLoader();
|
||||||
|
try {
|
||||||
|
setClassLoader(context, cl);
|
||||||
|
if (Build.VERSION.SDK_INT >= 28) {
|
||||||
|
DelegateComponentFactory.INSTANCE.loader = cl;
|
||||||
}
|
}
|
||||||
return delegate;
|
} catch (Exception e) {
|
||||||
|
// Should never happen
|
||||||
|
Log.e(InjectAPK.class.getSimpleName(), "", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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() {
|
private static DynAPK.Data dynData() {
|
||||||
|
@ -1,27 +1,42 @@
|
|||||||
package com.topjohnwu.magisk;
|
package com.topjohnwu.magisk;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.dummy.DummyReceiver;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
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 {
|
public class Mapping {
|
||||||
|
|
||||||
private static Map<String, String> map = new HashMap<>();
|
private static final Map<String, String> map = new HashMap<>();
|
||||||
public static Map<String, String> inverseMap;
|
public static final Map<String, Class<?>> internalMap = new HashMap<>();
|
||||||
|
public static final Map<String, String> inverseMap;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
map.put("a.Qzw", "a.e");
|
map.put("a.Q", "com.topjohnwu.magisk.core.App");
|
||||||
map.put("a.u7", "a.c");
|
map.put("f.u7", "com.topjohnwu.magisk.core.SplashActivity");
|
||||||
map.put("a.ii", "a.p");
|
map.put("fxQ.lk", "com.topjohnwu.magisk.core.Provider");
|
||||||
map.put("a.r", "a.h");
|
map.put("yy.E", "com.topjohnwu.magisk.core.Receiver");
|
||||||
map.put("xt.R", "a.b");
|
map.put("xt.R", "com.topjohnwu.magisk.ui.MainActivity");
|
||||||
map.put("lt5.a", "a.m");
|
map.put("lt5.a", "com.topjohnwu.magisk.ui.surequest.SuRequestActivity");
|
||||||
map.put("d.s", "a.j");
|
map.put("d.s", "com.topjohnwu.magisk.core.download.DownloadService");
|
||||||
map.put("w.d", "androidx.work.impl.background.systemjob.SystemJobService");
|
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());
|
inverseMap = new HashMap<>(map.size());
|
||||||
for (Map.Entry<String, String> e : map.entrySet()) {
|
for (Map.Entry<String, String> e : map.entrySet()) {
|
||||||
inverseMap.put(e.getValue(), e.getKey());
|
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) {
|
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…
Reference in New Issue
Block a user