UserMode下的APC插入

APC注入技术并不新鲜

原理是利用对方线程每次被唤醒时都会遍历APC队列指向用户对应的回调函数原理执行我们的代码

目标程序代码

#include <iostream>
#include <Windows.h>
 
int main()
{
	while (1)
		SleepEx(3000, 1);
 
	system("pause");
}

注入代码

#include <Windows.h>
#include <Tlhelp32.h>
#include <iostream>
 
// 用于保存线程 TID 的数组
ULONG ThreadIds[100] = { 0 };
 
// 提权
void EnableDebugPriv()
{
	HANDLE hToken;
	LUID sedebugnameValue;
	TOKEN_PRIVILEGES tkp;
 
	if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
	{
		std::cout << "提权失败。" << std::endl;
		return;
	}
 
	if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &sedebugnameValue))
	{
		CloseHandle(hToken);
		std::cout << "提权失败。" << std::endl;
		return;
	}
 
	tkp.PrivilegeCount = 1;
	tkp.Privileges[0].Luid = sedebugnameValue;
	tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
 
	if (!AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof tkp, NULL, NULL))
	{
		std::cout << "提权失败。" << std::endl;
		CloseHandle(hToken);
	}
	else std::cout << "提权成功!" << std::endl;
}
 
// 根据进程名称获取 PID
// ProcessName 进程名称
ULONG GetProcessIdByProcessName(char * ProcessName)
{
	HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	if (INVALID_HANDLE_VALUE == hSnapshot)
	{
		return 0;
	}
	PROCESSENTRY32 pi;
	pi.dwSize = sizeof(PROCESSENTRY32); //第一次使用必须初始化成员
	BOOL bRet = Process32First(hSnapshot, &pi);
	while (bRet)
	{
		char Name[MAX_PATH] = { 0 };
		sprintf(Name, "%ls", pi.szExeFile);
		if (strcmp(Name, ProcessName) == 0)
		{
			return pi.th32ProcessID; // 返回PID
		}
		
		bRet = Process32Next(hSnapshot, &pi);
	}
 
	return 0;
}
 
// 根据 PID 获取获取所有相应的线程 TID
// ProcessId    进程 PID
// pThreadId   数组用于保存线程TID
// ThreadIdLen 用于返回数组实际长度
BOOL GetAllThreadIdByProcessId(ULONG ProcessId, ULONG * pThreadId, ULONG * ThreadIdLen)
{
	HANDLE hThreadSnap = INVALID_HANDLE_VALUE;
	THREADENTRY32 te32 = { sizeof(THREADENTRY32) };
	ULONG Number = 0;
 
	// 把所有线程拍一个快照
	hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
	if (hThreadSnap == INVALID_HANDLE_VALUE)
		return(FALSE);
 
	// 在使用 Thread32First 前初始化 THREADENTRY32 的结构大小.
	te32.dwSize = sizeof(THREADENTRY32);
 
	// 现在获取系统线程列表, 并显示与指定进程相关的每个线程的信息	
	do {
		// 比对是否为该进程线程
		if (te32.th32OwnerProcessID == ProcessId)
		{
			// 是的话保存到线程数组中
			pThreadId[Number] = te32.th32ThreadID;
			Number++;
		}
	} while (Thread32Next(hThreadSnap, &te32));
 
	if (!Number)
		return FALSE;
	// 修改线程数量
	*ThreadIdLen = Number;
	return TRUE;
}
 
// APC注入
// ProcessName 要注入的进程名称
// DllName     要注入的DLL名称
BOOL ApcInjectDll(char * ProcessName, char * DllName)
{
	ULONG ProcessId = 0;
	ULONG IsFlag = TRUE;
	HANDLE hProcess = NULL, hThread = NULL;
	ULONG DllNameLen = strlen(DllName) + 1;
	PVOID pBaseAddr = NULL;
	ULONG dwRet = 0;
	PVOID pLoadLibraryA = NULL;
	ULONG ThreadIdsLen = 0;
 
	do 
	{
		// 根据进程名称获取 PID
		ProcessId = GetProcessIdByProcessName(ProcessName);
		if (0 >= ProcessId)
		{
			printf("ProcessId Error: %d\n", ProcessId);
			IsFlag = FALSE;
			break;
		}
		// 根据 PID 获取获取所有相应的线程 TID
		IsFlag = GetAllThreadIdByProcessId(ProcessId, ThreadIds, &ThreadIdsLen);
		if (!IsFlag)
		{
			printf("GetAllThreadIdByProcessId Error: %x\n", GetLastError());
			IsFlag = FALSE;
			break;
		}
		// 打开注入线程
		hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessId);
		if (hProcess == NULL)
		{
			
			printf("OpenProcess Error: %x\n", GetLastError());
			IsFlag = FALSE;
			break;
		}
		// 在注入的进程中申请空间
		pBaseAddr = 
			VirtualAllocEx(hProcess, NULL, DllNameLen, MEM_COMMIT | MEM_RESERVE, 
				PAGE_EXECUTE_READWRITE);
		if (pBaseAddr == NULL)
		{
			printf("VirtualAllocEx Error: %x\n", GetLastError());
			IsFlag = FALSE;
			break;
		}
		// 向申请的空间中写入 Dll 路径数据
		WriteProcessMemory(hProcess, pBaseAddr, DllName, DllNameLen, &dwRet);
		if (DllNameLen != dwRet)
		{
			printf("WriteProcessMemory Error: %x\n", GetLastError());
			IsFlag = FALSE;
			break;
		}
		// 获取 LoadLibraryA 地址
		pLoadLibraryA =
			GetProcAddress(GetModuleHandleA((char *)"kernel32.dll"), "LoadLibraryA");
		if (pLoadLibraryA == NULL)
		{
			printf("GetProcAddress Error: %x\n", GetLastError());
			IsFlag = FALSE;
			break;
		}
		// 遍历线程, 插入APC
		for (ULONG i = 0; i < ThreadIdsLen; i++)
		{
			// 打开线程
			hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, ThreadIds[i]);
			if (hThread != NULL)
			{
				// 插入 APC
				if (!QueueUserAPC((PAPCFUNC)pLoadLibraryA, hThread, (ULONG_PTR)pBaseAddr))
				{
					printf("QueueUserAPC Error: %x\n", GetLastError());
				}
				// 关闭线程句柄
				CloseHandle(hThread);
				hThread = NULL;
				printf("插入 APC 成功!\n");
			}
		}
 
	} while (FALSE);
 
	CloseHandle(hProcess);
	return IsFlag;
}
 
int main()
{
	// 提权
	EnableDebugPriv();
	// 注入我们想要执行的DLL
	ApcInjectDll((char *)"TestExe.exe", (char *)"./InjectDll.dll");
	system("pause");
	return 0;
}

原创文章,转载请注明: 转载自Windows内核安全驱动编程

本文链接地址: UserMode下的APC插入

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注