基于Netfilter的内核模块向出站数据包添加数据问题
内核模块添加TCP数据包载荷导致崩溃的问题修复
代码中的关键错误
- 未定义类型与变量:
struct iph、struct tcph是错误类型,应使用内核标准的struct iphdr、struct tcphdr;new_skb未提前声明。 - skb_copy_expand参数错误:第三个参数是需要新增的尾部空间大小,而非
skb->tail + extra_size,错误参数会导致分配远超需求的内存,触发OOM或内存越界。 - 未正确修改TCP载荷:没有将额外数据写入数据包的有效载荷区域,也未更新TCP相关长度字段。
- 校验和未重新计算:修改IP、TCP头部后未更新校验和,导致数据包校验失败,引发内核错误。
- ip_local_out使用不当:在NETFILTER钩子中无需手动调用
ip_local_out,钩子返回后内核会自动处理后续流程,手动调用会导致数据包重复处理或状态混乱。 - 非线性skb处理缺失:未确保skb处于线性可写状态,直接访问数据可能导致非法内存访问。
修正后的代码实现
#include <linux/kernel.h> #include <linux/module.h> #include <linux/netfilter.h> #include <linux/netfilter_ipv4.h> #include <linux/slab.h> #include <linux/inet.h> #include <linux/ip.h> #include <linux/tcp.h> #include <linux/skbuff.h> static struct nf_hook_ops nfho_out; unsigned int hook_out(void* priv, struct sk_buff* skb, const struct nf_hook_state* state) { struct iphdr* iphdr; struct tcphdr* tcphdr; struct sk_buff* new_skb; unsigned char extra_data[] = {'A', 'D', 'D', 'T', 'H', 'I', 'S', 0}; int extra_size = sizeof(extra_data); int tcp_payload_len; unsigned char* payload_ptr; // 检查skb有效性 if (!skb) return NF_ACCEPT; // 线性化skb确保可修改 if (skb_linearize(skb) != 0) { printk(KERN_WARNING "Failed to linearize skb\n"); return NF_ACCEPT; } // 获取IP头部并校验TCP协议 iphdr = ip_hdr(skb); if (!iphdr || iphdr->protocol != IPPROTO_TCP) return NF_ACCEPT; // 获取TCP头部 tcphdr = tcp_hdr(skb); if (!tcphdr) return NF_ACCEPT; // 过滤目标端口9999的数据包 if (ntohs(tcphdr->dest) != 9999) return NF_ACCEPT; // 复制skb并预留额外尾部空间 new_skb = skb_copy_expand(skb, skb_headroom(skb), extra_size, GFP_ATOMIC); if (!new_skb) { printk(KERN_WARNING "Failed to allocate new skb\n"); return NF_ACCEPT; } // 更新新skb的IP头部指针 iphdr = ip_hdr(new_skb); if (!iphdr) { kfree_skb(new_skb); return NF_ACCEPT; } // 更新IP总长度 int new_ip_len = ntohs(iphdr->tot_len) + extra_size; iphdr->tot_len = htons(new_ip_len); // 重新计算IP校验和 iphdr->check = 0; iphdr->check = ip_fast_csum((unsigned char*)iphdr, iphdr->ihl); // 更新新skb的TCP头部指针 tcphdr = tcp_hdr(new_skb); if (!tcphdr) { kfree_skb(new_skb); return NF_ACCEPT; } // 获取TCP载荷起始地址与原始长度 tcp_payload_len = new_ip_len - (iphdr->ihl * 4) - (tcphdr->doff * 4); payload_ptr = (unsigned char*)tcphdr + (tcphdr->doff * 4); // 将额外数据追加到TCP载荷末尾 memcpy(payload_ptr + tcp_payload_len, extra_data, extra_size); // 更新TCP校验和:先清零,再让内核自动计算 tcphdr->check = 0; new_skb->ip_summed = CHECKSUM_NONE; // 强制内核重新计算校验和 // 替换原skb,让内核继续处理新数据包 skb_replace(skb, new_skb); printk(KERN_INFO "Modified TCP Packet: added %d bytes\n", extra_size); return NF_ACCEPT; } static int __init mod_init() { nfho_out.hook = hook_out; nfho_out.hooknum = NF_INET_LOCAL_OUT; nfho_out.pf = PF_INET; nfho_out.priority = NF_IP_PRI_FIRST; return nf_register_hook(&nfho_out); } static void __exit mod_exit() { nf_unregister_hook(&nfho_out); } MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Add extra data to TCP packets destined to port 9999"); module_init(mod_init); module_exit(mod_exit);
关键修复说明
- skb线性化:使用
skb_linearize确保数据包处于线性状态,避免非线性skb导致的内存访问错误。 - 正确的skb扩展:
skb_copy_expand的第三个参数传入extra_size,仅分配所需的额外尾部空间。 - 头部字段更新:修改IP总长度后,重新计算IP校验和;TCP校验和清零并设置
new_skb->ip_summed = CHECKSUM_NONE,让内核自动重新计算。 - skb替换:使用
skb_replace将原skb替换为修改后的新skb,返回NF_ACCEPT让内核继续处理后续流程,无需手动调用ip_local_out。 - 变量声明规范:使用内核标准的
struct iphdr、struct tcphdr类型,所有变量提前声明。
内容的提问来源于stack exchange,提问作者nahjoh45
相关产品推荐
相关产品推荐

