1# SPDX-License-Identifier: GPL-2.0-only 2# This file is part of Scapy 3# See https://scapy.net/ for more information 4# Copyright (C) 2017 Maxence Tury 5 6""" 7SSLv2 Record. 8""" 9 10import struct 11 12from scapy.config import conf 13from scapy.error import log_runtime 14from scapy.compat import orb, raw 15from scapy.packet import Raw 16from scapy.layers.tls.session import _GenericTLSSessionInheritance 17from scapy.layers.tls.record import _TLSMsgListField, TLS 18from scapy.layers.tls.handshake_sslv2 import _sslv2_handshake_cls 19from scapy.layers.tls.basefields import (_SSLv2LengthField, _SSLv2PadField, 20 _SSLv2PadLenField, _TLSMACField) 21 22 23############################################################################### 24# SSLv2 Record Protocol # 25############################################################################### 26 27class _SSLv2MsgListField(_TLSMsgListField): 28 def __init__(self, name, default, length_from=None): 29 if not length_from: 30 length_from = lambda pkt: ((pkt.len & 0x7fff) - 31 (pkt.padlen or 0) - 32 len(pkt.mac)) 33 super(_SSLv2MsgListField, self).__init__(name, default, 34 length_from=length_from) 35 36 def m2i(self, pkt, m): 37 cls = Raw 38 if len(m) >= 1: 39 msgtype = orb(m[0]) 40 cls = _sslv2_handshake_cls.get(msgtype, Raw) 41 42 if cls is Raw: 43 return Raw(m) 44 else: 45 return cls(m, tls_session=pkt.tls_session) 46 47 def i2m(self, pkt, p): 48 cur = b"" 49 if isinstance(p, _GenericTLSSessionInheritance): 50 p.tls_session = pkt.tls_session 51 if not pkt.tls_session.frozen: 52 cur = p.raw_stateful() 53 p.post_build_tls_session_update(cur) 54 else: 55 cur = raw(p) 56 else: 57 cur = raw(p) 58 return cur 59 60 def addfield(self, pkt, s, val): 61 res = b"" 62 for p in val: 63 res += self.i2m(pkt, p) 64 return s + res 65 66 67class SSLv2(TLS): 68 """ 69 The encrypted_data is the encrypted version of mac+msg+pad. 70 """ 71 __slots__ = ["with_padding", "protected_record"] 72 name = "SSLv2" 73 fields_desc = [_SSLv2LengthField("len", None), 74 _SSLv2PadLenField("padlen", None), 75 _TLSMACField("mac", b""), 76 _SSLv2MsgListField("msg", []), 77 _SSLv2PadField("pad", "")] 78 79 def __init__(self, *args, **kargs): 80 self.with_padding = kargs.get("with_padding", False) 81 self.protected_record = kargs.get("protected_record", None) 82 super(SSLv2, self).__init__(*args, **kargs) 83 84 # Parsing methods 85 86 def _sslv2_mac_verify(self, msg, mac): 87 secret = self.tls_session.rcs.cipher.key 88 if secret is None: 89 return True 90 91 mac_len = self.tls_session.rcs.mac_len 92 if mac_len == 0: # should be TLS_NULL_WITH_NULL_NULL 93 return True 94 if len(mac) != mac_len: 95 return False 96 97 read_seq_num = struct.pack("!I", self.tls_session.rcs.seq_num) 98 alg = self.tls_session.rcs.hash 99 h = alg.digest(secret + msg + read_seq_num) 100 return h == mac 101 102 def pre_dissect(self, s): 103 if len(s) < 2: 104 raise Exception("Invalid record: header is too short.") 105 106 msglen = struct.unpack("!H", s[:2])[0] 107 if msglen & 0x8000: 108 hdrlen = 2 109 msglen_clean = msglen & 0x7fff 110 else: 111 hdrlen = 3 112 msglen_clean = msglen & 0x3fff 113 114 hdr = s[:hdrlen] 115 efrag = s[hdrlen:hdrlen + msglen_clean] 116 self.protected_record = s[:hdrlen + msglen_clean] 117 r = s[hdrlen + msglen_clean:] 118 119 mac = pad = b"" 120 121 # Decrypt (with implicit IV if block cipher) 122 mfrag = self._tls_decrypt(efrag) 123 124 # Extract MAC 125 maclen = self.tls_session.rcs.mac_len 126 if maclen == 0: 127 mac, pfrag = b"", mfrag 128 else: 129 mac, pfrag = mfrag[:maclen], mfrag[maclen:] 130 131 # Extract padding 132 padlen = 0 133 if hdrlen == 3: 134 padlen = orb(s[2]) 135 if padlen == 0: 136 cfrag, pad = pfrag, b"" 137 else: 138 cfrag, pad = pfrag[:-padlen], pfrag[-padlen:] 139 140 # Verify integrity 141 is_mac_ok = self._sslv2_mac_verify(cfrag + pad, mac) 142 if not is_mac_ok: 143 pkt_info = self.firstlayer().summary() 144 log_runtime.info("SSLv2: record integrity check failed [%s]", pkt_info) # noqa: E501 145 146 reconstructed_body = mac + cfrag + pad 147 return hdr + reconstructed_body + r 148 149 def post_dissect(self, s): 150 """ 151 SSLv2 may force us to commit the write connState here. 152 """ 153 if self.tls_session.triggered_prcs_commit: 154 if self.tls_session.prcs is not None: 155 self.tls_session.rcs = self.tls_session.prcs 156 self.tls_session.prcs = None 157 self.tls_session.triggered_prcs_commit = False 158 if self.tls_session.triggered_pwcs_commit: 159 if self.tls_session.pwcs is not None: 160 self.tls_session.wcs = self.tls_session.pwcs 161 self.tls_session.pwcs = None 162 self.tls_session.triggered_pwcs_commit = False 163 164 if self.tls_session.prcs is not None: 165 self.tls_session.prcs.seq_num += 1 166 self.tls_session.rcs.seq_num += 1 167 return s 168 169 def do_dissect_payload(self, s): 170 if s: 171 try: 172 p = SSLv2(s, _internal=1, _underlayer=self, 173 tls_session=self.tls_session) 174 except KeyboardInterrupt: 175 raise 176 except Exception: 177 if conf.debug_dissector: 178 raise 179 p = conf.raw_layer(s, _internal=1, _underlayer=self) 180 self.add_payload(p) 181 182 # Building methods 183 184 def _sslv2_mac_add(self, msg): 185 secret = self.tls_session.wcs.cipher.key 186 if secret is None: 187 return msg 188 189 write_seq_num = struct.pack("!I", self.tls_session.wcs.seq_num) 190 alg = self.tls_session.wcs.hash 191 h = alg.digest(secret + msg + write_seq_num) 192 return h + msg 193 194 def _sslv2_pad(self, s): 195 padding = b"" 196 block_size = self.tls_session.wcs.cipher.block_size 197 padlen = block_size - (len(s) % block_size) 198 if padlen == block_size: 199 padlen = 0 200 padding = b"\x00" * padlen 201 return s + padding 202 203 def post_build(self, pkt, pay): 204 if self.protected_record is not None: 205 # we do not update the tls_session 206 return self.protected_record + pay 207 208 if self.padlen is None: 209 cfrag = pkt[2:] 210 else: 211 cfrag = pkt[3:] 212 213 if self.pad == b"" and self.tls_session.wcs.cipher.type == 'block': 214 pfrag = self._sslv2_pad(cfrag) 215 else: 216 pad = self.pad or b"" 217 pfrag = cfrag + pad 218 219 padlen = self.padlen 220 if padlen is None: 221 padlen = len(pfrag) - len(cfrag) 222 hdr = pkt[:2] 223 if padlen > 0: 224 hdr += struct.pack("B", padlen) 225 226 # Integrity 227 if self.mac == b"": 228 mfrag = self._sslv2_mac_add(pfrag) 229 else: 230 mfrag = self.mac + pfrag 231 232 # Encryption 233 efrag = self._tls_encrypt(mfrag) 234 235 if self.len is not None: 236 tmp_len = self.len 237 if not self.with_padding: 238 tmp_len |= 0x8000 239 hdr = struct.pack("!H", tmp_len) + hdr[2:] 240 else: 241 # Update header with the length of TLSCiphertext.fragment 242 msglen_new = len(efrag) 243 if padlen: 244 if msglen_new > 0x3fff: 245 raise Exception("Invalid record: encrypted data too long.") 246 else: 247 if msglen_new > 0x7fff: 248 raise Exception("Invalid record: encrypted data too long.") 249 msglen_new |= 0x8000 250 hdr = struct.pack("!H", msglen_new) + hdr[2:] 251 252 # Now we commit the pending write state if it has been triggered (e.g. 253 # by an underlying TLSChangeCipherSpec or a SSLv2ClientMasterKey). We 254 # update nothing if the pwcs was not set. This probably means that 255 # we're working out-of-context (and we need to keep the default wcs). 256 # SSLv2 may force us to commit the reading connState here. 257 if self.tls_session.triggered_pwcs_commit: 258 if self.tls_session.pwcs is not None: 259 self.tls_session.wcs = self.tls_session.pwcs 260 self.tls_session.pwcs = None 261 self.tls_session.triggered_pwcs_commit = False 262 if self.tls_session.triggered_prcs_commit: 263 if self.tls_session.prcs is not None: 264 self.tls_session.rcs = self.tls_session.prcs 265 self.tls_session.prcs = None 266 self.tls_session.triggered_prcs_commit = False 267 268 if self.tls_session.pwcs is not None: 269 self.tls_session.pwcs.seq_num += 1 270 self.tls_session.wcs.seq_num += 1 271 272 return hdr + efrag + pay 273