| ## This file is part of Scapy |
| ## Copyright (C) 2017 Maxence Tury |
| ## This program is published under a GPLv2 license |
| |
| """ |
| SSLv2 Record. |
| """ |
| |
| import struct |
| |
| from scapy.config import conf |
| from scapy.error import log_runtime |
| from scapy.compat import * |
| from scapy.fields import * |
| from scapy.packet import * |
| from scapy.layers.tls.session import _GenericTLSSessionInheritance |
| from scapy.layers.tls.record import _TLSMsgListField, TLS |
| from scapy.layers.tls.handshake_sslv2 import _sslv2_handshake_cls |
| from scapy.layers.tls.basefields import (_SSLv2LengthField, _SSLv2PadField, |
| _SSLv2PadLenField, _TLSMACField) |
| |
| |
| ############################################################################### |
| ### SSLv2 Record Protocol ### |
| ############################################################################### |
| |
| class _SSLv2MsgListField(_TLSMsgListField): |
| def __init__(self, name, default, length_from=None): |
| if not length_from: |
| length_from=lambda pkt: ((pkt.len & 0x7fff) - |
| (pkt.padlen or 0) - |
| len(pkt.mac)) |
| super(_SSLv2MsgListField, self).__init__(name, default, length_from) |
| |
| def m2i(self, pkt, m): |
| cls = Raw |
| if len(m) >= 1: |
| msgtype = orb(m[0]) |
| cls = _sslv2_handshake_cls.get(msgtype, Raw) |
| |
| if cls is Raw: |
| return Raw(m) |
| else: |
| return cls(m, tls_session=pkt.tls_session) |
| |
| def i2m(self, pkt, p): |
| cur = b"" |
| if isinstance(p, _GenericTLSSessionInheritance): |
| p.tls_session = pkt.tls_session |
| if not pkt.tls_session.frozen: |
| cur = p.raw_stateful() |
| p.post_build_tls_session_update(cur) |
| else: |
| cur = raw(p) |
| else: |
| cur = raw(p) |
| return cur |
| |
| def addfield(self, pkt, s, val): |
| res = b"" |
| for p in val: |
| res += self.i2m(pkt, p) |
| return s + res |
| |
| |
| class SSLv2(TLS): |
| """ |
| The encrypted_data is the encrypted version of mac+msg+pad. |
| """ |
| __slots__ = ["with_padding", "protected_record"] |
| name = "SSLv2" |
| fields_desc = [ _SSLv2LengthField("len", None), |
| _SSLv2PadLenField("padlen", None), |
| _TLSMACField("mac", b""), |
| _SSLv2MsgListField("msg", []), |
| _SSLv2PadField("pad", "") ] |
| |
| def __init__(self, *args, **kargs): |
| self.with_padding = kargs.get("with_padding", False) |
| self.protected_record = kargs.get("protected_record", None) |
| super(SSLv2, self).__init__(*args, **kargs) |
| |
| ### Parsing methods |
| |
| def _sslv2_mac_verify(self, msg, mac): |
| secret = self.tls_session.rcs.cipher.key |
| if secret is None: |
| return True |
| |
| mac_len = self.tls_session.rcs.mac_len |
| if mac_len == 0: # should be TLS_NULL_WITH_NULL_NULL |
| return True |
| if len(mac) != mac_len: |
| return False |
| |
| read_seq_num = struct.pack("!I", self.tls_session.rcs.seq_num) |
| alg = self.tls_session.rcs.hash |
| h = alg.digest(secret + msg + read_seq_num) |
| return h == mac |
| |
| def pre_dissect(self, s): |
| if len(s) < 2: |
| raise Exception("Invalid record: header is too short.") |
| |
| msglen = struct.unpack("!H", s[:2])[0] |
| if msglen & 0x8000: |
| hdrlen = 2 |
| msglen_clean = msglen & 0x7fff |
| else: |
| hdrlen = 3 |
| msglen_clean = msglen & 0x3fff |
| |
| hdr = s[:hdrlen] |
| efrag = s[hdrlen:hdrlen+msglen_clean] |
| self.protected_record = s[:hdrlen+msglen_clean] |
| r = s[hdrlen+msglen_clean:] |
| |
| mac = pad = b"" |
| |
| cipher_type = self.tls_session.rcs.cipher.type |
| |
| # Decrypt (with implicit IV if block cipher) |
| mfrag = self._tls_decrypt(efrag) |
| |
| # Extract MAC |
| maclen = self.tls_session.rcs.mac_len |
| if maclen == 0: |
| mac, pfrag = b"", mfrag |
| else: |
| mac, pfrag = mfrag[:maclen], mfrag[maclen:] |
| |
| # Extract padding |
| padlen = 0 |
| if hdrlen == 3: |
| padlen = orb(s[2]) |
| if padlen == 0: |
| cfrag, pad = pfrag, b"" |
| else: |
| cfrag, pad = pfrag[:-padlen], pfrag[-padlen:] |
| |
| # Verify integrity |
| 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) |
| |
| reconstructed_body = mac + cfrag + pad |
| return hdr + reconstructed_body + r |
| |
| def post_dissect(self, s): |
| """ |
| SSLv2 may force us to commit the write connState here. |
| """ |
| if self.tls_session.triggered_prcs_commit: |
| if self.tls_session.prcs is not None: |
| self.tls_session.rcs = self.tls_session.prcs |
| self.tls_session.prcs = None |
| self.tls_session.triggered_prcs_commit = False |
| if self.tls_session.triggered_pwcs_commit: |
| if self.tls_session.pwcs is not None: |
| self.tls_session.wcs = self.tls_session.pwcs |
| self.tls_session.pwcs = None |
| self.tls_session.triggered_pwcs_commit = False |
| |
| if self.tls_session.prcs is not None: |
| self.tls_session.prcs.seq_num += 1 |
| self.tls_session.rcs.seq_num += 1 |
| return s |
| |
| def do_dissect_payload(self, s): |
| if s: |
| try: |
| p = SSLv2(s, _internal=1, _underlayer=self, |
| tls_session = self.tls_session) |
| except KeyboardInterrupt: |
| raise |
| except: |
| p = conf.raw_layer(s, _internal=1, _underlayer=self) |
| self.add_payload(p) |
| |
| |
| ### Building methods |
| |
| def _sslv2_mac_add(self, msg): |
| secret = self.tls_session.wcs.cipher.key |
| if secret is None: |
| return msg |
| |
| write_seq_num = struct.pack("!I", self.tls_session.wcs.seq_num) |
| alg = self.tls_session.wcs.hash |
| h = alg.digest(secret + msg + write_seq_num) |
| return h + msg |
| |
| def _sslv2_pad(self, s): |
| padding = b"" |
| block_size = self.tls_session.wcs.cipher.block_size |
| padlen = block_size - (len(s) % block_size) |
| if padlen == block_size: |
| padlen = 0 |
| padding = b"\x00" * padlen |
| return s + padding |
| |
| def post_build(self, pkt, pay): |
| if self.protected_record is not None: |
| # we do not update the tls_session |
| return self.protected_record + pay |
| |
| if self.padlen is None: |
| cfrag = pkt[2:] |
| else: |
| cfrag = pkt[3:] |
| |
| if self.pad == b"" and self.tls_session.wcs.cipher.type == 'block': |
| pfrag = self._sslv2_pad(cfrag) |
| else: |
| pad = self.pad or b"" |
| pfrag = cfrag + pad |
| |
| padlen = self.padlen |
| if padlen is None: |
| padlen = len(pfrag) - len(cfrag) |
| hdr = pkt[:2] |
| if padlen > 0: |
| hdr += struct.pack("B", padlen) |
| |
| # Integrity |
| if self.mac == b"": |
| mfrag = self._sslv2_mac_add(pfrag) |
| else: |
| mfrag = self.mac + pfrag |
| |
| # Encryption |
| efrag = self._tls_encrypt(mfrag) |
| |
| if self.len is not None: |
| l = self.len |
| if not self.with_padding: |
| l |= 0x8000 |
| hdr = struct.pack("!H", l) + hdr[2:] |
| else: |
| # Update header with the length of TLSCiphertext.fragment |
| msglen_new = len(efrag) |
| if padlen: |
| if msglen_new > 0x3fff: |
| raise Exception("Invalid record: encrypted data too long.") |
| else: |
| if msglen_new > 0x7fff: |
| raise Exception("Invalid record: encrypted data too long.") |
| msglen_new |= 0x8000 |
| hdr = struct.pack("!H", msglen_new) + hdr[2:] |
| |
| # Now we commit the pending write state if it has been triggered (e.g. |
| # by an underlying TLSChangeCipherSpec or a SSLv2ClientMasterKey). We |
| # update nothing if the pwcs was not set. This probably means that |
| # we're working out-of-context (and we need to keep the default wcs). |
| # SSLv2 may force us to commit the reading connState here. |
| if self.tls_session.triggered_pwcs_commit: |
| if self.tls_session.pwcs is not None: |
| self.tls_session.wcs = self.tls_session.pwcs |
| self.tls_session.pwcs = None |
| self.tls_session.triggered_pwcs_commit = False |
| if self.tls_session.triggered_prcs_commit: |
| if self.tls_session.prcs is not None: |
| self.tls_session.rcs = self.tls_session.prcs |
| self.tls_session.prcs = None |
| self.tls_session.triggered_prcs_commit = False |
| |
| if self.tls_session.pwcs is not None: |
| self.tls_session.pwcs.seq_num += 1 |
| self.tls_session.wcs.seq_num += 1 |
| |
| return hdr + efrag + pay |
| |