How You Can Change A PID process in Linux By 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 the Linux OS, and also experiment with the processes that received the modified PID.
Warning: Changing PID is a non-standard process, and under certain circumstances can lead to kernel panic.
Our test module will implement the character device/ dev/test when reading from which the process will be changed PID. For an example of implementing a character device, thanks to this article. The complete code of the module is given at the end of the article. Of course, the correct solution was to add a system call to the kernel itself, but this would require a recompilation of the kernel.
Environment
All the module testing activities were performed in a VirtualBox virtual machine with a 64-bit Linux distribution and a 4.14.4-1 kernel version. Communication with the machine was carried out using SSH.
Attempt # 1 simple solution
A couple of words about current: the current variable points to a task_struct structure with a process described in the kernel (PID, UID, GID, cmdline, namespaces, etc.
The first idea was simply to change the current-> pid parameter from the kernel module to the desired one.
static size_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 functionality of 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 command instead, create/dev/test and try.
[root@archilinux ~]# ./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 the process ID, what is it? A quick scan of the code getpid () pointed to a task_struct structure that describes the Linux process and the pid.c file in the kernel source code. The desired function is __task_pid_nr_ns. In the function code there is a call to task-> pids [type] .pid, this parameter we will change.
Let’s compile it and try again.
Since we tested on SSH, we were able 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 already something. But PID still has not changed.
Attempt # 3 non-exportable kernel symbols
A more careful study of 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 takes a task for which it is necessary to change the PID, the PID type and, in fact, the new PID. The creation of a new PID is handled by the function
struct pid * alloc_pid (struct pid_namespace * ns)
This function accepts only the namespace in which the new PID will reside, 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 can not be used in modules. In solving this problem I was helped by a wonderful article. 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); ... }
Let’s compile it and try again.
My parent PID 299 My PID 750 My new PID 751
Now PID has been changed! The kernel automatically allocated our program a free PID. But is it possible to use PID, which took another process, for example, PID 1? We add after the allocation code
My parent PID 314 My PID 1172 My new PID 1
Let’s compile it and try again.
My parent PID 314 My PID 1172 My new PID 1
Get the real PID 1.
Bash has issued an error that prevents the switching of tasks on the% n command, but all other functions work fine.
Interesting features of processes with a modified PID
PID 0: cannot be logged out
Let’s return to the code and change the PID to 0.
newpid->numbers[0].nr = 0;
Compiling and run it.
My parent PID284 My PID 1517 My new PID 0
Is PID 0 not so special? Now let’s try to exit.
The core falls! The kernel defined our task as IDLE TASK and, seeing the completion, just fell. Apparently, before the completion of our program should return a “normal” PID.
The invisible process
Let’s return to the code and expose the PID, guaranteed not to be occupied
newpid->numbers[0].nr = 12345;
Compiling and run it.
My parent PID296 My PID 735 My new PID 12345
Let’s see what’s in the /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 occupied a free PID. The previous PID also does not exist in /proc, and this is very strange. Perhaps we are in a different namespace and therefore are not visible to the main /proc. We’ll mount the new /proc, and see what’s 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
As before, our process does not exist, which means that we are in the usual namespace. Let’s check
ps -e | grep bash 296 pts/0 00:00:00 bash
Summary
Only one bash, from which we started the program. There is no previous PID or current one on the list.