Solved

Examining PE header with mapped file

Posted on 2006-07-23
9
503 Views
Last Modified: 2010-04-05
I have a DLL that maps a file and another DLL that returns information about the mapped file.  The problem is that the function which examines the PE header faults after a few hundred files.  I have not been able to find a handle leak.  Here is the source code:

library ms0001;

{$E dll}
{$R *.res}

uses
  windows;

function tc_openfileview(name: pchar; size: cardinal): pointer; stdcall; export;
var hmap: thandle;
    fh: thandle;
begin
  fh:=createfile(name, GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, 0, 0);

  if (fh > 0) then
    begin
      hmap:=createfilemapping(fh, nil, PAGE_READONLY, 0, 0, 'TC_FILE_MAP');
      closehandle(fh);

      if (hmap > 0) then
        result:=mapviewoffile(hmap, FILE_MAP_READ, 0, 0, size)
         else
        result:=nil;
      closehandle(hmap);
    end
     else
    result:=nil;
end;

function tc_closefileview(map: pointer): boolean; stdcall; export;
begin
  result:=unmapviewoffile(map);
end;

exports tc_openfileview, tc_closefileview;

begin
end.


library ms0003;

{$E dll}
{$R *.res}

uses
  windows;

function CheckPEFile(AData:PByte):Boolean; stdcall; export;
//return True if AData points on valid image file
var
 LPNtHdr:PImageNtHeaders;
begin
 Result:=False;
 try
  if PImageDosHeader(AData)^.e_magic<>PWord(PChar('MZ'))^ then Exit;

  LPNtHdr:=Pointer(Cardinal(AData)+Cardinal(PImageDosHeader(AData)^._lfanew));

  if LPNtHdr^.Signature<>PCardinal(PChar('PE'))^ then Exit;       //seems to always fault here

  if LPNtHdr^.FileHeader.Machine<>IMAGE_FILE_MACHINE_I386 then Exit;
  if LPNtHdr^.OptionalHeader.Magic<>IMAGE_NT_OPTIONAL_HDR_MAGIC then Exit;
  Result:=True;
 except
 end;
end;

exports checkpefile;

begin
end.

The main program iterates all files on drive C:\ and passes them to openfileview(filename, 4096) and then uses the returned pointer to pass to CheckPEFile

0
Comment
Question by:DSOM
  • 2
  • 2
  • 2
  • +2
9 Comments
 
LVL 33

Expert Comment

by:Slick812
ID: 17165249
hello DSOM, First, I will say that I used to do Mem mapped files the way you do in your code above, as I would Open a File with CreateFile( ) and then use CreateFileMapping(  ) to get a map handle and then imediately close the file handle with CloseHandle( ), then I would get the map pointer with MapViewOfFile(  ) and imediately close the file map handle with CloseHandle(hmap), , and this would work most of the time, BUT I had some problems, sometimes Inconsistant, so now I DO NOT close the file handle or the map Handle untile AFTER I call UnMapViewOfFile(  ), so the sequence I use now is

UnMapViewOfFile(pMap);
CloseHandle(hMap);
CloseHandle(hFile);

you may can use a record with the three open values in it ? ?

BUT this may not solve your problem, your way seems to work most of the time. . .

OK Next, I think you may have memory reading problems with the pointer assignmemt in -
 if LPNtHdr^.Signature<>PCardinal(PChar('PE'))^ then Exit;

the   PCardinal(PChar('PE'))^   is a 4 Byte assignment  PCardinal^  of a 3 byte memory block PChar('PE')^ you may want to "Pad" the PChar mem block to make it 4 bytes  PChar('PEX') , it has a null for the last byte    OR change it to  PWord(PChar('PE'))^ for a 2 Byte read.
But I did not try this so I am not sure about it as a problem solve
0
 

Author Comment

by:DSOM
ID: 17165388
I tried both suggestions but it still faults in the same place.  It's very frustrating.  It's always an access violation on reading the address.  Perhaps the mapped file view needs to be locked somehow.  I'll have to look into that.
0
 
LVL 12

Expert Comment

by:AmigoJack
ID: 17165922
unreasonable behaviour of major projects (or calculations) took me to the following: check project options - turn off code optimization and also turn off any compiler or syntax helping, turn on more strict rules. this might give you a few more compiler warnings or even errors, because standard settings are set for quite laidback programming. ok, this was under delphi 5, dont know which version youre using
0
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 17167023
Filehandles can be negative values but you check for 'if (fh > 0) then' meaning that you might be forgetting to close a few files.
Another problem could be that you're trying to examine a file that is smaller than the size you provided. There are a few such executables in the C:\Windows\System32 folder, which are basically some of the old MS-DOS commands like fastopen.exe and share.exe. Opening those files with a size of 4096 might of course lead to nasty problems.
0
Find Ransomware Secrets With All-Source Analysis

Ransomware has become a major concern for organizations; its prevalence has grown due to past successes achieved by threat actors. While each ransomware variant is different, we’ve seen some common tactics and trends used among the authors of the malware.

 
LVL 12

Expert Comment

by:AmigoJack
ID: 17168470
negative? Cardinal = DWord = THandle = HWND are 4 bytes unsigned. but checking for > 0 is indeed incorrect. the checking should be

if fh<> INVALID_FILE_HANDLE

for 99% i swear that createfile() returns MAX_DWORD when an error occurs instead of 0. see win32.hlp for more
0
 

Author Comment

by:DSOM
ID: 17168559
I am not opening the file but mapping it and the 4096 is a maximum value not an absolute.  A zero-byte file gives a nil value and a 1 byte file gives you 1 byte.

I tried with if (fh <> INVALID_HANDLE_VALUE) then instead of > 0 and it didn't change anything.

I am using Borland Developer Studio 2006

All the project options are already set to to strict rules and no optimiztions.
0
 
LVL 26

Accepted Solution

by:
Russell Libby earned 500 total points
ID: 17168784
You may want to try with something along the lines of this (tested on 160,000 files), which is MUCH safer checking wise than the code you have posted above. (you could also pull the code from TPECheck.Check to update your routine).

Regards,
Russell

----

--- utility unit PECheck ----
unit PECheck;

interface

uses
  Windows, SysUtils;

type
  TPECheck          =  class(TObject)
  private
     // Private declarations
     FFileName:     String;
     FFile:         THandle;
     FMapping:      THandle;
     FMemory:       Pointer;
  protected
     // Protected declarations
     procedure      Clear;
     function       GetIsOpen: Boolean;
     function       GetIsMapped: Boolean;
     procedure      SetFileName(Value: String);
  public
     // Public declarations
     constructor    Create;
     destructor     Destroy; override;
     function       Check: Boolean;
     property       FileName: String read FFileName write SetFileName;
     property       IsOpen: Boolean read GetIsOpen;
     property       IsMapped: Boolean read GetIsMapped;
  end;

implementation

function TPECheck.Check: Boolean;
var  lpDosHeader:   PImageDosHeader;
     lpNtHeader:    PImageNtHeaders;
     dwSize:        DWORD;
begin

  // Check memory
  if not(FFile = INVALID_HANDLE_VALUE) and Assigned(FMemory) then
  begin
     // Check file size first
     dwSize:=GetFileSize(FFile, nil);
     // Check return
     if (dwSize = $FFFFFFFF) or (dwSize < SizeOf(TImageDosHeader)) then
        // Failed to get size, or file is not large enough to be an exe
        result:=False
     else
     begin
        // Cast as DOS header
        lpDosHeader:=FMemory;
        // Check magic
        if (lpDosHeader^.e_magic = IMAGE_DOS_SIGNATURE) then
        begin
           // The file size must be at least as large as _lfanew, which is pointer to
           // new header plus the size of the new header.
           if (dwSize < DWORD((lpDosHeader^._lfaNew) + SizeOf(TImageNtHeaders))) then
              // Not valid
              result:=False
           else
           begin
              // Get NT header
              lpNtHeader:=Pointer(PChar(FMemory) + lpDosHeader^._lfaNew);
              // Check for valid PE signature
              result:=(lpNtHeader^.Signature = IMAGE_NT_SIGNATURE) and
                      (lpNtHeader^.FileHeader.Machine = IMAGE_FILE_MACHINE_I386) and
                      (lpNtHeader^.OptionalHeader.Magic = IMAGE_NT_OPTIONAL_HDR_MAGIC);
           end;
        end
        else
           // Not an exe image
           result:=False;
     end;
  end
  else
     // Not mapped
     result:=False;

end;

function TPECheck.GetIsOpen: Boolean;
begin

  // Is file open
  result:=not(FFile = INVALID_HANDLE_VALUE);

end;

function TPECheck.GetIsMapped: Boolean;
begin

  // Is file mapped?
  result:=Assigned(FMemory);

end;

procedure TPECheck.SetFileName(Value: String);
begin

  // Check against current file name
  if not(CompareText(Value, FFileName) = 0) then
  begin
     // Resource protection
     try
        // Clear current file handle and mapping
        Clear;
        // Check new file name
        if (Length(Value) > 0) then
        begin
           // Open the file for reading
           FFile:=CreateFile(PChar(Value), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, 0, 0);
           // Check handle
           if not(FFile = INVALID_HANDLE_VALUE) then
           begin
              // Create the file mapping
              FMapping:=CreateFileMapping(FFile, nil, PAGE_READONLY or SEC_COMMIT, 0, 0, nil);
              // Check mapping
              if not(FMapping = 0) then
              begin
                 // Map view of file
                 FMemory:=MapViewOfFile(FMapping, FILE_MAP_READ, 0, 0, 0);
              end;
           end;
        end;
     finally
        // Set new file name
        FFileName:=Value;
     end;
  end;

end;

procedure TPECheck.Clear;
begin

  // Resource protection
  try
     // Check file handle
     if not(FFile = INVALID_HANDLE_VALUE) then
     begin
        // Resource protection
        try
           // Check mapping handle
           if not(FMapping = 0) then
           begin
              // Resource protection
              try
                 // Check memory, unmap view of file
                 if Assigned(FMemory) then UnmapViewOfFile(FMemory);
              finally
                 // Close mapping handle
                 CloseHandle(FMapping);
              end;
           end;
        finally
           // Close file handle
           CloseHandle(FFile);
        end;
     end;
  finally
     // Clear values
     FFile:=INVALID_HANDLE_VALUE;
     FMapping:=0;
     FMemory:=nil;
  end;

end;

constructor TPECheck.Create;
begin

  // Perform inherited
  inherited Create;

  // Set initial default values
  SetLength(FFileName, 0);
  FFile:=INVALID_HANDLE_VALUE;
  FMapping:=0;
  FMemory:=nil;

end;

destructor TPECheck.Destroy;
begin

  // Resource protection
  try
     // Clear clear
     Clear;
  finally
     // Perform inherited
     inherited Destroy;
  end;

end;

end.


-- example usage --

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  PECheck, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation
{$R *.DFM}

procedure RecursePath(PeCheck: TPECheck; Path: String);
var  srFind:        TSearchRec;
     dwFind:        Integer;
begin

  // Init the find
  dwFind:=FindFirst(Path+'\*.*', faAnyFile, srFind);

  // Check find
  if (dwFind = 0) then
  begin
     // Resource protection
     try
        // While sucess
        while (dwFind = 0) do
        begin
           // Check name
           if (Length(srFind.Name) > 0) then
           begin
              // Check path
              if ((srFind.Attr and faDirectory) = faDirectory) then
              begin
                 // Check for . and ..
                 if not((srFind.Name = '.') or (srFind.Name = '..')) then
                 begin
                    // Recurse all files in folder
                    RecursePath(PeCheck, Path+'\'+srFind.Name);
                 end;
              end
              // Check the file name
              else
              begin
                 // Check the file
                 PeCheck.FileName:=Path+'\'+srFind.Name;
                 PeCheck.Check;
              end;
           end;
           // Find next
           dwFind:=FindNext(srFind);
        end;
     finally
        // Close the find
        FindClose(srFind);
     end;
  end;

end;

procedure CheckFiles(Path: String);
var  objPeCheck:    TPECheck;
     srFind:        TSearchRec;
     dwFind:        Integer;
     szPath:        String;
begin


  // Make sure path is correct
  szPath:=ExcludeTrailingBackSlash(Path);

  // Create PE checker object
  objPeCheck:=TPECheck.Create;

  // Resource protection
  try
     // Init the find
     dwFind:=FindFirst(szPath+'\*.*', faAnyFile, srFind);
     // Check find
     if (dwFind = 0) then
     begin
        // Resource protection
        try
           // While sucess
           while (dwFind = 0) do
           begin
              // Check name
              if (Length(srFind.Name) > 0) then
              begin
                 // Check path
                 if ((srFind.Attr and faDirectory) = faDirectory) then
                 begin
                    // Check for . and ..
                    if not((srFind.Name = '.') or (srFind.Name = '..')) then
                    begin
                       // Recurse all files in folder
                       RecursePath(objPeCheck, szPath+'\'+srFind.Name);
                    end;
                 end
                 // Check the file name
                 else
                 begin
                    // Check the file
                    objPeCheck.FileName:=szPath+'\'+srFind.Name;
                    objPeCheck.Check;
                 end;
              end;
              // Find next
              dwFind:=FindNext(srFind);
           end;
        finally
           // Close the find
           FindClose(srFind);
        end;
     end;
  finally
     // Free PE checker
     objPeCheck.Free;
  end;

end;


procedure TForm1.Button1Click(Sender: TObject);
begin

  CheckFiles('c:\');

end;

end.
0
 
LVL 33

Expert Comment

by:Slick812
ID: 17171464
Just as a suggestion, If your problem seems to be only with the file mapping, (not with opening the files with CreateFile), You can skip the mapping of the file, and just use your own Memory block, , I beleive the system will just create a system mem block and read the file comtents into it for a mam map fie, , You can just have the function   GetMem, or other memory allocation, and then just read the file into the mem block, then close the file handle. and do your PE analisis on your mem block, , go chance for map file to screw it up
0
 
LVL 26

Expert Comment

by:Russell Libby
ID: 17171576

That may speed things up, but the size of the data read into the memory block needs to be tracked, *period*. This is where the true problems lay:

For example:

>>  if PImageDosHeader(AData)^.e_magic<>PWord(PChar('MZ'))^ then Exit;
What if memory is only mapped to 1 byte?

>>  LPNtHdr:=Pointer(Cardinal(AData)+Cardinal(PImageDosHeader(AData)^._lfanew));
No check done to ensure the memory size >= size of TImageDosHeader. If the file just happens to start with MZ, then your in trouble. No validation done to ensure that the memory size is at least _lfanew bytes long + size of the nt header.

>>  if LPNtHdr^.Signature<>PCardinal(PChar('PE'))^ then Exit;      
Bad way to check memory, as the static PChar 'PE'#0 only occupies 3 bytes, but is typecast as ptr to a 4 byte struct

If a file starts with 'MZ' then the show is pretty much over because (a), the memory block wont be large enough to satisfy the dos header struct size, or (b) will be large enough, but god knows what the memory at _lfanew will translate into integer-wise.

Russell




0

Featured Post

How to improve team productivity

Quip adds documents, spreadsheets, and tasklists to your Slack experience
- Elevate ideas to Quip docs
- Share Quip docs in Slack
- Get notified of changes to your docs
- Available on iOS/Android/Desktop/Web
- Online/Offline

Join & Write a Comment

Hello everybody This Article will show you how to validate number with TEdit control, What's the TEdit control? TEdit is a standard Windows edit control on a form, it allows to user to write, read and copy/paste single line of text. Usua…
Introduction I have seen many questions in this Delphi topic area where queries in threads are needed or suggested. I know bumped into a similar need. This article will address some of the concepts when dealing with a multithreaded delphi database…
Internet Business Fax to Email Made Easy - With eFax Corporate (http://www.enterprise.efax.com), you'll receive a dedicated online fax number, which is used the same way as a typical analog fax number. You'll receive secure faxes in your email, fr…
This video explains how to create simple products associated to Magento configurable product and offers fast way of their generation with Store Manager for Magento tool.

757 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

20 Experts available now in Live!

Get 1:1 Help Now