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

简介: 远程线程注入是最常用的一种注入技术,在应用层注入是通过`CreateRemoteThread`这个函数实现的,该函数通过创建线程并调用 `LoadLibrary` 动态载入指定的DLL来实现注入,而在内核层同样存在一个类似的内核函数`RtlCreateUserThread`,但需要注意的是此函数未被公开,`RtlCreateUserThread`其实是对`NtCreateThreadEx`的包装,但最终会调用`ZwCreateThread`来实现注入,`RtlCreateUserThread`是`CreateRemoteThread`的底层实现。

远程线程注入是最常用的一种注入技术,在应用层注入是通过CreateRemoteThread这个函数实现的,该函数通过创建线程并调用 LoadLibrary 动态载入指定的DLL来实现注入,而在内核层同样存在一个类似的内核函数RtlCreateUserThread,但需要注意的是此函数未被公开,RtlCreateUserThread其实是对NtCreateThreadEx的包装,但最终会调用ZwCreateThread来实现注入,RtlCreateUserThreadCreateRemoteThread的底层实现。

基于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用于对函数传递参数,具体的函数原型如下所示;

typedef DWORD(WINAPI* pRtlCreateUserThread)(
    IN HANDLE                    ProcessHandle,          // 进程句柄
    IN PSECURITY_DESCRIPTOR      SecurityDescriptor,
    IN BOOL                      CreateSuspended,
    IN ULONG                     StackZeroBits,
    IN OUT PULONG                StackReserved,
    IN OUT PULONG                StackCommit,
    IN LPVOID                    StartAddress,          // 执行函数地址 LoadLibraryW
    IN LPVOID                    StartParameter,        // 参数传递
    OUT HANDLE                   ThreadHandle,          // 线程句柄
    OUT LPVOID                   ClientID
    );

由于我们加载DLL使用的是LoadLibraryW函数,此函数在运行时只需要一个参数,我们可以将DLL的路径传递进去,并调用LoadLibraryW以此来将特定模块拉起,该函数的定义规范如下所示;

HMODULE LoadLibraryW(
  [in] LPCWSTR lpLibFileName
);

根据上一篇文章中针对注入头文件lyshark.h的封装,本章将继续使用这个头文件中的函数,首先我们实现这样一个功能,将一段准备好的UCHAR字符串动态的写出到应用层进程内存,并以宽字节模式写出在对端内存中,这段代码可以写为如下样子;

// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com

#include "lyshark.h"

// 驱动卸载例程
VOID UnDriver(PDRIVER_OBJECT driver)
{
   
   
    DbgPrint("Uninstall Driver \n");
}

// 驱动入口地址
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
   
   
    DbgPrint("Hello LyShark \n");

    DWORD process_id = 7112;
    DWORD create_size = 1024;
    DWORD64 ref_address = 0;

    // 分配内存堆 《内核远程堆分配与销毁》 核心代码
    NTSTATUS Status = AllocMemory(process_id, create_size, &ref_address);

    DbgPrint("对端进程: %d \n", process_id);
    DbgPrint("分配长度: %d \n", create_size);
    DbgPrint("[*] 分配内核堆基址: %p \n", ref_address);

    UCHAR DllPath[256] = "C:\\hook.dll";
    UCHAR Item[256] = {
   
    0 };

    // 将字节转为双字
    for (int x = 0, y = 0; x < strlen(DllPath) * 2; x += 2, y++)
    {
   
   
        Item[x] = DllPath[y];
    }

    // 写出内存 《内核MDL读写进程内存》 核心代码
    ReadMemoryStruct ptr;

    ptr.pid = process_id;
    ptr.address = ref_address;
    ptr.size = strlen(DllPath) * 2;

    // 需要写入的数据
    ptr.data = ExAllocatePool(PagedPool, ptr.size);

    // 循环设置
    for (int i = 0; i < ptr.size; i++)
    {
   
   
        ptr.data[i] = Item[i];
    }

    // 写内存
    MDLWriteMemory(&ptr);

    Driver->DriverUnload = UnDriver;
    return STATUS_SUCCESS;
}

运行如上方所示的代码,将会在目标进程7112中开辟一段内存空间,并写出C:\hook.dll字符串,运行效果图如下所示;

image.png

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

image.png

继续实现所需要的子功能,实现动态获取Kernel32.dll模块里面LiadLibraryW这个导出函数的内存地址,这段代码相信你可以很容易的写出来,根据上节课的知识点我们可以二次封装一个GetProcessAddress来实现对特定模块基址的获取功能,如下是完整代码案例;

// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com

#include "lyshark.h"

// 实现取模块基址
PVOID GetProcessAddress(HANDLE ProcessID, PWCHAR DllName, PCCHAR FunctionName)
{
   
   
    PEPROCESS EProcess = NULL;
    NTSTATUS Status = STATUS_SUCCESS;
    KAPC_STATE ApcState;
    PVOID RefAddress = 0;

    // 根据PID得到进程EProcess结构
    Status = PsLookupProcessByProcessId(ProcessID, &EProcess);
    if (Status != STATUS_SUCCESS)
    {
   
   
        return Status;
    }

    // 判断目标进程是32位还是64位
    BOOLEAN IsWow64 = (PsGetProcessWow64Process(EProcess) != NULL) ? TRUE : FALSE;

    // 验证地址是否可读
    if (!MmIsAddressValid(EProcess))
    {
   
   
        return NULL;
    }

    // 将当前线程连接到目标进程的地址空间(附加进程)
    KeStackAttachProcess((PRKPROCESS)EProcess, &ApcState);

    __try
    {
   
   
        UNICODE_STRING DllUnicodeString = {
   
    0 };
        PVOID BaseAddress = NULL;

        // 得到进程内模块基地址
        RtlInitUnicodeString(&DllUnicodeString, DllName);

        BaseAddress = GetUserModuleAddress(EProcess, &DllUnicodeString, IsWow64);

        if (!BaseAddress)
        {
   
   
            return NULL;
        }

        DbgPrint("[*] 模块基址: %p \n", BaseAddress);

        // 得到该函数地址
        RefAddress = GetModuleExportAddress(BaseAddress, FunctionName, EProcess);
        DbgPrint("[*] 函数地址: %p \n", RefAddress);
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
   
   
        return NULL;
    }

    // 取消附加
    KeUnstackDetachProcess(&ApcState);
    return RefAddress;
}

VOID Unload(PDRIVER_OBJECT pDriverObj)
{
   
   
    DbgPrint("[-] 驱动卸载 \n");
}

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegPath)
{
   
   
    DbgPrint("Hello LyShark.com \n");

    // 取模块基址
    PVOID pLoadLibraryW = GetProcessAddress(5200, L"kernel32.dll", "LoadLibraryW");

    DbgPrint("[*] 所在内存地址 = %p \n", pLoadLibraryW);

    DriverObject->DriverUnload = Unload;
    return STATUS_SUCCESS;
}

编译并运行如上驱动代码,将自动获取PID=5200进程中Kernel32.dll模块内的LoadLibraryW的内存地址,输出效果图如下所示;

image.png

实现注入的最后一步就是调用自定义函数MyCreateRemoteThread该函数实现原理是调用RtlCreateUserThread开线程执行,这段代码的最终实现如下所示;

// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com

#include "lyshark.h"

// 定义函数指针
typedef PVOID(NTAPI* PfnRtlCreateUserThread)
(
    IN HANDLE ProcessHandle,
    IN PSECURITY_DESCRIPTOR SecurityDescriptor,
    IN BOOLEAN CreateSuspended,
    IN ULONG StackZeroBits,
    IN OUT size_t StackReserved,
    IN OUT size_t StackCommit,
    IN PVOID StartAddress,
    IN PVOID StartParameter,
    OUT PHANDLE ThreadHandle,
    OUT PCLIENT_ID ClientID
);

// 实现取模块基址
PVOID GetProcessAddress(HANDLE ProcessID, PWCHAR DllName, PCCHAR FunctionName)
{
   
   
    PEPROCESS EProcess = NULL;
    NTSTATUS Status = STATUS_SUCCESS;
    KAPC_STATE ApcState;
    PVOID RefAddress = 0;

    // 根据PID得到进程EProcess结构
    Status = PsLookupProcessByProcessId(ProcessID, &EProcess);
    if (Status != STATUS_SUCCESS)
    {
   
   
        return Status;
    }

    // 判断目标进程是32位还是64位
    BOOLEAN IsWow64 = (PsGetProcessWow64Process(EProcess) != NULL) ? TRUE : FALSE;

    // 验证地址是否可读
    if (!MmIsAddressValid(EProcess))
    {
   
   
        return NULL;
    }

    // 将当前线程连接到目标进程的地址空间(附加进程)
    KeStackAttachProcess((PRKPROCESS)EProcess, &ApcState);

    __try
    {
   
   
        UNICODE_STRING DllUnicodeString = {
   
    0 };
        PVOID BaseAddress = NULL;

        // 得到进程内模块基地址
        RtlInitUnicodeString(&DllUnicodeString, DllName);

        BaseAddress = GetUserModuleAddress(EProcess, &DllUnicodeString, IsWow64);

        if (!BaseAddress)
        {
   
   
            return NULL;
        }

        DbgPrint("[*] 模块基址: %p \n", BaseAddress);

        // 得到该函数地址
        RefAddress = GetModuleExportAddress(BaseAddress, FunctionName, EProcess);
        DbgPrint("[*] 函数地址: %p \n", RefAddress);
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
   
   
        return NULL;
    }

    // 取消附加
    KeUnstackDetachProcess(&ApcState);
    return RefAddress;
}

// 远程线程注入函数
BOOLEAN MyCreateRemoteThread(ULONG pid, PVOID pRing3Address, PVOID PParam)
{
   
   
    NTSTATUS status = STATUS_UNSUCCESSFUL;
    PEPROCESS pEProcess = NULL;
    KAPC_STATE ApcState = {
   
    0 };

    PfnRtlCreateUserThread RtlCreateUserThread = NULL;
    HANDLE hThread = 0;

    __try
    {
   
   
        // 获取RtlCreateUserThread函数的内存地址
        UNICODE_STRING ustrRtlCreateUserThread;
        RtlInitUnicodeString(&ustrRtlCreateUserThread, L"RtlCreateUserThread");
        RtlCreateUserThread = (PfnRtlCreateUserThread)MmGetSystemRoutineAddress(&ustrRtlCreateUserThread);
        if (RtlCreateUserThread == NULL)
        {
   
   
            return FALSE;
        }

        // 根据进程PID获取进程EProcess结构
        status = PsLookupProcessByProcessId((HANDLE)pid, &pEProcess);
        if (!NT_SUCCESS(status))
        {
   
   
            return FALSE;
        }

        // 附加到目标进程内
        KeStackAttachProcess(pEProcess, &ApcState);

        // 验证进程是否可读写
        if (!MmIsAddressValid(pRing3Address))
        {
   
   
            return FALSE;
        }

        // 启动注入线程
        status = RtlCreateUserThread(ZwCurrentProcess(),
            NULL,
            FALSE,
            0,
            0,
            0,
            pRing3Address,
            PParam,
            &hThread,
            NULL);
        if (!NT_SUCCESS(status))
        {
   
   
            return FALSE;
        }

        return TRUE;
    }

    __finally
    {
   
   
        // 释放对象
        if (pEProcess != NULL)
        {
   
   
            ObDereferenceObject(pEProcess);
            pEProcess = NULL;
        }

        // 取消附加进程
        KeUnstackDetachProcess(&ApcState);
    }

    return FALSE;
}

VOID Unload(PDRIVER_OBJECT pDriverObj)
{
   
   
    DbgPrint("[-] 驱动卸载 \n");
}

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegPath)
{
   
   
    DbgPrint("Hello LyShark.com \n");

    ULONG process_id = 5200;
    DWORD create_size = 1024;
    DWORD64 ref_address = 0;

    // -------------------------------------------------------
    // 取模块基址
    // -------------------------------------------------------

    PVOID pLoadLibraryW = GetProcessAddress(process_id, L"kernel32.dll", "LoadLibraryW");
    DbgPrint("[*] 所在内存地址 = %p \n", pLoadLibraryW);

    // -------------------------------------------------------
    // 应用层开堆
    // -------------------------------------------------------

    NTSTATUS Status = AllocMemory(process_id, create_size, &ref_address);

    DbgPrint("对端进程: %d \n", process_id);
    DbgPrint("分配长度: %d \n", create_size);
    DbgPrint("分配的内核堆基址: %p \n", ref_address);

    // 设置注入路径,转换为多字节
    UCHAR DllPath[256] = "C:\\lyshark_hook.dll";
    UCHAR Item[256] = {
   
    0 };

    for (int x = 0, y = 0; x < strlen(DllPath) * 2; x += 2, y++)
    {
   
   
        Item[x] = DllPath[y];
    }

    // -------------------------------------------------------
    // 写出数据到内存
    // -------------------------------------------------------

    ReadMemoryStruct ptr;

    ptr.pid = process_id;
    ptr.address = ref_address;
    ptr.size = strlen(DllPath) * 2;

    // 需要写入的数据
    ptr.data = ExAllocatePool(PagedPool, ptr.size);

    // 循环设置
    for (int i = 0; i < ptr.size; i++)
    {
   
   
        ptr.data[i] = Item[i];
    }

    // 写内存
    MDLWriteMemory(&ptr);

    // -------------------------------------------------------
    // 执行开线程函数
    // -------------------------------------------------------

    // 执行线程注入
    // 参数1:PID
    // 参数2:LoadLibraryW内存地址
    // 参数3:当前DLL路径
    BOOLEAN flag = MyCreateRemoteThread(process_id, pLoadLibraryW, ref_address);
    if (flag == TRUE)
    {
   
   
        DbgPrint("[*] 已完成进程 %d 注入文件 %s \n", process_id, DllPath);
    }

    DriverObject->DriverUnload = Unload;
    return STATUS_SUCCESS;
}

编译这段驱动程序,并将其放入虚拟机中,在C盘下面放置好一个名为lyshark_hook.dll文件,运行驱动程序将自动插入DLL到Win32Project进程内,输出效果图如下所示;

image.png

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

image.png

相关文章
|
25天前
|
XML C# 数据格式
掌握了在Windows平台上查看DLL依赖的方法
掌握了在Windows平台上查看DLL依赖的方法
168 4
|
6月前
|
存储 Java C语言
Windows 下 JNI 调用动态链接库 dll
Windows 下 JNI 调用动态链接库 dll
160 0
|
安全 虚拟化 Windows
Windows x64内核下注入DLL姿势之一
讲述了Windows x64内核下注入DLL的思路与流程
|
4月前
|
安全 数据处理 C++
LabVIEW调用外部DLL(动态链接库)
LabVIEW调用外部DLL(动态链接库)
26 0
|
6月前
|
存储 Java C++
Windows 下 JNA 调用动态链接库 dll
Windows 下 JNA 调用动态链接库 dll
118 0
|
6月前
|
开发框架 .NET
LabVIEW调用动态链接库DLL
LabVIEW调用动态链接库DLL
60 0
|
C++ Windows
C++ --- Dll文件的生成与调用(二)之动态库注入技术
C++ --- Dll文件的生成与调用(二)之动态库注入技术
155 0
|
网络协议 安全 API
驱动开发:内核远程线程实现DLL注入
在笔者上一篇文章`《内核RIP劫持实现DLL注入》`介绍了通过劫持RIP指针控制程序执行流实现插入DLL的目的,本章将继续探索全新的注入方式,通过`NtCreateThreadEx`这个内核函数实现注入DLL的目的,需要注意的是该函数在微软系统中未被导出使用时需要首先得到该函数的入口地址,`NtCreateThreadEx`函数最终会调用`ZwCreateThread`,本章在寻找函数的方式上有所不同,前一章通过内存定位的方法得到所需地址,本章则是通过解析导出表实现。
4944 0
|
开发框架 .NET Java
C#下反射动态加载dll后如何卸载?
C#下反射动态加载dll后如何卸载?
|
Shell
驱动开发:内核ShellCode线程注入
还记得`《驱动开发:内核LoadLibrary实现DLL注入》`中所使用的注入技术吗,我们通过`RtlCreateUserThread`函数调用实现了注入DLL到应用层并执行,本章将继续探索一个简单的问题,如何注入`ShellCode`代码实现反弹Shell,这里需要注意一般情况下`RtlCreateUserThread`需要传入两个最重要的参数,一个是`StartAddress`开始执行的内存块,另一个是`StartParameter`传入内存块的变量列表,而如果将`StartParameter`地址填充为`NULL`则表明不传递任何参数,也就是只在线程中执行`ShellCode`代码,利用
379 1