一 会话控制块session control block含义
scb是snort用来保存一次完整会话信息,包括会话Key,会话首次时间,会话最终时间,会话过期时间,ttl信息,snort策略ID,服务器端IP,客户端IP,服务端通信端口,客户端通信端口,协议,MAC地址,客户端请求字节数,服务端响应字节数,会话ID,客户端请求包数,服务端响应包数等,其定义在snort源码preprocessors/Session/session_common.h:
typedef struct _SessionControlBlock
{
SessionKey *key; // snort将session存储到hash表,key就是其相应的键值
StreamAppData *appDataList; // 将应用协议(egd, modbus, s7等)和应用协议解析数据及释放解析数据函数组织成双向链表
...
sfaddr_t client_ip; // 客户端IP
sfaddr_t server_ip; // 服务端IP
...
uint64_t initiaorBytes; // 客户端请求字节数
uint64_t responderBytes; // 服务端请求字节数
...
long session_lastest_time; // 会话最近时间
uint64_t requestpacketCount; // 客户端请求包数
uint64_t responsepacketCount; // 服务端请求包数
}SessionControlBlock;
1.SessionKey:snort将所有会话存入hash表,key就是hash表的key,SessionKey定义在session_api.h中:
typedef struct _StreamSessionKey
{
uint32_t ip_l[4];
uint32_t ip_h[4];
uint16_t port_l;
uint16_t port_h;
...
}StreamSessionKey;
snort在会话处理时,会根据key进行判断,如果两个session的key相同,则snort认为是同一个会话,即:两个会话ip,端口等信息一致,比如会话A:192.168.1.10:9000=>192.168.1.12:9000,会话B:192.168.1.12:9000=>192.168.1.10:9000,则A,B是同一个会话。
2.initiaorBytes和responderBytes是当前会话的请求和响应字节数,如果业务层会定期取会话的请求和响应字节数,那么对于同一个会话,这两个值是累加值,当会话关闭,这两个值会被snort清零。
3.每个snort包都包含一个session指针
snort包结构是SFSnortPacket,定义在dynamic-plugins/sf_engine/sf_snort,packet.h:
typedef struct _SFSnortPacket
{
const SFDAQ_PktHdr_t *pkt_header; // 包头信息
const uint8_t *pkt_data; // 指向包数据
...
void *stream_session; // 指向scb
}SFSnortPacket;
这样snort在处理包是可以和会话相关联。
二 会话处理函数集SessionAPI
1.SessionAPI是一个结构体,它包含了会话处理的所有函数指针,其定义在snort源码precessorors/session_api.h:
typedef struct _session_api
{
int version;
...
void *(*get_session)(void *, Packet *, SessionKey *); // 根据snort包和会话key查询会话
...
void (*get_session_bytes)(void *, uint64_t *initiaorBytes, uint64_t *responderBytes); // 从scb拿出请求和响应字节数
...
}SessionAPI;
2.SessionAPI被嵌入到全局对象_dpd中,方便在插件使用:
typedef struct _DynamicPreprocessorData
{
...
SessionAPI *sessionAPI
...
}DynamicPreprocessorData;
3.在插件中使用SessionAPI
static inline void Egd_Save_action(SFSnortPacket *p, EgdSessionData *egdSessionData)
{
...
if (p && p->stream_session) // 使用包里stream_session
{
_dpd.sessionAPI->get_session_id(p->stream_session, &savemdb.sessionID); // 从scb拿出sessionID
}拿出请求和响应字节数
...
}
EgdSessionData *GetEgdSessionData(SFSnortPacket *p, EdgConfig *config)
{
...
_dpd.sessionAPI->set_application_data(p->stream_session, PP_EGD, edgSessionData, FreeEgdSessionData);
}
set_application_data是spp_session.c的setApplicationData函数指针,将edgSessionData和FreeEgdSessionData插入scb的appDataList中并释放老的相同协议数据:
static int setApplicationData(void *scbptr,uint32_t protocol, void *data, StreamAppDataFree free_func)
{
...
if (scbptr) //scbptr指向scb
{
scb = (SessionControlBlock *)scpptr;
appData = scb->appDataList; // 找到协议双向链表
while (appData) // 遍历链表
{
if (appData->protocol == protocol) // 找到目标协议
{
if ((appData->freeFunc) && (appData->datapointer != data)) // 目标结点里数据指针和传入指针不同 则利用free_func将结点里协议数据释放
{
...
}
}
...
}
if (!appData) // 在链表中查不到该协议 则构造新结点插入链表头
{
...
}
}
}
三 初始化会话预处理initializeSessionPreproc
该函数定义在preprocessor/Stream6/spp_session.c:
void initializeSessionPreproc(struct _SnortConfig *sc, char *args)
{
if (session_configuration == NULL) // 会话配置只能初始化一次
{
session_configuration = initSessionConfiguration(); // 分配session_configuration存储空间并初始化相关参数
...
AddFuncToPreprocList(sc, sessionPacketProcessor, PP_SESSION_PRIORITY, PP_SESSION, PROTO_BIT_ALL); // 将sessionPacketProcessor装入snort策略的预处理链表中
}
else
{
WarningMessage("...);
}
}
四 会话预处理sessionPacketProcessor
该函数定义在preprocessor/Stream6/spp_session.c:
static void sessionPacketProcessor(Packet *p, void *context)
{
// 判断p是否合法
// 如果p的scb为空,则根据p的协议从相应SessionCache里的hash表查询scb,这里的hash表就是sfxhash,SessionCache是一个4元素数组,每个元素对应一种协议(tcp,udp,icmp,ip)会话缓存
...
if (t_now - scb->session_lastest_time > scb->session_config->long_connect_time_interval) // 达到会话保存时间 60s
{
scb->session_lastest_time = t_now;
SaveSessionFlowStaticsInfo(scb, 0); // 调用snort里保存网络会话信息接口,将相关信息保存到共享内存,以供审计进程做业务处理
}
...
}
1.会话缓存proto_session_caches
会话缓存是SessionCache结构的数组,维度是4,分别对应tcp,udp,icmp,ip四种协议:
typedef struct _SessionCache
{
SFXHASH *hashTable; // scb存储到sfxhash表
SFXHASH_NODE *nextTimeoutEvalNode;
...
}SessionCache;
2.会话保存时间
会话保存时间是long_connect_time_interval,由snort配置解析函数parseSessionConfiguration从snort,conf解析而得,如下:
3.会话预处理逻辑:
(1)如果packet里的ssnptr为空,则从会话缓存查询scb,并且按照snort配置保存网络会话信息;
(2)否则,获取scb,即scb = p→ssnptr;
(3)调用initializePacketPolicy等函数进行相关处理。
五 会话删除deleteSession
该函数定义在preprocessor/Stream6/spp_session.c:
static int deleteSession(void *sessionCache, void *session, char *delete_reason)
{
...
freeSessionApplicationData(scb); // 是否scb的appDataList 即协议链表
SaveSessionFlowStatisticsInfo(scb,1); // 网络会话断开业务处理
clearSessionApplicationData(scb); // 清除scb的请求字节等数据
}
需要注意是:当会话断开时,scb保存到共享内存后,请求字节数不为0,但其状态已经是close,其值表示相应会话的累加最值。
函数deleteSessionIfClosed会调用deleteSession:
static int deleteSessionIfClosed(Packet *p)
{
...
if (scb->is_session_state & STREAM_STATE_CLOSED) // 会话关闭状态
{
if (scb->is_session_deletion_delayed) // 延迟删除
...
switch (scb->protocol)
{
case IPPROTO_TCP: // 根据不同协议 删除scb
deleteSession(proto_session_caches[SESSION_PROTO_TCP], scb, "closed normally");
...
}
}
...
}
函数Preprocess调用check_session_closed(deleteSessionIfClosed的函数指针),其代码位于detect.c:
int Preprocess(Packet *p)
{
...
if (do_detect)
Detect(p); // snort检测
...
if (session_api)
session_api->check_session_closed(p); // 检测之后就删除会话
}
除了Preprocessor之外,还有pruneOneWaySession=》deleteSession,pruneSessionCache=》deleteSession。