suricata4.0.1源码分析¶
约定¶
代码对应位置
在做review的时候,会在这个链接中写相关注释,尽量不在本文档中贴代码。交流方式
可以通过微信promisechen或issue(推荐)进行沟通可在这里建立issue https://github.com/promisechen/suricata/issues假定按照这样进行配置编译
./configure –prefix=/usr –sysconfdir=/etc –localstatedir=/var –enable-nfqueue –enable-lua
当前进度:开始分析PostConfLoadedDetectSetup->DetectEngineCtxInitReal->ActionInitConfig
=======当前进度:开始分析PostConfLoadedSetup->MayDaemonize
>>>>>>> 0b6744533e3fa724b7fd4af7b05fc93a27b97fb6概述及全局观览¶
全局变量¶
定义 | 说明 |
---|---|
host_mode | 包括router和sniffer-only、auto三种模式,当使用auto模式时,在ips状态下设置为router,在ids下为sniffer-only |
alpd_ctx | 协议识别的全局变量,存放了各种协议识别使用的数据:如字符串,状态机等 |
sigmatch_table | 特征关键字匹配表,sid、priority、msg、within、distance等等。该变量主要应用于应用的识别和规则的检测。 |
tmqh_table | 提供了4种类型队列:simple,flow,packetpool,nfq,其中nfq不同于其他三种队列,他是内核中的Netfilter Queue。 simple是简单的先入先出的普通队列,packetpool |
tmm_modules | 线程模块的全局数组,有pcap/pfring/netmap等收包类线程,有flow管理类 |
alpd_ctx介绍及内存布局¶
sigmatch_table介绍(todo)¶
tmqh_table对应的Tmqh结构体说明¶
todo:tm_queuehandlers.h中
enum { TMQH_SIMPLE, TMQH_NFQ, TMQH_PACKETPOOL, TMQH_FLOW, TMQH_SIZE, }; typedef struct Tmqh_ { const char *name; Packet *(*InHandler)(ThreadVars *); /*< 是针对ThreadVars的输入,而非入队的意思,即从相应队列取出Packet。by clx 20171107 */ void (*InShutdownHandler)(ThreadVars *); /*< 貌似是把队列中的报文都拿出来的意思??。by clx 20171107 */ void (*OutHandler)(ThreadVars *, Packet *); /*< 将报文压入队列中。by clx 20171107*/ void *(*OutHandlerCtxSetup)(const char *); /*< 初始化线程上下文。by clx 20171107*/ void (*OutHandlerCtxFree)(void *); /*< 释放线程上下文。by clx 20171107*/ void (*RegisterTests)(void); } Tmqh; Tmqh tmqh_table[TMQH_SIZE];
main¶
- 主线
RunModeRegisterRunModes¶
调用RunModeIdsXXXXRegister将各种模式注册好(todo:以pcap的模式作为模板进行研究) 他们都调用RunModeRegisterNewRunMode紧张注册,
GlobalsInitPreConfig¶
初始化trans_q 和data_queues(todo:深入分析两个变量) CreateLowercaseTable(字母转换数组初始化) TimeInit SupportFastPatternForSigMatchTypes三个函数逐个调用。 SupportFastPatternForSigMatchTypes将DETECT_SM_LIST_PMATCH加入sm_fp_support_smlist_list链表,优先级是3
1.3 todo:
PostConfLoadedSetup¶
- MpmTableSetup
注册各种多模匹配算法,将ac ac-cuda ac_bs ac_tile hyperscan 这几种多模式匹配算法,注册到mpm_table(结构为MpmTableElmt)
全局变量中 mpm_default_matcher作为默认配置
- SpmTableSetup
注册各种单模匹配算法,将bm hyperscan这两种单模式匹配算法,注册到spm_table(结构为SpmTableElmt)的全局变量中
AppLayerSetup
AppLayerProtoDetectSetup
主要是对alpd_ctxl4层协议(tcp,udp,icmp,sctp)层面的多模和单模的注册和初始化, 主要是给alpd_ctx.spm_global_thread_ctx和MpmInitCtx调用进行赋值(todo:多模匹配算法插件接口)
alpd_ctx是协议识别的全局变量,存放了各种协议识别使用的数据:如字符串,状态机等
AppLayerParserSetup
AppLayerParserRegisterProtocolParsers
注册协议识别字符串特征或端口特征;注册协议解析函数回调
RegisterHTPParsers
http协议识别字符串注册,解析函数注册
AppLayerProtoDetectConfProtoDetectionEnabled(判断该协议是否启动)
AppLayerProtoDetectRegisterProtocol(注册http协议识别)
HTPRegisterPatternsForProtocolDetection:(将该协议识别的特征串放入alpd_ctx相应的状态机中)
这里将调用AppLayerProtoDetectPMRegisterPatternCI/CS注册字符串特征, 如果有端口特征则通过AppLayerProtoDetectPPRegister注册(如RegisterDNSUDPParsers),该函数有2个参数ProbingParserFPtr, 当命中端口后,还会运行该函数做进一步判断。
AppLayerParserRegisterXXXXX(该系列函数是注册协议解析的相关插件,todo:研究解析过程)
AppLayerProtoDetectPrepareState
(todo:详细分析协议维度字符串添加过程、内存布局):添加特征到状态机并编译
- AppLayerProtoDetectPMMapSignatures :添加到状态机
- AppLayerProtoDetectPMPrepareMpm :编译
SCHInfoLoadFromConfig
将配置文件中的host-os-policy的配置加入到一棵radix树上,在匹配是使用。(todo:识别或重组时使用??)
- SigTableSetup
注册关键字的各种回调,比如注册sid,content等相关回调,在读取加载规则库、应用识别的时候将调用相关回调函数. 目前看到这些函数应该是被SigInit调用.这里注册的关键非常的多,可以慢慢分析自己感兴趣的,其中发现很多关键字没有注册 Match这个回调。http相关的注册项有很多,http的一些注册还会初始化一些资源,后面以DetectHttpUriRegister为例。
- DetectSidRegister
注册了重要的函数DetectSidSetup,该函数将在加载规则库的时候,被调用。
DetectSidSetup将会把规则库中的sidstr赋给s->id
static int DetectSidSetup (DetectEngineCtx *de_ctx, Signature *s, const char *sidstr) { unsigned long id = 0; char *endptr = NULL; id = strtoul(sidstr, &endptr, 10); if (endptr == NULL || *endptr != '\0') { SCLogError(SC_ERR_INVALID_SIGNATURE, "invalid character as arg " "to sid keyword"); goto error; } if (id >= UINT_MAX) { SCLogError(SC_ERR_INVALID_NUMERIC_VALUE, "sid value to high, max %u", UINT_MAX); goto error; } if (id == 0) { SCLogError(SC_ERR_INVALID_NUMERIC_VALUE, "sid value 0 is invalid"); goto error; } if (s->id > 0) { SCLogError(SC_ERR_INVALID_RULE_ARGUMENT, "duplicated 'sid' keyword detected"); goto error; } s->id = (uint32_t)id; return 0; error: return -1; }
DetectPriorityRegister
注册了重要的函数DetectPrioritySetup,该函数将在加载规则库的时候,被调用。 DetectPrioritySetup将把规则库中的rawstr赋值给s->prio,但是相对DetectSidSetup多了一些pcre_exec、pcre_copy_substring相关函数调用,做什么用的呢?? 他们主要是判断关键字是否合法,并提取相关字段,注意regex、regex_study是static类型的,这2个全局变量在很多文件中都存在。
DetectHttpUriRegister 也注册了Setup回调。注册回调之后,重点注册了DetectAppLayerMpmRegister和DetectAppLayerInspectEngineRegister(todo:检查相关注册)
TmqhSetup
注册4中类型队列,后续各线程交互时使用
TmqhSimpleRegister
简单的普通的入队出队队列,主要注册了TmqhInputSimple和TmqhOutputSimple,TmqhInputSimple 输入回调,即从相应队列中获取报文,这里的input是针对ThreadVars来说的。
TmqhNfqRegister
内核层面的队列,即 Netfilter Queue队列,与其他三种队列不同,他只需要注册OutHandler
TmqhPacketpoolRegister
这个更像是一个dpdk中的mbuf,内核中的skb_mbuf之类的ringbuffer. 这个其实更像说是内存池,这种队列应该是用在 收包这一层层面。
- TmqhFlowRegister
根据flow进行分发的队列,出队列与Simple是一样的,入队会根据flow的hash进行除余得到相应的队列。 根据配置的不同,将选择不同的分发算法:TmqhOutputFlowHash TmqhOutputFlowIPPair
TmqhOutputFlowIPPair的部分代码
void TmqhOutputFlowIPPair(ThreadVars *tv, Packet *p) { int16_t qid = 0; uint32_t addr_hash = 0; int i; TmqhFlowCtx *ctx = (TmqhFlowCtx *)tv->outctx; if (p->src.family == AF_INET6) { for (i = 0; i < 4; i++) { addr_hash += p->src.addr_data32[i] + p->dst.addr_data32[i]; } } else { addr_hash = p->src.addr_data32[0] + p->dst.addr_data32[0]; } /* we don't have to worry about possible overflow, since * ctx->size will be lesser than 2 ** 31 for sure */ qid = addr_hash % ctx->size; PacketQueue *q = ctx->queues[qid].q; SCMutexLock(&q->mutex_q); PacketEnqueue(q, p); SCCondSignal(&q->cond_q); SCMutexUnlock(&q->mutex_q); return; }
SigParsePrepare
- 初始化config_pcre、config_pcre_extra、option_pcre三个全局变量,后面解析使用
opts |= PCRE_UNGREEDY; config_pcre = pcre_compile(regexstr, opts, &eb, &eo, NULL); if(config_pcre == NULL) { SCLogError(SC_ERR_PCRE_COMPILE, "pcre compile of \"%s\" failed at offset %" PRId32 ": %s", regexstr, eo, eb); exit(1); } config_pcre_extra = pcre_study(config_pcre, 0, &eb); if(eb != NULL) { SCLogError(SC_ERR_PCRE_STUDY, "pcre study failed: %s", eb); exit(1); } regexstr = OPTION_PCRE; opts |= PCRE_UNGREEDY; option_pcre = pcre_compile(regexstr, opts, &eb, &eo, NULL); if(option_pcre == NULL) { SCLogError(SC_ERR_PCRE_COMPILE, "pcre compile of \"%s\" failed at offset %" PRId32 ": %s", regexstr, eo, eb); exit(1); }
- xxxTagInit
存储结构的初始化,有三种存储方式STORAGE_HOSTSTORAGE_FLOWSTORAGE_IPPAIR分别用于不同类型的存储。 这里共初始化了host_tag_id、flow_tag_id、threshold_id、host_bit_id、ippair_bit_id5个储存实体对象。 应该与与规则中的tag、threshould关键字的实现相关;
static StorageList *storage_list = NULL; /**< by clx 20171109 储存链表*/ static int storage_max_id[STORAGE_MAX]; /**< by clx 20171109 三种储存方式的id编号*/ static int storage_registraton_closed = 0; /**< by clx 20171109 关闭标记,当设置为1时,不在注册*/ static StorageMapping **storage_map = NULL;/**< by clx 20171109 将储存链表上所有storage实体做映射成二维数组, 通过储存类型和在该类型的储存方式对应的id进行读取。如storage_map[STORAGE_HOST][host_tag_id]读取host_tag_id的存储注册函数*/
- DetectAddressTestConfVars、DetectPortTestConfVars
检查配置文件中vars.address-groups和vars.port-groups的合法性。
- RegisterAllModules
注册线程模式:流表管理相关、报文接收方式(pcap/pfring/netmap等) 线程类型共下面几类:其中文接收方式使用的是RECEIVE_TM和DECODE_TM, 其中五元组表有MANAGEMENT_TM|TM_FLAG_STREAM_TM|TM_FLAG_DETECT_TM三个专用类型,以af-packet为例
#define TM_FLAG_RECEIVE_TM 0x01 #define TM_FLAG_DECODE_TM 0x02 #define TM_FLAG_STREAM_TM 0x04 #define TM_FLAG_DETECT_TM 0x08 #define TM_FLAG_LOGAPI_TM 0x10 /**< TM is run by Log API */ #define TM_FLAG_MANAGEMENT_TM 0x20 #define TM_FLAG_COMMAND_TM 0x40
- 以af-packet为例:TmModuleDecodeAFPRegister和TmModuleReceiveAFPRegister分别定义了收包和解码的回调。
void TmModuleReceiveAFPRegister (void) { tmm_modules[TMM_RECEIVEAFP].name = "ReceiveAFP"; tmm_modules[TMM_RECEIVEAFP].ThreadInit = NoAFPSupportExit; tmm_modules[TMM_RECEIVEAFP].Func = NULL; tmm_modules[TMM_RECEIVEAFP].ThreadExitPrintStats = NULL; tmm_modules[TMM_RECEIVEAFP].ThreadDeinit = NULL; tmm_modules[TMM_RECEIVEAFP].RegisterTests = NULL; tmm_modules[TMM_RECEIVEAFP].cap_flags = 0; tmm_modules[TMM_RECEIVEAFP].flags = TM_FLAG_RECEIVE_TM; } /** * \brief Registration Function for DecodeAFP. * \todo Unit tests are needed for this module. */ void TmModuleDecodeAFPRegister (void) { tmm_modules[TMM_DECODEAFP].name = "DecodeAFP"; tmm_modules[TMM_DECODEAFP].ThreadInit = NoAFPSupportExit; tmm_modules[TMM_DECODEAFP].Func = NULL; tmm_modules[TMM_DECODEAFP].ThreadExitPrintStats = NULL; tmm_modules[TMM_DECODEAFP].ThreadDeinit = NULL; tmm_modules[TMM_DECODEAFP].RegisterTests = NULL; tmm_modules[TMM_DECODEAFP].cap_flags = 0; tmm_modules[TMM_DECODEAFP].flags = TM_FLAG_DECODE_TM; }
AppLayerHtpNeedFileInspection
为htp库设置一些标记,如解析响应、解析请求的标记
StorageFinalize
将xxxTagInit注册的实体,将储存链表上所有storage实体做映射成二维数组, 通过储存类型和在该类型的储存方式对应的id进行读取。如storage_map[STORAGE_HOST][host_tag_id]读取host_tag_id的存储注册函数
TmModuleRunInit
调用tmm_modules[i]->Init进行模块初始化
MayDaemonize
后台运行,检查是否进入Daemon模式。若需要进入Daemon模式,则会检测pidfile是否已经存在(daemon下只能有一个实例运行),然后进行Daemonize,最后创建一个pidfile。Daemonize的主要思路是:fork->子进程调用setsid创建一个新的session,关闭stdin、stdout、stderr,并告诉父进程 –> 父进程等待子进程通知,然后退出 –> 子进程继续执行.InitSignalHandler
注册各种信号,初始化信号handler。首先为SIGINT(ctrl-c触发)和SIGTERM(不带参数kill时触发)这两个常规退出信号分别注册handler,对SIGINT的处理是设置程序的状态标志为STOP,即让程序优雅地退出;而对SIGTERM是设置为KILL,即强杀。接着,程序会忽略SIGPIPE(这个信号通常是在Socket通信时向已关闭的连接另一端发送数据时收到)和SIGSYS(当进程尝试执行一个不存在的系统调用时收到)信号,以加强程序的容错性和健壮性。- HostInitConfig
与FlowInitConfig类似: host_hash,host_config
- SCAsn1LoadConfig
读取asn1-max-frames设置到全局变量asn1_max_frames_config
- CoredumpLoadConfig
coredump设置
- DecodeGlobalConfig
设置是否解析g_teredo_enabled
- PreRunInit
- 为流表,ip分片重组,tcp分片重组分配内存并进行初始化
StatsInit 统计初始化
DefragInit 分片重组,暂不关心
- FlowInitConfig
初始化flow的配置,flow_config是flow的全局配置,flow_hash是hash表,flow_spare_q flow节点 的队列,调用FlowAlloc分配内存之后,将把分配的flow放到flow_spare_q队列中。
/* global flow config */ typedef struct FlowCnf_ { uint32_t hash_rand; uint32_t hash_size; /**<by clx 20171110 hash筒的大小*/ uint64_t memcap; /**<by clx 20171110 flows最大占的内存限制*/ uint32_t max_flows; uint32_t prealloc; /**<by clx 20171110 最大并发数*/ uint32_t timeout_new; uint32_t timeout_est; uint32_t emerg_timeout_new; uint32_t emerg_timeout_est; uint32_t emergency_recovery; } FlowConfig;
- IPPairInitConfig
类似FlowInitConfig初始化ip的相关内存
LogFilestoreInitConfig 文件存储的配置
StreamTcpInitConfig 流重组的初始化,todo:暂不看tcp流重组的细节
AppLayerParserPostStreamSetup todo:暂不看tcp流重组的细节
AppLayerRegisterGlobalCounters todo:设置一些计数配置,后面研究下咋用的。
<<<<<<< HEAD
PostConfLoadedDetectSetup¶
- SCClassConfInit 解析classification.config配置文件相关;为解析classification.config
- ,注册正则匹配handle和相关正则。
从classification.config摘抄,下面的第一个规则指定了类型为attempted-admin,但是 他又重新设定了优先级为10,所以他最终优先级为10.而第二个规则只指定了attempted-admin,那么他就 使用默认优先级1.
# # config classification:shortname,short description,priority # config classification: attempted-admin,Attempted Administrator Privilege Gain,1 # Here are a few example rules: # # alert TCP any any -> any 80 (msg: "EXPLOIT ntpdx overflow"; # dsize: > 128; classtype:attempted-admin; priority:10; # # alert TCP any any -> any 25 (msg:"SMTP expn root"; flags:A+; \ # content:"expn root"; nocase; classtype:attempted-recon;) # # The first rule will set its type to "attempted-admin" and override # the default priority for that type to 10. # # The second rule set its type to "attempted-recon" and set its # priority to the default for that type. #
- SCReferenceConfInit();
解析reference.config配置文件相关;为解析reference.config,注册则匹配handle和相关正则。 对应的cveexploitdb等相关连接
# config reference: system URL config reference: bugtraq http://www.securityfocus.com/bid/
- SetupDelayedDetect
读取detect.delayed-detect的配置,detect.delayed-detect表示在加载规则库之前就启动 抓包,这样能够在IPS模式下减少系统的down time(宕机时间),但注意载离线模式下,将忽略改标记。
- DetectEngineMultiTenantSetup
目前给我的感觉是多级的检测,后面在看吧。
- DetectEngineCtxInit/DetectEngineCtxInitMinimal
初始化检测引擎上下文。
DetectEngineCtxLoadConf 加载配置
创建各种hash表
SigGroupHeadHashInit(de_ctx); MpmStoreInit(de_ctx); ThresholdHashInit(de_ctx); DetectParseDupSigHashInit(de_ctx); DetectAddressMapInit(de_ctx); (void)SRepInit(de_ctx);
SCClassConfLoadClassficationConfigFile 读取classfication的配置,并完成初始化
- SCClassConfInitContextAndLocalResources
注册hashtable de_ctx->class_conf_ht初始化以及fd文件描述符
- SCClassConfParseFile
读取解析并加入de_ctx->class_conf_ht hash表中
- SCClassConfIsLineBlankOrComment
过滤掉空行和注释行。
SCClassConfAddClasstype
解析一行并将相关字符 付给 SCClassConfClasstype 结构,而后将 其加到de_ctx->class_conf_ht hash表中,至此classification.config的 文件解析相关完毕。
对classification.config的做个简单的总结: 在PostConfLoadedDetectSetup--》SCClassConfInit中注册了相关正则匹配对象regex,在SCClassConfInitContextAndLocalResources 中注册了de_ctx->class_conf_ht hash表,最后在SCClassConfAddClasstype将classification.config的相关 配置都加载到de_ctx->class_conf_ht hash表中。
/** * \brief Parses a line from the classification file and adds it to Classtype * hash table in DetectEngineCtx, i.e. DetectEngineCtx->class_conf_ht. * * \param rawstr Pointer to the string to be parsed. * \param index Relative index of the string to be parsed. * \param de_ctx Pointer to the Detection Engine Context. * * \retval 0 On success. * \retval -1 On failure. */ static int SCClassConfAddClasstype(char *rawstr, uint8_t index, DetectEngineCtx *de_ctx) { char ct_name[64]; char ct_desc[512]; char ct_priority_str[16]; int ct_priority = 0; uint8_t ct_id = index; SCClassConfClasstype *ct_new = NULL; SCClassConfClasstype *ct_lookup = NULL; #define MAX_SUBSTRINGS 30 int ret = 0; int ov[MAX_SUBSTRINGS]; //by clx 20171113 之前注册了相关正则,这里进行正则的匹配,并将 //名称,描述,优先级解析出来。 ret = pcre_exec(regex, regex_study, rawstr, strlen(rawstr), 0, 0, ov, 30); if (ret < 0) { SCLogError(SC_ERR_INVALID_SIGNATURE, "Invalid Classtype in " "classification.config file"); goto error; } /* retrieve the classtype name */ ret = pcre_copy_substring((char *)rawstr, ov, 30, 1, ct_name, sizeof(ct_name)); if (ret < 0) { SCLogInfo("pcre_copy_substring() failed"); goto error; } /* retrieve the classtype description */ ret = pcre_copy_substring((char *)rawstr, ov, 30, 2, ct_desc, sizeof(ct_desc)); if (ret < 0) { SCLogInfo("pcre_copy_substring() failed"); goto error; } /* retrieve the classtype priority */ ret = pcre_copy_substring((char *)rawstr, ov, 30, 3, ct_priority_str, sizeof(ct_priority_str)); if (ret < 0) { SCLogInfo("pcre_copy_substring() failed"); goto error; } if (strlen(ct_priority_str) == 0) { goto error; } ct_priority = atoi(ct_priority_str); // by clx 20171113 创建一个对象,并将其加入hash表中。 /* Create a new instance of the parsed Classtype string */ ct_new = SCClassConfAllocClasstype(ct_id, ct_name, ct_desc, ct_priority); if (ct_new == NULL) goto error; /* Check if the Classtype is present in the HashTable. In case it's present * ignore it, as it is a duplicate. If not present, add it to the table */ ct_lookup = HashTableLookup(de_ctx->class_conf_ht, ct_new, 0); if (ct_lookup == NULL) { if (HashTableAdd(de_ctx->class_conf_ht, ct_new, 0) < 0) SCLogDebug("HashTable Add failed"); } else { SCLogDebug("Duplicate classtype found inside classification.config"); if (ct_new->classtype_desc) SCFree(ct_new->classtype_desc); if (ct_new->classtype) SCFree(ct_new->classtype); SCFree(ct_new); } return 0; error: return -1; }
SCRConfLoadReferenceConfigFile 与classfication类似
=======
>>>>>>> 0b6744533e3fa724b7fd4af7b05fc93a27b97fb6
参考文献¶
<<<<<<< HEAD开源引擎借鉴¶
=======
开源引擎借鉴¶
>>>>>>> 0b6744533e3fa724b7fd4af7b05fc93a27b97fb6支持协议维度识别和解析协议识别、解析插件化特征区分服务端和客户端