1 /* packet-snort.c
2  *
3  * Copyright 2011, Jakub Zawadzki <darkjames-ws@darkjames.pl>
4  * Copyright 2016, Martin Mathieson
5  *
6  * Google Summer of Code 2011 for The Honeynet Project
7  * Mentors:
8  *    Guillaume Arcas <guillaume.arcas (at) retiaire.org>
9  *    Jeff Nathan <jeffnathan (at) gmail.com>
10  *
11  * Wireshark - Network traffic analyzer
12  * By Gerald Combs <gerald@wireshark.org>
13  * Copyright 1998 Gerald Combs
14  *
15  * SPDX-License-Identifier: GPL-2.0-or-later
16  */
17 
18 
19 /* TODO:
20  * - sort out threading/channel-sync so works reliably in tshark
21  *    - postponed for now, as Qt crashes if call g_main_context_iteration()
22  *      at an inopportune time
23  * - have looked into writing a tap that could provide an interface for error messages/events and snort stats,
24  *   but not easy as taps are not usually listening when alerts are detected
25  * - for a content/pcre match, find all protocol fields that cover same bytes and show in tree
26  * - other use-cases as suggested in https://sharkfesteurope.wireshark.org/assets/presentations16eu/14.pptx
27  */
28 
29 
30 #include "config.h"
31 
32 #include <errno.h>
33 #include <ctype.h>
34 
35 #include <epan/epan.h>
36 #include <epan/proto.h>
37 #include <epan/packet.h>
38 #include <epan/prefs.h>
39 #include <epan/expert.h>
40 #include <epan/wmem_scopes.h>
41 #include <wsutil/file_util.h>
42 #include <wsutil/report_message.h>
43 #include <wiretap/wtap-int.h>
44 
45 #include "packet-snort-config.h"
46 
47 /* Forward declarations */
48 void proto_register_snort(void);
49 void proto_reg_handoff_snort(void);
50 
51 
52 static int proto_snort = -1;
53 
54 /* These are from parsing snort fast_alert output and/or looking up snort config */
55 static int hf_snort_raw_alert = -1;
56 static int hf_snort_classification = -1;
57 static int hf_snort_rule = -1;
58 static int hf_snort_msg = -1;
59 static int hf_snort_rev = -1;
60 static int hf_snort_sid = -1;
61 static int hf_snort_generator = -1;
62 static int hf_snort_priority = -1;
63 static int hf_snort_rule_string = -1;
64 static int hf_snort_rule_protocol = -1;
65 static int hf_snort_rule_filename = -1;
66 static int hf_snort_rule_line_number = -1;
67 static int hf_snort_rule_ip_var = -1;
68 static int hf_snort_rule_port_var = -1;
69 
70 static int hf_snort_reassembled_in = -1;
71 static int hf_snort_reassembled_from = -1;
72 
73 /* Patterns to match */
74 static int hf_snort_content = -1;
75 static int hf_snort_uricontent = -1;
76 static int hf_snort_pcre = -1;
77 
78 /* Web links */
79 static int hf_snort_reference = -1;
80 
81 /* General stats about the rule set */
82 static int hf_snort_global_stats = -1;
83 static int hf_snort_global_stats_rule_file_count = -1;     /* number of rules files */
84 static int hf_snort_global_stats_rule_count = -1;          /* number of rules in config */
85 
86 static int hf_snort_global_stats_total_alerts_count = -1;
87 static int hf_snort_global_stats_alert_match_number = -1;
88 
89 static int hf_snort_global_stats_rule_alerts_count = -1;
90 static int hf_snort_global_stats_rule_match_number = -1;
91 
92 
93 /* Subtrees */
94 static int ett_snort = -1;
95 static int ett_snort_rule = -1;
96 static int ett_snort_global_stats = -1;
97 
98 /* Expert info */
99 static expert_field ei_snort_alert = EI_INIT;
100 static expert_field ei_snort_content_not_matched = EI_INIT;
101 
102 static dissector_handle_t snort_handle;
103 
104 
105 /*****************************************/
106 /* Preferences                           */
107 
108 /* Where to look for alerts. */
109 enum alerts_source {
110     FromNowhere,      /* disabled */
111     FromRunningSnort,
112     FromUserComments  /* see https://blog.packet-foo.com/2015/08/verifying-iocs-with-snort-and-tracewrangler/ */
113 };
114 /* By default, dissector is effectively disabled */
115 static gint pref_snort_alerts_source = (gint)FromNowhere;
116 
117 /* Snort binary and config file */
118 #ifndef _WIN32
119 static const char *pref_snort_binary_filename = "/usr/sbin/snort";
120 static const char *pref_snort_config_filename = "/etc/snort/snort.conf";
121 #else
122 /* Default locations from Snort Windows installer */
123 static const char *pref_snort_binary_filename = "C:\\Snort\\bin\\snort.exe";
124 static const char *pref_snort_config_filename = "C:\\Snort\\etc\\snort.conf";
125 #endif
126 
127 /* Should rule stats be shown in protocol tree? */
128 static gboolean snort_show_rule_stats = FALSE;
129 
130 /* Should alerts be added as expert info? */
131 static gboolean snort_show_alert_expert_info = FALSE;
132 
133 /* Should we try to attach the alert to the tcp.reassembled_in frame instead of current one? */
134 static gboolean snort_alert_in_reassembled_frame = FALSE;
135 
136 /* Should Snort ignore checksum errors (as will likely be seen because of check offloading or
137  * possibly if trying to capture live in a container)? */
138 static gboolean snort_ignore_checksum_errors = TRUE;
139 
140 
141 /********************************************************/
142 /* Global variable with single parsed snort config      */
143 static SnortConfig_t *g_snort_config = NULL;
144 
145 
146 /******************************************************/
147 /* This is to keep track of the running Snort process */
148 typedef struct {
149     gboolean running;
150     gboolean working;
151 
152     GPid pid;
153     int in, out, err;   /* fds for talking to snort process */
154 
155     GString *buf;       /* Incomplete alert output that has been read */
156     wtap_dumper *pdh;   /* wiretap dumper used to deliver packets to 'in' */
157 
158     GIOChannel *channel; /* IO channel used for readimg stdout (alerts) */
159 
160     wmem_tree_t *alerts_tree;  /* Lookup from frame-number -> Alerts_t* */
161 } snort_session_t;
162 
163 /* Global instance of the snort session */
164 static snort_session_t current_session;
165 
166 static int snort_config_ok = TRUE;   /* N.B. Not running test at the moment... */
167 
168 
169 
170 /*************************************************/
171 /* An alert.
172    Created by parsing alert from snort, hopefully with more details linked from matched_rule. */
173 typedef struct Alert_t {
174     /* Rule */
175     guint32       sid;             /* Rule identifier */
176     guint32       rev;             /* Revision number of rule */
177     guint32       gen;             /* Which engine generated alert (not often interesting) */
178     int           prio;            /* Priority as reported in alert (not usually interesting) */
179 
180     char       *raw_alert;         /* The whole alert string as reported by snort */
181     gboolean   raw_alert_ts_fixed; /* Set when correct timestamp is restored before displaying */
182 
183     char       *msg;               /* Rule msg/description as it appears in the alert */
184     char       *classification;    /* Classification type of rule */
185 
186     Rule_t     *matched_rule;      /* Link to corresponding rule from snort config */
187 
188     guint32    original_frame;
189     guint32    reassembled_frame;
190 
191     /* Stats for this alert among the capture file. */
192     unsigned int overall_match_number;
193     unsigned int rule_match_number;
194 } Alert_t;
195 
196 /* Can have multiple alerts fire on same frame, so define this container */
197 typedef struct Alerts_t {
198 /* N.B. Snort limit appears to be 6 (at least with default config..) */
199 #define MAX_ALERTS_PER_FRAME 8
200     Alert_t alerts[MAX_ALERTS_PER_FRAME];
201     guint num_alerts;
202 } Alerts_t;
203 
204 
205 /* Add an alert to the map stored in current_session.
206  * N.B. even if preference 'snort_alert_in_reassembled_frame' is set,
207  * need to set to original frame now, and try to update it in the 2nd pass... */
add_alert_to_session_tree(guint frame_number,Alert_t * alert)208 static void add_alert_to_session_tree(guint frame_number, Alert_t *alert)
209 {
210     /* First look up tree to see if there is an existing entry */
211     Alerts_t *alerts = (Alerts_t*)wmem_tree_lookup32(current_session.alerts_tree, frame_number);
212     if (alerts == NULL) {
213         /* Create a new entry for the table */
214         alerts = g_new(Alerts_t, 1);
215         /* Deep copy of alert */
216         alerts->alerts[0] = *alert;
217         alerts->num_alerts = 1;
218         wmem_tree_insert32(current_session.alerts_tree, frame_number, alerts);
219     }
220     else {
221         /* See if there is room in the existing Alerts_t struct for this frame */
222         if (alerts->num_alerts < MAX_ALERTS_PER_FRAME) {
223             /* Deep copy of alert */
224             alerts->alerts[alerts->num_alerts++] = *alert;
225         }
226     }
227 }
228 
229 
230 /******************************************************************/
231 
232 /* Given an alert struct, look up by Snort ID (sid) and try to fill in other details to display. */
fill_alert_config(SnortConfig_t * snort_config,Alert_t * alert)233 static void fill_alert_config(SnortConfig_t *snort_config, Alert_t *alert)
234 {
235     guint global_match_number=0, rule_match_number=0;
236 
237     /* Look up rule by sid */
238     alert->matched_rule = get_rule(snort_config, alert->sid);
239 
240     /* Classtype usually filled in from alert rather than rule, but missing for supsported
241        comment format. */
242     if (pref_snort_alerts_source == FromUserComments) {
243         alert->classification = g_strdup(alert->matched_rule->classtype);
244     }
245 
246     /* Inform the config/rule about the alert */
247     rule_set_alert(snort_config, alert->matched_rule,
248                    &global_match_number, &rule_match_number);
249 
250     /* Copy updated counts into the alert */
251     alert->overall_match_number = global_match_number;
252     alert->rule_match_number = rule_match_number;
253 }
254 
255 
256 /* Helper functions for matching expected bytes against the packet buffer.
257   Case-sensitive comparison - can just memcmp().
258   Case-insensitive comparison - need to look at each byte and compare uppercase version */
content_compare_case_sensitive(const guint8 * memory,const char * target,guint length)259 static gboolean content_compare_case_sensitive(const guint8* memory, const char* target, guint length)
260 {
261     return (memcmp(memory, target, length) == 0);
262 }
263 
content_compare_case_insensitive(const guint8 * memory,const char * target,guint length)264 static gboolean content_compare_case_insensitive(const guint8* memory, const char* target, guint length)
265 {
266     for (guint n=0; n < length; n++) {
267         if (g_ascii_isalpha(target[n])) {
268             if (g_ascii_toupper(memory[n]) != g_ascii_toupper(target[n])) {
269                 return FALSE;
270             }
271         }
272         else {
273            if ((guint8)memory[n] != (guint8)target[n]) {
274                 return FALSE;
275             }
276         }
277     }
278 
279     return TRUE;
280 }
281 
282 /* Move through the bytes of the tvbuff, looking for a match against the
283  * regexp from the given content.
284  */
look_for_pcre(content_t * content,tvbuff_t * tvb,guint start_offset,guint * match_offset,guint * match_length)285 static gboolean look_for_pcre(content_t *content, tvbuff_t *tvb, guint start_offset, guint *match_offset, guint *match_length)
286 {
287     /* Create a regex object for the pcre in the content. */
288     GRegex *regex;
289     GMatchInfo *match_info;
290     gboolean match_found = FALSE;
291     GRegexCompileFlags regex_compile_flags = (GRegexCompileFlags)0;
292 
293     /* Make sure pcre string is ready for regex library. */
294     if (!content_convert_pcre_for_regex(content)) {
295         return FALSE;
296     }
297 
298     /* Copy remaining bytes into NULL-terminated string. Unfortunately, this interface does't allow
299        us to find patterns that involve bytes with value 0.. */
300     int length_remaining = tvb_captured_length_remaining(tvb, start_offset);
301     gchar *string = (gchar*)g_malloc(length_remaining + 1);
302     tvb_memcpy(tvb, (void*)string, start_offset, length_remaining);
303     string[length_remaining] = '\0';
304 
305     /* For pcre, translated_str already has / /[modifiers] removed.. */
306 
307     /* Apply any set modifier flags */
308     if (content->pcre_case_insensitive) {
309         regex_compile_flags = (GRegexCompileFlags)(regex_compile_flags | G_REGEX_CASELESS);
310     }
311     if (content->pcre_dot_includes_newline) {
312         regex_compile_flags = (GRegexCompileFlags)(regex_compile_flags | G_REGEX_DOTALL);
313     }
314     if (content->pcre_raw) {
315         regex_compile_flags = (GRegexCompileFlags)(regex_compile_flags | G_REGEX_RAW);
316     }
317     if (content->pcre_multiline) {
318         regex_compile_flags = (GRegexCompileFlags)(regex_compile_flags | G_REGEX_MULTILINE);
319     }
320 
321     /* Create regex */
322     regex = g_regex_new(content->translated_str,
323                         regex_compile_flags,
324                         (GRegexMatchFlags)0, NULL);
325 
326     /* Lookup PCRE match */
327     g_regex_match(regex, string, (GRegexMatchFlags)0, &match_info);
328     /* Only first match needed */
329     /* TODO: need to restart at any NULL before the final end? */
330     if (g_match_info_matches(match_info)) {
331         gint start_pos, end_pos;
332 
333         /* Find out where the match is */
334         g_match_info_fetch_pos(match_info,
335                                0, /* match_num */
336                                &start_pos, &end_pos);
337 
338         *match_offset = start_offset + start_pos;
339         *match_length = end_pos - start_pos;
340         match_found = TRUE;
341     }
342 
343     g_match_info_free(match_info);
344     g_regex_unref(regex);
345     g_free(string);
346 
347     return match_found;
348 }
349 
350 /* Move through the bytes of the tvbuff, looking for a match against the expanded
351    binary contents of this content object.
352  */
look_for_content(content_t * content,tvbuff_t * tvb,guint start_offset,guint * match_offset,guint * match_length)353 static gboolean look_for_content(content_t *content, tvbuff_t *tvb, guint start_offset, guint *match_offset, guint *match_length)
354 {
355     gint tvb_len = tvb_captured_length(tvb);
356 
357     /* Make sure content has been translated into binary string. */
358     guint converted_content_length = content_convert_to_binary(content);
359 
360     /* Look for a match at each position. */
361     for (guint m=start_offset; m <= (tvb_len-converted_content_length); m++) {
362         const guint8 *ptr = tvb_get_ptr(tvb, m, converted_content_length);
363         if (content->nocase) {
364             if (content_compare_case_insensitive(ptr, content->translated_str, content->translated_length)) {
365                 *match_offset = m;
366                 *match_length = content->translated_length;
367                 return TRUE;
368             }
369         }
370         else {
371             if (content_compare_case_sensitive(ptr, content->translated_str, content->translated_length)) {
372                 *match_offset = m;
373                 *match_length = content->translated_length;
374                 return TRUE;
375             }
376         }
377     }
378 
379     return FALSE;
380 }
381 
382 
383 
384 
385 /* Look for where the content match happens within the tvb.
386  * Set out parameters match_offset and match_length */
get_content_match(Alert_t * alert,guint content_idx,tvbuff_t * tvb,guint content_start_match,guint * match_offset,guint * match_length)387 static gboolean get_content_match(Alert_t *alert, guint content_idx,
388                                   tvbuff_t *tvb, guint content_start_match,
389                                   guint *match_offset, guint *match_length)
390 {
391     content_t *content;
392     Rule_t *rule = alert->matched_rule;
393 
394     /* Can't match if don't know rule */
395     if (rule == NULL) {
396         return FALSE;
397     }
398 
399     /* Get content object. */
400     content = &(rule->contents[content_idx]);
401 
402     /* Look for content match in the packet */
403     if (content->content_type == Pcre) {
404         return look_for_pcre(content, tvb, content_start_match, match_offset, match_length);
405     }
406     else {
407         return look_for_content(content, tvb, content_start_match, match_offset, match_length);
408     }
409 }
410 
411 
412 /* Gets called when snort process has died */
snort_reaper(GPid pid,gint status _U_,gpointer data)413 static void snort_reaper(GPid pid, gint status _U_, gpointer data)
414 {
415     snort_session_t *session = (snort_session_t *)data;
416     if (session->running && session->pid == pid) {
417         session->working = session->running = FALSE;
418         /* XXX, cleanup */
419     } else {
420         g_print("Errrrmm snort_reaper() %d != %d\n", session->pid, pid);
421     }
422 
423     /* Close the snort pid (may only make a difference on Windows?) */
424     g_spawn_close_pid(pid);
425 }
426 
427 /* Parse timestamp line of output.  This is done in part to get the packet_number back out of usec field...
428  * Return value is the input stream moved onto the next field following the timestamp */
snort_parse_ts(const char * ts,guint32 * frame_number)429 static const char* snort_parse_ts(const char *ts, guint32 *frame_number)
430 {
431     struct tm tm;
432     unsigned int usec;
433 
434     /* Timestamp */
435     memset(&tm, 0, sizeof(tm));
436     tm.tm_isdst = -1;
437     if (sscanf(ts, "%02d/%02d/%02d-%02d:%02d:%02d.%06u ",
438                &(tm.tm_mon), &(tm.tm_mday), &(tm.tm_year), &(tm.tm_hour), &(tm.tm_min), &(tm.tm_sec), &usec) != 7) {
439         return NULL;
440     }
441     tm.tm_mon -= 1;
442     tm.tm_year += 100;
443 
444     /* Store frame number (which was passed into this position when packet was submitted to snort) */
445     *frame_number = usec;
446 
447     return strchr(ts, ' ');
448 }
449 
450 /* Parse a fast output alert string */
snort_parse_fast_line(const char * line,Alert_t * alert)451 static gboolean snort_parse_fast_line(const char *line, Alert_t *alert)
452 {
453     static const char stars[] = " [**] ";
454 
455     static const char classification[] = "[Classification: ";
456     static const char priority[] = "[Priority: ";
457     const char *tmp_msg;
458 
459     /* Look for timestamp/frame-number */
460     if (!(line = snort_parse_ts(line, &(alert->original_frame)))) {
461         return FALSE;
462     }
463 
464     /* [**] */
465     if (!g_str_has_prefix(line+1, stars)) {
466         return FALSE;
467     }
468     line += sizeof(stars);
469 
470     /* [%u:%u:%u] */
471     if (sscanf(line, "[%u:%u:%u] ", &(alert->gen), &(alert->sid), &(alert->rev)) != 3) {
472         return FALSE;
473     }
474     if (!(line = strchr(line, ' '))) {
475         return FALSE;
476     }
477 
478     /* [**] again */
479     tmp_msg = line+1;
480     if (!(line = strstr(line, stars))) {
481         return FALSE;
482     }
483 
484     /* msg */
485     alert->msg = g_strndup(tmp_msg, line - tmp_msg);
486     line += (sizeof(stars)-1);
487 
488     /* [Classification: Attempted Administrator Privilege Gain] [Priority: 10] */
489 
490     if (g_str_has_prefix(line, classification)) {
491         /* [Classification: %s] */
492         char *tmp;
493         line += (sizeof(classification)-1);
494 
495         if (!(tmp = (char*)strstr(line, "] [Priority: "))) {
496             return FALSE;
497         }
498 
499         /* assume "] [Priority: " is not inside classification text :) */
500         alert->classification = g_strndup(line, tmp - line);
501 
502         line = tmp+2;
503     } else
504         alert->classification = NULL;
505 
506     /* Optimized: if al->classification we already checked this in strstr() above */
507     if (alert->classification || g_str_has_prefix(line, priority)) {
508         /* [Priority: %d] */
509         line += (sizeof(priority)-1);
510 
511         if ((sscanf(line, "%d", &(alert->prio))) != 1) {
512             return FALSE;
513         }
514 
515         if (!strstr(line, "] ")) {
516             return FALSE;
517         }
518     } else {
519         alert->prio = -1; /* XXX */
520     }
521 
522     return TRUE;
523 }
524 
525 /**
526  * snort_parse_user_comment()
527  *
528  * Parse line as written by TraceWranger
529  * e.g. "1:2011768:4 - ET WEB_SERVER PHP tags in HTTP POST"
530  */
snort_parse_user_comment(const char * line,Alert_t * alert)531 static gboolean snort_parse_user_comment(const char *line, Alert_t *alert)
532 {
533     /* %u:%u:%u */
534     if (sscanf(line, "%u:%u:%u", &(alert->gen), &(alert->sid), &(alert->rev)) != 3) {
535         return FALSE;
536     }
537 
538     /* Skip separator between numbers and msg */
539     if (!(line = strstr(line, " - "))) {
540         return FALSE;
541     }
542 
543     /* Copy to be consistent with other use of Alert_t */
544     alert->msg = g_strdup(line);
545 
546     /* No need to set other fields as assume zero'd out before this call.. */
547     return TRUE;
548 }
549 
550 /* Output data has been received from snort.  Read from channel and look for whole alerts. */
snort_fast_output(GIOChannel * source,GIOCondition condition,gpointer data)551 static gboolean snort_fast_output(GIOChannel *source, GIOCondition condition, gpointer data)
552 {
553     snort_session_t *session = (snort_session_t *)data;
554 
555     /* Loop here until all available input read */
556     while (condition & G_IO_IN) {
557         GIOStatus status;
558         char _buf[1024];
559         gsize len = 0;
560 
561         char *old_buf = NULL;
562         char *buf = _buf;
563         char *line;
564 
565         /* Try to read snort output info _buf */
566         status = g_io_channel_read_chars(source, _buf, sizeof(_buf)-1, &len, NULL);
567         if (status != G_IO_STATUS_NORMAL) {
568             if (status == G_IO_STATUS_AGAIN) {
569                 /* Blocked, so unset G_IO_IN and get out of this function */
570                 condition = (GIOCondition)(condition & ~G_IO_IN);
571                 break;
572             }
573             /* Other conditions here could be G_IO_STATUS_ERROR, G_IO_STATUS_EOF */
574             return FALSE;
575         }
576         /* Terminate buffer */
577         buf[len] = '\0';
578 
579         /* If we previously had part of a line, append the new bit we just saw */
580         if (session->buf) {
581             g_string_append(session->buf, buf);
582             buf = old_buf = g_string_free(session->buf, FALSE);
583             session->buf = NULL;
584         }
585 
586         /* Extract every complete line we find in the output */
587         while ((line = strchr(buf, '\n'))) {
588             /* Have a whole line, so can parse */
589             Alert_t alert;
590             memset(&alert, 0, sizeof(alert));
591 
592             /* Terminate received line */
593             *line = '\0';
594 
595             if (snort_parse_fast_line(buf, &alert)) {
596                 /*******************************************************/
597                 /* We have an alert line.                              */
598 #if 0
599                 g_print("%ld.%lu [%u,%u,%u] %s {%s} [%d]\n",
600                         alert.tv.tv_sec, alert.tv.tv_usec,
601                         alert.gen, alert.sid, alert.rev,
602                         alert.msg,
603                         alert.classification ? alert.classification : "(null)",
604                         alert.prio);
605 #endif
606 
607                 /* Copy the raw alert string itself */
608                 alert.raw_alert = g_strdup(buf);
609 
610                 /* See if we can get more info from the parsed config details */
611                 fill_alert_config(g_snort_config, &alert);
612 
613                 /* Add parsed alert into session->tree */
614                 /* Store in tree. Frame number hidden in fraction of second field, so associate
615                    alert with that frame. */
616                 add_alert_to_session_tree((guint)alert.original_frame, &alert);
617             }
618             else {
619                 g_print("snort_fast_output() line: '%s'\n", buf);
620             }
621 
622             buf = line+1;
623         }
624 
625         if (buf[0]) {
626             /* Only had part of a line - store it */
627             /* N.B. typically happens maybe once every 5-6 alerts. */
628             session->buf = g_string_new(buf);
629         }
630 
631         g_free(old_buf);
632     }
633 
634     if ((condition == G_IO_ERR) || (condition == G_IO_HUP) || (condition == G_IO_NVAL)) {
635         /* Will report errors (hung-up, or error) */
636 
637         /* g_print("snort_fast_output() cond: (h:%d,e:%d,r:%d)\n",
638          *         !!(condition & G_IO_HUP), !!(condition & G_IO_ERR), condition); */
639         return FALSE;
640     }
641 
642     return TRUE;
643 }
644 
645 
646 /* Return the offset in the frame where snort should begin looking inside payload. */
get_protocol_payload_start(const char * protocol,proto_tree * tree)647 static guint get_protocol_payload_start(const char *protocol, proto_tree *tree)
648 {
649     guint value = 0;
650 
651     /* For icmp, look from start, whereas for others start after them. */
652     gboolean look_after_protocol = (strcmp(protocol, "icmp") != 0);
653 
654     if (tree != NULL) {
655         GPtrArray *items = proto_all_finfos(tree);
656         if (items) {
657             guint i;
658             for (i=0; i< items->len; i++) {
659                 field_info *field = (field_info *)g_ptr_array_index(items,i);
660                 if (strcmp(field->hfinfo->abbrev, protocol) == 0) {
661                     value = field->start;
662                     if (look_after_protocol) {
663                         value += field->length;
664                     }
665                     break;
666                 }
667             }
668             g_ptr_array_free(items,TRUE);
669         }
670     }
671     return value;
672 }
673 
674 
675 /* Return offset that application layer traffic will begin from. */
get_content_start_match(Rule_t * rule,proto_tree * tree)676 static guint get_content_start_match(Rule_t *rule, proto_tree *tree)
677 {
678     /* Work out where snort would start looking for data in the frame */
679     return get_protocol_payload_start(rule->protocol, tree);
680 }
681 
682 /* Where this frame is later part of a reassembled complete PDU running over TCP, look up
683    and return that frame number. */
get_reassembled_in_frame(proto_tree * tree)684 static guint get_reassembled_in_frame(proto_tree *tree)
685 {
686     guint value = 0;
687 
688     if (tree != NULL) {
689         GPtrArray *items = proto_all_finfos(tree);
690         if (items) {
691             guint i;
692             for (i=0; i< items->len; i++) {
693                 field_info *field = (field_info *)g_ptr_array_index(items,i);
694                 if (strcmp(field->hfinfo->abbrev, "tcp.reassembled_in") == 0) {
695                     value = field->value.value.uinteger;
696                     break;
697                 }
698             }
699             g_ptr_array_free(items,TRUE);
700         }
701     }
702     return value;
703 }
704 
705 
706 /* Show the Snort protocol tree based on the info in alert */
snort_show_alert(proto_tree * tree,tvbuff_t * tvb,packet_info * pinfo,Alert_t * alert)707 static void snort_show_alert(proto_tree *tree, tvbuff_t *tvb, packet_info *pinfo, Alert_t *alert)
708 {
709     proto_tree *snort_tree = NULL;
710     guint n;
711     proto_item *ti, *rule_ti;
712     proto_tree *rule_tree;
713     Rule_t *rule = alert->matched_rule;
714 
715     /* May need to move to reassembled frame to show there instead of here */
716 
717     if (snort_alert_in_reassembled_frame && pinfo->fd->visited && (tree != NULL)) {
718         guint reassembled_frame = get_reassembled_in_frame(tree);
719 
720         if (reassembled_frame && (reassembled_frame != pinfo->num)) {
721             Alerts_t *alerts;
722 
723             /* Look up alerts for this frame */
724             alerts = (Alerts_t*)wmem_tree_lookup32(current_session.alerts_tree, pinfo->num);
725 
726             if (!alerts->alerts[0].reassembled_frame) {
727                 /* Update all alerts from this frame! */
728                 for (n=0; n < alerts->num_alerts; n++) {
729 
730                     /* Set forward/back frame numbers */
731                     alerts->alerts[n].original_frame = pinfo->num;
732                     alerts->alerts[n].reassembled_frame = reassembled_frame;
733 
734                     /* Add these alerts to reassembled frame */
735                     add_alert_to_session_tree(reassembled_frame, &alerts->alerts[n]);
736                 }
737             }
738         }
739     }
740 
741     /* Can only find start if we have the rule and know the protocol */
742     guint content_start_match = 0;
743     guint payload_start = 0;
744     if (rule) {
745         payload_start = content_start_match = get_content_start_match(rule, tree);
746     }
747 
748     /* Snort output arrived and was previously stored - so add to tree */
749     /* Take care not to try to highlight bytes that aren't there.. */
750     proto_item *alert_ti = proto_tree_add_protocol_format(tree, proto_snort, tvb,
751                                                           content_start_match >= tvb_captured_length(tvb) ? 0 : content_start_match,
752                                                           content_start_match >= tvb_captured_length(tvb) ? 0 : -1,
753                                                           "Snort: (msg: \"%s\" sid: %u rev: %u) [from %s]",
754                                                           alert->msg, alert->sid, alert->rev,
755                                                           (pref_snort_alerts_source == FromUserComments) ?
756                                                               "User Comment" :
757                                                               "Running Snort");
758     snort_tree = proto_item_add_subtree(alert_ti, ett_snort);
759 
760     if (snort_alert_in_reassembled_frame && (alert->reassembled_frame != 0)) {
761         if (alert->original_frame == pinfo->num) {
762             /* Show link forward to where alert is now shown! */
763             ti = proto_tree_add_uint(tree, hf_snort_reassembled_in, tvb, 0, 0,
764                                      alert->reassembled_frame);
765             proto_item_set_generated(ti);
766             return;
767         }
768         else {
769             tvbuff_t *reassembled_tvb;
770             /* Show link back to segment where alert was detected. */
771             ti = proto_tree_add_uint(tree, hf_snort_reassembled_from, tvb, 0, 0,
772                                      alert->original_frame);
773             proto_item_set_generated(ti);
774 
775             /* Should find this if look late enough.. */
776             reassembled_tvb = get_data_source_tvb_by_name(pinfo, "Reassembled TCP");
777             if (reassembled_tvb) {
778                 /* Will look for content using the TVB instead of just this frame's one */
779                 tvb = reassembled_tvb;
780             }
781             /* TODO: for correctness, would be good to lookup + remember the offset of the source
782              * frame within the reassembled PDU frame, to make sure we find the content in the
783              * correct place for every alert */
784         }
785     }
786 
787     snort_debug_printf("Showing alert (sid=%u) in frame %u\n", alert->sid, pinfo->num);
788 
789     /* Show in expert info if configured to. */
790     if (snort_show_alert_expert_info) {
791         expert_add_info_format(pinfo, alert_ti, &ei_snort_alert, "Alert %u: \"%s\"", alert->sid, alert->msg);
792     }
793 
794     /* Show the 'raw' alert string. */
795     if (rule) {
796         /* Fix up alert->raw_alert if not already done so first. */
797         if (!alert->raw_alert_ts_fixed) {
798             /* Write 6 figures to position after decimal place in timestamp. Must have managed to
799                parse out fields already, so will definitely be long enough for memcpy() to succeed. */
800             char digits[7];
801             g_snprintf(digits, 7, "%06u", pinfo->abs_ts.nsecs / 1000);
802             memcpy(alert->raw_alert+18, digits, 6);
803             alert->raw_alert_ts_fixed = TRUE;
804         }
805         ti = proto_tree_add_string(snort_tree, hf_snort_raw_alert, tvb, 0, 0, alert->raw_alert);
806         proto_item_set_generated(ti);
807     }
808 
809     /* Rule classification */
810     if (alert->classification) {
811         ti = proto_tree_add_string(snort_tree, hf_snort_classification, tvb, 0, 0, alert->classification);
812         proto_item_set_generated(ti);
813     }
814 
815     /* Put rule fields under a rule subtree */
816 
817     rule_ti = proto_tree_add_string_format(snort_tree, hf_snort_rule, tvb, 0, 0, "", "Rule");
818     proto_item_set_generated(rule_ti);
819     rule_tree = proto_item_add_subtree(rule_ti, ett_snort_rule);
820 
821     /* msg/description */
822     ti = proto_tree_add_string(rule_tree, hf_snort_msg, tvb, 0, 0, alert->msg);
823     proto_item_set_generated(ti);
824     /* Snort ID */
825     ti = proto_tree_add_uint(rule_tree, hf_snort_sid, tvb, 0, 0, alert->sid);
826     proto_item_set_generated(ti);
827     /* Rule revision */
828     ti = proto_tree_add_uint(rule_tree, hf_snort_rev, tvb, 0, 0, alert->rev);
829     proto_item_set_generated(ti);
830     /* Generator seems to correspond to gid. */
831     ti = proto_tree_add_uint(rule_tree, hf_snort_generator, tvb, 0, 0, alert->gen);
832     proto_item_set_generated(ti);
833     /* Default priority is 2 - very few rules have a different priority... */
834     ti = proto_tree_add_uint(rule_tree, hf_snort_priority, tvb, 0, 0, alert->prio);
835     proto_item_set_generated(ti);
836 
837     /* If we know the rule for this alert, show some of the rule fields */
838     if (rule && rule->rule_string) {
839         size_t rule_string_length = strlen(rule->rule_string);
840 
841         /* Show rule string itself. Add it as a separate data source so can read it all */
842         if (rule_string_length > 60) {
843             tvbuff_t *rule_string_tvb = tvb_new_child_real_data(tvb, rule->rule_string,
844                                                                 (guint)rule_string_length,
845                                                                 (guint)rule_string_length);
846             add_new_data_source(pinfo, rule_string_tvb, "Rule String");
847             ti = proto_tree_add_string(rule_tree, hf_snort_rule_string, rule_string_tvb, 0,
848                                        (gint)rule_string_length,
849                                        rule->rule_string);
850         }
851         else {
852             ti = proto_tree_add_string(rule_tree, hf_snort_rule_string, tvb, 0, 0,
853                                        rule->rule_string);
854         }
855         proto_item_set_generated(ti);
856 
857         /* Protocol from rule */
858         ti = proto_tree_add_string(rule_tree, hf_snort_rule_protocol, tvb, 0, 0, rule->protocol);
859         proto_item_set_generated(ti);
860 
861         /* Show file alert came from */
862         ti = proto_tree_add_string(rule_tree, hf_snort_rule_filename, tvb, 0, 0, rule->file);
863         proto_item_set_generated(ti);
864         /* Line number within file */
865         ti = proto_tree_add_uint(rule_tree, hf_snort_rule_line_number, tvb, 0, 0, rule->line_number);
866         proto_item_set_generated(ti);
867 
868         /* Show IP vars */
869         for (n=0; n < rule->relevant_vars.num_ip_vars; n++) {
870             ti = proto_tree_add_none_format(rule_tree, hf_snort_rule_ip_var, tvb, 0, 0, "IP Var: ($%s -> %s)",
871                                             rule->relevant_vars.ip_vars[n].name,
872                                             rule->relevant_vars.ip_vars[n].value);
873             proto_item_set_generated(ti);
874         }
875         /* Show Port vars */
876         for (n=0; n < rule->relevant_vars.num_port_vars; n++) {
877             ti = proto_tree_add_none_format(rule_tree, hf_snort_rule_port_var, tvb, 0, 0, "Port Var: ($%s -> %s)",
878                                             rule->relevant_vars.port_vars[n].name,
879                                             rule->relevant_vars.port_vars[n].value);
880             proto_item_set_generated(ti);
881         }
882     }
883 
884 
885     /* Show summary information in rule tree root */
886     proto_item_append_text(rule_ti, " %s (sid=%u, rev=%u)",
887                            alert->msg, alert->sid, alert->rev);
888 
889     /* More fields retrieved from the parsed config */
890     if (rule) {
891         guint content_last_match_end = 0;
892 
893         /* Work out which ip and port vars are relevant */
894         rule_set_relevant_vars(g_snort_config, rule);
895 
896         /* Contents */
897         for (n=0; n < rule->number_contents; n++) {
898 
899             /* Search for string among tvb contents so we can highlight likely bytes. */
900             unsigned int content_offset = 0;
901             gboolean match_found = FALSE;
902             unsigned int converted_content_length = 0;
903             int content_hf_item;
904             char *content_text_template;
905 
906             /* Choose type of content field to add */
907             switch (rule->contents[n].content_type) {
908                 case Content:
909                     content_hf_item = hf_snort_content;
910                     content_text_template = "Content: \"%s\"";
911                     break;
912                 case UriContent:
913                     content_hf_item = hf_snort_uricontent;
914                     content_text_template = "Uricontent: \"%s\"";
915                     break;
916                 case Pcre:
917                     content_hf_item = hf_snort_pcre;
918                     content_text_template = "Pcre: \"%s\"";
919                     break;
920                 default:
921                     continue;
922             }
923 
924             /* Will only try to look for content in packet ourselves if not
925                a negated content entry (i.e. beginning with '!') */
926             if (!rule->contents[n].negation) {
927                 /* Look up offset of match. N.B. would only expect to see on first content... */
928                 guint distance_to_add = 0;
929 
930                 /* May need to start looking from absolute offset into packet... */
931                 if (rule->contents[n].offset_set) {
932                     content_start_match = payload_start + rule->contents[n].offset;
933                 }
934                 /* ... or a number of bytes beyond the previous content match */
935                 else if (rule->contents[n].distance_set) {
936                     distance_to_add = (content_last_match_end-content_start_match) + rule->contents[n].distance;
937                 }
938                 else {
939                     /* No constraints about where it appears - go back to the start of the frame. */
940                     content_start_match = payload_start;
941                 }
942 
943 
944                 /* Now actually look for match from calculated position */
945                 /* TODO: could take 'depth' and 'within' into account to limit extent of search,
946                    but OK if just trying to verify what Snort already found. */
947                 match_found = get_content_match(alert, n,
948                                                 tvb, content_start_match+distance_to_add,
949                                                 &content_offset, &converted_content_length);
950                 if (match_found) {
951                     content_last_match_end = content_offset + converted_content_length;
952                 }
953             }
954 
955 
956             /* Show content in tree (showing position if known) */
957             ti = proto_tree_add_string_format(snort_tree, content_hf_item, tvb,
958                                               (match_found) ? content_offset : 0,
959                                               (match_found) ? converted_content_length : 0,
960                                               rule->contents[n].str,
961                                               content_text_template,
962                                               rule->contents[n].str);
963 
964             /* Next match position will be after this one */
965             if (match_found) {
966                 content_start_match = content_last_match_end;
967             }
968 
969             /* Show (only as text) attributes of content field */
970             if (rule->contents[n].fastpattern) {
971                 proto_item_append_text(ti, " (fast_pattern)");
972             }
973             if (rule->contents[n].rawbytes) {
974                 proto_item_append_text(ti, " (rawbytes)");
975             }
976             if (rule->contents[n].nocase) {
977                 proto_item_append_text(ti, " (nocase)");
978             }
979             if (rule->contents[n].negation) {
980                 proto_item_append_text(ti, " (negated)");
981             }
982             if (rule->contents[n].offset_set) {
983                 proto_item_append_text(ti, " (offset=%d)", rule->contents[n].offset);
984             }
985             if (rule->contents[n].depth != 0) {
986                 proto_item_append_text(ti, " (depth=%u)", rule->contents[n].depth);
987             }
988             if (rule->contents[n].distance_set) {
989                 proto_item_append_text(ti, " (distance=%d)", rule->contents[n].distance);
990             }
991             if (rule->contents[n].within != 0) {
992                 proto_item_append_text(ti, " (within=%u)", rule->contents[n].within);
993             }
994 
995             /* HTTP preprocessor modifiers */
996             if (rule->contents[n].http_method != 0) {
997                 proto_item_append_text(ti, " (http_method)");
998             }
999             if (rule->contents[n].http_client_body != 0) {
1000                 proto_item_append_text(ti, " (http_client_body)");
1001             }
1002             if (rule->contents[n].http_cookie != 0) {
1003                 proto_item_append_text(ti, " (http_cookie)");
1004             }
1005             if (rule->contents[n].http_user_agent != 0) {
1006                 proto_item_append_text(ti, " (http_user_agent)");
1007             }
1008 
1009             if (!rule->contents[n].negation && !match_found) {
1010                 /* Useful for debugging, may also happen when Snort is reassembling.. */
1011                 /* TODO: not sure why, but PCREs might not be found first time through, but will be
1012                  * found later, with the result that there will be 'not located' expert warnings,
1013                  * but when you click on the packet, it is matched after all... */
1014                 proto_item_append_text(ti, " - not located");
1015                 expert_add_info_format(pinfo, ti, &ei_snort_content_not_matched,
1016                                        "%s   \"%s\"   not found in frame",
1017                                        rule->contents[n].content_type==Pcre ? "PCRE" : "Content",
1018                                        rule->contents[n].str);
1019             }
1020         }
1021 
1022         /* References */
1023         for (n=0; n < rule->number_references; n++) {
1024             /* Substitute prefix and add to tree as clickable web links */
1025             ti = proto_tree_add_string(snort_tree, hf_snort_reference, tvb, 0, 0,
1026                                        expand_reference(g_snort_config, rule->references[n]));
1027             /* Make clickable */
1028             proto_item_set_url(ti);
1029             proto_item_set_generated(ti);
1030         }
1031     }
1032 
1033     /* Global rule stats if configured to. */
1034     if (snort_show_rule_stats) {
1035         unsigned int number_rule_files, number_rules, alerts_detected, this_rule_alerts_detected;
1036         proto_item *stats_ti;
1037         proto_tree *stats_tree;
1038 
1039         /* Create tree for these items */
1040         stats_ti = proto_tree_add_string_format(snort_tree, hf_snort_global_stats, tvb, 0, 0, "", "Global Stats");
1041         proto_item_set_generated(rule_ti);
1042         stats_tree = proto_item_add_subtree(stats_ti, ett_snort_global_stats);
1043 
1044         /* Get overall number of rules */
1045         get_global_rule_stats(g_snort_config, alert->sid, &number_rule_files, &number_rules, &alerts_detected,
1046                               &this_rule_alerts_detected);
1047         ti = proto_tree_add_uint(stats_tree, hf_snort_global_stats_rule_file_count, tvb, 0, 0, number_rule_files);
1048         proto_item_set_generated(ti);
1049         ti = proto_tree_add_uint(stats_tree, hf_snort_global_stats_rule_count, tvb, 0, 0, number_rules);
1050         proto_item_set_generated(ti);
1051 
1052         /* Overall alert stats (total, and where this one comes in order) */
1053         ti = proto_tree_add_uint(stats_tree, hf_snort_global_stats_total_alerts_count, tvb, 0, 0, alerts_detected);
1054         proto_item_set_generated(ti);
1055         ti = proto_tree_add_uint(stats_tree, hf_snort_global_stats_alert_match_number, tvb, 0, 0, alert->overall_match_number);
1056         proto_item_set_generated(ti);
1057 
1058         if (rule) {
1059             /* Stats just for this rule (overall, and where this one comes in order) */
1060             ti = proto_tree_add_uint(stats_tree, hf_snort_global_stats_rule_alerts_count, tvb, 0, 0, this_rule_alerts_detected);
1061             proto_item_set_generated(ti);
1062             ti = proto_tree_add_uint(stats_tree, hf_snort_global_stats_rule_match_number, tvb, 0, 0, alert->rule_match_number);
1063             proto_item_set_generated(ti);
1064 
1065             /* Add a summary to the stats root */
1066             proto_item_append_text(stats_ti, " (%u rules from %u files, #%u of %u alerts seen (%u/%u for sid %u))",
1067                                    number_rules, number_rule_files, alert->overall_match_number, alerts_detected,
1068                                    alert->rule_match_number, this_rule_alerts_detected, alert->sid);
1069         }
1070         else {
1071             /* Add a summary to the stats root */
1072             proto_item_append_text(stats_ti, " (%u rules from %u files, #%u of %u alerts seen)",
1073                                    number_rules, number_rule_files, alert->overall_match_number, alerts_detected);
1074         }
1075     }
1076 }
1077 
1078 /* Look for, and return, any user comment set for this packet.
1079    Currently used for fetching alerts in the format TraceWrangler can write out to */
get_user_comment_string(proto_tree * tree)1080 static const char *get_user_comment_string(proto_tree *tree)
1081 {
1082     const char *value = NULL;
1083 
1084     if (tree != NULL) {
1085         GPtrArray *items = proto_all_finfos(tree);
1086         if (items) {
1087             guint i;
1088 
1089             for (i=0; i< items->len; i++) {
1090                 field_info *field = (field_info *)g_ptr_array_index(items,i);
1091                 if (strcmp(field->hfinfo->abbrev, "frame.comment") == 0) {
1092                     value = field->value.value.string;
1093                     break;
1094                 }
1095                 /* This is the only item that can come before "frame.comment", so otherwise break out */
1096                 if (strncmp(field->hfinfo->abbrev, "pkt_comment", 11) != 0) {
1097                     break;
1098                 }
1099             }
1100             g_ptr_array_free(items,TRUE);
1101         }
1102     }
1103     return value;
1104 }
1105 
1106 
1107 /********************************************************************************/
1108 /* Main (post-)dissector function.                                              */
1109 static int
snort_dissector(tvbuff_t * tvb,packet_info * pinfo,proto_tree * tree,void * data _U_)1110 snort_dissector(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
1111 {
1112     Alerts_t *alerts;
1113 
1114     /* If not looking for alerts, return quickly */
1115     if (pref_snort_alerts_source == FromNowhere) {
1116         return 0;
1117     }
1118 
1119     /* Are we looking for alerts in user comments? */
1120     else if (pref_snort_alerts_source == FromUserComments) {
1121         /* Look for user comments containing alerts */
1122         const char *alert_string = get_user_comment_string(tree);
1123         if (alert_string) {
1124             alerts = (Alerts_t*)wmem_tree_lookup32(current_session.alerts_tree, pinfo->num);
1125             if (!alerts) {
1126                 Alert_t alert;
1127                 memset(&alert, 0, sizeof(alert));
1128                 if (snort_parse_user_comment(alert_string, &alert)) {
1129                     /* Copy the raw alert itself */
1130                     alert.raw_alert = g_strdup(alert_string);
1131 
1132                     /* See if we can get more info from the parsed config details */
1133                     fill_alert_config(g_snort_config, &alert);
1134 
1135                     /* Add parsed alert into session->tree */
1136                     add_alert_to_session_tree(pinfo->num, &alert);
1137                 }
1138             }
1139         }
1140     }
1141     else {
1142         /* We expect alerts from Snort.  Pass frame into snort on first pass. */
1143         if (!pinfo->fd->visited && current_session.working) {
1144             int write_err = 0;
1145             gchar *err_info;
1146             wtap_rec rec;
1147 
1148             /* First time, open current_session.in to write to for dumping into snort with */
1149             if (!current_session.pdh) {
1150                 wtap_dump_params params = WTAP_DUMP_PARAMS_INIT;
1151                 int open_err;
1152                 gchar *open_err_info;
1153 
1154                 /* Older versions of Snort don't support capture file with several encapsulations (like pcapng),
1155                  * so write in pcap format and hope we have just one encap.
1156                  * Newer versions of Snort can read pcapng now, but still
1157                  * write in pcap format; if "newer versions of Snort" really
1158                  * means "Snort, when using newer versions of libpcap", then,
1159                  * yes, they can read pcapng, but they can't read pcapng
1160                  * files with more than one encapsulation type, as libpcap's
1161                  * API currently can't handle that, so even those "newer
1162                  * versions of Snort" wouldn't handle multiple encapsulation
1163                  * types.
1164                  */
1165                 params.encap = pinfo->rec->rec_header.packet_header.pkt_encap;
1166                 params.snaplen = WTAP_MAX_PACKET_SIZE_STANDARD;
1167                 current_session.pdh = wtap_dump_fdopen(current_session.in,
1168                                                        wtap_pcap_file_type_subtype(),
1169                                                        WTAP_UNCOMPRESSED,
1170                                                        &params,
1171                                                        &open_err,
1172                                                        &open_err_info);
1173                 if (!current_session.pdh) {
1174                     /* XXX - report the error somehow? */
1175                     g_free(open_err_info);
1176                     current_session.working = FALSE;
1177                     return 0;
1178                 }
1179             }
1180 
1181             /* Start with all same values... */
1182             rec = *pinfo->rec;
1183 
1184             /* Copying packet details into wtp for writing */
1185             rec.ts = pinfo->abs_ts;
1186 
1187             /* NB: overwriting the time stamp so we can see packet number back if an alert is written for this frame!!!! */
1188             /* TODO: does this seriously affect snort's ability to reason about time?
1189              * At least all packets will still be in order... */
1190             rec.ts.nsecs = pinfo->fd->num * 1000;    /* XXX, max 999'999 frames */
1191 
1192             rec.rec_header.packet_header.caplen = tvb_captured_length(tvb);
1193             rec.rec_header.packet_header.len = tvb_reported_length(tvb);
1194             if (current_session.pdh->encap != rec.rec_header.packet_header.pkt_encap) {
1195                 /* XXX, warning! convert? */
1196             }
1197 
1198             /* Dump frame into snort's stdin */
1199             if (!wtap_dump(current_session.pdh, &rec, tvb_get_ptr(tvb, 0, tvb_reported_length(tvb)), &write_err, &err_info)) {
1200                 /* XXX - report the error somehow? */
1201                 g_free(err_info);
1202                 current_session.working = FALSE;
1203                 return 0;
1204             }
1205             if (!wtap_dump_flush(current_session.pdh, &write_err)) {
1206                 /* XXX - report the error somehow? */
1207                 current_session.working = FALSE;
1208                 return 0;
1209             }
1210 
1211             /* Give the io channel a chance to deliver alerts.
1212                TODO: g_main_context_iteration(NULL, FALSE); causes crashes sometimes when Qt events get to execute.. */
1213         }
1214     }
1215 
1216     /* Now look up stored alerts for this packet number, and display if found */
1217     if (current_session.alerts_tree && (alerts = (Alerts_t*)wmem_tree_lookup32(current_session.alerts_tree, pinfo->fd->num))) {
1218         guint n;
1219 
1220         for (n=0; n < alerts->num_alerts; n++) {
1221             snort_show_alert(tree, tvb, pinfo, &(alerts->alerts[n]));
1222         }
1223     } else {
1224         /* XXX, here either this frame doesn't generate alerts or we haven't received data from snort (async)
1225          *
1226          *      It's problem when user want to filter tree on initial run, or is running one-pass tshark.
1227          */
1228     }
1229 
1230     return tvb_reported_length(tvb);
1231 }
1232 
1233 
1234 /*------------------------------------------------------------------*/
1235 /* Start up Snort. */
snort_start(void)1236 static void snort_start(void)
1237 {
1238     GIOChannel *channel;
1239     /* int snort_output_id; */
1240     const gchar *argv[] = {
1241         pref_snort_binary_filename, "-c", pref_snort_config_filename,
1242         /* read from stdin */
1243         "-r", "-",
1244         /* don't log */
1245         "-N",
1246         /* output to console and silence snort */
1247         "-A", "console", "-q",
1248         /* normalize time */
1249         "-y", /* -U", */
1250         /* Optionally ignore checksum errors */
1251         "-k", "none",
1252         NULL
1253     };
1254 
1255     /* Truncate command to before -k if this pref off */
1256     if (!snort_ignore_checksum_errors) {
1257         argv[10] = NULL;
1258     }
1259 
1260     /* Enable field priming if required. */
1261     if (snort_alert_in_reassembled_frame) {
1262         /* Add items we want to try to get to find before we get called.
1263            For now, just ask for tcp.reassembled_in, which won't be seen
1264            on the first pass through the packets. */
1265         GArray *wanted_hfids = g_array_new(FALSE, FALSE, (guint)sizeof(int));
1266         int id = proto_registrar_get_id_byname("tcp.reassembled_in");
1267         g_array_append_val(wanted_hfids, id);
1268         set_postdissector_wanted_hfids(snort_handle, wanted_hfids);
1269     }
1270 
1271     /* Nothing to do if not enabled, but registered init function gets called anyway */
1272     if ((pref_snort_alerts_source == FromNowhere) ||
1273         !proto_is_protocol_enabled(find_protocol_by_id(proto_snort))) {
1274         return;
1275     }
1276 
1277     /* Create tree mapping packet_number -> Alerts_t*.  It will get recreated when packet list is reloaded */
1278     current_session.alerts_tree = wmem_tree_new_autoreset(wmem_epan_scope(), wmem_file_scope());
1279 
1280     /* Create afresh the config object by parsing the same file that snort uses */
1281     if (g_snort_config) {
1282         delete_config(&g_snort_config);
1283     }
1284     create_config(&g_snort_config, pref_snort_config_filename);
1285 
1286     /* Don't run Snort if not configured to */
1287     if (pref_snort_alerts_source == FromUserComments) {
1288         return;
1289     }
1290 
1291     /* Don't start if already running */
1292     if (current_session.running) {
1293         return;
1294     }
1295 
1296     /* Reset global stats */
1297     reset_global_rule_stats(g_snort_config);
1298 
1299     /* Need to test that we can run snort --version and that config can be parsed... */
1300     /* Does nothing at present */
1301     if (!snort_config_ok) {
1302         /* Can carry on without snort... */
1303         return;
1304     }
1305 
1306     /* About to run snort, so check that configured files exist, and that binary could be executed. */
1307     ws_statb64 binary_stat, config_stat;
1308 
1309     if (ws_stat64(pref_snort_binary_filename, &binary_stat) != 0) {
1310         snort_debug_printf("Can't run snort - executable '%s' not found\n", pref_snort_binary_filename);
1311         report_failure("Snort dissector: Can't run snort - executable '%s' not found\n", pref_snort_binary_filename);
1312         return;
1313     }
1314 
1315     if (ws_stat64(pref_snort_config_filename, &config_stat) != 0) {
1316         snort_debug_printf("Can't run snort - config file '%s' not found\n", pref_snort_config_filename);
1317         report_failure("Snort dissector: Can't run snort - config file '%s' not found\n", pref_snort_config_filename);
1318         return;
1319     }
1320 
1321 #ifdef S_IXUSR
1322     if (!(binary_stat.st_mode & S_IXUSR)) {
1323         snort_debug_printf("Snort binary '%s' is not executable\n", pref_snort_binary_filename);
1324         report_failure("Snort dissector: Snort binary '%s' is not executable\n", pref_snort_binary_filename);
1325         return;
1326     }
1327 #endif
1328 
1329 #ifdef _WIN32
1330     report_failure("Snort dissector: not yet able to launch Snort process under Windows");
1331     current_session.working = FALSE;
1332     return;
1333 #endif
1334 
1335     /* Create snort process and set up pipes */
1336     snort_debug_printf("\nRunning %s with config file %s\n", pref_snort_binary_filename, pref_snort_config_filename);
1337     if (!g_spawn_async_with_pipes(NULL,          /* working_directory */
1338                                   (char **)argv,
1339                                   NULL,          /* envp */
1340                                   (GSpawnFlags)( G_SPAWN_DO_NOT_REAP_CHILD), /* Leave out G_SPAWN_SEARCH_PATH */
1341                                   NULL,                   /* child setup - not supported in Windows, so we can't use it */
1342                                   NULL,                   /* user-data */
1343                                   &current_session.pid,   /* PID */
1344                                   &current_session.in,    /* stdin */
1345                                   &current_session.out,   /* stdout */
1346                                   &current_session.err,   /* stderr */
1347                                   NULL))                  /* error */
1348     {
1349         current_session.running = FALSE;
1350         current_session.working = FALSE;
1351         return;
1352     }
1353     else {
1354         current_session.running = TRUE;
1355         current_session.working = TRUE;
1356     }
1357 
1358     /* Setup handler for when process goes away */
1359     g_child_watch_add(current_session.pid, snort_reaper, &current_session);
1360 
1361     /******************************************************************/
1362     /* Create channel to get notified of snort alert output on stdout */
1363 
1364     /* Create channel itself */
1365     channel = g_io_channel_unix_new(current_session.out);
1366     current_session.channel = channel;
1367 
1368     /* NULL encoding supports binary or whatever the application outputs */
1369     g_io_channel_set_encoding(channel, NULL, NULL);
1370     /* Don't buffer the channel (settable because encoding set to NULL). */
1371     g_io_channel_set_buffered(channel, FALSE);
1372     /* Set flags */
1373     /* TODO: could set to be blocking and get sync that way? */
1374     g_io_channel_set_flags(channel, G_IO_FLAG_NONBLOCK, NULL);
1375     /* Try setting a large buffer here. */
1376     g_io_channel_set_buffer_size(channel, 256000);
1377 
1378     current_session.buf = NULL;
1379 
1380     /* Set callback for receiving data from the channel */
1381     g_io_add_watch_full(channel,
1382                         G_PRIORITY_HIGH,
1383                         (GIOCondition)(G_IO_IN|G_IO_ERR|G_IO_HUP),
1384                         snort_fast_output,  /* Callback upon data being written by snort */
1385                         &current_session,   /* User data */
1386                         NULL);              /* Destroy notification callback */
1387 
1388     current_session.working = TRUE;
1389 }
1390 
1391 /* This is the cleanup routine registered with register_postseq_cleanup_routine() */
snort_cleanup(void)1392 static void snort_cleanup(void)
1393 {
1394     /* Only close if we think its running */
1395     if (!current_session.running) {
1396         return;
1397     }
1398 
1399     /* Close dumper writing into snort's stdin.  This will cause snort to exit! */
1400     if (current_session.pdh) {
1401         int write_err;
1402         gchar *write_err_info;
1403         if (!wtap_dump_close(current_session.pdh, &write_err, &write_err_info)) {
1404             /* XXX - somehow report the error? */
1405             g_free(write_err_info);
1406         }
1407         current_session.pdh = NULL;
1408     }
1409 }
1410 
snort_file_cleanup(void)1411 static void snort_file_cleanup(void)
1412 {
1413     if (g_snort_config) {
1414         delete_config(&g_snort_config);
1415     }
1416 
1417     /* Disable field priming that got enabled in the init routine. */
1418     set_postdissector_wanted_hfids(snort_handle, NULL);
1419 }
1420 
1421 void
proto_reg_handoff_snort(void)1422 proto_reg_handoff_snort(void)
1423 {
1424     /* N.B. snort self-test here deleted, as I was struggling to get it to
1425      * work as a non-root user (couldn't read stdin)
1426      * TODO: could run snort just to get the version number and check the config file is readable?
1427      * TODO: could make snort config parsing less forgiving and use that as a test? */
1428 }
1429 
1430 void
proto_register_snort(void)1431 proto_register_snort(void)
1432 {
1433     static hf_register_info hf[] = {
1434         { &hf_snort_sid,
1435             { "Rule SID", "snort.sid", FT_UINT32, BASE_DEC, NULL, 0x00,
1436             "Snort Rule identifier", HFILL }},
1437         { &hf_snort_raw_alert,
1438             { "Raw Alert", "snort.raw-alert", FT_STRING, BASE_NONE, NULL, 0x00,
1439             "Full text of Snort alert", HFILL }},
1440         { &hf_snort_rule,
1441             { "Rule", "snort.rule", FT_STRING, BASE_NONE, NULL, 0x00,
1442             "Entire Snort rule string", HFILL }},
1443         { &hf_snort_msg,
1444             { "Alert Message", "snort.msg", FT_STRINGZ, BASE_NONE, NULL, 0x00,
1445             "Description of what the rule detects", HFILL }},
1446         { &hf_snort_classification,
1447             { "Alert Classification", "snort.class", FT_STRINGZ, BASE_NONE, NULL, 0x00,
1448             NULL, HFILL }},
1449         { &hf_snort_priority,
1450             { "Alert Priority", "snort.priority", FT_UINT32, BASE_DEC, NULL, 0x00,
1451             NULL, HFILL }},
1452         { &hf_snort_generator,
1453             { "Rule Generator", "snort.generator", FT_UINT32, BASE_DEC, NULL, 0x00,
1454             NULL, HFILL }},
1455         { &hf_snort_rev,
1456             { "Rule Revision", "snort.rev", FT_UINT32, BASE_DEC, NULL, 0x00,
1457             NULL, HFILL }},
1458         { &hf_snort_rule_string,
1459             { "Rule String", "snort.rule-string", FT_STRINGZ, BASE_NONE, NULL, 0x00,
1460             "Full text of Snort rule", HFILL }},
1461         { &hf_snort_rule_protocol,
1462             { "Protocol", "snort.protocol", FT_STRINGZ, BASE_NONE, NULL, 0x00,
1463             "Protocol name as given in the rule", HFILL }},
1464         { &hf_snort_rule_filename,
1465             { "Rule Filename", "snort.rule-filename", FT_STRINGZ, BASE_NONE, NULL, 0x00,
1466             "Rules file where Snort rule was parsed from", HFILL }},
1467         { &hf_snort_rule_line_number,
1468             { "Line number within rules file where rule was parsed from", "snort.rule-line-number", FT_UINT32, BASE_DEC, NULL, 0x00,
1469             NULL, HFILL }},
1470         { &hf_snort_rule_ip_var,
1471             { "IP variable", "snort.rule-ip-var", FT_NONE, BASE_NONE, NULL, 0x00,
1472             "IP variable used in rule", HFILL }},
1473         { &hf_snort_rule_port_var,
1474             { "Port variable used in rule", "snort.rule-port-var", FT_NONE, BASE_NONE, NULL, 0x00,
1475             NULL, HFILL }},
1476         { &hf_snort_reassembled_in,
1477             { "Reassembled frame where alert is shown", "snort.reassembled_in", FT_FRAMENUM, BASE_NONE, NULL, 0x00,
1478             NULL, HFILL }},
1479         { &hf_snort_reassembled_from,
1480             { "Segment where alert was triggered", "snort.reassembled_from", FT_FRAMENUM, BASE_NONE, NULL, 0x00,
1481             NULL, HFILL }},
1482         { &hf_snort_content,
1483             { "Content", "snort.content", FT_STRINGZ, BASE_NONE, NULL, 0x00,
1484             "Snort content field", HFILL }},
1485         { &hf_snort_uricontent,
1486             { "URI Content", "snort.uricontent", FT_STRINGZ, BASE_NONE, NULL, 0x00,
1487             "Snort URI content field", HFILL }},
1488         { &hf_snort_pcre,
1489             { "PCRE", "snort.pcre", FT_STRINGZ, BASE_NONE, NULL, 0x00,
1490             "Perl Compatible Regular Expression", HFILL }},
1491         { &hf_snort_reference,
1492             { "Reference", "snort.reference", FT_STRINGZ, BASE_NONE, NULL, 0x00,
1493             "Web reference provided as part of rule", HFILL }},
1494 
1495         /* Global stats */
1496         { &hf_snort_global_stats,
1497             { "Global Stats", "snort.global-stats", FT_STRING, BASE_NONE, NULL, 0x00,
1498             "Global statistics for rules and alerts", HFILL }},
1499         { &hf_snort_global_stats_rule_file_count,
1500             { "Number of rule files", "snort.global-stats.rule-file-count", FT_UINT32, BASE_DEC, NULL, 0x00,
1501             "Total number of rules files found in Snort config", HFILL }},
1502         { &hf_snort_global_stats_rule_count,
1503             { "Number of rules", "snort.global-stats.rule-count", FT_UINT32, BASE_DEC, NULL, 0x00,
1504             "Total number of rules found in Snort config", HFILL }},
1505         { &hf_snort_global_stats_total_alerts_count,
1506             { "Number of alerts detected", "snort.global-stats.total-alerts", FT_UINT32, BASE_DEC, NULL, 0x00,
1507             "Total number of alerts detected in this capture", HFILL }},
1508         { &hf_snort_global_stats_alert_match_number,
1509             { "Match number", "snort.global-stats.match-number", FT_UINT32, BASE_DEC, NULL, 0x00,
1510             "Number of match for this alert among all alerts", HFILL }},
1511 
1512         { &hf_snort_global_stats_rule_alerts_count,
1513             { "Number of alerts for this rule", "snort.global-stats.rule.alerts-count", FT_UINT32, BASE_DEC, NULL, 0x00,
1514             "Number of alerts detected for this rule", HFILL }},
1515         { &hf_snort_global_stats_rule_match_number,
1516             { "Match number for this rule", "snort.global-stats.rule.match-number", FT_UINT32, BASE_DEC, NULL, 0x00,
1517             "Number of match for this alert among those for this rule", HFILL }}
1518     };
1519     static gint *ett[] = {
1520         &ett_snort,
1521         &ett_snort_rule,
1522         &ett_snort_global_stats
1523     };
1524 
1525     static const enum_val_t alerts_source_vals[] = {
1526         {"from-nowhere",            "Not looking for Snort alerts",        FromNowhere},
1527         {"from-running-snort",      "From running Snort",                  FromRunningSnort},
1528         {"from-user-comments",      "From user packet comments",           FromUserComments},
1529         {NULL, NULL, -1}
1530     };
1531 
1532     static ei_register_info ei[] = {
1533         { &ei_snort_alert, { "snort.alert.expert", PI_SECURITY, PI_WARN, "Snort alert detected", EXPFILL }},
1534         { &ei_snort_content_not_matched, { "snort.content.not-matched", PI_PROTOCOL, PI_NOTE, "Failed to find content field of alert in frame", EXPFILL }},
1535     };
1536 
1537     expert_module_t* expert_snort;
1538 
1539     module_t *snort_module;
1540 
1541     proto_snort = proto_register_protocol("Snort Alerts", "Snort", "snort");
1542 
1543     proto_register_field_array(proto_snort, hf, array_length(hf));
1544     proto_register_subtree_array(ett, array_length(ett));
1545 
1546     /* Expert info */
1547     expert_snort = expert_register_protocol(proto_snort);
1548     expert_register_field_array(expert_snort, ei, array_length(ei));
1549 
1550     snort_module = prefs_register_protocol(proto_snort, NULL);
1551 
1552     prefs_register_obsolete_preference(snort_module, "enable_snort_dissector");
1553 
1554     prefs_register_enum_preference(snort_module, "alerts_source",
1555         "Source of Snort alerts",
1556         "Set whether dissector should run Snort and pass frames into it, or read alerts from user packet comments",
1557         &pref_snort_alerts_source, alerts_source_vals, FALSE);
1558 
1559     prefs_register_filename_preference(snort_module, "binary",
1560                                        "Snort binary",
1561                                        "The name of the snort binary file to run",
1562                                        &pref_snort_binary_filename, FALSE);
1563     prefs_register_filename_preference(snort_module, "config",
1564                                        "Configuration filename",
1565                                        "The name of the file containing the snort IDS configuration.  Typically snort.conf",
1566                                        &pref_snort_config_filename, FALSE);
1567 
1568     prefs_register_bool_preference(snort_module, "show_rule_set_stats",
1569                                    "Show rule stats in protocol tree",
1570                                    "Whether or not information about the rule set and detected alerts should "
1571                                    "be shown in the tree of every snort PDU tree",
1572                                    &snort_show_rule_stats);
1573     prefs_register_bool_preference(snort_module, "show_alert_expert_info",
1574                                    "Show alerts in expert info",
1575                                    "Whether or not expert info should be used to highlight fired alerts",
1576                                    &snort_show_alert_expert_info);
1577     prefs_register_bool_preference(snort_module, "show_alert_in_reassembled_frame",
1578                                    "Try to show alerts in reassembled frame",
1579                                    "Attempt to show alert in reassembled frame where possible.  Note that this won't work during live capture",
1580                                    &snort_alert_in_reassembled_frame);
1581     prefs_register_bool_preference(snort_module, "ignore_checksum_errors",
1582                                    "Tell Snort to ignore checksum errors",
1583                                    "When enabled, will run Snort with '-k none'",
1584                                    &snort_ignore_checksum_errors);
1585 
1586 
1587     snort_handle = create_dissector_handle(snort_dissector, proto_snort);
1588 
1589     register_init_routine(snort_start);
1590     register_postdissector(snort_handle);
1591 
1592     /* Callback to make sure we cleanup dumper being used to deliver packets to snort (this will tsnort). */
1593     register_postseq_cleanup_routine(snort_cleanup);
1594     /* Callback to allow us to delete snort config */
1595     register_cleanup_routine(snort_file_cleanup);
1596 }
1597 
1598 /*
1599  * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
1600  *
1601  * Local variables:
1602  * c-basic-offset: 4
1603  * tab-width: 8
1604  * indent-tabs-mode: nil
1605  * End:
1606  *
1607  * vi: set shiftwidth=4 tabstop=8 expandtab:
1608  * :indentSize=4:tabSize=8:noTabs=true:
1609  */
1610