PowerShell Pipeline

Investigating File Signatures Using PowerShell

Looking at the signature of a file quickly lets you know whether that file is really what it claims to be. Here's how to tap PowerShell to figure out the file signature.

Sometimes you want to do something different that doesn't require you to download and learn a new application. For instance, maybe you want to figure out the file signature (or magic number) of a file to determine if the extension of the file is really what it should be.

What is the magic number, you ask? It is a set of bytes, usually at the beginning of the file (but not always), that tells you what the format of a file is. In the case of an executable, it is marked by the byte array of 4D 5A which translates to MZ. More information about these file signatures and which ones correspond to what file type can be found here.

Sure, you could quickly grab something like a hex viewer, load the file up with that and, while using the proper byte offset, locate the magic number to determine if that text file is really what it claims to be. There are different methods of file screening that you can use to block certain file types from being saved to a location, but often, the screening is only able to be effective by looking at the file extension. By looking at the signature of a file, you can easily determine if a file is really what it claims to be.

PowerShell, being the powerful and amazing tool that it is, can actually be used to dig into a file and determine what the signature of that file is. Here, I'll take you through the steps of performing a simple lookup for a file for its signature and then take you through a function that I wrote called Get-FileSignature that simplifies all of this for you and provides pipeline support for multiple files.

Let's work through the code now!

$ByteLimit = 2
$Item = Get-Item .\handle.exe

Open a FileStream to the file, which will prevent other actions against the file (such as being able to read the contents) until it closes.

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

Determine the starting point where to look into the bytes of the file based on the offset.

[void]$filestream.Seek($ByteOffset, [IO.SeekOrigin]::Begin)

Create a byte buffer to read into and then read bytes from the starting point to a predetermined stopping point.

$bytebuffer = New-Object "Byte[]" ($filestream.Length - ($filestream.Length - $ByteLimit))
[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 and placing the converted data into its appropriate hex or string builder.

For ($i=0;$i -lt $ByteLimit;$i++) {
    [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(".")
    }
}

Present the output of the data.

 [pscustomobject] @{
    Hex = $hexstringBuilder.ToString()
    ASCII = $stringBuilder.ToString()
}

Note: PowerShell V5 brings in Format-Hex, which can provide an alternative approach to reading the file and displaying the hex and ASCII value to determine the magic number. Here is a quick demo of it in action.

PS C:\> Format-Hex -Path handle.exe | Select-Object -First 1


           Path: C:\Users\PROXB\Desktop\handle.exe

           00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

00000000   4D 5A 90 00 03 00 00 00 04 00 00 00 FF FF 00 00  MZ.............

Here, you can see that by using Select-Object and specifying the first line returned, we can see our executable has the proper signature of 4D5A (MZ).

As mentioned earlier, I wrote a function that can simplify a lot of this work for you and supports multiple files so you can quickly scan your files to check on the file formats based on the bytes of each file.

PS C:\> Get-FileSignature handle.exe


Name           : handle.exe
FullName       : C:\users\PROXB\Desktop\handle.exe
HexSignature   : 4D5A
ASCIISignature : MZ
Length         : 462936
Extension      : exe 

Source Code

function Get-FileSignature { 
    <# .synopsis="" displays="" a="" file="" signature="" for="" specified="" file="" or="" files="" .description="" displays="" a="" file="" signature="" for="" specified="" file="" or="" files.="" determined="" by="" getting="" the="" bytes="" of="" a="" file="" and="" looking="" at="" the="" number="" of="" bytes="" to="" return="" and="" where="" in="" the="" byte="" array="" to="" start.="" .parameter="" path="" the="" path="" to="" a="" file.="" can="" be="" multiple="" files.="" .parameter="" hexfilter="" a="" filter="" that="" can="" be="" used="" to="" find="" specific="" hex="" signatures.="" allows="" "*"="" wildcard.="" .parameter="" bytelimit="" how="" many="" bytes="" of="" the="" file="" signature="" to="" return.="" default="" value="" is="" 2.="" (display="" first="" 2="" bytes)="" .parameter="" byteoffset="" where="" in="" the="" byte="" array="" to="" start="" displaying="" the="" signature.="" default="" value="" is="" 0="" (first="" byte)="" .notes="" name:="" get-filesignature="" author:="" boe="" prox="" .outputs="" system.io.fileinfo.signature="" .link="" http://en.wikipedia.org/wiki/list_of_file_signatures="" #="">
    #Requires -Version 3.0
    [CmdletBinding()]
    Param(
       [Parameter(Position=0,Mandatory=$true, ValueFromPipelineByPropertyName=$true,ValueFromPipeline=$True)]
       [Alias("PSPath","FullName")]
       [string[]]$Path,
       [parameter()]
       [Alias('Filter')]
       [string]$HexFilter = "*",
       [parameter()]
       [int]$ByteLimit = 2,
       [parameter()]
       [Alias('OffSet')]
       [int]$ByteOffset = 0
    )
    Begin {
        #Determine how many bytes to return if using the $ByteOffset
        $TotalBytes = $ByteLimit + $ByteOffset

        #Clean up filter so we can perform a regex match
        #Also remove any spaces so we can make it easier to match
        [regex]$pattern = ($HexFilter -replace '\*','.*') -replace '\s',''
    }
    Process {  
        ForEach ($item in $Path) { 
            Try {                     
                $item = Get-Item -LiteralPath (Convert-Path $item) -Force -ErrorAction Stop
            } Catch {
                Write-Warning "$($item): $($_.Exception.Message)"
                Return
            }
            If (Test-Path -Path $item -Type Container) {
                Write-Warning ("Cannot find signature on directory: {0}" -f $item)
            } Else {
                Try {
                    If ($Item.length -ge $TotalBytes) {
                        #Open a FileStream to the file; this will prevent other actions against file until it closes
                        $filestream = New-Object IO.FileStream($Item, [IO.FileMode]::Open, [IO.FileAccess]::Read)

                        #Determine starting point
                        [void]$filestream.Seek($ByteOffset, [IO.SeekOrigin]::Begin)

                        #Create Byte buffer to read into and then read bytes from starting point to pre-determined stopping point
                        $bytebuffer = New-Object "Byte[]" ($filestream.Length - ($filestream.Length - $ByteLimit))
                        [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(".")
                            }
                        }
                        If (($hexstringBuilder.ToString() -replace '\s','') -match $pattern) {
                            $object = [pscustomobject]@{
                                Name = ($item -replace '.*\\(.*)','$1')
                                FullName = $item
                                HexSignature = $hexstringBuilder.ToString()
                                ASCIISignature = $stringBuilder.ToString()
                                Length = $item.length
                                Extension = $Item.fullname -replace '.*\.(.*)','$1'
                            }
                            $object.pstypenames.insert(0,'System.IO.FileInfo.Signature')
                            Write-Output $object
                        }
                    } ElseIf ($Item.length -eq 0) {
                        Write-Warning ("{0} has no data ({1} bytes)!" -f $item.name,$item.length)
                    } Else {
                        Write-Warning ("{0} size ({1}) is smaller than required total bytes ({2})" -f $item.name,$item.length,$TotalBytes)
                    }
                } Catch {
                    Write-Warning ("{0}: {1}" -f $item,$_.Exception.Message)
                }

                #Close the file stream so the file is no longer locked by the process
                $FileStream.Close()
            }
        }        
    }
}
That is all there is to reading the signatures of a file using PowerShell!

About the Author

Boe Prox is a Microsoft MVP in Windows PowerShell and a Senior Windows System Administrator. He has worked in the IT field since 2003, and he supports a variety of different platforms. He is a contributing author in PowerShell Deep Dives with chapters about WSUS and TCP communication. He is a moderator on the Hey, Scripting Guy! forum, and he has been a judge for the Scripting Games. He has presented talks on the topics of WSUS and PowerShell as well as runspaces to PowerShell user groups. He is an Honorary Scripting Guy, and he has submitted a number of posts as a to Microsoft's Hey, Scripting Guy! He also has a number of open source projects available on Codeplex and GitHub. His personal blog is at http://learn-powershell.net.

comments powered by Disqus
Most   Popular