Skip to content

Commit c38a5de

Browse files
authored
Update HSFZ with more details (#4544)
1 parent 87b6e26 commit c38a5de

File tree

2 files changed

+82
-43
lines changed

2 files changed

+82
-43
lines changed

scapy/contrib/automotive/bmw/hsfz.py

+49-24
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,9 @@
66
# scapy.contrib.description = HSFZ - BMW High-Speed-Fahrzeug-Zugang
77
# scapy.contrib.status = loads
88
import logging
9-
import struct
109
import socket
10+
import struct
1111
import time
12-
13-
from scapy.contrib.automotive import log_automotive
14-
from scapy.packet import Packet, bind_layers, bind_bottom_up
15-
from scapy.fields import IntField, ShortEnumField, XByteField
16-
from scapy.layers.inet import TCP
17-
from scapy.supersocket import StreamSocket
18-
from scapy.contrib.automotive.uds import UDS, UDS_TP
19-
from scapy.data import MTU
20-
2112
from typing import (
2213
Any,
2314
Optional,
@@ -28,29 +19,58 @@
2819
Union,
2920
)
3021

22+
from scapy.contrib.automotive import log_automotive
23+
from scapy.contrib.automotive.uds import UDS, UDS_TP
24+
from scapy.data import MTU
25+
from scapy.fields import (IntField, ShortEnumField, XByteField,
26+
ConditionalField, StrFixedLenField)
27+
from scapy.layers.inet import TCP, UDP
28+
from scapy.packet import Packet, bind_layers, bind_bottom_up
29+
from scapy.supersocket import StreamSocket
3130

3231
"""
3332
BMW HSFZ (High-Speed-Fahrzeug-Zugang / High-Speed-Car-Access).
3433
BMW specific diagnostic over IP protocol implementation.
3534
The physical interface for this connection is called ENET.
3635
"""
3736

37+
3838
# #########################HSFZ###################################
3939

4040

4141
class HSFZ(Packet):
42+
control_words = {
43+
0x01: "diagnostic_req_res",
44+
0x02: "acknowledge_transfer",
45+
0x10: "terminal15",
46+
0x11: "vehicle_ident_data",
47+
0x12: "alive_check",
48+
0x13: "status_data_inquiry",
49+
0x40: "incorrect_tester_address",
50+
0x41: "incorrect_control_word",
51+
0x42: "incorrect_format",
52+
0x43: "incorrect_dest_address",
53+
0x44: "message_too_large",
54+
0x45: "diag_app_not_ready",
55+
0xFF: "out_of_memory"
56+
}
4257
name = 'HSFZ'
4358
fields_desc = [
4459
IntField('length', None),
45-
ShortEnumField('type', 1, {0x01: "message",
46-
0x02: "echo"}),
47-
XByteField('src', 0),
48-
XByteField('dst', 0),
60+
ShortEnumField('control', 1, control_words),
61+
ConditionalField(
62+
XByteField('source', 0), lambda p: p.control == 1),
63+
ConditionalField(
64+
XByteField('target', 0), lambda p: p.control == 1),
65+
ConditionalField(
66+
StrFixedLenField("identification_string",
67+
None, None, lambda p: p.length),
68+
lambda p: p.control == 0x11)
4969
]
5070

5171
def hashret(self):
5272
# type: () -> bytes
53-
hdr_hash = struct.pack("B", self.src ^ self.dst)
73+
hdr_hash = struct.pack("B", self.source ^ self.target)
5474
pay_hash = self.payload.hashret()
5575
return hdr_hash + pay_hash
5676

@@ -71,6 +91,11 @@ def post_build(self, pkt, pay):
7191
bind_bottom_up(TCP, HSFZ, sport=6801)
7292
bind_bottom_up(TCP, HSFZ, dport=6801)
7393
bind_layers(TCP, HSFZ, sport=6801, dport=6801)
94+
95+
bind_bottom_up(UDP, HSFZ, sport=6811)
96+
bind_bottom_up(UDP, HSFZ, dport=6811)
97+
bind_layers(UDP, HSFZ, sport=6811, dport=6811)
98+
7499
bind_layers(HSFZ, UDS)
75100

76101

@@ -111,11 +136,11 @@ def recv(self, x=MTU, **kwargs):
111136

112137

113138
class UDS_HSFZSocket(HSFZSocket):
114-
def __init__(self, src, dst, ip='127.0.0.1', port=6801, basecls=UDS):
139+
def __init__(self, source, target, ip='127.0.0.1', port=6801, basecls=UDS):
115140
# type: (int, int, str, int, Type[Packet]) -> None
116141
super(UDS_HSFZSocket, self).__init__(ip, port)
117-
self.src = src
118-
self.dst = dst
142+
self.source = source
143+
self.target = target
119144
self.basecls = HSFZ
120145
self.outputcls = basecls
121146

@@ -128,7 +153,7 @@ def send(self, x):
128153

129154
try:
130155
return super(UDS_HSFZSocket, self).send(
131-
HSFZ(src=self.src, dst=self.dst) / x)
156+
HSFZ(source=self.source, target=self.target) / x)
132157
except Exception as e:
133158
# Workaround:
134159
# This catch block is currently necessary to detect errors
@@ -153,7 +178,7 @@ def recv(self, x=MTU, **kwargs):
153178

154179
def hsfz_scan(ip, # type: str
155180
scan_range=range(0x100), # type: Iterable[int]
156-
src=0xf4, # type: int
181+
source=0xf4, # type: int
157182
timeout=0.1, # type: Union[int, float]
158183
verbose=True # type: bool
159184
):
@@ -166,7 +191,7 @@ def hsfz_scan(ip, # type: str
166191
167192
:param ip: IPv4 address of target to scan
168193
:param scan_range: Range for HSFZ destination address
169-
:param src: HSFZ source address, used during the scan
194+
:param source: HSFZ source address, used during the scan
170195
:param timeout: Timeout for each request
171196
:param verbose: Show information during scan, if True
172197
:return: A list of open UDS_HSFZSockets
@@ -175,7 +200,7 @@ def hsfz_scan(ip, # type: str
175200
log_automotive.setLevel(logging.DEBUG)
176201
results = list()
177202
for i in scan_range:
178-
with UDS_HSFZSocket(src, i, ip) as sock:
203+
with UDS_HSFZSocket(source, i, ip) as sock:
179204
try:
180205
resp = sock.sr1(UDS() / UDS_TP(),
181206
timeout=timeout,
@@ -184,8 +209,8 @@ def hsfz_scan(ip, # type: str
184209
results.append((i, resp))
185210
if resp:
186211
log_automotive.debug(
187-
"Found endpoint %s, src=0x%x, dst=0x%x" % (ip, src, i))
212+
"Found endpoint %s, source=0x%x, target=0x%x" % (ip, source, i))
188213
except Exception as e:
189214
log_automotive.exception(
190215
"Error %s at destination address 0x%x" % (e, i))
191-
return [UDS_HSFZSocket(0xf4, dst, ip) for dst, _ in results]
216+
return [UDS_HSFZSocket(0xf4, target, ip) for target, _ in results]

test/contrib/automotive/bmw/hsfz.uts

+33-19
Original file line numberDiff line numberDiff line change
@@ -13,73 +13,87 @@ load_contrib("automotive.bmw.hsfz", globals_dict=globals())
1313

1414
= Basic Test 1
1515

16-
pkt = HSFZ(type=1, src=0xf4, dst=0x10)/Raw(b'\x11\x22\x33')
16+
pkt = HSFZ(control=1, source=0xf4, target=0x10)/Raw(b'\x11\x22\x33')
1717
assert bytes(pkt) == b'\x00\x00\x00\x05\x00\x01\xf4\x10\x11"3'
1818

1919
= Basic Test 2
2020

21-
pkt = HSFZ(type=1, src=0xf4, dst=0x10)/Raw(b'\x11\x22\x33\x11\x11\x11\x11\x11')
21+
pkt = HSFZ(control=1, source=0xf4, target=0x10)/Raw(b'\x11\x22\x33\x11\x11\x11\x11\x11')
2222
assert bytes(pkt) == b'\x00\x00\x00\x0a\x00\x01\xf4\x10\x11"3\x11\x11\x11\x11\x11'
2323

2424
= Basic Dissect Test
2525

2626
pkt = HSFZ(b'\x00\x00\x00\x0a\x00\x01\xf4\x10\x11"3\x11\x11\x11\x11\x11')
2727
assert pkt.length == 10
28-
assert pkt.src == 0xf4
29-
assert pkt.dst == 0x10
30-
assert pkt.type == 1
28+
assert pkt.source == 0xf4
29+
assert pkt.target == 0x10
30+
assert pkt.control == 1
3131
assert pkt[1].service == 17
3232
assert pkt[2].resetType == 34
3333

3434
= Build Test
3535

36-
pkt = HSFZ(src=0xf4, dst=0x10)/Raw(b"0" * 20)
36+
pkt = HSFZ(source=0xf4, target=0x10)/Raw(b"0" * 20)
3737
assert bytes(pkt) == b'\x00\x00\x00\x16\x00\x01\xf4\x10' + b"0" * 20
3838

3939
= Dissect Test
4040

4141
pkt = HSFZ(b'\x00\x00\x00\x18\x00\x01\xf4\x10\x67\x01' + b"0" * 20)
4242
assert pkt.length == 24
43-
assert pkt.src == 0xf4
44-
assert pkt.dst == 0x10
45-
assert pkt.type == 1
43+
assert pkt.source == 0xf4
44+
assert pkt.target == 0x10
45+
assert pkt.control == 1
4646
assert pkt.securitySeed == b"0" * 20
4747
assert len(pkt[1]) == pkt.length - 2
4848

4949
= Dissect Test with padding
5050

5151
pkt = HSFZ(b'\x00\x00\x00\x18\x00\x01\xf4\x10\x67\x01' + b"0" * 20 + b"p" * 100)
5252
assert pkt.length == 24
53-
assert pkt.src == 0xf4
54-
assert pkt.dst == 0x10
55-
assert pkt.type == 1
53+
assert pkt.source == 0xf4
54+
assert pkt.target == 0x10
55+
assert pkt.control == 1
5656
assert pkt.securitySeed == b"0" * 20
5757
assert pkt.load == b'p' * 100
5858

5959
= Dissect Test to short packet
6060

6161
pkt = HSFZ(b'\x00\x00\x00\x18\x00\x01\xf4\x10\x67\x01' + b"0" * 19)
6262
assert pkt.length == 24
63-
assert pkt.src == 0xf4
64-
assert pkt.dst == 0x10
65-
assert pkt.type == 1
63+
assert pkt.source == 0xf4
64+
assert pkt.target == 0x10
65+
assert pkt.control == 1
6666
assert pkt.securitySeed == b"0" * 19
6767

68+
6869
= Dissect Test very long packet
6970

7071
pkt = HSFZ(b'\x00\x0f\xff\x04\x00\x01\xf4\x10\x67\x01' + b"0" * 0xfff00)
7172
assert pkt.length == 0xfff04
72-
assert pkt.src == 0xf4
73-
assert pkt.dst == 0x10
74-
assert pkt.type == 1
73+
assert pkt.source == 0xf4
74+
assert pkt.target == 0x10
75+
assert pkt.control == 1
7576
assert pkt.securitySeed == b"0" * 0xfff00
7677

78+
79+
= Dissect identification
80+
81+
pkt = HSFZ(bytes.fromhex("000000320011444941474144523130424d574d4143374346436343463837393343424d5756494e5742413558373333333246483735373334"))
82+
assert pkt.length == 50
83+
assert pkt.control == 0x11
84+
assert b"BMW" in pkt.identification_string
85+
86+
pkt = UDP(bytes.fromhex("1a9be2d90040d67d000000320011444941474144523130424d574d4143374346436343463837393343424d5756494e5742413558373333333246483735373334"))
87+
assert pkt.length == 50
88+
assert pkt.control == 0x11
89+
assert b"BMW" in pkt.identification_string
90+
7791
= Test HSFZSocket
7892

7993

8094
server_up = threading.Event()
8195
def server():
82-
buffer = bytes(HSFZ(type=1, src=0xf4, dst=0x10) / Raw(b'\x11\x22\x33' * 1024))
96+
buffer = bytes(HSFZ(control=1, source=0xf4, target=0x10) / Raw(b'\x11\x22\x33' * 1024))
8397
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
8498
try:
8599
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)

0 commit comments

Comments
 (0)