赞
踩
Ubuntu18.04:用于挂载hook程序,抓取udp报文
Window10:udp报文的目的地,利用工具可检测到报文及其内容
Wireshark:抓取报文,便于同Netfilter hook到的报文进行比对
NetAsssit:用于在Windows端接收报文
流程如图所示:
具体操作过程(操作过程中任何一步有问题可以结合Q&A部分以及参考资料部分解决):
将udpFilter.c和Makefile放在同一个文件夹下
执行"make"命令,生成udpFilter.ko
执行“sudo insmod udpFilter.ko”命令,将其挂载至内核上
执行“lsmod”命令,可以查看其是否已挂载至内核上
启动python脚本,发送消息
执行“sudo rmmod udpFilter”命令,将其卸载
执行“dmesg”可查看日志中输出的信息
用Python程序写一个脚本即可实现,代码如下
import socket
udpsocket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
sdata = b"hello!" // 加b是为了将字符串转为字节
saddr=('ip',port) // 此处的ip应为自己的目标ip, port为端口号
udpsocket.sendto(sdata,addr)
在虚拟机上利用Wireshark,可抓取到该条报文,可以直接看到IP段和UDP段,并得到其中各条数据所在报文的位置,便于之后计算校验和
同时Windows端可接收到消息
对该条报文进行分析:
从ip段中提取出其源ip和目的ip,协议类型,并且该字段在报文中对应的位置
协议类型在IP段的10字节
源ip在IP段的13-16字节
目的ip在IP段的17-20字节
从UDP段中得出其源端口,目的端口,UDP段长度,UDP校验和以及其数据
数据在UDP段的8字节之后
源端口在UDP段的1-2字节
目的端口在UDP段的3-4字节
UDP段长度在UDP段的5-6字节
UDP校验和在UDP段的7-8字节
利用Linux内核模块Netfilter中的Hook函数将报文截取到,并且将其各字段打印到log中,与Wireshark中的流量进行对比
Hook函数:
unsigned int my_hook_func(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { // 这里是ip头 ip_header = (struct iphdr *)skb_network_header(skb); if (ip_header->protocol == 17) { printk("ip_saddr: %02x\n", ip_header->saddr); printk("ip_daddr: %02x\n", ip_header->daddr); // 这里是udp头 udp_header = (struct udphdr *)skb_transport_header(skb); printk(KERN_INFO "Got udp packet \n"); printk("udp_source: %02x\n", udp_header->source); printk("udp_dest: %02x\n", udp_header->dest); printk("udp_len: %02x\n", udp_header->len); //printk("udp_len(int): %d\n", udp_header->len); printk("udp_check: %02x\n", udp_header->check); // 这里输出udp报文中的data char *data = NULL; // 获取数据的起始地址 data = (char *)((char *)udp_header + 8); printk("udp_data_addr: 0x%p\n", data); printk("hex : data[0-5] = 0x%02x%02x%02x%02x%02x%02x\n", data[0], data[1], data[2],data[3], data[4], data[5]); printk("char : data[0-5] = %c%c%c%c%c%c\n", data[0], data[1], data[2],data[3], data[4], data[5]); } return NF_ACCEPT; }
结果
从结果中可以看出Netfilter中输出的字段与Wireshark流量中的字段相反,在之后修改数据时要注意这一点。
在上一步已经可以Hook到报文,现在可以对其内容进行修改。对UDP报文数据的修改其实就是对指针的一个操作,很简单。在修改UDP报文后,对修改后的报文要进行UDP校验和的计算。(UDP校验和算法在文末完整代码中)
将下列代码添加至Hook函数中
// 逐个修改字段,此处可以再进行改进,不然针对长字段修改较麻烦
data [0] = 't';
data [1] = 'h';
data [2] = 'a';
data [3] = 'n';
data [4] = 'k';
data [5] = 's';
// 重新计算校验和
int cs = checkSum(ip_header, udp_header);
// 将校验和倒转,保持跟Netfilter一致
int rcs = f8tol8(cs);
// 打印到log中
printk("f to l check: %02x\n", rcs);
// 修改校验和
udp_header->check = rcs;
在Windows端即可看到修改后的数据,从图中可看到,数据已经从“hello!”变为了“thanks”
Wireshark中的校验和显示为“unverified”
在Wireshark的Preferences中,找到UDP,将其“Validate the UDP checksum if possible”打勾即可。
Wireshark中的校验和不正确并且提示“maybe caused by “UDP checksum offload?””
先用“ethtool --show-offload enp0s3”检查网卡的rx-checksumming和tx-checksumming是否开启,若开启利用"ethtool --offload enp0s3 rx off tx off "关闭即可
在hook函数中添加修改数据的代码后,发送报文时,Windows端未接收到报文
与第二个问题解决方法一致
udpFilter.c
#include <linux/kernel.h> #include <linux/module.h> #include <linux/netfilter.h> #include <linux/netfilter_ipv4.h> #include <linux/skbuff.h> #include <linux/udp.h> #include <linux/ip.h> #include <linux/vmalloc.h> // Define some structs that will be used static struct nf_hook_ops nfho; struct udphdr *udp_header; struct iphdr *ip_header; const int MAX_NUM = 65536; // 将两个8bit合为一个16bit int b8tob16(unsigned char c1, unsigned char c2) { unsigned char t1; unsigned char t2; t1 = c1; t2 = c2; return (t1<<8)|t2; } // Exchange the first 8bit and the last 8bit between 16bit int f8tol8(int i) { unsigned char t1; unsigned char t2; // the last 8bit t1 = i&255; // the first 8bit t2 = (i&65280)>>8; return (t1<<8)|t2; } // 计算udp校验和 int checkSum(struct iphdr *iphr, struct udphdr *udphr) { struct iphdr *ih = NULL; struct udphdr *uh = NULL; unsigned char *ip_fields = NULL; unsigned char *udp_fields = NULL; char *udp_data = NULL; int *ud = NULL; int *sumarr = NULL; int sumcnt = 0; int sum = 0; ih = iphr; uh = udphr; ip_fields = (unsigned char *)ih; udp_fields = (unsigned char *)uh; ud = (int *)vmalloc(100*sizeof(int)); if(!ud) { printk("malloc for ud failure!"); return -1; } sumarr = (int *)vmalloc(200*sizeof(int)); if(!sumarr) { printk("malloc for sumarr failure!"); return -1; } // 将两个ip地址转变格式 //int ip_src1 = (ip_fields[12]<<8)|ip_fields[13]; int ip_src1 = b8tob16(ip_fields[12], ip_fields[13]); printk("ip_src[0-1] = 0x%02x%02x\n", ip_fields[12], ip_fields[13]); sumarr[sumcnt++] = ip_src1; int ip_src2 = b8tob16(ip_fields[14], ip_fields[15]); sumarr[sumcnt++] = ip_src2; int ip_dest1 = b8tob16(ip_fields[16], ip_fields[17]); sumarr[sumcnt++] = ip_dest1; int ip_dest2 = b8tob16(ip_fields[18], ip_fields[19]); sumarr[sumcnt++] = ip_dest2; // 将ip头部中的协议转变格式,并且前八位补0 int ip_prol = (int)ip_fields[9]; sumarr[sumcnt++] = ip_prol; // 将udp两个端口转变格式 int src_port = b8tob16(udp_fields[0], udp_fields[1]); sumarr[sumcnt++] = src_port; int dest_port = b8tob16(udp_fields[2], udp_fields[3]); sumarr[sumcnt++] = dest_port; // 将udp长度转变格式 int udp_l = b8tob16(udp_fields[4], udp_fields[5]); sumarr[sumcnt++] = udp_l; sumarr[sumcnt++] = udp_l; // 将校验和置为0 sumarr[sumcnt++] = 0; // 将udp中的数据转变格式 udp_data = (char *)((char *)uh + 8); int cnt = udp_l-8; printk("cnt = %d\n", cnt); if(cnt%2 == 0) { int cnt2 = cnt/2; int m = 0; int n = 0; while (cnt2 > 0) { ud[n] = b8tob16(udp_data[m], udp_data[m+1]); printk("udp_data = %2x%2x\n", udp_data[m],udp_data[m+1]); sumarr[sumcnt++] = ud[n]; printk("ud = %d\n", ud[n]); cnt2--; n++; m+=2; } } else { int cnt2 = cnt/2; int m = 0; int n = 0; while (cnt2 > 0) {; ud[n] = b8tob16(udp_data[m], udp_data[m+1]); sumarr[sumcnt++] = ud[n]; cnt2--; n++; m+=2; } ud[n] = b8tob16(udp_data[m], '0'); sumarr[sumcnt++] = ud[n]; } // 反码求和 int i ; for (i = 0; i < sumcnt; i++) { printk("One of the members of the check: %02x\n", sumarr[i]); //printk("One of the members of the check: %d\n", sumarr[i]); sum += sumarr[i]; if (sum > MAX_NUM) { sum = sum % MAX_NUM +1; } } sum = 65535 - sum; printk("sumcnt = %d\n", sumcnt); printk("My check = %d\n", sum); printk("My check(hex) = %02x\n", sum); vfree(ud); vfree(sumarr); return sum; } // Define our own hook function // The parameters of this function has been updated. // This is the prototype of this function // 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 *)) unsigned int my_hook_func(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { // 这里是ip头 ip_header = (struct iphdr *)skb_network_header(skb); if (ip_header->protocol == 17) { printk("ip_saddr: %02x\n", ip_header->saddr); printk("ip_daddr: %02x\n", ip_header->daddr); // 这里是udp头 udp_header = (struct udphdr *)skb_transport_header(skb); printk(KERN_INFO "Got udp packet \n"); printk("udp_source: %02x\n", udp_header->source); printk("udp_dest: %02x\n", udp_header->dest); printk("udp_len: %02x\n", udp_header->len); //printk("udp_len(int): %d\n", udp_header->len); printk("udp_check: %02x\n", udp_header->check); // 这里输出udp报文中的data char *data = NULL; // 获取数据的起始地址 data = (char *)((char *)udp_header + 8); printk("udp_data_addr: 0x%p\n", data); printk("hex : data[0-5] = 0x%02x%02x%02x%02x%02x%02x\n", data[0], data[1], data[2],data[3], data[4], data[5]); printk("char : data[0-5] = %c%c%c%c%c%c\n", data[0], data[1], data[2],data[3], data[4], data[5]); // 逐个修改字段,此处可以再进行改进,不然针对长字段修改较麻烦 data [0] = 't'; data [1] = 'h'; data [2] = 'a'; data [3] = 'n'; data [4] = 'k'; data [5] = 's'; // 重新计算校验和 int cs = checkSum(ip_header, udp_header); // 将校验和倒转,保持跟Netfilter一致 int rcs = f8tol8(cs); // 打印到log中 printk("f to l check: %02x\n", rcs); // 修改校验和 udp_header->check = rcs; } return NF_ACCEPT; } int init_module() { // Init the struct nfho nfho.hook = my_hook_func; nfho.hooknum = NF_INET_POST_ROUTING; nfho.pf = PF_INET; nfho.priority = NF_IP_PRI_FIRST; // Register the hook function by the struct nfho // This function has already update t2o nf_register_net_hook from nf_register_hook nf_register_net_hook(&init_net, &nfho); return 0; } void cleanup_module() { // Unregister the hook function // This function has also already update to nf_unregister_net_hook from nf_unregister_hook nf_unregister_net_hook(&init_net, &nfho); } MODULE_LICENSE("GPL"); MODULE_AUTHOR("UDP");
makefile
obj-m += udpFilter.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
udpMN
import socket
udpsocket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
sdata = b"hello!" // 加b是为了将字符串转为字节
saddr=('ip',port) // 此处的ip应为自己的目标ip, port为端口号
udpsocket.sendto(sdata,addr)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。