exploits-review-0x16

exploits review\\ 16h выпуск

крис касперски ака мыщъх, a.k.a. nezumi, a.k.a elraton, a.k.a. souriz, no-email

чем глубже в windows, тем больше дыр, поток которых прекращается не собирается, отрывая весьма соблазнительные перспективы для хакерских атак, как локальных, так и удаленных, к которым ни Microsoft, ни сторонние разработчики не готовы и которым придется конкретно напрячься, чтобы разрулить ситуацию, а мы к тому времени нароем новые дыры, так что никто без работы не останется.

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 трафик;

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

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:отсутствует;

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

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'а, написанного на языке Си и создающего файлы нулевого размера в бесконечном цикле:

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 сколько байт занимает файл с нулевой длинной?

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 (не столько про ключ, сколько про то зачем он) - тогда будет проще объяснить».

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

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

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


build 3.13 [ulnb0313.zip]

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

  1. Отсутствие ключа - аналог /SAFESEH:NO
  2. При указании ключа коллекционируется информация об обработчиках (аналог отсутствия ключа у ms-link), однако при отсутствии такой информации компоновка не отвергается (как у ms-link), а модуль маркируется как программа (dll) в которой запрещён SEH, При этом выдаётся информационное сообщение (из группы w-inf).
  3. Строго в соответствии с документацией допустимы ссылки на «внешние» обработчики (handler), в отличии от ms-link где такая ситуация приводит к ошибке. Это имеет значение при необходимости работы с «нестандартными» обработчиками и/или с библиотеками в которых их назначение отсутствует. Ссылки на внешние обработчики, которые НЕ определены в компонуемом порождают 'Unresolved external';
  4. Для упрощения работы с библиотеками Borland (в которых нет информации об обработчиках) делается «автоматическое» назначение обработчиков (они у Borland стандартные) - проверялось для C/CPP bc v5 и bcb v5/v6/bds4.
  5. Указание ключа -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, но только в Висле он был доведен но минимально работающего состояния.

Рисунок 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 и отсутствии отладчиков :)» Рисунок 8 BSOD возникающий из-за ошибки, допущенной разработчиками Windows, оставивших неинициализированные данные в таблицах, ответственных за поддержку SafeSEH