Fix Android 11 Keystore execution: Init framework and spoof UID 1000 (#85)
This commit resolves `KeyStore` API failures on Android 11 when running as a standalone CLI executable (UID 0), addressing both environment initialization and permission denial issues. 1. Initialize Android Framework Environment: Android 11 Keystore APIs expect a fully initialized application context and a Main Looper, which are missing in a raw root process. This patch: - Manually bootstraps `ActivityThread` via `systemMain()`. - Initializes `Looper.prepareMainLooper()`. - Injects a dummy `Application` object attached to the system context to satisfy `KeyStore.getApplicationContext()` checks. - Updates framework stubs to allow compilation of these hidden APIs. 2. Bypass Keystore Permission Checks via UID Spoofing: `KeyStoreService::generateKey` enforces the `P_INSERT` permission. Analysis of `permissions.cpp` reveals that UID 0 (Root) is explicitly denied this permission (granted only `P_GET`), whereas UID 1000 (System) holds all permissions (`~0`). To bypass this restriction, the binder interceptor now detects transactions originating from UID 0 and rewrites the `sender_euid` to 1000. This fools `KeyStoreService` into granting the request. 3. Refactor Execution Loop: Replaces the previous `Thread.sleep()` maintenance loop with `Looper.loop()`.
This commit is contained in:
@@ -386,6 +386,15 @@ void inspectAndRewriteTransaction(binder_transaction_data *txn_data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (hijack) {
|
if (hijack) {
|
||||||
|
// The kernel driver fills sender_euid. libbinder trusts this value
|
||||||
|
// to populate IPCThreadState. By changing it here, we fool the
|
||||||
|
// entire process (including the TEE implementation) into thinking
|
||||||
|
// the call came from system (1000).
|
||||||
|
if (txn_data->sender_euid == 0) {
|
||||||
|
LOGV("[Hook] Spoofing UID for transaction: 0 -> 1000");
|
||||||
|
txn_data->sender_euid = 1000;
|
||||||
|
}
|
||||||
|
|
||||||
uint64_t tx_id = ++g_transaction_id_counter;
|
uint64_t tx_id = ++g_transaction_id_counter;
|
||||||
info.transaction_id = tx_id;
|
info.transaction_id = tx_id;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
package org.matrix.TEESimulator
|
package org.matrix.TEESimulator
|
||||||
|
|
||||||
|
import android.app.ActivityThread
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.ContextWrapper
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.os.Looper
|
||||||
import java.security.Security
|
import java.security.Security
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||||
import org.matrix.TEESimulator.config.ConfigurationManager
|
import org.matrix.TEESimulator.config.ConfigurationManager
|
||||||
@@ -30,13 +35,15 @@ object App {
|
|||||||
SystemLogger.info("Welcome to TEESimulator!")
|
SystemLogger.info("Welcome to TEESimulator!")
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Initialize the Android framework environment
|
||||||
|
prepareEnvironment()
|
||||||
|
// Initialize and start the appropriate keystore interceptors.
|
||||||
|
initializeInterceptors()
|
||||||
|
|
||||||
// Load the package configuration.
|
// Load the package configuration.
|
||||||
ConfigurationManager.initialize()
|
ConfigurationManager.initialize()
|
||||||
// Set up the device's boot key and hash, which are crucial for attestation.
|
// Set up the device's boot key and hash, which are crucial for attestation.
|
||||||
AndroidDeviceUtils.setupBootKeyAndHash()
|
AndroidDeviceUtils.setupBootKeyAndHash()
|
||||||
// Initialize and start the appropriate keystore interceptors.
|
|
||||||
initializeInterceptors()
|
|
||||||
// Enter an infinite loop to keep the service running.
|
|
||||||
|
|
||||||
// Android ships with a stripped-down Bouncy Castle provider under the name "BC".
|
// Android ships with a stripped-down Bouncy Castle provider under the name "BC".
|
||||||
// We must remove the system provider first to ensure the full Bouncy Castle library
|
// We must remove the system provider first to ensure the full Bouncy Castle library
|
||||||
@@ -44,13 +51,43 @@ object App {
|
|||||||
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
|
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
|
||||||
Security.addProvider(BouncyCastleProvider())
|
Security.addProvider(BouncyCastleProvider())
|
||||||
|
|
||||||
maintainService()
|
// This starts the message queue processing. It blocks here indefinitely
|
||||||
|
// processing messages until Looper.myLooper().quit() is called.
|
||||||
|
Looper.loop()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
SystemLogger.error("A fatal error occurred in the main application thread.", e)
|
SystemLogger.error("A fatal error occurred in the main application thread.", e)
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Initializes the necessary Android framework internals to satisfy KeyStore requirements. */
|
||||||
|
private fun prepareEnvironment() {
|
||||||
|
// 1. Prepare Main Looper
|
||||||
|
if (Looper.getMainLooper() == null) {
|
||||||
|
@Suppress("deprecation") Looper.prepareMainLooper()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Initialize ActivityThread for the current process
|
||||||
|
val activityThread = ActivityThread.systemMain()
|
||||||
|
|
||||||
|
// 3. Get the system context
|
||||||
|
val systemContext = activityThread.getSystemContext()
|
||||||
|
|
||||||
|
// 4. Create a dummy Application object and attach the context
|
||||||
|
val app = Application()
|
||||||
|
val attachMethod =
|
||||||
|
ContextWrapper::class.java.getDeclaredMethod("attachBaseContext", Context::class.java)
|
||||||
|
attachMethod.isAccessible = true
|
||||||
|
attachMethod.invoke(app, systemContext)
|
||||||
|
|
||||||
|
// 5. Inject this application object into ActivityThread's mInitialApplication field.
|
||||||
|
// This is what KeyStore.getApplicationContext() looks for.
|
||||||
|
val mInitialApplicationField =
|
||||||
|
ActivityThread::class.java.getDeclaredField("mInitialApplication")
|
||||||
|
mInitialApplicationField.isAccessible = true
|
||||||
|
mInitialApplicationField.set(activityThread, app)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Selects and initializes the correct keystore interceptor based on the Android SDK version. It
|
* Selects and initializes the correct keystore interceptor based on the Android SDK version. It
|
||||||
* retries initialization until it succeeds.
|
* retries initialization until it succeeds.
|
||||||
@@ -89,15 +126,4 @@ object App {
|
|||||||
Keystore2Interceptor
|
Keystore2Interceptor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Puts the main thread into a long-running sleep loop. This is a common pattern to keep a
|
|
||||||
* background service process alive indefinitely.
|
|
||||||
*/
|
|
||||||
private fun maintainService() {
|
|
||||||
SystemLogger.info("Service started successfully. Entering maintenance mode.")
|
|
||||||
while (true) {
|
|
||||||
Thread.sleep(SERVICE_SLEEP_MS)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,4 +4,12 @@ public class ActivityThread {
|
|||||||
public static void initializeMainlineModules() {
|
public static void initializeMainlineModules() {
|
||||||
throw new UnsupportedOperationException("STUB!");
|
throw new UnsupportedOperationException("STUB!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ActivityThread systemMain() {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContextImpl getSystemContext() {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package android.app;
|
||||||
|
|
||||||
|
public class ContextImpl {
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user