From 45c216f1350b03f254939cb861fda5b92e8c25bf Mon Sep 17 00:00:00 2001 From: Nils Weiss Date: Sat, 7 Sep 2024 22:46:59 +0200 Subject: [PATCH] Fix #4472: Add ability to parse multiple DoIP packets in one TCP pkt (#4515) * Fix #4472: Add ability to parse multiple DoIP packets in one TCP packet via TCPSession * update * When not in app mode, tcp_reassemble sub-packets --------- Co-authored-by: gpotter2 <10530980+gpotter2@users.noreply.github.com> --- scapy/contrib/automotive/doip.py | 6 +++--- scapy/sessions.py | 26 +++++++++++++++++++----- test/contrib/automotive/doip.uts | 11 ++++++++++ test/pcaps/multiple_doip_layers.pcap.gz | Bin 0 -> 206 bytes 4 files changed, 35 insertions(+), 8 deletions(-) create mode 100644 test/pcaps/multiple_doip_layers.pcap.gz diff --git a/scapy/contrib/automotive/doip.py b/scapy/contrib/automotive/doip.py index 60b304638..fe514b981 100644 --- a/scapy/contrib/automotive/doip.py +++ b/scapy/contrib/automotive/doip.py @@ -257,15 +257,15 @@ class DoIP(Packet): def extract_padding(self, s): # type: (bytes) -> Tuple[bytes, Optional[bytes]] if self.payload_type == 0x8001: - return s[:self.payload_length - 4], None + return s[:self.payload_length - 4], s[self.payload_length - 4:] else: - return b"", None + return b"", s @classmethod def tcp_reassemble(cls, data, metadata, session): # type: (bytes, Dict[str, Any], Dict[str, Any]) -> Optional[Packet] length = struct.unpack("!I", data[4:8])[0] + 8 - if len(data) == length: + if len(data) >= length: return DoIP(data) return None diff --git a/scapy/sessions.py b/scapy/sessions.py index 01e005505..be95b7693 100644 --- a/scapy/sessions.py +++ b/scapy/sessions.py @@ -367,11 +367,22 @@ class TCPSession(IPSession): metadata.clear() # Check for padding padding = self._strip_padding(packet) - if padding: + while padding: # There is remaining data for the next payload. full_length = data.content_len - len(padding) metadata["relative_seq"] = relative_seq + full_length data.shiftleft(full_length) + # There might be a sub-payload hidden in the padding + sub_packet = tcp_reassemble( + bytes(data), + metadata, + tcp_session + ) + if sub_packet: + packet /= sub_packet + padding = self._strip_padding(sub_packet) + else: + break else: # No padding (data) left. Clear data.clear() @@ -397,10 +408,15 @@ class TCPSession(IPSession): """ pkt = sock.recv(stop_dissection_after=self.stop_dissection_after) # Now handle TCP reassembly - while pkt is not None: - pkt = self.process(pkt) + if self.app: + while pkt is not None: + pkt = self.process(pkt) + if pkt: + yield pkt + # keep calling process as there might be more + pkt = b"" # type: ignore + else: + pkt = self.process(pkt) # type: ignore if pkt: yield pkt - # keep calling process as there might be more - pkt = b"" # type: ignore return None diff --git a/test/contrib/automotive/doip.uts b/test/contrib/automotive/doip.uts index 161b9b3df..b246b5421 100644 --- a/test/contrib/automotive/doip.uts +++ b/test/contrib/automotive/doip.uts @@ -394,6 +394,17 @@ pkts = sniff(offline=tmp_file, session=TCPSession) assert pkts[0].haslayer(UDS_TP) assert pkts[0].service == 0x3E += TCPSession Test multiple DoIP messages + +filename = scapy_path("/test/pcaps/multiple_doip_layers.pcap.gz") + +pkts = sniff(offline=filename, session=TCPSession) +print(repr(pkts[0])) +print(repr(pkts[1])) +assert len(pkts) == 2 +assert pkts[0][DoIP].payload_length == 2 +assert pkts[0][DoIP:2].payload_length == 7 +assert pkts[1][DoIP].payload_length == 103 + DoIP Communication tests diff --git a/test/pcaps/multiple_doip_layers.pcap.gz b/test/pcaps/multiple_doip_layers.pcap.gz new file mode 100644 index 0000000000000000000000000000000000000000..79302b2bc76b30c320966de0843fd954d1b64bdb GIT binary patch literal 206 zcmV;<05Sg`iwFqhd7@?j18sF|bZKyGWnW}(X>ea`VR>b8b1raWVQ>Jua(L51CI%J; z1Yluc1d_9+u1~3AW?=AVfZ$cT&)gN@1Cb2O91N}u435l(4h#-#OOy|+;A943{sYV= z5gd~bN2M43*d8Fk!myy7fr05S0|!tu6F-pE05X=HpFu!W$)pivyru{P!%_wah8PU7 z1Z@2DwpAeG6+*y91FbNLIKT`v`Uud9;sA+XPQR=eV1}j(i1ITenKUxc6qqyu0K2Ek I^DF@X07)!T&j0`b literal 0 HcmV?d00001