suricata UT之SigTableSetup中关键字的功能函数解析一

本文介绍一些规则中的关键字处理过程,主要以TCP协议的ACK为例进行说明,这类关键字代表了二进制协议的关键字解析方式。

RegisterUnittests中对所有的用例进行注册,其中SigRegisterTests以及SigTableRegisterTests是保证规则成功解析加载,同时能够匹配符合条件的报文的用例。SigTableRegisterTests中的用例由全局变量sigmatch_table来组织,sigmatch_table变量中的用例部分RegisterTests是在SigTableSetup中进行注册的。

函数SigTableSetup对于所有的关键字对应的功能都进行了注册。其中包括常见的key:value形式的关键字,以及只有key的关键字。本章节先讲述一下key:value这些较为常见的关键字,如下这些关键字还是很容易理解的。

DetectSidRegister();
DetectPriorityRegister();
DetectPrefilterRegister();
DetectRevRegister();
DetectClasstypeRegister();
DetectReferenceRegister();
DetectTagRegister();
DetectThresholdRegister();
DetectMetadataRegister();
DetectMsgRegister();
DetectAckRegister();
DetectSeqRegister();
DetectContentRegister();
DetectUricontentRegister();

suricata使用sigmatch_table管理所有关键字所具备的功能,依次将对应的功能函数填充sigmatch_table数组中的指定字段。

以SigTableSetup函数中的DetectAckRegister函数为例加以说明,如下:

void DetectAckRegister(void)
{
    sigmatch_table[DETECT_ACK].name = "ack";
    sigmatch_table[DETECT_ACK].desc = "check for a specific TCP acknowledgement number";
    sigmatch_table[DETECT_ACK].url = DOC_URL DOC_VERSION "/rules/header-keywords.html#ack";
    sigmatch_table[DETECT_ACK].Match = DetectAckMatch;
    sigmatch_table[DETECT_ACK].Setup = DetectAckSetup;
    sigmatch_table[DETECT_ACK].Free = DetectAckFree;

    sigmatch_table[DETECT_ACK].SupportsPrefilter = PrefilterTcpAckIsPrefilterable;
    sigmatch_table[DETECT_ACK].SetupPrefilter = PrefilterSetupTcpAck;

    sigmatch_table[DETECT_ACK].RegisterTests = DetectAckRegisterTests;
}

Match对应的函数如下:

static int DetectAckMatch(DetectEngineThreadCtx *det_ctx,
                          Packet *p, const Signature *s, const SigMatchCtx *ctx)
{
    const DetectAckData *data = (const DetectAckData *)ctx;

    /* This is only needed on TCP packets */
    if (!(PKT_IS_TCP(p)) || PKT_IS_PSEUDOPKT(p)) {
        return 0;
    }

    return (data->ack == TCP_GET_ACK(p)) ? 1 : 0;
}

该函数会在检测报文的时候执行,比较规则中的ACK和报文中的ACK是否匹配。通过其入参可以得知Packet *p为需要匹配的报文,SigMatchCtx *ct存储的是从规则文件中解析出来的ACK字段的值存。
该函数功能是。

那么Match函数再什么时候执行呢?此处只是注册了match 的功能。
1,Match函数的执行是在DetectEngineInspectRulePacketMatches函数中,该函数中s->sm_arrays[DETECT_SM_LIST_MATCH]数组中的Match函数都会得到执行。

2,DetectEngineInspectRulePacketMatches函数作为一个回调函数注册在报文检测引擎的v1.Callback字段中。注册阶段是在将加载的规则转换成引擎上下文下挂的内部结构阶段,执行路径为
LoadSignatures->SigLoadSignatures->SigGroupBuild->SigMatchPrepare->DetectEnginePktInspectionSetup->DetectEnginePktInspectionAppend。
DetectEnginePktInspectionSetup->DetectEnginePktInspectionAppend中其实会注册两种回调函数,一种就是像ACK这种,属于二进制协议字段的匹配,往往是数字的匹配,例如DetectEngineInspectRulePacketMatches,会执行s->sm_arrays[DETECT_SM_LIST_MATCH]中的Match内容。另外一只是字符串的匹配,往往匹配的是载荷内容或者文本协议(HTTP)的内容,例如DetectEngineInspectRulePayloadMatches,关于字符串匹配这种关键字将会在下一章讲述。

3,最终的执行路径为DetectRun->DetectRulePacketRules->DetectEnginePktInspectionRun中的e->v1.Callback,v1.Callback即步骤1,2中的内容

Setup 对应的函数为DetectAckSetup,该函数的目的是在规则解析的时候执行,将一行字符串的规则转换为规则上下文中对应的字段值。当然不同的关键字其解析的key value 的组织形式会有一定的差异,如下:

static int DetectAckSetup(DetectEngineCtx *de_ctx, Signature *s, const char *optstr)
{
    DetectAckData *data = NULL;
    SigMatch *sm = NULL;

    data = SCMalloc(sizeof(DetectAckData));
    if (unlikely(data == NULL))
        goto error;

    sm = SigMatchAlloc();
    if (sm == NULL)
        goto error;

    sm->type = DETECT_ACK;

    if (-1 == ByteExtractStringUint32(&data->ack, 10, 0, optstr)) {
        goto error;
    }
    sm->ctx = (SigMatchCtx*)data;

    SigMatchAppendSMToList(s, sm, DETECT_SM_LIST_MATCH);
    s->flags |= SIG_FLAG_REQUIRE_PACKET;

    return 0;

error:
    if (data)
        SCFree(data);
    if (sm)
        SigMatchFree(sm);
    return -1;

}

optstr为规则文本中对应的ACK值,会被解析到SigMatch *sm 这样的结构中,sm这挂在s->init_data>smlists[DETECT_SM_LIST_MATCH],Signature *s就是规则解析出来之后再内存中的存储结构。对于规则中像ACK这种会进行数字匹配的key:value形式(例如seq),都是放在Signature *s中的s->init_data->smlists[DETECT_SM_LIST_MATCH]数组中。对于一些辅助字段例如sid,metadat往往直接挂在引擎上下文结构下面,例如DetectSidRegister,以及DetectMetadataRegister。

Free 对应的函数为DetectAckFree,功能是内存的回收。由于在Setup阶段会为规则申请对应的空间,在清理的时候需要释放这些空间。

当然对于ACK这样的关键字还支持prefilter,关于prefilter后面会抽出单独一篇进行介绍,这里不再赘述。

RegisterTests对应的就是对于该关键字UT用例的注册,相应的函数为DetectAckRegisterTests。在该函数中,注册了一条AC规则的解析用例DetectAckSigTest01。该用例目的很明确,就是将构造的TCP报文,设置特定的ACK值,送入到载入了若干个带有ACK的规则的引擎中,查看最终的结果是否符合预期。具体包含如下步骤:

  • 第一步,构造报文用到了前一篇文章中提及的UTHBuildPacket函数,并设置特定的ACK。
  • 第二步,函数DetectEngineCtxInit初始化引擎的上下文。
  • 第三步,SigInit加载若干个有效和非法的带有ACK的规则。
  • 第四步,SigGroupBuild将加载解析好的规则转化为引擎运行时的结构,主要是初始化引擎上下文的各个字段。
  • 第五步,由于suricata是多线程的架构,因此可能存在多个检测线程,DetectEngineThreadCtxInit就是初始化特定的检测线程的数据。
  • 第六步,SigMatchSignatures入参为引擎下文上,报文,目的是对于报文进行规则检测匹配。可以看到最终调用的是DetectRun函数。
  • 第七步,PacketAlertCheck检测报文的匹配结果是否是特定的sid。可以看到匹配的结果存储在报文上下文中,即p->alerts.alerts[i].s->id
  • 第八步,上述各个过程涉及到的内存清理,即SigGroupCleanup,DetectEngineThreadCtxDeinit等过程。上述的Free函数在此处就有执行,执行路径为SigCleanSignatures->SigFree->SigMatchFree->sigmatch_table[sm->type].Free

其实多数关键字用例,都是遵循着上述的7个步骤,只是在构造报文和规则方面存在差异。上述七步也是suricata迎请最为重要的代码部分,涵盖了规则加载,解析,引擎上下文构造,规则匹配等多个过程,这也是学习suricata最为重要的一些函数。

本文为CSDN村中少年原创文章,未经允许不得转载,博主链接这里

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

村中少年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值