diff --git a/scapy/layers/dcerpc.py b/scapy/layers/dcerpc.py index 5c9f150a7e6..215281c2946 100644 --- a/scapy/layers/dcerpc.py +++ b/scapy/layers/dcerpc.py @@ -2614,31 +2614,59 @@ def _up_pkt(self, pkt): # Since the connection-oriented transport guarantees sequentiality, the receiver # will always receive the fragments in order. - def _defragment(self, pkt): + def _defragment(self, pkt, body=None): """ Function to defragment DCE/RPC packets. """ uid = pkt.call_id if pkt.pfc_flags.PFC_FIRST_FRAG and pkt.pfc_flags.PFC_LAST_FRAG: # Not fragmented - return pkt + return body if pkt.pfc_flags.PFC_FIRST_FRAG or uid in self.frags: # Packet is fragmented - self.frags[uid] += pkt[DceRpc5].payload.payload.original + if body is None: + body = pkt[DceRpc5].payload.payload.original + self.frags[uid] += body if pkt.pfc_flags.PFC_LAST_FRAG: - pkt[DceRpc5].payload.remove_payload() - pkt[DceRpc5].payload /= self.frags[uid] - return pkt + return self.frags[uid] else: # Not fragmented - return pkt + return body - def _fragment(self, pkt): + # C706 sect 12.5.2.15 - PDU Body Length + # "The maximum PDU body size is 65528 bytes." + MAX_PDU_BODY_SIZE = 4176 + + def _fragment(self, pkt, body): """ Function to fragment DCE/RPC packets. """ - # unimplemented - pass + if len(body) > self.MAX_PDU_BODY_SIZE: + # Clear any PFC_*_FRAG flag + pkt.pfc_flags &= 0xFC + + # Iterate through fragments + cur = None + while body: + # Create a fragment + pkt_frag = pkt.copy() + + if cur is None: + # It's the first one + pkt_frag.pfc_flags += "PFC_FIRST_FRAG" + + # Split + cur, body = ( + body[: self.MAX_PDU_BODY_SIZE], + body[self.MAX_PDU_BODY_SIZE :], + ) + + if not body: + # It's the last one + pkt_frag.pfc_flags += "PFC_LAST_FRAG" + yield pkt_frag, cur + else: + yield pkt, body # [MS-RPCE] sect 3.3.1.5.2.2 @@ -2656,12 +2684,6 @@ def _fragment(self, pkt): # Similarly the signature output SHOULD be ignored. def in_pkt(self, pkt): - # Defragment - pkt = self._defragment(pkt) - if not pkt: - return - # Get opnum and options - opnum, opts = self._up_pkt(pkt) # Check for encrypted payloads body = None if conf.raw_layer in pkt.payload: @@ -2779,10 +2801,18 @@ def in_pkt(self, pkt): if pkt.auth_padding: padlen = len(pkt.auth_padding) body, pkt.auth_padding = body[:-padlen], body[-padlen:] - # Put back vt_trailer into the header - if pkt.vt_trailer: - vtlen = len(pkt.vt_trailer) - body, pkt.vt_trailer = body[:-vtlen], body[-vtlen:] + # Put back vt_trailer into the header, if present. + if _SECTRAILER_MAGIC in body: + body, pkt.vt_trailer = pkt.get_field("vt_trailer").getfield( + pkt, body + ) + # If it's a request / response, could be fragmented + if isinstance(pkt.payload, (DceRpc5Request, DceRpc5Response)) and body: + body = self._defragment(pkt, body) + if not body: + return + # Get opnum and options + opnum, opts = self._up_pkt(pkt) # Try to parse the payload if opnum is not None and self.rpc_bind_interface: # use opnum to parse the payload @@ -2798,9 +2828,28 @@ def in_pkt(self, pkt): return pkt if body: # Dissect payload using class - payload = cls(body, ndr64=self.ndr64, ndrendian=self.ndrendian, **opts) + try: + payload = cls( + body, ndr64=self.ndr64, ndrendian=self.ndrendian, **opts + ) + except Exception: + if conf.debug_dissector: + log_runtime.error("%s dissector failed", cls.__name__) + if cls is not None: + raise + payload = conf.raw_layer(body, _internal=1) pkt.payload[conf.raw_layer].underlayer.remove_payload() + if conf.padding_layer in payload: + # Most likely, dissection failed. + log_runtime.warning( + "Padding detected when dissecting %s. Looks wrong." % cls + ) + pad = payload[conf.padding_layer] + pad.underlayer.payload = conf.raw_layer(load=pad.load) pkt /= payload + # If a request was encrypted, we need to re-register it once re-parsed. + if not is_response and self.auth_level == RPC_C_AUTHN_LEVEL.PKT_PRIVACY: + self._up_pkt(pkt) elif not cls.fields_desc: # Request class has no payload pkt /= cls(ndr64=self.ndr64, ndrendian=self.ndrendian, **opts) @@ -2810,134 +2859,159 @@ def in_pkt(self, pkt): def out_pkt(self, pkt): assert DceRpc5 in pkt + # Register opnum and options self._up_pkt(pkt) - if pkt.auth_verifier is not None: - # Verifier already set - return [pkt] - if self.sspcontext and isinstance( - pkt.payload, (DceRpc5Request, DceRpc5Response) - ): + + # If it's a request / response, we can frag it + if isinstance(pkt.payload, (DceRpc5Request, DceRpc5Response)): + # The list of packet responses + pkts = [] + # Take the body payload, and eventually split it body = bytes(pkt.payload.payload) - signature = None - if self.auth_level in ( - RPC_C_AUTHN_LEVEL.PKT_INTEGRITY, - RPC_C_AUTHN_LEVEL.PKT_PRIVACY, - ): - # Account for padding when computing checksum/encryption - if pkt.auth_padding is None: - padlen = (-len(body)) % _COMMON_AUTH_PAD # authdata padding - pkt.auth_padding = b"\x00" * padlen - else: - padlen = len(pkt.auth_padding) - # Remember that vt_trailer is included in the PDU - if pkt.vt_trailer: - body += bytes(pkt.vt_trailer) - # Remember that padding IS SIGNED & ENCRYPTED - body += pkt.auth_padding - # Add the auth_verifier - pkt.auth_verifier = CommonAuthVerifier( - auth_type=self.ssp.auth_type, - auth_level=self.auth_level, - auth_context_id=self.auth_context_id, - auth_pad_length=padlen, - # Note: auth_value should have the correct length because when - # using PFC_SUPPORT_HEADER_SIGN, auth_len (and frag_len) is - # included in the token.. but this creates a dependency loop as - # you'd need to know the token length to compute the token. - # Windows solves this by setting the 'Maximum Signature Length' - # (or something similar) beforehand, instead of the real length. - # See `gensec_sig_size` in samba. - auth_value=b"\x00" - * self.ssp.MaximumSignatureLength(self.sspcontext), - ) - # Build pdu_header and sec_trailer - pdu_header = pkt.copy() - pdu_header.auth_len = len(pdu_header.auth_verifier) - 8 - pdu_header.frag_len = len(pdu_header) - sec_trailer = pdu_header.auth_verifier - # sec_trailer: include the sec_trailer but not the Authentication token - authval_len = len(sec_trailer.auth_value) - # sec_trailer.auth_value = None - # Discard everything out of the header - pdu_header.auth_padding = None - pdu_header.auth_verifier = None - pdu_header.payload.payload = NoPayload() - pdu_header.vt_trailer = None - signature = None - # [MS-RPCE] sect 2.2.2.12 - if self.auth_level == RPC_C_AUTHN_LEVEL.PKT_PRIVACY: - _msgs, signature = self.ssp.GSS_WrapEx( - self.sspcontext, - [ - # "PDU header" - SSP.WRAP_MSG( - conf_req_flag=False, - sign=self.header_sign, - data=bytes(pdu_header), - ), - # "PDU body" - SSP.WRAP_MSG( - conf_req_flag=True, - sign=True, - data=body, - ), - # "sec_trailer" - SSP.WRAP_MSG( - conf_req_flag=False, - sign=self.header_sign, - data=bytes(sec_trailer)[:-authval_len], - ), - ], - ) - s = _msgs[1].data # PDU body - elif self.auth_level == RPC_C_AUTHN_LEVEL.PKT_INTEGRITY: - signature = self.ssp.GSS_GetMICEx( - self.sspcontext, - [ - # "PDU header" - SSP.MIC_MSG( - sign=self.header_sign, - data=bytes(pdu_header), - ), - # "PDU body" - SSP.MIC_MSG( - sign=True, - data=body, - ), - # "sec_trailer" - SSP.MIC_MSG( - sign=self.header_sign, - data=bytes(sec_trailer)[:-authval_len], - ), - ], - pkt.auth_verifier.auth_value, - ) - s = body - else: - raise ValueError("Impossible") - # Put padding back in the header - if padlen: - s, pkt.auth_padding = s[:-padlen], s[-padlen:] - # Put back vt_trailer into the header - if pkt.vt_trailer: - vtlen = len(pkt.vt_trailer) - s, pkt.vt_trailer = s[:-vtlen], s[-vtlen:] - else: - s = body - # now inject the encrypted payload into the packet - pkt.payload.payload = conf.raw_layer(load=s) - # and the auth_value - if signature: - pkt.auth_verifier.auth_value = signature - else: - pkt.auth_verifier = None - return [pkt] + for pkt, body in self._fragment(pkt, body): + if pkt.auth_verifier is not None: + # Verifier already set + pkts.append(pkt) + continue + + # Sign / Encrypt + if self.sspcontext: + signature = None + if self.auth_level in ( + RPC_C_AUTHN_LEVEL.PKT_INTEGRITY, + RPC_C_AUTHN_LEVEL.PKT_PRIVACY, + ): + # Account for padding when computing checksum/encryption + if pkt.auth_padding is None: + padlen = (-len(body)) % _COMMON_AUTH_PAD # authdata padding + pkt.auth_padding = b"\x00" * padlen + else: + padlen = len(pkt.auth_padding) + # Remember that vt_trailer is included in the PDU + if pkt.vt_trailer: + body += bytes(pkt.vt_trailer) + # Remember that padding IS SIGNED & ENCRYPTED + body += pkt.auth_padding + # Add the auth_verifier + pkt.auth_verifier = CommonAuthVerifier( + auth_type=self.ssp.auth_type, + auth_level=self.auth_level, + auth_context_id=self.auth_context_id, + auth_pad_length=padlen, + # Note: auth_value should have the correct length because + # when using PFC_SUPPORT_HEADER_SIGN, auth_len + # (and frag_len) is included in the token.. but this + # creates a dependency loop as you'd need to know the token + # length to compute the token. Windows solves this by + # setting the 'Maximum Signature Length' (or something + # similar) beforehand, instead of the real length. + # See `gensec_sig_size` in samba. + auth_value=b"\x00" + * self.ssp.MaximumSignatureLength(self.sspcontext), + ) + # Build pdu_header and sec_trailer + pdu_header = pkt.copy() + pdu_header.auth_len = len(pdu_header.auth_verifier) - 8 + pdu_header.frag_len = len(pdu_header) + sec_trailer = pdu_header.auth_verifier + # sec_trailer: include the sec_trailer but not the + # Authentication token + authval_len = len(sec_trailer.auth_value) + # sec_trailer.auth_value = None + # Discard everything out of the header + pdu_header.auth_padding = None + pdu_header.auth_verifier = None + pdu_header.payload.payload = NoPayload() + pdu_header.vt_trailer = None + signature = None + # [MS-RPCE] sect 2.2.2.12 + if self.auth_level == RPC_C_AUTHN_LEVEL.PKT_PRIVACY: + _msgs, signature = self.ssp.GSS_WrapEx( + self.sspcontext, + [ + # "PDU header" + SSP.WRAP_MSG( + conf_req_flag=False, + sign=self.header_sign, + data=bytes(pdu_header), + ), + # "PDU body" + SSP.WRAP_MSG( + conf_req_flag=True, + sign=True, + data=body, + ), + # "sec_trailer" + SSP.WRAP_MSG( + conf_req_flag=False, + sign=self.header_sign, + data=bytes(sec_trailer)[:-authval_len], + ), + ], + ) + s = _msgs[1].data # PDU body + elif self.auth_level == RPC_C_AUTHN_LEVEL.PKT_INTEGRITY: + signature = self.ssp.GSS_GetMICEx( + self.sspcontext, + [ + # "PDU header" + SSP.MIC_MSG( + sign=self.header_sign, + data=bytes(pdu_header), + ), + # "PDU body" + SSP.MIC_MSG( + sign=True, + data=body, + ), + # "sec_trailer" + SSP.MIC_MSG( + sign=self.header_sign, + data=bytes(sec_trailer)[:-authval_len], + ), + ], + pkt.auth_verifier.auth_value, + ) + s = body + else: + raise ValueError("Impossible") + # Put padding back in the header + if padlen: + s, pkt.auth_padding = s[:-padlen], s[-padlen:] + # Put back vt_trailer into the header + if pkt.vt_trailer: + vtlen = len(pkt.vt_trailer) + s, pkt.vt_trailer = s[:-vtlen], s[-vtlen:] + else: + s = body + + # now inject the encrypted payload into the packet + pkt.payload.payload = conf.raw_layer(load=s) + # and the auth_value + if signature: + pkt.auth_verifier.auth_value = signature + else: + pkt.auth_verifier = None + # Add to the list + pkts.append(pkt) + return pkts + else: + return [pkt] def process(self, pkt: Packet) -> Optional[Packet]: + """ + Used when DceRpcSession is used for passive sniffing. + """ pkt = super(DceRpcSession, self).process(pkt) if pkt is not None and DceRpc5 in pkt: - return self.in_pkt(pkt) + rpkt = self.in_pkt(pkt) + if rpkt is None: + # We are passively dissecting a fragmented packet. Return + # just the header showing that it was indeed, fragmented. + pkt[DceRpc5].payload.remove_payload() + return pkt + return rpkt return pkt @@ -2947,6 +3021,7 @@ class DceRpcSocket(StreamSocket): """ def __init__(self, *args, **kwargs): + self.transport = kwargs.pop("transport", None) self.session = DceRpcSession( ssp=kwargs.pop("ssp", None), auth_level=kwargs.pop("auth_level", None), @@ -2957,7 +3032,11 @@ def __init__(self, *args, **kwargs): def send(self, x, **kwargs): for pkt in self.session.out_pkt(x): - return super(DceRpcSocket, self).send(pkt, **kwargs) + if self.transport == DCERPC_Transport.NCACN_NP: + # In this case DceRpcSocket wraps a SMB_RPC_SOCKET, call it directly. + self.ins.send(pkt, **kwargs) + else: + super(DceRpcSocket, self).send(pkt, **kwargs) def recv(self, x=None): pkt = super(DceRpcSocket, self).recv(x) diff --git a/scapy/layers/msrpce/rpcclient.py b/scapy/layers/msrpce/rpcclient.py index 2d0f3410602..1c1e87ba23c 100644 --- a/scapy/layers/msrpce/rpcclient.py +++ b/scapy/layers/msrpce/rpcclient.py @@ -81,6 +81,7 @@ def __init__(self, transport, ndr64=False, ndrendian="little", verb=True, **kwar self.ssp = kwargs.pop("ssp", None) # type: SSP self.sspcontext = None self.dcesockargs = kwargs + self.dcesockargs["transport"] = self.transport @classmethod def from_smblink(cls, smbcli, smb_kwargs={}, **kwargs): diff --git a/scapy/layers/smb2.py b/scapy/layers/smb2.py index b545a421cff..d7c18f07a16 100644 --- a/scapy/layers/smb2.py +++ b/scapy/layers/smb2.py @@ -3633,7 +3633,7 @@ class SMB2_IOCTL_Request(_SMB2_Payload, _NTLMPayloadPacket): LEIntField("MaxInputResponse", 0), LEIntField("OutputBufferOffset", None), LEIntField("OutputLen", None), # Called OutputCount. - LEIntField("MaxOutputResponse", 1024), + LEIntField("MaxOutputResponse", 65535), FlagsField("Flags", 0, -32, {0x00000001: "SMB2_0_IOCTL_IS_FSCTL"}), LEIntField("Reserved2", 0), _NTLMPayloadField( @@ -4707,7 +4707,14 @@ def recv(self, x=None): and self.session.Dialect and self.session.SigningKey and self.session.SigningRequired + # [MS-SMB2] sect 3.2.5.1.3 Verifying the Signature + # "The client MUST skip the processing in this section if any of:" + # - [...] decryption in section 3.2.5.1.1.1 succeeds and not smbh._decrypted + # - MessageId is 0xFFFFFFFFFFFFFFFF + and smbh.MID != 0xFFFFFFFFFFFFFFFF + # - Status in the SMB2 header is STATUS_PENDING + and smbh.Status != 0x00000103 ): smbh.verify( self.session.Dialect, @@ -4746,6 +4753,9 @@ def __init__(self, *args, **kwargs): self.Dialect = 0x0202 # Updated by parent self.Credits = 0 self.IsGuest = False + self.MaxTransactionSize = 0 + self.MaxReadSize = 0 + self.MaxWriteSize = 0 # Crypto parameters. Go read [MS-SMB2] to understand the names. self.SigningRequired = True self.SupportsEncryption = False diff --git a/scapy/layers/smbclient.py b/scapy/layers/smbclient.py index e31e1a47f01..e543008bf4e 100644 --- a/scapy/layers/smbclient.py +++ b/scapy/layers/smbclient.py @@ -164,9 +164,6 @@ def __init__(self, sock, ssp=None, *args, **kwargs): self.NegotiateCapabilities = None self.GUID = RandUUID()._fix() self.SequenceWindow = (0, 0) # keep track of allowed MIDs - self.MaxTransactionSize = 0 - self.MaxReadSize = 0 - self.MaxWriteSize = 0 if ssp is None: # We got no SSP. Assuming the server allows anonymous ssp = SPNEGOSSP( @@ -412,9 +409,9 @@ def receive_negotiate_response(self, pkt): bytes(pkt[SMB2_Header]), # nego response ) # Process max sizes - self.MaxReadSize = pkt.MaxReadSize - self.MaxTransactionSize = pkt.MaxTransactionSize - self.MaxWriteSize = pkt.MaxWriteSize + self.session.MaxReadSize = pkt.MaxReadSize + self.session.MaxTransactionSize = pkt.MaxTransactionSize + self.session.MaxWriteSize = pkt.MaxWriteSize # Process SecurityMode if pkt.SecurityMode.SIGNING_REQUIRED: self.session.SigningRequired = True @@ -1028,8 +1025,12 @@ def send(self, x): """ Internal ObjectPipe function. """ - # Reminder: this class is an ObjectPipe, it's just a queue - if self.use_ioctl: + # Reminder: this class is an ObjectPipe, it's just a queue. + + # Detect if DCE/RPC is fragmented. Then we must use Read/Write + is_frag = x.pfc_flags & 3 != 3 + + if self.use_ioctl and not is_frag: # Use IOCTLRequest pkt = SMB2_IOCTL_Request( FileId=self.PipeFileId, @@ -1062,6 +1063,9 @@ def send(self, x): resp = self.ins.sr1(pkt, verbose=0) if SMB2_Write_Response not in resp: raise ValueError("Failed sending WriteResponse ! %s" % resp.NTStatus) + # If fragmented, only read if it's the last. + if is_frag and not x.pfc_flags.PFC_LAST_FRAG: + return # We send a Read Request afterwards resp = self.ins.sr1( SMB2_Read_Request( @@ -1071,9 +1075,9 @@ def send(self, x): ) if SMB2_Read_Response not in resp: raise ValueError("Failed reading ReadResponse ! %s" % resp.NTStatus) - data = bytes(resp.Data) - # Handle BUFFER_OVERFLOW (big DCE/RPC response) - while resp.NTStatus == "STATUS_BUFFER_OVERFLOW": + super(SMB_RPC_SOCKET, self).send(resp.Data) + # Handle fragmented response + while resp.Data[3] & 2 != 2: # PFC_LAST_FRAG not set # Retrieve DCE/RPC full size resp = self.ins.sr1( SMB2_Read_Request( @@ -1081,8 +1085,7 @@ def send(self, x): ), verbose=0, ) - data += resp.Data - super(SMB_RPC_SOCKET, self).send(data) + super(SMB_RPC_SOCKET, self).send(resp.Data) def close(self): SMB_SOCKET.close(self) @@ -1611,7 +1614,7 @@ def _get_file(self, file, fd): offset = 0 # Read the file while length: - lengthRead = min(self.sock.atmt.MaxReadSize, length) + lengthRead = min(self.smbsock.session.MaxReadSize, length) fd.write( self.smbsock.read_request(fileId, Length=lengthRead, Offset=offset) ) @@ -1638,7 +1641,7 @@ def _send_file(self, fname, fd): # Send the file offset = 0 while True: - data = fd.read(self.sock.atmt.MaxWriteSize) + data = fd.read(self.smbsock.session.MaxWriteSize) if not data: # end of file break diff --git a/test/scapy/layers/dcerpc.uts b/test/scapy/layers/dcerpc.uts index 6847a5cdc9a..5a3ac0c7147 100644 --- a/test/scapy/layers/dcerpc.uts +++ b/test/scapy/layers/dcerpc.uts @@ -561,6 +561,7 @@ assert pkt.value.Params[0].Type == 3 = [PASSIVE] Passive sniffing of DCE/RPC packets encrypted with SPNEGOSSP[NTLMSSP] from scapy.libs.rfc3961 import * +import uuid bind_bottom_up(TCP, DceRpc5, dport=49679) bind_bottom_up(TCP, DceRpc5, sport=49679) @@ -582,10 +583,19 @@ pkts.show() conf.dcerpc_session_enable = False -assert pkts[16].load == b'\x00\x00\x02\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x001\x009\x002\x00.\x001\x006\x008\x00.\x000\x00.\x001\x000\x000\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x8a\xe3\x13q\x02\xf46q\x02@(\x00\x81\xbbz6D\x98\xf15\xad2\x98\xf08\x00\x10\x03\x02\x00\x00\x00\x04]\x88\x8a\xeb\x1c\xc9\x11\x9f\xe8\x08\x00+\x10H`\x02\x00\x00\x00' +# Packet 16 has an encrypted vt_trailer +assert pkts[16].vt_trailer.commands[0].Command == 2 +assert pkts[16].vt_trailer.commands[0].TransferSyntax == uuid.UUID('8a885d04-1ceb-11c9-9fe8-08002b104860') +assert pkts[16].load == b'\x00\x00\x02\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x001\x009\x002\x00.\x001\x006\x008\x00.\x000\x00.\x001\x000\x000\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00' + assert pkts[22].load == b'0\x00\x00\x00&\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00A\x00D\x00W\x00S\x00\x00\x00\xee`\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\xea\x00\x00\x00' assert pkts[23].load == b'\x00\x00\x00\x00\xad\xb3\xf5\xd1\x8eJ\xdeG\xa9\xa5\x85\xccvb\x8b\x970\x00\x00\x00\x03\x00\x00\x00\x1d\x83\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00' +# Packet 32 is defragmented and encrypted ! +assert pkts[32].auth_padding == b'\x00\x00\x00\x00\x00\x00\x00\x00' +assert len(pkts[32].load) == 33592 # reassembled +assert hashlib.sha256(pkts[32].load).digest() == b"\xc0\xb5\xde\x1c0\\\x02\x04\x1c\x7f\x05\xcc\xde\xd7\x01\xa5{\x917\xb4\xff\xc7\xa4\xd1\x89\xcd\x1cQ\xa1'3!" + = [PASSIVE] Passive sniffing of DCE/RPC packets encrypted with SPNEGOSSP[KerberosSSP] with AES from scapy.libs.rfc3961 import * @@ -609,7 +619,10 @@ pkts.show() conf.dcerpc_session_enable = False -assert pkts[15].load == b'\x00\x00\x02\x00\x00\x00\x00\x00\x1a M\xe2\xd6O\xd1\x11\xa3\xda\x00\x00\xf8u\xae\r\x00\x00\x02\x00\x00\x00\x00\x004\x00\x00\x00\x00\x00\x00\x004\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8a\xe3\x13q\x02\xf46q\x02@(\x005BQ\xe3\x06K\xd1\x11\xab\x04\x00\xc0O\xc2\xdc\xd2\x04\x00\x00\x003\x05qq\xba\xbe7I\x83\x19\xb5\xdb\xef\x9c\xcc6\x01\x00\x00\x00' +# Packet 15 has an encrypted vt_trailer +assert pkts[15].vt_trailer.commands[0].Command == 2 +assert pkts[15].load == b'\x00\x00\x02\x00\x00\x00\x00\x00\x1a M\xe2\xd6O\xd1\x11\xa3\xda\x00\x00\xf8u\xae\r\x00\x00\x02\x00\x00\x00\x00\x004\x00\x00\x00\x00\x00\x00\x004\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + assert pkts[21].load == b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00K\x00\x00\x00\x00\x00\x00\x00K\x00\x00\x00\x05\x00\x13\x00\r5BQ\xe3\x06K\xd1\x11\xab\x04\x00\xc0O\xc2\xdc\xd2\x04\x00\x02\x00\x00\x00\x13\x00\r\x04]\x88\x8a\xeb\x1c\xc9\x11\x9f\xe8\x08\x00+\x10H`\x02\x00\x02\x00\x00\x00\x01\x00\x0b\x02\x00\x00\x00\x01\x00\x07\x02\x00\x00\x87\x01\x00\t\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00' assert pkts[22].load == b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00K\x00\x00\x00\x00\x00\x00\x00K\x00\x00\x00\x05\x00\x13\x00\r5BQ\xe3\x06K\xd1\x11\xab\x04\x00\xc0O\xc2\xdc\xd2\x04\x00\x02\x00\x00\x00\x13\x00\r\x04]\x88\x8a\xeb\x1c\xc9\x11\x9f\xe8\x08\x00+\x10H`\x02\x00\x02\x00\x00\x00\x01\x00\x0b\x02\x00\x00\x00\x01\x00\x07\x02\x00\xc2\x03\x01\x00\t\x04\x00\xc0\xa8\x00d\x00\x00\x00\x00\x00'