Quando cerchiamo di individuare e bloccare una minaccia informatica, la prima cosa che ci viene in mente sono spesso gli IoC (Indicator of Compromise), ovvero dati atomici che rappresentano informazioni tecniche specifiche, come hash di file, indirizzi IP, domini, stringhe presenti all’interno di un eseguibile, ecc… Sebbene questi indicatori siano molto utili per identificare attività malevole, non sono sufficienti per prevenire eventuali varianti della stessa minaccia, poiché possono essere facilmente modificati dagli attaccanti. Ad esempio, è sufficiente modificare l’hash di un eseguibile malevolo oppure registrare un nuovo dominio per un server C&C, e l’indicatore smette di essere utile.
Per superare questo problema, si ricorre agli indicatori comportamentali, i quali si basano sull’analisi dei comportamenti osservati da una minaccia. Grazie a questi indicatori è possibile creare regole di detection molto più robuste, in grado di rilevare anche eventuali variazioni di una stessa campagna.
Tra gli indicatori comportamentali troviamo anche l’entropia, una funzione matematica utile a misurare il grado di “casualità” di una stringa.
In parole semplici, più una stringa contiene caratteri casuali, più la sua entropia sarà alta. Questo parametro può aiutarci a riconoscere elementi anomali, come nomi di file o domini generati automaticamente da malware, che appaiono “strani” proprio perché troppo casuali rispetto al normale.
Shannon Entropy
In informatica, l’entropia è un concetto preso in prestito dalla teoria dell’informazione di Claude Shannon.
Per calcolare l’entropia di Shannon di una stringa, si utilizza la formula:

dove:
- “pi” è la probabilità di occorrenza del carattere “i”
- la somma è su tutti i caratteri distinti della stringa
Esempio:
Stringa: “cijjdejdhekhflm”
Lunghezza totale: 15
Frequenza dei caratteri:

Calcolo dell’entropia:

Come facciamo a capire se il valore dell’entropia è alto oppure no?
Per rispondere a questa domanda dobbiamo prima conoscere qual è il valore massimo che l’entropia può assumere.
Purtroppo, questo valore non è unico, ma dipende dal tipo di alfabeto utilizzato per generare le stringhe.
In altre parole, il valore massimo dell’entropia varia in base al numero di simboli distinti presenti nell’alfabeto, indicato con la lettera k:


Perché l’entropia è utile nel Threat Hunting
Nel Threat Hunting, l’obiettivo non è soltanto rilevare minacce già conosciute, ma soprattutto scovare comportamenti anomali che potrebbero indicare attività malevole ancora sconosciute.
Ed è proprio qui che entra in gioco l’entropia. Molti malware o strumenti utilizzati dagli attaccanti generano automaticamente nomi di file, domini o chiavi di registro casuali, proprio per eludere i controlli basati su firme o su IoC. Calcolando l’entropia di questi elementi, ad esempio dei domini contattati dai dispositivi aziendali, un analista può individuare valori anomali e riconoscere elementi “troppo casuali per essere leciti”.
In questo modo, anche un dominio o un file mai visto prima può essere segnalato come potenzialmente sospetto.
L’entropia può essere applicata a qualsiasi tipo di dato, come nomi di file, domini, chiavi di registro o indirizzi URL, per capire quanto questi si discostano da ciò che normalmente ci si aspetterebbe di vedere in un sistema legittimo.
Alcuni Esempi pratici
- Rilevazione di domini DGA (Domain Generation Algorithm):
Alcuni malware generano migliaia di domini casuali per connettersi ai server di comando e controllo (C2). Analizzando l’entropia, è possibile identificarli automaticamente. - Analisi di file sospetti:
File o eseguibili con nomi apparentemente casuali (come x9df93b.exe) possono essere segnalati per ulteriori indagini. - Controllo di chiavi di registro o processi:
Un processo legittimo come explorer.exe presenta un basso livello di entropia, mentre una chiave o un processo con nome del tipo A4d93zPq1 può indicare attività di offuscamento o evasione.
In sintesi, l’entropia è una vera e propria bussola matematica che aiuta gli analisti a individuare elementi anomali all’interno di grandi volumi di dati, anche in assenza di indicatori di compromissione precisi.
Conclusioni
In conclusione, l’entropia è uno strumento molto utile per capire quanto un insieme di dati sia “ordinato” o “caotico”. L’entropia di Shannon aiuta a misurare quanto le informazioni siano imprevedibili, ma non sempre basta per avere un quadro completo.
Quando vogliamo capire quanto i nostri dati si allontanino da ciò che ci aspettiamo, entra in gioco l’entropia relativa, una misura più precisa e affidabile in molti casi pratici.
Per questo motivo, anche se l’entropia di Shannon resta un ottimo punto di partenza, l’entropia relativa permette di fare un passo avanti e ottenere analisi più accurate. Nel prossimo articolo parleremo proprio di questa seconda misura e di come può essere applicata in modo efficace.
Entropy in CrowdStrike
shannonEntropy()
Entropy in Splunk
ut_shannon()
Entropy in SentinelOne
Questa query è valida per le stringe composte dall’alfabeta [a-z]
|let string = <STRING>
//max value of Shannon Entropy in this case is log(26)
|let count_a = len(string.extract_matches('a'))
|let count_b = len(string.extract_matches('b'))
|let count_c = len(string.extract_matches('c'))
|let count_d = len(string.extract_matches('d'))
|let count_e = len(string.extract_matches('e'))
|let count_f = len(string.extract_matches('f'))
|let count_g = len(string.extract_matches('g'))
|let count_h = len(string.extract_matches('h'))
|let count_i = len(string.extract_matches('i'))
|let count_j = len(string.extract_matches('j'))
|let count_k = len(string.extract_matches('k'))
|let count_l = len(string.extract_matches('l'))
|let count_m = len(string.extract_matches('m'))
|let count_n = len(string.extract_matches('n'))
|let count_o = len(string.extract_matches('o'))
|let count_p = len(string.extract_matches('p'))
|let count_q = len(string.extract_matches('q'))
|let count_r = len(string.extract_matches('r'))
|let count_s = len(string.extract_matches('s'))
|let count_t = len(string.extract_matches('t'))
|let count_u = len(string.extract_matches('u'))
|let count_v = len(string.extract_matches('v'))
|let count_w = len(string.extract_matches('w'))
|let count_x = len(string.extract_matches('x'))
|let count_y = len(string.extract_matches('y'))
|let count_z = len(string.extract_matches('z'))
|let size = count_a + count_b + count_c + count_d + count_e + count_f + count_g + count_h + count_i + count_j + count_k + count_l + count_m + count_n + count_o + count_p + count_q + count_r + count_s + count_t + count_u + count_v + count_w + count_x + count_y + count_z
// Carattere a
|let prob_a = count_a > 0 ? count_a / size : 0
|let entropy_a = count_a > 0 ? (prob_a) * log(prob_a, 2) : 0
|let entropy_a_tot = count_a > 0 ? entropy_a : 0
// Carattere b
|let prob_b = count_b > 0 ? count_b / size : 0
|let entropy_b = count_b > 0 ? (prob_b) * log(prob_b, 2) : 0
|let entropy_b_tot = count_b > 0 ? entropy_b : 0
// Carattere c
|let prob_c = count_c > 0 ? count_c / size : 0
|let entropy_c = count_c > 0 ? (prob_c) * log(prob_c, 2) : 0
|let entropy_c_tot = count_c > 0 ? entropy_c : 0
// Carattere d
|let prob_d = count_d > 0 ? count_d / size : 0
|let entropy_d = count_d > 0 ? (prob_d) * log(prob_d, 2) : 0
|let entropy_d_tot = count_d > 0 ? entropy_d : 0
// Carattere e
|let prob_e = count_e > 0 ? count_e / size : 0
|let entropy_e = count_e > 0 ? (prob_e) * log(prob_e, 2) : 0
|let entropy_e_tot = count_e > 0 ? entropy_e : 0
// Carattere f
|let prob_f = count_f > 0 ? count_f / size : 0
|let entropy_f = count_f > 0 ? (prob_f) * log(prob_f, 2) : 0
|let entropy_f_tot = count_f > 0 ? entropy_f : 0
// Carattere g
|let prob_g = count_g > 0 ? count_g / size : 0
|let entropy_g = count_g > 0 ? (prob_g) * log(prob_g, 2) : 0
|let entropy_g_tot = count_g > 0 ? entropy_g : 0
// Carattere h
|let prob_h = count_h > 0 ? count_h / size : 0
|let entropy_h = count_h > 0 ? (prob_h) * log(prob_h, 2) : 0
|let entropy_h_tot = count_h > 0 ? entropy_h : 0
// Carattere i
|let prob_i = count_i > 0 ? count_i / size : 0
|let entropy_i = count_i > 0 ? (prob_i) * log(prob_i, 2) : 0
|let entropy_i_tot = count_i > 0 ? entropy_i : 0
// Carattere j
|let prob_j = count_j > 0 ? count_j / size : 0
|let entropy_j = count_j > 0 ? (prob_j) * log(prob_j, 2) : 0
|let entropy_j_tot = count_j > 0 ? entropy_j : 0
// Carattere k
|let prob_k = count_k > 0 ? count_k / size : 0
|let entropy_k = count_k > 0 ? (prob_k) * log(prob_k, 2) : 0
|let entropy_k_tot = count_k > 0 ? entropy_k : 0
// Carattere l
|let prob_l = count_l > 0 ? count_l / size : 0
|let entropy_l = count_l > 0 ? (prob_l) * log(prob_l, 2) : 0
|let entropy_l_tot = count_l > 0 ? entropy_l : 0
// Carattere m
|let prob_m = count_m > 0 ? count_m / size : 0
|let entropy_m = count_m > 0 ? (prob_m) * log(prob_m, 2) : 0
|let entropy_m_tot = count_m > 0 ? entropy_m : 0
// Carattere n
|let prob_n = count_n > 0 ? count_n / size : 0
|let entropy_n = count_n > 0 ? (prob_n) * log(prob_n, 2) : 0
|let entropy_n_tot = count_n > 0 ? entropy_n : 0
// Carattere o
|let prob_o = count_o > 0 ? count_o / size : 0
|let entropy_o = count_o > 0 ? (prob_o) * log(prob_o, 2) : 0
|let entropy_o_tot = count_o > 0 ? entropy_o : 0
// Carattere p
|let prob_p = count_p > 0 ? count_p / size : 0
|let entropy_p = count_p > 0 ? (prob_p) * log(prob_p, 2) : 0
|let entropy_p_tot = count_p > 0 ? entropy_p : 0
// Carattere q
|let prob_q = count_q > 0 ? count_q / size : 0
|let entropy_q = count_q > 0 ? (prob_q) * log(prob_q, 2) : 0
|let entropy_q_tot = count_q > 0 ? entropy_q : 0
// Carattere r
|let prob_r = count_r > 0 ? count_r / size : 0
|let entropy_r = count_r > 0 ? (prob_r) * log(prob_r, 2) : 0
|let entropy_r_tot = count_r > 0 ? entropy_r : 0
// Carattere s
|let prob_s = count_s > 0 ? count_s / size : 0
|let entropy_s = count_s > 0 ? (prob_s) * log(prob_s, 2) : 0
|let entropy_s_tot = count_s > 0 ? entropy_s : 0
// Carattere t
|let prob_t = count_t > 0 ? count_t / size : 0
|let entropy_t = count_t > 0 ? (prob_t) * log(prob_t, 2) : 0
|let entropy_t_tot = count_t > 0 ? entropy_t : 0
// Carattere u
|let prob_u = count_u > 0 ? count_u / size : 0
|let entropy_u = count_u > 0 ? (prob_u) * log(prob_u, 2) : 0
|let entropy_u_tot = count_u > 0 ? entropy_u : 0
// Carattere v
|let prob_v = count_v > 0 ? count_v / size : 0
|let entropy_v = count_v > 0 ? (prob_v) * log(prob_v, 2) : 0
|let entropy_v_tot = count_v > 0 ? entropy_v : 0
// Carattere w
|let prob_w = count_w > 0 ? count_w / size : 0
|let entropy_w = count_w > 0 ? (prob_w) * log(prob_w, 2) : 0
|let entropy_w_tot = count_w > 0 ? entropy_w : 0
// Carattere x
|let prob_x = count_x > 0 ? count_x / size : 0
|let entropy_x = count_x > 0 ? (prob_x) * log(prob_x, 2) : 0
|let entropy_x_tot = count_x > 0 ? entropy_x : 0
// Carattere y
|let prob_y = count_y > 0 ? count_y / size : 0
|let entropy_y = count_y > 0 ? (prob_y) * log(prob_y, 2) : 0
|let entropy_y_tot = count_y > 0 ? entropy_y : 0
// Carattere z
|let prob_z = count_z > 0 ? count_z / size : 0
|let entropy_z = count_z > 0 ? (prob_z) * log(prob_z, 2) : 0
|let entropy_z_tot = count_z > 0 ? entropy_z : 0
|let Array_Shannon_Entropy = array(entropy_a_tot, entropy_b_tot, entropy_c_tot, entropy_d_tot, entropy_e_tot, entropy_f_tot, entropy_g_tot, entropy_h_tot, entropy_i_tot, entropy_j_tot, entropy_k_tot, entropy_l_tot, entropy_m_tot, entropy_n_tot, entropy_o_tot, entropy_p_tot, entropy_q_tot, entropy_r_tot, entropy_s_tot, entropy_t_tot, entropy_u_tot, entropy_v_tot, entropy_w_tot, entropy_x_tot, entropy_y_tot, entropy_z_tot )
|let Shannon_Entropy= abs(Array_Shannon_Entropy.sum())
|group count() by Shannon_Entropy, string






