【问题标题】:How to find out if a exe file is a .Net exe or regular exe?如何确定 exe 文件是 .Net exe 还是常规 exe?
【发布时间】:2016-04-12 09:51:42
【问题描述】:

不是以下 SO 问题的重复: How do I tell if a win32 application uses the .NET runtime.

如果给定的 exe 文件是 .net exe 文件还是常规的 WIN32/WIN64 exe 文件,我如何以编程方式找出?

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

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

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

【问题讨论】:

  • 可能,是的。您还可以查询 exe 的依赖项列表以确定其依赖项之一是否需要 .Net - 链可能很长,并且取决于您需要信息的目的。如果只是exe,那就更简单了,如果整个过程需要.Net,那么你需要检查所有的依赖关系。
  • .NET 可执行文件是引导 .NET 的常规 win32 应用程序。不完全确定 PE 标头是否会给出提示。
  • 没有一个副本是真正详尽的。那些看起来很安全的,自己使用.NET(这个问题不是要问的)。归结为解析 PE 标头的那些没有指向官方文档的链接,甚至没有关于 OS 加载程序如何确定是否需要加载 CLR 的非官方信息。我正在投票重新开放。
  • 此链接可以帮助您入门; codeproject.com/Articles/12585/The-NET-File-Format,您将需要验证“混合模式”exe(即使用/clr 编译),但您的答案可能更简单,寻找导出函数_CorExeMain。看起来标题的 MS 规范在这里; msdn.microsoft.com/en-us/windows/hardware/gg463119.aspx

标签: c++ c windows winapi


【解决方案1】:

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

关于 PE 标头 is available (with a license agreement) 的 MS 文档。

最简单的方法可能是到list the dependent dlls(因为这仅适用于主exe)并查找mscoree.dll 的存在。导入表将包括 mscoree.dll 以及函数 _CorExeMain 的包含和条目(来自 mscoree.dll)。可以在here on SOhere on GitHub, that appears to be an extensive samplethis article (on CodeGuru) 找到更多关于此的链接,其中包含您需要的带有签名BOOL IsManaged(LPTSTR lpszImageName) 的函数的代码(许可证似乎限制重新发布)。

【讨论】:

    【解决方案2】:

    旧的备用是从带有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);
    }
    

    this Q+A 中提到了直接戳可执行文件以查找文件中的 CLR 标头的其他技术。很难猜测它们的面向未来的能力。

    【讨论】:

    • “很难猜测它们的未来性如何。” - 鉴于偏移量是documented,看起来它们几乎是未来的 -证明。而且由于 OS 加载器不是经常更改的东西,因此更有理由相信解析 PE 标头的方法实际上同样安全。
    • CLR 标头总是以这种方式定位的假设已经有些牵强。特别是随着 CoreCLR 正在转向 Linux 和 OSX,PE 格式意味着 bean 的操作系统。口头禅是始终避免依赖实现细节并使用记录在案的 api。
    • 虽然您总体上是对的,但我完全不相信您的两种解决方案中的任何一种都知道如何解释非 PE 图像。还是 Windows 上的 .NET Hosting API 实现了 ELF 解析器?
    • 很明显,随着 CLR 主机被移植,api 也将被移植。它现在正在全面展开。
    • 换句话说:此答案中的解决方案也无法识别外部平台的 .NET 程序集。如今,它们的能力并没有比解析 PE 标头更强大。这可能会在未来发生变化,但不会被引入/向后移植到 .NET 4 框架中,并且需要在客户端计算机上安装更新的 .NET。仍然很高兴知道,存在官方方法,以防您可以假设存在 .NET 4 安装。
    【解决方案3】:

    这个想法是检查特殊的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;
    }
    

    【讨论】:

    • 代码肯定会从错误处理中受益。至少您应该检查 API 调用是否失败,尽管测试预期的标头魔法值也可能不是一个坏主意。
    猜你喜欢
    • 1970-01-01
    • 2011-03-10
    • 2011-09-12
    • 1970-01-01
    • 2010-10-23
    • 1970-01-01
    • 1970-01-01
    • 2012-08-03
    • 2011-04-20
    相关资源
    最近更新 更多