如何确定一个exe文件是.NET exe还是普通exe?

9

这不是以下 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)
{
   ...
}

1
很可能是的。您还可以询问exe的依赖项列表,以确定是否需要一个依赖项使用.Net - 链条可能很长,这取决于您需要信息的内容。如果只是exe,则更简单,如果整个过程需要.Net,则需要检查所有依赖项。 - Niall
2
一个.NET可执行文件是一个常规的win32应用程序,它可以引导.NET。不确定PE头是否会提供关于此的提示。 - MicroVirus
1
这个链接可能会帮助你入门;http://www.codeproject.com/Articles/12585/The-NET-File-Format,你需要验证一下 "mixed mode" 的 exe(即用 /clr 编译的),但你的答案甚至可以更简单,直接查找导出函数 _CorExeMain。看起来 MS 头部的规范在这里;https://msdn.microsoft.com/en-us/windows/hardware/gg463119.aspx - Niall
2
确定 DLL 或 EXE 是否为托管组件 - phuclv
1
@LưuVĩnhPhúc。我知道,但那是一种依赖。这将需要分发(如果可能的话,有些工具是有限制的)该工具。 - Niall
显示剩余13条评论
3个回答

8

以前的做法是使用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_WARNING来抑制弃用错误,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);
}

其他直接搜索可执行文件中的CLR头文件的技术在这个问题和答案中提到。它们的可持续性很难预测。


2
“它们有多具备未来性是很难猜测的。”鉴于这些偏移量已经被记录,看起来它们非常具备未来性。而且由于操作系统加载器并不是经常更改的东西,这更加证明了解析PE头文件的方法实际上是非常安全的。 - IInspectable
对于CLR头部总是位于那个位置的假设已经是一种牵强附会了。特别是在CoreCLR转向Linux和OSX这样的操作系统时,PE格式就毫无意义了。我们的口号是始终避免依赖具体实现细节,而是使用文档中记录的API。 - Hans Passant
虽然您总体上是正确的,但我并不完全相信您提出的两个解决方案中的任何一个都能知道如何解释非PE图像。或者Windows上的.NET Hosting API是否实现了一个ELF解析器? - IInspectable
很明显,随着CLR主机的移植,API也将被移植。现在正在全力进行中。 - Hans Passant
换句话说,这个答案中的解决方案也无法识别非.NET平台上的程序集。它们在今天并没有比解析PE头更强大的能力。这种情况可能会在未来发生改变,但不会被引入/回溯到.NET 4框架中,并且需要客户机上更新的.NET安装。但还是好知道,如果可以假设存在.NET 4安装,则有一种正式的方法可用。 - IInspectable
但是等等,似乎binfmt与实际的PE文件一起在Linux等系统中使用。 - 0xC0000022L

8
一种方法是通过检查PE头等正确的标志来进行询问。有几个链接供进一步阅读; 这里这里(以及一个较旧的MSDN文章这里)。
关于PE头的MS文档可用(需要许可协议)
最简单的方法可能只是列出依赖的dll(因为这仅适用于主exe),并查找mscoree.dll的存在。导入表将包括mscoree.dll,并包含一个名为_CorExeMain的函数条目(来自mscoree.dll)。更多关于此的链接可以在这里的SO这里的GitHub,看起来是一个广泛的示例以及这篇CodeGuru上的文章中找到,其中包含一个具有BOOL IsManaged(LPTSTR lpszImageName)签名的函数的代码(许可证似乎限制重新发布)。

5

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

我最近写了类似的函数,以下是代码,您可以使用它。实际上,我使用了一个智能包装器来处理句柄,但在此处被省略,因此添加了显式调用CloseHandle。此外,在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;
}

2
代码肯定会从错误处理中受益。至少,您应该检查 API 调用是否失败,尽管测试预期的头部魔术值也不是一个坏主意。 - IInspectable

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接