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?

如果你用!thread检查IP的起始地址,当它调用LoadLibraryW时,IP不在任何加载模块的范围内。
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无法执行此操作,因此此方法将不起作用。

posted on 2020-10-08 08:36  活着的虫子  阅读(812)  评论(0编辑  收藏  举报

导航