Young87

当前位置:首页 >个人收藏

APC注入(QueueUserAPC)--Ring3

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)
	{
		KtCloseHandle(ProcessHandle);
		ProcessHandle = NULL;
	}
	
	return 0;
}

“If you keep on believing,the dreams that you wish will come true.”

除特别声明,本站所有文章均为原创,如需转载请以超级链接形式注明出处:SmartCat's Blog

上一篇: 史上最强gcc4.8安装教程【gcc4.8.2】支持多版本

下一篇: ELK整合SpringBoot日志收集

精华推荐