概念
Reflective DLL injection,即反射型DLL注入,是一种库注入技术,其中采用反射编程的概念来执行将库从内存加载到主机进程中的操作。因此,该库负责通过实现最小的可移植可执行(PE)文件加载器来加载自身。然后,它可以通过与主机系统和进程的最小交互来控制它将如何加载并与主机交互。
反射DLL注入是一种允许攻击者从内存注入,而不是从磁盘文件将DLL注入目标进程的技术。
概述
假设我们在主机进程中执行代码,并且我们希望注入的库已写入主机进程中内存的任意位置,反射 DLL 注入的工作原理如下:
- 执行通过 CreateRemoteThread() 或小型引导 shellcode 传递到库的 ReflectiveLoader 函数,该函数是库导出表中的导出函数。
- 由于库的图像当前存在于内存中的任意位置,ReflectiveLoader 将首先计算其自己的图像在内存中的当前位置,以便能够解析其自己的标头以供稍后使用。
- 然后ReflectiveLoader将解析主机进程kernel32.dll导出表,以计算加载程序所需的三个函数的地址,即LoadLibraryA、GetProcAddress和VirtualAlloc。
- ReflectiveLoader 现在将分配一个连续的内存区域,它将继续加载自己的图像。该位置并不重要,因为加载程序稍后会正确地重新定位图像。
- 库的标头和节被加载到内存中的新位置。
- 然后 ReflectiveLoader 将处理新加载的图像导入表副本,加载任何其他库并解析它们各自的导入函数地址。
- 然后 ReflectiveLoader 将处理新加载的图像重定位表副本。
- 然后 ReflectiveLoader 将使用 DLL_PROCESS_ATTACH 调用其新加载的图像的入口点函数 DllMain。该库现已成功加载到内存中。
- 最后,ReflectiveLoader 将返回执行到调用它的初始引导 shellcode,或者如果通过 CreateRemoteThread 调用它,则线程将终止。
参考代码:Reflective DLL Injection
需求
在某些业务场景下,希望在被注入进程退出时进行一些额外的操作。一般情况下,在一个进程中,通过LoadLibrary或者通过CreateRemoteThread方式LoadLibrary的DLL,在进程退出时都会调用DLL的DLL_PROCESS_DETACH方法,这样可以在此时做一些清理工作。
通过反射注入的DLL,因为在内存中自己实现对DLL的映射加载,没有调用系统API LoadLibrary函数,导致进程退出时,注入的DLL中的DLL_PROCESS_DETACH不会触发,无法实现后期的清理动作。
方案
当调用LoadLibrary加载DLL时,基本流程如下:
Peb->Ldr->InInitializationOrderModuleList记录了加载后的DLL相关信息,包含DLL的DllMain方法,即:EntryPoint方法,最后触发DLL中的DLL_PROCESS_ATTACH。
当调用FreeLibrary时,基本流程如下:
这里,通过遍历InInitializationOrderModuleList链表,当卸载某一系统dll时,Hook掉它的EntryPoint方法,即可实现在进程退出时进行后期的清理工作
pListHeader = &pLdr->InInitializationOrderModuleList;
pListEntry = (PLIST_ENTRY64)pListHeader->Flink;
for (;;)
{
if ( pListEntry == pListHeader )
{
break;
}
ldrDataTable = CONTAINING_RECORD(pListEntry, LDR_DATA_TABLE_ENTRY64, InInitializationOrderModuleList);
if ( _wcsicmp((PWCHAR)ldrDataTable->BaseDllName.Buffer, L"kernel32.dll") == 0 )
{
// Found it
ldrDataTable->EntryPoint = (ULONG64)DllMainDummy;
break;
}
pListEntry = (PLIST_ENTRY64)pListEntry->Flink;
}
BOOL APIENTRY DllMainDummy( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved )
{
WCHAR Name[MAX_PATH] = {0};
GetModuleFileNameW(hModule, Name, MAX_PATH);
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
wprintf(L"DllMainDummy: Hooked DLL_PROCESS_ATTACH, %ws \r\n", Name);
break;
case DLL_PROCESS_DETACH:
wprintf(L"DllMainDummy: Hooked DLL_PROCESS_DETACH, %ws \r\n", Name);
FreeLibrary(hModule);
break;
}
return TRUE;
}
此时,当进程退出时,就会卸载kernel32.dll,调用kernel32.dll中的DLL_PROCESS_DETACH分支,此时的kernel32.dll中EntryPoint已经被Hook掉了,就会触发我们的DllMainDummy方法。