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