metasploit-framework/tools/modules/verify_datastore.rb

111 lines
3.4 KiB
Ruby
Executable File

#!/usr/bin/env ruby
#
# This script parses a Metasploit module's use of the datastore to
# ensure that all datastore elements are both declared and used. Adding
# arbitrary elements to the datastore without first declaring them won't
# throw an error at runtime, but can occasionally be the cause of bugs and
# make troubleshooting more difficult.
#
# This script could use more serious option parsing, and a batch mode beyond
# bash's "for i in path/to/modules/*.rb; do verify_datastore.rb $i; done" Also,
# it assumes Metasploit's msf/core is in the load path.
#
infile = ARGV[0]
unless(infile && File.readable?(infile))
puts "Usage: #{$0} /path/to/module.rb"
exit(1)
end
verbose = false
mod = File.open(infile, "rb") {|f| f.read(f.stat.size)}
regex = {}
regex[:datastore] = /[^\x2e](datastore\x5b[\x22\x27]([^\x22\x27]+))/
regex[:comment] = /^[\s]*#/
regex[:opts] = /register_options/
regex[:opts_end] = /^[\s]*def[\s]+/
regex[:is_opt] = /^[\s]*(Opt[A-Z][^\x2e]+)\x2enew[\s]*\x28?[\x22\x27]([^\x22\x27]+)/
regex[:mixin] = /^[\s]*include[\s]+([^\s]+)/
regex[:class] = /^[\s]*class[\s]+Metasploit3[\s]*<[\s]*([A-Z][^\s]+)/
# regex[:require] = /^[\s]*require[\s]+[\x22\x27]([^\x22\x27]+)[\x22\x27]/
referenced_datastores = []
declared_datastores = {}
undeclared_datastores = []
unused_datastores = []
# Declared datastore finder
mod.each_line do |line|
next if line.match regex[:comment]
datastores = line.scan regex[:datastore]
datastores.each {|ds| referenced_datastores << ds[1]}
end
# Referenced datastore finder
in_opts = false
mod.each_line do |line|
in_opts = true if line.match regex[:opts]
in_opts = false if line.match regex[:opts_end]
next unless in_opts
if line.match regex[:is_opt]
# Assumes only one!
declared_datastores[$2] ||= $1
end
end
# Class and Mixin finder
$mixins = []
$class = nil
require 'msf/core' # Make sure this is in your path, or else all is for naught.
mod.each_line do |line|
if line.match regex[:class]
$class = ObjectSpace.class_eval($1)
elsif line.match regex[:mixin]
mixin = $1
begin
$mixins << ObjectSpace.module_eval(mixin)
rescue
puts "[-] Error including mixin: #{mixin}"
next
end
end
end
class Fakemod < $class
$mixins.each {|m| include m}
end
fakemod = Fakemod.new
inhereted_datastores = fakemod.options.keys
undeclared_datastores = referenced_datastores - (declared_datastores.keys + inhereted_datastores)
# It's common to not use some inhereted datastores, don't bother talking about them
unused_datastores = declared_datastores.keys - referenced_datastores
if verbose
puts "[+] --- Referenced datastore keys for #{infile}"
referenced_datastores.uniq.sort.each {|ds| puts ds}
puts "[+] --- Declared datastore keys for #{infile}"
declared_datastores.keys.sort.each {|opt| puts "%-30s%s" % [opt, declared_datastores[opt]] }
end
unless undeclared_datastores.empty?
puts "[-] %-60s : fail (undeclared)" % [infile]
puts "[-] The following datastore elements are undeclared" if verbose
undeclared_datastores.uniq.sort.each {|opt| puts " \e[31m#{opt}\e[0m" }
end
unless unused_datastores.empty?
puts "[*] %-60s : warn (unused)" % [infile]
puts "[*] The following datastore elements are unused" if verbose
unused_datastores.uniq.sort.each {|opt| puts " \e[33m#{opt}\e[0m" }
end
if undeclared_datastores.empty? && unused_datastores.empty?
puts "[+] %-60s : okay" % [infile]
end