Icons in .dll
or .exe
files are stored in the resources segment as Windows DIBs or PNGs since Windows Vista.
What function/library is used by the CreateIconFromResourceEx()
WinAPI function in the user32.dll
to read/decompress these PNG resources?
Does CreateIconFromResourceEx()
use the WIC
for this purpose ?
When a function like the CreateIconFromResourceEx()
is called, user32.dll
sometimes dynamically loads another DLL specified in the REG_SZ value HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\IconServiceLib
.
This load can be triggered with a function like the one below:
bool TriggerICSLoad()
{
if (PCHAR pc = (PCHAR)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, 40)) {
strcpy_s(pc, 39, "‰PNG\r\n\x1a\n"); //89 50 4E 47 OD 0A 1A 0A
CreateIconFromResourceEx((PBYTE)pc, 40, true, 0x00030000, 0, 0, LR_DEFAULTCOLOR); //Trigger the loading of the ICS library
GlobalFree(pc);
return true;
}
return false;
}
This causes a private variable user32.dll!gpICSProc
to be set to an address of a private function ConvertToDIBProc()
residing inside the ...\IconServiceLib
which is set by default to IconCodecService.dll
.
This is initiated by the IconCodecService.dll!DllMain()
that calls the exported function user32.dll!PrivateRegisterICSProc()
which looks like this:
bool PrivateRegisterICSProc(PCONVERT_TO_DIB_PROC AddrOfFn)
{
if ( user32.dll!gpICSProc )
return false;
user32.dll!gpICSProc = AddrOfFn;
return true;
}
From the above, you can see that checking whether the ...\IconServiceLib
has already been loaded by user32.dll
can be done at any time by calling the following function without any side effects:
bool IsICSLoaded()
{
if (HMODULE hModUSR = GetModuleHandle(L"user32.dll") )
{
if (PREGISTER_ICS_PROC pRegisterICSProc = (PREGISTER_ICS_PROC)GetProcAddress(hModUSR, "PrivateRegisterICSProc") )
return !(pRegisterICSProc)(0);
}
return false;
}
The ConvertToDIBProc()
residing in the IconCodecService.dll
is not exported nor documented but I reverse engineered it and it looks like this:
HGLOBAL ConvertToDIBProc(PBYTE pbBuffer, DWORD cbBufferSize, DWORD* pcbOutputSize, DWORD Flags)
{
if (CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_SPEED_OVER_MEMORY | COINIT_DISABLE_OLE1DDE) >=0) // In the original this is actually called in USER32.DLL
return 0;
for (unsigned char* p = pbBuffer; (p < pbBuffer + cbBufferSize); *(volatile unsigned char*)p, p += 4096); // Touch every page of the buffer
IWICImagingFactory* pIWICImagingFactory = NULL;
WICPixelFormatGUID PixelFormat = GUID_WICPixelFormat32bppBGRA;
IWICStream* pStreamIn = NULL;
IWICBitmapDecoder* instBitmapDecoder = NULL;
IWICBitmapDecoderInfo* pBitmapDecoderInfo = NULL;
IWICComponentInfo* ppDecoderIInfo = NULL;
IWICComponentInfo* ppIInfoEncoder = NULL;
IWICBitmapEncoderInfo* pBitmapEncoderInfo = NULL;
IWICBitmapEncoder* instBitmapEncoder = NULL;
IWICBitmapFrameEncode* pBitmapFrameEncoder = NULL;
IWICBitmapFrameDecode* pBitmapFrameDecode = NULL;
IWICPalette* pPalette = NULL;
HGLOBAL phglobal = NULL;
LPSTREAM pStreamOut = NULL;
int ExtraMemory;
WICRect rc;
HRESULT hr;
if ( !(Flags & 0x80000003) )
goto CLEANUP;
hr = CoCreateInstance(CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pIWICImagingFactory));
if (hr < 0) goto CLEANUP;
hr = pIWICImagingFactory->CreateStream(&pStreamIn);
if (hr < 0) goto CLEANUP;
hr = pStreamIn->InitializeFromMemory(pbBuffer, cbBufferSize);
if (hr < 0) goto CLEANUP;
hr = CreateStreamOnHGlobal(0, 0, &pStreamOut);
if (hr < 0) goto CLEANUP;
IID cls_WICDecoder;
if (Flags & 1)
cls_WICDecoder = CLSID_WICPngDecoder;
else
{
cls_WICDecoder = CLSID_WICJpegDecoder;
if (!(Flags & 2))
cls_WICDecoder = CLSID_NULL;
}
hr = pIWICImagingFactory->CreateComponentInfo(cls_WICDecoder, &ppDecoderIInfo);
if (hr < 0) goto CLEANUP;
hr = ppDecoderIInfo->QueryInterface(IID_IWICBitmapDecoderInfo, (LPVOID*)(&pBitmapDecoderInfo));
if (hr < 0) goto CLEANUP;
hr = pBitmapDecoderInfo->CreateInstance(&instBitmapDecoder);
if (hr < 0) goto CLEANUP;
hr = instBitmapDecoder->Initialize(pStreamIn, WICDecodeMetadataCacheOnDemand); // IWICBitmapDecoder::Initialize
if (hr < 0) goto CLEANUP;
UINT FrameCount;
hr = instBitmapDecoder->GetFrameCount(&FrameCount); // IWICBitmapDecoder::GetFrameCount
if ((hr < 0) || (!(FrameCount))) goto CLEANUP;
hr = instBitmapDecoder->GetFrame(0, &pBitmapFrameDecode); // IWICBitmapDecoder::GetFrame
if (hr < 0) goto CLEANUP;
UINT pixWidth;
int pixHeight;
hr = pBitmapFrameDecode->GetSize(&pixWidth, (UINT*)&pixHeight); // IWICBitmapFrameDecode::GetSize
if (hr < 0) goto CLEANUP;
double DPI_x;
double DPI_y;
hr = pBitmapFrameDecode->GetResolution(&DPI_x, &DPI_y); // IWICBitmapFrameDecode::GetResolution
if (hr < 0) goto CLEANUP;
if (Flags & 2)
{
hr = pBitmapFrameDecode->GetPixelFormat(&PixelFormat); // IWICBitmapFrameDecode::GetPixelFormat
if (hr<0) goto CLEANUP;
if (PixelFormat == GUID_WICPixelFormat8bppGray)
PixelFormat = GUID_WICPixelFormat24bppBGR;
else
PixelFormat = GUID_WICPixelFormat32bppBGRA;
}
ExtraMemory = 0;
if (Flags & 0x80000000)
{
UINT cbWidthColor;
UINT cbWidthMono;
UINT pixHeightAbs;
if (PixelFormat == GUID_WICPixelFormat32bppBGRA)
cbWidthColor = ((32 * pixWidth + 31) >> 3) & 0xFFFFFFFC;
else if (PixelFormat == GUID_WICPixelFormat24bppBGR)
cbWidthColor = ((24 * pixWidth + 31) >> 3) & 0xFFFFFFFC;
else
goto CLEANUP;
cbWidthMono = ((pixWidth + 31) >> 3) & 0xFFFFFFFC;
pixHeightAbs = pixHeight>=0 ? pixHeight : -pixHeight;
if (pixHeightAbs && (cbWidthMono < 0xFFFFFFFF / pixHeightAbs) && (cbWidthColor < 0xFFFFFFFF / pixHeightAbs) )
ExtraMemory = cbWidthMono * pixHeightAbs;
}
hr = pIWICImagingFactory->CreateComponentInfo(CLSID_WICBmpEncoder, &ppIInfoEncoder); // IWICImagingFactory::CreateComponentInfo
if (hr < 0) goto CLEANUP;
hr = ppIInfoEncoder->QueryInterface(IID_IWICBitmapEncoderInfo, (LPVOID*)&pBitmapEncoderInfo); // IWICComponentInfo::QueryInterface
if (hr < 0) goto CLEANUP;
hr = pBitmapEncoderInfo->CreateInstance(&instBitmapEncoder); // IWICBitmapEncoderInfo::CreateInstance
if (hr < 0) goto CLEANUP;
hr = instBitmapEncoder->Initialize(pStreamOut, WICBitmapEncoderNoCache); // IWICBitmapEncoder::Initialize
if (hr < 0) goto CLEANUP;
hr = instBitmapEncoder->CreateNewFrame(&pBitmapFrameEncoder, NULL); // IWICBitmapEncoder::CreateNewFrame
if (hr < 0) goto CLEANUP;
hr = pBitmapFrameEncoder->Initialize(NULL); // IWICBitmapFrameEncode::Initialize
if (hr < 0) goto CLEANUP;
hr = pBitmapFrameEncoder->SetSize(pixWidth, pixHeight); // IWICBitmapFrameEncode::SetSize
if (hr < 0) goto CLEANUP;
hr = pBitmapFrameEncoder->SetPixelFormat(&PixelFormat); // IWICBitmapFrameEncode::SetPixelFormat
if (hr < 0) goto CLEANUP;
hr = pBitmapFrameEncoder->SetResolution(DPI_x, DPI_y); // IWICBitmapFrameEncode::SetResolution
if (hr < 0) goto CLEANUP;
hr = pIWICImagingFactory->CreatePalette(&pPalette); // IWICImagingFactory::CreatePalette
if (hr >= 0)
{
hr = pBitmapFrameDecode->CopyPalette(pPalette); // IWICBitmapFrameDecode::CopyPalette
if (hr != WINCODEC_ERR_PALETTEUNAVAILABLE)
{
if (hr >= 0)
{
hr = pBitmapFrameEncoder->SetPalette(pPalette);
if (hr < 0)
goto CLEANUP;
}
else
goto CLEANUP;
}
}
rc = {0,0,(int)pixWidth,pixHeight};
hr = pBitmapFrameEncoder->WriteSource(pBitmapFrameDecode, &rc); //IWICBitmapFrameEncode::WriteSource
if (hr < 0) goto CLEANUP;
hr = pBitmapFrameEncoder->Commit(); // IWICBitmapFrameEncode::Commit
if (hr < 0) goto CLEANUP;
hr = instBitmapEncoder->Commit(); // IWICBitmapEncoder::Commit
if (hr < 0) goto CLEANUP;
hr = GetHGlobalFromStream(pStreamOut, &phglobal);
if (hr < 0) goto CLEANUP;
if (phglobal)
{
size_t GlobSize = GlobalSize(phglobal);
if (GlobSize > 54)
{
if (ExtraMemory)
{
HGLOBAL hReAllocated = GlobalReAlloc(phglobal, ExtraMemory + GlobSize, 0);// HGLOBAL, Size, idDecoder
if (!hReAllocated)
{
GlobalFree(phglobal);
phglobal = 0;
goto CLEANUP;
}
phglobal = hReAllocated;
}
if (pcbOutputSize)
*pcbOutputSize = (DWORD)GlobSize;
}
}
CLEANUP:
if (pPalette)
pPalette->Release();
if (pBitmapFrameEncoder)
pBitmapFrameEncoder->Release();
if (pBitmapEncoderInfo)
pBitmapEncoderInfo->Release();
if (ppDecoderIInfo)
ppDecoderIInfo->Release();
if (ppIInfoEncoder)
ppIInfoEncoder->Release();
if (instBitmapEncoder)
instBitmapEncoder->Release();
if (pBitmapFrameDecode)
pBitmapFrameDecode->Release();
if (pBitmapDecoderInfo)
pBitmapDecoderInfo->Release();
if (instBitmapDecoder)
instBitmapDecoder->Release();
if (pIWICImagingFactory)
pIWICImagingFactory->Release();
if (pStreamIn)
pStreamIn->Release();
if (pStreamOut)
pStreamOut->Release();
CoUninitialize(); // In the original this is actually called in USER32.DLL
return phglobal;
}
The function above takes a pointer to a memory buffer that contains a PNG
or JPEG
image and returns a memory handle (HGLOBAL
) to a Windows Device Independent Bitmap (DIB) consisting of the BITMAPFILEHEADER, BITMAPINFOHEADER and pixel array. It does this by using the Windows Imaging component (WIC) extensively.
This function also takes the Flags
parameter which has the following meanings:
0x00000000 : disallowed
0x00000001 : Convert PNG to 32bppBGRA DIB
0x00000002 : Convert 8bppGray JPEG to 24bppBGR DIB (convert all other JPEG formats to 32bppBGRA DIB).
0x00000003 : Convert 8bppGray PNG to 24bppBGR DIB (convert all other PNG formats to 32bppBGRA DIB).
0x80000000 : OR it (bitwise) with the remaining flags to allocate extra memory for a monochrome bitmap of the same dimensions (The extra memory count is not written to *pcbOutputSize but it is visible to GlobalSize(). This flag cannot be used alone).
Because the address of the ConvertToDIBProc()
is not exported, you can call it only by using extreme ways, as illustrated below:
HGLOBAL ConvertToDIBProcEw(PBYTE pbBuffer, DWORD cbBufferSize, DWORD* pcbOutputSize, DWORD Flags)
{
static PCONVERT_TO_DIB_PROC gpConvertToDIBProc = NULL;
if (!(pbBuffer) || !(cbBufferSize) || !(pcbOutputSize) || !(Flags))
return 0;
if (gpConvertToDIBProc)
{
return (gpConvertToDIBProc)(pbBuffer, cbBufferSize, pcbOutputSize, Flags);
}
else
{
PDLL_NOTIFICATION_CONTEXT pContext = (PDLL_NOTIFICATION_CONTEXT)_alloca(sizeof(DLL_NOTIFICATION_CONTEXT)); //This must be allocated on the stack or it will fail the subsequent IsAddrOnStack() check.
*pContext = { NULL, L"PrivateRegisterICSProc", L"user32.dll", &LocalRegisterICSProc };
if (GetICSLibraryName(&pContext->pLibFileName))
{
if (IsICSLoaded())
{
if (HMODULE hModICS = GetModuleHandle(pContext->pLibFileName))
{
pContext->hMod = hModICS;
if (HMODULE hModUSR = GetModuleHandle(pContext->pImportedModuleName))
{
PCHAR pc = wctombs(pContext->pImportedFunctionName);
if (__int64* op = (__int64*)GetProcAddress(hModUSR, pc))
{
INT32 ofs;
if ((*op & 0x0000000000FFFFFF) == 0x00000000003d8348) //check for the CMP [RIP+ofs],0 opcode
{
ofs = (INT32)((*op & 0x00FFFFFFFF000000) >> 24) + 8; //extract RIP relative offset of gpICSProc from the CMP qword ptr [RIP + ofs],0 opcode.
PDWORD pFn = *(PDWORD*)((PBYTE)op + ofs);
if (IsAddrInExecutableSection(hModICS, pFn))
{
if (((*pFn & 0xC7FFFB) == 0x00C48B48) || ((*pFn & 0xF8FFFE) == 0x00E08948)) // check for: MOV reg64,RSP (it has 2 synonymous encodings, sheesh!)
gpConvertToDIBProc = (PCONVERT_TO_DIB_PROC)pFn;
}
}
}
free(pc);
}
if (!gpConvertToDIBProc) //Try an alternative method to obtain the address of ConvertToDIBProc() if the method above failed.
{
PMY_LDR_DATA_TABLE_ENTRY pLdrDataTableEntry = NULL;
if ((LdrFindEntryForAddress(hModICS, &pLdrDataTableEntry) == STATUS_SUCCESS) && (pLdrDataTableEntry) && (hModICS == pLdrDataTableEntry->DllBase))
{
pLdrDataTableEntry->Padding1 = MY_LODWORD(pContext);
pLdrDataTableEntry->Padding2 = MY_HIDWORD(pContext);
if (IsAddrInExecutableSection(hModICS, pLdrDataTableEntry->EntryPoint))
pContext->pOrgDllEntryPoint = pLdrDataTableEntry->EntryPoint;
else
{
PIMAGE_NT_HEADERS NtHeader = ImageNtHeader((PVOID)hModICS);
if (NtHeader)
{
PLDR_INIT_ROUTINE AddressOfEntryPoint = (PLDR_INIT_ROUTINE)((PBYTE)hModICS + NtHeader->OptionalHeader.AddressOfEntryPoint);
if (IsAddrInExecutableSection(hModICS, AddressOfEntryPoint))
{
pContext->pOrgDllEntryPoint = AddressOfEntryPoint;
if ( (pContext->pOrgImpProcAddress = (PREGISTER_ICS_PROC)SetImpProcAddress((PBYTE)hModICS, pContext->pImportedFunctionName, pContext->pImportedModuleName, &LocalRegisterICSProc)))
{
(pContext->pOrgDllEntryPoint)(hModICS, DLL_PROCESS_DETACH, NULL); //So the CRT is not initialized twice
(pContext->pOrgDllEntryPoint)(hModICS, DLL_PROCESS_ATTACH, NULL); //This causes LocalRegisterICSProc() to be called
if (pContext->pOrgImpProcAddress) //Just in case the LocalRegisterICSProc() has not restored the imports
{
SetImpProcAddress((PBYTE)hModICS, pContext->pImportedFunctionName, pContext->pImportedModuleName, pContext->pOrgImpProcAddress); //Restore IAT
((PREGISTER_ICS_PROC)pContext->pOrgImpProcAddress)((PCONVERT_TO_DIB_PROC)pContext->Result); //Call the original imported function (the PrivateRegisterICSProc() )
}
gpConvertToDIBProc = (PCONVERT_TO_DIB_PROC)pContext->Result;
}
}
}
}
}
}
}
}
else //if (!IsICSLoaded())
{
if (LdrRegisterDllNotification(0, &DllNotificationCallback, pContext, &pContext->cookie) == STATUS_SUCCESS)
{
TriggerICSLoad();
if (pContext->cookie) //In case LocalDllInitRoutine() did not clean it up
LdrUnregisterDllNotification(pContext->cookie);
if (pContext->pOrgImpProcAddress) //In case LocalRegisterICSProc() has not restored the imports
{
if (HMODULE hModICS = GetModuleHandle(pContext->pLibFileName))
SetImpProcAddress((PBYTE)hModICS, pContext->pImportedFunctionName, pContext->pImportedModuleName, pContext->pOrgImpProcAddress);
}
if (IsICSLoaded())
gpConvertToDIBProc = (PCONVERT_TO_DIB_PROC)pContext->Result;
}
}
free((PVOID)pContext->pLibFileName);
}
}
if (gpConvertToDIBProc)
return (gpConvertToDIBProc)(pbBuffer, cbBufferSize, pcbOutputSize, Flags);
else
return 0;
}
bool __declspec(noinline) LocalRegisterICSProc(PCONVERT_TO_DIB_PROC pConvertToDIBProc) //If this function is inlined then IsAddrOnStack() may fail
{
PMY_LDR_DATA_TABLE_ENTRY pLdrDataTableEntry=NULL;
if ((LdrFindEntryForAddress(_ReturnAddress(), &pLdrDataTableEntry) == STATUS_SUCCESS) && (pLdrDataTableEntry))
{
PDLL_NOTIFICATION_CONTEXT pContext = (PDLL_NOTIFICATION_CONTEXT)((ULONGLONG)pLdrDataTableEntry->Padding1 | (((ULONGLONG)pLdrDataTableEntry->Padding2) << 32));
HMODULE hModICS = (HMODULE)pLdrDataTableEntry->DllBase;
if (IsAddrOnStack(pContext) && (pContext->hMod == hModICS)) //Sanity check
{
pContext->Result = pConvertToDIBProc;
if (PREGISTER_ICS_PROC pOrgImpProcAddress = (PREGISTER_ICS_PROC)pContext->pOrgImpProcAddress)
{
pContext->pOrgImpProcAddress = NULL;
if (IsAddrInExecutableSection(GetModuleHandle(pContext->pImportedModuleName), pOrgImpProcAddress))
{
SetImpProcAddress((PBYTE)hModICS, pContext->pImportedFunctionName, pContext->pImportedModuleName, pOrgImpProcAddress); //Restore import
return (pOrgImpProcAddress)(pConvertToDIBProc);
}
}
}
}
return false; //
}
bool __declspec(noinline) LocalDllInitRoutine(_In_ PVOID DllHandle, _In_ ULONG Reason, _In_opt_ PVOID Context) //If this function is inlined then IsAddrOnStack() may fail
{
BOOLEAN res = false;
PMY_LDR_DATA_TABLE_ENTRY pLdrDataTableEntry=NULL;
if ((LdrFindEntryForAddress(DllHandle, &pLdrDataTableEntry) == STATUS_SUCCESS) && (pLdrDataTableEntry) && (pLdrDataTableEntry->DllBase == DllHandle))
{
PDLL_NOTIFICATION_CONTEXT pContext = (PDLL_NOTIFICATION_CONTEXT)((ULONGLONG)pLdrDataTableEntry->Padding1 | (((ULONGLONG)pLdrDataTableEntry->Padding2) << 32));
if (IsAddrOnStack(pContext) && (pContext->hMod == DllHandle)) //Sanity check
{
PLDR_INIT_ROUTINE pOrgDllEntryPoint = pContext->pOrgDllEntryPoint;
if ((pOrgDllEntryPoint) && IsAddrInExecutableSection((HMODULE)DllHandle, pOrgDllEntryPoint)) //Sanity check
{
if (Reason == DLL_PROCESS_ATTACH)
{
if (pContext->cookie)
{
LdrUnregisterDllNotification(pContext->cookie);
pContext->cookie = NULL;
}
pContext->pOrgImpProcAddress = SetImpProcAddress((PBYTE)DllHandle, pContext->pImportedFunctionName, pContext->pImportedModuleName, pContext->pNewImpProcAddress);
}
res = (*pOrgDllEntryPoint)(DllHandle, Reason, Context); //Call the original DLL Entry Point
}
}
}
return res;
}
VOID CALLBACK DllNotificationCallback(_In_ ULONG NotificationReason, _In_ PLDR_DLL_NOTIFICATION_DATA pNotificationData, _In_opt_ PDLL_NOTIFICATION_CONTEXT pContext)
{
if ( (pContext) && (NotificationReason == LDR_DLL_NOTIFICATION_REASON_LOADED) )
{
printf("LOAD NOTIFY: %ws\n", pNotificationData->Loaded.FullDllName->Buffer);
if (!_wcsnicmp(pContext->pLibFileName, pNotificationData->Loaded.BaseDllName->Buffer, pNotificationData->Loaded.BaseDllName->Length) || !_wcsnicmp(pContext->pLibFileName, pNotificationData->Loaded.FullDllName->Buffer, pNotificationData->Loaded.FullDllName->Length ))
{
pContext->hMod = (HMODULE)pNotificationData->Loaded.DllBase;
if (bitness(pContext->hMod) != 64)
{
printf("ERROR: Handling non-64bit DLLs is not implemented\n");
pContext->Result = NULL;
return;
}
PMY_LDR_DATA_TABLE_ENTRY pLdrDataTableEntry = NULL;
if ((LdrFindEntryForAddress(pContext->hMod, &pLdrDataTableEntry) == STATUS_SUCCESS) && (pLdrDataTableEntry) && (pContext->hMod == pLdrDataTableEntry->DllBase))
{
pLdrDataTableEntry->Padding1 = MY_LODWORD(pContext);
pLdrDataTableEntry->Padding2 = MY_HIDWORD(pContext);
pContext->pOrgDllEntryPoint = pLdrDataTableEntry->EntryPoint;
pLdrDataTableEntry->EntryPoint = &LocalDllInitRoutine; // Needed because this dll notification is invoked BEFORE the imports are resolved in Windows 7.
}
else
pContext->Result = NULL;
}
}
}
typedef struct _DLL_NOTIFICATION_CONTEXT
{
LPCWSTR pLibFileName;
LPCWSTR pImportedFunctionName;
LPCWSTR pImportedModuleName;
PVOID pNewImpProcAddress;
HMODULE hMod;
PVOID pOrgImpProcAddress;
PLDR_INIT_ROUTINE pOrgDllEntryPoint;
PVOID cookie;
PVOID Result;
} DLL_NOTIFICATION_CONTEXT, * PDLL_NOTIFICATION_CONTEXT;
The function listed above works only in 64-bit Windows. If you are interested in the various helper functions called by it, write me so in the comments.