本文共 11788 字,大约阅读时间需要 39 分钟。
snort是一款著名的开源IPS,其主页地址:。更详细的介绍网上很多,可自行搜索了解。本博客主要介绍snort-2.9.5版本的模式匹配引擎的加载和匹配。
模式匹配引擎主要使用多模式匹配算法和单模式匹配算法。先由多模式匹配算法大概确定有哪些规则可能匹配成功,然后再通过单模式匹配算法去精确匹配。其配置格式如下:
config detection: search-method ac-split search-optimize max-pattern-len 20
snort的初始化函数主要做的工作是读取配置文件,第一次只是把所有配置项解析出来,第二次则是解析所有规则,将所有规则加载到对应的配置对象中。
void SnortInit(int argc, char **argv){ ... /* chew up the command line */ /* 命令行解析*/ ParseCmdLine(argc, argv); ... if (!ScVersionMode()) { /* Every run mode except version will potentially need output * If output plugins should become dynamic, this needs to move */ /* 注册输出模块 */ RegisterOutputPlugins(); } ... /* if we're using the rules system, it gets initialized here */ if (snort_conf_file != NULL) { SnortConfig *sc; /* initialize all the plugin modules */ /* 注册预处理模块*/ RegisterPreprocessors(); /* 注册规则解析相关模块*/ RegisterRuleOptions(); /* 解析配置函数,通过解析命令行获取配置文件snort.conf,再通过这个文件解析出所有配置*/ sc = ParseSnortConf(); /* Merge the command line and config file confs to take care of * command line overriding config file. * Set the global snort_conf that will be used during run time */ /* 合并命令行与配置文件中相关的配置,命令行的配置会覆盖配置文件中的配置 */ snort_conf = MergeSnortConfs(snort_cmd_line_conf, sc); /* Handles Fatal Errors itself. */ /* 事件队列,这个队列中的节点会被输出日志模块使用*/ SnortEventqNew(snort_conf->event_queue_config, snort_conf->event_queue); } else if (ScPacketLogMode() || ScPacketDumpMode()) { /* Make sure there is a log directory */ /* This will return the cmd line conf and resolve the output * configuration */ SnortConfig* sc = ParseSnortConf(); snort_conf = MergeSnortConfs(snort_cmd_line_conf, sc); SnortEventqNew(snort_conf->event_queue_config, snort_conf->event_queue); } ... /* 解析规则,由于规则中会使用一些变量,所有解析规则在解析配置后面,保证规则的正确性*/ ParseRules(snort_conf); ... fpCreateFastPacketDetection(snort_conf); ...}
SnortConfig * ParseSnortConf(void){ /* 创建snort的配置对象, 所有的配置都保存在这个结构里面,所以非常庞大 */ SnortConfig *sc = SnortConfNew();\ ... /* 这里是模式匹配引擎的配置对象的创建,在FastPatternConfigNew函数中会调用fpSetDefaults设置默认值,默认使用 MPSE_AC_BNFA 算法*/ sc->fast_pattern_config = FastPatternConfigNew(); ... /* 通过变量parse_rules来判断,0:解析配置;1:解析规则 */ parse_rules = 0; /* 这里是真正解析配置和规则文件的地方 */ if ( strcmp(file_name, NULL_CONF) ) ParseConfigFile(sc, sc->targeted_policies[policy_id], file_name);}
例如今天要介绍的模式匹配引擎的配置;假设其配置如下:
config detection: search-method ac-split search-optimize max-pattern-len 20
ParseRules函数最终也是调用ParseConfigFile完成规则解析,部分源码如下:
void ParseRules(SnortConfig *sc) { ... /* 设置标志:表明是解析规则*/ parse_rules = 1; ... /* 真正去解析规则 */ ParseConfigFile(sc, sc->targeted_policies[policy_id], snort_conf_file); ... /* Compile/Finish and Print the PortList Tables */ /* 为生成模式匹配引擎做准备,将规则进行去重等*/ PortTablesFinish(sc->port_tables, sc->fast_pattern_config); ... }
根据前面的准备工作来创建模式匹配引擎;
这里只列出了tcp数据包的匹配过程,UDP与TCP一样。其中mpseSearch就是模式匹配引擎的匹配函数,而rule_tree_match则是匹配规则的入口函数
int SnortMain(int argc, char *argv[]) { ... /* snort的初始化 包括预处理模块、模式匹配引擎、日志模块*/ SnortInit(argc, argv); ... /* 设置数据包解码函数*/ SetPktProcessor(); ... /* 循环处理数据包:先从内核获取数据包、解码、预处理、利用模式匹配引擎检测攻击、产生日志、做出动过:alert 、drop、pass */ PacketLoop(); return 0;}
数据包解码函数源码如下:
static int SetPktProcessor(void){ const char* slink = NULL; const char* extra = NULL; int dlt = DAQ_GetBaseProtocol(); switch ( dlt ) { case DLT_EN10MB: slink = "Ethernet"; grinder = DecodeEthPkt;/* 此处为设置以太网的解码函数*/ break;... default: /* oops, don't know how to handle this one */ FatalError("Cannot decode data link type %d\n", dlt); break; }... return 0;}
void PacketLoop (void){ ... while ( !exit_logged ) { /* 通过DAQ向内核获取数据包然后调用PacketCallback回调函数 */ error = DAQ_Acquire(pkts_to_read, PacketCallback, NULL); ... } ...}
PacketCallback函数源码:
static DAQ_Verdict PacketCallback( void* user, const DAQ_PktHdr_t* pkthdr, const uint8_t* pkt){... /* 处理数据包*/ verdict = ProcessPacket(&p, pkthdr, pkt, NULL);...}
DAQ_Verdict ProcessPacket( Packet* p, const DAQ_PktHdr_t* pkthdr, const uint8_t* pkt, void* ft){... /* 数据包解码*/ (*grinder) (p, pkthdr, pkt);... if ( !(p->packet_flags & PKT_IGNORE) ) { /* 调用预处理模块和检测引擎(模式匹配引擎)*/ Preprocess(p); /* 记录日志*/ log_func(p); }...}
int Preprocess(Packet * p){ ... // If the packet has errors, we won't analyze it. if ( p->error_flags ) { ... } else { ... if ( p->dsize ) { while ((idx != NULL) && !(p->packet_flags & PKT_PASS_RULE)) { if ( ((p->proto_bits & idx->proto_mask) || (idx->proto_mask == PROTO_BIT__ALL) ) && IsPreprocBitSet(p, idx->preproc_bit)) { /* 调用预处理模块 */ idx->func(p, idx->context); ... } else idx = idx->next; } } ... if ((do_detect) && (p->bytes_to_inspect != -1)) { /* Check if we are only inspecting a portion of this packet... */ if (p->bytes_to_inspect > 0) { DEBUG_WRAP(DebugMessage(DEBUG_DETECT, "Ignoring part of server " "traffic -- only looking at %d of %d bytes!!!\n", p->bytes_to_inspect, p->dsize);); p->dsize = (uint16_t)p->bytes_to_inspect; } /* 调用检测引擎*/ Detect(p); } else if (p->bytes_to_inspect == -1) { DEBUG_WRAP(DebugMessage(DEBUG_DETECT, "Ignoring server traffic!!!\n");); } } ...}
int Detect(Packet * p){... detected = fpEvalPacket(p);...}
int fpEvalPacket(Packet *p){... switch(ip_proto) { case IPPROTO_TCP: DEBUG_WRAP(DebugMessage(DEBUG_DETECT, "Detecting on TcpList\n");); if(p->tcph == NULL) { ip_proto = -1; break; } return fpEvalHeaderTcp(p, omd); default: break; } /* ** No Match on TCP/UDP, Do IP */ return fpEvalHeaderIp(p, ip_proto, omd);}
static inline int fpEvalHeaderTcp(Packet *p, OTNX_MATCH_DATA *omd){... if (dst != NULL) { if (fpEvalHeaderSW(dst, p, 1, 0, omd)) return 1; }...}
static inline int fpEvalHeaderSW(PORT_GROUP *port_group, Packet *p, int check_ports, char ip_rule, OTNX_MATCH_DATA *omd){... if (do_detect_content) {... if (p->uri_count > 0) {... if ((so != NULL) && (mpseGetPatternCount(so) > 0)) { start_state = 0; mpseSearch(so, UriBufs[i].uri, UriBufs[i].length, rule_tree_match, omd, &start_state);... } } } } } return 0;}
int mpseSearch( void *pvoid, const unsigned char * T, int n, int ( *action )(void* id, void * tree, int index, void *data, void *neg_list), void * data, int* current_state ){ ... switch( p->method ) { /* 这里就是调用多模式匹配算法AC*/ case MPSE_AC_BNFA: case MPSE_AC_BNFA_Q: /* return is actually the state */ ret = bnfaSearch((bnfa_struct_t*) p->obj, (unsigned char *)T, n, action, data, 0 /* start-state */, current_state ); ... default: PREPROC_PROFILE_END(mpsePerfStats); return 1; }}
rule_tree_match函数源码:
static int rule_tree_match( void * id, void *tree, int index, void * data, void * neg_list){... rval = detection_option_tree_evaluate(root, &eval_data);... return 0;}
static int detection_option_tree_evaluate(detection_option_tree_root_t *root, detection_option_eval_data_t *eval_data){... /* 这里就是匹配规则树*/ for ( i = 0; i< root->num_children; i++) { /* New tree, reset doe_ptr for safety */ UpdateDoePtr(NULL, 0); /* Increment number of events generated from that child */ /* 匹配规则树的节点*/ rval += detection_option_node_evaluate(root->children[i], eval_data); }...}
int detection_option_node_evaluate(detection_option_tree_node_t *node, detection_option_eval_data_t *eval_data){ ... /* No, haven't evaluated this one before... Check it. */ do { switch (node->option_type) { ... case RULE_OPTION_TYPE_CONTENT: if (node->evaluate) { /* This will be set in the fast pattern matcher if we found * a content and the rule option specifies not that * content. Essentially we've already evaluated this rule * option via the content option processing since only not * contents that are not relative in any way will have this * flag set */ if (dup_content_option_data.exception_flag) { if ((dup_content_option_data.last_check.ts.tv_sec == eval_data->p->pkth->ts.tv_sec) && (dup_content_option_data.last_check.ts.tv_usec == eval_data->p->pkth->ts.tv_usec) && (dup_content_option_data.last_check.packet_number == cur_eval_pkt_count) && (dup_content_option_data.last_check.rebuild_flag == (eval_data->p->packet_flags & PKT_REBUILT_STREAM))) { rval = DETECTION_OPTION_NO_MATCH; break; } } /* 调用BM算法匹配规则的模式串 */ rval = node->evaluate(&dup_content_option_data, eval_data->p); } break; case RULE_OPTION_TYPE_CONTENT_URI: if (node->evaluate) { /* 调用BM算法匹配规则的模式串 */ rval = node->evaluate(&dup_content_option_data, eval_data->p); } break; ... } } return result;}
本博客是涉及到模式匹配引擎的初始化和匹配过程,并未讨论其中的算法实现。
转载地址:http://vvtmd.baihongyu.com/