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.