DLL的远程注入技术

一、DLL注入

DLL的远程注入技术是目前Win32病毒广泛使用的一种技术。使用这种技术的病毒体通常位于一个DLL中,在系统启动的时候,一个EXE程序会将这个DLL加载至某些系统进程(如Explorer.exe)中运行
这样一来,普通的进程管理器就很难发现这种病毒了,而且即使发现了也很难清除,因为只要病毒寄生的进程不终止运行,那么这个DLL就不会在内存中卸载,
用户也就无法在资源管理器中删除这个DLL文件,真可谓一箭双雕哉。记得2003年QQ尾巴病毒肆虐的时候,就已经有些尾巴病毒的变种在使用这种技术了。
到了2004年初,我曾经尝试着仿真了一个QQ尾巴病毒,但独是跳过了DLL的远程加载技术。直到最近在学校论坛上看到了几位朋友在探讨这一技术,便忍不住将这一尘封已久的技术从我的记忆中拣了出来,以满足广大的技术爱好者们。
在阅读本文之前,你需要了解以下几个API函数:

·OpenProcess - 用于打开要寄生的目标进程。

·VirtualAllocEx/VirtualFreeEx - 用于在目标进程中分配/释放内存空间

·WriteProcessMemory - 用于在目标进程中写入要加载的DLL名称

·CreateRemoteThread - 远程加载DLL的核心内容,用于控制目标进程调用API函数。

·LoadLibrary - 目标进程通过调用此函数来加载病毒DLL。

 在此我只给出了简要的函数说明,关于函数的详细功能和介绍请参阅MSDN。

示例程序

我将在以下的篇幅中用一个简单的示例Virus.exe来实现这一技术。这个示例的界面如下图:

(1)首先运行Target.exe,这个文件是一个用Win32 Application向导生成的“Hello, World”程序,用来作为寄生的目标进程。

(2)然后在界面的编辑控件中输入进程的名称“Target.exe”,单击“注入DLL”按钮,这时候Virus.exe就会将当前目录下的DLL.dll注入至Target.exe进程中。

(3)在注入DLL.dll之后,你也可以单击“卸载DLL”来将已经注入的DLL卸载。

模拟的病毒体DLL.dll

这是一个简单的Win32 DLL程序,它仅由一个入口函数DllMain组成:

[cpp] view plain copy
  1. BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved )  
  2. {  
  3. switch ( fdwReason )  
  4. {  
  5. case DLL_PROCESS_ATTACH:  
  6. {  
  7. MessageBox( NULL, _T("DLL已进入目标进程。"), _T("信息"), MB_ICONINFORMATION );  
  8. }  
  9. break;  
  10. case DLL_PROCESS_DETACH:  
  11. {  
  12. MessageBox( NULL, _T("DLL已从目标进程卸载。"), _T("信息"), MB_ICONINFORMATION );  
  13. }  
  14. break;  
  15. }  
  16. return TRUE;  
  17. }   


如你所见,这里我在DLL被加载和卸载的时候调用了MessageBox,这是用来显示我的远程注入/卸载工作是否成功完成。而对于一个真正的病毒体来说,
它往往就是处理DLL_PROCESS_ATTACH事件,在其中加入了启动病毒代码的部分:

[cpp] view plain copy
  1. case DLL_PROCESS_ATTACH:  
  2. {  
  3. StartVirus();  
  4. }  
  5. break;   

注入!
现在要开始我们的注入工作了。首先,我们需要找到目标进程:

[cpp] view plain copy
  1. DWORD FindTarget(LPCTSTR lpszProcess)  
  2. {  
  3. DWORD dwRet = 0;  
  4. HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);  
  5. PROCESSENTRY32 pe32;  
  6. pe32.dwSize = sizeof( PROCESSENTRY32 );  
  7. Process32First(hSnapshot, &pe32);  
  8. do  
  9. {  
  10. if ( lstrcmpi(pe32.szExeFile, lpszProcess) == 0 )  
  11. {  
  12. dwRet = pe32.th32ProcessID;  
  13. break;  
  14. }  
  15. while (Process32Next(hSnapshot, &pe32));  
  16. CloseHandle( hSnapshot );  
  17. return dwRet;  
  18. }   


这里我使用了Tool Help函数库,当然如果你是NT系统的话,也可以选择PSAPI函数库。
这段代码的目的就是通过给定的进程名称来在当前系统中查找相应的进程,并返回该进程的ID。得到进程ID后,就可以调用OpenProcess来打开目标进程了:

[cpp] view plain copy
  1. // 打开目标进程  
  2. HANDLE hProcess = OpenProcess( PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION |  
  3.  PROCESS_VM_WRITE, FALSE, dwProcessID );   


现在有必要说一下OpenProcess第一个参数所指定的三种权限。在Win32系统下,每个进程都拥有自己的4G虚拟地址空间,各个进程之间都相互独立。如果一个进程需要完成跨进程的工作的话,那么它必须拥有目标进程的相应操作权限。

在这里,PROCESS_CREATE_THREAD表示我可以通过返回的进程句柄在该进程中创建新的线程,也就是调用CreateRemoteThread的权限

同理,PROCESS_VM_OPERATION则表示在该进程中分配/释放内存的权限,也就是调用VirtualAllocEx/VirtualFreeEx的权限
PROCESS_VM_WRITE表示可以向该进程的地址空间写入数据,也就是调用WriteProcessMemory的权限

至此目标进程已经打开,那么我们该如何来将DLL注入其中呢?在这之前,我请你看一行代码,是如何在本进程内显式加载DLL的:

[cpp] view plain copy
  1. HMODULE hDll = LoadLibrary( "DLL.dll" );   

那么,如果能控制目标进程调用LoadLibrary,不就可以完成DLL的远程注入了么?的确是这样,我们可以通过CreateRemoteThread将LoadLibrary作为目标进程的一个线程来启动,这样就可以完成“控制目标进程调用LoadLibrary”的工作了。到这里,也许你会想当然地写下类似这样的代码:

[cpp] view plain copy
  1. DWORD dwID;  
  2. LPVOID pFunc = LoadLibraryA;  
  3. HANDLE hThread = CreateRemoteThread( hProcess, NULL, 0,  
  4.  (LPTHREAD_START_ROUTINE)pFunc,   
  5. (LPVOID)"DLL.dll", 0, &dwID );   


不过结果肯定会让你大失所望——注入DLL失败

那么现在让我们来分析一下失败的原因吧。我是前说过,在Win32系统下,每个进程都拥有自己的4G虚拟地址空间,各个进程之间都是相互独立的。在这里,我们当作参数传入的字符串"DLL.dll"其实是一个数值,它表示这个字符串位于Virus.exe地址空间之中的地址而这个地址在传给Target.exe之后,它指向的东西就失去了有效性

举个例子来说,譬如A、B两栋大楼,我住在A楼的401;
那么B楼的401住的是谁我当然不能确定——也就是401这个门牌号在B楼失去了有效性,而且如果我想要入住B楼的话,
我就必须请B楼的楼长为我在B楼中安排新的住处(当然这个新的住处是否401也就不一定了)。

由此看来,我就需要做这么一系列略显繁杂的手续——

首先在Target.exe目标进程中分配一段内存空间;
然后向这段空间写入我要加载的DLL名称;

最后再调用CreateRemoteThread。

这段代码就成了这样:

[cpp] view plain copy
  1. // 向目标进程地址空间写入DLL名称  
  2. DWORD dwSize, dwWritten;  
  3. dwSize = lstrlenA( lpszDll ) + 1;  
  4. LPVOID lpBuf = VirtualAllocEx( hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE );  
  5. if ( NULL == lpBuf )  
  6. {  
  7. CloseHandle( hProcess );  
  8. // 失败处理  
  9. }  
  10. if ( WriteProcessMemory( hProcess, lpBuf, (LPVOID)lpszDll, dwSize, &dwWritten ) )  
  11. {  
  12. // 要写入字节数与实际写入字节数不相等,仍属失败  
  13. if ( dwWritten != dwSize )  
  14. {  
  15. VirtualFreeEx( hProcess, lpBuf, dwSize, MEM_DECOMMIT );  
  16. CloseHandle( hProcess );  
  17. // 失败处理  
  18. }  
  19. }  
  20. else  
  21. {  
  22. CloseHandle( hProcess );  
  23. // 失败处理  
  24. }  
  25. // 使目标进程调用LoadLibrary,加载DLL  
  26. DWORD dwID;  
  27. LPVOID pFunc = LoadLibraryA;  
  28. HANDLE hThread = CreateRemoteThread( hProcess, NULL, 0,   
  29. (LPTHREAD_START_ROUTINE)pFunc, lpBuf, 0, &dwID );   


需要说的有两点:

第一,由于我要在目标进程中为ANSI字符串来分配内存空间,所以这里凡是和目标进程相关的部分,都明确使用了后缀为“A”的API函数——当然,如果要使用Unicode字符串的话,可以换作后缀是“W”的API;
第二,在这里LoadLibrary的指针我是取的本进程的LoadLibraryA的地址,这是因为LoadLibraryA/LoadLibraryW位于kernel32.dll之中,
而Win32下每个应用程序都会把kernel32.dll加载到进程地址空间中一个固定的地址,所以这里的函数地址在Target.exe中也是有效的

在调用LoadLibrary完毕之后,我们就可以做收尾工作了:

[cpp] view plain copy
  1. // 等待LoadLibrary加载完毕  
  2. WaitForSingleObject( hThread, INFINITE );  
  3. // 释放目标进程中申请的空间  
  4. VirtualFreeEx( hProcess, lpBuf, dwSize, MEM_DECOMMIT );  
  5. CloseHandle( hThread );  
  6. CloseHandle( hProcess );   


在此解释一下WaitForSingleObject一句。由于我们是通过CreateRemoteThread在目标进程中另外开辟了一个LoadLibrary的线程,所以我们必须等待这个线程运行完毕才能够释放那段先前申请的内存。

好了,现在你可以尝试着整理这些代码并编译运行。运行Target.exe,然后开启一个有模块查看功能的进程查看工具(在这里我使用我的July)来查看Target.exe的模块,
你会发现在注入DLL之前,Target.exe中并没有DLL.dll的存在:

 在调用了注入代码之后,DLL.dll就位于Target.exe的模块列表之中了:

二、矛盾相生(解毒)

1、法一:与DLL注入过程类似

记得2004年初我将QQ尾巴病毒成功仿真后,有很多网友询问我如何才能杀毒,不过我都没有回答——因为当时我研究的重点并非病毒的寄生特性。这一寄生特性直到今天可以说我才仿真完毕,那么,我就将解毒的方法也一并公开吧。

和DLL的注入过程类似,只不过在这里使用了两个API:GetModuleHandle和FreeLibrary。出于篇幅考虑,我略去了与注入部分相似或相同的代码:

[cpp] view plain copy
  1. // 使目标进程调用GetModuleHandle,获得DLL在目标进程中的句柄  
  2. DWORD dwHandle, dwID;  
  3. LPVOID pFunc = GetModuleHandleA;  
  4. HANDLE hThread = CreateRemoteThread( hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)  
  5. pFunc, lpBuf, 0, &dwID );  
  6. // 等待GetModuleHandle运行完毕  
  7. WaitForSingleObject( hThread, INFINITE );  
  8. // 获得GetModuleHandle的返回值  
  9. GetExitCodeThread( hThread, &dwHandle );  
  10. // 释放目标进程中申请的空间  
  11. VirtualFreeEx( hProcess, lpBuf, dwSize, MEM_DECOMMIT );  
  12. CloseHandle( hThread );  
  13. // 使目标进程调用FreeLibrary,卸载DLL  
  14. pFunc = FreeLibrary;  
  15. hThread = CreateRemoteThread( hProcess, NULL, 0,  
  16.  (LPTHREAD_START_ROUTINE)pFunc,  
  17.  (LPVOID)dwHandle, 0, &dwID );  
  18. // 等待FreeLibrary卸载完毕  
  19. WaitForSingleObject( hThread, INFINITE );  
  20. CloseHandle( hThread );  
  21. CloseHandle( hProcess );   

用这个方法可以卸载一个进程中的DLL模块,当然包括那些非病毒体的DLL。所以,这段代码还是谨慎使用为好

在完成卸载之后,如果没有别的程序加载这个DLL,你就可以将它删除了。

2、法二:采用查杀工具

2、1.安装木马查杀工具。对于多数人而言,要想通过手工查杀DLL木马是不太现实的,因此安装一款可以查杀此类木马的反病毒软件,是非常有必要的。这里还要提醒读者的是,应及时升级病毒数据库,这样才能保证有效地查杀绝大部分木马病毒。当然,我们也可以在计算机中安装那些专门针对木马的查杀工具,例如木马克星。

2、2.查看是否有不明端口开放以及对端口通信进行监控。只要木马进行连接,接受/发送数据则必然会打开端口,DLL木马也不例外,我们可以通过“Netstat-ano”命令来查看TCP/UDP端口的连接,以及开放端口的进程标识符;

也可以直接使用进程端口查看工具Fport.exe来查看与端口对应的进程,以发现是否有不明的连接和端口开放。另外,有些DLL木马通过端口劫持或者端口重用的方法来进行通信,所以仅是查看端口还是不够的,有必要的话,我们可使用嗅探器来了解打开的端口到底在传输些什么数据。

2、3.检查系统目录下是否有可疑的DLL文件。安装好系统和所有应用程序之后,可对系统目录下System32文件夹中的EXE和DLL文件作一记录:在命令提示符下执行“dir*.exe>bak1.txt&dir*.dll>bak2.txt”,将所有的EXE和DLL文件信息导出成TXT文件保存。当日后发现异常时,可以使用相同的命令再次备份,并使用FC命令比较两次的EXE文件和DLL文件。通过这种方法,我们可以发现可疑的EXE和DLL文件,同时通过文件的大小、创建时间来判断是否为DLL木马。

2、4.查看系统进程调用的DLL文件。当我们怀疑有DLL木马插入到系统进程,可以使用一些第三方进程工具来查看进程所调用的DLL文件,然后进一步确认是否中了DLL木马。此类查看工具有进程猎手、进程间谍等等。另外,我们也可以使用XP系统自带的命令行工具TaskList,来显示进程调用的DLL文件,并将这些信息导出成TXT文件保存,以便随时进行比较。


但我们就要利用这种木马 因为他比较难查杀 所以\~~~~~~~~~~~~~~

3、法三:找出寄生的EXE

警报,病毒无法清除

如果系统中被植入了DLL木马,将会出现什么情况呢?

首先,系统被黑客远程控制,出现数据丢失等情况;其次,杀毒软件会报警系统中有病毒,但是却无法进行清除。例如,在电脑中运行了上面制作的上兴远控木马后,笔者电脑上安装的杀毒软件提示发现病毒,但是在删除DLL病毒文件时却失败了。即使我们手工删除病毒文件,也会因为DLL文件正在使用中,所以也无法彻底删除。即使进入安全模式,得到的也是同样的结果。
另外,由于DLL木马是插入到系统进程中的,因此通过任务管理器等进程工具,也无法发现任何木马进程,这给木马的清除带来了很大的困难!
无法删除病毒文件,无法查找到木马进程,那么到底该如何清除DLL注入式木马呢?

无所遁形,揪出木马藏身之所

虽然木马没有自己的进程,但是有一个宿主进程,只要结束宿主进程,停止DLL文件的调用,就可以删除DLL文件,进而清除木马。因此,清除DLL木马的第一步,就是找到木马注入的宿主进程。那么,如何才能找到木马注入的宿主进程呢?且让我们细细看来。

以清除“上兴远控木马”为例,从杀毒软件的报警提示中已经知道木马DLL文件名为“rejoice.dll”。因此,就可以通过一些查看进程调用DLL文件的进程管理工具,找到该文件的宿主进程,此类工具很多,比如大名鼎鼎的ICESword

运行IceSword后,点击左侧边栏“查看→进程”,就可以在其右侧窗口中看到所有进程列表。右键点击某进程,在弹出菜单中选择“模块信息”命令,即可看到该进程调用的所有DLL文件


提示:DLL注入式木马通常是将DLL文件,加载到explorer.exe、svchost.exe、winlogon.exe、iexplore.exe等系统进程中的。因此在查找DLL宿主文件时,可以关闭其它无关的程序,然后依次检查这几个进程中的DLL文件。

不过一个一个的检查系统进程,确实有些麻烦,如何才能快速的定位木马的宿主进程呢?可以使用一款名为“procexp”的进程管理工具。运行procexp后,在程序界面中间显示的是树状进程关系列表,下方是每个进程的详细信息。点击菜单“Find→Find DLL”命令,打开DLL文件查找对话框,在“DLL Substring”中输入要查找的关键词,这里输入刚才杀毒软件扫描出的DLL文件名“rejoice.dll”。然后点击“Search”按钮,在下方的列表中就可以看到该DLL文件是被哪个进程调用的了。


牛刀小试,清除普通进程DLL注入木马

对于大部分DLL注入木马,其注入到“iexplore.exe”和“explorer.exe”这两个进程。对于这类普通进程的DLL木马,清除将是非常方便的。

如果DLL文件是注入到“iexplore.exe”进程中,由于此进程就是IE浏览器进程,因此就需要先关掉所有IE窗口和相关程序,然后直接找到DLL文件进行删除就可以了。

如果DLL文件是注入到“explorer.exe”进程中,那么就略显麻烦一些。由于此进程用于显示桌面和资源管理器,因此,当通过任务管理器结束掉“explorer.exe”进程时,桌面无法看到,桌面上所有图标消失掉,"我的电脑"、"网上邻居"等所有图标都不见了,同时,也无法打开资源管理器找到木马文件进行删除。怎么办呢?

实际上,解决的方法也很简单。在任务管理器中点击菜单“文件→新任务运行”,打开“创建新任务”对话框,点击“浏览”按钮,通过浏览对话框就可以打开DLL文件所在的路径。然后选择“文件类型”为“所有文件”,即可显示并删除DLL文件了。


在浏览对话框中删除DLL文件

除恶务尽,清除特殊DLL注入木马

除了以上所说的注入普通进程的DLL木马之外,还有许多木马注入到系统里关键进程中,比如svchost.exe、smss.exe、winlogon.exe进程。这些进程使用普通方式无法结束,使用特殊工具结束掉进程后,却又很可能造成系统崩溃无法正常运行的情况。对于这些木马,我们可以通过以下两种方法进行清除。

1.使用IceSword卸载DLL文件

IceSword的功能十分强大,我们曾在以前作过介绍,在这里,就可以利用它卸载掉已经插入到正在运行的系统进程中的DLL文件。在IceSword的进程列表显示窗口中,右键点击DLL木马宿主进程,选择弹出命令“模块信息”。在进程模块信息对话框中找到DLL木马文件,选择文件后点击“强制解除”命令,即可将系统进程中的DLL木马文件卸载掉了


卸载系统进程中的DLL木马

4、法四:定时枚举,发现不是本身所用的就强制Free掉

防止DLL注入我用的是另一种方法:

//允许的模块列表
theApp.m_szAuthorizedList = " USER32.DLL RPCRT4.DLL KERNEL32.DLL GDI32.DLL ADVAPI32.DLL NTDLL.DLL MSVCRT.DLL SETUPAPI.DLL CFGMGR32.DLL WINMM.DLL SHELL32.DLL SHLWAPI.DLL MSVCR80.DLL MFC80.DLL OLE32.DLL COMCTL32.DLL OLEACC.DLL OLEAUT32.DLL ATL80.DLL MSVCM80.DLL MFC80CHS.DLL MSVCP80.DLL MFCM80.DLL MFCM80U.DLL MFC80U.DLL VCOMP.DLL SECUR32.DLL MSVCRT.DLL MSVCP60.DLL MFC60.DLL ";

然后定时枚举系统进程,找到自己进程本身并分析所加载的模块,发现不是本身所用的就强制FREE掉:

[cpp] view plain copy
  1. do  
  2. {  
  3.     if(!IsModuleAuthorized(me32.szModule))  
  4.     {  
  5.         HMODULE hmodule = me32.hModule;  
  6.         CloseHandle(hModuleSnap);    
  7.         FreeLibrary(hmodule);  
  8.         //发现可疑模块并已移除  
  9.         return;  
  10.     }  
  11. }  

 

5、法五: 拦截自身LoadLibraryExW 这个函数吧  

如果仅仅只是防御 远程线程 你可以按下面这样做  :

(1)在程序初始化时把自身程序的所有线程ID 用个ULONG 数组保存起来   
并动态维护这个ULONG数组 这个也是多线程程序所必需的吧 如果是单线程就更方便了
(2)然后 在LoadLibraryExW假函数里做一个判断   
判断当前的操作线程是否为自身程序原有的线程 如果不是 就是远程线程了   

[cpp] view plain copy
  1. ULONG __stdcall Fake_LoadLibraryExW(LPCWSTR lpwLibFileName,HANDLE hFile,DWORD dwFlags)     
  2. {  
  3.     ULONG TID=GetCurrentThreadId();  
  4.     //把这个TID 循环对比一下 是否存在于自身TID 数组     
  5.   
  6.   
  7.     if (flag)//存在则 通过     
  8.     {  
  9.         return MyReal_LoadLibraryExW(lpwLibFileName,hFile,dwFlags);  
  10.     }  
  11.     else  
  12.     {  
  13.         return FALSE;  
  14.     }  
  15. }  




(4)上面的方法可以防止远程线程 在自身程序加载DLL ,但还有个问题 值得注意
WINDOWS的 SetWindowsHookEx (设GUI全局钩子) 这个函数,因为他是切换到进程上下文 使用程序自身线程来加载DLL的 所以不能用判断TID的方法来判断了  

这里再提供二个思路   

(4、1)一个是 判断当前加载 的DLL 是否有WINDOWS签名 是则放行 否则拦截 (稍微有点费时 但非常安全)

(4、2)在一个是 判断路径  
系统加载DLL时是不会包含DLL路径的直接是类似“kernel32.dll” 这样的   
所以,可以判断当前路径是否包含路径分割符,是则拦截 否则放行 (速度快 安全性较高)


结合上面三种方法 自己写个规则来决定拦截与否吧
比直接定义模块名 兼容性高多了 也方便多了   

三、如何防止自己制作的DLL远程注入被查杀

 

原文链接: DLL的远程注入技术 版权所有,转载时请注明出处,违者必究。
注明出处格式:流沙团 ( http://gyarmy.com/post-376.html )

发表评论

0则评论给“DLL的远程注入技术”