# -*- coding: binary -*- require 'singleton' require 'rex/socket' require 'rex/socket/tcp' require 'rex/socket/ssl_tcp' require 'rex/socket/ssl_tcp_server' require 'rex/socket/udp' require 'rex/socket/ip' require 'timeout' ### # # Local communication class factory. # ### class Rex::Socket::Comm::Local include Singleton include Rex::Socket::Comm # # Creates an instance of a socket using the supplied parameters. # def self.create(param) # Work around jRuby socket implementation issues if(RUBY_PLATFORM == 'java') return self.create_jruby(param) end case param.proto when 'tcp' return create_by_type(param, ::Socket::SOCK_STREAM, ::Socket::IPPROTO_TCP) when 'udp' return create_by_type(param, ::Socket::SOCK_DGRAM, ::Socket::IPPROTO_UDP) when 'ip' return create_ip(param) else raise Rex::UnsupportedProtocol.new(param.proto), caller end end # # Creates an instance of a socket using the supplied parameters. # Use various hacks to make this work with jRuby # def self.create_jruby(param) sock = nil # Notify handlers of the before socket create event. self.instance.notify_before_socket_create(self, param) case param.proto when 'tcp' if (param.server?) sock = TCPServer.new(param.localport, param.localhost) klass = Rex::Socket::TcpServer if (param.ssl) klass = Rex::Socket::SslTcpServer end sock.extend(klass) else sock = TCPSocket.new(param.peerhost, param.peerport) klass = Rex::Socket::Tcp if (param.ssl) klass = Rex::Socket::SslTcp end sock.extend(klass) end when 'udp' if (param.server?) sock = UDPServer.new(param.localport, param.localhost) klass = Rex::Socket::UdpServer sock.extend(klass) else sock = UDPSocket.new(param.peerhost, param.peerport) klass = Rex::Socket::Udp sock.extend(klass) end else raise Rex::UnsupportedProtocol.new(param.proto), caller end sock.initsock(param) self.instance.notify_socket_created(self, sock, param) return sock end # # Creates a raw IP socket using the supplied Parameter instance. # Special-cased because of how different it is from UDP/TCP # def self.create_ip(param) self.instance.notify_before_socket_create(self, param) sock = ::Socket.open(::Socket::PF_INET, ::Socket::SOCK_RAW, ::Socket::IPPROTO_RAW) sock.setsockopt(::Socket::IPPROTO_IP, ::Socket::IP_HDRINCL, 1) # Configure broadcast support sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_BROADCAST, true) if (param.bare? == false) sock.extend(::Rex::Socket::Ip) sock.initsock(param) end self.instance.notify_socket_created(self, sock, param) sock end # # Creates a socket using the supplied Parameter instance. # def self.create_by_type(param, type, proto = 0) # Whether to use IPv6 addressing usev6 = false # Detect IPv6 addresses and enable IPv6 accordingly if ( Rex::Socket.support_ipv6?()) # Allow the caller to force IPv6 if (param.v6) usev6 = true end # Force IPv6 mode for non-connected UDP sockets if (type == ::Socket::SOCK_DGRAM and not param.peerhost) # FreeBSD allows IPv6 socket creation, but throws an error on sendto() # Windows 7 SP1 and newer also fail to sendto with IPv6 udp sockets unless Rex::Compat.is_freebsd or Rex::Compat.is_windows usev6 = true end end local = Rex::Socket.resolv_nbo(param.localhost) if param.localhost peer = Rex::Socket.resolv_nbo(param.peerhost) if param.peerhost if (local and local.length == 16) usev6 = true end if (peer and peer.length == 16) usev6 = true end if (usev6) if (local and local.length == 4) if (local == "\x00\x00\x00\x00") param.localhost = '::' elsif (local == "\x7f\x00\x00\x01") param.localhost = '::1' else param.localhost = '::ffff:' + Rex::Socket.getaddress(param.localhost, true) end end if (peer and peer.length == 4) if (peer == "\x00\x00\x00\x00") param.peerhost = '::' elsif (peer == "\x7f\x00\x00\x01") param.peerhost = '::1' else param.peerhost = '::ffff:' + Rex::Socket.getaddress(param.peerhost, true) end end param.v6 = true end else # No IPv6 support param.v6 = false end # Notify handlers of the before socket create event. self.instance.notify_before_socket_create(self, param) # Create the socket sock = nil if (param.v6) sock = ::Socket.new(::Socket::AF_INET6, type, proto) else sock = ::Socket.new(::Socket::AF_INET, type, proto) end # Bind to a given local address and/or port if they are supplied if param.localport or param.localhost begin sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, true) sock.bind(Rex::Socket.to_sockaddr(param.localhost, param.localport)) rescue ::Errno::EADDRNOTAVAIL,::Errno::EADDRINUSE sock.close raise Rex::BindFailed.new(param.localhost, param.localport), caller end end # Configure broadcast support for all datagram sockets if (type == ::Socket::SOCK_DGRAM) sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_BROADCAST, true) end # If a server TCP instance is being created... if (param.server?) sock.listen(256) if (param.bare? == false) klass = Rex::Socket::TcpServer if (param.ssl) klass = Rex::Socket::SslTcpServer end sock.extend(klass) sock.initsock(param) end # Otherwise, if we're creating a client... else chain = [] # If we were supplied with host information if (param.peerhost) # A flag that indicates whether we need to try multiple scopes retry_scopes = false # Always retry with link-local IPv6 addresses if Rex::Socket.is_ipv6?( param.peerhost ) and param.peerhost =~ /^fe80::/ retry_scopes = true end # Prepare a list of scope IDs to try when connecting to # link-level addresses. Read from /proc if it is available, # otherwise increment through the first 255 IDs. @@ip6_lla_scopes ||= [] if @@ip6_lla_scopes.length == 0 and retry_scopes # Linux specific interface lookup code if ::File.exists?( "/proc/self/net/igmp6" ) ::File.open("/proc/self/net/igmp6") do |fd| fd.each_line do |line| line = line.strip tscope, tint, junk = line.split(/\s+/, 3) next if not tint # Specifying lo in any connect call results in the socket # being unusable, even if the correct interface is set. next if tint == "lo" @@ip6_lla_scopes << tscope end end else # Other Unix-like platforms should support a raw scope ID [*(1 .. 255)].map{ |x| @@ip6_lla_scopes << x.to_s } end end ip6_scope_idx = 0 ip = param.peerhost port = param.peerport if param.proxies chain = param.proxies.dup chain.push(['host',param.peerhost,param.peerport]) ip = chain[0][1] port = chain[0][2].to_i end begin begin Timeout.timeout(param.timeout) do sock.connect(Rex::Socket.to_sockaddr(ip, port)) end rescue ::Timeout::Error raise ::Errno::ETIMEDOUT end rescue ::Errno::EHOSTUNREACH,::Errno::ENETDOWN,::Errno::ENETUNREACH,::Errno::ENETRESET,::Errno::EHOSTDOWN,::Errno::EACCES,::Errno::EINVAL # Rescue errors caused by a bad Scope ID for a link-local address if retry_scopes and @@ip6_lla_scopes[ ip6_scope_idx ] ip = param.peerhost + "%" + @@ip6_lla_scopes[ ip6_scope_idx ] ip6_scope_idx += 1 retry end sock.close raise Rex::HostUnreachable.new(ip, port), caller rescue ::Errno::EADDRNOTAVAIL,::Errno::EADDRINUSE sock.close raise Rex::InvalidDestination.new(ip, port), caller rescue Errno::ETIMEDOUT sock.close raise Rex::ConnectionTimeout.new(ip, port), caller rescue ::Errno::ECONNRESET,::Errno::ECONNREFUSED,::Errno::ENOTCONN,::Errno::ECONNABORTED sock.close # Report the actual thing we were trying to connect to here, not # param.peerhost, since that's the eventual target at the end of the # proxy chain raise Rex::ConnectionRefused.new(ip, port.to_i), caller end end if (param.bare? == false) case param.proto when 'tcp' klass = Rex::Socket::Tcp sock.extend(klass) sock.initsock(param) when 'udp' sock.extend(Rex::Socket::Udp) sock.initsock(param) end end if chain.size > 1 chain.each_with_index { |proxy, i| next_hop = chain[i + 1] if next_hop proxy(sock, proxy[0], next_hop[1], next_hop[2]) end } end # Now extend the socket with SSL and perform the handshake if(param.bare? == false and param.ssl) klass = Rex::Socket::SslTcp sock.extend(klass) sock.initsock(param) end end # Notify handlers that a socket has been created. self.instance.notify_socket_created(self, sock, param) sock end def self.proxy(sock, type, host, port) case type.downcase when 'sapni' packet_type = 'NI_ROUTE' route_info_version = 2 ni_version = 39 num_of_entries = 2 talk_mode = 1 # ref: http://help.sap.com/saphelp_dimp50/helpdata/En/f8/bb960899d743378ccb8372215bb767/content.htm num_rest_nodes = 1 shost, sport = sock.peerinfo.split(":") first_route_item = [shost, 0, sport, 0, 0].pack("A*CA*cc") route_data = [first_route_item.length, first_route_item].pack("NA*") route_data << [host, 0, port.to_s, 0, 0].pack("A*CA*cc") ni_packet = [ packet_type, 0, route_info_version, ni_version, num_of_entries, talk_mode, 0, 0, num_rest_nodes ].pack("A8c8") # Add the data block, according to sap documentation: # A 4-byte header precedes each data block. These 4 bytes give the # length of the data block (length without leading 4 bytes) # The data block (the route data) ni_packet << [route_data.length - 4].pack('N') + route_data # Now that we've built the whole packet, prepend its length before writing it to the wire ni_packet = [ni_packet.length].pack('N') + ni_packet size = sock.put(ni_packet) if size != ni_packet.length raise Rex::ConnectionProxyError.new(host, port, type, "Failed to send the entire request to the proxy"), caller end begin ret_len = sock.get_once(4, 30).unpack('N')[0] if ret_len and ret_len != 0 ret = sock.get_once(ret_len, 30) end rescue IOError raise Rex::ConnectionProxyError.new(host, port, type, "Failed to receive a response from the proxy"), caller end if ret and ret.length < 4 raise Rex::ConnectionProxyError.new(host, port, type, "Failed to receive a complete response from the proxy"), caller end if ret =~ /NI_RTERR/ case ret when /timed out/ raise Rex::ConnectionProxyError.new(host, port, type, "Connection to remote host #{host} timed out") when /refused/ raise Rex::ConnectionProxyError.new(host, port, type, "Connection to remote port #{port} closed") when /denied/ raise Rex::ConnectionProxyError.new(host, port, type, "Connection to #{host}:#{port} blocked by ACL") else raise Rex::ConnectionProxyError.new(host, port, type, "Connection to #{host}:#{port} failed (Unknown fail)") end elsif ret =~ /NI_PONG/ # success case # would like to print this "[*] remote native connection to #{host}:#{port} established\n" else raise Rex::ConnectionProxyError.new(host, port, type, "Connection to #{host}:#{port} failed (Unknown fail)") end when 'http' setup = "CONNECT #{host}:#{port} HTTP/1.0\r\n\r\n" size = sock.put(setup) if (size != setup.length) raise Rex::ConnectionProxyError.new(host, port, type, "Failed to send the entire request to the proxy"), caller end begin ret = sock.get_once(39,30) rescue IOError raise Rex::ConnectionProxyError.new(host, port, type, "Failed to receive a response from the proxy"), caller end if ret.nil? raise Rex::ConnectionProxyError.new(host, port, type, "Failed to receive a response from the proxy"), caller end resp = Rex::Proto::Http::Response.new resp.update_cmd_parts(ret.split(/\r?\n/)[0]) if resp.code != 200 raise Rex::ConnectionProxyError.new(host, port, type, "The proxy returned a non-OK response"), caller end when 'socks4' setup = [4,1,port.to_i].pack('CCn') + Socket.gethostbyname(host)[3] + Rex::Text.rand_text_alpha(rand(8)+1) + "\x00" size = sock.put(setup) if (size != setup.length) raise Rex::ConnectionProxyError.new(host, port, type, "Failed to send the entire request to the proxy"), caller end begin ret = sock.get_once(8, 30) rescue IOError raise Rex::ConnectionProxyError.new(host, port, type, "Failed to receive a response from the proxy"), caller end if (ret.nil? or ret.length < 8) raise Rex::ConnectionProxyError.new(host, port, type, "Failed to receive a complete response from the proxy"), caller end if ret[1,1] != "\x5a" raise Rex::ConnectionProxyError.new(host, port, type, "Proxy responded with error code #{ret[0,1].unpack("C")[0]}"), caller end when 'socks5' auth_methods = [5,1,0].pack('CCC') size = sock.put(auth_methods) if (size != auth_methods.length) raise Rex::ConnectionProxyError.new(host, port, type, "Failed to send the entire request to the proxy"), caller end ret = sock.get_once(2,30) if (ret[1,1] == "\xff") raise Rex::ConnectionProxyError.new(host, port, type, "The proxy requires authentication"), caller end if (Rex::Socket.is_ipv4?(host)) addr = Rex::Socket.gethostbyname(host)[3] setup = [5,1,0,1].pack('C4') + addr + [port.to_i].pack('n') elsif (Rex::Socket.support_ipv6? and Rex::Socket.is_ipv6?(host)) # IPv6 stuff all untested addr = Rex::Socket.gethostbyname(host)[3] setup = [5,1,0,4].pack('C4') + addr + [port.to_i].pack('n') else # Then it must be a domain name. # Unfortunately, it looks like the host has always been # resolved by the time it gets here, so this code never runs. setup = [5,1,0,3].pack('C4') + [host.length].pack('C') + host + [port.to_i].pack('n') end size = sock.put(setup) if (size != setup.length) raise Rex::ConnectionProxyError.new(host, port, type, "Failed to send the entire request to the proxy"), caller end begin response = sock.get_once(10, 30) rescue IOError raise Rex::ConnectionProxyError.new(host, port, type, "Failed to receive a response from the proxy"), caller end if (response.nil? or response.length < 10) raise Rex::ConnectionProxyError.new(host, port, type, "Failed to receive a complete response from the proxy"), caller end if response[1,1] != "\x00" raise Rex::ConnectionProxyError.new(host, port, type, "Proxy responded with error code #{response[1,1].unpack("C")[0]}"), caller end else raise RuntimeError, "The proxy type specified is not valid", caller end end ## # # Registration # ## def self.register_event_handler(handler) # :nodoc: self.instance.register_event_handler(handler) end def self.deregister_event_handler(handler) # :nodoc: self.instance.deregister_event_handler(handler) end def self.each_event_handler(handler) # :nodoc: self.instance.each_event_handler(handler) end end