From 4a0813044dc71eaed312242ee3aebc1f530f41cd Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Wed, 21 Jul 2021 13:51:16 +0200 Subject: tls-alerts.lua: Add listener to identify domains with unusual TLS alerts --- lua/tls-alerts.lua | 136 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 lua/tls-alerts.lua diff --git a/lua/tls-alerts.lua b/lua/tls-alerts.lua new file mode 100644 index 0000000..be79dc9 --- /dev/null +++ b/lua/tls-alerts.lua @@ -0,0 +1,136 @@ +-- +-- Wireshark listener to identify unusual TLS Alerts and associated domains. +-- Author: Peter Wu +-- +-- Load in Wireshark, then open the Tools -> TLS Alerts menu, or use tshark: +-- +-- $ tshark -q -Xlua_script:tls-alerts.lua -r some.pcapng +-- shavar.services.mozilla.com 1x Bad Certificate (42) +-- aus5.mozilla.org 3x Bad Certificate (42), 1x Unknown CA (48) +-- + +--local quic_stream = Field.new("quic.stream") +local tls_sni = Field.new("tls.handshake.extensions_server_name") +local tls_alert = Field.new("tls.alert_message.desc") + +-- Map from TCP stream -> SNI +local snis +-- Map from SNI -> (map of alerts -> counts) +local alerts + +local tw +local function reset_stats() + snis = {} + alerts = {} + if gui_enabled() then + tw:clear() + end +end + +local function tap_packet(pinfo, tvb, tcp_info) + local tcp_stream = tcp_info.th_stream + if not tcp_stream then + print('TCP stream somehow not found, is this QUIC? pkt=' .. pinfo.number) + return + end + + local f_sni = tls_sni() + if f_sni then + snis[tcp_stream] = f_sni.value + end + -- Ignore "Close Notify (0)" alerts since these are not unusual. + local f_alert = tls_alert() + if f_alert and f_alert.value ~= 0 then + local sni = snis[tcp_stream] or string.format("", tcp_stream) + -- Store counters for SNI -> Alerts mappings + local sni_alerts = alerts[sni] + if not alerts[sni] then + sni_alerts = {} + alerts[sni] = sni_alerts + end + local count = sni_alerts[f_alert.display] + if not count then + sni_alerts[f_alert.display] = 1 + else + sni_alerts[f_alert.display] = count + 1 + end + end +end + +local function round_to_multiple_of(val, multiple) + local rest = val % multiple + if rest == 0 then + return val + else + return val - rest + multiple + end +end + +local function output_all(callback, need_newline) + -- Align the domain to a multiple of four columns + local max_length = 16 + for sni in pairs(alerts) do + if #sni > max_length then + max_length = round_to_multiple_of(#sni + 1, 4) - 1 + end + end + local fmt = "%-" .. max_length .. "s %s" + if need_newline then fmt = fmt .. "\n" end + + for sni, alert_counts in pairs(alerts) do + table.sort(alert_counts, function(a, b) return a > b end) + local all_alerts + for alert, count in pairs(alert_counts) do + local sep = "" + local chunk = string.format("%dx %s", count, alert) + if all_alerts then + all_alerts = all_alerts .. ", " .. chunk + else + all_alerts = chunk + end + end + callback(string.format(fmt, sni, all_alerts)) + end +end + +-- Called periodically in the Wireshark GUI +local function gui_draw() + tw:clear() + output_all(function(text) + tw:append(text .. "\n") + end) +end + +-- Called at the end of tshark +local function cli_draw() + output_all(print) +end + +local function activate_tap() + -- Match TLS Client Hello with SNI extension or TLS alerts. + local tap = Listener.new("tcp", "(tls.handshake.type==1 and tls.handshake.extensions_server_name) or tls.alert_message") + + if gui_enabled() then + tw = TextWindow.new("TLS Alerts") + tw:set_atclose(function() + tap:remove() + tw = nil + end) + tap.draw = gui_draw + else + tap.draw = cli_draw + end + + tap.packet = tap_packet + tap.reset = reset_stats + reset_stats() + if gui_enabled() then + retap_packets() + end +end + +if gui_enabled() then + register_menu("TLS Alerts", activate_tap, MENU_TOOLS_UNSORTED) +else + activate_tap() +end -- cgit v1.2.1