この記事は、NFLaboratories Advent Calendar 2022 1日目の記事です。
こんにちは、研究開発部の保要 (@takahoyo) です。
弊社の公式Twitterでもアナウンスがあったとおり、7月に弊社のエンジニア14名でHack The Box 主催の Hack The Box Business CTF 2022 (以下、Business CTF)に出場していました。
#NFLabs エンジニア14名で企業対抗の #HackTheBox #BusinessCTF22 に参加し10位(日本企業1位)でした!
— 株式会社エヌ・エフ・ラボラトリーズ (@NFLaboratories) 2022年7月19日
(参加チーム数 657、プレイヤー数 2,979)
メンバーが協力して 24/37 の Flag を取得しました。
Fullpwn 3/8,
Web 4/5,
Pwn 2/5,
Crypto 5/6,
Rev 4/5,
Forensics 4/5,
Hardware 1/1,
Cloud 1/2 pic.twitter.com/cN6Yu4AoLZ
今回は私がこのCTFで解いていたForensicsジャンルの問題の中から、勉強になったと感じた Rouge を紹介します。*1
この問題の主なテーマは「暗号化されたSMBトラフィックの復号」ですが、そこに至るまでのパケット解析の内容も書いているので、少々長くなります。
問題の概要
Rougeは、今回のBusiness CTFのForensicsジャンルの中で2番目に回答チームが少ない問題(53solves / 657teams)で、運営がつけた難易度はMediumでした。
問題文は、以下のとおりです。
SecCorp has reached us about a recent cyber security incident. They are confident that a malicious entity has managed to access a shared folder that stores confidential files. Our threat intel informed us about an active dark web forum where disgruntled employees offer to give access to their employer's internal network for a financial reward. In this forum, one of SecCorp's employees offers to provide access to a low-privileged domain-joined user for 10K in cryptocurrency. Your task is to find out how they managed to gain access to the folder and what corporate secrets did they steal.
これをGoogle翻訳で日本語に訳すると、以下のようになります。
SecCorpは、最近のサイバーセキュリティインシデントについて私たちに連絡しました。 彼らは、悪意のあるエンティティが機密ファイルを保存する共有フォルダにアクセスしたことを確信しています。 私たちの脅威インテリジェンスは、不満を持った従業員が金銭的報酬を得るために雇用主の内部ネットワークへのアクセスを提供することを提案するアクティブなダークウェブフォーラムについて私たちに知らせました。 このフォーラムでは、SecCorpの従業員の1人が、暗号通貨で10Kの低特権ドメイン参加ユーザーへのアクセスを提供することを提案しています。 あなたの仕事は、彼らがどのようにしてフォルダにアクセスできたのか、そして彼らがどのような企業秘密を盗んだのかを知ることです。
Business CTFの問題はこのように問題文に具体的なシナリオが書かれているのが特徴で、問題を解く上でのヒントになります。 どうやら共有フォルダからどのような機密情報が盗まれたかを明らかにして欲しいという問題のようです。
この問題では問題文の他に、ZIPファイルが配布されています。 このZIPファイルの中身は、capture.pcapng という25Mbytes程度のpcapファイルです。
また、運営から「最新のWiresharkを使って解いて」という旨のアナウンスがあったので、今回は競技時点で最新のWiresharkの安定版バージョン(v3.6.6)で問題を解きました。
問題の解法
記録されているパケットの確認
pcapファイルに記録されているパケットをWiresharkで見ていきます。
画面右下のPacketsを見ると、18,106個のパケットが記録されていることがわかります。
記録されているパケット数が少し多いので、Wiresharkの統計機能を使って全体像を把握しても良いのですが、最初のパケットを見ると 77.74.198[.]52:4444
という、あまり使われないポート番号*2と通信してるパケットが見えます。
今回はこの通信を起点に解析を開始しました。
リバースシェルトラフィックの解析
77.74.198[.]52:4444
の通信をFollow TCP Streamで見てみます。
どうやら攻撃者が、リバースシェルでWindowsのコマンドを実行している通信のようです。 この通信からは以下のことが行われているのがわかります。
- 攻撃者はWS02を侵害しており、WS02のAdministratorsグループに所属している(=管理者権限を持つ)
WS02¥rpaker
のシェルを取得 - lsassのメモリダンプをComsvcs.dllで取得し、
3858793632.pmd
というファイルに保存 3858793632.pmd
を3858793632.zip
というファイル名でZIP圧縮- FTPで
3858793632.zip
をwindowsliveupdater[.]com
にアップロード
lsassは、 Local Security Authority Subsystem Service の略で、Windowsの認証を司るプロセスです。 このプロセスのメモリ空間にはユーザの認証情報がキャッシュされており、管理者権限を持つユーザであればこのプロセスのメモリにアクセスしてユーザの認証情報の窃取が可能です。 この手法は、MITRE ATT&CKでは T1003.001 に該当します。
lsassプロセスのメモリから認証情報を窃取する方法は、mimikatz の sekurlsa::logonpasswords
を使った方法が有名ですが、有名が故にmimikatzは多くのアンチウイルス製品に検知されやすいです。
そのため、現実の攻撃者もWindowsに標準でインストールされているプログラム Comsvcs.dll で lsassメモリダンプを行うことがあります。
このようにOSに標準でインストールされているプログラムやスクリプトを使った攻撃は、LOLBAS (Living Off The Land Binaries and Scripts) と呼ばれます。 LOLBASを使った攻撃は https://lolbas-project.github.io にまとまっており、Comsvcs.dllを使った手法も掲載されています。
LOLBASのプログラムは通常の用途でも使用されるため、このような攻撃のアンチウイルス製品での検知は難しいと思います。
FTPで転送されたファイルの抽出
lsassのメモリダンプがFTPで外部に送られていることがわかったので、FTP通信のパケットが記録されていないか見てみましょう。
パケットを見ていると、windowsliveupdater[.]com
を名前解決するDNS通信とFTPの通信が確認できます。
DNSの名前解決の結果、 windowsliveupdater[.]com
のIPアドレスが 77.74.198[.]52
であるとわかるので、その直後のFTP通信も攻撃者のコマンド実行によるものであるとわかります。
このFTP通信を Follow TCP Stream で見てみましょう。
3858793632.zip
を FTPのPassive modeでアップロードしているのがわかります。
Passive modeはクライアントからサーバに接続要求を行ってデータ転送を行うモードです。
データ転送はサーバから指定されたランダムなポート番号を使って行われ、クライアントから PASV
コマンドを実行した際にサーバから指定されます。
今回の場合、 (77,74,198,52,226,112)
が転送でサーバが使うように指定したIPアドレスとポート番号の情報です。
使われるポート番号は 5個目の数字*256+6個目の数字
で計算できるので、 226*256+112=57968
が使われるポート番号になります。
よって、Wiresharkで ip.addr == 77.74.198.52 and tcp.port == 57968
のようにフィルタすることで、データ転送に使っているTCPストリームが抽出できます。*3
このTCPストリームは単純にファイルのバイナリデータを転送しているだけなので、Follow TCP Streamすると転送しているファイルのバイナリデータを表示できます。*4
転送されているデータのファイルへの保存は、Show data as を Raw に変更して、Save as をクリックします。
ファイルに保存すると、3858793632.pmd
が含まれるZIPファイルになりました。
lsassメモリダンプの解析
3858793632.pmd が lsassプロセスのメモリダンプであることがわかっているので、このメモリダンプファイルを解析していきます。
lsassプロセスのメモリダンプの解析には、Kali Linuxにデフォルトでインストールされている pypykatz を使います。 pypykatzは、mimikatzをPythonで実装することを目指したプロジェクトです。
以下のページを参考に、pypykatzでlsassメモリダンプのファイルを読み込んで、mimikatzの sekurlsa::logonpasswords
を実行します。
pypykatz lsa minidump 3858793632.pmd
実行すると、mimikatzのようにメモリ上にキャッシュされているユーザのNTLMハッシュを抽出できます。
SMBトラフィックの解析
pcapファイル後半を見ていくと、fe80::7590:b535:f33:bac9
と fe80::6d97:d06c:1bac:cf89
の間でSMB通信が行われています。
fe80::/64
はIPv6のリンクローカルアドレスで、近隣のホストへの通信の際に用いられることがあります。
また、fe80::7590:b535:f33:bac9
のMACアドレスは 08:00:27:9d:df:49
で、これは先程のFTP通信で送信元になっていたホストのMACアドレスと同じでした。
つまり、攻撃者が侵害したホストから近隣のホストに対してアクセスを試みたと考えられます。
このSMB通信を詳細に見ていくと、侵害されたホスト fe80::7590:b535:f33:bac9
から fe80::6d97:d06c:1bac:cf89
(ホスト名: CORP-DC)に対して、 ユーザ CORP¥athomson
を使ってログインしている通信がありました。
このログインは成功しており、さらにその後 ConfidentialShare
というファイル共有にアクセスしている形跡が確認できます。
それでは、どのようなファイルがやり取りされたのでしょう? 続きのパケットを確認してみると、SMBトラフィックはSMBv3の機能により暗号化されており、やり取りされたファイルの中身が見れません。。
SMBトラフィックの復号
ここでようやく本題です。SMBv3の機能によって暗号化されたSMBトラフィックは復号できるのでしょうか。
SMBv3は、ファイル共有などに用いられるSMB (Server Message Block) プロトコルのバージョン3です。 SMBv3は、Windows 8.1 および Windows Server 2012から搭載されており、SMBv2にはなかった暗号化機能がサポートされており、ファイル共有で転送されるデータも暗号化されます。
SMBv3で暗号化されたトラフィックを復号方法を探していたところ、過去に他のCTFで出題された問題のWriteup記事を見つけました。
この記事によると、SMBの "Session Id" とそれに対応する "Session Key" を、Wiresharkの設定として入れることでSMBv3で暗号化されたトラフィックを復号できそうです。*5
"Session Id" はSMBのセッションを識別する値で、各パケットに含まれています。Wireshrakで確認でき、今回の場合 0x0000a00000000015
です。
"Session Key" は、SMBのデータを暗号化するためのセッションキーです。これはSMBのクライアントがサーバに対して送る Session Setup Request のパケットに暗号化された状態で含まれているため、このままでは使えません。
暗号化されたセッションキーの復号
SMBのセッションキーの暗号化方法については、以下のForumページの回答が載っていました。
このページの内容を図にまとめると、セッションキーの暗号化は以下のように行われています。赤字がSession Keyを求めるのに必要な情報です。
トラフィックの復号に使う元のセッションキーは、Key Exchange Key (NTLMv2 session base key) を用いてRC4で暗号化されます。RC4は可逆暗号なので、暗号鍵である Key Exchange Key がわかれば暗号化されたセッションキーから元のセッションキーを復号できます。
Key Exchange Keyは、 NTProofStr
をメッセージ、 ResponseKeyNT
をキーとしてHMAC_MD5で計算したものです。
NTProofStr
は、以下のように "Session Setup Request" のパケット内に含まれているため、これを使用します。
今回はこの値を計算しないため詳細な算出方法については省略しますが、クライアントおよびサーバで認証の度にランダムで生成されるチャレンジメッセージや現在時刻など、一時的な情報を元に算出されています。*6
ResponseKeyNT
は、認証を行うユーザ名とドメイン名を大文字にして結合した文字列(今回であれば ATHOMSONCORP
)をメッセージ、ユーザのパスワードのMD4ハッシュ(NTLMハッシュ)をキーとして、HMAC_MD5で計算したものです。
認証したユーザ、ドメインは "Session Setup Request"のパケット内に含まれます。 しかし、ユーザのパスワードは、パスワードがそのままネットワークに流れないチャレンジレスポンス形式のNTLM認証を使用しているため、パケット内には含まれません。
パスワードあるいはNTLMハッシュがないと元のセッションキーを計算できないのですが、今回の問題では 外部に持ち出された lsassメモリダンプ がパケットに残っており、それを解析することでユーザのNTLMハッシュを入手できます。
先程のlsassメモリダンプをpypykatzで解析した結果を確認すると、ユーザ athomson
のNTLMハッシュも残っていたのでこの値を使用します。
これで元のセッションキーを計算するのに必要な情報が揃ったので、元のセッションキーを復号するスクリプトを用意します。
スクリプトは前述のCTFのWriteupの筆者が作成した以下のスクリプトを改良して使いました。
Random Session Key calculator based off of data from a packet capture · GitHub
元のスクリプトではパスワードを引数で指定していましたが、改良版ではNTLMハッシュを引数に指定して実行できるようにしています。
import hashlib import hmac import argparse #stolen from impacket. Thank you all for your wonderful contributions to the community try: from Cryptodome.Cipher import ARC4 from Cryptodome.Cipher import DES from Cryptodome.Hash import MD4 except Exception: LOG.critical("Warning: You don't have any crypto installed. You need pycryptodomex") LOG.critical("See https://pypi.org/project/pycryptodomex/") def generateEncryptedSessionKey(keyExchangeKey, exportedSessionKey): cipher = ARC4.new(keyExchangeKey) cipher_encrypt = cipher.encrypt sessionKey = cipher_encrypt(exportedSessionKey) return sessionKey ### parser = argparse.ArgumentParser(description="Calculate the Random Session Key based on data from a PCAP (maybe).") parser.add_argument("-u","--user",required=True,help="User name") parser.add_argument("-d","--domain",required=True, help="Domain name") #parser.add_argument("-p","--password",required=True,help="Password of User") parser.add_argument("-H","--hash",required=True,help="NTLM hash of User") parser.add_argument("-n","--ntproofstr",required=True,help="NTProofStr. This can be found in PCAP (provide Hex Stream)") parser.add_argument("-k","--key",required=True,help="Encrypted Session Key. This can be found in PCAP (provide Hex Stream)") parser.add_argument("-v", "--verbose", action="store_true", help="increase output verbosity") args = parser.parse_args() #Upper Case User and Domain user = str(args.user).upper().encode('utf-16le') domain = str(args.domain).upper().encode('utf-16le') #Create 'NTLM' Hash of password #passw = args.password.encode('utf-16le') #hash1 = hashlib.new('md4', passw) #password = hash1.digest() password = args.hash.decode('hex') #Calculate the ResponseNTKey h = hmac.new(password, digestmod=hashlib.md5) h.update(user+domain) respNTKey = h.digest() #Use NTProofSTR and ResponseNTKey to calculate Key Excahnge Key NTproofStr = args.ntproofstr.decode('hex') h = hmac.new(respNTKey, digestmod=hashlib.md5) h.update(NTproofStr) KeyExchKey = h.digest() #Calculate the Random Session Key by decrypting Encrypted Session Key with Key Exchange Key via RC4 RsessKey = generateEncryptedSessionKey(KeyExchKey,args.key.decode('hex')) if args.verbose: print "USER WORK: " + user + "" + domain print "PASS HASH: " + password.encode('hex') print "RESP NT: " + respNTKey.encode('hex') print "NT PROOF: " + NTproofStr.encode('hex') print "KeyExKey: " + KeyExchKey.encode('hex') print "Random SK: " + RsessKey.encode('hex')
このスクリプトを実行することで、元のセッションキー 9ae0af5c19ba0de2ddbe70881d4263ac
を入手できました。
python2 generate_session_key.py -v -d CORP -u athomson -H 88d84bad705f61fcdea0d771301c3a7d -n d047ccdffaeafb22f222e15e719a34d4 -k 032c9ca4f6908be613b240062936e2d2
WiresharkでのSMBv3パケットの復号
Wireshark の "Preference" -> "Protocols" -> "SMB2" のメニューを見ると、"Secret session keys for decryption" という項目があります。
ここに先程取得した "Session ID" と "Session Key" を入力します。
"Session ID" は、Wiresharkに表示されているリトルエンディアンで0xを付けた表記 ( 0x0000a00000000015
)ではなく、ビッグエンディアンで0xを取った表記(1500000000a00000
)で入力してください。
ここで正しい値を入力できていれば、以下のように "Encrypted SMB3 data" の部分が復号されて通信内容が読めるようになります。
復号されたSMBトラフィックの解析
復号されたSMBトラフィックを見ていくと、customer_information.pdf
をファイルサーバから読むリクエストを送っているのがわかります。
Wiresharkでは、"File" -> "Export Objects" -> "SMB" を選択することで、SMBで転送されているファイルを抽出できます。この機能により、customer_information.pdf
をファイルとして保存します。
保存したファイルの中身を見ると機密と思われる情報が記載されており、このファイルの中にFLAGが書かれていました。
おわりに
この記事では、CTFで出題された問題のWriteupをベースに、暗号化されたSMBトラフィックの復号方法について解説しました。
しかしながら、現実にこのようにSMBトラフィックを復号して、どのような情報が持ち出されたか調査する状況に出くわすことがあるか考えると、以下の理由により稀だと思います。
- フルパケットキャプチャは大量のストレージを消費するので取得している組織は少ない
- 取得していてもインターネットの出入口くらいで、組織内NWはトラフィック量が多いのでパケットキャプチャしている組織は少ない
- どのデータにアクセスがあったかは、Windowsのイベントログの取得で十分な情報が得られる *7
今回のシナリオはやや現実離れしており、あまり今回得たスキルを使うことはないかもしれませんが、それでも問題を解くことでSMBプロトコルを深く知る機会にはなりました。 今後、SMBの暗号化機能を使う時は、暗号化された時にどのようなトラフィックが流れるのか、またそれが条件によっては復号できることを意識して使用しようと思います。
*1:ちなみに、この問題は現在、Hack The Box の Challenges の Retired の問題として登録されているので、Retiredにも挑戦できるライセンスがある方でネタバレされたくない方はご注意ください。 https://app.hackthebox.com/challenges/rogue
*2:見慣れてる人なら、これがよくリバースシェルで使われるポート番号とわかると思いますが…
*3: ftp-data とwiresharkでフィルタしてもデータ転送に使ってるTCPストリームの抽出が可能です
*4:このTCPストリームで転送されているデータ量が多いので、全部のデータがロードされるまで少し待つ必要がありました。
*5:SMBv3トラフィックの復号機能は SAMBA Wiki によると、Wireshark v2.5.0から段階的に追加されています。そのため、運営から最新のWiresharkで問題を解くようにアナウンスがあったと思われます。wiki.samba.org
*6:詳細はMicrosoft のNTLMv2の認証に関するドキュメントを参照。 learn.microsoft.com
*7: 共有ファイルの監査ログの取得方法については、以前別のブログ記事にも書いていました blog.nflabs.jp