DLL注入技术

属于是非常笼统了,基本没有啥自己写的东西qaq

一.dll注入的概念

​ 当一个进程运行时,它会加载并使用一些动态链接库(DLL)来提供额外的功能和资源。这些DLL可以被多个进程共享,使得代码重用和资源共享变得更加高效。DLL注入技术利用了这种共享机制。它允许向正在运行的进程中注入一个动态链接库(DLL),被注入的DLL可以利用它所在的进程的权限执行一些特殊的任务,比如修改进程内存中的数据、劫持进程的执行流程、监控进程的行为等等。
dll注入的本质就是把一个不属于某个进程的dll文件加载到该进程当中。

DLL注入与一般DLL加载的主要区别是加载的目标进程是其自身或者其他进程。

​ 插入基础知识:

Kernel32.dll(包含管理内存,进程和线程相关的函数),“User32.dll”(大部分是用户接口函数),和“GDI32.dll”(绘制图像和显示文本相关的函数)

二.dll技术的应用(主要方面)

image-20231008193615305

三.dll相关的API

OpenProcess 打开远程进程
VirtualAllocEx 在远程进程中申请内存空间
WriteProcessMemory 写入数据到远程进程
CreateRemoteThread 创建远程线程
Loadlibrary 加载模块
WaitForSingleObject 等待信号
VirturalFreeEx 释放远程进程内存空间
CloseHandle 关闭句柄

四.编写代码前的准备工作:

1.OpenProcess函数

​ OpenProcess是windows操作系统提供的一个函数,它用于打开一个现有的进程,并返回一个与该进程关联的进程句柄。该函数的原型如下:

1
2
3
4
5
HANDLE OpenProcess(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
DWORD dwProcessId
);

参数说明:

dwDesiredAccess:指定进程的访问权限。这可以是一些常量值的组合,如PROCESS_ALL_ACCESS、PROCESS_CREATE_PROCESS、PROCESS_QUERY_INFORMATION等。
bInheritHandle:指定新句柄是否可以被继承。如果为TRUE,则新句柄可以被继承;如果为FALSE,则新句柄不可以被继承。
dwProcessId:指定要打开的进程的进程ID号。

OpenProcess函数可以用于获取一个现有进程的句柄,以便在该进程中执行一些操作,如读取或写入进程内存、修改进程的安全属性、发送消息等。需要注意的是,不同的进程拥有不同的访问权限,因此在使用OpenProcess函数时需要正确设置进程的访问权限,以避免权限问题导致的操作失败。

OpenProcess函数常用于编写一些系统工具或调试程序,以及一些需要与其他进程交互的应用程序。在编写一些恶意软件或攻击程序时,OpenProcess函数也可以被用于获取另一个进程的句柄,并对其进行一些未授权的操作。因此,在使用OpenProcess函数时需要谨慎,以避免对系统造成不良影响。

2.VirtualAllocEx函数

VirtualAllocEx是Windows操作系统提供的一个函数,用于在指定进程的虚拟地址空间中分配内存。函数原型如下:

1
2
3
4
5
6
7
LPVOID VirtualAllocEx(
HANDLE hProcess,
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flAllocationType,
DWORD flProtect
);

函数参数说明:

hProcess:指定要在哪个进程中分配内存,该参数需要指定要分配内存的进程句柄。
lpAddress:指定欲分配的虚拟内存起始地址,如果为NULL,表示由系统自动选择一个合适的地址。
dwSize:指定要分配的内存大小,以字节为单位。
flAllocationType:指定内存分配的类型,可以使用一些常量值如MEM_COMMIT、MEM_RESERVE等。
flProtect:指定内存的保护属性,可以使用一些常量值如PAGE_READWRITE、PAGE_EXECUTE_READ等。

VirtualAllocEx函数的返回值是一个LPVOID类型的指针,指向在指定进程的虚拟地址空间中分配的内存的起始地址。如果函数调用失败,则返回NULL

使用VirtualAllocEx函数可以实现在指定进程的虚拟地址空间中分配内存,从而使得其他进程可以访问和使用该内存。需要注意的是,使用VirtualAllocEx函数分配的内存需要在使用完毕后通过VirtualFreeEx函数释放,以避免内存泄漏和资源浪费。

3.WriteProcessMemory函数:

WriteProcessMemory函数是Windows API中的一个函数,它可以将数据写入到指定进程的内存中。该函数可以用于在一个进程中注入代码或数据,实现进程间通信等功能。声明如下:

1
2
3
4
5
6
7
BOOL WriteProcessMemory(
HANDLE hProcess,
LPVOID lpBaseAddress,
LPCVOID lpBuffer,
SIZE_T nSize,
SIZE_T *lpNumberOfBytesWritten
);

参数说明:

hProcess:目标进程的句柄。
lpBaseAddress:要写入数据的目标进程内存的起始地址。
lpBuffer:要写入的数据缓冲区的指针。
nSize:要写入的数据的大小,以字节为单位。
lpNumberOfBytesWritten:一个指向变量的指针,用于接收实际写入的字节数。

WriteProcessMemory函数返回的是一个布尔类型的值,表示有没有写内存成功。

4.CreateRemoteThread函数

CreateRemoteThread是Windows操作系统提供的一个函数,它可以在指定的进程空间中创建一个远程线程,以便在该进程中执行指定的函数。函数原型如下:

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
);

函数参数说明:

hProcess:指定要在哪个进程中创建线程,该参数需要指定要创建线程的进程句柄。
lpThreadAttributes:指定线程的安全属性,如果不需要设置,则传入NULL即可。
dwStackSize:指定线程的堆栈大小,如果不需要设置,则传入0即可。
lpStartAddress:指定要在远程线程中执行的函数地址。
lpParameter:指定要传递给远程线程的参数。
dwCreationFlags:指定线程的创建标志,可以使用一些常量值如0、CREATE_SUSPENDED等。
lpThreadId:指向一个变量,用于返回线程ID号。

使用CreateRemoteThread函数可以实现在一个进程空间中注入一个线程,并且可以传递一些参数给该线程。该函数常用于编写一些系统工具或调试程序,以及一些需要与其他进程交互的应用程序。

以下是一个使用CreateRemoteThread函数实现DLL注入的示例代码:

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
#include <windows.h>

int main()
{
// 获取目标进程句柄
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 1234);
if (hProcess == NULL) {
return 0;
}

// 获取LoadLibrary函数地址
LPVOID lpLoadLibrary = GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "LoadLibraryA");
if (lpLoadLibrary == NULL) {
CloseHandle(hProcess);
return 0;
}

// 分配内存空间
LPVOID lpDllPath = VirtualAllocEx(hProcess, NULL, strlen("C:\\test.dll") + 1, MEM_COMMIT, PAGE_READWRITE);
if (lpDllPath == NULL) {
CloseHandle(hProcess);
return 0;
}

// 将DLL路径写入目标进程内存
WriteProcessMemory(hProcess, lpDllPath, "C:\\test.dll", strlen("C:\\test.dll") + 1, NULL);

// 在目标进程中创建远程线程
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpLoadLibrary, lpDllPath, 0, NULL);
if (hThread == NULL) {
VirtualFreeEx(hProcess, lpDllPath, strlen("C:\\test.dll") + 1, MEM_RELEASE);
CloseHandle(hProcess);
return 0;
}

// 等待线程结束
WaitForSingleObject(hThread, INFINITE);

// 关闭句柄,释放内存
CloseHandle(hThread);
VirtualFreeEx(hProcess, lpDllPath, strlen("C:\\test.dll") + 1, MEM_RELEASE);
CloseHandle(hProcess);

5.LoadLibrary函数

LoadLibrary函数是Windows操作系统提供的一个函数,它可以加载一个动态链接库(DLL)文件到进程空间中,并返回该DLL的句柄。

需要注意的是它仅仅是把dll文件加载到进程的空间,但是想要调用这个dll还需要别的操作,比如在这个dll的DllMain里面添加附加到线程的时候执行等等。

DllMain 是 Windows DLL 中的一个特殊函数,用于处理 DLL 的加载、卸载以及其他状态的通知。

该函数的签名为:

1
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved);

参数说明:

hModule: DLL 的模块句柄。该参数可以用来获取 DLL 中的其他资源,例如资源文件、函数地址等。
ul_reason_for_call: 表示 DLL 被加载、卸载或者其他状态的通知。可能的值包括:
DLL_PROCESS_ATTACH: DLL 被进程加载时调用。
DLL_PROCESS_DETACH: DLL 被进程卸载时调用。
DLL_THREAD_ATTACH: 进程创建新线程时调用。
DLL_THREAD_DETACH: 进程中线程退出时调用。
lpReserved: 保留参数。在实际开发中,一般不使用该参数。
实际使用中,DllMain 函数常用于执行一些初始化或清理操作,例如:

在 DLL 加载时,进行资源的初始化,例如初始化共享内存、建立临界区等。
在 DLL 卸载时,进行资源的释放,例如清除共享内存、释放临界区等。
需要注意的是,在 DllMain 函数中,有一些操作是不安全的,例如:

调用其他 DLL 函数。这可能会导致死锁或死循环,因为其他 DLL 函数也可能调用 DllMain 函数。
创建新线程。在 DllMain 函数中创建新线程可能会导致死锁,因为该函数会在 DLL 加载之前被调用,此时可能没有完成初始化操作。
调用某些系统函数。某些系统函数可能会使用其他 DLL,这可能会导致死锁或死循环。
因此,在编写 DllMain 函数时需要小心谨慎,避免出现安全问题。

五.DLL注入四个步骤

(四个步骤)

1)附加到目标/远程进程

2)在目标/远程进程内分配内存

3)将DLL文件路径,或者DLL文件,复制到目标/远程进程的内存空间

4)控制进程运行DLL文件

1.技术介绍:

​ 我们有多种方式可以控制进程运行我们的DLL文件。最普通的应该是“CreateRemoteThread()”和“NtCreateThreadEx()”函数;然而,不可能仅仅向这些函数传递一个DLL文件作为参数,我们必须提供一个包含执行起点的内存地址。为此,我们需要分配内存,使用“LoadLibrary()”加载我们的DLL文件,复制内存,等等。

​ 以下还有7种注入方法:

  • CreateRemoteThread()
  • NtCreateThreadEx()
  • QueueUserAPC
  • SetWindowsHookEx()
  • RtlCreateUserThread()
  • 利用SetThreadContext()找到的代码区域
  • 反射DLL

2.LoadLibrary()[附加函数原型和参数补充]

“LoadLibrary()”函数“被用于向调用进程的地址空间加载指定模块,而该指定模块可能导致其他模块被加载”。函数原型与参数说明如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
HMODULE WINAPI LoadLibrary(
_IN_ LPCRSTR lpFileName
);
//lpFileName [输入参数]
//模块名称。该模块可能是一个库模块(.dll文件),或者一个可执行模块(.exe文件)
//(…)
//若字符串指定了一个完全路径,则函数只在该路径下搜索模块;
//若字符串指定了一个相对路径或者无路径的模块名称,则函数使用标准搜索策略来查找模块。
//(…)
//若函数无法找到模块,则函数执行失败。当指定路径时,必须使用反斜线(\)而不是斜线(/)。
//(…)
//如果字符串指定了一个无路径的模块名称并且无文件名后缀,则函数默认在模块名称后面添加库文件后缀.dll。