Analysing PE executables and their ASLR, DEP, SEH and CFG security flags

In my previous blog post I briefly mentioned the benefit of analysing PE executables, the current file format used for Windows 32-bit and 64-bit executables. That got me coding for half an hour in Python, and in this post I’ll step through how we can answer these kinds of questions.

PE files contain a number of header values that indicate how the executable will behave with modern security technologies built-in to the operating system. Two of the most important in my mind are ASLR and DEP, but there are numerous other capabilities that compilers can support that are worth looking into.

ASLR is one of the most significant developments in system security in the last 15 years or so, and was brought into the mainstream by OpenBSD in 2003. Exploits that corrupt memory and modify the next execution location were widespread in the 1990s and early 2000s, as operating systems typically used predictable memory locations for key data when processes were created.

ASLR randomises the address space, making it more difficult for an attacker to predict locations of corrupted data structures, making entire classes of exploits less potent. ASLR typically throws an exception of one form or another, terminating a process, in the event of an exploitation attempt. This means previously potent Remote Code Execution exploits may remain somewhat potent in that they are turned into Denial of Service attacks.

However, ASLR has to be supported by the executable and the compiler that compiled it. Many system administrators incorrectly assume that if ASLR is enabled in the OS, then all software will benefit from its protections.

For system administrators who operate good INFOSEC and use supported and updated software, the protection will gradually enhance. This will be because developers in the supply chain will use compilers and build scripts that enable these protections, which naturally feeds into the supply chain. This is actually, for once, an aspect of the supply chain that is beneficial for cyber security (hurrah) – it definitely pays to use the latest versions of software.

For other cases, the picture is not so great. A great many programs are floating around, particularly legacy software products, that do not support ASLR, and these executables indicate the absence of support through their PE executable headers. If the executable does not support ASLR, then it will not be applied when the OS loads the executable. It is entirely possible a programmer will not enable ASLR flags in their compiler, the result being an executable that is vulnerable to RCE exploitation.

This means some software, for example that processing untrusted code and/or receiving data from systems on a network, could remain vulnerable to RCE exploits on a system supporting ASLR. Many system administrators do not recognise this as a risk.

PE format is used much more widely than executables, and can be found at the core of DLLs, SYS, FON font files, object code, and as been used on a variety of architectures.

How can we determine what options are supported by a particular binary file? Python has the answer. The pefile module provides a dissector for PE files that we can easily adapt for our purposes.

First, we do an upgrade of the pip installer that is part of Python as part of good housekeeping:

C:\> python -m pip install –upgrade pip

First, we install pefile using the Python package installer pip. (I’m assume here that Python 3.5 is installed already on a Windows host.)

C:\> pip install pefile

Now, let’s code up some test code to see if we can get pefile to work, picking a few headers out we can check:

import pefile

fname = "c:\\temp\\iexplore.exe"

pe = pefile.PE(fname)

def printPEHeader(headername, value):

   print("Header value", headername, ": ", hex(value))

print("PE File Header Printer v1.00")

printPEHeader("Length", pe.VS_VERSIONINFO.Length)

printPEHeader("Type", pe.VS_VERSIONINFO.Type)

printPEHeader("Value Length", pe.VS_VERSIONINFO.ValueLength)

printPEHeader("Signature", pe.VS_FIXEDFILEINFO.Signature)

printPEHeader("File Flags", pe.VS_FIXEDFILEINFO.FileFlags)

printPEHeader("File OS", pe.VS_FIXEDFILEINFO.FileOS)

And we get some useful output:

C:\>python pefile1.py

PE File Header Printer v1.00

Header value Length :  0x5c4

Header value Type :  0x0

Header value Value Length :  0x34

Header value Signature :  0xfeef04bd

Header value File Flags :  0x0

Header value File OS :  0x40004

Let’s do some rework and add in the detection logic for the security flags of interest:

# PE File ASLR, DEP, SEH and CFG detection script

# Author: Richard Gunstone

# http://richardgunstone.net rgunstone [at] bcs.org.uk

# ################################################# Imports

import pefile

# ################################################# Globals

fname = "c:\\temp\\iexplore.exe"

pe = pefile.PE(fname)

# Code for flag detection adapted from https://bit.ly/2LYPWYb

# Values for logical AND

DYNAMIC_BASE = 0x0040

NX_COMPAT = 0x0100

NO_SEH = 0x0400

GUARD_CF = 0x4000

# Use logical and to detect ASLR flag in DllCharacteristics PE header attribute

def aslr():

   return bool(pe.OPTIONAL_HEADER.DllCharacteristics & DYNAMIC_BASE)

# Use logical and to detect DEP flag in DllCharacteristics PE header attribute

def dep():

   return bool(pe.OPTIONAL_HEADER.DllCharacteristics & NX_COMPAT)

# Use logical and to detect SEH flag in DllCharacteristics PE header attribute

def seh():

   return bool(pe.OPTIONAL_HEADER.DllCharacteristics & NO_SEH)

# Use logical and to detect CFG flag in DllCharacteristics PE header attribute

def CFG():

   return bool(pe.OPTIONAL_HEADER.DllCharacteristics & GUARD_CF)

# End adapted code

# Print an arbitrary PE header as hex

def printPEHeader(headername, value):

   print("Header value", headername, ": ", hex(value))


print("PE File Header Printer v1.00")

print("------------[Extracts]------------------------")

printPEHeader("Length", pe.VS_VERSIONINFO.Length)

printPEHeader("Type", pe.VS_VERSIONINFO.Type)

printPEHeader("Value Length", pe.VS_VERSIONINFO.ValueLength)

printPEHeader("Signature", pe.VS_FIXEDFILEINFO.Signature)

printPEHeader("File Flags", pe.VS_FIXEDFILEINFO.FileFlags)

printPEHeader("File OS", pe.VS_FIXEDFILEINFO.FileOS)

print("------------[Security flags]------------------")

print("PE file supports ASLR?", aslr())

print("PE file supports DEP?", dep())

print("PE file supports SEH?", seh())

print("PE file supports CFG?", CFG())

print("------------[PE Dump]-------------------------")

print(pe.dump_info())

The above code prints ASLR flag data from any PE file, and will also output DEP, SEG and CFG security properties in addition. You can point it at any executable and it will give you a good indicator of the support for these kinds of capabilities in the OS.

So what can we see for a popular binary such as IEXPLORE.EXE?

PE File Header Printer v1.00
------------[Extracts]------------------------
Header value Length : 0x5c4
Header value Type : 0x0
Header value Value Length : 0x34
Header value Signature : 0xfeef04bd
Header value File Flags : 0x0
Header value File OS : 0x40004
------------[Security flags]------------------
PE file supports ASLR? True
PE file supports DEP? True
PE file supports SEH? False
PE file supports CFG? True
------------[PE Dump]-------------------------
<snip>

Some promising data there. ASLR, DEP and CFG are supported, as we might expect.

Reviewing PE headers is a good idea, particularly for Windows hosts that are implementing critical security functions within networks. With some Python coding you can easily enhance your awareness of where the issues may sit within your installed software base.

The problem that ASLR attempts to remedy has not gone away, and has in fact had a new lease of life through IoT devices now being connected to networks and the Internet. For IoT systems, some of which may have had rare updates and little or no significant system security enhancements, ASLR protections can be entirely absent.