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

Комментарии (3)


  1. Emelian
    19.07.2024 04:02
    +1

    Вспомнил свои наработки на эту тему. Вот мой сайт 2010-2011 года : https://erfaren.narod.ru/ (сейчас, правда, мы уже в составе России). Там, в статье «IdaPro v.6.1 demo: Серьезное испытание» осуществлена полная перекомпиляция файлов explorer.exe и comctl32.dll. Интересный был проект, но развития не получил, по разным причинам…


  1. KirpaPuto
    19.07.2024 04:02
    +2

    Редкий случай, когда ссылка на ТГ канал к месту.


  1. Tarmik
    19.07.2024 04:02
    +1

    Мне интереснее было бы подгрузить .DLL с RAMа используя недокументированные API.

    Я делал что-то подобное здесь: https://github.com/tapika/dllloader

    Но я использую API которые помечены как deprecated, что означает что API могут в любой момент удалить.