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 << "\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 這兩個函數,確實精確了不少,緣由是因爲計時的線程是非搶佔式的,執行完之後纔會執行其餘線程