Sunday, September 8, 2019

NTFS Timestamps vs NtSetInformationFile & MoveFile

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:

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

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

  1. NtQueryInformationFile - preserve file attributes
  2. MoveFileW - rename file to some temporal name
  3. NtSetInformationFile - set $SI timestamps
  4. MoveFileW - rename file to the original name and propagate $SI timestamps to $FN timestamps
  5. NtSetInformationFile - set $SI timestamps again
BeforeAfter
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

No comments:

Post a Comment

NTFS Timestamps vs NtSetInformationFile & MoveFile

TL;DR: NtSetInformationFile, MoveFileW, NtSetInformationFile sequence overwrites all timestamps in $STANDARD_INFORMATION, and $FILE_NAME a...