本著記錄自己瘋狂過的青春,對(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é)。