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.

file_dropper.rb 5.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. # -*- coding: binary -*-
  2. module Msf
  3. module Exploit::FileDropper
  4. def initialize(info = {})
  5. super
  6. register_advanced_options(
  7. [
  8. OptInt.new('FileDropperDelay', [false, 'Delay in seconds before attempting file cleanup'])
  9. ], self.class)
  10. end
  11. # Record file as needing to be cleaned up
  12. #
  13. # @param files [Array<String>] List of paths on the target that should
  14. # be deleted during cleanup. Each filename should be either a full
  15. # path or relative to the current working directory of the session
  16. # (not necessarily the same as the cwd of the server we're
  17. # exploiting).
  18. # @return [void]
  19. def register_files_for_cleanup(*files)
  20. @dropped_files ||= []
  21. @dropped_files += files
  22. nil
  23. end
  24. # Singular version
  25. alias register_file_for_cleanup register_files_for_cleanup
  26. # When a new session is created, attempt to delete any files that the
  27. # exploit created.
  28. #
  29. # @param (see Msf::Exploit#on_new_session)
  30. # @return [void]
  31. def on_new_session(session)
  32. super
  33. if session.type == 'meterpreter'
  34. session.core.use('stdapi') unless session.ext.aliases.include?('stdapi')
  35. end
  36. unless @dropped_files && @dropped_files.length > 0
  37. return
  38. end
  39. @dropped_files.delete_if do |file|
  40. exists_before = file_dropper_file_exist?(session, file)
  41. if file_dropper_delete(session, file)
  42. file_dropper_deleted?(session, file, exists_before)
  43. else
  44. false
  45. end
  46. end
  47. end
  48. # While the exploit cleanup do a last attempt to delete any files created
  49. # if there is a file_rm method available. Warn the user if any files were
  50. # not cleaned up.
  51. #
  52. # @see Msf::Exploit#cleanup
  53. # @see Msf::Post::File#file_rm
  54. def cleanup
  55. super
  56. # Check if file_rm method is available (local exploit, mixin support, module support)
  57. if not @dropped_files or @dropped_files.empty?
  58. return
  59. end
  60. if respond_to?(:file_rm)
  61. delay = datastore['FileDropperDelay']
  62. if delay
  63. print_status("Waiting #{delay}s before file cleanup...")
  64. select(nil,nil,nil,delay)
  65. end
  66. @dropped_files.delete_if do |file|
  67. begin
  68. file_rm(file)
  69. # We don't know for sure if file has been deleted, so always warn about it to the user
  70. false
  71. rescue ::Exception => e
  72. vprint_error("Failed to delete #{file}: #{e}")
  73. elog("Failed to delete #{file}: #{e.class}: #{e}")
  74. elog("Call stack:\n#{e.backtrace.join("\n")}")
  75. false
  76. end
  77. end
  78. end
  79. @dropped_files.each do |f|
  80. print_warning("This exploit may require manual cleanup of '#{f}' on the target")
  81. end
  82. end
  83. private
  84. # See if +path+ exists on the remote system and is a regular file
  85. #
  86. # @param path [String] Remote filename to check
  87. # @return [Boolean] True if the file exists, otherwise false.
  88. def file_dropper_file_exist?(session, path)
  89. if session.platform =~ /win/
  90. normalized = file_dropper_win_file(path)
  91. else
  92. normalized = path
  93. end
  94. if session.type == 'meterpreter'
  95. stat = session.fs.file.stat(normalized) rescue nil
  96. return false unless stat
  97. stat.file?
  98. else
  99. if session.platform =~ /win/
  100. f = shell_command_token("cmd.exe /C IF exist \"#{normalized}\" ( echo true )")
  101. if f =~ /true/
  102. f = shell_command_token("cmd.exe /C IF exist \"#{normalized}\\\\\" ( echo false ) ELSE ( echo true )")
  103. end
  104. else
  105. f = session.shell_command_token("test -f \"#{normalized}\" && echo true")
  106. end
  107. return false if f.nil? || f.empty?
  108. return false unless f =~ /true/
  109. true
  110. end
  111. end
  112. # Sends a file deletion command to the remote +session+
  113. #
  114. # @param [String] file The file to delete
  115. # @return [Boolean] True if the delete command has been executed in the remote machine, otherwise false.
  116. def file_dropper_delete(session, file)
  117. win_file = file_dropper_win_file(file)
  118. if session.type == 'meterpreter'
  119. begin
  120. # Meterpreter should do this automatically as part of
  121. # fs.file.rm(). Until that has been implemented, remove the
  122. # read-only flag with a command.
  123. if session.platform =~ /win/
  124. session.shell_command_token(%Q|attrib.exe -r #{win_file}|)
  125. end
  126. session.fs.file.rm(file)
  127. true
  128. rescue ::Rex::Post::Meterpreter::RequestError
  129. false
  130. end
  131. else
  132. win_cmds = [
  133. %Q|attrib.exe -r "#{win_file}"|,
  134. %Q|del.exe /f /q "#{win_file}"|
  135. ]
  136. # We need to be platform-independent here. Since we can't be
  137. # certain that {#target} is accurate because exploits with
  138. # automatic targets frequently change it, we just go ahead and
  139. # run both a windows and a unix command in the same line. One
  140. # of them will definitely fail and the other will probably
  141. # succeed. Doing it this way saves us an extra round-trip.
  142. # Trick shared by @mihi42
  143. session.shell_command_token("rm -f \"#{file}\" >/dev/null ; echo ' & #{win_cmds.join(" & ")} & echo \" ' >/dev/null")
  144. true
  145. end
  146. end
  147. # Checks if a file has been deleted by the current job
  148. #
  149. # @param [String] file The file to check
  150. # @return [Boolean] If the file has been deleted, otherwise false.
  151. def file_dropper_deleted?(session, file, exists_before)
  152. if exists_before && file_dropper_file_exist?(session, file)
  153. print_error("Unable to delete #{file}")
  154. false
  155. elsif exists_before
  156. print_good("Deleted #{file}")
  157. true
  158. else
  159. print_warning("Tried to delete #{file}, unknown result")
  160. true
  161. end
  162. end
  163. # Converts a file path to use the windows separator '\'
  164. #
  165. # @param [String] file The file path to convert
  166. # @return [String] The file path converted
  167. def file_dropper_win_file(file)
  168. file.gsub('/', '\\\\')
  169. end
  170. end
  171. end