diff options
Diffstat (limited to 'plugins/bhash/tlsh.c')
-rw-r--r-- | plugins/bhash/tlsh.c | 724 |
1 files changed, 724 insertions, 0 deletions
diff --git a/plugins/bhash/tlsh.c b/plugins/bhash/tlsh.c new file mode 100644 index 0000000..92064de --- /dev/null +++ b/plugins/bhash/tlsh.c @@ -0,0 +1,724 @@ + +/* Chrysalide - Outil d'analyse de fichiers binaires + * tlsh.c - calculs d'empreintes selon l'algorithme TLSH + * + * Copyright (C) 2021 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 <http://www.gnu.org/licenses/>. + */ + + +#include "tlsh.h" + + +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <sys/param.h> + + +#include <common/hex.h> +#include <common/pearson.h> + + +#define BUCKETS_COUNT 256 + +#define BUCKETS_USED 128 + +#define HASH_CODE_SIZE ((BUCKETS_USED * 2) / 8) + +#define TLSH_STRING_LEN (2 + 2 + 2 + 2 + HASH_CODE_SIZE * 2) + +#define TLSH_LENGTH_MULTIPLIER 12 +#define TLSH_QRATIO_MULTIPLIER 12 + + +/* Mémorisation des informations brutes */ +typedef struct _tlsh_info_t +{ + phys_t data_length; /* Taille des données traitées */ + + uint32_t buckets[BUCKETS_COUNT]; /* Bac pour compteurs */ + uint8_t checksum; /* Empreinte globale */ + + uint32_t q1; /* Première valeur pivot (75%) */ + uint32_t q2; /* Deuxième valeur pivot (50%) */ + uint32_t q3; /* Troisième valeur pivot (25%)*/ + + uint8_t q1_ratio : 4; /* Ratio de portion #1 */ + uint8_t q2_ratio : 4; /* Ratio de portion #2 */ + + uint8_t captured_length; /* Tranche associée à la taille*/ + +} tlsh_info_t; + +/* Récupération des informations d'une empreinte */ +typedef struct _recovered_tlsh_info_t +{ + uint8_t checksum; /* Empreinte globale */ + + uint8_t captured_length; /* Tranche associée à la taille*/ + + uint8_t q1_ratio : 4; /* Ratio de portion #1 */ + uint8_t q2_ratio : 4; /* Ratio de portion #2 */ + + uint8_t code[HASH_CODE_SIZE]; /* Coeur de l'empreinte */ + +} recovered_tlsh_info_t; + + +/* Détermine l'indice du compteur destiné à un triplet d'octets. */ +static uint8_t define_tlsh_mapping(uint8_t, uint8_t, uint8_t, uint8_t); + +/* Définit tous les compteurs associés aux triplets d'octets. */ +static bool fill_tlsh_buckets(const GBinContent *, tlsh_info_t *); + +/* Compare deux compteurs de triplets d'octets. */ +static int compare_tlsh_buckets(const uint32_t *, const uint32_t *); + +/* Détermine les points de pivot au sein des bacs de compteurs. */ +static void find_tlsh_quartiles(tlsh_info_t *); + +/* Construit une empreinte TLSH sur les bases calculées. */ +static char *build_tlsh_hash(const tlsh_info_t *, bool); + +/* Reconstruit les informations portées par une empreinte TLSH. */ +static bool recover_tlsh_hash(const char *, recovered_tlsh_info_t *); + +/* Calcule une différence entre deux valeurs selon deux axes. */ +static int32_t diff_tlsh_values_two_way(uint32_t, uint32_t, uint32_t); + +/* Calcule le degré de différence entre deux octets TLSH. */ +static uint8_t diff_tlsh_bits(uint8_t, uint8_t); + + + +/****************************************************************************** +* * +* Paramètres : salt = sel à intégrer à la préparation. * +* b0 = premier octet à manipuler. * +* b1 = deuxième octet à manipuler. * +* b2 = troisième octet à manipuler. * +* * +* Description : Détermine l'indice du compteur destiné à un triplet d'octets.* +* * +* Retour : Indice du bac de destination pour décompte. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static uint8_t define_tlsh_mapping(uint8_t salt, uint8_t b0, uint8_t b1, uint8_t b2) +{ + uint8_t result; /* Valeur à retourner */ + const uint8_t *table; /* Permutations à utiliser */ + + table = (const uint8_t *)get_pearson_permutations(); + + result = table[salt ^ b0]; + result = table[result ^ b1]; + result = table[result ^ b2]; + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : content = contenu binaire à consulter. * +* info = informations à constituer en partie. [OUT] * +* * +* Description : Définit tous les compteurs associés aux triplets d'octets. * +* * +* Retour : Bilan de l'opération. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static bool fill_tlsh_buckets(const GBinContent *content, tlsh_info_t *info) +{ + bool result; /* Bilan à retourner */ + phys_t len; /* Taille des données présentes*/ + vmpa2t start; /* Première position de donnée */ + const uint8_t *data; /* Données à parcourir */ + phys_t i; /* Boucle de parcours */ + uint8_t index; /* Indice de compteur visé */ + + result = false; + + len = g_binary_content_compute_size(content); + + if (len < 5) + goto exit; + + g_binary_content_compute_start_pos(content, &start); + data = g_binary_content_get_raw_access(content, &start, len); + + info->data_length = len; + + memset(info->buckets, 0, sizeof(uint32_t) * BUCKETS_COUNT); + info->checksum = 0; + + for (i = 0; i <= (len - 5); i++) + { + info->checksum = define_tlsh_mapping(1, data[i + 4], data[i + 3], info->checksum); + + index = define_tlsh_mapping( 49, data[i + 4], data[i + 3], data[i + 2]); + info->buckets[index]++; + + index = define_tlsh_mapping( 12, data[i + 4], data[i + 3], data[i + 1]); + info->buckets[index]++; + + index = define_tlsh_mapping( 84, data[i + 4], data[i + 3], data[i + 0]); + info->buckets[index]++; + + index = define_tlsh_mapping(178, data[i + 4], data[i + 2], data[i + 1]); + info->buckets[index]++; + + index = define_tlsh_mapping(166, data[i + 4], data[i + 2], data[i + 0]); + info->buckets[index]++; + + index = define_tlsh_mapping(230, data[i + 4], data[i + 1], data[i + 0]); + info->buckets[index]++; + + } + + result = true; + + exit: + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : a = premier bacs de décompte à consulter. * +* b = second bacs de décompte à consulter. * +* * +* Description : Compare deux compteurs de triplets d'octets. * +* * +* Retour : Bilan de comparaison. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static int compare_tlsh_buckets(const uint32_t *a, const uint32_t *b) +{ + int result; /* Bilan à retourner */ + + if (*a < *b) + result = -1; + + else if (*a > *b) + result = 1; + + else + result = 0; + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : info = informations à constituer en partie. [OUT] * +* * +* Description : Détermine les points de pivot au sein des bacs de compteurs. * +* * +* Retour : - * +* * +* Remarques : - * +* * +******************************************************************************/ + +static void find_tlsh_quartiles(tlsh_info_t *info) +{ + uint32_t copy[BUCKETS_USED]; /* Copie modifiable */ + + memcpy(copy, info->buckets, BUCKETS_USED * sizeof(uint32_t)); + + qsort(copy, BUCKETS_USED, sizeof(uint32_t), (__compar_fn_t)compare_tlsh_buckets); + + /** + * q1 = quantité telle que 75% des buckets >= q1 + * q2 = quantité telle que 50% des buckets >= q2 + * q3 = quantité telle que 25% des buckets >= q3 + */ + + info->q1 = copy[BUCKETS_USED / 4 - 1]; + + info->q2 = copy[BUCKETS_USED / 2 - 1]; + + info->q3 = copy[(3 * BUCKETS_USED) / 4 - 1]; + +} + + + + + +#include <math.h> + + +#define LOG_1_5 0.4054651 +#define LOG_1_3 0.26236426 +#define LOG_1_1 0.095310180 + + +unsigned char l_capturing(unsigned int len) { + int i; + if( len <= 656 ) { + i = (int) floor( logf((float) len) / LOG_1_5 ); + } else if( len <= 3199 ) { + i = (int) floor( logf((float) len) / LOG_1_3 - 8.72777 ); + } else { + i = (int) floor( logf((float) len) / LOG_1_1 - 62.5472 ); + } + + return (unsigned char) (i & 0xFF); +} + + + + + + + + +/****************************************************************************** +* * +* Paramètres : info = informations à consulter. * +* version = affichage de la version ? * +* * +* Description : Construit une empreinte TLSH sur les bases calculées. * +* * +* Retour : Empreinte construite ou NULL en cas d'échec. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static char *build_tlsh_hash(const tlsh_info_t *info, bool version) +{ + char *result; /* Empreinte à retourner */ + char *pos; /* Tête de lecture */ + char tmp[HASH_CODE_SIZE * 2]; /* Stockage temporaire */ + char *code; /* Empreinte des compteurs */ + size_t i; /* Boucle de parcours */ + size_t offset; /* Rang d'intervention */ + + static char hex_lookup[] = "0123456789ABCDEF"; + + result = malloc(TLSH_STRING_LEN + 1); + + /* Indication de version ? */ + + if (version) + { + result[0] = 'T'; + result[1] = '1'; + pos = result + 2; + } + else + pos = result; + + /* Empreinte concise */ + + *(pos++) = hex_lookup[info->checksum & 0xf]; + *(pos++) = hex_lookup[(info->checksum >> 4) & 0xf]; + + /* Taille représentée */ + + *(pos++) = hex_lookup[info->captured_length & 0xf]; + *(pos++) = hex_lookup[(info->captured_length >> 4) & 0xf]; + + /* Ratios */ + + *(pos++) = hex_lookup[info->q1_ratio]; + + *(pos++) = hex_lookup[info->q2_ratio]; + + /* Empreinte du contenu binaire */ + + code = &tmp[HASH_CODE_SIZE - 1]; + + for (i = 0; i < BUCKETS_USED; i++) + { + if ((i % 4) == 0) + *code = 0; + + offset = (i % 4) * 2; + + if (info->buckets[i] <= info->q1) + ; + + else if (info->buckets[i] <= info->q2) + (*code) |= (1 << offset); + + else if (info->buckets[i] <= info->q3) + (*code) |= (2 << offset); + + else + (*code) |= (3 << offset); + + if (((i + 1) % 4) == 0) + code--; + + } + + encode_hex(tmp, HASH_CODE_SIZE, false, pos); + + assert(pos + (HASH_CODE_SIZE * 2) < result + (TLSH_STRING_LEN + 1)); + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : content = contenu binaire à consulter. * +* version = affichage de la version ? * +* * +* Description : Calcule l'empreinte TLSH d'un contenu binaire. * +* * +* Retour : Empreinte TLSH calculée ou NULL en cas d'échec. * +* * +* Remarques : - * +* * +******************************************************************************/ + +char *compute_content_tlsh_hash(const GBinContent *content, bool version) +{ + char *result; /* Empreinte à retourner */ + bool status; /* Bilan d'un appel */ + tlsh_info_t info; /* Informations brutes */ + + result = NULL; + + status = fill_tlsh_buckets(content, &info); + if (!status) goto exit; + + find_tlsh_quartiles(&info); + + if (info.q3 == 0) + goto exit; + + info.q1_ratio = ((float)(info.q1 * 100) / (float)info.q3); + info.q2_ratio = ((float)(info.q2 * 100) / (float)info.q3); + + info.captured_length = l_capturing(info.data_length); + + result = build_tlsh_hash(&info, version); + + exit: + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : h = chaîne de caratères à valider. * +* * +* Description : Indique si une chaîne représente à priori une empreinte TLSH.* +* * +* Retour : Bilan de l'analyse. * +* * +* Remarques : - * +* * +******************************************************************************/ + +bool is_valid_tlsh_hash(const char *h) +{ + bool result; /* Bilan à renvoyer */ + size_t len; /* Taille de la chaîne */ + + len = strlen(h); + + if (len == (TLSH_STRING_LEN - 2)) + result = true; + + else if (len == TLSH_STRING_LEN) + result = (h[0] == 'T' && h[1] == '1'); + + else + result = false; + + // TODO check hex + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : h = chaîne de caratères à consulter. * +* info = informations portées par une empreinte TLSH. * +* * +* Description : Reconstruit les informations portées par une empreinte TLSH. * +* * +* Retour : Bilan de la reconstruction. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static bool recover_tlsh_hash(const char *h, recovered_tlsh_info_t *info) +{ + bool result; /* Bilan à renvoyer */ + const char *pos; /* Tête de lecture */ + uint8_t value; /* Valeur récupérée */ + size_t i; /* Boucle de parcours */ + + result = is_valid_tlsh_hash(h); + if (!result) goto exit; + + /* Indication de version ? */ + + pos = (h[0] == 'T' ? h + 2 : h); + + /* Empreinte concise */ + + result = decode_hex_digit(pos++, &value); + assert(result); + if (!result) goto exit; + + info->checksum = value; + + result = decode_hex_digit(pos++, &value); + assert(result); + if (!result) goto exit; + + info->checksum |= (value << 4); + + /* Taille représentée */ + + result = decode_hex_digit(pos++, &value); + assert(result); + if (!result) goto exit; + + info->captured_length = value; + + result = decode_hex_digit(pos++, &value); + assert(result); + if (!result) goto exit; + + info->captured_length |= (value << 4); + + /* Ratios */ + + result = decode_hex_digit(pos++, &value); + assert(result); + if (!result) goto exit; + + info->q1_ratio = value; + + result = decode_hex_digit(pos++, &value); + assert(result); + if (!result) goto exit; + + info->q2_ratio = value; + + /* Empreinte du contenu binaire */ + + for (i = 0; i < HASH_CODE_SIZE; i++) + { + result = decode_hex_digit(pos++, &value); + assert(result); + if (!result) goto exit; + + info->code[HASH_CODE_SIZE - i - 1] = (value << 4); + + result = decode_hex_digit(pos++, &value); + assert(result); + if (!result) goto exit; + + info->code[HASH_CODE_SIZE - i - 1] |= value; + + } + + exit: + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : a = première valeur à analyser. * +* b = seconde valeur à analyser. * +* range = espace de valeurs à considérer. * +* * +* Description : Calcule une différence entre deux valeurs selon deux axes. * +* * +* Retour : Différence déterminée entre les deux valeurs. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static int32_t diff_tlsh_values_two_way(uint32_t a, uint32_t b, uint32_t range) +{ + int32_t result; /* Différence à retourner */ + int32_t diff_1; /* Première différence */ + int32_t diff_2; /* Seconde différence */ + + if (a < b) + { + diff_1 = b - a; + diff_2 = range + a - b; + } + else + { + diff_1 = a - b; + diff_2 = range + b - a; + } + + result = MIN(diff_1, diff_2); + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : a = premier octet à analyser. * +* b = second octet à analyser. * +* * +* Description : Calcule le degré de différence entre deux octets TLSH. * +* * +* Retour : Différence déterminée entre les deux octets. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static uint8_t diff_tlsh_bits(uint8_t a, uint8_t b) +{ + uint8_t result; /* Valeur à renvoyer */ + uint8_t partial; /* Différence partielle */ + + result = 0; + + partial = abs(a % 4 - b % 4); + result += (partial == 3 ? 6 : partial); + + a /= 4; b /= 4; + + partial = abs(a % 4 - b % 4); + result += (partial == 3 ? 6 : partial); + + a /= 4; b /= 4; + + partial = abs(a % 4 - b % 4); + result += (partial == 3 ? 6 : partial); + + a /= 4; b /= 4; + + partial = abs(a % 4 - b % 4); + result += (partial == 3 ? 6 : partial); + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : ha = première chaîne de caratères à consulter. * +* hb = première chaîne de caratères à consulter. * +* length = l'indication de taille doit être considérée ? * +* diff = degré de différence relevé. [OUT] * +* * +* Description : Détermine la similarité entre deux empreintes TLSH. * +* * +* Retour : Validité de l'opération menée. * +* * +* Remarques : - * +* * +******************************************************************************/ + +bool compare_tlsh_hash(const char *ha, const char *hb, bool length, int32_t *diff) +{ + bool result; /* Validité à retourner */ + recovered_tlsh_info_t info_a; /* Empreinte à manipuler #0 */ + recovered_tlsh_info_t info_b; /* Empreinte à manipuler #1 */ + int32_t partial; /* Différence calculée */ + size_t i; /* Boucle de parcours */ + + result = recover_tlsh_hash(ha, &info_a); + if (!result) goto exit; + + result = recover_tlsh_hash(hb, &info_b); + if (!result) goto exit; + + *diff = 0; + + /* Empreinte concise */ + + if (info_a.checksum != info_b.checksum) + *diff += 1; + + /* Taille représentée */ + + if (length) + { + partial = diff_tlsh_values_two_way(info_a.captured_length, info_b.captured_length, 2 << 8); + + if (partial > 1) + partial *= TLSH_LENGTH_MULTIPLIER; + + *diff += partial; + + } + + /* Ratios */ + + partial = diff_tlsh_values_two_way(info_a.q1_ratio, info_b.q1_ratio, 2 << 4); + + if (partial > 1) + partial *= TLSH_QRATIO_MULTIPLIER; + + *diff += partial; + + partial = diff_tlsh_values_two_way(info_a.q2_ratio, info_b.q2_ratio, 2 << 4); + + if (partial > 1) + partial *= TLSH_QRATIO_MULTIPLIER; + + *diff += partial; + + /* Empreinte du contenu binaire */ + + for (i = 0; i < HASH_CODE_SIZE; i++) + *diff += diff_tlsh_bits(info_a.code[i], info_b.code[i]); + + exit: + + return result; + +} |