Voltar ao laboratório
Snippet Android 5 min

Snippet: Detectar Bypass de SSL Pinning no Android

19 de maio de 2026

Contexto

SSL Pinning garante que seu app só aceita conexões com certificados específicos. Atacantes usam proxies (Burp, Charles) ou Frida para contornar esse mecanismo. Este snippet detecta sinais de bypass em runtime.


1. Detector de Proxy/Man-in-the-Middle

Verifica se há um proxy HTTP configurado no dispositivo — indicador forte de tentativa de interceptação.

object ProxyDetector {

    fun isProxySet(): Boolean {
        val proxyHost = System.getProperty("http.proxyHost")
        val proxyPort = System.getProperty("http.proxyPort")
        
        if (!proxyHost.isNullOrBlank() && proxyPort != null) {
            return true
        }
        
        // Verificação via NetworkInfo (API 29+)
        val defaultProxy = ProxyInfo.buildDirectProxy("", 0)
        return defaultProxy.host?.isNotBlank() == true
    }
}

2. Detector de Frida

Frida injeta uma biblioteca nativa (frida-agent) no processo. Detectamos pela presença do arquivo e por portas abertas típicas do Frida Server.

object FridaDetector {

    fun isFridaPresent(): Boolean {
        return checkFridaFiles() || checkFridaPorts() || checkFridaThreads()
    }

    private fun checkFridaFiles(): Boolean {
        val fridaPaths = listOf(
            "/data/local/tmp/frida-server",
            "/data/local/tmp/re.frida.server",
            "/system/bin/frida-server",
        )
        return fridaPaths.any { File(it).exists() }
    }

    private fun checkFridaPorts(): Boolean {
        // Frida Server escuta na porta 27042 por padrão
        return try {
            Socket().use { socket ->
                socket.connect(InetSocketAddress("127.0.0.1", 27042), 200)
                true
            }
        } catch (e: Exception) {
            false
        }
    }

    private fun checkFridaThreads(): Boolean {
        return try {
            val maps = File("/proc/self/maps").readText()
            maps.contains("frida") || maps.contains("gum-js-loop")
        } catch (e: Exception) {
            false
        }
    }
}

3. Validação Adicional do Certificado no OkHttp

Além do pinning padrão, compare o fingerprint do certificado recebido com o esperado:

class CertificateValidator : X509TrustManager {

    // SHA-256 do certificado público esperado
    private val expectedFingerprint = "AA:BB:CC:DD:..."

    override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {
        val serverCert = chain[0]
        val actualFingerprint = serverCert.fingerprint()
        
        if (actualFingerprint != expectedFingerprint) {
            throw CertificateException("Certificado inválido: possível MITM detectado")
        }
    }

    private fun X509Certificate.fingerprint(): String {
        val digest = MessageDigest.getInstance("SHA-256")
        val bytes = digest.digest(encoded)
        return bytes.joinToString(":") { "%02X".format(it) }
    }

    override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {}
    override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
}

4. Integração na inicialização do app

class App : Application() {

    override fun onCreate() {
        super.onCreate()
        
        if (BuildConfig.DEBUG.not()) {
            runSecurityChecks()
        }
    }

    private fun runSecurityChecks() {
        when {
            FridaDetector.isFridaPresent() -> {
                reportThreat("frida_detected")
                exitProcess(1)
            }
            ProxyDetector.isProxySet() -> {
                reportThreat("proxy_detected")
                // Pode encerrar ou apenas alertar — decisão de produto
            }
        }
    }

    private fun reportThreat(type: String) {
        // Enviar evento para seu backend de segurança
        // Não bloquear na thread principal
        CoroutineScope(Dispatchers.IO).launch {
            securityApi.reportThreat(ThreatEvent(type = type, timestamp = System.currentTimeMillis()))
        }
    }
}

Considerações

O que este snippet NÃO cobre:

  • Bypass via patches de sistema (Xposed, LSPosed) — requer detecção adicional
  • Rooting avançado com Magisk Delta (DenyList) — use Play Integrity API
  • Emuladores — adicione isEmulator() check se necessário

Recomendação: Combine este detector com Play Integrity API (nível MEETS_DEVICE_INTEGRITY ou superior) para cobertura completa de ambientes comprometidos.


Referências

Snippet: Detectar Bypass de SSL Pinning no Android — APCosta Lab — APCosta