Coverage Report

Created: 2026-01-02 06:13

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