/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/net.rb
Ruby | 448 lines | 310 code | 71 blank | 67 comment | 25 complexity | 30c01b836cec9ec51f0c5d75d49fdd70 MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0, GPL-3.0, LGPL-2.1, GPL-2.0
- # -*- coding: binary -*-
- require 'rex/post/meterpreter'
- require 'rex/service_manager'
- module Rex
- module Post
- module Meterpreter
- module Ui
- ###
- #
- # The networking portion of the standard API extension.
- #
- ###
- class Console::CommandDispatcher::Stdapi::Net
- Klass = Console::CommandDispatcher::Stdapi::Net
- include Console::CommandDispatcher
- #
- # This module is used to extend the meterpreter session
- # so that local port forwards can be tracked and cleaned
- # up when the meterpreter session goes away
- #
- module PortForwardTracker
- def cleanup
- super
- if pfservice
- pfservice.deref
- end
- end
- attr_accessor :pfservice
- end
- #
- # Options for the route command.
- #
- @@route_opts = Rex::Parser::Arguments.new(
- "-h" => [ false, "Help banner." ])
- #
- # Options for the portfwd command.
- #
- @@portfwd_opts = Rex::Parser::Arguments.new(
- "-h" => [ false, "Help banner." ],
- "-l" => [ true, "The local port to listen on." ],
- "-r" => [ true, "The remote host to connect to." ],
- "-p" => [ true, "The remote port to connect to." ],
- "-L" => [ true, "The local host to listen on (optional)." ])
- #
- # List of supported commands.
- #
- def commands
- all = {
- "ipconfig" => "Display interfaces",
- "ifconfig" => "Display interfaces",
- "route" => "View and modify the routing table",
- "portfwd" => "Forward a local port to a remote service",
- "arp" => "Display the host ARP cache",
- "netstat" => "Display the network connections",
- "getproxy" => "Display the current proxy configuration",
- }
- reqs = {
- "ipconfig" => [ "stdapi_net_config_get_interfaces" ],
- "ifconfig" => [ "stdapi_net_config_get_interfaces" ],
- "route" => [
- # Also uses these, but we don't want to be unable to list them
- # just because we can't alter them.
- #"stdapi_net_config_add_route",
- #"stdapi_net_config_remove_route",
- "stdapi_net_config_get_routes"
- ],
- # Only creates tcp channels, which is something whose availability
- # we can't check directly at the moment.
- "portfwd" => [ ],
- "arp" => [ "stdapi_net_config_get_arp_table" ],
- "netstat" => [ "stdapi_net_config_get_netstat" ],
- "getproxy" => [ "stdapi_net_config_get_proxy" ],
- }
- all.delete_if do |cmd, desc|
- del = false
- reqs[cmd].each do |req|
- next if client.commands.include? req
- del = true
- break
- end
- del
- end
- all
- end
- #
- # Name for this dispatcher.
- #
- def name
- "Stdapi: Networking"
- end
- #
- # Displays network connections of the remote machine.
- #
- def cmd_netstat(*args)
- connection_table = client.net.config.netstat
- tbl = Rex::Ui::Text::Table.new(
- 'Header' => "Connection list",
- 'Indent' => 4,
- 'Columns' =>
- [
- "Proto",
- "Local address",
- "Remote address",
- "State",
- "User",
- "Inode",
- "PID/Program name"
- ])
- connection_table.each { |connection|
- tbl << [ connection.protocol, connection.local_addr_str, connection.remote_addr_str,
- connection.state, connection.uid, connection.inode, connection.pid_name]
- }
- if tbl.rows.length > 0
- print("\n" + tbl.to_s + "\n")
- else
- print_line("Connection list is empty.")
- end
- end
- #
- # Displays ARP cache of the remote machine.
- #
- def cmd_arp(*args)
- arp_table = client.net.config.arp_table
- tbl = Rex::Ui::Text::Table.new(
- 'Header' => "ARP cache",
- 'Indent' => 4,
- 'Columns' =>
- [
- "IP address",
- "MAC address",
- "Interface"
- ])
- arp_table.each { |arp|
- tbl << [ arp.ip_addr, arp.mac_addr, arp.interface ]
- }
- if tbl.rows.length > 0
- print("\n" + tbl.to_s + "\n")
- else
- print_line("ARP cache is empty.")
- end
- end
- #
- # Displays interfaces on the remote machine.
- #
- def cmd_ipconfig(*args)
- ifaces = client.net.config.interfaces
- if (ifaces.length == 0)
- print_line("No interfaces were found.")
- else
- ifaces.sort{|a,b| a.index <=> b.index}.each do |iface|
- print("\n" + iface.pretty + "\n")
- end
- end
- end
- alias :cmd_ifconfig :cmd_ipconfig
- #
- # Displays or modifies the routing table on the remote machine.
- #
- def cmd_route(*args)
- # Default to list
- if (args.length == 0)
- args.unshift("list")
- end
- # Check to see if they specified -h
- @@route_opts.parse(args) { |opt, idx, val|
- case opt
- when "-h"
- print(
- "Usage: route [-h] command [args]\n\n" +
- "Display or modify the routing table on the remote machine.\n\n" +
- "Supported commands:\n\n" +
- " add [subnet] [netmask] [gateway]\n" +
- " delete [subnet] [netmask] [gateway]\n" +
- " list\n\n")
- return true
- end
- }
- cmd = args.shift
- # Process the commands
- case cmd
- when "list"
- routes = client.net.config.routes
- # IPv4
- tbl = Rex::Ui::Text::Table.new(
- 'Header' => "IPv4 network routes",
- 'Indent' => 4,
- 'Columns' =>
- [
- "Subnet",
- "Netmask",
- "Gateway",
- "Metric",
- "Interface"
- ])
- routes.select {|route|
- Rex::Socket.is_ipv4?(route.netmask)
- }.each { |route|
- tbl << [ route.subnet, route.netmask, route.gateway, route.metric, route.interface ]
- }
- if tbl.rows.length > 0
- print("\n" + tbl.to_s + "\n")
- else
- print_line("No IPv4 routes were found.")
- end
- # IPv6
- tbl = Rex::Ui::Text::Table.new(
- 'Header' => "IPv6 network routes",
- 'Indent' => 4,
- 'Columns' =>
- [
- "Subnet",
- "Netmask",
- "Gateway",
- "Metric",
- "Interface"
- ])
- routes.select {|route|
- Rex::Socket.is_ipv6?(route.netmask)
- }.each { |route|
- tbl << [ route.subnet, route.netmask, route.gateway, route.metric, route.interface ]
- }
- if tbl.rows.length > 0
- print("\n" + tbl.to_s + "\n")
- else
- print_line("No IPv6 routes were found.")
- end
- when "add"
- # Satisfy check to see that formatting is correct
- unless Rex::Socket::RangeWalker.new(args[0]).length == 1
- print_error "Invalid IP Address"
- return false
- end
- unless Rex::Socket::RangeWalker.new(args[1]).length == 1
- print_error "Invalid Subnet mask"
- return false
- end
- print_line("Creating route #{args[0]}/#{args[1]} -> #{args[2]}")
- client.net.config.add_route(*args)
- when "delete"
- # Satisfy check to see that formatting is correct
- unless Rex::Socket::RangeWalker.new(args[0]).length == 1
- print_error "Invalid IP Address"
- return false
- end
- unless Rex::Socket::RangeWalker.new(args[1]).length == 1
- print_error "Invalid Subnet mask"
- return false
- end
- print_line("Deleting route #{args[0]}/#{args[1]} -> #{args[2]}")
- client.net.config.remove_route(*args)
- else
- print_error("Unsupported command: #{cmd}")
- end
- end
- #
- # Starts and stops local port forwards to remote hosts on the target
- # network. This provides an elementary pivoting interface.
- #
- def cmd_portfwd(*args)
- args.unshift("list") if args.empty?
- # For clarity's sake.
- lport = nil
- lhost = nil
- rport = nil
- rhost = nil
- # Parse the options
- @@portfwd_opts.parse(args) { |opt, idx, val|
- case opt
- when "-h"
- cmd_portfwd_help
- return true
- when "-l"
- lport = val.to_i
- when "-L"
- lhost = val
- when "-p"
- rport = val.to_i
- when "-r"
- rhost = val
- end
- }
- # If we haven't extended the session, then do it now since we'll
- # need to track port forwards
- if client.kind_of?(PortForwardTracker) == false
- client.extend(PortForwardTracker)
- client.pfservice = Rex::ServiceManager.start(Rex::Services::LocalRelay)
- end
- # Build a local port forward in association with the channel
- service = client.pfservice
- # Process the command
- case args.shift
- when "list"
- cnt = 0
- # Enumerate each TCP relay
- service.each_tcp_relay { |lhost, lport, rhost, rport, opts|
- next if (opts['MeterpreterRelay'] == nil)
- print_line("#{cnt}: #{lhost}:#{lport} -> #{rhost}:#{rport}")
- cnt += 1
- }
- print_line
- print_line("#{cnt} total local port forwards.")
- when "add"
- # Validate parameters
- if (!lport or !rhost or !rport)
- print_error("You must supply a local port, remote host, and remote port.")
- return
- end
- # Start the local TCP relay in association with this stream
- service.start_tcp_relay(lport,
- 'LocalHost' => lhost,
- 'PeerHost' => rhost,
- 'PeerPort' => rport,
- 'MeterpreterRelay' => true,
- 'OnLocalConnection' => Proc.new { |relay, lfd|
- create_tcp_channel(relay)
- })
- print_status("Local TCP relay created: #{lhost || '0.0.0.0'}:#{lport} <-> #{rhost}:#{rport}")
- # Delete local port forwards
- when "delete"
- # No local port, no love.
- if (!lport)
- print_error("You must supply a local port.")
- return
- end
- # Stop the service
- if (service.stop_tcp_relay(lport, lhost))
- print_status("Successfully stopped TCP relay on #{lhost || '0.0.0.0'}:#{lport}")
- else
- print_error("Failed to stop TCP relay on #{lhost || '0.0.0.0'}:#{lport}")
- end
- when "flush"
- counter = 0
- service.each_tcp_relay do |lhost, lport, rhost, rport, opts|
- next if (opts['MeterpreterRelay'] == nil)
- if (service.stop_tcp_relay(lport, lhost))
- print_status("Successfully stopped TCP relay on #{lhost || '0.0.0.0'}:#{lport}")
- else
- print_error("Failed to stop TCP relay on #{lhost || '0.0.0.0'}:#{lport}")
- next
- end
- counter += 1
- end
- print_status("Successfully flushed #{counter} rules")
- else
- cmd_portfwd_help
- end
- end
- def cmd_portfwd_help
- print_line "Usage: portfwd [-h] [add | delete | list | flush] [args]"
- print_line
- print @@portfwd_opts.usage
- end
- def cmd_getproxy
- p = client.net.config.get_proxy_config()
- print_line( "Auto-detect : #{p[:autodetect] ? "Yes" : "No"}" )
- print_line( "Auto config URL : #{p[:autoconfigurl]}" )
- print_line( "Proxy URL : #{p[:proxy]}" )
- print_line( "Proxy Bypass : #{p[:proxybypass]}" )
- end
- protected
- #
- # Creates a TCP channel using the supplied relay context.
- #
- def create_tcp_channel(relay)
- client.net.socket.create(
- Rex::Socket::Parameters.new(
- 'PeerHost' => relay.opts['PeerHost'],
- 'PeerPort' => relay.opts['PeerPort'],
- 'Proto' => 'tcp'
- )
- )
- end
- end
- end
- end
- end
- end