missing_payload_tests.rb 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. #!/usr/bin/env ruby
  2. ##
  3. # This module requires Metasploit: https://metasploit.com/download
  4. # Current source: https://github.com/rapid7/metasploit-framework
  5. ##
  6. #
  7. # Reads untest payload modules from log/untested-payloads.log (which can be produced by running `rake spec`) and prints
  8. # the statements that need to be added to `spec/modules/payloads_spec.rb`. **Note: this script depends on the payload
  9. # being loadable, so if module is not loadable, then the developer must manually determine which single needs to be tested
  10. # or which combinations of stages and stagers need to be tested.**
  11. #
  12. msfbase = __FILE__
  13. while File.symlink?(msfbase)
  14. msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase))
  15. end
  16. $:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', '..', 'lib')))
  17. require 'msfenv'
  18. framework = Msf::Simple::Framework.create()
  19. options_set_by_ancestor_reference_name = Hash.new { |hash, ancestor_reference_name|
  20. hash[ancestor_reference_name] = Set.new
  21. }
  22. framework.payloads.each_module { |reference_name, payload_class|
  23. next unless payload_class
  24. module_ancestors = payload_class.ancestors.select { |ancestor|
  25. # need to use try because name may be nil for anonymous Modules
  26. ancestor.name.try(:start_with?, 'Msf::Modules::')
  27. }
  28. ancestor_reference_names = module_ancestors.map { |module_ancestor|
  29. unpacked_module_ancestor_full_name = module_ancestor.name.sub(/^Msf::Modules::Mod/, '')
  30. .sub(/::MetasploitModule/, '')
  31. module_ancestor_full_name = [unpacked_module_ancestor_full_name].pack("H*")
  32. module_ancestor_full_name.sub(%r{^payload/}, '')
  33. }
  34. options = {
  35. reference_name: reference_name,
  36. ancestor_reference_names: ancestor_reference_names
  37. }
  38. # record for both ancestor_reference_names as which is untested is not known here
  39. ancestor_reference_names.each do |ancestor_reference_name|
  40. options_set_by_ancestor_reference_name[ancestor_reference_name].add options
  41. end
  42. }
  43. tested_options = Set.new
  44. $stderr.puts "Add the following context to `spec/modules/payloads_spec.rb` by inserting them in lexical order between the pre-existing contexts:"
  45. File.open('log/untested-payloads.log') { |f|
  46. f.each_line do |line|
  47. ancestor_reference_name = line.strip
  48. options_set = options_set_by_ancestor_reference_name[ancestor_reference_name]
  49. options_set.each do |options|
  50. # don't print a needed test twice
  51. unless tested_options.include? options
  52. reference_name = options[:reference_name]
  53. $stdout.puts
  54. $stdout.puts " context '#{reference_name}' do\n" \
  55. " it_should_behave_like 'payload cached size is consistent',\n" \
  56. " ancestor_reference_names: ["
  57. ancestor_reference_names = options[:ancestor_reference_names]
  58. if ancestor_reference_names.length == 1
  59. $stdout.puts " '#{ancestor_reference_names[0]}'"
  60. else
  61. $stdout.puts " '#{ancestor_reference_names[1]}',"
  62. $stdout.puts " '#{ancestor_reference_names[0]}'"
  63. end
  64. $stdout.puts " ],\n" \
  65. " dynamic_size: false,\n" \
  66. " modules_pathname: modules_pathname,\n" \
  67. " reference_name: '#{reference_name}'\n" \
  68. " end"
  69. tested_options.add options
  70. end
  71. end
  72. end
  73. }