src: dos2unix

Signed-off-by: Dakkshesh <beakthoven@gmail.com>
This commit is contained in:
Dakkshesh
2025-08-06 21:01:18 +05:30
parent fa2fa1c7d5
commit c5b82ef67d
16 changed files with 2822 additions and 2822 deletions
+17 -17
View File
@@ -1,17 +1,17 @@
BasedOnStyle: LLVM BasedOnStyle: LLVM
Language: Cpp Language: Cpp
Standard: c++20 Standard: c++20
ColumnLimit: 135 ColumnLimit: 135
AlignEscapedNewlines: Left AlignEscapedNewlines: Left
AllowShortFunctionsOnASingleLine: Empty AllowShortFunctionsOnASingleLine: Empty
AllowShortLambdasOnASingleLine: Empty AllowShortLambdasOnASingleLine: Empty
AlwaysBreakTemplateDeclarations: true AlwaysBreakTemplateDeclarations: true
IndentPPDirectives: AfterHash IndentPPDirectives: AfterHash
AccessModifierOffset: -4 AccessModifierOffset: -4
IndentWidth: 4 IndentWidth: 4
UseTab: Never UseTab: Never
+6 -6
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- <!--
Copyright 2025 Dakkshesh <beakthoven@gmail.com> Copyright 2025 Dakkshesh <beakthoven@gmail.com>
SPDX-License-Identifier: GPL-3.0-or-later SPDX-License-Identifier: GPL-3.0-or-later
--> -->
<manifest/> <manifest/>
@@ -1,188 +1,188 @@
/* /*
* Copyright 2025 Dakkshesh <beakthoven@gmail.com> * Copyright 2025 Dakkshesh <beakthoven@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
package io.github.beakthoven.TrickyStoreOSS package io.github.beakthoven.TrickyStoreOSS
import android.content.pm.IPackageManager import android.content.pm.IPackageManager
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Build import android.os.Build
import android.os.ServiceManager import android.os.ServiceManager
import android.os.SystemProperties import android.os.SystemProperties
import io.github.beakthoven.TrickyStoreOSS.core.config.Config import io.github.beakthoven.TrickyStoreOSS.core.config.Config
import io.github.beakthoven.TrickyStoreOSS.core.config.CustomPatchLevel import io.github.beakthoven.TrickyStoreOSS.core.config.CustomPatchLevel
import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
import org.bouncycastle.asn1.ASN1Integer import org.bouncycastle.asn1.ASN1Integer
import org.bouncycastle.asn1.DEROctetString import org.bouncycastle.asn1.DEROctetString
import org.bouncycastle.asn1.DERSequence import org.bouncycastle.asn1.DERSequence
import java.security.MessageDigest import java.security.MessageDigest
import java.util.concurrent.ThreadLocalRandom import java.util.concurrent.ThreadLocalRandom
fun getTransactCode(clazz: Class<*>, method: String): Int = fun getTransactCode(clazz: Class<*>, method: String): Int =
clazz.getDeclaredField("TRANSACTION_$method").apply { isAccessible = true } clazz.getDeclaredField("TRANSACTION_$method").apply { isAccessible = true }
.getInt(null) .getInt(null)
val bootHash: ByteArray by lazy { val bootHash: ByteArray by lazy {
getBootHashFromProp() ?: randomBytes() getBootHashFromProp() ?: randomBytes()
} }
val bootKey: ByteArray by lazy { val bootKey: ByteArray by lazy {
randomBytes() randomBytes()
} }
@OptIn(ExperimentalStdlibApi::class) @OptIn(ExperimentalStdlibApi::class)
private fun getBootHashFromProp(): ByteArray? { private fun getBootHashFromProp(): ByteArray? {
val digest = SystemProperties.get("ro.boot.vbmeta.digest", null) ?: return null val digest = SystemProperties.get("ro.boot.vbmeta.digest", null) ?: return null
return if (digest.length == 64) digest.hexToByteArray() else null return if (digest.length == 64) digest.hexToByteArray() else null
} }
private fun randomBytes(): ByteArray = ByteArray(32).also { private fun randomBytes(): ByteArray = ByteArray(32).also {
ThreadLocalRandom.current().nextBytes(it) ThreadLocalRandom.current().nextBytes(it)
} }
val patchLevel: Int val patchLevel: Int
get() = getCustomPatchLevel("system", false) get() = getCustomPatchLevel("system", false)
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(false) ?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(false)
val patchLevelLong: Int val patchLevelLong: Int
get() = getCustomPatchLevel("system", true) get() = getCustomPatchLevel("system", true)
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(true) ?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(true)
val vendorPatchLevel: Int val vendorPatchLevel: Int
get() = getCustomPatchLevel("vendor", false) get() = getCustomPatchLevel("vendor", false)
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(false) ?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(false)
val vendorPatchLevelLong: Int val vendorPatchLevelLong: Int
get() = getCustomPatchLevel("vendor", true) get() = getCustomPatchLevel("vendor", true)
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(true) ?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(true)
val bootPatchLevel: Int val bootPatchLevel: Int
get() = getCustomPatchLevel("boot", false) get() = getCustomPatchLevel("boot", false)
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(false) ?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(false)
val bootPatchLevelLong: Int val bootPatchLevelLong: Int
get() = getCustomPatchLevel("boot", true) get() = getCustomPatchLevel("boot", true)
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(true) ?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(true)
private val customPatchLevel: CustomPatchLevel? private val customPatchLevel: CustomPatchLevel?
get() = Config._customPatchLevel get() = Config._customPatchLevel
private fun getCustomPatchLevel(component: String, isLong: Boolean): Int? { private fun getCustomPatchLevel(component: String, isLong: Boolean): Int? {
val config = customPatchLevel ?: return null val config = customPatchLevel ?: return null
val value = when (component) { val value = when (component) {
"system" -> config.system ?: config.all "system" -> config.system ?: config.all
"vendor" -> config.vendor ?: config.all "vendor" -> config.vendor ?: config.all
"boot" -> config.boot ?: config.all "boot" -> config.boot ?: config.all
else -> config.all else -> config.all
} ?: return null } ?: return null
when { when {
value.equals("no", ignoreCase = true) -> return null value.equals("no", ignoreCase = true) -> return null
value.equals("prop", ignoreCase = true) -> return null value.equals("prop", ignoreCase = true) -> return null
} }
return parsePatchLevelValue(value, component, isLong) return parsePatchLevelValue(value, component, isLong)
} }
private fun parsePatchLevelValue(value: String, component: String, isLong: Boolean): Int? { private fun parsePatchLevelValue(value: String, component: String, isLong: Boolean): Int? {
val normalized = value.replace("-", "") val normalized = value.replace("-", "")
return try { return try {
when (normalized.length) { when (normalized.length) {
8 -> { 8 -> {
val year = normalized.substring(0, 4).toInt() val year = normalized.substring(0, 4).toInt()
val month = normalized.substring(4, 6).toInt() val month = normalized.substring(4, 6).toInt()
val day = normalized.substring(6, 8).toInt() val day = normalized.substring(6, 8).toInt()
if (isLong) year * 10000 + month * 100 + day if (isLong) year * 10000 + month * 100 + day
else year * 100 + month else year * 100 + month
} }
6 -> { 6 -> {
val year = normalized.substring(0, 4).toInt() val year = normalized.substring(0, 4).toInt()
val month = normalized.substring(4, 6).toInt() val month = normalized.substring(4, 6).toInt()
if (isLong) year * 10000 + month * 100 if (isLong) year * 10000 + month * 100
else year * 100 + month else year * 100 + month
} }
else -> { else -> {
Logger.e("Invalid patch level length for $component: $normalized") Logger.e("Invalid patch level length for $component: $normalized")
null null
} }
} }
} catch (e: NumberFormatException) { } catch (e: NumberFormatException) {
Logger.e("Patch level parse error for $component=$value", e) Logger.e("Patch level parse error for $component=$value", e)
null null
} }
} }
val osVersion: Int val osVersion: Int
get() = getOsVersion(Build.VERSION.SDK_INT) get() = getOsVersion(Build.VERSION.SDK_INT)
private val osVersionMap = mapOf( private val osVersionMap = mapOf(
Build.VERSION_CODES.BAKLAVA to 160000, Build.VERSION_CODES.BAKLAVA to 160000,
Build.VERSION_CODES.VANILLA_ICE_CREAM to 150000, Build.VERSION_CODES.VANILLA_ICE_CREAM to 150000,
Build.VERSION_CODES.UPSIDE_DOWN_CAKE to 140000, Build.VERSION_CODES.UPSIDE_DOWN_CAKE to 140000,
Build.VERSION_CODES.TIRAMISU to 130000, Build.VERSION_CODES.TIRAMISU to 130000,
Build.VERSION_CODES.S_V2 to 120100, Build.VERSION_CODES.S_V2 to 120100,
Build.VERSION_CODES.S to 120000, Build.VERSION_CODES.S to 120000,
Build.VERSION_CODES.R to 110000, Build.VERSION_CODES.R to 110000,
Build.VERSION_CODES.Q to 100000 Build.VERSION_CODES.Q to 100000
) )
private fun getOsVersion(sdkVersion: Int): Int = osVersionMap[sdkVersion] ?: 160000 private fun getOsVersion(sdkVersion: Int): Int = osVersionMap[sdkVersion] ?: 160000
fun String.convertPatchLevel(isLong: Boolean): Int = runCatching { fun String.convertPatchLevel(isLong: Boolean): Int = runCatching {
val parts = split("-") val parts = split("-")
when { when {
isLong && parts.size >= 3 -> parts[0].toInt() * 10000 + parts[1].toInt() * 100 + parts[2].toInt() isLong && parts.size >= 3 -> parts[0].toInt() * 10000 + parts[1].toInt() * 100 + parts[2].toInt()
parts.size >= 2 -> parts[0].toInt() * 100 + parts[1].toInt() parts.size >= 2 -> parts[0].toInt() * 100 + parts[1].toInt()
else -> throw IllegalArgumentException("Invalid patch level format: $this") else -> throw IllegalArgumentException("Invalid patch level format: $this")
} }
}.onFailure { }.onFailure {
Logger.e("Invalid patch level format: $this", it) Logger.e("Invalid patch level format: $this", it)
}.getOrDefault(202404) }.getOrDefault(202404)
fun IPackageManager.getPackageInfoCompat(name: String, flags: Long, userId: Int) = fun IPackageManager.getPackageInfoCompat(name: String, flags: Long, userId: Int) =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getPackageInfo(name, flags, userId) getPackageInfo(name, flags, userId)
} else { } else {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
getPackageInfo(name, flags.toInt(), userId) getPackageInfo(name, flags.toInt(), userId)
} }
val apexInfos: List<Pair<String, Long>> by lazy { val apexInfos: List<Pair<String, Long>> by lazy {
runCatching { runCatching {
val packageManager = IPackageManager.Stub.asInterface(ServiceManager.getService("package")) val packageManager = IPackageManager.Stub.asInterface(ServiceManager.getService("package"))
val packages = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { val packages = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
packageManager.getInstalledPackages(PackageManager.MATCH_APEX.toLong(), 0) packageManager.getInstalledPackages(PackageManager.MATCH_APEX.toLong(), 0)
} else { } else {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
packageManager.getInstalledPackages(PackageManager.MATCH_APEX, 0) packageManager.getInstalledPackages(PackageManager.MATCH_APEX, 0)
} }
packages.list packages.list
.map { it.packageName to it.longVersionCode } .map { it.packageName to it.longVersionCode }
.sortedBy { it.first } .sortedBy { it.first }
}.getOrElse { }.getOrElse {
Logger.e("Failed to get APEX package information") Logger.e("Failed to get APEX package information")
emptyList() emptyList()
} }
} }
val moduleHash: ByteArray by lazy { val moduleHash: ByteArray by lazy {
runCatching { runCatching {
val encodables = apexInfos.flatMap { (packageName, versionCode) -> val encodables = apexInfos.flatMap { (packageName, versionCode) ->
listOf( listOf(
DEROctetString(packageName.toByteArray()), DEROctetString(packageName.toByteArray()),
ASN1Integer(versionCode) ASN1Integer(versionCode)
) )
} }
val sequence = DERSequence(encodables.toTypedArray()) val sequence = DERSequence(encodables.toTypedArray())
MessageDigest.getInstance("SHA-256").digest(sequence.encoded) MessageDigest.getInstance("SHA-256").digest(sequence.encoded)
}.getOrElse { }.getOrElse {
Logger.e("Failed to compute module hash", it) Logger.e("Failed to compute module hash", it)
ByteArray(32) ByteArray(32)
} }
} }
fun String.trimLine(): String = trim().split("\n").joinToString("\n") { it.trim() } fun String.trimLine(): String = trim().split("\n").joinToString("\n") { it.trim() }
File diff suppressed because it is too large Load Diff
@@ -1,160 +1,160 @@
/* /*
* Copyright 2025 Dakkshesh <beakthoven@gmail.com> * Copyright 2025 Dakkshesh <beakthoven@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
package io.github.beakthoven.TrickyStoreOSS package io.github.beakthoven.TrickyStoreOSS
import android.system.keystore2.KeyEntryResponse import android.system.keystore2.KeyEntryResponse
import android.system.keystore2.KeyMetadata import android.system.keystore2.KeyMetadata
import android.util.Log import android.util.Log
import io.github.beakthoven.TrickyStoreOSS.CertificateUtils.putCertificateChain import io.github.beakthoven.TrickyStoreOSS.CertificateUtils.putCertificateChain
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.security.cert.Certificate import java.security.cert.Certificate
import java.security.cert.CertificateException import java.security.cert.CertificateException
import java.security.cert.CertificateFactory import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
object CertificateUtils { object CertificateUtils {
private const val TAG = "CertificateUtils" private const val TAG = "CertificateUtils"
sealed class CertificateResult<out T> { sealed class CertificateResult<out T> {
data class Success<T>(val data: T) : CertificateResult<T>() data class Success<T>(val data: T) : CertificateResult<T>()
data class Error(val message: String, val cause: Throwable? = null) : CertificateResult<Nothing>() data class Error(val message: String, val cause: Throwable? = null) : CertificateResult<Nothing>()
inline fun <R> map(transform: (T) -> R): CertificateResult<R> = when (this) { inline fun <R> map(transform: (T) -> R): CertificateResult<R> = when (this) {
is Success -> Success(transform(data)) is Success -> Success(transform(data))
is Error -> this is Error -> this
} }
fun getOrNull(): T? = when (this) { fun getOrNull(): T? = when (this) {
is Success -> data is Success -> data
is Error -> null is Error -> null
} }
} }
fun ByteArray?.toCertificate(): X509Certificate? { fun ByteArray?.toCertificate(): X509Certificate? {
return this?.let { bytes -> return this?.let { bytes ->
try { try {
val certFactory = CertificateFactory.getInstance("X.509") val certFactory = CertificateFactory.getInstance("X.509")
certFactory.generateCertificate(ByteArrayInputStream(bytes)) as? X509Certificate certFactory.generateCertificate(ByteArrayInputStream(bytes)) as? X509Certificate
} catch (e: CertificateException) { } catch (e: CertificateException) {
Log.w(TAG, "Couldn't parse certificate in keystore", e) Log.w(TAG, "Couldn't parse certificate in keystore", e)
null null
} }
} }
} }
fun ByteArray.toCertificateResult(): CertificateResult<X509Certificate> { fun ByteArray.toCertificateResult(): CertificateResult<X509Certificate> {
return try { return try {
val certFactory = CertificateFactory.getInstance("X.509") val certFactory = CertificateFactory.getInstance("X.509")
val certificate = certFactory.generateCertificate(ByteArrayInputStream(this)) as X509Certificate val certificate = certFactory.generateCertificate(ByteArrayInputStream(this)) as X509Certificate
CertificateResult.Success(certificate) CertificateResult.Success(certificate)
} catch (e: CertificateException) { } catch (e: CertificateException) {
CertificateResult.Error("Failed to parse certificate", e) CertificateResult.Error("Failed to parse certificate", e)
} }
} }
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
fun ByteArray?.toCertificates(): Collection<X509Certificate> { fun ByteArray?.toCertificates(): Collection<X509Certificate> {
return this?.let { bytes -> return this?.let { bytes ->
try { try {
val certFactory = CertificateFactory.getInstance("X.509") val certFactory = CertificateFactory.getInstance("X.509")
certFactory.generateCertificates(ByteArrayInputStream(bytes)) as Collection<X509Certificate> certFactory.generateCertificates(ByteArrayInputStream(bytes)) as Collection<X509Certificate>
} catch (e: CertificateException) { } catch (e: CertificateException) {
Log.w(TAG, "Couldn't parse certificates in keystore", e) Log.w(TAG, "Couldn't parse certificates in keystore", e)
emptyList() emptyList()
} }
} ?: emptyList() } ?: emptyList()
} }
fun Collection<Certificate>.toByteArray(): ByteArray? = runCatching { fun Collection<Certificate>.toByteArray(): ByteArray? = runCatching {
ByteArrayOutputStream().use { outputStream -> ByteArrayOutputStream().use { outputStream ->
forEach { cert -> outputStream.write(cert.encoded) } forEach { cert -> outputStream.write(cert.encoded) }
outputStream.toByteArray() outputStream.toByteArray()
} }
}.onFailure { }.onFailure {
Log.w(TAG, "Failed to convert certificates to byte array", it) Log.w(TAG, "Failed to convert certificates to byte array", it)
}.getOrNull() }.getOrNull()
fun Collection<Certificate>.toByteArrayList(): List<ByteArray>? = runCatching { fun Collection<Certificate>.toByteArrayList(): List<ByteArray>? = runCatching {
map { it.encoded } map { it.encoded }
}.onFailure { }.onFailure {
Log.w(TAG, "Failed to convert certificates to byte array list", it) Log.w(TAG, "Failed to convert certificates to byte array list", it)
}.getOrNull() }.getOrNull()
fun KeyEntryResponse?.getCertificateChain(): Array<Certificate>? { fun KeyEntryResponse?.getCertificateChain(): Array<Certificate>? {
val metadata = this?.metadata ?: return null val metadata = this?.metadata ?: return null
val leafCert = metadata.certificate?.toCertificate() ?: return null val leafCert = metadata.certificate?.toCertificate() ?: return null
return when (val chainBytes = metadata.certificateChain) { return when (val chainBytes = metadata.certificateChain) {
null -> arrayOf(leafCert) null -> arrayOf(leafCert)
else -> { else -> {
val additionalCerts = chainBytes.toCertificates() val additionalCerts = chainBytes.toCertificates()
buildList { buildList {
add(leafCert) add(leafCert)
addAll(additionalCerts) addAll(additionalCerts)
}.toTypedArray() }.toTypedArray()
} }
} }
} }
fun KeyEntryResponse.putCertificateChain(chain: Array<Certificate>): Result<Unit> { fun KeyEntryResponse.putCertificateChain(chain: Array<Certificate>): Result<Unit> {
return runCatching { return runCatching {
metadata.putCertificateChain(chain) metadata.putCertificateChain(chain)
} }
} }
fun KeyMetadata.putCertificateChain(chain: Array<Certificate>): Result<Unit> { fun KeyMetadata.putCertificateChain(chain: Array<Certificate>): Result<Unit> {
return runCatching { return runCatching {
if (chain.isEmpty()) return@runCatching if (chain.isEmpty()) return@runCatching
certificate = chain[0].encoded certificate = chain[0].encoded
if (chain.size > 1) { if (chain.size > 1) {
ByteArrayOutputStream().use { output -> ByteArrayOutputStream().use { output ->
for (i in 1 until chain.size) { for (i in 1 until chain.size) {
output.write(chain[i].encoded) output.write(chain[i].encoded)
} }
certificateChain = output.toByteArray() certificateChain = output.toByteArray()
} }
} else { } else {
certificateChain = null certificateChain = null
} }
} }
} }
} }
fun ByteArray?.toX509Certificate(): X509Certificate? = CertificateUtils.run { this@toX509Certificate.toCertificate() } fun ByteArray?.toX509Certificate(): X509Certificate? = CertificateUtils.run { this@toX509Certificate.toCertificate() }
fun ByteArray?.toX509Certificates(): Collection<X509Certificate> = CertificateUtils.run { this@toX509Certificates.toCertificates() } fun ByteArray?.toX509Certificates(): Collection<X509Certificate> = CertificateUtils.run { this@toX509Certificates.toCertificates() }
fun Collection<Certificate>.encodedBytes(): ByteArray? = CertificateUtils.run { this@encodedBytes.toByteArray() } fun Collection<Certificate>.encodedBytes(): ByteArray? = CertificateUtils.run { this@encodedBytes.toByteArray() }
fun Collection<Certificate>.encodedBytesList(): List<ByteArray>? = CertificateUtils.run { this@encodedBytesList.toByteArrayList() } fun Collection<Certificate>.encodedBytesList(): List<ByteArray>? = CertificateUtils.run { this@encodedBytesList.toByteArrayList() }
fun KeyEntryResponse.putCertificateChain(chain: Array<Certificate>): Result<Unit> { fun KeyEntryResponse.putCertificateChain(chain: Array<Certificate>): Result<Unit> {
return runCatching { return runCatching {
metadata.putCertificateChain(chain).getOrThrow() metadata.putCertificateChain(chain).getOrThrow()
} }
} }
fun KeyMetadata.putCertificateChain(chain: Array<Certificate>): Result<Unit> { fun KeyMetadata.putCertificateChain(chain: Array<Certificate>): Result<Unit> {
return runCatching { return runCatching {
if (chain.isEmpty()) return@runCatching if (chain.isEmpty()) return@runCatching
certificate = chain[0].encoded certificate = chain[0].encoded
if (chain.size > 1) { if (chain.size > 1) {
ByteArrayOutputStream().use { output -> ByteArrayOutputStream().use { output ->
for (i in 1 until chain.size) { for (i in 1 until chain.size) {
output.write(chain[i].encoded) output.write(chain[i].encoded)
} }
certificateChain = output.toByteArray() certificateChain = output.toByteArray()
} }
} else { } else {
certificateChain = null certificateChain = null
} }
} }
} }
@@ -1,155 +1,155 @@
/* /*
* Copyright 2025 Dakkshesh <beakthoven@gmail.com> * Copyright 2025 Dakkshesh <beakthoven@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
package io.github.beakthoven.TrickyStoreOSS package io.github.beakthoven.TrickyStoreOSS
import org.xmlpull.v1.XmlPullParser import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserException import org.xmlpull.v1.XmlPullParserException
import org.xmlpull.v1.XmlPullParserFactory import org.xmlpull.v1.XmlPullParserFactory
import java.io.IOException import java.io.IOException
import java.io.StringReader import java.io.StringReader
class XmlParser(private val xmlContent: String) { class XmlParser(private val xmlContent: String) {
sealed class ParseResult { sealed class ParseResult {
data class Success(val attributes: Map<String, String>) : ParseResult() data class Success(val attributes: Map<String, String>) : ParseResult()
data class Error(val message: String, val cause: Throwable? = null) : ParseResult() data class Error(val message: String, val cause: Throwable? = null) : ParseResult()
} }
fun obtainPath(path: String): ParseResult { fun obtainPath(path: String): ParseResult {
return try { return try {
val factory = XmlPullParserFactory.newInstance() val factory = XmlPullParserFactory.newInstance()
val parser = factory.newPullParser() val parser = factory.newPullParser()
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false) parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
parser.setInput(StringReader(xmlContent)) parser.setInput(StringReader(xmlContent))
val tags = path.split(".").toTypedArray() val tags = path.split(".").toTypedArray()
val result = readData(parser, tags, 0, mutableMapOf()) val result = readData(parser, tags, 0, mutableMapOf())
ParseResult.Success(result) ParseResult.Success(result)
} catch (e: XmlPullParserException) { } catch (e: XmlPullParserException) {
ParseResult.Error("XML parsing error: ${e.message}", e) ParseResult.Error("XML parsing error: ${e.message}", e)
} catch (e: IOException) { } catch (e: IOException) {
ParseResult.Error("IO error while parsing XML: ${e.message}", e) ParseResult.Error("IO error while parsing XML: ${e.message}", e)
} catch (e: Exception) { } catch (e: Exception) {
ParseResult.Error("Unexpected error: ${e.message}", e) ParseResult.Error("Unexpected error: ${e.message}", e)
} }
} }
@Throws(Exception::class) @Throws(Exception::class)
fun obtainPathLegacy(path: String): Map<String, String> { fun obtainPathLegacy(path: String): Map<String, String> {
when (val result = obtainPath(path)) { when (val result = obtainPath(path)) {
is ParseResult.Success -> return result.attributes is ParseResult.Success -> return result.attributes
is ParseResult.Error -> throw result.cause ?: Exception(result.message) is ParseResult.Error -> throw result.cause ?: Exception(result.message)
} }
} }
@Throws(IOException::class, XmlPullParserException::class) @Throws(IOException::class, XmlPullParserException::class)
private fun readData( private fun readData(
parser: XmlPullParser, parser: XmlPullParser,
tags: Array<String>, tags: Array<String>,
index: Int, index: Int,
tagCounts: MutableMap<String, Int> tagCounts: MutableMap<String, Int>
): Map<String, String> { ): Map<String, String> {
while (parser.next() != XmlPullParser.END_DOCUMENT) { while (parser.next() != XmlPullParser.END_DOCUMENT) {
if (parser.eventType != XmlPullParser.START_TAG) { if (parser.eventType != XmlPullParser.START_TAG) {
continue continue
} }
val currentTag = parser.name ?: continue val currentTag = parser.name ?: continue
val targetTag = tags[index] val targetTag = tags[index]
val tagParts = targetTag.split("[") val tagParts = targetTag.split("[")
val baseTagName = tagParts[0] val baseTagName = tagParts[0]
if (currentTag == baseTagName) { if (currentTag == baseTagName) {
return if (tagParts.size > 1) { return if (tagParts.size > 1) {
handleIndexedTag(parser, tags, index, tagCounts, currentTag, tagParts[1]) handleIndexedTag(parser, tags, index, tagCounts, currentTag, tagParts[1])
} else { } else {
handleRegularTag(parser, tags, index) handleRegularTag(parser, tags, index)
} }
} else { } else {
skipCurrentElement(parser) skipCurrentElement(parser)
} }
} }
throw XmlPullParserException("Path not found: ${tags.joinToString(".")}") throw XmlPullParserException("Path not found: ${tags.joinToString(".")}")
} }
@Throws(IOException::class, XmlPullParserException::class) @Throws(IOException::class, XmlPullParserException::class)
private fun handleIndexedTag( private fun handleIndexedTag(
parser: XmlPullParser, parser: XmlPullParser,
tags: Array<String>, tags: Array<String>,
index: Int, index: Int,
tagCounts: MutableMap<String, Int>, tagCounts: MutableMap<String, Int>,
currentTag: String, currentTag: String,
indexPart: String indexPart: String
): Map<String, String> { ): Map<String, String> {
val targetIndex = indexPart.replace("]", "").toIntOrNull() val targetIndex = indexPart.replace("]", "").toIntOrNull()
?: throw XmlPullParserException("Invalid index in tag: $indexPart") ?: throw XmlPullParserException("Invalid index in tag: $indexPart")
val currentCount = tagCounts.getOrDefault(currentTag, 0) val currentCount = tagCounts.getOrDefault(currentTag, 0)
return if (currentCount < targetIndex) { return if (currentCount < targetIndex) {
tagCounts[currentTag] = currentCount + 1 tagCounts[currentTag] = currentCount + 1
readData(parser, tags, index, tagCounts) readData(parser, tags, index, tagCounts)
} else { } else {
if (index == tags.size - 1) { if (index == tags.size - 1) {
readAttributes(parser) readAttributes(parser)
} else { } else {
readData(parser, tags, index + 1, tagCounts) readData(parser, tags, index + 1, tagCounts)
} }
} }
} }
@Throws(IOException::class, XmlPullParserException::class) @Throws(IOException::class, XmlPullParserException::class)
private fun handleRegularTag( private fun handleRegularTag(
parser: XmlPullParser, parser: XmlPullParser,
tags: Array<String>, tags: Array<String>,
index: Int index: Int
): Map<String, String> { ): Map<String, String> {
return if (index == tags.size - 1) { return if (index == tags.size - 1) {
readAttributes(parser) readAttributes(parser)
} else { } else {
readData(parser, tags, index + 1, mutableMapOf()) readData(parser, tags, index + 1, mutableMapOf())
} }
} }
@Throws(IOException::class, XmlPullParserException::class) @Throws(IOException::class, XmlPullParserException::class)
private fun readAttributes(parser: XmlPullParser): Map<String, String> { private fun readAttributes(parser: XmlPullParser): Map<String, String> {
val attributes = mutableMapOf<String, String>() val attributes = mutableMapOf<String, String>()
for (i in 0 until parser.attributeCount) { for (i in 0 until parser.attributeCount) {
val name = parser.getAttributeName(i) val name = parser.getAttributeName(i)
val value = parser.getAttributeValue(i) val value = parser.getAttributeValue(i)
if (name != null && value != null) { if (name != null && value != null) {
attributes[name] = value attributes[name] = value
} }
} }
if (parser.next() == XmlPullParser.TEXT) { if (parser.next() == XmlPullParser.TEXT) {
parser.text?.let { text -> parser.text?.let { text ->
attributes["text"] = text attributes["text"] = text
} }
} }
return attributes return attributes
} }
@Throws(XmlPullParserException::class, IOException::class) @Throws(XmlPullParserException::class, IOException::class)
private fun skipCurrentElement(parser: XmlPullParser) { private fun skipCurrentElement(parser: XmlPullParser) {
if (parser.eventType != XmlPullParser.START_TAG) { if (parser.eventType != XmlPullParser.START_TAG) {
throw IllegalStateException("Parser must be positioned at START_TAG") throw IllegalStateException("Parser must be positioned at START_TAG")
} }
var depth = 1 var depth = 1
while (depth != 0) { while (depth != 0) {
when (parser.next()) { when (parser.next()) {
XmlPullParser.END_TAG -> depth-- XmlPullParser.END_TAG -> depth--
XmlPullParser.START_TAG -> depth++ XmlPullParser.START_TAG -> depth++
} }
} }
} }
} }
fun String.toXmlParser(): XmlParser = XmlParser(this) fun String.toXmlParser(): XmlParser = XmlParser(this)
@@ -1,267 +1,267 @@
/* /*
* Copyright 2025 Dakkshesh <beakthoven@gmail.com> * Copyright 2025 Dakkshesh <beakthoven@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
package io.github.beakthoven.TrickyStoreOSS.core.config package io.github.beakthoven.TrickyStoreOSS.core.config
import android.content.pm.IPackageManager import android.content.pm.IPackageManager
import android.os.Build import android.os.Build
import android.os.FileObserver import android.os.FileObserver
import android.os.ServiceManager import android.os.ServiceManager
import android.security.keystore.KeyGenParameterSpec import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties import android.security.keystore.KeyProperties
import io.github.beakthoven.TrickyStoreOSS.CertificateHacker import io.github.beakthoven.TrickyStoreOSS.CertificateHacker
import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
import java.io.File import java.io.File
import java.security.KeyPairGenerator import java.security.KeyPairGenerator
import java.security.KeyStore import java.security.KeyStore
import java.security.SecureRandom import java.security.SecureRandom
import java.security.spec.ECGenParameterSpec import java.security.spec.ECGenParameterSpec
object Config { object Config {
private val hackPackages = mutableSetOf<String>() private val hackPackages = mutableSetOf<String>()
private val generatePackages = mutableSetOf<String>() private val generatePackages = mutableSetOf<String>()
private val packageModes = mutableMapOf<String, Mode>() private val packageModes = mutableMapOf<String, Mode>()
enum class Mode { enum class Mode {
AUTO, LEAF_HACK, GENERATE AUTO, LEAF_HACK, GENERATE
} }
private fun updateTargetPackages(f: File?) = runCatching { private fun updateTargetPackages(f: File?) = runCatching {
hackPackages.clear() hackPackages.clear()
generatePackages.clear() generatePackages.clear()
packageModes.clear() packageModes.clear()
// Default: always generate for these // Default: always generate for these
listOf("com.google.android.gsf", "com.google.android.gms", "com.android.vending").forEach { listOf("com.google.android.gsf", "com.google.android.gms", "com.android.vending").forEach {
generatePackages.add(it) generatePackages.add(it)
packageModes[it] = Mode.GENERATE packageModes[it] = Mode.GENERATE
} }
f?.readLines()?.forEach { f?.readLines()?.forEach {
if (it.isNotBlank() && !it.startsWith("#")) { if (it.isNotBlank() && !it.startsWith("#")) {
val n = it.trim() val n = it.trim()
when { when {
n.endsWith("!") -> { n.endsWith("!") -> {
val pkg = n.removeSuffix("!").trim() val pkg = n.removeSuffix("!").trim()
generatePackages.add(pkg) generatePackages.add(pkg)
packageModes[pkg] = Mode.GENERATE packageModes[pkg] = Mode.GENERATE
} }
n.endsWith("?") -> { n.endsWith("?") -> {
val pkg = n.removeSuffix("?").trim() val pkg = n.removeSuffix("?").trim()
hackPackages.add(pkg) hackPackages.add(pkg)
packageModes[pkg] = Mode.LEAF_HACK packageModes[pkg] = Mode.LEAF_HACK
} }
else -> { else -> {
// Auto mode // Auto mode
packageModes[n] = Mode.AUTO packageModes[n] = Mode.AUTO
} }
} }
} }
} }
Logger.i("update hack packages: $hackPackages, generate packages=$generatePackages, packageModes=$packageModes") Logger.i("update hack packages: $hackPackages, generate packages=$generatePackages, packageModes=$packageModes")
}.onFailure { }.onFailure {
Logger.e("failed to update target files", it) Logger.e("failed to update target files", it)
} }
private fun updateKeyBox(f: File?) = runCatching { private fun updateKeyBox(f: File?) = runCatching {
CertificateHacker.readFromXml(f?.readText()) CertificateHacker.readFromXml(f?.readText())
}.onFailure { }.onFailure {
Logger.e("failed to update keybox", it) Logger.e("failed to update keybox", it)
} }
private const val CONFIG_PATH = "/data/adb/tricky_store" private const val CONFIG_PATH = "/data/adb/tricky_store"
private const val TARGET_FILE = "target.txt" private const val TARGET_FILE = "target.txt"
private const val KEYBOX_FILE = "keybox.xml" private const val KEYBOX_FILE = "keybox.xml"
private const val TEE_STATUS_FILE = "tee_status" private const val TEE_STATUS_FILE = "tee_status"
private const val PATCHLEVEL_FILE = "security_patch.txt" private const val PATCHLEVEL_FILE = "security_patch.txt"
private val root = File(CONFIG_PATH) private val root = File(CONFIG_PATH)
@Volatile @Volatile
private var teeBroken: Boolean? = null private var teeBroken: Boolean? = null
private fun isTEEWorking(): Boolean { private fun isTEEWorking(): Boolean {
val alias = "tee_attest_test_key" val alias = "tee_attest_test_key"
return try { return try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
android.app.ActivityThread.initializeMainlineModules(); android.app.ActivityThread.initializeMainlineModules();
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
android.security.keystore2.AndroidKeyStoreProvider.install(); android.security.keystore2.AndroidKeyStoreProvider.install();
} else { } else {
android.security.keystore.AndroidKeyStoreProvider.install(); android.security.keystore.AndroidKeyStoreProvider.install();
} }
val keyStore = KeyStore.getInstance("AndroidKeyStore") val keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null) keyStore.load(null)
val keyPairGenerator = KeyPairGenerator.getInstance( val keyPairGenerator = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore") KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore")
val challenge = ByteArray(16).apply { val challenge = ByteArray(16).apply {
SecureRandom().nextBytes(this) SecureRandom().nextBytes(this)
} }
val parameterSpec = KeyGenParameterSpec.Builder( val parameterSpec = KeyGenParameterSpec.Builder(
alias, alias,
KeyProperties.PURPOSE_SIGN KeyProperties.PURPOSE_SIGN
) )
.setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1")) .setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1"))
.setDigests(KeyProperties.DIGEST_SHA256) .setDigests(KeyProperties.DIGEST_SHA256)
.setAttestationChallenge(challenge) .setAttestationChallenge(challenge)
.setIsStrongBoxBacked(false) .setIsStrongBoxBacked(false)
.build() .build()
keyPairGenerator.initialize(parameterSpec) keyPairGenerator.initialize(parameterSpec)
keyPairGenerator.generateKeyPair() keyPairGenerator.generateKeyPair()
keyStore.deleteEntry(alias) keyStore.deleteEntry(alias)
true true
} catch (e: Exception) { } catch (e: Exception) {
Logger.e("TEE check failure: ${e.message}") Logger.e("TEE check failure: ${e.message}")
false false
} }
} }
private fun storeTEEStatus(root: File) { private fun storeTEEStatus(root: File) {
val statusFile = File(root, TEE_STATUS_FILE) val statusFile = File(root, TEE_STATUS_FILE)
val status = isTEEWorking() val status = isTEEWorking()
teeBroken = !status teeBroken = !status
try { try {
statusFile.writeText("teeBroken=${!status}") statusFile.writeText("teeBroken=${!status}")
} catch (e: Exception) { } catch (e: Exception) {
Logger.e("Failed to write TEE status: ${e.message}") Logger.e("Failed to write TEE status: ${e.message}")
} }
} }
private fun loadTEEStatus(root: File) { private fun loadTEEStatus(root: File) {
val statusFile = File(root, TEE_STATUS_FILE) val statusFile = File(root, TEE_STATUS_FILE)
if (statusFile.exists()) { if (statusFile.exists()) {
val line = statusFile.readText().trim() val line = statusFile.readText().trim()
teeBroken = line == "teeBroken=true" teeBroken = line == "teeBroken=true"
} else { } else {
teeBroken = null teeBroken = null
} }
} }
object ConfigObserver : FileObserver(root, CLOSE_WRITE or DELETE or MOVED_FROM or MOVED_TO) { object ConfigObserver : FileObserver(root, CLOSE_WRITE or DELETE or MOVED_FROM or MOVED_TO) {
override fun onEvent(event: Int, path: String?) { override fun onEvent(event: Int, path: String?) {
path ?: return path ?: return
val f = when (event) { val f = when (event) {
CLOSE_WRITE, MOVED_TO -> File(root, path) CLOSE_WRITE, MOVED_TO -> File(root, path)
DELETE, MOVED_FROM -> null DELETE, MOVED_FROM -> null
else -> return else -> return
} }
when (path) { when (path) {
TARGET_FILE -> updateTargetPackages(f) TARGET_FILE -> updateTargetPackages(f)
KEYBOX_FILE -> updateKeyBox(f) KEYBOX_FILE -> updateKeyBox(f)
PATCHLEVEL_FILE -> updatePatchLevel(f) PATCHLEVEL_FILE -> updatePatchLevel(f)
} }
} }
} }
fun initialize() { fun initialize() {
root.mkdirs() root.mkdirs()
val scope = File(root, TARGET_FILE) val scope = File(root, TARGET_FILE)
if (scope.exists()) { if (scope.exists()) {
updateTargetPackages(scope) updateTargetPackages(scope)
} else { } else {
Logger.e("target.txt file not found, please put it to $scope !") Logger.e("target.txt file not found, please put it to $scope !")
} }
val keybox = File(root, KEYBOX_FILE) val keybox = File(root, KEYBOX_FILE)
if (!keybox.exists()) { if (!keybox.exists()) {
Logger.e("keybox file not found, please put it to $keybox !") Logger.e("keybox file not found, please put it to $keybox !")
} else { } else {
updateKeyBox(keybox) updateKeyBox(keybox)
} }
storeTEEStatus(root) storeTEEStatus(root)
val patchFile = File(root, PATCHLEVEL_FILE) val patchFile = File(root, PATCHLEVEL_FILE)
updatePatchLevel(if (patchFile.exists()) patchFile else null) updatePatchLevel(if (patchFile.exists()) patchFile else null)
ConfigObserver.startWatching() ConfigObserver.startWatching()
} }
private var iPm: IPackageManager? = null private var iPm: IPackageManager? = null
fun getPm(): IPackageManager? { fun getPm(): IPackageManager? {
if (iPm == null) { if (iPm == null) {
iPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package")) iPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"))
} }
return iPm return iPm
} }
fun needHack(callingUid: Int): Boolean = kotlin.runCatching { fun needHack(callingUid: Int): Boolean = kotlin.runCatching {
val ps = getPm()?.getPackagesForUid(callingUid) ?: return false val ps = getPm()?.getPackagesForUid(callingUid) ?: return false
if (teeBroken == null) loadTEEStatus(root) if (teeBroken == null) loadTEEStatus(root)
for (pkg in ps) { for (pkg in ps) {
when (packageModes[pkg]) { when (packageModes[pkg]) {
Mode.LEAF_HACK -> return true Mode.LEAF_HACK -> return true
Mode.AUTO -> { Mode.AUTO -> {
if (teeBroken == false) return true if (teeBroken == false) return true
} }
else -> {} else -> {}
} }
} }
return false return false
}.onFailure { Logger.e("failed to get packages", it) }.getOrNull() ?: false }.onFailure { Logger.e("failed to get packages", it) }.getOrNull() ?: false
fun needGenerate(callingUid: Int): Boolean = kotlin.runCatching { fun needGenerate(callingUid: Int): Boolean = kotlin.runCatching {
val ps = getPm()?.getPackagesForUid(callingUid) ?: return false val ps = getPm()?.getPackagesForUid(callingUid) ?: return false
if (teeBroken == null) loadTEEStatus(root) if (teeBroken == null) loadTEEStatus(root)
for (pkg in ps) { for (pkg in ps) {
when (packageModes[pkg]) { when (packageModes[pkg]) {
Mode.GENERATE -> return true Mode.GENERATE -> return true
Mode.AUTO -> { Mode.AUTO -> {
if (teeBroken == true) return true if (teeBroken == true) return true
} }
else -> {} else -> {}
} }
} }
return false return false
}.onFailure { Logger.e("failed to get packages", it) }.getOrNull() ?: false }.onFailure { Logger.e("failed to get packages", it) }.getOrNull() ?: false
@Volatile @Volatile
var _customPatchLevel: CustomPatchLevel? = null var _customPatchLevel: CustomPatchLevel? = null
fun updatePatchLevel(f: File?) = runCatching { fun updatePatchLevel(f: File?) = runCatching {
if (f == null || !f.exists()) { if (f == null || !f.exists()) {
_customPatchLevel = null _customPatchLevel = null
return@runCatching return@runCatching
} }
val lines = f.readLines().map { it.trim() }.filter { it.isNotEmpty() && !it.startsWith("#") } val lines = f.readLines().map { it.trim() }.filter { it.isNotEmpty() && !it.startsWith("#") }
if (lines.isEmpty()) { if (lines.isEmpty()) {
_customPatchLevel = null _customPatchLevel = null
return@runCatching return@runCatching
} }
if (lines.size == 1 && !lines[0].contains("=")) { if (lines.size == 1 && !lines[0].contains("=")) {
_customPatchLevel = CustomPatchLevel(all = lines[0]) _customPatchLevel = CustomPatchLevel(all = lines[0])
return@runCatching return@runCatching
} }
val map = mutableMapOf<String, String>() val map = mutableMapOf<String, String>()
for (line in lines) { for (line in lines) {
val idx = line.indexOf('=') val idx = line.indexOf('=')
if (idx > 0) { if (idx > 0) {
val key = line.substring(0, idx).trim().lowercase() val key = line.substring(0, idx).trim().lowercase()
val value = line.substring(idx + 1).trim() val value = line.substring(idx + 1).trim()
map[key] = value map[key] = value
} }
} }
val all = map["all"] val all = map["all"]
_customPatchLevel = CustomPatchLevel( _customPatchLevel = CustomPatchLevel(
system = map["system"] ?: all, system = map["system"] ?: all,
vendor = map["vendor"] ?: all, vendor = map["vendor"] ?: all,
boot = map["boot"] ?: all, boot = map["boot"] ?: all,
all = all all = all
) )
}.onFailure { }.onFailure {
Logger.e("failed to update patch level", it) Logger.e("failed to update patch level", it)
} }
} }
data class CustomPatchLevel( data class CustomPatchLevel(
val system: String? = null, val system: String? = null,
val vendor: String? = null, val vendor: String? = null,
val boot: String? = null, val boot: String? = null,
val all: String? = null val all: String? = null
) )
@@ -1,64 +1,64 @@
/* /*
* Copyright 2025 Dakkshesh <beakthoven@gmail.com> * Copyright 2025 Dakkshesh <beakthoven@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
package io.github.beakthoven.TrickyStoreOSS.core.logging package io.github.beakthoven.TrickyStoreOSS.core.logging
import android.util.Log import android.util.Log
object Logger { object Logger {
const val TAG = "TrickyStore" const val TAG = "TrickyStore"
sealed class LogLevel(val priority: Int) { sealed class LogLevel(val priority: Int) {
object Debug : LogLevel(Log.DEBUG) object Debug : LogLevel(Log.DEBUG)
object Info : LogLevel(Log.INFO) object Info : LogLevel(Log.INFO)
object Warning : LogLevel(Log.WARN) object Warning : LogLevel(Log.WARN)
object Error : LogLevel(Log.ERROR) object Error : LogLevel(Log.ERROR)
object Verbose : LogLevel(Log.VERBOSE) object Verbose : LogLevel(Log.VERBOSE)
} }
fun d(message: String) { fun d(message: String) {
Log.d(TAG, message) Log.d(TAG, message)
} }
fun e(message: String) { fun e(message: String) {
Log.e(TAG, message) Log.e(TAG, message)
} }
fun e(message: String, throwable: Throwable) { fun e(message: String, throwable: Throwable) {
Log.e(TAG, "wtf: $message", throwable) Log.e(TAG, "wtf: $message", throwable)
} }
fun i(message: String) { fun i(message: String) {
Log.i(TAG, message) Log.i(TAG, message)
} }
fun w(message: String) { fun w(message: String) {
Log.w(TAG, message) Log.w(TAG, message)
} }
fun w(message: String, throwable: Throwable) { fun w(message: String, throwable: Throwable) {
Log.w(TAG, message, throwable) Log.w(TAG, message, throwable)
} }
fun v(message: String) { fun v(message: String) {
Log.v(TAG, message) Log.v(TAG, message)
} }
fun log(level: LogLevel, message: String, throwable: Throwable? = null) { fun log(level: LogLevel, message: String, throwable: Throwable? = null) {
when (level) { when (level) {
is LogLevel.Debug -> if (throwable != null) Log.d(TAG, message, throwable) else Log.d(TAG, message) is LogLevel.Debug -> if (throwable != null) Log.d(TAG, message, throwable) else Log.d(TAG, message)
is LogLevel.Info -> if (throwable != null) Log.i(TAG, message, throwable) else Log.i(TAG, message) is LogLevel.Info -> if (throwable != null) Log.i(TAG, message, throwable) else Log.i(TAG, message)
is LogLevel.Warning -> if (throwable != null) Log.w(TAG, message, throwable) else Log.w(TAG, message) is LogLevel.Warning -> if (throwable != null) Log.w(TAG, message, throwable) else Log.w(TAG, message)
is LogLevel.Error -> if (throwable != null) Log.e(TAG, message, throwable) else Log.e(TAG, message) is LogLevel.Error -> if (throwable != null) Log.e(TAG, message, throwable) else Log.e(TAG, message)
is LogLevel.Verbose -> if (throwable != null) Log.v(TAG, message, throwable) else Log.v(TAG, message) is LogLevel.Verbose -> if (throwable != null) Log.v(TAG, message, throwable) else Log.v(TAG, message)
} }
} }
fun logIf(level: LogLevel, condition: Boolean = true, messageProvider: () -> String) { fun logIf(level: LogLevel, condition: Boolean = true, messageProvider: () -> String) {
if (condition && Log.isLoggable(TAG, level.priority)) { if (condition && Log.isLoggable(TAG, level.priority)) {
log(level, messageProvider()) log(level, messageProvider())
} }
} }
} }
@@ -1,181 +1,181 @@
/* /*
* Copyright 2025 Dakkshesh <beakthoven@gmail.com> * Copyright 2025 Dakkshesh <beakthoven@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
package io.github.beakthoven.TrickyStoreOSS.interceptors package io.github.beakthoven.TrickyStoreOSS.interceptors
import android.os.Binder import android.os.Binder
import android.os.IBinder import android.os.IBinder
import android.os.Parcel import android.os.Parcel
import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
open class BinderInterceptor : Binder() { open class BinderInterceptor : Binder() {
sealed class Result sealed class Result
data object Skip : Result() data object Skip : Result()
data object Continue : Result() data object Continue : Result()
data class OverrideData(val data: Parcel) : Result() data class OverrideData(val data: Parcel) : Result()
data class OverrideReply(val code: Int = 0, val reply: Parcel) : Result() data class OverrideReply(val code: Int = 0, val reply: Parcel) : Result()
companion object { companion object {
private const val BACKDOOR_TRANSACTION_CODE = 0xdeadbeef.toInt() private const val BACKDOOR_TRANSACTION_CODE = 0xdeadbeef.toInt()
private const val REGISTER_INTERCEPTOR_CODE = 1 private const val REGISTER_INTERCEPTOR_CODE = 1
private const val PRE_TRANSACT_CODE = 1 private const val PRE_TRANSACT_CODE = 1
private const val POST_TRANSACT_CODE = 2 private const val POST_TRANSACT_CODE = 2
private const val RESULT_SKIP = 1 private const val RESULT_SKIP = 1
private const val RESULT_CONTINUE = 2 private const val RESULT_CONTINUE = 2
private const val RESULT_OVERRIDE_REPLY = 3 private const val RESULT_OVERRIDE_REPLY = 3
private const val RESULT_OVERRIDE_DATA = 4 private const val RESULT_OVERRIDE_DATA = 4
fun getBinderBackdoor(binder: IBinder): IBinder? { fun getBinderBackdoor(binder: IBinder): IBinder? {
val data = Parcel.obtain() val data = Parcel.obtain()
val reply = Parcel.obtain() val reply = Parcel.obtain()
return try { return try {
val success = binder.transact(BACKDOOR_TRANSACTION_CODE, data, reply, 0) val success = binder.transact(BACKDOOR_TRANSACTION_CODE, data, reply, 0)
if (success) { if (success) {
Logger.d("Backdoor access granted for binder: $binder") Logger.d("Backdoor access granted for binder: $binder")
reply.readStrongBinder() reply.readStrongBinder()
} else { } else {
Logger.d("Backdoor access denied for binder: $binder") Logger.d("Backdoor access denied for binder: $binder")
null null
} }
} catch (e: Exception) { } catch (e: Exception) {
Logger.e("Failed to access binder backdoor", e) Logger.e("Failed to access binder backdoor", e)
null null
} finally { } finally {
data.recycle() data.recycle()
reply.recycle() reply.recycle()
} }
} }
fun registerBinderInterceptor( fun registerBinderInterceptor(
backdoor: IBinder, backdoor: IBinder,
target: IBinder, target: IBinder,
interceptor: BinderInterceptor interceptor: BinderInterceptor
) { ) {
val data = Parcel.obtain() val data = Parcel.obtain()
val reply = Parcel.obtain() val reply = Parcel.obtain()
try { try {
data.writeStrongBinder(target) data.writeStrongBinder(target)
data.writeStrongBinder(interceptor) data.writeStrongBinder(interceptor)
backdoor.transact(REGISTER_INTERCEPTOR_CODE, data, reply, 0) backdoor.transact(REGISTER_INTERCEPTOR_CODE, data, reply, 0)
Logger.d("Registered interceptor for target: $target") Logger.d("Registered interceptor for target: $target")
} catch (e: Exception) { } catch (e: Exception) {
Logger.e("Failed to register binder interceptor", e) Logger.e("Failed to register binder interceptor", e)
} finally { } finally {
data.recycle() data.recycle()
reply.recycle() reply.recycle()
} }
} }
} }
open fun onPreTransact( open fun onPreTransact(
target: IBinder, target: IBinder,
code: Int, code: Int,
flags: Int, flags: Int,
callingUid: Int, callingUid: Int,
callingPid: Int, callingPid: Int,
data: Parcel data: Parcel
): Result = Skip ): Result = Skip
open fun onPostTransact( open fun onPostTransact(
target: IBinder, target: IBinder,
code: Int, code: Int,
flags: Int, flags: Int,
callingUid: Int, callingUid: Int,
callingPid: Int, callingPid: Int,
data: Parcel, data: Parcel,
reply: Parcel?, reply: Parcel?,
resultCode: Int resultCode: Int
): Result = Skip ): Result = Skip
override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean { override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
val result = when (code) { val result = when (code) {
PRE_TRANSACT_CODE -> handlePreTransact(data) PRE_TRANSACT_CODE -> handlePreTransact(data)
POST_TRANSACT_CODE -> handlePostTransact(data) POST_TRANSACT_CODE -> handlePostTransact(data)
else -> return super.onTransact(code, data, reply, flags) else -> return super.onTransact(code, data, reply, flags)
} }
writeResultToReply(result, reply!!) writeResultToReply(result, reply!!)
return true return true
} }
private fun handlePreTransact(data: Parcel): Result { private fun handlePreTransact(data: Parcel): Result {
val target = data.readStrongBinder() val target = data.readStrongBinder()
val transactionCode = data.readInt() val transactionCode = data.readInt()
val transactionFlags = data.readInt() val transactionFlags = data.readInt()
val callingUid = data.readInt() val callingUid = data.readInt()
val callingPid = data.readInt() val callingPid = data.readInt()
val dataSize = data.readLong() val dataSize = data.readLong()
val transactionData = Parcel.obtain() val transactionData = Parcel.obtain()
return try { return try {
transactionData.appendFrom(data, data.dataPosition(), dataSize.toInt()) transactionData.appendFrom(data, data.dataPosition(), dataSize.toInt())
transactionData.setDataPosition(0) transactionData.setDataPosition(0)
onPreTransact(target, transactionCode, transactionFlags, callingUid, callingPid, transactionData) onPreTransact(target, transactionCode, transactionFlags, callingUid, callingPid, transactionData)
} finally { } finally {
transactionData.recycle() transactionData.recycle()
} }
} }
private fun handlePostTransact(data: Parcel): Result { private fun handlePostTransact(data: Parcel): Result {
val target = data.readStrongBinder() val target = data.readStrongBinder()
val transactionCode = data.readInt() val transactionCode = data.readInt()
val transactionFlags = data.readInt() val transactionFlags = data.readInt()
val callingUid = data.readInt() val callingUid = data.readInt()
val callingPid = data.readInt() val callingPid = data.readInt()
val resultCode = data.readInt() val resultCode = data.readInt()
val transactionData = Parcel.obtain() val transactionData = Parcel.obtain()
val transactionReply = Parcel.obtain() val transactionReply = Parcel.obtain()
return try { return try {
val dataSize = data.readLong().toInt() val dataSize = data.readLong().toInt()
transactionData.appendFrom(data, data.dataPosition(), dataSize) transactionData.appendFrom(data, data.dataPosition(), dataSize)
transactionData.setDataPosition(0) transactionData.setDataPosition(0)
data.setDataPosition(data.dataPosition() + dataSize) data.setDataPosition(data.dataPosition() + dataSize)
val replySize = data.readLong().toInt() val replySize = data.readLong().toInt()
val reply = if (replySize > 0) { val reply = if (replySize > 0) {
transactionReply.appendFrom(data, data.dataPosition(), replySize) transactionReply.appendFrom(data, data.dataPosition(), replySize)
transactionReply.setDataPosition(0) transactionReply.setDataPosition(0)
transactionReply transactionReply
} else null } else null
onPostTransact(target, transactionCode, transactionFlags, callingUid, callingPid, transactionData, reply, resultCode) onPostTransact(target, transactionCode, transactionFlags, callingUid, callingPid, transactionData, reply, resultCode)
} finally { } finally {
transactionData.recycle() transactionData.recycle()
transactionReply.recycle() transactionReply.recycle()
} }
} }
private fun writeResultToReply(result: Result, reply: Parcel) { private fun writeResultToReply(result: Result, reply: Parcel) {
when (result) { when (result) {
Skip -> reply.writeInt(RESULT_SKIP) Skip -> reply.writeInt(RESULT_SKIP)
Continue -> reply.writeInt(RESULT_CONTINUE) Continue -> reply.writeInt(RESULT_CONTINUE)
is OverrideReply -> { is OverrideReply -> {
reply.writeInt(RESULT_OVERRIDE_REPLY) reply.writeInt(RESULT_OVERRIDE_REPLY)
reply.writeInt(result.code) reply.writeInt(result.code)
reply.writeLong(result.reply.dataSize().toLong()) reply.writeLong(result.reply.dataSize().toLong())
reply.appendFrom(result.reply, 0, result.reply.dataSize()) reply.appendFrom(result.reply, 0, result.reply.dataSize())
result.reply.recycle() result.reply.recycle()
} }
is OverrideData -> { is OverrideData -> {
reply.writeInt(RESULT_OVERRIDE_DATA) reply.writeInt(RESULT_OVERRIDE_DATA)
reply.writeLong(result.data.dataSize().toLong()) reply.writeLong(result.data.dataSize().toLong())
reply.appendFrom(result.data, 0, result.data.dataSize()) reply.appendFrom(result.data, 0, result.data.dataSize())
result.data.recycle() result.data.recycle()
} }
} }
} }
} }
@@ -1,142 +1,142 @@
/* /*
* Copyright 2025 Dakkshesh <beakthoven@gmail.com> * Copyright 2025 Dakkshesh <beakthoven@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
package io.github.beakthoven.TrickyStoreOSS.interceptors package io.github.beakthoven.TrickyStoreOSS.interceptors
import android.os.IBinder import android.os.IBinder
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import android.os.ServiceManager import android.os.ServiceManager
import android.security.KeyStore import android.security.KeyStore
import android.security.keystore.KeystoreResponse import android.security.keystore.KeystoreResponse
import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
import kotlin.system.exitProcess import kotlin.system.exitProcess
abstract class BaseKeystoreInterceptor : BinderInterceptor() { abstract class BaseKeystoreInterceptor : BinderInterceptor() {
protected lateinit var keystore: IBinder protected lateinit var keystore: IBinder
protected var triedCount = 0 protected var triedCount = 0
protected var injected = false protected var injected = false
protected open val maxRetries: Int = 3 protected open val maxRetries: Int = 3
protected abstract val serviceName: String protected abstract val serviceName: String
protected abstract val injectionCommand: String protected abstract val injectionCommand: String
protected abstract val processName: String protected abstract val processName: String
fun tryRunKeystoreInterceptor(): Boolean { fun tryRunKeystoreInterceptor(): Boolean {
Logger.i("Trying to register ${this::class.simpleName} (attempt $triedCount)...") Logger.i("Trying to register ${this::class.simpleName} (attempt $triedCount)...")
val service = getService() ?: return false val service = getService() ?: return false
val backdoor = getBinderBackdoor(service) val backdoor = getBinderBackdoor(service)
return if (backdoor != null) { return if (backdoor != null) {
setupInterceptor(service, backdoor) setupInterceptor(service, backdoor)
} else { } else {
handleMissingBackdoor() handleMissingBackdoor()
} }
} }
protected open fun getService(): IBinder? = ServiceManager.getService(serviceName) protected open fun getService(): IBinder? = ServiceManager.getService(serviceName)
protected open fun setupInterceptor(service: IBinder, backdoor: IBinder): Boolean { protected open fun setupInterceptor(service: IBinder, backdoor: IBinder): Boolean {
keystore = service keystore = service
Logger.i("Registering for $serviceName: $keystore") Logger.i("Registering for $serviceName: $keystore")
registerBinderInterceptor(backdoor, service, this) registerBinderInterceptor(backdoor, service, this)
service.linkToDeath(createDeathRecipient(), 0) service.linkToDeath(createDeathRecipient(), 0)
onInterceptorSetup(service, backdoor) onInterceptorSetup(service, backdoor)
return true return true
} }
private fun handleMissingBackdoor(): Boolean { private fun handleMissingBackdoor(): Boolean {
if (triedCount >= maxRetries) { if (triedCount >= maxRetries) {
Logger.e("Tried injection $maxRetries times but still no backdoor, exiting") Logger.e("Tried injection $maxRetries times but still no backdoor, exiting")
exitProcess(1) exitProcess(1)
} }
if (!injected) { if (!injected) {
performInjection() performInjection()
injected = true injected = true
} }
triedCount++ triedCount++
return false return false
} }
protected open fun performInjection() { protected open fun performInjection() {
Logger.i("Attempting to inject into $processName...") Logger.i("Attempting to inject into $processName...")
val command = arrayOf("/system/bin/sh", "-c", injectionCommand) val command = arrayOf("/system/bin/sh", "-c", injectionCommand)
Logger.d("Injection command: ${command.joinToString(" ")}") Logger.d("Injection command: ${command.joinToString(" ")}")
val process = Runtime.getRuntime().exec(command) val process = Runtime.getRuntime().exec(command)
if (process.waitFor() != 0) { if (process.waitFor() != 0) {
Logger.e("Injection failed! Daemon will exit") Logger.e("Injection failed! Daemon will exit")
exitProcess(1) exitProcess(1)
} }
Logger.i("Injection completed successfully") Logger.i("Injection completed successfully")
} }
protected open fun createDeathRecipient(): IBinder.DeathRecipient = object : IBinder.DeathRecipient { protected open fun createDeathRecipient(): IBinder.DeathRecipient = object : IBinder.DeathRecipient {
override fun binderDied() { override fun binderDied() {
Logger.d("$serviceName died, daemon restarting") Logger.d("$serviceName died, daemon restarting")
exitProcess(0) exitProcess(0)
} }
} }
protected open fun onInterceptorSetup(service: IBinder, backdoor: IBinder) { protected open fun onInterceptorSetup(service: IBinder, backdoor: IBinder) {
// Default implementation does nothing // Default implementation does nothing
} }
} }
object InterceptorUtils { object InterceptorUtils {
fun createSuccessKeystoreResponse(): KeystoreResponse { fun createSuccessKeystoreResponse(): KeystoreResponse {
val parcel = Parcel.obtain() val parcel = Parcel.obtain()
try { try {
parcel.writeInt(KeyStore.NO_ERROR) parcel.writeInt(KeyStore.NO_ERROR)
parcel.writeString("") parcel.writeString("")
parcel.setDataPosition(0) parcel.setDataPosition(0)
return KeystoreResponse.CREATOR.createFromParcel(parcel) return KeystoreResponse.CREATOR.createFromParcel(parcel)
} finally { } finally {
parcel.recycle() parcel.recycle()
} }
} }
fun createSuccessReply(resultCode: Int = KeyStore.NO_ERROR): BinderInterceptor.OverrideReply { fun createSuccessReply(resultCode: Int = KeyStore.NO_ERROR): BinderInterceptor.OverrideReply {
val parcel = Parcel.obtain() val parcel = Parcel.obtain()
parcel.writeNoException() parcel.writeNoException()
parcel.writeInt(resultCode) parcel.writeInt(resultCode)
return BinderInterceptor.OverrideReply(0, parcel) return BinderInterceptor.OverrideReply(0, parcel)
} }
fun createByteArrayReply(data: ByteArray, resultCode: Int = KeyStore.NO_ERROR): BinderInterceptor.OverrideReply { fun createByteArrayReply(data: ByteArray, resultCode: Int = KeyStore.NO_ERROR): BinderInterceptor.OverrideReply {
val parcel = Parcel.obtain() val parcel = Parcel.obtain()
parcel.writeNoException() parcel.writeNoException()
parcel.writeByteArray(data) parcel.writeByteArray(data)
return BinderInterceptor.OverrideReply(resultCode, parcel) return BinderInterceptor.OverrideReply(resultCode, parcel)
} }
fun <T : Parcelable?> createTypedObjectReply(obj: T, flags: Int = 0, resultCode: Int = 0): BinderInterceptor.OverrideReply { fun <T : Parcelable?> createTypedObjectReply(obj: T, flags: Int = 0, resultCode: Int = 0): BinderInterceptor.OverrideReply {
val parcel = Parcel.obtain() val parcel = Parcel.obtain()
parcel.writeNoException() parcel.writeNoException()
parcel.writeTypedObject(obj, flags) parcel.writeTypedObject(obj, flags)
return BinderInterceptor.OverrideReply(resultCode, parcel) return BinderInterceptor.OverrideReply(resultCode, parcel)
} }
fun String.extractAlias(): String { fun String.extractAlias(): String {
return when { return when {
contains("_") -> split("_")[1] contains("_") -> split("_")[1]
else -> this else -> this
} }
} }
fun Parcel.hasException(): Boolean { fun Parcel.hasException(): Boolean {
return kotlin.runCatching { readException() }.exceptionOrNull() != null return kotlin.runCatching { readException() }.exceptionOrNull() != null
} }
} }
@@ -1,158 +1,158 @@
/* /*
* Copyright 2025 Dakkshesh <beakthoven@gmail.com> * Copyright 2025 Dakkshesh <beakthoven@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
package io.github.beakthoven.TrickyStoreOSS.interceptors package io.github.beakthoven.TrickyStoreOSS.interceptors
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.hardware.security.keymint.SecurityLevel import android.hardware.security.keymint.SecurityLevel
import android.os.IBinder import android.os.IBinder
import android.os.Parcel import android.os.Parcel
import android.system.keystore2.IKeystoreService import android.system.keystore2.IKeystoreService
import android.system.keystore2.KeyDescriptor import android.system.keystore2.KeyDescriptor
import android.system.keystore2.KeyEntryResponse import android.system.keystore2.KeyEntryResponse
import io.github.beakthoven.TrickyStoreOSS.CertificateHacker import io.github.beakthoven.TrickyStoreOSS.CertificateHacker
import io.github.beakthoven.TrickyStoreOSS.CertificateUtils import io.github.beakthoven.TrickyStoreOSS.CertificateUtils
import io.github.beakthoven.TrickyStoreOSS.core.config.Config import io.github.beakthoven.TrickyStoreOSS.core.config.Config
import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
import io.github.beakthoven.TrickyStoreOSS.getTransactCode import io.github.beakthoven.TrickyStoreOSS.getTransactCode
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.createTypedObjectReply import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.createTypedObjectReply
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.hasException import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.hasException
import io.github.beakthoven.TrickyStoreOSS.putCertificateChain import io.github.beakthoven.TrickyStoreOSS.putCertificateChain
@SuppressLint("BlockedPrivateApi") @SuppressLint("BlockedPrivateApi")
object Keystore2Interceptor : BaseKeystoreInterceptor() { object Keystore2Interceptor : BaseKeystoreInterceptor() {
private val getKeyEntryTransaction = private val getKeyEntryTransaction =
getTransactCode(IKeystoreService.Stub::class.java, "getKeyEntry") getTransactCode(IKeystoreService.Stub::class.java, "getKeyEntry")
private val deleteKeyTransaction = private val deleteKeyTransaction =
getTransactCode(IKeystoreService.Stub::class.java, "deleteKey") getTransactCode(IKeystoreService.Stub::class.java, "deleteKey")
override val serviceName = "android.system.keystore2.IKeystoreService/default" override val serviceName = "android.system.keystore2.IKeystoreService/default"
override val processName = "keystore2" override val processName = "keystore2"
override val injectionCommand = "exec ./inject `pidof keystore2` libTrickyStoreOSS.so entry" override val injectionCommand = "exec ./inject `pidof keystore2` libTrickyStoreOSS.so entry"
private var teeInterceptor: SecurityLevelInterceptor? = null private var teeInterceptor: SecurityLevelInterceptor? = null
private var strongBoxInterceptor: SecurityLevelInterceptor? = null private var strongBoxInterceptor: SecurityLevelInterceptor? = null
override fun onInterceptorSetup(service: IBinder, backdoor: IBinder) { override fun onInterceptorSetup(service: IBinder, backdoor: IBinder) {
setupSecurityLevelInterceptors(service, backdoor) setupSecurityLevelInterceptors(service, backdoor)
} }
private fun setupSecurityLevelInterceptors(service: IBinder, backdoor: IBinder) { private fun setupSecurityLevelInterceptors(service: IBinder, backdoor: IBinder) {
val ks = IKeystoreService.Stub.asInterface(service) val ks = IKeystoreService.Stub.asInterface(service)
val tee = kotlin.runCatching { ks.getSecurityLevel(SecurityLevel.TRUSTED_ENVIRONMENT) } val tee = kotlin.runCatching { ks.getSecurityLevel(SecurityLevel.TRUSTED_ENVIRONMENT) }
.getOrNull() .getOrNull()
if (tee != null) { if (tee != null) {
Logger.i("Registering for TEE SecurityLevel: $tee") Logger.i("Registering for TEE SecurityLevel: $tee")
val interceptor = SecurityLevelInterceptor(tee, SecurityLevel.TRUSTED_ENVIRONMENT) val interceptor = SecurityLevelInterceptor(tee, SecurityLevel.TRUSTED_ENVIRONMENT)
registerBinderInterceptor(backdoor, tee.asBinder(), interceptor) registerBinderInterceptor(backdoor, tee.asBinder(), interceptor)
teeInterceptor = interceptor teeInterceptor = interceptor
} else { } else {
Logger.i("No TEE SecurityLevel found") Logger.i("No TEE SecurityLevel found")
} }
val strongBox = kotlin.runCatching { ks.getSecurityLevel(SecurityLevel.STRONGBOX) } val strongBox = kotlin.runCatching { ks.getSecurityLevel(SecurityLevel.STRONGBOX) }
.getOrNull() .getOrNull()
if (strongBox != null) { if (strongBox != null) {
Logger.i("Registering for StrongBox SecurityLevel: $strongBox") Logger.i("Registering for StrongBox SecurityLevel: $strongBox")
val interceptor = SecurityLevelInterceptor(strongBox, SecurityLevel.STRONGBOX) val interceptor = SecurityLevelInterceptor(strongBox, SecurityLevel.STRONGBOX)
registerBinderInterceptor(backdoor, strongBox.asBinder(), interceptor) registerBinderInterceptor(backdoor, strongBox.asBinder(), interceptor)
strongBoxInterceptor = interceptor strongBoxInterceptor = interceptor
} else { } else {
Logger.i("No StrongBox SecurityLevel found") Logger.i("No StrongBox SecurityLevel found")
} }
} }
override fun onPreTransact( override fun onPreTransact(
target: IBinder, target: IBinder,
code: Int, code: Int,
flags: Int, flags: Int,
callingUid: Int, callingUid: Int,
callingPid: Int, callingPid: Int,
data: Parcel data: Parcel
): Result { ): Result {
if (code == getKeyEntryTransaction) { if (code == getKeyEntryTransaction) {
if (CertificateHacker.canHack()) { if (CertificateHacker.canHack()) {
Logger.d("intercept pre $target uid=$callingUid pid=$callingPid dataSz=${data.dataSize()}") Logger.d("intercept pre $target uid=$callingUid pid=$callingPid dataSz=${data.dataSize()}")
kotlin.runCatching { kotlin.runCatching {
data.enforceInterface(IKeystoreService.DESCRIPTOR) data.enforceInterface(IKeystoreService.DESCRIPTOR)
val descriptor = data.readTypedObject(KeyDescriptor.CREATOR) ?: return@runCatching val descriptor = data.readTypedObject(KeyDescriptor.CREATOR) ?: return@runCatching
if (Config.needGenerate(callingUid)) { if (Config.needGenerate(callingUid)) {
val response = SecurityLevelInterceptor.getKeyResponse(callingUid, descriptor.alias) val response = SecurityLevelInterceptor.getKeyResponse(callingUid, descriptor.alias)
?: return@runCatching ?: return@runCatching
Logger.i("Generate key for uid=$callingUid alias=${descriptor.alias}") Logger.i("Generate key for uid=$callingUid alias=${descriptor.alias}")
return createTypedObjectReply(response) return createTypedObjectReply(response)
} else if (Config.needHack(callingUid)) { } else if (Config.needHack(callingUid)) {
if (SecurityLevelInterceptor.shouldSkipLeafHack(callingUid, descriptor.alias)) { if (SecurityLevelInterceptor.shouldSkipLeafHack(callingUid, descriptor.alias)) {
Logger.i("skip leaf hack for uid=$callingUid alias=${descriptor.alias}") Logger.i("skip leaf hack for uid=$callingUid alias=${descriptor.alias}")
val response = SecurityLevelInterceptor.getKeyResponse(callingUid, descriptor.alias) val response = SecurityLevelInterceptor.getKeyResponse(callingUid, descriptor.alias)
if (response != null) { if (response != null) {
Logger.i("Found generated response for uid=$callingUid alias=${descriptor.alias}") Logger.i("Found generated response for uid=$callingUid alias=${descriptor.alias}")
return createTypedObjectReply(response) return createTypedObjectReply(response)
} else { } else {
Logger.e("No generated response found for uid=$callingUid alias=${descriptor.alias}") Logger.e("No generated response found for uid=$callingUid alias=${descriptor.alias}")
return@runCatching return@runCatching
} }
} else { } else {
Logger.i("proceeding with leaf hack for uid=$callingUid alias=${descriptor.alias}") Logger.i("proceeding with leaf hack for uid=$callingUid alias=${descriptor.alias}")
return Continue return Continue
} }
} }
return Skip return Skip
} }
} }
} }
return Skip return Skip
} }
override fun onPostTransact( override fun onPostTransact(
target: IBinder, target: IBinder,
code: Int, code: Int,
flags: Int, flags: Int,
callingUid: Int, callingUid: Int,
callingPid: Int, callingPid: Int,
data: Parcel, data: Parcel,
reply: Parcel?, reply: Parcel?,
resultCode: Int resultCode: Int
): Result { ): Result {
if (target != keystore || reply == null) return Skip if (target != keystore || reply == null) return Skip
if (reply.hasException()) return Skip if (reply.hasException()) return Skip
val p = Parcel.obtain() val p = Parcel.obtain()
Logger.d("intercept post $target uid=$callingUid pid=$callingPid dataSz=${data.dataSize()} replySz=${reply.dataSize()}") Logger.d("intercept post $target uid=$callingUid pid=$callingPid dataSz=${data.dataSize()} replySz=${reply.dataSize()}")
if (code == deleteKeyTransaction && resultCode == 0) { if (code == deleteKeyTransaction && resultCode == 0) {
data.enforceInterface("android.system.keystore2.IKeystoreService") data.enforceInterface("android.system.keystore2.IKeystoreService")
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR) val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)
if (keyDescriptor == null || keyDescriptor.domain == 0) return Skip if (keyDescriptor == null || keyDescriptor.domain == 0) return Skip
SecurityLevelInterceptor.keys.remove(SecurityLevelInterceptor.Key(callingUid, keyDescriptor.alias)) SecurityLevelInterceptor.keys.remove(SecurityLevelInterceptor.Key(callingUid, keyDescriptor.alias))
return Skip return Skip
} else if (code == getKeyEntryTransaction) { } else if (code == getKeyEntryTransaction) {
try { try {
data.enforceInterface("android.system.keystore2.IKeystoreService") data.enforceInterface("android.system.keystore2.IKeystoreService")
val response = reply.readTypedObject(KeyEntryResponse.CREATOR) val response = reply.readTypedObject(KeyEntryResponse.CREATOR)
if (response != null) { if (response != null) {
val chain = CertificateUtils.run { response.getCertificateChain() } val chain = CertificateUtils.run { response.getCertificateChain() }
if (chain != null) { if (chain != null) {
val newChain = CertificateHacker.hackCertificateChain(chain) val newChain = CertificateHacker.hackCertificateChain(chain)
response.putCertificateChain(newChain).getOrThrow() response.putCertificateChain(newChain).getOrThrow()
Logger.i("Hacked certificate for uid=$callingUid") Logger.i("Hacked certificate for uid=$callingUid")
return createTypedObjectReply(response) return createTypedObjectReply(response)
} else { } else {
p.recycle() p.recycle()
} }
} else { } else {
p.recycle() p.recycle()
} }
} catch (t: Throwable) { } catch (t: Throwable) {
Logger.e("failed to hack certificate chain of uid=$callingUid pid=$callingPid!", t) Logger.e("failed to hack certificate chain of uid=$callingUid pid=$callingPid!", t)
p.recycle() p.recycle()
} }
} }
return Skip return Skip
} }
} }
@@ -1,238 +1,238 @@
/* /*
* Copyright 2025 Dakkshesh <beakthoven@gmail.com> * Copyright 2025 Dakkshesh <beakthoven@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
package io.github.beakthoven.TrickyStoreOSS.interceptors package io.github.beakthoven.TrickyStoreOSS.interceptors
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.os.IBinder import android.os.IBinder
import android.os.Parcel import android.os.Parcel
import android.security.Credentials import android.security.Credentials
import android.security.KeyStore import android.security.KeyStore
import android.security.keymaster.ExportResult import android.security.keymaster.ExportResult
import android.security.keymaster.KeyCharacteristics import android.security.keymaster.KeyCharacteristics
import android.security.keymaster.KeymasterArguments import android.security.keymaster.KeymasterArguments
import android.security.keymaster.KeymasterCertificateChain import android.security.keymaster.KeymasterCertificateChain
import android.security.keymaster.KeymasterDefs import android.security.keymaster.KeymasterDefs
import android.security.keystore.IKeystoreCertificateChainCallback import android.security.keystore.IKeystoreCertificateChainCallback
import android.security.keystore.IKeystoreExportKeyCallback import android.security.keystore.IKeystoreExportKeyCallback
import android.security.keystore.IKeystoreKeyCharacteristicsCallback import android.security.keystore.IKeystoreKeyCharacteristicsCallback
import android.security.keystore.IKeystoreService import android.security.keystore.IKeystoreService
import io.github.beakthoven.TrickyStoreOSS.CertificateHacker import io.github.beakthoven.TrickyStoreOSS.CertificateHacker
import io.github.beakthoven.TrickyStoreOSS.core.config.Config import io.github.beakthoven.TrickyStoreOSS.core.config.Config
import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
import io.github.beakthoven.TrickyStoreOSS.getTransactCode import io.github.beakthoven.TrickyStoreOSS.getTransactCode
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.createByteArrayReply import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.createByteArrayReply
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.createSuccessKeystoreResponse import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.createSuccessKeystoreResponse
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.createSuccessReply import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.createSuccessReply
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.extractAlias import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.extractAlias
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.hasException import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.hasException
import java.math.BigInteger import java.math.BigInteger
import java.security.KeyPair import java.security.KeyPair
import java.util.Date import java.util.Date
@SuppressLint("BlockedPrivateApi") @SuppressLint("BlockedPrivateApi")
object KeystoreInterceptor : BaseKeystoreInterceptor() { object KeystoreInterceptor : BaseKeystoreInterceptor() {
private val getTransaction = private val getTransaction =
getTransactCode(IKeystoreService.Stub::class.java, "get") getTransactCode(IKeystoreService.Stub::class.java, "get")
private val generateKeyTransaction = private val generateKeyTransaction =
getTransactCode(IKeystoreService.Stub::class.java, "generateKey") getTransactCode(IKeystoreService.Stub::class.java, "generateKey")
private val getKeyCharacteristicsTransaction = private val getKeyCharacteristicsTransaction =
getTransactCode(IKeystoreService.Stub::class.java, "getKeyCharacteristics") getTransactCode(IKeystoreService.Stub::class.java, "getKeyCharacteristics")
private val exportKeyTransaction = private val exportKeyTransaction =
getTransactCode(IKeystoreService.Stub::class.java, "exportKey") getTransactCode(IKeystoreService.Stub::class.java, "exportKey")
private val attestKeyTransaction = private val attestKeyTransaction =
getTransactCode(IKeystoreService.Stub::class.java, "attestKey") getTransactCode(IKeystoreService.Stub::class.java, "attestKey")
override val serviceName = "android.security.keystore" override val serviceName = "android.security.keystore"
override val processName = "keystore" override val processName = "keystore"
override val injectionCommand = "exec ./inject `pidof keystore` libTrickyStoreOSS.so entry" override val injectionCommand = "exec ./inject `pidof keystore` libTrickyStoreOSS.so entry"
private const val DESCRIPTOR = "android.security.keystore.IKeystoreService" private const val DESCRIPTOR = "android.security.keystore.IKeystoreService"
private val keyArguments = HashMap<Key, CertificateHacker.KeyGenParameters>() private val keyArguments = HashMap<Key, CertificateHacker.KeyGenParameters>()
private val keyPairs = HashMap<Key, KeyPair>() private val keyPairs = HashMap<Key, KeyPair>()
data class Key(val uid: Int, val alias: String) data class Key(val uid: Int, val alias: String)
override fun onPreTransact( override fun onPreTransact(
target: IBinder, target: IBinder,
code: Int, code: Int,
flags: Int, flags: Int,
callingUid: Int, callingUid: Int,
callingPid: Int, callingPid: Int,
data: Parcel data: Parcel
): Result { ): Result {
if (CertificateHacker.canHack()) { if (CertificateHacker.canHack()) {
if (code == getTransaction) { if (code == getTransaction) {
if (Config.needHack(callingUid)) { if (Config.needHack(callingUid)) {
return Continue return Continue
} else if (Config.needGenerate(callingUid)) { } else if (Config.needGenerate(callingUid)) {
return Skip return Skip
} }
} else if (Config.needGenerate(callingUid)) { } else if (Config.needGenerate(callingUid)) {
when (code) { when (code) {
generateKeyTransaction -> { generateKeyTransaction -> {
kotlin.runCatching { kotlin.runCatching {
data.enforceInterface(DESCRIPTOR) data.enforceInterface(DESCRIPTOR)
val callback = IKeystoreKeyCharacteristicsCallback.Stub.asInterface(data.readStrongBinder()) val callback = IKeystoreKeyCharacteristicsCallback.Stub.asInterface(data.readStrongBinder())
val alias = data.readString()!!.extractAlias() val alias = data.readString()!!.extractAlias()
Logger.i("generateKeyTransaction uid $callingUid alias $alias") Logger.i("generateKeyTransaction uid $callingUid alias $alias")
val check = data.readInt() val check = data.readInt()
val kma = KeymasterArguments() val kma = KeymasterArguments()
val kgp = CertificateHacker.KeyGenParameters() val kgp = CertificateHacker.KeyGenParameters()
if (check == 1) { if (check == 1) {
kma.readFromParcel(data) kma.readFromParcel(data)
kgp.algorithm = kma.getEnum(KeymasterDefs.KM_TAG_ALGORITHM, 0) kgp.algorithm = kma.getEnum(KeymasterDefs.KM_TAG_ALGORITHM, 0)
kgp.keySize = kma.getUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 0).toInt() kgp.keySize = kma.getUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 0).toInt()
kgp.setEcCurveName(kgp.keySize) kgp.setEcCurveName(kgp.keySize)
kgp.purpose = kma.getEnums(KeymasterDefs.KM_TAG_PURPOSE) kgp.purpose = kma.getEnums(KeymasterDefs.KM_TAG_PURPOSE)
kgp.digest = kma.getEnums(KeymasterDefs.KM_TAG_DIGEST) kgp.digest = kma.getEnums(KeymasterDefs.KM_TAG_DIGEST)
kgp.certificateNotBefore = kma.getDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, Date()) kgp.certificateNotBefore = kma.getDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, Date())
if (kgp.algorithm == KeymasterDefs.KM_ALGORITHM_RSA) { if (kgp.algorithm == KeymasterDefs.KM_ALGORITHM_RSA) {
try { try {
val getArgumentByTag = KeymasterArguments::class.java.getDeclaredMethods().first { it.name == "getArgumentByTag" } val getArgumentByTag = KeymasterArguments::class.java.getDeclaredMethods().first { it.name == "getArgumentByTag" }
getArgumentByTag.isAccessible = true getArgumentByTag.isAccessible = true
val rsaArgument = getArgumentByTag.invoke(kma, KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT) val rsaArgument = getArgumentByTag.invoke(kma, KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT)
val getLongTagValue = KeymasterArguments::class.java.getDeclaredMethods().first { it.name == "getLongTagValue" } val getLongTagValue = KeymasterArguments::class.java.getDeclaredMethods().first { it.name == "getLongTagValue" }
getLongTagValue.isAccessible = true getLongTagValue.isAccessible = true
kgp.rsaPublicExponent = getLongTagValue.invoke(kma, rsaArgument) as BigInteger kgp.rsaPublicExponent = getLongTagValue.invoke(kma, rsaArgument) as BigInteger
} catch (ex: Exception) { } catch (ex: Exception) {
Logger.e("Read rsaPublicExponent error", ex) Logger.e("Read rsaPublicExponent error", ex)
} }
} }
keyArguments[Key(callingUid, alias)] = kgp keyArguments[Key(callingUid, alias)] = kgp
} }
val kc = KeyCharacteristics() val kc = KeyCharacteristics()
kc.swEnforced = KeymasterArguments() kc.swEnforced = KeymasterArguments()
kc.hwEnforced = kma kc.hwEnforced = kma
val ksr = createSuccessKeystoreResponse() val ksr = createSuccessKeystoreResponse()
callback.onFinished(ksr, kc) callback.onFinished(ksr, kc)
return createSuccessReply() return createSuccessReply()
}.onFailure { }.onFailure {
Logger.e("generateKeyTransaction error", it) Logger.e("generateKeyTransaction error", it)
} }
} }
getKeyCharacteristicsTransaction -> { getKeyCharacteristicsTransaction -> {
kotlin.runCatching { kotlin.runCatching {
data.enforceInterface(DESCRIPTOR) data.enforceInterface(DESCRIPTOR)
val callback = IKeystoreKeyCharacteristicsCallback.Stub.asInterface(data.readStrongBinder()) val callback = IKeystoreKeyCharacteristicsCallback.Stub.asInterface(data.readStrongBinder())
val alias = data.readString()!!.extractAlias() val alias = data.readString()!!.extractAlias()
Logger.i("getKeyCharacteristicsTransaction uid $callingUid alias $alias") Logger.i("getKeyCharacteristicsTransaction uid $callingUid alias $alias")
val kc = KeyCharacteristics() val kc = KeyCharacteristics()
val kma = KeymasterArguments() val kma = KeymasterArguments()
kma.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, keyArguments[Key(callingUid, alias)]!!.algorithm) kma.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, keyArguments[Key(callingUid, alias)]!!.algorithm)
kc.swEnforced = KeymasterArguments() kc.swEnforced = KeymasterArguments()
kc.hwEnforced = kma kc.hwEnforced = kma
val ksr = createSuccessKeystoreResponse() val ksr = createSuccessKeystoreResponse()
callback.onFinished(ksr, kc) callback.onFinished(ksr, kc)
return createSuccessReply() return createSuccessReply()
}.onFailure { }.onFailure {
Logger.e("getKeyCharacteristicsTransaction error", it) Logger.e("getKeyCharacteristicsTransaction error", it)
} }
} }
exportKeyTransaction -> { exportKeyTransaction -> {
kotlin.runCatching { kotlin.runCatching {
data.enforceInterface(DESCRIPTOR) data.enforceInterface(DESCRIPTOR)
val callback = IKeystoreExportKeyCallback.Stub.asInterface(data.readStrongBinder()) val callback = IKeystoreExportKeyCallback.Stub.asInterface(data.readStrongBinder())
val alias = data.readString()!!.extractAlias() val alias = data.readString()!!.extractAlias()
Logger.i("exportKeyTransaction uid $callingUid alias $alias") Logger.i("exportKeyTransaction uid $callingUid alias $alias")
val kp = CertificateHacker.generateKeyPair(keyArguments[Key(callingUid, alias)]!!) val kp = CertificateHacker.generateKeyPair(keyArguments[Key(callingUid, alias)]!!)
keyPairs[Key(callingUid, alias)] = kp!! keyPairs[Key(callingUid, alias)] = kp!!
val erP = Parcel.obtain() val erP = Parcel.obtain()
erP.writeInt(KeyStore.NO_ERROR) erP.writeInt(KeyStore.NO_ERROR)
erP.writeByteArray(kp.public.encoded) erP.writeByteArray(kp.public.encoded)
erP.setDataPosition(0) erP.setDataPosition(0)
val er = ExportResult.CREATOR.createFromParcel(erP) val er = ExportResult.CREATOR.createFromParcel(erP)
erP.recycle() erP.recycle()
callback.onFinished(er) callback.onFinished(er)
return createSuccessReply() return createSuccessReply()
}.onFailure { }.onFailure {
Logger.e("exportKeyTransaction error", it) Logger.e("exportKeyTransaction error", it)
} }
} }
attestKeyTransaction -> { attestKeyTransaction -> {
kotlin.runCatching { kotlin.runCatching {
data.enforceInterface(DESCRIPTOR) data.enforceInterface(DESCRIPTOR)
val callback = IKeystoreCertificateChainCallback.Stub.asInterface(data.readStrongBinder()) val callback = IKeystoreCertificateChainCallback.Stub.asInterface(data.readStrongBinder())
val alias = data.readString()!!.extractAlias() val alias = data.readString()!!.extractAlias()
Logger.i("attestKeyTransaction uid $callingUid alias $alias") Logger.i("attestKeyTransaction uid $callingUid alias $alias")
val check = data.readInt() val check = data.readInt()
val kma = KeymasterArguments() val kma = KeymasterArguments()
if (check == 1) { if (check == 1) {
kma.readFromParcel(data) kma.readFromParcel(data)
val attestationChallenge = kma.getBytes(KeymasterDefs.KM_TAG_ATTESTATION_CHALLENGE, ByteArray(0)) val attestationChallenge = kma.getBytes(KeymasterDefs.KM_TAG_ATTESTATION_CHALLENGE, ByteArray(0))
val ksr = createSuccessKeystoreResponse() val ksr = createSuccessKeystoreResponse()
val key = Key(callingUid, alias) val key = Key(callingUid, alias)
val ka = keyArguments[key]!! val ka = keyArguments[key]!!
ka.attestationChallenge = attestationChallenge ka.attestationChallenge = attestationChallenge
val chain = CertificateHacker.generateChain(callingUid, ka, keyPairs[key]!!) val chain = CertificateHacker.generateChain(callingUid, ka, keyPairs[key]!!)
val kcc = KeymasterCertificateChain(chain) val kcc = KeymasterCertificateChain(chain)
callback.onFinished(ksr, kcc) callback.onFinished(ksr, kcc)
} }
return createSuccessReply() return createSuccessReply()
}.onFailure { }.onFailure {
Logger.e("attestKeyTransaction error", it) Logger.e("attestKeyTransaction error", it)
} }
} }
} }
} }
} }
return Skip return Skip
} }
override fun onPostTransact( override fun onPostTransact(
target: IBinder, target: IBinder,
code: Int, code: Int,
flags: Int, flags: Int,
callingUid: Int, callingUid: Int,
callingPid: Int, callingPid: Int,
data: Parcel, data: Parcel,
reply: Parcel?, reply: Parcel?,
resultCode: Int resultCode: Int
): Result { ): Result {
if (target != keystore || code != getTransaction || reply == null) return Skip if (target != keystore || code != getTransaction || reply == null) return Skip
if (reply.hasException()) return Skip if (reply.hasException()) return Skip
val p = Parcel.obtain() val p = Parcel.obtain()
Logger.d("intercept post $target uid=$callingUid pid=$callingPid dataSz=${data.dataSize()} replySz=${reply.dataSize()}") Logger.d("intercept post $target uid=$callingUid pid=$callingPid dataSz=${data.dataSize()} replySz=${reply.dataSize()}")
try { try {
data.enforceInterface(DESCRIPTOR) data.enforceInterface(DESCRIPTOR)
val alias = data.readString() ?: "" val alias = data.readString() ?: ""
var response = reply.createByteArray() var response = reply.createByteArray()
when { when {
alias.startsWith(Credentials.USER_CERTIFICATE) -> { alias.startsWith(Credentials.USER_CERTIFICATE) -> {
response = CertificateHacker.hackCertificateChainUSR(response!!, alias.extractAlias(), callingUid) response = CertificateHacker.hackCertificateChainUSR(response!!, alias.extractAlias(), callingUid)
Logger.i("Hacked leaf certificate for uid=$callingUid") Logger.i("Hacked leaf certificate for uid=$callingUid")
return createByteArrayReply(response) return createByteArrayReply(response)
} }
alias.startsWith(Credentials.CA_CERTIFICATE) -> { alias.startsWith(Credentials.CA_CERTIFICATE) -> {
response = CertificateHacker.hackCertificateChainCA(response!!, alias.extractAlias(), callingUid) response = CertificateHacker.hackCertificateChainCA(response!!, alias.extractAlias(), callingUid)
Logger.i("Hacked CA certificate chain for uid=$callingUid") Logger.i("Hacked CA certificate chain for uid=$callingUid")
return createByteArrayReply(response) return createByteArrayReply(response)
} }
else -> p.recycle() else -> p.recycle()
} }
} catch (t: Throwable) { } catch (t: Throwable) {
Logger.e("failed to hack certificate chain of uid=$callingUid pid=$callingPid!", t) Logger.e("failed to hack certificate chain of uid=$callingUid pid=$callingPid!", t)
p.recycle() p.recycle()
} }
return Skip return Skip
} }
} }
@@ -1,180 +1,180 @@
/* /*
* Copyright 2025 Dakkshesh <beakthoven@gmail.com> * Copyright 2025 Dakkshesh <beakthoven@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
package io.github.beakthoven.TrickyStoreOSS.interceptors package io.github.beakthoven.TrickyStoreOSS.interceptors
import android.hardware.security.keymint.KeyParameter import android.hardware.security.keymint.KeyParameter
import android.hardware.security.keymint.KeyParameterValue import android.hardware.security.keymint.KeyParameterValue
import android.hardware.security.keymint.Tag import android.hardware.security.keymint.Tag
import android.os.IBinder import android.os.IBinder
import android.os.Parcel import android.os.Parcel
import android.system.keystore2.Authorization import android.system.keystore2.Authorization
import android.system.keystore2.IKeystoreSecurityLevel import android.system.keystore2.IKeystoreSecurityLevel
import android.system.keystore2.KeyDescriptor import android.system.keystore2.KeyDescriptor
import android.system.keystore2.KeyEntryResponse import android.system.keystore2.KeyEntryResponse
import android.system.keystore2.KeyMetadata import android.system.keystore2.KeyMetadata
import androidx.annotation.Keep import androidx.annotation.Keep
import io.github.beakthoven.TrickyStoreOSS.CertificateHacker import io.github.beakthoven.TrickyStoreOSS.CertificateHacker
import io.github.beakthoven.TrickyStoreOSS.core.config.Config import io.github.beakthoven.TrickyStoreOSS.core.config.Config
import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
import io.github.beakthoven.TrickyStoreOSS.getTransactCode import io.github.beakthoven.TrickyStoreOSS.getTransactCode
import io.github.beakthoven.TrickyStoreOSS.putCertificateChain import io.github.beakthoven.TrickyStoreOSS.putCertificateChain
import java.security.KeyPair import java.security.KeyPair
import java.security.cert.Certificate import java.security.cert.Certificate
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
class SecurityLevelInterceptor( class SecurityLevelInterceptor(
private val original: IKeystoreSecurityLevel, private val original: IKeystoreSecurityLevel,
private val level: Int private val level: Int
) : BinderInterceptor() { ) : BinderInterceptor() {
companion object { companion object {
private val generateKeyTransaction = private val generateKeyTransaction =
getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "generateKey") getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "generateKey")
private val deleteKeyTransaction = private val deleteKeyTransaction =
getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "deleteKey") getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "deleteKey")
private val createOperationTransaction = private val createOperationTransaction =
getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "createOperation") getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "createOperation")
@Keep @Keep
val keys = ConcurrentHashMap<Key, Info>() val keys = ConcurrentHashMap<Key, Info>()
@Keep @Keep
val keyPairs = ConcurrentHashMap<Key, Pair<KeyPair, List<Certificate>>>() val keyPairs = ConcurrentHashMap<Key, Pair<KeyPair, List<Certificate>>>()
@Keep @Keep
val skipLeafHacks = ConcurrentHashMap<Key, Boolean>() val skipLeafHacks = ConcurrentHashMap<Key, Boolean>()
@Keep @Keep
fun getKeyResponse(uid: Int, alias: String): KeyEntryResponse? = fun getKeyResponse(uid: Int, alias: String): KeyEntryResponse? =
keys[Key(uid, alias)]?.response keys[Key(uid, alias)]?.response
@Keep @Keep
fun getKeyPairs(uid: Int, alias: String): Pair<KeyPair, List<Certificate>>? = fun getKeyPairs(uid: Int, alias: String): Pair<KeyPair, List<Certificate>>? =
keyPairs[Key(uid, alias)] keyPairs[Key(uid, alias)]
@Keep @Keep
fun shouldSkipLeafHack(uid: Int, alias: String): Boolean = fun shouldSkipLeafHack(uid: Int, alias: String): Boolean =
skipLeafHacks[Key(uid, alias)] ?: false skipLeafHacks[Key(uid, alias)] ?: false
} }
data class Key(val uid: Int, val alias: String) data class Key(val uid: Int, val alias: String)
data class Info(val keyPair: KeyPair, val response: KeyEntryResponse) data class Info(val keyPair: KeyPair, val response: KeyEntryResponse)
override fun onPreTransact( override fun onPreTransact(
target: IBinder, target: IBinder,
code: Int, code: Int,
flags: Int, flags: Int,
callingUid: Int, callingUid: Int,
callingPid: Int, callingPid: Int,
data: Parcel data: Parcel
): Result { ): Result {
if (code == generateKeyTransaction) { if (code == generateKeyTransaction) {
Logger.i("intercept key gen uid=$callingUid pid=$callingPid") Logger.i("intercept key gen uid=$callingUid pid=$callingPid")
kotlin.runCatching { kotlin.runCatching {
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR) data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
val keyDescriptor = val keyDescriptor =
data.readTypedObject(KeyDescriptor.CREATOR) ?: return@runCatching data.readTypedObject(KeyDescriptor.CREATOR) ?: return@runCatching
val attestationKeyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR) val attestationKeyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)
val params = data.createTypedArray(KeyParameter.CREATOR)!! val params = data.createTypedArray(KeyParameter.CREATOR)!!
val aFlags = data.readInt() val aFlags = data.readInt()
val entropy = data.createByteArray() val entropy = data.createByteArray()
val kgp = CertificateHacker.KeyGenParameters(params) val kgp = CertificateHacker.KeyGenParameters(params)
if (Config.needGenerate(callingUid)) { if (Config.needGenerate(callingUid)) {
val pair = CertificateHacker.generateKeyPair(callingUid, keyDescriptor, attestationKeyDescriptor, kgp) val pair = CertificateHacker.generateKeyPair(callingUid, keyDescriptor, attestationKeyDescriptor, kgp)
?: return@runCatching ?: return@runCatching
keyPairs[Key(callingUid, keyDescriptor.alias)] = Pair(pair.first, pair.second) keyPairs[Key(callingUid, keyDescriptor.alias)] = Pair(pair.first, pair.second)
val response = buildResponse(pair.second, kgp, attestationKeyDescriptor ?: keyDescriptor) val response = buildResponse(pair.second, kgp, attestationKeyDescriptor ?: keyDescriptor)
keys[Key(callingUid, keyDescriptor.alias)] = Info(pair.first, response) keys[Key(callingUid, keyDescriptor.alias)] = Info(pair.first, response)
val p = Parcel.obtain() val p = Parcel.obtain()
p.writeNoException() p.writeNoException()
p.writeTypedObject(response.metadata, 0) p.writeTypedObject(response.metadata, 0)
return OverrideReply(0, p) return OverrideReply(0, p)
} else if (Config.needHack(callingUid)) { } else if (Config.needHack(callingUid)) {
if ((kgp.purpose.contains(7)) || (attestationKeyDescriptor != null)) { if ((kgp.purpose.contains(7)) || (attestationKeyDescriptor != null)) {
Logger.i("Generating key in generation mode for attestation: uid=$callingUid alias=${keyDescriptor.alias}") Logger.i("Generating key in generation mode for attestation: uid=$callingUid alias=${keyDescriptor.alias}")
val pair = CertificateHacker.generateKeyPair(callingUid, keyDescriptor, attestationKeyDescriptor, kgp) val pair = CertificateHacker.generateKeyPair(callingUid, keyDescriptor, attestationKeyDescriptor, kgp)
?: return@runCatching ?: return@runCatching
keyPairs[Key(callingUid, keyDescriptor.alias)] = Pair(pair.first, pair.second) keyPairs[Key(callingUid, keyDescriptor.alias)] = Pair(pair.first, pair.second)
val response = buildResponse(pair.second, kgp, attestationKeyDescriptor ?: keyDescriptor) val response = buildResponse(pair.second, kgp, attestationKeyDescriptor ?: keyDescriptor)
keys[Key(callingUid, keyDescriptor.alias)] = Info(pair.first, response) keys[Key(callingUid, keyDescriptor.alias)] = Info(pair.first, response)
SecurityLevelInterceptor.skipLeafHacks[Key(callingUid, keyDescriptor.alias)] = true SecurityLevelInterceptor.skipLeafHacks[Key(callingUid, keyDescriptor.alias)] = true
val p = Parcel.obtain() val p = Parcel.obtain()
p.writeNoException() p.writeNoException()
p.writeTypedObject(response.metadata, 0) p.writeTypedObject(response.metadata, 0)
return OverrideReply(0, p) return OverrideReply(0, p)
} else { } else {
skipLeafHacks.remove(Key(callingUid, keyDescriptor.alias)) skipLeafHacks.remove(Key(callingUid, keyDescriptor.alias))
Logger.i("Cleared skip flag for non-attestation key: uid=$callingUid alias=${keyDescriptor.alias}") Logger.i("Cleared skip flag for non-attestation key: uid=$callingUid alias=${keyDescriptor.alias}")
return Skip return Skip
} }
} }
}.onFailure { }.onFailure {
Logger.e("parse key gen request", it) Logger.e("parse key gen request", it)
} }
} }
return Skip return Skip
} }
private fun buildResponse( private fun buildResponse(
chain: List<Certificate>, chain: List<Certificate>,
params: CertificateHacker.KeyGenParameters, params: CertificateHacker.KeyGenParameters,
descriptor: KeyDescriptor descriptor: KeyDescriptor
): KeyEntryResponse { ): KeyEntryResponse {
val response = KeyEntryResponse() val response = KeyEntryResponse()
val metadata = KeyMetadata() val metadata = KeyMetadata()
metadata.keySecurityLevel = level metadata.keySecurityLevel = level
metadata.putCertificateChain(chain.toTypedArray()).getOrThrow() metadata.putCertificateChain(chain.toTypedArray()).getOrThrow()
val d = KeyDescriptor() val d = KeyDescriptor()
d.domain = descriptor.domain d.domain = descriptor.domain
d.nspace = descriptor.nspace d.nspace = descriptor.nspace
metadata.key = d metadata.key = d
val authorizations = ArrayList<Authorization>() val authorizations = ArrayList<Authorization>()
var a: Authorization var a: Authorization
for (i in params.purpose.toList()) { for (i in params.purpose.toList()) {
a = Authorization() a = Authorization()
a.keyParameter = KeyParameter() a.keyParameter = KeyParameter()
a.keyParameter.tag = Tag.PURPOSE a.keyParameter.tag = Tag.PURPOSE
a.keyParameter.value = KeyParameterValue.keyPurpose(i) a.keyParameter.value = KeyParameterValue.keyPurpose(i)
a.securityLevel = level a.securityLevel = level
authorizations.add(a) authorizations.add(a)
} }
for (i in params.digest.toList()) { for (i in params.digest.toList()) {
a = Authorization() a = Authorization()
a.keyParameter = KeyParameter() a.keyParameter = KeyParameter()
a.keyParameter.tag = Tag.DIGEST a.keyParameter.tag = Tag.DIGEST
a.keyParameter.value = KeyParameterValue.digest(i) a.keyParameter.value = KeyParameterValue.digest(i)
a.securityLevel = level a.securityLevel = level
authorizations.add(a) authorizations.add(a)
} }
a = Authorization() a = Authorization()
a.keyParameter = KeyParameter() a.keyParameter = KeyParameter()
a.keyParameter.tag = Tag.ALGORITHM a.keyParameter.tag = Tag.ALGORITHM
a.keyParameter.value = KeyParameterValue.algorithm(params.algorithm) a.keyParameter.value = KeyParameterValue.algorithm(params.algorithm)
a.securityLevel = level a.securityLevel = level
authorizations.add(a) authorizations.add(a)
a = Authorization() a = Authorization()
a.keyParameter = KeyParameter() a.keyParameter = KeyParameter()
a.keyParameter.tag = Tag.KEY_SIZE a.keyParameter.tag = Tag.KEY_SIZE
a.keyParameter.value = KeyParameterValue.integer(params.keySize) a.keyParameter.value = KeyParameterValue.integer(params.keySize)
a.securityLevel = level a.securityLevel = level
authorizations.add(a) authorizations.add(a)
a = Authorization() a = Authorization()
a.keyParameter = KeyParameter() a.keyParameter = KeyParameter()
a.keyParameter.tag = Tag.EC_CURVE a.keyParameter.tag = Tag.EC_CURVE
a.keyParameter.value = KeyParameterValue.ecCurve(params.ecCurve) a.keyParameter.value = KeyParameterValue.ecCurve(params.ecCurve)
a.securityLevel = level a.securityLevel = level
authorizations.add(a) authorizations.add(a)
a = Authorization() a = Authorization()
a.keyParameter = KeyParameter() a.keyParameter = KeyParameter()
a.keyParameter.tag = Tag.NO_AUTH_REQUIRED a.keyParameter.tag = Tag.NO_AUTH_REQUIRED
a.keyParameter.value = KeyParameterValue.boolValue(true) a.keyParameter.value = KeyParameterValue.boolValue(true)
a.securityLevel = level a.securityLevel = level
authorizations.add(a) authorizations.add(a)
metadata.authorizations = authorizations.toTypedArray<Authorization>() metadata.authorizations = authorizations.toTypedArray<Authorization>()
response.metadata = metadata response.metadata = metadata
response.iSecurityLevel = original response.iSecurityLevel = original
return response return response
} }
} }
+6 -6
View File
@@ -1,6 +1,6 @@
#Mon Aug 04 09:32:06 IST 2025 #Mon Aug 04 09:32:06 IST 2025
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
Vendored
+89 -89
View File
@@ -1,89 +1,89 @@
@rem @rem
@rem Copyright 2015 the original author or authors. @rem Copyright 2015 the original author or authors.
@rem @rem
@rem Licensed under the Apache License, Version 2.0 (the "License"); @rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License. @rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at @rem You may obtain a copy of the License at
@rem @rem
@rem https://www.apache.org/licenses/LICENSE-2.0 @rem https://www.apache.org/licenses/LICENSE-2.0
@rem @rem
@rem Unless required by applicable law or agreed to in writing, software @rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS, @rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and @rem See the License for the specific language governing permissions and
@rem limitations under the License. @rem limitations under the License.
@rem @rem
@if "%DEBUG%" == "" @echo off @if "%DEBUG%" == "" @echo off
@rem ########################################################################## @rem ##########################################################################
@rem @rem
@rem Gradle startup script for Windows @rem Gradle startup script for Windows
@rem @rem
@rem ########################################################################## @rem ##########################################################################
@rem Set local scope for the variables with windows NT shell @rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=. if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter. @rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe @rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute if "%ERRORLEVEL%" == "0" goto execute
echo. echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo. echo.
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation. echo location of your Java installation.
goto fail goto fail
:findJavaFromJavaHome :findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=% set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute if exist "%JAVA_EXE%" goto execute
echo. echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo. echo.
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation. echo location of your Java installation.
goto fail goto fail
:execute :execute
@rem Setup the command line @rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle @rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd if "%ERRORLEVEL%"=="0" goto mainEnd
:fail :fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code! rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1 exit /b 1
:mainEnd :mainEnd
if "%OS%"=="Windows_NT" endlocal if "%OS%"=="Windows_NT" endlocal
:omega :omega
+114 -114
View File
@@ -1,114 +1,114 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<AndroidAttestation> <AndroidAttestation>
<NumberOfKeyboxes>1</NumberOfKeyboxes> <NumberOfKeyboxes>1</NumberOfKeyboxes>
<Keybox DeviceID="sw"> <Keybox DeviceID="sw">
<Key algorithm="ecdsa"> <Key algorithm="ecdsa">
<PrivateKey format="pem"> <PrivateKey format="pem">
-----BEGIN EC PRIVATE KEY----- -----BEGIN EC PRIVATE KEY-----
MHcCAQEEICHghkMqFRmEWc82OlD8FMnarfk19SfC39ceTW28QuVEoAoGCCqGSM49 MHcCAQEEICHghkMqFRmEWc82OlD8FMnarfk19SfC39ceTW28QuVEoAoGCCqGSM49
AwEHoUQDQgAE6555+EJjWazLKpFMiYbMcK2QZpOCqXMmE/6sy/ghJ0whdJdKKv6l AwEHoUQDQgAE6555+EJjWazLKpFMiYbMcK2QZpOCqXMmE/6sy/ghJ0whdJdKKv6l
uU1/ZtTgZRBmNbxTt6CjpnFYPts+Ea4QFA== uU1/ZtTgZRBmNbxTt6CjpnFYPts+Ea4QFA==
-----END EC PRIVATE KEY----- -----END EC PRIVATE KEY-----
</PrivateKey> </PrivateKey>
<CertificateChain> <CertificateChain>
<NumberOfCertificates>2</NumberOfCertificates> <NumberOfCertificates>2</NumberOfCertificates>
<Certificate format="pem"> <Certificate format="pem">
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIICeDCCAh6gAwIBAgICEAEwCgYIKoZIzj0EAwIwgZgxCzAJBgNVBAYTAlVTMRMw MIICeDCCAh6gAwIBAgICEAEwCgYIKoZIzj0EAwIwgZgxCzAJBgNVBAYTAlVTMRMw
EQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRUwEwYD EQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRUwEwYD
VQQKDAxHb29nbGUsIEluYy4xEDAOBgNVBAsMB0FuZHJvaWQxMzAxBgNVBAMMKkFu VQQKDAxHb29nbGUsIEluYy4xEDAOBgNVBAsMB0FuZHJvaWQxMzAxBgNVBAMMKkFu
ZHJvaWQgS2V5c3RvcmUgU29mdHdhcmUgQXR0ZXN0YXRpb24gUm9vdDAeFw0xNjAx ZHJvaWQgS2V5c3RvcmUgU29mdHdhcmUgQXR0ZXN0YXRpb24gUm9vdDAeFw0xNjAx
MTEwMDQ2MDlaFw0yNjAxMDgwMDQ2MDlaMIGIMQswCQYDVQQGEwJVUzETMBEGA1UE MTEwMDQ2MDlaFw0yNjAxMDgwMDQ2MDlaMIGIMQswCQYDVQQGEwJVUzETMBEGA1UE
CAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMR29vZ2xlLCBJbmMuMRAwDgYDVQQLDAdB CAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMR29vZ2xlLCBJbmMuMRAwDgYDVQQLDAdB
bmRyb2lkMTswOQYDVQQDDDJBbmRyb2lkIEtleXN0b3JlIFNvZnR3YXJlIEF0dGVz bmRyb2lkMTswOQYDVQQDDDJBbmRyb2lkIEtleXN0b3JlIFNvZnR3YXJlIEF0dGVz
dGF0aW9uIEludGVybWVkaWF0ZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOue dGF0aW9uIEludGVybWVkaWF0ZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOue
efhCY1msyyqRTImGzHCtkGaTgqlzJhP+rMv4ISdMIXSXSir+pblNf2bU4GUQZjW8 efhCY1msyyqRTImGzHCtkGaTgqlzJhP+rMv4ISdMIXSXSir+pblNf2bU4GUQZjW8
U7ego6ZxWD7bPhGuEBSjZjBkMB0GA1UdDgQWBBQ//KzWGrE6noEguNUlHMVlux6R U7ego6ZxWD7bPhGuEBSjZjBkMB0GA1UdDgQWBBQ//KzWGrE6noEguNUlHMVlux6R
qTAfBgNVHSMEGDAWgBTIrel3TEXDo88NFhDkeUM6IVowzzASBgNVHRMBAf8ECDAG qTAfBgNVHSMEGDAWgBTIrel3TEXDo88NFhDkeUM6IVowzzASBgNVHRMBAf8ECDAG
AQH/AgEAMA4GA1UdDwEB/wQEAwIChDAKBggqhkjOPQQDAgNIADBFAiBLipt77oK8 AQH/AgEAMA4GA1UdDwEB/wQEAwIChDAKBggqhkjOPQQDAgNIADBFAiBLipt77oK8
wDOHri/AiZi03cONqycqRZ9pDMfDktQPjgIhAO7aAV229DLp1IQ7YkyUBO86fMy9 wDOHri/AiZi03cONqycqRZ9pDMfDktQPjgIhAO7aAV229DLp1IQ7YkyUBO86fMy9
Xvsiu+f+uXc/WT/7 Xvsiu+f+uXc/WT/7
-----END CERTIFICATE----- -----END CERTIFICATE-----
</Certificate> </Certificate>
<Certificate format="pem"> <Certificate format="pem">
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIICizCCAjKgAwIBAgIJAKIFntEOQ1tXMAoGCCqGSM49BAMCMIGYMQswCQYDVQQG MIICizCCAjKgAwIBAgIJAKIFntEOQ1tXMAoGCCqGSM49BAMCMIGYMQswCQYDVQQG
EwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmll EwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmll
dzEVMBMGA1UECgwMR29vZ2xlLCBJbmMuMRAwDgYDVQQLDAdBbmRyb2lkMTMwMQYD dzEVMBMGA1UECgwMR29vZ2xlLCBJbmMuMRAwDgYDVQQLDAdBbmRyb2lkMTMwMQYD
VQQDDCpBbmRyb2lkIEtleXN0b3JlIFNvZnR3YXJlIEF0dGVzdGF0aW9uIFJvb3Qw VQQDDCpBbmRyb2lkIEtleXN0b3JlIFNvZnR3YXJlIEF0dGVzdGF0aW9uIFJvb3Qw
HhcNMTYwMTExMDA0MzUwWhcNMzYwMTA2MDA0MzUwWjCBmDELMAkGA1UEBhMCVVMx HhcNMTYwMTExMDA0MzUwWhcNMzYwMTA2MDA0MzUwWjCBmDELMAkGA1UEBhMCVVMx
EzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxFTAT EzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxFTAT
BgNVBAoMDEdvb2dsZSwgSW5jLjEQMA4GA1UECwwHQW5kcm9pZDEzMDEGA1UEAwwq BgNVBAoMDEdvb2dsZSwgSW5jLjEQMA4GA1UECwwHQW5kcm9pZDEzMDEGA1UEAwwq
QW5kcm9pZCBLZXlzdG9yZSBTb2Z0d2FyZSBBdHRlc3RhdGlvbiBSb290MFkwEwYH QW5kcm9pZCBLZXlzdG9yZSBTb2Z0d2FyZSBBdHRlc3RhdGlvbiBSb290MFkwEwYH
KoZIzj0CAQYIKoZIzj0DAQcDQgAE7l1ex+HA220Dpn7mthvsTWpdamguD/9/SQ59 KoZIzj0CAQYIKoZIzj0DAQcDQgAE7l1ex+HA220Dpn7mthvsTWpdamguD/9/SQ59
dx9EIm29sa/6FsvHrcV30lacqrewLVQBXT5DKyqO107sSHVBpKNjMGEwHQYDVR0O dx9EIm29sa/6FsvHrcV30lacqrewLVQBXT5DKyqO107sSHVBpKNjMGEwHQYDVR0O
BBYEFMit6XdMRcOjzw0WEOR5QzohWjDPMB8GA1UdIwQYMBaAFMit6XdMRcOjzw0W BBYEFMit6XdMRcOjzw0WEOR5QzohWjDPMB8GA1UdIwQYMBaAFMit6XdMRcOjzw0W
EOR5QzohWjDPMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgKEMAoGCCqG EOR5QzohWjDPMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgKEMAoGCCqG
SM49BAMCA0cAMEQCIDUho++LNEYenNVg8x1YiSBq3KNlQfYNns6KGYxmSGB7AiBN SM49BAMCA0cAMEQCIDUho++LNEYenNVg8x1YiSBq3KNlQfYNns6KGYxmSGB7AiBN
C/NR2TB8fVvaNTQdqEcbY6WFZTytTySn502vQX3xvw== C/NR2TB8fVvaNTQdqEcbY6WFZTytTySn502vQX3xvw==
-----END CERTIFICATE----- -----END CERTIFICATE-----
</Certificate> </Certificate>
</CertificateChain> </CertificateChain>
</Key> </Key>
<Key algorithm="rsa"> <Key algorithm="rsa">
<PrivateKey format="pem"> <PrivateKey format="pem">
-----BEGIN RSA PRIVATE KEY----- -----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDAgyPcVogbuDAgafWwhWHG7r5/BeL1qEIEir6LR752/q7yXPKb MIICXQIBAAKBgQDAgyPcVogbuDAgafWwhWHG7r5/BeL1qEIEir6LR752/q7yXPKb
KvoyABQWAUKZiaFfz8aBXrNjWDwv0vIL5Jgyg92BSxbX4YVBeuVKvClqOm21wAQI KvoyABQWAUKZiaFfz8aBXrNjWDwv0vIL5Jgyg92BSxbX4YVBeuVKvClqOm21wAQI
O2jFVsHwIzmRZBmGTVC3TUCuykhMdzVsiVoMJ1q/rEmdXX0jYvKcXgLocQIDAQAB O2jFVsHwIzmRZBmGTVC3TUCuykhMdzVsiVoMJ1q/rEmdXX0jYvKcXgLocQIDAQAB
AoGBAL6GCwuZqAKm+xpZQ4p7txUGWwmjbcbpysxr88AsNNfXnpTGYGQo2Ix7f2V3 AoGBAL6GCwuZqAKm+xpZQ4p7txUGWwmjbcbpysxr88AsNNfXnpTGYGQo2Ix7f2V3
wc3qZAdKvo5yht8fCBHclygmCGjeldMu/Ja20IT/JxpfYN78xwPno45uKbqaPF/C wc3qZAdKvo5yht8fCBHclygmCGjeldMu/Ja20IT/JxpfYN78xwPno45uKbqaPF/C
woB2tqiWrx0014gozpvdsfNPnJQEQweBKY4gExZyW728mTpBAkEA4cbZJ2RsCRbs woB2tqiWrx0014gozpvdsfNPnJQEQweBKY4gExZyW728mTpBAkEA4cbZJ2RsCRbs
NoJtWUmDdAwh8bB0xKGlmGfGaXlchdPcRkxbkp6Uv7NODcxQFLEPEzQat/3V9gQU NoJtWUmDdAwh8bB0xKGlmGfGaXlchdPcRkxbkp6Uv7NODcxQFLEPEzQat/3V9gQU
0qMmytQcxQJBANpIWZd4XNVjD7D9jFJU+Y5TjhiYOq6ea35qWntdNDdVuSGOvUAy 0qMmytQcxQJBANpIWZd4XNVjD7D9jFJU+Y5TjhiYOq6ea35qWntdNDdVuSGOvUAy
DSg4fXifdvohi8wti2il9kGPu+ylF5qzr70CQFD+/DJklVlhbtZTThVFCTKdk6PY DSg4fXifdvohi8wti2il9kGPu+ylF5qzr70CQFD+/DJklVlhbtZTThVFCTKdk6PY
ENvlvbmCKSz3i9i624Agro1X9LcdBThv/p6dsnHKNHejSZnbdvjl7OnA1J0CQBW3 ENvlvbmCKSz3i9i624Agro1X9LcdBThv/p6dsnHKNHejSZnbdvjl7OnA1J0CQBW3
TPJ8zv+Ls2vwTZ2DRrCaL3DS9EObDyasfgP36dH3fUuRX9KbKCPwOstdUgDghX/y TPJ8zv+Ls2vwTZ2DRrCaL3DS9EObDyasfgP36dH3fUuRX9KbKCPwOstdUgDghX/y
qAPpPu6W1iNc6VRCvCECQQCQp0XaiXCyzWSWYDJCKMX4KFb/1mW6moXI1g8bi+5x qAPpPu6W1iNc6VRCvCECQQCQp0XaiXCyzWSWYDJCKMX4KFb/1mW6moXI1g8bi+5x
fs0scurgHa2GunZU1M9FrbXx8rMdn4Eiz6XxpVcPmy0l fs0scurgHa2GunZU1M9FrbXx8rMdn4Eiz6XxpVcPmy0l
-----END RSA PRIVATE KEY----- -----END RSA PRIVATE KEY-----
</PrivateKey> </PrivateKey>
<CertificateChain> <CertificateChain>
<NumberOfCertificates>2</NumberOfCertificates> <NumberOfCertificates>2</NumberOfCertificates>
<Certificate format="pem"> <Certificate format="pem">
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIICtjCCAh+gAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwYzELMAkGA1UEBhMCVVMx MIICtjCCAh+gAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwYzELMAkGA1UEBhMCVVMx
EzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxFTAT EzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxFTAT
BgNVBAoMDEdvb2dsZSwgSW5jLjEQMA4GA1UECwwHQW5kcm9pZDAeFw0xNjAxMDQx BgNVBAoMDEdvb2dsZSwgSW5jLjEQMA4GA1UECwwHQW5kcm9pZDAeFw0xNjAxMDQx
MjQwNTNaFw0zNTEyMzAxMjQwNTNaMHYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApD MjQwNTNaFw0zNTEyMzAxMjQwNTNaMHYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApD
YWxpZm9ybmlhMRUwEwYDVQQKDAxHb29nbGUsIEluYy4xEDAOBgNVBAsMB0FuZHJv YWxpZm9ybmlhMRUwEwYDVQQKDAxHb29nbGUsIEluYy4xEDAOBgNVBAsMB0FuZHJv
aWQxKTAnBgNVBAMMIEFuZHJvaWQgU29mdHdhcmUgQXR0ZXN0YXRpb24gS2V5MIGf aWQxKTAnBgNVBAMMIEFuZHJvaWQgU29mdHdhcmUgQXR0ZXN0YXRpb24gS2V5MIGf
MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDAgyPcVogbuDAgafWwhWHG7r5/BeL1 MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDAgyPcVogbuDAgafWwhWHG7r5/BeL1
qEIEir6LR752/q7yXPKbKvoyABQWAUKZiaFfz8aBXrNjWDwv0vIL5Jgyg92BSxbX qEIEir6LR752/q7yXPKbKvoyABQWAUKZiaFfz8aBXrNjWDwv0vIL5Jgyg92BSxbX
4YVBeuVKvClqOm21wAQIO2jFVsHwIzmRZBmGTVC3TUCuykhMdzVsiVoMJ1q/rEmd 4YVBeuVKvClqOm21wAQIO2jFVsHwIzmRZBmGTVC3TUCuykhMdzVsiVoMJ1q/rEmd
XX0jYvKcXgLocQIDAQABo2YwZDAdBgNVHQ4EFgQU1AwQG/jNY7n3OVK1DhNcpteZ XX0jYvKcXgLocQIDAQABo2YwZDAdBgNVHQ4EFgQU1AwQG/jNY7n3OVK1DhNcpteZ
k4YwHwYDVR0jBBgwFoAUKfrxrMxN0kyWQCd1trDpMuUH/i4wEgYDVR0TAQH/BAgw k4YwHwYDVR0jBBgwFoAUKfrxrMxN0kyWQCd1trDpMuUH/i4wEgYDVR0TAQH/BAgw
BgEB/wIBADAOBgNVHQ8BAf8EBAMCAoQwDQYJKoZIhvcNAQELBQADgYEAni1IX4xn BgEB/wIBADAOBgNVHQ8BAf8EBAMCAoQwDQYJKoZIhvcNAQELBQADgYEAni1IX4xn
M9waha2Z11Aj6hTsQ7DhnerCI0YecrUZ3GAi5KVoMWwLVcTmnKItnzpPk2sxixZ4 M9waha2Z11Aj6hTsQ7DhnerCI0YecrUZ3GAi5KVoMWwLVcTmnKItnzpPk2sxixZ4
Fg2Iy9mLzICdhPDCJ+NrOPH90ecXcjFZNX2W88V/q52PlmEmT7K+gbsNSQQiis6f Fg2Iy9mLzICdhPDCJ+NrOPH90ecXcjFZNX2W88V/q52PlmEmT7K+gbsNSQQiis6f
9/VCLiVE+iEHElqDtVWtGIL4QBSbnCBjBH8= 9/VCLiVE+iEHElqDtVWtGIL4QBSbnCBjBH8=
-----END CERTIFICATE----- -----END CERTIFICATE-----
</Certificate> </Certificate>
<Certificate format="pem"> <Certificate format="pem">
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIICpzCCAhCgAwIBAgIJAP+U2d2fB8gMMA0GCSqGSIb3DQEBCwUAMGMxCzAJBgNV MIICpzCCAhCgAwIBAgIJAP+U2d2fB8gMMA0GCSqGSIb3DQEBCwUAMGMxCzAJBgNV
BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBW BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBW
aWV3MRUwEwYDVQQKDAxHb29nbGUsIEluYy4xEDAOBgNVBAsMB0FuZHJvaWQwHhcN aWV3MRUwEwYDVQQKDAxHb29nbGUsIEluYy4xEDAOBgNVBAsMB0FuZHJvaWQwHhcN
MTYwMTA0MTIzMTA4WhcNMzUxMjMwMTIzMTA4WjBjMQswCQYDVQQGEwJVUzETMBEG MTYwMTA0MTIzMTA4WhcNMzUxMjMwMTIzMTA4WjBjMQswCQYDVQQGEwJVUzETMBEG
A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEVMBMGA1UE A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEVMBMGA1UE
CgwMR29vZ2xlLCBJbmMuMRAwDgYDVQQLDAdBbmRyb2lkMIGfMA0GCSqGSIb3DQEB CgwMR29vZ2xlLCBJbmMuMRAwDgYDVQQLDAdBbmRyb2lkMIGfMA0GCSqGSIb3DQEB
AQUAA4GNADCBiQKBgQCia63rbi5EYe/VDoLmt5TRdSMfd5tjkWP/96r/C3JHTsAs AQUAA4GNADCBiQKBgQCia63rbi5EYe/VDoLmt5TRdSMfd5tjkWP/96r/C3JHTsAs
Q+wzfNes7UA+jCigZtX3hwszl94OuE4TQKuvpSe/lWmgMdsGUmX4RFlXYfC78hdL Q+wzfNes7UA+jCigZtX3hwszl94OuE4TQKuvpSe/lWmgMdsGUmX4RFlXYfC78hdL
t0GAZMAoDo9Sd47b0ke2RekZyOmLw9vCkT/X11DEHTVm+Vfkl5YLCazOkjWFmwID t0GAZMAoDo9Sd47b0ke2RekZyOmLw9vCkT/X11DEHTVm+Vfkl5YLCazOkjWFmwID
AQABo2MwYTAdBgNVHQ4EFgQUKfrxrMxN0kyWQCd1trDpMuUH/i4wHwYDVR0jBBgw AQABo2MwYTAdBgNVHQ4EFgQUKfrxrMxN0kyWQCd1trDpMuUH/i4wHwYDVR0jBBgw
FoAUKfrxrMxN0kyWQCd1trDpMuUH/i4wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B FoAUKfrxrMxN0kyWQCd1trDpMuUH/i4wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B
Af8EBAMCAoQwDQYJKoZIhvcNAQELBQADgYEAT3LzNlmNDsG5dFsxWfbwjSVJMJ6j Af8EBAMCAoQwDQYJKoZIhvcNAQELBQADgYEAT3LzNlmNDsG5dFsxWfbwjSVJMJ6j
HBwp0kUtILlNX2S06IDHeHqcOd6os/W/L3BfRxBcxebrTQaZYdKumgf/93y4q+uc HBwp0kUtILlNX2S06IDHeHqcOd6os/W/L3BfRxBcxebrTQaZYdKumgf/93y4q+uc
DyQHXrF/unlx/U1bnt8Uqf7f7XzAiF343ZtkMlbVNZriE/mPzsF83O+kqrJVw4Op DyQHXrF/unlx/U1bnt8Uqf7f7XzAiF343ZtkMlbVNZriE/mPzsF83O+kqrJVw4Op
Lvtc9mL1J1IXvmM= Lvtc9mL1J1IXvmM=
-----END CERTIFICATE----- -----END CERTIFICATE-----
</Certificate> </Certificate>
</CertificateChain> </CertificateChain>
</Key> </Key>
</Keybox> </Keybox>
</AndroidAttestation> </AndroidAttestation>