本篇以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

最新文章

  1. React.js入门必须知道的那些事
  2. iptables实现正向代理
  3. 转载:C# this.invoke()作用 多线程操作UI 理解二
  4. C#使用基类的引用 and 虚方法和覆写方法
  5. 函数fil_extend_space_to_desired_size
  6. (转)PHP数组的总结(很全面啊)
  7. HTML7常用的类型刮刮乐 光棒效果
  8. P1282 多米诺骨牌
  9. include
  10. day1作业(格式化输出)
  11. python 列表元组加减乘除法
  12. linux Shell 脚本编写
  13. wince程序调用另外一个wince exe程序?
  14. spring 注解列表
  15. java实现Kafka的消费者示例
  16. poj 3164
  17. nodejs实现拉钩网爬虫
  18. 《Python数据分析》-Ch01 Python 程序库入门
  19. luogu P1029 最大公约数和最小公倍数问题
  20. 【洛谷 P1502】 窗口的星星(扫描线)

热门文章

  1. Linux命令_搜索文件
  2. Spring JDBC SqlQuery类示例
  3. Spring JDBC处理BLOB类型字段
  4. (转)android系统架构及源码目录结构
  5. svn -- svn配置自动启动
  6. Bind 和 ScaffoldColumn
  7. js 形如 &quot;1,2,3&quot;的操作
  8. Linux环境下Redis安装配置步骤[转]
  9. js中的坑
  10. sqlserver修改主机名