Exploits review (выпуск 0xA)

Автор: (c)Крис Касперски ака мыщъх

Norton Personal Firewall - локальный отказ в обслуживании

brief

Дополнительные защитные средства (такие как антивирусы, персональные брандмауэры, 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.

targets

Уязвимость впервые обнаружена в версии 9.1.0.33 и "реинсценирована" в версии 9.1.1.7, промежуточные версии выпущены без этой ошибки.

exploit

Исходный текст exploit'а на языке Си лежит на сервере компании Matousec: www.matousec.com/downloads/windows-personal-firewall-analysis/BTP00011P002NF.zip, а ниже приведен его ключевой фрагмент:

HANDLE file = CreateFile("\\\\.\\Global\\SymEvent", GENERIC_READ |
                        GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
                        NULL, OPEN_EXISTING, 0, NULL);
...
srand(GetTickCount());
char bufout[4], bufin[20] = "\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1";
DeviceIoControl(file, 0x00220404, (PVOID)bufin, 20, (PVOID)bufout, 4, &retlen, NULL);

Листинг 1. Ключевой фрагмент exploit'а David'а Matousek'а.

solution

Использовать стабильные версии между 9.1.0.33 и 9.1.1.7.

Страничка компании Matousec

Рисунок 1. Страничка компании Matousec - Transparent security, обнаружившей уязвимость в Norton Personal Firewall

Unrarlib - локальное переполнение буфера

brief

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 для автоматической проверки "пролетающих" архивов (а оно ее использует), то жертве вообще не требуется совершать никаких действий!!!

char ArcName[255]; /* RAR archive */
urarlib_get(void *output, ulong *size, char *filename, void *rarfile, char *psswd)
{
        ...
        strcpy(ArgName, filename); /* set file(s) to extract */
        strcpy(ArcName, rarfile); /* set RAR file name */
}

Листинг 2. Фрагмент библиотеки unrarlib, содержащий уязвимость.

targets

На данный момент уязвимость подтверждена в версии 0.4.0, про другие версии пока ничего неизвестно, также отсутствует перечень продуктов сторонних разработчиков, использующих библиотеку unrarlib в своих проектах.

exploit

Готовые exploit'ов в дикой природе еще не обнаружены, но используя "легальный" RAR-архив, длину файла можно легко увеличить с помощью hiew'а (или любого другого hex-редактора) и с его же помощью вбить туда боевой shell-код.

solution

Да поможет нам Аллах!

Здесь раздают unrarlib

Рисунок 2. Здесь раздают unrarlib.

small http server - локальное переполнение буфера

brief

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-адресов, тут присутствуют еще и другие факторы.

target

Уязвимость обнаружена в версии 3.05.64, о других мыщъх'у ничего не известно.

exploit

Фрагмент конфигурационного файла с "черным списком" IP-адресов, на которых наблюдается устойчивое воспроизведение ошибки (вместе с кратким описанием ситуации) лежит в мыщъхиной норе по адресу http://nezumi.org.ru/souriz/hack/http.cf_.

solution

Блокировать IP-адреса на брандмауэре.

Поле Deny-IP

Рисунок 3. Поле Deny-IP со списком блокируемых адресов, подверженное переполнению.

Full disclose: OpenBSD - переполнение буфера при получении фрагментированного пакета IPv6

brief

20 февраля 2007 года сотрудники лаборатории CoreLabs Advisory (подразделение компании Core Security Technologies), обнаружили, что при получении фрагментированного IPv6-пакета OpenBSD, воздвигнутая в конфигурации по умолчанию, выпадает в kernel panic. На следующий день разработчикам системы был выслан proof-of-concept exploit, демонстрирующий удаленный отказ в обслуживании, успешно подтвержденный и довольно оперативно исправленый заплаткой, выпущенной 26 февраля, однако статуса уязвимости ("vulnerability") ошибке так и не присвоили, поскольку по мнению OpenBSD-team'а, отказ в обслуживании - это не дыра, а так... просто мелкая неприятность, о которой пользователям знать совершенно необязательно.

Только одна удаленная дыра в конфигурации по умолчанию более чем за 8 лет эксплуатации

Рисунок 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), так что ее открытие можно назвать весьма эпохальным событием, привлекающим к себе внимание и вызывающим желание как следует во всем разобраться.

Всего лишь все удаленных дыры в конфигурации по умолчанию более чем за 10 лет эксплуатации

Рисунок 5. "Всего лишь все удаленных дыры в конфигурации по умолчанию более чем за 10 лет эксплуатации".

targets

Уязвимости подвержены следующие версии: OpenBSD 4.1, OpenBSD 4.0 Current, OpenBSD 4.0 Stable, OpenBSD 3.9, OpenBSD 3.8, OpenBSD 3.6 и OpenBSD 3.1.

exploit

Исходный текст 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 еще долгое время будет оставаться на уровне чуть выше абсолютного нуля, во всяком случае в глобальном масштабе.

solution

Заплатки для 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-трафика на встроенном брандмауэре.

Патч для OpenBSD, исправляющий ошибку

Рисунок 6. Патч для OpenBSD, исправляющий ошибку.

full disclose

Чтобы разобраться в дыре основательно и (самое главное!) самостоятельно, необходимо иметь установленную 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(), критические фрагменты которой в патче отсутствуют.

static struct mbuf *m_dup1(struct mbuf *m, int off, int len, int wait)
{
        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);
                l = MHLEN;
        }
                else
        {
                copyhdr = 0;
                MGET(n, wait, m->m_type);
                l = MLEN;
        }

        if (n && len > l)
        {
                MCLGET(n, wait);
                if ((n->m_flags & M_EXT) == 0)
                {
                        m_free(n);
                        n = NULL;
                }
        }

        if (!n) return (NULL);
        if (copyhdr) M_DUP_PKTHDR(n, m);
        m_copydata(m, off, len, mtod(n, caddr_t)); n->m_len = len;
        return (n);
}

Листинг 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:

// This is the definition (/sys/mbuf.h):

struct mbuf
{
        struct m_hdr m_hdr;
        {
                union
                {
                        struct
                        {
                                struct pkthdr MH_pkthdr; /* M_PKTHDR set */
                                union
                                {
                                        struct m_ext MH_ext; /* M_EXT set */
                                        char MH_databuf[MHLEN];
                                } MH_dat;
                        } MH;
                        char M_databuf[MLEN]; /* !M_PKTHDR, !M_EXT */
        } M_dat;
};

Листинг 6. Устройство структуры mbuf.

Парни из CoreLabs верно подметили, что одним из элементов структуры mbuf является структура m_ext, описанная в файле /sys/mbuf.h:

struct m_ext
{
        caddr_t ext_buf; /* start of buffer */
        /* free routine if not the usual */
        void (*ext_free)(caddr_t, u_int, void *);
        void *ext_arg; /* argument for ext_free */
        u_int ext_size; /* size of buffer, for ext_free */
        int ext_type;
        struct mbuf *ext_nextref;
        struct mbuf *ext_prevref;
        #ifdef DEBUG
                const char *ext_ofile;
                const char *ext_nfile;
                int ext_oline;
                int ext_nline;
        #endif
};

Листинг 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, очень сильно преувеличена.