(十五)洞悉linux下的Netfilter&iptables:开发自己的hook函数【实战】(上)
2015-11-25 09:14
603 查看
向Netfilter中注册自己的hook函数
数据包在协议栈中传递时会经过不同的HOOK点,而每个HOOK点上又被Netfilter预先注册了一系列hook回调函数,当每个清纯的数据包到达这些点后会被这些可恶hook函数轮番调戏一番。有时候我们就在想,只让系统自带的这些恶棍来快活,我自己能不能也make一个hook出来和它们同流合污呢?答案是肯定的。
我们来回顾一下目前系统中已经注册了的hook函数可分为以下几类:
它们在协议栈中位置如下:
首先我们心里要非常清楚的知道我们将要开发的这个hook函数位于哪个HOOK点的什么级别,它的前后分别是哪些函数,这一点很重要,因为遇到问题时至少心里有个谱。
我们今天讲的这个hook函数功能很简单,主要是向大家展示开发流程和方法。细节性的东西还需要每个人日积月累的修炼才行。
要注册一个hook函数需要用到nf_register_hook()或者nf_register_hooks()系统API和一个struct nf_hook_ops{}类型的结构体对象。最简单的hook函数如下:
我们在LOCAL_OUT这个HOOK点上,以最高优先级NF_IP_PRI_FIRST注册了一个名为myhook_func()的函数。从本机发出的所有数据包从协议栈进入Netfilter框架时,最先都会被该函数所看到,然后我们在这里就可以“胡作非为”了。
这个模块最后会被编译成名为myhook.ko的驱动模块,然后用insmod来将其加载。具体操作流程如下:
可以看到,我们自己的hook函数已经成功run起来了。我们可能不仅局限于做这么简单一个hook,没什么意义,也没啥成就感。况且这种hook压根儿就没有存在的价值,因为我们完全可以通过iptables来配置相应的规则而达到同样的目的。
OK,那我们就改造一下刚写的这个hook。让它实现的功能是:每收到5个ICMP报文就向指定的IP地址发送一个UDP报文。由于这个功能的开发牵扯到内核协议栈编程,关于协议栈部分打算在以后的系列博文中详细阐述。这里仅做个简单的普及入门就可以了。
我们要实现的功能是从内核中发一个报文,这完全不同于之前在用户层通过socket套接字编程的模式。
Godbach兄的文章http://blog.chinaunix.net/u/33048/showart_2043789.html,以及内核版的精华帖《教你修改以及重构skb》都是非常经典的参考文章。不太明白的童鞋可以去拜读一下:http://linux.chinaunix.net/bbs/thread-1152885-1-4.html。
再重申一下我们的目标,每收到5个ICMP报文就向指定IP(例如118.6.24.132)发送一个UDP报文。这里我是在内核里自己去DIY一个新的skb出来,构造数据包和发送数据包的过程如下:
上面这部分代码看不懂没关系,因为它需要比较熟练的内核协议栈编程知识,大家可以从整体上对其有个感性的把握就可以了。后面如果有时间我会再写个TCP/IP内核协议栈分析的系列文章,虽然CU上有很多大牛已经在写了,但每个人的收获不一样,和大家分享也是学习的另一种形式。好了,闲话不多说。我们这个hook的最终版本在“
myhook.zip ”下载。
接下来,激动人心的时刻又到了,我们来验证一下我们的hook函数是否可以按预期一样地进行工作。编译和加载流程如前面所述。我们为上层应用层往UDP报文中填充数据预留了接口,所以我们可以以如下的形式来调用build_and_xmit_udp()接口:
build_and_xmit_udp(ETH,SMAC,DMAC,”hello”,5,in_aton(SIP),in_aton(DIP),htons(SPORT),htons(DPORT));
通过wireshark抓包来验证一下是不是每收到5个ICMP报文就往118.6.24.132地址发送一个内容仅有“hello”字符串的UDP报文:
经过这么一番“改造”,我们自定义这个hook函数算是有点特色了。至此我们今天的内容就全部讲完了。估计有些人可能觉得还少了点什么,有没有悟性比较高的童鞋提出几点质疑来?没错,就是我们这个hook里设置的IP地址是固定的,包括MAC地址、源和目的端口以及发送的内容。用户空间我们根本没法对这些属性进行操作,骤然间,这个模块的可操作性和易用性大打折扣。那么我们到底如何才能从用户空间来操作这个新注册的hook呢?
未完,待续…
数据包在协议栈中传递时会经过不同的HOOK点,而每个HOOK点上又被Netfilter预先注册了一系列hook回调函数,当每个清纯的数据包到达这些点后会被这些可恶hook函数轮番调戏一番。有时候我们就在想,只让系统自带的这些恶棍来快活,我自己能不能也make一个hook出来和它们同流合污呢?答案是肯定的。
我们来回顾一下目前系统中已经注册了的hook函数可分为以下几类:
它们在协议栈中位置如下:
首先我们心里要非常清楚的知道我们将要开发的这个hook函数位于哪个HOOK点的什么级别,它的前后分别是哪些函数,这一点很重要,因为遇到问题时至少心里有个谱。
我们今天讲的这个hook函数功能很简单,主要是向大家展示开发流程和方法。细节性的东西还需要每个人日积月累的修炼才行。
要注册一个hook函数需要用到nf_register_hook()或者nf_register_hooks()系统API和一个struct nf_hook_ops{}类型的结构体对象。最简单的hook函数如下:
#include #include #include #include #include #include #include #include #include MODULE_LICENSE("GPL"); MODULE_AUTHOR("koorey KING"); MODULE_DESCRIPTION("My hook test"); static int pktcnt = 0; //我们自己定义的hook回调函数,丢弃每第5×n(n=1,2,3,4…)个ICMP报文。 static unsigned int myhook_func(unsigned int hooknum, struct sk_buff **skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)) { const struct iphdr *iph = (*skb)->nh.iph; if(iph->protocol == 1){ atomic_inc(&pktcnt); if(pktcnt%5 == 0){ printk(KERN_INFO "%d: drop an ICMP pkt to %u.%u.%u.%u !\n", pktcnt,NIPQUAD(iph->daddr)); return NF_DROP; } } return NF_ACCEPT; } static struct nf_hook_ops nfho={ .hook = myhook_func, //我们自己的hook回调处理函数 .owner = THIS_MODULE, .pf = PF_INET, .hooknum = NF_IP_LOCAL_OUT, //挂载在本地出口处 .priority = NF_IP_PRI_FIRST, //优先级最高 }; static int __init myhook_init(void) { return nf_register_hook(&nfho); } static void __exit myhook_fini(void) { nf_unregister_hook(&nfho); } module_init(myhook_init); module_exit(myhook_fini); |
这个模块最后会被编译成名为myhook.ko的驱动模块,然后用insmod来将其加载。具体操作流程如下:
可以看到,我们自己的hook函数已经成功run起来了。我们可能不仅局限于做这么简单一个hook,没什么意义,也没啥成就感。况且这种hook压根儿就没有存在的价值,因为我们完全可以通过iptables来配置相应的规则而达到同样的目的。
OK,那我们就改造一下刚写的这个hook。让它实现的功能是:每收到5个ICMP报文就向指定的IP地址发送一个UDP报文。由于这个功能的开发牵扯到内核协议栈编程,关于协议栈部分打算在以后的系列博文中详细阐述。这里仅做个简单的普及入门就可以了。
我们要实现的功能是从内核中发一个报文,这完全不同于之前在用户层通过socket套接字编程的模式。
Godbach兄的文章http://blog.chinaunix.net/u/33048/showart_2043789.html,以及内核版的精华帖《教你修改以及重构skb》都是非常经典的参考文章。不太明白的童鞋可以去拜读一下:http://linux.chinaunix.net/bbs/thread-1152885-1-4.html。
再重申一下我们的目标,每收到5个ICMP报文就向指定IP(例如118.6.24.132)发送一个UDP报文。这里我是在内核里自己去DIY一个新的skb出来,构造数据包和发送数据包的过程如下:
#define ETH "eth0" //接口名称 #define SIP "192.168.6.130" //接口的IP地址 #define DIP "118.6.24.132" //要发送UDP报文的目的IP地址 #define SPORT 39804 //源端口 #define DPORT 6980 //目的端口 unsigned char SMAC[ETH_ALEN] = {0x00,0x0C,0x29,0x33,0x2C,0x3C}; //eth0网卡地址 unsigned char DMAC[ETH_ALEN] = {0x00,0x50,0x56,0xF4,0x8B,0xB3};//默认网关的网卡地址 static int build_and_xmit_udp(char * eth, u_char * smac, u_char * dmac, u_char * pkt, int pkt_len,u_long sip, u_long dip, u_short sport, u_short dport) { struct sk_buff * skb = NULL; struct net_device * dev = NULL; struct ethhdr * ethdr = NULL; struct iphdr * iph = NULL; struct udphdr * udph = NULL; u_char * pdata = NULL; if(NULL == smac || NULL == dmac) goto out; if(NULL == (dev= dev_get_by_name(eth))) goto out; //通过alloc_skb()来为一个新的skb申请内存结构 skb = alloc_skb(pkt_len + sizeof(struct iphdr) + sizeof(struct udphdr) + LL_RESERVED_SPACE(dev), GFP_ATOMIC); if(NULL == skb) goto out; skb_reserve(skb, LL_RESERVED_SPACE(dev)); skb->dev = dev; skb->pkt_type = PACKET_OTHERHOST; skb->protocol = __constant_htons(ETH_P_IP); skb->ip_summed = CHECKSUM_NONE; skb->priority = 0; skb->nh.iph = (struct iphdr*)skb_put(skb, sizeof(struct iphdr)); skb->h.uh = (struct udphdr*)skb_put(skb, sizeof(struct udphdr)); pdata = skb_put(skb, pkt_len); //预留给上层用于数据填充的接口 { if(NULL != pkt) memcpy(pdata, pkt, pkt_len); } //“从上往下”填充skb结构,依次是UDP层--IP层--MAC层 udph = (struct udphdr *)skb->h.uh; memset(udph, 0, sizeof(struct udphdr)); udph->source = sport; udph->dest = dport; skb->csum = 0; udph->len = htons(sizeof(struct udphdr)+pkt_len); udph->check = 0; //填充IP层 iph = (struct iphdr*)skb->nh.iph; iph->version = 4; iph->ihl = sizeof(struct iphdr)>>2; iph->frag_off = 0; iph->protocol = IPPROTO_UDP; iph->tos = 0; iph->daddr = dip; iph->saddr = sip; iph->ttl = 0x40; iph->tot_len = __constant_htons(skb->len); iph->check = 0; iph->check = ip_fast_csum((unsigned char *)iph,iph->ihl); skb->csum = skb_checksum(skb, iph->ihl*4, skb->len - iph->ihl * 4, 0); udph->check = csum_tcpudp_magic(sip, dip, skb->len - iph->ihl * 4, IPPROTO_UDP, skb->csum); //填充MAC层 skb->mac.raw = skb_push(skb, 14); ethdr = (struct ethhdr *)skb->mac.raw; memcpy(ethdr->h_dest, dmac, ETH_ALEN); memcpy(ethdr->h_source, smac, ETH_ALEN); ethdr->h_proto = __constant_htons(ETH_P_IP); //调用dev_queue_xmit()发送报文 if(0 > dev_queue_xmit(skb)) goto out; out: if(NULL != skb) { dev_put (dev); kfree_skb (skb); } return(NF_ACCEPT); } |
myhook.zip ”下载。
接下来,激动人心的时刻又到了,我们来验证一下我们的hook函数是否可以按预期一样地进行工作。编译和加载流程如前面所述。我们为上层应用层往UDP报文中填充数据预留了接口,所以我们可以以如下的形式来调用build_and_xmit_udp()接口:
build_and_xmit_udp(ETH,SMAC,DMAC,”hello”,5,in_aton(SIP),in_aton(DIP),htons(SPORT),htons(DPORT));
通过wireshark抓包来验证一下是不是每收到5个ICMP报文就往118.6.24.132地址发送一个内容仅有“hello”字符串的UDP报文:
经过这么一番“改造”,我们自定义这个hook函数算是有点特色了。至此我们今天的内容就全部讲完了。估计有些人可能觉得还少了点什么,有没有悟性比较高的童鞋提出几点质疑来?没错,就是我们这个hook里设置的IP地址是固定的,包括MAC地址、源和目的端口以及发送的内容。用户空间我们根本没法对这些属性进行操作,骤然间,这个模块的可操作性和易用性大打折扣。那么我们到底如何才能从用户空间来操作这个新注册的hook呢?
未完,待续…
相关文章推荐
- [Linux]Directory Function
- Linux 删除文件夹和文件的命令
- Linux下快速静态编译Qt以及Qt动态/静态版本共存
- Linux下静态编译Qt程序
- 闲来瞎扯 -- 在vs2008下编写linux程序
- 在linux下用gsoap实现c语言的webservice实例
- centos6.4安装拼音输入法
- CentOS 6.4 安装极点五笔输入法
- YUM更换源(1)--yum找不到安装包 2013-01-18 20:08 8687人阅读 评论(1) 收藏 举报 分类: linux(70) 公司提供的CentOS VM中,/etc/yum.r
- linux 常用基础命令 ln 详细介绍
- 讲解Linux系统下如何自动备份MySQL数据的基本教程
- CentOS下重置MySQL的root密码的教程
- CentOS安装与配置JDK
- 转载的是linuxtone论坛上的前辈学运维的经验,用于共勉。
- linux操作记录
- Linux_ftp_命令行下下载文件get与上传文件put的命令应用
- Telnet部署与启动 windows&&linux
- Linux之通过NTP协议实现服务器时间同步的源码
- Xclock 生成
- strcmp,strncmp函数实现——string.h库函数