Skip to content
KitploitKITPLOIT
ИнструментыЭксплойтыБлог
Log in
Отправить
ИнструментыЭксплойтыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
LdrShuffle — Техника выполнения/внедрения кода с использованием манипуляции структурой модулей DLL в PEB | Kitploit
Инструменты/GitHubGitHub/rwxstoned/ldrshuffle
Анализ КодаЭксплуатацияЛатеральное перемещениеШелл-кодПост-эксплуатацияТестирование на ПроникновениеRed TeamingРазработка Полезной НагрузкиЭксплуатация Бинарных Файлов
GitHubrwxstoned/ldrshuffle

LdrShuffle

Техника выполнения/внедрения кода с использованием манипуляции структурой модулей DLL в PEB

28944101 год назадПроверено Kitploit

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться
Репозиторий

LdrShuffle

Скрытное выполнение кода путем изменения EntryPoint загруженных модулей во время выполнения.

Краткое описание

Процессы Windows имеют различные модули, загруженные во время выполнения. Каждый из этих модулей имеет определенную функцию DllMain(), которая будет вызвана при создании/уничтожении процесса или потока (четыре возможных сценария).

Чтобы правильно вызывать эти функции в течение жизненного цикла процесса, функции загрузчика Windows (ntdll!Ldrp*) обращаются к списку записей, содержащих ключевые параметры (включая поле EntryPoint) для каждого модуля.

Перезаписывая этот EntryPoint для DLL, мы гарантируем, что выполнение кода будет перенаправлено в нужное нам место.

Варианты использования

Это можно использовать как примитив для выполнения кода, так и для проксирования API, например, для выполнения определенных API с не подозрительным стеком вызовов, поскольку они будут вызваны легитимными функциями Windows.

Это также можно использовать для запуска выполнения в удаленном процессе, если атакующий имеет возможность читать и записывать память в целевом процессе. Аналогично Threadless Injection, это позволяет выполнять код в процессе без вызова классических API, связанных с выполнением (CreateRemoteThread, QueueUserAPC).

Проблемы

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

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

Реализация

Повторение основ загрузки DLL в Windows

Каждый процесс поддерживает список структур _LDR_DATA_TABLE_ENTRY во время выполнения. Эти структуры содержат множество деталей, относящихся к DLL, таких как ее EntryPoint (который мы будем перезаписывать), имя, некоторые хэши, временные метки, различные флаги и т. д. Некоторые из этих структур документированы, некоторые — нет.

Их можно просмотреть с помощью этой команды WinDbg:

dt nt!_LDR_DATA_TABLE_ENTRY 0xdeadbeef

0:006> dt nt!_LDR_DATA_TABLE_ENTRY 0x18d1c4838c0
ntdll!_LDR_DATA_TABLE_ENTRY
   +0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x0000018d`1c485de0 - 0x0000018d`1c4832b0 ]
   +0x010 InMemoryOrderLinks : _LIST_ENTRY [ 0x0000018d`1c485df0 - 0x0000018d`1c4832c0 ]
   +0x020 InInitializationOrderLinks : _LIST_ENTRY [ 0x0000018d`1c4832d0 - 0x0000018d`1c482c80 ]
   +0x030 DllBase          : 0x00007ffe`e87e0000 Void
   +0x038 EntryPoint       : 0x00007ffe`e8838d00 Void
   +0x040 SizeOfImage      : 0x2fe000
   +0x048 FullDllName      : _UNICODE_STRING "C:\WINDOWS\System32\KERNELBASE.dll"
   +0x058 BaseDllName      : _UNICODE_STRING "KERNELBASE.dll"
   +0x068 FlagGroup        : [4]  "???"
   +0x068 Flags            : 0x8a2cc
   +0x068 PackagedBinary   : 0y0
   +0x068 MarkedForRemoval : 0y0
   +0x068 ImageDll         : 0y1
(...)
   +0x0e0 MappingInfoIndexNode : _RTL_BALANCED_NODE
   +0x0f8 OriginalBase     : 0x00007ffe`e87e0000
   +0x100 LoadTime         : _LARGE_INTEGER 0x01db5f86`2fa735fc
   +0x108 BaseNameHashValue : 0x235bec4
   +0x10c LoadReason       : 0 ( LoadReasonStaticDependency )
   +0x110 ImplicitPathOptions : 0x4000
   +0x114 ReferenceCount   : 1
   +0x118 DependentLoadFlags : 0x800
   +0x11c SigningLevel     : 0 ''

Адрес структуры можно получить, обойдя дважды связанную структуру, на которую ссылается PEB процесса в структуре PEB_LDR_DATA.

dt nt!_PEB_LDR_DATA 0xb4b4c3c3

Обратите внимание на флаг DontCallForThreads. Как следует из названия, если этот флаг установлен, ОС НЕ будет вызывать DllMain() этого модуля для событий потока (т. е. DLL_THREAD_ATTACH или DLL_THREAD_DETACH).

При создании DLL необходимо следовать следующему шаблону, чтобы корректно взаимодействовать с функциями загрузчика ОС:

BOOL WINAPI DllMain(
    HINSTANCE hinstDLL,  // дескриптор модуля DLL
    DWORD fdwReason,     // причина вызова функции
    LPVOID lpvReserved ) // зарезервировано
{
    // Выполнение действий в зависимости от причины вызова.
    switch( fdwReason ) 
    { 
        case DLL_PROCESS_ATTACH:
         // Инициализация один раз для каждого нового процесса.
         // Возврат FALSE для неудачной загрузки DLL.
            break;

        case DLL_THREAD_ATTACH:
         // Инициализация, специфичная для потока.
            break;

        case DLL_THREAD_DETACH:
         // Очистка, специфичная для потока.
            break;

        case DLL_PROCESS_DETACH:
        
            if (lpvReserved != nullptr)
            {
                break; // не выполнять очистку при завершении процесса
            }
            
         // Выполнение необходимой очистки.
            break;
    }
    return TRUE;  // Успешная DLL_PROCESS_ATTACH.
}

Технические детали реализации

Настройка вызова API

Как описано выше, методика временно перезаписывает EntryPoint DLL, чтобы перенаправить выполнение. Поскольку мы не контролируем ничего, кроме перенаправления выполнения, необходимо предпринять некоторые меры для обработки того, что мы хотим запустить, с какими аргументами и как получить возвращаемое значение.

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

Эта структура определена следующим образом:

typedef struct _DATA_T {
    // Манипуляции со структурами LDR
    ULONG_PTR   runner;             // вредоносная точка входа для выполнения
    ULONG_PTR   bakOriginalBase;    // резервная копия перезаписанного OriginalBase
    ULONG_PTR   bakEntryPoint;      // резервная копия перезаписанного EntryPoint
    HANDLE      event;              // событие, сигнализирующее о выполнении Runner
    // вызов функции
    ULONG_PTR   ret;                // возвращаемое значение
    DWORD       createThread;       // запустить этот вызов API в новом потоке (требуется для wininet/winhttp)
    ULONG_PTR   function;           // вызываемая Windows API
    DWORD       dwArgs;             // количество аргументов
    ULONG_PTR   args[MAX_ARGS];     // массив аргументов
} DATA_T, * PDATA_T;

Для настройки выполнения API необходимо подготовить эти поля. Значение ret будет содержать возвращаемое значение после выполнения. event используется для синхронизации, чтобы сигнализировать о завершении выполнения. Все остальные поля являются входными и определяют, какой API вызывать (function), с какими аргументами (dwArgs и args[]), адрес функции Runner(), куда перенаправляется выполнение, и резервные копии перезаписанных исходных записей DLL (bakOriginalBase и bakEntryPoint).

Поле createThread должно быть установлено в 1 для тех сложных функций API, которые не будут работать в среде DllMain() (сюда входят многие библиотеки wininet и winhttp).

Вот пример настройки вызова MessageBoxA(), как показано в PoC:

 pDataT->dwArgs = 4;
 pDataT->runner = (ULONG_PTR)Runner;
 pDataT->function = (ULONG_PTR)MessageBoxA;
 pDataT->args[0] = (ULONG_PTR)0;
 pDataT->args[1] = (ULONG_PTR)"Hello";
 pDataT->args[2] = (ULONG_PTR)"LDRSHUFFLE";
 pDataT->args[3] = (ULONG_PTR)MB_OKCANCEL;
 pDataT->event = CreateEventA(NULL, FALSE, FALSE, "ExecEvt");

Изменение _LDR_DATA_TABLE_ENTRY

Функция UpdateLdr() отвечает за внесение соответствующего изменения в _LDR_DATA_TABLE_ENTRY целевого модуля.

RestoreLdr() восстановит эти изменения на более позднем этапе (вызывается из Runner()).

Скачать инструмент