Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >驱动开发:内核物理内存寻址读写

驱动开发:内核物理内存寻址读写

原创
作者头像
王 瑞
发布于 2023-06-26 01:04:04
发布于 2023-06-26 01:04:04
65800
代码可运行
举报
运行总次数:0
代码可运行

在某些时候我们需要读写的进程可能存在虚拟内存保护机制,在该机制下用户的CR3以及MDL读写将直接失效,从而导致无法读取到正确的数据,本章我们将继续研究如何实现物理级别的寻址读写。

首先,驱动中的物理页读写是指在驱动中直接读写物理内存页(而不是虚拟内存页)。这种方式的优点是它能够更快地访问内存,因为它避免了虚拟内存管理的开销,通过直接读写物理内存,驱动程序可以绕过虚拟内存的保护机制,获得对系统中内存的更高级别的访问权限。

想要实现物理页读写,第一步则是需要找到UserDirectoryTableBase的实际偏移地址,你一定会问这是个什么?别着急,听我来慢慢解释;

在操作系统中,每个进程都有一个KPROCESS结构体,它是进程的内部表示。该结构体中包含了一些重要的信息,包括UserDirectoryTableBase字段,它指向进程的页表目录表(Page Directory Table),也称为DirectoryTable页目录表。

Page Directory Table是一种数据结构,它在虚拟内存管理中起着重要的作用。它被用来存储将虚拟地址映射到物理地址的映射关系,其内部包含了一些指向页表的指针,每个页表中又包含了一些指向物理页面的指针。这些指针一起构成了一个树形结构,它被称为页表树(Page Table Tree)

代码语言:c
代码运行次数:0
运行
AI代码解释
复制
kd> dt _KPROCESS
ntdll!_KPROCESS
+0x278 UserTime         : Uint4B
+0x27c ReadyTime        : Uint4B
+0x280 UserDirectoryTableBase : Uint8B
+0x288 AddressPolicy    : UChar
+0x289 Spare2           : [71] UChar

#define GetDirectoryTableOffset 0x280

UserDirectoryTableBase字段包含了进程的页表树的根节点的物理地址,通过它可以找到进程的页表树,从而实现虚拟内存的管理。在WinDbg中,通过输入dt _KPROCESS可以查看进程的KPROCESS结构体的定义,从而找到UserDirectoryTableBase字段的偏移量,这样可以获取该字段在内存中的地址,进而获取DirectoryTable的地址。不同操作系统的KPROCESS结构体定义可能会有所不同,因此它们的UserDirectoryTableBase字段的偏移量也会不同。

通过上述原理解释,我们可知要实现物理页读写需要实现一个转换函数,因为在应用层传入的还是一个虚拟地址,通过TransformationCR3函数即可实现将虚拟地址转换到物理地址,函数内部实现了从虚拟地址到物理地址的转换过程,并返回物理地址。

代码语言:c
代码运行次数:0
运行
AI代码解释
复制
// 从用户层虚拟地址切换到物理页地址的函数
// 将 CR3 寄存器的末尾4个比特清零,这些比特是用于对齐的,不需要考虑
/*
    参数 cr3:物理地址。
    参数 VirtualAddress:虚拟地址。
*/
ULONG64 TransformationCR3(ULONG64 cr3, ULONG64 VirtualAddress)
{
  cr3 &= ~0xf;
  // 获取页面偏移量
  ULONG64 PAGE_OFFSET = VirtualAddress & ~(~0ul << 12);

  // 读取虚拟地址所在的三级页表项
  SIZE_T BytesTransferred = 0;
  ULONG64 a = 0, b = 0, c = 0;

  ReadPhysicalAddress((PVOID)(cr3 + 8 * ((VirtualAddress >> 39) & (0x1ffll))), &a, sizeof(a), &BytesTransferred);

  // 如果 P(存在位)为0,表示该页表项没有映射物理内存,返回0
  if (~a & 1)
  {
    return 0;
  }

  // 读取虚拟地址所在的二级页表项
  ReadPhysicalAddress((PVOID)((a & ((~0xfull << 8) & 0xfffffffffull)) + 8 * ((VirtualAddress >> 30) & (0x1ffll))), &b, sizeof(b), &BytesTransferred);

  // 如果 P 为0,表示该页表项没有映射物理内存,返回0
  if (~b & 1)
  {
    return 0;
  }

  // 如果 PS(页面大小)为1,表示该页表项映射的是1GB的物理内存,直接计算出物理地址并返回
  if (b & 0x80)
  {
    return (b & (~0ull << 42 >> 12)) + (VirtualAddress & ~(~0ull << 30));
  }

  // 读取虚拟地址所在的一级页表项
  ReadPhysicalAddress((PVOID)((b & ((~0xfull << 8) & 0xfffffffffull)) + 8 * ((VirtualAddress >> 21) & (0x1ffll))), &c, sizeof(c), &BytesTransferred);

  // 如果 P 为0,表示该页表项没有映射物理内存,返回0
  if (~c & 1)
  {
    return 0;
  }
  // 如果 PS 为1,表示该页表项映射的是2MB的物理内存,直接计算出物理地址并返回
  if (c & 0x80)
  {
    return (c & ((~0xfull << 8) & 0xfffffffffull)) + (VirtualAddress & ~(~0ull << 21));
  }
  // 读取虚拟地址所在的零级页表项,计算出物理地址并返回
  ULONG64 address = 0;
  ReadPhysicalAddress((PVOID)((c & ((~0xfull << 8) & 0xfffffffffull)) + 8 * ((VirtualAddress >> 12) & (0x1ffll))), &address, sizeof(address), &BytesTransferred);
  address &= ((~0xfull << 8) & 0xfffffffffull);
  if (!address)
  {
    return 0;
  }

  return address + PAGE_OFFSET;
}

这段代码将输入的CR3值和虚拟地址作为参数,并将CR3值和虚拟地址的偏移量进行一系列计算,最终得出物理地址。

其中,CR3是存储页表的物理地址,它保存了虚拟地址到物理地址的映射关系。该函数通过读取CR3中存储的页表信息,逐级访问页表,直到找到对应的物理地址。

该函数使用虚拟地址的高9位确定页表的索引,然后通过读取对应的页表项,得到下一级页表的物理地址。该过程重复执行,直到读取到页表的最后一级,得到物理地址。

最后,该函数将物理地址的低12位与虚拟地址的偏移量进行OR运算,得到最终的物理地址,并将其返回。

需要注意的是,该函数还会进行一些错误处理,例如在读取页表项时,如果该项没有被设置为有效,函数将返回0,表示无法访问对应的物理地址。

此时用户已经获取到了物理地址,那么读写就变得很容易了,当需要读取数据时调用ReadPhysicalAddress函数,其内部直接使用MmCopyMemory对内存进行拷贝即可,而对于写入数据而言,需要通过调用MmMapIoSpace先将物理地址转换为一个用户空间的虚拟地址,然后再通过RtlCopyMemory向内部拷贝数据即可实现写入,这三段代码的封装如下所示;

代码语言:c
代码运行次数:0
运行
AI代码解释
复制
#include <ntifs.h>
#include <windef.h>

#define GetDirectoryTableOffset 0x280
#define bit64 0x28
#define bit32 0x18

// 读取物理内存封装
// 这段代码实现了将物理地址映射到内核空间,然后将物理地址对应的数据读取到指定的缓冲区中。
/*
    address:需要读取的物理地址;
    buffer:读取到的数据需要保存到的缓冲区;
    size:需要读取的数据大小;
    BytesTransferred:实际读取到的数据大小。
*/
NTSTATUS ReadPhysicalAddress(PVOID address, PVOID buffer, SIZE_T size, SIZE_T* BytesTransferred)
{
  MM_COPY_ADDRESS Read = { 0 };
  Read.PhysicalAddress.QuadPart = (LONG64)address;
  return MmCopyMemory(buffer, Read, size, MM_COPY_MEMORY_PHYSICAL, BytesTransferred);
}

// 写入物理内存
// 这段代码实现了将数据写入物理地址的功能
/*
    参数 address:要写入的物理地址。
    参数 buffer:要写入的数据缓冲区。
    参数 size:要写入的数据长度。
    参数 BytesTransferred:实际写入的数据长度。
*/
NTSTATUS WritePhysicalAddress(PVOID address, PVOID buffer, SIZE_T size, SIZE_T* BytesTransferred)
{
  if (!address)
  {
    return STATUS_UNSUCCESSFUL;
  }

  PHYSICAL_ADDRESS Write = { 0 };
  Write.QuadPart = (LONG64)address;
    
    // 将物理空间映射为虚拟空间
  PVOID map = MmMapIoSpace(Write, size, (MEMORY_CACHING_TYPE)PAGE_READWRITE);

  if (!map)
  {
    return STATUS_UNSUCCESSFUL;
  }

    // 开始拷贝数据
  RtlCopyMemory(map, buffer, size);
  *BytesTransferred = size;
  MmUnmapIoSpace(map, size);
  return STATUS_SUCCESS;
}

// 从用户层虚拟地址切换到物理页地址的函数
// 将 CR3 寄存器的末尾4个比特清零,这些比特是用于对齐的,不需要考虑
/*
    参数 cr3:物理地址。
    参数 VirtualAddress:虚拟地址。
*/
ULONG64 TransformationCR3(ULONG64 cr3, ULONG64 VirtualAddress)
{
  cr3 &= ~0xf;
  // 获取页面偏移量
  ULONG64 PAGE_OFFSET = VirtualAddress & ~(~0ul << 12);

  // 读取虚拟地址所在的三级页表项
  SIZE_T BytesTransferred = 0;
  ULONG64 a = 0, b = 0, c = 0;

  ReadPhysicalAddress((PVOID)(cr3 + 8 * ((VirtualAddress >> 39) & (0x1ffll))), &a, sizeof(a), &BytesTransferred);

  // 如果 P(存在位)为0,表示该页表项没有映射物理内存,返回0
  if (~a & 1)
  {
    return 0;
  }

  // 读取虚拟地址所在的二级页表项
  ReadPhysicalAddress((PVOID)((a & ((~0xfull << 8) & 0xfffffffffull)) + 8 * ((VirtualAddress >> 30) & (0x1ffll))), &b, sizeof(b), &BytesTransferred);

  // 如果 P 为0,表示该页表项没有映射物理内存,返回0
  if (~b & 1)
  {
    return 0;
  }

  // 如果 PS(页面大小)为1,表示该页表项映射的是1GB的物理内存,直接计算出物理地址并返回
  if (b & 0x80)
  {
    return (b & (~0ull << 42 >> 12)) + (VirtualAddress & ~(~0ull << 30));
  }

  // 读取虚拟地址所在的一级页表项
  ReadPhysicalAddress((PVOID)((b & ((~0xfull << 8) & 0xfffffffffull)) + 8 * ((VirtualAddress >> 21) & (0x1ffll))), &c, sizeof(c), &BytesTransferred);

  // 如果 P 为0,表示该页表项没有映射物理内存,返回0
  if (~c & 1)
  {
    return 0;
  }
  // 如果 PS 为1,表示该页表项映射的是2MB的物理内存,直接计算出物理地址并返回
  if (c & 0x80)
  {
    return (c & ((~0xfull << 8) & 0xfffffffffull)) + (VirtualAddress & ~(~0ull << 21));
  }
  // 读取虚拟地址所在的零级页表项,计算出物理地址并返回
  ULONG64 address = 0;
  ReadPhysicalAddress((PVOID)((c & ((~0xfull << 8) & 0xfffffffffull)) + 8 * ((VirtualAddress >> 12) & (0x1ffll))), &address, sizeof(address), &BytesTransferred);
  address &= ((~0xfull << 8) & 0xfffffffffull);
  if (!address)
  {
    return 0;
  }

  return address + PAGE_OFFSET;
}

有了如上封装,那么我们就可以实现驱动读写了,首先我们实现驱动读取功能,如下这段代码是Windows驱动程序的入口函数DriverEntry,主要功能是读取指定进程的虚拟地址空间中指定地址处的4个字节数据。

代码首先通过 PsLookupProcessByProcessId 函数获取指定进程的 EPROCESS 结构体指针。然后获取该进程的 CR3值,用于将虚拟地址转换为物理地址。接下来,循环读取指定地址处的 4 个字节数据,每次读取 PAGE_SIZE 大小的物理内存数据。最后输出读取到的数据,并关闭对 EPROCESS 结构体指针的引用。

需要注意的是,该代码并没有进行有效性检查,如没有检查读取的地址是否合法、读取的数据是否在用户空间,因此存在潜在的风险。另外,该代码也没有考虑内核模式下访问用户空间数据的问题,因此也需要进行进一步的检查和处理。

代码语言:c
代码运行次数:0
运行
AI代码解释
复制
// 驱动卸载例程
extern "C" VOID DriverUnload(PDRIVER_OBJECT pDriver)
{
  UNREFERENCED_PARAMETER(pDriver);
  DbgPrint("Uninstall Driver \n");
}

// 驱动入口地址
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING path)
{
  DbgPrint("Hello LyShark \n");

  // 通过进程ID获取eprocess
  PEPROCESS pEProcess = NULL;
  NTSTATUS Status = PsLookupProcessByProcessId((HANDLE)4116, &pEProcess);

  if (NT_SUCCESS(Status) && pEProcess != NULL)
  {

    ULONG64 TargetAddress = 0x401000;
    SIZE_T TargetSize = 4;
    SIZE_T read = 0;

    // 分配读取空间
    BYTE* ReadBuffer = (BYTE *)ExAllocatePool(NonPagedPool, 1024);

    // 获取CR3用于转换
    PUCHAR Var = reinterpret_cast<PUCHAR>(pEProcess);
    ULONG64 CR3 = *(ULONG64*)(Var + bit64);
    if (!CR3)
    {
      CR3 = *(ULONG64*)(Var + GetDirectoryTableOffset);
    }

    DbgPrint("[CR3] 寄存器地址 = 0x%p \n", CR3);

    while (TargetSize)
    {
      // 开始循环切换到CR3
      ULONG64 PhysicalAddress = TransformationCR3(CR3, TargetAddress + read);
      if (!PhysicalAddress)
      {
        break;
      }

      // 读取物理内存
      ULONG64 ReadSize = min(PAGE_SIZE - (PhysicalAddress & 0xfff), TargetSize);
      SIZE_T BytesTransferred = 0;

      // reinterpret_cast 强制转为PVOID类型
      Status = ReadPhysicalAddress(reinterpret_cast<PVOID>(PhysicalAddress), reinterpret_cast<PVOID>((PVOID *)ReadBuffer + read), ReadSize, &BytesTransferred);
      TargetSize -= BytesTransferred;
      read += BytesTransferred;

      if (!NT_SUCCESS(Status))
      {
        break;
      }

      if (!BytesTransferred)
      {
        break;
      }
    }

    // 关闭引用
    ObDereferenceObject(pEProcess);

    // 输出读取字节
    for (size_t i = 0; i < 4; i++)
    {
      DbgPrint("[读入字节 [%d] ] => 0x%02X \n", i, ReadBuffer[i]);
    }
  }

  // 关闭引用
  UNREFERENCED_PARAMETER(path);

  // 卸载驱动
  pDriver->DriverUnload = DriverUnload;
  return STATUS_SUCCESS;
}

编译并运行上述代码片段,则会读取进程ID为41160x401000处的地址数据,并以字节的方式输出前四位,输出效果图如下所示;

写出数据与读取数据基本一致,只是调用方法从ReadPhysicalAddress变为了WritePhysicalAddress其他的照旧,但需要注意的是读者再使用写出时需要自行填充一段堆用于存储需要写出的字节集。

代码语言:c
代码运行次数:0
运行
AI代码解释
复制
// 驱动卸载例程
extern "C" VOID DriverUnload(PDRIVER_OBJECT pDriver)
{
  UNREFERENCED_PARAMETER(pDriver);
  DbgPrint("Uninstall Driver \n");
}

// 驱动入口地址
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING path)
{
  DbgPrint("Hello LyShark \n");

  // 物理页写
  PEPROCESS pEProcess = NULL;
  NTSTATUS Status = PsLookupProcessByProcessId((HANDLE)4116, &pEProcess);

  // 判断pEProcess是否有效
  if (NT_SUCCESS(Status) && pEProcess != NULL)
  {
    ULONG64 TargetAddress = 0x401000;
    SIZE_T TargetSize = 4;
    SIZE_T read = 0;

    // 申请空间并填充写出字节0x90
    BYTE* ReadBuffer = (BYTE *)ExAllocatePool(NonPagedPool, 1024);

    for (size_t i = 0; i < 4; i++)
    {
      ReadBuffer[i] = 0x90;
    }

    // 获取CR3用于转换
    PUCHAR Var = reinterpret_cast<PUCHAR>(pEProcess);
    ULONG64 CR3 = *(ULONG64*)(Var + bit64);
    if (!CR3)
    {
      CR3 = *(ULONG64*)(Var + GetDirectoryTableOffset);
      // DbgPrint("[CR3] 寄存器地址 = 0x%p \n", CR3);
    }

    while (TargetSize)
    {
      // 开始循环切换到CR3
      ULONG64 PhysicalAddress = TransformationCR3(CR3, TargetAddress + read);
      if (!PhysicalAddress)
      {
        break;
      }

      // 写入物理内存
      ULONG64 WriteSize = min(PAGE_SIZE - (PhysicalAddress & 0xfff), TargetSize);
      SIZE_T BytesTransferred = 0;
      Status = WritePhysicalAddress(reinterpret_cast<PVOID>(PhysicalAddress), reinterpret_cast<PVOID>(ReadBuffer + read), WriteSize, &BytesTransferred);
      TargetSize -= BytesTransferred;
      read += BytesTransferred;

      // DbgPrint("[写出数据] => %d | %0x02X \n", WriteSize, ReadBuffer + read);
      if (!NT_SUCCESS(Status))
      {
        break;
      }

      if (!BytesTransferred)
      {
        break;
      }
    }

    // 关闭引用
    ObDereferenceObject(pEProcess);
  }

  // 关闭引用
  UNREFERENCED_PARAMETER(path);

  // 卸载驱动
  pDriver->DriverUnload = DriverUnload;
  return STATUS_SUCCESS;
}

如上代码运行后,会向进程ID为41160x401000处写出4字节的0x90机器码,读者可通过第三方工具验证内存,输出效果如下所示;

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
3.2 Windows驱动开发:内核CR3切换读写内存
CR3是一种控制寄存器,它是CPU中的一个专用寄存器,用于存储当前进程的页目录表的物理地址。在x86体系结构中,虚拟地址的翻译过程需要借助页表来完成。页表是由页目录表和页表组成的,页目录表存储了页表的物理地址,而页表存储了实际的物理页框地址。因此,页目录表的物理地址是虚拟地址翻译的关键之一。
王 瑞
2023/11/25
1.4K0
3.2 Windows驱动开发:内核CR3切换读写内存
驱动开发:内核CR3切换读写内存
首先CR3是什么,CR3是一个寄存器,该寄存器内保存有页目录表物理地址(PDBR地址),其实CR3内部存放的就是页目录表的内存基地址,运用CR3切换可实现对特定进程内存地址的强制读写操作,此类读写属于有痕读写,多数驱动保护都会将这个地址改为无效,此时CR3读写就失效了,当然如果能找到CR3的正确地址,此方式也是靠谱的一种读写机制。
王 瑞
2022/11/20
1K0
驱动开发:内核CR3切换读写内存
驱动开发:内核读写内存浮点数
如前所述,在前几章内容中笔者简单介绍了内存读写的基本实现方式,这其中包括了CR3切换读写,MDL映射读写,内存拷贝读写,本章将在如前所述的读写函数进一步封装,并以此来实现驱动读写内存浮点数的目的。内存浮点数的读写依赖于读写内存字节的实现,因为浮点数本质上也可以看作是一个字节集,对于单精度浮点数来说这个字节集列表是4字节,而对于双精度浮点数,此列表长度则为8字节。
王 瑞
2023/05/30
6110
驱动开发:内核读写内存浮点数
驱动开发:内核读写内存多级偏移
让我们继续在《内核读写内存浮点数》的基础之上做一个简单的延申,如何实现多级偏移读写,其实很简单,读写函数无需改变,只是在读写之前提前做好计算工作,以此来得到一个内存偏移值,并通过调用内存写入原函数实现写出数据的目的。
王 瑞
2023/06/27
3920
驱动开发:内核读写内存多级偏移
驱动开发:内核解析内存四级页表
当今操作系统普遍采用64位架构,CPU最大寻址能力虽然达到了64位,但其实仅仅只是用到了48位进行寻址,其内存管理采用了9-9-9-9-12的分页模式,9-9-9-9-12分页表示物理地址拥有四级页表,微软将这四级依次命名为PXE、PPE、PDE、PTE这四项。
王 瑞
2023/05/29
6880
驱动开发:内核解析内存四级页表
驱动开发:摘除InlineHook内核钩子
在笔者上一篇文章《驱动开发:内核层InlineHook挂钩函数》中介绍了通过替换函数头部代码的方式实现Hook挂钩,对于ARK工具来说实现扫描与摘除InlineHook钩子也是最基本的功能,此类功能的实现一般可在应用层进行,而驱动层只需要保留一个读写字节的函数即可,将复杂的流程放在应用层实现是一个非常明智的选择,与《驱动开发:内核实现进程反汇编》中所使用的读写驱动基本一致,本篇文章中的驱动只保留两个功能,控制信号IOCTL_GET_CUR_CODE用于读取函数的前16个字节的内存,信号IOCTL_SET_ORI_CODE则用于设置前16个字节的内存。
王 瑞
2023/06/24
4360
驱动开发:摘除InlineHook内核钩子
通过x64分页机制的PTE Space实现内核漏洞利用x64中的分页机制重映射原语(概览)深入重映射机制:一些问题:猜测CR3总结
在研究NVIDIA DxgDdiEscape Handler的漏洞时,可以非常明显的感觉到过去几年中讨论的GDI原语的方法对于可靠的利用此漏洞毫无帮助。
战神伽罗
2021/01/07
1.5K0
驱动开发:内核RIP劫持实现DLL注入
本章将探索内核级DLL模块注入实现原理,DLL模块注入在应用层中通常会使用CreateRemoteThread直接开启远程线程执行即可,驱动级别的注入有多种实现原理,而其中最简单的一种实现方式则是通过劫持EIP的方式实现,其实现原理可总结为,挂起目标进程,停止目标进程EIP的变换,在目标进程开启空间,并把相关的指令机器码和数据拷贝到里面去,然后直接修改目标进程EIP使其强行跳转到我们拷贝进去的相关机器码位置,执行相关代码后,然后再次跳转回来执行原始指令集。
王 瑞
2023/06/16
1K0
驱动开发:内核RIP劫持实现DLL注入
驱动开发:内核中实现Dump进程转储
多数ARK反内核工具中都存在驱动级别的内存转存功能,该功能可以将应用层中运行进程的内存镜像转存到特定目录下,内存转存功能在应对加壳程序的分析尤为重要,当进程在内存中解码后,我们可以很容易的将内存镜像导出,从而更好的对样本进行分析,当然某些加密壳可能无效但绝大多数情况下是可以被转存的。
王 瑞
2022/12/28
1K0
驱动开发:内核中实现Dump进程转储
4.6 Windows驱动开发:内核遍历进程VAD结构体
在上一篇文章《内核中实现Dump进程转储》中我们实现了ARK工具的转存功能,本篇文章继续以内存为出发点介绍VAD结构,该结构的全程是Virtual Address Descriptor即虚拟地址描述符,VAD是一个AVL自平衡二叉树,树的每一个节点代表一段虚拟地址空间。程序中的代码段,数据段,堆段都会各种占用一个或多个VAD节点,由一个MMVAD结构完整描述。
王 瑞
2023/11/19
1.1K0
4.6 Windows驱动开发:内核遍历进程VAD结构体
3.1 Windows驱动开发:内核远程堆分配与销毁
在开始学习内核内存读写篇之前,我们先来实现一个简单的内存分配销毁堆的功能,在内核空间内用户依然可以动态的申请与销毁一段可控的堆空间,一般而言内核中提供了ZwAllocateVirtualMemory这个函数用于专门分配虚拟空间,而与之相对应的则是ZwFreeVirtualMemory此函数则用于销毁堆内存,当我们需要分配内核空间时往往需要切换到对端进程栈上再进行操作,接下来LyShark将从API开始介绍如何运用这两个函数实现内存分配与使用,并以此来作为驱动读写篇的入门知识。
王 瑞
2023/11/16
2920
3.1 Windows驱动开发:内核远程堆分配与销毁
认识 Linux 内存构成:Linux 内存调优之虚拟内存与物理内存认知
对每个人而言,真正的职责只有一个:找到自我。然后在心中坚守其一生,全心全意,永不停息。所有其它的路都是不完整的,是人的逃避方式,是对大众理想的懦弱回归,是随波逐流,是对内心的恐惧 ——赫尔曼·黑塞《德米安》
山河已无恙
2025/04/11
3070
认识 Linux 内存构成:Linux 内存调优之虚拟内存与物理内存认知
驱动开发:通过内存拷贝读写内存
内核中读写内存的方式有很多,典型的读写方式有CR3读写,MDL读写,以及今天要给大家分享的内存拷贝实现读写,拷贝读写的核心是使用MmCopyVirtualMemory这个内核API函数实现,通过调用该函数即可很容易的实现内存的拷贝读写。
王 瑞
2022/12/20
1.1K0
驱动开发:通过内存拷贝读写内存
驱动开发:内核中实现Dump进程转储
多数ARK反内核工具中都存在驱动级别的内存转存功能,该功能可以将应用层中运行进程的内存镜像转存到特定目录下,内存转存功能在应对加壳程序的分析尤为重要,当进程在内存中解码后,我们可以很容易的将内存镜像导出,从而更好的对样本进行分析,当然某些加密壳可能无效但绝大多数情况下是可以被转存的。
王 瑞
2022/11/18
7460
驱动开发:内核中实现Dump进程转储
暴力搜索内存进程对象反隐藏进程
我们前面说过几种隐藏进程的方法: 遍历进程活动链表(ActiveProcessLinks)
战神伽罗
2019/12/20
1.8K0
rust写操作系统 rCore tutorial 学习笔记:实验指导三 虚拟地址与页表
这是 os summer of code 2020 项目每日记录的一部分: 每日记录github地址(包含根据实验指导实现的每个阶段的代码):https://github.com/yunwei37/os-summer-of-code-daily
云微
2023/02/11
7700
驱动开发:内核运用LoadImage屏蔽驱动
在笔者上一篇文章《驱动开发:内核监视LoadImage映像回调》中LyShark简单介绍了如何通过PsSetLoadImageNotifyRoutine函数注册回调来监视驱动模块的加载,注意我这里用的是监视而不是监控之所以是监视而不是监控那是因为PsSetLoadImageNotifyRoutine无法实现参数控制,而如果我们想要控制特定驱动的加载则需要自己做一些事情来实现,如下LyShark将解密如何实现屏蔽特定驱动的加载。
王 瑞
2022/11/14
1.5K0
驱动开发:内核运用LoadImage屏蔽驱动
驱动开发:内核远程线程实现DLL注入
在笔者上一篇文章《内核RIP劫持实现DLL注入》介绍了通过劫持RIP指针控制程序执行流实现插入DLL的目的,本章将继续探索全新的注入方式,通过NtCreateThreadEx这个内核函数实现注入DLL的目的,需要注意的是该函数在微软系统中未被导出使用时需要首先得到该函数的入口地址,NtCreateThreadEx函数最终会调用ZwCreateThread,本章在寻找函数的方式上有所不同,前一章通过内存定位的方法得到所需地址,本章则是通过解析导出表实现。
王 瑞
2023/06/25
5670
驱动开发:内核远程线程实现DLL注入
4.5 Windows驱动开发:实现进程数据转储
多数ARK反内核工具中都存在驱动级别的内存转存功能,该功能可以将应用层中运行进程的内存镜像转存到特定目录下,内存转存功能在应对加壳程序的分析尤为重要,当进程在内存中解码后,我们可以很容易的将内存镜像导出,从而更好的对样本进行分析,当然某些加密壳可能无效但绝大多数情况下是可以被转存的。
王 瑞
2023/11/18
3060
4.5 Windows驱动开发:实现进程数据转储
驱动开发:内核LoadLibrary实现DLL注入
远程线程注入是最常用的一种注入技术,在应用层注入是通过CreateRemoteThread这个函数实现的,该函数通过创建线程并调用 LoadLibrary 动态载入指定的DLL来实现注入,而在内核层同样存在一个类似的内核函数RtlCreateUserThread,但需要注意的是此函数未被公开,RtlCreateUserThread其实是对NtCreateThreadEx的包装,但最终会调用ZwCreateThread来实现注入,RtlCreateUserThread是CreateRemoteThread的底层实现。
王 瑞
2023/06/13
1.6K0
驱动开发:内核LoadLibrary实现DLL注入
推荐阅读
相关推荐
3.2 Windows驱动开发:内核CR3切换读写内存
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档