PETools/Get-LibSymbols.ps1

function Get-LibSymbols
{
<#
.SYNOPSIS

    Displays symbolic information from Windows lib files.

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

.DESCRIPTION

    Get-LibSymbols parses and returns symbols in Windows .lib files
    in both decorated and undecorated form (for C++ functions).

.PARAMETER Path

    Specifies a path to one or more lib file locations.

.EXAMPLE

    C:\PS>Get-LibSymbols -Path msvcrt.lib

.EXAMPLE

    C:\PS>ls *.lib | Get-LibSymbols

.INPUTS

    System.String[]

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

.OUTPUTS

    COFF.SymbolInfo

.LINK

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

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

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

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

                public HEADER(BinaryReader br)
                {
                    this.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 = br.ReadUInt16();
                }
            }

            public class IMAGE_ARCHIVE_MEMBER_HEADER
            {
                public string Name;
                public DateTime Date;
                public ulong Size;
                public string EndHeader;

                public IMAGE_ARCHIVE_MEMBER_HEADER(BinaryReader br)
                {
                    string tempName = Encoding.UTF8.GetString(br.ReadBytes(16));
                    DateTime dt = new DateTime(1970, 1, 1, 0, 0, 0);
                    this.Name = tempName.Substring(0, tempName.IndexOf((Char) 47));
                    this.Date = dt.AddSeconds(Convert.ToDouble(Encoding.UTF8.GetString(br.ReadBytes(12)).Split((Char) 20)[0]));
                    br.ReadBytes(20); // Skip over UserID, GroupID, and Mode. They are useless fields.
                    this.Size = Convert.ToUInt64(Encoding.UTF8.GetString(br.ReadBytes(10)).Split((Char) 20)[0]);
                    this.EndHeader = Encoding.UTF8.GetString(br.ReadBytes(2));
                }
            }

            public class Functions
            {
                [DllImport("dbghelp.dll", SetLastError=true, PreserveSig=true)]
                public static extern int UnDecorateSymbolName(
                    [In] [MarshalAs(UnmanagedType.LPStr)] string DecoratedName,
                    [Out] StringBuilder UnDecoratedName,
                    [In] [MarshalAs(UnmanagedType.U4)] uint UndecoratedLength,
                    [In] [MarshalAs(UnmanagedType.U4)] uint Flags);
            }
        }
'@


        Add-Type -TypeDefinition $Code

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

    PROCESS
    {
        foreach ($File in $Path)
        {
            # Resolve the absolute path of the lib file. [IO.File]::OpenRead requires an absolute path.
            $LibFilePath = Resolve-Path $File

            # Pull out just the file name
            $LibFileName = Split-Path $LibFilePath -Leaf

            $IMAGE_SIZEOF_ARCHIVE_MEMBER_HDR = 60
            $IMAGE_ARCHIVE_START = "!<arch>`n" # Magic used for lib files
            $IMAGE_SIZEOF_LIB_HDR = $IMAGE_SIZEOF_ARCHIVE_MEMBER_HDR + $IMAGE_ARCHIVE_START.Length
            $IMAGE_ARCHIVE_END = "```n" # Footer of an archive header
            $SizeofCOFFFileHeader = 20

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

            $FileLength = $FileStream.Length

            # Validate lib header size
            if ($FileLength -lt $IMAGE_SIZEOF_LIB_HDR)
            {
                # You cannot parse the lib header if the file is not big enough to contain a lib header.
                Write-Error "$($LibFileName) is too small to store a lib header."
                $FileStream.Dispose()
                return
            }

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

            $ArchiveStart = [Text.Encoding]::UTF8.GetString($BinaryReader.ReadBytes(8))

            if ($ArchiveStart -ne $IMAGE_ARCHIVE_START)
            {
                Write-Error "$($LibFileName) does not contain a valid lib header."
                Dispose-Objects
                return
            }

            # Parse the first archive header
            $ArchiveHeader = New-Object COFF.IMAGE_ARCHIVE_MEMBER_HEADER($BinaryReader)

            if ($ArchiveHeader.EndHeader -ne $IMAGE_ARCHIVE_END)
            {
                Write-Error "$($LibFileName) does not contain a valid lib header."
                Dispose-Objects
                return
            }

            # Check for the existence of symbols
            if ($ArchiveHeader.Size -eq 0)
            {
                Write-Warning "$($LibFileName) contains no symbols."
                Dispose-Objects
                return
            }

            $NumberOfSymbols = $BinaryReader.ReadBytes(4)

            # The offsets in the first archive header of a Microsoft lib file are stored in big-endian format
            if ([BitConverter]::IsLittleEndian)
            {
                [Array]::Reverse($NumberOfSymbols)
            }

            $NumberOfSymbols = [BitConverter]::ToUInt32($NumberOfSymbols, 0)

            $SymbolOffsets = New-Object UInt32[]($NumberOfSymbols)

            foreach ($Offset in 0..($SymbolOffsets.Length - 1))
            {
                $SymbolOffset = $BinaryReader.ReadBytes(4)

                if ([BitConverter]::IsLittleEndian)
                {
                    [Array]::Reverse($SymbolOffset)
                }

                $SymbolOffsets[$Offset] = [BitConverter]::ToUInt32($SymbolOffset, 0)
            }

            $SymbolStringLength = $ArchiveHeader.Size + $IMAGE_SIZEOF_LIB_HDR - $FileStream.Position - 1
            # $SymbolStrings = [Text.Encoding]::UTF8.GetString($BinaryReader.ReadBytes($SymbolStringLength)).Split([Char] 0)

            # Write-Output $SymbolStrings

            # There will be many duplicate offset entries. Remove them.
            $SymbolOffsetsSorted = $SymbolOffsets | Sort-Object -Unique

            $SymbolOffsetsSorted | ForEach-Object {
                # Seek to the each repective offset in the file
                $FileStream.Seek($_, 'Begin') | Out-Null

                $ArchiveHeader = New-Object COFF.IMAGE_ARCHIVE_MEMBER_HEADER($BinaryReader)

                # This is not a true COFF header. It's the same size and mostly resembles a standard COFF header
                # but Microsoft placed a marker (0xFFFF) in the first WORD to indicate that the 'object file'
                # consists solely of the module name and symbol.
                $CoffHeader = New-Object COFF.HEADER($BinaryReader)

                # Check for 0xFFFF flag value
                if ($CoffHeader.NumberOfSections -eq [UInt16]::MaxValue)
                {
                    # Get the total length of the module and symbol name
                    $SymbolStringLength = $CoffHeader.NumberOfSymbols
                    $Symbols = [Text.Encoding]::UTF8.GetString($BinaryReader.ReadBytes($SymbolStringLength)).Split([Char] 0)

                    $DecoratedSymbol = $Symbols[0]
                    $UndecoratedSymbol = ''

                    # Default to a 'C' type symbol unless it starts with a '?'
                    $SymbolType = 'C'

                    # Is the symbol a C++ type?
                    if ($DecoratedSymbol.StartsWith('?'))
                    {
                        $StrBuilder = New-Object Text.Stringbuilder(512)
                        # Magically undecorated the convoluted C++ symbol into a proper C++ function definition
                        [COFF.Functions]::UnDecorateSymbolName($DecoratedSymbol, $StrBuilder, $StrBuilder.Capacity, 0) | Out-Null
                        $UndecoratedSymbol = $StrBuilder.ToString()
                        $SymbolType = 'C++'
                    }
                    else
                    {
                        if ($DecoratedSymbol[0] -eq '_' -or $DecoratedSymbol[0] -eq '@')
                        {
                            $UndecoratedSymbol = $DecoratedSymbol.Substring(1).Split('@')[0]
                        }
                        else
                        {
                            $UndecoratedSymbol = $DecoratedSymbol.Split('@')[0]
                        }
                    }

                    $SymInfo = @{
                        DecoratedName = $DecoratedSymbol
                        UndecoratedName = $UndecoratedSymbol
                        Module = $Symbols[1]
                        SymbolType = $SymbolType
                    }

                    $ParsedSymbol = New-Object PSObject -Property $SymInfo
                    $ParsedSymbol.PSObject.TypeNames[0] = 'COFF.SymbolInfo'

                    Write-Output $ParsedSymbol
                }
            }

            # Close file and binaryreader objects
            Dispose-Objects
        }
    }

    END {}
}