From 59cc621d980a0a7c460c8e64f032cb17d0970a2b Mon Sep 17 00:00:00 2001 From: LoveSy Date: Thu, 16 Sep 2021 21:36:04 +0800 Subject: [PATCH] Parasitic manager into a system app (#1103) * add activity controller * Hook `TRANSACTION_setActivityController` * Hook setActivityController * Inject into settings * Check categories for manager * Inject manager * Hook webview * Should not load modules for manager * Remove shouldBlock * remove pendding when server died * Add shortcut automatically * Load resources from manager * Fix shortcut sleep wait * Make R8 happy * Fix when am is proxy * lspd should run with euid=1000 after system server started * Add shortcut from daemon * Set injected package into gradle build script * Try to inject into shell instead of settings * Fix shortcut of shell and add icon * Fix proguard * hook handleReceiver * Properly solve conflict * Update shortcut everytime unlock * Clear log * Fix build * chageng to use process${variantCapped}Resources * Remove auto install manager * Fix crash on some devices * Fix new intent * Make app toast when restart fails * Fallback to recreate * refine code * Use fakecontext to set shortcut * Fix compatibility with Android 12 * Fix `credentialProtectedDataDir` * Fix webview * Fix recreate * Fix autofill * Fix webview cache context * Switch to inject into settings * Send notification from daemon * Change notification title --- app/src/main/AndroidManifest.xml | 2 - .../manager/receivers/ServiceReceiver.java | 55 --- .../manager/ui/activity/MainActivity.java | 34 +- .../manager/util/NotificationUtil.java | 90 ----- core/build.gradle.kts | 42 ++- core/proguard-rules.pro | 6 +- .../lspd/service/ILSPApplicationService.aidl | 2 + .../lsposed/lspd/service/ILSPosedService.aidl | 2 + core/src/main/cpp/main/include/jni_helper.h | 12 + core/src/main/cpp/main/src/main.cpp | 15 +- core/src/main/cpp/main/src/service.cpp | 125 +++++-- core/src/main/cpp/main/src/service.h | 3 + .../config/LSPApplicationServiceClient.java | 11 +- .../main/java/org/lsposed/lspd/core/Main.java | 8 +- .../lspd/hooker/HandleBindAppHooker.java | 1 + .../lspd/service/ActivityController.java | 202 ++++++++++ .../lspd/service/ActivityManagerService.java | 4 +- .../lsposed/lspd/service/BridgeService.java | 174 ++++++--- .../lspd/service/ConfigFileManager.java | 76 +++- .../lsposed/lspd/service/ConfigManager.java | 26 +- .../lspd/service/LSPApplicationService.java | 40 +- .../lspd/service/LSPManagerService.java | 346 +++++++++++++++++- .../lspd/service/LSPSystemServerService.java | 2 +- .../lsposed/lspd/service/LSPosedService.java | 67 ++-- .../lsposed/lspd/service/PackageService.java | 106 +----- .../lsposed/lspd/service/ServiceManager.java | 3 + .../org/lsposed/lspd/service/UserService.java | 6 + .../org/lsposed/lspd/util/FakeContext.java | 65 ++++ .../lspd/util/ParasiticManagerHooker.java | 199 ++++++++++ .../src/main/java/hidden/HiddenApiBridge.java | 28 ++ .../main/java/android/app/ActivityThread.java | 3 + .../java/android/app/IActivityController.java | 60 +++ .../java/android/app/IActivityManager.java | 3 + .../android/app/INotificationManager.java | 19 + .../main/java/android/app/Notification.java | 4 + .../java/android/app/NotificationChannel.java | 4 + .../android/content/BroadcastReceiver.java | 5 + .../main/java/android/content/Context.java | 6 + .../android/content/pm/ApplicationInfo.java | 1 + .../android/content/pm/IShortcutService.java | 24 ++ .../android/content/pm/ParceledListSlice.java | 6 + .../java/android/content/pm/ShortcutInfo.java | 28 ++ .../src/main/java/android/os/Handler.java | 4 + .../main/java/android/os/IUserManager.java | 2 + .../src/main/java/android/os/Parcelable.java | 10 + .../main/java/android/os/ResultReceiver.java | 4 + .../main/java/android/os/ShellCallback.java | 25 ++ .../main/java/android/os/ShellCommand.java | 33 ++ .../src/main/java/android/os/UserHandle.java | 5 + .../java/android/webkit/WebViewDelegate.java | 4 + .../java/android/webkit/WebViewFactory.java | 4 + .../webkit/WebViewFactoryProvider.java | 4 + .../org/lsposed/lspd/ILSPManagerService.aidl | 2 + 53 files changed, 1588 insertions(+), 424 deletions(-) delete mode 100644 app/src/main/java/org/lsposed/manager/receivers/ServiceReceiver.java delete mode 100644 app/src/main/java/org/lsposed/manager/util/NotificationUtil.java create mode 100644 core/src/main/java/org/lsposed/lspd/service/ActivityController.java create mode 100644 core/src/main/java/org/lsposed/lspd/util/FakeContext.java create mode 100644 core/src/main/java/org/lsposed/lspd/util/ParasiticManagerHooker.java create mode 100644 hiddenapi-stubs/src/main/java/android/app/IActivityController.java create mode 100644 hiddenapi-stubs/src/main/java/android/app/INotificationManager.java create mode 100644 hiddenapi-stubs/src/main/java/android/app/Notification.java create mode 100644 hiddenapi-stubs/src/main/java/android/app/NotificationChannel.java create mode 100644 hiddenapi-stubs/src/main/java/android/content/BroadcastReceiver.java create mode 100644 hiddenapi-stubs/src/main/java/android/content/pm/IShortcutService.java create mode 100644 hiddenapi-stubs/src/main/java/android/content/pm/ShortcutInfo.java create mode 100644 hiddenapi-stubs/src/main/java/android/os/Handler.java create mode 100644 hiddenapi-stubs/src/main/java/android/os/Parcelable.java create mode 100644 hiddenapi-stubs/src/main/java/android/os/ResultReceiver.java create mode 100644 hiddenapi-stubs/src/main/java/android/os/ShellCallback.java create mode 100644 hiddenapi-stubs/src/main/java/android/os/ShellCommand.java create mode 100644 hiddenapi-stubs/src/main/java/android/webkit/WebViewDelegate.java create mode 100644 hiddenapi-stubs/src/main/java/android/webkit/WebViewFactory.java create mode 100644 hiddenapi-stubs/src/main/java/android/webkit/WebViewFactoryProvider.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index baf5669a..4d107dc0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -51,8 +51,6 @@ - - diff --git a/app/src/main/java/org/lsposed/manager/receivers/ServiceReceiver.java b/app/src/main/java/org/lsposed/manager/receivers/ServiceReceiver.java deleted file mode 100644 index 4f2177e1..00000000 --- a/app/src/main/java/org/lsposed/manager/receivers/ServiceReceiver.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * This file is part of LSPosed. - * - * LSPosed is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * LSPosed is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with LSPosed. If not, see . - * - * Copyright (C) 2020 EdXposed Contributors - * Copyright (C) 2021 LSPosed Contributors - */ - -package org.lsposed.manager.receivers; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; - -import org.lsposed.manager.util.ModuleUtil; -import org.lsposed.manager.util.NotificationUtil; - -public class ServiceReceiver extends BroadcastReceiver { - - private static String getPackageName(Intent intent) { - Uri uri = intent.getData(); - return (uri != null) ? uri.getSchemeSpecificPart() : null; - } - - @Override - public void onReceive(final Context context, final Intent intent) { - int userId = intent.getIntExtra(Intent.EXTRA_USER, 0); - String packageName = getPackageName(intent); - if (packageName == null) { - return; - } - - ModuleUtil.InstalledModule module = ModuleUtil.getInstance().reloadSingleModule(packageName, userId); - if (module == null) { - return; - } - - var enabled = "org.lsposed.action.MODULE_UPDATED".equals(intent.getAction()); - var systemModule = intent.getBooleanExtra("systemModule", false); - NotificationUtil.showNotification(context, packageName, module.getAppName(), userId, enabled, systemModule); - } -} diff --git a/app/src/main/java/org/lsposed/manager/ui/activity/MainActivity.java b/app/src/main/java/org/lsposed/manager/ui/activity/MainActivity.java index 99b11b5f..b2a53eb7 100644 --- a/app/src/main/java/org/lsposed/manager/ui/activity/MainActivity.java +++ b/app/src/main/java/org/lsposed/manager/ui/activity/MainActivity.java @@ -39,7 +39,6 @@ import org.lsposed.manager.NavGraphDirections; import org.lsposed.manager.R; import org.lsposed.manager.databinding.ActivityMainBinding; import org.lsposed.manager.ui.activity.base.BaseActivity; -import org.lsposed.manager.util.NotificationUtil; public class MainActivity extends BaseActivity { private static final String KEY_PREFIX = MainActivity.class.getName() + '.'; @@ -100,13 +99,7 @@ public class MainActivity extends BaseActivity { if (intent.getAction() != null && intent.getAction().equals("android.intent.action.APPLICATION_PREFERENCES")) { navController.navigate(R.id.action_settings_fragment); } else if (ConfigManager.isBinderAlive()) { - if (NotificationUtil.NOTIFICATION_UUID.equals(intent.getStringExtra("uuid"))) { - navController.navigate( - NavGraphDirections.actionAppListFragment( - intent.getStringExtra("modulePackageName"), - intent.getIntExtra("moduleUserId", -1)) - ); - } else if (!TextUtils.isEmpty(intent.getDataString())) { + if (!TextUtils.isEmpty(intent.getDataString())) { switch (intent.getDataString()) { case "modules": navController.navigate(R.id.action_modules_fragment); @@ -119,6 +112,15 @@ public class MainActivity extends BaseActivity { navController.navigate(R.id.action_repo_fragment); } break; + default: + var data = intent.getData(); + if (data.getScheme().equals("module")) { + navController.navigate( + NavGraphDirections.actionAppListFragment( + data.getHost(), + data.getPort()) + ); + } } } } @@ -135,12 +137,16 @@ public class MainActivity extends BaseActivity { if (BuildCompat.isAtLeastS()) { recreate(); } else { - Bundle savedInstanceState = new Bundle(); - onSaveInstanceState(savedInstanceState); - finish(); - startActivity(newIntent(savedInstanceState, this)); - overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); - restarting = true; + try { + Bundle savedInstanceState = new Bundle(); + onSaveInstanceState(savedInstanceState); + startActivity(newIntent(savedInstanceState, this)); + finish(); + overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); + restarting = true; + } catch (Throwable e) { + recreate(); + } } } diff --git a/app/src/main/java/org/lsposed/manager/util/NotificationUtil.java b/app/src/main/java/org/lsposed/manager/util/NotificationUtil.java deleted file mode 100644 index 81736f34..00000000 --- a/app/src/main/java/org/lsposed/manager/util/NotificationUtil.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * This file is part of LSPosed. - * - * LSPosed is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * LSPosed is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with LSPosed. If not, see . - * - * Copyright (C) 2020 EdXposed Contributors - * Copyright (C) 2021 LSPosed Contributors - */ - -package org.lsposed.manager.util; - -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; - -import androidx.core.app.NotificationCompat; - -import org.lsposed.manager.R; -import org.lsposed.manager.ui.activity.MainActivity; - -import java.util.UUID; - -public final class NotificationUtil { - - public static final String NOTIFICATION_UUID = UUID.randomUUID().toString(); - private static final int NOTIFICATION_MODULE_NOT_ACTIVATED_YET = 0; - private static final int NOTIFICATION_MODULES_UPDATED = 1; - private static final int PENDING_INTENT_OPEN_APP_LIST = 0; - private static final String NOTIFICATION_MODULES_CHANNEL = "modules_channel_2"; - - public static void showNotification(Context context, - String modulePackageName, - String moduleName, - int moduleUserId, - boolean enabled, - boolean systemModule) { - NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - - NotificationChannel channel = new NotificationChannel(NOTIFICATION_MODULES_CHANNEL, - context.getString(R.string.Modules), - NotificationManager.IMPORTANCE_HIGH); - channel.setSound(null, null); - channel.setVibrationPattern(null); - notificationManager.createNotificationChannel(channel); - - String title = context.getString(enabled ? systemModule ? - R.string.xposed_module_updated_notification_title_system : - R.string.xposed_module_updated_notification_title : - R.string.module_is_not_activated_yet); - String content = context.getString(enabled ? systemModule ? - R.string.xposed_module_updated_notification_content_system : - R.string.xposed_module_updated_notification_content : - R.string.module_is_not_activated_yet_detailed, moduleName); - - Intent intent = new Intent(context, MainActivity.class) - .putExtra("uuid", NOTIFICATION_UUID) - .putExtra("modulePackageName", modulePackageName) - .putExtra("moduleUserId", moduleUserId) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - - PendingIntent contentIntent = PendingIntent.getActivity(context, PENDING_INTENT_OPEN_APP_LIST, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); - - NotificationCompat.BigTextStyle style = new NotificationCompat.BigTextStyle(); - style.bigText(content); - - NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NOTIFICATION_MODULES_CHANNEL) - .setContentTitle(title) - .setContentText(content) - .setAutoCancel(true) - .setSmallIcon(R.drawable.ic_extension) - .setColor(context.getColor(R.color.color_primary)) - .setContentIntent(contentIntent) - .setStyle(style); - - notificationManager.notify(modulePackageName, enabled ? NOTIFICATION_MODULES_UPDATED : NOTIFICATION_MODULE_NOT_ACTIVATED_YET, builder.build()); - } -} diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 331a5a13..8034360c 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -26,6 +26,9 @@ import org.apache.tools.ant.filters.FixCrLfFilter import org.apache.tools.ant.filters.ReplaceTokens import java.io.PrintStream import java.security.MessageDigest +import java.util.jar.JarFile +import java.util.zip.ZipOutputStream +import java.io.FileOutputStream plugins { id("com.android.application") @@ -58,13 +61,19 @@ dependencies { implementation("dev.rikka.ndk:riru:26.0.0") implementation("dev.rikka.ndk.thirdparty:cxx:1.1.0") implementation("io.github.vvb2060.ndk:dobby:1.2") - implementation("com.android.tools.build:apksig:7.0.1") + implementation("com.android.tools.build:apksig:7.0.2") implementation("org.apache.commons:commons-lang3:3.12.0") implementation("de.upb.cs.swt:axml:2.1.1") compileOnly("androidx.annotation:annotation:1.2.0") compileOnly(project(":hiddenapi-stubs")) implementation(project(":hiddenapi-bridge")) implementation(project(":manager-service")) + debugImplementation(files(File(project.buildDir, "tmp/debugR.jar")) { + builtBy("generateAppDebugRFile") + }) + releaseImplementation(files(File(project.buildDir, "tmp/releaseR.jar")) { + builtBy("generateAppReleaseRFile") + }) } android { @@ -93,7 +102,13 @@ android { } buildConfigField("int", "API_CODE", "$apiCode") - buildConfigField("String", "DEFAULT_MANAGER_PACKAGE_NAME", "\"$defaultManagerPackageName\"") + buildConfigField( + "String", + "DEFAULT_MANAGER_PACKAGE_NAME", + """"$defaultManagerPackageName"""" + ) + buildConfigField("String", "MANAGER_INJECTED_PKG_NAME", """"com.android.settings"""") + buildConfigField("int", "MANAGER_INJECTED_UID", """1000""") } lint { @@ -129,6 +144,27 @@ androidComponents.onVariants { v -> val zipFileName = "$moduleName-v$verName-$verCode-$variantLowered.zip" val magiskDir = "$buildDir/magisk/$variantLowered" + task("generateApp${variantCapped}RFile", Jar::class) { + dependsOn(":app:process${variantCapped}Resources") + doLast { + val rFile = JarFile( + File( + project(":app").buildDir, + "intermediates/compile_and_runtime_not_namespaced_r_class_jar/${variantLowered}/R.jar" + ) + ) + ZipOutputStream(FileOutputStream(File(project.buildDir, "tmp/${variantLowered}R.jar"))).use { + for (entry in rFile.entries()) { + if (entry.name.startsWith("org/lsposed/manager")) { + it.putNextEntry(entry) + rFile.getInputStream(entry).transferTo(it) + it.closeEntry() + } + } + } + } + } + afterEvaluate { val app = rootProject.project(":app").extensions.getByName("android") val outSrcDir = file("$buildDir/generated/source/signInfo/${variantLowered}") @@ -191,7 +227,7 @@ androidComponents.onVariants { v -> filter("tokens" to tokens) filter("eol" to FixCrLfFilter.CrLf.newInstance("lf")) } - from("${project(":app").buildDir}/outputs/apk/${variantLowered}") { + from("${project(":app").buildDir}/${if (rootProject.extra.properties["android.injected.invoked.from.ide"] == "true") "intermediates" else "outputs"}/apk/${variantLowered}") { include("*.apk") rename(".*\\.apk", "manager.apk") } diff --git a/core/proguard-rules.pro b/core/proguard-rules.pro index e3012624..9375dce5 100644 --- a/core/proguard-rules.pro +++ b/core/proguard-rules.pro @@ -12,12 +12,16 @@ public static void onPostFixupStaticTrampolines(java.lang.Class); } -keepclasseswithmembers class org.lsposed.lspd.service.BridgeService { - public static boolean execTransact(int, long, long, int); + public static boolean *(android.os.IBinder, int, long, long, int); public static android.os.IBinder getApplicationServiceForSystemServer(android.os.IBinder, android.os.IBinder); } -keepclasseswithmembers class org.lsposed.lspd.service.LogcatService { private int refreshFd(boolean); } + +-keepclassmembers class ** implements android.content.ContextWrapper { + public int getUserId(); +} -assumenosideeffects class android.util.Log { public static *** v(...); public static *** d(...); diff --git a/core/src/main/aidl/org/lsposed/lspd/service/ILSPApplicationService.aidl b/core/src/main/aidl/org/lsposed/lspd/service/ILSPApplicationService.aidl index 939d4d9b..404e6dc2 100644 --- a/core/src/main/aidl/org/lsposed/lspd/service/ILSPApplicationService.aidl +++ b/core/src/main/aidl/org/lsposed/lspd/service/ILSPApplicationService.aidl @@ -14,4 +14,6 @@ interface ILSPApplicationService { String getPrefsPath(String packageName); Bundle requestRemotePreference(String packageName, int userId, IBinder callback); + + ParcelFileDescriptor requestInjectedManagerBinder(out List binder); } diff --git a/core/src/main/aidl/org/lsposed/lspd/service/ILSPosedService.aidl b/core/src/main/aidl/org/lsposed/lspd/service/ILSPosedService.aidl index df10b2f2..b9c939ea 100644 --- a/core/src/main/aidl/org/lsposed/lspd/service/ILSPosedService.aidl +++ b/core/src/main/aidl/org/lsposed/lspd/service/ILSPosedService.aidl @@ -6,4 +6,6 @@ interface ILSPosedService { ILSPApplicationService requestApplicationService(int uid, int pid, String processName, IBinder heartBeat); oneway void dispatchSystemServerContext(in IBinder activityThread, in IBinder activityToken); + + boolean preStartManager(String pkgName, in Intent intent); } diff --git a/core/src/main/cpp/main/include/jni_helper.h b/core/src/main/cpp/main/include/jni_helper.h index 47326b4a..53909542 100644 --- a/core/src/main/cpp/main/include/jni_helper.h +++ b/core/src/main/cpp/main/include/jni_helper.h @@ -266,6 +266,12 @@ inline auto JNI_GetStaticObjectField(JNIEnv *env, const Class &clazz, jfieldID f return JNI_SafeInvoke(env, &JNIEnv::GetStaticObjectField, clazz, fieldId); } +template +[[maybe_unused]] +inline auto JNI_GetStaticIntField(JNIEnv *env, const Class &clazz, jfieldID fieldId) { + return JNI_SafeInvoke(env, &JNIEnv::GetStaticIntField, clazz, fieldId); +} + template [[maybe_unused]] inline auto @@ -292,6 +298,12 @@ inline auto JNI_CallStaticIntMethod(JNIEnv *env, const Class &clazz, Args &&...a return JNI_SafeInvoke(env, &JNIEnv::CallStaticIntMethod, clazz, std::forward(args)...); } +template +[[maybe_unused]] +inline auto JNI_CallStaticBooleanMethod(JNIEnv *env, const Class &clazz, Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallStaticBooleanMethod, clazz, std::forward(args)...); +} + template Array> [[maybe_unused]] inline auto JNI_GetArrayLength(JNIEnv *env, const Array &array) { diff --git a/core/src/main/cpp/main/src/main.cpp b/core/src/main/cpp/main/src/main.cpp index 1d890e69..34f8b765 100644 --- a/core/src/main/cpp/main/src/main.cpp +++ b/core/src/main/cpp/main/src/main.cpp @@ -31,6 +31,9 @@ namespace lspd { int *allowUnload = nullptr; + static constexpr uid_t kAidShell = 2000; + static constexpr uid_t kAidInet = 3003; + namespace { std::string magiskPath; @@ -46,7 +49,7 @@ namespace lspd { } void nativeForkAndSpecializePre(JNIEnv *env, jclass, jint *_uid, jint *, - jintArray *, jint *, + jintArray *gids, jint *, jobjectArray *, jint *, jstring *, jstring *nice_name, jintArray *, jintArray *, @@ -56,6 +59,16 @@ namespace lspd { jobjectArray *, jboolean *, jboolean *) { + if (*_uid == kAidShell) { + int array_size = *gids ? env->GetArrayLength(*gids) : 0; + auto region = std::make_unique(array_size + 1); + auto *new_gids = env->NewIntArray(array_size + 1); + if (*gids) env->GetIntArrayRegion(*gids, 0, array_size, region.get()); + region.get()[array_size] = kAidInet; + env->SetIntArrayRegion(new_gids, 0, array_size + 1, region.get()); + if (*gids) env->SetIntArrayRegion(*gids, 0, 1, region.get() + array_size); + *gids = new_gids; + } Context::GetInstance()->OnNativeForkAndSpecializePre(env, *_uid, *nice_name, *start_child_zygote, diff --git a/core/src/main/cpp/main/src/service.cpp b/core/src/main/cpp/main/src/service.cpp index df12c264..cfe99d73 100644 --- a/core/src/main/cpp/main/src/service.cpp +++ b/core/src/main/cpp/main/src/service.cpp @@ -33,19 +33,39 @@ namespace lspd { jboolean Service::exec_transact_replace(jboolean *res, JNIEnv *env, [[maybe_unused]] jobject obj, va_list args) { - jint code; va_list copy; va_copy(copy, args); - code = va_arg(copy, jint); + auto code = va_arg(copy, jint); + auto data_obj = va_arg(copy, jlong); + auto reply_obj = va_arg(copy, jlong); + auto flags = va_arg(copy, jint); va_end(copy); if (code == BRIDGE_TRANSACTION_CODE) [[unlikely]] { - *res = env->CallStaticBooleanMethodV(instance()->bridge_service_class_, - instance()->exec_transact_replace_methodID_, - args); + *res = JNI_CallStaticBooleanMethod(env, instance()->bridge_service_class_, + instance()->exec_transact_replace_methodID_, + obj, code, data_obj, reply_obj, flags); return true; + } else if (SET_ACTIVITY_CONTROLLER_CODE != -1 && + code == SET_ACTIVITY_CONTROLLER_CODE) [[unlikely]] { + va_copy(copy, args); + if (instance()->replace_activity_controller_methodID_) { + *res = JNI_CallStaticBooleanMethod(env, instance()->bridge_service_class_, + instance()->replace_activity_controller_methodID_, + obj, code, data_obj, reply_obj, flags); + } + va_end(copy); + // fallback the backup + } else if (code == (('_' << 24) | ('C' << 16) | ('M' << 8) | 'D')) { + va_copy(copy, args); + if (instance()->replace_shell_command_methodID_) { + *res = JNI_CallStaticBooleanMethod(env, instance()->bridge_service_class_, + instance()->replace_shell_command_methodID_, + obj, code, data_obj, reply_obj, flags); + } + va_end(copy); + return *res; } - return false; } @@ -64,28 +84,28 @@ namespace lspd { if (initialized_) [[unlikely]] return; // ServiceManager - if (auto service_manager_class = JNI_FindClass(env, "android/os/ServiceManager")) + if (auto service_manager_class = JNI_FindClass(env, "android/os/ServiceManager")) { service_manager_class_ = JNI_NewGlobalRef(env, service_manager_class); - else return; + } else return; get_service_method_ = JNI_GetStaticMethodID(env, service_manager_class_, "getService", "(Ljava/lang/String;)Landroid/os/IBinder;"); if (!get_service_method_) return; // IBinder - if (auto ibinder_class = JNI_FindClass(env, "android/os/IBinder")) + if (auto ibinder_class = JNI_FindClass(env, "android/os/IBinder")) { transact_method_ = JNI_GetMethodID(env, ibinder_class, "transact", "(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z"); - else return; + } else return; - if (auto binder_class = JNI_FindClass(env, "android/os/Binder")) + if (auto binder_class = JNI_FindClass(env, "android/os/Binder")) { binder_class_ = JNI_NewGlobalRef(env, binder_class); - else return; + } else return; binder_ctor_ = JNI_GetMethodID(env, binder_class_, "", "()V"); // Parcel - if (auto parcel_class = JNI_FindClass(env, "android/os/Parcel")) + if (auto parcel_class = JNI_FindClass(env, "android/os/Parcel")) { parcel_class_ = JNI_NewGlobalRef(env, parcel_class); - else return; + } else return; obtain_method_ = JNI_GetStaticMethodID(env, parcel_class_, "obtain", "()Landroid/os/Parcel;"); recycleMethod_ = JNI_GetMethodID(env, parcel_class_, "recycle", "()V"); @@ -102,17 +122,19 @@ namespace lspd { // createStringArray_ = env->GetMethodID(parcel_class_, "createStringArray", // "()[Ljava/lang/String;"); - if (auto deadObjectExceptionClass = JNI_FindClass(env, "android/os/DeadObjectException")) - deadObjectExceptionClass_ = JNI_NewGlobalRef(env, deadObjectExceptionClass); + if (auto dead_object_exception_class = JNI_FindClass(env, + "android/os/DeadObjectException")) { + deadObjectExceptionClass_ = JNI_NewGlobalRef(env, dead_object_exception_class); + } initialized_ = true; } void Service::HookBridge(const Context &context, JNIEnv *env) { - static bool hooked = false; + static bool kHooked = false; // This should only be ran once, so unlikely - if (hooked) [[unlikely]] return; + if (kHooked) [[unlikely]] return; if (!initialized_) [[unlikely]] return; - hooked = true; + kHooked = true; if (auto bridge_service_class = context.FindClassFromCurrentLoader(env, kBridgeServiceClassName)) bridge_service_class_ = JNI_NewGlobalRef(env, bridge_service_class); @@ -120,16 +142,34 @@ namespace lspd { LOGE("server class not found"); return; } + + constexpr const auto *hooker_sig = "(Landroid/os/IBinder;IJJI)Z"; + exec_transact_replace_methodID_ = JNI_GetStaticMethodID(env, bridge_service_class_, "execTransact", - "(IJJI)Z"); + hooker_sig); if (!exec_transact_replace_methodID_) { LOGE("execTransact class not found"); return; } - auto binderClass = JNI_FindClass(env, "android/os/Binder"); - exec_transact_backup_methodID_ = JNI_GetMethodID(env, binderClass, "execTransact", + + replace_activity_controller_methodID_ = JNI_GetStaticMethodID(env, bridge_service_class_, + "replaceActivityController", + hooker_sig); + if (!replace_activity_controller_methodID_) { + LOGE("replaceActivityShell class not found"); + } + + replace_shell_command_methodID_ = JNI_GetStaticMethodID(env, bridge_service_class_, + "replaceShellCommand", + hooker_sig); + if (!replace_shell_command_methodID_) { + LOGE("replaceShellCommand class not found"); + } + + auto binder_class = JNI_FindClass(env, "android/os/Binder"); + exec_transact_backup_methodID_ = JNI_GetMethodID(env, binder_class, "execTransact", "(IJJI)Z"); if (!sym_set_table_override) { LOGE("set table override not found"); @@ -143,6 +183,15 @@ namespace lspd { reinterpret_cast(sym_set_table_override)( &native_interface_replace_); } + if (auto activity_thread_class = JNI_FindClass(env, "android/app/IActivityManager$Stub")) { + if (auto *set_activity_controller_field = JNI_GetStaticFieldID(env, + activity_thread_class, + "TRANSACTION_setActivityController", + "I")) { + SET_ACTIVITY_CONTROLLER_CODE = JNI_GetStaticIntField(env, activity_thread_class, + set_activity_controller_field); + } + } LOGD("Done InitService"); } @@ -153,10 +202,10 @@ namespace lspd { return {env, nullptr}; } - auto bridgeServiceName = env->NewStringUTF(BRIDGE_SERVICE_NAME.data()); - auto bridgeService = JNI_CallStaticObjectMethod(env, service_manager_class_, - get_service_method_, bridgeServiceName); - if (!bridgeService) { + auto *bridge_service_name = env->NewStringUTF(BRIDGE_SERVICE_NAME.data()); + auto bridge_service = JNI_CallStaticObjectMethod(env, service_manager_class_, + get_service_method_, bridge_service_name); + if (!bridge_service) { LOGD("can't get %s", BRIDGE_SERVICE_NAME.data()); return {env, nullptr}; } @@ -166,13 +215,13 @@ namespace lspd { auto data = JNI_CallStaticObjectMethod(env, parcel_class_, obtain_method_); auto reply = JNI_CallStaticObjectMethod(env, parcel_class_, obtain_method_); - auto descriptor = env->NewStringUTF(BRIDGE_SERVICE_DESCRIPTOR.data()); + auto *descriptor = env->NewStringUTF(BRIDGE_SERVICE_DESCRIPTOR.data()); JNI_CallVoidMethod(env, data, write_interface_token_method_, descriptor); JNI_CallVoidMethod(env, data, write_int_method_, BRIDGE_ACTION_GET_BINDER); JNI_CallVoidMethod(env, data, write_string_method_, nice_name); JNI_CallVoidMethod(env, data, write_strong_binder_method_, heart_beat_binder); - auto res = JNI_CallBooleanMethod(env, bridgeService, transact_method_, + auto res = JNI_CallBooleanMethod(env, bridge_service, transact_method_, BRIDGE_TRANSACTION_CODE, data, reply, 0); @@ -196,28 +245,26 @@ namespace lspd { LOGE("Service not initialized"); return {env, nullptr}; } - auto bridgeServiceName = env->NewStringUTF(SYSTEM_SERVER_BRIDGE_SERVICE_NAME.data()); + auto *bridge_service_name = env->NewStringUTF(SYSTEM_SERVER_BRIDGE_SERVICE_NAME.data()); ScopedLocalRef binder{env, nullptr}; for (int i = 0; i < 3; ++i) { binder = JNI_CallStaticObjectMethod(env, service_manager_class_, - get_service_method_, bridgeServiceName); + get_service_method_, bridge_service_name); if (binder) { LOGD("Got binder for system server"); break; - } else { - LOGI("Fail to get binder for system server, try again in 1s"); - using namespace std::chrono_literals; - std::this_thread::sleep_for(1s); } - + LOGI("Fail to get binder for system server, try again in 1s"); + using namespace std::chrono_literals; + std::this_thread::sleep_for(1s); } if (!binder) { LOGW("Fail to get binder for system server"); return {env, nullptr}; } - auto method = JNI_GetStaticMethodID(env, bridge_service_class_, - "getApplicationServiceForSystemServer", - "(Landroid/os/IBinder;Landroid/os/IBinder;)Landroid/os/IBinder;"); + auto *method = JNI_GetStaticMethodID(env, bridge_service_class_, + "getApplicationServiceForSystemServer", + "(Landroid/os/IBinder;Landroid/os/IBinder;)Landroid/os/IBinder;"); auto heart_beat_binder = JNI_NewObject(env, binder_class_, binder_ctor_); auto app_binder = JNI_CallStaticObjectMethod(env, bridge_service_class_, method, binder, heart_beat_binder); @@ -226,4 +273,4 @@ namespace lspd { } return app_binder; } -} +} // namespace lspd diff --git a/core/src/main/cpp/main/src/service.h b/core/src/main/cpp/main/src/service.h index 0bd66af7..28522ff3 100644 --- a/core/src/main/cpp/main/src/service.h +++ b/core/src/main/cpp/main/src/service.h @@ -36,6 +36,7 @@ namespace lspd { constexpr static auto BRIDGE_SERVICE_NAME = "activity"sv; constexpr static auto SYSTEM_SERVER_BRIDGE_SERVICE_NAME = "serial"sv; constexpr static jint BRIDGE_ACTION_GET_BINDER = 2; + inline static jint SET_ACTIVITY_CONTROLLER_CODE = -1; public: inline static Service* instance() { @@ -72,6 +73,8 @@ namespace lspd { jclass bridge_service_class_ = nullptr; jmethodID exec_transact_replace_methodID_ = nullptr; + jmethodID replace_activity_controller_methodID_ = nullptr; + jmethodID replace_shell_command_methodID_ = nullptr; jclass binder_class_ = nullptr; jmethodID binder_ctor_ = nullptr; diff --git a/core/src/main/java/org/lsposed/lspd/config/LSPApplicationServiceClient.java b/core/src/main/java/org/lsposed/lspd/config/LSPApplicationServiceClient.java index acbad996..fdaae1e1 100644 --- a/core/src/main/java/org/lsposed/lspd/config/LSPApplicationServiceClient.java +++ b/core/src/main/java/org/lsposed/lspd/config/LSPApplicationServiceClient.java @@ -110,7 +110,16 @@ public class LSPApplicationServiceClient extends ApplicationServiceClient { } @Override - public Bundle requestRemotePreference(String packageName, int userId, IBinder callback) throws RemoteException { + public Bundle requestRemotePreference(String packageName, int userId, IBinder callback) { + return null; + } + + @Override + public ParcelFileDescriptor requestInjectedManagerBinder(List binder) { + try { + return service.requestInjectedManagerBinder(binder); + } catch (RemoteException | NullPointerException ignored) { + } return null; } diff --git a/core/src/main/java/org/lsposed/lspd/core/Main.java b/core/src/main/java/org/lsposed/lspd/core/Main.java index 3f8f4755..3a45f7ee 100644 --- a/core/src/main/java/org/lsposed/lspd/core/Main.java +++ b/core/src/main/java/org/lsposed/lspd/core/Main.java @@ -20,8 +20,6 @@ package org.lsposed.lspd.core; -import static org.lsposed.lspd.config.LSPApplicationServiceClient.serviceClient; - import android.app.ActivityThread; import android.app.LoadedApk; import android.content.pm.ApplicationInfo; @@ -30,6 +28,7 @@ import android.os.Environment; import android.os.IBinder; import android.os.Process; +import org.lsposed.lspd.BuildConfig; import org.lsposed.lspd.config.LSPApplicationServiceClient; import org.lsposed.lspd.deopt.PrebuiltMethodsDeopter; import org.lsposed.lspd.hooker.CrashDumpHooker; @@ -37,6 +36,7 @@ import org.lsposed.lspd.hooker.HandleBindAppHooker; import org.lsposed.lspd.hooker.LoadedApkCstrHooker; import org.lsposed.lspd.hooker.SystemMainHooker; import org.lsposed.lspd.service.ServiceManager; +import org.lsposed.lspd.util.ParasiticManagerHooker; import org.lsposed.lspd.util.Utils; import org.lsposed.lspd.yahfa.hooker.YahfaHooker; @@ -81,6 +81,10 @@ public class Main { XposedBridge.initXResources(); XposedInit.startsSystemServer = isSystem; PrebuiltMethodsDeopter.deoptBootMethods(); // do it once for secondary zygote + if (niceName.equals(BuildConfig.MANAGER_INJECTED_PKG_NAME) && ParasiticManagerHooker.start()) { + Utils.logI("Loaded manager, skipping next steps"); + return; + } installBootstrapHooks(isSystem, appDataDir); Utils.logI("Loading modules for " + niceName + "/" + Process.myUid()); XposedInit.loadModules(); diff --git a/core/src/main/java/org/lsposed/lspd/hooker/HandleBindAppHooker.java b/core/src/main/java/org/lsposed/lspd/hooker/HandleBindAppHooker.java index 7c3cbdda..50f5cae5 100644 --- a/core/src/main/java/org/lsposed/lspd/hooker/HandleBindAppHooker.java +++ b/core/src/main/java/org/lsposed/lspd/hooker/HandleBindAppHooker.java @@ -51,6 +51,7 @@ public class HandleBindAppHooker extends XC_MethodHook { // save app process name here for later use String appProcessName = (String) XposedHelpers.getObjectField(bindData, "processName"); String reportedPackageName = appInfo.packageName.equals("android") ? "system" : appInfo.packageName; + // Note: packageName="android" -> system_server process, ams pms etc; // packageName="system" -> android pkg, system dialogues. Utils.logD("processName=" + appProcessName + diff --git a/core/src/main/java/org/lsposed/lspd/service/ActivityController.java b/core/src/main/java/org/lsposed/lspd/service/ActivityController.java new file mode 100644 index 00000000..3ff5b1c5 --- /dev/null +++ b/core/src/main/java/org/lsposed/lspd/service/ActivityController.java @@ -0,0 +1,202 @@ +package org.lsposed.lspd.service; + +import static org.lsposed.lspd.service.ServiceManager.TAG; + +import android.annotation.SuppressLint; +import android.app.ActivityThread; +import android.app.IActivityController; +import android.app.IActivityManager; +import android.content.Context; +import android.content.Intent; +import android.os.Binder; +import android.os.IBinder; +import android.os.Parcel; +import android.os.ResultReceiver; +import android.os.ShellCallback; +import android.os.ShellCommand; +import android.util.Log; + +import androidx.annotation.NonNull; + +import org.lsposed.lspd.BuildConfig; + +import java.io.InputStream; +import java.io.PrintWriter; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +public class ActivityController extends IActivityController.Stub { + private static Constructor myActivityControllerConstructor = null; + private static Method myActivityControllerRunner = null; + private static boolean inited = false; + + private static IActivityController controller = null; + + static private ActivityController instance; + + static { + try { + Context ctx = ActivityThread.currentActivityThread().getSystemContext(); + var systemClassLoader = ctx.getClassLoader(); + @SuppressLint("PrivateApi") var myActivityControllerClass = Class.forName("com.android.server.am.ActivityManagerShellCommand$MyActivityController", false, systemClassLoader); + myActivityControllerConstructor = myActivityControllerClass.getDeclaredConstructor(IActivityManager.class, PrintWriter.class, InputStream.class, + String.class, boolean.class); + myActivityControllerConstructor.setAccessible(true); + myActivityControllerRunner = myActivityControllerClass.getDeclaredMethod("run"); + myActivityControllerRunner.setAccessible(true); + inited = true; + } catch (Throwable e) { + Log.e(TAG, "Failed to init ActivityController", e); + } + } + + private ActivityController() { + instance = this; + } + + static private @NonNull + ActivityController getInstance() { + if (instance == null) new ActivityController(); + return instance; + } + + static boolean replaceShellCommand(IBinder am, Parcel data) { + if (!inited) return false; + try { + var in = data.readFileDescriptor(); + var out = data.readFileDescriptor(); + var err = data.readFileDescriptor(); + String[] args = data.createStringArray(); + ShellCallback shellCallback = ShellCallback.CREATOR.createFromParcel(data); + ResultReceiver resultReceiver = ResultReceiver.CREATOR.createFromParcel(data); + + if (args.length > 0 && "monitor".equals(args[0])) { + new ShellCommand() { + @Override + public int onCommand(String cmd) { + final PrintWriter pw = getOutPrintWriter(); + String opt; + String gdbPort = null; + boolean monkey = false; + while ((opt = getNextOption()) != null) { + if (opt.equals("--gdb")) { + gdbPort = getNextArgRequired(); + } else if (opt.equals("-m")) { + monkey = true; + } else { + getErrPrintWriter().println("Error: Unknown option: " + opt); + return -1; + } + } + + return replaceMyControllerActivity(am, pw, getRawInputStream(), gdbPort, monkey); + } + + @Override + public void onHelp() { + + } + }.exec((Binder) am, in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(), args, shellCallback, resultReceiver); + return true; + } + } catch (Throwable e) { + Log.e(TAG, "replace shell command", e); + } finally { + data.setDataPosition(0); + } + return false; + } + + static boolean replaceActivityController(Parcel data) { + if (!inited) return false; + try { + var position = data.dataPosition(); + var controller = replaceActivityController(IActivityController.Stub.asInterface(data.readStrongBinder())); + var b = data.readInt(); + data.setDataSize(position); + data.setDataPosition(position); + data.writeStrongInterface(controller); + data.writeInt(b); + } catch (Throwable e) { + Log.e(TAG, "replace activity controller", e); + } finally { + data.setDataPosition(0); + } + return false; + } + + static private int replaceMyControllerActivity(IBinder am, PrintWriter pw, InputStream stream, String gdbPort, boolean monkey) { + try { + InvocationHandler handler = (proxy, method, args1) -> { + if (method.getName().equals("setActivityController")) { + try { + args1[0] = replaceActivityController((IActivityController) args1[0]); + } catch (Throwable e) { + Log.e(TAG, "replace activity controller", e); + } + } + return method.invoke(am, args1); + }; + var amProxy = Proxy.newProxyInstance(BridgeService.class.getClassLoader(), + new Class[]{myActivityControllerConstructor.getParameterTypes()[0]}, handler); + var ctrl = myActivityControllerConstructor.newInstance(amProxy, pw, stream, gdbPort, monkey); + myActivityControllerRunner.invoke(ctrl); + return 0; + } catch (Throwable e) { + Log.e(TAG, "run monitor", e); + return 1; + } + } + + static private IActivityController replaceActivityController(IActivityController controller) { + Log.d(TAG, "android.app.IActivityManager.setActivityController is called"); + ActivityController.controller = controller; + return getInstance(); + } + + @Override + public boolean activityStarting(Intent intent, String pkg) { + Log.d(TAG, "activity from " + pkg + " with " + intent + " with extras " + intent.getExtras() + " is starting"); + var snapshot = BridgeService.getService(); + if (snapshot != null && BuildConfig.MANAGER_INJECTED_PKG_NAME.equals(pkg)) { + try { + return snapshot.preStartManager(pkg, intent); + } catch (Throwable e) { + Log.e(TAG, "request manager", e); + } + } + return controller == null || controller.activityStarting(intent, pkg); + } + + @Override + public boolean activityResuming(String pkg) { + return controller == null || controller.activityResuming(pkg); + } + + @Override + public boolean appCrashed(String processName, int pid, String shortMsg, String longMsg, long timeMillis, String stackTrace) { + return controller == null || controller.appCrashed(processName, pid, shortMsg, longMsg, timeMillis, stackTrace); + } + + @Override + public int appEarlyNotResponding(String processName, int pid, String annotation) { + return controller == null ? 0 : controller.appNotResponding(processName, pid, annotation); + } + + @Override + public int appNotResponding(String processName, int pid, String processStats) { + return controller == null ? 0 : controller.appNotResponding(processName, pid, processStats); + } + + @Override + public int systemNotResponding(String msg) { + return controller == null ? -1 : controller.systemNotResponding(msg); + } + + @Override + public IBinder asBinder() { + return this; + } +} diff --git a/core/src/main/java/org/lsposed/lspd/service/ActivityManagerService.java b/core/src/main/java/org/lsposed/lspd/service/ActivityManagerService.java index ac90161d..6df6daa7 100644 --- a/core/src/main/java/org/lsposed/lspd/service/ActivityManagerService.java +++ b/core/src/main/java/org/lsposed/lspd/service/ActivityManagerService.java @@ -60,10 +60,11 @@ public class ActivityManagerService { if (binder == null) return null; try { binder.linkToDeath(deathRecipient, 0); + am = IActivityManager.Stub.asInterface(binder); + am.setActivityController(null, false); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(e)); } - am = IActivityManager.Stub.asInterface(binder); } return am; } @@ -161,4 +162,5 @@ public class ActivityManagerService { if (am == null) return null; return am.getCurrentUser(); } + } diff --git a/core/src/main/java/org/lsposed/lspd/service/BridgeService.java b/core/src/main/java/org/lsposed/lspd/service/BridgeService.java index 65f184ad..3cd3e606 100644 --- a/core/src/main/java/org/lsposed/lspd/service/BridgeService.java +++ b/core/src/main/java/org/lsposed/lspd/service/BridgeService.java @@ -34,6 +34,8 @@ import android.os.Parcel; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; +import android.system.ErrnoException; +import android.system.Os; import android.util.Log; import androidx.annotation.NonNull; @@ -46,6 +48,7 @@ public class BridgeService { private static final int TRANSACTION_CODE = ('_' << 24) | ('L' << 16) | ('S' << 8) | 'P'; private static final String DESCRIPTOR = "LSPosed"; private static final String SERVICE_NAME = "activity"; + private static final String SHORTCUT_ID = "org.lsposed.manager.shortcut"; enum ACTION { ACTION_UNKNOWN, @@ -103,7 +106,6 @@ public class BridgeService { }; public interface Listener { - void onSystemServerRestarted(); void onResponseFromBridgeService(boolean response); @@ -114,72 +116,87 @@ public class BridgeService { private static Listener listener; // For service + // This MUST run in main thread private static synchronized void sendToBridge(IBinder binder, boolean isRestart) { - do { - bridgeService = ServiceManager.getService(SERVICE_NAME); - if (bridgeService != null && bridgeService.pingBinder()) { - break; - } - - Log.i(TAG, "service " + SERVICE_NAME + " is not started, wait 1s."); - - try { - //noinspection BusyWait - Thread.sleep(1000); - } catch (Throwable e) { - Log.w(TAG, "sleep" + Log.getStackTraceString(e)); - } - } while (true); - - if (isRestart && listener != null) { - listener.onSystemServerRestarted(); - } - + assert Looper.myLooper() == Looper.getMainLooper(); try { - bridgeService.linkToDeath(bridgeRecipient, 0); - } catch (Throwable e) { - Log.w(TAG, "linkToDeath " + Log.getStackTraceString(e)); - var snapshot = bridgeService; - sendToBridge(binder, snapshot == null || !snapshot.isBinderAlive()); - return; + Os.seteuid(0); + } catch (ErrnoException e) { + Log.e(TAG, "seteuid 0", e); } + try { + do { + bridgeService = ServiceManager.getService(SERVICE_NAME); + if (bridgeService != null && bridgeService.pingBinder()) { + break; + } + + Log.i(TAG, "service " + SERVICE_NAME + " is not started, wait 1s."); + + try { + //noinspection BusyWait + Thread.sleep(1000); + } catch (Throwable e) { + Log.w(TAG, "sleep" + Log.getStackTraceString(e)); + } + } while (true); + + if (isRestart && listener != null) { + listener.onSystemServerRestarted(); + } - Parcel data = Parcel.obtain(); - Parcel reply = Parcel.obtain(); - boolean res = false; - // try at most three times - for (int i = 0; i < 3; i++) { try { - data.writeInterfaceToken(DESCRIPTOR); - data.writeInt(ACTION.ACTION_SEND_BINDER.ordinal()); - Log.v(TAG, "binder " + binder.toString()); - data.writeStrongBinder(binder); - if (bridgeService == null) break; - res = bridgeService.transact(TRANSACTION_CODE, data, reply, 0); - reply.readException(); + bridgeService.linkToDeath(bridgeRecipient, 0); } catch (Throwable e) { - Log.e(TAG, "send binder " + Log.getStackTraceString(e)); + Log.w(TAG, "linkToDeath " + Log.getStackTraceString(e)); var snapshot = bridgeService; sendToBridge(binder, snapshot == null || !snapshot.isBinderAlive()); return; - } finally { - data.recycle(); - reply.recycle(); } - if (res) break; + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + boolean res = false; + // try at most three times + for (int i = 0; i < 3; i++) { + try { + data.writeInterfaceToken(DESCRIPTOR); + data.writeInt(ACTION.ACTION_SEND_BINDER.ordinal()); + Log.v(TAG, "binder " + binder.toString()); + data.writeStrongBinder(binder); + if (bridgeService == null) break; + res = bridgeService.transact(TRANSACTION_CODE, data, reply, 0); + reply.readException(); + } catch (Throwable e) { + Log.e(TAG, "send binder " + Log.getStackTraceString(e)); + var snapshot = bridgeService; + sendToBridge(binder, snapshot == null || !snapshot.isBinderAlive()); + return; + } finally { + data.recycle(); + reply.recycle(); + } - Log.w(TAG, "no response from bridge, retry in 1s"); + if (res) break; + Log.w(TAG, "no response from bridge, retry in 1s"); + + try { + Thread.sleep(1000); + } catch (InterruptedException ignored) { + } + } + + if (listener != null) { + listener.onResponseFromBridgeService(res); + } + } finally { try { - Thread.sleep(1000); - } catch (InterruptedException ignored) { + Os.seteuid(1000); + } catch (ErrnoException e) { + Log.e(TAG, "seteuid 1000", e); } } - - if (listener != null) { - listener.onResponseFromBridgeService(res); - } } // For client @@ -204,8 +221,8 @@ public class BridgeService { } try { IApplicationThread at = ActivityThread.currentActivityThread().getApplicationThread(); - Context ct = ActivityThread.currentActivityThread().getSystemContext(); - service.dispatchSystemServerContext(at.asBinder(), Context_getActivityToken(ct)); + Context ctx = ActivityThread.currentActivityThread().getSystemContext(); + service.dispatchSystemServerContext(at.asBinder(), Context_getActivityToken(ctx)); } catch (Throwable e) { Log.e(TAG, "dispatch context: ", e); } @@ -264,7 +281,55 @@ public class BridgeService { return false; } - public static boolean execTransact(int code, long dataObj, long replyObj, int flags) { + @SuppressWarnings("unused") + public static boolean replaceShellCommand(IBinder obj, int code, long dataObj, long replyObj, int flags) { + Parcel data = ParcelUtils.fromNativePointer(dataObj); + Parcel reply = ParcelUtils.fromNativePointer(replyObj); + + if (data == null || reply == null) { + Log.w(TAG, "Got transaction with null data or reply"); + return false; + } + + try { + String descriptor = obj.getInterfaceDescriptor(); + if (descriptor == null || (!descriptor.equals("android.app.IActivityManager") && + !descriptor.equals("com.sonymobile.hookservice.HookActivityService"))) { + return false; + } + return ActivityController.replaceShellCommand(obj, data); + } catch (Throwable e) { + Log.e(TAG, "replace shell command", e); + return false; + } finally { + data.setDataPosition(0); + } + } + + @SuppressWarnings("unused") + public static boolean replaceActivityController(IBinder obj, int code, long dataObj, long replyObj, int flags) { + Parcel data = ParcelUtils.fromNativePointer(dataObj); + Parcel reply = ParcelUtils.fromNativePointer(replyObj); + + if (data == null || reply == null) { + Log.w(TAG, "Got transaction with null data or reply"); + return false; + } + + try { + String descriptor = ParcelUtils.readInterfaceDescriptor(data); + if (!descriptor.equals("android.app.IActivityManager") && + !descriptor.equals("com.sonymobile.hookservice.HookActivityService")) { + return false; + } + return ActivityController.replaceActivityController(data); + } finally { + data.setDataPosition(0); + } + } + + @SuppressWarnings("unused") + public static boolean execTransact(IBinder obj, int code, long dataObj, long replyObj, int flags) { if (code != TRANSACTION_CODE) return false; Parcel data = ParcelUtils.fromNativePointer(dataObj); @@ -312,5 +377,4 @@ public class BridgeService { } return null; } - } diff --git a/core/src/main/java/org/lsposed/lspd/service/ConfigFileManager.java b/core/src/main/java/org/lsposed/lspd/service/ConfigFileManager.java index 1efe4c14..49048ee9 100644 --- a/core/src/main/java/org/lsposed/lspd/service/ConfigFileManager.java +++ b/core/src/main/java/org/lsposed/lspd/service/ConfigFileManager.java @@ -3,9 +3,12 @@ package org.lsposed.lspd.service; import static org.lsposed.lspd.service.ServiceManager.TAG; import static org.lsposed.lspd.service.ServiceManager.toGlobalNamespace; +import android.content.res.AssetManager; +import android.content.res.Resources; import android.os.ParcelFileDescriptor; import android.os.SELinux; import android.os.SharedMemory; +import android.os.SystemProperties; import android.system.ErrnoException; import android.system.OsConstants; import android.util.Log; @@ -14,11 +17,14 @@ import androidx.annotation.Nullable; import org.lsposed.lspd.models.PreLoadedApk; import org.lsposed.lspd.util.Utils; +import org.w3c.dom.ls.LSResourceResolver; import java.io.BufferedReader; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; +import java.lang.reflect.Method; import java.nio.channels.Channels; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; @@ -37,10 +43,11 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.zip.ZipFile; -class ConfigFileManager { +public class ConfigFileManager { static final Path basePath = Paths.get("/data/adb/lspd"); static final File managerApkPath = basePath.resolve("manager.apk").toFile(); private static final Path lockPath = basePath.resolve("lock"); @@ -53,6 +60,8 @@ class ConfigFileManager { @SuppressWarnings("FieldCanBeLocal") private static FileLocker locker = null; + private static final Resources res; + static { try { Files.createDirectories(basePath); @@ -62,6 +71,71 @@ class ConfigFileManager { } catch (IOException e) { Log.e(TAG, Log.getStackTraceString(e)); } + Resources tmpRes; + try { + AssetManager am = AssetManager.class.newInstance(); + Method addAssetPath = AssetManager.class.getDeclaredMethod("addAssetPath", String.class); + addAssetPath.setAccessible(true); + addAssetPath.invoke(am, managerApkPath.getAbsolutePath()); + tmpRes = new Resources(am, null, null); + } catch (Throwable e) { + tmpRes = null; + } + res = tmpRes; + } + + public static Resources getResources() { + return res; + } + + // from AndroidRuntime.cpp + private static String readLocale() { + String locale = SystemProperties.get("persist.sys.locale", ""); + if (!locale.isEmpty()) { + return locale; + } + + String language = SystemProperties.get("persist.sys.language", ""); + if (!language.isEmpty()) { + String country = SystemProperties.get("persist.sys.country", ""); + String variant = SystemProperties.get("persist.sys.localevar", ""); + + String out = language; + if (!country.isEmpty()) { + out = out + "-" + country; + } + + if (!variant.isEmpty()) { + out = out + "-" + variant; + } + + return out; + } + + String productLocale = SystemProperties.get("ro.product.locale", ""); + if (!productLocale.isEmpty()) { + return productLocale; + } + + // If persist.sys.locale and ro.product.locale are missing, + // construct a locale value from the individual locale components. + String productLanguage = SystemProperties.get("ro.product.locale.language", "en"); + String productRegion = SystemProperties.get("ro.product.locale.region", "US"); + + return productLanguage + "-" + productRegion; + } + + static void reloadLocale() { + Locale locale = Locale.forLanguageTag(readLocale()); + Locale.setDefault(locale); + var conf = res.getConfiguration(); + conf.setLocale(Locale.forLanguageTag(readLocale())); + res.updateConfiguration(conf, res.getDisplayMetrics()); + } + + static ParcelFileDescriptor getManagerApk() throws FileNotFoundException { + SELinux.setFileContext(managerApkPath.getAbsolutePath(), "u:object_r:system_file:s0"); + return ParcelFileDescriptor.open(managerApkPath.getAbsoluteFile(), ParcelFileDescriptor.MODE_READ_ONLY); } static void deleteFolderIfExists(Path target) throws IOException { diff --git a/core/src/main/java/org/lsposed/lspd/service/ConfigManager.java b/core/src/main/java/org/lsposed/lspd/service/ConfigManager.java index b1907abd..006d9e1c 100644 --- a/core/src/main/java/org/lsposed/lspd/service/ConfigManager.java +++ b/core/src/main/java/org/lsposed/lspd/service/ConfigManager.java @@ -244,15 +244,6 @@ public class ConfigManager { } } - public void ensureManager() { - if (!PackageService.isAlive()) return; - new Thread(() -> { - if (PackageService.installManagerIfAbsent(manager, ConfigFileManager.managerApkPath)) { - updateManager(); - } - }).start(); - } - static ConfigManager getInstance() { if (instance == null) instance = new ConfigManager(); @@ -536,9 +527,7 @@ public class ConfigManager { // This is called when a new process created, use the cached result public boolean shouldSkipProcess(ProcessScope scope) { - return !cachedScope.containsKey(scope) && - !isManager(scope.uid) && - !shouldBlock(scope.processName); + return !cachedScope.containsKey(scope) && !isManager(scope.uid); } public boolean isUidHooked(int uid) { @@ -793,6 +782,15 @@ public class ConfigManager { return verboseLog; } + public ParcelFileDescriptor getManagerApk() { + try { + return ConfigFileManager.getManagerApk(); + } catch (Throwable e) { + Log.e(TAG, "failed to open manager apk", e); + return null; + } + } + public ParcelFileDescriptor getModulesLog() { try { var modulesLog = ServiceManager.getLogcatService().getModulesLog(); @@ -828,10 +826,6 @@ public class ConfigManager { return uid == managerUid; } - public boolean shouldBlock(String packageName) { - return packageName.equals("io.github.lsposed.manager") || isManager(packageName); - } - public String getPrefsPath(String fileName, int uid) { int userId = uid / PER_USER_RANGE; return miscPath + "/prefs" + (userId == 0 ? "" : String.valueOf(userId)) + "/" + fileName; diff --git a/core/src/main/java/org/lsposed/lspd/service/LSPApplicationService.java b/core/src/main/java/org/lsposed/lspd/service/LSPApplicationService.java index 8ef566fe..3b4f610e 100644 --- a/core/src/main/java/org/lsposed/lspd/service/LSPApplicationService.java +++ b/core/src/main/java/org/lsposed/lspd/service/LSPApplicationService.java @@ -23,14 +23,15 @@ import static org.lsposed.lspd.service.ServiceManager.TAG; import android.os.Bundle; import android.os.IBinder; +import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.util.Log; import android.util.Pair; import org.lsposed.lspd.models.Module; import org.lsposed.lspd.util.InstallerVerifier; -import org.lsposed.lspd.util.Utils; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; @@ -74,11 +75,14 @@ public class LSPApplicationService extends ILSPApplicationService.Stub { @Override public List getModulesList(String processName) throws RemoteException { ensureRegistered(); - int callingUid = getCallingUid(); - if (callingUid == 1000 && processName.equals("android")) { + int pid = getCallingPid(); + int uid = getCallingUid(); + if (uid == 1000 && processName.equals("android")) { return ConfigManager.getInstance().getModulesForSystemServer(); } - return ConfigManager.getInstance().getModulesForProcess(processName, callingUid); + if (ServiceManager.getManagerService().isRunningManager(pid, uid)) + return Collections.emptyList(); + return ConfigManager.getInstance().getModulesForProcess(processName, uid); } @Override @@ -105,17 +109,33 @@ public class LSPApplicationService extends ILSPApplicationService.Stub { @Override public boolean requestManagerBinder(String packageName, String path, List binder) throws RemoteException { ensureRegistered(); - if (ConfigManager.getInstance().isManager(getCallingUid()) && + var pid = getCallingPid(); + var uid = getCallingUid(); + if (ConfigManager.getInstance().isManager(uid) && ConfigManager.getInstance().isManager(packageName) && InstallerVerifier.verifyInstallerSignature(path)) { - var service = ServiceManager.getManagerService(); - if (Utils.isMIUI) { - service.new ManagerGuard(handles.get(getCallingPid())); + var heartbeat = handles.getOrDefault(pid, null); + if (heartbeat != null) { + binder.add(ServiceManager.getManagerService().obtainManagerBinder(heartbeat, pid, uid)); } - binder.add(service); return false; } - return ConfigManager.getInstance().shouldBlock(packageName); + return ConfigManager.getInstance().isManager(packageName); + } + + @Override + public ParcelFileDescriptor requestInjectedManagerBinder(List binder) throws RemoteException { + ensureRegistered(); + var pid = getCallingPid(); + var uid = getCallingUid(); + if (ServiceManager.getManagerService().postStartManager(pid, uid)) { + var heartbeat = handles.get(pid); + if (heartbeat != null) { + binder.add(ServiceManager.getManagerService().obtainManagerBinder(heartbeat, pid, uid)); + } + + } + return ConfigManager.getInstance().getManagerApk(); } public boolean hasRegister(int uid, int pid) { diff --git a/core/src/main/java/org/lsposed/lspd/service/LSPManagerService.java b/core/src/main/java/org/lsposed/lspd/service/LSPManagerService.java index 5f2b28c4..73a67d7f 100644 --- a/core/src/main/java/org/lsposed/lspd/service/LSPManagerService.java +++ b/core/src/main/java/org/lsposed/lspd/service/LSPManagerService.java @@ -22,77 +22,399 @@ package org.lsposed.lspd.service; import static android.content.Context.BIND_AUTO_CREATE; import static org.lsposed.lspd.service.ServiceManager.TAG; +import android.app.INotificationManager; import android.app.IServiceConnection; +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; import android.content.ComponentName; +import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutManager; import android.content.pm.VersionedPackage; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.Icon; +import android.net.Uri; +import android.os.Bundle; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.ResultReceiver; +import android.os.SELinux; import android.os.SystemProperties; import android.util.Log; +import androidx.annotation.NonNull; + import org.lsposed.lspd.BuildConfig; import org.lsposed.lspd.ILSPManagerService; import org.lsposed.lspd.models.Application; import org.lsposed.lspd.models.UserInfo; +import org.lsposed.lspd.util.FakeContext; +import org.lsposed.lspd.util.Utils; +import java.io.File; import java.io.FileDescriptor; import java.lang.reflect.InvocationTargetException; +import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.UUID; import de.robv.android.xposed.XposedBridge; +import hidden.HiddenApiBridge; import io.github.xposed.xposedservice.utils.ParceledListSlice; public class LSPManagerService extends ILSPManagerService.Stub { - - public Object guard = null; - private static final String PROP_NAME = "dalvik.vm.dex2oat-flags"; private static final String PROP_VALUE = "--inline-max-code-units=0"; + // this maybe useful when obtaining the manager binder + private static final String RANDOM_UUID = UUID.randomUUID().toString(); + private static final String SHORTCUT_ID = "org.lsposed.manager.shortcut"; + public static final int NOTIFICATION_ID = 114514; + public static final String CHANNEL_ID = "lsposed"; + public static final String CHANNEL_NAME = "LSPosed Manager"; + public static final int CHANNEL_IMP = NotificationManager.IMPORTANCE_HIGH; + + private static Icon managerIcon = null; + private static Icon notificationIcon = null; + private static Intent managerIntent = null; public class ManagerGuard implements IBinder.DeathRecipient { - private final IBinder binder; + private final @NonNull + IBinder binder; + private final int pid; + private final int uid; private final IServiceConnection connection = new IServiceConnection.Stub() { @Override public void connected(ComponentName name, IBinder service, boolean dead) { } }; - public ManagerGuard(IBinder binder) { + public ManagerGuard(@NonNull IBinder binder, int pid, int uid) { guard = this; + this.pid = pid; + this.uid = uid; this.binder = binder; try { this.binder.linkToDeath(this, 0); - var intent = new Intent(); - intent.setComponent(ComponentName.unflattenFromString("com.miui.securitycore/com.miui.xspace.service.XSpaceService")); - ActivityManagerService.bindService(intent, intent.getType(), connection, BIND_AUTO_CREATE, "android", 0); + if (Utils.isMIUI) { + var intent = new Intent(); + intent.setComponent(ComponentName.unflattenFromString("com.miui.securitycore/com.miui.xspace.service.XSpaceService")); + ActivityManagerService.bindService(intent, intent.getType(), connection, BIND_AUTO_CREATE, "android", 0); + } } catch (Throwable e) { Log.e(TAG, "manager guard", e); + guard = null; } } @Override public void binderDied() { try { - if (binder != null) binder.unlinkToDeath(this, 0); + binder.unlinkToDeath(this, 0); ActivityManagerService.unbindService(connection); } catch (Throwable e) { Log.e(TAG, "manager guard", e); } guard = null; } + + boolean isAlive() { + return binder.isBinderAlive(); + } } + public ManagerGuard guard = null; + + // guard to determine the manager or the injected app + // that is to say, to make the parasitic success, + // we should make sure no extra launch after parasitic + // launch is queued and before the process is started + private boolean pendingManager = false; + private int managerPid = -1; + LSPManagerService() { } + private static Icon getIcon(int res) { + var icon = ConfigFileManager.getResources().getDrawable(res, ConfigFileManager.getResources().newTheme()); + var bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(), icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); + icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight()); + icon.draw(new Canvas(bitmap)); + return Icon.createWithBitmap(bitmap); + } + + private static Icon getManagerIcon() { + if (managerIcon == null) { + managerIcon = getIcon(org.lsposed.manager.R.drawable.ic_launcher); + } + return managerIcon; + } + + private static Icon getNotificationIcon() { + if (notificationIcon == null) { + notificationIcon = getIcon(org.lsposed.manager.R.drawable.ic_extension); + } + return notificationIcon; + } + + private static Intent getManagerIntent() { + try { + if (managerIntent == null) { + var intent = PackageService.getLaunchIntentForPackage(BuildConfig.MANAGER_INJECTED_PKG_NAME); + if (intent == null) { + var pkgInfo = PackageService.getPackageInfo(BuildConfig.MANAGER_INJECTED_PKG_NAME, PackageManager.GET_ACTIVITIES, 0); + if (pkgInfo.activities != null && pkgInfo.activities.length > 0) { + for (var activityInfo : pkgInfo.activities) { + if (activityInfo.processName.equals(activityInfo.packageName)) { + intent = new Intent(); + intent.setComponent(new ComponentName(activityInfo.packageName, activityInfo.name)); + intent.setAction(Intent.ACTION_MAIN); + break; + } + } + } + } + if (intent.getCategories() != null) intent.getCategories().clear(); + intent.addCategory("org.lsposed.manager.LAUNCH_MANAGER"); + managerIntent = (Intent) intent.clone(); + } + } catch (Throwable e) { + Log.e(TAG, "get Intent", e); + } + return managerIntent; + + } + + public static PendingIntent getNotificationIntent(String modulePackageName, int moduleUserId) { + try { + var intent = (Intent) getManagerIntent().clone(); + intent.setData(Uri.parse("module://" + modulePackageName + ":" + moduleUserId)); + return PendingIntent.getActivity(new FakeContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); + } catch (Throwable e) { + Log.e(TAG, "get notification intent", e); + return null; + } + } + + public static void showNotification(String modulePackageName, + int moduleUserId, + boolean enabled, + boolean systemModule) { + try { + var context = new FakeContext(); + String title = context.getString(enabled ? systemModule ? + org.lsposed.manager.R.string.xposed_module_updated_notification_title_system : + org.lsposed.manager.R.string.xposed_module_updated_notification_title : + org.lsposed.manager.R.string.module_is_not_activated_yet); + String content = context.getString(enabled ? systemModule ? + org.lsposed.manager.R.string.xposed_module_updated_notification_content_system : + org.lsposed.manager.R.string.xposed_module_updated_notification_content : + org.lsposed.manager.R.string.module_is_not_activated_yet_detailed, modulePackageName); + + var notification = new Notification.Builder(context, CHANNEL_ID) + .setContentTitle(title) + .setContentText(content) + .setSmallIcon(getNotificationIcon()) + .setColor(context.getResources().getColor(org.lsposed.manager.R.color.color_primary)) + .setContentIntent(getNotificationIntent(modulePackageName, moduleUserId)) + .setAutoCancel(true) + .build(); + notification.extras.putString("android.substName", "LSPosed"); + var im = INotificationManager.Stub.asInterface(android.os.ServiceManager.getService("notification")); + final NotificationChannel channel = + new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, CHANNEL_IMP); + im.createNotificationChannels("android", + new android.content.pm.ParceledListSlice<>(Collections.singletonList(channel))); + im.enqueueNotificationWithTag("android", "android", "114514", NOTIFICATION_ID, notification, 0); + } catch (Throwable e) { + Log.e(TAG, "posted notification", e); + } + } + + public static void createOrUpdateShortcut() { + try { + var smCtor = ShortcutManager.class.getDeclaredConstructor(Context.class); + smCtor.setAccessible(true); + var context = new FakeContext(); + var sm = smCtor.newInstance(context); + if (!sm.isRequestPinShortcutSupported()) { + Log.d(TAG, "pinned shortcut not supported, skipping"); + return; + } + var shortcut = new ShortcutInfo.Builder(context, SHORTCUT_ID) + .setShortLabel("LSPosed") + .setLongLabel("LSPosed") + .setIntent(getManagerIntent()) + .setIcon(getManagerIcon()) + .build(); + + for (var shortcutInfo : sm.getPinnedShortcuts()) { + if (SHORTCUT_ID.equals(shortcutInfo.getId())) { + Log.d(TAG, "shortcut exists, updating"); + sm.updateShortcuts(Collections.singletonList(shortcut)); + return; + } + } + + sm.requestPinShortcut(shortcut, null); + Log.d(TAG, "done add shortcut"); + } catch (Throwable e) { + Log.e(TAG, "add shortcut", e); + } + } + + public ManagerGuard guardSnapshot() { + var snapshot = guard; + return snapshot != null && snapshot.isAlive() ? snapshot : null; + } + + private void ensureWebViewPermission(File f) { + if (!f.exists()) return; + SELinux.setFileContext(f.getAbsolutePath(), "u:object_r:privapp_data_file:s0"); + if (f.isDirectory()) { + for (var g : f.listFiles()) { + ensureWebViewPermission(g); + } + } + } + + private void ensureWebViewPermission() { + try { + var pkgInfo = PackageService.getPackageInfo(BuildConfig.MANAGER_INJECTED_PKG_NAME, 0, 0); + var cacheDir = new File(HiddenApiBridge.ApplicationInfo_credentialProtectedDataDir(pkgInfo.applicationInfo) + "/cache"); + var webviewDir = new File(cacheDir, "WebView"); + var httpCacheDir = new File(cacheDir, "http_cache"); + ensureWebViewPermission(webviewDir); + ensureWebViewPermission(httpCacheDir); + } catch (Throwable e) { + Log.w(TAG, "cannot ensure webview dir", e); + } + } + + // To start injected manager, we should take care about conflict + // with the target app since we won't inject into it + // if we are not going to display manager. + // Thus, when someone launching manager, we should no matter + // stop any process of the target app + // Ideally we should call force stop package here, + // however it's not feasible because it will cause deadlock + // Thus we will cancel the launch of the activity + // and manually start activity with force stopping + // However, the intent we got here is not complete since + // there's no extras. We cannot do the same thing + // where starting the target app while the manager is + // still running. + // We instead let the manager to restart the activity. + synchronized boolean preStartManager(String pkgName, Intent intent) { + // first, check if it's our target app, if not continue the start + if (BuildConfig.MANAGER_INJECTED_PKG_NAME.equals(pkgName)) { + Log.d(TAG, "starting target app of parasitic manager"); + // check if it's launching our manager + if (intent.getCategories() != null && + intent.getCategories().contains("org.lsposed.manager.LAUNCH_MANAGER")) { + Log.d(TAG, "requesting launch of manager"); + // a new launch for the manager + // check if there's one running + // or it's run by ourselves after force stopping + var snapshot = guardSnapshot(); + if (intent.getCategories().contains(RANDOM_UUID) || + (snapshot != null && snapshot.isAlive() && snapshot.uid == BuildConfig.MANAGER_INJECTED_UID)) { + Log.d(TAG, "manager is still running or is on its way"); + // there's one running parasitic manager + // or it's run by ourself after killing, resume it + return true; + } else { + // new parasitic manager launch, set the flag and kill + // old processes + // we do it by cancelling the launch (return false) + // and start activity in a new thread + pendingManager = true; + new Thread(() -> { + ensureWebViewPermission(); + stopAndStartActivity(pkgName, intent, true); + }).start(); + Log.d(TAG, "requested to launch manager"); + return false; + } + } else if (pendingManager) { + // there's still parasitic manager, cancel a normal launch until + // the parasitic manager is launch + Log.d(TAG, "previous request is not yet done"); + return false; + } + // this is a normal launch of the target app + // send it to the manager and let it to restart the package + // if the manager is running + // or normally restart without injecting + Log.d(TAG, "launching the target app normally"); + return true; + } + return true; + } + + synchronized void stopAndStartActivity(String packageName, Intent intent, boolean addUUID) { + try { + ActivityManagerService.forceStopPackage(packageName, 0); + Log.d(TAG, "stopped old package"); + if (addUUID) { + intent = (Intent) intent.clone(); + intent.addCategory(RANDOM_UUID); + } + ActivityManagerService.startActivityAsUserWithFeature("android", null, intent, intent.getType(), null, null, 0, 0, null, null, 0); + Log.d(TAG, "relaunching"); + } catch (RemoteException e) { + Log.e(TAG, "stop and start activity", e); + } + } + + // return true to inject manager + synchronized boolean shouldStartManager(int pid, int uid, String processName) { + if (uid != BuildConfig.MANAGER_INJECTED_UID || !BuildConfig.MANAGER_INJECTED_PKG_NAME.equals(processName) || !pendingManager) + return false; + // pending parasitic manager launch it processes + // now we have its pid so we allow it to be killed + // and thus reset the pending flag and mark its pid + pendingManager = false; + managerPid = pid; + Log.d(TAG, "starting injected manager: pid = " + pid + " uid = " + uid + " processName = " + processName); + return true; + } + + // return true to send manager binder + synchronized boolean postStartManager(int pid, int uid) { + return pid == managerPid && uid == BuildConfig.MANAGER_INJECTED_UID; + } + + public @NonNull + IBinder obtainManagerBinder(@NonNull IBinder heartbeat, int pid, int uid) { + new ManagerGuard(heartbeat, pid, uid); + if (postStartManager(pid, uid)) { + managerPid = 0; + } + return this; + } + + public boolean isRunningManager(int pid, int uid) { + var snapshotPid = managerPid; + var snapshotGuard = guardSnapshot(); + return (pid == snapshotPid && uid == BuildConfig.MANAGER_INJECTED_UID) || (snapshotGuard != null && snapshotGuard.pid == pid && snapshotGuard.uid == uid); + } + + void onSystemServerDied() { + pendingManager = false; + managerPid = 0; + guard = null; + } + @Override public IBinder asBinder() { return this; @@ -287,4 +609,10 @@ public class LSPManagerService extends ILSPManagerService.Stub { public Map getLogs() { return ConfigFileManager.getLogs(); } + + @Override + public void restartFor(Intent intent) throws RemoteException { + forceStopPackage(BuildConfig.MANAGER_INJECTED_PKG_NAME, 0); + stopAndStartActivity(BuildConfig.MANAGER_INJECTED_PKG_NAME, intent, false); + } } diff --git a/core/src/main/java/org/lsposed/lspd/service/LSPSystemServerService.java b/core/src/main/java/org/lsposed/lspd/service/LSPSystemServerService.java index a242df52..8e1d9946 100644 --- a/core/src/main/java/org/lsposed/lspd/service/LSPSystemServerService.java +++ b/core/src/main/java/org/lsposed/lspd/service/LSPSystemServerService.java @@ -71,7 +71,7 @@ public class LSPSystemServerService extends ILSPSystemServerService.Stub impleme } @Override - public ILSPApplicationService requestApplicationService(int uid, int pid, String processName, IBinder heartBeat) throws RemoteException { + public ILSPApplicationService requestApplicationService(int uid, int pid, String processName, IBinder heartBeat) { requested = true; if (ConfigManager.getInstance().shouldSkipSystemServer() || uid != 1000 || heartBeat == null || !"android".equals(processName)) return null; diff --git a/core/src/main/java/org/lsposed/lspd/service/LSPosedService.java b/core/src/main/java/org/lsposed/lspd/service/LSPosedService.java index 50acf6ec..2b6a1c0d 100644 --- a/core/src/main/java/org/lsposed/lspd/service/LSPosedService.java +++ b/core/src/main/java/org/lsposed/lspd/service/LSPosedService.java @@ -23,7 +23,6 @@ import static org.lsposed.lspd.service.PackageService.PER_USER_RANGE; import static org.lsposed.lspd.service.ServiceManager.TAG; import android.app.IApplicationThread; -import android.content.ComponentName; import android.content.IIntentReceiver; import android.content.Intent; import android.content.IntentFilter; @@ -33,7 +32,6 @@ import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; -import android.os.RemoteException; import android.util.Log; import java.util.Arrays; @@ -42,20 +40,22 @@ public class LSPosedService extends ILSPosedService.Stub { private static final int AID_NOBODY = 9999; private static final int USER_NULL = -10000; + @Override public ILSPApplicationService requestApplicationService(int uid, int pid, String processName, IBinder heartBeat) { if (Binder.getCallingUid() != 1000) { Log.w(TAG, "Someone else got my binder!?"); return null; } - if (ConfigManager.getInstance().shouldSkipProcess(new ConfigManager.ProcessScope(processName, uid))) { - Log.d(TAG, "Skipped " + processName + "/" + uid); - return null; - } if (ServiceManager.getApplicationService().hasRegister(uid, pid)) { Log.d(TAG, "Skipped duplicated request for uid " + uid + " pid " + pid); return null; } + if (!ServiceManager.getManagerService().shouldStartManager(pid, uid, processName) && + ConfigManager.getInstance().shouldSkipProcess(new ConfigManager.ProcessScope(processName, uid))) { + Log.d(TAG, "Skipped " + processName + "/" + uid); + return null; + } Log.d(TAG, "returned service"); return ServiceManager.requestApplicationService(uid, pid, heartBeat); } @@ -143,28 +143,7 @@ public class LSPosedService extends ILSPosedService.Stub { boolean enabled = Arrays.asList(enabledModules).contains(moduleName); boolean removed = intent.getAction().equals(Intent.ACTION_PACKAGE_FULLY_REMOVED) || intent.getAction().equals(Intent.ACTION_UID_REMOVED); - var action = enabled || removed ? "org.lsposed.action.MODULE_UPDATED" : - "org.lsposed.action.MODULE_NOT_ACTIVATAED"; - Intent broadcastIntent = new Intent(action); - broadcastIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); - broadcastIntent.addFlags(0x01000000); - broadcastIntent.addFlags(0x00400000); - broadcastIntent.setData(intent.getData()); - broadcastIntent.putExtras(intent.getExtras()); - broadcastIntent.putExtra(Intent.EXTRA_USER, userId); - broadcastIntent.putExtra("systemModule", systemModule); - var manager = ConfigManager.getInstance().getManagerPackageName(); - var component = ComponentName.createRelative(manager, ".receivers.ServiceReceiver"); - broadcastIntent.setComponent(component); - - try { - ActivityManagerService.broadcastIntentWithFeature(null, broadcastIntent, - null, null, 0, null, null, - null, -1, null, true, false, - 0); - } catch (Throwable t) { - Log.e(TAG, "Broadcast to manager failed: ", t); - } + LSPManagerService.showNotification(moduleName, userId, enabled || removed, systemModule); } if (moduleName != null && ConfigManager.getInstance().isManager(moduleName) && userId == 0) { @@ -178,8 +157,16 @@ public class LSPosedService extends ILSPosedService.Stub { } - synchronized public void dispatchBootCompleted(Intent intent) { - ConfigManager.getInstance().ensureManager(); + synchronized public void dispatchUserUnlocked(Intent intent) { + try { + while (!UserService.isUserUnlocked(0)) { + Log.d(TAG, "user is not yet unlocked, waiting for 1s..."); + Thread.sleep(1000); + } + LSPManagerService.createOrUpdateShortcut(); + } catch (Throwable e) { + Log.e(TAG, "dispatch user unlocked", e); + } } private void registerPackageReceiver() { @@ -213,15 +200,15 @@ public class LSPosedService extends ILSPosedService.Stub { Log.d(TAG, "registered package receiver"); } - private void registerBootReceiver() { + private void registerUnlockReceiver() { try { IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(Intent.ACTION_LOCKED_BOOT_COMPLETED); + intentFilter.addAction(Intent.ACTION_USER_UNLOCKED); ActivityManagerService.registerReceiver("android", null, new IIntentReceiver.Stub() { @Override public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser) { - new Thread(() -> dispatchBootCompleted(intent)).start(); + new Thread(() -> dispatchUserUnlocked(intent)).start(); try { ActivityManagerService.finishReceiver(this, resultCode, data, extras, false, intent.getFlags()); } catch (Throwable e) { @@ -230,16 +217,22 @@ public class LSPosedService extends ILSPosedService.Stub { } }, intentFilter, null, 0, 0); } catch (Throwable e) { - Log.e(TAG, "register boot receiver", e); + Log.e(TAG, "register unlock receiver", e); } - Log.d(TAG, "registered boot receiver"); + Log.d(TAG, "registered unlock receiver"); } @Override - public void dispatchSystemServerContext(IBinder activityThread, IBinder activityToken) throws RemoteException { + public void dispatchSystemServerContext(IBinder activityThread, IBinder activityToken) { Log.d(TAG, "received system context"); ActivityManagerService.onSystemServerContext(IApplicationThread.Stub.asInterface(activityThread), activityToken); - registerBootReceiver(); registerPackageReceiver(); + registerUnlockReceiver(); + } + + @Override + public boolean preStartManager(String pkgName, Intent intent) { + Log.d(TAG, "checking manager intent"); + return ServiceManager.getManagerService().preStartManager(pkgName, intent); } } diff --git a/core/src/main/java/org/lsposed/lspd/service/PackageService.java b/core/src/main/java/org/lsposed/lspd/service/PackageService.java index a3417c7b..de25c542 100644 --- a/core/src/main/java/org/lsposed/lspd/service/PackageService.java +++ b/core/src/main/java/org/lsposed/lspd/service/PackageService.java @@ -29,7 +29,6 @@ import android.content.Intent; import android.content.IntentSender; import android.content.pm.ApplicationInfo; import android.content.pm.ComponentInfo; -import android.content.pm.IPackageInstaller; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; @@ -47,14 +46,8 @@ import android.util.Pair; import androidx.annotation.NonNull; -import org.lsposed.lspd.BuildConfig; import org.lsposed.lspd.models.Application; -import org.lsposed.lspd.util.InstallerVerifier; -import java.io.File; -import java.io.FileInputStream; -import java.io.InputStream; -import java.io.OutputStream; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; @@ -66,7 +59,6 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.CountDownLatch; -import hidden.HiddenApiBridge; import io.github.xposed.xposedservice.utils.ParceledListSlice; public class PackageService { @@ -153,12 +145,6 @@ public class PackageService { return new ParceledListSlice<>(res); } - public static void grantRuntimePermission(String packageName, String permissionName, int userId) throws RemoteException { - IPackageManager pm = getPackageManager(); - if (pm == null) return; - pm.grantRuntimePermission(packageName, permissionName, userId); - } - private static Set fetchProcesses(PackageInfo pkgInfo) { HashSet processNames = new HashSet<>(); if (pkgInfo == null) return processNames; @@ -281,79 +267,27 @@ public class PackageService { return new ParceledListSlice<>(pm.queryIntentActivities(intent, resolvedType, flags, userId).getList()); } - @SuppressWarnings("JavaReflectionMemberAccess") - public static synchronized boolean installManagerIfAbsent(String packageName, File apkFile) { - IPackageManager pm = getPackageManager(); - if (pm == null) return false; + public static Intent getLaunchIntentForPackage(String packageName) throws RemoteException { + Intent intentToResolve = new Intent(Intent.ACTION_MAIN); + intentToResolve.addCategory(Intent.CATEGORY_INFO); + intentToResolve.setPackage(packageName); + ParceledListSlice ris = queryIntentActivities(intentToResolve, intentToResolve.getType(), 0, 0); - try { - // Uninstall manager when needed - PackageInfo pkgInfo = pm.getPackageInfo(packageName, 0, 0); - if (pkgInfo != null && pkgInfo.versionName != null && pkgInfo.applicationInfo != null) { - boolean versionMatch = pkgInfo.versionName.equals(BuildConfig.VERSION_NAME); - boolean signatureMatch = InstallerVerifier.verifyInstallerSignature(pkgInfo.applicationInfo.sourceDir); - if (versionMatch && signatureMatch && pkgInfo.versionCode >= BuildConfig.VERSION_CODE) - return false; - if (!signatureMatch || !versionMatch && pkgInfo.versionCode > BuildConfig.VERSION_CODE) - uninstallPackage(new VersionedPackage(pkgInfo.packageName, pkgInfo.versionCode), -1); - } - - if (!InstallerVerifier.verifyInstallerSignature(apkFile.getPath())) { - Log.w(TAG, apkFile + " verify signature false! skip install."); - return false; - } - - // Install manager - IPackageInstaller installerService = pm.getPackageInstaller(); - PackageInstaller installer = null; - // S Preview - if (Build.VERSION.SDK_INT > 30 || Build.VERSION.SDK_INT == 30 && Build.VERSION.PREVIEW_SDK_INT != 0) { - try { - Constructor installerConstructor = PackageInstaller.class.getConstructor(IPackageInstaller.class, String.class, String.class, int.class); - installerConstructor.setAccessible(true); - installer = installerConstructor.newInstance(installerService, null, null, 0); - } catch (Throwable ignored) { - } - } - if (installer == null) { - Constructor installerConstructor = PackageInstaller.class.getConstructor(IPackageInstaller.class, String.class, int.class); - installerConstructor.setAccessible(true); - installer = installerConstructor.newInstance(installerService, null, 0); - } - PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL); - int installFlags = HiddenApiBridge.PackageInstaller_SessionParams_installFlags(params); - installFlags |= 0x00000002/*PackageManager.INSTALL_REPLACE_EXISTING*/; - HiddenApiBridge.PackageInstaller_SessionParams_installFlags(params, installFlags); - - int sessionId = installer.createSession(params); - try (PackageInstaller.Session session = installer.openSession(sessionId)) { - try (InputStream is = new FileInputStream(apkFile); OutputStream os = session.openWrite(apkFile.getName(), 0, -1)) { - byte[] buf = new byte[8192]; - int len; - while ((len = is.read(buf)) > 0) { - os.write(buf, 0, len); - os.flush(); - session.fsync(os); - } - } - session.commit(new IntentSenderAdaptor() { - @Override - public void send(Intent result) { - int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE); - String message = result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE); - if (status != PackageInstaller.STATUS_SUCCESS) { - Log.w(TAG, "installation failed: " + status + " " + message); - } else { - Log.i(TAG, "installed manager successfully"); - } - } - }.getIntentSender()); - } - return true; - } catch (Throwable e) { - Log.e(TAG, e.getMessage(), e); - return false; + // Otherwise, try to find a main launcher activity. + if (ris == null || ris.getList().size() <= 0) { + // reuse the intent instance + intentToResolve.removeCategory(Intent.CATEGORY_INFO); + intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER); + intentToResolve.setPackage(packageName); + ris = queryIntentActivities(intentToResolve, intentToResolve.getType(), 0, 0); } + if (ris == null || ris.getList().size() <= 0) { + return null; + } + Intent intent = new Intent(intentToResolve); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.setClassName(ris.getList().get(0).activityInfo.packageName, + ris.getList().get(0).activityInfo.name); + return intent; } - } diff --git a/core/src/main/java/org/lsposed/lspd/service/ServiceManager.java b/core/src/main/java/org/lsposed/lspd/service/ServiceManager.java index 0d146e9c..1e4718b7 100644 --- a/core/src/main/java/org/lsposed/lspd/service/ServiceManager.java +++ b/core/src/main/java/org/lsposed/lspd/service/ServiceManager.java @@ -104,6 +104,8 @@ public class ServiceManager { waitSystemService(Context.USER_SERVICE); waitSystemService(Context.APP_OPS_SERVICE); + ConfigFileManager.reloadLocale(); + BridgeService.send(mainService, new BridgeService.Listener() { @Override public void onSystemServerRestarted() { @@ -123,6 +125,7 @@ public class ServiceManager { public void onSystemServerDied() { Log.w(TAG, "system server died"); systemServerService.putBinderForSystemServer(); + managerService.onSystemServerDied(); } }); diff --git a/core/src/main/java/org/lsposed/lspd/service/UserService.java b/core/src/main/java/org/lsposed/lspd/service/UserService.java index 0a521f4e..bd0bfdbb 100644 --- a/core/src/main/java/org/lsposed/lspd/service/UserService.java +++ b/core/src/main/java/org/lsposed/lspd/service/UserService.java @@ -82,4 +82,10 @@ public class UserService { if (userInfo == null) return userId; else return userInfo.id; } + + public static boolean isUserUnlocked(int userId) throws RemoteException { + IUserManager um = getUserManager(); + if (um == null) return false; + return um.isUserUnlocked(userId); + } } diff --git a/core/src/main/java/org/lsposed/lspd/util/FakeContext.java b/core/src/main/java/org/lsposed/lspd/util/FakeContext.java new file mode 100644 index 00000000..637a9fb1 --- /dev/null +++ b/core/src/main/java/org/lsposed/lspd/util/FakeContext.java @@ -0,0 +1,65 @@ +package org.lsposed.lspd.util; + +import static org.lsposed.lspd.service.ServiceManager.TAG; + +import android.content.ContentResolver; +import android.content.ContextWrapper; +import android.content.pm.ApplicationInfo; +import android.content.res.Resources; +import android.util.Log; + +import androidx.annotation.Nullable; + +import org.lsposed.lspd.service.ConfigFileManager; +import org.lsposed.lspd.service.PackageService; + +public class FakeContext extends ContextWrapper { + static ApplicationInfo systemApplicationInfo = null; + static Resources.Theme theme = null; + + public FakeContext() { + super(null); + } + + @Override + public String getPackageName() { + return "android"; + } + + @Override + public Resources getResources() { + return ConfigFileManager.getResources(); + } + + @Override + public ApplicationInfo getApplicationInfo() { + try { + if (systemApplicationInfo == null) + systemApplicationInfo = PackageService.getApplicationInfo("android", 0, 0); + } catch (Throwable e) { + Log.e(TAG, "getApplicationInfo", e); + } + return systemApplicationInfo; + } + + @Override + public ContentResolver getContentResolver() { + return null; + } + + public int getUserId() { + return 0; + } + + @Override + public Resources.Theme getTheme() { + if (theme == null) theme = getResources().newTheme(); + return theme; + } + + @Nullable + @Override + public String getAttributionTag() { + return null; + } +} diff --git a/core/src/main/java/org/lsposed/lspd/util/ParasiticManagerHooker.java b/core/src/main/java/org/lsposed/lspd/util/ParasiticManagerHooker.java new file mode 100644 index 00000000..88ed1818 --- /dev/null +++ b/core/src/main/java/org/lsposed/lspd/util/ParasiticManagerHooker.java @@ -0,0 +1,199 @@ +package org.lsposed.lspd.util; + +import static org.lsposed.lspd.config.ApplicationServiceClient.serviceClient; + +import android.app.ActivityThread; +import android.app.LoadedApk; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.IBinder; +import android.os.Process; +import android.os.RemoteException; +import android.util.AndroidRuntimeException; +import android.webkit.WebViewDelegate; +import android.webkit.WebViewFactory; +import android.webkit.WebViewFactoryProvider; + +import org.lsposed.lspd.BuildConfig; +import org.lsposed.lspd.ILSPManagerService; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +import de.robv.android.xposed.XC_MethodHook; +import de.robv.android.xposed.XC_MethodReplacement; +import de.robv.android.xposed.XposedBridge; +import de.robv.android.xposed.XposedHelpers; +import hidden.HiddenApiBridge; + +public class ParasiticManagerHooker { + private static final String CHROMIUM_WEBVIEW_FACTORY_METHOD = "create"; + + private static PackageInfo managerPkgInfo = null; + private static int managerFd = -1; + + private synchronized static PackageInfo getManagerPkgInfo(ApplicationInfo appInfo) { + if (managerPkgInfo == null) { + Context ctx = ActivityThread.currentActivityThread().getSystemContext(); + var sourceDir = "/proc/self/fd/" + managerFd; + managerPkgInfo = ctx.getPackageManager().getPackageArchiveInfo(sourceDir, PackageManager.GET_ACTIVITIES); + var newAppInfo = managerPkgInfo.applicationInfo; + newAppInfo.sourceDir = sourceDir; + newAppInfo.publicSourceDir = sourceDir; + newAppInfo.nativeLibraryDir = appInfo.nativeLibraryDir; + newAppInfo.packageName = appInfo.packageName; + newAppInfo.dataDir = HiddenApiBridge.ApplicationInfo_credentialProtectedDataDir(appInfo); + newAppInfo.deviceProtectedDataDir = appInfo.deviceProtectedDataDir; + HiddenApiBridge.ApplicationInfo_credentialProtectedDataDir(newAppInfo, HiddenApiBridge.ApplicationInfo_credentialProtectedDataDir(appInfo)); + newAppInfo.uid = appInfo.uid; + } + return managerPkgInfo; + } + + private static void hookForManager(ILSPManagerService managerService) { + var activityHooker = new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) { + for (var i = 0; i < param.args.length; ++i) { + if (param.args[i] instanceof ActivityInfo) { + var pkgInfo = getManagerPkgInfo(((ActivityInfo) param.args[i]).applicationInfo); + for (var activity : pkgInfo.activities) { + if ("org.lsposed.manager.ui.activity.MainActivity".equals(activity.name)) { + activity.applicationInfo = pkgInfo.applicationInfo; + param.args[i] = activity; + } + } + } + if (param.args[i] instanceof Intent) { + var intent = (Intent) param.args[i]; + checkIntent(managerService, intent); + intent.setComponent(new ComponentName(BuildConfig.MANAGER_INJECTED_PKG_NAME, "org.lsposed.manager.ui.activity.MainActivity")); + } + } + } + }; + var managerApkHooker = new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) { + Hookers.logD("ActivityThread#handleBindApplication() starts"); + Object bindData = param.args[0]; + ApplicationInfo appInfo = (ApplicationInfo) XposedHelpers.getObjectField(bindData, "appInfo"); + XposedHelpers.setObjectField(bindData, "appInfo", getManagerPkgInfo(appInfo).applicationInfo); + XposedHelpers.setObjectField(bindData, "providers", new ArrayList<>()); + } + }; + XposedHelpers.findAndHookMethod(ActivityThread.class, + "handleBindApplication", + "android.app.ActivityThread$AppBindData", + managerApkHooker); + + XposedBridge.hookAllConstructors(ActivityThread.ActivityClientRecord.class, activityHooker); + + var unhooks = new XC_MethodHook.Unhook[]{null}; + unhooks[0] = XposedHelpers.findAndHookMethod( + LoadedApk.class, "getClassLoader", new XC_MethodHook() { + @Override + protected void afterHookedMethod(MethodHookParam param) { + if (XposedHelpers.getObjectField(param.thisObject, "mApplicationInfo") == getManagerPkgInfo(null).applicationInfo) { + InstallerVerifier.sendBinderToManager((ClassLoader) param.getResult(), managerService.asBinder()); + unhooks[0].unhook(); + } + } + }); + + XposedBridge.hookAllMethods(ActivityThread.class, "handleReceiver", new XC_MethodReplacement() { + @Override + protected Object replaceHookedMethod(MethodHookParam param) { + for (var arg : param.args) { + if (arg instanceof BroadcastReceiver.PendingResult) { + ((BroadcastReceiver.PendingResult) arg).finish(); + } + } + return null; + } + }); + + XposedHelpers.findAndHookMethod(ActivityThread.class, "deliverNewIntents", ActivityThread.ActivityClientRecord.class, List.class, new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) { + if (param.args[1] == null) return; + for (var intent : (List) param.args[1]) { + checkIntent(managerService, intent); + } + } + }); + + if (Process.myUid() == 1000) { + XposedHelpers.findAndHookMethod(WebViewFactory.class, "getProvider", new XC_MethodReplacement() { + @Override + protected Object replaceHookedMethod(MethodHookParam param) { + var sProviderInstance = XposedHelpers.getStaticObjectField(WebViewFactory.class, "sProviderInstance"); + if (sProviderInstance != null) return sProviderInstance; + //noinspection unchecked + var providerClass = (Class) XposedHelpers.callStaticMethod(WebViewFactory.class, "getProviderClass"); + Method staticFactory = null; + try { + staticFactory = providerClass.getMethod( + CHROMIUM_WEBVIEW_FACTORY_METHOD, WebViewDelegate.class); + } catch (Exception e) { + Hookers.logE("error instantiating provider with static factory method", e); + } + + try { + var webViewDelegateConstructor = WebViewDelegate.class.getDeclaredConstructor(); + webViewDelegateConstructor.setAccessible(true); + sProviderInstance = staticFactory.invoke(null, webViewDelegateConstructor.newInstance()); + XposedHelpers.setStaticObjectField(WebViewFactory.class, "sProviderInstance", sProviderInstance); + Hookers.logD("Loaded provider: " + sProviderInstance); + return sProviderInstance; + } catch (Exception e) { + Hookers.logE("error instantiating provider", e); + throw new AndroidRuntimeException(e); + } + } + }); + } + } + + private static void checkIntent(ILSPManagerService managerService, Intent intent) { + if (managerService == null) return; + if (intent.getCategories() == null || !intent.getCategories().contains("org.lsposed.manager.LAUNCH_MANAGER")) { + Hookers.logD("Launching the original app, restarting"); + try { + managerService.restartFor(intent); + } catch (RemoteException e) { + Hookers.logE("restart failed", e); + } finally { + Process.killProcess(Process.myPid()); + } + } + } + + + static public boolean start() { + try { + List binder = new ArrayList<>(1); + var managerParcelFd = serviceClient.requestInjectedManagerBinder(binder); + if (binder.size() > 0 && binder.get(0) != null && managerParcelFd != null) { + managerFd = managerParcelFd.detachFd(); + var managerService = ILSPManagerService.Stub.asInterface(binder.get(0)); + hookForManager(managerService); + Utils.logD("injected manager"); + return true; + } else { + // Not manager + return false; + } + } catch (Throwable e) { + Utils.logE("failed to inject manager", e); + return false; + } + } +} diff --git a/hiddenapi-bridge/src/main/java/hidden/HiddenApiBridge.java b/hiddenapi-bridge/src/main/java/hidden/HiddenApiBridge.java index 2f5d73e3..58577658 100644 --- a/hiddenapi-bridge/src/main/java/hidden/HiddenApiBridge.java +++ b/hiddenapi-bridge/src/main/java/hidden/HiddenApiBridge.java @@ -19,14 +19,20 @@ package hidden; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageInstaller; import android.content.res.AssetManager; import android.content.res.Resources; import android.content.res.ResourcesImpl; import android.os.Binder; import android.os.Environment; +import android.os.Handler; import android.os.IBinder; +import android.os.UserHandle; import java.io.File; @@ -58,4 +64,26 @@ public class HiddenApiBridge { public static File Environment_getDataProfilesDePackageDirectory(int userId, String packageName) { return Environment.getDataProfilesDePackageDirectory(userId, packageName); } + + public static Intent Context_registerReceiverAsUser(Context ctx, BroadcastReceiver receiver, UserHandle user, + IntentFilter filter, String broadcastPermission, Handler scheduler) { + + return ctx.registerReceiverAsUser(receiver, user, filter, broadcastPermission, scheduler); + } + + public static UserHandle UserHandle_ALL(){ + return UserHandle.ALL; + } + + public static UserHandle UserHandle(int h){ + return new UserHandle(h); + } + + public static String ApplicationInfo_credentialProtectedDataDir(ApplicationInfo applicationInfo) { + return applicationInfo.credentialProtectedDataDir; + } + + public static void ApplicationInfo_credentialProtectedDataDir(ApplicationInfo applicationInfo, String dir) { + applicationInfo.credentialProtectedDataDir = dir; + } } diff --git a/hiddenapi-stubs/src/main/java/android/app/ActivityThread.java b/hiddenapi-stubs/src/main/java/android/app/ActivityThread.java index e9da3a1f..829d1e96 100644 --- a/hiddenapi-stubs/src/main/java/android/app/ActivityThread.java +++ b/hiddenapi-stubs/src/main/java/android/app/ActivityThread.java @@ -44,4 +44,7 @@ public final class ActivityThread { return null; } } + public static final class ActivityClientRecord { + + } } diff --git a/hiddenapi-stubs/src/main/java/android/app/IActivityController.java b/hiddenapi-stubs/src/main/java/android/app/IActivityController.java new file mode 100644 index 00000000..2a359f5f --- /dev/null +++ b/hiddenapi-stubs/src/main/java/android/app/IActivityController.java @@ -0,0 +1,60 @@ +package android.app; + +import android.content.Intent; +import android.os.Binder; +import android.os.IBinder; +import android.os.IInterface; + +public interface IActivityController extends IInterface { + /** + * The system is trying to start an activity. Return true to allow + * it to be started as normal, or false to cancel/reject this activity. + */ + boolean activityStarting(Intent intent, String pkg); + + /** + * The system is trying to return to an activity. Return true to allow + * it to be resumed as normal, or false to cancel/reject this activity. + */ + boolean activityResuming(String pkg); + + /** + * An application process has crashed (in Java). Return true for the + * normal error recovery (app crash dialog) to occur, false to kill + * it immediately. + */ + boolean appCrashed(String processName, int pid, + String shortMsg, String longMsg, + long timeMillis, String stackTrace); + + /** + * Early call as soon as an ANR is detected. + */ + int appEarlyNotResponding(String processName, int pid, String annotation); + + /** + * An application process is not responding. Return 0 to show the "app + * not responding" dialog, 1 to continue waiting, or -1 to kill it + * immediately. + */ + int appNotResponding(String processName, int pid, String processStats); + + /** + * The system process watchdog has detected that the system seems to be + * hung. Return 1 to continue waiting, or -1 to let it continue with its + * normal kill. + */ + int systemNotResponding(String msg); + + abstract class Stub extends Binder implements IActivityController { + + public static IActivityController asInterface(IBinder obj) { + throw new UnsupportedOperationException(); + } + + @Override + public IBinder asBinder() { + throw new UnsupportedOperationException(); + } + } +} diff --git a/hiddenapi-stubs/src/main/java/android/app/IActivityManager.java b/hiddenapi-stubs/src/main/java/android/app/IActivityManager.java index fb9387d1..ea757376 100644 --- a/hiddenapi-stubs/src/main/java/android/app/IActivityManager.java +++ b/hiddenapi-stubs/src/main/java/android/app/IActivityManager.java @@ -95,7 +95,10 @@ public interface IActivityManager extends IInterface { UserInfo getCurrentUser() throws RemoteException; + void setActivityController(IActivityController watcher, boolean imAMonkey) throws RemoteException; + abstract class Stub extends Binder implements IActivityManager { + public static int TRANSACTION_setActivityController; public static IActivityManager asInterface(IBinder obj) { throw new UnsupportedOperationException(); diff --git a/hiddenapi-stubs/src/main/java/android/app/INotificationManager.java b/hiddenapi-stubs/src/main/java/android/app/INotificationManager.java new file mode 100644 index 00000000..afd47239 --- /dev/null +++ b/hiddenapi-stubs/src/main/java/android/app/INotificationManager.java @@ -0,0 +1,19 @@ +package android.app; + +import android.content.pm.ParceledListSlice; +import android.os.Binder; +import android.os.IBinder; +import android.os.IInterface; +import android.os.RemoteException; + +public interface INotificationManager extends IInterface { + void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id, + Notification notification, int userId) throws RemoteException; + void createNotificationChannels(String pkg, ParceledListSlice channelsList) throws RemoteException; + + abstract class Stub extends Binder implements INotificationManager { + public static INotificationManager asInterface(IBinder obj) { + throw new UnsupportedOperationException(); + } + } +} diff --git a/hiddenapi-stubs/src/main/java/android/app/Notification.java b/hiddenapi-stubs/src/main/java/android/app/Notification.java new file mode 100644 index 00000000..14778691 --- /dev/null +++ b/hiddenapi-stubs/src/main/java/android/app/Notification.java @@ -0,0 +1,4 @@ +package android.app; + +public class Notification { +} diff --git a/hiddenapi-stubs/src/main/java/android/app/NotificationChannel.java b/hiddenapi-stubs/src/main/java/android/app/NotificationChannel.java new file mode 100644 index 00000000..dbd15704 --- /dev/null +++ b/hiddenapi-stubs/src/main/java/android/app/NotificationChannel.java @@ -0,0 +1,4 @@ +package android.app; + +public class NotificationChannel { +} diff --git a/hiddenapi-stubs/src/main/java/android/content/BroadcastReceiver.java b/hiddenapi-stubs/src/main/java/android/content/BroadcastReceiver.java new file mode 100644 index 00000000..d70f080a --- /dev/null +++ b/hiddenapi-stubs/src/main/java/android/content/BroadcastReceiver.java @@ -0,0 +1,5 @@ +package android.content; + +public abstract class BroadcastReceiver { + public abstract void onReceive(Context context, Intent intent); +} diff --git a/hiddenapi-stubs/src/main/java/android/content/Context.java b/hiddenapi-stubs/src/main/java/android/content/Context.java index 1084a435..fc22c449 100644 --- a/hiddenapi-stubs/src/main/java/android/content/Context.java +++ b/hiddenapi-stubs/src/main/java/android/content/Context.java @@ -1,9 +1,15 @@ package android.content; +import android.os.Handler; import android.os.IBinder; +import android.os.UserHandle; public class Context { public IBinder getActivityToken() { throw new UnsupportedOperationException("STUB"); } + public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user, + IntentFilter filter, String broadcastPermission, Handler scheduler) { + throw new UnsupportedOperationException("STUB"); + } } diff --git a/hiddenapi-stubs/src/main/java/android/content/pm/ApplicationInfo.java b/hiddenapi-stubs/src/main/java/android/content/pm/ApplicationInfo.java index e0d81c50..3f48f397 100644 --- a/hiddenapi-stubs/src/main/java/android/content/pm/ApplicationInfo.java +++ b/hiddenapi-stubs/src/main/java/android/content/pm/ApplicationInfo.java @@ -1,4 +1,5 @@ package android.content.pm; public class ApplicationInfo { + public String credentialProtectedDataDir; } diff --git a/hiddenapi-stubs/src/main/java/android/content/pm/IShortcutService.java b/hiddenapi-stubs/src/main/java/android/content/pm/IShortcutService.java new file mode 100644 index 00000000..75d351fd --- /dev/null +++ b/hiddenapi-stubs/src/main/java/android/content/pm/IShortcutService.java @@ -0,0 +1,24 @@ +package android.content.pm; + +import android.content.IntentSender; +import android.os.Binder; +import android.os.IBinder; +import android.os.IInterface; +import android.os.RemoteException; + +public interface IShortcutService extends IInterface { + boolean isRequestPinItemSupported(int user, int requestType) throws RemoteException; + ParceledListSlice getShortcuts(String packageName, int matchFlags, int userId) throws RemoteException; + ParceledListSlice getPinnedShortcuts(String packageName, int userId) throws RemoteException; + boolean requestPinShortcut(String packageName, ShortcutInfo shortcut, + IntentSender resultIntent, int userId) throws RemoteException; + + boolean updateShortcuts(String packageName, ParceledListSlice shortcuts, int userId) throws RemoteException; + + abstract class Stub extends Binder implements IShortcutService { + + public static IShortcutService asInterface(IBinder obj) { + throw new RuntimeException("STUB"); + } + } +} diff --git a/hiddenapi-stubs/src/main/java/android/content/pm/ParceledListSlice.java b/hiddenapi-stubs/src/main/java/android/content/pm/ParceledListSlice.java index 927754cc..6b98d7bd 100644 --- a/hiddenapi-stubs/src/main/java/android/content/pm/ParceledListSlice.java +++ b/hiddenapi-stubs/src/main/java/android/content/pm/ParceledListSlice.java @@ -1,4 +1,10 @@ package android.content.pm; +import java.util.List; + public class ParceledListSlice extends BaseParceledListSlice { + public ParceledListSlice(List list) { + throw new IllegalArgumentException("STUB"); + } + } diff --git a/hiddenapi-stubs/src/main/java/android/content/pm/ShortcutInfo.java b/hiddenapi-stubs/src/main/java/android/content/pm/ShortcutInfo.java new file mode 100644 index 00000000..24b8b062 --- /dev/null +++ b/hiddenapi-stubs/src/main/java/android/content/pm/ShortcutInfo.java @@ -0,0 +1,28 @@ +package android.content.pm; + +import android.os.Parcel; +import android.os.Parcelable; + +public class ShortcutInfo implements Parcelable { + public static final Creator CREATOR = new Creator<>() { + @Override + public ShortcutInfo createFromParcel(Parcel in) { + throw new IllegalArgumentException("STUB"); + } + + @Override + public ShortcutInfo[] newArray(int size) { + throw new IllegalArgumentException("STUB"); + } + }; + + @Override + public void writeToParcel(Parcel dest, int flags) { + throw new IllegalArgumentException("STUB"); + } + + @Override + public int describeContents() { + throw new IllegalArgumentException("STUB"); + } +} diff --git a/hiddenapi-stubs/src/main/java/android/os/Handler.java b/hiddenapi-stubs/src/main/java/android/os/Handler.java new file mode 100644 index 00000000..dd632722 --- /dev/null +++ b/hiddenapi-stubs/src/main/java/android/os/Handler.java @@ -0,0 +1,4 @@ +package android.os; + +public class Handler { +} diff --git a/hiddenapi-stubs/src/main/java/android/os/IUserManager.java b/hiddenapi-stubs/src/main/java/android/os/IUserManager.java index 7641c7cc..7026b0b9 100644 --- a/hiddenapi-stubs/src/main/java/android/os/IUserManager.java +++ b/hiddenapi-stubs/src/main/java/android/os/IUserManager.java @@ -19,6 +19,8 @@ public interface IUserManager extends IInterface { UserInfo getProfileParent(int userId) throws RemoteException; + boolean isUserUnlockingOrUnlocked(int userId) throws RemoteException; + abstract class Stub extends Binder implements IUserManager { public static IUserManager asInterface(IBinder obj) { diff --git a/hiddenapi-stubs/src/main/java/android/os/Parcelable.java b/hiddenapi-stubs/src/main/java/android/os/Parcelable.java new file mode 100644 index 00000000..5c716ee5 --- /dev/null +++ b/hiddenapi-stubs/src/main/java/android/os/Parcelable.java @@ -0,0 +1,10 @@ +package android.os; + +public interface Parcelable { + interface Creator{ + public T createFromParcel(Parcel source); + public T[] newArray(int size); + } + void writeToParcel(Parcel dest, int flags); + int describeContents(); +} diff --git a/hiddenapi-stubs/src/main/java/android/os/ResultReceiver.java b/hiddenapi-stubs/src/main/java/android/os/ResultReceiver.java new file mode 100644 index 00000000..358ae780 --- /dev/null +++ b/hiddenapi-stubs/src/main/java/android/os/ResultReceiver.java @@ -0,0 +1,4 @@ +package android.os; + +public class ResultReceiver { +} diff --git a/hiddenapi-stubs/src/main/java/android/os/ShellCallback.java b/hiddenapi-stubs/src/main/java/android/os/ShellCallback.java new file mode 100644 index 00000000..b8a5771f --- /dev/null +++ b/hiddenapi-stubs/src/main/java/android/os/ShellCallback.java @@ -0,0 +1,25 @@ +package android.os; + +public class ShellCallback implements Parcelable { + public static final Parcelable.Creator CREATOR = new Creator<>() { + @Override + public ShellCallback createFromParcel(Parcel source) { + throw new IllegalArgumentException("STUB"); + } + + @Override + public ShellCallback[] newArray(int size) { + throw new IllegalArgumentException("STUB"); + } + }; + + @Override + public void writeToParcel(Parcel dest, int flags) { + throw new IllegalArgumentException("STUB"); + } + + @Override + public int describeContents() { + throw new IllegalArgumentException("STUB"); + } +} diff --git a/hiddenapi-stubs/src/main/java/android/os/ShellCommand.java b/hiddenapi-stubs/src/main/java/android/os/ShellCommand.java new file mode 100644 index 00000000..56dac90c --- /dev/null +++ b/hiddenapi-stubs/src/main/java/android/os/ShellCommand.java @@ -0,0 +1,33 @@ +package android.os; + +import java.io.FileDescriptor; +import java.io.InputStream; +import java.io.PrintWriter; + +public abstract class ShellCommand { + public int exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err, + String[] args, ShellCallback callback, ResultReceiver resultReceiver) { + throw new IllegalArgumentException("STUB!"); + } + + public abstract int onCommand(String cmd); + public abstract void onHelp(); + + public String getNextOption(){ + throw new IllegalArgumentException("STUB!"); + } + + public String getNextArgRequired() { + throw new IllegalArgumentException("STUB!"); + } + + public PrintWriter getErrPrintWriter() { + throw new IllegalArgumentException("STUB!"); + } + public PrintWriter getOutPrintWriter() { + throw new IllegalArgumentException("STUB!"); + } + public InputStream getRawInputStream() { + throw new IllegalArgumentException("STUB!"); + } +} diff --git a/hiddenapi-stubs/src/main/java/android/os/UserHandle.java b/hiddenapi-stubs/src/main/java/android/os/UserHandle.java index a85185d1..310fe246 100644 --- a/hiddenapi-stubs/src/main/java/android/os/UserHandle.java +++ b/hiddenapi-stubs/src/main/java/android/os/UserHandle.java @@ -1,5 +1,7 @@ package android.os; +import android.annotation.NonNull; + public class UserHandle { public UserHandle(int h) { @@ -9,4 +11,7 @@ public class UserHandle { public int getIdentifier() { throw new RuntimeException("STUB"); } + + public static final @NonNull + UserHandle ALL = null; } diff --git a/hiddenapi-stubs/src/main/java/android/webkit/WebViewDelegate.java b/hiddenapi-stubs/src/main/java/android/webkit/WebViewDelegate.java new file mode 100644 index 00000000..3ad7cd29 --- /dev/null +++ b/hiddenapi-stubs/src/main/java/android/webkit/WebViewDelegate.java @@ -0,0 +1,4 @@ +package android.webkit; + +public class WebViewDelegate { +} diff --git a/hiddenapi-stubs/src/main/java/android/webkit/WebViewFactory.java b/hiddenapi-stubs/src/main/java/android/webkit/WebViewFactory.java new file mode 100644 index 00000000..e266da66 --- /dev/null +++ b/hiddenapi-stubs/src/main/java/android/webkit/WebViewFactory.java @@ -0,0 +1,4 @@ +package android.webkit; + +public class WebViewFactory { +} diff --git a/hiddenapi-stubs/src/main/java/android/webkit/WebViewFactoryProvider.java b/hiddenapi-stubs/src/main/java/android/webkit/WebViewFactoryProvider.java new file mode 100644 index 00000000..43b5fd5e --- /dev/null +++ b/hiddenapi-stubs/src/main/java/android/webkit/WebViewFactoryProvider.java @@ -0,0 +1,4 @@ +package android.webkit; + +public class WebViewFactoryProvider { +} diff --git a/manager-service/src/main/aidl/org/lsposed/lspd/ILSPManagerService.aidl b/manager-service/src/main/aidl/org/lsposed/lspd/ILSPManagerService.aidl index 4624d85c..3328452c 100644 --- a/manager-service/src/main/aidl/org/lsposed/lspd/ILSPManagerService.aidl +++ b/manager-service/src/main/aidl/org/lsposed/lspd/ILSPManagerService.aidl @@ -63,4 +63,6 @@ interface ILSPManagerService { void setHiddenIcon(boolean hide) = 33; Map getLogs() = 34; + + void restartFor(in Intent intent) = 35; }