τTau SolutionsOfuscación

Anti-análisis y hardening

referencia técnica · Revisado el 12 de agosto de 2026

1. Alcance de este documento, antes de nada

Aquí se documenta qué comprueba cada técnica y cómo se identifica su presencia en el binario, para que el analista entienda por qué su entorno le está dando resultados raros. No se documenta cómo desactivarlas, ni cómo parchearlas, ni cómo evadirlas.

La razón por la que este documento existe no es la curiosidad: es que estas protecciones se manifiestan como fallos de herramienta. Una aplicación que cierra sola al arrancar en un emulador, un jadx que decompila cien clases vacías, un Frida que se desconecta a los tres segundos. Quien no sabe que existe una capa de anti-análisis interpreta esos síntomas como un bug en su instrumental y pierde horas. La sección 10 es la tabla de síntoma → causa que resuelve eso, y enlaza con Diagnóstico de fallos.

La fuente principal es la OWASP Mobile Application Security Testing Guide (MASTG), que es un catálogo público, con identificadores estables y orientado a verificar que una aplicación implementa estas defensas. Se cita por identificador. Cuando una técnica muy conocida no aparece en la página de MASTG que le correspondería, se dice explícitamente y se busca otra fuente con autor identificable, en vez de atribuírsela a OWASP.

2. Qué es y por qué existe

La ofuscación (Ofuscadores comerciales) y el empaquetado (Packers y protectores) atacan el análisis estático: hacen caro leer el binario. No sirven de nada contra el análisis dinámico, donde el analista deja que la aplicación se descifre a sí misma y observa lo que hace.

El anti-análisis es la respuesta a eso: código que, en ejecución, comprueba si el entorno es el de un usuario normal o el de alguien que está mirando, y reacciona. La industria lo vende bajo la sigla RASP (Runtime Application Self-Protection), y las fichas de producto de los ofuscadores comerciales lo listan como un bloque aparte. DexGuard, por ejemplo, declara seis: certificate checks, debugger and emulator checks, root detection, hook detection, tamper detection y malware protection.

Dos observaciones que conviene tener presentes al leer todo lo que sigue.

Estas comprobaciones son detecciones basadas en artefactos, no pruebas. MASTG lo dice de su categoría de detección de herramientas: «este tipo de detección se basa en artefactos. No prueba que el código o la memoria de la aplicación hayan sido modificados, pero puede proporcionar señales útiles de que el entorno de ejecución es sospechoso». Y advierte de la otra cara: «algunas comprobaciones pueden producir falsos positivos cuando un dispositivo contiene herramientas de seguridad, herramientas de desarrollo, herramientas de accesibilidad o software de monitorización empresarial».

La plataforma les ha ido quitando terreno. Varias de las técnicas clásicas ya no funcionan o funcionan a medias por cambios de Android, y el detalle importa porque explica falsos negativos:

Cambio Desde Efecto sobre la detección
Visibilidad de procesos y servicios restringida Android 7.0 (API 24) «Las APIs a nivel de aplicación no exponen de forma fiable demonios no relacionados como frida-server»
ActivityManager.getRunningServices limitado a los propios Android 8.0 (API 26) La enumeración de procesos deja de servir
Identificadores no reseteables restringidos Android 10 (API 29) Build.getSerial(), getImei(), getDeviceId(), getSubscriberId() exigen READ_PRIVILEGED_PHONE_STATE o lanzan SecurityException
Package visibility Android 11 (API 30) Consultar si está instalado un gestor de root o un emulador lanza NameNotFoundException como si no lo estuviera: falso negativo salvo que se declare <queries> o QUERY_ALL_PACKAGES

Ese último es el más útil para el analista: una aplicación que comprueba paquetes de root y declara QUERY_ALL_PACKAGES o una lista <queries> larga en el manifiesto está anunciando su propia detección, y eso se ve sin ejecutar nada.

3. Anti-debug

MASTG lo cubre en MASTG-KNOW-0028 («Anti-Debugging») y su test asociado MASTG-TEST-0046. El encuadre, literal: «hay que lidiar con dos protocolos de depuración en Android: se puede depurar a nivel Java con JDWP o en la capa nativa con un depurador basado en ptrace». Y distingue entre defensas preventivas —impedir que el depurador se conecte— y reactivas —detectar que ya está conectado y reaccionar.

Comprobación Qué mira exactamente
Flag debuggable android:debuggable del manifiesto, vía context.getApplicationContext().getApplicationInfo().flags y ApplicationInfo.FLAG_DEBUGGABLE. También la propiedad global ro.debuggable
isDebuggerConnected android.os.Debug.isDebuggerConnected(). Su equivalente nativo lee gDvm.debuggerConnected / gDvm.debuggerActive
TracerPid El campo TracerPid de /proc/<pid>/status o /proc/self/status. Distinto de cero significa que alguien tiene un ptrace puesto
Medición de tiempos Debug.threadCpuTimeNanos(). MASTG: «como la depuración ralentiza la ejecución del proceso, se puede usar la diferencia de tiempo de ejecución para adivinar si hay un depurador conectado»
Manipulación de estructuras de JDWP En Android < 5.0, los campos jdwpAllowed, jdwpConfigured, jdwpTransport, jdwpPort, debuggerConnected, debuggerActive y jdwpState de DvmGlobals/gDvm. Desde 5.0, JdwpAdbState/JdwpSocketState, localizables por el símbolo _ZTVN3art4JDWP12JdwpAdbStateE de libart.so
fork + ptrace Un proceso hijo que se engancha al padre con PTRACE_ATTACH y PTRACE_CONT, ocupando la única plaza de trazador disponible

El matiz de TracerPid que casi nadie menciona, y que MASTG sí: «recuerda que esto solo aplica al código nativo. Si estás depurando una aplicación solo de Java/Kotlin, el valor del campo TracerPid debería ser 0». Un depurador JDWP no deja rastro ahí.

⚠️ ptrace(PTRACE_TRACEME) sobre sí mismo —la técnica más citada de todas: el proceso se traza a sí mismo para que nadie más pueda— no aparece en MASTG-KNOW-0028, que solo menciona PTRACE_ATTACH y PTRACE_CONT. La fuente con autor identificable donde sí está documentada es la charla de Tim Strazzere y Jon Sawyer, «Android Hacker Protection Level 0», DEF CON 22 (2014), que publica el pseudocódigo del JNI_OnLoad de APKProtect con ptrace(PTRACE_TRACEME, ...) y una función find_qemud_process(). No se atribuya a OWASP.

⚠️ Debug.waitingForDebugger() tampoco aparece en esa página de MASTG.

Cómo se identifica en el binario, sin ejecutar nada: la cadena TracerPid o /proc/self/status en el string_ids del DEX o en las cadenas de un .so; la referencia a Landroid/os/Debug;.isDebuggerConnected en method_ids; el símbolo ptrace importado por una biblioteca nativa; y android:debuggable en el manifiesto —que además es de por sí un dato que hay que reportar—.

4. Anti-emulador

MASTG-KNOW-0031 («Emulator Detection») y MASTG-TEST-0049. El detalle metodológico que sorprende: MASTG documenta estas comprobaciones a través de los campos Java de android.os.Build, no como lectura cruda de propiedades del sistema ro.*.

Campo de Build Valores o patrones que delatan un emulador
FINGERPRINT Empieza por generic; contiene test-keys, generic/sdk/generic, generic x86, vbox86p, ttvm
MODEL sdk, google_sdk, emulator, android sdk built for x86, android sdk built for x86_64, droid4x, tiantianvm, genymotion, andy, nox
MANUFACTURER unknown, genymotion, droid4x, tiantianvm, andy
HARDWARE goldfish, ranchu, vbox86, nox, ttvm
PRODUCT sdk, google_sdk, sdk_x86, sdk_google, vbox86p, droid4x, andy, ttvm, nox; empieza por itoolsavm
BRAND / DEVICE Empiezan por generic; generic x86, vbox86p, ttvm, andy, nox
BOARD unknown; contiene nox
ID frf91
TAGS test-keys
USER android-build
SERIAL / RADIO unknown; ambos deprecados en favor de Build.getSerial() y Build.getRadioVersion()

MASTG recomienda «normalizar a minúsculas al comprobar estos valores», detalle que importa si uno busca las cadenas en el binario.

Ficheros y sockets característicos, comprobados con java.io.File.exists():

Ruta Qué es
/dev/socket/qemud Socket del demonio de QEMU
/dev/qemu_pipe Tubería de comunicación de QEMU
/dev/goldfish_pipe Tubería del emulador Goldfish
/sys/qemu_trace Marcador de traza de QEMU
/dev/socket/genyd Socket del demonio de Genymotion
/dev/socket/baseband_genyd Socket de banda base de Genymotion

Telefonía: getLine1Number() devolviendo un número de la serie 1555521555415555215584 —los sufijos pares—, getNetworkOperatorName() devolviendo android en minúscula, y getVoiceMailNumber() devolviendo 15552175049. getLine1Number() está deprecado desde Android 13 (API 33) en favor de SubscriptionManager.getPhoneNumber(int).

Paquetes de emuladores, con los prefijos com.vphone., com.bignox., com.nox.mopen.app, me.haima., com.bluestacks, cn.itools., com.kop., com.kaopu., com.microvirt., y el paquete exacto com.google.android.launcher.layouts.genymotion.

OpenGL: GLES20.glGetString(GL_RENDERER) conteniendo Bluestacks o Translator.

⚠️ Tres artefactos clásicos que no aparecen en MASTG-KNOW-0031, comprobado por búsqueda sobre el fuente de la página: ro.kernel.qemu, /system/lib/libc_malloc_debug_qemu.so e IMEI 000000000000000. La propiedad ro.kernel.qemu es real y muy citada, pero no procede de OWASP. Donde sí están documentadas propiedades del sistema con nombre propio es en el trabajo de Maddie Stone, «Unpacking the Packed Unpacker» (Virus Bulletin 2018 / Black Hat USA 2018), que analiza un packer que «realiza más de 45 comprobaciones distintas en tiempo de ejecución» y nombra init.svc.qemud, qemu.sf.fake_camera e init.svc.goldfish-setup, más detección de Xposed vía la clase de/robv/android/xposed/XposedBridge.

Cómo se identifica en el binario: cualquiera de las cadenas de las tablas anteriores en el string_ids del DEX o en un .so. Es la categoría más fácil de detectar estáticamente, porque las comprobaciones son literalmente comparaciones contra constantes de texto — salvo que estén cifradas (Ofuscadores comerciales, sección 2.2), que es exactamente el motivo de que se cifren.

5. Detección de root y de Frida

5.1 Root — MASTG-KNOW-0027, MASTG-TEST-0045

Categoría Artefactos concretos
Binarios su /sbin/su, /system/bin/su, /system/bin/failsafe/su, /system/xbin/su, /system/sd/xbin/su, /data/local/su, /data/local/xbin/su, /data/local/bin/su; además recorrer System.getenv("PATH") buscando su, y la vía nativa con la syscall stat
Otros binarios /system/xbin/busybox
Ficheros de apps de rooting /system/app/Superuser.apk, /system/etc/init.d/99SuperSUDaemon, /dev/com.koushikdutta.superuser.daemon/, /system/xbin/daemonsu
Paquetes de gestores eu.chainfire.supersu, com.noshufou.android.su, com.koushikdutta.superuser, com.topjohnwu.magisk
Ejecución Runtime.getRuntime().exec("su"), que lanza IOException si no existe
Procesos Nombres que contengan supersu o superuser; el demonio daemonsu
Particiones system o data montadas con flag rw
ROM personalizada android.os.Build.TAGS conteniendo test-keys —MASTG lo ejemplifica con el método detectTestKeys() de RootBeer—
Certificados Ausencia de los certificados OTA de Google

5.2 Frida y herramientas de instrumentación — MASTG-KNOW-0030, MASTG-TEST-0048

Categoría Artefacto exacto
Mapeo de memoria /proc/self/maps y /proc/self/smaps; agente mapeado como frida-agent-64.so desde /data/local/tmp/re.frida.server/; gadget embebido como libfrida-gadget.so
Procesos y servicios El binario frida-server; getRunningServices, comando ps
Puerto TCP «frida-server escucha en el puerto TCP 27042 por defecto»
Cadenas en memoria LIBFRIDA, «que históricamente ha aparecido en los binarios de Frida Gadget y Frida Agent»
Nombres de hilo gum-js-loop y gdbus, leídos en /proc/self/task/<tid>/status
Sockets Unix /proc/self/net/unix, /proc/self/fd; sockets Unix abstractos con el prefijo frida-zymbiote- más un identificador aleatorio
Firma de la app GET_SIGNING_CERTIFICATES desde API 28, contrastado con un certificado fijado en la aplicación

⚠️ La cadena frida:rpc, que se cita mucho, no aparece en MASTG-KNOW-0030; la documentada es LIBFRIDA. Y el prefijo de socket que documenta MASTG hoy es frida-zymbiote-; re.frida.server aparece solo como componente de la ruta del agente.

La limitación que MASTG declara es la que más afecta al analista: «desde Android 7.0, nivel de API 24, la visibilidad de procesos y servicios está restringida, y las APIs a nivel de aplicación no exponen de forma fiable demonios no relacionados como frida-server». Por eso las comprobaciones útiles hoy son las que miran dentro del propio proceso/proc/self/*, nombres de hilo, cadenas en memoria— y no las que enumeran el sistema.

La fuente primaria que la propia MASTG cita es «The Jiu-Jitsu of Detecting Frida», de Bernhard Mueller.

Cómo se identifica en el binario: las rutas /proc/self/maps, /system/xbin/su, /data/local/tmp, los nombres de paquete com.topjohnwu.magisk y eu.chainfire.supersu, y las cadenas frida, LIBFRIDA, gum-js-loop en el DEX o en los .so. Y, de nuevo, la declaración de QUERY_ALL_PACKAGES o de una lista <queries> con gestores de root en el manifiesto.

6. Comprobación de integridad en ejecución

Dos familias distintas: comprobar quién firmó y comprobar qué se está ejecutando.

6.1 Verificación de la firma desde el propio código

Es la defensa contra el reempaquetado: la aplicación comprueba que sigue estando firmada con el certificado de su autor y no con uno cualquiera.

Las APIs, con sus niveles confirmados en la referencia oficial:

API Nivel Notas
PackageManager.GET_SIGNING_CERTIFICATES 28 Valor 0x08000000. «Devuelve los certificados de firma asociados a este paquete. Cada entrada es un certificado que el paquete ha demostrado estar autorizado a usar, normalmente un certificado de firma anterior desde el que ha rotado»
PackageManager.GET_SIGNATURES 1, deprecado en 28 Valor 0x40. «Este constante quedó obsoleta en el nivel de API 28: usa GET_SIGNING_CERTIFICATES en su lugar»
PackageManager.hasSigningCertificate(String, byte[], int) 28 La documentación lo recomienda en lugar de getPackageInfo con GET_SIGNATURES «porque tiene en cuenta la posibilidad de rotación del certificado de firma». Acepta CERT_INPUT_RAW_X509 o CERT_INPUT_SHA256
PackageManager.getInstallerPackageName(String) 5, deprecado en 30 «Identifica de qué market vino el paquete». Sustituido por getInstallSourceInfo(String)
PackageManager.getInstallSourceInfo(String) 30 Devuelve InstallSourceInfo. Sin permiso INSTALL_PACKAGES, getOriginatingPackageName() devuelve siempre null
PackageManager.getVerifiedSigningInfo(String, int) 36 Verifica la firma de un fichero arbitrario sin exigir que sea un archivo de paquete
SigningInfo 28 Constantes VERSION_JAR, VERSION_SIGNING_BLOCK_V2, VERSION_SIGNING_BLOCK_V3, VERSION_SIGNING_BLOCK_V4. Métodos getApkContentsSigners(), getSigningCertificateHistory(), getSchemeVersion(), hasMultipleSigners(), hasPastSigningCertificates(), signersMatchExactly(SigningInfo)

La forma canónica es: obtener el SigningInfo, calcular el SHA-256 del certificado y compararlo con un valor incrustado en el código —normalmente cifrado, y con frecuencia la comparación misma movida a nativo—. getSchemeVersion() permite además exigir un esquema mínimo: rechazar una instalación que solo tenga v1 elimina de golpe una clase de manipulaciones (Esquemas de firma).

Guardsquare lo vende exactamente así: «DexGuard da a tu aplicación la capacidad de asegurar que ha sido firmada con el certificado original».

⚠️ El checksum del classes.dex leído de la propia entrada ZIP del APK y la comprobación de getInstallerPackageName como controles anti-tamper son técnicas reales y frecuentes, pero no se han encontrado documentadas como tales en ninguna página de MASTG de las consultadas. Las APIs sí están confirmadas; su uso con ese fin, no. Se mencionan aquí como observación, no como cita.

6.2 Integridad del código en ejecución

MASTG-KNOW-0032 («Runtime Integrity Verification») organiza esto en cuatro preguntas, que es la mejor taxonomía disponible:

Pregunta Categoría Controles
¿Ha cambiado el destino de una llamada indirecta? Control Flow Integrity Checks Detección de hooks en PLT/GOT, en vtables, verificación de los puntos de entrada de ART
¿Ha cambiado el código ejecutable o los datos protegidos? Code Integrity Verification Checksums de memoria, detección de hooks inline
¿Se ha cargado código ejecutable nuevo en el proceso? Runtime Code Injection Detection Detección de inyección de bibliotecas dinámicas
¿Ha modificado un framework el runtime de la app? Framework Runtime Modification Detection Detección de Xposed

Los artefactos concretos que nombra: permisos sospechosos en /proc/self/mapsrwxp donde se espera r-xp—; la sección .data.rel.ro para las vtables; los campos de ArtMethod entry_point_from_quick_compiled_code_, entry_point_from_interpreter_ y access_flags_, más el flag kAccNative (0x0100); el trampolín ARM64 LDR X16, .+8 ; BR X16 con los bytes 50 00 00 58 00 02 1F D6 y su variante ARM32/Thumb LDR PC, [PC, #-4]; la inyección desde rutas escribibles como /data/local/tmp; y la detección de Xposed por la clase de/robv/android/xposed/XposedBridge en el classloader, o de LSPosed por META-INF/xposed/java_init.list y META-INF/xposed/native_init.list.

Ese último par es una huella estática: se ve mirando el ZIP.

7. Lógica movida a nativo

La forma más simple y más efectiva de dificultar el análisis: sacar el código de Java/Kotlin y ponerlo en C/C++ dentro de un .so (Código nativo y ELF).

Por qué funciona tan bien, y no es porque el nativo sea intrínsecamente más difícil:

  1. El DEX es un formato de alto nivel. Conserva tipos, firmas, jerarquía de clases y nombres de método. Un .so con los símbolos eliminados no conserva nada de eso: el decompilador de Ghidra tiene que inferir hasta el número de argumentos.
  2. No hay decompilador de referencia gratuito y bueno. jadx produce Java legible en segundos; reconstruir C desde ARM64 es un trabajo manual de horas por función.
  3. El anti-debug nativo es más potente, porque tiene acceso a ptrace, a /proc y al propio mapa de memoria del proceso (secciones 3 y 6.2).
  4. Se puede empaquetar y ofuscar aparte con las mismas técnicas que un binario de escritorio.

Los packers de nivel 3 lo llevan al extremo con lo que Happer llama JNI Transformation: «los packers pueden usar código nativo para reimplementar métodos seleccionados de la aplicación».

Cómo se identifica: un lib/ grande frente a un classes.dex pequeño; muchos métodos native declarados en el DEX —el flag ACC_NATIVE = 0x100 en el access_flags del encoded_method, con code_off a cero (Formato DEX, secciones 10 y 13)—; y en el .so, o bien símbolos exportados con el patrón Java_<paquete>_<clase>_<método> —registro por descubrimiento—, o bien solo JNI_OnLoad —registro dinámico con RegisterNatives(), que oculta la correspondencia—.

Consecuencia práctica: hay que bajar a Ghidra. Ver Análisis binario y estático.

8. PairIP

Es la protección de integridad que Google Play aplica del lado de la tienda, sin que el desarrollador toque su código.

Lo que Google publica. La documentación oficial no usa nunca el nombre «PairIP»: lo llama automatic protection o Automatic Integrity Protection. Sus dos funciones, verbatim:

«La protección automática de Google Play es un servicio que ayuda a proteger tus aplicaciones y juegos contra la redistribución no autorizada y la piratería.»

«La protección automática puede añadir una comprobación de instalador de Google Play al código de tu aplicación, que ocurre en tiempo de ejecución cuando se abre la aplicación.»

«La protección automática puede añadir comprobaciones en tiempo de ejecución al código de tu aplicación para detectar modificaciones, y usar técnicas avanzadas de ofuscación para impedir que las comprobaciones se eliminen o se sometan a ingeniería inversa.»

La segunda función tiene requisitos de elegibilidad publicados: 25.000 $/mes de gasto de consumidores, o al menos 20.000 $ en cada uno de los seis meses anteriores, o al menos un millón de usuarios activos mensuales en una categoría sensible, o pertenencia a un programa de Google Play.

Cómo se identifica. El nombre real del paquete es com.pairip, no com.google.android.pairip. Las huellas que se han podido verificar en fuentes de identificación mantenidas:

Huella Dónde Fuente
Lcom/pairip/licensecheck/LicenseContentProvider; Cadena del DEX Regla google_aip_installer_check de APKiD, autor Ivan Baheux
libpairipcore.so Nombre de la biblioteca nativa Regla google_aip de APKiD, autor Eduardo Novella
Export ExecuteProgram Símbolo de la ELF, junto a JNI_OnLoad y JNI_OnUnLoad Misma regla
<activity android:name="com.pairip.licensecheck.LicenseActivity"/> Manifiesto Issue #495 del repositorio de APKiD
<provider android:name="com.pairip.licensecheck.LicenseContentProvider"/> Manifiesto Ídem
Clase com.pairip.VMRunner, método nativo executeVM([B[Ljava/lang/Object;)Ljava/lang/Object; DEX Análisis de Ahmethan Gültekin, 3 de marzo de 2025
Clase com.pairip.SignatureCheck con verifyIntegrity(context) DEX Repositorio Solaree/pairipcore

El export ExecuteProgram y la clase VMRunner cuentan la historia: PairIP es una protección basada en máquina virtual, del tipo descrito en la sección 3.3 de Packers y protectores. El bytecode que interpreta viaja en assets/.

⚠️ Varios puntos no confirmados que conviene no propagar: la clase com.google.android.pairip.application.SuperpoweredApplication no existe en ninguna fuente consultada; y los nombres concretos de los ficheros de assets/ no se han podido verificar en ninguna fuente con autoría identificable.

Un matiz de detección que el propio issue de APKiD señala y que hay que respetar: la regla actual confunde la protección automática (com.pairip) con el uso normal de la Play Integrity API (com.google.android.play.core.integrity). Son cosas distintas: la primera la inyecta Play, la segunda la integra el desarrollador.

El corpus de verificación no contiene ninguna muestra con PairIP. Buscada la subcadena pairip en los 323 ficheros classes*.dex de los 86 contenedores —incluidos los cuatro juegos, que es donde más se aplica—, cero coincidencias. Tampoco hay libpairipcore.so en ningún lib/. El hueco sigue abierto y es honesto decirlo: todo lo de esta sección procede de fuentes externas, no de observación propia.

9. Play Integrity

Es la otra mitad, y no debe confundirse con la anterior: PairIP la aplica Play al binario; la Play Integrity API la llama el desarrollador desde su código.

Su función, verbatim: «ayuda a comprobar que las acciones de usuario y las peticiones al servidor vienen de tu aplicación genuina, instalada por Google Play, ejecutándose en un dispositivo Android genuino y certificado».

Los veredictos que emite:

Campo Subcampo Valores
deviceIntegrity deviceRecognitionVerdict MEETS_DEVICE_INTEGRITY, MEETS_BASIC_INTEGRITY, MEETS_STRONG_INTEGRITY, MEETS_VIRTUAL_INTEGRITY. Un array vacío significa dispositivo comprometido o emulador
deviceIntegrity recentDeviceActivity.deviceActivityLevel LEVEL_1LEVEL_4, UNEVALUATED
appIntegrity appRecognitionVerdict PLAY_RECOGNIZED, UNRECOGNIZED_VERSION, UNEVALUATED
accountDetails appLicensingVerdict LICENSED, UNLICENSED, UNEVALUATED
appAccessRiskVerdict appsDetected KNOWN_INSTALLED, UNKNOWN_INSTALLED, KNOWN_CAPTURING, UNKNOWN_CAPTURING, KNOWN_CONTROLLING, UNKNOWN_CONTROLLING, KNOWN_OVERLAYS, UNKNOWN_OVERLAYS
environmentDetails playProtectVerdict NO_ISSUES, NO_DATA, POSSIBLE_RISK, MEDIUM_RISK, HIGH_RISK, UNEVALUATED

Son opcionales, y hay que activarlos: MEETS_STRONG_INTEGRITY, appAccessRiskVerdict, playProtectVerdict, recentDeviceActivity y deviceRecall (en beta).

Relación con SafetyNet Attestation. Es su sucesora directa. La página oficial de retirada lo dice así: «la SafetyNet Attestation API quedó obsoleta en 2022 y se retiró por completo en enero de 2025. Los desarrolladores deben migrar a la Play Integrity API, que consolida varias ofertas de integridad —incluido el veredicto de integridad de SafetyNet Attestation— bajo una única API». Y sobre el estado actual: «si intentas llamar a la SafetyNet Attestation API recibirás un error. La API attest devuelve una tarea que siempre invoca el listener de fallo con una ApiException y un código de estado 7 (NETWORK_ERROR)». ⚠️ La página, en su estado actual, ya no publica la tabla de hitos intermedios: solo da «2022» y «enero de 2025», sin día.

Qué significa para quien analiza una aplicación legítimamente. Tres cosas, y ninguna es técnica de evasión:

  1. Los veredictos son señales, no verdad. MEETS_DEVICE_INTEGRITY ausente en un análisis sobre emulador es el resultado esperado y correcto: el dispositivo, efectivamente, no es uno certificado.
  2. La comprobación es del lado del servidor. El veredicto viaja firmado por Google al backend de la aplicación. Lo que se observa en el dispositivo es la petición, no la decisión. Un análisis que solo mire el cliente no va a entender por qué la aplicación rechaza operar.
  3. La presencia de la API es una huella estática legítima y reportable: las clases com.google.android.play.core.integrity.* en el DEX dicen que la aplicación depende de ella, lo cual es información de arquitectura útil y perfectamente publicable.

10. Los síntomas, que es lo que uno ve primero

El analista no encuentra estas protecciones: las padece. Esta tabla va en la dirección real de descubrimiento —del síntoma a la causa— y complementa Diagnóstico de fallos.

Síntoma Causa probable Sección
La aplicación cierra sola al arrancar en el emulador, sin traza en logcat Anti-emulador 4
Funciona en el emulador, cierra en el dispositivo rooteado Detección de root 5.1
Se cierra en cuanto se engancha el depurador; o el depurador no llega a enganchar Anti-debug: TracerPid, o ptrace sobre sí misma 3
Arranca, funciona unos segundos y muere sin excepción Comprobación temporizada o comprobación en un hilo aparte 3, 6.2
Muere en cuanto se inyecta Frida, o el proceso desaparece al adjuntar Detección de instrumentación: puerto 27042, /proc/self/maps, nombres de hilo 5.2
Arranca, pero cualquier operación de red devuelve error de autorización Play Integrity, veredicto rechazado en el servidor 9
Reempaquetada y firmada de nuevo, se instala y arranca pero se cierra o se degrada Verificación de la firma desde el propio código 6.1
El decompilador produce cientos de clases con métodos vacíos o native Lógica movida a nativo 7
El classes.dex tiene doscientas clases de infraestructura y nada más Packer 03
jadx produce código con goto ilegales que no compila Ofuscación de flujo de control 02, 2.4
Todas las cadenas del DEX son basura binaria Cifrado de cadenas 02, 2.2

Y la comprobación previa, que cuesta un minuto y ahorra varias horas: antes de ejecutar nada, mirar el manifiesto y las cadenas. QUERY_ALL_PACKAGES, una lista <queries> con com.topjohnwu.magisk, un com.pairip.licensecheck.LicenseActivity, un libpairipcore.so, la cadena TracerPid, la cadena /system/xbin/su: todo eso es estático, gratis, y dice de antemano qué va a pasar.

Fuentes

  1. OWASP MASTG — MASTG-KNOW-0028, «Anti-Debugging» — https://mas.owasp.org/MASTG/knowledge/android/MASVS-RESILIENCE/MASTG-KNOW-0028/ Consultado el 12 de agosto de 2026, con el fuente en raw.githubusercontent.com/OWASP/owasp-mastg/master/knowledge/android/MASVS-RESILIENCE/MASTG-KNOW-0028.md. De aquí sale íntegra la tabla de la sección 3, incluido el matiz de TracerPid y las estructuras de JDWP. Test asociado: MASTG-TEST-0046.
  2. OWASP MASTG — MASTG-KNOW-0031, «Emulator Detection» — https://mas.owasp.org/MASTG/knowledge/android/MASVS-RESILIENCE/MASTG-KNOW-0031/ Consultado el 12 de agosto de 2026. De aquí salen las cuatro tablas de la sección 4 y los cambios de plataforma de la sección 2. Test asociado: MASTG-TEST-0049.
  3. OWASP MASTG — MASTG-KNOW-0027, «Root Detection» — https://mas.owasp.org/MASTG/knowledge/android/MASVS-RESILIENCE/MASTG-KNOW-0027/ Consultado el 12 de agosto de 2026. De aquí sale la tabla de la sección 5.1. Test asociado: MASTG-TEST-0045.
  4. OWASP MASTG — MASTG-KNOW-0030, «Reverse Engineering Tool Detection» — https://mas.owasp.org/MASTG/knowledge/android/MASVS-RESILIENCE/MASTG-KNOW-0030/ Consultado el 12 de agosto de 2026. De aquí sale la tabla de la sección 5.2, las dos citas sobre artefactos y falsos positivos, y la referencia a «The Jiu-Jitsu of Detecting Frida» de Bernhard Mueller. Test asociado: MASTG-TEST-0048.
  5. OWASP MASTG — MASTG-KNOW-0032, «Runtime Integrity Verification» — https://mas.owasp.org/MASTG/knowledge/android/MASVS-RESILIENCE/MASTG-KNOW-0032/ Consultado el 12 de agosto de 2026. De aquí salen la taxonomía de cuatro preguntas de la sección 6.2 y todos sus artefactos concretos.
  6. PackageManager — https://developer.android.com/reference/android/content/pm/PackageManager y SigningInfo — https://developer.android.com/reference/android/content/pm/SigningInfo Consultados el 12 de agosto de 2026. De aquí sale íntegra la tabla de la sección 6.1, con los niveles de API y los valores constantes.
  7. Strazzere, T. y Sawyer, J. — «Android Hacker Protection Level 0», DEF CON 22 (2014) — https://www.defcon.org/images/defcon-22/dc-22-presentations/Strazzere-Sawyer/DEFCON-22-Strazzere-and-Sawyer-Android-Hacker-Protection-Level-UPDATED.pdf Consultado el 12 de agosto de 2026. De aquí sale ptrace(PTRACE_TRACEME) y find_qemud_process() de la sección 3, que MASTG no documenta.
  8. Stone, M. — «Unpacking the Packed Unpacker», Virus Bulletin 2018 / Black Hat USA 2018 — https://i.blackhat.com/us-18/Thu-August-9/us-18-Stone-Unpacking-The-Packed-Unpacker.pdf Consultado el 12 de agosto de 2026. De aquí salen las «más de 45 comprobaciones distintas» y las propiedades init.svc.qemud, qemu.sf.fake_camera e init.svc.goldfish-setup de la sección 4.
  9. Prevent unauthorized redistribution with automatic protection (Google Play Console) — https://support.google.com/googleplay/android-developer/answer/10183279 Consultado el 12 de agosto de 2026. De aquí salen las tres citas de la sección 8 y los requisitos de elegibilidad.
  10. APKiD — apkid/rules/dex/protectors.yara y elf/protectors.yararaw.githubusercontent.com/rednaga/APKiD/master/apkid/rules/ Consultados el 12 de agosto de 2026. De aquí salen las reglas google_aip_installer_check (autor Ivan Baheux) y google_aip (autor Eduardo Novella) con Lcom/pairip/licensecheck/LicenseContentProvider;, libpairipcore.so y el export ExecuteProgram de la sección 8.
  11. APKiD issue #495 — https://github.com/rednaga/APKiD/issues/495 y repositorio Solaree/pairipcore — https://github.com/Solaree/pairipcore Consultados el 12 de agosto de 2026. De aquí salen las entradas de manifiesto com.pairip.licensecheck.LicenseActivity y LicenseContentProvider, las clases com.pairip.VMRunner y com.pairip.SignatureCheck, y la advertencia sobre la confusión entre com.pairip y com.google.android.play.core.integrity.
  12. Gültekin, A. — «Reversing Google's New VM-Based Integrity Protection: PairIP», 3 de marzo de 2025 — https://blog.byterialab.com/reversing-googles-new-vm-based-integrity-protection-pairip/ Consultado el 12 de agosto de 2026. De aquí salen VMRunner, la firma de executeVM y el registro por RegisterNatives en JNI_OnLoad de la sección 8. Se han tomado únicamente los marcadores de identificación.
  13. Play Integrity API — Overview y Verdicts — https://developer.android.com/google/play/integrity/overview y https://developer.android.com/google/play/integrity/verdicts Consultados el 12 de agosto de 2026. De aquí salen la definición y la tabla completa de veredictos de la sección 9.
  14. SafetyNet Attestation deprecation timeline — https://developer.android.com/privacy-and-security/safetynet/deprecation-timeline Consultado el 12 de agosto de 2026. De aquí salen las dos citas sobre la retirada de la sección 9.
  15. DexGuard factsheet 2024 (Guardsquare) — https://www.guardsquare.com/hubfs/Website/Resources/Fact%20sheets/factsheet-DexGuard-2024.pdf Consultado el 12 de agosto de 2026. De aquí sale la lista de seis mecanismos RASP de la sección 2 y la cita sobre comprobación de certificado de la sección 6.1.
  16. Xue, Luo et al. — «Happer» — https://www4.comp.polyu.edu.hk/~csxluo/Happer.pdf Consultado el 12 de agosto de 2026. De aquí sale la JNI Transformation de la sección 7.
  17. Mediciones propias sobre el corpus — ejecutadas el 12 de agosto de 2026 sobre ~/corpus-apk con Python 3.12. De aquí sale la comprobación de ausencia de pairip en los 323 classes*.dex de la sección 8.