Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add debug_details and load_config_timestamp to PE module. #1976

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
30 changes: 30 additions & 0 deletions docs/modules/pe.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1089,6 +1089,36 @@ Reference

*Example: pe.pdb_path == "D:\\workspace\\2018_R9_RelBld\\target\\checkout\\custprof\\Release\\custprof.pdb"*

.. c:type:: debug_details

.. versionadded:: 4.4.0

Array of structures containing information about the PE's debug information.

.. c:member:: type

Type of debug information

.. c:member:: timestamp

Timestamp in the debug entry.

.. c:member:: pdb_path

Path of the PDB file for this entry.

.. c:type:: number_of_debug_details

.. versionadded:: 4.4.0

Number of entries in the debug_details array.

.. c:type:: load_config_timestamp

.. versionadded:: 4.4.0

Timestamp pulled from the load configuration, if present.

.. c:function:: exports(function_name)

Function returning true if the PE exports *function_name* or
Expand Down
69 changes: 69 additions & 0 deletions libyara/include/yara/pe.h
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,75 @@ typedef struct _IMAGE_RESOURCE_DIRECTORY
WORD NumberOfIdEntries;
} IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY;

typedef struct _IMAGE_LOAD_CONFIG_DIRECTORY32
{
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD GlobalFlagsClear;
DWORD GlobalFlagsSet;
DWORD CriticalSectionDefaultTimeout;
DWORD DeCommitFreeBlockThreshold;
DWORD DeCommitTotalFreeThreshold;
DWORD LockPrefixTable;
DWORD MaximumAllocationSize;
DWORD VirtualMemoryThreshold;
DWORD ProcessAffinityMask;
DWORD ProcessHeapFlags;
WORD CSDVersion;
WORD Reserved;
DWORD EditList;
DWORD SecurityCookie;
DWORD SEHandlerTable;
DWORD SEHandlerCount;
DWORD GuardCFCheckFunctionPointer;
DWORD GuardCFDispatchFunctionPointer;
DWORD GuardCFFunctionTable;
DWORD GuardCFFunctionCount;
DWORD GuardFlags;
BYTE CodeIntegrity[12];
DWORD GuardAddressTakenIatEntryTable;
DWORD GuardAddressTakenIatEntryCount;
DWORD GuardLongJumpTargetTable;
DWORD GuardLongJumpTargetCount;
} IMAGE_LOAD_CONFIG_DIRECTORY32, *PIMAGE_LOAD_CONFIG_DIRECTORY32;

typedef struct _IMAGE_LOAD_CONFIG_DIRECTORY64
{
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD GlobalFlagsClear;
DWORD GlobalFlagsSet;
DWORD CriticalSectionDefaultTimeout;
ULONGLONG DeCommitFreeBlockThreshold;
ULONGLONG DeCommitTotalFreeThreshold;
ULONGLONG LockPrefixTable;
ULONGLONG MaximumAllocationSize;
ULONGLONG VirtualMemoryThreshold;
ULONGLONG ProcessAffinityMask;
DWORD ProcessHeapFlags;
WORD CSDVersion;
WORD Reserved;
ULONGLONG EditList;
ULONGLONG SecurityCookie;
ULONGLONG SEHandlerTable;
ULONGLONG SEHandlerCount;
ULONGLONG GuardCFCheckFunctionPointer;
ULONGLONG GuardCFDispatchFunctionPointer;
ULONGLONG GuardCFFunctionTable;
ULONGLONG GuardCFFunctionCount;
DWORD GuardFlags;
BYTE CodeIntegrity[12];
ULONGLONG GuardAddressTakenIatEntryTable;
ULONGLONG GuardAddressTakenIatEntryCount;
ULONGLONG GuardLongJumpTargetTable;
ULONGLONG GuardLongJumpTargetCount;
} IMAGE_LOAD_CONFIG_DIRECTORY64, *PIMAGE_LOAD_CONFIG_DIRECTORY64;


#define IMAGE_DEBUG_TYPE_FPO 3
#define IMAGE_DEBUG_TYPE_MISC 4
#define IMAGE_DEBUG_TYPE_EXCEPTION 5
Expand Down
111 changes: 106 additions & 5 deletions libyara/modules/pe/pe.c
Original file line number Diff line number Diff line change
Expand Up @@ -292,12 +292,75 @@ static void pe_parse_rich_signature(PE* pe, uint64_t base_address)
yr_free(version_data);
}

static void pe_parse_load_config_directory(PE* pe)
{
PIMAGE_DATA_DIRECTORY data_dir;
// Use the 32bit version of this structure as we are only interested in
// extracting the timestamp which is at the same offset in both the 32bit and
// 64bit structures.
PIMAGE_LOAD_CONFIG_DIRECTORY32 load_config;
int64_t offset;

data_dir = pe_get_directory_entry(pe, IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG);

if (data_dir == NULL)
return;

if (yr_le32toh(data_dir->Size) == 0)
return;

if (yr_le32toh(data_dir->VirtualAddress) == 0)
return;

// Dear Future WXS,
//
// Can't do a size check here because, and I quote
// https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#load-configuration-directory
//
// <quote>
// The data directory entry for a pre-reserved SEH load configuration
// structure must specify a particular size of the load configuration
// structure because the operating system loader always expects it to be a
// certain value. In that regard, the size is really only a version check. For
// compatibility with Windows XP and earlier versions of Windows, the size
// must be 64 for x86 images.
// </quote>
//
// However, some files declare a size that is 148 bytes but are storing a
// structure that is 192 bytes. An example of this is
// f36c7873c86331db0fadecd1e9f48839bd42cdd72acf051987aee5f1b097cbbd, which is
// a Microsoft generated 64bit PE that declares the "wrong" size in the load
// configuration data directory entry.
//
// Instead of doing it just check if the 32bit structure fits after we follow
// the RVA to offset calculation.
//
// Love,
// Past WXS
offset = pe_rva_to_offset(pe, yr_le32toh(data_dir->VirtualAddress));
if (offset < 0)
return;

load_config = (PIMAGE_LOAD_CONFIG_DIRECTORY32) (pe->data + offset);

if (!struct_fits_in_pe(pe, load_config, IMAGE_LOAD_CONFIG_DIRECTORY32))
return;

yr_set_integer(
yr_le32toh(load_config->TimeDateStamp),
pe->object,
"load_config_timestamp");
}

static void pe_parse_debug_directory(PE* pe)
{
PIMAGE_DATA_DIRECTORY data_dir;
PIMAGE_DEBUG_DIRECTORY debug_dir;
int64_t debug_dir_offset;
int i, dcount;
// Use pdb_done to determine if we have already parsed the first available PDB
// path.
int parsed_dirs = 0, pdb_done = 0;
size_t pdb_path_len;
char* pdb_path = NULL;

Expand Down Expand Up @@ -332,6 +395,22 @@ static void pe_parse_debug_directory(PE* pe)
if (!struct_fits_in_pe(pe, debug_dir, IMAGE_DEBUG_DIRECTORY))
break;

// Intentionally pulling out timestamps and types even if it isn't CODEVIEW
// as it is still useful to know.
yr_set_integer(
yr_le32toh(debug_dir->TimeDateStamp),
pe->object,
"debug_details[%i].timestamp",
i);

yr_set_integer(
yr_le32toh(debug_dir->Type), pe->object, "debug_details[%i].type", i);

// Increment parsed_dirs here because we have filled in part of the
// structure at this index in the array, even if we can only populate other
// information from CODEVIEW debug entries.
parsed_dirs++;

if (yr_le32toh(debug_dir->Type) != IMAGE_DEBUG_TYPE_CODEVIEW)
continue;

Expand Down Expand Up @@ -387,11 +466,20 @@ static void pe_parse_debug_directory(PE* pe)

if (pdb_path_len > 0 && pdb_path_len < MAX_PATH)
{
yr_set_sized_string(pdb_path, pdb_path_len, pe->object, "pdb_path");
break;
// Earlier versions of YARA only parsed the first debug entry with a PDB
// path. We have to maintain this for backwards compatability reasons.
if (!pdb_done)
{
yr_set_sized_string(pdb_path, pdb_path_len, pe->object, "pdb_path");
}
// We always parse all PDB paths for debug_details array.
yr_set_sized_string(
pdb_path, pdb_path_len, pe->object, "debug_details[%i].pdb_path", i);
}
}
}

yr_set_integer(parsed_dirs, pe->object, "number_of_debug_details");
}

// Return a pointer to the resource directory string or NULL.
Expand Down Expand Up @@ -1656,7 +1744,10 @@ static void pe_parse_exports(PE* pe)
ordinal_base + i, pe->object, "export_details[%i].ordinal", exp_sz);

yr_set_integer(
yr_le32toh(function_addrs[i]), pe->object, "export_details[%i].rva", exp_sz);
yr_le32toh(function_addrs[i]),
pe->object,
"export_details[%i].rva",
exp_sz);

// Don't check for a failure here since some packers make this an invalid
// value.
Expand Down Expand Up @@ -1758,8 +1849,8 @@ void _process_authenticode(
const Authenticode* authenticode = auth_array->signatures[i];

signature_valid |= authenticode->verify_flags == AUTHENTICODE_VFY_VALID
? true
: false;
? true
: false;

yr_set_integer(
signature_valid, pe->object, "signatures[%i].verified", *sig_count);
Expand Down Expand Up @@ -3795,6 +3886,15 @@ begin_declarations
declare_function("is_32bit", "", "i", is_32bit);
declare_function("is_64bit", "", "i", is_64bit);

declare_integer("number_of_debug_details");
begin_struct_array("debug_details")
declare_integer("type");
declare_integer("timestamp");
declare_string("pdb_path");
end_struct_array("debug_details");

declare_integer("load_config_timestamp");

declare_integer("number_of_imports");
declare_integer("number_of_imported_functions");
declare_integer("number_of_delayed_imports");
Expand Down Expand Up @@ -4345,6 +4445,7 @@ int module_load(
pe_parse_header(pe, block->base, context->flags);
pe_parse_rich_signature(pe, block->base);
pe_parse_debug_directory(pe);
pe_parse_load_config_directory(pe);

#if defined(HAVE_LIBCRYPTO) && !defined(BORINGSSL)
pe_parse_certificates(pe);
Expand Down
28 changes: 26 additions & 2 deletions tests/test-pe.c
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,26 @@ int main(int argc, char** argv)
}",
"tests/data/tiny");

// Be sure to check pdb_path (not in the struct) to make sure we maintain
// historical parsing behavior.
assert_true_rule_file(
"import \"pe\" \
rule test { \
condition: \
pe.number_of_debug_details == 3 and \
pe.debug_details[0].type == 2 and \
pe.debug_details[0].timestamp == 1827812126 and \
pe.debug_details[0].pdb_path == \"mtxex.pdb\" and \
pe.debug_details[1].type == 13 and \
pe.debug_details[1].timestamp == 1827812126 and \
not defined pe.debug_details[1].pdb_path and \
pe.debug_details[2].type == 16 and \
pe.debug_details[2].timestamp == 1827812126 and \
not defined pe.debug_details[2].pdb_path and \
pe.pdb_path == \"mtxex.pdb\" \
}",
"tests/data/mtxex.dll");

#if defined(HAVE_LIBCRYPTO) || defined(HAVE_WINCRYPT_H) || \
defined(HAVE_COMMONCRYPTO_COMMONCRYPTO_H)

Expand Down Expand Up @@ -475,8 +495,12 @@ int main(int argc, char** argv)
/*
* mtxex.dll is
* 23e72ce7e9cdbc80c0095484ebeb02f56b21e48fd67044e69e7a2ae76db631e5, which was
* taken from a Windows 10 install. The details of which are: export_timestamp
* = 1827812126 dll_name = "mtxex.dll" number_of_exports = 4 export_details
* taken from a Windows 10 install. The details of which are:
*
* export_timestamp = 1827812126
* dll_name = "mtxex.dll"
* number_of_exports = 4
* export_details
* [0]
* offset = 1072
* name = "DllGetClassObject"
Expand Down