/***********************************************************************/ /* Copyright (C) 2002 Definitive Solutions, Inc. All Rights Reserved. */ /* THIS COMPUTER PROGRAM IS PROPRIETARY AND CONFIDENTIAL TO DEFINITIVE */ /* SOLUTIONS, INC. AND ITS LICENSORS AND CONTAINS TRADE SECRETS OF */ /* DEFINITIVE SOLUTIONS, INC. THAT ARE PROVIDED PURSUANT TO A WRITTEN */ /* AGREEMENT CONTAINING RESTRICTIONS ON USE AND DISCLOSURE. ANY USE, */ /* REPRODUCTION, OR TRANSFER EXCEPT AS PROVIDED IN SUCH AGREEMENT */ /* IS STRICTLY PROHIBITED. */ /***********************************************************************/ #include "stdafx.h" #include "MyDepends.h" #include "Generic.h" #include "MyApp.h" #include "MyLog.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // MAKEPTR is a macro that allows you to easily add to values (including // pointers) together without dealing with C's pointer arithmetic. It // essentially treats the last two parameters as DWORDs. The first // parameter is used to typecast the result to the appropriate pointer type. #define MAKEPTR(cast, ptr, addValue) (cast)((DWORD)(ptr) + (DWORD)(addValue)) ///////////////////////////////////////////////////////////////////////////// // MyDepends class // Constructor. MyDepends::MyDepends() : m_pmdl(NULL) { } // Destructor. /* virtual */ MyDepends::~MyDepends() { ASSERT_NULL_OR_POINTER(m_pmdl, ModuleDependencyList); delete m_pmdl; m_pmdl = NULL; } // Initialize. HRESULT MyDepends::Initialize(const CString sFullPath /* = "" */) { VALIDATE; HRESULT hr(S_OK); CString sPathLocal(sFullPath); // By default we use this module's path. if (sPathLocal.IsEmpty()) { DWORD dwErr(::GetModuleFileName(NULL, sPathLocal.GetBuffer(MAX_PATH), MAX_PATH)); sPathLocal.ReleaseBuffer(); if (0 == dwErr) { _ASSERTE(! "Error"); DOMYLOGST(API_FAILURE), "::GetModuleFileName", ::GetLastError(), Generic::GetSysErrorString()); hr = E_FAIL; } } // If we have a pathname. if (SUCCEEDED(hr)) { _ASSERTE(! sPathLocal.IsEmpty()); m_pmdl = new ModuleDependencyList(sPathLocal.GetBuffer(MAX_PATH)); sPathLocal.ReleaseBuffer(); if (m_pmdl && m_pmdl->IsValid()) { DOMYLOGA ("Created ModuleDependencyList for path <%s>.\n", sPathLocal); } else { _ASSERTE(! "Error"); DOMYLOGE ("Failed to create ModuleDependencyList for path <%s>.\n", sPathLocal); hr = E_FAIL; } } return hr; } // Write all data to the log file. HRESULT MyDepends::LogAllDepends() const { VALIDATE; _ASSERTE(m_pmdl && m_pmdl->IsValid() && "Must call Initialize() first!"); HRESULT hr(S_OK); // Iterate the modules. PMODULE_FILE_INFO pModInfo = NULL; while (pModInfo = m_pmdl->GetNextModule(pModInfo)) { DisplayFileInformation(pModInfo); // Log the not found ones too. PMODULE_FILE_INFO pNotFound = NULL; while (pNotFound = pModInfo->GetNextNotFoundModule(pNotFound)) { DOMYLOGA ("Module not found: <%s>.\n", pNotFound->GetBaseName()); } } return hr; } // void MyDepends::DisplayFileInformation(PMODULE_FILE_INFO pModInfo) const { VALIDATE; // Log header for this DLL. DOMYLOGA ("%s\n", pModInfo->GetBaseName()); // Get the file for the path. PSTR pszFullName = pModInfo->GetFullName(); HFILE hFile(_lopen(pszFullName, OF_READ)); char szFileDate[32] = { 0 }; char szFileTime[32] = { 0 }; FILETIME ft = { 0 }; if (HFILE_ERROR != hFile) { if (GetFileTime((HANDLE) hFile, 0, 0, &ft)) { GetFileDateAsString(&ft, szFileDate, sizeof(szFileDate)); GetFileTimeAsString(&ft, szFileTime, sizeof(szFileTime), TRUE); DOMYLOGA (" File Date: %s %s\n", szFileDate, szFileTime); } VERIFY(HFILE_ERROR != _lclose(hFile)); } // Log date/time and path. PeExe exe(pszFullName); TimeDateStampToFileTime(exe.GetTimeDateStamp(), &ft); GetFileDateAsString(&ft, szFileDate, sizeof(szFileDate)); GetFileTimeAsString(&ft, szFileTime, sizeof(szFileTime), TRUE); DOMYLOGA (" Date Stamp: %s %s\n", szFileDate, szFileTime); DOMYLOGA (" Path: %s\n", pszFullName); // Display the version info. ShowVersionInfo(pszFullName); } // void MyDepends::ShowVersionInfo(PSTR pszFileName) const { _ASSERTE(pszFileName); // How big is the version info? DWORD dummy(0); DWORD cbVerInfo(GetFileVersionInfoSize(pszFileName, &dummy)); if (cbVerInfo) { // Allocate space to hold the info. PBYTE pVerInfo = new BYTE[cbVerInfo]; if (pVerInfo) { if (GetFileVersionInfo(pszFileName, 0, cbVerInfo, pVerInfo)) { char* predefResStrings[] = { "CompanyName", "FileDescription", "FileVersion", "InternalName", "LegalCopyright", "OriginalFilename", "ProductName", "ProductVersion", NULL }; DOMYLOGA (" Version:\n"); // Iterate the version strings. for (unsigned i = 0; predefResStrings[i]; ++i) { char szQueryStr1[256] = { 0 }; char szQueryStr2[256] = { 0 }; // Format the string with the 1200 codepage (Unicode) wsprintf(szQueryStr1, "\\StringFileInfo\\%04X%04X\\%s", GetUserDefaultLangID(), 1200, predefResStrings[i]); // Format the string with the 1252 codepage (Windows Multilingual) wsprintf(szQueryStr2, "\\StringFileInfo\\%04X%04X\\%s", GetUserDefaultLangID(), 1252, predefResStrings[i]); // We may want to format a string with the "0000" codepage // Try first with the 1252 codepage PSTR pszVerRetVal = NULL; UINT cbReturn(0U); BOOL fFound(VerQueryValue(pVerInfo, szQueryStr1, (LPVOID*) &pszVerRetVal, &cbReturn)); // Hmm... 1252 wasn't found. Try the 1200 codepage. if (! fFound) { fFound = VerQueryValue(pVerInfo, szQueryStr2, (LPVOID*) &pszVerRetVal, &cbReturn); } if (fFound) { DOMYLOGA (" %s\t%s\n", predefResStrings[i], pszVerRetVal); } } delete [] pVerInfo; pVerInfo = NULL; } } } } // Convert a TimeDateStamp (i.e., # of seconds since 1/1/1970) into a FILETIME. void MyDepends::TimeDateStampToFileTime(DWORD timeDateStamp, LPFILETIME pFileTime) const { _ASSERTE(0 != timeDateStamp); _ASSERTE(pFileTime); // Magic... GMT... Don't ask.... __int64 t1970(0x019DB1DED53E8000); __int64 timeStampIn100nsIncr((__int64)timeDateStamp * 10000000); __int64 finalValue(t1970 + timeStampIn100nsIncr); memcpy(pFileTime, &finalValue, sizeof(finalValue)); } // BOOL MyDepends::GetFileDateAsString(LPFILETIME pFt, char* pszDate, unsigned cbIn) const { _ASSERTE(NULL != pFt); _ASSERTE(NULL != pszDate); _ASSERTE(0U != cbIn); BOOL bReturn(FALSE); FILETIME ftLocal = { 0 }; SYSTEMTIME st = { 0 }; if (FileTimeToLocalFileTime(pFt, &ftLocal)) { if (FileTimeToSystemTime(&ftLocal, &st)) { char szTemp[12] = { 0 }; wsprintf(szTemp, "%02u/%02u/%04u", st.wMonth, st.wDay, st.wYear); lstrcpyn(pszDate, szTemp, cbIn); bReturn = TRUE; } } return bReturn; } // BOOL MyDepends::GetFileTimeAsString(LPFILETIME pFt, char * pszTime, unsigned cbIn, BOOL fSeconds) const { _ASSERTE(NULL != pFt); _ASSERTE(NULL != pszTime); _ASSERTE(0U != cbIn); BOOL bReturn(FALSE); FILETIME ftLocal = { 0 }; SYSTEMTIME st = { 0 }; if (FileTimeToLocalFileTime(pFt, &ftLocal)) { if (FileTimeToSystemTime(&ftLocal, &st)) { char szTemp[12] = { 0 }; if (fSeconds) { wsprintf(szTemp, "%02u:%02u:%02u", st.wHour, st.wMinute, st.wSecond); } else { // No thanks.. Just hours and minutes. wsprintf(szTemp, "%02u:%02u", st.wHour, st.wMinute); } lstrcpyn(pszTime, szTemp, cbIn); bReturn = TRUE; } } return bReturn; } ///////////////////////////////////////////////////////////////////////////// // ModuleDependencyList // Constructor. ModuleDependencyList::ModuleDependencyList(PSTR pszFileName) : m_cModules(0U) , m_pList(NULL) , m_errMDL(errMDL_GENERAL_FAILURE) { _ASSERTE(pszFileName); // Make a copy of the path that we can modify to get just the path portion. PSTR pszJustPath = strdup(pszFileName); if (pszJustPath) { BOOL fHasPath(FALSE); PSTR pszEnd = strrchr(pszJustPath, '\\'); // Strip off the filename if (pszEnd) { *pszEnd = 0; fHasPath = TRUE; } // If a path was part of the input filename, save the current directory, // then switch to the new directory. char szOriginalPath[MAX_PATH + 1] = { 0 }; // This doesn't take into account "App_Paths"! if (fHasPath) { GetCurrentDirectory(MAX_PATH, szOriginalPath); SetCurrentDirectory(pszJustPath); } // Recursively build the module list. m_errMDL = AddModule(pszFileName); // Set things back to the way they were. if (fHasPath) { SetCurrentDirectory(szOriginalPath); } // Free the copy of the path that we allocated. free(pszJustPath); } } // Destructor. ModuleDependencyList::~ModuleDependencyList() { // Delete each ModuleFileInfo structures in the regular linked list PMODULE_FILE_INFO pTemp = m_pList; while (pTemp) { pTemp = m_pList->m_pNext; // Before we delete the module, delete each ModuleFileInfo // structures in the not found list. PMODULE_FILE_INFO pNotFound = m_pList->m_pNotFoundNext; while (pNotFound) { pNotFound = m_pList->m_pNotFoundNext->m_pNotFoundNext; delete m_pList->m_pNotFoundNext; m_pList->m_pNotFoundNext = NULL; m_pList->m_pNotFoundNext = pNotFound; } // Now it's OK to delete the module. delete m_pList; m_pList = pTemp; --m_cModules; } m_pList = NULL; } // ModuleDependencyList::errMDL ModuleDependencyList::GetErrorType() const { return m_errMDL; } // BOOL ModuleDependencyList::IsValid() const { return m_errMDL == errMDL_NO_ERROR; } // Returns the next module in the linked list of ModuleFileInfo's PMODULE_FILE_INFO ModuleDependencyList::GetNextModule(PMODULE_FILE_INFO p) { return p ? p->m_pNext : m_pList; } // Given the name of a file, find the ModuleFileInfo structure that // represents it. The fFullName parameter specifies whether the full path // names or just the base file names will be compared. PMODULE_FILE_INFO ModuleDependencyList::LookupModule(PSTR pszFileName, BOOL fFullName) { _ASSERTE(pszFileName); // Start at the list head. PMODULE_FILE_INFO p = m_pList; // While there's still entries in the list. while (p) { PSTR pszCompName = (fFullName) ? p->m_szFullName : p->m_szBaseName; if (0 == lstrcmpi(pszFileName, pszCompName)) { return p; } p = p->m_pNext; } return NULL; } // PSTR ModuleDependencyList::GetErrorString() const { switch (m_errMDL) { case errMDL_NO_ERROR: return "No error"; case errMDL_FILE_NOT_FOUND: return "File not found"; case errMDL_NOT_PE_FILE: return "Not a PE file"; case errMDL_GENERAL_FAILURE: return "General failure"; default: return ""; } } // unsigned ModuleDependencyList::GetNumberOfModules() const { return m_cModules; } // Adds a modules to the ModuleFileInfo list. If the module imports other // modules, this routine recurses to add them, and check their imports. ModuleDependencyList::errMDL ModuleDependencyList::AddModule(PSTR pszFileName) { ModuleDependencyList::errMDL errMdlReturn(errMDL_NO_ERROR); // Get easy access to the executable. PeExe peFile(pszFileName); if (peFile.IsValid()) { PMODULE_FILE_INFO pNew = new ModuleFileInfo(pszFileName); pNew->m_pNext = m_pList; m_pList = pNew; ++m_cModules; // Now see if this module imports any other modules. If so, we need // to recurse and add them as well. if (peFile.GetDataDirectoryEntrySize(IMAGE_DIRECTORY_ENTRY_IMPORT)) { // Make a pointer to the imports table. PIMAGE_IMPORT_DESCRIPTOR pImportDir = (PIMAGE_IMPORT_DESCRIPTOR) peFile.GetDataDirectoryEntryPointer(IMAGE_DIRECTORY_ENTRY_IMPORT); if (pImportDir) { // While there are still non-null IMAGE_IMPORT_DESCRIPTORs. while (pImportDir->Name) { // Get a pointer to the imported module's base name PSTR pszBaseName = (PSTR) peFile.GetReadablePointerFromRVA(pImportDir->Name); if (! pszBaseName) { break; } // Check to see if it's already in our list. Don't add // again if so. if (0 == LookupModule(pszBaseName, FALSE)) { // Search path supposedly has the same searching // algorithm as the the Win32 loader. char szPath[MAX_PATH + 1] = { 0 }; PSTR pszDontCare = NULL; if (SearchPath(0, pszBaseName, 0, MAX_PATH, szPath, &pszDontCare)) { AddModule(szPath); } else { pNew->AddNotFoundModule(pszBaseName); } } // Advance to next imported module. ++pImportDir; } } } } else { // Pietrek, you HACK! errMdlReturn = (errMDL) peFile.GetErrorType(); } return errMdlReturn; } ///////////////////////////////////////////////////////////////////////////// // ModuleFileInfo // Constructor. ModuleFileInfo::ModuleFileInfo(PSTR pszFileName) : m_pNext(NULL) , m_pNotFoundNext(NULL) { _ASSERTE(pszFileName); m_szBaseName[0] = 0; m_szFullName[0] = 0; // Find the last '\\' to obtain a pointer to just the base filename part. PSTR pszBaseName = strrchr(pszFileName, '\\'); // We found a path, so advance to the base filename. if (pszBaseName) { ++pszBaseName; } else { // No path. Use the same name for both. pszBaseName = pszFileName; } // Initialize the new ModuleFileInfo, and stick it at the head of the list. lstrcpyn(m_szFullName, pszFileName, sizeof(m_szFullName)); lstrcpyn(m_szBaseName, pszBaseName, sizeof(m_szBaseName)); } // Destructor. ModuleFileInfo::~ModuleFileInfo() { } // PSTR ModuleFileInfo::GetBaseName() { return m_szBaseName; } // PSTR ModuleFileInfo::GetFullName() { return m_szFullName; } // And an unlocatable module to the "not found" list. void ModuleFileInfo::AddNotFoundModule(PSTR pszFileName) { _ASSERTE(pszFileName); PMODULE_FILE_INFO pNew = new ModuleFileInfo(pszFileName); pNew->m_pNotFoundNext = m_pNotFoundNext; m_pNotFoundNext = pNew; } // For enumerating through the unlocatable imported modules. PMODULE_FILE_INFO ModuleFileInfo::GetNextNotFoundModule(PMODULE_FILE_INFO p) { return p ? p->m_pNotFoundNext : m_pNotFoundNext; } ///////////////////////////////////////////////////////////////////////////// // PeExe // Constructor. PeExe::PeExe(PSTR pszFileName) : ExeFile(pszFileName) , m_pNtHdr(NULL) { _ASSERTE(pszFileName); if (ExeFile::IsValid()) { // It's an EXE, but is it a *PE* file? If not, set code and bail. if (GetExeType() != exeType_PE) { m_errEF = errEXE_FILE_INVALID_FORMAT; } else { m_pNtHdr = MAKEPTR(PIMAGE_NT_HEADERS, GetBase(), GetSecondaryHeaderOffset()); } } } // Destructor. PeExe::~PeExe() { } // Given a IMAGE_DIRECTORY_ENTRY_XXX value (see WINNT.H), retrive the RVA // stored in the corresponding slot. DWORD PeExe::GetDataDirectoryEntryRVA(DWORD id) { if (id >= IMAGE_NUMBEROF_DIRECTORY_ENTRIES) { return (DWORD) -1; } else { return m_pNtHdr->OptionalHeader.DataDirectory[id].VirtualAddress; } } // Given a IMAGE_DIRECTORY_ENTRY_XXX value (see WINNT.H), return a pointer // to memory that corresponds to the RVA in the specified slot. PVOID PeExe::GetDataDirectoryEntryPointer(DWORD id) { if (id >= IMAGE_NUMBEROF_DIRECTORY_ENTRIES) { return (PVOID) -1; } else { DWORD va(m_pNtHdr->OptionalHeader.DataDirectory[id].VirtualAddress); if (! va) { return 0; } return GetReadablePointerFromRVA(va); } } // Given a IMAGE_DIRECTORY_ENTRY_XXX value (see WINNT.H), retrive the // size value stored in the corresponding slot. DWORD PeExe::GetDataDirectoryEntrySize(DWORD id) { if (id >= IMAGE_NUMBEROF_DIRECTORY_ENTRIES) { return (DWORD) -1; } else { return m_pNtHdr->OptionalHeader.DataDirectory[id].Size; } } // Given an RVA, translate it into a pointer within our linear memory // mapping for the executable. PVOID PeExe::GetReadablePointerFromRVA(DWORD rva) { DWORD fileOffset(RVAToFileOffset(rva)); if ((DWORD) -1 == fileOffset) { return 0; } else { return MAKEPTR(PVOID, GetBase(), fileOffset); } } // Given an RVA, figure out which section encompasses it. Next, using // the PointerToRawData field for the found section, return an actual // file offset that corresponds to the RVA. DWORD PeExe::RVAToFileOffset(DWORD rva) { PIMAGE_SECTION_HEADER pSectHdr = IMAGE_FIRST_SECTION(m_pNtHdr); for (unsigned i = 0; i < GetNumberOfSections(); ++i, ++pSectHdr) { DWORD cbMaxOnDisk(min(pSectHdr->Misc.VirtualSize, pSectHdr->SizeOfRawData)); DWORD startSectRVA = pSectHdr->VirtualAddress; DWORD endSectRVA = startSectRVA + cbMaxOnDisk; if ((rva >= startSectRVA) && (rva < endSectRVA)) { return pSectHdr->PointerToRawData + (rva - startSectRVA); } } // RVA not found in the section table... ooops! return (DWORD)-1; } ///////////////////////////////////////////////////////////////////////////// // MemoryMappedFile // COnstructor. MemoryMappedFile::MemoryMappedFile(PSTR pszFileName) : m_hFile(INVALID_HANDLE_VALUE) , m_hFileMapping(0) , m_pMemoryMappedFileBase(NULL) , m_cbFile (0) , m_errMMF(errMMF_FileOpen) { _ASSERTE(pszFileName); // Given a filename, the constructor opens a file handle, creates a file // mapping, and maps the entire file into memory. // First get a file handle. m_hFile = CreateFile(pszFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, (HANDLE) 0); if (m_hFile == INVALID_HANDLE_VALUE) { m_errMMF = errMMF_FileOpen; return; } m_cbFile = ::GetFileSize(m_hFile, 0); // Now, create a file mapping. m_hFileMapping = CreateFileMapping(m_hFile, NULL, PAGE_READONLY, 0, 0, NULL); // Oops. Something went wrong. Clean up. if (NULL == m_hFileMapping) { CloseHandle(m_hFile); m_hFile = INVALID_HANDLE_VALUE; m_errMMF = errMMF_FileMapping; return; } m_pMemoryMappedFileBase = (PCHAR )MapViewOfFile(m_hFileMapping, FILE_MAP_READ, 0, 0, 0); // Oops. Something went wrong. Clean up. if (NULL == m_pMemoryMappedFileBase) { CloseHandle(m_hFileMapping); m_hFileMapping = 0; CloseHandle(m_hFile); m_hFile = INVALID_HANDLE_VALUE; m_errMMF = errMMF_MapView; return; } m_errMMF = errMMF_NoError; } // Clean up everything that was created by the constructor. MemoryMappedFile::~MemoryMappedFile() { if (m_pMemoryMappedFileBase) { UnmapViewOfFile(m_pMemoryMappedFileBase); } if (m_hFileMapping) { CloseHandle(m_hFileMapping); } if (m_hFile != INVALID_HANDLE_VALUE) { CloseHandle(m_hFile); } m_errMMF = errMMF_FileOpen; } // PVOID MemoryMappedFile::GetBase() { return m_pMemoryMappedFileBase; } // DWORD MemoryMappedFile::GetFileSize() { return m_cbFile; } // BOOL MemoryMappedFile::IsValid() { return errMMF_NoError == m_errMMF; } // MemoryMappedFile::errMMF MemoryMappedFile::GetErrorType() { return m_errMMF; } ///////////////////////////////////////////////////////////////////////////// // ExeFile // Constructor. ExeFile::ExeFile(PSTR pszFileName) : MemoryMappedFile(pszFileName) , m_errEF(errEXE_FILE_FILE_NOT_FOUND) , m_secondaryHeaderOffset(-1) , m_exeType(exeType_Invalid) { _ASSERTE(pszFileName); // m_errorType already set to errEXE_FILE_FILE_NOT_FOUND. if (FALSE == MemoryMappedFile::IsValid()) { return; } // If we get here, the file exists, and was mapped. We're still not // sure that it's a valid EXE though. m_errEF = errEXE_FILE_INVALID_FORMAT; if (GetFileSize() < sizeof(IMAGE_DOS_HEADER)) { return; } PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER)GetBase(); if (IMAGE_DOS_SIGNATURE != pDosHdr->e_magic) { return; } // If we get here, it's at least a DOS 'MZ' file. m_errEF = errEXE_FILE_NO_ERROR; // Theoretically, this field must be >= 0x40 for it to be a non-DOS executable. if (pDosHdr->e_lfarlc < 0x40) { m_exeType = exeType_DOS; return; } // Sanity check. Make sure the "new header" offset isn't past the end // of the file. if (pDosHdr->e_lfanew > (LONG) GetFileSize()) { return; } // Make a pointer to the secondary header. m_secondaryHeaderOffset = pDosHdr->e_lfanew; PWORD pSecondHdr = MAKEPTR(PWORD, GetBase(), m_secondaryHeaderOffset); // Decide what type of EXE, based on the start of the secondary header switch (*pSecondHdr) { case IMAGE_OS2_SIGNATURE: m_exeType = exeType_NE; break; case IMAGE_VXD_SIGNATURE: m_exeType = exeType_VXD; break; case 0x4558: m_exeType = exeType_LX; break; // OS/2 2.X } if (*(PDWORD) pSecondHdr == IMAGE_NT_SIGNATURE) { m_exeType = exeType_PE; } } // Destructor. ExeFile::~ExeFile() { } // BOOL ExeFile::IsValid() { return errMMF_NoError == m_errMMF; } // ExeFile::errEF ExeFile::GetErrorType() { return m_errEF; } // DWORD ExeFile::GetSecondaryHeaderOffset() { return m_secondaryHeaderOffset; } // ExeFile::ExeType ExeFile::GetExeType() { return m_exeType; } // Returns a static string that describes what type this file is. PSTR ExeFile::GetFileTypeDescription() { switch (m_exeType) { case exeType_DOS: return "DOS"; case exeType_NE: return "NE"; case exeType_VXD: return "VXD"; case exeType_LX: return "LX"; case exeType_PE: return "PE"; default: return "Invalid"; } }