7.6 实现进程挂起与恢复

挂起与恢复进程是指暂停或恢复进程的工作状态,以达到一定的控制和管理效果。在 Windows 操作系统中,可以使用系统提供的函数实现进程的挂起和恢复,以达到对进程的控制和调度。需要注意,过度使用进程挂起/恢复操作可能会造成系统性能的降低,导致死锁等问题,因此在使用时应该谨慎而慎重。同时,通过和其他进程之间协同工作,也可以通过更加灵活的方式,实现进程的协调、交互等相应的功能,从而实现更加高效和可靠的进程管理。

要实现挂起进程,首先我们需要实现挂起线程,因为挂起进程的实现原理是通过调用SuspendThread函数循环将进程内的所有线程全部挂起后实现的,而要实现挂起线程则我们需要先确定指定进程内的线程信息,要实现枚举进程内的线程信息则可以通过以下几个步骤实现。

首先通过CreateToolhelp32Snapshot得到当前系统下所有的进程快照,并通过遍历进程的方式寻找是否符合我们所需要枚举的进程名,如果是则调用CreateToolhelp32Snapshot并通过传入TH32CS_SNAPTHREAD代表枚举线程,通过循环的方式遍历进程内的线程,每次通过调用OpenThread打开线程,并调用ZwQueryInformationThread查询该线程的入口信息以及线程所在的模块信息,最后以此输出即可得到当前进程内的所有线程信息。

#include <iostream> #include <Windows.h>   #include <TlHelp32.h> #include <Psapi.h>  using namespace std;  typedef enum _THREADINFOCLASS {   ThreadBasicInformation,   ThreadTimes,   ThreadPriority,   ThreadBasePriority,   ThreadAffinityMask,   ThreadImpersonationToken,   ThreadDescriptorTableEntry,   ThreadEnableAlignmentFaultFixup,   ThreadEventPair_Reusable,   ThreadQuerySetWin32StartAddress,   ThreadZeroTlsCell,   ThreadPerformanceCount,   ThreadAmILastThread,   ThreadIdealProcessor,   ThreadPriorityBoost,   ThreadSetTlsArrayAddress,   ThreadIsIoPending,   ThreadHideFromDebugger,   ThreadBreakOnTermination,   MaxThreadInfoClass }THREADINFOCLASS;  typedef struct _CLIENT_ID {   HANDLE UniqueProcess;   HANDLE UniqueThread; }CLIENT_ID;  typedef struct _THREAD_BASIC_INFORMATION {   LONG ExitStatus;   PVOID TebBaseAddress;   CLIENT_ID ClientId;   LONG AffinityMask;   LONG Priority;   LONG BasePriority; }THREAD_BASIC_INFORMATION, *PTHREAD_BASIC_INFORMATION;  extern "C" LONG(__stdcall * ZwQueryInformationThread) ( IN HANDLE ThreadHandle, IN THREADINFOCLASS ThreadInformationClass, OUT PVOID ThreadInformation, IN ULONG ThreadInformationLength, OUT PULONG ReturnLength OPTIONAL ) = NULL;  // 枚举进程内的线程 BOOL EnumThread(char *ProcessName) {   // 进程快照句柄   HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);   PROCESSENTRY32 process = { sizeof(PROCESSENTRY32) };    // 遍历进程   while (Process32Next(hProcessSnap, &process))   {     // char* 转 string     string s_szExeFile = process.szExeFile;     if (s_szExeFile == ProcessName)     {       HANDLE hThreadSnap = INVALID_HANDLE_VALUE;       THREADENTRY32 te32;        // 创建线程快照       hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);       if (hThreadSnap == INVALID_HANDLE_VALUE)       {         return FALSE;       }        // 为快照分配内存空间       te32.dwSize = sizeof(THREADENTRY32);        // 获取第一个线程的信息       if (!Thread32First(hThreadSnap, &te32))       {         return FALSE;       }        // 遍历线程       while (Thread32Next(hThreadSnap, &te32))       {         // 判断线程是否属于本进程         if (te32.th32OwnerProcessID == process.th32ProcessID)         {           // 打开线程           HANDLE hThread = OpenThread(             THREAD_ALL_ACCESS,        // 访问权限             FALSE,                    // 由此线程创建的进程不继承线程的句柄             te32.th32ThreadID         // 线程 ID             );           if (hThread == NULL)           {             return FALSE;           }            // 将区域设置设置为从操作系统获取的ANSI代码页           setlocale(LC_ALL, ".ACP");            // 获取 ntdll.dll 的模块句柄           HINSTANCE hNTDLL = ::GetModuleHandle("ntdll");            // 从 ntdll.dll 中取出 ZwQueryInformationThread           (FARPROC&)ZwQueryInformationThread = GetProcAddress(hNTDLL, "ZwQueryInformationThread");            // 获取线程入口地址           PVOID startaddr;                          // 用来接收线程入口地址           ZwQueryInformationThread(             hThread,                              // 线程句柄             ThreadQuerySetWin32StartAddress,      // 线程信息类型 ThreadQuerySetWin32StartAddress 线程入口地址             &startaddr,                           // 指向缓冲区的指针             sizeof(startaddr),                    // 缓冲区的大小             NULL             );            // 获取线程所在模块           THREAD_BASIC_INFORMATION tbi;            // _THREAD_BASIC_INFORMATION 结构体对象           TCHAR modname[MAX_PATH];                 // 用来接收模块全路径           ZwQueryInformationThread(             hThread,                             // 线程句柄             ThreadBasicInformation,              // 线程信息类型,ThreadBasicInformation :线程基本信息             &tbi,                                // 指向缓冲区的指针             sizeof(tbi),                         // 缓冲区的大小             NULL             );            // 检查入口地址是否位于某模块中           GetMappedFileName(             OpenProcess(                                        // 进程句柄             PROCESS_ALL_ACCESS,                                 // 访问权限             FALSE,                                              // 由此线程创建的进程不继承线程的句柄             (DWORD)tbi.ClientId.UniqueProcess                   // 唯一进程 ID             ),             startaddr,                            // 要检查的地址             modname,                              // 用来接收模块名的指针             MAX_PATH                              // 缓冲区大小             );           std::cout << "线程ID: " << te32.th32ThreadID << " 线程入口: " << startaddr << " 所在模块: " << modname << std::endl;         }       }     }   } }  int main(int argc, char* argv[]) {   EnumThread("lyshark.exe");    system("pause");   return 0; } 

读者可自行运行上述代码片段,即可枚举出当前运行进程lyshark.exe中所有的后动线程信息,如下图所示;

7.6 实现进程挂起与恢复

当我们能够得到当前进程内的线程信息后,接下来就是实现如何挂起或恢复进程内的特定线程,挂起线程可以使用SuspendThread 其函数声明如下:

DWORD SuspendThread(   HANDLE hThread ); 

其中,hThread 是一个指向线程句柄的指针,指向要挂起的线程的句柄,该函数返回挂起前线程的线程计数器值,表示被挂起线程在挂起前还未执行的指令数目。

可以多次调用 SuspendThread 函数将同一个线程进行多次挂起,每次返回被挂起前线程的线程计数器值,每调用一次则会阻塞该线程,其状态会变为挂起状态。当该线程被 ResumeThread 恢复时,它将继续从上次挂起时的位置开始执行。

ResumeThread 函数声明如下:

DWORD ResumeThread(   HANDLE hThread ); 

其中,hThread 是线程句柄,指向要恢复的线程的句柄。

调用 ResumeThread 函数可以让一个被挂起的线程从上次挂起的位置开始继续执行,函数返回值是被恢复的线程的先前挂起次数。当被恢复的线程的挂起计数器归零时,其状态将自动变为非挂起状态,并开始继续执行。

当有了上述两个函数的支持那么挂起线程将变得很容易实现了,首先后去所有进程快照,接着就是直接打开OpenThread()符合要求的线程,此时只需要调用SuspendThread(hThread)即可挂起一个线程,调用ResumeThread(hThread)则可以恢复一个线程,具体实现代码如下所示;

#include <windows.h> #include <stdio.h> #include <TlHelp32.h>  int Start_Stop_Thread(DWORD Pid, DWORD ThreadID, BOOL flag) {     THREADENTRY32 te32 = { 0 };     te32.dwSize = sizeof(THREADENTRY32);      // 获取全部线程快照     HANDLE hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);     if (INVALID_HANDLE_VALUE != hThreadSnap)     {         // 获取快照中第一条信息         BOOL bRet = Thread32First(hThreadSnap, &te32);         while (bRet)         {             // 只过滤出 pid 里面的线程             if (Pid == te32.th32OwnerProcessID)             {                 // 判断是否为ThreadID,暂停指定的TID                 if (ThreadID == te32.th32ThreadID)                 {                     // 打开线程                     HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te32.th32ThreadID);                      if (flag == TRUE)                     {                         ResumeThread(hThread);     // 恢复线程                     }                     else                     {                         SuspendThread(hThread);     // 暂停线程                     }                     CloseHandle(hThreadSnap);                 }             }             // 获取快照中下一条信息             bRet = Thread32Next(hThreadSnap, &te32);         }         return 0;     }     return -1; }  int main(int argc, char* argv[]) {     // 暂停或恢复进程ID = 4204 里面的线程ID = 10056     int ret = Start_Stop_Thread(4204, 10056, TRUE);    // TRUE = 恢复线程 FALSE = 挂起线程     printf("状态: %d n", ret);      system("pause");     return 0; } 

当有了上述功能的支持以后,那么实现挂起进程将变得很容易,读者只需要在特定一个进程内枚举出所有的活动线程,并通过循环的方式逐个挂起即可实现挂起整个进程的效果,这段完整代码如下所示;

#include <windows.h> #include <iostream> #include <tlhelp32.h> #include <Psapi.h>  #pragma comment(lib,"psapi.lib")  #define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0) #define SystemProcessesAndThreadsInformation    5       // 功能号 typedef DWORD(WINAPI* PQUERYSYSTEM)(UINT, PVOID, DWORD, PDWORD);  // 线程状态的枚举常量 typedef enum _THREAD_STATE {   StateInitialized,    // 初始化状态   StateReady,          // 准备状态   StateRunning,        // 运行状态   StateStandby,   StateTerminated,     // 关闭   StateWait,           // 等待   StateTransition,     // 切换   StateUnknown }THREAD_STATE;  // 线程处于等待的原因的枚举常量 typedef enum _KWAIT_REASON {   Executive,   FreePage,   PageIn,   PoolAllocation,   DelayExecution,   Suspended,   UserRequest,   WrExecutive,   WrFreePage,   WrPageIn,   WrPoolAllocation,   WrDelayExecution,   WrSuspended,   WrUserRequest,   WrEventPair,   WrQueue,   WrLpcReceive,   WrLpcReply,   WrVirtualMemory,   WrPageOut,   WrRendezvous,   Spare2,   Spare3,   Spare4,   Spare5,   Spare6,   WrKernel,   MaximumWaitReason }KWAIT_REASON;  typedef LONG   NTSTATUS; typedef LONG   KPRIORITY;  typedef struct _CLIENT_ID {   DWORD        UniqueProcess;   DWORD        UniqueThread; } CLIENT_ID, * PCLIENT_ID;  typedef struct _VM_COUNTERS {   SIZE_T        PeakVirtualSize;   SIZE_T        VirtualSize;   ULONG         PageFaultCount;   SIZE_T        PeakWorkingSetSize;   SIZE_T        WorkingSetSize;   SIZE_T        QuotaPeakPagedPoolUsage;   SIZE_T        QuotaPagedPoolUsage;   SIZE_T        QuotaPeakNonPagedPoolUsage;   SIZE_T        QuotaNonPagedPoolUsage;   SIZE_T        PagefileUsage;   SIZE_T        PeakPagefileUsage; } VM_COUNTERS;  // 线程信息结构体 typedef struct _SYSTEM_THREAD_INFORMATION {   LARGE_INTEGER   KernelTime;   LARGE_INTEGER   UserTime;   LARGE_INTEGER   CreateTime;   ULONG           WaitTime;   PVOID           StartAddress;   CLIENT_ID       ClientId;   KPRIORITY       Priority;   KPRIORITY       BasePriority;   ULONG           ContextSwitchCount;   LONG            State;// 状态,是THREAD_STATE枚举类型中的一个值   LONG            WaitReason;//等待原因, KWAIT_REASON中的一个值 } SYSTEM_THREAD_INFORMATION, * PSYSTEM_THREAD_INFORMATION;   typedef struct _UNICODE_STRING {   USHORT Length;   USHORT MaximumLength;   PWSTR  Buffer; } UNICODE_STRING, * PUNICODE_STRING;  // 进程信息结构体 typedef struct _SYSTEM_PROCESS_INFORMATION {   ULONG            NextEntryDelta;    // 指向下一个结构体的指针   ULONG            ThreadCount;       // 本进程的总线程数   ULONG            Reserved1[6];      // 保留   LARGE_INTEGER    CreateTime;        // 进程的创建时间   LARGE_INTEGER    UserTime;          // 在用户层的使用时间   LARGE_INTEGER    KernelTime;        // 在内核层的使用时间   UNICODE_STRING   ProcessName;       // 进程名   KPRIORITY        BasePriority;   ULONG            ProcessId;         // 进程ID   ULONG            InheritedFromProcessId;   ULONG            HandleCount;       // 进程的句柄总数   ULONG            Reserved2[2];      // 保留   VM_COUNTERS      VmCounters;   IO_COUNTERS      IoCounters;   SYSTEM_THREAD_INFORMATION Threads[5];    // 子线程信息数组 }SYSTEM_PROCESS_INFORMATION, * PSYSTEM_PROCESS_INFORMATION;  // 获取线程是被是否被挂起 1=表示线程被挂起  0=表示线程正常 -1=未知状态 int IsThreadSuspend(DWORD dwProcessID, DWORD dwThreadID) {   int nRet = 0;   NTSTATUS Status = 0;    PQUERYSYSTEM NtQuerySystemInformation = NULL;   PSYSTEM_PROCESS_INFORMATION pInfo = { 0 };    // 获取函数地址   NtQuerySystemInformation = (PQUERYSYSTEM) GetProcAddress(LoadLibrary("ntdll.dll"), "NtQuerySystemInformation");    DWORD   dwSize = 0;    // 获取信息所需的缓冲区大小   Status = NtQuerySystemInformation(SystemProcessesAndThreadsInformation,// 要获取的信息的类型     NULL, // 用于接收信息的缓冲区     0,  // 缓冲区大小     &dwSize   );    // 申请缓冲区   char* pBuff = new char[dwSize];   pInfo = (PSYSTEM_PROCESS_INFORMATION)pBuff;   if (pInfo == NULL)     return -1;    // 再次调用函数, 获取信息   Status = NtQuerySystemInformation(SystemProcessesAndThreadsInformation, // 要获取的信息的类型     pInfo, // 用于接收信息的缓冲区     dwSize,  // 缓冲区大小     &dwSize   );   if (!NT_SUCCESS(Status))   {     /*如果函数执行失败*/     delete[] pInfo;     return -1;   }    // 遍历结构体,找到对应的进程   while (1)   {     // 判断是否还有下一个进程     if (pInfo->NextEntryDelta == 0)       break;      // 判断是否找到了ID     if (pInfo->ProcessId == dwProcessID)     {        // 找到该进程下的对应的线程,也就是遍历所有线程       for (DWORD i = 0; i < pInfo->ThreadCount; i++)       {         if (pInfo->Threads[i].ClientId.UniqueThread == dwThreadID)         {           // 找到线程            // 如果线程被挂起           if (pInfo->Threads[i].State == StateWait&& pInfo->Threads[i].WaitReason == Suspended)           {             nRet = 1;             break;           }         }       }       break;     }     // 迭代到下一个节点     pInfo = (PSYSTEM_PROCESS_INFORMATION)(((PUCHAR)pInfo) + pInfo->NextEntryDelta);   }    delete[] pBuff;   return nRet; }  // 设置进程状态 挂起/非挂起 int SuspendProcess(DWORD dwProcessID, BOOL fSuspend) {   HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, dwProcessID);    if (hSnapshot != INVALID_HANDLE_VALUE)   {     THREADENTRY32 te = { sizeof(te) };     BOOL fOk = Thread32First(hSnapshot, &te);     for (; fOk; fOk = Thread32Next(hSnapshot, &te))     {       if (te.th32OwnerProcessID == dwProcessID)       {         HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME,FALSE, te.th32ThreadID);         if (hThread != NULL)         {           if (fSuspend)           {             if (IsThreadSuspend(dwProcessID, te.th32ThreadID) == 0)             {               SuspendThread(hThread);             }             else             {               return 0;             }           }           else           {             if (IsThreadSuspend(dwProcessID, te.th32ThreadID) == 1)             {               ResumeThread(hThread);             }             else             {               return 0;             }           }         }         CloseHandle(hThread);       }     }    }   CloseHandle(hSnapshot);   return 1; }  int main(int argc, char *argv[]) {   // 挂起进程   SuspendProcess(20308, TRUE);    // 恢复进程   SuspendProcess(20308, FALSE);    return 0; } 

读者可自行编译并运行上述代码,通过调用SuspendProcess函数并以此传入需要挂起的进程PID以及一个状态,当该状态为TRUE时则代表挂起进程,而当状态值为FALSE时则代表为恢复一个进程,当一个进程被挂起后其会出现卡死的现象,当恢复后一切都会变得正常。

本文作者: 王瑞
本文链接: https://www.lyshark.com/post/5fbc3082.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

发表评论

相关文章