diff --git a/daemon/src/main/kotlin/org/matrix/vector/daemon/VectorDaemon.kt b/daemon/src/main/kotlin/org/matrix/vector/daemon/VectorDaemon.kt index 3444e1c41c..3f917975c6 100644 --- a/daemon/src/main/kotlin/org/matrix/vector/daemon/VectorDaemon.kt +++ b/daemon/src/main/kotlin/org/matrix/vector/daemon/VectorDaemon.kt @@ -299,6 +299,7 @@ object VectorDaemon { */ fun softReboot() { Log.w(TAG, "Soft reboot: restarting the primary zygote") + Dex2OatServer.onSoftReboot() forgetDeliberateBoot() SystemProperties.set("ctl.restart", "zygote") } diff --git a/daemon/src/main/kotlin/org/matrix/vector/daemon/env/Dex2OatServer.kt b/daemon/src/main/kotlin/org/matrix/vector/daemon/env/Dex2OatServer.kt index 7db27300fc..0eb900e585 100644 --- a/daemon/src/main/kotlin/org/matrix/vector/daemon/env/Dex2OatServer.kt +++ b/daemon/src/main/kotlin/org/matrix/vector/daemon/env/Dex2OatServer.kt @@ -1,9 +1,13 @@ package org.matrix.vector.daemon.env import android.net.LocalServerSocket +import android.net.LocalSocket +import android.net.LocalSocketAddress import android.os.Build import android.os.FileObserver import android.os.SELinux +import android.os.SystemClock +import android.os.SystemProperties import android.system.ErrnoException import android.system.Os import android.system.OsConstants @@ -25,6 +29,7 @@ val DEX2OAT_MOUNT_FAILED = IManagerService.DEX2OAT_MOUNT_FAILED val DEX2OAT_SEPOLICY_INCORRECT = IManagerService.DEX2OAT_SEPOLICY_INCORRECT val DEX2OAT_SELINUX_PERMISSIVE = IManagerService.DEX2OAT_SELINUX_PERMISSIVE val DEX2OAT_CRASHED = IManagerService.DEX2OAT_CRASHED +val DEX2OAT_INJECTED = IManagerService.DEX2OAT_INJECTED object Dex2OatServer { private const val WRAPPER32 = "bin/dex2oat32" @@ -85,6 +90,7 @@ object Dex2OatServer { private fun onSelinuxEvent() { synchronized(this) { + if (compatibility == DEX2OAT_INJECTED) return if (compatibility == DEX2OAT_CRASHED) { SelinuxObserver.stopWatching() return @@ -209,7 +215,87 @@ object Dex2OatServer { doMountNative(enabled, dex2oatArray[0], dex2oatArray[1], dex2oatArray[2], dex2oatArray[3]) } + private fun injectionTargetName(): String { + val useArtService = SystemProperties.get("dalvik.vm.useartservice") + return if (Build.VERSION.SDK_INT >= 35 || + (Build.VERSION.SDK_INT == 34 && (useArtService == "true" || useArtService == "1"))) + "artd" + else "installd" + } + + private fun processExists(name: String): Boolean { + val entries = File("/proc").list() ?: return false + for (entry in entries) { + if (entry.isEmpty() || !entry[0].isDigit()) continue + val command = + runCatching { + File("/proc/$entry/cmdline").readBytes().toString(Charsets.UTF_8) + } + .getOrNull() ?: continue + val executable = command.substringBefore('\u0000') + if (executable.isNotEmpty() && File(executable).name == name) return true + } + return false + } + + private fun probeMonitor(name: String): Boolean { + val path = "/data/adb/lspd/dex2oat_monitor_$name" + val deadline = SystemClock.uptimeMillis() + 1000L + do { + var socket: LocalSocket? = null + try { + socket = LocalSocket() + socket.connect(LocalSocketAddress(path, LocalSocketAddress.Namespace.FILESYSTEM)) + runCatching { socket.setSoTimeout(250) } + val status = socket.inputStream.read() + if (status == 1) return true + if (status >= 0) return false + } catch (_: Throwable) { + } finally { + runCatching { socket?.close() } + } + try { + Thread.sleep(25L) + } catch (_: InterruptedException) { + return false + } + } while (SystemClock.uptimeMillis() < deadline) + return false + } + + private fun probeInjection(): Boolean { + val preferred = injectionTargetName() + if (processExists(preferred)) return probeMonitor(preferred) + val fallback = if (preferred == "artd") "installd" else "artd" + if (processExists(fallback)) return probeMonitor(fallback) + return probeMonitor(preferred) || probeMonitor(fallback) + } + + fun onSoftReboot() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) return + if (probeInjection()) { + if (!notMounted()) doMount(false) + if (notMounted()) { + compatibility = DEX2OAT_INJECTED + return + } + } + if (compatibility == DEX2OAT_INJECTED) return + doMount(false) + doMount(true) + compatibility = if (notMounted()) DEX2OAT_MOUNT_FAILED else DEX2OAT_OK + } + fun start() { + if (probeInjection()) { + if (!notMounted()) doMount(false) + if (notMounted()) { + compatibility = DEX2OAT_INJECTED + Log.i(TAG, "dex2oat injection is active; wrapper stays off") + return + } + } + if (notMounted()) { doMount(true) if (notMounted()) { diff --git a/dex2oat/src/main/cpp/CMakeLists.txt b/dex2oat/src/main/cpp/CMakeLists.txt index ae4c3ae9c6..6b5faaf57f 100644 --- a/dex2oat/src/main/cpp/CMakeLists.txt +++ b/dex2oat/src/main/cpp/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.10) project(dex2oat) add_executable(dex2oat dex2oat.cpp) -add_library(oat_hook SHARED oat_hook.cpp) +add_library(oat_hook SHARED oat_hook.cpp compiler_options.cpp) OPTION(LSPLT_BUILD_SHARED OFF) add_subdirectory(${VECTOR_ROOT}/external/lsplt/lsplt/src/main/jni external) diff --git a/dex2oat/src/main/cpp/compiler_options.cpp b/dex2oat/src/main/cpp/compiler_options.cpp new file mode 100644 index 0000000000..5eadf44611 --- /dev/null +++ b/dex2oat/src/main/cpp/compiler_options.cpp @@ -0,0 +1,17 @@ +#include +#include + +#include + +extern "C" __attribute__((visibility("default"))) void _ZN3art15CompilerOptionsC1Ev(void *self) { + using Constructor = void (*)(void *); + static Constructor original = + reinterpret_cast(dlsym(RTLD_NEXT, "_ZN3art15CompilerOptionsC1Ev")); + if (original == nullptr) return; + + original(self); + + constexpr size_t kInlineMaxCodeUnitsOffset = sizeof(void *) == 8 ? 16 : 8; + auto *units = reinterpret_cast(static_cast(self) + kInlineMaxCodeUnitsOffset); + *units = 0; +} diff --git a/dex2oat/src/main/cpp/oat_hook.cpp b/dex2oat/src/main/cpp/oat_hook.cpp index cc4cdb1b9d..ddad370896 100644 --- a/dex2oat/src/main/cpp/oat_hook.cpp +++ b/dex2oat/src/main/cpp/oat_hook.cpp @@ -211,6 +211,8 @@ void register_hook(dev_t dev, ino_t inode, const char* symbol, void* new_func, v #define PLT_HOOK_REGISTER(DEV, INODE, NAME) PLT_HOOK_REGISTER_SYM(DEV, INODE, #NAME, NAME) __attribute__((constructor)) static void initialize() { + if (getenv("VECTOR_INJECTED") != nullptr) return; + dev_t dev = 0; ino_t inode = 0; @@ -227,7 +229,7 @@ __attribute__((constructor)) static void initialize() { } if (dev == 0) { - LOGE("Could not locate dex2oat memory map"); + LOGD("Could not locate dex2oat memory map"); return; } diff --git a/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeViewModel.kt b/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeViewModel.kt index 725ff534dc..1d4bf958ce 100644 --- a/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeViewModel.kt +++ b/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeViewModel.kt @@ -427,7 +427,9 @@ class HomeViewModel( // deletes `dalvik.vm.dex2oat-flags` when it mounts the wrapper over dex2oat and sets // it when it unmounts, so either route suppresses the inlining. A wrapper that is not // OK therefore only costs anything when the property is not carrying the flag either. - if (dex2oat != IManagerService.DEX2OAT_OK && !inliningDisabled) { + if (dex2oat != IManagerService.DEX2OAT_OK && + dex2oat != IManagerService.DEX2OAT_INJECTED && + !inliningDisabled) { add(HealthIssue.Dex2oatWrapperBroken) } } diff --git a/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/SystemStatusScreen.kt b/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/SystemStatusScreen.kt index f63f0dd9c9..45900be852 100644 --- a/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/SystemStatusScreen.kt +++ b/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/SystemStatusScreen.kt @@ -814,7 +814,8 @@ private fun buildSections( str(R.string.info_dex2oat), dex2oatLabel(context, status.dex2oatWrapperState), health = - if (status.dex2oatWrapperState == IManagerService.DEX2OAT_OK) + if (status.dex2oatWrapperState == IManagerService.DEX2OAT_OK || + status.dex2oatWrapperState == IManagerService.DEX2OAT_INJECTED) Health.Good else Health.Bad, monospace = false, @@ -832,17 +833,29 @@ private fun buildSections( ) } -private fun dex2oatLabel(context: Context, state: Int): String = - context.getString( - when (state) { - IManagerService.DEX2OAT_OK -> R.string.info_supported - IManagerService.DEX2OAT_CRASHED -> R.string.info_dex2oat_crashed - IManagerService.DEX2OAT_MOUNT_FAILED -> R.string.info_dex2oat_mount_failed - IManagerService.DEX2OAT_SELINUX_PERMISSIVE -> R.string.info_dex2oat_selinux_permissive - IManagerService.DEX2OAT_SEPOLICY_INCORRECT -> R.string.info_dex2oat_sepolicy_incorrect - else -> R.string.info_unsupported - } - ) +private fun dex2oatLabel(context: Context, state: Int): String { + val label = + context.getString( + when (state) { + IManagerService.DEX2OAT_OK -> R.string.info_supported + IManagerService.DEX2OAT_INJECTED -> R.string.info_supported + IManagerService.DEX2OAT_CRASHED -> R.string.info_dex2oat_crashed + IManagerService.DEX2OAT_MOUNT_FAILED -> R.string.info_dex2oat_mount_failed + IManagerService.DEX2OAT_SELINUX_PERMISSIVE -> + R.string.info_dex2oat_selinux_permissive + IManagerService.DEX2OAT_SEPOLICY_INCORRECT -> + R.string.info_dex2oat_sepolicy_incorrect + else -> R.string.info_unsupported + } + ) + return when (state) { + IManagerService.DEX2OAT_OK -> + "$label (${context.getString(R.string.info_mounted)})" + IManagerService.DEX2OAT_INJECTED -> + "$label (${context.getString(R.string.info_injected)})" + else -> label + } +} @Composable private fun FrameworkToggle( diff --git a/manager/src/main/res/values-ar/strings.xml b/manager/src/main/res/values-ar/strings.xml index 932f431a3d..709b2c5d1e 100644 --- a/manager/src/main/res/values-ar/strings.xml +++ b/manager/src/main/res/values-ar/strings.xml @@ -42,6 +42,7 @@ محمَّلة غير محمَّلة محقون + مُركّب غير محقون إشعار الحالة إشعار دائم يبيّن أن Vector يعمل. diff --git a/manager/src/main/res/values-de/strings.xml b/manager/src/main/res/values-de/strings.xml index 55a712de70..42d958dd90 100644 --- a/manager/src/main/res/values-de/strings.xml +++ b/manager/src/main/res/values-de/strings.xml @@ -42,6 +42,7 @@ Geladen Nicht geladen Injiziert + Eingehängt Nicht injiziert Statusbenachrichtigung Eine dauerhafte Benachrichtigung, die anzeigt, dass Vector läuft. diff --git a/manager/src/main/res/values-es/strings.xml b/manager/src/main/res/values-es/strings.xml index d3fc8ad5cd..4dc9b083ad 100644 --- a/manager/src/main/res/values-es/strings.xml +++ b/manager/src/main/res/values-es/strings.xml @@ -42,6 +42,7 @@ Cargada No cargada Inyectado + Montado No inyectado Notificación de estado Una notificación permanente que indica que Vector está en marcha. diff --git a/manager/src/main/res/values-fa/strings.xml b/manager/src/main/res/values-fa/strings.xml index d4c8a06702..ae23b833ca 100644 --- a/manager/src/main/res/values-fa/strings.xml +++ b/manager/src/main/res/values-fa/strings.xml @@ -42,6 +42,7 @@ بارگذاری‌شده بارگذاری‌نشده تزریق‌شده + متصل‌شده تزریق‌نشده اعلان وضعیت اعلانی ماندگار که نشان می‌دهد Vector در حال اجراست. diff --git a/manager/src/main/res/values-fr/strings.xml b/manager/src/main/res/values-fr/strings.xml index 3a401d36b6..f10dbbe176 100644 --- a/manager/src/main/res/values-fr/strings.xml +++ b/manager/src/main/res/values-fr/strings.xml @@ -42,6 +42,7 @@ Chargée Non chargée Injecté + Monté Non injecté Notification d\'état Une notification persistante indiquant que Vector fonctionne. diff --git a/manager/src/main/res/values-in/strings.xml b/manager/src/main/res/values-in/strings.xml index 3c6c6390e6..324e83fd30 100644 --- a/manager/src/main/res/values-in/strings.xml +++ b/manager/src/main/res/values-in/strings.xml @@ -46,6 +46,7 @@ Dimuat Tidak dimuat Terinjeksi + Terpasang Tidak terinjeksi Notifikasi status Notifikasi permanen yang menandakan Vector sedang berjalan. diff --git a/manager/src/main/res/values-it/strings.xml b/manager/src/main/res/values-it/strings.xml index e71e14e18c..46d2d07330 100644 --- a/manager/src/main/res/values-it/strings.xml +++ b/manager/src/main/res/values-it/strings.xml @@ -42,6 +42,7 @@ Caricati Non caricati Iniettato + Montato Non iniettato Notifica di stato Una notifica permanente che indica che Vector è in esecuzione. diff --git a/manager/src/main/res/values-iw/strings.xml b/manager/src/main/res/values-iw/strings.xml index 385c875830..783c84b048 100644 --- a/manager/src/main/res/values-iw/strings.xml +++ b/manager/src/main/res/values-iw/strings.xml @@ -46,6 +46,7 @@ נטענה לא נטענה הוזרק + מחובר לא הוזרק התראת מצב התראה קבועה שמראה ש-Vector פועל. diff --git a/manager/src/main/res/values-ja/strings.xml b/manager/src/main/res/values-ja/strings.xml index 3b75f0c3f6..3fc7e6c89d 100644 --- a/manager/src/main/res/values-ja/strings.xml +++ b/manager/src/main/res/values-ja/strings.xml @@ -42,6 +42,7 @@ 読み込まれています 読み込まれていません 注入されています + マウント済み 注入されていません ステータス通知 Vector が実行中であることを示す常時表示の通知です。 diff --git a/manager/src/main/res/values-ko/strings.xml b/manager/src/main/res/values-ko/strings.xml index 533f397354..f497da7eb3 100644 --- a/manager/src/main/res/values-ko/strings.xml +++ b/manager/src/main/res/values-ko/strings.xml @@ -42,6 +42,7 @@ 로드됨 로드되지 않음 주입됨 + 마운트됨 주입되지 않음 상태 알림 Vector가 실행 중임을 알리는 상시 알림입니다. diff --git a/manager/src/main/res/values-pl/strings.xml b/manager/src/main/res/values-pl/strings.xml index e8f40a59c6..2464781ece 100644 --- a/manager/src/main/res/values-pl/strings.xml +++ b/manager/src/main/res/values-pl/strings.xml @@ -83,6 +83,7 @@ Załadowane Niezaładowane Wstrzyknięty + Zamontowany Niewstrzyknięty Powiadomienie o stanie Stałe powiadomienie pokazujące, że Vector jest uruchomiony. diff --git a/manager/src/main/res/values-pt-rBR/strings.xml b/manager/src/main/res/values-pt-rBR/strings.xml index 3977bb60ae..aacb7e8c81 100644 --- a/manager/src/main/res/values-pt-rBR/strings.xml +++ b/manager/src/main/res/values-pt-rBR/strings.xml @@ -42,6 +42,7 @@ Carregada Não carregada Injetado + Montado Não injetado Notificação de status Uma notificação permanente mostrando que o Vector está em execução. diff --git a/manager/src/main/res/values-ru/strings.xml b/manager/src/main/res/values-ru/strings.xml index c0c7f53709..d58ec83e11 100644 --- a/manager/src/main/res/values-ru/strings.xml +++ b/manager/src/main/res/values-ru/strings.xml @@ -38,6 +38,7 @@ Загружена Не загружена Внедрён + Смонтировано Не внедрён Уведомление о состоянии Постоянное уведомление о том, что Vector работает. diff --git a/manager/src/main/res/values-tr/strings.xml b/manager/src/main/res/values-tr/strings.xml index 701a438e64..2c7f586531 100644 --- a/manager/src/main/res/values-tr/strings.xml +++ b/manager/src/main/res/values-tr/strings.xml @@ -42,6 +42,7 @@ Yüklendi Yüklenmedi Enjekte edildi + Bağlandı Enjekte edilmedi Durum bildirimi Vector\'ün çalıştığını gösteren kalıcı bir bildirim. diff --git a/manager/src/main/res/values-uk/strings.xml b/manager/src/main/res/values-uk/strings.xml index 29cb7052f0..8b92023e1e 100644 --- a/manager/src/main/res/values-uk/strings.xml +++ b/manager/src/main/res/values-uk/strings.xml @@ -42,6 +42,7 @@ Завантажено Не завантажено Впроваджено + Змонтовано Не впроваджено Сповіщення про стан Постійне сповіщення, яке показує, що Vector працює. diff --git a/manager/src/main/res/values-vi/strings.xml b/manager/src/main/res/values-vi/strings.xml index c592b94e78..19e0611e16 100644 --- a/manager/src/main/res/values-vi/strings.xml +++ b/manager/src/main/res/values-vi/strings.xml @@ -42,6 +42,7 @@ Đã nạp Chưa nạp Đã tiêm + Đã gắn kết Chưa tiêm Thông báo trạng thái Một thông báo thường trực cho biết Vector đang chạy. diff --git a/manager/src/main/res/values-zh-rCN/strings.xml b/manager/src/main/res/values-zh-rCN/strings.xml index 5068e3cf5a..75fafdb191 100644 --- a/manager/src/main/res/values-zh-rCN/strings.xml +++ b/manager/src/main/res/values-zh-rCN/strings.xml @@ -42,6 +42,7 @@ 已加载 未加载 已注入 + 已挂载 未注入 状态通知 一条常驻通知,用于显示 Vector 正在运行。 diff --git a/manager/src/main/res/values-zh-rTW/strings.xml b/manager/src/main/res/values-zh-rTW/strings.xml index 76a243e73c..3db47cd386 100644 --- a/manager/src/main/res/values-zh-rTW/strings.xml +++ b/manager/src/main/res/values-zh-rTW/strings.xml @@ -42,6 +42,7 @@ 已載入 未載入 已注入 + 已掛載 未注入 狀態通知 一則常駐通知,用於顯示 Vector 正在執行。 diff --git a/manager/src/main/res/values/strings.xml b/manager/src/main/res/values/strings.xml index ec54516e14..ad875439ce 100644 --- a/manager/src/main/res/values/strings.xml +++ b/manager/src/main/res/values/strings.xml @@ -87,6 +87,7 @@ Loaded Not loaded Injected + Mounted Not injected Status notification A persistent notification showing that Vector is running. diff --git a/services/manager-service/src/main/aidl/org/matrix/vector/ipc/IManagerService.aidl b/services/manager-service/src/main/aidl/org/matrix/vector/ipc/IManagerService.aidl index 19319ba12a..b5b9c888d5 100644 --- a/services/manager-service/src/main/aidl/org/matrix/vector/ipc/IManagerService.aidl +++ b/services/manager-service/src/main/aidl/org/matrix/vector/ipc/IManagerService.aidl @@ -239,6 +239,8 @@ interface IManagerService { */ const int DEX2OAT_SEPOLICY_INCORRECT = 4; + const int DEX2OAT_INJECTED = 5; + // ---- module configuration --------------------------------------------------------------------- /** diff --git a/zygisk/module/customize.sh b/zygisk/module/customize.sh index 049ca7b8e9..616f8b0233 100644 --- a/zygisk/module/customize.sh +++ b/zygisk/module/customize.sh @@ -86,6 +86,14 @@ for file in module.prop action.sh service.sh uninstall.sh sepolicy.rule framewor extract "$ZIPFILE" "$file" "$MODPATH" done +if [ "$IS64BIT" = true ]; then + extract "$ZIPFILE" "zn_modules.$ABI64.txt" "$MODPATH" + mv "$MODPATH/zn_modules.$ABI64.txt" "$MODPATH/zn_modules.txt" +else + extract "$ZIPFILE" "zn_modules.$ABI32.txt" "$MODPATH" + mv "$MODPATH/zn_modules.$ABI32.txt" "$MODPATH/zn_modules.txt" +fi + ui_print "- Extracting Zygisk libraries" mkdir -p "$MODPATH/zygisk" diff --git a/zygisk/module/sepolicy.rule b/zygisk/module/sepolicy.rule index 8c96645da4..f814a3df60 100644 --- a/zygisk/module/sepolicy.rule +++ b/zygisk/module/sepolicy.rule @@ -5,14 +5,19 @@ allow shell shell dir write type xposed_file file_type typeattribute xposed_file mlstrustedobject -allow {dex2oat installd isolated_app shell} xposed_file {file dir} * +allow {dex2oat installd isolated_app shell artd} xposed_file {file dir} * allow dex2oat {unlabeled tmpfs} file * allow zygote dex2oat unix_stream_socket * +allow artd dex2oat process2 nosuid_transition +allow installd dex2oat process2 nosuid_transition + +allow artd artd process execmem +allow installd installd process execmem + type xposed_data file_type typeattribute xposed_data mlstrustedobject allow * xposed_data {file dir} * -# `serial_vector` is in no service_contexts, so it is labelled default_android_service allow system_server default_android_service service_manager find diff --git a/zygisk/module/zn_modules.arm64-v8a.txt b/zygisk/module/zn_modules.arm64-v8a.txt new file mode 100644 index 0000000000..5824d0d531 --- /dev/null +++ b/zygisk/module/zn_modules.arm64-v8a.txt @@ -0,0 +1,2 @@ +name=artd companion zygisk/arm64-v8a.so +name=installd companion zygisk/arm64-v8a.so diff --git a/zygisk/module/zn_modules.armeabi-v7a.txt b/zygisk/module/zn_modules.armeabi-v7a.txt new file mode 100644 index 0000000000..7ac1cbe885 --- /dev/null +++ b/zygisk/module/zn_modules.armeabi-v7a.txt @@ -0,0 +1,2 @@ +name=artd companion zygisk/armeabi-v7a.so +name=installd companion zygisk/armeabi-v7a.so diff --git a/zygisk/module/zn_modules.x86.txt b/zygisk/module/zn_modules.x86.txt new file mode 100644 index 0000000000..f2d71b187e --- /dev/null +++ b/zygisk/module/zn_modules.x86.txt @@ -0,0 +1,2 @@ +name=artd companion zygisk/x86.so +name=installd companion zygisk/x86.so diff --git a/zygisk/module/zn_modules.x86_64.txt b/zygisk/module/zn_modules.x86_64.txt new file mode 100644 index 0000000000..c66cb54031 --- /dev/null +++ b/zygisk/module/zn_modules.x86_64.txt @@ -0,0 +1,2 @@ +name=artd companion zygisk/x86_64.so +name=installd companion zygisk/x86_64.so diff --git a/zygisk/src/main/cpp/CMakeLists.txt b/zygisk/src/main/cpp/CMakeLists.txt index 52a4794faa..59964852b3 100644 --- a/zygisk/src/main/cpp/CMakeLists.txt +++ b/zygisk/src/main/cpp/CMakeLists.txt @@ -7,7 +7,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) add_subdirectory(${VECTOR_ROOT}/native native) -add_library(${PROJECT_NAME} SHARED module.cpp ipc_bridge.cpp) +add_library(${PROJECT_NAME} SHARED module.cpp ipc_bridge.cpp dex2oat_inject.cpp) target_include_directories(${PROJECT_NAME} PUBLIC include) target_link_libraries(${PROJECT_NAME} native log) diff --git a/zygisk/src/main/cpp/dex2oat_inject.cpp b/zygisk/src/main/cpp/dex2oat_inject.cpp new file mode 100644 index 0000000000..de13e7f5a6 --- /dev/null +++ b/zygisk/src/main/cpp/dex2oat_inject.cpp @@ -0,0 +1,639 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "zygisk_next_api.h" + +extern "C" char **environ; + +namespace vector::native::dex2oat { + +namespace { + +constexpr const char *kHooker32 = "/data/adb/modules/zygisk_vector/bin/liboat_hook32.so"; +constexpr const char *kHooker64 = "/data/adb/modules/zygisk_vector/bin/liboat_hook64.so"; +constexpr const char *kWrapper32 = "/data/adb/modules/zygisk_vector/bin/dex2oat32"; +constexpr const char *kWrapper64 = "/data/adb/modules/zygisk_vector/bin/dex2oat64"; +constexpr const char *kMonitorDir = "/data/adb/lspd"; +constexpr const char *kMonitorPrefix = "dex2oat_monitor_"; +constexpr const char *kPreloadPrefix = "LD_PRELOAD="; +constexpr const char *kCmdPrefix = "DEX2OAT_CMD="; +constexpr const char *kKeepFdsPrefix = "--keep-fds="; +constexpr char kRequestMagic = 'V'; + +constexpr size_t kMaxArgv = 256; +constexpr size_t kMaxEnvp = 256; + +using ExecveFn = int (*)(const char *, char *const[], char *const[]); +using ExecvFn = int (*)(const char *, char *const[]); +using PosixSpawnFn = int (*)(pid_t *, const char *, const posix_spawn_file_actions_t *, + const posix_spawnattr_t *, char *const[], char *const[]); + +const ZygiskNextAPI *g_api = nullptr; + +ExecveFn g_orig_execve = nullptr; +ExecvFn g_orig_execv = nullptr; +PosixSpawnFn g_orig_posix_spawn = nullptr; +PosixSpawnFn g_orig_posix_spawnp = nullptr; + +std::atomic g_hook_fd32{-1}; +std::atomic g_hook_fd64{-1}; +std::atomic g_companion_status{0}; +std::atomic g_monitor_started{false}; +std::atomic g_spawn_depth{0}; +std::string g_monitor_name; + +struct TransformBuffers { + char *argv[kMaxArgv + 2]; + char *envp[kMaxEnvp + 3]; + char preload[64]; + char preload_env[96]; + char keepfds[128]; + char env_preload[192]; + char env_cmd[4300]; + char env_injected[40]; + char cmd[4200]; +}; + +bool WriteAll(int fd, const void *data, size_t size) { + const char *p = static_cast(data); + size_t written = 0; + while (written < size) { + ssize_t n = write(fd, p + written, size - written); + if (n <= 0) return false; + written += static_cast(n); + } + return true; +} + +bool ReadAll(int fd, void *data, size_t size) { + char *p = static_cast(data); + size_t done = 0; + while (done < size) { + ssize_t n = read(fd, p + done, size - done); + if (n <= 0) return false; + done += static_cast(n); + } + return true; +} + +bool SendFds(int fd, uint8_t mask, const int *fds, int count) { + char byte = static_cast(mask); + struct iovec iov {}; + iov.iov_base = &byte; + iov.iov_len = 1; + + char control[CMSG_SPACE(sizeof(int) * 2)] {}; + struct msghdr msg {}; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + if (count > 0) { + msg.msg_control = control; + msg.msg_controllen = CMSG_SPACE(sizeof(int) * count); + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int) * count); + memcpy(CMSG_DATA(cmsg), fds, sizeof(int) * count); + } + + return sendmsg(fd, &msg, 0) == 1; +} + +bool ReceiveFds(int fd, uint8_t &mask, int fds[2]) { + char byte = 0; + struct iovec iov {}; + iov.iov_base = &byte; + iov.iov_len = 1; + + char control[CMSG_SPACE(sizeof(int) * 2)] {}; + struct msghdr msg {}; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = control; + msg.msg_controllen = sizeof(control); + + if (recvmsg(fd, &msg, 0) != 1) return false; + + mask = static_cast(byte); + int count = ((mask & 1) ? 1 : 0) + ((mask & 2) ? 1 : 0); + if (count == 0) return true; + + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + if (cmsg == nullptr || cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS) { + return false; + } + memcpy(fds, CMSG_DATA(cmsg), sizeof(int) * count); + return true; +} + +std::string ProcessName() { + char buffer[256] {}; + int fd = open("/proc/self/cmdline", O_RDONLY | O_CLOEXEC); + if (fd >= 0) { + ssize_t n = read(fd, buffer, sizeof(buffer) - 1); + close(fd); + if (n > 0) { + buffer[n] = '\0'; + std::string name(buffer); + auto nul = name.find('\0'); + if (nul != std::string::npos) name.resize(nul); + auto slash = name.rfind('/'); + if (slash != std::string::npos) name.erase(0, slash + 1); + if (!name.empty()) return name; + } + } + + fd = open("/proc/self/comm", O_RDONLY | O_CLOEXEC); + if (fd >= 0) { + ssize_t n = read(fd, buffer, sizeof(buffer) - 1); + close(fd); + if (n > 0) { + buffer[n] = '\0'; + std::string name(buffer); + while (!name.empty() && (name.back() == '\n' || name.back() == '\r')) name.pop_back(); + if (!name.empty()) return name; + } + } + return "unknown"; +} + +std::string SafeName(const std::string &name) { + std::string out; + for (char c : name) { + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || + c == '-' || c == '.') { + out.push_back(c); + } + } + if (out.empty() || out == "." || out == "..") return "unknown"; + return out; +} + +bool IsDex2Oat(const char *path, char *const argv[]) { + if (path != nullptr && strstr(path, "dex2oat") != nullptr) return true; + return argv != nullptr && argv[0] != nullptr && strstr(argv[0], "dex2oat") != nullptr; +} + +bool IsArtExec(const char *path, char *const argv[]) { + if (path != nullptr && strstr(path, "art_exec") != nullptr) return true; + return argv != nullptr && argv[0] != nullptr && strstr(argv[0], "art_exec") != nullptr; +} + +bool Is64BitExecutable(const char *path, char *const argv[]) { + const char *candidate = path; + if (candidate == nullptr && argv != nullptr) candidate = argv[0]; + if (candidate != nullptr) { + int fd = open(candidate, O_RDONLY | O_CLOEXEC); + if (fd >= 0) { + unsigned char header[5] {}; + ssize_t n = read(fd, header, sizeof(header)); + close(fd); + if (n == 5 && header[0] == 0x7f && header[1] == 'E' && header[2] == 'L' && + header[3] == 'F') { + return header[4] == 2; + } + } + if (strstr(candidate, "64") != nullptr) return true; + if (strstr(candidate, "32") != nullptr) return false; + } + return sizeof(void *) == 8; +} + +int HookFdFor(bool is64) { + return (is64 ? g_hook_fd64 : g_hook_fd32).load(std::memory_order_relaxed); +} + +bool WrapperMounted(const char *path, bool is64) { + if (path == nullptr) return false; + struct stat target {}; + struct stat wrapper {}; + if (stat(path, &target) != 0) return false; + if (stat(is64 ? kWrapper64 : kWrapper32, &wrapper) != 0) return false; + return target.st_dev == wrapper.st_dev && target.st_ino == wrapper.st_ino; +} + +bool Build(const char *path, char *const argv_in[], char *const env_in[], int fd, + TransformBuffers &buffers) { + if (argv_in == nullptr) return false; + + size_t argc = 0; + while (argv_in[argc] != nullptr) { + if (argc >= kMaxArgv) return false; + buffers.argv[argc] = argv_in[argc]; + argc++; + } + buffers.argv[argc] = nullptr; + + const char *cmd = nullptr; + if (path != nullptr && path[0] == '/') { + cmd = path; + } else if (argv_in[0] != nullptr && argv_in[0][0] == '/') { + cmd = argv_in[0]; + } else { + cmd = path; + } + + snprintf(buffers.preload, sizeof(buffers.preload), "/proc/self/fd/%d", fd); + snprintf(buffers.preload_env, sizeof(buffers.preload_env), "%s%s", kPreloadPrefix, + buffers.preload); + snprintf(buffers.cmd, sizeof(buffers.cmd), "%s%s", kCmdPrefix, cmd != nullptr ? cmd : ""); + + size_t envc = 0; + char *const *env = env_in != nullptr ? env_in : environ; + for (size_t i = 0; env != nullptr && env[i] != nullptr; i++) { + if (strncmp(env[i], kPreloadPrefix, strlen(kPreloadPrefix)) == 0) continue; + if (strncmp(env[i], kCmdPrefix, strlen(kCmdPrefix)) == 0) continue; + if (envc >= kMaxEnvp) return false; + buffers.envp[envc++] = env[i]; + } + buffers.envp[envc++] = buffers.preload_env; + buffers.envp[envc++] = buffers.cmd; + buffers.envp[envc++] = const_cast("VECTOR_INJECTED=1"); + buffers.envp[envc] = nullptr; + return true; +} + +bool BuildArtExec(const char *path, char *const argv_in[], char *const env_in[], + TransformBuffers &buffers) { + (void)path; + size_t argc = 0; + size_t separator = SIZE_MAX; + size_t keep_index = SIZE_MAX; + while (argv_in[argc] != nullptr) { + if (argc >= kMaxArgv) return false; + if (separator == SIZE_MAX && strcmp(argv_in[argc], "--") == 0) separator = argc; + if (separator == SIZE_MAX && + strncmp(argv_in[argc], kKeepFdsPrefix, strlen(kKeepFdsPrefix)) == 0) { + keep_index = argc; + } + argc++; + } + if (separator == SIZE_MAX || separator + 1 >= argc) return false; + + const char *target = argv_in[separator + 1]; + if (target == nullptr || strstr(target, "dex2oat") == nullptr) return false; + bool is64 = Is64BitExecutable(target, nullptr); + if (WrapperMounted(target, is64)) return false; + int fd = HookFdFor(is64); + if (fd < 0) return false; + + if (keep_index != SIZE_MAX) { + const char *old = argv_in[keep_index] + strlen(kKeepFdsPrefix); + if (*old == '\0') { + snprintf(buffers.keepfds, sizeof(buffers.keepfds), "%s%d", kKeepFdsPrefix, fd); + } else { + snprintf(buffers.keepfds, sizeof(buffers.keepfds), "%s%s:%d", kKeepFdsPrefix, old, fd); + } + } else { + snprintf(buffers.keepfds, sizeof(buffers.keepfds), "%s%d", kKeepFdsPrefix, fd); + } + + snprintf(buffers.preload, sizeof(buffers.preload), "/proc/self/fd/%d", fd); + snprintf(buffers.preload_env, sizeof(buffers.preload_env), "%s%s", kPreloadPrefix, + buffers.preload); + snprintf(buffers.cmd, sizeof(buffers.cmd), "%s%s", kCmdPrefix, target); + snprintf(buffers.env_preload, sizeof(buffers.env_preload), "--env=%s", buffers.preload_env); + snprintf(buffers.env_cmd, sizeof(buffers.env_cmd), "--env=%s", buffers.cmd); + snprintf(buffers.env_injected, sizeof(buffers.env_injected), "--env=VECTOR_INJECTED=1"); + + size_t out = 0; + for (size_t i = 0; i < argc; i++) { + if (i == separator) { + if (keep_index == SIZE_MAX) { + if (out + 5 >= kMaxArgv) return false; + buffers.argv[out++] = buffers.keepfds; + } + if (out + 5 >= kMaxArgv) return false; + buffers.argv[out++] = buffers.env_preload; + buffers.argv[out++] = buffers.env_cmd; + buffers.argv[out++] = buffers.env_injected; + } + if (i == keep_index) { + buffers.argv[out++] = buffers.keepfds; + continue; + } + buffers.argv[out++] = argv_in[i]; + } + buffers.argv[out] = nullptr; + + size_t envc = 0; + char *const *env = env_in != nullptr ? env_in : environ; + for (size_t i = 0; env != nullptr && env[i] != nullptr; i++) { + if (strncmp(env[i], kPreloadPrefix, strlen(kPreloadPrefix)) == 0) continue; + if (strncmp(env[i], kCmdPrefix, strlen(kCmdPrefix)) == 0) continue; + if (strncmp(env[i], "VECTOR_INJECTED=", 16) == 0) continue; + if (envc >= kMaxEnvp) return false; + buffers.envp[envc++] = env[i]; + } + buffers.envp[envc] = nullptr; + return true; +} + +bool Transform(const char *path, char *const argv_in[], char *const env_in[], + TransformBuffers &buffers) { + if (IsDex2Oat(path, argv_in)) { + bool is64 = Is64BitExecutable(path, argv_in); + if (WrapperMounted(path, is64)) return false; + int fd = HookFdFor(is64); + if (fd < 0) return false; + return Build(path, argv_in, env_in, fd, buffers); + } + if (IsArtExec(path, argv_in)) return BuildArtExec(path, argv_in, env_in, buffers); + return false; +} + +int HookExecve(const char *path, char *const argv[], char *const envp[]) { + if (g_orig_execve == nullptr) return -1; + if (g_spawn_depth.load(std::memory_order_relaxed) > 0) { + return g_orig_execve(path, argv, envp); + } + if (!IsDex2Oat(path, argv) && !IsArtExec(path, argv)) { + return g_orig_execve(path, argv, envp); + } + TransformBuffers buffers; + if (!Transform(path, argv, envp, buffers)) return g_orig_execve(path, argv, envp); + return g_orig_execve(path, buffers.argv, buffers.envp); +} + +int HookExecv(const char *path, char *const argv[]) { + if (g_orig_execv == nullptr) return -1; + if (g_spawn_depth.load(std::memory_order_relaxed) > 0) return g_orig_execv(path, argv); + if (!IsDex2Oat(path, argv) && !IsArtExec(path, argv)) return g_orig_execv(path, argv); + TransformBuffers buffers; + if (!Transform(path, argv, nullptr, buffers)) return g_orig_execv(path, argv); + if (g_orig_execve != nullptr) return g_orig_execve(path, buffers.argv, buffers.envp); + char **saved = environ; + environ = buffers.envp; + int result = g_orig_execv(path, buffers.argv); + environ = saved; + return result; +} + +int HookPosixSpawn(pid_t *pid, const char *path, const posix_spawn_file_actions_t *actions, + const posix_spawnattr_t *attr, char *const argv[], char *const envp[]) { + if (g_orig_posix_spawn == nullptr) return -1; + if (g_spawn_depth.load(std::memory_order_relaxed) > 0) { + return g_orig_posix_spawn(pid, path, actions, attr, argv, envp); + } + if (!IsDex2Oat(path, argv) && !IsArtExec(path, argv)) { + return g_orig_posix_spawn(pid, path, actions, attr, argv, envp); + } + TransformBuffers buffers; + if (!Transform(path, argv, envp, buffers)) { + return g_orig_posix_spawn(pid, path, actions, attr, argv, envp); + } + g_spawn_depth.fetch_add(1, std::memory_order_relaxed); + int result = g_orig_posix_spawn(pid, path, actions, attr, buffers.argv, buffers.envp); + g_spawn_depth.fetch_sub(1, std::memory_order_relaxed); + return result; +} + +int HookPosixSpawnp(pid_t *pid, const char *path, const posix_spawn_file_actions_t *actions, + const posix_spawnattr_t *attr, char *const argv[], char *const envp[]) { + if (g_orig_posix_spawnp == nullptr) return -1; + if (g_spawn_depth.load(std::memory_order_relaxed) > 0) { + return g_orig_posix_spawnp(pid, path, actions, attr, argv, envp); + } + if (!IsDex2Oat(path, argv) && !IsArtExec(path, argv)) { + return g_orig_posix_spawnp(pid, path, actions, attr, argv, envp); + } + TransformBuffers buffers; + if (!Transform(path, argv, envp, buffers)) { + return g_orig_posix_spawnp(pid, path, actions, attr, argv, envp); + } + g_spawn_depth.fetch_add(1, std::memory_order_relaxed); + int result = g_orig_posix_spawnp(pid, path, actions, attr, buffers.argv, buffers.envp); + g_spawn_depth.fetch_sub(1, std::memory_order_relaxed); + return result; +} + +template +bool InstallAt(void *target, T replacement, T *original) { + if (target == nullptr) return false; + + void *backup = nullptr; + int result = ZN_FAILED; + if (g_api != nullptr && g_api->inlineHook != nullptr) { + result = g_api->inlineHook(target, reinterpret_cast(replacement), &backup); + } + if (result != ZN_SUCCESS) { + if (HookInline(target, reinterpret_cast(replacement), &backup) != 0) return false; + } + *original = reinterpret_cast(backup); + return true; +} + +bool InstallHooks() { + void *execve_target = dlsym(RTLD_DEFAULT, "execve"); + void *execv_target = dlsym(RTLD_DEFAULT, "execv"); + void *spawn_target = dlsym(RTLD_DEFAULT, "posix_spawn"); + void *spawnp_target = dlsym(RTLD_DEFAULT, "posix_spawnp"); + + bool execve = InstallAt(execve_target, HookExecve, &g_orig_execve); + if (execv_target != execve_target) InstallAt(execv_target, HookExecv, &g_orig_execv); + bool spawn = InstallAt(spawn_target, HookPosixSpawn, &g_orig_posix_spawn); + bool spawnp = spawnp_target != spawn_target && + InstallAt(spawnp_target, HookPosixSpawnp, &g_orig_posix_spawnp); + return execve && (spawn || spawnp); +} + +bool AcquireHookFds(void *self_handle, int &fd32, int &fd64) { + if (g_api == nullptr || g_api->connectCompanion == nullptr) return false; + + int socket = g_api->connectCompanion(self_handle); + if (socket < 0) return false; + + struct timeval timeout {}; + timeout.tv_sec = 2; + setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); + setsockopt(socket, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)); + + std::string name = SafeName(ProcessName()); + unsigned char header[3] = {static_cast(kRequestMagic), + static_cast(sizeof(void *) == 8 ? 1 : 0), + static_cast(name.size())}; + if (!WriteAll(socket, header, sizeof(header)) || + !WriteAll(socket, name.data(), name.size())) { + close(socket); + return false; + } + + uint8_t mask = 0; + int fds[2] = {-1, -1}; + if (!ReceiveFds(socket, mask, fds)) { + close(socket); + return false; + } + + int index = 0; + fd32 = (mask & 1) ? fds[index++] : -1; + fd64 = (mask & 2) ? fds[index++] : -1; + if (fd32 >= 0) fcntl(fd32, F_SETFD, 0); + if (fd64 >= 0) fcntl(fd64, F_SETFD, 0); + + bool usable = fd32 >= 0 || fd64 >= 0; + if (usable) usable = InstallHooks(); + char status = usable ? 1 : 0; + WriteAll(socket, &status, 1); + close(socket); + + if (!usable) { + if (fd32 >= 0) close(fd32); + if (fd64 >= 0) close(fd64); + fd32 = -1; + fd64 = -1; + } + return usable; +} + +void *ServeMonitor(void *) { + std::string path = std::string(kMonitorDir) + "/" + kMonitorPrefix + g_monitor_name; + unlink(path.c_str()); + + int listener = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); + if (listener < 0) { + LOGE("dex2oat injection: cannot create monitor socket: {}", strerror(errno)); + return nullptr; + } + + struct sockaddr_un address {}; + address.sun_family = AF_UNIX; + strncpy(address.sun_path, path.c_str(), sizeof(address.sun_path) - 1); + if (bind(listener, reinterpret_cast(&address), sizeof(address)) != 0 || + listen(listener, 8) != 0) { + LOGE("dex2oat injection: cannot bind {}: {}", path.c_str(), strerror(errno)); + close(listener); + return nullptr; + } + chmod(path.c_str(), 0600); + LOGI("dex2oat injection: reporting status on {}", path.c_str()); + + for (;;) { + int client = accept(listener, nullptr, nullptr); + if (client < 0) { + if (errno == EINTR) continue; + break; + } + char status = g_companion_status.load(); + if (write(client, &status, 1) != 1) { + LOGD("dex2oat injection: a monitor client left early"); + } + close(client); + } + close(listener); + return nullptr; +} + +void StartMonitor(const std::string &name, char status) { + g_companion_status.store(status); + if (g_monitor_started.exchange(true)) return; + g_monitor_name = SafeName(name); + mkdir(kMonitorDir, 0700); + pthread_t thread; + if (pthread_create(&thread, nullptr, ServeMonitor, nullptr) != 0) { + LOGE("dex2oat injection: cannot start the monitor thread"); + return; + } + pthread_detach(thread); +} + +} + +void OnModuleLoaded(void *self_handle, const ZygiskNextAPI *api) { + g_api = api; + if (api == nullptr) return; + if (android_get_device_api_level() < 29) return; + + int fd32 = -1; + int fd64 = -1; + if (!AcquireHookFds(self_handle, fd32, fd64)) { + LOGW("dex2oat injection: no usable hook library; leaving dex2oat alone"); + return; + } + + g_hook_fd32.store(fd32, std::memory_order_relaxed); + g_hook_fd64.store(fd64, std::memory_order_relaxed); + LOGI("dex2oat injection: armed in {}", ProcessName().c_str()); +} + +void OnCompanionLoaded() { + LOGI("dex2oat injection: companion loaded in pid {}", static_cast(getpid())); +} + +void OnModuleConnected(int fd) { + struct timeval timeout {}; + timeout.tv_sec = 2; + setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); + + unsigned char header[3] {}; + if (!ReadAll(fd, header, sizeof(header)) || header[0] != kRequestMagic) { + close(fd); + return; + } + + uint8_t name_length = header[2]; + std::string name(name_length, '\0'); + if (name_length > 0 && !ReadAll(fd, name.data(), name_length)) { + close(fd); + return; + } + + int fds[2] = {-1, -1}; + uint8_t mask = 0; + int count = 0; + + int fd32 = open(kHooker32, O_RDONLY | O_CLOEXEC); + if (fd32 >= 0) { + mask |= 1; + fds[count++] = fd32; + } + int fd64 = open(kHooker64, O_RDONLY | O_CLOEXEC); + if (fd64 >= 0) { + mask |= 2; + fds[count++] = fd64; + } + + bool sent = SendFds(fd, mask, fds, count); + if (fd32 >= 0) close(fd32); + if (fd64 >= 0) close(fd64); + if (!sent) { + close(fd); + return; + } + + char status = 0; + ReadAll(fd, &status, 1); + char effective = (status == 1 && count > 0) ? 1 : 0; + StartMonitor(name, effective); + close(fd); +} + +} + +extern "C" __attribute__((visibility("default"))) ZygiskNextModule zn_module = { + ZYGISK_NEXT_API_VERSION, vector::native::dex2oat::OnModuleLoaded}; + +extern "C" __attribute__((visibility("default"))) ZygiskNextCompanionModule zn_companion_module = { + ZYGISK_NEXT_API_VERSION, vector::native::dex2oat::OnCompanionLoaded, + vector::native::dex2oat::OnModuleConnected}; diff --git a/zygisk/src/main/cpp/include/zygisk_next_api.h b/zygisk/src/main/cpp/include/zygisk_next_api.h new file mode 100644 index 0000000000..7e04897bef --- /dev/null +++ b/zygisk/src/main/cpp/include/zygisk_next_api.h @@ -0,0 +1,162 @@ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define ZYGISK_NEXT_API_VERSION 4 +#define ZYGISK_NEXT_HYOS_API_VERSION 1 + +#define ZN_SUCCESS 0 +#define ZN_FAILED 1 + +struct ZnSymbolResolver; +struct ZygiskNextRuntime; + +struct ZygiskNextAPI { + // Hook API + + // Do plt hook at symbol specified by the param `symbol` of library + // specified by the param `base_addr` The plt address of `symbol` in the + // library will be replaced with hook_handler, and its original value will + // be put to the address specified by `original` (can be null). You can use + // this api to do caller-oriented hook If you want to unhook, please call + // this function with hook_handler = original If hook succeed, returns + // ZN_SUCCESS, otherwise ZN_FAILED + int (*pltHook)(void* base_addr, const char* symbol, void* hook_handler, + void** original); + + // Do inline hook at the address specified by `target`, replace it with a + // new function specified by `addr`, and the param `original` receives the + // address of original function. You can use this api to achieve a global + // hook in current process. In the current implementation , an address can + // only hook once, so the module can't hook an address which is already + // hooked by an another module, except that the module unhooked it. If + // hooking succeed, returns ZN_SUCCESS, otherwise ZN_FAILED + int (*inlineHook)(void* target, void* addr, void** original); + + // Unhook the address which is formerly hooked. + // If hook succeed, returns ZN_SUCCESS, otherwise ZN_FAILED + int (*inlineUnhook)(void* target); + + // Symbol Resolver API + + // Obtain a new ZnSymbolResolver object + // `path` is required, which specifies the path of library to resolve. It + // can be an absolute path or just the file name of library, e.g. + // /system/lib64/libc.so or libc.so . If `base_addr` is non-zero, it will be + // used as the base address of the library. Otherwise, Zygisk Next will try + // to find out the base address of the specified library in this process. If + // succeed, it returns a valid pointer to the symbol resolver, otherwise + // nullptr is returned. + struct ZnSymbolResolver* (*newSymbolResolver)(const char* path, + void* base_addr); + + // Release the ZnSymbolResolver object pointed by `resolver`. + void (*freeSymbolResolver)(struct ZnSymbolResolver* resolver); + + // Retrieve the base address of the library of the resolver image in the + // process. + void* (*getBaseAddress)(struct ZnSymbolResolver* resolver); + + // Lookup the address of symbol by name or prefix (if `prefix` is true) + // If the symbol exists, the function returns its address, otherwise returns + // nullptr. If `size` is not nullptr, the size of the symbol will be put to + // *size . In the current implementation, gnu_debugdata resolution is + // supported. + void* (*symbolLookup)(struct ZnSymbolResolver* resolver, const char* name, + bool prefix, size_t* size); + + // Walk through the symbol table of the library, the callback will receive + // the name, the address, and the size of each symbol. Returning false in + // the callback means stop the walking. + void (*forEachSymbols)(struct ZnSymbolResolver* resolver, + bool (*callback)(const char* name, void* addr, + size_t size, void* data), + void* data); + + // Companion API + + // Create a unix sock stream connection to your declared companion process. + // The value of `handle` is the `self_handle` which you've received from + // onModuleLoaded. On success, it returns the file descriptor refer to the + // socket, otherwise -1 is returned. Please close this file descriptor by + // yourself. + int (*connectCompanion)(void* handle); + + // Return the runtime-specific API for the current process, or null if the + // process does not expose a supported runtime. The returned object remains + // valid for the lifetime of the process. Inspect its type before passing a + // runtime-specific module structure to registerModule. + const struct ZygiskNextRuntime* (*getRuntime)(void); +}; + +// Callbacks of an injected library +struct ZygiskNextModule { + // Please fill this with the target version of your module, e.g. + // ZYGISK_NEXT_API_VERSION + int target_api_version; + + // This callback will be called after all needed library of the main + // executable are loaded, and before the entry (i.e. `main`) of the main + // executable is called. + void (*onModuleLoaded)(void* self_handle, const struct ZygiskNextAPI* api); +}; + +enum ZygiskNextRuntimeType { + ZN_RUNTIME_HYOS = 1, +}; + +// Read-only process specialization information for applications forked by +// /system_ext/bin/hyos_spawner. The callback runs after the uid, gid, +// supplementary groups, and SELinux app context have been applied. The string +// pointers are non-null and only valid for the duration of the callback. +struct ZnHyosAppSpecializeArgs { + const char* process_name; + const char* package_name; + const char* se_info; +}; + +// HyperOS Rust Runtime callbacks registered through +// ZygiskNextRuntime.registerModule. +struct ZygiskNextHyosModule { + int target_api_version; + void (*onAppSpecialized)(const struct ZnHyosAppSpecializeArgs* args); +}; + +// Runtime-specific API returned by getRuntime. The module structure accepted +// by registerModule is selected by type. The runtime copies the structure +// before registerModule returns. +struct ZygiskNextRuntime { + enum ZygiskNextRuntimeType type; + int api_version; + int (*registerModule)(const void* module); +}; + +// Callbacks of a companion library +struct ZygiskNextCompanionModule { + int target_api_version; + + void (*onCompanionLoaded)(); + + // This callback will be called when your Zygisk Next module is trying to + // establish a connection with your companion module, i.e. + // `connectCompanion` is called. The `fd` param will be a unix sock stream + // file descriptor. Please close this file descriptor after use by yourself. + void (*onModuleConnected)(int fd); +}; + +// Please define your `zn_module` in your source file. +extern __attribute__((visibility("default"), + unused)) struct ZygiskNextModule zn_module; +extern __attribute__(( + visibility("default"), + unused)) struct ZygiskNextCompanionModule zn_companion_module; + +#ifdef __cplusplus +} +#endif