目录
本文主要介绍内核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套接字通信
3.1 netlink_kernel_create函数
用于创建一个内核态的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的相关知识点。