From 4c13ca820e4fa01ca62ad66c0665ebbee150f87c Mon Sep 17 00:00:00 2001 From: Cyrille Bagard Date: Mon, 25 Sep 2023 08:34:24 +0200 Subject: Handle private and global rules. --- src/analysis/scan/context-int.h | 2 + src/analysis/scan/context.c | 48 ++++++++++++++++++++++++ src/analysis/scan/context.h | 6 +++ src/analysis/scan/grammar.y | 34 ++++++++++++++--- src/analysis/scan/pattern.c | 10 ++--- src/analysis/scan/rule-int.h | 6 +++ src/analysis/scan/rule.c | 83 +++++++++++++++++++++++++++++++++++------ src/analysis/scan/rule.h | 15 +++++++- src/analysis/scan/scanner.c | 58 ++++++++++++++++++++++++---- src/analysis/scan/tokens.l | 3 ++ tests/analysis/scan/common.py | 2 + tests/analysis/scan/grammar.py | 66 ++++++++++++++++++++++++++++++++ 12 files changed, 301 insertions(+), 32 deletions(-) diff --git a/src/analysis/scan/context-int.h b/src/analysis/scan/context-int.h index 8a5fbaf..654ecca 100644 --- a/src/analysis/scan/context-int.h +++ b/src/analysis/scan/context-int.h @@ -83,6 +83,8 @@ struct _GScanContext full_match_tracker_t **full_trackers; /* Correspondances confirmées */ size_t full_count; /* Quantité de correspondances */ + bool global; /* Validation globale */ + rule_condition_t *conditions; /* Ensemble de règles suivies */ size_t cond_count; /* Quantité de ces conditions */ diff --git a/src/analysis/scan/context.c b/src/analysis/scan/context.c index c016f7e..8a9b600 100644 --- a/src/analysis/scan/context.c +++ b/src/analysis/scan/context.c @@ -246,6 +246,8 @@ static void g_scan_context_init(GScanContext *context) context->full_trackers = NULL; context->full_count = 0; + context->global = true; + context->conditions = NULL; context->cond_count = 0; @@ -751,6 +753,49 @@ bool g_scan_context_has_rule_for_name(const GScanContext *context, const char *n /****************************************************************************** * * * Paramètres : context = mémoire de résultats d'analyse à consulter. * +* * +* Description : Indique le bilan des règles globales. * +* * +* Retour : Bilan global des analyses menées. * +* * +* Remarques : - * +* * +******************************************************************************/ + +bool g_scan_context_has_global_match(const GScanContext *context) +{ + bool result; /* Bilan global à retourner */ + + result = context->global; + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : context = mémoire de résultats d'analyse à actualiser. * +* global = bilan global des analyses menées. * +* * +* Description : Définit le bilan des règles globales. * +* * +* Retour : - * +* * +* Remarques : - * +* * +******************************************************************************/ + +void g_scan_context_set_global_match(GScanContext *context, bool global) +{ + context->global = global; + +} + + +/****************************************************************************** +* * +* Paramètres : context = mémoire de résultats d'analyse à consulter. * * name = désignation de la règle ciblée. * * * * Description : Indique si une correspondance globale a pu être établie. * @@ -773,6 +818,9 @@ bool g_scan_context_has_match_for_rule(GScanContext *context, const char *name) result = false; + if (!context->global) + goto exit; + /* Recherche de la règle visée */ cond = NULL; diff --git a/src/analysis/scan/context.h b/src/analysis/scan/context.h index de8a213..7fb3dd4 100644 --- a/src/analysis/scan/context.h +++ b/src/analysis/scan/context.h @@ -101,6 +101,12 @@ bool g_scan_context_set_rule_condition(GScanContext *, const char *, GScanExpres /* Indique si un nom donné correspond à une règle. */ bool g_scan_context_has_rule_for_name(const GScanContext *, const char *); +/* Indique le bilan des règles globales. */ +bool g_scan_context_has_global_match(const GScanContext *); + +/* Définit le bilan des règles globales. */ +void g_scan_context_set_global_match(GScanContext *, bool); + /* Indique si une correspondance globale a pu être établie. */ bool g_scan_context_has_match_for_rule(GScanContext *, const char *); diff --git a/src/analysis/scan/grammar.y b/src/analysis/scan/grammar.y index 25bb536..e1f0e9e 100644 --- a/src/analysis/scan/grammar.y +++ b/src/analysis/scan/grammar.y @@ -73,11 +73,9 @@ typedef void *yyscan_t; sized_string_t *tmp_masks; /* Masques associés */ } masked; - + ScanRuleFlags rule_flags; /* Fanions pour règle */ GScanRule *rule; /* Nouvelle règle à intégrer */ - - GScanTokenNode *node; /* Bribe de motif à intégrer */ GSearchPattern *pattern; /* Nouveau motif à considérer */ @@ -140,6 +138,7 @@ YY_DECL; %token NOCASE "nocase" %token FULLWORD "fullword" %token PRIVATE "private" +%token GLOBAL "global" %token HEX_BYTES @@ -234,6 +233,8 @@ YY_DECL; %type UNSIGNED_INTEGER %type STRING +%type rule_flags +%type rule_flag %type rule %type PLAIN_TEXT @@ -345,14 +346,35 @@ YY_DECL; * Définition de règle. */ - rule : RAW_RULE RULE_NAME + rule : rule_flags RAW_RULE RULE_NAME { - *built_rule = g_scan_rule_new($2.data); + *built_rule = g_scan_rule_new($1, $3.data); $$ = *built_rule; } BRACE_IN meta bytes condition BRACE_OUT { - $$ = $3; + $$ = $4; + } + ; + + + rule_flags : /* empty */ + { + $$ = SRF_NONE; + } + | rule_flags rule_flag + { + $$ = $1 | $2; + } + ; + + rule_flag : "private" + { + $$ = SRF_PRIVATE; + } + | "global" + { + $$ = SRF_GLOBAL; } ; diff --git a/src/analysis/scan/pattern.c b/src/analysis/scan/pattern.c index 797f4ac..fe3babc 100644 --- a/src/analysis/scan/pattern.c +++ b/src/analysis/scan/pattern.c @@ -295,7 +295,7 @@ char *g_search_pattern_convert_as_text(const GSearchPattern *pattern, GScanConte * padding = éventuel bourrage initial à placer ou NULL. * * level = profondeur actuelle. * * fd = canal d'écriture. * -* trailing = impose une virgule finale ? * +* tail = décline la pose d'une virgule finale ? * * * * Description : Affiche un motif de recherche au format JSON. * * * @@ -305,7 +305,7 @@ char *g_search_pattern_convert_as_text(const GSearchPattern *pattern, GScanConte * * ******************************************************************************/ -void g_search_pattern_output_to_json(const GSearchPattern *pattern, GScanContext *context, const sized_string_t *padding, unsigned int level, int fd, bool trailing) +void g_search_pattern_output_to_json(const GSearchPattern *pattern, GScanContext *context, const sized_string_t *padding, unsigned int level, int fd, bool tail) { unsigned int i; /* Boucle de parcours */ GSearchPatternClass *class; /* Classe à activer */ @@ -339,10 +339,10 @@ void g_search_pattern_output_to_json(const GSearchPattern *pattern, GScanContext for (i = 0; i < level; i++) write(fd, padding->data, padding->len); - if (trailing) - write(fd, "},\n", 3); - else + if (tail) write(fd, "}\n", 2); + else + write(fd, "},\n", 3); } diff --git a/src/analysis/scan/rule-int.h b/src/analysis/scan/rule-int.h index b43cba9..1ca2b7f 100644 --- a/src/analysis/scan/rule-int.h +++ b/src/analysis/scan/rule-int.h @@ -37,6 +37,8 @@ struct _GScanRule { GObject parent; /* A laisser en premier */ + ScanRuleFlags flags; /* Propriétés de la règle */ + char *name; /* Désignation de la règle */ fnv64_t name_hash; /* Empreinte de la désignation */ @@ -56,5 +58,9 @@ struct _GScanRuleClass }; +/* Met en place une règle de détection statique avec motifs. */ +bool g_scan_rule_create(GScanRule *, ScanRuleFlags, const char *); + + #endif /* _ANALYSIS_SCAN_RULE_INT_H */ diff --git a/src/analysis/scan/rule.c b/src/analysis/scan/rule.c index a7d7765..68222dd 100644 --- a/src/analysis/scan/rule.c +++ b/src/analysis/scan/rule.c @@ -97,6 +97,8 @@ static void g_scan_rule_class_init(GScanRuleClass *klass) static void g_scan_rule_init(GScanRule *rule) { + rule->flags = SRF_NONE; + rule->name = NULL; rule->name_hash = 0; @@ -159,7 +161,8 @@ static void g_scan_rule_finalize(GScanRule *rule) /****************************************************************************** * * -* Paramètres : name = désignation à associer à la future règle. * +* 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. * * * @@ -169,12 +172,14 @@ static void g_scan_rule_finalize(GScanRule *rule) * * ******************************************************************************/ -GScanRule *g_scan_rule_new(const char *name) +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); @@ -185,7 +190,60 @@ GScanRule *g_scan_rule_new(const char *name) /****************************************************************************** * * -* Paramètres : rule = règle de détection à compléter. * +* 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. * @@ -202,7 +260,8 @@ const char *g_scan_rule_get_name(const GScanRule *rule, fnv64_t *hash) result = rule->name; - *hash = rule->name_hash; + if (hash != NULL) + *hash = rule->name_hash; return result; @@ -635,7 +694,7 @@ char *g_scan_rule_convert_as_text(const GScanRule *rule, GScanContext *context) * padding = éventuel bourrage initial à placer ou NULL. * * level = profondeur actuelle. * * fd = canal d'écriture. * -* trailing = impose une virgule finale ? * +* tail = décline la pose d'une virgule finale ? * * * * Description : Affiche une règle au format JSON. * * * @@ -645,10 +704,10 @@ char *g_scan_rule_convert_as_text(const GScanRule *rule, GScanContext *context) * * ******************************************************************************/ -void g_scan_rule_output_to_json(const GScanRule *rule, GScanContext *context, const sized_string_t *padding, unsigned int level, int fd, bool trailing) +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 */ - bool sub_trailing; /* Virgule finale */ + bool sub_tail; /* Saut de la virgule finale ? */ /* Introduction */ @@ -677,9 +736,9 @@ void g_scan_rule_output_to_json(const GScanRule *rule, GScanContext *context, co for (i = 0; i < rule->bytes_used; i++) { - sub_trailing = ((i + 1) < rule->bytes_used); + sub_tail = ((i + 1) == rule->bytes_used); - g_search_pattern_output_to_json(rule->bytes_locals[i], context, padding, level + 2, fd, sub_trailing); + g_search_pattern_output_to_json(rule->bytes_locals[i], context, padding, level + 2, fd, sub_tail); } @@ -707,10 +766,10 @@ void g_scan_rule_output_to_json(const GScanRule *rule, GScanContext *context, co for (i = 0; i < level; i++) write(fd, padding->data, padding->len); - if (trailing) - write(fd, "},\n", 3); - else + if (tail) write(fd, "}\n", 2); + else + write(fd, "},\n", 3); } diff --git a/src/analysis/scan/rule.h b/src/analysis/scan/rule.h index 6ab5105..3e6fe9d 100644 --- a/src/analysis/scan/rule.h +++ b/src/analysis/scan/rule.h @@ -53,11 +53,24 @@ typedef struct _GScanRule GScanRule; typedef struct _GScanRuleClass GScanRuleClass; +/* Particularités de règle à faire valoir */ +typedef enum _ScanRuleFlags +{ + SRF_NONE = (0 << 0), /* Absence de particularité */ + SRF_PRIVATE = (1 << 0), /* Règle silencieuse */ + SRF_GLOBAL = (1 << 1) /* Règle de base implicite */ + +} ScanRuleFlags; + + /* Indique le type défini pour une règle de détection par motifs. */ GType g_scan_rule_get_type(void); /* Crée une règle de détection statique à l'aide de motifs. */ -GScanRule *g_scan_rule_new(const char *); +GScanRule *g_scan_rule_new(ScanRuleFlags, const char *); + +/* Indique les particularités liées à une règle de détection. */ +ScanRuleFlags g_scan_rule_get_flags(const GScanRule *); /* Indique le nom associé à une règle de détection. */ const char *g_scan_rule_get_name(const GScanRule *, fnv64_t *); diff --git a/src/analysis/scan/scanner.c b/src/analysis/scan/scanner.c index 41fa0de..7b553e6 100644 --- a/src/analysis/scan/scanner.c +++ b/src/analysis/scan/scanner.c @@ -364,7 +364,7 @@ bool g_content_scanner_include_resource(GContentScanner *scanner, const char *pa if (!result) { - inc_name = g_scan_rule_get_name(included->rules[i], (fnv64_t []) { 0 }); + inc_name = g_scan_rule_get_name(included->rules[i], NULL); log_variadic_message(LMT_ERROR, "Can not import from '%s': rule '%s' already exists!", path, inc_name); @@ -459,7 +459,7 @@ bool g_content_scanner_add_rule(GContentScanner *scanner, GScanRule *rule) if (!result) { - inc_name = g_scan_rule_get_name(rule, (fnv64_t []) { 0 }); + inc_name = g_scan_rule_get_name(rule, NULL); log_variadic_message(LMT_ERROR, "Can not add rule: '%s' already exists!", inc_name); @@ -489,6 +489,11 @@ GScanContext *g_content_scanner_analyze(GContentScanner *scanner, GScanOptions * GScanContext *result; /* Bilan global à retourner */ bool status; /* Bilan d'opération locale */ size_t i; /* Boucle de parcours */ + bool global; /* Bilan des règles globales */ + GScanRule *rule; /* Règle à consulter */ + const char *name; /* Désignation de la règle */ + + /* Préparations... */ result = g_scan_context_new(options); @@ -510,6 +515,8 @@ GScanContext *g_content_scanner_analyze(GContentScanner *scanner, GScanOptions * } + /* Phase d'analyse */ + g_scan_context_set_content(result, content); g_engine_backend_run_scan(scanner->data_backend, result); @@ -519,6 +526,25 @@ GScanContext *g_content_scanner_analyze(GContentScanner *scanner, GScanOptions * for (i = 0; i < scanner->rule_count; i++) g_scan_rule_check(scanner->rules[i], scanner->data_backend, result); + /* Etablissement d'un bilan global */ + + global = true; + + for (i = 0; i < scanner->rule_count; i++) + { + rule = scanner->rules[i]; + + if ((g_scan_rule_get_flags(rule) & SRF_GLOBAL) == 0) + continue; + + name = g_scan_rule_get_name(rule, NULL); + + global = g_scan_context_has_match_for_rule(result, name); + + } + + g_scan_context_set_global_match(result, global); + exit: return result; @@ -645,29 +671,45 @@ char *g_content_scanner_convert_to_text(const GContentScanner *scanner, GScanCon void g_content_scanner_output_to_json(const GContentScanner *scanner, GScanContext *context, const sized_string_t *padding, unsigned int level, int fd) { - size_t i; /* Boucle de parcours */ - bool trailing; /* Virgule finale */ + size_t k; /* Boucle de parcours #1 */ + size_t i; /* Boucle de parcours #2 */ + size_t last_displayed; /* Dernier indice affiché */ + bool tail; /* Saut de la virgule finale ? */ /* Introduction */ - for (i = 0; i < level; i++) + for (k = 0; k < level; k++) write(fd, padding->data, padding->len); write(fd, "[\n", 2); /* Sous-traitance aux règles */ + for (i = scanner->rule_count; i > 0; i--) + if ((g_scan_rule_get_flags(scanner->rules[i - 1]) & SRF_PRIVATE) == 0) + break; + + if (i == 0) + goto nothing_to_display; + else + last_displayed = i - 1; + for (i = 0; i < scanner->rule_count; i++) { - trailing = ((i + 1) < scanner->rule_count); + if ((g_scan_rule_get_flags(scanner->rules[i]) & SRF_PRIVATE) == SRF_PRIVATE) + continue; - g_scan_rule_output_to_json(scanner->rules[i], context, padding, level + 1, fd, trailing); + tail = (i == last_displayed); + + g_scan_rule_output_to_json(scanner->rules[i], context, padding, level + 1, fd, tail); } /* Conclusion */ - for (i = 0; i < level; i++) + nothing_to_display: + + for (k = 0; k < level; k++) write(fd, padding->data, padding->len); write(fd, "]\n", 2); diff --git a/src/analysis/scan/tokens.l b/src/analysis/scan/tokens.l index 241c973..1a17344 100644 --- a/src/analysis/scan/tokens.l +++ b/src/analysis/scan/tokens.l @@ -403,6 +403,9 @@ bytes_fuzzy_id [\*A-Za-z_][\*A-Za-z0-9_]* %{ /* Définition locale d'une règle */ %} + "global" { return GLOBAL; } + "private" { return PRIVATE; } + "rule" { PUSH_STATE(rule_intro); return RAW_RULE; diff --git a/tests/analysis/scan/common.py b/tests/analysis/scan/common.py index 3b52e38..507b7e2 100644 --- a/tests/analysis/scan/common.py +++ b/tests/analysis/scan/common.py @@ -33,6 +33,8 @@ class RostTestClass(ChrysalideTestCase): else: self.assertFalse(ctx.has_match_for_rule('test')) + return scanner, ctx + def check_rule_success(self, rule, content = None): """Check for scan success.""" diff --git a/tests/analysis/scan/grammar.py b/tests/analysis/scan/grammar.py index 8b18f81..13a255b 100644 --- a/tests/analysis/scan/grammar.py +++ b/tests/analysis/scan/grammar.py @@ -1,4 +1,6 @@ +import json + from common import RostTestClass @@ -181,7 +183,71 @@ rule test { self.check_rule_success(rule) + def testPrivateRules(self): + """Ensure private rules remain silent.""" + + for private in [ True, False ]: + for state in [ True, False ]: + + rule = ''' +%srule silent { + + condition: + %s + +} + +rule test { + + condition: + silent + +} +''' % ('private ' if private else '', 'true' if state else 'false') + + scanner, ctx = self._validate_rule_result(rule, self._empty_content, state) + + data = scanner.convert_to_json(ctx) + jdata = json.loads(data) + + # Exemple : + # + # [{'bytes_patterns': [], 'matched': True, 'name': 'test'}, + # {'bytes_patterns': [], 'matched': True, 'name': 'silent'}] + + found = len([ j['name'] for j in jdata if j['name'] == 'silent' ]) > 0 + + self.assertTrue(private ^ found) + + + def testGlobalRules(self): + """Take global rules into account.""" + + for glob_state in [ True, False ]: + for state in [ True, False ]: + + rule = ''' +%srule silent { + + condition: + %s + +} + +rule test { + + condition: + true + +} +''' % ('global ' if glob_state else '', 'true' if state else 'false') + + expected = not(glob_state) or state + if expected: + self.check_rule_success(rule) + else: + self.check_rule_failure(rule) -- cgit v0.11.2-87-g4458