TL;DR: NtSetInformationFile, MoveFileW, NtSetInformationFile sequence overwrites all timestamps in $STANDARD_INFORMATION, and $FILE_NAME attributes.
There is no surprise here. After changing the file content, without changing the timestamps, change the file timestamps regardless of the content is a predictable next step. On first look, it's not possible - all timestamps from $FN attributes and $SI attribute modification timestamps are not accessible directly. But for some reason, Microsoft provided an easy way which looks like a feature.
The first thing is $SI attribute timestamps. Kernel API functions NtQueryInformationFile and NtSetInformationFile let you access and change all four of them, FILE_BASIC_INFORMATION:
typedef struct _FILE_BASIC_INFORMATION {
LARGE_INTEGER CreationTime;
LARGE_INTEGER LastAccessTime;
LARGE_INTEGER LastWriteTime;
LARGE_INTEGER ChangeTime;
ULONG FileAttributes;
} FILE_BASIC_INFORMATION, *PFILE_BASIC_INFORMATION;
including the attribute modification timestamps:
| Before | After |
|---|---|
MFT Entry Header Values: Entry: 73341 Sequence: 325 $LogFile Sequence Number: 190675918603 Allocated File Links: 2 $STANDARD_INFORMATION Attribute Values: Flags: Archive Owner ID: 0 Security ID: 1171 (S-1-5-32-544) Last User Journal Update Sequence Number: 30364219824 Created: 2019-09-07 01:29:07.401341000 (Eastern Daylight Time) File Modified: 2019-09-07 01:29:07.408341400 (Eastern Daylight Time) MFT Modified: 2019-09-07 01:29:07.408341400 (Eastern Daylight Time) Accessed: 2019-09-07 01:29:07.401341000 (Eastern Daylight Time) $FILE_NAME Attribute Values: Flags: Archive Name: TESTFI~1.TST Parent MFT Entry: 367786 Sequence: 290 Allocated Size: 0 Actual Size: 0 Created: 2019-09-07 01:29:07.401341000 (Eastern Daylight Time) File Modified: 2019-09-07 01:29:07.401341000 (Eastern Daylight Time) MFT Modified: 2019-09-07 01:29:07.401341000 (Eastern Daylight Time) Accessed: 2019-09-07 01:29:07.401341000 (Eastern Daylight Time) $FILE_NAME Attribute Values: Flags: Archive Name: TestFile106492.tst Parent MFT Entry: 367786 Sequence: 290 Allocated Size: 0 Actual Size: 0 Created: 2019-09-07 01:29:07.401341000 (Eastern Daylight Time) File Modified: 2019-09-07 01:29:07.401341000 (Eastern Daylight Time) MFT Modified: 2019-09-07 01:29:07.401341000 (Eastern Daylight Time) Accessed: 2019-09-07 01:29:07.401341000 (Eastern Daylight Time) Attributes: Type: $STANDARD_INFORMATION (16-0) Name: N/A Resident size: 72 Type: $FILE_NAME (48-3) Name: N/A Resident size: 90 Type: $FILE_NAME (48-2) Name: N/A Resident size: 102 Type: $DATA (128-4) Name: N/A Non-Resident size: 106492 init_size: 106492 5389569 5389570 5389571 5389572 5389573 5389574 5389575 5389576 5389577 5389578 5389579 5389580 5389581 5389582 5389583 5389584 13497149 13497150 13497151 13497152 13497153 13497154 13497155 13497156 13497157 13497158 | MFT Entry Header Values: Entry: 73341 Sequence: 325 $LogFile Sequence Number: 190676047709 Allocated File Links: 2 $STANDARD_INFORMATION Attribute Values: Flags: Archive Owner ID: 0 Security ID: 1171 (S-1-5-32-544) Last User Journal Update Sequence Number: 30364248096 Created: 1998-09-06 19:34:03.473275400 (Eastern Daylight Time) File Modified: 1998-09-06 19:34:03.473275400 (Eastern Daylight Time) MFT Modified: 1998-09-06 19:34:03.473275400 (Eastern Daylight Time) Accessed: 1998-09-06 19:34:03.473275400 (Eastern Daylight Time) $FILE_NAME Attribute Values: Flags: Archive Name: TESTFI~1.TST Parent MFT Entry: 367786 Sequence: 290 Allocated Size: 0 Actual Size: 0 Created: 2019-09-07 01:29:07.401341000 (Eastern Daylight Time) File Modified: 2019-09-07 01:29:07.401341000 (Eastern Daylight Time) MFT Modified: 2019-09-07 01:29:07.401341000 (Eastern Daylight Time) Accessed: 2019-09-07 01:29:07.401341000 (Eastern Daylight Time) $FILE_NAME Attribute Values: Flags: Archive Name: TestFile106492.tst Parent MFT Entry: 367786 Sequence: 290 Allocated Size: 0 Actual Size: 0 Created: 2019-09-07 01:29:07.401341000 (Eastern Daylight Time) File Modified: 2019-09-07 01:29:07.401341000 (Eastern Daylight Time) MFT Modified: 2019-09-07 01:29:07.401341000 (Eastern Daylight Time) Accessed: 2019-09-07 01:29:07.401341000 (Eastern Daylight Time) Attributes: Type: $STANDARD_INFORMATION (16-0) Name: N/A Resident size: 72 Type: $FILE_NAME (48-3) Name: N/A Resident size: 90 Type: $FILE_NAME (48-2) Name: N/A Resident size: 102 Type: $DATA (128-4) Name: N/A Non-Resident size: 106492 init_size: 106492 5389569 5389570 5389571 5389572 5389573 5389574 5389575 5389576 5389577 5389578 5389579 5389580 5389581 5389582 5389583 5389584 13497149 13497150 13497151 13497152 13497153 13497154 13497155 13497156 13497157 13497158 |
Next thing is $FN attribute timestamps. There is an interesting "side effect" of MoveFileW function. OS propagates all four timestamps from $SI attribute to $FN attributes, including the attribute modification timestamp. Looks like, it breaks all sense of the attribute modification timestamp, but for some reason, it works this way:
| Before | After |
|---|---|
MFT Entry Header Values: Entry: 73341 Sequence: 325 $LogFile Sequence Number: 190676047709 Allocated File Links: 2 $STANDARD_INFORMATION Attribute Values: Flags: Archive Owner ID: 0 Security ID: 1171 (S-1-5-32-544) Last User Journal Update Sequence Number: 30364248096 Created: 1998-09-06 19:34:03.473275400 (Eastern Daylight Time) File Modified: 1998-09-06 19:34:03.473275400 (Eastern Daylight Time) MFT Modified: 1998-09-06 19:34:03.473275400 (Eastern Daylight Time) Accessed: 1998-09-06 19:34:03.473275400 (Eastern Daylight Time) $FILE_NAME Attribute Values: Flags: Archive Name: TESTFI~1.TST Parent MFT Entry: 367786 Sequence: 290 Allocated Size: 0 Actual Size: 0 Created: 2019-09-07 01:29:07.401341000 (Eastern Daylight Time) File Modified: 2019-09-07 01:29:07.401341000 (Eastern Daylight Time) MFT Modified: 2019-09-07 01:29:07.401341000 (Eastern Daylight Time) Accessed: 2019-09-07 01:29:07.401341000 (Eastern Daylight Time) $FILE_NAME Attribute Values: Flags: Archive Name: TestFile106492.tst Parent MFT Entry: 367786 Sequence: 290 Allocated Size: 0 Actual Size: 0 Created: 2019-09-07 01:29:07.401341000 (Eastern Daylight Time) File Modified: 2019-09-07 01:29:07.401341000 (Eastern Daylight Time) MFT Modified: 2019-09-07 01:29:07.401341000 (Eastern Daylight Time) Accessed: 2019-09-07 01:29:07.401341000 (Eastern Daylight Time) Attributes: Type: $STANDARD_INFORMATION (16-0) Name: N/A Resident size: 72 Type: $FILE_NAME (48-3) Name: N/A Resident size: 90 Type: $FILE_NAME (48-2) Name: N/A Resident size: 102 Type: $DATA (128-4) Name: N/A Non-Resident size: 106492 init_size: 106492 5389569 5389570 5389571 5389572 5389573 5389574 5389575 5389576 5389577 5389578 5389579 5389580 5389581 5389582 5389583 5389584 13497149 13497150 13497151 13497152 13497153 13497154 13497155 13497156 13497157 13497158 | MFT Entry Header Values: Entry: 73341 Sequence: 325 $LogFile Sequence Number: 190676622365 Allocated File Links: 2 $STANDARD_INFORMATION Attribute Values: Flags: Archive Owner ID: 0 Security ID: 1171 (S-1-5-32-544) Last User Journal Update Sequence Number: 30364411688 Created: 1998-09-06 19:34:03.473275400 (Eastern Daylight Time) File Modified: 1998-09-06 19:34:03.473275400 (Eastern Daylight Time) MFT Modified: 2019-09-07 01:50:28.453613000 (Eastern Daylight Time) Accessed: 1998-09-06 19:34:03.473275400 (Eastern Daylight Time) $FILE_NAME Attribute Values: Flags: Archive Name: TESTFI~1.TST Parent MFT Entry: 367786 Sequence: 290 Allocated Size: 106496 Actual Size: 106492 Created: 1998-09-06 19:34:03.473275400 (Eastern Daylight Time) File Modified: 1998-09-06 19:34:03.473275400 (Eastern Daylight Time) MFT Modified: 1998-09-06 19:34:03.473275400 (Eastern Daylight Time) Accessed: 1998-09-06 19:34:03.473275400 (Eastern Daylight Time) $FILE_NAME Attribute Values: Flags: Archive Name: TestFile106492.tst~21 Parent MFT Entry: 367786 Sequence: 290 Allocated Size: 106496 Actual Size: 106492 Created: 1998-09-06 19:34:03.473275400 (Eastern Daylight Time) File Modified: 1998-09-06 19:34:03.473275400 (Eastern Daylight Time) MFT Modified: 1998-09-06 19:34:03.473275400 (Eastern Daylight Time) Accessed: 1998-09-06 19:34:03.473275400 (Eastern Daylight Time) Attributes: Type: $STANDARD_INFORMATION (16-0) Name: N/A Resident size: 72 Type: $FILE_NAME (48-6) Name: N/A Resident size: 90 Type: $FILE_NAME (48-5) Name: N/A Resident size: 108 Type: $DATA (128-4) Name: N/A Non-Resident size: 106492 init_size: 106492 5389569 5389570 5389571 5389572 5389573 5389574 5389575 5389576 5389577 5389578 5389579 5389580 5389581 5389582 5389583 5389584 13497149 13497150 13497151 13497152 13497153 13497154 13497155 13497156 13497157 13497158 |
As a solution, we can use a simple sequence:
- NtQueryInformationFile - preserve file attributes
- MoveFileW - rename file to some temporal name
- NtSetInformationFile - set $SI timestamps
- MoveFileW - rename file to the original name and propagate $SI timestamps to $FN timestamps
- NtSetInformationFile - set $SI timestamps again
| Before | After |
|---|---|
MFT Entry Header Values: Entry: 73341 Sequence: 326 $LogFile Sequence Number: 190677530353 Allocated File Links: 2 $STANDARD_INFORMATION Attribute Values: Flags: Archive Owner ID: 0 Security ID: 829 (S-1-5-21-1565470440-1633509369-3944455776-1000) Last User Journal Update Sequence Number: 30364669728 Created: 2019-09-07 02:22:25.075237500 (Eastern Daylight Time) File Modified: 2019-09-07 02:22:25.084238000 (Eastern Daylight Time) MFT Modified: 2019-09-07 02:22:25.084238000 (Eastern Daylight Time) Accessed: 2019-09-07 02:22:25.075237500 (Eastern Daylight Time) $FILE_NAME Attribute Values: Flags: Archive Name: TESTFI~1.TST Parent MFT Entry: 367786 Sequence: 290 Allocated Size: 0 Actual Size: 0 Created: 2019-09-07 02:22:25.075237500 (Eastern Daylight Time) File Modified: 2019-09-07 02:22:25.075237500 (Eastern Daylight Time) MFT Modified: 2019-09-07 02:22:25.075237500 (Eastern Daylight Time) Accessed: 2019-09-07 02:22:25.075237500 (Eastern Daylight Time) $FILE_NAME Attribute Values: Flags: Archive Name: TestFile106492.tst Parent MFT Entry: 367786 Sequence: 290 Allocated Size: 0 Actual Size: 0 Created: 2019-09-07 02:22:25.075237500 (Eastern Daylight Time) File Modified: 2019-09-07 02:22:25.075237500 (Eastern Daylight Time) MFT Modified: 2019-09-07 02:22:25.075237500 (Eastern Daylight Time) Accessed: 2019-09-07 02:22:25.075237500 (Eastern Daylight Time) Attributes: Type: $STANDARD_INFORMATION (16-0) Name: N/A Resident size: 72 Type: $FILE_NAME (48-3) Name: N/A Resident size: 90 Type: $FILE_NAME (48-2) Name: N/A Resident size: 102 Type: $DATA (128-4) Name: N/A Non-Resident size: 106492 init_size: 106492 255125 255126 255127 255128 255129 255130 255131 255132 255133 255134 255135 255136 255137 255138 255139 255140 13497149 13497150 13497151 13497152 13497153 13497154 13497155 13497156 13497157 13497158 | MFT Entry Header Values: Entry: 73341 Sequence: 326 $LogFile Sequence Number: 190677614018 Allocated File Links: 2 $STANDARD_INFORMATION Attribute Values: Flags: Archive Owner ID: 0 Security ID: 829 (S-1-5-21-1565470440-1633509369-3944455776-1000) Last User Journal Update Sequence Number: 30364689880 Created: 1998-09-06 20:25:24.029473100 (Eastern Daylight Time) File Modified: 1998-09-06 20:25:24.029473100 (Eastern Daylight Time) MFT Modified: 1998-09-06 20:25:24.029473100 (Eastern Daylight Time) Accessed: 1998-09-06 20:25:24.029473100 (Eastern Daylight Time) $FILE_NAME Attribute Values: Flags: Archive Name: TESTFI~1.TST Parent MFT Entry: 367786 Sequence: 290 Allocated Size: 106496 Actual Size: 106492 Created: 1998-09-06 20:25:24.029473100 (Eastern Daylight Time) File Modified: 1998-09-06 20:25:24.029473100 (Eastern Daylight Time) MFT Modified: 1998-09-06 20:25:24.029473100 (Eastern Daylight Time) Accessed: 1998-09-06 20:25:24.029473100 (Eastern Daylight Time) $FILE_NAME Attribute Values: Flags: Archive Name: TestFile106492.tst Parent MFT Entry: 367786 Sequence: 290 Allocated Size: 106496 Actual Size: 106492 Created: 1998-09-06 20:25:24.029473100 (Eastern Daylight Time) File Modified: 1998-09-06 20:25:24.029473100 (Eastern Daylight Time) MFT Modified: 1998-09-06 20:25:24.029473100 (Eastern Daylight Time) Accessed: 1998-09-06 20:25:24.029473100 (Eastern Daylight Time) Attributes: Type: $STANDARD_INFORMATION (16-0) Name: N/A Resident size: 72 Type: $FILE_NAME (48-8) Name: N/A Resident size: 90 Type: $FILE_NAME (48-7) Name: N/A Resident size: 102 Type: $DATA (128-4) Name: N/A Non-Resident size: 106492 init_size: 106492 255125 255126 255127 255128 255129 255130 255131 255132 255133 255134 255135 255136 255137 255138 255139 255140 13497149 13497150 13497151 13497152 13497153 13497154 13497155 13497156 13497157 13497158 |
The test program takes one command-line argument - file name, there is no support for a file name wildcards:
// Forever21.cpp
#include <windows.h>
#include <winternl.h>
#include <ntstatus.h>
HANDLE hStdOutput;
CHAR cFileBuffer[65536];
DWORD nNumberOfBytesWritten;
WCHAR cFileName[32768];
extern "C" void entry() {
if ((hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE)) != INVALID_HANDLE_VALUE) {
int argc = 0;
if (LPWSTR *argv = CommandLineToArgvW(GetCommandLineW(), &argc)) {
if (1 < argc) {
HANDLE hFile;
if ((hFile = CreateFileW(argv[1], FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0)) != INVALID_HANDLE_VALUE) {
NTSTATUS NtStatus; IO_STATUS_BLOCK IoStatusBlock; FILE_BASIC_INFORMATION FileBasicInformation;
if ((NtStatus = NtQueryInformationFile(hFile, &IoStatusBlock, &FileBasicInformation, sizeof FileBasicInformation, FILE_INFORMATION_CLASS::FileBasicInformation)) == STATUS_SUCCESS) {
LARGE_INTEGER FileTime; GetSystemTimeAsFileTime(reinterpret_cast<LPFILETIME>(&FileTime)); FileTime.QuadPart -= 21ll * 31'557'600 * 10'000'000;
FileBasicInformation.CreationTime = FileBasicInformation.LastAccessTime = FileBasicInformation.LastWriteTime = FileBasicInformation.ChangeTime = FileTime;
if (MoveFileW(argv[1], lstrcatW(lstrcpyW(cFileName, argv[1]), L"~21"))) {
if ((NtStatus = NtSetInformationFile(hFile, &IoStatusBlock, &FileBasicInformation, sizeof FileBasicInformation, FILE_INFORMATION_CLASS::FileBasicInformation)) == STATUS_SUCCESS) {
if (MoveFileW(cFileName, argv[1])) {
if ((NtStatus = NtSetInformationFile(hFile, &IoStatusBlock, &FileBasicInformation, sizeof FileBasicInformation, FILE_INFORMATION_CLASS::FileBasicInformation)) == STATUS_SUCCESS) {
WriteFile(hStdOutput, cFileBuffer, wsprintfA(cFileBuffer, "Forever21: \"%S\"\n", argv[1]), &nNumberOfBytesWritten, 0);
}
}
}
}
}
CloseHandle(hFile);
}
}
GlobalFree(argv);
}
}
ExitProcess(0);
}
Windows Driver Kit is required for Visual Studio build. But MinGW-w64 is an easy alternative. For example, it's bundled with Strawberry Perl. MinGW-w64 build command line is:
gcc.exe -Wl,--gc-sections,--subsystem,console -fno-exceptions -fno-asynchronous-unwind-tables -fno-rtti -nostdlib -s -e entry -oForever21.exe -O2 Forever21.cpp -lkernel32 -luser32 -lshell32 -lntdll
x64 build: Forever21.7z, SHA1(Forever21.exe)= 72f6974085797d680c168a7151606ce4634fb152