123 lines
		
	
	
		
			4.5 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			123 lines
		
	
	
		
			4.5 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
| module Best_Guess;
 | |
| 
 | |
| # given an input map file with the following format:
 | |
| # proto	dport	sport	name	category
 | |
| # (see https://docs.zeek.org/en/master/frameworks/input.html#reading-data-into-tables
 | |
| # for details on how the table is loaded),
 | |
| # load up the table on zeek_init and for each connection_state_remove
 | |
| # make a "best guess" of protocols based on proto+dport+sport.
 | |
| # Best guesses are written to bestguess according to Best_Guess::Info
 | |
| 
 | |
| # Table key is transport protocol + destination port + source port
 | |
| # Zeek will segfault if there is an unset value ('-') in the key,
 | |
| # so use unknown_transport and 0 for protocol and ports, respectively,
 | |
| # if they are not defined in the lookup.
 | |
| type Best_Guess_Key: record {
 | |
|   proto: transport_proto &optional;
 | |
|   dport: count &optional;
 | |
|   sport: count &optional;
 | |
| };
 | |
| 
 | |
| 
 | |
| # Other table values include name, category.
 | |
| type Best_Guess_Value: record {
 | |
|   name: string &optional;
 | |
|   category: string &optional;
 | |
| };
 | |
| 
 | |
| export {
 | |
|   redef enum Log::ID += { BEST_GUESS_LOG };
 | |
| 
 | |
|   #############################################################################
 | |
|   # This is the format of bestguess.log
 | |
| 
 | |
|   type Info: record {
 | |
| 
 | |
|     # Timestamp for when the event happened.
 | |
|     ts: time &log;
 | |
| 
 | |
|     # Unique ID for the connection.
 | |
|     uid: string &log;
 | |
| 
 | |
|     # The connection's 4-tuple of endpoint addresses/ports.
 | |
|     id: conn_id &log;
 | |
| 
 | |
|     # transport protocol
 | |
|     proto: transport_proto &log &optional;
 | |
| 
 | |
|     # protocol guess values for log
 | |
|     name: string &log &optional;
 | |
|     category: string &log &optional;
 | |
| 
 | |
|     # originating structure containing guess info
 | |
|     guess_info: Best_Guess_Value &optional;
 | |
|   };
 | |
| 
 | |
|   # Event that can be handled to access the record as it is sent on to the logging framework.
 | |
|   global log_best_guess: event(rec: Best_Guess::Info);
 | |
| }
 | |
| 
 | |
| # lookup table of Best_Guess_Key -> Best_Guess_Value to be loaded in zeek_init
 | |
| global proto_guesses: table[transport_proto, count, count] of Best_Guess_Value = table();
 | |
| # filespec containing best guess mappings
 | |
| global guest_map_filespec : string = @DIR + "/guess_ics_map.txt";
 | |
| 
 | |
| #############################################################################
 | |
| event zeek_init() &priority=5 {
 | |
|   # populate the lookup table from guest_map_filespec and then clean up the intermediate source
 | |
|   Input::add_table([$source=guest_map_filespec, $name="guess_ics_map",
 | |
|                     $idx=Best_Guess_Key, $val=Best_Guess_Value,
 | |
|                     $destination=proto_guesses, $want_record=T]);
 | |
|   Input::remove("guess_ics_map");
 | |
| 
 | |
|   # initialize bestguess.log
 | |
|   Log::create_stream(Best_Guess::BEST_GUESS_LOG, [$columns=Best_Guess::Info, $ev=log_best_guess, $path="bestguess"]);
 | |
| }
 | |
| 
 | |
| #############################################################################
 | |
| event connection_state_remove(c: connection) {
 | |
|   local p = get_port_transport_proto(c$id$resp_p);
 | |
|   local dp = port_to_count(c$id$resp_p);
 | |
|   local sp = port_to_count(c$id$orig_p);
 | |
|   local guess = Best_Guess_Value($name="");
 | |
|   local category: string = "";
 | |
| 
 | |
|   # 1. only check connections for which we don't already know "service"
 | |
|   # 2. skip ICMP, since dp and sp don't mean the same thing for ICMP
 | |
|   if (((!c?$service) || (|c$service| == 0)) && (p != icmp)) {
 | |
| 
 | |
|     # Look up permutations of transport protocol + destination port + source port
 | |
|     # from more-specific to less-specific.
 | |
|     if ([p, dp, sp] in proto_guesses)
 | |
|       guess = proto_guesses[p, dp, sp];
 | |
|     else if ([p, dp, 0] in proto_guesses)
 | |
|       guess = proto_guesses[p, dp, 0];
 | |
|     else if ([p, 0, sp] in proto_guesses)
 | |
|       guess = proto_guesses[p, 0, sp];
 | |
|     else if ([unknown_transport, dp, sp] in proto_guesses)
 | |
|       guess = proto_guesses[unknown_transport, dp, sp];
 | |
|     else if ([unknown_transport, dp, 0] in proto_guesses)
 | |
|       guess = proto_guesses[unknown_transport, dp, 0];
 | |
|     else if ([unknown_transport, 0, sp] in proto_guesses)
 | |
|       guess = proto_guesses[unknown_transport, 0, sp];
 | |
| 
 | |
|     # if a best guess was made based on protocol and ports, log it
 | |
|     if ((guess?$name) && (guess$name != "")) {
 | |
| 
 | |
|       # as category may be undefined, check before accessing
 | |
|       if (guess?$category)
 | |
|         category = guess$category;
 | |
| 
 | |
|       # log entry into bestguess.log
 | |
|       local info = Best_Guess::Info($ts=network_time(),
 | |
|                                     $uid=c$uid,
 | |
|                                     $id=c$id,
 | |
|                                     $proto=p,
 | |
|                                     $name=guess$name,
 | |
|                                     $category=category,
 | |
|                                     $guess_info=guess);
 | |
|       Log::write(Best_Guess::BEST_GUESS_LOG, info);
 | |
| 
 | |
|     } # found guess
 | |
|   } # if (p != icmp)
 | |
| } # connection_state_remove |