Недокументированные функции загрузки DLL. Стек вызовов LoadLibrary

от автора

Я успешно проболел половину курса системного программирования во время учебы в институте, и вот, спустя некоторое время, я все-таки решил разобраться, как в ОС Windows загружаются DLL от LoadLibrary до маппинга библиотеки в памяти.

Поисследовав открытые источники, я наткнулся на проект пользователя Github  paskalian WID_LoadLibrary, в котором автор воссоздал исходные коды практически всех этапов загрузки библиотеки. Однако, в настоящее время, с полтычка проект не заводится, так как давно поменялись сигнатуры функций и вообще раньше трава была зеленее.

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

Рассмотрим стек вызовов, реализующий загрузку библиотеки. Напишем простую программу:

 #include "Windows.h"  int main() {     LoadLibraryA("user32.dll"); } 

Скомпилируем, загрузим в IDA и пройдемся отладчиком

Во вкладке модулей загрузим отладочные символы для kernel32, ntdll и kernelbase

Подключимся ProcessHacker и будем отслеживать момент появления библиотеки в списке модулей

Итак, внутренности LoadLibraryA в kernel32:

Здесь просто вызов LoadLibraryA в kernelbase.

Kernelbase_LoadLibraryA:

Здесь видим проверку переданного имени на соответствие twain_32.dll. Если передано «twain_32.dll», к строке прибавляется «C:\Windows\» и снова вызывается kernelbase_LoadLibraryA.

Иначе, вызов пробрасывается в kernelbase_LoadLibraryExA с дополнительными параметрами 0, 0 (зарезервированное поле и пустые флаги)

kernelbase_LoadLibraryExA:

Здесь видим, что вызывается какая-то безымянная функция и вызов пробрасывается в LoadLibraryExW.

На самом деле, здесь происходит преобразование переданной строки к структуре UNICODE_STRING и LLExW передается UNICODE_STRING.Buffer, 0, dwFlags.

kernelbase_LoadLibraryExW:

Сначала проверяются переданные аргументы

if (!lpLibFileName || hFile || ((dwFlags & 0xFFFF0000) != 0) || (DatafileFlags == LLEXW_ASDATAFILE))

И если что-то не так, возвращается ошибка 0xC000000D

Далее инициализируется еще одна UNICODE_STRING из переданного ранее буфера, проверяется на корректность:

Длина не 0, иначе — ошибка

Если в конце переданы пробелы, они удаляются

Если доудалялись снова до пустой строки, то снова ошибка

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

Иначе, флаги проверяются на DONT_RESOLVE_DLL_REFERENCES, LOAD_PACKAGED_LIBRARY, LOAD_LIBRARY_REQUIRE_SIGNED_TARGET, LOAD_LIBRARY_OS_INTEGRITY_CONTINUITY

И загрузка далее продолжается на функции LdrLoadDll. Тут стоит сказать, что многие средства мониторинга хукают именно эту функцию и редко спускаются глубже.

Прототип функции:

typedef NTSTATUS(WINAPI* pLdrLoadDll)(PWCHAR PathToFile, ULONG Flags, PUNICODE_STRING ModuleFileName, PHANDLE ModuleHandle);

Перейдем к LdrLoadDll:

Здесь снова проверка флагов

И далее вызов функции LdrpInitializeDllPath, в которой инициализируется недокументированная структура, которую paskalian описал так:

struct LDR_UNKSTRUCT { PWSTR pInitNameMaybe; __declspec(align(16)) PWSTR Buffer; int Flags; PWSTR pDllName; char Pad1[84]; BOOLEAN IsInitedMaybe; char Pad2[3]; }; 

Прототип функции:

typedef NTSTATUS(__fastcall* pLdrpInitializeDllPath)(PWSTR DllName, PWSTR DllPath, LDR_UNKSTRUCT* DllPathInited);

Затем происходит вызов функции LdrpLoadDll

Эта функция, как и все следующие, уже является неэкспортируемой и крайне редко встречается в интернете.

Прототип функции:

typedef NTSTATUS(__fastcall* pLdrpLoadDll)(PUNICODE_STRING DllName, LDR_UNKSTRUCT* DllPathInited, ULONG Flags, LDR_DATA_TABLE_ENTRY** DllEntry);

Внутри этой функции происходит дополнительная проверка пути к Dll с помощью функции

LdrpPreprocessDllName, в которой резолвится имя, если включен редирект имён, а так же резолвятся DOS-пути.

Прототип:

typedef NTSTATUS(__fastcall* pLdrpPreprocessDllName)(PUNICODE_STRING DllName, PUNICODE_STRING ResName, PULONG pZero, PULONG pFlags);

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

Прототип функции:

typedef NTSTATUS(__fastcall* pLdrpLoadDllInternal)(PUNICODE_STRING FullPath, LDR_UNKSTRUCT* DllPathInited, ULONG Flags, ULONG LdrFlags, PLDR_DATA_TABLE_ENTRY LdrEntry, PLDR_DATA_TABLE_ENTRY LdrEntry2, PLDR_DATA_TABLE_ENTRY* DllEntry, NTSTATUS* pStatus);

LdrFlags и LdrEntry всегда равны 0

Внутри этой функции происходит все самое важное:

Проверяется, загружена ли dll ранее в процесс с помощью LdrpFastpthReloadedDll.

Глобально, функция нам не важна, поэтому прототипа не будет 🙂

Далее вызывается функция LdrpFindOrPrepareLoadingModule, в которой определяется, загружена ли уже dll куда-либо, принадлежит ли она KnownDll, либо возвращается placeholder для dll.

Здесь dll уже может появиться в процессе, как полностью загруженная (как в нашем случае с user32.dll), либо просто появится в списке модулей.

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

Прототип функции:

typedef NTSTATUS(__fastcall* pLdrpProcessWork)(PLDRP_LOAD_CONTEXT LoadContext, BOOLEAN IsLoadOwner);

Далее, тут же, в LdrpLoadDllInternal происходит инициализация Dll, добавление во все списки модулей и так далее.

Перейдем к LdrpProcessWork:

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

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

  • LoadLibrary(A,W)

  • LoadLibraryEx(A,W)

  • LdrLoadDll

  • LdrpLoadDll

  • LdrpLoadDllInternal

  • LdrpProcessWork

Если с LoadLibrary и LoadLibraryEx все совсем понятно, то LdrLoadDll вызывается путем стандартного набора функций:

HMODULE hNtdll = LoadLibraryA("ntdll.dll"); pLdrLoadDll fnLdrLoadDll = (pLdrLoadDll)GetProcAddress(hNtdll, "LdrLoadDll"); UNICODE_STRING ModuleFileName; RtlInitUnicodeStringEx(&ModuleFileName,L"user32.dll"); HANDLE hModule = NULL; NTSTATUS status = fnLdrLoadDll((PWSTR)(0x7F08 | 1), 0, &ModuleFileName, &hModule);

Далее уже сложнее.

Попробуем вызывать неэкспортируемую функцию LdrpLoadDll.

Самый простой способ выглядит так:

Получаем сигнатуру в виде байтов начала функции

Откроем IDA, найдем адрес функции в ntdll и откроем этот адрес в Hex View:

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

BYTE ldrpLoadDllStart[] = { 0x40, 0x55, 0x53, 0x56, 0x57, 0x41, 0x56, 0x41, 0x57, 0x48, 0x8D, 0x6C, 0x24, 0x88, 0x48, 0x81, 0xEC, 0x78, 0x01, 0x00, 0x00, 0x48, 0x8B, 0x05, 0xB8, 0xD1, 0x16, 0x00,  0x48, 0x33, 0xC4, 0x48, 0x89, 0x45, 0x60, 0x48 };

И ищем этот массив в загруженном модуле:

MODULEINFO modinfo = {}; HMODULE hNtdll = LoadLibraryA("ntdll.dll"); GetModuleInformation(GetCurrentProcess(), hNtdll, &modinfo, sizeof(modinfo)); void* addressLdrpLoadDllStart = 0; int size = sizeof(ldrpLoadDllStart); for (int i = 0; i < modinfo.SizeOfImage - size; i++) { if (memcmp((BYTE*)(modinfo.lpBaseOfDll) + i, ldrpLoadDllStart, size) == 0) { addressLdrpLoadDllStart = (BYTE*)(modinfo.lpBaseOfDll) + i; break; } }

И получаем функцию:

pLdrpLoadDll ldrpLoadDll = (pLdrpLoadDll)addressLdrpLoadDllStart;

Также, для корректного вызова, нам нужна функция ldrpInitializeDllPath, которую получаем точно так же.

Вызываем:

LDR_UNKSTRUCT someStruct = {}; LDR_DATA_TABLE_ENTRY* DllEntry = {}; ULONG flags = 0; WCHAR origDllPath[] = L"user32.dll"; UNICODE_STRING uniOrigDllName; RtlInitUnicodeStringEx(&uniOrigDllName, origDllPath); ldrpInitializeDllPath(uniOrigDllName.Buffer, (PWSTR)(0x7F08 | 1), &someStruct); ldrpLoadDll(&uniOrigDllName, &someStruct, NULL, &DllEntry);

Отлично, получили загруженную библиотеку.

Такой способ прост, но перестанет работать при ближайшем обновлении Windows, т.к. часто бывает, что байт-код функций меняется.

Именно поэтому, код paskalian сейчас и не запускается.

Здесь вспоминаем про отличный инструмент от @MichelleVermishelle  SymProcAddress

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

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

Итак, будем использовать идею Михаила с небольшим уточнением. Стандартные символы не содержат нужных нам функций. Поэтому посмотрим, откуда IDA качает символы:

Здесь видим, что символы скачиваются по ссылке вида http://msdl.microsoft.com/download/symbols/{module_name}.pdb/{некий_hash}/{modulename}.pdb

Этот хэш можно получить так:

GetPdbSignature
bool GetPdbSignature(const std::string& dllPath, GUID& pdbGuid, DWORD& pdbAge) { if (!SymInitialize(GetCurrentProcess(), NULL,TRUE)) { return false; } HMODULE hModule = LoadLibraryExA(dllPath.c_str(), NULL, DONT_RESOLVE_DLL_REFERENCES); if (!hModule) { SymCleanup(GetCurrentProcess()); return false; } MODULEINFO modInfo; if (!GetModuleInformation(GetCurrentProcess(), hModule, &modInfo, sizeof(modInfo))) { FreeLibrary(hModule); SymCleanup(GetCurrentProcess()); return false; } DWORD64 baseAddr = reinterpret_cast<DWORD64>(modInfo.lpBaseOfDll); IMAGEHLP_MODULE64 moduleInfo; ZeroMemory(&moduleInfo, sizeof(moduleInfo)); moduleInfo.SizeOfStruct = sizeof(moduleInfo);  if (!SymGetModuleInfo64(GetCurrentProcess(), (DWORD64)modInfo.lpBaseOfDll, &moduleInfo)) { FreeLibrary(hModule); SymCleanup(GetCurrentProcess()); return false; } pdbGuid = moduleInfo.PdbSig70; pdbAge = moduleInfo.PdbAge; FreeLibrary(hModule); SymCleanup(GetCurrentProcess());  return true; } 

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

Получаем GUID и AGE и формируем тот самый «хэш»:

GUID pdbGuid; DWORD pdbAge; GetPdbSignature(dllPath, pdbGuid, pdbAge); wchar_t guid_string[MAX_PATH] = {}; swprintf( guid_string, sizeof(guid_string) / sizeof(guid_string[0]), L"%08x%04x%04x%02x%02x%02x%02x%02x%02x%02x%02x%01x", pdbGuid.Data1, pdbGuid.Data2, pdbGuid.Data3, pdbGuid.Data4[0], pdbGuid.Data4[1], pdbGuid.Data4[2], pdbGuid.Data4[3], pdbGuid.Data4[4], pdbGuid.Data4[5], pdbGuid.Data4[6], pdbGuid.Data4[7], pdbAge);

Формируем URL и скачиваем символы в файл:

downloadDebugSymbols
bool downloadDebugSymbols(const std::wstring& guid, const std::wstring& filename) { std::wstring baseUrl = L"https://msdl.microsoft.com/download/symbols"; std::wstring pdbUrl = baseUrl + L"/" + filename + L"/" + guid + L"/" + filename;  HRESULT hr = URLDownloadToFileW( NULL, pdbUrl.c_str(), filename.c_str(), 0, NULL );  return SUCCEEDED(hr); } bool success = downloadDebugSymbols(guid_string, L"ntdll.pdb"); 

Реализуем получение адреса нужной нам функции:

GetAddressFromSymbols
FARPROC GetAddressFromSymbols(HANDLE hProcess, LPCSTR fullModulePath, LPCSTR pdbPath, LPCSTR lpProcName) { if (!SymInitialize(hProcess, NULL, TRUE)) { printf("SymInitialize failed: %lu\n", GetLastError()); return 0; } if (!SymSetSearchPath(hProcess, pdbPath)) { printf("SymSetSearchPath failed: %lu\n", GetLastError()); SymCleanup(hProcess); return 0; } DWORD64 baseOfDll = SymLoadModuleEx(hProcess, NULL, fullModulePath, NULL, 0, 0, NULL, 0); if (baseOfDll == 0) { printf("SymLoadModuleEx failed: %lu\n", GetLastError()); SymCleanup(hProcess); return 0; } SYMBOL_INFO* symbol = (SYMBOL_INFO*)malloc(sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)); symbol->MaxNameLen = MAX_SYM_NAME; symbol->SizeOfStruct = sizeof(SYMBOL_INFO); if (SymFromName(hProcess, lpProcName, symbol)) { printf("Symbol found: %s at address 0x%0llX\n", symbol->Name, symbol->Address); FARPROC result = (FARPROC)symbol->Address; free(symbol); SymCleanup(hProcess); return result; } else { printf("SymFromName failed: %lu\n", GetLastError()); } free(symbol); SymCleanup(hProcess); return 0; } 

Перейдем к реализации загрузки DLL

Определим все необходимые нам структуры и функции:

Очень много структур и прототипов
typedef struct _LSA_UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWSTR  Buffer; } LSA_UNICODE_STRING, * PLSA_UNICODE_STRING, UNICODE_STRING, * PUNICODE_STRING;  struct LDR_UNKSTRUCT { PWSTR pInitNameMaybe; __declspec(align(16)) PWSTR Buffer; int Flags; PWSTR pDllName; char Pad1[84]; BOOLEAN IsInitedMaybe; char Pad2[3]; }; typedef BOOLEAN(NTAPI* PLDR_INIT_ROUTINE)( _In_ PVOID DllHandle, _In_ ULONG Reason, _In_opt_ PVOID Context ); typedef struct _LDR_SERVICE_TAG_RECORD { struct _LDR_SERVICE_TAG_RECORD* Next; ULONG ServiceTag; } LDR_SERVICE_TAG_RECORD, * PLDR_SERVICE_TAG_RECORD; typedef struct _LDRP_CSLIST { PSINGLE_LIST_ENTRY Tail; } LDRP_CSLIST, * PLDRP_CSLIST; typedef enum _LDR_DDAG_STATE { LdrModulesMerged = -5, LdrModulesInitError = -4, LdrModulesSnapError = -3, LdrModulesUnloaded = -2, LdrModulesUnloading = -1, LdrModulesPlaceHolder = 0, LdrModulesMapping = 1, LdrModulesMapped = 2, LdrModulesWaitingForDependencies = 3, LdrModulesSnapping = 4, LdrModulesSnapped = 5, LdrModulesCondensed = 6, LdrModulesReadyToInit = 7, LdrModulesInitializing = 8, LdrModulesReadyToRun = 9 } LDR_DDAG_STATE; typedef struct _LDR_DDAG_NODE { LIST_ENTRY Modules; PLDR_SERVICE_TAG_RECORD ServiceTagList; ULONG LoadCount; ULONG LoadWhileUnloadingCount; ULONG LowestLink; union { LDRP_CSLIST Dependencies; SINGLE_LIST_ENTRY* RemovalLink; }; LDRP_CSLIST IncomingDependencies; LDR_DDAG_STATE State; SINGLE_LIST_ENTRY* CondenseLink; ULONG PreorderNumber; ULONG Pad; } LDR_DDAG_NODE, * PLDR_DDAG_NODE; typedef struct _RTL_BALANCED_NODE { union { struct _RTL_BALANCED_NODE* Children[2];                              struct { struct _RTL_BALANCED_NODE* Left;                                 struct _RTL_BALANCED_NODE* Right;                                }; }; union { struct { UCHAR Red : 1;                                                     UCHAR Balance : 2;                                                 }; ULONG ParentValue;                                                   }; } RTL_BALANCED_NODE, * PRTL_BALANCED_NODE; typedef enum _LDR_DLL_LOAD_REASON { LoadReasonStaticDependency, LoadReasonStaticForwarderDependency, LoadReasonDynamicForwarderDependency, LoadReasonDelayloadDependency, LoadReasonDynamicLoad, LoadReasonAsImageLoad, LoadReasonAsDataLoad, LoadReasonEnclavePrimary,  LoadReasonEnclaveDependency, LoadReasonPatchImage,  LoadReasonUnknown = -1 } LDR_DLL_LOAD_REASON, * PLDR_DLL_LOAD_REASON; typedef enum _LDR_HOT_PATCH_STATE { LdrHotPatchBaseImage, LdrHotPatchNotApplied, LdrHotPatchAppliedReverse, LdrHotPatchAppliedForward, LdrHotPatchFailedToPatch, LdrHotPatchStateMax, } LDR_HOT_PATCH_STATE, * PLDR_HOT_PATCH_STATE; typedef struct _LDRP_LOAD_CONTEXT { UNICODE_STRING BaseDllName; LDR_UNKSTRUCT* UnkStruct; HANDLE SectionHandle; DWORD Flags; NTSTATUS* pStatus; LDR_DATA_TABLE_ENTRY* Entry; _LIST_ENTRY WorkQueueListEntry; LDR_DATA_TABLE_ENTRY* ReplacedEntry; LDR_DATA_TABLE_ENTRY** pvImports; LDR_DATA_TABLE_ENTRY** IATCheck; PVOID pvIAT; ULONG SizeOfIAT; ULONG CurrentDll; PIMAGE_IMPORT_DESCRIPTOR pImageImportDescriptor; ULONG ImageImportDescriptorLen; __declspec(align(8)) ULONG OriginalIATProtect; PVOID GuardCFCheckFunctionPointer; __int64 GuardFlags; __int64 DllNameLenCompare; __int64 UnknownFunc; SIZE_T Size; __int64 UnknownPtr; HANDLE FileHandle; PIMAGE_DOS_HEADER ImageBase; wchar_t BaseDllNameBuffer[260]; } LDRP_LOAD_CONTEXT, * PLDRP_LOAD_CONTEXT; typedef struct _LDR_DATA_TABLE_ENTRY { LIST_ENTRY InLoadOrderLinks; LIST_ENTRY InMemoryOrderLinks; union { LIST_ENTRY InInitializationOrderLinks; LIST_ENTRY InProgressLinks; }; PIMAGE_DOS_HEADER DllBase; PLDR_INIT_ROUTINE EntryPoint; ULONG SizeOfImage; UNICODE_STRING FullDllName; UNICODE_STRING BaseDllName; union { UCHAR FlagGroup[4]; ULONG Flags; struct { ULONG PackagedBinary : 1; ULONG MarkedForRemoval : 1; ULONG ImageDll : 1; ULONG LoadNotificationsSent : 1; ULONG TelemetryEntryProcessed : 1; ULONG ProcessStaticImport : 1; ULONG InLegacyLists : 1; ULONG InIndexes : 1; ULONG ShimDll : 1; ULONG InExceptionTable : 1; ULONG ReservedFlags1 : 2; ULONG LoadInProgress : 1; ULONG LoadConfigProcessed : 1; ULONG EntryProcessed : 1; ULONG ProtectDelayLoad : 1; ULONG ReservedFlags3 : 2; ULONG DontCallForThreads : 1; ULONG ProcessAttachCalled : 1; ULONG ProcessAttachFailed : 1; ULONG CorDeferredValidate : 1; ULONG CorImage : 1; ULONG DontRelocate : 1; ULONG CorILOnly : 1; ULONG ChpeImage : 1; ULONG ChpeEmulatorImage : 1; ULONG ReservedFlags5 : 1; ULONG Redirected : 1; ULONG ReservedFlags6 : 2; ULONG CompatDatabaseProcessed : 1; }; }; USHORT ObsoleteLoadCount; USHORT TlsIndex; LIST_ENTRY HashLinks; ULONG TimeDateStamp; struct _ACTIVATION_CONTEXT* EntryPointActivationContext; PVOID Lock;  PLDR_DDAG_NODE DdagNode; LIST_ENTRY NodeModuleLink; struct _LDRP_LOAD_CONTEXT* LoadContext; PVOID ParentDllBase; PVOID SwitchBackContext; RTL_BALANCED_NODE BaseAddressIndexNode; RTL_BALANCED_NODE MappingInfoIndexNode; ULONG_PTR OriginalBase; LARGE_INTEGER LoadTime; ULONG BaseNameHashValue; LDR_DLL_LOAD_REASON LoadReason;  ULONG ImplicitPathOptions; ULONG ReferenceCount;  ULONG DependentLoadFlags; UCHAR SigningLevel;  ULONG CheckSum;  PVOID ActivePatchImageBase; LDR_HOT_PATCH_STATE HotPatchState; } LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY;  typedef NTSTATUS(__fastcall* pRtlInitUnicodeStringEx)(PUNICODE_STRING target, PCWSTR source); typedef NTSTATUS(__fastcall* pLdrpLoadDll)(PUNICODE_STRING DllName, LDR_UNKSTRUCT* DllPathInited, ULONG Flags, LDR_DATA_TABLE_ENTRY** DllEntry); typedef NTSTATUS(WINAPI* pfnLdrLoadDll)(PWCHAR PathToFile, ULONG Flags, PUNICODE_STRING ModuleFileName, PHANDLE ModuleHandle); typedef NTSTATUS(__fastcall* pLdrpLoadDllInternal)(PUNICODE_STRING FullPath, LDR_UNKSTRUCT* DllPathInited, ULONG Flags, ULONG LdrFlags, PLDR_DATA_TABLE_ENTRY LdrEntry, PLDR_DATA_TABLE_ENTRY LdrEntry2, PLDR_DATA_TABLE_ENTRY* DllEntry, NTSTATUS* pStatus); typedef NTSTATUS(__fastcall* pLdrpInitializeDllPath)(PWSTR DllName, PWSTR DllPath, LDR_UNKSTRUCT* DllPathInited); typedef NTSTATUS(__fastcall* pLdrpPreprocessDllName)(PUNICODE_STRING DllName, PUNICODE_STRING ResName, PULONG pZero, PULONG pFlags); typedef NTSTATUS(__fastcall* pLdrpFindOrPrepareLoadingModule)(PUNICODE_STRING FullPath, LDR_UNKSTRUCT* DllPathInited, ULONG Flags, ULONG LdrFlags, PLDR_DATA_TABLE_ENTRY LdrEntry, PLDR_DATA_TABLE_ENTRY* pLdrEntryLoaded, NTSTATUS* pStatus); typedef NTSTATUS(__fastcall* pLdrpProcessWork)(PLDRP_LOAD_CONTEXT LoadContext, BOOLEAN IsLoadOwner); 

Добавим пару DEFINE-ов, чтобы было удобно выбирать способ загрузки DLL:

#define MODE_LOADLIBRARYA 0 #define MODE_LOADLIBRARYEXA 1 #define MODE_LDRLOADDLL 2 #define MODE_LDRPLOADDLL 3 #define MODE_LDRPLOADDLLINTERNAL 4 #define MODE_LDRPPROCESSWORK 5

Реализуем загрузку библиотеки всеми возможными способами:

Реализация загрузки библиотеки
switch (mode) { case MODE_LOADLIBRARYA: { hResModule = LoadLibraryA(dllName); break; } case MODE_LOADLIBRARYEXA: { hResModule = LoadLibraryExA(dllName, 0, 0); break; } case MODE_LDRLOADDLL: { pLdrLoadDll fnLdrLoadDll = (pLdrLoadDll)GetProcAddress(hNtDll, "LdrLoadDll"); UNICODE_STRING ModuleFileName; RtlInitUnicodeStringEx(&ModuleFileName, GetWC(dllName)); HANDLE hModule = NULL; NTSTATUS status = fnLdrLoadDll((PWSTR)(0x7F08 | 1), 0, &ModuleFileName, &hModule); break; } case MODE_LDRPLOADDLL: { LDR_UNKSTRUCT someStruct = {}; LDR_DATA_TABLE_ENTRY* DllEntry = {}; ULONG flags = 0; UNICODE_STRING uniDllName; RtlInitUnicodeStringEx(&uniDllName, GetWC(dllName)); pLdrpInitializeDllPath ldrpInitializeDllPath = (pLdrpInitializeDllPath)(GetAddressFromSymbols(GetCurrentProcess(), "C:\\Windows\\System32\\ntdll.dll", "./ntdll.pdb", "LdrpInitializeDllPath")); ldrpInitializeDllPath(uniDllName.Buffer, (PWSTR)(0x7F08 | 1), &someStruct); pLdrpLoadDll ldrpLoadDll = (pLdrpLoadDll)(GetAddressFromSymbols(GetCurrentProcess(), "C:\\Windows\\System32\\ntdll.dll", "./ntdll.pdb", "LdrpLoadDll")); ldrpLoadDll(&uniDllName, &someStruct, NULL, &DllEntry); break; } case MODE_LDRPLOADDLLINTERNAL: { LDR_UNKSTRUCT someStruct = {}; LDR_DATA_TABLE_ENTRY* DllEntry = {}; ULONG flags = 0; UNICODE_STRING uniDllName; RtlInitUnicodeStringEx(&uniDllName, GetWC(dllName)); UNICODE_STRING FullDllPath; WCHAR Buffer[128]; FullDllPath.Length = 0; FullDllPath.MaximumLength = MAX_PATH - 4; FullDllPath.Buffer = Buffer; Buffer[0] = 0; pLdrpPreprocessDllName ldrpPreprocessDllName = (pLdrpPreprocessDllName)(GetAddressFromSymbols(GetCurrentProcess(), "C:\\Windows\\System32\\ntdll.dll", "./ntdll.pdb", "LdrpPreprocessDllName")); pLdrpLoadDllInternal ldrpLoadDllInternal = (pLdrpLoadDllInternal)(GetAddressFromSymbols(GetCurrentProcess(), "C:\\Windows\\System32\\ntdll.dll", "./ntdll.pdb", "LdrpLoadDllInternal"));  NTSTATUS res = ldrpPreprocessDllName(&uniDllName, &FullDllPath, 0, &flags); ldrpLoadDllInternal(&FullDllPath, &someStruct, flags, 0x4, 0, 0, &DllEntry, &res);  break; } case MODE_LDRPPROCESSWORK: { LDR_DATA_TABLE_ENTRY* pLdrEntryLoaded = 0; LDR_UNKSTRUCT undefStruct = {}; UNICODE_STRING uniDllName; RtlInitUnicodeStringEx(&uniDllName, GetWC(dllName)); pLdrpInitializeDllPath ldrpInitializeDllPath = (pLdrpInitializeDllPath)(GetAddressFromSymbols(GetCurrentProcess(), "C:\\Windows\\System32\\ntdll.dll", "./ntdll.pdb", "LdrpInitializeDllPath")); ldrpInitializeDllPath(uniDllName.Buffer, (PWSTR)(0x7F08 | 1), &undefStruct); ULONG flags = 0; UNICODE_STRING FullDllPath; WCHAR Buffer[128]; FullDllPath.Length = 0; FullDllPath.MaximumLength = MAX_PATH - 4; FullDllPath.Buffer = Buffer; Buffer[0] = 0; pLdrpPreprocessDllName ldrpPreprocessDllName = (pLdrpPreprocessDllName)(GetAddressFromSymbols(GetCurrentProcess(), "C:\\Windows\\System32\\ntdll.dll", "./ntdll.pdb", "LdrpPreprocessDllName")); NTSTATUS res = ldrpPreprocessDllName(&uniDllName, &FullDllPath, 0, &flags); pLdrpFindOrPrepareLoadingModule ldrpFindOrPrepareLoadingModule = (pLdrpFindOrPrepareLoadingModule)(GetAddressFromSymbols(GetCurrentProcess(), "C:\\Windows\\System32\\ntdll.dll", "./ntdll.pdb", "LdrpFindOrPrepareLoadingModule")); NTSTATUS Status = ldrpFindOrPrepareLoadingModule(&FullDllPath, &undefStruct, flags, 0x4, 0, &pLdrEntryLoaded, &res); pLdrpProcessWork ldrpProcessWork = (pLdrpProcessWork)(GetAddressFromSymbols(GetCurrentProcess(), "C:\\Windows\\System32\\ntdll.dll", "./ntdll.pdb", "LdrpProcessWork"));  if (Status == STATUS_DLL_NOT_FOUND) NTSTATUS res = ldrpProcessWork(pLdrEntryLoaded->LoadContext, TRUE); break; } 

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

Спасибо paskalian за большое исследование. Многое стало понятнее.

Спасибо @MichelleVermishelle за идею с отладочными символами.

Инструмент доступен на моем GitHub

Подписывайтесь на наш telegram-канал AUTHORITY


ссылка на оригинал статьи https://habr.com/ru/articles/829892/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *