class Ronin::CLI::Commands::Netcat

A ‘netcat` clone written in Ruby using the [async-io] gem.

[async-io]: github.com/socketry/async-io#readme

## Usage

[options] [--tcp | --udp | --ssl | --tls] {HOST PORT | -l [HOST] PORT | --unix PATH}

## Options

-v, --verbose                    Enables verbose output
    --tcp                        Uses the TCP protocol
    --udp                        Uses the UDP protocol
-U, --unix PATH                  Uses the UNIX socket protocol
-l, --listen                     Listens for incoming connections
-s, --source HOST                Source address to bind to
-p, --source-port PORT           Source port to bind to
-b, --buffer-size INT            Buffer size to use (Default: 4096)
-x, --hexdump                    Hexdumps each message that is received
    --ssl                        Enables SSL mode
    --tls                        Enables TLS mode
    --ssl-version 1|1.1|1.2      Specifies the required SSL version
    --ssl-cert FILE              Specifies the SSL certificate file
    --ssl-key FILE               Specifies the SSL key file
    --ssl-verify none|peer|fail-if-no-peer-cert|client-once|true|false
                                 SSL verification mode
    --ssl-ca-bundle PATH         Path to the file or directory of CA certificates
-h, --help                       Print help information

## Arguments

[HOST]                           The host to connect to or listen on
[POST]                           The port to connect to

Attributes

mode[R]

Whether to connect or listen for connections.

@return [:connect, :listen]

protocol[R]

The protocol to use.

@return [:tcp, :udp, :unix]

Public Class Methods

new(**kwargs) click to toggle source

Initializes the command.

@param [Hash{Symbol => Object}] kwargs

Additional keyword arguments.
Calls superclass method
# File lib/ronin/cli/commands/netcat.rb, line 178
def initialize(**kwargs)
  super(**kwargs)

  @protocol = :tcp
  @mode     = :connect
end

Public Instance Methods

async_endpoint() click to toggle source

Creates the async endpoint object.

@return [Async::IO::Endpoint]

# File lib/ronin/cli/commands/netcat.rb, line 280
def async_endpoint
  case @protocol
  when :tcp  then Async::IO::Endpoint.tcp(@host,@port)
  when :udp  then Async::IO::Endpoint.udp(@host,@port)
  when :unix then Async::IO::Endpoint.unix(options[:unix])
  when :ssl
    Async::IO::Endpoint.ssl(@host,@port, hostname:    @host,
                                         ssl_context: ssl_context)
  end
end
async_stdin() click to toggle source

Creates the async stdin stream.

@return [Async::IO::Stream]

# File lib/ronin/cli/commands/netcat.rb, line 296
def async_stdin
  Async::IO::Stream.new(Async::IO::Generic.new(self.stdin))
end
client_loop() click to toggle source

The client event loop.

# File lib/ronin/cli/commands/netcat.rb, line 303
def client_loop
  finished    = Async::Notification.new
  endpoint    = async_endpoint
  stdin       = async_stdin
  buffer_size = options[:buffer_size]

  Async do |task|
    socket = begin
               endpoint.connect
             rescue StandardError => error
               print_error(error.message)
               exit(1)
             end

    stream = Async::IO::Stream.new(socket)

    begin
      client = task.async do
        while (data = stream.read_partial(buffer_size))
          print_data(data)
        end
      rescue EOFError
        # ignore EOFError
      ensure
        finished.signal
      end

      user = task.async do
        while (data = stdin.read_partial(buffer_size))
          socket.write(data)
        end
      rescue EOFError
        # ignore EOFError
      ensure
        finished.signal
      end

      finished.wait
    ensure
      client.stop
      user.stop
      socket.close
    end
  end
end
load_async() click to toggle source

Loads the async-io library.

# File lib/ronin/cli/commands/netcat.rb, line 254
def load_async
  require 'async/notification'
  require 'async/io'
  require 'async/io/stream'
end
print_data(data) click to toggle source

Prints or hexdumps data to stdout.

@param [String] data

The data to print or hexdump.
run(*args) click to toggle source

Runs the ‘ronin netcat` command.

@param [Array<String>] args

Additional command-line arguments.
# File lib/ronin/cli/commands/netcat.rb, line 191
def run(*args)
  if options[:hexdump]
    require 'hexdump'
    @hexdump = Hexdump::Hexdump.new
  end

  case @mode
  when :connect
    @host, @port = *args

    unless @host
      print_error "host argument required"
      exit(-1)
    end

    unless @port
      print_error "port argument required"
      exit(-1)
    end

    if options[:verbose]
      if @protocol == :unix
        log_info "Connecting to #{options[:unix]} ..."
      else
        log_info "Connecting to #{@host}:#{@port} ..."
      end
    end

    load_async
    client_loop
  when :listen
    case args.length
    when 0
      @port = options.fetch(:port,0)
      @host = nil
    when 1
      @port = args[0].to_i
      @host = nil
    when 2
      @host = args[0]
      @port = args[1].to_i
    end

    if options[:verbose]
      if @protocol == :unix
        log_info "Listening on #{options[:unix]} ..."
      else
        if @host
          log_info "Listening #{@host}:#{@port} ..."
        else
          log_info "Listening port #{@port} ..."
        end
      end
    end

    load_async
    server_loop
  end
end
server_loop() click to toggle source

The server event loop.

# File lib/ronin/cli/commands/netcat.rb, line 352
def server_loop
  finished    = Async::Notification.new
  endpoint    = async_endpoint
  stdin       = async_stdin
  clients     = []
  buffer_size = options[:buffer_size]

  Async do |task|
    endpoint.accept do |socket|
      if options[:verbose]
        log_info "Client #{socket} connected"
      end

      clients << socket
      stream = Async::IO::Stream.new(socket)

      begin
        while (data = stream.read_partial(buffer_size))
          print_data(data)
        end
      rescue EOFError
        # ignore EOFError
      end

      clients.delete(socket)

      if options[:verbose]
        log_warn "Client #{socket} disconnected"
      end
    end

    task.async do
      while (data = stdin.read_partial(buffer_size))
        clients.each { |client| client.write(data) }
      end
    rescue EOFError
      # ignore EOFError
    ensure
      finished.signal
    end

    finished.wait
  rescue StandardError => error
    print_error(error.message)
    exit(1)
  ensure
    clients.each(&:close)
  end
end
ssl_context() click to toggle source

Creates an SSL context.

@return [Ronin::Support::Network::SSL]

# File lib/ronin/cli/commands/netcat.rb, line 265
def ssl_context
  Support::Network::SSL.context(
    version:   options[:ssl_version],
    verify:    options[:ssl_verify],
    key_file:  options[:ssl_key],
    cert_file: options[:ssl_cert],
    ca_bundle: options[:ssl_ca_bundle]
  )
end