PowerShellで作るポートスキャン機能付きネットワークスキャナー

Powershell

はじめに

前回作成したネットワークスキャナーに、ポートスキャン機能を付けました。同一ネットワーク上の全デバイスの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
}

スクリプトの解説

主要機能

  1. IEEE OUIデータベースの自動ダウンロードと処理
    スクリプト初回実行時に、IEEEのウェブサイトからOUI(Organizationally Unique Identifier)データベースをダウンロードし、検索しやすい形式に変換します。

  2. ネットワークスキャン
    指定したIPアドレス範囲に対してPingを送信し、応答のあったホストを検出します。

  3. MACアドレス収集
    ARPテーブルから各デバイスのMACアドレスを取得します。

  4. ベンダー情報の照会
    MACアドレスの先頭6桁(OUI部分)を使って、デバイスの製造元を特定します。

  5. ポートスキャン(オプション)
    検出したデバイスに対して、指定したポート(デフォルトでは一般的な21, 22, 23, 25, 53, 80, 443, 445, 3389, 8080)が開いているかを確認します。

  6. 結果のエクスポート
    収集した情報を見やすい表形式で表示し、CSVファイルとしても保存します。

パラメータ設定

スクリプトは以下のパラメータをカスタマイズできます:

  • OutputPath: 結果を出力するCSVファイルのパス
  • OuiDatabasePath: OUIデータベースを保存するパス
  • NetworkPrefix: スキャンするネットワークのプレフィックス(例:192.168.1.)
  • StartIP: スキャンを開始するIPアドレスの最後の数字
  • EndIP: スキャンを終了するIPアドレスの最後の数字
  • EnablePortScan: ポートスキャンを有効にするスイッチパラメータ
  • PortsToScan: スキャンするポート番号の配列

使用方法

基本的な実行方法

  1. スクリプトをダウンロードして任意の場所に保存します(例:network_scanner.ps1)
  2.  PowerShellを管理者権限で起動
  3. スクリプトのあるディレクトリに移動
  4. 以下のコマンドを実行
.\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

実行結果

スクリプトを実行すると、以下のような結果が表示されます。

  1. OUIデータベースのダウンロードと処理状況
  2. ポートスキャンが有効かどうかと、スキャン対象のポート一覧
  3. スキャンの進捗状況(パーセンテージ表示)
  4. 検出されたデバイスごとのIP、MAC、ベンダー、ホスト名
  5. ポートスキャンが有効な場合は、各デバイスのオープンポートと関連サービス
  6. 最後に、検出されたデバイスの総数と結果のサマリー

また、指定したパスに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 1
Write-Progress -Activity "ポートスキャン" -Status "ポート: $port" -PercentComplete (($progressCount / $Ports.Count) * 100) -Id 2 -ParentId 1

最後に

ポートスキャン機能を追加しました。
小規模ネットワークの管理やセキュリティチェックに使用してもらえたら幸いです。

今後は、オープンポートで動作しているサービスのバージョン情報を取得する機能、GUIインターフェイスの追加、既知のデバイスリストとの照合、などの機能を追加していきます。

以上です。

コメント

タイトルとURLをコピーしました