DLL注入和API拦截
1.0 SetWindowLongPtr(hwnd, GWLP_WNDPROC,MySubclassProc);改变窗口的行为。
如果打算从同一个进程中的窗口派生子类窗口,那么应该利用SetWindowSubclass,GetWindowSubclass,RemoveWindowSubclass,以及DefSubclassProc,参阅Subclassing Controls http://msdn.microsoft.com/en-us/library/bb773183%28VS.85%29.aspx 。
1.1 使用注册表来注入DLL
HKLM/Software/Microsoft/Windows NT/CurrentVersion/Windows/ 下AppInit_Dlls键的值可能 包含一个或一组DLL的文件名(通过空格或逗号分隔)。第一个DLL可以包含全路径,但其他包含的路径则将被忽略。我们最好将自己的DLL放到windows目录。创建一项LoadAppInit_Dlls的DWORD值为1启用这个注册表项。当User32.dll被映射到进程,收到DLL_PROCESS_ATTACH通知时会取得上述注册表键的值,并用LoadLibrary来载入这个字符串指定的每个DLL。
缺点:我们的DLL会被映射到所有使用了User32.dll的进程中,不能只把DLL注入到指定的进程中。
1.2 使用windows挂钩来注入DLL
进程A安装一个WH_GETMESSAGE挂钩HHOOK hHook = SetWindowsHookEx(WH_GETMESSAGE, GetMsgProc, hInstDll, 0);0表示要给系统中所有的GUI线程安装挂钩。
(1)进程B中一个线程准备向一个窗口派送一条消息。
(2)系统检查该线程是否已经安装了WH_GETMESSAGE挂钩。
(3)系统检查GetMsgProc所在的DLL是否已经被映射到进程B的地址空间中。
(4)如果DLL尚未被映射,那么系统会强制将该DLL映射到B的地址空间中,并将进程B中该DLL的锁计数器(lock count)递增。
(5)由于DLL的hInstDll是在进程B中映射的,因此系统会对它进行检查看它与该DLL在进程A中的位置是否相同。如果相同,那么在两个进程地址空间中GetMsgProc函数位于相同的位置。在这种情况下,系统可以直接在进程A的地址空间中调用GetMsgProc。如果不同,那么系统必须确定GetMsgProc函数在进程B的地址空间中的虚拟内存地址。这个地址计算方法:GetMsgProc B = hInstDll B + (GetMsgProc A - hInstDll A).
(6)系统在进程B中递增该DLL的锁计数器。
(7)系统在进程B的地址空间中调用GetMsgProc函数。
(8)当GetMsgProc返回时,系统递减DLL在进程B中锁计数器。
UnhookWindowsHookEx撤销映射。
说明:在使用windows内建的控件(如按钮,编辑框,静态框,组合框等)时,我们可以跨越进程的边界向它们发送消息并与它们进行交互,但我们不能对新的公共控件(ListView控件)如此。这样做是为了兼容16windows程序的缘故,16位的应用程序都在一个地址空间中,一个应用程序可以向另一个应用程序创建的窗口发送LB_GETTEXT消息,为了移值,操作系统通过在内部创建一个内存映射文件并在进程间复制字符串数据。
使用线程的消息队列来进行线程同步。
1.3 使用远程线程来注入DLL
从根本上来说,DLL注入技术要求目标进程中的一个 线程调用LoadLibrary来载入我们想要的DLL.
CreateRemoteThread函数它使得在另一个进程中创建线程变得非常容易。除一个额外的hProcess参数之外,它与CreadThread完全相同。参数pfnStartAddr可以传入LoadLibrary函数地址。如果直接使用LoadLibrary,则在编译链接的时候,链接器会生成一个调用,来调用我们模块的导入段中的一个转换函数,这个转换函数会跳到实际的函数。我们应该通过PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)GetProcAddress(_TEXR("Kernel32.dll", "LoadLibraryA(W)");
HANDLE hThread = CreateRemoteThread(hProcessRemote, NULL, 0, pfnThreadRtn, L"C://MyLib.dll", 0, NULL);
字符串“C://MyLib.dll”位于调用进程的空间中,如果直接这样会使远程进程崩掉,因此我们应该用VirtualAllocEx函数在另一个进程空间中分配一块内存,用WiteProcessMemory将数据写入。VirtualFreeEx释放分配的内存,ReadProcessMemory读取。(每个进程都用到Kernel32.dll而且系统在每个进程中都会将其映射到同一个地址)可以将其注入到我们进程中这样便于调试。
1.4 使用木马DLL来注入DLL
这种方式是把我们知道的一个进程必然会载入的一个DLL替换掉(在我们的DLL内容导出原来DLL导出的所有符号(用函数转发器很容易实现)。如果只想把这种方法用在一个应用程序中,则可以给我们的DLL起一个独一无二的名称,并修改应用程序的EXE模块的导入段(包含了一个模块所需的所有DLL的名称)。
1.5 把DLL作为调试器来注入
调试器可以在被调试进程中执行许多特殊的操作。系统载入一个被调试程序(debggee)的时候会在被调试程序的地址空间准备完毕之后,但被调试程序的主线程尚未开始执行任何代码之前,自动通知调试器。这时,调试器可以强制将一些代码注入到被调试程序的地址空间中(比如使用WriteProcessMemory),然后让被调试程序的主线程去执行这些代码。
这种方法要求我们对调试线程的CONTEXT结构进程操作,这也意味着我们必须编写与CPU相关的代码。为了让这种方法能在不同的CPU平台上正常工作,我们必须对源代码进行修改。此外,我们可能还必须手工编写一些我们想让被调试程序执行的机器语言指令。在默认的情况下,如果调试器终止,那么WINDOWS会自动终止被调试程序。但是,调试器可以通过DebugSetProcessKillOnExit并传入FALSE来改变默认的行为。在不终止一个进程的前提下停止调试该进程也是有可能的,这要归功于DebugActiveProcessStop函数。
1.6 使用CreateProcess来注入代码
这种方法要求被注入的进程是由我们的进程生成的。
(1)让进程生成一个被挂起的子进程。
(2)从.exe模块的文件头中取得主线程的起始内存地址。
(3)将位于该内存地址处的机器指令保存起来。
(4)强制将一些手工编写的机器指令写入到该内存地址处。这些指令应该调用LoadLibrary来载入一个DLL。
(5)让子进程的主线程恢复运行,从而让这些指令得到执行。
(6)把保存起来的原始指令恢复到起始地址处。
(7)让进程从起始地址继续执行,就好像什么都没有发生过一样。
由于我们的应用程序不是调试器,因此我们可以非常容易地对应用程序和注入的DLL进程调试。这种方法同时适用于控制台程序和GUI程序。
1.7 API拦截
a.通过覆盖代码来拦截API.
b.通过修改模块的导入段来拦截API.
http://research.microsoft.com/en-us/projects/detours/