天天看點

snort 源碼分支之規則結構分析(三)

前面介紹了規則頭資料結構分析、解析和比對過程。接下來分析規則選項的資料結構和解析、比對過程。

/*
 * 規則匹選項配資料結構
 */
typedef struct _OptFpList
{
    /* context data for this test */
    /* 比對時傳入的參數option_data, 例如 PatternMatchData*/
    void *context;

    /* 比對函數指針*/
    int (*OptTestFunc)(void *option_data, Packet *p);

    /* 解析規則選項時,使用next指針将所有的規則選項的比對對象鍊在一起*/
    struct _OptFpList *next;

    unsigned char isRelative;
    option_type_t type;

} OptFpList;

/*
 * 規則選項
 */
typedef struct _OptTreeNode
{
    /* plugin/detection functions go here */
    /* 檢測連結清單 */
    OptFpList *opt_func;
    /* 響應連結清單, 比如重置tcp連接配接*/
    RspFpList *rsp_func;  /* response functions */
    /* 日志輸對外連結表*/
    OutputFuncNode *outputFuncs; /* per sid enabled output functions */

    /* the ds_list is absolutely essential for the plugin system to work,
       it allows the plugin authors to associate "dynamic" data structures
       with the rule system, letting them link anything they can come up 
       with to the rules list */
    /* 這個結構非常重要,這是規則選項比對的核心結構, 裡面每一種類型的元素是一個連結清單,連結清單的元素是每個規則選項字段比對時需要用到的資料結構, 例如content字段對應于PatternMatchData的對象*/
    void *ds_list[PLUGIN_MAX];   /* list of plugin data struct pointers */

    /* 規則選項的計數索引*/
    int chain_node_number;

    int evalIndex;       /* where this rule sits in the evaluation sets */

    /* 協定類型 tcp、udp*/
    int proto;           /* protocol, added for integrity checks 
                            during rule parsing */

    int session_flag;    /* record session data */

    char *logto;         /* log file in which to write packets which 
                            match this rule*/
    /* metadata about signature */
    /* metadata 相關的資料,存在這個結構中, 還有 msg的調試也存儲在這當中*/
    SigInfo sigInfo;

    uint8_t stateless;  /* this rule can fire regardless of session state */
    uint8_t established; /* this rule can only fire if it is established */
    uint8_t unestablished;

    Event event_data;

    void* detection_filter; /* if present, evaluated last, after header checks */
    TagData *tag;

    /* stuff for dynamic rules activation/deactivation */
    int active_flag;
    int activation_counter;
    int countdown;
    int activates;
    int activated_by;

    struct _OptTreeNode *OTN_activation_ptr;
    struct _RuleTreeNode *RTN_activation_ptr;

    struct _OptTreeNode *next;

    struct _OptTreeNode *nextSoid;

    /* ptr to list of RTNs (head part) */
    struct _RuleTreeNode **proto_nodes;

    /**number of proto_nodes. */
    unsigned short proto_node_num;

    uint8_t failedCheckBits;
    char generated;

    uint16_t longestPatternLen;

    int rule_state; /* Enabled or Disabled */

    /* 性能統計 */
#ifdef PERF_PROFILING
    uint64_t ticks;
    uint64_t ticks_match;
    uint64_t ticks_no_match;
    uint64_t checks;
    uint64_t matches;
    uint64_t alerts;
    uint8_t noalerts;
#endif

    /* 性能統計 */
    int pcre_flag; /* PPM */
    uint64_t ppm_suspend_time; /* PPM */
    uint64_t ppm_disable_cnt; /*PPM */

    uint32_t num_detection_opts;

    /**unique index generated in ruleIndexMap.
     */
    /* 在選擇對應端口的規則時使用PortObject, 對規則進行分類*/
    int ruleIndex;

    /* List of preprocessor registered fast pattern contents */
    void *preproc_fp_list;

} OptTreeNode;


           

對資料結構中的主要字段進行解釋。接下來看如何解析指派。

同樣是從ParseRule中開始解析,内部調用ParseRuleOptions進行解析

/* 規則選項解析函數*/
OptTreeNode * ParseRuleOptions(SnortConfig *sc, RuleTreeNode *rtn,
                               char *rule_opts, RuleType rule_type, int protocol)
{
    OptTreeNode *otn;
    RuleOptOtnHandler otn_handler = NULL;
    int num_detection_opts = 0;
    char *dopt_keyword = NULL;
    OptFpList *fpl = NULL;
    int got_sid = 0;

    /* 配置設定記憶體 */
    otn = (OptTreeNode *)SnortAlloc(sizeof(OptTreeNode));

    /* 設定預設值、統計計數等*/
    otn->chain_node_number = otn_count;
    otn->proto = protocol;
    otn->event_data.sig_generator = GENERATOR_SNORT_ENGINE;
    otn->sigInfo.generator        = GENERATOR_SNORT_ENGINE;
    otn->sigInfo.rule_type        = SI_RULE_TYPE_DETECT; /* standard rule */
    otn->sigInfo.rule_flushing    = SI_RULE_FLUSHING_ON; /* usually just standard rules cause a flush*/

    /* Set the default rule state */
    otn->rule_state = ScDefaultRuleState();

    /* 隻有規則頭的情況*/
    if (rule_opts == NULL)
    {
        ...

        if (ScRequireRuleSid())
            ParseError("Each rule must contain a Rule-sid.");

        /* 規則選項與規則頭關聯,裡面涉及到policy相關的知識,後面會有部落格進行分析*/
        addRtnToOtn(otn, getParserPolicy(sc), rtn);

        otn->ruleIndex = RuleIndexMapAdd(ruleIndexMap,
                                         otn->sigInfo.generator,
                                         otn->sigInfo.id);
    }
    else
    {
        char **toks;
        int num_toks;
        /* 這個數組用來控制某些選項的配置方式,比如在一條規則中隻能出現一次msg*/
        char configured[sizeof(rule_options) / sizeof(RuleOptFunc)];
        int i;
        OptTreeNode *otn_dup;

        /* 規則選項的格式限制, 必須'c'開始 ')'結尾*/
        if ((rule_opts[0] != '(') || (rule_opts[strlen(rule_opts) - 1] != ')'))
        {
            ParseError("Rule options must be enclosed in '(' and ')'.");
        }

        /* Move past '(' and zero out ')' */
        /* 解析時, 剔除 '('、')'*/
        rule_opts++;
        rule_opts[strlen(rule_opts) - 1] = '\0';

        /* Used to determine if a rule option has already been configured
         * in the rule.  Some can only be configured once */
        memset(configured, 0, sizeof(configured));

        /* 規則選項都是以 ';'分隔, 是以使用它進行切分*/
        toks = mSplit(rule_opts, ";", 0, &num_toks, '\\');

        /* 對切分結果進行解析*/
        for (i = 0; i < num_toks; i++)
        {
            char **opts;
            int num_opts;
            char *option_args = NULL;
            int j;
            ...

            /* break out the option name from its data */
            /* 基本上每一項都是以 a:b (key:value)進行編寫, 是以需要再次切分*/
            opts = mSplit(toks[i], ":", 2, &num_opts, '\\');
            ...

            if (num_opts == 2)
            {
                /* 擷取key */
                option_args = opts[1];
            }

            /* 普通規則選項集合, 順序周遊進行搜尋比對key,如果存在調用key對應的解析函數進行解
               析處理,并設定已經配置過,再次出現報錯。如果沒有搜尋到,繼續往下比對
             */
            for (j = 0; rule_options[j].name != NULL; j++)
            {
                if (strcasecmp(opts[0], rule_options[j].name) == 0)
                {
                    if (configured[j] && rule_options[j].only_once)
                    {
                        ParseError("Only one '%s' rule option per rule.",
                                   opts[0]);
                    }

                    if ((option_args == NULL) && rule_options[j].args_required)
                    {
                        ParseError("No argument passed to keyword \"%s\".  "
                                   "Make sure you didn't forget a ':' or the "
                                   "argument to this keyword.\n", opts[0]);
                    }

                    rule_options[j].parse_func(sc, rtn, otn, rule_type, option_args);
                    configured[j] = 1;
                    break;
                }
            }

            /* Because we actually allow an sid of 0 */
            if ((rule_options[j].name != NULL) &&
                (strcasecmp(rule_options[j].name, RULE_OPT__SID) == 0))
            {
                got_sid = 1;
            }

            /* It's possibly a detection option plugin */
            /* 出現的規則字段如果是檢測引擎的需要使用的字段,特殊處理,這些回調函數是在snort啟動
               的時候注冊的一些解析函數, 通過RegisterRuleOptions注冊生成解析回調連結清單, 裡面會
               設定好每個選項對應的比對回調函數
            */
            if (rule_options[j].name == NULL)
            {
                RuleOptConfigFuncNode *dopt = rule_opt_config_funcs;

                for (; dopt != NULL; dopt = dopt->next)
                {
                    if (strcasecmp(opts[0], dopt->keyword) == 0)
                    {
                        /* 将value =>option_args傳入回調函數中進行解析處理*/
                        dopt->func(sc, option_args, otn, protocol);

                        /* If this option contains an OTN handler, save it for
                           use after the rule is done parsing. */
                        if (dopt->otn_handler != NULL)
                            otn_handler = dopt->otn_handler;

                        /* This is done so if we have a preprocessor/decoder
                         * rule, we can tell the user that detection options
                         * are not supported with those types of rules, and
                         * what the detection option is */
                        if ((dopt_keyword == NULL) &&
                            (dopt->type == OPT_TYPE_DETECTION))
                        {
                            dopt_keyword = SnortStrdup(opts[0]);
                        }

                        break;
                    }
                }

                /* 如果上面的兩個都沒比對上, 再在預處理回調解析函數連結清單中進行搜尋比對, 動作和上面一緻*/
                if (dopt == NULL)
                {
                    /* Maybe it's a preprocessor rule option */
                    PreprocOptionInit initFunc = NULL;
                    PreprocOptionEval evalFunc = NULL;
                    PreprocOptionFastPatternFunc fpFunc = NULL;
                    PreprocOptionOtnHandler preprocOtnHandler = NULL;
                    PreprocOptionCleanup cleanupFunc = NULL;
                    void *opt_data = NULL;

                    int ret = GetPreprocessorRuleOptionFuncs
                        (sc, opts[0], &initFunc, &evalFunc,
                         &preprocOtnHandler, &fpFunc, &cleanupFunc);

                    if (ret && (initFunc != NULL))
                    {
                        initFunc(sc, opts[0], option_args, &opt_data);
                        AddPreprocessorRuleOption(sc, opts[0], otn, opt_data, evalFunc);
                        if (preprocOtnHandler != NULL)
                            otn_handler = (RuleOptOtnHandler)preprocOtnHandler;
                        ...
                    }
                    else
                    {
                        /* Unrecognized rule option */
                        ParseError("Unknown rule option: '%s'.", opts[0]);
                    }
                }

                if (dopt_keyword == NULL)
                    dopt_keyword = SnortStrdup(opts[0]);

                num_detection_opts++;
            }

            mSplitFree(&opts, num_opts);
        }

        if ((dopt_keyword != NULL) &&
            (otn->sigInfo.rule_type != SI_RULE_TYPE_DETECT))
        {
            /* Preprocessor and decoder rules can not have
             * detection options */
            ParseError("Preprocessor and decoder rules do not support "
                       "detection options: %s.", dopt_keyword);
        }

        if (dopt_keyword != NULL)
            free(dopt_keyword);

        if (!got_sid && !ScTestMode())
            ParseError("Each rule must contain a rule sid.");
        ...
        /* 關聯 規則選項與規則頭*/
        addRtnToOtn(otn, getParserPolicy(sc), rtn);

        /* Check for duplicate SID */
        /* 通過gid 和sid 檢索是否存在重複的規則,存在進行合并 */
        otn_dup = OtnLookup(sc->otn_map, otn->sigInfo.generator, otn->sigInfo.id);
        if (otn_dup != NULL)
        {
            otn->ruleIndex = otn_dup->ruleIndex;

            if (mergeDuplicateOtn(sc, otn_dup, otn, rtn) == 0)
            {
                /* We are keeping the old/dup OTN and trashing the new one
                 * we just created - it's free'd in the remove dup function */
                mSplitFree(&toks, num_toks);
                return NULL;
            }
        }
        /* 不存在, 添加到map中*/
        else
        {
            otn->ruleIndex = RuleIndexMapAdd(ruleIndexMap,
                                             otn->sigInfo.generator,
                                             otn->sigInfo.id);
        }

        mSplitFree(&toks, num_toks);
    }

    otn->num_detection_opts += num_detection_opts;
    otn_count++;

    /* snort規則種類很多 通過rule_type 進行區分, 并對每種類型進行計數統計*/
    if (otn->sigInfo.rule_type == SI_RULE_TYPE_DETECT)
    {
        detect_rule_count++;
    }
    else if (otn->sigInfo.rule_type == SI_RULE_TYPE_DECODE)
    {
        //Set the bit if the decoder rule is enabled in the policies
        UpdateDecodeRulesArray(otn->sigInfo.id, ENABLE_RULE, ENABLE_ONE_RULE);
        decode_rule_count++;
    }
    else if (otn->sigInfo.rule_type == SI_RULE_TYPE_PREPROC)
    {
        preproc_rule_count++;
    }

    /* 與規則頭類似, 設定一個終止條件的回調函數*/
    fpl = AddOptFuncToList(OptListEnd, otn);
    fpl->type = RULE_OPTION_TYPE_LEAF_NODE;

    if (otn_handler != NULL)
    {
        otn_handler(sc, otn);
    }

    /* 檢查規則選項的合法性*/
    FinalizeContentUniqueness(sc, otn);
    ValidateFastPattern(otn);

    if ((thdx_tmp != NULL) && (otn->detection_filter != NULL))
    {
        ParseError("The \"detection_filter\" rule option and the \"threshold\" "
                   "rule option cannot be used in the same rule.\n");
    }

    if (thdx_tmp != NULL)
    {
        int rstat;

        thdx_tmp->sig_id = otn->sigInfo.id;
        thdx_tmp->gen_id = otn->sigInfo.generator;
        rstat = sfthreshold_create(sc, sc->threshold_config, thdx_tmp);

        if (rstat)
        {
            if (rstat == THD_TOO_MANY_THDOBJ)
            {
                ParseError("threshold (in rule): could not create threshold - "
                           "only one per sig_id=%u.", thdx_tmp->sig_id);
            }
            else
            {
                ParseError("threshold (in rule): could not add threshold "
                           "for sig_id=%u!\n", thdx_tmp->sig_id);
            }
        }

        thdx_tmp = NULL;
    }

    /* setup gid,sid->otn mapping */
    SoRuleOtnLookupAdd(sc->so_rule_otn_map, otn);
    OtnLookupAdd(sc->otn_map, otn);

    return otn;
}
           

以上規則解析函數的分析,然後下面分析規則檢測中content字段的具體解析過程

RegisterRuleOptions=> SetupPatternMatch=>PayloadSearchInit 

這個是初始化的注冊過程。

解析時,遇到content會調用PayloadSearchInit 進行處理。

/*
 * data : 實際的規則選項的内容, 例如 content:snort , data為snort
 */
static void PayloadSearchInit(struct _SnortConfig *sc, char *data, OptTreeNode * otn, int protocol)
{
    OptFpList *fpl;
    PatternMatchData *pmd;
    char *data_end;
    char *data_dup;
    char *opt_data;
    int opt_len = 0;
    char *next_opt;

    ...
    /* whack a new node onto the list */
    /* 以為content涉及到字元串比對, 預設内部使用bm算法, 是以需要建立bm需要的資料結構*/
    pmd = NewNode(otn, PLUGIN_PATTERN_MATCH);
    lastType = PLUGIN_PATTERN_MATCH;

    if (!data)
        ParseError("No Content Pattern specified!");

    ...
    data_dup = SnortStrdup(data);
    data_end = data_dup + strlen(data_dup);

    opt_data = PayloadExtractParameter(data_dup, &opt_len);
    /* 解析模式串, 并建構bm算法的壞字表和好字尾表*/
    ParsePattern(opt_data, otn, PLUGIN_PATTERN_MATCH);

    ...
    /* 設定bm算法的比對函數CheckANDPatternMatch, 并将fpl鍊到otn->opt_func中*/
    fpl = AddOptFuncToList(CheckANDPatternMatch, otn);
    fpl->type = RULE_OPTION_TYPE_CONTENT;
    pmd->buffer_func = CHECK_AND_PATTERN_MATCH;

    /* 比對時需要的參數*/
    fpl->context = pmd;
    pmd->fpl = fpl;

    // if content is followed by any comma separated options,
    // we have to parse them here.  content related options
    // separated by semicolons go straight to the callbacks.
    ...
}
           

解析完畢需要構模組化式比對引擎,這個比較複雜,需要單獨一個章節進行分析,比對過程也會使用一個章節進行分析。