NFLabs. エンジニアブログ

セキュリティやソフトウェア開発に関する情報を発信する技術者向けのブログです。

Hack The Box Business CTF 2022 - Rouge (Forensics) Writeup (暗号化されたSMBトラフィックの復号)

この記事は、NFLaboratories Advent Calendar 2022 1日目の記事です。

こんにちは、研究開発部の保要 (@takahoyo) です。

弊社の公式Twitterでもアナウンスがあったとおり、7月に弊社のエンジニア14名でHack The Box 主催の Hack The Box Business CTF 2022 (以下、Business CTF)に出場していました。

今回は私がこの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.pmd3858793632.zip というファイル名でZIP圧縮
  • FTPで 3858793632.zipwindowsliveupdater[.]com にアップロード

lsassは、 Local Security Authority Subsystem Service の略で、Windowsの認証を司るプロセスです。 このプロセスのメモリ空間にはユーザの認証情報がキャッシュされており、管理者権限を持つユーザであればこのプロセスのメモリにアクセスしてユーザの認証情報の窃取が可能です。 この手法は、MITRE ATT&CKでは T1003.001 に該当します。

attack.mitre.org

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-project.github.io

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で実装することを目指したプロジェクトです。

github.com

以下のページを参考に、pypykatzでlsassメモリダンプのファイルを読み込んで、mimikatzの sekurlsa::logonpasswords を実行します。

github.com

pypykatz lsa minidump 3858793632.pmd

実行すると、mimikatzのようにメモリ上にキャッシュされているユーザのNTLMハッシュを抽出できます。

SMBトラフィックの解析

pcapファイル後半を見ていくと、fe80::7590:b535:f33:bac9fe80::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記事を見つけました。

medium.com

この記事によると、SMBの "Session Id" とそれに対応する "Session Key" を、Wiresharkの設定として入れることでSMBv3で暗号化されたトラフィックを復号できそうです。*5

"Session Id" はSMBのセッションを識別する値で、各パケットに含まれています。Wireshrakで確認でき、今回の場合 0x0000a00000000015です。

"Session Key" は、SMBのデータを暗号化するためのセッションキーです。これはSMBのクライアントがサーバに対して送る Session Setup Request のパケットに暗号化された状態で含まれているため、このままでは使えません。

暗号化されたセッションキーの復号

SMBのセッションキーの暗号化方法については、以下のForumページの回答が載っていました。

social.msdn.microsoft.com

このページの内容を図にまとめると、セッションキーの暗号化は以下のように行われています。赤字が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