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核心编程)的更多相关文章

  1. CreateThread 线程操作与 _beginthreadex 线程安全(Windows核心编程)

    0x01 线程的创建 线程不同于进程,Windows 中的进程是拥有 '惰性' 的,本身并不执行任何代码,而执行代码的任务转交给主线程,列如使用 CreateProcess 创建一个进程打开 Cmd ...

  2. windows核心编程---第八章 使用内核对象进行线程同步

    使用内核对象进行线程同步. 前面我们介绍了用户模式下线程同步的几种方式.在用户模式下进行线程同步的最大好处就是速度非常快.因此当需要使用线程同步时用户模式下的线程同步是首选. 但是用户模式下的线程同步 ...

  3. 《windows核心编程系列》十九谈谈使用远程线程来注入DLL。

    windows内的各个进程有各自的地址空间.它们相互独立互不干扰保证了系统的安全性.但是windows也为调试器或是其他工具设计了一些函数,这些函数可以让一个进程对另一个进程进行操作.虽然他们是为调试 ...

  4. windows核心编程 - 线程同步机制

    线程同步机制 常用的线程同步机制有很多种,主要分为用户模式和内核对象两类:其中 用户模式包括:原子操作.关键代码段 内核对象包括:时间内核对象(Event).等待定时器内核对象(WaitableTim ...

  5. 【windows核心编程】 第六章 线程基础

    Windows核心编程 第六章 线程基础 欢迎转载 转载请注明出处:http://www.cnblogs.com/cuish/p/3145214.html 1. 线程的组成 ①    一个是线程的内核 ...

  6. 用户模式下的线程同步的分析(Windows核心编程)

    线程同步 同一进程或者同一线程可以生成许多不同的子线程来完成规定的任务,但是多个线程同时运行的情况下可能需要对某个资源进行读写访问,比如以下这个情况:创建两个线程对同一资源进行访问,最后打印出这个资源 ...

  7. 【windows核心编程】 第八章 用户模式下的线程同步

    Windows核心编程 第八章 用户模式下的线程同步 1. 线程之间通信发生在以下两种情况: ①    需要让多个线程同时访问一个共享资源,同时不能破坏资源的完整性 ②    一个线程需要通知其他线程 ...

  8. Windows核心编程:第4章 进程

    Github https://github.com/gongluck/Windows-Core-Program.git //第4章 进程.cpp: 定义应用程序的入口点. // #include &q ...

  9. 使用同步或异步的方式完成 I/O 访问和操作(Windows核心编程)

    0x01 Windows 中对文件的底层操作 Windows 为了方便开发人员操作 I/O 设备(这些设备包括套接字.管道.文件.串口.目录等),对这些设备的差异进行了隐藏,所以开发人员在使用这些设备 ...

随机推荐

  1. JDBC 连接池 & Template

    数据库连接池 # 概念:其实就是一个容器(集合),存放数据库连接的容器. * 当系统初始化号以后,容器被创建,容器中会申请一些连接对象,当用户来访问数据库时,从容其中获取连接对象,用户访问完之后,会将 ...

  2. VS2008开发WinCE程序编译速度慢的解决办法

    1.找到以下文件 C:\Windows\Microsoft.NET\Framework\v3.5\Microsoft.CompactFramework.Common.targets 2.用记事本打开该 ...

  3. Html5分页显示Table

    Html: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <met ...

  4. P1422小玉家的电费(JAVA语言)

    package 顺序与分支; /* * 题目描述 夏天到了,各家各户的用电量都增加了许多,相应的电费也交的更多了. 小玉家今天收到了一份电费通知单.小玉看到上面写:据闽价电[2006]27号规定, 月 ...

  5. sqli-labs系列——第三关

    less3 判断注入类型 这第三关有点意思,是一个带括号的数字型注入,这里需要闭合它的括号,之前遇到过很多这样的站,它的sql语句一般都是这样的: $sql = select * from user ...

  6. Azure Front Door(三)启用 Web Application Firewall (WAF) 保护Web 应用程序,拒绝恶意攻击

    一,引言 上一篇我们利用 Azure Front Door 为后端 VM 部署提供流量的负载均衡.因为是演示实例,也没有实际的后端实例代码,只有一个 "Index.html" 的静 ...

  7. 一款轻阅读应用ReadIT,记录我的RN躺坑之旅

    背景 一款轻阅读应用ReadIT,支持功能:优质文章推送.评论点赞.计划制定.计划闹钟.天气预报.收藏文章.深浅两套主题.多语言切换.极光推送等功能.后续还会继续集成功能.前后端均自主研发,借鉴市面较 ...

  8. Alluxio+HDFS+MapReduce集成及测试

    目录 1.在 HDFS 上配置 Alluxio 1.1.节点角色 1.2.软件版本 1.3.准备工作 1.3.1.设置 SSH 免密登录 1.3.2.安装 JDK 1.3.3.安装 Hadoop 1. ...

  9. K8S 本地 配置 Local PV 实践

    上面我们创建了后端是 hostPath 类型的 PV 资源对象,我们也提到了,使用 hostPath 有一个局限性就是,我们的 Pod 不能随便漂移,需要固定到一个节点上,因为一旦漂移到其他节点上去了 ...

  10. Mokito 单元测试与 Spring-Boot 集成测试

    Mokito 单元测试与 Spring-Boot 集成测试 版本说明 Java:1.8 JUnit:5.x Mokito:3.x H2:1.4.200 spring-boot-starter-test ...