summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Wu <peter@lekensteyn.nl>2017-07-03 19:38:42 +0200
committerPeter Wu <peter@lekensteyn.nl>2017-07-03 19:42:49 +0200
commit04cd18a57cdec3aee33c932fa431cb630905db08 (patch)
tree6e088bf8a35d44faad3bca57004099bce79d229e
parent7c8c5524212e438f0ae397fc58c5a93f7a88d69f (diff)
downloadwireshark-ssl-handshake-reassembly.tar.gz
[WIP] ssl: support fragmented Handshake messagesssl-handshake-reassembly
Track record fragments to support fragmented handshake messages. This commonly occurs with large Certificate messages spanning multiple TLS records in multiple TCP segments. TODO - this was changed halfway to not rely on hs_current_length/hs_total_length and instead use the fragments table. That does not work well for the second pass however. And without storing any form of state between records, we do not know whether it is a continuation or not. - Need to extend tests. Bug: 3303 Change-Id: I5e6d344063071646656c3a62bada773cd8f6910d
-rw-r--r--epan/dissectors/packet-ssl-utils.h7
-rw-r--r--epan/dissectors/packet-ssl.c220
2 files changed, 211 insertions, 16 deletions
diff --git a/epan/dissectors/packet-ssl-utils.h b/epan/dissectors/packet-ssl-utils.h
index 17e539d7f9..50490c40ee 100644
--- a/epan/dissectors/packet-ssl-utils.h
+++ b/epan/dissectors/packet-ssl-utils.h
@@ -421,6 +421,13 @@ typedef struct _SslSession {
dissector_handle_t app_handle;
guint32 last_nontls_frame;
gboolean is_session_resumed;
+
+ /* Tracks handshake reassembly during the first pass. */
+#if 0
+ guint32 hs_seqno;
+#endif
+ guint32 hs_total_length;
+ guint32 hs_current_length;
} SslSession;
/* RFC 5246, section 8.1 says that the master secret is always 48 bytes */
diff --git a/epan/dissectors/packet-ssl.c b/epan/dissectors/packet-ssl.c
index 8b1bad7765..389713da9c 100644
--- a/epan/dissectors/packet-ssl.c
+++ b/epan/dissectors/packet-ssl.c
@@ -343,6 +343,8 @@ void proto_reg_handoff_ssl(void);
/* Desegmentation of SSL streams */
/* table to hold defragmented SSL streams */
static reassembly_table ssl_reassembly_table;
+/* table to hold (reassembled) fragments for handshake messages */
+static reassembly_table ssl_handshake_reassembly_table;
/* initialize/reset per capture state data (ssl sessions cache) */
static void
@@ -2242,6 +2244,13 @@ process_ssl3_handshake(tvbuff_t *tvb, packet_info *pinfo,
* A single record can have multiple handshake messages and a single
* handshake message can span multiple records. This function uses a loop to
* handle the former and reassembly to handle the latter.
+ *
+ * Handshake reassembly uses a sequence number to identify a handshake msg.
+ * In the first pass, the sequence number of the first msg is set in the
+ * record.
+ *
+ * If we have a previous partial handshake message, assume plaintext.
+ * Otherwise we should check for encrypted messages that must be rejected.
*/
guint32 offset_end = offset + record_length;
const gchar *msg_type_str;
@@ -2249,6 +2258,38 @@ process_ssl3_handshake(tvbuff_t *tvb, packet_info *pinfo,
guint32 length;
gboolean is_first_message = TRUE;
gboolean is_continuation = FALSE;
+ guint32 hs_seqno;
+ guint32 msg_len;
+ fragment_head *fh;
+
+ /*
+ * TODO currently only one fragmented handshake message is supported per
+ * session which works if the only fragmented message is Certificate. But
+ * to support more cases, it should track the actual handshake msg seq no.
+ */
+ hs_seqno = GPOINTER_TO_UINT(session);
+
+ if (!PINFO_FD_VISITED(pinfo)) {
+ /*
+ * First pass: this record is a continuation if the previous handshake
+ * message was not fully seen.
+ */
+ is_continuation = session->hs_total_length && session->hs_current_length != session->hs_total_length;
+ } else {
+ fh = fragment_get_reassembled_id(&ssl_handshake_reassembly_table, pinfo, hs_seqno);
+ if (fh) {
+ /*
+ * Assume that the begin of this record is a continuation when a
+ * reassembled packet exists and when this reassembly was completed
+ * in this frame.
+ * TODO if there are multiple records per frame (possibly due to
+ * reassembled TCP segments), then it should also check the offset
+ * within the frame. Otherwise it might display the same handshake
+ * message multiple times. Perhaps by using reas_in_layer_num?
+ */
+ is_continuation = fh->reassembled_in != pinfo->num;
+ }
+ }
if (!is_continuation) {
/*
@@ -2260,6 +2301,7 @@ process_ssl3_handshake(tvbuff_t *tvb, packet_info *pinfo,
* encrypted contents for GCM/CCM cipher suites as used in TLS 1.2.
*/
if (maybe_encrypted) {
+ /* TODO this marks AEAD as encrypted, but also random IV as plaintext */
maybe_encrypted = tvb_bytes_exist(tvb, offset, 5) && tvb_get_ntoh40(tvb, offset) == 0;
}
if (maybe_encrypted) {
@@ -2283,33 +2325,177 @@ process_ssl3_handshake(tvbuff_t *tvb, packet_info *pinfo,
}
while (offset < offset_end) {
- msg_type = tvb_get_guint8(tvb, offset);
- length = tvb_get_ntoh24(tvb, offset + 1);
+ guint frag_len = offset_end - offset;
+ tvbuff_t *sub_tvb = NULL;
- /* Check the length in the handshake message. Assume it's an
- * encrypted handshake message if the message would pass
- * the record_length boundary. This is a workaround for the
- * situation where the first octet of the encrypted handshake
- * message is actually a known handshake message type.
+ /*
+ * 1. Read message length (if enough bytes are available).
*/
- if (!maybe_encrypted && offset + 4 + length <= offset_end)
+ if (!is_continuation) {
+ if (offset_end - offset >= 4) {
+ /* Not a continuation, length is available. */
+ length = tvb_get_ntoh24(tvb, offset + 1);
+ msg_len = 4 + length;
+ } else {
+ msg_len = 0; /* length unknown */
+ }
+
+ msg_type = tvb_get_guint8(tvb, offset);
msg_type_str = try_val_to_str(msg_type, ssl_31_handshake_type);
- else
- msg_type_str = NULL;
+ if (!msg_type_str) {
+ /*
+ * Unknown handshake message type, perhaps this is encrypted
+ * data or something else went wrong. Do not try to start
+ * reassembly and just stop here.
+ */
+ return;
+ }
+ } else {
+ if (!PINFO_FD_VISITED(pinfo)) {
+ /*
+ * Continuation, so a previous fragment *must* exist.
+ * This fragment head is also used in the next step.
+ */
+ fh = fragment_get(&ssl_handshake_reassembly_table, pinfo, hs_seqno, NULL);
+ DISSECTOR_ASSERT(fh);
- if (!msg_type_str) {
- /* only dissect further messages if the message type is valid. */
- return;
+ /*
+ * Keep adding fragments until the length can be extracted.
+ */
+ guint needed = 4;
+ tvbuff_t *len_tvb = tvb_new_composite();
+ /* Add head of fragment */
+ tvb_composite_append(len_tvb, fh->tvb_data);
+ needed -= MIN(needed, tvb_reported_length(fh->tvb_data));
+ /* Add remaining fragments. */
+ for (fragment_item *fd = fh->next; needed > 0 && fd; fd = fd->next) {
+ tvb_composite_append(len_tvb, fd->tvb_data);
+ needed -= MIN(needed, tvb_reported_length(fd->tvb_data));
+ }
+ /* Add data from current record. */
+ if (needed > 0) {
+ tvbuff_t *remaining_tvb = tvb_new_subset_remaining(tvb, offset);
+ tvb_composite_append(len_tvb, remaining_tvb);
+ needed -= MIN(needed, tvb_reported_length(remaining_tvb));
+ }
+ tvb_composite_finalize(len_tvb);
+
+ if (needed == 0) {
+ length = tvb_get_ntoh24(len_tvb, 1);
+ msg_len = 4 + length;
+ } else {
+ /* Not enough data to read the length. */
+ msg_len = 0; /* length unknown */
+ }
+ } else {
+ /*
+ * Second pass with continuation, if everything went OK we
+ * should have a reassembled message now.
+ */
+ fh = fragment_get_reassembled_id(&ssl_handshake_reassembly_table, pinfo, hs_seqno);
+ if (fh) {
+ msg_len = tvb_captured_length(fh->tvb_data);
+ } else {
+ /*
+ * Not enough fragments are received for reassembly.
+ */
+ msg_len = 0; /* length unknown */
+ }
+ }
+ }
+
+ /*
+ * 2. Consume the record data, using fragments reassembly if necessary.
+ * If all fragments are available, extract the handshake message TVB.
+ * Set the next state for the "is_continuation" flag.
+ */
+ if (!PINFO_FD_VISITED(pinfo)) {
+ /*
+ * Find the fragment offset (length of previous fragments, or 0 if
+ * this is not a continuation) and the fragment length (the subset
+ * of the current record data that must be added).
+ *
+ * If at this point the length is not yet known, the full record
+ * fragment must be added for reassembly.
+ */
+ guint frag_offset = 0;
+ if (msg_len != 0) {
+ if (!is_continuation) {
+ frag_len = MIN(frag_len, msg_len);
+ } else {
+ /* fh is already set when extracting the length. */
+#if 0
+ fh = fragment_get(&ssl_handshake_reassembly_table, pinfo, hs_seqno, NULL);
+ DISSECTOR_ASSERT(fh);
+#endif
+ frag_offset = fh->len;
+ for (fragment_item *fd = fh->next; fd; fd = fd->next) {
+ frag_offset += fd->len;
+ }
+ if (msg_len != 0) {
+ DISSECTOR_ASSERT(frag_offset < msg_len);
+ frag_len = MIN(frag_len, msg_len - frag_offset);
+ }
+ }
+ }
+
+ /*
+ * Either (1) continuation of previous message or (2) not enough
+ * data for length or (3) begin of new message. Store the current
+ * fragment for reassembly.
+ */
+ if (is_continuation || msg_len == 0 || frag_offset + frag_len < msg_len) {
+ is_continuation = msg_len == 0 || frag_offset + frag_len < msg_len;
+ fh = fragment_add_seq_next(&ssl_handshake_reassembly_table, tvb, offset,
+ pinfo, hs_seqno, NULL, frag_len, is_continuation);
+ if (fh) {
+ /* Fully reassembled. */
+ sub_tvb = fh->tvb_data;
+ }
+ } else {
+ /*
+ * This must be a handshake message which fully fits
+ * in this record and does not need reassembly.
+ */
+ DISSECTOR_ASSERT(!is_continuation);
+ sub_tvb = tvb_new_subset_length(tvb, offset, msg_len);
+ }
+ } else {
+ /*
+ * Second pass, so no need to add new fragments.
+ * XXX multiple fragments in one record are currently not handled
+ * well for second pass since we do not know where the fragment
+ * starts... So we do not know what frag_len should become in case
+ * of reassembly.
+ */
+ if (is_continuation) {
+ fh = fragment_get_reassembled_id(&ssl_handshake_reassembly_table, pinfo, hs_seqno);
+ is_continuation = fh && fh->reassembled_in != pinfo->num;
+ if (fh) {
+ /* Fully reassembled. */
+ sub_tvb = fh->tvb_data;
+ }
+ } else {
+ /*
+ * Not a continuation, but we can dissect only if the full
+ * message is received.
+ */
+ if (msg_len != 0 && msg_len <= offset_end - offset) {
+ sub_tvb = tvb_new_subset_length(tvb, offset, msg_len);
+ }
+ }
}
/*
* We have a valid message type and received all data, start the
* actual handshake dissection.
*/
- dissect_ssl3_handshake(tvb, pinfo, tree, offset, session,
- is_from_server, ssl, version, is_first_message);
+ if (sub_tvb) {
+ dissect_ssl3_handshake(sub_tvb, pinfo, tree, 0, session,
+ is_from_server, ssl, version, is_first_message);
+ }
- offset += 4 + length;
+ offset += frag_len;
is_first_message = FALSE; /* set up for next pass, if any */
}
}
@@ -4465,6 +4651,8 @@ proto_register_ssl(void)
register_cleanup_routine(ssl_cleanup);
reassembly_table_register(&ssl_reassembly_table,
&addresses_ports_reassembly_table_functions);
+ reassembly_table_register(&ssl_handshake_reassembly_table,
+ &addresses_ports_reassembly_table_functions);
register_decode_as(&ssl_da);
/* XXX: this seems unused due to new "Follow SSL" method, remove? */