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

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

Чем глубже в 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-фильтров, учитывающих весь трафик: www.microsoft.com/technet/prodtechnol/windows2000serv/reskit/intwork/inae_ips_neez.mspx?mfr=true, в том числе и unicast/multicast, и тогда ни один пакет не пройдет незамеченным. Увы! Далеко не все разработчики персональных брандмауэров учитывают это обстоятельство, что позволяет хакерам генерировать unicast-трафик и пускать его в обход брандмауэра.

targets

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

exploits

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

solution

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

Внешний вид программы VideoLAN

Рисунок 1. Внешний вид программы VideoLAN.

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

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

PAGE_GUARD

Рисунок 2. На вершине блока памяти, выделенного потоку, гордо возлегает страница с атрибутами PAGE_GUARD (ну, или “Guarded” - в терминах OllyDbg).

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

brief

В W2K (с большой задержкой против UNIX) наконец-то появилась поддержка квотирования дискового пространства, позволяющая администраторам умерять "аппетит" прожорливых пользователей. Ну, а кому понравится, когда ограничивают его свободу? Вот хакеры и взбунтовались и начали пакостить, обходя ограничения и поглощая все доступное дисковое пространство, приводящее к невозможности создания новых файлов и, как следствие - краху системы еще на ранних стадиях загрузки (при условии, что пользователям разрешено создавать файлы хотя бы в одном из каталогов системного тома, например, Documents-n-Settings или C:\WINDOWS\TEMP). Разработчики Windows, казалось бы, предусмотрели все, считая сколько физических кластеров занимают все созданные данными пользователем файлы (а для упакованных файлов берется их полный, а не сжатый размер). Но один маленький финт ушами они все-таки пропустили. Вопрос, мучивший хакеров еще со времен MS-DOS - сколько занимает файл нулевой длины? Ноль байт? Один кластер? Или... На самом деле, система не настолько глупа, чтобы выделять дисковое пространство файлу с нулевой длиной и потому формально их можно создавать сколько угодно. Вот только... У файла есть имя, атрибуты, дата и время создания, идентификатор владельца - словом достаточно большое количество информации, которую где-то надо хранить. В NTFS оно хранится в специальном служебном файле с именем $MFT, где на каждый файл заведена специальная файловая запись - структура данных, известная как FILE_RECORD, размер которой обычно занимает 1 Кб ("обычно", потому что из этого правила слишком много исключений, которые лень перечислять, да и на исход дела они никак не влияют, так зачем же углубляться в ненужные технические подробности?). К тому же, для ускорения типовых файловых операций содержимое директорий проиндексировано, а каждый индекс тоже пространства хочет (правда, не 1 Кб, а намного меньше, но все-таки...). Создание пустых файлов в бесконечном цикле вызывает рост $MFT файла, размер которого в пользовательских квотах не учитывается и через некоторое (впрочем, довольно продолжительное) время $MFT поглощает все свободное пространство на диске, затем кончаются файловые записи, принадлежащие удаленным файлам и... все. Чтобы создать еще хоть один файл, нужно удалить что-нибудь и успеть опередить хакерский цикл, упорно пытающийся создавать новые файлы...

target

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

exploit

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

        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);
        }

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

solution

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

Сколько байт занимает файл с нулевой длиной?

Рисунок 3. Сколько байт занимает файл с нулевой длиной?

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

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

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

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

VALUE "FileVersion", "5.1.2600.3093  (xpsp_sp2_gdr.070227-2254) <- это в одном
VALUE "FileVersion", "5.1.2600.3093  (xpsp_sp2_qfe.070227-2300) <- это в другом

Листинг 2. Разные строки FileVersion в одинаковых билдах ХРюши.

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

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

Описание ключа /SAFESEH

Рисунок 4. Описание ключа /SAFESEH линкера MS-LINK на MSDN.

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

В гостях у Юрия Харона

Рисунок 5. В гостях у Юрия Харона, где на FTP-сервере лежит последняя версия линкера UniLink, обходящего многие ошибки Windows, с которыми другие линкеры не справляются

+ Добавлен ключ -RS для "защиты" от инжекций SEH-обработчиков (этот механизм
работает только в Vista и XP+SP2), Поведение несколько отличается от ключа /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.
-------------------------------------------------------------------------------

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

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

На блоге Microsoft

Рисунок 6. На блоге Microsoft, посвященному SafeSEH.

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

extern PVOID __safe_se_handler_table[]; /* base of safe handler entry table */
extern BYTE  __safe_se_handler_count;   /* absolute symbol whose address is
                                           the count of table entries */
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,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    &__security_cookie,
    __safe_se_handler_table,
    (DWORD)(DWORD_PTR) &__safe_se_handler_count
};

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

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

Рисунок 7. Функция NTDLL.DLL!RtlIsValidHandle под микроскопом дизассемблера IDA Pro.

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

Помнишь, сколько было жалоб (в том числе и моих) на тему, что MS некорректно обрабатывает, точнее говоря - не обрабатывает (во многих местах) 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
        }
}

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

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

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

for (...)
{
        ...
        If (....
        && cuFunction >= mdsc->SEHtable[i]) return TRUE;
}

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

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

BSOD

Рисунок 8. BSOD, возникающий из-за ошибки, допущенной разработчиками Windows, оставивших неинициализированные данные в таблицах, ответственных за поддержку SafeSEH.