Have you ever wanted to build a port scanner? Maybe you realized: whoa, it can be kind’a slow.
Simply going through each port, one by one, can be a very slow process for one, lonely thread to manage. Maybe the connection will even get hung up on a port and that’ll annoying and cost you time as well.
Have you ever heard of multithreading before? We can spool up a bunch of work for some threads to perform our port scan. We could talk about concurrency and how it’s not parallelism and whatever, but — let’s not. Let’s just build a port scanner for now.
Using Ruby, it’s pretty straightforward to build an application to do this; and in under 100 lines of code, we’ll have ourselves a decently fast lil’ port scanner without all that much fuss.
We’ll need two gems from the Ruby standard library to get ourselves started.
socket which will perform the actual network connections, and
thread which will provide some rich threading features like the Mutex and Queue classes.
Quick Threading Background
If you’re pretty unfamiliar with threading, that’s ok! I’ll try to give a lil’ break down what a Mutex and Queue is and why I’m using them. It helps to know how they work.
A Mutex will provide a way to synchronize the behavior of our threads when accessing shared resources. In the case of this port scanner, in the form a command-line application, we’ll want a clean STDOUT experience without having threads print over one another and ignoring essential newline characters for clean output.
A Queue will provide a way for our threads to have a shared pool of jobs that need to get done. This acts like a thread safe Array which which can be accessed by threads to place and get jobs from. For our case, our jobs will be simply doing Socket connections for a given IP address to a given port.
Knowing How to Finish the Job
What we’ll do now before we’ll go into actually building out our own
PortScanner class is build up a class which will signal to our PortScanner threads that they’ve done their job and can exit. This is essentially an empty class in one line of code, but is very helpful for signaling the end of an operation to our threads. We should be have one these at the end of our Queue for each thread accessing the job pool.
It’s basically the threads signal to go home for the day.
Initializing the Port Scanner
Let’s create a basic class
PortScanner that accepts to keyword arguments
ports which will be the IP address or URL we’ll connect to and port range that will be scanned on that host. We’ll write the actual connection method in a minute.
Basically what we’re writing for the
initialize method is what will happen when we use
PortScanner.new host: "example.com", ports: (1..2014) to create a new instance of the scanner.
Notice how I’ve created a Mutex when the
PortScanner class is created. I will need to use it later. I’m also put the
ports arguments into their own instance variables to be accessed later by the rest of the methods in the class when needed. The
ports argument also has a default range similar to what you’ll find with port scanners like nmap. This means the class can be initialized without a
ports argument to simply use the default like so:
PortScanner.new host: "example.com"
Just two more methods to write and we’ll have ourselves a
PortScanner class. Let’s do that!
Now, please don’t be too afraid by the next method ( there’s room for refactoring in this tutorial, but don’t worry ):
There’s quite a bit of caution being used in this method with 3
rescue blocks and one to
ensure the socket is closed.
We’re just putting all that code into a Proc which will be processed later when we call it after it’s spool’d up and spool’d out of a job queue in the next method.
So, finally, the method to do the actual scanning!
This method will create it’s job pool, spool up the jobs in its own thread, and then spin up
10 threads by default to process the job pool and wait for it to finish.
To actually use our
PortScanner class could be something like this now to use 5 threads to scan our target host:
Run the code:
$ ruby port_scanner.rb
There you have it!
And Like that, we have ourselves a pretty slick lil’ port scanner, if I do say so myself! With some command-line arguments and a lil more pizzazz, we may have something pretty cool here.
I’d recommend you play around some more with Ruby, concurrency, threading, thread pools, port scanning, and all that fun stuff! It’s, well… fun!
There’s probably even better ways to write a threaded port scanner! So, experiment, benchmark ( with a grain of salt and plenty of scrutiny ) and have fun!
Go make sure your ports are all closed and what not. 🤣
Basically, you’ll hit limits somewhere, I’m sure. But, I’ve used this code to scan 1024 ports on a remote host in 1.2 seconds using 240 threads. So, that’s neat.
Verifying / Filtering Output
Some basic stuff I was using to verify and filtering the output:
Counting lines to verify I’ve scanned every port in my given range:
$ ruby port_scanner.rb | wc -l
Just checking for Open ports:
$ ruby port_scanner.rb | grep "Open"
Just checking for Closed ports:
$ ruby port_scanner.rb | grep "Closed"