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.”

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章