diff --git a/lib/msf/core/exploit/mixins.rb b/lib/msf/core/exploit/mixins.rb index 76cfac86e4..af2e59d8bb 100644 --- a/lib/msf/core/exploit/mixins.rb +++ b/lib/msf/core/exploit/mixins.rb @@ -59,7 +59,7 @@ require 'msf/core/exploit/vim_soap' require 'msf/core/exploit/wdbrpc' require 'msf/core/exploit/wdbrpc_client' require 'msf/core/exploit/afp' - +require 'msf/core/exploit/realport' # Telephony require 'msf/core/exploit/dialup' diff --git a/lib/msf/core/exploit/realport.rb b/lib/msf/core/exploit/realport.rb new file mode 100644 index 0000000000..72727f9594 --- /dev/null +++ b/lib/msf/core/exploit/realport.rb @@ -0,0 +1,236 @@ +# -*- coding: binary -*- + +require 'msf/core' +require 'msf/core/exploit/tcp' + +module Msf + +### +# +# This module provides methods for working with the RealPort protocol +# +### +module Exploit::Remote::RealPort + include Msf::Exploit::Remote::Tcp + + # + # Initializes an instance of an auxiliary module that uses RealPort + # + + def initialize(info = {}) + super + register_options( [ + Opt::RPORT(771) + ], Msf::Exploit::Remote::RealPort ) + end + + @@REALPORT_BAUD_MAP = { + '2400' => "\x03\x00", + '9600' => "\x00\xc0", + '19200' => "\x00\x60", + '38400' => "\x00\x20", + '57600' => "\x00\x30", + '76800' => "\x00\x10", + '115200' => "\x00\x10", # Yup, same as above + '230400' => "\x00\x08", + '460800' => "\x00\x04", + '921600' => "\x00\x02", + } + + # Connect to the RealPort service and send the initial handshake + # This has the benefit of retrieving the port count and product + # Returns true if it succeeds and nil otherwise + def realport_connect + connect + sock.put("\xfb\x01\xfb\x02\xfb\x18") + res = sock.get_once(12, 5) + return unless (res and res.length == 12) + + unless res[0,2] == "\xfc\x01" + vprint_error("#{rhost}:#{rport} Bad reply: #{res.inspect}") + return + end + + len = res[2,2].unpack("n").first + return unless len > 0 + + res = sock.get_once(len, 5) + unless res.length == len + vprint_error("#{rhost}:#{rport} Bad length: #{res.length} wanted #{len}") + return + end + + name,info = res.split("\xfc\x02", 2) + fields = info.unpack("n*") + + @realport_port_count = fields[1].to_i + @realport_name = name.gsub(/[\r\n]/, '') + + # The server also sends us an additional four-byte packet we can ignore here + # This throws away a \xFC\x18\x00\x04 sequence + sock.get_once(-1, 5) + + return true + end + + def realport_disconnect + disconnect + end + + def realport_baud_to_speed(baud) + @@REALPORT_BAUD_MAP[baud] + end + + def realport_recv_banner(port=0, timeout=30, max_data=4096) + # + # Data is received here, header is: + # a2 00 01 82 XX + # ^ [ counter ] [ length ] [ data ] + # + + # Can also see f0 here (keep alive) + + banner = "" + stime = Time.now.to_f + dcnt = 0 + pcnt = 0 + + while banner.length < max_data and (Time.now.to_f - stime) < timeout + + res = sock.get_once(1, 1) + unless res + if banner.length == 0 or pcnt < 3 + # Send a new line to wake up the remote end + realport_send(port, "\r") + pcnt += 1 + next + else + # Allow three empty reads *after* we have sent at least one probe and have data + dcnt += 1 + break if dcnt > 3 + next + end + end + bit = res.unpack("C").first + case bit + when (0xA0 + port) + # Read the packet sequence number (two bytes) + res = sock.get_once(2, 1) + when 0xF0 + # Skip this keep-alive response + when (0x80 + port) + # Read the one-byte length value + res = sock.get_once(1, 1) + if res + len = res.unpack("C").first + res = sock.get_once(len, 1) + if res + banner << res + end + end + end + end + banner + end + + def realport_send(port=0, data) + sock.put( [port].pack("C") + data ) + end + + def realport_close(port=0) + cprt = [ 0xb0 + port ].pack("C") + pkt = cprt + "\x28\x00\xc0\x00\xb0\x00\x01\x00\x00\x00\x00" + cprt + "\x0a\x03" + + # Response + # b2 0b 03 00 00 02 + + # Send a close request + sock.put(pkt) + res = sock.get_once(-1, 5) + + vprint_status("#{target_host}:#{rport} Port:#{port} Close:#{ res.inspect }") + return + end + + def realport_open(port=0, baud='9600') + + @realport_banner = '' + + cprt = [ 0xb0 + port ].pack("C") + aprt = [ 0xa0 + port ].pack("C") + + speed = realport_baud_to_speed(baud) + + # Open port + pkt1 = "\xf0" + cprt + "\x0a"+ "\x00" + + # Response + # b2 0b 00 00 00 02 + # ^ ^ <- port number + + # Open the port + sock.put(pkt1) + res = sock.get_once(-1, 5) + + vprint_status("#{target_host}:#{rport} Port:#{port} Baud:#{baud} Open:#{ res.inspect }") + + # Access the port + pkt2 = + cprt + "\x0e" + + cprt + "\x2a\x02\xc0\xf3" + + cprt + "\x10" + + cprt + "\x14" + + cprt + "\x16" + + cprt + "\x2c\x03\x00\x00" + + # Response (GOOD) + # b2 0f 00 00 00 00 b2 15 0f ff 0f ff b2 11 00 00 + # 13 b2 17 01 02 00 2f 06 a8 00 1c 20 00 00 00 00 + # 00 f3 f3 00 00 00 00 00 00 00 00 00 00 00 00 00 + # 00 + + # Response (BAD) + # \xFF \x17 Access to unopened port\x00 + + # Send negotiate request + sock.put(pkt2) + res = sock.get_once(-1, 5) + if res.to_s =~ /^\xff/ + vprint_status("#{target_host}:#{rport} Port:#{port} is closed: #{res.inspect}") + return :closed + end + + vprint_status("#{target_host}:#{rport} Port:#{port} Baud:#{baud} Negotiate:#{ res.inspect }") + + # Terminal settings + pkt3 = + cprt + "\x30\x03\xff\x00\x64" + + cprt + "\x2d\x03\xff\x0b\xff" + + cprt + "\x28" + speed + "\x04" + + cprt + "\x00\x01\x00\x00\x00\x00" + + cprt + "\x2c\x00\x12\x00" + + cprt + "\x2e\x11\x13\x16\x00\x00" + + cprt + "\x2f\x03\xff\x00\x64" + + cprt + "\x40\x37" + aprt + "\x0f\xff" + + # Response + # c2 12 00 00 f0 + # ^ + + # Send terminal settings request + sock.put(pkt3) + res = sock.get_once(-1, 5) + + if res.to_s =~ /^\xff/ + vprint_status("#{target_host}:#{rport} Port:#{port} is closed: #{res.inspect}") + return :closed + end + + vprint_status("#{target_host}:#{rport} Port:#{port} Baud:#{baud} Settings:#{ res.inspect }") + return :open + end + +end + + +end diff --git a/modules/auxiliary/scanner/scada/digi_realport_serialport_scan.rb b/modules/auxiliary/scanner/scada/digi_realport_serialport_scan.rb new file mode 100644 index 0000000000..d713af29e5 --- /dev/null +++ b/modules/auxiliary/scanner/scada/digi_realport_serialport_scan.rb @@ -0,0 +1,93 @@ +## +# $Id$ +## + +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::RealPort + include Msf::Auxiliary::Report + include Msf::Auxiliary::Scanner + + def initialize + super( + 'Name' => 'Digi RealPort Serial Server Port Scanner', + 'Description' => 'Identify active ports on RealPort-enabled serial servers.', + 'References' => + [ + ['URL', 'http://www.digi.com/pdf/fs_realport.pdf'], + ['URL', 'http://www.digi.com/support/productdetail?pid=2229&type=drivers'] + ], + 'Author' => + [ + 'hdm' + ], + 'License' => MSF_LICENSE + ) + + register_options( + [ + OptInt.new("BANNER_TIMEOUT", [true, "How long to capture data from the serial port", 5]), + OptString.new('BAUD_RATES', [true, "A space delimited list of baud rates to try for each port", "9600 115200"]), + OptString.new('PORTS', [true, "A space delimited list of 1-indexed serial port numbers to try, default is all supported", "ALL"]) + ], self.class) + end + + def setup + test_speeds = datastore['BAUD_RATES'].split(/\s+/) + test_speeds.each do |baud| + valid = realport_baud_to_speed(baud) + if not valid + raise RuntimeError, "The baud rate #{baud} is not supported" + end + end + end + + def run_host(target_host) + test_ports = datastore['PORTS'].upcase.split(/\s+/) + test_speeds = datastore['BAUD_RATES'].split(/\s+/) + + return unless realport_connect + + info = "#{@realport_name} ( ports: #{@realport_port_count} )" + vprint_status("#{target_host}:#{rport} is running #{info}") + report_service(:host => rhost, :port => rport, :name => "realport", :info => info) + + 1.upto(@realport_port_count) do |pnum| + unless test_ports.include?('ALL') or test_ports.include?(pnum.to_s) + # Skip this port + next + end + + test_speeds.each do |baud| + ret = realport_open(pnum - 1, baud) + break unless ret == :open + res = realport_recv_banner(pnum - 1, datastore['BANNER_TIMEOUT']) + if res and res.length > 0 + print_status("#{target_host}:#{rport} [port #{pnum} @ #{baud}bps] #{res.inspect}") + report_note( + :host => target_host, + :proto => 'tcp', + :port => rport, + :type => "realport.port#{pnum}.banner", + :data => {:baud => baud, :banner => res}, + :update => :unique_data + ) + + end + realport_close(pnum - 1) + end + end + + realport_disconnect + end + +end diff --git a/modules/auxiliary/scanner/scada/digi_realport_version.rb b/modules/auxiliary/scanner/scada/digi_realport_version.rb new file mode 100644 index 0000000000..9860efe614 --- /dev/null +++ b/modules/auxiliary/scanner/scada/digi_realport_version.rb @@ -0,0 +1,45 @@ +## +# $Id$ +## + +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::RealPort + include Msf::Auxiliary::Report + include Msf::Auxiliary::Scanner + + def initialize + super( + 'Name' => 'Digi RealPort Serial Server Version', + 'Description' => 'Detect serial servers that speak the RealPort protocol.', + 'References' => + [ + ['URL', 'http://www.digi.com/pdf/fs_realport.pdf'], + ['URL', 'http://www.digi.com/support/productdetail?pid=2229&type=drivers'] + ], + 'Author' => + [ + 'hdm' + ], + 'License' => MSF_LICENSE + ) + end + + def run_host(target_host) + if realport_connect + info = "#{@realport_name} ( ports: #{@realport_port_count} )" + print_status("#{target_host}:#{rport} #{info}") + report_service(:host => rhost, :port => rport, :name => "realport", :info => info) + end + realport_disconnect + end +end