Обзор эксплойтовКрис Касперски Хакер, номер #112, стр. 112-054-1 Чем глубже в Windows, тем больше дыр. Открывая весьма соблазнительные перспективы для хакеров, прекращаться этот поток дыр не собирается. Пока Microsoft и сторонние разработчики ищут противодействие локальным и удаленным атакам, мы к тому времени нароем новые дыры. Так что никто без работы не останется. Microsoft Windows – unicast/multicast traffic and firewallsBriefЭкспериментируя с программами потокового аудио/видео вещания (главным образом с 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 трафик и пускать его в обход брандмауэра. TargetsNT 3.51 SP2 и выше, SyGate Personal Firewall 4.5 и некоторые другие брандмауэры. ExploitsВ качестве «тестера», определяющего способность брандмауэра распознавать и блокировать различные виды unicast/multicast трафика, можно использовать бесплатную программу VidoeLAN, кстати говоря, распространяемую в исходных текстах: www.videolan.org. SolutionИспользовать в качестве шлюза для доступа в Сеть любую Linux или BSD-подобную систему, чей штатный брандмауэр влет бьет любой unicast/mulicast трафик. Microsoft Windows – обход ASLRBriefВ Висте появилась рандомизация адресного пространства. Она существенно затрудняет внедрение зловредного кода в «доверенные» процессы типа 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 поглощает все свободное пространство на диске, затем кончаются файловые записи, принадлежащие удаленным файлам и... все! Чтобы создать еще хоть один файл, нужно что-нибудь удалить и успеть опередить хакерский цикл, упорно пытающийся создавать новые файлы… TargetW2K и выше (в 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 и отсутствии отладчиков». |