[core] Split daemon (#1503)

This commit is contained in:
Nullptr 2021-12-30 09:21:44 +08:00 committed by GitHub
parent 1451b546c1
commit b9666c9d08
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 587 additions and 279 deletions

View File

@ -37,6 +37,9 @@ val repo = FileRepository(rootProject.file(".git"))
val refId = repo.refDatabase.exactRef("refs/remotes/origin/master").objectId!! val refId = repo.refDatabase.exactRef("refs/remotes/origin/master").objectId!!
val commitCount = Git(repo).log().add(refId).call().count() val commitCount = Git(repo).log().add(refId).call().count()
val injectedPackageName by extra("com.android.shell")
val injectedPackageUid by extra(2000)
val defaultManagerPackageName by extra("org.lsposed.manager") val defaultManagerPackageName by extra("org.lsposed.manager")
val apiCode by extra(93) val apiCode by extra(93)
val verCode by extra(commitCount + 4200) val verCode by extra(commitCount + 4200)

View File

@ -17,18 +17,12 @@
* Copyright (C) 2021 LSPosed Contributors * Copyright (C) 2021 LSPosed Contributors
*/ */
import com.android.build.gradle.BaseExtension
import com.android.ide.common.signing.KeystoreHelper
import org.apache.commons.codec.binary.Hex import org.apache.commons.codec.binary.Hex
import org.apache.tools.ant.filters.FixCrLfFilter import org.apache.tools.ant.filters.FixCrLfFilter
import org.apache.tools.ant.filters.ReplaceTokens import org.apache.tools.ant.filters.ReplaceTokens
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.FileOutputStream
import java.io.PrintStream
import java.security.MessageDigest import java.security.MessageDigest
import java.util.* import java.util.*
import java.util.jar.JarFile
import java.util.zip.ZipOutputStream
plugins { plugins {
id("com.android.application") id("com.android.application")
@ -43,10 +37,8 @@ val moduleMinRiruApiVersion = 25
val moduleMinRiruVersionName = "25.0.1" val moduleMinRiruVersionName = "25.0.1"
val moduleMaxRiruApiVersion = 25 val moduleMaxRiruApiVersion = 25
val injectedPackageName = "com.android.shell" val injectedPackageName: String by rootProject.extra
val injectedPackageUid = 2000 val injectedPackageUid: Int by rootProject.extra
val agpVersion: String by rootProject.extra
val defaultManagerPackageName: String by rootProject.extra val defaultManagerPackageName: String by rootProject.extra
val apiCode: Int by rootProject.extra val apiCode: Int by rootProject.extra
@ -169,18 +161,13 @@ dependencies {
implementation("dev.rikka.ndk:riru:26.0.0") implementation("dev.rikka.ndk:riru:26.0.0")
implementation("dev.rikka.ndk.thirdparty:cxx:1.2.0") implementation("dev.rikka.ndk.thirdparty:cxx:1.2.0")
implementation("io.github.vvb2060.ndk:dobby:1.2") implementation("io.github.vvb2060.ndk:dobby:1.2")
implementation("com.android.tools.build:apksig:$agpVersion")
implementation("org.apache.commons:commons-lang3:3.12.0") implementation("org.apache.commons:commons-lang3:3.12.0")
implementation("de.upb.cs.swt:axml:2.1.1") implementation("de.upb.cs.swt:axml:2.1.1")
compileOnly("androidx.annotation:annotation:1.3.0") compileOnly("androidx.annotation:annotation:1.3.0")
compileOnly(project(":hiddenapi-stubs")) compileOnly(project(":hiddenapi-stubs"))
implementation(project(":hiddenapi-bridge")) implementation(project(":hiddenapi-bridge"))
implementation(project(":manager-service")) implementation(project(":manager-service"))
android.applicationVariants.all { implementation(project(":daemon-service"))
"${name}Implementation"(files(File(project.buildDir, "tmp/${name}R.jar")) {
builtBy("generateApp${name}RFile")
})
}
} }
val zipAll = task("zipAll") { val zipAll = task("zipAll") {
@ -197,68 +184,15 @@ fun afterEval() = android.applicationVariants.forEach { variant ->
val magiskDir = "$buildDir/magisk/$variantLowered" val magiskDir = "$buildDir/magisk/$variantLowered"
task<Jar>("generateApp${variantCapped}RFile") {
dependsOn(":app:process${buildTypeCapped}Resources")
doLast {
val rFile = JarFile(
File(
project(":app").buildDir,
"intermediates/compile_and_runtime_not_namespaced_r_class_jar/${buildTypeLowered}/R.jar"
)
)
ZipOutputStream(
FileOutputStream(
File(
project.buildDir,
"tmp/${variantCapped}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()
}
}
}
}
}
val app = rootProject.project(":app").extensions.getByName<BaseExtension>("android")
val outSrcDir = file("$buildDir/generated/source/signInfo/${variantLowered}")
val outSrc = file("$outSrcDir/org/lsposed/lspd/util/SignInfo.java")
val signInfoTask = tasks.register("generate${variantCapped}SignInfo") {
dependsOn(":app:validateSigning${buildTypeCapped}")
outputs.file(outSrc)
doLast {
val sign = app.buildTypes.named(buildTypeLowered).get().signingConfig
outSrc.parentFile.mkdirs()
val certificateInfo = KeystoreHelper.getCertificateInfo(
sign?.storeType,
sign?.storeFile,
sign?.storePassword,
sign?.keyPassword,
sign?.keyAlias
)
PrintStream(outSrc).print(
"""
|package org.lsposed.lspd.util;
|public final class SignInfo {
| public static final byte[] CERTIFICATE = {${
certificateInfo.certificate.encoded.joinToString(",")
}};
|}""".trimMargin()
)
}
}
variant.registerJavaGeneratingTask(signInfoTask, outSrcDir)
val moduleId = "${flavorLowered}_$moduleBaseId" val moduleId = "${flavorLowered}_$moduleBaseId"
val zipFileName = "$moduleName-v$verName-$verCode-${flavorLowered}-$buildTypeLowered.zip" val zipFileName = "$moduleName-v$verName-$verCode-${flavorLowered}-$buildTypeLowered.zip"
val prepareMagiskFilesTask = task<Sync>("prepareMagiskFiles$variantCapped") { val prepareMagiskFilesTask = task<Sync>("prepareMagiskFiles$variantCapped") {
dependsOn("assemble$variantCapped", ":app:assemble$buildTypeCapped") dependsOn(
"assemble$variantCapped",
":app:assemble$buildTypeCapped",
":daemon:assemble$buildTypeCapped"
)
into(magiskDir) into(magiskDir)
from("${rootProject.projectDir}/README.md") from("${rootProject.projectDir}/README.md")
from("$projectDir/magisk_module") { from("$projectDir/magisk_module") {
@ -304,8 +238,13 @@ fun afterEval() = android.applicationVariants.forEach { variant ->
include("*.apk") include("*.apk")
rename(".*\\.apk", "manager.apk") rename(".*\\.apk", "manager.apk")
} }
from("${project(":daemon").buildDir}/outputs/apk/${buildTypeLowered}") {
include("*.apk")
rename(".*\\.apk", "daemon.apk")
}
into("lib") { into("lib") {
from("${buildDir}/intermediates/stripped_native_libs/$variantCapped/out/lib") from("${buildDir}/intermediates/stripped_native_libs/$variantCapped/out/lib")
from("${project(":daemon").buildDir}/intermediates/ndkBuild/$buildTypeLowered/obj/local")
} }
val dexOutPath = if (buildTypeLowered == "release") val dexOutPath = if (buildTypeLowered == "release")
"$buildDir/intermediates/dex/$variantCapped/minify${variantCapped}WithR8" else "$buildDir/intermediates/dex/$variantCapped/minify${variantCapped}WithR8" else
@ -363,12 +302,12 @@ val killLspd = task<Exec>("killLspd") {
commandLine(adb, "shell", "su", "-c", "killall", "lspd") commandLine(adb, "shell", "su", "-c", "killall", "lspd")
isIgnoreExitValue = true isIgnoreExitValue = true
} }
val pushLspd = task<Exec>("pushLspd") { val pushDaemon = task<Exec>("pushDaemon") {
dependsOn("mergeDexRiruDebug") dependsOn(":daemon:assembleRiruDebug")
workingDir("$buildDir/intermediates/dex/RiruDebug/mergeDexRiruDebug") workingDir("${project(":daemon").buildDir}/outputs/apk/debug")
commandLine(adb, "push", "classes.dex", "/data/local/tmp/lspd.dex") commandLine(adb, "push", "daemon-Zygisk-debug.apk", "/data/local/tmp/daemon.apk")
} }
val pushLspdNative = task<Exec>("pushLspdNative") { val pushDaemonNative = task<Exec>("pushDaemonNative") {
dependsOn("mergeRiruDebugNativeLibs") dependsOn("mergeRiruDebugNativeLibs")
doFirst { doFirst {
val abi: String = ByteArrayOutputStream().use { outputStream -> val abi: String = ByteArrayOutputStream().use { outputStream ->
@ -378,12 +317,12 @@ val pushLspdNative = task<Exec>("pushLspdNative") {
} }
outputStream.toString().trim() outputStream.toString().trim()
} }
workingDir("$buildDir/intermediates/merged_native_libs/RiruDebug/out/lib/$abi") workingDir("${project(":daemon").buildDir}/intermediates/ndkBuild/debug/obj/local/$abi")
} }
commandLine(adb, "push", "libdaemon.so", "/data/local/tmp/libdaemon.so") commandLine(adb, "push", "libdaemon.so", "/data/local/tmp/libdaemon.so")
} }
val reRunLspd = task<Exec>("reRunLspd") { val reRunDaemon = task<Exec>("reRunDaemon") {
dependsOn(pushLspd, pushLspdNative, killLspd) dependsOn(pushDaemon, pushDaemonNative, killLspd)
commandLine(adb, "shell", "su", "-c", "sh /data/adb/modules/*_lsposed/service.sh&") commandLine(adb, "shell", "su", "-c", "sh /data/adb/modules/*_lsposed/service.sh&")
isIgnoreExitValue = true isIgnoreExitValue = true
} }
@ -410,5 +349,5 @@ task<Exec>("reRunApp") {
dependsOn(pushApk) dependsOn(pushApk)
commandLine(adb, "shell", "su", "-c", "mv -f $tmpApk /data/adb/lspd/manager.apk") commandLine(adb, "shell", "su", "-c", "mv -f $tmpApk /data/adb/lspd/manager.apk")
isIgnoreExitValue = true isIgnoreExitValue = true
finalizedBy(reRunLspd) finalizedBy(reRunDaemon)
} }

View File

@ -84,6 +84,7 @@ extract "$ZIPFILE" 'post-fs-data.sh' "$MODPATH"
extract "$ZIPFILE" 'service.sh' "$MODPATH" extract "$ZIPFILE" 'service.sh' "$MODPATH"
extract "$ZIPFILE" 'uninstall.sh' "$MODPATH" extract "$ZIPFILE" 'uninstall.sh' "$MODPATH"
extract "$ZIPFILE" 'framework/lspd.dex' "$MODPATH" extract "$ZIPFILE" 'framework/lspd.dex' "$MODPATH"
extract "$ZIPFILE" 'daemon.apk' "$MODPATH"
extract "$ZIPFILE" 'lspd' "$MODPATH" extract "$ZIPFILE" 'lspd' "$MODPATH"
rm -f /data/adb/lspd/manager.apk rm -f /data/adb/lspd/manager.apk
extract "$ZIPFILE" 'manager.apk' '/data/adb/lspd' extract "$ZIPFILE" 'manager.apk' '/data/adb/lspd'

View File

@ -1,19 +1,19 @@
#!/system/bin/sh #!/system/bin/sh
dir=${0%/*} dir=${0%/*}
tmpLspdDex="/data/local/tmp/lspd.dex" tmpLspdApk="/data/local/tmp/daemon.apk"
debug="false" debug="false"
if [ -r $tmpLspdDex ]; then if [ -r $tmpLspdApk ]; then
java_options="-Djava.class.path=$tmpLspdDex" java_options="-Djava.class.path=$tmpLspdApk"
java_options="$java_options -Dlsp.library.path=/data/local/tmp" java_options="$java_options -Dlsp.library.path=/data/local/tmp"
debug="true" debug="true"
elif [ -d "$dir/system" ]; then elif [ -d "$dir/system" ]; then
java_options="-Djava.class.path=$dir/system/framework/lspd.dex" java_options="-Djava.class.path=$dir/system/daemon.apk"
java_options="$java_options -Dlsp.library.path=$dir" java_options="$java_options -Dlsp.library.path=$dir"
debug="true" debug="true"
else else
java_options="-Djava.class.path=$dir/framework/lspd.dex" java_options="-Djava.class.path=$dir/daemon.apk"
java_options="$java_options -Dlsp.library.path=$dir" java_options="$java_options -Dlsp.library.path=$dir"
fi fi
@ -29,4 +29,4 @@ if [ $debug = "true" ]; then
fi fi
# shellcheck disable=SC2086 # shellcheck disable=SC2086
exec /system/bin/app_process $java_options /system/bin --nice-name=lspd org.lsposed.lspd.core.Main "$@" >/dev/null 2>&1 exec /system/bin/app_process $java_options /system/bin --nice-name=lspd org.lsposed.lspd.Main "$@" >/dev/null 2>&1

View File

@ -25,5 +25,5 @@ if ! [ "$ZYGISK_ENABLED" = "$([ $FLAVOR = "zygisk" ] && echo 1)" ]; then
log -t "LSPosed" "$FLAVOR does not match, skipping" log -t "LSPosed" "$FLAVOR does not match, skipping"
exit exit
fi fi
rm -f "/data/local/tmp/lspd.dex" rm -f "/data/local/tmp/daemon.apk"
unshare -m sh -c "$MODDIR/lspd &" unshare -m sh -c "$MODDIR/lspd &"

View File

@ -1,7 +1,6 @@
include src/main/cpp/external/DexBuilder/Android.mk include src/main/cpp/external/DexBuilder/Android.mk
include src/main/cpp/external/yahfa/Android.mk include src/main/cpp/external/yahfa/Android.mk
include src/main/cpp/main/Android.mk include src/main/cpp/main/Android.mk
include src/main/cpp/daemon/Android.mk
$(call import-module,prefab/cxx) $(call import-module,prefab/cxx)
$(call import-module,prefab/riru) $(call import-module,prefab/riru)

View File

@ -35,7 +35,6 @@ import org.lsposed.lspd.hooker.CrashDumpHooker;
import org.lsposed.lspd.hooker.HandleBindAppHooker; import org.lsposed.lspd.hooker.HandleBindAppHooker;
import org.lsposed.lspd.hooker.LoadedApkCstrHooker; import org.lsposed.lspd.hooker.LoadedApkCstrHooker;
import org.lsposed.lspd.hooker.SystemMainHooker; import org.lsposed.lspd.hooker.SystemMainHooker;
import org.lsposed.lspd.service.ServiceManager;
import org.lsposed.lspd.util.ParasiticManagerHooker; import org.lsposed.lspd.util.ParasiticManagerHooker;
import org.lsposed.lspd.util.Utils; import org.lsposed.lspd.util.Utils;
import org.lsposed.lspd.yahfa.hooker.YahfaHooker; import org.lsposed.lspd.yahfa.hooker.YahfaHooker;
@ -100,8 +99,4 @@ public class Main {
forkPostCommon(true, forkPostCommon(true,
new File(Environment.getDataDirectory(), "android").toString(), "system_server"); new File(Environment.getDataDirectory(), "android").toString(), "system_server");
} }
public static void main(String[] args) {
ServiceManager.start(args);
}
} }

View File

@ -1,6 +1,6 @@
package org.lsposed.lspd.service; package org.lsposed.lspd.service;
import static org.lsposed.lspd.service.ServiceManager.TAG; import static org.lsposed.lspd.service.BridgeService.TAG;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.ActivityThread; import android.app.ActivityThread;

View File

@ -19,7 +19,6 @@
package org.lsposed.lspd.service; package org.lsposed.lspd.service;
import static org.lsposed.lspd.service.ServiceManager.TAG;
import static hidden.HiddenApiBridge.Binder_allowBlocking; import static hidden.HiddenApiBridge.Binder_allowBlocking;
import static hidden.HiddenApiBridge.Context_getActivityToken; import static hidden.HiddenApiBridge.Context_getActivityToken;
@ -27,15 +26,10 @@ import android.app.ActivityThread;
import android.app.IApplicationThread; import android.app.IApplicationThread;
import android.content.Context; import android.content.Context;
import android.os.Binder; import android.os.Binder;
import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
import android.os.Looper;
import android.os.Parcel; import android.os.Parcel;
import android.os.Process; import android.os.Process;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.ServiceManager;
import android.system.ErrnoException;
import android.system.Os;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@ -43,14 +37,10 @@ import androidx.annotation.Nullable;
import org.lsposed.lspd.BuildConfig; import org.lsposed.lspd.BuildConfig;
import java.lang.reflect.Field;
import java.util.Map;
public class BridgeService { public class BridgeService {
private static final int TRANSACTION_CODE = ('_' << 24) | ('L' << 16) | ('S' << 8) | 'P'; private static final int TRANSACTION_CODE = ('_' << 24) | ('L' << 16) | ('S' << 8) | 'P';
private static final String DESCRIPTOR = "LSPosed"; private static final String DESCRIPTOR = "LSPosed";
private static final String SERVICE_NAME = "activity"; protected static final String TAG = "LSPosed Bridge";
private static final String SHORTCUT_ID = "org.lsposed.manager.shortcut";
enum ACTION { enum ACTION {
ACTION_UNKNOWN, ACTION_UNKNOWN,
@ -62,40 +52,6 @@ public class BridgeService {
private static IBinder serviceBinder = null; private static IBinder serviceBinder = null;
private static ILSPosedService service = null; private static ILSPosedService service = null;
// for service
private static IBinder bridgeService;
private static final IBinder.DeathRecipient bridgeRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
Log.i(TAG, "service " + SERVICE_NAME + " is dead. ");
try {
//noinspection JavaReflectionMemberAccess DiscouragedPrivateApi
Field field = ServiceManager.class.getDeclaredField("sServiceManager");
field.setAccessible(true);
field.set(null, null);
//noinspection JavaReflectionMemberAccess DiscouragedPrivateApi
field = ServiceManager.class.getDeclaredField("sCache");
field.setAccessible(true);
Object sCache = field.get(null);
if (sCache instanceof Map) {
//noinspection rawtypes
((Map) sCache).clear();
}
Log.i(TAG, "clear ServiceManager");
} catch (Throwable e) {
Log.w(TAG, "clear ServiceManager: " + Log.getStackTraceString(e));
}
bridgeService.unlinkToDeath(this, 0);
bridgeService = null;
listener.onSystemServerDied();
new Handler(Looper.getMainLooper()).post(() -> sendToBridge(serviceBinder, true));
}
};
// for client // for client
private static final IBinder.DeathRecipient serviceRecipient = new IBinder.DeathRecipient() { private static final IBinder.DeathRecipient serviceRecipient = new IBinder.DeathRecipient() {
@Override @Override
@ -107,100 +63,6 @@ public class BridgeService {
} }
}; };
public interface Listener {
void onSystemServerRestarted();
void onResponseFromBridgeService(boolean response);
void onSystemServerDied();
}
private static Listener listener;
// For service
// This MUST run in main thread
private static synchronized void sendToBridge(IBinder binder, boolean isRestart) {
assert Looper.myLooper() == Looper.getMainLooper();
try {
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();
}
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;
}
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();
}
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 {
Os.seteuid(1000);
} catch (ErrnoException e) {
Log.e(TAG, "seteuid 1000", e);
}
}
}
// For client // For client
private static void receiveFromBridge(IBinder binder) { private static void receiveFromBridge(IBinder binder) {
if (binder == null) { if (binder == null) {
@ -231,13 +93,6 @@ public class BridgeService {
Log.i(TAG, "binder received"); Log.i(TAG, "binder received");
} }
public static void send(LSPosedService service, Listener listener) {
BridgeService.listener = listener;
BridgeService.service = service;
BridgeService.serviceBinder = service.asBinder();
sendToBridge(serviceBinder, false);
}
public static ILSPosedService getService() { public static ILSPosedService getService() {
return service; return service;
} }
@ -368,6 +223,7 @@ public class BridgeService {
return res; return res;
} }
@SuppressWarnings("unused")
public static IBinder getApplicationServiceForSystemServer(IBinder binder, IBinder heartBeat) { public static IBinder getApplicationServiceForSystemServer(IBinder binder, IBinder heartBeat) {
if (binder == null || heartBeat == null) return null; if (binder == null || heartBeat == null) return null;
try { try {

View File

@ -20,35 +20,11 @@
package org.lsposed.lspd.util; package org.lsposed.lspd.util;
import static org.lsposed.lspd.util.SignInfo.CERTIFICATE;
import android.os.IBinder; import android.os.IBinder;
import com.android.apksig.ApkVerifier;
import java.io.File;
import java.util.Arrays;
import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.XposedHelpers;
public class InstallerVerifier { public class InstallerVerifier {
public static boolean verifyInstallerSignature(String path) {
ApkVerifier verifier = new ApkVerifier.Builder(new File(path))
.setMinCheckedPlatformVersion(27)
.build();
try {
ApkVerifier.Result result = verifier.verify();
if (!result.isVerified()) {
return false;
}
boolean ret = Arrays.equals(result.getSignerCertificates().get(0).getEncoded(), CERTIFICATE);
Utils.logI("verifyInstallerSignature: " + ret);
return ret;
} catch (Throwable t) {
Utils.logE("verifyInstallerSignature: ", t);
return false;
}
}
public static boolean sendBinderToManager(final ClassLoader classLoader, IBinder binder) { public static boolean sendBinderToManager(final ClassLoader classLoader, IBinder binder) {
Utils.logI("Found LSPosed Manager"); Utils.logI("Found LSPosed Manager");

1
daemon-service/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

View File

@ -0,0 +1,58 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*
* Copyright (C) 2021 LSPosed Contributors
*/
plugins {
id("com.android.library")
}
val androidTargetSdkVersion: Int by rootProject.extra
val androidBuildToolsVersion: String by rootProject.extra
val androidMinSdkVersion: Int by rootProject.extra
val androidSourceCompatibility: JavaVersion by rootProject.extra
val androidTargetCompatibility: JavaVersion by rootProject.extra
android {
compileSdk = androidTargetSdkVersion
buildToolsVersion = androidBuildToolsVersion
defaultConfig {
minSdk = androidMinSdkVersion
targetSdk = androidTargetSdkVersion
consumerProguardFiles("proguard-rules.pro")
}
buildTypes {
release {
isMinifyEnabled = false
}
}
compileOptions {
sourceCompatibility = androidSourceCompatibility
targetCompatibility = androidTargetCompatibility
}
aidlPackagedList += "org/lsposed/lspd/models/Module.aidl"
aidlPackagedList += "org/lsposed/lspd/models/PreloadedApk.aidl"
}
dependencies {
compileOnly(project(":hiddenapi-stubs"))
}

21
daemon-service/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,21 @@
# 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

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="org.lsposed.lspd.daemonservice" />

View File

@ -24,7 +24,7 @@ import android.os.SystemProperties;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import org.lsposed.lspd.BuildConfig; import org.lsposed.lspd.daemonservice.BuildConfig;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.ZoneOffset; import java.time.ZoneOffset;

1
daemon/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

203
daemon/build.gradle.kts Normal file
View File

@ -0,0 +1,203 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*
* Copyright (C) 2021 LSPosed Contributors
*/
import com.android.build.gradle.BaseExtension
import com.android.ide.common.signing.KeystoreHelper
import java.io.PrintStream
import java.io.FileOutputStream
import java.util.Locale
import java.util.jar.JarFile
import java.util.zip.ZipOutputStream
plugins {
id("com.android.application")
}
val daemonName = "LSPosed"
val injectedPackageName: String by rootProject.extra
val injectedPackageUid: Int by rootProject.extra
val agpVersion: String by rootProject.extra
val defaultManagerPackageName: String by rootProject.extra
val apiCode: Int by rootProject.extra
val verCode: Int by rootProject.extra
val verName: String by rootProject.extra
val androidTargetSdkVersion: Int by rootProject.extra
val androidMinSdkVersion: Int by rootProject.extra
val androidBuildToolsVersion: String by rootProject.extra
val androidCompileSdkVersion: Int by rootProject.extra
val androidCompileNdkVersion: String by rootProject.extra
val androidSourceCompatibility: JavaVersion by rootProject.extra
val androidTargetCompatibility: JavaVersion by rootProject.extra
android {
compileSdk = androidCompileSdkVersion
ndkVersion = androidCompileNdkVersion
buildToolsVersion = androidBuildToolsVersion
buildFeatures {
prefab = true
}
defaultConfig {
applicationId = "org.lsposed.daemon"
minSdk = androidMinSdkVersion
targetSdk = androidTargetSdkVersion
versionCode = verCode
versionName = verName
multiDexEnabled = false
externalNativeBuild {
ndkBuild {
arguments += "-j${Runtime.getRuntime().availableProcessors()}"
}
}
buildConfigField("int", "API_CODE", "$apiCode")
buildConfigField(
"String",
"DEFAULT_MANAGER_PACKAGE_NAME",
""""$defaultManagerPackageName""""
)
buildConfigField("String", "MANAGER_INJECTED_PKG_NAME", """"$injectedPackageName"""")
buildConfigField("int", "MANAGER_INJECTED_UID", """$injectedPackageUid""")
}
lint {
isAbortOnError = true
isCheckReleaseBuilds = false
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles("proguard-rules.pro")
}
}
externalNativeBuild {
ndkBuild {
path("src/main/cpp/Android.mk")
}
}
compileOptions {
targetCompatibility(androidTargetCompatibility)
sourceCompatibility(androidSourceCompatibility)
}
buildTypes {
all {
externalNativeBuild {
ndkBuild {
arguments += "NDK_OUT=${File(buildDir, ".cxx/$name").absolutePath}"
}
}
}
}
}
fun afterEval() = android.applicationVariants.forEach { variant ->
val variantCapped = variant.name.capitalize(Locale.ROOT)
val variantLowered = variant.name.toLowerCase(Locale.ROOT)
tasks["merge${variantCapped}JniLibFolders"].enabled = false
tasks["merge${variantCapped}NativeLibs"].enabled = false
task<Jar>("generateApp${variantLowered}RFile") {
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()
}
}
}
}
}
val app = rootProject.project(":app").extensions.getByName<BaseExtension>("android")
val outSrcDir = file("$buildDir/generated/source/signInfo/${variantLowered}")
val outSrc = file("$outSrcDir/org/lsposed/lspd/util/SignInfo.java")
val signInfoTask = tasks.register("generate${variantCapped}SignInfo") {
dependsOn(":app:validateSigning${variantCapped}")
outputs.file(outSrc)
doLast {
val sign = app.buildTypes.named(variantLowered).get().signingConfig
outSrc.parentFile.mkdirs()
val certificateInfo = KeystoreHelper.getCertificateInfo(
sign?.storeType,
sign?.storeFile,
sign?.storePassword,
sign?.keyPassword,
sign?.keyAlias
)
PrintStream(outSrc).print(
"""
|package org.lsposed.lspd.util;
|public final class SignInfo {
| public static final byte[] CERTIFICATE = {${
certificateInfo.certificate.encoded.joinToString(",")
}};
|}""".trimMargin()
)
}
}
variant.registerJavaGeneratingTask(signInfoTask, outSrcDir)
}
afterEvaluate {
afterEval()
}
dependencies {
implementation("dev.rikka.ndk.thirdparty:cxx:1.2.0")
implementation("com.android.tools.build:apksig:$agpVersion")
implementation("org.apache.commons:commons-lang3:3.12.0")
compileOnly("androidx.annotation:annotation:1.3.0")
compileOnly(project(":hiddenapi-stubs"))
implementation(project(":hiddenapi-bridge"))
implementation(project(":daemon-service"))
implementation(project(":manager-service"))
android.applicationVariants.all {
"${name}Implementation"(files(File(project.buildDir, "tmp/${name}R.jar")) {
builtBy("generateApp${name}RFile")
})
}
}

21
daemon/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,21 @@
# 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

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.lsposed.daemon">
<application
android:allowBackup="true"
android:label="@string/app_name"
android:supportsRtl="true" />
</manifest>

View File

@ -7,3 +7,5 @@ LOCAL_STATIC_LIBRARIES := cxx
LOCAL_ALLOW_UNDEFINED_SYMBOLS := true LOCAL_ALLOW_UNDEFINED_SYMBOLS := true
LOCAL_LDLIBS := -llog LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY) include $(BUILD_SHARED_LIBRARY)
$(call import-module,prefab/cxx)

View File

@ -0,0 +1,15 @@
APP_CFLAGS := -Wall -Wextra
APP_CFLAGS += -fno-stack-protector -fomit-frame-pointer
APP_CFLAGS += -Wno-builtin-macro-redefined -D__FILE__=__FILE_NAME__
APP_CPPFLAGS := -std=c++20
APP_CONLYFLAGS := -std=c18
APP_LDFLAGS := -Wl,--exclude-libs,ALL
APP_STL := none
ifneq ($(NDK_DEBUG),1)
APP_CFLAGS += -Oz -flto
APP_CFLAGS += -Wno-unused -Wno-unused-parameter -Werror
APP_CFLAGS += -fvisibility=hidden -fvisibility-inlines-hidden
APP_CFLAGS += -fno-unwind-tables -fno-asynchronous-unwind-tables
APP_LDFLAGS += -flto -Wl,--gc-sections -Wl,--strip-all
endif

View File

@ -0,0 +1,10 @@
package org.lsposed.lspd;
import org.lsposed.lspd.service.ServiceManager;
public class Main {
public static void main(String[] args) {
ServiceManager.start(args);
}
}

View File

@ -0,0 +1,162 @@
package org.lsposed.lspd.service;
import static org.lsposed.lspd.service.ServiceManager.TAG;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Parcel;
import android.os.ServiceManager;
import android.system.ErrnoException;
import android.system.Os;
import android.util.Log;
import java.lang.reflect.Field;
import java.util.Map;
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";
enum ACTION {
ACTION_UNKNOWN,
ACTION_SEND_BINDER,
ACTION_GET_BINDER,
}
public interface Listener {
void onSystemServerRestarted();
void onResponseFromBridgeService(boolean response);
void onSystemServerDied();
}
private static IBinder serviceBinder = null;
private static Listener listener;
private static IBinder bridgeService;
private static final IBinder.DeathRecipient bridgeRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
Log.i(TAG, "service " + SERVICE_NAME + " is dead. ");
try {
//noinspection JavaReflectionMemberAccess DiscouragedPrivateApi
Field field = ServiceManager.class.getDeclaredField("sServiceManager");
field.setAccessible(true);
field.set(null, null);
//noinspection JavaReflectionMemberAccess DiscouragedPrivateApi
field = ServiceManager.class.getDeclaredField("sCache");
field.setAccessible(true);
Object sCache = field.get(null);
if (sCache instanceof Map) {
//noinspection rawtypes
((Map) sCache).clear();
}
Log.i(TAG, "clear ServiceManager");
} catch (Throwable e) {
Log.w(TAG, "clear ServiceManager: " + Log.getStackTraceString(e));
}
bridgeService.unlinkToDeath(this, 0);
bridgeService = null;
listener.onSystemServerDied();
new Handler(Looper.getMainLooper()).post(() -> sendToBridge(serviceBinder, true));
}
};
// For service
// This MUST run in main thread
private static synchronized void sendToBridge(IBinder binder, boolean isRestart) {
assert Looper.myLooper() == Looper.getMainLooper();
try {
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();
}
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;
}
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();
}
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 {
Os.seteuid(1000);
} catch (ErrnoException e) {
Log.e(TAG, "seteuid 1000", e);
}
}
}
public static void send(LSPosedService service, Listener listener) {
BridgeService.listener = listener;
BridgeService.serviceBinder = service.asBinder();
sendToBridge(serviceBinder, false);
}
}

View File

@ -46,7 +46,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.apache.commons.lang3.SerializationUtils; import org.apache.commons.lang3.SerializationUtils;
import org.lsposed.lspd.BuildConfig; import org.lsposed.daemon.BuildConfig;
import org.lsposed.lspd.models.Application; import org.lsposed.lspd.models.Application;
import org.lsposed.lspd.models.Module; import org.lsposed.lspd.models.Module;

View File

@ -60,7 +60,7 @@ import android.view.IWindowManager;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import org.lsposed.lspd.BuildConfig; import org.lsposed.daemon.BuildConfig;
import org.lsposed.lspd.ILSPManagerService; import org.lsposed.lspd.ILSPManagerService;
import org.lsposed.lspd.models.Application; import org.lsposed.lspd.models.Application;
import org.lsposed.lspd.models.UserInfo; import org.lsposed.lspd.models.UserInfo;
@ -79,7 +79,6 @@ import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.zip.ZipFile; import java.util.zip.ZipFile;
import de.robv.android.xposed.XposedBridge;
import hidden.HiddenApiBridge; import hidden.HiddenApiBridge;
import io.github.xposed.xposedservice.utils.ParceledListSlice; import io.github.xposed.xposedservice.utils.ParceledListSlice;
@ -502,7 +501,7 @@ public class LSPManagerService extends ILSPManagerService.Stub {
@Override @Override
public int getXposedApiVersion() { public int getXposedApiVersion() {
return XposedBridge.getXposedVersion(); return BuildConfig.API_CODE;
} }
@Override @Override

View File

@ -21,7 +21,8 @@ package org.lsposed.lspd.service;
import android.os.IBinder; import android.os.IBinder;
import de.robv.android.xposed.XposedBridge; import org.lsposed.daemon.BuildConfig;
import io.github.xposed.xposedservice.IXposedService; import io.github.xposed.xposedservice.IXposedService;
public class LSPModuleService extends IXposedService.Stub { public class LSPModuleService extends IXposedService.Stub {
@ -39,6 +40,6 @@ public class LSPModuleService extends IXposedService.Stub {
@Override @Override
public int getVersion() { public int getVersion() {
return XposedBridge.getXposedVersion(); return BuildConfig.API_CODE;
} }
} }

View File

@ -35,7 +35,7 @@ import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
import android.util.Log; import android.util.Log;
import org.lsposed.lspd.BuildConfig; import org.lsposed.daemon.BuildConfig;
import java.util.Arrays; import java.util.Arrays;

View File

@ -266,7 +266,7 @@ public class PackageService {
} }
} }
public static ParceledListSlice<ResolveInfo> queryIntentActivities(android.content.Intent intent, java.lang.String resolvedType, int flags, int userId) throws RemoteException { public static ParceledListSlice<ResolveInfo> queryIntentActivities(Intent intent, String resolvedType, int flags, int userId) throws RemoteException {
IPackageManager pm = getPackageManager(); IPackageManager pm = getPackageManager();
if (pm == null) return null; if (pm == null) return null;
return new ParceledListSlice<>(pm.queryIntentActivities(intent, resolvedType, flags, userId).getList()); return new ParceledListSlice<>(pm.queryIntentActivities(intent, resolvedType, flags, userId).getList());

View File

@ -29,11 +29,10 @@ import android.util.Log;
import com.android.internal.os.BinderInternal; import com.android.internal.os.BinderInternal;
import org.lsposed.lspd.BuildConfig; import org.lsposed.daemon.BuildConfig;
import java.io.File; import java.io.File;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;

View File

@ -0,0 +1,29 @@
package org.lsposed.lspd.util;
import com.android.apksig.ApkVerifier;
import java.io.File;
import java.util.Arrays;
import static org.lsposed.lspd.util.SignInfo.CERTIFICATE;
public class InstallerVerifier {
public static boolean verifyInstallerSignature(String path) {
ApkVerifier verifier = new ApkVerifier.Builder(new File(path))
.setMinCheckedPlatformVersion(27)
.build();
try {
ApkVerifier.Result result = verifier.verify();
if (!result.isVerified()) {
return false;
}
boolean ret = Arrays.equals(result.getSignerCertificates().get(0).getEncoded(), CERTIFICATE);
Utils.logI("verifyInstallerSignature: " + ret);
return ret;
} catch (Throwable t) {
Utils.logE("verifyInstallerSignature: ", t);
return false;
}
}
}

View File

@ -0,0 +1,3 @@
<resources>
<string name="app_name">LSPosed</string>
</resources>

View File

@ -14,7 +14,9 @@ include(
":service", ":service",
":interface", ":interface",
":hiddenapi-bridge", ":hiddenapi-bridge",
":manager-service" ":manager-service",
":daemon",
":daemon-service"
) )
val serviceRoot = "service" val serviceRoot = "service"