/* Chrysalide - Outil d'analyse de fichiers binaires
* rule.c - parcours de contenus à la recherche de motifs
*
* Copyright (C) 2022 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 Foobar. If not, see .
*/
#include "rule.h"
#include
#include
#include
#include
#include
#include
#include
#include "rule-int.h"
#include "matches/bytes.h"
#include "patterns/token.h"
#include "../../common/extstr.h"
#include "../../core/logs.h"
/* Initialise la classe des règles de détection statique. */
static void g_scan_rule_class_init(GScanRuleClass *);
/* Initialise une instance de règle de détection statique. */
static void g_scan_rule_init(GScanRule *);
/* Supprime toutes les références externes. */
static void g_scan_rule_dispose(GScanRule *);
/* Procède à la libération totale de la mémoire. */
static void g_scan_rule_finalize(GScanRule *);
/* Indique le type défini pour une règle de détection par motifs. */
G_DEFINE_TYPE(GScanRule, g_scan_rule, G_TYPE_OBJECT);
/******************************************************************************
* *
* Paramètres : klass = classe à initialiser. *
* *
* Description : Initialise la classe des règles de détection statique. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void g_scan_rule_class_init(GScanRuleClass *klass)
{
GObjectClass *object; /* Autre version de la classe */
object = G_OBJECT_CLASS(klass);
object->dispose = (GObjectFinalizeFunc/* ! */)g_scan_rule_dispose;
object->finalize = (GObjectFinalizeFunc)g_scan_rule_finalize;
}
/******************************************************************************
* *
* Paramètres : rule = instance à initialiser. *
* *
* Description : Initialise une instance de règle de détection statique. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void g_scan_rule_init(GScanRule *rule)
{
rule->flags = SRF_NONE;
rule->name = NULL;
rule->name_hash = 0;
rule->tags = NULL;
rule->tags_count = 0;
rule->bytes_locals = NULL;
rule->bytes_allocated = 0;
rule->bytes_used = 0;
rule->condition = NULL;
}
/******************************************************************************
* *
* Paramètres : rule = instance d'objet GLib à traiter. *
* *
* Description : Supprime toutes les références externes. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void g_scan_rule_dispose(GScanRule *rule)
{
size_t i; /* Boucle de parcours */
for (i = 0; i < rule->bytes_used; i++)
g_clear_object(&rule->bytes_locals[i]);
g_clear_object(&rule->condition);
G_OBJECT_CLASS(g_scan_rule_parent_class)->dispose(G_OBJECT(rule));
}
/******************************************************************************
* *
* Paramètres : rule = instance d'objet GLib à traiter. *
* *
* Description : Procède à la libération totale de la mémoire. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void g_scan_rule_finalize(GScanRule *rule)
{
size_t i; /* Boucle de parcours */
if (rule->name != NULL)
free(rule->name);
for (i = 0; i < rule->tags_count; i++)
free(rule->tags[i]);
if (rule->tags != NULL)
free(rule->tags);
if (rule->bytes_locals != NULL)
free(rule->bytes_locals);
G_OBJECT_CLASS(g_scan_rule_parent_class)->finalize(G_OBJECT(rule));
}
/******************************************************************************
* *
* Paramètres : flags = propriétés particulières à conférer à la règle. *
* name = désignation à associer à la future règle. *
* *
* Description : Crée une règle de détection statique à l'aide de motifs. *
* *
* Retour : Règle de détection mise en place. *
* *
* Remarques : - *
* *
******************************************************************************/
GScanRule *g_scan_rule_new(ScanRuleFlags flags, const char *name)
{
GScanRule *result; /* Structure à retourner */
result = g_object_new(G_TYPE_SCAN_RULE, NULL);
result->flags = flags;
result->name = strdup(name);
result->name_hash = fnv_64a_hash(name);
return result;
}
/******************************************************************************
* *
* Paramètres : rule = règle de détection à initialiser pleinement. *
* flags = propriétés particulières à conférer à la règle. *
* name = désignation à associer à la future règle. *
* *
* Description : Met en place une règle de détection statique avec motifs. *
* *
* Retour : Bilan de l'opération. *
* *
* Remarques : - *
* *
******************************************************************************/
bool g_scan_rule_create(GScanRule *rule, ScanRuleFlags flags, const char *name)
{
GScanRule *result; /* Structure à retourner */
result = g_object_new(G_TYPE_SCAN_RULE, NULL);
result->flags = flags;
result->name = strdup(name);
result->name_hash = fnv_64a_hash(name);
return result;
}
/******************************************************************************
* *
* Paramètres : rule = règle de détection à consulter. *
* *
* Description : Indique les particularités liées à une règle de détection. *
* *
* Retour : Propriétés particulières attachées à la règle. *
* *
* Remarques : - *
* *
******************************************************************************/
ScanRuleFlags g_scan_rule_get_flags(const GScanRule *rule)
{
ScanRuleFlags result; /* Fanions à retourner */
result = rule->flags;
return result;
}
/******************************************************************************
* *
* Paramètres : rule = règle de détection à consulter. *
* hash = empreinte précalculée associée au nom. [OUT] *
* *
* Description : Indique le nom associé à une règle de détection. *
* *
* Retour : Désignation humaine associée à la règle. *
* *
* Remarques : - *
* *
******************************************************************************/
const char *g_scan_rule_get_name(const GScanRule *rule, fnv64_t *hash)
{
const char *result; /* Désignation à retourner */
result = rule->name;
if (hash != NULL)
*hash = rule->name_hash;
return result;
}
/******************************************************************************
* *
* Paramètres : rule = règle de détection à compléter. *
* tag = étiquette à associer à la règle. *
* *
* Description : Lie une règle à une nouvelle étiquette. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
void g_scan_rule_add_tag(GScanRule *rule, const char *tag)
{
rule->tags = realloc(rule->tags, ++rule->tags_count * sizeof(char *));
rule->tags[rule->tags_count - 1] = strdup(tag);
}
/******************************************************************************
* *
* Paramètres : rule = règle de détection à consulter. *
* count = quantité d'éléments retournés. [OUT] *
* *
* Description : Indique les éventuelles étiquettes associées à une règle. *
* *
* Retour : Liste d'étiquettes associées à la règle consultée. *
* *
* Remarques : - *
* *
******************************************************************************/
const char * const *g_scan_rule_list_tags(const GScanRule *rule, size_t *count)
{
const char * const *result; /* Liste à retourner */
result = rule->tags;
*count = rule->tags_count;
return result;
}
/******************************************************************************
* *
* Paramètres : rule = règle de détection à compléter. *
* pattern = nouveau motif de détection. *
* *
* Description : Intègre une nouvelle variable locale à une règle. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
void g_scan_rule_add_local_variable(GScanRule *rule, GSearchPattern *pattern)
{
if (G_IS_BYTES_TOKEN(pattern))
{
if (rule->bytes_used == rule->bytes_allocated)
{
rule->bytes_allocated += PATTERN_ALLOC_SIZE;
rule->bytes_locals = realloc(rule->bytes_locals, rule->bytes_allocated * sizeof(GSearchPattern *));
}
rule->bytes_locals[rule->bytes_used++] = pattern;
g_object_ref(G_OBJECT(pattern));
}
}
/******************************************************************************
* *
* Paramètres : rule = règle de détection à consulter. *
* target = nom d'une variable locale à retrouver. *
* *
* Description : Fournit une variable locale à une règle selon un nom. *
* *
* Retour : Motif de détection retrouvé ou NULL en cas d'échec. *
* *
* Remarques : La propriétée de l'instance renvoyée est partagée ! *
* *
******************************************************************************/
const GSearchPattern *g_scan_rule_get_local_variable(GScanRule *rule, const char *target)
{
const GSearchPattern *result; /* Variable à retourner */
size_t i; /* Boucle de parcours */
const char *name; /* Désignation d'un motif */
result = NULL;
for (i = 0; i < rule->bytes_used; i++)
{
name = g_search_pattern_get_name(rule->bytes_locals[i]);
if (strcmp(name, target) == 0)
{
result = rule->bytes_locals[i];
break;
}
}
return result;
}
/******************************************************************************
* *
* Paramètres : rule = règle de détection à consulter. *
* target = nom d'une variable locale à retrouver. *
* count = quantité de motifs renvoyés. [OUT] *
* *
* Description : Fournit une liste de variables locales à partir d'un nom. *
* *
* Retour : Motifs de détection retrouvés ou NULL en cas d'échec. *
* *
* Remarques : La propriétée des instances renvoyées est partagée ! *
* *
******************************************************************************/
const GSearchPattern **g_scan_rule_get_local_variables(GScanRule *rule, const char *target, size_t *count)
{
const GSearchPattern **result; /* Variables à retourner */
size_t target_len; /* Nbre de caractères à évaluer*/
size_t len_without_star; /* Taille sans masque */
bool need_regex; /* Traitement complexe requis */
size_t i; /* Boucle de parcours */
char *regex; /* Définition complète */
regex_t preg; /* Expression compilée */
int ret; /* Bilan d'un appel */
const char *name; /* Désignation d'un motif */
result = NULL;
*count = 0;
/* Premier cas de figure : la liste complète est attendue */
if (target == NULL)
{
need_all_of_them:
*count = rule->bytes_used;
result = malloc(*count * sizeof(GSearchPattern *));
memcpy(result, rule->bytes_locals, *count);
}
/* Second cas de figure : identification au cas par cas */
else
{
target_len = strlen(target);
len_without_star = 0;
need_regex = false;
for (i = 0; i < target_len; i++)
if (target[i] == '*')
break;
else
len_without_star++;
for (i++; i < target_len; i++)
if (target[i] != '*')
{
need_regex = true;
goto try_harder;
}
if (len_without_star == 0)
goto need_all_of_them;
result = malloc(rule->bytes_used * sizeof(GSearchPattern *));
for (i = 0; i < rule->bytes_used; i++)
{
name = g_search_pattern_get_name(rule->bytes_locals[i]);
if (strncmp(name, target, len_without_star) == 0)
{
result[*count] = rule->bytes_locals[i];
(*count)++;
}
}
if (*count == 0)
{
free(result);
result = NULL;
}
}
try_harder:
/* Dernier cas de figure : une expression régulière est vraisemblablement de mise */
if (need_regex)
{
regex = strdup(target);
regex = strrpl(regex, "*", ".*");
regex = strprep(regex, "^");
regex = stradd(regex, "$");
ret = regcomp(&preg, regex, REG_NOSUB);
if (ret != 0)
{
LOG_ERROR_REGCOMP(&preg, ret);
goto done;
}
result = malloc(rule->bytes_used * sizeof(GSearchPattern *));
for (i = 0; i < rule->bytes_used; i++)
{
name = g_search_pattern_get_name(rule->bytes_locals[i]);
ret = regexec(&preg, name, 0, NULL, 0);
if (ret != REG_NOMATCH)
{
result[*count] = rule->bytes_locals[i];
(*count)++;
}
}
if (*count == 0)
{
free(result);
result = NULL;
}
regfree(&preg);
done:
free(regex);
}
return result;
}
/******************************************************************************
* *
* Paramètres : rule = règle de détection à compléter. *
* expr = expression de condition à satisfaire. *
* *
* Description : Définit l'expression d'une correspondance recherchée. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
void g_scan_rule_set_match_condition(GScanRule *rule, GScanExpression *expr)
{
rule->condition = expr;
g_object_ref(G_OBJECT(expr));
}
/******************************************************************************
* *
* Paramètres : rule = règle de détection à considérer. *
* backend = moteur d'analyse pour données brutes. *
* context = contexte de l'analyse à mener. *
* *
* Description : Prépare le suivi de recherche de motifs pour une règle. *
* *
* Retour : Bilan de l'opération à renvoyer. *
* *
* Remarques : - *
* *
******************************************************************************/
bool g_scan_rule_setup_backend(GScanRule *rule, GEngineBackend *backend, GScanContext *context)
{
bool result; /* Statut à retourner */
size_t maxsize; /* Taille maximale des atomes */
GSearchPattern *pattern; /* Motif à intégrer */
size_t i; /* Boucle de parcours */
/* Suivi des conditions de correspondance */
result = g_scan_context_set_rule_condition(context, rule->name, rule->condition);
if (!result) goto exit;
/* Programmation des motifs recherchés */
maxsize = g_engine_backend_get_atom_max_size(backend);
for (i = 0; i < rule->bytes_used && result; i++)
{
pattern = rule->bytes_locals[i];
result = g_bytes_token_enroll(G_BYTES_TOKEN(pattern), backend, maxsize);
}
exit:
return result;
}
/******************************************************************************
* *
* Paramètres : rule = règle de détection à considérer. *
* backend = moteur d'analyse pour données brutes. *
* *
* Description : Récupère les identifiants finaux pour les motifs recherchés. *
* *
* Retour : Bilan de l'opération à renvoyer. *
* *
* Remarques : - *
* *
******************************************************************************/
bool g_scan_rule_define_pattern_ids(GScanRule *rule, GEngineBackend *backend)
{
bool result; /* Statut à retourner */
size_t i; /* Boucle de parcours */
GSearchPattern *pattern; /* Motif à intégrer */
result = true;
for (i = 0; i < rule->bytes_used && result; i++)
{
pattern = rule->bytes_locals[i];
result = g_bytes_token_build_id(G_BYTES_TOKEN(pattern), backend);
}
return result;
}
/******************************************************************************
* *
* Paramètres : rule = règle de détection à considérer. *
* backend = moteur d'analyse pour données brutes. *
* context = contexte de l'analyse à mener. *
* *
* Description : Lance une analyse d'un contenu binaire selon une règle. *
* *
* Retour : Contexte de suivi pour l'analyse menée. *
* *
* Remarques : - *
* *
******************************************************************************/
void g_scan_rule_check(GScanRule *rule, GEngineBackend *backend, GScanContext *context)
{
scan_node_check_params_t params; /* Rassemblement de paramètres */
vmpa2t start; /* Point de début du contenu */
vmpa2t end; /* Point de fin du contenu */
size_t i; /* Boucle de parcours */
GSearchPattern *pattern; /* Motif à intégrer */
GScanMatches *matches; /* Correspondances établies */
/* Définition d'un contexte */
params.context = context;
params.content = g_scan_context_get_content(context);
params.allocator = g_umem_slice_new(sizeof(match_area_t));
g_binary_content_compute_start_pos(params.content, &start);
g_binary_content_compute_end_pos(params.content, &end);
params.content_start = start.physical;
params.content_end = end.physical;
/* Vérifications */
for (i = 0; i < rule->bytes_used; i++)
{
pattern = rule->bytes_locals[i];
matches = g_scan_bytes_matches_new();
g_bytes_token_check(G_BYTES_TOKEN(pattern), G_SCAN_BYTES_MATCHES(matches), ¶ms);
g_scan_context_register_full_matches(context, pattern, matches);
g_object_unref(G_OBJECT(matches));
}
g_object_unref(G_OBJECT(params.content));
//g_object_unref(G_OBJECT(params.allocator));
}
/******************************************************************************
* *
* Paramètres : rule = règle de détection à considérer. *
* context = contexte de l'analyse à mener. *
* full = force un affichage complet des résultats. *
* fd = canal d'écriture. *
* *
* Description : Affiche une règle au format texte. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
void g_scan_rule_output_to_text(const GScanRule *rule, GScanContext *context, bool full, int fd)
{
GScanOptions *options; /* Options de l'utilisateur */
bool selected; /* Affichage attendu ? */
size_t i; /* Boucle de parcours */
GBinContent *content; /* Contenu binaire scanné */
char *desc; /* Description de ce contenu */
/**
* Si la règle n'a pas fait mouche, rien n'est imprimé.
*/
if (!g_scan_context_has_match_for_rule(context, rule->name))
return;
options = g_scan_context_get_options(context);
selected = g_scan_options_has_tag_as_selected(options, NULL);
/**
* Si la règle comporte des étiquettes et que l'utilisateur en a spécifié
* également.
*/
if (rule->tags_count > 0 && !selected)
{
for (i = 0; i < rule->tags_count && !selected; i++)
selected = g_scan_options_has_tag_as_selected(options, rule->tags[i]);
}
if (selected)
{
write(fd, rule->name, strlen(rule->name));
if (g_scan_options_get_print_tags(options))
{
write(fd, " [", 2);
for (i = 0; i < rule->tags_count; i++)
{
if (i > 0)
write(fd, ",", 1);
write(fd, rule->tags[i], strlen(rule->tags[i]));
}
write(fd, "]", 1);
}
write(fd, " ", 1);
content = g_scan_context_get_content(context);
desc = g_binary_content_describe(content, true);
write(fd, desc, strlen(desc));
write(fd, "\n", 1);
free(desc);
g_object_unref(G_OBJECT(content));
if (full)
for (i = 0; i < rule->bytes_used; i++)
g_search_pattern_output_to_text(rule->bytes_locals[i], context, fd);
}
g_object_unref(G_OBJECT(options));
}
/******************************************************************************
* *
* Paramètres : rule = règle de détection à considérer. *
* context = contexte de l'analyse à mener. *
* *
* Description : Convertit une règle en texte. *
* *
* Retour : Données textuelles ou NULL en cas d'erreur. *
* *
* Remarques : - *
* *
******************************************************************************/
char *g_scan_rule_convert_as_text(const GScanRule *rule, GScanContext *context)
{
char *result; /* Données à retourner */
char *name; /* Nom "unique" pour le canal */
int ret; /* Bilan de création de nom */
int fd; /* Canal d'écriture */
struct stat info; /* Infos. incluant une taille */
ssize_t got; /* Données effectivement relues*/
static unsigned long long counter = 0;
result = NULL;
ret = asprintf(&name, "rost-rule2text-%llu", counter++);
if (ret == -1) goto exit;
fd = memfd_create(name, MFD_CLOEXEC);
if (fd == -1)
{
LOG_ERROR_N("memfd_create");
goto exit_with_name;
}
g_scan_rule_output_to_text(rule, context, true, fd);
ret = fstat(fd, &info);
if (ret != 0)
{
LOG_ERROR_N("fstat");
goto exit_with_name_and_fd;
}
result = malloc((info.st_size + 1) * sizeof(char));
lseek(fd, SEEK_SET, 0);
got = read(fd, result, info.st_size);
if (got != info.st_size)
{
LOG_ERROR_N("read");
free(result);
goto exit_with_name_and_fd;
}
result[info.st_size] = '\0';
exit_with_name_and_fd:
close(fd);
exit_with_name:
free(name);
exit:
return result;
}
/******************************************************************************
* *
* Paramètres : rule = règle de détection à considérer. *
* context = contexte de l'analyse à mener. *
* padding = éventuel bourrage initial à placer ou NULL. *
* level = profondeur actuelle. *
* fd = canal d'écriture. *
* tail = décline la pose d'une virgule finale ? *
* *
* Description : Affiche une règle au format JSON. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
void g_scan_rule_output_to_json(const GScanRule *rule, GScanContext *context, const sized_string_t *padding, unsigned int level, int fd, bool tail)
{
size_t i; /* Boucle de parcours #1 */
bool sub_tail; /* Saut de la virgule finale ? */
size_t k; /* Boucle de parcours #2 */
GBinContent *content; /* Contenu binaire scanné */
char *desc; /* Description de ce contenu */
/* Introduction */
for (i = 0; i < level; i++)
write(fd, padding->data, padding->len);
write(fd, "{\n", 2);
/* Désignation de la règle */
for (i = 0; i < (level + 1); i++)
write(fd, padding->data, padding->len);
write(fd, "\"name\": \"", 9);
write(fd, rule->name, strlen(rule->name));
write(fd, "\",\n", 3);
/* Etiquettes ? */
for (i = 0; i < (level + 1); i++)
write(fd, padding->data, padding->len);
write(fd, "\"tags\": [", 9);
if (rule->tags_count > 0)
{
write(fd, "\n", 1);
for (k = 0; k < rule->tags_count; k++)
{
for (i = 0; i < (level + 2); i++)
write(fd, padding->data, padding->len);
write(fd, "\"", 1);
write(fd, rule->tags[k], strlen(rule->tags[k]));
write(fd, "\"", 1);
if ((k + 1) < rule->tags_count)
write(fd, ",", 1);
write(fd, "\n", 1);
}
for (i = 0; i < (level + 1); i++)
write(fd, padding->data, padding->len);
}
write(fd, "],\n", 3);
/* Cible du scan */
for (i = 0; i < (level + 1); i++)
write(fd, padding->data, padding->len);
write(fd, "\"target\": \"", 11);
content = g_scan_context_get_content(context);
desc = g_binary_content_describe(content, true);
write(fd, desc, strlen(desc));
free(desc);
g_object_unref(G_OBJECT(content));
write(fd, "\",\n", 3);
/* Affichage des correspondances d'octets */
for (i = 0; i < (level + 1); i++)
write(fd, padding->data, padding->len);
write(fd, "\"bytes_patterns\": [\n", 20);
for (i = 0; i < rule->bytes_used; i++)
{
sub_tail = ((i + 1) == rule->bytes_used);
g_search_pattern_output_to_json(rule->bytes_locals[i], context, padding, level + 2, fd, sub_tail);
}
for (i = 0; i < (level + 1); i++)
write(fd, padding->data, padding->len);
write(fd, "],\n", 3);
/* Bilan du filtrage */
for (i = 0; i < (level + 1); i++)
write(fd, padding->data, padding->len);
write(fd, "\"matched\": ", 11);
if (g_scan_context_has_match_for_rule(context, rule->name))
write(fd, "true", 4);
else
write(fd, "false", 5);
write(fd, "\n", 1);
/* Conclusion */
for (i = 0; i < level; i++)
write(fd, padding->data, padding->len);
if (tail)
write(fd, "}\n", 2);
else
write(fd, "},\n", 3);
}
/******************************************************************************
* *
* Paramètres : rule = règle de détection à considérer. *
* context = contexte de l'analyse à mener. *
* *
* Description : Convertit une règle en JSON. *
* *
* Retour : Données textuelles au format JSON ou NULL en cas d'erreur. *
* *
* Remarques : - *
* *
******************************************************************************/
char *g_scan_rule_convert_as_json(const GScanRule *rule, GScanContext *context)
{
char *result; /* Données à retourner */
char *name; /* Nom "unique" pour le canal */
int ret; /* Bilan de création de nom */
int fd; /* Canal d'écriture */
sized_string_t padding; /* Bourrage pour le JSON */
struct stat info; /* Infos. incluant une taille */
ssize_t got; /* Données effectivement relues*/
static unsigned long long counter = 0;
result = NULL;
ret = asprintf(&name, "rost-rule2json-%llu", counter++);
if (ret == -1) goto exit;
fd = memfd_create(name, MFD_CLOEXEC);
if (fd == -1)
{
LOG_ERROR_N("memfd_create");
goto exit_with_name;
}
padding.data = " ";
padding.len = 3;
g_scan_rule_output_to_json(rule, context, &padding, 0, fd, false);
ret = fstat(fd, &info);
if (ret != 0)
{
LOG_ERROR_N("fstat");
goto exit_with_name_and_fd;
}
result = malloc((info.st_size + 1) * sizeof(char));
lseek(fd, SEEK_SET, 0);
got = read(fd, result, info.st_size);
if (got != info.st_size)
{
LOG_ERROR_N("read");
free(result);
goto exit_with_name_and_fd;
}
result[info.st_size] = '\0';
exit_with_name_and_fd:
close(fd);
exit_with_name:
free(name);
exit:
return result;
}