(十五)洞悉linux下的Netfilter&iptables:開發自己的hook函數【實戰】(上)

向Netfilter中註冊自己的hook函數

        數據包在協議棧中傳遞時會經過不同的HOOK點,而每個HOOK點上又被Netfilter預先註冊了一系列hook回調函數,當每個清純的數據包到達這些點後會被這些可惡hook函數輪番調戲一番。有時候我們就在想,只讓系統自帶的這些惡棍來快活,我自己能不能也make一個hook出來和它們同流合污呢?答案是肯定的。

       我們來回顧一下目前系統中已經註冊了的hook函數可分爲以下幾類:

       它們在協議棧中位置如下:

       首先我們心裏要非常清楚的知道我們將要開發的這個hook函數位於哪個HOOK點的什麼級別,它的前後分別是哪些函數,這一點很重要,因爲遇到問題時至少心裏有個譜。

       我們今天講的這個hook函數功能很簡單,主要是向大家展示開發流程和方法。細節性的東西還需要每個人日積月累的修煉才行。

 

        要註冊一個hook函數需要用到nf_register_hook()或者nf_register_hooks()系統API和一個struct nf_hook_ops{}類型的結構體對象。最簡單的hook函數如下:

#include

#include

#include

#include

#include

#include

#include

#include

#include

 

MODULE_LICENSE("GPL");

MODULE_AUTHOR("koorey KING");

MODULE_DESCRIPTION("My hook test");

 

static int pktcnt = 0;

//我們自己定義的hook回調函數,丟棄每第5×n(n=1,2,3,4…)個ICMP報文。

static unsigned int myhook_func(unsigned int hooknum, struct sk_buff **skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *))

{

   const struct iphdr *iph = (*skb)->nh.iph;

   if(iph->protocol == 1){

      atomic_inc(&pktcnt);

      if(pktcnt%5 == 0){

           printk(KERN_INFO "%d: drop an ICMP pkt to %u.%u.%u.%u !\n", pktcnt,NIPQUAD(iph->daddr));

           return NF_DROP;

      }

   }

   return NF_ACCEPT;

}

 

static struct nf_hook_ops nfho={

        .hook           = myhook_func,  //我們自己的hook回調處理函數

        .owner          = THIS_MODULE,

        .pf             = PF_INET,

        .hooknum        = NF_IP_LOCAL_OUT, //掛載在本地出口處

        .priority       = NF_IP_PRI_FIRST,  //優先級最高

};

 

static int __init myhook_init(void)

{

    return nf_register_hook(&nfho);

}

 

static void __exit myhook_fini(void)

{

    nf_unregister_hook(&nfho);

}

 

module_init(myhook_init);

module_exit(myhook_fini);

 

       我們在LOCAL_OUT這個HOOK點上,以最高優先級NF_IP_PRI_FIRST註冊了一個名爲myhook_func()的函數。從本機發出的所有數據包從協議棧進入Netfilter框架時,最先都會被該函數所看到,然後我們在這裏就可以“胡作非爲”了。

       這個模塊最後會被編譯成名爲myhook.ko的驅動模塊,然後用insmod來將其加載。具體操作流程如下:

       可以看到,我們自己的hook函數已經成功run起來了。我們可能不僅侷限於做這麼簡單一個hook,沒什麼意義,也沒啥成就感。況且這種hook壓根兒就沒有存在的價值,因爲我們完全可以通過iptables來配置相應的規則而達到同樣的目的。

       OK,那我們就改造一下剛寫的這個hook。讓它實現的功能是:每收到5個ICMP報文就向指定的IP地址發送一個UDP報文。由於這個功能的開發牽扯到內核協議棧編程,關於協議棧部分打算在以後的系列博文中詳細闡述。這裏僅做個簡單的普及入門就可以了。

      我們要實現的功能是從內核中發一個報文,這完全不同於之前在用戶層通過socket套接字編程的模式。

Godbach兄的文章http://blog.chinaunix.net/u/33048/showart_2043789.html,以及內核版的精華帖《教你修改以及重構skb》都是非常經典的參考文章。不太明白的童鞋可以去拜讀一下:http://linux.chinaunix.net/bbs/thread-1152885-1-4.html

      再重申一下我們的目標,每收到5個ICMP報文就向指定IP(例如118.6.24.132)發送一個UDP報文。這裏我是在內核裏自己去DIY一個新的skb出來,構造數據包和發送數據包的過程如下:

#define    ETH    "eth0"  //接口名稱

#define    SIP     "192.168.6.130" //接口的IP地址

#define    DIP     "118.6.24.132//要發送UDP報文的目的IP地址

#define    SPORT   39804   //源端口

#define    DPORT   6980    //目的端口

unsigned char SMAC[ETH_ALEN] = {0x00,0x0C,0x29,0x33,0x2C,0x3C}; //eth0網卡地址

unsigned char DMAC[ETH_ALEN] = {0x00,0x50,0x56,0xF4,0x8B,0xB3}; //默認網關的網卡地址

 

static int build_and_xmit_udp(char * eth, u_char * smac, u_char * dmac,

             u_char * pkt, int pkt_len,u_long sip, u_long dip,

             u_short sport, u_short dport)

{

  struct sk_buff * skb = NULL;

  struct net_device * dev = NULL;

  struct ethhdr * ethdr = NULL;

  struct iphdr * iph = NULL;

  struct udphdr * udph = NULL;

  u_char * pdata = NULL;

 

  if(NULL == smac || NULL == dmac)

      goto out;

 

  if(NULL == (dev= dev_get_by_name(eth)))

        goto out;

  //通過alloc_skb()來爲一個新的skb申請內存結構

  skb = alloc_skb(pkt_len + sizeof(struct iphdr) + sizeof(struct udphdr) + LL_RESERVED_SPACE(dev), GFP_ATOMIC);

 

  if(NULL == skb)

      goto out;

  skb_reserve(skb, LL_RESERVED_SPACE(dev));

 

  skb->dev = dev;

  skb->pkt_type = PACKET_OTHERHOST;

  skb->protocol = __constant_htons(ETH_P_IP);

  skb->ip_summed = CHECKSUM_NONE;

  skb->priority = 0;

 

  skb->nh.iph = (struct iphdr*)skb_put(skb, sizeof(struct iphdr));

  skb->h.uh = (struct udphdr*)skb_put(skb, sizeof(struct udphdr));

 

  pdata = skb_put(skb, pkt_len); //預留給上層用於數據填充的接口

  {

     if(NULL != pkt)

        memcpy(pdata, pkt, pkt_len);

  }

 

  //“從上往下”填充skb結構,依次是UDP--IP--MAC

  udph = (struct udphdr *)skb->h.uh;

  memset(udph, 0, sizeof(struct udphdr));

  udph->source = sport;

  udph->dest = dport;

  skb->csum = 0;

  udph->len = htons(sizeof(struct udphdr)+pkt_len);

  udph->check = 0;

  //填充IP

  iph = (struct iphdr*)skb->nh.iph;

  iph->version = 4;

  iph->ihl = sizeof(struct iphdr)>>2;

  iph->frag_off = 0;

  iph->protocol = IPPROTO_UDP;

  iph->tos = 0;

  iph->daddr = dip;

  iph->saddr = sip;

  iph->ttl = 0x40;

  iph->tot_len = __constant_htons(skb->len);

  iph->check = 0;

  iph->check = ip_fast_csum((unsigned char *)iph,iph->ihl);

 

  skb->csum = skb_checksum(skb, iph->ihl*4, skb->len - iph->ihl * 4, 0);

  udph->check = csum_tcpudp_magic(sip, dip, skb->len - iph->ihl * 4, IPPROTO_UDP, skb->csum);

  //填充MAC

  skb->mac.raw = skb_push(skb, 14);

  ethdr = (struct ethhdr *)skb->mac.raw;

  memcpy(ethdr->h_dest, dmac, ETH_ALEN);

  memcpy(ethdr->h_source, smac, ETH_ALEN);

  ethdr->h_proto = __constant_htons(ETH_P_IP);

  //調用dev_queue_xmit()發送報文

  if(0 > dev_queue_xmit(skb))

      goto out;

 

out:

   if(NULL != skb)

   {

        dev_put (dev);

        kfree_skb (skb);

   }

   return(NF_ACCEPT);

}

       上面這部分代碼看不懂沒關係,因爲它需要比較熟練的內核協議棧編程知識,大家可以從整體上對其有個感性的把握就可以了。後面如果有時間我會再寫個TCP/IP內核協議棧分析的系列文章,雖然CU上有很多大牛已經在寫了,但每個人的收穫不一樣,和大家分享也是學習的另一種形式。好了,閒話不多說。我們這個hook的最終版本在“  myhook.zip ”下載。

       接下來,激動人心的時刻又到了,我們來驗證一下我們的hook函數是否可以按預期一樣地進行工作。編譯和加載流程如前面所述。我們爲上層應用層往UDP報文中填充數據預留了接口,所以我們可以以如下的形式來調用build_and_xmit_udp()接口:

build_and_xmit_udp(ETH,SMAC,DMAC,”hello”,5,in_aton(SIP),in_aton(DIP),htons(SPORT),htons(DPORT));

       通過wireshark抓包來驗證一下是不是每收到5個ICMP報文就往118.6.24.132地址發送一個內容僅有“hello”字符串的UDP報文:

 

       經過這麼一番“改造”,我們自定義這個hook函數算是有點特色了。至此我們今天的內容就全部講完了。估計有些人可能覺得還少了點什麼,有沒有悟性比較高的童鞋提出幾點質疑來?沒錯,就是我們這個hook裏設置的IP地址是固定的,包括MAC地址、源和目的端口以及發送的內容。用戶空間我們根本沒法對這些屬性進行操作,驟然間,這個模塊的可操作性和易用性大打折扣。那麼我們到底如何才能從用戶空間來操作這個新註冊的hook呢?

       未完,待續…

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章