3

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:

  • 2020-01-30 02:23:55 = C:\Windows\system32\USER32.dll
    (bytes at file offset $F8, 6 bytes after the PE signature, are $3B $3E $32 $5E)
  • 2011-12-16 08:37:19 = C:\Windows\system32\msvcrt.dll
  • 2019-10-26 18:30:19 = C:\Program Files\Open-Shell\ClassicExplorer64.dll
  • 2020-03-12 06:38:45 = C:\Program Files\Java\jre1.8.0_251\bin\java.dll
  • 2025-07-05 11:00:00 = C:\Program Files\7-Zip\7-zip.dll
  • 2003-06-25 04:22:10 = C:\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:

  • 2075-10-13 12:54:12 = C:\Program Files\Firefox\api-ms-win-crt-environment-l1-1-0.dll
    (bytes at file offset $0D, 6 bytes after the PE signature, are $74 $D7 $F8 $C6)
  • 2105-11-25 12:44:53 = C:\Program Files\Firefox\api-ms-win-crt-time-l1-1-0.dll
  • 1988-11-01 22:53:54 = C:\Program Files\Firefox\api-ms-win-crt-utility-l1-1-0.dll
  • 2055-06-07 02:00:36 = C:\Program Files\Firefox\api-ms-win-core-processthreads-l1-1-1.dll
  • 2062-12-31 21:17:22 = C:\Program Files\Thunderbird\api-ms-win-crt-environment-l1-1-0.dll
  • 1974-04-21 14:27:11 = C:\Program Files\Thunderbird\api-ms-win-core-processthreads-l1-1-1.dll

Is 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:

  • 2025-01-27 22:01 = C:\Program Files\Firefox\api-ms-win-crt-environment-l1-1-0.dll

This 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;
1

1 Answer 1

6

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.

Sign up to request clarification or add additional context in comments.

1 Comment

Following the links: What is DLL import binding? and How to recognize different types of timestamps from quite a long way away. Today's version of the cited article doesn't link to an article anymore - once again the Wayback Machine helped me.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.