I don't know about you, but in Windows I use netstat pretty frequently. Netstat is a great tool to see what is happening on your network interface - listening ports, tcp connections and so on. Common arguments are:
netstat -na |
will tell you what ports your system has listening, as well as what you are connected to via TCP. |
netstat -nao |
will display the owning process ID for each connection and port |
netstat -naob |
(d'oh!) This will fail unless you "run as administrator" |
There's more you can do of course, but usually it's that last one that I seem to want, and the "run as admin" is always a hoop that you have to jump through, because running stuff in a cmd window as admin regularly is a great way to give an attacker a ready-made privesc situation.
Anyway, I got tired of this and decided to do this in PowerShell, without the elevation requirement and with more information
Let's start with the process information. In this list, in this context we just want the ID and the processname:
get-process | select id,processname,description
27560 Notepad Notepad.exe
5620 NVDisplay.Container
7080 NVDisplay.Container
7804 nvWmi64
13664 nvWmi64
18480 OBRecoveryServicesManagement...
7672 OfficeClickToRun
11080 OneDrive Microsoft OneDrive
19180 ONENOTEM Send to OneNote Tool
18404 OpenVPNConnect OpenVPN Connect
18468 OpenVPNConnect OpenVPN Connect
19108 OpenVPNConnect OpenVPN Connect
19140 OpenVPNConnect OpenVPN Connect
19148 openvpn-gui OpenVPN GUI for Windows
7764 openvpnserv
32504 OUTLOOK Microsoft Outlook
But what we really want is to take that process name and jam it into the list of UDP and TCP information. To do this we'll use Get-NetTCPConnection and Get-NetUDPEndPoint commands.
In each, we have a field "OwningProcess", which is the process ID - keep your eye on that prize!
Rather than loops and arrays to glue this info into one list, we can do it dynamically and just dump the info. Note that we've added an in-line expression to take the OwningProcess number and get the name of that process, using Get-Process. We'll use the process name rather than the description to keep this fitting on a normal display:
Get-NetUDPEndpoint | select LocalAddress,LocalPort,CreationTime,OwningProcess,@{Name="Process";Expression={(Get-Process -Id $_.OwningProcess).ProcessName}} | ft -auto
LocalAddress LocalPort CreationTime OwningProcess Process
------------ --------- ------------ ------------- -------
192.168.229.1 5353 1/3/2024 1:22:15 PM 7400 mDNSResponder
192.168.217.1 5353 1/1/2024 3:20:04 PM 8216 TeamViewer_Service
192.168.122.201 5353 1/3/2024 1:22:15 PM 7400 mDNSResponder
192.168.24.3 5353 1/3/2024 1:22:15 PM 7400 mDNSResponder
172.21.240.1 5353 1/3/2024 1:22:15 PM 7400 mDNSResponder
172.21.240.1 5353 1/1/2024 3:20:04 PM 8216 TeamViewer_Service
0.0.0.0 5353 1/3/2024 1:22:13 PM 2920 svchost
0.0.0.0 5050 1/1/2024 3:20:18 PM 13904 svchost
0.0.0.0 4500 1/1/2024 3:19:58 PM 7608 svchost
0.0.0.0 3702 1/3/2024 2:41:54 PM 13108 svchost
192.168.229.1 2177 1/3/2024 12:41:50 PM 24152 svchost
192.168.217.1 2177 1/3/2024 12:41:50 PM 24152 svchost
192.168.122.201 2177 1/3/2024 12:42:04 PM 24152 svchost
192.168.24.3 2177 1/3/2024 1:22:12 PM 24152 svchost
172.21.240.1 2177 1/1/2024 6:04:18 PM 24152 svchost
192.168.229.1 1900 1/3/2024 1:22:12 PM 5376 svchost
192.168.217.1 1900 1/3/2024 1:22:12 PM 5376 svchost
192.168.122.201 1900 1/3/2024 1:22:12 PM 5376 svchost
192.168.24.3 1900 1/3/2024 1:22:12 PM 5376 svchost
172.21.240.1 1900 1/3/2024 1:22:12 PM 5376 svchost
127.0.0.1 1900 1/3/2024 1:22:12 PM 5376 svchost
0.0.0.0 500 1/1/2024 3:19:58 PM 7608 svchost
192.168.229.1 138 1/3/2024 12:41:49 PM 4 System
192.168.217.1 138 1/3/2024 12:41:49 PM 4 System
192.168.122.201 138 1/3/2024 12:42:04 PM 4 System
192.168.24.3 138 1/3/2024 1:22:12 PM 4 System
172.21.240.1 138 1/1/2024 3:19:47 PM 4 System
192.168.229.1 137 1/3/2024 12:41:49 PM 4 System
192.168.217.1 137 1/3/2024 12:41:49 PM 4 System
192.168.122.201 137 1/3/2024 12:42:04 PM 4 System
192.168.24.3 137 1/3/2024 1:22:12 PM 4 System
172.21.240.1 137 1/1/2024 3:19:47 PM 4 System
0.0.0.0 123 1/3/2024 1:22:57 PM 13028 svchost
0.0.0.0 69 1/1/2024 3:19:59 PM 7868 SolarWinds TFTP Server
(only selected entries are shown)
Let's repeat for TCP sessions. Note that because TCP uses the concept of sessions, we have some new information here: information about the remote end, as well as the connection state. You can also add or select based on the "AppliedSetting" value (ie: Internet, Datacenter, Compat, Custom). I don't normally care about that - if it's listening or connected that's the thing for me.
Get-NetTCPConnection | select-object LocalAddress,LocalPort,RemoteAddress,RemotePort,State,CreationTime,OwningProcess, @{Name="Process";Expression={(Get-Process -Id $_.OwningProcess).ProcessName}} | ft -auto
LocalAddress LocalPort RemoteAddress RemotePort State CreationTime OwningProcess Process
------------ --------- ------------- ---------- ----- ------------ ------------- -------
127.0.0.1 23659 0.0.0.0 0 Listen 1/3/2024 7:56:00 AM 25660 vpnui
127.0.0.1 22272 0.0.0.0 0 Listen 1/2/2024 6:36:20 PM 25660 vpnui
127.0.0.1 22186 0.0.0.0 0 Listen 1/2/2024 6:28:17 PM 25660 vpnui
127.0.0.1 22185 0.0.0.0 0 Listen 1/2/2024 6:33:47 PM 25660 vpnui
127.0.0.1 22020 0.0.0.0 0 Listen 1/2/2024 6:21:54 PM 25660 vpnui
127.0.0.1 22019 0.0.0.0 0 Listen 1/2/2024 6:30:36 PM 25660 vpnui
127.0.0.1 21486 0.0.0.0 0 Listen 1/2/2024 5:57:59 PM 25660 vpnui
127.0.0.1 20682 127.0.0.1 20681 TimeWait 12/31/1600 7:00:00 PM 0 Idle
192.168.122.201 20676 75.2.13.80 443 Established 1/3/2024 4:36:10 PM 26244 firefox
192.168.122.201 20674 151.101.126.132 443 Established 1/3/2024 4:36:10 PM 26244 firefox
192.168.122.201 20673 104.88.157.163 443 Established 1/3/2024 4:36:10 PM 26244 firefox
192.168.122.201 20663 104.88.157.87 443 Established 1/3/2024 4:36:08 PM 6584 msedgewebview2
192.168.122.201 20662 184.150.70.65 443 Established 1/3/2024 4:36:08 PM 6584 msedgewebview2
192.168.122.201 20661 184.150.70.65 443 Established 1/3/2024 4:36:08 PM 6584 msedgewebview2
192.168.122.201 20651 40.99.227.18 443 Established 1/3/2024 4:35:02 PM 32504 OUTLOOK
192.168.122.201 20517 159.127.43.82 443 Established 1/3/2024 4:26:08 PM 26244 firefox
192.168.122.201 20513 104.36.115.111 443 Established 1/3/2024 4:26:08 PM 26244 firefox
192.168.122.201 20451 40.99.227.18 443 Established 1/3/2024 4:21:31 PM 6584 msedgewebview2
192.168.122.201 20169 34.107.243.93 443 Established 1/3/2024 4:10:31 PM 26244 firefox
127.0.0.1 20161 127.0.0.1 20160 Established 1/3/2024 4:10:30 PM 31432 firefox
127.0.0.1 20160 127.0.0.1 20161 Established 1/3/2024 4:10:30 PM 31432 firefox
127.0.0.1 20159 127.0.0.1 20158 Established 1/3/2024 4:10:30 PM 26244 firefox
127.0.0.1 20158 127.0.0.1 20159 Established 1/3/2024 4:10:30 PM 26244 firefox
A common problem in firewalls is dealing with long-lived, TCP sessions, for instance backups that last long enough for the firewall to kill them for reaching a maximum connection lifetime. Let's use the information we have to look for the longer-lived sessions on this machine. We'll add another expression, and change the CreationTime to lifetime of the connection (in seconds), and sort that lifetime from newest to oldest. Since we're looking for just the longest-lived connections, we'll pipe this through select and ask for just the last 10 entries:
$now = get-date
Get-NetTCPConnection | select-object LocalAddress,LocalPort,RemoteAddress,RemotePort,State,@{Name="LifetimeSec";Expression={($now-$_.CreationTime).seconds}},OwningProcess, @{Name="Process";Expression={(Get-Process -Id $_.OwningProcess).ProcessName}} | sort-object -property LifetimeSec | select-object -last 10 | ft -auto
LocalAddress LocalPort RemoteAddress RemotePort State LifetimeSec OwningProcess Process
------------ --------- ------------- ---------- ----- ----------- ------------- -------
127.0.0.1 49817 127.0.0.1 49818 Established 55 17936 atmgr
127.0.0.1 49822 127.0.0.1 49821 Established 55 17936 atmgr
0.0.0.0 49816 0.0.0.0 0 Bound 55 17936 atmgr
0.0.0.0 49818 0.0.0.0 0 Bound 55 17936 atmgr
0.0.0.0 49820 0.0.0.0 0 Bound 55 17936 atmgr
0.0.0.0 49822 0.0.0.0 0 Bound 55 17936 atmgr
0.0.0.0 49814 0.0.0.0 0 Bound 55 17936 atmgr
127.0.0.1 22019 0.0.0.0 0 Listen 56 25660 vpnui
127.0.0.1 30554 0.0.0.0 0 Listen 57 25660 vpnui
127.0.0.1 30454 0.0.0.0 0 Listen 57 25660 vpnui
I find representing the connection lifetime to be more useful than listing the start time most days, displaying the start time can really mess up the display (as you saw above).
If you want to run this from the CMD prompt instead of from PowerShell, stuff one or both of the commands into a file, for instance "ns.ps1", or create two scripts - ns-t.ps1 and ns-u.ps1 for TCP and UDP if that's better for your workflow.
Then create "ns.cmd:, containing:
powershell -noprofile -executionpolicy bypass c:\path\to\script\ns.ps1
Mind you, you'd thing you'd be better to run this inside of PowerShell, where you have more options to massage the data into whatever format you want, but sometimes there's nothing like getting the command just perfect (for you), so that you can bust it out with a minumum of fuss when you need it (that was the point of this whole thing after all).
Have you done something similar? Packaged up a complicated bit of powershell into a useful command that you then use daily? Please, share using our comment form.
References:
https://learn.microsoft.com/en-us/powershell/module/nettcpip/get-nettcpconnection?view=windowsserver2022-ps
https://learn.microsoft.com/en-us/powershell/module/nettcpip/get-netudpendpoint?view=windowsserver2022-ps
https://learn.microsoft.com/en-us/powershell/scripting/samples/managing-processes-with-process-cmdlets?view=powershell-7.4
Or, if you prefer SS64 to the original docs:
https://ss64.com/ps/get-process.html
https://ss64.com/ps/get-nettcpconnection.html
===============
Rob VandenBrink
[email protected]