GNU Core Utilites — это набор утилит для выполнения базовых пользовательских операций: создание директории, вывод файла на экран и так далее. По замыслу разработчиков, эти утилиты должны быть доступны в любой операционной системе, что мы и наблюдаем в настоящее время: для Windows есть Cygwin, ну а про *nix и говорить нечего. Сохранить единообразность работы в разных системах помогает стандарт POSIX, который в Coreutils пытаются соблюдать. Coreutils содержит такие часто используемые утилиты, как cat, tail, echo, wc и многие другие.
Для начала выберем самую тривиальную программу под названием yes. Её простота позволит разобраться с используемыми в Coreutils инструментами и библиотеками.
Утилита yes
Как говорится в мане, всё что умеет утилита yes — это бесконечно выводить «yn» в stdout. Если мы передадим yes какие-то аргументы, то вместо «y» yes будет выводить аргументы через пробел. Наверняка похожую программу писал каждый, кто начинал изучать C. А значит у многих есть возможность сравнить свой подход с тем, как это делают суровые бородатые дядьки из GNU. О практическом применении yes немного написано в Википедии.
Ubuntu 22.04 — Установка программ из исходных кодов
Исходный код
- Coreutils: git clone git://git.sv.gnu.org/coreutils
- Gnulib (заглянем туда пару раз): git clone git://git.savannah.gnu.org/gnulib.git
Coding style
Первое, на что обращаешь внимание — непривычное форматирование кода. Почитать о нём можно в соответствующей главе GNU Coding Standards. Например, при определении функции тип возвращаемого значения должен располагаться на отдельной строке, как и открывающая скобка:
int main (int argc, char **argv)
Для отступов и выравнивания используются только пробелы. Между различными уровнями вложенности разница в отступе составляет 2 пробела. Особо извращённую форму имеют фигурные скобки при операторах:
if (x < foo (y, z)) haha = bar[4] + 5; else < while (z) < haha += foo (z, z); z—; >return ++x + bar (); >
12 строк
yes.c начинается с обязательного для всех GPL-програм комментария. Он уже успел намозолить мне глаза в других программах и необходимость его наличия была для меня загадкой. Оказывается, что текст этого комментария зафиксирован в инструкции по применению GPL.
Именно в ней прописано, что все, кто желает выпускать своё ПО под GPL, должны добавлять эти 12 строк заявления о праве копирования в начало каждого файла исходного кода.
initialize_main
Первое, что делает программа, это вызов initialize_main . Эта функция предназначена для того, чтобы программа выполнила свои специфичные действия над аргументами. На практике, в Coreutils нет ни одной утилиты, которая бы использовала эту функцию для чего-то полезного. Везде используется заглушка, представленная в файле coreutils/src/system.h :
#ifndef initialize_main # define initialize_main(ac, av) #endif
Название программы
- Официальное название, которое пользователь не может изменить.
- Реальное название исполняемого файла.
Причём это название никак не зависит от имени исполняемого файла:
5 причин научиться собирать софт из исходников
Такое поведение обеспечивается специально определённым в начале файла макросом PROGRAM_NAME :
/* The official name of this program (e.g., no `g’ prefix). */ #define PROGRAM_NAME «yes»
Реальное название без всяких хитростей берётся из argv[0] и используется при выводе ошибок и подсказок:
Значение argv[0] помещается в глобальную переменную program_name с помощью вызова функции set_program_name во второй строке main :
set_program_name (argv[0]);
Функция set_program_name предоставляется библиотекой Gnulib. Соответствующий код находится в каталоге gnulib/lib/ , в файлах progname.h и progname.c . Интересно заметить, что set_program_name не просто сохраняет значения argv[0] в глобальную переменную program_name , объявленную в progname.h , но и выполняет дополнительные преобразования, связанные с тонкостями использования GNU Libtool, инструмента для разработки динамических библиотек.
Интернационализация
Coreutils используют по всему миру, поэтому во всех утилитах предусмотрена возможность локализации. Причём эта возможность обеспечивается минимальными усилиями благодаря использованию пакета GNU gettext. Немногих удивит использование именно gettext, ведь этот пакет распространился далеко за пределы проекта GNU. Например, интернационализация в моём любимом web-фреймворке Django построена именно на gettext. Про использование gettext совместно с различными языками и фреймворками уже писали на хабре.
Замечательным свойством gettext является то, что он во всех языках используется примерно одинаково, и C не исключение. Здесь есть стандартная магическая функция _ , использование которой можно найти в функции usage :
void usage (int status)
Определение функции _ находится в уже знакомом нам файле system.h :
#define _(msgid) gettext (msgid)
Инициализация механизма интернационализации в Coreutils производится вызовом трёх функций в main :
setlocale (LC_ALL, «»); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE);
- setlocale устанавливает стандартную локаль окружения в качестве рабочей для приложения
- bindtextdomain говорит, где искать файл с переводами для конкретного домена сообщений
- textdomain устанавливает текущий домен сообщений
Обработка ошибок
Двигаясь дальше по коду main , мы встречаем такую строку:
atexit (close_stdout);
Интуитивно можно подумать, что в функции close_stdout закрывается стандартный поток вывода, что исключает потерю данных, если мы подменили stdout каким-нибудь файловым дескриптором и используем буферизированный вывод. Но найти исходный код этой функции и понять, что же на самом деле там происходит, выполняются ли какие-нибудь дополнительные действия по подчистке ресурсов, у меня не получилось.
Аргументы командной строки
Это последний вопрос, который не касается работы самой программы. Здесь, как и в случае с интернационализацией, используется проверенное временем и пролезшее во многие проекты (например, в Python) решение — модуль getopt. Этот модуль очень прост: фактически, от разработчика требуется вызывать в цикле одну из функций getopt или getopt_long . Подробнее о getopt можно почитать в интернете, да и на хабре о нём тоже писали.
В Gnulib есть специальная функция parse_long_options для обработки аргументов —version и —help , которые любое GNU-приложение обязано поддерживать. Находится она в файле gnulib/lib/long-options.c и использует getopt_long в своей работе.
Исходный код yes является классным примером работы с getopt. Тут одновременно отсутствует излишняя для обучения сложность с разбором десятков аргументов и присутствует использование всех средств getopt. Сначала, естественно, выполняется вызов parse_long_options . Затем проверяется, что больше никаких опций-ключей не передано и остальные аргументы, если они есть, являются просто произвольными строками:
parse_long_options (argc, argv, PROGRAM_NAME, PACKAGE_NAME, Version, usage, AUTHORS, (char const *) NULL); if (getopt_long (argc, argv, «+», NULL, NULL) != -1) usage (EXIT_FAILURE);
Следующий код можно перевести на русский так: «Если в списке аргументов командой строки ничего кроме ключей —version и —help не было, то мы будем выводить „y“ в stdout»:
if (argc
Запись в argv[argc] не является ошибкой: стандарт ANSI C требует, чтобы элемент argv[argc] был нулевым указателем.
Главный цикл
Ну вот мы и добрались до самого функционала программы. Вот он весь, как есть:
while (true)
Здесь можно отметить, что все действия выполняются внутри условия if , а не в его теле. Значит, Кёрниган и Ритчи не врали, когда писали, что опытный C-программист реализует копирование строк так:
while (*dst++ = *src++) ;
Источник: habr.com
Исходные коды программ линукс
Комментарии
Популярные По порядку
Не удалось загрузить комментарии.
ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ
Лучший хакерский курс с Kali Linux на русском языке
Если нужно получить доступ к серверу, поправить работоспособность чужой сети или побаловаться с чьим-то сайтом – Kali Linux вам в помощь.
51 задание для оттачивания навыков системного администрирования
Разбираемся с тем, как стать системным администратором, и выполняя реальные задачи, получаем основные знания для работы и развития.
10 лучших ресурсов для изучения хакинга с помощью Kali Linux
Подборка 10 отличных ресурсов для изучения хакинга с помощью Kali Linux. Изучение данных материалов поможет вам раскрыть свои хакерские способности.
Источник: proglib.io
Кодим в ядре Linux`а №1
Много лет тому назад, когда наша эра только-только начиналась (когда был создан первый персональный компьютер ;-)), хакеры запускали свои бэкдоры дающие шелл и т.д. и называли эти проги названиями типа top, ps, nfsd, etc для того, чтобы сисоп когда смотрел список работающих процессов думал, что эти проги нормальные системные утилиты и игнорировал их. Но технический прогресс не стоял на месте и эту фишку всё чаще и чаще стали просекать. Тогда хаксоры стали модифицировать такие системные утилиты как ps 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 файлы: сначала должны быть
и другие при необходимости.
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;
>
Функция 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,
чтобы она стучала ему на мыло когда кто-то загружает модуль!
О более продвинутых методах загрузки, обнаружения и скрытия модулей мы поговорим в следующий раз.
Источник: xakep.ru