1.什么是内核模块 模块是可以根据需要加载和卸载到内核中的代码片段,通过动态挂载的方式可以扩展内核的功能,而无需重启系统。 例如,一种类型的模块是设备驱动程序,它允许内核访问连接到系统的硬件。如果没有内核模块,我们必须修改内核代码,将新功能代码片段兼容添加到内核源码中,然后重新编译内核镜像。 除了会让内核越来越大之外,还有一个缺点,即每次我们想要新功能时都需要我们重建和重启内核,并且一旦代码质量过低就会导致内核崩溃的严重事故。
查看系统中的内核模块可以使用lsmod
命令查看
2.编写一个简单的内核模块 下面代码段就是最简单的内核模块,当内核模块挂载成功,通过printk()
打印出Hello World!!!
,当内核模块卸载成功,通过printk()
打印出I am dead.
,键入dmesg
就可以内核日志。
编写内核模块还要关注当前的内核版本,不同版本某些函数的定义不同,会影响编译是否成功
运行环境:
Linux版本: CentOS 8.2
Kernel版本: 4.18.0-348.2.1.el8_5.x86_64
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> static int __init hello_init (void ) { printk(KERN_INFO "Hello World!!!\n" ); return 0 ; }static void __exit hello_cleanup (void ) { printk(KERN_INFO "I am dead.\n" ); } module_init(hello_init); module_exit(hello_cleanup);
module_init()
和module_exit()
分别为内核模块固定的初始化函数和退出清理函数
hello_init
和hello_cleanup
分别为需要被加载的自定义初始化函数和退出函数,内部就是扩展功能代码,能够被加载到内核模块框架
printk()
是内核的日志记录函数,用于记录信息或发出警告,每个printk()
都可以带有一个日志优先级,一共有8个优先级,内核有宏,可以在linux/kernel.h
中查看宏定义。 如果未指定优先级,则将使用默认优先级DEFAULT_MESSAGE_LOGLEVEL
3.如何编译内核模块 编译内核模块需要依赖Makefile 文件,编译成功后会输出一个后缀名为ko
的文件,该文件就是我们编写的内核模块
通过insmod xxx.ko
挂载
通过rmmod xxx
卸载
Makefile文件格式如下:
1 2 3 4 5 6 7 8 # obj-m += hello.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
保存文件后和内核模块c文件放置在同目录下,然后通过make
命令就可以编译出内核模块
挂载和卸载内核模块的效果:
4.解决编译过程的问题 执行make
编译后出现make: *** /lib/modules/4.18.0-348.2.1.el8_5.x86_64/build/: No such file or directory. Stop.
该问题是系统没有安装内核开发包,可以看下/usr/src/kernels/
,如果该目录下是空的,则可以说明没有安装内核开发包
安装内核开发包的命令:
1 2 3 4 # yum list |grep kernel # yum install kernel-devel.x86_64
安装完成后,在/lib/modules/4.18.0-348.2.1.el8_5.x86_64
目录下,通过ls -l
查看build
文件的链接, 如果没有指向build -> ../../../../usr/src/kernels/4.18.0-348.2.1.el8_5.x86_64/
的链接则需要重新创建一个软链接
操作命令如下:ln -s ../../../../usr/src/kernels/4.18.0-348.2.1.el8_5.x86_64/ build
创建软链接完成后,重新编译就可以
5.Netfilter Hook Netfiler框架是Linux防火墙的内核实现,常见的iptables和firewalld本质都是调用Netfilter来实现流量包的处理,另外还提供了hook机制来实现功能扩展,在内核里,每个网络命名空间(network namespace)都给ipv4
、ipv6
、arp
、bridge
、decnet
等维持一个netfilter钩子列表(struct nf_hook_entries *hooks[5])
netfilter提供5个hook点:
NF_INET_PRE_ROUTING
:刚刚进入网络层的数据包通过此点(刚刚进行完版本号,校验和等检测), 源地址转换在此点进行;
NF_INET_LOCAL_IN
:经路由查找后,送往本机的通过此检查点,INPUT包过滤在此点进行;
NF_INET_FORWARD
:转发的包通过此检测点,FORWARD包过滤在此点进行;
NF_INET_LOCAL_OUT
:所有马上要通过网卡出去的包通过此检测点,内置的目的地址转换功能(包括地址伪装)在此点进行;
NF_INET_POST_ROUTING
:本机进程发出的包通过此检测点,OUTPUT包过滤在此点进行。
通过这些hook点,可以让我们注册回调函数,扩展流量包处理功能,实现类似NAT、包过滤、追踪记录等,包的走向路径图可以参考下图:
6.编写Netfiler Hook函数
目标:基于netfilter hook在prerouting
和postrouting
打印出当前的目的ip和目的port
思路:
编写hook函数
将hook函数注册到hook options结构体
调用nf_register_net_hook
和nf_unregister_net_hook
完成hook函数的加载和卸载
Kernel版本为4.18的nf_hook函数定义为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 typedef unsigned int nf_hookfn (void *priv, struct sk_buff *skb, const struct nf_hook_state *state) ;struct nf_hook_ops { nf_hookfn *hook; struct net_device *dev ; void *priv; u_int8_t pf; unsigned int hooknum; int priority; };struct nf_hook_state { unsigned int hook; u_int8_t pf; struct net_device *in ; struct net_device *out ; struct sock *sk ; struct net *net ; int (*okfn)(struct net *, struct sock *, struct sk_buff *); };
关注nf_hook_ops
结构体的参数
nf_hookfn: 自定义的nfhook函数
pf: 协议簇,主要种类列表
PF_INET:ipv4协议
PF_INET6: ipv6协议
PF_ARP: arp协议
PF_BRIDGE: 二层网桥协议
hooknum: 指定加载自定义函数的hook点位置
priority: 执行优先级1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 enum nf_ip_hook_priorities { NF_IP_PRI_FIRST = INT_MIN, NF_IP_PRI_RAW_BEFORE_DEFRAG = -450 , NF_IP_PRI_CONNTRACK_DEFRAG = -400 , NF_IP_PRI_RAW = -300 , NF_IP_PRI_SELINUX_FIRST = -225 , NF_IP_PRI_CONNTRACK = -200 , NF_IP_PRI_MANGLE = -150 , NF_IP_PRI_NAT_DST = -100 , NF_IP_PRI_FILTER = 0 , NF_IP_PRI_SECURITY = 50 , NF_IP_PRI_NAT_SRC = 100 , NF_IP_PRI_SELINUX_LAST = 225 , NF_IP_PRI_CONNTRACK_HELPER = 300 , NF_IP_PRI_CONNTRACK_CONFIRM = INT_MAX, NF_IP_PRI_LAST = INT_MAX, };
注意:上面列举的hook点和hook优先级都是指定ip层(三层),如果涉及到bridge还有二层的hook点和hook优先级
涉及bridge的hook点和优先级
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 #define NF_BR_PRE_ROUTING 0 #define NF_BR_LOCAL_IN 1 #define NF_BR_FORWARD 2 #define NF_BR_LOCAL_OUT 3 #define NF_BR_POST_ROUTING 4 #define NF_BR_BROUTING 5 #define NF_BR_NUMHOOKS 6 enum nf_br_hook_priorities { NF_BR_PRI_FIRST = INT_MIN, NF_BR_PRI_NAT_DST_BRIDGED = -300 , NF_BR_PRI_FILTER_BRIDGED = -200 , NF_BR_PRI_BRNF = 0 , NF_BR_PRI_NAT_DST_OTHER = 100 , NF_BR_PRI_FILTER_OTHER = 200 , NF_BR_PRI_NAT_SRC = 300 , NF_BR_PRI_LAST = INT_MAX, };
6.1 编写hook函数 prerouting hook 函数
1 2 3 4 5 6 7 8 9 10 unsigned int pre_hookfn (void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { struct iphdr *iph = ip_hdr(skb); struct tcphdr *tcph = tcp_hdr(skb); printk(KERN_INFO "prerouting dest ip:port" ,ip->daddr,ntohs(tcph->dest)); return NF_ACCEPT; }
**postrouting hook 函数 **
1 2 3 4 5 6 7 8 9 10 unsigned int post_hookfn (void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { struct iphdr *iph = ip_hdr(skb); struct tcphdr *tcph = tcp_hdr(skb); printk(KERN_INFO "postrouting dest ip:port" ,ip->daddr,ntohs(tcph->dest)); return NF_ACCEPT; }
6.2 注册option 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 static struct nf_hook_ops in_nfho ; static struct nf_hook_ops out_nfho ; static int nf_init_fn (void ) { in_nfho.hook = pre_hookfn; in_nfho.pf = PF_INET; in_nfho.hooknum = NF_INET_PRE_ROUTING; in_nfho.priority = NF_IP_PRI_FIRST; out_nfho.hook = post_hookfn; out_nfho.pf = PF_INET; out_nfho.hooknum = NF_INET_POST_ROUTING; out_nfho.priority = NF_IP_PRI_FIRST; nf_register_net_hook(&init_net, &in_nfho); nf_register_net_hook(&init_net, &out_nfho); return 0 ; }
6.3 完整代码程序 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 #include <linux/module.h> #include <linux/kernel.h> #include <linux/ip.h> #include <linux/tcp.h> #include <linux/netfilter_ipv4.h> static struct nf_hook_ops in_nfho ; static struct nf_hook_ops out_nfho ; unsigned int pre_hookfn (void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { struct iphdr *iph = ip_hdr(skb); struct tcphdr *tcph = tcp_hdr(skb); printk(KERN_INFO "prerouting dest %pI4:%d\n" ,&iph->daddr,ntohs(tcph->dest)); return NF_ACCEPT; }unsigned int post_hookfn (void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { struct iphdr *iph = ip_hdr(skb); struct tcphdr *tcph = tcp_hdr(skb); printk("postrouting dest %pI4:%d\n" ,&iph->daddr,ntohs(tcph->dest)); return NF_ACCEPT; }static int nf_init_fn (void ) { in_nfho.hook = pre_hookfn; in_nfho.pf = PF_INET; in_nfho.hooknum = NF_INET_PRE_ROUTING; in_nfho.priority = NF_IP_PRI_FIRST; out_nfho.hook = post_hookfn; out_nfho.pf = PF_INET; out_nfho.hooknum = NF_INET_POST_ROUTING; out_nfho.priority = NF_IP_PRI_FIRST; nf_register_net_hook(&init_net, &in_nfho); nf_register_net_hook(&init_net, &out_nfho); return 0 ; }static int nfhook_init (void ) { printk("[+] Register tcp kernel module!\n" ); return nf_init_fn(); }static void nfhook_exit (void ) { nf_unregister_net_hook(&init_net, &in_nfho); nf_unregister_net_hook(&init_net, &out_nfho); printk(KERN_INFO "[+] Unregister tcp kernel module!\n" ); } module_init(nfhook_init); module_exit(nfhook_exit);
挂载效果: