一、dll注入的概念
当一个进程运行时,它会加载并使用一些动态链接库(DLL)来提供额外的功能和资源。这些DLL可以被多个进程共享,使得代码重用和资源共享变得更加高效。DLL注入技术利用了这种共享机制。它允许向正在运行的进程中注入一个动态链接库(DLL),被注入的DLL可以利用它所在的进程的权限执行一些特殊的任务,比如修改进程内存中的数据、劫持进程的执行流程、监控进程的行为等等。
dll注入的本质就是把一个不属于某个进程的dll文件加载到该进程当中。
dll注入技术设计的初衷是为了给第三方应用程序提供功能扩展的一种方式。比如当一款软件的作者因为各种原因停止对软件的更新维护,这样我们就可以通过把软件的扩展功能写入dll文件当中去,然后把dll注入到目标软件的进程当中,就可以和软件自带的dll一样正常使用了
二、dll技术的应用
DLL注入技术可以有多种应用,包括但不限于以下几个方面:
-
软件调试:开发人员可以使用DLL注入技术来调试应用程序。它可以在目标进程中注入一个调试DLL,用于监视和分析应用程序的行为,以帮助发现和修复错误。因为我们的软件编译好发布之后,由于平台不一样,所以我们完全可以通过别的一般的电脑来模拟用户,测试软件的功能是否一切正常。
-
病毒编写:恶意软件开发者可以利用DLL注入来实现恶意功能,如窃取用户信息、远程控制目标计算机等。这是一种常见的攻击技术,因此安全软件通常会检测和阻止恶意DLL注入。
-
反病毒软件:一些反病毒软件利用DLL注入技术来监视和防止恶意软件的运行。它们在目标进程中注入一个监控DLL,以便检测和拦截恶意行为。
-
游戏修改:一些玩家可能使用DLL注入技术来修改游戏的行为或增加额外的功能。这可能包括修改游戏内存中的数值、实现外挂功能等。然而,这种行为通常被游戏开发者视为违规行为,可能导致封号或其他惩罚。
三、dll相关的API
DLL注入相关API
OpenProcess | 打开远程进程 |
VirtualAllocEx | 在远程进程中申请内存空间 |
WriteProcessMemory | 写入数据到远程进程 |
CreateRemoteThread | 创建远程线程 |
Loadlibrary | 加载模块 |
WaitForSingleObject | 等待信号 |
VirtualFreeEx | 释放远程进程内存空间 |
CloseHandle | 关闭句柄 |
四、编写代码前的准备工作:
1、OpenProcess函数
OpenProcess是Windows操作系统提供的一个函数,它用于打开一个现有的进程,并返回一个与该进程关联的进程句柄。该函数的原型如下:
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操作系统提供的一个函数,用于在指定进程的虚拟地址空间中分配内存。函数原型如下:
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中的一个函数,它可以将数据写入到指定进程的内存中。该函数可以用于在一个进程中注入代码或数据,实现进程间通信等功能。声明如下:
BOOL WriteProcessMemory(
HANDLE hProcess,
LPVOID lpBaseAddress,
LPCVOID lpBuffer,
SIZE_T nSize,
SIZE_T *lpNumberOfBytesWritten
);
参数说明:
- hProcess:目标进程的句柄。
- lpBaseAddress:要写入数据的目标进程内存的起始地址。
- lpBuffer:要写入的数据缓冲区的指针。
- nSize:要写入的数据的大小,以字节为单位。
- lpNumberOfBytesWritten:一个指向变量的指针,用于接收实际写入的字节数。
WriteProcessMemory函数返回的是一个布尔类型的值,表示有没有写内存成功。
4、CreateRemoteThread函数
CreateRemoteThread是Windows操作系统提供的一个函数,它可以在指定的进程空间中创建一个远程线程,以便在该进程中执行指定的函数。函数原型如下:
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注入的示例代码:
#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里面添加附加到线程的时候执行等等。
6、如何在VS2019创建一个dll文件?
在 Visual Studio 2019 中创建 DLL 项目的步骤如下:
-
打开 Visual Studio 2019,点击 "创建新项目",选择 Visual C++ -> 动态链接库 (DLL) 项目。
-
在 "创建新项目" 对话框中,为项目命名并选择保存位置,然后单击 "下一步"。
-
在 "应用程序类型" 选项卡下,选择 "DLL" 选项,并将 "附加选项" 选项卡下的 "空项目" 复选框选中。
-
单击 "创建",Visual Studio 将生成一个名为 "Dll1" 的默认 DLL 项目。
-
在 "解决方案资源管理器" 中,展开 "源文件" 节点并双击 "Dll1.cpp" 文件。
-
在 "Dll1.cpp" 文件中编写 DLL 的代码,例如:
#include "pch.h"
#include "framework.h"
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
extern "C" __declspec(dllexport) void HelloWorld()
{
MessageBox(NULL, "Hello, World!", "Dll1.dll", MB_OK);
}
DllMain
是 Windows DLL 中的一个特殊函数,用于处理 DLL 的加载、卸载以及其他状态的通知。
该函数的签名为:
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注入实例编写
我们通过dll注入实现一个小功能:比如我有一个朋友叫浮沉,他最近痴迷于天天尬跑小游戏,但是他妈不想让他天天打电动,于是让我编写一个小软件提示他:人在做,妈再看。从而达到一定的惊醒目的,并且在他吃惊恐惧之余,趁机关掉这个小游戏。话不多说,让我们来一起愉快的编码吧!
1、首先是注入dll文件到目标进程的代码
还是先讲一下整体的思路:
1)首先我们需要对别的进程进行任何的操作,首先一步,也是最重要的一步就是要拿到目标进程的句柄,即通过OpneProcess()函数获取目标进程的句柄,通过句柄来执行后面的一切操作!
2)通过VirtualAllocEx函数在目标进程申请一块内存空间,在这块内存空间当中存放我们需要传递的参数,包括用到的变量等等,甚至还可以在这里写入函数的硬编码来直接调用执行
3)通过WriteProcessMemory函数进行跨进程的读写,主要就是把我们需要用到的数据,包括参数,变量等等写入到刚刚VirtualAllocEx申请的这段内存空间当中,在本文当中就是把LoadLibrary需要加载的dll文件的名称写道这里面来,方便后面给LoadLibrary传参
4)使用CreateRemoteThread函数+LoadLibrary在游戏进程当中创建一个远程线程并执行它,LoadLibrary正好满足CreateRemoteThread当中回调函数的格式,而且每个进程它一定会加载Kernel32.dll这个dll文件,所以就一定可以在远程的线程当中调用LoadLibrary这个函数,那么LoadLibrary函数做什么功能呢?》就是把我们写好的dll文件加载到当前的这个进程里面!
#include<iostream>
using namespace std;
#include<Windows.h>
#include <tlhelp32.h>
#include<tchar.h>
#pragma comment(lib, "msvcrt.lib")
DWORD GetProcessID(LPCTSTR szProcessName)
{
DWORD dwProcessID = 0;
HANDLE hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapShot == INVALID_HANDLE_VALUE) {
return dwProcessID;
}
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(pe32);
if (!Process32First(hSnapShot, &pe32)) {
CloseHandle(hSnapShot);
return dwProcessID;
}
do {
if (_tcsicmp(pe32.szExeFile, szProcessName) == 0) {
dwProcessID = pe32.th32ProcessID;
break;
}
} while (Process32Next(hSnapShot, &pe32));
CloseHandle(hSnapShot);
return dwProcessID;
}
int main() {
const char* data = "D://Dll1.dll";
int buffSize = strlen(data) + 1;
DWORD dwProcessId = GetProcessID(L"《天天尬跑》V1.0版本.exe");
HANDLE hGameProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
char* lpRemoteMem = (char*)VirtualAllocEx(hGameProcess, NULL, buffSize, MEM_COMMIT, PAGE_READWRITE);
SIZE_T nBytesWritten = 0;
BOOL bRet = WriteProcessMemory(hGameProcess, lpRemoteMem, data, buffSize, &nBytesWritten);
if (bRet == FALSE || nBytesWritten != buffSize) {
CloseHandle(hGameProcess);
VirtualFreeEx(hGameProcess, lpRemoteMem, 0, MEM_RELEASE);
return 0;
}
HANDLE hThread = CreateRemoteThread(hGameProcess, NULL, 0, (LPTHREAD_START_ROUTINE)LoadLibraryA, lpRemoteMem, 0, NULL);
WaitForSingleObject(hThread, -1);
VirtualFreeEx(hGameProcess, lpRemoteMem, 0, MEM_RELEASE);
CloseHandle(hThread);
CloseHandle(hGameProcess);
cout << "报告!浮沉的小游戏已经被做掉了!圆满完成任务!" << endl;
system("pause");
return 0;
}
2、在dll文件当中具体实现的功能
#include"pch.h"
#include<iostream>
using namespace std;
#include<Windows.h>
#include <tlhelp32.h>
#include<tchar.h>
#pragma comment(lib, "msvcrt.lib")
extern "C" __declspec(dllexport)DWORD GetProcessID(LPCTSTR szProcessName)
{
DWORD dwProcessID = 0;
HANDLE hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapShot == INVALID_HANDLE_VALUE) {
return dwProcessID;
}
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(pe32);
if (!Process32First(hSnapShot, &pe32)) {
CloseHandle(hSnapShot);
return dwProcessID;
}
do {
if (_tcsicmp(pe32.szExeFile, szProcessName) == 0) {
dwProcessID = pe32.th32ProcessID;
break;
}
} while (Process32Next(hSnapShot, &pe32));
CloseHandle(hSnapShot);
return dwProcessID;
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH: {
MessageBoxA(NULL, "浮沉又在打电动噢!", "严重警告!!!", MB_OK);
//必须要拿到句柄,才能对进程进行操作!!!
int pid = GetProcessID(L"《天天尬跑》V1.0版本.exe");
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, NULL, pid);
if (hProcess == NULL) {
// 处理打开进程失败的情况
int err = GetLastError();
MessageBoxA(NULL, (LPCSTR)GetLastError(), "严重警告!!!", MB_OK);
return -1;
}
BOOL bTerminated = TerminateProcess(hProcess, 0);
if (bTerminated) {
MessageBoxA(NULL, (LPCSTR)GetLastError(), "严重警告!!!", MB_OK);
}
CloseHandle(hProcess);
break;
}
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
在编写dll的时候有两点需要注意:
1、Tandy在这里调试代码的时候遇到了问题:明明包含了需要的头文件,但是却报了一堆找不到的错误:
这种情况下我们要考虑到头文件的包含顺序的问题,一般来说要把".h"这种放到最前面!于是通过把#include"pch.h"放在第一行,就成功编译通过了:
2、记得修改dll的属性为动态链接库
3、最终的执行效果图
我们可以看到,当我们执行dll注入的代码之后,浮沉玩游戏的窗口会弹出来一个提示框:浮沉又在打电动噢,然后无论是点击×号,还是点击确定,这个游戏进程都会被毫不留情地关闭掉!😂😂😂😂😂