In the latest Downclimb, I mentioned that you should hunt for software that is not compiled with best practices with regard to security in the same way you should hunt for malware. This came up due to an exploitable vuln in Counter Strike and other games which would have been more difficult to exploit had best practices been used.
Specifically, you should look for software that is not compiled with DEP or ASLR which are exploit mitigation features that come by default when you compile code with a modern compiler. This is easy to look for, and if it is missing, it likely indicates other problems with that software. If software does not have those settings then you should either get rid of it, work with the vendors to improve their software, or take this information into account when deciding between vendors. For software vendors, you should ensure checking for these features is part of your release process: Lack of DEP and ASLR in any executable is a failed build.
I created Serene to make these checks easy. However, in order to make it easier to hunt, you may want YARA sigs for this. This post shows how to write these YARA signatures, along with the final signatures.
Parsing executables for DEP and ASLR support
Both DEP and ASLR are enabled via bits in the PE header that tell the OS to enable those features. Using Kaitai Struct, we can parse a sample executable, such as Google Chrome (from C:\Program Files (x86)\Google\Chrome\Application\chrome.exe on a Windows system). On the left hand side choose the format kaitai.io
->formats
->executable
->microsoft_pe.ksy
then drag and drop Chrome. Then in the “object tree” window, look at the value for optionalHdr
->windows
->dllCharacteristics
which should show 0xC160
.
Thank you @kaitai_io for Kaitai Struct!
This value encodes the relevant features by enabling/disabling each bit as described by Microsoft in the MSDN here. The setting IMAGE_DLLCHARACTERISTICS_NX_COMPAT
at 0x0100
determines whether DEP is enabled for an executable. The setting IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE
at 0x0040
determines if ASLR is enabled.
This encoding is made more clearly in my old IceBuddha project. Unfortunately, I no longer maintain that project, but it should work for any PE files.
The value 0xC160
in binary is 1100000101100000
. The values 0x0100
and 0x0040
for DEP and ASLR are 0000000100000000
and 0000000001000000
so applying some simple boolean ANDs we get:
1100000101100000 &
0000000100000000
=
0000000100000000 // Value is non-zero, so DEP is enabled
and
1100000101100000 &
0000000001000000
=
0000000001000000 // Value is non-zero, so ASLR is enabled
You can see that the final values are non-zero, and thus DEP and ASLR are enabled.
Writing the YARA sig
YARA has a module for PE files, called PE, which makes things much easier to avoid having to do some of the offset math. Using this, checking if DEP is on is as simple as:
pe.dll_characteristics & pe.NX_COMPAT == 0
However, to reduce false positives, we’ll do some filtering:
- We only want to scan PE files as indicated by an “MZ” header (pretty standard).
- We want to make sure there is actually code in the file. Some applications use DLL’s that only contain data, called MUI files, and whether or not those have security features isn’t important.
- We’ll want to ignore driver (ie. .sys) files, since these concepts don’t really apply to kernel code.
Next, DEP support only matters for the main executable (the .exe file) and not the DLL’s, so when we check for DEP, we can ignore DLL’s. Also, all 64-bit processes have DEP enabled, so we only want to check 32-bit executables.
With that, here is the final rule set, with an additional check to look for 32-bit executables, since ASLR doesn’t do much for 32-bit executables.
YARA signature to check for DEP and ASLR
import "pe"
private rule IsPE {
condition:
// If it is a PE file (MZ header at offset 0,
// and PE signature at offset stored in header at 0x3C)
uint16(0) == 0x5A4D
and uint32(uint32(0x3C)) == 0x00004550
// Also ensure the file contains executable code
// We do this by checking SizeOfCode.
// If non-zero, it means there is executable code in the file.
// To get there we need to get to the start of the IMAGE_OPTIONAL_HEADER
// which is at: e_lfanew + sizeof(IMAGE_NT_HEADER.magic) + sizeof(IMAGE_FILE_HEADER)
// then this value starts at offset 4 in the structure.
and uint32(uint32(0x3C) + 4 + 20 + 4) != 0
// Also ignore driver (ie. .sys) files
and pe.characteristics & pe.SYSTEM == 0
}
rule Not_64_bit {
condition:
// Check machine type for whether this code is 32-bit or 64-bit
IsPE and pe.machine == pe.MACHINE_I386
}
rule DEP_missing
{
meta:
description = "DEP is not enabled"
condition:
IsPE and pe.dll_characteristics & pe.NX_COMPAT == 0
// All 64-bit executables have DEP, so only check 32-bit
and pe.machine == pe.MACHINE_I386
// Also ensure this is not a DLL
and pe.characteristics & pe.DLL == 0
}
rule ASLR_missing
{
meta:
description = "ASLR is not enabled"
condition:
IsPE and pe.dll_characteristics & pe.DYNAMIC_BASE == 0
}