<NME> pe.c <BEF> /* Copyright (c) 2014. The YARA Authors. All Rights Reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include <ctype.h> #include <stdio.h> #include <time.h> #include "../crypto.h" #if defined(HAVE_LIBCRYPTO) #include <authenticode-parser/authenticode.h> #include <openssl/evp.h> #endif #include <yara/dotnet.h> #include <yara/endian.h> #include <yara/mem.h> #include <yara/modules.h> #include <yara/pe.h> #include <yara/pe_utils.h> #include <yara/strutils.h> #include <yara/utils.h> #define MODULE_NAME pe #define IMPORT_STANDARD 1 #define IMPORT_DELAYED 2 #define IMPORT_ANY (~0) // http://msdn.microsoft.com/en-us/library/ms648009(v=vs.85).aspx #define RESOURCE_TYPE_CURSOR 1 #define RESOURCE_TYPE_BITMAP 2 #define RESOURCE_TYPE_ICON 3 #define RESOURCE_TYPE_MENU 4 #define RESOURCE_TYPE_DIALOG 5 #define RESOURCE_TYPE_STRING 6 #define RESOURCE_TYPE_FONTDIR 7 #define RESOURCE_TYPE_FONT 8 #define RESOURCE_TYPE_ACCELERATOR 9 #define RESOURCE_TYPE_RCDATA 10 #define RESOURCE_TYPE_MESSAGETABLE 11 #define RESOURCE_TYPE_GROUP_CURSOR \ 12 // MAKEINTRESOURCE((ULONG_PTR)(RT_CURSOR) + 11) #define RESOURCE_TYPE_GROUP_ICON \ 14 // MAKEINTRESOURCE((ULONG_PTR)(RT_ICON) + 11) #define RESOURCE_TYPE_VERSION 16 #define RESOURCE_TYPE_DLGINCLUDE 17 #define RESOURCE_TYPE_PLUGPLAY 19 #define RESOURCE_TYPE_VXD 20 #define RESOURCE_TYPE_ANICURSOR 21 #define RESOURCE_TYPE_ANIICON 22 #define RESOURCE_TYPE_HTML 23 #define RESOURCE_TYPE_MANIFEST 24 #define RESOURCE_CALLBACK_CONTINUE 0 #define RESOURCE_CALLBACK_ABORT 1 #define RESOURCE_ITERATOR_FINISHED 0 #define RESOURCE_ITERATOR_ABORTED 1 #define MAX_PE_IMPORTS 16384 #define MAX_PE_EXPORTS 8192 #define MAX_EXPORT_NAME_LENGTH 512 #define MAX_RESOURCES 65536 #define IS_RESOURCE_SUBDIRECTORY(entry) \ (yr_le32toh((entry)->OffsetToData) & 0x80000000) #define RESOURCE_OFFSET(entry) (yr_le32toh((entry)->OffsetToData) & 0x7FFFFFFF) typedef int (*RESOURCE_CALLBACK_FUNC)( PIMAGE_RESOURCE_DATA_ENTRY rsrc_data, int rsrc_type, int rsrc_id, int rsrc_language, const IMAGE_RESOURCE_DIR_STRING_U* type_string, const IMAGE_RESOURCE_DIR_STRING_U* name_string, const IMAGE_RESOURCE_DIR_STRING_U* lang_string, void* cb_data); static size_t available_space(PE* pe, void* pointer) { if ((uint8_t*) pointer < pe->data) return 0; if ((uint8_t*) pointer >= pe->data + pe->data_size) return 0; return pe->data + pe->data_size - (uint8_t*) pointer; } static int wide_string_fits_in_pe(PE* pe, char* data) { size_t i = 0; size_t space_left = available_space(pe, data); while (space_left >= 2) { if (data[i] == 0 && data[i + 1] == 0) return 1; space_left -= 2; i += 2; } return 0; } // Parse the rich signature. // http://www.ntcore.com/files/richsign.htm static void pe_parse_rich_signature(PE* pe, uint64_t base_address) { PIMAGE_DOS_HEADER mz_header; PRICH_SIGNATURE rich_signature = NULL; DWORD* rich_ptr = NULL; BYTE* raw_data = NULL; BYTE* clear_data = NULL; DWORD* p = NULL; uint32_t nthdr_offset = 0; uint32_t key = 0; size_t rich_len = 0; if (pe->data_size < sizeof(IMAGE_DOS_HEADER)) return; mz_header = (PIMAGE_DOS_HEADER) pe->data; if (yr_le16toh(mz_header->e_magic) != IMAGE_DOS_SIGNATURE) return; // To find the Rich marker we start at the NT header and work backwards, so // make sure we have at least enough data to get to the NT header. nthdr_offset = yr_le32toh(mz_header->e_lfanew); if (nthdr_offset > pe->data_size + sizeof(uint32_t) || nthdr_offset < 4) return; // Most files have the Rich header at offset 0x80, but that is not always // true. 582ce3eea9c97d5e89f7d83953a6d518b16770e635a19a456c0225449c6967a4 is // one sample which has a Rich header starting at offset 0x200. To properly // find the Rich header we need to start at the NT header and work backwards. p = (DWORD*) (pe->data + nthdr_offset - 4); while (p >= (DWORD*) (pe->data + sizeof(IMAGE_DOS_HEADER))) { if (yr_le32toh(*p) == RICH_RICH) { // The XOR key is the dword following the Rich value. We use this to find // DanS header only. key = *(p + 1); rich_ptr = p; --p; break; } // The NT header is 8 byte aligned so we can move back in 4 byte increments. --p; } // If we haven't found a key we can skip processing the rest. if (key == 0) return; // If we have found the key we need to now find the start (DanS). while (p >= (DWORD*) (pe->data + sizeof(IMAGE_DOS_HEADER))) { if (yr_le32toh((*(p) ^ key)) == RICH_DANS) { rich_signature = (PRICH_SIGNATURE) p; break; } --p; } if (rich_signature == NULL) return; // The three key values must all be equal and the first dword // XORs to "DanS". Then walk the buffer looking for "Rich" which marks the // end. Technically the XOR key should be right after "Rich" but it's not // important. if (yr_le32toh(rich_signature->key1) != yr_le32toh(rich_signature->key2) || yr_le32toh(rich_signature->key2) != yr_le32toh(rich_signature->key3) || (yr_le32toh(rich_signature->dans) ^ yr_le32toh(rich_signature->key1)) != RICH_DANS) { return; } // Multiple by 4 because we are counting in DWORDs. rich_len = (rich_ptr - (DWORD*) rich_signature) * 4; raw_data = (BYTE*) yr_malloc(rich_len); if (!raw_data) return; memcpy(raw_data, rich_signature, rich_len); yr_set_integer( base_address + ((uint8_t*) rich_signature - pe->data), pe->object, "rich_signature.offset"); yr_set_integer(rich_len, pe->object, "rich_signature.length"); yr_set_integer( yr_le32toh(rich_signature->key1), pe->object, "rich_signature.key"); clear_data = (BYTE*) yr_malloc(rich_len); if (!clear_data) { yr_free(raw_data); return; } // Copy the entire block here to be XORed. memcpy(clear_data, raw_data, rich_len); for (rich_ptr = (DWORD*) clear_data; rich_ptr < (DWORD*) (clear_data + rich_len); rich_ptr++) { *rich_ptr ^= rich_signature->key1; } yr_set_sized_string( (char*) raw_data, rich_len, pe->object, "rich_signature.raw_data"); yr_set_sized_string( (char*) clear_data, rich_len, pe->object, "rich_signature.clear_data"); yr_free(raw_data); yr_free(clear_data); } 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; size_t pdb_path_len; char* pdb_path = NULL; PIMAGE_DATA_DIRECTORY data_dir; PIMAGE_DEBUG_DIRECTORY debug_dir; int64_t debug_dir_offset; int64_t pcv_hdr_offset; int i, dcount; size_t pdb_path_len; char* pdb_path = NULL; return; if (yr_le32toh(data_dir->Size) % sizeof(IMAGE_DEBUG_DIRECTORY) != 0) return; if (yr_le32toh(data_dir->VirtualAddress) == 0) return; debug_dir_offset = pe_rva_to_offset(pe, yr_le32toh(data_dir->VirtualAddress)); if (debug_dir_offset < 0) return; dcount = yr_le32toh(data_dir->Size) / sizeof(IMAGE_DEBUG_DIRECTORY); for (i = 0; i < dcount; i++) { int64_t pcv_hdr_offset = 0; debug_dir = (PIMAGE_DEBUG_DIRECTORY) (pe->data + debug_dir_offset + i * sizeof(IMAGE_DEBUG_DIRECTORY)); for (i = 0; i < dcount; i++) { debug_dir = (PIMAGE_DEBUG_DIRECTORY)( pe->data + debug_dir_offset + i * sizeof(IMAGE_DEBUG_DIRECTORY)); // Sample: 0249e00b6d46bee5a17096559f18e671cd0ceee36373e8708f614a9a6c7c079e if (debug_dir->AddressOfRawData != 0) { if (yr_le32toh(debug_dir->Type) != IMAGE_DEBUG_TYPE_CODEVIEW) continue; if (yr_le32toh(debug_dir->AddressOfRawData) == 0) continue; pcv_hdr_offset = pe_rva_to_offset( pe, yr_le32toh(debug_dir->AddressOfRawData)); if (pcv_hdr_offset < 0) continue; PCV_HEADER cv_hdr = (PCV_HEADER)(pe->data + pcv_hdr_offset); if (yr_le32toh(cv_hdr->dwSignature) == CVINFO_PDB20_CVSIGNATURE) { PCV_INFO_PDB20 pdb20 = (PCV_INFO_PDB20) cv_hdr; if (struct_fits_in_pe(pe, pdb20, CV_INFO_PDB20)) pdb_path = (char*) (pdb20->PdbFileName); } else if (yr_le32toh(cv_hdr->dwSignature) == CVINFO_PDB70_CVSIGNATURE) { PCV_INFO_PDB70 pdb70 = (PCV_INFO_PDB70) cv_hdr; if (struct_fits_in_pe(pe, pdb70, CV_INFO_PDB70)) pdb_path = (char*) (pdb70->PdbFileName); } else if (yr_le32toh(cv_hdr->dwSignature) == CODEVIEW_SIGNATURE_MTOC) { PMTOC_ENTRY mtoc = (PMTOC_ENTRY) cv_hdr; if (struct_fits_in_pe(pe, mtoc, MTOC_ENTRY)) pdb_path = (char*) (mtoc->PdbFileName); } if (pdb_path != NULL) { pdb_path_len = strnlen( pdb_path, yr_min(available_space(pe, pdb_path), MAX_PATH)); if (pdb_path_len > 0 && pdb_path_len < MAX_PATH) { yr_set_sized_string(pdb_path, pdb_path_len, pe->object, "pdb_path"); break; } } } } // Return a pointer to the resource directory string or NULL. // The callback function will parse this and call yr_set_sized_string(). // The pointer is guaranteed to have enough space to contain the entire string. static const PIMAGE_RESOURCE_DIR_STRING_U parse_resource_name( PE* pe, const uint8_t* rsrc_data, PIMAGE_RESOURCE_DIRECTORY_ENTRY entry) { // If high bit is set it is an offset relative to rsrc_data, which contains // a resource directory string. if (yr_le32toh(entry->Name) & 0x80000000) { const PIMAGE_RESOURCE_DIR_STRING_U pNameString = (PIMAGE_RESOURCE_DIR_STRING_U) (rsrc_data + (yr_le32toh(entry->Name) & 0x7FFFFFFF)); // A resource directory string is 2 bytes for the length and then a variable // length Unicode string. Make sure we have at least 2 bytes. if (!fits_in_pe(pe, pNameString, 2)) return NULL; // Move past the length and make sure we have enough bytes for the string. if (!fits_in_pe( pe, pNameString, sizeof(uint16_t) + pNameString->Length * 2)) return NULL; return pNameString; } return NULL; } static int _pe_iterate_resources( PE* pe, PIMAGE_RESOURCE_DIRECTORY resource_dir, const uint8_t* rsrc_data, int rsrc_tree_level, int* type, int* id, int* language, const IMAGE_RESOURCE_DIR_STRING_U* type_string, const IMAGE_RESOURCE_DIR_STRING_U* name_string, const IMAGE_RESOURCE_DIR_STRING_U* lang_string, RESOURCE_CALLBACK_FUNC callback, void* callback_data) { int i, result = RESOURCE_ITERATOR_FINISHED; int total_entries; PIMAGE_RESOURCE_DIRECTORY_ENTRY entry; // A few sanity checks to avoid corrupt files if (yr_le32toh(resource_dir->Characteristics) != 0 || yr_le16toh(resource_dir->NumberOfNamedEntries) > 32768 || yr_le16toh(resource_dir->NumberOfIdEntries) > 32768) { return result; } total_entries = yr_le16toh(resource_dir->NumberOfNamedEntries) + yr_le16toh(resource_dir->NumberOfIdEntries); // The first directory entry is just after the resource directory, // by incrementing resource_dir we skip sizeof(resource_dir) bytes // and get a pointer to the end of the resource directory. entry = (PIMAGE_RESOURCE_DIRECTORY_ENTRY) (resource_dir + 1); for (i = 0; i < total_entries; i++) { if (!struct_fits_in_pe(pe, entry, IMAGE_RESOURCE_DIRECTORY_ENTRY)) { result = RESOURCE_ITERATOR_ABORTED; break; } switch (rsrc_tree_level) { case 0: *type = yr_le32toh(entry->Name); type_string = parse_resource_name(pe, rsrc_data, entry); break; case 1: *id = yr_le32toh(entry->Name); name_string = parse_resource_name(pe, rsrc_data, entry); break; case 2: *language = yr_le32toh(entry->Name); lang_string = parse_resource_name(pe, rsrc_data, entry); break; } if (IS_RESOURCE_SUBDIRECTORY(entry) && rsrc_tree_level < 2) { PIMAGE_RESOURCE_DIRECTORY directory = (PIMAGE_RESOURCE_DIRECTORY) (rsrc_data + RESOURCE_OFFSET(entry)); if (struct_fits_in_pe(pe, directory, IMAGE_RESOURCE_DIRECTORY)) { result = _pe_iterate_resources( pe, directory, rsrc_data, rsrc_tree_level + 1, type, id, language, type_string, name_string, lang_string, callback, callback_data); } else { result = RESOURCE_ITERATOR_ABORTED; } } else { PIMAGE_RESOURCE_DATA_ENTRY data_entry = (PIMAGE_RESOURCE_DATA_ENTRY) (rsrc_data + RESOURCE_OFFSET(entry)); if (struct_fits_in_pe(pe, data_entry, IMAGE_RESOURCE_DATA_ENTRY)) { if (callback( data_entry, *type, *id, *language, type_string, name_string, lang_string, callback_data) == RESOURCE_CALLBACK_ABORT) { result = RESOURCE_ITERATOR_ABORTED; } } else { result = RESOURCE_ITERATOR_ABORTED; } } if (result == RESOURCE_ITERATOR_ABORTED) break; entry++; } return result; } static int pe_iterate_resources( PE* pe, RESOURCE_CALLBACK_FUNC callback, void* callback_data) { int64_t offset; int type = -1; int id = -1; int language = -1; IMAGE_RESOURCE_DIR_STRING_U* type_string = NULL; IMAGE_RESOURCE_DIR_STRING_U* name_string = NULL; IMAGE_RESOURCE_DIR_STRING_U* lang_string = NULL; PIMAGE_DATA_DIRECTORY directory = pe_get_directory_entry( pe, IMAGE_DIRECTORY_ENTRY_RESOURCE); if (directory == NULL) return 0; if (yr_le32toh(directory->VirtualAddress) != 0) { PIMAGE_RESOURCE_DIRECTORY rsrc_dir; offset = pe_rva_to_offset(pe, yr_le32toh(directory->VirtualAddress)); if (offset < 0) return 0; rsrc_dir = (PIMAGE_RESOURCE_DIRECTORY) (pe->data + offset); if (struct_fits_in_pe(pe, rsrc_dir, IMAGE_RESOURCE_DIRECTORY)) { yr_set_integer( yr_le32toh(rsrc_dir->TimeDateStamp), pe->object, "resource_timestamp"); yr_set_integer( yr_le16toh(rsrc_dir->MajorVersion), pe->object, "resource_version.major"); yr_set_integer( yr_le16toh(rsrc_dir->MinorVersion), pe->object, "resource_version.minor"); _pe_iterate_resources( pe, rsrc_dir, pe->data + offset, 0, &type, &id, &language, type_string, name_string, lang_string, callback, callback_data); return 1; } } return 0; } // Align offset to a 32-bit boundary and add it to a pointer #define ADD_OFFSET(ptr, offset) \ (PVERSION_INFO)((uint8_t*) (ptr) + ((offset + 3) & ~3)) static void pe_parse_version_info(PIMAGE_RESOURCE_DATA_ENTRY rsrc_data, PE* pe) { PVERSION_INFO version_info; int64_t version_info_offset = pe_rva_to_offset( pe, yr_le32toh(rsrc_data->OffsetToData)); if (version_info_offset < 0) return; version_info = (PVERSION_INFO) (pe->data + version_info_offset); if (!struct_fits_in_pe(pe, version_info, VERSION_INFO)) return; if (!fits_in_pe(pe, version_info->Key, sizeof("VS_VERSION_INFO") * 2)) return; if (strcmp_w(version_info->Key, "VS_VERSION_INFO") != 0) return; version_info = ADD_OFFSET(version_info, sizeof(VERSION_INFO) + 86); while (fits_in_pe(pe, version_info->Key, sizeof("VarFileInfo") * 2) && strcmp_w(version_info->Key, "VarFileInfo") == 0 && yr_le16toh(version_info->Length) != 0) { version_info = ADD_OFFSET(version_info, yr_le16toh(version_info->Length)); } while (fits_in_pe(pe, version_info->Key, sizeof("StringFileInfo") * 2) && strcmp_w(version_info->Key, "StringFileInfo") == 0 && yr_le16toh(version_info->Length) != 0) { PVERSION_INFO string_table = ADD_OFFSET( version_info, sizeof(VERSION_INFO) + 30); version_info = ADD_OFFSET(version_info, yr_le16toh(version_info->Length)); while (struct_fits_in_pe(pe, string_table, VERSION_INFO) && wide_string_fits_in_pe(pe, string_table->Key) && yr_le16toh(string_table->Length) != 0 && string_table < version_info) { PVERSION_INFO string = ADD_OFFSET( string_table, sizeof(VERSION_INFO) + 2 * (strnlen_w(string_table->Key) + 1)); string_table = ADD_OFFSET(string_table, yr_le16toh(string_table->Length)); while (struct_fits_in_pe(pe, string, VERSION_INFO) && wide_string_fits_in_pe(pe, string->Key) && yr_le16toh(string->Length) != 0 && string < string_table) { char* string_value = (char*) ADD_OFFSET( string, sizeof(VERSION_INFO) + 2 * (strnlen_w(string->Key) + 1)); if (wide_string_fits_in_pe(pe, string_value)) { char key[64]; char value[256]; strlcpy_w(key, string->Key, sizeof(key)); strlcpy_w(value, string_value, sizeof(value)); // null terminator of string is not included in version value when // ValueLength is zero if (yr_le16toh(string->ValueLength) == 0) value[yr_le16toh(string->ValueLength)] = '\0'; yr_set_string(value, pe->object, "version_info[%s]", key); yr_set_string( key, pe->object, "version_info_list[%i].key", pe->version_infos); yr_set_string( value, pe->object, "version_info_list[%i].value", pe->version_infos); pe->version_infos += 1; } string = ADD_OFFSET(string, yr_le16toh(string->Length)); } } } } static void pe_set_resource_string_or_id( IMAGE_RESOURCE_DIR_STRING_U* rsrc_string, int rsrc_int, const char* string_description, const char* int_description, PE* pe) { if (rsrc_string) { // Multiply by 2 because it is a Unicode string. size_t length = rsrc_string->Length * 2; // Check if the whole string fits in the PE image. // If not, the name becomes UNDEFINED by default. if (fits_in_pe(pe, rsrc_string->NameString, length)) { yr_set_sized_string( (char*) rsrc_string->NameString, length, pe->object, string_description, pe->resources); } } else { yr_set_integer(rsrc_int, pe->object, int_description, pe->resources); } } static int pe_collect_resources( PIMAGE_RESOURCE_DATA_ENTRY rsrc_data, int rsrc_type, int rsrc_id, int rsrc_language, IMAGE_RESOURCE_DIR_STRING_U* type_string, IMAGE_RESOURCE_DIR_STRING_U* name_string, IMAGE_RESOURCE_DIR_STRING_U* lang_string, PE* pe) { // Don't collect too many resources. if (pe->resources > MAX_RESOURCES) return RESOURCE_CALLBACK_CONTINUE; yr_set_integer( yr_le32toh(rsrc_data->OffsetToData), pe->object, "resources[%i].rva", pe->resources); int64_t offset = pe_rva_to_offset(pe, yr_le32toh(rsrc_data->OffsetToData)); if (offset < 0) offset = YR_UNDEFINED; yr_set_integer(offset, pe->object, "resources[%i].offset", pe->resources); yr_set_integer( yr_le32toh(rsrc_data->Size), pe->object, "resources[%i].length", pe->resources); pe_set_resource_string_or_id( type_string, rsrc_type, "resources[%i].type_string", "resources[%i].type", pe); pe_set_resource_string_or_id( name_string, rsrc_id, "resources[%i].name_string", "resources[%i].id", pe); pe_set_resource_string_or_id( lang_string, rsrc_language, "resources[%i].language_string", "resources[%i].language", pe); // Resources we do extra parsing on if (rsrc_type == RESOURCE_TYPE_VERSION) pe_parse_version_info(rsrc_data, pe); pe->resources += 1; return RESOURCE_CALLBACK_CONTINUE; } static IMPORT_FUNCTION* pe_parse_import_descriptor( PE* pe, PIMAGE_IMPORT_DESCRIPTOR import_descriptor, char* dll_name, int* num_function_imports) { IMPORT_FUNCTION* head = NULL; IMPORT_FUNCTION* tail = NULL; int64_t offset = pe_rva_to_offset( pe, yr_le32toh(import_descriptor->OriginalFirstThunk)); // I've seen binaries where OriginalFirstThunk is zero. In this case // use FirstThunk. if (offset <= 0) offset = pe_rva_to_offset(pe, yr_le32toh(import_descriptor->FirstThunk)); if (offset < 0) return NULL; if (IS_64BITS_PE(pe)) { PIMAGE_THUNK_DATA64 thunks64 = (PIMAGE_THUNK_DATA64) (pe->data + offset); uint64_t func_idx = 0; while (struct_fits_in_pe(pe, thunks64, IMAGE_THUNK_DATA64) && yr_le64toh(thunks64->u1.Ordinal) != 0 && *num_function_imports < MAX_PE_IMPORTS) { char* name = NULL; uint16_t ordinal = 0; uint8_t has_ordinal = 0; uint64_t rva_address = 0; if (!(yr_le64toh(thunks64->u1.Ordinal) & IMAGE_ORDINAL_FLAG64)) { // If imported by name offset = pe_rva_to_offset(pe, yr_le64toh(thunks64->u1.Function)); if (offset >= 0) { PIMAGE_IMPORT_BY_NAME import = (PIMAGE_IMPORT_BY_NAME) (pe->data + offset); if (struct_fits_in_pe(pe, import, IMAGE_IMPORT_BY_NAME)) { name = (char*) yr_strndup( (char*) import->Name, yr_min(available_space(pe, import->Name), 512)); } } } else { // If imported by ordinal. Lookup the ordinal. name = ord_lookup(dll_name, yr_le64toh(thunks64->u1.Ordinal) & 0xFFFF); // Also store the ordinal. ordinal = yr_le64toh(thunks64->u1.Ordinal) & 0xFFFF; has_ordinal = 1; } rva_address = yr_le64toh(import_descriptor->FirstThunk + (sizeof(uint64_t) * func_idx)); if (name != NULL || has_ordinal == 1) { IMPORT_FUNCTION* imported_func = (IMPORT_FUNCTION*) yr_calloc( 1, sizeof(IMPORT_FUNCTION)); if (imported_func == NULL) { yr_free(name); continue; } imported_func->name = name; imported_func->ordinal = ordinal; imported_func->has_ordinal = has_ordinal; imported_func->rva = rva_address; imported_func->next = NULL; if (head == NULL) head = imported_func; if (tail != NULL) tail->next = imported_func; tail = imported_func; } (*num_function_imports)++; thunks64++; func_idx++; } } else { PIMAGE_THUNK_DATA32 thunks32 = (PIMAGE_THUNK_DATA32) (pe->data + offset); uint32_t func_idx = 0; while (struct_fits_in_pe(pe, thunks32, IMAGE_THUNK_DATA32) && yr_le32toh(thunks32->u1.Ordinal) != 0 && *num_function_imports < MAX_PE_IMPORTS) { char* name = NULL; uint16_t ordinal = 0; uint8_t has_ordinal = 0; uint32_t rva_address = 0; if (!(yr_le32toh(thunks32->u1.Ordinal) & IMAGE_ORDINAL_FLAG32)) { // If imported by name offset = pe_rva_to_offset(pe, yr_le32toh(thunks32->u1.Function)); if (offset >= 0) { PIMAGE_IMPORT_BY_NAME import = (PIMAGE_IMPORT_BY_NAME) (pe->data + offset); if (struct_fits_in_pe(pe, import, IMAGE_IMPORT_BY_NAME)) { name = (char*) yr_strndup( (char*) import->Name, yr_min(available_space(pe, import->Name), 512)); } } } else { // If imported by ordinal. Lookup the ordinal. name = ord_lookup(dll_name, yr_le32toh(thunks32->u1.Ordinal) & 0xFFFF); // Also store the ordinal. ordinal = yr_le32toh(thunks32->u1.Ordinal) & 0xFFFF; has_ordinal = 1; } rva_address = yr_le32toh(import_descriptor->FirstThunk + (sizeof(uint32_t) * func_idx)); if (name != NULL || has_ordinal == 1) { IMPORT_FUNCTION* imported_func = (IMPORT_FUNCTION*) yr_calloc( 1, sizeof(IMPORT_FUNCTION)); if (imported_func == NULL) { yr_free(name); continue; } imported_func->name = name; imported_func->ordinal = ordinal; imported_func->has_ordinal = has_ordinal; imported_func->rva = rva_address; imported_func->next = NULL; if (head == NULL) head = imported_func; if (tail != NULL) tail->next = imported_func; tail = imported_func; } (*num_function_imports)++; thunks32++; func_idx++; } } return head; } // // In Windows PE files, any character including 0x20 and above is allowed. // The only exceptions are characters that are invalid for file names in // Windows, which are "*<>?|. While they still can be present in the import // directory, such module can never be present in Windows, so we can treat them // as invalid. // // Explicit: The above also applies to slash, backslash (these form a relative // path in a subdirectory, which is allowed in the import directory) and colon // (which forms a file name with an Alternate Data Stream "test:file.dll" - also // allowed). // // Proof of concept: https://github.com/ladislav-zezula/ImportTest // // Samples // ------- // f561d60bff4342e529b2c793e216b73a72e6256f90ab24c3cc460646371130ca (imports // "test/file.dll") // b7f7b8a001769eb0f9c36cb27626b62cabdca9a716a222066028fcd206244b40 (imports // "test\file.dll") // 94cfb8223132da0a76f9dfbd35a29ab78e5806651758650292ab9c7baf2c0bc2 (imports // "test:file.dll") // eb2e2c443840276afe095fff05a3a24c00e610ac0e020233d6cd7a0b0b340fb1 (the // imported DLL) // static int pe_valid_dll_name(const char* dll_name, size_t n) { const unsigned char* c = (const unsigned char*) dll_name; size_t l = 0; while (l < n && *c != '\0') { if (*c < ' ' || *c == '\"' || *c == '*' || *c == '<' || *c == '>' || *c == '?' || *c == '|') { return false; } c++; l++; } return (l > 0 && l < n); } void pe_set_imports( PE* pe, IMPORTED_DLL* dll, const char* dll_name, const char* dll_number_of_functions, const char* fun_name, const char* fun_ordinal, const char* rva) { int dll_cnt = 0; for (; dll != NULL; dll = dll->next, dll_cnt++) { int fun_cnt = 0; for (IMPORT_FUNCTION* func = dll->functions; func != NULL; func = func->next, fun_cnt++) { yr_set_string(func->name, pe->object, fun_name, dll_cnt, fun_cnt); if (func->has_ordinal) yr_set_integer(func->ordinal, pe->object, fun_ordinal, dll_cnt, fun_cnt); else yr_set_integer(YR_UNDEFINED, pe->object, fun_ordinal, dll_cnt, fun_cnt); if (func->rva) yr_set_integer(func->rva, pe->object, rva, dll_cnt, fun_cnt); else yr_set_integer(YR_UNDEFINED, pe->object, rva, dll_cnt, fun_cnt); } yr_set_string(dll->name, pe->object, dll_name, dll_cnt); yr_set_integer(fun_cnt, pe->object, dll_number_of_functions, dll_cnt); } } // // Walk the imports and collect relevant information. It is used in the // "imports" function for comparison and in the "imphash" function for // calculation. // static IMPORTED_DLL* pe_parse_imports(PE* pe) { int64_t offset; int num_imports = 0; // Number of imported DLLs int num_function_imports = 0; // Total number of functions imported IMPORTED_DLL* head = NULL; IMPORTED_DLL* tail = NULL; PIMAGE_IMPORT_DESCRIPTOR imports; PIMAGE_DATA_DIRECTORY directory; // Default to 0 imports until we know there are any yr_set_integer(0, pe->object, "number_of_imports"); yr_set_integer(0, pe->object, "number_of_imported_functions"); directory = pe_get_directory_entry(pe, IMAGE_DIRECTORY_ENTRY_IMPORT); if (directory == NULL) return NULL; if (yr_le32toh(directory->VirtualAddress) == 0) return NULL; offset = pe_rva_to_offset(pe, yr_le32toh(directory->VirtualAddress)); if (offset < 0) return NULL; imports = (PIMAGE_IMPORT_DESCRIPTOR) (pe->data + offset); while (struct_fits_in_pe(pe, imports, IMAGE_IMPORT_DESCRIPTOR) && yr_le32toh(imports->Name) != 0 && num_imports < MAX_PE_IMPORTS) { int64_t offset = pe_rva_to_offset(pe, yr_le32toh(imports->Name)); if (offset >= 0) { IMPORTED_DLL* imported_dll; char* dll_name = (char*) (pe->data + offset); if (!pe_valid_dll_name(dll_name, pe->data_size - (size_t) offset)) { imports++; continue; } imported_dll = (IMPORTED_DLL*) yr_calloc(1, sizeof(IMPORTED_DLL)); if (imported_dll != NULL) { IMPORT_FUNCTION* functions = pe_parse_import_descriptor( pe, imports, dll_name, &num_function_imports); if (functions != NULL) { imported_dll->name = yr_strdup(dll_name); ; imported_dll->functions = functions; imported_dll->next = NULL; if (head == NULL) head = imported_dll; if (tail != NULL) tail->next = imported_dll; tail = imported_dll; } else { yr_free(imported_dll); } } } num_imports++; imports++; } yr_set_integer(num_imports, pe->object, "number_of_imports"); yr_set_integer(num_function_imports, pe->object, "number_of_imported_functions"); pe_set_imports( pe, head, "import_details[%i].library_name", "import_details[%i].number_of_functions", "import_details[%i].functions[%i].name", "import_details[%i].functions[%i].ordinal", "import_details[%i].functions[%i].rva"); return head; } // Delay-import descriptors made by MS Visual C++ 6.0 have old format // of delay import directory, where all entries are VAs (as opposite to RVAs // from newer MS compilers). We convert the delay-import directory entries to // RVAs by checking the lowest bit in the delay-import descriptor's Attributes // value uint64_t pe_normalize_delay_import_value( uint64_t image_base, uint64_t virtual_address) { // Ignore zero items if (virtual_address != 0) { // Sample: 0fc4cb0620f95bdd624f2c78eea4d2b59594244c6671cf249526adf2f2cb71ec // Contains artificially created delay import directory with incorrect // values: // // Attributes 0x00000000 <-- Old MS delay import // record, contains VAs NameRva 0x004010e6 // ModuleHandleRva 0x00000000 // DelayImportAddressTableRva 0x00001140 <-- WRONG! This is an RVA // DelayImportNameTableRva 0x004010c0 // BoundDelayImportTableRva 0x00000000 // ... if (virtual_address > image_base) { virtual_address = virtual_address - image_base; } } return virtual_address; } int pe_is_termination_delay_import_entry( PIMAGE_DELAYLOAD_DESCRIPTOR importDescriptor) { return ( importDescriptor->Attributes.AllAttributes == 0 && importDescriptor->DllNameRVA == 0 && importDescriptor->ModuleHandleRVA == 0 && importDescriptor->ImportAddressTableRVA == 0 && importDescriptor->ImportNameTableRVA == 0 && importDescriptor->BoundImportAddressTableRVA == 0 && importDescriptor->UnloadInformationTableRVA == 0 && importDescriptor->TimeDateStamp == 0); } char* pe_parse_delay_import_dll_name(PE* pe, uint64_t rva) { const int64_t offset = pe_rva_to_offset(pe, rva); if (offset < 0) return NULL; char* dll_name = (char*) (pe->data + offset); if (!pe_valid_dll_name(dll_name, pe->data_size - (size_t) offset)) return NULL; return yr_strdup(dll_name); } uint64_t pe_parse_delay_import_pointer( PE* pe, uint64_t pointerSize, uint64_t rva) { const int64_t offset = pe_rva_to_offset(pe, rva); const uint8_t* data = pe->data + offset; if (!fits_in_pe(pe, data, pointerSize)) return YR_UNDEFINED; if (IS_64BITS_PE(pe)) return yr_le64toh(*(uint64_t*) data); else return yr_le32toh(*(uint32_t*) data); } static void* pe_parse_delayed_imports(PE* pe) { int64_t offset; uint64_t num_imports = 0; // Number of imported DLLs uint64_t num_function_imports = 0; // Total number of functions imported uint64_t image_base = OptionalHeader(pe, ImageBase); uint64_t size_of_image = OptionalHeader(pe, SizeOfImage); uint64_t pointer_size = (IS_64BITS_PE(pe)) ? 8 : 4; uint64_t ordinal_mask = (IS_64BITS_PE(pe)) ? IMAGE_ORDINAL_FLAG64 : IMAGE_ORDINAL_FLAG32; IMPORTED_DLL* head_dll = NULL; IMPORTED_DLL* tail_dll = NULL; IMPORT_FUNCTION* head_fun = NULL; IMPORT_FUNCTION* tail_fun = NULL; PIMAGE_DELAYLOAD_DESCRIPTOR import_descriptor = NULL; PIMAGE_DATA_DIRECTORY directory = NULL; // Default to 0 imports until we know there are any yr_set_integer(0, pe->object, "number_of_delayed_imports"); yr_set_integer(0, pe->object, "number_of_delayed_imported_functions"); directory = pe_get_directory_entry(pe, IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT); if (directory == NULL) return NULL; if (yr_le32toh(directory->VirtualAddress) == 0) return NULL; offset = pe_rva_to_offset(pe, yr_le32toh(directory->VirtualAddress)); if (offset < 0) return NULL; import_descriptor = (PIMAGE_DELAYLOAD_DESCRIPTOR) (pe->data + offset); for (; struct_fits_in_pe(pe, import_descriptor, IMAGE_DELAYLOAD_DESCRIPTOR); import_descriptor++) { // Check for the termination entry if (pe_is_termination_delay_import_entry(import_descriptor)) break; DWORD Attributes = yr_le32toh(import_descriptor->Attributes.AllAttributes); DWORD DllNameRVA = yr_le32toh(import_descriptor->DllNameRVA); DWORD ModuleHandleRVA = yr_le32toh(import_descriptor->ModuleHandleRVA); DWORD ImportAddressTableRVA = yr_le32toh( import_descriptor->ImportAddressTableRVA); DWORD ImportNameTableRVA = yr_le32toh( import_descriptor->ImportNameTableRVA); DWORD BoundImportAddressTableRVA = yr_le32toh( import_descriptor->BoundImportAddressTableRVA); DWORD UnloadInformationTableRVA = yr_le32toh( import_descriptor->UnloadInformationTableRVA); // Valid delayed import entry starts either with 0 or 0x01. // We strict require one of the valid values here if (Attributes > 0x1) break; // Convert older (MS Visual C++ 6.0) delay-import descriptor to newer one. // These delay-import descriptors are distinguishable by lowest bit in // rec.Attributes to be zero. Sample: // 2775d97f8bdb3311ace960a42eee35dbec84b9d71a6abbacb26c14e83f5897e4 if (!IS_64BITS_PE(pe) && !Attributes) { DllNameRVA = (DWORD) pe_normalize_delay_import_value( image_base, DllNameRVA); ModuleHandleRVA = (DWORD) pe_normalize_delay_import_value( image_base, ModuleHandleRVA); ImportAddressTableRVA = (DWORD) pe_normalize_delay_import_value( image_base, ImportAddressTableRVA); ImportNameTableRVA = (DWORD) pe_normalize_delay_import_value( image_base, ImportNameTableRVA); BoundImportAddressTableRVA = (DWORD) pe_normalize_delay_import_value( image_base, BoundImportAddressTableRVA); UnloadInformationTableRVA = (DWORD) pe_normalize_delay_import_value( image_base, UnloadInformationTableRVA); } // Stop on blatantly invalid delay import entries (old PELIB behavior) if (ImportNameTableRVA >= size_of_image || ImportAddressTableRVA >= size_of_image || DllNameRVA < sizeof(IMAGE_DOS_HEADER) || ImportNameTableRVA < sizeof(IMAGE_DOS_HEADER)) break; char* dll_name = pe_parse_delay_import_dll_name(pe, DllNameRVA); if (dll_name == NULL) continue; IMPORTED_DLL* imported_dll = (IMPORTED_DLL*) yr_calloc( 1, sizeof(IMPORTED_DLL)); if (imported_dll == NULL) { yr_free(dll_name); continue; } imported_dll->name = dll_name; imported_dll->next = NULL; imported_dll->functions = NULL; head_fun = tail_fun = NULL; uint64_t name_rva = ImportNameTableRVA; uint64_t func_rva = ImportAddressTableRVA; for (;;) { uint64_t nameAddress = pe_parse_delay_import_pointer( pe, pointer_size, name_rva); uint64_t funcAddress = pe_parse_delay_import_pointer( pe, pointer_size, func_rva); // Value of YR_UNDEFINED means that value is outside of pe->data if (nameAddress == YR_UNDEFINED || funcAddress == YR_UNDEFINED) break; // Value of zero means that this is the end of the bound import name table if (nameAddress == 0 || funcAddress == 0) break; IMPORT_FUNCTION* imported_func = (IMPORT_FUNCTION*) yr_malloc( sizeof(IMPORT_FUNCTION)); if (imported_func == NULL) continue; imported_func->name = NULL; imported_func->has_ordinal = 0; imported_func->ordinal = 0; imported_func->rva = 0; imported_func->next = NULL; // Check name address. It could be ordinal, VA or RVA if (!(nameAddress & ordinal_mask)) { // Convert name address to RVA, if needed if (!Attributes) nameAddress = pe_normalize_delay_import_value( image_base, nameAddress); offset = pe_rva_to_offset(pe, nameAddress + sizeof(uint16_t)); imported_func->name = (char*) yr_strndup( (char*) (pe->data + offset), yr_min(available_space(pe, (char*) (pe->data + offset)), 512)); } else { // If imported by ordinal. Lookup the ordinal. imported_func->name = ord_lookup( dll_name, yr_le64toh(nameAddress) & 0xFFFF); // Also store the ordinal. imported_func->ordinal = yr_le64toh(nameAddress) & 0xFFFF; imported_func->has_ordinal = 1; } imported_func->rva = yr_le64toh(func_rva); num_function_imports++; name_rva += pointer_size; func_rva += pointer_size; if (head_fun == NULL) head_fun = imported_func; if (tail_fun != NULL) tail_fun->next = imported_func; tail_fun = imported_func; } num_imports++; imported_dll->functions = head_fun; if (head_dll == NULL) head_dll = imported_dll; if (tail_dll != NULL) tail_dll->next = imported_dll; tail_dll = imported_dll; } yr_set_integer(num_imports, pe->object, "number_of_delayed_imports"); yr_set_integer( num_function_imports, pe->object, "number_of_delayed_imported_functions"); pe_set_imports( pe, head_dll, "delayed_import_details[%i].library_name", "delayed_import_details[%i].number_of_functions", "delayed_import_details[%i].functions[%i].name", "delayed_import_details[%i].functions[%i].ordinal", "delayed_import_details[%i].functions[%i].rva"); return head_dll; } // // Walk the exports and collect relevant information. It is used in the // "exports" function for comparison. // static void pe_parse_exports(PE* pe) { PIMAGE_DATA_DIRECTORY directory; PIMAGE_EXPORT_DIRECTORY exports; int64_t offset; int64_t export_start; uint32_t i, j; uint32_t number_of_exports; uint32_t number_of_names; uint32_t ordinal_base; size_t export_size; size_t remaining; size_t name_len; uint32_t exp_sz = 0; DWORD* names = NULL; WORD* ordinals = NULL; DWORD* function_addrs = NULL; // If not a PE file, return YR_UNDEFINED if (pe == NULL) return; // Default to 0 exports until we know there are any yr_set_integer(0, pe->object, "number_of_exports"); directory = pe_get_directory_entry(pe, IMAGE_DIRECTORY_ENTRY_EXPORT); if (directory == NULL) return; if (yr_le32toh(directory->VirtualAddress) == 0) return; offset = pe_rva_to_offset(pe, yr_le32toh(directory->VirtualAddress)); if (offset < 0) return; export_start = offset; export_size = yr_le32toh(directory->Size); exports = (PIMAGE_EXPORT_DIRECTORY) (pe->data + offset); if (!struct_fits_in_pe(pe, exports, IMAGE_EXPORT_DIRECTORY)) return; number_of_exports = yr_min( yr_le32toh(exports->NumberOfFunctions), MAX_PE_EXPORTS); ordinal_base = yr_le32toh(exports->Base); yr_set_integer( yr_le32toh(exports->TimeDateStamp), pe->object, "export_timestamp"); offset = pe_rva_to_offset(pe, yr_le32toh(exports->Name)); if (offset > 0) { remaining = pe->data_size - (size_t) offset; name_len = strnlen((char*) (pe->data + offset), remaining); yr_set_sized_string( (char*) (pe->data + offset), name_len, pe->object, "dll_name"); } if (number_of_exports * sizeof(DWORD) > pe->data_size - offset) return; if (yr_le32toh(exports->NumberOfNames) > 0) { offset = pe_rva_to_offset(pe, yr_le32toh(exports->AddressOfNames)); if (offset < 0) return; if (yr_le32toh(exports->NumberOfNames) * sizeof(DWORD) > pe->data_size - offset) return; names = (DWORD*) (pe->data + offset); } offset = pe_rva_to_offset(pe, yr_le32toh(exports->AddressOfNameOrdinals)); if (offset < 0) return; ordinals = (WORD*) (pe->data + offset); if (available_space(pe, ordinals) < sizeof(WORD) * number_of_exports) return; offset = pe_rva_to_offset(pe, yr_le32toh(exports->AddressOfFunctions)); if (offset < 0) return; function_addrs = (DWORD*) (pe->data + offset); if (available_space(pe, function_addrs) < sizeof(DWORD) * number_of_exports) return; number_of_names = yr_min( yr_le32toh(yr_le32toh(exports->NumberOfNames)), number_of_exports); // Mapping out the exports is a bit janky. We start with the export address // array. The index from that array plus the ordinal base is the ordinal for // that export. To find the name we walk the ordinal array looking for a value // that matches our index. If one exists we look up the corresponding RVA from // the names array and follow it to get the name. If one does not exist then // the export has no name. // // Ordinal base: 5 // 0 1 2 // Address array: [ 0x00000011 | 0x00000022 | 0x00000033 ] // 0 1 2 // Ordinal array: [ 0x0000 | 0x0002 | 0x0001 ] // 0 1 // Names array: [ 0x00000044 | 0x00000055 ] // // The function at RVA 0x00000011 (index 0) has ordinal 5 (base + index). The // index can be found in position 0 in the ordinal array. Using 0 to index // into the name array gives us an RVA (0x00000044) which we can follow to get // the name. // // The function at RVA 0x00000022 (index 1) has ordinal 6 (base + index). The // index can be found in position 2 in the ordinal array. 2 is out of bounds // for the names array so this function is exported without a name. // // The function at RVA 0x00000033 (index 2) has ordinal 7 (base + index). The // index can be found in position 1 in the ordinal array. Using 1 to index // into the name array gives us an RVA (0x00000055) which we can follow to get // the name. // // If the RVA from the address array is within the export directory it is a // forwarder RVA and points to a NULL terminated ASCII string. for (i = 0; i < number_of_exports; i++) { yr_set_integer( ordinal_base + i, pe->object, "export_details[%i].ordinal", exp_sz); // Don't check for a failure here since some packers make this an invalid // value. offset = pe_rva_to_offset(pe, yr_le32toh(function_addrs[i])); if (offset > export_start && offset < export_start + export_size) { remaining = pe->data_size - (size_t) offset; name_len = strnlen((char*) (pe->data + offset), remaining); yr_set_sized_string( (char*) (pe->data + offset), yr_min(name_len, MAX_EXPORT_NAME_LENGTH), pe->object, "export_details[%i].forward_name", exp_sz); } else { yr_set_integer(offset, pe->object, "export_details[%i].offset", exp_sz); } if (names != NULL) { for (j = 0; j < number_of_exports; j++) { if (yr_le16toh(ordinals[j]) == i && j < number_of_names) { offset = pe_rva_to_offset(pe, yr_le32toh(names[j])); if (offset > 0) { remaining = pe->data_size - (size_t) offset; name_len = strnlen((char*) (pe->data + offset), remaining); yr_set_sized_string( (char*) (pe->data + offset), yr_min(name_len, MAX_EXPORT_NAME_LENGTH), pe->object, "export_details[%i].name", exp_sz); } break; } } } exp_sz++; } yr_set_integer(exp_sz, pe->object, "number_of_exports"); } // BoringSSL (https://boringssl.googlesource.com/boringssl/) doesn't support // some features used in pe_parse_certificates, if you are using BoringSSL // instead of OpenSSL you should define BORINGSSL for YARA to compile properly, // but you won't have signature-related features in the PE module. #if defined(HAVE_LIBCRYPTO) && !defined(BORINGSSL) #define write_certificate(cert, pe, fmt, ...) \ do \ { \ char thumbprint_ascii[YR_SHA1_LEN * 2 + 1]; \ for (int j = 0; j < cert->sha1.len; ++j) \ sprintf(thumbprint_ascii + (j * 2), "%02x", cert->sha1.data[j]); \ \ yr_set_string( \ (char*) thumbprint_ascii, pe->object, fmt ".thumbprint", __VA_ARGS__); \ \ yr_set_string(cert->issuer, pe->object, fmt ".issuer", __VA_ARGS__); \ yr_set_string(cert->subject, pe->object, fmt ".subject", __VA_ARGS__); \ /* Versions are zero based, so add one. */ \ yr_set_integer(cert->version + 1, pe->object, fmt ".version", __VA_ARGS__); \ yr_set_string(cert->sig_alg, pe->object, fmt ".algorithm", __VA_ARGS__); \ yr_set_string( \ cert->sig_alg_oid, pe->object, fmt ".algorithm_oid", __VA_ARGS__); \ yr_set_string(cert->serial, pe->object, fmt ".serial", __VA_ARGS__); \ yr_set_integer(cert->not_before, pe->object, fmt ".not_before", __VA_ARGS__); \ yr_set_integer(cert->not_after, pe->object, fmt ".not_after", __VA_ARGS__); \ } while (0) void _process_authenticode( PE* pe, AuthenticodeArray* auth_array, int* sig_count) { if (!auth_array || !auth_array->count) return; /* If any signature will be valid -> file is correctly signed */ bool signature_valid = false; for (size_t i = 0; i < auth_array->count; ++i) { const Authenticode* authenticode = auth_array->signatures[i]; signature_valid = authenticode->verify_flags == AUTHENTICODE_VFY_VALID ? true : false; yr_set_integer( signature_valid, pe->object, "signatures[%i].verified", *sig_count); yr_set_string( authenticode->digest_alg, pe->object, "signatures[%i].digest_alg", *sig_count); if (authenticode->digest.data) { char* digest_ascii = yr_malloc(authenticode->digest.len * 2 + 1); for (int j = 0; j < authenticode->digest.len; ++j) sprintf(digest_ascii + (j * 2), "%02x", authenticode->digest.data[j]); yr_set_string(digest_ascii, pe->object, "signatures[%i].digest", *sig_count); yr_free(digest_ascii); } if (authenticode->file_digest.data) { char* digest_ascii = yr_malloc(authenticode->file_digest.len * 2 + 1); for (int j = 0; j < authenticode->file_digest.len; ++j) sprintf( digest_ascii + (j * 2), "%02x", authenticode->file_digest.data[j]); yr_set_string( digest_ascii, pe->object, "signatures[%i].file_digest", *sig_count); yr_free(digest_ascii); } if (authenticode->certs) { yr_set_integer( authenticode->certs->count, pe->object, "signatures[%i].number_of_certificates", *sig_count); for (int k = 0; k < authenticode->certs->count; ++k) { write_certificate( authenticode->certs->certs[k], pe, "signatures[%i].certificates[%i]", *sig_count, k); } } const Signer* signer = authenticode->signer; if (signer) { /* For compatibility with previous YARA rules, write information * about signing certificate in the same way */ if (signer->chain && signer->chain->count >= 1) { const Certificate* sign_cert = signer->chain->certs[0]; write_certificate(sign_cert, pe, "signatures[%i]", *sig_count); } yr_set_string( signer->program_name, pe->object, "signatures[%i].signer_info.program_name", *sig_count); yr_set_string( signer->digest_alg, pe->object, "signatures[%i].signer_info.digest_alg", *sig_count); if (signer->digest.data) { char* digest_ascii = yr_malloc(signer->digest.len * 2 + 1); for (int j = 0; j < signer->digest.len; ++j) sprintf(digest_ascii + (j * 2), "%02x", signer->digest.data[j]); yr_set_string( digest_ascii, pe->object, "signatures[%i].signer_info.digest", *sig_count); yr_free(digest_ascii); } if (signer->chain) { yr_set_integer( signer->chain->count, pe->object, "signatures[%i].signer_info.length_of_chain", *sig_count); for (int k = 0; k < signer->chain->count; ++k) { write_certificate( signer->chain->certs[k], pe, "signatures[%i].signer_info.chain[%i]", *sig_count, k); } } } if (authenticode->countersigs) { yr_set_integer( authenticode->countersigs->count, pe->object, "signatures[%i].number_of_countersignatures", *sig_count); for (int j = 0; j < authenticode->countersigs->count; ++j) { const Countersignature* counter = authenticode->countersigs->counters[j]; yr_set_integer( counter->verify_flags == COUNTERSIGNATURE_VFY_VALID, pe->object, "signatures[%i].countersignatures[%i].verified", *sig_count, j); yr_set_string( counter->digest_alg, pe->object, "signatures[%i].countersignatures[%i].digest_alg", *sig_count, j); yr_set_integer( counter->sign_time, pe->object, "signatures[%i].countersignatures[%i].sign_time", *sig_count, j); if (counter->digest.data) { char* digest_ascii = yr_malloc(counter->digest.len * 2 + 1); for (int j = 0; j < counter->digest.len; ++j) sprintf(digest_ascii + (j * 2), "%02x", counter->digest.data[j]); yr_set_string( digest_ascii, pe->object, "signatures[%i].countersignatures[%i].digest", *sig_count, j); yr_free(digest_ascii); } if (counter->chain) { yr_set_integer( counter->chain->count, pe->object, "signatures[%i].countersignatures[%i].length_of_chain", *sig_count, j); for (int k = 0; k < counter->chain->count; ++k) { write_certificate( counter->chain->certs[k], pe, "signatures[%i].countersignatures[%i].chain[%i]", *sig_count, j, k); } } } } (*sig_count)++; } yr_set_integer(signature_valid, pe->object, "is_signed"); } static void pe_parse_certificates(PE* pe) { int counter = 0; PIMAGE_DATA_DIRECTORY directory = pe_get_directory_entry( pe, IMAGE_DIRECTORY_ENTRY_SECURITY); if (directory == NULL) return; // Default to 0 signatures until we know otherwise. yr_set_integer(0, pe->object, "number_of_signatures"); // directory->VirtualAddress is a file offset. Don't call pe_rva_to_offset(). if (yr_le32toh(directory->VirtualAddress) == 0 || yr_le32toh(directory->VirtualAddress) > pe->data_size || yr_le32toh(directory->Size) > pe->data_size || yr_le32toh(directory->VirtualAddress) + yr_le32toh(directory->Size) > pe->data_size) { return; } AuthenticodeArray* auth_array = parse_authenticode(pe->data, pe->data_size); _process_authenticode(pe, auth_array, &counter); authenticode_array_free(auth_array); yr_set_integer(counter, pe->object, "number_of_signatures"); } #endif // defined(HAVE_LIBCRYPTO) const char* pe_get_section_full_name( PE* pe, const char* section_name, uint64_t section_name_length, uint64_t* section_full_name_length) { // section_name is an 8-byte, null-padded UTF-8 encoded string. If the string // is exactly 8 characters long, there is no terminating null. For longer // names, this field contains a slash (/) that is followed by an ASCII // representation of a decimal number that is an offset into the string table. // Sample: 2e9c671b8a0411f2b397544b368c44d7f095eb395779de0ad1ac946914dfa34c // Check if any param is NULL if (pe == NULL || section_name == NULL || section_full_name_length == NULL) return NULL; // Set length to zero *section_full_name_length = 0; // Offset and number of records in coff table uint64_t coff_offset = yr_le32toh( pe->header->FileHeader.PointerToSymbolTable); uint64_t coff_number = yr_le32toh(pe->header->FileHeader.NumberOfSymbols); // If section name start with '/' and file contain coff table then section // name is stored in string table if (coff_offset == 0 || section_name[0] != '/') { *section_full_name_length = section_name_length; return section_name; } // Calculate offset of string table (String table is immediately after coff // table) uint64_t string_offset = coff_offset + coff_number * sizeof(IMAGE_SYMBOL); uint64_t string_index = 0; // Calculate string index/offset in string table for (int i = 1; i < IMAGE_SIZEOF_SHORT_NAME && isdigit(section_name[i]); i++) string_index = (string_index * 10) + (section_name[i] - '0'); // Calculate string pointer const char* string = (char*) (pe->data + string_offset + string_index); // Check string for (uint64_t len = 0; fits_in_pe(pe, string, len + 1); len++) { // Valid string if (string[len] == 0) { *section_full_name_length = len; return string; } // string contain unprintable character if (!isprint(string[len])) return NULL; } // String do not fit into pe file return NULL; } static void pe_parse_header(PE* pe, uint64_t base_address, int flags) { PIMAGE_SECTION_HEADER section; PIMAGE_DATA_DIRECTORY data_dir; char section_name[IMAGE_SIZEOF_SHORT_NAME + 1]; int sect_name_length, scount, ddcount; uint64_t highest_sec_siz = 0; uint64_t highest_sec_ofs = 0; uint64_t section_end; uint64_t last_section_end; yr_set_integer(1, pe->object, "is_pe"); yr_set_integer( yr_le16toh(pe->header->FileHeader.Machine), pe->object, "machine"); yr_set_integer( yr_le16toh(pe->header->FileHeader.NumberOfSections), pe->object, "number_of_sections"); yr_set_integer( yr_le32toh(pe->header->FileHeader.TimeDateStamp), pe->object, "timestamp"); yr_set_integer( yr_le32toh(pe->header->FileHeader.PointerToSymbolTable), pe->object, "pointer_to_symbol_table"); yr_set_integer( yr_le32toh(pe->header->FileHeader.NumberOfSymbols), pe->object, "number_of_symbols"); yr_set_integer( yr_le32toh(pe->header->FileHeader.SizeOfOptionalHeader), pe->object, "size_of_optional_header"); yr_set_integer( yr_le16toh(pe->header->FileHeader.Characteristics), pe->object, "characteristics"); yr_set_integer( flags & SCAN_FLAGS_PROCESS_MEMORY ? base_address + yr_le32toh(OptionalHeader(pe, AddressOfEntryPoint)) : pe_rva_to_offset( pe, yr_le32toh(OptionalHeader(pe, AddressOfEntryPoint))), pe->object, "entry_point"); yr_set_integer( yr_le32toh(OptionalHeader(pe, AddressOfEntryPoint)), pe->object, "entry_point_raw"); yr_set_integer( IS_64BITS_PE(pe) ? yr_le64toh(OptionalHeader(pe, ImageBase)) : yr_le32toh(OptionalHeader(pe, ImageBase)), pe->object, "image_base"); yr_set_integer( yr_le32toh(OptionalHeader(pe, NumberOfRvaAndSizes)), pe->object, "number_of_rva_and_sizes"); yr_set_integer( yr_le32toh(OptionalHeader(pe, Magic)), pe->object, "opthdr_magic"); yr_set_integer( OptionalHeader(pe, MajorLinkerVersion), pe->object, "linker_version.major"); yr_set_integer( OptionalHeader(pe, MinorLinkerVersion), pe->object, "linker_version.minor"); yr_set_integer( yr_le32toh(OptionalHeader(pe, SizeOfCode)), pe->object, "size_of_code"); yr_set_integer( yr_le32toh(OptionalHeader(pe, SizeOfInitializedData)), pe->object, "size_of_initialized_data"); yr_set_integer( yr_le32toh(OptionalHeader(pe, SizeOfUninitializedData)), pe->object, "size_of_uninitialized_data"); yr_set_integer( yr_le32toh(OptionalHeader(pe, BaseOfCode)), pe->object, "base_of_code"); if (!IS_64BITS_PE(pe)) { yr_set_integer( yr_le32toh(pe->header->OptionalHeader.BaseOfData), pe->object, "base_of_data"); } yr_set_integer( yr_le32toh(OptionalHeader(pe, SectionAlignment)), pe->object, "section_alignment"); yr_set_integer( yr_le32toh(OptionalHeader(pe, FileAlignment)), pe->object, "file_alignment"); yr_set_integer( yr_le16toh(OptionalHeader(pe, MajorOperatingSystemVersion)), pe->object, "os_version.major"); yr_set_integer( yr_le16toh(OptionalHeader(pe, MinorOperatingSystemVersion)), pe->object, "os_version.minor"); yr_set_integer( yr_le16toh(OptionalHeader(pe, MajorImageVersion)), pe->object, "image_version.major"); yr_set_integer( yr_le16toh(OptionalHeader(pe, MinorImageVersion)), pe->object, "image_version.minor"); yr_set_integer( yr_le16toh(OptionalHeader(pe, MajorSubsystemVersion)), pe->object, "subsystem_version.major"); yr_set_integer( yr_le16toh(OptionalHeader(pe, MinorSubsystemVersion)), pe->object, "subsystem_version.minor"); yr_set_integer( yr_le32toh(OptionalHeader(pe, Win32VersionValue)), pe->object, "win32_version_value"); yr_set_integer( yr_le32toh(OptionalHeader(pe, SizeOfImage)), pe->object, "size_of_image"); yr_set_integer( yr_le32toh(OptionalHeader(pe, SizeOfHeaders)), pe->object, "size_of_headers"); yr_set_integer(yr_le32toh(OptionalHeader(pe, CheckSum)), pe->object, "checksum"); yr_set_integer( yr_le16toh(OptionalHeader(pe, Subsystem)), pe->object, "subsystem"); yr_set_integer( OptionalHeader(pe, DllCharacteristics), pe->object, "dll_characteristics"); yr_set_integer( IS_64BITS_PE(pe) ? yr_le64toh(OptionalHeader(pe, SizeOfStackReserve)) : yr_le32toh(OptionalHeader(pe, SizeOfStackReserve)), pe->object, "size_of_stack_reserve"); yr_set_integer( IS_64BITS_PE(pe) ? yr_le64toh(OptionalHeader(pe, SizeOfStackCommit)) : yr_le32toh(OptionalHeader(pe, SizeOfStackCommit)), pe->object, "size_of_stack_commit"); yr_set_integer( IS_64BITS_PE(pe) ? yr_le64toh(OptionalHeader(pe, SizeOfHeapReserve)) : yr_le32toh(OptionalHeader(pe, SizeOfHeapReserve)), pe->object, "size_of_heap_reserve"); yr_set_integer( IS_64BITS_PE(pe) ? yr_le64toh(OptionalHeader(pe, SizeOfHeapCommit)) : yr_le32toh(OptionalHeader(pe, SizeOfHeapCommit)), pe->object, "size_of_heap_commit"); yr_set_integer( yr_le32toh(OptionalHeader(pe, LoaderFlags)), pe->object, "loader_flags"); data_dir = IS_64BITS_PE(pe) ? pe->header64->OptionalHeader.DataDirectory : pe->header->OptionalHeader.DataDirectory; ddcount = yr_le16toh(OptionalHeader(pe, NumberOfRvaAndSizes)); ddcount = yr_min(ddcount, IMAGE_NUMBEROF_DIRECTORY_ENTRIES); for (int i = 0; i < ddcount; i++) { if (!struct_fits_in_pe(pe, data_dir, IMAGE_DATA_DIRECTORY)) break; yr_set_integer( yr_le32toh(data_dir->VirtualAddress), pe->object, "data_directories[%i].virtual_address", i); yr_set_integer( yr_le32toh(data_dir->Size), pe->object, "data_directories[%i].size", i); data_dir++; } pe_iterate_resources( pe, (RESOURCE_CALLBACK_FUNC) pe_collect_resources, (void*) pe); yr_set_integer(pe->resources, pe->object, "number_of_resources"); yr_set_integer(pe->version_infos, pe->object, "number_of_version_infos"); section = IMAGE_FIRST_SECTION(pe->header); scount = yr_min( yr_le16toh(pe->header->FileHeader.NumberOfSections), MAX_PE_SECTIONS); for (int i = 0; i < scount; i++) { if (!struct_fits_in_pe(pe, section, IMAGE_SECTION_HEADER)) break; memcpy(section_name, section->Name, IMAGE_SIZEOF_SHORT_NAME); section_name[IMAGE_SIZEOF_SHORT_NAME] = '\0'; // Basically do rstrip('\0'), find the rightmost non-null character. // Samples like // 0043812838495a45449a0ac61a81b9c16eddca1ad249fb4f7fdb1c4505e9bb34 contain // sections with additional characters after the first null. for (sect_name_length = IMAGE_SIZEOF_SHORT_NAME - 1; sect_name_length >= 0; --sect_name_length) { if (section_name[sect_name_length] != '\0') break; } uint64_t sect_full_name_length = 0; const char* full_section_name = pe_get_section_full_name( pe, section_name, sect_name_length + 1, &sect_full_name_length); yr_set_sized_string( (char*) section_name, sect_name_length + 1, pe->object, "sections[%i].name", i); yr_set_sized_string( full_section_name, sect_full_name_length, pe->object, "sections[%i].full_name", i); yr_set_integer( yr_le32toh(section->Characteristics), pe->object, "sections[%i].characteristics", i); yr_set_integer( yr_le32toh(section->SizeOfRawData), pe->object, "sections[%i].raw_data_size", i); yr_set_integer( yr_le32toh(section->PointerToRawData), pe->object, "sections[%i].raw_data_offset", i); yr_set_integer( yr_le32toh(section->VirtualAddress), pe->object, "sections[%i].virtual_address", i); yr_set_integer( yr_le32toh(section->Misc.VirtualSize), pe->object, "sections[%i].virtual_size", i); yr_set_integer( yr_le32toh(section->PointerToRelocations), pe->object, "sections[%i].pointer_to_relocations", i); yr_set_integer( yr_le32toh(section->PointerToLinenumbers), pe->object, "sections[%i].pointer_to_line_numbers", i); yr_set_integer( yr_le32toh(section->NumberOfRelocations), pe->object, "sections[%i].number_of_relocations", i); yr_set_integer( yr_le32toh(section->NumberOfLinenumbers), pe->object, "sections[%i].number_of_line_numbers", i); // This will catch the section with the highest raw offset to help checking // if overlay data is present. If two sections have the same raw pointer // but different raw sizes the largest one is used. An example of this case // is file: cf62bf1815a93e68e6c5189f689286b66c4088b9507cf3ecf835e4ac3f9ededa section_end = yr_le32toh(section->PointerToRawData) + yr_le32toh(section->SizeOfRawData); if (section_end > highest_sec_ofs + highest_sec_siz) { highest_sec_ofs = yr_le32toh(section->PointerToRawData); highest_sec_siz = yr_le32toh(section->SizeOfRawData); } section++; } // An overlay is data appended to a PE file. Its location is at // RawData + RawOffset of the last section on the physical file last_section_end = highest_sec_siz + highest_sec_ofs; // For PE files that have overlaid data overlay.offset contains the offset // within the file where the overlay starts and overlay.size contains the // size. If the PE file doesn't have an overlay both fields are 0, if the // file is not a PE file (or is a malformed PE) both fields are YR_UNDEFINED. if (last_section_end && (pe->data_size > last_section_end)) { yr_set_integer(last_section_end, pe->object, "overlay.offset"); yr_set_integer(pe->data_size - last_section_end, pe->object, "overlay.size"); } else { yr_set_integer(0, pe->object, "overlay.offset"); yr_set_integer(0, pe->object, "overlay.size"); } } // // Given a posix timestamp argument, make sure not_before <= arg <= not_after // define_function(valid_on) { int64_t timestamp; int64_t not_before; int64_t not_after; if (yr_is_undefined(yr_parent(), "not_before") || yr_is_undefined(yr_parent(), "not_after")) { return_integer(YR_UNDEFINED); } timestamp = integer_argument(1); not_before = yr_get_integer(yr_parent(), "not_before"); not_after = yr_get_integer(yr_parent(), "not_after"); return_integer(timestamp >= not_before && timestamp <= not_after); } define_function(section_index_addr) { YR_OBJECT* module = yr_module(); YR_SCAN_CONTEXT* context = yr_scan_context(); int64_t offset; int64_t size; int64_t addr = integer_argument(1); int64_t n = yr_get_integer(module, "number_of_sections"); if (yr_is_undefined(module, "number_of_sections")) return_integer(YR_UNDEFINED); for (int i = 0; i < yr_min(n, MAX_PE_SECTIONS); i++) { if (context->flags & SCAN_FLAGS_PROCESS_MEMORY) { offset = yr_get_integer(module, "sections[%i].virtual_address", i); size = yr_get_integer(module, "sections[%i].virtual_size", i); } else { offset = yr_get_integer(module, "sections[%i].raw_data_offset", i); size = yr_get_integer(module, "sections[%i].raw_data_size", i); } if (addr >= offset && addr < offset + size) return_integer(i); } return_integer(YR_UNDEFINED); } define_function(section_index_name) { YR_OBJECT* module = yr_module(); char* name = string_argument(1); int64_t n = yr_get_integer(module, "number_of_sections"); if (yr_is_undefined(module, "number_of_sections")) return_integer(YR_UNDEFINED); for (int i = 0; i < yr_min(n, MAX_PE_SECTIONS); i++) { SIZED_STRING* sect = yr_get_string(module, "sections[%i].name", i); if (sect != NULL && strcmp(name, sect->c_string) == 0) return_integer(i); } return_integer(YR_UNDEFINED); } define_function(exports) { SIZED_STRING* search_name = sized_string_argument(1); SIZED_STRING* function_name = NULL; YR_OBJECT* module = yr_module(); PE* pe = (PE*) module->data; // If not a PE, return YR_UNDEFINED. if (pe == NULL) return_integer(YR_UNDEFINED); // If PE, but no exported functions, return false. int n = (int) yr_get_integer(module, "number_of_exports"); if (n == 0) return_integer(0); for (int i = 0; i < n; i++) { function_name = yr_get_string(module, "export_details[%i].name", i); if (function_name == NULL) continue; if (ss_icompare(function_name, search_name) == 0) return_integer(1); } return_integer(0); } define_function(exports_regexp) { RE* regex = regexp_argument(1); SIZED_STRING* function_name = NULL; YR_OBJECT* module = yr_module(); PE* pe = (PE*) module->data; // If not a PE, return YR_UNDEFINED. if (pe == NULL) return_integer(YR_UNDEFINED); // If PE, but no exported functions, return false. int n = (int) yr_get_integer(module, "number_of_exports"); if (n == 0) return_integer(0); for (int i = 0; i < n; i++) { function_name = yr_get_string(module, "export_details[%i].name", i); if (function_name == NULL) continue; if (yr_re_match(yr_scan_context(), regex, function_name->c_string) != -1) return_integer(1); } return_integer(0); } define_function(exports_ordinal) { int64_t ordinal = integer_argument(1); YR_OBJECT* module = yr_module(); PE* pe = (PE*) module->data; // If not a PE, return YR_UNDEFINED. if (pe == NULL) return_integer(YR_UNDEFINED); // If PE, but no exported functions, return false. int n = (int) yr_get_integer(module, "number_of_exports"); if (n == 0) return_integer(0); if (ordinal == 0 || ordinal > n) return_integer(0); for (int i = 0; i < n; i++) { int64_t exported_ordinal = yr_object_get_integer( module, "export_details[%i].ordinal", i); if (exported_ordinal == ordinal) return_integer(1); } return_integer(0); } define_function(exports_index_name) { SIZED_STRING* search_name = sized_string_argument(1); SIZED_STRING* function_name = NULL; YR_OBJECT* module = yr_module(); PE* pe = (PE*) module->data; // If not a PE, return YR_UNDEFINED. if (pe == NULL) return_integer(YR_UNDEFINED); // If PE, but no exported functions, return false. int n = (int) yr_get_integer(module, "number_of_exports"); if (n == 0) return_integer(YR_UNDEFINED); for (int i = 0; i < n; i++) { function_name = yr_get_string(module, "export_details[%i].name", i); if (function_name == NULL) continue; if (ss_icompare(function_name, search_name) == 0) return_integer(i); } return_integer(YR_UNDEFINED); } define_function(exports_index_ordinal) { int64_t ordinal = integer_argument(1); YR_OBJECT* module = yr_module(); PE* pe = (PE*) module->data; // If not a PE, return YR_UNDEFINED. if (pe == NULL) return_integer(YR_UNDEFINED); // If PE, but no exported functions, return false. int n = (int) yr_get_integer(module, "number_of_exports"); if (n == 0) return_integer(YR_UNDEFINED); if (ordinal == 0 || ordinal > n) return_integer(YR_UNDEFINED); for (int i = 0; i < n; i++) { int64_t exported_ordinal = yr_object_get_integer( module, "export_details[%i].ordinal", i); if (exported_ordinal == ordinal) return_integer(i); } return_integer(YR_UNDEFINED); } define_function(exports_index_regex) { RE* regex = regexp_argument(1); SIZED_STRING* function_name = NULL; YR_OBJECT* module = yr_module(); PE* pe = (PE*) module->data; // If not a PE, return YR_UNDEFINED. if (pe == NULL) return_integer(YR_UNDEFINED); // If PE, but no exported functions, return false. int n = (int) yr_get_integer(module, "number_of_exports"); if (n == 0) return_integer(YR_UNDEFINED); for (int i = 0; i < n; i++) { function_name = yr_get_string(module, "export_details[%i].name", i); if (function_name == NULL) continue; if (yr_re_match(yr_scan_context(), regex, function_name->c_string) != -1) { return_integer(i); } } return_integer(YR_UNDEFINED); } #if defined(HAVE_LIBCRYPTO) || defined(HAVE_WINCRYPT_H) || \ defined(HAVE_COMMONCRYPTO_COMMONCRYPTO_H) // // Generate an import hash: // https://www.mandiant.com/blog/tracking-malware-import-hashing/ // It is important to make duplicates of the strings as we don't want // to alter the contents of the parsed import structures. // define_function(imphash) { YR_OBJECT* module = yr_module(); IMPORTED_DLL* dll; yr_md5_ctx ctx; unsigned char digest[YR_MD5_LEN]; char* digest_ascii; size_t i; bool first = true; PE* pe = (PE*) module->data; // If not a PE, return YR_UNDEFINED. if (!pe) return_string(YR_UNDEFINED); // Lookup in cache first. digest_ascii = (char*) yr_hash_table_lookup(pe->hash_table, "imphash", NULL); if (digest_ascii != NULL) return_string(digest_ascii); yr_md5_init(&ctx); dll = pe->imported_dlls; while (dll) { IMPORT_FUNCTION* func; size_t dll_name_len; char* dll_name; // If extension is 'ocx', 'sys' or 'dll', chop it. char* ext = strstr(dll->name, "."); if (ext && (strncasecmp(ext, ".ocx", 4) == 0 || strncasecmp(ext, ".sys", 4) == 0 || strncasecmp(ext, ".dll", 4) == 0)) { dll_name_len = (ext - dll->name); } else { dll_name_len = strlen(dll->name); } // Allocate a new string to hold the dll name. dll_name = (char*) yr_malloc(dll_name_len + 1); if (!dll_name) return ERROR_INSUFFICIENT_MEMORY; strlcpy(dll_name, dll->name, dll_name_len + 1); func = dll->functions; while (func) { char* final_name; size_t final_name_len = dll_name_len + strlen(func->name) + 1; if (!first) final_name_len++; // Additional byte to accommodate the extra comma final_name = (char*) yr_malloc(final_name_len + 1); if (final_name == NULL) break; sprintf(final_name, first ? "%s.%s" : ",%s.%s", dll_name, func->name); // Lowercase the whole thing. for (i = 0; i < final_name_len; i++) final_name[i] = tolower(final_name[i]); yr_md5_update(&ctx, final_name, final_name_len); yr_free(final_name); func = func->next; first = false; } yr_free(dll_name); dll = dll->next; } yr_md5_final(digest, &ctx); digest_ascii = (char*) yr_malloc(YR_MD5_LEN * 2 + 1); if (digest_ascii == NULL) return ERROR_INSUFFICIENT_MEMORY; // Transform the binary digest to ascii for (i = 0; i < YR_MD5_LEN; i++) { sprintf(digest_ascii + (i * 2), "%02x", digest[i]); } digest_ascii[YR_MD5_LEN * 2] = '\0'; yr_hash_table_add(pe->hash_table, "imphash", NULL, digest_ascii); return_string(digest_ascii); } #endif // defined(HAVE_LIBCRYPTO) || defined(HAVE_WINCRYPT_H) int64_t pe_imports_dll(IMPORTED_DLL* dll, char* dll_name) { if (dll == NULL) return 0; int64_t result = 0; for (; dll != NULL; dll = dll->next) { if (strcasecmp(dll->name, dll_name) == 0) { IMPORT_FUNCTION* fun = dll->functions; for (; fun != NULL; fun = fun->next) { result++; } } } return result; } int64_t pe_imports(IMPORTED_DLL* dll, char* dll_name, char* fun_name) { if (dll == NULL) return 0; for (; dll != NULL; dll = dll->next) { if (strcasecmp(dll->name, dll_name) == 0) { IMPORT_FUNCTION* fun = dll->functions; for (; fun != NULL; fun = fun->next) { if (strcasecmp(fun->name, fun_name) == 0) return 1; } } } return 0; } int64_t pe_imports_regexp( YR_SCAN_CONTEXT* context, IMPORTED_DLL* dll, RE* dll_name, RE* fun_name) { if (dll == NULL) return 0; int64_t result = 0; for (; dll != NULL; dll = dll->next) { if (yr_re_match(context, dll_name, dll->name) > 0) { IMPORT_FUNCTION* fun = dll->functions; for (; fun != NULL; fun = fun->next) { if (yr_re_match(context, fun_name, fun->name) > 0) result++; } } } return result; } int64_t pe_imports_ordinal(IMPORTED_DLL* dll, char* dll_name, uint64_t ordinal) { if (dll == NULL) return 0; for (; dll != NULL; dll = dll->next) { if (strcasecmp(dll->name, dll_name) == 0) { IMPORT_FUNCTION* fun = dll->functions; for (; fun != NULL; fun = fun->next) { if (fun->has_ordinal && fun->ordinal == ordinal) return 1; } } } return 0; } define_function(imports_standard) { char* dll_name = string_argument(1); char* function_name = string_argument(2); YR_OBJECT* module = yr_module(); PE* pe = (PE*) module->data; if (!pe) return_integer(YR_UNDEFINED); return_integer(pe_imports(pe->imported_dlls, dll_name, function_name)); } define_function(imports) { int64_t flags = integer_argument(1); char* dll_name = string_argument(2); char* function_name = string_argument(3); YR_OBJECT* module = yr_module(); PE* pe = (PE*) module->data; if (!pe) return_integer(YR_UNDEFINED); if (flags & IMPORT_STANDARD && pe_imports(pe->imported_dlls, dll_name, function_name)) { return_integer(1); } if (flags & IMPORT_DELAYED && pe_imports(pe->delay_imported_dlls, dll_name, function_name)) { return_integer(1); } return_integer(0); } define_function(imports_standard_ordinal) { char* dll_name = string_argument(1); int64_t ordinal = integer_argument(2); YR_OBJECT* module = yr_module(); PE* pe = (PE*) module->data; if (!pe) return_integer(YR_UNDEFINED); return_integer(pe_imports_ordinal(pe->imported_dlls, dll_name, ordinal)) } define_function(imports_ordinal) { int64_t flags = integer_argument(1); char* dll_name = string_argument(2); int64_t ordinal = integer_argument(3); YR_OBJECT* module = yr_module(); PE* pe = (PE*) module->data; if (!pe) return_integer(YR_UNDEFINED); if (flags & IMPORT_STANDARD && pe_imports_ordinal(pe->imported_dlls, dll_name, ordinal)) { return_integer(1); } if (flags & IMPORT_DELAYED && pe_imports_ordinal(pe->delay_imported_dlls, dll_name, ordinal)) { return_integer(1); } return_integer(0); } define_function(imports_standard_regex) { RE* dll_name = regexp_argument(1); RE* function_name = regexp_argument(2); YR_OBJECT* module = yr_module(); PE* pe = (PE*) module->data; if (!pe) return_integer(YR_UNDEFINED); return_integer(pe_imports_regexp( yr_scan_context(), pe->imported_dlls, dll_name, function_name)) } define_function(imports_regex) { int64_t flags = integer_argument(1); RE* dll_name = regexp_argument(2); RE* function_name = regexp_argument(3); YR_OBJECT* module = yr_module(); PE* pe = (PE*) module->data; if (!pe) return_integer(YR_UNDEFINED); int64_t result = 0; if (flags & IMPORT_STANDARD) result += pe_imports_regexp( yr_scan_context(), pe->imported_dlls, dll_name, function_name); if (flags & IMPORT_DELAYED) result += pe_imports_regexp( yr_scan_context(), pe->delay_imported_dlls, dll_name, function_name); return_integer(result); } define_function(imports_standard_dll) { char* dll_name = string_argument(1); YR_OBJECT* module = yr_module(); PE* pe = (PE*) module->data; if (!pe) return_integer(YR_UNDEFINED); return_integer(pe_imports_dll(pe->imported_dlls, dll_name)); } define_function(imports_dll) { int64_t flags = integer_argument(1); char* dll_name = string_argument(2); YR_OBJECT* module = yr_module(); PE* pe = (PE*) module->data; if (!pe) return_integer(YR_UNDEFINED); int64_t result = 0; if (flags & IMPORT_STANDARD) result += pe_imports_dll(pe->imported_dlls, dll_name); if (flags & IMPORT_DELAYED) result += pe_imports_dll(pe->delay_imported_dlls, dll_name); return_integer(result); } define_function(locale) { YR_OBJECT* module = yr_module(); PE* pe = (PE*) module->data; uint64_t locale = integer_argument(1); if (yr_is_undefined(module, "number_of_resources")) return_integer(YR_UNDEFINED); // If not a PE file, return YR_UNDEFINED if (pe == NULL) return_integer(YR_UNDEFINED); int n = (int) yr_get_integer(module, "number_of_resources"); for (int i = 0; i < n; i++) { uint64_t rsrc_language = yr_get_integer(module, "resources[%i].language", i); if ((rsrc_language & 0xFFFF) == locale) return_integer(1); } return_integer(0); } define_function(language) { YR_OBJECT* module = yr_module(); PE* pe = (PE*) module->data; uint64_t language = integer_argument(1); if (yr_is_undefined(module, "number_of_resources")) return_integer(YR_UNDEFINED); // If not a PE file, return YR_UNDEFINED if (pe == NULL) return_integer(YR_UNDEFINED); int n = (int) yr_get_integer(module, "number_of_resources"); for (int i = 0; i < n; i++) { uint64_t rsrc_language = yr_get_integer(module, "resources[%i].language", i); if ((rsrc_language & 0xFF) == language) return_integer(1); } return_integer(0); } define_function(is_dll) { int64_t characteristics; YR_OBJECT* module = yr_module(); if (yr_is_undefined(module, "characteristics")) return_integer(YR_UNDEFINED); characteristics = yr_get_integer(module, "characteristics"); return_integer(characteristics & IMAGE_FILE_DLL); } define_function(is_32bit) { YR_OBJECT* module = yr_module(); PE* pe = (PE*) module->data; if (pe == NULL) return_integer(YR_UNDEFINED); return_integer(IS_64BITS_PE(pe) ? 0 : 1); } define_function(is_64bit) { YR_OBJECT* module = yr_module(); PE* pe = (PE*) module->data; if (pe == NULL) return_integer(YR_UNDEFINED); return_integer(IS_64BITS_PE(pe) ? 1 : 0); } // _rich_version // // Returns the number of rich signatures that match the specified version and // toolid numbers. // static uint64_t _rich_version( YR_OBJECT* module, uint64_t version, uint64_t toolid) { int64_t rich_length; int64_t rich_count; PRICH_SIGNATURE clear_rich_signature; SIZED_STRING* rich_string; uint64_t result = 0; // Check if the required fields are set if (yr_is_undefined(module, "rich_signature.length")) return YR_UNDEFINED; rich_length = yr_get_integer(module, "rich_signature.length"); rich_string = yr_get_string(module, "rich_signature.clear_data"); // If the clear_data was not set, return YR_UNDEFINED if (rich_string == NULL) return YR_UNDEFINED; if (version == YR_UNDEFINED && toolid == YR_UNDEFINED) return false; clear_rich_signature = (PRICH_SIGNATURE) rich_string->c_string; // Loop over the versions in the rich signature rich_count = (rich_length - sizeof(RICH_SIGNATURE)) / sizeof(RICH_VERSION_INFO); for (int i = 0; i < rich_count; i++) { DWORD id_version = yr_le32toh(clear_rich_signature->versions[i].id_version); int match_version = (version == RICH_VERSION_VERSION(id_version)); int match_toolid = (toolid == RICH_VERSION_ID(id_version)); if ((version == YR_UNDEFINED || match_version) && (toolid == YR_UNDEFINED || match_toolid)) { result += yr_le32toh(clear_rich_signature->versions[i].times); } } return result; } define_function(rich_version) { return_integer(_rich_version(yr_module(), integer_argument(1), YR_UNDEFINED)); } define_function(rich_version_toolid) { return_integer( _rich_version(yr_module(), integer_argument(1), integer_argument(2))); } define_function(rich_toolid) { return_integer(_rich_version(yr_module(), YR_UNDEFINED, integer_argument(1))); } define_function(rich_toolid_version) { return_integer( _rich_version(yr_module(), integer_argument(2), integer_argument(1))); } define_function(calculate_checksum) { YR_OBJECT* module = yr_module(); PE* pe = (PE*) module->data; uint64_t csum = 0; size_t csum_offset; if (pe == NULL) return_integer(YR_UNDEFINED); csum_offset = ((uint8_t*) &(pe->header->OptionalHeader) + offsetof(IMAGE_OPTIONAL_HEADER32, CheckSum)) - pe->data; for (size_t i = 0; i <= pe->data_size / 4; i++) { // Treat the CheckSum field as 0 -- the offset is the same for // PE32 and PE64. if (4 * i == csum_offset) continue; if (4 * i + 4 <= pe->data_size) { csum += ((uint64_t) pe->data[4 * i] + ((uint64_t) pe->data[4 * i + 1] << 8) + ((uint64_t) pe->data[4 * i + 2] << 16) + ((uint64_t) pe->data[4 * i + 3] << 24)); } else { for (size_t j = 0; j < pe->data_size % 4; j++) csum += (uint64_t) pe->data[4 * i + j] << (8 * j); } if (csum > 0xffffffff) csum = (csum & 0xffffffff) + (csum >> 32); } csum = (csum & 0xffff) + (csum >> 16); csum += (csum >> 16); csum &= 0xffff; csum += pe->data_size; return_integer(csum); } define_function(rva_to_offset) { YR_OBJECT* module = yr_module(); PE* pe = (PE*) module->data; uint64_t rva; int64_t offset; if (pe == NULL) return_integer(YR_UNDEFINED); rva
{'addition_count': 14, 'commit_subject': 'Issue https://github.com/VirusTotal/yara/issues/1505 (#1506)', 'deletion_count': 7, 'file_extension': '.c', 'lang': 'c', 'license': 'bsd-3-clause', 'repo_name': 'VirusTotal/yara'}