/* Chrysalide - Outil d'analyse de fichiers binaires * hyperscan.c - méthode de recherche basée sur la bibliothèque Hyperscan d'Intel * * Copyright (C) 2024 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 "hyperscan.h" #include #include #include #include "hyperscan-int.h" #include "../../../../core/logs.h" /* ---------------------- IMPLANTATION D'UNE NOUVELLE APPROCHE ---------------------- */ /* Initialise la classe des méthodes basée sur Hyperscan. */ static void g_hyperscan_backend_class_init(GHyperscanBackendClass *); /* Initialise une instance de méthodes basée sur Hyperscan. */ static void g_hyperscan_backend_init(GHyperscanBackend *); /* Supprime toutes les références externes. */ static void g_hyperscan_backend_dispose(GHyperscanBackend *); /* Procède à la libération totale de la mémoire. */ static void g_hyperscan_backend_finalize(GHyperscanBackend *); /* --------------------- IMPLEMENTATION DES FONCTIONS DE CLASSE --------------------- */ /* Indique la taille maximale des suites d'octets recherchées. */ size_t g_hyperscan_backend_get_atom_max_size(const GHyperscanBackend *); /* Inscrit dans le moteur une chaîne de caractères à rechercher. */ static bool g_hyperscan_backend_enroll_plain_pattern(GHyperscanBackend *, const uint8_t *, size_t, uint32_t [2]); /* Met en ordre les derniers détails avant un premier scan. */ static bool g_hyperscan_backend_warm_up(GHyperscanBackend *); /* Récupère les identifiants finaux pour un motif recherché. */ static patid_t g_hyperscan_backend_build_plain_pattern_id(const GHyperscanBackend *, const uint32_t [2]); /* Détermine le nombre d'identifiants constitués. */ static size_t g_hyperscan_backend_count_plain_pattern_ids(const GHyperscanBackend *); /* Informations utiles au traitement d'un événement */ typedef struct _hyperscan_context_t { const size_t *lengths; /* Nombre d'octets considérés */ const uint32_t *coverages; /* Départ et quantité de suivis*/ #ifndef NDEBUG size_t used; /* Nombre d'éléments utiles */ const unsigned int *lit_ids; /* Identifiants internes */ #endif GUMemSlice **matches; /* Zones d'enregistrements */ } hyperscan_context_t; /* Prend note d'une correspondance trouvée par Hyperscan. */ static int handle_hyperscan_match_event(unsigned int, unsigned long long, unsigned long long, unsigned int, const hyperscan_context_t *); /* Parcours un contenu binaire à la recherche de motifs. */ static void g_hyperscan_backend_run_scan(const GHyperscanBackend *, GScanContext *); /* Imprime quelques faits quant aux éléments mis en place. */ static void g_hyperscan_backend_output_stats(const GHyperscanBackend *); /* ---------------------------------------------------------------------------------- */ /* IMPLANTATION D'UNE NOUVELLE APPROCHE */ /* ---------------------------------------------------------------------------------- */ /* Indique le type défini pour un moteur de recherche pour données. */ G_DEFINE_TYPE(GHyperscanBackend, g_hyperscan_backend, G_TYPE_ENGINE_BACKEND); /****************************************************************************** * * * Paramètres : klass = classe à initialiser. * * * * Description : Initialise la classe des méthodes basée sur Hyperscan. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_hyperscan_backend_class_init(GHyperscanBackendClass *klass) { GObjectClass *object; /* Autre version de la classe */ GEngineBackendClass *backend; /* Version de classe parente */ object = G_OBJECT_CLASS(klass); object->dispose = (GObjectFinalizeFunc/* ! */)g_hyperscan_backend_dispose; object->finalize = (GObjectFinalizeFunc)g_hyperscan_backend_finalize; backend = G_ENGINE_BACKEND_CLASS(klass); backend->get_max_size = (get_backend_atom_max_size_fc)g_hyperscan_backend_get_atom_max_size; backend->enroll_plain = (enroll_plain_into_backend_fc)g_hyperscan_backend_enroll_plain_pattern; backend->warm_up = (warm_up_backend_fc)g_hyperscan_backend_warm_up; backend->build_id = (build_backend_plain_pattern_id_fc)g_hyperscan_backend_build_plain_pattern_id; backend->count_ids = (count_backend_plain_pattern_ids_fc)g_hyperscan_backend_count_plain_pattern_ids; backend->run_scan = (run_backend_scan_fc)g_hyperscan_backend_run_scan; backend->output = (output_backend_stats_fc)g_hyperscan_backend_output_stats; } /****************************************************************************** * * * Paramètres : backend = instance à initialiser. * * * * Description : Initialise une instance de méthodes basée sur Hyperscan. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_hyperscan_backend_init(GHyperscanBackend *backend) { backend->atoms = NULL; backend->lengths = NULL; backend->coverages = NULL; backend->allocated = 0; backend->used = 0; backend->database = NULL; backend->scratch = NULL; } /****************************************************************************** * * * Paramètres : backend = instance d'objet GLib à traiter. * * * * Description : Supprime toutes les références externes. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_hyperscan_backend_dispose(GHyperscanBackend *backend) { G_OBJECT_CLASS(g_hyperscan_backend_parent_class)->dispose(G_OBJECT(backend)); } /****************************************************************************** * * * Paramètres : backend = instance d'objet GLib à traiter. * * * * Description : Procède à la libération totale de la mémoire. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_hyperscan_backend_finalize(GHyperscanBackend *backend) { if (backend->atoms != NULL) free(backend->atoms); if (backend->lengths != NULL) free(backend->lengths); if (backend->coverages != NULL) free(backend->coverages); if (backend->scratch != NULL) hs_free_scratch(backend->scratch); if (backend->database != NULL) hs_free_database(backend->database); G_OBJECT_CLASS(g_hyperscan_backend_parent_class)->finalize(G_OBJECT(backend)); } /****************************************************************************** * * * Paramètres : - * * * * Description : Crée une méthode de recherche avec la bibliothèque Hyperscan.* * * * Retour : Méthode mise en place. * * * * Remarques : - * * * ******************************************************************************/ GEngineBackend *g_hyperscan_backend_new(void) { GHyperscanBackend *result; /* Structure à retourner */ result = g_object_new(G_TYPE_HYPERSCAN_BACKEND, NULL); return G_ENGINE_BACKEND(result); } /* ---------------------------------------------------------------------------------- */ /* IMPLEMENTATION DES FONCTIONS DE CLASSE */ /* ---------------------------------------------------------------------------------- */ /****************************************************************************** * * * Paramètres : backend = moteur de recherche à consulter. * * * * Description : Indique la taille maximale des suites d'octets recherchées. * * * * Retour : Valeur strictement positive. * * * * Remarques : - * * * ******************************************************************************/ size_t g_hyperscan_backend_get_atom_max_size(const GHyperscanBackend *backend) { size_t result; /* Taille à faire connaître */ result = ~0; return result; } /****************************************************************************** * * * Paramètres : backend = moteur de recherche à manipuler. * * plain = chaîne de caractères classique à intégrer. * * len = taille de cette chaîne. * * tmp_id = identifiants temporaires vers le motif. [OUT] * * * * Description : Inscrit dans le moteur une chaîne de caractères à rechercher.* * * * Retour : Bilan de l'opération. * * * * Remarques : - * * * ******************************************************************************/ static bool g_hyperscan_backend_enroll_plain_pattern(GHyperscanBackend *backend, const uint8_t *plain, size_t len, uint32_t tmp_id[2]) { bool result; /* Bilan à retourner */ size_t i; /* Boucle de parcours */ int ret; /* Bilan d'une comparaison */ result = true; /*Recherche d'un motif déjà sollicité */ for (i = 0; i < backend->used; i++) { if (backend->lengths[i] != len) continue; ret = memcmp(backend->atoms[i], plain, backend->lengths[i]); if (ret == 0) { tmp_id[0] = i; tmp_id[1] = backend->coverages[i * 2 + EXPR_COVERAGE_COUNT]; backend->coverages[i * 2 + EXPR_COVERAGE_COUNT]++; break; } } /* Introduction d'un nouveau motif au besoin */ if (i == backend->used) { if (backend->used == backend->allocated) { if (backend->allocated == 0) backend->allocated = 64; else backend->allocated *= 2; backend->atoms = realloc(backend->atoms, backend->allocated * sizeof(const uint8_t *)); backend->lengths = realloc(backend->lengths, backend->allocated * sizeof(size_t)); backend->coverages = realloc(backend->coverages, backend->allocated * 2 * sizeof(uint32_t)); } backend->atoms[i] = plain; backend->lengths[i] = len; backend->coverages[i * 2 + EXPR_COVERAGE_COUNT] = 1; backend->used++; tmp_id[0] = i; tmp_id[1] = 0; } return result; } /****************************************************************************** * * * Paramètres : backend = moteur de recherche à préparer. * * * * Description : Met en ordre les derniers détails avant un premier scan. * * * * Retour : Bilan de l'opération. * * * * Remarques : - * * * ******************************************************************************/ static bool g_hyperscan_backend_warm_up(GHyperscanBackend *backend) { bool result; /* Bilan à retourner */ uint32_t current_start; /* Indice de gestionnaire */ size_t i; /* Boucle de parcours */ hs_compile_error_t *error; /* Compléments d'information */ hs_error_t ret; /* Code de retour */ hs_platform_info_t platform; result = false; /* Mise à jour de la couverture des gestionnaires de suivi */ current_start = 0; for (i = 0; i < backend->used; i++) { backend->coverages[i * 2 + EXPR_COVERAGE_START] = current_start; backend->coverages[i * 2 + EXPR_COVERAGE_END] += current_start; current_start = backend->coverages[i * 2 + EXPR_COVERAGE_END]; } /* Enregistrement des expressions à prendre en compte */ backend->lit_ids = malloc(backend->used * sizeof(unsigned)); for (i = 0; i < backend->used; i++) backend->lit_ids[i] = i; //hs_populate_platform(&platform); //platform.tune = 1; //platform.cpu_features = HS_CPU_FEATURES_AVX512; ret = hs_compile_lit_multi((const char *const *)backend->atoms, NULL, backend->lit_ids, backend->lengths, backend->used, HS_MODE_BLOCK, NULL, &backend->database, &error); //printf("ret: %d -vs- %d\n", ret, HS_SUCCESS); if (ret != HS_SUCCESS) { log_variadic_message(LMT_EXT_ERROR, _("Unable to compile %zu patterns: \"%s\""), backend->used, error->message); printf("FAILED: %s\n", error->message); hs_free_compile_error(error); goto exit; } #if 0 do { //hs_platform_info_t platform; char *__info; //hs_populate_platform(&platform); printf("TUNE: %u\n", platform.tune); printf("CPU: %llx - AVX2? %d - AVX512? %d\n", platform.cpu_features, platform.cpu_features & HS_CPU_FEATURES_AVX2, platform.cpu_features & HS_CPU_FEATURES_AVX512); hs_database_info(backend->database, &__info); printf("INFO: %s\n", __info); } while (0); #endif /* Création d'un espace de travail */ ret = hs_alloc_scratch(backend->database, &backend->scratch); //printf("ret: %d -vs- %d\n", ret, HS_SUCCESS); if (ret != HS_SUCCESS) { log_variadic_message(LMT_EXT_ERROR, _("Unable to allocate scratch space")); goto exit; } result = true; exit: //printf("result: %d\n", result); return result; } /****************************************************************************** * * * Paramètres : backend = moteur de recherche à manipuler. * * tmp_id = identifiants temporaires vers le motif. [OUT] * * * * Description : Récupère les identifiants finaux pour un motif recherché. * * * * Retour : Identifiant constitué ou INVALID_PATTERN_ID en cas d'échec. * * * * Remarques : - * * * ******************************************************************************/ static patid_t g_hyperscan_backend_build_plain_pattern_id(const GHyperscanBackend *backend, const uint32_t tmp_id[2]) { patid_t result; /* Identifiant à retourner */ size_t index; /* Indice reconstitué */ assert(tmp_id[0] < backend->used); index = tmp_id[0] * 2; result = backend->coverages[index + EXPR_COVERAGE_START] + tmp_id[1]; assert(result < backend->coverages[index + EXPR_COVERAGE_END]); return result; } /****************************************************************************** * * * Paramètres : backend = moteur de recherche à manipuler. * * * * Description : Détermine le nombre d'identifiants constitués. * * * * Retour : Quantité de gestionnaires de suivi à prévoir. * * * * Remarques : - * * * ******************************************************************************/ static size_t g_hyperscan_backend_count_plain_pattern_ids(const GHyperscanBackend *backend) { size_t result; /* Quantité à retourner */ if (backend->used == 0) result = 0; else result = backend->coverages[(backend->used - 1) * 2 + EXPR_COVERAGE_END]; return result; } /****************************************************************************** * * * Paramètres : backend = moteur de recherche à manipuler. * * context = lieu d'enregistrement des résultats. * * * * Description : Prend note d'une correspondance trouvée par Hyperscan. * * * * Retour : 0 afin de poursuivre les recherches. * * * * Remarques : - * * * ******************************************************************************/ static int handle_hyperscan_match_event(unsigned int id, unsigned long long from, unsigned long long to, unsigned int flags, const hyperscan_context_t *context) { phys_t offset; /* Point de départ établi */ const uint32_t *coverage_base; /* Base des suivis */ uint32_t k; /* Boucle de parcours */ uint32_t final_k; /* Dernier indice à traiter */ //return 0; #ifndef NDEBUG assert(id < context->used); assert(id == context->lit_ids[id]); #endif offset = to - context->lengths[id]; coverage_base = context->coverages + id * 2; k = coverage_base[EXPR_COVERAGE_START]; final_k = coverage_base[EXPR_COVERAGE_END]; for (; k < final_k; k++) g_umem_slice_put_uint64(context->matches[k], offset); return 0; } /****************************************************************************** * * * Paramètres : backend = moteur de recherche à manipuler. * * context = lieu d'enregistrement des résultats. * * * * Description : Parcours un contenu binaire à la recherche de motifs. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_hyperscan_backend_run_scan(const GHyperscanBackend *backend, GScanContext *context) { GBinContent *content; /* Contenu binaire manipulé */ phys_t dlen; /* Quantité de données */ vmpa2t pos; /* Point de départ ciblé */ const bin_t *data; /* Données à analyser */ #ifndef NDEBUG siez_t count; /* Nombre de zones prévues */ #endif hyperscan_context_t hcontext; /* Rassemblement d'informations*/ GUMemSlice **matches; /* Zones d'enregistrements */ hs_error_t ret; /* Code de retour */ /* Récupération d'un accès aux données */ content = g_scan_context_get_content(context); dlen = g_binary_content_compute_size(content); g_binary_content_compute_start_pos(content, &pos); data = g_binary_content_get_raw_access(content, &pos, dlen); /* Préparation de l'accès aux éléments utiles */ hcontext.lengths = backend->lengths; hcontext.coverages = backend->coverages; #ifndef NDEBUG hcontext.used = backend->used; hcontext.lit_ids = backend->lit_ids; #endif #ifndef NDEBUG matches = g_scan_context_get_match_storages(context, &count); assert(count == backend->used); #else matches = g_scan_context_get_match_storages(context, (size_t []){ 0 }); #endif hcontext.matches = matches; /* Lancement de l'analyse */ ret = hs_scan(backend->database, (const char *)data, dlen, 0 /* Arg. inutilisé */, backend->scratch, (match_event_handler)handle_hyperscan_match_event, &hcontext); //printf("ret ok? %d\n", ret == HS_SUCCESS); g_object_unref(G_OBJECT(content)); } /****************************************************************************** * * * Paramètres : backend = moteur de recherche à consulter. * * * * Description : Imprime quelques faits quant aux éléments mis en place. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_hyperscan_backend_output_stats(const GHyperscanBackend *backend) { printf("TODO: %s\n", __FUNCTION__); }