Una favola per non piangere: la minaccia WannaCry spiegata ad un bambino [4/4]

Finalmente, è giunto il momento di concludere la nostra fiaba. L’attacco è andato a buon fine, i malintenzionati sono riusciti a criptare i nostri file e ci hanno anche detto come pagare, dopo averci spaventato con dei minacciosi conti alla rovescia. Ora, vediamo come gli attaccanti possono infine ripristinare i nostri amati file.


Tempo di lettura stimato: 15 minuti
Difficoltà:


Finalmente, è giunto il momento di concludere la nostra fiaba.

L’attacco è andato a buon fine, i malintenzionati sono riusciti a criptare i nostri file e ci hanno anche detto come pagare, dopo averci spaventato con dei minacciosi conti alla rovescia. Ora, vediamo come gli attaccanti possono infine ripristinare i nostri amati file.

Capitolo quarto: la liberazione

Il folletto caricò il riscatto su un piccolo carro, e si preparò a partire; prese poi il cofanetto sigillato con il lucchetto rosso, annunciando che sarebbe ritornato appena possibile, e si allontanò in direzione della Foresta del Pianto. Appena uscito dalla città si ricongiunse con gli altri scagnozzi del mago, ed insieme si recarono dal loro padrone. Dietro di loro, a debita distanza, i soldati della guardia reale ne seguivano le tracce con molta attenzione, sperando di potere in questo modo catturare il mago Ransomio. Quando i folletti entrarono nella foresta, però, i soldati non poterono più continuare il loro inseguimento, e quindi non restò loro altro da fare che aspettare, nella speranza che i folletti o il loro padrone uscissero nuovamente in direzione della capitale. Nel frattempo, il mago stava aspettando pazientemente i suoi servi con i suoi nuovi tesori. Al loro arrivo, si fece consegnare immediatamente i dieci cofanetti chiusi con i lucchetti rossi, perché non voleva che qualcuna delle vittime perisse a causa dell’incantesimo del sale. Con dei forti strattoni aprì i lucchetti uno dopo l’altro: la chiave di questi lucchetti infatti era il mago stesso.

Se l’attaccato accetta di pagare, la AttackerKey privata (cioè la chiave rossa, nella storia il mago stesso) dovrebbe essere utilizzata per decriptare la VictimKey privata (cioè le chiavi di diversi colori della storia). Si noti che la chiave privata dell’attaccante (che è sempre la stessa per tutti gli attaccati) non viene mai trasferita sul pc delle vittime, ma viene sempre custodita con cura e utilizzata solo per decriptare la chiave della vittima.

I soldati si prepararono a catturare le loro prede, scegliendo il nascondiglio migliore per tendere loro un’imboscata. Ma ecco, dal bosco uscirono invece con gran fatica dieci contadini, stregati e controllati dal potere di Ransomio. La trappola ordita dal re era fallita miseramente, ma comunque quei poveretti portavano al collo le chiavi estratte dai cofanetti; i soldati quindi se le fecero consegnare, e si diressero al galoppo verso la città.

Nel frattempo, vocabolario alla mano il sovrano stava pronunciando tutte le parole conosciute, sperando di riuscire a liberare almeno un’altra delle sue figlie. Quando il capo delle guardie fece il suo ingresso, non rivolse alcuna parola al re, ma si diresse a testa bassa in direzione dell’ultimo cofanetto. Il re subito si zittì, ed ecco che la chiave verde riuscì con un click ad aprire il suo lucchetto. All’interno del cofanetto, il soldato trovò i quattro biglietti mancanti, e pronunciandone le parole il re poté quindi liberare tutte le sue figlie. Le casse dello stato erano vuote, ma il suo tesoro più grande era salvo, così come la prole di tutti i ricchi del regno. Nonostante la disavventura, per le strade della capitale fu organizzata una gran festa.

Una volta decriptata la VictimKey privata (la chiave verde, e anche le chiavi degli altri colori) l’attacco è concluso: con questa chiave è infatti possibile decifrare tutte le chiavi simmetriche rimanenti, e quindi attraverso l’algoritmo AES è possibile recuperare l’interezza dei file sul sistema della vittima.

Epilogo

Per tutto il giorno le strade furono invase da gente festante, e si sprecarono bevande e cibi succulenti. A notte fonda però, quando ormai anche i più resistenti festaioli del regno stavano ormai rincasando, avvenne la tragedia: uno ad uno, tutti i ragazzi che erano stati rinchiusi nei cristalli di sale si svegliarono, uscirono silenziosamente dalle loro dimore e si misero in cammino verso la Foresta del Pianto, dove furono accolti e guidati dai folletti di Ransomio. Tutti loro infatti erano stati stregati dal mago nella notte della loro cattura, ed era stato loro ordinato di radunarsi nella selva la notte successiva alla loro liberazione. La mattina, in tutto il regno vi furono profonda tristezza e grida disperate. Il re ordinò a tutti gli uomini di raggiungere la dimora dello stregone per recuperare i ragazzi, anche a costo di estirpare ogni singolo albero, ma la Foresta si dimostrò troppo fitta per essere rasa al suolo. Di tutte le vittime dell’incanto, solo il rabbioso chihuahua rimase con la sua famiglia, incrementando la disperazione del padrone di casa. Il mago Ransomio usò i suoi poteri per far innamorare di sé la più bella tra le principesse, e se la sposò; tutti gli altri ragazzi, invece, vissero per il resto della loro vita nella foresta, lavorando come domestici dello stregone.

Fu però il re Finestrone a subire la sorte peggiore: senza le sue figlie, l’accordo con i grandi regni vicini non poté essere rispettato, e quindi la guerra fu inevitabile. Per il piccolo regno fu una tremenda disfatta, e il povero sovrano fu detronizzato e rinchiuso nelle segrete del castello fino alla fine dei suoi giorni. La fertile terra del MonteRosso fu spartita tra i cinque invasori, ma anche per loro la festa non durò a lungo: dal cuore dell’oscura Foresta del Pianto, infatti, un piccolo mago era libero di tramare nell’ombra…

Perdonatemi, avrei preferito chiudere con il quarto capitolo. Questo epilogo amaro però è giustificato dalla caratteristica del software di cui stiamo parlando.

In tutta questa storia, sono poche le certezze.

Prima di tutto, siamo sicuri del fatto che qualcuna delle vittime ha pagato il riscatto. Non possiamo conoscerne il numero, ma sicuramente qualcuno ha preferito cedere alla richiesta dei criminali piuttosto che perdere i propri file.

Un’altra certezza riguarda le motivazioni di tale attacco: se l’NSA non avesse cercato di trarre guadagno dalle vulnerabilità dei sistemi Windows, e se gli utenti avessero la buona abitudine di tenere costantemente aggiornato il proprio sistema operativo, sicuramente l’attacco non sarebbe avvenuto, o comunque avrebbe avuto dimensioni più contenute.

Resta invece un grande punto interrogativo: gli utenti che hanno pagato sono effettivamente riusciti ad ottenere indietro i propri file?

Purtroppo è difficile dare una risposta a questa domanda. Ciò che è certo è che in passato molte vittime di RansomWare non sono riusciti a recuperare i propri dati, perché gli attaccanti hanno preferito richiedere ulteriori riscatti oppure semplicemente perché non ne erano in grado:  spesso, i RansomWare si limitano a fingere di criptare i dati, provvedendo invece a sovrascrivere i file con delle sequenze di byte casuali e quindi distruggendo totalmente l’informazione contenuta in memoria.

Per concludere, c’è un altro motivo per cui è sconsigliabile pagare un riscatto in questo tipo di situazioni: anche qualora fosse effettivamente possibile recuperare i dati, gli attaccanti otterrebbero un vantaggio incassando il riscatto, e sarebbero quindi invogliati ad organizzare ulteriori crimini nel futuro; per questo, è importantissimo evitare di pagare, in modo da rendere lo sforzo per organizzare l’attacco controproducente.

Se volete minimizzare il rischio di un attacco, quindi, non vi resta che seguire questi semplici consigli:

  • Tenere aggiornato il proprio sistema operativo e l’antivirus
  • Utilizzare il pc con attenzione, evitando di cliccare su allegati o siti internet sospetti
  • Effettuare regolarmente un backup dei dati importanti, facendo bene attenzione a scollegare la memoria dal sistema al termine dell’operazione in modo da proteggere le copie da eventuali attacchi

Prima di salutarvi, vorrei proporvi un programmino scritto in Go che simula il comportamento di WannaCry su tre frammenti di testo, riassumendo  quanto descritto in questi quattro articoli. Buon divertimento!


package main



import (

"bytes"

"crypto/aes"

"crypto/cipher"

"crypto/rand"

"crypto/rsa"

"crypto/sha1"

"encoding/base64"

"encoding/json"

"errors"

"fmt"

"io"

"log"

"strings"

)



func main() {



texts := []string{}

text1 := "Amor, ch’a nullo amato amar perdona, mi prese del costui piacer sì forte, che, come vedi, ancor non m’abbandona."

texts = append(texts, text1)

text2 := "Si conobbero. Lui conobbe lei e se stesso, perché in verità non s’era mai saputo. E lei conobbe lui e se stessa, perché pur essendosi saputa sempre, mai s’era potuta riconoscere così."

texts = append(texts, text2)

text3 := "La vita passa e noi la lasciamo passare come l'acqua del fiume, e solo quando manca ci accorgiamo che manca."

texts = append(texts, text3)



fmt.Println("Preparing...")

fmt.Println("**************")



fmt.Println("Creating Attacker Key...")

attackerPrivateKey, _ := createRsaKeyPair()

fmt.Println("Completed.")

attackerPublicKey := attackerPrivateKey.PublicKey

// attackerPrivateJson, _ := json.Marshal(attackerPrivateKey)

// attackerPublicJson, _ := json.Marshal(attackerPublicKey)

// fmt.Printf("Attacker Private Key: %s\n", attackerPrivateJson)

// fmt.Printf("Attacker Public Key: %s\n", attackerPublicJson)

fmt.Println("**************")



fmt.Println("Creating Demo Key...")

demoPrivateKey, _ := createRsaKeyPair()

fmt.Println("Completed.")

demoPublicKey := demoPrivateKey.PublicKey

// demoPrivateJson, _ := json.Marshal(demoPrivateKey)

// demoPublicJson, _ := json.Marshal(demoPublicKey)

// fmt.Printf("Demo Private Key: %s\n", demoPrivateJson)

// fmt.Printf("Demo Public Key: %s\n", demoPublicJson)

fmt.Println("**************")

fmt.Println("Ready to simulate a WannaCry infection!")



fmt.Println("Our targets are...")

fmt.Printf("---Text 1: %s\n", text1)

fmt.Printf("---Text 2: %s\n", text2)

fmt.Printf("---Text 3: %s\n", text3)

fmt.Println("**************")

fmt.Println("Creating Victim Key...")

victimPrivateKey, _ := createRsaKeyPair()

fmt.Println("Completed.")

victimPublicKey := victimPrivateKey.PublicKey

victimPrivateJson, _ := json.Marshal(victimPrivateKey)

// victimPublicJson, _ := json.Marshal(victimPublicKey)

// fmt.Printf("Victim Private Key: %s\n", victimPrivateJson)

// fmt.Printf("Victim Public Key: %s\n", victimPublicJson)

fmt.Println("**************")

fmt.Println("Encrypting Victim Private Key with Attacker Public Key...")

ekyKey := encryptBufWithRsa(victimPrivateJson, attackerPublicKey)

fmt.Println("Deleting Victim Private Key, to make decrypting impossible...")

victimPrivateKey = nil

victimPrivateJson = nil

fmt.Println("**************")

fmt.Println("Encrypting Texts...")

keys := [][]byte{}



for i := range texts {

fmt.Println("Encrypting text: ", i+1)

fmt.Println("Generating random Symmetric 32 bit key...")

key := generateRandomAesKey()

// fmt.Printf("Key: %s\n", key)

fmt.Println("Encrypting text with AES...")

encryptText, _ := aesEncrypt(key, texts[i])

fmt.Println("Done.")

texts[i] = encryptText

if i == 0 {

fmt.Println("Encrypting Symmetric Key with Demo Public Key...")

encryptKey, _ := rsaEncrypt(key, demoPublicKey)

keys = append(keys, encryptKey)

fmt.Println("Done.")

} else {

fmt.Println("Encrypting Symmetric Key with Victim Public Key...")

encryptKey, _ := rsaEncrypt(key, victimPublicKey)

keys = append(keys, encryptKey)

fmt.Println("Done.")

}

}

fmt.Println("**************")

fmt.Println("Now the texts are like this:")

for i := range texts {

text := texts[i]

fmt.Printf("---Text %d: %s\n", i+1, text)

}

fmt.Println("**************")

fmt.Println("Scared? Don't worry! If you pay, your texts will return like before!")

fmt.Println("Yes, your texts are still alive... We can prove it with a small set of texts!")

fmt.Println("**************")

demoCryptKey := keys[0]

fmt.Println("Decrypting Symmetric Key with Demo Private Key...")

demoKey, _ := rsaDecrypt(demoCryptKey, demoPrivateKey)

fmt.Println("Done.")

fmt.Println("Decrypting text with AES...")

demoCryptText := texts[0]

demoText, _ := aesDecrypt(demoKey, demoCryptText)

texts[0] = demoText

fmt.Println("Done.")

fmt.Println("Here's your text!")

fmt.Printf("---Text 1: %s\n", demoText)

fmt.Println("**************")

fmt.Println("Are you ready to pay? Yes? Good, Good... Eheheheh...")

fmt.Println("Decrypting the Victim Private Key with the Attacker Private Key...")

victimPrivateJson = decryptBufWithRsa(ekyKey, attackerPrivateKey)

var retrievedPrivateKey *rsa.PrivateKey

json.Unmarshal(victimPrivateJson, &retrievedPrivateKey)

fmt.Println("Done.")

fmt.Println("**************")

fmt.Println("Decrypting texts...")

for i := range texts {

if i != 0 {

fmt.Println("Decrypting text: ", i+1)

cryptKey := keys[i]

fmt.Println("Decrypting Symmetric Key with the retrieved Victim Private Key...")

key, _ := rsaDecrypt(cryptKey, retrievedPrivateKey)

fmt.Println("Done.")

fmt.Println("Decrypting text with AES...")

cryptText := texts[i]

text, _ := aesDecrypt(key, cryptText)

texts[i] = text

fmt.Println("Done.")

}

}

fmt.Println("**************")

fmt.Println("Now the texts are like this:")

for i := range texts {

text := texts[i]

fmt.Printf("---Text %d: %s\n", i+1, text)

}

fmt.Println("**************")

fmt.Println("WannaCry simulation completed correctly!")

}



// AES functions



func generateRandomAesKey() []byte {

key := make([]byte, 32)

rand.Read(key)

return key

}



func addBase64Padding(value string) string {

m := len(value) % 4

if m != 0 {

value += strings.Repeat("=", 4-m)

}



return value

}



func removeBase64Padding(value string) string {

return strings.Replace(value, "=", "", -1)

}



func pad(src []byte) []byte {

padding := aes.BlockSize - len(src)%aes.BlockSize

padtext := bytes.Repeat([]byte{byte(padding)}, padding)

return append(src, padtext...)

}



func unpad(src []byte) ([]byte, error) {

length := len(src)

unpadding := int(src[length-1])



if unpadding > length {

return nil, errors.New("unpad error. This could happen when incorrect encryption key is used")

}



return src[:(length - unpadding)], nil

}



func aesEncrypt(key []byte, text string) (string, error) {

block, err := aes.NewCipher(key)

if err != nil {

return "", err

}



msg := pad([]byte(text))

ciphertext := make([]byte, aes.BlockSize+len(msg))

iv := ciphertext[:aes.BlockSize]

if _, err := io.ReadFull(rand.Reader, iv); err != nil {

return "", err

}



cfb := cipher.NewCFBEncrypter(block, iv)

cfb.XORKeyStream(ciphertext[aes.BlockSize:], []byte(msg))

finalMsg := removeBase64Padding(base64.URLEncoding.EncodeToString(ciphertext))

return finalMsg, nil

}



func aesDecrypt(key []byte, text string) (string, error) {

block, err := aes.NewCipher(key)

if err != nil {

return "", err

}



decodedMsg, err := base64.URLEncoding.DecodeString(addBase64Padding(text))

if err != nil {

return "", err

}



if (len(decodedMsg) % aes.BlockSize) != 0 {

return "", errors.New("blocksize must be multipe of decoded message length")

}



iv := decodedMsg[:aes.BlockSize]

msg := decodedMsg[aes.BlockSize:]



cfb := cipher.NewCFBDecrypter(block, iv)

cfb.XORKeyStream(msg, msg)



unpadMsg, err := unpad(msg)

if err != nil {

return "", err

}



return string(unpadMsg), nil

}



//RSA functions



func encryptBufWithRsa(buf []byte, pub rsa.PublicKey) [][]byte {

res := [][]byte{}

chunks := split(buf, 128)

for i := range chunks {

chunk := chunks[i]

encryptChunk, _ := rsaEncrypt(chunk, pub)

res = append(res, encryptChunk)

}

return res

}



func decryptBufWithRsa(buf [][]byte, priv *rsa.PrivateKey) []byte {

res := []byte{}

for i := range buf {

chunk := buf[i]

decryptChunk, _ := rsaDecrypt(chunk, priv)

res = append(res, decryptChunk...)

}

return res

}



func split(buf []byte, lim int) [][]byte {

var chunk []byte

chunks := make([][]byte, 0, len(buf)/lim+1)

for len(buf) >= lim {

chunk, buf = buf[:lim], buf[lim:]

chunks = append(chunks, chunk)

}

if len(buf) > 0 {

chunks = append(chunks, buf[:len(buf)])

}

return chunks

}



func createRsaKeyPair() (*rsa.PrivateKey, error) {



size := 2048



priv, err := rsa.GenerateKey(rand.Reader, size)



if err != nil {

log.Fatalf("Failed to generate %d-bit key", size)

return nil, err

}



return priv, err



}



func rsaEncrypt(in []byte, pub rsa.PublicKey) ([]byte, error) {



sha1 := sha1.New()



out, err := rsa.EncryptOAEP(sha1, rand.Reader, &pub, in, nil)



if err != nil {

log.Fatalf("Failed to encrypt message %v", err)

return nil, err

}



return out, nil

}



func rsaDecrypt(ciphertext []byte, priv *rsa.PrivateKey) ([]byte, error) {



sha1 := sha1.New()



out, err := rsa.DecryptOAEP(sha1, rand.Reader, priv, ciphertext, nil)



if err != nil {

log.Fatalf("Failed to decrypt message %v", err)

return nil, err

}



return out, nil

}

 

Pubblicato da Mauro Valota

Ingegnere informatico, appassionato di teatro e letteratura. O forse, più probabilmente, un permaloso Glitch in Matrix. Laureato in ingegneria informatica all’Università degli Studi di Bergamo.