PETools/Get-ObjDump.ps1

function Get-ObjDump
{
<#
.SYNOPSIS

    Displays information about one or more Windows object files.

    PowerSploit Function: Get-ObjDump
    Author: Matthew Graeber (@mattifestation)
    License: BSD 3-Clause
    Required Dependencies: None
    Optional Dependencies: None

.DESCRIPTION

    Get-ObjDump parses and returns nearly identical infomation as the dumpbin
    utility. By nature of Get-ObjDump returning objects though, it lends itself
    much better to manipulation since every field is an object.

.PARAMETER Path

    Specifies a path to one or more object file locations.

.EXAMPLE

    C:\PS>Get-ObjDump -Path main.obj

.EXAMPLE

    C:\PS>ls *.obj | Get-ObjDump

.EXAMPLE

    C:\PS>$ObjectFile = Get-ObjDump -Path shellcode.obj
    C:\PS>$CodeBytes = $ObjectFile.SectionHeaders | ? {$_.Name -eq '.text'} | % {$_.RawData}

    Description
    -----------
    Pulls the raw bytes out of the text section. Note that in this form,
    no relocations have been fixed up.

.INPUTS

    System.String[]

    You can pipe a file system path (in quotation marks) to Get-ObjDump.

.OUTPUTS

    COFF.OBJECT_FILE

.LINK

    http://www.exploit-monday.com/
#>

    [CmdletBinding()] Param (
        [Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True)]
        [ValidateScript({ Test-Path $_ })]
        [String[]]
        $Path
    )

    BEGIN
    {
        $Code = @'
        using System;
        using System.IO;
        using System.Text;

        namespace COFF
        {
            public enum Machine : ushort
            {
                UNKNOWN = 0,
                I386 = 0x014C, // Intel 386.
                R3000 = 0x0162, // MIPS little-endian =0x160 big-endian
                R4000 = 0x0166, // MIPS little-endian
                R10000 = 0x0168, // MIPS little-endian
                WCEMIPSV2 = 0x0169, // MIPS little-endian WCE v2
                ALPHA = 0x0184, // Alpha_AXP
                SH3 = 0x01A2, // SH3 little-endian
                SH3DSP = 0x01A3,
                SH3E = 0x01A4, // SH3E little-endian
                SH4 = 0x01A6, // SH4 little-endian
                SH5 = 0x01A8, // SH5
                ARM = 0x01C0, // ARM Little-Endian
                THUMB = 0x01C2,
                ARMV7 = 0x01C4, // ARM Thumb-2 Little-Endian
                AM33 = 0x01D3,
                POWERPC = 0x01F0, // IBM PowerPC Little-Endian
                POWERPCFP = 0x01F1,
                IA64 = 0x0200, // Intel 64
                MIPS16 = 0x0266, // MIPS
                ALPHA64 = 0x0284, // ALPHA64
                MIPSFPU = 0x0366, // MIPS
                MIPSFPU16 = 0x0466, // MIPS
                AXP64 = ALPHA64,
                TRICORE = 0x0520, // Infineon
                CEF = 0x0CEF,
                EBC = 0x0EBC, // EFI public byte Code
                AMD64 = 0x8664, // AMD64 (K8)
                M32R = 0x9041, // M32R little-endian
                ARM64 = 0xAA64, // ARMv8 in 64-bit mode
                CEE = 0xC0EE
            }

            [Flags]
            public enum CoffHeaderCharacteristics : ushort
            {
                RELOCS_STRIPPED = 0x0001, // Relocation info stripped from file.
                EXECUTABLE_IMAGE = 0x0002, // File is executable (i.e. no unresolved external references).
                LINE_NUMS_STRIPPED = 0x0004, // Line nunbers stripped from file.
                LOCAL_SYMS_STRIPPED = 0x0008, // Local symbols stripped from file.
                AGGRESIVE_WS_TRIM = 0x0010, // Agressively trim working set
                LARGE_ADDRESS_AWARE = 0x0020, // App can handle >2gb addresses
                REVERSED_LO = 0x0080, // public bytes of machine public ushort are reversed.
                BIT32_MACHINE = 0x0100, // 32 bit public ushort machine.
                DEBUG_STRIPPED = 0x0200, // Debugging info stripped from file in .DBG file
                REMOVABLE_RUN_FROM_SWAP = 0x0400, // If Image is on removable media =copy and run from the swap file.
                NET_RUN_FROM_SWAP = 0x0800, // If Image is on Net =copy and run from the swap file.
                SYSTEM = 0x1000, // System File.
                DLL = 0x2000, // File is a DLL.
                UP_SYSTEM_ONLY = 0x4000, // File should only be run on a UP machine
                REVERSED_HI = 0x8000 // public bytes of machine public ushort are reversed.
            }

            public class HEADER
            {
                public Machine Machine;
                public ushort NumberOfSections;
                public DateTime TimeDateStamp;
                public uint PointerToSymbolTable;
                public uint NumberOfSymbols;
                public ushort SizeOfOptionalHeader;
                public CoffHeaderCharacteristics Characteristics;

                public HEADER(BinaryReader br)
                {
                    this.Machine = (Machine) br.ReadUInt16();
                    this.NumberOfSections = br.ReadUInt16();
                    this.TimeDateStamp = (new DateTime(1970, 1, 1, 0, 0, 0)).AddSeconds(br.ReadUInt32());
                    this.PointerToSymbolTable = br.ReadUInt32();
                    this.NumberOfSymbols = br.ReadUInt32();
                    this.SizeOfOptionalHeader = br.ReadUInt16();
                    this.Characteristics = (CoffHeaderCharacteristics) br.ReadUInt16();
                }
            }

            [Flags]
            public enum SectionHeaderCharacteristics : uint
            {
                TYPE_NO_PAD = 0x00000008, // Reserved.
                CNT_CODE = 0x00000020, // Section contains code.
                CNT_INITIALIZED_DATA = 0x00000040, // Section contains initialized data.
                CNT_UNINITIALIZED_DATA = 0x00000080, // Section contains uninitialized data.
                LNK_INFO = 0x00000200, // Section contains comments or some other type of information.
                LNK_REMOVE = 0x00000800, // Section contents will not become part of image.
                LNK_COMDAT = 0x00001000, // Section contents comdat.
                NO_DEFER_SPEC_EXC = 0x00004000, // Reset speculative exceptions handling bits in the TLB entries for this section.
                GPREL = 0x00008000, // Section content can be accessed relative to GP
                MEM_FARDATA = 0x00008000,
                MEM_PURGEABLE = 0x00020000,
                MEM_16BIT = 0x00020000,
                MEM_LOCKED = 0x00040000,
                MEM_PRELOAD = 0x00080000,
                ALIGN_1BYTES = 0x00100000,
                ALIGN_2BYTES = 0x00200000,
                ALIGN_4BYTES = 0x00300000,
                ALIGN_8BYTES = 0x00400000,
                ALIGN_16BYTES = 0x00500000, // Default alignment if no others are specified.
                ALIGN_32BYTES = 0x00600000,
                ALIGN_64BYTES = 0x00700000,
                ALIGN_128BYTES = 0x00800000,
                ALIGN_256BYTES = 0x00900000,
                ALIGN_512BYTES = 0x00A00000,
                ALIGN_1024BYTES = 0x00B00000,
                ALIGN_2048BYTES = 0x00C00000,
                ALIGN_4096BYTES = 0x00D00000,
                ALIGN_8192BYTES = 0x00E00000,
                ALIGN_MASK = 0x00F00000,
                LNK_NRELOC_OVFL = 0x01000000, // Section contains extended relocations.
                MEM_DISCARDABLE = 0x02000000, // Section can be discarded.
                MEM_NOT_CACHED = 0x04000000, // Section is not cachable.
                MEM_NOT_PAGED = 0x08000000, // Section is not pageable.
                MEM_SHARED = 0x10000000, // Section is shareable.
                MEM_EXECUTE = 0x20000000, // Section is executable.
                MEM_READ = 0x40000000, // Section is readable.
                MEM_WRITE = 0x80000000 // Section is writeable.
            }

            public enum AMD64RelocationType : ushort
            {
                ABSOLUTE,
                ADDR64,
                ADDR32,
                ADDR32NB,
                REL32,
                REL32_1,
                REL32_2,
                REL32_3,
                REL32_4,
                REL32_5,
                SECTION,
                SECREL,
                SECREL7,
                TOKEN,
                SREL32,
                PAIR,
                SSPAN32
            }

            public enum ARMRelocationType : ushort
            {
                ABSOLUTE,
                ADDR32,
                ADDR32NB,
                BRANCH24,
                BRANCH11,
                TOKEN,
                BLX24 = 0x08,
                BLX11 = 0x09,
                SECTION = 0x0E,
                SECREL = 0x0F,
                MOV32A = 0x10,
                MOV32T = 0x11,
                BRANCH20T = 0x12,
                BRANCH24T = 0x14,
                BLX23T = 0x15
            }

            public enum ARMv8RelocationType : ushort
            {
                ABSOLUTE,
                ADDR32,
                ADDR32NB,
                BRANCH26,
                PAGEBASE_REL21,
                REL21,
                PAGEOFFSET_12A,
                PAGEOFFSET_12L,
                SECREL,
                SECREL_LOW12A,
                SECREL_HIGH12A,
                SECREL_LOW12L,
                TOKEN,
                SECTION,
                ADDR64
            }

            public enum X86RelocationType : ushort
            {
                ABSOLUTE,
                DIR16,
                DIR32 = 0x06,
                DIR32NB = 0x07,
                SEG12 = 0x09,
                SECTION = 0x0A,
                SECREL = 0x0B,
                TOKEN = 0x0C,
                SECREL7 = 0x0D,
                REL32 = 0x14
            }

            public class RelocationEntry
            {
                public uint VirtualAddress;
                public uint SymbolTableIndex;
                public Enum Type;
                public string Name;

                public RelocationEntry(BinaryReader br)
                {
                    this.VirtualAddress = br.ReadUInt32();
                    this.SymbolTableIndex = br.ReadUInt32();
                    // Default to X86RelocationType. This will be changed once the processor type is determined
                    this.Type = (X86RelocationType) br.ReadUInt16();
                }
            }

            public class SECTION_HEADER
            {
                public string Name;
                public uint PhysicalAddress;
                public uint VirtualSize;
                public uint VirtualAddress;
                public uint SizeOfRawData;
                public uint PointerToRawData;
                public uint PointerToRelocations;
                public uint PointerToLinenumbers;
                public ushort NumberOfRelocations;
                public ushort NumberOfLinenumbers;
                public SectionHeaderCharacteristics Characteristics;
                public Byte[] RawData;
                public RelocationEntry[] Relocations;

                public SECTION_HEADER(BinaryReader br)
                {
                    this.Name = Encoding.UTF8.GetString(br.ReadBytes(8)).Split((Char) 0)[0];
                    this.PhysicalAddress = br.ReadUInt32();
                    this.VirtualSize = this.PhysicalAddress;
                    this.VirtualAddress = br.ReadUInt32();
                    this.SizeOfRawData = br.ReadUInt32();
                    this.PointerToRawData = br.ReadUInt32();
                    this.PointerToRelocations = br.ReadUInt32();
                    this.PointerToLinenumbers = br.ReadUInt32();
                    this.NumberOfRelocations = br.ReadUInt16();
                    this.NumberOfLinenumbers = br.ReadUInt16();
                    this.Characteristics = (SectionHeaderCharacteristics) br.ReadUInt32();
                }
            }

            public enum SectionNumber : short
            {
                UNDEFINED,
                ABSOLUTE = -1,
                DEBUG = -2
            }

            [Flags]
            public enum TypeClass : short
            {
                TYPE_NULL,
                TYPE_VOID,
                TYPE_CHAR,
                TYPE_SHORT,
                TYPE_INT,
                TYPE_LONG,
                TYPE_FLOAT,
                TYPE_DOUBLE,
                TYPE_STRUCT,
                TYPE_UNION,
                TYPE_ENUM,
                TYPE_MOE,
                TYPE_BYTE,
                TYPE_WORD,
                TYPE_UINT,
                TYPE_DWORD,
                DTYPE_POINTER = 0x100,
                DTYPE_FUNCTION = 0x200,
                DTYPE_ARRAY = 0x300,
                DTYPE_NULL = 0x400 // Technically, this is defined as 0 in the MSB
            }

            public enum StorageClass : byte
            {
                NULL,
                AUTOMATIC,
                EXTERNAL,
                STATIC,
                REGISTER,
                EXTERNAL_DEF,
                LABEL,
                UNDEFINED_LABEL,
                MEMBER_OF_STRUCT,
                ARGUMENT,
                STRUCT_TAG,
                MEMBER_OF_UNION,
                UNION_TAG,
                TYPE_DEFINITION,
                ENUM_TAG,
                MEMBER_OF_ENUM,
                REGISTER_PARAM,
                BIT_FIELD,
                BLOCK = 0x64,
                FUNCTION = 0x65,
                END_OF_STRUCT = 0x66,
                FILE = 0x67,
                SECTION = 0x68,
                WEAK_EXTERNAL = 0x69,
                CLR_TOKEN = 0x6B,
                END_OF_FUNCTION = 0xFF
            }

            public class SYMBOL_TABLE
            {
                public string Name;
                public uint Value;
                public SectionNumber SectionNumber;
                public TypeClass Type;
                public StorageClass StorageClass;
                public byte NumberOfAuxSymbols;
                public Object AuxSymbols;
                private Byte[] NameArray;

                public SYMBOL_TABLE(BinaryReader br)
                {
                    this.NameArray = br.ReadBytes(8);

                    if (this.NameArray[0] == 0 && this.NameArray[1] == 0 &&this.NameArray[2] == 0 &&this.NameArray[3] == 0)
                    {
                        // Per specification, if the high DWORD is 0, then then low DWORD is an index into the string table
                        this.Name = "/" + BitConverter.ToInt32(NameArray, 4).ToString();
                    }
                    else
                    {
                        this.Name = Encoding.UTF8.GetString(NameArray).Trim(((char) 0));
                    }

                    this.Value = br.ReadUInt32();
                    this.SectionNumber = (SectionNumber) br.ReadInt16();
                    this.Type = (TypeClass) br.ReadInt16();
                    if ((((int) this.Type) & 0xff00) == 0) { this.Type = (TypeClass) Enum.Parse(typeof(TypeClass), ((int) this.Type | 0x400).ToString());}
                    this.StorageClass = (StorageClass) br.ReadByte();
                    this.NumberOfAuxSymbols = br.ReadByte();
                }
            }

            public class SECTION_DEFINITION
            {
                public uint Length;
                public ushort NumberOfRelocations;
                public ushort NumberOfLinenumbers;
                public uint CheckSum;
                public ushort Number;
                public byte Selection;

                public SECTION_DEFINITION(BinaryReader br)
                {
                    this.Length = br.ReadUInt32();
                    this.NumberOfRelocations = br.ReadUInt16();
                    this.NumberOfLinenumbers = br.ReadUInt16();
                    this.CheckSum = br.ReadUInt32();
                    this.Number = br.ReadUInt16();
                    this.Selection = br.ReadByte();
                    br.ReadBytes(3);
                }
            }
        }
'@


        Add-Type -TypeDefinition $Code

        function Dispose-Objects
        {
            $BinaryReader.Dispose()
            $FileStream.Dispose()
        }
    }

    PROCESS
    {
        foreach ($File in $Path) {

        # Resolve the absolute path of the object file. [IO.File]::OpenRead requires an absolute path.
        $ObjFilePath = Resolve-Path $File

        # Pull out just the file name
        $ObjFileName = Split-Path $ObjFilePath -Leaf

        # Fixed structure sizes
        $SizeofCOFFFileHeader = 20
        $SizeofSectionHeader = 40
        $SizeofSymbolTableEntry = 18
        $SizeofRelocationEntry = 10

        # Open the object file for reading
        $FileStream = [IO.File]::OpenRead($ObjFilePath)

        $FileLength = $FileStream.Length

        if ($FileLength -lt $SizeofCOFFFileHeader)
        {
            # You cannot parse the COFF header if the file is not big enough to contain a COFF header.
            Write-Error "$($ObjFileName) is too small to store a COFF header."
            Dispose-Objects
            return
        }

        # Open a BinaryReader object for the object file
        $BinaryReader = New-Object IO.BinaryReader($FileStream)

        # Parse the COFF header
        $CoffHeader = New-Object COFF.HEADER($BinaryReader)

        if ($CoffHeader.SizeOfOptionalHeader -ne 0)
        {
            # Per the PECOFF specification, an object file does not have an optional header
            Write-Error "Coff header indicates the existence of an optional header. An object file cannot have an optional header."
            Dispose-Objects
            return
        }

        if ($CoffHeader.PointerToSymbolTable -eq 0)
        {
            Write-Error 'An object file is supposed to have a symbol table.'
            Dispose-Objects
            return
        }

        if ($FileLength -lt (($CoffHeader.NumberOfSections * $SizeofSectionHeader) + $SizeofCOFFFileHeader))
        {
            # The object file isn't big enough to store the number of sections present.
            Write-Error "$($ObjFileName) is too small to store section header data."
            Dispose-Objects
            return
        }

        # A string collection used to store section header names. This collection is referenced while
        # parsing the symbol table entries whose name is the same as the section header. In this case,
        # the symbol entry will have a particular auxiliary symbol table entry.
        $SectionHeaderNames = New-Object Collections.Specialized.StringCollection

        # Correlate the processor type to the relocation type. There are more relocation type defined
        # in the PECOFF specification, but I don't expect those to be present. In that case, relocation
        # entries default to X86RelocationType.
        $SectionHeaders = New-Object COFF.SECTION_HEADER[]($CoffHeader.NumberOfSections)
        $MachineTypes = @{ [COFF.Machine]::I386  = [COFF.X86RelocationType]
                           [COFF.Machine]::AMD64 = [COFF.AMD64RelocationType]
                           [COFF.Machine]::ARMV7 = [COFF.ARMRelocationType]
                           [COFF.Machine]::ARM64 = [COFF.ARMv8RelocationType] }

        # Parse section headers
        for ($i = 0; $i -lt $CoffHeader.NumberOfSections; $i++)
        {
            $SectionHeaders[$i] = New-Object COFF.SECTION_HEADER($BinaryReader)

            # Add the section name to the string collection. This will be referenced during symbol table parsing.
            $SectionHeaderNames.Add($SectionHeaders[$i].Name) | Out-Null

            # Save the current filestream position. We are about to jump out of place.
            $SavedFilePosition = $FileStream.Position

            # Check to see if the raw data points beyond the actual file size
            if (($SectionHeaders[$i].PointerToRawData + $SectionHeaders[$i].SizeOfRawData) -gt $FileLength)
            {
                Write-Error "$($SectionHeaders[$i].Name) section header's raw data exceeds the size of the object file."
                return
            }
            else
            {
                # Read the raw data into a byte array
                $FileStream.Seek($SectionHeaders[$i].PointerToRawData, 'Begin') | Out-Null
                $SectionHeaders[$i].RawData = $BinaryReader.ReadBytes($SectionHeaders[$i].SizeOfRawData)
            }

            # Check to see if the section has a relocation table
            if ($SectionHeaders[$i].PointerToRelocations -and $SectionHeaders[$i].NumberOfRelocations)
            {
                # Check to see if the relocation entries point beyond the actual file size
                if (($SectionHeaders[$i].PointerToRelocations + ($SizeofRelocationEntry * $SectionHeaders[$i].NumberOfRelocations)) -gt $FileLength)
                {
                    Write-Error "$($SectionHeaders[$i].Name) section header's relocation entries exceeds the soze of the object file."
                    return
                }

                $FileStream.Seek($SectionHeaders[$i].PointerToRelocations, 'Begin') | Out-Null

                $Relocations = New-Object COFF.RelocationEntry[]($SectionHeaders[$i].NumberOfRelocations)

                for ($j = 0; $j -lt $SectionHeaders[$i].NumberOfRelocations; $j++)
                {
                    $Relocations[$j] = New-Object COFF.RelocationEntry($BinaryReader)
                    # Cast the relocation as its respective type
                    $Relocations[$j].Type = ($Relocations[$j].Type.value__ -as $MachineTypes[$CoffHeader.Machine])
                }

                # Add the relocation table entry to the section header
                $SectionHeaders[$i].Relocations = $Relocations
            }
    
            # Restore the original filestream pointer
            $FileStream.Seek($SavedFilePosition, 'Begin') | Out-Null
        }

        # Retrieve the contents of the COFF string table
        $SymTableSize = $CoffHeader.NumberOfSymbols * $SizeofSymbolTableEntry
        $StringTableOffset = $CoffHeader.PointerToSymbolTable + $SymTableSize

        if ($StringTableOffset -gt $FileLength)
        {
            Write-Error 'The string table points beyond the end of the file.'
            Dispose-Objects
            return
        }

        $FileStream.Seek($StringTableOffset, 'Begin') | Out-Null
        $StringTableLength = $BinaryReader.ReadUInt32()

        if ($StringTableLength -gt $FileLength)
        {
            Write-Error "The string table's length exceeds the length of the file."
            Dispose-Objects
            return
        }

        $StringTable = [Text.Encoding]::UTF8.GetString($BinaryReader.ReadBytes($StringTableLength))

        $RawSymbolTable = New-Object COFF.SYMBOL_TABLE[]($CoffHeader.NumberOfSymbols)

        # Retrieve the symbol table
        if ($FileLength -lt $StringTableOffset)
        {
            "Symbol table is larger than the file size."
            return
        }

        $FileStream.Seek($CoffHeader.PointerToSymbolTable, 'Begin') | Out-Null
        $NumberofRegularSymbols = 0

        <#
            Go through each symbol table looking for auxiliary symbols to parse

            Currently supported auxiliary symbol table entry formats:
            1) .file
            2) Entry names that match the name of a section header
        #>

        for ($i = 0; $i -lt $CoffHeader.NumberOfSymbols; $i++)
        {
            # Parse the symbol tables regardless of whether they are normal or auxiliary symbols
            $RawSymbolTable[$i] = New-Object COFF.SYMBOL_TABLE($BinaryReader)

            if ($RawSymbolTable[$i].NumberOfAuxSymbols -eq 0)
            {
                # This symbol table entry has no auxiliary symbols
                $NumberofRegularSymbols++
            }
            elseif ($RawSymbolTable[$i].Name -eq '.file')
            {
                $TempPosition = $FileStream.Position # Save filestream position
                # Retrieve the file name
                $RawSymbolTable[$i].AuxSymbols = [Text.Encoding]::UTF8.GetString($BinaryReader.ReadBytes($RawSymbolTable[$i].NumberOfAuxSymbols * $SizeofSymbolTableEntry)).TrimEnd(([Char] 0))
                $FileStream.Seek($TempPosition, 'Begin') | Out-Null # Restore filestream position
            }
            elseif ($SectionHeaderNames.Contains($RawSymbolTable[$i].Name))
            {
                $TempPosition = $FileStream.Position # Save filestream position
                $RawSymbolTable[$i].AuxSymbols = New-Object COFF.SECTION_DEFINITION($BinaryReader)
                $FileStream.Seek($TempPosition, 'Begin') | Out-Null # Restore filestream position
            }
        }

        # Create an array of symbol table entries without auxiliary table entries
        $SymbolTable = New-Object COFF.SYMBOL_TABLE[]($NumberofRegularSymbols)
        $j = 0

        for ($i = 0; $i -lt $CoffHeader.NumberOfSymbols; $i++)
        {
            $SymbolTable[$j] = $RawSymbolTable[$i] # FYI, the first symbol table entry will never be an aux symbol
            $j++

            # Skip over the auxiliary symbols
            if ($RawSymbolTable[$i].NumberOfAuxSymbols -ne 0)
            {
                $i += $RawSymbolTable[$i].NumberOfAuxSymbols
            }
        }

        # Dispose the binaryreader and filestream objects
        Dispose-Objects

        # Fix the section names if any of them point to the COFF string table
        for ($i = 0; $i -lt $CoffHeader.NumberOfSections; $i++)
        {
            if ($SectionHeaders[$i].Name.IndexOf('/') -eq 0)
            {
                $StringTableIndex = $SectionHeaders[$i].Name.SubString(1)

                if ($StringTableIndex -match '^[1-9][0-9]*$')
                {
                    $StringTableIndex = ([Int] $StringTableIndex) - 4

                    if ($StringTableIndex -gt ($StringTableLength + 4))
                    {
                        Write-Error 'String table entry exceeds the bounds of the object file.'
                    }

                    $Length = $StringTable.IndexOf(([Char] 0), $StringTableIndex)
                    $SectionHeaders[$i].Name = $StringTable.Substring($StringTableIndex, $Length)
                }
            }
        }

        # Fix the symbol table names
        for ($i = 0; $i -lt $SymbolTable.Length; $i++)
        {
            if ($SymbolTable[$i].Name.IndexOf('/') -eq 0)
            {
                $StringTableIndex = $SymbolTable[$i].Name.SubString(1)

                if ($StringTableIndex -match '^[1-9][0-9]*$')
                {
                    $StringTableIndex = ([Int] $StringTableIndex) - 4
                    $Length = $StringTable.IndexOf(([Char] 0), $StringTableIndex) - $StringTableIndex
                    $SymbolTable[$i].Name = $StringTable.Substring($StringTableIndex, $Length)
                }
            }
        }

        # Apply symbol names to the relocation entries
        $SectionHeaders | Where-Object { $_.Relocations } | % {
            $_.Relocations | % { $_.Name = $RawSymbolTable[$_.SymbolTableIndex].Name }
        }

        $Result = @{
            COFFHeader = $CoffHeader
            SectionHeaders = $SectionHeaders
            SymbolTable = $SymbolTable
        }

        $ParsedObjectFile = New-Object PSObject -Property $Result
        $ParsedObjectFile.PSObject.TypeNames[0] = 'COFF.OBJECT_FILE'
        Write-Output $ParsedObjectFile

        }
    }

    END {}
}