Техника сетевых атак
Шрифт:
·
· Dump of file NETBIOS.DLL
·
· File Type: DLL
·
· Summary
·
· 1000 bss
· 1000 data
· 1000 edata
· 1000 idata
· 1000 rdata
· 1000 reloc
Процессы UNIX могут исполняться в одном из двух режимов - режиме задачи и режиме ядра. Для обеспечения безопасности каждым из режимов используется свой собственный стек. Возникает вопрос, - каким образом системная функция получает аргументы, если они остаются в стеке задачи?
Понять это можно, разобравшись в механизме переключения из режима задачи в режим ядра. В UNIX для перехода в привилегированный режим используются прерывания. Инструкция, вызывающая прерывание, автоматически переводит процессор в привилегированный режим и передает управление подпрограмме обработки прерывания. Существует специальная таблица прерываний (доступная только ядру операционной системы) в которой индекс каждой ячейки численно равен номеру прерывания и содержит адрес, на который будет передано управление в случае возникновения данного прерывания. Более подробно о прерываниях можно прочитать в любой толковой книге по ассемблеру и технической документации процессора.
Врезка «замечание»
В операционной системе LINUX для вызова системных функций используется прерывание 0x80, а в операционных системах, совместимых с System V для той же цели необходимо передать управление по фиксированному адресу 0007:00000000 (сегмент семь, смещение ноль). Номер вызываемой функции и передаваемые ей аргументы задаются в регистрах (в LIUX) или заталкиваются в стек (в системах, совместимых с System V).
Таким образом, использование прерываний (или фиксированного адреса) позволяет пользовательской задаче передать управление только на предусмотренные ядром подпрограммы, а не произвольный адрес памяти. Однако стек ядра прикладному коду не доступен, и передать аргументы функции обычным путем невозможно. Тем не менее, ядру доступно пространство памяти всех задач и оно в состоянии «вытащить» требуемые параметры самостоятельно. Конкретная реализация зависит от выбранной аппаратной платформы и поэтому не будет рассмотрена. Достаточно понять - прикладные программы не могут пагубно воздействовать на ядро (конечно при отсутствии в нем ошибок реализации).
В операционных системах наподобие MS-DOS (и первых версиях UNIX) существовала возможность обращаться с оборудованием в обход операционной системы, манипулируя непосредственно с портами ввода-вывода [112]. Современные процессоры при попытке пользовательского кода обратиться к порту, генерируют исключение, передавая управление операционной системе, предоставляя ей возможность самой расправиться со злоумышленником. В результате, доступ может быть отвергнут, а приложение, нарушившие субординацию - закрыто, или же ядро может эмулировать чтение (запись) в порт, не выполняя ее на самом деле.
На бумаге броне UNIX позавидовал бы любой крейсер средних размеров, но в действительности все не так гладко [113]. Многие системы оказались взломаны «благодаря» умению UNIX в аварийных ситуациях сбрасывать дамп памяти (core dump– на жаргоне русскоязычных программистов звучащий кора) в общедоступный файл на диск. Достаточно часто в нем удается обнаружить пароли или другую информацию, облегчающую проникновение в систему. Приверженцы UNIX уверяют, - уязвимость устраняется правильным администрированием. Но сколько на свете существует неопытных администраторов?
Врезка «замечание» *
У Кена Томпсона есть автомобиль, который он помогал сконструировать. В отличие от большинства автомобилей, у него нет ни спидометра, ни указателя бензина, никаких из тех многочисленных лампочек, которые отравляют жизнь современному водителю. Взамен, если водитель делает ошибку, гигантский вопросительный знак загорается в центре приборной доски. "Опытный водитель", говорит Томпсон, "обычно узнает, что не так". Аноним
Впрочем, ситуация действительно исправляется правильным администрированием системы и скорее относится к разряду проблем социальных (где найти каждому компьютеру хорошего администратора?) и психологических (оставлю-ка я все настойки по умолчанию!), не представляя никакой технической проблемы.
Хуже обстоит дело с разделяемыми областями памяти и именованными каналами, - то есть средствами межпроцессорного взаимодействия. Ведь система, в которой не существует никаких механизмов обмена данными между процессами, - никому не нужна. А если UNIX поддерживает механизмы межпроцессорного взаимодействия, не приводит ли это к нарушению политики безопасности?
Успех UNIX в частности объяснялся наличием удобного и простого средства межпроцессорного взаимодействия - конвейера (позаимствованного из операционной системы DTSS - Dartmouth time-sharing System), подробно описанного в главе «Устройство конвейера и перенаправление ввода-вывода». Но таким способом могли общаться между собой лишь родственные процессы, и это сильно ограничивало возможные области применения (впрочем, существовали и так называемые, именованные каналы, доступные всем остальным процессам).
В UNIX System V появился пакет IPC (interposes communication), значительно расширяющий возможности межпроцессорного взаимодействия. Поддерживались: механизм передачи сообщений, разделяемая память и семафоры, необходимые для синхронизации процессоров. Все трое могли взаимодействовать с любыми, не обязательно родственными процессами, поэтому остро стал вопрос обеспечения безопасности.
Каждый совместно используемый объект (например, регион памяти) связан со структурой данных, описывающей права доступа и перечисляющей пользовательские и групповые коды идентификации. Права доступа в чем-то сходны с атрибутами файлов, - можно выборочно разрешать запись, чтение, назначаемые как отдельным пользователям, так и целым группам. При условии отсутствия ошибок реализации такая система выглядит внутренне не противоречивой и как будто бы надежа.
На самом деле программисты частенько беспечно относятся к установке атрибутов защиты и предоставляют доступ к разделяемой памяти (возможно содержащей приватные данные) любому процессу в системе. Атаки такого рода мало распространены и не представляют большого интереса для взломщиков, поскольку их возможности весе же очень ограничены.
В худшем положении оказываются разработчики ядра, вынужденные выполнять многочисленные и не всегда очевидные проверки. Например, область разделенной памяти, подключенная к адресному пространству одного из процессоров, может оказаться расположенной слишком близко к стеку. Если стек вырастет настолько, что пересечет границу разделяемой памяти, произойдет фатальная ошибка памяти, а на некоторых аппаратных платформах данные, заносимые в стек, игнорируют защиту от записи!