DLL注入工作原理
简介
我最近研究了一个问题,Winlogon中两个线程的交互导致错误检查。一个线程是初始化GDI的Winlogon线程。这个场景的有趣之处在于另一个线程是如何在这个进程中结束的。
线程在干什么?
下面是线程堆栈的用户一半。线程试图加载DLL
ChildEBP RetAddr Args to Child 0058eaec 773901ad 773901d9 0058eafc 00240022 ntdll!KiFastSystemCallRet 0058eb0c 775d96f3 775d1808 00000000 77e6f032 USER32!NtUserRegisterWindowMessage+0xc 0058ed24 775e4755 00000000 00000001 7c837512 comctl32!InitGlobalMetrics+0x44 0058ed3c 775e426a 00000031 0058ed68 7763490c comctl32!_ProcessAttach+0x98 0058ed48 7763490c 775d0000 00000001 00000000 comctl32!DllMain+0x21 0058ed68 7c81a352 775d0000 00000001 00000000 comctl32!_DllMainCRTStartup+0x52 0058ed88 7c833465 776348ba 775d0000 00000001 ntdll!LdrpCallInitRoutine+0x14 0058ee90 7c834311 00000000 00000000 7c8e2e58 ntdll!LdrpRunInitializeRoutines+0x367 0058f124 7c834065 00000000 00080e98 0058f3ec ntdll!LdrpLoadDll+0x3cd 0058f3a0 77e41bf3 00080e98 0058f3ec 0058f3cc ntdll!LdrLoadDll+0x198 0058f408 77e5c70b 7c8e2e58 00000000 00000000 kernel32!LoadLibraryExW+0x1b2 0058f41c 7c92a6a1 7c8e2e58 00000000 7c8e2e58 kernel32!LoadLibraryW+0x11 0058f454 7c92a65f 7c8e2e58 7c8d0000 7c9297b6 SHELL32!SHFusionLoadLibrary+0x2a 0058f460 7c9297b6 00000020 00000008 0058f6a8 SHELL32!DelayLoadCC+0x15 0058f694 7c929728 0058f6a8 0000007c 00000001 SHELL32!SHFusionInitializeIDCC+0x92 0058f8b4 7c92966f 7c8d0000 0000007c 00000001 SHELL32!SHFusionInitializeFromModuleID+0x3a 0058f8c8 7c92962c 7c8d0000 00000001 0058f8f8 SHELL32!_ProcessAttach+0x34 0058f8d8 7c92bb63 7c8d0000 00000001 00000000 SHELL32!DllMain+0x27 0058f8f8 7c81a352 7c8d0000 00000001 00000000 SHELL32!_DllMainCRTStartup+0x52 0058f918 7c833465 7c92bb1b 7c8d0000 00000001 ntdll!LdrpCallInitRoutine+0x14 0058fa20 7c834311 00000000 00000000 00000004 ntdll!LdrpRunInitializeRoutines+0x367
这个函数是加载并调用init依赖DLL的函数
0058fcb4 7c834065 00000000 00080760 0058ff7c ntdll!LdrpLoadDll+0x3cd 0058ff30 77e41bf3 00080760 0058ff7c 0058ff5c ntdll!LdrLoadDll+0x198
0058ff5c是Unicode字符串指向DLL名称的指针
0058ff98 77e5c70b 00570254 00000000 00000000 kernel32!LoadLibraryExW+0x1b2 0058ffac 0057017e 00570254 00000000 00200008 kernel32!LoadLibraryW+0x11 WARNING: Frame IP not in any known module. Following frames may be wrong. 0058fff4 00000000 00570228 00905a4d 00000003 0x57017e
正在加载的DLL依赖于其他DLL。加载第一个DLL时,将加载并初始化这些DLL。因此,如果DLL'A'调用了DLL'B',则加载程序在加载DLL'A'时加载DLL'B'。
What is so unusual about this thread?
1: kd> !thread THREAD 86edd020 Cid 7884.7528 Teb: 7ffdc000 Win32Thread: bc1adb48 RUNNING on processor 1 Not impersonating DeviceMap e10018c0 Owning Process 87c51d88 Image: winlogon.exe Wait Start TickCount 2567944 Ticks: 0 Context Switch Count 4 LargeStack UserTime 00:00:00.015 KernelTime 00:00:00.000 Start Address 0x00570000
Start Address。这不是显示在"!peb"输出的任何模块中。
这个!PEB扩展将显示加载的模块列表和进程的地址范围。由于空间原因,此处未显示。但是这个地址不在任何加载的模块中。
我们来看看函数:
00570000 55 push ebp 00570001 8bec mov ebp,esp 00570003 83ec3c sub esp,3Ch 00570006 8365e800 and dword ptr [ebp-18h],0 0057000a 8365ec00 and dword ptr [ebp-14h],0 0057000e 8365f800 and dword ptr [ebp-8],0 00570012 8365dc00 and dword ptr [ebp-24h],0 00570016 8365f000 and dword ptr [ebp-10h],0 1: kd> u 0057001a 8365e000 and dword ptr [ebp-20h],0 0057001e 8365f400 and dword ptr [ebp-0Ch],0 00570022 6a01 push 1 00570024 8b4508 mov eax,dword ptr [ebp+8] ß 第一个参数是指向函数列表的指针. 00570027 ff5004 call dword ptr [eax+4] 0057002a 8945fc mov dword ptr [ebp-4],eax 0057002d 8b4508 mov eax,dword ptr [ebp+8] ß Function block. 00570030 ff5010 call dword ptr [eax+10h] 1: kd> u 00570033 8945e4 mov dword ptr [ebp-1Ch],eax 00570036 837de400 cmp dword ptr [ebp-1Ch],0 0057003a 0f84c0010000 je 00570200第一个参数是一个函数块。这就是作为初始参数传递的。传递了哪些函数? 1: kd> dds 570228 l 5 00570228 77e5c6fa kernel32!LoadLibraryW 0057022c 77e6c2dc kernel32!SetErrorMode 00570230 77e70531 kernel32!GetCurrentDirectoryW 00570234 77e70d67 kernel32!SetCurrentDirectoryW 00570238 77e63ec7 kernel32!GetProcessHeap
这些函数是标准的kernel32调用。所以,问题是它为什么要这么做?
线程在干什么?
基于IP不在任何模块中的事实,IP是页对齐的,并且线程被传递函数地址作为它的初始参数,看起来这个线程被“注入”到这个进程中。
线程是怎么注射的?
不知道在另一个进程中分配了哪个进程。这个函数接受一个进程句柄作为输入,它可以是一个不同的进程。然后代码可以通过WriteProcessMemory移动到进程中。然后可以使用从VirtualAllocEx返回的内存地址的起始地址创建线程。
我们结束了吗?
不——还记得地址块吗?这是必需的,因为加载程序没有加载模块。所以函数没有被链接器解析。所以移动的代码之外的函数的地址是未知的。由于在WindowsServer2003中,某些DLL的函数保持在同一地址,因此可以将它们传递给另一个进程。Vista和beyond无法执行此操作,因此此方法将不起作用。