Suspend: 挂起指定的线程
备注:不要永远挂起线程, 因为在Win32中,进程堆是线程安全的对象,并且由于在不访问堆的情况下很难在Win32中完成很多工作,因此在Win32中挂起线程极有可能使进程死锁。
那么,为什么首先还要有SuspendThread函数呢?
调试器在调试过程中使用它冻结进程中的所有线程。调试器还可以使用它冻结进程中除一个线程之外的所有线程,因此您一次只能专注于一个线程。由于调试器是一个独立的进程,因此不会在调试器中创建死锁。
Link: Why you should never suspend a thread
并且我们不应该用来SuspendThread
同步两个线程,因为没有实际的同步保证。发生的是SuspendThread
信号通知调度程序挂起线程并立即返回。如果调度程序正忙于执行其他操作,则它可能无法立即处理挂起请求,因此被挂起的线程将在借来的时间上运行,直到调度器处理挂起请求为止,此时它实际上已被挂起。
如果要确保线程确实被挂起,则需要执行同步操作,该操作取决于线程被挂起的事实。因为这是操作的先决条件,并且由于操作是同步的,所以这会强制处理该暂停请求,您知道在返回时肯定已经发生了暂停。
传统的做法是调用GetThreadContext
,因为这需要内核从被挂起的线程的上下文中读取,这首先需要将上下文保存在该位置,而该前提是必须先将线程挂起。
一些代码:
#pragma pack(push, 1) struct myByteCode { BYTE push; DWORD pushValue; BYTE call; INT32 callOffset; BYTE ret; }; #pragma pack(pop) // allocate executable page for shellcode struct myByteCode* mem = (struct myByteCode*) VirtualAlloc(0, sizeof(struct myByteCode), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if (!mem) { // error handling... } // copy shellcode to page mem->push = 0x68; mem->pushValue = (DWORD) ThreadCallbackParameter; mem->call = 0xE8; mem->callOffset = ((INT_PTR)ThreadCallback) - ((INT_PTR)(&mem->ret)); mem->ret = 0xC3; DWORD ignored; if (!VirtualProtect(mem, sizeof(struct myByteCode), PAGE_EXECUTE, &ignored)) { // error handling... } FlushInstructionCache(GetCurrentProcess(), mem, sizeof(struct myByteCode)); // redirect thread to shellcode ...
需要FlushInstructionCache()
在执行分配的内存之前先对其进行调用。而且,您还应该READ/WRITE(用
VirtualProtect)
从内存中删除这些标志,以避免恶意代码劫持您的Shell代码来执行其他操作。