Skip to content

Commit a9e8ab7

Browse files
author
HD Moore
committed
Land rapid7#6220, adds ATG client module
2 parents f6fdabf + e107ec2 commit a9e8ab7

File tree

1 file changed

+258
-0
lines changed

1 file changed

+258
-0
lines changed
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
##
2+
# encoding: utf-8
3+
# This module requires Metasploit: http://metasploit.com/download
4+
# Current source: https://github.com/rapid7/metasploit-framework
5+
##
6+
7+
require 'msf/core'
8+
9+
class Metasploit3 < Msf::Auxiliary
10+
include Msf::Auxiliary::Report
11+
include Msf::Exploit::Remote::Tcp
12+
include Msf::Auxiliary::Scanner
13+
14+
def initialize
15+
super(
16+
'Name' => 'Veeder-Root Automatic Tank Gauge (ATG) Administrative Client',
17+
'Description' => %q{
18+
This module acts as a simplistic administrative client for interfacing
19+
with Veeder-Root Automatic Tank Gauges (ATGs) or other devices speaking
20+
the TLS-250 and TLS-350 protocols. This has been tested against
21+
GasPot, a honeypot meant to simulate ATGs; it has not been tested
22+
against anything else, so use at your own risk.
23+
},
24+
'Author' =>
25+
[
26+
'Jon Hart <jon_hart[at]rapid7.com>' # original metasploit module
27+
],
28+
'License' => MSF_LICENSE,
29+
'References' =>
30+
[
31+
['URL', 'https://community.rapid7.com/community/infosec/blog/2015/01/22/the-internet-of-gas-station-tank-gauges'],
32+
['URL', 'http://www.trendmicro.com/vinfo/us/security/news/cybercrime-and-digital-threats/the-gaspot-experiment'],
33+
['URL', 'https://github.com/sjhilt/GasPot'],
34+
['URL', 'http://www.veeder.com/us/automatic-tank-gauge-atg-consoles'],
35+
['URL', 'http://www.chipkin.com/files/liz/576013-635.pdf'],
36+
['URL', 'http://www.veeder.com/gold/download.cfm?doc_id=6227']
37+
],
38+
'DefaultAction' => 'INVENTORY',
39+
'Actions' =>
40+
[
41+
[ 'ALARM',
42+
{
43+
'Description' => 'I30200 Sensor alarm history (untested)',
44+
'TLS-350_CMD' => "\x01I30200"
45+
}
46+
],
47+
[ 'ALARM_RESET',
48+
{
49+
'Description' => 'IS00300 Remote alarm reset (untested)',
50+
'TLS-350_CMD' => "\x01IS00300"
51+
}
52+
],
53+
[ 'DELIVERY',
54+
{
55+
'Description' => 'I20200 Delivery report',
56+
'TLS-350_CMD' => "\x01I20200"
57+
}
58+
],
59+
[ 'INVENTORY',
60+
{
61+
'Description' => '200/I20100 In-tank inventory report',
62+
'TLS-250_CMD' => "\x01200",
63+
'TLS-350_CMD' => "\x01I20100"
64+
}
65+
],
66+
[ 'LEAK',
67+
{
68+
'Description' => 'I20300 Leak report',
69+
'TLS-350_CMD' => "\x01I20300"
70+
}
71+
],
72+
[ 'RELAY',
73+
{
74+
'Description' => 'I40600 Relay status (untested)',
75+
'TLS-350_CMD' => "\x01I40600"
76+
}
77+
],
78+
[ 'RESET',
79+
{
80+
'Description' => 'IS00100 Reset (untested)',
81+
'TLS-350_CMD' => "\x01IS00100"
82+
}
83+
],
84+
[ 'CLEAR_RESET',
85+
{
86+
'Description' => 'IS00200 Clear Reset Flag (untested)',
87+
'TLS-350_CMD' => "\x01IS00200"
88+
}
89+
],
90+
[ 'SENSOR',
91+
{
92+
'Description' => 'I30100 Sensor status (untested)',
93+
'TLS-350_CMD' => "\x01I30100"
94+
}
95+
],
96+
[ 'SENSOR_DIAG',
97+
{
98+
'Description' => 'IB0100 Sensor diagnostics (untested)',
99+
'TLS-350_CMD' => "\x01IB0100"
100+
}
101+
],
102+
[ 'SHIFT',
103+
{
104+
'Description' => 'I20400 Shift report',
105+
'TLS-350_CMD' => "\x01I20400"
106+
}
107+
],
108+
[ 'SET_TANK_NAME',
109+
{
110+
'Description' => 'S602 set tank name (use TANK_NUMBER and TANK_NAME options)',
111+
'TLS-350_CMD' => "\x01S602"
112+
}
113+
],
114+
# [ 'SET_TIME',
115+
# {
116+
# 'Description' => 'S50100 Set time of day (use TIME option) (untested)',
117+
# 'TLS-350_CMD' => "\x01S50100"
118+
# }
119+
# ],
120+
[ 'STATUS',
121+
{
122+
'Description' => 'I20500 In-tank status report',
123+
'TLS-350_CMD' => "\x01I20500"
124+
}
125+
],
126+
[ 'SYSTEM_STATUS',
127+
{
128+
'Description' => 'I10100 System status report (untested)',
129+
'TLS-350_CMD' => "\x01I10100"
130+
}
131+
],
132+
[ 'TANK_ALARM',
133+
{
134+
'Description' => 'I20600 Tank alarm history (untested)',
135+
'TLS-350_CMD' => "\x01I20600"
136+
}
137+
],
138+
[ 'TANK_DIAG',
139+
{
140+
'Description' => 'IA0100 Tank diagnostics (untested)',
141+
'TLS-350_CMD' => "\x01IA0100"
142+
}
143+
],
144+
[ 'VERSION',
145+
{
146+
'Description' => 'Version information',
147+
'TLS-250_CMD' => "\x01980",
148+
'TLS-350_CMD' => "\x01I90200"
149+
}
150+
]
151+
]
152+
)
153+
154+
register_options(
155+
[
156+
Opt::RPORT(10001),
157+
OptInt.new('TANK_NUMBER', [false, 'The tank number to operate on (use with SET_TANK_NAME, 0 to change all)', 1]),
158+
OptString.new('TANK_NAME', [false, 'The tank name to set (use with SET_TANK_NAME, defaults to random)'])
159+
]
160+
)
161+
deregister_options('SSL', 'SSLCipher', 'SSLVerifyMode', 'SSLVersion')
162+
163+
register_advanced_options(
164+
[
165+
OptEnum.new('PROTOCOL', [true, 'The Veeder-Root TLS protocol to speak', 'TLS-350', %w(TLS-350 TLS-250)]),
166+
OptInt.new('TIMEOUT', [true, 'Time in seconds to wait for responses to our probes', 5])
167+
]
168+
)
169+
end
170+
171+
def setup
172+
# ensure that the specified command is implemented for the desired version of the TLS protocol
173+
unless action.opts.keys.include?(protocol_opt_name)
174+
fail_with(Failure::BadConfig, "#{action.name} not defined for #{protocol}")
175+
end
176+
177+
# ensure that the tank number is set for the commands that need it
178+
if action.name == 'SET_TANK_NAME' && (tank_number < 0 || tank_number > 99)
179+
fail_with(Failure::BadConfig, "TANK_NUMBER #{tank_number} is invalid")
180+
end
181+
182+
unless timeout > 0
183+
fail_with(Failure::BadConfig, "Invalid timeout #{timeout} -- must be > 0")
184+
end
185+
end
186+
187+
def get_response(request)
188+
sock.put(request)
189+
response = sock.get_once(-1, timeout)
190+
response
191+
end
192+
193+
def peer
194+
"#{rhost}:#{rport}"
195+
end
196+
197+
def protocol
198+
datastore['PROTOCOL']
199+
end
200+
201+
def protocol_opt_name
202+
protocol + '_CMD'
203+
end
204+
205+
def tank_name
206+
@tank_name ||= (datastore['TANK_NAME'] ? datastore['TANK_NAME'] : Rex::Text.rand_text_alpha(16))
207+
end
208+
209+
def tank_number
210+
datastore['TANK_NUMBER']
211+
end
212+
213+
def time
214+
if datastore['TIME']
215+
Time.parse(datastore['TIME']).to_i
216+
else
217+
Time.now.to_i
218+
end
219+
end
220+
221+
def timeout
222+
datastore['TIMEOUT']
223+
end
224+
225+
def run_host(_host)
226+
begin
227+
connect
228+
case action.name
229+
when 'SET_TANK_NAME'
230+
# send the set tank name command to change the tank name(s)
231+
if tank_number == 0
232+
vprint_status("#{peer} -- setting all tank names to #{tank_name}")
233+
else
234+
vprint_status("#{peer} -- setting tank ##{tank_number}'s name to #{tank_name}")
235+
end
236+
request = "#{action.opts[protocol_opt_name]}#{format('%02d', tank_number)}#{tank_name}\n"
237+
sock.put(request)
238+
# reconnect
239+
disconnect
240+
connect
241+
# send an inventory probe to show that it succeeded
242+
inventory_probe = "#{actions.find { |a| a.name == 'INVENTORY' }.opts[protocol_opt_name]}\n"
243+
inventory_response = get_response(inventory_probe)
244+
message = "#{peer} #{protocol} #{action.opts['Description']}:\n#{inventory_response}"
245+
if inventory_response.include?(tank_name)
246+
print_good message
247+
else
248+
print_warning message
249+
end
250+
else
251+
response = get_response("#{action.opts[protocol_opt_name]}\n")
252+
print_good("#{peer} #{protocol} #{action.opts['Description']}:\n#{response}")
253+
end
254+
ensure
255+
disconnect
256+
end
257+
end
258+
end

0 commit comments

Comments
 (0)