Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР #97, ЯНВАРЬ 2007 г.

Обзор эксплойтов

Крис Касперски

Хакер, номер #097, стр. 056

WikyBlog: HTML-инжектинг

Brief

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

Targets

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

Exploit

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

Solution

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

GNU gv: удаленное переполнение буфера

Brief

6 октября 2006 года 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 байт со всеми вытекающими отсюда последствиями. Ошибка была подтверждена разработчиками тремя днями позже, тогда же она появилась и на www.securityfocus.com/bid/20978.

Targets

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

Exploits

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

Solution

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

Linux Keel: удаленное переполнение буфера

Brief

В ходе очередной проверки исходных текстов ядра Linux'а Евгений Тео (Eugene Teo), входящий в коллектив разработчиков, обнаружил довольно экзотичную ошибку целочисленного переполнения в функции 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), неполный перечень которых содержится на www.securityfocus.com/bid/21353/info. В версии 2.6.18.4 уязвимость отсутствует.

Exploit

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

Solution

Одновременно с публикацией сообщения о дыре был выпущен «лечебный» патч – «bridge: fix possible overflow in get_fdb_entries», выложенный на официальном сайте: www.keel.org/git/?p=linux/keel/git/torvalds/linux-2.6.git;a=commit;h=ba8379b220509e9448c00a77cf6c15ac2a559cc7, а коллектив разработчиков ядра оперативно выпустил свежие версии 2.6.17.10 и 2.4.33.2 специально для устранения этой проблемы.

MS Windows: отказ в обслуживании из-за переполнения в спулере печати

Brief

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

Target

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

Exploit

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

Solution

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

Disclose

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

Прототип функции 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

);

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

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

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

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 = "\\%sx00" % (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_blahx00"

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

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

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

.text:777D47B9 sub_777D47B9 proc near ; CODE XREF: sub_777D4634+68^p

.text:777D47B9

.text:777D47B9 arg_0 = dword ptr 4

.text:777D47B9

.text:777D47B9 lea eax, [esp+arg_0]

.text:777D47BD push eax

.text:777D47BE push offset dword_777D1AD8

.text:777D47C3 push offset off_777D1A28

.text:777D47C8 call NdrClientCall2

.text:777D47CD add esp, 0Ch

.text:777D47D0 retn 14h

.text:777D47D0 sub_777D47B9 endp

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

На первый взгляд, проблема решается легкой правкой WINSPOOL.DRV: ставим в начало функции GetPrinterData переходник на свободное место, достаточно просторное для размещения нескольких машинных команд, проверяющих корректность аргумента nSize. Естественно, придется скорректировать контрольную сумму файла WINSPOOL.DRV при помощи утилиты EDITBIN.EXE, входящей в состав SDK, и усмирить SFC путем копирования исправленной версии WINSPOOL.DRV в WINNTSystem32dllcache (естественно, делать это надо при выключенном SFC, или загрузившись с другой системы). Вот только эффект от проделанной операции будет, мягко говоря, нулевой. А все потому, что WINSPOOL.DRV используется только локально, а при печати по NetBIOS все вызовы идут через RPC, и перехватывать следует NdrClientCall2 из RPCRT4.DLL. Ее описание отсутствует в SDK, но, к счастью, IDA Pro знает ее прототип:

CLIENT_CALL_RETU _imp_NdrClientCall2(PMIDL_STUB_DESC pStubDescriptor, PFORMAT_STRING pFormat, ...)

Здесь pFormat - указатель на строку аргументов, описывающих вызываемый метод и его параметры. В частности, метод GetPrinterData проходит под кодовым обозначаем 1Ah и передается в шестом, считая от нуля, байте форматной строки (которая, на самом деле, никакая не строка, поскольку содержит внутри себя нули и прочие непечатные символы). Параметр nSize передается через стек следующим образом:

Вызов GetPrinterData через механизм RPC

.text:777D53D3 push [ebp+pcbNeeded]

.text:777D53D6 push [ebp+nSize]

.text:777D53D9 push [ebp+pData]

.text:777D53DC push [ebp+pType]

.text:777D53DF push [ebp+pValueName]

.text:777D53E2 mov eax, [ebp+var_44]

.text:777D53E5 push dword ptr [eax+4]

.text:777D53E8 call sub_777D542F

.text:777D542F sub_777D542F proc near ; CODE XREF: GetPrinterDataW+A4^p

.text:777D542F

.text:777D542F arg_0 = dword ptr 4

.text:777D542F

.text:777D542F lea eax, [esp+arg_0]

.text:777D5433 push eax

.text:777D5434 push offset pFormat

.text:777D5439 push offset pStubDescriptor

.text:777D543E call NdrClientCall2

.text:777D5443 add esp, 0Ch

.text:777D5446 retn 18h

.text:777D5446 sub_777D542F endp

.text:777D20C0 pFormat db 0 ; DATA XREF: sub_777D542F+5vo

.text:777D20C1 db 48h ; H

.text:777D20C2 db 0

.text:777D20C3 db 0

.text:777D20C4 db 0

.text:777D20C5 db 0

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

.text:777D20C7 db 0

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

В качестве альтернативного варианта можно воспользоваться правкой RPCRT4.DLL в памяти по методике, описанной в статье «Метафизика wmf-файлов». Для этого прописываем в следующей ветке реестра HKLMSoftwareMicrosoftWindows NTCurrentVersionWindowsAppInit_DLLs специально созданную для этих целей динамическую библиотеку, которая будет отображаться на адресное пространство каждого процессора. Внутри DllEntry мы выполняем загрузку RPCRT4.DLL через LoadLibrary, правим ее, и все! Если приложение не использует RPCRT4.DLL (что маловероятно), мы просто теряем немного памяти. Если же приложение подгружает RPCRT4.DLL через таблицу импорта или через LoadLibrary (что намного более вероятно), оно просто отсылается к уже загруженной (и исправленной) копии RPCRT4.DLL и хакер не имеет никаких шансов атаковать систему! Естественно, по сравнению с правкой на диске, время загрузки файлов и потребность системы в памяти ощутимо возрастут, а риск угробить систему останется все тот же. Если динамическая библиотека, осуществляющая правку, будет реализована с ошибками, система упадет прежде, чем загрузится пользовательский интерфейс, и для исправления ситуации придется прибегнуть к помощи все той же консоли восстановления (втыкать винчестер с убитой NT вторым), удаляя нашу динамическую библиотеку. Впрочем, это уже детали.

Главное, что изготовление заплаток своим силами вполне возможно, и, пока другие ждут помощи от Microsoft, правильные хакеры защищаются самостоятельно (залатанный RPCRT4.DLL из-за лицензионных ограничений к статье не прилагается :)).

Содержание
ttfb: 7.282018661499 ms