Skip to content

Commit

Permalink
Added the ronin-nmap grep command (closes #3).
Browse files Browse the repository at this point in the history
  • Loading branch information
postmodern committed May 13, 2024
1 parent 8bb6977 commit 4ab9236
Show file tree
Hide file tree
Showing 6 changed files with 1,653 additions and 1 deletion.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Commands:
completion
convert
dump
grep
help
import
print
Expand Down
1 change: 1 addition & 0 deletions gemspec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ generated_files:
- man/ronin-nmap-completion.1
- man/ronin-nmap-convert.1
- man/ronin-nmap-dump.1
- man/ronin-nmap-grep.1
- man/ronin-nmap-import.1
- man/ronin-nmap-print.1
- man/ronin-nmap-scan.1
Expand Down
378 changes: 378 additions & 0 deletions lib/ronin/nmap/cli/commands/grep.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,378 @@
# frozen_string_literal: true
#
# ronin-nmap - A Ruby library for automating nmap and importing nmap scans.
#
# Copyright (c) 2023 Hal Brodigan (postmodern.mod3@gmail.com)
#
# ronin-nmap is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# ronin-nmap is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with ronin-nmap. If not, see <https://www.gnu.org/licenses/>.
#

require 'ronin/nmap/cli/command'

require 'command_kit/colors'
require 'command_kit/printing/indent'
require 'nmap/xml'

module Ronin
module Nmap
class CLI
module Commands
#
# Parses and searches nmap XML file(s) for the pattern.
#
# ## Usage
#
# ronin-nmap grep [options] PATTERN XML_FILE [...]
#
# ## Options
#
# -h, --help Print help information
#
# ## Arguments
#
class Grep < Command

include CommandKit::Colors
include CommandKit::Printing::Indent

usage '[options] PATTERN XML_FILE [...]'

argument :pattern, required: true,
desc: 'The pattern to search for'

argument :xml_file, required: true,
repeats: true,
desc: 'The nmap XML file to search'

description 'Parses and searches nmap XML file(s) for the pattern'

man_page 'ronin-nmap-grep.1'

#
# Runs the `ronin-nmap grep` command.
#
# @param [String] pattern
# The pattern to search for.
#
# @param [Array<String>] xml_files
# The nmap `.xml` file(s) to grep.
#
def run(pattern,*xml_files)
xml_files.each do |xml_file|
unless File.file?(xml_file)
print_error "no such file or directory: #{xml_file}"
next
end

xml = ::Nmap::XML.open(xml_file)
hosts = grep_xml(xml,pattern)

highlight_hosts(hosts,pattern)
end
end

#
# Searches the parsed nmap XML for the text pattern.
#
# @param [::Nmap::XML] xml
# The parsed nmap XML object.
#
# @param [String] pattern
# The text pattern to search for.
#
# @return [Enumerator::Lazy<::Nmap::XML::Host>]
# The nmap XML host objects that contain the text pattern.
#
def grep_xml(xml,pattern)
xml.each_up_host.lazy.filter do |host|
match_host(host,pattern)
end
end

#
# Determines if the nmap XML host object contains the text pattern.
#
# @param [::Nmap::XML::Host] host
# The nmap XML host object to search.
#
# @param [String] pattern
# The text pattern to search for.
#
# @return [Boolean]
#
def match_host(host,pattern)
hostnames = host.each_hostname
open_ports = host.each_open_port
host_script = host.host_script

hostnames.any? { |hostname| match_hostname(hostname,pattern) } ||
open_ports.any? { |port| match_port(port,pattern) } ||
(host_script && match_scripts(host_script,pattern))
end

#
# Determines if the nmap XML hostname object contains the text
# pattern.
#
# @param [::Nmap::XML::Hostname] hostname
# The nmap XML hostname object to search.
#
# @param [String] pattern
# The text pattern to search for.
#
# @return [Boolean]
#
def match_hostname(hostname,pattern)
hostname.name.match(pattern)
end

#
# Determines if the nmap XML port object contains the text pattern.
#
# @param [::Nmap::XML::Port] port
# The nmap XML port object to search.
#
# @param [String] pattern
# The text pattern to search for.
#
# @return [Boolean]
#
def match_port(port,pattern)
match_scripts(port,pattern) || if (service = port.service)
match_service(service,pattern)
end
end

#
# Determines if the nmap XML service object contains the text pattern.
#
# @param [::Nmap::XML::Service] service
# The nmap XML service object to search.
#
# @param [String] pattern
# The text pattern to search for.
#
# @return [Boolean]
#
def match_service(service,pattern)
product = service.product
version = service.version
extra_info = service.extra_info

service.name.match(pattern) ||
(product && product.match(pattern)) ||
(version && version.match(pattern)) ||
(extra_info && extra_info.match(pattern))
end

#
# Determines if the nmap XML scripts object contains the text pattern.
#
# @param [::Nmap::XML::Scripts] has_scripts
# The nmap XML object that includes `Nmap::XML::Scripts`.
#
# @param [String] pattern
# The text pattern to search for.
#
# @return [Boolean]
#
def match_scripts(has_scripts,pattern)
has_scripts.scripts.any? do |id,script|
match_script(script,pattern)
end
end

#
# Determines if the nmap XML script object contains the text pattern.
#
# @param [::Nmap::XML::Script] script
# The nmap XML script object to search.
#
# @param [String] pattern
# The text pattern to search for.
#
# @return [Boolean]
#
def match_script(script,pattern)
script.id.match(pattern) || script.output.match(pattern)
end

#
# Prints the nmap hosts with the pattern highlighted in the output.
#
# @param [Enumerator::Lazy<::Nmap::XML::Host>] hosts
# The nmap hosts to print.
#
# @param [String] pattern
# The pattern to highlight in the output.
#
def highlight_hosts(hosts,pattern)
hosts.each do |host|
highlight_host(host,pattern)
puts
end
end

#
# Prints the nmap host with the pattern highlighted in the output.
#
# @param [::Nmap::XML::Host] host
# The nmap host to print.
#
# @param [String] pattern
# The text pattern to highlight in the output.
#
def highlight_host(host,pattern)
addresses = host.addresses
hostnames = host.hostnames

unless hostnames.empty?
puts "[ #{addresses.first} / #{highlight(hostnames.first,pattern)} ]"
else
puts "[ #{addresses.first} ]"
end
puts

indent do
if addresses.length > 1
puts "[ addresses ]"
puts

indent do
addresses.each do |address|
puts address
end
end
puts
end

if hostnames.length > 1
puts "[ hostnames ]"
puts

indent do
hostnames.each do |hostname|
puts highlight(hostname,pattern)
end
end
puts
end

if (host_script = host.host_script)
puts "[ host scripts ]"
puts

indent do
highlight_scripts(host_script)
end
end

puts "[ ports ]"
puts

indent do
host.each_open_port do |port|
highlight_port(port,pattern)
end
end
end
end

#
# Prints the nmap port with the pattern highlighted in the output.
#
# @param [::Nmap::XML::Port] port
# The nmap XML port object to print.
#
# @param [String] pattern
# The text pattern to highlight in the output.
#
def highlight_port(port,pattern)
port_line = "#{port.number}/#{port.protocol}\t#{port.state}"

if (service = port.service)
port_line << "\t#{highlight(service,pattern)}"

if (extra_info = service.extra_info)
port_line << " #{highlight(extra_info,pattern)}"
end
end

puts port_line

unless port.scripts.empty?
puts

indent do
highlight_scripts(port,pattern)
end
end
end

#
# Prints the nmap scripts with the pattern highlighted in the output.
#
# @param [::Nmap::XML::Scripts] has_scripts
# The nmap XML object that has scripts.
#
# @param [String] pattern
# The text pattern to highlight in the output.
#
def highlight_scripts(has_scripts,pattern)
has_scripts.scripts.each_value do |script|
highlight_script(script,pattern)
puts
end
end

#
# Prints the nmap script with the pattern highlighted in the output.
#
# @param [::Nmap::XML::Script] script
# The nmap XML script object to print.
#
# @param [String] pattern
# The text pattern to highlight in the output.
#
def highlight_script(script,pattern)
puts "#{highlight(script.id,pattern)}:"

indent do
script.output.strip.each_line do |line|
puts highlight(line,pattern)
end
end
end

#
# Highlights the pattern in the text.
#
# @param [String] text
# The text to modify.
#
# @param [String] pattern
# The pattern to highlight.
#
# @return [String]
# The modified text.
#
def highlight(text,pattern)
text.to_s.gsub(pattern,colors.bold(colors.red(pattern)))
end

end
end
end
end
end
Loading

0 comments on commit 4ab9236

Please sign in to comment.