Ofuscadores comerciales
1. Qué es y por qué existe
R8 renombra (R8 y ProGuard). Un ofuscador comercial hace eso y
además cifra, aplana, inserta ruido y esconde llamadas, y en algunos casos vende junto a
ello comprobaciones de integridad en ejecución. La diferencia práctica para quien analiza es
que con R8 el código decompilado es correcto y feo, y con un ofuscador comercial el
decompilador puede directamente no producir código válido.
Este documento cataloga las transformaciones —que es la parte reutilizable, porque los productos van y vienen pero el repertorio es finito—, describe los cuatro productos relevantes y publica una tabla de huellas. Es el complemento de Packers y protectores, que trata el caso en el que ya no hay código que decompilar porque viaja cifrado.
Alcance. Se documenta qué transformación aplica cada producto y cómo se identifica. No se documenta cómo revertir ninguna, ni cómo neutralizar las comprobaciones de integridad que algunos añaden. La sección 8 se acerca a la frontera al tratar el descifrado de cadenas y se queda del lado de la identificación; el tratamiento del descifrado como análisis estático para leer código está en Deofuscación, sección 5.
Todas las cifras y listas de esta página vienen de la fuente del propio fabricante o de un estudio publicado, y las huellas concretas vienen de las reglas YARA de APKiD o de medición directa sobre el corpus. Lo que no se ha podido verificar va marcado.
2. Catálogo de transformaciones
Once transformaciones cubren prácticamente todo lo que se encuentra. Para cada una: qué hace, qué le hace al decompilador y qué huella deja.
2.1 Renombrado de identificadores
Lo que hace R8. Clases, campos, métodos y paquetes pasan a a, b, c. La variante
agresiva mueve además todas las clases a un solo paquete —Netflix tiene 17.329 clases en
un paquete llamado o— o al paquete raíz.
Efecto: el decompilador produce código correcto. El lector pierde toda la semántica de los nombres, que es la mitad de la comprensión. Huella: mediana de longitud de identificador de 1 a 3 (R8 y ProGuard, sección 10). Paquete raíz poblado.
Dos refinamientos que distinguen al producto de pago:
- Sobrecarga por nombre (name overloading): reutilizar el mismo nombre para métodos con
firmas distintas y para campos de tipos distintos. Es legal en el
DEX, donde unmethod_idllevaclass_idx,proto_idxyname_idx, pero convierte el índice en ambiguo para quien busca por nombre. - Alfabetos hostiles. El estudio de Wermke et al. lo documenta: «Allatori y DexGuard
construyen sobre el alfabeto de ofuscación de nombres de ProGuard y añaden palabras
reservadas de Windows (
AUX,NUL)». Dong et al. observaron en malware nombres con caracteres Unicode (È,ô) y secuencias confusas (llllllll,oO00O0oo). La gramática deSimpleNamedelDEXes mucho más permisiva que la de Java (Formato DEX, sección 7), así que un ofuscador puede emitir nombres que ningún decompilador a Java puede reproducir como código compilable.
Ese último punto explica una huella muy concreta de APKiD: las reglas dexguard_b y
dexguard_d buscan literalmente clases llamadas AUX, CON e IF en cualquier
combinación de mayúsculas, y la de Lo/ —Lo/AUX;, Lo/CON;— la marca como específica de
DexGuard porque otros ofuscadores usan esos nombres pero no dentro de un paquete o.
2.2 Cifrado de cadenas
Las constantes de texto se sustituyen por datos cifrados más una llamada a un descifrador que se ejecuta al vuelo. Es la transformación más rentable para el que ofusca, porque las cadenas literales son la principal vía de orientación de quien lee código ajeno: nombres de endpoints, claves de preferencias, mensajes de error, nombres de clases usados por reflexión.
Efecto: el decompilador produce b(c("Èk9@sg^W0~aa ÿ")) en vez de "Wrong Password" —el
ejemplo es de la propia documentación de Zelix—. La búsqueda por texto deja de funcionar.
Huella: un string_ids lleno de secuencias no imprimibles o de longitud uniforme, y uno o
pocos métodos estáticos invocados desde cientos de sitios con argumentos constantes
distintos.
El límite lo declara el propio fabricante. Zelix: «el cifrado de cadenas de Zelix KlassMaster no es, ni puede ser, fundamentalmente irreversible». Wermke et al. lo dicen igual: «hay que llegar a un compromiso entre la fuerza del cifrado y el impacto en rendimiento de descifrar. El descifrador tiene que estar dentro del programa, lo que hace el cifrado inadecuado para ocultar información sensible».
2.3 Cifrado de clases
El bytecode de clases enteras se guarda cifrado y se descifra y carga en ejecución. Es la frontera con el packing: cuando se aplica a todo el código, ya es un packer (Packers y protectores); aplicado selectivamente a unas pocas clases, es una transformación más del ofuscador.
Efecto: esas clases sencillamente no están en el DEX. El decompilador no las ve.
Huella: ficheros de datos opacos y de alta entropía en assets/ o en la raíz del APK
con nombres cortos, y un class_defs_size menor de lo que sugiere el tamaño del APK.
2.4 Aplanamiento y ofuscación del flujo de control
Se reestructuran los if/while/for en formas que no tienen equivalente directo en Java:
un despachador con un switch sobre una variable de estado, saltos cruzados, predicados
opacos cuyo valor es constante pero indecidible localmente.
Efecto: Zelix lo describe con precisión: «normalmente fuerza a los decompiladores a
insertar una serie de etiquetas y sentencias goto ilegales en el código fuente que
producen» y «a veces el código fuente se ofusca aún más por los errores del decompilador».
Es decir: la salida ni siquiera compila.
Huella: densidad anómala de goto/goto/16/goto/32 y de packed-switch/sparse-switch
(Bytecode Dalvik); métodos con un solo bucle enorme y
un switch sobre un registro que se reasigna en cada rama.
2.5 Inserción de código muerto y basura
Instrucciones y ramas que nunca se ejecutan, o que se ejecutan y no hacen nada.
Efecto: infla el volumen a leer y despista al análisis automático.
Huella: nop en cantidad, aritmética sin consumidor, ramas cuya condición es constante.
Obfuscapk implementa tres de estas de forma pura y documentada: Nop («inserta código
basura; Nop inserta instrucciones aleatorias de no-operación»), ArithmeticBranch
(«inserta cómputos aritméticos e instrucciones de salto como código basura») y Goto
(«inserta una instrucción goto que apunta al final del método»).
2.6 Indirección por reflexión
Una llamada directa objeto.metodo(x) se sustituye por
Class.forName(s1).getMethod(s2, …).invoke(objeto, x), casi siempre con s1 y s2
cifrados (sección 2.2).
Efecto: el grafo de llamadas se rompe. Dong et al. lo explican bien: «la reflexión es una
buena opción para ocultar comportamientos del programa porque puede transferir el control a
una función determinada de forma implícita, algo que las herramientas de análisis estático
del estado del arte no manejan bien».
Huella: proporción elevada de Ljava/lang/reflect/Method;.invoke y Class.forName sobre
el total de invocaciones. Zelix la vende como Reference Obfuscation: «permite ofuscar las
referencias a campos o métodos sustituyéndolas por llamadas a la Reflection API o por
invokedynamic», y además «cifra los nombres de los campos, métodos y clases a los que se
accede por esa vía».
Dato de escala, de Dong et al. sobre 114.560 aplicaciones: de 121.262 llamadas reflexivas observadas, 116.595 (96,2 %) tenían objetivo resoluble. Y su conclusión sobre prevalencia es menos alarmante de lo que suele decirse: «las proporciones de uso de reflexión en aplicaciones benignas y en malware son similares» y «la mayoría de los casos de reflexión se usan para invocar funciones ocultas o para dar soporte a compatibilidad hacia atrás».
2.7 Ofuscación aritmética
Expresiones aritméticas y lógicas simples se sustituyen por otras equivalentes pero enrevesadas (aritmética booleana mixta, identidades algebraicas). Guardsquare lo vende literalmente como protección de fórmulas: «DexGuard protege las fórmulas propietarias transformando expresiones aritméticas y lógicas simples en código difícil de analizar».
Efecto: el decompilador produce código correcto e incomprensible.
Huella: densidad anómala de xor-int, and-int, shl-int y compañía. La regla
dexguard_c de APKiD se apoya exactamente en eso: busca un método con la firma
a(IIZI[I[[I[I)V que contenga xor-int/2addr, con el comentario «xor es bastante raro en
código legítimo».
2.8 Ocultación de llamadas a la API
Caso particular de 2.6 dirigido a las llamadas que delatan. Guardsquare: «DexGuard añade reflexión para acceder a APIs sensibles, como las APIs estándar de Android para validación de firmas o para operaciones criptográficas».
Efecto: un análisis que busque PackageManager.getPackageInfo o javax.crypto no
encuentra nada.
Huella: ausencia sospechosa de llamadas a APIs que los permisos del manifiesto implican.
Obfuscapk tiene una transformación específica, AdvancedReflection, descrita como «usa
reflexión para invocar APIs peligrosas del framework de Android».
2.9 Ofuscación de recursos
Dos cosas distintas que se confunden:
- Nombres de entrada en
resources.arsc: el nombre simbólico (activity_main) se colapsa o se sustituye. - Rutas de fichero dentro del
APK:res/layout/activity_main.xmlpasa ares/a/b.xmlo ares/raw/AW.xml.
Lo notable es que hay una vía oficial de Google para esto, en aapt2 optimize:
Opción de aapt2 optimize |
Qué hace (texto de la propia herramienta) |
|---|---|
--collapse-resource-names |
«Colapsa los nombres de recurso a un único valor en el key string pool. Se pueden exceptuar recursos con la directiva no_collapse» |
--shorten-resource-paths |
«Acorta las rutas de los recursos dentro del APK. Se pueden exceptuar con no_path_shorten» |
--save-obfuscation-map <f> |
«Ruta donde volcar el mapa de rutas/nombres originales a ofuscados» |
Ese último fichero es el mapping.txt de los recursos, y es lo que hace que esta
transformación sea determinísticamente reversible si se tiene el mapa y prácticamente
irreversible si no. Ver Deofuscación, sección 4.
Huella: medida directamente sobre el corpus, hay tres firmas distintas y bien separadas:
Firma en aapt2 dump resources |
Qué es | Visto en |
|---|---|---|
anim/0_resource_name_obfuscated |
aapt2 --collapse-resource-names. La cadena aparece una sola vez en el resources.arsc, porque todas las entradas apuntan al mismo índice del key string pool |
Snapchat (22.890 entradas) |
anim/(name removed) |
Mismo mecanismo, cadena centinela distinta. También una sola aparición en el fichero | WhatsApp (54.797 entradas) |
anim/2130771970 |
El nombre se sustituye por el propio resource ID en decimal (0x7f010002). Hay tantas cadenas distintas como entradas |
Netflix, con DexGuard |
La cadena 0_resource_name_obfuscated está dentro del binario de aapt2 37.0.0 de esta
máquina, junto a no_collapse y a los descriptores protobuf aapt.pb.CollapsedNamesMap: es
un artefacto oficial, no de un ofuscador de terceros.
2.10 Virtualización
La forma más fuerte. El cuerpo del método se traduce a un bytecode propio y se sustituye por una llamada a un intérprete de ese bytecode incluido en la aplicación. Guardsquare: «la virtualización de código transforma las implementaciones de los métodos en instrucciones para máquinas virtuales generadas aleatoriamente».
Efecto: no hay nada que decompilar. El analista tiene que reconstruir primero la máquina
virtual y luego su juego de instrucciones, que es distinto en cada build.
Huella: métodos enormes con un switch gigante sobre un array de bytes, y un array de
datos que no es texto ni recurso.
2.11 Endurecimiento del código nativo
Mover lógica a .so y proteger ese .so. Guardsquare: «DexGuard endurece las bibliotecas
nativas frente a la ingeniería inversa y la manipulación, incluida la interfaz entre las
bibliotecas nativas y el código de la aplicación». Se trata en
Anti-análisis y hardening, sección 7.
3. DexGuard (Guardsquare)
El hermano de pago de ProGuard, del mismo fabricante. Nació en 2012, dos años antes de que
se fundara Guardsquare. Sede en Lovaina, Bélgica. Licencia comercial sin precio público;
el estudio de Wermke et al. lo registra en su tabla comparativa como «On request», frente a
los 290 $ de Allatori y los 800 $ de DexProtector.
Lo que dice de sí mismo su ficha de producto de 2024, verbatim y agrupado por el eje de este documento:
| Categoría | Técnicas declaradas |
|---|---|
| Ofuscación estática | Name obfuscation (clases, campos, métodos, bibliotecas nativas, y también nombres de recursos, ficheros de recursos, ficheros de assets y atributos XML de recursos), control flow obfuscation, arithmetic obfuscation, call hiding, removal of Android logging code |
| Cifrado | Data encryption: «cifra cadenas sensibles… También cifra clases, ficheros de assets, ficheros de recursos y bibliotecas nativas» |
| Virtualización | Code virtualization |
| Nativo | Native code obfuscation |
| RASP | Certificate checks, debugger and emulator checks, root detection, hook detection, tamper detection, malware protection |
Y la frase que explica por qué se lo encuentra uno tan a menudo: «DexGuard es compatible hacia atrás con ProGuard. Eso hace fácil la migración: puedes reutilizar tu configuración de ProGuard e implementar las capas adicionales de protección de DexGuard». Cubre además aplicaciones multiplataforma —Unity, Cordova, Ionic, Flutter, React Native— y declara «más de 900 clientes en todo el mundo».
⚠️ sin verificar: si DexGuard hace packing en el sentido de
Packers y protectores. Guardsquare no usa esa palabra en
ninguna de sus páginas consultadas; lo que sí declara es cifrado de clases, que es la
transformación 2.3. La evidencia del corpus que sigue no respalda tratarlo como packer:
el classes.dex de Netflix contiene código real decompilable, no un stub.
3.1 La evidencia real en el Netflix del corpus
com.netflix.mediaclient.sai.zip, base.apk, 21,9 MB, versionName 8.76.0 build 7 50451.
Todo lo que sigue está medido directamente, no inferido de una regla de detección.
Cadenas y clases que nombran al producto, encontradas recorriendo string_ids y
class_defs de los cinco classes*.dex:
DEX |
Cadena |
|---|---|
classes.dex |
DEXGUARD, Dexguard |
classes2.dex, classes5.dex |
Lcom/netflix/mediaclient/dexguard/impl/DexguardImpl$DexguardModule; |
classes3.dex |
Lcom/netflix/mediaclient/service/player/streamingplayback/exosessionplayer/DexguardInitializerError; |
classes3.dex |
Ignore dexguard error |
classes3.dex |
SPY-37220 - dexguard unable to decrypt ASE |
Las dos últimas son las más elocuentes: son mensajes de manejo de errores del propio
Netflix ante fallos del descifrado de DexGuard, con hasta un identificador de incidencia
interno. El producto está integrado como un módulo de inyección de dependencias
(DexguardModule) del propio código de la aplicación.
Repaquetado agresivo. El manifiesto declara android:name="o.KY" como clase
Application. De las 43.838 clases definidas en los cinco DEX, 17.329 están en un
paquete llamado o —el 39,5 %—, mientras que el resto conserva su paquete real
(com/netflix/…, androidx/…, com/google/android/gms/…, 1.337 paquetes distintos).
Es decir: DexGuard mueve el código propio a un paquete de una letra y deja las bibliotecas de
terceros donde estaban.
Cifrado de clases y de assets. En la raíz del APK, no en assets/, hay ocho
ficheros con nombres de dos caracteres:
a- 9.329 bytes b'java/lang/Integer' + 9.312 bytes de alta entropía
b- 1.153 java/lang/Integer + …
c- 9.893 java/lang/Integer + …
d- 46.969 java/lang/Integer + …
e- 9.900 java/io/Serializable + …
f- 45.460 java/io/Serializable + …
g- 14.012 java/io/Serializable + …
h- 69.996 java/io/Serializable + …
Cada uno empieza por un descriptor de tipo Java en ASCII plano y sigue con datos opacos. Hay
además un assets/e de 205.861 bytes sin cabecera reconocible y un assets/volicense_av1.dat
de 135 bytes que tampoco es texto.
Ofuscación de recursos, completa. De las 4.656 entradas bajo res/, 4.655 están en
res/raw/ con nombres de una a dos letras (A.xml, AW.xml, BA.xml…), y la restante en
res/xml/. La estructura por tipo de recurso ha desaparecido del ZIP. Y en el
resources.arsc, el nombre de cada entrada es su propio resource ID en decimal:
type anim id=01 entryCount=54
resource 0x7f010002 anim/2130771970
() (file) res/raw/f.xml type=XML
0x7f010002 = 2.130.771.970. Es una firma distinta de la de aapt2 (sección 2.9) y por sí
sola distingue a DexGuard de una aplicación optimizada con las herramientas de Google.
Lo que DexGuard se llevó por delante. No hay ni un META-INF/*.kotlin_module en el
APK, cuando aplicaciones ofuscadas con R8 a secas conservan decenas (sección 6.2). No hay
marcador ~~R8 en ningún DEX; solo un ~~D8{"compilation-mode":"release","version":"1.6.68"}
residual de alguna dependencia.
Y lo que no. La mediana de longitud de identificador del primer DEX de Netflix es
10, con solo un 22,0 % de identificadores de tres caracteres o menos. Sus primeras clases
son Landroid/support/v4/media/MediaBrowserCompat$ConnectionCallback;, legibles. El paquete
com/netflix/mediaclient/graphql/models/type/ conserva nombres como ArtworkAlignment y
ArtworkFallbackStrategy. DexGuard está configurado selectivamente: repaquetado y
cifrado sobre una parte del código, no sobre todo.
4. Allatori
De Smardec Inc. Versión 9.8, publicada el 1 de junio de 2026. Ofuscador de bytecode Java
con soporte explícito de Android: se engancha en build.gradle con un doLast tras la
compilación Java y antes de generar el DEX. Eso lo sitúa en un punto distinto de la
cadena que R8, y explica que sus efectos y los de R8 se acumulen.
Su lista de características, verbatim: Name Obfuscation, Flow Obfuscation, Debug Info Obfuscation, String Encryption, 100% Protection Against Popular Decompilers, Optimizing, Watermarking, Incremental Obfuscation, Stack Trace Utility, Build Tool Interface, J2ME Obfuscation, Android Obfuscation.
Dos cosas que no tiene ningún otro de esta lista:
- Marca de agua (watermarking): «cualquier cadena que se incruste en los
jarde la aplicación. Puede ser el copyright, el nombre del cliente, el nombre de la empresa o cualquier otra información que identifique unívocamente el build». Es decir, Allatori añade información identificatoria a propósito, al contrario que el resto. - Caducidad (expiry): «se usa para poner fecha de caducidad a la aplicación. Las
comprobaciones de caducidad se insertan en muchos métodos, no solo en
main».
Su cifrado de cadenas tiene cuatro niveles (enable por defecto —solo cadenas seguras—,
disable, maximum —todos los literales—, maximum-with-warnings) y dos tipos, fast y
strong. El renombrado se configura por separado para paquetes, clases, métodos, campos y
variables locales, con esquemas de nombres que incluyen carácter único, alfabético,
numérico y palabras reservadas.
Licencias: 290 $ por desarrollador individual, 3.750 $ site, 4.850 $ business, y gratuito para proyectos educativos y no comerciales.
Huella: la regla allatori_demo de APKiD busca la cadena ALLATORIxDEMO en el DEX, que
la versión de evaluación incrusta. Un build de pago no la lleva.
5. Zelix KlassMaster
De Zelix Pty Ltd, Australia. Se autodenomina «el primer ofuscador Java de segunda generación de verdad, de uso intensivo». Sus cinco funciones son Name Obfuscation, Flow Obfuscation, String Encryption, Reference Obfuscation y Trim.
Es el más útil de los cuatro como documentación, porque explica sus propios mecanismos con precisión inusual. Ya se le ha citado en las secciones 2.2, 2.4 y 2.6. Dos detalles adicionales:
- Las variables locales no se renombran, se eliminan: «los nombres originales de las
variables locales se han quitado». Es la misma operación que borrar el
debug_info_itemdelDEX(Formato DEX, sección 12), y es irreversible por construcción. - Method Parameter Changes: añade parámetros extra a los métodos —su ejemplo muestra un
longde más— para reforzar la ofuscación por referencia.
Trim es su reductor: «reduce el tamaño de la aplicación eliminando las clases, campos y
métodos sin usar y los atributos de bytecode innecesarios», con análisis de las llamadas a la
Reflection API que podrían ocultar usos legítimos. Es el equivalente del shrinking de R8.
Es un ofuscador de bytecode Java, no de DEX. Su propia página lo define así: «un
ofuscador Java cambia el bytecode Java». La documentación cubre Java ME MIDlet, Ant, Maven y
Gradle, pero no menciona Android ni DEX. ⚠️ sin verificar: la ausencia de mención no
prueba la ausencia de soporte; no se ha comprobado si existe una vía específica para Android.
En la práctica se lo encuentra uno en aplicaciones Android igualmente, porque cualquier
ofuscador de .class funciona antes de D8.
Precios: 585 $ estándar, 290 $ para organizaciones de hasta dos personas, 2.195 $ site.
6. obfuscapk
Obfuscapk es lo contrario de los tres anteriores: académico, de código abierto (licencia
MIT) y de caja negra —opera sobre el APK ya construido, sin código fuente—. Publicado
por Simone Aonzo, Gabriel Claudiu Georgiu, Luca Verderame y Alessio Merlo en SoftwareX
vol. 11 (2020), art. 100403.
Vale la pena por dos motivos. Uno, su catálogo está documentado y es reproducible, lo que lo convierte en el banco de pruebas natural para un deofuscador. Dos, la separación en categorías es una taxonomía útil por sí misma. Sus 21 transformaciones:
| Categoría | Transformaciones |
|---|---|
| Trivial | NewAlignment, NewSignature, Rebuild |
| Rename | ClassRename (incluido el manifiesto), FieldRename, MethodRename |
| Encryption | AssetEncryption, ConstStringEncryption, LibEncryption, ResStringEncryption |
| Code | AdvancedReflection, ArithmeticBranch, CallIndirection, DebugRemoval, Goto, MethodOverload, Nop, Reflection, Reorder |
| Resource | RandomManifest |
| Other | VirusTotal |
CallIndirection («añade métodos envoltorio que invocan a los originales, modificando el
flujo de control»), MethodOverload («explota la sobrecarga de Java creando métodos void
nuevos con nombres idénticos») y Reorder («cambia el orden de los bloques básicos y
reorganiza el código con instrucciones goto») son buenos ejemplos de transformaciones que
un evaluador puede aplicar y medir.
Depende de apktool, apksigner, zipalign y Java.
⚠️ no ejecutado localmente: obfuscapk no está instalado y su dependencia apktool tampoco.
7. Tabla de huellas
Reunida a partir de las reglas YARA de APKiD (versión 3.1.0 de master), de la
documentación de cada fabricante y de la medición directa sobre el corpus. Las huellas de
APKiD se citan verbatim de los ficheros de reglas.
| Transformación | Cómo se ve en el binario | Herramientas que la aplican |
|---|---|---|
| Renombrado | Mediana de longitud de identificador 1–3; paquete raíz o de una letra poblado | R8, DexGuard, Allatori, Zelix, obfuscapk |
| Renombrado hostil | Clases AUX, CON, IF (reglas dexguard_b, dexguard_d); nombres con Unicode no ASCII |
DexGuard, Allatori |
| Cifrado de cadenas | string_ids con secuencias no imprimibles; descifrador estático invocado desde cientos de sitios |
DexGuard, Allatori, Zelix, DexProtector, obfuscapk |
| Cifrado de clases/assets | Ficheros opacos de alta entropía con nombre corto en la raíz o en assets/ |
DexGuard (a-…h- en Netflix), DexProtector (assets/classes.dex.dat, assets/dp.mp3) |
| Flujo de control | Densidad anómala de goto y switch; salida del decompilador que no compila |
DexGuard, Allatori, Zelix, obfuscapk (Goto, Reorder) |
| Código muerto | nop en masa, aritmética sin consumidor |
obfuscapk (Nop, ArithmeticBranch) |
| Reflexión | Proporción alta de Class.forName + Method.invoke con argumentos cifrados |
DexGuard (call hiding), Zelix (Reference Obfuscation), obfuscapk |
| Aritmética | Método a(IIZI[I[[I[I)V con xor-int/2addr (regla dexguard_c) |
DexGuard |
| Recursos, nombres | 0_resource_name_obfuscated (una sola vez en el pool) · (name removed) · <resourceID en decimal> |
aapt2 --collapse-resource-names · DexGuard |
| Recursos, rutas | res/a/b.xml, res/0.xml planos · todo bajo res/raw/XX.xml |
aapt2 --shorten-resource-paths · DexGuard |
| Marca de agua | Cadena ALLATORIxDEMO (regla allatori_demo) |
Allatori (versión demo) |
| Runtime DexGuard | guardsquare/dexguard/runtime/, Ldexguard/util/TamperDetector;, Ldexguard/util/CertificateChecker;, com/guardsquare/dexguard/ (reglas dexguard_c, dexguard_d) |
DexGuard |
| Nativo DexGuard | libdgrt.so, símbolo Java_com_guardsquare_dexguard_runtime_detection_HookDetector (reglas dexguard_native, dexguard_native_a) |
DexGuard 9.x |
| ELF falsificado | Cabecera 7f 45 4c 46 … 44 50 4c 46 — \x7fELF seguido de DPLF (regla dexprotector) |
DexProtector |
| Arxan / Digital.ai | Paquetes L(aaaaaa|bbbbbb|…)/[a-z]{6} y Lcom/arxan/guardit; fichero guardit4j.fin en la raíz |
Arxan / Digital.ai |
| Gemalto | lib/<abi>/libmedl.so |
Gemalto |
Nota metodológica sobre APKiD: es una herramienta de identificación por firma, con las
limitaciones que eso implica. Su versión 3.1.0 declara 297 reglas —132 con etiqueta packer,
75 protector, 68 obfuscator, 14 compiler— y está bajo licencia dual GPL-3.0 y
comercial. La firman Caleb Fenton y Tim Strazzere; el mayor contribuidor por número de
commits es hoy Eduardo Novella.
⚠️ no ejecutado localmente: APKiD no está instalado. Las reglas se han leído directamente de
los ficheros .yara del repositorio; las huellas que se marcan como «medidas sobre el
corpus» se han comprobado con lectores propios, no con APKiD.
Y un resultado negativo que importa: ninguna de las huellas de packer del catálogo de
APKiD aparece en el corpus. Buscadas por nombre de fichero y por contenido de los 323
classes*.dex de los 86 contenedores, no hay libjiagu, ni libsecexe, ni libDexHelper,
ni libshell*.so, ni assets/ijiami.dat, ni pairip. Ver
Packers y protectores, sección 7.
8. Qué NO se puede ofuscar
Esta sección es la más importante para quien construye herramientas, porque describe el suelo: lo que sigue siendo legible pase lo que pase, y sobre lo que se apoya todo el análisis de una aplicación protegida.
8.1 La superficie declarada en el manifiesto
Los android:name de activity, service, receiver y provider tienen que corresponder
a clases que el framework pueda instanciar por nombre. Se pueden renombrar de forma
coherente —el manifiesto se reescribe con el nombre nuevo, y eso es lo que hace R8, y lo
que hace Netflix, cuya Application es o.KY— pero no pueden desaparecer ni dejar de
corresponder. Consecuencia: el manifiesto siempre da la lista exacta de puntos de entrada,
y por tanto el conjunto de clases desde las que arranca cualquier análisis.
Lo mismo vale para los intent-filter, los permisos, los meta-data y los provider
autorities: son contrato con el sistema y con otras aplicaciones.
La formulación académica más precisa que se ha encontrado es de Wermke et al.: «las clases que necesitan ser accesibles desde un contexto externo… las clases que extienden clases nativas de Android como activities, services o content providers deberían quedar sin ofuscar en la mayoría de los casos para que la biblioteca o el sistema puedan invocar sus callbacks».
⚠️ sin verificar: no se ha encontrado ninguna página oficial de Google que afirme
literalmente que AGP genera reglas -keep automáticas a partir del manifiesto. Lo
confirmado es la afirmación funcional: los componentes del manifiesto son entry points del
grafo de R8.
8.2 Los puntos de entrada del framework
Todo método que el framework llama por nombre a través de una interfaz o de una superclase
conserva ese nombre: onCreate, onBind, onReceive, run, compare, test. No es una
concesión del ofuscador, es que el nombre está en la superclase, que vive fuera de la
aplicación. Renombrar el método de la subclase rompería el override.
De ahí que la métrica de la sección 10 de R8 y ProGuard nunca llegue al 100 %: incluso en la aplicación más ofuscada del corpus queda un 8 % de métodos con nombre largo, y son estos.
Las reglas por defecto de AGP amplían ese suelo de forma bien documentada
(R8 y ProGuard, sección 5.2): sobreviven con su nombre los set*/get*
de las subclases de View, los values()/valueOf() de los enum, el campo CREATOR de
todo Parcelable y los métodos anotados con @JavascriptInterface.
8.3 Las llamadas a la API de Android
android.*, java.*, javax.* y androidx.* en su parte no reempaquetada no son parte de
la aplicación: son referencias externas resueltas en tiempo de carga. Aparecen en type_ids
y method_ids con su nombre real, siempre. Ocultarlas exige la indirección por reflexión
de la sección 2.6, que cambia el problema —hay que resolver la cadena— pero no lo elimina, y
que además deja su propia huella.
⚠️ sin verificar: no se ha encontrado una cita oficial que enuncie esto de forma literal. El
mecanismo indirecto sí está documentado: en ProGuard las bibliotecas se pasan con
-libraryjars y se referencian sin procesarse.
8.4 Los métodos nativos
La regla estándar que AGP incluye por defecto es explícita:
-keepclasseswithmembernames,includedescriptorclasses class * { native <methods>; }
con la justificación de Guardsquare: «conservar sus nombres y los de sus clases, para que se
puedan seguir enlazando con la biblioteca nativa». La documentación de JNI de Android da la
alternativa: registrar los métodos dinámicamente con RegisterNatives() desde JNI_OnLoad(),
en cuyo caso «el único símbolo que normalmente hay que exportar es JNI_OnLoad()». Un
ofuscador serio usa esa segunda vía, y entonces el nombre del método Java sí se puede
renombrar — pero la correspondencia sigue existiendo dentro de JNI_OnLoad, en el binario
nativo.
8.5 Lo que sobrevive por descuido
Cosas que no son estructuralmente irrenombrables pero que en la práctica nadie renombra:
META-INF/*.kotlin_module. Conservan el nombre del módulo Gradle. En Shazam, con un 70 % de identificadores cortos, siguen ahíAMSKit_release.kotlin_moduleyapp_googleRelease.kotlin_module; en Reddit,account_impl.kotlin_module,awards_public.kotlin_module,deeplinkdispatch_release.kotlin_module. Es el organigrama del proyecto, intacto. DexGuard sí los elimina: Netflix no tiene ninguno.META-INF/*.versiony*.properties. Netflix lleva 98 ficherosandroidx.<artefacto>.versiony 30play-services-*.propertiesen la raíz, cada uno con el nombre y la versión de una dependencia. Es unSBOMcompleto entregado gratis.- Los mensajes de error y de telemetría propios.
SPY-37220 - dexguard unable to decrypt ASEno debería estar ahí. - Los nombres de recursos referenciados desde el manifiesto, que tienen que resolverse.
Ese suelo es lo que hace posible el análisis pese a todo, y es la base de las heurísticas de Deofuscación, sección 5.
9. Cuánta ofuscación hay realmente
Dos estudios grandes, con cifras que conviene no confundir.
Wermke et al., ACSAC 2018, «A Large Scale Investigation of Obfuscation Use in Google Play», sobre 1.762.868 aplicaciones gratuitas de Google Play:
- «solo el 24,92 % de las aplicaciones están ofuscadas por el desarrollador».
- «Aproximadamente el 25 % de las aplicaciones están ofuscadas, pero ese número sube al 50 % para las más populares, con más de 10 millones de descargas».
- Pero el detector encontró el patrón de renombrado de la familia ProGuard en 1.137.228 (64,51 %). La diferencia con el 24,92 % es la clave: «un gran porcentaje de aplicaciones no fueron ofuscadas intencionadamente por el desarrollador original, sino que incluían bibliotecas de terceros que usaban ofuscación».
- «ProGuard es, con diferencia, la herramienta de ofuscación más popular para Android».
- Y un resultado sobre el factor humano: «el 78 % falló al usar ProGuard correctamente en un escenario más complejo y realista».
Dong et al., SecureComm 2018, «Understanding Android Obfuscation Techniques», sobre 114.560 aplicaciones (26.614 de Google Play, 65.666 de mercados chinos, 22.280 de malware), recogidas entre 2016 y 2017:
| Técnica | Google Play | Mercados de terceros | Malware |
|---|---|---|---|
| Renombrado de identificadores | 57,0 % | 73,0 % | 63,5 % |
| Cifrado de cadenas | 0,0 % | 0,1 % | 5,3 % |
La segunda fila es el dato que más contradice la intuición: el cifrado de cadenas es prácticamente inexistente en aplicaciones legítimas. La explicación que dan los autores es económica: «el cifrado de cadenas no es una característica habitual de los ofuscadores de estantería (ProGuard). Los ofuscadores que ofrecen cifrado de cadenas son caros (DexGuard, DexProtector)».
Consecuencia para quien construye herramientas: un descifrador de cadenas sirve para
analizar malware y aplicaciones de alto valor, no para el catálogo general. Y el
renombrado, que sí es masivo, es exactamente el problema que mapping.txt resuelve cuando
está y que hay que inferir cuando no (Deofuscación).
Los datos del corpus son coherentes con esos rangos. De las 22 aplicaciones comerciales
medidas en detalle en R8 y ProGuard, sección 10.2, 15 tienen
renombrado de identificadores —mediana de longitud de 1 a 3— y 7 no lo tienen
—Telegram, Netflix, Clash Royale, Coin Master, Signal, LinkedIn Learning y Candy Crush, con
medianas de 10 a 17—, aunque varias de estas últimas sí pasaron por R8 con shrinking. Y
solo una, Netflix, tiene cifrado de cadenas y de clases. Aplicar un ofuscador comercial
es la excepción, no la norma.
Fuentes
- 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 íntegra la tabla de técnicas declaradas de la sección 3, las citas de las secciones 2.1, 2.7, 2.8, 2.10 y 2.11, y la compatibilidad hacia atrás con ProGuard.
- DexGuard (página de producto) — https://www.guardsquare.com/dexguard Consultado el 12 de agosto de 2026. De aquí sale que no hay precio público y el enfoque de capas superpuestas y polimorfismo de la sección 3.
- ProGuard manual — Configuration examples — https://www.guardsquare.com/manual/configuration/examples Consultado el 12 de agosto de 2026. De aquí sale la regla estándar de métodos nativos y su justificación (sección 8.4).
- Allatori — Features / Docs / Price — http://www.allatori.com/features.html,
http://www.allatori.com/doc.html, http://www.allatori.com/price.html
Consultados el 12 de agosto de 2026. De aquí salen la lista de características, el
watermarking, el expiry, los niveles de cifrado de cadenas, la integración en Gradle
antes del
DEXy los precios de la sección 4. - Zelix KlassMaster — Features y subpáginas —
https://www.zelix.com/klassmaster/features.html y las páginas
featuresNameObfuscation.html,featuresFlowObfuscation.html,featuresStringEncryption.html,featuresReferenceObfuscation.html,featuresTrim.htmlConsultadas el 12 de agosto de 2026. De aquí salen las citas de las secciones 2.2, 2.4, 2.6 y toda la sección 5, incluida la admisión de que su cifrado de cadenas no es irreversible. - Obfuscapk — https://github.com/ClaudiuGeorgiu/Obfuscapk Consultado el 12 de agosto de 2026. De aquí sale la lista completa de 21 transformaciones con sus descripciones (sección 6) y la cita del paper de SoftwareX. ⚠️ https://www.sciencedirect.com/science/article/pii/S2352711020300108 devolvió HTTP 403; la referencia bibliográfica procede del README del repositorio.
- Wermke, Huaman, Acar, Reaves, Traynor, Fahl — «A Large Scale Investigation of
Obfuscation Use in Google Play», ACSAC 2018 — https://arxiv.org/pdf/1801.02742
Consultado el 12 de agosto de 2026. De aquí salen las cifras de la sección 9, la nota
sobre
AUX/NULde la sección 2.1, la tabla de licencias citada en la sección 3 y la formulación sobre lo que no puede ofuscarse de la sección 8.1. ⚠️ https://dl.acm.org/doi/10.1145/3274694.3274726 devolvió HTTP 403; se ha usado la versión de arXiv. - Dong, Li, Diao, Liu, Liu, Li, Xu, Chen, Wang, Zhang — «Understanding Android Obfuscation Techniques: A Large-Scale Investigation in the Wild», SecureComm 2018 — https://diaowenrui.github.io/paper/securecomm18-dong.pdf Consultado el 12 de agosto de 2026. De aquí salen las dos tablas de prevalencia de la sección 9, las citas sobre reflexión de la sección 2.6 y la observación sobre alfabetos de renombrado de la sección 2.1.
- APKiD — reglas YARA — https://github.com/rednaga/APKiD, ficheros
apkid/rules/apk/obfuscators.yara,apk/packers.yara,dex/obfuscators.yara,dex/packers.yara,elf/obfuscators.yara,elf/packers.yara, leídos en crudo desderaw.githubusercontent.com/rednaga/APKiD/master/Consultados el 12 de agosto de 2026. De aquí salen todas las huellas de la tabla de la sección 7 marcadas con nombre de regla, y los datos del proyecto (versión 3.1.0, licencia dual, autoría, recuento de reglas). aapt2 optimize --help, build-tools 37.0.0 — ejecutado el 12 de agosto de 2026. De aquí salen--collapse-resource-names,--shorten-resource-paths,--save-obfuscation-mapy las directivasno_collapse/no_path_shortende la sección 2.9.- JNI tips — https://developer.android.com/training/articles/perf-jni
Consultado el 12 de agosto de 2026. De aquí sale
RegisterNatives()desdeJNI_OnLoad()y la advertencia sobre keep rules de la sección 8.4. - Mediciones propias sobre el corpus — ejecutadas el 12 de agosto de 2026 sobre
~/corpus-apkconbuild-tools37.0.0 (aapt237.0.0) y Python 3.12. De aquí sale toda la sección 3.1 —cadenas de DexGuard, distribución de paquetes, ficherosa-…h-, estructura deres/, volcado deresources.arsc—, las tres firmas de recursos de la sección 2.9, la ausencia de huellas de packer de la sección 7 y las observaciones sobrekotlin_modulede la sección 8.5.