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. 微信小程序自定义Tabber,附详细源码

    目录 1,前言 2,说明 3,核心代码 1,前言 分享一个完整的微信小程序自定义Tabber,tabber按钮可以设置为跳转页面,也可以设置为功能按钮.懒得看文字的可以直接去底部,博主分享了小程序代码 ...

  2. 解决.NET Core Ajax请求后台传送参数过大请求失败问题

    解决.NET Core Ajax请求后台传送参数过大请求失败问题 今天在项目上遇到一个坑, 在.Net Core中通过ajax向mvc的controller传递对象时,控制器(controller)的 ...

  3. 来,Consul 服务发现入个门(一看就会的那种)

    前言 在微服务架构中,对于一个系统,会划分出多个微服务,而且都是独立开发.独立部署,最后聚合在一起形成一个系统提供服务.当服务数量增多时,这些小服务怎么管理?调用方又怎么能确定服务的IP和端口?服务挂 ...

  4. 【odoo14】第八章、服务侧开发-进阶

    本章代码位于作为GITHUB库 https://github.com/PacktPublishing/Odoo-14-Development-Cookbook-Fourth-Edition 在第五章( ...

  5. 「视频小课堂」Logstash如何成为镇得住场面的数据管道(文字版)

    视频地址 B站视频地址:Logstash如何成为镇得住场面的数据管道 公众号视频地址:Logstash如何成为镇得住场面的数据管道 知乎视频地址:Logstash如何成为镇得住场面的数据管道 内容 首 ...

  6. 滑动窗口解决最小子串问题 leetcode3. Longest Substring Without Repeating Characters

    问题描述: Given a string, find the length of the longest substring without repeating characters. Example ...

  7. 让你弄懂js中的闭包

    目录 闭包 闭包如何产生 闭包是什么 常见的闭包 闭包的作用 闭包的生命周期 闭包的应用 闭包的缺点 内存泄露 内存溢出 闭包面试题 闭包 之前在我执行上下文执行上下文栈这篇文章中,出现了这样一个题目 ...

  8. vue 给一个值重置初始值

    查了下资料很多都是如下: 1. this.$options.data() 这个可以获取原始的data值,this.$data 获取当前状态下的data,拷贝重新赋值一下就行了. 1 Object.as ...

  9. 攻防世界 reverse Replace

    Replace 湖湘杯2018 查壳upx,手动脱壳,修复IAT,去掉重定向便可以运行. ida查看,流程清晰.关键函数check_E51090. int __cdecl main(int argc, ...

  10. 14、MyBatis教程之全部(包括所有章节)

    MyBatis 3.5.5 教程 1.环境准备 jdk 8 + MySQL 5.7.19 maven-3.6.1 IDEA 学习前需要掌握: JDBC MySQL Java 基础 Maven Juni ...