windows.h : 2.DLL注入技术

windows中的大部分应用程序都是基于消息机制的,根据不同的消息完成不同的功能。 钩子这个机制就是由windows操作系统提供的可以用来截获和监视系统中这些消息的。

钩子又可以分为局部钩子和全局钩子。其中局部钩子是针对某个线程的,全局钩子是作用于整个系统的基于消息的应用。全局钩子需要使用DLL文件,在DLL中实现相应的钩子函数。

一. 全局钩子注入

首先介绍稍后用到的函数

SetWindowsHookEX 函数

//将程序定义的钩子函数安装到挂钩链中,安装钩子程序可以监视系统是否存在某些类型的事件,这些事件
//和特定线程或调用线程所在的桌面所有线程相关联

HHOOK WINAPI SetWindowsHookEX(
    _In_ int idHook, //要安装的钩子程序的类型
    _In_ HOOKPROC lpfn, //一个指向钩子程序的指针,如果dwThreadId=0或者指定不同进程创建线程,则
                        //lpfn必须指向DLL中的钩子过程,否则lpfn可以指向与当前进程关联的代码函数
    _In_ HINSANCE hMod, //包含由lpfn参数指向的函数的DLL句柄,如果是当前进程则hMod必须为NULL
    _In_ DWORD dwThreadId) //与钩子程序关联的线程标识符。如果为0,则与所有系统中的线程相关联

//函数成功返回钩子函数的句柄
//失败返回NULL

其中钩子类型(第一个参数idhook)分为以下几种:

钩子类型

全局钩子函数的代码需要在独立的DLL中,这样在一个进程执行时才可以加载这个DLL,并运行其中的钩子函数。这个加载DLL到特定进程的过程就是DLL注入

为了让DLL注入到所有进程中,设置WH_GETMESSAGE监视消息队列。windows系统基于消息驱动,所以所有进程都有自己的一个消息队列,都会加载WH_GETMESSAGE类型的全局钩子DLL

设置全局钩子的代码:

//设置全局钩子
BOOL SetGlobalHook() {
	g_hHook = ::SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)GetMsgProc, g_hDllModule, 0);
	if (NULL == g_hHook) {
		return FALSE;
	}
	return TRUE;
}

回调函数代码

//回调函数代码
LRESULT GetMsgProc(int code, WPARAM wParam, LPARAM lParam) {
	return ::CallNextHookEx(g_hHook, code, wParam, lParam);
}

其中回调函数的参数和返回值的数据类型时固定的。 callnexthookex表示把当前钩子传递给钩子链中的下一个钩子。

卸载钩子代码:

//卸载钩子
BOOL UnsetGlobalHook() {
	if (g_hHook) {
		UnhookWindowsHookEx(g_hHook);
	}
	return TRUE;
}

本程序中使用的方法是在DLL中创建一块共享内存由多个进程共用。具体的实现方法如下

//共享内存
#pragma data_seg("mydata")
HHOOK g_hHook = NULL;
#pragma data_seg()
#pragma comment(linker,"/SECTION:mydata,RWS")

使用data_seg()创建数据段,然后通过设置连接器的RWS参数设置为 可读可写可共享的共享数据段。

实现:

1.创建DLL工程项目

新建项目中选择windows桌面项目向导,选择动态链接库dll

定义dll入口函数如下

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

HMODULE g_hDllModule = NULL;


BOOL APIENTRY DllMain(HMODULE hModule,
	DWORD  ul_reason_for_call,
	LPVOID lpReserved
)
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
	{
		g_hDllModule = hModule;
		break;
	}
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
	case DLL_PROCESS_DETACH:
		break;
	}
	return TRUE;
}

点击生成之后在debug目录下生成dll文件

 可以在其他程序中对dll进行加载调用

// Test.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <Windows.h>


int _tmain(int argc, _TCHAR* argv[])
{
	typedef BOOL(*typedef_SetGlobalHook)();
	typedef BOOL(*typedef_UnsetGlobalHook)();
	HMODULE hDll = NULL;
	typedef_SetGlobalHook SetGlobalHook = NULL;
	typedef_UnsetGlobalHook UnsetGlobalHook = NULL;
	BOOL bRet = FALSE;

	do
	{
		hDll = ::LoadLibrary("GlobalHook_Test.dll");
		if (NULL == hDll)
		{
			printf("LoadLibrary Error[%d]\n", ::GetLastError());
			break;
		}
		SetGlobalHook = (typedef_SetGlobalHook)::GetProcAddress(hDll, "SetGlobalHook");
		if (NULL == SetGlobalHook)
		{
			printf("GetProcAddress Error[%d]\n", ::GetLastError());
			break;
		}
		bRet = SetGlobalHook();
		if (bRet)
		{
			printf("SetGlobalHook OK.\n");
		}
		else
		{
			printf("SetGlobalHook ERROR.\n");
		}

		system("pause");

		UnsetGlobalHook = (typedef_UnsetGlobalHook)::GetProcAddress(hDll, "UnsetGlobalHook");
		if (NULL == UnsetGlobalHook)
		{
			printf("GetProcAddress Error[%d]\n", ::GetLastError());
			break;
		}
		UnsetGlobalHook();
		printf("UnsetGlobalHook OK.\n");

	}while(FALSE);

	system("pause");
	return 0;
}

加载完毕发现再执行其他exe文件时,使用进程查看器发现加载模块里加载了之前生成的dll文件,说明dll注入成功。

继续执行卸载钩子后发现相应的dll没有被加载,说明钩子卸载成功:

二. 远线程注入

所谓远线程注入是指一个进程在另一个进程中创建线程的技术。

函数介绍:

OpenProcess函数

//打开现有的本地进程对象
HANDLE WINAPI OpenProcess(
    _In_ DWORD dwDesiredAccess, //访问进程对象
    _In_ BOOL bInheritHandle,    //是否继承该进程的句柄
    _In_ DWORD dwProcessId)        //要打开的本地进程的PID

VirtualAllocEx函数

//在指定进程的虚拟地址空间内保留,提交或更改内存的状态
LPVOID WINAPI VirtualAllocEx(
    _In_ HANDLE hProcess,  //过程句柄
    _In_opt_ LPVOID lpAddress,  //指定要分配页面所需的起始地址的指针
    _In_ SIZE_T dwSize,  //要分配的内存大小,以字节为单位
    _In_ DWORD flAllocationType,  //内存分配类型
    _In_ DWORD flProtect)  //要分配的页面区域的内存保护

//如果成功,返回分配页面的基址
//失败返回NULL

flAllocationType 可取下列值:

MEM_COMMIT:为特定的页面区域分配内存中或磁盘的页面文件中的物理存储

MEM_PHYSICAL :分配物理内存(仅用于地址窗口扩展内存

MEM_RESERVE:保留进程的虚拟地址空间,而不分配任何物理存储。保留页面可通过继续调用VirtualAlloc()而被占用

MEM_RESET :指明在内存中由参数lpAddress和dwSize指定的数据无效

MEM_TOP_DOWN:在尽可能高的地址上分配内存(Windows 98忽略此标志)

MEM_WRITE_WATCH:必须与MEM_RESERVE一起指定,使系统跟踪那些被写入分配区域的页面(仅针对Windows 98)

flProtect可取下列值:

PAGE_READONLY: 该区域为只读。如果应用程序试图访问区域中的页的时候,将会被拒绝访

PAGE_READWRITE 区域可被应用程序读写

PAGE_EXECUTE: 区域包含可被系统执行的代码。试图读写该区域的操作将被拒绝。

PAGE_EXECUTE_READ :区域包含可执行代码,应用程序可以读该区域。

PAGE_EXECUTE_READWRITE: 区域包含可执行代码,应用程序可以读写该区域。

PAGE_GUARD: 区域第一次被访问时进入一个STATUS_GUARD_PAGE异常,这个标志要和其他保护标志合并使用,表明区域被第一次访问的权限

PAGE_NOACCESS: 任何访问该区域的操作将被拒绝

PAGE_NOCACHE: RAM中的页映射到该区域时将不会被微处理器缓存(cached)

WriteProcessMemory 函数

//在指定进程中将数据写入内存区域,整个区域必须可访问
BOOL WINAPI WriteProcessMemory(
    _In_ HANDLE hProcess,  //要修改的进程内存的句柄
    _In_ LPVOID lpBaseAddress,  //指向指定进程中写入数据的基地址指针
    _In_ LPCVOID lpBuffer,  //指向缓冲区的指针,包含要写入指定进程地址空间的数据
    _In_ SIZE_T nSize,
    _In_ SIZE_T *lpNumberOfBytesWritten) //指向变量的指针
//函数成功返回值不为零
//失败返回值为零
    

CreateRemoteThread 函数

HANDLE WINAPI CreateRemoteThread(
    _In_ HANDLE hProcess,  //创建线程的进程句柄    
    _In_ LPSECURITY_ATTRIBUTES lpThreadAttributes, //指向安全属性的指针
    _In_ SIZE_T dwStackSize,  //堆栈的初始大小
    _In_ LPTHREAD_START_ROUTING lpStartAddress,  //指向远程进程中线程的起始地址
    _In_ LPVOID lpParameter,  //传递给线程函数的变量的指针
    _In_ DWORD dwCreationFlags,  //控制线程创建的标志,若为0,表示线程在创建后立即运行
    _Out_ LPDWORD lpThreadId)  //指向接收线程标识符的变量的指针

//返回新线程句柄或者 NULL

实现原理:进程需要加载的自身原本需要的动态dll 使用的函数时LoadLibrary(lpFilename); 其中参数是要加载的dll 路径

而createRemoteThread函数需要指定多线程函数地址和多线程参数地址。因为loadLibrary函数是目标进程一定会执行的,并且把参数作为要加载的dll

所以在机器指令中相当与存在一条指令,从Loadlibrary函数入口地址开始运行,如果把这个函数地址作为要创建的多线程的地址,并把dll路径作为函数的参数,就可以利用loadLibrary加载相应的dll了

但是 windows使用基址随机化,所以每次开机系统DLL加载基址不一样,所以LoadLibrary函数基址不一样,但是有些系统DLL 要求开机之后地址是固定的,所以自己程序的LoadLibrary函数地址与其他

进程中的地址是相同的。

代码实现 :

#include<Windows.h>
#include<iostream>
#include<tchar.h>
using namespace std;
//定义函数showError
void ShowError(const char* content) {
	cout << content << endl;
}
//使用CreateRemoteThread 实现远线程注入
BOOL CreateRemoteThreadInjiectDll(DWORD dwProcessId, LPCWSTR pszDllFileName) {
	HANDLE hProcess = NULL;
	DWORD dwSize = 0;
	LPVOID pDllAddr = NULL;
	FARPROC pFuncProcAddr = NULL;
	//打开注入进程,获取进程句柄
	hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
	if (NULL == hProcess) {
		ShowError("OpenProcess");
		return FALSE;
	}
	//再注入进程中申请内存
	dwSize = 1 + ::lstrlen(pszDllFileName);
	pDllAddr = ::VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);
	if (NULL == pDllAddr) {
		ShowError("VirtualAllocEX");
		return FALSE;
	}
	//向申请的内存中写入数据
	if (FALSE == ::WriteProcessMemory(hProcess, pDllAddr, pszDllFileName, dwSize, NULL)) {
		ShowError("WriteProcessMemory");
		return FALSE;
	}
	//获取LoadLibrary函数地址
	pFuncProcAddr = ::GetProcAddress(::GetModuleHandle(_T("kernel32.dll")), "LoadLibraryA");
	if (NULL == pFuncProcAddr) {
		ShowError("GetProcAddress_LoadLibraryA");
		return FALSE;
	}
	//使用CreateRemoteThread创建远线程,实现DLL注入
	HANDLE hRemoteThread = ::CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pFuncProcAddr,pDllAddr,0,NULL);
	if (NULL == hRemoteThread) {
		ShowError("CreateRemoteThread");
		return FALSE;
	}
	//关闭句柄
	::CloseHandle(hProcess);
	return TRUE;
}

下面来做一个测试,打算做一个打开电脑qq就自动弹出一个框,里面有文字内容,弹窗需要写成一个DLL模块供目标进程加载。

1. 首先是根据程序名称“qq.exe”找到进程号

使用如下程序可以将QQ,exe的进程号给揪出来

#include"inject.h"
#include<tlhelp32.h>//声明快照bai函数的头文件
#include<stdio.h>


int main(int argc, char *argv[])
{
	DWORD QQid; //记录qq 的进程号

	PROCESSENTRY32 pe32;
	//在使用这个结构之前,先设置它的大小
	pe32.dwSize = sizeof(pe32);
	//给系统内的所有进程拍一个快照
	HANDLE hProcessSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	//遍历进程快照,轮流显示每个进程的信息
	BOOL bMore = ::Process32First(hProcessSnap, &pe32);
	while (bMore)
	{
		char output[256];
		const WCHAR* wc = pe32.szExeFile;
		sprintf_s(output, "%ws", wc);
		if (strcmp("QQ.exe", output) == 0)//如果找到进程名为abc.exe
		{
			
			QQid = pe32.th32ProcessID;
			break;
			//HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe32.th32ProcessID);//获取句柄
			/*这里已经打开那个进程的句柄了*/
		}
		printf("next  %ls\n",pe32.szExeFile);
	
		
		bMore = ::Process32Next(hProcessSnap, &pe32);//寻找下一个
	}
	// 远线程注入 DLL
#ifndef _WIN64
	BOOL bRet = CreateRemoteThreadInjectDll(QQid, "C:\\Users\\DemonGan\\Desktop\\CreateRemoteThread_Test\\Debug\\TestDll.dll");
#else 
	BOOL bRet = CreateRemoteThreadInjectDll(1144, "C:\\Users\\DemonGan\\Desktop\\CreateRemoteThread_Test\\x64\\Debug\\TestDll.dll");
#endif

	if (FALSE == bRet)
	{
		printf("Inject Dll Error.\n");
	}
	printf("Inject Dll OK.\n");
	system("pause");
	return 0;
	return 0;
}

2.  把dll文件做好运行效果如图

下面是dll弹窗代码:

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "stdafx.h"
#include<tchar.h>
HMODULE g_hDllModule = NULL;


BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
					 )
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
	{
		g_hDllModule = hModule;
		::MessageBox(NULL, _T("hhh."), _T("OK"), MB_OK);
		break;
	}
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
	case DLL_PROCESS_DETACH:
		break;
	}
	return TRUE;
}

突破sission 0 隔离:

在进行系统进程的注入时,会遇到一个叫session 0 的隔离机制,这时候需要使用更为底层的函数ZwCreateThreadEx来创建远线程,具体原理与CreateRemoteThread相同

三. APC注入

apc是 asynchronous procedure call 异步程序调用。 在windows系统中,每一个线程有一个APC队列,把我们自己定义的apc函数压入到这个队列中就可以得到执行机会

函数介绍:

QueueUserAPC 函数

DWORD WINAPI QueueUserAPC(
    _In_ PAPCFUNC pfnAPC, //当指定线程执行可警告的等待操作时,指向应用程序提供的APC函数指针
    _In_ HANDLE hThread,  //线程句柄,该句柄必须有THREAD_SET_CONTEXT访问权限
    _In_ ULONG_PTR dwData) //传递有pfnAPC参数指向的APC函数的单个值

函数实现:

#include"inject.h"

//定义函数showError
void ShowError(const char* content) {
	printf("%s\n", content);
}
//使用CreateRemoteThread 实现远线程注入
BOOL CreateRemoteThreadInjiectDll(DWORD dwProcessId, LPCWSTR pszDllFileName) {
	HANDLE hProcess = NULL;
	DWORD dwSize = 0;
	LPVOID pDllAddr = NULL;
	FARPROC pFuncProcAddr = NULL;
	//打开注入进程,获取进程句柄
	hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
	if (NULL == hProcess) {
		ShowError("OpenProcess");
		return FALSE;
	}
	
	//再注入进程中申请内存
	dwSize = 1 + ::lstrlen(pszDllFileName);
	pDllAddr = ::VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);
	if (NULL == pDllAddr) {
		ShowError("VirtualAllocEX");
		return FALSE;
	}
	//向申请的内存中写入数据
	if (FALSE == ::WriteProcessMemory(hProcess, pDllAddr, pszDllFileName, dwSize, NULL)) {
		ShowError("WriteProcessMemory");
		return FALSE;
	}
	//获取LoadLibrary函数地址
	pFuncProcAddr = ::GetProcAddress(::GetModuleHandle(_T("kernel32.dll")), "LoadLibraryA");
	if (NULL == pFuncProcAddr) {
		ShowError("GetProcAddress_LoadLibraryA");
		return FALSE;
	}
	//使用CreateRemoteThread创建远线程,实现DLL注入
	HANDLE hRemoteThread = ::CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pFuncProcAddr,pDllAddr,0,NULL);
	if (NULL == hRemoteThread) {
		ShowError("CreateRemoteThread");
		return FALSE;
	}
	//关闭句柄
	::CloseHandle(hProcess);
	return TRUE;
}
// 根据进程名称获取PID
DWORD GetProcessIdByProcessName(LPCWSTR 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(LPCTSTR pszProcessName, LPCTSTR 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;
		}
		//获取loadlibrarya地址
		pLoadLibraryAFunc = ::GetProcAddress(::GetModuleHandle(L"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/mid_Faker/article/details/112567506
今日推荐