Pull to refresh

Меняем PID процесса в Linux с помощью модуля ядра

Reading time 4 min
Views 11K
В этой статье мы попытаемся создать модуль ядра, способный изменить PID уже запущенного процесса в ОС Linux, а так же поэкспериментировать с процессами, получившими измененный PID.



Предупреждение: смена PID — нестандартный процесс, и при определенных обстоятельствах может привести к панике ядра.

Наш тестовый модуль будет реализовывать символьное устройство /dev/test, при чтении с которого процессу будет изменен PID. За пример реализации символьного устройства спасибо этой статье. Полный код модуля приведен в конце статьи. Конечно, самым правильным решением было добавить системный вызов в само ядро, однако это потребует перекомпиляцию ядра.

Окружение


Все действия по тестированию модуля выполнялись в виртуальной машине VirtualBox с 64 битным дистрибутивомLInux и версией ядра 4.14.4-1. Связь с машиной осуществлялась с помощью SSH.

Попытка #1 простое решение


Пару слов о current: переменная current указывает на структуру task_struct с описанием процесса в ядре(PID, UID, GID, cmdline, namespaces и т.д)

Первой идеей было просто поменять параметр current->pid из модуля ядра на нужный.

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);
 ,,,
}

Для проверки работоспособности модуля я написал программу на 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);
}

Загрузим модуль коммандой insmod, создадим /dev/test и попробуем.

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

PID не изменился. Возможно, это не единственное место, где указывается PID.

Попытка #2 дополнительные поля PID


Если не current->pid является идентификатором процесса, то что является? Быстрый просмотр кода getpid() навел на структуру task_struct, описывающую процесс Linux и файл pid.c в исходном коде ядра. Нужная функция — __task_pid_nr_ns. В коде функции встречается обращение task->pids[type].pid, этот параметр мы и изменим

Компилируем, пробуем



Так как тестировал я по SSH, мне удалось получить вывод программы до падения ядра:

My parent PID 293
My PID 1689
My new PID 1689

Первый результат, уже что-то. Но PID все равно не изменился.

Попытка #3 не экспортируемые символы ядра


Более внимательное изучение pid.c дало функцию, которая делает то, что нам нужно
static void __change_pid(struct task_struct *task, enum pid_type type,
struct pid *new)

Функция принимает задачу, для которой надо изменить PID, тип PID и, собственно, новый PID. Созданием нового PID занимается функция
struct pid *alloc_pid(struct pid_namespace *ns)

Эта функция принимает только пространство имен, в котором будет находиться новый PID, это пространство можно получить с помощью task_active_pid_ns.
Но есть одна проблема: эти символы ядра не экспортируются ядром и не могут использоваться в модулях. В решении этой проблемы мне помогла замечательная статья. Код функции find_sym взят оттуда.

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);
 ...
}

Комплируем, запускаем

My parent PID 299
My PID 750
My new PID 751

PID изменен! Ядро автоматически выделило нашей программе свободный PID. Но можно ли использовать PID, который занял другой процесс, например PID 1? Добавим после аллокации код

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

Комплируем, запускаем

My parent PID 314
My PID 1172
My new PID 1

Получаем настоящий PID 1!



Bash выдал ошибку, из-за которой не будет работать переключение задач по комманде %n, но все остальные функции работают отлично.

Интересные особенности процессов с измененным PID


PID 0: войти нельзя выйти


Вернемся к коду и изменим PID на 0.

newpid->numbers[0].nr = 0;
Комплируем, запускаем

My parent PID284
My PID 1517
My new PID 0

Выходит PID 0 не такой и особенный? Радуемся, пишм exit и…



Ядро падает! Ядро определило нашу задачу как IDLE TASK и, увидев завершение, просто упало. Видимо, перед завершением наша программа должна вернуть себе «нормальный» PID.

Процесс-невидимка


Вернемся к коду и выставим PID, гарантированно не занятый
newpid->numbers[0].nr = 12345;

Комплируем, запускаем

My parent PID296
My PID 735
My new PID 12345

Посмотрим, что находится в /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

Как видим /proc не определяет наш процесс, даже если мы заняли свободный PID. Предыдущего PID тоже нет в /proc, и это весьма странно. Возможно, мы находимся в другом пространстве имен и поэтому не видны основному /proc. Смонтируем новый /proc, и посмотрим что там

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

По прежнему нашего процесса нет, а значит мы в обычном пространстве имен. Проверим

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

Только один bash, с которого мы и запускали программу. Ни предыдущего PID, ни текущего в списке нет.

Ссылка на github
Tags:
Hubs:
+28
Comments 20
Comments Comments 20

Articles