Posted:05/07/2015 12:00PM
Mike Mclain discusses how to use python to make Windows port mapping more useful
Last night (as i was perusing reddit) my internet connectivity inexplicably stopped. While i am no stranger to identifying and debugging network problems, this particular occurrence peeked my curiosity because of my sudden inability to access local or LAN addresses (like 127.0.0.1 or 192.168.1.1 respectively), which is the hallmark of a PC related issue (like driver problems, PC hardware problems, virus, et cetera).
Conversely, upon regaining network and internet connectivity (noting that everything went back to normal after around five minutes), i decided to snoop around in my ASUS RT-AC68R router's system logs in order to try and determine if something nefarious was aloof or if my PC just happened to encounter a natural bug / buffer overload.
Likewise, after examining the system logs and finding a few active connections that felt somewhat suspicious, i was left with the task of trying to track down the source of these active connections on my PC when i hit a bit of a snafu in resolving the network connections to their corresponding application name.
To elaborate further, the most common method of examining active network connections on a Window's PC (particular for a Windows Vista or Windows 7 operating system) is to either utilize the GUI Task Manager Resource Monitor or the Terminal (CMD.exe) command netstat -aon
. Conversely, while the GUI approach will not be examined further, the execution of the netstat -aon
command yields a console output similar to:
Active Connections Proto Local Address Foreign Address State PID TCP 0.0.0.0:80 0.0.0.0:0 LISTENING 14232 TCP 0.0.0.0:81 0.0.0.0:0 LISTENING 1836 TCP 0.0.0.0:135 0.0.0.0:0 LISTENING 388 TCP 0.0.0.0:443 0.0.0.0:0 LISTENING 14232 TCP 0.0.0.0:444 0.0.0.0:0 LISTENING 1836 TCP 0.0.0.0:445 0.0.0.0:0 LISTENING 4 TCP 0.0.0.0:554 0.0.0.0:0 LISTENING 5504 TCP 0.0.0.0:1170 0.0.0.0:0 LISTENING 7124 TCP 0.0.0.0:2869 0.0.0.0:0 LISTENING 4 TCP 0.0.0.0:2968 0.0.0.0:0 LISTENING 5936 TCP 0.0.0.0:4242 0.0.0.0:0 LISTENING 2092 TCP 0.0.0.0:5357 0.0.0.0:0 LISTENING 4 TCP 0.0.0.0:6800 0.0.0.0:0 LISTENING 8020 TCP 0.0.0.0:6802 0.0.0.0:0 LISTENING 8020 TCP 0.0.0.0:10243 0.0.0.0:0 LISTENING 4 TCP 0.0.0.0:27036 0.0.0.0:0 LISTENING 19484
and both approaches only provide the application's Process ID (PID), which is not immediately helpful in determining if a connection is malicious or not without further investigation.
Likewise, in order to aid in this determination, either the GUI Task Manager Resource Monitor (which will not be discussed) or the Terminal (CMD.exe) command tasklist /v
(which produces output similar to:
Image Name PID Session Name Session# Mem Usage Status User Name CPU Time Window Title ================================================================================================================================================= System Idle Process 0 Services 0 24 K Unknown NT AUTHORITY\SYSTEM 1401:41:10 N/A System 4 Services 0 23,556 K Unknown N/A 9:45:53 N/A smss.exe 328 Services 0 276 K Unknown N/A 0:00:00 N/A csrss.exe 592 Services 0 3,388 K Unknown N/A 0:01:33 N/A csrss.exe 668 Console 1 43,248 K Running N/A 0:33:06 N/A wininit.exe 676 Services 0 764 K Unknown N/A 0:00:00 N/A winlogon.exe 732 Console 1 2,600 K Unknown N/A 0:00:00 N/A services.exe 780 Services 0 9,684 K Unknown N/A 0:01:03 N/A lsass.exe 788 Services 0 11,164 K Unknown N/A 0:09:42 N/A lsm.exe 796 Services 0 2,464 K Unknown N/A 0:00:12 N/A svchost.exe 900 Services 0 7,488 K Unknown N/A 2:18:17 N/A nvvsvc.exe 984 Services 0 6,268 K Unknown N/A 0:00:03 N/A nvSCPAPISvr.exe 1008 Services 0 2,024 K Unknown N/A 0:09:28 N/A svchost.exe 388 Services 0 8,612 K Unknown N/A 0:00:56 N/A
) must be utilized to associate the obtained PID (via netstat
) to an executable name. While this approach is not particularly difficult (noting that grep
ers can perform some plumbing and obtain meaningful results with some success); however, such complexities feel overly cryptic given the fundamental usefulness and general application of this particular task (a notion that is supported by the innate improvements found in the Windows 8 Task Manager).
Nevertheless, such inherent difficulties (in performing this relatively straightforward task) annoyed me to the point of writing a python script that would execute and process the results from the netstat -aon
command and then associate the PID obtained with its corresponding executable name (via executing and processing the tasklist /v
command), and then returning the result obtained in an easy to read format (similar to:
Port RemoteIP PID Executable Name User Title ===================================================================== 80 0.0.0.0 14232 Skype.exe Desktop\User N/A 81 0.0.0.0 1836 httpd.exe N/A N/A 135 0.0.0.0 388 svchost.exe N/A N/A 443 0.0.0.0 14232 Skype.exe Desktop\User N/A 444 0.0.0.0 1836 httpd.exe N/A N/A 445 0.0.0.0 4 System N/A N/A 554 0.0.0.0 5504 wmpnetwk.exe N/A N/A 1170 0.0.0.0 7124 PlexDlnaServer.exe Desktop\User N/A 2869 0.0.0.0 4 System N/A N/A 2968 0.0.0.0 5936 EEventManager.exe Desktop\User Epson Event Manager Background 4242 0.0.0.0 2092 CrashPlanService.exe N/A N/A 5357 0.0.0.0 4 System N/A N/A
).
Likewise, in order to obtain this objective, the following python code was created:
# -*- coding: utf-8 -*- """ A Simplistic Python Script to map the results obtained from the netstat command with the application running them. """ # Used to run console commands from python from subprocess import check_output # Used to create regular expressions import re def id_netstat_processes(): """ A Simplistic Python Script to map the results obtained from the netstat command with the application running them. """ # First Run netstat to get network connections # Options # a Displays all active TCP connections and the TCP and UDP ports on which # the computer is listening. # o Displays active TCP connections and includes the process ID (PID) for each # connection. You can find the application based on the PID on the Processes # tab inWindows Task Manager. This parameter can be combined with -a, -n, and -p. # n Displays active TCP connections, however, addresses and port numbers are # expressed numerically and no attempt is made to determine names. result = check_output("netstat -aon", shell=True) # Now make an array of terms to remove from the data obtained from netstat clean_up_array = [ ("Active Connections", ""), ("Proto", ""), ("Local Address", ""), ("Foreign Address", ""), ("State", ""), ("PID", ""), ("\r", ""), ("\t", " ") ] # Remove terms from the netstat data for find, replace in clean_up_array: result = result.replace(find, replace) # * Because I am feeling feisty, I will utilize an evil regular expression to extract the # netstat information via regex groups via the () command # # * Likewise Because terminal output is space padded rather than tabbed we need to account # for variable spacing via the regex (space)* or ' *' expression # # * Becuase the 1st group is either UDP or TCP use (UDP|TCP) to find either or # # * Because the 2ed group is either a IPV4 xxx.xxx.xxx.xxx or IPV6 XXXX::XXXX::XXXX::XXXX::XXXX%xx address or [::] # Use [0-9]*\\.[0-9]*\\.[0-9]*\\.[0-9]* for any or none number length with dots or # [ *[a-z0-9]*:* *[a-z0-9]*:* *[a-z0-9]*:* *[a-z0-9]*:* *[a-z0-9%]*\\] for any or none letters or numbers of : until the port : # # * Because the 3ed group is the port use any number [0-9]* # # * Because the 4th group is remote ip (i only had ipv4) or *:* use [0-9]*\\.[0-9]*\\.[0-9]*\\.[0-9]*|\\[:*\\] or \\* # # * Because the 5th group is the remote ip use any number [0-9]* # # * Because the 6th group is the status use LISTENING|ESTABLISHED|TIME_WAIT|CLOSE_WAIT with any or none group find # # * Becuase the 7th group is pid use any number [0-9]* # a ugly regex string that extracts the required information into groups... does not support IPV6 remote address atm but supports local IPV6 reexstring = " *(UDP|TCP) *([0-9]*\\.[0-9]*\\.[0-9]*\\.[0-9]*|\\[ *[a-z0-9]*:* *[a-z0-9]*:* *[a-z0-9]*:* *[a-z0-9]*:* *[a-z0-9%]*\\]):([0-9]*) *([0-9]*\\.[0-9]*\\.[0-9]*\\.[0-9]*|\\[:*\\]|\\*):(\\*|[0-9]*) *(LISTENING|ESTABLISHED|TIME_WAIT|CLOSE_WAIT)* *([0-9]*)" # Build the regex string regexcompiled = re.compile(reexstring) # Process the input items = regexcompiled.finditer(result) networkitems = [] # Loop thru the results for match in items: # we could just do data.append((match.groups())) but do this for user Configurability # Extract and trim the data obtained networktype = match.group(1).strip() localip = match.group(2).strip() localport = match.group(3).strip() remoteip = match.group(4).strip() remoteport = match.group(5).strip() # Because status can be None we need to check for None if not match.group(6) is None: status = match.group(6).strip() else: status = "" pid = match.group(7).strip() # Append items to an array for future processing networkitems.append(( localip, localport, remoteip, remoteport, status, pid)) # At this point we are ready to get a list of all PID running tasklist = check_output("tasklist /v", shell=True) # Again our console result needs to be cleaned up prior to processing clean_up_array = [ ("Image Name", "" ), ("PID", "" ), ("Session Name", "" ), ("Session#", "" ), ("Mem Usage", "" ), ("Status", "" ), ("User Name", "" ), ("CPU Time", "" ), ("Window Title", "" ), ("=",""), ("\r", ""), ("\t", " ") ] # Remove terms from the tasklist data for find, replace in clean_up_array: tasklist = tasklist.replace(find, replace) # Because application names can have spaces in them , our regex becomes a touch more complex and requires look aheads via ? # Likewise, because the terminal is heavily space padded we can safely assuming two spaces will end each segment # thus, ' *' (or space space *) and ' *' (space space space *) are used in the look ahead as group stoppers # Beyond this, wildcard (.*) for any characters are used for group extraction regexstring2 = "^(.*?) *([0-9]*) *(.*?) *([0-9]*) *([0-9,]* .) *(.*?) *(.*?) *([0-9:]*) *(.*?) " # To make life easier, a dictionary will be utilized to lookup the pid tasks = {} # Build the regex string And allow for multiline processing regexcompiled2 = re.compile(regexstring2, re.MULTILINE) # Process the input items = regexcompiled2.finditer(tasklist) # Loop thru the results for match in items: # Extract and trim the data obtained imagename = match.group(1).strip() pid = match.group(2).strip() # Sometimes this approach yields an empty string at the start check for this and continue if found if pid == '': continue sessionname = match.group(3).strip() sessionnumber = match.group(4).strip() memory = match.group(5).strip() status = match.group(6).strip() user = match.group(7).strip() cputime = match.group(8).strip() title = match.group(9).strip() # Populate our dictionary with information tasks[pid] = (imagename,sessionname,sessionnumber,memory,status,user,cputime,title) # Create a variable to hold our output output = "" # Loop thru all netstat items for item in networkitems: # Extract our array object localip, localport, remoteip, remoteport, status, pid = item # See if the PID exists within our PID array if pid in tasks.keys(): # If so extract the PID information and add it to the output imagename,sessionname,sessionnumber,memory,status,user,cputime,title = tasks[pid] output += localport.ljust(10) + remoteip.ljust(20) + pid.ljust(10) + imagename.ljust(35) + user.ljust(35) + title + "\n" else: # If not report the error in the output, if this happens then the application is likely something hidden deep in # administrative privileges and googling will be required to attempt to access it. This is the domain of viruses! output += localport.ljust(10) + remoteip.ljust(20) + "PID "+ pid +" Missing" + "\n" # Dump our output to a file temp = open("netstat.txt", 'w') temp.write(output) temp.close() # This is our application entry point if __name__ == "__main__": # Run our extraction function id_netstat_processes()
and such code can be easily modified to meet the demands of your particular application or used as is.
While i am personally amazed that such functionality is not standard (at least within Windows Vista or Windows 7), utilizing python to obtained such results was a relatively straightforward task. Likewise, such approaches can be extended further, for example, through the utilization of the getopt
class (in order to add terminal command support for file saving or output formatting), or compiled into a standalone executable (for implementation into a executable terminal command ) via the py2exe
package, if so desired.
Enjoy!
By Mike Mclain