diff --git a/doc/scapy/layers/automotive.rst b/doc/scapy/layers/automotive.rst index bf74fc3c561..8316d8e14e9 100644 --- a/doc/scapy/layers/automotive.rst +++ b/doc/scapy/layers/automotive.rst @@ -1158,7 +1158,7 @@ then casted to ``UDS`` objects through the ``basecls`` parameter Usage example:: with PcapReader("test/contrib/automotive/ecu_trace.pcap") as sock: - udsmsgs = sniff(session=ISOTPSession, session_kwargs={"use_ext_addr":False, "basecls":UDS}, count=50, opened_socket=sock) + udsmsgs = sniff(session=ISOTPSession(use_ext_addr=False, basecls=UDS), count=50, opened_socket=sock) ecu = Ecu() @@ -1183,7 +1183,7 @@ Usage example:: session = EcuSession() with PcapReader("test/contrib/automotive/ecu_trace.pcap") as sock: - udsmsgs = sniff(session=ISOTPSession, session_kwargs={"supersession": session, "use_ext_addr":False, "basecls":UDS}, count=50, opened_socket=sock) + udsmsgs = sniff(session=ISOTPSession(use_ext_addr=False, basecls=UDS, supersession=session)), count=50, opened_socket=sock) ecu = session.ecu print(ecu.log) diff --git a/doc/scapy/usage.rst b/doc/scapy/usage.rst index a06b5ba200d..dc4aca70014 100644 --- a/doc/scapy/usage.rst +++ b/doc/scapy/usage.rst @@ -783,9 +783,8 @@ Those sessions can be used using the ``session=`` parameter of ``sniff()``. Exam .. note:: To implement your own Session class, in order to support another flow-based protocol, start by copying a sample from `scapy/sessions.py `_ - Your custom ``Session`` class only needs to extend the :py:class:`~scapy.sessions.DefaultSession` class, and implement a ``on_packet_received`` function, such as in the example. + Your custom ``Session`` class only needs to extend the :py:class:`~scapy.sessions.DefaultSession` class, and implement a ``process`` or a ``recv`` function, such as in the examples. -.. note:: Would you need it, you can use: ``class TLS_over_TCP(TLSSession, TCPSession): pass`` to sniff TLS packets that are defragmented. How to use TCPSession to defragment TCP packets ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/scapy/arch/bpf/supersocket.py b/scapy/arch/bpf/supersocket.py index 4f3aea6cd32..3e57a64d1b0 100644 --- a/scapy/arch/bpf/supersocket.py +++ b/scapy/arch/bpf/supersocket.py @@ -425,9 +425,9 @@ def nonblock_recv(self): class L3bpfSocket(L2bpfSocket): - def recv(self, x=BPF_BUFFER_LENGTH): + def recv(self, x=BPF_BUFFER_LENGTH, **kwargs): """Receive on layer 3""" - r = SuperSocket.recv(self, x) + r = SuperSocket.recv(self, x, **kwargs) if r: r.payload.time = r.time return r.payload diff --git a/scapy/arch/libpcap.py b/scapy/arch/libpcap.py index 7ac3fcf66fe..fc4b210f4fa 100644 --- a/scapy/arch/libpcap.py +++ b/scapy/arch/libpcap.py @@ -38,13 +38,14 @@ import scapy.consts from typing import ( - cast, + Any, Dict, List, NoReturn, Optional, Tuple, Type, + cast, ) if not scapy.consts.WINDOWS: @@ -571,9 +572,9 @@ def send(self, x): class L3pcapSocket(L2pcapSocket): desc = "read/write packets at layer 3 using only libpcap" - def recv(self, x=MTU): - # type: (int) -> Optional[Packet] - r = L2pcapSocket.recv(self, x) + def recv(self, x=MTU, **kwargs): + # type: (int, **Any) -> Optional[Packet] + r = L2pcapSocket.recv(self, x, **kwargs) if r: r.payload.time = r.time return r.payload diff --git a/scapy/arch/linux.py b/scapy/arch/linux.py index d597ca4457a..58100e5479a 100644 --- a/scapy/arch/linux.py +++ b/scapy/arch/linux.py @@ -593,9 +593,9 @@ def send(self, x): class L3PacketSocket(L2Socket): desc = "read/write packets at layer 3 using Linux PF_PACKET sockets" - def recv(self, x=MTU): - # type: (int) -> Optional[Packet] - pkt = SuperSocket.recv(self, x) + def recv(self, x=MTU, **kwargs): + # type: (int, **Any) -> Optional[Packet] + pkt = SuperSocket.recv(self, x, **kwargs) if pkt and self.lvl == 2: pkt.payload.time = pkt.time return pkt.payload diff --git a/scapy/automaton.py b/scapy/automaton.py index 27e2cef3e1e..63a03ce3f03 100644 --- a/scapy/automaton.py +++ b/scapy/automaton.py @@ -636,11 +636,13 @@ def fileno(self): # type: () -> int return self.spb.fileno() - def recv(self, n=MTU): - # type: (Optional[int]) -> Any + # note: _ATMT_supersocket may return bytes in certain cases, which + # is expected. We cheat on typing. + def recv(self, n=MTU, **kwargs): # type: ignore + # type: (int, **Any) -> Any r = self.spb.recv(n) if self.proto is not None and r is not None: - r = self.proto(r) + r = self.proto(r, **kwargs) return r def close(self): diff --git a/scapy/config.py b/scapy/config.py index 3be0bfec4a7..7c4215998ff 100755 --- a/scapy/config.py +++ b/scapy/config.py @@ -701,6 +701,13 @@ def _iface_changer(attr, val, old): return val # type: ignore +def _reset_tls_nss_keys(attr, val, old): + # type: (str, Any, Any) -> Any + """Reset conf.tls_nss_keys when conf.tls_nss_filename changes""" + conf.tls_nss_keys = None + return val + + class Conf(ConfClass): """ This object contains the configuration of Scapy. @@ -775,7 +782,8 @@ class Conf(ConfClass): filter = "" #: when 1, store received packet that are not matched into `debug.recv` debug_match = False - #: When 1, print some TLS session secrets when they are computed. + #: When 1, print some TLS session secrets when they are computed, and + #: warn about the session recognition. debug_tls = False wepkey = "" #: holds the Scapy interface list and manager @@ -901,6 +909,16 @@ class Conf(ConfClass): #: a safety mechanism: the maximum amount of items included in a PacketListField #: or a FieldListField max_list_count = 100 + #: When the TLS module is loaded (not by default), the following turns on sessions + tls_session_enable = False + #: Filename containing NSS Keys Log + tls_nss_filename = Interceptor( + "tls_nss_filename", + None, + _reset_tls_nss_keys + ) + #: Dictionary containing parsed NSS Keys + tls_nss_keys = None def __getattribute__(self, attr): # type: (str) -> Any diff --git a/scapy/contrib/automotive/bmw/hsfz.py b/scapy/contrib/automotive/bmw/hsfz.py index 376a968e4b4..558141fc8bc 100644 --- a/scapy/contrib/automotive/bmw/hsfz.py +++ b/scapy/contrib/automotive/bmw/hsfz.py @@ -19,6 +19,7 @@ from scapy.data import MTU from typing import ( + Any, Optional, Tuple, Type, @@ -88,8 +89,8 @@ def __init__(self, ip='127.0.0.1', port=6801): StreamSocket.__init__(self, s, HSFZ) self.buffer = b"" - def recv(self, x=MTU): - # type: (int) -> Optional[Packet] + def recv(self, x=MTU, **kwargs): + # type: (int, **Any) -> Optional[Packet] if self.buffer: len_data = self.buffer[:4] else: @@ -104,7 +105,7 @@ def recv(self, x=MTU): if len(self.buffer) != len_int: return None - pkt = self.basecls(self.buffer) # type: Packet + pkt = self.basecls(self.buffer, **kwargs) # type: Packet self.buffer = b"" return pkt @@ -141,11 +142,11 @@ def send(self, x): self.close() return 0 - def recv(self, x=MTU): - # type: (int) -> Optional[Packet] + def recv(self, x=MTU, **kwargs): + # type: (int, **Any) -> Optional[Packet] pkt = super(UDS_HSFZSocket, self).recv(x) if pkt: - return self.outputcls(bytes(pkt.payload)) + return self.outputcls(bytes(pkt.payload), **kwargs) else: return pkt diff --git a/scapy/contrib/automotive/doip.py b/scapy/contrib/automotive/doip.py index bce9944aaa0..096bdb9e586 100644 --- a/scapy/contrib/automotive/doip.py +++ b/scapy/contrib/automotive/doip.py @@ -33,6 +33,7 @@ from scapy.data import MTU from typing import ( + Any, Union, Tuple, Optional, @@ -294,8 +295,8 @@ def __init__(self, ip='127.0.0.1', port=13400, activate_routing=True, self._activate_routing( source_address, target_address, activation_type, reserved_oem) - def recv(self, x=MTU): - # type: (int) -> Optional[Packet] + def recv(self, x=MTU, **kwargs): + # type: (int, **Any) -> Optional[Packet] if self.buffer: len_data = self.buffer[:8] else: @@ -310,7 +311,7 @@ def recv(self, x=MTU): if len(self.buffer) != len_int: return None - pkt = self.basecls(self.buffer) # type: Packet + pkt = self.basecls(self.buffer, **kwargs) # type: Packet self.buffer = b"" return pkt @@ -407,9 +408,9 @@ def send(self, x): return super(UDS_DoIPSocket, self).send(pkt) - def recv(self, x=MTU): - # type: (int) -> Optional[Packet] - pkt = super(UDS_DoIPSocket, self).recv(x) + def recv(self, x=MTU, **kwargs): + # type: (int, **Any) -> Optional[Packet] + pkt = super(UDS_DoIPSocket, self).recv(x, **kwargs) if pkt and pkt.payload_type == 0x8001: return pkt.payload else: diff --git a/scapy/contrib/automotive/ecu.py b/scapy/contrib/automotive/ecu.py index c2caa769de6..7458468c95b 100644 --- a/scapy/contrib/automotive/ecu.py +++ b/scapy/contrib/automotive/ecu.py @@ -469,17 +469,16 @@ class EcuSession(DefaultSession): """ def __init__(self, *args, **kwargs): # type: (Any, Any) -> None - DefaultSession.__init__(self, *args, **kwargs) self.ecu = Ecu(logging=kwargs.pop("logging", True), verbose=kwargs.pop("verbose", True), store_supported_responses=kwargs.pop("store_supported_responses", True)) # noqa: E501 + super(EcuSession, self).__init__(*args, **kwargs) - def on_packet_received(self, pkt): - # type: (Optional[Packet]) -> None + def process(self, pkt: Packet) -> Optional[Packet]: if not pkt: - return + return None self.ecu.update(pkt) - DefaultSession.on_packet_received(self, pkt) + return pkt class EcuResponse: diff --git a/scapy/contrib/isotp/isotp_native_socket.py b/scapy/contrib/isotp/isotp_native_socket.py index 77e4d0f7af9..34a77cb84aa 100644 --- a/scapy/contrib/isotp/isotp_native_socket.py +++ b/scapy/contrib/isotp/isotp_native_socket.py @@ -23,6 +23,7 @@ # Typing imports from typing import ( + Any, Optional, Union, Tuple, @@ -387,9 +388,9 @@ def recv_raw(self, x=0xffff): ts = get_last_packet_timestamp(self.ins) return self.basecls, pkt, ts - def recv(self, x=0xffff): - # type: (int) -> Optional[Packet] - msg = SuperSocket.recv(self, x) + def recv(self, x=0xffff, **kwargs): + # type: (int, **Any) -> Optional[Packet] + msg = SuperSocket.recv(self, x, **kwargs) if msg is None: return msg diff --git a/scapy/contrib/isotp/isotp_soft_socket.py b/scapy/contrib/isotp/isotp_soft_socket.py index cc6be464d70..96411c41908 100644 --- a/scapy/contrib/isotp/isotp_soft_socket.py +++ b/scapy/contrib/isotp/isotp_soft_socket.py @@ -186,9 +186,9 @@ def recv_raw(self, x=0xffff): return self.basecls, tup[0], float(tup[1]) return self.basecls, None, None - def recv(self, x=0xffff): - # type: (int) -> Optional[Packet] - msg = super(ISOTPSoftSocket, self).recv(x) + def recv(self, x=0xffff, **kwargs): + # type: (int, **Any) -> Optional[Packet] + msg = super(ISOTPSoftSocket, self).recv(x, **kwargs) if msg is None: return None diff --git a/scapy/contrib/isotp/isotp_utils.py b/scapy/contrib/isotp/isotp_utils.py index 49269f20aab..da1d5e73ab9 100644 --- a/scapy/contrib/isotp/isotp_utils.py +++ b/scapy/contrib/isotp/isotp_utils.py @@ -14,12 +14,15 @@ from scapy.utils import EDecimal from scapy.packet import Packet from scapy.sessions import DefaultSession +from scapy.supersocket import SuperSocket from scapy.contrib.isotp.isotp_packet import ISOTP, N_PCI_CF, N_PCI_SF, \ N_PCI_FF, N_PCI_FC # Typing imports from typing import ( + cast, Iterable, + Iterator, Optional, Union, List, @@ -336,20 +339,23 @@ class ISOTPSession(DefaultSession): def __init__(self, *args, **kwargs): # type: (Any, Any) -> None - super(ISOTPSession, self).__init__(*args, **kwargs) self.m = ISOTPMessageBuilder( use_ext_address=kwargs.pop("use_ext_address", None), rx_id=kwargs.pop("rx_id", None), basecls=kwargs.pop("basecls", ISOTP)) + super(ISOTPSession, self).__init__(*args, **kwargs) - def on_packet_received(self, pkt): - # type: (Optional[Packet]) -> None + def recv(self, sock: SuperSocket) -> Iterator[Packet]: + """ + Will be called by sniff() to ask for a packet + """ + pkt = sock.recv() if not pkt: return self.m.feed(pkt) while len(self.m) > 0: - rcvd = self.m.pop() - if self._supersession: - self._supersession.on_packet_received(rcvd) - else: - super(ISOTPSession, self).on_packet_received(rcvd) + rcvd = cast(Optional[Packet], self.m.pop()) + if rcvd: + rcvd = self.process(rcvd) + if rcvd: + yield rcvd diff --git a/scapy/layers/dcerpc.py b/scapy/layers/dcerpc.py index 8caf6857fb3..e1901c30b1c 100644 --- a/scapy/layers/dcerpc.py +++ b/scapy/layers/dcerpc.py @@ -91,6 +91,11 @@ EPacketListField, ) +# Typing imports +from typing import ( + Optional, +) + # DCE/RPC Packet DCE_RPC_TYPE = { @@ -1895,12 +1900,11 @@ def _process_dcerpc_packet(self, pkt): pkt = self._parse_with_opnum(pkt, opnum, opts) return pkt - def on_packet_received(self, pkt): + def process(self, pkt: Packet) -> Optional[Packet]: if DceRpc5 in pkt: - return super(DceRpcSession, self).on_packet_received( - self._process_dcerpc_packet(pkt) - ) - return super(DceRpcSession, self).on_packet_received(pkt) + return self._process_dcerpc_packet(pkt) + else: + return pkt # --- TODO cleanup below diff --git a/scapy/layers/inet.py b/scapy/layers/inet.py index ad12ce82515..8741b3bdad6 100644 --- a/scapy/layers/inet.py +++ b/scapy/layers/inet.py @@ -527,7 +527,6 @@ def i2h(self, pkt, x): class IP(Packet, IPTools): - __slots__ = ["_defrag_pos"] name = "IP" fields_desc = [BitField("version", 4, 4), BitField("ihl", None, 4), @@ -625,38 +624,7 @@ def mysummary(self): def fragment(self, fragsize=1480): """Fragment IP datagrams""" - lastfragsz = fragsize - fragsize -= fragsize % 8 - lst = [] - fnb = 0 - fl = self - while fl.underlayer is not None: - fnb += 1 - fl = fl.underlayer - - for p in fl: - s = raw(p[fnb].payload) - if len(s) <= lastfragsz: - lst.append(p) - continue - - nb = (len(s) - lastfragsz + fragsize - 1) // fragsize + 1 - for i in range(nb): - q = p.copy() - del q[fnb].payload - del q[fnb].chksum - del q[fnb].len - if i != nb - 1: - q[fnb].flags |= 1 - fragend = (i + 1) * fragsize - else: - fragend = i * fragsize + lastfragsz - q[fnb].frag += i * fragsize // 8 - r = conf.raw_layer(load=s[i * fragsize:fragend]) - r.overload_fields = p[fnb].payload.overload_fields.copy() - q.add_payload(r) - lst.append(q) - return lst + return fragment(self, fragsize=fragsize) def in4_pseudoheader(proto, u, plen): @@ -1145,6 +1113,9 @@ def inet_register_l3(l2, l3): @conf.commands.register def fragment(pkt, fragsize=1480): """Fragment a big IP datagram""" + if fragsize < 8: + warning("fragsize cannot be lower than 8") + fragsize = max(fragsize, 8) lastfragsz = fragsize fragsize -= fragsize % 8 lst = [] @@ -1189,86 +1160,115 @@ def overlap_frag(p, overlap, fragsize=8, overlap_fragsize=None): return qfrag + fragment(p, fragsize) -def _defrag_list(lst, defrag, missfrag): - """Internal usage only. Part of the _defrag_logic""" - p = lst[0] - lastp = lst[-1] - if p.frag > 0 or lastp.flags.MF: # first or last fragment missing - missfrag.extend(lst) - return - p = p.copy() - if conf.padding_layer in p: - del p[conf.padding_layer].underlayer.payload - ip = p[IP] - if ip.len is None or ip.ihl is None: - c_len = len(ip.payload) - else: - c_len = ip.len - (ip.ihl << 2) - txt = conf.raw_layer() - for q in lst[1:]: - if c_len != q.frag << 3: # Wrong fragmentation offset - if c_len > q.frag << 3: - warning("Fragment overlap (%i > %i) %r || %r || %r" % (c_len, q.frag << 3, p, txt, q)) # noqa: E501 - missfrag.extend(lst) - break - if q[IP].len is None or q[IP].ihl is None: - c_len += len(q[IP].payload) +class BadFragments(ValueError): + def __init__(self, *args, **kwargs): + self.frags = kwargs.pop("frags", None) + super(BadFragments, self).__init__(*args, **kwargs) + + +def _defrag_iter_and_check_offsets(frags): + """ + Internal generator used in _defrag_ip_pkt + """ + offset = 0 + for pkt, o, length in frags: + if offset != o: + if offset > o: + op = ">" + else: + op = "<" + warning("Fragment overlap (%i %s %i) on %r" % (offset, op, o, pkt)) + raise BadFragments + offset += length + yield bytes(pkt[IP].payload) + + +def _defrag_ip_pkt(pkt, frags): + """ + Defragment a single IP packet. + + :param pkt: the new pkt + :param frags: a defaultdict(list) used for storage + :return: a tuple (fragmented, defragmented_value) + """ + ip = pkt[IP] + if pkt.frag != 0 or ip.flags.MF: + # fragmented ! + uid = (ip.id, ip.src, ip.dst, ip.proto) + if ip.len is None or ip.ihl is None: + fraglen = len(ip.payload) else: - c_len += q[IP].len - (q[IP].ihl << 2) - if conf.padding_layer in q: - del q[conf.padding_layer].underlayer.payload - txt.add_payload(q[IP].payload.copy()) - if q.time > p.time: - p.time = q.time - else: - ip.flags.MF = False - del ip.chksum - del ip.len - p = p / txt - p._defrag_pos = max(x._defrag_pos for x in lst) - defrag.append(p) + fraglen = ip.len - (ip.ihl << 2) + # (pkt, frag offset, frag len) + frags[uid].append((pkt, ip.frag << 3, fraglen)) + if not ip.flags.MF: # no more fragments = last fragment + curfrags = sorted(frags[uid], key=lambda x: x[1]) # sort by offset + try: + data = b"".join(_defrag_iter_and_check_offsets(curfrags)) + except ValueError: + # bad fragment + badfrags = frags[uid] + del frags[uid] + raise BadFragments(frags=badfrags) + # re-build initial packet without fragmentation + p = curfrags[0][0].copy() + pay_class = p[IP].payload.__class__ + p[IP].flags.MF = False + p[IP].remove_payload() + p[IP].len = None + p[IP].chksum = None + # append defragmented payload + p /= pay_class(data) + # cleanup + del frags[uid] + return True, p + return True, None + return False, pkt def _defrag_logic(plist, complete=False): - """Internal function used to defragment a list of packets. + """ + Internal function used to defragment a list of packets. It contains the logic behind the defrag() and defragment() functions """ - frags = defaultdict(lambda: []) + frags = defaultdict(list) final = [] - pos = 0 - for p in plist: - p._defrag_pos = pos - pos += 1 - if IP in p: - ip = p[IP] - if ip.frag != 0 or ip.flags.MF: - uniq = (ip.id, ip.src, ip.dst, ip.proto) - frags[uniq].append(p) - continue - final.append(p) - - defrag = [] - missfrag = [] - for lst in frags.values(): - lst.sort(key=lambda x: x.frag) - _defrag_list(lst, defrag, missfrag) - defrag2 = [] - for p in defrag: - q = p.__class__(raw(p)) - q._defrag_pos = p._defrag_pos - q.time = p.time - defrag2.append(q) + notfrag = [] + badfrag = [] + # Defrag + for i, pkt in enumerate(plist): + if IP not in pkt: + # no IP layer + if complete: + final.append(pkt) + continue + try: + fragmented, defragmented_value = _defrag_ip_pkt( + pkt, + frags, + ) + except BadFragments as ex: + if complete: + final.extend(ex.frags) + else: + badfrag.extend(ex.frags) + continue + if complete and defragmented_value: + final.append(defragmented_value) + elif defragmented_value: + if fragmented: + final.append(defragmented_value) + else: + notfrag.append(defragmented_value) + # Return if complete: - final.extend(defrag2) - final.extend(missfrag) - final.sort(key=lambda x: x._defrag_pos) if hasattr(plist, "listname"): name = "Defragmented %s" % plist.listname else: name = "Defragmented" return PacketList(final, name=name) else: - return PacketList(final), PacketList(defrag2), PacketList(missfrag) + return PacketList(notfrag), PacketList(final), PacketList(badfrag) @conf.commands.register @@ -1911,7 +1911,7 @@ def parse_args(self, ip, port, srcip=None, **kargs): self.src = self.l4.src self.sack = self.l4[TCP].ack self.rel_seq = None - self.rcvbuf = TCPSession(prn=self._transmit_packet, store=False) + self.rcvbuf = TCPSession() bpf = "host %s and host %s and port %i and port %i" % (self.src, self.dst, self.sport, @@ -1996,7 +1996,7 @@ def receive_data(self, pkt): # Answer with an Ack self.send(self.l4) # Process data - will be sent to the SuperSocket through this - self.rcvbuf.on_packet_received(pkt) + self._transmit_packet(self.rcvbuf.process(pkt)) @ATMT.ioevent(ESTABLISHED, name="tcp", as_supersocket="tcplink") def outgoing_data_received(self, fd): diff --git a/scapy/layers/netflow.py b/scapy/layers/netflow.py index 0ce79dd4a91..69cfc6b77a3 100644 --- a/scapy/layers/netflow.py +++ b/scapy/layers/netflow.py @@ -64,11 +64,16 @@ ) from scapy.packet import Packet, bind_layers, bind_bottom_up from scapy.plist import PacketList -from scapy.sessions import IPSession, DefaultSession +from scapy.sessions import IPSession from scapy.layers.inet import UDP from scapy.layers.inet6 import IP6Field +# Typing imports +from typing import ( + Optional, +) + class NetflowHeader(Packet): name = "Netflow Header" @@ -1596,25 +1601,21 @@ class NetflowSession(IPSession): See help(scapy.layers.netflow) for more infos. """ def __init__(self, *args, **kwargs): - IPSession.__init__(self, *args, **kwargs) self.definitions = {} self.definitions_opts = {} self.ignored = set() + super(NetflowSession, self).__init__(*args, **kwargs) - def _process_packet(self, pkt): + def process(self, pkt: Packet) -> Optional[Packet]: + pkt = super(NetflowSession, self).process(pkt) + if not pkt: + return _netflowv9_defragment_packet(pkt, self.definitions, self.definitions_opts, self.ignored) return pkt - def on_packet_received(self, pkt): - # First, defragment IP if necessary - pkt = self._ip_process_packet(pkt) - # Now handle NetflowV9 defragmentation - pkt = self._process_packet(pkt) - DefaultSession.on_packet_received(self, pkt) - class NetflowOptionsRecordScopeV9(NetflowRecordV9): name = "Netflow Options Template Record V9/10 - Scope" diff --git a/scapy/layers/tls/handshake.py b/scapy/layers/tls/handshake.py index eeffad947bb..9d283e3ea75 100644 --- a/scapy/layers/tls/handshake.py +++ b/scapy/layers/tls/handshake.py @@ -463,7 +463,7 @@ def tls_session_update(self, msg_str): # RFC 8701: GREASE of TLS will send unknown versions # here. We have to ignore them if ver in _tls_version: - self.tls_session.advertised_tls_version = ver + s.advertised_tls_version = ver break if isinstance(e, TLS_Ext_SignatureAlgorithms): s.advertised_sig_algs = e.sig_algs @@ -1329,7 +1329,14 @@ def tls_session_update(self, msg_str): self.tls_session.session_hash = ( Hash_MD5().digest(to_hash) + Hash_SHA().digest(to_hash) ) - self.tls_session.compute_ms_and_derive_keys() + if self.tls_session.pre_master_secret: + self.tls_session.compute_ms_and_derive_keys() + + if not self.tls_session.master_secret: + # There are still no master secret (we're just passive) + if self.tls_session.use_nss_master_secret_if_present(): + # we have a NSS file + self.tls_session.compute_ms_and_derive_keys() ############################################################################### diff --git a/scapy/layers/tls/keyexchange.py b/scapy/layers/tls/keyexchange.py index dc6546d9919..cd81a20adaf 100644 --- a/scapy/layers/tls/keyexchange.py +++ b/scapy/layers/tls/keyexchange.py @@ -747,7 +747,7 @@ def fill_missing(self): if s.client_kx_privkey and s.server_kx_pubkey: pms = s.client_kx_privkey.exchange(s.server_kx_pubkey) s.pre_master_secret = pms.lstrip(b"\x00") - if not s.extms or s.session_hash: + if not s.extms: # If extms is set (extended master secret), the key will # need the session hash to be computed. This is provided # by the TLSClientKeyExchange. Same in all occurrences @@ -781,7 +781,7 @@ def post_dissection(self, m): if s.server_kx_privkey and s.client_kx_pubkey: ZZ = s.server_kx_privkey.exchange(s.client_kx_pubkey) s.pre_master_secret = ZZ.lstrip(b"\x00") - if not s.extms or s.session_hash: + if not s.extms: s.compute_ms_and_derive_keys() def guess_payload_class(self, p): @@ -828,7 +828,7 @@ def fill_missing(self): if s.client_kx_privkey and s.server_kx_pubkey: s.pre_master_secret = pms - if not s.extms or s.session_hash: + if not s.extms: s.compute_ms_and_derive_keys() def post_build(self, pkt, pay): @@ -854,7 +854,7 @@ def post_dissection(self, m): if s.server_kx_privkey and s.client_kx_pubkey: ZZ = s.server_kx_privkey.exchange(ec.ECDH(), s.client_kx_pubkey) s.pre_master_secret = ZZ - if not s.extms or s.session_hash: + if not s.extms: s.compute_ms_and_derive_keys() @@ -918,7 +918,7 @@ def pre_dissect(self, m): warning(err) s.pre_master_secret = pms - if not s.extms or s.session_hash: + if not s.extms: s.compute_ms_and_derive_keys() return pms @@ -934,7 +934,7 @@ def post_build(self, pkt, pay): s = self.tls_session s.pre_master_secret = enc - if not s.extms or s.session_hash: + if not s.extms: s.compute_ms_and_derive_keys() if s.server_tmp_rsa_key is not None: diff --git a/scapy/layers/tls/record.py b/scapy/layers/tls/record.py index c8e55a34757..e6c59456913 100644 --- a/scapy/layers/tls/record.py +++ b/scapy/layers/tls/record.py @@ -40,14 +40,6 @@ if conf.crypto_valid_advanced: from scapy.layers.tls.crypto.cipher_aead import Cipher_CHACHA20_POLY1305 -# Util - - -def _tls_version_check(version, min): - """Returns if version >= min, or False if version == None""" - if version is None: - return False - return version >= min ############################################################################### # TLS Record Protocol # @@ -216,7 +208,7 @@ def addfield(self, pkt, s, val): # Add TLS13ClientHello in case of HelloRetryRequest # Add ChangeCipherSpec for middlebox compatibility if (isinstance(pkt, _GenericTLSSessionInheritance) and - _tls_version_check(pkt.tls_session.tls_version, 0x0304) and + pkt.tls_session.tls_version == 0x0304 and not isinstance(pkt.msg[0], TLS13ServerHello) and not isinstance(pkt.msg[0], TLS13ClientHello) and not isinstance(pkt.msg[0], TLSChangeCipherSpec)): @@ -336,8 +328,14 @@ def dispatch_hook(cls, _pkt=None, *args, **kargs): return SSLv2 # Not SSLv2: continuation return _TLSEncryptedContent + if plen >= 5: + # Check minimum length + msglen = struct.unpack('!H', _pkt[3:5])[0] + 5 + if plen < msglen: + # This is a fragment + return conf.padding_layer # Check TLS 1.3 - if s and _tls_version_check(s.tls_version, 0x0304): + if s and s.tls_version == 0x0304: _has_cipher = lambda x: ( x and not isinstance(x.cipher, Cipher_NULL) ) @@ -575,12 +573,24 @@ def do_dissect_payload(self, s): as the TLS session to be used would get lost. """ if s: + # Check minimum length + if len(s) < 5: + p = conf.raw_layer(s, _internal=1, _underlayer=self) + self.add_payload(p) + return + msglen = struct.unpack('!H', s[3:5])[0] + 5 + if len(s) < msglen: + # This is a fragment + self.add_payload(conf.padding_layer(s)) + return try: p = TLS(s, _internal=1, _underlayer=self, tls_session=self.tls_session) except KeyboardInterrupt: raise except Exception: + if conf.debug_dissector: + raise p = conf.raw_layer(s, _internal=1, _underlayer=self) self.add_payload(p) @@ -734,11 +744,11 @@ def post_build(self, pkt, pay): return hdr + efrag + pay def mysummary(self): - s = super(TLS, self).mysummary() + s, n = super(TLS, self).mysummary() if self.msg: s += " / " s += " / ".join(getattr(x, "_name", x.name) for x in self.msg) - return s + return s, n ############################################################################### # TLS ChangeCipherSpec # diff --git a/scapy/layers/tls/record_sslv2.py b/scapy/layers/tls/record_sslv2.py index abe5004610a..8d311faaadc 100644 --- a/scapy/layers/tls/record_sslv2.py +++ b/scapy/layers/tls/record_sslv2.py @@ -141,7 +141,7 @@ def pre_dissect(self, s): is_mac_ok = self._sslv2_mac_verify(cfrag + pad, mac) if not is_mac_ok: pkt_info = self.firstlayer().summary() - log_runtime.info("TLS: record integrity check failed [%s]", pkt_info) # noqa: E501 + log_runtime.info("SSLv2: record integrity check failed [%s]", pkt_info) # noqa: E501 reconstructed_body = mac + cfrag + pad return hdr + reconstructed_body + r diff --git a/scapy/layers/tls/record_tls13.py b/scapy/layers/tls/record_tls13.py index b505bc8e20f..ff8f0acec4d 100644 --- a/scapy/layers/tls/record_tls13.py +++ b/scapy/layers/tls/record_tls13.py @@ -15,7 +15,6 @@ import struct -from scapy.config import conf from scapy.error import log_runtime, warning from scapy.compat import raw, orb from scapy.fields import ByteEnumField, PacketField, XStrField @@ -125,7 +124,7 @@ def _tls_auth_decrypt(self, s): return e.args except AEADTagError as e: pkt_info = self.firstlayer().summary() - log_runtime.info("TLS: record integrity check failed [%s]", pkt_info) # noqa: E501 + log_runtime.info("TLS 1.3: record integrity check failed [%s]", pkt_info) # noqa: E501 return e.args def pre_dissect(self, s): @@ -172,15 +171,7 @@ def do_dissect_payload(self, s): Note that overloading .guess_payload_class() would not be enough, as the TLS session to be used would get lost. """ - if s: - try: - p = TLS(s, _internal=1, _underlayer=self, - tls_session=self.tls_session) - except KeyboardInterrupt: - raise - except Exception: - p = conf.raw_layer(s, _internal=1, _underlayer=self) - self.add_payload(p) + return TLS.do_dissect_payload(self, s) # Building methods @@ -223,3 +214,10 @@ def post_build(self, pkt, pay): self.tls_session.triggered_pwcs_commit = False return hdr + frag + pay + + def mysummary(self): + s, n = super(TLS13, self).mysummary() + if self.inner and self.inner.msg: + s += " / " + s += " / ".join(getattr(x, "_name", x.name) for x in self.inner.msg) + return s, n diff --git a/scapy/layers/tls/session.py b/scapy/layers/tls/session.py index 8121bb24cfa..d78562508e7 100644 --- a/scapy/layers/tls/session.py +++ b/scapy/layers/tls/session.py @@ -10,6 +10,7 @@ """ import binascii +import collections import socket import struct @@ -18,7 +19,7 @@ from scapy.error import log_runtime, warning from scapy.packet import Packet from scapy.pton_ntop import inet_pton -from scapy.sessions import DefaultSession +from scapy.sessions import TCPSession from scapy.utils import repr_hex, strxor from scapy.layers.inet import TCP from scapy.layers.tls.crypto.compression import Comp_NULL @@ -34,7 +35,8 @@ def load_nss_keys(filename): """ Parses a NSS Keys log and returns unpacked keys in a dictionary. """ - keys = {} + # http://udn.realityripple.com/docs/Mozilla/Projects/NSS/Key_Log_Format + keys = collections.defaultdict(dict) try: fd = open(filename) fd.close() @@ -65,11 +67,10 @@ def load_nss_keys(filename): # Warn that a duplicated entry was detected. The latest one # will be kept in the resulting dictionary. - if data[0] in keys: + if client_random in keys[data[0]]: warning("Duplicated entry for %s !", data[0]) - keys[data[0]] = {"ClientRandom": client_random, - "Secret": secret} + keys[data[0]][client_random] = secret return keys @@ -368,6 +369,9 @@ def __init__(self, self.dport = dport self.sid = sid + # Identify duplicate sessions + self.firsttcp = None + # Our TCP socket. None until we send (or receive) a packet. self.sock = None @@ -529,6 +533,21 @@ def __setattr__(self, name, val): self.pwcs.connection_end = val super(tlsSession, self).__setattr__(name, val) + # Get infos from underlayer + + def set_underlayer(self, _underlayer): + if isinstance(_underlayer, TCP): + tcp = _underlayer + self.sport = tcp.sport + self.dport = tcp.dport + try: + self.ipsrc = tcp.underlayer.src + self.ipdst = tcp.underlayer.dst + except AttributeError: + pass + if self.firsttcp is None: + self.firsttcp = tcp.seq + # Mirroring def mirror(self): @@ -541,15 +560,15 @@ def mirror(self): client and the server. In such a situation, it should be used every time the message being read comes from a different side than the one read right before, as the reading state becomes the writing state, and - vice versa. For instance you could do: + vice versa. For instance you could do:: - client_hello = open('client_hello.raw').read() - + client_hello = open('client_hello.raw').read() + - m1 = TLS(client_hello) - m2 = TLS(server_hello, tls_session=m1.tls_session.mirror()) - m3 = TLS(server_cert, tls_session=m2.tls_session) - m4 = TLS(client_keyexchange, tls_session=m3.tls_session.mirror()) + m1 = TLS(client_hello) + m2 = TLS(server_hello, tls_session=m1.tls_session.mirror()) + m3 = TLS(server_cert, tls_session=m2.tls_session) + m4 = TLS(client_keyexchange, tls_session=m3.tls_session.mirror()) """ self.ipdst, self.ipsrc = self.ipsrc, self.ipdst @@ -598,12 +617,16 @@ def compute_master_secret(self): if conf.debug_tls: log_runtime.debug("TLS: master secret: %s", repr_hex(ms)) - def compute_ms_and_derive_keys(self): + def use_nss_master_secret_if_present(self) -> bool: # Load the master secret from an NSS Key dictionary - if self.nss_keys and self.nss_keys.get("CLIENT_RANDOM", False) and \ - self.nss_keys["CLIENT_RANDOM"].get("Secret", False): - self.master_secret = self.nss_keys["CLIENT_RANDOM"]["Secret"] + if not self.nss_keys or "CLIENT_RANDOM" not in self.nss_keys: + return False + if self.client_random in self.nss_keys["CLIENT_RANDOM"]: + self.master_secret = self.nss_keys["CLIENT_RANDOM"][self.client_random] + return True + return False + def compute_ms_and_derive_keys(self): if not self.master_secret: self.compute_master_secret() @@ -903,13 +926,20 @@ def eq(self, other): return False - def __repr__(self): + def repr(self, _underlayer=None): sid = repr(self.sid) if len(sid) > 12: sid = sid[:11] + "..." + if _underlayer and _underlayer.dport != self.dport: + return "%s:%s > %s:%s" % (self.ipdst, str(self.dport), + self.ipsrc, str(self.sport)) return "%s:%s > %s:%s" % (self.ipsrc, str(self.sport), self.ipdst, str(self.dport)) + def __repr__(self): + return self.repr() + + ############################################################################### # Session singleton # ############################################################################### @@ -946,14 +976,8 @@ def __init__(self, _pkt="", post_transform=None, _internal=0, self.wcs_snap_init = self.tls_session.wcs.snapshot() if isinstance(_underlayer, TCP): - tcp = _underlayer - self.tls_session.sport = tcp.sport - self.tls_session.dport = tcp.dport - try: - self.tls_session.ipsrc = tcp.underlayer.src - self.tls_session.ipdst = tcp.underlayer.dst - except AttributeError: - pass + # Get information from _underlayer + self.tls_session.set_underlayer(_underlayer) # Load a NSS Key Log file if conf.tls_nss_filename is not None: @@ -1079,25 +1103,52 @@ def show2(self): s.rcs = rcs_snap s.wcs = wcs_snap - def mysummary(self): - return "TLS %s / %s" % (repr(self.tls_session), - getattr(self, "_name", self.name)) + def mysummary(self, first=True): + from scapy.layers.tls.record import TLS + from scapy.layers.tls.record_tls13 import TLS13 + if ( + self.underlayer and + isinstance(self.underlayer, _GenericTLSSessionInheritance) + ): + summary = getattr(self, "_name", self.name) + else: + _underlayer = None + if self.underlayer and isinstance(self.underlayer, TCP): + _underlayer = self.underlayer + summary = "TLS %s / %s" % ( + self.tls_session.repr(_underlayer=_underlayer), + getattr(self, "_name", self.name) + ) + return summary, [TLS, TLS13] @classmethod def tcp_reassemble(cls, data, metadata, session): - # Used with TLSSession + # Used with TCPSession from scapy.layers.tls.record import TLS from scapy.layers.tls.record_tls13 import TLS13 if cls in (TLS, TLS13): length = struct.unpack("!H", data[3:5])[0] + 5 - if len(data) == length: - return cls(data) - elif len(data) > length: - pkt = cls(data) - if hasattr(pkt.payload, "tcp_reassemble"): - return pkt.payload.tcp_reassemble(data[length:], metadata, session) - else: - return pkt + if len(data) >= length: + # get the underlayer as it is used to populate tls_session + underlayer = metadata["original"][TCP].copy() + underlayer.remove_payload() + # eventually get the tls_session now for TLS.dispatch_hook + tls_session = None + if conf.tls_session_enable: + s = tlsSession() + s.set_underlayer(underlayer) + tls_session = conf.tls_sessions.find(s) + if tls_session: + if tls_session.dport != underlayer.dport: + tls_session = tls_session.mirror() + if tls_session.firsttcp == underlayer.seq: + log_runtime.info( + "TLS: session %s is a duplicate of a previous " + "dissection. Discard it" % repr(tls_session) + ) + conf.tls_sessions.rem(tls_session, force=True) + tls_session = None + return cls(data, _underlayer=underlayer, tls_session=tls_session) else: return cls(data) @@ -1123,11 +1174,12 @@ def add(self, session): else: self.sessions[h] = [session] - def rem(self, session): - s = self.find(session) - if s: - log_runtime.info("TLS: previous session shall not be overwritten") - return + def rem(self, session, force=False): + if not force: + s = self.find(session) + if s: + log_runtime.info("TLS: previous session shall not be overwritten") + return h = session.hash() self.sessions[h].remove(session) @@ -1140,10 +1192,10 @@ def find(self, session): if h in self.sessions: for k in self.sessions[h]: if k.eq(session): - if conf.tls_verbose: + if conf.debug_tls: log_runtime.info("TLS: found session matching %s", k) return k - if conf.tls_verbose: + if conf.debug_tls: log_runtime.info("TLS: did not find session matching %s", session) return None @@ -1162,8 +1214,13 @@ def __repr__(self): return "\n".join(map(lambda x: fmt % x, res)) -class TLSSession(DefaultSession): +class TLSSession(TCPSession): def __init__(self, *args, **kwargs): + # XXX this doesn't bring any value. + warning( + "TLSSession is deprecated and will be removed in a future version. " + "Please use TCPSession instead with conf.tls_session_enable=True" + ) server_rsa_key = kwargs.pop("server_rsa_key", None) super(TLSSession, self).__init__(*args, **kwargs) self._old_conf_status = conf.tls_session_enable @@ -1176,10 +1233,5 @@ def toPacketList(self): return super(TLSSession, self).toPacketList() +# Instantiate the TLS sessions holder conf.tls_sessions = _tls_sessions() -conf.tls_session_enable = False -conf.tls_verbose = False -# Filename containing NSS Keys Log -conf.tls_nss_filename = None -# Dictionary containing parsed NSS Keys -conf.tls_nss_keys = None diff --git a/scapy/libs/extcap.py b/scapy/libs/extcap.py index b61912e057a..be0e6b05663 100644 --- a/scapy/libs/extcap.py +++ b/scapy/libs/extcap.py @@ -158,8 +158,8 @@ def __init__(self, *_: Any, **kwarg: Any) -> None: self.reader = PcapReader(self.fd) # type: ignore self.ins = self.reader # type: ignore - def recv(self, x: int = MTU) -> Packet: - return self.reader.recv(x) + def recv(self, x: int = MTU, **kwargs: Any) -> Packet: + return self.reader.recv(x, **kwargs) def close(self) -> None: self.proc.kill() diff --git a/scapy/packet.py b/scapy/packet.py index e35b1bfc45a..4074d12789f 100644 --- a/scapy/packet.py +++ b/scapy/packet.py @@ -88,6 +88,7 @@ class Packet( "packetfields", "original", "explicit", "raw_packet_cache", "raw_packet_cache_fields", "_pkt", "post_transforms", + "stop_dissection_after", # then payload, underlayer and parent "payload", "underlayer", "parent", "name", @@ -146,6 +147,7 @@ def __init__(self, _internal=0, # type: int _underlayer=None, # type: Optional[Packet] _parent=None, # type: Optional[Packet] + stop_dissection_after=None, # type: Optional[Type[Packet]] **fields # type: Any ): # type: (...) -> None @@ -174,6 +176,7 @@ def __init__(self, self.direction = None # type: Optional[int] self.sniffed_on = None # type: Optional[_GlobInterfaceType] self.comment = None # type: Optional[bytes] + self.stop_dissection_after = stop_dissection_after if _pkt: self.dissect(_pkt) if not _internal: @@ -1033,9 +1036,22 @@ def do_dissect_payload(self, s): :param str s: the raw layer """ if s: + if ( + self.stop_dissection_after and + isinstance(self, self.stop_dissection_after) + ): + # stop dissection here + p = conf.raw_layer(s, _internal=1, _underlayer=self) + self.add_payload(p) + return cls = self.guess_payload_class(s) try: - p = cls(s, _internal=1, _underlayer=self) + p = cls( + s, + stop_dissection_after=self.stop_dissection_after, + _internal=1, + _underlayer=self, + ) except KeyboardInterrupt: raise except Exception: diff --git a/scapy/sendrecv.py b/scapy/sendrecv.py index 85136ab18a5..a994d0399b3 100644 --- a/scapy/sendrecv.py +++ b/scapy/sendrecv.py @@ -1092,20 +1092,17 @@ def _run(self, iface=None, # type: Optional[_GlobInterfaceType] started_callback=None, # type: Optional[Callable[[], Any]] session=None, # type: Optional[_GlobSessionType] - session_kwargs={}, # type: Dict[str, Any] **karg # type: Any ): # type: (...) -> None self.running = True + self.count = 0 + lst = [] # Start main thread # instantiate session if not isinstance(session, DefaultSession): session = session or DefaultSession - session = session(prn=prn, store=store, - **session_kwargs) - else: - session.prn = prn - session.store = store + session = session() # sniff_sockets follows: {socket: label} sniff_sockets = {} # type: Dict[SuperSocket, _GlobInterfaceType] if opened_socket is not None: @@ -1238,8 +1235,28 @@ def stop_cb(): for s in sockets: if s is close_pipe: # type: ignore break + # The session object is passed the socket to call recv() on, + # and may perform additional processing (ip defrag, etc.) try: - p = s.recv() + packets = session.recv(s) + # A session can return multiple objects + for p in packets: + if lfilter and not lfilter(p): + continue + p.sniffed_on = sniff_sockets[s] + # post-processing + self.count += 1 + if store: + lst.append(p) + if prn: + result = prn(p) + if result is not None: + print(result) + # check + if (stop_filter and stop_filter(p)) or \ + (0 < count <= self.count): + self.continue_sniff = False + break except EOFError: # End of stream try: @@ -1262,18 +1279,6 @@ def stop_cb(): if conf.debug_dissector >= 2: raise continue - if p is None: - continue - if lfilter and not lfilter(p): - continue - p.sniffed_on = sniff_sockets[s] - # on_packet_received handles the prn/storage - session.on_packet_received(p) - # check - if (stop_filter and stop_filter(p)) or \ - (0 < count <= session.count): - self.continue_sniff = False - break # Removed dead sockets for s in dead_sockets: del sniff_sockets[s] @@ -1289,7 +1294,7 @@ def stop_cb(): s.close() elif close_pipe: close_pipe.close() - self.results = session.toPacketList() + self.results = PacketList(lst, "Sniffed") def start(self): # type: () -> None diff --git a/scapy/sessions.py b/scapy/sessions.py index 8317204060f..72856484564 100644 --- a/scapy/sessions.py +++ b/scapy/sessions.py @@ -10,105 +10,55 @@ import socket import struct -from scapy.compat import raw, orb +from scapy.compat import orb from scapy.config import conf from scapy.packet import NoPayload, Packet -from scapy.plist import PacketList from scapy.pton_ntop import inet_pton # Typing imports from typing import ( Any, - Callable, DefaultDict, Dict, + Iterator, List, Optional, Tuple, - cast + cast, + TYPE_CHECKING, ) +from scapy.compat import Self +if TYPE_CHECKING: + from scapy.supersocket import SuperSocket class DefaultSession(object): """Default session: no stream decoding""" - def __init__( - self, - prn=None, # type: Optional[Callable[[Packet], Any]] - store=False, # type: bool - supersession=None, # type: Optional[DefaultSession] - *args, # type: Any - **karg # type: Any - ): - # type: (...) -> None - self.__prn = prn - self.__store = store - self.lst = [] # type: List[Packet] - self.__count = 0 - self._supersession = supersession - if self._supersession: - self._supersession.prn = self.__prn - self._supersession.store = self.__store - self.__store = False - self.__prn = None - - @property - def store(self): - # type: () -> bool - return self.__store - - @store.setter - def store(self, val): - # type: (bool) -> None - if self._supersession: - self._supersession.store = val - else: - self.__store = val - - @property - def prn(self): - # type: () -> Optional[Callable[[Packet], Any]] - return self.__prn - - @prn.setter - def prn(self, f): - # type: (Optional[Any]) -> None - if self._supersession: - self._supersession.prn = f - else: - self.__prn = f + def __init__(self, supersession: Optional[Self] = None): + if supersession and not isinstance(supersession, DefaultSession): + supersession = supersession() + self.supersession = supersession - @property - def count(self): - # type: () -> int - if self._supersession: - return self._supersession.count - else: - return self.__count - - def toPacketList(self): - # type: () -> PacketList - if self._supersession: - return PacketList(self._supersession.lst, "Sniffed") - else: - return PacketList(self.lst, "Sniffed") + def process(self, pkt: Packet) -> Optional[Packet]: + """ + Called to pre-process the packet + """ + # Optionally handle supersession + if self.supersession: + return self.supersession.process(pkt) + return pkt - def on_packet_received(self, pkt): - # type: (Optional[Packet]) -> None - """DEV: entry point. Will be called by sniff() for each - received packet (that passes the filters). + def recv(self, sock: 'SuperSocket') -> Iterator[Packet]: """ + Will be called by sniff() to ask for a packet + """ + pkt = sock.recv() if not pkt: return - if not isinstance(pkt, Packet): - raise TypeError("Only provide a Packet.") - self.__count += 1 - if self.store: - self.lst.append(pkt) - if self.prn: - result = self.prn(pkt) - if result is not None: - print(result) + pkt = self.process(pkt) + if pkt: + yield pkt class IPSession(DefaultSession): @@ -123,39 +73,11 @@ def __init__(self, *args, **kwargs): DefaultSession.__init__(self, *args, **kwargs) self.fragments = defaultdict(list) # type: DefaultDict[Tuple[Any, ...], List[Packet]] # noqa: E501 - def _ip_process_packet(self, packet): - # type: (Packet) -> Optional[Packet] - from scapy.layers.inet import _defrag_list, IP + def process(self, packet: Packet) -> Optional[Packet]: + from scapy.layers.inet import IP, _defrag_ip_pkt if IP not in packet: return packet - ip = packet[IP] - packet._defrag_pos = 0 - if ip.frag != 0 or ip.flags.MF: - uniq = (ip.id, ip.src, ip.dst, ip.proto) - self.fragments[uniq].append(packet) - if not ip.flags.MF: # end of frag - try: - if self.fragments[uniq][0].frag == 0: - # Has first fragment (otherwise ignore) - defrag = [] # type: List[Packet] - _defrag_list(self.fragments[uniq], defrag, []) - defragmented_packet = defrag[0] - defragmented_packet = defragmented_packet.__class__( - raw(defragmented_packet) - ) - defragmented_packet.time = packet.time - return defragmented_packet - finally: - del self.fragments[uniq] - return None - else: - return packet - - def on_packet_received(self, pkt): - # type: (Optional[Packet]) -> None - if not pkt: - return None - super(IPSession, self).on_packet_received(self._ip_process_packet(pkt)) + return _defrag_ip_pkt(packet, self.fragments)[1] # type: ignore class StringBuffer(object): @@ -174,16 +96,26 @@ def __init__(self): # type: () -> None self.content = bytearray(b"") self.content_len = 0 + self.noff = 0 # negative offset self.incomplete = [] # type: List[Tuple[int, int]] - def append(self, data, seq): - # type: (bytes, int) -> None + def append(self, data: bytes, seq: Optional[int] = None) -> None: data_len = len(data) - seq = seq - 1 + if seq is None: + seq = self.content_len + seq = seq - 1 - self.noff + if seq < 0: + # Data is located before the start of the current buffer + # (e.g. the first fragment was missing) + self.content = bytearray(b"\x00" * (-seq)) + self.content + self.content_len += (-seq) + self.noff += seq + seq = 0 if seq + data_len > self.content_len: + # Data is located after the end of the current buffer self.content += b"\x00" * (seq - self.content_len + data_len) - # If data was missing, mark it. - self.incomplete.append((self.content_len, seq)) + # As data was missing, mark it. + # self.incomplete.append((self.content_len, seq)) self.content_len = seq + data_len assert len(self.content) == self.content_len # XXX removes empty space marker. @@ -192,6 +124,10 @@ def append(self, data, seq): # self.incomplete.remove([???]) memoryview(self.content)[seq:seq + data_len] = data + def shiftleft(self, i: int) -> None: + self.content = self.content[i:] + self.content_len -= i + def full(self): # type: () -> bool # Should only be true when all missing data was filled up, @@ -254,7 +190,7 @@ def __init__(self, app=False, *args, **kwargs): super(TCPSession, self).__init__(*args, **kwargs) self.app = app if app: - self.data = b"" + self.data = StringBuffer() self.metadata = {} # type: Dict[str, Any] self.session = {} # type: Dict[str, Any] else: @@ -266,6 +202,9 @@ def __init__(self, app=False, *args, **kwargs): self.tcp_sessions = defaultdict( dict ) # type: DefaultDict[bytes, Dict[str, Any]] + # Setup stopping dissection condition + from scapy.layers.inet import TCP + self.stop_dissection_after = TCP def _get_ident(self, pkt, session=False): # type: (Packet, bool) -> bytes @@ -283,28 +222,50 @@ def xor(x, y): # Uni-directional return src + dst + struct.pack("!HH", pkt.dport, pkt.sport) - def _process_packet(self, pkt): - # type: (Packet) -> Optional[Packet] + def _strip_padding(self, pkt: Packet) -> Optional[bytes]: + """Strip the packet of any padding, and return the padding. + """ + pad = pkt.getlayer(conf.padding_layer) + if pad is not None and pad.underlayer is not None: + # strip padding + del pad.underlayer.payload + return cast(bytes, pad.load) + return None + + def process(self, pkt: Packet) -> Optional[Packet]: """Process each packet: matches the TCP seq/ack numbers to follow the TCP streams, and orders the fragments. """ + _pkt = super(TCPSession, self).process(pkt) + if pkt is None: + return None + else: # Python 3.8 := would be nice + pkt = cast(Packet, _pkt) + packet = None # type: Optional[Packet] if self.app: # Special mode: Application layer. Use on top of TCP pay_class = pkt.__class__ - if not hasattr(pay_class, "tcp_reassemble"): - # Being on top of TCP, we have no way of knowing - # when a packet ends. - return pkt - self.data += bytes(pkt) - pkt = pay_class.tcp_reassemble( - self.data, + if hasattr(pay_class, "tcp_reassemble"): + tcp_reassemble = pay_class.tcp_reassemble + else: + # There is no tcp_reassemble. Just dissect the packet + tcp_reassemble = lambda data, *_: pay_class(data) + self.data.append(bytes(pkt)) + packet = tcp_reassemble( + bytes(self.data), self.metadata, self.session ) - if pkt: - self.data = b"" - self.metadata = {} - return pkt + if packet: + padding = self._strip_padding(packet) + if padding: + # There is remaining data for the next payload. + self.data.shiftleft(len(self.data) - len(padding)) + else: + # No padding (data) left. Clear + self.data.clear() + self.metadata.clear() + return packet return None from scapy.layers.inet import IP, TCP @@ -321,13 +282,12 @@ def _process_packet(self, pkt): tcp_session = self.tcp_sessions[self._get_ident(pkt, True)] # Let's guess which class is going to be used if "pay_class" not in metadata: - pay_class = pay.__class__ + pay_class = pkt[TCP].guess_payload_class(new_data) if hasattr(pay_class, "tcp_reassemble"): tcp_reassemble = pay_class.tcp_reassemble else: - # We can't know for sure when a packet ends. - # Ignore. - return pkt + # There is no tcp_reassemble. Just dissect the packet + tcp_reassemble = lambda data, *_: pay_class(data) metadata["pay_class"] = pay_class metadata["tcp_reassemble"] = tcp_reassemble else: @@ -352,9 +312,9 @@ def _process_packet(self, pkt): metadata["tcp_psh"] = True # XXX TODO: check that no empty space is missing in the buffer. # XXX Currently, if a TCP fragment was missing, we won't notice it. - packet = None # type: Optional[Packet] if data.full(): # Reassemble using all previous packets + metadata["original"] = pkt packet = tcp_reassemble( bytes(data), metadata, @@ -364,11 +324,19 @@ def _process_packet(self, pkt): if packet: if "seq" in metadata: pkt[TCP].seq = metadata["seq"] - # Clear buffer - data.clear() # Clear TCP reassembly metadata metadata.clear() - del self.tcp_frags[ident] + # Check for padding + padding = self._strip_padding(packet) + if 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) + else: + # No padding (data) left. Clear + data.clear() + del self.tcp_frags[ident] # Rebuild resulting packet pay.underlayer.remove_payload() if IP in pkt: @@ -379,18 +347,14 @@ def _process_packet(self, pkt): return pkt return None - def on_packet_received(self, pkt): - # type: (Optional[Packet]) -> None - """Hook to the Sessions API: entry point of the dissection. - This will defragment IP if necessary, then process to - TCP reassembly. + def recv(self, sock: 'SuperSocket') -> Iterator[Packet]: """ - if not pkt: - return None - # First, defragment IP if necessary - pkt = self._ip_process_packet(pkt) + Will be called by sniff() to ask for a packet + """ + pkt = sock.recv(stop_dissection_after=self.stop_dissection_after) if not pkt: return None # Now handle TCP reassembly - pkt = self._process_packet(pkt) - DefaultSession.on_packet_received(self, pkt) + pkt = self.process(pkt) + if pkt: + yield pkt diff --git a/scapy/supersocket.py b/scapy/supersocket.py index 8afec10b173..06f7019a78f 100644 --- a/scapy/supersocket.py +++ b/scapy/supersocket.py @@ -175,13 +175,13 @@ def recv_raw(self, x=MTU): """Returns a tuple containing (cls, pkt_data, time)""" return conf.raw_layer, self.ins.recv(x), None - def recv(self, x=MTU): - # type: (int) -> Optional[Packet] + def recv(self, x=MTU, **kwargs): + # type: (int, **Any) -> Optional[Packet] cls, val, ts = self.recv_raw(x) if not val or not cls: return None try: - pkt = cls(val) # type: Packet + pkt = cls(val, **kwargs) # type: Packet except KeyboardInterrupt: raise except Exception: @@ -321,8 +321,8 @@ def __init__(self, msg = "Your Linux Kernel does not support Auxiliary Data!" log_runtime.info(msg) - def recv(self, x=MTU): - # type: (int) -> Optional[Packet] + def recv(self, x=MTU, **kwargs): + # type: (int, **Any) -> Optional[Packet] data, sa_ll, ts = self._recv_raw(self.ins, x) if sa_ll[2] == socket.PACKET_OUTGOING: return None @@ -338,7 +338,7 @@ def recv(self, x=MTU): lvl = 3 try: - pkt = cls(data) + pkt = cls(data, **kwargs) except KeyboardInterrupt: raise except Exception: @@ -418,13 +418,13 @@ def __init__(self, sock, basecls=None): SimpleSocket.__init__(self, sock) self.basecls = basecls - def recv(self, x=MTU): - # type: (int) -> Optional[Packet] + def recv(self, x=MTU, **kwargs): + # type: (int, **Any) -> Optional[Packet] data = self.ins.recv(x, socket.MSG_PEEK) x = len(data) if x == 0: return None - pkt = self.basecls(data) # type: Packet + pkt = self.basecls(data, **kwargs) # type: Packet pad = pkt.getlayer(conf.padding_layer) if pad is not None and pad.underlayer is not None: del pad.underlayer.payload @@ -445,12 +445,12 @@ def __init__(self, sock, basecls=None): super(SSLStreamSocket, self).__init__(sock, basecls) # 65535, the default value of x is the maximum length of a TLS record - def recv(self, x=65535): - # type: (int) -> Optional[Packet] + def recv(self, x=65535, **kwargs): + # type: (int, **Any) -> Optional[Packet] pkt = None # type: Optional[Packet] if self._buf != b"": try: - pkt = self.basecls(self._buf) + pkt = self.basecls(self._buf, **kwargs) except Exception: # We assume that the exception is generated by a buffer underflow # noqa: E501 pass @@ -462,7 +462,7 @@ def recv(self, x=65535): self._buf += buf x = len(self._buf) - pkt = self.basecls(self._buf) + pkt = self.basecls(self._buf, **kwargs) if pkt is not None: pad = pkt.getlayer(conf.padding_layer) @@ -511,9 +511,9 @@ def __init__(self, self.reader = PcapReader(self.tcpdump_proc.stdout) self.ins = self.reader # type: ignore - def recv(self, x=MTU): - # type: (int) -> Optional[Packet] - return self.reader.recv(x) + def recv(self, x=MTU, **kwargs): + # type: (int, **Any) -> Optional[Packet] + return self.reader.recv(x, **kwargs) def close(self): # type: () -> None @@ -562,11 +562,11 @@ def select(sockets, remain=None): # type: (List[SuperSocket], Any) -> List[SuperSocket] return sockets - def recv(self, *args): - # type: (*Any) -> Optional[Packet] + def recv(self, x=None, **kwargs): + # type: (Optional[int], Any) -> Optional[Packet] try: pkt = next(self.iter) - return pkt.__class__(bytes(pkt)) + return pkt.__class__(bytes(pkt), **kwargs) except StopIteration: raise EOFError diff --git a/scapy/tools/UTscapy.py b/scapy/tools/UTscapy.py index da6ca39a2ef..ad734a7b04b 100644 --- a/scapy/tools/UTscapy.py +++ b/scapy/tools/UTscapy.py @@ -89,9 +89,12 @@ def scapy_path(fname): class no_debug_dissector: """Context object used to disable conf.debug_dissector""" + def __init__(self, reverse=False): + self.new_value = reverse + def __enter__(self): self.old_dbg = conf.debug_dissector - conf.debug_dissector = False + conf.debug_dissector = self.new_value def __exit__(self, exc_type, exc_value, traceback): conf.debug_dissector = self.old_dbg diff --git a/scapy/utils.py b/scapy/utils.py index 6c6fd2dfa7e..211378cc9eb 100644 --- a/scapy/utils.py +++ b/scapy/utils.py @@ -1411,15 +1411,15 @@ def __enter__(self): # type: () -> PcapReader return self - def read_packet(self, size=MTU): - # type: (int) -> Packet + def read_packet(self, size=MTU, **kwargs): + # type: (int, **Any) -> Packet rp = super(PcapReader, self)._read_packet(size=size) if rp is None: raise EOFError s, pkt_info = rp try: - p = self.LLcls(s) # type: Packet + p = self.LLcls(s, **kwargs) # type: Packet except KeyboardInterrupt: raise except Exception: @@ -1436,9 +1436,9 @@ def read_packet(self, size=MTU): p.wirelen = pkt_info.wirelen return p - def recv(self, size=MTU): # type: ignore - # type: (int) -> Packet - return self.read_packet(size=size) + def recv(self, size=MTU, **kwargs): # type: ignore + # type: (int, **Any) -> Packet + return self.read_packet(size=size, **kwargs) def __next__(self): # type: ignore # type: () -> Packet @@ -1720,7 +1720,7 @@ def _read_block_dsb(self, block, size): # TLS Key Log if secrets_type == 0x544c534b: - if getattr(conf, "tls_nss_keys", False) is False: + if getattr(conf, "tls_sessions", False) is False: warning("PcapNg: TLS Key Log available, but " "the TLS layer is not loaded! Scapy won't be able " "to decrypt the packets.") @@ -1739,8 +1739,8 @@ def _read_block_dsb(self, block, size): else: # Note: these attributes are only available when the TLS # layer is loaded. - conf.tls_nss_keys = keys # type: ignore - conf.tls_session_enable = True # type: ignore + conf.tls_nss_keys = keys + conf.tls_session_enable = True else: warning("PcapNg: Unknown DSB secrets type (0x%x)!", secrets_type) @@ -1757,15 +1757,15 @@ def __enter__(self): # type: () -> PcapNgReader return self - def read_packet(self, size=MTU): - # type: (int) -> Packet + def read_packet(self, size=MTU, **kwargs): + # type: (int, **Any) -> Packet rp = super(PcapNgReader, self)._read_packet(size=size) if rp is None: raise EOFError s, (linktype, tsresol, tshigh, tslow, wirelen, comment) = rp try: cls = conf.l2types.num2layer[linktype] # type: Type[Packet] - p = cls(s) # type: Packet + p = cls(s, **kwargs) # type: Packet except KeyboardInterrupt: raise except Exception: @@ -1781,9 +1781,8 @@ def read_packet(self, size=MTU): p.comment = comment return p - def recv(self, size=MTU): # type: ignore - # type: (int) -> Packet - return self.read_packet() + def recv(self, size: int = MTU, **kwargs: Any) -> 'Packet': # type: ignore + return self.read_packet(size=size, **kwargs) class GenericPcapWriter(object): @@ -2383,8 +2382,8 @@ def _convert_erf_timestamp(self, t): # The details of ERF Packet format can be see here: # https://www.endace.com/erf-extensible-record-format-types.pdf - def read_packet(self, size=MTU): - # type: (int) -> Packet + def read_packet(self, size=MTU, **kwargs): + # type: (int, **Any) -> Packet # General ERF Header have exactly 16 bytes hdr = self.f.read(16) @@ -2414,7 +2413,7 @@ def read_packet(self, size=MTU): pb = s[2:size] from scapy.layers.l2 import Ether try: - p = Ether(pb) # type: Packet + p = Ether(pb, **kwargs) # type: Packet except KeyboardInterrupt: raise except Exception: diff --git a/test/configs/linux.utsc b/test/configs/linux.utsc index f43e870c136..25fbb6bd1d6 100644 --- a/test/configs/linux.utsc +++ b/test/configs/linux.utsc @@ -2,6 +2,7 @@ "testfiles": [ "test/*.uts", "test/scapy/layers/*.uts", + "test/scapy/layers/tls/*.uts", "test/contrib/*.uts", "test/tools/*.uts", "test/contrib/automotive/*.uts", @@ -10,8 +11,7 @@ "test/contrib/automotive/gm/*.uts", "test/contrib/automotive/bmw/*.uts", "test/contrib/automotive/xcp/*.uts", - "test/contrib/automotive/autosar/*.uts", - "test/tls/tests_tls_netaccess.uts" + "test/contrib/automotive/autosar/*.uts" ], "remove_testfiles": [ "test/windows.uts", @@ -21,9 +21,7 @@ "onlyfailed": true, "preexec": { "test/contrib/*.uts": "load_contrib(\"%name%\")", - "test/cert.uts": "load_layer(\"tls\")", - "test/sslv2.uts": "load_layer(\"tls\")", - "test/tls*.uts": "load_layer(\"tls\")" + "test/scapy/layers/tls/*.uts": "load_layer(\"tls\")" }, "kw_ko": [ "osx", diff --git a/test/configs/windows.utsc b/test/configs/windows.utsc index c065cb604cb..5aaab483f48 100644 --- a/test/configs/windows.utsc +++ b/test/configs/windows.utsc @@ -2,6 +2,7 @@ "testfiles": [ "test\\*.uts", "test\\scapy\\layers\\*.uts", + "test\\scapy\\layers\\tls\\*.uts", "test\\tls\\tests_tls_netaccess.uts", "test\\contrib\\automotive\\obd\\*.uts", "test\\contrib\\automotive\\scanner\\*.uts", @@ -20,9 +21,7 @@ "onlyfailed": true, "preexec": { "test\\contrib\\*.uts": "load_contrib(\"%name%\")", - "test\\cert.uts": "load_layer(\"tls\")", - "test\\sslv2.uts": "load_layer(\"tls\")", - "test\\tls*.uts": "load_layer(\"tls\")" + "test\\scapy\\layers\\tls\\*.uts": "load_layer(\"tls\")" }, "kw_ko": [ "brotli", diff --git a/test/configs/windows2.utsc b/test/configs/windows2.utsc index e82fe0f8575..c231de57f85 100644 --- a/test/configs/windows2.utsc +++ b/test/configs/windows2.utsc @@ -2,12 +2,12 @@ "testfiles": [ "*.uts", "scapy\\layers\\*.uts", - "test\\contrib\\automotive\\obd\\*.uts", - "test\\contrib\\automotive\\gm\\*.uts", - "test\\contrib\\automotive\\bmw\\*.uts", - "test\\contrib\\automotive\\*.uts", - "test\\contrib\\automotive\\autosar\\*.uts", - "tls\\tests_tls_netaccess.uts", + "scapy\\layers\\tls\\*.uts", + "contrib\\automotive\\obd\\*.uts", + "contrib\\automotive\\gm\\*.uts", + "contrib\\automotive\\bmw\\*.uts", + "contrib\\automotive\\*.uts", + "contrib\\automotive\\autosar\\*.uts", "contrib\\*.uts" ], "remove_testfiles": [ @@ -18,9 +18,7 @@ "onlyfailed": true, "preexec": { "contrib\\*.uts": "load_contrib(\"%name%\")", - "cert.uts": "load_layer(\"tls\")", - "sslv2.uts": "load_layer(\"tls\")", - "tls*.uts": "load_layer(\"tls\")" + "scapy\\layers\\tls\\*.uts": "load_layer(\"tls\")" }, "format": "html", "kw_ko": [ diff --git a/test/contrib/automotive/ecu.uts b/test/contrib/automotive/ecu.uts index ede976bc0f5..4ced520357b 100644 --- a/test/contrib/automotive/ecu.uts +++ b/test/contrib/automotive/ecu.uts @@ -607,7 +607,7 @@ assert unanswered_packets[0].diagnosticSessionType == 4 = Analyze multiple UDS messages udsmsgs = sniff(offline=scapy_path("test/pcaps/ecu_trace.pcap.gz"), - session=ISOTPSession, session_kwargs={"use_ext_address":False, "basecls":UDS}, + session=ISOTPSession(use_ext_address=False, basecls=UDS), count=50, timeout=3) assert len(udsmsgs) == 50 @@ -638,7 +638,7 @@ assert len(ecu.log["TransferData"]) == 2 session = EcuSession() with PcapReader(scapy_path("test/pcaps/ecu_trace.pcap.gz")) as sock: - udsmsgs = sniff(session=ISOTPSession, session_kwargs={"supersession": session, "use_ext_address":False, "basecls":UDS}, count=50, opened_socket=sock, timeout=3) + udsmsgs = sniff(session=ISOTPSession(supersession=session, use_ext_address=False, basecls=UDS), count=50, opened_socket=sock, timeout=3) assert len(udsmsgs) == 50 @@ -668,12 +668,12 @@ session = EcuSession() conf.contribs['CAN']['swap-bytes'] = True with PcapReader(scapy_path("test/pcaps/gmlan_trace.pcap.gz")) as sock: - gmlanmsgs = sniff(session=ISOTPSession, session_kwargs={"supersession": session, "rx_id":[0x241, 0x641, 0x101], "basecls":GMLAN}, count=2, opened_socket=sock, timeout=3) + gmlanmsgs = sniff(session=ISOTPSession(supersession=session, rx_id=[0x241, 0x641, 0x101], basecls=GMLAN), count=2, opened_socket=sock, timeout=3) ecu = session.ecu print("Check 1 after change to diagnostic mode") assert len(ecu.supported_responses) == 1 assert ecu.state == EcuState(session=3) - gmlanmsgs = sniff(session=ISOTPSession, session_kwargs={"supersession": session, "rx_id":[0x241, 0x641, 0x101], "basecls":GMLAN}, count=8, opened_socket=sock) + gmlanmsgs = sniff(session=ISOTPSession(supersession=session, rx_id=[0x241, 0x641, 0x101], basecls=GMLAN), count=6, opened_socket=sock) ecu = session.ecu print("Check 2 after some more messages were read1") assert len(ecu.supported_responses) == 3 @@ -681,13 +681,13 @@ with PcapReader(scapy_path("test/pcaps/gmlan_trace.pcap.gz")) as sock: assert ecu.state.session == 3 print("assert 1") assert ecu.state.communication_control == 1 - gmlanmsgs = sniff(session=ISOTPSession, session_kwargs={"supersession": session, "rx_id":[0x241, 0x641, 0x101], "basecls":GMLAN}, count=10, opened_socket=sock) + gmlanmsgs = sniff(session=ISOTPSession(supersession=session, rx_id=[0x241, 0x641, 0x101], basecls=GMLAN), count=2, opened_socket=sock) ecu = session.ecu print("Check 3 after change to programming mode (bootloader)") assert len(ecu.supported_responses) == 4 assert ecu.state.session == 2 assert ecu.state.communication_control == 1 - gmlanmsgs = sniff(session=ISOTPSession, session_kwargs={"supersession": session, "rx_id":[0x241, 0x641, 0x101], "basecls":GMLAN}, count=16, opened_socket=sock) + gmlanmsgs = sniff(session=ISOTPSession(supersession=session, rx_id=[0x241, 0x641, 0x101], basecls=GMLAN), count=6, opened_socket=sock) ecu = session.ecu print("Check 4 after gaining security access") assert len(ecu.supported_responses) == 6 @@ -703,8 +703,8 @@ conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 4 conf.contribs['CAN']['swap-bytes'] = True conf.debug_dissector = True -gmlanmsgs = sniff(offline=scapy_path("test/pcaps/gmlan_trace.pcap.gz"), session=ISOTPSession, - session_kwargs={"supersession": session, "rx_id":[0x241, 0x641, 0x101], "basecls":GMLAN}, +gmlanmsgs = sniff(offline=scapy_path("test/pcaps/gmlan_trace.pcap.gz"), + session=ISOTPSession(supersession=session, rx_id=[0x241, 0x641, 0x101], basecls=GMLAN), count=200, timeout=6) ecu = session.ecu diff --git a/test/contrib/isotp_soft_socket.uts b/test/contrib/isotp_soft_socket.uts index de7f731b2e8..b2b7295805d 100644 --- a/test/contrib/isotp_soft_socket.uts +++ b/test/contrib/isotp_soft_socket.uts @@ -726,7 +726,7 @@ candump_fd = BytesIO(b''' vcan0 541 [8] 10 0A DE AD BE EF AA AA vcan0 241 [3] 30 00 00 vcan0 541 [5] 21 AA AA AA AA''') -pkts = sniff(opened_socket=CandumpReader(candump_fd), session=ISOTPSession, timeout=1, session_kwargs={"use_ext_address": False}) +pkts = sniff(opened_socket=CandumpReader(candump_fd), session=ISOTPSession(use_ext_address=False), timeout=1) assert len(pkts) == 6 if not len(pkts): @@ -809,13 +809,6 @@ isotp = pkts[0] assert isotp.data == dhex("") assert (isotp.rx_id == 0x241) -= ISOTPSession tests - -ses = ISOTPSession() -ses.on_packet_received(None) -ses.on_packet_received([None, None]) -assert True - = Receive a two-frame ISOTP message with TestSocket(CAN) as isocan, ISOTPSoftSocket(isocan, tx_id=0x641, rx_id=0x241) as s, TestSocket(CAN) as cans: diff --git a/test/pcaps/tls_tcp_frag_withnss.pcap.gz b/test/pcaps/tls_tcp_frag_withnss.pcap.gz new file mode 100644 index 00000000000..a9c19fcc770 Binary files /dev/null and b/test/pcaps/tls_tcp_frag_withnss.pcap.gz differ diff --git a/test/scapy/layers/inet.uts b/test/scapy/layers/inet.uts index e16c769e4bc..e767afcbad9 100644 --- a/test/scapy/layers/inet.uts +++ b/test/scapy/layers/inet.uts @@ -82,6 +82,16 @@ sniff(offline=tmp_file, session=IPSession, prn=callback) assert len(dissected_packets) == 1 assert raw(dissected_packets[0]) == raw(packet) += IPSession - contains non-IP packets + +pkts = fragment(IP(dst="10.0.0.5")/ICMP()/("X"*1500)) +pkts.insert(1, ARP()) +assert len(pkts) == 3 + +pkts = sniff(offline=pkts, session=IPSession) +assert len(pkts) == 2 +assert pkts[1].load == b"X" * 1500 + = StringBuffer buffer = StringBuffer() @@ -150,6 +160,25 @@ assert len(frags2) == 2 assert len(frags2[0]) == 20 + paylen - paylen % 8 assert len(frags2[1]) == 20 + 1 + paylen % 8 += fragment() with fragsize lower than 8 +paylen = 5 +fragsize = paylen +frags1 = fragment(IP() / ("X" * paylen), paylen) +assert len(frags1) == 1 +assert bytes(frags1[0].payload) == b"X" * paylen + +fragsize = paylen + 1 +frags2 = fragment(IP() / ("X" * paylen), fragsize) +assert len(frags2) == 1 +assert bytes(frags2[0].payload) == b"X" * paylen + +paylen = 16 +fragsize = 5 +frags3 = fragment(IP() / ("X" * paylen), fragsize) +assert len(frags3) == 2 +assert bytes(frags3[0].payload) == b"X" * 8 +assert bytes(frags3[1].payload) == b"X" * 8 + = defrag() nonfrag, unfrag, badfrag = defrag(frags) assert not nonfrag @@ -161,7 +190,7 @@ defrags = defragment(frags) * we should have one single packet assert len(defrags) == 1 * which should be the same as pkt reconstructed -assert defrags[0] == IP(raw(pkt)) +assert bytes(defrags[0]) == bytes(pkt) = defragment() uses timestamp of last fragment payloadlen, fragsize = 100, 8 @@ -191,10 +220,8 @@ b = base64.b64decode('bnmYJ63mREVTUwEACABFAAV0U8UgrDIR+kEEAgIECv0DxApz1F5olFRytj c = base64.b64decode('bnmYJ63mREVTUwEACABFAAFHU8UBWDIRHcMEAgIECv0DxDtlufeCT1zQktat4aEVA8MF0FO1sNbpEQtqfu5Al//OJISaRvtaArR/tLUj2CoZjS7uEnl7QpP/Ui/gR0YtyLurk9yTw7Vei0lSz4cnaOJqDiTGAKYwzVxjnoR1F3n8lplgQaOalVsHx9UAAQABAAADLAAEobkBA8epAAEAAQAAAywABKG5AQzHvwABAAEAAAMsAASnmYIMx5MAAQABAAADLAAEp5mCDcn9AAEAAQAAAqUABKeZhAvKFAABAAEAAAOEAAShuQIfyisAAQABAAADhAAEobkCKcpCAAEAAQAAA4QABKG5AjPKWQABAAEAAAOEAAShuQI9ynAAAQABAAADhAAEobkCC8nPAAEAAQAAA4QABKG5AgzJ5gABAAEAAAOEAASnmYQMAAApIAAAAAAAAAA=') d = base64.b64decode('////////REVTUwEACABFAABOawsAAIARtGoK/QExCv0D/wCJAIkAOry/3wsBEAABAAAAAAAAIEVKRkRFQkZFRUJGQUNBQ0FDQUNBQ0FDQUNBQ0FDQUFBAAAgAAEAABYP/WUAAB6N4XIAAB6E4XsAAACR/24AADyEw3sAABfu6BEAAAkx9s4AABXB6j4AAANe/KEAAAAT/+wAAB7z4QwAAEuXtGgAAB304gsAABTB6z4AAAdv+JAAACCu31EAADm+xkEAABR064sAABl85oMAACTw2w8AADrKxTUAABVk6psAABnF5joAABpA5b8AABjP5zAAAAqV9WoAAAUW+ukAACGS3m0AAAEP/vAAABoa5eUAABYP6fAAABX/6gAAABUq6tUAADXIyjcAABpy5Y0AABzb4yQAABqi5V0AAFXaqiUAAEmRtm4AACrL1TQAAESzu0wAAAzs8xMAAI7LcTQAABxN47IAAAbo+RcAABLr7RQAAB3Q4i8AAAck+NsAABbi6R0AAEdruJQAAJl+ZoEAABDH7zgAACOA3H8AAAB5/4YAABQk69sAAEo6tcUAABJU7asAADO/zEAAABGA7n8AAQ9L8LMAAD1DwrwAAB8F4PoAABbG6TkAACmC1n0AAlHErjkAABG97kIAAELBvT4AAEo0tcsAABtC5L0AAA9u8JEAACBU36sAAAAl/9oAABBO77EAAA9M8LMAAA8r8NQAAAp39YgAABB874MAAEDxvw4AAEgyt80AAGwsk9MAAB1O4rEAAAxL87QAADtmxJkAAATo+xcAAAM8/MMAABl55oYAACKh3V4AACGj3lwAAE5ssZMAAC1x0o4AAAO+/EEAABNy7I0AACYp2dYAACb+2QEAABB974IAABc36MgAAA1c8qMAAAf++AEAABDo7xcAACLq3RUAAA8L8PQAAAAV/+oAACNU3KsAABBv75AAABFI7rcAABuH5HgAABAe7+EAAB++4EEAACBl35oAAB7c4SMAADgJx/YAADeVyGoAACKN3XIAAA/C8D0AAASq+1UAAOHPHjAAABRI67cAAABw/48=') -old_debug_dissector = conf.debug_dissector -conf.debug_dissector = 0 -plist = PacketList([Ether(x) for x in [a, b, c, d]]) -conf.debug_dissector = old_debug_dissector +with no_debug_dissector(): + plist = PacketList([Ether(x) for x in [a, b, c, d]]) left, defragmented, errored = defrag(plist) assert len(left) == 1 @@ -465,6 +492,63 @@ pkt2.len = 0 pkt3 = IP(raw(pkt2)) assert pkt3.load == data += TCPSession: test tcp_reassemble with variable orders + +class CustomPacket(Packet): + fields_desc = [ + ByteField("len", 0), + StrLenField("a", 0, length_from=lambda pkt: pkt.len - 1), + ] + @classmethod + def tcp_reassemble(cls, data, metadata, session): + length = struct.unpack("!B", data[:1])[0] + if len(data) < length: + return None + return CustomPacket(data) + + +# above we have a CustomPacket that is X bytes long. +bind_layers(TCP, CustomPacket, sport=12345) + +with no_debug_dissector(reverse=True): + # incremental order + pkts = sniff(offline=[ + IP(dst="1.1.1.1", src="2.2.2.2")/TCP(sport=12345, dport=12, seq=1)/b"\x05a", + IP(dst="1.1.1.1", src="2.2.2.2")/TCP(sport=12345, dport=12, seq=3)/"b", + IP(dst="1.1.1.1", src="2.2.2.2")/TCP(sport=12345, dport=12, seq=4)/"c", + IP(dst="1.1.1.1", src="2.2.2.2")/TCP(sport=12345, dport=12, seq=5)/"d", + ], session=TCPSession) + assert pkts[0][CustomPacket].a == b"abcd" + # same with a pcapng + tmp_file = get_temp_file() + wrpcap(tmp_file, [ + IP(dst="1.1.1.1", src="2.2.2.2")/TCP(sport=12345, dport=12, seq=1)/b"\x05a", + IP(dst="1.1.1.1", src="2.2.2.2")/TCP(sport=12345, dport=12, seq=3)/"b", + IP(dst="1.1.1.1", src="2.2.2.2")/TCP(sport=12345, dport=12, seq=4)/"c", + IP(dst="1.1.1.1", src="2.2.2.2")/TCP(sport=12345, dport=12, seq=5)/"d", + ]) + pkts = sniff(offline=tmp_file, session=TCPSession) + assert pkts[0][CustomPacket].a == b"abcd" + # messed up order: fragments 2 and 3 arrive in the wrong order + pkts = sniff(offline=[ + IP(dst="1.1.1.1", src="2.2.2.2")/TCP(sport=12345, dport=12, seq=1)/b"\x05a", + IP(dst="1.1.1.1", src="2.2.2.2")/TCP(sport=12345, dport=12, seq=4)/"c", + IP(dst="1.1.1.1", src="2.2.2.2")/TCP(sport=12345, dport=12, seq=3)/"b", + IP(dst="1.1.1.1", src="2.2.2.2")/TCP(sport=12345, dport=12, seq=5)/"d", + ], session=TCPSession) + assert pkts[0][CustomPacket].a == b"abcd" + # messed up order: fragment 1 arrives not in first position + pkts = sniff(offline=[ + IP(dst="1.1.1.1", src="2.2.2.2")/TCP(sport=12345, dport=12, seq=6)/"e", + IP(dst="1.1.1.1", src="2.2.2.2")/TCP(sport=12345, dport=12, seq=4)/"c", + IP(dst="1.1.1.1", src="2.2.2.2")/TCP(sport=12345, dport=12, seq=3)/"b", + IP(dst="1.1.1.1", src="2.2.2.2")/TCP(sport=12345, dport=12, seq=5)/"d", + IP(dst="1.1.1.1", src="2.2.2.2")/TCP(sport=12345, dport=12, seq=1)/b"\x06a", + ], session=TCPSession) + assert pkts[0][CustomPacket].a == b"abcde" + +split_layers(TCP, CustomPacket, sport=12345) + = Layer binding @@ -500,9 +584,8 @@ value == 26908070 test.i2repr("", value) == '7:28:28.70' = IPv4 - UDP null checksum -conf.debug_dissector = False -IP(raw(IP()/UDP()/Raw(b"\xff\xff\x01\x6a")))[UDP].chksum == 0xFFFF -conf.debug_dissector = True +with no_debug_dissector(): + IP(raw(IP()/UDP()/Raw(b"\xff\xff\x01\x6a")))[UDP].chksum == 0xFFFF = IPv4 - (IP|UDP|TCP|ICMP)Error query = IP(dst="192.168.0.1", src="192.168.0.254", ttl=1)/UDP()/DNS() diff --git a/test/tls/__init__.py b/test/scapy/layers/tls/__init__.py similarity index 100% rename from test/tls/__init__.py rename to test/scapy/layers/tls/__init__.py diff --git a/test/cert.uts b/test/scapy/layers/tls/cert.uts similarity index 100% rename from test/cert.uts rename to test/scapy/layers/tls/cert.uts diff --git a/test/tls/example_client.py b/test/scapy/layers/tls/example_client.py old mode 100755 new mode 100644 similarity index 100% rename from test/tls/example_client.py rename to test/scapy/layers/tls/example_client.py diff --git a/test/tls/example_server.py b/test/scapy/layers/tls/example_server.py old mode 100755 new mode 100644 similarity index 100% rename from test/tls/example_server.py rename to test/scapy/layers/tls/example_server.py diff --git a/test/tls/pki/ca_cert.pem b/test/scapy/layers/tls/pki/ca_cert.pem similarity index 100% rename from test/tls/pki/ca_cert.pem rename to test/scapy/layers/tls/pki/ca_cert.pem diff --git a/test/tls/pki/ca_key.pem b/test/scapy/layers/tls/pki/ca_key.pem similarity index 100% rename from test/tls/pki/ca_key.pem rename to test/scapy/layers/tls/pki/ca_key.pem diff --git a/test/tls/pki/cli_cert.pem b/test/scapy/layers/tls/pki/cli_cert.pem similarity index 100% rename from test/tls/pki/cli_cert.pem rename to test/scapy/layers/tls/pki/cli_cert.pem diff --git a/test/tls/pki/cli_key.pem b/test/scapy/layers/tls/pki/cli_key.pem similarity index 100% rename from test/tls/pki/cli_key.pem rename to test/scapy/layers/tls/pki/cli_key.pem diff --git a/test/tls/pki/srv_cert.pem b/test/scapy/layers/tls/pki/srv_cert.pem similarity index 100% rename from test/tls/pki/srv_cert.pem rename to test/scapy/layers/tls/pki/srv_cert.pem diff --git a/test/tls/pki/srv_key.pem b/test/scapy/layers/tls/pki/srv_key.pem similarity index 100% rename from test/tls/pki/srv_key.pem rename to test/scapy/layers/tls/pki/srv_key.pem diff --git a/test/sslv2.uts b/test/scapy/layers/tls/sslv2.uts similarity index 99% rename from test/sslv2.uts rename to test/scapy/layers/tls/sslv2.uts index bde0a9e0d96..aeab19ba0aa 100644 --- a/test/sslv2.uts +++ b/test/scapy/layers/tls/sslv2.uts @@ -85,7 +85,7 @@ mk_enc.decryptedkey is None = Reading SSLv2 session - Importing server compromised key import os -filename = scapy_path("/test/tls/pki/srv_key.pem") +filename = scapy_path("/test/scapy/layers/tls/pki/srv_key.pem") rsa_key = PrivKeyRSA(filename) t.tls_session.server_rsa_key = rsa_key @@ -278,5 +278,5 @@ s.wcs.cipher.iv == b'\x01'*8 ############################ Automaton behaviour ############################## ############################################################################### -# see test/tls/tests_tls_netaccess.uts +# see scapy/layers/tls/clientserver.uts diff --git a/test/tls.uts b/test/scapy/layers/tls/tls.uts similarity index 96% rename from test/tls.uts rename to test/scapy/layers/tls/tls.uts index 72bca4b8084..0d51bb00c1b 100644 --- a/test/tls.uts +++ b/test/scapy/layers/tls/tls.uts @@ -1188,7 +1188,7 @@ load_layer("tls") from scapy.layers.tls.cert import PrivKeyRSA from scapy.layers.tls.record import TLSApplicationData import os -filename = scapy_path("/test/tls/pki/srv_key.pem") +filename = scapy_path("/test/scapy/layers/tls/pki/srv_key.pem") key = PrivKeyRSA(filename) ch = b'\x16\x03\x01\x005\x01\x00\x001\x03\x01X\xac\x0e\x8c\xe46\xe9\xedo\xda\x085$M\xae$\x90\xd9\xa93\xb7(\x13J\xf9\xc5?\xef\xf4\x96\xa1\xfa\x00\x00\x04\x00/\x00\xff\x01\x00\x00\x04\x00#\x00\x00' sh = b'\x16\x03\x01\x005\x02\x00\x001\x03\x01\x88\xac\xd4\xaf\x93~\xb5\x1b8c\xe7)\xa6\x9b\xa9\xed\xf3\xf3*\xdb\x00\x8bB\xf6\n\xcbz\x8eP\x83`G\x00\x00/\x00\x00\t\xff\x01\x00\x01\x00\x00#\x00\x00\x16\x03\x01\x03\xac\x0b\x00\x03\xa8\x00\x03\xa5\x00\x03\xa20\x82\x03\x9e0\x82\x02\x86\xa0\x03\x02\x01\x02\x02\t\x00\xfe\x04W\r\xc7\'\xe9\xf60\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0b\x05\x000T1\x0b0\t\x06\x03U\x04\x06\x13\x02MN1\x140\x12\x06\x03U\x04\x07\x0c\x0bUlaanbaatar1\x170\x15\x06\x03U\x04\x0b\x0c\x0eScapy Test PKI1\x160\x14\x06\x03U\x04\x03\x0c\rScapy Test CA0\x1e\x17\r160916102811Z\x17\r260915102811Z0X1\x0b0\t\x06\x03U\x04\x06\x13\x02MN1\x140\x12\x06\x03U\x04\x07\x0c\x0bUlaanbaatar1\x170\x15\x06\x03U\x04\x0b\x0c\x0eScapy Test PKI1\x1a0\x18\x06\x03U\x04\x03\x0c\x11Scapy Test Server0\x82\x01"0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x000\x82\x01\n\x02\x82\x01\x01\x00\xcc\xf1\xf1\x9b`-`\xae\xf2\x98\r\')\xd9\xc0\tYL\x0fJ0\xa8R\xdf\xe5\xb1!\x9fO\xc3=V\x93\xdd_\xc6\xf7\xb3\xf6U\x8b\xe7\x92\xe2\xde\xf2\x85I\xb4\xa1,\xf4\xfdv\xa8g\xca\x04 `\x11\x18\xa6\xf2\xa9\xb6\xa6\x1d\xd9\xaa\xe5\xd9\xdb\xaf\xe6\xafUW\x9f\xffR\x89e\xe6\x80b\x80!\x94\xbc\xcf\x81\x1b\xcbg\xc2\x9d\xb5\x05w\x04\xa6\xc7\x88\x18\x80xh\x956\xde\x97\x1b\xb6a\x87B\x1au\x98E\x82\xeb>2\x11\xc8\x9b\x86B9\x8dM\x12\xb7X\x1b\x19\xf3\x9d+\xa1\x98\x82\xca\xd7;$\xfb\t9\xb0\xbc\xc2\x95\xcf\x82)u\x16)?B \x17+M@\x8cVl\xad\xba\x0f4\x85\xb1\x7f@yqx\xb7\xa5\x04\xbb\x94\xf7\xb5A\x95\xee|\xeb\x8d\x0cyhY\xef\xcb\xb3\xfa>x\x1e\xeegLz\xdd\xe0\x99\xef\xda\xe7\xef\xb2\t]\xbe\x80 !\x05\x83,D\xdb]*v)\xa5\xb0#\x88t\x07T"\xd6)z\x92\xf5o-\x9e\xe7\xf8&+\x9cXe\x02\x03\x01\x00\x01\xa3o0m0\t\x06\x03U\x1d\x13\x04\x020\x000\x0b\x06\x03U\x1d\x0f\x04\x04\x03\x02\x05\xe00\x1d\x06\x03U\x1d\x0e\x04\x16\x04\x14\xa1+ p\xd2k\x80\xe5e\xbc\xeb\x03\x0f\x88\x9ft\xad\xdd\xf6\x130\x1f\x06\x03U\x1d#\x04\x180\x16\x80\x14fS\x94\xf4\x15\xd1\xbdgh\xb0Q725\xe1\xa4\xaa\xde\x07|0\x13\x06\x03U\x1d%\x04\x0c0\n\x06\x08+\x06\x01\x05\x05\x07\x03\x010\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00\x81\x88\x92sk\x93\xe7\x95\xd6\xddA\xee\x8e\x1e\xbd\xa3HX\xa7A5?{}\xd07\x98\x0e\xb8,\x94w\xc8Q6@\xadY\t(\xc8V\xd6\xea[\xac\xb4\xd8?h\xb7f\xca\xe1V7\xa9\x00e\xeaQ\xc9\xec\xb2iI]\xf9\xe3\xc0\xedaT\xc9\x12\x9f\xc6\xb0\nsU\xe8U5`\xef\x1c6\xf0\xda\xd1\x90wV\x04\xb8\xab8\xee\xf7\t\xc5\xa5\x98\x90#\xea\x1f\xdb\x15\x7f2(\x81\xab\x9b\x85\x02K\x95\xe77Q{\x1bH.\xfb>R\xa3\r\xb4F\xa9\x92:\x1c\x1f\xd7\n\x1eXJ\xfa.Q\x8f)\xc6\x1e\xb8\x0e1\x0es\xf1\'\x88\x17\xca\xc8i\x0c\xfa\x83\xcd\xb3y\x0e\x14\xb0\xb8\x9b/:-\t\xe3\xfc\x06\xf0:n\xfd6;+\x1a\t*\xe8\xab_\x8c@\xe4\x81\xb2\xbc\xf7\x83g\x11nN\x93\xea"\xaf\xff\xa3\x9awWv\xd0\x0b8\xac\xf8\x8a\x945\x8e\xd7\xd4a\xcc\x01\xff$\xb4\x8fa#\xba\x88\xd7Y\xe4\xe9\xba*N\xb5\x15\x0f\x9c\xd0\xea\x06\x91\xd9\xde\xab\x16\x03\x01\x00\x04\x0e\x00\x00\x00' @@ -1206,13 +1206,18 @@ assert isinstance(t.msg[0], TLSApplicationData) assert t.msg[0].data == b"" t.getlayer(TLS, 2).msg[0].data == b"To boldly go where no man has gone before...\n" -= Auto provide the session += Auto-provide the session: use TCPSession with conf.tls_session_enable conf.debug_dissector = 2 + +conf.tls_session_enable = True +conf.tls_sessions.server_rsa_key = key + client = "192.168.0.1" server = "1.2.3.4" -bc = Ether()/IP(src=client, dst=server)/TCP(sport=51478, dport=443, seq=1) -bs = Ether()/IP(src=server, dst=client)/TCP(sport=443, dport=51478, seq=1) +bc = Ether()/IP(src=client, dst=server)/TCP(sport=51478, dport=443, seq=RandShort()) +bs = Ether()/IP(src=server, dst=client)/TCP(sport=443, dport=51478, seq=RandShort()) + pcap = [ bc/ch, bs/sh, @@ -1220,11 +1225,12 @@ pcap = [ bs/fin, bc/data ] -res = sniff(offline=pcap, session=TLSSession(server_rsa_key=key)) +res = sniff(offline=pcap, session=TCPSession) res[4].show() assert res[4].getlayer(TLS, 2).msg[0].data == b"To boldly go where no man has gone before...\n" +conf.tls_session_enable = False ############################################################################### ############################## Building packets ############################### @@ -1356,7 +1362,7 @@ test_tls_without_cryptography() with no_debug_dissector(): pkt = Ether(hex_bytes('00155dfb587a00155dfb58430800450005dc54d3400070065564400410d40a00000d01bb044e8b86744e16063ac45010faf06ba9000016030317c30200005503035cb336a067d53a5d2cedbdfec666ac740afbd0637ddd13eddeab768c3c63abee20981a0000d245f1c905b329323ad67127cd4b907a49f775c331d0794149aca7cdc02800000d0005000000170000ff010001000b000ec6000ec300090530820901308206e9a00302010202132000036e72aded906765595fae000000036e72300d06092a864886f70d01010b050030818b310b30090603550406130255533113')) - assert TLSServerHello in pkt + assert conf.padding_layer in pkt ############################################################################### ########################### TLS Misc tests #################################### @@ -1484,7 +1490,7 @@ assert raw(p) == a with no_debug_dissector(): p = Ether(b'RU\x10\x00\x02\x02RT\x00\x124V\x08\x00E\x00\x05\xc8\r\xd8\x00\x00@\x06\x96\x9d\x9c&\xce\x12\xc0\xa8\xa5\xd9\x01\xbb\xc0\x1f\x00w$\x02\x03\xbe\xc5#P\x10#(\x0b\x9e\x00\x00\x16\x03\x03\x0e4\x02\x00\x00M\x03\x03^\xfa\xb5~\x88\xdf\xdc#}\'\xa0\xff\xa2\xe2\xb5\xec\x0e\x93\xa8\xe0\xde\x01[\x13[F\x151 x\xc6\xcc `)\x00\x00\x8aZ\x90l\xda\x0b\xe1\xec[i\x13\xa7\x8e\xb9a\x98"\x8a7L\x9d\x90\xe0\x01\x06c$9\xc0\'\x00\x00\x05\xff\x01\x00\x01\x00\x0b\x00\x0c\x8e\x00\x0c\x8b\x00\x06n0\x82\x06j0\x82\x05R\xa0\x03\x02\x01\x02\x02\x10EY\xe8\x1c\x1e\x9a\xe0?X\xaa\xc3\xbc\xcd`jh0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0b\x05\x000\x81\x8f1\x0b0\t\x06\x03U\x04\x06\x13\x02GB1\x1b0\x19\x06\x03U\x04\x08\x13\x12Greater Manchester1\x100\x0e\x06\x03U\x04\x07\x13\x07Salford1\x180\x16\x06\x03U\x04\n\x13\x0fSectigo Limited1705\x06\x03U\x04\x03\x13.Sectigo RSA Domain Validation Secure Server CA0\x1e\x17\r190309000000Z\x17\r210308235959Z0W1!0\x1f\x06\x03U\x04\x0b\x13\x18Domain Control Validated1\x1d0\x1b\x06\x03U\x04\x0b\x13\x14PositiveSSL Wildcard1\x130\x11\x06\x03U\x04\x03\x0c\n*.mql5.net0\x82\x01"0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x000\x82\x01\n\x02\x82\x01\x01\x00\xcb\xbcn=\xbaGd\xe1XB\x07\xc9\xb1\xc8/\x86\xaa4Z\xbdNk\xfb\xffR\x8f\xe4\x1c^\x91m8\xb9^\x97\xa5\xd3N\xfb\x80\x92\x8ap\xda\x15\x9f\xee\xe7\xb3\xc8?\xb0>~\xaa\x07\x91\xb1\x99q\xe2\xe5\xc8\x9b\x1d5\xa0\x96,\x98\xdaW\x93\x95\x8e%\xe8\xd4L\xeb\xcbSg\x15"\xba\xb7\xc7\x1f\xe9\xd6\x1a\xe6E\x1d\xc8\x1e%\xd36\xe0/r\xd1\xce1C\xce\x91&\xa1\x08*R\xbf\x8cu\xb0\xda\x0e\x1e2\xd66\x1df&3\x9b\x03\x0b\xcam:\xf7\x12\xd9ud(\xae\xdc\xbci\x85\xbd\xcf\xeb{\x15:\xbd\x0e\x11\x1bi\xd8\xff]y~E\x15\x95\xee\xe9\xea\xc6Cr~\xaa\x07\x91\xb1\x99q\xe2\xe5\xc8\x9b\x1d5\xa0\x96,\x98\xdaW\x93\x95\x8e%\xe8\xd4L\xeb\xcbSg\x15"\xba\xb7\xc7\x1f\xe9\xd6\x1a\xe6E\x1d\xc8\x1e%\xd36\xe0/r\xd1\xce1C\xce\x91&\xa1\x08*R\xbf\x8cu\xb0\xda\x0e\x1e2\xd66\x1df&3\x9b\x03\x0b\xcam:\xf7\x12\xd9ud(\xae\xdc\xbci\x85\xbd\xcf\xeb{\x15:\xbd\x0e\x11\x1bi\xd8\xff]y~E\x15\x95\xee\xe9\xea\xc6Cr~\xaa\x07\x91\xb1\x99q\xe2\xe5\xc8\x9b\x1d5\xa0\x96,\x98\xdaW\x93\x95\x8e%\xe8\xd4L\xeb\xcbSg\x15"\xba\xb7\xc7\x1f\xe9\xd6\x1a\xe6E\x1d\xc8\x1e%\xd36\xe0/r\xd1\xce1C\xce\x91&\xa1\x08*R\xbf\x8cu\xb0\xda\x0e\x1e2\xd66\x1df&3\x9b\x03\x0b\xcam:\xf7\x12\xd9ud(\xae\xdc\xbci\x85\xbd\xcf\xeb{\x15:\xbd\x0e\x11\x1bi\xd8\xff]y~E\x15\x95\xee\xe9\xea\xc6Cr.Q2MzGY[k@" in packets[13].msg[0].data conf = bck_conf + += pcap file & external TLS Key Log file with TCPSession (without extms) +* GH3722 + +# Write SSLKEYLOGFILE +temp_sslkeylog = get_temp_file() +with open(temp_sslkeylog, "w") as fd: + fd.write("CLIENT_RANDOM 09F91DA01B1FEB50B691C932959111E5E1D676437F7A42DE47EA881F6295D4E7 EE119869B732F0F9561FFDD95E50A2ACBF268EE0C7C33B409E68C1972E0B280944F7345E845E82F909CCFEB61C456E1F\n") + +bck_conf = conf +conf.tls_session_enable = True +conf.tls_nss_filename = temp_sslkeylog + +packets = sniff(offline=scapy_path("test/pcaps/tls_tcp_frag_withnss.pcap.gz"), session=TCPSession) +packets.show() + +assert packets[8].getlayer(TLS, 3).msg[0].msgtype == 20 +assert packets[8].getlayer(TLS, 3).msg[0].vdata == b'\n\xd4`\xf0\xd9X\x02\x10Z\x81\xf4l' +assert packets[10].getlayer(TLS, 3).msg[0].msgtype == 20 +assert packets[10].getlayer(TLS, 3).msg[0].vdata == b'\xa6>f\xd8\xacf\x99| \xbd<\xa1' +assert packets[11].msg[0].data == b'GET /uuid HTTP/1.1\r\nUser-Agent: Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.22000.832\r\nHost: httpbin.org\r\nConnection: Keep-Alive\r\n\r\n' +assert packets[13].msg[0].data == b'HTTP/1.1 200 OK\r\nDate: Sat, 20 Aug 2022 22:32:24 GMT\r\nContent-Type: application/json\r\nContent-Length: 53\r\nConnection: keep-alive\r\nServer: gunicorn/19.9.0\r\nAccess-Control-Allow-Origin: *\r\nAccess-Control-Allow-Credentials: true\r\n\r\n{\n "uuid": "5bad226d-504a-4416-a11a-8a5f8edbdbbd"\n}\n' + +# Test summary() +assert packets[6].summary() == 'Ether / IP / TCP / TLS 52.87.105.151:443 > 10.211.55.3:51933 / TLS / TLS Handshake - Certificate / TLS / TLS Handshake - Server Key Exchange / TLS / TLS Handshake - Server Hello Done' +assert packets[8].summary() == 'Ether / IP / TCP / TLS 10.211.55.3:51933 > 52.87.105.151:443 / TLS / TLS Handshake - Client Key Exchange / TLS / TLS ChangeCipherSpec / TLS / TLS Handshake - Finished' +conf = bck_conf diff --git a/test/tls13.uts b/test/scapy/layers/tls/tls13.uts similarity index 99% rename from test/tls13.uts rename to test/scapy/layers/tls/tls13.uts index c046048ed1c..12d58560874 100644 --- a/test/tls13.uts +++ b/test/scapy/layers/tls/tls13.uts @@ -1214,6 +1214,8 @@ assert len(ch.client_shares[0].key_exchange) == 97 = Parse TLS 1.3 Client Hello with non-rfc 5077 ticket +from scapy.layers.tls.keyexchange_tls13 import TLS_Ext_PreSharedKey_CH + ch = TLS(b'\x16\x03\x01\x01\x1a\x01\x00\x01\x16\x03\x03\xec\x9c>\xb2\x9e|B\x05\x17f\x86\xc8\x18\x0421\x87\x87\x12\xf6\xec\xa2J\x95\x84[\xf8\xab\xe9gK> \xc6%\xff&wn)\xb2\xf5\xe8_x\x96\xe9\nEsK\xda\x86o\x82f\xa5\xbadk\xf4Ar~}\x00\x08\x13\x02\x13\x03\x13\x01\x00\xff\x01\x00\x00\xc5\x00\x0b\x00\x04\x03\x00\x01\x02\x00\n\x00\x16\x00\x14\x00\x1d\x00\x17\x00\x1e\x00\x19\x00\x18\x01\x00\x01\x01\x01\x02\x01\x03\x01\x04\x00#\x00\x00\x00\x16\x00\x00\x00\x17\x00\x00\x00\r\x00\x1e\x00\x1c\x04\x03\x05\x03\x06\x03\x08\x07\x08\x08\x08\t\x08\n\x08\x0b\x08\x04\x08\x05\x08\x06\x04\x01\x05\x01\x06\x01\x00+\x00\x03\x02\x03\x04\x00-\x00\x02\x01\x01\x003\x00&\x00$\x00\x1d\x00 l\x19\xe1f1 )6\xbf\x91\x9e\xab\xd2\x06\x16\x0b|\x88\xf7,\xf1\x88\x99Z\xb6\xb3\x93\xe4\x08z\x8a\t\x00)\x00:\x00\x15\x00\x0fClient_identity\x00\x00\x00\x00\x00! m\xf3^\xc1l\xac5\xf2\xe3=\xeb\xe3\x81\xd3\xb3\xdd\xbd\xbd\x01\xc9\xdd\x01i\x8c1\xa0ye\xcd\x04\x9e\x9c') assert isinstance(ch.msg[0].ext[9], TLS_Ext_PreSharedKey_CH) diff --git a/test/tls/tests_tls_netaccess.uts b/test/scapy/layers/tls/tlsclientserver.uts similarity index 95% rename from test/tls/tests_tls_netaccess.uts rename to test/scapy/layers/tls/tlsclientserver.uts index 6b6324a8396..f0a86c0e3c5 100644 --- a/test/tls/tests_tls_netaccess.uts +++ b/test/scapy/layers/tls/tlsclientserver.uts @@ -67,8 +67,8 @@ def run_tls_test_server(expected_data, q, curve=None, cookie=False, client_auth= print("Server started !") with captured_output() as (out, err): # Prepare automaton - mycert = scapy_path("/test/tls/pki/srv_cert.pem") - mykey = scapy_path("/test/tls/pki/srv_key.pem") + mycert = scapy_path("/test/scapy/layers/tls/pki/srv_cert.pem") + mykey = scapy_path("/test/scapy/layers/tls/pki/srv_key.pem") print(mykey) print(mycert) assert os.path.exists(mycert) @@ -97,9 +97,9 @@ def run_tls_test_server(expected_data, q, curve=None, cookie=False, client_auth= def run_openssl_client(msg, suite="", version="", tls13=False, client_auth=False, psk=None, sess_out=None): # Run client - CA_f = scapy_path("/test/tls/pki/ca_cert.pem") - mycert = scapy_path("/test/tls/pki/cli_cert.pem") - mykey = scapy_path("/test/tls/pki/cli_key.pem") + CA_f = scapy_path("/test/scapy/layers/tls/pki/ca_cert.pem") + mycert = scapy_path("/test/scapy/layers/tls/pki/cli_cert.pem") + mykey = scapy_path("/test/scapy/layers/tls/pki/cli_key.pem") args = [ "openssl", "s_client", "-connect", "127.0.0.1:4433", "-debug", @@ -215,8 +215,8 @@ def run_tls_test_client(send_data=None, cipher_suite_code=None, version=None, client_auth=False, key_update=False, stop_server=True, session_ticket_file_out=None, session_ticket_file_in=None): print("Loading client...") - mycert = scapy_path("/test/tls/pki/cli_cert.pem") if client_auth else None - mykey = scapy_path("/test/tls/pki/cli_key.pem") if client_auth else None + mycert = scapy_path("/test/scapy/layers/tls/pki/cli_cert.pem") if client_auth else None + mykey = scapy_path("/test/scapy/layers/tls/pki/cli_key.pem") if client_auth else None commands = [send_data] if key_update: commands.append(b"key_update") diff --git a/test/testsocket.py b/test/testsocket.py index 8017bfef98f..9709a99a350 100644 --- a/test/testsocket.py +++ b/test/testsocket.py @@ -136,8 +136,8 @@ def send(self, x): self.no_error_for_x_tx_pkts -= 1 return super(UnstableSocket, self).send(x) - def recv(self, x=MTU): - # type: (int) -> Optional[Packet] + def recv(self, x=MTU, **kwargs): + # type: (int, **Any) -> Optional[Packet] if self.no_error_for_x_tx_pkts == 0: if random.randint(0, 1000) == 42: self.no_error_for_x_tx_pkts = 10 @@ -153,7 +153,7 @@ def recv(self, x=MTU): return None if self.no_error_for_x_tx_pkts > 0: self.no_error_for_x_tx_pkts -= 1 - return super(UnstableSocket, self).recv(x) + return super(UnstableSocket, self).recv(x, **kwargs) def cleanup_testsockets():