snort 规则结构分析(二)

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

规则头解析的源码剖析就到这里分析完了。

扫描二维码关注公众号,回复: 5485559 查看本文章

看一眼如何调用回调函数进行匹配的

/*
 *规则头检测函数
 * 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;

}

猜你喜欢

转载自blog.csdn.net/guoguangwu/article/details/88383376