Я успешно проболел половину курса системного программирования во время учебы в институте, и вот, спустя некоторое время, я все-таки решил разобраться, как в ОС 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/
Добавить комментарий