Skip to content

Commit e651bc1

Browse files
committed
Land rapid7#8951, Hwbridge auto padding fix and flowcontrol
2 parents 9b5350f + 200a1b4 commit e651bc1

File tree

7 files changed

+112
-19
lines changed

7 files changed

+112
-19
lines changed

documentation/modules/post/hardware/automotive/getvinfo.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ PIDs to ASCII.
3030

3131
Optional byte-value to use for padding all CAN bus packets to an 8-byte length. Padding is disabled by default.
3232

33+
**FC**
34+
35+
Optional. If true forces sending flow control packets on all multibyte ISO-TP requests
36+
3337
## Scenarios
3438

3539
Given a standard vehicle ECU that is connected to can2 of the HWBridge device:

lib/msf/base/sessions/hwbridge.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,10 @@ def shell_init
196196

197197
attr_accessor :console # :nodoc:
198198
attr_accessor :alive # :nodoc:
199+
attr_accessor :api_version
200+
attr_accessor :fw_version
201+
attr_accessor :hw_version
202+
attr_accessor :device_name
199203
private
200204
attr_accessor :rstream # :nodoc:
201205

lib/rex/post/hwbridge/extensions/automotive/automotive.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,11 +119,16 @@ def send_isotp_and_wait_for_response(bus, src_id, dst_id, data, opt = {})
119119
# TODO: Implement sending ISO-TP > 8 bytes
120120
data = [ data ] if data.is_a? Integer
121121
if data.size < 8
122-
data = padd_packet(data, opt['PADDING']) if opt.key? 'PADDING'
122+
# Padding is handled differently after 0.0.3
123+
if Gem::Version.new(client.api_version) < Gem::Version.new('0.0.4')
124+
data = padd_packet(data, opt['PADDING']) if opt.key? 'PADDING'
125+
end
123126
data = array2hex(data).join
124127
request_str = "/automotive/#{bus}/isotpsend_and_wait?srcid=#{src_id}&dstid=#{dst_id}&data=#{data}"
125128
request_str += "&timeout=#{opt['TIMEOUT']}" if opt.key? "TIMEOUT"
126129
request_str += "&maxpkts=#{opt['MAXPKTS']}" if opt.key? "MAXPKTS"
130+
request_str += "&padding=#{opt['PADDING']}" if opt.key? "PADDING" # Won't hurt to use in older versions
131+
request_str += "&fc=#{opt['FC']}" if opt.key? "FC" # Force flow control
127132
return check_for_errors(client.send_request(request_str))
128133
end
129134
nil

lib/rex/post/hwbridge/ui/console/command_dispatcher/automotive.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,13 +174,17 @@ def cmd_isotpsend(*args)
174174
data = ''
175175
timeout = nil
176176
maxpackets = nil
177+
flowcontrol = false
178+
padding = nil
177179
cansend_opts = Rex::Parser::Arguments.new(
178180
'-h' => [ false, 'Help Banner' ],
179181
'-b' => [ true, 'Target bus'],
180182
'-I' => [ true, 'CAN ID'],
181183
'-R' => [ true, 'Return ID'],
182184
'-D' => [ true, 'Data packet in Hex (Do not include ISOTP command size)'],
183185
'-t' => [ true, 'Timeout value'],
186+
'-p' => [ true, 'Padding value, none if not specified'],
187+
'-C' => [ false, 'Force flow control'],
184188
'-m' => [ true, 'Max packets to receive']
185189
)
186190
cansend_opts.parse(args) do |opt, _idx, val|
@@ -199,6 +203,10 @@ def cmd_isotpsend(*args)
199203
data = val
200204
when '-t'
201205
timeout = val.to_i
206+
when '-p'
207+
padding = val
208+
when '-C'
209+
flowcontrol = true
202210
when '-m'
203211
maxpackets = val.to_i
204212
end
@@ -224,6 +232,8 @@ def cmd_isotpsend(*args)
224232
opt = {}
225233
opt['TIMEOUT'] = timeout unless timeout.nil?
226234
opt['MAXPKTS'] = maxpackets unless maxpackets.nil?
235+
opt['PADDING'] = padding unless padding.nil?
236+
opt['FC'] = true unless flowcontrol == false
227237
result = client.automotive.send_isotp_and_wait_for_response(bus, id, ret, bytes, opt)
228238
if result.key? 'Packets'
229239
result['Packets'].each do |pkt|

modules/auxiliary/client/hwbridge/connect.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ def autoload_extensions(sess)
104104
if self.hw_specialty.has_key? 'rftransceiver'
105105
sess.load_rftransceiver if self.hw_specialty['rftransceiver'] == true
106106
end
107+
sess.api_version = self.api_version if self.api_version
108+
sess.fw_version = self.fw_version if self.fw_version
109+
sess.hw_version = self.hw_version if self.hw_version
110+
sess.device_name = self.device_name if self.device_name
107111
end
108112

109113
#
@@ -129,6 +133,18 @@ def get_status
129133
if data.key? 'hw_capabilities'
130134
self.hw_capabilities = data['hw_capabilities']
131135
end
136+
if data.key? 'api_version'
137+
self.api_version = data['api_version']
138+
end
139+
if data.key? 'fw_version'
140+
self.fw_version = data['fw_version']
141+
end
142+
if data.key? 'hw_vesrion'
143+
self.hw_version = data['hw_version']
144+
end
145+
if data.key? 'device_name'
146+
self.device_name = data['device_name']
147+
end
132148
end
133149
end
134150
end
@@ -153,9 +169,17 @@ def run
153169

154170
attr_reader :hw_specialty
155171
attr_reader :hw_capabilities
172+
attr_reader :api_version
173+
attr_reader :fw_version
174+
attr_reader :hw_version
175+
attr_reader :device_name
156176

157177
protected
158178

159179
attr_writer :hw_specialty
160180
attr_writer :hw_capabilities
181+
attr_writer :api_version
182+
attr_writer :fw_version
183+
attr_writer :hw_version
184+
attr_writer :device_name
161185
end

modules/auxiliary/server/local_hwbridge.rb

Lines changed: 62 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class MetasploitModule < Msf::Auxiliary
1111
include Msf::Exploit::Remote::HttpServer::HTML
1212
include Msf::Auxiliary::Report
1313

14-
HWBRIDGE_API_VERSION = "0.0.1"
14+
HWBRIDGE_API_VERSION = "0.0.4"
1515

1616
def initialize(info = {})
1717
super(update_info(info,
@@ -170,13 +170,24 @@ def candump(bus, id, timeout, maxpkts)
170170
# srcid = hex id of the sent packet
171171
# dstid = hex id of the return packets
172172
# data = string of hex bytes to send
173-
# timeout = optional int to timeout on lack of response
174-
# maxpkts = max number of packets to recieve
175-
def isotp_send_and_wait(bus, srcid, dstid, data, timeout = 2000, maxpkts = 3)
173+
# OPT = Options
174+
# timeout = optional int to timeout on lack of response
175+
# maxpkts = max number of packets to recieve
176+
# padding = append bytes to end of packet (Doesn't increase reported ISO-TP size)
177+
# fc = flow control, if true forces flow control packets
178+
def isotp_send_and_wait(bus, srcid, dstid, data, opt = {})
176179
result = {}
177180
result["Success"] = false
178181
srcid = srcid.to_i(16).to_s(16)
179182
dstid = dstid.to_i(16).to_s(16)
183+
timeout = 2000
184+
maxpkts = 3
185+
flowcontrol = nil
186+
padding = nil
187+
timeout = opt['TIMEOUT'] if opt.key? 'TIMEOUT'
188+
maxpkts = opt['MAXPKTS'] if opt.key? 'MAXPKTS'
189+
padding = opt['PADDING'] if opt.key? 'PADDING'
190+
flowcontrol = opt['FC'] if opt.key? 'FC'
180191
bytes = data.scan(/../)
181192
if bytes.size > 8
182193
print_error("Data section currently has to be less than 8 bytes")
@@ -185,6 +196,10 @@ def isotp_send_and_wait(bus, srcid, dstid, data, timeout = 2000, maxpkts = 3)
185196
sz = "%02x" % bytes.size
186197
bytes = sz + bytes.join
187198
end
199+
if padding && bytes.size < 16 # 16 == 8 bytes because of ascii size
200+
padding = "%02x" % padding.to_i
201+
bytes += ([ padding ] * (16 - bytes.size)).join
202+
end
188203
# Should we ever require isotpsend for this?
189204
`which cansend`
190205
unless $?.success?
@@ -193,15 +208,43 @@ def isotp_send_and_wait(bus, srcid, dstid, data, timeout = 2000, maxpkts = 3)
193208
end
194209
@can_interfaces.each do |can|
195210
if can == bus
196-
candump(bus, dstid, timeout, maxpkts)
197-
system("cansend #{bus} #{srcid}##{bytes}")
198-
@packets_sent += 1
199-
@last_sent = Time.now.to_i
200-
result["Success"] = true if $?.success?
201-
result["Packets"] = []
202-
$candump_sniffer.join
203-
unless @pkt_response.empty?
204-
result = @pkt_response
211+
if flowcontrol
212+
candump(bus, dstid, timeout, 1)
213+
system("cansend #{bus} #{srcid}##{bytes}")
214+
@packets_sent += 1
215+
@last_sent = Time.now.to_i
216+
result["Success"] = true if $?.success?
217+
result["Packets"] = []
218+
$candump_sniffer.join
219+
unless @pkt_response.empty?
220+
result = @pkt_response
221+
if result.key?("Packets") && result["Packets"].size > 0 && result["Packets"][0].key?("DATA")
222+
if result["Packets"][0]["DATA"][0] == "10"
223+
system("cansend #{bus} #{srcid}#3000000000000000")
224+
candump(bus, dstid, timeout, maxpkts)
225+
@packets_sent += 1
226+
@last_sent = Time.now.to_i
227+
$candump_sniffer.join
228+
unless @pkt_response.empty?
229+
if @pkt_response.key?("Packets") && @pkt_response["Packets"].size > 0
230+
result["Packets"] += @pkt_response["Packets"]
231+
end
232+
end
233+
end
234+
end
235+
end
236+
237+
else
238+
candump(bus, dstid, timeout, maxpkts)
239+
system("cansend #{bus} #{srcid}##{bytes}")
240+
@packets_sent += 1
241+
@last_sent = Time.now.to_i
242+
result["Success"] = true if $?.success?
243+
result["Packets"] = []
244+
$candump_sniffer.join
245+
unless @pkt_response.empty?
246+
result = @pkt_response
247+
end
205248
end
206249
end
207250
end
@@ -252,11 +295,12 @@ def on_request_uri(cli, request)
252295
elsif request.uri =~ /automotive\/(\w+)\/isotpsend_and_wait\?srcid=(\w+)&dstid=(\w+)&data=(\w+)/
253296
bus = $1; srcid = $2; dstid = $3; data = $4
254297
print_status("Request to send ISO-TP packet and wait for response #{srcid}##{data} => #{dstid}") if datastore['VERBOSE']
255-
timeout = 1500
256-
maxpkts = 3
257-
timeout = $1 if request.uri =~ /&timeout=(\d+)/
258-
maxpkts = $1 if request.uri =~ /&maxpkts=(\d+)/
259-
send_response_html(cli, isotp_send_and_wait(bus, srcid, dstid, data, timeout, maxpkts).to_json(), { 'Content-Type' => 'application/json' })
298+
opt = {}
299+
opt['TIMEOUT'] = $1 if request.uri =~ /&timeout=(\d+)/
300+
opt['MAXPKTS'] = $1 if request.uri =~ /&maxpkts=(\d+)/
301+
opt['PADDING'] = $1 if request.uri =~ /&padding=(\d+)/
302+
opt['FC'] = true if request.uri =~ /&fc=true/i
303+
send_response_html(cli, isotp_send_and_wait(bus, srcid, dstid, data, opt).to_json(), { 'Content-Type' => 'application/json' })
260304
else
261305
send_response_html(cli, not_supported().to_json(), { 'Content-Type' => 'application/json' })
262306
end

modules/post/hardware/automotive/getvinfo.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ def initialize(info={})
2424
OptInt.new('SRCID', [true, "Module ID to query", 0x7e0]),
2525
OptInt.new('DSTID', [false, "Expected reponse ID, defaults to SRCID + 8", 0x7e8]),
2626
OptInt.new('PADDING', [false, "Optinal end of packet padding", nil]),
27+
OptBool.new('FC', [false, "Optinal forces flow control", nil]),
2728
OptBool.new('CLEAR_DTCS', [false, "Clear any DTCs and reset MIL if errors are present", false]),
2829
OptString.new('CANBUS', [false, "CAN Bus to perform scan on, defaults to connected bus", nil])
2930
])
@@ -33,6 +34,7 @@ def initialize(info={})
3334
def run
3435
opt = {}
3536
opt['PADDING'] = datastore["PADDING"] if datastore["PADDING"]
37+
opt['FC'] = datastore['FC'] if datastore['FC']
3638
pids = get_current_data_pids(datastore["CANBUS"], datastore["SRCID"], datastore["DSTID"], opt)
3739
if pids.size == 0
3840
print_status("No reported PIDs. You may not be properly connected")

0 commit comments

Comments
 (0)