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:
- How I can get this MFT timestamp using PowerShell?
- 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
.\fls.exe -rp -o 21262336 \\.\PhysicalDrive0 | Select-String -Pattern "NewFile.txt"
.\istat.exe -o 21262336 \\.\PhysicalDrive0 115472-128-9
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
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
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!
Pingback: Revisiting Get-FileStamp with Reflection | Learn Powershell | Achieve More
Pingback: Weekend Scripter: Use PowerShell to Investigate File Signatures—Part 2 - Hey, Scripting Guy! Blog - Site Home - TechNet Blogs