IMAGE_FILE_HEADER.TimeDateStamp is described as:
The low 32 bits of the time stamp of the image. This represents the date and time the image was created by the linker. The value is represented in the number of seconds elapsed since midnight (00:00:00), January 1, 1970, Universal Coordinated Time, according to the system clock.
So it's a Unix time/epoch. For the vast majority of DLLs (or EXEs) on an old Win7x64 system I get plausible values, like:
C:\Windows\system32\USER32.dll$F8, 6 bytes after the PE signature, are $3B $3E $32 $5E)C:\Windows\system32\msvcrt.dllC:\Program Files\Open-Shell\ClassicExplorer64.dllC:\Program Files\Java\jre1.8.0_251\bin\java.dllC:\Program Files\7-Zip\7-zip.dllC:\Program Files (x86)\Winamp\winamp.exe (version 2.95 from back then)But for Mozilla products like Firefox 115.20.0esr (x64) and Thunderbird 68.8.0 (x64) there are DLLs whose name start with api-ms-win- which produce weird dates/times when interpreted the same way:
C:\Program Files\Firefox\api-ms-win-crt-environment-l1-1-0.dll$0D, 6 bytes after the PE signature, are $74 $D7 $F8 $C6)C:\Program Files\Firefox\api-ms-win-crt-time-l1-1-0.dllC:\Program Files\Firefox\api-ms-win-crt-utility-l1-1-0.dllC:\Program Files\Firefox\api-ms-win-core-processthreads-l1-1-1.dllC:\Program Files\Thunderbird\api-ms-win-crt-environment-l1-1-0.dllC:\Program Files\Thunderbird\api-ms-win-core-processthreads-l1-1-1.dllIs it just gibberish in those DLLs? How to interpret them correctly? Were these DLLs compiled in C# and it's some kind of compiler bug? Process Explorer 16.32x64 displays a much better date for such DLLs:
C:\Program Files\Firefox\api-ms-win-crt-environment-l1-1-0.dllThis should be reproducible on other Windows systems with other DLLs, too. The following is needed in case it's not defined already - shouldn't be of much interest:
type
IMAGE_OPTIONAL_HEADER32 = record
Magic: Word;
MajorLinkerVersion, MinorLinkerVersion: Byte;
SizeOfCode, SizeOfInitializedData, SizeOfUninitializedData,
AddressOfEntryPoint, BaseOfCode, BaseOfData: DWORD;
// NT additional fields.
ImageBase, SectionAlignment, FileAlignment: DWORD;
MajorOperatingSystemVersion, MinorOperatingSystemVersion,
MajorImageVersion, MinorImageVersion,
MajorSubsystemVersion, MinorSubsystemVersion: Word;
Win32VersionValue, SizeOfImage, SizeOfHeaders, CheckSum: DWORD;
Subsystem, DllCharacteristics: Word;
SizeOfStackReserve, SizeOfStackCommit, SizeOfHeapReserve,
SizeOfHeapCommit, LoaderFlags, NumberOfRvaAndSizes: DWORD;
DataDirectory: array [0..IMAGE_NUMBEROF_DIRECTORY_ENTRIES - 1] of IMAGE_DATA_DIRECTORY;
end;
PIMAGE_NT_HEADERS32 = ^IMAGE_NT_HEADERS;
IMAGE_NT_HEADERS = record
Signature: DWORD;
FileHeader: IMAGE_FILE_HEADER;
OptionalHeader: IMAGE_OPTIONAL_HEADER32;
end;
TImgSecHdrMisc = record
case Integer of
0: (PhysicalAddress: DWORD);
1: (VirtualSize: DWORD);
end;
PIMAGE_SECTION_HEADER = ^IMAGE_SECTION_HEADER;
_IMAGE_SECTION_HEADER = record
Name: array [0..IMAGE_SIZEOF_SHORT_NAME - 1] of BYTE;
Misc: TImgSecHdrMisc;
VirtualAddress, SizeOfRawData, PointerToRawData,
PointerToRelocations, PointerToLinenumbers: DWORD;
NumberOfRelocations, NumberOfLinenumbers: WORD;
Characteristics: DWORD;
end;
TLoadedImage= record
ModuleName: PAnsiChar;
hFile: THandle;
MappedAddress: PUCHAR;
FileHeader: PIMAGE_NT_HEADERS32;
LastRvaSection: PIMAGE_SECTION_HEADER;
NumberOfSections: ULONG;
Sections: PIMAGE_SECTION_HEADER;
Characteristics: ULONG;
fSystemImage, fDOSImage: ByteBool;
Links: LIST_ENTRY;
SizeOfImage: ULONG;
end;
function MapAndLoad(ImageName, DllPath: PAnsiChar; var LoadedImage: TLoadedImage;
DotDll: BOOL; ReadOnly: BOOL): BOOL; stdcall; external 'imagehlp.dll';
function UnMapAndLoad(const LoadedImage: TLoadedImage): BOOL; stdcall; external 'imagehlp.dll';
function StrPasW( const WStr: PWideChar ): WideString;
begin
Result:= WStr;
end;
function SystemTimeToString( t: SystemTime ): WideString;
const
BUF= 200;
var
w1: PWideChar;
begin
GetMem( w1, BUF* 2 );
result:= '';
if GetDateFormatW( LOCALE_USER_DEFAULT, 0, @t, nil, w1, BUF )<> 0 then result:= result+ StrPasW( w1 );
if GetTimeFormatW( LOCALE_USER_DEFAULT, 0, @t, nil, w1, BUF )<> 0 then result:= result+ ' '+ StrPasW( w1 );
FreeMem( w1 );
end;
function FileTimeToString( t: FileTime ): WideString;
var
s: SystemTime;
begin
FileTimeToSystemTime( t, s );
result:= SystemTimeToString( s );
end;
The interesting part is here, where I read 2 different DLLs - one has a date in the future, one a plausible date:
function IntToFileTime( i: Int64 ): TFileTime;
begin
result.dwLowDateTime := Int64Rec(i).Lo;
result.dwHighDateTime := Int64Rec(i).Hi;
end;
const
DELTA_UNIXTIME_FILETIME: Int64= 116444736000000000;
function UnixTimeToFileTime( iUnix: Int64 ): TFileTime;
begin
result:= IntToFileTime( iUnix* Int64(10000000)+ DELTA_UNIXTIME_FILETIME ); // 1 s to 100 ns
end;
procedure TfrmMain.Button1Click(Sender: TObject);
var
vLI: TLoadedImage;
begin
if MapAndLoad( PAnsiChar('C:\Program Files\Firefox\api-ms-win-core-localization-l1-2-0.dll'), nil, vLI, FALSE, TRUE ) then begin
Caption:= Caption+ ' '+ FileTimeToString( UnixTimeToFileTime( vLI.FileHeader^.FileHeader.TimeDateStamp ) ); // '2040-06-21 10:44:07' = weird
UnmapAndLoad( vLI );
end;
if MapAndLoad( PAnsiChar('C:\Program Files\Firefox\nss3.dll'), nil, vLI, FALSE, TRUE ) then begin
Caption:= Caption+ ' '+ FileTimeToString( UnixTimeToFileTime( vLI.FileHeader^.FileHeader.TimeDateStamp ) ); // '2025-01-27 14:21:04' = correct
UnmapAndLoad( vLI );
end;
end;
From Raymond Chen's blog:
Why are the module timestamps in Windows 10 so nonsensical?
One of the fields in the Portable Executable (PE) header is called
TimeDateStamp. It’s a 32-bit value representing the time the file was created, in the form of seconds since January 1, 1970 UTC. But starting in Windows 10, those timestamps are all nonsense. If you look at the timestamps of various files, you’ll see that they appear to be random numbers, completely unrelated to any timestamp. What’s going on?One of the changes to the Windows engineering system begun in Windows 10 is the move toward reproducible builds. This means that if you start with the exact same source code, then you should finish with the exact same binary code.
There are lots of things that hamper reproducibility...
...
Timestamps are another source of non-determinism. Even if all the inputs are identical, the outputs will still be different because of the timestamps.
Okay, at least we can fix the issue with the file format. Setting the timestamp to be a hash of the resulting binary preserves reproducibility.
“Okay, but why not set the file timestamp to the the timestamp of the source code the binary was created from? That way, it’s still a timestamp at least.” That still breaks reproducibility, because that means that touching a file without making any changes will result in a change in binary output.
Remember what the timestamp is used for: It’s used by the module loader to determine whether bound imports should be trusted. We’ve already seen cases where the timestamp is inaccurate. For example, if you rebind a DLL, then the rebound DLL has the same timestamp as the original, rather than the timestamp of the rebind, because you don’t want to break the bindings of other DLLs that bound to your DLL.
So the timestamp is already unreliable.
The timestamp is really a unique ID that tells the loader, “The exports of this DLL have not changed since the last time anybody bound to it.” And a hash is a reproducible unique ID.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With