本篇以x86(开启PAE) 以及x64 Win7系统 不借助微软API突破内存的写拷贝机制进行讲述

https://bbs.pediy.com/thread-222949.htm
 
0x01 Before Starting

1. PAE: 
       Physical Address Extension,Inter为了支持更大的物理内存寻址而设计的x86寻址方式,虚拟地址没有变化都是32位,只是描述物理内存的位数由原先的32为增加到36位,能够最多寻址 2^4 * 4GB = 64GB内存,也就意味着你机器上如果存在超过4GB的内存条,那么一般都可以被充分利用到,这只是体现在多进程多任务的性能上,并没有增加一个进程的寻址空间,仍然为4GB。微软喜欢把页面表基地址放在0xC0000000上,当发生进程切换操作时这块页表内容会随CR3引导的页面表的内容而发生改变(一般内核的高2GB不会变化太大,主要体现在低2GB内存),那么这就有规律可言,在内核情景分析中可能大家都已经见过未开启PAE的几个公式:
 
 1) 未开启PAE状态下 (10/10/12)
          PTE = (VA >> 12) << 2 + PTE_BASE
          PDE = (VA >> 22) << 2 + PTE_BASE
          因为 PDE_BASE 是描述PTE_BASE的PTE
          显然

PDE_BASE = (PTE_BASE >> 12) << 2 + PTE_BASE = (0xC0000000 >> 12) << 2 +

0xC0000000 = 0xC0300000

 
那么自己推导下PAE下的计算方式
2) 开启PAE状态下 (2/9/9/12)

PTE = (VA >> 12) << 3 + PTE_BASE

PDE = (VA >> 21) << 3 + PTE_BASE

PDPE = (VA >> 30) << 3 + PDE_BASE

因为 PDE_BASE 是描述PTE_BASE的PTE

显然 PDE_BASE = (PTE_BASE >> 12) << 3 + PTE_BASE = (0xC0000000 >> 12) << 3 + 0xC0000000 = 0xC0600000

2. x64 公式推导

WRK或者WDK开发包头文件中定义了64位下 PTE_BASE 的内容

1
2
3
4
#define PTE_BASE  0xFFFFF68000000000UI64
#define PPE_BASE  0xFFFFF6FB7DA00000UI64
#define PDE_BASE  0xFFFFF6FB40000000UI64
#define PXE_BASE  0xFFFFF6FB7DBED000UI64
 

自然,这几个值看起来都是固定了,其实是因为PTE_BASE固定的,才有个下面这几个固定的值,计算方式如下:

PDE_BASE = ((PTE_BASE & 0x0000FFFFFFFFF000) >> 12) * 8 + PTE_BASE

= 0xF68000000 * 8 + PTE_BASE

= 0x7B40000000 + PTE_BASE = 0xFFFFF6FB40000000
PPE_BASE = ((PDE_BASE & 0x0000FFFFFFFFF000) >> 12) * 8 + PTE_BASE

= 0xF6FB40000 * 8 + PTE_BASE = 0x7B7DA00000 + PTE_BASE

= 0xFFFFF6FB7DA00000
PXE_BASE = ((PPE_BASE & 0x0000FFFFFFFFF000) >> 12) * 8 + PTE_BASE

= 0xF6FB7DA00 * 8 + PTE_BASE

= 0x7B7DBED000 + PTE_BASE = 0xFFFFF6FB7DBED000

在PAE开启状态下

(下文默认)

或者x64系统下,描述PTE结构的定义为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
typedef struct _MMPTE_HARDWARE {
    ULONGLONG Valid : 1;
    ULONGLONG Write : 1;        // UP version
    ULONGLONG Owner : 1;
    ULONGLONG WriteThrough : 1;
    ULONGLONG CacheDisable : 1;
    ULONGLONG Accessed : 1;
    ULONGLONG Dirty : 1;
    ULONGLONG LargePage : 1;
    ULONGLONG Global : 1;
    ULONGLONG CopyOnWrite : 1// software field
    ULONGLONG Prototype : 1;   // software field
    ULONGLONG reserved0 : 1;  // software field
    ULONGLONG PageFrameNumber : 28;
    ULONG64 reserved1 : 24 - (_HARDWARE_PTE_WORKING_SET_BITS+1);
    ULONGLONG SoftwareWsIndex : _HARDWARE_PTE_WORKING_SET_BITS;
    ULONG64 NoExecute : 1;
} MMPTE_HARDWARE, *PMMPTE_HARDWARE;
 
typedef struct _MMPTE {
    union  {
        //ULONG_PTR Long;
        MMPTE_HARDWARE Hard;
        //MMPTE_HARDWARE_LARGEPAGE HardLarge;
        //HARDWARE_PTE Flush;
        //MMPTE_PROTOTYPE Proto;
        //MMPTE_SOFTWARE Soft;
        //MMPTE_TRANSITION Trans;
        //MMPTE_SUBSECTION Subsect;
        //MMPTE_LIST List;
        } u;
} MMPTE;
 
typedef MMPTE *PMMPTE;
 

0x02 Physical Memory Patch

实际上这个ULONGLONG CopyOnWrite : 1; // software field我并没有看出什么玄机,重点是这个ULONGLONG Write : 1;        // UP version
找到虚拟地址对应的PTE项,将Write位置为1,自然这块内存就不再为写拷贝了,看Inter手册上对这个字段的描述也不是特别的清楚,下图为2MB的大页面对应的结构,跟4KB的小页面也差不了多少,对R/W字段的描述也不是很明显,只是WRK/Win2000上的这个software field的3个字段全部为Ignored... 
 
 
这个位起着的作用看上去不是只有一个可写属性,当我写一个Dll让一个目标进程去Load然后用这种方式把他的PE头给Patch了之后,达到了与MDL修改物理内存一样的效果(MDL其实也是一个突破CopyOnWrite的一个方法),以后这个进程再也加载不起来这个Dll了,因为原始的物理页已经被修改了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
typedef struct tag_CTRLV2
{
    PVOID lpAddress;
    PVOID lpPatchContext;
    ULONG ulSize;
 
} CtrlV2, *PCtrlV2;
 
BOOLEAN ModifyPhysicalAddressX86(PCtrlV2 pV2)
{
    if (g_bPAEON)
    {
        PMMPTE_PAE ProtectPTE = MiGetPteAddressForPAE(pV2->lpAddress);
        __try
        {
            if (ProtectPTE->Valid)
            {
                // Disable CopyOnWrite
                ProtectPTE->Write = 1;
                // Now Patch Physical Memory
                memcpy(pV2->lpAddress, pV2->lpPatchContext, pV2->ulSize);
 
                DbgPrint("[Wxoit] ModifyPhysicalAddressX86 pV2->lpAddress:%x, Context:%x\r\n"
                    pV2->lpAddress, *(ULONG*)pV2->lpAddress);
            }
        }
        __except(EXCEPTION_EXECUTE_HANDLER)
        {
            DbgPrint("[Wxoit] ModifyPhysicalAddressX86 Raise Exception %x", GetExceptionCode());
        }
    }
 
    return TRUE;
}
 
第一次加载NopDll.dll 并Patch NopDll.dll 的PE DOS_SIGNATURE。
 
第二次加载NopDll.dll时,发现这个Dll已经是一个bad exe format
 

当然这个方法,我也给大家支持了64位,但是警告大家不要去随意搞系统的内存,出问题本人概不负责...

代码写的比较急,没有支持跨进程操作物理内存,大家如果想做只要KeStackAttachProcess下就OK了,
代码在最后的附件中
 

0x02 Things of MDL

最后就当作福利吧,前段时间在看MDL的一些API,把我所学分享给大家。

IoAllocateMdl

MmProbeAndLockPages/MmBuildMdlForNonPagedPool

MmMapLockedPagesSpecifyCache

MDL不止只有下面描述的结构,在这个结构的后面还存在着这个MDL描述的所有的物理页的页面帧号

1
2
3
4
5
6
7
8
9
10
typedef struct _MDL {
    struct _MDL *Next;
    CSHORT Size;
    CSHORT MdlFlags;
    struct _EPROCESS *Process;
    PVOID MappedSystemVa;
    PVOID StartVa;
    ULONG ByteCount;
    ULONG ByteOffset;
} MDL, *PMDL;

1. IoAllocateMdl

PMDL

IoAllocateMdl(

IN PVOID VirtualAddress,

IN ULONG Length,

IN BOOLEAN SecondaryBuffer,

IN BOOLEAN ChargeQuota,

IN OUT PIRP Irp OPTIONAL

)

       这个API没啥好说的,就是小心点大小检测,当传入的Length越过了0x17个页面时,对MDL的大小有要求(不能超过0xFFFF),第三参数只有在第五参数存在时才有意义:标志这个是不是一个链式内存(一般只有在IRP结构中需要处理),第四参数没看到在哪用。一般地,三四五参数都传NULL。
 
2.

MmProbeAndLockPages

         VOID
MmProbeAndLockPages (
     IN OUT PMDL MemoryDescriptorList,
     IN KPROCESSOR_MODE AccessMode,
     IN LOCK_OPERATION Operation
     )

好了,这个API开始就要注意了,这块特别容易抛异常

1. 进入这个函数之前,不要随便给MDL置标记(不管是你手动的还是API帮你置的位),特别是

MDL_PAGES_LOCKED

MDL_MAPPED_TO_SYSTEM_VA

MDL_SOURCE_IS_NONPAGED_POOL

MDL_PARTIAL

MDL_IO_SPACE

2. 存在当前模式,如果传入UserMode,那么在第一步初始化MDL如果描述的虚拟地址是一个内核地址,那么这直接抛0xC0000005异常

3. 这个API紧接这会去锁住MDL描述的物理内存页面,当你传入MDL的虚拟地址是一个Ring3地址, 也会校验你传入的Operation,

其中

一个页面不具有写属性你却传入了 IoWriteAccess/IoModifyAccess 那么不好意思,同样RaiseException

4. 检查当前进程(对是当前进程!,调用这个函数如果你要修改别人家的物理内存那么请先KeStackAttachProcess )

的虚拟内存对应的物理

页面映射关系,如果你尝试传入一个缺页的内存,这个函数会尝试处理这个缺页情况,再做类似第三步的动作

5. 即使找到了虚拟页面映射的物理页面,如果传入

IoWriteAccess/IoModifyAccess  也会校验对应的VAD是否具有MM_READWRITE属性

使用这个函数时,如果你要修改内存那么不必急着传入

IoWriteAccess/IoModifyAccess 这样会造成这个函数代码内部的检测逻辑,因为最

后在调用MmMapLockedPagesSpecifyCache 函数时,不管是Ring3还是Ring0应该都是具有读写属性的。在我的理解上来看.......

3.

MmBuildMdlForNonPagedPool

VOID

MmBuildMdlForNonPagedPool (

IN OUT PMDL MemoryDescriptorList

    )
 
这个函数很简单,就负责置MDL的标志位以及填充页面帧号,当然也要求当前进程的页面表能够访问到的内存
MemoryDescriptorList->MdlFlags |= MDL_SOURCE_IS_NONPAGED_POOL;
 
4.

MmMapLockedPagesSpecifyCache

PVOID
MmMapLockedPagesSpecifyCache (
     IN PMDL MemoryDescriptorList,
     IN KPROCESSOR_MODE AccessMode,
     IN MEMORY_CACHING_TYPE CacheType,
     IN PVOID RequestedAddress,
     IN ULONG BugCheckOnFailure,
     IN MM_PAGE_PRIORITY Priority
     )
 
当MDL的页面帧号都填充完毕时,通过

MmMapLockedPagesSpecifyCache最后一步映射物理内存到当前进程页面表中,

不知道微软是怎么想到设计这个接口的,这个函数实在过于强大。强大不光体现在他能越过内存的CopyOnWrite机制,
而且通过

MmMapLockedPagesSpecifyCache得到的虚拟内存地址具有读写属性......

 
1. KernelMode 内核模式下会得到一个内核地址,我们都知道内核中申请或者Map的内存都是可读可写可执行的
2. UserMode 用户模式下Map的地址同样具有读写属性,具体实现见MiMapLockedPagesInUserSpace,在LoadImage回调下
    这个函数有进程的AddressCreationLock限制,所以在模块回调时不要用UserMode!
 
至少到目前为止的Windows版本都是可读写的。
 
说到这里,我想到某厂的驱动开发人员写了这样一段代码,看的我哭笑不得

 
这个人即想把MDL映射到内核地址(

MDL_MAPPED_TO_SYSTEM_VA

),又使用UserMode的映射....... 局外人啊。不过

这段代码不会出什么问题,因为

MmMapLockedPagesSpecifyCache 还是先校验

AccessMode的,如果是UserMode就不会

MDL_MAPPED_TO_SYSTEM_VA标记了,而且这个厂商用这个方法 Patch 动态库让动态库无法加载,实在让人深恶痛

绝,因为改了物理内存,所有进程都加载不了这个动态库了。
 
而且从时间上的观察来看,这个厂商甚至不知道这些函数干了些啥,只知道这样可以获取内存的写权限......
 
 

 
      jpg改rar

Windows内存放血篇,突破物理内存的CopyOnWrite的更多相关文章

  1. 全面介绍Windows内存管理机制及C++内存分配实例(四):内存映射文件

    本文背景: 在编程中,很多Windows或C++的内存函数不知道有什么区别,更别谈有效使用:根本的原因是,没有清楚的理解操作系统的内存管理机制,本文企图通过简单的总结描述,结合实例来阐明这个机制. 本 ...

  2. 全面介绍Windows内存管理机制及C++内存分配实例

    转自:http://blog.csdn.net/yeming81/article/details/2046193 本文基本上是windows via c/c++上的内容,笔记做得不错.. 本文背景: ...

  3. Windows内存管理和linux内存管理

    windows内存管理 windows 内存管理方式主要分为:页式管理,段式管理,段页式管理. 页式管理的基本原理是将各进程的虚拟空间划分为若干个长度相等的页:页式管理把内存空间按照页的大小划分成片或 ...

  4. windows内存体系结构 内存查询,读,写(附录源码)

    “进程内存管理器”这个程序实现的最基本功能也就是对内存的读写,之前的两篇文章也就是做的一个铺垫,介绍了内核模式切换和IoDeviceControl函数进行的应用程序与驱动程序通信的问题.接下来就进入正 ...

  5. 【C/C++学院】0724-堆栈简单介绍/静态区/内存完毕篇/多线程

    [送给在路上的程序猿] 对于一个开发人员而言,可以胜任系统中随意一个模块的开发是其核心价值的体现. 对于一个架构师而言,掌握各种语言的优势并能够运用到系统中.由此简化系统的开发.是其架构生涯的第一步. ...

  6. 第13章 Windows内存体系结构

    13.1 Windows的虚拟地址空间安排 13.1.1虚拟地址空间的分区(即虚拟地址空间布局) 进程的地址空间划分 分区 x86 32位 Windows 3GB用户模式下的x86 32位Window ...

  7. windows内存映射学习及帮助类实现

    本文通过创建文件内存映射类,学习windows内存映射相关知识:创建内存映射文件后,可以按照内存操作方式操作文件:支持32位程序处理超过4G大小的文件. 感谢http://blog.csdn.net/ ...

  8. Windows内存管理[转]

    本文主要内容:1.基本概念:物理内存.虚拟内存:物理地址.虚拟地址.逻辑地址:页目录,页表2.Windows内存管理3.CPU段式内存管理4.CPU页式内存管理 一.基本概念1. 两个内存概念物理内存 ...

  9. Windows内存原理与内存管理

    WIndows为每个进程分配了4GB的虚拟地址空间,让每个进程都认为自己拥有4GB的内存空间,4GB怎么来的? 32位 CPU可以取地址的空间为2的32次方,就是4GB(正如16位CPU有20根寻址线 ...

随机推荐

  1. e587. Filling Basic Shapes

    There are two ways to fill basic shapes like lines and rectangles. The first is to use specific draw ...

  2. 模式识别之bayes---bayes 简单天气预测实现实例

    Bayes Classifier 分类 在模式识别的实际应用中,贝叶斯方法绝非就是post正比于prior*likelihood这个公式这么简单,一般而言我们都会用正态分布拟合likelihood来实 ...

  3. Linux及Windows系统配置JDK环境变量

    1.Linux系统配置方法 记住,要下载JDK-8u121-linux-x64.tar.gz,而不是JRE-8u121-linux-x64.tar.gz,JDK中含很多开发者实用工具,比如javac. ...

  4. 使用openstackclient调用Keystone v3 API

    本文内容属于个人原创,转载务必注明出处:  http://www.cnblogs.com/Security-Darren/p/4138945.html 考虑到Keystone社区逐渐弃用第二版身份AP ...

  5. python调用ansible接口API执行命令

    python版本:Python 2.6.6 ansible版本:ansible 2.3.1.0      下载地址:https://releases.ansible.com/ansible/ 调用脚本 ...

  6. CentOS 7 打开关闭FirewallD防火墙端口命令

    CentOS 7 使用firewalld代替了原来的iptables,使用方法如下: >>>关闭防火墙 systemctl stop firewalld.service        ...

  7. 安卓开发笔记——Gallery组件+ImageSwitcher组件

    什么是Gallery? Gallery是一个水平的列表选择框,它允许用户通过拖动来查看上一个.下一个列表选项. 下图是今天要实现的最终效果: 利用Gallery组件实现的一个横向显示图像列表,可以通过 ...

  8. 【LFM】隐语义模型

    模型解释: http://blog.csdn.net/harryhuang1990/article/details/9924377

  9. 本地文件到通过flume到hdfs

    配置文件 cd /usr/app/flume1.6/conf vi flume-dirTohdfs.properties #agent1 name agent1.sources=source1 age ...

  10. vue路由跳转报错解决

    vue路由跳转: setTimeout(function () { console.log(this); this.$router.push("/login"); },800) 语 ...