作者:bioforge [email protected] 原名: 翻譯,修改: duanjigang 翻譯參考:raodan (raod_at_30san.com) 2003-08-22
第一章 簡介
本文將描述如何利用Linux網絡堆棧的竅門(不一定都是漏洞)來達到一些目的,或者是惡意的,或者是出於其它意圖的。文中會就後門通訊對Netfilter鉤子進行討論,並在本地機器上實現將這個傳輸從基於Libpcap的嗅探器(sniffer)中隱藏。 Netfilter是2.4內核的一個子系統。Netfilter可以通過在內核的網絡代碼中使用各種鉤子來實現數據包過濾,網絡地址轉換(NAT)和連接跟蹤等網絡欺騙。這些鉤子被放置在內核代碼段,或者靜態編譯進內核,或者作為一個可動態加載/卸載的可卸載模塊,然後就可以注冊稱之為網絡事件的函數(比如數據包的接收)。
1.1 本文論述的內容
本文將講述內核模塊的編寫者如何利用Netfilter的鉤子來達到任何目的,以及怎樣將網絡傳輸從一個Libpcap的應用中隱藏掉。盡管Linux2.4支持對IPV4,IPV6以及DECnet的鉤子,本文只提及IPV4的鉤子。但是,對IPV4的大多數應用內容同樣也可以應用於其他協議。出於教學目的,我們在附錄A給出了一個可以工作的內核模塊,實現基本的數據包過濾功能。針對本文中所列技術的所有開發和試驗都在Intel機子上的Linux2.4.5系統上進行過。對Netfilte 鉤子行為的測試使用的是回環設備(Loopback device),以太網設備和一個點對點接口的調制解調器。 對Netfilter進行完全理解是我撰寫本文的另一個初衷。我不能保證這篇文章所附的代碼100%的沒有差錯,但是所列舉的所有代碼我都事先測試過了。我已經飽嘗了內核錯誤帶來的磨砺,而你卻不必再經受這些。同樣,我不會為按照這篇文檔所說的任何東西進行的作所所為帶來的損失而負責。閱讀本篇文章的讀者最好熟悉C程序設計語言,並且對內核可卸載模塊有一定的經驗。 如果我在文中犯了任何錯誤的話,請告知我。我對於你們的建議和針對此文的改進或者其它的Netfilter應用會傾心接受。
1.2 本文不會涉及到的方面
本文並不是Netfilter的完全貫穿(或者進進出出的講解)。也不是iptables命令的介紹。如果你想更好的學習iptables的命令,可以去咨詢man手冊。 讓我們從介紹Nerfilter的使用開始吧……….
第二章 各種NetFilter 鉤子及其用法
2.1 Linux內核對數據包的處理
我將盡最大努力去分析內核處理數據包的詳細內幕,然而對於事件觸發處理以及之後的Netfilter 鉤子不做介紹。原因很簡單,因為Harald Welte 關於這個已經寫了一篇再好不過的文章,如果你想獲取更多關於Linux對數據包的相關處理知識的話,我強烈建議你也閱讀一下這篇文章。目前,就認為數據包只是經過了Linux內核的網絡堆棧,它穿過幾層鉤子,在經過這些鉤子時,數據包被解析,保留或者丟棄。這就是所謂的Netfilter 鉤子。
2.2 Ipv4中的Netfilter鉤子
Netfilter為IPV4定義了5個鉤子。可以在 linux/netfilter-ipv4.h裡面找到這些符號的定義,表2.1列出了這些鉤子。
表 2.1. ipv4中定義的鉤子
CODE:[Copy to clipboard]鉤子名稱 調用時機 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 返回碼
CODE:[Copy to clipboard]返回碼 含義 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再次調用這個鉤子函數,很明顯,你應該慎重的應用這個返回值,以免程序陷入死循環。
第三章 注冊和注銷NetFilter 鉤子
注冊一個鉤子函數是一個圍繞nf_hook_ops結構體的很簡單的過程,在linux/netfilter.h中有這個結構體的定義,定義如下:
CODE:[Copy to clipboard]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鉤子的注冊
CODE:[Copy to clipboard]/* Sample code to install a Netfilter hook function that will * drop all incoming packets. */ #define __KERNEL__ #define MODULE #include #include #include #include
/* 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()函數,並將注冊時用到的結構體地址再次作為注銷函數參數使用就可以了。 第四章 基本的NetFilter數據包過濾技術 4.1 鉤子函數近距離接觸 現在是我們來查看獲得的數據如何傳入鉤子函數並被用來進行過濾決策的時候了。所以,我們需要更多的關注於nf_hookfn函數的模型。Linux/netfilter.h給出了如下的接口定義:
CODE:[Copy to clipboard]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接收到的數據中最有趣和最有用的部分進行了分析,下面就要開始介紹如何利用這些信息對數據包進行各種各樣的過濾。
4.2 基於接口的過濾 這將是我們能做的最簡單的過濾技術。是否還記得我們的鉤子函數接收到的net_device結構體?利用net_device結構體中的name鍵值,我們可以根據數據包的目的接口名或者源接口名來丟棄這些數據包。為了拋棄所有發向”eth0”的數據,我們只需要比較一下“in->name”和“eth0”,如果匹配的話,鉤子函數返回NF_DROP,然後這個數據包就被銷毀了。它就是這樣的簡單。列表2給出了示例代碼。請注意輕量級防火牆(LWFW)會使用到這裡提到的所有過濾方法。LWFW同時還包含了一個IOCTL方法來動態改變自身的行為。
列表2. 基於源接口(網卡名)的數據過濾技術
CODE:[Copy to clipboard]/* 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 #include #include #include /* For IP header */ #include #include
/* 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地址的過濾技術。 4.3 基於IP地址的過濾 類似基於接口的數據包過濾技術,基於源/目的IP地址的數據包過濾技術也很簡單。這次我們對sk_buff結構體比較感興趣。現在應該記起來,Skb參數是一個指向sk_buff結構體的指針的指針。為了避免運行時出現錯誤,通常有一個好的習慣就是另外聲明一個指針指向sk_buff結構體的指針,把它賦值為雙重指針所指向的內容,像這樣:
CODE:[Copy to clipboard]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地址
CODE:[Copy to clipboard]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地址的過濾技術
CODE:[Copy to clipboard]/* 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 #include #include #include /* For IP header */ #include #include
/* 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); } 4.4 基於TCP端口的過濾 另外一個要執行的簡單的規則就是基於TCP目的端口的數據包過濾。這比檢驗IP地址稍微復雜一點,因為我們要自己創建一個指向TCP頭的指針。還記得前面關於傳輸層頭和網絡層頭所做的討論嗎?獲得一個TCP頭指針很簡單,只需要申請一個指向tcphdr(定義在linux/tcp.h中)結構體的指針,並將它指向包數據中的IP頭後面。或許一個例子就可以了。列表5展示了怎樣檢測一個數據包的TCP目的端口與我們想丟棄數據的指定端口是否一致。與列表3一樣,這段代碼也是從LWFW中拿出來的 列表5. 檢測接收到數據包的TCP目的端口
CODE:[Copy to clipboard]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是網絡字節序時,這個函數才能工作。數據包過濾技術的基礎就是:對於一個特定的數據包,你必須對怎樣到達你想要的信息段的方法非常了解。下面,我們將進入更有趣的世界。
第五章 NetFilter鉤子其他可能的用法
在這裡我將會就Netfilter在其它方面的更有趣的應用給你作一些建議。在5.1我會給你提供一些思想源泉。5.2節將會討論並提供能運行的代碼,這個代碼使一個基於內核的FTP密碼嗅探器,能夠遠程獲取密碼。事實上,它運行的很好以至於我有些驚恐,所以將它寫了出來。
5.1 隱藏後門守護進程
內核模塊編程實際上是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的密碼嗅探器。嗅探向外發送的密碼並保存起來,直到神秘數據包到來所要這些信息的時候,就將它發送出去。 好了,上面是一些簡單的思想列表。最後一個想法將會在下一節中進行詳細的介紹,因為這一節為讀者提供了一個很好的機會,使得我們能夠接觸更多的內核內部的網段絡代碼。
5.2 基於內核的FTP密碼獲取Sniffer
針對前面談到的概念,這裡給出了一個例證—一個後門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中找到這些有效的值。最有用的都在表三列了出來。
表三.常見接口(網卡)類型
CODE:[Copy to clipboard]類型碼 接口類型 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卻會嘗試去對已經釋放掉的數據包做相同的事情。好了,代碼的討論已經足夠了,現在是看代碼的時候了。 5.2.1 nsniffer 的代碼 代碼超過發貼上限,見附件 5.2.2 getpass.c 代碼 代碼超過發貼上限,見附件
第六章 在Libpcap中隱藏網絡通訊
6.1 SOCK_PACKET, SOCK_RAW 和Libpcap
系統管理員經常用到的一些軟件可“數據包嗅探器”這個標題進行分類。最普通的用於一般目的的數據包嗅探器有 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套結字接口中隱藏一個網絡傳輸。
6.2 給狼披上羊皮 (這個譯法借鑒於參考譯文)
當一個數據包被接收到並發送給一個packet套結字時,packet_rcv()函數會被調用。可以在net/packet/af_packet.c中找到這個函數的源代碼。packet_rcv()負責使數據通過所有可能應用於數據目的地的Netfilter,最終將數據投遞到用戶空間。為了從PACKET中隱藏數據包,我們需要設法讓packet_rcv()對於一些特定的數據包一點也不調用。我們怎樣實現這個?當然是優秀的ol式的函數劫持了。 函數劫持的基本操作是:如果我們知道一個內核函數,甚至是那些沒有被導出的函數的入口地址,我們可以在實際的代碼運行前將這個函數重定位到其他的位置。為了達到這樣的目的,我們首先要從這個函數的開始,保存其原來的指令字節,然後將它們換成跳轉到我們的代碼處執行的絕對跳轉指令。例如以i386匯編語言實現該操作如下:
CODE:[Copy to clipboard]movl (address of our function), %eax jmp *eax 這些指令產生的16進制代碼如下(假設函數地址為0):
CODE:[Copy to clipboard]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地址的數據包。 第七章 結束語 希望到現在為止,你對於什麼是Netfilter,怎樣使用Netfilter,可以對Netfilter做些什麼已經有了一個基本的了解。你應該也具有了在本地機器上將一些特定的網絡傳輸從運行在這些機器上的嗅探型軟件中隱藏的知識了。如果你想要關於這方面的壓縮包的話,可以直接給我發送E-mail郵件。我會為你做的任何修改,注釋和建議而感激。現在,我就把這些有趣的東西留給你,你可以自由發揮自己的想象力。
附錄A 輕量級防火牆 A.1 縱覽 輕量級防火牆(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結構體的一份拷貝值,並顯示它的內容。
A.2 源代碼 lwfw.c 見附件 A.3 lwfw.h,Makefile 見附件 A.4 譯者添加的測試程序 下面是譯者自己在學習時寫的一個對LWFW的過濾規則進行設置和改動的例子,你也可以對此段代碼進行修改,當模塊成功加載之後,建立一個字符設備文件,然後這個程序就能運行了。
CODE:[Copy to clipboard]/* Name: test.c Author: duanjigang Date: 2006-5-15 */ #include #include #include #include #include #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); } 附錄B 第六部分的代碼 這裡是一個簡單的模塊,在這個模塊中將對packet_rcv()函數和raw_rcv()函數進行替換,從而隱藏到達或者離開我們指定所IP地址的數據包。默認的IP是“127.0.0.1”,但是,可以通過修改#define IP 來改動這個值。同樣提供了一個bash的腳本,負責從Sytem.map文件中獲取所需函數的地址,並且負責模塊的插入,在插入模塊時,以所需的格式將這些函數的地址傳遞給內核。這個加載腳本是grem寫的。原來是為我的mod-off項目而寫,經過簡單的修改就能用於這裡的模塊,再次感謝grem。 這裡給出的模塊只是原理性的代碼,沒有任何模塊隱藏的方法。有很重要的一點需要記住,盡管這個模塊能夠從運行於同一台機子上的嗅探器中隱藏指定的傳輸,但是,位於同一個網段上的其他機子上的嗅探器仍然能夠看到這些數據包。看了這個模塊,精干的讀者很快就能設計一些Netfilter鉤子函數來阻斷任何一種想要阻斷的數據包。我就利用本文中提到的技術成功地在其它內核模塊項目中實現了對控制和信息獲取數據包的隱藏。 (此處代碼見附件)
[參考文獻]: [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