WindowsインスタンスのIMDS通信をIMDSパケットアナライザーの代わりにnetsh traceでトレースして分析してみた
どうもさいちゃんです。
以前下記のブログでWindowsインスタンスではIMDS通信を確認するためのIMDSパケットアナライザーがサポートされておらず、IMDSv2への移行する際にハマったというブログを書きました。
IMDSパケットアナライザーの代替案としてnetsh traceを使用したIMDS通信の分析方法をご紹介します。
本ブログが同じ課題をお持ちの皆さんの参考になれば幸いです。
なぜnetsh traceを使うのか
netshは、Windowsのネットワーク設定を管理するためのコマンドラインツールで、ネットワークインターフェースの設定変更やトラブルシューティングに使用されます。
その中でも「netsh trace」は、ネットワークパケットをキャプチャする機能を提供しており、追加のソフトウェアをインストールすることなく、コマンドプロンプトから簡単にネットワークトラフィックを記録・分析できます。
注意点
ファイル形式の変換が必要
- キャプチャしたETLファイルは、Wiresharkなどの一般的なツールで開くためにPCAPNG形式への変換が必要です
- 変換には専用ツール(etl2pcapngなど)のダウンロードとインストールが必要になります
システムリソースへの影響
- CPU使用率: パケットキャプチャ処理により、CPU負荷が上昇する可能性があります
- メモリ消費: バッファリング処理でメモリ使用量が増加する可能性があります
- ディスク容量: ETLファイルが大容量になる可能性があります(デフォルトで最大250MB)
本番環境で実施する場合は、事前に検証環境でリソース影響を確認することをお勧めします。
他の方法でのIMDS通信のキャプチャについて
トラフィックミラーリングも使えそうだと思い、同時に調査してみたがトラフィックミラーリングではIMDSv1の通信の確認ができないことが判明しました。
ドキュメントにも記載がある通り、VPCトラフィックミラーリングは一部の通信のミラーリングに一部制限があります。
制限される通信の中にメタデータサービスへのアクセスも含まれているようでトラフィックミラーリングではうまくパケットキャプチャができませんでした。
これが出来れば複数のインスタンスのIMDS通信の解析を1つのインスタンスでできるので便利そうだなと思ったのですが、AWS側だけの設定では分析は難しいようです。
やってみた
今回やりたいことは以下です。
- EC2でnetsh traceを実行。トレースファイルを作成
- トレースファイルをpcapngファイルへ変換
- 変換したファイルとプロセス番号を確認するためのプロセスファイルをS3へ保存
- Wiresharkなどのパケット解析ツールを使いどのプロセスがIMDSv1を使用しているかを特定する
S3バケットの作成
最初に上記手順3でファイルを保存するためのS3バケットを作成します。
グローバルで一意な名前を付けてもらえれば基本的にはデフォルト設定で問題ありません。
バケットポリシーを細かく制御したい場合は下記のように、EC2に紐づいているロールからのS3へのアクセスを許可してあげるバケットポリシーを付けてあげるのがいいと思います。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowEC2RoleAccess",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::アカウントID:role/your-ec2-role-name"
},
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::your-bucket-name",
"arn:aws:s3:::your-bucket-name/*"
]
},
{
"Sid": "AllowSSLRequestsOnly",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::your-bucket-name",
"arn:aws:s3:::your-bucket-name/*"
],
"Condition": {
"Bool": {
"aws:SecureTransport": "false"
}
}
}
]
}
上記のバケットを作成後、EC2側にポリシーをアタッチしましょう。
最低限、上記のバケットへの、PutObject,GetObject,ListBucketを許可してあげれば問題ないです。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowIMDSTraceBucketAccess",
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::your-bucket-name",
"arn:aws:s3:::your-bucket-name/*"
]
}
]
}
下記の スクリプトを使用する方法ではRuncommandで実行する想定です。
スクリプトを使用する場合はSSMが使えるように対象のロールにAmazonSSMManagedInstanceCoreもつけておきましょう。
これで準備は完了です。実際にnetsh traceでキャプチャファイルを作成していきましょう。
netsh traceを使う方法(手動)
EC2にRDPしたら、
管理者権限のコマンドプロンプトでtraceファイル保存先のディレクトリを作成し、移動します。
C:\Users\Administrator>mkdir C:\temp
C:\Users\Administrator>cd C:\temp
C:\temp>
netsh traceを実行してみます。
先ほど作ったディレクトリ内にトレースファイルを保存するように設定します。
ファイルが大きくなりすぎないように、IMDS通信の確認だけでいい場合はオプションでIPv4.Address=169.254.169.254を指定してあげましょう。
C:\temp>netsh trace start capture=yes tracefile=c:\temp\imds_trace.etl provider=Microsoft-Windows-TCPIP report=yes
Trace configuration:
-------------------------------------------------------------------
Status: Running
Trace File: C:\temp\imds_trace.etl
Append: Off
Circular: On
Max Size: 250 MB
Report: Off
IMDS通信テスト
IMDS通信テスト(v1)
Write-Host "Testing IMDSv1..." -ForegroundColor Red
$start1 = Get-Date
$instanceId1 = Invoke-RestMethod -Uri "http://169.254.169.254/latest/meta-data/instance-id"
$end1 = Get-Date
Write-Host "IMDSv1 Result: $instanceId1 (Time: $(($end1-$start1).TotalMilliseconds)ms)" -ForegroundColor Red
IMDS通信テスト(v2)
Write-Host "Testing IMDSv2..." -ForegroundColor Green
$start2 = Get-Date
$token = Invoke-RestMethod -Uri "http://169.254.169.254/latest/api/token" -Method PUT -Headers @{"X-aws-ec2-metadata-token-ttl-seconds" = "21600"}
$instanceId2 = Invoke-RestMethod -Uri "http://169.254.169.254/latest/meta-data/instance-id" -Headers @{"X-aws-ec2-metadata-token" = $token}
$end2 = Get-Date
Write-Host "IMDSv2 Result: $instanceId2 (Time: $(($end2-$start2).TotalMilliseconds)ms)" -ForegroundColor Green
テストが終わったらトレース停止コマンドを実行します。
C:\temp>netsh trace stop
Merging traces ... done
Generating data collection ... done
The trace file and additional troubleshooting information have been compiled as "c:\temp\imds_trace.cab".
File location = c:\temp\imds_trace.etl
Tracing session was successfully stopped.
traceファイル保存先のディレクトリを確認するとetlファイルが出来ていますが、そのままではWiresharkで開けないのでetl2pcapng.exeというファイル変換ツールを使用してpacpngファイルに変換する
> etl2pcapng.exe imds_trace.etl imds_trace.pcapng
IF: medium=eth ID=0 IfIndex=13 VlanID=0
Wrote 7725 frames to imds_trace.pcapng

pacpngファイルが作成されたのでこのファイルをWiresharkで解析する
netsh traceを使う方法(スクリプト)
netshを使う場合インスタンスごとにnetshコマンドでパケットをキャプチャする必要がありますが、もう少し現実的な方法(数日程度のキャプチャを想定した方法)でRunComanndで一気に実行できるようなスクリプトを生成AIを活用して作成してみました。
コマンドドキュメントはAWS-RunPowerShellScriptを使用して下記のスクリプトを対象インスタンスで実行します。
trace開始スクリプト
[CmdletBinding()]
param()
try {
# Get current timestamp
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
# Get instance information
$hostname = $env:COMPUTERNAME
$instanceId = "unknown"
try {
$token = Invoke-RestMethod -Uri "http://169.254.169.254/latest/api/token" -Method PUT -Headers @{"X-aws-ec2-metadata-token-ttl-seconds" = "21600"} -TimeoutSec 5 -ErrorAction Stop
$instanceId = Invoke-RestMethod -Uri "http://169.254.169.254/latest/meta-data/instance-id" -Headers @{"X-aws-ec2-metadata-token" = $token} -TimeoutSec 5 -ErrorAction Stop
}
catch {
Write-Output "[WARN] Failed to get instance ID. Using hostname instead: $_"
$instanceId = $hostname
}
Write-Output "================================================"
Write-Output "IMDS v2 Network Trace Start"
Write-Output "================================================"
Write-Output "Start Time: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
Write-Output "Instance ID: $instanceId"
Write-Output "Hostname: $hostname"
Write-Output ""
# Check if there's an existing trace running
$existingTrace = netsh trace show status
$traceStatusString = $existingTrace -join " "
if ($traceStatusString -notmatch "There is no trace session currently in progress" -and $traceStatusString -notmatch "not running") {
Write-Output "[WARN] Existing trace is running. Stopping it automatically..."
$stopResult = netsh trace stop
Start-Sleep -Seconds 2
Write-Output "[INFO] Existing trace stopped"
Write-Output ""
}
# Start network trace
Write-Output "Starting network trace..."
$traceResult = netsh trace start capture=yes maxsize=500 overwrite=yes IPv4.Address=169.254.169.254
if ($LASTEXITCODE -eq 0) {
Write-Output ""
Write-Output "[SUCCESS] Network trace started successfully"
Write-Output "Trace file will be saved at:"
Write-Output "C:\Users\$env:USERNAME\AppData\Local\Temp\NetTraces\NetTrace.etl"
Write-Output ""
Write-Output "To stop the trace, run stop-imds-trace-v2-en.ps1"
# Save trace information
$infoDir = "C:\IMDSTrace"
if (!(Test-Path $infoDir)) {
New-Item -ItemType Directory -Path $infoDir -Force | Out-Null
}
@{
StartTime = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
InstanceId = $instanceId
Hostname = $hostname
} | ConvertTo-Json | Out-File -FilePath "$infoDir\current-trace-info.json" -Encoding UTF8
Write-Output ""
Write-Output "[INFO] Trace information saved to: $infoDir\current-trace-info.json"
}
else {
Write-Output "[ERROR] Failed to start network trace"
Write-Output $traceResult
exit 1
}
}
catch {
Write-Output "[ERROR] An error occurred: $_"
Write-Output $_.ScriptStackTrace
exit 1
}
エラーなくスクリプトの実行が成功するとアウトプットはこんな感じです。
================================================
IMDS v2 Network Trace Start
================================================
Start Time: 2025-10-31 10:22:27
Instance ID: i-xxxxxxxxxxx
Hostname: EC2AMAZ-08VODQF
Starting network trace...
[SUCCESS] Network trace started successfully
Trace file will be saved at:
C:\Users\EC2AMAZ-08VODQF$\AppData\Local\Temp\NetTraces\NetTrace.etl
To stop the trace, run stop-imds-trace-v2-en.ps1
[INFO] Trace information saved to: C:\IMDSTrace\current-trace-info.json
インスタンス内のセッションを確認してみます。
PS C:\Windows\system32> logman query -ets
Data Collector Set Type Status
-------------------------------------------------------------------------------
AppModel Trace Running
AutoLogger-Diagtrack-Listener Trace Running
AWSNVMe Trace Running
CloudExperienceHostOobe Trace Running
DiagLog Trace Running
EventLog-Application Trace Running
EventLog-System Trace Running
NtfsLog Trace Running
UAL_Usermode_Provider Trace Running
TileStore Trace Running
UBPM Trace Running
WdiContextLog Trace Running
LSA Trace Running
MpWppCoreTracing-20251031-103124-00000003-100000000 Trace Running
MpWppTracing-20251031-103124-00000003-fffffffeffffffff Trace Running
1DSListener Trace Running
WindowsUpdate_trace_log Trace Running
-NetTrace-WORKGROUP-EC2AMAZ-46FFTNL$ Trace Running
MSDTC_TRACE_SESSION Trace Running
UAL_Kernelmode_Provider Trace Running
-NetTrace-WORKGROUP-EC2AMAZ-46FFTNL$のセッションがトレースセッションです。
トレースが開始されているので手動の場合と同じようにIMDS通信のテストを行います。
停止スクリプト
trace停止+S3保存スクリプト
$S3BucketName = "your-actual-bucket-name" # ← ここにあなたのバケット名を入力
$S3KeyPrefix = "imds-traces"
$KeepLocalFiles = $false
$Etl2PcapngPath = "C:\Tools\etl2pcapng.exe"
try {
Write-Output "================================================"
Write-Output "IMDS v2 トレース停止+S3アップロード"
Write-Output "================================================"
Write-Output "停止時刻: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
Write-Output ""
# インスタンス情報を取得
$instanceId = Invoke-RestMethod -Uri "http://169.254.169.254/latest/meta-data/instance-id" -Headers @{"X-aws-ec2-metadata-token" = (Invoke-RestMethod -Uri "http://169.254.169.254/latest/api/token" -Method PUT -Headers @{"X-aws-ec2-metadata-token-ttl-seconds" = "21600"})} -TimeoutSec 5
Write-Output "インスタンスID: $instanceId"
Write-Output "S3バケット: $S3BucketName"
Write-Output ""
# トレースの状態を確認
$traceStatus = netsh trace show status
$traceStatusString = $traceStatus -join " "
if ($traceStatusString -match "There is no trace session currently in progress" -or $traceStatusString -match "not running") {
Write-Warning "実行中のトレースがありません"
exit 1
}
# トレースを停止
Write-Output "トレースを停止しています..."
$stopResult = netsh trace stop
# 停止結果から出力ファイルのパスを取得
$etlFilePath = ""
foreach ($line in $stopResult) {
if ($line -match "File location\s*=\s*(.+)") {
$etlFilePath = $matches[1].Trim()
break
}
}
if ([string]::IsNullOrEmpty($etlFilePath) -or !(Test-Path $etlFilePath)) {
Write-Error "トレースファイルが見つかりません"
exit 1
}
Write-Output "[成功] トレースが停止されました"
Write-Output "ETLファイル: $etlFilePath"
# ファイルサイズを確認
$etlFile = Get-Item $etlFilePath
Write-Output "ETLファイルサイズ: $([math]::Round($etlFile.Length / 1MB, 2)) MB"
Write-Output ""
# 作業ディレクトリに移動
$workDir = Split-Path $etlFilePath -Parent
Set-Location $workDir
Write-Output "作業ディレクトリ: $workDir"
# CABファイルが存在する場合は解凍
$cabFilePath = $etlFilePath.Replace(".etl", ".cab")
if (Test-Path $cabFilePath) {
Write-Output "CABファイルを解凍しています..."
expand -r $cabFilePath /f:processes.txt $workDir
Write-Output "[成功] processes.txt を抽出しました"
}
# タイムスタンプを生成
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
# etl2pcapngが利用可能か確認し、なければダウンロード
$pcapngFilePath = ""
if (!(Test-Path $Etl2PcapngPath)) {
Write-Output "etl2pcapng.exe が見つかりません。ダウンロードを試みます..."
# ツールディレクトリを作成
$toolDir = Split-Path $Etl2PcapngPath -Parent
if (!(Test-Path $toolDir)) {
New-Item -ItemType Directory -Path $toolDir -Force | Out-Null
}
# GitHubから最新版をダウンロード
try {
$downloadUrl = "https://github.com/microsoft/etl2pcapng/releases/latest/download/etl2pcapng.exe"
Write-Output "ダウンロード元: $downloadUrl"
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Invoke-WebRequest -Uri $downloadUrl -OutFile $Etl2PcapngPath -UseBasicParsing
if (Test-Path $Etl2PcapngPath) {
Write-Output "[成功] etl2pcapng.exe をダウンロードしました"
}
}
catch {
Write-Warning "etl2pcapng.exe のダウンロードに失敗しました: $_"
Write-Warning "ETLファイルをそのままアップロードします"
}
}
if (Test-Path $Etl2PcapngPath) {
Write-Output ""
Write-Output "ETLファイルをPCAPNG形式に変換しています..."
$pcapngFileName = "NetTrace-${instanceId}-${timestamp}.pcapng"
$pcapngFilePath = Join-Path $workDir $pcapngFileName
& $Etl2PcapngPath $etlFilePath $pcapngFilePath
if (Test-Path $pcapngFilePath) {
$pcapngFile = Get-Item $pcapngFilePath
Write-Output "[成功] PCAPNG変換完了"
Write-Output "PCAPNGファイル: $pcapngFileName"
Write-Output "PCAPNGサイズ: $([math]::Round($pcapngFile.Length / 1MB, 2)) MB"
}
else {
Write-Warning "PCAPNG変換に失敗しました。ETLファイルをアップロードします"
}
}
else {
Write-Warning "ETLファイルをそのままアップロードします"
}
Write-Output ""
Write-Output "S3にファイルをアップロードしています..."
# S3キーのプレフィックスを準備
$s3KeyBase = "${S3KeyPrefix}/${instanceId}/${timestamp}"
# アップロード結果を格納
$uploadedFiles = @()
# PCAPNGファイルをアップロード(存在する場合)
if (![string]::IsNullOrEmpty($pcapngFilePath) -and (Test-Path $pcapngFilePath)) {
$pcapngS3Key = "${s3KeyBase}/$(Split-Path $pcapngFilePath -Leaf)"
Write-S3Object -BucketName $S3BucketName -File $pcapngFilePath -Key $pcapngS3Key
$uploadedFiles += "s3://$S3BucketName/$pcapngS3Key"
Write-Output "[アップロード] PCAPNG: $pcapngS3Key"
}
else {
# PCAPNGがない場合はETLファイルをアップロード
$etlFileName = "NetTrace-${instanceId}-${timestamp}.etl"
$etlNewPath = Join-Path $workDir $etlFileName
Copy-Item $etlFilePath $etlNewPath
$etlS3Key = "${s3KeyBase}/$etlFileName"
Write-S3Object -BucketName $S3BucketName -File $etlNewPath -Key $etlS3Key
$uploadedFiles += "s3://$S3BucketName/$etlS3Key"
Write-Output "[アップロード] ETL: $etlS3Key"
}
# processes.txtをアップロード(存在する場合)
$processesFile = Join-Path $workDir "processes.txt"
if (Test-Path $processesFile) {
$processesS3Key = "${s3KeyBase}/processes.txt"
Write-S3Object -BucketName $S3BucketName -File $processesFile -Key $processesS3Key
$uploadedFiles += "s3://$S3BucketName/$processesS3Key"
Write-Output "[アップロード] Processes: $processesS3Key"
}
# トレース情報ファイルを作成してアップロード
$infoFileName = "trace-info.txt"
$infoFilePath = Join-Path $workDir $infoFileName
@"
=== IMDS v2 Trace Information ===
Instance ID: $instanceId
Trace Stop Time: $(Get-Date -Format "yyyy-MM-dd HH:mm:ss")
Original ETL Size: $([math]::Round($etlFile.Length / 1MB, 2)) MB
Converted to PCAPNG: $(if($pcapngFilePath -and (Test-Path $pcapngFilePath)){"Yes"}else{"No"})
Uploaded Files:
$($uploadedFiles -join "`n")
Download Commands:
$($uploadedFiles | ForEach-Object { "aws s3 cp `"$_`" ." } | Out-String)
"@ | Out-File -FilePath $infoFilePath -Encoding UTF8
$infoS3Key = "${s3KeyBase}/$infoFileName"
Write-S3Object -BucketName $S3BucketName -File $infoFilePath -Key $infoS3Key
Write-Output "[アップロード] Info: $infoS3Key"
Write-Output ""
Write-Output "[成功] すべてのファイルがS3にアップロードされました"
Write-Output ""
Write-Output "================================================"
Write-Output "アップロードされたファイル:"
foreach ($file in $uploadedFiles) {
Write-Output " $file"
}
Write-Output ""
Write-Output "ダウンロードコマンド:"
Write-Output "aws s3 sync `"s3://$S3BucketName/$s3KeyBase/`" ."
Write-Output "================================================"
# ローカルファイルのクリーンアップ
if (!$KeepLocalFiles) {
Write-Output ""
Write-Output "ローカルファイルをクリーンアップしています..."
# 作業ディレクトリ内のトレース関連ファイルを削除
Remove-Item "$workDir\NetTrace.*" -Force -ErrorAction SilentlyContinue
Remove-Item "$workDir\processes.txt" -Force -ErrorAction SilentlyContinue
if ($pcapngFilePath) { Remove-Item $pcapngFilePath -Force -ErrorAction SilentlyContinue }
Remove-Item $infoFilePath -Force -ErrorAction SilentlyContinue
Write-Output "[完了] ローカルファイルを削除しました"
}
else {
Write-Output ""
Write-Output "[情報] ローカルファイルは保持されます"
Write-Output "保存場所: $workDir"
}
Write-Output ""
Write-Output "[完了] すべての処理が正常に終了しました"
}
catch {
Write-Error "エラーが発生しました: $_"
exit 1
}
上記のスクリプトを実行し、成功すると対象のS3バケットにこのように3つファイルが出力されます。

- NetTrace-{instanceId}-{timestamp}.pcapng
- ネットワーク通信のキャプチャファイル。Wiresharkで開いてIMDSへの通信内容を詳細に分析できる。
- processes.txt
- トレース実行時に動いていたプロセスの一覧。PIDとプロセス名の対応表で、どのアプリケーションがIMDSにアクセスしているかを特定するために使用する。
- trace-info.txt
- トレースのメタデータ(インスタンスID、停止時刻、ファイルサイズ)とS3からのダウンロードコマンドが記載された情報ファイル。
これらのファイルを使用して分析を行います。
Wiresharkでの解析
先ほどお伝えした停止スクリプトにはWiresharkで解析出来るようにpcapngファイルへの変換とS3へのtraceファイルとプロセス特定のためのファイルのアップロードまでが含まれています。
IMDSv2を使用した通信ではHTTPヘッダーにX-aws-ec2-metadata-tokenヘッダーが追加されているはずなので下記のフィルターでIMDS通信HTTPリクエストのみを表示させてみます。
※あらかじめIMDS通信のみをトレースしてる場合はIPでのフィルタは不要です。
IMDSv1の通信を確認したい場合
ip.dst == 169.254.169.254 and http.request.method == "GET" and !(http matches "X-aws-ec2-metadata-token") and !(frame.comment contains "PID=0")
IMDSv2の通信を確認したい場合
ip.dst == 169.254.169.254 and http contains "X-aws-ec2-metadata-token" and !(frame.comment contains "PID=0")
IMDSv1

IMDSv2

IMDSv2通信はトークンを発行するのでHTTPヘッダーに「X-aws-ec2-metadata-token」があります。
これがあるかないかで絞り込んでいます。
絞り込んだ後は各通信のPIDとプロセスIDが記載されたprocesses.txtファイルを照らし合わせることでプロセスの特定が可能です。

最後に
前回のブログを書いてからかなり時間がたってしまいましたが、なんとかWindowインスタンスでIMDSv1通信を利用するプロセスを特定する方法をアウトプットできました。
上記の作業はかなり時間と手間がかかるので、検証機を用意して検証機でのテスト後段階的にIMDSvへ移行するというのが最もシンプルな方法だと思います。
しかし、どうしても本番機をIMDSv2並行する前にプロセスの特定を行いたい、まずはどのようなプロセスがIMDSv1を利用しているか特定してから慎重に検証を行いたいなどの場合にこの方法が活用できるかもしれません。
ただ、上記のスクリプトを使用する場合は、インスタンスが停止するとトレースも停止してしまうので気を付けてください。
定期的に停止が行われるインスタンスに関してはEventBridgeなどを使ったコマンドの定期実行やWindowsタスクスケジューラーなどでのスクリプト実行が必要になります。
余裕があったらその方法もブログで紹介します。






