function Get-PEHeader { <# .SYNOPSIS Parses and outputs the PE header of a process in memory or a PE file on disk. PowerSploit Function: Get-PEHeader Author: Matthew Graeber (@mattifestation) License: BSD 3-Clause Required Dependencies: None Optional Dependencies: PETools.format.ps1xml .DESCRIPTION Get-PEHeader retrieves PE headers including imports and exports from either a file on disk or a module in memory. Get-PEHeader will operate on single PE header but you can also feed it the output of Get-ChildItem or Get-Process! Get-PEHeader works on both 32 and 64-bit modules. .OUTPUTS System.Object Returns a custom object consisting of the following: compile time, section headers, module name, DOS header, imports, exports, file header, optional header, and PE signature. .EXAMPLE C:\PS> Get-Process cmd | Get-PEHeader Description ----------- Returns the full PE headers of every loaded module in memory .EXAMPLE C:\PS> Get-ChildItem C:\Windows\*.exe | Get-PEHeader Description ----------- Returns the full PE headers of every exe in C:\Windows\ .EXAMPLE C:\PS> Get-PEHeader C:\Windows\System32\kernel32.dll Module : C:\Windows\System32\kernel32.dll DOSHeader : PE+_IMAGE_DOS_HEADER FileHeader : PE+_IMAGE_FILE_HEADER OptionalHeader : PE+_IMAGE_OPTIONAL_HEADER32 SectionHeaders : {.text, .data, .rsrc, .reloc} Imports : {@{Ordinal=; FunctionName=RtlUnwind; ModuleName=API-MS-Win-Core-RtlSupport-L1-1-0. dll; VA=0x000CB630}, @{Ordinal=; FunctionName=RtlCaptureContext; ModuleName=API-MS -Win-Core-RtlSupport-L1-1-0.dll; VA=0x000CB63C}, @{Ordinal=; FunctionName=RtlCaptu reStackBackTrace; ModuleName=API-MS-Win-Core-RtlSupport-L1-1-0.dll; VA=0x000CB650} , @{Ordinal=; FunctionName=NtCreateEvent; ModuleName=ntdll.dll; VA=0x000CB66C}...} Exports : {@{ForwardedName=; FunctionName=lstrlenW; Ordinal=0x0552; VA=0x0F022708}, @{Forwar dedName=; FunctionName=lstrlenA; Ordinal=0x0551; VA=0x0F026A23}, @{ForwardedName=; FunctionName=lstrlen; Ordinal=0x0550; VA=0x0F026A23}, @{ForwardedName=; FunctionN ame=lstrcpynW; Ordinal=0x054F; VA=0x0F04E54E}...} .EXAMPLE C:\PS> $Proc = Get-Process cmd C:\PS> $Kernel32Base = ($Proc.Modules | Where-Object {$_.ModuleName -eq 'kernel32.dll'}).BaseAddress C:\PS> Get-PEHeader -ProcessId $Proc.Id -ModuleBaseAddress $Kernel32Base Module : DOSHeader : PE+_IMAGE_DOS_HEADER FileHeader : PE+_IMAGE_FILE_HEADER OptionalHeader : PE+_IMAGE_OPTIONAL_HEADER32 SectionHeaders : {.text, .data, .rsrc, .reloc} Imports : {@{Ordinal=; FunctionName=RtlUnwind; ModuleName=API-MS-Win-Core-RtlSupport-L1-1-0. dll; VA=0x77B8B6D9}, @{Ordinal=; FunctionName=RtlCaptureContext; ModuleName=API-MS -Win-Core-RtlSupport-L1-1-0.dll; VA=0x77B8B4CB}, @{Ordinal=; FunctionName=RtlCaptu reStackBackTrace; ModuleName=API-MS-Win-Core-RtlSupport-L1-1-0.dll; VA=0x77B95277} , @{Ordinal=; FunctionName=NtCreateEvent; ModuleName=ntdll.dll; VA=0x77B4FF54}...} Exports : {@{ForwardedName=; FunctionName=lstrlenW; Ordinal=0x0552; VA=0x08221720}, @{Forwar dedName=; FunctionName=lstrlenA; Ordinal=0x0551; VA=0x08225A3B}, @{ForwardedName=; FunctionName=lstrlen; Ordinal=0x0550; VA=0x08225A3B}, @{ForwardedName=; FunctionN ame=lstrcpynW; Ordinal=0x054F; VA=0x0824D566}...} Description ----------- A PE header is returned upon providing the module's base address. This technique would be useful for dumping the PE header of a rogue module that is invisible to Windows - e.g. a reflectively loaded meterpreter binary (metsrv.dll). .NOTES Be careful if you decide to specify a module base address. Get-PEHeader does not check for the existence of an MZ header. An MZ header is not a prerequisite for reflectively loading a module in memory. If you provide an address that is not an actual PE header, you could crash the process. .LINK http://www.exploit-monday.com/2012/07/get-peheader.html #> [CmdletBinding(DefaultParameterSetName = 'OnDisk')] Param ( # Path to the portable executable file on disk [Parameter(Position = 0, Mandatory = $True, ParameterSetName = 'OnDisk', ValueFromPipelineByPropertyName = $True)] [Alias('FullName')] [String[]] $FilePath, # The process ID [Parameter(Position = 0, Mandatory = $True, ParameterSetName = 'InMemory', ValueFromPipelineByPropertyName = $True)] [Alias('Id')] [Int] $ProcessID, # The name of the module. This parameter is typically only used in pipeline expressions [Parameter(Position = 2, ParameterSetName = 'InMemory', ValueFromPipelineByPropertyName = $True)] [Alias('MainModule')] [Alias('Modules')] [System.Diagnostics.ProcessModule[]] $Module, # The base address of the module [Parameter(Position = 1, ParameterSetName = 'InMemory')] [IntPtr] $ModuleBaseAddress ) PROCESS { switch ($PsCmdlet.ParameterSetName) { 'OnDisk' { if ($FilePath.Length -gt 1) { foreach ($Path in $FilePath) { Get-PEHeader $Path } } if (!(Test-Path $FilePath)) { Write-Warning 'Invalid path or file does not exist.' return } $FilePath = Resolve-Path $FilePath if ($FilePath.GetType() -eq [System.Array]) { $ModuleName = $FilePath[0] } else { $ModuleName = $FilePath } } 'InMemory' { if ($Module.Length -gt 1) { foreach ($Mod in $Module) { $ModuleBaseAddress = $Mod.BaseAddress Get-PEHeader -ProcessID $ProcessID -Module $Mod -ModuleBaseAddress $ModuleBaseAddress } } if ($ProcessID -eq $PID) { Write-Warning 'You cannot parse the PE header of the current process. Open another instance of PowerShell.' return } if ($Module) { $ModuleName = $Module[0].FileName } else { $ModuleName = '' } } } $code = @" using System; using System.Runtime.InteropServices; public class PE { [Flags] public enum IMAGE_DOS_SIGNATURE : ushort { DOS_SIGNATURE = 0x5A4D, // MZ OS2_SIGNATURE = 0x454E, // NE OS2_SIGNATURE_LE = 0x454C, // LE VXD_SIGNATURE = 0x454C, // LE } [Flags] public enum IMAGE_NT_SIGNATURE : uint { VALID_PE_SIGNATURE = 0x00004550 // PE00 } [Flags] public enum IMAGE_FILE_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, 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 CEE = 0xC0EE } [Flags] public enum IMAGE_FILE_CHARACTERISTICS : ushort { IMAGE_RELOCS_STRIPPED = 0x0001, // Relocation info stripped from file. IMAGE_EXECUTABLE_IMAGE = 0x0002, // File is executable (i.e. no unresolved external references). IMAGE_LINE_NUMS_STRIPPED = 0x0004, // Line nunbers stripped from file. IMAGE_LOCAL_SYMS_STRIPPED = 0x0008, // Local symbols stripped from file. IMAGE_AGGRESIVE_WS_TRIM = 0x0010, // Agressively trim working set IMAGE_LARGE_ADDRESS_AWARE = 0x0020, // App can handle >2gb addresses IMAGE_REVERSED_LO = 0x0080, // public bytes of machine public ushort are reversed. IMAGE_32BIT_MACHINE = 0x0100, // 32 bit public ushort machine. IMAGE_DEBUG_STRIPPED = 0x0200, // Debugging info stripped from file in .DBG file IMAGE_REMOVABLE_RUN_FROM_SWAP = 0x0400, // If Image is on removable media =copy and run from the swap file. IMAGE_NET_RUN_FROM_SWAP = 0x0800, // If Image is on Net =copy and run from the swap file. IMAGE_SYSTEM = 0x1000, // System File. IMAGE_DLL = 0x2000, // File is a DLL. IMAGE_UP_SYSTEM_ONLY = 0x4000, // File should only be run on a UP machine IMAGE_REVERSED_HI = 0x8000 // public bytes of machine public ushort are reversed. } [Flags] public enum IMAGE_NT_OPTIONAL_HDR_MAGIC : ushort { PE32 = 0x10b, PE64 = 0x20b } [Flags] public enum IMAGE_SUBSYSTEM : ushort { UNKNOWN = 0, // Unknown subsystem. NATIVE = 1, // Image doesn't require a subsystem. WINDOWS_GUI = 2, // Image runs in the Windows GUI subsystem. WINDOWS_CUI = 3, // Image runs in the Windows character subsystem. OS2_CUI = 5, // image runs in the OS/2 character subsystem. POSIX_CUI = 7, // image runs in the Posix character subsystem. NATIVE_WINDOWS = 8, // image is a native Win9x driver. WINDOWS_CE_GUI = 9, // Image runs in the Windows CE subsystem. EFI_APPLICATION = 10, EFI_BOOT_SERVICE_DRIVER = 11, EFI_RUNTIME_DRIVER = 12, EFI_ROM = 13, XBOX = 14, WINDOWS_BOOT_APPLICATION = 16 } [Flags] public enum IMAGE_DLLCHARACTERISTICS : ushort { DYNAMIC_BASE = 0x0040, // DLL can move. FORCE_INTEGRITY = 0x0080, // Code Integrity Image NX_COMPAT = 0x0100, // Image is NX compatible NO_ISOLATION = 0x0200, // Image understands isolation and doesn't want it NO_SEH = 0x0400, // Image does not use SEH. No SE handler may reside in this image NO_BIND = 0x0800, // Do not bind this image. WDM_DRIVER = 0x2000, // Driver uses WDM model TERMINAL_SERVER_AWARE = 0x8000 } [Flags] public enum IMAGE_SCN : 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. } [StructLayout(LayoutKind.Sequential, Pack=1)] public struct _IMAGE_DOS_HEADER { public IMAGE_DOS_SIGNATURE e_magic; // Magic number public ushort e_cblp; // public bytes on last page of file public ushort e_cp; // Pages in file public ushort e_crlc; // Relocations public ushort e_cparhdr; // Size of header in paragraphs public ushort e_minalloc; // Minimum extra paragraphs needed public ushort e_maxalloc; // Maximum extra paragraphs needed public ushort e_ss; // Initial (relative) SS value public ushort e_sp; // Initial SP value public ushort e_csum; // Checksum public ushort e_ip; // Initial IP value public ushort e_cs; // Initial (relative) CS value public ushort e_lfarlc; // File address of relocation table public ushort e_ovno; // Overlay number [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)] public string e_res; // This will contain 'Detours!' if patched in memory public ushort e_oemid; // OEM identifier (for e_oeminfo) public ushort e_oeminfo; // OEM information; e_oemid specific [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst=10)] // , ArraySubType=UnmanagedType.U4 public ushort[] e_res2; // Reserved public ushorts public int e_lfanew; // File address of new exe header } [StructLayout(LayoutKind.Sequential, Pack=1)] public struct _IMAGE_FILE_HEADER { public IMAGE_FILE_MACHINE Machine; public ushort NumberOfSections; public uint TimeDateStamp; public uint PointerToSymbolTable; public uint NumberOfSymbols; public ushort SizeOfOptionalHeader; public IMAGE_FILE_CHARACTERISTICS Characteristics; } [StructLayout(LayoutKind.Sequential, Pack=1)] public struct _IMAGE_NT_HEADERS32 { public IMAGE_NT_SIGNATURE Signature; public _IMAGE_FILE_HEADER FileHeader; public _IMAGE_OPTIONAL_HEADER32 OptionalHeader; } [StructLayout(LayoutKind.Sequential, Pack=1)] public struct _IMAGE_NT_HEADERS64 { public IMAGE_NT_SIGNATURE Signature; public _IMAGE_FILE_HEADER FileHeader; public _IMAGE_OPTIONAL_HEADER64 OptionalHeader; } [StructLayout(LayoutKind.Sequential, Pack=1)] public struct _IMAGE_OPTIONAL_HEADER32 { public IMAGE_NT_OPTIONAL_HDR_MAGIC Magic; public byte MajorLinkerVersion; public byte MinorLinkerVersion; public uint SizeOfCode; public uint SizeOfInitializedData; public uint SizeOfUninitializedData; public uint AddressOfEntryPoint; public uint BaseOfCode; public uint BaseOfData; public uint ImageBase; public uint SectionAlignment; public uint FileAlignment; public ushort MajorOperatingSystemVersion; public ushort MinorOperatingSystemVersion; public ushort MajorImageVersion; public ushort MinorImageVersion; public ushort MajorSubsystemVersion; public ushort MinorSubsystemVersion; public uint Win32VersionValue; public uint SizeOfImage; public uint SizeOfHeaders; public uint CheckSum; public IMAGE_SUBSYSTEM Subsystem; public IMAGE_DLLCHARACTERISTICS DllCharacteristics; public uint SizeOfStackReserve; public uint SizeOfStackCommit; public uint SizeOfHeapReserve; public uint SizeOfHeapCommit; public uint LoaderFlags; public uint NumberOfRvaAndSizes; [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst=16)] public _IMAGE_DATA_DIRECTORY[] DataDirectory; } [StructLayout(LayoutKind.Sequential, Pack=1)] public struct _IMAGE_OPTIONAL_HEADER64 { public IMAGE_NT_OPTIONAL_HDR_MAGIC Magic; public byte MajorLinkerVersion; public byte MinorLinkerVersion; public uint SizeOfCode; public uint SizeOfInitializedData; public uint SizeOfUninitializedData; public uint AddressOfEntryPoint; public uint BaseOfCode; public ulong ImageBase; public uint SectionAlignment; public uint FileAlignment; public ushort MajorOperatingSystemVersion; public ushort MinorOperatingSystemVersion; public ushort MajorImageVersion; public ushort MinorImageVersion; public ushort MajorSubsystemVersion; public ushort MinorSubsystemVersion; public uint Win32VersionValue; public uint SizeOfImage; public uint SizeOfHeaders; public uint CheckSum; public IMAGE_SUBSYSTEM Subsystem; public IMAGE_DLLCHARACTERISTICS DllCharacteristics; public ulong SizeOfStackReserve; public ulong SizeOfStackCommit; public ulong SizeOfHeapReserve; public ulong SizeOfHeapCommit; public uint LoaderFlags; public uint NumberOfRvaAndSizes; [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst=16)] public _IMAGE_DATA_DIRECTORY[] DataDirectory; } [StructLayout(LayoutKind.Sequential, Pack=1)] public struct _IMAGE_DATA_DIRECTORY { public uint VirtualAddress; public uint Size; } [StructLayout(LayoutKind.Sequential, Pack=1)] public struct _IMAGE_EXPORT_DIRECTORY { public uint Characteristics; public uint TimeDateStamp; public ushort MajorVersion; public ushort MinorVersion; public uint Name; public uint Base; public uint NumberOfFunctions; public uint NumberOfNames; public uint AddressOfFunctions; // RVA from base of image public uint AddressOfNames; // RVA from base of image public uint AddressOfNameOrdinals; // RVA from base of image } [StructLayout(LayoutKind.Sequential, Pack=1)] public struct _IMAGE_SECTION_HEADER { [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)] public string Name; 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 IMAGE_SCN Characteristics; } [StructLayout(LayoutKind.Sequential, Pack=1)] public struct _IMAGE_IMPORT_DESCRIPTOR { public uint OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA) public uint TimeDateStamp; // 0 if not bound, // -1 if bound, and real date/time stamp // in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND) // O.W. date/time stamp of DLL bound to (Old BIND) public uint ForwarderChain; // -1 if no forwarders public uint Name; public uint FirstThunk; // RVA to IAT (if bound this IAT has actual addresses) } [StructLayout(LayoutKind.Sequential, Pack=1)] public struct _IMAGE_THUNK_DATA { public IntPtr AddressOfData; // PIMAGE_IMPORT_BY_NAME } [StructLayout(LayoutKind.Sequential, Pack=1)] public struct _IMAGE_IMPORT_BY_NAME { public ushort Hint; public char Name; } } "@ $location = [PsObject].Assembly.Location $compileParams = New-Object System.CodeDom.Compiler.CompilerParameters $assemblyRange = @("System.dll", $location) $compileParams.ReferencedAssemblies.AddRange($assemblyRange) $compileParams.GenerateInMemory = $True Add-Type -TypeDefinition $code -passthru -WarningAction SilentlyContinue | Out-Null function Get-DelegateType { Param ( [Parameter(Position = 0, Mandatory = $True)] [Type[]] $Parameters, [Parameter(Position = 1)] [Type] $ReturnType = [Void] ) $Domain = [AppDomain]::CurrentDomain $DynAssembly = New-Object System.Reflection.AssemblyName('ReflectedDelegate') $AssemblyBuilder = $Domain.DefineDynamicAssembly($DynAssembly, [System.Reflection.Emit.AssemblyBuilderAccess]::Run) $ModuleBuilder = $AssemblyBuilder.DefineDynamicModule('InMemoryModule', $false) $TypeBuilder = $ModuleBuilder.DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate]) $ConstructorBuilder = $TypeBuilder.DefineConstructor('RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, $Parameters) $ConstructorBuilder.SetImplementationFlags('Runtime, Managed') $MethodBuilder = $TypeBuilder.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $ReturnType, $Parameters) $MethodBuilder.SetImplementationFlags('Runtime, Managed') return $TypeBuilder.CreateType() } function Get-ProcAddress { Param ( [Parameter(Position = 0, Mandatory = $True)] [String] $Module, [Parameter(Position = 1, Mandatory = $True)] [String] $Procedure ) # Get a reference to System.dll in the GAC $SystemAssembly = [AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') } $UnsafeNativeMethods = $SystemAssembly.GetType('Microsoft.Win32.UnsafeNativeMethods') # Get a reference to the GetModuleHandle and GetProcAddress methods $GetModuleHandle = $UnsafeNativeMethods.GetMethod('GetModuleHandle') $GetProcAddress = $UnsafeNativeMethods.GetMethod('GetProcAddress') # Get a handle to the module specified $Kern32Handle = $GetModuleHandle.Invoke($null, @($Module)) $tmpPtr = New-Object IntPtr $HandleRef = New-Object System.Runtime.InteropServices.HandleRef($tmpPtr, $Kern32Handle) # Return the address of the function return $GetProcAddress.Invoke($null, @([System.Runtime.InteropServices.HandleRef]$HandleRef, $Procedure)) } $OnDisk = $True if ($PsCmdlet.ParameterSetName -eq 'InMemory') { $OnDisk = $False } $OpenProcessAddr = Get-ProcAddress kernel32.dll OpenProcess $OpenProcessDelegate = Get-DelegateType @([UInt32], [Bool], [UInt32]) ([IntPtr]) $OpenProcess = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($OpenProcessAddr, $OpenProcessDelegate) $ReadProcessMemoryAddr = Get-ProcAddress kernel32.dll ReadProcessMemory $ReadProcessMemoryDelegate = Get-DelegateType @([IntPtr], [IntPtr], [IntPtr], [Int], [Int].MakeByRefType()) ([Bool]) $ReadProcessMemory = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($ReadProcessMemoryAddr, $ReadProcessMemoryDelegate) $CloseHandleAddr = Get-ProcAddress kernel32.dll CloseHandle $CloseHandleDelegate = Get-DelegateType @([IntPtr]) ([Bool]) $CloseHandle = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($CloseHandleAddr, $CloseHandleDelegate) if ($OnDisk) { $FileStream = New-Object System.IO.FileStream($FilePath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read) $FileByteArray = New-Object Byte[]($FileStream.Length) $FileStream.Read($FileByteArray, 0, $FileStream.Length) | Out-Null $FileStream.Close() $Handle = [System.Runtime.InteropServices.GCHandle]::Alloc($FileByteArray, 'Pinned') $PEBaseAddr = $Handle.AddrOfPinnedObject() } else { # Size of the memory page allocated for the PE header $HeaderSize = 0x1000 # Allocate space for when the PE header is read from the remote process $PEBaseAddr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($HeaderSize + 1) # Get handle to the process $hProcess = $OpenProcess.Invoke(0x10, $false, $ProcessID) # PROCESS_VM_READ (0x00000010) # Read PE header from remote process if (!$ReadProcessMemory.Invoke($hProcess, $ModuleBaseAddress, $PEBaseAddr, $HeaderSize, [Ref] 0)) { if ($ModuleName) { Write-Warning "Failed to read PE header of $ModuleName" } else { Write-Warning "Failed to read PE header of process ID: $ProcessID" } Write-Warning "Error code: 0x$([System.Runtime.InteropServices.Marshal]::GetLastWin32Error().ToString('X8'))" $CloseHandle.Invoke($hProcess) | Out-Null return } } $DosHeader = [System.Runtime.InteropServices.Marshal]::PtrToStructure($PEBaseAddr, [PE+_IMAGE_DOS_HEADER]) $PointerNtHeader = [IntPtr] ($PEBaseAddr.ToInt64() + $DosHeader.e_lfanew) $NtHeader = [System.Runtime.InteropServices.Marshal]::PtrToStructure($PointerNtHeader, [PE+_IMAGE_NT_HEADERS32]) $Architecture = ($NtHeader.FileHeader.Machine).ToString() # Define relevant structure types depending upon whether the binary is 32 or 64-bit if ($Architecture -eq 'AMD64') { $PEStruct = @{ IMAGE_OPTIONAL_HEADER = [PE+_IMAGE_OPTIONAL_HEADER64] NT_HEADER = [PE+_IMAGE_NT_HEADERS64] } Write-Verbose "Architecture: $Architecture" Write-Verbose 'Proceeding with parsing a 64-bit binary.' } elseif ($Architecture -eq 'I386') { $PEStruct = @{ IMAGE_OPTIONAL_HEADER = [PE+_IMAGE_OPTIONAL_HEADER32] NT_HEADER = [PE+_IMAGE_NT_HEADERS32] } Write-Verbose "Architecture: $Architecture" Write-Verbose 'Proceeding with parsing a 32-bit binary.' } else { Write-Warning 'This parser only supports binaries compiled for x86 or AMD64.' return } # Need to get a new NT header in case the architecture changed $NtHeader = [System.Runtime.InteropServices.Marshal]::PtrToStructure($PointerNtHeader, $PEStruct['NT_HEADER']) # Display all section headers $NumSections = $NtHeader.FileHeader.NumberOfSections $NumRva = $NtHeader.OptionalHeader.NumberOfRvaAndSizes $PointerSectionHeader = [IntPtr] ($PointerNtHeader.ToInt64() + [System.Runtime.InteropServices.Marshal]::SizeOf($PEStruct['NT_HEADER'])) $SectionHeaders = New-Object PE+_IMAGE_SECTION_HEADER[]($NumSections) foreach ($i in 0..($NumSections - 1)) { $SectionHeaders[$i] = [System.Runtime.InteropServices.Marshal]::PtrToStructure(([IntPtr] ($PointerSectionHeader.ToInt64() + ($i * [System.Runtime.InteropServices.Marshal]::SizeOf([PE+_IMAGE_SECTION_HEADER])))), [PE+_IMAGE_SECTION_HEADER]) } if (!$OnDisk) { $ReadSize = $NtHeader.OptionalHeader.SizeOfImage # Free memory allocated for the PE header [System.Runtime.InteropServices.Marshal]::FreeHGlobal($PEBaseAddr) $PEBaseAddr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($ReadSize + 1) # Read process memory of each section header foreach ($SectionHeader in $SectionHeaders) { if (!$ReadProcessMemory.Invoke($hProcess, [IntPtr] ($ModuleBaseAddress.ToInt64() + $SectionHeader.VirtualAddress), [IntPtr] ($PEBaseAddr.ToInt64() + $SectionHeader.VirtualAddress), $SectionHeader.VirtualSize, [Ref] 0)) { if ($ModuleName) { Write-Warning "Failed to read $($SectionHeader.Name) section of $ModuleName" } else { Write-Warning "Failed to read $($SectionHeader.Name) section of process ID: $ProcessID" } Write-Warning "Error code: 0x$([System.Runtime.InteropServices.Marshal]::GetLastWin32Error().ToString('X8'))" $CloseHandle.Invoke($hProcess) | Out-Null return } } # Close handle to the remote process since we no longer need to access the process. $CloseHandle.Invoke($hProcess) | Out-Null } function Get-Exports() { if ($NTHeader.OptionalHeader.DataDirectory[0].VirtualAddress -eq 0) { Write-Verbose 'Module does not contain any exports' return } # List all function Rvas in the export table $ExportPointer = [IntPtr] ($PEBaseAddr.ToInt64() + $NtHeader.OptionalHeader.DataDirectory[0].VirtualAddress) # This range will be used to test for the existence of forwarded functions $ExportDirLow = $NtHeader.OptionalHeader.DataDirectory[0].VirtualAddress if ($OnDisk) { $ExportPointer = Convert-RVAToFileOffset $ExportPointer $ExportDirLow = Convert-RVAToFileOffset $ExportDirLow $ExportDirHigh = $ExportDirLow.ToInt32() + $NtHeader.OptionalHeader.DataDirectory[0].Size } else { $ExportDirHigh = $ExportDirLow + $NtHeader.OptionalHeader.DataDirectory[0].Size } $ExportDirectory = [System.Runtime.InteropServices.Marshal]::PtrToStructure($ExportPointer, [PE+_IMAGE_EXPORT_DIRECTORY]) $AddressOfNamePtr = [IntPtr] ($PEBaseAddr.ToInt64() + $ExportDirectory.AddressOfNames) $NameOrdinalAddrPtr = [IntPtr] ($PEBaseAddr.ToInt64() + $ExportDirectory.AddressOfNameOrdinals) $AddressOfFunctionsPtr = [IntPtr] ($PEBaseAddr.ToInt64() + $ExportDirectory.AddressOfFunctions) $NumNamesFuncs = $ExportDirectory.NumberOfFunctions - $ExportDirectory.NumberOfNames $NumNames = $ExportDirectory.NumberOfNames $NumFunctions = $ExportDirectory.NumberOfFunctions $Base = $ExportDirectory.Base # Recalculate file offsets based upon relative virtual addresses if ($OnDisk) { $AddressOfNamePtr = Convert-RVAToFileOffset $AddressOfNamePtr $NameOrdinalAddrPtr = Convert-RVAToFileOffset $NameOrdinalAddrPtr $AddressOfFunctionsPtr = Convert-RVAToFileOffset $AddressOfFunctionsPtr } if ($NumFunctions -gt 0) { # Create an empty hash table that will contain indices to exported functions and their RVAs $FunctionHashTable = @{} foreach ($i in 0..($NumFunctions - 1)) { $RvaFunction = [System.Runtime.InteropServices.Marshal]::ReadInt32($AddressOfFunctionsPtr.ToInt64() + ($i * 4)) # Function is exported by ordinal if $RvaFunction -ne 0. I.E. NumberOfFunction != the number of actual, exported functions. if ($RvaFunction) { $FunctionHashTable[[Int]$i] = $RvaFunction } } # Create an empty hash table that will contain indices into RVA array and the function's name $NameHashTable = @{} foreach ($i in 0..($NumNames - 1)) { $RvaName = [System.Runtime.InteropServices.Marshal]::ReadInt32($AddressOfNamePtr.ToInt64() + ($i * 4)) $FuncNameAddr = [IntPtr] ($PEBaseAddr.ToInt64() + $RvaName) if ($OnDisk) { $FuncNameAddr= Convert-RVAToFileOffset $FuncNameAddr } $FuncName = [System.Runtime.InteropServices.Marshal]::PtrToStringAnsi($FuncNameAddr) $NameOrdinal = [Int][System.Runtime.InteropServices.Marshal]::ReadInt16($NameOrdinalAddrPtr.ToInt64() + ($i * 2)) $NameHashTable[$NameOrdinal] = $FuncName } foreach ($Key in $FunctionHashTable.Keys) { $Result = @{} if ($NameHashTable[$Key]) { $Result['FunctionName'] = $NameHashTable[$Key] } else { $Result['FunctionName'] = '' } if (($FunctionHashTable[$Key] -ge $ExportDirLow) -and ($FunctionHashTable[$Key] -lt $ExportDirHigh)) { $ForwardedNameAddr = [IntPtr] ($PEBaseAddr.ToInt64() + $FunctionHashTable[$Key]) if ($OnDisk) { $ForwardedNameAddr = Convert-RVAToFileOffset $ForwardedNameAddr } $ForwardedName = [System.Runtime.InteropServices.Marshal]::PtrToStringAnsi($ForwardedNameAddr) # This script does not attempt to resolve the virtual addresses of forwarded functions $Result['ForwardedName'] = $ForwardedName } else { $Result['ForwardedName'] = '' } $Result['Ordinal'] = "0x$(($Key + $Base).ToString('X4'))" # Uncomment this after I somehow manage to implement the RVA for the imports # $Result['RVA'] = "0x$($FunctionHashTable[$Key].ToString('X8'))" $Result['VA'] = "0x$(($FunctionHashTable[$Key] + $PEBaseAddr).ToString("X$([IntPtr]::Size*2)"))" $Export = New-Object PSObject -Property $Result $Export.PSObject.TypeNames.Insert(0, 'Export') $Export } } else { Write-Verbose 'Module does not export any functions.' } } function Get-Imports() { if ($NTHeader.OptionalHeader.DataDirectory[1].VirtualAddress -eq 0) { Write-Verbose 'Module does not contain any imports' return } $FirstImageImportDescriptorPtr = [IntPtr] ($PEBaseAddr.ToInt64() + $NtHeader.OptionalHeader.DataDirectory[1].VirtualAddress) if ($OnDisk) { $FirstImageImportDescriptorPtr = Convert-RVAToFileOffset $FirstImageImportDescriptorPtr } $ImportDescriptorPtr = $FirstImageImportDescriptorPtr $i = 0 # Get all imported modules while ($true) { $ImportDescriptorPtr = [IntPtr] ($FirstImageImportDescriptorPtr.ToInt64() + ($i * [System.Runtime.InteropServices.Marshal]::SizeOf([PE+_IMAGE_IMPORT_DESCRIPTOR]))) $ImportDescriptor = [System.Runtime.InteropServices.Marshal]::PtrToStructure($ImportDescriptorPtr, [PE+_IMAGE_IMPORT_DESCRIPTOR]) if ($ImportDescriptor.OriginalFirstThunk -eq 0) { break } $DllNamePtr = [IntPtr] ($PEBaseAddr.ToInt64() + $ImportDescriptor.Name) if ($OnDisk) { $DllNamePtr = Convert-RVAToFileOffset $DllNamePtr } $DllName = [System.Runtime.InteropServices.Marshal]::PtrToStringAnsi($DllNamePtr) $FirstFuncAddrPtr = [IntPtr] ($PEBaseAddr.ToInt64() + $ImportDescriptor.FirstThunk) if ($OnDisk) { $FirstFuncAddrPtr = Convert-RVAToFileOffset $FirstFuncAddrPtr } $FuncAddrPtr = $FirstFuncAddrPtr $FirstOFTPtr = [IntPtr] ($PEBaseAddr.ToInt64() + $ImportDescriptor.OriginalFirstThunk) if ($OnDisk) { $FirstOFTPtr = Convert-RVAToFileOffset $FirstOFTPtr } $OFTPtr = $FirstOFTPtr $j = 0 while ($true) { $FuncAddrPtr = [IntPtr] ($FirstFuncAddrPtr.ToInt64() + ($j * [System.Runtime.InteropServices.Marshal]::SizeOf([PE+_IMAGE_THUNK_DATA]))) $FuncAddr = [System.Runtime.InteropServices.Marshal]::PtrToStructure($FuncAddrPtr, [PE+_IMAGE_THUNK_DATA]) $OFTPtr = [IntPtr] ($FirstOFTPtr.ToInt64() + ($j * [System.Runtime.InteropServices.Marshal]::SizeOf([PE+_IMAGE_THUNK_DATA]))) $ThunkData = [System.Runtime.InteropServices.Marshal]::PtrToStructure($OFTPtr, [PE+_IMAGE_THUNK_DATA]) $Result = @{ ModuleName = $DllName } if (([System.Convert]::ToString($ThunkData.AddressOfData.ToInt64(),2)).PadLeft(32, '0')[0] -eq '1') { # Trim high order bit in order to get the ordinal value $TempOrdinal = [System.Convert]::ToInt64(([System.Convert]::ToString($ThunkData.AddressOfData.ToInt64(),2))[1..63] -join '', 2) $TempOrdinal = $TempOrdinal.ToString('X16')[-1..-4] [Array]::Reverse($TempOrdinal) $Ordinal = '' $TempOrdinal | ForEach-Object { $Ordinal += $_ } $Result['Ordinal'] = "0x$Ordinal" $Result['FunctionName'] = '' } else { $ImportByNamePtr = [IntPtr] ($PEBaseAddr.ToInt64() + [Int64]$ThunkData.AddressOfData + 2) if ($OnDisk) { $ImportByNamePtr = Convert-RVAToFileOffset $ImportByNamePtr } $FuncName = [System.Runtime.InteropServices.Marshal]::PtrToStringAnsi($ImportByNamePtr) $Result['Ordinal'] = '' $Result['FunctionName'] = $FuncName } $Result['VA'] = "0x$($FuncAddr.AddressOfData.ToString("X$([IntPtr]::Size*2)"))" if ($FuncAddr.AddressOfData -eq 0) { break } if ($OFTPtr -eq 0) { break } $Import = New-Object PSObject -Property $Result $Import.PSObject.TypeNames.Insert(0, 'Import') $Import $j++ } $i++ } } function Convert-RVAToFileOffset([IntPtr] $Rva) { foreach ($Section in $SectionHeaders) { if ((($Rva.ToInt64() - $PEBaseAddr.ToInt64()) -ge $Section.VirtualAddress) -and (($Rva.ToInt64() - $PEBaseAddr.ToInt64()) -lt ($Section.VirtualAddress + $Section.VirtualSize))) { return [IntPtr] ($Rva.ToInt64() - ($Section.VirtualAddress - $Section.PointerToRawData)) } } # Pointer did not fall in the address ranges of the section headers return $Rva } $PEFields = @{ Module = $ModuleName DOSHeader = $DosHeader PESignature = $NTHeader.Signature FileHeader = $NTHeader.FileHeader OptionalHeader = $NTHeader.OptionalHeader SectionHeaders = $SectionHeaders Imports = Get-Imports Exports = Get-Exports } if ($Ondisk) { $Handle.Free() } else { # Free memory allocated for the PE header [System.Runtime.InteropServices.Marshal]::FreeHGlobal($PEBaseAddr) } $PEHeader = New-Object PSObject -Property $PEFields $PEHeader.PSObject.TypeNames.Insert(0, 'PEHeader') return $PEHeader } }