学习笔记之卸载远程目标进程中的DLL模块
(2007-07-23 23:51:02)转载▼
学习笔记之卸载远程目标进程中的DLL模块2007/7/23
1.首先得把DLL模块中的线程结束
使用CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD,0);创建系统线程的快照然后用Thread32First()和
Thread32Next()遍历系统中所有线程.将遍历到的线程保存到THREADENTRY32结构,然后判断结构中的th32OwnerProcessID成员是否与目标进程ID是否相等从而判断该线程是否为目标进程的.然后用函数OpenThread()打开该线程.但是OpenThread函数在VC6中未被定义.该函数存在于kernel32.dll中.在使用时需要自己定义:第三个参数指定要打开的线程的ID(从THREADENTRY32结构获取)
typedef HANDLE (WINAPI*OPENTHREAD)(DWORD dwFlag, BOOL bInheritHandle, DWORD dwThreadId);
然后用GetProcAddress函数从kernel32.dll中获取OpenThread函数的地址后就可以使用该函数了
OPENTHREAD OpenThread=(OPENTHREAD)GetProcAddress(GetModuleHandle("Kernel32"), "OpenThread");
用OpenThread函数打开线程获取线程句柄
HANDLE hThread=OpenThread(THREAD_ALL_ACCESS,FALSE,Thread.th32ThreadID);
有了线程的句柄后就可以调用NtQueryInformationThread函数获取线程的入口地址
函数NtQueryInformationThread在VC6中也未被定义需要自己定义然后再使用
NtQueryInformationThread函数的第一个参数即为线程句柄,第二个参数为一个枚举值而该枚举类型在VC6中未被定义,同样需要自己定义
NtQueryInformationThread函数的定义
typedef DWORD (CALLBACK* NTQUERYINFORMATIONTHREAD)(HANDLE,DWORD,PVOID,DWORD,PDWORD);
获取NtQueryInformationThread函数的地址:NtQueryInformationThread函数存在于ntdll.dll中,ntdll.dll与kernel32.dll一样,在每个进程开始时,系统都为他们做了一会拷贝所以可以直接用
GetProcAddress函数获取其地址
NTQUERYINFORMATIONTHREAD NtQueryInformationThread=(NTQUERYINFORMATIONTHREAD)GetProcAddress(GetModuleHandle("ntdll.dll"),"NtQueryInformationThread");
定义NtQueryInformationThread要用到的枚举类型:
typedef enum _THREAD_INFORMATION_CLASS
{
ThreadBasicInformation,
ThreadTimes,
ThreadPriority,
ThreadBasePriority,
ThreadAffinityMask,
ThreadImpersonationToken,
ThreadDescriptorTableEntry,
ThreadEnableAlignmentFaultFixup,
ThreadEventPair,
ThreadQuerySetWin32StartAddress,
ThreadZeroTlsCell,
ThreadPerformanceCount,
ThreadAmILastThread,
ThreadIdealProcessor,
ThreadPriorityBoost,
ThreadSetTlsArrayAddress,
ThreadIsIoPending,
ThreadHideFromDebugger
}THREAD_INFORMATION_CLASS,*PTHREAD_INFORMATION_CLASS;
在此处NtQueryInformationThread函数将用到此枚举类型的第9个值ThreadQuerySetWin32StartAddress
可以不定义此枚举类型,直接将NtQueryInformationThread函数的第二个参数设为数值9也可以
关于NtQueryInformationThread函数的详细信息了解不多
此处的用法为:
NtQueryInformationThread(hThread,ThreadQuerySetWin32StartAddress,&Start,0x4,NULL);
第一个参数为线程句柄,由OpenThread函数获取,第二个参数为枚举值不多说了
第三个参数为一个DWORD变量的指针,此变量就是用来接收线程入口地址的.第四个参数只能为0x4暂时还不知道是什么意思.第五个参数也是一个DWORD变量指针,但在此处可以设为NULL
判断该线程的入口地址是否在某DLL模块中的方法为:
用NtQueryInformationThread函数获取的线程入口地址 - 该DLL模块句柄(需要先转换为DWORD值)
再用得到的差值与该DLL模块文件的大小相比较如果该差值正好小于或等于DLL模块文件的大小
说明该线程入口地址在该DLL模块之中.
这里所需要模块句柄,对于它的获取稍后再讲.
最后就可以调用TerminateThread(hThread,0);函数结束该线程
用上述方法结束该DLL模块中的所有线程后就可以对该DLL模块进行卸载操作了
2.要卸载DLL模块首先需要获取该DLL模块的句柄
可以通过GetModuleHandle函数获取
也可以通过创建快照CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,processID);的然后用
Module32First和Module32Next遍历模块的方法获取
先说说用GetModuleHandle函数获取.此方法需要用CreateRemoteThread函数在远程进程中创建线程,让该线程调用Kernel32.dll中的GetModuleHandle函数.但是只这样还不行.因为GetModuleHandle函数需要以DLL模块文件名做参数.既然是远程线程调用GetModuleHandle函数.还需要先把DLL模块文件名写入目标进程的地址空间中.最后用CreateRemoteThread函数创建线程执行GetModuleHandle函数来获取DLL句柄
由于是用CreateRemoteThread函数远程执行GetModuleHandle函数.所以无法直接从GetModuleHandle函数得到返回值(也就是DLL句柄).在此必在WaitForSingleObject函数之后用GetExitCodeThread函数来获取线程的退出代码.如果线程正确返回.该退出代码就是线程函数(GetModuleHandle)的返回值然后再用同样的方法用CreateRemoteThread远程创建线程调用FreeLibrary函数来卸载该DLL
(注:此方法本人还没试验成功,理论上是可行的)
现在再来看第二种方法,通过CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,processID)创建进程模块的快照,然后用Module32First和Module32Next遍历进程中所有模块.Module32First和Module32Next函数会将遍历结果保存到MODULEENTRY32结构中.通过查询该结构中的szExePath或szModule成员来判断是否为我们要卸载的目标模块.其中szExePath保存了模块文件的包括全路径的文件名而szModule只包括模块文件名不含路径
3.两个关键的问题解决了,剩下的就是如何卸载DLL模块的问题
要卸载DLL模块需要用到FreeLibrary函数.由于是卸载远程进程中的模块必须让远程进程来执行该函数.
所以将再次用到CreateRemoteThread函数来创建远程线程
以下是实现方法:
首先从Kernel32.dll模块中获取FreeLibrary函数的地址
LPVOID pFunc=(LPTHREAD_START_ROUTINE)GetProcAddress(Pkernel32,"FreeLibrary");//其中Pkernel32是Kernel32.dll的句柄
最后再调用
HANDLE hThread = CreateRemoteThread( process, NULL, 0, (LPTHREAD_START_ROUTINE)pFunc, (LPVOID)Module->hModule, 0, &dwID/*用来接收新线程的ID*/ );
其中第五个参数就是我们上一步骤中获取的模块句柄
说明一下.由于一个进程可以多次调用LoadLibrary函数来装载一个DLL模块(调用一次LoadLibrary函数系统就会对该DLL模块增加一个引用计数并不是说该进程中会有多个相同的DLL模块)为了防止这种情况.最好用一个循环来进行卸载操作.同样通过用GetExitCodeThread的方法获取FreeLibrary函数的执行情况
直到返回结果为False为止.这样才表示完成了对该模块的卸载
4.另外附上一点关于CreateToolhelp32Snapshot函数的资料
CreateToolhelp32Snapshot函数为指定的进程、进程使用的堆[HEAP]、模块[MODULE]、线程[THREAD])建立一个快照[snapshot]。
原型:
HANDLE WINAPI CreateToolhelp32Snapshot(DWORD dwFlags,DWORD th32ProcessID);
参数:
dwFlags
[输入]指定快照中包含的系统内容,这个参数能够使用下列数值(变量)中的一个。
TH32CS_INHERIT - 声明快照句柄是可继承的。
TH32CS_SNAPALL - 在快照中包含系统中所有的进程和线程。
TH32CS_SNAPHEAPLIST - 在快照中包含在th32ProcessID中指定的进程的所有的堆。
TH32CS_SNAPMODULE - 在快照中包含在th32ProcessID中指定的进程的所有的模块。
TH32CS_SNAPPROCESS - 在快照中包含系统中所有的进程。
TH32CS_SNAPTHREAD - 在快照中包含系统中所有的线程。
th32ProcessID
[输入]指定将要快照的进程ID。如果该参数为0表示快照当前进程。该参数只有在设置了TH32CS_SNAPHEAPLIST或TH32CS_SNAPMOUDLE后才有效,在其他情况下该参数被忽略,所有的进程都会被快照。
返回值:
调用成功,返回快照的句柄,调用失败,返回INVAID_HANDLE_VALUE。
5.新问题
在该次学习中所发现的新问题.某些程序在开始时总是要载入很多相关的DLL模块
比如QQGame.exe启动时就载入了多达109个模块.有些是常见的如Ntdll.dll kernel32.dll Gdi32.dll ole32.dll等等.也有一些QQGame.exe自己的DLL模块这些模块完成QQGAME.EXE的一些特殊功能.但是QQGAME.EXE启动后并没有马上就调用某些模块中的东西.比如QQGAME.EXE中的一个HelpDll.dll模块.我们就可以例用这样的模块来启动我们的病毒.我们可将该DLL模块文件拷贝到一隐蔽的目录下.然后自己从新写一个新的DLL模块.该模块应具备的功能.1首先要能载入我们拷贝的真正的DLL模块.2载入或者启动我们的病毒程序.3完成这两样工作后马上进行自我卸载. 最后将写好的DLL文件放到先前HelpDll.dll的目录下复盖真正的DLL文件.这样我们的病毒就会随QQGame.exe的启动而启动了
上面的例子本人测试成功.不知道其它的进程或模块会不会支持这样的方式
1.首先得把DLL模块中的线程结束
使用CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD,0);创建系统线程的快照然后用Thread32First()和
Thread32Next()遍历系统中所有线程.将遍历到的线程保存到THREADENTRY32结构,然后判断结构中的th32OwnerProcessID成员是否与目标进程ID是否相等从而判断该线程是否为目标进程的.然后用函数OpenThread()打开该线程.但是OpenThread函数在VC6中未被定义.该函数存在于kernel32.dll中.在使用时需要自己定义:第三个参数指定要打开的线程的ID(从THREADENTRY32结构获取)
typedef HANDLE (WINAPI*OPENTHREAD)(DWORD dwFlag, BOOL bInheritHandle, DWORD dwThreadId);
然后用GetProcAddress函数从kernel32.dll中获取OpenThread函数的地址后就可以使用该函数了
OPENTHREAD OpenThread=(OPENTHREAD)GetProcAddress(GetModuleHandle("Kernel32"), "OpenThread");
用OpenThread函数打开线程获取线程句柄
HANDLE hThread=OpenThread(THREAD_ALL_ACCESS,FALSE,Thread.th32ThreadID);
有了线程的句柄后就可以调用NtQueryInformationThread函数获取线程的入口地址
函数NtQueryInformationThread在VC6中也未被定义需要自己定义然后再使用
NtQueryInformationThread函数的第一个参数即为线程句柄,第二个参数为一个枚举值而该枚举类型在VC6中未被定义,同样需要自己定义
NtQueryInformationThread函数的定义
typedef DWORD (CALLBACK* NTQUERYINFORMATIONTHREAD)(HANDLE,DWORD,PVOID,DWORD,PDWORD);
获取NtQueryInformationThread函数的地址:NtQueryInformationThread函数存在于ntdll.dll中,ntdll.dll与kernel32.dll一样,在每个进程开始时,系统都为他们做了一会拷贝所以可以直接用
GetProcAddress函数获取其地址
NTQUERYINFORMATIONTHREAD NtQueryInformationThread=(NTQUERYINFORMATIONTHREAD)GetProcAddress(GetModuleHandle("ntdll.dll"),"NtQueryInformationThread");
定义NtQueryInformationThread要用到的枚举类型:
typedef enum _THREAD_INFORMATION_CLASS
{
ThreadBasicInformation,
ThreadTimes,
ThreadPriority,
ThreadBasePriority,
ThreadAffinityMask,
ThreadImpersonationToken,
ThreadDescriptorTableEntry,
ThreadEnableAlignmentFaultFixup,
ThreadEventPair,
ThreadQuerySetWin32StartAddress,
ThreadZeroTlsCell,
ThreadPerformanceCount,
ThreadAmILastThread,
ThreadIdealProcessor,
ThreadPriorityBoost,
ThreadSetTlsArrayAddress,
ThreadIsIoPending,
ThreadHideFromDebugger
}THREAD_INFORMATION_CLASS,*PTHREAD_INFORMATION_CLASS;
在此处NtQueryInformationThread函数将用到此枚举类型的第9个值ThreadQuerySetWin32StartAddress
可以不定义此枚举类型,直接将NtQueryInformationThread函数的第二个参数设为数值9也可以
关于NtQueryInformationThread函数的详细信息了解不多
此处的用法为:
NtQueryInformationThread(hThread,ThreadQuerySetWin32StartAddress,&Start,0x4,NULL);
第一个参数为线程句柄,由OpenThread函数获取,第二个参数为枚举值不多说了
第三个参数为一个DWORD变量的指针,此变量就是用来接收线程入口地址的.第四个参数只能为0x4暂时还不知道是什么意思.第五个参数也是一个DWORD变量指针,但在此处可以设为NULL
判断该线程的入口地址是否在某DLL模块中的方法为:
用NtQueryInformationThread函数获取的线程入口地址 - 该DLL模块句柄(需要先转换为DWORD值)
再用得到的差值与该DLL模块文件的大小相比较如果该差值正好小于或等于DLL模块文件的大小
说明该线程入口地址在该DLL模块之中.
这里所需要模块句柄,对于它的获取稍后再讲.
最后就可以调用TerminateThread(hThread,0);函数结束该线程
用上述方法结束该DLL模块中的所有线程后就可以对该DLL模块进行卸载操作了
2.要卸载DLL模块首先需要获取该DLL模块的句柄
可以通过GetModuleHandle函数获取
也可以通过创建快照CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,processID);的然后用
Module32First和Module32Next遍历模块的方法获取
先说说用GetModuleHandle函数获取.此方法需要用CreateRemoteThread函数在远程进程中创建线程,让该线程调用Kernel32.dll中的GetModuleHandle函数.但是只这样还不行.因为GetModuleHandle函数需要以DLL模块文件名做参数.既然是远程线程调用GetModuleHandle函数.还需要先把DLL模块文件名写入目标进程的地址空间中.最后用CreateRemoteThread函数创建线程执行GetModuleHandle函数来获取DLL句柄
由于是用CreateRemoteThread函数远程执行GetModuleHandle函数.所以无法直接从GetModuleHandle函数得到返回值(也就是DLL句柄).在此必在WaitForSingleObject函数之后用GetExitCodeThread函数来获取线程的退出代码.如果线程正确返回.该退出代码就是线程函数(GetModuleHandle)的返回值然后再用同样的方法用CreateRemoteThread远程创建线程调用FreeLibrary函数来卸载该DLL
(注:此方法本人还没试验成功,理论上是可行的)
现在再来看第二种方法,通过CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,processID)创建进程模块的快照,然后用Module32First和Module32Next遍历进程中所有模块.Module32First和Module32Next函数会将遍历结果保存到MODULEENTRY32结构中.通过查询该结构中的szExePath或szModule成员来判断是否为我们要卸载的目标模块.其中szExePath保存了模块文件的包括全路径的文件名而szModule只包括模块文件名不含路径
3.两个关键的问题解决了,剩下的就是如何卸载DLL模块的问题
要卸载DLL模块需要用到FreeLibrary函数.由于是卸载远程进程中的模块必须让远程进程来执行该函数.
所以将再次用到CreateRemoteThread函数来创建远程线程
以下是实现方法:
首先从Kernel32.dll模块中获取FreeLibrary函数的地址
LPVOID pFunc=(LPTHREAD_START_ROUTINE)GetProcAddress(Pkernel32,"FreeLibrary");//其中Pkernel32是Kernel32.dll的句柄
最后再调用
HANDLE hThread = CreateRemoteThread( process, NULL, 0, (LPTHREAD_START_ROUTINE)pFunc, (LPVOID)Module->hModule, 0, &dwID/*用来接收新线程的ID*/ );
其中第五个参数就是我们上一步骤中获取的模块句柄
说明一下.由于一个进程可以多次调用LoadLibrary函数来装载一个DLL模块(调用一次LoadLibrary函数系统就会对该DLL模块增加一个引用计数并不是说该进程中会有多个相同的DLL模块)为了防止这种情况.最好用一个循环来进行卸载操作.同样通过用GetExitCodeThread的方法获取FreeLibrary函数的执行情况
直到返回结果为False为止.这样才表示完成了对该模块的卸载
4.另外附上一点关于CreateToolhelp32Snapshot函数的资料
CreateToolhelp32Snapshot函数为指定的进程、进程使用的堆[HEAP]、模块[MODULE]、线程[THREAD])建立一个快照[snapshot]。
原型:
HANDLE WINAPI CreateToolhelp32Snapshot(DWORD dwFlags,DWORD th32ProcessID);
参数:
dwFlags
[输入]指定快照中包含的系统内容,这个参数能够使用下列数值(变量)中的一个。
TH32CS_INHERIT - 声明快照句柄是可继承的。
TH32CS_SNAPALL - 在快照中包含系统中所有的进程和线程。
TH32CS_SNAPHEAPLIST - 在快照中包含在th32ProcessID中指定的进程的所有的堆。
TH32CS_SNAPMODULE - 在快照中包含在th32ProcessID中指定的进程的所有的模块。
TH32CS_SNAPPROCESS - 在快照中包含系统中所有的进程。
TH32CS_SNAPTHREAD - 在快照中包含系统中所有的线程。
th32ProcessID
[输入]指定将要快照的进程ID。如果该参数为0表示快照当前进程。该参数只有在设置了TH32CS_SNAPHEAPLIST或TH32CS_SNAPMOUDLE后才有效,在其他情况下该参数被忽略,所有的进程都会被快照。
返回值:
调用成功,返回快照的句柄,调用失败,返回INVAID_HANDLE_VALUE。
5.新问题
在该次学习中所发现的新问题.某些程序在开始时总是要载入很多相关的DLL模块
比如QQGame.exe启动时就载入了多达109个模块.有些是常见的如Ntdll.dll kernel32.dll Gdi32.dll ole32.dll等等.也有一些QQGame.exe自己的DLL模块这些模块完成QQGAME.EXE的一些特殊功能.但是QQGAME.EXE启动后并没有马上就调用某些模块中的东西.比如QQGAME.EXE中的一个HelpDll.dll模块.我们就可以例用这样的模块来启动我们的病毒.我们可将该DLL模块文件拷贝到一隐蔽的目录下.然后自己从新写一个新的DLL模块.该模块应具备的功能.1首先要能载入我们拷贝的真正的DLL模块.2载入或者启动我们的病毒程序.3完成这两样工作后马上进行自我卸载. 最后将写好的DLL文件放到先前HelpDll.dll的目录下复盖真正的DLL文件.这样我们的病毒就会随QQGame.exe的启动而启动了
上面的例子本人测试成功.不知道其它的进程或模块会不会支持这样的方式
来源: <http://blog.sina.com.cn/s/blog_56ea069101000b3k.html>
来自为知笔记(Wiz)