/* Chrysalide - Outil d'analyse de fichiers binaires
* finder.c - recherche de gadgets pour ROP
*
* Copyright (C) 2015-2019 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 "finder.h"
#include
#include
#include
#include
#include "helper.h"
#include "helper_arm.h"
/* Actions selon l'architecture */
typedef struct _domain_ops
{
size_t (* list) (char ***);
GProcContext * (* get) (const GArchProcessor *, size_t);
const phys_t * (* setup) (size_t *);
} domain_ops;
/* Données utiles à transmettre */
typedef struct _search_domain
{
GExeFormat *format; /* Format du fichier binaire */
GBinContent *content; /* Contenu associé récupéré */
GArchProcessor *proc; /* Processeur idéal en place */
domain_ops ops; /* Actions particulières */
mrange_t *exe_ranges; /* Liste de zones exécutables */
size_t exe_count; /* Nombre de ces zones */
phys_t sum; /* Surface totale à parcourir */
size_t runs_count; /* Nombre de passages à faire */
size_t runs_done; /* Nombre de passages effectués*/
} search_domain;
/* Désassemble rapidement une instruction. */
static GArchInstruction *disassemble_instruction_in_domain(const search_domain *, size_t, vmpa2t *);
/* Etend la constitution d'une chaîne d'instructions. */
static rop_chain *push_new_instruction(rop_chain *, GArchInstruction *);
/* Désassemble en amont d'une position autant que possible. */
static void look_backward_for_gadgets(const search_domain *, size_t, const mrange_t *, const vmpa2t *, unsigned int, rop_chain *);
/* Etablit une liste de tous les gadgets présents. */
static rop_chain **list_all_gadgets_in_domain(const search_domain *, size_t, unsigned int, update_search_progress_cb, GObject *, size_t *);
/******************************************************************************
* *
* Paramètres : domain = ensemble d'auxiliaires à disposition. *
* index = indice du type de contexte désiré. *
* pos = tête de lecture pour le désassemblage. *
* *
* Description : Désassemble rapidement une instruction. *
* *
* Retour : Instruction créée ou NULL en cas d'échec. *
* *
* Remarques : - *
* *
******************************************************************************/
static GArchInstruction *disassemble_instruction_in_domain(const search_domain *domain, size_t index, vmpa2t *pos)
{
GArchInstruction *result; /* Instruction à retourner */
GProcContext *ctx; /* Contexte de désassemblage */
ctx = domain->ops.get(domain->proc, index);
result = g_arch_processor_disassemble(domain->proc, ctx, domain->content, pos, domain->format);
if (result != NULL)
{
g_arch_instruction_call_hook(result, IPH_LINK, domain->proc, ctx, domain->format);
g_arch_instruction_call_hook(result, IPH_POST, domain->proc, ctx, domain->format);
}
g_object_unref(G_OBJECT(ctx));
return result;
}
/******************************************************************************
* *
* Paramètres : instrs = liste d'instructions à compléter ou NULL. *
* instr = nouvelle instruction à ajouter. *
* *
* Description : Etend la constitution d'une chaîne d'instructions. *
* *
* Retour : Série d'instruction complétée. *
* *
* Remarques : - *
* *
******************************************************************************/
static rop_chain *push_new_instruction(rop_chain *chain, GArchInstruction *instr)
{
rop_chain *result; /* Chaîne à retourner */
if (chain == NULL)
{
result = (rop_chain *)calloc(1, sizeof(rop_chain));
result->instrs = (GArchInstruction **)calloc(1, sizeof(GArchInstruction *));
result->count = 1;
}
else
{
result = chain;
result->count++;
result->instrs = (GArchInstruction **)realloc(result->instrs, result->count * sizeof(GArchInstruction *));
}
if (result->count > 1)
memmove(&result->instrs[1], &result->instrs[0], (result->count - 1) * sizeof(GArchInstruction *));
result->instrs[0] = instr;
return result;
}
/******************************************************************************
* *
* Paramètres : domain = ensemble d'auxiliaires à disposition. *
* index = indice du type de contexte désiré. *
* exe_range = couverture globale dont il ne faut pas sortir. *
* ret = point de retour final déjà trouvé. *
* max_depth = profondeur maximale des recherches. *
* chain = chaîne d'instructions à compléter. [OUT] *
* *
* Description : Désassemble en amont d'une position autant que possible. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void look_backward_for_gadgets(const search_domain *domain, size_t index, const mrange_t *exe_range, const vmpa2t *ret, unsigned int max_depth, rop_chain *chain)
{
const phys_t *ins_sizes; /* Tailles potentielles */
size_t sizes_count; /* Quantité de tailles à tester*/
vmpa2t last; /* Dernier point de départ */
unsigned int i; /* Boucle de parcours #1 */
GArchInstruction *instr; /* Elément de gadget trouvé */
size_t k; /* Boucle de parcours #2 */
vmpa2t start; /* Point de départ courant */
vmpa2t end; /* Point d'arrivée obtenu */
phys_t diff; /* Volume de données traité */
mrange_t range; /* Couverture de l'instruction */
ins_sizes = domain->ops.setup(&sizes_count);
copy_vmpa(&last, ret);
/* On parcours jusqu'à la profondeur maximale */
for (i = 0; i < max_depth; i++)
{
instr = NULL;
for (k = 0; k < sizes_count && instr == NULL; k++)
{
copy_vmpa(&start, &last);
deminish_vmpa(&start, ins_sizes[k]);
/* Est-on toujours dans les clous ? */
if (!mrange_contains_addr(exe_range, &start)) break;
copy_vmpa(&end, &start);
instr = disassemble_instruction_in_domain(domain, index, &end);
if (instr == NULL) continue;
/* La jointure est-elle parfaite ? */
if (cmp_vmpa(&end, &last) != 0)
{
g_object_unref(G_OBJECT(instr));
instr = NULL;
continue;
}
/* S'il s'agit d'un point de retour, on laisse la main à une autre liste */
if (g_arch_instruction_get_flags(instr) & AIF_RETURN_POINT)
{
g_object_unref(G_OBJECT(instr));
instr = NULL;
continue;
}
}
/* Aucune instruction n'a été trouvée à cette profondeur, on s'arrête donc */
if (instr == NULL) break;
copy_vmpa(&last, &start);
diff = compute_vmpa_diff(&end, &start);
init_mrange(&range, &start, diff);
g_arch_instruction_set_range(instr, &range);
push_new_instruction(chain, instr);
}
}
/******************************************************************************
* *
* Paramètres : domain = ensemble d'auxiliaires à disposition. *
* index = indice du type de contexte désiré. *
* max_depth = profondeur maximale des recherches. *
* update = fonction de suivi mise à disposition. *
* data = données à associer à une phase d'actualisation. *
* count = nombre de gadgets trouvés. [OUT] *
* *
* Description : Etablit une liste de tous les gadgets présents. *
* *
* Retour : Liste de listes d'instructions, à libérer après usage. *
* *
* Remarques : - *
* *
******************************************************************************/
static rop_chain **list_all_gadgets_in_domain(const search_domain *domain, size_t index, unsigned int max_depth, update_search_progress_cb update, GObject *data, size_t *count)
{
rop_chain **result; /* Liste de listes à renvoyer */
phys_t done; /* Accumulation des quantités */
size_t i; /* Boucle de parcours #1 */
phys_t max; /* Borne d'un parcours */
phys_t k; /* Boucle de parcours #2 */
gdouble fraction; /* Progression générale */
vmpa2t ret; /* Emplacement du retour */
vmpa2t tmp; /* Copie de travail modifiable */
GArchInstruction *gadget; /* Nouveau gadget détecté */
phys_t diff; /* Volume de données traité */
mrange_t ins_range; /* Emplacement d'une instruct° */
vmpa2t end; /* Point d'arrivée obtenu */
rop_chain *chain; /* Nouvelle chaîne trouvée */
result = NULL;
*count = 0;
done = 0;
for (i = 0; i < domain->exe_count; i++)
{
max = get_mrange_length(&domain->exe_ranges[i]);
for (k = 0; k < max; k++)
{
/* Affichage de la progression */
fraction = 1.0 * (domain->runs_done * domain->sum + done + k);
fraction /= (domain->runs_count * domain->sum);
update(data, fraction);
/* Réception de la première / dernière instruction */
copy_vmpa(&ret, get_mrange_addr(&domain->exe_ranges[i]));
advance_vmpa(&ret, k);
copy_vmpa(&tmp, &ret);
gadget = disassemble_instruction_in_domain(domain, index, &tmp);
if (gadget == NULL) continue;
/* A-t-on bien affaire à une instruction de retour ? */
if (!(g_arch_instruction_get_flags(gadget) & AIF_RETURN_POINT))
{
g_object_unref(G_OBJECT(gadget));
continue;
}
/* Ne déborde-t-on pas dans une zone voisine ? */
diff = compute_vmpa_diff(&ret, &tmp);
init_mrange(&ins_range, &ret, diff);
compute_mrange_end_addr(&ins_range, &end);
if (!mrange_contains_mrange(&domain->exe_ranges[i], &ins_range))
{
g_object_unref(G_OBJECT(gadget));
continue;
}
/* Ajout d'un nouvel ensemble de gadgets */
g_arch_instruction_set_range(gadget, &ins_range);
chain = push_new_instruction(NULL, gadget);
look_backward_for_gadgets(domain, index, &domain->exe_ranges[i], &ret, max_depth, chain);
result = (rop_chain **)realloc(result, ++(*count) * sizeof(rop_chain *));
result[*count - 1] = chain;
}
done += max;
}
return result;
}
/******************************************************************************
* *
* Paramètres : binary = binaire dont le contenu est à traiter. *
* max_depth = profondeur maximale des recherches. *
* update = fonction de suivi mise à disposition. *
* data = données à associer à une phase d'actualisation. *
* count = nombre de gadgets trouvés. [OUT] *
* *
* Description : Etablit une liste de tous les gadgets présents. *
* *
* Retour : Liste de listes d'instructions, à libérer après usage. *
* *
* Remarques : - *
* *
******************************************************************************/
found_rop_list *list_all_gadgets(GExeFormat *format, unsigned int max_depth, update_search_progress_cb update, GObject *data, size_t *count)
{
found_rop_list *result; /* Liste de listes à renvoyer */
const char *target; /* Sous-traitance requise */
search_domain domain; /* Outils pour la recherche */
GBinPortion *portions; /* Couche première de portions */
char **names; /* Désignations humaines liées */
size_t i; /* Boucle de parcours */
/* Constitution du socle commun */
g_object_ref(G_OBJECT(format));
domain.format = format;
domain.content = g_known_format_get_content(G_KNOWN_FORMAT(format));
target = g_exe_format_get_target_machine(format);
domain.proc = get_arch_processor_for_key(target);
bool collect_x_ranges(GBinPortion *portion, GBinPortion *parent, BinaryPortionVisit visit, void *unused)
{
const mrange_t *range;
if (visit == BPV_SHOW)
{
if (g_binary_portion_get_rights(portion) & PAC_EXEC)
{
range = g_binary_portion_get_range(portion);
domain.exe_ranges = (mrange_t *)realloc(domain.exe_ranges, ++domain.exe_count * sizeof(mrange_t));
copy_mrange(&domain.exe_ranges[domain.exe_count - 1], range);
}
}
return true;
}
domain.exe_ranges = NULL;
domain.exe_count = 0;
portions = g_exe_format_get_portions(format);
g_binary_portion_visit(portions, (visit_portion_fc)collect_x_ranges, NULL);
g_object_unref(G_OBJECT(portions));
/* Récupération des différents contextes */
if (strcmp(target, "armv7") == 0)
{
domain.ops.list = list_rop_contexts_for_arm;
domain.ops.get = get_rop_contexts_for_arm;
domain.ops.setup = setup_instruction_sizes_for_arm;
}
else
{
domain.ops.list = list_rop_contexts_by_default;
domain.ops.get = get_rop_contexts_by_default;
domain.ops.setup = setup_instruction_sizes_by_default;
}
*count = domain.ops.list(&names);
/* Calcul de la surface totale à parcourir */
domain.sum = 0;
for (i = 0; i < domain.exe_count; i++)
domain.sum += get_mrange_length(&domain.exe_ranges[i]);
/* Détermination des différents parcours */
domain.runs_count = *count;
domain.runs_done = 0;
/* Parcours des différentes surfaces */
result = (found_rop_list *)calloc(*count, sizeof(found_rop_list));
for (i = 0; i < *count; i++)
{
result[i].category = names[i];
result[i].gadgets = list_all_gadgets_in_domain(&domain, i, max_depth, update, data, &result[i].count);
domain.runs_done++;
}
free(names);
free(domain.exe_ranges);
g_object_unref(G_OBJECT(domain.proc));
g_object_unref(G_OBJECT(domain.content));
g_object_unref(G_OBJECT(domain.format));
return result;
}
/******************************************************************************
* *
* Paramètres : list = ensemble de gadgets trouvés à supprimer. *
* *
* Description : Libère la mémoire des gadgets trouvés pour du ROP. *
* *
* Retour : *
* *
* Remarques : - *
* *
******************************************************************************/
void free_rop_list(found_rop_list *list)
{
size_t i; /* Boucle de parcours #1 */
rop_chain *chain; /* Accès direct à une chaîne */
size_t j; /* Boucle de parcours #2 */
for (i = 0; i < list->count; i++)
{
chain = list->gadgets[i];
for (j = 0; j < chain->count; j++)
g_object_unref(G_OBJECT(chain->instrs[j]));
free(chain->instrs);
free(chain);
}
free(list->gadgets);
free(list);
}