-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add support for the HSFZ protocol
The High Speed Fahrzeugzugang (HSFZ) protocol is an automotive protocol used to tunnel UDS traffic through TCP. Since version 4.1, Wireshark contains [1] a protocol dissector for HSFZ enabling own implementations of this protocol. Scapy also contains an implementation of HSFZ [2]. [1]: wireshark/wireshark@e5ced7a [2]: https://github.com/secdev/scapy/blob/master/scapy/contrib/automotive/bmw/hsfz.py Co-authored-by: Ferdinand Jarisch <[email protected]> Co-authored-by: Tobias Specht <[email protected]>
- Loading branch information
1 parent
d97251e
commit 22531b2
Showing
6 changed files
with
848 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
import asyncio | ||
from argparse import Namespace | ||
|
||
from gallia.command import UDSDiscoveryScanner | ||
from gallia.log import get_logger | ||
from gallia.services.uds.core.service import ( | ||
DiagnosticSessionControlRequest, | ||
DiagnosticSessionControlResponse, | ||
UDSRequest, | ||
) | ||
from gallia.services.uds.helpers import raise_for_mismatch | ||
from gallia.transports.base import TargetURI | ||
from gallia.transports.hsfz import HSFZConnection | ||
from gallia.utils import auto_int, write_target_list | ||
|
||
logger = get_logger(__name__) | ||
|
||
|
||
class HSFZDiscoverer(UDSDiscoveryScanner): | ||
"""ECU and routing discovery scanner for HSFZ""" | ||
|
||
COMMAND = "hsfz" | ||
SHORT_HELP = "" | ||
|
||
def configure_parser(self) -> None: | ||
self.parser.add_argument( | ||
"--reversed", | ||
action="store_true", | ||
help="scan in reversed order", | ||
) | ||
self.parser.add_argument( | ||
"--src-addr", | ||
type=auto_int, | ||
default=0xF4, | ||
help="HSFZ source address", | ||
) | ||
self.parser.add_argument( | ||
"--start", | ||
metavar="INT", | ||
type=auto_int, | ||
default=0x00, | ||
help="set start address", | ||
) | ||
self.parser.add_argument( | ||
"--stop", | ||
metavar="INT", | ||
type=auto_int, | ||
default=0xFF, | ||
help="set end address", | ||
) | ||
|
||
async def _probe( | ||
self, | ||
conn: HSFZConnection, | ||
req: UDSRequest, | ||
timeout: float, | ||
) -> bool: | ||
data = req.pdu | ||
result = False | ||
|
||
await asyncio.wait_for(conn.write_diag_request(data), timeout) | ||
|
||
# Broadcast endpoints deliver more responses. | ||
# Make sure to flush the receive queue properly. | ||
while True: | ||
try: | ||
raw_resp = await asyncio.wait_for(conn.read_diag_request(), timeout) | ||
except TimeoutError: | ||
return result | ||
|
||
resp = DiagnosticSessionControlResponse.parse_static(raw_resp) | ||
raise_for_mismatch(req, resp) | ||
result = True | ||
|
||
async def probe( | ||
self, | ||
host: str, | ||
port: int, | ||
src_addr: int, | ||
dst_addr: int, | ||
timeout: float, | ||
ack_timeout: float = 1.0, | ||
) -> TargetURI | None: | ||
req = DiagnosticSessionControlRequest(0x01) | ||
|
||
try: | ||
conn = await HSFZConnection.connect( | ||
host, | ||
port, | ||
src_addr, | ||
dst_addr, | ||
ack_timeout, | ||
) | ||
except TimeoutError: | ||
return None | ||
|
||
try: | ||
result = await self._probe(conn, req, timeout) | ||
except (TimeoutError, ConnectionError): | ||
return None | ||
finally: | ||
await conn.close() | ||
|
||
if result: | ||
return TargetURI.from_parts( | ||
"hsfz", | ||
host, | ||
port, | ||
{ | ||
"src_addr": f"{src_addr:#02x}", | ||
"dst_addr": f"{dst_addr:#02x}", | ||
"ack_timeout": int(ack_timeout) * 1000, | ||
}, | ||
) | ||
return None | ||
|
||
async def main(self, args: Namespace) -> None: | ||
found = [] | ||
gen = ( | ||
range(args.stop + 1, args.start) if args.reversed else range(args.start, args.stop + 1) | ||
) | ||
|
||
for dst_addr in gen: | ||
logger.info(f"testing target {dst_addr:#02x}") | ||
|
||
target = await self.probe( | ||
args.target.hostname, | ||
args.target.port, | ||
args.src_addr, | ||
dst_addr, | ||
args.timeout, | ||
) | ||
|
||
if target is not None: | ||
logger.info(f"found {dst_addr:#02x}") | ||
found.append(target) | ||
|
||
logger.result(f"Found {len(found)} targets") | ||
ecus_file = self.artifacts_dir.joinpath("ECUs.txt") | ||
logger.result(f"Writing urls to file: {ecus_file}") | ||
await write_target_list(ecus_file, found, self.db_handler) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.