Netstat, but Better and in PowerShell, (Fri, Jan 5th)

This post was originally published on this site

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"
 This gives you the process name rather than the process ID

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:pathtoscriptns.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
rob@coherentsecurity.com

(c) SANS Internet Storm Center. https://isc.sans.edu Creative Commons Attribution-Noncommercial 3.0 United States License.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.