这一章节包括两部分,snort的规则匹配过程和插件运行的过程。在之前的章节中也能看到,插件的主要功能函数eval其实是保存在了规则最后解析出的那个数据结构中的,所有这里将两部分的知识写在同一章节了。
这里接上文数据包捕获后的回调packet_callback
继续说,首先回顾一下代码
DAQ_Verdict Snort::packet_callback(
void*, const DAQ_PktHdr_t* pkthdr, const uint8_t* pkt)
{
...............
DAQ_Verdict verdict = process_packet(s_packet, pkthdr, pkt); //数据包解析,以及规则匹配
ActionManager::execute(s_packet); //这里对这个包进行一些action的操作,主要是基于Action接口,具体见下文
...............
return verdict;
}
这里需要重点看的,是process_packet
这个函数,它在里面进行了数据包的解析工作,并进入检测功能的入口。
DAQ_Verdict Snort::process_packet(
Packet* p, const DAQ_PktHdr_t* pkthdr, const uint8_t* pkt, bool is_frag)
{
aux_counts.rx_bytes += pkthdr->caplen;
PacketManager::decode(p, pkthdr, pkt, is_frag); //对数据包进行解码
.................
if (is_frag)
{
p->packet_flags |= (PKT_PSEUDO | PKT_REBUILT_FRAG);
p->pseudo_type = PSEUDO_PKT_IP;
}
set_policy(p); // FIXIT-M should not need this here
if ( !(p->packet_flags & PKT_IGNORE) )
{
clear_file_data();
main_hook(p); //检测功能的入口
// FIXIT-L remove this onload when DAQng can push multiple packets
if ( p->flow )
DetectionEngine::onload(p->flow);
}
..............
return DAQ_VERDICT_PASS;
}
这个main_hook在其他地方进行了初始化,这里它所对应的函数实际为DetectionEngine::inspect
bool DetectionEngine::inspect(Packet* p)
{
bool inspected = false;
{
PacketLatency::Context pkt_latency_ctx { p };
if ( p->ptrs.decode_flags & DECODE_ERR_FLAGS ) //如果包解析失败了,就将其drop掉
{
if ( SnortConfig::inline_mode() and
SnortConfig::checksum_drop(p->ptrs.decode_flags & DECODE_ERR_CKSUM_ALL) )
{
p->active->drop_packet(p);
}
}
else
{
enable_content(p);
p->alt_dsize = 0; // FIXIT-H should be redundant
InspectorManager::execute(p); //这里进行一些预处理的操作
inspected = true;
if ( !all_disabled(p) )
{
if ( detect(p, true) ) //实际的检测功能
return false; // don't finish out offloaded packets
}
}
finish_inspect_with_latency(p);
}
finish_inspect(p, inspected);
return true;
}
这里detect
函数会进入检测部分的处理,中间的调用过程这里就不跟了,它最后会调用到static void fpEvalPacket(Packet* p, FPTask task)
这个函数
static void fpEvalPacket(Packet* p, FPTask task)
{
OtnxMatchData* omd = p->context->otnx;
/* Run UDP rules against the UDP header of Teredo packets */
// FIXIT-L udph is always inner; need to check for outer
if ( p->ptrs.udph && (p->proto_bits & (PROTO_BIT__TEREDO | PROTO_BIT__GTP)) )
fpEvalPacketUdp(p, omd, task);
switch (p->type())
{
case PktType::IP:
fpEvalHeaderIp(p, omd, task);
fpEvalHeaderSvc(p, omd, SNORT_PROTO_IP, task);
break;
case PktType::ICMP:
fpEvalHeaderIcmp(p, omd, task);
fpEvalHeaderSvc(p, omd, SNORT_PROTO_ICMP, task);
break;
case PktType::TCP:
fpEvalHeaderTcp(p, omd, task);
fpEvalHeaderSvc(p, omd, SNORT_PROTO_TCP, task);
break;
case PktType::UDP:
fpEvalHeaderUdp(p, omd, task);
fpEvalHeaderSvc(p, omd, SNORT_PROTO_UDP, task);
break;
...............
default:
break;
}
}
这里会根据各个协议不同的情况进行处理,这里以tcp为例,继续往下分析。首先调用fpEvalHeaderTcp
函数
static inline void fpEvalHeaderTcp(Packet* p, OtnxMatchData* omd)
{
PortGroup* src = nullptr, * dst = nullptr, * any = nullptr;
//这里首先根据端口信息(p->ptrs.dp目的端口 p->ptrs.sp源端口)等,获取到相应的PortGroup,赋值给src、dst、any变量
if ( !prmFindRuleGroupTcp(SnortConfig::get_conf()->prmTcpRTNX, p->ptrs.dp, p->ptrs.sp, &src, &dst, &any) )
return;
//然后根据相应的PortGroup的情况,分别进行处理
if ( dst )
fpEvalHeaderSW(dst, p, 1, 0, 0, omd);
if ( src )
fpEvalHeaderSW(src, p, 1, 0, 0, omd);
if ( any )
fpEvalHeaderSW(any, p, 1, 0, 0, omd);
}
该函数首先会根据端口等信息获取到对应的portgroup对象,而这里的portgroup对象,在前面的章节中也有提到,它本身是根据端口保存了带有相应端口的规则信息的,在获取到对应端口的portgroup对象之后,调用fpEvalHeaderSW
函数,最后这里会调用进detection_option_node_evaluate
,使用相关的规则的node的evaluate函数,对包的信息进行检测和处理,这里对应的evaluate函数即是先前规则解析到对应插件的检测功能函数,调用完成后,会返回相应的检测结果,这里由一个枚举类型描述
enum EvalStatus { NO_MATCH, MATCH, NO_ALERT, FAILED_BIT };
这里简单列一下IpsOptions中的ack检测的,eval函数
IpsOption::EvalStatus TcpAckOption::eval(Cursor&, Packet* p)
{
Profile profile(tcpAckPerfStats);
if ( p->ptrs.tcph && config.eval(p->ptrs.tcph->th_ack) )
return MATCH;
return NO_MATCH;
}
可以看到,这里仅仅是检查了包的ack项,如果存在的话,则返回MACTH
规则匹配和插件运行这一部分其实比较复杂,我这部分的代码也没有逐行看完,只有了解大致的流程,首先它会根据端口信息获取到对应的portgroup,再对portgroup中的信息进行遍历,并调用相关插件的eval函数,若检测成功则返回MATCH。