こんにちは! VBScriptの世界へようこそ。
業務の自動化を進めていくと、「フォルダの中にあるファイルを自動でチェックしたい」「怪しいファイルがないか見張りいたい」という実践的な課題にぶつかりますよね。
今回は、プログラミング初学者の方から「マクロの記録や単純な自動化は卒業したぞ!」というステップアップ層に向けて、ファイルシステムオブジェクト(FSO)と、ちょっとディープな「ファイルの正体(マジックバイト)」を暴くセキュリティスクリプトの作り方を解説します。
「ここをクリアすれば、VBScriptのファイル操作の基本はバッチリですよ!」と温かく導きますので、ぜひ最後までついてきてくださいね。
—
1. なぜ「拡張子」だけを見てはいけないのか?
Windowsを使っていると、ファイル名の後ろにある「.txt」や「.jpg」「.exe」といった拡張子を見て、私たちはファイルの性質を判断しますよね。
しかし、悪意あるユーザーや、セキュリティ意識の低いユーザーが、
- 本当は実行ファイル(.exe)なのに、名前を `report.txt.exe` ではなく `report.txt` に偽装する
- 危険なスクリプトを画像ファイルに見せかける
といった「拡張子偽装」を行うことがあります。これをVBScriptの標準機能である `GetExtensionName` だけで見ていると、「あ、これはテキストファイルですね」と騙されてしまいます。
そこで登場するのが、ファイルが持つ「マジックバイト(ヘッダー情報)」です。ファイルの中身の先頭数バイトには、「私はJPEG画像です」「私はZIPアーカイブ(またはOfficeファイル)です」という絶対に偽装できない固有の署名(シグネチャ)が刻まれています。
今回は、このマジックバイトと拡張子を突き合わせ、「おや、名前はテキストだけど中身はexeじゃないか!」と検知するプロ仕様のスクリプトを作ってみましょう。
—
2. スクリプトの全体像:何をするプログラムか?
今回作成するVBScriptは、指定したフォルダ内を巡回し、以下のチェックを行います。
1. FSO(FileSystemObject)を使ってファイルの拡張子を取得する。
2. ADODB.Streamを使って、ファイルの先頭数バイト(バイナリデータ)を読み込む。
3. マジックバイトのパターンと照合し、本来のファイル形式を特定する。
4. 「自称する拡張子」と「本当のファイル形式」が一致しているか監査し、不一致なら警告ログを吐く!
それでは、実際のコードを見てみましょう。現場でそのままコピー&ペーストして使える実用的なコードです。
—
3. 実装コード:拡張子偽装ハンター・スクリプト
以下のコードをメモ帳などに貼り付け、拡張子を `.vbs` (例: `audit_files.vbs`)として保存してください。
‘ ==============================================================================
‘ スクリプト名: CheckFileSpoofing.vbs
‘ 概要: マジックバイトと拡張子の不一致(拡張子偽装)を検出するセキュリティスクリプト
‘ 対象読者: VBScriptの基本から一歩進んだファイル操作を学びたい方
‘ ==============================================================================
Option Explicit
‘ メイン処理の実行
Call AuditFolder(“C:\Work\CheckTarget”)
Sub AuditFolder(targetFolderPath)
Dim fso, folder, file, ext, magicBytes, actualType
Set fso = CreateObject(“Scripting.FileSystemObject”)
‘ フォルダが存在するかチェック
If Not fso.FolderExists(targetFolderPath) Then
WScript.Echo “エラー: 指定されたフォルダが存在しません: ” & targetFolderPath
Exit Sub
End If
Set folder = fso.GetFolder(targetFolderPath)
WScript.Echo “— ファイル監査を開始します: ” & targetFolderPath & ” —”
‘ フォルダ内の全ファイルをループ処理
For Each file in folder.Files
‘ 1. FSOの GetExtensionName で「自称する拡張子」を取得
ext = LCase(fso.GetExtensionName(file.Name))
‘ 2. ファイルの先頭バイト(マジックバイト)を読み取って「本当の形式」を判定
actualType = GetFileMagicType(file.Path)
‘ 3. 監査ロジック(拡張子と中身の乖離をチェック)
Call CheckConsistency(file.Name, ext, actualType)
Next
WScript.Echo “— ファイル監査が完了しました —”
‘ オブジェクトの解放(メモリ管理の基本!)
Set folder = Nothing
Set fso = Nothing
End Sub
‘ ——————————————————————————
‘ 関数: GetFileMagicType
‘ 役割: ADODB.Streamを使ってファイルの先頭数バイトを読み込み、マジックバイトを判定する
‘ ——————————————————————————
Function GetFileMagicType(filePath)
Const adTypeBinary = 1
Dim stream, bytes, hexHeader
On Error Resume Next
Set stream = CreateObject(“ADODB.Stream”)
If Err.Number <> 0 Then
GetFileMagicType = “ERROR”
Exit Function
End If
stream.Type = adTypeBinary
stream.Open
stream.LoadFromFile filePath
‘ 先頭から4バイトを読み込む
bytes = stream.Read(4)
stream.Close
Set stream = Nothing
On Error GoTo 0
‘ バイト配列を16進数の文字列に変換する(例: “4D5A9000″)
hexHeader = BytesToHex(bytes)
‘ マジックバイトによる判定
If Left(hexHeader, 4) = “4D5A” Then
GetFileMagicType = “EXE” ‘ Windows実行ファイル (MZ…)
ElseIf Left(hexHeader, 8) = “504B0304” Then
GetFileMagicType = “ZIP” ‘ ZIP圧縮ファイル(Officeドキュメント含む)
ElseIf Left(hexHeader, 6) = “FFD8FF” Then
GetFileMagicType = “JPG” ‘ JPEG画像
ElseIf Left(hexHeader, 8) = “89504E47” Then
GetFileMagicType = “PNG” ‘ PNG画像
ElseIf Left(hexHeader, 8) = “25504446” Then
GetFileMagicType = “PDF” ‘ PDF文書
Else
GetFileMagicType = “UNKNOWN”
End If
End Function
‘ ——————————————————————————
‘ 補助関数: BytesToHex
‘ 役割: バイナリデータを16進数文字列に変換する
‘ ——————————————————————————
Function BytesToHex(bytes)
Dim i, stream, result
Set stream = CreateObject(“ADODB.Stream”)
stream.Type = 1 ‘ adTypeBinary
stream.Open
stream.Write bytes
stream.Position = 0
stream.Type = 2 ‘ adTypeText
stream.Charset = “iso-8859-1”
result = stream.ReadText
stream.Close
Set stream = Nothing
Dim hexStr
hexStr = “”
For i = 1 To Len(result)
Dim h
h = Hex(Asc(Mid(result, i, 1)))
If Len(h) = 1 Then h = “0” & h
hexStr = hexStr & h
Next
BytesToHex = hexStr
End Function
‘ ——————————————————————————
‘ 監査ロジック: 乖離チェック
‘ ——————————————————————————
Sub CheckConsistency(fileName, ext, actualType)
Dim isSuspicious
isSuspicious = False
‘ 例: 拡張子が txt なのに、中身が EXE だった場合
If ext = “txt” And actualType = “EXE” Then
isSuspicious = True
‘ 例: 拡張子が jpg なのに、中身が EXE だった場合
ElseIf (ext = “jpg” Or ext = “jpeg”) And actualType = “EXE” Then
isSuspicious = True
End If
If isSuspicious Then
WScript.Echo “[警告!] 拡張子偽装の疑いがあります!” & _
” ファイル名: ” & fileName & _
” (自称拡張子: ” & ext & ” / 本当の中身: ” & actualType & “)”
Else
WScript.Echo “[正常] ” & fileName & ” (拡張子: ” & ext & ” / 判定: ” & actualType & “)”
End If
End Sub
—
4. コードの重要ポイントと「初心者が陥りやすい罠」
ここまで読んだ方の中には、「うわっ、ADODB.Streamとか出てきて難しそう……」と思った方もいるかもしれません。でも大丈夫!ここを押さえればVBScriptの視野が一気に広がります。
① `FileSystemObject` (FSO) の限界を知る
FSOはファイルやフォルダを作る・消す・調べる(`GetExtensionName` など)には最高の相棒ですが、「ファイルの中身(バイナリ)」を直接読み解く能力はありません。
だからこそ、テキストファイルを扱うときは `TextStream` を使い、今回のようなバイナリ(マジックバイト)を扱うときは `ADODB.Stream` を組み合わせるという、適材適所のオブジェクト使い分けが必要になります。これがプロの技術です。
② メモリの解放(`Set … = Nothing`)をサボらない
VBScriptでは、スクリプトが終了すれば使ったメモリは自動で回収されますが、長時間のループや巨大なフォルダを巡回するスクリプトでは、使い終わったオブジェクト(`Set folder = Nothing` や `Set stream = Nothing`)をこまめに解放することが、メモリリークを防ぐ重要なマナーです。
③ なぜ `Option Explicit` を書くのか?
コードの2行目にある `Option Explicit`、これ実はめちゃくちゃ重要です。
これを書いておくと、変数の宣言し忘れ(タイポによるバグ)を強制的にエラーにしてくれます。 「あれ、動かないな…あ、変数名を打ち間違えてた!」という無駄なデバッグ時間をゼロにしてくれるので、VBScriptを書くときは必ずおまじないのように先頭に書きましょう。
—
5. まとめ
今回は、FSOによるファイル操作の基本を押さえつつ、一歩進んだ「マジックバイトによるファイル監査」のスクリプトをご紹介しました。
- FSOは「外側(名前や拡張子、サイズ)」を見る得意技がある。
- ADODB.Streamは「内側(バイナリ・中身の正体)」を暴く得意技がある。
この2つを組み合わせることで、業務自動化だけでなく、ちょっとしたセキュリティチェックツールまでVBScriptで自作できるようになります。
「ここをクリアすれば、VBScriptの基本はバッチリですよ!」
ぜひご自身の環境でも試して、自動化マスターへの階段を一段上ってみてくださいね。それでは、次回の応用編もお楽しみに!
