Packers y protectores
1. Alcance de este documento, antes de nada
Este documento explica el modelo de funcionamiento de un packer y las huellas que permiten
identificarlo. No explica cómo extraer el payload, ni cómo volcar el DEX descifrado de
memoria, ni cómo instrumentar el cargador. Esa línea —identificar sí, neutralizar no— es
la que separa este documento de un manual de desempaquetado, y la sección 9 explica el
razonamiento económico y legal que hay detrás en vez de dejarlo como una prohibición sin
argumento.
La consecuencia práctica: aquí hay nombres de fichero, nombres de clase, cadenas y estructuras que sirven para contestar la pregunta «¿qué me he encontrado?». Buena parte de la literatura académica que se cita describe además cómo desempaquetar; de esos trabajos se ha extraído únicamente el modelo y las huellas.
2. Qué es un packer y por qué existe
Un ofuscador (Ofuscadores comerciales) transforma el código
para que sea difícil de leer. Un packer hace algo distinto y más radical: quita el
código del APK. Lo cifra, lo guarda como datos, y pone en su lugar un classes.dex
mínimo cuyo único trabajo es descifrarlo y cargarlo en tiempo de ejecución.
Duan et al. lo enuncian de forma compacta: «una aplicación Android empaquetada suele empaquetar su código Java, y también su código nativo, dentro de ficheros de recurso binarios. Normalmente mantiene además un componente Java ficticio, que actúa únicamente como despachador para lanzar el procedimiento de desempaquetado».
Existe por dos motivos que conviene separar. El legítimo: proteger propiedad intelectual —la lógica de un juego, un algoritmo de recomendación, un DRM— frente a la copia y el reempaquetado. El otro: el malware lo usa para evadir el análisis estático de los antivirus, y de hecho lo usa mucho más que las aplicaciones legítimas (sección 6).
Para el analista, la diferencia con un ofuscador es cualitativa. Con un ofuscador queda
código que decompilar, aunque sea horrible. Con un packer, el decompilador abre el APK y
encuentra doscientas clases de infraestructura. Todo lo que le interesa está en un fichero
de assets/ que es ruido.
3. El modelo común
Casi todos los packers implementan la misma arquitectura, con variaciones en el cifrado, en el momento de la carga y en el nivel de granularidad.
APK empaquetado
┌──────────────────────────────────────────────────────────────────┐
│ AndroidManifest.xml │
│ <application android:name="com.qihoo.util.StubApplication"/> │ ①
├──────────────────────────────────────────────────────────────────┤
│ classes.dex ← el STUB. Pocas clases, ninguna de la app real │ ②
├──────────────────────────────────────────────────────────────────┤
│ assets/ijiami.dat · assets/classes0.jar · assets/sealed1.dex │ ③
│ ← el DEX real, cifrado │
├──────────────────────────────────────────────────────────────────┤
│ lib/arm64-v8a/libjiagu.so · libDexHelper.so · libshella.so │ ④
│ ← el descifrador y el cargador, en nativo │
└──────────────────────────────────────────────────────────────────┘
Y su secuencia de arranque:
Zygote crea el proceso
│
▼
① Instancia la clase declarada en android:name ─── la del packer, no la de la app
│
▼
② Application.attachBaseContext(Context) ─── el gancho: se ejecuta ANTES de onCreate
│
├──► System.loadLibrary("jiagu") ─── ④ carga el descifrador nativo
│ │
│ ├──► lee ③, descifra el DEX real
│ └──► lo entrega al runtime
│
├──► DexClassLoader / InMemoryDexClassLoader
│ o manipulación directa de estructuras de ART
│
└──► por reflexión, sustituye el ClassLoader del proceso
y restaura la Application original de la app
│
▼
③ A partir de aquí la aplicación se ejecuta como si nada
3.1 El gancho: attachBaseContext
Es el detalle técnico que hace posible todo lo demás, y merece su propia explicación porque es donde converge toda la literatura.
Application hereda de ContextWrapper un método protected void attachBaseContext(Context base),
disponible desde el nivel de API 1. La documentación de Application da la otra mitad: «puedes
proporcionar tu propia implementación creando una subclase y especificando el nombre
completamente cualificado de esa subclase como atributo android:name en la etiqueta
<application> de tu AndroidManifest.xml. La clase Application… se instancia antes que
cualquier otra clase cuando se crea el proceso de tu aplicación/paquete».
Duan et al. lo señalan como el punto canónico: «AttachBaseContext() es la función que los
packers suelen sobrescribir para realizar estas tareas, ya que el framework la llama
incluso antes que OnCreate() y tiene el momento ideal para el preprocesado».
DexHunter añade el matiz que explica una huella concreta: las clases envoltorio
com.bangcle.protect.ApplicationWrapper y com.shell.SuperApplication «solo aparecen si el
fichero dex original tenía una clase Application». Es decir, el packer sustituye la
Application del desarrollador y, si existía, la reintroduce después. Un manifiesto que
declare una Application con un nombre de paquete que no tiene nada que ver con el de la
aplicación es la señal más barata que existe.
3.2 Las tres vías de carga
| Vía | API | Qué implica |
|---|---|---|
DexClassLoader |
Nivel 3 | «Carga clases de ficheros .jar y .apk que contengan una entrada classes.dex. Se puede usar para ejecutar código no instalado como parte de una aplicación». Deja el DEX descifrado en disco |
InMemoryDexClassLoader |
Nivel 26 | «Carga clases desde un buffer que contiene un fichero DEX. Se puede usar para ejecutar código que no se ha escrito en el sistema de ficheros local» |
| Manipulación directa de ART | — | El nativo modifica las estructuras internas del runtime. La más difícil de observar |
Corrección frecuente: DexClassLoader no está obsoleta. Lo que está obsoleto es su
parámetro optimizedDirectory, que «está deprecado y no tiene efecto desde el nivel de API
26». La clase sigue documentada y sin aviso de deprecación, con la advertencia de siempre:
«No caches clases optimizadas en almacenamiento externo. El almacenamiento externo no
proporciona los controles de acceso necesarios para proteger tu aplicación frente a ataques
de inyección de código».
Los constructores de InMemoryDexClassLoader llegaron en tres oleadas, y el dato sirve para
acotar la edad de un packer: el de un solo ByteBuffer en API 26, el de array de buffers en
API 27, y el que además acepta librarySearchPath en API 29.
La tercera vía, la manipulación de estructuras, la documentan los trabajos de
desempaquetado. Happer describe que «los packers enganchan ClassLinker::LoadMethod para
modificar los objetos ArtMethod», y DexHunter que se «modifican estructuras de datos
clave en memoria, como DexHeader, ClassDef y ArtMethod». Las funciones nativas del
runtime que aparecen citadas son openDexFileNative(), openDexFile_bytearray() y
DexFile::<init>.
3.3 Tres niveles de sofisticación
La literatura describe tres regímenes claramente distintos. Los ordinales son una construcción editorial de este documento; ⚠️ sin verificar: no se ha encontrado ninguna fuente que use la etiqueta «primera / segunda / tercera generación» de forma literal, así que cada nivel se ancla a la fuente que lo describe.
Nivel 1 — liberación completa. Todo el DEX original se descifra de una vez antes de que
el flujo de control llegue a él. AppSpear lo llama full-code releasing: «descifra el fichero
DEX cifrado entero antes de que el flujo de control llegue a él». Parema lo formula como
regla: «los mecanismos tradicionales de empaquetado de aplicaciones siguen una regla de
escribir-y-luego-ejecutar».
Nivel 2 — descifrado incremental y por capas. AppSpear: «incremental code releasing, que descifra selectivamente solo la porción de código que se ejecuta realmente, y puede volver a cifrarla después de ejecutarla». El packer de Baidu, según el mismo trabajo, usa «cifrado en dos capas». Duan et al. lo miden: «estrategia de desempaquetado multicapa… capa a capa durante la ejecución», con Bangcle en 9 capas, ijiami en 4, Qihoo en 4 y Tencent en 40.
Nivel 3 — virtualización y traslado a nativo. Parema: «los packers de Android más recientes adoptan protecciones basadas en máquina virtual que nunca liberan el DCode… traducen el DCode a otro tipo de bytecode personalizado, denominado PCode, e incrustan una máquina virtual personalizada (P-VM)». Happer describe la variante complementaria: «JNI Transformation… los packers pueden usar código nativo para reimplementar métodos seleccionados de la aplicación».
Ese tercer nivel es la razón de que el desempaquetado genérico haya dejado de ser un problema
resoluble de una vez: no hay ningún momento en el que el DEX original exista.
4. Las familias
Origen, mercado y qué las distingue. El estado de cada sitio web se ha comprobado directamente el 12 de agosto de 2026 y se declara, porque buena parte de este ecosistema es chino y sus sitios entran y salen.
| Familia | Vendedor / origen | Estado del sitio | Qué la distingue |
|---|---|---|---|
| Jiagu / 加固保 | Qihoo 360 (Beijing, China), plataforma «360 Tianyu» | jiagu.360.cn responde, pero es una SPA vacía sin contenido legible |
El packer chino más extendido. Su .so incluye una función señuelo llamada youAreFooled |
| Bangcle | Bangcle (China) | ❌ bangcle.com devuelve HTTP 500 |
Uno de los más antiguos. 9 capas de descifrado según Duan et al. |
| SecNeo | SecNeo (China) | ❌ secneo.com NXDOMAIN |
Tres variantes (A, B, C) distinguibles por su biblioteca nativa |
| Tencent Legu / 乐固 | Tencent (China) | ❌ portal de producto inaccesible | El más agresivo por capas: 40 según Duan et al. Assets con nombres deliberadamente confusos (0OO00l111l1l) |
| Virbox Protector | Beijing Shendun Technology (Pekín, China) | ✅ shell.virbox.com |
Cubre nativo, Java, .NET, Python, ARM Linux. Ofrece shell y virtualización |
| DexProtector | Licel | ✅ dexprotector.com, licelus.com |
«Cifra de forma segura cadenas, clases enteras, assets y recursos». Motor InvokeDynamic. Falsifica la cabecera ELF de sus bibliotecas |
| Ijiami / 爱加密 | Beijing Zhiyou Wang'an (Pekín, China) | ⚠️ ijiami.cn sí, www.ijiami.cn no resuelve |
Declara «tecnología de endurecimiento de sexta generación» y «más de un millón de apps» |
| Baidu | Baidu (China) | ✅ apkprotect.baidu.com |
Declara «6W+ apps endurecidas» y cobertura de AAB |
| AppSealing | Rebautizado como DoveRunner | ⚠️ redirige; doverunner.com devuelve 403 |
Producto AppSecurity. Marca sus assets con un directorio propio |
| Promon Shield | Promon AS, Oslo, Noruega, fundada en 2005 por Tom Lysemose | ✅ promon.io |
«RASP, anti-tamper, anti-repackaging y protección de código de primer nivel, todo integrado en profundidad post-compilación» |
| DexGuard | Guardsquare, Lovaina, Bélgica | ✅ | Cifra clases selectivamente. Ver la nota de abajo |
| Alibaba / Aliyun | Alibaba (China) | ❌ producto retirado o movido: 404 y NXDOMAIN | Su huella (libmobisec.so) sigue en las reglas de APKiD |
| APKProtect / Naga | — | — | Varias generaciones con huellas muy distintas |
| KiwiSec | KiwiSec | — | Bibliotecas libkiwi* |
Sobre DexGuard en modo packing. Guardsquare declara cifrado de clases, pero no usa la
palabra packing en ninguna de sus páginas consultadas, y la evidencia del Netflix del
corpus va en contra de tratarlo como packer: su classes.dex contiene 8.920 clases reales y
decompilables, con androidx y com.google.android.gms
legibles (Ofuscadores comerciales, sección 3.1). Lo que sí
tiene son ocho ficheros cifrados en la raíz. Es cifrado selectivo de clases, la
transformación 2.3 de ese documento, no sustitución del DEX por un stub. ⚠️ sin verificar:
que DexGuard tenga además un modo de packing completo; no se ha encontrado documentación del
fabricante que lo describa.
5. Huellas de detección
Esta es la sección central. Todo lo que sigue está transcrito de las reglas YARA de APKiD
3.1.0, leídas en crudo del repositorio, salvo lo marcado como procedente de otra fuente.
APKiD es la mejor documentación de huellas que existe: 297 reglas, de las cuales 132 con
etiqueta packer y 75 con etiqueta protector, bajo licencia dual GPL-3.0 y comercial.
5.1 Por familia
| Familia | assets/ y raíz |
lib/ |
Clases DEX y cadenas |
|---|---|---|---|
| Jiagu / Qihoo 360 | assets/jiagu_data.bin, assets/sign.bin |
libjiagu.so, libjiagu_art.so, libapktoolplus_jiagu.so, libprotectClass.so |
En la .so: JIAGU_APP_NAME, JIAGU_SO_BASE_NAME, JIAGU_ENCRYPTED_DEX_NAME, JIAGU_HASH_FILE_NAME. Clase com.qihoo.util.StubApplication |
| Bangcle | assets/bangcleplugin/container.dex, bangcleclasses.jar, bangcle_classes.jar, assets/secData0.jar |
libsecexe.so, libsecmain.so, libSecShell.so, libSecShell-x86.so |
com.shell.NativeApplication, com.bangcle.protect.ApplicationWrapper, com.shell.SuperApplication |
| SecNeo | assets/classes0.jar |
libDexHelper.so, libDexHelper-x86.so; variante B libdexjni.so, variante C libdatajar.so |
— |
| Tencent Legu | assets/tosversion, assets/0OO00l111l1l, assets/0OO00oo01l1l, assets/o0oooOO0ooOo.dat; variante VMP assets/wsDal.jar, assets/WSSEC[A-D].jar |
libshell.so, libshella-<v>.so, libshellx-<v>.so, libmobisecy.so, libshell-superv*.<año>.so, libxgVipSecurity.so |
Lcom/tencent/StubShell/TxAppEntry;, Lcom/tencent/StubShell/a, com.tencent.StubShell.ProxyShell. Ruta /mix.dex |
| Virbox | — | libv++.so, libv++_64.so, libsandhook.so, libsandhook-native.so |
En la ELF, la cadena Virbox Protector. Clase Lvirbox/StubApp; |
| DexProtector | assets/classes.dex.dat, assets/classes<n>.dex.dat, assets/resources.dat, assets/dp.mp3, assets/dp.<abi>.so.dat |
libdexprotector.<algo>.so, libalice.so |
Cabecera ELF falsificada: \x7fELF seguido de DPLF en el offset 4 |
| Ijiami | assets/ijiami.dat, ijiami.ajm/ijiami3.ajm, assets/ijm_lib/, assets/IJMDal.Data |
assets/libijmDataEncryption.so |
Stub UPX modificado: la magia UPX! sustituida por AJM! |
| Baidu | baiduprotect1.jar, assets/baiduprotect.jar |
libbaiduprotect.so |
com.baidu.protect.StubApplication |
| Alibaba | — | libmobisec.so |
com.ali.mobisecenhance.StubApplication |
| AppSealing | assets/appsealing.dex, assets/sealed1.dex, assets/AppSealing/* |
libcovault.so, libcovault-appsec.so |
Lcom/inka/appsealing/AppSealingApplication;, AppSealingLoader … v1.2.2, APPSEALING-CORE-VERSION_2.10.10 |
| Promon Shield | — | libshield.so o un nombre aleatorio lib[a-z]{10,12}.so |
Secciones ELF anómalas .ncc (código), .ncd (datos), .ncu |
| KiwiSec / Kiro | assets/sbox |
libkiroro.so, libkiwicrash.so, libkiwi_dumper.so, libKwProtectSDK.so, libkadp.so, libwhite-box.so |
Lcom/kiwisec/crash/CrashUtils;, Lcom/kiwivm/security/StubApplication; |
| APKProtect | assets/apkprotect.bin, assets/apkprotect/classes.dex.bin, apkprotect-build.properties, META-INF/APKPROTECT.RSA |
libapkprotect.so, libAPKProtect.so |
Ruta apkprotect.com/key.dat |
| Naga | assets/maindata/fake_classes.dex |
libedog.so/libddog.so/libfdog.so/libvdog.so, libchaosvmp.so, libxloader.so |
— |
| Otros stubs | — | — | Lcom/manxi/shell/MXApplication; (Manxi), Lcom/merry/wapper/WapperApplication; (PangXie), Lcom/vdog/VDogApplication; (Crazy Dog), Lcom/inca/security/Proxy/JNISoxProxy; (AppGuard), Lcom/security/inner/stub000/x; (DingXiang), Lcom/nesun/stub/ZAP; (Nesun), Lweb/apache/sax/app; (Medusah AppSolid) |
5.2 Huellas transversales, que valen para familias que aún no tienen regla
Las anteriores envejecen: cambian con cada versión del producto. Estas no.
El stub UPX modificado. Tres familias comprimen su biblioteca nativa con UPX y sustituyen
la magia UPX! para que upx -d no la reconozca:
| Sustituto | Familia |
|---|---|
SEC! |
Bangcle y SecNeo |
\x03\x02\x01\x00 |
Bangcle y SecNeo, versiones nuevas |
AJM! |
Ijiami |
La regla busca el marcador dos veces: en los primeros 200 bytes y en los últimos 50 del fichero.
La secuencia de arranque en el bytecode. La regla apkguard_dex de APKiD no busca
nombres: busca la forma del código del stub, con los opcodes anotados uno a uno.
invoke-super {v14, v15}, Landroid/app/Application;.attachBaseContext:(Landroid/content/Context;)V
invoke-virtual {v14}, …getClassLoader:()Ljava/lang/ClassLoader;
new-instance v4, Ldalvik/system/DexClassLoader;
invoke-direct {…}, Ldalvik/system/DexClassLoader;.<init>:(String;String;String;ClassLoader;)V
invoke-virtual {v4, v9}, Ldalvik/system/DexClassLoader;.loadClass:(String;)Ljava/lang/Class;
const-string v10, "attachBaseContext"
invoke-virtual {…}, Ljava/lang/Class;.getDeclaredMethod:(…)
invoke-virtual {…}, Ljava/lang/reflect/Method;.invoke:(…)
Ese patrón —attachBaseContext → DexClassLoader → getDeclaredMethod("attachBaseContext")
→ invoke— es el packer, con independencia de quién lo haya escrito. Es la huella más
duradera que hay, y es genérica.
Anomalías estructurales del DEX. La regla jiagu_k de APKiD comprueba
(dex.header.data_size + dex.header.data_offset) < dex.header.file_size: hay bytes en el
fichero que la cabecera no declara. Es la clase de comprobación que solo se puede hacer
leyendo el fichero por el map_list y no por la cabecera
(Formato DEX, sección 5). En la misma línea: un
class_defs_size de dos cifras en un APK de 30 MB, un assets/ con un fichero de varios
megabytes de entropía uniforme, o un lib/ con una biblioteca cuyo nombre no aparece en
ningún System.loadLibrary visible.
Cabeceras falsificadas. DexProtector escribe DPLF —presumiblemente «DexProtector
Linkable Format»— justo después de \x7fELF en el offset 4, donde debería ir la clase y el
endianness. El comentario de la propia regla incluye el volcado:
0x00000000 7f45 4c46 0101 0100 4450 4c46 0000 0000 .ELF....DPLF.... // armeabi-v7a
0x00000000 7f45 4c46 0201 0100 4450 4c46 00e0 0100 .ELF....DPLF.... // Aarch64
Nótese que los bytes 4 y 5 —clase y data encoding— sí son válidos; lo que se sobrescribe es
el resto del EI_PAD.
5.3 Corroboración independiente
Las reglas de APKiD son firmas; conviene comprobar que describen algo real. El análisis de
Romain Thomas sobre Legu (Quarkslab, 26 de noviembre de 2019) nombra los mismos cuatro
assets: «el packer usa los primeros 16 bytes de assets/tosversion xoreados con una clave
fija», y enumera «tosversion, 0OO00l111l1l, 0OO00oo01l1l, o0oooOO0ooOo.dat». Coinciden
exactamente con la regla tencent_legu.
⚠️ En cambio los nombres de biblioteca de aquel sample —orange-super.2019.so,
orangea-4.1.0.XY.so— no coinciden con los libshell*.so de las reglas. Es el
recordatorio de siempre: las huellas por nombre de fichero envejecen y varían entre versiones
del mismo producto.
6. Prevalencia real
6.1 El 0,59 %
La cifra de referencia sobre cuántas aplicaciones de Google Play llevan packer detectable es un 0,59 %. Conviene arrastrarla siempre con su fuente y con sus tres avisos, porque hay otra cifra casi idéntica con la que se confunde.
La frase original, literal:
«Compare that to Google Play at 0.59% packed and 2.65% obfuscated, an order of magnitude lower on every measure.»
Eduardo Blázquez, «Practical Android Software Protection in the Wild» — An Appetizer,
blog de Quarkslab, 4 de junio de 2026. El artículo acompaña al trabajo revisado por pares
Blázquez, E. y Tapiador, J., «Practical Android Software Protection in the Wild»,
ACM Computing Surveys 58(2), art. 36, DOI 10.1145/3757735, pp. 36:1–36:32. El estudio
analizó 2.450.338 APK (el 99 % de su conjunto de datos), de los cuales «96.169
aplicaciones (~4 %) usaban al menos un packer, ofuscador o protector: 50.664 con packers,
45.320 con ofuscadores y 185 con protectores». La detección se hizo con APKiD.
Tres avisos que hay que arrastrar con la cifra:
- ⚠️ El 0,59 % se ha leído en el blog del primer autor, no en el artículo revisado:
dl.acm.org/doi/full/10.1145/3757735devuelve HTTP 403 y no hay preprint abierto. - ⚠️ El tamaño de la muestra concreta de Google Play detrás de ese 0,59 % no se ha podido confirmar: vive en tablas que el blog publica solo como imagen.
- El propio autor pone el límite, y hay que citarlo con la cifra: «como APKiD se apoya en reglas YARA para la detección, las técnicas anti-análisis no cubiertas por su conjunto de reglas pueden pasar desapercibidas, lo que potencialmente rebaja los resultados».
Y una trampa que hay que evitar activamente: AppSpear (RAID 2015) reporta un 0,58 % que se parece muchísimo y no es lo mismo —es sobre malware de 2013, otra población y otro año—. Confundirlos es fácil y el error se propaga.
6.2 Las otras mediciones
| Fuente | Población | N | Empaquetado |
|---|---|---|---|
| Blázquez y Tapiador, ACM CSUR 58(2) | Google Play | ⚠️ no confirmado | 0,59 % |
| Mauthe, Kargén, Shahmehri, SANER 2021 | Google Play | 13.594 | 127 apps (0,93 %, aritmética propia: el artículo solo publica conteos) |
| Ruggia et al., AsiaCCS 2024 | Google Play, más descargadas, abril de 2023 | 21.154 | 0 — «APKiD no pudo reconocer ningún packer ni protector» |
| Duan et al., NDSS 2018 | Malware, 2010–2015 | 93.910 | 13,89 % |
| Ruggia et al., AsiaCCS 2024 | Malware | — | 11,4 % |
| Blázquez y Tapiador, ACM CSUR 58(2) | Huawei AppGallery / Qihoo 360 / Baidu | — | 43 % / 40 % / 25 % |
La conclusión que sostienen las seis filas a la vez, y que es lo que de verdad importa:
- En Google Play, el packer es una rareza estadística: por debajo del 1 % en las tres mediciones, y cero en la muestra de aplicaciones más descargadas.
- En malware, es un orden de magnitud más frecuente: del 11 % al 14 %.
- En los mercados chinos, es lo normal: del 25 % al 43 %.
6.3 El corpus: cero packers
Buscadas las huellas de la sección 5.1 en los 86 contenedores del corpus —por nombre de
entrada del ZIP, en el base.apk y en cada split, y por contenido en los 323
ficheros classes*.dex— el resultado es ninguna coincidencia. No hay libjiagu, ni
libsecexe, ni libDexHelper, ni libshell*.so, ni assets/ijiami.dat, ni libcovault,
ni libv++, ni libdexprotector, ni libbaiduprotect, ni ninguna de las clases stub de la
tabla.
Es coherente con el 0,59 %: con 86 aplicaciones, la mayoría de F-Droid y el resto del catálogo internacional de Play, la expectativa estadística de encontrar un packer es inferior a una. El corpus no está mal construido; es que el fenómeno no está ahí.
Consecuencia operativa: un detector de packers no se puede validar contra este corpus. Cualquier prueba será de no-regresión y de ausencia de falsos positivos, que sigue siendo valiosa —un detector que dispara sobre 86 aplicaciones limpias es peor que ninguno— pero no mide el recall.
7. Qué se puede seguir haciendo con una app empaquetada
Encontrarse un packer no deja al analista a ciegas. Deja intacto todo lo que el packer no puede tocar porque es contrato con el sistema (Ofuscadores comerciales, sección 8).
Sigue disponible:
| Superficie | Qué se saca |
|---|---|
AndroidManifest.xml |
Nombre de paquete, versión, minSdk/targetSdk, permisos, componentes exportados, intent-filter, provider authorities, meta-data. El manifiesto no está cifrado, porque el sistema lo lee antes de ejecutar nada |
resources.arsc y res/ |
Cadenas de interfaz en todos los idiomas, colores, layouts. Con frecuencia el packer no toca los recursos |
| Firma | Certificado, cadena, esquemas v1–v4, lineage. Ver Esquemas de firma |
lib/*.so |
Análisis del binario nativo. Aquí está el descifrador y, en los packers de nivel 3, también la lógica de la aplicación |
Estructura del ZIP |
Orden de entradas, alineación, marcas de herramienta. Ver Contenedor ZIP |
| El propio stub | Nombres de clase, cadenas, y la biblioteca que carga: identifica al packer y con frecuencia también a su versión |
| Comportamiento observable | Tráfico de red, ficheros creados, permisos ejercidos. Análisis dinámico |
Ya no está disponible: el código Java de la aplicación, su grafo de llamadas, sus cadenas
literales, y todo análisis estático que dependa de ellos —detección de SDKs y trackers
por clase, SBOM desde el DEX, cobertura de decompilación—.
La regla honesta para un informe: decir que la aplicación está empaquetada, decir por qué familia si se sabe, y declarar explícitamente qué análisis quedan invalidados. Un informe que dice «0 trackers detectados» sobre una aplicación empaquetada está mintiendo por omisión, y esa es exactamente la clase de fallo silencioso que un producto serio no puede permitirse.
8. Recetas de identificación
Ejecutables con lo que hay instalado, sin herramientas específicas.
CORPUS=~/corpus-apk
BT=~/Library/Android/sdk/build-tools/37.0.0
APP=$CORPUS/com.termux_1002.apk
| Qué se busca | Orden |
|---|---|
Clase Application declarada |
$BT/aapt2 dump xmltree --file AndroidManifest.xml $APP | grep -A6 'E: application' |
| Bibliotecas nativas | unzip -l $APP 'lib/*' |
| Assets sospechosos | unzip -l $APP 'assets/*' | sort -k1 -n -r | head |
Número de clases del primer DEX |
$BT/dexdump -f <(unzip -p $APP classes.dex) | grep class_defs_size |
| Cadenas del stub | unzip -p $APP classes.dex | strings | grep -iE 'stubapp|shell|jiagu|secmain|dexhelper' |
| Entropía de un asset | unzip -p $APP assets/X | xxd | head — datos cifrados no tienen estructura visible |
Ejecutado sobre el Netflix del corpus, el volcado del manifiesto da
android:name="o.KY", que no es una clase stub conocida sino el resultado del
repaquetado de DexGuard: la comprobación de la sección 3.1 acierta al levantar la sospecha y
el resto del análisis la descarta.
La herramienta de referencia es APKiD:
apkid -j app.apk
⚠️ no ejecutado localmente: APKiD no está instalado y no se instala nada en esta máquina. La
sintaxis procede de su README; sus reglas se han leído directamente del repositorio y son
la fuente de la sección 5.
9. Por qué desempaquetar está fuera del alcance
No es mojigatería, y conviene tener el argumento escrito.
Es un pozo sin fondo por diseño. El repaso de la sección 3.3 muestra la trayectoria: los
packers pasaron de liberar todo el DEX de una vez a liberarlo por capas —hasta 40 en
Tencent— y de ahí a no liberarlo nunca, traduciéndolo a un bytecode propio interpretado por
una máquina virtual generada por build. Cada avance del desempaquetado genérico se responde
con una capa más. Un desempaquetador es correcto contra la versión concreta del packer contra
la que se probó, y deja de serlo con la siguiente. El coste de mantenimiento no converge.
El público es minúsculo. Los datos de la sección 6 son inequívocos: por debajo del 1 % en Google Play, cero en la muestra de las más descargadas, y ni una en las 86 aplicaciones del corpus. Dedicar el esfuerzo más caro de una herramienta de análisis al 0,59 % de los casos, y además a la parte que peor envejece, es una mala asignación aunque fuese gratis.
El riesgo legal es real y asimétrico. Extraer el payload de un protector comercial es una elusión de una medida técnica de protección, con tratamiento distinto en cada jurisdicción y, en varias, con excepciones para investigación en seguridad que no cubren la distribución de una herramienta de propósito general. Detectar e informar no tiene ese problema en ninguna parte: es leer un fichero y decir qué hay dentro.
Y hay una asimetría técnica a favor. La detección es barata —unos cuantos patrones sobre
el ZIP y sobre el DEX—, estable —las huellas estructurales de la sección 5.2 llevan años
sin cambiar—, verificable y publicable. Es, además, lo que el analista necesita el 100 % de
las veces: saber a qué se enfrenta. Desempaquetar solo lo necesita a veces, y quien lo
necesita de verdad tiene herramientas especializadas y un laboratorio.
La frontera resultante, en una frase: se identifica y se informa; no se extrae.
Fuentes
- APKiD — reglas YARA y datos del proyecto — https://github.com/rednaga/APKiD, ficheros
apkid/rules/apk/packers.yara,apk/protectors.yara,dex/packers.yara,elf/packers.yara,elf/protectors.yara,apkid/__init__.py,README.md,LICENSE.COMMERCIAL, leídos en crudo desderaw.githubusercontent.com/rednaga/APKiD/master/Consultados el 12 de agosto de 2026. De aquí sale íntegra la sección 5, y el recuento de reglas, la versión 3.1.0, la licencia dual y la autoría. - Blázquez, E. — «Practical Android Software Protection in the Wild» — An Appetizer —
https://blog.quarkslab.com/practical-android-software-protection-in-the-wild-an-appetizer.html
Consultado el 12 de agosto de 2026 (publicado el 4 de junio de 2026). De aquí sale el
0,59 % de la sección 6.1, el tamaño del conjunto de datos, el reparto entre packers,
ofuscadores y protectores, las cifras de los mercados chinos y el caveat sobre APKiD.
Artículo revisado subyacente: Blázquez, E. y Tapiador, J., ACM Computing Surveys 58(2),
art. 36, DOI 10.1145/3757735. ⚠️
dl.acm.org/doi/full/10.1145/3757735devolvió HTTP 403. - Duan, Zhang, Roy, Bianchi, Bao, Zhang, Wang, Xing — «Things You May Not Know About
Android (Un)Packers: A Systematic Study based on Whole-System Emulation», NDSS 2018 —
https://www.cs.ucr.edu/~heng/pubs/DroidUnpack_ndss18.pdf
Consultado el 12 de agosto de 2026. De aquí salen la definición de packer de la sección 2,
la cita canónica sobre
attachBaseContextde la sección 3.1, el número de capas por familia de la sección 3.3 y el 13,89 % de malware de la sección 6.2. ⚠️ La URLndss2018_01A-4_Duan_paper.pdfque circula está rota; la correcta en el sitio de NDSS esndss2018_04A-4_Duan_paper.pdf. - Zhang, Luo, Yin — «DexHunter: Toward Extracting Hidden Code from Packed Android
Applications», ESORICS 2015 — https://www4.comp.polyu.edu.hk/~csxluo/DexHunter.pdf
Consultado el 12 de agosto de 2026. De aquí salen la lista de clases
Applicationstub por familia y la nota sobreApplicationWrapper/SuperApplicationde la sección 3.1, y los nombres de asset de la sección 5.1. - Yang, Zhang, Wang, Zhu, Xue — «AppSpear: Bytecode Decrypting and DEX Reassembling for
Packed Android Malware», RAID 2015 —
https://link.springer.com/chapter/10.1007/978-3-319-26362-5_17 (DOI 10.1007/978-3-319-26362-5_17)
Consultado el 12 de agosto de 2026. De aquí salen full-code releasing, incremental code
releasing y el cifrado en dos capas de Baidu (sección 3.3), y el 0,58 % que la sección
6.1 advierte de no confundir.
El PDF que circula en
gosec.sjtu.edu.cn/publications/RAID2015Yang.pdfya no existe (404 el 12 de agosto de 2026); se sustituye por la entrada editorial, que es estable. - Xue, Luo et al. — «PackerGrind», «Happer», «Parema» —
https://www4.comp.polyu.edu.hk/~csxluo/PackerGrind.pdf,
https://www4.comp.polyu.edu.hk/~csxluo/Happer.pdf,
https://www4.comp.polyu.edu.hk/~csxluo/Parema.pdf
Consultados el 12 de agosto de 2026. De aquí salen el gancho de
ClassLinker::LoadMethody losArtMethodde la sección 3.2, la JNI Transformation y la P-VM de la sección 3.3. - Thomas, R. — «A Glimpse into Tencent's Legu Packer» — https://blog.quarkslab.com/a-glimpse-into-tencents-legu-packer.html Consultado el 12 de agosto de 2026 (publicado el 26 de noviembre de 2019). De aquí sale la corroboración independiente de la sección 5.3.
DexClassLoader,InMemoryDexClassLoader,PathClassLoader,BaseDexClassLoader— https://developer.android.com/reference/dalvik/system/DexClassLoader, https://developer.android.com/reference/dalvik/system/InMemoryDexClassLoader Consultados el 12 de agosto de 2026. De aquí sale la tabla de vías de carga de la sección 3.2, los niveles de API de cada constructor y la corrección sobre la no deprecación deDexClassLoader.ApplicationyContextWrapper.attachBaseContext— https://developer.android.com/reference/android/app/Application, https://developer.android.com/reference/android/content/ContextWrapper Consultados el 12 de agosto de 2026. De aquí salen las dos citas de la sección 3.1 sobreandroid:namey sobre el momento de instanciación.- Sitios de los fabricantes —
shell.virbox.com,dexprotector.com,licelus.com/products/dexprotector,promon.io/about-us,ijiami.cn,apkprotect.baidu.com,jiagu.360.cn,docs.doverunner.com,guardsquare.com/dexguardConsultados el 12 de agosto de 2026. De aquí sale la tabla de la sección 4, incluido el estado de cada sitio. Fallaron:bangcle.com(HTTP 500),secneo.com(NXDOMAIN), los portales de producto de Tencent (404 y reto anti-bot),senseshield.com(NXDOMAIN),jaq.alibaba.com(NXDOMAIN) yaliyun.com/product/security/mobilesecurity(404). - Mauthe, Kargén, Shahmehri, SANER 2021 — https://www.ida.liu.se/~ulfka17/papers/SANER2021.pdf y Ruggia et al., AsiaCCS 2024 — https://s3.eurecom.fr/docs/asiaccs24_ruggia.pdf Consultados el 12 de agosto de 2026. De aquí salen las dos filas correspondientes de la tabla de la sección 6.2.
- Mediciones propias sobre el corpus — ejecutadas el 12 de agosto de 2026 sobre
~/corpus-apkconbuild-tools37.0.0 y Python 3.12. De aquí sale la sección 6.3 —la búsqueda de las huellas de la sección 5.1 sobre los 86 contenedores y los 323classes*.dex— y la comprobación del manifiesto de Netflix de las secciones 4 y 8.