[2020]AYY防火墙开发,第0篇之Linux 驱动与Netfilter框架入门 huoji ayywaf,huoji,netfilter,ayy防火墙 2020-10-18 1337 次浏览 0 次点赞 最开始本来想白嫖某大会资格的,想用Apache模块做waf然后做神经网络学习做一些xxoo的 然后发现Apache各种坑 而且很多功能受限,气死人了,所以本着能做大就做大的标准来做.索性按照军用级的标准去写这个ayysec. 这是第0部分 我会持续更新.首先我们需要了解一切的基石: linux 网络过滤驱动 也叫做linux netfilter 别担心,他跟我之前的那个个人流量防火墙用NDIS过滤驱动不同的是,linux的很容易.windows的太复杂了. 本系列教程只支持IPV4 反正IPV6也没什么人用目前(2020年) ### 首先你得搞清楚 iptables和 netfilter的关系 iptables是用户态的 netfilter则是内核态的 防火墙管理员通过iptables工具,将“防火墙规则”写入到netfilter的规则数据库中 所以Linux防火墙比较正确的名称应该是netfilter/iptables ![](https://key08.com/usr/uploads/2020/10/3877044605.png) 搞清楚就很容易了.我们编写内核态的netfiler 接管系统网络包 然后做一些防火墙该做的XXOO的事情 ### 其次搞清楚我们要接管的类型 Netfilter 中定义了五个关于ipv4的hook类型 > NF_IP_PRE_ROUTING 刚刚通过数据链路层解包(从上一篇博文我们了解到netfilter是在网络层作用),进入网络层的数据包通过此点(刚刚进行完版本号,校验 和等检测),目的地址转换在此点进行; NF_IP_LOCAL_IN 经路由查找后,送往本机的通过此检查点,INPUT包过滤在此点进行; NF_IP_FORWARD 目的地是其他主机的数据包,要转发的包通过此检测点,FORWARD包过滤在此点进行 ; NF_IP_LOCAL_OUT 本机进程发出的包通过此检测点,OUTPUT包过滤在此点进行; NF_IP_POST_ROUTING 所有马上便要通过网络设备出去的包通过此检测点,内置的源地址转换功能(包括地址伪装)在此点进行 ; 然后你知道netfilter要返回的几个类型: > NF_ACCEPT:继续正常传输数据报。 这个返回值告诉Netfilter,到目前为止,该数据包还是被接受的并且该数据包应当被递交到网络堆栈的下一个阶段 NF_DROP:丢弃该数据报,不再传输。 该数据包将被完全的丢弃,所有为它分配的资源都应当被释放。 NF_STOLEN:忘掉该数据包。 该hook函数将从此开始对数据包的处理,并且Netfilter应当放弃对该数据包做任何的处理。但是,这并不意味着该数据包的资源已经被释放。这个数据包以及它独自的sk_buff数据结构仍然有效,只是hook函数从Netfilter获取了该数据包的所有权。 NF_QUEUE:将该数据包插入到用户空间。 对该数据报进行排队(通常用于将数据报给用户空间的进程进行处理)。 NF_REPEAT:再次调用该hook函数。慎用,因为很容易造成死循环。 关键结构sk_buf(非常重要) ![](https://key08.com/usr/uploads/2020/10/452277245.png) 最后简单驱动hello world: C文件: ```cpp #include #include #include MODULE_LICENSE("GPL"); MODULE_AUTHOR("huoji"); MODULE_DESCRIPTION("ayyfw network filter\n"); static int __init hello_init(void) { printk(KERN_EMERG "hello, init\n"); return 0; } static void __exit hello_exit(void) { printk(KERN_EMERG "hello, exit\n"); } module_init(hello_init); module_exit(hello_exit); ``` Makefile ```cpp ifneq ($(KERNELRELEASE),) obj-m := ayysec.o else PWD := $(shell pwd) KVER := $(shell uname -r) KDIR := /lib/modules/$(KVER)/build all: $(MAKE) -C $(KDIR) M=$(PWD) modules clean: rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions endif ``` 然后输入 ```cpp make insmod ayysec.ko dmesg 就可以看到helloworld 删除: rmmod ayysec ``` 网络过滤驱动: ```cpp #include #include #include #include #include #include #include #include #include #include MODULE_LICENSE("GPL"); MODULE_AUTHOR("huoji"); MODULE_DESCRIPTION("ayyfw network filter\n"); static struct nf_hook_ops hook_options; static unsigned int hook_func(const struct nf_hook_ops *ops, struct sk_buff *skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)) { if (!skb || !in || !out) return NF_ACCEPT; unsigned char *data_end_ptr = skb_tail_pointer(skb); struct iphdr *ip_header = ip_hdr(skb); if (!data_end_ptr || !ip_header) return NF_ACCEPT; if (ip_header->protocol == IPPROTO_TCP) { struct udphdr *udph = (struct udphdr *)((u8 *)ip_header + (ip_header->ihl << 2)); unsigned char *data_prt = (unsigned char *)((unsigned char *)udph + sizeof(struct udphdr)); unsigned char *it = data_prt; for (it = data_prt; it != data_end_ptr; ++it) { char c = *(char *)it; if (c == '\0') break; printk(KERN_EMERG "0x%02x ", c); } printk(KERN_EMERG "UDP MEME"); } //printk(KERN_EMERG "[DebugMessage] netfilter_hook: saddr: %08X protocol: %d \n", ip_header->saddr, ip_header->protocol); /* if (ip_header->protocol == IPPROTO_TCP) { struct tcphdr *tcp = tcp_hdr(skb); if (!tcp) return NF_ACCEPT; unsigned char *data_prt = (unsigned char *)((unsigned char *)tcp + (tcp->doff * 4)); unsigned char *it = data_prt; for (it = data_prt; it != data_end_ptr; ++it) { char c = *(char *)it; if (c == '\0') break; printk(KERN_EMERG "0x%02x ", c); } } 获取IPhead: struct iphdr *ip_header = ip_hdr(skb); 获取tcp头: struct tcphdr *tcp = tcp_hdr(skb); 获取UDP头: struct udphdr *udph = udp_hdr(skb); //这个好像是错误的,因为此时sk_buff的transport_header并没有指向正确的udp头,而是和network_header一同指向了ip头.网上说的,没测试过 网上推荐: udph = (struct udphdr *) ((u8 *) ip_header + (ip_header->ihl << 2)); tcp->dest = 目的端口 注意这玩意是ushort类型 比如90端口 就是要 "\x00\x5a"; 你可以用ntohs() 转换成十进制 ip_header->protocol = 协议 IPPROTO_TCP IPPROTO_UDP IPPROTO_ICMP ip_header->saddr = 发送者地址 ip_header->daddr 你懂得 in->name = 网卡 获取skb结束指针 unsigned char *data_end_ptr = skb_tail_pointer(skb); 获取skb开始指针 unsigned char *data_prt = (unsigned char *)((unsigned char *)tcp + (tcp->doff * 4)); 得到TCP报文数据 for (it = data_prt; it != data_end_ptr; ++it) { char c = *(char *)it; if (c == '\0') break; printk("%c", c); } 得到UDP开始指针 unsigned char *data_prt = (unsigned char *)((unsigned char *)udph + sizeof(struct udphdr)); 打印udp数据 for (it = data_prt; it != data_end_ptr; ++it) { char c = *(char *)it; if (c == '\0') break; printk("%c", c); } */ return NF_ACCEPT; } static int __init ayysec_init(void) { printk(KERN_EMERG "[DebugMessage] ayywaf init\n"); hook_options.hook = (nf_hookfn *)hook_func; hook_options.hooknum = NF_INET_PRE_ROUTING; hook_options.pf = PF_INET; hook_options.priority = NF_IP_PRI_FIRST; nf_register_hook(&hook_options); return 0; } static void __exit ayysec_exit(void) { printk(KERN_EMERG "[DebugMessage] ayywaf exit\n"); nf_unregister_hook(&hook_options); } module_init(ayysec_init); module_exit(ayysec_exit); ``` 注意点: linux中的ip地址是NF_INET,别傻乎乎的传xxx.xxx.xxx.xxx 有了这些数据,我们就可以愉快的过滤封包了 :) 接下来要做的: 解析http协议(重要) 解析SSL协议(重要) 本文由 huoji 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。 点赞 0
内核方面感觉通用性不好搞,持续关注中