Catching and handling events in the Linux file system

Introduction

In the previous article, we looked at building and installing the package on Linux systems, in which we mentioned the Linux Kernel Module (LKM) and promised to reveal later details about the path to it and its creation. Well, his time has come. LKM - we choose you.





Need for implementation

"We replaced the Windows driver with the Linux Kernel Module LKM ..." so, let's go back mentally to the very beginning of the path. We have a Windows driver that monitors and intercepts file access events. How to port it or what to replace it in Linux systems? Digging into the architecture , reading about the interception and implementation of such technologies in Linux , we realized that the task is absolutely nontrivial, containing a bunch of pitfalls.





Inotify





, , «» Inotify. Inotify – , , . «» – fanotify. , . , , , , , fanotify . , fanotify – userspace , .





Virtual File System





VFS.





VFS Dtrace, eBPF bcc, , , . , LKM. :



• ;





• , , ;





• .





Janus, SElinux AppArmor





, Linux. , . Janus. LKM . SELinux AppArmor . SELinux :



• ;

• (. Access Vector Cache, AVC);

• ;

• ;

• (selinuxfs) -.





«», ! , , .









ftrace , LKM ftrace. , , debugfs, Linux . Hook' clone open:



• openat,

• rename,

• unlink,

• unlinkat.



, , , , , .









userspace. , :



• socket kernel userspace;

• / .





, netlink socket, Windows - FltSendMessage. inet socket, . , .Net Core, userspace , netlink.





netlink .






int open_netlink_connection(void)
{
    //initialize our variables
    int sock;
    struct sockaddr_nl addr;
    int group = NETLINK_GROUP;

    //open a new socket connection
    sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_USERSOCK);

    //if the socket failed to open,
    if (sock < 0) 
    {
        //inform the user
        printf("Socket failed to initialize.\n");
        //return the error value
        return sock;
    }

    //initialize our addr structure by filling it with zeros
    memset((void *) &addr, 0, sizeof(addr));
    //specify the protocol family
    addr.nl_family = AF_NETLINK;
    //set the process id to the current process id
    addr.nl_pid = getpid();

    //bind the address to the socket created, and if it failed,
    if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) 
    {
        //inform the user
        printf("bind < 0.\n");
        //return the function with a symbolic error code
        return -1;
    }

    //set the option so that we can receive packets whose destination
    //is the group address specified (so that we can receive the message broadcasted by the kernel)
    if (setsockopt(sock, 270, NETLINK_ADD_MEMBERSHIP, &group, sizeof(group)) < 0) 
    {
        //if it failed, inform the user
        printf("setsockopt < 0\n");
        //return the function with a symbolic error code
        return -1;
    }

    //if we got thus far, then everything
    //went fine. Return our socket.
    return sock;
}

char* read_kernel_message(int sock)
{
    //initialize the variables
    //that we are going to need
    struct sockaddr_nl nladdr;
    struct msghdr msg;
    struct iovec iov;
    char* buffer[CHUNK_SIZE];
    char* kernelMessage;
    int ret;

    memset(&msg, 0, CMSG_SPACE(MAX_PAYLOAD));
    memset(&nladdr, 0, sizeof(nladdr));
    memset(&iov, 0, sizeof(iov));
    //specify the buffer to save the message
    iov.iov_base = (void *) &buffer;
    //specify the length of our buffer
    iov.iov_len = sizeof(buffer);

    //pass the pointer of our sockaddr structure
    //that will save the source IP and port of the connection
    msg.msg_name = (void *) &(dest_addr);
    //give the size of our structure
    msg.msg_namelen = sizeof(dest_addr);
    //pass our scatter/gather I/O structure pointer
    msg.msg_iov = &iov;
    //we will pass only one buffer array,
    //therefore we will specify that here
    msg.msg_iovlen = 1;

    //listen/wait for new data
    ret = recvmsg(sock, &msg, 0);

    //if message was received successfully,
    if(ret >= 0)
    {
        //get the string data and save them to a local variable
        char* buf = NLMSG_DATA((struct nlmsghdr *) &buffer);

        //allocate memory for our kernel message
        kernelMessage = (char*)malloc(CHUNK_SIZE);

        //copy the kernel data to our allocated space
        strcpy(kernelMessage, buf);

        //return the pointer that points to the kernel data
        return kernelMessage;
    }
    
    //if we got that far, reading the message failed,
    //so we inform the user and return a NULL pointer
    printf("Message could not received.\n");
    return NULL;
}

int send_kernel_message(int sock, char* kernelMessage)
{
    //initialize the variables
    //that we are going to need
    struct msghdr msg;
    struct iovec iov;
    char* buffer[CHUNK_SIZE];    
    int ret;

    memset(&msg, 0, CMSG_SPACE(MAX_PAYLOAD));
    memset(&iov, 0, sizeof(iov));

    nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
    memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));
    nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
    nlh->nlmsg_pid = getpid();
    nlh->nlmsg_flags = 0;

    char buff[160];
    snprintf(buff, sizeof(buff), "From:DSSAgent;Action:return;Message:%s;", kernelMessage);
    strcpy(NLMSG_DATA(nlh), buff);

    iov.iov_base = (void *)nlh;
    iov.iov_len = nlh->nlmsg_len;
    //pass the pointer of our sockaddr structure
    //that will save the source IP and port of the connection
    msg.msg_name = (void *) &(dest_addr);
    //give the size of our structure
    msg.msg_namelen = sizeof(dest_addr);
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;
    printf("Sending message to kernel (%s)\n",(char *)NLMSG_DATA(nlh));
    ret = sendmsg(sock, &msg, 0);
    return ret;
}

int sock_netlink_connection()
{
	sock_fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_USER);
    if (sock_fd < 0)
        return -1;


    memset(&src_addr, 0, sizeof(src_addr));
    src_addr.nl_family = AF_NETLINK;
    src_addr.nl_pid = getpid(); /* self pid */


    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 */


    nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
    memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));
    nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
    nlh->nlmsg_pid = getpid();
    nlh->nlmsg_flags = 0;


    strcpy(NLMSG_DATA(nlh), "From:DSSAgent;Action:hello;");


    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 to kernel\n");
    sendmsg(sock_fd, &msg, 0);
    printf("Waiting for message from kernel\n");


    /* Read message from kernel */
    recvmsg(sock_fd, &msg, 0);
    printf("Received message payload: %s\n", (char *)NLMSG_DATA(nlh));
	
	return sock_fd;
}
void sock_netlink_disconnection(int sock)
{
	close(sock);
    free(nlh);
}
      
      



, , Net.Core – pid , . , , , . uid , .





char* get_username_by_pid(int pid)
{ 
  register struct passwd *pw;
  register uid_t uid;
  int c;
  FILE *fp;
  char filename[255];
  sprintf(filename, "/proc/%d/loginuid", pid);
  char cc[8];
  
  //   
  if((fp= fopen(filename, "r"))==NULL)
    {
        perror("Error occured while opening file");
        return "";
    }
  // ,     
  while((fgets(cc, 8, fp))!=NULL) {}
     
  fclose(fp);
  
  uid = atoi(cc);

  pw = getpwuid (uid);
  if (pw)
  {
      return pw->pw_name;
  }
  else
  {
      return "";
  }
}
      
      







netlink LKM.





static int fh_init(void)
{
    int err;
	struct netlink_kernel_cfg cfg =
	{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0)
		.groups = 1,
#endif
		.input = nl_recv_msg,
	};

#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 36)
	nl_sk = netlink_kernel_create(&init_net, NETLINK_USER, &cfg);
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 32)
	nl_sk = netlink_kernel_create(&init_net, NETLINK_USER, 0, nl_recv_msg, NULL, THIS_MODULE);
#else
	nl_sk = netlink_kernel_create(NETLINK_USER, 0, nl_recv_msg, THIS_MODULE);
#endif

	if (!nl_sk)
	{
		printk(KERN_ERR "%s Could not create netlink socket\n", __func__);
		return 1;
	}

	err = fh_install_hooks(hooks, ARRAY_SIZE(hooks));
	if (err)
		return err;

	p_list_hook_files = (tNode *)kmalloc(sizeof(tNode), GFP_KERNEL);
	p_list_hook_files->next = NULL;
	p_list_hook_files->value = 0;

	pr_info("module loaded\n");

	return 0;
}
module_init(fh_init);

static void fh_exit(void)
{
	delete_list(p_list_hook_files);
	fh_remove_hooks(hooks, ARRAY_SIZE(hooks));
	netlink_kernel_release(nl_sk);
	pr_info("module unloaded\n");
}
module_exit(fh_exit);
      
      



Socket . , , , pid . Userspace , , , ( ). .





static void send_msg_to_user(const char *msgText)
{
	int msgLen = strlen(msgText);

	struct sk_buff *skb = nlmsg_new(NLMSG_ALIGN(msgLen), GFP_KERNEL);

	if (!skb)
	{
		printk(KERN_ERR "%s Allocation skb failure.\n", __func__);
		return;
	}

	struct nlmsghdr *nlh = nlmsg_put(skb, 0, 1, NLMSG_DONE, msgLen, 0);

	if (!nlh)
	{
		printk(KERN_ERR "%s Create nlh failure.\n", __func__);
		nlmsg_free(skb);
		return;
	}

	NETLINK_CB(skb).dst_group = 0;
	strncpy(nlmsg_data(nlh), msgText, msgLen);

	int errorVal = nlmsg_unicast(nl_sk, skb, pid);

	if (errorVal < 0)
		printk(KERN_ERR "%s nlmsg_unicast() error: %d\n", __func__, errorVal);
}

static void return_msg_to_user(struct nlmsghdr *nlh)
{
	pid = nlh->nlmsg_pid;

	const char *msg = "Init socket from kernel";
	const int msg_size = strlen(msg);

	struct sk_buff *skb = nlmsg_new(msg_size, 0);
	if (!skb)
	{
		printk(KERN_ERR "%s Failed to allocate new skb\n", __func__);
		return;
	}

	nlh = nlmsg_put(skb, 0, 0, NLMSG_DONE, msg_size, 0);
	NETLINK_CB(skb).dst_group = 0;
	strncpy(nlmsg_data(nlh), msg, msg_size);

	int res = nlmsg_unicast(nl_sk, skb, pid);
	if (res < 0)
		printk(KERN_ERR "%s Error while sending back to user (%i)\n", __func__, res);
}
      
      



, , ( ) , ( pid ).





static void parse_return_from_user(char *return_msg)
{
	char *msg = np_extract_value(return_msg, "Message", ';');
	const char *file_name = strsep(&msg, "|");

	printk(KERN_INFO "%s Name:(%s) Permiss:(%s)\n", __func__, file_name, msg);

	if (strstr(msg, "Deny"))
		reload_name_list(p_list_hook_files, file_name, Deny);
	else
		reload_name_list(p_list_hook_files, file_name, Allow);
}

static void free_guards(void)
{
	// Possibly unpredictable behavior during cleaning
	memset(&guards, 0, sizeof(struct process_guards));
}

static void change_guards(char *msg)
{
	char *path = np_extract_value(msg, "Path", ';');
	char *count_str = np_extract_value(msg, "Count", ';');

	if (path && strlen(path) && count_str && strlen(count_str))
	{
		int i, found = -1;

		for (i = 0; i < guards.count; ++i)
			if (guards.process[i].file_path && !strcmp(path, guards.process[i].file_path))
				found = i;

		guards.is_busy = 1;

		int count;
		kstrtoint(count_str, 10, &count);

		if (count > 0)
		{
			if (found == -1)
			{
				strcpy(guards.process[guards.count].file_path, path);
				found = guards.count;
				guards.count++;
			}

			for (i = 0; i < count; ++i)
			{
				char buff[8];
				snprintf(buff, sizeof(buff), "Pid%d", i + 1);
				char *pid = np_extract_value(msg, buff, ';');
				if (pid && strlen(pid))
					kstrtoint(pid, 10, &guards.process[found].allow_pids[i]);
				else
					guards.process[found].allow_pids[i] = 0;
			}

			guards.process[found].allow_pids[count] = 0;
		}
		else
		{
			if (found >= 0)
			{
				for (i = found; i < guards.count - 1; ++i)
					guards.process[i] = guards.process[i + 1];

				guards.count--;
			}
		}

		guards.is_busy = 0;
	}
}

// Example message is "From:CryptoCli;Action:clear;" or "From:DSSAgent;Action:init;"
static void nl_recv_msg(struct sk_buff *skb)
{
	printk(KERN_INFO "%s <--\n", __func__);

	struct nlmsghdr *nlh = (struct nlmsghdr *)skb->data;

	printk(KERN_INFO "%s Netlink received msg payload:%s\n", __func__, (char *)nlmsg_data(nlh));

	char *msg = (char *)nlmsg_data(nlh);

	if (msg && strlen(msg))
	{
		char *from = np_extract_value(msg, "From", ';');
		char *action = np_extract_value(msg, "Action", ';');

		if (from && strlen(from) && action && strlen(action))
		{
			
			if (!strcmp(from, "DSSAgent"))
			{
				if (!strcmp(action, "init"))
				{
					return_msg_to_user(nlh);
				}
				else if (!strcmp(action, "return"))
				{
					parse_return_from_user(msg);
				}
				else
				{
					printk(KERN_ERR "%s Failed msg, \"From\" is %s and \"Action\" is %s\n", __func__, from, action);
				}
			}
			else if (!strcmp(from, "CryptoCli"))
			{
				if (!strcmp(action, "clear"))
				{
					free_guards();
				}
				else if (!strcmp(action, "change"))
				{
					change_guards(msg);
				}
				else
				{
					printk(KERN_ERR "%s Failed msg, \"From\" is %s and \"Action\" is %s\n", __func__, from, action);
				}
			}
			else
			{
				printk(KERN_ERR "%s Failed msg, \"From\" is %s and \"Action\" is %s\n", __func__, from, action);
			}

		}
		else
		{
			printk(KERN_ERR "%s Failed parse msg, don`t found \"From\" and \"Action\" (%s)\n", __func__, msg);
		}
	}
	else
	{
		printk(KERN_ERR "%s Failed parse struct nlmsg_data, msg is empty\n", __func__);
	}

	printk(KERN_INFO "%s -->\n", __func__);
}

static bool check_file_access(char *fname, int processPid)
{
	if (fname && strlen(fname))
	{
		int i;

		for (i = 0; i < guards.count; ++i)
		{
			if (!strcmp(fname, guards.process[i].file_path) && guards.process[i].allow_pids[0] != 0)
			{
				int j;
				
				for (j = 0; guards.process[i].allow_pids[j] != 0; ++j)
					if (processPid == guards.process[i].allow_pids[j])
						return true;

				return false;
			}
		}
		
		// Not found filename in guards
		if (strstr(fname, filetype))
		{
			char *processName = current->comm;

			printk(KERN_INFO "%s service pid = %d\n", __func__, pid);
			printk(KERN_INFO "%s file name = %s, process pid: %d, , process name = %s\n", __func__, fname, processPid, processName);

			if (processPid == pid)
			{
				return true;
			}
			else
			{
				add_list(p_list_hook_files, processPid, fname, None);

				char *buffer = kmalloc(4096, GFP_KERNEL);
				sprintf(buffer, "%s|%s|%d", fname, processName, processPid);
				send_msg_to_user(buffer);
				kfree(buffer);

				ssleep(5);

				bool ret = true;

				if (find_list(p_list_hook_files, fname) == Deny)
					ret = false;

				delete_node(p_list_hook_files, fname);

				return ret;
			}
		}
	}

	return true;
}
      
      







LKM ftrace, . , , «». userspace . Linux , «», , «» system. .service , ExecStart ExecStop :





ExecStartPre=/bin/sh /__/prestart.sh
ExecStopPost=/sbin/rmmod _.ko
      
      



prestart.sh:





#!/bin/sh

MOD_VAL=$(lsmod | grep _ | wc -l)

cd /___
make clean
make all

if [ $MOD_VAL = 1 ]
then
    for proc in $(ps aux | grep DSS.Agent | awk '{print $2}'); do kill -9 $proc; done
else
    /sbin/insmod / ___/_.ko
fi
      
      



, : , , , « », , Windows. . , . , DevOps, , Linux / LKM, Access Control List (ACL). , Linux. , , , , MS Forms Avalonia Linux.





  • LKM ftrace





  • uid pid





  • Netlink socket












All Articles