Change the process PID on Linux using the kernel module

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



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

    Our test module will implement the character device / dev / test, upon reading from which the process will change its PID. For an example implementation of a character device, thanks to this article. The full module code is given at the end of the article. Of course, the most correct solution was to add a system call to the kernel itself, however this would require recompiling 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 indicates a task_struct structure with a description of 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 functionality of the module, I wrote a program in C ++:

    #include 
    #include 
    #include 
    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);
    }
    

    We 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 the process identifier, then what is? A quick look at the getpid () code pointed to the task_struct structure describing the Linux process and the pid.c file in the kernel source code. The required function is __task_pid_nr_ns. In the function code, there is a call task-> pids [type] .pid, we will change this parameter.

    Compile, try.



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

    My parent PID 293
    My PID 1689
    My new PID 1689

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

    Attempt # 3 non-exportable kernel characters


    A closer look at pid.c gave a function that does what we need. The function accepts a task for which we need to change the PID, type of PID and, in fact, the new PID. The function is creating a new PID. This function accepts only the namespace in which the new PID will be located, this space can be obtained using . But there is one problem: these kernel symbols are not exported by the kernel and cannot be used in modules. A wonderful article helped me solve this problem . The find_sym function code is taken from there.
    static void __change_pid(struct task_struct *task, enum pid_type type,
    struct pid *new)


    struct pid *alloc_pid(struct pid_namespace *ns)

    task_active_pid_ns


    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 another process took, like PID 1? Add code after allocation

    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 generated an error due to which task switching on the% n command will not work, but all other functions work fine.

    Interesting features of processes with a modified PID


    PID 0: cannot log in


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

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

    My parent PID284
    My PID 1517
    My new PID 0

    It turns out PID 0 is not so special? Rejoice, write exit and ...



    The core is falling! The kernel defined our task as IDLE TASK and, upon completion, simply crashed. Apparently, before the end of our program should return to itself a "normal" PID.

    Invisibility process


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

    We 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 took a free PID. There is no previous PID in / proc either, which is very strange. We may be in a different namespace and therefore not visible to the main / proc. 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, there is no our process, which means that we are in the usual namespace. Let's 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 in the list.

    Github link

    Also popular now: