Dll注入技术0——经典注入

概念

dll注入是一种将Windows动态链接库注入到目标进程中的技术,具体的说,就是将dll文件加载到一个进程的虚拟地址空间中。对某个进程进行dll注入,也就意味着dll模块与该进程共用一个进程空间,则这个dll文件就有了操纵这个进程空间的能力,以达到执行dll模块中的代码修改进程数据的能力。dll注入技术在逆向工程,病毒,外挂,调试等技术领域都有广泛的应用,它也是Windows API hook技术的基础。

什么是DLL

dll文件是Windows操作系统下的动态链接库,动态链接库的使用及其广泛,它解决了一些静态链接带来的缺点,如内存空间冗余,文件大小臃肿等问题。通常在没有特意设置的情况下,对源代码进行编译的结果默认是动态链接的,在Windows操作系统中,如kernel32.dll,ntdll.dll,user32.dll几乎是必用的动态库。

dll文件本质上也是PE文件,基本的文件结构和可执行文件是类似的。对开发者来说,dll文件提供了一系列导出函数来使用,可以用显式的方式在程序中加载动态库,也可以在编译环境中提前设置好。而dll注入技术则是将未加载的外部dll模块加载到正在运行的进程当中,当我们无法重新编译某个程序,却又想操纵其进程空间时,就可以用到dll注入技术。

如何实现

由于要对进程级别的对象进行操纵,最自然的方式就是利用Windows API。Windows API是Windows系统为开发者提供的一系列接口,每个接口可以看作是操作系统提供的某项服务。不过Windows API在如今看来有许多明显的缺点,主要是设计方面的问题,所以在使用上可能不像一些拥有良好接口的函数库方便和易用,不过遇到问题多查查手册就好了。dll注入可以使用多种不同的手段达成,但这些手段归恨到底都是让目标进程加载目的dll文件。

全局钩子注入

原理:全局钩子注入实质上是利用了操作系统的加载dll文件的特性来达到注入效果的,即当一个dll模块已经存在于内存当中时,且某个进程对它的函数进行了调用,系统就会将这个dll模块加载到该进程空间当中。Windows提供了一个钩子机制,它可以截获和监视系统中的Windows消息,只要熟悉Windows的都知道,Windows的绝大部分应用程序都是基于消息机制的,而消息钩子每次截获到对应消息时,都可以进行一些处理,这个处理发生在一个回调函数中,也就是说,我们只需要将这个回调函数设置在dll文件中,当钩子截获到其他进程的消息时,操作系统就会调用这个回调函数,于是dll文件也就被注入到了该进程之中。

实现:首先要制作dll文件,主要的功能都包含在这个dll文件之中,包括设置钩子,取消钩子以及钩取后的处理函数。

#include <Windows.h>

BOOL SetGlobalHook();

BOOL UnsetGlobalHook();

LRESULT GetMsgProc(int, WPARAM, LPARAM);

#pragma data_seg("my_data")
	HHOOK g_hHook = NULL; //全局钩子句柄
#pragma data_seg()

#pragma comment(linker, "/SECTION:mydata,RWS")
//由于钩子必须全局的,所以要特别设置一个共享数据段用来存放全局钩子句柄,使得其他进程空间也有权限访问。

HMODULE g_hDllModule = NULL;

LRESULT GetMsgProc(int code, WPARAM wParam, LPARAM lParam) //回调函数
{
	return ::CallNextHookEx(g_hHook, code, wParam, lParam); //传递钩子
}

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

BOOL UnsetGlobalHook() //取消钩子
{
	if (g_hHook)
	{
		::UnhookWindowsHookEx(g_hHook);
	}
	return TRUE;
}

BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD fdwReason, PVOID fImpLoad) //入口点
{
	switch (fdwReason)
	{
	case DLL_PROCESS_ATTACH:
	{
		g_hDllModule = hInstDll;
		break;
	}
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
	case DLL_PROCESS_DETACH:
		break;
	}
	return TRUE;
}

这里定义的设置钩子和取消钩子的两个函数仅仅是对SetWindowsHookEx和UnhookWindowsHookEx进行了封装,添加了一点条件判断。核心功能在dll文件的代码中已经完成了,剩下的就是启动程序了,这个启动程序应该加载上述dll文件,并调用该dll文件的接口来设置全局钩子,这之后,一旦有进程发送消息到消息队列,dll模块就会注入到该进程,启动代码如下:

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

int main(int argc, TCHAR* argv[])
{
	typedef BOOL(*pfSetGlobalHook)();
	typedef BOOL(*pfUnsetGlobalHook)();
	HMODULE hDll = NULL;
	pfSetGlobalHook SetGlobalHook = NULL;
	pfUnsetGlobalHook UnsetGlobalHook = NULL;
	BOOL ret;

	do {
		hDll = ::LoadLibrary("GlobalHook_dll.dll");
		if (hDll == NULL)
		{
			std::cout << "LoadLibrary wrong!" << std::endl;
			break;
		}

		SetGlobalHook = (pfSetGlobalHook)::GetProcAddress(hDll, "SetGlobalHook");
		if (SetGlobalHook == NULL)
		{
			std::cout << "get SetGlobalHook wrong!" << std::endl;
			break;
		}

		ret = SetGlobalHook();
		if (ret)
			std::cout << "set hook ok!" << std::endl;
		else
			std::cout << "set hook wrong!" << std::endl;
		system("pause");

		UnsetGlobalHook = (pfUnsetGlobalHook)::GetProcAddress(hDll, "UnsetGlobalHook");
		if (UnsetGlobalHook == NULL)
		{
			std::cout << "get UnsetGlobalHook wrong!" << std::endl;
			break;
		}
		ret = UnsetGlobalHook();
		if (ret)
			std::cout << "unset hook ok!" << std::endl;
		else
			std::cout << "unset hook wrong!" << std::endl;
	} while (FALSE);

	return 0;
}

远线程注入

相较于全局钩子注入,远线程注入更灵活,也更符合我们的逻辑,可以说大多数dll注入都是使用的这种方法。远线程注入利用了几个关键的Windows API,分别是OpenProcess,VirtualAlloc,WriteProcessMemory,CreateRemoteThread。大概的流程或逻辑如下:

  1. 调用OpenProcess打开一个进程并获取它的句柄,
  2. 调用VirtualAlloc在目标进程中分配一块空间
  3. 调用WriteProcessMemory向这块空间中写入要注入的dll路径
  4. 调用GetProcAddress获取LoadLibrary函数的地址
  5. 调用CreateRemoteThread在目标进程中启动一个线程,这个线程调用LoadLibrary,参数为dll路径

具体代码如下:

BOOL CreateRemoteThreadInjectDll(DWORD dwProcessId, const char *pszDllFileName)
{
	HANDLE hProcess = NULL;
	DWORD dwSize = 0;
	LPVOID pDllAddr = NULL;
	FARPROC pFuncProcAddr = NULL;

	hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
	if (hProcess == NULL)
	{
		ShowError("OpenProcess Wrong!");
		return FALSE;
	}

	dwSize = 1 + ::strlen(pszDllFileName); //考虑'\0'字符结尾
	pDllAddr = ::VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);
	if (pDllAddr == NULL)
	{
		ShowError("memory alloc wrong!");
		return FALSE;
	}

	if (!::WriteProcessMemory(hProcess, pDllAddr, pszDllFileName, dwSize, NULL))
	{
		ShowError("write data to process wrong!");
		return FALSE;
	}

	pFuncProcAddr = ::GetProcAddress(::GetModuleHandle("kernel32.dll"), "LoadLibraryA");
	if (pFuncProcAddr == NULL)
	{
		ShowError("get function address wrong!");
		return FALSE;
	}

	HANDLE hRemoteThread = ::CreateRemoteThread(hProcess, NULL, 0, 
		(LPTHREAD_START_ROUTINE)pFuncProcAddr, pDllAddr, 0, NULL);
	if (hRemoteThread == NULL)
	{
		ShowError(("create remote thread wrong!");
		return FALSE;
	}
	::CloseHandle(hProcess);
	return TRUE;
}

本质上就是纯粹的API调用,没什么可说的。

PS:对于服务进程来说,由于存在SESSION 0隔离机制,所以使用CreateRemoteThread是不被允许的,这时可以使用一个native层的函数ZwCreateThread来达到注入的效果,具体的实现代码与上述相同,只是最后调用的是ZwCreateThread函数而已。

关于SESSION 0隔离:https://technet.microsoft.com/zh-cn/ee791007.aspx

猜你喜欢

转载自blog.csdn.net/qq_35713009/article/details/86721445