当前位置 博文首页 > KOOKNUT的博客:APC注入(QueueUserAPC)--Ring3

    KOOKNUT的博客:APC注入(QueueUserAPC)--Ring3

    作者:[db:作者] 时间:2021-07-02 18:36

    APC英文全称(Asynchronous Procedure Call),我们一般译为异步过程调用。它是一种Windows的软中断机制,一般分为两种:

    • 内核APC:由系统产生的APC。
    • 用户APC:由应用层程序产生的APC。

    当一个线程从等待状态(线程调用SleepEx、SignalObjectAndWait、WaitForSingleObjectEx、WaitForMultipleObjectsEx等函数是会进入可唤醒状态)中苏醒时,线程会检查有没有APC需要去执行,如果有APC,则去执行这些异步过程调用函数。而我们在Ring3层,可以利用QueueUserAPC函数APC过程添加到目标线程的APC队列中,当线程恢复执行之前,APC会被执行,完成我们的注入。
    当我们添加APC后,线程不会立即就调用APC函数,只有当线程被唤醒时,才会调用,所以为了增加代码被唤醒得几率,在程序中向所有线程插入APC。
    写这篇文章之际,我也翻看了之前的笔记,回顾了自己做的过程中遇到的问题,有一个可有意思的现象,就是当我向线程添加APC队列时,感觉对操作线程的顺序有要求。
    例一:Win7 x86 x64 Taskmgr.exe Explorer.exe在按照以下代码插入各个线程APC队列时,会导致目标进程奔溃。当然测试自己写的Test.exe目标程序,还是可以通过的。

    if (Thread32First(SnapshotHandle, &ThreadEntry32))
    	{
    		do 
    		{
    			if (ThreadEntry32.th32OwnerProcessID == ProcessId)
    			{
    				HANDLE ThreadHandle = OpenThread(THREAD_SET_CONTEXT, FALSE, ThreadEntry32.th32ThreadID);
    				if (ThreadHandle)
    				{
    					QueueUserAPC((PAPCFUNC)__LoadLibrary, ThreadHandle,
    						(ULONG_PTR)VirtualAddress);
    					CloseHandle(ThreadHandle);
    				}
    			}
    		} while (Thread32Next(SnapshotHandle, &ThreadEntry32));
    	}
    

    例二:Win7 x86 x64 Taskmgr.exe Explorer.exe在按照以下代码插入各个线程APC队列时,注入成功。我试着将线程Id先保存起来,然后倒着插入APC队列。

        DWORD v1[15] = { 0 };//为了测试,直接写死的15
    	int j = 0;
    	if (Thread32First(SnapshotHandle, &ThreadEntry32))
    	{
    		do 
    		{
    			if (ThreadEntry32.th32OwnerProcessID == ProcessId)
    			{
    				v1[j++] = ThreadEntry32.th32ThreadID;
    			}
    		} while (Thread32Next(SnapshotHandle, &ThreadEntry32));
    	}
    	for (j = 15; j > 0; j--)
    	{
    		HANDLE ThreadHandle = OpenThread(THREAD_SET_CONTEXT, FALSE,v1[j]);
    		if (ThreadHandle)
    		{
    			QueueUserAPC((PAPCFUNC)__LoadLibrary, ThreadHandle,
    				(ULONG_PTR)VirtualAddress);
    			CloseHandle(ThreadHandle);
    		}
    	}
    

    我当初很是奇怪,不知道为什么会发生这种状况,我所看过的书籍也是按照第一种方式注入的,但是我在自己的虚拟机测试并不能成功。最后调试、尝试了很多次之后,我还是妥协了,最后按照这种倒着插APC的方式,进行注入。
    最终,我使用vector动态数组来存储线程Id。
    最终代码:

    #include"QueueUserApc.h"
    #include"Helper.h"
    
    
    
    #ifdef UNICODE
    LPFN_LOADLIBRARYW __LoadLibrary = NULL;
    #else
    LPFN_LOADLIBRARYA __LoadLibrary = NULL;
    #endif
    
    
    
    int _tmain()
    {
    	//控制台识别中文
    	setlocale(LC_ALL, "Chinese-simplified");
    
    	TCHAR ProcessImageName[MAX_PATH] = { 0 };//保存进程名字
    
    	TCHAR CurrentFullPath[MAX_PATH] = { 0 }; //当前进程的完整路径
    
    	TCHAR TargetProcessFullPath[MAX_PATH] = { 0 };//目标进程的完整路径
    	ULONG_PTR TargetProcessPathLength = MAX_PATH;
    
    	ULONG ProcessId = 0;//目标进程Id
    
    
    	HANDLE ProcessHandle = INVALID_HANDLE_VALUE;//进程句柄
    	LPVOID VirtualAddress = NULL;
    
    	SIZE_T ReturnLength = 0;
    
    	BOOL  IsOk = FALSE;
    
    	//注入的启动程序和目标程序的位数
    	BOOL  SourceIsWow64 = FALSE;
    	BOOL  TargetIsWow64 = FALSE;
    
    
    	_tprintf(_T("输入一个进程ImageName\r\n"));
    
    
    	TCHAR RcceiveChar = _gettchar();//接受字符串
    	int i = 0;//用来偏移ProcessName字符数组
    	while (RcceiveChar != '\n')
    	{
    		ProcessImageName[i++] = RcceiveChar;
    		RcceiveChar = _gettchar();
    		//ProcessImageName = 0x000000db28fceed0 "Taskmgr.exe"
    	}
    
    	GetCurrentDirectory(MAX_PATH, CurrentFullPath);//保存当前进程的完整路径
    
    	IsWow64Process(GetCurrentProcess(), &SourceIsWow64);//得到当前进程位数
    	//SourceIsWow64 = 0x00000000
    	ProcessId = KtGetProcessIdentify(ProcessImageName);//通过进程名得到进程Id
    	//ProcessId = 0x00003aa0
    	if (ProcessId == 0)
    	{
    		return 0;
    	}
    	IsOk = KtGetProcessFullPath(TargetProcessFullPath,
    		&TargetProcessPathLength, ProcessId, FALSE);
    
    	if (IsOk == FALSE)
    	{
    		return 0;
    	}
    	//判断目标进程位数
    	KtIsWow64Process(TargetProcessFullPath, &TargetIsWow64);
    	//TargetIsWow64 = 0x00000000
    	if (SourceIsWow64 == TRUE && TargetIsWow64 == TRUE)
    	{
    		_tcscat_s(CurrentFullPath, _T("\\Dll.dll"));
    	}
    	else if (SourceIsWow64 == FALSE && TargetIsWow64 == FALSE)
    	{
    		_tcscat_s(CurrentFullPath, _T("\\Dll.dll"));
    	}
    	//_tcscat_s(CurrentFullPath, _T("\\Dll.dll"));//Win 7 32位测试用
    
    
    
    	ProcessHandle = KtOpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessId);
    	ULONG BufferLength = 0;
    	//在目标进程空间中申请内存
    	BufferLength = (_tcslen(CurrentFullPath) + 1) * sizeof(TCHAR);
    
    	//目标进程空间中申请内存
    	VirtualAddress = VirtualAllocEx(ProcessHandle, NULL, BufferLength, MEM_COMMIT, PAGE_READWRITE);
    
    	if (VirtualAddress == NULL)
    	{
    		KtCloseHandle(ProcessHandle);
    		return 0;
    	}
    	//目标进程空间中写入数据
    	if (KtProcessMemoryWriteSafe(ProcessHandle, VirtualAddress, CurrentFullPath, BufferLength, &ReturnLength) == FALSE)
    	{
    		VirtualFreeEx(ProcessHandle, VirtualAddress, BufferLength, MEM_RELEASE);
    		KtCloseHandle(ProcessHandle);
    		return 0;
    	}
    
    
    	//获得目标进程下的所有线程
    	vector<HANDLE> ThreadId{};
    	if (KtGetThreadIdentify((HANDLE)ProcessId, ThreadId) == FALSE)
    	{
    		VirtualFreeEx(ProcessHandle, VirtualAddress, BufferLength, MEM_RELEASE);
    		KtCloseHandle(ProcessHandle);
    		return 0;
    	}
    
    	HMODULE  Kernel32ModuleBase = NULL;
    
    	
    	Kernel32ModuleBase = GetModuleHandle(_T("KERNEL32.DLL"));
    	//Kernel32ModuleBase = kernel32.dll!0x00007ffe83fa0000 (加载符号以获取其他信息) {unused=0x00905a4d }
    	if (Kernel32ModuleBase == NULL)
    	{
    		VirtualFreeEx(ProcessHandle, VirtualAddress, BufferLength, MEM_RELEASE);
    		KtCloseHandle(ProcessHandle);
    		return 0;
    	}
    
    #ifdef UNICODE
    	__LoadLibrary = (LPFN_LOADLIBRARYW)GetProcAddress(Kernel32ModuleBase, "LoadLibraryW");
    #else
    	__LoadLibrary = (LPFN_LOADLIBRARYA)GetProcAddress(Kernel32ModuleBase, "LoadLibraryA");
    #endif
    
    	if (__LoadLibrary == NULL) {
    
    		KtCloseHandle(ProcessHandle);
    		return 0;
    	}
    
    	for (i = ThreadId.size() - 1; i >= 0; i--)
    	{
    		HANDLE ThreadHandle = KtOpenThread(THREAD_SET_CONTEXT, FALSE, ThreadId[i]);
    		if (ThreadHandle)
    		{
    			/*
    			很奇怪,得按照逆序来插入APC队列
    			如果按照toolhelper32直接枚举出来的ThreadId,依此顺序插入,
    			会导致目标进程奔溃(测试Win7 x86 x64 Taskmgr.exe和Explorer.exe)
    			按照逆序插入没什么问题
    			*/
    
    			//向目标进程中的各个线程的APC队列插入执行体
    			QueueUserAPC((PAPCFUNC)__LoadLibrary,
    				ThreadHandle,
    				(ULONG_PTR)VirtualAddress);
    			CloseHandle(ThreadHandle);
    		}
    	}
    
    	ThreadId.~vector();//执行析构,释放内存
    	if (VirtualAddress != NULL)
    	{
    		VirtualFreeEx(ProcessHandle, VirtualAddress, BufferLength, MEM_RELEASE);
    		VirtualAddress = NULL;
    	}
    	if (ProcessHandle)
    	{