Автор: (c)Крис Касперски ака мыщъх
Дополнительные защитные средства (такие как антивирусы, персональные брандмауэры, etc) зачастую сами становятся объектом атаки и вместо обещанного рекламой усиления защиты мы получаем новые дыры, одна из которых была обнаружена David'ом Matousec'ом (основателем и руководителем одноименной исследовательской компании - Matousec-Transparent security, www.matousec.com).
15 сентября 2006 года, экспериментируя с версией 9.1.0.33, он посылал псевдоустройству '\Device\SymEvent' (созданному брандмауэром) различные IOCTL-запросы, которых оно, признаться, не ожидало и от удивления высадило систему на полный BSOD. David уведомил производителя об ошибке, которая была исправлена в следующей версии и зафиксирована в базе Security Focus под номером BID 20051 (множественные локальные отказы в обслуживании в драйвере SymEvent) но(!) в версии 9.1.1.7 разработчики вернули ошибку на место, что привело к возможности обрушения системы древним exploit'ом! Подробности об этом инциденте можно найти на: www.matousec.com/info/advisories/Norton-Insufficient-validation-of-SymEvent-driver-input-buffer.php и http://www.securityfocus.com/bid/22961/info.
Уязвимость впервые обнаружена в версии 9.1.0.33 и "реинсценирована" в версии 9.1.1.7, промежуточные версии выпущены без этой ошибки.
Исходный текст exploit'а на языке Си лежит на сервере компании Matousec: www.matousec.com/downloads/windows-personal-firewall-analysis/BTP00011P002NF.zip, а ниже приведен его ключевой фрагмент:
Листинг 1. Ключевой фрагмент exploit'а David'а Matousek'а.
Использовать стабильные версии между 9.1.0.33 и 9.1.1.7.
Рисунок 1. Страничка компании Matousec - Transparent security, обнаружившей уязвимость в Norton Personal Firewall
Unique RAR File Library представляет собой бесплатную кросс-платформенную библиотеку, распространяемую в исходных текстах (http://www.unrarlib.org/) и как легко догадаться из ее названия, позволяющую сторонним программистам создавать независимые утилиты для распаковки RAR-архивов или интегрировать библиотечный код в свои собственные продукты, обеспечивая его "прозрачную" поддержку. К сожалению, библиотека не свободна от ошибок и некоторые из которых носят характер критических, как например, ошибка переполнения в имени файла, обнаруженная хакером по кличке starcadi (starcadi@autistici.org) и описанная на http://securityvulns.com/news/Unrarlib/BO.html.
Суть ошибки состоит в том, что имя распаковываемого файла копируется в локальный буфер фиксированного размера длиной в 255 байт (см. листинг 2), что (вместе с путем) является пределом для Windows и файл с более длинным именем ни создать, ни открыть не удаться, но в архиве длина имени файла ограничена только длиной самого архива (то есть, не ограничена вообще!). Естественно, такой архив нельзя создать легальным путем с помощью самого RAR'а, но что мешает хакеру "смастерить" архив самостоятельно (формат-то известен!) или "надругаться" над уже существующим?! При этом мы получаем классическое переполнение стека с возможностью передачи управления на shell-код (содержащийся в имени файла) и захвату управления машиной (естественно, на процессорах с поддержкой NX/XD флагов и задействованным аппаратным DEP на Windows или аналогичными защитными средствами из мира UNIX, делающих стек неисполняемым и использующих рандомизацию адресного пространства, атака окажется чрезвычайно затруднена, но все-таки возможна! Впрочем, это тема для совсем другого разговора).
Передав такой архив по сети, мы одним взмахом руки превращаем локальную уязвимость в удаленную, причем если антивирус или другое программное обеспечение использует библиотеку unrarlib для автоматической проверки "пролетающих" архивов (а оно ее использует), то жертве вообще не требуется совершать никаких действий!!!
Листинг 2. Фрагмент библиотеки unrarlib, содержащий уязвимость.
На данный момент уязвимость подтверждена в версии 0.4.0, про другие версии пока ничего неизвестно, также отсутствует перечень продуктов сторонних разработчиков, использующих библиотеку unrarlib в своих проектах.
Готовые exploit'ов в дикой природе еще не обнаружены, но используя "легальный" RAR-архив, длину файла можно легко увеличить с помощью hiew'а (или любого другого hex-редактора) и с его же помощью вбить туда боевой shell-код.
Да поможет нам Аллах!
Рисунок 2. Здесь раздают unrarlib.
small http (www.smallsrv.com) - надежный, проворный и чрезвычайно компактный http.ftp/proxy/pop3/smtp/dns/dhcp-сервер в одном флаконе (а для граждан бывшего СНГ к тому же еще и бесплатный). сейчас он стоит в норе у мыщъх'а, сменив War FTP-сервер, и мыщъх, естественно, следит за его безопасностью, шурша логами и добавляя в Black-List все новые IP-адреса, обладатели которых - конкретные крысы или просто всякие там поисковые машины, качающие все без разбору и напрягающие канал, досаждая не только мыщъху, но и всем остальным посетителям. Так вот, после добавления нового IP в "Deny-IP" сервер через некоторое время вылетел в soft-ice, который мыщъх держит всегда запущенным, демонстрируя ситуацию типичного переполнения.
Анализ показал, что последний внесенный в Black-List IP (87.250.254.249), принадлежащий yandex'у, был усечен сервером до 87.250.254, и при попытке подключения с адреса 87.250.254.xxx, у small http что-то "перемкнуло" внутри парсера IP-адресов и возникло необработанное исключение, отловленное айсом. Дело кончилось тем, что мыщъх, отправив разработчику уведомление об ошибке, перенес Black-List на персональный брандмауэр (ну, типа workaround такой).
Эксперименты со списком блокируемых адресов быстро опровергли первоначальную гипотезу о фиксированной длине поля Deny-IP и не позволили установить условия, при которых происходит "усечение" последнего введенного адреса. Судя по всему, помимо количества IP-адресов, тут присутствуют еще и другие факторы.
Уязвимость обнаружена в версии 3.05.64, о других мыщъх'у ничего не известно.
Фрагмент конфигурационного файла с "черным списком" IP-адресов, на которых наблюдается устойчивое воспроизведение ошибки (вместе с кратким описанием ситуации) лежит в мыщъхиной норе по адресу http://nezumi.org.ru/souriz/hack/http.cf_.
Блокировать IP-адреса на брандмауэре.
Рисунок 3. Поле Deny-IP со списком блокируемых адресов, подверженное переполнению.
20 февраля 2007 года сотрудники лаборатории CoreLabs Advisory (подразделение компании Core Security Technologies), обнаружили, что при получении фрагментированного IPv6-пакета OpenBSD, воздвигнутая в конфигурации по умолчанию, выпадает в kernel panic. На следующий день разработчикам системы был выслан proof-of-concept exploit, демонстрирующий удаленный отказ в обслуживании, успешно подтвержденный и довольно оперативно исправленый заплаткой, выпущенной 26 февраля, однако статуса уязвимости ("vulnerability") ошибке так и не присвоили, поскольку по мнению OpenBSD-team'а, отказ в обслуживании - это не дыра, а так... просто мелкая неприятность, о которой пользователям знать совершенно необязательно.
Рисунок 4. "Только одна удаленная дыра в конфигурации по умолчанию более чем за 8 лет эксплуатации".
Такое положение дел разозлило парней из CoreLabs и они ценой недели беспощадных исследований (пиво, сигареты, трава, чай, кофе и свечи от геморроя впридачу) доказали возможность удаленного захвата управления, выпустив 5 марта "боевую" версию explout'а с shell-кодом на борту, от которого разработчикам OpenBSD было уже не отвертеться, и вся последующая неделя ушла на переписку с CoreLabs, подготовившей за это время развернутый отчет по безопасности, опубликованный ими на собственном сайте http://www.coresecurity.com/?action=item&id=1703. 13 марта координатор проекта Theo de Raadt переслал его на Bugtraq (http://archives.neohapsis.com/archives/openbsd/2007-03/0417.html), откуда он разошелся по другим сайтам, прямо или косвенно связанных с безопасностью.
Это вторая дыра в OpenBSD, обнаруженная за последние 10 лет "промышленной" эксплуатации (предыдущая сидела в демоне SSH), так что ее открытие можно назвать весьма эпохальным событием, привлекающим к себе внимание и вызывающим желание как следует во всем разобраться.
Рисунок 5. "Всего лишь все удаленных дыры в конфигурации по умолчанию более чем за 10 лет эксплуатации".
Уязвимости подвержены следующие версии: OpenBSD 4.1, OpenBSD 4.0 Current, OpenBSD 4.0 Stable, OpenBSD 3.9, OpenBSD 3.8, OpenBSD 3.6 и OpenBSD 3.1.
Исходный текст exploit'а можно найти в отчете CoreLabs, доступном по адресу http://www.coresecurity.com/?action=item&id=1703. Он написан на Питоне и требует библиотеки Impacket, используемой для создания сырых (raw) сокетов и доступной для бесплатного скачивания по адресу http://oss.coresecurity.com/projects/impacket.html. Shell-код состоит из одной-единственной инструкции INT 03h (точка останова, вызывающая всплытие отладчика) и последующей за ней командами балансировки ESP и возврата внутрь ядра. Фрагментированный IPv6-пакет засовывается внутрь ICMP-пакета с полем type, равным 128 (ICMP echo request), который должен быть послан злоумышленником непосредственно по локальной сети, либо через какой-нибудь тоннель “IPv6 over IPv4”, в противном случае удаленная атака не состоится и хакер склеит ласты, а он их непременно склеит, т.к. популярность протокола IPv6 еще долгое время будет оставаться на уровне чуть выше абсолютного нуля, во всяком случае в глобальном масштабе.
Заплатки для OpenBSD 4.0 и 3.9 доступны по следующим адресам: ftp://ftp.openbsd.org/pub/OpenBSD/patches/4.0/common/010_m_dup1.patch и ftp://ftp.openbsd.org/pub/OpenBSD/patches/3.9/common/020_m_dup1.patch.
Версия 4.1 залатана непосредственно в исходном коде и отдельной заплатки для нее нет.
Как вариант, можно не устанавливать заплатку, а заблокировать весь IPv6-трафик на встроенном в OpenBSD брандмауэре (естественно, при условии, что он не нужен), для этого необходимо выполнить следующую последовательность действий:
# добавить следующую строку в файл /etc/pf.conf: block in quick inet6 all # загрузить обновленный pf.conf # внутрь запущенного PF посредством утилиты pfctl pfctl -f /etc/pf.conf # разрешить его использование pfctl -e -f /etc/pf.conf # посмотреть текущий статус # на предмет проверки успешности принятия нового правила pfctl -s rules
Листинг 3. Блокирование всего IPv6-трафика на встроенном брандмауэре.
Рисунок 6. Патч для OpenBSD, исправляющий ошибку.
Чтобы разобраться в дыре основательно и (самое главное!) самостоятельно, необходимо иметь установленную OpenBSD, а поскольку такой внутри мыщъхиной норы и не обозначилось, пришлось курить исходные тексты. Но исходные тесты OpenBSD - это же очень сильно до хрена и курить ее можно целый сезон, а она даже не убавится. Не-е-ет... Тут нужно мыслить стратегически и действовать по плану!
Сетевой стек - весьма масштабное сооружение и заблудиться в нем намного проще, чем понять его устройство хотя бы в самых общих чертах, да нам это, собственно, и не нужно, поскольку чтобы локализовать ошибку в миллионах строк программного кода, достаточно просто взглянуть на патч, представляющий собой простой diff-файл, возникший в результате сравнения двух версий: исправленной и старой:
--- sys/kern/uipc_mbuf2.c 17 Mar 2006 04:15:51 -0000 1.24 +++ sys/kern/uipc_mbuf2.c 7 Mar 2007 19:21:48 -0000 1.24.2.1 @@ -226,16 +226,14 @@ m_dup1(struct mbuf *m, int off, int len, { struct mbuf *n; int l; - int copyhdr; if (len > MCLBYTES) return (NULL); if (off == 0 && (m->m_flags & M_PKTHDR) != 0) { copyhdr = 1; MGETHDR(n, wait, m->m_type); + M_DUP_PKTHDR(n, m); l = MHLEN; } else { - copyhdr = 0; MGET(n, wait, m->m_type); l = MLEN; } @@ -249,8 +247,6 @@ m_dup1(struct mbuf *m, int off, int len, if (!n) return (NULL). - if (copyhdr) - M_DUP_PKTHDR(n, m); m_copydata(m, off, len, mtod(n, caddr_t)); n->m_len = len;
Листинг 4. Патч для OpenBSD, приведенный с незначительными сокращениями.
С первого взгляда суть изменений совершенна неясна, можно даже сказать - мистически непонятна. Разработчики просто переместили макрос M_DUP_PKTHDR из конца функции m_dup1() внутрь ветки "if (off == 0 &&...", попутно избавившись от переменной-флага copyhdr. Но ведь алгоритм функции m_dup1() остался прежним и при этом совершенно непостижимым...
Ковырять патч дальше бессмысленно. Ничего нового выжать из него не удастся и без помощи исходных текстов не обойтись.
Идем на http://fxr.watson.org/fxr/source/kern/uipc_mbuf2.c?v=OPENBSD и смотрим на полный код функции m_dup1(), критические фрагменты которой в патче отсутствуют.
Листинг 5. Полный исходный текст функции m_dup1().
Злобный diff покоцал ветвь "if (n && len > l)", сбив нас с толку и завязав наш хвост двойным морским узлом, но теперь мы вникли в тему: в исправленной версии макрос M_DUP_PKTHDR вызывается до MCLGET, а в старой - после. Осталось только узнать, чем все эти макросы занимаются. Нет ничего проще - на fxr.watson.org все они представлены ссылками, щелкнув по которым мы переходим к месту их определения, снабженного комментариями. Точно таким же путем разбираемся с M_PKTHDR и m->m_flags, проясняющих смысл конструкции "if (off == 0 && (m->m_flags & M_PKTHDR) != 0)", который в переводе на русский язык звучит приблизительно так: если смещение (off) пакета равно нулю, но оно не совпадает с началом пакета, то мы имеем дело с фрагментом пакета, для обработки которого входим внутрь ветки if.
Макрос MGETHDR выделяет память под специальную структуру mbuf (в данном случае указатель на нее помещается в переменную n) и тут же инициализирует ее для хранения пакетов типа m->m_type. Макрос MCLGET "заглатывает" заполненные структуры mbuf и объединяет их в кластер, осуществляя сборку пакетов, но(!) в нефикисенной версии объединение пакетов происходит до вызова макроса M_DUP_PKTHDR, копирующего переданный функции указатель m в выделенную и проинициализированную переменную n.
Вот где собака зарыта!!! Поскольку выделение памяти под фрагменты осуществляется сразу же после инициализации mbuf и до заполнения ее полей реальными значениями, то попытка копирования всех фрагментов функцией m_copydata() в переменную m приводит к переполнению. А все потому, что макрос M_DUP_PKTHDR стоит не на месте! Вроде бы мелочь, а какие последствия она вызывает... Кстати говоря, парни из CoreLabs этот момент никак не объясняют, заставляя нас гадать, как связана фрагментация с переполнением и на сколько фрагментов пакет необходимо разбить для успешной атаки. Забавно, но некоторые ресурсы по безопасности (особенно русские), передирая (и конечно же, перевирая) письмо Theo de Raadt'а, к прилагательному "фрагментированный" добавляют эпитет "сильно". Дескать - шлите, ребята, сильно фрагментированные IPv6-пакеты и валите OpenBSD косяками. На самом деле, в оригинале слова "сильно" отсутствует и прилагаемый к письму exploit разбивает IP-пакет всего на два фрагмента, так что называть его “сильно фрагментированным” можно только накурившись дряни, пополам смешанной с удобрениями. Но это все лирика, пора переходить к технике передачи управления на shell-код, поскольку вгонять ядро в панику как-то неинтересно.
Поскольку это не совсем обычное переполнение, то традиционные приемы здесь не подходят и начинать приходится с изучения полей структуры mbuf.h, описанной в файле /sys/mbuf.h:
Листинг 6. Устройство структуры mbuf.
Парни из CoreLabs верно подметили, что одним из элементов структуры mbuf является структура m_ext, описанная в файле /sys/mbuf.h:
Листинг 7. Устройство структуры m_ext.
Среди множества разных типов данных в структуру m_ext входит указатель на функцию ext_free, которая вызывается из функции m_free(), когда приходит последний фрагмент пакета (см. листинг 5). А это значит, что подменив ext_arg указателем на shell-код, мы вместо банального краха системы добьемся перехвата управления.
Вся сложность в том, что мы не знаем - где именно размещается переполняемая структура mbuf в памяти, поэтому возникает задача определения ее дислокации. Парни из CoreLabs называют ее поиском правильного "трамплина" (right trampoline) и делают они это следующим образом... Но прежде - небольшое лирическое отступление. Словарь "Мультилекс" переводит "trampoline" как "батут" и по этому поводу вспоминается следующая невероятно правдоподобная история из жизни. В одном из небольших городов театр проездом давал "Грозу" Островского, в которой по сценарию Катерина бросалась в реку (и, как любил поговаривать мой папа, если бы она жила в наше время, то не утопилась, а бросилась бы под поезд). Естественно, для смягчения последствий падения использовались маты. С собой их не возили, перекладывая задачу возведенная декораций на местных и вот местные, покурив хорошей травы, вместо мата по ошибке положили батут. Короче, бросается значит, Катерина в "реку" и... тут же с криком вылетает обратно. И так несколько раз. Актеры с трудом сдерживаются (сцена-то трагическая), зрители в трансе и в этот момент один из стоящих на сцене произносит: "Да... Не принимает матушка-Волга..."
С трамплином возникает та же ситуация, только вместо Волги у нас BSD, а вылетает не Катерина, а исключение. И продолжает вылетать до тех пор, пока мы не угадаем точную локацию переполняемой структуры в памяти, которую парни из CoreLabs добывают довольно варварским путем: "objdump -d /bsd | grep esi | grep jmp", то есть дизассемблируют конкретную версию OpenBSD и ищут в ней инструкцию jmp esi. При чем тут esi? По чистой случайности компилятор разместил в нем указатель на переполняемую структуру, но... где гарантия, что при малейшем изменении исходного кода или компиляции с другими ключами, компилятор не выберет иную стратегию поведения и не засунет указатель совсем в другой регистр?! Увы, такой гарантии у нас нет, а потому proof-of-concept exploit крайне не универсален и не надежен. Атаковать произвольную систему с его помощью не получится и он годится только для взлома систем, установленных "из коробки" (т.е. поставляемых в уже откомпилированном виде), да и то в разных версиях положение трамплина будет различным, так что опасность, грозящая пользователям OpenBSD, очень сильно преувеличена.