找回密码
 立即注册
首页 业界区 业界 驱动开发:内核LoadLibrary实现DLL注入

驱动开发:内核LoadLibrary实现DLL注入

明思义 2025-6-6 20:00:55
远程线程注入是最常用的一种注入技术,在应用层注入是通过CreateRemoteThread这个函数实现的,该函数通过创建线程并调用 LoadLibrary 动态载入指定的DLL来实现注入,而在内核层同样存在一个类似的内核函数RtlCreateUserThread,但需要注意的是此函数未被公开,RtlCreateUserThread其实是对NtCreateThreadEx的包装,但最终会调用ZwCreateThread来实现注入,RtlCreateUserThread是CreateRemoteThread的底层实现。
基于LoadLibrary实现的注入原理可以具体分为如下几步;

  • 1.调用AllocMemory,在对端应用层开辟空间,函数封装来源于《内核远程堆分配与销毁》章节;
  • 2.调用MDLWriteMemory,将DLL路径字符串写出到对端内存,函数封装来源于《内核MDL读写进程内存》章节;
  • 3.调用GetUserModuleAddress,获取到kernel32.dll模块基址,函数封装来源于《内核远程线程实现DLL注入》章节;
  • 4.调用GetModuleExportAddress,获取到LoadLibraryW函数的内存地址,函数封装来源于《内核远程线程实现DLL注入》章节;
  • 5.最后调用本章封装函数MyCreateRemoteThread,将应用层DLL动态转载到进程内,实现DLL注入;
总结起来就是首先在目标进程申请一块空间,空间里面写入要注入的DLL的路径字符串或者是一段ShellCode,找到该内存中LoadLibrary的基址并传入到RtlCreateUserThread中,此时进程自动加载我们指定路径下的DLL文件。
注入依赖于RtlCreateUserThread这个未到处内核函数,该内核函数中最需要关心的参数是ProcessHandle用于接收进程句柄,StartAddress接收一个函数地址,StartParameter用于对函数传递参数,具体的函数原型如下所示;
  1. typedef DWORD(WINAPI* pRtlCreateUserThread)(
  2.     IN HANDLE                    ProcessHandle,          // 进程句柄
  3.     IN PSECURITY_DESCRIPTOR      SecurityDescriptor,
  4.     IN BOOL                      CreateSuspended,
  5.     IN ULONG                     StackZeroBits,
  6.     IN OUT PULONG                StackReserved,
  7.     IN OUT PULONG                StackCommit,
  8.     IN LPVOID                    StartAddress,          // 执行函数地址 LoadLibraryW
  9.     IN LPVOID                    StartParameter,        // 参数传递
  10.     OUT HANDLE                   ThreadHandle,          // 线程句柄
  11.     OUT LPVOID                   ClientID
  12.     );
复制代码
由于我们加载DLL使用的是LoadLibraryW函数,此函数在运行时只需要一个参数,我们可以将DLL的路径传递进去,并调用LoadLibraryW以此来将特定模块拉起,该函数的定义规范如下所示;
  1. HMODULE LoadLibraryW(
  2.   [in] LPCWSTR lpLibFileName
  3. );
复制代码
根据上一篇文章中针对注入头文件lyshark.h的封装,本章将继续使用这个头文件中的函数,首先我们实现这样一个功能,将一段准备好的UCHAR字符串动态的写出到应用层进程内存,并以宽字节模式写出在对端内存中,这段代码可以写为如下样子;
  1. // 署名权
  2. // right to sign one's name on a piece of work
  3. // PowerBy: LyShark
  4. // Email: me@lyshark.com
  5. #include "lyshark.h"
  6. // 驱动卸载例程
  7. VOID UnDriver(PDRIVER_OBJECT driver)
  8. {
  9.         DbgPrint("Uninstall Driver \n");
  10. }
  11. // 驱动入口地址
  12. NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
  13. {
  14.         DbgPrint("Hello LyShark \n");
  15.         DWORD process_id = 7112;
  16.         DWORD create_size = 1024;
  17.         DWORD64 ref_address = 0;
  18.         // 分配内存堆 《内核远程堆分配与销毁》 核心代码
  19.         NTSTATUS Status = AllocMemory(process_id, create_size, &ref_address);
  20.         DbgPrint("对端进程: %d \n", process_id);
  21.         DbgPrint("分配长度: %d \n", create_size);
  22.         DbgPrint("[*] 分配内核堆基址: %p \n", ref_address);
  23.         UCHAR DllPath[256] = "C:\\hook.dll";
  24.         UCHAR Item[256] = { 0 };
  25.         // 将字节转为双字
  26.         for (int x = 0, y = 0; x < strlen(DllPath) * 2; x += 2, y++)
  27.         {
  28.                 Item[x] = DllPath[y];
  29.         }
  30.         // 写出内存 《内核MDL读写进程内存》 核心代码
  31.         ReadMemoryStruct ptr;
  32.         ptr.pid = process_id;
  33.         ptr.address = ref_address;
  34.         ptr.size = strlen(DllPath) * 2;
  35.         // 需要写入的数据
  36.         ptr.data = ExAllocatePool(PagedPool, ptr.size);
  37.         // 循环设置
  38.         for (int i = 0; i < ptr.size; i++)
  39.         {
  40.                 ptr.data[i] = Item[i];
  41.         }
  42.         // 写内存
  43.         MDLWriteMemory(&ptr);
  44.         Driver->DriverUnload = UnDriver;
  45.         return STATUS_SUCCESS;
  46. }
复制代码
运行如上方所示的代码,将会在目标进程7112中开辟一段内存空间,并写出C:\hook.dll字符串,运行效果图如下所示;
1.png

此处你可以通过x64dbg附加到应用层进程内,并观察内存0000000002200000会看到如下字符串已被写出,双字类型则是每一个字符空一格,效果图如下所示;
2.png

继续实现所需要的子功能,实现动态获取Kernel32.dll模块里面LiadLibraryW这个导出函数的内存地址,这段代码相信你可以很容易的写出来,根据上节课的知识点我们可以二次封装一个GetProcessAddress来实现对特定模块基址的获取功能,如下是完整代码案例;
  1. // 署名权
  2. // right to sign one's name on a piece of work
  3. // PowerBy: LyShark
  4. // Email: me@lyshark.com
  5. #include "lyshark.h"
  6. // 实现取模块基址
  7. PVOID GetProcessAddress(HANDLE ProcessID, PWCHAR DllName, PCCHAR FunctionName)
  8. {
  9.         PEPROCESS EProcess = NULL;
  10.         NTSTATUS Status = STATUS_SUCCESS;
  11.         KAPC_STATE ApcState;
  12.         PVOID RefAddress = 0;
  13.         // 根据PID得到进程EProcess结构
  14.         Status = PsLookupProcessByProcessId(ProcessID, &EProcess);
  15.         if (Status != STATUS_SUCCESS)
  16.         {
  17.                 return Status;
  18.         }
  19.         // 判断目标进程是32位还是64位
  20.         BOOLEAN IsWow64 = (PsGetProcessWow64Process(EProcess) != NULL) ? TRUE : FALSE;
  21.         // 验证地址是否可读
  22.         if (!MmIsAddressValid(EProcess))
  23.         {
  24.                 return NULL;
  25.         }
  26.         // 将当前线程连接到目标进程的地址空间(附加进程)
  27.         KeStackAttachProcess((PRKPROCESS)EProcess, &ApcState);
  28.         __try
  29.         {
  30.                 UNICODE_STRING DllUnicodeString = { 0 };
  31.                 PVOID BaseAddress = NULL;
  32.                 // 得到进程内模块基地址
  33.                 RtlInitUnicodeString(&DllUnicodeString, DllName);
  34.                 BaseAddress = GetUserModuleAddress(EProcess, &DllUnicodeString, IsWow64);
  35.                 if (!BaseAddress)
  36.                 {
  37.                         return NULL;
  38.                 }
  39.                 DbgPrint("[*] 模块基址: %p \n", BaseAddress);
  40.                 // 得到该函数地址
  41.                 RefAddress = GetModuleExportAddress(BaseAddress, FunctionName, EProcess);
  42.                 DbgPrint("[*] 函数地址: %p \n", RefAddress);
  43.         }
  44.         __except (EXCEPTION_EXECUTE_HANDLER)
  45.         {
  46.                 return NULL;
  47.         }
  48.         // 取消附加
  49.         KeUnstackDetachProcess(&ApcState);
  50.         return RefAddress;
  51. }
  52. VOID Unload(PDRIVER_OBJECT pDriverObj)
  53. {
  54.         DbgPrint("[-] 驱动卸载 \n");
  55. }
  56. NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegPath)
  57. {
  58.         DbgPrint("Hello LyShark.com \n");
  59.         // 取模块基址
  60.         PVOID pLoadLibraryW = GetProcessAddress(5200, L"kernel32.dll", "LoadLibraryW");
  61.         DbgPrint("[*] 所在内存地址 = %p \n", pLoadLibraryW);
  62.         DriverObject->DriverUnload = Unload;
  63.         return STATUS_SUCCESS;
  64. }
复制代码
编译并运行如上驱动代码,将自动获取PID=5200进程中Kernel32.dll模块内的LoadLibraryW的内存地址,输出效果图如下所示;
3.png

实现注入的最后一步就是调用自定义函数MyCreateRemoteThread该函数实现原理是调用RtlCreateUserThread开线程执行,这段代码的最终实现如下所示;
  1. // 署名权
  2. // right to sign one's name on a piece of work
  3. // PowerBy: LyShark
  4. // Email: me@lyshark.com
  5. #include "lyshark.h"
  6. // 定义函数指针
  7. typedef PVOID(NTAPI* PfnRtlCreateUserThread)
  8. (
  9.         IN HANDLE ProcessHandle,
  10.         IN PSECURITY_DESCRIPTOR SecurityDescriptor,
  11.         IN BOOLEAN CreateSuspended,
  12.         IN ULONG StackZeroBits,
  13.         IN OUT size_t StackReserved,
  14.         IN OUT size_t StackCommit,
  15.         IN PVOID StartAddress,
  16.         IN PVOID StartParameter,
  17.         OUT PHANDLE ThreadHandle,
  18.         OUT PCLIENT_ID ClientID
  19. );
  20. // 实现取模块基址
  21. PVOID GetProcessAddress(HANDLE ProcessID, PWCHAR DllName, PCCHAR FunctionName)
  22. {
  23.         PEPROCESS EProcess = NULL;
  24.         NTSTATUS Status = STATUS_SUCCESS;
  25.         KAPC_STATE ApcState;
  26.         PVOID RefAddress = 0;
  27.         // 根据PID得到进程EProcess结构
  28.         Status = PsLookupProcessByProcessId(ProcessID, &EProcess);
  29.         if (Status != STATUS_SUCCESS)
  30.         {
  31.                 return Status;
  32.         }
  33.         // 判断目标进程是32位还是64位
  34.         BOOLEAN IsWow64 = (PsGetProcessWow64Process(EProcess) != NULL) ? TRUE : FALSE;
  35.         // 验证地址是否可读
  36.         if (!MmIsAddressValid(EProcess))
  37.         {
  38.                 return NULL;
  39.         }
  40.         // 将当前线程连接到目标进程的地址空间(附加进程)
  41.         KeStackAttachProcess((PRKPROCESS)EProcess, &ApcState);
  42.         __try
  43.         {
  44.                 UNICODE_STRING DllUnicodeString = { 0 };
  45.                 PVOID BaseAddress = NULL;
  46.                 // 得到进程内模块基地址
  47.                 RtlInitUnicodeString(&DllUnicodeString, DllName);
  48.                 BaseAddress = GetUserModuleAddress(EProcess, &DllUnicodeString, IsWow64);
  49.                 if (!BaseAddress)
  50.                 {
  51.                         return NULL;
  52.                 }
  53.                 DbgPrint("[*] 模块基址: %p \n", BaseAddress);
  54.                 // 得到该函数地址
  55.                 RefAddress = GetModuleExportAddress(BaseAddress, FunctionName, EProcess);
  56.                 DbgPrint("[*] 函数地址: %p \n", RefAddress);
  57.         }
  58.         __except (EXCEPTION_EXECUTE_HANDLER)
  59.         {
  60.                 return NULL;
  61.         }
  62.         // 取消附加
  63.         KeUnstackDetachProcess(&ApcState);
  64.         return RefAddress;
  65. }
  66. // 远程线程注入函数
  67. BOOLEAN MyCreateRemoteThread(ULONG pid, PVOID pRing3Address, PVOID PParam)
  68. {
  69.         NTSTATUS status = STATUS_UNSUCCESSFUL;
  70.         PEPROCESS pEProcess = NULL;
  71.         KAPC_STATE ApcState = { 0 };
  72.         PfnRtlCreateUserThread RtlCreateUserThread = NULL;
  73.         HANDLE hThread = 0;
  74.         __try
  75.         {
  76.                 // 获取RtlCreateUserThread函数的内存地址
  77.                 UNICODE_STRING ustrRtlCreateUserThread;
  78.                 RtlInitUnicodeString(&ustrRtlCreateUserThread, L"RtlCreateUserThread");
  79.                 RtlCreateUserThread = (PfnRtlCreateUserThread)MmGetSystemRoutineAddress(&ustrRtlCreateUserThread);
  80.                 if (RtlCreateUserThread == NULL)
  81.                 {
  82.                         return FALSE;
  83.                 }
  84.                 // 根据进程PID获取进程EProcess结构
  85.                 status = PsLookupProcessByProcessId((HANDLE)pid, &pEProcess);
  86.                 if (!NT_SUCCESS(status))
  87.                 {
  88.                         return FALSE;
  89.                 }
  90.                 // 附加到目标进程内
  91.                 KeStackAttachProcess(pEProcess, &ApcState);
  92.                 // 验证进程是否可读写
  93.                 if (!MmIsAddressValid(pRing3Address))
  94.                 {
  95.                         return FALSE;
  96.                 }
  97.                 // 启动注入线程
  98.                 status = RtlCreateUserThread(ZwCurrentProcess(),
  99.                         NULL,
  100.                         FALSE,
  101.                         0,
  102.                         0,
  103.                         0,
  104.                         pRing3Address,
  105.                         PParam,
  106.                         &hThread,
  107.                         NULL);
  108.                 if (!NT_SUCCESS(status))
  109.                 {
  110.                         return FALSE;
  111.                 }
  112.                 return TRUE;
  113.         }
  114.         __finally
  115.         {
  116.                 // 释放对象
  117.                 if (pEProcess != NULL)
  118.                 {
  119.                         ObDereferenceObject(pEProcess);
  120.                         pEProcess = NULL;
  121.                 }
  122.                 // 取消附加进程
  123.                 KeUnstackDetachProcess(&ApcState);
  124.         }
  125.         return FALSE;
  126. }
  127. VOID Unload(PDRIVER_OBJECT pDriverObj)
  128. {
  129.         DbgPrint("[-] 驱动卸载 \n");
  130. }
  131. NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegPath)
  132. {
  133.         DbgPrint("Hello LyShark.com \n");
  134.         ULONG process_id = 5200;
  135.         DWORD create_size = 1024;
  136.         DWORD64 ref_address = 0;
  137.         // -------------------------------------------------------
  138.         // 取模块基址
  139.         // -------------------------------------------------------
  140.         PVOID pLoadLibraryW = GetProcessAddress(process_id, L"kernel32.dll", "LoadLibraryW");
  141.         DbgPrint("[*] 所在内存地址 = %p \n", pLoadLibraryW);
  142.         // -------------------------------------------------------
  143.         // 应用层开堆
  144.         // -------------------------------------------------------
  145.         NTSTATUS Status = AllocMemory(process_id, create_size, &ref_address);
  146.         DbgPrint("对端进程: %d \n", process_id);
  147.         DbgPrint("分配长度: %d \n", create_size);
  148.         DbgPrint("分配的内核堆基址: %p \n", ref_address);
  149.         // 设置注入路径,转换为多字节
  150.         UCHAR DllPath[256] = "C:\\lyshark_hook.dll";
  151.         UCHAR Item[256] = { 0 };
  152.         for (int x = 0, y = 0; x < strlen(DllPath) * 2; x += 2, y++)
  153.         {
  154.                 Item[x] = DllPath[y];
  155.         }
  156.         // -------------------------------------------------------
  157.         // 写出数据到内存
  158.         // -------------------------------------------------------
  159.         ReadMemoryStruct ptr;
  160.         ptr.pid = process_id;
  161.         ptr.address = ref_address;
  162.         ptr.size = strlen(DllPath) * 2;
  163.         // 需要写入的数据
  164.         ptr.data = ExAllocatePool(PagedPool, ptr.size);
  165.         // 循环设置
  166.         for (int i = 0; i < ptr.size; i++)
  167.         {
  168.                 ptr.data[i] = Item[i];
  169.         }
  170.         // 写内存
  171.         MDLWriteMemory(&ptr);
  172.         // -------------------------------------------------------
  173.         // 执行开线程函数
  174.         // -------------------------------------------------------
  175.         // 执行线程注入
  176.         // 参数1:PID
  177.         // 参数2:LoadLibraryW内存地址
  178.         // 参数3:当前DLL路径
  179.         BOOLEAN flag = MyCreateRemoteThread(process_id, pLoadLibraryW, ref_address);
  180.         if (flag == TRUE)
  181.         {
  182.                 DbgPrint("[*] 已完成进程 %d 注入文件 %s \n", process_id, DllPath);
  183.         }
  184.         DriverObject->DriverUnload = Unload;
  185.         return STATUS_SUCCESS;
  186. }
复制代码
编译这段驱动程序,并将其放入虚拟机中,在C盘下面放置好一个名为lyshark_hook.dll文件,运行驱动程序将自动插入DLL到Win32Project进程内,输出效果图如下所示;
4.png

回到应用层进程,则可看到如下图所示的注入成功提示信息;
5.png


来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册