Determining programmatically whether a file was built with LAA, ASLR, DEP, or OS-assisted /GS


Today's Little Program parses a module to determine whether or not it was built with the following flags:

Remember, Little Programs do little error checking. In particular, this Little Program does no range checking, so a malformed binary can result in wild pointers.

#include <windows.h>
#include <imagehlp.h>
#include <stdio.h> // horrors! mixing stdio and C++!
#include <stddef.h>

class MappedImage
{
public:
 bool MapImage(const char* fileName);
 void ProcessResults();
 ~MappedImage();

private:
 WORD GetCharacteristics();

 template<typename T>
 WORD GetDllCharacteristics();

 template<typename T>
 bool HasSecurityCookie();

private:
 HANDLE file_ = INVALID_HANDLE_VALUE;
 HANDLE mapping_ = nullptr;
 void *imageBase_ = nullptr;
 IMAGE_NT_HEADERS* headers_ = nullptr;
 int bitness_ = 0;
};

bool MappedImage::MapImage(const char* fileName)
{
 file_ = CreateFile(fileName, GENERIC_READ,
    FILE_SHARE_READ,
    NULL,
    OPEN_EXISTING,
    0,
    NULL);
 if (file_ == INVALID_HANDLE_VALUE) return false;

 mapping_ = CreateFileMapping(file_, NULL, PAGE_READONLY,
                              0, 0, NULL);
 if (!mapping_) return false;

 imageBase_ = MapViewOfFile(mapping_, FILE_MAP_READ, 0, 0, 0);
 if (!imageBase_) return false;

 headers_ = ImageNtHeader(imageBase_);
 if (!headers_) return false;
 if (headers_->Signature != IMAGE_NT_SIGNATURE) return false;

 switch (headers_->OptionalHeader.Magic) {
 case IMAGE_NT_OPTIONAL_HDR32_MAGIC: bitness_ = 32; break;
 case IMAGE_NT_OPTIONAL_HDR64_MAGIC: bitness_ = 64; break;
 default: return false;
 }

 return true;
}

MappedImage::~MappedImage()
{
 if (imageBase_) UnmapViewOfFile(imageBase_);
 if (mapping_) CloseHandle(mapping_);
 if (file_ != INVALID_HANDLE_VALUE) CloseHandle(file_);
}

WORD MappedImage::GetCharacteristics()
{
 return headers_->FileHeader.Characteristics;
}

template<typename T>
WORD MappedImage::GetDllCharacteristics()
{
  return reinterpret_cast<T*>(headers_)->
    OptionalHeader.DllCharacteristics;
}

template<typename T>
bool MappedImage::HasSecurityCookie()
{
 ULONG size;
 T *data = static_cast<T*>(ImageDirectoryEntryToDataEx(
    imageBase_, TRUE, IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG,
    &size, NULL));
 if (!data) return false;
 ULONG minSize = offsetof(T, SecurityCookie) +
                 sizeof(data->SecurityCookie);
 if (size < minSize) return false;
 if (data->Size < minSize) return false;
 return data->SecurityCookie != 0;
}

void MappedImage::ProcessResults()
{
 printf("%d-bit binary\n", bitness_);
 auto Characteristics = GetCharacteristics();
 printf("Large address aware: %s\n",
    (Characteristics & IMAGE_FILE_LARGE_ADDRESS_AWARE)
    ? "Yes" : "No");

 auto DllCharacteristics = bitness_ == 32
    ? GetDllCharacteristics<IMAGE_NT_HEADERS32>()
    : GetDllCharacteristics<IMAGE_NT_HEADERS64>();

 printf("ASLR: %s\n",
    (DllCharacteristics & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE)
    ? "Yes" : "No");
 printf("ASLR^2: %s\n",
    (DllCharacteristics & IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA)
    ? "Yes" : "No");
 printf("DEP: %s\n",
    (DllCharacteristics & IMAGE_DLLCHARACTERISTICS_NX_COMPAT)
    ? "Yes" : "No");
 printf("TS Aware: %s\n",
    (DllCharacteristics & IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE)
    ? "Yes" : "No");

 bool hasSecurityCookie =
    bitness_ == 32 ? HasSecurityCookie<IMAGE_LOAD_CONFIG_DIRECTORY32>()
                   : HasSecurityCookie<IMAGE_LOAD_CONFIG_DIRECTORY64>();
 printf("/GS: %s\n", hasSecurityCookie
    ? "Yes" : "No");
}

int __cdecl main(int argc, char**argv)
{
 MappedImage mappedImage;
 if (mappedImage.MapImage(argv[1])) {
  mappedImage.ProcessResults();
 }
 return 0;
}

Let's see what happened.

First we use the Map­Image method to load the binary and map it into memory. While we're at it, we sniff at the headers to determine whether it is a 32-bit or 64-bit binary.

The Get­Characteristics method merely extracts the Characteristics from the File­Header. This is easy because the File­Header is the same for 32-bit and 64-bit binaries.

The Get­Dll­Characteristics method has two versions depending on the image bitness. In both cases, it extracts the Dll­Characteristics field, but the location of the field depends on the structure.

The Has­Security­Cookie method also has two versions depending on the image bitness. The minimum size necessary to get OS-assisted stack overflow protection is the size that encompasses the Security­Cookie member, and in order to get that extra protection, the member needs to be nonzero.

What is OS-assisted stack overflow protection?

First, I'm going to assume that you've read Compiler Security Checks In Depth.

Okay, welcome back.

In theory, /GS could be implemented entirely in application code, with no need for operating system assistance. And in fact, that's what happens when the executable is run on older versions of Windows (like Windows 98 or Windows 2000). But the module can tell the operating system, "Hey, here is where I put my security cookie," and if the operating system understands this field, then it will go in and make the security cookie even more randomer than random by mixing in some cryptographically secure random bits.

Okay, so that's the program. Note that some of these flags are meaningless in DLLs, so be careful to interpret the output correctly.

Comments (25)
  1. skSdnW says:

    The ASLR detection is unreliable, that is, the image might not be relocated at run-time even when the flag is set.

    My suggestion is to see if the image has relocations, if not then it probably will not get ASLR. The last time I brought this up somebody correctly pointed out that a image with position-independent code does not need relocations. I have never seen a PIE Win32 .exe in the wild (nor have I really been looking) but I guess it is possible in theory.

    If you trust the image and it is a .dll you could VirtualAlloc some code at its preferred load address and see if LoadLibrary works (.dlls without relocations is very rare though). For a .exe I don't really know, you could perhaps start it as a suspended process? Not sure if the random ASLR base is allowed to be the same as the preferred base which I guess could happen if the number is really random (excluding the alignment requirements that ASLR already fulfils).

    [I think you misread the title. The sample code detects whether the image was built with ASLR enabled, not whether ASLR will actually be applied when loaded. (The latter is dependent upon things that cannot be determined by static analysis.) -Raymond]
  2. anonymouscommenter says:

    Are you absolutely sure that the OS really "mixes in" some cryptographically secure random bits, and that these bits stem from a cryptographically secure source?

    JFTR: I'd use LoadLibraryExW(L"…", NULL, LOAD_LIBRARY_AS_DATAFILE) to map the file

  3. anonymouscommenter says:

    skSdnW: "The ASLR detection is unreliable, that is, the image might not be relocated at run-time even when the flag is set."

    Not a requirement. Specifically:

    "Today's Little Program parses a module to determine whether or not it was built with the following flags:

    /DYNAMIC­BASE, also known as Address Space Layout Randomization (ASLR)"

  4. skSdnW says:

    @McBucket: I'm not trying to call out this sample specifically, I'm just saying that detecting if ASLR will actually be applied is hard to detect. Even Process Explorer gets it wrong and it should be able to tell because it knows the actual address used and the preferred address…

  5. anonymouscommenter says:

    @skSdnW: "Even Process Explorer gets it wrong and it should be able to tell because it knows the actual address used and the preferred address…"

    The most common reason for a DLL not to be loaded at its preferred address is that it's already occupied (in full or in part). I discrepancy between the actual address and the preferred address doesn't not imply ASLR is to blame.

  6. skSdnW says:

    @Steve: If the dll has to move and it is marked as ASLR aware then ASLR will take part in the relocation, my point was that Process Explorer will erroneously mark something with ASLR even though it has not been relocated and instead loaded at its preferred address. This is easy to do, just use editbin on a .exe that does not have relocations…

  7. anonymouscommenter says:

    I was always a fan of the constant 0 security cookie. Kind of hard to overwrite that with strcpy() or wstrcpy() w/o being caught.

    I suspect in general it's a very bad idea to write a program that toggles these bits. Most of them scream "break me" if turned on unawares. I did use some tool for turning on /LARGEADDRESSAWARE because the .NET 2.0 compiler is dumb, but that was my own binary.

    The only one I could imagine safe to set on somebody else's binary is /TSAWARE.

  8. skSdnW says:

    @Raymond: To be really pedantic, your code checks to see if the image currently has the ASLR bit set, not if it was built to be ASLR aware. To fulfill certain MS logo requirements your .exe must have the ASLR bit set. You can pass this test if the .exe was linked with /FIXED if you editbin the .exe afterwards but it of course does not give you the added security. A case of following the letter of the law and not the spirit…

  9. anonymouscommenter says:

    @skSdnW: Take an EXE linked with /FIXED and change the flag. Kaboom.

  10. Myria says:

    I personally just use "dumpbin /headers" to check these =)

    By the way, the newest one is "control-flow guard" from Visual Studio 2015 and Windows 10.  It is a DllCharacteristics flag, IMAGE_DLLCHARACTERISTICS_GUARD_CF.  This flag is declared in the 8.1 SDK, but wasn't fully used in 8.1.  Control-flow guards are crazy.  All indirect jumps and calls validate that the target pointer is a valid target address before jumping there, to try to verify that it's not about to start a ROP chain.

    Although it doesn't matter in a sample program such as this, I would be a *lot* more careful if you're going to write a program to check these flags in a potentially-hostile EXE or DLL.  There are many ways in which a PE file could attempt to cause exploits in programs that parse them.

    [Agreed that if the binary is potentially hostile, you must be far more careful than this. The idea here is that you want to write a build verification step to ensure that all the binaries in your product are compiled with specific flags. Here's how you can do it. -Raymond]
  11. anonymouscommenter says:

    skSdnW: "To be really pedantic, your code checks to see if the image currently has the ASLR bit set, not if it was built to be ASLR aware."

    Being pedantic for the wrong reason (e.g. showing off) is wrong. Read the requirements again:

    "Today's Little Program parses a module to determine whether or not it was built with the following flags:

    /DYNAMIC­BASE, also known as Address Space Layout Randomization (ASLR)"

    Then read the code again, and tell us where the code doesn't match the requirements.

  12. anonymouscommenter says:

    @Myria: At least they didn't block intentional ROP chains. (Disclaimer: don't use outside of x86-32 unless you like undefined behavior)

  13. anonymouscommenter says:

    I'm curious what sort of program would not support ASLR. And if you support ASLR, why would ASLRR support need to be declared?

    Is it simply a question of performance, or do people write code that fails if it's not loaded at the right address?

  14. skSdnW says:

    @McBucket: Please read part you quoted one more time.

    Let me focus a bit on another flag first. There is a difference between linking with /TSAWARE and adding the flag later with editbin. When you link with /TSAWARE the MS linker will try extra hard to layout the PE sections in a way so that the read-only sections can be shared between instances, this can sometimes make your .exe slightly larger than linking without /TSAWARE (Depends on compiler/linker flags used and linker version). No matter how the flag was set, at runtime you will escape the %windir% compatibility shim in Terminal Server scenarios.

    Now let me try ASLR one last time. With a recent MS linker if you link with both /FIXED and /DYNAMICBASE you get "LINK : fatal error LNK1295: '/FIXED' not compatible with '/DYNAMICBASE' specification; link without '/FIXED'". Older and/or 3rd-party linkers default to /FIXED for .exe files and might not support /DYNAMICBASE at all. This would be fine except that certain MS logo requirements say that IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE must be set and the way to fulfill this requirement is to edit the PE header after linking if your toolchain does not support said flag. Now we are in a situation where the linker did not produce a ASLR aware image but it still has the flag set claiming that we are aware. When running this .exe Windows will be unable to relocate it because it has no relocations section so it will effectively run without ASLR for this image yet most tools will claim that it supports ASLR. The best a static analysis tool can do is to issue a warning if there are no relocations. Process Explorer on the other hand could check because it knows the address where it actually loaded…

  15. anonymouscommenter says:

    I've built that /DYNAMICBASE no-relocs DLL before. The DLL will load anywhere. Your toolchain probably doesn't generate code that can tolerate this condition. I'd be amazed that Windows doesn't relocate that on encountering it for an EXE. If you do this with an EXE that doesn't understand and has even 1 address-of-function operation, it will crash.

  16. anonymouscommenter says:

    Gabe: Yes, people do write code that fails if ASLR is turned on.

    Source: blogs.msdn.com/…/45481.aspx

  17. anonymouscommenter says:

    @Gabe: It's usually a case of stripping relocs. I wrote a nastier case a long time ago. The program allocated all of its address space in one giant arena. If the system libraries were to move any lower there wouldn't be enough free space to actually allocate that much RAM. Thankfully this wasn't a Windows program so I could order that this case not happen. This was before 64 bit CPUs were consumer level so making this as a 64 bit program was not feasible.

  18. anonymouscommenter says:

    @Raymond: Whats_ with_ the_ odd_ naming_ convention_ in_ the_ code_?  Just_ wondering_…

  19. anonymouscommenter says:

    @Dave

    That's one of the popular C++ naming conventions [google://c++ naming conventions private variables].

    'Tis strange you didn't encounter it before.

  20. Medinoc says:

    This ASLR stuff reminded me, is it still the case that relocated DLLs can't be shared in memory? I vaguely remember reading something to that effect here…

  21. anonymouscommenter says:

    @Medinoc: ASLR is tied to the paging system in Vista and later.  What happens, at least in my experience up through Windows 7, is that each ASLR'd DLL is assigned a randomized load address by the kernel that applies for all processes.  If that address is available in a process, it's loaded there and has relocations applied by the kernel.  This relocated DLL is shared as that relocated image among all processes that load it at that location.  If the address is unavailable in a given process, ntdll.dll will re-relocate it to an arbitrary address, and this image will not be shared.

    Yes, Windows has two relocation engines: one in the kernel, and one in ntdll.dll.  You can tell which is in use for /DYNAMICBASE images by checking IMAGE_OPTIONAL_HEADER::ImageBase.  If the image has not had relocation patches applied, yet ImageBase in memory != ImageBase on disk, then the kernel did the relocation.  The kernel patches ImageBase to make it look like the image was not relocated.

  22. anonymouscommenter says:

    Joshua: If an image is able to be relocated, how would you write code that depends on whether the base was deterministic, random, or random with high entropy?

    What am I missing here?

  23. anonymouscommenter says:

    @Gabe: Because the image wasn't able to be relocated and somebody set the flag that says it is. Setting the flag does not actually enable it to be relocated.

    Of course you could manage to do something even stupider like depending on the fact it was always deterministic for EXEs and hard-coding something that really depends on the load address (like HINSTANCE).

  24. anonymouscommenter says:

    Joshua: I think you're answering the wrong question. What I'm asking is, given that a DLL can be relocated, why would it matter how randomly the rebasing address is determined?

  25. anonymouscommenter says:

    Maybe it managed to depend on being loaded lower than the system DLLs.

Comments are closed.

Skip to main content