Skip to content

How Windows CRT Checks For Managed Module

While debugging an issue I came across an interesting piece of code in Windows CRT source. So far I have been using the code below to determine if an executable is managed or native. Basically I am just checking for the presence of IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR Data Directory entry in PE headers.

  1.  
  2. int CheckCLRHeader(PBYTE pbFile)
  3. {
  4. PIMAGE_DOS_HEADER pDOSHeader;
  5. PIMAGE_NT_HEADERS pNTHeader;
  6. PIMAGE_OPTIONAL_HEADER32 pNTHeader32;
  7. PIMAGE_OPTIONAL_HEADER64 pNTHeader64;
  8. PIMAGE_DATA_DIRECTORY pDataDirectory;
  9.  
  10. if(NULL == pbFile)
  11. {
  12. cout << "Invalid file" << endl;
  13. return 0;
  14. }
  15.  
  16. pDOSHeader = reinterpret_cast
  17. < PIMAGE_DOS_HEADER >(pbFile);
  18. if(IMAGE_DOS_SIGNATURE != pDOSHeader->e_magic)
  19. {
  20. cout << "Cannot find DOS header. Not an executable file" << endl;
  21. return 0;
  22. }
  23.  
  24. pNTHeader = ImageNtHeader(pbFile);
  25. if(NULL == pNTHeader)
  26. {
  27. cout << "Cannot find PE header. Invalid or corrupt file" << endl;
  28. return 0;
  29. }
  30.  
  31. if(IMAGE_NT_SIGNATURE != pNTHeader->Signature)
  32. {
  33. cout << "Cannot fine PE signature. Invalid or corrupt file" << endl;
  34. return 0;
  35. }
  36.  
  37. switch(pNTHeader->OptionalHeader.Magic)
  38. {
  39. case IMAGE_NT_OPTIONAL_HDR32_MAGIC:
  40. pNTHeader32 = reinterpret_cast
  41. < PIMAGE_OPTIONAL_HEADER32 >(&pNTHeader->OptionalHeader);
  42. if(pNTHeader32->NumberOfRvaAndSizes <= IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR)
  43. {
  44. cout << "No CLR Data Dictionary. Not a Managed Assembly" << endl;
  45. return 0;
  46. }
  47. pDataDirectory = pNTHeader32->DataDirectory;
  48. break;
  49.  
  50. case IMAGE_NT_OPTIONAL_HDR64_MAGIC:
  51. pNTHeader64 = reinterpret_cast
  52. < PIMAGE_OPTIONAL_HEADER64 >(&pNTHeader->OptionalHeader);
  53. if(pNTHeader64->NumberOfRvaAndSizes <= IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR)
  54. {
  55. cout << "No CLR Data Dictionary. Not a Managed Assembly" << endl;
  56. return 0;
  57. }
  58. pDataDirectory = pNTHeader64->DataDirectory;
  59. break;
  60.  
  61. default:
  62. cout << "Invalid NT header. Invalid or corrupt file" << endl;
  63. return 0;
  64. }
  65.  
  66. if(NULL == pDataDirectory)
  67. {
  68. cout << "Cannot find data directories. Invalid or corrupt file" << endl;
  69. return 0;
  70. }
  71.  
  72. if(0 == pDataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR].VirtualAddress ||
  73. 0 == pDataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR].Size)
  74. {
  75. cout << "COM Data Directory not found. Not a Managed Assembly" << endl;
  76. return 0;
  77. }
  78.  
  79. cout << "Managed Assembly" << endl;
  80.  
  81. return 1;
  82. }
  83.  

The code for check_managed_app function in crtexe.c is similar but slightly different in how it checks for the same IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR but only checks for presence of VirtualAddress.

  1.  
  2. /***
  3. *check_managed_app() - Check for a managed executable
  4. *
  5. *Purpose:
  6. * Determine if the EXE the startup code is linked into is a managed app
  7. * by looking for the COM Runtime Descriptor in the Image Data Directory
  8. * of the PE or PE+ header.
  9. *
  10. *Entry:
  11. * None
  12. *
  13. *Exit:
  14. * 1 if managed app, 0 if not.
  15. *
  16. *Exceptions:
  17. *
  18. *******************************************************************************/
  19.  
  20. static int __cdecl check_managed_app (
  21. void
  22. )
  23. {
  24. PIMAGE_DOS_HEADER pDOSHeader;
  25. PIMAGE_NT_HEADERS pPEHeader;
  26. PIMAGE_OPTIONAL_HEADER32 pNTHeader32;
  27. PIMAGE_OPTIONAL_HEADER64 pNTHeader64;
  28.  
  29. pDOSHeader = (PIMAGE_DOS_HEADER)&__ImageBase;
  30. if ( pDOSHeader->e_magic != IMAGE_DOS_SIGNATURE )
  31. return 0;
  32.  
  33. pPEHeader = (PIMAGE_NT_HEADERS)((char *)pDOSHeader +
  34. pDOSHeader->e_lfanew);
  35. if ( pPEHeader->Signature != IMAGE_NT_SIGNATURE )
  36. return 0;
  37.  
  38. pNTHeader32 = (PIMAGE_OPTIONAL_HEADER32)&pPEHeader->OptionalHeader;
  39. switch ( pNTHeader32->Magic ) {
  40. case IMAGE_NT_OPTIONAL_HDR32_MAGIC:
  41. /* PE header */
  42. /* prefast assumes we are overflowing __ImageBase */
  43. #pragma warning(push)
  44. #pragma warning(disable:26000)
  45. if ( pNTHeader32->NumberOfRvaAndSizes <=
  46. IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR )
  47. return 0;
  48. #pragma warning(pop)
  49. return !! pNTHeader32 ->
  50. DataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR] .
  51. VirtualAddress;
  52. case IMAGE_NT_OPTIONAL_HDR64_MAGIC:
  53. /* PE+ header */
  54. pNTHeader64 = (PIMAGE_OPTIONAL_HEADER64)pNTHeader32;
  55. if ( pNTHeader64->NumberOfRvaAndSizes <=
  56. IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR )
  57. return 0;
  58. return !! pNTHeader64 ->
  59. DataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR] .
  60. VirtualAddress;
  61. }
  62.  
  63. /* Not PE or PE+, so not managed */
  64. return 0;
  65. }
  66.  

I came across this code while debugging past the main() function. I thought this might be a new piece of code in CRT source that comes with VS2008 only to be proven wrong when searching through the CRT source that comes with all versions of Visual Studio starting from VS2002. Another interesting discovery (at least for me) is that similar code is in various CRT source files. In VS2008 CRT source the files crt0dat.c, crt0.c and crtexe.c contain check_managed_app function in Win32 CRT and crt0dat.c in WinCE CRT. In VS2005 the same function is in crt0.c, crt0dat.c and crtexe.c. In VS2003 and VS2002 it is in crt0.c and crtexe.c. There are other gems of code in CRT source waiting to be discovered!