Windows x64隐藏可执行内存

news/2024/11/28 17:48:46/

文章目录

      • 实现效果
      • 实现原理
      • VAD内存
        • 什么是VAD内存
        • 查看VAD内存
        • VAD属性
        • VAD内存可利用的点
      • x64分页机制
        • W7 x64下任意地址PDT PTE算法
        • W10 x64定位随机化页表基址
      • 实现隐藏可执行内存
      • 隐藏内存对抗

实现效果

在这里插入图片描述

驱动程序在Test进程中申请一块内存地址并打印,然后控制台程序在接收到输入的地址后开始跳转执行。申请的内存必须具有可执行属性,否则三环程序会抛异常。
在这里插入图片描述

打开CE查看0x1F0000的内存,属性仅显示可读可写,再查看内存区域,1F0000的地址处也只显示读写属性,说明我们成功的隐藏了一块可执行内存。

实现原理

实现的原理其实很简单,就是在申请一块不可执行的内存后,通过修改PDE和PTE的属性位,将这块连续的内存区域设置为可执行,从而达到隐藏可执行内存的目的。

所需要的前置知识有两块,一个是VAD内存管理,一个是x64的分页机制。

实际上病毒木马也会用到这个操作,在申请内存的时候先申请一块不可执行的内存,然后再通过VirtualProtetc的方式来修改内存属性,这样可以躲避掉一部分的杀软查杀。尽管隐藏的方式比较low,但好过直接申请可执行的内存。

VAD内存

什么是VAD内存

0: kd> dt _EPROCESS ffffaa0daaa3f080
ntdll!_EPROCESS
+0x628 VadRoot          : _RTL_AVL_TREE

在EPROCESS进程结构体+0x628的位置有一个成员叫VadRoot,这是一个二叉树结构,里面保存了这个进程所有内存的信息。

查看VAD内存

再来查看一下当前这块进程的vad内存,用!vad命令

在这里插入图片描述

其中:

  • Level表示层级
  • start表示起始地址,这个是页号,真正的虚拟地址要乘以0x1000
  • end是结束地址 也是页号
  • Commit表示提交属性
  • Mapped表示映射内存
  • Private表示私有内存
  • 倒数第二列是内存属性

在这里插入图片描述

  • 如果commit属性为Mapoed Exe,说明这是一个私有PE文件的映射,在最后一列会出现PE文件的路径

可以看到VAD树把当前进程所有的内存块都清晰的展现出来了,如果你在进程里开辟了一块内存用来执行shellcode的话,那么只要一遍历,直接就能查出来。

VAD属性

VAD是管理虚拟内存的,每一个进程有自己单独的一个VAD树 , 使用VirtualAllocate函数申请一个内存,则会在VAD树上增加一个结点,是_MMVAD结构体,私有内存一般是MMVAD_SHORT,映射内存一般是MMVAD_LONG’

_MMVAD结构

0: kd> dt _MMVAD
nt!_MMVAD+0x000 Core             : _MMVAD_SHORT+0x040 u2               : <unnamed-tag>+0x048 Subsection       : Ptr64 _SUBSECTION+0x050 FirstPrototypePte : Ptr64 _MMPTE+0x058 LastContiguousPte : Ptr64 _MMPTE+0x060 ViewLinks        : _LIST_ENTRY+0x070 VadsProcess      : Ptr64 _EPROCESS+0x078 u4               : <unnamed-tag>+0x080 FileObject       : Ptr64 _FILE_OBJECT

_MMVAD_SHORT结构

kd> dt _MMVAD_SHORT 876f4f50  -r1
nt!_MMVAD_SHORT+0x000 u1               : <unnamed-tag>+0x000 Balance          : 0y00+0x000 Parent           : 0x8757afc0 _MMVAD //有无父节点+0x004 LeftChild        : (null) +0x008 RightChild       : (null) //有无左子树与右子树+0x00c StartingVpn      : 0xd0+0x010 EndingVpn        : 0xd0//起始页与结束页+0x014 u                : <unnamed-tag>//锁页+0x000 LongFlags        : 0x98000001+0x000 VadFlags         : _MMVAD_FLAGS+0x018 PushLock         : _EX_PUSH_LOCK+0x000 Locked           : 0y0+0x000 Waiting          : 0y0+0x000 Waking           : 0y0+0x000 MultipleShared   : 0y0+0x000 Shared           : 0y0000000000000000000000000000 (0)+0x000 Value            : 0+0x000 Ptr              : (null) +0x01c u5               : <unnamed-tag>+0x000 LongFlags3       : 0+0x000 VadFlags3        : _MMVAD_FLAGS3
kd> dt _MMVAD_FLAGS 876f4f50+14
nt!_MMVAD_FLAGS+0x000 CommitCharge     : 0y0000000000000000001 (0x1)+0x000 NoChange         : 0y0//如果为1则不可改属性+0x000 VadType          : 0y000//类型+0x000 MemCommit        : 0y0+0x000 Protection       : 0y11000 (0x18)//保护+0x000 Spare            : 0y00+0x000 PrivateMemory    : 0y1

VAD内存可利用的点

之前我们说了句柄的对抗,实际上游戏和杀软在内存上也会做很多手脚,来达到保护自身的目的。这里有几个可以操作的点。

保护内存属性不被修改

一个是修改_MMVAD_FLAGS结构里面的NoChange字段,这个字段被置为1之后,表示这块内存属性不能被修改,即使你调用VirtualProtect这样的API也是没用的。可以保护自己的内存属性不被修改。

TP一字节保护

bool ProtectMemory(void* memory)
{auto vad = GetMemoryVad(memory);if(vad) {vad->Core.u.LongFlags &= 0x10000;}
}

TP的一字节保护也是修改了其中的一个标志位,这个标志位被修改后,你就不能再修改内存属性了

隐藏私有内存

还有一个可以玩的地方是可以利用VAD来隐藏私有内存

+0x00c StartingVpn      : 0xd0
+0x010 EndingVpn        : 0xd0//起始页与结束页

这种方法需要找到隐藏的VAD节点,让StartingVpn等于EndingVpn,就可以达到隐藏效果了。跟断链的套路一样,只不过这里断的是二叉树。

这种方式仅限于隐藏一小块不频繁使用的私有内存,而且有蓝屏的几率,不稳定。

x64分页机制

说完了VAD的内存管理,再来说x64的分页机制。这里只说如何在x64下通过任意一个虚拟地址获取PDE和PTE的方法。其他的分页知识各位需要自行补充。

#### x86 PDE PTE基址

PTEBase=0xC0000000 
PDEBase=0xC0300000

这个是XP下的PTE和PTE的基地址

访问页目录表的公式:0xC0300000+PDI*4
访问页表的公式:0xC0000000+PDI*4096+PTI*4

再通过这个公式,我们就可以修改任意一个地址的PDE和PTE属性。

W7 x64下任意地址PDT PTE算法

在这里插入图片描述

在IDA中找到MiIsAddressValid这个函数,这个函数的原理就是通过查看PDE和PTE的P位是否为零的方式,来判断当前的内存地址是否有效。

从下往上,分别是PTE,PDE,PPE和PXE的算法,在w7x64PDE和PTE的基地址都是固定的,直接拿过来用就可以了。这个没什么可说的。

W10 x64定位随机化页表基址

到了W10 x64下,情况就不一样了, windows 10 14316开始实现了页表随机化 ,PTE的基地址变成了随机的。但是我们只要把PTE的基地址拿到了,剩下的三个基地址就可以推算出来。

获取PTE基地址

在这里插入图片描述

W10我们可以用这个函数``MmGetVirtualForPhysical`,其中rdx里面的值是页表基地址,也就是PTE的基地址,拿到了这个基地址,其他的就可以推算出来了。

在这里插入图片描述

MmGetVirtualForPhysical进行反汇编比对PTE,发现地址是一样的。有了PTE的基地址,那么我们就可以拿到任意一个地址的PTE

拿任意一个地址的PTE的公式(这里要取后48位):

(f807`16a90fb0/0x1000)*8

简化一下就成了这个样子

(f807`16a90fb0>>12)<<3+PTEBase

在这里插入图片描述

实际上跟IDA里面的这个pde的算法是一样的,两种都可以,只不过我用的比较方便理解

根据PTE求PDE

把PTE当成一个虚拟地址来求物理地址,求得的物理地址就是PDT,原理就是指向PTE所在物理页面的PTE是PDE。

PDEBase=(B08000000000>>12)<<3+FFFFB08000000000

在这里插入图片描述

0: kd> !pte 0VA 0000000000000000
PXE at FFFFB0D86C361000    PPE at FFFFB0D86C200000    PDE at FFFFB0D840000000    PTE at FFFFB08000000000
contains 0A000000072D3867  contains 0000000000000000
pfn 72d3      ---DA--UWEV  contains 0000000000000000
not valid

算出来的PDE结果是一样的

根据PDE算PPE

同样把PDE看成是一个普通的虚拟地址

PPEBase=(B0D840000000>>12)<<3+PTEBase

根据PPE算PXE

PXEBase=(B0D86C200000>>12)+PTEBase

实际上,定位随机化页表基址有很种方法,各路神仙好像都有自己的套路,鹅厂的方法是通过CR3里面的字段计算拿到页表基地址,有兴趣可以自行研究一下。用哪个都不重要,理解透一个以后封装拿来用就行了。

那么有了任意地址的PDE和PTE算法之后,我们就实现了修改任意一个地址读写属性的操作了。

实现隐藏可执行内存

最后万事俱备,再来实现隐藏可执行内存的操作。示例代码如下:

PVOID AllocateMemory(HANDLE pid, SIZE_T size)
{//获取进程结构体PEPROCESS Process=NULL;PVOID BaseAddress = 0;NTSTATUS status= PsLookupProcessByProcessId(pid, &Process);if (!NT_SUCCESS(status)){return NULL;}//判断进程是否退出if (PsGetProcessExitStatus(Process)!=STATUS_PENDING){ObDereferenceObject(Process);return NULL;}//附加进程KAPC_STATE kApcState = {0};KeStackAttachProcess(Process, &kApcState);//附加 ZwAllocateVirtualMemory(NtCurrentProcess(), &BaseAddress, 0, &size, MEM_COMMIT, PAGE_READWRITE);if (NT_SUCCESS(status)){RtlZeroMemory(BaseAddress, size);memcpy(BaseAddress, shellcode, sizeof(shellcode));//修改PDE和PTE 设置可写和可执行属性SetExecutePage(BaseAddress, size);}KeUnstackDetachProcess(&kApcState);return BaseAddress;}

实际上就是申请一块内存,在申请完了之后用修改PDE和PTE属性的方式来修改内存属性。

隐藏内存对抗

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E9osXzn6-1670137929254)(Windows x64隐藏可执行内存.assets/1669908278279.png)]

在CE的设置里面把这几个复选框给勾上,然后重启CE,重新附加进程内存。

在这里插入图片描述

会发现此时我们的内存属性显示成了真正的可读可写可执行的内存方式,这是因为CE在开启这几个选项之后,会利用驱动查PDE PTE属性的方式来查询当前的内存。

在这里插入图片描述

为什么知道是驱动?因为现在CE不仅可以读取三环的地址,也可以读取到零环的内存地址。

不过应该没有哪家公司会用这种方式来进行检测,毕竟这对抗都到物理内存层面了,而且CE上面的选项也提示了,开启之后扫描会变得很慢。

在这里插入图片描述

再来看这个VAD树的遍历,依然还是欺骗过去了。

完整代码:

https://download.csdn.net/download/qq_38474570/87230134?spm=1001.2014.3001.5501


http://www.ppmy.cn/news/1650.html

相关文章

Python字典

在Python中&#xff0c;字典是一系列键值对。每个键都与一个值相关联&#xff0c;你可使用键来访问相关联的值。与键相关联的值可以是数、字符串、列表乃至字典。事实上&#xff0c;可将任何Python对象用作字典中的值。 alien_0 {‘color’: ‘green’, ‘points’: 5} print…

2022.12.4 学习周报

文章目录摘要文献阅读1.题目2.摘要3.介绍4.方法5.实验5.1 数据集5.2 网络模型5.3 实验表现6.展望深度学习1.LSTM原理1.1 什么是LSTM&#xff1f;1.2 遗忘门&#xff08;forget gate&#xff09;1.3 输入门&#xff08;input gate&#xff09;1.4 输出门&#xff08;output gate…

粒子群算法和鲸鱼算法的比较(Matlab代码实现)

目录 1 粒子群优化算法 2 鲸鱼优化算法 3 粒子群算法和鲸鱼算法比较 4 Matlab代码实现 1 粒子群优化算法 粒子群优化算法(PSO&#xff1a;Particle swarm optimization) 是一种进化计算技术&#xff08;evolutionary computation&#xff09;。源于对鸟群捕食的行为研究…

堆(二叉堆)-优先队列-数据结构和算法(Java)

文章目录1 概述1.1 定义1.2 二叉堆表示法2 API3 堆相关算法3.1 上浮&#xff08;由下至上的堆有序化&#xff09;3.2 下沉&#xff08;由上至下的堆有序化&#xff09;3.3 插入元素3.4 删除最大元素4 实现5 性能和分析5.1 调整数组的大小5.2 元素的不可变性6 简单测试6 后记1 概…

企业为何要认定高新技术企业,都有哪些好处?

近些年&#xff0c;越来越多的企业都开始进行申报高新技术企业了。根据数据显示&#xff0c;我国科技型中小企业、高新技术企业的规模突破了20万家。那么&#xff0c;企业为何一定要认定高新技术企业&#xff0c;到底有哪些好处呢&#xff1f; 一、政策方面 1、税收优惠。企业评…

使用 MySQL 进行分页

拥有一个大型数据集并且只需要获取特定数量的行&#xff0c;这就是 LIMIT子句的存在原因。它允许限制 SQL 查询语句返回的结果中的行数。 分页是指将大型数据集划分为较小部分的过程。 通过一次获取小块数据来更快地向用户发送数据的能力是使用分页的好处之一。 工作原理 分…

开源消息引擎系统 Kafka 3新特性,一文带你了解

文章目录1、Kafka 简介2、kafka3 的安装配置3、Kafka 当中 Raft 的介绍4、Raft 算法介绍5、Kafka 常见问题1、Kafka 简介 Kafka 是一款开源的消息引擎系统。一个典型的 Kafka 体系架构包括若干 Producer、若干 Broker、若干 Consumer&#xff0c;以及一个 ZooKeeper 集群&#…

CesiumForUnreal之UE世界坐标与WGS84经纬度坐标转换原理与应用

文章目录 1.UE世界坐标与经纬度坐标转换原理1.1 坐标系介绍1.1.1 UE坐标系1.1.2 地理坐标系1.1.3 ECEF坐标系1.1.4 投影坐标系1.1.5 ENU坐标系1.2 BLH转UE1.2.1 BLH→ECEF1.2.2 ECEF→UE2. 坐标转换应用2.1 蓝图代码2.2 转换结果3.参考资料1.UE世界坐标与经纬度坐标转换原理 1…