ICKelin / article

读书笔记,博客文章
MIT License
116 stars 18 forks source link

连接跟踪的一种非常暴力的获取方式 #5

Open ICKelin opened 6 years ago

ICKelin commented 6 years ago

在项目当中有时需要连接跟踪相关的信息,netfilter官网提供了一个libnetfilter_conntrack的用户态库,通过netlink的方式与内核对应的模块通信操作conntrack,但是会有性能瓶颈,还有就是问题不好定位,本文提供一种用户层获取连接跟踪的实现思路,在继续阅读之前,请确保具备以下基本知识:

解决思路很清晰,在内核层面netfilter钩子当中hook数据包,然后强行在L4和payload之间封装一层协议,有点类似运营商植入广告,将连接跟踪的信息通过这层协议传递给用户态,用户态先解这层协议。再读取数据。

比如说,用户层比较关心DNAT之前的目的地址(origin_ip)和目的端口(origin_port),那么可以将origin_ip和origin_port再加上payload的长度三个字段作为一层协议,插入到tcp和payload之间。这样用户层的应用只需要正常读数据,读取协议头,再读取payload即可。

以TCP为例,部分示例代码如下:

// tcp
static int handle_tcp(struct sk_buff *skb){
    struct iphdr *ip = ip_hdr(skb);
    struct tcphdr *tcp = tcp_hdr(skb);

    // conntrack
    enum ip_conntrack_info conntrack_info;  
    struct nf_conntrack_tuple *origin_tuple = NULL;
    struct nf_conn *ct = NULL;

    // inject buffer
    char insert_data[8];
    char *ptr = &insert_data[0];

    // origin payload
    char *origin_data = skb_network_header(skb) + ip->ihl * 4 + tcp->doff * 4;
    int origin_len = skb->tail - skb->network_header - ip->ihl * 4 - tcp->doff * 4;

    ct = get_conntrack(skb, &conntrack_info);
    if (ct == NULL) {
        return NF_ACCEPT;
    }

    origin_tuple = &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple;

    // force accepted pkt
    if (is_accepted_pkt(origin_tuple, ip->daddr, tcp->dest) != 0) {
        return NF_ACCEPT;
    }

    // empty payload
    if (origin_len == 0) {
        return NF_ACCEPT;
    }

    // modify tcp payload
    ptr = encode32u(ptr, origin_tuple->dst.u3.ip);
    ptr = encode16u(ptr, ntohs(origin_tuple->dst.u.all));
    ptr = encode16u(ptr, origin_len);

    // expand skb tailroom
    if (skb_tailroom(skb) < 8) {
        if (expand_skb(skb, skb_headroom(skb), skb_tailroom(skb) + 40) != 0) {
            return NF_DROP; 
        }
    }

    // copy conntrack data into skb payload 
    memmove(origin_data + 8, origin_data, origin_len);
    memcpy(origin_data, &insert_data[0], 8);

    nfct_seqadj_ext_add(ct);
    __nf_nat_mangle_tcp_packet(skb, ct, conntrack_info, 
            ip->ihl * 4, 0, origin_len, 
            origin_data, origin_len + 8, true);

    return NF_ACCEPT;
}

在用户态先读八个字节的协议,将origin_ip和origin_port以及payload长度length读取出来,然后再读取length字节的数据。不需要任何库,难点在于编写一个稳定的内核模块。