Path: blob/master/modules/auxiliary/admin/atg/atg_client.rb
19500 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Auxiliary6include Msf::Auxiliary::Report7include Msf::Exploit::Remote::Tcp8include Msf::Auxiliary::Scanner910def initialize11super(12'Name' => 'Veeder-Root Automatic Tank Gauge (ATG) Administrative Client',13'Description' => %q{14This module acts as a simplistic administrative client for interfacing15with Veeder-Root Automatic Tank Gauges (ATGs) or other devices speaking16the TLS-250 and TLS-350 protocols. This has been tested against17GasPot and Conpot, both honeypots meant to simulate ATGs; it has not18been tested against anything else, so use at your own risk.19},20'Author' => [21'Jon Hart <jon_hart[at]rapid7.com>' # original metasploit module22],23'License' => MSF_LICENSE,24'References' => [25['URL', 'https://www.rapid7.com/blog/post/2015/01/22/the-internet-of-gas-station-tank-gauges/'],26['URL', 'https://www.trendmicro.com/vinfo/us/security/news/cybercrime-and-digital-threats/the-gaspot-experiment'],27['URL', 'https://github.com/sjhilt/GasPot'],28['URL', 'https://github.com/mushorg/conpot'],29['URL', 'https://www.veeder.com/us/automatic-tank-gauge-atg-consoles'],30['URL', 'https://cdn.chipkin.com/files/liz/576013-635.pdf'],31['URL', 'https://docs.veeder.com/gold/download.cfm?doc_id=6227']32],33'DefaultAction' => 'INVENTORY',34'Actions' => [35[36'ALARM',37{38'Description' => 'I30200 Sensor alarm history (untested)',39'TLS-350_CMD' => "\x01I30200"40}41],42[43'ALARM_RESET',44{45'Description' => 'IS00300 Remote alarm reset (untested)',46'TLS-350_CMD' => "\x01IS00300"47}48],49[50'DELIVERY',51{52'Description' => 'I20200 Delivery report',53'TLS-350_CMD' => "\x01I20200"54}55],56[57'INVENTORY',58{59'Description' => '200/I20100 In-tank inventory report',60'TLS-250_CMD' => "\x01200",61'TLS-350_CMD' => "\x01I20100"62}63],64[65'LEAK',66{67'Description' => 'I20300 Leak report',68'TLS-350_CMD' => "\x01I20300"69}70],71[72'RELAY',73{74'Description' => 'I40600 Relay status (untested)',75'TLS-350_CMD' => "\x01I40600"76}77],78[79'RESET',80{81'Description' => 'IS00100 Reset (untested)',82'TLS-350_CMD' => "\x01IS00100"83}84],85[86'CLEAR_RESET',87{88'Description' => 'IS00200 Clear Reset Flag (untested)',89'TLS-350_CMD' => "\x01IS00200"90}91],92[93'SENSOR',94{95'Description' => 'I30100 Sensor status (untested)',96'TLS-350_CMD' => "\x01I30100"97}98],99[100'SENSOR_DIAG',101{102'Description' => 'IB0100 Sensor diagnostics (untested)',103'TLS-350_CMD' => "\x01IB0100"104}105],106[107'SHIFT',108{109'Description' => 'I20400 Shift report',110'TLS-350_CMD' => "\x01I20400"111}112],113[114'SET_TANK_NAME',115{116'Description' => 'S602 set tank name (use TANK_NUMBER and TANK_NAME options)',117'TLS-350_CMD' => "\x01S602"118}119],120# [ 'SET_TIME',121# {122# 'Description' => 'S50100 Set time of day (use TIME option) (untested)',123# 'TLS-350_CMD' => "\x01S50100"124# }125# ],126[127'STATUS',128{129'Description' => 'I20500 In-tank status report',130'TLS-350_CMD' => "\x01I20500"131}132],133[134'SYSTEM_STATUS',135{136'Description' => 'I10100 System status report (untested)',137'TLS-350_CMD' => "\x01I10100"138}139],140[141'TANK_ALARM',142{143'Description' => 'I20600 Tank alarm history (untested)',144'TLS-350_CMD' => "\x01I20600"145}146],147[148'TANK_DIAG',149{150'Description' => 'IA0100 Tank diagnostics (untested)',151'TLS-350_CMD' => "\x01IA0100"152}153],154[155'VERSION',156{157'Description' => 'Version information',158'TLS-250_CMD' => "\x01980",159'TLS-350_CMD' => "\x01I90200"160}161]162],163'Notes' => {164'Stability' => [],165'SideEffects' => [IOC_IN_LOGS],166'Reliability' => []167}168)169170register_options(171[172Opt::RPORT(10001),173OptInt.new('TANK_NUMBER', [false, 'The tank number to operate on (use with SET_TANK_NAME, 0 to change all)', 1]),174OptString.new('TANK_NAME', [false, 'The tank name to set (use with SET_TANK_NAME, defaults to random)'])175]176)177deregister_options('SSL', 'SSLCipher', 'SSLVerifyMode', 'SSLVersion')178179register_advanced_options(180[181OptEnum.new('PROTOCOL', [true, 'The Veeder-Root TLS protocol to speak', 'TLS-350', %w[TLS-350 TLS-250]]),182OptInt.new('TIMEOUT', [true, 'Time in seconds to wait for responses to our probes', 5])183]184)185end186187def setup188# ensure that the specified command is implemented for the desired version of the TLS protocol189unless action.opts.keys.include?(protocol_opt_name)190fail_with(Failure::BadConfig, "#{action.name} not defined for #{protocol}")191end192193# ensure that the tank number is set for the commands that need it194if action.name == 'SET_TANK_NAME' && (tank_number < 0 || tank_number > 99)195fail_with(Failure::BadConfig, "TANK_NUMBER #{tank_number} is invalid")196end197198unless timeout > 0199fail_with(Failure::BadConfig, "Invalid timeout #{timeout} -- must be > 0")200end201end202203def get_response(request)204sock.put(request)205response = sock.get_once(-1, timeout)206response.strip!207response += ' (command not understood)' if response == '9999FF1B'208response209end210211def protocol212datastore['PROTOCOL']213end214215def protocol_opt_name216protocol + '_CMD'217end218219def tank_name220@tank_name ||= datastore['TANK_NAME'] || Rex::Text.rand_text_alpha(16)221end222223def tank_number224datastore['TANK_NUMBER']225end226227def time228if datastore['TIME']229Time.parse(datastore['TIME']).to_i230else231Time.now.to_i232end233end234235def timeout236datastore['TIMEOUT']237end238239def run_host(_host)240connect241case action.name242when 'SET_TANK_NAME'243# send the set tank name command to change the tank name(s)244if tank_number == 0245vprint_status("Setting all tank names to #{tank_name}")246else247vprint_status("Setting tank ##{tank_number}'s name to #{tank_name}")248end249request = "#{action.opts[protocol_opt_name]}#{format('%02d', tank_number)}#{tank_name}\n"250sock.put(request)251# reconnect252disconnect253connect254# send an inventory probe to show that it succeeded255inventory_probe = "#{actions.find { |a| a.name == 'INVENTORY' }.opts[protocol_opt_name]}\n"256inventory_response = get_response(inventory_probe)257message = "#{protocol} #{action.opts['Description']}:\n#{inventory_response}"258if inventory_response.include?(tank_name)259print_good message260else261print_warning message262end263else264response = get_response("#{action.opts[protocol_opt_name]}\n")265print_good("#{protocol} #{action.opts['Description']}:")266print_line(response)267end268ensure269disconnect270end271end272273274