Análisis de un "Nuisance Malware" construido en AndroLua
Análisis de un Malware que aprovecha el interprete AndroLua, utilizado para ejecutar codigo Lua, logrando abusar del modo inmersivo y las capacidades de accesibilidad de Android.
Información
| FILE INFORMATION | APP INFORMATION |
File Name Sky-vpn-Premium.apk | App Name Sky vpn |
Size 1.27MB | Package Name com.wg.xvideos.app |
MD5 627054992ef21db2e00766f40f419b06 | Main Activity com.androlua.Welcome |
SHA1 f0a62335a7d0c04d91f3ed2f7170058eed6a057f | Target SDK 26 Min SDK - Max SDK - |
SHA256 2c40207ffdd44a0ce1527fc2125e604f973cfa1858283afc77d3410f830ef645 | Android Version Name - Android Version Code - |
Distribución y Detección
Navegando por grupos locales de Telegram Argentina, encontré esta aplicación Sky-vpn-Premium.apk siendo distribuida alegando ser, por su nombre, algún tipo de VPN para dispositivos Android.
VirusTotal detectó comportamiento anómalo, alertando sobre un posible PUA y asignándole las etiquetas androlua, dnotua, andr, destaca la ejecución de archivos Lua mediante el intérprete AndroLua. Este intérprete permite crear aplicaciones móviles utilizando el lenguaje Lua para interactuar con la API de Java, consiguiendo funcionar como una aplicación normal de Android.
La misma muestra también circula con otros nombres1, con diferentes extensiones2 y otras similares que podrían tener un comportamiento distinto3.
Aunque VirusTotal detecta al ejecutable Dalvik (.dex) como el peligroso, lo más interesante son todos los archivos Lua que se pueden ver. Aquellos que se encuentran dentro del directorio lua/ son los mismos encontrados en el proyecto AndroLua_pro , por lo que es posible que se haya utilizado este mismo para el desarrollo de la aplicación.
Según la documentación del repositorio, init.lua es el archivo de configuración y empaquetado, mientras que main.lua es mencionado como el programa de entrada y el cual debe tener la lógica principal.
Análisis de la Aplicación
Lo primero a revisar es el AndroidManifest.xml, y uno de los puntos más llamativos son los permisos que aparecen bajo el estado android.permission.UNKNOWN.
No estoy del todo seguro de por qué ocurre esto, sin embargo el output de logcat muestra que el PackageParser está “ignorando duplicados”.
1
2
3
05-10 19:54:39.794 1802 1833 W PackageParser: Ignoring duplicate
uses-permissions/uses-permissions-sdk-m: android.permission.UNKNOWN
in package: com.wg.xvideos.app at: Binary XML file line #68
Revisando otros reportes4 asociados a aplicaciones utilizando AndroLua, este comportamiento aparece con cierta frecuencia. Podría ser una intención deliberada para dificultar el análisis, o talvez simplemente un error en el proyecto.
Hay algunos servicios que apuntan a código de Tencent, pero es solamente código basura, no se llaman en ningún momento. De todas formas, no parece haber cambios importantes en este archivo con respecto al encontrado en el repositorio original (AndroidManifest.xml), y ese es justamente el problema.
El framework AndroLua esta pensado para permitir que el codigo Lua pueda ejecutarse sin restricciones y evitar tener que preocuparse por permisos y configuraciones. Por eso, de forma predeterminada crea aplicaciones con acceso a servicios de accesibilidad (accessibility_service_config.xml), acceso a rutas del sistema (androlua_filepaths.xml), acceso completo a la JVM y todo a disposición del código Lua insertado.
Intérprete de Comandos y Scripts
De los diez archivos .lua presentes, solo dos contienen código legible: main.lua.bak y layout.lua.bak. Su función es bastante clara, main.lua.bak bloquea la pantalla y reproduce en bucle un archivo de audio. Para lograrlo, activa el modo inmersivo mediante SYSTEM_UI_FLAG_HIDE_NAVIGATION y SYSTEM_UI_FLAG_IMMERSIVE, este modo oculta la barra de navegación y es común su uso. Sin embargo, el script mantiene ese estado con un temporizador (Ticker) que se ejecuta cada 10 milisegundos, haciendo imposible salir de la aplicación con la pantalla táctil.
Por otra parte, layout.lua.bak solo construye un botón con un texto en chino y una firma: “惊不惊喜 意不意外 亮少修改联系QQ2328031994”. La traducción sería algo como: “¿Te ha sorprendido? ¿Te ha tomado por sorpresa? Contacta a Liang Shao para realizar modificaciones: QQ 2328031994”. Ese número es un identificador Tencent QQ, un valor único asociado a una cuenta específica.
El usuario vinculado a ese identificador es liangshao666. A partir de su repositorio, existe una posibilidad de que se trate del autor o al menos, el responsable de modificar esta aplicación.
Archivos Ofuscados
Estos archivos .bak son interesantes porque podrían reflejar el comportamiento de la aplicación, pero estos no son los que se están ejecutando realmente. Los archivos utilizados por la muestra son los .lua que están dentro de lua/ y assets/, pero tanto main.lua como el resto de los scripts se encuentran ofuscados. A primera vista pareceria tratarse de base64 pero hay otro tipo de manipulacion en juego.
La responsable de procesar estos scripts ofuscados es la librería nativa libluajava.so. Esta librería actúa como puente entre Lua y Java, exponiendo toda la JVM al intérprete Lua. La función de interés es luaL_loadbufferx, que decodifica los scripts al momento de cargarlos, realizando distintas operaciones según los datos que recibe. aunque voy a centrarme únicamente en la variante que utiliza la muestra.
El flujo realiza dos comprobaciones principales y 4 operaciones donde manipula los datos.
Primero reconoce cómo está codificado el contenido, verifica si el primer byte es el carácter =. Si lo es, decodifica el buffer completo usando base64. Luego hace otra comprobación verificando nuevamente, si el nuevo primer byte ahora es 0x1c. Si efectivamente lo es, entonces realiza una operacion “XOR acumulado”, luego define el primer byte con el valor 0x78 (el magic number de zlib) y por ultimo realiza un “zlib inflate” que en definitiva es una descompression de datos. El resultado de esto debería ser el Lua bytecode de los archivos mencionados.
flowchart TB
A[luaL_loadbufferx] --> B["byte[0] == '=' ?"]
B -->|yes| F[Base64 decode]
F --> C
B -->|no| D["byte[0] == 0x1b ?"]
D -->|yes| E["byte[1] == 'L' ?"]
D -->|no| C
E -->|yes| G[lua_load]
E -->|no| R[Rolling XOR]
R --> G
C["byte[0] == 0x1c ?"]
C -->|yes| H[Cumulative XOR]
H --> I["byte[0] = 0x78"]
I --> J[zlib inflate]
C -->|no| G
J --> G
Con unas pocas líneas en Python puedo reproducir la misma lógica y obtener los binarios Lua originales. El encabezado 1b 4c 75 61 corresponde a \x1bLua, la firma estándar utilizada por el bytecode de Lua.
1
2
3
4
5
6
file decoded_main.luac
decoded_main.luac: Lua bytecode, version 5.3
xxd -g 1 -l 32 decoded_main.luac
00000000: 1b 4c 75 61 53 00 19 93 0d 0a 1a 0a 04 04 04 08 .LuaS...........
00000010: 08 78 56 00 00 00 00 00 00 00 00 00 00 00 28 77 .xV...........(w
Ahora con Unluac puedo decompilar los binarios y tratar de leer el código fuente. Tenga en cuenta que Unluac debe estar compilado en 32 bits.
1
java -jar unluac_2025_12_23.jar decoded_main.luac > source_main.lua
El código parece tener otra capa de ofuscación en los nombres de variables y símbolos. No vale la pena perder tiempo intentando deofuscar completamente el código, mientras la lógica sea entendible es suficiente. Aun así, el resultado permite relacionar source_main.lua con el contenido original de main.lua.bak.
izquierda source_main.lua / derecha main.lua.bak
En esencia, el comportamiento parece ser el mismo. No encontré otro tipo de comportamiento en este archivo.
izquierda source_main.lua / derecha main.lua.bak
De todas formas, todavía queda revisar el resto de los scripts Lua en busca de código oculto. A diferencia de main.lua, no tengo archivos de referencia equivalentes para comparar el comportamiento original. Por eso, en lugar de intentar interpretar directamente los scripts decompilados y ofuscados, voy a desensamblar el bytecode de Lua para leer el assembly. Una operación similar puede verse en Luraph_Deobfuscator. Luadec también debe compilarse en 32 bits.
El único archivo que sobresale es DebugAssistant.lua. Se encarga de construir una ventana de “menú”. Probablemente se trate de un MOD para algún juego, eso explicaría por qué hay código basura dentro de la aplicación. Igualmente, DebugAssistant.lua no se ejecuta en ningún momento y no contiene lógica maliciosa.
Origen y Atribución
Este comportamiento es muy similar al descrito en el denominado “incidente del 27 de septiembre”, un evento que tuvo lugar el 27 de septiembre de 2019 y que afectó a múltiples universidades chinas. El software original se conoce con el nombre “送给最好的TA.apk” literalmente “Para esa persona tan especial”.
La muestra “Sky-vpn-Premium.apk” tiene el mismo comportamiento, reproducir audio con el volumen forzado al máximo y bloquear los controles del dispositivo. Varios medios y artículos mencionan que la app comenzó como una broma entre estudiantes y que luego fue modificada por terceros para incluir capacidades de recolección de datos privados.
“El creador de esta aplicación es un estudiante universitario de una prestigiosa universidad. Cuando la desarrolló, pensó que había demasiados estudiantes que jugaban con el móvil en clase, por lo que quiso darles una lección. Sin embargo, lo que no esperaba era que la aplicación se difundiera a una velocidad vertiginosa y que, además, se modificara su código, convirtiéndola en un virus capaz de recopilar datos personales. Por lo tanto, a los estudiantes que se encuentren con este virus, lo mejor es que no lo sigan difundiendo.”
Conclusión
La aplicación analizada en este artículo es una variante modificada de una app que comenzó a circular en China alrededor de 2019. Su función principal consiste en bloquear el uso de la pantalla táctil y reproducir audio a volumen elevado. No encontré otra funcionalidad maliciosa durante el análisis.
La presencia de los archivos layout.lua.bak y main.lua.bak sin ofuscación dentro de la aplicación probablemente explica por qué esta muestra se reprodujo y modificó tantas veces. A diferencia del resto, estos archivos pueden leerse y editarse fácilmente, permitiendo reutilizar la lógica principal sin necesidad de comprender el mecanismo de carga implementado en libluajava.so. Por este motivo, no hay que descartar que existan otras versiones que podrían incorporar funcionalidades maliciosas adicionales.








