9

This is not a duplicate off following SO question: How do I tell if a win32 application uses the .NET runtime.

How can I find out programatically if a given exe file is a .net exe file or a regular WIN32/WIN64 exe file?

The question is not about interrogating a running process but it's about an exe file and as the tags suggest, a solution written in VB.net or C# is not required.

I need a function with a signature such as:

// return true if filename is a exe file for .Net
bool IsExeFileDotNet(LPCTSTR filename)
{
   ...
}
Community
  • 1
  • 1
Jabberwocky
  • 48,281
  • 17
  • 65
  • 115
  • The PE header contains details on what the exe requires w.r.t. "bit-ness", .Net etc. – Niall Apr 12 '16 at 10:02
  • @Niall so suggest parsing the PE Header is the way to go ? – Jabberwocky Apr 12 '16 at 10:03
  • What exactly would you look for in the PE header? – David Heffernan Apr 12 '16 at 10:05
  • 1
    Probably, yes. You could also interrogate the list of dependencies of the exe to determine if .Net will be required by one of its dependencies - the chain can be long and depends on what you need the information for. If just the exe, then it is simpler, if the process as a whole requires .Net, then you will need to check all the dependencies. – Niall Apr 12 '16 at 10:06
  • 2
    A .NET executable is a regular win32 application that bootstraps .NET. Not entirely sure if the PE header would give hints about this. – MicroVirus Apr 12 '16 at 10:07
  • @Niall, just the .exe is enough. – Jabberwocky Apr 12 '16 at 10:07
  • 1
    None of the duplicates is really exhaustive. The ones that appear to be safe, use .NET themselves (which this question isn't asking for). And the ones that boil down to parsing the PE's headers have no link to official documentation, or even unofficial information on how the OS loader determines, whether the CLR needs to be loaded. I'm voting to re-open. – IInspectable Apr 12 '16 at 10:12
  • It's not a duplicate, all "duplicates" are for running processes and/or for .net (vb.net or C#). I need a _programmatic_ solution callable from pure Win32 (C, C++ or anything else). – Jabberwocky Apr 12 '16 at 10:13
  • @DavidHeffernan _What exactly would you look for in the PE header?_ That's more or less what I'd need to know. – Jabberwocky Apr 12 '16 at 10:17
  • 1
    This link may get your started; http://www.codeproject.com/Articles/12585/The-NET-File-Format, you will need to verify for "mixed mode" exe (i.e. compiled with `/clr`), but your answer could be even simpler, looking for an exported function `_CorExeMain`. Looks like the MS spec for the header is here; https://msdn.microsoft.com/en-us/windows/hardware/gg463119.aspx – Niall Apr 12 '16 at 10:28
  • @Niall looks promising. If you could vote for reopening the question.... – Jabberwocky Apr 12 '16 at 10:30
  • 2
    [Determining Whether a DLL or EXE Is a Managed Component](http://www.codeguru.com/cpp/w-p/dll/openfaq/article.php/c14001/Determining-Whether-a-DLL-or-EXE-Is-a-Managed-Component.htm) – phuclv Apr 12 '16 at 10:49
  • 1
    Possible duplicate of [How do I tell if a win32 application uses the .NET runtime](http://stackoverflow.com/questions/751254/how-do-i-tell-if-a-win32-application-uses-the-net-runtime). There are several tools in that page that can check the exe – phuclv Apr 12 '16 at 10:49
  • The next follow up could then simply be to list the dependent dlls and check for mscoree.dll... http://stackoverflow.com/q/33881603/3747990 – Niall Apr 12 '16 at 11:00
  • @LưuVĩnhPhúc. That is not a duplicate, he specifically wants this to be done programmatically, whilst an open source tool could give him some code the needs, a tool is not what is being asked for. – Niall Apr 12 '16 at 11:16
  • @Niall but you can call the tool from your code – phuclv Apr 12 '16 at 11:20
  • 1
    @LưuVĩnhPhúc. I know, but that is something of a crutch. It would require the distribution (if even possible, some of these tools are limited) of the tool as well. – Niall Apr 12 '16 at 11:22
  • @LưuVĩnhPhúc calling a tool is *not* an option. – Jabberwocky Apr 12 '16 at 11:28

3 Answers3

8

One approach is to interrogate the PE header etc. for the correct flags. There are several links for further reading; here and here (and an older MSDN article here).

MS documentation on the PE header is available (with a license agreement).

The easiest may just be to list the dependent dlls (since this is for the main exe only) and look for the presence of mscoree.dll. The import table will include mscoree.dll and include and entry for the function _CorExeMain (from mscoree.dll). More links on this can be found here on SO and here on GitHub, that appears to be an extensive sample, and this article (on CodeGuru) with code for a function with the signature BOOL IsManaged(LPTSTR lpszImageName) that you require (license seems to restrict re-publishing).

Community
  • 1
  • 1
Niall
  • 30,036
  • 10
  • 99
  • 142
8

The olden standby was to read the target runtime version from the file with GetFileVersion(). It will fail with ERROR_BAD_FORMAT when the executable file does not contain the CLR header. Works in any bitness and any target architecture of the assembly. You'll have to use it like this:

#define USE_DEPRECATED_CLR_API_WITHOUT_WARNING
#include <mscoree.h>
#pragma comment(lib, "mscoree.lib")
#include <assert.h>

bool IsExeFileDotNet(LPCWSTR filename)
{
    WCHAR buf[16];
    HRESULT hr = GetFileVersion(filename, buf, 16, NULL);
    assert(hr == S_OK || hr == HRESULT_FROM_WIN32(ERROR_BAD_FORMAT));
    return hr == S_OK;
}

Do note the use of USE_DEPRECATED_CLR_API_WITHOUT_WARNING to suppress a deprecation error, the MSCorEE api is liable to disappear in a future major release of .NET.

The non-deprecated way is to use ICLRMetaHost::GetFileVersion(), disadvantage is that it can only work when the machine has .NET 4 installed. Not exactly a major problem today. Looks like this:

#include <Windows.h>
#include <metahost.h>
#include <assert.h>
#pragma comment(lib, "mscoree.lib")

bool IsExeFileDotNet(LPCWSTR filename)
{
    ICLRMetaHost* host;
    HRESULT hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (void**)&host);
    assert(SUCCEEDED(hr));
    if (hr == S_OK) {
        WCHAR buf[16];
        DWORD written;
        hr = host->GetVersionFromFile(filename, buf, &written);
        assert(hr == S_OK || hr == HRESULT_FROM_WIN32(ERROR_BAD_FORMAT));
        host->Release();
    }
    return SUCCEEDED(hr);
}

Other techniques that poke the executable file directly to look for the CLR header in the file are mentioned in this Q+A. How future-proof they may be is very hard to guess.

Community
  • 1
  • 1
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • 2
    *"How future-proof they may be is very hard to guess."* - Given, that the offsets are [documented](https://msdn.microsoft.com/en-us/library/windows/desktop/ms680305.aspx), it looks like they are pretty much future-proof. And since the OS loader isn't something that changes frequently, this is all the more reason to believe, that the approaches parsing the PE headers is in fact just as safe. – IInspectable Apr 12 '16 at 11:46
  • The assumption that the CLR header will always be located that way is already a stretch. Particularly as CoreCLR is moving to Linux and OSX, operating systems where the PE format means beans. The mantra is to always avoid relying on implementation details and use the documented api. – Hans Passant Apr 12 '16 at 11:50
  • While you are right in general, I'm not at all convinced, that either of your two solutions would know how to interpret non-PE images. Or does the .NET Hosting API on Windows implement an ELF-parser? – IInspectable Apr 12 '16 at 12:02
  • Clearly as the CLR host gets ported, the api will get ported as well. It is in full swing right now. – Hans Passant Apr 12 '16 at 12:04
  • In other words: The solutions in this answer cannot identify .NET assemblies for foreign platforms either. They aren't any more capable today, than parsing PE headers. This may change in the future, but will not be introduced/backported into the .NET 4 framework, and requires an updated .NET installation on the client's machine. Still good to know, that an official method exists, in case you can assume a .NET 4 installation being present. – IInspectable Apr 12 '16 at 15:49
  • But wait, it seems as if binfmt is used in conjunction with actual PE files, even on Linux etc. – 0xC0000022L Mar 29 '22 at 06:57
5

The idea is to check if special PE directory IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR exists.

I've written similar function recently, here the code, you can use it. In fact I've used a smart wrapper for handle, but it's omitted here, so expicit call of CloseHandle added. Also it's a good idea to check errors after ReadFile/SetFilePointer calls. Anyway I hope it can be useful:

BOOL IsDotNetApp(LPCWSTR szPath)
{
    HANDLE hFile = CreateFileW(szPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
    if (INVALID_HANDLE_VALUE == hFile)
        return FALSE;

    DWORD temp;

    IMAGE_DOS_HEADER IMAGE_DOS_HEADER_;
    ReadFile(hFile, &IMAGE_DOS_HEADER_, sizeof(IMAGE_DOS_HEADER_), &temp, NULL);

    SetFilePointer(hFile, IMAGE_DOS_HEADER_.e_lfanew, NULL, FILE_BEGIN);

    const int nNtHeaderMaxSize = sizeof(IMAGE_NT_HEADERS64);
    BYTE NT_HEADERS[nNtHeaderMaxSize];
    ReadFile(hFile, NT_HEADERS, nNtHeaderMaxSize, &temp, NULL); 

    PIMAGE_NT_HEADERS pNT_HEADERS = (PIMAGE_NT_HEADERS)NT_HEADERS;
    BOOL bRes;
    if (pNT_HEADERS->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC)
    {
        bRes = 0 != ((PIMAGE_NT_HEADERS32)NT_HEADERS)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR].VirtualAddress;
    }
    else if (pNT_HEADERS->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC)
    {
        bRes = 0 != ((PIMAGE_NT_HEADERS64)NT_HEADERS)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR].VirtualAddress;
    }
    else
    {
        // Unknown header type
        bRes = FALSE;
    }

    CloseHandle(hFile);

    return bRes;
}
Artem Razin
  • 1,234
  • 8
  • 22
  • 2
    The code would certainly profit from error handling. At the very least you should check API calls for failure, although testing expected header magic values may not be a bad idea either. – IInspectable Apr 12 '16 at 15:53