# -*- coding: binary -*- # sf - Sept 2010 # require 'thread' require 'rex/logging' require 'rex/socket' module Rex module Proto module Proxy # # A Socks4a proxy server. # class Socks4a # # A client connected to the Socks4a server. # class Client REQUEST_VERSION = 4 REPLY_VERSION = 0 COMMAND_CONNECT = 1 COMMAND_BIND = 2 REQUEST_GRANTED = 90 REQUEST_REJECT_FAILED = 91 REQUEST_REJECT_CONNECT = 92 REQUEST_REJECT_USERID = 93 HOST = 1 PORT = 2 # # A Socks4a packet. # class Packet def initialize @version = REQUEST_VERSION @command = 0 @dest_port = 0 @dest_ip = '0.0.0.0' @userid = '' end # # A helper function to recv in a Socks4a packet byte by byte. # # sf: we could just call raw = sock.get_once but some clients # seem to need reading this byte by byte instead. # def Packet.recv( sock, timeout=30 ) raw = '' # read in the 8 byte header while( raw.length < 8 ) raw << sock.read( 1 ) end # if its a request there will be more data if( raw[0..0].unpack( 'C' ).first == REQUEST_VERSION ) # read in the userid while( raw[8..raw.length].index( "\x00" ) == nil ) raw << sock.read( 1 ) end # if a hostname is going to be present, read it in ip = raw[4..7].unpack( 'N' ).first if( ( ip & 0xFFFFFF00 ) == 0x00000000 and ( ip & 0x000000FF ) != 0x00 ) hostname = '' while( hostname.index( "\x00" ) == nil ) hostname += sock.read( 1 ) end raw << hostname end end # create a packet from this raw data... packet = Packet.new packet.from_r( raw ) ? packet : nil end # # Pack a packet into raw bytes for transmitting on the wire. # def to_r raw = [ @version, @command, @dest_port, Rex::Socket.addr_atoi( @dest_ip ) ].pack( 'CCnN' ) return raw if( @userid.empty? ) return raw + [ @userid ].pack( 'Z*' ) end # # Unpack a raw packet into its components. # def from_r( raw ) return false if( raw.length < 8 ) @version = raw[0..0].unpack( 'C' ).first return false if( @version != REQUEST_VERSION and @version != REPLY_VERSION ) @command = raw[1..1].unpack( 'C' ).first @dest_port = raw[2..3].unpack( 'n' ).first @dest_ip = Rex::Socket.addr_itoa( raw[4..7].unpack( 'N' ).first ) if( raw.length > 8 ) @userid = raw[8..raw.length].unpack( 'Z*' ).first # if this is a socks4a request we can resolve the provided hostname if( self.is_hostname? ) hostname = raw[(8+@userid.length+1)..raw.length].unpack( 'Z*' ).first @dest_ip = self.resolve( hostname ) # fail if we couldnt resolve the hostname return false if( not @dest_ip ) end else @userid = '' end return true end def is_connect? @command == COMMAND_CONNECT ? true : false end def is_bind? @command == COMMAND_BIND ? true : false end attr_accessor :version, :command, :dest_port, :dest_ip, :userid protected # # Resolve the given hostname into a dotted IP address. # def resolve( hostname ) if( not hostname.empty? ) begin return Rex::Socket.addr_itoa( Rex::Socket.gethostbyname( hostname )[3].unpack( 'N' ).first ) rescue ::SocketError return nil end end return nil end # # As per the Socks4a spec, check to see if the provided dest_ip is 0.0.0.XX # which indicates after the @userid field contains a hostname to resolve. # def is_hostname? ip = Rex::Socket.addr_atoi( @dest_ip ) if( ip & 0xFFFFFF00 == 0x00000000 ) return true if( ip & 0x000000FF != 0x00 ) end return false end end # # A mixin for a socket to perform a relay to another socket. # module Relay # # Relay data coming in from relay_sock to this socket. # def relay( relay_client, relay_sock ) @relay_client = relay_client @relay_sock = relay_sock # start the relay thread (modified from Rex::IO::StreamAbstraction) @relay_thread = Rex::ThreadFactory.spawn("SOCKS4AProxyServerRelay", false) do loop do closed = false buf = nil begin s = Rex::ThreadSafe.select( [ @relay_sock ], nil, nil, 0.2 ) if( s == nil || s[0] == nil ) next end rescue closed = true end if( closed == false ) begin buf = @relay_sock.sysread( 32768 ) closed = true if( buf == nil ) rescue closed = true end end if( closed == false ) total_sent = 0 total_length = buf.length while( total_sent < total_length ) begin data = buf[total_sent, buf.length] sent = self.write( data ) if( sent > 0 ) total_sent += sent end rescue closed = true break end end end if( closed ) @relay_client.stop ::Thread.exit end end end end end # # Create a new client connected to the server. # def initialize( server, sock ) @server = server @lsock = sock @rsock = nil @client_thread = nil @mutex = ::Mutex.new end # # Start handling the client connection. # def start # create a thread to handle this client request so as to not block the socks4a server @client_thread = Rex::ThreadFactory.spawn("SOCKS4AProxyClient", false) do begin @server.add_client( self ) # get the initial client request packet request = Packet.recv( @lsock ) raise "Invalid Socks4 request packet received." if not request # handle the request begin # handle socks4a conenct requests if( request.is_connect? ) # perform the connection request params = { 'PeerHost' => request.dest_ip, 'PeerPort' => request.dest_port, } params['Context'] = @server.opts['Context'] if @server.opts.has_key?('Context') @rsock = Rex::Socket::Tcp.create( params ) # and send back success to the client response = Packet.new response.version = REPLY_VERSION response.command = REQUEST_GRANTED @lsock.put( response.to_r ) # handle socks4a bind requests elsif( request.is_bind? ) # create a server socket for this request params = { 'LocalHost' => '0.0.0.0', 'LocalPort' => 0, } params['Context'] = @server.opts['Context'] if @server.opts.has_key?('Context') bsock = Rex::Socket::TcpServer.create( params ) # send back the bind success to the client response = Packet.new response.version = REPLY_VERSION response.command = REQUEST_GRANTED response.dest_ip = '0.0.0.0' response.dest_port = bsock.getlocalname()[PORT] @lsock.put( response.to_r ) # accept a client connection (2 minute timeout as per spec) begin ::Timeout.timeout( 120 ) do @rsock = bsock.accept end rescue ::Timeout::Error raise "Timeout reached on accept request." end # close the listening socket bsock.close # verify the connection is from the dest_ip origionally specified by the client rpeer = @rsock.getpeername raise "Got connection from an invalid peer." if( rpeer[HOST] != request.dest_ip ) # send back the client connect success to the client # # sf: according to the spec we send this response back to the client, however # I have seen some clients who bawk if they get this second response. # response = Packet.new response.version = REPLY_VERSION response.command = REQUEST_GRANTED response.dest_ip = rpeer[HOST] response.dest_port = rpeer[PORT] @lsock.put( response.to_r ) else raise "Unknown request command received #{request.command} received." end rescue # send back failure to the client response = Packet.new response.version = REPLY_VERSION response.command = REQUEST_REJECT_FAILED @lsock.put( response.to_r ) # raise an exception to close this client connection raise "Failed to handle the clients request." end # setup the two way relay for full duplex io @lsock.extend( Relay ) @rsock.extend( Relay ) # start the socket relays... @lsock.relay( self, @rsock ) @rsock.relay( self, @lsock ) rescue wlog( "Client.start - #{$!}" ) self.stop end end end # # Stop handling the client connection. # def stop @mutex.synchronize do if( not @closed ) begin @lsock.close if @lsock rescue end begin @rsock.close if @rsock rescue end @client_thread.kill if( @client_thread and @client_thread.alive? ) @server.remove_client( self ) @closed = true end end end end # # Create a new Socks4a server. # def initialize( opts={} ) @opts = { 'ServerHost' => '0.0.0.0', 'ServerPort' => 1080 } @opts = @opts.merge( opts ) @server = nil @clients = ::Array.new @running = false @server_thread = nil end # # Check if the server is running. # def is_running? return @running end # # Start the Socks4a server. # def start begin # create the servers main socket (ignore the context here because we don't want a remote bind) @server = Rex::Socket::TcpServer.create( 'LocalHost' => @opts['ServerHost'], 'LocalPort' => @opts['ServerPort'] ) # signal we are now running @running = true # start the servers main thread to pick up new clients @server_thread = Rex::ThreadFactory.spawn("SOCKS4AProxyServer", false) do while( @running ) do begin # accept the client connection sock = @server.accept # and fire off a new client instance to handle it Client.new( self, sock ).start rescue wlog( "Socks4a.start - server_thread - #{$!}" ) end end end rescue wlog( "Socks4a.start - #{$!}" ) return false end return true end # # Block while the server is running. # def join @server_thread.join if @server_thread end # # Stop the Socks4a server. # def stop if( @running ) # signal we are no longer running @running = false # stop any clients we have (create a new client array as client.stop will delete from @clients) clients = [] clients.concat( @clients ) clients.each do | client | client.stop end # close the server socket @server.close if @server # if the server thread did not terminate gracefully, kill it. @server_thread.kill if( @server_thread and @server_thread.alive? ) end return !@running end def add_client( client ) @clients << client end def remove_client( client ) @clients.delete( client ) end attr_reader :opts end end; end; end