/* Copyright (c) 2013 Wildfire Games * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* * DLL delay loading and notification */ #include "precompiled.h" #include "lib/sysdep/os/win/wdll_delay_load.h" #include "lib/sysdep/cpu.h" #include "lib/sysdep/os/win/win.h" #include "lib/sysdep/os/win/winit.h" WINIT_REGISTER_LATE_SHUTDOWN2(wdll_Shutdown); // last - DLLs are unloaded here //----------------------------------------------------------------------------- // delay loading (modified from VC7 DelayHlp.cpp and DelayImp.h) #if MSC_VERSION && MSC_VERSION >= 1700 // FACILITY_VISUALCPP is already defined in winerror.h in VC2012 # undef FACILITY_VISUALCPP #endif #define FACILITY_VISUALCPP ((LONG)0x6d) #define VcppException(sev,status) ((sev) | (FACILITY_VISUALCPP<<16) | status) typedef IMAGE_THUNK_DATA * PImgThunkData; typedef const IMAGE_THUNK_DATA * PCImgThunkData; typedef DWORD RVA; typedef struct ImgDelayDescr { DWORD grAttrs; // attributes RVA rvaDLLName; // RVA to dll name RVA rvaHmod; // RVA of module handle RVA rvaIAT; // RVA of the IAT RVA rvaINT; // RVA of the INT RVA rvaBoundIAT; // RVA of the optional bound IAT RVA rvaUnloadIAT; // RVA of optional copy of original IAT DWORD dwTimeStamp; // 0 if not bound, // O.W. date/time stamp of DLL bound to (Old BIND) } ImgDelayDescr, * PImgDelayDescr; typedef const ImgDelayDescr * PCImgDelayDescr; enum DLAttr { // Delay Load Attributes dlattrRva = 0x1 // RVAs are used instead of pointers // Having this set indicates a VC7.0 // and above delay load descriptor. }; enum { dliStartProcessing, // used to bypass or note helper only dliNoteStartProcessing = dliStartProcessing, dliNotePreLoadLibrary, // called just before LoadLibrary, can // override w/ new HMODULE return val dliNotePreGetProcAddress, // called just before GetProcAddress, can // override w/ new FARPROC return value dliFailLoadLib, // failed to load library, fix it by // returning a valid HMODULE dliFailGetProc, // failed to get proc address, fix it by // returning a valid FARPROC dliNoteEndProcessing // called after all processing is done, no // no bypass possible at this point except // by longjmp()/throw()/RaiseException. }; typedef struct DelayLoadProc { BOOL fImportByName; union { LPCSTR szProcName; DWORD dwOrdinal; }; } DelayLoadProc; typedef struct DelayLoadInfo { DWORD cb; // size of structure PCImgDelayDescr pidd; // raw form of data (everything is there) FARPROC * ppfn; // points to address of function to load LPCSTR szDll; // name of dll DelayLoadProc dlp; // name or ordinal of procedure HMODULE hmodCur; // the hInstance of the library we have loaded FARPROC pfnCur; // the actual function that will be called DWORD dwLastError;// error received (if an error notification) } DelayLoadInfo, * PDelayLoadInfo; typedef FARPROC (WINAPI *PfnDliHook)(unsigned dliNotify, PDelayLoadInfo pdli); //----------------------------------------------------------------------------- // load notification static WdllLoadNotify* notify_list; void wdll_add_notify(WdllLoadNotify* notify) { notify->next = notify_list; notify_list = notify; } static FARPROC WINAPI notify_hook(unsigned dliNotify, PDelayLoadInfo pdli) { if(dliNotify != dliNoteEndProcessing) return 0; for(WdllLoadNotify* n = notify_list; n; n = n->next) if(strncasecmp(pdli->szDll, n->dll_name, strlen(n->dll_name)) == 0) n->func(); return 0; } //----------------------------------------------------------------------------- // hook // The "notify hook" gets called for every call to the // delay load helper. This allows a user to hook every call and // skip the delay load helper entirely. // // dliNotify == { // dliStartProcessing | // dliNotePreLoadLibrary | // dliNotePreGetProc | // dliNoteEndProcessing} // on this call. // EXTERN_C PfnDliHook __pfnDliNotifyHook2 = notify_hook; // This is the failure hook, dliNotify = {dliFailLoadLib|dliFailGetProc} EXTERN_C PfnDliHook __pfnDliFailureHook2 = 0; #if !ICC_VERSION #pragma intrinsic(strlen,memcmp,memcpy) #endif // utility function for calculating the index of the current import // for all the tables (INT, BIAT, UIAT, and IAT). inline unsigned IndexFromPImgThunkData(PCImgThunkData pitdCur, PCImgThunkData pitdBase) { return (unsigned) (pitdCur - pitdBase); } // C++ template utility function for converting RVAs to pointers // extern "C" const IMAGE_DOS_HEADER __ImageBase; template X PFromRva(RVA rva) { return X(PBYTE(&__ImageBase) + rva); } // structure definitions for the list of unload records typedef struct UnloadInfo * PUnloadInfo; typedef struct UnloadInfo { PUnloadInfo puiNext; PCImgDelayDescr pidd; } UnloadInfo; // utility function for calculating the count of imports given the base // of the IAT. NB: this only works on a valid IAT! inline unsigned CountOfImports(PCImgThunkData pitdBase) { unsigned cRet = 0; PCImgThunkData pitd = pitdBase; while (pitd->u1.Function) { pitd++; cRet++; } return cRet; } extern "C" PUnloadInfo __puiHead = 0; struct ULI : public UnloadInfo { ULI(PCImgDelayDescr pidd_) { pidd = pidd_; Link(); } ~ULI() { Unlink(); } void* operator new(size_t cb) { return ::LocalAlloc(LPTR, cb); } void operator delete(void* pv) { ::LocalFree(pv); } void Unlink() { PUnloadInfo* ppui = &__puiHead; while (*ppui && *ppui != this) ppui = &((*ppui)->puiNext); if (*ppui == this) *ppui = puiNext; } void Link() { puiNext = __puiHead; __puiHead = this; } }; // For our own internal use, we convert to the old // format for convenience. // struct InternalImgDelayDescr { DWORD grAttrs; // attributes LPCSTR szName; // pointer to dll name HMODULE * phmod; // address of module handle PImgThunkData pIAT; // address of the IAT PCImgThunkData pINT; // address of the INT PCImgThunkData pBoundIAT; // address of the optional bound IAT PCImgThunkData pUnloadIAT; // address of optional copy of original IAT DWORD dwTimeStamp; // 0 if not bound, // O.W. date/time stamp of DLL bound to (Old BIND) }; typedef InternalImgDelayDescr * PIIDD; typedef const InternalImgDelayDescr * PCIIDD; static inline PIMAGE_NT_HEADERS WINAPI PinhFromImageBase(HMODULE hmod) { return PIMAGE_NT_HEADERS(PBYTE(hmod) + PIMAGE_DOS_HEADER(hmod)->e_lfanew); } static inline void WINAPI OverlayIAT(PImgThunkData pitdDst, PCImgThunkData pitdSrc) { memcpy(pitdDst, pitdSrc, CountOfImports(pitdDst) * sizeof IMAGE_THUNK_DATA); } static inline DWORD WINAPI TimeStampOfImage(PIMAGE_NT_HEADERS pinh) { return pinh->FileHeader.TimeDateStamp; } static inline bool WINAPI FLoadedAtPreferredAddress(PIMAGE_NT_HEADERS pinh, HMODULE hmod) { return UINT_PTR(hmod) == pinh->OptionalHeader.ImageBase; } extern "C" FARPROC WINAPI __delayLoadHelper2(PCImgDelayDescr pidd, FARPROC* ppfnIATEntry) { // Set up some data we use for the hook procs but also useful for // our own use // InternalImgDelayDescr idd = { pidd->grAttrs, PFromRva(pidd->rvaDLLName), PFromRva(pidd->rvaHmod), PFromRva(pidd->rvaIAT), PFromRva(pidd->rvaINT), PFromRva(pidd->rvaBoundIAT), PFromRva(pidd->rvaUnloadIAT), pidd->dwTimeStamp }; DelayLoadInfo dli = { sizeof(DelayLoadInfo), pidd, ppfnIATEntry, idd.szName, { 0 }, 0, 0, 0 }; if (!(idd.grAttrs & dlattrRva)) { PDelayLoadInfo rgpdli[1] = { &dli }; RaiseException(VcppException(ERROR_SEVERITY_ERROR, ERROR_INVALID_PARAMETER), 0, 1, PULONG_PTR(rgpdli)); return 0; } HMODULE hmod = *idd.phmod; // Calculate the index for the IAT entry in the import address table // N.B. The INT entries are ordered the same as the IAT entries so // the calculation can be done on the IAT side. // const unsigned iIAT = IndexFromPImgThunkData(PCImgThunkData(ppfnIATEntry), idd.pIAT); const unsigned iINT = iIAT; PCImgThunkData pitd = &(idd.pINT[iINT]); dli.dlp.fImportByName = !IMAGE_SNAP_BY_ORDINAL(pitd->u1.Ordinal); if (dli.dlp.fImportByName) dli.dlp.szProcName = LPCSTR(PFromRva(RVA(UINT_PTR(pitd->u1.AddressOfData)))->Name); else dli.dlp.dwOrdinal = DWORD(IMAGE_ORDINAL(pitd->u1.Ordinal)); // Call the initial hook. If it exists and returns a function pointer, // abort the rest of the processing and just return it for the call. // FARPROC pfnRet = NULL; if (__pfnDliNotifyHook2) { pfnRet = ((*__pfnDliNotifyHook2)(dliStartProcessing, &dli)); if (pfnRet != NULL) goto HookBypass; } // Check to see if we need to try to load the library. // if (hmod == 0) { if (__pfnDliNotifyHook2) { hmod = HMODULE(((*__pfnDliNotifyHook2)(dliNotePreLoadLibrary, &dli))); } if (hmod == 0) { hmod = ::LoadLibraryA(dli.szDll); } if (hmod == 0) { dli.dwLastError = ::GetLastError(); if (__pfnDliFailureHook2) { // when the hook is called on LoadLibrary failure, it will // return 0 for failure and an hmod for the lib if it fixed // the problem. // hmod = HMODULE((*__pfnDliFailureHook2)(dliFailLoadLib, &dli)); } if (hmod == 0) { PDelayLoadInfo rgpdli[1] = { &dli }; RaiseException(VcppException(ERROR_SEVERITY_ERROR, ERROR_MOD_NOT_FOUND), 0, 1, PULONG_PTR(rgpdli)); // If we get to here, we blindly assume that the handler of the exception // has magically fixed everything up and left the function pointer in // dli.pfnCur. // return dli.pfnCur; } } // Store the library handle. If it is already there, we infer // that another thread got there first, and we need to do a // FreeLibrary() to reduce the refcount // HMODULE hmodT = HMODULE(InterlockedExchangePointer((PVOID *) idd.phmod, PVOID(hmod))); if (hmodT != hmod) { // add lib to unload list if we have unload data if (pidd->rvaUnloadIAT) { new ULI(pidd); } } else { ::FreeLibrary(hmod); } } // Go for the procedure now. // dli.hmodCur = hmod; if (__pfnDliNotifyHook2) { pfnRet = (*__pfnDliNotifyHook2)(dliNotePreGetProcAddress, &dli); } if (pfnRet == 0) { if (pidd->rvaBoundIAT && pidd->dwTimeStamp) { // bound imports exist...check the timestamp from the target image // PIMAGE_NT_HEADERS pinh(PinhFromImageBase(hmod)); if (pinh->Signature == IMAGE_NT_SIGNATURE && TimeStampOfImage(pinh) == idd.dwTimeStamp && FLoadedAtPreferredAddress(pinh, hmod)) { // Everything is good to go, if we have a decent address // in the bound IAT! // pfnRet = FARPROC(UINT_PTR(idd.pBoundIAT[iIAT].u1.Function)); if (pfnRet != 0) { goto SetEntryHookBypass; } } } pfnRet = ::GetProcAddress(hmod, dli.dlp.szProcName); } if (pfnRet == 0) { dli.dwLastError = ::GetLastError(); if (__pfnDliFailureHook2) { // when the hook is called on GetProcAddress failure, it will // return 0 on failure and a valid proc address on success // pfnRet = (*__pfnDliFailureHook2)(dliFailGetProc, &dli); } if (pfnRet == 0) { PDelayLoadInfo rgpdli[1] = { &dli }; RaiseException(VcppException(ERROR_SEVERITY_ERROR, ERROR_PROC_NOT_FOUND), 0, 1, PULONG_PTR(rgpdli)); // If we get to here, we blindly assume that the handler of the exception // has magically fixed everything up and left the function pointer in // dli.pfnCur. // pfnRet = dli.pfnCur; } } SetEntryHookBypass: *ppfnIATEntry = pfnRet; HookBypass: if (__pfnDliNotifyHook2) { dli.dwLastError = 0; dli.hmodCur = hmod; dli.pfnCur = pfnRet; (*__pfnDliNotifyHook2)(dliNoteEndProcessing, &dli); } return pfnRet; } static void UnloadAllDlls() { PUnloadInfo pui; // free all DLLs (avoid BoundsChecker warning) while((pui = __puiHead) != 0) if(pui->pidd->rvaUnloadIAT) { PCImgDelayDescr pidd = pui->pidd; HMODULE* phmod = PFromRva(pidd->rvaHmod); HMODULE hmod = *phmod; OverlayIAT( PFromRva(pidd->rvaIAT), PFromRva(pidd->rvaUnloadIAT) ); ::FreeLibrary(hmod); *phmod = NULL; delete (ULI*)pui; // changes __puiHead! } } //----------------------------------------------------------------------------- static Status wdll_Shutdown() { UnloadAllDlls(); return INFO::OK; }