Memory leak when using IShellItem2.GetString() - c++

I'm using the following code to enumerate the contents of the Recyclebin Shell folder and get the file type of each item.
The code gives the expected results but if I call the function in a loop it looks like there's some memory leak when using the IshellItem2 GetString function (see attached screenshot at the end).
Am I cleaning up everything properly?
Am I misinterpreting the results?
void Test1()
{
// Get recyclebin ishellitem
IShellItem* psiRecycleBin;
if (SUCCEEDED(SHGetKnownFolderItem(FOLDERID_RecycleBinFolder, KF_FLAG_DEFAULT,
NULL, IID_PPV_ARGS(&psiRecycleBin))))
{
// Get ishellitem contents enumerator
IEnumShellItems* pesi;
if (SUCCEEDED(psiRecycleBin->BindToHandler(NULL, BHID_EnumItems, IID_PPV_ARGS(&pesi))))
{
IShellItem* psi;
while (pesi->Next(1, &psi, NULL) == S_OK)
{
// Get ishellitem2 from ishellitem
IShellItem2* psi2;
if (SUCCEEDED(psi->QueryInterface(IID_PPV_ARGS(&psi2))))
{
// Get the item file type string
LPWSTR fileType = NULL;
if (SUCCEEDED(psi2->GetString(PKEY_ItemTypeText, &fileType)))
{
CoTaskMemFree(fileType);
}
psi2->Release();
}
psi->Release();
}
pesi->Release();
}
psiRecycleBin->Release();
}
}
And I'm calling it in loop like this:
#define STRICT_TYPED_ITEMIDS
#include <shlobj.h>
#include <propkey.h>
#include <iostream>
void Test1();
int main()
{
(void)CoInitialize(NULL);
std::cout << "Enumerating recyclebin items..\n";
for (int ii = 0; ii < 5000; ii++)
{
Test1();
}
CoUninitialize();
return 0;
}
When debugging this console program in VS in the memory diagnostics window this is what I get:
Thanks for the help

yes, here really exist memory leak, related to HIDDENRECYCLEBINDATAV2 structure from shell32.dll
partial definition of it:
struct HIDDENRECYCLEBINDATAV2
{
//... some mebers
FILETIME time;
PWSTR pszLocationBeforeDelete = 0; // !!! not released
PWSTR pszLocationInRecycleBin = 0; // !!! not released
HRESULT Serialize(PBYTE *, PUSHORT);
static HRESULT Deserialize(
_In_reads_bytes_opt_(cbStream) const BYTE *pbStream ,
_In_ USHORT cbStream,
_Out_ HIDDENRECYCLEBINDATAV2 ** pphrbd);
static HRESULT Initialize(HIDDENRECYCLEBINDATAV1 const *, HIDDENRECYCLEBINDATAV2**);
};
this structure hold 2 strings - file path from where it deleted ( pszLocationBeforeDelete - this is my name, i don't know original) and current file path in Recycle Bin ( pszLocationInRecycleBin - again my name)
this names allocated inside Deserialize method, by call IStream_ReadStrLong and must be freed with CoTaskMemFree. but how i found - CoTaskMemFree never called for this two strings.
pseudo code for Deserialize :
static HRESULT HIDDENRECYCLEBINDATAV2::Deserialize(
_In_reads_bytes_opt_(cbInit) const BYTE *pbStream ,
_In_ USHORT cbStream,
_Out_ HIDDENRECYCLEBINDATAV2 ** pphrbd)
{
HRESULT hr = E_FAIL;
if (HIDDENRECYCLEBINDATAV2 *phrbd = new HIDDENRECYCLEBINDATAV2)
{
if (IStream *pStream = SHCreateMemStream(pbStream, cbStream))
{
if (0 <= (hr = IStream_ReadStrLong(pStream, &phrbd->pszLocationBeforeDelete)) &&
0 <= (hr = IStream_ReadStrLong(pStream, &phrbd->pszLocationInRecycleBin)))
{
*pphrbd = phrbd, phrbd = 0;
}
pStream->Release();
}
CoTaskMemFree(phrbd); // !! error, need delete phrbd
}
return hr;
}
and it called from CBitBucket::_ValidateItem :
HRESULT InitDeletedItem(PCWSTR pszLocationBeforeDelete, PCWSTR pszLocationBeforeDelete, DELETEDITEM *);
static HRESULT CBitBucket::_ValidateItem(_ITEMIDLIST_RELATIVE const *, DELETEDITEM ** ppdi)
{
HIDDENRECYCLEBINDATAV2 * phrbd;
if (0 <= HIDDENRECYCLEBINDATAV2::Deserialize(pbStream, cbStream, &phrbd))
{
if (DELETEDITEM * pdi = new DELETEDITEM)
{
if (0 <= InitDeletedItem( phrbd->pszLocationBeforeDelete,
phrbd->pszLocationInRecycleBin, pdi))
{
*ppdi = pdi, pdi = 0;
}
if (pdi) delete pdi;
}
CoTaskMemFree(phrbd); // !! error, need delete phrbd
}
}
in both functions - memory for HIDDENRECYCLEBINDATAV2 simply released with CoTaskMemFree api, but memory for strings inside this structure not released. i think need add
HIDDENRECYCLEBINDATAV2::~HIDDENRECYCLEBINDATAV2()
{
CoTaskMemFree(pszLocationInRecycleBin);
CoTaskMemFree(pszLocationBeforeDelete);
}
to this structure and call delete instead CoTaskMemFree
how possible found this ? i hook RtlAllocateHeap and RtlFreeHeap before second call to Test1() (important do this not on first call, because during first call may be additional libs load, some differed initialization, etc.. - all this can distort the real result)and log all alloc/free calls in current thread. also i replace while (pesi->Next..) to if (pesi->Next..) (usually one iteration is enough ). and i found that count of alloc on 2 more than count of free. so i easy found from where this 2 allocations- inside IStream_ReadStrLong. then i set breakpoint here and easy view from where this called :
CBitBucket::_ValidateItem
HIDDENRECYCLEBINDATAV2::Deserialize
IStream_ReadStrLong
CoTaskMemAlloc
partial demo code for log:
struct AI
{
PVOID BaseAddress;
PVOID From;
ULONG Size;
ULONG Flags;
};
struct TID
{
AI *pi;
ULONG nAlloc, nFree, nCells, MaxAllocDelta;
BOOL bNotLog;
TID()
{
RtlZeroMemory(this, sizeof(*this));
}
};
BOOLEAN NTAPI hook_RtlFreeHeap(PVOID HeapHandle, ULONG Flags, PVOID BaseAddress )
{
TID* p = RTL_FRAME<TID>::get();
if (!p || p->bNotLog)
{
return RtlFreeHeap(HeapHandle, Flags, BaseAddress) != 0;
}
p->bNotLog = TRUE;
if (!RtlFreeHeap(HeapHandle, Flags, BaseAddress))
{
__debugbreak();
}
if (BaseAddress)
{
AI* pi = p->pi;
ULONG n = p->nCells;
do
{
if (pi->BaseAddress == BaseAddress)
{
pi->BaseAddress = 0;
p->nFree++;
break;
}
} while (pi++, --n);
if (!n)
{
__debugbreak();
}
}
p->bNotLog = FALSE;
return TRUE;
}
PVOID NTAPI hook_RtlAllocateHeap( PVOID HeapHandle, ULONG Flags, SIZE_T Size )
{
TID* p = RTL_FRAME<TID>::get();
if (!p || p->bNotLog)
{
return RtlAllocateHeap(HeapHandle, Flags, Size);
}
p->bNotLog = TRUE;
if (PVOID BaseAddress = RtlAllocateHeap(HeapHandle, Flags, Size))
{
AI* pi = p->pi;
ULONG n = p->nCells;
do
{
if (!pi->BaseAddress)
{
pi->BaseAddress = BaseAddress;
pi->From = _ReturnAddress();
pi->Size = (ULONG)Size;
pi->Flags = Flags;
p->nAlloc++;
ULONG k = p->nAlloc - p->nFree;
if (k > p->MaxAllocDelta)
{
p->MaxAllocDelta = k;
}
break;
}
} while (pi++, --n);
if (!n)
{
__debugbreak();
}
p->bNotLog = FALSE;
return BaseAddress;
}
return 0;
}
void TestEx()
{
enum { cell_count = 0x1000 };
if (AI* pi = new AI[cell_count])
{
Test1();// first call
// hook RtlAllocateHeap + RtlFreeHeap
{
RtlZeroMemory(pi, cell_count * sizeof(AI));
RTL_FRAME<TID> f;
f.pi = pi;
f.nCells = cell_count;
Test1();// second call
DbgPrint("%x(%x) %x\n", f.nAlloc, f.nFree, f.MaxAllocDelta);
if (f.nAlloc - f.nFree)
{
ULONG n = cell_count;
AI* qi = pi;
do
{
if (qi->BaseAddress)
{
DbgPrint("%p> %x %x\n", qi->From, qi->Size, qi->Flags);
}
} while (qi++, --n);
}
}
delete [] pi;
}
}

Related

SetProcessValidCallTargets trows error 87

Im trying to use the control flow guard api to set a area of memory to be execution valid.
I used these examples for reverence material:
https://github.com/BreakingMalwareResearch/CFGExceptions/blob/master/CFGExceptions/main.cpp
https://github.com/trailofbits/cfg-showcase/blob/master/cfg_valid_targets.cpp#L111
Here is my code:
#include <stdio.h>
#include <Windows.h>
#include <psapi.h>
#include "cfgTest.h"
BOOL GetMemoryAllocationBaseAndRegionSize(PVOID, PVOID*, PSIZE_T);
INT main() {
HANDLE hProcess;
NTSTATUS ntStatus = ERROR_SUCCESS;
STARTUPINFOA startupInfo;
PROCESS_INFORMATION processInformation = { 0 };
CFG_CALL_TARGET_INFO cfgCallTargetInfoList[1];
DWORD dwPid = 0;
SIZE_T stRegionSize = NULL;
PVOID pvAllocationBase = NULL;
// Function pointers
SETPROCESSVALIDCALLTARGETS pSetProcessValidCallTargets = NULL;
PVOID pvAddressToAddCfgExceptionTo = NULL;
//
// Get address of SetProcessValidCallTargets
//
CONST HMODULE hNtdll = LoadLibraryW(L"ntdll.dll");
CONST HMODULE hKernelbase = LoadLibraryW(L"Kernelbase.dll");
if (hKernelbase && hNtdll) {
pSetProcessValidCallTargets = (SETPROCESSVALIDCALLTARGETS)GetProcAddress(hKernelbase, "SetProcessValidCallTargets");
pvAddressToAddCfgExceptionTo = GetProcAddress(hNtdll, "NtSetContextThread");
}
else {
return ERROR_MOD_NOT_FOUND;
}
FreeLibrary(hNtdll);
FreeLibrary(hKernelbase);
// Get memory allocation base and region size by calling VirtualProtect.
if (GetMemoryAllocationBaseAndRegionSize(pvAddressToAddCfgExceptionTo, &pvAllocationBase, &stRegionSize) == FALSE) {
ntStatus = ERROR_UNHANDLED_ERROR;
goto lblCleanup;
}
//
// Add cfg exception
//
cfgCallTargetInfoList[0].Flags = CFG_CALL_TARGET_VALID;
cfgCallTargetInfoList[0].Offset = (ULONG_PTR)pvAddressToAddCfgExceptionTo - (ULONG_PTR)pvAllocationBase;;
if (pSetProcessValidCallTargets(GetCurrentProcess(), pvAllocationBase, stRegionSize, 1, cfgCallTargetInfoList) == FALSE) {
printf("%d", GetLastError());
ntStatus = ERROR_UNHANDLED_ERROR;
goto lblCleanup;
}
lblCleanup:
if (processInformation.hProcess) {
CloseHandle(processInformation.hProcess);
}
if (processInformation.hThread) {
CloseHandle(processInformation.hThread);
}
return ntStatus;
}
BOOL GetMemoryAllocationBaseAndRegionSize(PVOID pvAddress, PVOID* ppvAllocationBase, PSIZE_T pstRegionSize) {
SIZE_T stErr = 0;
MEMORY_BASIC_INFORMATION tMemoryBasicInformation = { 0 };
stErr = VirtualQuery(pvAddress, &tMemoryBasicInformation, sizeof(tMemoryBasicInformation));
if (0 == stErr) {
return FALSE;
}
*ppvAllocationBase = tMemoryBasicInformation.AllocationBase;
*pstRegionSize = tMemoryBasicInformation.RegionSize;
return TRUE;
}
#pragma once
typedef enum _VIRTUAL_MEMORY_INFORMATION_CLASS
{
VmPrefetchInformation,
VmPagePriorityInformation,
VmCfgCallTargetInformation
} VIRTUAL_MEMORY_INFORMATION_CLASS;
typedef struct _MEMORY_RANGE_ENTRY
{
PVOID VirtualAddress;
SIZE_T NumberOfBytes;
} MEMORY_RANGE_ENTRY, * PMEMORY_RANGE_ENTRY;
typedef struct _VM_INFORMATION
{
DWORD dwNumberOfOffsets;
DWORD dwMustBeZero;
PDWORD pdwOutput;
PCFG_CALL_TARGET_INFO ptOffsets;
} VM_INFORMATION, * PVM_INFORMATION;
typedef NTSTATUS(NTAPI* NTSETINFORMATIONVIRTUALMEMORY)(
HANDLE hProcess,
VIRTUAL_MEMORY_INFORMATION_CLASS VmInformationClass,
ULONG_PTR NumberOfEntries,
PMEMORY_RANGE_ENTRY VirtualAddresses,
PVOID VmInformation,
ULONG VmInformationLength
);
typedef BOOL(WINAPI* SETPROCESSVALIDCALLTARGETS)(
HANDLE hProcess,
PVOID VirtualAddress,
SIZE_T RegionSize,
ULONG NumberOfOffsets,
PCFG_CALL_TARGET_INFO OffsetInformation
);
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
My problem is SetProcessValidCallTargets returns FALSE and GetLastError() tells me its 87 which means an invalid parameter has been passed. But i have no idea what is going wrong. Could anyone see whats going on?

QueryWorkingSet always returning false

I'm trying to get the size of allocated are for a process, descriminated by image, private and mapped. I'm using QueryWorkingSet to get the Working Set Information and then the Working Set Block.
When i called it for the first time, the GetLastError method return 24 which is to be expected so the next time I call QueryWorkingSet I set a diferent size for the block but then im getting an error code of 998.
Am I using QueryWorkingSet wrong or Im getting the handle for the process with the wrong access rights or Im the resize is not enough?
#include "pch.h"
#include <Windows.h>
#include<WinDef.h>
#include <psapi.h>
#include <iostream>
typedef struct {
DWORD img;
DWORD map;
DWORD prv;
} CommitCounters, *PCommitCounters;
BOOL GetCommitCountersFromProcess(_In_ int pid, _Out_ PCommitCounters committedCounter ) {
int numberOfTries = 3;
SYSTEM_INFO si;
GetSystemInfo(&si);
DWORD pageSz = si.dwPageSize;
PSAPI_WORKING_SET_INFORMATION wsi, *pwsi;
pwsi = &wsi;
DWORD ws_size;
MEMORY_BASIC_INFORMATION mbi, *pmbi;
pmbi = &mbi;
HANDLE processHandle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
wsi.NumberOfEntries = 0;
QueryWorkingSet(processHandle, &wsi, sizeof(pwsi));
BOOL res = false;
committedCounter->img = 0;
committedCounter->map = 0;
committedCounter->prv = 0;
while (numberOfTries > 0) {
DWORD lastError = GetLastError();
//ERROR_BAD_LENGTH
if (lastError == 24) {
ws_size = sizeof(wsi) + sizeof(PSAPI_WORKING_SET_INFORMATION) + sizeof(PSAPI_WORKING_SET_BLOCK) * wsi.NumberOfEntries;
pwsi = (PSAPI_WORKING_SET_INFORMATION*) malloc(ws_size);
pwsi->NumberOfEntries = wsi.NumberOfEntries;
BOOL resQws = QueryWorkingSet(processHandle, &wsi, ws_size);
DWORD teste = sizeof(wsi);
if (resQws) {
for (int i = 0; i < pwsi->NumberOfEntries; i++) {
PSAPI_WORKING_SET_BLOCK ws_block = pwsi->WorkingSetInfo[1];
//Access page information.
SIZE_T size = VirtualQuery((LPCVOID*)ws_block.VirtualPage, &mbi, 1);
if (size != 0 && pmbi ->State == 0x1000) {
switch (pmbi->Type)
{
case 0x1000000: // MEM_IMAGE
committedCounter->img += pageSz;
break;
case 0x40000: //MEM_MAPPED
committedCounter->map += pageSz;
break;
case 0x20000: //MEM_PRIVATE
committedCounter->prv += pageSz;
break;
}
}
else if (size == 0) {
return res;
}
}
CloseHandle(processHandle);
res = true;
return res;
}
free(pwsi);
}
numberOfTries--;
}
CloseHandle(processHandle);
return false;
}
You have a typo in your code. Just change:
BOOL resQws = QueryWorkingSet(processHandle, &wsi, ws_size);
to:
BOOL resQws = QueryWorkingSet(processHandle, pwsi, ws_size);
And then the call succeeds.
There may be further errors but I did not investigate those.

How to get a version of the embedded IE ActiveX control?

I've just embedded an IE/web browser ActiveX control in my C++ (MFC) application. And I'm curious how do I get the version of IE used for it?
This is sort of a hack, but you can use mshtml.dll that is an integral part of any IE control, and retrieve its version. Next is the code snippet from one of my projects:
#define SIZEOF(f) (sizeof(f) / sizeof(f[0]))
HMODULE hModMshtl = ::GetModuleHandle(L"mshtml.dll");
if(hModMshtl)
{
TCHAR buffMshtl[MAX_PATH];
buffMshtl[0] = 0;
::GetModuleFileName(hModMshtl, buffMshtl, SIZEOF(buffMshtl));
buffMshtl[SIZEOF(buffMshtl) - 1] = 0;
CString strProdName;
VS_FIXEDFILEINFO vi;
if(GetFileVersionAndProductName(buffMshtl, &vi, &strProdName))
{
//Got it
_tprintf(L"%s v.%d.%d.%d.%d",
strProdName.GetString(),
(DWORD)((vi.dwProductVersionLS & 0xFFFF0000) >> 16),
(DWORD)(vi.dwProductVersionLS & 0xFFFF),
(DWORD)((vi.dwProductVersionMS & 0xFFFF0000) >> 16),
(DWORD)(vi.dwProductVersionMS & 0xFFFF)
);
}
}
and here's how you can get its version:
BOOL GetFileVersionAndProductName(LPCTSTR pFilePath, VS_FIXEDFILEINFO* pOutVersionInfo, CString* pOutProductName)
{
BOOL bRes = FALSE;
CString strFilePath = pFilePath;
LPCTSTR pDescBuf = NULL;
struct LANGANDCODEPAGE {
WORD wLanguage;
WORD wCodePage;
};
CString strProdName;
BYTE* pData = NULL;
if(!strFilePath.IsEmpty())
{
//Get size needed
DWORD dwDummy;
DWORD dwSz = ::GetFileVersionInfoSize((LPTSTR)strFilePath.GetString(), &dwDummy);
if(dwSz > 0)
{
//Reserve mem
pData = new (std::nothrow)BYTE[dwSz];
if(pData)
{
//Retrieve version info
if(::GetFileVersionInfo((LPTSTR)strFilePath.GetString(), NULL, dwSz, pData))
{
UINT nczBufLn;
VS_FIXEDFILEINFO* pVi = NULL;
if(VerQueryValue(pData, _T("\\"), (VOID**)&pVi, &nczBufLn))
{
if(pVi &&
nczBufLn >= sizeof(*pVi) &&
pVi->dwSignature == 0xFEEF04BD)
{
//Got it
bRes = TRUE;
if(pOutVersionInfo)
*pOutVersionInfo = *pVi;
}
}
struct LANGANDCODEPAGE
{
WORD wLanguage;
WORD wCodePage;
} *lpTranslate = NULL;
// Read the list of languages and code pages.
UINT cbTranslate;
if(VerQueryValue(pData, L"\\VarFileInfo\\Translation", (LPVOID*)&lpTranslate, &cbTranslate))
{
//Get first language
if(lpTranslate &&
cbTranslate >= sizeof(*lpTranslate))
{
//Retrieve product name
CString strBlock;
strBlock.Format(L"\\StringFileInfo\\%04x%04x\\ProductName",
lpTranslate[0].wLanguage,
lpTranslate[0].wCodePage);
UINT dwProdLn = 0;
VOID* lpBufferName = NULL;
if(VerQueryValue(pData, strBlock, &lpBufferName, &dwProdLn))
{
//Get name
memcpy(strProdName.GetBufferSetLength(dwProdLn), lpBufferName, dwProdLn * sizeof(TCHAR));
strProdName.ReleaseBuffer();
}
}
}
}
}
}
}
if(pOutProductName)
*pOutProductName = strProdName;
//Free mem
if(pData)
delete[] pData;
return bRes;
}

Exported function forwarded to itself?

I ran into an extremely weird issue today when messing around with the parsing the Windows Portable Executable file structure today. Specifically in the Export table.
I found myself getting a Stack Overflow (so this seemed like the most appropriate QA board) when trying to resolve the function address of an Exported function in a DLL.
I've written my own version of GetProcAddress which does the parsing manually rather than calling the existing GetProcAddress method. Please don't just tell me to use the existing GetProcAddress method, it's not suitable for my current situation and I want to learn something from this.
For most of the situations I encounter, my version has worked admirably and hasn't hit any issues. However the function was tested against a DLL named API-MS-Win-Core-ProcessThreads-L1-1-0.dll (as part of a recursive parse of Kernel32.dll) and this is when the StackOverflow occurred.
I've narrowed it down to the following function exported from API-MS-Win-Core-ProcessThreads-L1-1-0.dll:
CreateRemoteThreadEx
Now, this exported function is actually a forwarded export. Usually this is no worries; I've written my function so that it should handle forwarded exports. However this function is forwarded to
api-ms-win-core-processthreads-l1-1-0.CreateRemoteThreadEx
Anyone seeing the problem here? Stepping through the code, my GetProcAddress function then calls LoadLibrary on api-ms-win-core-processthreads-l1-1-0 and then attempts to recursively lookup CreateRemoteThreadEx. On the very next iteration, however, the CreateRemoteThreadEx function is again forwarded... to
api-ms-win-core-processthreads-l1-1-0.CreateRemoteThreadEx
And so begins the StackOverflow. After a bit more investigation I found that the result of calling
LoadLibraryA("api-ms-win-core-processthreads-l1-1-0");
Returns the same result as
LoadLibraryA("kernel32.dll");
I'm stumped.
Here's my current code:
#include <Windows.h>
#define MKPTR(p1,p2) ((DWORD_PTR)(p1) + (DWORD_PTR)(p2))
INT LookupExport(IMAGE_DOS_HEADER* pDosHd, DWORD* pNames, DWORD nNames, LPCSTR lpProcName)
{
// Do a binary search on the name pointer table
INT start = 0,
index = -1,
middle = -1,
end = nNames - 1,
cmp = 0;
CHAR *pName;
while (start <= end && index == -1)
{
middle = (start + end) >> 1;
pName = (CHAR*)MKPTR(pDosHd, pNames[middle]);
if ((cmp = strcmp(pName, lpProcName)) == 0)
index = middle; // found
else if (cmp < 0)
start = middle + 1;
else
end = middle;
}
return index;
}
FARPROC InternalGetProcAddress(HMODULE hModule, LPCSTR lpProcName)
{
BOOL ordinalSearch = HIWORD(lpProcName) == 0;
WORD ordinal = 0;
IMAGE_DOS_HEADER *pDosHd = (IMAGE_DOS_HEADER*)hModule;
if (pDosHd->e_magic != IMAGE_DOS_SIGNATURE)
return NULL;
IMAGE_NT_HEADERS *pNtHd = (IMAGE_NT_HEADERS*)MKPTR(pDosHd, pDosHd->e_lfanew);
if (pNtHd->Signature != IMAGE_NT_SIGNATURE)
return NULL;
IMAGE_DATA_DIRECTORY directory = pNtHd->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
if (directory.Size == 0 || directory.VirtualAddress == 0)
return NULL;
IMAGE_EXPORT_DIRECTORY *pExports = (IMAGE_EXPORT_DIRECTORY*)MKPTR(pDosHd, directory.VirtualAddress);
if (!ordinalSearch)
{
INT index = LookupExport(pDosHd, (DWORD*)MKPTR(pDosHd, pExports->AddressOfNames), pExports->NumberOfNames, lpProcName);
if (index == -1)
return NULL;
ordinal = ((WORD*)MKPTR(pDosHd, pExports->AddressOfNameOrdinals))[index];
}
else
{
ordinal = LOWORD(lpProcName);
}
INT delta = pExports->Base - 1;
DWORD dwAddress = ((DWORD*)MKPTR(pDosHd, pExports->AddressOfFunctions))[ordinal - delta];
// Check whether forwarded:
if (dwAddress >= directory.VirtualAddress && dwAddress < (directory.VirtualAddress + directory.Size))
{
CHAR pForward[256];
strcpy(pForward, (CHAR*)MKPTR(pDosHd, dwAddress));
CHAR *pFunction = strchr(pForward, '.');
if (pFunction == NULL)
return NULL;
// break into seperate parts and recurse
*pFunction++ = 0;
return InternalGetProcAddress(LoadLibraryA(pForward), pFunction);
}
return (FARPROC)MKPTR(hModule, dwAddress);
}
Any insight would be greatly appreciated.
Okay after following #sergmat's advice I took a look at the API Set documentation (found here for anyone interested). I've now modified my GetProcAddress code to do a naive lookup of the Api Set table.
#include <Windows.h>
#define MKPTR(p1,p2) ((DWORD_PTR)(p1) + (DWORD_PTR)(p2))
typedef struct _stripped_peb32 {
BYTE unused1[0x038];
PVOID ApiSet;
BYTE unused2[0x1AC];
} PEB32;
typedef struct _stripped_peb64 {
BYTE unused1[0x068];
PVOID ApiSet;
BYTE unused2[0x23C];
} PEB64;
typedef struct _PROCESS_BASIC_INFORMATION {
PVOID Reserved1;
LPVOID PebBaseAddress;
PVOID Reserved2[2];
ULONG_PTR UniqueProcessId;
PVOID Reserved3;
} PROCESS_BASIC_INFORMATION;
typedef struct _api_set_host {
DWORD ImportModuleName;
WORD ImportModuleNameLength;
DWORD HostModuleName;
WORD HostModuleNameLength;
} API_SET_HOST;
typedef struct _api_set_host_descriptor {
DWORD NumberOfHosts;
API_SET_HOST Hosts[1];
} API_SET_HOST_DESCRIPTOR;
typedef struct _api_set_entry {
DWORD Name;
WORD NameLength;
DWORD HostDescriptor;
} API_SET_ENTRY;
typedef struct _api_set_header {
DWORD unknown1;
DWORD NumberOfEntries;
API_SET_ENTRY Entries[1];
} API_SET_HEADER;
typedef NTSTATUS (__stdcall *fnNtQueryInformationProcess)(HANDLE ProcessHandle, DWORD ProcessInformationClass, PVOID ProcessInformation, ULONG ProcessInformationLength, PULONG ReturnLength);
API_SET_HEADER *GetApiSetHeader()
{
fnNtQueryInformationProcess NtQueryInformationProcess = (fnNtQueryInformationProcess)GetProcAddress(LoadLibraryW(L"ntdll.dll"), "NtQueryInformationProcess");
if (!NtQueryInformationProcess)
return NULL;
PROCESS_BASIC_INFORMATION info;
if (NtQueryInformationProcess(GetCurrentProcess(), 0, &info, sizeof(info), NULL) != S_OK)
return NULL;
#if defined(_WIN32)
return (API_SET_HEADER*)(((PEB32*)info.PebBaseAddress)->ApiSet);
#elif defined(_WIN64)
return (API_SET_HEADER*)(((PEB64*)info.PebBaseAddress)->ApiSet);
#else
return NULL; // unsupported architecture
#endif
}
HMODULE ResolveImportMap(LPCSTR lpModuleName)
{
API_SET_HEADER *pHeader = GetApiSetHeader();
if (pHeader == NULL)
return NULL;
API_SET_ENTRY *pEntry = pHeader->Entries;
API_SET_HOST_DESCRIPTOR* pDescriptor;
wchar_t module[128];
// First, normalize the LPCSTR, the API Set table doesn't have the API- prefix
if (strnicmp("api-", lpModuleName, 4) == 0)
lpModuleName += 4;
// Next convert the LPCSTR to a unicode string for comparison and remove the extension (if found)
mbstowcs(module, lpModuleName, sizeof(module) / sizeof(wchar_t));
wchar_t *dot = wcsrchr(module, L'.');
if (dot) *dot = L'\0';
// Begin the lookup:
// todo: implement a case-insensitive binary search, not much to be gained for the effort IMO as there's
// only 35 entries in the current version of Windows 7, but the option is there for performance nuts.
for(unsigned long i = 0; i < pHeader->NumberOfEntries; ++i, ++pEntry)
{
// Check the top-level host map
if (wcsnicmp(module, (const wchar_t*)MKPTR(pHeader, pEntry->Name), pEntry->NameLength) == 0)
{
pDescriptor = (API_SET_HOST_DESCRIPTOR*)MKPTR(pHeader, pEntry->HostDescriptor);
// iterate backwards through the hosts to find the most important one (I think this is naive)
for(unsigned long j = pDescriptor->NumberOfHosts; j > 0; --j)
{
if (pDescriptor->Hosts[j - 1].HostModuleNameLength)
{
memcpy(module, (const void*)MKPTR(pHeader, pDescriptor->Hosts[j - 1].HostModuleName), pDescriptor->Hosts[j - 1].HostModuleNameLength);
module[pDescriptor->Hosts[j - 1].HostModuleNameLength / sizeof(wchar_t)] = L'\0';
return GetModuleHandleW(module); // All the modules should already be loaded, so use GetModuleHandle rather than LoadLibrary
}
}
}
}
return NULL;
}
INT LookupExport(IMAGE_DOS_HEADER* pDosHd, DWORD* pNames, DWORD nNames, LPCSTR lpProcName)
{
// Do a binary search on the name pointer table
INT start = 0,
index = -1,
middle = -1,
end = nNames - 1,
cmp = 0;
CHAR *pName;
while (start <= end && index == -1)
{
middle = (start + end) >> 1;
pName = (CHAR*)MKPTR(pDosHd, pNames[middle]);
if ((cmp = strcmp(pName, lpProcName)) == 0)
index = middle;
else if (cmp < 0)
start = middle + 1;
else
end = middle;
}
return index;
}
FARPROC InternalGetProcAddress(HMODULE hModule, LPCSTR lpProcName)
{
if (hModule == NULL)
return NULL;
BOOL ordinalSearch = HIWORD(lpProcName) == 0;
WORD ordinal = 0;
IMAGE_DOS_HEADER *pDosHd = (IMAGE_DOS_HEADER*)hModule;
if (pDosHd->e_magic != IMAGE_DOS_SIGNATURE)
return NULL;
IMAGE_NT_HEADERS *pNtHd = (IMAGE_NT_HEADERS*)MKPTR(pDosHd, pDosHd->e_lfanew);
if (pNtHd->Signature != IMAGE_NT_SIGNATURE)
return NULL;
IMAGE_DATA_DIRECTORY directory = pNtHd->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
if (directory.Size == 0 || directory.VirtualAddress == 0)
return NULL;
IMAGE_EXPORT_DIRECTORY *pExports = (IMAGE_EXPORT_DIRECTORY*)MKPTR(pDosHd, directory.VirtualAddress);
if (!ordinalSearch)
{
INT index = LookupExport(pDosHd, (DWORD*)MKPTR(pDosHd, pExports->AddressOfNames), pExports->NumberOfNames, lpProcName);
if (index == -1)
return NULL;
ordinal = ((WORD*)MKPTR(pDosHd, pExports->AddressOfNameOrdinals))[index];
}
else
{
ordinal = LOWORD(lpProcName);
}
INT ordbase = pExports->Base - 1;
DWORD dwAddress = ((DWORD*)MKPTR(pDosHd, pExports->AddressOfFunctions))[ordinal - ordbase];
// Check whether forwarded:
if (dwAddress >= directory.VirtualAddress && dwAddress < (directory.VirtualAddress + directory.Size))
{
CHAR pForward[256];
strcpy(pForward, (CHAR*)MKPTR(pDosHd, dwAddress));
CHAR *pFunction = strchr(pForward, '.');
if (pFunction == NULL)
return NULL;
// break into seperate parts and recurse
*pFunction++ = 0;
// check if ordinal-forwarded
if (*pFunction == '#')
pFunction = (PSTR)(unsigned short)(atoi(++pFunction));
HMODULE hDestination = LoadLibraryA(pForward);
// detect an infinite loop, the forward declaration requests the same module handle with
// the same function lookup, this could be an Api Set (Windows7+)
if (hDestination == hModule && (ordinalSearch ? LOWORD(pFunction) == LOWORD(lpProcName) : strcmp(pFunction, lpProcName) == 0))
hDestination = ResolveImportMap(pForward); // ResolveImportMap will return NULL if not an API Set and so avoid infinite recursion
return InternalGetProcAddress(hDestination, pFunction);
}
return (FARPROC)MKPTR(hModule, dwAddress);
}

Windows CD Burning API

We need to programatically burn files to CD in a C\C++ Windows XP/Vista application we are developing using Borlands Turbo C++.
What is the simplest and best way to do this? We would prefer a native windows API (that doesnt rely on MFC) so as not to rely on any third party software/drivers if one is available.
We used the following:
Store files in the directory returned by GetBurnPath, then write using Burn. GetCDRecordableInfo is used to check when the CD is ready.
#include <stdio.h>
#include <imapi.h>
#include <windows.h>
struct MEDIAINFO {
BYTE nSessions;
BYTE nLastTrack;
ULONG nStartAddress;
ULONG nNextWritable;
ULONG nFreeBlocks;
};
//==============================================================================
// Description: CD burning on Windows XP
//==============================================================================
#define CSIDL_CDBURN_AREA 0x003b
SHSTDAPI_(BOOL) SHGetSpecialFolderPathA(HWND hwnd, LPSTR pszPath, int csidl, BOOL fCreate);
SHSTDAPI_(BOOL) SHGetSpecialFolderPathW(HWND hwnd, LPWSTR pszPath, int csidl, BOOL fCreate);
#ifdef UNICODE
#define SHGetSpecialFolderPath SHGetSpecialFolderPathW
#else
#define SHGetSpecialFolderPath SHGetSpecialFolderPathA
#endif
//==============================================================================
// Interface IDiscMaster
const IID IID_IDiscMaster = {0x520CCA62,0x51A5,0x11D3,{0x91,0x44,0x00,0x10,0x4B,0xA1,0x1C,0x5E}};
const CLSID CLSID_MSDiscMasterObj = {0x520CCA63,0x51A5,0x11D3,{0x91,0x44,0x00,0x10,0x4B,0xA1,0x1C,0x5E}};
typedef interface ICDBurn ICDBurn;
// Interface ICDBurn
const IID IID_ICDBurn = {0x3d73a659,0xe5d0,0x4d42,{0xaf,0xc0,0x51,0x21,0xba,0x42,0x5c,0x8d}};
const CLSID CLSID_CDBurn = {0xfbeb8a05,0xbeee,0x4442,{0x80,0x4e,0x40,0x9d,0x6c,0x45,0x15,0xe9}};
MIDL_INTERFACE("3d73a659-e5d0-4d42-afc0-5121ba425c8d")
ICDBurn : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE GetRecorderDriveLetter(
/* [size_is][out] */ LPWSTR pszDrive,
/* [in] */ UINT cch) = 0;
virtual HRESULT STDMETHODCALLTYPE Burn(
/* [in] */ HWND hwnd) = 0;
virtual HRESULT STDMETHODCALLTYPE HasRecordableDrive(
/* [out] */ BOOL *pfHasRecorder) = 0;
};
//==============================================================================
// Description: Get burn pathname
// Parameters: pathname - must be at least MAX_PATH in size
// Returns: Non-zero for an error
// Notes: CoInitialize(0) must be called once in application
//==============================================================================
int GetBurnPath(char *path)
{
ICDBurn* pICDBurn;
int ret = 0;
if (SUCCEEDED(CoCreateInstance(CLSID_CDBurn, NULL,CLSCTX_INPROC_SERVER,IID_ICDBurn,(LPVOID*)&pICDBurn))) {
BOOL flag;
if (pICDBurn->HasRecordableDrive(&flag) == S_OK) {
if (SHGetSpecialFolderPath(0, path, CSIDL_CDBURN_AREA, 0)) {
strcat(path, "\\");
}
else {
ret = 1;
}
}
else {
ret = 2;
}
pICDBurn->Release();
}
else {
ret = 3;
}
return ret;
}
//==============================================================================
// Description: Get CD pathname
// Parameters: pathname - must be at least 5 bytes in size
// Returns: Non-zero for an error
// Notes: CoInitialize(0) must be called once in application
//==============================================================================
int GetCDPath(char *path)
{
ICDBurn* pICDBurn;
int ret = 0;
if (SUCCEEDED(CoCreateInstance(CLSID_CDBurn, NULL,CLSCTX_INPROC_SERVER,IID_ICDBurn,(LPVOID*)&pICDBurn))) {
BOOL flag;
WCHAR drive[5];
if (pICDBurn->GetRecorderDriveLetter(drive, 4) == S_OK) {
sprintf(path, "%S", drive);
}
else {
ret = 1;
}
pICDBurn->Release();
}
else {
ret = 3;
}
return ret;
}
//==============================================================================
// Description: Burn CD
// Parameters: None
// Returns: Non-zero for an error
// Notes: CoInitialize(0) must be called once in application
//==============================================================================
int Burn(void)
{
ICDBurn* pICDBurn;
int ret = 0;
if (SUCCEEDED(CoCreateInstance(CLSID_CDBurn, NULL,CLSCTX_INPROC_SERVER,IID_ICDBurn,(LPVOID*)&pICDBurn))) {
if (pICDBurn->Burn(NULL) != S_OK) {
ret = 1;
}
pICDBurn->Release();
}
else {
ret = 2;
}
return ret;
}
//==============================================================================
bool GetCDRecordableInfo(long *FreeSpaceSize)
{
bool Result = false;
IDiscMaster *idm = NULL;
IDiscRecorder *idr = NULL;
IEnumDiscRecorders *pEnumDiscRecorders = NULL;
ULONG cnt;
long type;
long mtype;
long mflags;
MEDIAINFO mi;
try {
CoCreateInstance(CLSID_MSDiscMasterObj, 0, CLSCTX_ALL, IID_IDiscMaster, (void**)&idm);
idm->Open();
idm->EnumDiscRecorders(&pEnumDiscRecorders);
pEnumDiscRecorders->Next(1, &idr, &cnt);
pEnumDiscRecorders->Release();
idr->OpenExclusive();
idr->GetRecorderType(&type);
idr->QueryMediaType(&mtype, &mflags);
idr->QueryMediaInfo(&mi.nSessions, &mi.nLastTrack, &mi.nStartAddress, &mi.nNextWritable, &mi.nFreeBlocks);
idr->Release();
idm->Close();
idm->Release();
Result = true;
}
catch (...) {
Result = false;
}
if (Result == true) {
Result = false;
if (mtype == 0) {
// No Media inserted
Result = false;
}
else {
if ((mflags & 0x04) == 0x04) {
// Writable Media
Result = true;
}
else {
Result = false;
}
if (Result == true) {
*FreeSpaceSize = (mi.nFreeBlocks * 2048);
}
else {
*FreeSpaceSize = 0;
}
}
}
return Result;
}
To complement the accepted answer, we added this helper function to programatically change the burn directory on the fly as this was a requirement of ours.
typedef HMODULE (WINAPI * SHSETFOLDERPATHA)( int , HANDLE , DWORD , LPCTSTR );
int SetBurnPath( char * cpPath )
{
SHSETFOLDERPATHA pSHSetFolderPath;
HANDLE hShell = LoadLibraryA( "shell32.dll" );
if( hShell == NULL )
return -2;
DWORD dwOrdinal = 0x00000000 + 231;
pSHSetFolderPath = (SHSETFOLDERPATHA)GetProcAddress( hShell, (LPCSTR)dwOrdinal );
if( pSHSetFolderPath == NULL )
return -3;
if( pSHSetFolderPath( CSIDL_CDBURN_AREA, NULL, 0, cpPath ) == S_OK )
return 0;
return -1;
}
This is the information for IMAPI in MSDN site http://msdn.microsoft.com/en-us/library/aa939967.aspx
You should be able to use the shell's ICDBurn interface. Back in the XP day MFC didn't even have any classes for cd burning. I'll see if I can find some examples for you, but it's been a while since I looked at this.