Summary: Windows PowerShell MVP, Boe Prox, concludes this two-part series about investigating file signatures with Windows PowerShell and the .NET Framework.
Microsoft Scripting Guy, Ed Wilson, is here. Welcome back guest blogger, Boe Prox, for the conclusion of the two-part series he started yesterday. Also read Use PowerShell to Investigate File Signatures—Part 1.
Today I am going to demo a function that I wrote and discuss a couple of items that I came across while investigating the file signatures. But first, I am going to talk about a couple of things that helped shape the direction of my function, which is called Get-FileSignature (the download is available on the Script Center Repository).
I wanted to know if there was a way to easily determine if an extension had been altered by looking at the LastWriteTime time stamp. The reason is that if a drive or folder was being blocked from hosting files of a certain type, files that are already there could have their extension altered to avoid being detected by using a casual scan such as the following:
Get-ChildItem -Filter *.iso
You could use *.iso*, or *.iso.*, or any other means, but there is a potential to miss files in the scan. The point is that there is always the possibility of an extension being changed because a user knows of the file’s impending removal.
When I tested the LastWriteTime of a file after I changed the extension, I found that nothing changed and the time stamp remained the same.
$file = Convert-Path en_windows_8_enterprise_x64_dvd_917522.iso
Get-Item $File | Select Name,LastWriteTime
Rename-Item -Path $File -NewName "$File.txt" -Verbose
Get-Item "$File.name.txt" | Select Name,LastWriteTime
As you can see, there is no change to LastWriteTime when the extension is changed. Fortunately, I am able to use a function that I wrote called Get-FileTimeStamp. This function uses P/Invoke to display the ChangeTime (also known as MFT TimeStamp), which updates with any change, including metadata changes such as the extension. (I also wrote a blog post about this topic: Finding a File’s MFT Timestamp using PowerShell.)
Now I have a more useful way to determine if a file extension had been changed. I will reiterate that this is not a foolproof way of determining that the extension has changed because any change to the file (such as an attribute) will trigger the ChangeTime to update. The Get-FileSignature function uses a portion of the code from Get-FileTimeStamp to provide the ChangeTime.
Get-Content vs. [io.file]::ReadAllBytes() vs. FileStream
Next on my agenda was to find the most efficient way of getting the file signature. I needed a method that would take into account not only small files, but also larger files, such as the 3 GB ISO file that I am using in my examples today. I looked at three possible ways of getting the file signature:
- Use Get-Content with the –Encoding Byte parameter
- Use [io.file]::ReadAllBytes()
- Use [io.filestream] (the examples that I presented in Part 1)
Any of these methods would work fairly quickly on smaller files, so I will not waste any time looking at that performance. Instead, we will tackle the ISO file—not only because it is large, but because it uses a byte offset that others (such as .exe files) do not:
$ByteLimit = 5
$ByteOffset = 0x8001
$total = $ByteLimit + $ByteOffset
#Takes some time to run on larger files as long as they are less than 2GB; fine against smaller files
$sum = (Measure-Command {
Get-Content -Path $File -Encoding Byte -TotalCount $total | Select -First $ByteLimit -Skip $ByteOffset
}).TotalMilliseconds
[pscustomobject]@{
Type = 'GetContent'
TotalMilliseconds = $sum
}
#Still slower on larger files; fine against smaller files
$sum = (Measure-Command {
[io.file]::ReadAllBytes($File) | Select -First $ByteLimit -Skip $ByteOffset
}).TotalMilliseconds
[pscustomobject]@{
Type = 'ReadAllBytes'
TotalMilliseconds = $sum
}
#Most work setting up, but faster with larger files
$Sum = (Measure-Command {
#Open a FileStream to the file; this will prevent other actions against file until it closes
$filestream = New-Object IO.FileStream($File, [IO.FileMode]::Open, [IO.FileAccess]::Read)
#Determine starting point
$filestream.Position = $ByteOffset
$bytebuffer = New-Object "Byte[]" -ArgumentList $ByteLimit
[void]$filestream.Read($bytebuffer, 0, $bytebuffer.Length)
$bytebuffer
$filestream.Close()
$filestream.Dispose()
}).TotalMilliseconds
[pscustomobject]@{
Type = 'FileStream'
TotalMilliseconds = $sum
}
We can see a limitation with Get-Content in files that exceed 2 GB in size. Even then, it was still in the upwards of 2000 milliseconds to get to that point. The ReadAllBytes method was much faster, but it forces us to read the entire file before grabbing the specified bytes needed for the signature. The approach that I discussed yesterday (even with some of the setup) was the fastest method available, and it became the approach that I chose for my function.
Get-FileSignature
Speaking of the function, let’s take a look at it in action. It supports pipeline input, and it allows you to specify a byte offset and the number of bytes to return. It is perfect for looking for a specific file signature. I also added a –HexFilter, which can be used to filter for a specific file signature by its hex signature.
In this case, I want to find only ISO files:
Get-ChildItem -Path G: -Recurse -File |
Get-FileSignature -ByteLimit 5 -ByteOffset 0x8001 -HexFilter "4344 3030 31" |
Format-Table
Looks like I had more than one ISO file residing on this drive.
Depending on the type of file that you are looking for, you can adjust the parameters to suit your needs, and then use this function to locate files that may not quite be what they appear.
Here is one last demo that uses the signature for an .exe file:
Get-ChildItem -Path G: -Recurse -File |
Get-FileSignature -ByteLimit 2 -ByteOffset 0 -HexFilter "4D5A" |
Format-Table
Looks like .exe files are not the only files that carry the 4D5A hex filter—.dlls are just one of many other files that hold the same file signature.
That is it for my two-part series about finding file signatures by using Windows PowerShell. Thank-you to Ed for giving me a couple of days to talk about this. Hope everyone found it interesting! You can download the function from this series from the Script Center Repository: Get-FileSignature.
~Boe
Thank you, Boe, for writing these two posts and sharing.
I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy