Finding a File’s MFT Timestamp using PowerShell

During my last couple of articles dealing with writing and reading files without updating their LastAccess and LastWrite timestamps which has seen a bit of popularity from folks.  While these methods work great for the casual onlooker, digging down deeper into the file will show that in fact, there is another timestamp that does get updated, the MFT (ChangeTime) timestamp. A former co-worker of mine, Patrick Olsen was able to show this using some of his utilities. This led me down 2 paths:

  1. How I can get this MFT timestamp using PowerShell?
  2. How can I block this MFT timestamp from being updated?

This article aims to knock of the first question by finding the MFT timestamp using a combination of P/Invoke and PowerShell.

Using the same methods that Patrick used to look at the MFT timestamp on a file called “NewFile.txt”, we can see all of the available timestamps on the file. The SleuthKit tools can be downloaded from the following site:

http://www.sleuthkit.org/sleuthkit/download.php

.\mmls.exe \\.\PhysicalDrive0

image

.\fls.exe -rp -o 21262336 \\.\PhysicalDrive0 | 
Select-String -Pattern "NewFile.txt"

image

.\istat.exe -o 21262336 \\.\PhysicalDrive0 115472-128-9

image

Here you can see the MFT timestamp from the NewFile.txt and it clearly shows it being at 23:31:52 on 17 Feb 2013. Using the Write-File function, I will write to the file and you will only see the MFT timestamp being updated while everything else will remain the same.

Write-File -File NewFile.txt -InputObject "This is a test" -Append

Now lets take another look at those timestamps.

.\istat.exe -o 21262336 \\.\PhysicalDrive0 115472-128-9

image

As you can see, everything remained the same with the exception of the MFT timestamp. So how do I accomplish this same type check using PowerShell? The answer lies with using the NtQueryInformationFile function. Fortunately, there was an already available example that had exactly what I needed. I stripped out all of the unneeded code from the C# example and made it into something that would work great with my PowerShell function.

        $sig = @'
        using System;
        using System.Runtime.InteropServices; // for DllImport and friends
        using Microsoft.Win32.SafeHandles; // for SafeHandle
        using System.Collections.Generic; // for ParseFileAttributes helper function (List<FileAttributes> only
        using System.IO; // for test main (FileStream) only
        using System.Text; // for test main (Encoding) only
        using System.Threading; // for test main (Thread.Sleep) only
        using System.Diagnostics; // for test main (Trace.Write[Line]) only
        public class Nt
        {

            struct IO_STATUS_BLOCK
            {
                internal uint status;
                internal ulong information;
            }
            enum FILE_INFORMATION_CLASS
            {
                FileDirectoryInformation = 1,        // 1
                FileFullDirectoryInformation,        // 2
                FileBothDirectoryInformation,        // 3
                FileBasicInformation,            // 4
                FileStandardInformation,        // 5
                FileInternalInformation,        // 6
                FileEaInformation,            // 7
                FileAccessInformation,            // 8
                FileNameInformation,            // 9
                FileRenameInformation,            // 10
                FileLinkInformation,            // 11
                FileNamesInformation,            // 12
                FileDispositionInformation,        // 13
                FilePositionInformation,        // 14
                FileFullEaInformation,            // 15
                FileModeInformation = 16,        // 16
                FileAlignmentInformation,        // 17
                FileAllInformation,            // 18
                FileAllocationInformation,        // 19
                FileEndOfFileInformation,        // 20
                FileAlternateNameInformation,        // 21
                FileStreamInformation,            // 22
                FilePipeInformation,            // 23
                FilePipeLocalInformation,        // 24
                FilePipeRemoteInformation,        // 25
                FileMailslotQueryInformation,        // 26
                FileMailslotSetInformation,        // 27
                FileCompressionInformation,        // 28
                FileObjectIdInformation,        // 29
                FileCompletionInformation,        // 30
                FileMoveClusterInformation,        // 31
                FileQuotaInformation,            // 32
                FileReparsePointInformation,        // 33
                FileNetworkOpenInformation,        // 34
                FileAttributeTagInformation,        // 35
                FileTrackingInformation,        // 36
                FileIdBothDirectoryInformation,        // 37
                FileIdFullDirectoryInformation,        // 38
                FileValidDataLengthInformation,        // 39
                FileShortNameInformation,        // 40
                FileHardLinkInformation = 46        // 46    
            }
            [StructLayout(LayoutKind.Explicit)]
            struct FILE_BASIC_INFORMATION
            {
                [FieldOffset(0)]
                internal long CreationTime;
                [FieldOffset(8)]
                internal long LastAccessTime;
                [FieldOffset(16)]
                internal long LastWriteTime;
                [FieldOffset(24)]
                internal long ChangeTime;
                [FieldOffset(32)]
                internal ulong FileAttributes;
            }
            [DllImport("ntdll.dll", SetLastError = true)]
            static extern IntPtr NtQueryInformationFile(SafeFileHandle fileHandle, ref IO_STATUS_BLOCK IoStatusBlock, IntPtr pInfoBlock, uint length, FILE_INFORMATION_CLASS fileInformation);

            public static bool GetFourFileTimes(string path2file,
                    out DateTime creationTime, out DateTime lastAccessTime, out DateTime lastWriteTime, out DateTime changeTime, out string errMsg)
            {
                bool brc = false;
                creationTime = default(DateTime);
                lastAccessTime = default(DateTime);
                lastWriteTime = default(DateTime);
                changeTime = default(DateTime);
                errMsg = string.Empty;
                IntPtr p_fbi = IntPtr.Zero;
                try
                {
                    FILE_BASIC_INFORMATION fbi = new FILE_BASIC_INFORMATION();
                    IO_STATUS_BLOCK iosb = new IO_STATUS_BLOCK();
                    using (FileStream fs = new FileStream(path2file, FileMode.Open, FileAccess.Read, FileShare.Read))
                    {
                        p_fbi = Marshal.AllocHGlobal(Marshal.SizeOf(fbi));                
                        IntPtr iprc = NtQueryInformationFile(fs.SafeFileHandle, ref iosb, p_fbi, (uint)Marshal.SizeOf(fbi), FILE_INFORMATION_CLASS.FileBasicInformation);
                        brc = iprc == IntPtr.Zero && iosb.status == 0;
                        if (brc)
                        {
                            brc = false;
                            fbi = (FILE_BASIC_INFORMATION)Marshal.PtrToStructure(p_fbi, typeof(FILE_BASIC_INFORMATION));
                            creationTime = DateTime.FromFileTime(fbi.CreationTime);
                            lastAccessTime = DateTime.FromFileTime(fbi.LastAccessTime);
                            lastWriteTime = DateTime.FromFileTime(fbi.LastWriteTime);
                            changeTime = DateTime.FromFileTime(fbi.ChangeTime);
                            brc = true;
                        }
                    }
                }
                catch (Exception ex)
                {
                    brc = false;
                    errMsg = ex.Message;
                }
                finally
                {
                    if (p_fbi != IntPtr.Zero) { Marshal.FreeHGlobal(p_fbi); }
                }
                return brc;
            }
        }
'@

I was then able to use Add-Type to load up the code and then set up some variables that will be used as references for checking the file timestamps.

#region Create Win32 API object
If (-Not $Global:NTQueryFile) {
    $Global:NTQueryFile = Add-Type -TypeDefinition $sig -PassThru
}
#endregion Create Win32 API object

#region Create reference variables
$creationTime = (Get-Date)
$lastAccessTime = (Get-Date)
$lastWriteTime = (Get-Date)
$changeTime = (Get-Date)
$errorMsg = $null
#endregion Create reference variables

From there I call the public function GetFourFileTimes to get the timestamps of a specified file.

[NT]::GetFourFileTimes($item,
    [ref]$creationTime,
    [ref]$lastAccessTime,
    [ref]$lastWriteTime,
    [ref]$changeTime,
    [ref]$errorMsg
    )
New-Object PSObject -Property @{
    FileName = $item
    CreationDate = $creationTime
    LastWriteTime = $lastWriteTime
    LastAccessTime = $lastAccessTime 
    ChangeTime = $changeTime
}

I wrapped all of this up into a function called Get-FileTimeStamp, that when used gives you a similar output of the MFT timestamp (now ChangeTime) along with other timestamps.

Get-FileTimeStamp -File .\NewFile.txt

image

As you can see, it is the exact same time as used with the istat.exe file available in the  SleuthKit suite.

The next obvious step is to figure out how to prevent the MFT timestamp from being updated when accessing or even writing to a file. I have a couple of ideas in mind and just need to put the code together and test it out before publishing an article and associated function.  But rest assured that I will post my findings as soon as I am confident that it works properly!

Until then, feel free to download the script using the link below and let me know what you think!

Download Get-FileTimeStamp

Script Repository

This entry was posted in powershell and tagged , , , , , . Bookmark the permalink.

2 Responses to Finding a File’s MFT Timestamp using PowerShell

  1. Pingback: Revisiting Get-FileStamp with Reflection | Learn Powershell | Achieve More

  2. Pingback: Weekend Scripter: Use PowerShell to Investigate File Signatures—Part 2 - Hey, Scripting Guy! Blog - Site Home - TechNet Blogs

Leave a comment