Recent

Author Topic: Getting File Size from PE Header Fails with Lazarus  (Read 11674 times)

msintle

  • Sr. Member
  • ****
  • Posts: 379
Re: Getting File Size from PE Header Fails with Lazarus
« Reply #15 on: October 25, 2023, 04:03:13 pm »
To clarify - is it true (or not) that Lazarus adds debug information to the tail end of an executable in non-standard form?
It's important to realize there is more than one type of debug information.

What is now considered debug information is DWARF or stabs.  I'll mention specifics about DWARF because those are fresh in my mind.  In the case of DWARF, every "part" of the DWARF debug info is in a PE section, therefore the debug info is part of the PE file and, as a result, that information will be included in the PE size.

If I remember correctly, stabs, which is another type of debugging information, is also stored in a PE section.  It would be good if someone confirmed this for stabs because it's been a while since I dealt with it and my recollection might not be right.

The other type of debug information, is the COFF symbol table, what has been referred to as "overlay" in this thread.  That information, unlike DWARF, is NOT stored in PE sections therefore it can be considered as not being part of the executable.   In the case of Windows, there is the "Pointer to symbol table" to locate it in the program file. 

As far as standard/non-standard... first, it isn't Lazarus that adds debug information, it is FPC that puts it there (regardless of the type of debug information), second, at least in the case of Windows, the "standard" way to store the COFF symbol table is to append it to what is PE information proper (what is in sections) and update the "pointer to symbol table" to enable locating it.  In Windows, that's pretty much the standard way. 

An alternative would be to create a debug directory entry (which is what Delphi does for its debug information) but, that seems redundant for COFF symbols given that the "pointer to symbol table" is there for that.

HTH.

That's very helpful indeed. Is there a way to obtain the size of this COFF table from its symbol table pointer, perhaps? Such that the code at the start of the thread may be updated to account for this extra data and made foolproof by so doing?

440bx

  • Hero Member
  • *****
  • Posts: 6556
Re: Getting File Size from PE Header Fails with Lazarus
« Reply #16 on: October 25, 2023, 05:03:10 pm »
Is there a way to obtain the size of this COFF table from its symbol table pointer, perhaps?
Yes, there is. 

The "pointer to symbol table" allows you to locate the start of the COFF symbol table.  There is a field in the optional header that specifies the number of entries in that table.

The string table follows the COFF symbol entries, therefore, the string table is located at "Location of COFF table" + (size of COFF entry * number of entries.)   At that location, there is a DWORD that gives the size of the entire string table (I believe including the DWORD itself).  The string table is just a bunch of null terminated c strings one after another. 

NOTE: the size of each COFF symbol entry is 18 bytes or, what is the same sizeof(TIMAGE_SYMBOL) found in the definitions below.

you'll need some data types... (to have and use their sizes) the following are the necessary types for the calculations I mentioned above:
Code: Pascal  [Select][+][-]
  1. // -----------------------------------------------------------------------------
  2. // COFF IMAGE SYMBOL related types and contants
  3.  
  4. // see:
  5. //
  6. // http://www.delorie.com/djgpp/doc/coff/symtab.html
  7. //
  8. // for detailed information about the meaning of the fields in IMAGE_SYMBOL as
  9. // well as winnt.h
  10.  
  11.  
  12. const
  13.   // these value can be found in the SymSectionNumber to indicate the meaning
  14.   // of the SymValue field
  15.  
  16.   IMAGE_SYM_UNDEFINED      =        0;       // symbol is undefined or common
  17.   IMAGE_SYM_ABSOLUTE       = word(-1);       // symbol is an absolute value
  18.   IMAGE_SYM_DEBUG          = word(-2);       // symbol is a special debug item
  19.   IMAGE_SYM_SECTION_MAX    = word($FEFF);    // values 0xFF00-0xFFFF are special
  20.  
  21.   IMAGE_SYM_SECTION_MAX_EX = high(int16);
  22.  
  23.  
  24. const
  25.   // Symbol Types - fundamental types
  26.  
  27.   IMAGE_SYM_TYPE_NULL       = $0000;         // no type
  28.   IMAGE_SYM_TYPE_VOID       = $0001;         //
  29.   IMAGE_SYM_TYPE_CHAR       = $0002;         // type character
  30.   IMAGE_SYM_TYPE_SHORT      = $0003;         // type short integer
  31.   IMAGE_SYM_TYPE_INT        = $0004;         //
  32.   IMAGE_SYM_TYPE_LONG       = $0005;         //
  33.   IMAGE_SYM_TYPE_FLOAT      = $0006;         //
  34.   IMAGE_SYM_TYPE_DOUBLE     = $0007;         //
  35.   IMAGE_SYM_TYPE_STRUCT     = $0008;         //
  36.   IMAGE_SYM_TYPE_UNION      = $0009;         //
  37.   IMAGE_SYM_TYPE_ENUM       = $000A;         // enumeration
  38.   IMAGE_SYM_TYPE_MOE        = $000B;         // member of enumeration
  39.   IMAGE_SYM_TYPE_BYTE       = $000C;         //
  40.   IMAGE_SYM_TYPE_WORD       = $000D;         //
  41.   IMAGE_SYM_TYPE_UINT       = $000E;         //
  42.   IMAGE_SYM_TYPE_DWORD      = $000F;         //
  43.   IMAGE_SYM_TYPE_PCODE      = $8000;         //
  44.  
  45.    // Symbol types - derived types
  46.  
  47.   IMAGE_SYM_DTYPE_NULL      =     0;         // no derived type
  48.   IMAGE_SYM_DTYPE_POINTER   =     1;         // pointer
  49.   IMAGE_SYM_DTYPE_FUNCTION  =     2;         // function
  50.   IMAGE_SYM_DTYPE_ARRAY     =     3;         // array
  51.  
  52.  
  53. // Type entry
  54. //
  55. // The type field in the symbol table entry contains information about the basic
  56. // and derived type for the symbol. This information is generated by the C
  57. // compilation system only if the -g option is used. Each symbol has exactly one
  58. // basic or fundamental type but can have more than one derived type. The format
  59. // of the 16-bit type entry is:
  60. //
  61. // dx are two bits each, type is one nibble (4 bits)
  62. //
  63. // d6      d5      d4      d3      d2      d1      type
  64. //
  65. //
  66. // Bits 0 through 3, called type, indicate one of the fundamental types given in
  67. // the Fundamental types table below:
  68. //
  69. // Fundamental types
  70. //
  71. // Mnemonic                      Value                    Type
  72. //
  73. // T_NULL                            0       type not assigned
  74. //      1            function argument                   T_ARG
  75. //             (used only by compiler)
  76. //
  77. //      2                    character                  T_CHAR
  78. //      3                short integer                 T_SHORT
  79. //      4                      integer                   T_INT
  80. //      5                 long integer                  T_LONG
  81. //      6               floating point                 T_FLOAT
  82. //      7                  double word                T_DOUBLE
  83. //      8                    structure                T_STRUCT
  84. //      9                        union                 T_UNION
  85. //     10                  enumeration                  T_ENUM
  86. //     11        member of enumeration                   T_MOE
  87. //     12           unsigned character                 T_UCHAR
  88. //     13               unsigned short                T_USHORT
  89. //     14             unsigned integer                  T_UINT
  90. //     15                unsigned long                 T_ULONG
  91. //
  92. // Bits 4 through 15 are arranged as six 2-bit fields marked d1 through d6.
  93. // These d fields represent levels of the derived types given in given in the
  94. // Derived types table below:
  95. //
  96. // Derived types
  97. //
  98. // Mnemonic        Value                  Type
  99. //
  100. // DT_NON              0       no derived type
  101. // DT_PTR              1               pointer
  102. // DT_FCN              2              function
  103. // DT_ARY              3                 array
  104. //
  105. // The following examples demonstrate the interpretation of the symbol table
  106. // entry representing type:
  107. //
  108. //    char *func();
  109. //
  110. // Here func is the name of a function that returns a pointer to a character.
  111. // The fundamental type of func is 2 (character), the d1 field is 2 (function),
  112. // and the d2 field is 1 (pointer).
  113. //
  114. // Therefore, the type word in the symbol table for func contains the
  115. // hexadecimal number 0x62, which is interpreted to mean a function that
  116. // returns a pointer to a character.
  117. //
  118. //
  119. //    short *tabptr[10][25][3];
  120. //
  121. // Here tabptr is a three-dimensional array of pointers to short integers. The
  122. // fundamental type of tabptr is 3 (short integer); the d1, d2, and d3 fields
  123. // each contains a 3 (array), and the d4 field is 1 (pointer).
  124. //
  125. // Therefore, the type entry in the symbol table contains the hexadecimal
  126. // number 0x7f3 indicating a three-dimensional array of pointers to short
  127. // integers.
  128.  
  129.  
  130. const
  131.    // Symbol storage classes
  132.  
  133.   IMAGE_SYM_CLASS_END_OF_FUNCTION  = byte(-1);
  134.   IMAGE_SYM_CLASS_NULL             =  $0;
  135.   IMAGE_SYM_CLASS_AUTOMATIC        =  $1;
  136.   IMAGE_SYM_CLASS_EXTERNAL         =  $2;
  137.   IMAGE_SYM_CLASS_STATIC           =  $3;
  138.   IMAGE_SYM_CLASS_REGISTER         =  $4;
  139.   IMAGE_SYM_CLASS_EXTERNAL_DEF     =  $5;
  140.   IMAGE_SYM_CLASS_LABEL            =  $6;
  141.   IMAGE_SYM_CLASS_UNDEFINED_LABEL  =  $7;
  142.   IMAGE_SYM_CLASS_MEMBER_OF_STRUCT =  $8;
  143.   IMAGE_SYM_CLASS_ARGUMENT         =  $9;
  144.   IMAGE_SYM_CLASS_STRUCT_TAG       =  $A;
  145.   IMAGE_SYM_CLASS_MEMBER_OF_UNION  =  $B;
  146.   IMAGE_SYM_CLASS_UNION_TAG        =  $C;
  147.   IMAGE_SYM_CLASS_TYPE_DEFINITION  =  $D;
  148.   IMAGE_SYM_CLASS_UNDEFINED_STATIC =  $E;
  149.   IMAGE_SYM_CLASS_ENUM_TAG         =  $F;
  150.   IMAGE_SYM_CLASS_MEMBER_OF_ENUM   = $10;
  151.   IMAGE_SYM_CLASS_REGISTER_PARAM   = $11;
  152.   IMAGE_SYM_CLASS_BIT_FIELD        = $12;
  153.  
  154.   IMAGE_SYM_CLASS_FAR_EXTERNAL     = $44;
  155.  
  156.   IMAGE_SYM_CLASS_BLOCK            = $64;
  157.   IMAGE_SYM_CLASS_FUNCTION         = $65;
  158.   IMAGE_SYM_CLASS_END_OF_STRUCT    = $66;
  159.   IMAGE_SYM_CLASS_FILE             = $67;
  160.  
  161.   IMAGE_SYM_CLASS_SECTION          = $68;
  162.   IMAGE_SYM_CLASS_WEAK_EXTERNAL    = $69;
  163.  
  164.   IMAGE_SYM_CLASS_CLR_TOKEN        = $6B;
  165.  
  166.  
  167. type
  168.   // if the symbol name is inline and it is exactly 8 characters long then it
  169.   // is not null terminated.  This buffer type provides a large enough buffer
  170.   // where the symbol name can be copied and be null terminated.  Note that
  171.   // this buffer may also be used to hold a symbol name that is present in the
  172.   // string table, for this reason it cannot be made to simply hold 8 characters
  173.   // and a null terminator since most strings in the string table are longer
  174.   // than that.
  175.  
  176.   TSYM_NAME_BUFFER  = array[0..1023] of char;
  177.  
  178.   TSYMBOL_NAME_INFO = packed record
  179.     case integer of
  180.       0 : (SymName         : packed array[0..7] of char);
  181.       1 : (
  182.            SymNameInline   : boolean32;  // use SymNameOffset if false
  183.            SymNameOffset   : DWORD;      // offset to name into string table
  184.           );
  185.   end;
  186.  
  187.  
  188. type
  189.   // WARNING: this definition depends on how FPC does bitpacking and also how
  190.   //          it accesses bitpacked fields (this definition is tested and known
  191.   //          valid only for FPC v3.0.4)
  192.  
  193.   TSYM_TYPE_RANGE         = -2..5;  // the bitfields 1..6 start at index 0
  194.   TSYM_TYPE_DERIVED_RANGE =  0..5;
  195.  
  196.   TSYM_TYPE = bitpacked record
  197.     case integer of
  198.     0 : (
  199.           FundamentalType  : T4BITS;
  200.  
  201.           DerivedType1     : T2BITS;
  202.           DerivedType2     : T2BITS;
  203.           DerivedType3     : T2BITS;
  204.           DerivedType4     : T2BITS;
  205.           DerivedType5     : T2BITS;
  206.           DerivedType6     : T2BITS;
  207.         );
  208.  
  209.     1 : (
  210.           // tested the array representation with value $7F3 and it _seems_ to
  211.           // work as expected.
  212.  
  213.           DerivedType      : bitpacked array[TSYM_TYPE_RANGE] of T2BITS;
  214.         );
  215.  
  216.     2 : (
  217.           TypeWord         : word;
  218.         );
  219.  
  220.   end;
  221.  
  222. type
  223.   PIMAGE_SYMBOL = ^TIMAGE_SYMBOL;                                      // 18
  224.   TIMAGE_SYMBOL = packed record
  225.     SymNameInfo            : TSYMBOL_NAME_INFO;
  226.     SymValue               : DWORD;
  227.     SymSectionNumber       : word;       // usually the 1 based section number
  228.                                          // but see IMAGE_SYM... constants
  229.  
  230.     SymType                : TSYM_TYPE;  // see IMAGE_SYM_TYPE...  constants
  231.     SymStorageClass        : byte;       // see IMAGE_SYM_CLASS... constants
  232.     SymNumberOfAuxSymbols  : byte;
  233.   end;
  234.  

Lastly, it's quite likely that the above equivalent definitions are found somewhere in the units that come with FPC but, I use my own, that's what the above is.

HTH.
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

msintle

  • Sr. Member
  • ****
  • Posts: 379
Re: Getting File Size from PE Header Fails with Lazarus
« Reply #17 on: October 25, 2023, 05:11:24 pm »
Would it be too much to ask for a working sample?

440bx

  • Hero Member
  • *****
  • Posts: 6556
Re: Getting File Size from PE Header Fails with Lazarus
« Reply #18 on: October 25, 2023, 06:55:17 pm »
Would it be too much to ask for a working sample?
Unfortunately, in this case, yes because I don't use the definitions that come with Lazarus.  As a result even the smallest working sample would take close to 3,000 lines (most of them definition of constants, types and structures.)

here is an excerpt of my code:
Code: Pascal  [Select][+][-]
  1.  
  2.   // --------------------------------------------------------------------------
  3.   // determine if there is a COFF symbol table in the PE file
  4.  
  5.   if InPeData.pe_FileHeader^.PointerToSymbolTable = 0 then
  6.   begin
  7.     // there is no COFF symbol table, there is nothing to dump
  8.  
  9.     exit(TRUE);
  10.   end;
  11.  
  12.   with CoffSymTableInfo do
  13.   begin
  14.     CoffSymTableEntryCount := InPeData.pe_FileHeader^.NumberOfSymbols;
  15.  
  16.     if CoffSymTableEntryCount = 0 then
  17.     begin
  18.       // there is an address for a COFF symbol table but the size indicates
  19.       // there is no symbol table, can't dump a zero size table
  20.  
  21.       // might not be a bad idea to issue a warning about this situation
  22.  
  23.       if IsDebuggerPresent() then asm int 3 end;
  24.  
  25.       exit(TRUE);
  26.     end;
  27.   end;
  28.  
  29.  
  30.   // --------------------------------------------------------------------------
  31.   // there is a table and it is not empty, get a pointer to it and ensure the
  32.   // entire table is readable
  33.  
  34.   with InPeData, CoffSymTableInfo do
  35.   begin
  36.     CoffSymTable := PIMAGE_SYMBOL
  37.                       (
  38.                        pe_Fileptr + pe_FileHeader^.PointerToSymbolTable
  39.                       );
  40.  
  41.     if IsBadReadPtr(CoffSymTable,
  42.                     CoffSymTableEntryCount * sizeof(CoffSymTable^)) then exit;
  43.  
  44.     // determine the pointer to the COFF SYMBOL NAMES string table
  45.  
  46.     StringTable := pointer(CoffSymTable + CoffSymTableEntryCount);
  47.  
  48.     if IsBadReadPtr(StringTable, sizeof(StringTable^.Size))         then exit;
  49.     if IsBadReadPtr(StringTable, StringTable^.Size)                 then exit;
  50.   end;
  51.  

The code above shows how to calculate the location of the COFF symbol table and the String table as well as ensuring they are both readable.

Below is the code to walk the string table, in your case this is not necessary, I included it only to show how you could walk it if you have to.

Code: Pascal  [Select][+][-]
  1.  
  2. // ----------------------------------------------------------------------------
  3.  
  4. function CoffStringTableCount
  5.            (
  6.             const InStringTable : PSYMBOL_STRING_TABLE
  7.            )
  8.          : DWORD;
  9.   { used to determine the number of strings in the strings table but, this    }
  10.   { function is _not_ currently used and it is usually removed by the linker  }
  11.  
  12. var
  13.   StringTableSize : DWORD;
  14.  
  15.   StringPtr       : pchar;
  16.   StringLength    : DWORD;
  17.   StringCount     : DWORD;
  18.  
  19.   ScannedBytes    : DWORD;
  20.  
  21. begin
  22.   result      := 0;
  23.  
  24.   StringCount := 0;           // when done counting, result will have this value
  25.  
  26.   with InStringTable^ do
  27.   begin
  28.     StringTableSize := Size;
  29.  
  30.     StringPtr       := Characters;
  31.  
  32.     ScannedBytes    := pbyte(StringPtr) - pbyte(InStringTable);
  33.  
  34.     // verify that the number of scanned bytes equals the size of the size field
  35.     // in the string table.  if that is not the case then a change has been made
  36.     // in the record definition of a string table which may prevent this routine
  37.     // from working properly.
  38.  
  39.     if ScannedBytes <> sizeof(Size)                                 then exit;
  40.   end;
  41.  
  42.   while TRUE do
  43.   begin
  44.     if IsBadStringPtrA(StringPtr, STRING_MAX_LENGTH)                then exit;
  45.  
  46.     StringLength    := strlen(StringPtr);
  47.     ScannedBytes    += StringLength + 1; // account for the null terminator
  48.  
  49.     if ScannedBytes >= StringTableSize                              then break;
  50.  
  51.     inc(StringCount);
  52.  
  53.     StringPtr       += StringLength + 1;
  54.   end;
  55.  
  56.   result            := StringCount;      // return the count of strings
  57. end;
  58.  

I posted the data type definitions used by the code in the previous post.

If you have any questions, just ask.

HTH.
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

440bx

  • Hero Member
  • *****
  • Posts: 6556
Re: Getting File Size from PE Header Fails with Lazarus
« Reply #19 on: October 26, 2023, 11:05:06 pm »
Attached to this post is the source code of a program that calculates the size of its COFF symbol table.  Note that it does not use the sections to figure it out.

Compile it with debugging information to walk through the code (recommendation: DWARF)

HTH.
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

KodeZwerg

  • Hero Member
  • *****
  • Posts: 2269
  • Fifty shades of code.
    • Delphi & FreePascal
Re: Getting File Size from PE Header Fails with Lazarus
« Reply #20 on: October 27, 2023, 03:13:05 am »
To OP:
I will try to port my way, pure WinAPI, to Lazarus and send source here.

Perhaps you can also paste that code here and I will see if I can make any progress with it, if you haven't yet had time to port it. A pure WinAPI approach does sound good indeed.
Here is my way, sorry for scary looking, its cutted out of a really big method that analyze many aspects of Executable data.
Tested and works with 32/64 bit files.
Code: Pascal  [Select][+][-]
  1. program project1;
  2.  
  3. {$APPTYPE CONSOLE}
  4.  
  5. uses
  6.   Windows, SysUtils, Classes;
  7.  
  8. function EndOfPE(const AFilename: string): UInt64;
  9. var
  10.   FS: TStream;
  11.   dwSig: DWORD;
  12.   wSig: Word;
  13.   i: Integer;
  14.   HeaderDos: IMAGE_DOS_HEADER;
  15.   HeaderFile: IMAGE_FILE_HEADER;
  16.   HeaderOptional64: IMAGE_OPTIONAL_HEADER64;
  17.   HeaderOptional32: IMAGE_OPTIONAL_HEADER32;
  18.   HeaderSection: array of IMAGE_SECTION_HEADER;
  19.   PHeaderSection: array of PImageSectionHeader;
  20.   Is64Bit: Boolean;
  21. begin
  22.   SetLastError(ERROR_SUCCESS);
  23.   Result := 0;
  24.   FS := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyNone);
  25.   try
  26.     FS.Read(HeaderDos, SizeOf(IMAGE_DOS_HEADER));
  27.     if (HeaderDos.e_magic = IMAGE_DOS_SIGNATURE) then
  28.       begin
  29.         try
  30.           FS.Seek(HeaderDos._lfanew, soFromBeginning);
  31.           FS.Read(wSig, SizeOf(wSig));
  32.           FS.Seek(HeaderDos._lfanew, soFromBeginning);
  33.           FS.Read(dwSig, SizeOf(dwSig));
  34.           if (HeaderDos.e_magic <> IMAGE_DOS_SIGNATURE) then
  35.             Exit;
  36.           if (wSig <> IMAGE_NT_SIGNATURE) then
  37.             Exit;
  38.           FS.Read(HeaderFile, SizeOf(IMAGE_FILE_HEADER));
  39.           if (HeaderFile.SizeOfOptionalHeader > 0) then
  40.             begin
  41.               i := FS.Position;
  42.               FS.Read(wSig, SizeOf(wSig));
  43.               Is64Bit := (wSig = IMAGE_NT_OPTIONAL_HDR64_MAGIC);
  44.               FS.Seek(i, soFromBeginning);
  45.               FS.Read(dwSig, SizeOf(dwSig));
  46.               FS.Seek(i, soFromBeginning);
  47.               if Is64Bit then
  48.                 FS.Read(HeaderOptional64, SizeOf(IMAGE_OPTIONAL_HEADER64))
  49.               else
  50.                 FS.Read(HeaderOptional32, SizeOf(IMAGE_OPTIONAL_HEADER32));
  51.             end;
  52.           if (HeaderFile.NumberOfSections > 0) then
  53.             begin
  54.               try
  55.                 SetLength(HeaderSection, HeaderFile.NumberOfSections);
  56.                 SetLength(PHeaderSection, HeaderFile.NumberOfSections);
  57.                 for i := 1 to HeaderFile.NumberOfSections do
  58.                   begin
  59.                     FS.Read(HeaderSection[i - 1], SizeOf(IMAGE_SECTION_HEADER));
  60.                     PHeaderSection[i - 1] := @HeaderSection[i - 1];
  61.                     with PHeaderSection[i - 1]^ do
  62.                       if (PointerToRawData + SizeOfRawData > Result) then
  63.                         Result := PointerToRawData + SizeOfRawData;
  64.                   end;
  65.               except
  66.               end;
  67.             end;
  68.         finally
  69.         end;
  70.       end;
  71.   finally
  72.     FS.Free;
  73.   end;
  74. end;
  75.  
  76. begin
  77.   WriteLn(EndOfPE(ParamStr(0)));
  78.   ReadLn;
  79. end.
« Last Edit: Tomorrow at 31:76:97 xm by KodeZwerg »

440bx

  • Hero Member
  • *****
  • Posts: 6556
Re: Getting File Size from PE Header Fails with Lazarus
« Reply #21 on: October 27, 2023, 04:47:24 am »
What I don't like about using the sections to determine the size of the PE part of the file is that, that method does not provide any information as to what kind of data follows the PE part.

Normally, in a properly constructed PE file, if there is a COFF symbol table then the pointer to the symbol table will have a non-zero value and that value _must_ be the size of the PE part.

Also, it would be fair for an antivirus to consider an exe to be infected if there is _unreferenced_ data following the PE part.  That's fair because the antivirus cannot simply assume that whatever follows it is "benign". Not to mention that simply appending data to PE part does not scale (what if there are two different blocks of data to be appended by different utilities that know nothing of one another ?... that method doesn't cut that mustard.)
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

msintle

  • Sr. Member
  • ****
  • Posts: 379
Re: Getting File Size from PE Header Fails with Lazarus
« Reply #22 on: October 28, 2023, 12:40:01 pm »
To OP:
I will try to port my way, pure WinAPI, to Lazarus and send source here.

Perhaps you can also paste that code here and I will see if I can make any progress with it, if you haven't yet had time to port it. A pure WinAPI approach does sound good indeed.
Here is my way, sorry for scary looking, its cutted out of a really big method that analyze many aspects of Executable data.
Tested and works with 32/64 bit files.
Code: Pascal  [Select][+][-]
  1. program project1;
  2.  
  3. {$APPTYPE CONSOLE}
  4.  
  5. uses
  6.   Windows, SysUtils, Classes;
  7.  
  8. function EndOfPE(const AFilename: string): UInt64;
  9. var
  10.   FS: TStream;
  11.   dwSig: DWORD;
  12.   wSig: Word;
  13.   i: Integer;
  14.   HeaderDos: IMAGE_DOS_HEADER;
  15.   HeaderFile: IMAGE_FILE_HEADER;
  16.   HeaderOptional64: IMAGE_OPTIONAL_HEADER64;
  17.   HeaderOptional32: IMAGE_OPTIONAL_HEADER32;
  18.   HeaderSection: array of IMAGE_SECTION_HEADER;
  19.   PHeaderSection: array of PImageSectionHeader;
  20.   Is64Bit: Boolean;
  21. begin
  22.   SetLastError(ERROR_SUCCESS);
  23.   Result := 0;
  24.   FS := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyNone);
  25.   try
  26.     FS.Read(HeaderDos, SizeOf(IMAGE_DOS_HEADER));
  27.     if (HeaderDos.e_magic = IMAGE_DOS_SIGNATURE) then
  28.       begin
  29.         try
  30.           FS.Seek(HeaderDos._lfanew, soFromBeginning);
  31.           FS.Read(wSig, SizeOf(wSig));
  32.           FS.Seek(HeaderDos._lfanew, soFromBeginning);
  33.           FS.Read(dwSig, SizeOf(dwSig));
  34.           if (HeaderDos.e_magic <> IMAGE_DOS_SIGNATURE) then
  35.             Exit;
  36.           if (wSig <> IMAGE_NT_SIGNATURE) then
  37.             Exit;
  38.           FS.Read(HeaderFile, SizeOf(IMAGE_FILE_HEADER));
  39.           if (HeaderFile.SizeOfOptionalHeader > 0) then
  40.             begin
  41.               i := FS.Position;
  42.               FS.Read(wSig, SizeOf(wSig));
  43.               Is64Bit := (wSig = IMAGE_NT_OPTIONAL_HDR64_MAGIC);
  44.               FS.Seek(i, soFromBeginning);
  45.               FS.Read(dwSig, SizeOf(dwSig));
  46.               FS.Seek(i, soFromBeginning);
  47.               if Is64Bit then
  48.                 FS.Read(HeaderOptional64, SizeOf(IMAGE_OPTIONAL_HEADER64))
  49.               else
  50.                 FS.Read(HeaderOptional32, SizeOf(IMAGE_OPTIONAL_HEADER32));
  51.             end;
  52.           if (HeaderFile.NumberOfSections > 0) then
  53.             begin
  54.               try
  55.                 SetLength(HeaderSection, HeaderFile.NumberOfSections);
  56.                 SetLength(PHeaderSection, HeaderFile.NumberOfSections);
  57.                 for i := 1 to HeaderFile.NumberOfSections do
  58.                   begin
  59.                     FS.Read(HeaderSection[i - 1], SizeOf(IMAGE_SECTION_HEADER));
  60.                     PHeaderSection[i - 1] := @HeaderSection[i - 1];
  61.                     with PHeaderSection[i - 1]^ do
  62.                       if (PointerToRawData + SizeOfRawData > Result) then
  63.                         Result := PointerToRawData + SizeOfRawData;
  64.                   end;
  65.               except
  66.               end;
  67.             end;
  68.         finally
  69.         end;
  70.       end;
  71.   finally
  72.     FS.Free;
  73.   end;
  74. end;
  75.  
  76. begin
  77.   WriteLn(EndOfPE(ParamStr(0)));
  78.   ReadLn;
  79. end.

Thank you, but this doesn't work.

I've tried both with GUI and console Win32 apps, the result is again underreporting the file size by quite a margin.

msintle

  • Sr. Member
  • ****
  • Posts: 379
Re: Getting File Size from PE Header Fails with Lazarus
« Reply #23 on: October 28, 2023, 12:43:54 pm »
Attached to this post is the source code of a program that calculates the size of its COFF symbol table.  Note that it does not use the sections to figure it out.

Compile it with debugging information to walk through the code (recommendation: DWARF)

HTH.

Thank you so much for this.

It seems like the project you attached compiles a 64 bit Windows command line app, with which I was able to reproduce your results.

However with my 32 bit test cases, I ran into the attached issue at compile time.

Any thoughts on how to fix the assembler code therein?

msintle

  • Sr. Member
  • ****
  • Posts: 379
Re: Getting File Size from PE Header Fails with Lazarus
« Reply #24 on: October 28, 2023, 12:50:20 pm »
What I don't like about using the sections to determine the size of the PE part of the file is that, that method does not provide any information as to what kind of data follows the PE part.

Normally, in a properly constructed PE file, if there is a COFF symbol table then the pointer to the symbol table will have a non-zero value and that value _must_ be the size of the PE part.

Also, it would be fair for an antivirus to consider an exe to be infected if there is _unreferenced_ data following the PE part.  That's fair because the antivirus cannot simply assume that whatever follows it is "benign". Not to mention that simply appending data to PE part does not scale (what if there are two different blocks of data to be appended by different utilities that know nothing of one another ?... that method doesn't cut that mustard.)

Your reservations aside, I commented out the asm code, and your code worked 100% perfectly in both my GUI and console apps, reporting the file size perfectly.

A massive thank you!!!

EDIT: Sadly, it still doesn't quite work %)

Appending data to the end of the file, we simply get the full file size, instead of the file size for the executable itself alone.

So this is really no different than calling an ordinary file size query in practical use?
« Last Edit: October 28, 2023, 12:54:55 pm by msintle »

440bx

  • Hero Member
  • *****
  • Posts: 6556
Re: Getting File Size from PE Header Fails with Lazarus
« Reply #25 on: October 28, 2023, 01:01:35 pm »
Thank you so much for this.

It seems like the project you attached compiles a 64 bit Windows command line app, with which I was able to reproduce your results.

However with my 32 bit test cases, I ran into the attached issue at compile time.

Any thoughts on how to fix the assembler code therein?
You're welcome.

The project I attached compiles in 32 and 64 bit without any problems for me.

From the picture you posted, it seems you copied some of the code from my program into your own project.  If that is what you did then you missed copying the line  that says "{$ASMMODE        INTEL}" to let the compiler know what assembly language it should use.  Also, you may need to set Compiler Options -> Parsing -> Assembler style (-R) to "Intel".  see the attached picture for 1000 words ;)

With those two corrections your program should compile (presuming it is free of syntax errors)

HTH.
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

440bx

  • Hero Member
  • *****
  • Posts: 6556
Re: Getting File Size from PE Header Fails with Lazarus
« Reply #26 on: October 28, 2023, 01:20:42 pm »
EDIT: Sadly, it still doesn't quite work %)

Appending data to the end of the file, we simply get the full file size, instead of the file size for the executable itself alone.

So this is really no different than calling an ordinary file size query in practical use?
It's very important to understand the following: an executable is NOT supposed to have _unreferenced_ data.  IOW, if somehow something (a utility, a virus, a whathaveyou) manages to append data to an exe or dll without referencing that data somewhere (in whatever place is appropriate in the PE file - which is a reference to the data) then that data cannot be presumed to be benign. 

To detect that some unreferenced data has been appended you will need to iterate through the sections to determine what the file size _should_ be, that is, the method you originally used.  That method works in all cases but, it has the disadvantage that it does NOT tell you if the appended data is legit or not.  If it's legit then it should be referenced somewhere in the PE part of the file.

It is different than querying the O/S for the file size because, IF (note the big IF) the PE file is put together correctly then the data that has been appended should be pointed to by the "Pointer to symbol table" field even though the data may not be a symbol table, which makes the use of the field for that purpose a bit questionable but it is the only way I know of to "declare" that data that is not part of the PE data is present in the file.

If you want to be thorough then you can use your sections method AND verify that the sections method yields the same value as that specified in "Pointer to symbol table".  If the values differ then that data is unreferenced and its purpose should be considered dubious.

FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

440bx

  • Hero Member
  • *****
  • Posts: 6556
Re: Getting File Size from PE Header Fails with Lazarus
« Reply #27 on: October 28, 2023, 04:02:35 pm »
Attached to this post is an "enhanced" version of the example I previously posted.

This version calculates the PE size using the PointerToSymbolTable and the sections method and it compares the results (which should be the same.)

It also computes the size of the COFF symbol table and the string table and compares the total calculated size to the total size declared in the PE file.

It emits a warning if there is an inconsistency in sizes - which occurs if a "utilty" simply appends data to the exe without creating a reference to it in the PE part.

HTH.
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

msintle

  • Sr. Member
  • ****
  • Posts: 379
Re: Getting File Size from PE Header Fails with Lazarus
« Reply #28 on: October 28, 2023, 06:39:06 pm »
Thank you so much for this.

It seems like the project you attached compiles a 64 bit Windows command line app, with which I was able to reproduce your results.

However with my 32 bit test cases, I ran into the attached issue at compile time.

Any thoughts on how to fix the assembler code therein?
You're welcome.

The project I attached compiles in 32 and 64 bit without any problems for me.

From the picture you posted, it seems you copied some of the code from my program into your own project.  If that is what you did then you missed copying the line  that says "{$ASMMODE        INTEL}" to let the compiler know what assembly language it should use.  Also, you may need to set Compiler Options -> Parsing -> Assembler style (-R) to "Intel".  see the attached picture for 1000 words ;)

With those two corrections your program should compile (presuming it is free of syntax errors)

HTH.

Correct, that pleased the compiler - thank you!

msintle

  • Sr. Member
  • ****
  • Posts: 379
Re: Getting File Size from PE Header Fails with Lazarus
« Reply #29 on: October 28, 2023, 06:47:18 pm »
Attached to this post is an "enhanced" version of the example I previously posted.

This version calculates the PE size using the PointerToSymbolTable and the sections method and it compares the results (which should be the same.)

It also computes the size of the COFF symbol table and the string table and compares the total calculated size to the total size declared in the PE file.

It emits a warning if there is an inconsistency in sizes - which occurs if a "utilty" simply appends data to the exe without creating a reference to it in the PE part.

HTH.

Wow, sounds like we've (well, you've) nailed it!

Please see this command line output:

Code: Text  [Select][+][-]
  1.  Directory of c:\Users\c\Desktop\CoffSymTable2\lib\x86_64-win64
  2.  
  3. 10/28/2023  07:40 PM    <DIR>          .
  4. 10/28/2023  07:40 PM    <DIR>          ..
  5. 10/28/2023  07:41 PM               495 CoffSymTable.compiled
  6. 10/28/2023  07:41 PM           306,835 CoffSymTable.exe
  7. 10/28/2023  07:41 PM            36,392 CoffSymTable.o
  8. 10/28/2023  07:41 PM            19,220 libimp_80_kernel32.a
  9. 10/28/2023  07:41 PM             8,420 libimp_80_ntdll.a
  10. 10/28/2023  07:41 PM             2,052 _80_kernel32.o
  11. 10/28/2023  07:41 PM            16,508 _80_kernel32.ppu
  12. 10/28/2023  07:41 PM           147,600 _80_ntdll.o
  13. 10/28/2023  07:41 PM            90,566 _80_ntdll.ppu
  14.                9 File(s)        628,088 bytes
  15.                2 Dir(s)  213,113,245,696 bytes free
  16.  
  17. c:\Users\c\Desktop\CoffSymTable2\lib\x86_64-win64>copy /b CoffSymTable.exe + CoffSymTable.o c.exe
  18. CoffSymTable.exe
  19. Overwrite c.exe? (Yes/No/All): y
  20. CoffSymTable.o
  21.         1 file(s) copied.
  22.  
  23. c:\Users\c\Desktop\CoffSymTable2\lib\x86_64-win64>c
  24.  
  25.  
  26.                     Calculate the size of the COFF symbol table
  27.  
  28.  
  29.                                 Program file size :    343227
  30.  
  31.                                   size of PE data :    240128
  32.     size of entire COFF table (entries + strings) :    103099
  33.  
  34.                         PE size using PE sections :    240128
  35.                            COFF symbol table size :     21348
  36.                                 string table size :     45359
  37.  
  38.  
  39.     -- program executed successfully --
  40.  
  41.  
  42.     press ENTER/RETURN to end this program

So PE section size + COFF symbol table size + string table size returns the desired actual binary size.

The other calculations still return the grown file size.

Once again, many thanks!!!

Much gratitude for all your kind and patient assistance.

 

TinyPortal © 2005-2018