Mirror of metasploit
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

rop.rb 6.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. # -*- coding: binary -*-
  2. require 'metasm'
  3. require 'rex/compat'
  4. require 'rex/text/table'
  5. require 'rex/ui/text/output/stdio'
  6. require 'rex/ui/text/color'
  7. module Rex
  8. module RopBuilder
  9. class RopBase
  10. def initialize()
  11. @stdio = Rex::Ui::Text::Output::Stdio.new
  12. @gadgets = []
  13. end
  14. def to_csv(gadgets = [])
  15. if gadgets.empty? and @gadgets.nil? or @gadgets.empty?
  16. @stdio.print_error("No gadgets collected to convert to CSV format.")
  17. return
  18. end
  19. # allow the users to import gadget collections from multiple files
  20. if @gadgets.empty? or @gadgets.nil?
  21. @gadgets = gadgets
  22. end
  23. table = Rex::Text::Table.new(
  24. 'Header' => "#{@file} ROP Gadgets",
  25. 'Indent' => 1,
  26. 'Columns' =>
  27. [
  28. "Address",
  29. "Raw",
  30. "Disassembly",
  31. ])
  32. @gadgets.each do |gadget|
  33. table << [gadget[:address], gadget[:raw].unpack('H*')[0], gadget[:disasm].gsub(/\n/, ' | ')]
  34. end
  35. return table.to_csv
  36. end
  37. def import(file)
  38. begin
  39. data = File.new(file, 'r').read
  40. rescue
  41. @stdio.print_error("Error reading #{file}")
  42. return []
  43. end
  44. if data.empty? or data.nil?
  45. return []
  46. end
  47. data.gsub!(/\"/, '')
  48. data.gsub!("Address,Raw,Disassembly\n", '')
  49. @gadgets = []
  50. data.each_line do |line|
  51. addr, raw, disasm = line.split(',', 3)
  52. if addr.nil? or raw.nil? or disasm.nil?
  53. @stdio.print_error("Import file format corrupted")
  54. return []
  55. end
  56. disasm.gsub!(/: /, ":\t")
  57. disasm.gsub!(' | ', "\n")
  58. raw = [raw].pack('H*')
  59. @gadgets << {:file => file, :address => addr, :raw => raw, :disasm => disasm.chomp!}
  60. end
  61. @gadgets
  62. end
  63. def print_msg(msg, color=true)
  64. if not @stdio
  65. @stdio = Rex::Ui::Text::Output::Stdio.new
  66. end
  67. if color == true
  68. @stdio.auto_color
  69. else
  70. @stdio.disable_color
  71. end
  72. @stdio.print_raw(@stdio.substitute_colors(msg))
  73. end
  74. end
  75. class RopCollect < RopBase
  76. def initialize(file="")
  77. @stdio = Rex::Ui::Text::Output::Stdio.new
  78. @file = file if not file.empty?
  79. @bin = Metasm::AutoExe.decode_file(file) if not file.empty?
  80. @disassembler = @bin.disassembler if not @bin.nil?
  81. if @disassembler
  82. @disassembler.cpu = Metasm::Ia32.new('386_common')
  83. end
  84. super()
  85. end
  86. def collect(depth, pattern)
  87. matches = []
  88. gadgets = []
  89. # find matches by scanning for the pattern
  90. matches = @disassembler.pattern_scan(pattern)
  91. if @bin.kind_of?(Metasm::PE)
  92. @bin.sections.each do |section|
  93. next if section.characteristics.include? 'MEM_EXECUTE'
  94. # delete matches if the address is outside the virtual address space
  95. matches.delete_if do |ea|
  96. va = section.virtaddr + @bin.optheader.image_base
  97. ea >= va and ea < va + section.virtsize
  98. end
  99. end
  100. elsif @bin.kind_of?(Metasm::ELF)
  101. @bin.segments.each do |seg|
  102. next if seg.flags.include? 'X'
  103. matches.delete_if do |ea|
  104. ea >= seg.vaddr and ea < seg.vaddr + seg.memsz
  105. end
  106. end
  107. elsif @bin.kind_of?(Metasm::MachO)
  108. @bin.segments.each do |seg|
  109. next if seg.initprot.include? 'EXECUTE'
  110. matches.delete_if do |ea|
  111. ea >= seg.virtaddr and ea < seg.virtaddr + seg.filesize
  112. end
  113. end
  114. end
  115. gadgets = process_gadgets(matches, depth)
  116. gadgets.each do |gadget|
  117. @gadgets << gadget
  118. end
  119. gadgets
  120. end
  121. def pattern_search(pattern)
  122. p = Regexp.new("(" + pattern + ")")
  123. matches = []
  124. @gadgets.each do |gadget|
  125. disasm = ""
  126. addrs = []
  127. gadget[:disasm].each_line do |line|
  128. addr, asm = line.split("\t", 2)
  129. addrs << addr
  130. disasm << asm
  131. end
  132. if gadget[:raw] =~ p or gadget[:disasm] =~ p or disasm =~ p
  133. matches << {:gadget => gadget, :disasm => disasm, :addrs => addrs}
  134. end
  135. end
  136. matches.each do |match|
  137. @stdio.print_status("gadget with address: %bld%cya#{match[:gadget][:address]}%clr matched")
  138. color_pattern(match[:gadget], match[:disasm], match[:addrs], p)
  139. end
  140. matches
  141. end
  142. def color_pattern(gadget, disasm, addrs, p)
  143. idx = disasm.index(p)
  144. if idx.nil?
  145. print_msg(gadget[:disasm])
  146. return
  147. end
  148. disasm = disasm.insert(idx, "%bld%grn")
  149. asm = ""
  150. cnt = 0
  151. colors = false
  152. disasm.each_line do |line|
  153. # if we find this then we are in the matching area
  154. if line.index(/\%bld\%grn/)
  155. colors = true
  156. end
  157. asm << "%clr" + addrs[cnt] + "\t"
  158. # color the remaining parts of the gadget
  159. if colors and line.index("%bld%grn").nil?
  160. asm << "%bld%grn" + line
  161. else
  162. asm << line
  163. end
  164. cnt += 1
  165. end
  166. asm << "%clr\n"
  167. print_msg(asm)
  168. end
  169. def process_gadgets(rets, num)
  170. ret = {}
  171. gadgets = []
  172. tmp = []
  173. rets.each do |ea|
  174. insn = @disassembler.disassemble_instruction(ea)
  175. next if not insn
  176. xtra = insn.bin_length
  177. num.downto(0) do |x|
  178. addr = ea - x
  179. # get the disassembled instruction at this address
  180. di = @disassembler.disassemble_instruction(addr)
  181. # skip invalid instructions
  182. next if not di
  183. next if di.opcode.props[:setip]
  184. next if di.opcode.props[:stopexec]
  185. # get raw bytes
  186. buf = @disassembler.read_raw_data(addr, x + xtra)
  187. # make sure disassembling forward leads to our instruction
  188. next if not ends_with_addr(buf, addr, ea)
  189. dasm = ""
  190. while addr <= ea
  191. di = @disassembler.disassemble_instruction(addr)
  192. dasm << ("0x%08x:\t" % addr) + di.instruction.to_s + "\n"
  193. addr = addr + di.bin_length
  194. end
  195. if not tmp.include?(ea)
  196. tmp << ea
  197. else
  198. next
  199. end
  200. # otherwise, we create a new tailchunk and add it to the list
  201. ret = {:file => @file, :address => ("0x%08x" % (ea - x)), :raw => buf, :disasm => dasm}
  202. gadgets << ret
  203. end
  204. end
  205. gadgets
  206. end
  207. private
  208. def ends_with_addr(raw, base, addr)
  209. dasm2 = Metasm::Shellcode.decode(raw, @disassembler.cpu).disassembler
  210. offset = 0
  211. while ((di = dasm2.disassemble_instruction(offset)))
  212. return true if (base + offset) == addr
  213. return false if di.opcode.props[:setip]
  214. return false if di.opcode.props[:stopexec]
  215. offset = di.next_addr
  216. end
  217. false
  218. end
  219. def raw_instructions(raw)
  220. insns = []
  221. d2 = Metasm::Shellcode.decode(raw, @disassembler.cpu).disassembler
  222. addr = 0
  223. while ((di = d2.disassemble_instruction(addr)))
  224. insns << di.instruction
  225. addr = di.next_addr
  226. end
  227. insns
  228. end
  229. end
  230. end
  231. end