Loading

第三章 注入技术---APC注入

一、APC注入

什么是APC?
https://blog.csdn.net/qq_38474570/article/details/104326170
https://bbs.pediy.com/thread-217298.htm#msg_header_h1_2
每一个线程都有自己的APC队列,使用QueueUserAPC函数把一个APC函数压入APC队列中。当处于处于用户模式的APC压入线程APC队列后,该线程并不直接调用APC函数,除非该线程处于可通知状态,调用的顺序为先入先出(FIFO)。

二、API

QueueUserAPC函数
https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-queueuserapc

三、实现

一个进程包含多个线程,为了确保能够执行插入的APC,应向目标进程的所有线程都插入相同的APC,实现加载DLL的操作
实现APC注入的具体流程如下:
首先,通过OpenProcess函数打开目标进程,获取目标进程的句柄。
然后,通过调用WIN32 API函数CreateToolhelp32Snapshot、Thread32First以及Thread32Next遍历线程快照,获取目标进程的所有ID。
接着,调用VirtualAllocEx函数在目标进程中申请内存,并通过WriteProcessMemory函数向内存中写入DLL的注入路径。
最后,遍历获取的线程ID,并调用OpenThread函数以THREAD_ALL_ACCESS访问权限打开线程,获取线程句柄。并调用QueueUserAPC函数向线程插入APC函数,设置APC函数的地址为LoadLibraryA函数的地址,并设置APC函数参数为上述DLL路径地址。

四、代码实现

ApcInject.h

#ifndef _APC_INJECT_H_
#define _APC_INJECT_H_
#include <Windows.h>
#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);
#endif

APC_Test.cpp

// APC_Test.cpp : 定义控制台应用程序的入口点。
//
#include<tchar.h>
#include<stdio.h>
#include "ApcInject.h"


int _tmain(int argc, _TCHAR* argv[])
{
	BOOL bRet = FALSE;

	// APC注入
#ifdef _WIN64
	bRet = ApcInjectDll("explorer.exe", "C:\\C C++\\winhack\\remotedll\\001\\001.dll");
#else
	bRet = ApcInjectDll("explorer.exe", "C:\\C C++\\winhack\\remotedll\\001\\001.dll");
#endif
	if (bRet)
	{
		printf("APC Inject OK.\n");
	}
	else
	{
		printf("APC Inject ERROR.\n");
	}

	system("pause");
	return 0;
}

ApcInject.cpp

#include <tchar.h>
#include <stdio.h>
#include "ApcInject.h"


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;
}

五、小结

APC注入原理是利用当线程被唤醒时APC中的注册函数会执行的机制,并以此去执行DLL加载代码,从而完成DLL注入。为了增加APC执行的可能性,应向目标进程中所有的线程都插入APC。
如果出现向指定进程的所有线程插入APC导致进程崩溃的问题,则可以采取倒序遍历线程ID的方式进行倒序插入来解决程序崩溃的问题。

posted @ 2021-09-09 22:05  Ctrl_C+Ctrl_V  阅读(698)  评论(0编辑  收藏  举报