Getting File Size from PE Header Fails with Lazarus lazarus / 34-Windows ---------------------------------------------------------------------- msintle · 2023-10-24 19:04 So here's an odd one. Code like this works beautifully in Delphi: Code: Pascal [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i { call passing HInstance as the Executable parameter, add Windows to uses }function GetExeSize(Executable: THandle): Cardinal;type PImageDosHeader = ^TImageDosHeader; TImageDosHeader = packed record e_magic: Word; e_ignore: packed array [0..28] of Word; _lfanew: Longint; end;var i, NumSections: Integer; p: PAnsiChar;begin Result := 0; p := Pointer(Executable); Inc(p, PImageDosHeader(p)^._lfanew + SizeOf(DWORD)); NumSections := PImageFileHeader(p)^.NumberOfSections; Inc(p, SizeOf(TImageFileHeader) + SizeOf(TImageOptionalHeader)); for i := 1 to NumSections do begin with PImageSectionHeader(p)^ do if PointerToRawData+SizeOfRawData > Result then Result := PointerToRawData + SizeOfRawData; Inc(p, SizeOf(TImageSectionHeader)); end;end; But it fails in Lazarus. To clarify, it does still compile and run - but returns the wrong file size (always falling short a bit). Is Lazarus constructing the PE header differently somehow, perhaps? ---------------------------------------------------------------------- Fibonacci · 2023-10-24 19:11 Works 100% fine if reading 32bit i386. For 64bit the code will be differet. eg TImageOptionalHeader default is TImageOptionalHeader32, and for 64bit you need to use TImageOptionalHeader64 BTW. Remove debug info, this is non standard and weird method to get "file size" ---------------------------------------------------------------------- msintle · 2023-10-24 19:29 Quote from: Fibonacci on October 24, 2023, 09:11:32 pmWorks 100% fine if reading 32bit i386. Not for me. I am building x86 targets. Happy to upload both a console and a GUI test app if people would like. Reproduces instantly. Quote from: Fibonacci on October 24, 2023, 09:11:32 pmFor 64bit the code will be differet. eg TImageOptionalHeader default is TImageOptionalHeader32, and for 64bit you need to use TImageOptionalHeader64 Thanks for noting that, will definitely come in handy in the future. Quote from: Fibonacci on October 24, 2023, 09:11:32 pmBTW. Remove debug info, this is non standard and weird method to get "file size" How do you mean? In Delphi, it works properly even with debug info. Is the way Lazarus is adding debug information then non-standard and breaking the standard header based file size calculation process? Regarding your comment that this is a weird method to get file size, it is the only method to get file size when you are building a stub application (such as a self-extractor which would have an archive of variable size appended to its end, for example). ---------------------------------------------------------------------- 440bx · 2023-10-25 02:11 @msintle, For a general utility your code will _not_ work because sizeof(TImageOptionalHeader) is likely determined at compile time (otherwise the code you showed would not compile.) if that code has to deal with executables other than itself then it will only be right when the executable it is dealing with happens to be the same bitness as the program. If the bitness between them differ then the result will be wrong due to the wrong size of TImageOptionalHeader. On a different note, why do you calculate the file size that way instead of simply asking the O/S for the file size ? HTH. ---------------------------------------------------------------------- KodeZwerg · 2023-10-25 02:51 I do use something similar in my Advanced-Properties app that was compiled with Delphi. It is very handy to detect anything that in my app I declared as "overlay" (or extract/strip such data) It is also very reliable used in stubs (no matter what bitness when using correct header translation) to detect the end of stub in a 100% accurate way. Many oldschool and also newschool archivers that do produce executable files, like WinRAR, are using this way to know where streaming of archive begin. To OP: I will try to port my way, pure WinAPI, to Lazarus and send source here. ---------------------------------------------------------------------- Fibonacci · 2023-10-25 07:01 Quote from: msintle on October 24, 2023, 09:29:29 pmHow do you mean? In Delphi, it works properly even with debug info. Is the way Lazarus is adding debug information then non-standard and breaking the standard header based file size calculation process? I dont have Delphi so cant compare the outputs, but FPC is adding debug info after EOF, overlay. If you build your exe without debug info, your function will display correct size. In my case debug info adds ~60 KB after PE sections (with only uses Windows). 170496 {your function} + 62931 {overlay size} = 233427 {actual file size on the disk} Other than overlay debug info, it also creates many actual PE sections. ---------------------------------------------------------------------- 440bx · 2023-10-25 07:43 Quote from: Fibonacci on October 25, 2023, 09:01:23 am but FPC is adding debug info after EOF, overlay. I'm probably misunderstanding something but, I have not seen an instance where FPC adds debug info _after_ the executable's EOF in a PE file. Do you have an example of such an occurrence and/or how to produce such a file ? TIA. The following is what I've always seen (Note: details for non-debug sections have been removed for brevity): Code: Text [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i 40.0178 - IMAGE_SECTION_HEADER(s) - Number of sections : 10 40.0178 SECTION: 1 of 10 40.0178 178 [8] Name : .text 40.01a0 SECTION: 2 of 10 contains: Thread Local Storage (TLS) directory 40.01a0 1a0 [8] Name : .data 40.01c8 SECTION: 3 of 10 40.01c8 1c8 [8] Name : .rdata 40.01f0 SECTION: 4 of 10 40.01f0 1f0 [8] Name : .bss 40.0218 SECTION: 5 of 10 40.0218 218 [8] Name : .CRT 40.0240 SECTION: 6 of 10 contains: Imports directory 40.0240 + Imports address table directory 40.0240 240 [8] Name : .idata 40.0268 SECTION: 7 of 10 40.0268 268 [8] Name : /4 1.c3f0 [12] .debug_info 40.0270 270 [4] Virtual size : 9202 (37,378) 40.0274 274 [4] (Relative) Virtual address : 1.6000 [Va: 41.6000] [FO: ea00] [ /4] 40.0278 278 [4] Size of raw data : 9400 (37,888) 40.027c 27c [4] File offset/pointer to raw data : ea00 [Va: 41.6000] [FO: ea00] [ /4] 40.0280 280 [4] File offset/pointer to relocations : 0 40.0284 284 [4] File offset/pointer to linenumbers : 0 40.0288 288 [2] Number of relocations : 0 40.028a 28a [2] Number of linenumbers : 0 40.028c 28c [4] Characteristics : 4200.0040 40.028c + SCN_CNT_INITIALIZED_DATA 40 40.028c + SCN_ALIGN_NOTSPECIFIED 0 40.028c + SCN_MEM_DISCARDABLE 200.0000 40.028c + SCN_MEM_READ 4000.0000 40.0290 SECTION: 8 of 10 40.0290 290 [8] Name : /16 1.c3fc [14] .debug_abbrev 40.0298 298 [4] Virtual size : 244 ( 580) 40.029c 29c [4] (Relative) Virtual address : 2.0000 [Va: 42.0000] [FO: 1.7e00] [ /16] 40.02a0 2a0 [4] Size of raw data : 400 (1,024) 40.02a4 2a4 [4] File offset/pointer to raw data : 1.7e00 [Va: 42.0000] [FO: 1.7e00] [ /16] 40.02a8 2a8 [4] File offset/pointer to relocations : 0 40.02ac 2ac [4] File offset/pointer to linenumbers : 0 40.02b0 2b0 [2] Number of relocations : 0 40.02b2 2b2 [2] Number of linenumbers : 0 40.02b4 2b4 [4] Characteristics : 4200.0040 40.02b4 + SCN_CNT_INITIALIZED_DATA 40 40.02b4 + SCN_ALIGN_NOTSPECIFIED 0 40.02b4 + SCN_MEM_DISCARDABLE 200.0000 40.02b4 + SCN_MEM_READ 4000.0000 40.02b8 SECTION: 9 of 10 40.02b8 2b8 [8] Name : /30 1.c40a [12] .debug_line 40.02c0 2c0 [4] Virtual size : bd5 (3,029) 40.02c4 2c4 [4] (Relative) Virtual address : 2.1000 [Va: 42.1000] [FO: 1.8200] [ /30] 40.02c8 2c8 [4] Size of raw data : c00 (3,072) 40.02cc 2cc [4] File offset/pointer to raw data : 1.8200 [Va: 42.1000] [FO: 1.8200] [ /30] 40.02d0 2d0 [4] File offset/pointer to relocations : 0 40.02d4 2d4 [4] File offset/pointer to linenumbers : 0 40.02d8 2d8 [2] Number of relocations : 0 40.02da 2da [2] Number of linenumbers : 0 40.02dc 2dc [4] Characteristics : 4200.0040 40.02dc + SCN_CNT_INITIALIZED_DATA 40 40.02dc + SCN_ALIGN_NOTSPECIFIED 0 40.02dc + SCN_MEM_DISCARDABLE 200.0000 40.02dc + SCN_MEM_READ 4000.0000 40.02e0 SECTION: 10 of 10 40.02e0 2e0 [8] Name : /42 1.c416 [13] .debug_frame 40.02e8 2e8 [4] Virtual size : 29c ( 668) 40.02ec 2ec [4] (Relative) Virtual address : 2.2000 [Va: 42.2000] [FO: 1.8e00] [ /42] 40.02f0 2f0 [4] Size of raw data : 400 (1,024) 40.02f4 2f4 [4] File offset/pointer to raw data : 1.8e00 [Va: 42.2000] [FO: 1.8e00] [ /42] 40.02f8 2f8 [4] File offset/pointer to relocations : 0 40.02fc 2fc [4] File offset/pointer to linenumbers : 0 40.0300 300 [2] Number of relocations : 0 40.0302 302 [2] Number of linenumbers : 0 40.0304 304 [4] Characteristics : 4200.0040 40.0304 + SCN_CNT_INITIALIZED_DATA 40 40.0304 + SCN_ALIGN_NOTSPECIFIED 0 40.0304 + SCN_MEM_DISCARDABLE 200.0000 40.0304 + SCN_MEM_READ 4000.0000 40.0308 - IMAGE HEADER - 248 filler bytes - 40.0308 308 [ 8] __ __ __ __ __ __ __ __ 00 00 00 00 00 00 00 00 ________........ 40.0310 310 [16] 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 40.0320 320 [16] 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ etc etc etc In the above, all file offsets are within the real size of the PE file (IOW, not appended to what "used to be" its real size) ---------------------------------------------------------------------- KodeZwerg · 2023-10-25 07:46 My app does confirm what Fibonacci say. I attached an Image that visualize it. ---------------------------------------------------------------------- Fibonacci · 2023-10-25 07:47 Quote from: 440bx on October 25, 2023, 09:43:12 amDo you have an example of such an occurrence and/or how to produce such a file ? TIA. 1. OPs function returns real image size, which is different than file size, ergo "there is something more in the file" 2. Open binary in eg. PE-Bear and see Overlay ---------------------------------------------------------------------- 440bx · 2023-10-25 07:54 @KodeZwerg & @Fibonacci Which binary ?... I don't see a binary anywhere in this thread ... did I miss an elephant in the room ? ---------------------------------------------------------------------- Fibonacci · 2023-10-25 08:07 Ok, if you cant/dont want to compile yourself, here you go, binary in the attachment Full source code Code: Pascal [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i uses Windows; { call passing HInstance as the Executable parameter, add Windows to uses }function GetExeSize(Executable: THandle): Cardinal;type PImageDosHeader = ^TImageDosHeader; TImageDosHeader = packed record e_magic: Word; e_ignore: packed array [0..28] of Word; _lfanew: Longint; end;var i, NumSections: Integer; p: PAnsiChar;begin Result := 0; p := Pointer(Executable); Inc(p, PImageDosHeader(p)^._lfanew + SizeOf(DWORD)); NumSections := PImageFileHeader(p)^.NumberOfSections; Inc(p, SizeOf(TImageFileHeader) + SizeOf(TImageOptionalHeader)); for i := 1 to NumSections do begin with PImageSectionHeader(p)^ do if PointerToRawData+SizeOfRawData > Result then Result := PointerToRawData + SizeOfRawData; Inc(p, SizeOf(TImageSectionHeader)); end;end; begin writeln('image size = ', GetExeSize(GetModuleHandle(nil))); readln;end. (app return) image size = 58368 real file size = 98818 40450 bytes overlay data ---------------------------------------------------------------------- KodeZwerg · 2023-10-25 08:08 Quote from: 440bx on October 25, 2023, 09:54:28 am@KodeZwerg & @Fibonacci Which binary ?... I don't see a binary anywhere in this thread ... did I miss an elephant in the room ? I just opened Lazarus, a new form, pressed run and on screen image i clicked "okay" Afterwards I run my app that I mentioned earlier over the compiled executable. ---------------------------------------------------------------------- 440bx · 2023-10-25 08:44 Thank you both, Fibonacci and KodeZwerg, for the additional information. Now I see what you guys are talking about. Just for the record, there is no need to calculate the "real executable size" because that is given in the PE header. Using the executable that Fibonacci posted: Code: Text [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i 40.0080 - IMAGE_NT_HEADERS - 40.0080 80 [4] Signature : 00004550 (PE00) 40.0084 - IMAGE_FILE_HEADER - 40.0084 84 [2] Machine : 14c 40.0084 + IMAGE_FILE_MACHINE_I386 40.0086 86 [2] Number of sections : b ( 11) 40.0088 88 [4] Time Date Stamp : 0 40.008c 8c [4] Pointer to symbol table : e400 40.0090 90 [4] Number of symbols : 314 (788) 40.0094 94 [2] Size of optional header : e0 (224) 40.0096 96 [2] Characteristics : 107 40.0096 + IMAGE_FILE_RELOCS_STRIPPED 1 40.0096 + IMAGE_FILE_EXECUTABLE_IMAGE 2 40.0096 + IMAGE_FILE_LINE_NUMS_STRIPPED 4 40.0096 + IMAGE_FILE_32BIT_MACHINE 100 Note that the "Pointer to symbol table" is where what you guys have been calling the "overlay" starts. Therefore the "executable" size is 0xE400 which is 58,368. The size of the "overlay" can be calculated by simply subtracting 0x400 0xE400 from the file size reported by the O/S. (note: corrected wrong 0x400 size to 0xE400) Also, if needed, the symbol table (referred to as "overlay" in this thread) has a header with information that may be useful (depending on what you're looking for.) e.g, in the file posted: Code: Text [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i address n/a - COFF SYMBOL TABLE - File pointer to symbol table : e400 Number of symbols : 314 ( 788) File pointer to string table : 1.1b68 String table size : 669a (26,266) Again, thank you both for providing the information I needed to make sense of what was being talked about in this thread. And just for the record, since the symbol table is NOT in a section, it MUST be after the last section, therefore using the "pointer to symbol" table as the "executable" size is reliable (barring a malformed PE file.) ETA: Corrected: 0x400 to 0xE400 ---------------------------------------------------------------------- msintle · 2023-10-25 12:54 Quote from: KodeZwerg on October 25, 2023, 04:51:49 amTo 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. Excellent discussion and contributions from everyone, thank you very much. To clarify - is it true (or not) that Lazarus adds debug information to the tail end of an executable in non-standard form? Probably asking for this behavior to be changed is moot, but yes, it does break things and would prevent debugging of stubs in the Lazarus IDE (not a happy proposition). Also: It does not happen on Linux. Meaning, debug data does not produce incorrect file sizes when reading the ELF header directly and computing the file size in that manner. For completeness, macOS is untested due to its app-bundle structure and how it is innately hostile to data appended to the tail end of a stub with things like code signing, notarization, etc. ---------------------------------------------------------------------- 440bx · 2023-10-25 13:51 Quote from: msintle on October 25, 2023, 02:54:09 pmTo 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. ---------------------------------------------------------------------- msintle · 2023-10-25 14:03 Quote from: 440bx on October 25, 2023, 03:51:01 pmQuote from: msintle on October 25, 2023, 02:54:09 pmTo 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 · 2023-10-25 15:03 Quote from: msintle on October 25, 2023, 04:03:13 pmIs 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][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i // -------- ---------------------------------------------------------------------/ / COFF IMAGE SYMBOL related types and contants // see://// http://www.delorie.com/djgpp/doc/coff/symtab.html//// for detailed information about the meaning of the fields in IMAGE_SYMBOL as// well as winnt.h const // these value can be found in the SymSectionNumber to indicate the meaning // of the SymValue field IMAGE_SYM_UNDEFINED = 0; // symbol is undefined or common IMAGE_SYM_ABSOLUTE = word(-1); // symbol is an absolute value IMAGE_SYM_DEBUG = word(-2); // symbol is a special debug item IMAGE_SYM_SECTION_MAX = word($FEFF); // values 0xFF00-0xFFFF are special IMAGE_SYM_SECTION_MAX_EX = high(int16); const // Symbol Types - fundamental types IMAGE_SYM_TYPE_NULL = $0000; // no type IMAGE_SYM_TYPE_VOID = $0001; // IMAGE_SYM_TYPE_CHAR = $0002; // type character IMAGE_SYM_TYPE_SHORT = $0003; // type short integer IMAGE_SYM_TYPE_INT = $0004; // IMAGE_SYM_TYPE_LONG = $0005; // IMAGE_SYM_TYPE_FLOAT = $0006; // IMAGE_SYM_TYPE_DOUBLE = $0007; // IMAGE_SYM_TYPE_STRUCT = $0008; // IMAGE_SYM_TYPE_UNION = $0009; // IMAGE_SYM_TYPE_ENUM = $000A; // enumeration IMAGE_SYM_TYPE_MOE = $000B; // member of enumeration IMAGE_SYM_TYPE_BYTE = $000C; // IMAGE_SYM_TYPE_WORD = $000D; // IMAGE_SYM_TYPE_UINT = $000E; // IMAGE_SYM_TYPE_DWORD = $000F; // IMAGE_SYM_TYPE_PCODE = $8000; // // Symbol types - derived types IMAGE_SYM_DTYPE_NULL = 0; // no derived type IMAGE_SYM_DTYPE_POINTER = 1; // pointer IMAGE_SYM_DTYPE_FUNCTION = 2; // function IMAGE_SYM_DTYPE_ARRAY = 3; // array // Type entry//// The type field in the symbol table entry contains information about the basic// and derived type for the symbol. This information is generated by the C// compilation system only if the -g option is used. Each symbol has exactly one// basic or fundamental type but can have more than one derived type. The format// of the 16-bit type entry is://// dx are two bits each, type is one nibble (4 bits)//// d6 d5 d4 d3 d2 d1 type////// Bits 0 through 3, called type, indicate one of the fundamental types given in// the Fundamental types table below://// Fundamental types//// Mnemonic Value Type//// T_NULL 0 type not assigned// 1 function argument T_ARG// (used only by compiler)//// 2 character T_CHAR// 3 short integer T_SHORT// 4 integer T_INT// 5 long integer T_LONG// 6 floating point T_FLOAT// 7 double word T_DOUBLE// 8 structure T_STRUCT// 9 union T_UNION// 10 enumeration T_ENUM// 11 member of enumeration T_MOE// 12 unsigned character T_UCHAR// 13 unsigned short T_USHORT// 14 unsigned integer T_UINT// 15 unsigned long T_ULONG//// Bits 4 through 15 are arranged as six 2-bit fields marked d1 through d6.// These d fields represent levels of the derived types given in given in the// Derived types table below://// Derived types//// Mnemonic Value Type//// DT_NON 0 no derived type// DT_PTR 1 pointer// DT_FCN 2 function// DT_ARY 3 array//// The following examples demonstrate the interpretation of the symbol table// entry representing type://// char *func();//// Here func is the name of a function that returns a pointer to a character.// The fundamental type of func is 2 (character), the d1 field is 2 (function),// and the d2 field is 1 (pointer).//// Therefore, the type word in the symbol table for func contains the// hexadecimal number 0x62, which is interpreted to mean a function that// returns a pointer to a character.////// short *tabptr[10][25][3];//// Here tabptr is a three-dimensional array of pointers to short integers. The// fundamental type of tabptr is 3 (short integer); the d1, d2, and d3 fields// each contains a 3 (array), and the d4 field is 1 (pointer).//// Therefore, the type entry in the symbol table contains the hexadecimal// number 0x7f3 indicating a three-dimensional array of pointers to short// integers. const // Symbol storage classes IMAGE_SYM_CLASS_END_OF_FUNCTION = byte(-1); IMAGE_SYM_CLASS_NULL = $0; IMAGE_SYM_CLASS_AUTOMATIC = $1; IMAGE_SYM_CLASS_EXTERNAL = $2; IMAGE_SYM_CLASS_STATIC = $3; IMAGE_SYM_CLASS_REGISTER = $4; IMAGE_SYM_CLASS_EXTERNAL_DEF = $5; IMAGE_SYM_CLASS_LABEL = $6; IMAGE_SYM_CLASS_UNDEFINED_LABEL = $7; IMAGE_SYM_CLASS_MEMBER_OF_STRUCT = $8; IMAGE_SYM_CLASS_ARGUMENT = $9; IMAGE_SYM_CLASS_STRUCT_TAG = $A; IMAGE_SYM_CLASS_MEMBER_OF_UNION = $B; IMAGE_SYM_CLASS_UNION_TAG = $C; IMAGE_SYM_CLASS_TYPE_DEFINITION = $D; IMAGE_SYM_CLASS_UNDEFINED_STATIC = $E; IMAGE_SYM_CLASS_ENUM_TAG = $F; IMAGE_SYM_CLASS_MEMBER_OF_ENUM = $10; IMAGE_SYM_CLASS_REGISTER_PARAM = $11; IMAGE_SYM_CLASS_BIT_FIELD = $12; IMAGE_SYM_CLASS_FAR_EXTERNAL = $44; IMAGE_SYM_CLASS_BLOCK = $64; IMAGE_SYM_CLASS_FUNCTION = $65; IMAGE_SYM_CLASS_END_OF_STRUCT = $66; IMAGE_SYM_CLASS_FILE = $67; IMAGE_SYM_CLASS_SECTION = $68; IMAGE_SYM_CLASS_WEAK_EXTERNAL = $69; IMAGE_SYM_CLASS_CLR_TOKEN = $6B; type // if the symbol name is inline and it is exactly 8 characters long then it // is not null terminated. This buffer type provides a large enough buffer // where the symbol name can be copied and be null terminated. Note that // this buffer may also be used to hold a symbol name that is present in the // string table, for this reason it cannot be made to simply hold 8 characters // and a null terminator since most strings in the string table are longer // than that. TSYM_NAME_BUFFER = array[0..1023] of char; TSYMBOL_NAME_INFO = packed record case integer of 0 : (SymName : packed array[0..7] of char); 1 : ( SymNameInline : boolean32; // use SymNameOffset if false SymNameOffset : DWORD; // offset to name into string table ); end; type // WARNING: this definition depends on how FPC does bitpacking and also how // it accesses bitpacked fields (this definition is tested and known // valid only for FPC v3.0.4) TSYM_TYPE_RANGE = -2..5; // the bitfields 1..6 start at index 0 TSYM_TYPE_DERIVED_RANGE = 0..5; TSYM_TYPE = bitpacked record case integer of 0 : ( FundamentalType : T4BITS; DerivedType1 : T2BITS; DerivedType2 : T2BITS; DerivedType3 : T2BITS; DerivedType4 : T2BITS; DerivedType5 : T2BITS; DerivedType6 : T2BITS; ); 1 : ( // tested the array representation with value $7F3 and it _seems_ to // work as expected. DerivedType : bitpacked array[TSYM_TYPE_RANGE] of T2BITS; ); 2 : ( TypeWord : word; ); end; type PIMAGE_SYMBOL = ^TIMAGE_SYMBOL; // 18 TIMAGE_SYMBOL = packed record SymNameInfo : TSYMBOL_NAME_INFO; SymValue : DWORD; SymSectionNumber : word; // usually the 1 based section number // but see IMAGE_SYM... constants SymType : TSYM_TYPE; // see IMAGE_SYM_TYPE... constants SymStorageClass : byte; // see IMAGE_SYM_CLASS... constants SymNumberOfAuxSymbols : byte; end; 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. ---------------------------------------------------------------------- msintle · 2023-10-25 15:11 Would it be too much to ask for a working sample? ---------------------------------------------------------------------- 440bx · 2023-10-25 16:55 Quote from: msintle on October 25, 2023, 05:11:24 pmWould 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][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i // ----- --------------------------------------------------------------------- // determine if there is a COFF symbol table in the PE file if InPeData.pe_FileHeader^.PointerToSymbolTable = 0 then begin // there is no COFF symbol table, there is nothing to dump exit(TRUE); end; with CoffSymTableInfo do begin CoffSymTableEntryCount := InPeData.pe_FileHeader^.NumberOfSymbols; if CoffSymTableEntryCount = 0 then begin // there is an address for a COFF symbol table but the size indicates // there is no symbol table, can't dump a zero size table // might not be a bad idea to issue a warning about this situation if IsDebuggerPresent() then asm int 3 end; exit(TRUE); end; end; // ----------------------------------------------------------- --------------- // there is a table and it is not empty, get a pointer to it and ensure the // entire table is readable with InPeData, CoffSymTableInfo do begin CoffSymTable := PIMAGE_SYMBOL ( pe_Fileptr + pe_FileHeader^.PointerToSymbolTable ); if IsBadReadPtr(CoffSymTable, CoffSymTableEntryCount * sizeof(CoffSymTable^)) then exit; // determine the pointer to the COFF SYMBOL NAMES string table StringTable := pointer(CoffSymTable + CoffSymTableEntryCount); if IsBadReadPtr(StringTable, sizeof(StringTable^.Size)) then exit; if IsBadReadPtr(StringTable, StringTable^.Size) then exit; end; 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][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i // ------- --------------------------------------------------------------------- function CoffStringTableCount ( const InStringTable : PSYMBOL_STRING_TABLE ) : DWORD; { used to determine the number of strings in the strings table but, this } { function is _not_ currently used and it is usually removed by the linker } var StringTableSize : DWORD; StringPtr : pchar; StringLength : DWORD; StringCount : DWORD; ScannedBytes : DWORD; begin result := 0; StringCount := 0; // when done counting, result will have this value with InStringTable^ do begin StringTableSize := Size; StringPtr := Characters; ScannedBytes := pbyte(StringPtr) - pbyte(InStringTable); // verify that the number of scanned bytes equals the size of the size field // in the string table. if that is not the case then a change has been made // in the record definition of a string table which may prevent this routine // from working properly. if ScannedBytes <> sizeof(Size) then exit; end; while TRUE do begin if IsBadStringPtrA(StringPtr, STRING_MAX_LENGTH) then exit; StringLength := strlen(StringPtr); ScannedBytes += StringLength + 1; // account for the null terminator if ScannedBytes >= StringTableSize then break; inc(StringCount); StringPtr += StringLength + 1; end; result := StringCount; // return the count of stringsend; I posted the data type definitions used by the code in the previous post. If you have any questions, just ask. HTH. ---------------------------------------------------------------------- 440bx · 2023-10-26 21:05 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. ---------------------------------------------------------------------- KodeZwerg · 2023-10-27 01:13 Quote from: msintle on October 25, 2023, 02:54:09 pmQuote from: KodeZwerg on October 25, 2023, 04:51:49 amTo 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][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i program project1; {$APPTYPE CONSOLE} uses Windows, SysUtils, Classes; function EndOfPE(const AFilename: string): UInt64;var FS: TStream; dwSig: DWORD; wSig: Word; i: Integer; HeaderDos: IMAGE_DOS_HEADER; HeaderFile: IMAGE_FILE_HEADER; HeaderOptional64: IMAGE_OPTIONAL_HEADER64; HeaderOptional32: IMAGE_OPTIONAL_HEADER32; HeaderSection: array of IMAGE_SECTION_HEADER; PHeaderSection: array of PImageSectionHeader; Is64Bit: Boolean;begin SetLastError(ERROR_SUCCESS); Result := 0; FS := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyNone); try FS.Read(HeaderDos, SizeOf(IMAGE_DOS_HEADER)); if (HeaderDos.e_magic = IMAGE_DOS_SIGNATURE) then begin try FS.Seek(HeaderDos._lfanew, soFromBeginning); FS.Read(wSig, SizeOf(wSig)); FS.Seek(HeaderDos._lfanew, soFromBeginning); FS.Read(dwSig, SizeOf(dwSig)); if (HeaderDos.e_magic <> IMAGE_DOS_SIGNATURE) then Exit; if (wSig <> IMAGE_NT_SIGNATURE) then Exit; FS.Read(HeaderFile, SizeOf(IMAGE_FILE_HEADER)); if (HeaderFile.SizeOfOptionalHeader > 0) then begin i := FS.Position; FS.Read(wSig, SizeOf(wSig)); Is64Bit := (wSig = IMAGE_NT_OPTIONAL_HDR64_MAGIC); FS.Seek(i, soFromBeginning); FS.Read(dwSig, SizeOf(dwSig)); FS.Seek(i, soFromBeginning); if Is64Bit then FS.Read(HeaderOptional64, SizeOf(IMAGE_OPTIONAL_HEADER64)) else FS.Read(HeaderOptional32, SizeOf(IMAGE_OPTIONAL_HEADER32)); end; if (HeaderFile.NumberOfSections > 0) then begin try SetLength(HeaderSection, HeaderFile.NumberOfSections); SetLength(PHeaderSection, HeaderFile.NumberOfSections); for i := 1 to HeaderFile.NumberOfSections do begin FS.Read(HeaderSection[i - 1], SizeOf(IMAGE_SECTION_HEADER)); PHeaderSection[i - 1] := @HeaderSection[i - 1]; with PHeaderSection[i - 1]^ do if (PointerToRawData + SizeOfRawData > Result) then Result := PointerToRawData + SizeOfRawData; end; except end; end; finally end; end; finally FS.Free; end;end; begin WriteLn(EndOfPE(ParamStr(0))); ReadLn;end. ---------------------------------------------------------------------- 440bx · 2023-10-27 02:47 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.) ---------------------------------------------------------------------- msintle · 2023-10-28 10:40 Quote from: KodeZwerg on October 27, 2023, 03:13:05 amQuote from: msintle on October 25, 2023, 02:54:09 pmQuote from: KodeZwerg on October 25, 2023, 04:51:49 amTo 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][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i program project1; {$APPTYPE CONSOLE} uses Windows, SysUtils, Classes; function EndOfPE(const AFilename: string): UInt64;var FS: TStream; dwSig: DWORD; wSig: Word; i: Integer; HeaderDos: IMAGE_DOS_HEADER; HeaderFile: IMAGE_FILE_HEADER; HeaderOptional64: IMAGE_OPTIONAL_HEADER64; HeaderOptional32: IMAGE_OPTIONAL_HEADER32; HeaderSection: array of IMAGE_SECTION_HEADER; PHeaderSection: array of PImageSectionHeader; Is64Bit: Boolean;begin SetLastError(ERROR_SUCCESS); Result := 0; FS := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyNone); try FS.Read(HeaderDos, SizeOf(IMAGE_DOS_HEADER)); if (HeaderDos.e_magic = IMAGE_DOS_SIGNATURE) then begin try FS.Seek(HeaderDos._lfanew, soFromBeginning); FS.Read(wSig, SizeOf(wSig)); FS.Seek(HeaderDos._lfanew, soFromBeginning); FS.Read(dwSig, SizeOf(dwSig)); if (HeaderDos.e_magic <> IMAGE_DOS_SIGNATURE) then Exit; if (wSig <> IMAGE_NT_SIGNATURE) then Exit; FS.Read(HeaderFile, SizeOf(IMAGE_FILE_HEADER)); if (HeaderFile.SizeOfOptionalHeader > 0) then begin i := FS.Position; FS.Read(wSig, SizeOf(wSig)); Is64Bit := (wSig = IMAGE_NT_OPTIONAL_HDR64_MAGIC); FS.Seek(i, soFromBeginning); FS.Read(dwSig, SizeOf(dwSig)); FS.Seek(i, soFromBeginning); if Is64Bit then FS.Read(HeaderOptional64, SizeOf(IMAGE_OPTIONAL_HEADER64)) else FS.Read(HeaderOptional32, SizeOf(IMAGE_OPTIONAL_HEADER32)); end; if (HeaderFile.NumberOfSections > 0) then begin try SetLength(HeaderSection, HeaderFile.NumberOfSections); SetLength(PHeaderSection, HeaderFile.NumberOfSections); for i := 1 to HeaderFile.NumberOfSections do begin FS.Read(HeaderSection[i - 1], SizeOf(IMAGE_SECTION_HEADER)); PHeaderSection[i - 1] := @HeaderSection[i - 1]; with PHeaderSection[i - 1]^ do if (PointerToRawData + SizeOfRawData > Result) then Result := PointerToRawData + SizeOfRawData; end; except end; end; finally end; end; finally FS.Free; end;end; begin WriteLn(EndOfPE(ParamStr(0))); ReadLn;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 · 2023-10-28 10:43 Quote from: 440bx on October 26, 2023, 11:05:06 pmAttached 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 · 2023-10-28 10:50 Quote from: 440bx on October 27, 2023, 04:47:24 amWhat 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? ---------------------------------------------------------------------- 440bx · 2023-10-28 11:01 Quote from: msintle on October 28, 2023, 12:43:54 pmThank 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. ---------------------------------------------------------------------- 440bx · 2023-10-28 11:20 Quote from: msintle on October 28, 2023, 12:50:20 pmEDIT: 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. ---------------------------------------------------------------------- 440bx · 2023-10-28 14:02 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. ---------------------------------------------------------------------- msintle · 2023-10-28 16:39 Quote from: 440bx on October 28, 2023, 01:01:35 pmQuote from: msintle on October 28, 2023, 12:43:54 pmThank 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 · 2023-10-28 16:47 Quote from: 440bx on October 28, 2023, 04:02:35 pmAttached 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][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i Directory of c:\Users\c\Desktop\CoffSymTable2\lib\x86_64-win64 10/28/2023 07:40 PM