Build a Port Scanner in Ruby

Dhaivat Pandya

zoll, schmugel, scanner,Sometimes, we box ourselves up in our HTTP-only world. Sometimes, Rails eclipses all other facets of Ruby.

In this article, we’ll build a simple port scanner in pure Ruby, using the sockets support that comes with the Ruby distribution.

Port scanners aren’t typically things we expect Rubyists create; that’s the kind of stuff that the C people deal with. A lot of people are surprised when they find out that Metasploit , an amazing penetration suite, is actually written in Ruby.

The truth is that Ruby has grown into an incredibly versatile language, making it a tool that can be used in a wide variety of things, including something as systems-y as a port scanner.

Let’s dive right in!

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.

Win an Annual Membership to Learnable,

SitePoint's Learning Platform

  • Marc Janssen

    Nice article. It was fun playing with it. Love to read more articles dealing with fun stuff.

    Cheers,
    Marc.

    • Dhaivat Pandya

      Hi Marc,

      Thanks for the compliments!


      Dhaivat

  • http://gwk-international.com/ John Apps

    Windows appears to want 127.0.0.1 rather than localhost. The message received was
    scanner.rb:5:in `connect': An address incompatible with the requested protocol w
    as used. – connect(2) (Errno::EAFNOSUPPORT)
    from scanner.rb:5:in `open_port’
    from scanner.rb:11:in `main’
    from scanner.rb:15:in `’
    Using the numeric address worked just fine.
    Thank you for an interesting and intriguing article!

  • Jake

    Really recommend you remove “Sharing” sentences, makes me never want to come back to the site.

  • Garrett B

    Wow, I love the highlight feature you’re using on this post!