NMAP without NMAP - Port Testing and Scanning with PowerShell

Published: 2022-10-31
Last Updated: 2022-10-31 01:52:42 UTC
by Rob VandenBrink (Version: 1)
4 comment(s)

Ever needed to do a portscan and didn't have nmap installed?  I've had this more than once on an internal pentest or more often just on run-rate "is that port open? / is there a host firewall in the way?" testing.

Often we see folks doing something cludgy with telnet or ftp, but Powershell Test-NetConnection (tnc for short) does a fine job of spot testing for an open port or to see if an IP is in use.

For a quick ping test, run tnc with just the target host as an argument:

PS L:\> tnc 8.8.8.8

ComputerName           : 8.8.8.8

RemoteAddress          : 8.8.8.8

InterfaceAlias         : Ethernet 2

SourceAddress          : 192.168.122.201

PingSucceeded          : True

PingReplyDetails (RTT) : 15 ms    

For a quick traceroute:

PS L:\> tnc 8.8.8.8 -traceroute

ComputerName           : 8.8.8.8

RemoteAddress          : 8.8.8.8

InterfaceAlias         : Ethernet 2

SourceAddress          : 192.168.122.201

PingSucceeded          : True

PingReplyDetails (RTT) : 13 ms

TraceRoute             : 192.168.122.1

                         99.254.226.1

                         66.185.90.177

                         24.156.147.129

                         209.148.235.222

                         72.14.216.54

                         108.170.228.0

                         172.253.69.113

                         8.8.8.8

Limiting the hopcount of traceroute:

PS L:\> tnc 8.8.8.8 -traceroute -hops 3

WARNING: Trace route to destination 8.8.8.8 did not complete. Trace terminated :: 66.185.90.177

ComputerName           : 8.8.8.8

RemoteAddress          : 8.8.8.8

InterfaceAlias         : Ethernet 2

SourceAddress          : 192.168.122.201

PingSucceeded          : True

PingReplyDetails (RTT) : 16 ms

TraceRoute             : 192.168.122.1

                         99.254.226.1

                         66.185.90.177

Testing an open port:

PS L:\> tnc 8.8.8.8 -port 443

ComputerName     : 8.8.8.8

RemoteAddress    : 8.8.8.8

RemotePort       : 443

InterfaceAlias   : Ethernet 2

SourceAddress    : 192.168.122.201

TcpTestSucceeded : True

Or, for a (not so) quick port scan:

$ip = "192.168.122.241"

$result = @()

for ($port = 9099; $port -le 9101 ; $port ++) {

   $result += (tnc $ip -port $port) | select remoteport, tcptestsucceeded

   }

PS L:\> $result

RemotePort TcpTestSucceeded

---------- ----------------

      9099            False

      9100             True

      9101            False

Or if you just want the open ports:

$ip = "192.168.122.241"

$result = @()

for ($port = 9099; $port -le 9101 ; $port ++) {

   $a = (tnc $ip -port $port) | select remoteport, tcptestsucceeded

   if ($a.tcptestsucceeded ) {

        $result += $a

        }

   }

As you may have noticed, tnc is a decent tool for testing a single port, but for a portscan it makes for the slowest tool ever.  It is however a a great tool for troubleshooting an individual service because the syntax is so easy to remember in a pinch.  Using Net.Sockets directly makes for a much faster scan, with slightly more complicated syntax:

new-object system.net.sockets.tcpclient -argumentlist "192.168.122.241","9100"

(yes, system.net.sockets.udpclient is also a thing, everything below works for udp too)

So our port scanner morphs to:

$targetservers = "192.168.122.241", "192.168.122.1"

$ports = "22","23","80","443","9100"

$result = @()

foreach ($target in $targetservers) {

    foreach ($port in $ports) {

       $a = new-object system.net.sockets.tcpclient -argumentlist $target,$port

       if ($a.connected) {

           $r = new-object -type psobject

           $r | add-member -membertype noteproperty -name host -value $target

           $r | add-member -membertype noteproperty -name port -value $port

           $r | add-member -membertype noteproperty -name state -value "Open"

           $result += $r

           }

       }

    }

Hmm, this still takes forever to come back from closed ports.  How about this adding in a 100ms timeout on each connection - you can play with that number as needed:

$targetservers = "192.168.122.241", "192.168.122.1"

$ports = "22","23","80","443","9100"

$result = @()

foreach ($target in $targetservers) {

    foreach ($port in $ports) {

        $obj = new-Object system.Net.Sockets.TcpClient

        $connect = $obj.BeginConnect($target,$port,$null,$null)

        $Wait = $connect.AsyncWaitHandle.WaitOne(100,$false)

        If (-Not $Wait) {

            write-host $target 'port' $port 'Closed - Timeout'

        }

        else {

            $value = "Open"

            write-host $target 'port' $port $value

            $r = new-object -type psobject

            $r | add-member -membertype noteproperty -name host -value $target

            $r | add-member -membertype noteproperty -name port -value $port

            $r | add-member -membertype noteproperty -name state -value $value

            $result += $r

        }

    }

}

192.168.122.241 port 22 Closed - Timeout

192.168.122.241 port 23 Closed - Timeout

192.168.122.241 port 80 Open

192.168.122.241 port 443 Open

192.168.122.241 port 9100 Open

192.168.122.1 port 22 Open

192.168.122.1 port 23 Closed - Timeout

192.168.122.1 port 80 Open

192.168.122.1 port 443 Open

192.168.122.1 port 9100 Closed - Timeout

 

PS L:\> $result

host            port state

----            ---- -----

192.168.122.241 80   Open

192.168.122.241 443  Open

192.168.122.241 9100 Open

192.168.122.1   22   Open

192.168.122.1   80   Open

192.168.122.1   443  Open

You can of course play with this final script as needed - For instance if you wanted to do a whole range of ports you might change the"foreach ($port in $ports)" loop back to a for loop:

$lport = 0
$hport = 65535
for ($port = $lport; $port -le $hport ; $port ++)

Or if you wanted to scan a /24 subnet, you might change the "foreach ($target in $targetservers) " loop to:
$net = "192.168.122."
for ($h = 1; $h -le 254 ; $h++)
    $target = $net+$h

This set of approaches is NOT meant to replace NMAP - for instance I can't tell you for instance how often I use NMAP scripts in a week!  However, if you are testing a site-to-site VPN, testing a router or firewall ACL, or checking a host firewall, almost always the host you are testing from is a customer's server, and you won't have NMAP available.  Considering that for so many people, the go-to test is to install a telnet (bad idea), then test for an open port using "telnet <target> <port>", with success being a blank screen, these approaches give you a LOT quicker and a LOT more reliably.

If you've got a any improvements to these scripts. in particular maybe a better way to indicate why a port might be not open (for instance - did it time out, send an ICMP port unreachable ro send a RST?) - by all means post to our comment section.  The scripts and snips I posted are only as good as they needed to be the last time I needed them, by all means lets make these better!


References:
Test-Netconnection:
https://learn.microsoft.com/en-us/previous-versions/windows/powershell-scripting/dn372891(v=wps.630)

system.Net.Sockets.TcpClient
https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.tcpclient?view=net-6.0

 

===============
Rob VandenBrink
rob@coherentsecurity.com

4 comment(s)

Comments

Thank you, Rob. This is incredibly useful.
nice script,
I added a latency output and shortened the result object creation

$targetservers = "77.21.200.99"

$ports = "443"

$result = @()

foreach ($target in $targetservers) {

foreach ($port in $ports) {

$obj = new-Object system.Net.Sockets.TcpClient

$connect = $obj.BeginConnect($target,$port,$null,$null)

$latency=(Measure-Command {
$Wait = $connect.AsyncWaitHandle.WaitOne(100,$false)
}).TotalMilliseconds



If (-Not $Wait) {

write-host $target 'port' $port 'Closed - Timeout'

}

else {

$value = "Open"

write-host $target 'port' $port $value ("{0:0.##}" -f $latency)


$result += [PSCustomObject]@{
host = $target
port = $port
state = $value
latency = ("{0:0.##}" -f $latency)
}

}

}

}
[deleted]
Foreach-object -parallel is your friend
https://devblogs.microsoft.com/powershell/powershell-foreach-object-parallel-feature/

then you can run everything in parallel if you need to scan many ports or many IPs.

Remember to use thread-safe objects for result. I.e. ConcurrentDictionary

Diary Archives