线程操作与进程挂起(Windows核心编程)
0x01 线程挂起与切换
- 对于挂起进程,挂起线程则比较简单,利用 ResumeThread 与 SuspendThread 即可,当线挂起时线程用户状态下的一切操作都会暂停
#include <Windows.h>
#include <iostream>
#include <process.h>
using namespace std;
unsigned __stdcall ThreadFun1(void *pvParam);
int main(int argc, char *argv[])
{
DWORD ThreadId1 = NULL;
HANDLE MyThread1 = (HANDLE)_beginthreadex(NULL, NULL, ThreadFun1, NULL, CREATE_SUSPENDED, (unsigned *)&ThreadId1);
// 挂起进程
SuspendThread(MyThread1);
cout << "线程开始挂起" << endl;
// 重新释放线程,也就是运行挂起的线程
ResumeThread(MyThread1);
ResumeThread(MyThread1);
WaitForSingleObject(MyThread1, INFINITE);
CloseHandle(MyThread1);
return 0;
}
unsigned __stdcall ThreadFun1(void *pvParam)
{
cout << "ThreadFun1" << endl;
return true;
}
- 为什么进行了两次 ResumeThread 操作呢,是因为 CREATE_SUSPENDED 也会将线程内核计数加 1 变为了 2

- 使用 SwitchToThread 切换线程
#include <Windows.h>
#include <iostream>
#include <process.h>
using namespace std;
unsigned __stdcall ThreadFun1(void *pvParam);
unsigned __stdcall ThreadFun2(void *pvParam);
int main(int argc, char *argv[])
{
DWORD ThreadId1 = NULL; DWORD ThreadId2 = NULL;
// 创建两个线程,并运行
HANDLE MyThread1 = (HANDLE)_beginthreadex(NULL, NULL, ThreadFun1, NULL, CREATE_SUSPENDED, (unsigned *)&ThreadId1); ResumeThread(MyThread1);
HANDLE MyThread2 = (HANDLE)_beginthreadex(NULL, NULL, ThreadFun2, NULL, CREATE_SUSPENDED, (unsigned *)&ThreadId2); ResumeThread(MyThread2);
// 等待线程关闭
WaitForSingleObject(MyThread1, INFINITE); WaitForSingleObject(MyThread2, INFINITE);
// 关闭句柄
CloseHandle(MyThread1); CloseHandle(MyThread2);
return 0;
}
unsigned __stdcall ThreadFun1(void *pvParam)
{
cout << " 切换 ThreadFun2 " << endl;
SwitchToThread();
cout << " ThreadFun1 is start! " << endl;
return true;
}
unsigned __stdcall ThreadFun2(void *pvParam)
{
cout << " ThreadFun2 is start! " << endl;
cout << " 切换 ThreadFun1 " << endl;
Sleep(200);
SwitchToThread();
return true;
}
- 其实 SwitchToThread 的根本作用就是 CPU 资源让给其他的线程

0x02 挂起进程
- Windows 并没有给出相应的内核 API 函数来挂起某一个进程,想要挂起进程就必须遍历进程中的所有运行中的线程并且将它们挂起,和挂起线程类似,只不过需要操作多个线程,示例如下:
#include <Windows.h>
#include <iostream>
#include <process.h>
#include <stdio.h>
#include <tlhelp32.h>
#include <strsafe.h>
using namespace std;
unsigned __stdcall ThreadFun1(void *pvParam);
unsigned __stdcall ThreadFun2(void *pvParam);
unsigned __stdcall ThreadFun3(void *pvParam);
DWORD WINAPI ControlProThreads(DWORD ProcessId, BOOL Suspend);
VOID WINAPI ErrorCodeTransformation(DWORD ErrorCode);
int main(int argc, char *argv[])
{
// 创建 3 个挂起线程 MyThread1、MyThread2、MyThread3,挂起计数为 1
DWORD ThreadId1 = NULL, ThreadId2 = NULL, ThreadId3 = NULL;
HANDLE MyThread1 = (HANDLE)_beginthreadex(NULL, NULL, ThreadFun1, NULL, CREATE_SUSPENDED, (unsigned *)&ThreadId1);
HANDLE MyThread2 = (HANDLE)_beginthreadex(NULL, NULL, ThreadFun2, NULL, CREATE_SUSPENDED, (unsigned *)&ThreadId2);
HANDLE MyThread3 = (HANDLE)_beginthreadex(NULL, NULL, ThreadFun3, NULL, CREATE_SUSPENDED, (unsigned *)&ThreadId3);
// 将挂起计数 -1 变为 0,表示运行线程
ResumeThread(MyThread1); ResumeThread(MyThread2); ResumeThread(MyThread3);
// 获取当前进程的 ID 保存在 ProcessId 中
HANDLE DupProcessHandle = NULL; DWORD ProcessId;
DuplicateHandle(GetCurrentProcess(), GetCurrentProcess(), GetCurrentProcess(), &DupProcessHandle, 0, FALSE, DUPLICATE_SAME_ACCESS);
ProcessId = GetProcessId(DupProcessHandle);
// 用于挂起进程的函数,参数一为需要挂起的进程 ID,参数而表示是否为挂起操作,反之则为释放操作
DWORD res = ControlProThreads(ProcessId, TRUE);
// 若调用失败,打印错误信息
if (res != TRUE) ErrorCodeTransformation(res);
// 等待所有线程退出
HANDLE Threads[3]; Threads[0] = MyThread1; Threads[1] = MyThread2; Threads[2] = MyThread3;
WaitForMultipleObjects(3, Threads, TRUE, INFINITE);
// 关闭句柄
CloseHandle(MyThread1); CloseHandle(MyThread2); CloseHandle(MyThread3);
return 0;
}
DWORD WINAPI ControlProThreads(DWORD ProcessId, BOOL Suspend)
{
// 定义函数错误代码
DWORD LastError = NULL;
// 通过 CreateToolhelp32Snapshot 函数获取当前系统所有线程的快照
HANDLE ProcessHandle = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, ProcessId);
if (ProcessHandle == INVALID_HANDLE_VALUE)
{
// 函数调用失败,返回错误代码,方便打印出错误信息
LastError = GetLastError();
return LastError;
}
// 判断进程是否为当前进程,SkipMainThread 的作用是是否跳过主线程
INT SkipMainThread = 0; HANDLE DupProcessHandle = NULL;
DuplicateHandle(GetCurrentProcess(), GetCurrentProcess(), GetCurrentProcess(), &DupProcessHandle, 0, FALSE, DUPLICATE_SAME_ACCESS);
DWORD IsThisProcess = GetProcessId(DupProcessHandle);
// 如果传入的进程 ID 不是当前进程,则将 SkipMainThread 变为 1,表示不跳过主线程
if (ProcessId != IsThisProcess) SkipMainThread = 1;
THREADENTRY32 ThreadInfo = { sizeof(ThreadInfo) };
BOOL fOk = Thread32First(ProcessHandle, &ThreadInfo);
while (fOk)
{
// 将线程 ID 转换为线程句柄
HANDLE ThreadHandle = OpenThread(THREAD_SUSPEND_RESUME, FALSE, ThreadInfo.th32ThreadID);
// 是否为传入进程的子线程,且判断是挂起操作还是释放操作
if (ThreadInfo.th32OwnerProcessID == ProcessId && Suspend == TRUE)
{
// 如果是当前进程,就跳过主线程,SkipMainThread == 0 表示为主线程
if (SkipMainThread != 0)
{
// 挂起线程
DWORD count = SuspendThread(ThreadHandle);
cout << "[*] 线程号: " << ThreadInfo.th32ThreadID << " 挂起系数 + 1 " << " 上一次挂起系数为: " << count << endl;
}
SkipMainThread++;
}
else if(ThreadInfo.th32OwnerProcessID == ProcessId && Suspend == FALSE)
{
if (SkipMainThread != 0)
{
// 释放线程
DWORD count = ResumeThread(ThreadHandle);
cout << "[-] 线程号: " << ThreadInfo.th32ThreadID << " 挂起系数 - 1 " << " 上一次挂起系数为: " << count << endl;
}
SkipMainThread++;
}
fOk = Thread32Next(ProcessHandle, &ThreadInfo);
}
// 关闭句柄
CloseHandle(ProcessHandle);
}
unsigned __stdcall ThreadFun1(void *pvParam)
{
cout << "ThreadFun1" << endl;
// 睡眠 2000 毫秒,模拟线程正在执行工作
Sleep(2000);
return true;
}
unsigned __stdcall ThreadFun2(void *pvParam)
{
cout << "ThreadFun2" << endl;
// 睡眠 2000 毫秒,模拟线程正在执行的工作
Sleep(2000);
return true;
}
unsigned __stdcall ThreadFun3(void *pvParam)
{
cout << "ThreadFun3" << endl;
// 睡眠 2000 毫秒,模拟线程正在执行的工作
Sleep(2000);
return true;
}
// 如果返回错误,可调用此函数打印详细错误信息
VOID WINAPI ErrorCodeTransformation(DWORD ErrorCode)
{
LPVOID lpMsgBuf; LPVOID lpDisplayBuf; DWORD dw = ErrorCode;
// 将错误代码转换为错误信息
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, NULL
);
lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT, (lstrlen((LPCTSTR)lpMsgBuf) + 40) * sizeof(TCHAR));
StringCchPrintf((LPTSTR)lpDisplayBuf, LocalSize(lpDisplayBuf), TEXT("错误代码 %d : %s"), dw, lpMsgBuf);
// 弹窗显示错误信息
MessageBox(NULL, (LPCTSTR)lpDisplayBuf, TEXT("Error"), MB_OK);
LocalFree(lpMsgBuf); LocalFree(lpDisplayBuf); ExitProcess(dw);
}
- 值得注意的是,ControlProThreads 函数会判断传入的进程 ID 是否为当前进程 ID,如果是当前进程 则跳过主线程,否则主线程被挂起,所有的操作都会暂停,相当于一种特殊的死锁
- 而 ErrorCodeTransformation 函数则是将错误代码转换为错误信息,方便查找出错误,在使用内核 API 进行编程时尤其需要注意这一点
- 当然 ControlProThreads 运行起来也会有一定的风险,因为获取的快照之后可能会有新线程创建,也会有旧进程销毁;如下图所示,挂起了除了主线程外的当前进程的所有线程

- 如果需要释放挂起的进程,只需要将 FALSE 传递给 ControlProThreads 的第二个参数即可
// 用于挂起进程的函数,参数一为需要挂起的进程 ID,参数而表示是否为挂起操作,反之则为释放操作
ControlProThreads(ProcessId, TRUE);
Sleep(2000);
cout << "\n 睡眠 2000 毫秒 \n" << endl;
ControlProThreads(ProcessId, FALSE);
- 这样就相当于暂停线程再运行线程,相应的假如多次挂起,则需要对应多次释放操作,线程内核计数变为 0 的时候就可以被 CUP 调度了

0x03 线程(进程)执行时间
- 想要获取进程和线程的时间信息,可以使用微软提供的 GetThreadTime 和 GetProcessTime 这两个函数
#include <Windows.h>
#include <iostream>
#include <process.h>
using namespace std;
unsigned __stdcall ThreadFun1(void *pvParam);
int main(int argc, char *argv[])
{
// 创建进程
TCHAR CmdLine[8] = TEXT("CMD.EXE");
STARTUPINFO StartInfo = { sizeof(StartInfo) }; PROCESS_INFORMATION ProcessInfo = { 0 };
CreateProcess(NULL, CmdLine, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &StartInfo, &ProcessInfo);
WaitForSingleObject(ProcessInfo.hProcess, INFINITE);
// 获取进程执行的时间
FILETIME ftCreationTime, ftExitTime, ftKernelTime, ftUserTime;
GetProcessTimes(ProcessInfo.hProcess, &ftCreationTime, &ftExitTime, &ftKernelTime, &ftUserTime);
cout << "hProcess 所用的内核时间为: " << ftKernelTime.dwLowDateTime / 10000 << " - hProcess 所用的用户时间为: " << ftUserTime.dwLowDateTime / 10000 << endl;
// 创建线程
DWORD ThreadId1 = NULL;
HANDLE MyThread1 = (HANDLE)_beginthreadex(NULL, NULL, ThreadFun1, NULL, CREATE_SUSPENDED, (unsigned *)&ThreadId1); ResumeThread(MyThread1);
WaitForSingleObject(MyThread1, INFINITE);
// 获取线程的执行时间
GetThreadTimes(MyThread1, &ftCreationTime, &ftExitTime, &ftKernelTime, &ftUserTime);
cout << "MyThread1 所用的内核时间为: " << ftKernelTime.dwLowDateTime / 10000 << " - MyThread1 所用的用户时间为: " << ftUserTime.dwLowDateTime / 10000 << endl;
// 关闭句柄
CloseHandle(MyThread1);
return 0;
}
unsigned __stdcall ThreadFun1(void *pvParam)
{
Sleep(100);
return true;
}
- 运行如下所示,还是有一点不准确的

- 要想获得更准确的运行时间可以这么办,利用 QueryPerformanceFrequency 配合 QueryPerformanceCounter 就可以达到更为精确的运行时间
#include <Windows.h>
#include <iostream>
#include <process.h>
using namespace std;
int main(int argc, char *argv[])
{
LARGE_INTEGER nFreq; LARGE_INTEGER nBeginTime; LARGE_INTEGER nEndTime; double time;
QueryPerformanceFrequency(&nFreq);
// 开始计时
QueryPerformanceCounter(&nBeginTime);
// 创建进程
TCHAR CmdLine[8] = TEXT("CMD.EXE");
STARTUPINFO StartInfo = { sizeof(StartInfo) }; PROCESS_INFORMATION ProcessInfo = { 0 };
CreateProcess(NULL, CmdLine, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &StartInfo, &ProcessInfo);
WaitForSingleObject(ProcessInfo.hProcess, INFINITE);
// 结束及时
QueryPerformanceCounter(&nEndTime);
time = (double)(nEndTime.QuadPart - nBeginTime.QuadPart) / (double)nFreq.QuadPart;
cout << "进程运行时间为: " << time << " 秒 " << endl;
return 0;
}
- 相比 GetThreadTime 和 GetProcessTime 这两个函数,确实精确了很多,原因是由于计时的线程是非抢占式的,执行完以后才会执行其他线程

线程操作与进程挂起(Windows核心编程)的更多相关文章
- CreateThread 线程操作与 _beginthreadex 线程安全(Windows核心编程)
0x01 线程的创建 线程不同于进程,Windows 中的进程是拥有 '惰性' 的,本身并不执行任何代码,而执行代码的任务转交给主线程,列如使用 CreateProcess 创建一个进程打开 Cmd ...
- windows核心编程---第八章 使用内核对象进行线程同步
使用内核对象进行线程同步. 前面我们介绍了用户模式下线程同步的几种方式.在用户模式下进行线程同步的最大好处就是速度非常快.因此当需要使用线程同步时用户模式下的线程同步是首选. 但是用户模式下的线程同步 ...
- 《windows核心编程系列》十九谈谈使用远程线程来注入DLL。
windows内的各个进程有各自的地址空间.它们相互独立互不干扰保证了系统的安全性.但是windows也为调试器或是其他工具设计了一些函数,这些函数可以让一个进程对另一个进程进行操作.虽然他们是为调试 ...
- windows核心编程 - 线程同步机制
线程同步机制 常用的线程同步机制有很多种,主要分为用户模式和内核对象两类:其中 用户模式包括:原子操作.关键代码段 内核对象包括:时间内核对象(Event).等待定时器内核对象(WaitableTim ...
- 【windows核心编程】 第六章 线程基础
Windows核心编程 第六章 线程基础 欢迎转载 转载请注明出处:http://www.cnblogs.com/cuish/p/3145214.html 1. 线程的组成 ① 一个是线程的内核 ...
- 用户模式下的线程同步的分析(Windows核心编程)
线程同步 同一进程或者同一线程可以生成许多不同的子线程来完成规定的任务,但是多个线程同时运行的情况下可能需要对某个资源进行读写访问,比如以下这个情况:创建两个线程对同一资源进行访问,最后打印出这个资源 ...
- 【windows核心编程】 第八章 用户模式下的线程同步
Windows核心编程 第八章 用户模式下的线程同步 1. 线程之间通信发生在以下两种情况: ① 需要让多个线程同时访问一个共享资源,同时不能破坏资源的完整性 ② 一个线程需要通知其他线程 ...
- Windows核心编程:第4章 进程
Github https://github.com/gongluck/Windows-Core-Program.git //第4章 进程.cpp: 定义应用程序的入口点. // #include &q ...
- 使用同步或异步的方式完成 I/O 访问和操作(Windows核心编程)
0x01 Windows 中对文件的底层操作 Windows 为了方便开发人员操作 I/O 设备(这些设备包括套接字.管道.文件.串口.目录等),对这些设备的差异进行了隐藏,所以开发人员在使用这些设备 ...
随机推荐
- Mock 框架 Moq 的使用
Mock 框架 Moq 的使用 Intro Moq 是 .NET 中一个很流行的 Mock 框架,使用 Mock 框架我们可以只针对我们关注的代码进行测试,对于依赖项使用 Mock 对象配置预期的依赖 ...
- 扫盲贴|如何评价一款App的稳定性和质量?
作者:友盟+移动开发专家 张文 「崩溃」与「卡顿」.「异常退出」等一样,是影响App稳定性常见的三种情况.相关数据显示,当iOS的崩溃率超过0.8%,Android的崩溃率超过0.4%的时候,活跃用户 ...
- [源码分析] 消息队列 Kombu 之 mailbox
[源码分析] 消息队列 Kombu 之 mailbox 0x00 摘要 本系列我们介绍消息队列 Kombu.Kombu 的定位是一个兼容 AMQP 协议的消息队列抽象.通过本文,大家可以了解 Komb ...
- 复制粘贴Ctrl+C改为自定义单键
目录 那一瞬间不想用Ctrl+C了 (一)搜索渠道 关键词搜索 (二)方案对比 (三)最终方案Ditto 使用方法 (四)案例 1. 替换Ctrl+C快捷键: 2. 将英文小写替换为大写: 那一瞬间不 ...
- Git - 使用命令和P4Merge进行diff
P4Merge P4Merge是Git的一个第三发Diff和Merge工具(可视化冲突解决工具). 下载地址: https://www.perforce.com/downloads/visual-me ...
- 使用docker搭建sonarqube
sonarqube是一款代码质量检查工具,使用sonar扫描我们写过的代码,可以有助于检查出代码的bug.规范性和健壮性,有助于提高我们的代码质量. 一.安装docker 安装完成之后,命令行输入 d ...
- FFmpeg API的简单实践应用
0. 前言 利用 FFmpeg 编译链接生成的可执行程序本身可以实现很多特定的功能,但如果我们有自己的个性化需求,想要在自己开发的项目中使用 ffmpeg 的一些功能,就需要理解并应用其已经实现好的A ...
- 一键部署etcd集群管理脚本
一.编写脚本 1 #!/bin/sh 2 # 安装 3 # ./run.sh etcd03 etcd01=http://192.168.2.44:2380,etcd02=http://192.168. ...
- 对于api接口的爬虫,通常的解决方法
对于api接口的爬虫,通常的解决方法: 依靠爬虫功能的IP库 SDK 源站用不同的状态码进行打标,用户登录账号,登录成功返回200,登录失败返回其他状态码.
- 第15 章 : 深入解析 Linux 容器
深入解析 Linux 容器 今天的内容主要分成以下三个部分 资源隔离和限制: 容器镜像的构成: 容器引擎的构成: 前两个部分就是资源隔离和限制还有容器镜像的构成,第三部分会以一个业界比较成熟的容器引擎 ...