17

How can I be sure that a file passed to my program is a valid exe file ?

actually my program takes a file as input and runs it, but user can input any file so I have to make sure that the input is a valid exe.

John Saunders
  • 160,644
  • 26
  • 247
  • 397
Embedd_0913
  • 16,125
  • 37
  • 97
  • 135
  • For any exe, or just .NET exe's ? – leppie May 19 '10 at 07:50
  • 2
    there are several valid executable file formats that can be sussed fairly easily ( i will leave to my betters to enumerate) but the question I have is 'have you lost your mind?' ;-) – Sky Sanders May 19 '10 at 07:51
  • "Exe file" is a wide term... If you read first two bytes in file, you can know that it is EXE if it starts with `MZ` (DOS 32bit EXE, or Windows PE EXE), but you never can be sure until you try to run it. – Cipi May 19 '10 at 07:52
  • 1
    @Cipi: DLL's contain that signature too. I think all PE files do. – leppie May 19 '10 at 07:54
  • Why not just try to run it and see if it fails ? – Cédric Rup May 19 '10 at 08:01
  • @Praveen: 1) Since "C#" is already in the tags, it doesn't need to be in the title. 2) "Hi", and "thanks" do not belong in questions. – John Saunders May 19 '10 at 08:01
  • 1
    I sure hope this program isn't a web site! Is you security system good enough and do you trust your users well enough to run arbitrary EXEs? – Daniel Renshaw May 19 '10 at 08:06
  • @John: While I understand your reasoning re #2, I find it hard to fault someone for extending a natural courtesy to those helping out. – Ben Zotto May 19 '10 at 08:06
  • 1
    @quixoto: what "fault" are you talking about? I informed him of the standard here, which differs from the standard used in newsgroups or other web forums. I didn't downvote or anything. – John Saunders May 19 '10 at 08:14
  • @John: Sorry, "fault" was the wrong word to use. Just noting that it doesn't seem problematic to me to use (brief) pleasantries. Makes me more likely to help someone out, since behind the questions and answers are real people. :) For what it's worth, I don't see mention of any explicit standard about this in the site FAQs. (Could well be that I've not been paying close enough attention.) – Ben Zotto May 19 '10 at 08:23
  • See http://meta.stackexchange.com/questions/5029/are-taglines-signatures-disallowed and http://meta.stackexchange.com/questions/2950/should-hi-thanks-and-taglines-and-salutations-be-removed-from-posts. – John Saunders May 19 '10 at 08:32
  • @leppie: EXEs can be distinguished from DLLs by looking at the PE file headers http://support.microsoft.com/kb/65122 – Sandeep Datta May 19 '10 at 11:07
  • 1
    @Praveen: please see my answer below which is more in-depth than checking the file extension or leading magic bytes, but does not require you to actually try to execute the file. It uses knowledge of the PE format to do multiple verifications. – Chris Schmich May 19 '10 at 11:25

6 Answers6

25

If you want something more in-depth than "does the filename end in '.exe'?" but you don't want to actually have to run the program, you can check for the existence and validity of the PE headers. Additionally, checking the leading 2 bytes ("MZ" for PE files) will return true for DLLs as well. If you don't want that, you can try this approach.

Matt Pietrek has written a couple of great articles describing the PE format:

The two important data structures here are IMAGE_DOS_HEADER and IMAGE_NT_HEADERS32/IMAGE_NT_HEADERS64. These structures are defined in winnt.h in the Windows SDK. Many of these PE structures are described here.

You can work with the PE headers using managed code (similar to this approach). The following code returns true for 32- (i386) and 64-bit (IA64, AMD64) .exe PE files (e.g. returns false for DLLs). See the bottom for the usage (ExeChecker.IsValidExe). If desired, you can add additional checks to support more architectures or to do more validation. See winnt.h for more constants.

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;
        }
    }
}
Chris Schmich
  • 29,128
  • 5
  • 77
  • 94
11

It depends on your definition of "validity".

  • If you want to validate that the user passes an "EXE" file, check the file extension.
  • If you want to validate that the user passes a runnable EXE file (regardless of extension), check the first two bytes of the file. They should contain the value "MZ".
Hosam Aly
  • 41,555
  • 36
  • 141
  • 182
  • 5
    So for you a text file that happens to be called `hello.exe` and starts with `MZ` is an executable file? – Konrad Rudolph May 19 '10 at 08:05
  • This is the correct answer, the best you're going to be able to do. – NibblyPig May 19 '10 at 08:08
  • @Konrad: I think the only way to know for real is to validate the checksum of the file at the offset 12h, but again, there is always a chance (however minimal it may be) that the value is coincidentally correct. – Hosam Aly May 19 '10 at 08:16
  • Konrad is perfectly right, and in addition your approach would also classify any dll as executable, but an executable also requires that there is an entry point, i.e. a main method to start execution with. Another point is (although it is not clear if the OP needs that), that an executable does not necessarily need a .exe extension. There are also other valid extensions for executable files in Windows (e.g. .com, and many more, see http://antivirus.about.com/od/securitytips/a/fileextview.htm). – Dirk Vollmar May 19 '10 at 08:18
  • 5
    @Hosam Aly: No. The only reliable way is to check every byte in the file to see if it carries a valid opcode – or at the very least, check for the presence and consistency of the *complete* EXE header and entry point definition. Why do you think doesn’t the WinAPI provide an easy way to check whether a file is a valid executable? Because no easy way exists. – Konrad Rudolph May 19 '10 at 08:21
  • @0xA3, @Konrad: I agree, but I don't think the OP really cares about COM files, and it really isn't easy to check the complete header and entry point. The OP is probably asking for a much simpler approach, considering his profile. I believe he could start with the simple checks I suggested, and then use the way suggested by 0xA3. – Hosam Aly May 19 '10 at 08:27
  • @Hosam Aly: True, a combined solution won’t hurt, if only because this check may be more efficient than triggering a Win32 exception. – Konrad Rudolph May 19 '10 at 08:41
  • @bniwredyc: MZ or ZM? When starting with ZM, you'll get "Error in EXE file" when you try to start the program! – mox Oct 11 '11 at 07:42
  • What happens when you want to verify a text file (non-executable) that contains MZ as the first two characters? Maybe I'm dumb in assuming that these would actually be the first two bytes in the text file. Just curious. – w3bshark Nov 05 '14 at 18:53
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";
}
Anthony Faull
  • 17,549
  • 5
  • 55
  • 73
  • 2
    @GSP: Windows executables begin with the letters MZ (that's 4D 5A in hexadecimal) which are the initials of Mark Zbikowski who designed the file format. This is the magic number for EXE files. – Anthony Faull Oct 11 '16 at 07:57
  • I see (M: 77=0x4D, Z: 90=0x5A). Thanks! – GSP Oct 11 '16 at 10:35
7

A very primitive check would be to check for the file extension:

Path.GetExtension(filename).Equals(".exe", 
    StringComparison.InvariantCultureIgnoreCase);

However, Windows supports a variety of extensions for executable files (e.g. .cmd, .com, .cpl, .scr and many more), so the check above would not cover all executable files.

As mentioned by others, you can also check the magic numbers in the file header for the existence of e.g. MZ (and some other more rare signatures). This second check could be used in addition to checking the extension(s) though you can never be certain that the file is not a simple text file accidentally starting with the same text.

If you are going to start the executable to be checked anyway, then it is probably safest to simply start it with proper exception handling:

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

NB: You didn't mention any details about your own application, but whenever executing code which comes via user input you should make sure that your user can be trusted.

Dirk Vollmar
  • 172,527
  • 53
  • 255
  • 316
  • 4
    Test if it burns by putting your hand within? you'll sure have an answer, but I'm not sure it's really what's the OP had in mind ;o) – Vinzz May 19 '10 at 08:02
  • What if I pass destoroyeverything.exe? – bniwredyc May 19 '10 at 08:06
  • 1
    @Vinzz, of course you can always check file extensions and magic numbers, btu to be definitely certain that the file actually executes you would have to try. And in case the application would start the file anyway proper exception handling would be the way to go. – Dirk Vollmar May 19 '10 at 08:06
  • 2
    @Vinzz: This is probably the most reliable answer that *can* be given. And even if not, it is certainly the most reliable answer that works without implementing machine code disassembler that checks whether each opcode in the “exe” is valid. – Konrad Rudolph May 19 '10 at 08:06
  • 1
    @bniwredyc: Security is a totally different question. But I would not consider passing in an application that the user can start anyway to be too critical unless the application is executed within a different security context. – Dirk Vollmar May 19 '10 at 08:08
2

You can check if a file is a valid PE format file using the PE Format DLL.

PE files can contain more than just executable code. The can contain resources as well as code, or just resources and no code. PE files can also be native or managed, or native but linked to managed code etc. Depending on what you want to do, being able to check for these things would be useful.

PE_EXE3 pef;
int     ok = FALSE;

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

You may also find the following functions useful:

pef.GetIsOnlyResource()
pef.GetHasExecutableCode()
pef.GetIsPureDotNetModule()
Stephen Kellett
  • 3,078
  • 1
  • 22
  • 25
0

If you want to just check the file extension:

if (String.Equals(Path.GetExtension(filePath), ".exe", StringComparison.InvariantCultureIgnoreCase))
{
    ...
}

If you need to verify that the file is really executable you can analyze its header. There is information about .exe file header: link.

You also can try to run the file as 0xA3 suggested

Community
  • 1
  • 1
bniwredyc
  • 8,649
  • 1
  • 39
  • 52