APC(Asynchronous Procedure Call)异步过程调用是一种`Windows`操作系统的核心机制,它允许在进程上下文中执行用户定义的函数,而无需创建线程或等待OS执行完成。该机制适用于一些频繁的、短暂的或非常细微的操作,例如改变线程优先级或通知线程处理任务。在`APC机制`中,当某些事件发生时(例如文件IO,网络IO或定时器触发),这些事件将被操作系统添加到一个`APC队列`中,该队列绑定到执行线程。在下一次发生`ALERTABLE`的事件时(例如调用SleepEx或SignalObjectAndWait时),OS将弹出`APC函数`并在执行线程上下文中调用该函数,并在执行完毕后恢复线程执行。
APC(Asynchronous Procedure Call)异步过程调用是一种
Windows
操作系统的核心机制,它允许在进程上下文中执行用户定义的函数,而无需创建线程或等待OS执行完成。该机制适用于一些频繁的、短暂的或非常细微的操作,例如改变线程优先级或通知线程处理任务。在
APC机制
中,当某些事件发生时(例如文件IO,网络IO或定时器触发),这些事件将被操作系统添加到一个
APC队列
中,该队列绑定到执行线程。在下一次发生
ALERTABLE
的事件时(例如调用SleepEx或SignalObjectAndWait时),OS将弹出
APC函数
并在执行线程上下文中调用该函数,并在执行完毕后恢复线程执行。
APC机制与DLL注入的关系在于,可以使用APC机制将某些代码注入到另一个进程中,并由该进程执行。读者可以使用
NtQueueApcThread
或
QueueUserAPC
等函数将用户定义的函数添加到目标进程中的
APC队列
中,目标线程将在调用
SleepEx
或
SignalObjectAndWait
等函数时执行注入函数。但读者需要注意,注入函数必须是一个简短的、没有长时间阻塞的代码块,通常会加载DLL或者在目标线程中打开其他进程。
QueueUserAPC 函数允许将一个用户定义的函数添加到指定线程对应的APC队列中。该函数中的APC指异步过程调用,是指通过消息的方式将一段代码投递给线程去执行的一种机制,常用于处理异步操作,该函数的函数原型如下:
DWORD WINAPI QueueUserAPC( PAPCFUNC pfnAPC, HANDLE hThread, ULONG_PTR dwData );
|
参数说明如下:
pfnAPC: 一个指向线程函数的函数指针,在本函数被执行时调用。
hThread: 目标线程的句柄。
dwData: 传递给线程函数的参数。
当调用
QueueUserAPC
函数时,该函数将在目标线程的
APC队列
中添加一个
APC
入口,APC的入口点为
pfnAPC
。当目标线程处于
alertable
状态时,即调用了如
SleepEx
等同于处理
APCs
的等待函数时,系统会将
APC
从队列中弹出,并调用pfnAPC。在DLL注入中,可以使用
QueueUserAPC
函数向目标进程内的线程的APC队列中插入一个我们定义的函数的指针,使该函数在目标线程执行时运行,从而实现DLL注入的目的。
APC一部注入原理可以总结为如下几个步骤,每个线程在可被唤醒时在其APC链中的函数将有机会执行被执行,每一个线程都具有一个APC链,那么只要在APC链中添加一个APC,就可以完成我们所需要的DLL注入的功能;
1.WriteProcessMemory 将需要加载的DLL的完整路径写入目标进程空间
2.获得LoadLibraryA函数的地址,当然也可以是LoadLibraryW函数的地址
3.枚举目标进程中的所有线程,为每个线程添加一个APC函数,这样增加了注入成功的机会.
利用当线程被唤醒时APC中的注册函数会被执行的机制,并以此去执行我们的DLL加载代码,进而完成DLL注入的目的,通过APC注入的流程步骤大致如下;
1.当进程里某个线程执行到
SleepEx()
或者
WaitForSingleObjectEx()
时,系统就会产生一个软中断
2.当线程再次被唤醒时,此线程会首先执行APC队列中的被注册的函数
3.利用
QueueUserAPC()
可以在软中断时向线程的APC队列插入一个函数指针,此处插入
Loadlibrary()
4.当插入函数被执行时则会加载Loadlibrary并将其指向的DLL模块插入到进程内
但读者需要注意一点,不论如何目标程序必须有执行
SleepEx()
或者
WaitForSingleObjectEx()
否则DLL不会加载,读者可自行做实验测试是否可以注入成功;
#include <windows.h> #include <iostream> #include <TlHelp32.h> #include <tchar.h>
DWORD FindProcessID(LPCTSTR szProcessName) { DWORD dwPID = 0xFFFFFFFF; HANDLE hSnapShot = INVALID_HANDLE_VALUE; PROCESSENTRY32 pe; pe.dwSize = sizeof(PROCESSENTRY32); hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, NULL); Process32First(hSnapShot, &pe); do { if (!_tcsicmp(szProcessName, (LPCTSTR)pe.szExeFile)) { dwPID = pe.th32ProcessID; break; } } while (Process32Next(hSnapShot, &pe)); CloseHandle(hSnapShot); return dwPID; }
BOOL ApcInjectDll(DWORD dwPid, char* szDllName) { int nDllLen = lstrlen(szDllName) + sizeof(char);
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid); if (hProcess == NULL) { return FALSE; }
PVOID pDllAddr = VirtualAllocEx(hProcess, NULL, nDllLen, MEM_COMMIT, PAGE_READWRITE); if (pDllAddr == NULL) { CloseHandle(hProcess); return FALSE; }
#ifdef _WIN64 size_t dwWriteNum = 0; WriteProcessMemory(hProcess, pDllAddr, szDllName, nDllLen, &dwWriteNum); #else DWORD dwWriteNum = 0; WriteProcessMemory(hProcess, pDllAddr, szDllName, nDllLen, &dwWriteNum); #endif CloseHandle(hProcess);
THREADENTRY32 te = { 0 }; te.dwSize = sizeof(THREADENTRY32);
HANDLE handleSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); if (INVALID_HANDLE_VALUE == handleSnap) { CloseHandle(hProcess); return FALSE; }
FARPROC pFunAddr = GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA"); DWORD dwRet = 0;
if (Thread32First(handleSnap, &te)) { do { if (te.th32OwnerProcessID == dwPid) { HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te.th32ThreadID); if (hThread) { dwRet = QueueUserAPC((PAPCFUNC)pFunAddr, hThread, (ULONG_PTR)pDllAddr);
CloseHandle(hThread); } } } while (Thread32Next(handleSnap, &te)); }
CloseHandle(handleSnap); return TRUE; }
int main(int argc, char *argv[]) { DWORD pid = FindProcessID("lyshark.exe"); std::cout << "进程PID: " << pid << std::endl;
bool flag = ApcInjectDll(pid, (char *)"d://hook.dll"); std::cout << "注入状态: " << flag << std::endl;
return 0; }
|