Bootstrap

Linux之NetLink学习笔记

目录

一.NetLink介绍

二.NetLink用法:

1.netlink调用过程:

2.驱动文件ko文件的初始化与停止使用方式:

3.NetLink套接字通信

3.2 以上代码中的结构体详解

1. 结构体nlmsghdr结构

2.struct msghdr结构

3.struct iovec结构

4.Netlink交互驱动hook功能使用案例

4.1利用hook内核kill调用实现驱动层进程保护


        本文主要介绍内核netlink机制使用总结以及通过netlink实现进程防护案例总结

一.NetLink介绍

       NetLink是一种基于应用层跟内核态的通信机制,其特点是一种异步全双工的通信方式,支持内核态主动发起通信的机制。该机制提供了一组特殊的API接口,用户态则通过socket API调用。内核发送的数据再应用层接收后会保存在接收进程socket的缓存中,再由接收进程处理。

二.NetLink用法:

1.netlink调用过程:

2.驱动文件ko文件的初始化与停止使用方式:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/unistd.h>
#include <linux/time.h>
#include <asm/uaccess.h>
#include <linux/sched.h>
#include <linux/kallsyms.h>

/* 模块入口函数 */
static int __init my_ko_init(void) {
    /**
        doing
    */
    printk("I am init self ko !!!");
    return 0;
}


/* 模块退出函数 */
static void __exit my_ko_exit(void) {
    // 注销netlink协议
    printk("I am exit self ko !!!");
    return;
}

module_init(my_ko_init)
module_exit(my_ko__exit)
MODULE_AUTHOR("my_self_ko");
MODULE_DESCRIPTION("self ko study");
MODULE_LICENSE("GPL");

注:自定义的ko文件使用,按照如上图的模版标准来进行实现对应的功能,该模版是初始化跟退出还原方法,通过module_init,module_exit,两个函数进行插入加载跟卸载还原内核态信息(避免出现内核崩溃).

makefile

obj-m += my_self_ko.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

Makefile:

        obj-m:表示生成的.o文件,编译完成后会生成一个对应名称的ko文件。(不会将编译生成的ko文件导入到内核),如果有多个依赖,使用*-objs方式,写法如下:

obj-m := self_kkk.o

self_kkk-objs := aaa.o bbb.o
obj-y: 表示会将生成的ko文件直接拷贝到系统内核中。 (示例中并未展示)

        -C :指定的是系统内核模块代码的Makefile,

        M:表示返回到当前路径并执行当前Makefile,

        modules:指明编译的目标是生成内核模块的.ko文件

3.NetLink套接字通信

用于创建一个内核态的netlink的socket服务。该函数对应的参数在不同的版本之间是不同的设定:

头文件:linux/netlink.h

//系统内核定义函数如下:
 netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg)
//查询几个不同内核版本的函数定义
a. LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24)

        netlink_kernel_create(NETLINK_SELF_MODULE,0,netlink_kernel_rcv,THIS_MODULE);

b. KERNEL_VERSION(2,6,24)<  LINUX_VERSION_CODE < KERNEL_VERSION(3,6,0)

        netlink_kernel_create(&init_net, NETLINK_SELF_MODULE,0, netlink_kernel_rcv, NULL, THIS_MODULE);

c. LINUX_VERSION_CODE >= KERNEL_VERSION(3,6,0)

        struct netlink_kernel_cfg cfg = {
                .input = netlink_kernel_rcv,
        };

        netlink_kernel_create(&init_net, NETLINK_SELF_MODULE,0, &cfg);

 参数2:自定义的通信消息类型,根据实际定义,但是避免跟系统冲以及不要超过系统范围。

 参数3:默认设置0

 参数4:设置系统消息回调函数,不同版本该接口有差异性,如上代码。

具体的方式通过客户端主动链接,双向通信。如下:

内核ko驱动代码:(内核版本取自centos7.x的3.10.x版本)

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/pid.h>
#include <linux/spinlock.h> 
#include <linux/stddef.h>
#include <linux/version.h>
#include <net/ip.h>

#define NETLINK_TEST 27
#define SELF_MSG_TYPE NLMSG_MIN_TYPE + 1 // 自定义类型
static struct sock *nlsock = NULL;

/* netlink消息处理函数 */
static int netlink_kernel_receive_message(struct sk_buff *skb, struct nlmsghdr *nlh)
{
    struct sk_buff *skb_rsp;
    struct nlmsghdr *nlh_rsp;
    void *value = NULL;
    int len = nlh->nlmsg_len;
    int pid = nlh->nlmsg_pid;
    value = NLMSG_DATA(nlh);
    printk(KERN_INFO"value = %s - %d",(char*)value,nlh->nlmsg_type);

    // rsp
    char str[] = "hello apply";
    len = strlen(str);
    
    int datalen = NLMSG_SPACE(len);
    skb_rsp = alloc_skb(datalen, GFP_KERNEL);

    nlh_rsp = nlmsg_put(skb_rsp, 0, 0, 0, len,0);
    nlh_rsp->nlmsg_pid = 0;
    nlh_rsp->nlmsg_type = SELF_MSG_TYPE;
    nlh_rsp->nlmsg_seq = 0;

   
    memcpy(NLMSG_DATA(nlh_rsp),str, len);
    netlink_unicast(nlsock, skb_rsp, pid, MSG_DONTWAIT);
    return 0;
}

static void netlink_kernel_receive(struct sk_buff *skb)
{
    int res;
    res = netlink_rcv_skb(skb, &netlink_kernel_receive_message);
    return;
}

/* 注册netlink协议 */
static int __init ker_init(void)
{
    struct netlink_kernel_cfg cfg = {
        .input = netlink_kernel_receive,
    };

    nlsock = netlink_kernel_create(&init_net, NETLINK_TEST, &cfg);

    if (!nlsock)
    {
         printk(KERN_INFO"netlink module init fail \n");
        return -1;
    }

    return 0;
}

/* 模块退出函数,注销netlink协议 */
static void __exit ker_exit(void)
{
    if (nlsock)
    {
        netlink_kernel_release(nlsock);
        nlsock = NULL;
    }
    return;
}

module_init(ker_init)
module_exit(ker_exit)
MODULE_DESCRIPTION("linux module for netlink' kernel");
MODULE_LICENSE("GPL");

客户端:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <linux/netlink.h>

#define NL_MSG_LEN_MAX  4096
#define NETLINK_TEST 27

#define SELF_MSG_TYPE NLMSG_MIN_TYPE + 1 // 自定义类型

static int nlfd_sock = -1;
static struct nlmsghdr *nlf_hand = NULL;

int read_msg()
{
    struct iovec iov;
    struct msghdr msg;
    int ret;
    memset(&msg, 0, sizeof(struct msghdr));
    memset(nlf_hand, 0, NLMSG_SPACE(NL_MSG_LEN_MAX));
    nlf_hand->nlmsg_len = NLMSG_LENGTH(NL_MSG_LEN_MAX);

    iov.iov_base = (void*)nlf_hand;
    iov.iov_len = nlf_hand->nlmsg_len;
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;

    // get data from kernel
    ret = recvmsg(nlfd_sock, &msg, 0);
    if (ret < 0) {
        printf("read_msg: recvmsg error\n");
        return -1;
    }
    printf("read_msg: recvmsg %s\n",(char*)NLMSG_DATA(nlf_hand));
    return 0;
}
int send_msg(void* data, int len, int msg_type,int pid)
{
    struct iovec iov;
    struct msghdr msg;
    struct sockaddr_nl dst;
    int ret = 0;
    memset(&msg, 0, sizeof(struct msghdr));
    memset(&dst, 0, sizeof(struct sockaddr_nl));

    // 目标地址
    dst.nl_family = AF_NETLINK;
    dst.nl_pid = 0;
    dst.nl_groups = 0; // 组播

    // data len
    nlf_hand->nlmsg_len = NLMSG_SPACE(len);
    // sender's pid
    nlf_hand->nlmsg_pid = (pid != -1) ? pid : getpid(); 
    // msg type
    nlf_hand->nlmsg_type = msg_type;
    // msg flags
    nlf_hand->nlmsg_flags = NLM_F_REQUEST;

    // make data
    iov.iov_base = (void *)nlf_hand; //
    iov.iov_len = nlf_hand->nlmsg_len;
    memcpy((char *)NLMSG_DATA(nlf_hand);, data, len);


    msg.msg_name = (void *)&dst; // 目标地址
    msg.msg_namelen = sizeof(struct sockaddr_nl);
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1; //iov len

    ret= sendmsg(nlfd_sock, &msg, 0);
    return ret;
}

int create_sock()
{
    struct iovec iov;
    struct sockaddr_nl src;

    nlfd_sock = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_TEST);
    if (nlfd_sock == -1) {
        return -1;
    }
    memset(&src, 0, sizeof(struct sockaddr_nl));
    src.nl_family = AF_NETLINK;
    src.nl_pid = getpid();
    src.nl_groups = 0;
    bind(nlfd_sock, (struct sockaddr*)&src, sizeof(struct sockaddr_nl));
    nlf_hand = (struct nlmsghdr *)malloc(NLMSG_SPACE(NL_MSG_LEN_MAX));
    if (!nlf_hand) {
        return -1;
    }
    return 0;
}

int main()
{
    create_sock();
    char str[10] = "Hello kernel";
    printf("before ===%s\n",str);
    send_msg(str,strlen(str),SELF_MSG_TYPE,-1);
    printf("after ===%s\n",str);
    read_msg();
    return 0;
}

上述代码中自定义通信类型,不可以跟系统重复,否则会导致写入ko文件失败.如下:

注:驱动文件ko插入内核NetLink启动失败导致内核ko驱动不可用:

       如调用失败,最可能原因,第二个参数NETLINK_TEST可能已经被系统占用。需要重新更换该类型参数。可以通过: cat /proc/net/netlink 查看当前使用的内核类型,自我使用的消息类型否跟内核冲突,如果冲突,请更换一个未使用的。另外应用层也需同步修改NETLINK_TEST(所创建的sockt类型内外一致)如下所示:

cat /proc/net/netlink 如下截取的部分数据(注意看第二列):

创建内核socket时的方法:

nlsock = netlink_kernel_create(&init_net, NETLINK_TEST, &cfg);

上述3.1描述具体的netlink方式通信(内核&应用层),另外需要注意,不同版本的内核,上述方法存在偏差,实际使用请具体参考内核对应函数的定义。

3.2 以上代码中的结构体详解

1. 结构体nlmsghdr结构

      Linux 内核中 Netlink 消息头的结构体,用于在 Netlink 协议中封装消息的信息。Netlink 是一种用于在内核空间和用户空间之间进行通信的协议,常用于网络子系统和其他内核模块之间的通信。

struct nlmsghdr {

    __u32 nlmsg_len;   // 长度(包括头部)

    __u16 nlmsg_type;  // 类型

    __u16 nlmsg_flags; // 标志

    __u32 nlmsg_seq;   // 序列号

    __u32 nlmsg_pid;   // 发送方的进程 ID

};

nlmsg_len: 指示整个消息的长度,包括消息头和数据部分的长度。

nlmsg_type: 指示消息的类型,表示消息的用途或类别。不同的消息类型用于不同的目的,例如,RTM_NEWLINK 用于通知网络接口状态的改变。

nlmsg_flags: 包含一些标志位,用于指示消息的一些属性或特性。

nlmsg_seq: 序列号,用于在通信的过程中标识消息的顺序。

nlmsg_pid: 发送方的进程 ID,用于标识消息的发送者。

使用 Netlink 协议进行通信时,通常通过创建一个 struct nlmsghdr 结构体,设置相应的字段,并在消息中包含数据部分。这个结构体会与消息的数据部分一起传递给系统调用函数,如 sendmsg 或 recvmsg。

例如:

#include <linux/netlink.h>

struct nlmsghdr *nlh;
int msg_len;

// 创建一个消息头
msg_len = NLMSG_LENGTH(payload_size);  // payload_size 是消息数据部分的长度
nlh = (struct nlmsghdr *)malloc(msg_len);
nlh->nlmsg_len = msg_len;
nlh->nlmsg_type = MY_NETLINK_MSG_TYPE;
nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
nlh->nlmsg_seq = 123;  // 适当设置序列号
nlh->nlmsg_pid = getpid();  // 获取当前进程 ID

// 将 nlh 与消息数据一起发送
2.struct msghdr结构

       该结构是Linux 系统中用于在系统调用中传递消息头信息的结构体。它通常用于一些套接字相关的系统调用,比如 sendmsg 和 recvmsg。

struct msghdr {

    void         *msg_name;       // 指向目标地址的指针

    socklen_t     msg_namelen;    // 目标地址的长度

    struct iovec *msg_iov;        // 指向数据缓冲区的数组

    size_t        msg_iovlen;     // 数据缓冲区数组的元素数量

    void         *msg_control;    // 指向辅助数据的指针

    socklen_t     msg_controllen; // 辅助数据的长度

    int           msg_flags;      // 操作标志

};

msg_name: 指向目标地址的指针,用于存储发送或接收数据的目标地址。可以为 NULL,表示不关心地址信息。

msg_namelen: 目标地址的长度。

msg_iov: 指向一个 iovec 结构体数组的指针,每个 iovec 结构体描述一个数据缓冲区。

msg_iovlen: 数据缓冲区数组的元素数量。

msg_control: 指向辅助数据(例如,控制信息)的指针。

msg_controllen: 辅助数据的长度。

msg_flags: 操作标志,用于指定一些额外的操作或属性。

通常,当你调用 sendmsg 发送消息时,会填充一个 struct msghdr 结构体,其中包含了要发送的数据、目标地址等信息。同样,当你调用 recvmsg 接收消息时,接收到的数据和其他信息也会填充到一个 struct msghdr 结构体中。例如:

#include <sys/socket.h>

struct msghdr msg;
struct iovec iov;

char buffer[1024];  // 假设有一个用于存储数据的缓冲区

iov.iov_base = buffer;
iov.iov_len = sizeof(buffer);

msg.msg_name = NULL;          // 不关心目标地址
msg.msg_namelen = 0;
msg.msg_iov = &iov;            // 数据缓冲区
msg.msg_iovlen = 1;            // 数据缓冲区的数量
msg.msg_control = NULL;        // 没有辅助数据
msg.msg_controllen = 0;
msg.msg_flags = 0;             // 操作标志

// 调用 sendmsg 或 recvmsg 函数,传递 msg 结构体
3.struct iovec结构

    该结构是在 Linux 系统上用于进行分散/聚集 I/O 操作(scatter-gather I/O)的结构体。这个结构体定义在头文件 <sys/uio.h> 中。它通常用于函数,如 readv、writev、preadv 和 pwritev,这些函数支持在一次系统调用中对多个非连续内存区域进行 I/O 操作。结构如下:

struct iovec {

    void  *iov_base; /* Starting address of buffer */

    size_t iov_len;  /* Number of bytes to transfer */

};

 例如:

#include <sys/uio.h>
#include <unistd.h>
#include <fcntl.h>

int main() {
    struct iovec iov[3];
    ssize_t nwritten;

    int fd = open("example.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    // 设置三个缓冲区
    char buf1[] = "This is ";
    char buf2[] = "a simple ";
    char buf3[] = "example.";

    // 填充 struct iovec 结构体数组
    iov[0].iov_base = buf1;
    iov[0].iov_len = sizeof(buf1) - 1;

    iov[1].iov_base = buf2;
    iov[1].iov_len = sizeof(buf2) - 1;

    iov[2].iov_base = buf3;
    iov[2].iov_len = sizeof(buf3) - 1;

    // 使用 writev 将多个缓冲区的内容一次性写入文件
    nwritten = writev(fd, iov, 3);
    if (nwritten == -1) {
        perror("writev");
        return 1;
    }

    close(fd);

    return 0;
}

如上write 函数被用于将三个缓冲区的内容一次性写入文件。这样可以避免多次调用 write 函数,提高效率。

以上就是有关于netlink相关的知识总结,如后续再有总结,请见后续文章。该处小插一曲,顺带记录点有关系统hook学习案例,且附带有通过hook实现的程序保护防杀功能案例,请见下文4标题。

4.Netlink交互驱动hook功能使用案例

4.1利用hook内核kill调用实现驱动层进程保护

      首先编写驱动文件ko,内部实现对应的kill函数,自定义kill函数,通过应用层跟驱动netlink通信,将需要保护的进程id,填充驱动层面,如此以来就能避免通过kill -9杀死对应的进程。从而实现一种进程保护机制。同时可以通过编写对应的工具通过netlink方式通信,灵活的更改某些进程的保护状态。(前提条件是一定要设置自定义的hook函数,需要将kill函数提前设置hook,对应的内核类型_NR_kill)

注: 通信的条件,使用系统内定义的宏定义类型,or自定义的信息类型。自定义使用的netlink通信类型,查看系统已经使用的:cat /proc/net/netlink,再行决定如何定义对应的类型。自定义过程定义通信类型,需要自己编写对应的驱动。举例见如下:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/unistd.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <linux/sched.h>
#include <linux/version.h>
#include <linux/kallsyms.h>
// 可根据实际内核版本包含相关的头文件
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 17, 0)
#else
#endif
// 进程信息
struct process_info {
    struct list_head list;
    int pid;
};
struct process_mgr {
    struct list_head head;
    int nums;
};

static struct process_mgr pro_mgr_handle;
unsigned long * sys_table_ptr;
//存储原始的kill系统调用指针
asmlinkage long (*original_kill)(pid_t pid, int sig);
// 自定义的 hook 函数
asmlinkage long hooked_kill(int pid, int sig) 
{
    struct process_info *entry = NULL;
    list_for_each_entry(entry,&pro_mgr_handle.head,list){
        if(entry->pid == pid && sig == SIGKILL){
            printk(KERN_INFO "kill from %s\n", current->comm);
            return -EPERM;
        }
    }
    // 调用原始系统调用
    return original_kill(pid, sig);
}


void init_process_mgr(void)
{
    INIT_LIST_HEAD(&pro_mgr_handle.head);
    pro_mgr_handle.nums = 0;
}

void add_process(int pid)
{
    struct process_info *entry = NULL;
    struct process_info *tmp = NULL;
    if(pid == 0){
        return; 
    }
    list_for_each_entry(entry,&pro_mgr_handle.head,list){
        if(entry->pid == pid){
            return;
        }
    }
    tmp = (struct process_info*)kzalloc(sizeof(struct process_info),GFP_KERNEL);
    if(tmp){
        tmp->pid = pid;
        list_add_tail(&tmp->list,&pro_mgr_handle.head);
        pro_mgr_handle.nums++;
    }
}

void del_process(int pid,bool isAll)
{
    struct process_info *entry = NULL;
    struct process_info *next = NULL;
    if(pid == 0){
        return;
    }

    if(list_empty(&pro_mgr_handle.head) || pro_mgr_handle.nums == 0){
        return;
    }

    if(isAll){
        list_for_each_entry_safe(entry,next,&pro_mgr_handle.head,list){
            list_del(&entry->list);
            kfree(entry);   
        }
        pro_mgr_handle.nums = 0; 
    }else{
        list_for_each_entry_safe(entry,next,&pro_mgr_handle.head,list){
            if(entry->pid == pid){
                list_del(&entry->list);
                kfree(entry);
                pro_mgr_handle.nums--;
                break;
            }
        }
    }
}
static int __init syscall_hook_init(void) 
{
    init_process_mgr();
    add_process(38341); //该处直接调用测试,具体更换进程id即可。另外可结合netlink动态交互
    // 获取系统调用表地址
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 7, 0)
    sys_table_ptr = (unsigned long*)kallsyms_lookup_name("sys_call_table");
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 17, 0)
    sys_table_ptr = (unsigned long*)kallsyms_lookup_name("sys_call_table").val;
#else
    sys_table_ptr = (unsigned long*)kallsyms_lookup_name("sys_call_table");
#endif
    // 原始函数指针
    original_kill = (long(*)(int,int))sys_table_ptr[__NR_kill];
    // 修改页面保护属性,可写
    write_cr0(read_cr0() & (~0x10000));
    //  hook kill 函数
    sys_table_ptr[__NR_kill] = (unsigned long)hooked_kill;
    // 恢复页面保护属性
    write_cr0(read_cr0() | 0x10000);
    printk(KERN_INFO "Syscall hook installed for _NR_kill\n");
    return 0;
}

static void __exit syscall_hook_exit(void) {
    // 移除 hook,恢复原始系统调用
    // 修改页面保护属性,可写
    write_cr0(read_cr0() & (~0x10000));
    // 恢复原始系统调用
    sys_table_ptr[__NR_kill] = (unsigned long)original_kill;
    // 恢复页面保护属性
    write_cr0(read_cr0() | 0x10000);
    printk(KERN_INFO "restore _NR_kill\n");
}

module_init(syscall_hook_init);
module_exit(syscall_hook_exit);
MODULE_LICENSE("GPL");

 温馨提醒:文中的代码请勿用于线上环境,仅进用于自行学习使用。因为hook不同系统操作不当,容易引起系统崩溃。

续4.1中钩子系统的kill联合netlink实现动态的功能防护保护功能案例:

驱动代码:

#include <linux/unistd.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <linux/sched.h>
#include <linux/kallsyms.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/pid.h>
#include <linux/spinlock.h> 
#include <linux/stddef.h>
#include <linux/version.h>
#include <linux/fs.h>
#include <linux/fs_struct.h>
#include <net/ip.h>

// 可根据实际内核版本包含相关的头文件
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 17, 0)
#else
#endif
// netlink消息类型
#define NETLINK_TEST 27 

enum{
    SELF_MSG_TYPE_PROCESS_OPER  = NLMSG_MIN_TYPE + 1, // 自定义类型
    SELF_MSG_TYPE_MAX,
};

struct data_msg{
    int pid;
    int type;
    bool isAll;
};

static struct sock *nlsock = NULL;
// decleare fun
void add_process(int pid);
void del_process(int pid,bool isAll);


// 进程信息
struct process_info {
    struct list_head list;
    int pid;
};
struct process_mgr {
    struct list_head head;
    int nums;
};

static struct process_mgr pro_mgr_handle;
unsigned long * sys_table_ptr;

//存储原始的kill系统调用指针
asmlinkage long (*original_kill)(pid_t pid, int sig);
// 自定义的 hook 函数
asmlinkage long hooked_kill(int pid, int sig) 
{
    struct process_info *entry = NULL;
    list_for_each_entry(entry,&pro_mgr_handle.head,list){
        if(entry->pid == pid && sig == SIGKILL){
            printk(KERN_INFO "kill %s - %d - %s\n", current->comm,current->pid,current->fs->pwd.dentry->d_name.name); // 驱动文件名称
            return -EPERM;
        }
    }
    // 调用原始系统调用
    return original_kill(pid, sig);
}

/* netlink消息处理函数 */
static int netlink_kernel_receive_message(struct sk_buff *skb, struct nlmsghdr *nlh)
{
    struct sk_buff *skb_rsp;
    struct nlmsghdr *nlh_rsp;
    struct data_msg *dm = NULL;
    char str[128] = {0}; 
    int datalen = 0;
    int len = nlh->nlmsg_len;
    int pid = nlh->nlmsg_pid;
    dm = (struct data_msg*)NLMSG_DATA(nlh);
    printk(KERN_INFO"dm->pid = %d, dm->type = %d ",dm->pid,dm->type);

    if(dm->type == 1){
        add_process(dm->pid);
    }else if(dm->type == 2 || dm->type == 3){
        del_process(dm->pid,dm->isAll);
    }
    
    // rsp
    sprintf(str,"%d process %s  kernel list",dm->pid,(dm->type == 1) ? "add into" : "del from");
    len = strlen(str);
    
    datalen = NLMSG_SPACE(len);
    skb_rsp = alloc_skb(datalen, GFP_KERNEL);

    nlh_rsp = nlmsg_put(skb_rsp, 0, 0, 0, len,0);
    nlh_rsp->nlmsg_pid = 0;
    nlh_rsp->nlmsg_type = SELF_MSG_TYPE_PROCESS_OPER;
    nlh_rsp->nlmsg_seq = 0;

   
    memcpy(NLMSG_DATA(nlh_rsp),str, len);
    netlink_unicast(nlsock, skb_rsp, pid, MSG_DONTWAIT);
    return 0;
}

static void netlink_kernel_receive(struct sk_buff *skb)
{
    int res;
    res = netlink_rcv_skb(skb, &netlink_kernel_receive_message);
    return;
}



void init_process_mgr(void)
{
    INIT_LIST_HEAD(&pro_mgr_handle.head);
    pro_mgr_handle.nums = 0;
}

void add_process(int pid)
{
    struct process_info *entry = NULL;
    struct process_info *tmp = NULL;
    if(pid == 0){
        return; 
    }
    list_for_each_entry(entry,&pro_mgr_handle.head,list){
        if(entry->pid == pid){
            return;
        }
    }
    tmp = (struct process_info*)kzalloc(sizeof(struct process_info),GFP_KERNEL);
    if(tmp){
        tmp->pid = pid;
        list_add_tail(&tmp->list,&pro_mgr_handle.head);
        pro_mgr_handle.nums++;
    }
}

void del_process(int pid,bool isAll)
{
    struct process_info *entry = NULL;
    struct process_info *next = NULL;

    if(list_empty(&pro_mgr_handle.head) || pro_mgr_handle.nums == 0){
        return;
    }

    if(isAll){
        list_for_each_entry_safe(entry,next,&pro_mgr_handle.head,list){
            list_del(&entry->list);
            kfree(entry);   
        }
        pro_mgr_handle.nums = 0; 
    }else{
        list_for_each_entry_safe(entry,next,&pro_mgr_handle.head,list){
            if(entry->pid == pid){
                list_del(&entry->list);
                kfree(entry);
                pro_mgr_handle.nums--;
                break;
            }
        }
    }
}
static int __init syscall_hook_init(void) 
{
     struct netlink_kernel_cfg cfg = {
        .input = netlink_kernel_receive,
    };

    nlsock = netlink_kernel_create(&init_net, NETLINK_TEST, &cfg);

    // init process mgr
    init_process_mgr();

    // 获取系统调用表地址
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 7, 0)
    sys_table_ptr = (unsigned long*)kallsyms_lookup_name("sys_call_table");
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 17, 0)
    sys_table_ptr = (unsigned long*)kallsyms_lookup_name("sys_call_table").val;
#else
    sys_table_ptr = (unsigned long*)kallsyms_lookup_name("sys_call_table");
#endif
    // 原始函数指针
    original_kill = (long(*)(int,int))sys_table_ptr[__NR_kill];
    // 修改页面保护属性,可写
    write_cr0(read_cr0() & (~0x10000));
    //  hook kill 函数
    sys_table_ptr[__NR_kill] = (unsigned long)hooked_kill;
    // 恢复页面保护属性
    write_cr0(read_cr0() | 0x10000);


    if (!nlsock)
    {
        printk(KERN_INFO"netlink module init fail \n");
        return -1;
    }
    printk(KERN_INFO "Syscall hook installed for _NR_kill\n");
    return 0;
}

static void __exit syscall_hook_exit(void) {
    // 移除 hook,恢复原始系统调用
    // 修改页面保护属性,可写
    write_cr0(read_cr0() & (~0x10000));
    // 恢复原始系统调用
    sys_table_ptr[__NR_kill] = (unsigned long)original_kill;
    // 恢复页面保护属性
    write_cr0(read_cr0() | 0x10000);
    
    if(nlsock){
        netlink_kernel_release(nlsock);
        nlsock = NULL;
    }
    
    printk(KERN_INFO "restore _NR_kill\n");
}

module_init(syscall_hook_init);
module_exit(syscall_hook_exit);
MODULE_LICENSE("GPL");

客户端:

#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <linux/netlink.h>

#define NL_MSG_LEN_MAX  4096
#define NETLINK_TEST 27

// msg type
enum{
    SELF_MSG_TYPE_PROCESS_OPER  = NLMSG_MIN_TYPE + 1, // 自定义类型
    SELF_MSG_TYPE_MAX,
};

struct data_msg{
    int data;
    int type;
    bool isAll;
};

static int nlfd_sock = -1;
static struct nlmsghdr *nlf_hand = NULL;

int read_msg()
{
    struct iovec iov;
    struct msghdr msg;
    int ret;

    memset(&msg, 0, sizeof(struct msghdr));
    memset(nlf_hand, 0, NLMSG_SPACE(NL_MSG_LEN_MAX));

    nlf_hand->nlmsg_len = NLMSG_LENGTH(NL_MSG_LEN_MAX);

    iov.iov_base = (void*)nlf_hand;
    iov.iov_len = nlf_hand->nlmsg_len;
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;

    // get data from kernel
    ret = recvmsg(nlfd_sock, &msg, 0);
    if (ret < 0) {
        printf("read_msg: recvmsg error\n");
        return -1;
    }
    printf("read_msg: %s\n",(char*)NLMSG_DATA(nlf_hand));
    return 0;
}
int send_msg(const void* data, int len, int type,int pid)
{
    struct iovec iov;
    struct msghdr msg;
    struct sockaddr_nl dst;
    char *ptr = NULL;
    memset(&msg, 0, sizeof(struct msghdr));
    memset(&dst, 0, sizeof(struct sockaddr_nl));

    // 目标地址
    dst.nl_family = AF_NETLINK;
    dst.nl_pid = 0;
    dst.nl_groups = 0;

    // data len
    nlf_hand->nlmsg_len = NLMSG_SPACE(len);
    // sender's pid
    nlf_hand->nlmsg_pid = (pid != -1) ? pid : getpid(); 
    // msg type
    nlf_hand->nlmsg_type = type;
    // msg flags
    nlf_hand->nlmsg_flags = NLM_F_REQUEST;

    // make data
    iov.iov_base = (void *)nlf_hand; //
    iov.iov_len = nlf_hand->nlmsg_len;
    ptr = (char *)NLMSG_DATA(nlf_hand);
    memcpy(ptr, data, len);


    msg.msg_name = (void *)&dst; // 目标地址
    msg.msg_namelen = sizeof(struct sockaddr_nl);
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1; //iov len

    int nrtn = sendmsg(nlfd_sock, &msg, 0);
    return nrtn;
}

int create_sock()
{
    struct iovec iov;
    struct sockaddr_nl src;

    nlfd_sock = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_TEST);
    if (nlfd_sock == -1) {
        return -1;
    }
    memset(&src, 0, sizeof(struct sockaddr_nl));
    src.nl_family = AF_NETLINK;
    src.nl_pid = getpid();
    src.nl_groups = 0;
    bind(nlfd_sock, (struct sockaddr*)&src, sizeof(struct sockaddr_nl));
    nlf_hand = (struct nlmsghdr *)malloc(NLMSG_SPACE(NL_MSG_LEN_MAX));
    if (!nlf_hand) {
        return -1;
    }
}

int main()
{
    create_sock();
    struct data_msg _data = {-1,0,false};
    int pid;
    int opr;
    printf("add input 1,del input 2,del all 3\n");
    scanf("%d",&opr);
    
    if(opr == 3){
        _data.isAll = true;
    }
    if(opr == 1 || opr == 2){
        scanf("%d",&pid);
        _data.data = pid;
    }
    _data.type = opr;
   

    send_msg((char*)&_data,sizeof(_data),SELF_MSG_TYPE_PROCESS_OPER,-1);
    read_msg();
    printf("after ===%d\n",pid);
    return 0;
}

总结:上述针对系统内核kill的hook调用使用举例实现的自己功能,除此之外,系统还有好多如此方式处理。后续会持续笔记有关hook的相关知识点。

;