はじめに
前回作成したネットワークスキャナーに、ポートスキャン機能を付けました。同一ネットワーク上の全デバイスのMACアドレスを収集し、各デバイスのベンダー(製造元)情報を自動で取得できるだけでなく、ポートスキャン機能も備えているため、デバイスのセキュリティ状態も確認できます。
このスクリプトで出来ること
- ローカルネットワーク内の全IPアドレスに対してPingを実行
- 応答があったデバイスのMACアドレスを取得
- IEEE OUIデータベースを使用して、MACアドレスからベンダー情報を特定
- オプションで各デバイスに対してポートスキャンを実行し、オープンポートを検出(新)
- 検出したデバイス情報とポート情報をCSVファイルに出力(新)
- 結果をコンソール上にも分かりやすく表示
スクリプトのコード
# 同一ネットワークに接続している端末のMACアドレスを収集し、ベンダー情報を取得して
# CSVファイルに保存するPowerShellスクリプト (ポートスキャン機能追加版)
param (
    [string]$OutputPath = "$PSScriptRoot\network_devices.csv",
    [string]$OuiDatabasePath = "$PSScriptRoot\oui_database.csv",
    [string]$NetworkPrefix = "192.168.1.",
    [int]$StartIP = 1,
    [int]$EndIP = 254,
    [switch]$EnablePortScan = $false,
    [int[]]$PortsToScan = @(21, 22, 23, 25, 53, 80, 443, 445, 3389, 8080)
)
# OUIデータベースの初期化と作成
function Initialize-OUIDatabase {
    param ([string]$DatabasePath)
    
    Write-Host "OUIデータベースをダウンロードしています..." -ForegroundColor Cyan
    
    # IEEE OUIデータベースをダウンロード
    $ouiUrl = "http://standards-oui.ieee.org/oui/oui.txt"
    $tempFile = "$env:TEMP\oui_temp.txt"
    
    try {
        Invoke-WebRequest -Uri $ouiUrl -OutFile $tempFile
        Write-Host "ダウンロード完了" -ForegroundColor Green
    }
    catch {
        Write-Host "ダウンロード失敗: $($_.Exception.Message)" -ForegroundColor Red
        return $null
    }
    
    # OUIデータを処理してCSVに変換
    $ouiData = @()
    
    Write-Host "OUIデータベースを処理しています..." -ForegroundColor Cyan
    Get-Content -Path $tempFile | ForEach-Object {
        if ($_ -match "([0-9A-F]{6})\s+\(hex\)\s+(.+)") {
            $ouiData += [PSCustomObject]@{
                OUI = $matches[1]
                Vendor = $matches[2].Trim()
            }
        }
    }
    
    # CSVとして保存
    $ouiData | Export-Csv -Path $DatabasePath -NoTypeInformation
    Remove-Item -Path $tempFile -ErrorAction SilentlyContinue
    
    Write-Host "OUIデータベースを作成しました: $DatabasePath" -ForegroundColor Green
    return $DatabasePath
}
# MACアドレスからベンダー情報を取得する関数
function Get-MACVendor {
    param (
        [string]$MACAddress,
        [string]$DatabasePath
    )
    
    $oui = ($MACAddress -replace '[-:]', '').Substring(0, 6).ToUpper()
    $vendor = Import-Csv -Path $DatabasePath | 
              Where-Object { $_.OUI -eq $oui } | 
              Select-Object -ExpandProperty Vendor -First 1
    
    if ($vendor) {
        return $vendor
    } else {
        return "不明"
    }
}
# 特定のIPアドレスの指定ポートをスキャンする関数
function Test-Port {
    param(
        [string]$IPAddress,
        [int]$Port,
        [int]$Timeout = 100
    )
    
    $tcpClient = New-Object System.Net.Sockets.TcpClient
    try {
        $connectionResult = $tcpClient.BeginConnect($IPAddress, $Port, $null, $null)
        $waitResult = $connectionResult.AsyncWaitHandle.WaitOne($Timeout, $false)
        
        if ($waitResult) {
            try {
                $tcpClient.EndConnect($connectionResult)
                return $true
            } catch {
                return $false
            }
        }
    } catch {
        return $false
    } finally {
        $tcpClient.Close()
    }
    return $false
}
# IPアドレスに対してポートスキャンを実行し、オープンポートのリストを返す関数
function Get-OpenPorts {
    param(
        [string]$IPAddress,
        [int[]]$Ports
    )
    
    $openPorts = @()
    $portServices = @{
        20 = "FTP-Data"
        21 = "FTP"
        22 = "SSH"
        23 = "Telnet"
        25 = "SMTP"
        53 = "DNS"
        80 = "HTTP"
        110 = "POP3"
        123 = "NTP"
        143 = "IMAP"
        443 = "HTTPS"
        445 = "SMB"
        993 = "IMAPS"
        995 = "POP3S"
        3306 = "MySQL"
        3389 = "RDP"
        5900 = "VNC"
        8080 = "HTTP-Proxy"
        8443 = "HTTPS-Alt"
    }
    
    Write-Host "  ポートスキャン中..." -NoNewline
    $progressCount = 0
    
    foreach ($port in $Ports) {
        $progressCount++
        Write-Progress -Activity "ポートスキャン" -Status "ポート: $port" -PercentComplete (($progressCount / $Ports.Count) * 100) -Id 2 -ParentId 1
        
        if (Test-Port -IPAddress $IPAddress -Port $port) {
            $serviceName = if ($portServices.ContainsKey($port)) { $portServices[$port] } else { "不明" }
            $openPorts += [PSCustomObject]@{
                Port = $port
                Service = $serviceName
            }
        }
    }
    
    Write-Progress -Activity "ポートスキャン" -Completed -Id 2
    Write-Host " 完了" -ForegroundColor Green
    return $openPorts
}
# メイン処理開始
Write-Host "ネットワークデバイス検出ツール(ポートスキャン機能付き)" -ForegroundColor Cyan
Write-Host "--------------------------------------------" -ForegroundColor Cyan
if ($EnablePortScan) {
    Write-Host "ポートスキャンが有効: $($PortsToScan -join ', ')" -ForegroundColor Yellow
    Write-Host "注意: ポートスキャンは時間がかかる場合があります" -ForegroundColor Yellow
} else {
    Write-Host "ポートスキャンは無効です (有効にするには -EnablePortScan を使用)" -ForegroundColor Gray
}
# OUIデータベースの確認
if (-not (Test-Path -Path $OuiDatabasePath)) {
    $OuiDatabasePath = Initialize-OUIDatabase -DatabasePath $OuiDatabasePath
    if ($null -eq $OuiDatabasePath) {
        Write-Host "OUIデータベースの作成に失敗しました。処理を終了します。" -ForegroundColor Red
        exit
    }
}
# ARPテーブルをクリア
Write-Host "ARPテーブルをクリアしています..." -ForegroundColor Cyan
arp -d
Write-Host "スキャンを開始: ${NetworkPrefix}${StartIP} から ${NetworkPrefix}${EndIP}" -ForegroundColor Cyan
$activeHosts = @()
# IPスキャン実行
for ($i = $StartIP; $i -le $EndIP; $i++) {
    $ip = "${NetworkPrefix}${i}"
    $progress = [math]::Round((($i - $StartIP) / ($EndIP - $StartIP + 1)) * 100)
    Write-Progress -Activity "ネットワークスキャン" -Status "IPアドレス: $ip" -PercentComplete $progress -Id 1
    
    # Ping送信
    if (Test-Connection -ComputerName $ip -Count 1 -Quiet -ErrorAction SilentlyContinue) {
        Write-Host "${ip} は応答中..." -NoNewline
        
        # MACアドレス取得
        $arpResult = arp -a $ip | Select-String '([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})'
        
        if ($arpResult) {
            $arpResult -match '([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})' | Out-Null
            $macAddress = $matches[0] -replace ':', '-'
            
            # ベンダー取得
            $vendor = Get-MACVendor -MACAddress $macAddress -DatabasePath $OuiDatabasePath
            
            # ホスト名取得
            try {
                $hostname = [System.Net.Dns]::GetHostEntry($ip).HostName
            } catch {
                $hostname = "不明"
            }
            
            # ポートスキャン(有効な場合)
            $openPorts = @()
            $portsInfo = ""
            
            if ($EnablePortScan) {
                $openPorts = Get-OpenPorts -IPAddress $ip -Ports $PortsToScan
                if ($openPorts.Count -gt 0) {
                    $portsInfo = ($openPorts | ForEach-Object { "$($_.Port)($($_.Service))" }) -join ", "
                    Write-Host "  オープンポート: $portsInfo" -ForegroundColor Yellow
                } else {
                    Write-Host "  オープンポートなし" -ForegroundColor Gray
                }
            } else {
                Write-Host " 完了" -ForegroundColor Green
            }
            
            Write-Host "  MAC: $macAddress  ベンダー: $vendor" -ForegroundColor Gray
            
            # 結果追加
            $activeHosts += [PSCustomObject]@{
                IPAddress = $ip
                MACAddress = $macAddress
                Vendor = $vendor
                HostName = $hostname
                DetectedTime = Get-Date -Format "yyyy/MM/dd HH:mm"
                OpenPorts = if ($EnablePortScan) { $portsInfo } else { "スキャンなし" }
            }
        } else {
            Write-Host " MAC取得失敗" -ForegroundColor Red
        }
    }
}
Write-Progress -Activity "ネットワークスキャン" -Completed -Id 1
# 結果表示
if ($activeHosts.Count -gt 0) {
    Write-Host "`n検出結果: $($activeHosts.Count)台" -ForegroundColor Cyan
    
    if ($EnablePortScan) {
        # ポートスキャン有効の場合、ポート情報を含めて表示
        $activeHosts | Format-Table -Property IPAddress, MACAddress, Vendor, HostName, OpenPorts -AutoSize
    } else {
        # ポートスキャン無効の場合、通常表示
        $activeHosts | Format-Table -Property IPAddress, MACAddress, Vendor, HostName -AutoSize
    }
    
    # CSV出力
    $activeHosts | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8
    Write-Host "CSVファイル出力完了: $OutputPath" -ForegroundColor Green
} else {
    Write-Host "デバイスが検出されませんでした" -ForegroundColor Yellow
}スクリプトの解説
主要機能
- 
IEEE OUIデータベースの自動ダウンロードと処理 
 スクリプト初回実行時に、IEEEのウェブサイトからOUI(Organizationally Unique Identifier)データベースをダウンロードし、検索しやすい形式に変換します。
- 
ネットワークスキャン 
 指定したIPアドレス範囲に対してPingを送信し、応答のあったホストを検出します。
- 
MACアドレス収集 
 ARPテーブルから各デバイスのMACアドレスを取得します。
- 
ベンダー情報の照会 
 MACアドレスの先頭6桁(OUI部分)を使って、デバイスの製造元を特定します。
- 
ポートスキャン(オプション) 
 検出したデバイスに対して、指定したポート(デフォルトでは一般的な21, 22, 23, 25, 53, 80, 443, 445, 3389, 8080)が開いているかを確認します。
- 
結果のエクスポート 
 収集した情報を見やすい表形式で表示し、CSVファイルとしても保存します。
パラメータ設定
スクリプトは以下のパラメータをカスタマイズできます:
- OutputPath: 結果を出力するCSVファイルのパス
- OuiDatabasePath: OUIデータベースを保存するパス
- NetworkPrefix: スキャンするネットワークのプレフィックス(例:192.168.1.)
- StartIP: スキャンを開始するIPアドレスの最後の数字
- EndIP: スキャンを終了するIPアドレスの最後の数字
- EnablePortScan: ポートスキャンを有効にするスイッチパラメータ
- PortsToScan: スキャンするポート番号の配列
使用方法
基本的な実行方法
- スクリプトをダウンロードして任意の場所に保存します(例:network_scanner.ps1)
- PowerShellを管理者権限で起動
- スクリプトのあるディレクトリに移動
- 以下のコマンドを実行
.\network_scanner.ps1
ポートスキャンを有効して実行
.\network_scanner.ps1 -EnablePortScanカスタムポートを指定してポートスキャンを実行
.\network_scanner.ps1 -EnablePortScan -PortsToScan @(80, 443, 8080, 3306)特定のIPアドレス範囲をスキャンする場合
.\network_scanner.ps1 -NetworkPrefix "10.0.0." -StartIP 1 -EndIP 100 -EnablePortScan結果の出力先を変更
.\network_scanner.ps1 -OutputPath "C:\Reports\network_scan_result.csv" -EnablePortScan実行結果
スクリプトを実行すると、以下のような結果が表示されます。
- OUIデータベースのダウンロードと処理状況
- ポートスキャンが有効かどうかと、スキャン対象のポート一覧
- スキャンの進捗状況(パーセンテージ表示)
- 検出されたデバイスごとのIP、MAC、ベンダー、ホスト名
- ポートスキャンが有効な場合は、各デバイスのオープンポートと関連サービス
- 最後に、検出されたデバイスの総数と結果のサマリー
また、指定したパスにCSVファイルが生成され、以下の情報が含まれます。
- IPアドレス
- MACアドレス
- ベンダー名
- ホスト名(取得できた場合)
- 検出日時
- オープンポートとサービス(ポートスキャンが有効な場合)
<実行結果のサンプル>
技術的なポイント
OUIデータベースの自動取得
このスクリプトでは、IEEEのウェブサイトから最新のOUIデータベースを自動的に取得し、ローカルにCSVとして保存します。
# IEEE OUIデータベースをダウンロード
$ouiUrl = "http://standards-oui.ieee.org/oui/oui.txt"
$tempFile = "$env:TEMP\oui_temp.txt"
try {
    Invoke-WebRequest -Uri $ouiUrl -OutFile $tempFile
    Write-Host "ダウンロード完了" -ForegroundColor Green
}
catch {
    Write-Host "ダウンロード失敗: $($_.Exception.Message)" -ForegroundColor Red
    return $null
}
TCPClientによるポートスキャン
ポートスキャンは、.NET FrameworkのTcpClientクラスを使用して実装されています。
function Test-Port {
    param(
        [string]$IPAddress,
        [int]$Port,
        [int]$Timeout = 100
    )
   
    $tcpClient = New-Object System.Net.Sockets.TcpClient
    try {
        $connectionResult = $tcpClient.BeginConnect($IPAddress, $Port, $null, $null)
        $waitResult = $connectionResult.AsyncWaitHandle.WaitOne($Timeout, $false)
       
        if ($waitResult) {
            try {
                $tcpClient.EndConnect($connectionResult)
                return $true
            } catch {
                return $false
            }
        }
    } catch {
        return $false
    } finally {
        $tcpClient.Close()
    }
    return $false
}
arp コマンドの活用
MACアドレスを正確に取得するために、最初にARPテーブルをクリアしてから、Pingを実行後にARPテーブルから情報を取得します。
# ARPテーブルをクリア
arp -d
# Pingで応答を確認後
$arpResult = arp -a $ip | Select-String '([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})'
進捗表示の実装
スクリプトは、ユーザーへのフィードバックとして、IPスキャンとポートスキャンの両方で進捗状況を表示します。
Write-Progress -Activity "ネットワークスキャン" -Status "IPアドレス: $ip" -PercentComplete $progress -Id 1Write-Progress -Activity "ポートスキャン" -Status "ポート: $port" -PercentComplete (($progressCount / $Ports.Count) * 100) -Id 2 -ParentId 1最後に
ポートスキャン機能を追加しました。
小規模ネットワークの管理やセキュリティチェックに使用してもらえたら幸いです。
今後は、オープンポートで動作しているサービスのバージョン情報を取得する機能、GUIインターフェイスの追加、既知のデバイスリストとの照合、などの機能を追加していきます。
以上です。
コメント