现在的位置: 首页 > 综合 > 正文

Hacking the Linux Kernel Network Stack中文版(转)

2013年06月18日 ⁄ 综合 ⁄ 共 23882字 ⁄ 字号 评论关闭

http://blog.chinaunix.net/u2/62281/showart_1092826.html

前一段时间看到这篇帖子,确实很经典,于是翻出了英文原版再读,顺便再翻译出来供大家学习,这篇文
章的中文版也早都有了,但是出于完全理解的目的,我还是将他翻译了出来,加进了自己的代码,虽然在上一周的翻译过程中,我尽量保留文章的原汁原味,但错误
肯定在所难免,在末尾附上原文和我自己调试通过的代码,已够构运行,大家能够参考一下!(有错误之处请指出)

[size=5]深入
Linux内核网络堆栈[/size]

作者: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是网络字节序时,这个函数才能工作。数据包过滤技术的基础就是:对于一个特定的数据包,您必须对
怎样到达您想要的信息段的方法很了解。下面,我们将进入更有趣的世界。

[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

抱歉!评论已关闭.