Last active
December 4, 2021 18:57
-
-
Save eXenon/5421dd857a86ff1424be to your computer and use it in GitHub Desktop.
Quick SSL Packet Parser / Builder
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Constants - taken from dpkt/ssl.py | |
import struct | |
# SSLv3/TLS versions | |
SSL3_V = 0x0300 | |
TLS1_V = 0x0301 | |
TLS11_V = 0x0302 | |
TLS12_V = 0x0303 | |
SSL3_VERSION_STR = { | |
SSL3_V: 'SSL3', | |
TLS1_V: 'TLS 1.0', | |
TLS11_V: 'TLS 1.1', | |
TLS12_V: 'TLS 1.2' | |
} | |
SSL3_VERSION_BYTES = set(('\x03\x00', '\x03\x01', '\x03\x02', '\x03\x03')) | |
# Alert levels | |
SSL3_AD_WARNING = 1 | |
SSL3_AD_FATAL = 2 | |
alert_level_str = { | |
SSL3_AD_WARNING: 'SSL3_AD_WARNING', | |
SSL3_AD_FATAL: 'SSL3_AD_FATAL' | |
} | |
# SSL3 alert descriptions | |
SSL3_AD_CLOSE_NOTIFY = 0 | |
SSL3_AD_UNEXPECTED_MESSAGE = 10 # fatal | |
SSL3_AD_BAD_RECORD_MAC = 20 # fatal | |
SSL3_AD_DECOMPRESSION_FAILURE = 30 # fatal | |
SSL3_AD_HANDSHAKE_FAILURE = 40 # fatal | |
SSL3_AD_NO_CERTIFICATE = 41 | |
SSL3_AD_BAD_CERTIFICATE = 42 | |
SSL3_AD_UNSUPPORTED_CERTIFICATE = 43 | |
SSL3_AD_CERTIFICATE_REVOKED = 44 | |
SSL3_AD_CERTIFICATE_EXPIRED = 45 | |
SSL3_AD_CERTIFICATE_UNKNOWN = 46 | |
SSL3_AD_ILLEGAL_PARAMETER = 47 # fatal | |
# TLS1 alert descriptions | |
TLS1_AD_DECRYPTION_FAILED = 21 | |
TLS1_AD_RECORD_OVERFLOW = 22 | |
TLS1_AD_UNKNOWN_CA = 48 # fatal | |
TLS1_AD_ACCESS_DENIED = 49 # fatal | |
TLS1_AD_DECODE_ERROR = 50 # fatal | |
TLS1_AD_DECRYPT_ERROR = 51 | |
TLS1_AD_EXPORT_RESTRICTION = 60 # fatal | |
TLS1_AD_PROTOCOL_VERSION = 70 # fatal | |
TLS1_AD_INSUFFICIENT_SECURITY = 71 # fatal | |
TLS1_AD_INTERNAL_ERROR = 80 # fatal | |
TLS1_AD_USER_CANCELLED = 90 | |
TLS1_AD_NO_RENEGOTIATION = 100 | |
#/* codes 110-114 are from RFC3546 */ | |
TLS1_AD_UNSUPPORTED_EXTENSION = 110 | |
TLS1_AD_CERTIFICATE_UNOBTAINABLE = 111 | |
TLS1_AD_UNRECOGNIZED_NAME = 112 | |
TLS1_AD_BAD_CERTIFICATE_STATUS_RESPONSE = 113 | |
TLS1_AD_BAD_CERTIFICATE_HASH_VALUE = 114 | |
TLS1_AD_UNKNOWN_PSK_IDENTITY = 115 # fatal | |
# Mapping alert types to strings | |
alert_description_str = { | |
SSL3_AD_CLOSE_NOTIFY: 'SSL3_AD_CLOSE_NOTIFY', | |
SSL3_AD_UNEXPECTED_MESSAGE: 'SSL3_AD_UNEXPECTED_MESSAGE', | |
SSL3_AD_BAD_RECORD_MAC: 'SSL3_AD_BAD_RECORD_MAC', | |
SSL3_AD_DECOMPRESSION_FAILURE: 'SSL3_AD_DECOMPRESSION_FAILURE', | |
SSL3_AD_HANDSHAKE_FAILURE: 'SSL3_AD_HANDSHAKE_FAILURE', | |
SSL3_AD_NO_CERTIFICATE: 'SSL3_AD_NO_CERTIFICATE', | |
SSL3_AD_BAD_CERTIFICATE: 'SSL3_AD_BAD_CERTIFICATE', | |
SSL3_AD_UNSUPPORTED_CERTIFICATE: 'SSL3_AD_UNSUPPORTED_CERTIFICATE', | |
SSL3_AD_CERTIFICATE_REVOKED: 'SSL3_AD_CERTIFICATE_REVOKED', | |
SSL3_AD_CERTIFICATE_EXPIRED: 'SSL3_AD_CERTIFICATE_EXPIRED', | |
SSL3_AD_CERTIFICATE_UNKNOWN: 'SSL3_AD_CERTIFICATE_UNKNOWN', | |
SSL3_AD_ILLEGAL_PARAMETER: 'SSL3_AD_ILLEGAL_PARAMETER', | |
TLS1_AD_DECRYPTION_FAILED: 'TLS1_AD_DECRYPTION_FAILED', | |
TLS1_AD_RECORD_OVERFLOW: 'TLS1_AD_RECORD_OVERFLOW', | |
TLS1_AD_UNKNOWN_CA: 'TLS1_AD_UNKNOWN_CA', | |
TLS1_AD_ACCESS_DENIED: 'TLS1_AD_ACCESS_DENIED', | |
TLS1_AD_DECODE_ERROR: 'TLS1_AD_DECODE_ERROR', | |
TLS1_AD_DECRYPT_ERROR: 'TLS1_AD_DECRYPT_ERROR', | |
TLS1_AD_EXPORT_RESTRICTION: 'TLS1_AD_EXPORT_RESTRICTION', | |
TLS1_AD_PROTOCOL_VERSION: 'TLS1_AD_PROTOCOL_VERSION', | |
TLS1_AD_INSUFFICIENT_SECURITY: 'TLS1_AD_INSUFFICIENT_SECURITY', | |
TLS1_AD_INTERNAL_ERROR: 'TLS1_AD_INTERNAL_ERROR', | |
TLS1_AD_USER_CANCELLED: 'TLS1_AD_USER_CANCELLED', | |
TLS1_AD_NO_RENEGOTIATION: 'TLS1_AD_NO_RENEGOTIATION', | |
TLS1_AD_UNSUPPORTED_EXTENSION: 'TLS1_AD_UNSUPPORTED_EXTENSION', | |
TLS1_AD_CERTIFICATE_UNOBTAINABLE: 'TLS1_AD_CERTIFICATE_UNOBTAINABLE', | |
TLS1_AD_UNRECOGNIZED_NAME: 'TLS1_AD_UNRECOGNIZED_NAME', | |
TLS1_AD_BAD_CERTIFICATE_STATUS_RESPONSE: 'TLS1_AD_BAD_CERTIFICATE_STATUS_RESPONSE', | |
TLS1_AD_BAD_CERTIFICATE_HASH_VALUE: 'TLS1_AD_BAD_CERTIFICATE_HASH_VALUE', | |
TLS1_AD_UNKNOWN_PSK_IDENTITY: 'TLS1_AD_UNKNOWN_PSK_IDENTITY' | |
} | |
# Constants stolen from github.com/tintinweb/scapy-ssl_tls | |
TLS_VERSIONS = { 0x0002:"SSL_2_0", | |
0x0300:"SSL_3_0", | |
0x0301:"TLS_1_0", | |
0x0302:"TLS_1_1", | |
0x0303:"TLS_1_2", | |
0x0100:"PROTOCOL_DTLS_1_0_OPENSSL_PRE_0_9_8f", | |
0xfeff:"DTLS_1_0", | |
0xfefd:"DTLS_1_1", | |
} | |
TLS_CONTENT_TYPES = {0x14:"change_cipher_spec", | |
0x15:"alert", | |
0x16:"handshake", | |
0x17:"application_data", | |
0x18:"heartbeat", | |
0xff:"unknown"} | |
TLS_HANDSHAKE_TYPES = {0x00:"hello_request", | |
0x01:"client_hello", | |
0x02:"server_hello", | |
0x0b:"certificate", | |
0x0c:"server_key_exchange", | |
0x0d:"certificate_request", | |
0x0e:"server_hello_done", | |
0x0f:"certificate_verify", | |
0x10:"client_key_exchange", | |
0x20:"finished", | |
0x21:"certificate_url", | |
0x22:"certificate_stats", | |
0xff:"unknown"} | |
TLS_EXTENSION_TYPES = { | |
0x0000:"server_name", | |
0x0001:"max_fragment_length", | |
0x0002:"client_certificate_url", | |
0x0003:"trusted_ca_keys", | |
0x0004:"truncated_hmac", | |
0x0005:"status_request", | |
0x000a:"elliptic_curves", | |
0x000b:"ec_point_formats", | |
0x000d:"signature_algorithms", | |
0x000f:"heartbeat", | |
0x0023:"session_ticket_tls", | |
0x3374:"next_protocol_negotiation", | |
0xff01:"renegotiationg_info", | |
} | |
TLS_ALERT_LEVELS = { 0x01: "warning", | |
0x02: "fatal", | |
0xff: "unknown", } | |
TLS_ALERT_DESCRIPTIONS = { | |
0:"CLOSE_NOTIFY", | |
10:"UNEXPECTE_MESSAGE", | |
20:"BAD_RECORD_MAC", | |
21:"DESCRIPTION_FAILED_RESERVED", | |
22:"RECORD_OVERFLOW", | |
30:"DECOMPRESSION_FAILURE", | |
40:"HANDSHAKE_FAILURE", | |
41:"NO_CERTIFICATE_RESERVED", | |
43:"BAD_CERTIFICATE", | |
43:"UNSUPPORTED_CERTIFICATE", | |
44:"CERTIFICATE_REVOKED", | |
45:"CERTIFICATE_EXPIRED", | |
46:"CERTIFICATE_UNKNOWN", | |
47:"ILLEGAL_PARAMETER", | |
48:"UNKNOWN_CA", | |
49:"ACCESS_DENIED", | |
50:"DECODE_ERROR", | |
51:"DECRYPT_ERROR", | |
60:"EXPORT_RESTRICTION_RESERVED", | |
70:"PROTOCOL_VERSION", | |
71:"INSUFFICIENT_SECURITY", | |
86:"INAPPROPRIATE_FALLBACK", | |
80:"INTERNAL_ERROR", | |
90:"USER_CANCELED", | |
100:"NO_RENEGOTIATION", | |
110:"UNSUPPORTED_EXTENSION", | |
111:"CERTIFICATE_UNOBTAINABLE", | |
112:"UNRECOGNIZED_NAME", | |
113:"BAD_CERTIFICATE_STATUS_RESPNSE", | |
114:"BAD_CERTIFICATE_HASH_VALUE", | |
255:"UNKNOWN_255", } | |
TLS_EXT_MAX_FRAGMENT_LENGTH_ENUM = { | |
0x01: 2 ** 9, | |
0x02: 2 ** 10, | |
0x03: 2 ** 11, | |
0x04: 2 ** 12, | |
0xff: 'unknown', | |
} | |
class TLSCipherSuite: | |
''' | |
make ciphersuites available as class props (autocompletion) | |
''' | |
NULL_WITH_NULL_NULL = 0x0000 | |
RSA_WITH_NULL_MD5 = 0x0001 | |
RSA_WITH_NULL_SHA1 = 0x0002 | |
RSA_WITH_NULL_SHA256 = 0x003b | |
RSA_WITH_3DES_EDE_CBC_SHA = 0x000a | |
DHE_RSA_WITH_3DES_EDE_CBC_SHA = 0x0016 | |
DHE_DSS_WITH_3DES_EDE_CBC_SHA = 0x0013 | |
RSA_WITH_3DES_EDE_CBC_SHA = 0x000a | |
DHE_RSA_WITH_AES_128_CBC_SHA = 0x0033 | |
DHE_DSS_WITH_AES_128_CBC_SHA = 0x0032 | |
RSA_WITH_AES_128_CBC_SHA = 0x002f | |
RSA_WITH_IDEA_CBC_SHA = 0x0007 | |
DHE_DSS_WITH_RC4_128_SHA = 0x0066 | |
RSA_WITH_RC4_128_SHA = 0x0005 | |
RSA_WITH_RC4_128_MD5 = 0x0004 | |
DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA = 0x0063 | |
RSA_EXPORT1024_WITH_DES_CBC_SHA = 0x0062 | |
RSA_EXPORT1024_WITH_RC2_CBC_56_MD5 = 0x0061 | |
DHE_RSA_WITH_DES_CBC_SHA = 0x0015 | |
DHE_DSS_WITH_DES_CBC_SHA = 0x0012 | |
RSA_WITH_DES_CBC_SHA = 0x0009 | |
DHE_DSS_EXPORT1024_WITH_RC4_56_SHA = 0x0065 | |
RSA_EXPORT1024_WITH_RC4_56_SHA = 0x0064 | |
RSA_EXPORT1024_WITH_RC4_56_MD5 = 0x0060 | |
DHE_RSA_EXPORT_WITH_DES40_CBC_SHA = 0x0014 | |
DHE_DSS_EXPORT_WITH_DES40_CBC_SHA = 0x0011 | |
RSA_EXPORT_WITH_DES40_CBC_SHA = 0x0008 | |
RSA_EXPORT_WITH_RC2_CBC_40_MD5 = 0x0006 | |
RSA_EXPORT_WITH_RC4_40_MD5 = 0x0003 | |
RSA_WITH_AES_256_CBC_SHA = 0x0035 | |
DHE_DSS_WITH_AES_256_CBC_SHA = 0x0038 | |
DHE_RSA_WITH_AES_256_CBC_SHA = 0x0039 | |
ECDHE_ECDSA_WITH_AES_256_CBC_SHA = 0xc00a | |
ECDH_RSA_WITH_AES_256_CBC_SHA = 0xc00f | |
ECDHE_RSA_WITH_AES_256_CBC_SHA = 0xc014 | |
SRP_SHA_RSA_WITH_AES_256_CBC_SHA = 0xc021 | |
SRP_SHA_DSS_WITH_AES_256_CBC_SHA = 0xc022 | |
DHE_DSS_WITH_CAMELLIA_256_CBC_SHA = 0x0087 | |
DHE_RSA_WITH_CAMELLIA_256_CBC_SHA = 0x0088 | |
ECDH_ECDSA_WITH_AES_256_CBC_SHA = 0xc005 | |
RSA_WITH_CAMELLIA_256_CBC_SHA = 0x0084 | |
TLS_FALLBACK_SCSV = 0x5600 | |
TLS_RENEGOTIATION_SCSV = 0x00ff | |
TLS_CIPHER_SUITES = dict((v, k) for k, v in TLSCipherSuite.__dict__.items() if not k.startswith("__")) | |
# struct format strings for parsing buffer lengths | |
# don't forget, you have to pad a 3-byte value with \x00 | |
_SIZE_FORMATS = ['!B', '!H', '!I', '!I'] | |
# Helper | |
def find_key(d, s): | |
for k in d: | |
if d[k] == s: | |
return k | |
return False | |
# A class to wrap a binary string into a readable buffer | |
# The buffer is consumed progressively and directly converted | |
class CleverBuffer: | |
def __init__(self, buf): | |
self._buf = buf | |
self._orig = buf | |
def getGeneric(self, l, t): | |
i = struct.unpack(t, self._buf[0:l])[0] | |
self._buf = self._buf[l:] | |
return i | |
def getByte(self): | |
return self.getGeneric(1, "!B") | |
def getInt(self): | |
return self.getGeneric(2, "!H") | |
def getLong(self): | |
return self.getGeneric(4, "!I") | |
def getString(self, l): | |
s = self._buf[:l] | |
self._buf = self._buf[l:] | |
return s | |
def get3Int(self): | |
b = '\x00' + self._buf[:3] | |
self._buf = self._buf[3:] | |
return struct.unpack("!I", b)[0] | |
def getRemainder(self): | |
return self._buf | |
# Parser functions : | |
""" | |
SSLv3 Header : | |
Byte 0 : SSL record type | |
Byte 1-2 : SSL version | |
Byte 3-4 : Length of data | |
""" | |
def SSL3Header(buf): | |
b = CleverBuffer(buf) | |
header = {} | |
header["record type"] = TLS_CONTENT_TYPES[b.getByte()] | |
header["version"] = TLS_VERSIONS[b.getInt()] | |
header["length"] = b.getInt() | |
if header["record type"] == "handshake": | |
header["content"] = SSL3Handshake(b.getRemainder()) | |
elif header["record type"] == "application_data": | |
header["content"] = SSL3ApplicationData(b.getRemainder()) | |
return header | |
""" | |
SSLv3 Handshake | |
Byte 0 : Content type | |
Byte 1-3 : Length | |
""" | |
def SSL3Handshake(buf): | |
b = CleverBuffer(buf) | |
header = {} | |
header["content type"] = TLS_HANDSHAKE_TYPES[b.getByte()] | |
header["length"] = b.get3Int() | |
if header["content type"] == "client_hello": | |
header["content"] = SSL3HandhsakeClientHello(b.getRemainder()) | |
return header | |
""" | |
SSLv3 Handshake Client Hello | |
Byte 0-1 : SSL version | |
Byte 2-5 : GMT Unix Time | |
Byte 6-33: Random bytes | |
Byte 34 : SessionID Length | |
Byte 34-n: SessionID | |
Byte n-m : Cipher Suites | |
Byte m-o : Compression Methods | |
""" | |
def SSL3HandhsakeClientHello(buf): | |
b = CleverBuffer(buf) | |
header = {} | |
header["ssl version"] = TLS_VERSIONS[b.getInt()] | |
header["gmt unix time"] = b.getLong() | |
header["random bytes"] = b.getString(28) | |
header["session id length"] = b.getByte() | |
header["session id"] = b.getString(header["session id length"]) | |
header["cipher suites length"] = b.getInt() | |
header["cipher suites"] = [] | |
for i in range(header["cipher suites length"] / 2): | |
header["cipher suites"].append(TLS_CIPHER_SUITES[b.getInt()]) | |
header["compression methods length"] = b.getByte() | |
header["compression methods"] = [] | |
for i in range(header["compression methods length"]): | |
header["compression methods"].append(b.getByte()) | |
return header | |
""" | |
SSLv3 Application Data | |
Byte n : Encrypted Application Data | |
""" | |
def SSL3ApplicationData(buf): | |
return {"encrypted application data":buf} | |
# Packet builder | |
def SSL3BuildClientHello(headers): | |
acc = "" | |
contentlength = 0 | |
handshakelength = 0 | |
data = headers | |
# Header | |
acc = "\x16\x03\x00" # Insert length afterwards | |
data = data["content"] | |
# Handshake | |
acc += "\x01" # insert length afterwards | |
contentlength += 4 | |
data = data["content"] | |
# Client Hello | |
tmp = "\x03\x00" + struct.pack("!I", data["gmt unix time"]) + data["random bytes"] + struct.pack("!B", data["session id length"]) + data["session id"] | |
contentlength += len(tmp) | |
handshakelength += len(tmp) | |
acc += tmp | |
for i in data["cipher suites"]: | |
acc += struct.pack("!H", find_key(TLS_CIPHER_SUITES, i)) | |
contentlength += 2 | |
handshakelength += 2 | |
acc += "\x01\x00" | |
contentlength += 2 | |
handshakelength += 2 | |
# Insert lengths | |
acc = acc[0:3] + struct.pack("!H", contentlength) + acc[3:] | |
acc = acc[0:6] + "\x00" + struct.pack("!H", handshakelength) + acc[6:] # struct doesn't handle 3 byte integers - hopefully, the handshake won't be too long ^^ | |
return acc | |
# Block copying | |
def POODLEreplace(buf, blockstart, blocksize): | |
tmp = buf | |
block = tmp[blockstart:blockstart+blocksize] | |
return tmp[0:(len(tmp) - blocksize)] + block | |
if __name__=="__main__": | |
d = SSL3Header("\x16\x03\x00\x00\x33\x01\x00\x00\x2f\x03\x00\x54\xad\x03\x0a\xf7\x04\x05\xfc\xd2\x14\x5a\x6e\x44\xd3\xe8\x71\xcb\xa2\x9e\xf6\x89\x24\xf4\xfe\xc4\x62\x9a\x84\x8e\xe5\x58\x7f\x00\x00\x08\x00\x05\x00\x0a\x00\x13\x00\x04\x01\x00") | |
s = SSL3BuildClientHello(d) | |
acc = "" | |
for i in s: | |
acc += hex(ord(i)) + " " | |
print(acc) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment