【权限维持技术】Windows DLL远程线程注入

admin 2023年12月9日16:08:29评论29 views字数 5762阅读19分12秒阅读模式

L

免责声

本文仅用于技术讨论与学习,利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者及本公众号不为此承担任何责任。

DLL 注入DLL注入是一种常见的安全威胁,指的是将恶意动态链接库(DLL)文件注入到正常程序的进程空间内。通过这种技术,攻击者能够在目标程序内执行任意代码,常见的注入方式包括使用Windows API如CreateRemoteThread和SetWindowsHookEx等。这种方法的隐蔽性好,易于绕过传统的杀毒软件检测,因为它利用了正常程序的合法操作,因此在木马存在感极强,利用率极高。

远程线程注入

最常见的DLL注入方法就是远程线程注入,它涉及在目标进程的地址空间内创建新的线程来执行加载器或直接执行注入代码:

  1. 打开目标进程,获得其进程句柄。
  2. 为DLL路径或注入代码在目标进程内存中分配空间。
  3. 将DLL路径或注入代码写入分配的内存区域。
  4. 使用Windows API(如CreateRemoteThread)在目标进程中启动新线程,执行加载DLL的操作或注入代码。


待注入DLL实现

1. 首先使用Visual Studio创建一个DLL项目:

【权限维持技术】Windows DLL远程线程注入

2. Visual Studio会自动帮助我们生成框架:

【权限维持技术】Windows DLL远程线程注入

// dllmain.cpp : 定义 DLL 应用程序的入口点。#include "pch.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;}






其中:

  1. DllMain函数是DLL的主要入口点。APIENTRY宏定义用于声明由Windows API调用的函数。
  2. DllMain函数是DLL的主要入口点。APIENTRY宏定义用于声明由Windows API调用的函数。
  3. 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文件

远程线程注入器实现1. Visual Studio创建一个控制台应用:

【权限维持技术】Windows DLL远程线程注入

2. 完成如下代码,将会对代码内容进行进一步讲解:

// ConsoleApplication3.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。//





#include <iostream>#include <Windows.h>





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);
这行代码调用 Windows API 函数 OpenProcess 来获取一个可以用于访问指定进程的句柄。具体来说:
    • 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);
    这行代码通过调用 VirtualAllocEx 函数,在一个指定的远程进程(process)的地址空间内分配内存。
    详细解释如下:
      • 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 允许进程对这片内存进行读取和写入操作。
      5. 然后把DLL文件加载到目标进程中:
      WriteProcessMemory(process, mem, dllPath, wcslen(dllPath) * sizeof(wchar_t), NULL)
      这行代码使用 Windows API 的 WriteProcessMemory 函数,将数据写入其他进程的地址空间中。具体来说:
        • process:是一个句柄,它指向您要写入内存的目标进程。这个句柄通常是通过调用 OpenProcess 函数获得的。
        • mem:是目标进程中的内存地址,您要将数据写入这个地址。这个地址之前是通过调用 VirtualAllocEx 动态分配得到的。
        • dllPath:是一个指向宽字符字符串(wchar_t*)的指针,即您要写入目标进程内存的数据。在这种情况下,它通常指向一个DLL文件的路径。
        • wcslen(dllPath) * sizeof(wchar_t):计算 dllPath 字符串的字节大小,以确保写入正确的数据量。wcslen 函数计算字符串中的字符数(不包括最后的空字符),而 sizeof(wchar_t) 获取宽字符的大小(通常是2个字节)。两者相乘得到整个宽字符串所占用的字节大小。
        • NULL:这是一个可选的参数,用于接收实际写入的字节数的指针。在这里用 NULL 代替,意味着我们不需要这个信息。
        6. 然后创建远程线程,调用DLL文件:
        HANDLE thread = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)LoadLibraryW, mem, 0, NULL);
        这行代码通过调用 Windows API 函数 CreateRemoteThread 在目标进程中创建一个新线程。
        具体来说:
          • 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 表示我们不需要线程标识符。
          7. 然后就等待执行结束:
          WaitForSingleObject(thread, INFINITE);
          这行代码调用 WaitForSingleObject 函数,它是Windows API中的一个同步函数,用于等待指定对象(例如线程或事件)的状态变为信号状态(意味着对象的指定事件已发生)。
          详细解释如下:
            • thread:是之前通过 CreateRemoteThread 创建的新线程的句柄。它标识了一个具体的线程对象,WaitForSingleObject 将会对这个线程的状态进行监视。
            • INFINITE:这个参数指定了函数的等待时间。INFINITE 是一个特殊的值,表示函数将无限期地等待,直到被监视的对象发出信号。换句话说,如果你传递 INFINITE 给 WaitForSingleObject,函数将会一直阻塞调用线程,直到目标线程结束。
            这行代码的使用场景是:在你使用 CreateRemoteThread 函数并且开始一个新线程以在远程进程中执行 LoadLibrary 函数加载DLL之后,你需要等待这个DLL加载操作完成。使用 WaitForSingleObject 可以确保在DLL加载(或尝试加载)完成前,执行DLL注入代码的进程不会继续执行或退出。

            复现
            ConsoleApplication3.exe 14120 Dll_01_hello.dll

            【权限维持技术】Windows DLL远程线程注入

            总结
            本文介绍了DLL注入技术,并用代码实现了最常见的DLL注入技术——远程线程注入,最终复现成功。DLL注入技术是木马绕杀软领域非常重要的技术,掌握DLL注入技术对于权限维持、入侵检测意义重大。

            原文始发于微信公众号(赛博安全狗):【权限维持技术】Windows DLL远程线程注入

            • 左青龙
            • 微信扫一扫
            • weinxin
            • 右白虎
            • 微信扫一扫
            • weinxin
            admin
            • 本文由 发表于 2023年12月9日16:08:29
            • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                             【权限维持技术】Windows DLL远程线程注入http://cn-sec.com/archives/2283191.html

            发表评论

            匿名网友 填写信息