the only good website on the internet
quaso.engineering
1---
2title: Writing A Simple Ruby Script To Automate Nmap
3hero: ruby-nmap-thumb.png
4---
5
6An 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.
7
8I'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.
9
10Anyway, to do this we will use [a class from Ruby's standard library called PTY](https://ruby-doc.org/stdlib-2.4.1/libdoc/pty/rdoc/PTY.html). `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`.
11
12```ruby
13#!/usr/bin/ruby
14
15require 'pty'
16cmd = "nmap -sV #{ARGV[0]}"
17
18PTY.spawn( cmd ) do |stdout, stdin, pid|
19 loop do
20 stdin.puts ' '
21 puts stdout.gets.chomp
22 sleep 0.1
23 end
24end
25```
26
27This 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.
28
29```ruby
30#!/usr/bin/ruby
31
32require 'pty'
33require 'fileutils'
34
35FileUtils.mkdir_p 'nmap'
36cmd = "nmap -sV -sC -oA nmap/initial #{ARGV[0]}"
37
38PTY.spawn( cmd ) do |stdout, stdin, pid|
39 loop do
40 stdin.puts ' '
41 puts stdout.gets.chomp
42
43 running = %x[ ps -p #{pid} -o comm= ]
44 if running.include? "defunct"
45 break
46 end
47
48 sleep 0.1
49 end
50end
51```
52
53Now 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'.
54
55If 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.
56
57```ruby
58#!/usr/bin/ruby
59
60require 'pty'
61require 'fileutils'
62require 'progress_bar'
63
64FileUtils.mkdir_p 'nmap'
65cmd = "nmap -sV -sC -oA nmap/initial #{ARGV[0]}"
66
67$syn_bar = ProgressBar.new
68$srv_bar = ProgressBar.new
69$nse_bar = ProgressBar.new
70
71$syn_progress = 0
72$srv_progress = 0
73$nse_progress = 0
74
75$step = "init"
76
77def increment_bar(stdout)
78 new_status = stdout.match(/[[:digit:]]{1,2}\.[[:digit:]]{2}/)
79 new_status ? new_status = new_status[0].to_i : return
80
81 if stdout.include? "SYN Stealth Scan"
82 if $step != "syn"
83 puts "Step 1/3 [SYN Stealth Scan]"
84 $step = "syn"
85 end
86
87 inc_amount = new_status - $syn_progress
88 $syn_progress = new_status
89 $syn_bar.increment! inc_amount
90 elsif stdout.include? "Service"
91 if $step != "srv"
92 puts "Step 2/3 [Service Scan]"
93 $step = "srv"
94 end
95
96 inc_amount = new_status - $srv_progress
97 $srv_progress = new_status
98 $srv_bar.increment! inc_amount
99 elsif stdout.include? "NSE Timing"
100 if $step != "nse" && $step != 'init'
101 # NSE Timing shows up before it actually begins
102 puts "Step 3/3 [NSE]"
103 $step = "nse"
104 end
105
106 inc_amount = new_status - $nse_progress
107 $nse_progress = new_status
108 $nse_bar.increment! inc_amount
109 end
110end
111
112PTY.spawn( cmd ) do |stdout, stdin, pid|
113 loop do
114 stdin.puts ' '
115 response = stdout.gets.chomp
116
117 increment_bar(response)
118
119 running = %x[ ps -p #{pid} -o comm= ]
120 if running.include? "defunct"
121 break
122 end
123
124 sleep 0.1
125 end
126end
127```
128
129This is a really quick and dirty way of getting a progress bar using the library `progress_bar`. Make sure to install it with:
130
131 gem install progress_bar
132
133Obviously 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).
134
135Now 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.