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[])
{
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);
ResumeThread(MyThread1); ResumeThread(MyThread2); ResumeThread(MyThread3);
HANDLE DupProcessHandle = NULL; DWORD ProcessId;
DuplicateHandle(GetCurrentProcess(), GetCurrentProcess(), GetCurrentProcess(), &DupProcessHandle, 0, FALSE, DUPLICATE_SAME_ACCESS);
ProcessId = GetProcessId(DupProcessHandle);
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;
HANDLE ProcessHandle = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, ProcessId);
if (ProcessHandle == INVALID_HANDLE_VALUE)
{
LastError = GetLastError();
return LastError;
}
INT SkipMainThread = 0; HANDLE DupProcessHandle = NULL;
DuplicateHandle(GetCurrentProcess(), GetCurrentProcess(), GetCurrentProcess(), &DupProcessHandle, 0, FALSE, DUPLICATE_SAME_ACCESS);
DWORD IsThisProcess = GetProcessId(DupProcessHandle);
if (ProcessId != IsThisProcess) SkipMainThread = 1;
THREADENTRY32 ThreadInfo = { sizeof(ThreadInfo) };
BOOL fOk = Thread32First(ProcessHandle, &ThreadInfo);
while (fOk)
{
HANDLE ThreadHandle = OpenThread(THREAD_SUSPEND_RESUME, FALSE, ThreadInfo.th32ThreadID);
if (ThreadInfo.th32OwnerProcessID == ProcessId && Suspend == TRUE)
{
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;
Sleep(2000);
return true;
}
unsigned __stdcall ThreadFun2(void *pvParam)
{
cout << "ThreadFun2" << endl;
Sleep(2000);
return true;
}
unsigned __stdcall ThreadFun3(void *pvParam)
{
cout << "ThreadFun3" << endl;
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 的第二个参数即可
ControlProThreads(ProcessId, TRUE);
Sleep(2000);
cout << "
睡眠 2000 毫秒
" << 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 这两个函数,确实精确了很多,原因是由于计时的线程是非抢占式的,执行完以后才会执行其他线程