📜 ⬆️ ⬇️

Change the PID of a process in Linux using a kernel module

In this article we will try to create a kernel module that can change the PID of an already running process in Linux, as well as experiment with processes that have received a modified PID.



Warning : changing the PID is a non-standard process, and under certain circumstances can lead to a kernel panic.

Our test module will implement the character device / dev / test, when read from which the process will change the PID. For an example of implementing a character device, thanks to this article. The full code of the module is given at the end of the article. Of course, the most appropriate solution was to add a system call to the kernel itself, but this would require recompilation of the kernel.
')

Environment


All actions for testing the module were performed in a VirtualBox virtual machine with a 64-bit Linux distribution and kernel version 4.14.4-1. Communication with the machine was carried out using SSH.

Attempt # 1 simple solution


A few words about current : The current variable points to the task_struct structure describing the process in the kernel (PID, UID, GID, cmdline, namespaces, etc.)

The first idea was to simply change the current-> pid parameter from the kernel module to the one you need.

static ssize_t device_read( struct file *filp, char *buffer, size_t length, loff_t * offset ) { printk( "PID: %d.\n",current->pid); current->pid = 1; printk( "new PID: %d.\n",current->pid); ,,, } 

To test the module, I wrote a program in C ++:

 #include <iostream> #include <fstream> #include <unistd.h> int main() { std::cout << "My parent PID " << getppid() << std::endl; std::cout << "My PID " << getpid() << std::endl; std::fstream f("/dev/test",std::ios_base::in); if(!f) { std::cout << "f error"; return -1; } std::string str; f >> str; std::cout << "My new PID " << getpid() << std::endl; execl("/bin/bash","/bin/bash",NULL); } 

Load the module with the insmod command, create / dev / test and try.

 [root@archlinux ~]# ./a.out My parent PID 293 My PID 782 My new PID 782 

PID has not changed. Perhaps this is not the only place where the PID is indicated.

Attempt # 2 additional PID fields


If not current-> pid is a process id, then what is? A quick scan of the getpid () code pointed to the task_struct structure, which describes the Linux process and the pid.c file in the kernel source code. The required function is __task_pid_nr_ns. The function code contains the call task-> pids [type] .pid, this parameter will be changed

Compile, try



Since I tested on SSH, I managed to get the output of the program before the kernel crashed:

 My parent PID 293 My PID 1689 My new PID 1689 

The first result is something. But the PID still hasn't changed.

Attempt # 3 non-exported kernel symbols


A closer look at pid.c gave a function that does what we need.
static void __change_pid(struct task_struct *task, enum pid_type type,
struct pid *new)

The function accepts the task for which it is necessary to change the PID, the type of the PID and, in fact, the new PID. Creating a new PID is engaged in the function
struct pid *alloc_pid(struct pid_namespace *ns)

This function accepts only the namespace in which the new PID will be located; this space can be obtained using task_active_pid_ns .
But there is one problem: these kernel symbols are not exported by the kernel and cannot be used in modules. A great article helped me solve this problem. The function code find_sym is taken from there.

 static asmlinkage void (*change_pidR)(struct task_struct *task, enum pid_type type, struct pid *pid); static asmlinkage struct pid* (*alloc_pidR)(struct pid_namespace *ns); static int __init test_init( void ) { printk( KERN_ALERT "TEST driver loaded!\n" ); change_pidR = find_sym("change_pid"); alloc_pidR = find_sym("alloc_pid"); ... } static ssize_t device_read( struct file *filp, char *buffer, size_t length, loff_t * offset ) { printk( "PID: %d.\n",current->pid); struct pid* newpid; newpid = alloc_pidR(task_active_pid_ns(current)); change_pidR(current,PIDTYPE_PID,newpid); printk( "new PID: %d.\n",current->pid); ... } 

Compile, run

 My parent PID 299 My PID 750 My new PID 751 

PID changed! The kernel automatically allocated a free PID to our program. But is it possible to use a PID that has taken a different process, for example PID 1? Add after the allocation code

 newpid->numbers[0].nr = 1; 

Compile, run

 My parent PID 314 My PID 1172 My new PID 1 

We get a real PID 1!



Bash gave an error that prevents task switching by command% n, but all other functions work fine.

Interesting features of processes with a modified PID


PID 0: login cannot exit


Let's go back to the code and change the PID to 0.

newpid->numbers[0].nr = 0;
Compile, run

 My parent PID284 My PID 1517 My new PID 0 

Does the PID 0 come out not so special? We rejoice, write exit and ...



The kernel is falling! The kernel defined our task as IDLE TASK and, seeing the completion, just fell. Apparently, before completion, our program should retake the “normal” PID.

Invisible process


Let's go back to the code and set the PID, guaranteed not busy
newpid->numbers[0].nr = 12345;

Compile, run

 My parent PID296 My PID 735 My new PID 12345 

Let's see what is in / proc

 1 148 19 224 288 37 79 86 93 consoles fb kcore locks partitions swaps version 10 149 2 226 29 4 8 87 acpi cpuinfo filesystems key-users meminfo sched_debug sys vmallocinfo 102 15 20 23 290 5 80 88 asound crypto fs keys misc schedstat sysrq-trigger vmstat 11 16 208 24 291 6 81 89 buddyinfo devices interrupts kmsg modules scsi sysvipc zoneinfo 12 17 21 25 296 7 82 9 bus diskstats iomem kpagecgroup mounts self thread-self 13 176 210 26 3 737 83 90 cgroups dma ioports kpagecount mtrr slabinfo timer_list 139 18 22 27 30 76 84 91 cmdline driver irq kpageflags net softirqs tty 14 182 222 28 31 78 85 92 config.gz execdomains kallsyms loadavg pagetypeinfo stat uptime 

As you can see / proc does not define our process, even if we have occupied a free PID. The previous PID is also not in / proc, and this is very strange. We may be in a different namespace and therefore not visible to the main / proc. We will mount a new / proc, and see what is there

 1 14 18 210 25 291 738 81 9 bus devices fs key-users locks pagetypeinfo softirqs timer_list 10 148 182 22 26 296 741 82 90 cgroups diskstats interrupts keys meminfo partitions stat tty 102 149 19 222 27 30 76 83 92 cmdline dma iomem kmsg misc sched_debug swaps uptime 11 15 2 224 28 37 78 84 93 config.gz driver ioports kpagecgroup modules schedstat sys version 12 16 20 226 288 4 79 85 acpi consoles execdomains irq kpagecount mounts scsi sysrq-trigger vmallocinfo 13 17 208 23 29 6 8 86 asound cpuinfo fb kallsyms kpageflags mtrr self sysvipc vmstat 139 176 21 24 290 7 80 87 buddyinfo crypto filesystems kcore loadavg net slabinfo thread-self zoneinfo 

Our process is still not there, which means we are in the usual namespace. Check

ps -e | grep bash
296 pts/0 00:00:00 bash

Only one bash from which we started the program. Neither the previous PID nor the current one is listed.

Github link

Source: https://habr.com/ru/post/345040/


All Articles