PowerSploit/PETools/Get-LibSymbols.ps1

283 lines
9.9 KiB
PowerShell

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 {}
}