正直、Googleスプレッドシートをマルウェアへの命令や実行結果取得に使うのは、正規サービスの悪用の中ではネタ枠だと思っていた。
githubでGoogleスプレッドシートをC2として使うツールは公開されているので、どこかでは使われているんだろうなぁとは認識していたが本当にしっかり使われているとは。
github.com
以下は、Googleの記事内の攻撃概要図。

セルの使い方
基本的には、3種類の目的でセルを使用。
| セル | 役割 |
|---|---|
| A1 | コマンド受信 & ステータス応答(ポーリング対象) |
| A2〜An | データ転送(コマンド出力、ファイルアップロード/ダウンロード用) |
| V1 | 被害ホストの情報(ユーザー名、ホスト名、OS、ローカルIP、言語、タイムゾーン等) |
列は、A列とV列しか使わない。
セルA1が最も重要で、ここでマルウェアと攻撃者がやり取りを行う。
セルA1 コマンド配信とマルウェア状態取得
こちらが、A1に書き込まれるデータの形式。
<type>-<command_id>-<arg_1>-<arg_2>
4つのフィールドをハイフンで区切ったデータを使用する。
type:
| 値 | 指示方向 | 書き込む側 |
|---|---|---|
| C | 攻撃者→マルウェア | 攻撃者 |
| S | マルウェア→攻撃者 | マルウェア |
command_id:
| 値 | 操作 |
|---|---|
| C | シェルコマンド実行 |
| U | シートからターゲットへファイル転送 |
| D | ターゲットからシートへファイル転送 |
つまり、A1の内容がCから始まったらマルウェアが読み取って操作が行われSから始まるデータを書き込む。
A1の内容がSから始まったら攻撃者側で操作を行って次のコマンドとなるCから始まるデータを書き込む。
arg_1とarg_2は、typeとcommand_idで役割が変わる。以下の表は、Googleの解説と提示されたYARAルールから推測できるもの。
| 構文 | arg_1 | arg_2 |
|---|---|---|
| C-C-<arg_1> | Base64シェルコマンド | なし |
| C-U-<arg_1>-<arg_2> | Base64ファイルパス | データ最終行番号 |
| C-D-<arg_1> | Base64ファイルパス | なし |
| S-C-%s | コマンド成功時はR、失敗時はエラー文字列 | なし |
| S-U-%s-1 | ファイルアップロード成功時はR、失敗時はエラー文字列 | 1(固定値?) |
| S-D-R-%d | R(ファイルダウンロード成功時) | データ最終行番号 |
| S-D-%s-0 | ファイルダウンロード失敗時のエラー文字列 | 0 |
arg_1は、type:Cなら、base64エンコードされたデータ。type:Sなら、成功か失敗メッセージ。
arg_2は、A2以降にデータを残す処理を行うかで使用されたりされなかったりするようだ。
C(Command)— シェルコマンド実行
command_id:Cのシェルコマンド実行指示の例。
攻撃者 → A1: C-C-<Base64エンコードされたコマンド> 例: C-C-aWQgMj4mMQ(→ "id 2>&1")
記事中の初期検知でもsh -c id 2>&1というプロセスツリーが確認されているので、おそらく上記のような感じでマルウェアがBase64デコード後、sh -c "<コマンド>" 2>&1で実行したのかと。
実行結果をBase64エンコードしてA2以降に書き込み、A1にステータスを返す。
マルウェア → A1: S-C-R (Server-Command-Result = 成功) → A2〜An: <出力データのBase64チャンク>
D(Download)— ターゲットからファイル窃取
command_id:Dのターゲットからのファイルダウンロードの例。
攻撃者 → A1: C-D-<Base64エンコードされたファイルパス> 例: C-D-L2V0Yy9wYXNzd2Q=( → "/etc/passwd")
マルウェアは指定されたファイルを読み取り、45KBずつ分割してBase64エンコードし、A2から順にセルに書き込む。
マルウェア → A2: <最初の45KBチャンクのBase64> → A3: <次の45KBチャンク> ... → A15: <最後のチャンク> → A1: S-D-R-15 (Server-Download-Result-最終行15)
攻撃者はA1のS-D-R-15を見て「A2からA15までにデータがある」と分かり、A2:A15を一括読み取りしてBase64デコード・結合すれば元ファイルが復元できる。
45KBというのは、Google Sheetsの1セルあたり50,000文字制限に由来するらしい。
U(Upload)— ターゲットへのファイル送り込み
command_id:Uのターゲットへのファイルアップロードの例。
攻撃者がA2以降にデータを事前配置してからA1にコマンドを書く。
Step 1 — 攻撃者がデータを配置:
攻撃者 → A2: <ファイルの45KBチャンク1のBase64>
→ A3: <チャンク2>
...
→ A50: <最終チャンク>
Step 2 — 攻撃者がコマンド発行:
攻撃者 → A1: C-U-<Base64エンコードされたファイルパス>-50
(書き込み先パス) (データ最終行)
例: C-U-L3Zhci90bXAvdG9vbA==-50(→ "/var/tmp/tool", A2:A50からデータ読み取り)
マルウェアはA2:A50を読み取り、各セルのBase64をデコード・結合して指定パスに書き出す。
マルウェア → A1: S-U-R-1 (Server-Upload-Result-成功)
スプレッドシート使用の流れ
上記のような処理は、例えば以下のような流れになると思われる。
A1の内容 誰のターンか ──────────────────────────────────────── (空) マルウェアがポーリング中... ↓ 攻撃者がA1に書き込み C-C-aWQ= マルウェアのターン(コマンド実行) ↓ マルウェアが実行→A2に出力→A1を上書き S-C-R 攻撃者のターン(結果回収→次コマンド投入) ↓ 攻撃者がA2の出力を回収→A1を上書き C-D-L2V0Yy9zaGFkb3c= マルウェアのターン(ファイル窃取) ↓ マルウェアがファイル分割→A2〜A8に書き込み→A1を上書き S-D-R-8 攻撃者のターン(A2:A8回収→次コマンド) ↓ ...
V1セルには、起動時1回のみホスト情報登録。たぶん、A1の内容が長くなったら見にくくなるから、V列にした?
検知・調査観点
sheets.googleapis.comとoauth2.googleapis.comへのアクセスを悪用するのだが、それだけの観点だと正規のGoogle Sheetsアドオンやスクリプトのポーリングと通信パターンがほぼ同一かと。
とりあえずは、ブラウザ以外からのGoogle Sheets APIアクセス検知ができれば怪しいと判断しやすそう。
Google曰く、特に「batchClear or batchUpdate or valueRenderOption=FORMULAを含むものでプロセスがブラウザ以外のものが怪しい」。
この考え方は、GRIDTIDEに限らずGoogle Sheets C2全般に有効そう。
Active Countermeasureが、似たような調査時の検証を行った内容が公開されている。
www.activecountermeasures.com
完全に余談だが、Active Countermeasureの記事で使っているzeek-cut、調査のために超便利そう。始めて知りました。
github.com
想像で作ったやつ
Google Sheets APIでやれば、できるんだろうなという理解で。
雰囲気で作ったら簡易バージョンがサクッとできたので、そのうち説明を作ろうかと思います。

他の攻撃者
ネタじゃなかったのか、GoogleスプレッドシートC2。
UNC2814の他にも実は、使っている事例が複数あったと知りました。
Voldemort
APT41はGC2-sheet使っていた
スプレッドシート=C2チャネル?
ふと思ったが、protonスプレッドシートのAPIとか公開されてC2チャネルとして活用されたらヤバそう。
もう、スプレッドシートを見たらC2チャネルに見える人生が始まってしまった。