Skip to content

Latest commit

 

History

History
322 lines (272 loc) · 12.1 KB

0x06_流还原相关.md

File metadata and controls

322 lines (272 loc) · 12.1 KB

流还原相关

为了实现ip碎片重组以及流还原等功能,检测出分片发送的攻击报文。snort中进行了流缓存相关的操作。

流缓存

这里首先在顺便提一下插件的初始化,在前面的章节中提到了,ips相关的插件,都是存在s_options集合中,而在snort进行初始化的时候,同时也会调用IpsManager::setup_options();对相关插件进行初始化。Snort::thread_init_unprivileged();这个函数进行了一系列的初始化操作,其中就包括了插件的初始化,这个函数本身在Analyzer对象的run函数中执行

void IpsManager::setup_options()
{
    for ( auto* p : s_options )
        if ( p->init && p->api->tinit )
            p->api->tinit(SnortConfig::get_conf());
}

而需要进行流缓存,是要进行一定的内存分配的,这里的内存预分配就是在流相关的模块初始化的地发进行的。

这里对应的模块的代码是在src/stream/base/stream_base.cc中
void StreamBase::tinit()
{
    assert(!flow_con);
    flow_con = new FlowControl;
    InspectSsnFunc f;

    StreamHAManager::tinit();

    if ( config.ip_cfg.max_sessions )
    {
        if ( (f = InspectorManager::get_session(PROTO_BIT__IP)) )
            flow_con->init_proto(PktType::IP, config.ip_cfg, f);
    }
    if ( config.icmp_cfg.max_sessions )
    {
        if ( (f = InspectorManager::get_session(PROTO_BIT__ICMP)) )
            flow_con->init_proto(PktType::ICMP, config.icmp_cfg, f);
    }
    if ( config.tcp_cfg.max_sessions )
    {
        if ( (f = InspectorManager::get_session(PROTO_BIT__TCP)) )
            flow_con->init_proto(PktType::TCP, config.tcp_cfg, f);
    }
    if ( config.udp_cfg.max_sessions )
    {
        if ( (f = InspectorManager::get_session(PROTO_BIT__UDP)) )
            flow_con->init_proto(PktType::UDP, config.udp_cfg, f);
    }
    if ( config.user_cfg.max_sessions )
    {
        if ( (f = InspectorManager::get_session(PROTO_BIT__PDU)) )
            flow_con->init_proto(PktType::PDU, config.user_cfg, f);
    }
    if ( config.file_cfg.max_sessions )
    {
        if ( (f = InspectorManager::get_session(PROTO_BIT__FILE)) )
            flow_con->init_proto(PktType::FILE, config.file_cfg, f);
    }
    uint32_t max = config.tcp_cfg.max_sessions + config.udp_cfg.max_sessions
        + config.user_cfg.max_sessions;

    if ( max > 0 )
        flow_con->init_exp(max);

    FlushBucket::set(config.footprint);
}

这里初始化了FlowControl对象,并调用init_proto函数对各种协议类型相关的流进行了初始化。

void FlowControl::init_proto(
    PktType type, const FlowConfig& fc, InspectSsnFunc get_ssn)
{
    if ( !fc.max_sessions || !get_ssn )
        return;
    //这里首先根据类型获取相应的proto对象,这个数组本身的下标就是协议类型
    auto& con = proto[to_utype(type)];
    //新建流缓存对象并申请内存,顺带一提,snort在默认配置下,这部分占用了总共消耗的3/4甚至更多的内存,这里一条流对应一个flow对象
    con.cache = new FlowCache(fc);
    con.mem = (Flow*)snort_calloc(fc.max_sessions, sizeof(Flow));

    for ( unsigned i = 0; i < fc.max_sessions; ++i )
        con.cache->push(con.mem + i);

    con.get_ssn = get_ssn;
    types.emplace_back(type);
}

流缓存本身在stream模块中的process函数中被使用到,它是在eval函数中被执行的

bool FlowControl::process(PktType type, Packet* p)
{
    auto& con = proto[to_utype(type)];

    if ( !con.cache )
        return false;

    FlowKey key;     //这里存着ip端口一类的信息
    set_key(&key, p);
    Flow* flow = con.cache->find(&key);   //这里应该是就是根据包中的源目ip信息等,看看有没有对应的流

    if ( !flow )    //这里进行了一些检查,应该是对这个包需不需要就行流缓存或者跟踪做的判断
    {
        if ( !want_flow(type, p) )
            return true;

        flow = con.cache->get(&key);  //这里和find的主要区别是,flow有一个hash table存储,这里调用get会新建相关hash table的结点

        if ( !flow )
            return true;
    }
    if ( !flow->session )   //如果没有session这里会进行初始化
    {
        flow->init(type);
        flow->session = con.get_ssn(flow);
    }

    con.num_flows += process(flow, p);

    // FIXIT-M refactor to unlink_uni immediately after session
    // is processed by inspector manager (all flows)
    if ( flow->next && is_bidirectional(flow) )
        con.cache->unlink_uni(flow);

    return true;
}

接下来调用的process(flow, p);函数实现如下

unsigned FlowControl::process(Flow* flow, Packet* p)
{
    unsigned news = 0;

    flow->previous_ssn_state = flow->ssn_state;

    p->flow = flow;
    p->disable_inspect = flow->is_inspection_disabled();

    last_pkt_type = p->type();
    preemptive_cleanup();

    flow->set_direction(p);
    flow->session->precheck(p);

    if ( flow->flow_state != Flow::FlowState::SETUP )
    {
        set_inspection_policy(SnortConfig::get_conf(), flow->inspection_policy_id);
        set_ips_policy(SnortConfig::get_conf(), flow->ips_policy_id);
        set_network_policy(SnortConfig::get_conf(), flow->network_policy_id);
    }

    else
    {
        init_roles(p, flow);
        DataBus::publish(FLOW_STATE_SETUP_EVENT, p);

        if ( flow->flow_state == Flow::FlowState::SETUP ||
            (flow->flow_state == Flow::FlowState::INSPECT &&
             (!flow->ssn_client || !flow->session->setup(p))) )
            flow->set_state(Flow::FlowState::ALLOW);

        ++news;
    }

    // This requires the packet direction to be set
    if ( p->proto_bits & PROTO_BIT__MPLS )
        flow->set_mpls_layer_per_dir(p);

    if ( p->type() == PktType::PDU )  // FIXIT-H cooked or PDU?
        DetectionEngine::onload(flow);

    switch ( flow->flow_state )
    {
    case Flow::FlowState::SETUP:
        flow->set_state(Flow::FlowState::ALLOW);
        break;

    case Flow::FlowState::INSPECT:
        assert(flow->ssn_client);
        assert(flow->ssn_server);
        break;

    case Flow::FlowState::ALLOW:
        if ( news )
            Stream::stop_inspection(flow, p, SSN_DIR_BOTH, -1, 0);
        else
            DetectionEngine::disable_all(p);

        p->ptrs.decode_flags |= DECODE_PKT_TRUST;
        break;

    case Flow::FlowState::BLOCK:
        if ( news )
            Stream::drop_traffic(flow, SSN_DIR_BOTH);
        else
            Active::block_again();

        DetectionEngine::disable_all(p);
        break;

    case Flow::FlowState::RESET:
        if ( news )
            Stream::drop_traffic(flow, SSN_DIR_BOTH);
        else
            Active::reset_again();

        Stream::blocked_flow(flow, p);
        DetectionEngine::disable_all(p);
        break;
    }

    return news;
}

这里给相应的包对象设置流成员对象的值,而session 对应各种协议的会话 例如 tcp udp 等等, 在InspectorManager::execute中调用对应的process函数

p->flow->session->process(p);

例如tcpsession,它可能会调用 TcpEventLogger::log_tcp_events() 对相应的build in 事件进行触发,例如teardrop攻击的检测。

总的来说流缓存下来用于各种检查和分析,流本身对应一个flowkey,这里保存流的源目的ip地址一类的信息,需要处理或者获取对应流缓存实例的时候,均通过这个key,流缓存类有一个hashtable,通过它来存储。

ip碎片重组

上面提到的调用对应的process函数,而ip碎片的重组,就是通过调用int IpSession::process(Packet* p)来实现的,其中会调用void Defrag::process(Packet* p, FragTracker* ft),进行碎片重组的操作,这里首先会检查是否过期等等,然后调用int Defrag::insert(Packet* p, FragTracker* ft, FragEngine* fe)对碎片信息进行检查,例如teardrop攻击的检测。并且找到(如果存在的话)当前包的左侧和右侧的包,这里处理的是包可能存在部分重叠的问题。然后会调用int Defrag::add_frag_node函数,将包插入FragTracker的链表中。

int Defrag::add_frag_node(
    FragTracker* ft,
    FragEngine*,
    const uint8_t* fragStart,
    int16_t fragLength,
    char lastfrag,
    int16_t len,
    uint16_t slide,
    uint16_t trunc,
    uint16_t frag_offset,
    Fragment* left,
    Fragment** retFrag)
{
    Fragment* newfrag = nullptr;  /* new frag container */
    int16_t newSize = len - slide - trunc;

    if (newSize <= 0)
    {
        /*
         * zero size frag
         */
        trace_logf(stream_ip,
            "zero size frag after left & right trimming "
            "(len: %d  slide: %d  trunc: %d)\n",
            len, slide, trunc);

        ip_stats.discards++;

#ifdef DEBUG_MSGS
        newfrag = ft->fraglist;
        while (newfrag)
        {
            trace_logf(stream_ip,
                "Size: %d, offset: %d, len %d, "
                "Prev: 0x%p, Next: 0x%p, This: 0x%p, Ord: %d, %s\n",
                newfrag->size, newfrag->offset,
                newfrag->flen, (void*) newfrag->prev,
                (void*) newfrag->next, (void*) newfrag, newfrag->ord,
                newfrag->last ? "Last" : "");
            newfrag = newfrag->next;
        }
#endif

        return FRAG_INSERT_ANOMALY;
    }

    newfrag = new Fragment(fragLength, fragStart, ft->ordinal++);

    /*
     * twiddle the frag values for overlaps
     */
    newfrag->data = newfrag->fptr + slide;
    newfrag->size = newSize;
    newfrag->offset = frag_offset;
    newfrag->last = lastfrag;

    trace_logf(stream_ip,
        "[+] Adding new frag, offset %d, size %d\n"
        "   nf->data = nf->fptr(%p) + slide (%d)\n"
        "   nf->size = len(%d) - slide(%d) - trunc(%d)\n",
        newfrag->offset, newfrag->size, newfrag->fptr,
        slide, fragLength, slide, trunc);

    /*
     * insert the new frag into the list
     */
    add_node(ft, left, newfrag);

    trace_logf(stream_ip,
        "[*] Inserted new frag %d@%d ptr %p data %p prv %p nxt %p\n",
        newfrag->size, newfrag->offset, (void*) newfrag, newfrag->data,
        (void*) newfrag->prev, (void*) newfrag->next);

    /*
     * record the current size of the data in the fraglist
     */
    ft->frag_bytes += newfrag->size;

    trace_logf(stream_ip,
        "[#] accumulated bytes on FragTracker %u, count"
        " %d\n", ft->frag_bytes, ft->fraglist_count);

    *retFrag = newfrag;
    return FRAG_INSERT_OK;
}

首先根据碎片的起始地址和长度,新建fragment对象,然后调用add_node(ft, left, newfrag);,将包对应的fragment对象插入链表中,这里会传入一个left对象,它是当前碎片左边的碎片包,如果不存在的话,则会将新建的这个frag的结点,作为FragTracker* ft这个里面的fraglist的第一个结点。

int Defrag::insert(Packet* p, FragTracker* ft, FragEngine* fe)被调用过后,process函数中会调用FragIsComplete来检查碎片是否合并完全,这里首先会确认,frag的第一个和最后一个,是不是都到了,然后确认长度等信息是否已知,然后调用 FragRebuild,对包进行重建,以确保完成ip碎片整理的操作。 在重建完成后,调用

snort::Snort::process_packet(dpkt, dpkt->pkth, dpkt->pkt, true);

将这里的包传入snort的检测系统中,进行下一步的检测。

tcp流还原

首先tcp会话创建和初始化类似上文中的ip重组,他们的接口是一样的,都是通过调用process函数。 重组模块的初始化在执行process后的TcpSession::set_os_policy()中,首先创建了TcpSegmentDescriptor对象。 然后通过调用queue_packet_for_reassembly对包进行重组,这里有两个参数TcpReassemblerState为重组时相关的信息,TcpSegmentDescriptor这里为对应seg的描述信息,包括流和包等成员对象 ,如果seg_count为0 则将seg插入空的seglist中,seglist的结点为TcpSegmentNode对象,由上面两个参数共同的数据初始化完成,插入时还传入了left节点,这里如果没有left,则将传入的节点作为头节点 后续的left查找操作类似ip重组,这里的操作过程其实和ip流几乎是一样的。