c32ef4a3be
Add an explicit require for the new cert_provider in framework.rb in case it has not yet been loaded. This should address the Travis failure on initial PR, although the gem version in socket has not been updated, so this might take a bit to propagate. In the end, if the dependency already gives us this functionality by the time we call Rex::Socket::Ssl then this commit can safely be dropped
535 lines
13 KiB
Ruby
535 lines
13 KiB
Ruby
# -*- coding: binary -*-
|
|
|
|
#
|
|
# Standard Library
|
|
#
|
|
|
|
require 'monitor'
|
|
|
|
#
|
|
# Project
|
|
#
|
|
|
|
require 'metasploit/framework/version'
|
|
require 'msf/base/config'
|
|
require 'msf/core'
|
|
require 'msf/util'
|
|
|
|
module Msf
|
|
|
|
###
|
|
#
|
|
# This class is the primary context that modules, scripts, and user
|
|
# interfaces interact with. It ties everything together.
|
|
#
|
|
###
|
|
class Framework
|
|
include MonitorMixin
|
|
|
|
#
|
|
# Versioning information
|
|
#
|
|
|
|
Major = Metasploit::Framework::Version::MAJOR
|
|
Minor = Metasploit::Framework::Version::MINOR
|
|
Point = Metasploit::Framework::Version::PATCH
|
|
Release = "-#{Metasploit::Framework::Version::PRERELEASE}"
|
|
Version = Metasploit::Framework::VERSION
|
|
|
|
Revision = "$Revision$"
|
|
|
|
# EICAR canary
|
|
EICARCorrupted = ::Msf::Util::EXE.is_eicar_corrupted?
|
|
|
|
#
|
|
# Mixin meant to be included into all classes that can have instances that
|
|
# should be tied to the framework, such as modules.
|
|
#
|
|
module Offspring
|
|
|
|
#
|
|
# A reference to the framework instance from which this offspring was
|
|
# derived.
|
|
#
|
|
attr_accessor :framework
|
|
end
|
|
|
|
require 'msf/core/thread_manager'
|
|
require 'msf/core/module_manager'
|
|
require 'msf/core/session_manager'
|
|
require 'msf/core/plugin_manager'
|
|
require 'msf/core/db_manager'
|
|
require 'msf/core/event_dispatcher'
|
|
require 'rex/json_hash_file'
|
|
require 'msf/core/cert_provider'
|
|
|
|
#
|
|
# Creates an instance of the framework context.
|
|
#
|
|
def initialize(options={})
|
|
self.options = options
|
|
# call super to initialize MonitorMixin. #synchronize won't work without this.
|
|
super()
|
|
|
|
# Allow specific module types to be loaded
|
|
types = options[:module_types] || Msf::MODULE_TYPES
|
|
|
|
self.events = EventDispatcher.new(self)
|
|
self.modules = ModuleManager.new(self,types)
|
|
self.datastore = DataStore.new
|
|
self.jobs = Rex::JobContainer.new
|
|
self.plugins = PluginManager.new(self)
|
|
self.uuid_db = Rex::JSONHashFile.new(::File.join(Msf::Config.config_directory, "payloads.json"))
|
|
self.browser_profiles = Hash.new
|
|
|
|
# Configure the thread factory
|
|
Rex::ThreadFactory.provider = Metasploit::Framework::ThreadFactoryProvider.new(framework: self)
|
|
|
|
# Configure the SSL certificate generator
|
|
Rex::Socket::Ssl.cert_provider = Msf::Ssl::CertProvider
|
|
|
|
subscriber = FrameworkEventSubscriber.new(self)
|
|
events.add_exploit_subscriber(subscriber)
|
|
events.add_session_subscriber(subscriber)
|
|
events.add_general_subscriber(subscriber)
|
|
events.add_db_subscriber(subscriber)
|
|
events.add_ui_subscriber(subscriber)
|
|
end
|
|
|
|
def inspect
|
|
"#<Framework (#{sessions.length} sessions, #{jobs.length} jobs, #{plugins.length} plugins#{db.active ? ", #{db.driver} database active" : ""})>"
|
|
end
|
|
|
|
#
|
|
# Returns the module set for encoders.
|
|
#
|
|
def encoders
|
|
return modules.encoders
|
|
end
|
|
|
|
#
|
|
# Returns the module set for exploits.
|
|
#
|
|
def exploits
|
|
return modules.exploits
|
|
end
|
|
|
|
#
|
|
# Returns the module set for nops
|
|
#
|
|
def nops
|
|
return modules.nops
|
|
end
|
|
|
|
#
|
|
# Returns the module set for payloads
|
|
#
|
|
def payloads
|
|
return modules.payloads
|
|
end
|
|
|
|
#
|
|
# Returns the module set for auxiliary modules
|
|
#
|
|
def auxiliary
|
|
return modules.auxiliary
|
|
end
|
|
|
|
#
|
|
# Returns the module set for post modules
|
|
#
|
|
def post
|
|
return modules.post
|
|
end
|
|
|
|
#
|
|
# Returns the framework version in Major.Minor format.
|
|
#
|
|
def version
|
|
Version
|
|
end
|
|
|
|
#
|
|
# Event management interface for registering event handler subscribers and
|
|
# for interacting with the correlation engine.
|
|
#
|
|
attr_reader :events
|
|
#
|
|
# Module manager that contains information about all loaded modules,
|
|
# regardless of type.
|
|
#
|
|
attr_reader :modules
|
|
#
|
|
# The global framework datastore that can be used by modules.
|
|
#
|
|
attr_reader :datastore
|
|
#
|
|
# The framework instance's aux manager. The aux manager is responsible
|
|
# for collecting and cataloging all aux information that comes in from
|
|
# aux modules.
|
|
#
|
|
attr_reader :auxmgr
|
|
#
|
|
# Background job management specific to things spawned from this instance
|
|
# of the framework.
|
|
#
|
|
attr_reader :jobs
|
|
#
|
|
# The framework instance's plugin manager. The plugin manager is
|
|
# responsible for exposing an interface that allows for the loading and
|
|
# unloading of plugins.
|
|
#
|
|
attr_reader :plugins
|
|
#
|
|
# The framework instance's payload uuid database. The payload uuid
|
|
# database is used to record and match the unique ID values embedded
|
|
# into generated payloads.
|
|
#
|
|
attr_reader :uuid_db
|
|
#
|
|
# The framework instance's browser profile store. These profiles are
|
|
# generated by client-side modules and need to be shared across
|
|
# different contexts.
|
|
#
|
|
attr_reader :browser_profiles
|
|
|
|
# The framework instance's db manager. The db manager
|
|
# maintains the database db and handles db events
|
|
#
|
|
# @return [Msf::DBManager]
|
|
def db
|
|
synchronize {
|
|
@db ||= Msf::DBManager.new(self, options)
|
|
}
|
|
end
|
|
|
|
# Session manager that tracks sessions associated with this framework
|
|
# instance over the course of their lifetime.
|
|
#
|
|
# @return [Msf::SessionManager]
|
|
def sessions
|
|
synchronize {
|
|
@sessions ||= Msf::SessionManager.new(self)
|
|
}
|
|
end
|
|
|
|
# The framework instance's thread manager. The thread manager
|
|
# provides a cleaner way to manage spawned threads
|
|
#
|
|
# @return [Msf::ThreadManager]
|
|
def threads
|
|
synchronize {
|
|
@threads ||= Msf::ThreadManager.new(self)
|
|
}
|
|
end
|
|
|
|
# Whether {#threads} has been initialized
|
|
#
|
|
# @return [true] if {#threads} has been initialized
|
|
# @return [false] otherwise
|
|
def threads?
|
|
synchronize {
|
|
instance_variable_defined? :@threads
|
|
}
|
|
end
|
|
|
|
def search(match, logger: nil)
|
|
# Check if the database is usable
|
|
use_db = true
|
|
if self.db
|
|
if !(self.db.migrated && self.db.modules_cached)
|
|
logger.print_warning("Module database cache not built yet, using slow search") if logger
|
|
use_db = false
|
|
end
|
|
else
|
|
logger.print_warning("Database not connected, using slow search") if logger
|
|
use_db = false
|
|
end
|
|
|
|
# Used the database for search
|
|
if use_db
|
|
return self.db.search_modules(match)
|
|
end
|
|
|
|
# Do an in-place search
|
|
matches = []
|
|
[ self.exploits, self.auxiliary, self.post, self.payloads, self.nops, self.encoders ].each do |mset|
|
|
mset.each do |m|
|
|
begin
|
|
o = mset.create(m[0])
|
|
if o && !o.search_filter(match)
|
|
matches << o
|
|
end
|
|
rescue
|
|
end
|
|
end
|
|
end
|
|
matches
|
|
end
|
|
|
|
protected
|
|
|
|
# @!attribute options
|
|
# Options passed to {#initialize}
|
|
#
|
|
# @return [Hash]
|
|
attr_accessor :options
|
|
|
|
attr_writer :events # :nodoc:
|
|
attr_writer :modules # :nodoc:
|
|
attr_writer :datastore # :nodoc:
|
|
attr_writer :auxmgr # :nodoc:
|
|
attr_writer :jobs # :nodoc:
|
|
attr_writer :plugins # :nodoc:
|
|
attr_writer :db # :nodoc:
|
|
attr_writer :uuid_db # :nodoc:
|
|
attr_writer :browser_profiles # :nodoc:
|
|
end
|
|
|
|
class FrameworkEventSubscriber
|
|
include Framework::Offspring
|
|
def initialize(framework)
|
|
self.framework = framework
|
|
end
|
|
|
|
def report_event(data)
|
|
if framework.db.active
|
|
framework.db.report_event(data)
|
|
end
|
|
end
|
|
|
|
include GeneralEventSubscriber
|
|
|
|
#
|
|
# Generic handler for module events
|
|
#
|
|
def module_event(name, instance, opts={})
|
|
if framework.db.active
|
|
event = {
|
|
:workspace => framework.db.find_workspace(instance.workspace),
|
|
:name => name,
|
|
:username => instance.owner,
|
|
:info => {
|
|
:module_name => instance.fullname,
|
|
:module_uuid => instance.uuid
|
|
}.merge(opts)
|
|
}
|
|
|
|
report_event(event)
|
|
end
|
|
end
|
|
|
|
##
|
|
# :category: ::Msf::GeneralEventSubscriber implementors
|
|
def on_module_run(instance)
|
|
opts = { :datastore => instance.datastore.to_h }
|
|
module_event('module_run', instance, opts)
|
|
end
|
|
|
|
##
|
|
# :category: ::Msf::GeneralEventSubscriber implementors
|
|
def on_module_complete(instance)
|
|
module_event('module_complete', instance)
|
|
end
|
|
|
|
##
|
|
# :category: ::Msf::GeneralEventSubscriber implementors
|
|
def on_module_error(instance, exception=nil)
|
|
module_event('module_error', instance, :exception => exception.to_s)
|
|
end
|
|
|
|
include ::Msf::UiEventSubscriber
|
|
##
|
|
# :category: ::Msf::UiEventSubscriber implementors
|
|
def on_ui_command(command)
|
|
if framework.db.active
|
|
report_event(:name => "ui_command", :info => {:command => command})
|
|
end
|
|
end
|
|
|
|
##
|
|
# :category: ::Msf::UiEventSubscriber implementors
|
|
def on_ui_stop()
|
|
if framework.db.active
|
|
report_event(:name => "ui_stop")
|
|
end
|
|
end
|
|
|
|
##
|
|
# :category: ::Msf::UiEventSubscriber implementors
|
|
def on_ui_start(rev)
|
|
#
|
|
# The database is not active at startup time unless msfconsole was
|
|
# started with a database.yml, so this event won't always be saved to
|
|
# the db. Not great, but best we can do.
|
|
#
|
|
info = { :revision => rev }
|
|
report_event(:name => "ui_start", :info => info)
|
|
end
|
|
|
|
require 'msf/core/session'
|
|
|
|
include ::Msf::SessionEvent
|
|
|
|
#
|
|
# Generic handler for session events
|
|
#
|
|
def session_event(name, session, opts={})
|
|
address = session.session_host
|
|
|
|
if not (address and address.length > 0)
|
|
elog("Session with no session_host/target_host/tunnel_peer")
|
|
dlog("#{session.inspect}", LEV_3)
|
|
return
|
|
end
|
|
|
|
if framework.db.active
|
|
ws = framework.db.find_workspace(session.workspace)
|
|
event = {
|
|
:workspace => ws,
|
|
:username => session.username,
|
|
:name => name,
|
|
:host => address,
|
|
:info => {
|
|
:session_id => session.sid,
|
|
:session_info => session.info,
|
|
:session_uuid => session.uuid,
|
|
:session_type => session.type,
|
|
:username => session.username,
|
|
:target_host => address,
|
|
:via_exploit => session.via_exploit,
|
|
:via_payload => session.via_payload,
|
|
:tunnel_peer => session.tunnel_peer,
|
|
:exploit_uuid => session.exploit_uuid
|
|
}.merge(opts)
|
|
}
|
|
report_event(event)
|
|
end
|
|
end
|
|
|
|
|
|
##
|
|
# :category: ::Msf::SessionEvent implementors
|
|
def on_session_open(session)
|
|
opts = { :datastore => session.exploit_datastore.to_h, :critical => true }
|
|
session_event('session_open', session, opts)
|
|
framework.db.report_session(:session => session)
|
|
end
|
|
|
|
##
|
|
# :category: ::Msf::SessionEvent implementors
|
|
def on_session_upload(session, lpath, rpath)
|
|
session_event('session_upload', session, :local_path => lpath, :remote_path => rpath)
|
|
framework.db.report_session_event({
|
|
:etype => 'upload',
|
|
:session => session,
|
|
:local_path => lpath,
|
|
:remote_path => rpath
|
|
})
|
|
end
|
|
##
|
|
# :category: ::Msf::SessionEvent implementors
|
|
def on_session_download(session, rpath, lpath)
|
|
session_event('session_download', session, :local_path => lpath, :remote_path => rpath)
|
|
framework.db.report_session_event({
|
|
:etype => 'download',
|
|
:session => session,
|
|
:local_path => lpath,
|
|
:remote_path => rpath
|
|
})
|
|
end
|
|
|
|
##
|
|
# :category: ::Msf::SessionEvent implementors
|
|
def on_session_close(session, reason='')
|
|
session_event('session_close', session)
|
|
if session.db_record
|
|
# Don't bother saving here, the session's cleanup method will take
|
|
# care of that later.
|
|
session.db_record.close_reason = reason
|
|
session.db_record.closed_at = Time.now.utc
|
|
end
|
|
end
|
|
|
|
#def on_session_interact(session)
|
|
# $stdout.puts('session_interact', session.inspect)
|
|
#end
|
|
|
|
##
|
|
# :category: ::Msf::SessionEvent implementors
|
|
def on_session_command(session, command)
|
|
session_event('session_command', session, :command => command)
|
|
framework.db.report_session_event({
|
|
:etype => 'command',
|
|
:session => session,
|
|
:command => command
|
|
})
|
|
end
|
|
|
|
##
|
|
# :category: ::Msf::SessionEvent implementors
|
|
def on_session_output(session, output)
|
|
# Break up the output into chunks that will fit into the database.
|
|
buff = output.dup
|
|
chunks = []
|
|
if buff.length > 1024
|
|
while buff.length > 0
|
|
chunks << buff.slice!(0,1024)
|
|
end
|
|
else
|
|
chunks << buff
|
|
end
|
|
chunks.each { |chunk|
|
|
session_event('session_output', session, :output => chunk)
|
|
framework.db.report_session_event({
|
|
:etype => 'output',
|
|
:session => session,
|
|
:output => chunk
|
|
})
|
|
}
|
|
end
|
|
|
|
##
|
|
# :category: ::Msf::SessionEvent implementors
|
|
def on_session_route(session, route)
|
|
framework.db.report_session_route(session, route)
|
|
end
|
|
|
|
##
|
|
# :category: ::Msf::SessionEvent implementors
|
|
def on_session_route_remove(session, route)
|
|
framework.db.report_session_route_remove(session, route)
|
|
end
|
|
|
|
##
|
|
# :category: ::Msf::SessionEvent implementors
|
|
def on_session_script_run(session, script)
|
|
framework.db.report_session_event({
|
|
:etype => 'script_run',
|
|
:session => session,
|
|
:local_path => script
|
|
})
|
|
end
|
|
|
|
##
|
|
# :category: ::Msf::SessionEvent implementors
|
|
def on_session_module_run(session, mod)
|
|
framework.db.report_session_event({
|
|
:etype => 'module_run',
|
|
:session => session,
|
|
:local_path => mod.fullname
|
|
})
|
|
end
|
|
|
|
#
|
|
# This is covered by on_module_run and on_session_open, so don't bother
|
|
#
|
|
#require 'msf/core/exploit'
|
|
#include ExploitEvent
|
|
#def on_exploit_success(exploit, session)
|
|
#end
|
|
|
|
end
|
|
end
|
|
|