From 5362aa12e9878741ed59ff5695426a4a3283ab6c Mon Sep 17 00:00:00 2001 From: Bogdan Calapod Date: Mon, 22 Jun 2026 19:40:45 +0300 Subject: [PATCH 1/2] feat: Add nmap script --- .../Poor Man's Nmap/Poor-Mans-Nmap.ps1 | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 PowerShell Scanners/Poor Man's Nmap/Poor-Mans-Nmap.ps1 diff --git a/PowerShell Scanners/Poor Man's Nmap/Poor-Mans-Nmap.ps1 b/PowerShell Scanners/Poor Man's Nmap/Poor-Mans-Nmap.ps1 new file mode 100644 index 0000000..7d39d1c --- /dev/null +++ b/PowerShell Scanners/Poor Man's Nmap/Poor-Mans-Nmap.ps1 @@ -0,0 +1,115 @@ +# Poor man's nmap - PDQ Connect PowerShell scanner edition. +# Auto-detects the local subnet, parallel ICMP sweep, then TCP-connect probes +# common ports on each live host. Emits ONE OBJECT PER LISTENING HOST so the +# scanner maps the properties to columns (IPAddress, Hostname, MAC, OpenPorts). +# No params, no Format-Table, no ConvertTo-Csv - those produce strings, and the +# scanner would just reflect String.Length into a bogus "Length" column. + +# --- Config (edit here instead of params) ------------------------------------ +$Ports = @(21,22,23,25,53,80,110,135,139,143,389,443,445,587,993,995,1433,3306,3389,5432,5900,8080,8443) +$TimeoutMs = 500 + +$ServiceNames = @{ + 21='ftp'; 22='ssh'; 23='telnet'; 25='smtp'; 53='dns'; 80='http'; 110='pop3' + 135='msrpc'; 139='netbios'; 143='imap'; 389='ldap'; 443='https'; 445='smb' + 587='submission'; 993='imaps'; 995='pop3s'; 1433='mssql'; 3306='mysql' + 3389='rdp'; 5432='postgres'; 5900='vnc'; 8080='http-alt'; 8443='https-alt' +} + +function Get-IPRange { + param([Parameter(Mandatory)][string]$CIDR) + $parts = $CIDR -split '/' + $base = [System.Net.IPAddress]::Parse($parts[0]) + $prefix = [int]$parts[1] + + $bytes = $base.GetAddressBytes() + [Array]::Reverse($bytes) + $ipInt = [System.BitConverter]::ToUInt32($bytes, 0) + + if ($prefix -eq 0) { $mask = [uint32]0 } + else { $mask = [uint32](([math]::Pow(2, $prefix) - 1)) -shl (32 - $prefix) } + + $network = [uint32]($ipInt -band $mask) + $hostBits = [uint32]((-bnot $mask) -band [uint32]::MaxValue) + $broadcast = [uint32]($network -bor $hostBits) + + if ($prefix -ge 31) { $lo = [int64]$network; $hi = [int64]$broadcast } + else { $lo = [int64]$network + 1; $hi = [int64]$broadcast - 1 } + + for ($i = $lo; $i -le $hi; $i++) { + $b = [System.BitConverter]::GetBytes([uint32]$i) + [Array]::Reverse($b) + ([System.Net.IPAddress]::new($b)).IPAddressToString + } +} + +# --- Auto-detect the local subnet -------------------------------------------- +$cfg = Get-NetIPConfiguration -ErrorAction SilentlyContinue | + Where-Object { $_.IPv4DefaultGateway -and $_.NetAdapter.Status -eq 'Up' } | + Select-Object -First 1 +if (-not $cfg) { throw "Could not auto-detect an active IPv4 network." } +$Cidr = "$($cfg.IPv4Address.IPAddress)/$($cfg.IPv4Address.PrefixLength)" + +$targets = @(Get-IPRange -CIDR $Cidr) + +# --- Host discovery (parallel ICMP, batched) --------------------------------- +$live = New-Object System.Collections.Generic.List[string] +$batch = 256 +for ($i = 0; $i -lt $targets.Count; $i += $batch) { + $slice = $targets[$i..([math]::Min($i + $batch - 1, $targets.Count - 1))] + $pingers = foreach ($ip in $slice) { + $p = [System.Net.NetworkInformation.Ping]::new() + [PSCustomObject]@{ IP = $ip; Pinger = $p; Task = $p.SendPingAsync($ip, $TimeoutMs) } + } + try { [void][System.Threading.Tasks.Task]::WaitAll([System.Threading.Tasks.Task[]]($pingers.Task), $TimeoutMs + 1000) } catch {} + foreach ($pg in $pingers) { + if ($pg.Task.Status -eq [System.Threading.Tasks.TaskStatus]::RanToCompletion -and + $pg.Task.Result.Status -eq 'Success') { + $live.Add($pg.IP) + } + $pg.Pinger.Dispose() + } +} + +# --- Port scan + enrichment (emit only LISTENING hosts) ---------------------- +$results = foreach ($ip in $live) { + $conns = foreach ($port in $Ports) { + $c = [System.Net.Sockets.TcpClient]::new() + [PSCustomObject]@{ Port = $port; Client = $c; Task = $c.ConnectAsync($ip, $port) } + } + try { [void][System.Threading.Tasks.Task]::WaitAll([System.Threading.Tasks.Task[]]($conns.Task), $TimeoutMs) } catch {} + + $open = foreach ($cn in $conns) { + $isOpen = ($cn.Task.Status -eq [System.Threading.Tasks.TaskStatus]::RanToCompletion -and $cn.Client.Connected) + $cn.Client.Close() + if ($isOpen) { + $name = $ServiceNames[[int]$cn.Port] + if ($name) { "$($cn.Port)/$name" } else { "$($cn.Port)" } + } + } + $open = @($open) + if ($open.Count -eq 0) { continue } # skip hosts with nothing listening + + $hostname = try { [System.Net.Dns]::GetHostEntry($ip).HostName } catch { '' } + + $mac = '' + try { + $n = Get-NetNeighbor -IPAddress $ip -ErrorAction Stop | + Where-Object { $_.LinkLayerAddress } | Select-Object -First 1 + if ($n) { $mac = $n.LinkLayerAddress } + } catch { + $arpLine = (arp -a $ip 2>$null | Select-String $ip) + if ($arpLine -match '([0-9a-fA-F]{2}[-:]){5}[0-9a-fA-F]{2}') { $mac = $Matches[0] } + } + + [PSCustomObject]@{ + IPAddress = $ip + Hostname = $hostname + MAC = $mac + OpenPorts = ($open -join ', ') + } +} + +# Emit objects to the pipeline -> scanner builds one column per property, +# one row per listening host. +$results | Sort-Object { [System.Version]$_.IPAddress } From 4cbb2fa3f64a5585905edfafccff7f1891f407b2 Mon Sep 17 00:00:00 2001 From: Bogdan Calapod Date: Sat, 27 Jun 2026 10:27:31 +0300 Subject: [PATCH 2/2] task: Update Readme --- PowerShell Scanners/Poor Man's Nmap/README.md | 53 +++++++++++++++++++ .../{Poor-Mans-Nmap.ps1 => Script.ps1} | 0 2 files changed, 53 insertions(+) create mode 100644 PowerShell Scanners/Poor Man's Nmap/README.md rename PowerShell Scanners/Poor Man's Nmap/{Poor-Mans-Nmap.ps1 => Script.ps1} (100%) diff --git a/PowerShell Scanners/Poor Man's Nmap/README.md b/PowerShell Scanners/Poor Man's Nmap/README.md new file mode 100644 index 0000000..6944577 --- /dev/null +++ b/PowerShell Scanners/Poor Man's Nmap/README.md @@ -0,0 +1,53 @@ +# Poor Man's Nmap + +## Instructions + +[How to use this repository](../../README.md) + +## Description + +Does a simple port scan over devices on the local network, based on the common default ports of usual +services. + +Serves as a simple port scanner without needing to use something like [nmap](https://nmap.org/download). + +The scanner: + +1. Auto-detects the active local IPv4 subnet using `Get-NetIPConfiguration` (the adapter that is `Up` and has a default gateway). +2. Runs a parallel ICMP sweep across the subnet to find live hosts. +3. TCP-connect probes a set of common service ports on each live host. +4. Enriches every listening host with its hostname (via DNS) and MAC address (via `Get-NetNeighbor`, falling back to `arp`). + +One object is emitted per listening host, so the scanner maps the properties to columns. Hosts with no +open ports are skipped. + +The following common ports/services are scanned by default: + +`21/ftp`, `22/ssh`, `23/telnet`, `25/smtp`, `53/dns`, `80/http`, `110/pop3`, `135/msrpc`, `139/netbios`, +`143/imap`, `389/ldap`, `443/https`, `445/smb`, `587/submission`, `993/imaps`, `995/pop3s`, `1433/mssql`, +`3306/mysql`, `3389/rdp`, `5432/postgres`, `5900/vnc`, `8080/http-alt`, `8443/https-alt` + +## Requirements + +* The device running the scan must have an active IPv4 adapter with a default gateway. +* Network and host firewalls must permit outbound ICMP and TCP connections for results to be accurate. + +This scanner takes no parameters. Configuration (the list of ports in `$Ports` and the per-probe +`$TimeoutMs`) is edited directly in `Script.ps1`. + +## Compatibility + +* PDQ Connect + +## Output + +| Column | Description | +| --- | --- | +| `IPAddress` | The IPv4 address of the listening host. | +| `Hostname` | The DNS hostname of the host, if it could be resolved. | +| `MAC` | The MAC (link-layer) address of the host, if available. | +| `OpenPorts` | A comma-separated list of the open ports and their service names (e.g. `80/http, 443/https`). | + +## Author + +* Bogdan Calapod diff --git a/PowerShell Scanners/Poor Man's Nmap/Poor-Mans-Nmap.ps1 b/PowerShell Scanners/Poor Man's Nmap/Script.ps1 similarity index 100% rename from PowerShell Scanners/Poor Man's Nmap/Poor-Mans-Nmap.ps1 rename to PowerShell Scanners/Poor Man's Nmap/Script.ps1