在2.6内核中, netlink相关的接口函数随着版本的变化很大,现在网上流传的多数代码是以较老的版本(2.6.24以下)作为依托。这里,我将一段基于2.6.18的简单的代码移植到了2.6.27中,希望可以帮助大家理解其中的变化。
这个程序主要分为内核模块和用户模块。主要功能是监控一个指定的文件,如果他被打开了,记录打开的方式、程序、时间等信息到指定的log文件中。请到源码中理解代码具体的含义和作用:
(1) 内核部分:
文件 netlinkp.c(不同内核版本代码主要不同在这个文件)
#include <linux/string.h> #include <linux/mm.h> #include <net/sock.h> #include <net/netlink.h> #include <linux/sched.h> #define VER_2_6_27 //表示是2.6.27的内核版本 #define TASK_COMM_LEN 16 #define NETLINK_TEST 29 //自定义的netlink通讯号 #define AUDITPATH "/root/audit" //监控的文件名 #define MAX_LENGTH 256 //记录监控文件的用户程序的pid,他是内核与用户程序通过netlink通讯的依据 static u32 pid=0; static struct sock *nl_sk = NULL; /* * 向用户程序发送消息 * */ int netlink_sendmsg(const void *buffer, unsigned int size) { struct sk_buff *skb; struct nlmsghdr *nlh; int len = NLMSG_SPACE(1200); if((!buffer) || (!nl_sk) || (pid == 0)) return 1; skb = alloc_skb(len, GFP_ATOMIC); if (!skb){ printk(KERN_ERR "net_link: allocat_skb failed./n"); return 1; } //必须将netlink关联到一个skb上 nlh = nlmsg_put(skb,0,0,0,1200,0); //netlink 通讯只用到skb中的一个叫cb的buffer NETLINK_CB(skb).pid = 0; memcpy(NLMSG_DATA(nlh), buffer, size); //单播方式发送到pid指定的用户程序 if( netlink_unicast(nl_sk, skb, pid, MSG_DONTWAIT) < 0){ printk(KERN_ERR "net_link: can not unicast skb /n"); return 1; } return 0; } /* * 通过@pathname 获得全路径名 * */ void get_fullname(const char *pathname,char *fullname) { //内核中有 #define current (get_current()),代表当前进程的信息 #ifdef VER_2_6_27 struct dentry *tmp_dentry = current->fs->pwd.dentry; #elif VER_2_6_18 struct dentry *tmp_dentry = current->fs->pwd; #endif char tmp_path[MAX_LENGTH]; char local_path[MAX_LENGTH]; memset(tmp_path,0,MAX_LENGTH); memset(local_path,0,MAX_LENGTH); if (*pathname == '/') { strcpy(fullname,pathname); return; } while (tmp_dentry != NULL) { if (!strcmp(tmp_dentry->d_iname,"/")) break; strcpy(tmp_path,"/"); strcat(tmp_path,tmp_dentry->d_iname); strcat(tmp_path,local_path); strcpy(local_path,tmp_path); tmp_dentry = tmp_dentry->d_parent; } strcpy(fullname,local_path); strcat(fullname,"/"); strcat(fullname,pathname); return; } /* * 重定义open系统调用后会在原来open系统调用的基础上调用此函数 * @ 如果不是指定的监控文件,会直接返回 * @ 如果是指定的监控文件,则将相应的信息送到用户程序 */ int AuditOpen(const char *pathname,int flags, int ret) { char commandname[TASK_COMM_LEN]; char fullname[256]; unsigned int size; // = strlen(pathname) + 32 + TASK_COMM_LEN; void * buffer; // = kmalloc(size, 0); memset(fullname, 0, 256); get_fullname(pathname, fullname); if (strncmp(fullname,AUDITPATH,15) != 0) return 1; strncpy(commandname,get_current()->comm,TASK_COMM_LEN); size = strlen(fullname) + 16 + TASK_COMM_LEN + 1; buffer = kmalloc(size, 0); memset(buffer, 0, size); *((int*)buffer) = current->uid; ; *((int*)buffer + 1) = current->pid; *((int*)buffer + 2) = flags; *((int*)buffer + 3) = ret; strcpy( (char*)( 4 + (int*)buffer ), commandname); strcpy( (char*)( 4 + TASK_COMM_LEN/4 +(int*)buffer ), fullname); //将相应的信息通过netlink 发送 netlink_sendmsg(buffer, size); return 0; } /* * 这个函数是netlink_kernel_create中需要的回调函数 * @ 在不同版本中,这个函数的原型是不同的 * */ #ifdef VER_2_6_27 void nl_data_ready (struct sk_buff *__skb) { struct nlmsghdr *nlh; struct sk_buff *skb; skb = skb_get(__skb); if (skb->len >= NLMSG_SPACE(0)) { nlh = (struct nlmsghdr *)skb->data; if( pid != 0 ) printk("Pid != 0 /n "); pid = nlh->nlmsg_pid; //获取应用程序的pid printk("net_link: pid is %d, data %s:/n", pid, (char *)NLMSG_DATA(nlh)); kfree_skb(skb); } return; } #elif VER_2_6_18 void nl_data_ready (struct sock *sk, int len) { struct sk_buff *skb; struct nlmsghdr *nlh; skb = skb_dequeue(&(sk->sk_receive_queue)); nlh = (struct nlmsghdr *)skb->data; if (skb->len >= NLMSG_SPACE(0)) { nlh = (struct nlmsghdr *)skb->data; if( pid != 0 ) printk("Pid != 0 /n "); pid = nlh->nlmsg_pid; //获取应用程序的pid printk("net_link: pid is %d, data %s:/n", pid, (char *)NLMSG_DATA(nlh)); kfree_skb(skb); } return; } #endif /* * 初始化 netlink,在sdthook.c中的模块初始化函数中调用 * */ void netlink_init(void) { #ifdef VER_2_6_27 struct net netlink; nl_sk = netlink_kernel_create(&netlink, NETLINK_TEST, 0, nl_data_ready, NULL, THIS_MODULE); #elif VER_2_6_18 nl_sk = netlink_kernel_create(NETLINK_TEST, 0, nl_data_ready, THIS_MODULE); #endif // NETLINK_TEST, 0, nl_data_ready, NULL, THIS_MODULE); //adjusted by zxc if (!nl_sk) { printk(KERN_ERR "net_link: Cannot create netlink socket./n"); if (nl_sk != NULL) sock_release(nl_sk->sk_socket); } else printk("net_link: create socket ok./n"); } /* * 卸载 netlink,在sdthook.c中的模块卸载函数中调用 * */ void netlink_release(void) { if (nl_sk != NULL) sock_release(nl_sk->sk_socket); }
为了保持代码段的完整性,贴出其他两个的源码
文件 syscalltable.c
#ifndef _LARGEFILE64_SOURCE #define _LARGEFILE64_SOURCE #endif #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/syscalls.h> #include <linux/file.h> #include <linux/fs.h> #include <linux/string.h> #include <linux/mm.h> #include <linux/sched.h> #include <linux/unistd.h> void netlink_release(void); void netlink_init(void); int AuditOpen(const char *pathname, int flags, int ret); void *get_sys_call_table(void); unsigned int clear_and_return_cr0(void); void setback_cr0(unsigned int val); void **sys_call_table; asmlinkage long (* orig_open)(const char *pathname, int flags, mode_t mode); /* * 重定义的open系统调用 * @ 在原系统调用的基础上,调用AuditOpen * */ asmlinkage long hacked_open(const char *pathname, int flags, mode_t mode) { long ret; if( pathname == NULL ) return -1; //printk("Open Intercepted : %s /n", pathname); ret = orig_open(pathname, flags, mode); AuditOpen(pathname,flags,ret); return ret; } /* * 模块初始化 * @ 将open系统调用重定向到hacked_open * @ 初始化netlink * */ static int __init init(void) { unsigned int orig_cr0 = clear_and_return_cr0(); //关写保护 sys_call_table = get_sys_call_table(); //获取系统调用表基址 printk("Info: sys_call_table found at %lx/n",(unsigned long)sys_call_table) ; //Hook Sys Call Open orig_open = sys_call_table[__NR_open]; //备份原open系统调用 sys_call_table[__NR_open] = hacked_open; //设置__NR_open系统调用 setback_cr0(orig_cr0); //开写保护 //Initialize Netlink netlink_init(); return 0; } /* * 模块卸载 * @ 将open系统调用重定向回原来的函数 * @ 卸载netlink * */ static void __exit exit(void) { unsigned int orig_cr0 = clear_and_return_cr0(); sys_call_table[__NR_open] = orig_open; setback_cr0(orig_cr0); netlink_release(); } module_init(init); module_exit(exit);
文件 syscalltable.c
#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> //中断描述表的描述结构 struct idt_descriptor { unsigned short off_low; unsigned short sel; unsigned char none, flags; unsigned short off_high; }; /* * 获得系统调用基址 * */ void *get_system_call(void) { unsigned char idtr[6]; unsigned long base; struct idt_descriptor desc; asm ("sidt %0" : "=m" (idtr)); //将中断描述符表基址付给idtr base = *((unsigned long *) &idtr[2]); memcpy(&desc, (void *) (base + (0x80*8)), sizeof(desc)); return((void *) ((desc.off_high << 16) + desc.off_low)); } /* * 获得系统调用表 * */ void *get_sys_call_table(void) { void *system_call = get_system_call(); unsigned char *p; unsigned long s_c_t; int count = 0; p = (unsigned char *) system_call; while (!((*p == 0xff) && (*(p+1) == 0x14) && (*(p+2) == 0x85))){ p++; if (count++ > 500) { count = -1; break; } } if (count != -1){ p += 3; s_c_t = *((unsigned long *) p); } else s_c_t = 0; return((void *) s_c_t); } /* * 清除写保护,并返回原CR0寄存器的值 * */ unsigned int clear_and_return_cr0(void) { unsigned int cr0 = 0; unsigned int ret; asm volatile ("movl %%cr0, %%eax" : "=a"(cr0)); ret = cr0; // 清除CR0的20位,这是个写保护位 cr0 &= 0xfffeffff; asm volatile ("movl %%eax, %%cr0" : : "a"(cr0)); return ret; } /* * 设置新的CR0控制寄存器的值 * */ void setback_cr0(unsigned int val) { asm volatile ("movl %%eax, %%cr0" : : "a"(val)); }
(2) 用户部分:
唯一的文件 auditdeamon.c.
#include <sys/stat.h> #include <sys/socket.h> #include <sys/types.h> #include <linux/netlink.h> #include <linux/socket.h> #include <fcntl.h> #include <asm/types.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <signal.h> #include <pwd.h> #define TM_FMT "%Y-%m-%d %H:%M:%S" #define NETLINK_TEST 29 #define MAX_PAYLOAD 1024 /* maximum payload size*/ int sock_fd; struct msghdr msg; struct nlmsghdr *nlh = NULL; struct sockaddr_nl src_addr, dest_addr; struct iovec iov; FILE *logfile; /* * 日志函数 * @ 如果被监控的文件被打开,则会调用这个函数 * @ 会将打开信息写到logfile指定的文件中 * */ void Log(char *commandname,int uid, int pid, char *file_path, int flags,int ret) { char logtime[64]; char username[32]; struct passwd *pwinfo; char openresult[10]; char opentype[16]; if (ret > 0) strcpy(openresult,"success"); else strcpy(openresult,"failed"); if (flags & O_RDONLY ) strcpy(opentype, "Read"); else if (flags & O_WRONLY ) strcpy(opentype, "Write"); else if (flags & O_RDWR ) strcpy(opentype, "Read/Write"); else strcpy(opentype,"other"); time_t t=time(0); if (logfile == NULL) return; pwinfo = getpwuid(uid); strcpy(username,pwinfo->pw_name); strftime(logtime, sizeof(logtime), TM_FMT, localtime(&t) ); fprintf(logfile,"%s(%d) %s(%d) %s /"%s/" %s %s/n",username,uid,commandname,pid,logtime,file_path,opentype, openresult); printf("%s(%d) %s(%d) %s /"%s/" %s %s/n",username,uid,commandname,pid,logtime,file_path,opentype, openresult); } /* * 用户程序向内核发送pid函数 * @ 通过netlink发送 * @ 这个pid是这种方式通讯的桥梁 * */ void sendpid(unsigned int pid) { //Send message to initialize memset(&msg, 0, sizeof(msg)); memset(&src_addr, 0, sizeof(src_addr)); src_addr.nl_family = AF_NETLINK; src_addr.nl_pid = pid; //self pid src_addr.nl_groups = 0; //not in mcast groups bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr)); memset(&dest_addr, 0, sizeof(dest_addr)); dest_addr.nl_family = AF_NETLINK; dest_addr.nl_pid = 0; //For Linux Kernel dest_addr.nl_groups = 0; //unicast /* Fill the netlink message header */ nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD); nlh->nlmsg_pid = pid; /* self pid */ nlh->nlmsg_flags = 0; /* Fill in the netlink message payload */ iov.iov_base = (void *)nlh; iov.iov_len = nlh->nlmsg_len; msg.msg_name = (void *)&dest_addr; msg.msg_namelen = sizeof(dest_addr); msg.msg_iov = &iov; msg.msg_iovlen = 1; //printf(" Sending message. .../n"); sendmsg(sock_fd, &msg, 0); } /* * 如果向程序进程发送SIGTERM信号,将会在这里处理 * @ 主要工作就是将日志文件关了,否则日志数据存放于缓冲区,而没有真正的写入 * @ 当然还要释放 nlh * */ void killdeal_func() { printf("The process is killed! /n"); //close(sock_fd); if (logfile != NULL) fclose(logfile); if (nlh != NULL) free(nlh); exit(0); } /* * 主函数入口 * */ int main(int argc, char *argv[]){ char buff[110]; void killdeal_func(); char logpath[32]; if (argc == 1) strcpy(logpath,"./log"); else if (argc == 2) strncpy(logpath, argv[1],32); else { printf("commandline parameters error! please check and try it! /n"); exit(1); } signal(SIGTERM,killdeal_func); sock_fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_TEST); //分配接收内核netlink message的变量 nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD)); memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD)); sendpid(getpid()); /* open the log file at the begining of daemon, in case of this operation causes deadlock */ logfile=fopen(logpath, "w+"); if (logfile == NULL) { printf("AAWaring: can not create log file/n"); exit(1); } //循环获取消息 while(1) { //Read message from kernel unsigned int uid, pid,flags,ret; char * file_path; char * commandname; //内核没有消息将会阻塞在此 recvmsg(sock_fd, &msg, 0); uid = *( (unsigned int *)NLMSG_DATA(nlh) ); pid = *( 1 + (int *)NLMSG_DATA(nlh) ); flags = *( 2 + (int *)NLMSG_DATA(nlh) ); ret = *( 3 + (int *)NLMSG_DATA(nlh) ); commandname = (char *)( 4 + (int *)NLMSG_DATA(nlh)); file_path = (char *)( 4 + 16/4 + (int *)NLMSG_DATA(nlh)); //写入日志 Log(commandname, uid,pid, file_path,flags,ret); } close(sock_fd); free(nlh); fclose(logfile); return 0; }
代码使用说明:
内核部分Makefile:
obj-m:=AuditModule.o AuditModule-objs :=sdthook.o syscalltable.o netlinkp.o KDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) default: $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules clean: $(RM) -rf .*.cmd *.mod.c *.o *.ko .tmp*
将三个内核部分文件和Makefile文件拷到同一目录下,直接make,在# insmod AuditModule.ko。
创建待监听的文件 :# touch /root/audit
对于应用程序部分, # gcc auditdeamon.c
后台运行 : # a.out &
测试,通过各种方式打开测试文件:
# cat /root/audit
# vi /root/audit
当然你也可以自己写代码打开 /root/audit
最后通过 : # kill -15(或者-SIGTERM) pid(a.out &的pid,可以通过ps -aux查看)
结束监控后,查看 a.out 同级目录下的log文件,便可查看到日志信息了。