/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 | ¶ms, |
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 | ¤t_session.pid, /* PID */ |
1353 | 0 | ¤t_session.in, /* stdin */ |
1354 | 0 | ¤t_session.out, /* stdout */ |
1355 | 0 | ¤t_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, ¤t_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 | ¤t_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 | | */ |