/*++ Copyright (c) 1995-1997 Microsoft Corporation Module Name: ver.cxx Abstract: This module contains an NTSD debugger extension for dumping module version resources. Author: Keith Moore (keithmo) 16-Sep-1997 Revision History: --*/ #include "inetdbgp.h" PSTR VersionLabels[] = { "CompanyName", "FileDescription", "FileVersion", "InternalName", "LegalCopyright", "OriginalFilename", "ProductName", "ProductVersion" }; #define NUM_LABELS ( sizeof(VersionLabels) / sizeof(VersionLabels[0]) ) typedef struct _ENUM_CONTEXT { PSTR ModuleName; INT NameLength; } ENUM_CONTEXT, *PENUM_CONTEXT; /************************************************************ * Dump File Version Info ************************************************************/ PIMAGE_RESOURCE_DIRECTORY FindResourceDir( IN PIMAGE_RESOURCE_DIRECTORY BaseResourceDir, IN PIMAGE_RESOURCE_DIRECTORY TargetResourceDir, IN USHORT ResourceId ) /*++ Routine Description: Finds the specified resource directory. Arguments: BaseResourceDir - The (remote) address of the *start* of the resource section. TargetResourceDir - The (remote) address of the resource directory to search. ResourceId - The resource ID we're looking for. Return Value: PIMAGE_RESOURCE_DIRECTORY - Pointer to the resource directory corresponding to ResourceId if successful, NULL otherwise. --*/ { IMAGE_RESOURCE_DIRECTORY localDir; IMAGE_RESOURCE_DIRECTORY_ENTRY localEntry; PIMAGE_RESOURCE_DIRECTORY_ENTRY remoteEntry; USHORT i; // // Read the target resource directory. // if( !ReadMemory( (ULONG_PTR)TargetResourceDir, &localDir, sizeof(localDir), NULL ) ) { return NULL; } // // Scan it. // remoteEntry = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)( TargetResourceDir + 1 ); for( i = localDir.NumberOfNamedEntries + localDir.NumberOfIdEntries ; i > 0 ; i--, remoteEntry++ ) { // // Read the directory entry. // if( !ReadMemory( (ULONG_PTR)remoteEntry, &localEntry, sizeof(localEntry), NULL ) ) { return NULL; } // // If the entry is a directory and the IDs match, then return it. // if( localEntry.DataIsDirectory == 0 ) { continue; } if( localEntry.NameIsString == 0 && localEntry.Id == ResourceId ) { return (PIMAGE_RESOURCE_DIRECTORY) ( (ULONG_PTR)BaseResourceDir + localEntry.OffsetToDirectory ); } } return NULL; } // FindResourceDir PIMAGE_RESOURCE_DATA_ENTRY FindResourceData( IN PIMAGE_RESOURCE_DIRECTORY BaseResourceDir, IN PIMAGE_RESOURCE_DIRECTORY TargetResourceDir, IN USHORT ResourceId ) /*++ Routine Description: Finds the specified resource data item. Arguments: BaseResourceDir - The (remote) address of the *start* of the resource section. TargetResourceDir - The (remote) address of the resource directory to search. ResourceId - The resource ID we're looking for. This may be zero to return any resource. Return Value: PIMAGE_RESOURCE_DATA_ENTRY - Pointer to the resource data entry corresponding to ResourceId if successful, NULL otherwise. --*/ { IMAGE_RESOURCE_DIRECTORY localDir; IMAGE_RESOURCE_DIRECTORY_ENTRY localEntry; PIMAGE_RESOURCE_DIRECTORY_ENTRY remoteEntry; USHORT i; // // Read the target resource directory. // if( !ReadMemory( (ULONG_PTR)TargetResourceDir, &localDir, sizeof(localDir), NULL ) ) { return NULL; } // // Scan it. // remoteEntry = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)( TargetResourceDir + 1 ); for( i = localDir.NumberOfNamedEntries + localDir.NumberOfIdEntries ; i > 0 ; i--, remoteEntry++ ) { // // Read the directory entry. // if( !ReadMemory( (ULONG_PTR)remoteEntry, &localEntry, sizeof(localEntry), NULL ) ) { return NULL; } // // If the entry is not a directory and the IDs match (or the // requested ID is zero, meaning any ID) then return it. // if( localEntry.DataIsDirectory != 0 ) { continue; } if( localEntry.NameIsString == 0 && ( localEntry.Id == ResourceId || ResourceId == 0 ) ) { return (PIMAGE_RESOURCE_DATA_ENTRY) ( (ULONG_PTR)BaseResourceDir + localEntry.OffsetToDirectory ); } } return NULL; } // FindResourceData BOOL DumpVersionResource( IN PVOID VersionResource ) /*++ Routine Description: Dumps a version resource block. Arguments: VersionResource - The version resource to dump. Return Value: BOOL - TRUE if successful, FALSE if the version resource block was corrupt or unreadable. --*/ { ULONG charSet; LPVOID version; UINT versionLength; INT i; VS_FIXEDFILEINFO * fixedFileInfo; CHAR label[MAX_PATH]; // // Get the language/character-set pair. // if( !VerQueryValueA( VersionResource, "\\VarFileInfo\\Translation", &version, &versionLength ) ) { return FALSE; } charSet = *(LPDWORD)version; charSet = (DWORD)MAKELONG( HIWORD(charSet), LOWORD(charSet) ); // // Get the root block so we can determine if this is a free or // checked build. // if( !VerQueryValue( VersionResource, "\\", &version, &versionLength ) ) { return FALSE; } fixedFileInfo = (VS_FIXEDFILEINFO *)version; dprintf( "%-19s = 0x%08lx (%s)\n", "dwFileFlags", fixedFileInfo->dwFileFlags, ( ( fixedFileInfo->dwFileFlags & VS_FF_DEBUG ) != 0 ) ? "CHECKED" : "FREE" ); // // Dump the various version strings. // for( i = 0 ; i < NUM_LABELS ; i++ ) { wsprintfA( label, "\\StringFileInfo\\%08lX\\%s", charSet, VersionLabels[i] ); if( VerQueryValue( VersionResource, label, &version, &versionLength ) ) { dprintf( "%-19s = %s\n", VersionLabels[i], version ); } } dprintf( "\n" ); return TRUE; } // DumpVersionResource VOID FindAndDumpVersionResourceByAddress( IN ULONG_PTR ModuleAddress, IN PSTR ModuleName ) /*++ Routine Description: Locates and dumps the version resource for the module based at the specified address. Arguments: ModuleAddress - The base address of the module to dump. ModuleName - The module name, for display purposes. Return Value: None. --*/ { IMAGE_DOS_HEADER dosHeader; IMAGE_NT_HEADERS ntHeaders; PIMAGE_OPTIONAL_HEADER optionalHeader; PIMAGE_DATA_DIRECTORY dataDir; PIMAGE_RESOURCE_DIRECTORY baseResourceDir; PIMAGE_RESOURCE_DIRECTORY tmpResourceDir; PIMAGE_RESOURCE_DATA_ENTRY dataEntry; IMAGE_RESOURCE_DATA_ENTRY localDataEntry; PVOID versionResource; // // Setup locals so we know how to cleanup on exit. // versionResource = NULL; // // Read & validate the image headers. // if( !ReadMemory( ModuleAddress, &dosHeader, sizeof(dosHeader), NULL ) ) { dprintf( "inetdbg.ver: cannot read DOS header @ 0x%p\n", ModuleAddress ); goto cleanup; } if( dosHeader.e_magic != IMAGE_DOS_SIGNATURE ) { dprintf( "inetdbg.ver: module @ 0x%p has invalid DOS header\n", ModuleAddress ); goto cleanup; } if( !ReadMemory( ModuleAddress + dosHeader.e_lfanew, &ntHeaders, sizeof(ntHeaders), NULL ) ) { dprintf( "inetdbg.ver: cannot read NT headers @ 0x%p\n", ModuleAddress ); goto cleanup; } if( ntHeaders.Signature != IMAGE_NT_SIGNATURE ) { dprintf( "inetdbg.ver: module @ 0x%p has invalid NT headers\n", ModuleAddress ); goto cleanup; } optionalHeader = &ntHeaders.OptionalHeader; if( optionalHeader->Magic != IMAGE_NT_OPTIONAL_HDR_MAGIC ) { dprintf( "inetdbg.ver: module @ 0x%p has invalid optional header\n", ModuleAddress ); goto cleanup; } // // Locate the resource. // dataDir = &optionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE]; if( dataDir->VirtualAddress == 0 || dataDir->Size == 0 ) { dprintf( "inetdbg.ver: module @ 0x%p has no resource information\n", ModuleAddress ); goto cleanup; } baseResourceDir = (PIMAGE_RESOURCE_DIRECTORY) ( ModuleAddress + dataDir->VirtualAddress ); // // Now go and find the resource in the image. Since resources are // stored heirarchally, we're basically for the resource path: // // VS_FILE_INFO\VS_VERSION_INFO\LanguageId // // For the language ID, we'll first try 0x409 (English) and if // that fails, we'll take any language. // dataEntry = NULL; tmpResourceDir = FindResourceDir( baseResourceDir, baseResourceDir, (USHORT)VS_FILE_INFO ); if( tmpResourceDir != NULL ) { tmpResourceDir = FindResourceDir( baseResourceDir, tmpResourceDir, (USHORT)VS_VERSION_INFO ); if( tmpResourceDir != NULL ) { dataEntry = FindResourceData( baseResourceDir, tmpResourceDir, 0x409 ); if( dataEntry == NULL ) { dataEntry = FindResourceData( baseResourceDir, tmpResourceDir, 0 ); } } } if( dataEntry == NULL ) { dprintf( "inetdbg.ver: cannot find version resource\n" ); goto cleanup; } // // Actually read the dir entry. // if( !ReadMemory( (ULONG_PTR)dataEntry, &localDataEntry, sizeof(localDataEntry), NULL ) ) { dprintf( "inetdbg.ver: error reading resource\n" ); goto cleanup; } // // Now we can allocate & read the resource. // versionResource = malloc( localDataEntry.Size ); if( versionResource == NULL ) { dprintf( "inetdbg.ver: not enough memory\n" ); goto cleanup; } if( !ReadMemory( ModuleAddress + localDataEntry.OffsetToData, versionResource, localDataEntry.Size, NULL ) ) { dprintf( "inetdbg.ver: error reading resource\n" ); goto cleanup; } // // Dump it. // dprintf( "Module @ 0x%p = %s\n", ModuleAddress, ModuleName ); if( !DumpVersionResource( versionResource ) ) { dprintf( "Cannot interpret version resource\n" ); goto cleanup; } cleanup: if( versionResource != NULL ) { free( versionResource ); } } // FindAndDumpVersionResourceByAddress BOOLEAN CALLBACK VerpEnumProc( IN PVOID Param, IN PMODULE_INFO ModuleInfo ) { PENUM_CONTEXT context; INT baseNameLength; context = (PENUM_CONTEXT)Param; baseNameLength = strlen( ModuleInfo->BaseName ); // // If the user wants all modules, or if the specified module matches // the "tail" of the module name, dump it. // if( context->ModuleName == NULL || ( baseNameLength >= context->NameLength && !_stricmp( context->ModuleName, ModuleInfo->BaseName + baseNameLength - context->NameLength ) ) ) { FindAndDumpVersionResourceByAddress( ModuleInfo->DllBase, ModuleInfo->BaseName ); } return TRUE; } // VerpEnumProc VOID FindAndDumpVersionResourceByName( IN PSTR ModuleName ) /*++ Routine Description: Locates and dumps the version resource for the specified module. Arguments: ModuleName - The name of the module to dump. If this is NULL then all modules are dumped. Return Value: None. --*/ { ENUM_CONTEXT context; context.ModuleName = ModuleName; if( ModuleName == NULL ) { context.NameLength = 0; } else { context.NameLength = strlen( ModuleName ); } if( !EnumModules( VerpEnumProc, (PVOID)&context ) ) { dprintf( "error retrieving module list\n" ); } } // FindAndDumpVersionResourceByName DECLARE_API( ver ) /*++ Routine Description: This function is called as an NTSD extension to format and dump module version info. Arguments: hCurrentProcess - Supplies a handle to the current process (at the time the extension was called). hCurrentThread - Supplies a handle to the current thread (at the time the extension was called). CurrentPc - Supplies the current pc at the time the extension is called. lpExtensionApis - Supplies the address of the functions callable by this extension. lpArgumentString - Supplies the asciiz string that describes the ansi string to be dumped. Return Value: None. --*/ { ULONG module; PSTR endPointer; INIT_API(); // // Skip leading blanks. // while( *lpArgumentString == ' ' || *lpArgumentString == '\t' ) { lpArgumentString++; } if( *lpArgumentString == '\0' ) { // // No argument passed, dump all modules. // FindAndDumpVersionResourceByName( NULL ); } else { module = strtoul( lpArgumentString, &endPointer, 16 ); if( *endPointer != ' ' && *endPointer != '\t' && *endPointer != '\0' ) { // // Assume the argument is actually a module name, not // a base address. // FindAndDumpVersionResourceByName( lpArgumentString ); } else { FindAndDumpVersionResourceByAddress( module, NULL ); } } } // DECLARE_API( ver )