thumbnail

Writing A Simple Ruby Script To Automate Nmap

from development on 2018-10-29 ↩ back

An nmap scan should always be the first thing to do when you start a box, and since I'm too lazy to write nmap -sV -sC -oA initial box_ip (and I want a progress bar instead of having to constantly press a button to see progress), we're just going to write a script to do it for us.

I'm using Ruby for this because I think it's just as readable as Python (so I don't have to explain as much) and because I'm not bothered to learn how to do this in Python, which means that this is the first time I've ever shown code on this website that isn't Python.

Anyway, to do this we will use a class from Ruby's standard library called PTY. PTY allows you to spawn an external process and then interact with that process by using puts to write to it's stdin and gets to read from it's stdout.

#!/usr/bin/ruby

require 'pty'
cmd = "nmap -sV #{ARGV[0]}"

PTY.spawn( cmd ) do |stdout, stdin, pid|
  loop do
    stdin.puts ' '
    puts stdout.gets.chomp
    sleep 0.1
  end
end

This is our initial code. It runs nmap with the box IP as an argument and every 0.1 seconds, it sends a space character to stdin and prints stdout so we can see the progress of the scan. This works but right now we are only running nmap with -sV, so now we should add all the arguments we need.

#!/usr/bin/ruby

require 'pty'
require 'fileutils'

FileUtils.mkdir_p 'nmap'
cmd = "nmap -sV -sC -oA nmap/initial #{ARGV[0]}"

PTY.spawn( cmd ) do |stdout, stdin, pid|
  loop do
    stdin.puts ' '
    puts stdout.gets.chomp

    running = %x[ ps -p #{pid} -o comm= ]
    if running.include? "defunct"
      break
    end

    sleep 0.1
  end
end

Now we are creating a folder where all the nmap scripts will be stored using the library 'fileutils' and we've edited the 'cmd' variable to use all the arguments. I've also added a check to see if nmap has finshed in order to break out of the loop by checking if the process name includes the words 'defunct'.

If you run this you will see that stdout becomes very messy as the progress is constantly being called. Let's get that progress bar working.

#!/usr/bin/ruby

require 'pty'
require 'fileutils'
require 'progress_bar'

FileUtils.mkdir_p 'nmap'
cmd = "nmap -sV -sC -oA nmap/initial #{ARGV[0]}"

$syn_bar = ProgressBar.new
$srv_bar = ProgressBar.new
$nse_bar = ProgressBar.new

$syn_progress = 0
$srv_progress = 0
$nse_progress = 0

$step = "init"

def increment_bar(stdout)
  new_status = stdout.match(/[[:digit:]]{1,2}\.[[:digit:]]{2}/)
  new_status ? new_status = new_status[0].to_i : return

  if stdout.include? "SYN Stealth Scan"
    if $step != "syn"
      puts "Step 1/3 [SYN Stealth Scan]"
      $step = "syn"
    end

    inc_amount = new_status - $syn_progress
    $syn_progress = new_status
    $syn_bar.increment! inc_amount
  elsif stdout.include? "Service"
    if $step != "srv"
      puts "Step 2/3 [Service Scan]"
      $step = "srv"
    end

    inc_amount = new_status - $srv_progress
    $srv_progress = new_status
    $srv_bar.increment! inc_amount
  elsif stdout.include? "NSE Timing"
    if $step != "nse" && $step != 'init'
      # NSE Timing shows up before it actually begins
      puts "Step 3/3 [NSE]"
      $step = "nse"
    end

    inc_amount = new_status - $nse_progress
    $nse_progress = new_status
    $nse_bar.increment! inc_amount
  end
end

PTY.spawn( cmd ) do |stdout, stdin, pid|
  loop do
    stdin.puts ' '
    response = stdout.gets.chomp

    increment_bar(response)

    running = %x[ ps -p #{pid} -o comm= ]
    if running.include? "defunct"
      break
    end

    sleep 0.1
  end
end

This is a really quick and dirty way of getting a progress bar using the library progress_bar. Make sure to install it with:

gem install progress_bar

Obviously I'm not super proud this script, it's actually pretty terrible for my standards, so much so that I don't want to explain it. But it does what I wanted it to and only took me 5 minutes to write. Someday I may comeback to it to clean up all the repeating code and stop using so many global variables (but I say that about all the code I write, so whatever).

Now I can move save this as /opt/scan-box and call it with /opt/scan-box box_ip. You could also put it in /bin so you can call it with just scan-box box_ip, but I don't like doing that.

Subscribe

Want to get an email every time I post something? Enter your email address below.

I promise I won't spam you.