如何在内核空间中向数据包追加数据?

6

我正在尝试从内核空间附加一些数据到数据包上。我有一个回声客户端和服务器。我在命令行中输入如下命令:./client "message",服务器就会将其回显。服务器是通过./server运行的。

现在,客户端和服务器位于两台不同的机器上(可能是虚拟机)。我正在编写一个运行在客户端机器上的内核模块。它的工作是在数据包离开机器时在"message"之后附加"12345"。我在下面展示代码。

/*
 * This is ibss_obsf_cat.c
 */

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/netfilter.h>
#include <linux/skbuff.h>
#include <linux/netdevice.h>
#include <linux/udp.h>
#include <linux/ip.h>

#undef __KERNEL__
#include <linux/netfilter_ipv4.h>
#define __KERNEL__


/*
 * Function prototypes ...
 */

static unsigned int cat_obsf_begin (unsigned int hooknum,
                struct sk_buff *skb,
                const struct net_device *in,
                const struct net_device *out,
                int (*okfn)(struct sk_buff *));

static void hex_dump (char str[], int len)
{

}

/*
 * struct nf_hook_ops instance initialization
 */

static struct nf_hook_ops cat_obsf_ops __read_mostly = {
    .pf = NFPROTO_IPV4,
    .priority = 1,
    .hooknum = NF_IP_POST_ROUTING,
    .hook = cat_obsf_begin,
};

/*
 * Module init and exit functions. 
 * No need to worry about that.
 */

static int __init cat_obsf_init (void)
{
    printk(KERN_ALERT "cat_obsf module started...\n");
    return nf_register_hook(&cat_obsf_ops);
}

static void __exit cat_obsf_exit (void)
{
    nf_unregister_hook(&cat_obsf_ops);
    printk(KERN_ALERT "cat_obsf module stopped...\n");
}

/*
 * Modification of the code begins here. 
 * Here are all the functions and other things.
 */

static unsigned int cat_obsf_begin (unsigned int hooknum,
                struct sk_buff *skb,
                const struct net_device *in,
                const struct net_device *out,
                int (*okfn)(struct sk_buff *))
{ 
    struct iphdr *iph;
    struct udphdr *udph;
    unsigned char *data;
    unsigned char dt[] = "12345";
    unsigned char *tmp;
    unsigned char *ptr;

    int i, j, len;

    if (skb){
        iph = ip_hdr(skb);

        if (iph && iph->protocol && (iph->protocol == IPPROTO_UDP)){
            udph = (struct udphdr *) ((__u32 *)iph + iph->ihl);
            data = (char *)udph + 8;

            if(ntohs(udph->dest) == 6000){
                for (i=0; data[i]; i++);
                len = i;

                //printk(KERN_ALERT "\nData length without skb: %d", len);
                //printk(KERN_ALERT "Data is: %s", data);
                //printk(KERN_ALERT "dt size: %lu", sizeof(dt));
                //printk(KERN_ALERT "skb->len: %d", skb->len);
                tmp = kmalloc(200*sizeof(char), GFP_KERNEL);

                memcpy(tmp, data, len);
                ptr = tmp + len;
                memcpy(ptr, dt, sizeof(dt));

                printk(KERN_ALERT "tmp: %s", tmp);


                printk(KERN_ALERT "skb->tail: %d", skb->tail);
                //skb_put(skb, sizeof(dt));
                printk(KERN_ALERT "skb->end: %d", skb->end);
                printk(KERN_ALERT "skb->tail: %d", skb->tail);
                printk(KERN_ALERT "skb->tail(int): %d", (unsigned int)skb->tail);

                //memset(data, 0, len + sizeof(dt));

                //memcpy(data, tmp, len + sizeof(dt));

                //skb_add_data(skb, tmp, len+sizeof(dt));

                printk(KERN_ALERT "Now data is: %s", data);
                for(i=0; data[i]; i++);
                printk(KERN_ALERT "data length: %d", i);

                kfree(tmp);

            }       
        }   
    }
    return NF_ACCEPT;
}

/*
 * Nothing to be touched hereafter
 */

module_init(cat_obsf_init);
module_exit(cat_obsf_exit);

MODULE_AUTHOR("Rifat");
MODULE_DESCRIPTION("Module for packet mangling");
MODULE_LICENSE("GPL");

我希望在从内核空间发送时,将“消息”变为“message12345”,以便服务器可以得到“message12345”并将其回显,客户端只需读取“message12345”。但是我在使用skb_put()和skb_add_data()函数时遇到了麻烦,不知道哪里出错了。如果有人能帮我检查代码,我将非常感激。提前致谢。我还提供了Makefile以方便使用,这是针对分布式内核而不是构建内核的。
#If KERNELRELEASE is defined, we've been invoked from the
#kernel build system and use its language
ifneq ($(KERNELRELEASE),)
    obj-m := ibss_obsf_cat.o

#Otherwise we were called directly from the command
#line; invoke the kernel build system.
else

    KERNELDIR ?= /lib/modules/$(shell uname -r)/build
    PWD := $(shell pwd)

default:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules

endif

现在我非常确定skb->end - skb->tail太小了,以至于我必须在内核空间创建新的数据包。我已经使用了alloc_skb()、skb_reserve()、skb_header_pointer()和其他有用的skb函数来创建一个新的skb,但是我现在遇到的问题是如何在数据包流动路径中路由新创建的数据包。如何使用ip_route_me_harder()?我查看了xtables-addons软件包以获取建议,但他们使用的函数与Linux内核中的不同。欢迎任何建议。

2个回答

2

大约一年前,针对内核2.6.26,我是这样做的:

// Do we need extra space?
if(len - skb_tailroom(skb) > 0){

  // Expand skb tail until we have enough room for the extra data
  if (pskb_expand_head(skb, 0, extra_data_len - skb_tailroom(skb), GFP_ATOMIC)) {
    // allocation failed. Do whatever you need to do
  }

  // Allocation succeeded

  // Reserve space in skb and return the starting point
  your_favourite_structure* ptr = (your_favourite_structure*) 
                                  skb_push(skb, sizeof(*ptr)); 

  // Now either set each field of your structure or memcpy into it.
  // Remember you can use a char*

}

不要忘记:

  • 重新计算UDP校验和,因为您更改了传输数据中的数据。

  • 更改IP头中的tot_len(总长度)字段,因为您向数据包添加了数据。

  • 重新计算IP头校验和,因为您更改了tot_len字段。

额外提示:这只是一件简单的事情。我在您的代码中看到您将tmp分配为200字节数组,并使用它来存储消息的数据。如果您发送一个更大的数据包,由于内存溢出而导致内核崩溃会让您很难调试。


而且内核空间中的校验和对我来说一开始相当模糊。 - rr_ovi
@Fred,你能否对此发表评论:https://dev59.com/emjWa4cB1Zd3GeqPtLXa - user2087340
@user2087340,你给了我这个问题的链接。你具体是什么意思? - Fred
1
skb_push 应该不是 skb_put 吗?skb_push 是添加到头部空间,而不是尾部空间。 - YOLT
@YOLT 这个答案已经有8年了。在此期间,内核的API完全有可能发生了变化。 - Fred
显示剩余3条评论

1

我已经解决了这个问题。它很简单。我要做的就是发布我的代码,以供将来参考和讨论。

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/netfilter.h>
#include <linux/netdevice.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <linux/mm.h>
#include <linux/err.h>
#include <linux/crypto.h>
#include <linux/init.h>
#include <linux/crypto.h>
#include <linux/scatterlist.h>
#include <net/ip.h>
#include <net/udp.h>
#include <net/route.h>

#undef __KERNEL__
#include <linux/netfilter_ipv4.h>
#define __KERNEL__

#define IP_HDR_LEN 20
#define UDP_HDR_LEN 8
#define TOT_HDR_LEN 28

static unsigned int pkt_mangle_begin(unsigned int hooknum,
                        struct sk_buff *skb,
                        const struct net_device *in,
                        const struct net_device *out,
                        int (*okfn)(struct sk_buff *));

static struct nf_hook_ops pkt_mangle_ops __read_mostly = {
    .pf = NFPROTO_IPV4,
    .priority = 1,
    .hooknum = NF_IP_LOCAL_OUT,
    .hook = pkt_mangle_begin,
};

static int __init pkt_mangle_init(void)
{
    printk(KERN_ALERT "\npkt_mangle module started ...");
    return nf_register_hook(&pkt_mangle_ops);
}

static void __exit pkt_mangle_exit(void)
{
    nf_unregister_hook(&pkt_mangle_ops);
    printk(KERN_ALERT "pkt_mangle module stopped ...");
} 

static unsigned int pkt_mangle_begin (unsigned int hooknum,
                        struct sk_buff *skb,
                        const struct net_device *in,
                        const struct net_device *out,
                        int (*okfn)(struct sk_buff *))
{ 
    struct iphdr *iph;
    struct udphdr *udph;
    unsigned char *data;

    unsigned int data_len;
    unsigned char extra_data[] = "12345";
    unsigned char *temp;
    unsigned int extra_data_len;
    unsigned int tot_data_len;

    unsigned int i;

    __u16 dst_port, src_port;

    if (skb) {
        iph = (struct iphdr *) skb_header_pointer (skb, 0, 0, NULL);

        if (iph && iph->protocol &&(iph->protocol == IPPROTO_UDP)) {
            udph = (struct udphdr *) skb_header_pointer (skb, IP_HDR_LEN, 0, NULL);
            src_port = ntohs (udph->source);
            dst_port = ntohs (udph->dest);

            if (src_port == 6000) {
                printk(KERN_ALERT "UDP packet goes out");
                data = (unsigned char *) skb_header_pointer (skb, IP_HDR_LEN+UDP_HDR_LEN, 0, NULL);
                data_len = skb->len - TOT_HDR_LEN;

                    temp = kmalloc(512 * sizeof(char), GFP_ATOMIC);
                memcpy(temp, data, data_len);

                unsigned char *ptr = temp + data_len - 1;
                extra_data_len = sizeof(extra_data);
                memcpy(ptr, extra_data, extra_data_len);
                tot_data_len = data_len + extra_data_len - 1;

                skb_put(skb, extra_data_len - 1);

                memcpy(data, temp, tot_data_len);

                /* Manipulating necessary header fields */
                iph->tot_len = htons(tot_data_len + TOT_HDR_LEN);
                udph->len = htons(tot_data_len + UDP_HDR_LEN);

                /* Calculation of IP header checksum */
                iph->check = 0;
                ip_send_check (iph);

                /* Calculation of UDP checksum */
                udph->check = 0;
                int offset = skb_transport_offset(skb);
                int len = skb->len - offset;
                udph->check = ~csum_tcpudp_magic((iph->saddr), (iph->daddr), len, IPPROTO_UDP, 0);

                 }
        }
    }
    return NF_ACCEPT;
}


module_init(pkt_mangle_init);
module_exit(pkt_mangle_exit);

MODULE_AUTHOR("Rifat Rahman Ovi: <rifatrahmanovi@gmail.com>");
MODULE_DESCRIPTION("Outward Packet Mangling and Decryption in Kernel Space");
MODULE_LICENSE("GPL");

这里的问题是,我忘记更新长度字段和校验和。现在,如果我在这里正确呈现代码,一切都应该顺利进行。还有一些其他的辅助函数没有包含在这里。


2
我认为这段代码一点也不好。1)你从未释放temp,因此存在内存泄漏的风险。2)skb_put不会扩展缓冲区,因此你在skb内部将字节添加到UDP负载的末尾,而实际上并没有拥有该内存,只是希望它不会引起问题。 - Tom Ritter
是的,你说得对。那是我除了“hello world”之外的第一个模块,所以它会引起问题。因此,为了增加包,我需要分配一个新的缓冲区,并执行一些操作。不能保证使用skb_put()不会在使用方式上造成伤害。顺便说一下,它是为了填充数据包中的一些字节而编写的,最终在一些内核崩溃后才对我清晰明了。但我忘了这个帖子。谢谢你的指点。我很快就会调整代码。你看看这个模块的描述...它只是一个巨大项目的开始,这里只有一半提到了。谢谢。 - rr_ovi
你好!我知道这是一个非常老的帖子,但你能告诉我在发送/接收链中这个钩子被调用的确切位置吗? - Gaurav Suman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接