Last active
November 15, 2022 10:50
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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. |
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.
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
This Gist provides a version of
ExeType
that can detect 64 bit executable files. It is linked from article #8 on DelphiDabbler.com.