部分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

