Skip to content

Commit d9503fe

Browse files
committed
minor improvements
1 parent c5827d5 commit d9503fe

4 files changed

Lines changed: 25 additions & 26 deletions

File tree

daemon/README.md

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ Unlike the injected framework code, the daemon does not hook methods directly. I
99

1010
The daemon relies on a dual-IPC architecture and extensive use of Android Binder mechanisms to orchestrate the framework lifecycle without triggering SELinux denials or breaking system stability.
1111

12-
1. **Bootstrapping & Bridge (`core/`)**: The daemon starts early in the boot process. It forces its primary Binder (`VectorService`) into `system_server` by hijacking transactions on the Android `activity` service.
13-
2. **Privileged IPC Provider (`ipc/`)**: Android's sandbox prevents target processes from reading the framework APK, accessing SQLite databases, or resolving hidden ART symbols. The daemon exploits its root/system-level permissions to act as an asset server. It provides three critical components to hooked processes over Binder IPC:
14-
* **Framework Loader DEX**: Dispatched via `SharedMemory` to avoid disk-based detection and bypass SELinux `exec` restrictions.
15-
* **Obfuscation Maps**: Dictionaries provided over IPC when API protection is enabled, allowing the injected code to correctly resolve the randomized class names at runtime.
16-
* **Dynamic Module Scopes**: Fast, lock-free lookups of which modules should be loaded into a specific UID/ProcessName.
17-
3. **State Management (`data/`)**: To ensure IPC calls resolve in microseconds without race conditions, the daemon uses an **Immutable State Container** (`DaemonState`). Module topology and scopes are built into a frozen snapshot in the background, which is atomically swapped into memory. High-volume module preference updates are isolated in a separate `PreferenceStore` to prevent state pollution.
18-
4. **Native Environment (`env/` & JNI)**: Background threads (C++ and Kotlin Coroutines) handle low-level system subversion, including `dex2oat` compilation hijacking and logcat monitoring.
12+
1. _Bootstrapping & Bridge (`core/`)_: The daemon starts early in the boot process. It forces its primary Binder (`VectorService`) into `system_server` by hijacking transactions on the Android `activity` service.
13+
2. _Privileged IPC Provider (`ipc/`)_: Android's sandbox prevents target processes from reading the framework APK, accessing SQLite databases, or resolving hidden ART symbols. The daemon exploits its root/system-level permissions to act as an asset server. It provides three critical components to hooked processes over Binder IPC:
14+
* _Framework Loader DEX_: Dispatched via `SharedMemory` to avoid disk-based detection and bypass SELinux `exec` restrictions.
15+
* _Obfuscation Maps_: Dictionaries provided over IPC when API protection is enabled, allowing the injected code to correctly resolve the randomized class names at runtime.
16+
* _Dynamic Module Scopes_: Fast, lock-free lookups of which modules should be loaded into a specific UID/ProcessName.
17+
3. _State Management (`data/`)_: To ensure IPC calls resolve in microseconds without race conditions, the daemon uses an _Immutable State Container_ (`DaemonState`). Module topology and scopes are built into a frozen snapshot in the background, which is atomically swapped into memory. High-volume module preference updates are isolated in a separate `PreferenceStore` to prevent state pollution.
18+
4. _Native Environment (`env/` & JNI)_: Background threads (C++ and Kotlin Coroutines) handle low-level system subversion, including `dex2oat` compilation hijacking and logcat monitoring.
1919

2020
## Directory Layout
2121

@@ -34,30 +34,30 @@ src/main/
3434
## Core Technical Mechanisms
3535

3636
### 1. IPC Routing (The Two Doors)
37-
* **Door 1 (`SystemServerService`)**: A native-to-native entry point used exclusively for the **System-Level Initialization** of `system_server`. By proxying the hardware `serial` service (via `IServiceCallback`), the daemon provides a rendezvous point accessible to the system before the Activity Manager is even initialized. It handles raw UID/PID/Heartbeat packets to authorize the base system framework hook.
38-
* **Door 2 (`VectorService`)**: The **Application-Level Entrance** used by user-space apps. Since user apps are forbidden by SELinux from accessing hardware services like `serial`, they use the "Activity Bridge" to reach the daemon. This door utilizes an action-based protocol allowing the daemon to perform **Scope Filtering**—matching the calling process against the current `DaemonState` before granting access to the framework.
37+
* _Door 1 (`SystemServerService`)_: A native-to-native entry point used exclusively for the _System-Level Initialization_ of `system_server`. By proxying the hardware `serial` service (via `IServiceCallback`), the daemon provides a rendezvous point accessible to the system before the Activity Manager is even initialized. It handles raw UID/PID/Heartbeat packets to authorize the base system framework hook.
38+
* _Door 2 (`VectorService`)_: The _Application-Level Entrance_ used by user-space apps. Since user apps are forbidden by SELinux from accessing hardware services like `serial`, they use the "Activity Bridge" to reach the daemon. This door utilizes an action-based protocol allowing the daemon to perform _Scope Filtering_—matching the calling process against the current `DaemonState` before granting access to the framework.
3939

4040
### 2. AOT Compilation Hijacking (`dex2oat`)
4141
To prevent Android's ART from inlining hooked methods (which makes them unhookable), Vector hijacks the Ahead-of-Time (AOT) compiler.
42-
* **Mechanism**: The daemon (`Dex2OatServer`) mounts a C++ wrapper binary (`bin/dex2oatXX`) over the system's actual `dex2oat` binaries in the `/apex` mount namespace.
43-
* **FD Passing**: When the wrapper executes, to read the original compiler or the `liboat_hook.so`, it opens a UNIX domain socket to the daemon. The daemon (running as root) opens the files and passes the File Descriptors (FDs) back to the wrapper via `SCM_RIGHTS`.
44-
* **Execution**: The wrapper uses `memfd_create` and `sendfile` to load the hook, bypassing execute restrictions, and uses `LD_PRELOAD` to inject the hook into the real `dex2oat` process while appending `--inline-max-code-units=0`.
42+
* _Mechanism_: The daemon (`Dex2OatServer`) mounts a C++ wrapper binary (`bin/dex2oatXX`) over the system's actual `dex2oat` binaries in the `/apex` mount namespace.
43+
* _FD Passing_: When the wrapper executes, to read the original compiler or the `liboat_hook.so`, it opens a UNIX domain socket to the daemon. The daemon (running as root) opens the files and passes the File Descriptors (FDs) back to the wrapper via `SCM_RIGHTS`.
44+
* _Execution_: The wrapper uses `memfd_create` and `sendfile` to load the hook, bypassing execute restrictions, and uses `LD_PRELOAD` to inject the hook into the real `dex2oat` process while appending `--inline-max-code-units=0`.
4545

4646
### 3. API Protection & DEX Obfuscation
4747
To prevent unauthorized apps from detecting the framework or invoking the Xposed API, the daemon randomizes framework and loader class names on each boot. JNI maps the input `SharedMemory` via `MAP_SHARED` to gain direct, zero-copy access to the physical pages populated by Java. Using the [DexBuilder](https://github.com/JingMatrix/DexBuilder) library, the daemon mutates the DEX string pool in-place; this is highly efficient as the library's Intermediate Representation points directly to the mapped buffer, avoiding unnecessary heap allocations during the randomization process.
4848

49-
Once mutation is complete, the finalized DEX is written into a new `SharedMemory` region and the original plaintext handle is closed. Because signatures are now randomized, the daemon provides **Obfuscation Maps** via Door 1 and Door 2. These dictionaries allow the injected code to correctly "re-link" and resolve the framework's internal classes at runtime despite their randomized names.
49+
Once mutation is complete, the finalized DEX is written into a new `SharedMemory` region and the original plaintext handle is closed. Because signatures are now randomized, the daemon provides _Obfuscation Maps_ via Door 1 and Door 2. These dictionaries allow the injected code to correctly "re-link" and resolve the framework's internal classes at runtime despite their randomized names.
5050

51-
### 4. Lifecycle & State Tracking
52-
The daemon must precisely know which apps are installed and which processes are running.
53-
* **Broadcasts**: `VectorService` registers a hidden `IIntentReceiver` to listen for `ACTION_PACKAGE_ADDED`, `REMOVED`, and `ACTION_LOCKED_BOOT_COMPLETED`.
54-
* **UID Observers**: `IUidObserver` tracks `onUidActive` and `onUidGone`. When a process becomes active, the daemon uses a forged `ContentProvider` call (`send_binder`) to proactively push the `IXposedService` binder into the target process, bypassing standard `bindService` limitations.
51+
### 4. Lifecycle & Process Injection
52+
Vector uses a proactive _Push Model_ to distribute the `IXposedService` binder. Upon detecting a process start via `IUidObserver`, the daemon utilizes `getContentProviderExternal` to obtain a direct line to the module's internal provider. It then executes a synchronous `IContentProvider.call()`, passing the control binder within a `Bundle`. This ensures the framework reference is injected into the target process’s memory before its `Application.onCreate()` executes, bypassing the detection and latency associated with standard `bindService` calls.
53+
54+
_Remote Preferences & Files_ are supported by a combination of the injected Binder and custom SELinux types. The daemon stores preferences and shared files in directories labeled `xposed_data`. Because the policy allows global access to this type, the injected binder simply provides the path or File Descriptor, and the target app can perform direct I/O, bypassing standard per-app sandbox restrictions.
5555

5656
## Development & Maintenance Guidelines
5757

5858
When modifying the daemon, strictly adhere to the following principles:
5959

60-
1. **Never Block IPC Threads**: AIDL `onTransact` methods are called synchronously by the Android framework and target apps. Blocking these threads (e.g., by executing raw SQL queries or heavy I/O directly) will cause Application Not Responding (ANR) crashes system-wide. Always read from the lock-free, immutable `DaemonState` snapshot exposed by `ConfigCache.state`.
61-
2. **Resource Determinism**: The daemon runs indefinitely. Leaking a single `Cursor`, `ParcelFileDescriptor`, or `SharedMemory` instance will eventually exhaust system limits and crash the OS. Always use Kotlin's `.use { }` blocks or explicit C++ RAII wrappers for native resources.
62-
3. **Isolate OEM Quirks**: Android OS behavior varies wildly between manufacturers (e.g., Lenovo hiding cloned apps in user IDs 900-909, MIUI killing background dual-apps). Place all OEM-specific logic in `utils/Workarounds.kt` to prevent core logic pollution.
63-
4. **Context Forgery (`FakeContext`)**: The daemon does not have a real Android `Context`. To interact with system APIs that require one (like building Notifications or querying packages), use `FakeContext`. Be aware that standard `Context` methods may crash if not explicitly mocked.
60+
1. _Never Block IPC Threads_: AIDL `onTransact` methods are called synchronously by the Android framework and target apps. Blocking these threads (e.g., by executing raw SQL queries or heavy I/O directly) will cause Application Not Responding (ANR) crashes system-wide. Always read from the lock-free, immutable `DaemonState` snapshot exposed by `ConfigCache.state`.
61+
2. _Resource Determinism_: The daemon runs indefinitely. Leaking a single `Cursor`, `ParcelFileDescriptor`, or `SharedMemory` instance will eventually exhaust system limits and crash the OS. Always use Kotlin's `.use { }` blocks or explicit C++ RAII wrappers for native resources.
62+
3. _Isolate OEM Quirks_: Android OS behavior varies wildly between manufacturers (e.g., Lenovo hiding cloned apps in user IDs 900-909, MIUI killing background dual-apps). Place all OEM-specific logic in `utils/Workarounds.kt` to prevent core logic pollution.
63+
4. _Context Forgery (`FakeContext`)_: The daemon does not have a real Android `Context`. To interact with system APIs that require one (like building Notifications or querying packages), use `FakeContext`. Be aware that standard `Context` methods may crash if not explicitly mocked.

daemon/src/main/kotlin/org/matrix/vector/daemon/ipc/ApplicationService.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ object ApplicationService : ILSPApplicationService.Stub() {
9090
override fun getModulesList(): List<Module> {
9191
val info = ensureRegistered()
9292
if (info.key.uid == Process.SYSTEM_UID && info.processName == "system") {
93-
return ConfigCache.getModulesForSystemServer() // Needs implementation in ConfigCache
93+
return ConfigCache.getModulesForSystemServer()
9494
}
9595
if (ManagerService.isRunningManager(getCallingPid(), info.key.uid)) {
9696
return emptyList()

daemon/src/main/kotlin/org/matrix/vector/daemon/ipc/ModuleService.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import org.matrix.vector.daemon.system.PER_USER_RANGE
2121
import org.matrix.vector.daemon.system.activityManager
2222

2323
private const val TAG = "VectorModuleService"
24-
private const val SEND_BINDER = "send_binder"
2524

2625
class ModuleService(private val loadedModule: Module) : IXposedService.Stub() {
2726

@@ -35,7 +34,7 @@ class ModuleService(private val loadedModule: Module) : IXposedService.Stub() {
3534

3635
fun uidStarts(uid: Int) {
3736
if (uidSet.add(uid)) {
38-
val module = ConfigCache.getModuleByUid(uid) // Needs impl in ConfigCache
37+
val module = ConfigCache.getModuleByUid(uid)
3938
if (module?.file?.legacy == false) {
4039
val service = serviceMap.getOrPut(module) { ModuleService(module) }
4140
service.sendBinder(uid)

daemon/src/main/kotlin/org/matrix/vector/daemon/system/NotificationManager.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ import org.matrix.vector.daemon.data.FileSystem
2424
import org.matrix.vector.daemon.utils.FakeContext
2525

2626
private const val TAG = "VectorNotifManager"
27-
private const val STATUS_CHANNEL_ID = "lsposed_status"
28-
private const val UPDATED_CHANNEL_ID = "lsposed_module_updated"
27+
private const val STATUS_CHANNEL_ID = "vector_status"
28+
private const val UPDATED_CHANNEL_ID = "vector_module_updated"
2929
private const val STATUS_NOTIF_ID = 2000
3030

3131
object NotificationManager {

0 commit comments

Comments
 (0)