APC注入
前言:
"APC"是"Asynchronous Procedure Call"(异步过程调用)的缩写,它是一种软中断机制,当一个线程从等待状态中苏醒时(线程调用SignalObjectAndWait 、SleepEx、WaitForSingleObjectEx、WaitForMultipleObjectsEx、MsgWaitForMultipleObjectsEx函数时会进入可唤醒状态),它会检测有没有APC交付给自己。如果有,就会执行这些APC过程。APC有两种形式,由系统产生的APC称为内核模式APC,由应用程序产生的APC称为用户模式APC。APC调用的顺序为先入先出(FIFO)。我们可以使用 QueueUserAPC 函数把一个APC函数压入APC队列中
函数介绍:
DWORD WINAPI QueueUserAPC( _In_ PAPCFUNC pfnAPC, //APC函数的地址 _In_ HANDLE hThread, //线程句柄 _In_ ULONG_PTR dwData //APC函数的参数 ); //APC函数原型 VOID NTAPI PAPCFUNC( _In_ ULONG_PTR Parameter );
实现原理:
将 QueueUserAPC 函数的第一个参数(函数地址)设置的是LoadLibraryA函数地址;第三个参数(传递参数)设置的是DLL路径,第二个参数要注入的进程的线程句柄,那么执行APC时便会调用LoadLibraryA函数加载指定路径的DLL,完成DLL注入操作。
注意:一个进程可能包含多个线程,为了确保能够执行插入的APC,应向目标进程的所有线程都插入相同的APC,实现加载DLL的操作
实现流程:
(1).通过OpenProcess打开目标进程,获取目标进程的句柄
(2).使用VirtualAllocEx在目标进程中申请空间
(3).使用WriteProcessMemory函数在刚申请的空间中写入要注入的DLL路径
(4).获取LoadLibraryA函数地址
(5).通过CreateToolhelp32Snapshot、Thread32First以及Thread32Next遍历线程快照,获取目标进程的所有线程ID
(6).遍历获取的线程ID,通过OpenThread函数以THREAD_ALL_ACCESS访问权限打开线程,获取线程句柄
(7).使用QueueUserAPC向所有线程插入APC函数,参数1是(4)步获取的地址,参数2是(6)步获取的句柄,参数3是(2)步申请空间的首地址
实现代码:
//APC注入 BOOL CInjectDlg::ApcInjectDll(char* pszProcessName, char* pszDllFileName) { DWORD* pThreadId = NULL; DWORD dwThreadIdLength = 0; //根据进程名获取PID DWORD dwProcessId = GetProcessIdByProcessName(pszProcessName); if (dwProcessId <= 0) { return FALSE; } //根据PID获取所有相应的线程ID DWORD bRet = GetAllThreadIdByProcessId(dwProcessId, &pThreadId, &dwThreadIdLength); if (FALSE == bRet) { return FALSE; } //打开目标进程 HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId); if (NULL == hProcess) { MessageBox(L"打开目标进程失败!"); return FALSE; } // 在目标进程中申请空间 LPVOID lpPathAddr = VirtualAllocEx( hProcess, // 目标进程句柄 0, // 指定申请地址 strlen(pszDllFileName) + 1, // 申请空间大小 MEM_RESERVE | MEM_COMMIT, // 内存的状态 PAGE_READWRITE); // 内存属性 if (NULL == lpPathAddr) { MessageBox(L"在目标进程中申请空间失败!"); CloseHandle(hProcess); return FALSE; } // 3.在目标进程中写入Dll路径 if (FALSE == WriteProcessMemory( hProcess, // 目标进程句柄 lpPathAddr, // 目标进程地址 pszDllFileName, // 写入的缓冲区 strlen(pszDllFileName) + 1, // 缓冲区大小 NULL)) // 实际写入大小 { MessageBox(L"目标进程中写入Dll路径失败!"); CloseHandle(hProcess); return FALSE; } //5.获取LoadLibraryA的函数地址 //FARPROC可以自适应32位与64位 FARPROC pFuncProcAddr = GetProcAddress(GetModuleHandle((LPCWSTR)L"kernel32.dll"), "LoadLibraryA"); if (NULL == pFuncProcAddr) { MessageBox(L"获取LoadLibrary函数地址失败!"); CloseHandle(hProcess); return FALSE; } // 遍历线程, 插入APC for (int i = 0; i < dwThreadIdLength; i++) { // 打开线程 HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, pThreadId[i]); if (hThread) { // 插入APC QueueUserAPC((PAPCFUNC)pFuncProcAddr, hThread, (ULONG_PTR)lpPathAddr); // 关闭线程句柄 CloseHandle(hThread); hThread = NULL; } } return TRUE; } //根据进程名获取PID DWORD CInjectDlg::GetProcessIdByProcessName(char* pszProcessName) { //1.创建进程快照 HANDLE hSnap = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, //遍历进程快照1 0); //进程PID if (NULL == hSnap) { MessageBox(L"创建进程快照失败!"); return 0; } //2.获取第一条进程快照信息 PROCESSENTRY32 stcPe = { sizeof(stcPe) }; if (Process32First(hSnap, &stcPe)) { //3.循环遍历进程Next do { //获取快照信息 USES_CONVERSION; CString ProcessName = A2T(pszProcessName); if (!lstrcmp(stcPe.szExeFile, ProcessName)) { //4.关闭句柄 CloseHandle(hSnap); return stcPe.th32ProcessID; } } while (Process32Next(hSnap, &stcPe)); } //4.关闭句柄 CloseHandle(hSnap); return 0; } //根据PID获取所有相应的线程ID BOOL CInjectDlg::GetAllThreadIdByProcessId(DWORD dwProcessId, DWORD** ppThreadId, DWORD* pdwThreadIdLength) { DWORD* pThreadId = NULL; //统计线程个数 DWORD dwThreadIdLength = 0; //默认情况下,一个线程的栈要预留1M的内存空间, //而一个进程中可用的内存空间只有2G,所以理论 //上一个进程中最多可以开2048个线程 DWORD dwBuffLength = 2048; //申请内存 pThreadId = new DWORD[dwBuffLength]; if (pThreadId == NULL) { MessageBox(L"申请内存失败!"); return FALSE; } //将申请的控件初始化为0 RtlZeroMemory(pThreadId, (dwBuffLength * sizeof(DWORD))); //1.创建线程快照 HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); if (NULL == hSnap) { MessageBox(L"创建线程快照失败!"); delete[] pThreadId; pThreadId = NULL; return FALSE; } //2.第一次遍历线程 THREADENTRY32 th32 = { sizeof(th32) }; if (Thread32First(hSnap, &th32)) { //3.循环遍历线程 do { //判断该线程是否属于这个进程 if (th32.th32OwnerProcessID == dwProcessId) { pThreadId[dwThreadIdLength] = th32.th32ThreadID; dwThreadIdLength++; } } while (Thread32Next(hSnap, &th32)); CloseHandle(hSnap); *ppThreadId = pThreadId; *pdwThreadIdLength = dwThreadIdLength; return TRUE; } else { MessageBox(L"创建线程快照失败!"); delete[] pThreadId; pThreadId = NULL; CloseHandle(hSnap); return FALSE; } }