diff options
-rw-r--r-- | epan/dissectors/packet-ssl-utils.h | 7 | ||||
-rw-r--r-- | epan/dissectors/packet-ssl.c | 220 |
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? */ |