使用注册表来注入DLL
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows\
中的
AppInit_Dlls键可能会包含
一个DLL的文件名或一组DLL的文件名,我们将其设为我们要注入的DLL,之后我们为了让其执行,还必须创建一个名为
LoadAppInit_Dlls的键,类型为DWORD,值设为
1。当User32.dll被映射到一个新的进程时,会收到
DLL_PROCESS_ATTACH通知,当User32.dll对它进行处理的时候,会取得上述注册表键的值,并调用LoadLibrary来载入这个字符串中指定的每个DLL。当系统载入每个DLL的时候,会调用它们的
DllMain函数,并将参数fdwReason的值设为DLL_PROCESS_ATTACH,这个每个DLL就能够对自己进行初始化了。由于被注入的DLL是在进程生命期的早起被注入的,因此我们在调用函数的时候应该慎重。调用Kernel32.dll中的函数应该没有问题,但是调用其他DLL中的函数可能会导致问题,甚至可能会导致蓝屏。User32.dll不会检查每个DLL的载入或初始化是否成功。
优点: 方便
缺点: 1.必须是使用了User32.dll的进程,所有GUI可以,但CUI不可。
2.所有GUI进程中的线程都在执行我们的代码,容器易崩溃。
3.不能只在特定的需要的时候加载到特定的需要的进程上。
使用Windows挂钩来注入DLL
我们可以用挂钩来将一个DLL注入到进程的地址空间中。
SetWindowsHookEx(WH_GETMESSAGE,GetMsgProc,hInstDll,0); 这是通过调用
SetWindowsHookEx安装了一个
WH_GETMESSAGE的挂钩。
GetMsgProc是一个函数的地址,在窗口即将处理一条消息的时候,系统应用调用这个函数。第三个参数是一个DLL,这个DLL中包含了GetMsgProc函数。在windows 中,hInstDll的值是进程地址空间中DLL被映射到虚拟内存地址。最后一个参数0表示要给哪个线程安装挂钩。0是要给系统中的所有GUI线程安装挂钩的意思。
注意,当系统把挂钩过滤函数所在的DLL注入或映射到地址空间中时,会映射整个DLL,而不仅仅只是挂钩过滤函数,这意味着该DLL内的所有函数存在与进程B中,能够为进程B中的为何线程调用。
为了从另一个进程的窗口来创建一个子类窗口,我们可以先给创建窗口的线程设置一个WH_GETMESSAGE挂钩,然后当GetMsgProc函数被调用的时候,我们就可以调用SetWindowLongPtr来派生子类窗口。当然,子类窗口的窗口过程必须和GetMsgProc函数在同一个DLL中。
优点:
可以用UnhookWindowsHookEx来撤销映射。
我们不能在派生完子窗口之后马上就把挂钩清除,在子类窗口的整个生命期内,这个挂钩必须一直有效。
使用远程线程来注入DLL
注入DLL的第三种方法是使用远程线程(remote thread),它提供了最高的灵活性。
DLL注入技术要求目标进程中的一个线程调用LoadLibrary来载入我们想要的DLL。由于我们不能轻易地控制别人进程中的线程,因此这种方法要求我们在目标进程中创建一个新的线程。由于这个线程是我们自己创建的,因此我们可以对它执行的代码加以控制。
Windows提供了GreateRemoteThread函数。用其来调用LoadLibrary函数,注入我们DLL。
问题:
1.不能直接传入LoadLibrary函数,所以要我们写一个函数来调用LoadLibrary。
2.DLL路径字符串问题,我们需要把DLL的路径字符串存放到远程进程的地址空间中去。幸运的是,Windows提供了VirtualAllocEx函数,它可以让一个进程在另一个进程的地址空间中分配一块内存。
步骤:
(1)取得注入进程ID dwRemoteProcessId;
(2)取得注入DLL的完全路径,并将其转换为宽字符模式pszLibFileName;
(3)利用Windows API OpenProcess打开宿主进程,应该开启下列选项:
a.PROCESS_CREATE_THREAD:允许在宿主进程中创建线程;
b.PROCESS_VM_OPERATION:允许对宿主进程中进行VM操作;
c.PROCESS_VM_WRITE:允许对宿主进程进行VM写。
(4)利用Windows API VirtualAllocEx函数在远程线程的VM中分配DLL完整路径宽字符所需的存储空间,并利用Windows API WriteProcessMemory函数将完整路径写入该存储空间;
(5)利用Windows API GetProcAddress取得Kernel32模块中LoadLibraryW函数的地址,这个函数将作为随后将启动的远程线程的入口函数;
(6)利用Windows API CreateRemoteThread启动远程线程,将LoadLibraryW的地址作为远程线程的入口函数地址,将宿主进程里被分配空间中存储的完整DLL路径作为线程入口函数的参数以另其启动指定的DLL;
(7)清理现场。
示范代码:
//打开目标进程 HANDLE hProcess=OpenProcess(PROCESS_ALL_ACCESS,FALSE,g_Pid); if (!hProcess) { AfxMessageBox(TEXT("打开进程失败")); return; } //在目标进程申请一段内存区域 来存放DLL路径 LPVOID pRemoteBase=VirtualAllocEx(hProcess,NULL,0x1000,MEM_COMMIT,PAGE_READWRITE); if (pRemoteBase==NULL) { AfxMessageBox(TEXT("申请内存区域失败")); return; } //在目标进程中写入DLL路径 写入的长度要+1 字符串终止符 if (!WriteProcessMemory(hProcess,pRemoteBase,(LPTSTR)(LPCTSTR)DllPath,DllPath.GetLength()+1,NULL)) { AfxMessageBox(TEXT("进程写入失败")); //失败就释放原先申请的内存区域 撤销内存页的提交状态 VirtualFreeEx(hProcess,pRemoteBase,0x1000,MEM_DECOMMIT); return; } //得到LoadLibraryA的函数地址 因为Kernel32的加载地址在每个应用程序中都一样 LPTHREAD_START_ROUTINE pfn=(LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle("Kernel32.dll"),"LoadLibraryA"); //创建远程线程 执行加载 HANDLE hRemoteThread = CreateRemoteThread(hProcess,NULL,0,pfn,pRemoteBase,0,NULL); if (hRemoteThread==NULL) { AfxMessageBox(TEXT("创建远程线程失败")); //释放原先申请的内存区域 撤销内存页的提交状态 VirtualFreeEx(hProcess,pRemoteBase,0x1000,MEM_DECOMMIT); return; } AfxMessageBox(TEXT("成功注入")); //等待线程退出 WaitForSingleObject(hRemoteThread,-1); //释放原先申请的内存区域 撤销内存页的提交状态 VirtualFreeEx(hProcess,pRemoteBase,0x1000,MEM_DECOMMIT); //关闭句柄 CloseHandle(hRemoteThread); CloseHandle(hProcess);
使用木马DLL来注入DLL
如果熟悉exe和DLL的文件格式,我们可以用我们仿造的.dll把程序原本要载入的.dll替换掉。
把DLL作为调试器来注入
这种方法要求我们对被调试线程的CONTEXT结构进行操作,这也意味着我们必须编写与CPU相关的代码。其原理是系统载入一个被调试程序的时候,会在被调试程序的地址空间准备完毕之后,且被调试程序的主线程尚未开始执行任何代码之前,自动通知调试器。这是,调试器可以强制将一些代码注入到被调试程序的地址空间中,然后让被调试程序的主线程去执行这些代码。
使用CreateProcess来注入代码
如果要注入代码的进程是由我们的进程生成的,那么事情就比较好办了。我们的进程(父进程)可以在创建新进程的时候将它挂起。这种方法允许我们改变子进程的状态,同时又不影响它的执行,因为它根本还没有开始执行。但是,父进程也会得到子进程的主线程的句柄。通过这个句柄,可以对线程执行的代码进行修改。
优点:
1.它在应用程序开始之前得到地址空间;
2.由于我们的应用程序不是调试器,因此我们可以非常容易地对应用程序和注入DLL进行调试;
3.这种方法同时适用于控制台应用程序和GUI应用程序。
缺点:
1.只有当我们的代码在父进程中的时候,我们才能使用这种方法;
2.我们必须为不同的CPU平台做相应的修改。