diff --git a/modules/auxiliary/admin/atg/atg_client.rb b/modules/auxiliary/admin/atg/atg_client.rb new file mode 100644 index 0000000000..9962d57c5b --- /dev/null +++ b/modules/auxiliary/admin/atg/atg_client.rb @@ -0,0 +1,258 @@ +## +# encoding: utf-8 +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + include Msf::Auxiliary::Report + include Msf::Exploit::Remote::Tcp + include Msf::Auxiliary::Scanner + + def initialize + super( + 'Name' => 'Veeder-Root Automatic Tank Gauge (ATG) Administrative Client', + 'Description' => %q{ + This module acts as a simplistic administrative client for interfacing + with Veeder-Root Automatic Tank Gauges (ATGs) or other devices speaking + the TLS-250 and TLS-350 protocols. This has been tested against + GasPot, a honeypot meant to simulate ATGs; it has not been tested + against anything else, so use at your own risk. + }, + 'Author' => + [ + 'Jon Hart ' # original metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['URL', 'https://community.rapid7.com/community/infosec/blog/2015/01/22/the-internet-of-gas-station-tank-gauges'], + ['URL', 'http://www.trendmicro.com/vinfo/us/security/news/cybercrime-and-digital-threats/the-gaspot-experiment'], + ['URL', 'https://github.com/sjhilt/GasPot'], + ['URL', 'http://www.veeder.com/us/automatic-tank-gauge-atg-consoles'], + ['URL', 'http://www.chipkin.com/files/liz/576013-635.pdf'], + ['URL', 'http://www.veeder.com/gold/download.cfm?doc_id=6227'] + ], + 'DefaultAction' => 'INVENTORY', + 'Actions' => + [ + [ 'ALARM', + { + 'Description' => 'I30200 Sensor alarm history (untested)', + 'TLS-350_CMD' => "\x01I30200" + } + ], + [ 'ALARM_RESET', + { + 'Description' => 'IS00300 Remote alarm reset (untested)', + 'TLS-350_CMD' => "\x01IS00300" + } + ], + [ 'DELIVERY', + { + 'Description' => 'I20200 Delivery report', + 'TLS-350_CMD' => "\x01I20200" + } + ], + [ 'INVENTORY', + { + 'Description' => '200/I20100 In-tank inventory report', + 'TLS-250_CMD' => "\x01200", + 'TLS-350_CMD' => "\x01I20100" + } + ], + [ 'LEAK', + { + 'Description' => 'I20300 Leak report', + 'TLS-350_CMD' => "\x01I20300" + } + ], + [ 'RELAY', + { + 'Description' => 'I40600 Relay status (untested)', + 'TLS-350_CMD' => "\x01I40600" + } + ], + [ 'RESET', + { + 'Description' => 'IS00100 Reset (untested)', + 'TLS-350_CMD' => "\x01IS00100" + } + ], + [ 'CLEAR_RESET', + { + 'Description' => 'IS00200 Clear Reset Flag (untested)', + 'TLS-350_CMD' => "\x01IS00200" + } + ], + [ 'SENSOR', + { + 'Description' => 'I30100 Sensor status (untested)', + 'TLS-350_CMD' => "\x01I30100" + } + ], + [ 'SENSOR_DIAG', + { + 'Description' => 'IB0100 Sensor diagnostics (untested)', + 'TLS-350_CMD' => "\x01IB0100" + } + ], + [ 'SHIFT', + { + 'Description' => 'I20400 Shift report', + 'TLS-350_CMD' => "\x01I20400" + } + ], + [ 'SET_TANK_NAME', + { + 'Description' => 'S602 set tank name (use TANK_NUMBER and TANK_NAME options)', + 'TLS-350_CMD' => "\x01S602" + } + ], + # [ 'SET_TIME', + # { + # 'Description' => 'S50100 Set time of day (use TIME option) (untested)', + # 'TLS-350_CMD' => "\x01S50100" + # } + # ], + [ 'STATUS', + { + 'Description' => 'I20500 In-tank status report', + 'TLS-350_CMD' => "\x01I20500" + } + ], + [ 'SYSTEM_STATUS', + { + 'Description' => 'I10100 System status report (untested)', + 'TLS-350_CMD' => "\x01I10100" + } + ], + [ 'TANK_ALARM', + { + 'Description' => 'I20600 Tank alarm history (untested)', + 'TLS-350_CMD' => "\x01I20600" + } + ], + [ 'TANK_DIAG', + { + 'Description' => 'IA0100 Tank diagnostics (untested)', + 'TLS-350_CMD' => "\x01IA0100" + } + ], + [ 'VERSION', + { + 'Description' => 'Version information', + 'TLS-250_CMD' => "\x01980", + 'TLS-350_CMD' => "\x01I90200" + } + ] + ] + ) + + register_options( + [ + Opt::RPORT(10001), + OptInt.new('TANK_NUMBER', [false, 'The tank number to operate on (use with SET_TANK_NAME, 0 to change all)', 1]), + OptString.new('TANK_NAME', [false, 'The tank name to set (use with SET_TANK_NAME, defaults to random)']) + ] + ) + deregister_options('SSL', 'SSLCipher', 'SSLVerifyMode', 'SSLVersion') + + register_advanced_options( + [ + OptEnum.new('PROTOCOL', [true, 'The Veeder-Root TLS protocol to speak', 'TLS-350', %w(TLS-350 TLS-250)]), + OptInt.new('TIMEOUT', [true, 'Time in seconds to wait for responses to our probes', 5]) + ] + ) + end + + def setup + # ensure that the specified command is implemented for the desired version of the TLS protocol + unless action.opts.keys.include?(protocol_opt_name) + fail_with(Failure::BadConfig, "#{action.name} not defined for #{protocol}") + end + + # ensure that the tank number is set for the commands that need it + if action.name == 'SET_TANK_NAME' && (tank_number < 0 || tank_number > 99) + fail_with(Failure::BadConfig, "TANK_NUMBER #{tank_number} is invalid") + end + + unless timeout > 0 + fail_with(Failure::BadConfig, "Invalid timeout #{timeout} -- must be > 0") + end + end + + def get_response(request) + sock.put(request) + response = sock.get_once(-1, timeout) + response + end + + def peer + "#{rhost}:#{rport}" + end + + def protocol + datastore['PROTOCOL'] + end + + def protocol_opt_name + protocol + '_CMD' + end + + def tank_name + @tank_name ||= (datastore['TANK_NAME'] ? datastore['TANK_NAME'] : Rex::Text.rand_text_alpha(16)) + end + + def tank_number + datastore['TANK_NUMBER'] + end + + def time + if datastore['TIME'] + Time.parse(datastore['TIME']).to_i + else + Time.now.to_i + end + end + + def timeout + datastore['TIMEOUT'] + end + + def run_host(_host) + begin + connect + case action.name + when 'SET_TANK_NAME' + # send the set tank name command to change the tank name(s) + if tank_number == 0 + vprint_status("#{peer} -- setting all tank names to #{tank_name}") + else + vprint_status("#{peer} -- setting tank ##{tank_number}'s name to #{tank_name}") + end + request = "#{action.opts[protocol_opt_name]}#{format('%02d', tank_number)}#{tank_name}\n" + sock.put(request) + # reconnect + disconnect + connect + # send an inventory probe to show that it succeeded + inventory_probe = "#{actions.find { |a| a.name == 'INVENTORY' }.opts[protocol_opt_name]}\n" + inventory_response = get_response(inventory_probe) + message = "#{peer} #{protocol} #{action.opts['Description']}:\n#{inventory_response}" + if inventory_response.include?(tank_name) + print_good message + else + print_warning message + end + else + response = get_response("#{action.opts[protocol_opt_name]}\n") + print_good("#{peer} #{protocol} #{action.opts['Description']}:\n#{response}") + end + ensure + disconnect + end + end +end