Suricata-Stream引擎

Thank Zhihao Tao for your hard work. The document spent countless nights and weekends, using his hard work to make it convenient for everyone.
If you have any questions, please send a email to zhihao.tao@outlook.com


1. 流引擎

流引擎(Stream)跟踪TCP连接。该引擎分为两部分:流跟踪引擎和重组引擎。流跟踪引擎监视连接状态,重组引擎将按原样重组流。

2. 流跟踪引擎

2.1 配置选项

2.1.1 memcap选项

流引擎有两个可以设置的memcap。一个用于流跟踪引擎,一个用于重组引擎。

  • 流跟踪引擎将流的信息保留在内存中。有关状态TCP序列号TCP窗口的信息。为了保留此信息,它可以利用memcap允许的容量。
#define STREAMTCP_DEFAULT_MEMCAP                (32 * 1024 * 1024) /* 32mb */
SC_ATOMIC_SET(stream_config.memcap, STREAMTCP_DEFAULT_MEMCAP);
stream:
  memcap: 64mb                # Max memory usage (in bytes) for TCP session tracking
  • 重组引擎必须将数据段保存在内存中,以便能够重建流。为了避免资源不足,使用memcap限制使用的内存。
#define STREAMTCP_DEFAULT_REASSEMBLY_MEMCAP     (64 * 1024 * 1024) /* 64mb */
SC_ATOMIC_SET(stream_config.reassembly_memcap , STREAMTCP_DEFAULT_REASSEMBLY_MEMCAP);
reassembly:
  memcap: 256mb             # Memory reserved for stream data reconstruction (in bytes)

2.1.2 checksum_validation选项

TCP数据包具有所谓的校验和。这是一个内部代码,可以查看数据包是否已到达良好状态。流引擎将不会处理校验和错误的数据包。可以通过输入no代替yes来取消此选项。

stream:
  checksum_validation: yes    # Validate packet checksum, reject packets with invalid checksums.
  1. 收到TCP报文时,需要判断校验和。
TmEcode StreamTcp (ThreadVars *tv, Packet *p, void *data, PacketQueue *pq, PacketQueue *postpq)
{
...
        if (stream_config.flags & STREAMTCP_INIT_FLAG_CHECKSUM_VALIDATION) {
            if (StreamTcpValidateChecksum(p) == 0) {
                StatsIncr(tv, stt->counter_tcp_invalid_checksum);
                return TM_ECODE_OK;
            }
        } else {
            p->flags |= PKT_IGNORE_CHECKSUM;
        }
    } else {
        p->flags |= PKT_IGNORE_CHECKSUM; //TODO check that this is set at creation
    }
  1. 进行校验和校验。
static inline int StreamTcpValidateChecksum(Packet *p)
{
...
    if (p->level4_comp_csum == -1) {
        if (PKT_IS_IPV4(p)) {
            p->level4_comp_csum = TCPChecksum(p->ip4h->s_ip_addrs,
                                              (uint16_t *)p->tcph,
                                              (p->payload_len +
                                                  TCP_GET_HLEN(p)),
                                              p->tcph->th_sum);

2.1.3 prealloc-sessions选项

prealloc_sessions选项指示Suricata在内存中保持多个会话就绪。目的是为了减轻Suricata因快速会话创建而过载的情况,

stream:
  prealloc-sessions: 2k       # 2k sessions prealloc'd per stream thread
  • 默认值
#define STREAMTCP_DEFAULT_PREALLOC              2048
stream_config.prealloc_sessions = STREAMTCP_DEFAULT_PREALLOC;
  • TcpSession创建预分配的内存池。
TmEcode StreamTcpThreadInit(ThreadVars *tv, void *initdata, void **data)
{
...
    SCMutexLock(&ssn_pool_mutex);
    if (ssn_pool == NULL) {
        ssn_pool = PoolThreadInit(1, /* thread */
                0, /* unlimited */
                stream_config.prealloc_sessions,
                sizeof(TcpSession),
                StreamTcpSessionPoolAlloc,
                StreamTcpSessionPoolInit, NULL,
                StreamTcpSessionPoolCleanup, NULL);
        stt->ssn_pool_id = 0;
        SCLogDebug("pool size %d, thread ssn_pool_id %d", PoolThreadSize(ssn_pool), stt->ssn_pool_id);
...

2.1.4 midstream选项

TCP会话以三次握手开始。之后,数据就可以发送或接收。会话可以持续很长时间。
但是通常在启动了几个TCP会话后启动Suricata。Suricata会错过这些会话的原始设置。这个设置总是包含很多信息。如果希望Suricata从那时起检查流,可以通过将midstream选项设置为true来进行检查。默认设置为false

midstream: false             # do not allow midstream 

2.1.4.1 协议识别

  • 多模协议识别时,如果midstream选项是false则不进行检测。
static AppProto AppLayerProtoDetectPMGetProto(
        AppLayerProtoDetectThreadCtx *tctx,
        Flow *f, const uint8_t *buf, uint16_t buflen,
        uint8_t direction, AppProto *pm_results, bool *rflow)
{
...
    /* pattern found, yay */
    if (m > 0) {
        FLOW_SET_PM_DONE(f, direction);
        SCReturnUInt((uint16_t)m);

    /* handle non-found in non-midstream case */
    } else if (!stream_config.midstream) {
        /* we can give up if mpm gave no results and its search depth
         * was reached. */
        if (m < 0) {
            FLOW_SET_PM_DONE(f, direction);
            SCReturnUInt(0);
        } else if (m == 0) {
            SCReturnUInt(0);
        }
        SCReturnUInt((uint16_t)m);

    /* handle non-found in midstream case */
    } else if (m <= 0) {
 ...
  • 端口协议识别时,需要重置流的检查方向,再次进行检测。
static AppProto AppLayerProtoDetectPPGetProto(Flow *f,
        const uint8_t *buf, uint32_t buflen,
        uint8_t ipproto, const uint8_t idir,
        bool *reverse_flow)
{
...
again_midstream:
...
 noparsers:
    if (stream_config.midstream == true && idir == dir) {
        if (idir == STREAM_TOSERVER) {
            dir = STREAM_TOCLIENT;
        } else {
            dir = STREAM_TOSERVER;
        }
        SCLogDebug("no match + midstream, retry the other direction %s",
                (dir == STREAM_TOSERVER) ? "toserver" : "toclient");
        goto again_midstream;
    }
...

2.1.4.2 TCP协议处理

  • midstream选项是false时,没有状态的tcp流不再处理。
static int StreamTcpPacketStateNone(ThreadVars *tv, Packet *p,
                        StreamTcpThread *stt, TcpSession *ssn, PacketQueue *pq)
{
...
    } else if ((p->tcph->th_flags & (TH_SYN|TH_ACK)) == (TH_SYN|TH_ACK)) {
        if (stream_config.midstream == FALSE &&
                stream_config.async_oneside == FALSE)
            return 0;
...
    } else if (p->tcph->th_flags & TH_ACK) {
        if (stream_config.midstream == FALSE)
            return 0;
...
  • 重用新的TCP会话
int TcpSessionPacketSsnReuse(const Packet *p, const Flow *f, const void *tcp_ssn)
{
    if (p->proto == IPPROTO_TCP && p->tcph != NULL) {
        if (TcpSessionPacketIsStreamStarter(p) == 1) {
            if (TcpSessionReuseDoneEnough(p, f, tcp_ssn) == 1) {
                return 1;
            }
        }
    }
    return 0;
}

static int TcpSessionPacketIsStreamStarter(const Packet *p)
{
    if (p->tcph->th_flags == TH_SYN) {
        SCLogDebug("packet %"PRIu64" is a stream starter: %02x", p->pcap_cnt, p->tcph->th_flags);
        return 1;
    }

    if (stream_config.midstream == TRUE || stream_config.async_oneside == TRUE) {
        if (p->tcph->th_flags == (TH_SYN|TH_ACK)) {
            SCLogDebug("packet %"PRIu64" is a midstream stream starter: %02x", p->pcap_cnt, p->tcph->th_flags);
            return 1;
        }
    }
    return 0;
}

static int TcpSessionReuseDoneEnough(const Packet *p, const Flow *f, const TcpSession *ssn)
{
    if (p->tcph->th_flags == TH_SYN) {
        return TcpSessionReuseDoneEnoughSyn(p, f, ssn);
    }

    if (stream_config.midstream == TRUE || stream_config.async_oneside == TRUE) {
        if (p->tcph->th_flags == (TH_SYN|TH_ACK)) {
            return TcpSessionReuseDoneEnoughSynAck(p, f, ssn);
        }
    }

    return 0;
}

2.1.5 async_oneside选项

Suricata能够看到连接的所有数据包。不过,有些网络使其更加复杂。一些网络流量遵循与另一部分不同的路径,换句话说:流量是异步的。为了确保Suricata会检查它所看到的一个部分,而不是感到混乱,AsyncOneSide选项将被激活。默认情况下,选项设置为false

async_oneside: false         # do not enable async stream handling
  • async_oneside选项是false时,没有状态的tcp流不再处理。
static int StreamTcpPacketStateNone(ThreadVars *tv, Packet *p,
                        StreamTcpThread *stt, TcpSession *ssn, PacketQueue *pq)
{
...
    } else if ((p->tcph->th_flags & (TH_SYN|TH_ACK)) == (TH_SYN|TH_ACK)) {
        if (stream_config.midstream == FALSE &&
                stream_config.async_oneside == FALSE)
            return 0;
...
    } else if (p->tcph->th_flags & TH_ACK) {
        if (stream_config.midstream == FALSE)
            return 0;
...
  • 当我们收到一个SYN包,现在开始接收SYN/ACK时,如果我们从发送SYN的同一个主机接收ACK,这意味着ASNYC流
static int StreamTcpPacketStateSynSent(ThreadVars *tv, Packet *p,
                        StreamTcpThread *stt, TcpSession *ssn, PacketQueue *pq)
{
...
    } else if (p->tcph->th_flags & TH_ACK) {
        /* Handle the asynchronous stream, when we receive a  SYN packet
           and now istead of receving a SYN/ACK we receive a ACK from the
           same host, which sent the SYN, this suggests the ASNYC streams.*/
        if (stream_config.async_oneside == FALSE)
            return 0;

...
  • 在TCP的TCP_SYN_RECV状态,收到ACK报文,如果数据包的序列号等于预期的序列号处理数据。
static int StreamTcpPacketStateSynRecv(ThreadVars *tv, Packet *p,
...
    } else if (p->tcph->th_flags & TH_ACK) {
...
            /* If asynchronous stream handling is allowed then set the session,
               if packet's seq number is equal the expected seq no.*/
        } else if (stream_config.async_oneside == TRUE &&
                (SEQ_EQ(TCP_GET_SEQ(p), ssn->server.next_seq)))
        {
            /*set the ASYNC flag used to indicate the session as async stream
              and helps in relaxing the windows checks.*/
...
  • 其他状态参加源码。

2.1.6 inline选项

Suricata在normal/IDS模式下分块检查内容。在inline/IPS模式下,在滑动窗口方式检查内容。

inline: no                   # stream inline mode

如果Suricata被设置为inline模式,它必须在发送到接收方之前立即检查数据包。通过这种方式,Suricata能够在需要时直接丢弃数据包。

2.1.6.1 IDS模式

IDS模式下,Suricata以块的形式检查流量。属于通常的检测模式。


IDS

2.1.6.2 IPS模式

Inline/IPS模式下,Suricata以滑动窗口的方式检查流量。

IPS

2.1.7 drop-invalid选项

drop-invalid选项可以设置为no,以避免阻止流引擎认为无效的数据包。这对于覆盖某些第2层IPS设置中出现的一些奇怪情况非常有用。

drop-invalid: yes           # in inline mode, drop packets that are invalid with regards to streaming engine

2.1.8 bypass选项

当会话的任一侧达到其深度时,旁路选项将激活流/会话的bypass
警告
绕道会导致重要交通流丢失。小心使用。

bypass: no                  # Bypass packets when stream.reassembly.depth is reached.

2.1.9 max-synack-queued选项

最大的SYN/ACK入队数。

#define STREAMTCP_DEFAULT_MAX_SYNACK_QUEUED     5
max-synack-queued: 5        # Max different SYN/ACKs to queue

3. 流重组引擎

流重组
流重组

3.1 配置选项

3.1.1 memcap选项

流引擎有两个可以设置的memcap。一个用于流跟踪引擎,一个用于重组引擎。

  • 流跟踪引擎将流的信息保留在内存中。有关状态TCP序列号TCP窗口的信息。为了保留此信息,它可以利用memcap允许的容量。
#define STREAMTCP_DEFAULT_MEMCAP                (32 * 1024 * 1024) /* 32mb */
SC_ATOMIC_SET(stream_config.memcap, STREAMTCP_DEFAULT_MEMCAP);
stream:
  memcap: 64mb                # Max memory usage (in bytes) for TCP session tracking
  • 重组引擎必须将数据段保存在内存中,以便能够重建流。为了避免资源不足,使用memcap限制使用的内存。
#define STREAMTCP_DEFAULT_REASSEMBLY_MEMCAP     (64 * 1024 * 1024) /* 64mb */
SC_ATOMIC_SET(stream_config.reassembly_memcap , STREAMTCP_DEFAULT_REASSEMBLY_MEMCAP);
reassembly:
  memcap: 256mb             # Memory reserved for stream data reconstruction (in bytes)

3.1.2 depth选项

depth选项,可以控制流重组的范围。默认为1MB。执行文件提取的协议分析器可以覆盖每个流上的depth设置。

depth: 1mb                # The depth of the reassembling.

3.1.3 chunk_size选项

对重组数据是进行分块检查。这些块的大小设置toserver_chunk_sizetoclient_chunk_size。为了避免边界的可预测性,可以通过添加一个随机因素来改变其大小。

toserver_chunk_size: 2560 # inspect raw stream in chunks of at least this size
toclient_chunk_size: 2560 # inspect raw stream in chunks of at least
randomize-chunk-size: yes
#randomize-chunk-range: 10

3.1.4 raw选项

raw重组选项装通过简单的内容,pcre关键字利用和其他未在特定协议缓冲区(如http_uri)上执行的有效负载检查来完成的。这种类型的重新组装可以关闭。

reassembly:
  raw: no

3.1.5 segment-prealloc选项

传入段存储在流中的列表中。为了避免恒定的内存分配,使用了每个线程内存池。

reassembly:
  segment-prealloc: 2048    # pre-alloc 2k segments per thread

3.1.6 check-overlap-different-data选项

在同一序列号上重新发送不同的数据是一种扰乱网络检测的方法。

reassembly:
  check-overlap-different-data: true

3.1.7 IDS和IPS对于重组数据的处理

  • Normal/IDS对确认的数据进行重组
    IDS
  • Inline/IPS对未确认的数据进行重组
    IPS
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351