Skip to content

Instantly share code, notes, and snippets.

@eXenon
Last active December 4, 2021 18:57
Show Gist options
  • Save eXenon/5421dd857a86ff1424be to your computer and use it in GitHub Desktop.
Save eXenon/5421dd857a86ff1424be to your computer and use it in GitHub Desktop.
Quick SSL Packet Parser / Builder
# 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