535 lines
17 KiB
C
535 lines
17 KiB
C
|
/* dlcheck - verify that a DLL using delay-load calls APIs that have
|
||
|
* stubs in kernel32.dll (aka dload.lib)
|
||
|
*
|
||
|
* HISTORY:
|
||
|
* 25-Nov-98 barrybo Wrote it.
|
||
|
*/
|
||
|
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <windows.h>
|
||
|
#include <imagehlp.h>
|
||
|
#include <delayimp.h>
|
||
|
#include <dloaddef.h>
|
||
|
|
||
|
// Function Forward Parameters...
|
||
|
void Usage( void );
|
||
|
int __cdecl main( int, char ** );
|
||
|
|
||
|
int DloadBreakOnFail = FALSE;
|
||
|
extern int DloadDbgPrint = FALSE;
|
||
|
|
||
|
// implemented in kernel32p.lib
|
||
|
FARPROC
|
||
|
WINAPI
|
||
|
DelayLoadFailureHook (
|
||
|
LPCSTR pszDllName,
|
||
|
LPCSTR pszProcName
|
||
|
);
|
||
|
|
||
|
|
||
|
typedef FARPROC (WINAPI *PfnKernel32HookProc)(
|
||
|
LPCSTR pszDllName,
|
||
|
LPCSTR pszProcName
|
||
|
);
|
||
|
|
||
|
PfnKernel32HookProc __pfnFailureProc = DelayLoadFailureHook;
|
||
|
|
||
|
const char rgstrUsage[] = {
|
||
|
"Verify that delayloaded imports all have failure handlers in kernel32.\n"
|
||
|
"usage: dlcheck [switches] image-name\n"
|
||
|
"where: [-?] display this message\n"
|
||
|
" [-l] use the live version of kernel32.dll on the machine\n"
|
||
|
" [-s] use the static dload.lib linked into dlcheck\n"
|
||
|
" [-t] test the static dload.lib linked into dlcheck and exit\n"
|
||
|
" [-i <inifile>] use the information in inifile to check a binary\n"
|
||
|
" [-f] force check the binary (assumes -s)\n"
|
||
|
"\n"
|
||
|
};
|
||
|
|
||
|
HANDLE BaseDllHandle;
|
||
|
PLOADED_IMAGE g_pli;
|
||
|
PIMAGE_SECTION_HEADER g_DelaySection;
|
||
|
char g_szImageName[MAX_PATH];
|
||
|
char g_szDelayLoadHandler[MAX_PATH];
|
||
|
BOOL fForceCheckImage = FALSE;
|
||
|
|
||
|
//
|
||
|
// Convert an absolute pointer that points into the image if the image
|
||
|
// was loaded as a DLL at its preferred base, into a pointer into the
|
||
|
// DLL as it was mapped by imagehlp.
|
||
|
//
|
||
|
void *
|
||
|
ConvertImagePointer(void * p)
|
||
|
{
|
||
|
if (!p) {
|
||
|
return NULL;
|
||
|
} else {
|
||
|
return (void *)((ULONG_PTR)(p) -
|
||
|
(ULONG_PTR)g_pli->FileHeader->OptionalHeader.ImageBase +
|
||
|
(ULONG_PTR)g_pli->MappedAddress -
|
||
|
(ULONG_PTR)g_DelaySection->VirtualAddress +
|
||
|
(ULONG_PTR)g_DelaySection->PointerToRawData);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void *
|
||
|
RvaToPtr(DWORD_PTR rva)
|
||
|
{
|
||
|
DWORD i;
|
||
|
PIMAGE_SECTION_HEADER pSect;
|
||
|
if (!rva)
|
||
|
return NULL;
|
||
|
|
||
|
for (i = 0; i < g_pli->NumberOfSections; i++) {
|
||
|
pSect = g_pli->Sections+i;
|
||
|
if (rva >= g_pli->Sections[i].VirtualAddress &&
|
||
|
rva <= (g_pli->Sections[i].VirtualAddress + g_pli->Sections[i].Misc.VirtualSize))
|
||
|
{
|
||
|
return (PVOID)
|
||
|
(g_pli->MappedAddress +
|
||
|
g_pli->Sections[i].PointerToRawData +
|
||
|
(rva - g_pli->Sections[i].VirtualAddress));
|
||
|
}
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
void Usage( void )
|
||
|
{
|
||
|
puts(rgstrUsage);
|
||
|
|
||
|
exit (1);
|
||
|
}
|
||
|
|
||
|
BOOLEAN ImageLinksToKernel32Handler( void )
|
||
|
{
|
||
|
PIMAGE_IMPORT_DESCRIPTOR Imports;
|
||
|
ULONG ImportSize;
|
||
|
PULONG_PTR pIAT;
|
||
|
PIMAGE_IMPORT_BY_NAME pImport;
|
||
|
|
||
|
Imports = (PIMAGE_IMPORT_DESCRIPTOR)
|
||
|
ImageDirectoryEntryToData(g_pli->MappedAddress,
|
||
|
FALSE,
|
||
|
IMAGE_DIRECTORY_ENTRY_IMPORT,
|
||
|
&ImportSize
|
||
|
);
|
||
|
if (!Imports) {
|
||
|
// Image has delayload imports, but no true imports.
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
while (Imports->Name) {
|
||
|
char *szName;
|
||
|
|
||
|
szName = ImageRvaToVa(g_pli->FileHeader, (PVOID)g_pli->MappedAddress, Imports->Name, NULL);
|
||
|
|
||
|
if (szName && _stricmp(szName, "KERNEL32.DLL") == 0) {
|
||
|
pIAT = ImageRvaToVa(g_pli->FileHeader,
|
||
|
(PVOID)g_pli->MappedAddress,
|
||
|
Imports->OriginalFirstThunk,
|
||
|
NULL);
|
||
|
|
||
|
while (pIAT && *pIAT) {
|
||
|
pImport = ImageRvaToVa(g_pli->FileHeader,
|
||
|
(PVOID)g_pli->MappedAddress,
|
||
|
(ULONG) *pIAT,
|
||
|
NULL);
|
||
|
|
||
|
if (pImport && _stricmp(pImport->Name, "DelayLoadFailureHook") == 0) {
|
||
|
return TRUE;
|
||
|
}
|
||
|
pIAT++;
|
||
|
}
|
||
|
}
|
||
|
Imports++;
|
||
|
}
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Validate that the statically-linked delayload stub table is not
|
||
|
// blatantly broken. The most common error is not listing the functions
|
||
|
// in the correct order so the binary search fails.
|
||
|
//
|
||
|
int ValidateStaticDelayloadStubs()
|
||
|
{
|
||
|
extern const DLOAD_DLL_MAP g_DllMap;
|
||
|
UINT i, j;
|
||
|
int Errors = 0;
|
||
|
|
||
|
//
|
||
|
// Ensure that the DLL map is in alphabetical order.
|
||
|
//
|
||
|
for (i = 1; i < g_DllMap.NumberOfEntries; i++)
|
||
|
{
|
||
|
if (strcmp(g_DllMap.pDllEntry[i-1].pszDll,
|
||
|
g_DllMap.pDllEntry[i].pszDll) >= 0)
|
||
|
{
|
||
|
fprintf(stderr, "DLCHECK : error DL000001 : Static delayload table is corrupted\n"
|
||
|
" %s and %s not in alphabetical order\n",
|
||
|
g_DllMap.pDllEntry[i-1].pszDll,
|
||
|
g_DllMap.pDllEntry[i].pszDll);
|
||
|
Errors = 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// For each DLL...
|
||
|
for (i = 0; i < g_DllMap.NumberOfEntries; i++)
|
||
|
{
|
||
|
const DLOAD_DLL_ENTRY *pEntry = &g_DllMap.pDllEntry[i];
|
||
|
|
||
|
//
|
||
|
// Name must be lowercase.
|
||
|
//
|
||
|
char szLower[MAX_PATH];
|
||
|
strcpy(szLower, pEntry->pszDll);
|
||
|
_strlwr(szLower);
|
||
|
if (strcmp(szLower, pEntry->pszDll) != 0)
|
||
|
{
|
||
|
fprintf(stderr, "DLCHECK : error DL000002 : Static delayload table is corrupted\n"
|
||
|
" %s must be all-lowercase\n",
|
||
|
pEntry->pszDll);
|
||
|
Errors = 1;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Ensure that the exports are in alphabetical order
|
||
|
//
|
||
|
{
|
||
|
const DLOAD_PROCNAME_MAP *pProcNameMap = pEntry->pProcNameMap;
|
||
|
|
||
|
if (pProcNameMap)
|
||
|
{
|
||
|
const DLOAD_PROCNAME_ENTRY *pProcNameEntry = pProcNameMap->pProcNameEntry;
|
||
|
for (j = 1; j < pProcNameMap->NumberOfEntries; j++)
|
||
|
{
|
||
|
if (strcmp(pProcNameEntry[j-1].pszProcName,
|
||
|
pProcNameEntry[j].pszProcName) >= 0)
|
||
|
{
|
||
|
fprintf(stderr, "DLCHECK : error DL000003 : Static delayload table is corrupted\n"
|
||
|
" %s.%s and %s.%s not in alphabetical order\n",
|
||
|
g_DllMap.pDllEntry[i].pszDll,
|
||
|
pProcNameEntry[j-1].pszProcName,
|
||
|
g_DllMap.pDllEntry[i].pszDll,
|
||
|
pProcNameEntry[j].pszProcName);
|
||
|
|
||
|
Errors = 1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Ensure that the ordinals are in alphabetical order
|
||
|
//
|
||
|
{
|
||
|
const DLOAD_ORDINAL_MAP *pOrdinalMap = pEntry->pOrdinalMap;
|
||
|
|
||
|
if (pOrdinalMap)
|
||
|
{
|
||
|
const DLOAD_ORDINAL_ENTRY *pOrdinalEntry = pOrdinalMap->pOrdinalEntry;
|
||
|
for (j = 1; j < pOrdinalMap->NumberOfEntries; j++)
|
||
|
{
|
||
|
if (pOrdinalEntry[j-1].dwOrdinal >= pOrdinalEntry[j].dwOrdinal)
|
||
|
{
|
||
|
fprintf(stderr, "DLCHECK : error DL000001 : Static delayload table is corrupted\n"
|
||
|
" %s.%d and %s.%d not in numeric order\n",
|
||
|
g_DllMap.pDllEntry[i].pszDll,
|
||
|
pOrdinalEntry[j-1].dwOrdinal,
|
||
|
g_DllMap.pDllEntry[i].pszDll,
|
||
|
pOrdinalEntry[j-1].dwOrdinal);
|
||
|
Errors = 1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
return Errors;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
__cdecl
|
||
|
main (
|
||
|
int c,
|
||
|
char *v[]
|
||
|
)
|
||
|
{
|
||
|
PImgDelayDescr Imports;
|
||
|
ULONG ImportSize;
|
||
|
char *szName;
|
||
|
PIMAGE_THUNK_DATA pINT;
|
||
|
DelayLoadInfo dlinfo;
|
||
|
FARPROC fp;
|
||
|
int ReturnValue;
|
||
|
BOOL fCallHandler;
|
||
|
BOOL fPE32;
|
||
|
|
||
|
if (*v[1] == '-' || *v[1] == '/') {
|
||
|
switch ( *(v[1]+1) ) {
|
||
|
case 's':
|
||
|
case 'S':
|
||
|
if (c != 3) {
|
||
|
Usage();
|
||
|
}
|
||
|
strcpy(g_szImageName, v[2]);
|
||
|
break; // nothing needs to be done.
|
||
|
|
||
|
case 'l':
|
||
|
case 'L':
|
||
|
__pfnFailureProc = (PfnKernel32HookProc)GetProcAddress(GetModuleHandleA("kernel32.dll"), "DelayLoadFailureHook");
|
||
|
if (!__pfnFailureProc) {
|
||
|
fprintf(stderr, "DLCHECK : fatal error %d: looking up kernel32 delayload hook\n", GetLastError());
|
||
|
return 1;
|
||
|
}
|
||
|
if (c != 3) {
|
||
|
Usage();
|
||
|
}
|
||
|
strcpy(g_szImageName, v[2]);
|
||
|
break;
|
||
|
|
||
|
case 'i':
|
||
|
case 'I':
|
||
|
if (c != 3) {
|
||
|
Usage();
|
||
|
}
|
||
|
{
|
||
|
char szIniFile[MAX_PATH];
|
||
|
char szTemp[MAX_PATH];
|
||
|
char szTemp2[MAX_PATH];
|
||
|
char* p = v[2];
|
||
|
|
||
|
if (p[1] != ':')
|
||
|
{
|
||
|
// not a full path...
|
||
|
GetCurrentDirectory(sizeof(szTemp), szTemp);
|
||
|
sprintf(szIniFile, "%s\\%s", szTemp, v[2]);
|
||
|
}
|
||
|
|
||
|
GetPrivateProfileString("Default", "DelayLoadHandler", "", g_szDelayLoadHandler, sizeof(g_szDelayLoadHandler), szIniFile);
|
||
|
|
||
|
// foomodule.dll.ini -> foomodule.dll
|
||
|
strcpy(g_szImageName, v[2]);
|
||
|
p = strstr(g_szImageName, ".ini");
|
||
|
if (p)
|
||
|
{
|
||
|
*p = '\0';
|
||
|
}
|
||
|
|
||
|
if (_stricmp(g_szDelayLoadHandler, "FORCE") == 0)
|
||
|
{
|
||
|
// if the delayload handler is set to FORCE, we check the binary as if it were
|
||
|
// using kernel32
|
||
|
fForceCheckImage = TRUE;
|
||
|
}
|
||
|
|
||
|
if ((_stricmp(g_szDelayLoadHandler, "kernel32") != 0) &&
|
||
|
(_stricmp(g_szDelayLoadHandler, "FORCE") != 0))
|
||
|
{
|
||
|
// currently only able to check dll's who use kernel32.dll for their delayload handler
|
||
|
fprintf(stdout, "DLCHECK : warning DL000000 : Unable to check delayload failure behavior\n"
|
||
|
" %s uses %s as a handler, not kernel32\n", g_szImageName, g_szDelayLoadHandler);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// foomodule.dll -> d:\binaries.x86chk\foomodule.dll
|
||
|
if (ExpandEnvironmentStrings("%_NTPostBld%", szTemp, sizeof(szTemp)) == 0)
|
||
|
{
|
||
|
fprintf(stderr, "DLCHECK : fatal error : _NTPostBld environment variable not set\n");
|
||
|
return 1;
|
||
|
}
|
||
|
GetPrivateProfileString("Default", "DestinationDir", "", szTemp2, sizeof(szTemp2), szIniFile);
|
||
|
strcat(szTemp, "\\");
|
||
|
strcat(szTemp, szTemp2);
|
||
|
strcat(szTemp, g_szImageName);
|
||
|
strcpy(g_szImageName, szTemp);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 't':
|
||
|
case 'T':
|
||
|
if (c != 2) {
|
||
|
Usage();
|
||
|
}
|
||
|
return ValidateStaticDelayloadStubs();
|
||
|
|
||
|
case 'f':
|
||
|
case 'F':
|
||
|
if (c != 3)
|
||
|
{
|
||
|
Usage();
|
||
|
}
|
||
|
fForceCheckImage = TRUE;
|
||
|
strcpy(g_szImageName, v[2]);
|
||
|
break; // nothing needs to be done.
|
||
|
|
||
|
default:
|
||
|
Usage();
|
||
|
}
|
||
|
} else {
|
||
|
Usage();
|
||
|
}
|
||
|
|
||
|
g_pli = ImageLoad(g_szImageName, NULL);
|
||
|
if (!g_pli) {
|
||
|
fprintf(stderr, "DLCHECK : fatal error %d: loading '%s'\n", GetLastError(), g_szImageName);
|
||
|
return 1;
|
||
|
}
|
||
|
Imports = (PImgDelayDescr)
|
||
|
ImageDirectoryEntryToDataEx(g_pli->MappedAddress,
|
||
|
FALSE,
|
||
|
IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT,
|
||
|
&ImportSize,
|
||
|
&g_DelaySection
|
||
|
);
|
||
|
if (!Imports) {
|
||
|
fprintf(stdout, "DLCHECK : warning DL000000: image '%s' has no delayload imports\n", g_szImageName);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
fPE32 = g_pli->FileHeader->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC ? TRUE : FALSE;
|
||
|
|
||
|
if (fForceCheckImage)
|
||
|
{
|
||
|
fCallHandler = TRUE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
fCallHandler = ImageLinksToKernel32Handler();
|
||
|
}
|
||
|
|
||
|
if (!fCallHandler) {
|
||
|
fprintf(stderr, "DLCHECK : fatal errror : DLL doesn't import kernel32!DelayLoadFailureHook.\n"
|
||
|
"(use -f option to override)\n"
|
||
|
"\n");
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Walk each delayloaded DLL
|
||
|
//
|
||
|
ReturnValue = 0; // assume success
|
||
|
|
||
|
if (Imports->grAttrs & dlattrRva) {
|
||
|
PImgDelayDescrV2 pImportsV2 = (PImgDelayDescrV2)Imports;
|
||
|
szName = (char *)RvaToPtr(pImportsV2->rvaDLLName);
|
||
|
pINT = (PIMAGE_THUNK_DATA)RvaToPtr(pImportsV2->rvaINT);
|
||
|
} else {
|
||
|
PImgDelayDescrV1 pImportsV1 = (PImgDelayDescrV1)Imports;
|
||
|
szName = (char *)ConvertImagePointer((void *)pImportsV1->szName);
|
||
|
pINT = (PIMAGE_THUNK_DATA)ConvertImagePointer((void *)pImportsV1->pINT);
|
||
|
}
|
||
|
|
||
|
while (szName) {
|
||
|
// printf("DelayLoad DLL %s\n", szName);
|
||
|
char szModuleName[MAX_PATH];
|
||
|
char szImportName[MAX_PATH];
|
||
|
|
||
|
{
|
||
|
char* p;
|
||
|
// change "module.dll" to just "module"
|
||
|
strcpy(szModuleName, szName);
|
||
|
p = szModuleName;
|
||
|
while (*p != '\0')
|
||
|
{
|
||
|
if (*p == '.')
|
||
|
{
|
||
|
*p = '\0';
|
||
|
break;
|
||
|
}
|
||
|
p++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Walk each function called from the delayloaded DLL
|
||
|
//
|
||
|
|
||
|
while (pINT->u1.AddressOfData) {
|
||
|
dlinfo.cb = sizeof(dlinfo);
|
||
|
dlinfo.pidd = NULL;
|
||
|
dlinfo.ppfn = NULL;
|
||
|
dlinfo.szDll = szName;
|
||
|
dlinfo.pfnCur = NULL;
|
||
|
dlinfo.dwLastError = ERROR_NOT_ENOUGH_MEMORY;
|
||
|
dlinfo.dlp.szProcName = NULL; // Make sure the upper 32 bits are zeroed out on win64.
|
||
|
|
||
|
if (
|
||
|
( fPE32 && IMAGE_SNAP_BY_ORDINAL32(((PIMAGE_THUNK_DATA32)pINT)->u1.AddressOfData)) ||
|
||
|
(!fPE32 && IMAGE_SNAP_BY_ORDINAL64(((PIMAGE_THUNK_DATA64)pINT)->u1.AddressOfData))
|
||
|
)
|
||
|
{
|
||
|
sprintf(szImportName, "Ordinal%d", IMAGE_ORDINAL(pINT->u1.AddressOfData));
|
||
|
dlinfo.dlp.fImportByName = FALSE;
|
||
|
dlinfo.dlp.dwOrdinal = IMAGE_ORDINAL((ULONG)pINT->u1.AddressOfData);
|
||
|
} else {
|
||
|
PIMAGE_IMPORT_BY_NAME pImport;
|
||
|
if (Imports->grAttrs & dlattrRva) {
|
||
|
pImport = (PIMAGE_IMPORT_BY_NAME)RvaToPtr(pINT->u1.AddressOfData);
|
||
|
} else {
|
||
|
pImport = (PIMAGE_IMPORT_BY_NAME)ConvertImagePointer((void *)pINT->u1.AddressOfData);
|
||
|
}
|
||
|
sprintf(szImportName, "%s", pImport->Name);
|
||
|
dlinfo.dlp.fImportByName = TRUE;
|
||
|
dlinfo.dlp.szProcName = pImport->Name;
|
||
|
}
|
||
|
|
||
|
if (fCallHandler) {
|
||
|
//
|
||
|
// Call the delayload handler and see what it does.
|
||
|
//
|
||
|
try {
|
||
|
fp = (*__pfnFailureProc)(dlinfo.szDll, dlinfo.dlp.szProcName);
|
||
|
if (!fp) {
|
||
|
fprintf(stderr, "DLCHECK : error DL000000: %s imports %s!%s which is not handled.\n", g_szImageName, szModuleName, szImportName);
|
||
|
ReturnValue = 1;
|
||
|
} else {
|
||
|
printf("DLCHECK : %s imports %s!%s - OK.\n", g_szImageName, szModuleName, szImportName);
|
||
|
}
|
||
|
} except (EXCEPTION_EXECUTE_HANDLER) {
|
||
|
fprintf(stderr, "DLCHECK : error %x: %s imports %s!%s - handler threw an exception.\n", GetExceptionCode(), g_szImageName, szModuleName, szImportName);
|
||
|
ReturnValue = 1;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
printf("DLCHECK : %s imports %s!%s - not checked.\n", g_szImageName, szModuleName, szImportName);
|
||
|
}
|
||
|
|
||
|
if (fPE32) {
|
||
|
pINT = (PIMAGE_THUNK_DATA)(((PIMAGE_THUNK_DATA32)pINT)++);
|
||
|
} else {
|
||
|
pINT = (PIMAGE_THUNK_DATA)(((PIMAGE_THUNK_DATA64)pINT)++);
|
||
|
}
|
||
|
}
|
||
|
if (Imports->grAttrs & dlattrRva) {
|
||
|
PImgDelayDescrV2 pImportsV2 = (PImgDelayDescrV2)Imports;
|
||
|
pImportsV2++;
|
||
|
Imports = (PImgDelayDescr)pImportsV2;
|
||
|
szName = (char *)RvaToPtr(pImportsV2->rvaDLLName);
|
||
|
pINT = (PIMAGE_THUNK_DATA)RvaToPtr(pImportsV2->rvaINT);
|
||
|
} else {
|
||
|
PImgDelayDescrV1 pImportsV1 = (PImgDelayDescrV1)Imports;
|
||
|
pImportsV1++;
|
||
|
Imports = (PImgDelayDescr)pImportsV1;
|
||
|
szName = (char *)ConvertImagePointer((void *)pImportsV1->szName);
|
||
|
pINT = (PIMAGE_THUNK_DATA)ConvertImagePointer((void *)pImportsV1->pINT);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (ReturnValue == 0)
|
||
|
{
|
||
|
printf("DLCHECK : succeeded on %s \n", g_szImageName);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
fprintf(stderr, "DLCHECK : failed on %s \n", g_szImageName);
|
||
|
}
|
||
|
|
||
|
return ReturnValue;
|
||
|
}
|