Автор: (c)Крис Касперски ака мыщъх
18 апреля 2006 года обнаружилась дыра в процессорах AMD K7/K8, позволяющая одному процессу заглянуть внутрь сопроцессорного контекста другого процесса, что ведет к утечке данных и упрощает атаку на криптографические системы: www.securityfocus.com/bid/17600.
Для быстрого переключения контекста Linux и BSD-системы используют пару команд fxsave/fxrstor, сохраняющих/восстанавливающих регистры сопроцессора в/из оперативной памяти. Коварство fxrstor заключается в том, что она сохраняет указатель команд (FIP), указатель данных (Data Pointer) и опкод последней инструкции только в том случае, если бит ES (exception summary) в статусном слове сопроцессора x87 установлен. При переключении контекста ось не обнуляет эти данные и они становятся доступны "посторонним" процессам: kernel.org/pub/Linux/kernel/v2.6/ChangeLog-2.6.16.9;
Строго говоря, это не ошибка, а документированная особенность процессоров AMD, на которую прежде никто не обращал внимания, поскольку с процессорами Intel в этом плане все ок. Уязвимость затрагивает все Linux и BSD-системы, а возможно, и Windows.
Для реализации атаки exploit'а не требуется, достаточно просто выполнять команду fxsave в цикле, надеясь поймать что-то интересное.
Разработчики Linux/BSD уже выпустили патчи (securityfocus.com/bid/17600/solution), очищающие ES-бит и загружающие фиктивный double на стек сопроцессора. Вот фрагмент заплатки для BSD:
static double dummy = 0.0; static fpu_clean_state() { u_short status;fnstsw(&status); if (status & 0x80) fnclex(); __asm __volatile("ffree %%st(7);fld %0" : : "m" (dummy)); }
Рисунок 1. Значения EIP/RIP, FOP и Data Pointer сохраняются, только если бит ES установлен.
Jari Kirma - один из разработчиков FreeBSD - обратил внимание, что на AMD x86-64 непривилегированный пользователь может получить непосредственный доступ к оборудованию с прикладного уровня. Это объясняется тем, что x86-64 имеет два механизма разделения привилегий: код, исполняющийся на уровне ядра, имеет доступ ко всем портам ввода/вывода и обычно является "посредником" между железом и user-mode, что не всегда удобно, поэтому процессор поддерживает специальную карту, позволяющую "открыть" часть портов, разрешив к ним доступ с прикладного уровня (подобная карта имеется и у Intel'а, но там она по умолчанию заблокирована).
Суть в том, что вплоть до FreeBSD/amd64 5.4-RELEASE эта таблица инициализировалась неправильно, образуя огромную дыру в системе безопасности. Злоумышленник (или некорректно работающий код) может вызывать отказ в обслуживании, разрушать или похищать информацию и т.д., подробнее об этом можно прочитать на: www.security.freebsd.org/advisories/FreeBSD-SA-05:03.amd64.asc.
Уязвимость затрагивает только операционную систему FreeBSD версии 5.4 или ниже, работающую на платформе AMD x86-64. На все остальные системы эта дыра не распространяется.
Для реализации данной уязвимости exploit не требуется, достаточно установить обработчик исключений и последовательно перебрать все порты, определяя - какие из них доступны на запись/чтение, а какие - нет.
Cуществует несколько решений этой проблемы, например - обновить систему до версии 5-STABLE или наложить заплатку, которую можно скачать с сервера ftp://ftp.FreeBSD.org/pub/FreeBSD/CERT/patches/SA-05:03/amd64.patch, после чего перекомпилировать ядро.
Рисунок 2. AMD-64 собственной персоной.
8 августа 2006 года сразу два специалиста Reed Arvin из Canaudit, Inc и Matt Miller из Leviathan Security Group обратили внимание на то, что процесс winlogon.exe (ответственный за регистрацию пользователей в системе, блокировку компьютера и т.д.) начинает поиск динамических библиотек с домашнего каталога пользователя и только потом переходит к системному каталогу Windows, поэтому любой пользователь может легко повысить свои привилегии до SYSTEM, если положит в свой домашний каталог "заряженную" DLL. Удаленная атака легко реализуется через зловредную Web-страничку, запрашивающую имя пользователя и пароль. IE их высылает автоматически и если компьютер допускает удаленные подключения (что характерно для серверов), злоумышленнику достаточно закинуть DLL в HOME, выйти из системы и войти еще раз. Подробности тут: www.microsoft.com/technet/security/Bulletin/MS06-051.mspx.
Уязвимости подвержены Windows 2000, XP SP1, XP SP 2, Server 2003 и Server 2003 SP1.
Для реализации данной атаки exploit не требуется.
Microsoft уже выпустила заплатки для всех уязвимых систем, выложив их на download-сервер. Если же по каким-то причинам он недоступен, можно воспользоваться альтернативным решением, задействовав режим безопасного поиска динамических библиотек. Для этого необходимо запустить редактор реестра (regedt32.exe), открыть следующую ветвь реестра - HKLM\SYSTEM\CurrentControlSet\Control\Session Manager и добавить значение "SafeDllSearchMode" типа DWORD, установленное в 1, после чего перезагрузить машину.
Рисунок 3. Процесс winlogon.exe по умолчанию ищет динамические библиотеки в домашней директории пользователя.
За Microsoft уже закрепилась устойчивая репутации компании, никогда не исправляющей крупные ошибки с первого раза. Вот и сейчас, когда эпидемия MSBLAST еще свежа в памяти (и мутированные черви до сих пор бродят по Сети), в RPC (Remote Procedure Call - механизм удаленного вызова процедур) обнаружилась новая ошибка переполнения, допускающая удаленную засылку shell-кода с захватом управления на правах SYSTEM.
Мы не знаем, кто первый обнаружил ошибку, но 8 августа 2006 года Американское Общество US-CERT (United States Computer Emergency Readiness Team) и Калифорнийский Институт SANS (SysAdmin, Audit, Network, Security) практически одновременно выслали свои рапорты в Microsoft. Публичный пресс-релиз (www.kb.cert.org/vuls/id/650769) не раскрывал никаких технических деталей, но несмотря на это, 10 августа уже появился рабочий exploit, являющийся частью проекта Metasploit Framework: www.metasploit.com.
Microsoft присвоила уязвимости критический уровень безопасности и в тот же день выпустила бюллетень MS06-040: microsoft.com/technet/security/Bulletin/MS06-040.mspx.
Рисунок 4. Стартовый экран Metasploit Framework.
Microsoft занесла в список уязвимых систем Windows 2000, XP SP1, XP SP2, Server 2003 и Server 2003 SP1, в то время как создатели Metasploit Framework exploit'а претендовали на "поддержку" NT 4.0, Windows 2000 SP0-SP4, XP SP0-SP1, подчеркивая что удаленное выполнение shell-кода на XP SP2/Windows 2003 SP1 невозможно и максимум, что можно устроить, это отказ в обслуживании. неудачная атака вызывает перезагрузку Windows 2000 и остановку всех SMB-сервисов на XP. Парни из Microsoft Security Response Center Blog провели свое собственное расследование и выяснили, что удаленное выполнение кода возможно только на Windows 2000 и XP SP 1. Им не удалось атаковать ни XP SP 2, ни Server 2003, ни Server 2003 SP 1: blogs.technet.com/msrc/archive/2006/08/11/446078.aspx (однако следует помнить, что между "не удалось атаковать" и "атаковать невозможно" огромная разница!).
Рисунок 5. Блог команды Security Response Center.
Готовый exploit (написанный на языке Perl) можно свободно скачать по адресам http://metasploit.com/projects/Framework/exploits.html#netapi_ms06_040 и http://milw0rm.com/exploits/2162.
Рисунок 6. Exploit MS Windows NetpIsRemote() Remote Overflow на сайте MilwOrm.
Microsoft уже выпустила заплатки для большинства своих систем, доступные через службу Windows Update, однако при желании можно обойтись и без них: достаточно заблокировать SMB-трафик из внешней Сети, для чего необходимо закрыть два TCP-порта - 139 и 445 (однако это сделает невозможной работу приложений, работающих через SMB).
Рисунок 7. Тянем заплатку с сервера Microsoft.
Механизм RPC является низкоуровневым средством межкомпьютерного взаимодействия, предоставляющим прозрачный механизм удаленного вызова процедур, который с точки зрения прикладной программы выглядит так, как будто процедура находится на локальной машине.
На базе RPC построены многие системные сервисы и в частности, SMB (Server Message Block), реализующий именованные каналы (named pipes) и использующий PRC в качестве транспорта. В свою очередь, SMB используется для удаленного доступа к файлам, папкам и принтерам.
И все бы ничего, но в функции NetpIsRemote(), сосредоточенной в библиотеке NetApi32.DLL, допущена ошибка контроля границ, приводящая к традиционному стековому переполнению, с возможностью подмены адреса возврата. на системах с активным DEP, предотвращающим выполнение кода в неисполняемых областях памяти, приходится хитрить, атакуя жертву по одному из сценариев, описанных в статье "Переполнение буфера на системах с неисполняемым стеком", которую можно найти на прилагаемом к журналу диске или скачать с мыщъх'ого ftp://nezumi.org.ru (напоминаю, что сервер установлен на рабочей машине и потому доступен не все время, а только тогда, когда мыщъх шевелит хвостом).
Рисунок 8. Мыщъх'иный ftp на раздаче.
Если бы NetpIsRemote() была документированной функцией, мы бы просто передавали ей аргументы различной длины, пытаясь вызывать переполнение, но к сожалению ее прототип неизвестен, а потому приходится прибегать к тяжелой артиллерии, то есть к дизассемблированию.
Загружаем библиотеку NetApi32.DLL в IDA Pro и начинаем исследовать функцию NetpIsRemote(), пытаясь "глазами" найти место, в котором происходит переполнение, в первую очередь обращая внимания на циклы, копирующие блоки памяти (типа mov ecx,[eax]/mov [ebx],eax/add eax,4) или вызовы функций memcpy(), memmove(), strcpy(), wcscpy() и т.д.
Функция NetpIsRemote() выглядит обманчиво маленькой, но если присмотреться повнимательнее, можно обнаружить множество условных переходов, ведущих на CHUNK'и (то есть, на ее продолжение), совокупный объем которых довольно велик и затруднителен для анализа.
Рисунок 9. Обольстительно короткая NetpIsRemote().
Беглый поиск обнаруживает пару подозрительных функций wcscpy() и wcscat(), однако wcscpy() отпадает сразу, поскольку копирует строку фиксированной длины, жестко прошитую внутри NetApi32.DLL, а wcscat() восходит к функции I_NetNameCanonicalize(), перспективы переполнения которой на данном этапе исследований весьма туманны и неясны (см. листинг 1).
7CD25C24 lea eax,[ebp+var_20C] 7CD25C2A push esi 7CD25C2B push eax 7CD25C2C mov eax,[ebp+arg_4] 7CD25C2F push dword ptr [eax+4] 7CD25C32 push edi 7CD25C33 call I_NetNameCanonicalize 7CD25C38 cmp [ebp+var_4],edi 7CD25C3B jz loc_7CD25C48 7CD25C3D mov eax,[ebp+arg_C] 7CD25C40 lea ebx,[ebp+var_20C] ... 7CD25C6E push asc_7CD17CF4 7CD25C73 push [ebp+arg_C] 7CD25C76 call ds:__imp_wcscpy 7CD25C7C pop ecx 7CD25C7D pop ecx 7CD25C7E push ebx 7CD25C7F push [ebp+arg_C] 7CD25C82 call ds:__imp_wcscat
Листинг 1. Дизассемблерный фрагмент NetpIsRemote() с потенциально опасными функциями wcscpy() и wcscat().
Но ведь в нашем распоряжении есть готовая заплатка! Давайте, чтобы не блуждать впотьмах, просто сравним дизассемблерные листинги функции NetpIsRemote() до и после обновления - все изменения станут сразу очевидны!
Скачиваем заплатку с официального сервера Microsoft (для моей Windows 2000 SP4 это download.microsoft.com/download/9f06d3f3-87d0-445d-8a41-d2ffef9a40ba/windows2000-kb921883-x86-rus.exe), представляющую собой обыкновенный самораспаковывающийся cab-архив. В прошлом ревю мы показывали, как извлечь его содержимое с помощью hiew'а, однако есть и более короткий путь - достаточно "скормить" исполняемый файл RAR'у и все упакованные файлы предстанут перед нашими глазами!
Рисунок 10. Распаковка пакета обновления с помощью RAR'a.
Извлекаем из архива NetApi32.DLL, переименовываем ее, например, в NetApi32-new.DLL и загружаем в дизассемблер. Какие различия между старой и новой версией NetpIsRemote() мы увидим? В первую очередь, это гнусная выходка компилятора, инвертировавшего условные переходы, в результате чего ветви программы поменялись местами:
JNZ branch_A -> JZ branch_B branch_B branch_A
Листинг 2. Инверсия ветвей в старой и новой версиях NetApi32.DLL.
Непонятно? Что ж, покажем это на конкретном примере:
Листинг 3. Конкретный пример инверсии ветвей, слева показана старая версия NetApi32.DLL, справа - новая. Красным цветом выделена ветвь А, синим - B.
Приведенный фрагмент функции NetpIsRemote() идентичен в обоих версиях, но порядок машинных команд поменялся местами, ослепляя большинство анализаторов типа fc.exe и wnindiff, которыми мы пользовались ранее. Как же быть? А вот как! Берем блок A из старой версии NetApi32.DLL и ищем похожий на него блок в NetApi32-new.DLL, затем добиваемся, чтобы первые строки блоков в обоих окнах дизассемблера совпадали (что легко осуществляется мышью или курсорными клавишами). Теперь начинаем быстро переключаться с одного окна дизассемблера на другое по <ALT-TAB>, при этом несовпадающие строки начинают интенсивно мерцать, выдавая различия с головой (аналогичным образом астрономы ищут и новые звезды, т.е. звезды, меняющие свой блеск - попеременно проецируя на экран несколько слайдов, снятых в разное время, они смотрят - не начнет ли какая точка ритмично мигать; способ древний, как мамонт, но очень надежный).
Действуя таким образом, мы довольно быстро найдем функцию wcslen(), которой не было ранее и которая контролирует длину строки перед копированием:
Листинг 4. Старая (слева) и новая (справа) версии NetpIsRemote().
Ага! Вот она! Точнее - оно! Переполнение буфера, в смысле! Между функциями I_NetNameCanonicalize() и wcscpy()/wcscat() в обновленной версии NetApi32.DLL внедрен вызов wcslen(), проверяющий размер строки, возращенный I_NetNameCanonicalize() перед его копированием в... блок памяти, переданнй по указателю через аргумент функции (arg_C в старой и arg_8 в новой версии). Сама же I_NetNameCanonicalize() также принимает на "грудь" указатель, извлекаемый из структуры, переданной NetpIsRemote() в качестве аргумента.
Все это создает крайне благоприятные условия для реализации атаки и засылки shell-кода. Достаточно "всего лишь" передать слишком длинную строку функции I_NetNameCanonicalize() вместе с указателем на крошечный буфер, неспособный вместить "канонизированное" имя. Локальный exploit пишется без проблем, ведь функция NetpIsRemote() экспортируется и все, что нам нужно, это восстановить ее прототип.
Однако локальный exploit - не слишком-то полезная штука, гораздо больший интерес вызывают удаленные exploit'ы. Можем ли мы использовать эту уязвимость для атаки и если да, то как?
Переходим в начало функции NetpIsRemote() и нажав <ALT-V> [view], <O> [open sub-view], <O> [cross references], смотрим по перекрестным ссылкам - какие функции ее вызывают. Большинство функций семейства I_*() доступны через SMB Remote APIs, т.е. их можно вызывать удаленно по RPC-протоколу, инкапсулированному в SMB. Подробнее об этом можно прочитать на форуме immunitysec'а в сообщении известного хакера H D Moore'а: lists.immunitysec.com/pipermail/dailydave/2006-August/003400.html, а в MSDN можно найти готовый пример реализации Bloodhound'а Parser DLL for SMB Remote APIs, расположенный в файле REMAPI.C и основанный на функции CreateProtocol(), описанной в windowssdk.msdn.microsoft.com/en-us/library/ms707941.aspx.
Рисунок 11. СreateProtocol() на MSDN.
Основная ценность REMAPI.C заключается в том, что имена I_*() функций (совпадающих с именами команд SMB-протокола) в нем перечислены "прямым текстом". Фактически, это единственный источник информации, который у нас только есть.
Рисунок 12. REMAPIC.C – основной источник информации по SMB-командам.
Просматривая список перекрестных ссылок, необходимо отобрать функции, присутствующие в файле REMAPI.C: I_NetPathType(), I_NetNameValidate(), I_NetNameCanonicalize(), I_NetNameCompare() и I_NetPathCompare().
Рисунок 13. Добываем список перекрестных ссылок на NetpIsRemote().
Теперь необходимо проанализировать дизассемблерный листинг каждой из них и найти хотя бы одну функцию, передающую NetpIsRemote() свой собственный аргумент вместе с указателем на локальный буфер фиксированного размера. Как ни смешно, но этой функцией оказывается... I_NetNameCanonicalize(). Нет, это не ошибка! I_NetNameCanonicalize() вызывает NetpIsRemote(), а NetpIsRemote() вызывает I_NetNameCanonicalize() - вот такая, значит, рекурсия у нас получается. Руки бы поотрывать тем, кто это писал (вместе с хвостом)! Но довольно эмоций! Ниже приведен дизассемблерный фрагмент I_NetNameCanonicalize(), в котором и происходит переполнение.
7CD1F980 loc_7CD1F980: ; CODE XREF: I_NetNameCanonicalize+A4?j 7CD1F982 push 104h ; размер буфера(?) 7CD1F987 lea eax,[ebp+var_230] ; указатель на... 7CD1F98D push eax ; ...локальный буфер. 7CD1F98E lea eax,[ebp+var_20] ; указатель на другой... 7CD1F991 push eax ; ...локальный буфер 7CD1F992 push [ebp+arg_0] ; аргумент функции 7CD1F995 call NetpIsRemote ; вызов уязвимой функции
Листинг 5. Вот, где происходит переполнение.
Таким образом, все, что нам нужно - это вызвать функцию I_NetNameCanonicalize(), также являющуюся командой протокола SMB и передать ей слишком длинную строку, вызывающую срыв стека, с подменой адреса возврата, что легко осуществляется из любой точки Сети, если, конечно, внешний SMB-трафик не отсекается брандмауэром.
Проблема в том, что I_NetNameCanonicalize() ни хрена не документирована и ее прототип неизвестен. Кое-какую информацию удается нарыть в файле netapi32.inc, входящем в состав ассемблера MASM, но она слишком малоинформативна.
I_NetNameCanonicalize PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
Листинг 6. Прототип I_NetNameCanonicalize().
К счастью, у нас есть Bloodhound Parser, заботливо предоставленный Microsoft, позволяющий сниффить сеть и декодировать пролетающие SMB-команды. Аргументы, правда, не декодируются, но об их назначении нетрудно догадаться и самостоятельно. Еще можно дизассемблировать I_NetNameCanonicalize(), разобравшись - какие аргументы она ожидает. Это рутинная и неинтересная работа, к тому же уже проделанная авторами metasploit exploit'а, так что не будем повторяться, а лучше позаимствуем готовый "движок" и адаптируем его для собственных нужд.