🌐

C# - MoTW と Zone.Identifierストリーム

に公開

はじめに

Excel マクロがブロックされたり、EXE ファイルの実行が SmartScreen によって制限されることがあります。
本記事では、まず、これらの制約の原因となる MoTW(Mark of the Web)と、その実装メカニズムである Zone.Identifier ストリームについて記載します。
その後、C#、PowerShell、コマンドプロンプトでの Zone.Identifier 操作を記載します。

参考情報

https://ascii.jp/elem/000/001/550/1550399/

https://qiita.com/SAITO_Keita/items/4ce171bce91b6daa5cbf

テスト環境

ここに記載した情報は、Windows 11 24H2 で動作確認しています。
C#ソースコードは、Visual Studio Community 2022 を利用した下記プロジェクトで生成したモジュールで確認しています。

  • Windows Forms - .NET Framework 4.8
  • Windows Forms - .NET 8
  • WPF - .NET Framework 4.8
  • WPF - .NET 8

MoTW と Zone.Identifier ストリーム

MoTW

MoTW(Mark of the Web)は、Windows 独自のセキュリティ機能で、インターネット経由で取得したファイルに適用される属性です。
MoTW が付与されたファイルは、特定の条件下で実行が制限されることがあります。

  • マクロ付きのOfficeファイル(Excel、Wordなど)は、マクロの実行が制限されます
  • スクリプトファイル(.ps1, .vbs, .jsなど)は、 PowerShell スクリプトや VBScript の実行が制限されます
  • 実行ファイル(.exe, .dllなど)は、Windows の SmartScreen フィルターによって実行が制限されます

Zone.Identifier ストリーム

MoTW の実装には、Zone.Identifier ストリームと呼ばれる特殊な NTFS 代替データストリーム(ADS:Alternate Data Streams)が使用されます。
Zone.Identifier ストリームには、ファイルの出所に関する情報が記録されます。

[ZoneTransfer]
ZoneId=3
ReferrerUrl=http://example.com
HostUrl=http://download.example.com/file.exe
  • ZoneId: ファイルの取得元のセキュリティゾーン
  • ReferrerUrl: ダウンロード元の Web ページ URL
  • HostUrl: ファイルを取得したサーバーの URL
  • HostIpAddress: ダウンロード元の IP アドレス(記録されない場合あり)

ZoneIdの種類:

ZoneId 意味 影響
0 ローカルコンピュータ 制限なし
1 ローカルイントラネット 制限なし
2 信頼済みサイト 制限なし
3 インターネット ブロック、もしくは、警告
4 制限付きサイト 基本的にブロック

Chrome、FireFox は独自のセキュリティモデルに基づいて、ZoneId を決定します。
Edge でダウンロードした場合、Windows「インターネットオプション」セキュリティタブの各ゾーンに設定されているサイトに基づいて、ZoneId を決定します。

マクロ付き Office ファイルの影響:

  • ZoneId=0,1,2 は、マクロの実行が許可されます
  • ZoneId=3 は、保護ビューで開かれ、マクロの実行が制限されます
  • ZoneId=4 は、基本的にブロックされます

スクリプトファイルの影響:

  • ZoneId=0,1,2 は、スクリプト実行が許可されます
  • ZoneId=3 は、Windows のセキュリティ機能(SmartScreen、PowerShell 実行ポリシーなど)によってブロック、もしくは、警告が表示されます
  • ZoneId=4 は、基本的にブロックされます

実行ファイルの影響:

  • ZoneId=0,1,2 は、実行が許可されます
  • ZoneId=3 は、SmartScreen による警告が表示されます
  • ZoneId=4 は、基本的にブロックされます

アーカイブからの継承

MoTW が付与された(Zone.Identifier ストリームが付与された)アーカイブを解凍したファイルに、対象属性値が継承されるかは、解凍ソフトが Zone.Identifier ストリームをどのように扱うかに依存します。

  • Windows 標準 API を利用した解凍(Explorer、PowerShell Expand-Archive)
    • アーカイブの ZoneId が、解凍後のファイルにも継承されます
    • ZoneId 以外の属性値は継承されないことがあります(ReferrerUrl が、ダウンロード元の Web ページ URLではなく、ローカルの対象アーカイブパスに変更される等)
  • 7zip、WinRAR などのサードパーティーソフト
    • 基本的に、Zone.Identifier は継承されません

C# での操作

Zone.Identifier 存在確認

確認対象 <filename> の場合、<filename>:Zone.Identifier の存在有無で確認できます。

string filePath = @"C:\path\to\hoge.exe:Zone.Identifier";
// Zone.Identifier 存在確認
if (File.Exists(filePath))
{
  Console.WriteLine("Zone.Identifier が存在します。");
}
else
{
  Console.WriteLine("Zone.Identifier は存在しません。");
}

Zone.Identifier 内容取得

string filePath = @"C:\path\to\hoge.exe:Zone.Identifier";
// Zone.Identifier 存在確認
if (File.Exists(filePath))
{
  // Zone.Identifier 内容取得
  using (var reader = new StreamReader(filePath))
  {
    Console.WriteLine(reader.ReadToEnd());
  }
}
else
{
  Console.WriteLine("Zone.Identifier は存在しません。");
}

Zone.Identifier 削除

Windows 11:
<filename>:Zone.Identifier を System.IO.File.DeleteFile すると、Zone.Identifier のみを削除できます。(後述、Windows 10 以前の手法でも削除可能)

string filePath = @"C:\path\to\hoge.exe:Zone.Identifier";
// Zone.Identifier 存在確認
if (File.Exists(filePath))
{
  try
  {
    // Zone.Identifier 削除
    File.Delete(filePath);
  }
  catch
  {
    Console.WriteLine("Zone.Identifier 削除に失敗しました。");
  }
}
else
{
  Console.WriteLine("Zone.Identifier は存在しません。");
} 

Windows 10 以前:
Zone.Identifier のみを削除するには、WIN32API - DeleteFile 利用が必要です。

string filePath = @"C:\path\to\hoge.exe:Zone.Identifier";
// Zone.Identifier 存在確認
if (File.Exists(filePath))
{
  // Zone.Identifier 削除
  if (NativeMethods.DeleteFile(filePath))
  {
    Console.WriteLine("Zone.Identifier を削除しました。");
  }
  else
  {
    Console.WriteLine("Zone.Identifier 削除に失敗しました。");
  }
}
else
{
  Console.WriteLine("Zone.Identifier は存在しません。");
} 
private static class NativeMethods
{
  // WIN32QPI
  [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
  public static extern bool DeleteFile(string name);
}

Zone.Identifier 更新

string filePath = @"C:\path\to\hoge.exe:Zone.Identifier";
var sb = new StringBuilder();
sb.AppendLine("[ZoneTransfer]");
sb.AppendLine("ZoneId=3");
sb.Append("ReferrerUrl=https://example.com");

try
{
  // Zone.Identifier 更新
  File.WriteAllText(filePath, sb.ToString());
}
catch
{
  Console.WriteLine("Zone.Identifier 更新に失敗しました。");
}

PowerShell での操作

Zone.Identifier 存在確認

Get-Item -Path "C:\path\to\hoge.exe" -Stream Zone.Identifier

Zone.Identifier 内容表示

Get-Content -Path "C:\path\to\hoge.exe" -Stream Zone.Identifier

Zone.Identifier 削除

対象ファイルの Zone.Identifier 削除は、2パターン存在します。

Remove-Item -Path "C:\path\to\hoge.exe" -Stream Zone.Identifier
Unblock-File -Path "C:\path\to\hoge.exe"

フォルダ内のすべてのファイルを対象として、Zone.Identifier 削除は下記で可能です。

Get-ChildItem -Path "C:\path\to\folder" -Recurse | Unblock-File

Zone.Identifier 更新

# 既存のファイルの Zone.Identifier を上書き(完全に置き換える)
Set-Content -Path "C:\path\to\hoge.exe" -Stream "Zone.Identifier" -Value @(
"[ZoneTransfer]", "ZoneId=3", "ReferrerUrl=https://example.com")

# 既存のファイルの Zone.Identifier を保持し、新しいデータを追記
Add-Content -Path "C:\path\to\hoge.exe" -Value "追加の内容"

コマンドプロンプトでの操作

NTFS 代替データストリーム(ADS)を調査・管理するためのツールとして、Steram.exe - Sysinternals | Microsoft Learn が提供されていますが、標準コマンドの範囲で記載します。

Zone.Identifier 存在確認

dir を /r オプションで実行して <filename> とペアで <filename>:Zone.Identifier:$DATA が存在するか否かで確認できます。

dir <対象フォルダパス> /r
2025/06/01 00:00    1,234,567 hoge.zip
                          257 hoge.zip:Zone.Identifier:$DATA

Zone.Identifier 内容表示

more を利用することで、Zone.Identifier を表示することができます。

more < hoge.zip:Zone.Identifier
[ZoneTransfer]
ZoneId=3
ReferrerUrl=http://example.com
HostUrl=http://download.example.com/file.exe

出典

本記事は、2025/06/02 Qiita 投稿記事の転載です。

C# - MoTW と Zone.Identifierストリーム

Discussion