1. Resumen Ejecutivo
Se realizó un análisis exhaustivo de tráfico de red sobre un dispositivo Android TV Box que ejecuta una aplicación IPTV no autorizada. El trabajo comprendió la captura y análisis de ocho sesiones de tráfico de red, la decompilación del APK, y el diseño de controles de red en un router Mikrotik para forzar el tráfico de autenticación de la aplicación a través de una VPN específica.
La aplicación utiliza técnicas avanzadas de evasión: dominios generados algorítmicamente (DGA), múltiples IPs hardcodeadas en el código nativo encriptado, y una lista exhaustiva de resolvers DNS alternativos (DoH, DoT, DNS público chino). La solución final implementada fue una ruta estática por rangos de IP de Cloudflare a través del túnel WireGuard, que resultó ser el enfoque más robusto al operar a nivel de routing, independientemente de cualquier mecanismo de resolución DNS que use la aplicación.
| Parámetro | Valor |
| Dispositivo | Android TV Box — Android 11 |
| IP local | 172.16.2.253 (LAN 172.16.2.0/24) |
| APK principal | com.android.mgstv / com.android.msandroid |
| Versión APK | 43404 → 43405 (auto-actualización detectada) |
| Serial device (SN) | 5936037ae199ee3fec36ef6c65446dc6 |
| User ID asignado | 783031970 (cuando autenticado) |
| Router | Mikrotik con WireGuard (wireguard1) hacia México |
| Capturas analizadas | 8 archivos PCAP — 3.003 a 18.518 paquetes c/u |
| Solución final | Rutas estáticas por rango Cloudflare vía WireGuard |
2. Infraestructura de la Aplicación
2.1 Endpoints HTTP identificados (puerto 80 — texto plano)
Los siguientes endpoints fueron capturados en texto claro, permitiendo análisis completo de requests y parámetros:
| Host | IP | Endpoint / Función |
| emowvv.dqiswip4.xyz | 172.67.212.138 | POST /api/portalCore/checkForceBind — Auth principal |
| vgwbm.uwfyobivh.com | 104.21.2.120 | GET /epg/v2/live/app/utc-3/26 — Guía de canales EPG |
| yvhcn.hxjebagrv.com | 172.67.129.19 | POST /api/adserver/v2/get_content — Publicidad |
| nxiqj.jgrqyxupl.com | 172.67.148.10 | GET /notice/api/get_notice — Notificaciones |
| zxiws.tcgwhnvym.com | 104.21.29.101 | GET /notice/api/get_notice — Notificaciones (backup) |
| iyut.xgw3sdzoac.com | 172.67.168.132 | GET /MarketServer/update — Actualizaciones APK |
| sgyc.bfj1k2g4v.com | 104.18.53.7 | GET /v1/stargazer — WebSocket persistente |
| 64.31.8.44:23455 | 64.31.8.44 | GET /live/*.m3u8, *.ts — Streaming IPTV (HLS) |
2.2 Endpoints HTTPS identificados (por SNI/TLS)
| Host (SNI) | IP | Función identificada |
| emowvv.dqiswip4.xyz | 172.67.212.138 / 104.21.85.241 | Portal core — Auth HTTPS (failover) |
| eskna.ucpjdhivl.com | 172.67.141.221 | Desconocido — dominio DGA |
| giernh.ss58oknn.com | 104.21.3.74 | Desconocido — dominio DGA |
| sfgknh.qho3cnsyil.com | 104.18.52.105 | Desconocido — dominio DGA |
| oslogs.umeng.com | 47.246.109.109 | Analytics Umeng (Alibaba) — logs |
| utoken.umeng.com | 223.109.148.139 | Autenticación Umeng |
| audid.umeng.com | 123.183.232.13 | Device ID fingerprinting Umeng |
| play.googleapis.com | 216.239.32.223 | Google Play Services |
| mtalk.google.com | 142.250.0.188 | Firebase Cloud Messaging (FCM) |
| hwlauncher-apps-o.api.leiniao.com | 8.209.78.228 | API launcher Leiniao/Huawei |
2.3 Streaming IPTV
El servidor de streaming principal fue 64.31.8.44:23455. El dispositivo descarga segmentos MPEG-TS (.ts) cada aproximadamente 5 segundos mediante HLS, con dos streams simultáneos activos durante las sesiones normales. Se identificaron dos streams activos:
- Stream 1: /live/cyx_93531158996778016/*.ts — descargando segmentos secuenciales cada 5 seg.
- Stream 2: /live/cyx-E82F71FB4B37af64B2F9D947B8FC/*.ts — segundo canal en buffer.
El tráfico de streaming representó más de 13.800 paquetes (~1 MB) en una sola sesión de captura, siendo el flujo más voluminoso de toda la captura.
2.4 Tráfico P2P cifrado
Se identificó tráfico UDP cifrado hacia aproximadamente 50 IPs externas en puertos altos aleatorios. Los payloads comienzan con el byte 0x40 y tienen tamaños fijos de 244 y 260 bytes, sugiriendo un protocolo P2P propietario del sistema IPTV. Varios IPs distintos comparten el mismo puerto de destino (37111), patrón típico de swarm P2P.
- 190.89.30.70:44845 — 313 paquetes UDP (P2P activo en cap. 8)
- 38.165.228.24:30910 — 188 paquetes UDP (P2P activo en cap. 8)
- 181.94.228.29:63455 — 288 paquetes (mayor flujo P2P en cap. 3)
3. Análisis del APK
3.1 Protección — Packer IJM (ijiami)
La aplicación está protegida con el packer comercial IJM (ijiami), ampliamente utilizado en aplicaciones Android del mercado chino. Esta protección hace que el código Java/Kotlin real nunca esté disponible en texto plano en el disco — se desencripta únicamente en memoria en tiempo de ejecución.
| Componente | Descripción |
| s.h.e.l.l.S | Application wrapper — carga libexec.so y desencripta el DEX real |
| s.h.e.l.l.N | Interfaz JNI con funciones nativas — b2b(), al(), l(), r(), ra() |
| s.h.e.l.l.A | AppComponentFactory — intercepta instanciación de componentes Android |
| libexec.so | Loader nativo — desencripta ijiami.ajm en memoria (5.279 strings) |
| libexecmain.so | Dispatcher nativo — tabla de funciones x.101 a x.255 (457 strings) |
| ijiami.ajm | DEX real encriptado — 2,3 MB — formato indl01 — AES-128-CBC |
| ijiami.dat | Datos encriptados — 4,5 MB — firma: 8030d1966a1056d907f85a2d43894f84 |
3.2 Esquema de configuración de dominios
Se encontró el archivo assets/domain_test.json con el esquema de configuración de endpoints. Los valores ‘xx’ son placeholders — los valores reales se inyectan desde el DEX desencriptado en runtime:
{
«portal_main»: «xx», // emowvv.dqiswip4.xyz
«portal_backup»: «xx»,
«epg_main»: «xx», // vgwbm.uwfyobivh.com
«epg_backup»: «xx»,
«market_main»: «xx», // iyut.xgw3sdzoac.com
«notice_main»: «xx», // nxiqj / zxiws.tcgwhnvym.com
«ad_main»: «xx», // yvhcn.hxjebagrv.com
«dccore_main»: «xx», // emowvv.dqiswip4.xyz
«diamond_main»: «xx»,
«datacollect_main»: «xx»
}
3.3 Assets relevantes identificados
| Archivo | Tamaño | Descripción |
| assets/ijiami.ajm | 2,3 MB | DEX real encriptado — formato indl01 — contiene dominios e IPs |
| assets/ijiami.dat | 4,5 MB | Datos encriptados adicionales |
| assets/domain_test.json | 542 B | Esquema de configuración de endpoints (valores en runtime) |
| assets/signed.bin | 71 KB | Verificación de firma del APK — bloquea reempaquetado |
| assets/IJMDal.Data | 17 KB | Datos del packer IJM |
| assets/images/*_encrypted.png | 16-32 B | Blobs de datos cifrados disfrazados de imágenes PNG |
| lib/arm64-v8a/libexec.so | — | Loader nativo principal |
| lib/arm64-v8a/libumeng-spy.so | — | SDK de telemetría Umeng/Alibaba |
3.4 Observaciones de seguridad del APK
- Los dominios de API tienen nombres generados algorítmicamente (DGA-like) — evitan listas negras estáticas.
- Todos los dominios resuelven a IPs de Cloudflare, lo que dificulta el bloqueo por IP individual.
- El body del POST /api/portalCore/checkForceBind viaja con datos en Base64 + cifrado simétrico — envía el serial del dispositivo.
- SDK Umeng (Alibaba) recopila telemetría detallada y la envía a servidores en China (47.246.109.109, 223.109.x.x).
- El APK verifica su propia firma antes de desencriptar — no puede reempaquetarse sin romper el mecanismo.
- La app incluye código de anti-debugging que inspecciona /proc/self/maps buscando libaoc.so, libart.so y linker64.
4. Análisis de las Capturas — Cronología
4.1 Captura 3 — Sesión normal autenticada (línea base)
| INFO | 18.518 paquetes — Sesión completamente funcional con streaming activo |
Primera captura de referencia. El dispositivo inicia desde una IP sin restricciones y completa el flujo de autenticación exitosamente.
- checkForceBind ejecutado con éxito → userId=783031970 asignado
- EPG descargado (~12 MB) desde vgwbm.uwfyobivh.com
- Streaming IPTV activo: 13.816 paquetes TCP a 64.31.8.44:23455 (~1 MB)
- WebSocket /v1/stargazer activo en sgyc.bfj1k2g4v.com
- Tráfico P2P UDP a ~50 IPs externas en puertos altos
- SDK Umeng activo: oslogs, utoken, audid hacia servidores Alibaba en China
4.2 Captura 4 — IP bloqueada, acceso restringido al home
| BLOCK | 3.003 paquetes — checkForceBind falla — userId vacío — sin streaming |
- checkForceBind ejecutado (pkt #85-#479) pero respuesta del servidor indica IP bloqueada
- userId= vacío en todos los requests subsiguientes
- EPG descargado igualmente (~12 MB) — el home se muestra pero sin contenido autenticado
- Sin streaming IPTV, sin WebSocket, sin publicidad
- La captura es asimétrica (solo tráfico saliente visible)
4.3 Capturas 5, 6, 7, 8 — Proceso de bloqueo progresivo
| Cap. | Acción tomada | Comportamiento app | Resultado |
| 5 | Bloqueo DoH 8.8.8.8:443 | Usa IP cacheada 104.21.85.241 hardcodeada. DNS UDP:53 sigue pasando. | IP real en uso — sin efecto |
| 6 | Bloqueo extendido DoH | Detecta DoT (puerto 853) hacia 172.16.2.1. Conecta a emowvv antes del DNS. | IP hardcodeada ignorada en DNS |
| 7 | Bloqueo IP 172.67.212.138 | Failover automático a IP backup 104.21.85.241. userId= vacío. | Failover a IP backup |
| 8 | Bloqueo rango CF completo | Escala a DoH en 1.1.1.1:443 (conecta), 223.5.5.5:443 (AliDNS), 9.9.9.10:443 (Quad9). | Nuevos resolvers DoH activos |
5. Mecanismos de Evasión Identificados
5.1 Resolución DNS — Cascada de fallbacks
La aplicación implementa una estrategia de resolución DNS en cascada con múltiples fallbacks, todos hardcodeados en el DEX encriptado:
| Orden | Servidor | IP | Puerto/Proto | Estado en cap. 8 |
| 1 | Google DoH | 8.8.8.8 | 443 TCP | Bloqueado ✗ |
| 2 | Cloudflare DoH | 1.1.1.1 | 443 TCP | Conecta — activo ✓ |
| 3 | AliDNS DoH | 223.5.5.5 | 443 TCP | Intentando |
| 4 | Quad9 DoH | 9.9.9.10 | 443 TCP | Intentando |
| 5 | Google DNS | 8.8.8.8 | 53 UDP | Pasa (regla dstnat ineficaz) |
| 6 | Local resolver | 172.16.2.1 | 53 UDP / 853 DoT | Pasa — responde IP real |
| 7 | IP hardcodeada | Varias CF | — | Bypasea DNS completamente |
5.2 IPs hardcodeadas en el DEX encriptado
Este es el mecanismo más crítico: la aplicación tiene múltiples IPs hardcodeadas dentro del ijiami.ajm y las usa directamente sin necesidad de consultar DNS. La evidencia es clara en múltiples capturas donde la conexión se establece antes de que se realice cualquier query DNS:
- Captura 5/6: Conecta a 104.21.85.241:443 en pkt[37], DNS query en pkt[86] — 49 paquetes después
- Captura 7: Conecta a 104.21.85.241:443 en pkt[38], DNS query en pkt[99] — 61 paquetes después
- Captura 8: Con rango CF bloqueado, escala a nuevos resolvers DoH para obtener nuevas IPs
IPs de Cloudflare confirmadas para emowvv.dqiswip4.xyz a lo largo de las capturas:
172.67.212.138 — usada en cap. 4, 5, 6
104.21.85.241 — usada en cap. 5, 6, 7 (failover automático)
5.3 Verificación de firma anti-reempaquetado
El loader libexec.so verifica la firma del APK (MD5: 8030d1966a1056d907f85a2d43894f84) antes de desencriptar el DEX real. Cualquier modificación del APK — incluyendo intentar parchear los dominios hardcodeados — rompe la verificación y el loader no desencripta. Esto hace inviable el parcheo estático del APK.
5.4 Anti-debugging
El código en s.h.e.l.l.S.il() lee /proc/self/maps y detecta la presencia de entornos de análisis buscando las librerías:
/lib64/libart.so
/lib64/libaoc.so
/bin/linker64
Si detecta un entorno instrumentado, puede alterar su comportamiento o negarse a desencriptar.
6. Controles de Red Implementados en Mikrotik
6.1 Solución final — Rutas estáticas por rango Cloudflare
Luego de iterar a través de múltiples enfoques (DNS estático, DNAT, bloqueo de IPs individuales), la solución más robusta fue implementar rutas estáticas que fuerzan todo el tráfico hacia los rangos de IP de Cloudflare a salir por el túnel WireGuard hacia México. Esta solución opera a nivel de routing, lo que la hace independiente de cualquier mecanismo DNS que use la aplicación.
/ip route
add dst-address=104.16.0.0/12 gateway=wireguard1 comment=»Cloudflare via MX»
add dst-address=172.64.0.0/13 gateway=wireguard1 comment=»Cloudflare via MX»
6.2 Evolución de los controles implementados
| Control | Implementación | Resultado |
| DNS estático | /ip dns static add name=emowvv… address=172.31.255.1 | Ineficaz — app usa IPs hardcodeadas y DoH |
| Intercepción DNS UDP:53 | dstnat redirect to-ports=53 in-interface=bridge | Parcialmente ineficaz — regla no aplicaba correctamente |
| Bloqueo DoH 8.8.8.8:443 | filter drop dst-address=8.8.8.8 dst-port=443 | Efectivo para 8.8.8.8 — app escala a 1.1.1.1 |
| Bloqueo IP individual | filter drop dst-address=172.67.212.138 | App hace failover a 104.21.85.241 automáticamente |
| Bloqueo rango CF | filter drop dst-address-list=cloudflare_ranges | App escala a nuevos DoH — EPG seguía pasando |
| Ruta estática CF→WG | /ip route add 104.16.0.0/12 gateway=wireguard1 | SOLUCIÓN FINAL — independiente de DNS |
6.3 Controles adicionales recomendados
Para un bloqueo completo de todos los canales DNS alternativos identificados:
# Bloquear todos los DoH conocidos usados por la app
/ip firewall address-list
add list=doh_servers address=8.8.8.8 comment=»Google DoH»
add list=doh_servers address=1.1.1.1 comment=»Cloudflare DoH»
add list=doh_servers address=1.0.0.1 comment=»Cloudflare DoH alt»
add list=doh_servers address=223.5.5.5 comment=»AliDNS»
add list=doh_servers address=223.6.6.6 comment=»AliDNS alt»
add list=doh_servers address=9.9.9.9 comment=»Quad9″
add list=doh_servers address=9.9.9.10 comment=»Quad9 ECS»
/ip firewall filter
add chain=forward action=drop protocol=tcp dst-port=443 \
dst-address-list=doh_servers src-address=172.16.2.253
# Interceptar todo DNS UDP/TCP desde el dispositivo
/ip firewall nat
add chain=dstnat action=redirect to-ports=53 protocol=udp \
dst-port=53 src-address=172.16.2.253 place-before=0
# Bloquear DoT puerto 853
add chain=forward action=drop protocol=tcp dst-port=853 \
src-address=172.16.2.253
7. Fingerprint del Dispositivo
Los siguientes parámetros identifican de manera única al dispositivo en todas las capturas:
| Parámetro | Valor |
| IP local | 172.16.2.253 |
| Serial (SN) | 5936037ae199ee3fec36ef6c65446dc6 |
| User ID | 783031970 (cuando autenticado) |
| APK package | com.android.mgstv / com.android.msandroid |
| APK versión | 43404 → 43405 |
| Build fecha APK | 2024-11-11 14:53:20_30_11_ |
| OS | Android 11 |
| User-Agent HTTP | okhttp/3.12.12 |
| Firma APK (MD5) | 8030d1966a1056d907f85a2d43894f84 |
| Timezone EPG | UTC-3 (Argentina) |
| Idioma | es (español) |
| mDNS device name | TV-Oficina |
8. Trabajo Pendiente — Dump con Frida
Para obtener la lista completa de IPs hardcodeadas y la configuración real de dominios dentro del DEX encriptado, se requiere un dump de memoria en runtime usando Frida. El setup requerido ya fue identificado:
8.1 Setup disponible
- Android Studio con AVD configurado y app instalada
- Emulador con acceso root (imagen AOSP sin Google Play)
- frida-tools instalado en PC
- frida-server para x86_64 listo para subir al emulador
8.2 Comando para dump
# Subir frida-server al emulador
adb push frida-server /data/local/tmp/
adb shell chmod 755 /data/local/tmp/frida-server
adb shell ‘su -c «/data/local/tmp/frida-server &»‘
# Dump de todos los DEX en memoria (incluye el desencriptado)
frida-dexdump -U -f com.android.mgstv –deep -o ./dex_dump/
# Buscar dominios e IPs en el DEX dumpeado
strings dex_dump/*.dex | grep -E ‘dqiswip4|portalCore|portal_main|[0-9]{1,3}\.[0-9]{1,3}\.[0-9]’
8.3 Resultado esperado
El dump debería revelar la lista completa de IPs hardcodeadas (actualmente se conocen al menos dos: 172.67.212.138 y 104.21.85.241), la lista de resolvers DoH en orden de prioridad, y los valores reales del archivo domain_test.json que actualmente muestran ‘xx’ como placeholder.
9. Estado Final de Controles
| Control | Descripción | Estado |
| DoH Google (8.8.8.8:443) | Bloqueo TCP port 443 dst 8.8.8.8 | OK |
| DoH Cloudflare (1.1.1.1:443) | Pendiente en bloqueo DoH list | PEND |
| DoH AliDNS (223.5.5.5:443) | Pendiente en bloqueo DoH list | PEND |
| DoH Quad9 (9.9.9.10:443) | Pendiente en bloqueo DoH list | PEND |
| DoT puerto 853 | Bloqueo TCP/UDP port 853 | PEND |
| DNS UDP:53 intercepción | dstnat redirect — regla verificar | PEND |
| IP 172.67.212.138 bloqueada | filter drop chain forward | OK |
| IP 104.21.85.241 bloqueada | Incluida en rango CF | OK |
| Rango 104.16.0.0/12 via WG | Ruta estática wireguard1 | OK |
| Rango 172.64.0.0/13 via WG | Ruta estática wireguard1 | OK |
| Auth emowvv bloqueada | userId= vacío en todos los requests | OK |
| Streaming IPTV | Sin .ts / .m3u8 en capturas post-bloqueo | OK |
10. Conclusiones
La aplicación IPTV analizada implementa un conjunto sofisticado de mecanismos de evasión que van más allá de lo típico en apps de esta categoría: packer comercial con verificación de firma, IPs de destino hardcodeadas en código nativo encriptado, y una cascada de resolvers DNS alternativos (DoH/DoT hacia Google, Cloudflare, AliDNS y Quad9) para garantizar conectividad incluso ante bloqueos parciales.
La solución de rutas estáticas a nivel de routing resultó ser la más efectiva porque opera por debajo de todos los mecanismos de evasión de la app: no importa qué DNS use, no importa qué IP obtenga para el dominio de auth — si esa IP cae en el rango de Cloudflare, el paquete sale por el túnel WireGuard hacia México.
El análisis completo de las IPs hardcodeadas en el DEX queda como tarea pendiente con el dump de Frida, lo que permitiría refinar los controles si en el futuro la app agrega IPs fuera del rango de Cloudflare.