在有些时候,不得不让进程处于暂停运行的状态。比如,病毒有两个运行的进程,它们在不断互相检测,当一个病毒进程发现另一个病毒进程被结束了,那么它会再次把被结束的那个病毒进程运行起来。由于两个病毒进程的互相检测频率较高,因此很难把两个病毒的进程结束掉。因此,只能让两个病毒进程都暂停后,再结束两个病毒的进程。
进程的暂停实质上是线程的暂停,因为进程是一个资源容器,而真正占用CPU时间的是线程。如果需要将进程暂停,就需要将进程中所有的线程全部暂停。关于进程的暂停与恢复,实质是对进程中的全部线程进行暂停与恢复。
1. 暂停与恢复线程所需函数
让线程暂停所使用的API函数是SuspendThread(),其定义如下:
- DWORD SuspendThread(
- HANDLE hThread // handle to thread
- );
该函数只有一个参数,是要暂停线程的句柄。获得线程的句柄使用OpenThread()函数,该函数的定义如下:
- HANDLE OpenThread(
- DWORD dwDesiredAccess, // access right
- BOOL bInheritHandle, // handle inheritance option
- DWORD dwThreadId // thread identifier
- );
该函数的使用方法与OpenProcess()类似,只是第3个参数是dwThreadId,即线程ID。
OpenThread()函数在VC6默认提供的PSDK中是不存在的,必须安装更新高版本的PSDK才可以使用该函数。如果没有更新PSDK的版本,那么需要使用LoadLibrary()和GetProcAddress()来动态调用OpenThread()函数。如果使用更高版本的VC开发环境,那么OpenThread()函数可以直接使用。
要暂停进程中的全部线程,则离不开枚举线程。枚举线程的函数是Thread32First()和Thread32Next()两个。在枚举线程前,仍然要使用CreateToolhelp32Snapshot()函数来创建系统进程快照,但是该函数不能创建指定进程中的线程快照。因为不能创建指定进程中的线程快照,所以在暂停线程时,必须对枚举到的线程进行判断,判断其是否属于指定进程中的线程。在THREADENTRY32结构体中,th32ThreadID表示当前枚举到线程的线程ID,th32OwnerProcessID则表示线程所属的进程ID。这样,在枚举线程时,只要判断是否属于指定的进程,即可进行暂停操作。
与线程暂停相对的是恢复暂停的线程。恢复暂停的线程的函数是ResumeThread(),其定义如下:
- DWORD ResumeThread(
- HANDLE hThread // handle to thread
- );
该函数的使用方法与SuspendThread()一样。恢复暂停的线程的方式与暂停线程的方式类似,不再重复说明。
2. 线程暂停与恢复的代码
线程暂停的代码如下:
- void CManageProcessDlg::OnBtnStop()
- {
- // TODO: Add your control notification handler code here
- int nPid = -1;
- nPid = GetSelectPid();
- // 进程 ID 为 0,则返回
- if ( nPid == 0 )
- {
- return ;
- }
- // 创建线程快照
- HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, nPid);
- if ( hSnap == INVALID_HANDLE_VALUE )
- {
- AfxMessageBox("CreateToolhelp32Snapshot Error");
- return ;
- }
- THREADENTRY32 Te32 = { 0 };
- Te32.dwSize = sizeof(THREADENTRY32);
- BOOL bRet = Thread32First(hSnap, &Te32);
- // 循环获取线程快照中的每一项
- while ( bRet )
- {
- // 得到属于选中进程的线程
- if ( Te32.th32OwnerProcessID == nPid )
- {
- // 打开线程
- HANDLE hThread = OpenThread(THREAD_ALL_ACCESS,
- FALSE, Te32.th32ThreadID);
- // 暂停线程
- SuspendThread(hThread);
- CloseHandle(hThread);
- }
- bRet = Thread32Next(hSnap, &Te32);
- }
- CloseHandle(hSnap);
- }
线程恢复的代码如下:
- void CManageProcessDlg::OnBtnResume()
- {
- // TODO: Add your control notification handler code here
- int nPid = -1;
- nPid = GetSelectPid();
- // 进程 ID 为 0,则返回
- if ( nPid == 0 )
- {
- return ;
- }
- // 创建线程快照
- HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, nPid);
- if ( hSnap == INVALID_HANDLE_VALUE )
- {
- AfxMessageBox("CreateToolhelp32Snapshot Error");
- return ;
- }
- THREADENTRY32 Te32 = { 0 };
- Te32.dwSize = sizeof(THREADENTRY32);
- BOOL bRet = Thread32First(hSnap, &Te32);
- // 循环获取线程快照中的每一项
- while ( bRet )
- {
- // 得到属于选中进程的线程
- if ( Te32.th32OwnerProcessID == nPid )
- {
- // 打开线程
- HANDLE hThread = OpenThread(THREAD_ALL_ACCESS,
- FALSE, Te32.th32ThreadID);
- // 暂停线程
- ResumeThread(hThread);
- CloseHandle(hThread);
- }
- bRet = Thread32Next(hSnap, &Te32);
- }
- }
3. 系统相关辅助工具介绍
首先看一款关于进程管理的工具Process Explorer,该工具的界面如图1所示。
图1 Process Explorer界面
该软件的功能非常强大。当启动一个进程或者结束一个进程的时候,该软件会高亮显示被启动或结束的进程。当然,它的功能非常多。大家自己研究挖掘一下。这里重点介绍该工具的一个小功能,单击菜单“Option”->“Replace Task Manager”。该功能是用来替换系统的任务管理器,也就是将Process Explorer设为默认的任务管理器。请按照上述设置方法进行设置,然后按下Ctrl+Shift+Esc组合键试试,系统默认的任务管理器不见了,而显示的是Process Explorer,系统默认的任务管理器已经被Process Explorer所替换。如果想要还原到原来的任务管理器,只要再次单击“Replace Task Manager”菜单项就可以了。
替换任务管理器的功能是如何实现的呢?原理非常简单,就是对注册表做了手脚,但是如何知道对注册表做了什么样的改动呢?另外一个值得推荐的工具叫作RegMon,它是用来监控注册表变化的工具。该软件如图2所示。
图2 RegMon界面
打开RegMon工具后,按下Ctrl+L组合键会出现“RegMon Filter”界面,在“Include”中填入“procexp.exe”(procexp.exe是Process Explorer工具的文件名),如图3所示。
图3 RegMon Filter界面
对“RegMon Filter”界面设置完毕后,单击“OK”按钮确认,回到Process Explorer工具中,单击其菜单的“Replace Task Manager”菜单项,看RegMon捕获到的注册表信息,如图4和图5所示。
图4 Process Explorer修改注册表的项
图5 Process Explorer修改注册表键的值
打开注册表编辑器,查看被修改注册表键值的内容,如图6所示。
图6 注册表中被修改的值
在注册表中,HKLM\Software\Microsoft\Windows NT\CurrentVersion\ImageFileExecution\taskmgr.exe\debugger的值为D盘下的ProcExp.exe的文件(该文件是Process Explorer的文件名),将该键值删掉,再按下Ctrl+Shift+Esc键,默认的任务管理器出现了。这就是注册表中有名的映像劫持,这是很多病毒、木马等恶意程序常用的方法。大家可以自己在这里编写的任务管理器中添加替换系统任务管理器的功能以做练习。