Skip to content

Instantly share code, notes, and snippets.

@delphidabbler
Last active November 15, 2022 10:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save delphidabbler/1b90a542f94ae686509e285805a31665 to your computer and use it in GitHub Desktop.
Save delphidabbler/1b90a542f94ae686509e285805a31665 to your computer and use it in GitHub Desktop.
Delphi Pascal function and type that can detect if a file is a DOS or Windows executable and determine its type.
// MIT License https://delphidabbler.mit-license.org/2022-
// Copyright (c) 2022, Peter D Johnson (https://gravatar.com/delphidabbler)
unit UExeType;
interface
type
TExeType = (
etUnknown, // unknown file kind: not an executable
etError, // error file kind: used for files that don't exist
etDOS, // DOS executable
etExe32, // 32 bit executable
etExe16, // 16 bit executable
etDLL32, // 32 bit DLL
etDLL16, // 16 bit DLL
etVXD, // virtual device driver
etExe64, // 64 bit executable
etDLL64, // 64 bit DLL
etROM // ROM image (PE format)
);
function ExeType(const FileName: string): TExeType;
implementation
uses
{$IF CompilerVersion >= 23.0} // Delphi XE2 and later
WinApi.Windows, System.SysUtils, System.Classes;
{$ELSE}
Windows, SysUtils, Classes,
{$ENDIF}
function ExeType(const FileName: string): TExeType;
const
cWinHeaderOffset = $3C; // offset of "pointer" to windows header in file
cNEAppTypeOffset = $0D; // offset in NE windows header app type field
cDOSMagic = $5A4D; // magic number of a DOS executable
cNEMagic = $454E; // magic number of a NE executable (Win 16)
cPEMagic = $4550; // magic nunber of a PE/PE+ executable (Win 32/64)
cLEMagic = $454C; // magic number of a Virtual Device Driver
cNEDLLFlag = $80; // flag in NE app type field indicating a DLL
cPEDLLFlag = $2000; // flag in PE Characteristics field indicating s DLL
cPE32Magic = $10B; // magic number of a 32 bit PE executable
cPE64Magic = $20B; // magic number of a 64 bit executable
cPEROMMagic = $107; // magic number of a ROM image
var
FS: TFileStream; // stream onto executable file
WinMagic: Word; // word that contains PE/NE/LE magic #
WinMagicEx: Word; // additional magic # for PE exec file
HdrOffset: LongWord; // offset of windows header in exec file
DOSHeader: IMAGE_DOS_HEADER; // DOS header record
PEFileHdr: IMAGE_FILE_HEADER; // PE file header record
PEOptHdrMagic: Word; // PE "optional" header magic #
AppFlagsNE: Byte; // byte defining DLLs in NE format
DOSFileSize: Integer; // size of DOS file
IsPEDLL: Boolean; // whether PE file is DLL
begin
try
// Open stream onto file: raises exception if can't be read
FS := TFileStream.Create(FileName, fmOpenRead + fmShareDenyNone);
try
// Assume unkown file type
Result := etUnknown;
// Any exec file is at least size of DOS header long
if FS.Read(DOSHeader, SizeOf(DOSHeader)) < SizeOf(DOSHeader) then
Exit;
// DOS files begin with "MZ"
if DOSHeader.e_magic <> cDOSMagic then
Exit;
// DOS files have length >= size indicated at by DOS header's
// e_cblp and e_cp fields. e_cblp stores the number of 512 byte
// pages in the file. e_cp stores the number of bytes used in the
// last page of the file.
DOSFileSize := (DOSHeader.e_cp - 1) * 512 + DOSHeader.e_cblp;
if FS.Size < DOSFileSize then
Exit;
// DOS file relocation offset must be within DOS file size.
if DOSHeader.e_lfarlc > DOSFileSize then
Exit;
// We know we have an executable file: assume its a DOS program
Result := etDOS;
// Try to find offset of Windows program header
if FS.Size <= cWinHeaderOffset + SizeOf(LongWord) then
// file too small for windows header "pointer": it's a DOS file
Exit;
// read it
FS.Position := cWinHeaderOffset;
FS.ReadBuffer(HdrOffset, SizeOf(LongInt));
// Now try to read first word of Windows program header
if FS.Size <= HdrOffset + SizeOf(Word) then
// file too small to contain header: it's a DOS file
Exit;
FS.Position := HdrOffset;
// This word should be NE, PE or LE per file type: check which
FS.ReadBuffer(WinMagic, SizeOf(Word));
case WinMagic of
cPEMagic:
begin
// 'PE' signature is followed by a zero word
if FS.Read(WinMagicEx, SizeOf(WinMagicEx)) < SizeOf(WinMagicEx) then
Exit;
if WinMagicEx <> 0 then
Exit;
// 32 or 64 bit Windows application: now check whether app or DLL
// by reading file header record and checking Characteristics field
if FS.Read(PEFileHdr, SizeOf(PEFileHdr)) < SizeOf(PEFileHdr) then
Exit;
IsPEDLL := (PEFileHdr.Characteristics and cPEDLLFlag)
= cPEDLLFlag;
// PE & PE+ format file must have "optional" header whose size is
// given by file header record's SizeOfOptionalHeader field, which
// must be non-zero & file must be large enough to accommodate it
if PEFileHdr.SizeOfOptionalHeader = 0 then
Exit;
if FS.Size < HdrOffset + SizeOf(WinMagic) + SizeOf(WinMagicEx)
+ PEFileHdr.SizeOfOptionalHeader then
Exit;
// check if 32 bit, 64 bit (or ROM) by reading Word value following
// file header (actually this is first field of "optional" PE header)
// read magic number at start of "optional" PE header that follows
if FS.Read(PEOptHdrMagic, SizeOf(PEOptHdrMagic))
< SizeOf(PEOptHdrMagic) then
Exit;
case PEOptHdrMagic of
cPE32Magic:
if IsPEDLL then
Result := etDLL32
else
Result := etExe32;
cPE64Magic:
if IsPEDLL then
Result := etDLL64
else
Result := etExe64;
cPEROMMagic:
Result := etROM;
else
Result := etUnknown; // unknown PE magic number
end;
end;
cNEMagic:
begin
// We have 16 bit Windows executable: check whether app or DLL
if FS.Size <= HdrOffset + cNEAppTypeOffset + SizeOf(AppFlagsNE) then
// app flags field would be beyond EOF: assume DOS
Exit;
// read app flags byte
FS.Position := HdrOffset + cNEAppTypeOffset;
FS.ReadBuffer(AppFlagsNE, SizeOf(AppFlagsNE));
if (AppFlagsNE and cNEDLLFlag) = cNEDLLFlag then
// app flags indicate DLL
Result := etDLL16
else
// app flags indicate program
Result := etExe16;
end;
cLEMagic:
// We have a Virtual Device Driver
Result := etVXD;
else
// DOS application
{Do nothing - DOS result already set};
end;
finally
FS.Free;
end;
except
// Exception raised in function => error result
Result := etError;
end;
end;
end.
@delphidabbler
Copy link
Author

This Gist provides a version of ExeType that can detect 64 bit executable files. It is linked from article #8 on DelphiDabbler.com.

@delphidabbler
Copy link
Author

Changing to use TStream.Read and checking read vs expected bytes should lead to clearer, more concise code than repeatedly checking TStream.Size before calling TStream.ReadBuffer.

Then will only need to check size before setting stream position.

@delphidabbler
Copy link
Author

Added a new check for file being large enough to store optional PE header size

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment