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端可接收到消息
对该条报文进行分析:
利用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
#include
#include
#include
#include
#include
#include
#include
// 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)
这里基于Netfilter实现了对本机发出的UDP报文的截获和修改,若要对发往某一确定ip或者源ip是确定的UDP报文进行修改,可以通过修改hook条件来达到目的;
可将本机当作一个路由,对于接到本机的设备的流量,也可通过修改hook条件达到修改的目的;
理论上也可以通过对代码中变量的修改达到对TCP报文截获和修改。