上篇对照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;
}