Building A Simple Python Port Scanner
Somebody once told me “If you can’t write code, you’ll always rely on those who can.” I’ve reflected on that quite a bit over the nine or so years I’ve been working in technology plagued with imposter syndrome and here are my thoughts: If you can’t cultivate food, you’ll always rely on those who can. If you can’t build a home, you’ll always rely on those who can. If you can’t respond to medical emergencies, you’ll always rely on those who can. If you don’t have the knowledge, resources or time to do anything you’ll always rely on others and, as such, we all do. Humanity has become highly specialized and we all tend to occupy one very small niche over the duration of our careers. Information Technology is a bit of a quagmire in and of itself and, in 2020, is really just a broad umbrella term for dozens of more narrowly focused job sectors. Between support, networking, system administration, devops, security, compliance, telecom, research, teaching, forensics, technical writing, development, hardware, project management, business administration and database administration, one simply can’t expect to be a master-of-all-trades. And so, to the quote above, after all these years I would finally respond “yeah…maybe if you’re a developer.”
I got started in IT because I loved the technology and the creative challenges it presented, but I never had much of a passion for the code running behind the scenes. I’ve had a lot of people ask me in the past whether or not coding skills are a requirement to get into security and, to an extent, I would say no. While there are many careers in security focused on coding or code review, such as vulnerability research, there are also many that aren’t. Some of these include security operations and analysis, network security, and security engineering/architecture (I use these titles fluidly – I’ve seen both be used to describe infrastructure-focused roles as well as secure development roles. I’m not sure the industry has decided yet which is which). Even the less technical penetration tester roles are likely to be focused more on using available tools over creating custom exploits. Everyone’s mileage will vary and so I encourage everyone to pursue the things that interest them and find roles that make them happy. With that anecdote out of the way, I would like to suggest that learning some scripting could go a long way. Automation is being pushed pretty heavily in our industry and so the ability to eliminate tedious manual tasks is going to start separating employable candidates from not-so-desirable ones. I don’t say that to discourage people trying to break into the industry rather than just remind others that part of working in technology means weathering with changes to that technology. We never want to break out of our comfort zones, but discomfort is the catalyst for change. And change for the better is a good thing. Scripting is definitely a useful skill that can be leveraged to find higher paying jobs, but it’s also a creative hobby that people outside of IT learn for fun. I’ve yet to reach that point of bliss, but I have accepted the challenge of learning to code and, honestly, using the ideas in your head as building blocks to create a working application is pretty cool. I recently chose Python 3 as my first language and have spent the last few months gaining an understanding of it. This article will discuss how to build a simple port scanner in Python.
At its very core, a port scanner is just that – you give it a target and it attempts to communicate over networking ports. If communication times out it knows that the port is closed. If it receives data back, it knows the port is open. Port scanning has a number of uses from reconnaissance to network troubleshooting and any technology professional should know how to use them. For our Python port scanner we’ll start by importing a few modules we’ll need for the project to run:
The socket module is obviously going to be used to access network services, re allows us to perform input validation using regular expression, and the ipaddress module will allow us to accept user input and pass them as network addresses. With that said, the first thing we’ll need for our port scanner is our target:
In our get_target() function, we prompt a user to input the IP of the host they want to scan. A while loop ensures the user feeds the program a properly formatted IP address and will continually reissue the prompt until the validation check is passed. Lastly, we translate the user input into an IP address and hold that IP in memory.
So now we have our target. Logically the next piece we’ll need is our ports. I wanted to build the functionality into my program where the user could specify what ports or port range they were interested in and came up with the following function:
At the top again we prompt the user for input, but this time we give them the option of selecting the common port range, all TCP ports, or specific ports of their choosing separated by commas. The if/else loop that follows instructs the computer on how to handle our user’s input. If they specify option 3 to scan only specific ports they are added to a list for later use. Similar to our get_target() function, anything ingested other than expected input will deliver an error message and force the user to the beginning of the function.
The last thing we need for our scanner to work is the actual scanning capability. Using the information already collected we want to then communicate with our target to determine what ports are open. This allows us to gain a better understanding of the device, what software it might be running and what services it’s providing:
The first thing I did was alter the default period of time in seconds my scanner will wait before assuming a port is closed. Without specifying this I found my scanner was quite generous in waiting for communication back over each port and quickly decided that, with 65,000 ports, a slow scanner is an inefficient one. I found that the socket module did not actually need much time at all to make this determination and set the timeout to .00001 seconds for each port. The scan() function will iterate through both of our previous functions to store our target and ports in memory for use, which we then pass to our try statement. We have the program attempt connection over each port, where it passes the connection attempt to a result variable. At the very bottom of the program is an except clause I used to troubleshoot the result of each connection attempt simply by printing the result variable. I found I was receiving either “0” or “10050” as the output. A quick DuckDuckGo search led me to discover these as Winsock errors, where “0” meant the connection attempt was successful and “10050” meant the network was down (i.e. the port is closed). With this knowledge I then constructed my if/else loop where the appropriate message was delivered to the user based on the result of the connection attempt. At the very end I call my scan() function to execute the program:
Limitations/Revision Plans
The most obvious limitation of my scanner so far is that it only accepts a single IP address as a target. I’m working through some issues with the ipaddress module that would allow me to scan an entire subnet through the use of CIDR notation. Something like this:
I also plan on adding the functionality to resolve IP addresses to host names, perform banner grabbing for further target reconnaissance, and cleaner parsing of scan results. Suggestions for accomplishing this are welcome and can be sent via email to crusec@protonmail.ch. You can also follow along with the project as it evolves here.