mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-01-12 02:03:39 +00:00
Support RootService on stub APKs
This commit is contained in:
parent
edcf9f1b0c
commit
54e3f1998a
@ -13,11 +13,6 @@ import io.michaelrocks.paranoid.Obfuscate;
|
|||||||
|
|
||||||
@Obfuscate
|
@Obfuscate
|
||||||
public class DynAPK {
|
public class DynAPK {
|
||||||
|
|
||||||
// Indices of the object array
|
|
||||||
private static final int STUB_VERSION_ENTRY = 0;
|
|
||||||
private static final int CLASS_COMPONENT_MAP = 1;
|
|
||||||
|
|
||||||
private static File dynDir;
|
private static File dynDir;
|
||||||
private static Method addAssetPath;
|
private static Method addAssetPath;
|
||||||
|
|
||||||
@ -41,21 +36,6 @@ public class DynAPK {
|
|||||||
return new File(getDynDir(c), "update.apk");
|
return new File(getDynDir(c), "update.apk");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Data load(Object o) {
|
|
||||||
Object[] arr = (Object[]) o;
|
|
||||||
Data data = new Data();
|
|
||||||
data.version = (int) arr[STUB_VERSION_ENTRY];
|
|
||||||
data.classToComponent = (Map<String, String>) arr[CLASS_COMPONENT_MAP];
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Object pack(Data data) {
|
|
||||||
Object[] arr = new Object[2];
|
|
||||||
arr[STUB_VERSION_ENTRY] = data.version;
|
|
||||||
arr[CLASS_COMPONENT_MAP] = data.classToComponent;
|
|
||||||
return arr;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void addAssetPath(AssetManager asset, String path) {
|
public static void addAssetPath(AssetManager asset, String path) {
|
||||||
try {
|
try {
|
||||||
if (addAssetPath == null)
|
if (addAssetPath == null)
|
||||||
@ -65,7 +45,28 @@ public class DynAPK {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class Data {
|
public static class Data {
|
||||||
public int version;
|
// Indices of the object array
|
||||||
public Map<String, String> classToComponent;
|
private static final int STUB_VERSION = 0;
|
||||||
|
private static final int CLASS_COMPONENT_MAP = 1;
|
||||||
|
private static final int ROOT_SERVICE = 2;
|
||||||
|
private static final int ARR_SIZE = 3;
|
||||||
|
|
||||||
|
private final Object[] arr;
|
||||||
|
|
||||||
|
public Data() { arr = new Object[ARR_SIZE]; }
|
||||||
|
public Data(Object o) { arr = (Object[]) o; }
|
||||||
|
public Object getObject() { return arr; }
|
||||||
|
|
||||||
|
public int getVersion() { return (int) arr[STUB_VERSION]; }
|
||||||
|
public void setVersion(int version) { arr[STUB_VERSION] = version; }
|
||||||
|
public Map<String, String> getClassToComponent() {
|
||||||
|
// noinspection unchecked
|
||||||
|
return (Map<String, String>) arr[CLASS_COMPONENT_MAP];
|
||||||
|
}
|
||||||
|
public void setClassToComponent(Map<String, String> map) {
|
||||||
|
arr[CLASS_COMPONENT_MAP] = map;
|
||||||
|
}
|
||||||
|
public Class<?> getRootService() { return (Class<?>) arr[ROOT_SERVICE]; }
|
||||||
|
public void setRootService(Class<?> service) { arr[ROOT_SERVICE] = service; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,16 +22,15 @@ import kotlin.system.exitProcess
|
|||||||
open class App() : Application() {
|
open class App() : Application() {
|
||||||
|
|
||||||
constructor(o: Any) : this() {
|
constructor(o: Any) : this() {
|
||||||
Info.stub = DynAPK.load(o)
|
val data = DynAPK.Data(o)
|
||||||
|
// Add the root service name mapping
|
||||||
|
data.classToComponent[RootRegistry::class.java.name] = data.rootService.name
|
||||||
|
// Send back the actual root service class
|
||||||
|
data.rootService = RootRegistry::class.java
|
||||||
|
Info.stub = data
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
Shell.setDefaultBuilder(Shell.Builder.create()
|
|
||||||
.setFlags(Shell.FLAG_MOUNT_MASTER)
|
|
||||||
.setInitializers(ShellInit::class.java)
|
|
||||||
.setTimeout(2))
|
|
||||||
Shell.EXECUTOR = IODispatcherExecutor()
|
|
||||||
|
|
||||||
// Always log full stack trace with Timber
|
// Always log full stack trace with Timber
|
||||||
Timber.plant(Timber.DebugTree())
|
Timber.plant(Timber.DebugTree())
|
||||||
Thread.setDefaultUncaughtExceptionHandler { _, e ->
|
Thread.setDefaultUncaughtExceptionHandler { _, e ->
|
||||||
@ -41,6 +40,12 @@ open class App() : Application() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun attachBaseContext(base: Context) {
|
override fun attachBaseContext(base: Context) {
|
||||||
|
Shell.setDefaultBuilder(Shell.Builder.create()
|
||||||
|
.setFlags(Shell.FLAG_MOUNT_MASTER)
|
||||||
|
.setInitializers(ShellInit::class.java)
|
||||||
|
.setTimeout(2))
|
||||||
|
Shell.EXECUTOR = IODispatcherExecutor()
|
||||||
|
|
||||||
// Some context magic
|
// Some context magic
|
||||||
val app: Application
|
val app: Application
|
||||||
val impl: Context
|
val impl: Context
|
||||||
|
@ -10,7 +10,11 @@ import timber.log.Timber
|
|||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
class RootRegistry : RootService() {
|
class RootRegistry(stub: Any?) : RootService() {
|
||||||
|
|
||||||
|
constructor() : this(null)
|
||||||
|
|
||||||
|
private val className: String? = stub?.javaClass?.name
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// Always log full stack trace with Timber
|
// Always log full stack trace with Timber
|
||||||
@ -26,6 +30,10 @@ class RootRegistry : RootService() {
|
|||||||
return Binder()
|
return Binder()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getComponentName(): ComponentName {
|
||||||
|
return ComponentName(packageName, className ?: javaClass.name)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: PLACEHOLDER
|
// TODO: PLACEHOLDER
|
||||||
object Connection : CountDownLatch(1), ServiceConnection {
|
object Connection : CountDownLatch(1), ServiceConnection {
|
||||||
override fun onServiceConnected(name: ComponentName, service: IBinder) {
|
override fun onServiceConnected(name: ComponentName, service: IBinder) {
|
||||||
|
1
stub/proguard-rules.pro
vendored
1
stub/proguard-rules.pro
vendored
@ -30,3 +30,4 @@
|
|||||||
-keepclassmembers class com.topjohnwu.magisk.dummy.* { <init>(); }
|
-keepclassmembers class com.topjohnwu.magisk.dummy.* { <init>(); }
|
||||||
-keepclassmembers class com.topjohnwu.magisk.DownloadActivity { <init>(); }
|
-keepclassmembers class com.topjohnwu.magisk.DownloadActivity { <init>(); }
|
||||||
-keepclassmembers class com.topjohnwu.magisk.FileProvider { <init>(); }
|
-keepclassmembers class com.topjohnwu.magisk.FileProvider { <init>(); }
|
||||||
|
-keepclassmembers class com.topjohnwu.magisk.DelegateRootService { <init>(); }
|
||||||
|
@ -18,7 +18,7 @@ public class DelegateApplication extends Application {
|
|||||||
protected void attachBaseContext(Context base) {
|
protected void attachBaseContext(Context base) {
|
||||||
super.attachBaseContext(base);
|
super.attachBaseContext(base);
|
||||||
|
|
||||||
receiver = InjectAPK.setup(this);
|
receiver = DynLoad.setup(this);
|
||||||
if (receiver != 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);
|
||||||
@ -27,6 +27,13 @@ public class DelegateApplication extends Application {
|
|||||||
} catch (Exception ignored) { /* Impossible */ }
|
} catch (Exception ignored) { /* Impossible */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
if (receiver != null)
|
||||||
|
receiver.onCreate();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onConfigurationChanged(Configuration newConfig) {
|
public void onConfigurationChanged(Configuration newConfig) {
|
||||||
super.onConfigurationChanged(newConfig);
|
super.onConfigurationChanged(newConfig);
|
||||||
|
@ -16,7 +16,7 @@ public class DelegateComponentFactory extends AppComponentFactory {
|
|||||||
AppComponentFactory receiver;
|
AppComponentFactory receiver;
|
||||||
|
|
||||||
public DelegateComponentFactory() {
|
public DelegateComponentFactory() {
|
||||||
InjectAPK.componentFactory = this;
|
DynLoad.componentFactory = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
package com.topjohnwu.magisk;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.ContextWrapper;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
import io.michaelrocks.paranoid.Obfuscate;
|
||||||
|
|
||||||
|
@Obfuscate
|
||||||
|
public class DelegateRootService extends ContextWrapper {
|
||||||
|
|
||||||
|
public DelegateRootService() {
|
||||||
|
super(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void attachBaseContext(Context base) {
|
||||||
|
if (DynLoad.inject(base) == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Create the actual RootService and call its attachBaseContext
|
||||||
|
try {
|
||||||
|
Constructor<?> ctor = DynLoad.apkData.getRootService().getConstructor(Object.class);
|
||||||
|
ctor.setAccessible(true);
|
||||||
|
Object service = ctor.newInstance(this);
|
||||||
|
Method m = ContextWrapper.class.getDeclaredMethod("attachBaseContext", Context.class);
|
||||||
|
m.setAccessible(true);
|
||||||
|
m.invoke(service, base);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(DelegateRootService.class.getSimpleName(), "", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -115,7 +115,7 @@ public class DownloadActivity extends Activity {
|
|||||||
File apk = dynLoad ? DynAPK.current(this) : new File(getCacheDir(), "manager.apk");
|
File apk = dynLoad ? DynAPK.current(this) : new File(getCacheDir(), "manager.apk");
|
||||||
request(apkLink).setExecutor(AsyncTask.THREAD_POOL_EXECUTOR).getAsFile(apk, file -> {
|
request(apkLink).setExecutor(AsyncTask.THREAD_POOL_EXECUTOR).getAsFile(apk, file -> {
|
||||||
if (dynLoad) {
|
if (dynLoad) {
|
||||||
InjectAPK.setup(this);
|
DynLoad.setup(this);
|
||||||
runOnUiThread(() -> {
|
runOnUiThread(() -> {
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
Toast.makeText(themed, relaunch_app, Toast.LENGTH_LONG).show();
|
Toast.makeText(themed, relaunch_app, Toast.LENGTH_LONG).show();
|
||||||
|
@ -23,13 +23,10 @@ import java.lang.reflect.Field;
|
|||||||
import io.michaelrocks.paranoid.Obfuscate;
|
import io.michaelrocks.paranoid.Obfuscate;
|
||||||
|
|
||||||
@Obfuscate
|
@Obfuscate
|
||||||
public class InjectAPK {
|
public class DynLoad {
|
||||||
|
|
||||||
static Object componentFactory;
|
static Object componentFactory;
|
||||||
|
static final DynAPK.Data apkData = createApkData();
|
||||||
private static DelegateComponentFactory getComponentFactory() {
|
|
||||||
return (DelegateComponentFactory) componentFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void copy(InputStream src, OutputStream dest) throws IOException {
|
private static void copy(InputStream src, OutputStream dest) throws IOException {
|
||||||
try (InputStream s = src) {
|
try (InputStream s = src) {
|
||||||
@ -42,13 +39,9 @@ public class InjectAPK {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
// Dynamically load APK, inject ClassLoader into ContextImpl, then
|
||||||
static Application setup(Context context) {
|
// create the actual Application instance from the loaded APK
|
||||||
// Get ContextImpl
|
static Application inject(Context context) {
|
||||||
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);
|
||||||
|
|
||||||
@ -64,7 +57,7 @@ public class InjectAPK {
|
|||||||
try {
|
try {
|
||||||
copy(new FileInputStream(external), new FileOutputStream(apk));
|
copy(new FileInputStream(external), new FileOutputStream(apk));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(InjectAPK.class.getSimpleName(), "", e);
|
Log.e(DynLoad.class.getSimpleName(), "", e);
|
||||||
apk.delete();
|
apk.delete();
|
||||||
} finally {
|
} finally {
|
||||||
external.delete();
|
external.delete();
|
||||||
@ -84,7 +77,7 @@ public class InjectAPK {
|
|||||||
copy(src, new FileOutputStream(apk));
|
copy(src, new FileOutputStream(apk));
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(InjectAPK.class.getSimpleName(), "", e);
|
Log.e(DynLoad.class.getSimpleName(), "", e);
|
||||||
apk.delete();
|
apk.delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -95,21 +88,30 @@ public class InjectAPK {
|
|||||||
PackageInfo pkgInfo = pm.getPackageArchiveInfo(apk.getPath(), 0);
|
PackageInfo pkgInfo = pm.getPackageArchiveInfo(apk.getPath(), 0);
|
||||||
try {
|
try {
|
||||||
return createApp(context, cl, pkgInfo.applicationInfo);
|
return createApp(context, cl, pkgInfo.applicationInfo);
|
||||||
} catch (Exception e) {
|
} catch (ReflectiveOperationException e) {
|
||||||
Log.e(InjectAPK.class.getSimpleName(), "", e);
|
Log.e(DynLoad.class.getSimpleName(), "", e);
|
||||||
apk.delete();
|
apk.delete();
|
||||||
}
|
}
|
||||||
// fallthrough
|
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inject and create Application, or setup redirections for the current app
|
||||||
|
static Application setup(Context context) {
|
||||||
|
Application app = inject(context);
|
||||||
|
if (app != null) {
|
||||||
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClassLoader cl = new RedirectClassLoader();
|
ClassLoader cl = new RedirectClassLoader();
|
||||||
try {
|
try {
|
||||||
setClassLoader(context, cl);
|
setClassLoader(context, cl);
|
||||||
if (Build.VERSION.SDK_INT >= 28) {
|
if (Build.VERSION.SDK_INT >= 28) {
|
||||||
getComponentFactory().loader = cl;
|
((DelegateComponentFactory) componentFactory).loader = cl;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(InjectAPK.class.getSimpleName(), "", e);
|
Log.e(DynLoad.class.getSimpleName(), "", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@ -120,40 +122,42 @@ public class InjectAPK {
|
|||||||
// Create the receiver Application
|
// Create the receiver Application
|
||||||
Object app = cl.loadClass(info.className)
|
Object app = cl.loadClass(info.className)
|
||||||
.getConstructor(Object.class)
|
.getConstructor(Object.class)
|
||||||
.newInstance(DynAPK.pack(dynData()));
|
.newInstance(apkData.getObject());
|
||||||
|
|
||||||
// Create the receiver component factory
|
// Create the receiver component factory
|
||||||
Object factory = null;
|
if (Build.VERSION.SDK_INT >= 28 && componentFactory != null) {
|
||||||
if (Build.VERSION.SDK_INT >= 28) {
|
Object factory = cl.loadClass(info.appComponentFactory).newInstance();
|
||||||
factory = cl.loadClass(info.appComponentFactory).newInstance();
|
DelegateComponentFactory delegate = (DelegateComponentFactory) componentFactory;
|
||||||
|
delegate.loader = cl;
|
||||||
|
delegate.receiver = (AppComponentFactory) factory;
|
||||||
}
|
}
|
||||||
|
|
||||||
setClassLoader(context, cl);
|
setClassLoader(context, cl);
|
||||||
|
|
||||||
// Finally, set variables
|
|
||||||
if (Build.VERSION.SDK_INT >= 28) {
|
|
||||||
getComponentFactory().loader = cl;
|
|
||||||
getComponentFactory().receiver = (AppComponentFactory) factory;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (Application) app;
|
return (Application) app;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace LoadedApk mClassLoader
|
// Replace LoadedApk mClassLoader
|
||||||
private static void setClassLoader(Context impl, ClassLoader cl)
|
private static void setClassLoader(Context context, ClassLoader cl)
|
||||||
throws NoSuchFieldException, IllegalAccessException {
|
throws NoSuchFieldException, IllegalAccessException {
|
||||||
Field mInfo = impl.getClass().getDeclaredField("mPackageInfo");
|
// Get ContextImpl
|
||||||
|
while (context instanceof ContextWrapper) {
|
||||||
|
context = ((ContextWrapper) context).getBaseContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
Field mInfo = context.getClass().getDeclaredField("mPackageInfo");
|
||||||
mInfo.setAccessible(true);
|
mInfo.setAccessible(true);
|
||||||
Object loadedApk = mInfo.get(impl);
|
Object loadedApk = mInfo.get(context);
|
||||||
Field mcl = loadedApk.getClass().getDeclaredField("mClassLoader");
|
Field mcl = loadedApk.getClass().getDeclaredField("mClassLoader");
|
||||||
mcl.setAccessible(true);
|
mcl.setAccessible(true);
|
||||||
mcl.set(loadedApk, cl);
|
mcl.set(loadedApk, cl);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DynAPK.Data dynData() {
|
private static DynAPK.Data createApkData() {
|
||||||
DynAPK.Data data = new DynAPK.Data();
|
DynAPK.Data data = new DynAPK.Data();
|
||||||
data.version = BuildConfig.STUB_VERSION;
|
data.setVersion(BuildConfig.STUB_VERSION);
|
||||||
data.classToComponent = Mapping.inverseMap;
|
data.setClassToComponent(Mapping.inverseMap);
|
||||||
|
data.setRootService(DelegateRootService.class);
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user