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

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

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

Хакер, номер #112, стр. 112-054-1

Чем глубже в Windows, тем больше дыр. Открывая весьма соблазнительные перспективы для хакеров, прекращаться этот поток дыр не собирается. Пока Microsoft и сторонние разработчики ищут противодействие локальным и удаленным атакам, мы к тому времени нароем новые дыры. Так что никто без работы не останется.

Microsoft Windows – unicast/multicast traffic and firewalls

Brief

Экспериментируя с программами потокового аудио/видео вещания (главным образом с VideoLAN), я с удивлением обнаружил, что мой любимый SyGate Personal Firewall 4.5 в упор не видит ни входящего, ни исходящего unicast/multicast трафика и, соответственно, не может заблокировать его, что очень странно и подозрительно. Особенно в случае с unicast-трафиком, работающим поверх IP и, с этой точки зрения, ничем не отличающимся от прочих IP-пакетов. Но, тем не менее, факт! Упрямый и труднообъяснимый. Беглое расследование показало, что, начиная еще с NT 4.0 и NT 3.51 SP2, обработка unicast/multicast-потоков выведена в отдельное «делопроизводство» внутри сетевой подсистемы. Мотивы вполне ясны и особенно ощутимы на «тонких» каналах связи. «Выхватывая» unicast/multicast пакеты из общего сетевого трафика, операционная система уделяет им максимум внимания, оттесняя весь остальной TCP/IP-трафик на второй план. Другими словами, чтобы не реализовывать приоритетный сетевой ввод/вывод, разработчики Windows сделали исключение лишь для unicast/multicast-трафика. Кстати, чтобы это выяснить, совершенно необязательно иметь секс с отладчиком и дизассемблером. Достаточно раскурить MSDN: technet2.microsoft.com/windowsserver/en/library/3da7c55f-cb91-406a-8596-7b120ebf10f81033.mspx?mfr=true. Там же можно нарыть и примеры создания IP-фильтров, учитывающих весь трафик, в том числе и unicast/multicast, и тогда ни один пакет не пройдет незамеченным (www.microsoft.com/technet/prodtechnol/windows2000serv/reskit/intwork/inae_ips_neez.mspx?mfr=true). Увы! Далеко не все разработчики персональных брандмауэров учитывают это обстоятельство, что позволяет хакерам генерировать unicast трафик и пускать его в обход брандмауэра.

Targets

NT 3.51 SP2 и выше, SyGate Personal Firewall 4.5 и некоторые другие брандмауэры.

Exploits

В качестве «тестера», определяющего способность брандмауэра распознавать и блокировать различные виды unicast/multicast трафика, можно использовать бесплатную программу VidoeLAN, кстати говоря, распространяемую в исходных текстах: www.videolan.org.

Solution

Использовать в качестве шлюза для доступа в Сеть любую Linux или BSD-подобную систему, чей штатный брандмауэр влет бьет любой unicast/mulicast трафик.

Microsoft Windows – обход ASLR

Brief

В Висте появилась рандомизация адресного пространства. Она существенно затрудняет внедрение зловредного кода в «доверенные» процессы типа explorer.exe, которым разрешен выход в Сеть. Классическая схема внедрения (VirtualAllocEx, WriteProcessMemory, SetThreadContext) распознается практически всеми антивирусами и персональными брандмауэрами, написанными еще много лет назад. Поэтому хакеры усовершенствовали методику, отказавшись от функции SetThreadContext, посредством которой они изменяли регистр EIP так, чтобы он указывал на внедренный код. В новой схеме передача управления осуществлялась путем заполнения стека главного потока (благо, его местоположение, вплоть до Висты, оставалось постоянным) указателями на внедренный код. Поскольку комбинация команд VirtualAllocEx/WriteProcessMemory распространена среди «честных» программ и представляет собой легальный механизм межпроцессорного взаимодействия, то никакие защиты на нее не ругаются. Но с появлением Висты ситуация изменилась, и базовый адрес стека стал располагаться по случайным адресам, что должно было положить конец хакерству, но… так и не положило.

Потому как существует замечательная API-функция VirtualQueryEx, возвращающая карту памяти целевого процесса, и не менее замечательная API-функция VirtualProtectEx, сообщающая атрибуты страницы. Так вот, стек представляет собой блок памяти, на вершине которого лежит страница с атрибутами PAGE_GUARD, что является его характерной чертой, позволяющей отличать стек от всех остальных регионов памяти (примечание: некоторые программы также пользуются флагом PAGE_GUARD для динамического выделения памяти, но очень и очень немногие). Важно понять, что PAGE_GUARD определяет не текущее значение регистра ESP, а самое высокое положение указателя вершины стека, когда-либо достигнутое потоком в процессе его существования. Реальное же значение ESP, как правило, намного ниже, но что нам стоит заполнить указателями на внедренный код весь блок от PAGE_GUARD и до его конца?!

Кстати говоря, поскольку операционная система выделяет стек постранично и делает это через менеджер памяти, общий с кучей, то функцией VirtualFreeEx мы можем освобождать страницы, принадлежащие стеку одного из потоков целевого процесса, возвращая их в общий пул свободной памяти. И тогда куча окажется прямо в стеке! Программа, пытаясь прочитать локальные переменные или стянуть адрес возврата из функции, встретит что-то очень неожиданное и, скорее всего, рухнет, если, конечно, мы не подложим в строго определенные места указатели, передающие управление на внедренный нами код. При желании можно придумать и другие разновидности атак, но уже ясно, что ASLR никакая не защита, а так… пугало для пионеров.

Targets

Виста/Server 2008 (в более ранних системах рандомизация адресного пространства отсутствует, но атака прекрасно совместима с ними, включая линейку 9x).

Exploit

Не требуется. Любой отладчик (например, Olly) без труда найдет стек основного потока в целевом процессе по карте памяти.

Solution

Отсутствует.

Microsoft Windows – ошибка подсчета квотирования

Brief

В W2K (с большой задержкой против UNIX) наконец-то появилась поддержка квотирования дискового пространства, позволяющая администраторам умерять «аппетит» прожорливых пользователей. Ну а кому понравится, когда ограничивают свободу? Вот хакеры взбунтовались и начали пакостить, обходя ограничения и поглощая все доступное дисковое пространство. Что, естественно, приводит к невозможности создания новых файлов и, как следствие, краху системы еще на ранних стадиях загрузки. Это при условии, что пользователям разрешено создавать файлы хотя бы в одном из каталогов системного тома, например, Documents-n-Setting или C:\WINDOWS\TEMP. Разработчики Windows, казалось бы, предусмотрели все, включая то, сколько физических кластеров занимают созданные пользователем файлы (для упакованных файлов берется полный, а не сжатый размер).

Но один маленький финт ушами они пропустили. Вопрос, мучавший хакеров еще со времен MS-DOS, – сколько занимает файл нулевой длины? Ноль байт? Один кластер? Или... На самом деле, система не настолько глупа, чтобы выделять дисковое пространство файлу с нулевой длиной и потому формально их можно создавать сколько угодно. Вот только у файла есть имя, атрибуты, дата и время создания, идентификатор владельца – словом, информация, которую где-то надо хранить. В NTFS она хранится в специальном служебном файле с именем $MFT, где на каждый «нулевой» заведена специальная файловая запись – структура данных, известная, как FILE_RECORD. Обычно ее размер занимает 1 Кб («обычно» – потому, что из этого правила слишком много исключений, которые лень перечислять, да и на исход дела они никак не влияют).

К тому же, для ускорения типовых файловых операций, содержимое директорий проиндексировано, а каждый индекс тоже хочет пространства (правда, не 1 Кб, а намного меньше). Создание пустых файлов в бесконечном цикле вызывает рост $MFT файла, размер которого в пользовательских квотах не учитывается. Через некоторое (довольно продолжительное) время $MFT поглощает все свободное пространство на диске, затем кончаются файловые записи, принадлежащие удаленным файлам и... все! Чтобы создать еще хоть один файл, нужно что-нибудь удалить и успеть опередить хакерский цикл, упорно пытающийся создавать новые файлы…

Target

W2K и выше (в NT 3.x/4.x нет квот, но данная схема атаки применима и для них).

Exploit

Ниже приведен исходный код боевого exploit'а, написанного на языке Си и создающего файлы нулевого размера в бесконечном цикле:

Exploit, обходящий систему дисковых квот в W2K и более старших системах

int a; FILE *f; char buf[256];

for (;;)

{

sprintf(buf,"%04Xh-%04Xh-%04Xh-%04Xh-%04Xh-%04Xh",

rand(),rand(),rand(),rand(),rand(),rand());

f = fopen(buf,"wb"); if (f) fclose(f);

}

Solution

Отсутствует.

Небезопасный SafeSEH

Двадцать восьмого декабря 2007 года в 5:47 PM я получил от легендарного во всех отношениях хакера Юрия Харона следующее письмо (приводимое, естественно, с его разрешения):

«Нашел я ошибку в форточках. Слов нет, одни эмоции :(. Добавляя новую «защиту», они умудрились оставить непроинициализированные переменные (будут интересны подробности – расскажу), в результате чего отваливаем на BSOD при SEH в некоторых (старых) драйверах. Убббивать... Три дня угробил на поиск :(.

Теперь эта прошла и вылезла следующая, которую я обнаружил совершенно случайно – нет, ну вот как так можно? Два варианта ntkrnlpa.exe. Версия одна и та же. Билд один и тот же. Но в version info присутствует строка, и разные они даже по размеру:

VALUE "FileVersion", "5.1.2600.3093 (xpsp_sp2_gdr.070227-2254) // – это в одном;

VALUE "FileVersion", "5.1.2600.3093 (xpsp_sp2_qfe.070227-2300) // – это в другом.

При этом (заметим в скобках), оба файлы получены с windows update, просто один обновился сразу же, как только вышел (в июне-июле), а второй только сейчас (на варю когда ставил). И вот на том, который «сейчас», ошибка и вылезла. Причем, опять какая-то наведенка :(.

Интересно сколько я ее искать буду...».

Я сообщил, что подробности, разумеется, интересны и тут же получил ответ: «Напомни завтра (ночью) – я щас уже офигел и спать пошел. Пока, чтобы писать меньше, почитай про ключ /SAFESEH в текущем ms-link (не столько про ключ, сколько про то, зачем он) – тогда будет проще объяснить».

А пока Харон спит (то есть, теперь он, конечно, не спит… хотя никаких гарантий на этот счет ни у кого нет), мы отправимся по ссылке, ведущей на Хароновский ftp-сервер: ftp://ftp.styx.cabel.net/, где в директории pub лежит замечательный (и бесплатный – для некоммерческого использования) линкер UniLink. Открываем файл whatsnew_ru.txt и втыкаем:

Фрагмент файла whatsnew_ru.txt из комплекта поставки линкера UniLink

build 3.13 [ulnb0313.zip]

+ Добавлен ключ -RS для «защиты» от инжекций SEH-обработчиков (этот механизм работает только в Vista и XPsp2). Поведение несколько отличается от ключа /SAFESEH ms-link (v8 или старше):

Отсутствие ключа – аналог /SAFESEH:NO

При указании ключа коллекционируется информация об обработчиках (аналог отсутствия ключа у ms-link), однако, если такой информации нет, компоновка не отвергается (как у ms-link), а модуль маркируется, как программа (dll), в которой запрещен SEH. Также выдается информационное сообщение (из группы w-inf).

Строго в соответствии с документацией, допустимы ссылки на «внешние» обработчики (handler); в отличие от ms-link, где такая ситуация приводит к ошибке. Это имеет значение при необходимости работы с «нестандартными» обработчиками и/или с библиотеками, в которых их назначение отсутствует. Ссылки на внешние обработчики, которые НЕ определены в компонуемом, порождают 'Unresolved external'.

Для упрощения работы с библиотеками Borland (в которых нет информации об обработчиках) совершается «автоматическое» назначение обработчиков (они у Borland стандартные) – проверялось для C/CPP bc v5 и bcb v5/v6/bds4.

Указание ключа -RS+ приводит к запрету ИИ в отношении библиотек Borland.

Для остальных компиляторов можно использовать служебные файлы, применяя директиву .safeseh ml.

Механизм SafeSEH, призванный предотвратить подмену обработчика структурных исключений при атаке на переполняющиеся буфера, в зачаточном виде появился еще в XP. Об этом можно узнать, раскурив MSDN (отправные точки для поиска: blogs.msdn.com/greggm/archive/2004/07/22/191544.aspx и msdn2.microsoft.com/en-us/library/9a89h429.aspx). Но только в Висте он был доведен до минимально работающего состояния.

В чем его суть? Если раньше указатели на обработчики структурных исключений хранились в стеке, беспрепятственно доступном на запись/чтение, то теперь они переместились в специальные секции PE-файла, доступные только на чтение и формируемые статическим образом еще на этапе сборки программы при активном участии со стороны линкера и компилятора.

Новые структуры PE-файла, отвечающие за поддержку SafeSEH

extern PVOID __safe_se_handler_table[];

extern BYTE __safe_se_handler_count;

typedef struct {

DWORD Size;

DWORD TimeDateStamp;

WORD MajorVersion;

WORD MinorVersion;

DWORD GlobalFlagsClear;

DWORD GlobalFlagsSet;

DWORD CriticalSectionDefaultTimeout;

DWORD DeCommitFreeBlockThreshold;

DWORD DeCommitTotalFreeThreshold;

DWORD LockPrefixTable; // VA

DWORD MaximumAllocationSize;

DWORD VirtualMemoryThreshold;

DWORD ProcessHeapFlags;

DWORD ProcessAffinityMask;

WORD CSDVersion;

WORD Reserved1;

DWORD EditList; // VA

DWORD_PTR *SecurityCookie;

PVOID *SEHandlerTable;

DWORD SEHandlerCount;

} IMAGE_LOAD_CONFIG_DIRECTORY32_2;

const IMAGE_LOAD_CONFIG_DIRECTORY32_2 _load_config_used = {

sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_2),

0,

... skipped …

0,

&__security_cookie,

__safe_se_handler_table,

(DWORD)(DWORD_PTR) &__safe_se_handler_count

};

Утром Харон проснулся и отписал: «Ты про SafeSEH прочитал? Тогда рассказываю. Как оказалось (хоть и врали, что это только для Висты), механизм уже используется в XP SP2 (но не всех «подбилдах»!) для драйверов. А поскольку драйвера могут быть собраны как с этим ключом, так и без, то используется он только в ситуации, когда назначено. Практически, если посмотреть в процедуру RtlIsValidHandle, мы увидим, что, когда RtlLookupFuncionTable возвращает NULL (то есть, нет таблиц), хандлер считается валидным (что правильно). При возврате INVALID_HANDLE_VALUE (возникает при IMAGE_DLLCHARACTERISTICS_NO_SEH) хандлер считается не валидным. Ну а все остальное рассматривается, как описатель диапазона. Все вроде как правильно.

Теперь смотрим в RtlLookupFunctionTable и видим, что возвращаемое значение (точнее, два значения) берутся из описания модуля, в диапазон адресов которого попадает текущее исключение. Сиречь, опять же все правильно. Теперь идем в то место, где этот самый описатель модуля формируется (MiCaptureImageExceptionValues, вызываемую из MmLoadSystemImage), и видим подтверждение старого доброго правила — если обезьяне выдать пистолет, то ее обороноспособность понизится :).

Помнишь, сколько было жалоб (в том числе и моих), что MS (во многих местах) некорректно обрабатывает – точнее говоря, не обрабатывает – NumRvaAndSize в заголовке PE'шника? Они решили исправиться. Но поручили это своим «пионэрам». И вот что получилось в результате:

Псевдокод, обрабатывающий поле NumRvaAndSize PE-заголовка

if(peh->OptHdr.DllCharacteristics & ...NO_SEH)

mdsc->SEHtable = mdsc->SEHcount = -1;

else

if(peh->NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG)

{

if(peh->DataDir[...] == NULL) mdsc->SEHtable = mdsc->SEHcount = 0;

else

{

// init values

}

}

Обращаем внимание, что при NumRvaAndSizes <= ...LOAD_CONFIG значения в таблице описания модуля остаются неинициализированными!

Теперь вспоминаем, что память под эти описания (при загрузке драйверов) берется динамически из nonpagedpool, и возвращаемся в обработку исключений. Что происходит, когда RtlLookupFunctionTable возвращает не 0 и не -1? Начинаем разбирать таблицу. Имеем нечто, вроде:

Псевдокод функции разбора таблицы исключений SEHtable

for(...)

{

...

if(....

&& cuFunction >= mdsc->SEHtable[i]) return TRUE;

}

Вспоминаем, что SEHtable у нас неинициализированный (содержит мусор), и получаем что? Правильно, GPF. А теперь припоминаем, что это место мы проходим при обработке любого исключения в драйвере (даже вполне штатного, со своими обработчиками), в том числе, на IRQL > DISP. И что? Правильно – BSOD. Например, при DebugPrint в release build и отсутствии отладчиков».

Содержание