Crowdstrike informuje: odszyfrowywalne oprogramowanie PartyTicket Ransomware atakuje ukraińskie podmioty

23 lutego 2022 r. przeprowadzono niszczycielskie cyberataki na jednostki ukraińskie. Według doniesień branżowych oprogramowanie ransomware oparte na PartyTicket Ransomware oraz Hermetic Ransom zostało zidentyfikowane w kilku organizacjach dotkniętych atakami, wykorzystano przy tym wyrafinowane oprogramowanie nazwane przez CrowdStrike Intelligence Tracks jako DriveSlayer (HermeticWiper).

Analiza PartyTicket Ransomware pokazuje, że szyfruje on tylko powierzchownie pliki i nieprawidłowo inicjalizuje klucz szyfrowania, pozwalając na odzyskanie zaszyfrowanego pliku z powiązanym rozszerzeniem . encryptedJB.

Analiza techniczna

Próbka ransowmware PartyTicket ma hash SHA256  4dc13bb83a16d4ff9865a51b3e4d24112327c526c1392e14d56f20d6f4eaf382. It has Zaobserwowano powiązanie z nazwami plików cdir.exe, cname.exe, connh.exe i intpub.exe.

Próbka ransomware — napisana przy użyciu Go w wersji 1.10.1 — zawiera wiele symboli, które odnoszą się do amerykańskiego systemu politycznego voteFor403, C:/projects/403forBiden/wHiteHousE i primaryElectionProcess.

Ransomware iteruje po wszystkich literach dysków i rekursywnie wylicza pliki na każdym dysku i jego podfolderach, z wyłączeniem ścieżek plików zawierających ciągi Windows i Program Files oraz ścieżkę folderu C:\Documents and Settings. Do szyfrowania wybrane są pliki o następujących rozszerzeniach:

acl, avi, bat, bmp, cab, cfg, chm, cmd, com, contact, crt, css, dat, dip, dll, doc, docx, dot, encryptedjb , epub, exe, gif, htm, html, ico, in, iso, jpeg, jpg, mp3, msi, odt, one, ova, pdf, pgsql, png, ppt, pptx, pub, rar, rtf, sfx, sql, txt, url, vdi, vsd, wma, wmv, wtv, xls, xlsx, xml, xps, zip

Dla każdej ścieżki pliku, która przechodzi wcześniej opisaną kontrolę ścieżki i rozszerzenia, ransomware kopiuje swoje wystąpienie do tego samego katalogu, z którego zostało wykonane,  używając wiersza poleceń, przekazuje ścieżkę pliku jako argument. Nadrzędny proces ransomware nazywa swoje klony losowym identyfikatorem UUID wygenerowanym przez bibliotekę publiczną, która wykorzystuje aktualny znacznik czasu i adresy MAC kart sieciowych zainfekowanego hosta.

Twórca złośliwego oprogramowania próbował wykorzystać typy WaitGroup Go do implementacji współbieżności. Jednak ze względu na prawdopodobny błąd w kodowaniu, ransomware tworzy bardzo dużą liczbę wątków (jeden na wyliczoną ścieżkę pliku) i kopiuje własny plik binarny do bieżącego katalogu tyle razy, ile jest wybranych plików. Po zakończeniu wszystkich wątków szyfrowania oryginalny plik binarny usuwa się za pomocą wiersza poleceń.

Gdy próbka otrzymuje ścieżkę pliku jako argument, szyfruje plik przy użyciu AES w trybie Galois/Counter (GCM). Klucz AES jest generowany przy użyciu funkcji Go rand package’s Intn w celu wybrania przesunięć w tablicy znaków 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ, generując klucz 32-bajtowy. Z powodu innego błędu kodowania, odkrytego w funkcji Intn następuje aktualizowanie po wygenerowaniu klucza, co oznacza, że ten sam klucz AES jest generowany przy każdym uruchomieniu pliku binarnego i jego klonów. Wszystkie pliki zaszyfrowane na hoście są szyfrowane tym samym kluczem, a znajomość odpowiedniego klucza próbki PartyTicket umożliwia ich odszyfrowanie. Skrypt wykorzystujący tę lukę do odzyskania zaszyfrowanych plików jest dostępny na CrowdStrike Git Repository.

Dla każdego pliku użyty jest klucz szyfrowania AES, za pomocą protokołu RSA-OAEP przy użyciu publicznego klucza RSA o następujących parametrach:

Modulus (N): 0xcbb94cb189a638b51e7cfe161cd92edb7145ecbd93989e78c94f8c15c61829286fd834d80c931daed4ac4aba14835fd3a6721602bcaa7193245fc6cf8e1d3261460ff3f1cbae3d44690beb989adee69ac486a932ee44dbdf0a44e772ab9822a16753cd08bdbb169f866f722114ee69c0cf1588fdaf8f7efd1c3ed243786078593f9b0cd867bab2b170c1843660d16e2181ae679137e2650551a41631398e027206e22a55858c741079ceafd50d5bd69546d4d52f5a33b0a576e1750d3f83afa1ce4403d768cbd670b443f61794b44705a8b1132c0c0ce77dbd04053ba20aec9baf23944270f10d16ad0727ed490c91c7f469278827c20a3e560f7c84015f7e1bExponent (E): 0x10001

Przed szyfrowaniem ransomware zmienia nazwę pliku, używając formatu <original file name>.[vote2024forjb@protonmail[.]com].encryptedJB (“JB” najprawdopodobniej oznacza inicjały prezydenta Stanów Zjednoczonych Joseph’a Biden’a, biorąc pod uwagę inne treści polityczne w pliku binarnym). Następnie oprogramowanie ransomware nadpisuje zawartość zaszyfrowanymi danymi. PartyTicket zaszyfruje tylko pierwsze 9437184 bajty (9,44 MB) pliku. Jeśli przekazany jako argument plik , jest większy niż ten wskazany limit, wszelkie dane powyżej tego limitu pozostaną niezaszyfrowane. Po zaszyfrowaniu zawartości pliku PartyTicket dołącza na końcu pliku zaszyfrowany klucz AES RSA.

Oprogramowanie ransomware zapisuje również żądanie okupu w formacie HTML, w katalogu pulpitu użytkownika o nazwie read_me.html. Wykonuje to przed rozpoczęciem szyfrowania plików (Rysunek 1). O ile nie są to celowe błędy, konstrukcje gramatyczne zawarte w notatce sugerują, że prawdopodobnie nie została napisana ani zweryfikowana przez osobę biegle posługującą się językiem angielskim.

Rysunek 1. Notatka z żądaniem okupu Ransomware

Ocena

W momencie pisania tego artykułu CrowdStrike Intelligence nie przypisywało działań PartyTicket konkretnej grupie cyber przestępców.

Oprogramowanie ransomware zawiera błędy implementacji, przez co jego szyfrowanie jest możliwe do załamania oraz powolne. Ta luka sugeruje, że autor złośliwego oprogramowania albo nie miał doświadczenia w pisaniu w Go, albo też dysponował zbyt małą ilością wolnego czasu do weryfikacji poprawności jego działania. W szczególności PartyTicket nie jest tak zaawansowany jak DriveSlayer, który implementuje logikę parsowania NTFS niskiego poziomu. Względna niedojrzałość i polityczne przesłanie oprogramowania ransomware, czas wdrażania i ukierunkowanie na podmioty ukraińskie są zgodne z jego wykorzystaniem jako dodatkowa funkcja, tuż obok aktywności DriveSlayer, a nie jako uzasadniona próba wyłudzenia okupu.

 YARA Malware Search Engine

Następujące reguły YARA można użyć do wykrycia PartyTicket:

rule CrowdStrike_PartyTicket_01 : ransomware golang {    meta:        copyright = „(c) 2022 CrowdStrike Inc.”        description = „Detects Golang-based crypter”        version = „202202250130”        last_modified = „2022-02-25”    strings:        $ = „.encryptedJB” ascii        $start = { ff 20 47 6f 20 62 75 69 6c 64 20 49 44 3a 20 22 }        $end = { 0a 20 ff }    condition:        uint16(0) == 0x5A4D and uint32(uint32(0x3C)) == 0x00004550 and        for 1 of ($end) : ( @start < @ and @start + 1024 > @) and        all of them} rule CrowdStrike_PartyTicket_02 : PartyTicket golang {    meta:        copyright = „(c) 2022 CrowdStrike Inc.”        description = „Detects Golang-based PartyTicket ransomware”        version = „202202250130”        last_modified = „2022-02-25”      strings:        $s1 = „voteFor403”        $s2 = „highWay60”        $s3 = „randomiseDuration”        $s4 = „subscribeNewPartyMember”        $s5 = „primaryElectionProces”        $s6 = „baggageGatherings”        $s7 = „getBoo”        $s8 = „selfElect”        $s9 = „wHiteHousE”        $s10 = „encryptedJB”        $goid = { ff 20 47 6f 20 62 75 69 6c 64 20 49 44 3a 20 22 71 62 30 48 37 41 64 57 41 59 44 7a 66 4d 41 31 4a 38 30 42 2f 6e 4a 39 46 46 38 66 75 70 4a 6c 34 71 6e 45 34 57 76 41 35 2f 50 57 6b 77 45 4a 66 4b 55 72 52 62 59 4e 35 39 5f 4a 62 61 2f 32 6f 30 56 49 79 76 71 49 4e 46 62 4c 73 44 73 46 79 4c 32 22 0a 20 ff }        $pdb = „C://projects//403forBiden//wHiteHousE”    condition:        (uint32(0) == 0x464c457f or (uint16(0) == 0x5a4d and uint16(uint32(0x3c)) == 0x4550)) and 4 of ($s*) or $pdb or $goid}

Skrypt do odszyfrowywania zaszyfrowanych plików PartyTicket

Ze względu na omówione wcześniej błędy implementacji w generowaniu klucza AES, możliwe jest odzyskanie klucza AES używanego do szyfrowania przez PartyTicket. Poniższy skrypt Go odszyfrowuje pliki zaszyfrowane przez PartyTicket. Poniższy skrypt Go odszyfrowuje pliki zaszyfrowane próbką PartyTicket 4dc13bb83a16d4ff9865a51b3e4d24112327c526c1392e14d56f20d6f4eaf382. Skrypt przyjmuje plik do odszyfrowania jako argument za pomocą flagi „-p” i zapisuje odszyfrowane dane wyjściowe w „decrypted.bin” w tym samym katalogu. Skrypt może być zbudowany jako plik wykonywalny lub uruchamiany za pomocą pakietu Go run. Został przetestowany przy użyciu wersji Go go1.16.6.

package main import (       „crypto/aes”       „crypto/cipher”       „encoding/hex”       „fmt”       „os”       „flag”) func main() {        encrypted_filepath := flag.String(„p”, „encrypted.bin”, „Path to encrypted file”)       flag.Parse()        fmt.Printf(„Decrypting file : %s\n”, *encrypted_filepath)       key_bytes := []byte(„6FBBD7P95OE8UT5QRTTEBIWAR88S74DO”)       key := hex.EncodeToString(key_bytes)       fmt.Printf(„Decryption key : %s\n”, key_bytes)        dat, err := os.ReadFile(*encrypted_filepath)       if err != nil {              fmt.Println(„Unable to open file, please supply path of encrypted file with flag -p, default file path is ./encrypted.bin”)              os.Exit(3)       }        decrypted_filepath := „decrypted.bin”       filecontents := dat       encrypted_contents := filecontents[:len(filecontents) – 288]       enc_size := len(encrypted_contents)       bsize := 1048604       cycles := enc_size / bsize        if cycles == 0{                encrypted := hex.EncodeToString(encrypted_contents)              decrypted := decrypt(encrypted, key)              write_output(decrypted_filepath, decrypted)              } else {                     for i:=0; i<cycles; i++ { if i >= 9 {                                   start := 9 * bsize                                   end := enc_size                                   data := string(encrypted_contents[start:end])                                   write_output(decrypted_filepath, data)                                   break                            }                            block_start := i * bsize                            block_end := (i+1) * bsize                            if block_end > enc_size{                                   block_end := enc_size                            encrypted:=hex.EncodeToString(encrypted_contents[block_start:block_end])                                   decrypted := decrypt(encrypted, key)                                   write_output(decrypted_filepath, decrypted)                             }                      encrypted:=hex.EncodeToString(encrypted_contents[block_start:block_end])                            decrypted := decrypt(encrypted, key)                            write_output(decrypted_filepath, decrypted)                     }              }               fmt.Printf(„Decrypted file written to : %s\n”, decrypted_filepath)        } func write_output(filepath string, data string) {              f, err := os.OpenFile(filepath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)              if err != nil {                     panic(err)              }              byte_data := []byte(data)              f.Write(byte_data)              f.Close()} func decrypt(encryptedString string, keyString string) (decryptedString string) {        key, _ := hex.DecodeString(keyString)       enc, _ := hex.DecodeString(encryptedString)        block, err := aes.NewCipher(key)       if err != nil {              panic(err.Error())       }       aesGCM, err := cipher.NewGCM(block)       if err != nil {              panic(err.Error())       }       nonceSize := aesGCM.NonceSize()       nonce, ciphertext := enc[:nonceSize], enc[nonceSize:]       plaintext, err := aesGCM.Open(nil, nonce, ciphertext, nil)       if err != nil {              panic(err.Error())       }        return fmt.Sprintf(„%s”, plaintext)}

Źródło CrowdStrike