Sunday, July 22, 2012

Get-PEHeader - A Scriptable In-memory and On-disk PE Parsing Utility

Introducing, yet another PE parsing utility! Where Get-PEHeader differentiates itself though is that it will parse 32 and 64-bit executables both on disk and loaded in memory. Where it really shines is in its scriptability. For example, you can pipe the output of ls (Get-ChildItem) or ps (Get-Process) right to Get-PEHeader and it will return to you a fully parsed PE header.

In this post, I'm going to show you how to:

1) Set it up and get help
2) Use Get-PEHeader to analyze every loaded module in a process
3) Interactively inspect the in-memory PE header of kernel32.dll
4) Do some quick and dirty memory forensics using a reflectively loaded meterpreter sample
5) Analyze PE headers on disk

Setting things up

I've decided to package all of my PE tools from the PowerSploit project into a module for increased portability. For those who aren't terribly familiar with PowerShell, importing a module isn't initially straightforward. Just follow the following steps and refer to the screenshot below to get things up and running:

1) Download the PETools folder from the PowerSploit project
2) Drop the folder into one of your module paths. You can view your default module paths in Powershell by typing:


 - After you drop the folder in the module path, restart PowerShell
 - The default per-user module path is: "$Env:HomeDrive$Env:HOMEPATH\Documents\WindowsPowerShell\Modules"
 - The default computer-level module path is: "$Env:windir\System32\WindowsPowerShell\v1.0\Modules"

3) Import all the scripts in the module

Import-Module PETools

4) You can view all the cmdlets that were imported by typing:

Get-Command -Module PETools

5) For individualized help on each command, Get-Help is your friend.

Setting up the PETools module for use

Using Get-PEHeader to analyze every loaded module in a process

This is easy. We're going to use cmd.exe as an example. Simply run the following command:

$CmdModules = Get-Process cmd | Get-PEHeader

Note that this will only work if there is only one instance of cmd.exe open. If there is more than one, just give Get-Process a specific process ID.

Once the command completes (it may take ~30-40 seconds) you can start poking around. If you try this yourself, you'll see that it parsed all of the following modules:

$CmdModules | Select-Object Module


Parsing PE headers of cmd.exe loaded modules

Interactively poking around the in memory PE header for kernel32.dll

Now you can start digging deeper. Let's check out kernel32.dll. In particular, I'm interested in looking at all of its exported functions.

$Kernel32PE = $CmdModules[2]

You'll see that listing the module's exports lists the virtual address, exported ordinal, name, and forwarded name (if available)

Inspecting the exported functions of kernel32.dll in memory

Doing some quick and dirty memory forensics using a reflectively loaded meterpreter sample

Now let's move beyond the novelties and start doing some in-memory malware analysis. I injected meterpreter into one of my running processes. For the armchair malware analyst, you probably wouldn't stumble upon anything terribly suspicious when attempting to view the loaded dlls of the infected process. That's because meterpreter gets reflectively loaded - i.e. it has a self-loading stub that loads itself in memory rather than having Windows do it. Since the main meterpreter dll - metsrv.dll is not visible to Windows in user-mode, you have to dig in a little deeper to find the address where it was loaded in memory. To quickly find the address, I attached to the process in WinDbg and used the .imgscan command to quickly determine the base address of metsrv.dll - 0x02560000. Armed with the knowledge of the infected process ID and the base address of the malicious module, you can use Get-PEHeader to dissect the PE header of meterpreter in memory.

$EvilHiddenDll = Get-PEHeader -ProcessId 4448 -ModuleBaseAddress 0x02560000
$EvilHiddenDll.Exports | Sort-Object -Descending VirtualAddress

Tip to malware authors: if you're loading your own module in memory, the executable doesn't need to start with 'MZ.' Just sayin'

As can be seen in the screenshot below, there are some things that just don't look right. First, if you've looked at enough DOS headers, you'd notice that some of those values don't look right. So if you broke out a hex editor and a disassembler, you'd see that the DOS header was comprised of an x86 assembly self-loader stub. Fortunately, you don't even have to get that sophisticated. If you look at the exports, you'll see '_ReflectiveLoader@0' and 'Init' - a sure sign you're dealing with meterpreter. By the way, the name decoration around ReflectiveLoader tells you that it's a stdcall function that takes zero arguments.

Pulling apart meterpreter in memory
metsrv.dll optional and file headers. Note correctly displayed TimeDateStamp (in GMT)

Analyzing PE headers of files on disk

Finally, you can use Get-PEHeader to analyze the PE headers of files on disk:

$CalcHeader = Get-PEHeader C:\Windows\System32\calc.exe

Parsing the calc.exe PE header on disk

Get-PEHeader in its current form is not nearly as sophisticated as dumpbin or CFF Explorer. Those tools are only able to parse PEs on disk, however. Currently, Get-PEHeader will parse and return the following:

- DOS Header
- File Header
- Optional Header
- Data Directories
- Section Headers
- Imports
- Exports

Should I find the time and motivation in the future, I hope to add the following features:

- Resource parsing
- CLR header parsing
- Resolution of addresses of forwarded functions
- Authenticode signature parsing
- etc.

Let me know what you think and please let me know if you have any questions (please no generic PowerShell questions) or bug reports. Enjoy!


  1. Hey there good stuff.... Can you point metro resources as why MZ is not required? Thanks

    1. Thanks.

      I'd like to be able to parse all resources at some point. Regardless, I definitely need to refer to the updated PE spec to see what has been added for Windows 8.

      If you're asking why the MZ header is not required, it's because if you load yourself into memory, you don't need 'MZ' as a sanity check.

  2. This is fantastic stuff. Good job!

    That reminds me - did you happen to see this nifty looking poster:

  3. Thanks. :D

    I have checked out that poster. Ange does awesome work. My main references though were this poster from and winnt.h.

  4. This is awesome! If you decide to add the authenticode/sigcheck-type functionality - that would be amazing... Also, since I haven't had the time to go through the code, my question is 'can this function be administered remotely like icm?' If so, I'm in heaven.

    1. Thanks. I agree that certificate parsing would be awesome but it's not a priority for me right now.

      You can certainly run it remotely with Invoke-Command but you'll have to pass the Get-PEHeader function definition along with the Get-PEHeader command itself since the remote machine doesn't have the function defined. I wrote an Invoke-Command proxy function to seamlessly automate this process so that the function definition will be automatically passed to the remote machine.