metasploit-framework/lib/rex/proto/proxy/socks4a.rb

442 lines
12 KiB
Ruby

# -*- 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