July 25, 2024

DoNex/DarkRace Ransomware Decryptor

Computest Sector 7 was asked by Team High-Tech Crime of the Dutch Police to help with writing a decryptor for the DoNex/DarkRace ransomware. DoNex is a relatively new ransomware group, which probably explains why its encryptor contains a simple to abuse mistake. It appears to be the same group that was working under the name DarkRace last year, as the DoNex encryptor we investigated is essentially the same as a DarkRace encryptor we looked at. We have submitted our decryptor to the No More Ransom initiative to help victims recover their files for free.

Gijs Rijnders, the analyst from the Dutch Police who worked on this, has found this weakness in the encryption procedure: it uses a stream cipher (Salsa20) without generating a new nonce or key for each file. Stream ciphers work by generating a keystream, based on the key and nonce, and then XOR-ing the keystream with the parts of the files that are encrypted. By re-using the key and nonce, all encrypted files end up encrypted with the same keystream. Therefore, it would be possible to find an encrypted file for which the unencrypted file is still available, then XOR those files to recover the keystream. With the keystream all other files can be recovered (even though the actual key is not known). For more information, Gijs presented his research at REcon 2024.

In practice, having the unencrypted version of a file still available is quite common after a ransomware attack: for example, the file might have been downloaded from the internet and still available online, or received by email and still available in a mailbox. How much of the keystream do we need to recover? Thankfully, we don’t need to find the original of the largest encrypted file. Any file that is at least 1 MiB will do.

We investigated a sample of the ransomware encryptor (SHA256 hash: 0adde4246aaa9fb3964d1d6cf3c29b1b13074015b250eb8e5591339f92e1e3ca) to make sure we fully understood how the encryption procedure works. See the rest of this post for these findings, and if you are interested in the decryptor, see the page with the decryption tools at No More Ransom.

Encryption process

Configuration

First, the device reads its own configuration from an XML string in its data section. This string is XORed with the byte 0xA9.

for ( i = 0; i < 0x21C0; i += 64 )
{
	*(__m128i *)&xml_config[i] = _mm_xor_si128((__m128i)xmmword_4295D0, *(__m128i *)&xml_config[i]);
	*(__m128i *)&xml_config[i + 16] = _mm_xor_si128((__m128i)xmmword_4295D0, *(__m128i *)&xml_config[i + 16]);
	*(__m128i *)&xml_config[i + 32] = _mm_xor_si128(*(__m128i *)&xml_config[i + 32], (__m128i)xmmword_4295D0);
	*(__m128i *)&xml_config[i + 48] = _mm_xor_si128((__m128i)xmmword_4295D0, *(__m128i *)&xml_config[i + 48]);
}
for ( ; i < 0x21E7; ++i )
	xml_config[i] ^= 0xA9u;

Result:

<?xml version='1.0' encoding='UTF-8'?>
<root>
	<white_extens>386;adv;ani;bat;bin;cab;cmd;com;cpl;cur;deskthemepack;diagcab;diagcfg;diagpkg;dll;drv;exe;hlp;icl;icns;ico;ics;idx;lnk;mod;mpa;msc;msp;msstyles;msu;nls;nomedia;ocx;prf;ps1;rom;rtp;scr;shs;spl;sys;theme;themepack;wpx;lock;key;hta;msi;pdb;search-ms</white_extens>
	<white_files>bootmgr;autorun.inf;boot.ini;bootfont.bin;bootsect.bak;desktop.ini;iconcache.db;ntldr;ntuser.dat;ntuser.dat.log;ntuser.ini;thumbs.db;GDIPFONTCACHEV1.DAT;d3d9caps.dat</white_files>
	<white_folders>$recycle.bin;config.msi;$windows.~bt;$windows.~ws;windows;boot;program files;program files (x86);programdata;system volume information;tor browser;windows.old;intel;msocache;perflogs;x64dbg;public;all users;default;microsoft;appdata</white_folders>
	<kill_keep>sql;oracle;mysq;chrome;veeam;firefox;excel;msaccess;onenote;outlook;powerpnt;winword;wuauclt</kill_keep>
	<services>vss;sql;svc$;memtas;mepocs;msexchange;sophos;veeam;backup;GxVss;GxBlr;GxFWD;GxCVD;GxCIMgr</services>
	<black_db>ldf;mdf</black_db>
	<encryption_thread>30</encryption_thread>
	<walk_thread>15</walk_thread>
	<local_disks>true</local_disks>
	<network_shares>true</network_shares>
	<kill_processes>true</kill_processes>
	<kill_services>true</kill_services>
	<shutdown_system>true</shutdown_system>
	<delete_eventlogs>true</delete_eventlogs>
	<cmd>wmic shadowcopy delete /nointeractive</cmd>
	<cmd>vssadmin Delete Shadows /All /Quiet</cmd>
	<content>            !!! DoNex ransomware warning !!!

&gt;&gt;&gt;&gt; Your data are stolen and encrypted

	The data will be published on TOR website if you do not pay the ransom

	Links for Tor Browser:
	(...)


&gt;&gt;&gt;&gt; What guarantees that we will not deceive you?

	We are not a politically motivated group and we do not need anything other than your money.

	If you pay, we will provide you the programs for decryption and we will delete your data.

	If we do not give you decrypters, or we do not delete your data after payment, then nobody will pay us in the future.

	Therefore to us our reputation is very important. We attack the companies worldwide and there is no dissatisfied victim after payment.


&gt;&gt;&gt;&gt; You need contact us and decrypt one file for free on these TOR sites with your personal DECRYPTION ID

	Download and install TOR Browser https://www.torproject.org/
	Write to a chat and wait for the answer, we will always answer you.

	You can install qtox to contanct us online https://tox.chat/download.html
	Tox ID Contact: (...)

	Mail (OnionMail) Support: (...)

&gt;&gt;&gt;&gt; Warning! Do not DELETE or MODIFY any files, it can lead to recovery problems!

&gt;&gt;&gt;&gt; Warning! If you do not pay the ransom we will attack your company repeatedly again!
	</content>
	<ico>AAA(...)</ico>
</root>

The configuration specifies for example which file extensions and folders to ignore, which processes to terminate and what commands to run first, the ransom note and an icon.

Then, it runs the specified commands one by one. For our sample, the commands are:

  • wmic shadowcopy delete /nointeractive
  • vssadmin Delete Shadows /All /Quiet

Keys

Next, it generates the key that will be used for the Salsa20 encryption of files. It calls CryptGenRandom to generate 16 random bytes, so sadly, predictable keys here are ruled out.

The Salsa20 key is then encrypted using a RSA public key. This encrypted key is appended to every file encrypted by the ransomware. The RSA public key used for the encryption is also obtained from the executable, but it is in the overlay data (the data after the data for the PE format). To read it, it opens its own executable file and then parses the PE header to find the end of the last section and starts reading the data after that. This is done to make it easy to generate a new version, with a different encryption key, allowing the files for each customer to be encrypted using a new RSA key.

The key data format consists of a 512 byte hex-encoded modulus, followed by 6 hex-encoded bytes for the exponent (0x10001, or 65537), together denoting the RSA public key. Finally, the last 9 bytes are the file extension that is appended to every file (f58A66B51 for our sample). The file extension is located in the overlay data too, as it is likely to change in a new version or for different victims.

00037e00  45 33 39 35 38 38 30 30  41 34 45 45 37 34 42 46  |E3958800A4EE74BF|
00037e10  35 39 38 33 39 36 37 45  33 43 36 35 38 36 39 33  |5983967E3C658693|
00037e20  43 41 39 33 37 37 37 45  42 38 43 41 37 39 46 44  |CA93777EB8CA79FD|
00037e30  37 32 34 46 36 45 36 46  37 31 43 44 34 37 32 34  |724F6E6F71CD4724|
00037e40  46 46 43 44 30 46 32 34  34 41 45 42 45 33 33 42  |FFCD0F244AEBE33B|
00037e50  38 37 43 43 39 46 34 35  33 38 37 38 41 42 30 43  |87CC9F453878AB0C|
00037e60  32 44 44 36 39 34 30 36  43 38 41 44 46 41 43 45  |2DD69406C8ADFACE|
00037e70  37 41 46 39 46 41 45 34  36 41 33 37 42 35 45 35  |7AF9FAE46A37B5E5|
00037e80  46 43 38 33 35 44 42 33  41 45 33 46 32 32 36 31  |FC835DB3AE3F2261|
00037e90  43 44 37 36 38 46 35 35  43 45 31 35 46 33 32 37  |CD768F55CE15F327|
00037ea0  45 36 44 42 36 31 34 32  38 33 30 41 36 43 46 35  |E6DB6142830A6CF5|
00037eb0  39 39 38 31 34 33 33 33  30 32 36 38 43 46 43 37  |998143330268CFC7|
00037ec0  31 35 35 45 33 42 31 42  30 31 36 31 42 41 31 30  |155E3B1B0161BA10|
00037ed0  39 34 30 33 46 44 46 41  33 44 36 31 41 30 33 41  |9403FDFA3D61A03A|
00037ee0  44 32 34 45 44 37 46 32  42 34 31 45 38 41 30 42  |D24ED7F2B41E8A0B|
00037ef0  41 45 37 34 41 38 43 39  33 38 46 39 37 42 36 34  |AE74A8C938F97B64|
00037f00  38 35 34 36 43 45 33 45  43 30 41 44 38 42 34 31  |8546CE3EC0AD8B41|
00037f10  31 35 41 31 35 36 44 35  36 38 45 45 34 39 39 42  |15A156D568EE499B|
00037f20  30 44 36 34 31 31 42 30  46 39 42 43 36 45 35 30  |0D6411B0F9BC6E50|
00037f30  38 37 44 30 36 32 45 33  44 35 34 31 42 33 46 45  |87D062E3D541B3FE|
00037f40  30 42 39 35 30 34 31 32  43 33 39 39 32 37 36 45  |0B950412C399276E|
00037f50  42 30 45 46 34 44 33 39  37 34 33 41 45 38 34 31  |B0EF4D39743AE841|
00037f60  31 42 36 42 34 32 44 42  42 43 35 36 39 34 32 34  |1B6B42DBBC569424|
00037f70  35 38 31 36 41 43 31 42  46 39 39 45 35 41 31 42  |5816AC1BF99E5A1B|
00037f80  31 46 43 33 38 37 30 32  31 33 45 43 41 38 34 35  |1FC3870213ECA845|
00037f90  38 30 37 44 35 44 46 38  31 45 42 30 37 41 43 39  |807D5DF81EB07AC9|
00037fa0  46 37 36 38 39 34 45 33  31 42 38 39 41 32 36 34  |F76894E31B89A264|
00037fb0  30 46 41 34 30 38 35 38  43 44 45 46 44 32 42 39  |0FA40858CDEFD2B9|
00037fc0  35 31 42 34 30 39 32 37  42 36 34 46 43 31 41 33  |51B40927B64FC1A3|
00037fd0  36 34 46 43 45 37 38 31  44 46 34 44 39 30 45 42  |64FCE781DF4D90EB|
00037fe0  38 46 36 34 44 43 38 44  34 30 39 39 41 32 33 33  |8F64DC8D4099A233|
00037ff0  45 42 33 44 37 39 31 35  30 38 38 30 39 32 38 39  |EB3D791508809289|
00038000  30 31 30 30 30 31 66 35  38 41 36 36 42 35 31     |010001f58A66B51|

Encryption

Then starts the encryption phase of the attack. If in the configuration “local_disks” is set to “true” (which was the case for our sample), it first encrypts the files on all local disks. Then, if “network_shares” is set to “true” it will continue with encrypting the files on mapped network shares as well.

Before encrypting the local disks, it will terminate the processes listed in the “services” configuration variable. It also writes a .bat file that will loop and kill the processes in the “kill_keep” variable, presumably because they would automatically be relaunched.

 v1 = fopen("C:\\ProgramData\\1.bat", "wb");
fwrite(":start\r\n", 1u, 8u, v1);
fwrite("ping 127.0.0.1 -n 2 >nul ", 1u, 0x19u, v1);
value_from_config = get_value_from_config(config, config, (int)"kill_keep", 0, 0, 1);
value_from_xml_entry = (const char *)get_value_from_xml_entry(value_from_config);
v4 = (char *)sub_401EA0(value_from_xml_entry, (char *)L";");
for ( i = v4; *i; i += strlen(i) + 1 )
{
	fwrite("& taskkill /f /im ", 1u, 0x12u, v1);
	fwrite(i, 1u, strlen(i), v1);
	fwrite("* ", 1u, 2u, v1);
}
fwrite("\r\ngoto start", 1u, 0xCu, v1);
fclose(v1);
Sleep(1000u);
WinExec("cmd /c C:\\ProgramData\\1.bat", 0);

Then it starts the actual encryption. Enumerating the files and encrypting them use a configurable number of threads (15 and 30 for our sample respectively).

Like many ransomware families, it only encrypts certain parts of each file. Exactly how much of the file it encrypts depends on the file size:

  • If the file is less than 1 MiB, it is fully encrypted.
  • Otherwise, if the file is 10 MiB or less, the first 1 MiB is encrypted.
  • Otherwise, if the file is 100 MiB or less, it encrypts five blocks of 1 MiB each.
  • Otherwise (i.e.: the file is larger than 100 MiB), it encrypts a hundred blocks of 1 MiB each.

Additionally, the keystream is not just reused between files, but the state of the encryption algorithm is reset for every 1 MiB encrypted block as well. This is why our pair of encrypted and unencrypted files only needs the original to be 1 MiB or larger to allow us to recover the entire keystream.

For files of 10 MiB or more, the the blocks that are encrypted get distributed over the file. The exact method for this depends on whether the file is local or on a network share.

For files on a network share, the blocks are distributed uniformly over the file: it takes the entire size of the file, divides that by the number of blocks and for each file fragment of that size, the first 1 MiB is encrypted.

For local files, the logic is a bit more complicated. It still divides the size of the file by the number of blocks, but it rounds up the start address of each block to the Windows allocation granularity (64 kiB). They do this because it is required when memory mapping a file using the CreateFileMappingA and MapViewOfFile Windows API functions.

This procedure actually contains a bug: there is problematic range of filesizes just over 100 MiB (104857700 - 104922624 bytes) where the last block it wants to write is of bounds of the file mapping due to the rounding up, which means MapViewOfFile returns an error. This means the last block is not encrypted.

After encrypting a file, a footer is appended to the file. This footer is 512 bytes, where the first 256 contain the RSA encrypted Salsa20 key and the last 256 bytes are NUL. For the files where the last block failed, the footer is also not written, making it 512 NUL bytes. Technically, these files cannot be decrypted by the official decryption tool provided by the attacker because it would rely on the footer to contain the correct key.

Finally, a ransom note is written into the directory where the encrypted file was.

Cleanup

Finally, once all encryption threads are finished the ransomware will clear the “application”, “system” and “security” eventlogs (if “delete_eventlogs” is set), clean up itself and the 1.bat script and reboot (if “shutdown_system” is set).

Decryptor

We have written a decryptor for this ransomware that decrypts all files, when given the plaintext and encrypted version of a file. This decryptor has to follow the logic of which blocks to decrypt from the encryptor, including the buggy range at just over 100 MiB. The decryptor can be downloaded for free from No More Ransom (alternative link), including the user manual.

Screenshot of the decryptor.

Menu