2011/11/29

「超光懐中電灯」を解析してみた(続編)…まだ続きそう…

追記 2012/10/01

「超光懐中電灯」で調べて本Blogエントリにたどり着いた方へ

本エントリは、2011年11月時点のアプリケーションを解析した結果です。その後アプリケーションは幾度かのバージョンアップを経てApperHandという広告モジュールを導入した結果、いくつかのウィルス対策ベンダからPUP(利用することが望ましくないプログラム)としてスキャンソフトで検知されるようになっています。

本アプリについては、本Blogエントリ「改めて警告します。「超光懐中電灯無料アプリ」は利用することが望ましくないアプリです」を参照ください。


前編は後々まとめて公開します。
@ChihiroShiiji さんの「どうしてこうなった?不思議なパーミッションを要求するAndroidアプリ・カタログ」 をご覧ください。

(前回のまとめ)
「迷惑な広告無し」と謳いながらアプリの機能に不要と思われるPermissionが付与されている超光懐中電灯。
AndroidManifest.xmlを見ると10件もの広告モジュールが組み込まれている事がわかった。
さらにdexを解析すると、広告モジュールの多くが端末情報や位置情報を取得するメソッドを実行している事が判明する。
不可思議なパーミッションの問題は、プライバシー情報の漏えい問題へと発展するのか?!


というわけで、実際に広告モジュールが収集した情報が外部に送信されているか解析を進めてみました。
結論から申し上げますと超光懐中電灯の広告モジュールは黒です。しかもある広告モジュールの仕様はかなり危険です。スパイウェア認定していいくらいに。
なお、限られた時間でもくもくと書きながら解析をしているので、誤字脱字含め勘違い等あればご指摘ください。
結論だけが知りたい方は記事の最下部をご覧ください。。。


先日のTwieetにも書きましたが、広告モジュールが直接HttpRequestを送信しているケースもあり、MAISTのように端末のLogから送信内容を確認するのは困難です。
そこでアプリを実際に動かして通信をキャプチャし解析するか、コードを直接解析するかの2択になるのですが残念なことに前者は上手くいきませんでした。(多分私の環境の問題で広告モジュールが上手く動作しなかった)
そこで(いたしかたなく)コードを解析することに。(smaliのコードを読むのは初めてなので、金床さんのサイトを参考にさせていただきました。)

ここからは私の解析の流れをそのまま記録しておきます。
先に書いた通り、smaliのコードを読むのが初めて、というか静的解析自体がほぼ初めてなので後で何方かが再検証できるようにという目的です。

まず、この膨大な広告モジュールのどこから手をつけるかというところで、取得している情報から最もインパクトの大きいであろうJumpTapというモジュールに絞ります。
こやつは、LocationManagerと、TelephonyManagerから取得できるであろうあらゆる端末情報を収集している憎いやつです。

まず、前回の解析結果の続きから見てみます。
以下はLocatoinManagerのgrepの結果です。

./com/jumptap/adtag/utils/JtLocation$1.smali(66,91): invoke-static {}, Lcom/jumptap/adtag/utils/JtLocation;->access$100()Landroid/location/LocationManager;
./com/jumptap/adtag/utils/JtLocation$1.smali(70,48): invoke-virtual {v0, p0}, Landroid/location/LocationManager;->removeUpdates(Landroid/location/LocationListener;)V
./com/jumptap/adtag/utils/JtLocation.smali(11,23): .field private static locationManager:Landroid/location/LocationManager;
./com/jumptap/adtag/utils/JtLocation.smali(32,59): sput-object v0, Lcom/jumptap/adtag/utils/JtLocation;->locationManager:Landroid/location/LocationManager;
./com/jumptap/adtag/utils/JtLocation.smali(64,56): .method static synthetic access$100()Landroid/location/LocationManager;
./com/jumptap/adtag/utils/JtLocation.smali(69,59): sget-object v0, Lcom/jumptap/adtag/utils/JtLocation;->locationManager:Landroid/location/LocationManager;
./com/jumptap/adtag/utils/JtLocation.smali(128,59): sget-object v0, Lcom/jumptap/adtag/utils/JtLocation;->locationManager:Landroid/location/LocationManager;
./com/jumptap/adtag/utils/JtLocation.smali(168,38): check-cast v0, Landroid/location/LocationManager;
./com/jumptap/adtag/utils/JtLocation.smali(170,59): sput-object v0, Lcom/jumptap/adtag/utils/JtLocation;->locationManager:Landroid/location/LocationManager;
./com/jumptap/adtag/utils/JtLocation.smali(173,59): sget-object v0, Lcom/jumptap/adtag/utils/JtLocation;->locationManager:Landroid/location/LocationManager;
./com/jumptap/adtag/utils/JtLocation.smali(177,48): invoke-virtual {v0, v1}, Landroid/location/LocationManager;->getLastKnownLocation(Ljava/lang/String;)Landroid/location/Location;
./com/jumptap/adtag/utils/JtLocation.smali(193,59): sget-object v0, Lcom/jumptap/adtag/utils/JtLocation;->locationManager:Landroid/location/LocationManager;
./com/jumptap/adtag/utils/JtLocation.smali(203,56): invoke-virtual/range {v0 .. v5}, Landroid/location/LocationManager;->requestLocationUpdates(Ljava/lang/String;JFLandroid/location/LocationListener;)V

どうやらJtLocation.smaliでロケーションを取得しているようです。
とりあえずJtLocation.smaliを見てみるとありました。
.method private registerLocationListeners(Landroid/content/Context;)Vの中の56行目になります。

.line 56
sget-object v0, Lcom/jumptap/adtag/utils/JtLocation;->locationManager:Landroid/location/LocationManager;

const-string v1, "network"

invoke-virtual {v0, v1}, Landroid/location/LocationManager;->getLastKnownLocation(Ljava/lang/String;)Landroid/location/Location;

move-result-object v0

invoke-static {v0}, Lcom/jumptap/adtag/utils/JtLocation;->setCurrentLocation(Landroid/location/Location;)V

registerLocationListenersメソッドの中でandroid.location.LocationManager.getLastKnownLocationをCallし、その結果をsetCurrentLocationメソッドに投げています。
名前の通りsetCurrentLocationは現在地の情報を格納するためのsetアクセサです。
となるとgetアクセサもあるはず。

.method public static getCurrentLocation()Landroid/location/Location;
.registers 1

.prologue
.line 107
sget-object v0, Lcom/jumptap/adtag/utils/JtLocation;->currentLocation:Landroid/location/Location;

return-object v0
.end method

ありました。次にこのgetCurrentLocationが使われているメソッドを探してみましょう。
「getCurrentLocation」をgrepします。

./com/jumptap/adtag/utils/JtAdManager.smali(743,61): invoke-static {}, Lcom/jumptap/adtag/utils/JtLocation;->getCurrentLocation()Landroid/location/Location;
./com/jumptap/adtag/utils/JtLocation.smali(89,23): .method public static getCurrentLocation()Landroid/location/Location;

JtAdManager.smaliで使われています。
早速該当箇所を探してみます。

.method public static getLocation(Landroid/content/Context;)Ljava/lang/String;
.registers 6
.parameter "context"

.prologue
.line 95
invoke-static {p0}, Lcom/jumptap/adtag/utils/JtLocation;->init(Landroid/content/Context;)V

.line 96
const/4 v1, 0x0

.line 97
.local v1, retVal:Ljava/lang/String;
invoke-static {}, Lcom/jumptap/adtag/utils/JtLocation;->getCurrentLocation()Landroid/location/Location;

move-result-object v0

この後、位置情報はStringBuilderで「llatitude/longitude=xxx%2Cxxx」という形に形成されてCall元にReturnしていました。
さて、このメソッドをCallしてるのはどこでしょうか。「getLocation」でgrepした結果です。

./com/jumptap/adtag/callbacks\JtWebviewCb.smali(73,16): .method public getLocation(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
./com/jumptap/adtag/callbacks\JtWebviewCb.smali(90,36): const-string v3, "JtWebViewCB::getLocation options: "
./com/jumptap/adtag/utils/JtAdManager.smali(730,23): .method public static getLocation(Landroid/content/Context;)Ljava/lang/String;
./com/jumptap/adtag/utils/JtAdUrlBuilder.smali(191,64): invoke-static {v2}, Lcom/jumptap/adtag/utils/JtAdManager;->getLocation(Landroid/content/Context;)Ljava/lang/String;

最終行(4行目)に注目してください。「JtAdUrlBuilder.smali」つまり「JtAdUrlBuilder」クラスで利用されています。
クラス名から推測するとURLの生成クラスである可能性があります。核心に近づいてきました。
該当メソッドはかなり長いので該当部分だけ抜き出します。

.method public getAdUrl(Landroid/webkit/WebView;Ljava/lang/String;)Ljava/lang/String;


.line 40
iget-object v2, p0, Lcom/jumptap/adtag/utils/JtAdUrlBuilder;->context:Landroid/content/Context;

invoke-static {v2}, Lcom/jumptap/adtag/utils/JtAdManager;->getLocation(Landroid/content/Context;)Ljava/lang/String;

move-result-object v1

.line 41
.local v1, location:Ljava/lang/String;
if-eqz v1, :cond_9a

.line 42
const-string v2, "&ll="

invoke-virtual {v0, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

move-result-object v2

invoke-static {v1}, Lcom/jumptap/adtag/utils/JtAdUrlBuilder;->encodeParam(Ljava/lang/String;)Ljava/lang/String;

move-result-object v3

invoke-virtual {v2, v3}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

今まで取得してきたパラメータをStringBuilderで次々に連結させているようです。
位置情報は"&ll="というパラメータ名と結合しエンコードしています。

ここで、端末情報が含まれるか合わせて確認しましょう。

.line 51
const-string v2, "&hid="

invoke-virtual {v0, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

move-result-object v2

iget-object v3, p0, Lcom/jumptap/adtag/utils/JtAdUrlBuilder;->context:Landroid/content/Context;

invoke-static {v3}, Lcom/jumptap/adtag/utils/JtAdManager;->getHID(Landroid/content/Context;)Ljava/lang/String;

move-result-object v3

invoke-static {v3}, Lcom/jumptap/adtag/utils/JtAdUrlBuilder;->encodeParam(Ljava/lang/String;)Ljava/lang/String;

move-result-object v3

invoke-virtual {v2, v3}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

これのようですね。JtAdManager.smaliに該当のメソッドがありました。

.method public static getHID(Landroid/content/Context;)Ljava/lang/String;
.registers 3
.parameter "context"

.prologue
.line 79
invoke-static {p0}, Lcom/jumptap/adtag/utils/JtAdManager;->getTelephonyManager(Landroid/content/Context;)Landroid/telephony/TelephonyManager;

move-result-object v1

.line 81
.local v1, telephonyManager:Landroid/telephony/TelephonyManager;
const-string v0, ""

.line 82
.local v0, imei:Ljava/lang/String;
if-eqz v1, :cond_c

.line 83
invoke-virtual {v1}, Landroid/telephony/TelephonyManager;->getDeviceId()Ljava/lang/String;

move-result-object v0

.line 85
return-object v0
.end method

「Landroid/telephony/TelephonyManager;->getDeviceId()」からIMEIを取得している事がわかります。
つまり"&hid="はIMEIのパラメータが入っているということですね。
さぁ、こうやって一つ筒見ていくと、このUrlBuilderは以下の文字列を生成します。

&ua=(UserAgent)&pub=(publisherId)&spot=(SpotId)&site=(SiteId)&ll=(Location)&country=&pc=postalCode&mt-age=age&mt-gender=&mt-hhi=hhi&hid=(DeviceID)&a=(AdultContentType)&client-ip=(LocalIpAddress)&l=(Language)&c=3&version=(Version(広告モジュールのバージョン))&mt-speed=(ConnectionType)&mt-jtlib=(JtLibVer(広告モジュールのライブラリバージョン))&mt-bundle=(BundleVersion)&mt-os=(Os)&mt-osversion=(SDKVersion)&mt-model=(AndroidModel)&mt-make=(android.os.Build.Brand)&mt-fw=(KernelVersion)&mt-operator=(OperatorName)&mt-nradio=(NetworkType)&mt-dradio=(PhoneType)&mt-subno=(SubscriberId)&

…パラメータ名を見てドキッとしました。postalCodeとかAgeとかgenderとか…
固定値もしくはNullが入っているようですが、パラメータが多すぎて調べきれない…
いずれにしても広告モジュールの仕組みではこれらのパーソナルデータを送信する為のパラメータが用意されているということだけでもかなり凶悪です。
今日のところはこのくらいにしておきます。(小出しですいません)

あ、それから実機の通信データをキャプチャしていただける方緩募です。


※調査中ですが、パラメータの内容をメモ
&ua=(UserAgent)android.webkit.WebView.getSettings()
&pub=(publisherId)(JtAdWidgetSettings内のパラメータに依存。デフォルトNull)
&spot=(SpotId)(JtAdWidgetSettings内のパラメータに依存。デフォルトNull)
&site=(SiteId)(JtAdWidgetSettings内のパラメータに依存。デフォルトNull)
&ll=(Location)android.location.LocationManager.getLastKnownLocation()
&country=(Country)(JtAdWidgetSettings内のパラメータに依存。デフォルト"")
&pc=(postalCode)(JtAdWidgetSettings内のパラメータに依存。デフォルト"")
&mt-age=(age)(JtAdWidgetSettings内のパラメータに依存。デフォルト"")
&mt-gender=(gender)(JtAdWidgetSettings内のパラメータに依存。デフォルト"")
&mt-hhi=(hhi)(JtAdWidgetSettings内のパラメータに依存。デフォルト"")
&hid=(DeviceID)android.telephony.TelephonyManager.getDeviceId()
&a=(AdultContentType)(JtAdWidgetSettings内のパラメータに依存。デフォルト"notallowed")
&client-ip=(LocalIpAddress)java.net.NetworkInterface.getInetAddresses()
&l=(Language)(JtAdWidgetSettings内のパラメータに依存。デフォルト"en")
&c=3
&version=(Version(広告モジュールのバージョン))
&mt-speed=(ConnectionType)"WiFi"or"3G"
&mt-jtlib=(JtLibVer(広告モジュールのライブラリバージョン))
&mt-bundle=(BundleVersion)(JtAdWidgetSettings内のパラメータに依存。デフォルト"com.jumptap.adtag-android")
&mt-os=(Os)(JtAdWidgetSettings内のパラメータに依存。デフォルト"Linux")
&mt-osversion=(SDKVersion)android.os.Build$VERSION.RELEASE
&mt-model=(AndroidModel)android.os.Build.DEVICE
&mt-make=(Brand)android.os.Build.Brand()
&mt-fw=(KernelVersion)(/proc/versionから取得)
&mt-operator=(OperatorName)android.telephony.TelephonyManager.getNetworkOperatorName()
&mt-nradio=(NetworkType)android.telephony.TelephonyManager.getNetworkType()
&mt-dradio=(PhoneType)android.telephony.TelephonyManager.getPhoneType()
&mt-subno=(SubscriberId)android.telephony.TelephonyManager.getSubscriberId()

0 件のコメント:

コメントを投稿