/* Chrysalide - Outil d'analyse de fichiers binaires * pe.c - support du format Portable Executable * * Copyright (C) 2010-2024 Cyrille Bagard * * This file is part of Chrysalide. * * Chrysalide is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * Chrysalide is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Chrysalide. If not, see . */ #include "format.h" #include #include #include "pe-int.h" //#include "rich.h" //#include "symbols.h" /* ------------------------- DEFINITION D'UN NOUVEAU FORMAT ------------------------- */ /* Initialise la classe des formats d'exécutables ELF. */ static void g_pe_format_class_init(GPeFormatClass *); /* Initialise une instance de format d'exécutable ELF. */ static void g_pe_format_init(GPeFormat *); /* Supprime toutes les références externes. */ static void g_pe_format_dispose(GPeFormat *); /* Procède à la libération totale de la mémoire. */ static void g_pe_format_finalize(GPeFormat *); /* --------------------- IMPLEMENTATION DES FONCTIONS DE CLASSE --------------------- */ /* Indique la désignation interne du format. */ static char *g_pe_format_get_key(const GPeFormat *); /* Fournit une description humaine du format. */ static char *g_pe_format_get_description(const GPeFormat *); /* Assure l'interprétation d'un format en différé. */ static bool g_pe_format_analyze(GPeFormat *); /* Informe quant au boutisme utilisé. */ static SourceEndian g_pe_format_get_endianness(const GPeFormat *); /* Indique le type d'architecture visée par le format. */ static char *g_pe_format_get_target_machine(const GPeFormat *); /* Fournit l'adresse principale associée à un format. */ static bool g_pe_format_get_main_address(GPeFormat *, vmpa2t *); /* Etend la définition des portions au sein d'un binaire. */ static bool g_pe_format_refine_portions(GPeFormat *); /* Fournit l'emplacement d'une section donnée. */ static bool g_pe_format_get_section_range_by_name(const GPeFormat *, const char *, mrange_t *); /* ---------------------------------------------------------------------------------- */ /* DEFINITION D'UN NOUVEAU FORMAT */ /* ---------------------------------------------------------------------------------- */ /****************************************************************************** * * * Paramètres : content = contenu binaire à traiter. * * * * Description : Valide un contenu comme étant un format PE. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ bool check_pe_format(const GBinContent *content) { bool result; /* Bilan à faire remonter */ vmpa2t addr; /* Tête de lecture initiale */ char magic[4]; /* Idenfiant standard */ GPeFormat format; /* Format factice */ init_vmpa(&addr, 0, VMPA_NO_VIRTUAL); result = g_binary_content_read_raw(content, &addr, 2, (bin_t *)magic); if (result) result = (memcmp(magic, "\x4d\x5a" /* MZ */, 2) == 0); if (result) { ((GKnownFormat *)&format)->content = (GBinContent *)content; result = read_dos_image_header(&format, &format.dos_header); } if (result) { init_vmpa(&addr, format.dos_header.e_lfanew, VMPA_NO_VIRTUAL); result = g_binary_content_read_raw(content, &addr, 4, (bin_t *)magic); if (result) result = (memcmp(magic, "\x50\x45\x00\x00" /* PE00 */, 4) == 0); } return result; } /* Indique le type défini pour un format d'exécutable ELF. */ G_DEFINE_TYPE(GPeFormat, g_pe_format, G_TYPE_EXECUTABLE_FORMAT); /****************************************************************************** * * * Paramètres : klass = classe à initialiser. * * * * Description : Initialise la classe des formats d'exécutables ELF. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_pe_format_class_init(GPeFormatClass *klass) { GObjectClass *object; /* Autre version de la classe */ GKnownFormatClass *known; /* Version de format connu */ GProgramFormatClass *prgm; /* Version en format basique */ GExecutableFormatClass *exe; /* Version en exécutable */ object = G_OBJECT_CLASS(klass); object->dispose = (GObjectFinalizeFunc/* ! */)g_pe_format_dispose; object->finalize = (GObjectFinalizeFunc)g_pe_format_finalize; known = G_KNOWN_FORMAT_CLASS(klass); known->get_key = (known_get_key_fc)g_pe_format_get_key; known->get_desc = (known_get_desc_fc)g_pe_format_get_description; known->analyze = (known_analyze_fc)g_pe_format_analyze; prgm = G_PROGRAM_FORMAT_CLASS(klass); prgm->get_endian = (program_get_endian_fc)g_pe_format_get_endianness; prgm->get_range_by_name = (get_range_by_name_fc)g_pe_format_get_section_range_by_name; exe = G_EXECUTABLE_FORMAT_CLASS(klass); exe->get_machine = (get_target_machine_fc)g_pe_format_get_target_machine; exe->get_main_addr = (get_main_addr_fc)g_pe_format_get_main_address; exe->refine_portions = (refine_portions_fc)g_pe_format_refine_portions; exe->translate_phys = g_executable_format_translate_offset_into_vmpa_with_portions; exe->translate_virt = g_executable_format_translate_address_into_vmpa_with_portions; } /****************************************************************************** * * * Paramètres : format = instance à initialiser. * * * * Description : Initialise une instance de format d'exécutable ELF. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_pe_format_init(GPeFormat *format) { format->sections = NULL; format->loaded = false; } /****************************************************************************** * * * Paramètres : format = instance d'objet GLib à traiter. * * * * Description : Supprime toutes les références externes. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_pe_format_dispose(GPeFormat *format) { G_OBJECT_CLASS(g_pe_format_parent_class)->dispose(G_OBJECT(format)); } /****************************************************************************** * * * Paramètres : format = instance d'objet GLib à traiter. * * * * Description : Procède à la libération totale de la mémoire. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_pe_format_finalize(GPeFormat *format) { if (format->sections != NULL) free(format->sections); G_OBJECT_CLASS(g_pe_format_parent_class)->finalize(G_OBJECT(format)); } /****************************************************************************** * * * Paramètres : content = contenu binaire à parcourir. * * * * Description : Prend en charge un nouveau format PE. * * * * Retour : Adresse de la structure mise en place ou NULL en cas d'échec.* * * * Remarques : - * * * ******************************************************************************/ GPeFormat *g_pe_format_new(GBinContent *content) { GPeFormat *result; /* Structure à retourner */ result = g_object_new(G_TYPE_PE_FORMAT, NULL); if (!g_pe_format_create(result, content)) g_clear_object(&result); return result; } /****************************************************************************** * * * Paramètres : format = description du format connu à consulter. * * content = contenu binaire à parcourir. * * * * Description : Met en place une nouvelle instance de format PE. * * * * Retour : Bilan de l'opération. * * * * Remarques : - * * * ******************************************************************************/ bool g_pe_format_create(GPeFormat *format, GBinContent *content) { bool result; /* Bilan à retourner */ result = true;//check_pe_format(content); if (result) result = g_executable_format_create(G_EXECUTABLE_FORMAT(format), content); return result; } /****************************************************************************** * * * Paramètres : format = format en place à consulter. * * * * Description : Présente l'en-tête MS-DOS du format chargé. * * * * Retour : Pointeur vers la description principale. * * * * Remarques : - * * * ******************************************************************************/ const image_dos_header_t *g_pe_format_get_dos_header(const GPeFormat *format) { const image_dos_header_t *result; /* Informations à retourner */ result = &format->dos_header; return result; } /****************************************************************************** * * * Paramètres : format = format en place à consulter. * * * * Description : Présente l'en-tête NT du format chargé. * * * * Retour : Pointeur vers la description principale. * * * * Remarques : - * * * ******************************************************************************/ const image_nt_headers_t *g_pe_format_get_nt_headers(const GPeFormat *format) { const image_nt_headers_t *result; /* Informations à retourner */ result = &format->nt_headers; return result; } /****************************************************************************** * * * Paramètres : format = format en place à consulter. * * * * Description : Indique si le format PE est en 32 bits ou en 64 bits. * * * * Retour : true si le format est en 32 bits, false sinon. * * * * Remarques : - * * * ******************************************************************************/ bool g_pe_format_get_is_32b(const GPeFormat *format) { bool result; /* Nature à retourner */ assert(format->loaded); switch (format->nt_headers.optional_header.header_32.magic) { case IMAGE_NT_OPTIONAL_HDR32_MAGIC: result = true; break; case IMAGE_NT_OPTIONAL_HDR64_MAGIC: result = false; break; default: result = true; assert(false); break; } return result; } /****************************************************************************** * * * Paramètres : format = format en place à consulter. * * count = taille (fixe) du tableau renvoyé. [OUT] * * * * Description : Offre un raccourci vers les répertoires du format PE. * * * * Retour : Pointeur vers le tableau des répertoires. * * * * Remarques : - * * * ******************************************************************************/ const image_data_directory_t *g_pe_format_get_directories(const GPeFormat *format, size_t *count) { const image_data_directory_t *result; /* Liste à retourner */ if (g_pe_format_get_is_32b(format)) { result = format->nt_headers.optional_header.header_32.data_directory; if (count != NULL) *count = format->nt_headers.optional_header.header_32.number_of_rva_and_sizes; } else { result = format->nt_headers.optional_header.header_64.data_directory; if (count != NULL) *count = format->nt_headers.optional_header.header_64.number_of_rva_and_sizes; } return result; } /****************************************************************************** * * * Paramètres : format = format en place à consulter. * * index = indice du répertoire visé. * * * * Description : Extrait le contenu d'un répertoire du format PE. * * * * Retour : Pointeur vers un contenu chargé ou NULL. * * * * Remarques : - * * * ******************************************************************************/ void *g_pe_format_get_directory(const GPeFormat *format, size_t index) { void *result; /* Données à retourner */ size_t max; /* Quantité de répertoires */ const image_data_directory_t *dir; /* Localisation du répertoire */ vmpa2t pos; /* Tête de lecture */ bool status; /* Bilan d'un traitement */ image_export_directory_t *export; /* Répertoire de type 0 */ image_import_descriptor_t *imports; /* Répertoire de type 1 */ size_t imported_count; /* Quantité de DLL requises */ result = NULL; dir = g_pe_format_get_directories(format, &max); if (index >= max) goto exit; dir += index; status = g_executable_format_translate_address_into_vmpa(G_EXECUTABLE_FORMAT(format), dir->virtual_address, &pos); if (!status) goto exit; switch (index) { case IMAGE_DIRECTORY_ENTRY_EXPORT: export = malloc(sizeof(image_export_directory_t)); status = read_pe_image_export_directory(format, &pos, export); if (!status) { free(export); goto exit; } result = export; break; case IMAGE_DIRECTORY_ENTRY_IMPORT: imports = NULL; imported_count = 0; do { imports = realloc(imports, ++imported_count * sizeof(image_import_descriptor_t)); status = read_pe_image_import_descriptor(format, &pos, imports + (imported_count - 1)); if (!status) { free(imports); goto exit; } } while (imports[imported_count - 1].original_first_thunk != 0); result = imports; break; } exit: return result; } /****************************************************************************** * * * Paramètres : format = format en place à consulter. * * count = taille (fixe) du tableau renvoyé. [OUT] * * * * Description : Offre un raccourci vers les sections du format PE. * * * * Retour : Pointeur vers la liste des sections. * * * * Remarques : - * * * ******************************************************************************/ const image_section_header_t *g_pe_format_get_sections(const GPeFormat *format, size_t *count) { const image_section_header_t *result; /* Liste à retourner */ if (count != NULL) *count = format->nt_headers.file_header.number_of_sections; result = format->sections; return result; } /* ---------------------------------------------------------------------------------- */ /* IMPLEMENTATION DES FONCTIONS DE CLASSE */ /* ---------------------------------------------------------------------------------- */ /****************************************************************************** * * * Paramètres : format = description de l'exécutable à consulter. * * * * Description : Indique la désignation interne du format. * * * * Retour : Désignation du format. * * * * Remarques : - * * * ******************************************************************************/ static char *g_pe_format_get_key(const GPeFormat *format) { char *result; /* Désignation à retourner */ result = strdup("pe"); return result; } /****************************************************************************** * * * Paramètres : format = description de l'exécutable à consulter. * * * * Description : Fournit une description humaine du format. * * * * Retour : Description du format. * * * * Remarques : - * * * ******************************************************************************/ static char *g_pe_format_get_description(const GPeFormat *format) { char *result; /* Désignation à retourner */ result = strdup("Portable Executable"); return result; } /****************************************************************************** * * * Paramètres : format = format chargé dont l'analyse est lancée. * * * * Description : Assure l'interprétation d'un format en différé. * * * * Retour : Bilan de l'opération. * * * * Remarques : - * * * ******************************************************************************/ static bool g_pe_format_analyze(GPeFormat *format) { bool result; /* Bilan à retourner */ vmpa2t start; /* Zone de départ des sections */ uint16_t count; /* Quantité de sections */ uint16_t i; /* Boucle de parcours */ result = read_dos_image_header(format, &format->dos_header); if (!result) goto error; result = read_pe_nt_header(format, &format->nt_headers, &start); if (!result) goto error; /* Chargement des définitions des sections déclarées */ copy_vmpa(&format->sections_start, &start); count = format->nt_headers.file_header.number_of_sections; format->sections = malloc(count * sizeof(image_section_header_t)); for (i = 0; i < count; i++) { result = read_pe_image_section_header(format, &start, &format->sections[i]); if (!result) goto error; } /* Passage de relais */ if (result) result = G_KNOWN_FORMAT_CLASS(g_pe_format_parent_class)->analyze(G_KNOWN_FORMAT(format)); #if 0 extract_pe_rich_header(format); result = load_pe_symbols(format, gid, status); if (!result) goto error; #endif error: return result; } /****************************************************************************** * * * Paramètres : format = informations chargées à consulter. * * * * Description : Informe quant au boutisme utilisé. * * * * Retour : Indicateur de boutisme. * * * * Remarques : - * * * ******************************************************************************/ static SourceEndian g_pe_format_get_endianness(const GPeFormat *format) { SourceEndian result; /* Boutisme à retourner */ /** * Sauf exception, le boutisme est généralement petit. * * Cf. https://reverseengineering.stackexchange.com/a/17923 * https://docs.microsoft.com/en-us/cpp/build/overview-of-arm-abi-conventions?view=msvc-160#endianness */ result = SRE_LITTLE; return result; } /****************************************************************************** * * * Paramètres : format = description de l'exécutable à consulter. * * name = nom de la section recherchée. * * range = emplacement en mémoire à renseigner. [OUT] * * * * Description : Fournit l'emplacement d'une section donnée. * * * * Retour : Bilan de l'opération. * * * * Remarques : - * * * ******************************************************************************/ static bool g_pe_format_get_section_range_by_name(const GPeFormat *format, const char *name, mrange_t *range) { bool result; /* Bilan à retourner */ uint16_t i; /* Boucle de parcours */ image_section_header_t *section; /* Section à traiter */ char tmp[IMAGE_SIZEOF_SHORT_NAME + 1]; /* Nom de la section */ vmpa2t addr; /* Emplacement dans le binaire */ result = false; for (i = 0, section = format->sections; i < format->nt_headers.file_header.number_of_sections; i++, section++) { memcpy(tmp, section->name, IMAGE_SIZEOF_SHORT_NAME); tmp[IMAGE_SIZEOF_SHORT_NAME] = '\0'; if (strcmp(name, tmp) != 0) continue; init_vmpa(&addr, section->pointer_to_raw_data, section->virtual_address); init_mrange(range, &addr, section->size_of_raw_data); result = true; break; } return result; } /****************************************************************************** * * * Paramètres : format = informations chargées à consulter. * * * * Description : Indique le type d'architecture visée par le format. * * * * Retour : Identifiant de l'architecture ciblée par le format. * * * * Remarques : - * * * ******************************************************************************/ static char *g_pe_format_get_target_machine(const GPeFormat *format) { char *result; /* Identifiant à retourner */ switch (format->nt_headers.file_header.machine) { case IMAGE_FILE_MACHINE_I386: result = strdup("i386"); break; case IMAGE_FILE_MACHINE_R3000: case IMAGE_FILE_MACHINE_R4000: case IMAGE_FILE_MACHINE_R10000: case IMAGE_FILE_MACHINE_WCEMIPSV2: case IMAGE_FILE_MACHINE_MIPS16: case IMAGE_FILE_MACHINE_MIPSFPU: case IMAGE_FILE_MACHINE_MIPSFPU16: result = strdup("mips"); break; case IMAGE_FILE_MACHINE_ARM: case IMAGE_FILE_MACHINE_THUMB: case IMAGE_FILE_MACHINE_ARMNT: result = strdup("armv7"); break; case IMAGE_FILE_MACHINE_UNKNOWN: default: result = NULL; break; } return result; } /****************************************************************************** * * * Paramètres : format = description de l'exécutable à consulter. * * addr = adresse principale trouvée si possible. [OUT] * * * * Description : Fournit l'adresse principale associée à un format. * * * * Retour : Bilan des recherches. * * * * Remarques : - * * * ******************************************************************************/ static bool g_pe_format_get_main_address(GPeFormat *format, vmpa2t *addr) { bool result; /* Bilan à retourner */ virt_t ep; /* Point d'entrée */ if (g_pe_format_get_is_32b(format)) ep = format->nt_headers.optional_header.header_32.address_of_entry_point; else ep = format->nt_headers.optional_header.header_64.address_of_entry_point; result = g_executable_format_translate_address_into_vmpa(G_EXECUTABLE_FORMAT(format), ep, addr); return result; } /****************************************************************************** * * * Paramètres : format = informations chargées à consulter. * * * * Description : Etend la définition des portions au sein d'un binaire. * * * * Retour : Bilan des définitions de portions. * * * * Remarques : - * * * ******************************************************************************/ static bool g_pe_format_refine_portions(GPeFormat *format) { bool result; /* Bilan à retourner */ vmpa2t origin; /* Origine d'une définition */ uint16_t i; /* Boucle de parcours */ image_section_header_t *section; /* Section à traiter */ vmpa2t addr; /* Emplacement dans le binaire */ GBinaryPortion *portion; /* Nouvelle portion de binaire */ char name[IMAGE_SIZEOF_SHORT_NAME + 1]; /* Nom de la section */ PortionAccessRights rights; /* Droits d'accès à la section */ result = true; copy_vmpa(&origin, &format->sections_start); for (i = 0, section = format->sections; i < format->nt_headers.file_header.number_of_sections; i++, section++) { if (section->pointer_to_raw_data == 0) continue; /* Emplacement */ init_vmpa(&addr, section->pointer_to_raw_data, section->virtual_address); portion = g_binary_portion_new(&addr, section->size_of_raw_data); /* Nom */ memcpy(name, section->name, IMAGE_SIZEOF_SHORT_NAME); name[IMAGE_SIZEOF_SHORT_NAME] = '\0'; g_binary_portion_set_desc(portion, name); /* Droits d'accès */ rights = PAC_NONE; if (section->characteristics & IMAGE_SCN_MEM_EXECUTE) rights |= PAC_EXEC; if (section->characteristics & IMAGE_SCN_MEM_READ) rights |= PAC_READ; if (section->characteristics & IMAGE_SCN_MEM_WRITE) rights |= PAC_WRITE; g_binary_portion_set_rights(portion, rights); /* Inclusion finale */ result = g_executable_format_include_portion(G_EXECUTABLE_FORMAT(format), portion, &origin); unref_object(portion); if (!result) break; advance_vmpa(&origin, sizeof(image_section_header_t)); } return result; }