find_badchars.rb 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  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. # This script is intended to assist an exploit developer in deducing what
  8. # "bad characters" exist for a given input path to a program.
  9. #
  10. begin
  11. msfbase = __FILE__
  12. while File.symlink?(msfbase)
  13. msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase))
  14. end
  15. gem 'rex-text'
  16. $:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', '..', 'lib')))
  17. require 'msfenv'
  18. $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB']
  19. require 'rex'
  20. OutStatus = "[*] "
  21. OutError = "[-] "
  22. $args = Rex::Parser::Arguments.new(
  23. "-b" => [ true, "The list of characters to avoid: '\\x00\\xff'" ],
  24. "-h" => [ false, "Help banner" ],
  25. "-i" => [ true, "Read memory contents from the supplied file path" ],
  26. "-t" => [ true, "The format that the memory contents are in (empty to list)" ])
  27. def usage
  28. $stderr.puts("\n" + " Usage: #{File.basename($0)} <options>\n" + $args.usage)
  29. exit
  30. end
  31. def show_format_list
  32. $stderr.puts("Supported formats:\n")
  33. $stderr.puts(" raw raw binary data\n")
  34. $stderr.puts(" windbg output from windbg's \"db\" command\n")
  35. $stderr.puts(" gdb output from gdb's \"x/bx\" command\n")
  36. $stderr.puts(" hex hex bytes like \"\\xFF\\x41\" or \"eb fe\"\n")
  37. end
  38. def debug_buffer(name, buf)
  39. str = "\n#{buf.length} bytes of "
  40. str << name
  41. str += ":" if buf.length > 0
  42. str += "\n\n"
  43. $stderr.puts str
  44. if buf.length > 0
  45. $stderr.puts Rex::Text.to_hex_dump(buf)
  46. end
  47. end
  48. # Input defaults
  49. badchars = ''
  50. fmt = 'raw'
  51. input = $stdin
  52. # Output
  53. new_badchars = ''
  54. # Parse the argument and rock it
  55. $args.parse(ARGV) { |opt, idx, val|
  56. case opt
  57. when "-i"
  58. begin
  59. input = File.new(val)
  60. rescue
  61. $stderr.puts(OutError + "Failed to open file #{val}: #{$!}")
  62. exit
  63. end
  64. when "-b"
  65. badchars = Rex::Text.dehex(val)
  66. when "-t"
  67. if (val =~ /^(raw|windbg|gdb|hex)$/)
  68. fmt = val
  69. else
  70. if val.nil? or val.length < 1
  71. show_format_list
  72. else
  73. $stderr.puts(OutError + "Invalid format: #{val}")
  74. end
  75. exit
  76. end
  77. when "-h"
  78. usage
  79. end
  80. }
  81. if input == $stdin
  82. $stderr.puts(OutStatus + "Please paste the memory contents in \"" + fmt + "\" format below (end with EOF):\n")
  83. end
  84. # Working data set
  85. from_msf = Rex::Text.charset_exclude(badchars)
  86. from_dbg = ''
  87. # Process the input
  88. from_dbg = input.read
  89. case fmt
  90. when "raw"
  91. # this should already be in the correct format :)
  92. when "windbg"
  93. translated = ''
  94. from_dbg.each_line do |ln|
  95. translated << ln.chomp[10,47].gsub!(/(-| )/, '')
  96. end
  97. from_dbg = Rex::Text.hex_to_raw(translated)
  98. when "gdb"
  99. translated = ''
  100. from_dbg.each_line do |ln|
  101. translated << ln.chomp.split(':')[1].gsub!(/0x/, '\x').gsub!(/ /, '')
  102. end
  103. from_dbg = Rex::Text.hex_to_raw(translated)
  104. when "hex"
  105. translated = ''
  106. from_dbg.each_line do |ln|
  107. translated << ln.chomp.gsub!(/ /,'')
  108. end
  109. from_dbg = Rex::Text.hex_to_raw(translated)
  110. end
  111. =begin
  112. # Uncomment these to debug stuff ..
  113. debug_buffer("BadChars", badchars)
  114. debug_buffer("memory contents", from_dbg)
  115. debug_buffer("Rex::Text.charset_exclude() output", from_msf)
  116. =end
  117. # Find differences between the two data sets
  118. from_msf = from_msf.unpack('C*')
  119. from_dbg = from_dbg.unpack('C*')
  120. minlen = from_msf.length
  121. minlen = from_dbg.length if from_dbg.length < minlen
  122. (0..(minlen-1)).each do |idx|
  123. ch1 = from_msf[idx]
  124. ch2 = from_dbg[idx]
  125. if ch1 != ch2
  126. str = "Byte at index 0x%04x differs (0x%02x became 0x%02x)" % [idx, ch1, ch2]
  127. $stderr.puts OutStatus + str
  128. new_badchars << ch1
  129. end
  130. end
  131. # show the results
  132. if new_badchars.length < 1
  133. $stderr.puts(OutStatus + "All characters matched, no new bad characters discovered.")
  134. else
  135. $stderr.puts(OutStatus + "Proposed BadChars: \"" + Rex::Text.to_hex(new_badchars) + "\"")
  136. end
  137. rescue SignalException => e
  138. puts("Aborted! #{e}")
  139. end