利用netfilter截獲、修改數(shù)據(jù)包基礎(chǔ)與實(shí)現(xiàn)

本著記錄自己瘋狂過的青春,對(duì)netfilter的應(yīng)用學(xué)習(xí)做點(diǎn)記錄。

0x00前提

雙網(wǎng)卡硬件設(shè)備作中間件,一個(gè)網(wǎng)卡接收向另一網(wǎng)卡轉(zhuǎn)發(fā)時(shí)進(jìn)行操作數(shù)據(jù)包。下文中舉例eth0為進(jìn)入,eth1為發(fā)出,相對(duì)而言。本次開發(fā)利用netfilter框架進(jìn)行完整開發(fā),基本實(shí)現(xiàn)對(duì)數(shù)據(jù)包的任意操作。

0x01 什么是netfilter

Netfilter是linux2.4.x引入的一個(gè)子系統(tǒng),作為一個(gè)通用的、抽象的框架,提供了一整套的Hook函數(shù)管理機(jī)>制,使得諸如數(shù)據(jù)包過濾,網(wǎng)絡(luò)地址轉(zhuǎn)換,和基于協(xié)議類型的連接跟蹤成為可能。通俗點(diǎn)說,netfilter提供了一系列的接口,將一個(gè)到達(dá)本機(jī)的數(shù)據(jù)包或者經(jīng)本機(jī)轉(zhuǎn)發(fā)的數(shù)據(jù)包流程中添加了一些可供用戶操作的點(diǎn),這些點(diǎn)被稱為HOOK點(diǎn)。

netfilter共有5個(gè)hook點(diǎn),分別為:

【1】NF_IP_PRE_ROUTING:剛剛進(jìn)入網(wǎng)絡(luò)層,還未進(jìn)行路由查找的包,通過此處。

【2】NF_IP_POST_ROUTING:進(jìn)入網(wǎng)絡(luò)層已經(jīng)經(jīng)過路由查找,確定轉(zhuǎn)發(fā),將要離開本設(shè)備的包,通過此處。

【3】NF_IP_LOCAL_IN:通過路由查找,確定發(fā)往本機(jī)的包,通過此處。

【4】NF_IP_LOCAL_OUT:從本機(jī)進(jìn)程剛發(fā)出的包,通過此處。

【5】NF_IP_FORWARD:經(jīng)路由查找后,要轉(zhuǎn)發(fā)的包,在POST_ROUTING之前。

在這次實(shí)踐應(yīng)用中主要完成將本機(jī)接收到、發(fā)送的所有數(shù)據(jù)包(包括TCP、UDP、ICMP協(xié)議)攔截,執(zhí)行想要執(zhí)行的操作后放行(諸如加密、修改信息、偽造信息等調(diào)戲行為),本機(jī)開發(fā)時(shí)用到的HOOK點(diǎn)為NF_IP_LOCAL_OUT和NF_IP_LOCAL_IN,但在ARM中時(shí),由于充當(dāng)一個(gè)路由的功能,要將eth0接收到的包轉(zhuǎn)發(fā)出去,將修改HOOK點(diǎn)為NF_IP_PRE_ROUTING,對(duì)接受的數(shù)據(jù)包路由之前進(jìn)行所有操作,反過來eth1接收的也相同。

0x02開發(fā)前置知識(shí)

1.hook函數(shù)注冊(cè)與解注冊(cè)

在使用netfilter框架進(jìn)行你想要的操作時(shí),你需要先向內(nèi)核注冊(cè)自己的鉤子函數(shù),要告訴你的內(nèi)核現(xiàn)在我要進(jìn)行處理了,那么這個(gè)鉤子函數(shù)包括些什么信息呢?

static struct nf_hook_ops nfho = {  
    .hook = my_func,  
    .pf = PF_INET,  
    .hooknum =NF_INET_LOCAL_IN ,  
    .priority = NF_IP_PRI_FIRST,  
    .owner = THIS_MODULE,  
}; 

上面的例子中定義了一個(gè)nf_hook_ops類型的結(jié)構(gòu)體nfho,它包含的結(jié)構(gòu)體成員有:

.hook;表示你所定義的hook函數(shù)名稱。

.pf;協(xié)議簇,示例中PF_INET表示IPv4。

.hooknum;表示這個(gè)鉤子在數(shù)據(jù)包處理流程中的位置,示例中為經(jīng)路由選擇確認(rèn)本機(jī)接受,進(jìn)入本機(jī)進(jìn)程前。

.prioity;你所注冊(cè)的鉤子函數(shù)優(yōu)先級(jí),示例中NF_IP_PRI_FIRST表示優(yōu)先級(jí)最高。

.owner;表示所屬的模塊,示例中屬于此模塊。

這樣一個(gè)鉤子函數(shù)就定義好了,只需要在模塊載入時(shí),對(duì)他進(jìn)行注冊(cè),如果失敗,打印內(nèi)核級(jí)別錯(cuò)誤信息:

static int __init http_init(void)  
{  
    if (nf_register_hook(&nfho)) 
    {  
        printk(KERN_ERR"nf_register_hook() failed\n");  
        return -1;  
    }  
return 0;  
}

同理,在模塊退出時(shí),要對(duì)所注冊(cè)的鉤子函數(shù)進(jìn)行解注冊(cè):

static void __exit http_exit(void)  
{  
    nf_unregister_hook(&nfho);
}  

關(guān)于模塊的加載與退出,可以參考內(nèi)核編程hello word!,簡(jiǎn)單易懂。

2.什么是skb

要在內(nèi)核中對(duì)到達(dá)設(shè)備的數(shù)據(jù)包進(jìn)行操作,就必須先了解:

(1)數(shù)據(jù)包到達(dá)設(shè)備后被存放在哪里?

(2)如何一層一層除去數(shù)據(jù)包的包頭結(jié)構(gòu),得到數(shù)據(jù)部分?

(3)處理之后可以直接發(fā)送嗎?

(4)如果不能,需要再進(jìn)行什么操作呢?

先看問題(1),這就需要我們了解skb的基本結(jié)構(gòu)以及------一系列接下來將要用到的結(jié)構(gòu)體。

skb的結(jié)構(gòu)體源碼就不放了,一搜一大把,主要對(duì)我們需要用到的幾部分簡(jiǎn)單介紹:

union {  
                 struct tcphdr   *th;  
                 struct udphdr   *uh;  
                 struct icmphdr  *icmph;  
                 struct igmphdr  *igmph;  
                 struct iphdr    *ipiph;  
                 struct ipv6hdr  *ipv6h;  
                 unsigned char   *raw;  
         } h;  

這里的union h;表示一個(gè)完整數(shù)據(jù)包的各種頭,我們常用到的iphdr,tcphdr,udphdr,icmpdr;

對(duì)iphdr的常用成員做簡(jiǎn)單介紹:

ipiph->ihl;IP頭部的長(zhǎng)度,表示有32位長(zhǎng)度多少個(gè),在計(jì)算ip頭部的長(zhǎng)度時(shí),通常用ipiph->ihl*4。

ipiph->tot_len;整個(gè)ip報(bào)文的長(zhǎng)度,減去ip頭部的長(zhǎng)度,就是ip包的數(shù)據(jù)部分了,在使用過程中需要注意網(wǎng)絡(luò)字節(jié)序的問題。

ipiph->ttl;生存時(shí)間,規(guī)定了一個(gè)數(shù)據(jù)包最多可以經(jīng)過的路由器數(shù)目,當(dāng)這個(gè)值被減小為0時(shí),這個(gè)包將被丟棄,防止無限循環(huán)下去。

ipiph->protocol;協(xié)議類型,表示是哪個(gè)協(xié)議傳來的數(shù)據(jù)包,這里的協(xié)議是指?jìng)鬏攲拥膮f(xié)議TCP等等。

ipiph->check;ip頭部的校驗(yàn)值,只對(duì)頭部進(jìn)行檢驗(yàn),而不對(duì)其中的數(shù)據(jù)進(jìn)行校驗(yàn)。

ipiph->saddr;IP數(shù)據(jù)包的源IP地址。

ipiph->daddr;IP數(shù)據(jù)包的目的IP地址。

tcphdr的常用成員簡(jiǎn)單介紹:

 th->source;表示該數(shù)據(jù)包的源端口號(hào)。

 th->dest;表示該數(shù)據(jù)包的目的端口號(hào)。

th->doff;TCP頭部的長(zhǎng)度,表示共包含了多少個(gè)32位的數(shù)據(jù),所以要計(jì)算tcp頭部的長(zhǎng)度時(shí)需要th->doff*4。

th->check;TCP頭部的校驗(yàn)和,當(dāng)我們對(duì)整個(gè)報(bào)文的數(shù)據(jù)部分做了加密或其他處理時(shí),需要重新計(jì)算校驗(yàn)和。

udphdr和icmphdr中的頭部長(zhǎng)度可以直接使用sizeof(sturct udphdr/icmphdr)來計(jì)算。

skb->len與skb->data_len:

skb->len;表示skb->data指針?biāo)傅臄?shù)據(jù)區(qū)域的長(zhǎng)度,它包括:線性區(qū)域的數(shù)據(jù)長(zhǎng)度、非線性數(shù)據(jù)區(qū)域的數(shù)據(jù)長(zhǎng)度。

skb->data_len;表示非線性數(shù)據(jù)區(qū)域的數(shù)據(jù)長(zhǎng)度。

我們?cè)诖蛴?shù)據(jù)信息時(shí),會(huì)經(jīng)常不能打印完全,缺了一部分,這就是因?yàn)閐ata_len部分不為0,data所指的數(shù)據(jù)區(qū)域打印到線性數(shù)據(jù)完畢后,便停止了,這時(shí)我們可以使用一個(gè)函數(shù),將skb的非線性部分轉(zhuǎn)換為線性部分,重新打印,這樣就可以打印出所有的數(shù)據(jù)部分了,代碼示例:

if(skb->data_len!=0)
{           
    if(skb_linearize(skb))
    {
        printk("error line skb\r\n");
        printk("skb->data_len %d\r\n",skb->data_len);
        return NF_DROP;

    }

}

表示當(dāng)我們的非線性數(shù)據(jù)不為0時(shí),使用函數(shù)skb->linearize轉(zhuǎn)換,可以將skb的數(shù)據(jù)尾部指針向后推data_len個(gè)長(zhǎng)度,將data_len部分也連道data指針?biāo)傅奈膊俊?/p>

3.重新計(jì)算校驗(yàn)和

當(dāng)我們對(duì)數(shù)據(jù)區(qū)的數(shù)據(jù)進(jìn)行操作之后,不可以直接發(fā)送原來的數(shù)據(jù)包,還需要對(duì)校驗(yàn)字段進(jìn)行修改,修改的校驗(yàn)字段主要有:skb的校驗(yàn)字段,iph的校驗(yàn)字段,和傳輸層(TCP、UDP、ICMP)的校驗(yàn)字段。

以tcp為例計(jì)算校驗(yàn)和:

iph->check=0;
iph->check=ip_fast_csum((unsigned char*)iph, iph->ihl);
if(skb->ip_summed == CHECKSUM_HW)
{

        tcph->check=csum_tcpudp_magic(iph->saddr,iph->daddr,(ntohs(iph ->tot_len)-iph->ihl*4), IPPROTO_TCP,csum_partial(tcph,(ntohs(iph ->tot_len)-iph->ihl*4),0)); 
    skb->csum = offsetof(struct tcphdr,check);      
}

重新計(jì)算校驗(yàn)和后的數(shù)據(jù)包就可以繼續(xù)在網(wǎng)絡(luò)中傳輸了。

0x03開發(fā)過程

基本思路為:首先判斷協(xié)議;

              根據(jù)每個(gè)協(xié)議的特點(diǎn),計(jì)算數(shù)據(jù)部分位置和數(shù)據(jù)部分長(zhǎng)度;    

              對(duì)數(shù)據(jù)部分做想進(jìn)行的處理,加密解密等等;

              重新計(jì)算校驗(yàn)和,返回處理結(jié)果。

在函數(shù)返回值時(shí),有下列五種:

NF_DROP;丟棄該數(shù)據(jù)包,一般可用作過濾手段。

NF_ACCEPT;接受該數(shù)據(jù)包。

NF_STOLEN;-沒有用過,也沒有理解具體的意義。

NF_QUEUE;用戶隊(duì)列中排隊(duì),在學(xué)習(xí)netlink將內(nèi)核的數(shù)據(jù)包發(fā)送給用戶態(tài)時(shí)會(huì)用到。

NF_REPEAT;重新調(diào)用該Hook函數(shù)。

下面以TCP報(bào)文為例:

if(likely(iph->protocol==IPPROTO_TCP))
{
    tcph=tcp_hdr(skb);
    data=skb->data+iph->ihl*4+tcph->doff*4;
    header=iph->ihl*4+tcph->doff*4;
    length=skb->len-iph->ihl*4-tcph->doff*4;
    if(skb->len-header>0)
    {
        printk("**************now_start_in_data*****************\n");
        printk("header length is %d",header);
        printk("\r\n");
        printk("len-header is %d",skb->len-header);
        printk("\r\n");
        printk("data length is %d",length);
        printk("\r\n");
        if(skb->data_len!=0)
        {           
            if(skb_linearize(skb))
            {
                printk("error line skb\r\n");
                printk("skb->data_len %d\r\n",skb->data_len);
                return NF_DROP;

            }

        }
        for(i=0;i<length;i++)
        {
            printk("%c",data[i]);           
        }
        iph->check=0;
        iph->check=ip_fast_csum((unsigned char*)iph, iph->ihl);
        if(skb->ip_summed == CHECKSUM_HW)
        {

            tcph->check=csum_tcpudp_magic(iph->saddr,iph->daddr,(ntohs(iph ->tot_len)-iph->ihl*4), IPPROTO_TCP,csum_partial(tcph,(ntohs(iph ->tot_len)-iph->ihl*4),0)); 
            skb->csum = offsetof(struct tcphdr,check);      
        }
    }
}
return NF_ACCEPT;

對(duì)一個(gè)完整的tcp數(shù)據(jù)包截獲在發(fā)送就完成了?。?em>-轉(zhuǎn)發(fā)還不會(huì))

0x04完整代碼

#include <linux/time.h> 
#include <linux/init.h>  
#include <linux/netfilter.h>
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/netdevice.h>  
#include <linux/netfilter_ipv4.h>
#include <linux/net.h>
#include <net/ip.h>
#include <linux/if_ether.h>
#include <net/protocol.h>
#include <net/icmp.h>
#include <net/tcp.h>
#include <linux/if_vlan.h>
#define CHECKSUM_HW 1
unsigned int my_func(unsigned int hooknum,struct sk_buff *skb,const struct net_device *in,const struct net_device *out,int (*okfn)(struct sk_buff *))
{   struct iphdr *iph=ip_hdr(skb);
    struct tcphdr *tcph;
    struct udphdr *udph;
    struct icmphdr *icmph;
    struct net_device   *master;
    int i=0,ret=-1;
    int header=0;
    int index=0;
    unsigned char *data=NULL;
    int length=0;
    if(likely(iph->protocol==IPPROTO_TCP))
    {
        tcph=tcp_hdr(skb);
        data=skb->data+iph->ihl*4+tcph->doff*4;
        header=iph->ihl*4+tcph->doff*4;
        length=skb->len-iph->ihl*4-tcph->doff*4;
        if(skb->len-header>0)
        {
            printk("**************now_start_in_data*****************\n");
            printk("header length is %d",header);
            printk("\r\n");
            printk("len-header is %d",skb->len-header);
            printk("\r\n");
            printk("data length is %d",length);
            printk("\r\n");
            if(skb->data_len!=0)
            {           
                if(skb_linearize(skb))
                {
                    printk("error line skb\r\n");
                    printk("skb->data_len %d\r\n",skb->data_len);
                    return NF_DROP;

                }

            }
            for(i=0;i<length;i++)
            {
                printk("%c",data[i]);           
            }
            iph->check=0;
            iph->check=ip_fast_csum((unsigned char*)iph, iph->ihl);
            if(skb->ip_summed == CHECKSUM_HW)
            {

                tcph->check=csum_tcpudp_magic(iph->saddr,iph->daddr,(ntohs(iph ->tot_len)-iph->ihl*4), IPPROTO_TCP,csum_partial(tcph,(ntohs(iph ->tot_len)-iph->ihl*4),0)); 
                skb->csum = offsetof(struct tcphdr,check);      
            }
        }
    }
    else if(likely(iph->protocol==IPPROTO_UDP))
    {
        udph=udp_hdr(skb);
        data=skb->data+iph->ihl*4+sizeof(struct udphdr);
        header=iph->ihl*4+sizeof(struct udphdr);
        length=ntohs(iph->tot_len)-iph->ihl*4-sizeof(struct udphdr);
        if(skb->len-header>0)
        {
        printk("header length is %d",header);
        printk("\r\n");
        printk("len -header is  %d",skb->len-header);
        printk("\r\n");
        printk("data length is %d",length);
        printk("\r\n");
        for(i=0;i<length;i++)
        {
            printk(" %02x",data[i]);
            if((i+1)%16==0)
            printk("\r\n");
        }
        if(skb->data_len!=0)
        {           
            if(skb_linearize(skb))
            {
                printk("error line skb\r\n");
                printk("skb->data_len %d\r\n",skb->data_len);
                return NF_DROP;

            }

        }
        for(i=0;i<length;i++)
        {
            printk("%c",data[i]);           
        }
        iph->check=0;
        iph->check=ip_fast_csum((unsigned char*)iph, iph->ihl);
        if(skb->ip_summed == CHECKSUM_HW)
        {
            udph->check=csum_tcpudp_magic(iph->saddr,iph->daddr,(ntohs(iph ->tot_len)-iph->ihl*4), IPPROTO_UDP,csum_partial(udph, (ntohs(iph ->tot_len)-iph->ihl*4), 0));
        }
        printk("*********************UDPend********************\n");
        }
    }
    else if(likely(iph->protocol==IPPROTO_ICMP))
    {
        icmph=icmp_hdr(skb);
        data=skb->data+iph->ihl*4+sizeof(struct icmphdr);
        header=iph->ihl*4+sizeof(struct icmphdr);   
        length=ntohs(iph->tot_len)-iph->ihl*4-sizeof(struct icmphdr);
        if(skb->len-header>0)
        {
        printk("header length is %d",header);
        printk("\r\n");
        printk("len - header is %d",skb->len-header);
        printk("\r\n");
        printk("data length is  %d",length);
        printk("\r\n");
        if(skb->data_len!=0)
        {           
            if(skb_linearize(skb))
            {
                printk("error line skb\r\n");
                printk("skb->data_len %d\r\n",skb->data_len);
                return NF_DROP;

            }

        }
        for(i=0;i<length;i++)
        {
            printk("%c",data[i]);           
        }
        printk("\r\n");

        iph->check=0;
        iph->check=ip_fast_csum((unsigned char*)iph, iph->ihl);
        if(skb->ip_summed == CHECKSUM_HW)
        {
            icmph->checksum=ip_compute_csum(icmph, (ntohs(iph ->tot_len)-iph->ihl*4));
        }
        printk("*********************ICMPend********************\n");
        }
    }
    return NF_ACCEPT;

}

static struct nf_hook_ops nfho = {  
    .hook = my_func,  
    .pf = PF_INET,  
    .hooknum =NF_INET_LOCAL_IN ,  
    .priority = NF_IP_PRI_FIRST,  
    .owner = THIS_MODULE,  
}; 

static int __init http_init(void)  
{  

    if (nf_register_hook(&nfho)) {  
        printk(KERN_ERR"nf_register_hook() failed\n");  
    return -1;  
    }  
    return 0;  
}  
static void __exit http_exit(void)  
{  
    nf_unregister_hook(&nfho);
}  

module_init(http_init);  
module_exit(http_exit);  
MODULE_AUTHOR("AFCC_");  
MODULE_LICENSE("GPL"); 

makefile:

ifneq ($(KERNELRELEASE),)
    obj-m += in.o
else
    PWD := $(shell pwd)
    KVER := $(shell uname -r)
    KDIR := /lib/modules/$(KVER)/build
default:    
    $(MAKE) -C $(KDIR)  M=$(PWD) modules
all:
    make -C $(KDIR) M=$(PWD) modules 
clean:
    rm -rf *.o *.mod.c *.ko *.symvers *.order *.makers
 endif

接下來將會(huì)把轉(zhuǎn)發(fā)部分學(xué)習(xí),用于硬件雙網(wǎng)卡的轉(zhuǎn)發(fā)orz

學(xué)習(xí)過程中看了許許多多大佬的博客才會(huì)有今天的總結(jié),但也走了不少?gòu)澛?,希望能讓自己好好整理這段時(shí)間的所學(xué)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 簡(jiǎn)介 用簡(jiǎn)單的話來定義tcpdump,就是:dump the traffic on a network,根據(jù)使用者...
    保川閱讀 6,077評(píng)論 1 13
  • 1 前言 防火墻(Firewall),就是一個(gè)隔離工具,工作于主機(jī)或者網(wǎng)絡(luò)的邊緣,對(duì)于進(jìn)出本主機(jī)或本網(wǎng)絡(luò)的報(bào)文,根...
    魏鎮(zhèn)坪閱讀 7,150評(píng)論 1 23
  • 簡(jiǎn)介 用簡(jiǎn)單的話來定義tcpdump,就是:dump the traffic on a network,根據(jù)使用者...
    JasonShi6306421閱讀 1,343評(píng)論 0 1
  • 剛才我的大學(xué)群里,一個(gè)我們當(dāng)年整個(gè)學(xué)校的男神,現(xiàn)在的優(yōu)秀的4A亞洲區(qū)創(chuàng)意總監(jiān)(PS,也是典型的PB)不婚主義者,突...
    萌雨閱讀 500評(píng)論 0 1
  • 文/寺主人 我曾經(jīng)也是一名學(xué)渣,后來慢慢進(jìn)化之后就有很多人跑來問我有什么秘訣,怎么樣才可以高效率地進(jìn)行學(xué)習(xí)、工作和...
    寺主人閱讀 1,824評(píng)論 2 31

友情鏈接更多精彩內(nèi)容