/* OpenIDA - Outil d'analyse de fichiers binaires
 * theseus.c - décompilation en fonction du flot d'exécution
 *
 * Copyright (C) 2010-2011 Cyrille Bagard
 *
 *  This file is part of OpenIDA.
 *
 *  OpenIDA 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.
 *
 *  OpenIDA 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 Foobar.  If not, see .
 */
#include "theseus.h"
#include 
#include 
#include 
/* Traite en premier lieu les adresses marquées "impératives". */
static bool register_white_list(GOpenidaBinary *);
/* Indique si une ligne peut faire l'objet d'un nouveau suivi. */
static bool can_line_be_followed(const GRenderingLine *);
/* Indique si une ligne a déjà été traitée lors d'un suivi. */
static bool can_line_be_processed(const GRenderingLine *);
/* Marque une ligne comme ayant été traitée. */
static void mark_line_as_done(GRenderingLine *, bool);
/* Suit le flot d'exécution à partir d'un point donné. */
static bool follow_flow_from_line(GOpenidaBinary *, vmpa_t);
/* Désassemble une nouvelle instruction à partir d'une adresse. */
static GRenderingLine *disassemble_target_address(GOpenidaBinary *, GRenderingLine *, vmpa_t);
/* Insère dans le flux existant les nouvelles lignes crées. */
static bool insert_new_lines_into_flow(GOpenidaBinary *, GRenderingLine *, GRenderingLine *);
/*
mov	edx, 0x80480fb
call	[edx]
mov	edx, 0x80480fb
add	edx, 0x8
call	[edx]
mov	edx, 0x80480fb
add	edx, 0x10
call	[edx]
mov	edx, 0x80480fb
add	edx, 0xc
call	[edx]
mov	edx, 0x80480fb
add	edx, 0x14
call	[edx]
mov	edx, 0x80480fb
add	edx, 0x4
call	[edx]
*/
static const vmpa_t _white_list[] = {
    0x80480fbull,
    0x8048103ull,
    0x804810bull,
    0x8048107ull,
    0x804810full,
    0x80480ffull
};
static const size_t _white_list_count = 6;
/******************************************************************************
*                                                                             *
*  Paramètres  : ref = espace de référencement global.                        *
*                                                                             *
*  Description : Initialise le greffon pour les bornes de routine.            *
*                                                                             *
*  Retour      : true.                                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/
G_MODULE_EXPORT bool init_plugin(GObject *ref)
{
    return true;
}
/******************************************************************************
*                                                                             *
*  Paramètres  : -                                                            *
*                                                                             *
*  Description : Fournit une indication sur le type d'opération(s) menée(s).  *
*                                                                             *
*  Retour      : Description d'une action.                                    *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/
G_MODULE_EXPORT PluginAction get_plugin_action(void)
{
    return PGA_CODE_PROCESS;
}
/******************************************************************************
*                                                                             *
*  Paramètres  : binary = représentation binaire à traiter.                   *
*                action = action attendue.                                    *
*                                                                             *
*  Description : Exécute une action définie sur un binaire chargé.            *
*                                                                             *
*  Retour      : true si une action a été menée, false sinon.                 *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/
G_MODULE_EXPORT bool execute_action_on_binary(GOpenidaBinary *binary, PluginAction action)
{
    bool result;                            /* Bilan à retourner           */
    GRenderingLine *lines;                  /* Lignes désassemblées        */
    GExeFormat *format;                     /* Format du binaire traité    */
    vmpa_t start;                           /* Point de départ de l'action */
    GRenderingOptions *options;
    GRenderingLine *iter;
    lines = g_openida_binary_get_lines(binary);
    format = g_openida_binary_get_format(binary);
    start = g_exe_format_get_entry_point(format);
    result = register_white_list(binary);
    result &= follow_flow_from_line(binary, start);
    //follow_flow_from_line(binary, 0x0804810bull);
    options = g_openida_binary_get_options(binary);
    for (iter = lines; iter != NULL; iter = g_rendering_line_get_next_iter(lines, iter, NULL))
    {
        printf(" >>>>>> %c%c  ",
               g_object_get_data(G_OBJECT(iter), "theseus_white") != NULL ? '!' : ' ',
               g_object_get_data(G_OBJECT(iter), "theseus_done") != NULL ? '*' : ' ');
        g_content_exporter_add_text(G_CONTENT_EXPORTER(iter), options, 0/*MRD_BLOCK*/, stdout);
        fflush(stdout);
        printf("\n");
    }
    //exit(-1);
    return result;
}
/******************************************************************************
*                                                                             *
*  Paramètres  : binary = représentation binaire à traiter.                   *
*                                                                             *
*  Description : Traite en premier lieu les adresses marquées "impératives".  *
*                                                                             *
*  Retour      : true si le déroulement s'est bien effectué, false sinon.     *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/
static bool register_white_list(GOpenidaBinary *binary)
{
    bool result;                            /* Bilan à retourner           */
    GRenderingLine *lines;                  /* Lignes désassemblées        */
    size_t i;                               /* Boucle de parcours          */
    GRenderingLine *target;                 /* Ligne à transformer ?       */
    GRenderingLine *last;                   /* Nouvelle ligne principale   */
    GRenderingLine *new;                    /* Nouvelles lignes de rendu   */
    result = true;
    lines = g_openida_binary_get_lines(binary);
    for (i = 0; i < _white_list_count && result; i++)
    {
        target = g_rendering_line_find_by_address(lines, NULL, _white_list[i]);
        target = g_rendering_line_loop_for_code(target, NULL);
        new = disassemble_target_address(binary, target, _white_list[i]);
        last = g_rendering_line_get_last_iter(new, NULL);
        mark_line_as_done(last, true);
        result = insert_new_lines_into_flow(binary, target, new);
        result &= follow_flow_from_line(binary, _white_list[i]);
    }
    return result;
}
/******************************************************************************
*                                                                             *
*  Paramètres  : line = ligne de rendu à analyser.                            *
*                                                                             *
*  Description : Indique si une ligne peut faire l'objet d'un nouveau suivi.  *
*                                                                             *
*  Retour      : true si le suivi peut continuer, false sinon.                *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/
static bool can_line_be_followed(const GRenderingLine *line)
{
    bool result;                            /* Bilan d'analyse à renvoyer  */
    result = true;
    if (g_object_get_data(G_OBJECT(line), "theseus_done") != NULL)
        result = (g_object_get_data(G_OBJECT(line), "theseus_white") != NULL);
    return result;
}
/******************************************************************************
*                                                                             *
*  Paramètres  : line = ligne de rendu à analyser.                            *
*                                                                             *
*  Description : Indique si une ligne a déjà été traitée lors d'un suivi.     *
*                                                                             *
*  Retour      : true si le suivi peut continuer, false sinon.                *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/
static bool can_line_be_processed(const GRenderingLine *line)
{
    return (g_object_get_data(G_OBJECT(line), "theseus_done") == NULL);
}
/******************************************************************************
*                                                                             *
*  Paramètres  : line  = ligne de rendu à traiter.                            *
*                white = type de marquage à apposer (liste blanche ou normal).*
*                                                                             *
*  Description : Marque une ligne comme ayant été traitée.                    *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/
static void mark_line_as_done(GRenderingLine *line, bool white)
{
    if (white)
        g_object_set_data(G_OBJECT(line), "theseus_white", line);
    else
        g_object_set_data(G_OBJECT(line), "theseus_done", line);
}
/******************************************************************************
*                                                                             *
*  Paramètres  : binary = représentation binaire à traiter.                   *
*                start  = point de départ de la procédure.                    *
*                                                                             *
*  Description : Suit le flot d'exécution à partir d'un point donné.          *
*                                                                             *
*  Retour      : true si le déroulement s'est bien effectué, false sinon.     *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/
static bool follow_flow_from_line(GOpenidaBinary *binary, vmpa_t start)
{
    bool result;                            /* Bilan de opérations         */
    GRenderingLine *lines;                  /* Lignes désassemblées        */
    GRenderingLine *first;                  /* Première ligne à traiter    */
    vmpa_t addr;                            /* Adresse référencée          */
    GRenderingLine *new;                    /* Nouvelles lignes de rendu   */
    GRenderingLine *iter;                   /* Boucle de parcours          */
    GArchInstruction *instr;                /* Instruction à ausculter     */
    InstructionLinkType type;               /* Type de référence           */
    GRenderingLine *target;                 /* Ligne visée par la référence*/
    lines = g_openida_binary_get_lines(binary);
    first = g_rendering_line_find_by_address(lines, NULL, start);
    first = g_rendering_line_loop_for_code(first, NULL);
    if (!can_line_be_followed(first))
        return true;
    //if (start != 0x0804810bull) return true;
    printf("###################################### Passage\n");
    /* Alignement sur le point d'entrée */
    result = true;
    addr = get_rendering_line_address(first);
    if (addr < start)
    {
        new = disassemble_target_address(binary, first, start);
        result = insert_new_lines_into_flow(binary, target, new);
        first = g_rendering_line_find_by_address(lines, NULL, start);
        //return true;
    }
    else g_object_set_data(G_OBJECT(first), "theseus_done", binary);
    printf("Analyse :: 0x%016llx\n", get_rendering_line_address(first));
    /* Poursuite du suivi du flot... */
    for (iter = g_rendering_line_get_next_iter(lines, first, NULL);
         iter != NULL/* && result*/;
         iter = g_rendering_line_get_next_iter(lines, iter, NULL))
    {
        /* On ne traite que du code ici ! */
        if (!G_IS_CODE_LINE(iter)) continue;
        /*
        printf("testing... 0x%08llx => %d\n",
               get_rendering_line_address(iter),
               can_line_be_processed(iter));
        */
        if (!can_line_be_processed(iter))
            break;
        instr = g_code_line_get_instruction(G_CODE_LINE(iter));
        /* Si le code n'es pas désassemblé ! */
        if (G_IS_DB_INSTRUCTION(instr))
        {
            new = disassemble_target_address(binary, iter, get_rendering_line_address(iter));
            result = insert_new_lines_into_flow(binary, iter, new);
            if (!result)
            {
                mark_line_as_done(iter, false);
                break;
            }
            else
            {
                iter = new;
                mark_line_as_done(iter, false);
                instr = g_code_line_get_instruction(G_CODE_LINE(iter));
                if (!G_IS_DB_INSTRUCTION(instr)) break;
            }
        }
        else mark_line_as_done(iter, false);
        type = g_arch_instruction_get_link(instr, &addr);
        if (get_rendering_line_address(iter) ==  0x0804811aull)
            printf(" == got it !! ==\n");
        switch (type)
        {
            case ILT_JUMP:
            case ILT_JUMP_IF_FALSE:
            case ILT_JUMP_IF_TRUE:
                result = follow_flow_from_line(binary, addr);
                break;
           default:
               break;
        }
        if (get_rendering_line_address(iter) ==  0x0804811aull)
            printf(" == continue ? %d\n", result);
    }
    return result;
}
/******************************************************************************
*                                                                             *
*  Paramètres  : binary = représentation binaire à traiter.                   *
*                old    = ligne contenant l'adresse visée à remplacer.        *
*                target = adresse de la nouvelle instruction à voir.          *
*                                                                             *
*  Description : Désassemble une nouvelle instruction à partir d'une adresse. *
*                                                                             *
*  Retour      : Nouvelle(s) ligne(s) en place ou NULL en cas d'échec.        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/
static GRenderingLine *disassemble_target_address(GOpenidaBinary *binary, GRenderingLine *old, vmpa_t target)
{
    GRenderingLine *result;                 /* Lignes en place à renvoyer  */
    GExeFormat *format;                     /* Format du binaire fourni    */
    GArchProcessor *proc;                   /* Architecture du binaire     */
    GRenderingOptions *options;             /* Options de désassemblage    */
    bin_t *data;                            /* Données binaires chargées   */
    off_t length;                           /* Taille du code binaire      */
    vmpa_t old_addr;                        /* Adresse de ligne à casser   */
    off_t offset;                           /* Position dans le contenu    */
    vmpa_t i;                               /* Boucle de parcours          */
    GArchInstruction *instr;                /* Instruction décodée         */
    GRenderingLine *line;                   /* Nouvelle ligne de rendu     */
    result = NULL;
    format = g_openida_binary_get_format(binary);
    proc = get_arch_processor_from_format(format);
    options = g_openida_binary_get_options(binary);
    data = g_openida_binary_get_data(binary, &length);
    old_addr = get_rendering_line_address(old);
    if (!g_exe_format_translate_address_into_offset(format, old_addr, &offset))
        return NULL;
    /* Si on doit laisser sur le carreau quelques octets perdus... */
    for (i = 0; i < (target - old_addr); i++)
    {
        instr = g_db_instruction_new_from_data(data, &offset, length, 0/*base*/, proc);
        g_arch_instruction_set_location(instr, 0/*base*/ + offset - 1, 1, old_addr + i);
        line = g_code_line_new(old_addr + i, instr, options);
        g_rendering_line_add_to_lines(&result, line);
    }
    /* Décodage des instructions */
    instr = g_arch_processor_decode_instruction(proc, NULL /*FIXME*/, data,
                                                &offset, length, 0/*offset*/, target);
    line = g_code_line_new(target, instr, options);
    g_rendering_line_add_to_lines(&result, line);
    mark_line_as_done(line, false);
    return result;
}
/******************************************************************************
*                                                                             *
*  Paramètres  : binary = représentation binaire à traiter.                   *
*                old    = ancienne ligne, point de départ des remplacements.  *
*                new    = nouvelle(s) ligne(s) de représentation.             *
*                                                                             *
*  Description : Insère dans le flux existant les nouvelles lignes crées.     *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/
static bool insert_new_lines_into_flow(GOpenidaBinary *binary, GRenderingLine *old, GRenderingLine *new)
{
    bool result;                            /* Bilan de opérations         */
    GExeFormat *format;                     /* Format du binaire fourni    */
    GArchProcessor *proc;                   /* Architecture du binaire     */
    GRenderingOptions *options;             /* Options de désassemblage    */
    bin_t *data;                            /* Données binaires chargées   */
    off_t length;                           /* Taille du code binaire      */
    GRenderingLine **root;                  /* Racine des lignes de rendu  */
    GRenderingLine *iter;                   /* Boucle de parcours          */
    vmpa_t start;                           /* Adresse de début du bloc    */
    vmpa_t end;                             /* Adresse de fin du bloc      */
    vmpa_t max;                             /* Adresse de fin de ligne     */
    vmpa_t i;                               /* Boucle de parcours          */
    off_t offset;                           /* Position dans le contenu    */
    GArchInstruction *instr;                /* Instruction décodée         */
    GRenderingLine *line;                   /* Nouvelle ligne de rendu     */
    GRenderingLine *last;                   /* Dernière ligne à vérifier   */
    format = g_openida_binary_get_format(binary);
    proc = get_arch_processor_from_format(format);
    options = g_openida_binary_get_options(binary);
    data = g_openida_binary_get_data(binary, &length);
    /* Etendue du bloc de remplacement */
    iter = g_rendering_line_get_last_iter(new, NULL);
    start = get_rendering_line_address(new);
    end = get_rendering_line_address(iter);
    end += get_rendering_line_length(iter) - 1;
    /* Bourrage nécessaire ? */
    root = g_openida_binary_get_lines_root(binary);
    iter = g_rendering_line_find_by_address(*root, NULL, end);
    max = get_rendering_line_address(iter);
    max += get_rendering_line_length(iter);
    for (i = end + 1; i < max; i++)
    {
        if (!g_exe_format_translate_address_into_offset(format, i, &offset))
            /**
             * Comme les adresses proviennent à l'origine de partie valide,
             * on considère que l'appel est toujours un succès.
             * Cela permet d'avoir deux bilan possible : procédure bien déroulée,
             * ou bien remplacement impossible pour cause de place déjà prise.
             */
            /*return false*/;
        instr = g_db_instruction_new_from_data(data, &offset, length, 0/*base*/, proc);
        g_arch_instruction_set_location(instr, 0/*base*/ + offset - 1, 1, i);
        line = g_code_line_new(i, instr, options);
        g_rendering_line_add_to_lines(&new, line);
    }
    /* S'assure qu'on ne touche à rien de sacré */
    result = true;
    last = g_rendering_line_find_by_address(*root, NULL, end);
    for (iter = g_rendering_line_find_by_address(*root, NULL, start);
         iter != NULL && result;
         iter = g_rendering_line_get_next_iter(*root, iter, last))
    {
        result = can_line_be_processed(iter);
    }
    /* Remplacement en bonne et due forme */
    if (result)
    {
        g_rendering_line_remove_range(root, start, end);
        g_rendering_line_insert_lines(root, &new);
    }
    return result;
}