• 7.6 实现进程挂起与恢复


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

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

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

    #include 
    #include   
    #include 
    #include 
    
    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中所有的后动线程信息,如下图所示;

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

    DWORD SuspendThread(
      HANDLE hThread
    );
    

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

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

    ResumeThread 函数声明如下:

    DWORD ResumeThread(
      HANDLE hThread
    );
    

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

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

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

    #include 
    #include 
    #include 
    
    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 
    #include 
    #include 
    #include 
    
    #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 许可协议。转载请注明出处!

  • 相关阅读:
    分享的ise文件synthesize出错,如何解决?
    枚举 小蓝的漆房
    初级测试工程师(阶段总结)
    2022-08-19 第六小组 瞒春 学习笔记
    java从零开始系统性学习完整笔记(一)
    模型查搜相关研究
    炫酷动漫游戏网站页面设计html页面前端源码
    师傅教你~LNMP源码搭建
    Docker:使用 JupyterLab 进行数据科学
    20220627
  • 原文地址:https://www.cnblogs.com/LyShark/p/17725667.html