Solved

Examining PE header with mapped file

Posted on 2006-07-23
9
513 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
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 2
  • 2
  • 2
  • +2
9 Comments
 
LVL 34

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
Independent Software Vendors: We Want Your Opinion

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
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
 
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 34

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

Free Tool: ZipGrep

ZipGrep is a utility that can list and search zip (.war, .ear, .jar, etc) archives for text patterns, without the need to extract the archive's contents.

One of a set of tools we're offering as a way to say thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

The uses clause is one of those things that just tends to grow and grow. Most of the time this is in the main form, as it's from this form that all others are called. If you have a big application (including many forms), the uses clause in the in…
Introduction The parallel port is a very commonly known port, it was widely used to connect a printer to the PC, if you look at the back of your computer, for those who don't have newer computers, there will be a port with 25 pins and a small print…
Add bar graphs to Access queries using Unicode block characters. Graphs appear on every record in the color you want. Give life to numbers. Hopes this gives you ideas on visualizing your data in new ways ~ Create a calculated field in a query: …
Visualize your data even better in Access queries. Given a date and a value, this lesson shows how to compare that value with the previous value, calculate the difference, and display a circle if the value is the same, an up triangle if it increased…
Suggested Courses
Course of the Month11 days, 15 hours left to enroll

623 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