本文介绍一些规则中的关键字处理过程,主要以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村中少年原创文章,未经允许不得转载,博主链接这里。