rapid7/metasploit-framework

View on GitHub
modules/post/multi/gather/check_malware.rb

Summary

Maintainability
A
3 hrs
Test Coverage
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'net/http'
require 'uri'

class MetasploitModule < Msf::Post
  include Msf::Post::File

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Multi Gather Malware Verifier',
        'Description' => %q{
          This module will check a file for malware on VirusTotal based on the checksum.
        },
        'License' => MSF_LICENSE,
        'Author' => [ 'sinn3r'],
        'Platform' => [ 'osx', 'win', 'linux' ],
        'SessionTypes' => [ 'shell', 'meterpreter' ]
      )
    )

    register_options(
      [
        OptString.new('APIKEY', [true, 'VirusTotal API key', '501caf66349cc7357eb4398ac3298fdd03dec01a3e2f3ad576525aa7b57a1987']),
        OptString.new('REMOTEFILE', [true, 'A file to check from the remote machine'])

      ]
    )
  end

  def rhost
    session.session_host
  end

  def get_report(api_key, checksum)
    #
    # We have to use Net::HTTP instead of HttpClient because of the following error:
    # The supplied module name is ambiguous: undefined method `register_autofilter_ports'
    #
    url = URI.parse('https://www.virustotal.com/vtapi/v2/file/report')
    req = Net::HTTP::Post.new(url.path, initheader = { 'Host' => 'www.virustotal.com' })
    req.set_form_data({ 'apikey' => api_key, 'resource' => checksum })
    http = Net::HTTP.new(url.host, url.port)
    http.use_ssl = true
    res = http.start { |http| http.request(req) }

    unless res
      print_error("#{rhost} - Connection timed out")
      return ''
    end

    case res.code
    when 204
      print_error("#{rhost} - You have reached the request limit, please wait for one minute to try again")
      return ''
    when 403
      print_error("#{rhost} - No privilege to execute this request probably due to an invalye API key")
      return ''
    end

    body = ''
    begin
      body = JSON.parse(res.body)
    rescue JSON::ParserError
      print_error("#{rhost} - Unable to parse the response")
      return body
    end

    body
  end

  def show_report(res, filename)
    md5 = res['md5'] || ''
    sha1 = res['sha1'] || ''
    sha256 = res['sha256'] || ''

    print_status("#{rhost} - MD5: #{md5}") unless md5.blank?
    print_status("#{rhost} - SHA1: #{sha1}") unless sha1.blank?
    print_status("#{rhost} - SHA256: #{sha256}") unless sha256.blank?

    tbl = Rex::Text::Table.new(
      'Header' => "Analysis Report: #{filename} (#{res['positives']} / #{res['total']}): #{res['sha256']}",
      'Indent' => 1,
      'Columns' => ['Antivirus', 'Detected', 'Version', 'Result', 'Update']
    )

    res['scans'].each do |result|
      product = result[0]
      detected = result[1]['detected'].to_s
      version = result[1]['version'] || ''
      sig_name = result[1]['result'] || ''
      timestamp = result[1]['update'] || ''

      tbl << [product, detected, version, sig_name, timestamp]
    end

    report_note({
      host: session,
      type: 'malware.sample',
      data: tbl.to_csv
    })
    print_status tbl.to_s
  end

  def run
    filename = datastore['REMOTEFILE']
    api_key = datastore['APIKEY']

    unless file?(filename)
      print_error("#{rhost} - File not found: #{filename}")
      return
    end

    checksum = file_remote_digestsha1(filename)
    print_status("#{rhost} - Checking: #{filename}...")
    report = get_report(api_key, checksum)

    return if report.blank?

    print_status("#{rhost} - VirusTotal message: #{report['verbose_msg']}")
    if report['response_code'] == 1
      show_report(report, File.basename(filename))
    end
  end
end