DLL注入

1,198 阅读5分钟

DLL注入

DLL注入指的是向其他进程插入DLL文件,是通过强制让其他进程调用LoadLibrary()API实现的. DLL注入和一般的DLL加载的区别在于,目标是其它进程还是自身进程.

当DLL被加载到进程后,会自动调用DllMain(),可以通过往DllMain()函数写入代码来实现相应的功能.

DLL注入的方式有以下三种

  1. windows消息勾取

  2. 远程线程注入

  3. 使用注册表

下面看一下这三种注入方式的原理,实现以及调试方法.

Windows消息勾取

Windows消息勾取的原理

Windows是消息驱动系统,可以通过发送消息,处理消息来实现不同的功能.消息发送的过程大致可以分为以下几步.

graph TD
触发事件 --> 封装消息 --> 系统消息队列  --> 应用程序消息队列 --> 应用程序 --> 应用程序处理函数

钩子可以用来偷看或截取消息,设钩则是在的流程中设置一个中间点,信息都会先通过这里.

Windows消息勾取是在 系统消息队列--->应用程序消息队列 设置钩子,而消息钩子会提前于应用程序得到消息.对于特定的消息,会先将钩子处理函数所在的DLL注入到对应的进程中,并调用钩子处理函数对消息进行处理.

graph TD
系统消息队列 --> 钩子1 --> 钩子2 --> ... --> 应用程序消息队列

多个钩子组成的链条被称为沟链.通过调用SetWindowsHookEx()API即可设置需要的钩子.

Windows消息钩子的实现

DLL库

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved)
{
	switch (dwReason)
	{
	case DLL_PROCESS_ATTACH:
		g_hInstance = hinstDLL;
		break;
	
	case DLL_PROCESS_DETACH:
		break;
	}

	return TRUE;
}

当DLL被加载到进程中,会先调用DllMain(),并且dwReason为DLL_PROCESS_ATTACH,根据switch分支语句,将DLL的句柄赋给全局变量g_hInstance.

	__declspec(dllexport) void HookStart()
	{
		g_hHook = SetWindowsHookEx(WH_KEYBOARD,//键盘钩子
                KeybordProc,//处理函数地址
                g_hInstance, //DLL句柄
                0);//目标是所有进程
	}

这里在导出函数HookStart()中调用SetWindowsHookEx()API,主要是为了方便.

LRESULT CALLBACK KeybordProc(int nCode, WPARAM wParam, LPARAM lParam)
{
	CHAR szPath[MAX_PATH] = { 0 };//文件路径
	CHAR* p = nullptr;

	if (nCode >= 0)
	{
		if (!(lParam & 0x80000000))//释放键盘按键时
		{
			GetModuleFileNameA(nullptr, szPath, MAX_PATH);
                        //获取当前进程加载模块的文件的完整路径
			p = strrchr(szPath, '\\');//获得进程名称
			//比较进程名称
			if (!strcmp(DEF_PROCESS_NAME, p+1))
			{
				return 1;//不将消息传递给应用程序
			}
		}
	}

	//如果不是notepad.exe或者nCode<0,则将消息传递给下一个钩子或者应用程序
	return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}

KeyBoardProc是回调函数,当勾取到消息的时候,系统会自动调用这个函数.

HookMain

//定义的名称
#define DEF_DLL_NAME "KeyHook.dll"
#define DEF_HOOKSTART "HookStart"
#define DEF_HOOKSTOP "HookStop"

//定义的类型
typedef void (*PFN_HOOKSTART)();
typedef void (*PFN_HOOKSTOP)();

int main()
{
    HMODULE hDll = nullptr;//dll句柄
    PFN_HOOKSTART HookStart = nullptr;//开始勾取函数
    PFN_HOOKSTOP HookStop = nullptr;//停止勾取函数

    //加载DLL
    hDll = LoadLibraryA(DEF_DLL_NAME);

    //获得导出函数地址
    HookStart = (PFN_HOOKSTART)GetProcAddress(hDll, DEF_HOOKSTART);
    HookStop = (PFN_HOOKSTOP)GetProcAddress(hDll, DEF_HOOKSTOP);

    //开始勾取
    HookStart();

    //等待结束
    printf("press 'q' to quit\n");
    while (getchar() != 'q');

    //停止勾取
    HookStop();

    return 0;

}

Windows消息勾取的调试

  • 打开OD,加载notepad.exe并运行.
  • 打开HookMain.exe,设置钩子.
  • 点击Options选项中的Debugging options,在Events中选中Break On New Module(DLL),即在加载新的DLL时暂停. image.png
  • 在打开的记事本中使用键盘输入
  • KeyHook.dll被注入到notepad.exe的进程中
  • 在View选项中选择Executable modules,这样就可以看到被注入的dll
  • 要注意在加载KeyHook.dll时,可能会先加载其他的dll,此时只要按f9就可以了 image.png

远程线程注入

远程线程注入的原理

远程线程注入简单的说,就是调用CreateRemoteThread(),在其他的进程中创建一条线程,执行LoadLibrary(),将特定的DLL加载到进程中间.

远程线程注入的实现

int main(int argc,char* argv[])
{
	if (argc != 3)
		return 1;

	if (InjectDll((DWORD)atol(argv[1]),argv[2]))
		printf("Success!!\n");
	else
		printf("Failed!!\n");

	return 0;
	
}

通过命令行参数获得进程的PID和要在注入的dll路径.(注意dll路径要写完整,要不然目标进程无法找到dll,即使显示注入成功,实际dll并没有注入)

	//获取目标进程句柄
	hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID);
	if (hProcess == nullptr)
	{
		return FALSE;
	}

通过传入的PID,调用OpenProcess()API,得到进程的句柄,以便之后用来操作进程.


	//在目标进程中分配空间
	dwBufSize = (strlen(szDllPath)+1);
	lpRemoteBuf = VirtualAllocEx(hProcess, nullptr, dwBufSize, MEM_COMMIT, PAGE_READWRITE);
        
	//在分配的空间中写入dll路径
	WriteProcessMemory(hProcess, lpRemoteBuf, (LPCVOID)szDllPath, dwBufSize, nullptr);

在目标进程中分配空间,并将dll路径写.注意这边是对目标进程进行操作.

        //获得LoadLibrary地址
	hMod = GetModuleHandleA("kernel32.dll");
	lpThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryA");

因为kernel32.dll在进程中的地址都是相同的,因此LoadLibrary()API的地址也是相同的,在原进程中获得的API地址等于目标进程的地址.

//创建远程线程
CreateRemoteThread(
hProcess, 
nullptr, 
0,
lpThreadProc,//线程入口地址
lpRemoteBuf, //参数
0,
nullptr
);

这里先看一下LoadLibrary()和ThreadProc()的定义:

HMODULE LoadLibrary(
  LPCSTR lpLibFileName
);
//返回值为4字节的dll模块句柄
//参数为指针类型

DWORD WINAPI ThreadProc(
  LPVOID lpParameter
);
//返回值为4字节的退出码
//参数为指针类型

由此可见,LoadLibrary()和ThreadProc()的结构几乎是完全一样的,所以可以将LoadLibrary()API当作线程处理函数,当作CreateRemoteThread()的第四个参数传递, 第五个参数也同时变为dll的路径.

远程线程注入的调试

调试方法于Windows消息勾取的调试方法相同,可以查看之前写的内容.

注册表注入

注册表注入的实现和原理

打开注册表编辑器regedit.exe,进入以下路径

image.png 将要注入的DLL路径写入AppInit_DLLs,并且将LoadAppInit_DLLs的值设为1.重启后,指定的DLL就会被注入到进程程中.

原因是:当进程加载User32.dll时,会读取AppInit_DLLs和LoadAppInit_DLLs,若有值,就会调用LoadLibrary()加载指定的DLL.


以上内容来自于阅读逆向工程核心原理第21和23章的内容,为了整理内容和实践书中的代码就写下了这篇博客.