Add EdXposed Service to update module path

This commit is contained in:
LoveSy 2020-12-05 06:05:46 +08:00 committed by solohsu
parent fcd4b15496
commit 3389af6edc
16 changed files with 502 additions and 23 deletions

View File

@ -130,6 +130,7 @@ afterEvaluate {
dependsOn cleanTemplate
dependsOn tasks.getByPath(":dexmaker:copyDex${variantCapped}")
dependsOn tasks.getByPath(":dalvikdx:copyDex${variantCapped}")
dependsOn tasks.getByPath(":edxp-service:copyDex${variantCapped}")
dependsOn tasks.getByPath(":edxp-${backendLowered}:copyDex${variantCapped}")
}

View File

@ -229,6 +229,7 @@ namespace edxp {
scope.emplace(std::move(app_pkg_name));
}
scope.insert(module_pkg_name); // Always add module itself
if (module_pkg_name == installer_pkg_name_) scope.erase("android");
LOGI("scope of %s is:\n%s", module_pkg_name.c_str(), ([&scope = scope]() {
std::ostringstream join;
std::copy(scope.begin(), scope.end(),
@ -296,7 +297,8 @@ namespace edxp {
fs::perms::others_exec);
path_chown(prefs_path, uid, 0);
}
if (pkg_name == installer_pkg_name_ || pkg_name == kPrimaryInstallerPkgName) {
if (pkg_name == installer_pkg_name_ || pkg_name == kPrimaryInstallerPkgName ||
pkg_name == "android") {
auto conf_path = GetConfigPath();
if (!path_exists<true>(conf_path)) {
fs::create_directories(conf_path);
@ -307,11 +309,14 @@ namespace edxp {
}
fs::permissions(conf_path, fs::perms::owner_all | fs::perms::group_all);
fs::permissions(log_path, fs::perms::owner_all | fs::perms::group_all);
if (const auto &[r_uid, r_gid] = path_own(conf_path); r_uid != uid) {
path_chown(conf_path, uid, 0, true);
if (pkg_name == "android") uid = -1;
if (const auto &[r_uid, r_gid] = path_own(conf_path);
(uid != -1 && r_uid != uid) || r_gid != 1000u) {
path_chown(conf_path, uid, 1000u, true);
}
if (const auto &[r_uid, r_gid] = path_own(log_path); r_uid != uid) {
path_chown(log_path, uid, 0, true);
if (const auto &[r_uid, r_gid] = path_own(log_path);
(uid != -1 && r_uid != uid) || r_gid != 1000u) {
path_chown(log_path, uid, 1000u, true);
}
if (pkg_name == kPrimaryInstallerPkgName) {
@ -330,7 +335,7 @@ namespace edxp {
}
}
void ConfigManager::Init(){
void ConfigManager::Init() {
fs::path misc_path("/data/adb/edxp/misc_path");
try {
RirudSocket rirud_socket{};

View File

@ -63,6 +63,8 @@ namespace edxp {
inline const auto &GetDataPathPrefix() const { return data_path_prefix_; }
inline static const auto &GetMiscPath() {return misc_path_;}
inline static auto GetFrameworkPath(const std::string &suffix = {}) {
return misc_path_ / "framework" / suffix;
}

View File

@ -64,6 +64,7 @@ namespace edxp {
std::ifstream is(path, std::ios::binary);
if (!is.good()) {
LOGE("Cannot load path %s", path.c_str());
continue;
}
dexes.emplace_back(std::istreambuf_iterator<char>(is),
std::istreambuf_iterator<char>());
@ -88,19 +89,14 @@ namespace edxp {
jmethodID initMid = JNI_GetMethodID(env, in_memory_classloader, "<init>",
"([Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V");
jclass byte_buffer_class = JNI_FindClass(env, "java/nio/ByteBuffer");
jmethodID byte_buffer_wrap = JNI_GetStaticMethodID(env, byte_buffer_class, "wrap",
"([B)Ljava/nio/ByteBuffer;");
auto buffer_array = env->NewObjectArray(dexes.size(), byte_buffer_class, nullptr);
for (size_t i = 0; i != dexes.size(); ++i) {
const auto dex = dexes.at(i);
auto byte_array = env->NewByteArray(dex.size());
env->SetByteArrayRegion(byte_array, 0, dex.size(),
dex.data());
auto buffer = JNI_CallStaticObjectMethod(env, byte_buffer_class, byte_buffer_wrap,
byte_array);
auto &dex = dexes.at(i);
auto buffer = env->NewDirectByteBuffer(reinterpret_cast<void *>(dex.data()),
dex.size());
env->SetObjectArrayElement(buffer_array, i, buffer);
}
jobject my_cl = env->NewObject(in_memory_classloader, initMid,
jobject my_cl = JNI_NewObject(env, in_memory_classloader, initMid,
buffer_array, sys_classloader);
env->DeleteLocalRef(classloader);
env->DeleteLocalRef(sys_classloader);
@ -240,8 +236,67 @@ namespace edxp {
if (!skip_) {
PreLoadDex(ConfigManager::GetInjectDexPaths());
}
ConfigManager::GetInstance()->EnsurePermission("android", 1000);
}
void Context::RegisterEdxpService(JNIEnv *env) {
auto path = ConfigManager::GetFrameworkPath("edservice.dex");
std::ifstream is(path, std::ios::binary);
if (!is.good()) {
LOGE("Cannot load path %s", path.c_str());
return;
}
std::vector<unsigned char> dex{std::istreambuf_iterator<char>(is),
std::istreambuf_iterator<char>()};
LOGD("Loaded %s with size %zu", path.c_str(), dex.size());
jclass classloader = JNI_FindClass(env, "java/lang/ClassLoader");
jmethodID getsyscl_mid = JNI_GetStaticMethodID(
env, classloader, "getSystemClassLoader", "()Ljava/lang/ClassLoader;");
jobject sys_classloader = JNI_CallStaticObjectMethod(env, classloader, getsyscl_mid);
if (UNLIKELY(!sys_classloader)) {
LOGE("getSystemClassLoader failed!!!");
return;
}
// load dex
jobject bufferDex = env->NewDirectByteBuffer(reinterpret_cast<void *>(dex.data()),
dex.size());
jclass in_memory_classloader = JNI_FindClass(env, "dalvik/system/InMemoryDexClassLoader");
jmethodID initMid = JNI_GetMethodID(env, in_memory_classloader, "<init>",
"(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V");
jobject my_cl = JNI_NewObject(env, in_memory_classloader,
initMid,
bufferDex,
sys_classloader);
env->DeleteLocalRef(classloader);
env->DeleteLocalRef(sys_classloader);
env->DeleteLocalRef(in_memory_classloader);
if (UNLIKELY(my_cl == nullptr)) {
LOGE("InMemoryDexClassLoader creation failed!!!");
return;
}
auto service_class = (jclass) env->NewGlobalRef(
FindClassFromLoader(env, my_cl, "com.elderdrivers.riru.edxp.service.ServiceProxy"));
if (LIKELY(service_class)) {
jfieldID path_fid = JNI_GetStaticFieldID(env, service_class, "CONFIG_PATH",
"Ljava/lang/String;");
if (LIKELY(path_fid)) {
env->SetStaticObjectField(service_class, path_fid, env->NewStringUTF(
ConfigManager::GetMiscPath().c_str()));
jmethodID install_mid = JNI_GetStaticMethodID(env, service_class,
"install", "()V");
if (LIKELY(install_mid)) {
JNI_CallStaticVoidMethod(env, service_class, install_mid);
LOGW("Installed EdXposed Service");
}
}
}
}
int
Context::OnNativeForkSystemServerPost(JNIEnv *env, [[maybe_unused]] jclass clazz, jint res) {
@ -262,11 +317,8 @@ namespace edxp {
PrepareJavaEnv(env);
// only do work in child since FindAndCall would print log
FindAndCall(env, "forkSystemServerPost", "(I)V", res);
} else {
[[maybe_unused]] auto config_managers = ConfigManager::ReleaseInstances();
auto context = Context::ReleaseInstance();
LOGD("skipped android");
}
RegisterEdxpService(env);
} else {
// in zygote process, res is child zygote pid
// don't print log here, see https://github.com/RikkaApps/Riru/blob/77adfd6a4a6a81bfd20569c910bc4854f2f84f5e/riru-core/jni/main/jni_native_method.cpp#L55-L66

View File

@ -120,6 +120,8 @@ namespace edxp {
static std::tuple<bool, uid_t, std::string> GetAppInfoFromDir(JNIEnv *env, jstring dir, jstring nice_name);
friend std::unique_ptr<Context> std::make_unique<Context>();
static void RegisterEdxpService(JNIEnv *env);
};
}

View File

@ -229,6 +229,7 @@ extract "${ZIPFILE}" 'uninstall.sh' "${MODPATH}"
extract "${ZIPFILE}" 'system/framework/edconfig.jar' "${MODPATH}"
extract "${ZIPFILE}" 'system/framework/eddalvikdx.dex' "${MODPATH}"
extract "${ZIPFILE}" 'system/framework/eddexmaker.dex' "${MODPATH}"
extract "${ZIPFILE}" 'system/framework/edservice.dex' "${MODPATH}"
extract "${ZIPFILE}" 'system/framework/edxp.dex' "${MODPATH}"
if [ "$ARCH" = "x86" ] || [ "$ARCH" = "x64" ]; then

View File

@ -53,10 +53,10 @@ sepolicy() {
#fi
DEFAULT_BASE_PATH="${PATH_PREFIX}${EDXP_MANAGER}"
BASE_PATH="/data/misc/$(cat /data/adb/edxp/misc_path)/0"
BASE_PATH="/data/misc/$(cat /data/adb/edxp/misc_path)"
LOG_PATH="${BASE_PATH}/log"
CONF_PATH="${BASE_PATH}/conf"
LOG_PATH="${BASE_PATH}/0/log"
CONF_PATH="${BASE_PATH}/0/conf"
DISABLE_VERBOSE_LOG_FILE="${CONF_PATH}/disable_verbose_log"
LOG_VERBOSE=true
OLD_PATH=${PATH}
@ -164,5 +164,6 @@ fi
chcon -R u:object_r:system_file:s0 "${MODDIR}"
chcon -R ${PATH_CONTEXT} "${LOG_PATH}"
chcon -R u:object_r:magisk_file:s0 $BASE_PATH
chown -R ${PATH_OWNER} "${LOG_PATH}"
chmod -R 666 "${LOG_PATH}"

2
edxp-service/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/build
/template_override/system/framework

73
edxp-service/build.gradle Normal file
View File

@ -0,0 +1,73 @@
apply plugin: 'com.android.application'
sourceCompatibility = "7"
targetCompatibility = "7"
android {
compileSdkVersion androidCompileSdkVersion.toInteger()
defaultConfig {
applicationId "com.elderdrivers.riru.edxp.yahfa"
minSdkVersion 26
targetSdkVersion 28
versionCode 1
versionName "1.0"
multiDexEnabled false
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
ndkVersion androidCompileNdkVersion
}
dependencies {
compileOnly project(':hiddenapi-stubs')
implementation project(':edxp-common')
}
preBuild.doLast {
def imlFile = file(project.name + ".iml")
try {
def parsedXml = (new groovy.util.XmlParser()).parse(imlFile)
def jdkNode = parsedXml.component[1].orderEntry.find { it.'@type' == 'jdk' }
parsedXml.component[1].remove(jdkNode)
def sdkString = "Android API " + android.compileSdkVersion.substring("android-".length()) + " Platform"
new groovy.util.Node(parsedXml.component[1], 'orderEntry', ['type': 'jdk', 'jdkName': sdkString, 'jdkType': 'Android SDK'])
groovy.xml.XmlUtil.serialize(parsedXml, new FileOutputStream(imlFile))
} catch (FileNotFoundException e) {
// nop, iml not found
}
}
afterEvaluate {
tasks.withType(JavaCompile) {
options.compilerArgs << "-Xbootclasspath/p:${hiddenApiStubJarFilePath}"
}
android.applicationVariants.all { variant ->
def variantNameCapped = variant.name.capitalize()
def variantNameLowered = variant.name.toLowerCase()
task("copyDex${variantNameCapped}", type: Copy) {
dependsOn "assemble${variantNameCapped}"
dependsOn tasks.getByPath(":edxp-common:copyCommonProperties")
def dexOutPath = variant.name.contains("release") ?
"${buildDir}/intermediates/dex/${variantNameLowered}/minify${variantNameCapped}WithR8" :
"${buildDir}/intermediates/dex/${variantNameLowered}/mergeDex${variantNameCapped}"
from (dexOutPath){
rename("classes.dex", "edservice.dex")
}
destinationDir file(templateRootPath + "system/framework/")
outputs.upToDateWhen { false }
}
}
}

34
edxp-service/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,34 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
-dontoptimize
-dontobfuscate
-keep interface com.elderdrivers.riru.common.KeepAll
-keep interface com.elderdrivers.riru.common.KeepMembers
-keep class * implements com.elderdrivers.riru.common.KeepAll { *; }
-keepclassmembers class * implements com.elderdrivers.riru.common.KeepMembers { *; }
-keepclasseswithmembers class * {
native <methods>;
}

View File

@ -0,0 +1 @@
<manifest package="com.elderdrivers.riru.edxp.service" />

View File

@ -0,0 +1,211 @@
package com.elderdrivers.riru.edxp.service;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.annotation.SuppressLint;
import android.app.ActivityThread;
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.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.UserHandle;
import android.os.UserManager;
import android.widget.Toast;
import com.elderdrivers.riru.edxp.util.Utils;
import java.io.File;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Scanner;
import static com.elderdrivers.riru.edxp.service.ServiceProxy.CONFIG_PATH;
public class PackageReceiver {
private static final BroadcastReceiver RECEIVER = new BroadcastReceiver() {
private PackageManager pm = null;
private final String MODULES_LIST_FILENAME = "conf/modules.list";
private final String ENABLED_MODULES_LIST_FILENAME = "conf/enabled_modules.list";
private String getPackageName(Intent intent) {
Uri uri = intent.getData();
return (uri != null) ? uri.getSchemeSpecificPart() : null;
}
private void getPackageManager() {
if (pm != null) return;
ActivityThread activityThread = ActivityThread.currentActivityThread();
if (activityThread == null) {
Utils.logW("ActivityThread is null");
return;
}
Context context = activityThread.getSystemContext();
if (context == null) {
Utils.logW("context is null");
return;
}
pm = context.getPackageManager();
}
private boolean isXposedModule(ApplicationInfo app) {
return app != null && app.enabled && app.metaData != null && app.metaData.containsKey("xposedmodule");
}
private PackageInfo getPackageInfo(String packageName) {
getPackageManager();
if (pm == null) {
Utils.logW("PM is null");
return null;
}
try {
return pm.getPackageInfo(packageName, PackageManager.GET_META_DATA);
} catch (PackageManager.NameNotFoundException e) {
return null;
}
}
private Map<String, String> loadEnabledModules(int uid) {
HashMap<String, String> result = new HashMap<>();
try {
File enabledModules = new File(CONFIG_PATH, uid + "/" + ENABLED_MODULES_LIST_FILENAME);
if (!enabledModules.exists()) return result;
Scanner scanner = new Scanner(enabledModules);
if (scanner.hasNextLine()) {
String packageName = scanner.nextLine();
PackageInfo info = getPackageInfo(packageName);
if (info != null && isXposedModule(info.applicationInfo)) {
result.put(packageName, info.applicationInfo.sourceDir);
} else {
Utils.logW(String.format("remove obsolete package %s", packageName));
}
}
} catch (Throwable e) {
Utils.logE("Unable to read enabled modules", e);
}
return result;
}
private void updateModuleList(int uid, String packageName) {
Map<String, String> enabledModules = loadEnabledModules(uid);
if (!enabledModules.containsKey(packageName)) return;
try {
File moduleListFile = new File(CONFIG_PATH, uid + "/" + MODULES_LIST_FILENAME);
moduleListFile.createNewFile();
PrintWriter modulesList = new PrintWriter(moduleListFile);
PrintWriter enabledModulesList = new PrintWriter(new File(CONFIG_PATH, uid + "/" + ENABLED_MODULES_LIST_FILENAME));
for (Map.Entry<String, String> module : enabledModules.entrySet()) {
modulesList.println(module.getValue());
enabledModulesList.println(module.getKey());
}
modulesList.close();
enabledModulesList.close();
} catch (Throwable e) {
Utils.logE("Fail to update module list", e);
}
}
@Override
public void onReceive(Context context, Intent intent) {
if (Objects.requireNonNull(intent.getAction()).equals(Intent.ACTION_PACKAGE_REMOVED) && intent.getBooleanExtra(Intent.EXTRA_REPLACING, false))
// Ignore existing packages being removed in order to be updated
return;
String packageName = getPackageName(intent);
if (packageName == null)
return;
if (intent.getAction().equals(Intent.ACTION_PACKAGE_CHANGED)) {
// make sure that the change is for the complete package, not only a
// component
String[] components = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
if (components != null) {
boolean isForPackage = false;
for (String component : components) {
if (packageName.equals(component)) {
isForPackage = true;
break;
}
}
if (!isForPackage)
return;
}
}
PackageInfo pkgInfo = getPackageInfo(packageName);
if (pkgInfo != null && !isXposedModule(pkgInfo.applicationInfo)) return;
try {
UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
@SuppressLint("DiscouragedPrivateApi")
Method m = UserManager.class.getDeclaredMethod("getUsers");
m.setAccessible(true);
for (Object uh : (List<Object>) m.invoke(um)) {
int uid = (int) uh.getClass().getDeclaredField("id").get(uh);
Utils.logI("updating uid: " + uid);
updateModuleList(uid, packageName);
}
Toast.makeText(context, "EdXposed: Updated " + packageName, Toast.LENGTH_SHORT).show();
} catch (Throwable e) {
Utils.logW("update failed", e);
}
}
};
public static void register() {
ActivityThread activityThread = ActivityThread.currentActivityThread();
if (activityThread == null) {
Utils.logW("ActivityThread is null");
return;
}
Context context = activityThread.getSystemContext();
if (context == null) {
Utils.logW("context is null");
return;
}
UserHandle userHandleAll;
try {
//noinspection JavaReflectionMemberAccess
Field field = UserHandle.class.getDeclaredField("ALL");
userHandleAll = (UserHandle) field.get(null);
} catch (Throwable e) {
Utils.logW("UserHandle.ALL", e);
return;
}
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
intentFilter.addDataScheme("package");
HandlerThread thread = new HandlerThread("edxp-PackageReceiver");
thread.start();
Handler handler = new Handler(thread.getLooper());
try {
@SuppressLint("DiscouragedPrivateApi")
Method method = Context.class.getDeclaredMethod("registerReceiverAsUser", BroadcastReceiver.class, UserHandle.class, IntentFilter.class, String.class, Handler.class);
method.invoke(context, RECEIVER, userHandleAll, intentFilter, null, handler);
Utils.logI("registered package receiver");
} catch (Throwable e) {
Utils.logW("registerReceiver failed", e);
}
}
}

View File

@ -0,0 +1,84 @@
package com.elderdrivers.riru.edxp.service;
import android.os.Handler;
import android.os.IBinder;
import android.os.IServiceManager;
import android.os.Looper;
import android.os.ServiceManager;
import com.elderdrivers.riru.common.KeepAll;
import com.elderdrivers.riru.edxp.util.Utils;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ServiceProxy implements InvocationHandler, KeepAll {
public static String CONFIG_PATH = null;
private static IServiceManager original;
public synchronized static void install() throws ReflectiveOperationException {
if (original != null) return;
Method method = ServiceManager.class.getDeclaredMethod("getIServiceManager");
Field field = ServiceManager.class.getDeclaredField("sServiceManager");
method.setAccessible(true);
field.setAccessible(true);
original = (IServiceManager) method.invoke(null);
field.set(null, Proxy.newProxyInstance(
ServiceProxy.class.getClassLoader(),
new Class[]{IServiceManager.class},
new ServiceProxy()
));
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
switch (method.getName()) {
case "addService": {
if (args.length > 1 && args[0] instanceof String && args[1] instanceof IBinder) {
final String name = (String) args[0];
final IBinder service = (IBinder) args[1];
args[1] = onAddService(name, service);
}
return method.invoke(original, args);
}
// case "getService":
// if(args.length == 1 && args[0] instanceof String && method.getReturnType() == IBinder.class) {
// final String name = (String) args[0];
// final IBinder service = (IBinder)method.invoke(original, args);
// return onGetService(name, service);
// }
// return method.invoke(original, args);
default:
return method.invoke(original, args);
}
}
private IBinder onAddService(String name, IBinder service) {
if ("activity".equals(name)) {
try {
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
PackageReceiver.register();
}
});
} catch (Throwable e) {
Utils.logW("Error in registering package receiver", e);
}
return service;
}
return service;
}
// protected IBinder onGetService(String name, IBinder service) {
// return service;
// }
}

View File

@ -0,0 +1,4 @@
package android.os;
public interface IServiceManager {
}

View File

@ -1,6 +1,12 @@
package android.os;
public class ServiceManager {
private static IServiceManager sServiceManager;
private static IServiceManager getIServiceManager() {
throw new IllegalArgumentException("Stub!");
}
public static IBinder getService(String name) {
throw new UnsupportedOperationException("STUB");
}

View File

@ -1 +1 @@
include ':edxp-core', ':xposed-bridge', ':hiddenapi-stubs', ':dexmaker', ':dalvikdx', ':edxp-common', ':edxp-yahfa', ':edxp-sandhook'
include ':edxp-core', ':xposed-bridge', ':hiddenapi-stubs', ':dexmaker', ':dalvikdx', ':edxp-common', ':edxp-yahfa', ':edxp-sandhook', ':edxp-service'