Windows 核心编程研究系列之二:读取指定物理内存地址中的内容

 

[原创/讨论]Windows核心编程研究系列之二:

读取指定物理内存地址中的内容

关键字:windows内核,物理内存

 

大家知道在windows NT中,如果已知虚拟地址可以通过

进程页表或者内核提供的MmGetPhysicalAddress来取得对应的

物理地址。

现在我们反过来看一下,如果已知一个物理内存地 址 (假设地址有效),如何取得物理地址中的内容呢?经过一番思考后,我发现一个方法,但我这里先卖个关子,为什么呢?

因为我觉得这个方法有些繁琐。经过和csdn的各位高手讨论

之后,加上本人的一共总结出3种方法,现在想和大家分享一下,

下面不再废话,立即进入正题J

 

[方法1]:使用内核提供的MmMapIoSpace函数

原来内核早就提供了很简单的接口,就是MmMapIoSpace函数,不过在DDK文档中看到该函数的说明如下:

 

PVOID MmMapIoSpace(

   IN PHYSICAL_ADDRESS  PhysicalAddress,

   IN ULONG  NumberOfBytes,

IN MEMORY_CACHING_TYPE CacheType );

 

它只有3个形参,这和实际Masm32v9.0声明的4个形参有所

不同,为了确定,我通过 IDA反汇编证实该函数确实有4个形参。经过测试,我猜测中间的ULONG NumberOfBytes参数实际由64位字节的(两个双字)高低两部分组成,即在Masm32v9.0中有:

 

PVOID MmMapIoSpace(

   IN PHYSICAL_ADDRESS  PhysicalAddress,

IN ULONG NumberOfBytesHighPart,

IN ULONG NumberOfBytesLowPart,

IN MEMORY_CACHING_TYPE CacheType );

 

这样的话调用就很简单了:

invoke  MmMapIoSpace,_phyaddr,0,4,MmNonCached

若成功该函数返回影射后的虚拟地址,否则返回NULL。这样就可以间接达到读取物理地址中内容的第一个方法。但可能我对参数功能的猜测也有错误,如果有误请指出。不胜感谢。

 

 

[方法2]:通过操作物理内存对象来完成到虚拟地址的影射

简单的说这个方法的基本思想如下:

 

a :取得本进程句柄;

b :取得物理内存对象的句柄;

c :将物理内存地址影射到进程的虚拟地址空间中。

 

下面是我从C改写后的汇编关键代码:

 

;***************************************************************************

GetValByPhyAddr  proc uses esi edi ebx _phyaddr

     local   hpmem:dword

     local   hp:dword

     local   dwsize:dword

     local   dwbase:dword

     local   dwret:dword

     local   cid:CLIENT_ID

     local   stLR:LARGE_INTEGER

     local   ObjAttrsMem:OBJECT_ATTRIBUTES

     local   ObjAttrsP:OBJECT_ATTRIBUTES

 

     mov    dwbase,0

     mov    dwsize,4

     push   _phyaddr

     pop     stLR.LowPart

     mov    stLR.HighPart,0

 

     invoke PsGetCurrentProcessId

     mov    cid.UniqueProcess,eax

     mov    cid.UniqueThread,0

 

     invoke InitObjAttrs,addr ObjAttrsP,0

     ;取得本进程句柄

     Invoke      ZwOpenProcess,addr hp,PROCESS_DUP_HANDLE,/

                 addr ObjAttrsP,addr cid

 

     invoke InitObjAttrs,addr ObjAttrsMem,addr devphymem

 

     ;取得物理对象的句柄

     Invoke     ZwOpenSection,addr hpmem,/

                     SECTION_MAP_READ or SECTION_MAP_WRITE,/

                     addr ObjAttrsMem

 

     ;将物理内存地址影射到进程的虚拟地址空间中

     

 

 

invoke      ZwMapViewOfSection,hpmem,hp,addr dwbase,/

                     0,4,addr stLR,addr dwsize,1,/

                     MEM_TOP_DOWN,PAGE_READWRITE

 

mov   edx,dwbase

     mov    eax,[edx]  ;取得影射后对应虚拟地址中的内容

     mov    dwret,eax

 

     ;释放资源

     invoke ZwUnmapViewOfSection,hp,addr dwbase

     invoke ZwClose,hpmem

     invoke ZwClose,hp

 

     mov    eax,dwret

     ret

GetValByPhyAddr  endp

;***************************************************************************

 

 

[方法3]:关闭CPU分页机制达到直接读取物理地址的目的

 

 

3种方法就是我前面卖关子的方法J ,这种方法不依赖于OS,了解保护模式的朋友都知道:进入保护模式后如果关闭分页机制则CPU就会将线形地址直接解释成物理地址。

 

关闭分页很简单,只需3行汇编代码:

 

mov    eax,cr0

and    eax,7fffffffh

mov    cr0,eax

 

打开分页同样很简单:

 

mov eax,cr0

oreax,80000000h  

mov cr0,eax

 

既然这么简单,为什么我在前面说这个方法有些繁琐呢?原因之一是如果仅仅关闭分页就会使原来依赖于分页的windows变得一团糟,马上会发生著名的蓝屏现象,不爽哦!L

那要怎么做呢?经过我的尝试,发现有3点非常重要:

 

 

1 :要保证windows处在较高的IRQL级别,防止关闭分页后

       被打断;

2 :要将处理分页、读取物理地址的指令块放到线形地址等于

       物理地址的页中;

3 :要做好数据的恢复工作。

 

1点和第3点都容易理解,可能有人会疑问第2点,为什么要放到线形地址等于物理地址的空间中呢?原因其实也很简单,就是一旦关闭分页,所有地址都将以物理地址来解释,如果先前的

虚拟地址和物理地址不相同那么很可能就会发生执行错误指令流的问题。(当然也可以不相同,只要你能保证关分页后能执行正确指令即可。)

 

那么如何将物理地址和虚拟地址设置为相同呢?我的方法是将虚拟地址对应地页表PTE中的物理页基址改为包容即可。

我随机选择了一个虚拟地址 0x1c00000 ,为了简单它正好是页面的一个基址。下面我构造了它的PTE使得其中的物理也帧指向它:

 

mov   edx,0ffdf0000h

     mov    eax,[edx]

     mov    tmp0,eax            ; 保存原始值

     ;mov   dword ptr [edx],1c00027h

     mov    dword ptr [edx],1c00007h

 

     ;初始化PTE

     mov    edx,0c03ff7c0h

     mov    eax,[edx]

     and     eax,0fffff000h

     or  eax,7h

     mov    edx,0c030001ch

     mov    ebx,[edx]

     mov    tmp1,ebx                  ; 保存原始值

     mov    dword ptr [edx],eax

     ;刷新TLB

     mov    eax,cr3

     mov    cr3,eax

 

           最后两句指令在我前一篇: <<Windows内核编程研究一:改变进程PTE>>中有过说明,这里就不解释了。

 

结束了PTE的设置后,下面就是将指令块拷贝到指定位置,代码如下:

 

mov   ecx,slen

mov   esi,subasm

mov   edi,1c00000h

cld

rep           movsb

 

subasm指令块完成的就是实际设置分页和读取物理地址内容的工作,如下:

 

subasm:

           mov     eax,cr0

           and     eax,7fffffffh               ;关闭分页机制

           mov     cr0,eax    

           jmp     SHORT PageC

PageC:

           mov    edx,[ecx]

 

mov   eax,cr0

           or  eax,80000000h            ;打开分页机制

           mov    cr0,eax

          

jmp    SHORT PageO

PageO:

          

jmp    edi

slen   =   $ - subasm

 

 

借用一句话作为结尾“一切都那么清楚明白,然而却无法透彻理解。理解就是改变,就是超越自己已有的认识。”

感谢CSDN热心朋友提供的方法和意见,给我很好的启发。

       末尾,感谢各位观赏,如有误谬之处请不吝指出,不胜感谢。希望有同好的朋友能够一起讨论,一起交流。

 

Email:  19796174@qq.com

QQ    19796174

 

最后“预告”一下我的第3篇文章:

<<Windows核心编程研究系列之三:突破windows共享文件打开限制>>

 

主要内容:

很多用No_Share打开的文件,正常情况下不能在其他进程用

CreateFile打开,或者是仅仅用FILE_SHARE_READ打开的

文件不能再以写入目而打开,那么如何绕过windows的这种保护呢?敬请期待。J

 

感谢观赏,拜拜!

 

 

Mydo(侯佩|hopy|福信克|FreeThink)

20061219晚于合肥温馨的家中J

 

上一篇:MySQL8.0 - 新特性 - Instant Add Column


下一篇:[原创/讨论][windows核心编程一外传]关于访问虚拟地址0的方法。