etc/parser/peview.ps1

using namespace System.IO
using namespace System.Management.Automation

function Get-PeView {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory)]
    [ValidateScript({!!($script:file = Convert-Path -Path $_ -ErrorAction 0)})]
    [ValidateNotNullOrEmpty()]
    [String]$Path,

    [Parameter()][Alias('h')][Switch]$Headers,
    [Parameter()][Alias('e')][Switch]$Export,
    [Parameter()][Alias('i')][Switch]$Import,
    [Parameter()][Alias('r')][Switch]$Resources,
    [Parameter()][Alias('x')][Switch]$Exception,
    [Parameter()][Alias('c')][Switch]$Certificates,
    [Parameter()][Alias('b')][Switch]$BaseRelocation,
    [Parameter()][Alias('d')][Switch]$Debugs, # plural in case to prevent `Debug` duplication
    [Parameter()][Alias('y')][Switch]$DelayImport,

    [Parameter()]
    [ValidateScript({![String]::IsNullOrEmpty($_)})]
    [ScriptBlock]$Callback
  )

  begin {
    $Path, $RvaAndSizes = $file, ('Export', 'Import', 'Resources', 'Exception', 'Certificates',
      'BaseRelocation', 'Debugs', 'Architecture', 'GlobalPointer', 'ThreadStorage',
      'LoadConfiguration', 'BoundImport', 'ImportAddressTable', 'DelayImport', 'ComDescriptor',
      'Reserved'
    )

    function Get-Block([String]$Name, [ScriptBlock]$Fields) {
      end {
        if (!($var = $ExecutionContext.SessionState.PSVariable.Get($Name)).Value) {
          $var = Set-Variable -Name $Name -Value ([Ordered]@{}) -Scope Script -PassThru
        }

        $Fields.Ast.FindAll({$args[0].CommandElements}, $true).ToArray().ForEach{
          $type, $desc, $pack = $_.CommandElements.Value
          $type = $type -creplace 'Ptr', $Bitness
          $var.Value[$desc] = $pack ? $((1..$pack).ForEach{$br."Read$($type)"()}) : $(
            $br."Read$($type)"()
          )
        }
      }
    }

    function Convert-RvaToRaw([UInt32]$Rva, [UInt32]$Align=$IMAGE_OPTIONAL_HEADER.FileAlignment) {
      end {
        [ScriptBlock]$Aligner = {
          param([UInt32]$Size) ($Size -band ($Align - 1)) ? (($Size -band ($Align * -1)) + $Align) : $Size
        }

        $Sections.ForEach{
          if (($Rva -ge $_.VirtualAddress) -and ($Rva -lt ($_.VirtualAddress + (& $Aligner $_.VirtualSize)))) {
            return ($Rva - ($_.VirtualAddress - $_.PointerToRawData))
          }
        }
      }
    }

    function Get-RawString([UInt32]$Offset, [Switch]$NoMove) {
      end {
        $cur = $fs.Position
        $fs.Position = $Offset
        while (($c = $br.ReadByte())) { $str += [Char]$c }
        if ($NoMove) { $fs.Position = $cur }
        $str
      }
    }

    $exclude = ,'Path' + ('', 'Optional').ForEach{ [PSCmdlet]::"$($_)CommonParameters" }
    if ($PSBoundParameters.Callback) { [void]$PSBoundParameters.Remove('Callback') }
  }
  process {}
  end {
    try {
      $br = [BinaryReader]::new(($fs = [File]::OpenRead($Path)))
      if ($br.ReadUInt16() -ne 0x5A4D) { # ignore obsolete IMAGE_DOS_HEADER fields
        throw [InvalidOperationException]::new('DOS signature has not been found.')
      }
      $fs.Position = 0x3C # IMAGE_DOS_HEADER->e_lfanew
      $fs.Position = $br.ReadInt32() # jump to IMAGE_NT_HEADERS
      if ($br.ReadUInt32() -ne 0x4550) {
        throw [InvalidOperationException]::new('PE signature has not been found.')
      }
      Get-Block IMAGE_FILE_HEADER {
        UInt16 Machine
        UInt16 NumberOfSections
        UInt32 TimeDateStamp
        UInt32 PointerToSymbolTable
        UInt32 NumberOfSymbols
        UInt16 SizeOfOptionalHeader
        UInt16 Characteristics
      }
      $Bitness = (0x20, 0x40)[$IMAGE_FILE_HEADER.SizeOfOptionalHeader / 0x10 - 0x0E]
      Get-Block IMAGE_OPTIONAL_HEADER {
        UInt16 Magic
        Byte   MajorLinkerVersion
        Byte   MinorLinkerVersion
        UInt32 SizeOfCode
        UInt32 SizeOfInitializedData
        UInt32 SizeOfUninitializedData
        UInt32 AddressOfEntryPoint
        UInt32 BaseOfCode
      }
      if ($Bitness -eq 0x20) { Get-Block IMAGE_OPTIONAL_HEADER {UInt32 BaseOfData} }
      Get-Block IMAGE_OPTIONAL_HEADER {
        UIntPtr ImageBase
        UInt32  SectionAlignment
        UInt32  FileAlignment
        UInt16  MajorOperatingSystemVersion
        UInt16  MinorOperatingSystemVersion
        UInt16  MajorImageVersion
        UInt16  MinorImageVersion
        UInt16  MajorSubsystemVersion
        UInt16  MinorSubsystemVersion
        UInt32  Win32VersionValue
        UInt32  SizeOfImage
        UInt32  SizeOfHeaders
        UInt32  Checksum
        UInt16  Subsystem
        UInt16  DllCharacteristics
        UIntPtr SizeOfStackReserve
        UIntPtr SizeOfStackCommit
        UIntPtr SizeOfHeapReserve
        UIntPtr SizeOfHeapCommit
        UInt32  LoaderFlags
        UInt32  NumberOfRvaAndSizes
      }
      if ($IMAGE_OPTIONAL_HEADER.NumberOfRvaAndSizes -ne 0x10) {
        throw [InvalidOperationException]::new('PE directories are abnormal.')
      }
      $DataDirectories = (1..$IMAGE_OPTIONAL_HEADER.NumberOfRvaAndSizes).ForEach{
        [PSCustomObject]@{
          Name = $RvaAndSizes[$_ - 1]
          RVA = $br.ReadUInt32()
          Size = $br.ReadUInt32()
        }
      }
      $Sections = (1..$IMAGE_FILE_HEADER.NumberOfSections).ForEach{
        [PSCustomObject]@{
          Name = [String]::new($br.ReadBytes(0x08)).Trim("`0")
          VirtualSize = $br.ReadUInt32()
          VirtualAddress = $br.ReadUInt32()
          SizeOfRawData = $br.ReadUInt32()
          PointerToRawData = $br.ReadUInt32()
          PointerToRelocations = $br.ReadUInt32()
          PointerToLinenumbers = $br.ReadUInt32()
          NumberOfRelocations = $br.ReadUInt16()
          NumberOfLinenumbers = $br.ReadUInt16()
          Characteristics = $br.ReadUInt32()
        }
      }

      if (($selector = $PSBoundParameters.Keys.Where{$_ -notin $exclude}) -and $selector -ne 'Headers') {
        if (!($Data = $DataDirectories.Where{$_.Name -ceq $selector}).RVA) {
          throw [InvalidOperationException]::new("$selector is empty.")
        }
        & $Callback
      }
    }
    catch { Write-Verbose $_ }
    finally {
      ($br, $fs).ForEach{ if ($_) { $_.Dispose() } }
    }

    if ($selector -eq 'Headers') { $DataDirectories, $Sections }
  }
}