exploit-reviews-0x007

exploits review\\ 7йвыпуск

крис касперски ака мыщъх, no-email

brief:впопулярном (и при том совершенно бесплатном!) программном обеспечении для создания blog'ов — WikyBlog (www.wikyblog.com), отпочковавшимся от не менее популярной свободной энциклопедии Wikipedia (www.wikipedia.org), обнаружены множественные дыры, связанные с некорректной фильтрацией пользовательского ввода данных в полях «login» и «search», размещенных на странице Панели Управления (www.wikyblog.com/Special/Main/ControlPanel) и позволяющие атакующему выполнять произвольный HTML/JavaScript/VBScript-код в контексте уязвимого сайта, воруя cookies, содержащие данные авторизации. уязвимость была обнаружена хакерским коллективом HackersCenterITSecurityResearchTeam [HSC], описавших ее на своем сайте в короткой заметке, датируемой 1 декабря 2006 года http://www.hackerscenter.com/archive/view.asp?id=26544 и уже на следующий день перекочевавшую на Security Focus: http://www.securityfocus.com/bid/21406.

targets:уязвимости подвержена самая последняя на данный момент версия WikyBlog – 1.3.2, выпущенной 14 ноября 2006 года, о более древних версиях (доступных для скачивания на http://sourceforge.net/projects/wikyblog) пока ничего не известно;

exploit:исходный текст proof-of-conceptexploit'а (представляющий собой простейший XSS-скрипт) можно найти на сайте HTS-группы по ссылке, приведенной выше, там же находится и скриншет атакованного сайта (см. рис. 1);

solutionведущий разработчик WikiBlog'а (известный под ником Cobalt –justinms66@users.sourceforge.net) пока никак не отреагировал на сообщение об уязвимости, так что пользователям WikiBlog'ов ничего другого не остается как сидеть на измене и ждать новостей (ну или латать дыры самостоятельно, благо исходные тексты открыты);


Рисунок 1 сайт www.wikyblog.com после атаки

brief:6 октября Renaud Lifchitz (r.lifchitz@sysdream.com) – ведущий сотрудник компании Sysdream (www.sysdream.com) обнаружил ошибку переполнения в GNU gv, приводящую к возможности удаленного выполнения shell-кода в контексте уязвимого приложения. GNU gv — это де-факто стандартный вьювер ps и pdf файлов под X'ми, входящий практически во все LINUX-дистрибутивы и используемый некоторыми браузерами (в частности, Epiphany) по умолчанию, а так же входящий в состав других продуктов, одним из которых является вьювер Evince. дыра кроется в функции ps_gettext, находящейся в «ps.c» файле и представляет собой классический пример «срыва стека», возникающего при передаче слишком длинных комментариях в некоторых полях заголовка (например, поле «%%DocumentMedia»), копируемых в text-буфер фиксированного размера 257 байт со всеми вытекающими отсюда последствиями. ошибка была подтверждена разработчиками тремя днями спустя, тогда же она появилась и на http://www.securityfocus.com/bid/20978;

targets:уязвимости подвержена версия 3.6.2, остальные пока не проверялись, но, судя по всему, эта дыра присутствует и в них;

exploits:для реализации атаки имеется большое количество вполне боевых exploit'ов, вот только некоторых из них: Linux IA32 Reverse TCP Shell on 192.168.110.247:4321 —http://www.securityfocus.com/data/vulnerabilities/exploits/hello-reverseshell.ps (ps-файл) и его исходный код на Си —www.securityfocus.com/data/vulnerabilities/exploits/evince-ps-field-bof.c; генератор ps-файлов с shell-кодом на борту в исходных текстах на Си: http://www.securityfocus.com/archive/1/452868;

solutionобновленная версия GNU gv может быть скачена как непосредственно с его «родного» сайта http://www.gnu.org/software/gv, так и с сайтов производителей LINUX-дистрибутивов, большинство из которых уже выпустило свои заплатки;

Рисунок 2 Evince – один из многих просмотрщиков документов, использующих уязвимую версию GNU gv

brief:в ходе очередной проверки исходных текстов ядра Linux'а, Евгений Тео (EugeneTeo), входящий в коллектив разработчиков, обнаружил довольно экзотичную ошибку целочисленного переполнения в функции Get_FDB_Entries, о чем и поведал народу на своем blog'е в заметке «MOKB-29-11-2006: Linux 2.6.7 - 2.6.18.3 get_fdb_entries() Integer Overflow», датируемой 29 ноября 2006 года: http://projects.info-pull.com/mokb/MOKB-29-11-2006.html; дыра кроется в функции get_fdb_entries (находящийся в файле net/bridge/br_ioctl.c), которая при передаче определенных аргументов (и наличии двух или более сетевых адаптеров на машине) может затирать память ядра функцией memcpy, что (при успешной атаке) позволяет выполнять shell-код на уровне нулевого кольца, то есть с наивысшими привилегиями! и хотя возможность удаленных атак поставлена под сомнение, потенциальная угроза все-таки есть;

targets:уязвимости подвержено множество версий семейства 2.6.x.x (и, по некоторым данным некоторые версии семейства 2.4.x.x), неполный перечень которых содержится на http://www.securityfocus.com/bid/21353/info, причем в версии 2.6.18.4 уязвимость отсутствует;

exploit:на данный момент уязвимость не подкреплена никаким exploit'ом и вообще о ней очень мало что известно, что открывает большой оперативный простор для всевозможных экспериментов и исследований;

solutionодновременно с публикацией сообщения о дыре был выпущен «лечебны» патч — «bridge: fixpossibleoverflowinget_fdb_entries», выложенный на официальном сайте: http://www.kernel.org/git/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commit;h=ba8379b220509e9448c00a77cf6c15ac2a559cc7, а коллектив разработчиков ядра оперативно выпустил свежие версии 2.6.17.10 и 2.4.33.2 специально для устранения этой проблемы;

Рисунок 3 фрагмент уязвимой функции get_fdb_entries

brief:2 декабря 2006 года польским хакером по кличке h07 (h07@interia.pl) был опубликован exploit (написанный на языке Питон), подключающийся к службе печати через NetBIOS и вызывающий необрабатываемое исключение в суплере печати, приводящее к отказу в обслуживании: http://downloads.securityfocus.com/vulnerabilities/exploits/21404.py. ошибка кроется в функции GetPrinterData, экспортируемой динамической библиотекой WINSPOOL.DRV (да не введет нас ее расширение в заблуждение — никакой это не драйвер, а самый обыкновенная DLL, исполняющаяся на прикладном уровне), принимающей в одном из аргументов количество байт, которое необходимо выделить для записи конфигурации принтера, но не проверяющий его значение на «политкорретность», в результате чего при запросе >=512Мбайт функция VirtualAlloc обламывается с выделением, возвращая вместо памяти нулевой указатель, сигнализирующий об ошибке, который так же никто не проверяет и при попытке обращения к нему процессор генерирует исключение, приводящее к аварийному завершению процесса spoolsv.exe (Служба Печати), что, конечно, не смертельно, но все-таки очень неприятно. тем не менее, возможность захвата управления отсутствует, что внушает некоторый оптимизм;

Рисунок 4 боевой exploit польского хакера h07, срывающий крышу службе печати

target:уязвимости подвержена вся линейка Windows 2000 как со всеми установленными заплатками (вплоть до SP4), так и без них. о других системах ничего не известно, но, вероятнее всего, дыра присутствует и в них;

exploit:http://downloads.securityfocus.com/vulnerabilities/exploits/21404.py;

solutionMicrosoft пока не представила никаких заплаток, что не есть хорошо. Можно даже сказать, что это совсем хреново. Отключать службу печати — не предлагать, поскольку количество принтеров в большинстве контор не совпадает с количеством машин и без разделения ресурсов никак не обойтись. Можно, правда, отсечь удаленных пользователей брандмауэром, но гораздо интереснее изготовить заплатку самому!

disclose:начнем исследование с того, что заглянем в MSDN (например, тот, что идет в одном комплекте с MicrosoftVisualStudio 6.0) и посмотрим на прототип функции GetPrinterData, который выглядит так:

DWORD GetPrinterData(

HANDLE hPrinter, handle to a printer or print server LPTSTR pValueName, string that identifies the data to retrieve

LPDWORD pType, variable that receives the type of data LPBYTE pData, buffer that receives the configuration data

DWORD nSize, size, in bytes, of buffer LPDWORD pcbNeeded receives the required buffer size, in bytes

);

Листинг 1 прототип функции GetPrinterData

Параметр nSize задает размер буфера в байтах, выделяемого функцией. Вообще, это, конечно, глупость — поручать выделение памяти функции. Передача указателя на блок памяти, выделенный программистом, выглядела бы более логично и… безопасно. Но горячие парни из Microsoft программируют быстрее, чем думают, а думают они не головой, а совсем другой частью тела. Индусы, короче. Ну что с них возьмешь?! Ладно, пускай и дальше не думают. Нам, хакерам, это только на пользу идет. Достаточно передавать в качестве nSize такой размер памяти, который заведомо не может быть выделен и результат себя ждать не заставит. Вне зависимости от количества физической памяти, адресное пространство процессов на 32-разрядных платформах составляет 4 Гбайта, из которых обычно половина выделяется системе, а половина — на стек, секции кода/данных PE-файла и всех загруженных динамических библиотек. Остаток занимает куча. На серверах имеется возможность ужать систему до одного гигабайта, отдав его куче, поэтому больше 3 Гигабайт запрашивать не имеет смысла. Все равно не дадут и крах наступает уже на отметке в 512 Мбайт.

Рисунок 5 результаты поиска функции OpenPrinterEx в Platform SDK

Но чтобы реализовать атаку, необходимо в первом параметре hPrinter передать дескриптор принтера, открываемый (по документации!) функцией OpenPrinter, экспортируемый все той же самой динамической библиотекой WINSPOOL.DRV. Вот только exploit использует не OpenPrinter, а OpenPrinterEx, которой ни в старом MSDN (тот, что идет с VisualStudio 6.0), ни в свежем Platform SDK что-то не наблюдается. Недокументированная функция? Но в таблице экспорта WINSPOOL.DRV она отсутствует (в чем легко убедится с помощью утилиты DUMPBIN.EXE «DUMPBIN /EXPORTS WINSPOOL.DRV > out») и возникает резонный вопрос — как же, черт возьми, все это работает?! А в том, что exploit работает — можно не сомневаться.

class OpenPrinterEx(Structure):

alignment = 4

opnum = 69

structure = (

('printer', ':', B1),

('null', '<L=0'),

('str', '<L=0'),

('null2', '<L=0'),

('access', '<L=0'),

('level', '<L=1'),

('id1', '<L=1'),

('level2', '<L=10941724'),

('size', '<L=28'),

('id2', '<L=0x42424242'),

('id3', '<L=0x43434343'),

('build', '<L=2600'),

('major', '<L=3'),

('minor', '<L=0'),

('processor', '<L=0xFFFFFFFF'),

('client', ':', B2),

('user', ':', B2),

)

class GetPrinterData(Structure):

alignment = 4

opnum = 26

structure = (

('handle', '%s'),

('value', ':', B2),

('offered', '<L'),

)

query = OpenPrinterEx()

printer = «\\\\%s\x00» % (host)

query['printer'] = B1()

query['printer']['id'] = 0x41414141

query['printer']['max'] = len(printer)

query['printer']['actual'] = len(printer)

query['printer']['str'] = printer.encode('utf_16_le')

query = GetPrinterData()

value = «blah_blah\x00»

query['handle'] = handle

query['value'] = B2()

query['value']['max'] = len(value)

query['value']['actual'] = len(value)

query['value']['str'] = value.encode('utf_16_le')

query['offered'] = memory_size

Листинг 2 фрагмент exploit'а, демонстрирующий технику вызова GetPriterData, принимающую дескриптор, возвращенный OpenPrinterEx

Поиск по сайту Microsoft дает всего лишь одну ссылку на OpenPrinterEx, вскользь упоминаемую при описании структуры PRINTPROVIDOR в DDK и реализуемую драйвером принтера: http://msdn2.microsoft.com/en-us/library/aa506552.aspx. Чуть-чуть более подробное описание содержится в технической документации на Самбу (см. раздел «SambaPrintingInternals» — http://samba.org/samba/docs/man/Samba-Developers-Guide/devprinting.html), после прочтения которого становится ясно, что exploit вызывает OpenPrinterEx через механизм удаленного вызова процедур – RemoteProcedureCall (RPC), без обращения к WINSPOOL.DRV. Собственно говоря, и в самом WINSPOOL.DRV функция OpenPrinter реализована через RPC (см. листинг 3).

.text:777D47B9 sub_777D47B9proc near; CODE XREF: sub_777D4634+68↑p

.text:777D47B9

.text:777D47B9 arg_0= dword ptr 4

.text:777D47B9

.text:777D47B9leaeax, [esp+arg_0]

.text:777D47BDpusheax

.text:777D47BEpushoffset dword_777D1AD8

.text:777D47C3pushoffset off_777D1A28

.text:777D47C8callNdrClientCall2

.text:777D47CDaddesp, 0Ch

.text:777D47D0retn14h

.text:777D47D0 sub_777D47B9endp

Листинг 3 фрагмент WINSPOOL.DRV, реализующий функцию OpenPrinter через механизм RPC

При создании exploit'а на эти тонкости можно не обращать внимания, достаточно лишь взять любой сырец, печатающий на принтере через NetBIOS (в смысле — удаленно) и сразу же после OpenPrinter/OpenPrinterEx воткнуть вызов GetPriterData с некорректным значением nSize. Какая в конце концов разница какие механизмы задействует Windows и какие функции при этом реально вызываются. Главное, что незалатанный спулер печати падает! А это — хорошо! Ну, кому-то, может быть и хорошо, а тому, кто падает — как-то не очень. Особенно, если падать приходится много раз на дню при печати многостраничного документа. Но как только мы захотим заштопать систему своими лапами, тут уже абстрагироваться от анатомических подробностей внутренней реализации Windows никак не получится.

Рисунок 6 дизассемблирование динамической библиотеки WINSPOOL.DRV, являющийся всего лишь «оберткой» вокруг реальных принтерных функций, вызываемых через механизм RPC

На первый взгляд проблема решается легкой правкой WINSPOOL.DRV — ставим в начало функции GetPrinterData переходник на свободное место, достаточно просторное для размещения нескольких машинных команд, проверяющих корректность аргумента nSize. Естественно, придется скорректировать контрольную сумму файла WINSPOOL.DRV (что можно сделать при помощи утилиты EDITBIN.EXE, входящей в состав SDK) и усмирить SFC, путем копирования исправленной версии WINSPOOL.DRV в WINNT\System32\dllcache (естественно, делать это надо при выключенном SFC или загрузившись с другой системы). Вот только… эффект от проделанной операции будет, мягко говоря, нулевой. А все потому, что WINSPOOL.DRV используется только локально, а при печати через NetBIOS все вызовы идут через RPC и перехватывать следует NdrClientCall2 из RPCRT4.DLL, описание которой отсутствует в SDK, но, к счастью, IDA Pro знает ее прототип: CLIENT_CALL_RETURN _imp_NdrClientCall2(PMIDL_STUB_DESCpStubDescriptor, PFORMAT_STRINGpFormat,…), где pFormat — указатель на строку параметров, описывающих вызываемый метод и его параметры. В частности, метод GetPrinterData проходит под кодовым обозначаем 1Ah (см. листинг 2) и передается в 6'ом, считая от нуля, байте форматной строки (которая на самом деле никакая не строка, поскольку содержит внутри себя нули и прочие непечатные символы). Параметр nSize передается через стек следующим образом (см. листинг 4):

.text:777D53D3push [ebp+pcbNeeded]

.text:777D53D6push [ebp+nSize]

.text:777D53D9push [ebp+pData]

.text:777D53DCpush [ebp+pType]

.text:777D53DFpush [ebp+pValueName]

.text:777D53E2mov eax, [ebp+var_44]

.text:777D53E5push dword ptr [eax+4]

.text:777D53E8call sub_777D542F

.text:777D542F sub_777D542Fproc near; CODE XREF: GetPrinterDataW+A4↑p

.text:777D542F

.text:777D542F arg_0= dword ptr 4

.text:777D542F

.text:777D542Flea eax, [esp+arg_0]

.text:777D5433push eax

.text:777D5434push offset pFormat

.text:777D5439push offset pStubDescriptor

.text:777D543Ecall NdrClientCall2

.text:777D5443add esp, 0Ch

.text:777D5446retn 18h

.text:777D5446 sub_777D542F endp

.text:777D20C0 pFormatdb 0; DATA XREF: sub_777D542F+5↓o

.text:777D20C1db 48h ; H

.text:777D20C2db 0

.text:777D20C3db 0

.text:777D20C4db 0

.text:777D20C5db 0

.text:777D20C6db 1Ah ; метод GetPrinterData

.text:777D20C7db 0

Листинг 4 вызов GetPrinterData черезмеханизм RPC

Таким образом, в момент вызова функции NdrClientCall2, указатель на параметры лежит в стеке по смещению [ESP-0Ch], а по смещению +14h от его начала находится nSize, который мы и должны проверять на корректность. Но прежде необходимо проанализировать указатель на форматную строку, находящуюся в стеке по смещению [ESP-08h], убедившись, что 6'й байт равен 1Ah, т.е. вызывается метод GetPrinterData, а не что-то иное. Ассемблерный код труда написать не составит и каждый сможет это сделать сам. Главное — не забывать о проверках на нулевые указатели, чтобы исправляя одну ошибку, не создавать на ее месте десяток новых. Так же, проверочный код нельзя размещать в секции данных — хоть там полно свободного места, но на машинах с аппаратной поддержкой DEP это работать не будет и тут же возникнет исключение. Поскольку, механизм RPC – это, фактически, фундамент, на котором базируется Windows NT, править RPCRT4.DLL стоит с огромной осторожностью, поскольку, если он окажется поврежден, загрузить систему не удастся. С другой стороны, при правке файла на диске, мы всегда сможем сделать откат, воткнув винчестер с поврежденной NT в компьютер вторым, или воспользовавшись консолью восстановления (находится на дистрибутивном CD) и скопировав оригинальный RPCRT4.DLL поверх исправленного.

В качестве альтернативного варианта, можно воспользоваться правкой RPCRT4.DLL в памяти по методике, описанной в статье «метафизика wmf файлов»: прописываем в следующей ветке реестра HKLM\Software\Microsoft\WindowsNT\CurrentVersion\Windows\AppInit_DLLs специально созданную для этих целей динамическую библиотеку, которая будет отображаться на адресное пространство каждого процессора. Внутри DllEntry мы выполняем загрузку RPCRT4.DLL через LoadLibrary, правим ее и все! Если приложение не использует RPCRT4.DLL (что навряд ли), мы просто теряем немного памяти. Если же приложение подгружает RPCRT4.DLL через таблицу импорта или через LoadLibrary (что намного более вероятно), оно просто отсылается к уже загруженной (и исправленной!) копии RPCRT4.DLL и хакер не имеет никаких шансов атаковать систему! Естественно, по сравнению с правкой на диске, время загрузки файлов и потребность системы в памяти ощутимо возрастут, а риск угробить систему — все тот же. Если динамическая библиотека, осуществляющая правку, будет реализована с ошибками, система упадет прежде, чем загрузиться пользовательский интерфейс и для исправления ситуации придется к помощи все той же консоли восстановления (втыкать винчестер с убитой NT «вторым»), удаляя нашу динамическую библиотеку на хрен. Впрочем, это уже детали. Главное, что изготовление заплаток своим лапами — вполне возможно и пока другие ждут помощи от Microsoft, правильные хакеры защищаются самостоятельно (залатанный RPCRT4.DLL из-за долбанных лицензионных ограничений к статье, естественно, не прилагается), во всем полагаясь на свой хвост, который, если верить энциклопедиям, может достигать 45 см длины — http://ru.wikipedia.org/wiki/%D0%9C%D1%8B%D1%88%D1%8C. Ну, на счет 45 см это они, конечно, загнули. В природе таких мышей не встречается, иначе меня начнет мучить жуткий комплекс неполноценности.

Рисунок 7 мышь познается по длине хвоста