NFLabs. エンジニアブログ

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

NFLabs. Cybersecurity Challenge for Students 2023 DFIR hard 作問者 Writeup

この記事は NFLaboratories Advent Calendar 2023 14 日目の記事です。

はじめに

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

弊社では、今年初めて学生限定のセキュリティコンテスト「NFLabs. Cybersecurity Challenge for Students 2023」を開催しました。

connpass.com

コンテストはCTF形式でしたが、通常のCTFとは異なり実務に近い分野の問題の出題を目指して、OSINT, DFIR, Web, Malware, Dev, Pentest の6ジャンルで問題を出題しました。 また、出題される問題の難易度も難しいものばかりでなく easy, medium, hard の3段階で出題しました。

このうち、私はDFIRの作問、レビューを担当しました。 DFIRというと、ディスクやメモリの解析などを想像される方も多いと思いますが、今回はログ解析にフォーカスしており、ネットワークトラフィックログやLinuxやWindows、アプリケーションのイベントログの解析に挑戦してもらいました。 通常のCTFでは、ログ解析の問題はほとんど出題されないため*1、アンケートを読んでいると、そのような問題が出題されて良かったという参加者の意見が多かったのが印象的でした。

この記事では、私が作成したDFIRのhard問題 である「developer」の解法を紹介します。 なお、「developer」は約50名の参加者のうち競技期間中に4名が正解し、最初に正解があったのは競技開始から約3時間後です。*2

問題紹介

問題は、以下のように「開発者が偽のインストーラを実行してしまい、パスワードが盗まれてしまった。盗まれたパスワードを特定して欲しい」という設定で始まります。*3

開発者が使用していたクラウドサービスが侵害を受けて原因を調査していたところ、ある開発者が複数のサービスや開発端末で同じパスワードを使用していたことがわかりました。

そこであなたはパスワードが漏えいした原因を調査するために、開発者の端末を回収して調査をすることにしました。 開発者に不審な出来事がなかったかヒアリングしたところ、「そういえば、開発に使うソフトウェアをインストールするために、ISOファイルに入っていたインストーラを実行したが、何も表示されなかったなぁ。。」と話しています。 開発者がこの行動を行った大体の時間を聞いて、あなたはこの行動が行われた時間のトラフィックログとOSのイベントログを抽出しました。

このログから、開発者 developer の漏えいしたパスワードを調べてください。

解析を行ってもらう問題ファイルは、pcapファイルとWindowsイベントログファイル(.evtx)です。

ちなみに、ネットワークとエンドポイントのログ両方を出しましたが、これはネットワークとエンドポイント両方での解析が重要であることを理解してもらう狙いがありました。 エンドポイント/ネットワークログの一方の解析では発生したイベントの解像度が低く、適切な調査が出来ないことがあります。 この問題でも、ネットワークとエンドポイントいずれかのログを読むだけでは解くことができず、両方のログを行き来しながら読めないと解けないようになっています。

解法

pcapファイルの解析

Wiresharkでpcapファイルを開きます。

どのようなトラフィックがあるか確認するため、Conversationsを確認します。

以下のことがわかります。

  • 10.1.1.116がクライアント、10.1.1.117がサーバ
  • 80/tcp (HTTP), 443/tcp (HTTPS), 21/tcp (FTP) の通信と、37039/tcp, 59847/tcp のhigh port の通信がある

80/tcp

80/tcp の通信は1ストリームしかありません。Follow HTTP Streamで通信内容を確認します。

以下のことがわかります。

  • http://10.1.1.117/lib に対してアクセス
  • Serverヘッダは SimpleHTTP/0.6 Python/3.10.8
    • 何らかのファイルのダウンロードに使用と思われる
  • 難読化されたスクリプトのダウンロード
    • ekovnI (Invoke) みたいな文字列が見える

443/tcp

tcp.port == 443 でDisplay Filter をかける。

443/tcpは通常SSL/TLSを使用したHTTPSの通信に使われますが、SSL/TLS通信であれば、サーバのなりすましを確認するために証明書をダウンロードしたり、暗号化を行うために必要な鍵の交換などを行うハンドシェイクの通信が発生するはずです。しかし、今回はそのハンドシェイク通信がないため、HTTPSではない通信であることがわかります。

80/tcpと同様に、Follow TCP Stream で見てみます。

人が読めるASCII文字列はなさそうです。 この場合、何らかのバイナリデータか、何らかの方法で暗号化されているデータの可能性が考えられますが、この時点ではわかりません。

21/tcp, 37039/tcp, 59847/tcp

他のポートの通信と同様に、Follow TCP Stream で見てみます。

FTPで hoge というファイルをアップロードしています。

また、227 Entering passive mode (10,1,1,117,144,175). および 227 Entering passive mode (10,1,1,117,233,199) というメッセージから 、Passive mode を用いてファイルをアップロードしていることもわかります。 Passive modeで使われるポート番号はメッセージから 144256+175=37039 および 233256+199=59847 のように計算できるので、このTCPポートを使って通信しているデータをExportします。

Windows イベントログの解析

続いてWindowsイベントログの解析を行います。

イベントログの解析は参加者の中にはWindowsのイベントログを一つずつ見ていって、怪しいログを探していった人もいるかもしれません。 しかし、Windowsイベントログは大量にあるため、当たりもつけずに見つけるのは時間がかかる作業であり非効率的です。

そこで、WIndowsイベントログの解析ツールである Hayabusa を使い、Windows イベントログのトリアージ *4 を行います。

github.com

今回は、 csv-timeline 機能を用いて、CSVのWindowsイベントログのタイムラインを作成します。 このタイムラインは、単にイベントログを列挙してタイムラインが作成されるのではなく、Hayabusa Rule に含まれるルールにマッチしたものから作成されます。*5

hayabusa-2.10.0-win-x64.exe csv-timeline -d <Windowsイベントログが保存されているフォルダ> -o timeline.csv

オプションは、「Which set of detection rules would you like to load? · 1. Core ( status: test, stable | level: high, critical )」、「Include Emerging Threats rules? · yes」、「Include Threat Hunting rules? · yes」を選びます。

実行すると、3件の Severity: High のアラートが出てきます。

出力されたCSVをExcelや Eric Immerman's tool の Timeline Exporer を用いて確認します。

確認すると、SysmonのEvent ID:1 のログ(プロセスの起動)から以下の3つのイベントを発見しています。

  • 名前を変更したLOLBIN
    • コマンドラインを見ると、powershell.exeをchrome.exeにrenameしていることがわかる。
  • Comsvcs.dl を使ったプロセスメモリダンプ
    • "C:\Windows\System32\rundll32.exe" C:\windows\System32\comsvcs.dll MiniDump 668 C:\Users\DEVELO~1\AppData\Local\Temp\20231122.dump full
    • 親プロセスはchrome.exe のため、ここからプロセスのメモリダンプが実行されたと思われる

このことから、PowerShellを実行していることがわかるので、このコードを解析していきます。

PowerShellのコードの解析

イベントログから見つかったコード

Hayabusaのアラートで見つかったBase64にエンコードされたPowerShellのコードをデコードしてみます。

難読化されてそう。 この難読化されているコードを頑張って解いても良いですが、今回は Microsoft-Windows-PowerShell%4Operational.evtx があるので、まずはこのログを見てみます。

見てみると、先ほどよりも難読化が解除されたコードが残っています。; が命令の句切れなので、ここで改行して、ちょっと整形します。

文字列が並び替えられたり、変数がわかりにくくなっており、やや読みにくいですが、このスクリプトでは以下の処理を実行しています。

  • http://10.1.1.117/lib からファイルをダウンロードし、実行
    • ここからlibはPowerShellのスクリプトだと考えられる
  • SH("a") を実行し、$a に格納
  • 10.1.1.117:443 にコネクションを張る
  • 受信したデータを $b2 に格納
  • D($a, $b2) を実行し、$d に格納
  • $d の結果をIEXで実行し、 $sB に格納
    • IEXは Invoke-EXpression の略で、指定した文字列をPowerShellのコマンドとして評価または実行し、出力された結果を返します *6
  • $sB のデータの後ろに 'PS ' + (pwd).path + '> を追加
  • $sB をByte型に変換、E($a, $sB) を実行し、$SBY に格納
  • $SBY をサーバに送信

サーバから受信したデータをコマンドとして実行し、その後結果をサーバに返すのはリバースシェルの挙動です。

しかし、今回は 受信したデータを D という関数に通し、送信するデータを E という関数に通しています。 これらの関数は、このスクリプト内には存在しません。 よって、一般的に定義されている関数でもないので、これらはダウンロードしてきたlibの中にこれらの関数があると考えられます。

lib

libの本体はpcapファイルに残っています。

しかし、先ほどのコードと同様に Microsoft-Windows-PowerShell%4Operational.evtx を見てみると、ある程度難読化を解除されたコードが残っていることがわかります。

これも、; で改行して整形します。

少し見やすくなったので、この状態で各関数の処理を把握します。

まとめると、各関数では、以下のことを行っています。

  • 関数SH
    • 第1引数の文字列のSHA256の値をバイト列で返す
  • 関数E
    • 第1引数の文字列をKeyとして、第2引数のデータをAES CBCモードで暗号化する
    • 暗号化したデータの前にAESのIVをつける
  • 関数D
    • 第1引数の文字列をKeyとして、第2引数のデータをAES CBCモードで復号する
    • 受信したデータの先頭16bytesをIVとする。

PowerShell解析結果のまとめ

以上より、PowerShellはサーバからAESで暗号化されたPowerShellのコードを受け取り実行、実行した結果をAESで暗号化して送信するリバースシェルのコードであるとわかりました。 また、暗号化の鍵は文字列 a のSHA256のハッシュ値で、IVは先頭16バイトに付与されています。

暗号化されたトラフィックの復号

暗号化されたトラフィックを復号できれば、攻撃者がどのような行動を行ったか把握できそうなので、これまでの解析結果を元にトラフィックを復号します。 トラフィックのpcapをパースできる Scapy や暗号ライブラリ Pycryptodome を用いると、Pythonでは以下のように復号スクリプトを書けます。

from scapy.all import *

import hashlib
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from Crypto.Util import Padding

def SH(key):
    return hashlib.sha256(key.encode()).digest()


def d(key, data):
    iv = data[:16]
    aes = AES.new(key=key, mode=AES.MODE_CBC, iv=iv)
    res = Padding.unpad(aes.decrypt(data[16:]), AES.block_size)
    return res.decode()


key = SH("a")
pcap = rdpcap("traffic.pcap")

for p in pcap:
    if p["IP"].src=="10.1.1.116" and p["TCP"].dport==443 and p["TCP"].flags=="PA":
        print("C: " + d(key,p["Raw"].load))
    elif p["IP"].src=="10.1.1.117" and p["TCP"].sport==443 and p["TCP"].flags=="PA":
        print("S: " + d(key,p["Raw"].load))

これを実行すると、以下のように実行したコマンドが復号できます。

コマンドの復号結果から、攻撃者は以下のコマンドを実行したことがわかります。

  • ユーザの権限確認
  • lsass.exe のメモリダンプを取得し、ファイルに保存後、ZIP圧縮
  • ZIPファイル をlibに含まれるE関数でAES暗号化。暗号キーは文字列 gj をsha256にしたもの
  • FTPで 10.1.1.117 にアップロード

FTPで転送されたファイルの復号

FTPでアップロードされていたファイルがAESで暗号化されたZIPファイルであることがわかったので、トラフィックを復号した時と同様のPythonスクリプトを書き、復号します。

data1.bin (SHA1: f6235ef821615447384a5695ba4f2d21fefc90f2 )が、WiresharkでエクスポートしたFTPでアップロードされていたファイルです。

import hashlib
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from Crypto.Util import Padding

key = "gj"

f = open("data1.bin", "rb")
data = f.read()
f.close()

k = hashlib.sha256(key.encode()).digest()
iv = data[:16]
aes = AES.new(key=k, mode=AES.MODE_CBC, iv=iv)
res = Padding.unpad(aes.decrypt(data[16:]), AES.block_size)

f = open("data1.zip", "wb")
f.write(res)
f.close()

これを実行すると、data1.zip というZIPファイルができ、その中に攻撃者が標的の環境で取得した lsass.exe のメモリダンプが入っています。

dumpファイルの解析

lsass.exe のdumpファイルの解析には、 pypykatz を使います。

pypykatz lsa minidump 20231122.dump

実行すると以下のように表示され、ユーザ developer の NTLMハッシュが b9a342164519c83554615eb152b582a3 であることがわかります。

パスワードクラック

あとは、このNTLMハッシュから元のパスワードかを特定します。

今回はCrack Station を使用します。

パスワードクラックが出来るか試してみると1件ヒットし、developerのパスワードは otakudeveloper であるとわかります。

一般的に知られていないパスワードや複雑なパスワードであれば、このように特定することは困難ですが、このパスワードは著名なパスワードリスト rockyou.txt に含まれるパスワードなので、簡単に特定できてしまいます。 ワードリストに乗っているような簡単なパスワードを使用することはやめましょう。

余談と反省

  • PowerShellの痕跡は勘だったり、Hayabusaを使うとすぐに見つけてしまえるので、せっかくのISOファイルを開いたシナリオがあまり意味なくなってしまいました。実際に参加者のWriteupを見ると、ヒューリスティックにPowerShellのイベントログが残ってることを察してた人が多かったです。

  • ISOファイルを開いた痕跡は、Windowsのイベントログ ( Microsoft-Windows-VHDMP-Operational.evtx )に残ります。

  • 今回出題したテクニックは、私が以前ブログに書いたWriteupの内容と一部被っていたので、このブログを読んでた人は少し有利だったかもしれないです。

blog.nflabs.jp

  • 意図せず被害者端末から攻撃者のサーバにFTPでZIPファイルをアップロードする際、コマンドをなぜか2回実行されてしまいました。その結果、pcapファイルのサイズが大きくなってしまいました。申し訳ありません。

おわりに

今回の問題は、ログ分析の問題と言いつつも、マルウェア解析、プログラミング、ネットワークプロトコル、暗号、認証情報を窃取する攻撃手法の知識 など複数の知識が要求される問題でした。 実際のインシデントレスポンスやフォレンジック業務でもこのように様々なスキルが必要になることがあるので、この問題を通じて参加者が様々な点で学びを得られたのであれば幸いです。

明日はアドベントカレンダー最終日。今回のイベントのリーダーによる 開催記 & Writeup賞の発表です。 お楽しみに!

*1:個人的な見解ですが、ログ解析の問題がほとんど出題されないのは、難易度が低く出題してもコンテストとして成り立たないからだと思っています。

*2:今回のコンテストでは、各ジャンルのHard問題を一番速く解いた人にFirst Blood賞が贈呈されるためか、すぐ解かれました。 他の問題はFirst Blood賞が出るまで時間がかかったので、簡単すぎたかもしれません。!

*3:実際のDFIRでは、漏れたパスワードを明らかにするのは稀だと思いますが、今回はコンテストということもあるので調べてもらうことにしました。

*4:トリアージは元々は救急事故現場などで使われる用語で、医療や治療の優先度を見極めることです。この文脈では怪しいイベントに当たりをつけて、優先的に調査することを指します

*5:ルールは、HayabusaのフォーマットやSigmaなどで独自に追加できます

*6:https://learn.microsoft.com/ja-jp/powershell/module/microsoft.powershell.utility/invoke-expression