/* Chrysalide - Outil d'analyse de fichiers binaires
 * format.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 <http://www.gnu.org/licenses/>.
 */


#include "format.h"


#include <assert.h>
#include <string.h>


#include "format-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_find_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->find_range_by_name = (find_range_by_name_fc)g_pe_format_find_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_find_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;

}