snort 源码分析之模式匹配引擎

2023-11-15

   snort是一款著名的开源IPS,其主页地址:snort 官网。更详细的介绍网上很多,可自行搜索了解。本博客主要介绍snort-2.9.5版本的模式匹配引擎的加载和匹配。

   模式匹配引擎主要使用多模式匹配算法和单模式匹配算法。先由多模式匹配算法大概确定有哪些规则可能匹配成功,然后再通过单模式匹配算法和其他规则选项去精确匹配。其配置格式如下:

config detection: search-method ac-split search-optimize max-pattern-len 20

   snort的初始化函数主要做的工作是读取配置文件,第一次只是把所有配置项解析出来,第二次则是解析所有规则,将所有规则加载到对应的配置对象中。

   SnortInit为入口,主要步骤如下(预处理模块、输出模块、动态库加载等暂不介绍):

  1:命令行解析,本博客暂不介绍, 

  2:注册一些解析配置的函数,作为准备工作, 主要由以下三个注册函数完成:

  •        RegisterOutputPlugins:注册输出日志解析配置等函数。在程序运行中会将防护日志记录到对应的配置中。
  •        RegisterPreprocessors:注册预处理模块解析配置等函数。由ARP、连接管理、http解析、端口扫描等模块,他们基本都是为后面的模式匹配引擎做准备工作的。
  •        RegisterRuleOptions:注册解析规则内容、匹配等函数。其中与模式匹配引擎息息相关的是content、uricontent字段的内容,模式匹配引擎根据其来生成的。

  3:解析配置文件

  •      ParseSnortConf :解析配置文件,模式匹配引擎的配置也在这个函数中完成

  4:解析规则

  •      ParseRules:解析配置文件,将所有规则加载到内存中,然后做相关处理 

  5:生成引擎

  •      fpCreateFastPacketDetection:根据第3步解析到的模式匹配引擎配置,将第4步解析到的规则重新组织生成模式匹配引擎,在匹配规则中用。

  •   SnortInit的部分源码如下:
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);
} 

ParseConfigFile函数的源码比较长,暂不贴出来。大概讲一下其逻辑。

1:打开文件,完整的读取一行。

2:调用mSplit对该行切分成两个元素

3:将切分好的第一个元素循环与snort_conf_keywords比较看是否有相关配置的解析函数,如果有,再判断parse_rules来决定是解析配置还是规则,最终会调用snort_conf_keywords[i].parse_func(sc, p, args)来解析相关配置。如果有特殊配置需要处理, 以此类推,进行处理

    例如今天要介绍的模式匹配引擎的配置;假设其配置如下:

 config detection: search-method ac-split search-optimize max-pattern-len 20

  ParseConfigFile的执行流程如下:

  •   ParseConfigFile=》snort_conf_keywords[i].parse_func(sc, p, args)=》ParseConfig=》config_opts[i].parse_func(sc, opts)=》ConfigDetection   通过这一系列的调用模式匹配引擎的配置就算是解析完成了

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);
    ...
 } 

fpCreateFastPacketDetection函数

根据前面的准备工作来创建模式匹配引擎;

主要工作:

1:将规则重新分类,从每条规则中选出一个最特殊的模式串出来,加入到模式匹配引擎中,然后生成模式匹配引擎,

2: 再将每条规则重新组织。在解析的时候,规则的匹配函数是以链表的形式组织,经过这个函数后变成了一棵树,这样做的优点是去重,节约内存。至此,模式匹配引擎的初始化完成。

3:然后通过SetPktProcessor函数设置各类数据包解码函数,

4:最终调用PacketLoop进行处理数据包

PacketLoop的处理流程

1:通过DAQ_Acquire从内核中获取数据包,

2:调用PacketCallback函数处理数据包,

3:调用ProcessPacket,在该函数中调用grinder进行解码,

4:然后调用Preprocess来调用预处理插件和模式匹配引擎匹配接口函数Detect,

5:然后依次调用fpEvalPacket=>fpEvalHeaderTcp=>fpEvalHeaderSW=>mpseSearch=>rule_tree_match=>detection_option_tree_evaluate=>detection_option_node_evaluate

这里只列出了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);
...
}

  • ProcessPacket函数源码:
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);
    }
...
}
  • Preprocess函数源码:
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"););
        }
    } 
    ...
} 
  • Detect函数源码:
int Detect(Packet * p)
{
...
    detected = fpEvalPacket(p);
...
}

  • fpEvalPacket函数源码:
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);
}

  • fpEvalHeaderTcp函数源码:
static inline int fpEvalHeaderTcp(Packet *p, OTNX_MATCH_DATA *omd)
{
...

    if (dst != NULL)
    {
        if (fpEvalHeaderSW(dst, p, 1, 0, omd))
            return 1;
    }
...
}

  • fpEvalHeaderSW函数源码:
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;
}

  • mpseSearch函数源码:此函数如果执行成功会调用rule_tree_match函数继续匹配规则,进行检测
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;
}

  • detection_option_tree_evaluate函数源码:
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);
    }
...
}
  • detection_option_node_evaluate函数源码:
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;
} 

本博客是涉及到模式匹配引擎的初始化和匹配过程,并未讨论其中的算法实现。 

可以参考 https://github.com/testwill/Multi-pattern-matching-engine , 有snort的ac实现。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

snort 源码分析之模式匹配引擎 的相关文章

随机推荐

  • hdu 5756:Boss Bo

    题目链接如下 Problem 5756 先用dfs确定每个节点的序号编号 并且可以获得每个节点可以包括的子树节点区间范围 再用线段树建立一棵树 在第一次建立的时候我们记录每个节点的深度 然后再进行一次dfs 这次dfs用来更新以不同节点为根
  • 【实验九】【使用触发器实现数据完整性】

    文章目录 触发器 一 实现域完整性 二 实现参照完整性 三 比较约束与触发器的执行顺序 Reference 触发器 触发器 trigger 是用户定义在关系表上的一类由事件驱动的特殊过程 触发器又叫做事件 条件 动作 event condi
  • ThinkPHP6.0 多应用模式 部署 Layuiadmin 单页版

    QQ 23426945 PHP技术群 159789818 个人技术博客 https www itqaq com TP6 0中的路由省略应用名只能用入口文件绑定应用 和 域名绑定应用 经过测试 最后得出域名绑定应用是最合适的部署方式 如果有更
  • React源码分析3-render阶段(穿插scheduler和reconciler)

    本章将讲解 react 的核心阶段之一 render阶段 我们将探究以下部分内容的源码 更新任务的触发 更新任务的创建 reconciler 过程同步和异步遍历及执行任务 scheduler 是如何实现帧空闲时间调度任务以及中断任务的 触发
  • SpringBoot 如何实现多数据源

    SpringBoot 如何实现多数据源 第一步 配置yml spring datasource type com alibaba druid pool DruidDataSource datasource1 url jdbc mysql 1
  • vue毕业设计分享 100例 (五)

    文章目录 前言 题目1 基于SSM的婚纱摄影业务系统 br 题目2 基于SSM的家教网课学习平台 br 题目3 基于SSM的家庭美食食谱管理系统 br 题目4 基于SSM的驾校预约培训管理系统 br 题目5 基于SSM的教师评价考核管理系统
  • 远程登陆之SSH的简单用法及命令

    SSH简单使用 SSH的安装 启动服务器的SSH服务 SSH远程登陆 口令登陆 公钥登陆 配置别名 传输文件 SSH只是一种协议 存在多种实现 既有商业实现 也有开源实现 OpenSSH是一种免费开源实现 OpenSSH是用于使用SSH协议
  • zabbix监控硬件

    一 通过snmp监控 1 idrac上开启snmp服务 2 然后到zabbix server服务器上测试一下是否能get到数据 snmpget v 2c c 1 3 6 1 4 1 674 10892 2 1 1 2 0 3 添加主机 二
  • rabbitmq的发布确认和事务

    2019独角兽企业重金招聘Python工程师标准 gt gt gt confirm的工作机制 Confirms是增加的一个确认机制的类 继承自标准的AMQP 这个类只包含了两个方法 confirm select和confirm select
  • 读取dgn文件思路

    1 opendgn 2 sourceforge里面有dgn读取内容 3 dgndirect 4 DGNLib 5 Teigha 6 lt
  • ERROR: cannot launch node of type [turtlebot_teleop/turtlebot_teleop_key] 问题解决

    当遇到问题 采取方式为 也就是说先用 rospack find 命令找是否存在 不存在就安装 sudo apt get install ros kinetic XXX 从网上查需要安装哪些内容 转载于 https www cnblogs c
  • 使用U盘为虚拟机安装系统

    转载自点击打开链接http www cnblogs com happy xiaoxiao p 8010547 html 前提 使用虚拟机安装WIN8系统时 由于WIN8镜像文件大于4G无法使用虚拟安装 所以使用U盘安装 1 装有U盘启动的W
  • 点云高度归一化处理(附 matlab 代码)

    由于不同地物之间存在着高程的差异 为了去除地形起伏对点云数据高程值的影响 所以需要根据提取出的地面点进行点云归一化处理 这一步是很多算法的基础 可以提高后续点云分类或分割的准确度等 如下图所示 归一化的过程其实相对简单 遍历每一个非地面点
  • Simon‘s writting 全网最全笔记

    石墨文档写完上传的 格式有点乱 凑合着看 排班不重要 内容最重要 一键三连的朋友可以私信我要word pdf 图片 markdown 那个看得舒服看哪个 An IELTS training course Understand the tas
  • Visual Studio下安装C/C++图形库(easyx)2022版

    一 Easyx下载链接 EasyX Graphics Library for C 二 Visual Studio上安装Easyx 1 右击EasyX 20220901 exe 选择管理员身份运行 2 点击下一步 3 它会自动检测 根据自己需
  • python3 新式邮件写法 附件乱码 解决

    import sys import re import mimetypes import base64 import traceback from pathlib import PurePath from datetime import d
  • 经过几年和前端调接口,我把抓包调试摸透了,浏览器岂非我对手

    场景 我们在和前端对接接口的时候 前端都是根据后端提供的接口api swagger地址或者yapi 其他接口管理平台进行接口联调 mock一些测试数据调试 调好了然后在本地和后端联调接口 没有问题后再发测试环境 测试环境再发预发布 预发布通
  • 打工族必看!省钱订餐攻略,经本人亲测多日!

    这不是广告哦 不像其他平台一样需要扫码关注等等 我的常用平台是不需要关注的 只要领取即可使用 我已经在这个平台点外卖很久了 几个月了 平常我会在这个平台领取优惠券 和同事一起拼单下单 每个人能省下三四块钱 有时候多的话可以省下七八块钱 这个
  • DevEco studio 一直加载gradle

    在使用DevEco studio 时一直处于提示gradle的问题 该现象和之前的Androoid Studio类似一直去网络中请求下载指定的gradle的版本 此时类比Android Studio先将gradle版本下载下来 然后放置在指
  • snort 源码分析之模式匹配引擎

    snort是一款著名的开源IPS 其主页地址 snort 官网 更详细的介绍网上很多 可自行搜索了解 本博客主要介绍snort 2 9 5版本的模式匹配引擎的加载和匹配 模式匹配引擎主要使用多模式匹配算法和单模式匹配算法 先由多模式匹配算法