mirror of
https://github.com/kdrag0n/safetynet-fix.git
synced 2024-10-04 13:49:51 +00:00
java: Initial implementation of high-level module payload
R8 minification is essential for this, because otherwise the Kotlin standard library is too big and results in the app compiling to multiple dex files. It's not impossible to load multiple dex buffers, but let's keep it simple here.
This commit is contained in:
parent
9a63924813
commit
b0416cd3aa
@ -18,8 +18,9 @@ android {
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
@ -32,7 +33,5 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'androidx.core:core-ktx:1.6.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.3.1'
|
||||
implementation 'com.google.android.material:material:1.4.0'
|
||||
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.5.21'
|
||||
}
|
||||
|
10
java_module/app/proguard-rules.pro
vendored
10
java_module/app/proguard-rules.pro
vendored
@ -18,4 +18,12 @@
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
#-renamesourcefileattribute SourceFile
|
||||
|
||||
-keep class dev.kdrag0n.safetynetriru.EntryPoint {
|
||||
public static void init();
|
||||
}
|
||||
|
||||
-keepclassmembers class dev.kdrag0n.safetynetriru.ProxyKeyStoreSpi {
|
||||
public <init>(...);
|
||||
}
|
||||
|
@ -0,0 +1,16 @@
|
||||
package dev.kdrag0n.safetynetriru
|
||||
|
||||
@Suppress("unused")
|
||||
object EntryPoint {
|
||||
@JvmStatic
|
||||
fun init() {
|
||||
runCatching {
|
||||
logDebug("Entry point: Initializing SafetyNet patch")
|
||||
SecurityBridge.init()
|
||||
}.recoverCatching { e ->
|
||||
// Throwing an exception would require the JNI code to handle exceptions, so just catch
|
||||
// everything here.
|
||||
logDebug("Error in entry point", e)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
package dev.kdrag0n.safetynetriru
|
||||
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.security.Key
|
||||
import java.security.KeyStoreSpi
|
||||
import java.security.cert.Certificate
|
||||
import java.util.*
|
||||
|
||||
class ProxyKeyStoreSpi private constructor(
|
||||
private val orig: KeyStoreSpi,
|
||||
) : KeyStoreSpi() {
|
||||
@Suppress("unused")
|
||||
constructor() : this(androidImpl!!)
|
||||
|
||||
init {
|
||||
logDebug("Init proxy KeyStore SPI")
|
||||
}
|
||||
|
||||
// Avoid breaking other, legitimate uses of key attestation in Google Play Services, e.g.
|
||||
// - com.google.android.gms.auth.cryptauth.register.ReEnrollmentChimeraService
|
||||
// - tk_trace.129-RegisterForKeyPairOperation
|
||||
private fun isCallerSafetyNet() = Thread.currentThread().stackTrace.any {
|
||||
// a.a.engineGetCertificateChain(Unknown Source:15)
|
||||
// java.security.KeyStore.getCertificateChain(KeyStore.java:1087)
|
||||
// com.google.ccc.abuse.droidguard.DroidGuard.initNative(Native Method)
|
||||
// com.google.ccc.abuse.droidguard.DroidGuard.init(DroidGuard.java:447)
|
||||
// java.lang.reflect.Method.invoke(Native Method)
|
||||
// xvq.b(:com.google.android.gms@212621053@21.26.21 (190400-387928701):1)
|
||||
// xuc.a(:com.google.android.gms@212621053@21.26.21 (190400-387928701):5)
|
||||
// xuc.eX(:com.google.android.gms@212621053@21.26.21 (190400-387928701):1)
|
||||
// dzx.onTransact(:com.google.android.gms@212621053@21.26.21 (190400-387928701):8)
|
||||
// android.os.Binder.execTransactInternal(Binder.java:1179)
|
||||
// android.os.Binder.execTransact(Binder.java:1143)
|
||||
logDebug("Stack trace element: $it")
|
||||
it.className.contains("DroidGuard", ignoreCase = true)
|
||||
}
|
||||
|
||||
override fun engineGetCertificateChain(alias: String?): Array<Certificate>? {
|
||||
logDebug("Proxy key store: get certificate chain")
|
||||
|
||||
if (isCallerSafetyNet()) {
|
||||
logDebug("Blocking call")
|
||||
throw UnsupportedOperationException()
|
||||
} else {
|
||||
logDebug("Allowing call")
|
||||
return orig.engineGetCertificateChain(alias)
|
||||
}
|
||||
}
|
||||
|
||||
// Direct delegation. We have to do this manually because the Kotlin compiler can only do it
|
||||
// for interfaces, not abstract classes.
|
||||
override fun engineGetKey(alias: String?, password: CharArray?): Key? = orig.engineGetKey(alias, password)
|
||||
override fun engineGetCertificate(alias: String?): Certificate? = orig.engineGetCertificate(alias)
|
||||
override fun engineGetCreationDate(alias: String?): Date? = orig.engineGetCreationDate(alias)
|
||||
override fun engineSetKeyEntry(alias: String?, key: Key?, password: CharArray?, chain: Array<out Certificate>?) = orig.engineSetKeyEntry(alias, key, password, chain)
|
||||
override fun engineSetKeyEntry(alias: String?, key: ByteArray?, chain: Array<out Certificate>?) = orig.engineSetKeyEntry(alias, key, chain)
|
||||
override fun engineSetCertificateEntry(alias: String?, cert: Certificate?) = orig.engineSetCertificateEntry(alias, cert)
|
||||
override fun engineDeleteEntry(alias: String?) = orig.engineDeleteEntry(alias)
|
||||
override fun engineAliases(): Enumeration<String>? = orig.engineAliases()
|
||||
override fun engineContainsAlias(alias: String?) = orig.engineContainsAlias(alias)
|
||||
override fun engineSize() = orig.engineSize()
|
||||
override fun engineIsKeyEntry(alias: String?) = orig.engineIsKeyEntry(alias)
|
||||
override fun engineIsCertificateEntry(alias: String?) = orig.engineIsCertificateEntry(alias)
|
||||
override fun engineGetCertificateAlias(cert: Certificate?): String? = orig.engineGetCertificateAlias(cert)
|
||||
override fun engineStore(stream: OutputStream?, password: CharArray?) = orig.engineStore(stream, password)
|
||||
override fun engineLoad(stream: InputStream?, password: CharArray?) = orig.engineLoad(stream, password)
|
||||
|
||||
companion object {
|
||||
@Volatile internal var androidImpl: KeyStoreSpi? = null
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package dev.kdrag0n.safetynetriru
|
||||
|
||||
import java.security.Provider
|
||||
|
||||
// This is mostly just a pass-through provider that exists to change the provider's ClassLoader.
|
||||
// This works because Service looks up the class by name from the *provider* ClassLoader, not
|
||||
// necessarily the bootstrap one.
|
||||
class ProxyProvider(
|
||||
orig: Provider,
|
||||
) : Provider(orig.name, orig.version, orig.info) {
|
||||
init {
|
||||
logDebug("Init proxy provider - wrapping $orig")
|
||||
|
||||
putAll(orig)
|
||||
this["KeyStore.${SecurityBridge.PROVIDER_NAME}"] = ProxyKeyStoreSpi::class.java.name
|
||||
}
|
||||
|
||||
override fun getService(type: String?, algorithm: String?): Service? {
|
||||
logDebug("Provider: get service - type=$type algorithm=$algorithm")
|
||||
return super.getService(type, algorithm)
|
||||
}
|
||||
|
||||
override fun getServices(): MutableSet<Service>? {
|
||||
logDebug("Get services")
|
||||
return super.getServices()
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package dev.kdrag0n.safetynetriru
|
||||
|
||||
import java.security.KeyStore
|
||||
import java.security.KeyStoreSpi
|
||||
import java.security.Security
|
||||
|
||||
internal object SecurityBridge {
|
||||
const val PROVIDER_NAME = "AndroidKeyStore"
|
||||
|
||||
fun init() {
|
||||
logDebug("Initializing SecurityBridge")
|
||||
|
||||
val realProvider = Security.getProvider(PROVIDER_NAME)
|
||||
val realKeystore = KeyStore.getInstance(PROVIDER_NAME)
|
||||
val realSpi = realKeystore.get<KeyStoreSpi>("keyStoreSpi")
|
||||
logDebug("Real provider=$realProvider, keystore=$realKeystore, spi=$realSpi")
|
||||
|
||||
val provider = ProxyProvider(realProvider)
|
||||
logDebug("Removing real provider")
|
||||
Security.removeProvider("AndroidKeyStore")
|
||||
logDebug("Inserting provider $provider")
|
||||
Security.insertProviderAt(provider, 1)
|
||||
ProxyKeyStoreSpi.androidImpl = realSpi
|
||||
logDebug("Security hooks installed")
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package dev.kdrag0n.safetynetriru
|
||||
|
||||
import android.util.Log
|
||||
|
||||
private const val DEBUG = true
|
||||
private const val TAG = "SafetyNetRiru/SARU"
|
||||
|
||||
internal fun <T> Any.get(name: String) = this::class.java.getDeclaredField(name).let { field ->
|
||||
field.isAccessible = true
|
||||
@Suppress("unchecked_cast")
|
||||
field.get(this) as T
|
||||
}
|
||||
|
||||
internal fun logDebug(msg: String) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, msg)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun logDebug(msg: String, e: Throwable) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, msg, e)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user