当前位置:   article > 正文

利用Linux内核模块Netfilter hook UDP报文_maybe caused by udp checksum offload

maybe caused by udp checksum offload

利用Linux内核模块Netfilter hook UDP报文,并对其中的部分数据进行修改


  1. 实验环境

    Ubuntu18.04:用于挂载hook程序,抓取udp报文

    Window10:udp报文的目的地,利用工具可检测到报文及其内容

  2. 实验工具

    Wireshark:抓取报文,便于同Netfilter hook到的报文进行比对

    NetAsssit:用于在Windows端接收报文

  3. 实验流程

    流程如图所示:

在这里插入图片描述

具体操作过程(操作过程中任何一步有问题可以结合Q&A部分以及参考资料部分解决):

  1. 将udpFilter.c和Makefile放在同一个文件夹下

  2. 执行"make"命令,生成udpFilter.ko

  3. 执行“sudo insmod udpFilter.ko”命令,将其挂载至内核上

  4. 执行“lsmod”命令,可以查看其是否已挂载至内核上

  5. 启动python脚本,发送消息

  6. 执行“sudo rmmod udpFilter”命令,将其卸载

  7. 执行“dmesg”可查看日志中输出的信息

  8. 模拟UDP协议

    用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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
  9. Wireshark抓取流量并分析

    在虚拟机上利用Wireshark,可抓取到该条报文,可以直接看到IP段和UDP段,并得到其中各条数据所在报文的位置,便于之后计算校验和

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MZqsriTm-1584454800070)(C:\Users\王嘉磊\AppData\Roaming\Typora\typora-user-images\image-20200317122644974.png)]

    同时Windows端可接收到消息

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u96jf2Ls-1584454800072)(C:\Users\王嘉磊\AppData\Roaming\Typora\typora-user-images\image-20200317122753758.png)]

    对该条报文进行分析:

    1. 从ip段中提取出其源ip和目的ip,协议类型,并且该字段在报文中对应的位置

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tNp7HLDU-1584454800074)(C:\Users\王嘉磊\AppData\Roaming\Typora\typora-user-images\image-20200317123138430.png)]

      协议类型在IP段的10字节

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1Qrsjzjv-1584454800075)(C:\Users\王嘉磊\AppData\Roaming\Typora\typora-user-images\image-20200317123214028.png)]

      源ip在IP段的13-16字节

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BczGZaJ2-1584454800076)(C:\Users\王嘉磊\AppData\Roaming\Typora\typora-user-images\image-20200317123337297.png)]

      目的ip在IP段的17-20字节

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rRRaXBmM-1584454800078)(C:\Users\王嘉磊\AppData\Roaming\Typora\typora-user-images\image-20200317123320457.png)]

    2. 从UDP段中得出其源端口,目的端口,UDP段长度,UDP校验和以及其数据

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MOsTKw7N-1584454800079)(C:\Users\王嘉磊\AppData\Roaming\Typora\typora-user-images\image-20200317123529673.png)]

      数据在UDP段的8字节之后

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QFc9814s-1584454800080)(C:\Users\王嘉磊\AppData\Roaming\Typora\typora-user-images\image-20200317123605099.png)]

      源端口在UDP段的1-2字节

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MPjk0WHk-1584454800081)(C:\Users\王嘉磊\AppData\Roaming\Typora\typora-user-images\image-20200317123729729.png)]

      目的端口在UDP段的3-4字节

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cmxsb49s-1584454800082)(C:\Users\王嘉磊\AppData\Roaming\Typora\typora-user-images\image-20200317123750724.png)]

      UDP段长度在UDP段的5-6字节

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-px5zyRdr-1584454800083)(C:\Users\王嘉磊\AppData\Roaming\Typora\typora-user-images\image-20200317123810078.png)]

      UDP校验和在UDP段的7-8字节

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sJOgFGmC-1584454800084)(C:\Users\王嘉磊\AppData\Roaming\Typora\typora-user-images\image-20200317123840375.png)]

  10. Hook报文

    利用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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    结果

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QzooDrfO-1584454800085)(C:\Users\王嘉磊\AppData\Roaming\Typora\typora-user-images\image-20200317125157504.png)]

    从结果中可以看出Netfilter中输出的字段与Wireshark流量中的字段相反,在之后修改数据时要注意这一点。

  11. 修改报文

    在上一步已经可以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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在Windows端即可看到修改后的数据,从图中可看到,数据已经从“hello!”变为了“thanks”

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sMcWJNPE-1584454800087)(C:\Users\王嘉磊\AppData\Roaming\Typora\typora-user-images\image-20200317130933579.png)]

  12. Q&A
    1. Wireshark中的校验和显示为“unverified”

      在Wireshark的Preferences中,找到UDP,将其“Validate the UDP checksum if possible”打勾即可。

    2. Wireshark中的校验和不正确并且提示“maybe caused by “UDP checksum offload?””

      先用“ethtool --show-offload enp0s3”检查网卡的rx-checksumming和tx-checksumming是否开启,若开启利用"ethtool --offload enp0s3 rx off tx off "关闭即可

    3. 在hook函数中添加修改数据的代码后,发送报文时,Windows端未接收到报文

      与第二个问题解决方法一致

  13. 参考资料
    • 利用netfilter hook TCP报文:https://www.cnblogs.com/southday/p/11006936.html
    • netfilter 详解:https://zhuanlan.zhihu.com/p/61343421
    • makefile 详解:https://blog.csdn.net/liang13664759/article/details/1771246
    • UDP详解 https://www.cnblogs.com/sxiszero/p/11565108.html
    • sk_buff详解 https://blog.csdn.net/shanshanpt/article/details/21024465
    • udphdr结构详解 http://www.cppblog.com/aurain/archive/2008/11/21/67461.html
    • tcphdr结构详解 https://www.cnblogs.com/chengliangsheng/archive/2014/03/22/3598883.html
    • iphdr结构详解 https://blog.csdn.net/caofengtao1314/article/details/52753894
    • wireshark显示checksum不正确 https://blog.csdn.net/wangqi0079/article/details/9064557
  14. 完整代码

    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");
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  1. 后记
    1. 这里基于Netfilter实现了对本机发出的UDP报文的截获和修改,若要对发往某一确定ip或者源ip是确定的UDP报文进行修改,可以通过修改hook条件来达到目的;

    2. 可将本机当作一个路由,对于接到本机的设备的流量,也可通过修改hook条件达到修改的目的;

    3. 理论上也可以通过对代码中变量的修改达到对TCP报文截获和修改。

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/160439
推荐阅读
相关标签
  

闽ICP备14008679号