PETools/Get-PEHeader.ps1
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. .PARAMETER FilePath Specifies the path to the portable executable file on disk .PARAMETER ProcessID Specifies the process ID. .PARAMETER Module The name of the module. This parameter is typically only used in pipeline expressions .PARAMETER ModuleBaseAddress The base address of the module .PARAMETER GetSectionData Retrieves raw section data. .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 ( [Parameter(Position = 0, Mandatory = $True, ParameterSetName = 'OnDisk', ValueFromPipelineByPropertyName = $True)] [Alias('FullName')] [String[]] $FilePath, [Parameter(Position = 0, Mandatory = $True, ParameterSetName = 'InMemory', ValueFromPipelineByPropertyName = $True)] [Alias('Id')] [Int] $ProcessID, [Parameter(Position = 2, ParameterSetName = 'InMemory', ValueFromPipelineByPropertyName = $True)] [Alias('MainModule')] [Alias('Modules')] [System.Diagnostics.ProcessModule[]] $Module, [Parameter(Position = 1, ParameterSetName = 'InMemory')] [IntPtr] $ModuleBaseAddress, [Parameter()] [Switch] $GetSectionData ) 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) { $BaseAddr = $Mod.BaseAddress Get-PEHeader -ProcessID $ProcessID -Module $Mod -ModuleBaseAddress $BaseAddr } } if (-not $ModuleBaseAddress) { return } 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 = '' } } } try { [PE] | Out-Null } catch [Management.Automation.RuntimeException] { $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, ARMNT = 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 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_DATA32 { public Int32 AddressOfData; // PIMAGE_IMPORT_BY_NAME } [StructLayout(LayoutKind.Sequential, Pack=1)] public struct _IMAGE_THUNK_DATA64 { public Int64 AddressOfData; // PIMAGE_IMPORT_BY_NAME } [StructLayout(LayoutKind.Sequential, Pack=1)] public struct _IMAGE_IMPORT_BY_NAME { public ushort Hint; public char Name; } } "@ $compileParams = New-Object System.CodeDom.Compiler.CompilerParameters $compileParams.ReferencedAssemblies.AddRange(@('System.dll', 'mscorlib.dll')) $compileParams.GenerateInMemory = $True Add-Type -TypeDefinition $code -CompilerParameters $compileParams -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, [Type] $OpenProcessDelegate) $ReadProcessMemoryAddr = Get-ProcAddress kernel32.dll ReadProcessMemory $ReadProcessMemoryDelegate = Get-DelegateType @([IntPtr], [IntPtr], [IntPtr], [Int], [Int].MakeByRefType()) ([Bool]) $ReadProcessMemory = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($ReadProcessMemoryAddr, [Type] $ReadProcessMemoryDelegate) $CloseHandleAddr = Get-ProcAddress kernel32.dll CloseHandle $CloseHandleDelegate = Get-DelegateType @([IntPtr]) ([Bool]) $CloseHandle = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($CloseHandleAddr, [Type] $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, [Type] [PE+_IMAGE_DOS_HEADER]) $PointerNtHeader = [IntPtr] ($PEBaseAddr.ToInt64() + $DosHeader.e_lfanew) $NtHeader = [System.Runtime.InteropServices.Marshal]::PtrToStructure($PointerNtHeader, [Type] [PE+_IMAGE_NT_HEADERS32]) $Architecture = ($NtHeader.FileHeader.Machine).ToString() $BinaryPtrWidth = 4 # Define relevant structure types depending upon whether the binary is 32 or 64-bit if ($Architecture -eq 'AMD64') { $BinaryPtrWidth = 8 $PEStruct = @{ IMAGE_OPTIONAL_HEADER = [PE+_IMAGE_OPTIONAL_HEADER64] NT_HEADER = [PE+_IMAGE_NT_HEADERS64] } $ThunkDataStruct = [PE+_IMAGE_THUNK_DATA64] Write-Verbose "Architecture: $Architecture" Write-Verbose 'Proceeding with parsing a 64-bit binary.' } elseif ($Architecture -eq 'I386' -or $Architecture -eq 'ARMNT' -or $Architecture -eq 'THUMB') { $PEStruct = @{ IMAGE_OPTIONAL_HEADER = [PE+_IMAGE_OPTIONAL_HEADER32] NT_HEADER = [PE+_IMAGE_NT_HEADERS32] } $ThunkDataStruct = [PE+_IMAGE_THUNK_DATA32] Write-Verbose "Architecture: $Architecture" Write-Verbose 'Proceeding with parsing a 32-bit binary.' } else { Write-Warning 'Get-PEHeader only supports binaries compiled for x86, AMD64, and ARM.' return } # Need to get a new NT header in case the architecture changed $NtHeader = [System.Runtime.InteropServices.Marshal]::PtrToStructure($PointerNtHeader, [Type] $PEStruct['NT_HEADER']) # Display all section headers $NumSections = $NtHeader.FileHeader.NumberOfSections $NumRva = $NtHeader.OptionalHeader.NumberOfRvaAndSizes $PointerSectionHeader = [IntPtr] ($PointerNtHeader.ToInt64() + [System.Runtime.InteropServices.Marshal]::SizeOf([Type] $PEStruct['NT_HEADER'])) $SectionHeaders = New-Object PSObject[]($NumSections) foreach ($i in 0..($NumSections - 1)) { $SectionHeaders[$i] = [System.Runtime.InteropServices.Marshal]::PtrToStructure(([IntPtr] ($PointerSectionHeader.ToInt64() + ($i * [System.Runtime.InteropServices.Marshal]::SizeOf([Type] [PE+_IMAGE_SECTION_HEADER])))), [Type] [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 } if ($PSBoundParameters['GetSectionData']) { foreach ($i in 0..($NumSections - 1)) { $RawBytes = $null if ($OnDisk) { $RawBytes = New-Object Byte[]($SectionHeaders[$i].SizeOfRawData) [Runtime.InteropServices.Marshal]::Copy([IntPtr] ($PEBaseAddr.ToInt64() + $SectionHeaders[$i].PointerToRawData), $RawBytes, 0, $SectionHeaders[$i].SizeOfRawData) } else { $RawBytes = New-Object Byte[]($SectionHeaders[$i].VirtualSize) [Runtime.InteropServices.Marshal]::Copy([IntPtr] ($PEBaseAddr.ToInt64() + $SectionHeaders[$i].VirtualAddress), $RawBytes, 0, $SectionHeaders[$i].VirtualSize) } $SectionHeaders[$i] = Add-Member -InputObject ($SectionHeaders[$i]) -MemberType NoteProperty -Name RawData -Value $RawBytes -PassThru -Force } } 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, [Type] [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'))" $Result['RVA'] = "0x$($FunctionHashTable[$Key].ToString("X$($BinaryPtrWidth*2)"))" #$Result['VA'] = "0x$(($FunctionHashTable[$Key] + $PEBaseAddr.ToInt64()).ToString("X$($BinaryPtrWidth*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([Type] [PE+_IMAGE_IMPORT_DESCRIPTOR]))) $ImportDescriptor = [System.Runtime.InteropServices.Marshal]::PtrToStructure($ImportDescriptorPtr, [Type] [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([Type] $ThunkDataStruct))) $FuncAddr = [System.Runtime.InteropServices.Marshal]::PtrToStructure($FuncAddrPtr, [Type] $ThunkDataStruct) $OFTPtr = [IntPtr] ($FirstOFTPtr.ToInt64() + ($j * [System.Runtime.InteropServices.Marshal]::SizeOf([Type] $ThunkDataStruct))) $ThunkData = [System.Runtime.InteropServices.Marshal]::PtrToStructure($OFTPtr, [Type] $ThunkDataStruct) $Result = @{ ModuleName = $DllName } if (([System.Convert]::ToString($ThunkData.AddressOfData, 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, 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['RVA'] = "0x$($FuncAddr.AddressOfData.ToString("X$($BinaryPtrWidth*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') $ScriptBlock = { $SymServerURL = 'http://msdl.microsoft.com/download/symbols' $FileName = $this.Module.Split('\')[-1] $Request = "{0}/{1}/{2:X8}{3:X}/{1}" -f $SymServerURL, $FileName, $this.FileHeader.TimeDateStamp, $this.OptionalHeader.SizeOfImage $Request = "$($Request.Substring(0, $Request.Length - 1))_" $WebClient = New-Object Net.WebClient $WebClient.Headers.Add('User-Agent', 'Microsoft-Symbol-Server/6.6.0007.5') Write-Host "Downloading $FileName from the Microsoft symbol server..." $CabBytes = $WebClient.DownloadData($Request) $CabPath = "$PWD\$($FileName.Split('.')[0]).cab" Write-Host "Download complete. Saving it to $("$(Split-Path $CabPath)\$FileName")." [IO.File]::WriteAllBytes($CabPath, $CabBytes) $Shell = New-Object -Comobject Shell.Application $CabFile = $Shell.Namespace($CabPath).Items() $Destination = $Shell.Namespace((Split-Path $CabPath)) $Destination.CopyHere($CabFile) Remove-Item $CabPath -Force } $PEHeader = Add-Member -InputObject $PEHeader -MemberType ScriptMethod -Name DownloadFromMSSymbolServer -Value $ScriptBlock -PassThru -Force return $PEHeader } } |