Summary: Windows PowerShell MVP, Boe Prox, talks about investigating file signatures by using Windows PowerShell.

Microsoft Scripting Guy, Ed Wilson, is here. This weekend we have a two-part series from guest blogger, Boe Prox

When you work with File Server Resource Manager (FSRM), one of the cool things that you can do is block files, based on file type. This allows a system administrator to prevent various types of files from being saved to a specified drive or folder, based on the requirement.

For instance, let’s say that you want to prevent executables (*.exe) and iso image files (*.iso) from being saved to a specific drive. We would create a file screen that would block these types of files from being saved to the folder.

Image of command output

The message says Access Denied, but it really means that this file type is blocked. Let’s view the event log to verify:

Get-WinEvent -FilterHashtable @{

    LogName = 'Application'

    Id = '8215'

} -MaxEvents 1 | Select *

Image of command output

Of course, this won’t stop someone who is determined to save a file to a “restricted drive or folder.” One simple workaround is to save the file with a different extension (or no extension at all), and then save it to the location that is blocking the known file type:

Rename-Item -Path .\ILSpy.exe -NewName ILSpy.exe.txt

Copy-Item -Path .\ILSpy.exe.txt -Destination F:\

Get-ChildItem F: -Filter *.exe.txt

Image of command output

As you can see, I had no issues moving the executable. The same could be said if I wanted to move an ISO file—I could simply add a .txt extension, and it would go without issue!

By using Windows PowerShell, I am able to open a file stream to a file, inspect it for a specific set of bytes, and determine whether the extension truly matches the type of file that it is. This set of bytes is commonly known as the “magic number” or the file signature, which is used to determine the type of a file.

This isn’t valid for all file types (for example, .txt and .ps1 don’t really play well). You can view a list of file signatures Wikipedia, but it is not a complete list by far. However, this list does provide what I need to determine that the file signatures are for executable and an ISO files:

Hex signature

ISO 8859-1

Offset

File extension

4D 5A

MZ

0

exe

43 44 30 30 31

CD001

0x8001, 0x8801 or 0x9001

ISO

Knowing that the hex signature for an executable is 4D5A, we can test this against the “hidden executable” to see whether it is truly a text file. How do we get to that point? Good question!

Let’s start by looking at the requirements, and then work to dig into the file for the correct answer.

We already know that the file signature is 4D5A and that the total bytes expected is 2 bytes (4D is one byte and 5A is byte number 2). Because the offset is 0, I know that I need to look at the first 2 bytes of the stream. By now you might be wondering what stream I keep on talking about and how do I get to that point.

First I need to get the full path to the file. Working with .net types and running methods against only a file name will cause errors to occur and hinder our progress.

#Get full path; otherwise we will have errors opening the file stream

$file = Convert-Path .\ILSpy.exe.txt

I can now open a file stream object by using the IO.FileStream constructor and supplying parameters to tell it to open the file in Read mode:

#Open the file stream on the file

$filestream = New-Object IO.FileStream -ArgumentList $file, ([IO.FileMode]::Open), ([IO.FileAccess]::Read)

Image of command output

During this time, the file will be locked. This means that nothing else can open it until I close the file stream later. I can tell that I am at the beginning of the file via the Position property.

I am starting at the 0 position, so my ByteOffset will be 0. In this case, I do not need to adjust the position of the file stream, but I will when finding the signature on the ISO file.

#Specify the starting point

$ByteOffset = 0

#Specify the number of bytes to return; only needs to be as big as the signature

$ByteLimit = 2

#Set starting point

$filestream.Position = $ByteOffset

Next up is to create the byte buffer, which will hold the file signature data. I want to make sure that I only make it as big as the number of bytes we need. This becomes more important when we are dealing with very large files such as ISO files.

After that has been created, we can read the bytes. We start at the place we specified in the file stream position by using the Read method, the byte buffer, the starting point in the current position of the stream, and the total number of bytes to read.

#Create a byte buffer to hold the data

$bytebuffer = New-Object "Byte[]" -ArgumentList $ByteLimit

#Read the data into the buffer

[void]$filestream.Read($bytebuffer, 0, $bytebuffer.Length)

Image of command output

Inspecting the buffer, we see that we have our two bytes available to view. At this point, it still isn’t clear what those values are and if they actually match the file signature needed for an .exe file. We need to convert these values to hex values and also to a more humanly readable value (ISO 8859-1 from the table). I will enlist the aid of a couple of StringBuilder Classes to accomplish this:

#Create string builder objects for hex and ascii display

$hexstringBuilder = New-Object Text.StringBuilder

$stringBuilder = New-Object Text.StringBuilder

The following script is the logic that I use to handle the conversion to a hex value and then verify if the actual hex value is a legal letter or number. If that value is not one of those, it will be a period (.).

#Begin converting bytes

For ($i=0;$i -lt $ByteLimit;$i++) {

    If ($i%2) {

        [void]$hexstringBuilder.Append(("{0:X}" -f $bytebuffer[$i]).PadLeft(2, "0"))

    } Else {

        If ($i -eq 0) {

            [void]$hexstringBuilder.Append(("{0:X}" -f $bytebuffer[$i]).PadLeft(2, "0"))

        } Else {

            [void]$hexstringBuilder.Append(" ")

            [void]$hexstringBuilder.Append(("{0:X}" -f $bytebuffer[$i]).PadLeft(2, "0"))

        }       

    }

    If ([char]::IsLetterOrDigit($bytebuffer[$i])) {

        [void]$stringBuilder.Append([char]$bytebuffer[$i])

    } Else {

        [void]$stringBuilder.Append(".")

    }

}

I use the Append() method of the StringBuilder object to add the data into the object. By using the IsLetterOrDigit method of [char], I can quickly determine what type of value I am dealing with and handle it appropriately. But I’m not done yet because I cannot simply type $stringbuilder or $hexstringbuilder and see my results.

Image of command output

I need to force the object as a string by using the ToString() method to actually see the results:

[pscustomobject]@{

    Fullname = $File

    HexSignature = $hexstringBuilder.ToString()

    ASCIISignature = $stringBuilder.ToString()

}

Image of command output

By comparing both of these values with what was in our table, we can see that this file is indeed an executable! One last thing. We need to close the file stream so the file is no longer being locked:

#Close the file stream

$filestream.Close()

What about that ISO file? The same approach can be used, with a simple adjustment to $ByteOffset. In this case, it is an offset of 0x8001 (32679 bytes), and we are expecting 5 bytes for the signature.

#Get full path; otherwise we will have errors opening the file stream

$file = Convert-Path en_windows_8_enterprise_x64_dvd_917522.iso.txt

#Open the file stream on the file

$filestream = New-Object IO.FileStream -ArgumentList $file, ([IO.FileMode]::Open), ([IO.FileAccess]::Read)

#Specify the starting point

$ByteOffset = 0x8001

#Specify the number of bytes to return; only needs to be as big as the signature

$ByteLimit = 5

#Set starting point

$filestream.Position = $ByteOffset

The following image shows that the position has in fact changed to the byte offset that I specified:

Image of command output

#Create a byte buffer to hold the data

$bytebuffer = New-Object "Byte[]" -ArgumentList $ByteLimit

#Read the data into the buffer

[void]$filestream.Read($bytebuffer, 0, $bytebuffer.Length)

#Create string builder objects for hex and ascii display

$hexstringBuilder = New-Object Text.StringBuilder

$stringBuilder = New-Object Text.StringBuilder

#Begin converting bytes

For ($i=0;$i -lt $ByteLimit;$i++) {

    If ($i%2) {

        [void]$hexstringBuilder.Append(("{0:X}" -f $bytebuffer[$i]).PadLeft(2, "0"))

    } Else {

        If ($i -eq 0) {

            [void]$hexstringBuilder.Append(("{0:X}" -f $bytebuffer[$i]).PadLeft(2, "0"))

        } Else {

            [void]$hexstringBuilder.Append(" ")

            [void]$hexstringBuilder.Append(("{0:X}" -f $bytebuffer[$i]).PadLeft(2, "0"))

        }       

    }

    If ([char]::IsLetterOrDigit($bytebuffer[$i])) {

        [void]$stringBuilder.Append([char]$bytebuffer[$i])

    } Else {

        [void]$stringBuilder.Append(".")

    }

}

[pscustomobject]@{

    Fullname = $File

    HexSignature = $hexstringBuilder.ToString()

    ASCIISignature = $stringBuilder.ToString()

}

#Close the file stream

$filestream.Close()

Image of command output

Definitely an ISO file based on the signatures…and this took really no time at all to run because I took advantage of the Seek method against this over 3 GB file.

With that, you can see how to find a file signature on any file (assuming you know what type of signature you are looking for) and easily find if a file type isn’t quite what it seems. There are some items that influenced my decision to choose a file stream as the preferred method to get the file signature. That and a demo function will be revealed in tomorrow’s post.

~Boe

Thanks, Boe, for once again sharing your time and knowledge. Looking forward to the conclusion tomorrow.

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