在开始学习内核内存读写篇之前,我们先来实现一个简单的内存分配销毁堆的功能,在内核空间内用户依然可以动态的申请与销毁一段可控的堆空间,一般而言内核中提供了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