verify_datastore.rb 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. #!/usr/bin/env ruby
  2. #
  3. # This script parses a Metasploit module's use of the datastore to
  4. # ensure that all datastore elements are both declared and used. Adding
  5. # arbitrary elements to the datastore without first declaring them won't
  6. # throw an error at runtime, but can occasionally be the cause of bugs and
  7. # make troubleshooting more difficult.
  8. #
  9. # This script could use more serious option parsing, and a batch mode beyond
  10. # bash's "for i in path/to/modules/*.rb; do verify_datastore.rb $i; done" Also,
  11. # it assumes Metasploit's msf/core is in the load path.
  12. #
  13. infile = ARGV[0]
  14. unless(infile && File.readable?(infile))
  15. puts "Usage: #{$0} /path/to/module.rb"
  16. exit(1)
  17. end
  18. verbose = false
  19. mod = File.open(infile, "rb") {|f| f.read(f.stat.size)}
  20. regex = {}
  21. regex[:datastore] = /[^\x2e](datastore\x5b[\x22\x27]([^\x22\x27]+))/
  22. regex[:comment] = /^[\s]*#/
  23. regex[:opts] = /register_options/
  24. regex[:opts_end] = /^[\s]*def[\s]+/
  25. regex[:is_opt] = /^[\s]*(Opt[A-Z][^\x2e]+)\x2enew[\s]*\x28?[\x22\x27]([^\x22\x27]+)/
  26. regex[:mixin] = /^[\s]*include[\s]+([^\s]+)/
  27. regex[:class] = /^[\s]*class[\s]+Metasploit3[\s]*<[\s]*([A-Z][^\s]+)/
  28. # regex[:require] = /^[\s]*require[\s]+[\x22\x27]([^\x22\x27]+)[\x22\x27]/
  29. referenced_datastores = []
  30. declared_datastores = {}
  31. undeclared_datastores = []
  32. unused_datastores = []
  33. # Declared datastore finder
  34. mod.each_line do |line|
  35. next if line.match regex[:comment]
  36. datastores = line.scan regex[:datastore]
  37. datastores.each {|ds| referenced_datastores << ds[1]}
  38. end
  39. # Referenced datastore finder
  40. in_opts = false
  41. mod.each_line do |line|
  42. in_opts = true if line.match regex[:opts]
  43. in_opts = false if line.match regex[:opts_end]
  44. next unless in_opts
  45. if line.match regex[:is_opt]
  46. # Assumes only one!
  47. declared_datastores[$2] ||= $1
  48. end
  49. end
  50. # Class and Mixin finder
  51. $mixins = []
  52. $class = nil
  53. mod.each_line do |line|
  54. if line.match regex[:class]
  55. $class = ObjectSpace.class_eval($1)
  56. elsif line.match regex[:mixin]
  57. mixin = $1
  58. begin
  59. $mixins << ObjectSpace.module_eval(mixin)
  60. rescue
  61. puts "[-] Error including mixin: #{mixin}"
  62. next
  63. end
  64. end
  65. end
  66. class Fakemod < $class
  67. $mixins.each {|m| include m}
  68. end
  69. fakemod = Fakemod.new
  70. inhereted_datastores = fakemod.options.keys
  71. undeclared_datastores = referenced_datastores - (declared_datastores.keys + inhereted_datastores)
  72. # It's common to not use some inhereted datastores, don't bother talking about them
  73. unused_datastores = declared_datastores.keys - referenced_datastores
  74. if verbose
  75. puts "[+] --- Referenced datastore keys for #{infile}"
  76. referenced_datastores.uniq.sort.each {|ds| puts ds}
  77. puts "[+] --- Declared datastore keys for #{infile}"
  78. declared_datastores.keys.sort.each {|opt| puts "%-30s%s" % [opt, declared_datastores[opt]] }
  79. end
  80. unless undeclared_datastores.empty?
  81. puts "[-] %-60s : fail (undeclared)" % [infile]
  82. puts "[-] The following datastore elements are undeclared" if verbose
  83. undeclared_datastores.uniq.sort.each {|opt| puts " \e[31m#{opt}\e[0m" }
  84. end
  85. unless unused_datastores.empty?
  86. puts "[*] %-60s : warn (unused)" % [infile]
  87. puts "[*] The following datastore elements are unused" if verbose
  88. unused_datastores.uniq.sort.each {|opt| puts " \e[33m#{opt}\e[0m" }
  89. end
  90. if undeclared_datastores.empty? && unused_datastores.empty?
  91. puts "[+] %-60s : okay" % [infile]
  92. end