The Basics
First of all, what is a port scanner? It is exactly what it sounds like; it takes in an IP address, and tells you which ports are “open”, i.e. on which ports the computer is ready to communicate. What is the point of scanning ports? Open ports can pose major security risks, especially if the administrator doesn’t know why exactly they have been left open. The program at the other end may be an ancient version of some server software that has a ton of publicized buffer overflow attacks, making the machine an easy target for attackers. So, knowing what ports you have open is important. Finally, how do we know if ports are open or not? There’s actually a ton of ways to do this, because TCP/IP (the protocol that computers usually use to talk to one another) provides a lot of different ways (all with different advantages and disadvantages) to check a port. The simplest one (and the one we will be using) is called “TCP connect scanning”. The idea is to try to connect to a host on a specified port, if the host responds, the port is open, otherwise it isn’t. This approach does have disadvantages. If you’re an attacker, the host will probably log the request. Secondly, it is much less efficient than some other methods (such as a TCP SYN scan). Our port scanner will employ a simple technique; it will take a range of ports and a host, then try to connect to the host on each of the given ports. Ports are open on whichever connections are successful. Let’s get into the code.The Simple Method
We’re going to be using the socket library provided by Ruby in order to accomplish our ends. Here’s the code:require 'socket' #socket library
def open_port(host, port)
sock = Socket.new(:INET, :STREAM)
raw = Socket.sockaddr_in(port, host)
puts "#{port} open." if sock.connect(raw)
rescue (Errno::ECONNREFUSED)
rescue(Errno::ETIMEDOUT)
end
def main(host, start_port, end_port)
until start_port == end_port do
open_port(host, start_port)
start_port += 1
end
end
main ARGV[0], ARGV[1].to_i, ARGV[2].to_i
As you can tell, the code is very simple. Before we go in to a bit of detail, you can run the scanner on your own machine:
ruby scanner.rb localhost 1 100
That will run the scanner on localhost (i.e. the machine you ran the code on), checking all ports from 1 to 100, inclusive.
The code takes the address to scan as the first argument, the starting port as the second argument and the ending port as the third one and prints the ports that are open. Let’s breakdown the code to see how it does this.
First of all, use the ARGV
array to read the arguments off the command line. Pass these arguments into the main method, which then simply loops through the required ports and checks if they are open with the open_port
method (which also prints the output).
open_port
functions by, as discussed, opening a connection to the machine we’re scanning at a specified port to see if it responds. But, this implementation has a few issues. It waits until a port is scanned before moving to the next one. However, it clearly doesn’t need to; the order that we check the ports in has no relevance to the results. So, let’s try writing an a version of the port scanner that doesn’t wait.
Taking It Another Step
How do we do this? We obviously need some way to delegate tasks so that they can be done outside the “normal” course of things, ie. we don’t have to wait around for them. This is usually called concurrency. There’s a few problems with doing concurrency in Ruby. First of all, the “official” (meaning, MRI) implementation of Ruby implements a system where separate “threads” (kind of like processes, except more lightweight) can’t run at the same time; they all run under one thread. That means that two things cannot occur at exactly the same moment. As such, we can’t scan two ports at once. However, this doesn’t mean that Ruby is useless in this case – if we split up our ports into different threads, those threads can still be scheduled to run while we are waiting on other sockets. How are we going to deal with all this threading? I’m personally a fan of the Celluloid library for Ruby, which uses the Actor model to make threading a breeze. In fact, I like it so much that I even wrote an article about it, which you should probably read since I’ll be using Celluloid in the rest of this article. Here’s a very quick n’ dirty implementation that we’re going to improve on:require 'celluloid'
require 'socket'
class ScanPort
include Celluloid
def initialize(port, host)
@port = port
@host = host
end
def run
begin
sock = Socket.new(:INET, :STREAM)
raw = Socket.sockaddr_in(@port, @host)
puts "#{@port} open." if sock.connect(raw)
rescue
if sock != nil
sock.close
end
end
end
end
def main
host = ARGV[0]
start_port = ARGV[1].to_i
end_port = ARGV[2].to_i
until start_port == end_port do
sp = ScanPort.new start_port, host
sp.async.run
start_port += 1
end
end
main
Wow, that seems quite complicated at first glance. Actually, we’ve reused a lot of code from our earlier version. First of all, we define a class called ScanPort
which will be used to spawn “actors” (basically, objects in threads). Notice the include
line within the class; this essentially makes the class an actor.
We have the run
method within the ScanPort
class (I just chose to call it run
; we could have called it anything we wanted) It does essentially the same thing as the openport
method in the earlier version, except that it closes the socket immediately after checking the port. We do this so that the number of open sockets does not exceed the system limit.
The main
method is quite simple. The most important line is line 36 – notice we don’t call sp.run
, instead, it is sp.async.run
. This calls the run
method asynchronously, i.e. we move on without waiting for it to end.
Another important point to note is that the code sample omits a lot of error handling for the sake of brevity and simplicity – it should definitely use some kind of supervision group (that’s a Celluloid concept).
When run this gives roughly the same result as earlier. You might notice that it runs in about the same or more time than the synchronous version! The thing is, we are starting a thread for every single port in the range we provide – that takes up time and resources! What if, instead, we allotted each actor with only a range of ports to check? That would mean less threads, so less time taken starting up and shutting down threads! Let’s give it a go:
require 'celluloid'
require 'socket'
class ScanPort
include Celluloid
def initialize(start_port, end_port, host)
@start_port = start_port
@end_port = end_port
@host = host
end
def run
until @start_port == @end_port do
scan @start_port
@start_port += 1
end
end
def scan(port)
begin
sock = Socket.new(:INET, :STREAM) #build the socket
raw = Socket.sockaddr_in(port, @host)
puts "#{port} open." if sock.connect(raw)
rescue
if sock != nil
sock.close
end
end
end
end
def main
host = ARGV[0]
start_port = ARGV[1].to_i
end_port = ARGV[2].to_i
segment_size = 100
until start_port >= end_port do
sp = ScanPort.new start_port, start_port + segment_size, host
sp.async.run
start_port += segment_size
end
end
main
The code is largely the same, however, we have added a segment_size
variable, which you can change depending on how many ports you want each actor to process. Setting this number too high will mean that there will be little concurrency, setting it too low will mean that a lot of time is spent just creating and destroying threads. I ran this from 1 to 2000 ports on my machine and it turned out roughly 50% of the real time of the synchronous version. Obviously, there’s a ton of factors that affect this, but that’s the general idea.
If you want your code to run with “real” concurrency, i.e. with threads running at exactly the same time as others, you’ll have to pick an implementation of Ruby that doesn’t have the “everything under single thread” issue. JRuby is a popular choice.
Wrapping it Up
Port scanners are incredibly interesting pieces of software. Hopefully, you enjoyed creating a version of a port scanner (albeit quite simple) in Ruby.Frequently Asked Questions about Building a Port Scanner in Ruby
What is a port scanner and why is it important?
A port scanner is a software application designed to probe a server or host for open ports. These open ports are doorways into a computer’s systems and are often used by hackers to gain unauthorized access. Therefore, a port scanner is an essential tool in network security as it helps identify open ports and vulnerabilities that could be exploited by attackers.
Why should I use Ruby to build a port scanner?
Ruby is a high-level, interpreted programming language that is known for its simplicity and productivity. It has a clean syntax that is easy to read and write, making it a great choice for building a port scanner. Moreover, Ruby has a rich set of built-in functions and libraries, which can simplify the process of creating complex network applications like a port scanner.
How does a port scanner work in Ruby?
A port scanner in Ruby works by establishing a connection to a specific port on a host or server. If the connection is successful, it means the port is open. If not, the port is closed. This is done using Ruby’s Socket library, which provides methods for creating and manipulating network sockets.
Can I use a port scanner to scan multiple hosts at once?
Yes, you can. By using Ruby’s threading capabilities, you can create a multi-threaded port scanner that can scan multiple hosts simultaneously. This can significantly speed up the scanning process, especially when scanning a large network.
What is Nmap and how does it compare to a Ruby port scanner?
Nmap, or Network Mapper, is a free and open-source tool for network discovery and security auditing. It can be used to detect hosts and services on a computer network, thus creating a “map” of the network. While Nmap is a powerful tool, a Ruby port scanner can be a simpler and more customizable alternative, especially for those already familiar with Ruby programming.
How can I protect my network from port scanning?
There are several ways to protect your network from port scanning. One is to use a firewall to block incoming connections from unknown sources. Another is to regularly update and patch your systems to fix any known vulnerabilities. Regularly monitoring your network for unusual activity can also help detect and prevent port scanning.
Can I use a port scanner to scan for specific services?
Yes, you can. By specifying the port numbers associated with specific services, you can use a port scanner to check if those services are running on a host or server. For example, you can scan port 80 to check if a web server is running, or port 22 to check for an SSH server.
Is it legal to use a port scanner?
The legality of using a port scanner can vary depending on your location and the intent behind the scanning. In general, it is legal to use a port scanner for legitimate, authorized purposes, such as network security testing. However, using a port scanner to find vulnerabilities with the intent to exploit them is illegal.
How can I optimize the performance of my Ruby port scanner?
There are several ways to optimize the performance of your Ruby port scanner. One is to use threading to scan multiple ports or hosts simultaneously. Another is to adjust the timeout value to reduce the time spent waiting for a response from each port. You can also use a technique called “port knocking” to scan for a sequence of ports in a specific order.
Can I integrate my Ruby port scanner with other security tools?
Yes, you can. Ruby’s flexibility and interoperability make it easy to integrate your port scanner with other security tools. For example, you can use Ruby’s Net::HTTP library to send the results of your port scan to a web server, or use the JSON library to format the results for easy parsing by other tools.
I'm a developer, math enthusiast and student.