如何查找一个文件是否为可执行文件(exe)?

17

我如何确保传递给我的程序的文件是有效的exe文件?

实际上,我的程序将文件作为输入并运行它,但用户可以输入任何文件,因此我必须确保输入是有效的exe文件。


2
有几种有效的可执行文件格式可以相当容易地被检测出来(我会留给更专业的人去列举),但我要问的是:“你疯了吗?”;-) - Sky Sanders
1
@Cipi:DLL文件也包含该签名。我认为所有PE文件都是这样的。 - leppie
1
我真希望这个程序不是一个网站!您的安全系统足够好吗?您信任用户足够运行任意EXE文件吗? - Daniel Renshaw
1
@quixoto:你说的“错误”是什么?我告诉他这里使用的标准与新闻组或其他网络论坛使用的标准不同。我没有投反对票或做任何事情。 - John Saunders
1
@Praveen:请看下面我的答案,它比检查文件扩展名或前导魔术字节更详细,但不需要实际尝试执行文件。它利用PE格式的知识进行多次验证。 - Chris Schmich
显示剩余8条评论
6个回答

25
如果您想要比“文件名是否以“.exe”结尾更深入的了解,但又不想实际运行程序,那么您可以检查PE头的存在和有效性。 此外,检查前2个字节(PE文件的“MZ”)也会对DLL返回true。 如果您不想要这个,可以尝试这个方法。 Matt Pietrek写了几篇关于PE格式的好文章:
- 深入了解Win32可移植可执行文件格式中的“Peering Inside the PE: A Tour of the Win32 Portable Executable File Format”。 - 深入了解Win32可移植可执行文件格式的“An In-Depth Look into the Win32 Portable Executable File Format”
这里两个重要的数据结构是CODE>IMAGE_DOS_HEADER和IMAGE_NT_HEADERS32/IMAGE_NT_HEADERS64。这些结构在Windows SDK中的winnt.h中定义。这里描述了许多这些PE结构。
你可以使用托管代码(类似于这种方法)来处理PE头文件。以下代码适用于32位(i386)和64位(IA64,AMD64).exe PE文件,返回true(例如,对于DLL文件返回false)。请参见底部的用法(ExeChecker.IsValidExe)。如果需要,您可以添加其他检查以支持更多体系结构或进行更多验证。有关更多常量,请参见winnt.h
using System;
using System.IO;
using System.Runtime.InteropServices;

namespace ExeChecker
{
    [StructLayout(LayoutKind.Sequential)]
    struct IMAGE_DOS_HEADER
    {
        public ushort e_magic;    // Magic number
        public ushort e_cblp;     // Bytes on last page of file
        public ushort e_cp;       // Pages in file
        public ushort e_crlc;     // Relocations
        public ushort e_cparhdr;  // Size of header in paragraphs
        public ushort e_minalloc; // Minimum extra paragraphs needed
        public ushort e_maxalloc; // Maximum extra paragraphs needed
        public ushort e_ss;       // Initial (relative) SS value
        public ushort e_sp;       // Initial SP value
        public ushort e_csum;     // Checksum
        public ushort e_ip;       // Initial IP value
        public ushort e_cs;       // Initial (relative) CS value
        public ushort e_lfarlc;   // File address of relocation table
        public ushort e_ovno;     // Overlay number
        public uint e_res1;       // Reserved
        public uint e_res2;       // Reserved
        public ushort e_oemid;    // OEM identifier (for e_oeminfo)
        public ushort e_oeminfo;  // OEM information; e_oemid specific
        public uint e_res3;       // Reserved
        public uint e_res4;       // Reserved
        public uint e_res5;       // Reserved
        public uint e_res6;       // Reserved
        public uint e_res7;       // Reserved
        public int e_lfanew;      // File address of new exe header
    }

    [StructLayout(LayoutKind.Sequential)]
    struct IMAGE_FILE_HEADER
    {
        public ushort Machine;
        public ushort NumberOfSections;
        public uint TimeDateStamp;
        public uint PointerToSymbolTable;
        public uint NumberOfSymbols;
        public ushort SizeOfOptionalHeader;
        public ushort Characteristics;
    }

    [StructLayout(LayoutKind.Sequential)]
    struct IMAGE_NT_HEADERS_COMMON
    {
        public uint Signature;
        public IMAGE_FILE_HEADER FileHeader;
    }

    [StructLayout(LayoutKind.Sequential)]
    struct IMAGE_NT_HEADERS32
    {
        public uint Signature;
        public IMAGE_FILE_HEADER FileHeader;
        public IMAGE_OPTIONAL_HEADER32 OptionalHeader;
    }

    [StructLayout(LayoutKind.Sequential)]
    struct IMAGE_NT_HEADERS64
    {
        public uint Signature;
        public IMAGE_FILE_HEADER FileHeader;
        public IMAGE_OPTIONAL_HEADER64 OptionalHeader;
    }

    [StructLayout(LayoutKind.Sequential)]
    struct IMAGE_OPTIONAL_HEADER32
    {
        public ushort Magic;
        public byte MajorLinkerVersion;
        public byte MinorLinkerVersion;
        public uint SizeOfCode;
        public uint SizeOfInitializedData;
        public uint SizeOfUninitializedData;
        public uint AddressOfEntryPoint;
        public uint BaseOfCode;
        public uint BaseOfData;
        public uint ImageBase;
        public uint SectionAlignment;
        public uint FileAlignment;
        public ushort MajorOperatingSystemVersion;
        public ushort MinorOperatingSystemVersion;
        public ushort MajorImageVersion;
        public ushort MinorImageVersion;
        public ushort MajorSubsystemVersion;
        public ushort MinorSubsystemVersion;
        public uint Win32VersionValue;
        public uint SizeOfImage;
        public uint SizeOfHeaders;
        public uint CheckSum;
        public ushort Subsystem;
        public ushort DllCharacteristics;
        public uint SizeOfStackReserve;
        public uint SizeOfStackCommit;
        public uint SizeOfHeapReserve;
        public uint SizeOfHeapCommit;
        public uint LoaderFlags;
        public uint NumberOfRvaAndSizes;
    }

    [StructLayout(LayoutKind.Sequential)]
    struct IMAGE_OPTIONAL_HEADER64
    {
        public ushort Magic;
        public byte MajorLinkerVersion;
        public byte MinorLinkerVersion;
        public uint SizeOfCode;
        public uint SizeOfInitializedData;
        public uint SizeOfUninitializedData;
        public uint AddressOfEntryPoint;
        public uint BaseOfCode;
        public ulong ImageBase;
        public uint SectionAlignment;
        public uint FileAlignment;
        public ushort MajorOperatingSystemVersion;
        public ushort MinorOperatingSystemVersion;
        public ushort MajorImageVersion;
        public ushort MinorImageVersion;
        public ushort MajorSubsystemVersion;
        public ushort MinorSubsystemVersion;
        public uint Win32VersionValue;
        public uint SizeOfImage;
        public uint SizeOfHeaders;
        public uint CheckSum;
        public ushort Subsystem;
        public ushort DllCharacteristics;
        public ulong SizeOfStackReserve;
        public ulong SizeOfStackCommit;
        public ulong SizeOfHeapReserve;
        public ulong SizeOfHeapCommit;
        public uint LoaderFlags;
        public uint NumberOfRvaAndSizes;
    }

    static class ExeChecker
    {
        public static bool IsValidExe(string fileName)
        {
            if (!File.Exists(fileName))
                return false;

            try
            {
                using (var stream = File.OpenRead(fileName))
                {
                    IMAGE_DOS_HEADER dosHeader = GetDosHeader(stream);
                    if (dosHeader.e_magic != IMAGE_DOS_SIGNATURE)
                        return false;

                    IMAGE_NT_HEADERS_COMMON ntHeader = GetCommonNtHeader(stream, dosHeader);
                    if (ntHeader.Signature != IMAGE_NT_SIGNATURE)
                        return false;

                    if ((ntHeader.FileHeader.Characteristics & IMAGE_FILE_DLL) != 0)
                        return false;

                    switch (ntHeader.FileHeader.Machine)
                    {
                        case IMAGE_FILE_MACHINE_I386:
                            return IsValidExe32(GetNtHeader32(stream, dosHeader));

                        case IMAGE_FILE_MACHINE_IA64:
                        case IMAGE_FILE_MACHINE_AMD64:
                            return IsValidExe64(GetNtHeader64(stream, dosHeader));
                    }
                }
            }
            catch (InvalidOperationException)
            {
                return false;
            }

            return true;
        }

        static bool IsValidExe32(IMAGE_NT_HEADERS32 ntHeader)
        {
            return ntHeader.OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC;
        }

        static bool IsValidExe64(IMAGE_NT_HEADERS64 ntHeader)
        {
            return ntHeader.OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC;
        }

        static IMAGE_DOS_HEADER GetDosHeader(Stream stream)
        {
            stream.Seek(0, SeekOrigin.Begin);
            return ReadStructFromStream<IMAGE_DOS_HEADER>(stream);
        }

        static IMAGE_NT_HEADERS_COMMON GetCommonNtHeader(Stream stream, IMAGE_DOS_HEADER dosHeader)
        {
            stream.Seek(dosHeader.e_lfanew, SeekOrigin.Begin);
            return ReadStructFromStream<IMAGE_NT_HEADERS_COMMON>(stream);
        }

        static IMAGE_NT_HEADERS32 GetNtHeader32(Stream stream, IMAGE_DOS_HEADER dosHeader)
        {
            stream.Seek(dosHeader.e_lfanew, SeekOrigin.Begin);
            return ReadStructFromStream<IMAGE_NT_HEADERS32>(stream);
        }

        static IMAGE_NT_HEADERS64 GetNtHeader64(Stream stream, IMAGE_DOS_HEADER dosHeader)
        {
            stream.Seek(dosHeader.e_lfanew, SeekOrigin.Begin);
            return ReadStructFromStream<IMAGE_NT_HEADERS64>(stream);
        }

        static T ReadStructFromStream<T>(Stream stream)
        {
            int structSize = Marshal.SizeOf(typeof(T));
            IntPtr memory = IntPtr.Zero;

            try
            {
                memory = Marshal.AllocCoTaskMem(structSize);
                if (memory == IntPtr.Zero)
                    throw new InvalidOperationException();

                byte[] buffer = new byte[structSize];
                int bytesRead = stream.Read(buffer, 0, structSize);
                if (bytesRead != structSize)
                    throw new InvalidOperationException();

                Marshal.Copy(buffer, 0, memory, structSize);

                return (T)Marshal.PtrToStructure(memory, typeof(T));
            }
            finally
            {
                if (memory != IntPtr.Zero)
                    Marshal.FreeCoTaskMem(memory);
            }
        }

        const ushort IMAGE_DOS_SIGNATURE = 0x5A4D;  // MZ
        const uint IMAGE_NT_SIGNATURE = 0x00004550; // PE00

        const ushort IMAGE_FILE_MACHINE_I386 = 0x014C;  // Intel 386
        const ushort IMAGE_FILE_MACHINE_IA64 = 0x0200;  // Intel 64
        const ushort IMAGE_FILE_MACHINE_AMD64 = 0x8664; // AMD64

        const ushort IMAGE_NT_OPTIONAL_HDR32_MAGIC = 0x10B; // PE32
        const ushort IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20B; // PE32+

        const ushort IMAGE_FILE_DLL = 0x2000;
    }

    class Program
    {
        static int Main(string[] args)
        {
            if (args.Length == 0)
            {
                Console.WriteLine("Please specify a file name to check.");
                return 1;
            }

            bool isValid = ExeChecker.IsValidExe(args[0]);
            Console.WriteLine(isValid);

            return 0;
        }
    }
}

11

这取决于你对“有效性”的定义。

  • 如果你想验证用户是否上传了一个“EXE”文件,请检查文件扩展名。
  • 如果你想验证用户是否上传了一个可运行的EXE文件(不考虑扩展名),请检查文件的前两个字节。它们应该包含值“MZ”。

5
对你而言,一个名为hello.exe且以MZ开头的文本文件就是可执行文件? - Konrad Rudolph
这是正确的答案,这是你能够做到最好的。 - NibblyPig
5
@Hosam Aly: 不行。唯一可靠的方法是检查文件中的每个字节,以查看它是否包含有效的操作码——或者至少,检查完整的EXE文件头和入口点定义的存在和一致性。你认为为什么WinAPI没有提供一种简单的方法来检查文件是否为有效的可执行文件?因为不存在简单的方法。 - Konrad Rudolph
@bniwredyc:MZ还是ZM?如果你从ZM开始,当你尝试启动程序时会出现“EXE文件错误”! - mox
当您想要验证一个包含MZ作为前两个字符的文本文件(非可执行文件)时会发生什么?也许我太傻了,认为这些实际上是文本文件中的前两个字节。只是好奇。 - w3bshark
显示剩余4条评论

9
bool IsExeFile(string path)
{
    var twoBytes = new byte[2];
    using(var fileStream = File.Open(path, FileMode.Open))
    {
        fileStream.Read(twoBytes, 0, 2);
    }

    return Encoding.UTF8.GetString(twoBytes) == "MZ";
}

2
@GSP:Windows可执行文件以字母MZ(十六进制为4D 5A)开头,这是文件格式设计者Mark Zbikowski的缩写。这是EXE文件的魔数。 - Anthony Faull
我看到了(M:77 = 0x4D,Z:90 = 0x5A)。谢谢! - GSP

7
一个非常原始的检查方法是检查文件扩展名:
Path.GetExtension(filename).Equals(".exe", 
    StringComparison.InvariantCultureIgnoreCase);

然而,Windows支持多种可执行文件扩展名(例如.cmd、.com、.cpl、.scr等等等),因此上述检查并不能覆盖所有可执行文件。
正如其他人所提到的,您还可以检查文件头中的魔数是否存在,例如MZ(以及一些其他更罕见的标志)。虽然第二个检查可以与检查扩展名一起使用,但您永远无法确定该文件不是一个简单的文本文件,意外地以相同的文本开头。
如果您要启动要检查的可执行文件,则最安全的方法可能是使用适当的异常处理来启动它。
const int ERROR_BAD_EXE_FORMAT = 193;
try
{
    ProcessStartInfo psi = new ProcessStartInfo();
    psi.UseShellExecute = false;
    psi.FileName = @"C:\tmp\testOLElocal_4.docx";
    Process.Start(psi);
}
catch (Win32Exception ex)
{
    if (ex.NativeErrorCode == ERROR_BAD_EXE_FORMAT)
    {
        // The exception message would be
        // "The specified executable is not a valid application for this OS platform."
        //
        Console.WriteLine("Not a valid executable.");
    }
    else
    {
        throw;
    }
}

注意:您没有提及有关自己应用程序的任何详细信息,但是每当执行来自用户输入的代码时,您应该确保您的用户是可信的。

4
尝试用手接触来测试是否会燃烧?你一定会得到一个答案,但我不确定这是否是原帖作者想要的;o) - Vinzz
如果我传递 destoroyeverything.exe 会怎样? - bniwredyc
1
@Vinzz,当然你可以始终检查文件扩展名和魔数,但要确保文件实际上执行,你必须尝试一下。如果应用程序无论如何都会启动该文件,则适当的异常处理是正确的方法。 - Dirk Vollmar
2
@Vinzz:这可能是可以给出的最可靠的答案。即使不是,它肯定是最可靠的答案,而无需实现机器码反汇编器来检查“exe”中的每个操作码是否有效。 - Konrad Rudolph
1
@bniwredyc:安全性是一个完全不同的问题。但是,除非应用程序在不同的安全上下文中执行,否则我不认为将用户可以随意启动的应用程序传递给它会太重要。 - Dirk Vollmar

2
您可以使用PE格式DLL检查文件是否为有效的PE格式文件。
PE文件不仅包含可执行代码,还可以包含资源和代码,或者只包含资源而没有代码。PE文件也可以是本机或托管的,或者是本机但链接到托管代码等。根据您想要做什么,能够检查这些内容将会非常有用。
PE_EXE3 pef;
int     ok = FALSE;

if (pef.openFile(fileNameAndPath))
{
    if (pef.IsValid())
    {
        ok = pef.GetHasExecutableCode();
    }
    pef.closeFile();
}

您可能也会发现以下功能很有用:
pef.GetIsOnlyResource()
pef.GetHasExecutableCode()
pef.GetIsPureDotNetModule()

0
如果您只想检查文件扩展名:
if (String.Equals(Path.GetExtension(filePath), ".exe", StringComparison.InvariantCultureIgnoreCase))
{
    ...
}

如果您需要验证文件是否真正可执行,可以分析其头部。有关.exe文件头的信息,请参见link
您还可以尝试按照0xA3的建议运行该文件。

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