-
Notifications
You must be signed in to change notification settings - Fork 17
/
icmp_recon.rb
executable file
·363 lines (304 loc) · 10.8 KB
/
icmp_recon.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
#!/usr/bin/env ruby
# == Synopsis
# This application is designed to perform ICMP reconnaissance on a range of IP addresses and output the results
# in several potential formats.
#
# It uses nmap for the underlying scanning duties so it's required that nmap will be installed.
# to do the ICMP scanning required nmap needs to run as root (on linux) so either the -s (sudo) option needs to be passed
# which produces some annoying prompts for passwords, or you could run this script as root :)
#
# Input can either be on the command line or as a series of ranges in a file. NB at the moment RTF reporting doesn't work
# with ranges supplied in a file.
#
#
# == Pre-Requisites
#
#
# == ToDo
#
# * Get RTF report working with the file option
# * Add back in the dead hosts rather than just ones that respond. (change how @all_hosts is calculated.)
# *
#
# == Author
# Author:: Rory McCune
# Copyright:: Copyright (c) 2013 Rory Mccune
# License:: GPLv3
#
# == Options
# -h, --help Displays help message
# -v, --version Display the version, then exit
# -i, --input Range of IP addresses to Scan
# -f, --file File containing ranges to scan
# -s, --sudo Use sudo to run nmap scans
# --reportPrefix Prefix for report files (Default is icmp_recon)
# --csvReport Create a CSV report of the results
# --hmtlReport Create an HTML report of the results
# --rtfReport Create an RTF report of the results
#
#
# == Usage
#
#
#
#
#
class IcmpRecon
VERSION = '0.0.1'
def initialize(arguments)
require 'optparse'
require 'logger'
require 'ostruct'
begin
require 'nmap/parser'
require 'ipaddress'
require 'logger'
rescue LoadError
puts "icmp_recon requires two gems to run"
puts "install nmap-parser and ipaddress and try again"
exit
end
begin
require 'nokogiri'
rescue LoadError
puts "Couldn't load nokogiri"
puts "try gem install nokogiri"
exit
end
@options = OpenStruct.new
@options.input_file = ''
@options.input_ranges = Array.new
@options.report_file_base = 'icmp_recon'
@options.nmap_command = 'nmap'
@options.csv_report = false
@options.html_report = false
@options.rtf_report = false
@options.excel_report = false
@options.bypass_root_check = false
opts = OptionParser.new do |opts|
opts.banner = "ICMP Reconnaissance Tool #{VERSION}"
opts.on("-f", "--file [FILE]", "Input File with IP Address Range List") do |file|
@options.input_file = file
end
opts.on("-i", "--input [INPUT]", "Input Address range to scan") do |range|
@options.input_ranges << range
end
opts.on("-s", "--sudo", "Run nmap commands using sudo") do |sudo|
@options.nmap_command = 'sudo nmap'
@options.bypass_root_check = true
end
opts.on("--csvReport", "Create a CSV report") do |csvrep|
@options.csv_report = true
end
opts.on("--htmlReport", "Create an HTML report") do |htmlrep|
@options.html_report = true
end
opts.on("--rtfReport", "Create an RTF report") do |rtfrep|
@options.rtf_report = true
end
opts.on("--excelReport", "Create an Excel Report") do |excelrep|
@options.excel_report = true
end
opts.on("--reportPrefix [REPREF]", "Prefix for report files") do |reppref|
@options.report_file_base = reppref + "_icmp"
end
opts.on("-b", "Bypass root check") do |bypass|
@options.bypass_root_check = true
end
opts.on("-h", "--help", "-?", "--?", "Get Help") do |help|
puts opts
exit
end
opts.on("-v", "--version", "Get Version") do |ver|
puts "ICMP Reconnaissance Tool #{VERSION}"
exit
end
end
opts.parse!(arguments)
unless @options.input_file.length > 0 || @options.input_ranges.length > 0
puts "You need to either specify a range or an input file"
puts opts
exit
end
@log = Logger.new('icmp-recon-analyzer-log')
@log.level = Logger::DEBUG
unless Process.uid == 0 || @options.bypass_root_check
@log.debug('errored on root check')
puts "You need root permissions to run this properly"
puts "Either run this script as root or use the sudo option (-s)"
puts "or if your sure it's ok, use the -b option to bypass this check"
exit
end
unless @options.rtf_report || @options.csv_report || @options.html_report || @options.excel_report
@log.debug('errored on reporting check')
puts "No reporting specified"
puts " you need to use one or more of --csvReport, --rtfReport, --htmlReport or --excelReport"
exit
end
@ip_address_ranges = Array.new
if @options.input_file.length > 0
begin
@input_file = File.open(@options.input_file,'r')
rescue Exception => e
puts "Couldn't open the input file provided, check permissions/spelling?"
puts e
exit
end
@ip_address_ranges = File.open(@options.input_file,'r+').readlines
@ip_address_ranges.each {|line| line.chomp!}
#Getting rid of anything that's obviously not an IP address, really it should be 7 I think (4 digits and 3 .'s)
@ip_address_ranges.delete_if {|ip| ip.length < 4}
@log.debug("there are #{@ip_address_ranges.length} ranges to scan")
end
if @options.input_ranges.length > 0
@ip_address_ranges = @options.input_ranges
end
@echo_hosts = Array.new
@timestamp_hosts = Array.new
@address_mask_hosts = Array.new
@all_hosts = Array.new
end
def run
icmp_scan
csv_report if @options.csv_report
html_report if @options.html_report
rtf_report if @options.rtf_report
excel_report if @options.excel_report
end
def icmp_scan
@ip_address_ranges.each do |range|
echo_parser = Nmap::Parser.parsescan(@options.nmap_command, "-sP -PE #{range}")
echo_parser.hosts("up") do |host|
@echo_hosts << host.addr
end
#Hack to get all hosts into the all hosts lists including ones that don't respond.
echo_parser.hosts do |host|
@all_hosts << host.addr
end
timestamp_parser = Nmap::Parser.parsescan(@options.nmap_command, "-sP -PP #{range}")
timestamp_parser.hosts("up") do |host|
@timestamp_hosts << host.addr
end
address_mask_parser = Nmap::Parser.parsescan(@options.nmap_command, "-sP -PM #{range}")
address_mask_parser.hosts("up") do |host|
@address_mask_hosts << host.addr
end
end
#@all_hosts = (@address_mask_hosts + @echo_hosts + @timestamp_hosts).uniq
end
def csv_report
report = File.new(@options.report_file_base + '.csv','w+')
report.puts "ICMP Reconnaissance Report"
report.puts "-------------------------"
report.puts "Address, Echo Response?, Timestamp Response?, Netmask Response?"
@all_hosts.each do |address|
report.print address + ","
@echo_hosts.include?(address) ? report.print("y,") : report.print("n,")
@timestamp_hosts.include?(address) ? report.print("y,") : report.print("n,")
@address_mask_hosts.include?(address) ? report.puts("y") : report.puts("n")
end
end
def excel_report
begin
require 'rubyXL'
rescue LoadError
puts "You need the rubyXL gem to run the excel report"
puts "try gem install rubyXL"
exit
end
workbook = RubyXL::Workbook.new
icmp_sheet = workbook.worksheets[0]
icmp_sheet.sheet_name = "ICMP Recon Results"
icmp_sheet.add_cell(0,0,"Address")
icmp_sheet.add_cell(0,1,"Echo Response")
icmp_sheet.add_cell(0,2,"Timestamp Response")
icmp_sheet.add_cell(0,3,"Netmask Response")
row_count = 1
@all_hosts.each do |address|
icmp_sheet.add_cell(row_count,0,address)
if @echo_hosts.include?(address)
icmp_sheet.add_cell(row_count,1,"Y")
icmp_sheet.sheet_data[row_count][1].change_fill('d4004b')
else
icmp_sheet.add_cell(row_count,1,"N")
icmp_sheet.sheet_data[row_count][1].change_fill('27ae60')
end
if @timestamp_hosts.include?(address)
icmp_sheet.add_cell(row_count,2,"Y")
icmp_sheet.sheet_data[row_count][2].change_fill('d4004b')
else
icmp_sheet.add_cell(row_count,2,"N")
icmp_sheet.sheet_data[row_count][2].change_fill('27ae60')
end
if @address_mask_hosts.include?(address)
icmp_sheet.add_cell(row_count,3,"Y")
icmp_sheet.sheet_data[row_count][3].change_fill('d4004b')
else
icmp_sheet.add_cell(row_count,3,"N")
icmp_sheet.sheet_data[row_count][3].change_fill('27ae60')
end
row_count = row_count + 1
end
workbook.write(@options.report_file_base + '.xlsx')
end
def html_report
@builder = Nokogiri::HTML::Builder.new do |doc|
doc.html {
doc.head {
doc.title "ICMP Recon Report"
doc.style {
doc.text "table, th, td {border: 1px solid black;}"
doc.text "td {text-align:center;}"
}
}
doc.body {
doc.h1 "ICMP Reconnaissance Report"
doc.table {
doc.tr {
doc.th "IP Address"
doc.th "ICMP Echo Response?"
doc.th "ICMP Timestamp Response?"
doc.th "ICMP Netmask Response?"
}
@all_hosts.each do |address|
doc.tr{
doc.td{
doc.b address
}
@echo_hosts.include?(address) ? doc.td("y", :bgcolor => "CC0033"):doc.td("n", :bgcolor => "33CC33")
@timestamp_hosts.include?(address) ? doc.td("y", :bgcolor => "CC0033"):doc.td("n", :bgcolor => "33CC33")
@address_mask_hosts.include?(address) ? doc.td("y", :bgcolor => "CC0033"):doc.td("n", :bgcolor => "33CC33")
}
end
}
}
}
end
@report_file = File.new(@options.report_file_base + '.html','w+')
@report_file.puts @builder.to_html
end
def rtf_report
require 'rtf'
document = RTF::Document.new(RTF::Font.new(RTF::Font::ROMAN, 'Arial'))
@all_hosts.each do |address|
table = document.table(range.size + 1,4,2000,2000,2000,2000)
table[0][0] << 'IP Address'
table[0][1] << 'ICMP Echo Response?'
table[0][2] << 'ICMP Timestamp Response?'
table[0][3] << 'ICMP Netmask Response?'
row = 1
table[row][0] << address
@echo_hosts.include?(address) ? table[row][1] << 'Y': table[row][1] << 'N'
@timestamp_hosts.include?(address) ? table[row][2] << 'Y': table[row][2] << 'N'
@address_mask_hosts.include?(address) ? table[row][3] << 'Y': table[row][3] << 'N'
row = row + 1
end
@report_file = File.open(@options.report_file_base + '.rtf','w+') do |file|
file.write(document.to_rtf)
end
end
end
if __FILE__ == $0
recon = IcmpRecon.new(ARGV)
recon.run
end