驱动开发:内核远程堆分配与销毁

简介: 在开始学习内核内存读写篇之前,我们先来实现一个简单的内存分配销毁堆的功能,在内核空间内用户依然可以动态的申请与销毁一段可控的堆空间,一般而言内核中提供了`ZwAllocateVirtualMemory`这个函数用于专门分配虚拟空间,而与之相对应的则是`ZwFreeVirtualMemory`此函数则用于销毁堆内存,当我们需要分配内核空间时往往需要切换到对端进程栈上再进行操作,接下来`LyShark`将从API开始介绍如何运用这两个函数实现内存分配与使用,并以此来作为驱动读写篇的入门知识。

在开始学习内核内存读写篇之前,我们先来实现一个简单的内存分配销毁堆的功能,在内核空间内用户依然可以动态的申请与销毁一段可控的堆空间,一般而言内核中提供了ZwAllocateVirtualMemory这个函数用于专门分配虚拟空间,而与之相对应的则是ZwFreeVirtualMemory此函数则用于销毁堆内存,当我们需要分配内核空间时往往需要切换到对端进程栈上再进行操作,接下来LyShark将从API开始介绍如何运用这两个函数实现内存分配与使用,并以此来作为驱动读写篇的入门知识。

首先以内存分配为例ZwAllocateVirtualMemory()函数,该系列函数在ntifs.h头文件内,且如果需要使用则最好提前在程序头部进行声明,该函数的微软官方定义如下所示;

NTSYSAPI NTSTATUS ZwAllocateVirtualMemory(
  [in]      HANDLE    ProcessHandle,           // 进程句柄
  [in, out] PVOID     *BaseAddress,            // 指向将接收已分配页面区域基址的变量的指针
  [in]      ULONG_PTR ZeroBits,                // 节视图基址中必须为零的高顺序地址位数
  [in, out] PSIZE_T   RegionSize,              // 指向将接收已分配页面区域的实际大小
  [in]      ULONG     AllocationType,          // 包含指定要执行的分配类型的标志的位掩码
  [in]      ULONG     Protect                  // 包含页面保护标志的位掩码
);

参数ProcessHandle用于传入一个进程句柄此处我们可以通过NtCurrentProcess()获取到当前自身进程的句柄。

参数BaseAddress则用于接收分配堆地址的首地址,此处指向将接收已分配页面区域基址的变量的指针。

参数RegionSize则用于指定需要分配的内存空间大小,此参数的初始值指定区域的大小(以字节为单位)并向上舍入到下一个主机页大小边界。

参数AllocationType用于指定分配内存的属性,通常我们会用到的只有两种MEM_COMMIT指定为提交类型,MEM_PHYSICAL则用于分配物理内存,此标志仅用于地址窗口扩展AWE内存。 如果设置了MEM_PHYSICAL则还必须设置MEM_RESERVE不能设置其他标志,并且必须将保护设置为PAGE_READWRITE

参数Protect用于设置当前分批堆的保护属性,通常当我们需要分配一段可执行指令的内存空间时会使用PAGE_EXECUTE_READWRITE,如果无执行需求则推荐使用PAGE_READWRITE属性。

在对特定进程分配堆时第一步就是要切入到该进程的进程栈中,此时可通过KeStackAttachProcess()切换到进程栈,于此对应的是KeUnstackDetachProcess()脱离进程栈,这两个函数的具体定义如下;

// 附加到进程栈
void KeStackAttachProcess(
        PRKPROCESS   PROCESS,      // 传入EProcess结构
  [out] PRKAPC_STATE ApcState      // 指向KAPC_STATE结构的不透明指针
);
// 接触附加
void KeUnstackDetachProcess(
  [in] PRKAPC_STATE ApcState       // 指向KAPC_STATE结构的不透明指针
);

此处如果需要附加进程栈则必须提供该进程的PRKPROCESS也就是EProcess结构,此结构可通过PsLookupProcessByProcessId()获取到,该函数接收一个进程PID并将此PID转为EProcess结构,函数定义如下;

NTSTATUS PsLookupProcessByProcessId(
  [in]  HANDLE    ProcessId,          // 进程PID
  [out] PEPROCESS *Process            // 输出EP结构
);

基本的函数介绍完了,那么这段代码应该不难理解了,如下代码中需要注意一点,参数OUT PVOID Buffer用于输出堆地址而不是输入地址。

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

#include <ntifs.h>
#include <windef.h>

// 定义声明
NTSTATUS ZwAllocateVirtualMemory(
    __in HANDLE  ProcessHandle,
    __inout PVOID  *BaseAddress,
    __in ULONG_PTR  ZeroBits,
    __inout PSIZE_T  RegionSize,
    __in ULONG  AllocationType,
    __in ULONG  Protect
);

// 分配内存空间
NTSTATUS AllocMemory(IN ULONG ProcessPid, IN SIZE_T Length, OUT PVOID Buffer)
{
    NTSTATUS Status = STATUS_SUCCESS;
    PEPROCESS pEProcess = NULL;
    KAPC_STATE ApcState = { 0 };
    PVOID BaseAddress = NULL;

    // 通过进程PID得到进程EProcess
    Status = PsLookupProcessByProcessId((HANDLE)ProcessPid, &pEProcess);
    if (!NT_SUCCESS(Status) && !MmIsAddressValid(pEProcess))
    {
        return STATUS_UNSUCCESSFUL;
    }

    // 验证内存可读
    if (!MmIsAddressValid(pEProcess))
    {
        return STATUS_UNSUCCESSFUL;
    }
    __try
    {
        // 附加到进程栈
        KeStackAttachProcess(pEProcess, &ApcState);

        // 分配内存
        Status = ZwAllocateVirtualMemory(NtCurrentProcess(), &BaseAddress, 0, &Length, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
        RtlZeroMemory(BaseAddress, Length);

        // 返回内存地址
        *(PVOID*)Buffer = BaseAddress;

        // 脱离进程栈
        KeUnstackDetachProcess(&ApcState);
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        KeUnstackDetachProcess(&ApcState);
        Status = STATUS_UNSUCCESSFUL;
    }

    ObDereferenceObject(pEProcess);
    return Status;
}

VOID UnDriver(PDRIVER_OBJECT driver)
{
    DbgPrint(("Uninstall Driver Is OK \n"));
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
    DbgPrint(("hello lyshark \n"));

    DWORD process_id = 4160;
    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);

    Driver->DriverUnload = UnDriver;
    return STATUS_SUCCESS;
}

运行如上代码片段,则会在进程PID=4160中开辟一段堆空间,输出效果如下;

image.png

与创建堆相对ZwFreeVirtualMemory()则用于销毁一个堆,其微软定义如下所示;

NTSYSAPI NTSTATUS ZwFreeVirtualMemory(
  [in]      HANDLE  ProcessHandle,    // 进程句柄
  [in, out] PVOID   *BaseAddress,     // 堆基址
  [in, out] PSIZE_T RegionSize,       // 销毁长度
  [in]      ULONG   FreeType          // 释放类型
);

相对于创建来说,销毁堆则必须传入堆空间BaseAddress基址,以及堆空间的RegionSize长度,需要格外注意FreeType参数,这是一个位掩码,其中包含描述ZwFreeVirtualMemory将为页面指定区域执行的任意操作类型。如果传入MEM_DECOMMIT则将取消提交页面的指定区域,页面进入保留状态。而如果设置为MEM_RELEASE则堆地址将被立即释放。

销毁堆空间FreeMemory()的完整代码如下所示,销毁是我们使用MEM_RELEASE参数即立即销毁。

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

#include <ntifs.h>
#include <windef.h>

// 申请堆
NTSTATUS ZwAllocateVirtualMemory(
    __in HANDLE  ProcessHandle,
    __inout PVOID  *BaseAddress,
    __in ULONG_PTR  ZeroBits,
    __inout PSIZE_T  RegionSize,
    __in ULONG  AllocationType,
    __in ULONG  Protect
);

// 销毁堆
NTSYSAPI NTSTATUS ZwFreeVirtualMemory(
    __in      HANDLE  ProcessHandle,
    __inout PVOID   *BaseAddress,
    __inout PSIZE_T RegionSize,
    __in      ULONG   FreeType
);

// 分配内存空间
NTSTATUS AllocMemory(IN ULONG ProcessPid, IN SIZE_T Length, OUT PVOID Buffer)
{
    NTSTATUS Status = STATUS_SUCCESS;
    PEPROCESS pEProcess = NULL;
    KAPC_STATE ApcState = { 0 };
    PVOID BaseAddress = NULL;

    // 通过进程PID得到进程EProcess
    Status = PsLookupProcessByProcessId((HANDLE)ProcessPid, &pEProcess);
    if (!NT_SUCCESS(Status) && !MmIsAddressValid(pEProcess))
    {
        return STATUS_UNSUCCESSFUL;
    }

    // 验证内存可读
    if (!MmIsAddressValid(pEProcess))
    {
        return STATUS_UNSUCCESSFUL;
    }
    __try
    {
        // 附加到进程栈
        KeStackAttachProcess(pEProcess, &ApcState);

        // 分配内存
        Status = ZwAllocateVirtualMemory(NtCurrentProcess(), &BaseAddress, 0, &Length, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
        RtlZeroMemory(BaseAddress, Length);

        // 返回内存地址
        *(PVOID*)Buffer = BaseAddress;

        // 脱离进程栈
        KeUnstackDetachProcess(&ApcState);
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        KeUnstackDetachProcess(&ApcState);
        Status = STATUS_UNSUCCESSFUL;
    }

    ObDereferenceObject(pEProcess);
    return Status;
}

// 注销内存空间
NTSTATUS FreeMemory(IN ULONG ProcessPid, IN SIZE_T Length, IN PVOID BaseAddress)
{
    NTSTATUS Status = STATUS_SUCCESS;
    PEPROCESS pEProcess = NULL;
    KAPC_STATE ApcState = { 0 };

    Status = PsLookupProcessByProcessId((HANDLE)ProcessPid, &pEProcess);
    if (!NT_SUCCESS(Status) && !MmIsAddressValid(pEProcess))
    {
    return STATUS_UNSUCCESSFUL;
    }

    if (!MmIsAddressValid(pEProcess))
    {
        return STATUS_UNSUCCESSFUL;
    }

    __try
    {
        // 附加到进程栈
        KeStackAttachProcess(pEProcess, &ApcState);

        // 释放内存
        Status = ZwFreeVirtualMemory(NtCurrentProcess(), &BaseAddress, &Length, MEM_RELEASE);

        // 脱离进程栈
        KeUnstackDetachProcess(&ApcState);
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        KeUnstackDetachProcess(&ApcState);
        Status = STATUS_UNSUCCESSFUL;
    }

    ObDereferenceObject(pEProcess);
    return Status;
}

VOID UnDriver(PDRIVER_OBJECT driver)
{
    DbgPrint(("Uninstall Driver Is OK \n"));
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
    DbgPrint(("hello lyshark \n"));

    DWORD process_id = 4160;
    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);

    Status = FreeMemory(process_id, create_size, ref_address);
    DbgPrint("销毁堆地址: %p \n", ref_address);

    Driver->DriverUnload = UnDriver;
    return STATUS_SUCCESS;
}

编译并运行如上这段代码,代码会首先调用AllocMemory()函数实现分配堆,然后调用FreeMemory()函数销毁堆,并输出销毁地址,如下图所示;

image.png

相关文章
|
6月前
|
监控 安全 Unix
进程回收的实现方式与注意事项:Linux C/C中的回收机制
进程回收的实现方式与注意事项:Linux C/C中的回收机制
304 1
|
6月前
|
存储 机器学习/深度学习 算法
内存学习(六):引导内存分配器(初始化)
内存学习(六):引导内存分配器(初始化)
95 0
|
3月前
|
Ubuntu Linux 数据安全/隐私保护
内核实验(七):使用内核KFIFO环形缓冲区机制
本文通过一个内核模块实验,演示了如何在Linux内核中使用KFIFO环形缓冲区机制,包括定义KFIFO、编写驱动程序以及在Qemu虚拟机中进行编译、部署和测试,展示了KFIFO在无需额外加锁的情况下如何安全地在读者和写者线程间进行数据传输。
100 0
内核实验(七):使用内核KFIFO环形缓冲区机制
|
5月前
|
存储 Java C++
Java虚拟机(JVM)管理内存划分为多个区域:程序计数器记录线程执行位置;虚拟机栈存储线程私有数据
Java虚拟机(JVM)管理内存划分为多个区域:程序计数器记录线程执行位置;虚拟机栈存储线程私有数据,如局部变量和操作数;本地方法栈支持native方法;堆存放所有线程的对象实例,由垃圾回收管理;方法区(在Java 8后变为元空间)存储类信息和常量;运行时常量池是方法区一部分,保存符号引用和常量;直接内存非JVM规范定义,手动管理,通过Buffer类使用。Java 8后,永久代被元空间取代,G1成为默认GC。
60 2
|
6月前
|
存储 Linux 程序员
【Linux C/C++ 堆内存分布】深入理解Linux进程的堆空间管理
【Linux C/C++ 堆内存分布】深入理解Linux进程的堆空间管理
322 0
|
12月前
|
API Windows
3.1 Windows驱动开发:内核远程堆分配与销毁
在开始学习内核内存读写篇之前,我们先来实现一个简单的内存分配销毁堆的功能,在内核空间内用户依然可以动态的申请与销毁一段可控的堆空间,一般而言内核中提供了`ZwAllocateVirtualMemory`这个函数用于专门分配虚拟空间,而与之相对应的则是`ZwFreeVirtualMemory`此函数则用于销毁堆内存,当我们需要分配内核空间时往往需要切换到对端进程栈上再进行操作,接下来`LyShark`将从API开始介绍如何运用这两个函数实现内存分配与使用,并以此来作为驱动读写篇的入门知识。
42 0
3.1 Windows驱动开发:内核远程堆分配与销毁
|
存储 缓存 Java
深入理解Java虚拟机_自动内存分配管理_01
深入理解Java虚拟机_自动内存分配管理_01
106 0
|
算法 程序员 调度
3.1操作系统(内存管理的概念 分配与回收 空间的扩充)
一.内存 1.什么是内存?有何作用? 几个常用的数量单位 2.进程运行的基本原理 1. 指令的工作原理 2.逻辑地址vs物理地址 3.从写程序到程序运行 4.装入的三种方式 1.绝对装入 2. 可重定位装入(静态重定位) 3. 动态运行时装入(动态重定位) 5.链接的三种方式 1. 静态链接 2. 装入时动态链接 3. 运行时动态链接 二、内存管理的概念 1.内存空间的分配与回收 1.单一连续分配 2. 固定分区分配 3. 动态分区分配 (1)系统要用什么样的数据结构记录内存的使用情况? (2)当很多个空闲分区都能满足需求时,应该选择哪个分区
3.1操作系统(内存管理的概念 分配与回收 空间的扩充)
|
算法 安全
操作系统第五章_04 设备的分配与回收
操作系统第五章_04 设备的分配与回收
491 0
操作系统第五章_04 设备的分配与回收
|
Linux 调度
【操作系统】进程的创建与销毁
【操作系统】进程的创建与销毁
【操作系统】进程的创建与销毁