如何查明exe文件是.Net exe还是常规exe

How to find out if a exe file is a .Net exe or regular exe?

本文关键字:exe Net 常规 文件 何查明      更新时间:2023-10-16

这不是下面SO问题的重复:如何判断win32应用程序是否使用.NET运行时。

如果给定的exe文件是.net exe文件还是常规的WIN32/WIN64 exe文件,我如何通过程序查找

这个问题不是关于询问一个正在运行的进程,而是关于一个exe文件,正如标签所示,不需要用VB.net或C#编写的解决方案。

我需要一个带有签名的函数,例如:

// return true if filename is a exe file for .Net
bool IsExeFileDotNet(LPCTSTR filename)
{
   ...
}

旧的备用是使用GetFileVersion()从文件中读取目标运行时版本。当可执行文件不包含CLR头时,它将以ERROR_BAD_FORMAT失败。适用于程序集的任何位和任何目标体系结构。你必须这样使用它:

#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;
}

请注意使用use_DEPRECATED_CLR_API_WITHOUT_WWARNING来抑制弃用错误,MSCorEE API可能会在未来的.NET主要版本中消失。

不推荐使用的方法是使用ICLRMetaHost::GetFileVersion(),缺点是它只能在计算机安装了.NET 4时工作。今天不是什么大问题。看起来像这样:

#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);
}

本Q+A中提到了直接插入可执行文件以在文件中查找CLR头的其他技术。很难猜测它们未来会如何证明。

一种方法是询问PE标头等以获取正确的标志。有几个链接可供进一步阅读;这里和这里(以及MSDN的一篇旧文章)。

PE标头上的MS文档可用(带有许可协议)。

最简单的方法可能只是列出依赖的dll(因为这只适用于主exe)并查找mscoree.dll的存在。导入表将包括mscoree.dll以及函数_CorExeMain的include和entry(来自mscoree..dll)。有关这方面的更多链接可以在SO和GitHub上找到,这似乎是一个广泛的示例,以及本文(关于CodeGuru),其中包含您所需的签名为BOOL IsManaged(LPTSTR lpszImageName)的函数的代码(许可证似乎限制了重新发布)。

这个想法是检查是否存在特殊的PE目录IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR

我最近写了类似的函数,这里是代码,你可以使用它。事实上,我已经为句柄使用了智能包装器,但这里省略了它,所以添加了CloseHandle的expicit调用。此外,在ReadFile/SetFilePointer调用后检查错误也是一个好主意。无论如何,我希望它能有用:

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;
}