Flare-On 7 Challenge Writeup
Flare-On Challenge(以下、Flare-On)は、FireEyeの分析チーム FLARE (The Front Line Applied Research & Expertise)が主催しているCTF (Capture The Flag)形式のセキュリティコンテストです。
毎年8月から9月頃に開催され今年で7回目となります。特徴としてはWindowsを中心としたバイナリ解析の問題にフォーカスしたコンテストで、問題で出されたファイルを解析してFlagを入手します。
例年11~12個の問題が出され、正解のFlagを入力して次の問題のファイルを入手・解析する流れとなっています。期間が6週間で、期間内に全問題を解くと賞品が進呈されるコンテストです。
今年は、世界中から5,000人以上が参加していました。
今回私も参加してみて、FLAREチームが実際の現場で観測・分析をした内容が問題に組み込まれているため、実践的な問題が多いという印象を受けました。
今回は、特に印象深かった2つの問題のWriteupを紹介したいと思います。これらの問題は、私だけでなく他参加者の間でも評価が高い問題でした。
注意点) 主催者側からも注意点として挙げられていますが、問題を解くのは仮想マシン上で行う必要があります。
問題として出されたファイルはシステムに影響を与えたり、セキュリティ製品により駆除される事があります。
Challenge 4
使用ツール) Excel、Python、oletools、pcodedmp、pcode2code
問題
Nobody likes analysing infected documents, but it pays the bills. Reverse this macro thrill-ride to discover how to get it to show you the key.
(意訳) ドキュメントファイルを分析するのを好きな人はいないと思いますが、その価値はあります。ワクワクするようなこのマクロを調べてキーを発見して下さい。
解析対象のファイルはxlsファイルで、問題文からもマクロを解析してFlagを入手する問題だろうと考え、ネットワーク隔離している仮想解析環境上のExcelでマクロを調べようとしました。
しかしマクロを有効にすると、「プロシージャの呼び出し、または引数が不正です。」という実行時エラーが発生します。
そこで、Philippe Lagadec氏が開発しているOffice解析ツールのデファクトスタンダードの一つといえるoletoolsに含まれているolevbaでファイルを調べてみます。
>python olevba.py report.xls
VBA Stompingが検出されており、VBAソースコードとP-codeが違うことを示しています。
ここで、P-codeについて少し整理します。
Office 97 - 2003まではファイル全体がCFBF(Compound File Binary Format)形式でしたが、Office 2007以降はZIP形式で圧縮されたOfficeファイルの中にある"vbaProject.bin"(このファイル名はデフォルトで変更可能)がCFBF形式で、マクロが含まれています。マクロは、下記3つの形態で保存されています。
- 圧縮されたソースコード
- P-Code : 中間コード 。 VBE(Visual Basic Editor) で1行毎にコンパイルされるコード。
- Execodes : P-Codeが実行された時に作られる。どのようなデータかは、ドキュメント化されておらずキャッシュ用途と考えられている。
マクロの実行に関係するコードは、ソースコードかP-Codeですが、ファイルを開くOfficeアプリケーションのバージョンがファイルを作成したものと同じか異なるかでマクロの実行動作が違ってきます。
Officeのバージョンが同じ |
P-codeが実行される。 またP-codeをデコンパイルしたソースがエディタに表示される。 |
Officeのバージョンが違う |
ソースコードがP-codeにコンパイルされて実行される。これにより違うバージョンのOfficeで開いた場合もマクロの実行が可能となっている。 |
同じバージョンで開くという条件を満たせば、ソースコードを削除してもP-codeによりマクロを実行する事が可能です。この仕様を悪用した検知回避テクニックが"VBA Stomping"と呼ばれています。
ファイルの中には、マクロのソースコードも残っておりolevbaで内容を見る事はできますが、特定の関数が削除されており実行できません。
これらの事から、この問題のポイントはP-codeを解析する事だと考えました。
そこで、P-codeの解析をする方法について調べた結果、ファイルからP-codeを抽出するDr. Vesselin Bontchev 氏のpcodedmpと、pcodedmpが作成したP-CodeをVBAソースに変換するZilio Nicolas氏のpcode2codeがある事がわかり、VBAコードを抽出する事ができました。(いずれもpython3で動作を確認しています)
> python pcodedmp.py -d report.xls -o p-code.txt
> python pcode2code.py -p pcode.txt -o pcode-macro.txt
一箇所VBAのソースコードへのデコンパイルが失敗した箇所があったので、そこは手動で修正しました。
' a generic exception occured at line 68: cannot concatenate 'str' and 'NoneType' objects
' # Ld fn
' # Sharp
' # LitDefault
' # Ld wabbit
' # PutRec
==> Put #fn, , wabbit
新しくxlsファイルを作成し、抽出したマクロのソースコードを追加します。後、問題のxlsファイルにはフォームも含まれておりそのフォームに難読化されたデータがあるので、同じく追加します。
新しいxlsファイルのVBA上でエラーを修正して、デバッグをしていくと以下処理がある事が分かりました。
- 解析環境でないかのチェック
- ネットワークに接続していない場合は、エラーダイアログを表示して終了
- 稼働しているプロセス名をWMIで確認し、"vmv"、 "vmt"、"vbox"が含まれていた場合は、エラーダイアログを表示して終了
- ユーザドメイン名が、"FLARE-ON"でない場合は、エラーダイアログを表示して終了。このユーザ名がマルチバイトXORの鍵となっており、フォームのテキストボックスに設定されているデータの復号に使われます。
上記チェックをコード編集して無効にし、ユーザドメイン名を格納する変数に固定で"FLARE-ON"にすると、フォームテキストボックスの暗号化データが復号されて、%appdata%¥Microsoft¥v.png に保存し、シート上に表示されました。
修正したVBA ソースコード
Private Declare Function InternetGetConnectedState Lib "wininet.dll" (ByRef dwflags As Long, ByVal dwReserved As Long) As Long
Private Declare Function mciSendString Lib "winmm.dll" Alias "mciSendStringA" (ByVal lpstrCommand As String, ByVal lpstrReturnString As String, ByVal uReturnLength As Long, ByVal hwndCallback As Long) As Long
Private Declare Function GetShortPathName Lib "kernel32" (ByVal lpszLongPath As String, ByVal lpszShortPath As String, ByVal lBuffer As Long) As Long
Public Function GetInternetConnectedState() As Boolean
GetInternetConnectedState = InternetGetConnectedState(0, 0)
End Function
Function rigmarole(es As Variant) As String
Dim furphy As String
Dim c As Integer
Dim s As String
Dim cc As Integer
furphy = ""
For i = 1 To Len(es) Step 4
c = CDec("&H" & Mid(es, i, 2))
s = CDec("&H" & Mid(es, i + 2, 2))
cc = c - s
furphy = furphy + Chr(cc)
Next i
rigmarole = furphy
End Function
Function folderol()
Dim wabbit() As Byte
Dim fn As Integer: fn = FreeFile
Dim onzo As Variant
Dim mf As String
Dim xertz As Variant
Dim buff(0 To 7) As Byte
onzo = Split(F.L, ".")
' ネットワーク接続状態チェック
If GetInternetConnectedState = False Then
MsgBox "Cannot establish Internet connection.", vbCritical, "Error"
End
End If
Set fudgel = GetObject(rigmarole(onzo(7)))
Set twattling = fudgel.ExecQuery(rigmarole(onzo(8)), , 48)
' 仮想環境関連のプロセスチェック
For Each p In twattling
Dim pos As Integer
pos = InStr(LCase(p.Name), "vmw") + InStr(LCase(p.Name), "vmt") + InStr(LCase(p.Name), rigmarole(onzo(9)))
If pos > 0 Then
MsgBox rigmarole(onzo(4)), vbCritical, rigmarole(onzo(6))
End
End If
Next
xertz = Array(&H11, &H22, &H33, &H44, &H55, &H66, &H77, &H88, &H99, &HAA, &HBB, &HCC, &HDD, &HEE)
' ユーザードメイン名チェック
Set groke = CreateObject(rigmarole(onzo(10)))
firkin = groke.UserDomain
If firkin <> rigmarole(onzo(3)) Then
MsgBox rigmarole(onzo(4)), vbCritical, rigmarole(onzo(6))
End
End If
n = Len(firkin)
For i = 1 To n
buff(n - i) = Asc(Mid$(firkin, i, 1))
Next
wabbit = canoodle(F.T.Text, 2, 285729, buff)
mf = Environ(rigmarole(onzo(0))) & rigmarole(onzo(11))
Open mf For Binary Lock Read Write As #fn
Put #fn, , wabbit
Close #fn
Set panuding = Sheet1.Shapes.AddPicture(mf, False, True, 12, 22, 600, 310)
End Function
Function canoodle(panjandrum As String, ardylo As Integer, s As Long, bibble As Variant) As Byte()
Dim quean As Long
Dim cattywampus As Long
Dim kerfuffle() As Byte
Dim length As Long
length = Len(panjandrum)
ReDim kerfuffle(length)
quean = 0
For cattywampus = 1 To Len(panjandrum) Step 4
kerfuffle(quean) = CByte("&H" & Mid(panjandrum, cattywampus + ardylo, 2)) Xor bibble(quean Mod (UBound(bibble) + 1))
quean = quean + 1
If quean = UBound(kerfuffle) Then
Exit For
End If
Next cattywampus
canoodle = kerfuffle
End Function
復号データはpngで、その画像にFlagが描かれていました。
Challenge 7
使用ツール) Wireshark、IDA (代替候補 Ghidra, x64dbg)、Python、010 Editor、Visual Studio、CEF Explorer 、Netcat
問題
Hello,
Here at Reynholm Industries we pride ourselves on everything. It's not easy to admit, but recently one of our most valuable servers was breached. We don't believe in host monitoring so all we have is a network packet capture. We need you to investigate and determine what data was extracted from the server, if any.
(意訳) Reynholm Industriesの者です。認めたくありませんが我々の重要サーバの一台が侵害されました。ホスト内のデータは信頼ができず、提供できるのはネットワーク通信のパケットキャプチャだけです。それをもとに窃取されたデータがあるかを調べて下さい。
解析対象ファイルはpcapファイルで、問題文から架空の企業 "Reynholm Industries"で1台のサーバが侵害された為、このpcapを調査し盗まれたデータを特定するというシナリオです。
最初に通信データの全体的な流れを把握するために、Wiresharkのメニュー [統計] - [対話] を選択し、"IPV4"の画面を確認します。
IPアドレスは、192.168.0.1、192.168.68.21、192.168.68.1の3つがあるのが分かります。
192.168.0.1 と 192.168.68.21 の間では2パケットしかやりとりがないので、まずこの通信を調べます。
192.168.68.21 から192.168.0.1にDNS クエリーを投げていますが、応答は返ってきていません。
次に、192.168.68.21と192.168.68.1の通信ですが、192.168.68.1から192.168.68.21への通信量が多いのが目につきます。
これらのIPアドレスを条件にしてフィルタリングします。
192.168.68.1に対してもDNSクエリーを投げて"it-dept.reynholm-industries.com"のIPアドレス(192.168.68.1)を取得しています。この事から192.168.68.1は、DNSサーバでホスト名が"it-dept.reynholm-industries.com"である事が分かります。
このサーバーは、HTTPレスポンスも返しており、レスポンスデータから、IIS 6.0でAPS.NETが稼働している事も分かります。(かなり古いバージョンですね。。)
何か手掛かりがないか、メニューから [ファイル] - [オブジェクトをエクスポート] - [HTTP] を選択し、Webサーバからダウンロードされたコンテンツを確認してみます。
どうもIT部門の掲示板のようです。
会話を見ていくとIT部門のMossは、サーバが非常に古いバージョンなので最新のセキュリティパッチを適用した方が良いと意見しているのですが、上司と思われるDenholmは聞く耳を持っていないようです。。
この会話から何かしらの脆弱性が存在するサーバであろうと推測します。
ここまでで、推測を含め分かったネットワーク構成やサーバの情報についてまとめてみます。
次にHTTP通信にフォーカスして見ると、PROPFINDメソッドでエクスプロイトコードらしきものが送られているのが見つかりました。
"IIS 6.0 PROPFIND"で検索をすると、遠隔からコード実行が可能な脆弱性CVE-2017-7269が存在する事が分かりました。
もう少し調べてみると、Fortinetの解析記事が見つかり、そこに書かれている内容と一致する事からもCVE-2017-7269が悪用されたと推測します。
記事の方には、デコーダのpythonスクリプトも掲載されており、それを元に復号しました。
パケット No.127のデータを選択し右クリックで[パケットバイト列をエクスポート]を選択し、Raw形式で保存します。
保存したデータからペイロードの前データを削除し、スクリプトで復号しました。
復号スクリプト (Python2で動作確認)
import sys
import os
import string
argvs = sys.argv
if len(sys.argv) != 2:
print("Usage: set param1: encode file path")
sys.exit(1)
enc_file = argvs[1]
dec_file = enc_file + ".decode"
decoded_bytes = ""
enc_fb = open(enc_file, "rb")
encoded_bytes = enc_fb.read()
enc_fb.close()
l = len(encoded_bytes) / 2
decoded_bytes = str()
for i in range(l):
bloc k= encoded_bytes[i*2:i*2+2]
decoded_byte_low = ord(block[1]) & 0x0F
decoded_byte_high = ((ord(block[1]) >> 4) + (ord(block[0]) & 0x0F)) & 0x0F
decoded_byte=chr(decoded_byte_low + (decoded_byte_high <<4))
decoded_bytes+=decoded_byte
printable_decoded_bytes = ''.join(c for c in decoded_bytes if c in string.printable)
#ASCII display
print printable_decoded_bytes
#hexadecimal display
b = bytearray(decoded_bytes)
print ''.join('{:02x}'.format(x) for x in b).upper()
with open(dec_file, "wb") as dec_fb:
dec_fb.write(b)
復号したデータは、010 Editorで[File] - [Export Hex..] でC言語の配列形式で保存します。
その配列にあるデータを実行する簡単なプログラムを書きます。
その後に、Visual Studioに付属しているclコマンドでexeファイルを作成し、デバッガーで解析をします。
例) cl ch7-shell1.c
※個人の好みですが、シェルコードをEXEに変換するツールは色々とありますが、今回は簡易的にこのようなやり方で進めました。
これにより復号したコードをデバッガで解析できるようになります。
注意点としてコードはdataセクションに含まれるため、コードが実行できるようにdataセクションをCEF Explorerを使い実行可能領域に変更します。
デバッグ時には、復号したコードの先頭部は処理の意味がなくアクセスバイオレーションが発生するので最初の命令で次のcall 命令に移動するようにします。IDA Debuggerの場合は、移動先の命令にカーソルをセットし、右クリック->「Set IP」もしくはCTRL+Nで移動できます。
デバッグ解析の結果、このコードは、192.168.68.21のポート4444にリバースコネクトするシェルコードである事が分かりました。
ポート4444でフィルターし、パケットサイズの大きさからパケット No.290のデータがWebサーバで実行されたコードであると考えられます。その後のパケット No.294のデータが、攻撃者の機器に送られたコードの実行結果ではないかと推測します。
No.290で送り込まれた実行コードを調べるために、その直前に送り込まれたNo.284のペイロードを、上記No.127のペイロードをデバッグした際と同じ手順でexeを作成します。デバッグするホストマシンのIPアドレスは、192.168.68.1に設定しておきます。
そして、同じ仮想ネットワークに属する192.168.68.21のIPアドレスを割り当てたマシンをセットアップします。
そのマシンで、Netcatを使い他機器からポート4444へTCP接続された時にNo.290のデータを送り込むようにした状態にして 、作成したexeをデバッグします。(以下rc.binは、No.290のデータ)
> nc -l -p 4444 < ./rc.bin
デバッグした結果、送り込まれたコードはC:¥accounts.txtを読み込み、送信している事が分かりました。
IT部門の掲示板の会話を改めて見てみると、accounts.txtは、従業員のユーザ名とパスワードが書かれたファイルでした。
そのデータを、鍵 "intrepidmango" でRC4暗号化をしていました。
RC4暗号処理
そこで、No.294のデータをRC4で復号します。
復号スクリプト(Python2で動作確認)
import sys
import pefile
import re
import argparse
from struct import unpack, unpack_from
def rc4(data, key):
x = 0
box = range(256)
for i in range(256):
x = (x + box[i] + ord(key[i % len(key)])) % 256
box[i], box[x] = box[x], box[i]
x = 0
y = 0
out = []
for char in data:
x = (x + 1) % 256
y = (y + box[x]) % 256
box[x], box[y] = box[y], box[x]
out.append(chr(ord(char) ^ box[(box[x] + box[y]) % 256]))
return ''.join(out)
def main():
argvs = sys.argv
argc = len(argvs)
if (argc !=2):
print "python ./rc4-decrypt.py file"
quit()
enc_data = open(argvs[1], 'rb').read()
rc4key = "\x69\x6E\x74\x72\x65\x70\x69\x64\x6D\x61\x6E\x67\x6F"
dec_data = rc4(enc_data, rc4key)
with open('rc4_decrypted', 'wb') as dec:
dec.write(dec_data)
if __name__ == "__main__":
main()
結果、ユーザとパスワードの一覧を確認できFlagを入手する事ができました。
最後に
今回2つの問題のWriteupを紹介しましたが、かなり実践的な内容ではないでしょうか。
私個人もFlare-Onを通して、知らなかったテクニックを知る事や解析ツールのブラッシュアップをする事ができました。
今回解説したChallenge4とChallenge7を含め全問題とWriteupが既に公開されているので、まだチャレンジをした事がなく興味を持たれた方は、是非そちらも見てみて下さい。
Flare-On 7 Challenge Solutions
例年7月に開催している弊社セミナー " Macnica Networks Day"でも、MNCTFと題したCTFを実施しているのですが、今年はオンラインだった事もあり開催しませんでした。
来年のセミナーでは、あの男が再び戻ってくる予定ですのでご期待下さい。
フォローしませんか?