本文共 8744 字,大约阅读时间需要 29 分钟。
上篇对照snort的规则简单介绍了一下那些规则字段
接下来看一下规则头和规则选项的数据结构
/* 规则头匹配函数链表*/typedef struct _RuleFpList{ /* context data for this test */ /* 目前规则头没有使用 */ void *context; /* rule check function pointer */ /* 规则头匹配的函数类型指针,参数:数据包,规则头对象, 下一个需要匹配的对象:这样就可以在函数内部进行遍历链表实现整个规则头的匹配, 标志*/ int (*RuleHeadFunc)(Packet *, struct _RuleTreeNode *, struct _RuleFpList *, int); /* pointer to the next rule function node */ struct _RuleFpList *next;} RuleFpList;/* * 规则头 */typedef struct _RuleTreeNode{ /*规则头的匹配函数 是一个单链表*/ RuleFpList *rule_func; /* match functions.. (Bidirectional etc.. ) */ /* 规则头计数,会去重*/ int head_node_number; /* 规则类型 比如上一篇的举例:RULE_TYPE__ALERT*/ RuleType type; /* 规则头中的 源IP组*/ IpAddrSet *sip; /* 目的IP组*/ IpAddrSet *dip; /* 协议类型 : tcp udp */ int proto; /* 源端口组*/ PortObject * src_portobject; /* 目的端口则*/ PortObject * dst_portobject; /* 标记 : 数据包方向, 端口类型:any等等*/ uint32_t flags; /* control flags */ /* stuff for dynamic rules activation/deactivation */ /* 与 Dynamic规则一起使用*/ int active_flag; int activation_counter; int countdown; ActivateListNode *activate_list;#if 0 struct _RuleTreeNode *right; /* ptr to the next RTN in the list */ /** list of rule options to associate with this rule node */ OptTreeNode *down; #endif /**points to global parent RTN list (Drop/Alert) which contains this * RTN. */ /*该规则所属某一类的规则头链表头 alert*/ struct _ListHead *listhead; /**reference count from otn. Multiple OTNs can reference this RTN with the same * policy. */ /* 规则选项的引用计数, 多个规则选项从属于一个规则头的情况非常多*/ unsigned int otnRefCount;} RuleTreeNode;/* 规则链表头 */typedef struct _ListHead{ struct _OutputFuncNode *LogList; struct _OutputFuncNode *AlertList; struct _RuleListNode *ruleListNode;} ListHead;/* 规则链表, 将各类规则链表头ListHead链成一个链表, 方便检测规则是否合法,比如name="snort", 没有这类动作的规则,程序报错*/typedef struct _RuleListNode{ ListHead *RuleList; /* The rule list associated with this node */ RuleType mode; /* the rule mode */ int rval; /* 0 == no detection, 1 == detection event */ int evalIndex; /* eval index for this rule set */ char *name; /* name of this rule list (for debugging) */ struct _RuleListNode *next; /* the next RuleListNode */} RuleListNode;
前面提到规则头是存在很多冗余的,接下来分析snort是如何去重的。
snort在解析规则头的时候,在ProcessHeadNode函数中,调用findHeadNode进行查询之前是否存在一样的规则头,
如果存在rtn->otnRefCount++, 增加引用计数,否则创建新的规则头对象,设置各类匹配回调函数。
以源码的形式进行分析规则头的解析流程
static void ParseRule(SnortConfig *sc, SnortPolicy *p, char *args, RuleType rule_type, ListHead *list){... /* 如果没有规则头, 使用默认规则头, tcp any any -> any any*/ if (*args == '(') { test_rtn.flags |= ANY_DST_PORT; test_rtn.flags |= ANY_SRC_PORT; test_rtn.flags |= ANY_DST_IP; test_rtn.flags |= ANY_SRC_IP; test_rtn.flags |= BIDIRECTIONAL; test_rtn.type = rule_type; protocol = IPPROTO_TCP; roptions = args; DEBUG_WRAP(DebugMessage(DEBUG_CONFIGRULES, "Preprocessor Rule detected\n");); } else { /* proto ip port dir ip port r*/ /* 调用工具函数 mpspit进行切分读取的规则内容, 规则头以空格或者'\t'分隔,所以传入" \t",规则头有7项,但是没有了动作例如alert,所以规则头剩余6项,顺带切分规则选项,所以传入7, num_toks返回实际切分的个数, 最后一个参数为转义字符, 有的规则很长,使用'\\'进行换行*/ toks = mSplit(args, " \t", 7, &num_toks, '\\'); /* A rule might not have rule options */ if (num_toks < 6) { ParseError("Bad rule in rules file: %s", args); } /* 正常处理, toks[6]:规则选项*/ if (num_toks == 7) roptions = toks[6]; /* 规则动作进入函数前就已经解析了*/ test_rtn.type = rule_type; ... /* Set the rule protocol - fatal errors if protocol not found */ /* 前一篇的实例:alert tcp $HOME_NET any -> $EXTERNAL_NET $HTTP_PORTS */ /* 第一项 规则协议 */ protocol = GetRuleProtocol(toks[0]); test_rtn.proto = protocol; ... /* 解析源IP组, 会从snort_config对象中的targeted_policies策略对象中的ip_vartable链表中获取$HOME_NET的值*/ ProcessIP(sc, toks[1], &test_rtn, SRC, 0); ... /*解析源IP组, 会从snort_config对象中的targeted_policies策略对象中的portVarTable中的哈希表中查找 PortObject对象*/ if (ParsePortList(&test_rtn, portVarTable, nonamePortVarTable, toks[2], protocol, 0 /* =src port */ )) { ParseError("Bad source port: '%s'", toks[2]); } /* 规则头的数据包方向解析,先判定格式*/ if ((strcmp(toks[3], RULE_DIR_OPT__DIRECTIONAL) != 0) && (strcmp(toks[3], RULE_DIR_OPT__BIDIRECTIONAL) != 0)) { ParseError("Illegal direction specifier: %s", toks[3]); } /* New in version 1.3: support for bidirectional rules * This checks the rule "direction" token and sets the bidirectional * flag if the token = '<>' */ /* 默认"->", 否则 "<>"*/ if (strcmp(toks[3], RULE_DIR_OPT__BIDIRECTIONAL) == 0) { DEBUG_WRAP(DebugMessage(DEBUG_CONFIGRULES,"Bidirectional rule!\n");); test_rtn.flags |= BIDIRECTIONAL; } ... /* 设置规则头所属的规则动作的链表头*/ test_rtn.listhead = list; /* 内部会查询是否已经存在该规则头和设置规则头的匹配回调函数*/ rtn = ProcessHeadNode(sc, &test_rtn, list); ... }}
规则头回调函数源码分析
static RuleTreeNode * ProcessHeadNode(SnortConfig *sc, RuleTreeNode *test_node, ListHead *list){ /* 查找是否存在冗余*/ RuleTreeNode *rtn = findHeadNode(sc, test_node, getParserPolicy(sc)); /* 不存在, 直接创建,并且深拷贝*/ if (rtn == NULL) { DEBUG_WRAP(DebugMessage(DEBUG_CONFIGRULES,"Building New Chain head node\n");); rtn = (RuleTreeNode *)SnortAlloc(sizeof(RuleTreeNode)); rtn->otnRefCount++; /* copy the prototype header info into the new header block */ XferHeader(test_node, rtn); head_count++; rtn->head_node_number = head_count; /* initialize the function list for the new RTN */ /* 设置各类回调函数*/ SetupRTNFuncList(rtn); /* add link to parent listhead */ rtn->listhead = list;... } else { /*存在增加引用计数*/ rtn->otnRefCount++; FreeRuleTreeNode(test_node); } return rtn;}
static void SetupRTNFuncList(RuleTreeNode * rtn){ /* 如果规则头都是双向的, 直接设置回调函数CheckBidirectional*/ if(rtn->flags & BIDIRECTIONAL) { DEBUG_WRAP(DebugMessage(DEBUG_CONFIGRULES,"CheckBidirectional->\n");); AddRuleFuncToList(CheckBidirectional, rtn); } /* 根据规则选项逐个设置回调函数*/ else { /* Attach the proper port checking function to the function list */ /* * the in-line "if's" check to see if the "any" or "not" flags have * been set so the PortToFunc call can determine which port testing * function to attach to the list */ PortToFunc(rtn, (rtn->flags & ANY_DST_PORT ? 1 : 0), (rtn->flags & EXCEPT_DST_PORT ? 1 : 0), DST); /* as above */ PortToFunc(rtn, (rtn->flags & ANY_SRC_PORT ? 1 : 0), (rtn->flags & EXCEPT_SRC_PORT ? 1 : 0), SRC); /* link in the proper IP address detection function */ AddrToFunc(rtn, SRC); /* last verse, same as the first (but for dest IP) ;) */ AddrToFunc(rtn, DST); } /* tack the end (success) function to the list */ /* snort中采用链式方式管理回调函数, 每个函数只有返回为1才可能成功, 所以在链表尾部设置一个成功的结束函数让规则匹配结束, 设计巧妙*/ AddRuleFuncToList(RuleListEnd, rtn);}/* 该结束函数啥都没做 直接返回1, 检测规则头是否匹配成功,直接判定返回值是否为1就ok了 */int RuleListEnd(Packet *p, struct _RuleTreeNode *rtn_idx, RuleFpList *fp_list, int check_ports){ return 1;}/* 将rfunc匹配回调函数insert到链表尾部*/void AddRuleFuncToList(int (*rfunc) (Packet *, struct _RuleTreeNode *, struct _RuleFpList *, int), RuleTreeNode * rtn){ RuleFpList *idx; DEBUG_WRAP(DebugMessage(DEBUG_CONFIGRULES,"Adding new rule to list\n");); idx = rtn->rule_func; if(idx == NULL) { rtn->rule_func = (RuleFpList *)SnortAlloc(sizeof(RuleFpList)); rtn->rule_func->RuleHeadFunc = rfunc; } else { while(idx->next != NULL) idx = idx->next; idx->next = (RuleFpList *)SnortAlloc(sizeof(RuleFpList)); idx = idx->next; idx->RuleHeadFunc = rfunc; }}
规则头解析的源码剖析就到这里分析完了。
看一眼如何调用回调函数进行匹配的
/* *规则头检测函数 * rtn : 规则头 * p : 数据包 * check_ports : 检测标志 */int fpEvalRTN(RuleTreeNode *rtn, Packet *p, int check_ports){... /* 判定返回值是否1, 不为1 失败*/ if(!rtn->rule_func->RuleHeadFunc(p, rtn, rtn->rule_func, check_ports)) { DEBUG_WRAP(DebugMessage(DEBUG_DETECT, " => Header check failed, checking next node\n");); DEBUG_WRAP(DebugMessage(DEBUG_DETECT, " => returned from next node check\n");); PREPROC_PROFILE_END(ruleRTNEvalPerfStats); return 0; }... /* 成功返回1*/ return 1;}
转载地址:http://hvtmd.baihongyu.com/