Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР #100, АПРЕЛЬ 2007 г.

Обзор эксплойтов

Крис Касперски

Хакер, номер #100, стр. 056

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

Brief

Дополнительные защитные средства (антивирусы, персональные брандмауэры и т.д.) зачастую сами становятся объектом атаки, и, вместо обещанного рекламой усиления защиты, мы получаем новые дыры, одна из которых была обнаружена David'ом Matousek'ом (основателем и руководителем одноименной исследовательской компании — Matousec - Transparent security). 15 сентября 2006 года, экспериментируя с версий 9.1.0.33, он посылал псевдоустройству DeviceSymEvent (созданному брандмауэром) различные 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.

Targets

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

Exploit

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

Ключевой фрагмент exploit'а David'а Matousek'а

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]="11111111111111111111";

DeviceIoControl(file,0x00220404,(PVOID)bufin,20,(PVOID)bufout,4,&retlen,NULL);

Solution

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

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

Brief

Unique RAR File Library представляет собой бесплатную кроссплатформенную библиотеку, распространяемую в исходных текстах (www.unrarlib.org) и, как легко догадаться из ее названия, позволяющую сторонним программистам создавать независимые утилиты для распаковки RAR-архивов или интегрировать библиотечный код в свои собственные продукты, обеспечивая его прозрачную поддержку. К сожалению, библиотека не свободна от ошибок, и некоторые из которых носят характер критических, как, например, ошибка переполнения в имени файла, обнаруженная хакером по кличке starcadi (starcadi@autistici.org) и описанная на http://securityvulns.com/news/Unrarlib/BO.html. Суть ошибки состоит в том, что имя распаковываемого файла копируется в локальный буфер фиксированного размера длинной в 255 байт, что является пределом для Windows, и файл с более длинным именем ни создать, ни открыть не удастся. Но в архиве длина имени файла ограничена только длиной самого архива (то есть не ограничена вообще). Естественно, такой архив нельзя создать легальным путем с помощью самого RAR'а, но что мешает хакеру смастерить архив самостоятельно или надругаться над уже существующим? При этом мы получаем классическое переполнение стека с возможностью передачи управления на shell-код (содержащийся в имени файла) и захвата управления машиной. Передав такой архив по сети, мы одним взмахом руки превращаем локальную уязвимость в удаленную. Причем если антивирус или другое программное обеспечение использует библиотеку unrarlib для автоматической проверки пролетающих архивов (а оно ее использует), то жертве вообще не требуется совершать никаких действий.

Targets

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

Exploit

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

Solution:

Да поможет нам Аллах :).

Small http server: локальное переполнение буфера

Brief

Small http (smallsrv.com) - надежный, проворный и чрезвычайно компактный http/ftp/proxy/pop3/smtp/dns/dhcp-сервер в одном флаконе (а для граждан бывшего СНГ к тому же еще и бесплатный). Сейчас он стоит у меня на компе, сменив War FTP-сервер, и я, естественно, слежу за его безопасностью, шурша логами и добавляя в блэк-лист все новые IP-адреса, обладатели которых - конкретные крысы. Так вот после добавления нового IP в Deny-IP, сервер через некоторое время вылетел в SoftICE, который я держу всегда запущенным, демонстрируя ситуацию типичного переполнения. Анализ показал, что последний внесенный в блэк-лист IP (87.250.254.249), принадлежащий Yandex'у, был усечен сервером до 87.250.254, и, при попытке подключения с адреса 87.250.254.xxx, у small http что-то перемкнуло внутри парсера IP-адресов и возникло необработанное исключение, отловленное айсом. Дело кончилось тем, что я, отправив разработчику уведомление об ошибке, перенес блэк-лист на персональный брандмауэр (типа workaround такой). Эксперименты со списком блокируемых адресов быстро опровергли первоначальную гипотезу о фиксированной длине поля Deny-IP и не позволили поставить условия, при которых происходит усечение последнего введенного адреса. Судя по всему, помимо количества IP-адресов, тут присутствуют еще и другие факторы.

Target

Уязвимость обнаружена в версии 3.05.64; о других мне ничего не известно.

Exploit

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

Solution

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

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

Brief

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

Такое положение дел разозлило парней из CoreLabs, и они ценой недели беспощадных исследований доказали возможность удаленного захвата управления, выпустив 5 марта боевую версию сплоита с shell-кодом на борту, от которого разработчикам OpenBSD было уже не отвертеться. И вся последующая неделя ушла на переписку с CoreLabs, подготовившей за это время развернутый отчет по безопасности, который был опубликован ими на собственном сайте. 13 марта координатор проекта Theo de Raadt переслал его на Bugtraq, откуда он разошелся по другим сайтам, прямо или косвенно связанным с безопасностью. Это вторая дыра в OpenBSD, обнаруженная за последние 10 лет промышленной эксплуатации (предыдущая сидела в демоне SSH), так что ее открытие можно назвать эпохальным событием, привлекающим к себе внимание и вызывающим желание как следует во всем разобраться.

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

Solutions

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

Блокирование всего IPv6-трафика на встроенном брандмауэре

# добавить следующую строку в файл /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

Чтобы разобраться в дыре основательно и (самое главное) самостоятельно, необходимо иметь установленную OpenBSD, а поскольку таковой внутри моей норы не обнаружилось, пришлось курить исходные тексты. Но исходные тексты OpenBSD курить можно до бесконечности, а они даже не убавятся. Нет! Тут нужно мыслить стратегически и действовать по плану! Сетевой стек — весьма масштабное сооружение, и заблудиться в нем намного проще, чем понять его устройство, хотя бы и в самых общих чертах. Нам это, собственно, и не нужно, поскольку, чтобы локализовать ошибку в миллионах строк программного кода, достаточно просто взглянуть на патч, представляющий собой простой diff-файл, который возник в результате сравнения двух версий: исправленной и старой:

Патч для OpenBSD, приведенный с незначительными сокращениями

--- sys/ke/uipc_mbuf2.c 17 Mar 2006 04:15:51 -0000 1.24

+++ sys/ke/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)

retu (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)

retu (NULL);

- if (copyhdr)

- M_DUP_PKTHDR(n, m);

m_copydata(m, off, len, mtod(n, caddr_t));

n->m_len = len;

На первый взгляд суть изменений совершенно неясна. Разработчики просто переместили макрос M_DUP_PKTHDR из конца функции m_dup1() внутрь ветки «if (off == 0 &&...», попутно избавившись от переменной-флага copyhdr. Но ведь алгоритм функции m_dup1() остался прежним и при этом совершенно непостижимым. Ковырять патч дальше бессмысленно. Ничего нового выжать из него не удастся, и без помощи исходных текстов не обойтись.

Идем на http://fxr.watson.org/fxr/source/ke/uipc_mbuf2.c?v=OPENBSD и смотрим на полный код функции m_dup1(), критические фрагменты которого в патче отсутствуют:

Полный исходный текст функции 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) retu (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) retu (NULL);

if (copyhdr) M_DUP_PKTHDR(n, m);

m_copydata(m, off, len, mtod(n, caddr_t)); n->m_len = len;

retu (n);

}

Злобный 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:

Устройство структуры mbuf

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;

};

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

Устройство структуры m_ext

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

};

Из множества разных типов данных в структуру m_ext входит указатель на функцию ext_free, которая вызывается из m_free(), когда приходит последний фрагмент пакета. А это значит, что, заменив 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, очень сильно преувеличена.

Содержание
ttfb: 4836.935043335 ms