# ========================================================= # MAX 网络工具箱 - 端口扫描 # 远程端口扫描 / 本机监听端口查看 # ========================================================= $Common = "C:\Setup\windows\lib\common.ps1" if (Test-Path $Common) { . $Common } # ========================================================= # 全局数据 # ========================================================= $CommonPorts = @{ 21 = "FTP" 22 = "SSH" 23 = "Telnet" 25 = "SMTP" 53 = "DNS" 80 = "HTTP" 110 = "POP3" 135 = "RPC" 139 = "NetBIOS" 143 = "IMAP" 443 = "HTTPS" 445 = "SMB" 993 = "IMAPS" 995 = "POP3S" 1433 = "MSSQL" 1521 = "Oracle" 3306 = "MySQL" 3389 = "RDP" 5432 = "PostgreSQL" 5900 = "VNC" 6379 = "Redis" 7000 = "Common Service" 7500 = "Dashboard" 8080 = "HTTP-Alt" 8443 = "HTTPS-Alt" 9200 = "Elasticsearch" 27017 = "MongoDB" } # ========================================================= # 辅助函数 # ========================================================= function Get-PrimaryIPv4 { try { $route = Get-NetRoute -DestinationPrefix "0.0.0.0/0" -ErrorAction SilentlyContinue | Where-Object { $_.NextHop -ne "0.0.0.0" } | Sort-Object RouteMetric | Select-Object -First 1 if ($route) { $ip = Get-NetIPAddress -AddressFamily IPv4 -InterfaceIndex $route.InterfaceIndex -ErrorAction SilentlyContinue | Where-Object { $_.IPAddress -notlike "127.*" -and $_.IPAddress -notlike "169.254.*" } | Select-Object -First 1 if ($ip) { return $ip.IPAddress } } } catch {} return "127.0.0.1" } function Test-Port { param( [string]$Target, [int]$Port, [int]$TimeoutMs = 500 ) $client = $null try { $client = New-Object System.Net.Sockets.TcpClient $async = $client.BeginConnect($Target, $Port, $null, $null) $success = $async.AsyncWaitHandle.WaitOne($TimeoutMs, $false) if (-not $success) { return $false } $client.EndConnect($async) return $true } catch { return $false } finally { if ($client) { $client.Close() $client.Dispose() } } } function Convert-PortText { param([string]$Text) $result = @() foreach ($part in ($Text -split ",")) { $item = $part.Trim() if ($item -match "^(\d+)-(\d+)$") { $start = [int]$Matches[1] $end = [int]$Matches[2] if ($start -lt 1) { $start = 1 } if ($end -gt 65535) { $end = 65535 } if ($start -le $end) { $result += $start..$end } } elseif ($item -match "^\d+$") { $port = [int]$item if ($port -ge 1 -and $port -le 65535) { $result += $port } } } return @($result | Sort-Object -Unique) } # ========================================================= # 功能函数 # ========================================================= function Scan-Ports { param( [string]$Target, [int[]]$Ports, [int]$TimeoutMs = 500 ) if ([string]::IsNullOrWhiteSpace($Target)) { Write-Err "目标不能为空" return } if (-not $Ports -or $Ports.Count -eq 0) { Write-Err "端口列表为空" return } Write-Section "端口扫描" Write-Info "扫描目标: $Target" Write-Info "端口数量: $($Ports.Count)" Write-Info "超时时间: $TimeoutMs ms" Write-Host "" $openPorts = @() $total = $Ports.Count $index = 0 foreach ($port in $Ports) { $index++ if ($index -eq 1 -or $index % 100 -eq 0 -or $index -eq $total) { Write-Host " 进度: $index / $total" -ForegroundColor DarkGray } $isOpen = Test-Port -Target $Target -Port $port -TimeoutMs $TimeoutMs if ($isOpen) { $service = "Unknown" if ($CommonPorts.ContainsKey($port)) { $service = $CommonPorts[$port] } $item = [PSCustomObject]@{ Target = $Target Port = $port Protocol = "TCP" Service = $service Status = "Open" } $openPorts += $item Write-OK "开放: $port/tcp $service" } } Write-Host "" Write-OK "扫描完成。开放端口: $($openPorts.Count) / $total" if ($openPorts.Count -gt 0) { $openPorts | Format-Table Port, Protocol, Service, Status -AutoSize $export = Read-Host " 是否导出 CSV?输入 y 导出" if ($export -eq "y") { $logDir = "C:\Setup\Logs" New-Item -ItemType Directory -Path $logDir -Force | Out-Null $file = Join-Path $logDir "port-scan-$(Get-Date -Format 'yyyyMMdd-HHmmss').csv" $openPorts | Export-Csv -Path $file -Encoding UTF8 -NoTypeInformation Write-OK "已导出: $file" } } } function Show-ListeningPorts { Write-Section "本机监听端口" try { $items = Get-NetTCPConnection -State Listen -ErrorAction Stop | Sort-Object LocalPort | ForEach-Object { $processName = "" try { $processName = (Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue).ProcessName } catch {} [PSCustomObject]@{ LocalAddress = $_.LocalAddress LocalPort = $_.LocalPort PID = $_.OwningProcess Process = $processName } } $items | Format-Table LocalAddress, LocalPort, PID, Process -AutoSize } catch { Write-Warn "Get-NetTCPConnection 不可用,改用 netstat" netstat -ano | Select-String "LISTENING|监听" } } # ========================================================= # 主循环 # ========================================================= do { $localIp = Get-PrimaryIPv4 Write-Header "端口扫描" Write-Info "本机主要 IP: $localIp" Write-Line Write-Section "操作菜单" Write-Info "1. 扫描本机常用端口" Write-Info "2. 扫描本机全部端口 1-65535" Write-Info "3. 扫描指定主机常用端口" Write-Info "4. 自定义目标和端口" Write-Info "5. 查看本机监听端口" Write-Info "0. 退出" Write-Host "" $choice = Read-Host " 请选择" Write-Line switch ($choice) { "1" { Scan-Ports -Target $localIp -Ports @($CommonPorts.Keys) -TimeoutMs 500 Pause-Max } "2" { Write-Warn "全端口扫描耗时较长" $confirm = Read-Host " 输入 YES 确认继续" if ($confirm -eq "YES") { Scan-Ports -Target $localIp -Ports (1..65535) -TimeoutMs 200 } else { Write-Warn "已取消" } Pause-Max } "3" { $target = Read-Host " 请输入目标 IP 或域名" if (-not [string]::IsNullOrWhiteSpace($target)) { Scan-Ports -Target $target -Ports @($CommonPorts.Keys) -TimeoutMs 500 } Pause-Max } "4" { $target = Read-Host " 请输入目标 IP 或域名" $portText = Read-Host " 请输入端口,例如 80,443,3389 或 1-1024" $ports = Convert-PortText -Text $portText if ($ports.Count -gt 1000) { Write-Warn "端口数量较多: $($ports.Count)" $confirm = Read-Host " 输入 YES 确认继续" if ($confirm -ne "YES") { Write-Warn "已取消" Pause-Max continue } } Scan-Ports -Target $target -Ports $ports -TimeoutMs 500 Pause-Max } "5" { Show-ListeningPorts Pause-Max } "0" { Write-OK "已退出" break } default { Write-Warn "无效选项" Pause-Max } } } while ($choice -ne "0")