Спецвыпуск журнала «Хакер» 47, октябрь 2004 г.
Спецвыпуск журнала «Хакер» 47, октябрь 2004 г. читать книгу онлайн
Электронная версия известного компьютерного журнала
Внимание! Книга может содержать контент только для совершеннолетних. Для несовершеннолетних чтение данного контента СТРОГО ЗАПРЕЩЕНО! Если в книге присутствует наличие пропаганды ЛГБТ и другого, запрещенного контента - просьба написать на почту [email protected] для удаления материала
Но в результате разделения аппаратно-зависимых и аппаратно-независимых компонентов ядра разработчикам удалось добиться того, что большая часть ядра может быть перенесена на любую платформу.
Остается малая, но аппаратно-зависимая часть, которая включает следующие компоненты:
– запуск и инициализация системы на низком уровне;
– первичная обработка внутренних и внешних прерываний;
– управление памятью;
– переключение между режимами пользователя и ядра;
– части драйверов, связанные с особенностями аппаратуры.
Как видно, в зависимой части осталось лишь небольшое число функций, которые переписывают при переносе ОС на другую платформу.
Давай теперь рассмотрим основные функции и подсистемы ядра. При включении выполняется инициализация системы. Эта функция занимается запуском и раскруткой. Средство раскрутки загружает полное ядро в память и запускает систему. Следующая функция – управление памятью, которая отображает виртуальную память процессов в оперативку. Кроме того, этот компонент обеспечивает использование одних и тех же областей оперативки для разных процессов с использованием внешних носителей. Основными подсистемами ядра являются подсистема управления файлами и подсистема управления процессами. Остановимся на них поподробнее, поскольку эти системы являются основным источником уязвимостей ядра.
В UNIX каждому файлу в соответствие ставится некий индекс, в котором содержатся описание размещения информации на физическом носителе, права доступа, владелец и другие данные. Каждый файл имеет только один индекс. Когда процесс обращается к файлу по имени, ядро возвращает индекс файла. То есть каждое имя является указателем.
Индексы хранятся в файловой системе, однако при работе с файлом ядро заносит их в таблицу индексов, которая находится в ОЗУ. Кроме таблицы индексов, ядро использует еще две информационные структуры: таблицу файлов и таблицу дескрипторов файла. Пользователь может получить доступ к файловым дескрипторам и раскрыть информацию.
Итак, основные компоненты файловой системы:
– Блок загрузки. Располагается в начале файловой системы и содержит программу начальной загрузки.
– Суперблок. Здесь обозначаются свойства файловой системы: размер, расположение свободного пространства, количество файлов и другая информация.
– Список индексов. Размер списка указывается администратором при генерации файловой системы.
– Информационные блоки. Содержат данные файлов, а также служебные данные. Информационный блок может принадлежать только одному файлу.
После того как загрузка ядра выполнена, нужно как-то создавать, завершать и следить за существующими процессами и нитями (здесь нить – это «процесс», выполняемый на общей виртуальной памяти). Этим занимается функция управления процессами и нитями. Ввиду мультипроцессорности *nix ядро обеспечивает разделение процессорного времени или процессоров, что создает эффект параллельности выполнения разных задач. Ядро – это невыгружаемый компонент, и поэтому процесс, выполняющийся в режиме ядра, продолжает свое выполнение до тех пор, пока не вернется в режим задачи либо пока не перейдет в состояние «сна». Благодаря невыгружаемости ядро обеспечивает целостность информационных структур и стабильность работы.
Кроме обозначенных подсистем, существуют также коммуникационные средства, которые отвечают за обеспечение обмена данными. Ну и замыкающей функцией является программный интерфейс, который делает возможным доступ к ядру из более высокого уровня (со стороны пользовательских процессов).
Как видно из вышесказанного, архитектура ОС в целом и архитектура ядра в частности – это стройная, хорошо продуманная система взаимодействия компонентов. Однако несмотря на это любая *nix – уязвимая система. В том числе и на самом нижнем уровне – ядре. Одна из основных причин уязвимостей ядра – возраст ОС. С одной стороны, клоны этой операционной системы становятся популярнее день ото дня в течение 25 лет, и это уникальный случай! Кроме того, на протяжении этих лет наращиваются и возможности системы, что является большим плюсом. Однако качественные улучшения структуры не успевали (и не успевают) за ростом ее возможностей. И поэтому можно утверждать, что современные варианты UNIX структурированы не идеально. Рассмотрим основные типы уязвимостей.
Одна из самых распространенных уязвимостей программного обеспечения и ОС в частности. Эту уязвимость вызывает небольшая ошибка, позволяющая, однако, творить чудеса. Ошибка переполнения буфера случается, если в программе происходит копирование данных без проверки свободного места в пункте назначения (буфере). Когда данных слишком много, происходит переполнение и информация попадает за границы буфера. Умелое использование этого факта позволяет запускать произвольный код с правами переполненного приложения (то есть вполне может быть, что с правами администратора). Существует огромное количество такого рода уязвимостей. Главная причина уязвимости – использование некоторых функций стандартной библиотеки языка С, не проверяющих размеры своих аргументов (например strcpy, strcat, gets или sprintf), а *nix-системы (в том числе и большая часть ядра), как ты помнишь, почти целиком написаны на C. Актуальность этой уязвимости доказывает хотя бы последний найденный баг. В UNIX 9.x найдены множественные переполнения буфера в функциях strcpy() и p_stcopy(), позволяющие локальному пользователю переписывать в стеке значение регистра eip, что может привести к выполнению произвольного кода c root-правами (см. www.securitylab.ru).
Данная проблема характерна как для *nix, так и для Windows. В никсах эта дырка обнаруживается в ядре и не имеет такого широкого распространения, как переполнение буфера. Правильно используя данную уязвимость, можно изменять файлы в системе. С первого взгляда кажется, что такая возможность не представляет особой ценности, однако подобным образом могут быть получены повышенные привилегии при помощи модификации критических файлов типа /etc/passwd и др.
Если вышеперечисленные проблемы ядра носят «хронический» характер, то следующие уязвимости – разовые, характерные для определенного клона и его версии:
1) таблица перенаправления может быть подменена удаленными пользователями, если посылать пакеты с подделанным исходным адресом;
2) /proc/tty/driver/serial раскрывает точное число введенных символов через последовательные ссылки. В результате локальный атакующий может определить длину пароля и задержку между нажатиями клавиш в течение ввода пароля;
3) локальный пользователь может эксплуатировать уязвимость состояния операции чтения файла в системном вызове execve(), чтобы аварийно завершить работу системы;
4) уязвимость в программе обработки TCP-опций входящих пакетов. Причем уязвимость действительна, если в правилах встроенного фаервола применяется tcp-option. Во всем виновата функция tcp_find_option, которая некорректно обрабатывает поле длины пакета. Если это значение больше 127, программа зацикливается. Таким образом, можно исчерпать системные ресурсы и вызвать отказ в обслуживании (DoS).
Итак, мы видим, что ядра систем *nix уязвимы. Некоторые уязвимости возникают из-за непосредственных ошибок при реализации. Другие – плоды изначально неправильной структуры ядра.
При выполнении операций над группой файлов не пытайся использовать шаблон «*.*», как в Windows, поскольку в файловой системе *nix отсутствует понятие расширения файла.
Если встретишь файл с именем «name1.name2.name3.etc», не паникуй – это вполне допустимое имя файла, не противоречащее правилам образования имен в UNIX.
Общее название командных интерпретаторов – shell, поскольку они являются «оболочкой» ядра.