/* Chrysalide - Outil d'analyse de fichiers binaires * collect.c - collecte de différents registres en remontant le flot d'exécution * * Copyright (C) 2018 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 "collect.h" #include #include #include #include #include "hops.h" /* Suivi de l'usage d'un registre */ typedef struct _collected_register { GArchRegister *reg; /* Registre usité à tracer */ bool required; /* Registre utile ? */ GArchInstruction *written; /* Emplacement d'écriture */ } collected_register; /* Suivi d'un flot d'exécution */ typedef struct _call_stack { collected_register *registers; /* Liste de registres suivis */ size_t count; /* Taille de cette liste */ instr_iter_t *iter; /* Boucle de parcours */ bool use_current; /* Traite l'instruction pointée*/ bool skip_syscall; /* Acceptation des rencontres */ } call_stack; /* Collection de registres */ struct _tracked_path { call_stack *stacks; /* Piles d'exécution suivies */ size_t count; /* Nombre de ces piles */ }; /* Copie les informations de pile d'appels. */ static void copy_call_stack(call_stack *, const call_stack *); /* Libère la mémoire des infos relatives à une pile d'appels. */ static void clean_call_stack(call_stack *); /* Fournit une structure de suivi de registres pour une branche. */ static size_t fork_register_tracker(tracked_path *, size_t, GArchProcessor *, GArchInstruction *); /* Change la tête de lecture pour le parcours des instructions. */ static void change_register_tracker_iter(tracked_path *, size_t, GArchProcessor *, GArchInstruction *); /* Détermine si tous les registres recherchés ont été trouvés. */ static bool got_all_tracked_registers(const tracked_path *, size_t); /****************************************************************************** * * * Paramètres : dest = zone de destination des données copiées. [OUT] * * src = source des données à copier. * * * * Description : Copie les informations de pile d'appels. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void copy_call_stack(call_stack *dest, const call_stack *src) { size_t i; /* Boucle de parcours */ if (src->count == 0) { dest->registers = NULL; dest->count = 0; } else { dest->registers = (collected_register *)malloc(src->count * sizeof(collected_register)); dest->count = src->count; for (i = 0; i < src->count; i++) { dest->registers[i].reg = src->registers[i].reg; dest->registers[i].required = src->registers[i].required; dest->registers[i].written = src->registers[i].written; g_object_ref(G_OBJECT(dest->registers[i].reg)); if (dest->registers[i].written != NULL) g_object_ref(G_OBJECT(dest->registers[i].written)); } } dest->iter = src->iter != NULL ? copy_instruction_iterator(src->iter) : NULL; dest->use_current = src->use_current; dest->skip_syscall = src->skip_syscall; } /****************************************************************************** * * * Paramètres : stack = information sur une pile d'appels à supprimer. * * * * Description : Libère la mémoire des infos relatives à une pile d'appels. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void clean_call_stack(call_stack *stack) { size_t i; /* Boucle de parcours */ for (i = 0; i < stack->count; i++) { g_object_unref(G_OBJECT(stack->registers[i].reg)); if (stack->registers[i].written != NULL) g_object_unref(G_OBJECT(stack->registers[i].written)); } if (stack->registers != NULL) free(stack->registers); if (stack->iter != NULL) delete_instruction_iterator(stack->iter); } /****************************************************************************** * * * Paramètres : base = position de parcours initiale. * * * * Description : Crée une structure de suivi de registres vide. * * * * Retour : Structure prête à emploi. * * * * Remarques : - * * * ******************************************************************************/ tracked_path *create_register_tracker(instr_iter_t *base) { tracked_path *result; /* Structure à retourner */ result = (tracked_path *)malloc(sizeof(tracked_path)); result->stacks = (call_stack *)malloc(sizeof(call_stack)); result->count = 1; copy_call_stack(&result->stacks[0], (call_stack []) { { .count = 0, .iter = base, .use_current = true, .skip_syscall = true } }); return result; } /****************************************************************************** * * * Paramètres : model = suivi déjà en place d'où l'inspiration doit venir. * * sid = identifiant de la pile d'exécution initiale. * * * * Description : Crée une structure de suivi de registres initialisée. * * * * Retour : Structure prête à emploi. * * * * Remarques : - * * * ******************************************************************************/ tracked_path *create_register_tracker_from(const tracked_path *model, size_t sid) { tracked_path *result; /* Structure à retourner */ result = (tracked_path *)malloc(sizeof(tracked_path)); result->stacks = (call_stack *)malloc(sizeof(call_stack)); result->count = 1; copy_call_stack(&result->stacks[0], &model->stacks[sid]); return result; } /****************************************************************************** * * * Paramètres : path = chemin d'exécution à traiter. * * * * Description : Efface une structure de suivi de registres. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ void delete_register_tracker(tracked_path *path) { size_t i; /* Boucle de parcours */ assert(path->count >= 1); for (i = 0; i < path->count; i++) clean_call_stack(&path->stacks[i]); free(path->stacks); free(path); } /****************************************************************************** * * * Paramètres : path = chemin d'exécution à consulter. * * * * Description : Dénombre les piles d'exécutions différentes conservées. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ size_t count_register_tracker_stacks(const tracked_path *path) { size_t result; /* Quantité à retourner */ result = path->count; return result; } /****************************************************************************** * * * Paramètres : path = chemin d'exécution à traiter. * * sid = identifiant de la pile d'exécution racine à copier. * * proc = processeur de l'architecture pour les instructions. * * dest = prochaine instruction à traiter. * * * * Description : Fournit une structure de suivi de registres pour une branche.* * * * Retour : Indice de la nouvelle pile d'exécution à suivre. * * * * Remarques : - * * * ******************************************************************************/ static size_t fork_register_tracker(tracked_path *path, size_t sid, GArchProcessor *proc, GArchInstruction *dest) { size_t result; /* Indice à retourner */ result = path->count; path->stacks = (call_stack *)realloc(path->stacks, ++path->count * sizeof(call_stack)); copy_call_stack(&path->stacks[result], &path->stacks[sid]); change_register_tracker_iter(path, result, proc, dest); return result; } /****************************************************************************** * * * Paramètres : path = chemin d'exécution à traiter. * * sid = identifiant de la pile d'exécution à traiter. * * proc = processeur de l'architecture pour les instructions. * * dest = prochaine instruction à traiter. * * * * Description : Change la tête de lecture pour le parcours des instructions. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void change_register_tracker_iter(tracked_path *path, size_t sid, GArchProcessor *proc, GArchInstruction *dest) { const mrange_t *range; /* Couverture d'une instruction*/ instr_iter_t *iter; /* Tête de lecture */ if (path->stacks[sid].iter != NULL) delete_instruction_iterator(path->stacks[sid].iter); range = g_arch_instruction_get_range(dest); iter = g_arch_processor_get_iter_from_address(proc, get_mrange_addr(range)); path->stacks[sid].iter = iter; path->stacks[sid].use_current = true; } /****************************************************************************** * * * Paramètres : path = chemin d'exécution à traiter. * * sid = identifiant de la pile d'exécution à traiter. * * reg = registre concerné par la procédure. * * where = localisation de l'écriture ou importance de la note. * * * * Description : Note le besoin ou l'usage d'un registre donné. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ void mark_register_in_tracker(tracked_path *path, size_t sid, GArchRegister *reg, GArchInstruction *where) { call_stack *stack; /* Pile d'exécution concernée */ size_t i; /* Boucle de parcours */ collected_register *collected; /* Accès simplifié */ int ret; /* Bilan d'une comparaison */ stack = &path->stacks[sid]; /* Mise à jour d'un élément présent ? */ for (i = 0; i < stack->count; i++) { collected = &stack->registers[i]; ret = g_arch_register_compare(collected->reg, reg); if (ret != 0) continue; if (where == NULL) collected->required = true; else { if (collected->written == NULL) { collected->written = where; g_object_ref(G_OBJECT(where)); } } break; } /* Ajout d'une nouvelle note */ if (i == stack->count) { stack->count++; stack->registers = (collected_register *)realloc(stack->registers, stack->count * sizeof(collected_register)); collected = &stack->registers[stack->count - 1]; collected->reg = reg; g_object_ref(G_OBJECT(reg)); if (where == NULL) { collected->required = true; collected->written = NULL; } else { collected->required = false; collected->written = where; g_object_ref(G_OBJECT(where)); } } } /****************************************************************************** * * * Paramètres : path = chemin d'exécution à consulter. * * sid = identifiant de la pile d'exécution à traiter. * * * * Description : Détermine si tous les registres recherchés ont été trouvés. * * * * Retour : Besoin en poursuite d'études. * * * * Remarques : - * * * ******************************************************************************/ static bool got_all_tracked_registers(const tracked_path *path, size_t sid) { bool result; /* Bilan à retourner */ call_stack *stack; /* Pile d'exécution concernée */ size_t i; /* Boucle de parcours */ result = true; stack = &path->stacks[sid]; for (i = 0; i < stack->count && result; i++) if (stack->registers[i].required) result = (stack->registers[i].written != NULL); return result; } /****************************************************************************** * * * Paramètres : path = chemin d'exécution à consulter et compléter. * * sid = identifiant de la pile d'exécution à traiter. * * proc = processeur de l'architecture pour les instructions. * * hops = opérations spécialement adaptées à une architecture. * * * * Description : Se lance à la recherche de la définition de registres. * * * * Retour : true si toutes les définitions demandées ont été trouvées. * * * * Remarques : - * * * ******************************************************************************/ bool look_for_registers(tracked_path *path, size_t sid, GArchProcessor *proc, const hunting_ops *hops) { bool result; /* Bilan de l'opération */ call_stack *stack; /* Pile d'exécution concernée */ GArchInstruction *instr; /* Instruction analysée */ GArchOperand *operand; /* Destination d'instruction ? */ GArchRegister *reg; /* Registre en première ligne */ size_t count; /* Nombre de sources présentes */ bool first; /* Premier aiguillage ? */ size_t i; /* Boucle de parcours */ const instr_link_t *link; /* Détails d'un lien */ size_t next; /* Indice de la pile suivante */ stack = &path->stacks[sid]; while (stack->iter != NULL && !got_all_tracked_registers(path, sid)) { if (stack->use_current) { instr = get_instruction_iterator_current(stack->iter); stack->use_current = false; } else instr = get_instruction_iterator_prev(stack->iter); /* Détection de fin de parcours (#1) */ if (instr == NULL) { delete_instruction_iterator(stack->iter); stack->iter = NULL; break; } if (hops->is_syscall(instr) && !stack->skip_syscall) { delete_instruction_iterator(stack->iter); stack->iter = NULL; break; } stack->skip_syscall = false; /* Traitement de l'instruction courante */ g_arch_instruction_lock_operands(instr); if (_g_arch_instruction_count_operands(instr) > 0) { operand = _g_arch_instruction_get_operand(instr, 0); if (G_IS_REGISTER_OPERAND(operand)) { reg = g_register_operand_get_register(G_REGISTER_OPERAND(operand)); mark_register_in_tracker(path, sid, reg, instr); } g_object_unref(G_OBJECT(operand)); } g_arch_instruction_unlock_operands(instr); /* Détermination de l'instruction suivante */ g_arch_instruction_lock_src(instr); count = g_arch_instruction_count_sources(instr); first = true; for (i = 0; i < count; i++) { link = g_arch_instruction_get_source(instr, i); switch (link->type) { case ILT_EXEC_FLOW: case ILT_JUMP: case ILT_CASE_JUMP: case ILT_JUMP_IF_TRUE: case ILT_JUMP_IF_FALSE: if (first) { change_register_tracker_iter(path, sid, proc, link->linked); first = false; } else { next = fork_register_tracker(path, sid, proc, link->linked); look_for_registers(path, next, proc, hops); /** * Rechargement car un fork_register_tracker() a pu déplacer la liste via realloc(). */ stack = &path->stacks[sid]; } break; default: break; } unref_instr_link(link); } g_arch_instruction_unlock_src(instr); /* Détection de fin de parcours (#2) */ if (g_arch_instruction_get_flags(instr) & AIF_ROUTINE_START) { delete_instruction_iterator(stack->iter); stack->iter = NULL; break; } } result = got_all_tracked_registers(path, sid); return result; } /****************************************************************************** * * * Paramètres : path = chemin d'exécution à traiter. * * sid = identifiant de la pile d'exécution à traiter. * * reg = registre concerné par la procédure. * * * * Description : Retrouve la dernière modification d'un registre donné. * * * * Retour : Localisation de l'écriture ou importante de la note. * * * * Remarques : - * * * ******************************************************************************/ GArchInstruction *get_register_write_location(const tracked_path *path, size_t sid, const GArchRegister *reg) { GArchInstruction *result; /* Emplacement à retourner */ call_stack *stack; /* Pile d'exécution concernée */ size_t i; /* Boucle de parcours */ collected_register *collected; /* Accès simplifié */ int ret; /* Bilan d'une comparaison */ result = NULL; stack = &path->stacks[sid]; for (i = 0; i < stack->count; i++) { collected = &stack->registers[i]; ret = g_arch_register_compare(collected->reg, reg); if (ret != 0) continue; result = collected->written; if (result != NULL) g_object_ref(G_OBJECT(result)); break; } return result; }