Skip to content

Commit

Permalink
Add debug_infos array to pe module.
Browse files Browse the repository at this point in the history
Fun fact: debug information is actually an array of structures. Historically,
YARA has stopped parsing after finding the first entry with a PDB path (with
some other restrictions around the type of debug entry this is). However, each
entry can have different information (including pdb paths), so let's add an
array of debug_infos structures which contain timestamp, type and pdb path.

Just in testing I discovered legit binaries that have different PDB paths in
them, which is actually kind of interesting.
  • Loading branch information
wxsBSD committed Sep 28, 2023
1 parent d399826 commit c1afe65
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 7 deletions.
53 changes: 48 additions & 5 deletions libyara/modules/pe/pe.c
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,9 @@ static void pe_parse_debug_directory(PE* pe)
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 +335,27 @@ static void pe_parse_debug_directory(PE* pe)
if (!struct_fits_in_pe(pe, debug_dir, IMAGE_DEBUG_DIRECTORY))
break;

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

// Intentionally pulling out timestamps even if it isn't CODEVIEW as it is
// still useful to know.
yr_set_integer(
yr_le32toh(debug_dir->Type),
pe->object,
"debug_infos[%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 +411,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_infos array.
yr_set_sized_string(
pdb_path, pdb_path_len, pe->object, "debug_infos[%i].pdb_path", i);
}
}
}

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

// Return a pointer to the resource directory string or NULL.
Expand Down Expand Up @@ -1656,7 +1689,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 +1794,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 +3831,13 @@ begin_declarations
declare_function("is_32bit", "", "i", is_32bit);
declare_function("is_64bit", "", "i", is_64bit);

declare_integer("number_of_debug_infos");
begin_struct_array("debug_infos")
declare_integer("type");
declare_integer("timestamp");
declare_string("pdb_path");
end_struct_array("debug_infos");

declare_integer("number_of_imports");
declare_integer("number_of_imported_functions");
declare_integer("number_of_delayed_imports");
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_infos == 3 and \
pe.debug_infos[0].type == 2 and \
pe.debug_infos[0].timestamp == 1827812126 and \
pe.debug_infos[0].pdb_path == \"mtxex.pdb\" and \
pe.debug_infos[1].type == 13 and \
pe.debug_infos[1].timestamp == 1827812126 and \
not defined pe.debug_infos[1].pdb_path and \
pe.debug_infos[2].type == 16 and \
pe.debug_infos[2].timestamp == 1827812126 and \
not defined pe.debug_infos[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

0 comments on commit c1afe65

Please sign in to comment.