You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

部分api-ms-win-开头DLL的IMAGE_FILE_HEADER.TimeDateStamp非Unix时间戳问题

解析IMAGE_FILE_HEADER.TimeDateStamp字段的异常问题

IMAGE_FILE_HEADER.TimeDateStamp的官方描述为:

镜像时间戳的低32位,表示链接器创建镜像的日期和时间,值为自1970年1月1日00:00:00 UTC起经过的秒数。

本质这就是Unix时间戳(Epoch时间)。在Win7x64系统中,绝大多数DLL/EXE的该字段解析后都是合理日期,比如:

  • 2020-01-30 02:23:55 = C:\Windows\system32\USER32.dll(PE签名后第6字节的文件偏移$F8处的字节为$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(对应当时的2.95版本)

但在Mozilla产品(如Firefox 115.20.0esr (x64)和Thunderbird 68.8.0 (x64))中,部分以api-ms-win-开头的DLL,按同样方式解析会得到异常日期:

  • 2075-10-13 12:54:12 = C:\Program Files\Firefox\api-ms-win-crt-environment-l1-1-0.dll(PE签名后第6字节的文件偏移$0D处的字节为$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

Process Explorer 16.32x64却能显示这类DLL的合理日期,比如:

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

相关定义代码(非核心):

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;

核心代码(读取两个DLL,一个解析异常一个正常):

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;

正确解析方法

这些api-ms-win-开头的DLL并非C#编译产物,它们是Windows的API集(API Sets),属于特殊的PE文件——本身没有实际代码,仅作为转发器指向系统中的真实DLL。这类文件的IMAGE_FILE_HEADER.TimeDateStamp字段未按标准Unix时间戳写入,而是存储了其他值。

要获取这类文件的正确创建日期,不能直接解析TimeDateStamp字段,推荐两种方式:

1. 读取文件系统时间戳

使用Windows API直接获取文件在磁盘上的创建/修改时间,这也是Process Explorer显示正确日期的方式。修改后的代码示例:

procedure TfrmMain.Button2Click(Sender: TObject);
var
  hFile: THandle;
  ftCreate, ftAccess, ftWrite: TFileTime;
begin
  hFile := CreateFileW(PWideChar('C:\Program Files\Firefox\api-ms-win-core-localization-l1-2-0.dll'),
                       GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, 0, 0);
  if hFile <> INVALID_HANDLE_VALUE then begin
    if GetFileTime(hFile, @ftCreate, @ftAccess, @ftWrite) then begin
      Caption := Caption + ' ' + FileTimeToString(ftWrite); // 最后修改时间更接近编译时间
    end;
    CloseHandle(hFile);
  end;
end;

2. 解析API集转发表获取真实DLL

API集的转发信息存储在.text节中,格式为target.dll,exported_function。可读取该节内容提取目标DLL路径,再加载目标DLL读取其TimeDateStamp字段。但这种方式复杂度较高,仅需获取日期时,读取文件系统时间更简单可靠。


内容的提问来源于stack exchange,提问作者AmigoJack

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 06:14:58